#!/usr/bin/env php
<?php

declare(strict_types=1);

// echo "James says hello :)\n";

// Workaround for https://github.com/php/pie/issues/537 and https://github.com/box-project/box/issues/1577
error_reporting(error_reporting() & ~E_DEPRECATED);

Phar::mapPhar('pie.phar');

require 'phar://pie.phar/.box/bin/check-requirements.php';

$_SERVER['SCRIPT_FILENAME'] = 'phar://pie.phar/bin/pie';
require 'phar://pie.phar/bin/pie';

// phpcs:ignore Generic.Files.InlineHTML.Found
__HALT_COMPILER(); ?>
W               "   resources/pie-settings-schema.jsonA      A        +   resources/composer-json-php-ext-schema.json        nbA         resources/spc/build.sh        Ǥ         resources/spc/craft.ymlz       z   bӤ         resources/spc/spc.Dockerfile          55Ƥ      )   .box/src/IsExtensionConflictFulfilled.php        <1m         .box/src/Checker.php        txȲ         .box/src/IsFulfilled.php          z      "   .box/src/RequirementCollection.php        b         .box/src/Terminal.php        h(      "   .box/src/IsPhpVersionFulfilled.php        aN         .box/src/Printer.php        j)         .box/src/Requirement.php        l#3ۤ         .box/src/IO.php        ?      !   .box/src/IsExtensionFulfilled.php        CǤ         .box/bin/check-requirements.php        !         .box/.requirements.php          Z         .box/vendor/autoload.php        DI٤      $   .box/vendor/composer/ClassLoader.php?      ?  2@u      ,   .box/vendor/composer/autoload_namespaces.php          /t      &   .box/vendor/composer/autoload_psr4.php,      ,  ۤ         .box/vendor/composer/LICENSE.      .         (   .box/vendor/composer/autoload_static.php        DN      "   .box/vendor/composer/installed.php,      ,  !      &   .box/vendor/composer/autoload_real.php$      $  /'Վ      *   .box/vendor/composer/autoload_classmap.php
      
  h      #   .box/vendor/composer/semver/LICENSE        Bh      .   .box/vendor/composer/semver/src/Comparator.php        #      ,   .box/vendor/composer/semver/src/Interval.php        r}      *   .box/vendor/composer/semver/src/Semver.php              9   .box/vendor/composer/semver/src/Constraint/Constraint.php&      &  S      4   .box/vendor/composer/semver/src/Constraint/Bound.phpm      m  ɤ      A   .box/vendor/composer/semver/src/Constraint/MatchAllConstraint.php<      <  r      >   .box/vendor/composer/semver/src/Constraint/MultiConstraint.phpT      T  Z6      B   .box/vendor/composer/semver/src/Constraint/ConstraintInterface.php        LKJ      B   .box/vendor/composer/semver/src/Constraint/MatchNoneConstraint.phpZ      Z  %(      -   .box/vendor/composer/semver/src/Intervals.php9      9  """      1   .box/vendor/composer/semver/src/VersionParser.php<      <  	:      4   .box/vendor/composer/semver/src/CompilingMatcher.php        g      *   .box/vendor/composer/InstalledVersions.php          >         src/Util/Emoji.php        rA,          src/Util/PieVersion.php	      	  lL      +   src/Util/ProcessFailedWithLimitedOutput.php         V      &   src/Util/OutputFormatterWithPrefix.php        O         src/Util/Process.php        BM*         src/Util/FileNotFound.phpP      P  d      &   src/Util/PackageVerificationStatus.php        rX         src/Building/Build.phpQ      Q  7:         src/Building/UnixBuild.php7&      7&  Bפ         src/Building/WindowsBuild.php)      )  Qu֤      (   src/Building/ExtensionBinaryNotFound.php}      }        $   src/Building/PlaceholderReplacer.php8      8  JzƤ      -   src/DependencyResolver/DependencyResolver.php(      (  L:z      8   src/DependencyResolver/ResolveDependencyWithComposer.php        XĤ      +   src/DependencyResolver/DependencyStatus.php4      4  u&       2   src/DependencyResolver/FetchDependencyStatuses.php        t3դ      5   src/DependencyResolver/UnableToResolveRequirement.phpO      O  Y-'      4   src/DependencyResolver/DetermineMinimumStability.php        :      H   src/DependencyResolver/DependencyInstaller/PrescanSystemDependencies.php(      (  L(      K   src/DependencyResolver/DependencyInstaller/SystemDependenciesDefinition.php	      	  ϩ      5   src/DependencyResolver/RequestedPackageAndVersion.php        ݦ      5   src/DependencyResolver/BundledPhpExtensionRefusal.phpR      R        7   src/DependencyResolver/IncompatibleThreadSafetyMode.php        +Ƥ      "   src/DependencyResolver/Package.php$*      $*  Rz      -   src/DependencyResolver/InvalidPackageName.php        !S      <   src/DependencyResolver/IncompatibleOperatingSystemFamily.phpO      O  'z      '   src/Installing/UninstallUsingUnlink.php        "         src/Installing/Install.php	      	           src/Installing/Uninstall.php        U땰      !   src/Installing/WindowsInstall.php}"      }"  7C      *   src/Installing/FailedToRemoveExtension.php        OĤ      A   src/Installing/InstallForPhpProject/InstallPiePackageFromPath.php        Nrk'      A   src/Installing/InstallForPhpProject/ComposerFactoryForProject.php!      !  7Fy      <   src/Installing/InstallForPhpProject/FindMatchingPackages.php        ?      C   src/Installing/InstallForPhpProject/DetermineExtensionsRequired.phpG      G  艥      >   src/Installing/InstallForPhpProject/InstallSelectedPackage.php        E٤         src/Installing/SetupIniFile.php[      [  .פ      )   src/Installing/PackageMetadataMissing.phpe      e  "zԤ      8   src/Installing/Ini/RemoveIniEntryWithFileGetContents.php|      |   O      8   src/Installing/Ini/StandardAdditionalPhpIniDirectory.php        PъC      5   src/Installing/Ini/PreCheckExtensionAlreadyLoaded.php        ;      %   src/Installing/Ini/OndrejPhpenmod.php        C      /   src/Installing/Ini/AddExtensionToTheIniFile.php        *j      )   src/Installing/Ini/DockerPhpExtEnable.php
      
  ΍Ӥ      +   src/Installing/Ini/StandardSinglePhpIni.php        ݛ      /   src/Installing/Ini/PickBestSetupIniApproach.php	      	  3C      8   src/Installing/Ini/CheckAndAddExtensionToIniIfNeeded.php4
      4
  M      '   src/Installing/Ini/SetupIniApproach.php{      {  bѦ      5   src/Installing/Ini/IsExtensionAlreadyInTheIniFile.php        S>4      %   src/Installing/Ini/RemoveIniEntry.php        .         src/Installing/UnixInstall.php        af         src/File/FullPathToSelf.php        kHf         src/File/SudoUnlink.php        _Rܤ         src/File/FailedToCreateFile.php        d         src/File/SudoFilePut.php        S|         src/File/FailedToWriteFile.php7      7  .y         src/File/BinaryFile.php        ߢ         src/File/Sudo.php        Hj*      !   src/File/SudoNotFoundOnSystem.php        ?K5         src/File/WindowsDelete.phpX      X  <q         src/File/FailedToUnlinkFile.php        tt\      )   src/File/BinaryFileFailedVerification.php9      9  Q      ,   src/File/SudoRequiresInteractiveTerminal.php        hA         src/File/SudoCreate.php        d`         src/ExtensionType.php        j>      2   src/Command/InstallExtensionsForProjectCommand.phpI1      I1  J.         src/Command/DownloadCommand.phpk      k  L      %   src/Command/RepositoryListCommand.php        '      '   src/Command/RepositoryRemoveCommand.php	      	  cˤ         src/Command/InfoCommand.php2      2  n:u         src/Command/ArgvInput.php        y*$         src/Command/BuildCommand.php        	      $   src/Command/RepositoryAddCommand.php%      %  ٤          src/Command/InvokeSubCommand.php        B         src/Command/InstallCommand.php#      #           src/Command/ShowCommand.php          j)         src/Command/CommandHelper.phpQ      Q  7      !   src/Command/SelfVerifyCommand.php        j~7          src/Command/UninstallCommand.phpL      L  *ڤ      !   src/Command/SelfUpdateCommand.phpH!      H!  m$         src/Container.php#      #  "         src/Platform.phpo      o  T
         src/Settings.php
      
  tǈ         src/ConfigureOption.phpm      m  |X٤      3   src/SelfManage/BuildTools/PhpizeBuildToolFinder.php        &uݤ      0   src/SelfManage/BuildTools/CheckAllBuildTools.php        C4      3   src/SelfManage/BuildTools/BinaryBuildToolFinder.php        џ      )   src/SelfManage/Update/ReleaseMetadata.php        Ф      !   src/SelfManage/Update/Channel.php1      1  },      3   src/SelfManage/Update/FetchPieReleaseFromGitHub.phpb      b  waV      )   src/SelfManage/Update/FetchPieRelease.phpJ      J  Q䇖      (   src/SelfManage/Update/ReleaseIsNewer.php9      9  8      9   src/SelfManage/Update/PiePharMissingFromLatestRelease.php              /   src/SelfManage/Verify/GithubCliNotAvailable.php        Cڤ      :   src/SelfManage/Verify/VerifyPieReleaseUsingAttestation.php        ڂ       :   src/SelfManage/Verify/GithubCliAttestationVerification.phpa      a  a      '   src/SelfManage/Verify/VerifyPiePhar.php        #      %   src/SelfManage/Verify/Attestation.php/      /  .ڤ      /   src/SelfManage/Verify/FailedToVerifyRelease.php        B^դ      :   src/SelfManage/Verify/FallbackVerificationUsingOpenSsl.php!      !  14Ҥ      (   src/Downloading/PackageReleaseAssets.php              %   src/Downloading/DownloadedPackage.php              .   src/Downloading/GithubPackageReleaseAssets.php9      9  M>      %   src/Downloading/DownloadUrlMethod.php          +x      6   src/Downloading/Exception/CouldNotFindReleaseAsset.php	      	  ,mD      :   src/ComposerIntegration/BundledPhpExtensionsRepository.php'      '  e7      @   src/ComposerIntegration/PhpBinaryPathBasedPlatformRepository.phpw      w  gU      2   src/ComposerIntegration/InstallAndBuildProcess.php
      
  G2      0   src/ComposerIntegration/PieComposerInstaller.phpr      r  .      4   src/ComposerIntegration/AddInstalledJsonMetadata.php        ut      (   src/ComposerIntegration/PieOperation.php        *̤      .   src/ComposerIntegration/PieComposerFactory.php        %k      ,   src/ComposerIntegration/MinimalHelperSet.php        =z      -   src/ComposerIntegration/ComposerRunFailed.php        ೊ      1   src/ComposerIntegration/InstalledJsonMetadata.php              F   src/ComposerIntegration/Listeners/RemoveUnrelatedInstallOperations.php        V{1      H   src/ComposerIntegration/Listeners/OverrideDownloadUrlInstallListener.phpY      Y        H   src/ComposerIntegration/Listeners/CouldNotDetermineDownloadUrlMethod.php9      9  o.      6   src/ComposerIntegration/ComposerIntegrationHandler.php)!      )!  V      )   src/ComposerIntegration/PieJsonEditor.php        J      2   src/ComposerIntegration/VersionSelectorFactory.php        :k      ,   src/ComposerIntegration/UninstallProcess.php              .   src/ComposerIntegration/PieComposerRequest.php:      :  *s      ,   src/ComposerIntegration/QuieterConsoleIO.php        h      )   src/ComposerIntegration/VendorCleanup.php@
      @
  ݤ      /   src/ComposerIntegration/PiePackageInstaller.php        O         src/ExtensionName.php&      &  eR6      *   src/Platform/WindowsExtensionAssetName.phpS      S  r/      %   src/Platform/InstalledPiePackages.php
      
  ip"         src/Platform/Architecture.php        U	      +   src/Platform/PrePackagedSourceAssetName.php_      _  *         src/Platform/TargetPlatform.php        Bj      +   src/Platform/PrePackagedBinaryAssetName.phpe
      e
           src/Platform/PiePackageList.php        T±         src/Platform/DebugBuild.php        I      !   src/Platform/ThreadSafetyMode.php                  src/Platform/LibcFlavour.php        q:         src/Platform/PackageManager.php        X      9   src/Platform/TargetPhp/Exception/ExtensionPathProblem.php        c\`      9   src/Platform/TargetPhp/Exception/InvalidPhpBinaryPath.php`      `        9   src/Platform/TargetPhp/Exception/ExtensionIsNotLoaded.php6      6  9b,      (   src/Platform/TargetPhp/PhpBinaryPath.phpF      F   T/      %   src/Platform/TargetPhp/PhpizePath.php	      	        &   src/Platform/OperatingSystemFamily.php        +'9פ          src/Platform/WindowsCompiler.phpD      D  ߖ          src/Platform/OperatingSystem.php        [mǤ         bin/pie        L      C   vendor/fidry/cpu-core-counter/src/Finder/OnlyInPowerShellFinder.php\      \  J̤      C   vendor/fidry/cpu-core-counter/src/Finder/CmiCmdletLogicalFinder.php        t[#      <   vendor/fidry/cpu-core-counter/src/Finder/HwLogicalFinder.phpc      c  l      A   vendor/fidry/cpu-core-counter/src/Finder/SkipOnOSFamilyFinder.php	      	  })      ?   vendor/fidry/cpu-core-counter/src/Finder/WmicPhysicalFinder.php        jhS      ;   vendor/fidry/cpu-core-counter/src/Finder/FinderRegistry.phpB
      B
  %q      >   vendor/fidry/cpu-core-counter/src/Finder/_NProcessorFinder.php        8      >   vendor/fidry/cpu-core-counter/src/Finder/NullCpuCoreFinder.php              @   vendor/fidry/cpu-core-counter/src/Finder/ProcOpenBasedFinder.php}
      }
  @Pq      :   vendor/fidry/cpu-core-counter/src/Finder/CpuCoreFinder.php        c#      ?   vendor/fidry/cpu-core-counter/src/Finder/LscpuLogicalFinder.php=      =  7k      A   vendor/fidry/cpu-core-counter/src/Finder/OnlyOnOSFamilyFinder.php	      	  *Ue      >   vendor/fidry/cpu-core-counter/src/Finder/EnvVariableFinder.php        ͈[e      @   vendor/fidry/cpu-core-counter/src/Finder/LscpuPhysicalFinder.php        1V;      D   vendor/fidry/cpu-core-counter/src/Finder/CmiCmdletPhysicalFinder.php        q      8   vendor/fidry/cpu-core-counter/src/Finder/NProcFinder.php          cM      :   vendor/fidry/cpu-core-counter/src/Finder/CpuInfoFinder.php	      	  z      ?   vendor/fidry/cpu-core-counter/src/Finder/DummyCpuCoreFinder.php        3      >   vendor/fidry/cpu-core-counter/src/Finder/WmicLogicalFinder.php        "b      =   vendor/fidry/cpu-core-counter/src/Finder/NProcessorFinder.php        s      =   vendor/fidry/cpu-core-counter/src/Finder/HwPhysicalFinder.phpg      g  GȤ      I   vendor/fidry/cpu-core-counter/src/Finder/WindowsRegistryLogicalFinder.php        fY      /   vendor/fidry/cpu-core-counter/src/Diagnoser.php        H3ԇ      4   vendor/fidry/cpu-core-counter/src/CpuCoreCounter.php#      #  mX      =   vendor/fidry/cpu-core-counter/src/NumberOfCpuCoreNotFound.php(      (  J      ;   vendor/fidry/cpu-core-counter/src/ParallelisationResult.php        BJפ      >   vendor/fidry/cpu-core-counter/src/Executor/ProcessExecutor.php        KS      ?   vendor/fidry/cpu-core-counter/src/Executor/ProcOpenExecutor.php/      /  ^1 d      -   vendor/fidry/cpu-core-counter/bin/execute.php        <=      +   vendor/fidry/cpu-core-counter/bin/trace.php        M$      .   vendor/fidry/cpu-core-counter/bin/diagnose.php)      )  n      -   vendor/symfony/polyfill-intl-grapheme/LICENSE,      ,  H      5   vendor/symfony/polyfill-intl-grapheme/bootstrap80.phpB      B  `-      3   vendor/symfony/polyfill-intl-grapheme/bootstrap.php8      8  m      2   vendor/symfony/polyfill-intl-grapheme/Grapheme.php1      1  j1         vendor/symfony/finder/LICENSE,      ,  U          vendor/symfony/finder/Finder.php^      ^  Hmw      #   vendor/symfony/finder/Gitignore.php        k'         vendor/symfony/finder/Glob.php        m+G      >   vendor/symfony/finder/Exception/DirectoryNotFoundException.php        RI      9   vendor/symfony/finder/Exception/AccessDeniedException.php        cWޤ      %   vendor/symfony/finder/SplFileInfo.php        }      /   vendor/symfony/finder/Comparator/Comparator.php        2TG      5   vendor/symfony/finder/Comparator/NumberComparator.php
      
  hMLۤ      3   vendor/symfony/finder/Comparator/DateComparator.php        Y      A   vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php        f1      ;   vendor/symfony/finder/Iterator/DepthRangeFilterIterator.phpb      b  ,      5   vendor/symfony/finder/Iterator/PathFilterIterator.php        dސ{      /   vendor/symfony/finder/Iterator/LazyIterator.php        -9      9   vendor/symfony/finder/Iterator/FileTypeFilterIterator.phpo      o  "f      :   vendor/symfony/finder/Iterator/DateRangeFilterIterator.phpy      y        =   vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php(      (   襤      7   vendor/symfony/finder/Iterator/CustomFilterIterator.php        #      ;   vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.phpn      n  كt      =   vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php        D      :   vendor/symfony/finder/Iterator/SizeRangeFilterIterator.phpP      P  gɤ      9   vendor/symfony/finder/Iterator/FilenameFilterIterator.phpQ      Q        3   vendor/symfony/finder/Iterator/SortableIterator.php        澺      <   vendor/symfony/finder/Iterator/FilecontentFilterIterator.php        ~       6   vendor/symfony/polyfill-php84/Resources/Deprecated.php        ^6      8   vendor/symfony/polyfill-php84/Resources/RoundingMode.php              <   vendor/symfony/polyfill-php84/Resources/stubs/Deprecated.phpD      D  Ž      D   vendor/symfony/polyfill-php84/Resources/stubs/ReflectionConstant.php        Է      >   vendor/symfony/polyfill-php84/Resources/stubs/RoundingMode.php\      \  <H      ;   vendor/symfony/polyfill-php84/Resources/stubs/Pdo/Pgsql.phpO      O  tj      :   vendor/symfony/polyfill-php84/Resources/stubs/Pdo/Odbc.php        }      ;   vendor/symfony/polyfill-php84/Resources/stubs/Pdo/Dblib.php        l8      >   vendor/symfony/polyfill-php84/Resources/stubs/Pdo/Firebird.phpR      R  ?f      ;   vendor/symfony/polyfill-php84/Resources/stubs/Pdo/Mysql.php        Q=/      <   vendor/symfony/polyfill-php84/Resources/stubs/Pdo/Sqlite.php
      
  @&      %   vendor/symfony/polyfill-php84/LICENSE,      ,         -   vendor/symfony/polyfill-php84/bootstrap82.php        Y a      -   vendor/symfony/polyfill-php84/bootstrap80.php        -      '   vendor/symfony/polyfill-php84/Php84.phpv=      v=  [CT      +   vendor/symfony/polyfill-php84/bootstrap.php        \      '   vendor/symfony/process/ProcessUtils.php        0         vendor/symfony/process/LICENSE,      ,  U      +   vendor/symfony/process/ExecutableFinder.php        [bk      &   vendor/symfony/process/InputStream.php	      	  HgK      "   vendor/symfony/process/Process.phpi      i  6!=}      7   vendor/symfony/process/Exception/ExceptionInterface.php        +      5   vendor/symfony/process/Exception/RuntimeException.php        >H      3   vendor/symfony/process/Exception/LogicException.php        W      =   vendor/symfony/process/Exception/ProcessTimedOutException.php        j#a      =   vendor/symfony/process/Exception/ProcessSignaledException.php        4      =   vendor/symfony/process/Exception/InvalidArgumentException.php        ˅      ;   vendor/symfony/process/Exception/ProcessFailedException.php        Y'      >   vendor/symfony/process/Exception/RunProcessFailedException.php        U-.      =   vendor/symfony/process/Messenger/RunProcessMessageHandler.php        ^|Ĥ      6   vendor/symfony/process/Messenger/RunProcessMessage.php        Q      6   vendor/symfony/process/Messenger/RunProcessContext.phpt      t  䌻p      -   vendor/symfony/process/Pipes/WindowsPipes.php        74      .   vendor/symfony/process/Pipes/AbstractPipes.php              *   vendor/symfony/process/Pipes/UnixPipes.php5      5  WŤ      /   vendor/symfony/process/Pipes/PipesInterface.php        \	      (   vendor/symfony/process/PhpSubprocess.php        gsg      %   vendor/symfony/process/PhpProcess.php	      	        .   vendor/symfony/process/PhpExecutableFinder.php	      	  6˼      '   vendor/symfony/event-dispatcher/LICENSE,      ,  U      <   vendor/symfony/event-dispatcher/EventSubscriberInterface.php        s      <   vendor/symfony/event-dispatcher/EventDispatcherInterface.php        a(\      9   vendor/symfony/event-dispatcher/Debug/WrappedListener.php        yG      B   vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.phpg7      g7  Й      <   vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php}      }   Ǥ      3   vendor/symfony/event-dispatcher/EventDispatcher.php'#      '#  IJ      0   vendor/symfony/event-dispatcher/GenericEvent.php        m      =   vendor/symfony/event-dispatcher/Attribute/AsEventListener.php        U      M   vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php!      !  OͰ      K   vendor/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php{      {  פ      $   vendor/symfony/string/ByteString.php>9      >9        -   vendor/symfony/string/Resources/functions.phpT      T  0      <   vendor/symfony/string/Resources/data/wcswidth_table_wide.php2      2  ?Y      <   vendor/symfony/string/Resources/data/wcswidth_table_zero.php]<      ]<  ?p      2   vendor/symfony/string/Slugger/SluggerInterface.php        ^      .   vendor/symfony/string/Slugger/AsciiSlugger.php        IVm         vendor/symfony/string/LICENSE,      ,  զ_Ϥ      )   vendor/symfony/string/CodePointString.php(      (  M      (   vendor/symfony/string/AbstractString.phpI      I  ,	      '   vendor/symfony/string/UnicodeString.phpH5      H5  %ä      /   vendor/symfony/string/AbstractUnicodeString.phph      h  &U      6   vendor/symfony/string/Exception/ExceptionInterface.phpQ      Q  $դ      4   vendor/symfony/string/Exception/RuntimeException.phpp      p  0ʤ      <   vendor/symfony/string/Exception/InvalidArgumentException.php        e      $   vendor/symfony/string/LazyString.phpo      o  jݤ      6   vendor/symfony/string/Inflector/InflectorInterface.phpC      C  Qcc      3   vendor/symfony/string/Inflector/FrenchInflector.phpK      K  n@      4   vendor/symfony/string/Inflector/EnglishInflector.phphF      hF  {	      %   vendor/symfony/polyfill-ctype/LICENSE,      ,        '   vendor/symfony/polyfill-ctype/Ctype.php        8      -   vendor/symfony/polyfill-ctype/bootstrap80.phpr      r  F)      +   vendor/symfony/polyfill-ctype/bootstrap.php@      @  jQ9      <   vendor/symfony/service-contracts/Test/ServiceLocatorTest.php~      ~  Âs      @   vendor/symfony/service-contracts/Test/ServiceLocatorTestCase.php        p>      ;   vendor/symfony/service-contracts/ServiceSubscriberTrait.php        r      (   vendor/symfony/service-contracts/LICENSE,      ,        B   vendor/symfony/service-contracts/ServiceMethodsSubscriberTrait.phpM      M  jĤ      3   vendor/symfony/service-contracts/ResetInterface.php        񁒳      <   vendor/symfony/service-contracts/ContainerAwareInterface.php        ?>ۤ      ?   vendor/symfony/service-contracts/ServiceCollectionInterface.phpr      r  K0      ?   vendor/symfony/service-contracts/ServiceSubscriberInterface.php
      
  Ŵ      =   vendor/symfony/service-contracts/ServiceProviderInterface.phpf      f  Τ      @   vendor/symfony/service-contracts/Attribute/SubscribedService.php        Xѓ      7   vendor/symfony/service-contracts/Attribute/Required.php        e;Z      8   vendor/symfony/service-contracts/ServiceLocatorTrait.php        v|      1   vendor/symfony/deprecation-contracts/function.php        Oݤ      ,   vendor/symfony/deprecation-contracts/LICENSE,      ,  K      "   vendor/symfony/filesystem/Path.phpf      f  _+      !   vendor/symfony/filesystem/LICENSE,      ,  U      (   vendor/symfony/filesystem/Filesystem.phpu      u  mI      :   vendor/symfony/filesystem/Exception/ExceptionInterface.php        nj      8   vendor/symfony/filesystem/Exception/RuntimeException.php        iҤ      =   vendor/symfony/filesystem/Exception/FileNotFoundException.php        <\P      <   vendor/symfony/filesystem/Exception/IOExceptionInterface.php        '~i      3   vendor/symfony/filesystem/Exception/IOException.php              @   vendor/symfony/filesystem/Exception/InvalidArgumentException.php        *      @   vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php_      _  d      B   vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.phpa	      a	  |ⳤ      @   vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.phpf      f  P      F   vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php9      9  >|zK      (   vendor/symfony/polyfill-mbstring/LICENSE,      ,  H      -   vendor/symfony/polyfill-mbstring/Mbstring.phpn      n  <      0   vendor/symfony/polyfill-mbstring/bootstrap80.php'      '  	~
      .   vendor/symfony/polyfill-mbstring/bootstrap.php          Nc)      1   vendor/symfony/event-dispatcher-contracts/LICENSE,      ,        F   vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php        A      3   vendor/symfony/event-dispatcher-contracts/Event.php        с@      X   vendor/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.phpo     o c,      L   vendor/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.phpD5      D5        R   vendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalComposition.phpD      D  'CԤ      T   vendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php{      {  je      F   vendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php        %      /   vendor/symfony/polyfill-intl-normalizer/LICENSE,      ,  H      7   vendor/symfony/polyfill-intl-normalizer/bootstrap80.php        ,      5   vendor/symfony/polyfill-intl-normalizer/bootstrap.php        #p      6   vendor/symfony/polyfill-intl-normalizer/Normalizer.phpd%      d%  u      0   vendor/symfony/console/Resources/completion.fish        ܾ      /   vendor/symfony/console/Resources/completion.zsh              4   vendor/symfony/console/Resources/bin/hiddeninput.exe $       $  v      0   vendor/symfony/console/Resources/completion.bash        zߤ      2   vendor/symfony/console/Helper/DescriptorHelper.phpN
      N
  D'TB      0   vendor/symfony/console/Helper/TableSeparator.php        &      '   vendor/symfony/console/Helper/Table.phpg      g  F¤      2   vendor/symfony/console/Helper/InputAwareHelper.php              0   vendor/symfony/console/Helper/TableCellStyle.php        c      6   vendor/symfony/console/Helper/DebugFormatterHelper.php          3      3   vendor/symfony/console/Helper/ProgressIndicator.phph      h        5   vendor/symfony/console/Helper/TerminalInputHelper.phpt      t  S      0   vendor/symfony/console/Helper/QuestionHelper.phpN      N  ]k֤      /   vendor/symfony/console/Helper/ProcessHelper.php        6      1   vendor/symfony/console/Helper/FormatterHelper.php        FN      (   vendor/symfony/console/Helper/Helper.phps      s  !q      1   vendor/symfony/console/Helper/HelperInterface.phpI      I  @Ɔ      -   vendor/symfony/console/Helper/ProgressBar.phpFO      FO  ʤ      (   vendor/symfony/console/Helper/Dumper.php-      -  ͤ      +   vendor/symfony/console/Helper/TableRows.phpN      N  $      +   vendor/symfony/console/Helper/TableCell.php        c'      /   vendor/symfony/console/Helper/OutputWrapper.php        Ro      ,   vendor/symfony/console/Helper/TableStyle.php1      1  (      +   vendor/symfony/console/Helper/HelperSet.phpK      K  U>ߤ      7   vendor/symfony/console/Helper/SymfonyQuestionHelper.php        9ܤ         vendor/symfony/console/LICENSE,      ,  U      ,   vendor/symfony/console/Style/OutputStyle.phps      s  K8L      /   vendor/symfony/console/Style/StyleInterface.php
      
  $      -   vendor/symfony/console/Style/SymfonyStyle.php;      ;  m̤      !   vendor/symfony/console/Cursor.php        u      3   vendor/symfony/console/SignalRegistry/SignalMap.php        V      8   vendor/symfony/console/SignalRegistry/SignalRegistry.phpa      a  )      0   vendor/symfony/console/Completion/Suggestion.php<      <  <Mؖ      ;   vendor/symfony/console/Completion/CompletionSuggestions.phpF      F  $*      A   vendor/symfony/console/Completion/Output/BashCompletionOutput.php        S      @   vendor/symfony/console/Completion/Output/ZshCompletionOutput.php        yZ1      A   vendor/symfony/console/Completion/Output/FishCompletionOutput.php        +Ǥ      F   vendor/symfony/console/Completion/Output/CompletionOutputInterface.php        3O      5   vendor/symfony/console/Completion/CompletionInput.php        p,9          vendor/symfony/console/Color.php        .{j      2   vendor/symfony/console/Command/CompleteCommand.php$      $  4      *   vendor/symfony/console/Command/Command.phpY      Y  +:C      .   vendor/symfony/console/Command/ListCommand.php
      
  '      0   vendor/symfony/console/Command/LockableTrait.php        1      =   vendor/symfony/console/Command/SignalableCommandInterface.php^      ^  9P      .   vendor/symfony/console/Command/LazyCommand.php        x      3   vendor/symfony/console/Command/TraceableCommand.php%(      %(  ;\(      .   vendor/symfony/console/Command/HelpCommand.phpU
      U
  	Ƥ      8   vendor/symfony/console/Command/DumpCompletionCommand.phpY      Y  (8v      +   vendor/symfony/console/Debug/CliRequest.phpU      U  vr      4   vendor/symfony/console/Event/ConsoleCommandEvent.php.      .        -   vendor/symfony/console/Event/ConsoleEvent.phpP      P  ^O      6   vendor/symfony/console/Event/ConsoleTerminateEvent.php        99      2   vendor/symfony/console/Event/ConsoleErrorEvent.php        aM      3   vendor/symfony/console/Event/ConsoleSignalEvent.php        _W      #   vendor/symfony/console/Terminal.phpq      q  ,      8   vendor/symfony/console/Question/ConfirmationQuestion.php(      (  F*      ,   vendor/symfony/console/Question/Question.php              2   vendor/symfony/console/Question/ChoiceQuestion.php]      ]  /Z      &   vendor/symfony/console/Application.php        ִH      6   vendor/symfony/console/EventListener/ErrorListener.phpA      A  %sӤ      7   vendor/symfony/console/Exception/ExceptionInterface.php        l      5   vendor/symfony/console/Exception/RuntimeException.php        *b      ?   vendor/symfony/console/Exception/NamespaceNotFoundException.php        BLH      3   vendor/symfony/console/Exception/LogicException.php        SML      :   vendor/symfony/console/Exception/MissingInputException.php        Qg;      >   vendor/symfony/console/Exception/RunCommandFailedException.phps      s  ٤      =   vendor/symfony/console/Exception/InvalidArgumentException.php        u i      =   vendor/symfony/console/Exception/CommandNotFoundException.php        fZj      ;   vendor/symfony/console/Exception/InvalidOptionException.php        /el      9   vendor/symfony/console/Formatter/OutputFormatterStyle.php              =   vendor/symfony/console/Formatter/OutputFormatterInterface.php        `)      4   vendor/symfony/console/Formatter/OutputFormatter.php$      $  c      8   vendor/symfony/console/Formatter/NullOutputFormatter.php        %      B   vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php        &      =   vendor/symfony/console/Formatter/NullOutputFormatterStyle.php[      [  }8a      F   vendor/symfony/console/Formatter/WrappableOutputFormatterInterface.php        #ó      >   vendor/symfony/console/Formatter/OutputFormatterStyleStack.php
      
  ¤      9   vendor/symfony/console/Tester/CommandCompletionTester.php        R      -   vendor/symfony/console/Tester/TesterTrait.php        ^      /   vendor/symfony/console/Tester/CommandTester.phpG	      G	  Sb      @   vendor/symfony/console/Tester/Constraint/CommandIsSuccessful.php!      !  к*      3   vendor/symfony/console/Tester/ApplicationTester.php        &l6      =   vendor/symfony/console/DataCollector/CommandDataCollector.php        O      (   vendor/symfony/console/ConsoleEvents.php~      ~  ~6      5   vendor/symfony/console/Output/TrimmedBufferOutput.php2      2  T      .   vendor/symfony/console/Output/StreamOutput.php         v      6   vendor/symfony/console/Output/ConsoleSectionOutput.php!      !  1w"      /   vendor/symfony/console/Output/ConsoleOutput.php        c      (   vendor/symfony/console/Output/Output.php        }Y0      /   vendor/symfony/console/Output/AnsiColorMode.php        K      ,   vendor/symfony/console/Output/NullOutput.php#      #  S      0   vendor/symfony/console/Output/BufferedOutput.phpF      F  +n      8   vendor/symfony/console/Output/ConsoleOutputInterface.php/      /  RФ      1   vendor/symfony/console/Output/OutputInterface.php        '      /   vendor/symfony/console/Logger/ConsoleLogger.php        @      6   vendor/symfony/console/Messenger/RunCommandMessage.php        mr      =   vendor/symfony/console/Messenger/RunCommandMessageHandler.php+      +  &VCǤ      6   vendor/symfony/console/Messenger/RunCommandContext.php&      &  b7      2   vendor/symfony/console/CI/GithubActionReporter.php]      ]  총      .   vendor/symfony/console/Attribute/AsCommand.php]      ]  ֚      3   vendor/symfony/console/SingleCommandApplication.php        SUʤ      0   vendor/symfony/console/Input/InputDefinition.php.      .        &   vendor/symfony/console/Input/Input.phpQ      Q        *   vendor/symfony/console/Input/ArgvInput.php0      0  ͲҤ      4   vendor/symfony/console/Input/InputAwareInterface.phpU      U  }U      ,   vendor/symfony/console/Input/InputOption.phpu!      u!  RSX;      +   vendor/symfony/console/Input/ArrayInput.php        GA      /   vendor/symfony/console/Input/InputInterface.phpU      U  @-x      9   vendor/symfony/console/Input/StreamableInputInterface.php         &      .   vendor/symfony/console/Input/InputArgument.php        \      ,   vendor/symfony/console/Input/StringInput.php
      
  Ɨg      D   vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.phpw      w  #LҤ      ?   vendor/symfony/console/CommandLoader/CommandLoaderInterface.php*      *  8      ?   vendor/symfony/console/CommandLoader/ContainerCommandLoader.php        hH      =   vendor/symfony/console/CommandLoader/FactoryCommandLoader.php"      "  J6\      0   vendor/symfony/console/Descriptor/Descriptor.php`
      `
  %|      4   vendor/symfony/console/Descriptor/JsonDescriptor.phpq      q  x꽤      8   vendor/symfony/console/Descriptor/MarkdownDescriptor.php        :ؤ      4   vendor/symfony/console/Descriptor/TextDescriptor.php0      0  4<      9   vendor/symfony/console/Descriptor/DescriptorInterface.phpQ      Q  m'      <   vendor/symfony/console/Descriptor/ApplicationDescription.php        xۜԤ      @   vendor/symfony/console/Descriptor/ReStructuredTextDescriptor.php)      )  p      3   vendor/symfony/console/Descriptor/XmlDescriptor.php0&      0&  ww         vendor/autoload.php        ޏѤ      (   vendor/justinrainbow/json-schema/LICENSE                A   vendor/justinrainbow/json-schema/src/JsonSchema/SchemaStorage.php          хJ      D   vendor/justinrainbow/json-schema/src/JsonSchema/DraftIdentifiers.php          L{      =   vendor/justinrainbow/json-schema/src/JsonSchema/Validator.php        &Z      J   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Constraint.php        m      P   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ObjectConstraint.php        ;'      P   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/NumberConstraint.phpI      I  80n      G   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.php                X   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/LooseTypeCheck.phpD      D  ˳      \   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/TypeCheckInterface.php        Mi̤      Y   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/StrictTypeCheck.phpE      E  bI      O   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstConstraint.phpu      u  '      \   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/RefConstraint.phpj      j        b   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinLengthConstraint.php        J᧤      ^   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AllOfConstraint.php}      }  y      a   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/RequiredConstraint.php        6P      ^   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AnyOfConstraint.php<      <        ^   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ItemsConstraint.php        ~]      V   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/Factory.php        |1      a   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinItemsConstraint.php        H      ^   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ConstConstraint.phpz      z        e   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/DependenciesConstraint.php        j+욤      i   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ExclusiveMaximumConstraint.php        |      i   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ExclusiveMinimumConstraint.php        ^a      \   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/NotConstraint.php        O      c   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/IfThenElseConstraint.php        U7      f   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxPropertiesConstraint.php        0=      f   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinPropertiesConstraint.php        {'/      d   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/UniqueItemsConstraint.php               c   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PropertiesConstraint.php        Wݤ      `   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/Draft07Constraint.php        I0f      `   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ContentConstraint.php        M      a   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ContainsConstraint.php        xcZ      a   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxItemsConstraint.php        Yl      ]   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/TypeConstraint.php        ܤ      h   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PropertiesNamesConstraint.php        }M      ^   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/OneOfConstraint.php$      $  &{      m   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AdditionalPropertiesConstraint.php        8h      `   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinimumConstraint.php        V*      `   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PatternConstraint.php        J      j   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PatternPropertiesConstraint.php        m.Ǥ      b   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxLengthConstraint.php              `   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaximumConstraint.php        o      ]   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/EnumConstraint.phpT      T  bO]      h   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AdditionalItemsConstraint.php        CG      c   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MultipleOfConstraint.php        ຖ      _   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/FormatConstraint.php.      .  p%      \   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/RefConstraint.phpj      j  !gɤ      b   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MinLengthConstraint.php        J      ^   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AllOfConstraint.php}      }  jᑤ      a   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/RequiredConstraint.php         Q]      ^   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AnyOfConstraint.php<      <  DvM      ^   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ItemsConstraint.php        #      V   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/Factory.phpP      P  sps      a   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MinItemsConstraint.php        6G?      ^   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ConstConstraint.phpz      z  f      e   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/DependenciesConstraint.php        hV      i   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ExclusiveMaximumConstraint.php        w&      i   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ExclusiveMinimumConstraint.php        ><       \   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/NotConstraint.php        %      f   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MaxPropertiesConstraint.php        )ƴj      f   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MinPropertiesConstraint.php        'x      d   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/UniqueItemsConstraint.php        X
      c   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PropertiesConstraint.php        E      a   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ContainsConstraint.php        Ȏ      a   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MaxItemsConstraint.php        }'      ]   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/TypeConstraint.php              h   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PropertiesNamesConstraint.php        \       ^   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/OneOfConstraint.php$      $  ą      m   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AdditionalPropertiesConstraint.php        Τ      `   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MinimumConstraint.php        7d      `   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PatternConstraint.php        di      j   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PatternPropertiesConstraint.php        We	      b   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MaxLengthConstraint.php        Ѥ      `   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MaximumConstraint.php              ]   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/EnumConstraint.phpT      T  NgX      `   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/Draft06Constraint.phpN      N  /      h   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AdditionalItemsConstraint.php              c   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MultipleOfConstraint.php        S      _   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/FormatConstraint.phpE      E  j      N   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/BaseConstraint.php              N   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeConstraint.php3+      3+  \<4      T   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/CollectionConstraint.php        G      P   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/SchemaConstraint.php+      +        S   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/UndefinedConstraint.phpTA      TA  Z+դ      S   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstraintInterface.php        8Ɗ      N   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/EnumConstraint.php        |      P   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php:      :  9      P   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/StringConstraint.phpo      o  e
      I   vendor/justinrainbow/json-schema/src/JsonSchema/UriRetrieverInterface.php        Mx      H   vendor/justinrainbow/json-schema/src/JsonSchema/UriResolverInterface.php,      ,  JF      J   vendor/justinrainbow/json-schema/src/JsonSchema/SchemaStorageInterface.php        &      C   vendor/justinrainbow/json-schema/src/JsonSchema/Entity/ErrorBag.php        '      F   vendor/justinrainbow/json-schema/src/JsonSchema/Entity/JsonPointer.php        F      H   vendor/justinrainbow/json-schema/src/JsonSchema/Entity/ErrorBagProxy.php        
uq      E   vendor/justinrainbow/json-schema/src/JsonSchema/Tool/DeepComparer.php        Q      ]   vendor/justinrainbow/json-schema/src/JsonSchema/Tool/Validator/RelativeReferenceValidator.php        Un      O   vendor/justinrainbow/json-schema/src/JsonSchema/Tool/Validator/UriValidator.php        k)      A   vendor/justinrainbow/json-schema/src/JsonSchema/Tool/DeepCopy.phpi      i  _      S   vendor/justinrainbow/json-schema/src/JsonSchema/Exception/JsonDecodingException.php        xF      T   vendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaException.phph      h  3$      P   vendor/justinrainbow/json-schema/src/JsonSchema/Exception/ExceptionInterface.phpc       c   Wf      N   vendor/justinrainbow/json-schema/src/JsonSchema/Exception/RuntimeException.php{      {  #/      T   vendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidConfigException.phpk      k  ʻ      ^   vendor/justinrainbow/json-schema/src/JsonSchema/Exception/UnresolvableJsonPointerException.php        eŤ      Q   vendor/justinrainbow/json-schema/src/JsonSchema/Exception/ValidationException.php3      3        W   vendor/justinrainbow/json-schema/src/JsonSchema/Exception/ResourceNotFoundException.phpn      n  Mפ      ]   vendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaMediaTypeException.phpq      q  \      V   vendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidArgumentException.php        <Э      W   vendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSourceUriException.phpv      v  LYx      R   vendor/justinrainbow/json-schema/src/JsonSchema/Exception/UriResolverException.phpd      d  {      C   vendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriResolver.php        
~       T   vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/AbstractRetriever.php              X   vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/UriRetrieverInterface.php        ף      G   vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/Curl.php        'S      R   vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/PredefinedArray.php.      .  ٔ      R   vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/FileGetContents.php        =y      D   vendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php$      $  yM      C   vendor/justinrainbow/json-schema/src/JsonSchema/ConstraintError.phpi      i  e[B      8   vendor/justinrainbow/json-schema/src/JsonSchema/Enum.phpg       g   /      ;   vendor/justinrainbow/json-schema/src/JsonSchema/Rfc3339.php        C      K   vendor/justinrainbow/json-schema/src/JsonSchema/Iterator/ObjectIterator.php+      +  TA      8   vendor/justinrainbow/json-schema/bin/update-changelog.sh&	      &	  }X      2   vendor/justinrainbow/json-schema/bin/validate-json        w      7   vendor/justinrainbow/json-schema/bin/prepare-release.shn      n  HpҤ      =   vendor/justinrainbow/json-schema/bin/extract-release-notes.sh        rs      +   vendor/composer/class-map-generator/LICENSE        DӤ      :   vendor/composer/class-map-generator/src/PhpFileCleaner.php        >-      =   vendor/composer/class-map-generator/src/ClassMapGenerator.php6      6        9   vendor/composer/class-map-generator/src/PhpFileParser.php8      8  !c^      4   vendor/composer/class-map-generator/src/ClassMap.php        m7      4   vendor/composer/class-map-generator/src/FileList.phpb      b  -         vendor/composer/ClassLoader.php?      ?  2@u      '   vendor/composer/autoload_namespaces.php          /t      !   vendor/composer/autoload_psr4.php                  vendor/composer/LICENSE.      .         #   vendor/composer/autoload_static.php*     * g      <   vendor/composer/composer/res/composer-repository-schema.json          븂      1   vendor/composer/composer/res/composer-schema.jsonj     j '      6   vendor/composer/composer/res/composer-lock-schema.json        rf          vendor/composer/composer/LICENSE,      ,  Vg      *   vendor/composer/composer/src/bootstrap.php        ?Ф      7   vendor/composer/composer/src/Composer/Util/Silencer.php(      (  |      2   vendor/composer/composer/src/Composer/Util/Tar.php        uD      2   vendor/composer/composer/src/Composer/Util/Zip.phpA      A  T#s      C   vendor/composer/composer/src/Composer/Util/StreamContextFactory.phpg%      g%        <   vendor/composer/composer/src/Composer/Util/PackageSorter.phpW      W  -!      =   vendor/composer/composer/src/Composer/Util/NoProxyPattern.phpw*      w*  H8      =   vendor/composer/composer/src/Composer/Util/ComposerMirror.phpD	      D	  7s+      >   vendor/composer/composer/src/Composer/Util/ProcessExecutor.phpG      G  VZ      5   vendor/composer/composer/src/Composer/Util/GitLab.php1      1  ʤ      D   vendor/composer/composer/src/Composer/Util/ForgejoRepositoryData.phpM      M  ׷M      9   vendor/composer/composer/src/Composer/Util/ForgejoUrl.phpe      e        9   vendor/composer/composer/src/Composer/Util/Filesystem.phpz      z  -      2   vendor/composer/composer/src/Composer/Util/Svn.php&      &  o      =   vendor/composer/composer/src/Composer/Util/HttpDownloader.phpG      G  )      9   vendor/composer/composer/src/Composer/Util/AuthHelper.phpB      B  }&y      3   vendor/composer/composer/src/Composer/Util/Loop.php        B      ;   vendor/composer/composer/src/Composer/Util/ErrorHandler.php        qV      ?   vendor/composer/composer/src/Composer/Util/MetadataMinifier.php        Ϊ      @   vendor/composer/composer/src/Composer/Util/Http/RequestProxy.phpe      e   C      =   vendor/composer/composer/src/Composer/Util/Http/ProxyItem.phpg      g  p      @   vendor/composer/composer/src/Composer/Util/Http/ProxyManager.phpW      W        B   vendor/composer/composer/src/Composer/Util/Http/CurlDownloader.php}      }  ts      <   vendor/composer/composer/src/Composer/Util/Http/Response.php
      
  NES      @   vendor/composer/composer/src/Composer/Util/Http/CurlResponse.php        Bߝ      7   vendor/composer/composer/src/Composer/Util/Platform.php*      *  u      2   vendor/composer/composer/src/Composer/Util/Git.phpu      u  ?      7   vendor/composer/composer/src/Composer/Util/Perforce.phpK      K  /vS      1   vendor/composer/composer/src/Composer/Util/Hg.php        2      8   vendor/composer/composer/src/Composer/Util/TlsHelper.php/      /  ]om      8   vendor/composer/composer/src/Composer/Util/IniHelper.phpn      n  $      8   vendor/composer/composer/src/Composer/Util/Bitbucket.php&      &  ̤      5   vendor/composer/composer/src/Composer/Util/GitHub.php%'      %'  WJL9      >   vendor/composer/composer/src/Composer/Util/ConfigValidator.php%      %  hÔ      :   vendor/composer/composer/src/Composer/Util/PackageInfo.phpD      D  rqb3      9   vendor/composer/composer/src/Composer/Util/SyncHelper.php
      
  'N      ?   vendor/composer/composer/src/Composer/Util/RemoteFilesystem.phpl      l  XϤ      2   vendor/composer/composer/src/Composer/Util/Url.php              6   vendor/composer/composer/src/Composer/Util/Forgejo.php        	gä      q   vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilter.php        g-fM      m   vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterInterface.php        +      n   vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilter.php        |[X      k   vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterFactory.php[      [  .      m   vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreAllPlatformRequirementFilter.php        }      H   vendor/composer/composer/src/Composer/DependencyResolver/PoolBuilder.php              F   vendor/composer/composer/src/Composer/DependencyResolver/Decisions.php        4      H   vendor/composer/composer/src/Composer/DependencyResolver/GenericRule.php_      _  3]      D   vendor/composer/composer/src/Composer/DependencyResolver/Problem.php        Ϥ4      C   vendor/composer/composer/src/Composer/DependencyResolver/Solver.phpc      c  y      J   vendor/composer/composer/src/Composer/DependencyResolver/Rule2Literals.phpy
      y
  }      K   vendor/composer/composer/src/Composer/DependencyResolver/RuleWatchChain.php        nl      A   vendor/composer/composer/src/Composer/DependencyResolver/Rule.phpM      M  :"      D   vendor/composer/composer/src/Composer/DependencyResolver/Request.php_!      _!        O   vendor/composer/composer/src/Composer/DependencyResolver/SolverBugException.php        $S      N   vendor/composer/composer/src/Composer/DependencyResolver/MultiConflictRule.php        㓂      A   vendor/composer/composer/src/Composer/DependencyResolver/Pool.php,      ,  ^      J   vendor/composer/composer/src/Composer/DependencyResolver/DefaultPolicy.php*      *   |Գ      Q   vendor/composer/composer/src/Composer/DependencyResolver/LocalRepoTransaction.php.      .  C      J   vendor/composer/composer/src/Composer/DependencyResolver/PoolOptimizer.phpWL      WL  ׋      L   vendor/composer/composer/src/Composer/DependencyResolver/LockTransaction.php~      ~  60      T   vendor/composer/composer/src/Composer/DependencyResolver/SolverProblemsException.php4      4  W      D   vendor/composer/composer/src/Composer/DependencyResolver/RuleSet.phpm      m  ^'#y      L   vendor/composer/composer/src/Composer/DependencyResolver/PolicyInterface.phpw      w  Dp)Ҥ      L   vendor/composer/composer/src/Composer/DependencyResolver/RuleSetIterator.php0
      0
  3      M   vendor/composer/composer/src/Composer/DependencyResolver/RuleSetGenerator.php3      3  qV      K   vendor/composer/composer/src/Composer/DependencyResolver/RuleWatchGraph.phpz      z  Ť      H   vendor/composer/composer/src/Composer/DependencyResolver/Transaction.phpD9      D9  0Q      W   vendor/composer/composer/src/Composer/DependencyResolver/SecurityAdvisoryPoolFilter.php|      |  UI      J   vendor/composer/composer/src/Composer/DependencyResolver/RuleWatchNode.phpo      o  eW      b   vendor/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php        4XE      V   vendor/composer/composer/src/Composer/DependencyResolver/Operation/SolverOperation.phpe      e  e7      W   vendor/composer/composer/src/Composer/DependencyResolver/Operation/InstallOperation.php$      $  '<      Y   vendor/composer/composer/src/Composer/DependencyResolver/Operation/OperationInterface.php        \ܽ      d   vendor/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php#      #  4T      V   vendor/composer/composer/src/Composer/DependencyResolver/Operation/UpdateOperation.php        t      Y   vendor/composer/composer/src/Composer/DependencyResolver/Operation/UninstallOperation.php        mP8      0   vendor/composer/composer/src/Composer/Config.php5f      5f  I      1   vendor/composer/composer/src/Composer/Factory.php~      ~  w      F   vendor/composer/composer/src/Composer/Json/JsonValidationException.phpn      n  @YZ      <   vendor/composer/composer/src/Composer/Json/JsonFormatter.php        Wm      >   vendor/composer/composer/src/Composer/Json/JsonManipulator.php2      2  w葤      7   vendor/composer/composer/src/Composer/Json/JsonFile.php[8      [8  Ϻ/      2   vendor/composer/composer/src/Composer/Composer.phpt      t        A   vendor/composer/composer/src/Composer/Config/JsonConfigSource.phpG=      G=  ,      F   vendor/composer/composer/src/Composer/Config/ConfigSourceInterface.php
      
  
,      @   vendor/composer/composer/src/Composer/Plugin/PluginInterface.phpC      C  [      K   vendor/composer/composer/src/Composer/Plugin/Capability/CommandProvider.phpx      x  Ƨ      F   vendor/composer/composer/src/Composer/Plugin/Capability/Capability.php        (      E   vendor/composer/composer/src/Composer/Plugin/PreFileDownloadEvent.phpA      A  w&      C   vendor/composer/composer/src/Composer/Plugin/PrePoolCreateEvent.php        Zv      =   vendor/composer/composer/src/Composer/Plugin/CommandEvent.php_      _         8   vendor/composer/composer/src/Composer/Plugin/Capable.php        F)      =   vendor/composer/composer/src/Composer/Plugin/PluginEvents.phpj      j  U      >   vendor/composer/composer/src/Composer/Plugin/PluginManager.phpw      w  2X      G   vendor/composer/composer/src/Composer/Plugin/PluginBlockedException.php        MÀZ      C   vendor/composer/composer/src/Composer/Plugin/PreCommandRunEvent.php&      &  ;~9e      F   vendor/composer/composer/src/Composer/Plugin/PostFileDownloadEvent.php        D*	      /   vendor/composer/composer/src/Composer/Cache.php\,      \,        A   vendor/composer/composer/src/Composer/Command/OutdatedCommand.php^      ^        E   vendor/composer/composer/src/Composer/Command/DumpAutoloadCommand.php1      1  .;      A   vendor/composer/composer/src/Composer/Command/DiagnoseCommand.phpi      i  [ޤ      B   vendor/composer/composer/src/Composer/Command/RunScriptCommand.php        }      A   vendor/composer/composer/src/Composer/Command/ValidateCommand.php$      $  `ݩJ      F   vendor/composer/composer/src/Composer/Command/CreateProjectCommand.phpb      b  W*!      B   vendor/composer/composer/src/Composer/Command/ProhibitsCommand.php8      8  ie٤      C   vendor/composer/composer/src/Composer/Command/RepositoryCommand.php</      </  P      G   vendor/composer/composer/src/Composer/Command/BaseDependencyCommand.php3      3  5;      C   vendor/composer/composer/src/Composer/Command/BaseConfigCommand.phpp      p  MY      =   vendor/composer/composer/src/Composer/Command/BaseCommand.phpH      H  ?       A   vendor/composer/composer/src/Composer/Command/SuggestsCommand.php        !      @   vendor/composer/composer/src/Composer/Command/RequireCommand.phpw      w  PJ      A   vendor/composer/composer/src/Composer/Command/LicensesCommand.php        p`)      =   vendor/composer/composer/src/Composer/Command/HomeCommand.php        JՓ      ?   vendor/composer/composer/src/Composer/Command/ConfigCommand.php        rϟɤ      =   vendor/composer/composer/src/Composer/Command/BumpCommand.php(      (   ?      ?   vendor/composer/composer/src/Composer/Command/RemoveCommand.php>      >  E      @   vendor/composer/composer/src/Composer/Command/ArchiveCommand.php"      "  At!      >   vendor/composer/composer/src/Composer/Command/AboutCommand.php        	zɤ      ?   vendor/composer/composer/src/Composer/Command/StatusCommand.php!      !        B   vendor/composer/composer/src/Composer/Command/ReinstallCommand.php9%      9%  
 S      D   vendor/composer/composer/src/Composer/Command/ScriptAliasCommand.php        ,U      ?   vendor/composer/composer/src/Composer/Command/UpdateCommand.phpS      S        ?   vendor/composer/composer/src/Composer/Command/GlobalCommand.php        -J      @   vendor/composer/composer/src/Composer/Command/InstallCommand.php          Zh      =   vendor/composer/composer/src/Composer/Command/ShowCommand.phpl     l X      @   vendor/composer/composer/src/Composer/Command/DependsCommand.php        3V7      =   vendor/composer/composer/src/Composer/Command/InitCommand.phpN`      N`        C   vendor/composer/composer/src/Composer/Command/ClearCacheCommand.php        8)u      >   vendor/composer/composer/src/Composer/Command/AuditCommand.php]      ]  ۯ      J   vendor/composer/composer/src/Composer/Command/CheckPlatformReqsCommand.phpY#      Y#  }E      ?   vendor/composer/composer/src/Composer/Command/SearchCommand.php        !      =   vendor/composer/composer/src/Composer/Command/FundCommand.phpV      V  S      A   vendor/composer/composer/src/Composer/Command/CompletionTrait.php#      #  _m      =   vendor/composer/composer/src/Composer/Command/ExecCommand.phpf      f  C      C   vendor/composer/composer/src/Composer/Command/SelfUpdateCommand.phpp      p  e.      G   vendor/composer/composer/src/Composer/Command/PackageDiscoveryTrait.phptS      tS  q      C   vendor/composer/composer/src/Composer/Console/GithubActionError.phpa      a  	z      E   vendor/composer/composer/src/Composer/Console/HtmlOutputFormatter.php        x      =   vendor/composer/composer/src/Composer/Console/Application.php        w      C   vendor/composer/composer/src/Composer/Console/Input/InputOption.php^      ^  T\̤      E   vendor/composer/composer/src/Composer/Console/Input/InputArgument.php
      
  [1      M   vendor/composer/composer/src/Composer/Question/StrictConfirmationQuestion.php
      
        2   vendor/composer/composer/src/Composer/Compiler.php1      1  n~Ԥ      8   vendor/composer/composer/src/Composer/IO/IOInterface.phpX       X   ?0i      5   vendor/composer/composer/src/Composer/IO/BufferIO.php        I      3   vendor/composer/composer/src/Composer/IO/NullIO.phpz	      z	  P      6   vendor/composer/composer/src/Composer/IO/ConsoleIO.php4      4  o!      3   vendor/composer/composer/src/Composer/IO/BaseIO.php-+      -+  Ws      =   vendor/composer/composer/src/Composer/Script/ScriptEvents.php        Fs      6   vendor/composer/composer/src/Composer/Script/Event.php
      
  Bw      9   vendor/composer/composer/src/Composer/PartialComposer.php
      
        R   vendor/composer/composer/src/Composer/Exception/IrrecoverableDownloadException.php        (1      B   vendor/composer/composer/src/Composer/Exception/NoSslException.php        2      3   vendor/composer/composer/src/Composer/Installer.php        kY      K   vendor/composer/composer/src/Composer/PHPStan/ConfigReturnTypeExtension.php          p      S   vendor/composer/composer/src/Composer/PHPStan/RuleReasonDataReturnTypeExtension.phpf
      f
  }       E   vendor/composer/composer/src/Composer/Downloader/FossilDownloader.php         y      F   vendor/composer/composer/src/Composer/Downloader/ArchiveDownloader.php!      !  ߻2      A   vendor/composer/composer/src/Composer/Downloader/HgDownloader.php|      |  `&}      B   vendor/composer/composer/src/Composer/Downloader/TarDownloader.php        36      C   vendor/composer/composer/src/Composer/Downloader/GzipDownloader.phpm      m   Ҥ      B   vendor/composer/composer/src/Composer/Downloader/VcsDownloader.phpm3      m3  +B      H   vendor/composer/composer/src/Composer/Downloader/DownloaderInterface.php        _(      H   vendor/composer/composer/src/Composer/Downloader/FilesystemException.php        ._      L   vendor/composer/composer/src/Composer/Downloader/DvcsDownloaderInterface.php6      6  7      G   vendor/composer/composer/src/Composer/Downloader/TransportException.phpE      E  0
g      B   vendor/composer/composer/src/Composer/Downloader/RarDownloader.php	      	   Ҥ      C   vendor/composer/composer/src/Composer/Downloader/PathDownloader.php6      6  l      B   vendor/composer/composer/src/Composer/Downloader/ZipDownloader.php:      :        D   vendor/composer/composer/src/Composer/Downloader/DownloadManager.php=      =  x&      R   vendor/composer/composer/src/Composer/Downloader/VcsCapableDownloaderInterface.phpC      C  .(      G   vendor/composer/composer/src/Composer/Downloader/PerforceDownloader.phpp      p        Q   vendor/composer/composer/src/Composer/Downloader/MaxFileSizeExceededException.php        C      C   vendor/composer/composer/src/Composer/Downloader/PharDownloader.phpu      u        J   vendor/composer/composer/src/Composer/Downloader/ChangeReportInterface.php'      '  ½2      B   vendor/composer/composer/src/Composer/Downloader/SvnDownloader.php#      #  f      C   vendor/composer/composer/src/Composer/Downloader/FileDownloader.phpR      R  P      B   vendor/composer/composer/src/Composer/Downloader/GitDownloader.phpBc      Bc  ,餤      A   vendor/composer/composer/src/Composer/Downloader/XzDownloader.php        &      >   vendor/composer/composer/src/Composer/Autoload/ClassLoader.php?      ?  2@u      D   vendor/composer/composer/src/Composer/Autoload/ClassMapGenerator.php	      	  ZiI      D   vendor/composer/composer/src/Composer/Autoload/AutoloadGenerator.php              R   vendor/composer/composer/src/Composer/EventDispatcher/EventSubscriberInterface.phpu      u  i+       ?   vendor/composer/composer/src/Composer/EventDispatcher/Event.php              I   vendor/composer/composer/src/Composer/EventDispatcher/EventDispatcher.php        uG      R   vendor/composer/composer/src/Composer/EventDispatcher/ScriptExecutionException.php        _!F      J   vendor/composer/composer/src/Composer/Advisory/PartialSecurityAdvisory.php7
      7
  yi^      :   vendor/composer/composer/src/Composer/Advisory/Auditor.phpI      I  "97      J   vendor/composer/composer/src/Composer/Advisory/IgnoredSecurityAdvisory.php]      ]  v7Ĥ      >   vendor/composer/composer/src/Composer/Advisory/AuditConfig.php        .[      C   vendor/composer/composer/src/Composer/Advisory/SecurityAdvisory.php	      	  W֤      ;   vendor/composer/composer/src/Composer/InstalledVersions.phpC      C  <nw      B   vendor/composer/composer/src/Composer/Package/PackageInterface.phps/      s/  ~      =   vendor/composer/composer/src/Composer/Package/RootPackage.php6      6  (      J   vendor/composer/composer/src/Composer/Package/CompletePackageInterface.php        K      L   vendor/composer/composer/src/Composer/Package/Archiver/BaseExcludeFilter.php(      (  ӗ      L   vendor/composer/composer/src/Composer/Package/Archiver/ArchiverInterface.php        Ln      I   vendor/composer/composer/src/Composer/Package/Archiver/ArchiveManager.php$      $  BĎ      G   vendor/composer/composer/src/Composer/Package/Archiver/PharArchiver.php:      :  lӵ      P   vendor/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFinder.php        ^>      K   vendor/composer/composer/src/Composer/Package/Archiver/GitExcludeFilter.php        i      P   vendor/composer/composer/src/Composer/Package/Archiver/ComposerExcludeFilter.phpz      z  -n5      F   vendor/composer/composer/src/Composer/Package/Archiver/ZipArchiver.php&      &  Q      P   vendor/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFilter.php              B   vendor/composer/composer/src/Composer/Package/RootAliasPackage.phpO      O  /      D   vendor/composer/composer/src/Composer/Package/Dumper/ArrayDumper.php        }MI      C   vendor/composer/composer/src/Composer/Package/Comparer/Comparer.php_      _  s*      F   vendor/composer/composer/src/Composer/Package/RootPackageInterface.php	      	  q;k      N   vendor/composer/composer/src/Composer/Package/Loader/ValidatingArrayLoader.php        Ert_      P   vendor/composer/composer/src/Composer/Package/Loader/InvalidPackageException.phpl      l  l4ؤ      H   vendor/composer/composer/src/Composer/Package/Loader/LoaderInterface.phph      h        C   vendor/composer/composer/src/Composer/Package/Loader/JsonLoader.php}      }  Vo      D   vendor/composer/composer/src/Composer/Package/Loader/ArrayLoader.phpjJ      jJ  Z:      J   vendor/composer/composer/src/Composer/Package/Loader/RootPackageLoader.php/      /  xW      >   vendor/composer/composer/src/Composer/Package/AliasPackage.php&      &   	      F   vendor/composer/composer/src/Composer/Package/CompleteAliasPackage.phpY      Y  sou      A   vendor/composer/composer/src/Composer/Package/CompletePackage.php        P2      8   vendor/composer/composer/src/Composer/Package/Locker.phpNZ      NZ  	Ĥ      H   vendor/composer/composer/src/Composer/Package/Version/VersionGuesser.phpH      H  ^f      I   vendor/composer/composer/src/Composer/Package/Version/StabilityFilter.php        f      G   vendor/composer/composer/src/Composer/Package/Version/VersionBumper.php        m'      G   vendor/composer/composer/src/Composer/Package/Version/VersionParser.phpS      S  v|      I   vendor/composer/composer/src/Composer/Package/Version/VersionSelector.php/      /        9   vendor/composer/composer/src/Composer/Package/Package.phpC      C  _      6   vendor/composer/composer/src/Composer/Package/Link.php4      4  bCH      =   vendor/composer/composer/src/Composer/Package/BasePackage.php        ./K      C   vendor/composer/composer/src/Composer/Installer/BinaryInstaller.php8      8  !҅      @   vendor/composer/composer/src/Composer/Installer/PackageEvent.php        .C      C   vendor/composer/composer/src/Composer/Installer/InstallerEvents.php        d2A      B   vendor/composer/composer/src/Composer/Installer/InstallerEvent.php        i      A   vendor/composer/composer/src/Composer/Installer/PackageEvents.php'      '  Օ      D   vendor/composer/composer/src/Composer/Installer/ProjectInstaller.php        T      A   vendor/composer/composer/src/Composer/Installer/NoopInstaller.phpL      L        F   vendor/composer/composer/src/Composer/Installer/InstallerInterface.php:      :  [      G   vendor/composer/composer/src/Composer/Installer/InstallationManager.php;b      ;b  ѐ`      H   vendor/composer/composer/src/Composer/Installer/MetapackageInstaller.php        ~       D   vendor/composer/composer/src/Composer/Installer/LibraryInstaller.php+      +  @ߑ      K   vendor/composer/composer/src/Composer/Installer/BinaryPresenceInterface.php        #p      C   vendor/composer/composer/src/Composer/Installer/PluginInstaller.phpL      L  ޟ      M   vendor/composer/composer/src/Composer/Installer/SuggestedPackagesReporter.php          d      F   vendor/composer/composer/src/Composer/Repository/RepositoryFactory.php        -i      R   vendor/composer/composer/src/Composer/Repository/InstalledFilesystemRepository.php        \^      G   vendor/composer/composer/src/Composer/Repository/ComposerRepository.php      e      H   vendor/composer/composer/src/Composer/Repository/RepositoryInterface.php              O   vendor/composer/composer/src/Composer/Repository/InvalidRepositoryException.php        .*      H   vendor/composer/composer/src/Composer/Repository/CompositeRepository.php        L O      H   vendor/composer/composer/src/Composer/Repository/InstalledRepository.php3      3  *ܤ      D   vendor/composer/composer/src/Composer/Repository/RepositoryUtils.phpj
      j
  /      M   vendor/composer/composer/src/Composer/Repository/InstalledArrayRepository.php        0(Y      I   vendor/composer/composer/src/Composer/Repository/FilesystemRepository.phpD      D  \P=_      H   vendor/composer/composer/src/Composer/Repository/LockArrayRepository.php        n.      D   vendor/composer/composer/src/Composer/Repository/ArrayRepository.php)      )  R      K   vendor/composer/composer/src/Composer/Repository/CanonicalPackagesTrait.php        a1      Q   vendor/composer/composer/src/Composer/Repository/InstalledRepositoryInterface.php}      }  H@      J   vendor/composer/composer/src/Composer/Repository/RootPackageRepository.php&      &  @\      G   vendor/composer/composer/src/Composer/Repository/PlatformRepository.php        =Τ      J   vendor/composer/composer/src/Composer/Repository/VersionCacheInterface.phpd      d  ͖:      L   vendor/composer/composer/src/Composer/Repository/WritableArrayRepository.php        1-      F   vendor/composer/composer/src/Composer/Repository/RepositoryManager.phpd      d  L      P   vendor/composer/composer/src/Composer/Repository/WritableRepositoryInterface.php2      2  2      C   vendor/composer/composer/src/Composer/Repository/PathRepository.php#      #  B3      B   vendor/composer/composer/src/Composer/Repository/Vcs/GitDriver.php"      "  //      E   vendor/composer/composer/src/Composer/Repository/Vcs/GitHubDriver.phpj]      j]  D1z      A   vendor/composer/composer/src/Composer/Repository/Vcs/HgDriver.php        Z㋤      K   vendor/composer/composer/src/Composer/Repository/Vcs/VcsDriverInterface.php
      
        E   vendor/composer/composer/src/Composer/Repository/Vcs/GitLabDriver.phpIR      IR  3B      F   vendor/composer/composer/src/Composer/Repository/Vcs/ForgejoDriver.php-      -  b      B   vendor/composer/composer/src/Composer/Repository/Vcs/VcsDriver.php              B   vendor/composer/composer/src/Composer/Repository/Vcs/SvnDriver.php4      4  m      G   vendor/composer/composer/src/Composer/Repository/Vcs/PerforceDriver.php        }X      E   vendor/composer/composer/src/Composer/Repository/Vcs/FossilDriver.php        L4פ      K   vendor/composer/composer/src/Composer/Repository/Vcs/GitBitbucketDriver.php4?      4?  GȤ      T   vendor/composer/composer/src/Composer/Repository/ConfigurableRepositoryInterface.php        -=      G   vendor/composer/composer/src/Composer/Repository/ArtifactRepository.phpQ      Q  .2f7      P   vendor/composer/composer/src/Composer/Repository/RepositorySecurityException.php        v      B   vendor/composer/composer/src/Composer/Repository/RepositorySet.phpF      F  e-Ѥ      E   vendor/composer/composer/src/Composer/Repository/FilterRepository.phpt      t  @,      N   vendor/composer/composer/src/Composer/Repository/AdvisoryProviderInterface.php        xǇt      B   vendor/composer/composer/src/Composer/Repository/VcsRepository.php6R      6R  zh      C   vendor/composer/composer/src/Composer/Repository/PearRepository.phpK      K  R      F   vendor/composer/composer/src/Composer/Repository/PackageRepository.php        ,n      9   vendor/composer/composer/src/Composer/SelfUpdate/Keys.php        z	      =   vendor/composer/composer/src/Composer/SelfUpdate/Versions.php        G`      :   vendor/composer/composer/src/Composer/Platform/Runtime.php        10      ?   vendor/composer/composer/src/Composer/Platform/HhvmDetector.php        $7      :   vendor/composer/composer/src/Composer/Platform/Version.php
      
  nZ͏      +   vendor/composer/composer/phpstan/rules.neon        ~d      %   vendor/composer/composer/bin/composere      e  3q:      $   vendor/composer/composer/bin/compile        a}Ѥ      )   vendor/composer/metadata-minifier/LICENSE        hg^      :   vendor/composer/metadata-minifier/src/MetadataMinifier.php
      
  l3         vendor/composer/installed.php        "}      !   vendor/composer/autoload_real.php        o&Ǥ      "   vendor/composer/autoload_files.phpK#      K#  i      %   vendor/composer/autoload_classmap.php*     * JS      (   vendor/composer/ca-bundle/res/cacert.pemxs     xs fO      !   vendor/composer/ca-bundle/LICENSE        *!^`      *   vendor/composer/ca-bundle/src/CaBundle.php+      +  N      4   vendor/composer/spdx-licenses/res/spdx-licenses.json1      1  Yzx      6   vendor/composer/spdx-licenses/res/spdx-exceptions.json;      ;  W      %   vendor/composer/spdx-licenses/LICENSE        Bh      2   vendor/composer/spdx-licenses/src/SpdxLicenses.phpw&      w&  U      "   vendor/composer/platform_check.php        <E         vendor/composer/semver/LICENSE        Bh      )   vendor/composer/semver/src/Comparator.phpD
      D
  Ș܁      '   vendor/composer/semver/src/Interval.php~      ~  E|V      %   vendor/composer/semver/src/Semver.phpO      O  @      4   vendor/composer/semver/src/Constraint/Constraint.php1      1  .      /   vendor/composer/semver/src/Constraint/Bound.phpe
      e
  M      <   vendor/composer/semver/src/Constraint/MatchAllConstraint.php        D      9   vendor/composer/semver/src/Constraint/MultiConstraint.phpA%      A%  w      =   vendor/composer/semver/src/Constraint/ConstraintInterface.php        vx      =   vendor/composer/semver/src/Constraint/MatchNoneConstraint.php        ?T>      (   vendor/composer/semver/src/Intervals.phpO      O  3      ,   vendor/composer/semver/src/VersionParser.phpAV      AV  n!Z      /   vendor/composer/semver/src/CompilingMatcher.php          }~@         vendor/composer/pcre/LICENSE        hg^      (   vendor/composer/pcre/src/MatchResult.php        	'      *   vendor/composer/pcre/src/PcreException.php^      ^  T      6   vendor/composer/pcre/src/MatchAllWithOffsetsResult.php              !   vendor/composer/pcre/src/Preg.phpE      E  ho      3   vendor/composer/pcre/src/MatchWithOffsetsResult.php        Q/Ju      4   vendor/composer/pcre/src/MatchStrictGroupsResult.php        B)|ˤ      9   vendor/composer/pcre/src/UnexpectedNullMatchException.php              7   vendor/composer/pcre/src/MatchAllStrictGroupsResult.php{      {  B@ޤ      ?   vendor/composer/pcre/src/PHPStan/UnsafeStrictGroupsCallRule.php        <B      G   vendor/composer/pcre/src/PHPStan/PregMatchParameterOutTypeExtension.php        SS=      3   vendor/composer/pcre/src/PHPStan/PregMatchFlags.php        zk]      L   vendor/composer/pcre/src/PHPStan/PregReplaceCallbackClosureTypeExtension.php        ɻ      <   vendor/composer/pcre/src/PHPStan/InvalidRegexPatternRule.phpC      C  n       E   vendor/composer/pcre/src/PHPStan/PregMatchTypeSpecifyingExtension.phpC      C  KV      *   vendor/composer/pcre/src/ReplaceResult.php        L=      +   vendor/composer/pcre/src/MatchAllResult.php        Ti      "   vendor/composer/pcre/src/Regex.php        
      #   vendor/composer/pcre/extension.neon        &&1      &   vendor/composer/xdebug-handler/LICENSE)      )  #;^      0   vendor/composer/xdebug-handler/src/PhpConfig.php              .   vendor/composer/xdebug-handler/src/Process.php:      :  7      4   vendor/composer/xdebug-handler/src/XdebugHandler.phpT      T  [      -   vendor/composer/xdebug-handler/src/Status.php        :T      %   vendor/composer/InstalledVersions.phpC      C  <nw         vendor/seld/jsonlint/LICENSE"      "  asy      0   vendor/seld/jsonlint/src/Seld/JsonLint/Lexer.php"      "  H*      4   vendor/seld/jsonlint/src/Seld/JsonLint/Undefined.php        g      ;   vendor/seld/jsonlint/src/Seld/JsonLint/ParsingException.php        #      5   vendor/seld/jsonlint/src/Seld/JsonLint/JsonParser.phpt`      t`  S`=      @   vendor/seld/jsonlint/src/Seld/JsonLint/DuplicateKeyException.php        >f	      !   vendor/seld/jsonlint/bin/jsonlint        )ä         vendor/seld/phar-utils/LICENSE"      "  ?e      %   vendor/seld/phar-utils/src/Linter.php        '4#      )   vendor/seld/phar-utils/src/Timestamps.php        (6      "   vendor/seld/signal-handler/LICENSE"      "  ?e      0   vendor/seld/signal-handler/src/SignalHandler.phpO      O  ֘      )   vendor/marc-mabe/php-enum/src/EnumMap.php-      -  lQ      7   vendor/marc-mabe/php-enum/src/EnumSerializableTrait.php        W      &   vendor/marc-mabe/php-enum/src/Enum.php(:      (:        )   vendor/marc-mabe/php-enum/src/EnumSet.php        PG{      %   vendor/marc-mabe/php-enum/LICENSE.txt        CѤ      .   vendor/marc-mabe/php-enum/stubs/Stringable.php          9         vendor/psr/container/LICENSEy      y  Op      /   vendor/psr/container/src/ContainerInterface.php              8   vendor/psr/container/src/ContainerExceptionInterface.php          ^33      7   vendor/psr/container/src/NotFoundExceptionInterface.php          >悤      #   vendor/psr/event-dispatcher/LICENSE(      (  }]      <   vendor/psr/event-dispatcher/src/EventDispatcherInterface.php        'a      ;   vendor/psr/event-dispatcher/src/StoppableEventInterface.php              =   vendor/psr/event-dispatcher/src/ListenerProviderInterface.php        e         vendor/psr/log/LICENSE=      =  pO         vendor/psr/log/src/LogLevel.phpP      P        !   vendor/psr/log/src/NullLogger.php        (/      "   vendor/psr/log/src/LoggerTrait.php
      
  WP      %   vendor/psr/log/src/AbstractLogger.php        ۛ      '   vendor/psr/log/src/LoggerAwareTrait.php[      [  ۤ      &   vendor/psr/log/src/LoggerInterface.php
      
  q      /   vendor/psr/log/src/InvalidArgumentException.php`       `    X1      +   vendor/psr/log/src/LoggerAwareInterface.php           {      .   vendor/psr/simple-cache/src/CacheException.php          ٭q      .   vendor/psr/simple-cache/src/CacheInterface.php        {)      8   vendor/psr/simple-cache/src/InvalidArgumentException.php        3      2   vendor/thecodingmachine/safe/lib/special_cases.phpS      S  >O"      =   vendor/thecodingmachine/safe/lib/Exceptions/PcreException.phpS      S  eP%~      B   vendor/thecodingmachine/safe/lib/Exceptions/SimplexmlException.php;      ;  ;44      @   vendor/thecodingmachine/safe/lib/Exceptions/OpensslException.php          Fh      F   vendor/thecodingmachine/safe/lib/Exceptions/SafeExceptionInterface.php[       [   ANd      =   vendor/thecodingmachine/safe/lib/Exceptions/JsonException.php          -L      =   vendor/thecodingmachine/safe/lib/Exceptions/CurlException.php<      <  )*R      6   vendor/thecodingmachine/safe/lib/DateTimeImmutable.phpI!      I!  wۤ      -   vendor/thecodingmachine/safe/lib/DateTime.php	      	  ~      $   vendor/thecodingmachine/safe/LICENSE0      0  w'W      /   vendor/thecodingmachine/safe/rector-migrate.phpA      A  ߗq      1   vendor/thecodingmachine/safe/generated/libxml.php/      /  柚k      0   vendor/thecodingmachine/safe/generated/uodbc.php)      )  lh      0   vendor/thecodingmachine/safe/generated/xdiff.php)      )  s1Uv      0   vendor/thecodingmachine/safe/generated/pgsql.php)      )  %      2   vendor/thecodingmachine/safe/generated/inotify.php5      5  ~B /      /   vendor/thecodingmachine/safe/generated/uopz.php#      #  ,֤      3   vendor/thecodingmachine/safe/generated/datetime.php;      ;  Y      /   vendor/thecodingmachine/safe/generated/info.php#      #  ,`      D   vendor/thecodingmachine/safe/generated/Exceptions/PgsqlException.php5      5  xq      E   vendor/thecodingmachine/safe/generated/Exceptions/IbmDb2Exception.php6      6  n      C   vendor/thecodingmachine/safe/generated/Exceptions/SolrException.php4      4  ^Y/      A   vendor/thecodingmachine/safe/generated/Exceptions/PsException.php2      2  ]I      B   vendor/thecodingmachine/safe/generated/Exceptions/ComException.php3      3  MR      F   vendor/thecodingmachine/safe/generated/Exceptions/InotifyException.php7      7  ܤ      E   vendor/thecodingmachine/safe/generated/Exceptions/CubridException.php6      6  Ĥ      C   vendor/thecodingmachine/safe/generated/Exceptions/Oci8Exception.php4      4  hs      G   vendor/thecodingmachine/safe/generated/Exceptions/DatetimeException.php8      8  Q      E   vendor/thecodingmachine/safe/generated/Exceptions/SodiumException.php6      6  T      F   vendor/thecodingmachine/safe/generated/Exceptions/SocketsException.php7      7  l      D   vendor/thecodingmachine/safe/generated/Exceptions/ArrayException.php5      5  	ޤ      C   vendor/thecodingmachine/safe/generated/Exceptions/UopzException.php4      4  *      C   vendor/thecodingmachine/safe/generated/Exceptions/HashException.php4      4  ;G      B   vendor/thecodingmachine/safe/generated/Exceptions/RrdException.php3      3  v_N      D   vendor/thecodingmachine/safe/generated/Exceptions/XdiffException.php5      5  mϤ      B   vendor/thecodingmachine/safe/generated/Exceptions/FtpException.php3      3        B   vendor/thecodingmachine/safe/generated/Exceptions/RnpException.php3      3  *      C   vendor/thecodingmachine/safe/generated/Exceptions/ZlibException.php4      4  ʢ_      B   vendor/thecodingmachine/safe/generated/Exceptions/LzfException.php3      3        D   vendor/thecodingmachine/safe/generated/Exceptions/ShmopException.php5      5         D   vendor/thecodingmachine/safe/generated/Exceptions/IconvException.php5      5  Ფ      D   vendor/thecodingmachine/safe/generated/Exceptions/GnupgException.php5      5  a\      F   vendor/thecodingmachine/safe/generated/Exceptions/NetworkException.php7      7   䫤      D   vendor/thecodingmachine/safe/generated/Exceptions/IbaseException.php5      5  EӤ      B   vendor/thecodingmachine/safe/generated/Exceptions/GmpException.php3      3  @	a      D   vendor/thecodingmachine/safe/generated/Exceptions/PosixException.php5      5  [yߤ      H   vendor/thecodingmachine/safe/generated/Exceptions/MailparseException.php9      9  n       G   vendor/thecodingmachine/safe/generated/Exceptions/ClassobjException.php8      8   ä      C   vendor/thecodingmachine/safe/generated/Exceptions/MiscException.php4      4  4pS      E   vendor/thecodingmachine/safe/generated/Exceptions/SsdeepException.php6      6  iP)      G   vendor/thecodingmachine/safe/generated/Exceptions/FileinfoException.php8      8        F   vendor/thecodingmachine/safe/generated/Exceptions/RpminfoException.php7      7  ?|      B   vendor/thecodingmachine/safe/generated/Exceptions/FpmException.php3      3  1k      I   vendor/thecodingmachine/safe/generated/Exceptions/FilesystemException.php:      :        B   vendor/thecodingmachine/safe/generated/Exceptions/SplException.php3      3  #      B   vendor/thecodingmachine/safe/generated/Exceptions/EioException.php3      3  {      F   vendor/thecodingmachine/safe/generated/Exceptions/SessionException.php7      7  bD      C   vendor/thecodingmachine/safe/generated/Exceptions/LdapException.php4      4        G   vendor/thecodingmachine/safe/generated/Exceptions/ReadlineException.php8      8  *ˤ      E   vendor/thecodingmachine/safe/generated/Exceptions/LibxmlException.php6      6  %դ      D   vendor/thecodingmachine/safe/generated/Exceptions/UodbcException.php5      5  n      E   vendor/thecodingmachine/safe/generated/Exceptions/ApacheException.php6      6  s      F   vendor/thecodingmachine/safe/generated/Exceptions/OpcacheException.php7      7  {      B   vendor/thecodingmachine/safe/generated/Exceptions/SemException.php3      3  P٤      G   vendor/thecodingmachine/safe/generated/Exceptions/FunchandException.php8      8  Z¤      E   vendor/thecodingmachine/safe/generated/Exceptions/FilterException.php6      6  WV      C   vendor/thecodingmachine/safe/generated/Exceptions/YamlException.php4      4  Hz\      F   vendor/thecodingmachine/safe/generated/Exceptions/StringsException.php7      7  n	e      B   vendor/thecodingmachine/safe/generated/Exceptions/XmlException.php3      3  AU      C   vendor/thecodingmachine/safe/generated/Exceptions/InfoException.php4      4  `'      E   vendor/thecodingmachine/safe/generated/Exceptions/PspellException.php6      6  KѤ      C   vendor/thecodingmachine/safe/generated/Exceptions/ExecException.php4      4  P      B   vendor/thecodingmachine/safe/generated/Exceptions/DirException.php3      3  B0      D   vendor/thecodingmachine/safe/generated/Exceptions/Bzip2Exception.php5      5  ʕ]      F   vendor/thecodingmachine/safe/generated/Exceptions/GettextException.php7      7  ˌ3      D   vendor/thecodingmachine/safe/generated/Exceptions/MysqlException.php5      5  8^      G   vendor/thecodingmachine/safe/generated/Exceptions/CalendarException.php8      8  [J      E   vendor/thecodingmachine/safe/generated/Exceptions/SqlsrvException.php6      6  =      E   vendor/thecodingmachine/safe/generated/Exceptions/XmlrpcException.php6      6  RLӤ      D   vendor/thecodingmachine/safe/generated/Exceptions/ImageException.php5      5  K@      C   vendor/thecodingmachine/safe/generated/Exceptions/ApcuException.php4      4  ]o      D   vendor/thecodingmachine/safe/generated/Exceptions/PcntlException.php5      5  ο      E   vendor/thecodingmachine/safe/generated/Exceptions/StreamException.php6      6  )      B   vendor/thecodingmachine/safe/generated/Exceptions/YazException.php3      3  o      C   vendor/thecodingmachine/safe/generated/Exceptions/ImapException.php4      4  eƊ      I   vendor/thecodingmachine/safe/generated/Exceptions/OutcontrolException.php:      :  c;I      E   vendor/thecodingmachine/safe/generated/Exceptions/MysqliException.php6      6  E      B   vendor/thecodingmachine/safe/generated/Exceptions/VarException.php3      3  OK(      H   vendor/thecodingmachine/safe/generated/Exceptions/ErrorfuncException.php9      9  ?T      B   vendor/thecodingmachine/safe/generated/Exceptions/UrlException.php3      3        B   vendor/thecodingmachine/safe/generated/Exceptions/ZipException.php3      3  T\      C   vendor/thecodingmachine/safe/generated/Exceptions/Ssh2Exception.php4      4  1      G   vendor/thecodingmachine/safe/generated/Exceptions/MbstringException.php8      8  ,-      E   vendor/thecodingmachine/safe/generated/Exceptions/SwooleException.php6      6  )5      2   vendor/thecodingmachine/safe/generated/openssl.php5      5        0   vendor/thecodingmachine/safe/generated/pcntl.php)      )  UB      1   vendor/thecodingmachine/safe/generated/stream.php/      /  j      .   vendor/thecodingmachine/safe/generated/fpm.php               /   vendor/thecodingmachine/safe/generated/apcu.php#      #  ٤      6   vendor/thecodingmachine/safe/generated/8.6/openssl.phpGv      Gv  @#      4   vendor/thecodingmachine/safe/generated/8.6/pcntl.php8      8  y      <   vendor/thecodingmachine/safe/generated/8.6/functionsList.phpZa      Za  %      =   vendor/thecodingmachine/safe/generated/8.6/rector-migrate.php        }U}      3   vendor/thecodingmachine/safe/generated/8.6/yaml.php        J4      3   vendor/thecodingmachine/safe/generated/8.6/zlib.php          q(      3   vendor/thecodingmachine/safe/generated/8.6/ldap.phpA      A  X      2   vendor/thecodingmachine/safe/generated/session.php5      5  xS       3   vendor/thecodingmachine/safe/generated/fileinfo.php;      ;  1̤      /   vendor/thecodingmachine/safe/generated/oci8.php#      #  ;      0   vendor/thecodingmachine/safe/generated/bzip2.php)      )  ч4      .   vendor/thecodingmachine/safe/generated/var.php        B媤      .   vendor/thecodingmachine/safe/generated/com.php        vڤ      =   vendor/thecodingmachine/safe/generated/8.3/rector-migrate.php        @      0   vendor/thecodingmachine/safe/generated/iconv.php)      )  g      7   vendor/thecodingmachine/safe/generated/8.2/datetime.php(      (  ZW      6   vendor/thecodingmachine/safe/generated/8.2/openssl.phpSs      Ss  <      5   vendor/thecodingmachine/safe/generated/8.2/stream.php,      ,  #v      2   vendor/thecodingmachine/safe/generated/8.2/fpm.php        T      3   vendor/thecodingmachine/safe/generated/8.2/oci8.php4:      4:  P(      <   vendor/thecodingmachine/safe/generated/8.2/functionsList.phpyd      yd  '<Ĥ      =   vendor/thecodingmachine/safe/generated/8.2/rector-migrate.php        @      6   vendor/thecodingmachine/safe/generated/8.2/network.phpy      y  <al7      3   vendor/thecodingmachine/safe/generated/8.2/imap.phpP      P  BӤ      8   vendor/thecodingmachine/safe/generated/8.2/errorfunc.php4      4  k}      5   vendor/thecodingmachine/safe/generated/8.2/mysqli.php                9   vendor/thecodingmachine/safe/generated/8.2/filesystem.phpKI      KI  JoM      4   vendor/thecodingmachine/safe/generated/8.2/array.php        NҤ      4   vendor/thecodingmachine/safe/generated/8.2/gnupg.php        x      2   vendor/thecodingmachine/safe/generated/8.2/gmp.php          '       2   vendor/thecodingmachine/safe/generated/8.2/rnp.php/      /  p      3   vendor/thecodingmachine/safe/generated/8.2/ssh2.php-      -  _.0      3   vendor/thecodingmachine/safe/generated/8.2/hash.php        ?V      3   vendor/thecodingmachine/safe/generated/8.2/misc.php        XH      3   vendor/thecodingmachine/safe/generated/8.2/curl.phpL      L  R      5   vendor/thecodingmachine/safe/generated/8.2/sodium.php        %tp      4   vendor/thecodingmachine/safe/generated/8.2/image.php        _C5      3   vendor/thecodingmachine/safe/generated/8.2/exec.php
      
  s      3   vendor/thecodingmachine/safe/generated/8.2/ldap.phpOB      OB  H?ɤ      1   vendor/thecodingmachine/safe/generated/swoole.php/      /  $q      8   vendor/thecodingmachine/safe/generated/functionsList.php        oQ      2   vendor/thecodingmachine/safe/generated/network.php5      5  DԤ      .   vendor/thecodingmachine/safe/generated/eio.php        qaA      2   vendor/thecodingmachine/safe/generated/opcache.php5      5  ޗ      /   vendor/thecodingmachine/safe/generated/imap.php#      #  /YP      /   vendor/thecodingmachine/safe/generated/solr.php#      #  o      2   vendor/thecodingmachine/safe/generated/gettext.php5      5  +*-      .   vendor/thecodingmachine/safe/generated/url.php              4   vendor/thecodingmachine/safe/generated/errorfunc.phpA      A  ZҤ      .   vendor/thecodingmachine/safe/generated/lzf.php        ~W      1   vendor/thecodingmachine/safe/generated/mysqli.php/      /  ]^      /   vendor/thecodingmachine/safe/generated/json.php#      #  <      1   vendor/thecodingmachine/safe/generated/pspell.php/      /  䢩Ǥ      5   vendor/thecodingmachine/safe/generated/filesystem.phpG      G        .   vendor/thecodingmachine/safe/generated/ftp.php        1id      0   vendor/thecodingmachine/safe/generated/array.php)      )        -   vendor/thecodingmachine/safe/generated/ps.php        &l      0   vendor/thecodingmachine/safe/generated/gnupg.php)      )  D      .   vendor/thecodingmachine/safe/generated/gmp.php        }      .   vendor/thecodingmachine/safe/generated/rnp.php        ts      .   vendor/thecodingmachine/safe/generated/zip.php        <ݞ      2   vendor/thecodingmachine/safe/generated/sockets.php5      5  _:      1   vendor/thecodingmachine/safe/generated/filter.php/      /  *ͤ      /   vendor/thecodingmachine/safe/generated/ssh2.php#      #  R      2   vendor/thecodingmachine/safe/generated/rpminfo.php5      5        4   vendor/thecodingmachine/safe/generated/8.4/uodbc.php9:      9:  ]lƤ      4   vendor/thecodingmachine/safe/generated/8.4/pgsql.phpZI      ZI  V2      6   vendor/thecodingmachine/safe/generated/8.4/inotify.phpz      z  vդ      7   vendor/thecodingmachine/safe/generated/8.4/datetime.php)      )  {#      3   vendor/thecodingmachine/safe/generated/8.4/info.phpn      n  N      6   vendor/thecodingmachine/safe/generated/8.4/openssl.phpt      t  eE      5   vendor/thecodingmachine/safe/generated/8.4/stream.php.      .  ]ޤ      <   vendor/thecodingmachine/safe/generated/8.4/functionsList.phpa      a   fu      =   vendor/thecodingmachine/safe/generated/8.4/rector-migrate.php        ^      6   vendor/thecodingmachine/safe/generated/8.4/network.php        _      3   vendor/thecodingmachine/safe/generated/8.4/imap.phpN      N  2L      6   vendor/thecodingmachine/safe/generated/8.4/gettext.php,      ,  I*^      5   vendor/thecodingmachine/safe/generated/8.4/pspell.phpn      n  nN      2   vendor/thecodingmachine/safe/generated/8.4/ftp.php(      (  |jJ      4   vendor/thecodingmachine/safe/generated/8.4/array.phpg      g  4Ƿ      6   vendor/thecodingmachine/safe/generated/8.4/sockets.phpo)      o)  Zi_      3   vendor/thecodingmachine/safe/generated/8.4/misc.php        n      3   vendor/thecodingmachine/safe/generated/8.4/curl.phpY      Y  weL      5   vendor/thecodingmachine/safe/generated/8.4/sodium.php          ^D@      3   vendor/thecodingmachine/safe/generated/8.4/zlib.php          jx      4   vendor/thecodingmachine/safe/generated/8.4/image.phpa      a  (J      2   vendor/thecodingmachine/safe/generated/8.4/xml.php        BI#      7   vendor/thecodingmachine/safe/generated/8.4/mbstring.php%      %  0      4   vendor/thecodingmachine/safe/generated/8.4/posix.php        r      7   vendor/thecodingmachine/safe/generated/8.4/readline.phpT      T  8쾤      5   vendor/thecodingmachine/safe/generated/8.4/ibmDb2.php-      -        3   vendor/thecodingmachine/safe/generated/8.4/ldap.phpA      A  ˕*      9   vendor/thecodingmachine/safe/generated/8.4/outcontrol.php        c      5   vendor/thecodingmachine/safe/generated/8.4/apache.php        yO.      /   vendor/thecodingmachine/safe/generated/yaml.php#      #  ɤ      0   vendor/thecodingmachine/safe/generated/mysql.php)      )  Ɣ;      3   vendor/thecodingmachine/safe/generated/funchand.php;      ;  Xz      /   vendor/thecodingmachine/safe/generated/hash.php#      #  c      /   vendor/thecodingmachine/safe/generated/misc.php#      #  /왤      /   vendor/thecodingmachine/safe/generated/curl.php#      #  jɧ      3   vendor/thecodingmachine/safe/generated/calendar.php;      ;  T      1   vendor/thecodingmachine/safe/generated/sodium.php/      /        4   vendor/thecodingmachine/safe/generated/8.5/uodbc.php<      <  9>      4   vendor/thecodingmachine/safe/generated/8.5/xdiff.php        -0      4   vendor/thecodingmachine/safe/generated/8.5/pcntl.php        1Ǿ      <   vendor/thecodingmachine/safe/generated/8.5/functionsList.phpqa      qa  E      =   vendor/thecodingmachine/safe/generated/8.5/rector-migrate.php         .0      6   vendor/thecodingmachine/safe/generated/8.5/rpminfo.php>      >  >      3   vendor/thecodingmachine/safe/generated/8.5/zlib.php          o      4   vendor/thecodingmachine/safe/generated/8.5/shmop.phpT      T  Y8      /   vendor/thecodingmachine/safe/generated/zlib.php#      #  M[      0   vendor/thecodingmachine/safe/generated/image.php)      )  p٤      .   vendor/thecodingmachine/safe/generated/spl.php        n绤      0   vendor/thecodingmachine/safe/generated/shmop.php)      )  '      .   vendor/thecodingmachine/safe/generated/sem.php        򕴤      .   vendor/thecodingmachine/safe/generated/rrd.php        i      /   vendor/thecodingmachine/safe/generated/exec.php#      #  pٶ      5   vendor/thecodingmachine/safe/generated/8.1/libxml.php        cH      4   vendor/thecodingmachine/safe/generated/8.1/uodbc.php:      :  ݯ      4   vendor/thecodingmachine/safe/generated/8.1/xdiff.php!      !  5      4   vendor/thecodingmachine/safe/generated/8.1/pgsql.phpIG      IG  |      6   vendor/thecodingmachine/safe/generated/8.1/inotify.php        ߻      3   vendor/thecodingmachine/safe/generated/8.1/uopz.php              7   vendor/thecodingmachine/safe/generated/8.1/datetime.php&      &  "ˤ      3   vendor/thecodingmachine/safe/generated/8.1/info.phpo      o        6   vendor/thecodingmachine/safe/generated/8.1/openssl.phpUq      Uq  ` i      4   vendor/thecodingmachine/safe/generated/8.1/pcntl.phpO      O  ae      5   vendor/thecodingmachine/safe/generated/8.1/stream.php'      '  	      2   vendor/thecodingmachine/safe/generated/8.1/fpm.php(      (  Sg뫤      3   vendor/thecodingmachine/safe/generated/8.1/apcu.phpZ      Z  r*      6   vendor/thecodingmachine/safe/generated/8.1/session.php        Lᥤ      7   vendor/thecodingmachine/safe/generated/8.1/fileinfo.php        %W      3   vendor/thecodingmachine/safe/generated/8.1/oci8.php8      8  ӮK      4   vendor/thecodingmachine/safe/generated/8.1/bzip2.php        n~ɋ      2   vendor/thecodingmachine/safe/generated/8.1/var.phpT      T  D'      2   vendor/thecodingmachine/safe/generated/8.1/com.php

      

  "e      4   vendor/thecodingmachine/safe/generated/8.1/iconv.phpF
      F
  ҧ.A      5   vendor/thecodingmachine/safe/generated/8.1/swoole.php        a\      <   vendor/thecodingmachine/safe/generated/8.1/functionsList.php`      `  U)r      =   vendor/thecodingmachine/safe/generated/8.1/rector-migrate.php        Ȯɤ      6   vendor/thecodingmachine/safe/generated/8.1/network.php`      `  "껤      2   vendor/thecodingmachine/safe/generated/8.1/eio.phpI      I  9FZ      6   vendor/thecodingmachine/safe/generated/8.1/opcache.php        /פ      3   vendor/thecodingmachine/safe/generated/8.1/imap.phpP      P  !Ĥ      3   vendor/thecodingmachine/safe/generated/8.1/solr.phpK      K  '      6   vendor/thecodingmachine/safe/generated/8.1/gettext.php              2   vendor/thecodingmachine/safe/generated/8.1/url.php        k̤      8   vendor/thecodingmachine/safe/generated/8.1/errorfunc.php        3"      2   vendor/thecodingmachine/safe/generated/8.1/lzf.php        (*      5   vendor/thecodingmachine/safe/generated/8.1/mysqli.php]      ]  B      3   vendor/thecodingmachine/safe/generated/8.1/json.php        _      5   vendor/thecodingmachine/safe/generated/8.1/pspell.php        ;      9   vendor/thecodingmachine/safe/generated/8.1/filesystem.phpGI      GI  /~      2   vendor/thecodingmachine/safe/generated/8.1/ftp.phpU&      U&  GJ      4   vendor/thecodingmachine/safe/generated/8.1/array.php	      	  lƤ      1   vendor/thecodingmachine/safe/generated/8.1/ps.php5Y      5Y  Mä      4   vendor/thecodingmachine/safe/generated/8.1/gnupg.php`      `  K      2   vendor/thecodingmachine/safe/generated/8.1/gmp.phpD      D  Nt      2   vendor/thecodingmachine/safe/generated/8.1/zip.phpB	      B	  I:      6   vendor/thecodingmachine/safe/generated/8.1/sockets.phpT(      T(  U      5   vendor/thecodingmachine/safe/generated/8.1/filter.php        qߤ      3   vendor/thecodingmachine/safe/generated/8.1/ssh2.php	-      	-  "      6   vendor/thecodingmachine/safe/generated/8.1/rpminfo.php9      9  L      3   vendor/thecodingmachine/safe/generated/8.1/yaml.php        ò-      4   vendor/thecodingmachine/safe/generated/8.1/mysql.phpT/      T/  zNi      7   vendor/thecodingmachine/safe/generated/8.1/funchand.php
      
  Vu      3   vendor/thecodingmachine/safe/generated/8.1/hash.php`      `        3   vendor/thecodingmachine/safe/generated/8.1/misc.phpn      n        3   vendor/thecodingmachine/safe/generated/8.1/curl.php2      2  E6      7   vendor/thecodingmachine/safe/generated/8.1/calendar.php        s      5   vendor/thecodingmachine/safe/generated/8.1/sodium.php        E$      3   vendor/thecodingmachine/safe/generated/8.1/zlib.php!      !  [$[      4   vendor/thecodingmachine/safe/generated/8.1/image.php        ̤      2   vendor/thecodingmachine/safe/generated/8.1/spl.php         㫤      4   vendor/thecodingmachine/safe/generated/8.1/shmop.php        L >      2   vendor/thecodingmachine/safe/generated/8.1/sem.phpe      e  @Z      2   vendor/thecodingmachine/safe/generated/8.1/rrd.php8      8  jY      3   vendor/thecodingmachine/safe/generated/8.1/exec.phpw
      w
  %'a      2   vendor/thecodingmachine/safe/generated/8.1/xml.php        ݙ)W      5   vendor/thecodingmachine/safe/generated/8.1/ssdeep.phps      s  )ZO      6   vendor/thecodingmachine/safe/generated/8.1/strings.php        ;B      7   vendor/thecodingmachine/safe/generated/8.1/mbstring.php&      &  x@      3   vendor/thecodingmachine/safe/generated/8.1/pcre.php        kD$      4   vendor/thecodingmachine/safe/generated/8.1/posix.php        ZͶ      5   vendor/thecodingmachine/safe/generated/8.1/cubrid.php[      [  $      7   vendor/thecodingmachine/safe/generated/8.1/readline.phpM      M   0      5   vendor/thecodingmachine/safe/generated/8.1/ibmDb2.php        ROƤ      4   vendor/thecodingmachine/safe/generated/8.1/ibase.php^3      ^3  5*      3   vendor/thecodingmachine/safe/generated/8.1/ldap.phpA      A  8M      9   vendor/thecodingmachine/safe/generated/8.1/outcontrol.php
      
  (      5   vendor/thecodingmachine/safe/generated/8.1/xmlrpc.php~      ~  n      2   vendor/thecodingmachine/safe/generated/8.1/dir.phpR	      R	  +vk      8   vendor/thecodingmachine/safe/generated/8.1/mailparse.php
      
  Ch.      5   vendor/thecodingmachine/safe/generated/8.1/sqlsrv.php        {      2   vendor/thecodingmachine/safe/generated/8.1/yaz.php
      
  2Z      7   vendor/thecodingmachine/safe/generated/8.1/classobj.php        GC      5   vendor/thecodingmachine/safe/generated/8.1/apache.php	      	  }      .   vendor/thecodingmachine/safe/generated/xml.php        |Ť      1   vendor/thecodingmachine/safe/generated/ssdeep.php/      /  mɤ      2   vendor/thecodingmachine/safe/generated/strings.php5      5  N!      3   vendor/thecodingmachine/safe/generated/mbstring.php;      ;        /   vendor/thecodingmachine/safe/generated/pcre.php#      #  hڤ      0   vendor/thecodingmachine/safe/generated/posix.php)      )  |Dz      1   vendor/thecodingmachine/safe/generated/cubrid.php/      /        3   vendor/thecodingmachine/safe/generated/readline.php;      ;  bDX      1   vendor/thecodingmachine/safe/generated/ibmDb2.php/      /  %`E      0   vendor/thecodingmachine/safe/generated/ibase.php)      )  ͤ      /   vendor/thecodingmachine/safe/generated/ldap.php#      #  */H      5   vendor/thecodingmachine/safe/generated/outcontrol.phpG      G  U)#      1   vendor/thecodingmachine/safe/generated/xmlrpc.php/      /  yG      .   vendor/thecodingmachine/safe/generated/dir.php        [Z      4   vendor/thecodingmachine/safe/generated/mailparse.phpA      A  y/      1   vendor/thecodingmachine/safe/generated/sqlsrv.php/      /  %      .   vendor/thecodingmachine/safe/generated/yaz.php        '      3   vendor/thecodingmachine/safe/generated/classobj.php;      ;  [      1   vendor/thecodingmachine/safe/generated/apache.php/      /  !      )   vendor/illuminate/container/Container.php2      2        3   vendor/illuminate/container/RewindableGenerator.phpi      i        6   vendor/illuminate/container/EntryNotFoundException.php          1Ҥ      $   vendor/illuminate/container/Util.php        `      +   vendor/illuminate/container/BoundMethod.php        t      8   vendor/illuminate/container/ContextualBindingBuilder.php(	      (	  Ф      D   vendor/illuminate/contracts/Support/CanBeEscapedWhenCastToString.phpI      I  ŝ_0      3   vendor/illuminate/contracts/Support/Responsable.php2      2  e      5   vendor/illuminate/contracts/Support/ValidatedData.php                7   vendor/illuminate/contracts/Support/MessageProvider.php          A       0   vendor/illuminate/contracts/Support/Htmlable.php          뤏#      1   vendor/illuminate/contracts/Support/Arrayable.php        PT      2   vendor/illuminate/contracts/Support/MessageBag.php        g      2   vendor/illuminate/contracts/Support/Renderable.php          _ޤ      :   vendor/illuminate/contracts/Support/DeferrableProvider.php          Tܤ      0   vendor/illuminate/contracts/Support/Jsonable.php          RQؤ      A   vendor/illuminate/contracts/Support/DeferringDisplayableValue.php               5   vendor/illuminate/contracts/Notifications/Factory.php              8   vendor/illuminate/contracts/Notifications/Dispatcher.phpd      d  U
=      .   vendor/illuminate/contracts/Bus/Dispatcher.php        F"      6   vendor/illuminate/contracts/Bus/QueueingDispatcher.php        *&      /   vendor/illuminate/contracts/Validation/Rule.php        7:      9   vendor/illuminate/contracts/Validation/ValidationRule.php        +       2   vendor/illuminate/contracts/Validation/Factory.php        9{      8   vendor/illuminate/contracts/Validation/DataAwareRule.php          qܤ      @   vendor/illuminate/contracts/Validation/UncompromisedVerifier.php        GL      4   vendor/illuminate/contracts/Validation/Validator.phpm      m  D}ۤ      =   vendor/illuminate/contracts/Validation/ValidatorAwareRule.php<      <  ΅      8   vendor/illuminate/contracts/Validation/InvokableRule.php        Գ      @   vendor/illuminate/contracts/Validation/ValidatesWhenResolved.php          >OG      7   vendor/illuminate/contracts/Validation/ImplicitRule.php                H   vendor/illuminate/contracts/Session/Middleware/AuthenticatesSessions.phpf       f   鲤      /   vendor/illuminate/contracts/Session/Session.php7      7  v      1   vendor/illuminate/contracts/Config/Repository.php        jBä      ,   vendor/illuminate/contracts/Auth/Factory.php        "      1   vendor/illuminate/contracts/Auth/UserProvider.phpZ      Z  N0      4   vendor/illuminate/contracts/Auth/Authenticatable.php        ,ƾ      2   vendor/illuminate/contracts/Auth/StatefulGuard.php                E   vendor/illuminate/contracts/Auth/Middleware/AuthenticatesRequests.phpc       c   4v      5   vendor/illuminate/contracts/Auth/CanResetPassword.php        y<(      :   vendor/illuminate/contracts/Auth/PasswordBrokerFactory.php        mM      3   vendor/illuminate/contracts/Auth/PasswordBroker.php	      	  +r      6   vendor/illuminate/contracts/Auth/SupportsBasicAuth.php{      {  ycf      8   vendor/illuminate/contracts/Auth/Access/Authorizable.php6      6  e      0   vendor/illuminate/contracts/Auth/Access/Gate.php        Ǥ      *   vendor/illuminate/contracts/Auth/Guard.phpK      K  o<      4   vendor/illuminate/contracts/Auth/MustVerifyEmail.php        w      6   vendor/illuminate/contracts/Debug/ExceptionHandler.phpK      K  a      =   vendor/illuminate/contracts/Redis/LimiterTimeoutException.phpy       y   c{      -   vendor/illuminate/contracts/Redis/Factory.php
      
  +      /   vendor/illuminate/contracts/Redis/Connector.php|      |        0   vendor/illuminate/contracts/Redis/Connection.php              >   vendor/illuminate/contracts/Console/PromptsForMissingInput.php\       \   S      .   vendor/illuminate/contracts/Console/Kernel.php        t      2   vendor/illuminate/contracts/Console/Isolatable.phpP       P   kdO      3   vendor/illuminate/contracts/Console/Application.php        \.K      3   vendor/illuminate/contracts/Container/Container.php        9=C      E   vendor/illuminate/contracts/Container/CircularDependencyException.php          .      D   vendor/illuminate/contracts/Container/BindingResolutionException.php          k      B   vendor/illuminate/contracts/Container/ContextualBindingBuilder.phpz      z  r      ?   vendor/illuminate/contracts/Pagination/LengthAwarePaginator.php)      )  xPU      :   vendor/illuminate/contracts/Pagination/CursorPaginator.php
      
  z      4   vendor/illuminate/contracts/Pagination/Paginator.php	      	  $:      .   vendor/illuminate/contracts/Cookie/Factory.php        #      6   vendor/illuminate/contracts/Cookie/QueueingFactory.phpk      k  Fz      8   vendor/illuminate/contracts/Database/ModelIdentifier.php        ʄW      H   vendor/illuminate/contracts/Database/Eloquent/CastsInboundAttributes.php        ߈c      A   vendor/illuminate/contracts/Database/Eloquent/CastsAttributes.php        >H      J   vendor/illuminate/contracts/Database/Eloquent/SupportsPartialRelations.php        @j      :   vendor/illuminate/contracts/Database/Eloquent/Castable.php              N   vendor/illuminate/contracts/Database/Eloquent/SerializesCastableAttributes.php        /6B:      9   vendor/illuminate/contracts/Database/Eloquent/Builder.php.      .  _      L   vendor/illuminate/contracts/Database/Eloquent/DeviatesCastableAttributes.php        s      9   vendor/illuminate/contracts/Database/Query/Expression.php7      7  y)      B   vendor/illuminate/contracts/Database/Query/ConditionExpression.phpl       l   +bc      6   vendor/illuminate/contracts/Database/Query/Builder.php          )(Z      >   vendor/illuminate/contracts/Database/Events/MigrationEvent.php\       \   ;ä      +   vendor/illuminate/contracts/Http/Kernel.phpp      p  $      1   vendor/illuminate/contracts/Routing/Registrar.php
      
        3   vendor/illuminate/contracts/Routing/UrlRoutable.phpq      q  'ݤ      8   vendor/illuminate/contracts/Routing/BindingRegistrar.php        A6      4   vendor/illuminate/contracts/Routing/UrlGenerator.php        
      7   vendor/illuminate/contracts/Routing/ResponseFactory.php        Q      :   vendor/illuminate/contracts/Encryption/StringEncrypter.php:      :  b)      ;   vendor/illuminate/contracts/Encryption/EncryptException.php          jJ      ;   vendor/illuminate/contracts/Encryption/DecryptException.php           2v      4   vendor/illuminate/contracts/Encryption/Encrypter.php        nvԤ      2   vendor/illuminate/contracts/Filesystem/Factory.php        Pk      @   vendor/illuminate/contracts/Filesystem/FileNotFoundException.php|       |   1įd      ?   vendor/illuminate/contracts/Filesystem/LockTimeoutException.php{       {   q      5   vendor/illuminate/contracts/Filesystem/Filesystem.php        I      0   vendor/illuminate/contracts/Filesystem/Cloud.php           f      ,   vendor/illuminate/contracts/Mail/Factory.php          3(F      .   vendor/illuminate/contracts/Mail/MailQueue.php        !`4      +   vendor/illuminate/contracts/Mail/Mailer.php        >(ͤ      -   vendor/illuminate/contracts/Mail/Mailable.php        aP      /   vendor/illuminate/contracts/Mail/Attachable.php          @</      1   vendor/illuminate/contracts/Events/Dispatcher.php          y      @   vendor/illuminate/contracts/Events/ShouldDispatchAfterCommit.php^       ^   1A      D   vendor/illuminate/contracts/Events/ShouldHandleEventsAfterCommit.phpb       b   `x      <   vendor/illuminate/contracts/Broadcasting/ShouldBroadcast.php'      '  6      4   vendor/illuminate/contracts/Broadcasting/Factory.php!      !  zΫ      @   vendor/illuminate/contracts/Broadcasting/HasBroadcastChannel.php        vc7R      ?   vendor/illuminate/contracts/Broadcasting/ShouldBroadcastNow.phpu       u   G      8   vendor/illuminate/contracts/Broadcasting/Broadcaster.php;      ;  m$!      ;   vendor/illuminate/contracts/Broadcasting/ShouldBeUnique.phpY       Y   ~      5   vendor/illuminate/contracts/Process/ProcessResult.php              6   vendor/illuminate/contracts/Process/InvokedProcess.php        r(      -   vendor/illuminate/contracts/Cache/Factory.php        AA      :   vendor/illuminate/contracts/Cache/LockTimeoutException.phpv       v   
      +   vendor/illuminate/contracts/Cache/Store.phpm      m  hWΤ      0   vendor/illuminate/contracts/Cache/Repository.phpU      U  Ԥ      2   vendor/illuminate/contracts/Cache/LockProvider.php5      5  o@      *   vendor/illuminate/contracts/Cache/Lock.php3      3  g:o      7   vendor/illuminate/contracts/Foundation/CachesRoutes.php^      ^        <   vendor/illuminate/contracts/Foundation/ExceptionRenderer.php          Eސ      6   vendor/illuminate/contracts/Foundation/Application.php        R&      :   vendor/illuminate/contracts/Foundation/MaintenanceMode.php        b)      >   vendor/illuminate/contracts/Foundation/CachesConfiguration.php        .      .   vendor/illuminate/contracts/Hashing/Hasher.php        T      1   vendor/illuminate/contracts/Queue/ShouldQueue.phpO       O   at      -   vendor/illuminate/contracts/Queue/Factory.php        ;D      7   vendor/illuminate/contracts/Queue/ShouldBeEncrypted.phpU       U   +      +   vendor/illuminate/contracts/Queue/Queue.php	      	  ?      5   vendor/illuminate/contracts/Queue/QueueableEntity.php        Y      9   vendor/illuminate/contracts/Queue/QueueableCollection.php        U7{      4   vendor/illuminate/contracts/Queue/EntityResolver.php        Zɤ      <   vendor/illuminate/contracts/Queue/ShouldQueueAfterCommit.phpn       n         C   vendor/illuminate/contracts/Queue/ShouldBeUniqueUntilProcessing.phpx       x   0Q      -   vendor/illuminate/contracts/Queue/Monitor.php        '      =   vendor/illuminate/contracts/Queue/EntityNotFoundException.php        >pܤ      4   vendor/illuminate/contracts/Queue/ClearableQueue.php          	      )   vendor/illuminate/contracts/Queue/Job.php        d`      4   vendor/illuminate/contracts/Queue/ShouldBeUnique.phpR       R   ql      ,   vendor/illuminate/contracts/View/Factory.php        !ʤ      +   vendor/illuminate/contracts/View/Engine.php	      	  _4      )   vendor/illuminate/contracts/View/View.php        Q      =   vendor/illuminate/contracts/View/ViewCompilationException.phpy       y   xt+פ      2   vendor/illuminate/contracts/Translation/Loader.php0      0  !@      6   vendor/illuminate/contracts/Translation/Translator.php        px      ?   vendor/illuminate/contracts/Translation/HasLocalePreference.php          p      ,   vendor/illuminate/contracts/Pipeline/Hub.php&      &  jB      1   vendor/illuminate/contracts/Pipeline/Pipeline.php        (.+         vendor/webmozart/assert/LICENSE<      <  t}      &   vendor/webmozart/assert/src/Assert.php              %   vendor/webmozart/assert/src/Mixin.phpf     f \I      8   vendor/webmozart/assert/src/InvalidArgumentException.php[      [  *i      7   vendor/thephpf/attestation/resources/trusted-root.jsonlt      t   e      >   vendor/thephpf/attestation/src/FulcioSigstoreOidExtensions.php(      (  3      .   vendor/thephpf/attestation/src/Attestation.php        >]>~      7   vendor/thephpf/attestation/src/FilenameWithChecksum.phpH      H  ku      7   vendor/thephpf/attestation/src/AttestationException.php          4@      L   vendor/thephpf/attestation/src/Verification/VerifyAttestationWithOpenSsl.php<      <  _6      A   vendor/thephpf/attestation/src/Verification/VerifyAttestation.php        d      ]   vendor/thephpf/attestation/src/Verification/Exception/IssuerCertificateVerificationFailed.php4      4  x      H   vendor/thephpf/attestation/src/Verification/Exception/DigestMismatch.php        T<G      P   vendor/thephpf/attestation/src/Verification/Exception/FailedToVerifyArtifact.php                L   vendor/thephpf/attestation/src/Verification/Exception/MissingAttestation.php        }      T   vendor/thephpf/attestation/src/Verification/Exception/MismatchingExtensionValues.php        B7      W   vendor/thephpf/attestation/src/Verification/Exception/InvalidDerEncodedStringLength.php=      =  z\̤      U   vendor/thephpf/attestation/src/Verification/Exception/SignatureVerificationFailed.php        V      Z   vendor/thephpf/attestation/src/Verification/Exception/NoIssuerCertificateInTrustedRoot.php        l      R   vendor/thephpf/attestation/src/Verification/Exception/InvalidSubjectDefinition.php=      =  _G      C   vendor/thephpf/attestation/src/Verification/Exception/NoOpenSsl.php        J]_      ]   vendor/thephpf/attestation/test/integration/Verification/VerifyAttestationWithOpenSslTest.php\      \  x`4      A   vendor/thephpf/attestation/test/unit/FilenameWithChecksumTest.php4      4  EҤ      U   vendor/thephpf/attestation/test/fixture/pie-with-attestation-for-different-owner.phar[     [ P         vendor/react/promise/LICENSEg      g  F      5   vendor/react/promise/src/Internal/RejectedPromise.phpw      w  H_*l      7   vendor/react/promise/src/Internal/CancellationQueue.php        p      6   vendor/react/promise/src/Internal/FulfilledPromise.php=	      =	  m      &   vendor/react/promise/src/functions.php-      -        -   vendor/react/promise/src/PromiseInterface.phpY      Y  aW      $   vendor/react/promise/src/Promise.php(      (  w%      9   vendor/react/promise/src/Exception/CompositeException.phpc      c  &*&      6   vendor/react/promise/src/Exception/LengthException.php^       ^   ?q      %   vendor/react/promise/src/Deferred.php        j1      .   vendor/react/promise/src/functions_include.phpa       a   |      {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": "https://raw.githubusercontent.com/php/pie/main/resources/pie-settings-schema.json",
    "title": "PIE configuration file schema",
    "description": "Schema for PIE tool configuration file",
    "type": "object",
    "properties": {
        "channel": {
            "type": "string",
            "description": "Which update channel to use when running self-update",
            "enum": [
                "nightly",
                "preview",
                "stable"
            ]
        }
    }
}
{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": "https://raw.githubusercontent.com/php/pie/main/resources/composer-json-php-ext-schema.json",
    "title": "composer.json php-ext schema",
    "description": "Schema for the proposed php-ext section in composer.json that the new PECL will use to build packages",
    "type": "object",
    "properties": {
        "php-ext": {
            "type": "object",
            "description": "Settings for PHP extension packages.",
            "properties": {
                "extension-name": {
                    "type": "string",
                    "description": "If specified, this will be used as the name of the extension, where needed by tooling. If this is not specified, the extension name will be derived from the Composer package name (e.g. `vendor/name` would become `ext-name`). The extension name may be specified with or without the `ext-` prefix, and tools that use this must normalise this appropriately.",
                    "example": "ext-xdebug"
                },
                "priority": {
                    "type": "integer",
                    "description": "This is used to add a prefix to the INI file, e.g. `90-xdebug.ini` which affects the loading order. The priority is a number in the range 10-99 inclusive, with 10 being the highest priority (i.e. will be processed first), and 99 being the lowest priority (i.e. will be processed last). There are two digits so that the files sort correctly on any platform, whether the sorting is natural or not.",
                    "minimum": 10,
                    "maximum": 99,
                    "example": 80,
                    "default": 80
                },
                "support-zts": {
                    "type": "boolean",
                    "description": "Does this package support Zend Thread Safety",
                    "example": false,
                    "default": true
                },
                "support-nts": {
                    "type": "boolean",
                    "description": "Does this package support non-Thread Safe mode",
                    "example": false,
                    "default": true
                },
                "build-path": {
                    "type": ["string", "null"],
                    "description": "If specified, this is the subdirectory that will be used to build the extension instead of the root of the project.",
                    "example": "my-extension-source",
                    "default": null
                },
                "download-url-method": {
                    "oneOf": [
                        {
                            "type": "string",
                            "description": "If specified, this technique will be used to override the URL that PIE uses to download the asset. The default, if not specified, is composer-default.",
                            "deprecated": true,
                            "enum": ["composer-default", "pre-packaged-source", "pre-packaged-binary"],
                            "example": "composer-default",
                            "default": "composer-default"
                        },
                        {
                            "type": "array",
                            "description": "Multiple techniques can be specified, in which case PIE will try each in turn until one succeeds. The first technique that succeeds will be used.",
                            "items": {
                                "type": "string",
                                "description": "If specified, this technique will be used to override the URL that PIE uses to download the asset. The default, if not specified, is composer-default.",
                                "enum": ["composer-default", "pre-packaged-source", "pre-packaged-binary"],
                                "example": ["pre-packaged-binary", "composer-default"]
                            },
                            "minItems": 1,
                            "default": ["composer-default"]
                        }
                    ]
                },
                "os-families": {
                    "type": "array",
                    "minItems": 1,
                    "description": "An array of OS families to mark as compatible with the extension. Specifying this property will mean this package is not installable with PIE on any OS family not listed here. Must not be specified alongside os-families-exclude.",
                    "items": {
                        "type": "string",
                        "enum": ["windows", "bsd", "darwin", "solaris", "linux", "unknown"],
                        "description": "The name of the OS family to mark as compatible."
                    }
                },
                "os-families-exclude": {
                    "type": "array",
                    "minItems": 1,
                    "description": "An array of OS families to mark as incompatible with the extension. Specifying this property will mean this package is installable on any OS family except those listed here. Must not be specified alongside os-families.",
                    "items": {
                        "type": "string",
                        "enum": ["windows", "bsd", "darwin", "solaris", "linux", "unknown"],
                        "description": "The name of the OS family to exclude."
                    }
                },
                "configure-options": {
                    "type": "array",
                    "description": "These configure options make up the flags that can be passed to ./configure when installing the extension.",
                    "items": {
                        "type": "object",
                        "required": ["name"],
                        "properties": {
                            "name": {
                                "type": "string",
                                "description": "The name of the flag, this would typically be prefixed with `--`, for example, the value 'the-flag' would be passed as `./configure --the-flag`.",
                                "example": "without-xdebug-compression",
                                "pattern": "^[a-zA-Z0-9][a-zA-Z0-9-_]*$"
                            },
                            "needs-value": {
                                "type": "boolean",
                                "description": "If this is set to true, the flag needs a value (e.g. --with-somelib=<path>), otherwise it is a flag without a value (e.g. --enable-some-feature).",
                                "example": false,
                                "default": false
                            },
                            "description": {
                                "type": "string",
                                "description": "The description of what the flag does or means.",
                                "example": "Disable compression through zlib"
                            }
                        }
                    }
                }
            },
            "allOf": [
                {
                    "not": {
                        "required": ["os-families", "os-families-exclude"]
                    }
                }
            ]
        }
    }
}
#!/usr/bin/env bash

# A little test build script for manually building a self-contained PIE executable
# Note; needs `spc` pre-installed:
# curl -fsSL -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-x86_64
# chmod +x spc

set -xeuo pipefail

SPC_ROOT=$(dirname "$0")
PIE_PROJECT_ROOT="$SPC_ROOT/../.."

# First up, build the regular pie.phar
cd "$PIE_PROJECT_ROOT"
php box.phar compile
mv pie.phar "$SPC_ROOT/pie.phar"

cd "$SPC_ROOT"

# Build the static PHP micro.sfx
./spc craft

# Combine pie.phar with the micro.sfx
./spc micro:combine pie.phar --output=pie.elf

# Docker build & run the test with it
docker build --file spc.Dockerfile --tag pie-spc-test .
docker run --rm -ti pie-spc-test
php-version: 8.4
extensions: "curl,filter,iconv,openssl,phar,zlib"
sapi: micro
download-options:
  prefer-pre-built: true
# docker build --file spc.Dockerfile --tag pie-spc-test .
# docker run --rm -ti pie-spc-test
FROM php:7.4-cli

# need
RUN apt-get update && \
    apt-get install -y git

COPY pie.elf /usr/bin/pie

CMD ["pie", "install", "asgrim/example-pie-extension"]
<?php

declare (strict_types=1);
namespace HumbugBox4610\KevinGH\RequirementChecker;

use function extension_loaded;
final class IsExtensionConflictFulfilled implements IsFulfilled
{
    private $conflictingExtension;
    public function __construct(string $requiredExtension)
    {
        $this->conflictingExtension = $requiredExtension;
    }
    public function __invoke(): bool
    {
        return !extension_loaded($this->conflictingExtension);
    }
}
<?php

declare (strict_types=1);
namespace HumbugBox4610\KevinGH\RequirementChecker;

use InvalidArgumentException;
use function count;
use function sprintf;
final class Checker
{
    private static $requirementsConfig;
    public static function checkRequirements(): bool
    {
        $requirements = self::retrieveRequirements();
        $checkPassed = $requirements->evaluateRequirements();
        $io = new IO();
        self::printCheck($checkPassed, new Printer($io->getVerbosity(), $io->hasColorSupport()), $requirements);
        return $checkPassed;
    }
    public static function printCheck($checkPassed, Printer $printer, RequirementCollection $requirements): void
    {
        if (\false === $checkPassed && IO::VERBOSITY_VERY_VERBOSE > $printer->getVerbosity()) {
            $printer->setVerbosity(IO::VERBOSITY_VERY_VERBOSE);
        }
        $verbosity = IO::VERBOSITY_VERY_VERBOSE;
        $iniPath = $requirements->getPhpIniPath();
        $printer->title('Box Requirements Checker', $verbosity);
        $printer->printv('> Using PHP ', $verbosity);
        $printer->printvln(\PHP_VERSION, $verbosity, 'green');
        if ($iniPath) {
            $printer->printvln('> PHP is using the following php.ini file:', $verbosity);
            $printer->printvln('  ' . $iniPath, $verbosity, 'green');
        } else {
            $printer->printvln('> PHP is not using any php.ini file.', $verbosity, 'yellow');
        }
        $printer->printvln('', $verbosity);
        if (count($requirements) > 0) {
            $printer->printvln('> Checking Box requirements:', $verbosity);
            $printer->printv('  ', $verbosity);
        } else {
            $printer->printvln('> No requirements found.', $verbosity);
        }
        $errorMessages = [];
        foreach ($requirements->getRequirements() as $requirement) {
            if ($errorMessage = $printer->getRequirementErrorMessage($requirement)) {
                if (IO::VERBOSITY_DEBUG === $printer->getVerbosity()) {
                    $printer->printvln('✘ ' . $requirement->getTestMessage(), IO::VERBOSITY_DEBUG, 'red');
                    $printer->printv('  ', IO::VERBOSITY_DEBUG);
                    $errorMessages[] = $errorMessage;
                } else {
                    $printer->printv('E', $verbosity, 'red');
                    $errorMessages[] = $errorMessage;
                }
                continue;
            }
            if (IO::VERBOSITY_DEBUG === $printer->getVerbosity()) {
                $printer->printvln('✔ ' . $requirement->getTestMessage(), IO::VERBOSITY_DEBUG, 'green');
                $printer->printv('  ', IO::VERBOSITY_DEBUG);
            } else {
                $printer->printv('.', $verbosity, 'green');
            }
        }
        if (IO::VERBOSITY_DEBUG !== $printer->getVerbosity() && count($requirements) > 0) {
            $printer->printvln('', $verbosity);
        }
        if ($requirements->evaluateRequirements()) {
            $printer->block('OK', 'Your system is ready to run the application.', $verbosity, 'success');
        } else {
            $printer->block('ERROR', 'Your system is not ready to run the application.', $verbosity, 'error');
            $printer->title('Fix the following mandatory requirements:', $verbosity, 'red');
            foreach ($errorMessages as $errorMessage) {
                $printer->printv(' * ' . $errorMessage, $verbosity);
            }
        }
        $printer->printvln('', $verbosity);
    }
    private static function retrieveRequirements(): RequirementCollection
    {
        if (null === self::$requirementsConfig) {
            self::$requirementsConfig = __DIR__ . '/../.requirements.php';
        }
        $config = require self::$requirementsConfig;
        $requirements = new RequirementCollection();
        foreach ($config as $constraint) {
            $requirements->addRequirement(self::createCondition($constraint['type'], $constraint['condition']), $constraint['message'], $constraint['helpMessage']);
        }
        return $requirements;
    }
    private static function createCondition($type, $condition): IsFulfilled
    {
        switch ($type) {
            case 'php':
                return new IsPhpVersionFulfilled($condition);
            case 'extension':
                return new IsExtensionFulfilled($condition);
            case 'extension-conflict':
                return new IsExtensionConflictFulfilled($condition);
            default:
                throw new InvalidArgumentException(sprintf('Unknown requirement type "%s".', $type));
        }
    }
}
<?php

declare (strict_types=1);
namespace HumbugBox4610\KevinGH\RequirementChecker;

interface IsFulfilled
{
    public function __invoke(): bool;
}
<?php

declare (strict_types=1);
namespace HumbugBox4610\KevinGH\RequirementChecker;

use ArrayIterator;
use Countable;
use IteratorAggregate;
use Traversable;
use function count;
use function get_cfg_var;
final class RequirementCollection implements IteratorAggregate, Countable
{
    private $requirements = [];
    private $phpIniPath;
    public function __construct($phpIniPath = null)
    {
        $this->phpIniPath = $phpIniPath ?? get_cfg_var('cfg_file_path');
    }
    public function getIterator(): Traversable
    {
        return new ArrayIterator($this->requirements);
    }
    public function count(): int
    {
        return count($this->requirements);
    }
    public function add(Requirement $requirement): void
    {
        $this->requirements[] = $requirement;
    }
    public function addRequirement(IsFulfilled $checkIsFulfilled, string $testMessage, string $helpText): void
    {
        $this->add(new Requirement($checkIsFulfilled, $testMessage, $helpText));
    }
    public function getRequirements(): array
    {
        return $this->requirements;
    }
    public function getPhpIniPath()
    {
        return $this->phpIniPath;
    }
    public function evaluateRequirements()
    {
        return array_reduce($this->requirements, static function (bool $checkPassed, Requirement $requirement): bool {
            return $checkPassed && $requirement->isFulfilled();
        }, \true);
    }
}
<?php

declare (strict_types=1);
namespace HumbugBox4610\KevinGH\RequirementChecker;

use function exec;
use function fclose;
use function fopen;
use function function_exists;
use function getenv;
use function preg_match;
use function proc_close;
use function proc_open;
use function sapi_windows_cp_get;
use function sapi_windows_cp_set;
use function sapi_windows_vt100_support;
use function stream_get_contents;
use function trim;
use const DIRECTORY_SEPARATOR;
class Terminal
{
    private static $width;
    private static $height;
    private static $stty;
    public function getWidth(): int
    {
        $width = getenv('COLUMNS');
        if (\false !== $width) {
            return (int) trim($width);
        }
        if (!isset(self::$width)) {
            self::initDimensions();
        }
        return self::$width ?: 80;
    }
    public function getHeight(): int
    {
        $height = getenv('LINES');
        if (\false !== $height) {
            return (int) trim($height);
        }
        if (!isset(self::$height)) {
            self::initDimensions();
        }
        return self::$height ?: 50;
    }
    public static function hasSttyAvailable(): bool
    {
        if (isset(self::$stty)) {
            return self::$stty;
        }
        if (!function_exists('exec')) {
            return \false;
        }
        exec('stty 2>&1', $output, $exitcode);
        return self::$stty = 0 === $exitcode;
    }
    private static function initDimensions(): void
    {
        if ('\\' === DIRECTORY_SEPARATOR) {
            if (preg_match('/^(\d+)x(\d+)(?: \((\d+)x(\d+)\))?$/', trim(getenv('ANSICON') ?: ''), $matches)) {
                self::$width = (int) $matches[1];
                self::$height = isset($matches[4]) ? (int) $matches[4] : (int) $matches[2];
            } elseif (!self::hasVt100Support() && self::hasSttyAvailable()) {
                self::initDimensionsUsingStty();
            } elseif (null !== $dimensions = self::getConsoleMode()) {
                self::$width = (int) $dimensions[0];
                self::$height = (int) $dimensions[1];
            }
        } else {
            self::initDimensionsUsingStty();
        }
    }
    private static function hasVt100Support(): bool
    {
        return function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support(fopen('php://stdout', 'wb'));
    }
    private static function initDimensionsUsingStty(): void
    {
        if ($sttyString = self::getSttyColumns()) {
            if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) {
                self::$width = (int) $matches[2];
                self::$height = (int) $matches[1];
            } elseif (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) {
                self::$width = (int) $matches[2];
                self::$height = (int) $matches[1];
            }
        }
    }
    private static function getConsoleMode(): ?array
    {
        $info = self::readFromProcess('mode CON');
        if (null === $info || !preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
            return null;
        }
        return [(int) $matches[2], (int) $matches[1]];
    }
    private static function getSttyColumns(): ?string
    {
        return self::readFromProcess('stty -a | grep columns');
    }
    private static function readFromProcess(string $command): ?string
    {
        if (!function_exists('proc_open')) {
            return null;
        }
        $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
        $cp = function_exists('sapi_windows_cp_set') ? sapi_windows_cp_get() : 0;
        if (!$process = @proc_open($command, $descriptorspec, $pipes, null, null, ['suppress_errors' => \true])) {
            return null;
        }
        $info = stream_get_contents($pipes[1]);
        fclose($pipes[1]);
        fclose($pipes[2]);
        proc_close($process);
        if ($cp) {
            sapi_windows_cp_set($cp);
        }
        return $info;
    }
}
<?php

declare (strict_types=1);
namespace HumbugBox4610\KevinGH\RequirementChecker;

use HumbugBox4610\Composer\Semver\Semver;
use function sprintf;
use const PHP_MAJOR_VERSION;
use const PHP_MINOR_VERSION;
use const PHP_RELEASE_VERSION;
final class IsPhpVersionFulfilled implements IsFulfilled
{
    private $requiredPhpVersion;
    public function __construct(string $requiredPhpVersion)
    {
        $this->requiredPhpVersion = $requiredPhpVersion;
    }
    public function __invoke(): bool
    {
        return Semver::satisfies(sprintf('%d.%d.%d', PHP_MAJOR_VERSION, PHP_MINOR_VERSION, PHP_RELEASE_VERSION), $this->requiredPhpVersion);
    }
}
<?php

declare (strict_types=1);
namespace HumbugBox4610\KevinGH\RequirementChecker;

use function array_shift;
use function count;
use function explode;
use function ltrim;
use function min;
use function sprintf;
use function str_pad;
use function str_repeat;
use function strlen;
use function trim;
use function wordwrap;
use const PHP_EOL;
final class Printer
{
    private $styles = ['reset' => "\x1b[0m", 'red' => "\x1b[31m", 'green' => "\x1b[32m", 'yellow' => "\x1b[33m", 'title' => "\x1b[33m", 'error' => "\x1b[37;41m", 'success' => "\x1b[30;42m"];
    private $verbosity;
    private $supportColors;
    private $width;
    public function __construct(int $verbosity, bool $supportColors, ?int $width = null)
    {
        if (null === $width) {
            $terminal = new Terminal();
            $width = $terminal->getWidth();
        }
        $this->verbosity = $verbosity;
        $this->supportColors = $supportColors;
        $this->width = $width ?: 80;
    }
    public function getVerbosity(): int
    {
        return $this->verbosity;
    }
    public function setVerbosity($verbosity): void
    {
        $this->verbosity = $verbosity;
    }
    public function title(string $title, int $verbosity, ?string $style = null): void
    {
        if (null === $style) {
            $style = 'title';
        }
        $this->printvln('', $verbosity, $style);
        $this->printvln($title, $verbosity, $style);
        $this->printvln(str_repeat('=', min(strlen($title), $this->width)), $verbosity, $style);
        $this->printvln('', $verbosity, $style);
    }
    public function getRequirementErrorMessage(Requirement $requirement): ?string
    {
        if ($requirement->isFulfilled()) {
            return null;
        }
        return wordwrap($requirement->getHelpText(), $this->width - 3, PHP_EOL . '   ') . PHP_EOL;
    }
    public function block(string $title, string $message, int $verbosity, ?string $style = null): void
    {
        $prefix = ' [' . $title . '] ';
        $lineLength = $this->width - strlen($prefix) - 1;
        if ($lineLength < 0) {
            $lineLength = 0;
        }
        $message = $prefix . trim($message);
        $lines = [];
        $remainingMessage = $message;
        $wrapped = wordwrap($remainingMessage, $lineLength, '¬');
        $wrapped = explode('¬', $wrapped);
        do {
            $line = array_shift($wrapped);
            if ($lines && $lineLength > 0) {
                $line = str_repeat(' ', strlen($prefix)) . ltrim($line);
            }
            $lines[] = str_pad($line, $this->width, ' ', \STR_PAD_RIGHT);
        } while (count($wrapped));
        $this->printvln('', $verbosity);
        $this->printvln(str_repeat(' ', $this->width), $verbosity, $style);
        foreach ($lines as $line) {
            $this->printvln($line, $verbosity, $style);
        }
        $this->printv(str_repeat(' ', $this->width), $verbosity, $style);
        $this->printvln('', $verbosity);
    }
    public function printvln(string $message, int $verbosity, ?string $style = null): void
    {
        $this->printv($message, $verbosity, $style);
        $this->printv(PHP_EOL, $verbosity, null);
    }
    public function printv(string $message, int $verbosity, ?string $style = null): void
    {
        if ($verbosity > $this->verbosity) {
            return;
        }
        $message = wordwrap($message, $this->width);
        $message = sprintf('%s%s%s', $this->supportColors && isset($this->styles[$style]) ? $this->styles[$style] : '', $message, $this->supportColors ? $this->styles['reset'] : '');
        if ('1' === getenv('BOX_REQUIREMENTS_CHECKER_LOG_TO_STDOUT')) {
            echo $message;
        } else {
            fwrite(\STDERR, $message);
        }
    }
}
<?php

declare (strict_types=1);
namespace HumbugBox4610\KevinGH\RequirementChecker;

final class Requirement
{
    private $checkIsFulfilled;
    private $fulfilled;
    private $testMessage;
    private $helpText;
    public function __construct(IsFulfilled $checkIsFulfilled, string $testMessage, string $helpText)
    {
        $this->checkIsFulfilled = $checkIsFulfilled;
        $this->testMessage = $testMessage;
        $this->helpText = $helpText;
    }
    public function isFulfilled(): bool
    {
        if (!isset($this->fulfilled)) {
            $this->fulfilled = $this->checkIsFulfilled->__invoke();
        }
        return $this->fulfilled;
    }
    public function getIsFullfilledChecker(): IsFulfilled
    {
        return $this->checkIsFulfilled;
    }
    public function getTestMessage(): string
    {
        return $this->testMessage;
    }
    public function getHelpText(): string
    {
        return $this->helpText;
    }
}
<?php

declare (strict_types=1);
namespace HumbugBox4610\KevinGH\RequirementChecker;

use function fstat;
use function function_exists;
use function getenv;
use function implode;
use function posix_isatty;
use function preg_match;
use function preg_quote;
use function sapi_windows_vt100_support;
use function sprintf;
use function str_replace;
use function stream_isatty;
use const DIRECTORY_SEPARATOR;
use const STDOUT;
final class IO
{
    public const VERBOSITY_QUIET = 16;
    public const VERBOSITY_NORMAL = 32;
    public const VERBOSITY_VERBOSE = 64;
    public const VERBOSITY_VERY_VERBOSE = 128;
    public const VERBOSITY_DEBUG = 256;
    private $interactive;
    private $verbosity = self::VERBOSITY_NORMAL;
    private $colorSupport;
    private $options;
    public function __construct()
    {
        $this->options = implode(' ', $_SERVER['argv']);
        $shellVerbosity = $this->configureVerbosity();
        $this->interactive = $this->checkInteractivity($shellVerbosity);
        $this->colorSupport = $this->checkColorSupport();
    }
    public function isInteractive(): bool
    {
        return $this->interactive;
    }
    public function getVerbosity(): int
    {
        return $this->verbosity;
    }
    public function hasColorSupport(): bool
    {
        return $this->colorSupport;
    }
    public function hasParameter($values): bool
    {
        $values = (array) $values;
        foreach ($values as $value) {
            $regexp = sprintf('/\s%s\b/', str_replace(' ', '\s+', preg_quote($value, '/')));
            if (1 === preg_match($regexp, $this->options)) {
                return \true;
            }
        }
        return \false;
    }
    private function checkInteractivity(int $shellVerbosity): bool
    {
        if (-1 === $shellVerbosity) {
            return \false;
        }
        if (\true === $this->hasParameter(['--no-interaction', '-n'])) {
            return \false;
        }
        if (function_exists('posix_isatty') && !@posix_isatty(STDOUT) && \false === getenv('SHELL_INTERACTIVE')) {
            return \false;
        }
        return \true;
    }
    private function configureVerbosity(): int
    {
        switch ($shellVerbosity = (int) getenv('SHELL_VERBOSITY')) {
            case -1:
                $this->verbosity = self::VERBOSITY_QUIET;
                break;
            case 1:
                $this->verbosity = self::VERBOSITY_VERBOSE;
                break;
            case 2:
                $this->verbosity = self::VERBOSITY_VERY_VERBOSE;
                break;
            case 3:
                $this->verbosity = self::VERBOSITY_DEBUG;
                break;
            default:
                $shellVerbosity = 0;
                break;
        }
        if ($this->hasParameter(['--quiet', '-q'])) {
            $this->verbosity = self::VERBOSITY_QUIET;
            $shellVerbosity = -1;
        } elseif ($this->hasParameter(['-vvv', '--verbose=3', '--verbose 3'])) {
            $this->verbosity = self::VERBOSITY_DEBUG;
            $shellVerbosity = 3;
        } elseif ($this->hasParameter(['-vv', '--verbose=2', '--verbose 2'])) {
            $this->verbosity = self::VERBOSITY_VERY_VERBOSE;
            $shellVerbosity = 2;
        } elseif ($this->hasParameter(['-v', '--verbose=1', '--verbose 1', '--verbose'])) {
            $this->verbosity = self::VERBOSITY_VERBOSE;
            $shellVerbosity = 1;
        }
        return $shellVerbosity;
    }
    private function checkColorSupport(): bool
    {
        if ($this->hasParameter(['--ansi'])) {
            return \true;
        }
        if ($this->hasParameter(['--no-ansi'])) {
            return \false;
        }
        if (DIRECTORY_SEPARATOR === '\\') {
            return function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support(STDOUT) || \false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') || 'xterm' === getenv('TERM');
        }
        if (function_exists('stream_isatty')) {
            return stream_isatty(STDOUT);
        }
        if (function_exists('posix_isatty')) {
            return posix_isatty(STDOUT);
        }
        $stat = fstat(STDOUT);
        return $stat ? 020000 === ($stat['mode'] & 0170000) : \false;
    }
}
<?php

declare (strict_types=1);
namespace HumbugBox4610\KevinGH\RequirementChecker;

use function extension_loaded;
final class IsExtensionFulfilled implements IsFulfilled
{
    private $requiredExtension;
    public function __construct(string $requiredExtension)
    {
        $this->requiredExtension = $requiredExtension;
    }
    public function __invoke(): bool
    {
        return extension_loaded($this->requiredExtension);
    }
}
<?php

namespace HumbugBox4610\KevinGH\RequirementChecker;

if (isset($_SERVER['BOX_REQUIREMENT_CHECKER'])) {
    $enableRequirementChecker = $_SERVER['BOX_REQUIREMENT_CHECKER'];
    if (is_bool($enableRequirementChecker) && !$enableRequirementChecker) {
        return;
    }
    if (is_string($enableRequirementChecker) && in_array(strtolower($enableRequirementChecker), ['false', '0'], \true)) {
        return;
    }
    if (!is_bool($enableRequirementChecker) && !is_string($enableRequirementChecker)) {
        echo \PHP_EOL . 'Unhandled value type for "BOX_REQUIREMENT_CHECKER". Got "' . gettype($enableRequirementChecker) . '". Proceeding with the requirement checks.' . \PHP_EOL;
    }
}
if (\false === in_array(\PHP_SAPI, array('cli', 'phpdbg', 'embed', 'micro'), \true)) {
    echo \PHP_EOL . 'The application may only be invoked from a command line, got "' . \PHP_SAPI . '"' . \PHP_EOL;
    exit(1);
}
require __DIR__ . '/../vendor/autoload.php';
if (!Checker::checkRequirements()) {
    exit(1);
}
<?php

namespace _HumbugBox6ab58bf96c33;

return array (
  0 => 
  array (
    'type' => 'php',
    'condition' => '8.1.*||8.2.*||8.3.*||8.4.*||8.5.*',
    'source' => NULL,
    'message' => 'This application requires a PHP version matching "8.1.*||8.2.*||8.3.*||8.4.*||8.5.*".',
    'helpMessage' => 'This application requires a PHP version matching "8.1.*||8.2.*||8.3.*||8.4.*||8.5.*".',
  ),
  1 => 
  array (
    'type' => 'extension',
    'condition' => 'date',
    'source' => 'webmozart/assert',
    'message' => 'The package "webmozart/assert" requires the extension "date".',
    'helpMessage' => 'The package "webmozart/assert" requires the extension "date". You either need to enable it or request the application to be shipped with a polyfill for this extension.',
  ),
  2 => 
  array (
    'type' => 'extension',
    'condition' => 'filter',
    'source' => 'webmozart/assert',
    'message' => 'The package "webmozart/assert" requires the extension "filter".',
    'helpMessage' => 'The package "webmozart/assert" requires the extension "filter". You either need to enable it or request the application to be shipped with a polyfill for this extension.',
  ),
  3 => 
  array (
    'type' => 'extension',
    'condition' => 'iconv',
    'source' => 'symfony/polyfill-mbstring',
    'message' => 'The package "symfony/polyfill-mbstring" requires the extension "iconv".',
    'helpMessage' => 'The package "symfony/polyfill-mbstring" requires the extension "iconv". You either need to enable it or request the application to be shipped with a polyfill for this extension.',
  ),
  4 => 
  array (
    'type' => 'extension',
    'condition' => 'json',
    'source' => 'composer/composer',
    'message' => 'The package "composer/composer" requires the extension "json".',
    'helpMessage' => 'The package "composer/composer" requires the extension "json". You either need to enable it or request the application to be shipped with a polyfill for this extension.',
  ),
  5 => 
  array (
    'type' => 'extension',
    'condition' => 'json',
    'source' => 'justinrainbow/json-schema',
    'message' => 'The package "justinrainbow/json-schema" requires the extension "json".',
    'helpMessage' => 'The package "justinrainbow/json-schema" requires the extension "json". You either need to enable it or request the application to be shipped with a polyfill for this extension.',
  ),
  6 => 
  array (
    'type' => 'extension',
    'condition' => 'json',
    'source' => 'thephpf/attestation',
    'message' => 'The package "thephpf/attestation" requires the extension "json".',
    'helpMessage' => 'The package "thephpf/attestation" requires the extension "json". You either need to enable it or request the application to be shipped with a polyfill for this extension.',
  ),
  7 => 
  array (
    'type' => 'extension',
    'condition' => 'openssl',
    'source' => 'composer/ca-bundle',
    'message' => 'The package "composer/ca-bundle" requires the extension "openssl".',
    'helpMessage' => 'The package "composer/ca-bundle" requires the extension "openssl". You either need to enable it or request the application to be shipped with a polyfill for this extension.',
  ),
  8 => 
  array (
    'type' => 'extension',
    'condition' => 'pcre',
    'source' => 'composer/ca-bundle',
    'message' => 'The package "composer/ca-bundle" requires the extension "pcre".',
    'helpMessage' => 'The package "composer/ca-bundle" requires the extension "pcre". You either need to enable it or request the application to be shipped with a polyfill for this extension.',
  ),
  9 => 
  array (
    'type' => 'extension',
    'condition' => 'reflection',
    'source' => 'marc-mabe/php-enum',
    'message' => 'The package "marc-mabe/php-enum" requires the extension "reflection".',
    'helpMessage' => 'The package "marc-mabe/php-enum" requires the extension "reflection". You either need to enable it or request the application to be shipped with a polyfill for this extension.',
  ),
  10 => 
  array (
    'type' => 'extension-conflict',
    'condition' => 'psr',
    'source' => 'symfony/service-contracts',
    'message' => 'The package "symfony/service-contracts" conflicts with the extension "psr".',
    'helpMessage' => 'The package "symfony/service-contracts" conflicts with the extension "psr". You need to disable it in order to run this application.',
  ),
);<?php

// autoload.php @generated by Composer

if (PHP_VERSION_ID < 50600) {
    if (!headers_sent()) {
        header('HTTP/1.1 500 Internal Server Error');
    }
    $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
    if (!ini_get('display_errors')) {
        if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
            fwrite(STDERR, $err);
        } elseif (!headers_sent()) {
            echo $err;
        }
    }
    throw new RuntimeException($err);
}

require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInitHumbugBox4610::getLoader();
<?php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Autoload;

/**
 * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
 *
 *     $loader = new \Composer\Autoload\ClassLoader();
 *
 *     // register classes with namespaces
 *     $loader->add('Symfony\Component', __DIR__.'/component');
 *     $loader->add('Symfony',           __DIR__.'/framework');
 *
 *     // activate the autoloader
 *     $loader->register();
 *
 *     // to enable searching the include path (eg. for PEAR packages)
 *     $loader->setUseIncludePath(true);
 *
 * In this example, if you try to use a class in the Symfony\Component
 * namespace or one of its children (Symfony\Component\Console for instance),
 * the autoloader will first look for the class under the component/
 * directory, and it will then fallback to the framework/ directory if not
 * found before giving up.
 *
 * This class is loosely based on the Symfony UniversalClassLoader.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @see    https://www.php-fig.org/psr/psr-0/
 * @see    https://www.php-fig.org/psr/psr-4/
 */
class ClassLoader
{
    /** @var \Closure(string):void */
    private static $includeFile;

    /** @var string|null */
    private $vendorDir;

    // PSR-4
    /**
     * @var array<string, array<string, int>>
     */
    private $prefixLengthsPsr4 = array();
    /**
     * @var array<string, list<string>>
     */
    private $prefixDirsPsr4 = array();
    /**
     * @var list<string>
     */
    private $fallbackDirsPsr4 = array();

    // PSR-0
    /**
     * List of PSR-0 prefixes
     *
     * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
     *
     * @var array<string, array<string, list<string>>>
     */
    private $prefixesPsr0 = array();
    /**
     * @var list<string>
     */
    private $fallbackDirsPsr0 = array();

    /** @var bool */
    private $useIncludePath = false;

    /**
     * @var array<string, string>
     */
    private $classMap = array();

    /** @var bool */
    private $classMapAuthoritative = false;

    /**
     * @var array<string, bool>
     */
    private $missingClasses = array();

    /** @var string|null */
    private $apcuPrefix;

    /**
     * @var array<string, self>
     */
    private static $registeredLoaders = array();

    /**
     * @param string|null $vendorDir
     */
    public function __construct($vendorDir = null)
    {
        $this->vendorDir = $vendorDir;
        self::initializeIncludeClosure();
    }

    /**
     * @return array<string, list<string>>
     */
    public function getPrefixes()
    {
        if (!empty($this->prefixesPsr0)) {
            return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
        }

        return array();
    }

    /**
     * @return array<string, list<string>>
     */
    public function getPrefixesPsr4()
    {
        return $this->prefixDirsPsr4;
    }

    /**
     * @return list<string>
     */
    public function getFallbackDirs()
    {
        return $this->fallbackDirsPsr0;
    }

    /**
     * @return list<string>
     */
    public function getFallbackDirsPsr4()
    {
        return $this->fallbackDirsPsr4;
    }

    /**
     * @return array<string, string> Array of classname => path
     */
    public function getClassMap()
    {
        return $this->classMap;
    }

    /**
     * @param array<string, string> $classMap Class to filename map
     *
     * @return void
     */
    public function addClassMap(array $classMap)
    {
        if ($this->classMap) {
            $this->classMap = array_merge($this->classMap, $classMap);
        } else {
            $this->classMap = $classMap;
        }
    }

    /**
     * Registers a set of PSR-0 directories for a given prefix, either
     * appending or prepending to the ones previously set for this prefix.
     *
     * @param string              $prefix  The prefix
     * @param list<string>|string $paths   The PSR-0 root directories
     * @param bool                $prepend Whether to prepend the directories
     *
     * @return void
     */
    public function add($prefix, $paths, $prepend = false)
    {
        $paths = (array) $paths;
        if (!$prefix) {
            if ($prepend) {
                $this->fallbackDirsPsr0 = array_merge(
                    $paths,
                    $this->fallbackDirsPsr0
                );
            } else {
                $this->fallbackDirsPsr0 = array_merge(
                    $this->fallbackDirsPsr0,
                    $paths
                );
            }

            return;
        }

        $first = $prefix[0];
        if (!isset($this->prefixesPsr0[$first][$prefix])) {
            $this->prefixesPsr0[$first][$prefix] = $paths;

            return;
        }
        if ($prepend) {
            $this->prefixesPsr0[$first][$prefix] = array_merge(
                $paths,
                $this->prefixesPsr0[$first][$prefix]
            );
        } else {
            $this->prefixesPsr0[$first][$prefix] = array_merge(
                $this->prefixesPsr0[$first][$prefix],
                $paths
            );
        }
    }

    /**
     * Registers a set of PSR-4 directories for a given namespace, either
     * appending or prepending to the ones previously set for this namespace.
     *
     * @param string              $prefix  The prefix/namespace, with trailing '\\'
     * @param list<string>|string $paths   The PSR-4 base directories
     * @param bool                $prepend Whether to prepend the directories
     *
     * @throws \InvalidArgumentException
     *
     * @return void
     */
    public function addPsr4($prefix, $paths, $prepend = false)
    {
        $paths = (array) $paths;
        if (!$prefix) {
            // Register directories for the root namespace.
            if ($prepend) {
                $this->fallbackDirsPsr4 = array_merge(
                    $paths,
                    $this->fallbackDirsPsr4
                );
            } else {
                $this->fallbackDirsPsr4 = array_merge(
                    $this->fallbackDirsPsr4,
                    $paths
                );
            }
        } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
            // Register directories for a new namespace.
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            $this->prefixDirsPsr4[$prefix] = $paths;
        } elseif ($prepend) {
            // Prepend directories for an already registered namespace.
            $this->prefixDirsPsr4[$prefix] = array_merge(
                $paths,
                $this->prefixDirsPsr4[$prefix]
            );
        } else {
            // Append directories for an already registered namespace.
            $this->prefixDirsPsr4[$prefix] = array_merge(
                $this->prefixDirsPsr4[$prefix],
                $paths
            );
        }
    }

    /**
     * Registers a set of PSR-0 directories for a given prefix,
     * replacing any others previously set for this prefix.
     *
     * @param string              $prefix The prefix
     * @param list<string>|string $paths  The PSR-0 base directories
     *
     * @return void
     */
    public function set($prefix, $paths)
    {
        if (!$prefix) {
            $this->fallbackDirsPsr0 = (array) $paths;
        } else {
            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
        }
    }

    /**
     * Registers a set of PSR-4 directories for a given namespace,
     * replacing any others previously set for this namespace.
     *
     * @param string              $prefix The prefix/namespace, with trailing '\\'
     * @param list<string>|string $paths  The PSR-4 base directories
     *
     * @throws \InvalidArgumentException
     *
     * @return void
     */
    public function setPsr4($prefix, $paths)
    {
        if (!$prefix) {
            $this->fallbackDirsPsr4 = (array) $paths;
        } else {
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            $this->prefixDirsPsr4[$prefix] = (array) $paths;
        }
    }

    /**
     * Turns on searching the include path for class files.
     *
     * @param bool $useIncludePath
     *
     * @return void
     */
    public function setUseIncludePath($useIncludePath)
    {
        $this->useIncludePath = $useIncludePath;
    }

    /**
     * Can be used to check if the autoloader uses the include path to check
     * for classes.
     *
     * @return bool
     */
    public function getUseIncludePath()
    {
        return $this->useIncludePath;
    }

    /**
     * Turns off searching the prefix and fallback directories for classes
     * that have not been registered with the class map.
     *
     * @param bool $classMapAuthoritative
     *
     * @return void
     */
    public function setClassMapAuthoritative($classMapAuthoritative)
    {
        $this->classMapAuthoritative = $classMapAuthoritative;
    }

    /**
     * Should class lookup fail if not found in the current class map?
     *
     * @return bool
     */
    public function isClassMapAuthoritative()
    {
        return $this->classMapAuthoritative;
    }

    /**
     * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
     *
     * @param string|null $apcuPrefix
     *
     * @return void
     */
    public function setApcuPrefix($apcuPrefix)
    {
        $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
    }

    /**
     * The APCu prefix in use, or null if APCu caching is not enabled.
     *
     * @return string|null
     */
    public function getApcuPrefix()
    {
        return $this->apcuPrefix;
    }

    /**
     * Registers this instance as an autoloader.
     *
     * @param bool $prepend Whether to prepend the autoloader or not
     *
     * @return void
     */
    public function register($prepend = false)
    {
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);

        if (null === $this->vendorDir) {
            return;
        }

        if ($prepend) {
            self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
        } else {
            unset(self::$registeredLoaders[$this->vendorDir]);
            self::$registeredLoaders[$this->vendorDir] = $this;
        }
    }

    /**
     * Unregisters this instance as an autoloader.
     *
     * @return void
     */
    public function unregister()
    {
        spl_autoload_unregister(array($this, 'loadClass'));

        if (null !== $this->vendorDir) {
            unset(self::$registeredLoaders[$this->vendorDir]);
        }
    }

    /**
     * Loads the given class or interface.
     *
     * @param  string    $class The name of the class
     * @return true|null True if loaded, null otherwise
     */
    public function loadClass($class)
    {
        if ($file = $this->findFile($class)) {
            $includeFile = self::$includeFile;
            $includeFile($file);

            return true;
        }

        return null;
    }

    /**
     * Finds the path to the file where the class is defined.
     *
     * @param string $class The name of the class
     *
     * @return string|false The path if found, false otherwise
     */
    public function findFile($class)
    {
        // class map lookup
        if (isset($this->classMap[$class])) {
            return $this->classMap[$class];
        }
        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
            return false;
        }
        if (null !== $this->apcuPrefix) {
            $file = apcu_fetch($this->apcuPrefix.$class, $hit);
            if ($hit) {
                return $file;
            }
        }

        $file = $this->findFileWithExtension($class, '.php');

        // Search for Hack files if we are running on HHVM
        if (false === $file && defined('HHVM_VERSION')) {
            $file = $this->findFileWithExtension($class, '.hh');
        }

        if (null !== $this->apcuPrefix) {
            apcu_add($this->apcuPrefix.$class, $file);
        }

        if (false === $file) {
            // Remember that this class does not exist.
            $this->missingClasses[$class] = true;
        }

        return $file;
    }

    /**
     * Returns the currently registered loaders keyed by their corresponding vendor directories.
     *
     * @return array<string, self>
     */
    public static function getRegisteredLoaders()
    {
        return self::$registeredLoaders;
    }

    /**
     * @param  string       $class
     * @param  string       $ext
     * @return string|false
     */
    private function findFileWithExtension($class, $ext)
    {
        // PSR-4 lookup
        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

        $first = $class[0];
        if (isset($this->prefixLengthsPsr4[$first])) {
            $subPath = $class;
            while (false !== $lastPos = strrpos($subPath, '\\')) {
                $subPath = substr($subPath, 0, $lastPos);
                $search = $subPath . '\\';
                if (isset($this->prefixDirsPsr4[$search])) {
                    $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
                    foreach ($this->prefixDirsPsr4[$search] as $dir) {
                        if (file_exists($file = $dir . $pathEnd)) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-4 fallback dirs
        foreach ($this->fallbackDirsPsr4 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
                return $file;
            }
        }

        // PSR-0 lookup
        if (false !== $pos = strrpos($class, '\\')) {
            // namespaced class name
            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
        } else {
            // PEAR-like class name
            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
        }

        if (isset($this->prefixesPsr0[$first])) {
            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
                if (0 === strpos($class, $prefix)) {
                    foreach ($dirs as $dir) {
                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-0 fallback dirs
        foreach ($this->fallbackDirsPsr0 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                return $file;
            }
        }

        // PSR-0 include paths.
        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
            return $file;
        }

        return false;
    }

    /**
     * @return void
     */
    private static function initializeIncludeClosure()
    {
        if (self::$includeFile !== null) {
            return;
        }

        /**
         * Scope isolated include.
         *
         * Prevents access to $this/self from included files.
         *
         * @param  string $file
         * @return void
         */
        self::$includeFile = \Closure::bind(static function($file) {
            include $file;
        }, null, null);
    }
}
<?php

// autoload_namespaces.php @generated by Composer

$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);

return array(
);
<?php

// autoload_psr4.php @generated by Composer

$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);

return array(
    'HumbugBox4610\\KevinGH\\RequirementChecker\\' => array($baseDir . '/src'),
    'HumbugBox4610\\Composer\\Semver\\' => array($vendorDir . '/composer/semver/src'),
);

Copyright (c) Nils Adermann, Jordi Boggiano

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.

<?php

// autoload_static.php @generated by Composer

namespace Composer\Autoload;

class ComposerStaticInitHumbugBox4610
{
    public static $prefixLengthsPsr4 = array (
        'H' =>
        array (
            'HumbugBox4610\\KevinGH\\RequirementChecker\\' => 41,
            'HumbugBox4610\\Composer\\Semver\\' => 30,
        ),
    );

    public static $prefixDirsPsr4 = array (
        'HumbugBox4610\\KevinGH\\RequirementChecker\\' =>
        array (
            0 => __DIR__ . '/../..' . '/src',
        ),
        'HumbugBox4610\\Composer\\Semver\\' =>
        array (
            0 => __DIR__ . '/..' . '/composer/semver/src',
        ),
    );

    public static $classMap = array (
        'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
        'HumbugBox4610\\Composer\\Semver\\Comparator' => __DIR__ . '/..' . '/composer/semver/src/Comparator.php',
        'HumbugBox4610\\Composer\\Semver\\CompilingMatcher' => __DIR__ . '/..' . '/composer/semver/src/CompilingMatcher.php',
        'HumbugBox4610\\Composer\\Semver\\Constraint\\Bound' => __DIR__ . '/..' . '/composer/semver/src/Constraint/Bound.php',
        'HumbugBox4610\\Composer\\Semver\\Constraint\\Constraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/Constraint.php',
        'HumbugBox4610\\Composer\\Semver\\Constraint\\ConstraintInterface' => __DIR__ . '/..' . '/composer/semver/src/Constraint/ConstraintInterface.php',
        'HumbugBox4610\\Composer\\Semver\\Constraint\\MatchAllConstraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/MatchAllConstraint.php',
        'HumbugBox4610\\Composer\\Semver\\Constraint\\MatchNoneConstraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/MatchNoneConstraint.php',
        'HumbugBox4610\\Composer\\Semver\\Constraint\\MultiConstraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/MultiConstraint.php',
        'HumbugBox4610\\Composer\\Semver\\Interval' => __DIR__ . '/..' . '/composer/semver/src/Interval.php',
        'HumbugBox4610\\Composer\\Semver\\Intervals' => __DIR__ . '/..' . '/composer/semver/src/Intervals.php',
        'HumbugBox4610\\Composer\\Semver\\Semver' => __DIR__ . '/..' . '/composer/semver/src/Semver.php',
        'HumbugBox4610\\Composer\\Semver\\VersionParser' => __DIR__ . '/..' . '/composer/semver/src/VersionParser.php',
        'HumbugBox4610\\KevinGH\\RequirementChecker\\Checker' => __DIR__ . '/../..' . '/src/Checker.php',
        'HumbugBox4610\\KevinGH\\RequirementChecker\\IO' => __DIR__ . '/../..' . '/src/IO.php',
        'HumbugBox4610\\KevinGH\\RequirementChecker\\IsExtensionConflictFulfilled' => __DIR__ . '/../..' . '/src/IsExtensionConflictFulfilled.php',
        'HumbugBox4610\\KevinGH\\RequirementChecker\\IsExtensionFulfilled' => __DIR__ . '/../..' . '/src/IsExtensionFulfilled.php',
        'HumbugBox4610\\KevinGH\\RequirementChecker\\IsFulfilled' => __DIR__ . '/../..' . '/src/IsFulfilled.php',
        'HumbugBox4610\\KevinGH\\RequirementChecker\\IsPhpVersionFulfilled' => __DIR__ . '/../..' . '/src/IsPhpVersionFulfilled.php',
        'HumbugBox4610\\KevinGH\\RequirementChecker\\Printer' => __DIR__ . '/../..' . '/src/Printer.php',
        'HumbugBox4610\\KevinGH\\RequirementChecker\\Requirement' => __DIR__ . '/../..' . '/src/Requirement.php',
        'HumbugBox4610\\KevinGH\\RequirementChecker\\RequirementCollection' => __DIR__ . '/../..' . '/src/RequirementCollection.php',
        'HumbugBox4610\\KevinGH\\RequirementChecker\\Terminal' => __DIR__ . '/../..' . '/src/Terminal.php',
    );

    public static function getInitializer(ClassLoader $loader)
    {
        return \Closure::bind(function () use ($loader) {
            $loader->prefixLengthsPsr4 = ComposerStaticInitHumbugBox4610::$prefixLengthsPsr4;
            $loader->prefixDirsPsr4 = ComposerStaticInitHumbugBox4610::$prefixDirsPsr4;
            $loader->classMap = ComposerStaticInitHumbugBox4610::$classMap;

        }, null, ClassLoader::class);
    }
}
<?php

namespace HumbugBox4610;

return array('root' => array('name' => 'humbug/requirement-checker', 'pretty_version' => '4.x.x-dev', 'version' => '4.9999999.9999999.9999999-dev', 'reference' => null, 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev' => \true), 'versions' => array('composer/semver' => array('pretty_version' => '3.4.4', 'version' => '3.4.4.0', 'reference' => '198166618906cb2de69b95d7d47e5fa8aa1b2b95', 'type' => 'library', 'install_path' => __DIR__ . '/./semver', 'aliases' => array(), 'dev_requirement' => \false), 'ergebnis/composer-normalize' => array('pretty_version' => '2.50.0', 'version' => '2.50.0.0', 'reference' => '80971fe24ff10709789942bcbe9368b2c704097c', 'type' => 'composer-plugin', 'install_path' => __DIR__ . '/../ergebnis/composer-normalize', 'aliases' => array(), 'dev_requirement' => \true), 'ergebnis/json' => array('pretty_version' => '1.6.0', 'version' => '1.6.0.0', 'reference' => '7b56d2b5d9e897e75b43e2e753075a0904c921b1', 'type' => 'library', 'install_path' => __DIR__ . '/../ergebnis/json', 'aliases' => array(), 'dev_requirement' => \true), 'ergebnis/json-normalizer' => array('pretty_version' => '4.10.1', 'version' => '4.10.1.0', 'reference' => '77961faf2c651c3f05977b53c6c68e8434febf62', 'type' => 'library', 'install_path' => __DIR__ . '/../ergebnis/json-normalizer', 'aliases' => array(), 'dev_requirement' => \true), 'ergebnis/json-pointer' => array('pretty_version' => '3.7.1', 'version' => '3.7.1.0', 'reference' => '43bef355184e9542635e35dd2705910a3df4c236', 'type' => 'library', 'install_path' => __DIR__ . '/../ergebnis/json-pointer', 'aliases' => array(), 'dev_requirement' => \true), 'ergebnis/json-printer' => array('pretty_version' => '3.8.1', 'version' => '3.8.1.0', 'reference' => '211d73fc7ec6daf98568ee6ed6e6d133dee8503e', 'type' => 'library', 'install_path' => __DIR__ . '/../ergebnis/json-printer', 'aliases' => array(), 'dev_requirement' => \true), 'ergebnis/json-schema-validator' => array('pretty_version' => '4.5.1', 'version' => '4.5.1.0', 'reference' => 'b739527a480a9e3651360ad351ea77e7e9019df2', 'type' => 'library', 'install_path' => __DIR__ . '/../ergebnis/json-schema-validator', 'aliases' => array(), 'dev_requirement' => \true), 'fidry/makefile' => array('pretty_version' => '1.0.3', 'version' => '1.0.3.0', 'reference' => '3d0350840eeeda8127e014f0f7f86917c7f7b4f1', 'type' => 'library', 'install_path' => __DIR__ . '/../fidry/makefile', 'aliases' => array(), 'dev_requirement' => \true), 'humbug/requirement-checker' => array('pretty_version' => '4.x.x-dev', 'version' => '4.9999999.9999999.9999999-dev', 'reference' => null, 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev_requirement' => \false), 'justinrainbow/json-schema' => array('pretty_version' => 'v6.7.2', 'version' => '6.7.2.0', 'reference' => '6fea66c7204683af437864e7c4e7abf383d14bc0', 'type' => 'library', 'install_path' => __DIR__ . '/../justinrainbow/json-schema', 'aliases' => array(), 'dev_requirement' => \true), 'localheinz/diff' => array('pretty_version' => '1.3.0', 'version' => '1.3.0.0', 'reference' => '33bd840935970cda6691c23fc7d94ae764c0734c', 'type' => 'library', 'install_path' => __DIR__ . '/../localheinz/diff', 'aliases' => array(), 'dev_requirement' => \true), 'marc-mabe/php-enum' => array('pretty_version' => 'v4.7.2', 'version' => '4.7.2.0', 'reference' => 'bb426fcdd65c60fb3638ef741e8782508fda7eef', 'type' => 'library', 'install_path' => __DIR__ . '/../marc-mabe/php-enum', 'aliases' => array(), 'dev_requirement' => \true), 'myclabs/deep-copy' => array('pretty_version' => '1.13.4', 'version' => '1.13.4.0', 'reference' => '07d290f0c47959fd5eed98c95ee5602db07e0b6a', 'type' => 'library', 'install_path' => __DIR__ . '/../myclabs/deep-copy', 'aliases' => array(), 'dev_requirement' => \true), 'nikic/php-parser' => array('pretty_version' => 'v5.7.0', 'version' => '5.7.0.0', 'reference' => 'dca41cd15c2ac9d055ad70dbfd011130757d1f82', 'type' => 'library', 'install_path' => __DIR__ . '/../nikic/php-parser', 'aliases' => array(), 'dev_requirement' => \true), 'phar-io/manifest' => array('pretty_version' => '2.0.4', 'version' => '2.0.4.0', 'reference' => '54750ef60c58e43759730615a392c31c80e23176', 'type' => 'library', 'install_path' => __DIR__ . '/../phar-io/manifest', 'aliases' => array(), 'dev_requirement' => \true), 'phar-io/version' => array('pretty_version' => '3.2.1', 'version' => '3.2.1.0', 'reference' => '4f7fd7836c6f332bb2933569e566a0d6c4cbed74', 'type' => 'library', 'install_path' => __DIR__ . '/../phar-io/version', 'aliases' => array(), 'dev_requirement' => \true), 'phpunit/php-code-coverage' => array('pretty_version' => '10.1.16', 'version' => '10.1.16.0', 'reference' => '7e308268858ed6baedc8704a304727d20bc07c77', 'type' => 'library', 'install_path' => __DIR__ . '/../phpunit/php-code-coverage', 'aliases' => array(), 'dev_requirement' => \true), 'phpunit/php-file-iterator' => array('pretty_version' => '4.1.0', 'version' => '4.1.0.0', 'reference' => 'a95037b6d9e608ba092da1b23931e537cadc3c3c', 'type' => 'library', 'install_path' => __DIR__ . '/../phpunit/php-file-iterator', 'aliases' => array(), 'dev_requirement' => \true), 'phpunit/php-invoker' => array('pretty_version' => '4.0.0', 'version' => '4.0.0.0', 'reference' => 'f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7', 'type' => 'library', 'install_path' => __DIR__ . '/../phpunit/php-invoker', 'aliases' => array(), 'dev_requirement' => \true), 'phpunit/php-text-template' => array('pretty_version' => '3.0.1', 'version' => '3.0.1.0', 'reference' => '0c7b06ff49e3d5072f057eb1fa59258bf287a748', 'type' => 'library', 'install_path' => __DIR__ . '/../phpunit/php-text-template', 'aliases' => array(), 'dev_requirement' => \true), 'phpunit/php-timer' => array('pretty_version' => '6.0.0', 'version' => '6.0.0.0', 'reference' => 'e2a2d67966e740530f4a3343fe2e030ffdc1161d', 'type' => 'library', 'install_path' => __DIR__ . '/../phpunit/php-timer', 'aliases' => array(), 'dev_requirement' => \true), 'phpunit/phpunit' => array('pretty_version' => '10.5.63', 'version' => '10.5.63.0', 'reference' => '33198268dad71e926626b618f3ec3966661e4d90', 'type' => 'library', 'install_path' => __DIR__ . '/../phpunit/phpunit', 'aliases' => array(), 'dev_requirement' => \true), 'sebastian/cli-parser' => array('pretty_version' => '2.0.1', 'version' => '2.0.1.0', 'reference' => 'c34583b87e7b7a8055bf6c450c2c77ce32a24084', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/cli-parser', 'aliases' => array(), 'dev_requirement' => \true), 'sebastian/code-unit' => array('pretty_version' => '2.0.0', 'version' => '2.0.0.0', 'reference' => 'a81fee9eef0b7a76af11d121767abc44c104e503', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/code-unit', 'aliases' => array(), 'dev_requirement' => \true), 'sebastian/code-unit-reverse-lookup' => array('pretty_version' => '3.0.0', 'version' => '3.0.0.0', 'reference' => '5e3a687f7d8ae33fb362c5c0743794bbb2420a1d', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/code-unit-reverse-lookup', 'aliases' => array(), 'dev_requirement' => \true), 'sebastian/comparator' => array('pretty_version' => '5.0.5', 'version' => '5.0.5.0', 'reference' => '55dfef806eb7dfeb6e7a6935601fef866f8ca48d', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/comparator', 'aliases' => array(), 'dev_requirement' => \true), 'sebastian/complexity' => array('pretty_version' => '3.2.0', 'version' => '3.2.0.0', 'reference' => '68ff824baeae169ec9f2137158ee529584553799', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/complexity', 'aliases' => array(), 'dev_requirement' => \true), 'sebastian/diff' => array('pretty_version' => '5.1.1', 'version' => '5.1.1.0', 'reference' => 'c41e007b4b62af48218231d6c2275e4c9b975b2e', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/diff', 'aliases' => array(), 'dev_requirement' => \true), 'sebastian/environment' => array('pretty_version' => '6.1.0', 'version' => '6.1.0.0', 'reference' => '8074dbcd93529b357029f5cc5058fd3e43666984', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/environment', 'aliases' => array(), 'dev_requirement' => \true), 'sebastian/exporter' => array('pretty_version' => '5.1.4', 'version' => '5.1.4.0', 'reference' => '0735b90f4da94969541dac1da743446e276defa6', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/exporter', 'aliases' => array(), 'dev_requirement' => \true), 'sebastian/global-state' => array('pretty_version' => '6.0.2', 'version' => '6.0.2.0', 'reference' => '987bafff24ecc4c9ac418cab1145b96dd6e9cbd9', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/global-state', 'aliases' => array(), 'dev_requirement' => \true), 'sebastian/lines-of-code' => array('pretty_version' => '2.0.2', 'version' => '2.0.2.0', 'reference' => '856e7f6a75a84e339195d48c556f23be2ebf75d0', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/lines-of-code', 'aliases' => array(), 'dev_requirement' => \true), 'sebastian/object-enumerator' => array('pretty_version' => '5.0.0', 'version' => '5.0.0.0', 'reference' => '202d0e344a580d7f7d04b3fafce6933e59dae906', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/object-enumerator', 'aliases' => array(), 'dev_requirement' => \true), 'sebastian/object-reflector' => array('pretty_version' => '3.0.0', 'version' => '3.0.0.0', 'reference' => '24ed13d98130f0e7122df55d06c5c4942a577957', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/object-reflector', 'aliases' => array(), 'dev_requirement' => \true), 'sebastian/recursion-context' => array('pretty_version' => '5.0.1', 'version' => '5.0.1.0', 'reference' => '47e34210757a2f37a97dcd207d032e1b01e64c7a', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/recursion-context', 'aliases' => array(), 'dev_requirement' => \true), 'sebastian/type' => array('pretty_version' => '4.0.0', 'version' => '4.0.0.0', 'reference' => '462699a16464c3944eefc02ebdd77882bd3925bf', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/type', 'aliases' => array(), 'dev_requirement' => \true), 'sebastian/version' => array('pretty_version' => '4.0.1', 'version' => '4.0.1.0', 'reference' => 'c51fa83a5d8f43f1402e3f32a005e6262244ef17', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/version', 'aliases' => array(), 'dev_requirement' => \true), 'symfony/deprecation-contracts' => array('pretty_version' => 'v3.6.0', 'version' => '3.6.0.0', 'reference' => '63afe740e99a13ba87ec199bb07bbdee937a5b62', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', 'aliases' => array(), 'dev_requirement' => \true), 'symfony/polyfill-ctype' => array('pretty_version' => 'v1.33.0', 'version' => '1.33.0.0', 'reference' => 'a3cc8b044a6ea513310cbd48ef7333b384945638', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-ctype', 'aliases' => array(), 'dev_requirement' => \true), 'symfony/yaml' => array('pretty_version' => 'v6.4.34', 'version' => '6.4.34.0', 'reference' => '7bca30dabed7900a08c5ad4f1d6483f881a64d0f', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/yaml', 'aliases' => array(), 'dev_requirement' => \true), 'theseer/tokenizer' => array('pretty_version' => '1.3.1', 'version' => '1.3.1.0', 'reference' => 'b7489ce515e168639d17feec34b8847c326b0b3c', 'type' => 'library', 'install_path' => __DIR__ . '/../theseer/tokenizer', 'aliases' => array(), 'dev_requirement' => \true)));
<?php

// autoload_real.php @generated by Composer

class ComposerAutoloaderInitHumbugBox4610
{
    private static $loader;

    public static function loadClassLoader($class)
    {
        if ('Composer\Autoload\ClassLoader' === $class) {
            require __DIR__ . '/ClassLoader.php';
        }
    }

    /**
     * @return \Composer\Autoload\ClassLoader
     */
    public static function getLoader()
    {
        if (null !== self::$loader) {
            return self::$loader;
        }

        spl_autoload_register(array('ComposerAutoloaderInitHumbugBox4610', 'loadClassLoader'), true, true);
        self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
        spl_autoload_unregister(array('ComposerAutoloaderInitHumbugBox4610', 'loadClassLoader'));

        require __DIR__ . '/autoload_static.php';
        call_user_func(\Composer\Autoload\ComposerStaticInitHumbugBox4610::getInitializer($loader));

        $loader->setClassMapAuthoritative(true);
        $loader->register(true);

        return $loader;
    }
}
<?php

// autoload_classmap.php @generated by Composer

$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);

return array(
    'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
    'HumbugBox4610\\Composer\\Semver\\Comparator' => $vendorDir . '/composer/semver/src/Comparator.php',
    'HumbugBox4610\\Composer\\Semver\\CompilingMatcher' => $vendorDir . '/composer/semver/src/CompilingMatcher.php',
    'HumbugBox4610\\Composer\\Semver\\Constraint\\Bound' => $vendorDir . '/composer/semver/src/Constraint/Bound.php',
    'HumbugBox4610\\Composer\\Semver\\Constraint\\Constraint' => $vendorDir . '/composer/semver/src/Constraint/Constraint.php',
    'HumbugBox4610\\Composer\\Semver\\Constraint\\ConstraintInterface' => $vendorDir . '/composer/semver/src/Constraint/ConstraintInterface.php',
    'HumbugBox4610\\Composer\\Semver\\Constraint\\MatchAllConstraint' => $vendorDir . '/composer/semver/src/Constraint/MatchAllConstraint.php',
    'HumbugBox4610\\Composer\\Semver\\Constraint\\MatchNoneConstraint' => $vendorDir . '/composer/semver/src/Constraint/MatchNoneConstraint.php',
    'HumbugBox4610\\Composer\\Semver\\Constraint\\MultiConstraint' => $vendorDir . '/composer/semver/src/Constraint/MultiConstraint.php',
    'HumbugBox4610\\Composer\\Semver\\Interval' => $vendorDir . '/composer/semver/src/Interval.php',
    'HumbugBox4610\\Composer\\Semver\\Intervals' => $vendorDir . '/composer/semver/src/Intervals.php',
    'HumbugBox4610\\Composer\\Semver\\Semver' => $vendorDir . '/composer/semver/src/Semver.php',
    'HumbugBox4610\\Composer\\Semver\\VersionParser' => $vendorDir . '/composer/semver/src/VersionParser.php',
    'HumbugBox4610\\KevinGH\\RequirementChecker\\Checker' => $baseDir . '/src/Checker.php',
    'HumbugBox4610\\KevinGH\\RequirementChecker\\IO' => $baseDir . '/src/IO.php',
    'HumbugBox4610\\KevinGH\\RequirementChecker\\IsExtensionConflictFulfilled' => $baseDir . '/src/IsExtensionConflictFulfilled.php',
    'HumbugBox4610\\KevinGH\\RequirementChecker\\IsExtensionFulfilled' => $baseDir . '/src/IsExtensionFulfilled.php',
    'HumbugBox4610\\KevinGH\\RequirementChecker\\IsFulfilled' => $baseDir . '/src/IsFulfilled.php',
    'HumbugBox4610\\KevinGH\\RequirementChecker\\IsPhpVersionFulfilled' => $baseDir . '/src/IsPhpVersionFulfilled.php',
    'HumbugBox4610\\KevinGH\\RequirementChecker\\Printer' => $baseDir . '/src/Printer.php',
    'HumbugBox4610\\KevinGH\\RequirementChecker\\Requirement' => $baseDir . '/src/Requirement.php',
    'HumbugBox4610\\KevinGH\\RequirementChecker\\RequirementCollection' => $baseDir . '/src/RequirementCollection.php',
    'HumbugBox4610\\KevinGH\\RequirementChecker\\Terminal' => $baseDir . '/src/Terminal.php',
);
Copyright (C) 2015 Composer

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.
<?php

namespace HumbugBox4610\Composer\Semver;

use HumbugBox4610\Composer\Semver\Constraint\Constraint;
class Comparator
{
    public static function greaterThan($version1, $version2)
    {
        return self::compare($version1, '>', $version2);
    }
    public static function greaterThanOrEqualTo($version1, $version2)
    {
        return self::compare($version1, '>=', $version2);
    }
    public static function lessThan($version1, $version2)
    {
        return self::compare($version1, '<', $version2);
    }
    public static function lessThanOrEqualTo($version1, $version2)
    {
        return self::compare($version1, '<=', $version2);
    }
    public static function equalTo($version1, $version2)
    {
        return self::compare($version1, '==', $version2);
    }
    public static function notEqualTo($version1, $version2)
    {
        return self::compare($version1, '!=', $version2);
    }
    /**
    @phpstan-param
    */
    public static function compare($version1, $operator, $version2)
    {
        $constraint = new Constraint($operator, $version2);
        return $constraint->matchSpecific(new Constraint('==', $version1), \true);
    }
}
<?php

namespace HumbugBox4610\Composer\Semver;

use HumbugBox4610\Composer\Semver\Constraint\Constraint;
class Interval
{
    private $start;
    private $end;
    public function __construct(Constraint $start, Constraint $end)
    {
        $this->start = $start;
        $this->end = $end;
    }
    public function getStart()
    {
        return $this->start;
    }
    public function getEnd()
    {
        return $this->end;
    }
    public static function fromZero()
    {
        static $zero;
        if (null === $zero) {
            $zero = new Constraint('>=', '0.0.0.0-dev');
        }
        return $zero;
    }
    public static function untilPositiveInfinity()
    {
        static $positiveInfinity;
        if (null === $positiveInfinity) {
            $positiveInfinity = new Constraint('<', \PHP_INT_MAX . '.0.0.0');
        }
        return $positiveInfinity;
    }
    public static function any()
    {
        return new self(self::fromZero(), self::untilPositiveInfinity());
    }
    public static function anyDev()
    {
        return array('names' => array(), 'exclude' => \true);
    }
    public static function noDev()
    {
        return array('names' => array(), 'exclude' => \false);
    }
}
<?php

namespace HumbugBox4610\Composer\Semver;

use HumbugBox4610\Composer\Semver\Constraint\Constraint;
class Semver
{
    const SORT_ASC = 1;
    const SORT_DESC = -1;
    private static $versionParser;
    public static function satisfies($version, $constraints)
    {
        if (null === self::$versionParser) {
            self::$versionParser = new VersionParser();
        }
        $versionParser = self::$versionParser;
        $provider = new Constraint('==', $versionParser->normalize($version));
        $parsedConstraints = $versionParser->parseConstraints($constraints);
        return $parsedConstraints->matches($provider);
    }
    public static function satisfiedBy(array $versions, $constraints)
    {
        $versions = array_filter($versions, function ($version) use ($constraints) {
            return Semver::satisfies($version, $constraints);
        });
        return array_values($versions);
    }
    public static function sort(array $versions)
    {
        return self::usort($versions, self::SORT_ASC);
    }
    public static function rsort(array $versions)
    {
        return self::usort($versions, self::SORT_DESC);
    }
    private static function usort(array $versions, $direction)
    {
        if (null === self::$versionParser) {
            self::$versionParser = new VersionParser();
        }
        $versionParser = self::$versionParser;
        $normalized = array();
        foreach ($versions as $key => $version) {
            $normalizedVersion = $versionParser->normalize($version);
            $normalizedVersion = $versionParser->normalizeDefaultBranch($normalizedVersion);
            $normalized[] = array($normalizedVersion, $key);
        }
        usort($normalized, function (array $left, array $right) use ($direction) {
            if ($left[0] === $right[0]) {
                return 0;
            }
            if (Comparator::lessThan($left[0], $right[0])) {
                return -$direction;
            }
            return $direction;
        });
        $sorted = array();
        foreach ($normalized as $item) {
            $sorted[] = $versions[$item[1]];
        }
        return $sorted;
    }
}
<?php

namespace HumbugBox4610\Composer\Semver\Constraint;

class Constraint implements ConstraintInterface
{
    const OP_EQ = 0;
    const OP_LT = 1;
    const OP_LE = 2;
    const OP_GT = 3;
    const OP_GE = 4;
    const OP_NE = 5;
    const STR_OP_EQ = '==';
    const STR_OP_EQ_ALT = '=';
    const STR_OP_LT = '<';
    const STR_OP_LE = '<=';
    const STR_OP_GT = '>';
    const STR_OP_GE = '>=';
    const STR_OP_NE = '!=';
    const STR_OP_NE_ALT = '<>';
    /**
    @phpstan-var
    */
    private static $transOpStr = array('=' => self::OP_EQ, '==' => self::OP_EQ, '<' => self::OP_LT, '<=' => self::OP_LE, '>' => self::OP_GT, '>=' => self::OP_GE, '<>' => self::OP_NE, '!=' => self::OP_NE);
    /**
    @phpstan-var
    */
    private static $transOpInt = array(self::OP_EQ => '==', self::OP_LT => '<', self::OP_LE => '<=', self::OP_GT => '>', self::OP_GE => '>=', self::OP_NE => '!=');
    /**
    @phpstan-var
    */
    protected $operator;
    protected $version;
    protected $prettyString;
    protected $lowerBound;
    protected $upperBound;
    /**
    @phpstan-param
    */
    public function __construct($operator, $version)
    {
        if (!isset(self::$transOpStr[$operator])) {
            throw new \InvalidArgumentException(sprintf('Invalid operator "%s" given, expected one of: %s', $operator, implode(', ', self::getSupportedOperators())));
        }
        $this->operator = self::$transOpStr[$operator];
        $this->version = $version;
    }
    public function getVersion()
    {
        return $this->version;
    }
    /**
    @phpstan-return
    */
    public function getOperator()
    {
        return self::$transOpInt[$this->operator];
    }
    public function matches(ConstraintInterface $provider)
    {
        if ($provider instanceof self) {
            return $this->matchSpecific($provider);
        }
        return $provider->matches($this);
    }
    public function setPrettyString($prettyString)
    {
        $this->prettyString = $prettyString;
    }
    public function getPrettyString()
    {
        if ($this->prettyString) {
            return $this->prettyString;
        }
        return $this->__toString();
    }
    /**
    @phpstan-return
    */
    public static function getSupportedOperators()
    {
        return array_keys(self::$transOpStr);
    }
    /**
    @phpstan-param
    @phpstan-return
    */
    public static function getOperatorConstant($operator)
    {
        return self::$transOpStr[$operator];
    }
    /**
    @phpstan-param
    */
    public function versionCompare($a, $b, $operator, $compareBranches = \false)
    {
        if (!isset(self::$transOpStr[$operator])) {
            throw new \InvalidArgumentException(sprintf('Invalid operator "%s" given, expected one of: %s', $operator, implode(', ', self::getSupportedOperators())));
        }
        $aIsBranch = strpos($a, 'dev-') === 0;
        $bIsBranch = strpos($b, 'dev-') === 0;
        if ($operator === '!=' && ($aIsBranch || $bIsBranch)) {
            return $a !== $b;
        }
        if ($aIsBranch && $bIsBranch) {
            return $operator === '==' && $a === $b;
        }
        if (!$compareBranches && ($aIsBranch || $bIsBranch)) {
            return \false;
        }
        return \version_compare($a, $b, $operator);
    }
    public function compile($otherOperator)
    {
        if (strpos($this->version, 'dev-') === 0) {
            if (self::OP_EQ === $this->operator) {
                if (self::OP_EQ === $otherOperator) {
                    return sprintf('$b && $v === %s', \var_export($this->version, \true));
                }
                if (self::OP_NE === $otherOperator) {
                    return sprintf('!$b || $v !== %s', \var_export($this->version, \true));
                }
                return 'false';
            }
            if (self::OP_NE === $this->operator) {
                if (self::OP_EQ === $otherOperator) {
                    return sprintf('!$b || $v !== %s', \var_export($this->version, \true));
                }
                if (self::OP_NE === $otherOperator) {
                    return 'true';
                }
                return '!$b';
            }
            return 'false';
        }
        if (self::OP_EQ === $this->operator) {
            if (self::OP_EQ === $otherOperator) {
                return sprintf('\version_compare($v, %s, \'==\')', \var_export($this->version, \true));
            }
            if (self::OP_NE === $otherOperator) {
                return sprintf('$b || \version_compare($v, %s, \'!=\')', \var_export($this->version, \true));
            }
            return sprintf('!$b && \version_compare(%s, $v, \'%s\')', \var_export($this->version, \true), self::$transOpInt[$otherOperator]);
        }
        if (self::OP_NE === $this->operator) {
            if (self::OP_EQ === $otherOperator) {
                return sprintf('$b || (!$b && \version_compare($v, %s, \'!=\'))', \var_export($this->version, \true));
            }
            if (self::OP_NE === $otherOperator) {
                return 'true';
            }
            return '!$b';
        }
        if (self::OP_LT === $this->operator || self::OP_LE === $this->operator) {
            if (self::OP_LT === $otherOperator || self::OP_LE === $otherOperator) {
                return '!$b';
            }
        } else if (self::OP_GT === $otherOperator || self::OP_GE === $otherOperator) {
            return '!$b';
        }
        if (self::OP_NE === $otherOperator) {
            return 'true';
        }
        $codeComparison = sprintf('\version_compare($v, %s, \'%s\')', \var_export($this->version, \true), self::$transOpInt[$this->operator]);
        if ($this->operator === self::OP_LE) {
            if ($otherOperator === self::OP_GT) {
                return sprintf('!$b && \version_compare($v, %s, \'!=\') && ', \var_export($this->version, \true)) . $codeComparison;
            }
        } elseif ($this->operator === self::OP_GE) {
            if ($otherOperator === self::OP_LT) {
                return sprintf('!$b && \version_compare($v, %s, \'!=\') && ', \var_export($this->version, \true)) . $codeComparison;
            }
        }
        return sprintf('!$b && %s', $codeComparison);
    }
    public function matchSpecific(Constraint $provider, $compareBranches = \false)
    {
        $noEqualOp = str_replace('=', '', self::$transOpInt[$this->operator]);
        $providerNoEqualOp = str_replace('=', '', self::$transOpInt[$provider->operator]);
        $isEqualOp = self::OP_EQ === $this->operator;
        $isNonEqualOp = self::OP_NE === $this->operator;
        $isProviderEqualOp = self::OP_EQ === $provider->operator;
        $isProviderNonEqualOp = self::OP_NE === $provider->operator;
        if ($isNonEqualOp || $isProviderNonEqualOp) {
            if ($isNonEqualOp && !$isProviderNonEqualOp && !$isProviderEqualOp && strpos($provider->version, 'dev-') === 0) {
                return \false;
            }
            if ($isProviderNonEqualOp && !$isNonEqualOp && !$isEqualOp && strpos($this->version, 'dev-') === 0) {
                return \false;
            }
            if (!$isEqualOp && !$isProviderEqualOp) {
                return \true;
            }
            return $this->versionCompare($provider->version, $this->version, '!=', $compareBranches);
        }
        if ($this->operator !== self::OP_EQ && $noEqualOp === $providerNoEqualOp) {
            return !(strpos($this->version, 'dev-') === 0 || strpos($provider->version, 'dev-') === 0);
        }
        $version1 = $isEqualOp ? $this->version : $provider->version;
        $version2 = $isEqualOp ? $provider->version : $this->version;
        $operator = $isEqualOp ? $provider->operator : $this->operator;
        if ($this->versionCompare($version1, $version2, self::$transOpInt[$operator], $compareBranches)) {
            return !(self::$transOpInt[$provider->operator] === $providerNoEqualOp && self::$transOpInt[$this->operator] !== $noEqualOp && \version_compare($provider->version, $this->version, '=='));
        }
        return \false;
    }
    public function __toString()
    {
        return self::$transOpInt[$this->operator] . ' ' . $this->version;
    }
    public function getLowerBound()
    {
        $this->extractBounds();
        return $this->lowerBound;
    }
    public function getUpperBound()
    {
        $this->extractBounds();
        return $this->upperBound;
    }
    private function extractBounds()
    {
        if (null !== $this->lowerBound) {
            return;
        }
        if (strpos($this->version, 'dev-') === 0) {
            $this->lowerBound = Bound::zero();
            $this->upperBound = Bound::positiveInfinity();
            return;
        }
        switch ($this->operator) {
            case self::OP_EQ:
                $this->lowerBound = new Bound($this->version, \true);
                $this->upperBound = new Bound($this->version, \true);
                break;
            case self::OP_LT:
                $this->lowerBound = Bound::zero();
                $this->upperBound = new Bound($this->version, \false);
                break;
            case self::OP_LE:
                $this->lowerBound = Bound::zero();
                $this->upperBound = new Bound($this->version, \true);
                break;
            case self::OP_GT:
                $this->lowerBound = new Bound($this->version, \false);
                $this->upperBound = Bound::positiveInfinity();
                break;
            case self::OP_GE:
                $this->lowerBound = new Bound($this->version, \true);
                $this->upperBound = Bound::positiveInfinity();
                break;
            case self::OP_NE:
                $this->lowerBound = Bound::zero();
                $this->upperBound = Bound::positiveInfinity();
                break;
        }
    }
}
<?php

namespace HumbugBox4610\Composer\Semver\Constraint;

class Bound
{
    private $version;
    private $isInclusive;
    public function __construct($version, $isInclusive)
    {
        $this->version = $version;
        $this->isInclusive = $isInclusive;
    }
    public function getVersion()
    {
        return $this->version;
    }
    public function isInclusive()
    {
        return $this->isInclusive;
    }
    public function isZero()
    {
        return $this->getVersion() === '0.0.0.0-dev' && $this->isInclusive();
    }
    public function isPositiveInfinity()
    {
        return $this->getVersion() === \PHP_INT_MAX . '.0.0.0' && !$this->isInclusive();
    }
    public function compareTo(Bound $other, $operator)
    {
        if (!\in_array($operator, array('<', '>'), \true)) {
            throw new \InvalidArgumentException('Does not support any other operator other than > or <.');
        }
        if ($this == $other) {
            return \false;
        }
        $compareResult = version_compare($this->getVersion(), $other->getVersion());
        if (0 !== $compareResult) {
            return ('>' === $operator ? 1 : -1) === $compareResult;
        }
        return '>' === $operator ? $other->isInclusive() : !$other->isInclusive();
    }
    public function __toString()
    {
        return sprintf('%s [%s]', $this->getVersion(), $this->isInclusive() ? 'inclusive' : 'exclusive');
    }
    public static function zero()
    {
        return new Bound('0.0.0.0-dev', \true);
    }
    public static function positiveInfinity()
    {
        return new Bound(\PHP_INT_MAX . '.0.0.0', \false);
    }
}
<?php

namespace HumbugBox4610\Composer\Semver\Constraint;

class MatchAllConstraint implements ConstraintInterface
{
    protected $prettyString;
    public function matches(ConstraintInterface $provider)
    {
        return \true;
    }
    public function compile($otherOperator)
    {
        return 'true';
    }
    public function setPrettyString($prettyString)
    {
        $this->prettyString = $prettyString;
    }
    public function getPrettyString()
    {
        if ($this->prettyString) {
            return $this->prettyString;
        }
        return (string) $this;
    }
    public function __toString()
    {
        return '*';
    }
    public function getUpperBound()
    {
        return Bound::positiveInfinity();
    }
    public function getLowerBound()
    {
        return Bound::zero();
    }
}
<?php

namespace HumbugBox4610\Composer\Semver\Constraint;

class MultiConstraint implements ConstraintInterface
{
    /**
    @phpstan-var
    */
    protected $constraints;
    protected $prettyString;
    protected $string;
    protected $conjunctive;
    protected $lowerBound;
    protected $upperBound;
    public function __construct(array $constraints, $conjunctive = \true)
    {
        if (\count($constraints) < 2) {
            throw new \InvalidArgumentException('Must provide at least two constraints for a MultiConstraint. Use ' . 'the regular Constraint class for one constraint only or MatchAllConstraint for none. You may use ' . 'MultiConstraint::create() which optimizes and handles those cases automatically.');
        }
        $this->constraints = $constraints;
        $this->conjunctive = $conjunctive;
    }
    public function getConstraints()
    {
        return $this->constraints;
    }
    public function isConjunctive()
    {
        return $this->conjunctive;
    }
    public function isDisjunctive()
    {
        return !$this->conjunctive;
    }
    public function compile($otherOperator)
    {
        $parts = array();
        foreach ($this->constraints as $constraint) {
            $code = $constraint->compile($otherOperator);
            if ($code === 'true') {
                if (!$this->conjunctive) {
                    return 'true';
                }
            } elseif ($code === 'false') {
                if ($this->conjunctive) {
                    return 'false';
                }
            } else {
                $parts[] = '(' . $code . ')';
            }
        }
        if (!$parts) {
            return $this->conjunctive ? 'true' : 'false';
        }
        return $this->conjunctive ? implode('&&', $parts) : implode('||', $parts);
    }
    public function matches(ConstraintInterface $provider)
    {
        if (\false === $this->conjunctive) {
            foreach ($this->constraints as $constraint) {
                if ($provider->matches($constraint)) {
                    return \true;
                }
            }
            return \false;
        }
        if ($provider instanceof MultiConstraint && $provider->isDisjunctive()) {
            return $provider->matches($this);
        }
        foreach ($this->constraints as $constraint) {
            if (!$provider->matches($constraint)) {
                return \false;
            }
        }
        return \true;
    }
    public function setPrettyString($prettyString)
    {
        $this->prettyString = $prettyString;
    }
    public function getPrettyString()
    {
        if ($this->prettyString) {
            return $this->prettyString;
        }
        return (string) $this;
    }
    public function __toString()
    {
        if ($this->string !== null) {
            return $this->string;
        }
        $constraints = array();
        foreach ($this->constraints as $constraint) {
            $constraints[] = (string) $constraint;
        }
        return $this->string = '[' . implode($this->conjunctive ? ' ' : ' || ', $constraints) . ']';
    }
    public function getLowerBound()
    {
        $this->extractBounds();
        if (null === $this->lowerBound) {
            throw new \LogicException('extractBounds should have populated the lowerBound property');
        }
        return $this->lowerBound;
    }
    public function getUpperBound()
    {
        $this->extractBounds();
        if (null === $this->upperBound) {
            throw new \LogicException('extractBounds should have populated the upperBound property');
        }
        return $this->upperBound;
    }
    public static function create(array $constraints, $conjunctive = \true)
    {
        if (0 === \count($constraints)) {
            return new MatchAllConstraint();
        }
        if (1 === \count($constraints)) {
            return $constraints[0];
        }
        $optimized = self::optimizeConstraints($constraints, $conjunctive);
        if ($optimized !== null) {
            list($constraints, $conjunctive) = $optimized;
            if (\count($constraints) === 1) {
                return $constraints[0];
            }
        }
        return new self($constraints, $conjunctive);
    }
    /**
    @phpstan-return
    */
    private static function optimizeConstraints(array $constraints, $conjunctive)
    {
        if (!$conjunctive) {
            $left = $constraints[0];
            $mergedConstraints = array();
            $optimized = \false;
            for ($i = 1, $l = \count($constraints); $i < $l; $i++) {
                $right = $constraints[$i];
                if ($left instanceof self && $left->conjunctive && $right instanceof self && $right->conjunctive && \count($left->constraints) === 2 && \count($right->constraints) === 2 && ($left0 = (string) $left->constraints[0]) && $left0[0] === '>' && $left0[1] === '=' && ($left1 = (string) $left->constraints[1]) && $left1[0] === '<' && ($right0 = (string) $right->constraints[0]) && $right0[0] === '>' && $right0[1] === '=' && ($right1 = (string) $right->constraints[1]) && $right1[0] === '<' && substr($left1, 2) === substr($right0, 3)) {
                    $optimized = \true;
                    $left = new MultiConstraint(array($left->constraints[0], $right->constraints[1]), \true);
                } else {
                    $mergedConstraints[] = $left;
                    $left = $right;
                }
            }
            if ($optimized) {
                $mergedConstraints[] = $left;
                return array($mergedConstraints, \false);
            }
        }
        return null;
    }
    private function extractBounds()
    {
        if (null !== $this->lowerBound) {
            return;
        }
        foreach ($this->constraints as $constraint) {
            if (null === $this->lowerBound || null === $this->upperBound) {
                $this->lowerBound = $constraint->getLowerBound();
                $this->upperBound = $constraint->getUpperBound();
                continue;
            }
            if ($constraint->getLowerBound()->compareTo($this->lowerBound, $this->isConjunctive() ? '>' : '<')) {
                $this->lowerBound = $constraint->getLowerBound();
            }
            if ($constraint->getUpperBound()->compareTo($this->upperBound, $this->isConjunctive() ? '<' : '>')) {
                $this->upperBound = $constraint->getUpperBound();
            }
        }
    }
}
<?php

namespace HumbugBox4610\Composer\Semver\Constraint;

interface ConstraintInterface
{
    public function matches(ConstraintInterface $provider);
    /**
    @phpstan-param
    */
    public function compile($otherOperator);
    public function getUpperBound();
    public function getLowerBound();
    public function getPrettyString();
    public function setPrettyString($prettyString);
    public function __toString();
}
<?php

namespace HumbugBox4610\Composer\Semver\Constraint;

class MatchNoneConstraint implements ConstraintInterface
{
    protected $prettyString;
    public function matches(ConstraintInterface $provider)
    {
        return \false;
    }
    public function compile($otherOperator)
    {
        return 'false';
    }
    public function setPrettyString($prettyString)
    {
        $this->prettyString = $prettyString;
    }
    public function getPrettyString()
    {
        if ($this->prettyString) {
            return $this->prettyString;
        }
        return (string) $this;
    }
    public function __toString()
    {
        return '[]';
    }
    public function getUpperBound()
    {
        return new Bound('0.0.0.0-dev', \false);
    }
    public function getLowerBound()
    {
        return new Bound('0.0.0.0-dev', \false);
    }
}
<?php

namespace HumbugBox4610\Composer\Semver;

use HumbugBox4610\Composer\Semver\Constraint\Constraint;
use HumbugBox4610\Composer\Semver\Constraint\ConstraintInterface;
use HumbugBox4610\Composer\Semver\Constraint\MatchAllConstraint;
use HumbugBox4610\Composer\Semver\Constraint\MatchNoneConstraint;
use HumbugBox4610\Composer\Semver\Constraint\MultiConstraint;
class Intervals
{
    /**
    @phpstan-var
    */
    private static $intervalsCache = array();
    /**
    @phpstan-var
    */
    private static $opSortOrder = array('>=' => -3, '<' => -2, '>' => 2, '<=' => 3);
    public static function clear()
    {
        self::$intervalsCache = array();
    }
    public static function isSubsetOf(ConstraintInterface $candidate, ConstraintInterface $constraint)
    {
        if ($constraint instanceof MatchAllConstraint) {
            return \true;
        }
        if ($candidate instanceof MatchNoneConstraint || $constraint instanceof MatchNoneConstraint) {
            return \false;
        }
        $intersectionIntervals = self::get(new MultiConstraint(array($candidate, $constraint), \true));
        $candidateIntervals = self::get($candidate);
        if (\count($intersectionIntervals['numeric']) !== \count($candidateIntervals['numeric'])) {
            return \false;
        }
        foreach ($intersectionIntervals['numeric'] as $index => $interval) {
            if (!isset($candidateIntervals['numeric'][$index])) {
                return \false;
            }
            if ((string) $candidateIntervals['numeric'][$index]->getStart() !== (string) $interval->getStart()) {
                return \false;
            }
            if ((string) $candidateIntervals['numeric'][$index]->getEnd() !== (string) $interval->getEnd()) {
                return \false;
            }
        }
        if ($intersectionIntervals['branches']['exclude'] !== $candidateIntervals['branches']['exclude']) {
            return \false;
        }
        if (\count($intersectionIntervals['branches']['names']) !== \count($candidateIntervals['branches']['names'])) {
            return \false;
        }
        foreach ($intersectionIntervals['branches']['names'] as $index => $name) {
            if ($name !== $candidateIntervals['branches']['names'][$index]) {
                return \false;
            }
        }
        return \true;
    }
    public static function haveIntersections(ConstraintInterface $a, ConstraintInterface $b)
    {
        if ($a instanceof MatchAllConstraint || $b instanceof MatchAllConstraint) {
            return \true;
        }
        if ($a instanceof MatchNoneConstraint || $b instanceof MatchNoneConstraint) {
            return \false;
        }
        $intersectionIntervals = self::generateIntervals(new MultiConstraint(array($a, $b), \true), \true);
        return \count($intersectionIntervals['numeric']) > 0 || $intersectionIntervals['branches']['exclude'] || \count($intersectionIntervals['branches']['names']) > 0;
    }
    public static function compactConstraint(ConstraintInterface $constraint)
    {
        if (!$constraint instanceof MultiConstraint) {
            return $constraint;
        }
        $intervals = self::generateIntervals($constraint);
        $constraints = array();
        $hasNumericMatchAll = \false;
        if (\count($intervals['numeric']) === 1 && (string) $intervals['numeric'][0]->getStart() === (string) Interval::fromZero() && (string) $intervals['numeric'][0]->getEnd() === (string) Interval::untilPositiveInfinity()) {
            $constraints[] = $intervals['numeric'][0]->getStart();
            $hasNumericMatchAll = \true;
        } else {
            $unEqualConstraints = array();
            for ($i = 0, $count = \count($intervals['numeric']); $i < $count; $i++) {
                $interval = $intervals['numeric'][$i];
                if ($interval->getEnd()->getOperator() === '<' && $i + 1 < $count) {
                    $nextInterval = $intervals['numeric'][$i + 1];
                    if ($interval->getEnd()->getVersion() === $nextInterval->getStart()->getVersion() && $nextInterval->getStart()->getOperator() === '>') {
                        if (\count($unEqualConstraints) === 0 && (string) $interval->getStart() !== (string) Interval::fromZero()) {
                            $unEqualConstraints[] = $interval->getStart();
                        }
                        $unEqualConstraints[] = new Constraint('!=', $interval->getEnd()->getVersion());
                        continue;
                    }
                }
                if (\count($unEqualConstraints) > 0) {
                    if ((string) $interval->getEnd() !== (string) Interval::untilPositiveInfinity()) {
                        $unEqualConstraints[] = $interval->getEnd();
                    }
                    if (\count($unEqualConstraints) > 1) {
                        $constraints[] = new MultiConstraint($unEqualConstraints, \true);
                    } else {
                        $constraints[] = $unEqualConstraints[0];
                    }
                    $unEqualConstraints = array();
                    continue;
                }
                if ($interval->getStart()->getVersion() === $interval->getEnd()->getVersion() && $interval->getStart()->getOperator() === '>=' && $interval->getEnd()->getOperator() === '<=') {
                    $constraints[] = new Constraint('==', $interval->getStart()->getVersion());
                    continue;
                }
                if ((string) $interval->getStart() === (string) Interval::fromZero()) {
                    $constraints[] = $interval->getEnd();
                } elseif ((string) $interval->getEnd() === (string) Interval::untilPositiveInfinity()) {
                    $constraints[] = $interval->getStart();
                } else {
                    $constraints[] = new MultiConstraint(array($interval->getStart(), $interval->getEnd()), \true);
                }
            }
        }
        $devConstraints = array();
        if (0 === \count($intervals['branches']['names'])) {
            if ($intervals['branches']['exclude']) {
                if ($hasNumericMatchAll) {
                    return new MatchAllConstraint();
                }
            }
        } else {
            foreach ($intervals['branches']['names'] as $branchName) {
                if ($intervals['branches']['exclude']) {
                    $devConstraints[] = new Constraint('!=', $branchName);
                } else {
                    $devConstraints[] = new Constraint('==', $branchName);
                }
            }
            if ($intervals['branches']['exclude']) {
                if (\count($constraints) > 1) {
                    return new MultiConstraint(array_merge(array(new MultiConstraint($constraints, \false)), $devConstraints), \true);
                }
                if (\count($constraints) === 1 && (string) $constraints[0] === (string) Interval::fromZero()) {
                    if (\count($devConstraints) > 1) {
                        return new MultiConstraint($devConstraints, \true);
                    }
                    return $devConstraints[0];
                }
                return new MultiConstraint(array_merge($constraints, $devConstraints), \true);
            }
            $constraints = array_merge($constraints, $devConstraints);
        }
        if (\count($constraints) > 1) {
            return new MultiConstraint($constraints, \false);
        }
        if (\count($constraints) === 1) {
            return $constraints[0];
        }
        return new MatchNoneConstraint();
    }
    /**
    @phpstan-return
    */
    public static function get(ConstraintInterface $constraint)
    {
        $key = (string) $constraint;
        if (!isset(self::$intervalsCache[$key])) {
            self::$intervalsCache[$key] = self::generateIntervals($constraint);
        }
        return self::$intervalsCache[$key];
    }
    /**
    @phpstan-return
    */
    private static function generateIntervals(ConstraintInterface $constraint, $stopOnFirstValidInterval = \false)
    {
        if ($constraint instanceof MatchAllConstraint) {
            return array('numeric' => array(new Interval(Interval::fromZero(), Interval::untilPositiveInfinity())), 'branches' => Interval::anyDev());
        }
        if ($constraint instanceof MatchNoneConstraint) {
            return array('numeric' => array(), 'branches' => array('names' => array(), 'exclude' => \false));
        }
        if ($constraint instanceof Constraint) {
            return self::generateSingleConstraintIntervals($constraint);
        }
        if (!$constraint instanceof MultiConstraint) {
            throw new \UnexpectedValueException('The constraint passed in should be an MatchAllConstraint, Constraint or MultiConstraint instance, got ' . \get_class($constraint) . '.');
        }
        $constraints = $constraint->getConstraints();
        $numericGroups = array();
        $constraintBranches = array();
        foreach ($constraints as $c) {
            $res = self::get($c);
            $numericGroups[] = $res['numeric'];
            $constraintBranches[] = $res['branches'];
        }
        if ($constraint->isDisjunctive()) {
            $branches = Interval::noDev();
            foreach ($constraintBranches as $b) {
                if ($b['exclude']) {
                    if ($branches['exclude']) {
                        $branches['names'] = array_intersect($branches['names'], $b['names']);
                    } else {
                        $branches['exclude'] = \true;
                        $branches['names'] = array_diff($b['names'], $branches['names']);
                    }
                } else if ($branches['exclude']) {
                    $branches['names'] = array_diff($branches['names'], $b['names']);
                } else {
                    $branches['names'] = array_merge($branches['names'], $b['names']);
                }
            }
        } else {
            $branches = Interval::anyDev();
            foreach ($constraintBranches as $b) {
                if ($b['exclude']) {
                    if ($branches['exclude']) {
                        $branches['names'] = array_merge($branches['names'], $b['names']);
                    } else {
                        $branches['names'] = array_diff($branches['names'], $b['names']);
                    }
                } else if ($branches['exclude']) {
                    $branches['names'] = array_diff($b['names'], $branches['names']);
                    $branches['exclude'] = \false;
                } else {
                    $branches['names'] = array_intersect($branches['names'], $b['names']);
                }
            }
        }
        $branches['names'] = array_unique($branches['names']);
        if (\count($numericGroups) === 1) {
            return array('numeric' => $numericGroups[0], 'branches' => $branches);
        }
        $borders = array();
        foreach ($numericGroups as $group) {
            foreach ($group as $interval) {
                $borders[] = array('version' => $interval->getStart()->getVersion(), 'operator' => $interval->getStart()->getOperator(), 'side' => 'start');
                $borders[] = array('version' => $interval->getEnd()->getVersion(), 'operator' => $interval->getEnd()->getOperator(), 'side' => 'end');
            }
        }
        $opSortOrder = self::$opSortOrder;
        usort($borders, function ($a, $b) use ($opSortOrder) {
            $order = version_compare($a['version'], $b['version']);
            if ($order === 0) {
                return $opSortOrder[$a['operator']] - $opSortOrder[$b['operator']];
            }
            return $order;
        });
        $activeIntervals = 0;
        $intervals = array();
        $index = 0;
        $activationThreshold = $constraint->isConjunctive() ? \count($numericGroups) : 1;
        $start = null;
        foreach ($borders as $border) {
            if ($border['side'] === 'start') {
                $activeIntervals++;
            } else {
                $activeIntervals--;
            }
            if (!$start && $activeIntervals >= $activationThreshold) {
                $start = new Constraint($border['operator'], $border['version']);
            } elseif ($start && $activeIntervals < $activationThreshold) {
                if (version_compare($start->getVersion(), $border['version'], '=') && ($start->getOperator() === '>' && $border['operator'] === '<=' || $start->getOperator() === '>=' && $border['operator'] === '<')) {
                    unset($intervals[$index]);
                } else {
                    $intervals[$index] = new Interval($start, new Constraint($border['operator'], $border['version']));
                    $index++;
                    if ($stopOnFirstValidInterval) {
                        break;
                    }
                }
                $start = null;
            }
        }
        return array('numeric' => $intervals, 'branches' => $branches);
    }
    /**
    @phpstan-return
    */
    private static function generateSingleConstraintIntervals(Constraint $constraint)
    {
        $op = $constraint->getOperator();
        if (strpos($constraint->getVersion(), 'dev-') === 0) {
            $intervals = array();
            $branches = array('names' => array(), 'exclude' => \false);
            if ($op === '!=') {
                $intervals[] = new Interval(Interval::fromZero(), Interval::untilPositiveInfinity());
                $branches = array('names' => array($constraint->getVersion()), 'exclude' => \true);
            } elseif ($op === '==') {
                $branches['names'][] = $constraint->getVersion();
            }
            return array('numeric' => $intervals, 'branches' => $branches);
        }
        if ($op[0] === '>') {
            return array('numeric' => array(new Interval($constraint, Interval::untilPositiveInfinity())), 'branches' => Interval::noDev());
        }
        if ($op[0] === '<') {
            return array('numeric' => array(new Interval(Interval::fromZero(), $constraint)), 'branches' => Interval::noDev());
        }
        if ($op === '!=') {
            return array('numeric' => array(new Interval(Interval::fromZero(), new Constraint('<', $constraint->getVersion())), new Interval(new Constraint('>', $constraint->getVersion()), Interval::untilPositiveInfinity())), 'branches' => Interval::anyDev());
        }
        return array('numeric' => array(new Interval(new Constraint('>=', $constraint->getVersion()), new Constraint('<=', $constraint->getVersion()))), 'branches' => Interval::noDev());
    }
}
<?php

namespace HumbugBox4610\Composer\Semver;

use HumbugBox4610\Composer\Semver\Constraint\ConstraintInterface;
use HumbugBox4610\Composer\Semver\Constraint\MatchAllConstraint;
use HumbugBox4610\Composer\Semver\Constraint\MultiConstraint;
use HumbugBox4610\Composer\Semver\Constraint\Constraint;
class VersionParser
{
    private static $modifierRegex = '[._-]?(?:(stable|beta|b|RC|alpha|a|patch|pl|p)((?:[.-]?\d+)*+)?)?([.-]?dev)?';
    private static $stabilitiesRegex = 'stable|RC|beta|alpha|dev';
    /**
    @phpstan-return
    */
    public static function parseStability($version)
    {
        $version = (string) preg_replace('{#.+$}', '', (string) $version);
        if (strpos($version, 'dev-') === 0 || '-dev' === substr($version, -4)) {
            return 'dev';
        }
        preg_match('{' . self::$modifierRegex . '(?:\+.*)?$}i', strtolower($version), $match);
        if (!empty($match[3])) {
            return 'dev';
        }
        if (!empty($match[1])) {
            if ('beta' === $match[1] || 'b' === $match[1]) {
                return 'beta';
            }
            if ('alpha' === $match[1] || 'a' === $match[1]) {
                return 'alpha';
            }
            if ('rc' === $match[1]) {
                return 'RC';
            }
        }
        return 'stable';
    }
    /**
    @phpstan-return
    */
    public static function normalizeStability($stability)
    {
        $stability = strtolower((string) $stability);
        if (!in_array($stability, array('stable', 'rc', 'beta', 'alpha', 'dev'), \true)) {
            throw new \InvalidArgumentException('Invalid stability string "' . $stability . '", expected one of stable, RC, beta, alpha or dev');
        }
        return $stability === 'rc' ? 'RC' : $stability;
    }
    public function normalize($version, $fullVersion = null)
    {
        $version = trim((string) $version);
        $origVersion = $version;
        if (null === $fullVersion) {
            $fullVersion = $version;
        }
        if (preg_match('{^([^,\s]++) ++as ++([^,\s]++)$}', $version, $match)) {
            $version = $match[1];
        }
        if (preg_match('{@(?:' . self::$stabilitiesRegex . ')$}i', $version, $match)) {
            $version = substr($version, 0, strlen($version) - strlen($match[0]));
        }
        if (\in_array($version, array('master', 'trunk', 'default'), \true)) {
            $version = 'dev-' . $version;
        }
        if (stripos($version, 'dev-') === 0) {
            return 'dev-' . substr($version, 4);
        }
        if (preg_match('{^([^,\s+]++)\+[^\s]++$}', $version, $match)) {
            $version = $match[1];
        }
        if (preg_match('{^v?(\d{1,5}+)(\.\d++)?(\.\d++)?(\.\d++)?' . self::$modifierRegex . '$}i', $version, $matches)) {
            $version = $matches[1] . (!empty($matches[2]) ? $matches[2] : '.0') . (!empty($matches[3]) ? $matches[3] : '.0') . (!empty($matches[4]) ? $matches[4] : '.0');
            $index = 5;
        } elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3}){0,2})' . self::$modifierRegex . '$}i', $version, $matches)) {
            $version = (string) preg_replace('{\D}', '.', $matches[1]);
            $index = 2;
        }
        if (isset($index)) {
            if (!empty($matches[$index])) {
                if ('stable' === $matches[$index]) {
                    return $version;
                }
                $version .= '-' . $this->expandStability($matches[$index]) . (isset($matches[$index + 1]) && '' !== $matches[$index + 1] ? ltrim($matches[$index + 1], '.-') : '');
            }
            if (!empty($matches[$index + 2])) {
                $version .= '-dev';
            }
            return $version;
        }
        if (preg_match('{(.*?)[.-]?dev$}i', $version, $match)) {
            try {
                $normalized = $this->normalizeBranch($match[1]);
                if (strpos($normalized, 'dev-') === \false) {
                    return $normalized;
                }
            } catch (\Exception $e) {
            }
        }
        $extraMessage = '';
        if (preg_match('{ +as +' . preg_quote($version) . '(?:@(?:' . self::$stabilitiesRegex . '))?$}', $fullVersion)) {
            $extraMessage = ' in "' . $fullVersion . '", the alias must be an exact version';
        } elseif (preg_match('{^' . preg_quote($version) . '(?:@(?:' . self::$stabilitiesRegex . '))? +as +}', $fullVersion)) {
            $extraMessage = ' in "' . $fullVersion . '", the alias source must be an exact version, if it is a branch name you should prefix it with dev-';
        }
        throw new \UnexpectedValueException('Invalid version string "' . $origVersion . '"' . $extraMessage);
    }
    public function parseNumericAliasPrefix($branch)
    {
        if (preg_match('{^(?P<version>(\d++\.)*\d++)(?:\.x)?-dev$}i', (string) $branch, $matches)) {
            return $matches['version'] . '.';
        }
        return \false;
    }
    public function normalizeBranch($name)
    {
        $name = trim((string) $name);
        if (preg_match('{^v?(\d++)(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?$}i', $name, $matches)) {
            $version = '';
            for ($i = 1; $i < 5; ++$i) {
                $version .= isset($matches[$i]) ? str_replace(array('*', 'X'), 'x', $matches[$i]) : '.x';
            }
            return str_replace('x', '9999999', $version) . '-dev';
        }
        return 'dev-' . $name;
    }
    public function normalizeDefaultBranch($name)
    {
        if ($name === 'dev-master' || $name === 'dev-default' || $name === 'dev-trunk') {
            return '9999999-dev';
        }
        return (string) $name;
    }
    public function parseConstraints($constraints)
    {
        $prettyConstraint = (string) $constraints;
        $orConstraints = preg_split('{\s*\|\|?\s*}', trim((string) $constraints));
        if (\false === $orConstraints) {
            throw new \RuntimeException('Failed to preg_split string: ' . $constraints);
        }
        $orGroups = array();
        foreach ($orConstraints as $orConstraint) {
            $andConstraints = preg_split('{(?<!^|as|[=>< ,]) *(?<!-)[, ](?!-) *(?!,|as|$)}', $orConstraint);
            if (\false === $andConstraints) {
                throw new \RuntimeException('Failed to preg_split string: ' . $orConstraint);
            }
            if (\count($andConstraints) > 1) {
                $constraintObjects = array();
                foreach ($andConstraints as $andConstraint) {
                    foreach ($this->parseConstraint($andConstraint) as $parsedAndConstraint) {
                        $constraintObjects[] = $parsedAndConstraint;
                    }
                }
            } else {
                $constraintObjects = $this->parseConstraint($andConstraints[0]);
            }
            if (1 === \count($constraintObjects)) {
                $constraint = $constraintObjects[0];
            } else {
                $constraint = new MultiConstraint($constraintObjects);
            }
            $orGroups[] = $constraint;
        }
        $parsedConstraint = MultiConstraint::create($orGroups, \false);
        $parsedConstraint->setPrettyString($prettyConstraint);
        return $parsedConstraint;
    }
    /**
    @phpstan-return
    */
    private function parseConstraint($constraint)
    {
        if (preg_match('{^([^,\s]++) ++as ++([^,\s]++)$}', $constraint, $match)) {
            $constraint = $match[1];
        }
        if (preg_match('{^([^,\s]*?)@(' . self::$stabilitiesRegex . ')$}i', $constraint, $match)) {
            $constraint = '' !== $match[1] ? $match[1] : '*';
            if ($match[2] !== 'stable') {
                $stabilityModifier = $match[2];
            }
        }
        if (preg_match('{^(dev-[^,\s@]+?|[^,\s@]+?\.x-dev)#.+$}i', $constraint, $match)) {
            $constraint = $match[1];
        }
        if (preg_match('{^(v)?[xX*](\.[xX*])*$}i', $constraint, $match)) {
            if (!empty($match[1]) || !empty($match[2])) {
                return array(new Constraint('>=', '0.0.0.0-dev'));
            }
            return array(new MatchAllConstraint());
        }
        $versionRegex = 'v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.(\d++))?(?:' . self::$modifierRegex . '|\.([xX*][.-]?dev))(?:\+[^\s]+)?';
        if (preg_match('{^~>?' . $versionRegex . '$}i', $constraint, $matches)) {
            if (strpos($constraint, '~>') === 0) {
                throw new \UnexpectedValueException('Could not parse version constraint ' . $constraint . ': ' . 'Invalid operator "~>", you probably meant to use the "~" operator');
            }
            if (isset($matches[4]) && '' !== $matches[4] && null !== $matches[4]) {
                $position = 4;
            } elseif (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) {
                $position = 3;
            } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) {
                $position = 2;
            } else {
                $position = 1;
            }
            if (!empty($matches[8])) {
                $position++;
            }
            $stabilitySuffix = '';
            if (empty($matches[5]) && empty($matches[7]) && empty($matches[8])) {
                $stabilitySuffix .= '-dev';
            }
            $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1));
            $lowerBound = new Constraint('>=', $lowVersion);
            $highPosition = max(1, $position - 1);
            $highVersion = $this->manipulateVersionString($matches, $highPosition, 1) . '-dev';
            $upperBound = new Constraint('<', $highVersion);
            return array($lowerBound, $upperBound);
        }
        if (preg_match('{^\^' . $versionRegex . '($)}i', $constraint, $matches)) {
            if ('0' !== $matches[1] || '' === $matches[2] || null === $matches[2]) {
                $position = 1;
            } elseif ('0' !== $matches[2] || '' === $matches[3] || null === $matches[3]) {
                $position = 2;
            } else {
                $position = 3;
            }
            $stabilitySuffix = '';
            if (empty($matches[5]) && empty($matches[7]) && empty($matches[8])) {
                $stabilitySuffix .= '-dev';
            }
            $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1));
            $lowerBound = new Constraint('>=', $lowVersion);
            $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev';
            $upperBound = new Constraint('<', $highVersion);
            return array($lowerBound, $upperBound);
        }
        if (preg_match('{^v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.[xX*])++$}', $constraint, $matches)) {
            if (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) {
                $position = 3;
            } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) {
                $position = 2;
            } else {
                $position = 1;
            }
            $lowVersion = $this->manipulateVersionString($matches, $position) . '-dev';
            $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev';
            if ($lowVersion === '0.0.0.0-dev') {
                return array(new Constraint('<', $highVersion));
            }
            return array(new Constraint('>=', $lowVersion), new Constraint('<', $highVersion));
        }
        if (preg_match('{^(?P<from>' . $versionRegex . ') +- +(?P<to>' . $versionRegex . ')($)}i', $constraint, $matches)) {
            $lowStabilitySuffix = '';
            if (empty($matches[6]) && empty($matches[8]) && empty($matches[9])) {
                $lowStabilitySuffix = '-dev';
            }
            $lowVersion = $this->normalize($matches['from']);
            $lowerBound = new Constraint('>=', $lowVersion . $lowStabilitySuffix);
            $empty = function ($x) {
                return $x === 0 || $x === '0' ? \false : empty($x);
            };
            if (!$empty($matches[12]) && !$empty($matches[13]) || !empty($matches[15]) || !empty($matches[17]) || !empty($matches[18])) {
                $highVersion = $this->normalize($matches['to']);
                $upperBound = new Constraint('<=', $highVersion);
            } else {
                $highMatch = array('', $matches[11], $matches[12], $matches[13], $matches[14]);
                $this->normalize($matches['to']);
                $highVersion = $this->manipulateVersionString($highMatch, $empty($matches[12]) ? 1 : 2, 1) . '-dev';
                $upperBound = new Constraint('<', $highVersion);
            }
            return array($lowerBound, $upperBound);
        }
        if (preg_match('{^(<>|!=|>=?|<=?|==?)?\s*(.*)}', $constraint, $matches)) {
            try {
                try {
                    $version = $this->normalize($matches[2]);
                } catch (\UnexpectedValueException $e) {
                    if (substr($matches[2], -4) === '-dev' && preg_match('{^[0-9a-zA-Z-./]+$}', $matches[2])) {
                        $version = $this->normalize('dev-' . substr($matches[2], 0, -4));
                    } else {
                        throw $e;
                    }
                }
                $op = $matches[1] ?: '=';
                if ($op !== '==' && $op !== '=' && !empty($stabilityModifier) && self::parseStability($version) === 'stable') {
                    $version .= '-' . $stabilityModifier;
                } elseif ('<' === $op || '>=' === $op) {
                    if (!preg_match('/-' . self::$modifierRegex . '$/', strtolower($matches[2]))) {
                        if (strpos($matches[2], 'dev-') !== 0) {
                            $version .= '-dev';
                        }
                    }
                }
                return array(new Constraint($matches[1] ?: '=', $version));
            } catch (\Exception $e) {
            }
        }
        $message = 'Could not parse version constraint ' . $constraint;
        if (isset($e)) {
            $message .= ': ' . $e->getMessage();
        }
        throw new \UnexpectedValueException($message);
    }
    /**
    @phpstan-param
    */
    private function manipulateVersionString(array $matches, $position, $increment = 0, $pad = '0')
    {
        for ($i = 4; $i > 0; --$i) {
            if ($i > $position) {
                $matches[$i] = $pad;
            } elseif ($i === $position && $increment) {
                $matches[$i] += $increment;
                if ($matches[$i] < 0) {
                    $matches[$i] = $pad;
                    --$position;
                    if ($i === 1) {
                        return null;
                    }
                }
            }
        }
        return $matches[1] . '.' . $matches[2] . '.' . $matches[3] . '.' . $matches[4];
    }
    private function expandStability($stability)
    {
        $stability = strtolower($stability);
        switch ($stability) {
            case 'a':
                return 'alpha';
            case 'b':
                return 'beta';
            case 'p':
            case 'pl':
                return 'patch';
            case 'rc':
                return 'RC';
            default:
                return $stability;
        }
    }
}
<?php

namespace HumbugBox4610\Composer\Semver;

use HumbugBox4610\Composer\Semver\Constraint\Constraint;
use HumbugBox4610\Composer\Semver\Constraint\ConstraintInterface;
class CompilingMatcher
{
    /**
    @phpstan-var
    */
    private static $compiledCheckerCache = array();
    /**
    @phpstan-var
    */
    private static $resultCache = array();
    private static $enabled;
    /**
    @phpstan-var
    */
    private static $transOpInt = array(Constraint::OP_EQ => Constraint::STR_OP_EQ, Constraint::OP_LT => Constraint::STR_OP_LT, Constraint::OP_LE => Constraint::STR_OP_LE, Constraint::OP_GT => Constraint::STR_OP_GT, Constraint::OP_GE => Constraint::STR_OP_GE, Constraint::OP_NE => Constraint::STR_OP_NE);
    public static function clear()
    {
        self::$resultCache = array();
        self::$compiledCheckerCache = array();
    }
    /**
    @phpstan-param
    */
    public static function match(ConstraintInterface $constraint, $operator, $version)
    {
        $resultCacheKey = $operator . $constraint . ';' . $version;
        if (isset(self::$resultCache[$resultCacheKey])) {
            return self::$resultCache[$resultCacheKey];
        }
        if (self::$enabled === null) {
            self::$enabled = !\in_array('eval', explode(',', (string) ini_get('disable_functions')), \true);
        }
        if (!self::$enabled) {
            return self::$resultCache[$resultCacheKey] = $constraint->matches(new Constraint(self::$transOpInt[$operator], $version));
        }
        $cacheKey = $operator . $constraint;
        if (!isset(self::$compiledCheckerCache[$cacheKey])) {
            $code = $constraint->compile($operator);
            self::$compiledCheckerCache[$cacheKey] = $function = eval('return function($v, $b){return ' . $code . ';};');
        } else {
            $function = self::$compiledCheckerCache[$cacheKey];
        }
        return self::$resultCache[$resultCacheKey] = $function($version, strpos($version, 'dev-') === 0);
    }
}
<?php

namespace HumbugBox4610\Composer;

use HumbugBox4610\Composer\Autoload\ClassLoader;
use HumbugBox4610\Composer\Semver\VersionParser;
class InstalledVersions
{
    private static $selfDir = null;
    /**
    @psalm-var
    */
    private static $installed;
    private static $installedIsLocalDir;
    private static $canGetVendors;
    /**
    @psalm-var
    */
    private static $installedByVendor = array();
    /**
    @psalm-return
    */
    public static function getInstalledPackages()
    {
        $packages = array();
        foreach (self::getInstalled() as $installed) {
            $packages[] = array_keys($installed['versions']);
        }
        if (1 === \count($packages)) {
            return $packages[0];
        }
        return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
    }
    /**
    @psalm-return
    */
    public static function getInstalledPackagesByType($type)
    {
        $packagesByType = array();
        foreach (self::getInstalled() as $installed) {
            foreach ($installed['versions'] as $name => $package) {
                if (isset($package['type']) && $package['type'] === $type) {
                    $packagesByType[] = $name;
                }
            }
        }
        return $packagesByType;
    }
    public static function isInstalled($packageName, $includeDevRequirements = \true)
    {
        foreach (self::getInstalled() as $installed) {
            if (isset($installed['versions'][$packageName])) {
                return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === \false;
            }
        }
        return \false;
    }
    public static function satisfies(VersionParser $parser, $packageName, $constraint)
    {
        $constraint = $parser->parseConstraints((string) $constraint);
        $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
        return $provided->matches($constraint);
    }
    public static function getVersionRanges($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }
            $ranges = array();
            if (isset($installed['versions'][$packageName]['pretty_version'])) {
                $ranges[] = $installed['versions'][$packageName]['pretty_version'];
            }
            if (array_key_exists('aliases', $installed['versions'][$packageName])) {
                $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
            }
            if (array_key_exists('replaced', $installed['versions'][$packageName])) {
                $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
            }
            if (array_key_exists('provided', $installed['versions'][$packageName])) {
                $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
            }
            return implode(' || ', $ranges);
        }
        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }
    public static function getVersion($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }
            if (!isset($installed['versions'][$packageName]['version'])) {
                return null;
            }
            return $installed['versions'][$packageName]['version'];
        }
        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }
    public static function getPrettyVersion($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }
            if (!isset($installed['versions'][$packageName]['pretty_version'])) {
                return null;
            }
            return $installed['versions'][$packageName]['pretty_version'];
        }
        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }
    public static function getReference($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }
            if (!isset($installed['versions'][$packageName]['reference'])) {
                return null;
            }
            return $installed['versions'][$packageName]['reference'];
        }
        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }
    public static function getInstallPath($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }
            return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
        }
        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }
    /**
    @psalm-return
    */
    public static function getRootPackage()
    {
        $installed = self::getInstalled();
        return $installed[0]['root'];
    }
    /**
    @psalm-return
    */
    public static function getRawData()
    {
        @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', \E_USER_DEPRECATED);
        if (null === self::$installed) {
            if (substr(__DIR__, -8, 1) !== 'C') {
                self::$installed = include __DIR__ . '/installed.php';
            } else {
                self::$installed = array();
            }
        }
        return self::$installed;
    }
    /**
    @psalm-return
    */
    public static function getAllRawData()
    {
        return self::getInstalled();
    }
    /**
    @psalm-param
    */
    public static function reload($data)
    {
        self::$installed = $data;
        self::$installedByVendor = array();
        self::$installedIsLocalDir = \false;
    }
    private static function getSelfDir()
    {
        if (self::$selfDir === null) {
            self::$selfDir = strtr(__DIR__, '\\', '/');
        }
        return self::$selfDir;
    }
    /**
    @psalm-return
    */
    private static function getInstalled()
    {
        if (null === self::$canGetVendors) {
            self::$canGetVendors = method_exists('HumbugBox4610\Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
        }
        $installed = array();
        $copiedLocalDir = \false;
        if (self::$canGetVendors) {
            $selfDir = self::getSelfDir();
            foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
                $vendorDir = strtr($vendorDir, '\\', '/');
                if (isset(self::$installedByVendor[$vendorDir])) {
                    $installed[] = self::$installedByVendor[$vendorDir];
                } elseif (is_file($vendorDir . '/composer/installed.php')) {
                    $required = require $vendorDir . '/composer/installed.php';
                    self::$installedByVendor[$vendorDir] = $required;
                    $installed[] = $required;
                    if (self::$installed === null && $vendorDir . '/composer' === $selfDir) {
                        self::$installed = $required;
                        self::$installedIsLocalDir = \true;
                    }
                }
                if (self::$installedIsLocalDir && $vendorDir . '/composer' === $selfDir) {
                    $copiedLocalDir = \true;
                }
            }
        }
        if (null === self::$installed) {
            if (substr(__DIR__, -8, 1) !== 'C') {
                $required = require __DIR__ . '/installed.php';
                self::$installed = $required;
            } else {
                self::$installed = array();
            }
        }
        if (self::$installed !== array() && !$copiedLocalDir) {
            $installed[] = self::$installed;
        }
        return $installed;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Util;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class Emoji
{
    public const GREEN_CHECKMARK = '✅';
    public const WARNING         = '⚠️ ';
    public const PROHIBITED      = '🚫';
    public const CROSS           = '❌';
    public const INFO            = 'ℹ️ ';
}
<?php

declare(strict_types=1);

namespace Php\Pie\Util;

use Composer\InstalledVersions;

use function class_exists;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class PieVersion
{
    /**
     * Note: magic constant that causes Symfony Console to not display a version
     * {@see Application::getLongVersion()}
     */
    private const SYMFONY_MAGIC_CONST_UNKNOWN = 'UNKNOWN';

    /**
     * This value is replaced dynamically by Box with the real version when
     * we build the PHAR. It is based on the Git tag and/or version
     *
     * It will be replaced with `2.0.0` on an exact tag match, or something
     * like `2.0.0@e558e33` on a commit following a tag.
     *
     * When running not in a PHAR, this will not be replaced, so this
     * method needs additional logic to determine the version.
     *
     * @link https://box-project.github.io/box/configuration/#pretty-git-tag-placeholder-git
     */
    private const PIE_VERSION = '1.4.5@f65dec0';

    public static function isPharBuild(): bool
    {
        // phpcs:ignore Generic.Strings.UnnecessaryStringConcat.Found
        return self::PIE_VERSION !== '@pie_version' . '@';
    }

    /**
     * A static method to try to find the version of PIE you are currently
     * running. If running in the PHAR built with Box, this should return a
     * realistic-looking version; usually either a tag (e.g. `2.0.0`), or a tag
     * and following commit short hash (e.g. `2.0.0@e558e33`). If not this will
     * fall back to some other techniques to try to determine a version.
     *
     * @return non-empty-string
     */
    public static function get(): string
    {
        if (self::isPharBuild()) {
            return self::PIE_VERSION;
        }

        if (! class_exists(InstalledVersions::class)) {
            return self::SYMFONY_MAGIC_CONST_UNKNOWN;
        }

        /**
         * This tries to determine the version based on Composer; if we are
         * the root package (i.e. you're developing on it), this will most
         * likely be something like `dev-main` (branch name).
         */
        $installedVersion = InstalledVersions::getVersion(InstalledVersions::getRootPackage()['name']);
        if ($installedVersion === null || $installedVersion === '') {
            return self::SYMFONY_MAGIC_CONST_UNKNOWN;
        }

        return $installedVersion;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Util;

use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;

use function Safe\file_put_contents;
use function sprintf;
use function strlen;
use function sys_get_temp_dir;
use function uniqid;

use const DIRECTORY_SEPARATOR;

class ProcessFailedWithLimitedOutput extends ProcessFailedException
{
    public static function fromProcessFailedException(ProcessFailedException $previous): self|ProcessFailedException
    {
        $process = $previous->getProcess();

        if (strlen($process->getOutput()) > 5000) {
            $tempOutputFile = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('pie_make_output_', true);
            file_put_contents(
                $tempOutputFile,
                sprintf(
                    "Output:\n================\n%s\n\nError Output:\n================\n%s",
                    $process->getOutput(),
                    $process->getErrorOutput(),
                ),
            );

            return new self($process, $tempOutputFile);
        }

        return $previous;
    }

    public function __construct(Process $process, string $fileWithOutput)
    {
        parent::__construct($process);

        $error = sprintf(
            'The command "%s" failed. Output was saved in "%s" as it was too long' . "\n\nExit Code: %s(%s)\n\nWorking directory: %s",
            $process->getCommandLine(),
            $fileWithOutput,
            $process->getExitCode() ?? '?',
            $process->getExitCodeText() ?? '?',
            $process->getWorkingDirectory() ?? '?',
        );

        $this->message = $error;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Util;

use Composer\Factory;
use Symfony\Component\Console\Formatter\OutputFormatter;

class OutputFormatterWithPrefix extends OutputFormatter
{
    /**
     * @param non-empty-string $linePrefix
     *
     * @inheritDoc
     */
    public function __construct(private readonly string $linePrefix, bool $decorated = false, array $styles = [])
    {
        parent::__construct($decorated, $styles);
    }

    /** @param non-empty-string $linePrefix */
    public static function newWithPrefix(string $linePrefix): self
    {
        return new self($linePrefix, false, Factory::createAdditionalStyles());
    }

    public function format(string|null $message): string|null
    {
        $formatted = parent::format($message);

        if ($formatted === null) {
            return null;
        }

        return $this->linePrefix . $formatted;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Util;

use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process as SymfonyProcess;

use function str_contains;
use function strtolower;
use function trim;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class Process
{
    public const NO_TIMEOUT    = null;
    public const SHORT_TIMEOUT = 10;

    private function __construct()
    {
    }

    /**
     * Just a helper to invoke a Symfony Process command with a simplified API
     * for the common invocations we have in PIE.
     *
     * Things to note:
     *  - uses mustRun (i.e. throws exception if command execution fails)
     *  - very short timeout by default (5 seconds)
     *  - output is trimmed
     *
     * @param list<string>                                                         $command
     * @param callable(SymfonyProcess::ERR|SymfonyProcess::OUT, string): void|null $outputCallback
     * @param array<string, scalar>|null                                           $env
     *
     * @throws ProcessFailedException
     */
    public static function run(
        array $command,
        string|null $workingDirectory = null,
        int|null $timeout = self::NO_TIMEOUT,
        callable|null $outputCallback = null,
        array|null $env = null,
    ): string {
        return trim((new SymfonyProcess($command, $workingDirectory, $env, timeout: $timeout))
            ->mustRun($outputCallback)
            ->getOutput());
    }

    public static function processProbablyPermissionDenied(ProcessFailedException $e): bool
    {
        $mergedProcessOutput = strtolower($e->getProcess()->getErrorOutput() . $e->getProcess()->getOutput());

        $needles = [
            'permission denied',
            'you must be root',
            'operation not permitted',
            'are you root',
            'has to be run with superuser privileges',
        ];

        foreach ($needles as $needle) {
            if (str_contains($mergedProcessOutput, $needle)) {
                return true;
            }
        }

        return false;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Util;

use RuntimeException;

use function sprintf;

class FileNotFound extends RuntimeException
{
    public static function fromFilename(string $filename): self
    {
        return new self(sprintf(
            'File "%s" does not exist',
            $filename,
        ));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Util;

enum PackageVerificationStatus
{
    case Verified;
    case ChecksumMismatch;
    case ActualBinaryNotFound;
    case InstalledBinaryMetadataMissing;
    case ChecksumMetadataMissing;
    case InstalledBinaryPathDoesNotMatchActualBinaryPath;

    public function description(): string
    {
        return match ($this) {
            self::Verified => Emoji::GREEN_CHECKMARK,
            self::ChecksumMismatch => Emoji::PROHIBITED . ' - checksum mismatch',
            self::ActualBinaryNotFound => Emoji::WARNING . ' - extension file not found',
            self::InstalledBinaryMetadataMissing => Emoji::WARNING . ' - installed extension metadata missing',
            self::ChecksumMetadataMissing => Emoji::WARNING . ' - binary checksum metadata missing',
            self::InstalledBinaryPathDoesNotMatchActualBinaryPath => Emoji::WARNING . ' - binary path mismatch',
        };
    }

    public function isVerified(): bool
    {
        return $this === self::Verified;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Building;

use Composer\IO\IOInterface;
use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\File\BinaryFile;
use Php\Pie\Platform\TargetPlatform;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
interface Build
{
    /** @param list<non-empty-string> $configureOptions */
    public function __invoke(
        DownloadedPackage $downloadedPackage,
        TargetPlatform $targetPlatform,
        array $configureOptions,
        IOInterface $io,
    ): BinaryFile;
}
<?php

declare(strict_types=1);

namespace Php\Pie\Building;

use Composer\IO\IOInterface;
use LogicException;
use Php\Pie\ComposerIntegration\BundledPhpExtensionsRepository;
use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\Downloading\DownloadUrlMethod;
use Php\Pie\File\BinaryFile;
use Php\Pie\Platform\TargetPhp\PhpizePath;
use Php\Pie\Platform\TargetPlatform;
use Php\Pie\Util\Process;
use Php\Pie\Util\ProcessFailedWithLimitedOutput;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process as SymfonyProcess;

use function count;
use function file_exists;
use function implode;
use function Safe\rename;
use function sprintf;

use const DIRECTORY_SEPARATOR;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class UnixBuild implements Build
{
    /** {@inheritDoc} */
    public function __invoke(
        DownloadedPackage $downloadedPackage,
        TargetPlatform $targetPlatform,
        array $configureOptions,
        IOInterface $io,
    ): BinaryFile {
        $selectedDownloadMethod = DownloadUrlMethod::fromDownloadedPackage($downloadedPackage);
        switch ($selectedDownloadMethod) {
            case DownloadUrlMethod::PrePackagedBinary:
                return $this->prePackagedBinary($downloadedPackage, $io);

            case DownloadUrlMethod::ComposerDefaultDownload:
            case DownloadUrlMethod::PrePackagedSourceDownload:
                return $this->buildFromSource($downloadedPackage, $targetPlatform, $configureOptions, $io);

            default:
                throw new LogicException('Unsupported download method: ' . $selectedDownloadMethod->value);
        }
    }

    private function prePackagedBinary(
        DownloadedPackage $downloadedPackage,
        IOInterface $io,
    ): BinaryFile {
        $expectedSoFile = $downloadedPackage->extractedSourcePath . '/' . $downloadedPackage->package->extensionName()->name() . '.so';

        if (! file_exists($expectedSoFile)) {
            throw ExtensionBinaryNotFound::fromPrePackagedBinary($expectedSoFile);
        }

        $io->write(sprintf(
            '<info>Pre-packaged binary found:</info> %s',
            $expectedSoFile,
        ));

        return BinaryFile::fromFileWithSha256Checksum($expectedSoFile);
    }

    /** @param list<non-empty-string> $configureOptions */
    private function buildFromSource(
        DownloadedPackage $downloadedPackage,
        TargetPlatform $targetPlatform,
        array $configureOptions,
        IOInterface $io,
    ): BinaryFile {
        $outputCallback = null;
        if ($io->isVerbose()) {
            $outputCallback = static function (string $type, string $outputMessage) use ($io): void {
                $io->write(sprintf(
                    '%s%s%s',
                    $type === SymfonyProcess::ERR ? '<comment>' : '',
                    $outputMessage,
                    $type === SymfonyProcess::ERR ? '</comment>' : '',
                ));
            };
        }

        $phpizePath = $targetPlatform->phpizePath ?? PhpizePath::guessFrom($targetPlatform->phpBinaryPath);

        /**
         * Call a cleanup first; most of the time, we expect to be changing a
         * version (e.g. upgrade, downgrade), in which case the source is
         * already clean anyway; however, sometimes we want to rebuild the
         * current ext, so this will perform a clean first
         */
        $this->cleanup($phpizePath, $downloadedPackage, $io, $outputCallback);

        $this->phpize(
            $phpizePath,
            $downloadedPackage,
            $io,
            $outputCallback,
        );

        $io->write('<info>phpize complete</info>.');

        $phpConfigPath = $targetPlatform->phpBinaryPath->phpConfigPath();
        if ($phpConfigPath !== null) {
            $configureOptions[] = '--with-php-config=' . $phpConfigPath;
        }

        $this->configure($downloadedPackage, $configureOptions, $io, $outputCallback);

        $optionsOutput = count($configureOptions) ? ' with options: ' . implode(' ', $configureOptions) : '.';
        $io->write('<info>Configure complete</info>' . $optionsOutput);

        try {
            $this->make($targetPlatform, $downloadedPackage, $io, $outputCallback);
        } catch (ProcessFailedException $p) {
            throw ProcessFailedWithLimitedOutput::fromProcessFailedException($p);
        }

        $expectedSoFile = $downloadedPackage->extractedSourcePath . '/modules/' . $downloadedPackage->package->extensionName()->name() . '.so';

        if (! file_exists($expectedSoFile)) {
            throw ExtensionBinaryNotFound::fromExpectedBinary($expectedSoFile);
        }

        $io->write(sprintf(
            '<info>Build complete:</info> %s',
            $expectedSoFile,
        ));

        return BinaryFile::fromFileWithSha256Checksum($expectedSoFile);
    }

    private function renamesToConfigM4(DownloadedPackage $downloadedPackage, IOInterface $io): void
    {
        $configM4 = $downloadedPackage->extractedSourcePath . DIRECTORY_SEPARATOR . 'config.m4';
        if (file_exists($configM4)) {
            return;
        }

        $io->write('config.m4 does not exist; checking alternatives', verbosity: IOInterface::VERY_VERBOSE);
        foreach (['config0.m4', 'config9.m4'] as $alternateConfigM4) {
            $fullPathToAlternate = $downloadedPackage->extractedSourcePath . DIRECTORY_SEPARATOR . $alternateConfigM4;
            if (file_exists($fullPathToAlternate)) {
                $io->write(sprintf('Renaming %s to config.m4', $alternateConfigM4), verbosity: IOInterface::VERY_VERBOSE);
                rename($fullPathToAlternate, $configM4);

                return;
            }
        }
    }

    /** @param callable(SymfonyProcess::ERR|SymfonyProcess::OUT, string): void|null $outputCallback */
    private function phpize(
        PhpizePath $phpize,
        DownloadedPackage $downloadedPackage,
        IOInterface $io,
        callable|null $outputCallback,
    ): void {
        $phpizeCommand = [$phpize->phpizeBinaryPath];

        $io->write(
            '<comment>Running phpize step using: ' . implode(' ', $phpizeCommand) . '</comment>',
            verbosity: IOInterface::VERBOSE,
        );

        $this->renamesToConfigM4($downloadedPackage, $io);

        Process::run(
            $phpizeCommand,
            $downloadedPackage->extractedSourcePath,
            outputCallback: $outputCallback,
        );
    }

    /**
     * @param list<non-empty-string>                                               $configureOptions
     * @param callable(SymfonyProcess::ERR|SymfonyProcess::OUT, string): void|null $outputCallback
     */
    private function configure(
        DownloadedPackage $downloadedPackage,
        array $configureOptions,
        IOInterface $io,
        callable|null $outputCallback,
    ): void {
        $configureCommand = ['./configure', ...$configureOptions];

        $io->write(
            '<comment>Running configure step with: ' . implode(' ', $configureCommand) . '</comment>',
            verbosity: IOInterface::VERBOSE,
        );

        Process::run(
            $configureCommand,
            $downloadedPackage->extractedSourcePath,
            outputCallback: $outputCallback,
        );
    }

    /** @param callable(SymfonyProcess::ERR|SymfonyProcess::OUT, string): void|null $outputCallback */
    private function make(
        TargetPlatform $targetPlatform,
        DownloadedPackage $downloadedPackage,
        IOInterface $io,
        callable|null $outputCallback,
    ): void {
        $makeCommand = ['make'];

        if ($targetPlatform->makeParallelJobs === 1) {
            $io->write('Running make without parallelization - try providing -jN to PIE where N is the number of cores you have.');
        } else {
            $makeCommand[] = sprintf('-j%d', $targetPlatform->makeParallelJobs);
        }

        $makeCommand = BundledPhpExtensionsRepository::augmentMakeCommandForPhpBundledExtensions(
            $makeCommand,
            $downloadedPackage,
        );

        $io->write(
            '<comment>Running make step with: ' . implode(' ', $makeCommand) . '</comment>',
            verbosity: IOInterface::VERBOSE,
        );

        Process::run(
            $makeCommand,
            $downloadedPackage->extractedSourcePath,
            outputCallback: $outputCallback,
        );
    }

    /** @param callable(SymfonyProcess::ERR|SymfonyProcess::OUT, string): void|null $outputCallback */
    private function cleanup(
        PhpizePath $phpize,
        DownloadedPackage $downloadedPackage,
        IOInterface $io,
        callable|null $outputCallback,
    ): void {
        /**
         * A basic, but fallible check to see if we should clean first. This
         * should work "most" of the time, unless someone has removed the
         * configure script manually...
         */
        if (! file_exists($downloadedPackage->extractedSourcePath . '/configure')) {
            $io->write(
                '<comment>Skipping phpize --clean, configure does not exist</comment>',
                verbosity: IOInterface::VERBOSE,
            );

            return;
        }

        $phpizeCleanCommand = [$phpize->phpizeBinaryPath, '--clean'];

        $io->write(
            '<comment>Running phpize --clean step using: ' . implode(' ', $phpizeCleanCommand) . '</comment>',
            verbosity: IOInterface::VERBOSE,
        );

        Process::run(
            $phpizeCleanCommand,
            $downloadedPackage->extractedSourcePath,
            outputCallback: $outputCallback,
        );

        $io->write('<info>Build files cleaned up.</info>');
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Building;

use Composer\IO\IOInterface;
use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\File\BinaryFile;
use Php\Pie\Platform\TargetPlatform;
use Php\Pie\Platform\WindowsExtensionAssetName;

use function sprintf;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class WindowsBuild implements Build
{
    /** {@inheritDoc} */
    public function __invoke(
        DownloadedPackage $downloadedPackage,
        TargetPlatform $targetPlatform,
        array $configureOptions,
        IOInterface $io,
    ): BinaryFile {
        $prebuiltDll = WindowsExtensionAssetName::determineDllName($targetPlatform, $downloadedPackage);

        $io->write(
            sprintf(
                '<info>Nothing to build on Windows, prebuilt DLL found:</info> %s',
                $prebuiltDll,
            ),
            verbosity: IOInterface::VERBOSE,
        );

        return BinaryFile::fromFileWithSha256Checksum($prebuiltDll);
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Building;

use RuntimeException;

use function sprintf;

class ExtensionBinaryNotFound extends RuntimeException
{
    public static function fromPrePackagedBinary(string $expectedBinaryName): self
    {
        return new self(sprintf(
            'Expected pre-packaged binary does not exist: %s',
            $expectedBinaryName,
        ));
    }

    public static function fromExpectedBinary(string $expectedBinaryName): self
    {
        return new self(sprintf(
            'Build complete, but expected %s does not exist.',
            $expectedBinaryName,
        ));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Building;

use Composer\IO\IOInterface;
use DateTimeInterface;
use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\Platform\TargetPlatform;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use Safe\Exceptions\FilesystemException;
use SplFileInfo;

use function assert;
use function in_array;
use function Safe\file_get_contents;
use function Safe\file_put_contents;
use function str_ireplace;

/**
 * This tries to support some of the PECL style replacements, but is less flexible. In PECL, you would define in your
 * package.xml something like;
 *
 *     <tasks:replace from="@package_version@" to="version" type="package-info" />
 *
 * This would perform a replacement on the given file. I [searched through GitHub](https://github.com/search?q=%22tasks%3Areplace%22+language%3AXML&type=code&p=1&l=XML)
 * to find some common replacements to make, and defined them in a mostly hard-coded way, or at least the ones that
 * make sense or low-hanging fruit (all underscores can also use hyphens):
 *
 * - @name@ or @package_name@ - replaces with the extension name (NOT the Composer package name)
 * - @version@ or @package_version@ - replaces with the Composer "pretty" version
 * - @release_date@ - release date according to Composer package, formatted 'Y-m-d\TH:i:sP'
 * - @php_bin@ - path to the PHP binary
 *
 * Some omitted replacements are `@php_dir@` (seems to actually point to PEAR directory, which is redundant anyway),
 * `@bin_dir@` (could get this with PHP_BINDIR constant from the $targetPlatform, but not sure if is worth the time),
 * and a few other variations (@phd_ide_version@ for example, which could be replaced with @package_version@).
 *
 * If this feature needs more flexibility in future, we can look into it, but this implementation seems to be a fairly
 * easy win for some basic replacements anyway...
 */
class PlaceholderReplacer
{
    private const FILE_EXTENSIONS = ['c', 'h'];

    public function replacePlaceholdersWithPlaceholderReplacements(IOInterface $io, TargetPlatform $targetPlatform, DownloadedPackage $downloadedPackage): void
    {
        $replacements = [
            'ext-name' => $downloadedPackage->package->extensionName()->name(),
            'version' => $downloadedPackage->package->composerPackage()->getPrettyVersion(),
            'release-date' => (string) $downloadedPackage->package->composerPackage()->getReleaseDate()?->format(DateTimeInterface::ATOM),
            'php-bin' => $targetPlatform->phpBinaryPath->phpBinaryPath,
        ];

        foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($downloadedPackage->extractedSourcePath)) as $file) {
            assert($file instanceof SplFileInfo);

            // Refuse to follow symlinks into files the package author did not
            // legitimately own at build time (e.g., an archive entry that points
            // at the invoking user's $HOME).
            if ($file->isLink()) {
                continue;
            }

            if (! $file->isFile() || ! in_array($file->getExtension(), self::FILE_EXTENSIONS)) {
                continue;
            }

            $io->write('Replacing placeholders in: ' . $file->getPathname(), verbosity: IOInterface::VERY_VERBOSE);
            try {
                $this->replaceReplacementsInFile($replacements, $file->getPathname());
            } catch (FilesystemException $e) {
                $io->write('Placeholder replacement failed in : ' . $file->getPathname(), verbosity: IOInterface::VERBOSE);
                $io->write((string) $e, verbosity: IOInterface::VERBOSE);
            }
        }
    }

    /**
     * @param array{ext-name: string, version: string, release-date: string, php-bin: string} $replacements
     *
     * @throws FilesystemException
     */
    private function replaceReplacementsInFile(array $replacements, string $filename): void
    {
        file_put_contents(
            $filename,
            str_ireplace(
                [
                    '@name@',
                    '@package_name@',
                    '@package-name@',
                    '@package_version@',
                    '@package-version@',
                    '@release_date@',
                    '@release-date@',
                    '@php_bin@',
                    '@php-bin@',
                ],
                [
                    $replacements['ext-name'],
                    $replacements['ext-name'],
                    $replacements['ext-name'],
                    $replacements['version'],
                    $replacements['version'],
                    $replacements['release-date'],
                    $replacements['release-date'],
                    $replacements['php-bin'],
                    $replacements['php-bin'],
                ],
                file_get_contents($filename),
            ),
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\DependencyResolver;

use Composer\Composer;
use Php\Pie\Platform\TargetPlatform;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
interface DependencyResolver
{
    /** @throws UnableToResolveRequirement */
    public function __invoke(
        Composer $composer,
        TargetPlatform $targetPlatform,
        RequestedPackageAndVersion $requestedPackageAndVersion,
        bool $forceInstallPackageVersion,
    ): Package;
}
<?php

declare(strict_types=1);

namespace Php\Pie\DependencyResolver;

use Composer\Composer;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\IO\IOInterface;
use Composer\Package\CompletePackageInterface;
use Php\Pie\ComposerIntegration\QuieterConsoleIO;
use Php\Pie\ComposerIntegration\VersionSelectorFactory;
use Php\Pie\ExtensionType;
use Php\Pie\Platform\TargetPlatform;
use Php\Pie\Platform\ThreadSafetyMode;

use function in_array;
use function Safe\preg_match;
use function sprintf;
use function str_ends_with;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class ResolveDependencyWithComposer implements DependencyResolver
{
    public function __construct(
        private readonly IOInterface $io,
        private readonly QuieterConsoleIO $arrayCollectionIo,
    ) {
    }

    public function __invoke(
        Composer $composer,
        TargetPlatform $targetPlatform,
        RequestedPackageAndVersion $requestedPackageAndVersion,
        bool $forceInstallPackageVersion,
    ): Package {
        $versionSelector = VersionSelectorFactory::make($composer, $requestedPackageAndVersion, $targetPlatform);

        $package = $versionSelector->findBestCandidate(
            $requestedPackageAndVersion->package,
            $requestedPackageAndVersion->version,
            platformRequirementFilter: PlatformRequirementFilterFactory::fromBoolOrList($forceInstallPackageVersion),
            io: $this->arrayCollectionIo,
        );

        if (! $package instanceof CompletePackageInterface) {
            throw UnableToResolveRequirement::fromRequirement($requestedPackageAndVersion, $this->arrayCollectionIo);
        }

        /**
         * If a specific commit hash is requested, override the references in the package. This is approximately what
         * Composer does anyway:
         *
         * > ArrayLoader::parseLinks is in charge of this, it drops commit refs, for package resolution purposes we
         * > only use dev-main, but we ensure in the PoolBuilder that root references (#...) are set so the dev-main
         * > package has its source and dist refs overridden to be whatever you specify and that applies at install
         * > time then but package metadata is only read from the branch's head
         */
        if ($requestedPackageAndVersion->version !== null && preg_match('/#([a-f0-9]{40})$/', $requestedPackageAndVersion->version, $matches)) {
            $package->setSourceDistReferences($matches[1]);
        }

        if (! ExtensionType::isValid($package->getType())) {
            throw UnableToResolveRequirement::toPhpOrZendExtension($package, $requestedPackageAndVersion);
        }

        $piePackage = Package::fromComposerCompletePackage($package);

        $this->assertBuildProviderProvidersBundledExtensions($targetPlatform, $piePackage, $forceInstallPackageVersion);

        if (! $forceInstallPackageVersion) {
            $this->assertCompatibleOsFamily($targetPlatform, $piePackage);
            $this->assertCompatibleThreadSafetyMode($targetPlatform->threadSafety, $piePackage);
        }

        return $piePackage;
    }

    private function assertCompatibleThreadSafetyMode(ThreadSafetyMode $threadSafetyMode, Package $resolvedPackage): void
    {
        if ($threadSafetyMode === ThreadSafetyMode::NonThreadSafe && ! $resolvedPackage->supportNts()) {
            throw IncompatibleThreadSafetyMode::ztsExtensionOnNtsPlatform();
        }

        if ($threadSafetyMode === ThreadSafetyMode::ThreadSafe && ! $resolvedPackage->supportZts()) {
            throw IncompatibleThreadSafetyMode::ntsExtensionOnZtsPlatform();
        }
    }

    private function assertCompatibleOsFamily(TargetPlatform $targetPlatform, Package $resolvedPackage): void
    {
        if ($resolvedPackage->compatibleOsFamilies() !== null && ! in_array($targetPlatform->operatingSystemFamily, $resolvedPackage->compatibleOsFamilies(), true)) {
            throw IncompatibleOperatingSystemFamily::notInCompatibleOperatingSystemFamilies(
                $resolvedPackage->compatibleOsFamilies(),
                $targetPlatform->operatingSystemFamily,
            );
        }

        if ($resolvedPackage->incompatibleOsFamilies() !== null && in_array($targetPlatform->operatingSystemFamily, $resolvedPackage->incompatibleOsFamilies(), true)) {
            throw IncompatibleOperatingSystemFamily::inIncompatibleOperatingSystemFamily(
                $resolvedPackage->incompatibleOsFamilies(),
                $targetPlatform->operatingSystemFamily,
            );
        }
    }

    private function assertBuildProviderProvidersBundledExtensions(TargetPlatform $targetPlatform, Package $piePackage, bool $forceInstallPackageVersion): void
    {
        if (! $piePackage->isBundledPhpExtension()) {
            return;
        }

        if (str_ends_with($targetPlatform->phpBinaryPath->phpVersionWithExtra(), '-dev')) {
            throw BundledPhpExtensionRefusal::forPhpExtraVersion($targetPlatform->phpBinaryPath);
        }

        $buildProvider           = $targetPlatform->phpBinaryPath->buildProvider();
        $identifiedBuildProvider = false;
        $note                    = '<options=bold,underscore;fg=red>Note:</> ';

        if ($buildProvider === 'https://github.com/docker-library/php') {
            $identifiedBuildProvider = true;
            $this->io->write(sprintf(
                '<comment>%sYou should probably use "docker-php-ext-install %s" instead</comment>',
                $note,
                $piePackage->extensionName()->name(),
            ));
        }

        if ($buildProvider === 'Debian') {
            $identifiedBuildProvider = true;
            $this->io->write(sprintf(
                '<comment>%sYou should probably use "apt install php%s-%s" or "apt install php-%s" (or similar) instead</comment>',
                $note,
                $targetPlatform->phpBinaryPath->majorMinorVersion(),
                $piePackage->extensionName()->name(),
                $piePackage->extensionName()->name(),
            ));
        }

        if ($buildProvider === 'Remi\'s RPM repository <https://rpms.remirepo.net/> #StandWithUkraine') {
            $identifiedBuildProvider = true;
            $this->io->write(sprintf(
                '<comment>%sYou should probably use "dnf install php-%s" instead</comment>',
                $note,
                $piePackage->extensionName()->name(),
            ));
        }

        if ($buildProvider === 'Homebrew') {
            $identifiedBuildProvider = true;
            $this->io->write(sprintf(
                '<comment>%sThe bundled extension %s is likely already installed with Homebrew, and you should use that version.</comment>',
                $note,
                $piePackage->extensionName()->name(),
            ));
        }

        if ($identifiedBuildProvider && ! $forceInstallPackageVersion) {
            throw BundledPhpExtensionRefusal::forPackage($piePackage);
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\DependencyResolver;

use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\ConstraintInterface;
use Php\Pie\Util\Emoji;

use function sprintf;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class DependencyStatus
{
    public function __construct(
        public readonly string $name,
        public readonly ConstraintInterface $requireConstraint,
        public readonly Constraint|null $installedVersion,
    ) {
    }

    public function asPrettyString(): string
    {
        $statusTemplate = sprintf('%s: %s %%s', $this->name, $this->requireConstraint->getPrettyString());
        if ($this->installedVersion === null) {
            return sprintf($statusTemplate, Emoji::PROHIBITED . ' (not installed)');
        }

        if (! $this->requireConstraint->matches($this->installedVersion)) {
            return sprintf($statusTemplate, Emoji::PROHIBITED . ' (your version is ' . $this->installedVersion->getVersion() . ')');
        }

        return sprintf($statusTemplate, Emoji::GREEN_CHECKMARK);
    }

    public function satisfied(): bool
    {
        return $this->installedVersion !== null && $this->requireConstraint->matches($this->installedVersion);
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\DependencyResolver;

use Composer\Composer;
use Composer\Package\CompletePackageInterface;
use Composer\Semver\Constraint\Constraint;
use Php\Pie\ComposerIntegration\PhpBinaryPathBasedPlatformRepository;
use Php\Pie\Platform\InstalledPiePackages;
use Php\Pie\Platform\TargetPlatform;

use function array_key_exists;
use function count;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class FetchDependencyStatuses
{
    /** @return list<DependencyStatus> */
    public function __invoke(TargetPlatform $targetPlatform, Composer $composer, CompletePackageInterface $package): array
    {
        $requires = $package->getRequires();

        if (count($requires) <= 0) {
            return [];
        }

        /** @var array<string, Constraint> $platformConstraints */
        $platformConstraints = [];
        $composerPlatform    = new PhpBinaryPathBasedPlatformRepository($targetPlatform->phpBinaryPath, $composer, new InstalledPiePackages(), null);
        foreach ($composerPlatform->getPackages() as $platformPackage) {
            $platformConstraints[$platformPackage->getName()] = new Constraint('==', $platformPackage->getVersion());
        }

        $checkedPackages = [];

        foreach ($requires as $requireName => $requireLink) {
            $checkedPackages[] = new DependencyStatus(
                $requireName,
                $requireLink->getConstraint(),
                array_key_exists($requireName, $platformConstraints) ? $platformConstraints[$requireName] : null,
            );
        }

        return $checkedPackages;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\DependencyResolver;

use Composer\Package\PackageInterface;
use Php\Pie\ComposerIntegration\QuieterConsoleIO;
use RuntimeException;

use function count;
use function implode;
use function sprintf;

class UnableToResolveRequirement extends RuntimeException
{
    public function __construct(string $message, public readonly RequestedPackageAndVersion $requestedPackageAndVersion)
    {
        parent::__construct($message);
    }

    public static function fromRequirement(
        RequestedPackageAndVersion $requestedPackageAndVersion,
        QuieterConsoleIO $io,
    ): self {
        $errors = $io->errors;

        return new self(
            sprintf(
                'Unable to find an installable package %s for %s, with minimum stability %s.%s',
                $requestedPackageAndVersion->package,
                $requestedPackageAndVersion->version !== null ? sprintf('version %s', $requestedPackageAndVersion->version) : 'the latest compatible version',
                DetermineMinimumStability::fromRequestedVersion($requestedPackageAndVersion),
                count($errors) ? "\n\n" . implode("\n\n", $errors) : '',
            ),
            $requestedPackageAndVersion,
        );
    }

    public static function toPhpOrZendExtension(PackageInterface $locatedComposerPackage, RequestedPackageAndVersion $requestedPackageAndVersion): self
    {
        return new self(
            sprintf(
                'Package %s was not of type php-ext or php-ext-zend (requested %s%s).',
                $locatedComposerPackage->getName(),
                $requestedPackageAndVersion->package,
                $requestedPackageAndVersion->version !== null ? sprintf(' for version %s', $requestedPackageAndVersion->version) : '',
            ),
            $requestedPackageAndVersion,
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\DependencyResolver;

use Composer\Semver\VersionParser;
use Webmozart\Assert\Assert;

use function str_starts_with;

/**
 * Utility to extract a valid Stability (see {@see \Composer\Package\BasePackage::$stabilities}) from a requested
 * package version in a predictable way.
 *
 * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
 */
final class DetermineMinimumStability
{
    private const STABILITY_STABLE = 'stable';
    private const STABILITY_RC     = 'RC';
    private const STABILITY_BETA   = 'beta';
    private const STABILITY_ALPHA  = 'alpha';
    private const STABILITY_DEV    = 'dev';

    private const DEFAULT_MINIMUM_STABILITY = self::STABILITY_STABLE;

    /** @phpstan-assert self::STABILITY_* $stability */
    private static function assertValidStabilityString(string $stability): void
    {
        Assert::oneOf(
            $stability,
            [
                self::STABILITY_STABLE,
                self::STABILITY_RC,
                self::STABILITY_BETA,
                self::STABILITY_ALPHA,
                self::STABILITY_DEV,
            ],
        );
    }

    /** @return self::STABILITY_* */
    public static function fromRequestedVersion(RequestedPackageAndVersion $requestedPackageAndVersion): string
    {
        if ($requestedPackageAndVersion->version === null) {
            if (str_starts_with($requestedPackageAndVersion->package, 'php/')) {
                return self::STABILITY_DEV;
            }

            return self::DEFAULT_MINIMUM_STABILITY;
        }

        $stability = VersionParser::parseStability($requestedPackageAndVersion->version);
        self::assertValidStabilityString($stability);

        return $stability;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\DependencyResolver\DependencyInstaller;

use Composer\Composer;
use Composer\IO\IOInterface;
use Php\Pie\DependencyResolver\DependencyResolver;
use Php\Pie\DependencyResolver\DependencyStatus;
use Php\Pie\DependencyResolver\FetchDependencyStatuses;
use Php\Pie\DependencyResolver\RequestedPackageAndVersion;
use Php\Pie\Platform\PackageManager;
use Php\Pie\Platform\TargetPlatform;
use Throwable;

use function array_filter;
use function array_key_exists;
use function array_map;
use function array_unique;
use function array_values;
use function count;
use function implode;
use function sprintf;
use function str_replace;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class PrescanSystemDependencies
{
    public function __construct(
        private readonly DependencyResolver $dependencyResolver,
        private readonly FetchDependencyStatuses $fetchDependencyStatuses,
        private readonly SystemDependenciesDefinition $systemDependenciesDefinition,
        private readonly PackageManager|null $packageManager,
        private readonly IOInterface $io,
    ) {
    }

    public function __invoke(
        Composer $composer,
        TargetPlatform $targetPlatform,
        RequestedPackageAndVersion $requestedNameAndVersion,
        bool $autoInstallIfMissing,
    ): void {
        if ($this->packageManager === null) {
            $this->io->writeError('<comment>Skipping pre-scan of system dependencies, as a supported package manager could not be detected.</comment>', verbosity: IOInterface::VERBOSE);

            return;
        }

        $this->io->write(sprintf('Checking system dependencies are present for extension %s', $requestedNameAndVersion->prettyNameAndVersion()), verbosity: IOInterface::VERBOSE);

        $package = ($this->dependencyResolver)(
            $composer,
            $targetPlatform,
            $requestedNameAndVersion,
            true,
        );

        $unmetDependencies = array_filter(
            ($this->fetchDependencyStatuses)($targetPlatform, $composer, $package->composerPackage()),
            static function (DependencyStatus $dependencyStatus): bool {
                return ! $dependencyStatus->satisfied();
            },
        );

        if (! count($unmetDependencies)) {
            $this->io->write('All system dependencies are already installed.', verbosity: IOInterface::VERBOSE);

            return;
        }

        $this->io->write(
            sprintf('Extension %s has unmet dependencies: %s', $requestedNameAndVersion->prettyNameAndVersion(), implode(', ', array_map(static fn (DependencyStatus $status): string => $status->name, $unmetDependencies))),
            verbosity: IOInterface::VERBOSE,
        );

        $packageManagerPackages = array_values(array_unique(array_filter(array_map(
            fn (DependencyStatus $unmetDependency): string|null => $this->packageManagerPackageForDependency($unmetDependency, $this->packageManager),
            $unmetDependencies,
        ))));

        if (! count($packageManagerPackages)) {
            $this->io->writeError('No system dependencies could be installed automatically by PIE.', verbosity: IOInterface::VERBOSE);

            return;
        }

        $proposedInstallCommand = implode(' ', $this->packageManager->installCommand($packageManagerPackages));

        if (! $this->io->isInteractive() && ! $autoInstallIfMissing) {
            $this->io->writeError('<warning>You are not running in interactive mode, and you did not provide the --auto-install-system-dependencies flag.');
            $this->io->writeError('You may need to run: ' . $proposedInstallCommand . '</warning>');
            $this->io->writeError('');

            return;
        }

        $this->io->write(sprintf('<info>Need to install missing system dependencies:</info> %s', $proposedInstallCommand));

        if ($this->io->isInteractive() && ! $autoInstallIfMissing) {
            if (! $this->io->askConfirmation('<question>Would you like to install them now?</question>', false)) {
                $this->io->write('<comment>Ok, but things might not work. Just so you know.</comment>');

                return;
            }
        }

        try {
            $this->packageManager->install($packageManagerPackages);

            $this->io->write('<info>Missing system dependencies have been installed.</info>');
        } catch (Throwable $anything) {
            $this->io->writeError(sprintf('<info>Failed to install missing system dependencies:</info> %s', $anything->getMessage()));
        }
    }

    private function packageManagerPackageForDependency(DependencyStatus $unmetDependency, PackageManager $packageManager): string|null
    {
        $depName = str_replace('lib-', '', $unmetDependency->name);

        if (! array_key_exists($depName, $this->systemDependenciesDefinition->definition)) {
            $this->io->writeError(
                sprintf('Could not automatically install "%s", as PIE does not have the package manager definition.', $unmetDependency->name),
                verbosity: IOInterface::VERBOSE,
            );

            return null;
        }

        if (! array_key_exists($packageManager->value, $this->systemDependenciesDefinition->definition[$depName])) {
            $this->io->writeError(
                sprintf('Could not automatically install "%s", as PIE does not have a definition for "%s"', $unmetDependency->name, $packageManager->value),
                verbosity: IOInterface::VERBOSE,
            );

            return null;
        }

        $packageManagerPackage = $this->systemDependenciesDefinition->definition[$depName][$packageManager->value];

        // Note: ideally, we should also parse the version constraint. This initial iteration will ignore that, to be improved later.
        $this->io->write(
            sprintf('Adding %s package %s to be installed for %s', $packageManager->value, $packageManagerPackage, $unmetDependency->name),
            verbosity: IOInterface::VERBOSE,
        );

        return $packageManagerPackage;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\DependencyResolver\DependencyInstaller;

use Php\Pie\Platform\PackageManager;

class SystemDependenciesDefinition
{
    /** @param array<non-empty-string, array<non-empty-string, non-empty-string>> $definition */
    public function __construct(public readonly array $definition)
    {
    }

    /**
     * Checks for the existence of these libraries should be added into
     * {@see \Php\Pie\ComposerIntegration\PhpBinaryPathBasedPlatformRepository::addLibrariesUsingPkgConfig()}
     */
    public static function default(): self
    {
        return new self([
            'sodium' => [
                PackageManager::Apt->value => 'libsodium-dev',
                PackageManager::Apk->value => 'libsodium-dev',
                PackageManager::Dnf->value => 'pkgconfig(libsodium)',
                PackageManager::Yum->value => 'pkgconfig(libsodium)',
            ],
            'jpeg' => [
                PackageManager::Apt->value => 'libjpeg-dev',
                PackageManager::Apk->value => 'libjpeg-turbo-dev',
                PackageManager::Dnf->value => 'pkgconfig(libjpeg)',
                PackageManager::Yum->value => 'pkgconfig(libjpeg)',
            ],
            'zip' => [
                PackageManager::Apt->value => 'libzip-dev',
                PackageManager::Apk->value => 'libzip-dev',
                PackageManager::Dnf->value => 'pkgconfig(libzip)',
                PackageManager::Yum->value => 'pkgconfig(libzip)',
            ],
            'xslt' => [
                PackageManager::Apt->value => 'libxslt1-dev',
                PackageManager::Apk->value => 'libxslt-dev',
                PackageManager::Dnf->value => 'pkgconfig(libxslt)',
                PackageManager::Yum->value => 'pkgconfig(libxslt)',
            ],
            'ffi' => [
                PackageManager::Apt->value => 'libffi-dev',
                PackageManager::Apk->value => 'libffi-dev',
                PackageManager::Dnf->value => 'pkgconfig(libffi)',
                PackageManager::Yum->value => 'pkgconfig(libffi)',
            ],
            'curl' => [
                PackageManager::Apt->value => 'libcurl4-openssl-dev',
                PackageManager::Apk->value => 'curl-dev',
                PackageManager::Dnf->value => 'pkgconfig(libcurl)',
                PackageManager::Yum->value => 'pkgconfig(libcurl)',
            ],
        ]);
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\DependencyResolver;

use function str_contains;

/**
 * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
 *
 * @immutable
 */
final class RequestedPackageAndVersion
{
    /**
     * @param non-empty-string      $package
     * @param non-empty-string|null $version
     *
     * @throws InvalidPackageName
     */
    public function __construct(
        public readonly string $package,
        public readonly string|null $version,
    ) {
        if (! str_contains($this->package, '/')) {
            throw InvalidPackageName::fromMissingForwardSlash($this);
        }
    }

    public function prettyNameAndVersion(): string
    {
        if ($this->version === null) {
            return $this->package;
        }

        return $this->package . ':' . $this->version;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\DependencyResolver;

use Php\Pie\Platform\TargetPhp\PhpBinaryPath;
use RuntimeException;

use function sprintf;

use const PHP_EOL;

class BundledPhpExtensionRefusal extends RuntimeException
{
    public static function forPackage(Package $package): self
    {
        return new self(sprintf(
            'Bundled PHP extension %s should be installed by your distribution, not by PIE.%s%sCombining installation methods of bundled PHP extensions can lead to confusing and unintended consequences.%s%sIf you are really sure, you want to install %s using PIE, re-run the command with the --force flag.',
            $package->name(),
            PHP_EOL,
            PHP_EOL,
            PHP_EOL,
            PHP_EOL,
            $package->name(),
        ));
    }

    public static function forPhpExtraVersion(PhpBinaryPath $phpBinaryPath): self
    {
        return new self(sprintf(
            'Cannot install bundled PHP extension for non-stable versions of PHP (detected: %s)',
            $phpBinaryPath->phpVersionWithExtra(),
        ));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\DependencyResolver;

use RuntimeException;

class IncompatibleThreadSafetyMode extends RuntimeException
{
    public static function ztsExtensionOnNtsPlatform(): self
    {
        return new self('This extension does not support being installed on a non-Thread Safe PHP installation');
    }

    public static function ntsExtensionOnZtsPlatform(): self
    {
        return new self('This extension does not support being installed on a Thread Safe PHP installation');
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\DependencyResolver;

use Composer\Package\CompletePackageInterface;
use InvalidArgumentException;
use Php\Pie\ComposerIntegration\InstalledJsonMetadata;
use Php\Pie\ConfigureOption;
use Php\Pie\Downloading\DownloadUrlMethod;
use Php\Pie\ExtensionName;
use Php\Pie\ExtensionType;
use Php\Pie\File\BinaryFile;
use Php\Pie\File\BinaryFileFailedVerification;
use Php\Pie\Platform\OperatingSystem;
use Php\Pie\Platform\OperatingSystemFamily;
use Php\Pie\Platform\TargetPlatform;
use Php\Pie\Util\PackageVerificationStatus;
use Safe\Exceptions\UrlException;
use Webmozart\Assert\Assert;

use function array_key_exists;
use function array_map;
use function array_slice;
use function count;
use function explode;
use function file_exists;
use function implode;
use function is_array;
use function is_string;
use function Safe\parse_url;
use function str_contains;
use function str_starts_with;
use function strlen;
use function strtolower;

use const DIRECTORY_SEPARATOR;

/**
 * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
 *
 * @immutable
 */
final class Package
{
    /** @var list<ConfigureOption> */
    private array $configureOptions = [];
    private int $priority           = 80;
    private string|null $buildPath  = null;
    /** @var non-empty-list<OperatingSystemFamily>|null */
    private array|null $compatibleOsFamilies = null;
    /** @var non-empty-list<OperatingSystemFamily>|null */
    private array|null $incompatibleOsFamilies = null;
    private bool $supportZts                   = true;
    private bool $supportNts                   = true;
    /** @var non-empty-list<DownloadUrlMethod>|null */
    private array|null $supportedDownloadUrlMethods = null;
    private readonly InstalledJsonMetadata $installedJsonMetadata;

    public function __construct(
        private readonly CompletePackageInterface $composerPackage,
        private readonly ExtensionType $extensionType,
        private readonly ExtensionName $extensionName,
        private readonly string $name,
        private readonly string $version,
        private readonly string|null $downloadUrl,
    ) {
        $this->installedJsonMetadata = InstalledJsonMetadata::fromComposerPackage($this->composerPackage);
    }

    public static function fromComposerCompletePackage(CompletePackageInterface $completePackage): self
    {
        $package = new self(
            $completePackage,
            ExtensionType::tryFrom($completePackage->getType()) ?? ExtensionType::PhpModule,
            ExtensionName::determineFromComposerPackage($completePackage),
            $completePackage->getPrettyName(),
            $completePackage->getPrettyVersion(),
            $completePackage->getDistUrl(),
        );

        $phpExtOptions = $completePackage->getPhpExt();

        $package->configureOptions = $phpExtOptions !== null && array_key_exists('configure-options', $phpExtOptions)
            ? array_map(
                static fn (array $configureOption): ConfigureOption => ConfigureOption::fromComposerJsonDefinition($configureOption),
                $phpExtOptions['configure-options'],
            )
            : [];

        $package->supportZts = $phpExtOptions['support-zts'] ?? true;
        $package->supportNts = $phpExtOptions['support-nts'] ?? true;

        $buildPath = $phpExtOptions['build-path'] ?? null;
        if ($buildPath !== null) {
            if (
                str_starts_with($buildPath, '/')
                || str_starts_with($buildPath, '\\')
                || (strlen($buildPath) > 1 && $buildPath[1] === ':')
            ) {
                throw new InvalidArgumentException('php-ext.build-path must be a relative path.');
            }

            if (str_contains($buildPath, '..')) {
                throw new InvalidArgumentException('php-ext.build-path cannot contain ".." segments.');
            }
        }

        $package->buildPath = $buildPath;

        $compatibleOsFamilies   = $phpExtOptions['os-families'] ?? null;
        $incompatibleOsFamilies = $phpExtOptions['os-families-exclude'] ?? null;

        if ($compatibleOsFamilies !== null && $incompatibleOsFamilies !== null) {
            throw new InvalidArgumentException('Cannot specify both "os-families" and "os-families-exclude" in composer.json');
        }

        $package->compatibleOsFamilies   = self::convertInputStringsToOperatingSystemFamilies($compatibleOsFamilies);
        $package->incompatibleOsFamilies = self::convertInputStringsToOperatingSystemFamilies($incompatibleOsFamilies);

        $package->priority = $phpExtOptions['priority'] ?? 80;

        if ($phpExtOptions !== null && array_key_exists('download-url-method', $phpExtOptions)) {
            /** @var string|list<string> $extOptionValue */
            $extOptionValue = $phpExtOptions['download-url-method'];
            $methods        = is_array($extOptionValue) ? $extOptionValue : [$extOptionValue];
            if (count($methods) > 0) {
                $package->supportedDownloadUrlMethods = array_map(
                    static fn (string $method): DownloadUrlMethod => DownloadUrlMethod::from($method),
                    $methods,
                );
            }
        }

        return $package;
    }

    public function prettyNameAndVersion(): string
    {
        return $this->name . ':' . $this->version;
    }

    public function githubOrgAndRepository(): string
    {
        if ($this->downloadUrl === null || str_contains($this->downloadUrl, '/' . $this->name . '/')) {
            return $this->name;
        }

        if (! str_starts_with($this->downloadUrl, 'https://api.github.com/repos/')) {
            return $this->name;
        }

        try {
            $parsed = parse_url($this->downloadUrl);
        } catch (UrlException) {
            return $this->name;
        }

        if (! is_array($parsed) || ! array_key_exists('path', $parsed) || ! is_string($parsed['path'])) {
            return $this->name;
        }

        // Converts https://api.github.com/repos/<user>/<repository>/zipball/<sha>" to "<user>/<repository>"
        return implode('/', array_slice(explode('/', $parsed['path']), 2, 2));
    }

    /**
     * @param list<string>|null $input
     *
     * @return non-empty-list<OperatingSystemFamily>|null
     */
    private static function convertInputStringsToOperatingSystemFamilies(array|null $input): array|null
    {
        if ($input === null) {
            return null;
        }

        Assert::isNonEmptyList($input, 'Expected operating systems families to be a non-empty list.');

        return array_map(
            static function ($value): OperatingSystemFamily {
                Assert::inArray(
                    strtolower($value),
                    OperatingSystemFamily::asValuesList(),
                    'Expected operating system family to be one of: %2$s. Got: %s',
                );

                return OperatingSystemFamily::from(strtolower($value));
            },
            $input,
        );
    }

    public function composerPackage(): CompletePackageInterface
    {
        return $this->composerPackage;
    }

    public function extensionType(): ExtensionType
    {
        return $this->extensionType;
    }

    public function extensionName(): ExtensionName
    {
        return $this->extensionName;
    }

    public function isBundledPhpExtension(): bool
    {
        return str_starts_with($this->name(), 'php/');
    }

    public function name(): string
    {
        return $this->name;
    }

    public function version(): string
    {
        return $this->version;
    }

    /** @return list<ConfigureOption> */
    public function configureOptions(): array
    {
        return $this->configureOptions;
    }

    public function downloadUrl(): string|null
    {
        return $this->downloadUrl;
    }

    public function priority(): int
    {
        return $this->priority;
    }

    public function buildPath(): string|null
    {
        return $this->buildPath;
    }

    /** @return non-empty-list<OperatingSystemFamily>|null */
    public function compatibleOsFamilies(): array|null
    {
        return $this->compatibleOsFamilies;
    }

    /** @return non-empty-list<OperatingSystemFamily>|null */
    public function incompatibleOsFamilies(): array|null
    {
        return $this->incompatibleOsFamilies;
    }

    public function supportZts(): bool
    {
        return $this->supportZts;
    }

    public function supportNts(): bool
    {
        return $this->supportNts;
    }

    /** @return non-empty-list<DownloadUrlMethod>|null */
    public function supportedDownloadUrlMethods(): array|null
    {
        return $this->supportedDownloadUrlMethods;
    }

    public function installedJsonMetadata(): InstalledJsonMetadata
    {
        return $this->installedJsonMetadata;
    }

    public function verifyPackageStatus(TargetPlatform $targetPlatform): PackageVerificationStatus
    {
        $extensionPath    = $targetPlatform->phpBinaryPath->extensionPath();
        $extensionEnding  = $targetPlatform->operatingSystem === OperatingSystem::Windows ? '.dll' : '.so';
        $phpExtensionName = $this->extensionName->name();

        $actualBinaryPathByConvention = $extensionPath . DIRECTORY_SEPARATOR . $phpExtensionName . $extensionEnding;

        // The extension may not be in the usual path (since you can specify a full path to an extension in the INI file)
        if (! file_exists($actualBinaryPathByConvention)) {
            return PackageVerificationStatus::ActualBinaryNotFound;
        }

        $pieExpectedBinaryPath = $this->installedJsonMetadata()->installedBinary();
        $pieExpectedChecksum   = $this->installedJsonMetadata()->binaryChecksum();

        if ($pieExpectedBinaryPath === null) {
            return PackageVerificationStatus::InstalledBinaryMetadataMissing;
        }

        if ($pieExpectedChecksum === null) {
            return PackageVerificationStatus::ChecksumMetadataMissing;
        }

        if ($pieExpectedBinaryPath !== $actualBinaryPathByConvention) {
            return PackageVerificationStatus::InstalledBinaryPathDoesNotMatchActualBinaryPath;
        }

        $expectedBinaryFileFromMetadata = new BinaryFile($pieExpectedBinaryPath, $pieExpectedChecksum);
        $actualBinaryFile               = BinaryFile::fromFileWithSha256Checksum($actualBinaryPathByConvention);

        try {
            $expectedBinaryFileFromMetadata->verifyAgainstOther($actualBinaryFile);
        } catch (BinaryFileFailedVerification) {
            return PackageVerificationStatus::ChecksumMismatch;
        }

        return PackageVerificationStatus::Verified;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\DependencyResolver;

use RuntimeException;

use function sprintf;

class InvalidPackageName extends RuntimeException
{
    public function __construct(string $message, public readonly RequestedPackageAndVersion $requestedPackageAndVersion)
    {
        parent::__construct($message);
    }

    public static function fromMissingForwardSlash(RequestedPackageAndVersion $requestedPackageAndVersion): self
    {
        return new self(
            sprintf(
                'Requested package name "%s" is invalid; it should contain a forward slash, like "vendor/package".',
                $requestedPackageAndVersion->package,
            ),
            $requestedPackageAndVersion,
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\DependencyResolver;

use Php\Pie\Platform\OperatingSystemFamily;
use RuntimeException;

use function array_map;
use function implode;
use function sprintf;

class IncompatibleOperatingSystemFamily extends RuntimeException
{
    /** @param list<OperatingSystemFamily> $required */
    public static function notInCompatibleOperatingSystemFamilies(array $required, OperatingSystemFamily $current): self
    {
        return new self(sprintf(
            'This extension does not support the "%s" operating system family. It is compatible with the following families: "%s".',
            $current->value,
            implode('", "', array_map(static fn (OperatingSystemFamily $osFamily): string => $osFamily->value, $required)),
        ));
    }

    /** @param list<OperatingSystemFamily> $incompatibleOsFamilies */
    public static function inIncompatibleOperatingSystemFamily(array $incompatibleOsFamilies, OperatingSystemFamily $current): self
    {
        return new self(sprintf(
            'This extension does not support the "%s" operating system family. It is incompatible with the following families: "%s".',
            $current->value,
            implode('", "', array_map(static fn (OperatingSystemFamily $osFamily): string => $osFamily->value, $incompatibleOsFamilies)),
        ));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing;

use Composer\Util\Platform as ComposerPlatform;
use Php\Pie\ComposerIntegration\InstalledJsonMetadata;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\File\BinaryFile;
use Php\Pie\File\FailedToUnlinkFile;
use Php\Pie\File\Sudo;
use Php\Pie\File\SudoUnlink;
use Php\Pie\Platform\OperatingSystem;
use Php\Pie\Platform\TargetPlatform;
use Php\Pie\Util\Process;
use RuntimeException;

use function file_exists;
use function is_writable;
use function sprintf;

use const DIRECTORY_SEPARATOR;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class UninstallUsingUnlink implements Uninstall
{
    public function __invoke(TargetPlatform $targetPlatform, Package $package): BinaryFile
    {
        $pieMetadata           = $package->installedJsonMetadata();
        $pieExpectedBinaryPath = $pieMetadata->installedBinary();
        $pieExpectedChecksum   = $pieMetadata->binaryChecksum();

        if ($pieExpectedBinaryPath === null || $pieExpectedChecksum === null) {
            throw PackageMetadataMissing::duringUninstall(
                $package,
                $pieMetadata,
                [
                    InstalledJsonMetadata::KEY_INSTALLED_BINARY,
                    InstalledJsonMetadata::KEY_BINARY_CHECKSUM,
                ],
            );
        }

        $installRoot = (string) ComposerPlatform::getEnv('INSTALL_ROOT');

        // Sanity check the extension metadata points to the correct expected location
        $extensionPathByConvention = $targetPlatform->phpBinaryPath->extensionPath($installRoot)
            . DIRECTORY_SEPARATOR
            . ($targetPlatform->operatingSystem === OperatingSystem::Windows ? 'php_' : '')
            . $package->extensionName()->name()
            . ($targetPlatform->operatingSystem === OperatingSystem::Windows ? '.dll' : '.so');
        if ($extensionPathByConvention !== $pieExpectedBinaryPath) {
            throw new RuntimeException(sprintf(
                'Stored metadata path "%s" did not match expected path "%s"',
                $pieExpectedBinaryPath,
                $extensionPathByConvention,
            ));
        }

        $expectedBinaryFile = new BinaryFile($pieExpectedBinaryPath, $pieExpectedChecksum);
        $expectedBinaryFile->verify();

        // If the target directory isn't writable, or a .so file already exists and isn't writable, try to use sudo
        if (file_exists($expectedBinaryFile->filePath) && ! is_writable($expectedBinaryFile->filePath) && Sudo::exists()) {
            Process::run([Sudo::find(), 'rm', '--', $expectedBinaryFile->filePath], timeout: Process::SHORT_TIMEOUT);

            // Removal worked, bail out
            if (! file_exists($expectedBinaryFile->filePath)) {
                return $expectedBinaryFile;
            }
        }

        try {
            SudoUnlink::singleFile($expectedBinaryFile->filePath);
        } catch (FailedToUnlinkFile $failedToUnlinkFile) {
            throw FailedToRemoveExtension::withFilename($expectedBinaryFile, $failedToUnlinkFile);
        }

        return $expectedBinaryFile;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing;

use Composer\IO\IOInterface;
use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\File\BinaryFile;
use Php\Pie\Platform\TargetPlatform;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
interface Install
{
    /**
     * Install the extension in the given target platform's PHP, and return the location of the installed shared object
     * or DLL, depending on the platform implementation.
     */
    public function __invoke(
        DownloadedPackage $downloadedPackage,
        TargetPlatform $targetPlatform,
        BinaryFile|null $builtBinaryFile,
        IOInterface $io,
        bool $attemptToSetupIniFile,
    ): BinaryFile;
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing;

use Php\Pie\DependencyResolver\Package;
use Php\Pie\File\BinaryFile;
use Php\Pie\Platform\TargetPlatform;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
interface Uninstall
{
    public function __invoke(TargetPlatform $targetPlatform, Package $package): BinaryFile;
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing;

use Composer\IO\IOInterface;
use FilesystemIterator;
use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\File\BinaryFile;
use Php\Pie\File\WindowsDelete;
use Php\Pie\Platform\TargetPlatform;
use Php\Pie\Platform\WindowsExtensionAssetName;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use RuntimeException;
use SplFileInfo;

use function assert;
use function dirname;
use function file_exists;
use function is_file;
use function Safe\copy;
use function Safe\mkdir;
use function Safe\realpath;
use function sprintf;
use function str_contains;
use function str_replace;
use function str_starts_with;
use function strlen;
use function substr;

use const DIRECTORY_SEPARATOR;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class WindowsInstall implements Install
{
    public function __construct(private readonly SetupIniFile $setupIniFile)
    {
    }

    public function __invoke(
        DownloadedPackage $downloadedPackage,
        TargetPlatform $targetPlatform,
        BinaryFile|null $builtBinaryFile,
        IOInterface $io,
        bool $attemptToSetupIniFile,
    ): BinaryFile {
        $extractedSourcePath = $downloadedPackage->extractedSourcePath;
        $sourceDllName       = WindowsExtensionAssetName::determineDllName($targetPlatform, $downloadedPackage);
        $sourcePdbName       = str_replace('.dll', '.pdb', $sourceDllName);
        assert($sourcePdbName !== '');

        $destinationDllName = $this->copyExtensionDll($targetPlatform, $downloadedPackage, $sourceDllName);
        $io->write('<info>Copied DLL to:</info> ' . $destinationDllName);

        $destinationPdbName = $this->copyExtensionPdb($targetPlatform, $downloadedPackage, $sourcePdbName, $destinationDllName);
        if ($destinationPdbName !== null) {
            $io->write('<info>Copied PDB to:</info> ' . $destinationPdbName);
        }

        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($extractedSourcePath, FilesystemIterator::SKIP_DOTS),
        );

        foreach ($iterator as $file) {
            assert($file instanceof SplFileInfo);

            /**
             * Skip directories, the main DLL, PDB, and any symlinks the archive
             * may have shipped (symlink-followed targets fall outside the source
             * dir's containment guarantees).
             */
            if (
                $file->isDir()
                || $file->isLink()
                || $this->normalisedPathsMatch($file->getPathname(), $sourceDllName)
                || $this->normalisedPathsMatch($file->getPathname(), $sourcePdbName)
            ) {
                continue;
            }

            $destinationExtraDll = $this->copyDependencyDll($targetPlatform, $file);
            if ($destinationExtraDll !== null) {
                $io->write('<info>Copied extra DLL:</info> ' . $destinationExtraDll);

                continue;
            }

            $destinationPathname = $this->copyExtraFile($targetPlatform, $downloadedPackage, $file);
            $io->write('<info>Copied extras:</info> ' . $destinationPathname);
        }

        $binaryFile = BinaryFile::fromFileWithSha256Checksum($destinationDllName);

        ($this->setupIniFile)(
            $targetPlatform,
            $downloadedPackage,
            $binaryFile,
            $io,
            $attemptToSetupIniFile,
        );

        return $binaryFile;
    }

    /**
     * Normalise both path parameters (i.e. replace `\` with `/`) and compare them. Useful if the two paths are built
     * differently with different/incorrect directory separators, e.g. "C:\path\to/thing" vs "C:\path\to\thing"
     */
    private function normalisedPathsMatch(string $first, string $second): bool
    {
        return str_replace('\\', '/', $first) === str_replace('\\', '/', $second);
    }

    /**
     * Copy the main PHP extension DLL into the extension path.
     *
     * @param non-empty-string $sourceDllName
     *
     * @return non-empty-string
     */
    private function copyExtensionDll(TargetPlatform $targetPlatform, DownloadedPackage $downloadedPackage, string $sourceDllName): string
    {
        $destinationDllName = $targetPlatform->phpBinaryPath->extensionPath() . DIRECTORY_SEPARATOR
            . 'php_' . $downloadedPackage->package->extensionName()->name() . '.dll';

        if (file_exists($destinationDllName)) {
            WindowsDelete::usingMoveToTemp($destinationDllName);
        }

        copy($sourceDllName, $destinationDllName);

        if (! file_exists($destinationDllName) && ! is_file($destinationDllName)) {
            throw new RuntimeException('Failed to install DLL to ' . $destinationDllName);
        }

        return $destinationDllName;
    }

    /**
     * Copy the PDB (Program Database, which is debugging information basically), into the same directory as the DLL,
     * if it exists.
     *
     * Returns `null` if the source PDB does not exist (and thus, does not need to be copied).
     *
     * @param non-empty-string $sourcePdbName
     * @param non-empty-string $destinationDllName
     *
     * @return non-empty-string|null
     */
    private function copyExtensionPdb(TargetPlatform $targetPlatform, DownloadedPackage $downloadedPackage, string $sourcePdbName, string $destinationDllName): string|null
    {
        if (! file_exists($sourcePdbName)) {
            return null;
        }

        $destinationPdbName = str_replace('.dll', '.pdb', $destinationDllName);
        assert($destinationPdbName !== '');

        copy($sourcePdbName, $destinationPdbName);

        if (! file_exists($destinationPdbName) && ! is_file($destinationPdbName)) {
            throw new RuntimeException('Failed to install PDB to ' . $destinationPdbName);
        }

        return $destinationPdbName;
    }

    /**
     * Any other DLL file included in the source package should be copied into the same path where `php.exe` is - these
     * would commonly be dependencies/libraries that the extension depends on, and is bundled with.
     *
     * If the file is NOT a DLL, this method will return `null`
     *
     * @return non-empty-string|null
     */
    private function copyDependencyDll(TargetPlatform $targetPlatform, SplFileInfo $file): string|null
    {
        if ($file->getExtension() !== 'dll') {
            return null;
        }

        $destinationExtraDll = dirname($targetPlatform->phpBinaryPath->phpBinaryPath) . DIRECTORY_SEPARATOR . $file->getFilename();

        copy($file->getPathname(), $destinationExtraDll);

        if (! file_exists($destinationExtraDll) && ! is_file($destinationExtraDll)) {
            throw new RuntimeException('Failed to copy to ' . $destinationExtraDll);
        }

        return $destinationExtraDll;
    }

    /**
     * Any other remaining file should be copied into the "extras" path, e.g. `C:\php\extras\my-php-ext\.`
     *
     * @return non-empty-string
     */
    private function copyExtraFile(TargetPlatform $targetPlatform, DownloadedPackage $downloadedPackage, SplFileInfo $file): string
    {
        $extrasRoot = dirname($targetPlatform->phpBinaryPath->phpBinaryPath) . DIRECTORY_SEPARATOR
            . 'extras' . DIRECTORY_SEPARATOR
            . $downloadedPackage->package->extensionName()->name();

        $relativeName = substr($file->getPathname(), strlen($downloadedPackage->extractedSourcePath) + 1);

        if (str_contains($relativeName, '..' . DIRECTORY_SEPARATOR) || str_starts_with($relativeName, '..')) {
            throw new RuntimeException(sprintf(
                'Refusing to copy extra file with traversal segment: %s',
                $relativeName,
            ));
        }

        $destinationFullFilename = $extrasRoot . DIRECTORY_SEPARATOR . $relativeName;

        $destinationPath = dirname($destinationFullFilename);

        if (! file_exists($destinationPath)) {
            mkdir($destinationPath, 0777, true);
        }

        $destinationReal = realpath($destinationPath);
        $extrasReal      = realpath($extrasRoot);

        if (! str_starts_with($destinationReal . DIRECTORY_SEPARATOR, $extrasReal . DIRECTORY_SEPARATOR)) {
            throw new RuntimeException(sprintf(
                'Refusing to copy extra file: destination %s escapes extras root %s',
                $destinationPath,
                $extrasRoot,
            ));
        }

        copy($file->getPathname(), $destinationFullFilename);
        if (! file_exists($destinationFullFilename) && ! is_file($destinationFullFilename)) {
            throw new RuntimeException('Failed to copy to ' . $destinationFullFilename);
        }

        return $destinationFullFilename;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing;

use Php\Pie\File\BinaryFile;
use RuntimeException;
use Throwable;

use function sprintf;

class FailedToRemoveExtension extends RuntimeException
{
    public static function withFilename(BinaryFile $extension, Throwable $previous): self
    {
        return new self(
            sprintf(
                'Failed to remove extension file: %s',
                $extension->filePath,
            ),
            previous: $previous,
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\InstallForPhpProject;

use Composer\IO\IOInterface;
use Composer\Package\RootPackageInterface;
use Php\Pie\Command\InvokeSubCommand;
use Php\Pie\ComposerIntegration\PieJsonEditor;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;

use function sprintf;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class InstallPiePackageFromPath
{
    public function __construct(private readonly InvokeSubCommand $invokeSubCommand)
    {
    }

    /** @param non-empty-string $piePackagePath */
    public function __invoke(
        Command $invokeContext,
        string $piePackagePath,
        RootPackageInterface $pieRootPackage,
        PieJsonEditor $pieJsonEditor,
        InputInterface $input,
        IOInterface $io,
    ): int {
        $io->write(sprintf('Installing PIE extension from <info>%s</info>', $piePackagePath));
        $pieJsonEditor
            ->ensureExists()
            ->addRepository('path', $piePackagePath);

        try {
            return ($this->invokeSubCommand)(
                $invokeContext,
                [
                    'command' => 'install',
                    'requested-package-and-version' => $pieRootPackage->getName() . ':*@dev',
                ],
                $input,
            );
        } finally {
            $io->write(
                sprintf(
                    'Removing temporary path repository: %s',
                    $piePackagePath,
                ),
                verbosity: IOInterface::VERBOSE,
            );
            $pieJsonEditor->removeRepository($piePackagePath);
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\InstallForPhpProject;

use Composer\Composer;
use Composer\Factory as ComposerFactory;
use Composer\IO\IOInterface;
use Composer\Package\RootPackageInterface;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class ComposerFactoryForProject
{
    private Composer|null $memoizedComposer = null;

    public function composer(IOInterface $io): Composer
    {
        if ($this->memoizedComposer === null) {
            $this->memoizedComposer = ComposerFactory::create($io);
        }

        return $this->memoizedComposer;
    }

    public function rootPackage(IOInterface $io): RootPackageInterface
    {
        return $this->composer($io)->getPackage();
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\InstallForPhpProject;

use Composer\Composer;
use Composer\Package\CompletePackageInterface;
use Composer\Repository\RepositoryInterface;
use OutOfRangeException;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\ExtensionName;
use Php\Pie\ExtensionType;

use function array_filter;
use function array_key_exists;
use function array_merge;
use function array_values;
use function count;
use function usort;

/**
 * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
 *
 * @phpstan-type MatchingPackages = list<array{name: string, description: ?string, ...}>
 */
class FindMatchingPackages
{
    /**
     * @param list<array{name: string, description: ?string, downloads?: int}> $matches
     * @param non-empty-string                                                 $searchTerm
     *
     * @return MatchingPackages
     */
    private function filterToCompatible(Composer $pieComposer, array $matches, string $searchTerm): array
    {
        if (ExtensionName::isValidExtensionName($searchTerm)) {
            $extensionName = ExtensionName::normaliseFromString($searchTerm);

            $matches = array_filter(
                $matches,
                static function (array $match) use ($pieComposer, $extensionName): bool {
                    $package = $pieComposer->getRepositoryManager()->findPackage($match['name'], '*');

                    if (! $package instanceof CompletePackageInterface || ! ExtensionType::isValid($package->getType())) {
                        return false;
                    }

                    return Package::fromComposerCompletePackage($package)->extensionName()->name() === $extensionName->name();
                },
            );
        }

        if (! count($matches)) {
            throw new OutOfRangeException('No matches found for ' . $searchTerm);
        }

        usort($matches, static function (array $a, array $b): int {
            return (array_key_exists('downloads', $b) ? $b['downloads'] : 0)
                <=> (array_key_exists('downloads', $a) ? $a['downloads'] : 0);
        });

        return $matches;
    }

    /** @return MatchingPackages */
    public function byProvider(Composer $pieComposer, ExtensionName $extensionName): array
    {
        $matches = [];
        foreach ($pieComposer->getRepositoryManager()->getRepositories() as $repo) {
            $matches = array_merge($matches, $repo->getProviders($extensionName->nameWithExtPrefix()));
        }

        return $this->filterToCompatible($pieComposer, array_values($matches), $extensionName->name());
    }

    /**
     * @param non-empty-string $searchTerm
     *
     * @return MatchingPackages
     */
    public function bySearching(Composer $pieComposer, string $searchTerm): array
    {
        $matches = [];
        foreach ($pieComposer->getRepositoryManager()->getRepositories() as $repo) {
            $matches = array_merge($matches, $repo->search($searchTerm, RepositoryInterface::SEARCH_FULLTEXT, 'php-ext'));
            $matches = array_merge($matches, $repo->search($searchTerm, RepositoryInterface::SEARCH_FULLTEXT, 'php-ext-zend'));
        }

        return $this->filterToCompatible($pieComposer, $matches, $searchTerm);
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\InstallForPhpProject;

use Composer\Composer;
use Composer\Package\Link;
use Composer\Repository\InstalledRepository;
use Composer\Repository\RootPackageRepository;
use Php\Pie\ExtensionName;

use function array_filter;
use function in_array;
use function ksort;
use function str_starts_with;
use function strlen;
use function substr;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class DetermineExtensionsRequired
{
    public static function linkFilter(Link $link): bool
    {
        $linkTarget = $link->getTarget();
        if (! str_starts_with($linkTarget, 'ext-')) {
            return false;
        }

        return ExtensionName::isValidExtensionName(substr($linkTarget, strlen('ext-')));
    }

    /** @return array<string, Link> */
    public function forProject(Composer $composer): array
    {
        $requires          = [];
        $removeDevPackages = [];

        /** {@see \Composer\Command\CheckPlatformReqsCommand::execute} */
        $installedRepo = $composer->getRepositoryManager()->getLocalRepository();
        if (! $installedRepo->getPackages()) {
            $installedRepo = $composer->getLocker()->getLockedRepository();
        } else {
            $removeDevPackages = $installedRepo->getDevPackageNames();
        }

        foreach (array_filter($composer->getPackage()->getDevRequires(), [self::class, 'linkFilter']) as $require => $link) {
            $requires[$require] = $link;
        }

        $installedRepo = new InstalledRepository([$installedRepo, new RootPackageRepository(clone $composer->getPackage())]);

        foreach ($installedRepo->getPackages() as $package) {
            if (in_array($package->getName(), $removeDevPackages, true)) {
                continue;
            }

            foreach (array_filter($package->getRequires(), [self::class, 'linkFilter']) as $require => $link) {
                $requires[$require] = $link;
            }
        }

        ksort($requires);

        return $requires;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\InstallForPhpProject;

use Php\Pie\Command\InvokeSubCommand;
use Php\Pie\DependencyResolver\RequestedPackageAndVersion;
use Php\Pie\ExtensionName;
use Php\Pie\Util\OutputFormatterWithPrefix;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class InstallSelectedPackage
{
    public function __construct(
        private readonly InvokeSubCommand $invokeSubCommand,
    ) {
    }

    public function withSubCommand(
        ExtensionName $ext,
        RequestedPackageAndVersion $selectedPackage,
        Command $command,
        InputInterface $input,
    ): int {
        $params = [
            'command' => 'install',
            'requested-package-and-version' => $selectedPackage->prettyNameAndVersion(),
        ];

        return ($this->invokeSubCommand)(
            $command,
            $params,
            $input,
            OutputFormatterWithPrefix::newWithPrefix('  ' . $ext->name() . '> '),
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing;

use Composer\IO\IOInterface;
use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\ExtensionType;
use Php\Pie\File\BinaryFile;
use Php\Pie\Installing\Ini\SetupIniApproach;
use Php\Pie\Platform\TargetPlatform;
use Php\Pie\Util\Emoji;

use function sprintf;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class SetupIniFile
{
    public function __construct(private readonly SetupIniApproach $setupIniApproach)
    {
    }

    public function __invoke(
        TargetPlatform $targetPlatform,
        DownloadedPackage $downloadedPackage,
        BinaryFile $binaryFile,
        IOInterface $io,
        bool $attemptToSetupIniFile,
    ): void {
        if (
            $attemptToSetupIniFile
            && $this->setupIniApproach->canBeUsed($targetPlatform)
            && $this->setupIniApproach->setup($targetPlatform, $downloadedPackage, $binaryFile, $io)
        ) {
            $io->write(sprintf(
                '<info>%s Extension is enabled and loaded in</info> %s',
                Emoji::GREEN_CHECKMARK,
                $targetPlatform->phpBinaryPath->phpBinaryPath,
            ));
        } else {
            if (! $attemptToSetupIniFile) {
                $io->write('Automatic extension enabling was skipped.', verbosity: IOInterface::VERY_VERBOSE);
            }

            $io->write(sprintf('<comment>%s Extension has NOT been automatically enabled.</comment>', Emoji::WARNING));
            $io->write(sprintf(
                '<comment>You must now add "%s=%s" to your php.ini</comment>',
                $downloadedPackage->package->extensionType() === ExtensionType::PhpModule ? 'extension' : 'zend_extension',
                $downloadedPackage->package->extensionName()->name(),
            ));
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing;

use Php\Pie\ComposerIntegration\InstalledJsonMetadata;
use Php\Pie\DependencyResolver\Package;
use RuntimeException;

use function array_diff;
use function array_keys;
use function count;
use function implode;
use function sprintf;

class PackageMetadataMissing extends RuntimeException
{
    /** @param list<string> $wantedKeys */
    public static function duringUninstall(Package $package, InstalledJsonMetadata $actualMetadata, array $wantedKeys): self
    {
        $missingKeys = array_diff($wantedKeys, array_keys($actualMetadata->all()));

        return new self(sprintf(
            'PIE metadata was missing for package %s. Missing metadata key%s: %s',
            $package->name(),
            count($missingKeys) === 1 ? '' : 's',
            implode(', ', $missingKeys),
        ));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\Ini;

use Composer\IO\IOInterface;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\ExtensionType;
use Php\Pie\File\FailedToWriteFile;
use Php\Pie\File\SudoFilePut;
use Php\Pie\Platform\TargetPlatform;
use Webmozart\Assert\Assert;

use function array_filter;
use function array_map;
use function array_merge;
use function array_walk;
use function file_exists;
use function in_array;
use function is_dir;
use function preg_quote;
use function Safe\file_get_contents;
use function Safe\preg_replace;
use function Safe\scandir;
use function sprintf;

use const DIRECTORY_SEPARATOR;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class RemoveIniEntryWithFileGetContents implements RemoveIniEntry
{
    /** @return list<string> Returns a list of INI files that were updated to remove the extension */
    public function __invoke(Package $package, TargetPlatform $targetPlatform, IOInterface $io): array
    {
        $allIniFiles = [];

        $mainIni = $targetPlatform->phpBinaryPath->loadedIniConfigurationFile();
        if ($mainIni !== null) {
            $allIniFiles[] = $mainIni;
        }

        $additionalIniDirectory = $targetPlatform->phpBinaryPath->additionalIniDirectory();
        if ($additionalIniDirectory !== null && file_exists($additionalIniDirectory) && is_dir($additionalIniDirectory)) {
            $filenames = scandir($additionalIniDirectory);
            Assert::isList($filenames);
            Assert::allString($filenames);
            $allIniFiles = array_merge(
                array_map(
                    static function (string $path) use ($additionalIniDirectory): string {
                        return $additionalIniDirectory . DIRECTORY_SEPARATOR . $path;
                    },
                    array_filter(
                        $filenames,
                        static function (string $path) use ($additionalIniDirectory): bool {
                            if (in_array($path, ['.', '..'])) {
                                return false;
                            }

                            return file_exists($additionalIniDirectory . DIRECTORY_SEPARATOR . $path);
                        },
                    ),
                ),
                $allIniFiles,
            );
        }

        // Make sure all symlinks are resolved
        $allIniFiles = array_filter(array_map('realpath', $allIniFiles));

        // Anchor on the right with \b so uninstalling `foo` doesn't also
        // rewrite the prefix of `extension=foo_other`. preg_quote on the
        // extension name is defence-in-depth in case future ExtensionName
        // validation ever loosens past `^[A-Za-z][a-zA-Z0-9_]+$`.
        $regex = sprintf(
            '/^(%s\s*=\s*%s)\b/m',
            $package->extensionType() === ExtensionType::PhpModule ? 'extension' : 'zend_extension',
            preg_quote($package->extensionName()->name(), '/'),
        );

        $updatedIniFiles = [];
        array_walk(
            $allIniFiles,
            static function (string $iniFile) use (&$updatedIniFiles, $regex, $package, $io): void {
                $currentContent = file_get_contents($iniFile);

                if ($currentContent === '') {
                    return;
                }

                $replacedContent = preg_replace(
                    $regex,
                    '; $1 ; removed by PIE',
                    $currentContent,
                );

                if ($replacedContent === $currentContent) {
                    return;
                }

                try {
                    SudoFilePut::contents($iniFile, $replacedContent);
                } catch (FailedToWriteFile) {
                    $io->writeError(sprintf(
                        '<error>Failed to remove extension "%s" from INI file "%s"</error>',
                        $package->extensionName()->name(),
                        $iniFile,
                    ));

                    return;
                }

                $updatedIniFiles[] = $iniFile;
            },
        );

        return $updatedIniFiles;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\Ini;

use Composer\IO\IOInterface;
use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\File\BinaryFile;
use Php\Pie\File\Sudo;
use Php\Pie\File\SudoCreate;
use Php\Pie\File\SudoUnlink;
use Php\Pie\Platform\TargetPlatform;

use function file_exists;
use function is_writable;
use function rtrim;
use function sprintf;

use const DIRECTORY_SEPARATOR;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class StandardAdditionalPhpIniDirectory implements SetupIniApproach
{
    public function __construct(
        private readonly CheckAndAddExtensionToIniIfNeeded $checkAndAddExtensionToIniIfNeeded,
    ) {
    }

    public function canBeUsed(TargetPlatform $targetPlatform): bool
    {
        return $targetPlatform->phpBinaryPath->additionalIniDirectory() !== null;
    }

    public function setup(
        TargetPlatform $targetPlatform,
        DownloadedPackage $downloadedPackage,
        BinaryFile $binaryFile,
        IOInterface $io,
    ): bool {
        $additionalIniFilesPath = $targetPlatform->phpBinaryPath->additionalIniDirectory();

        /** In practice, this shouldn't happen since {@see canBeUsed()} checks this */
        if ($additionalIniFilesPath === null) {
            return false;
        }

        if (! file_exists($additionalIniFilesPath)) {
            $io->write(
                sprintf(
                    'PHP is configured to use additional INI file path %s, but it did not exist.',
                    $additionalIniFilesPath,
                ),
                verbosity: IOInterface::VERBOSE,
            );

            return false;
        }

        if (! is_writable($additionalIniFilesPath) && ! Sudo::exists()) {
            $io->write(
                sprintf(
                    'PHP is configured to use additional INI file path %s, but it was not writable by PIE.',
                    $additionalIniFilesPath,
                ),
                verbosity: IOInterface::VERBOSE,
            );

            return false;
        }

        $expectedIniFile = sprintf(
            '%s%s%d-%s.ini',
            rtrim($additionalIniFilesPath, DIRECTORY_SEPARATOR),
            DIRECTORY_SEPARATOR,
            $downloadedPackage->package->priority(),
            $downloadedPackage->package->extensionName()->name(),
        );

        $pieCreatedTheIniFile = false;
        if (! file_exists($expectedIniFile)) {
            $io->write(
                sprintf(
                    'Creating new INI file based on extension priority: %s',
                    $expectedIniFile,
                ),
                verbosity: IOInterface::VERY_VERBOSE,
            );
            $pieCreatedTheIniFile = true;
            SudoCreate::file($expectedIniFile);
        }

        $addingExtensionWasSuccessful = ($this->checkAndAddExtensionToIniIfNeeded)(
            $expectedIniFile,
            $targetPlatform,
            $downloadedPackage,
            $io,
            null,
        );

        if (! $addingExtensionWasSuccessful && $pieCreatedTheIniFile) {
            SudoUnlink::singleFile($expectedIniFile);
        }

        return $addingExtensionWasSuccessful;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\Ini;

use Composer\IO\IOInterface;
use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\File\BinaryFile;
use Php\Pie\Platform\TargetPhp\Exception\ExtensionIsNotLoaded;
use Php\Pie\Platform\TargetPlatform;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class PreCheckExtensionAlreadyLoaded implements SetupIniApproach
{
    public function canBeUsed(TargetPlatform $targetPlatform): bool
    {
        return true;
    }

    public function setup(
        TargetPlatform $targetPlatform,
        DownloadedPackage $downloadedPackage,
        BinaryFile $binaryFile,
        IOInterface $io,
    ): bool {
        try {
            $targetPlatform->phpBinaryPath->assertExtensionIsLoadedInRuntime(
                $downloadedPackage->package->extensionName(),
                $io,
            );

            return true;
        } catch (ExtensionIsNotLoaded) {
            return false;
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\Ini;

use Composer\IO\IOInterface;
use Composer\Util\Platform;
use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\File\BinaryFile;
use Php\Pie\File\Sudo;
use Php\Pie\File\SudoCreate;
use Php\Pie\File\SudoUnlink;
use Php\Pie\Platform\TargetPlatform;
use Php\Pie\Util\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;

use function array_unshift;
use function file_exists;
use function is_dir;
use function is_writable;
use function rtrim;
use function Safe\preg_match;
use function sprintf;

use const DIRECTORY_SEPARATOR;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class OndrejPhpenmod implements SetupIniApproach
{
    private const DEFAULT_PHPENMOD            = 'phpenmod';
    private const DEFAULT_MODS_AVAILABLE_PATH = '/etc/php/%s/mods-available';

    public function __construct(
        private readonly CheckAndAddExtensionToIniIfNeeded $checkAndAddExtensionToIniIfNeeded,
        private readonly string $phpenmod = self::DEFAULT_PHPENMOD,
        private readonly string $modsAvailablePath = self::DEFAULT_MODS_AVAILABLE_PATH,
    ) {
    }

    public function canBeUsed(TargetPlatform $targetPlatform): bool
    {
        return $this->phpenmodPath() !== null;
    }

    public function setup(
        TargetPlatform $targetPlatform,
        DownloadedPackage $downloadedPackage,
        BinaryFile $binaryFile,
        IOInterface $io,
    ): bool {
        $phpenmodPath = $this->phpenmodPath();

        /** In practice, this shouldn't happen since {@see canBeUsed()} checks this */
        if ($phpenmodPath === null) {
            return false;
        }

        // the Ondrej repo uses an additional php.ini directory, if this isn't set, we may not actually be using Ondrej repo for this particular PHP install
        $additionalPhpIniPath = $targetPlatform->phpBinaryPath->additionalIniDirectory();

        if ($additionalPhpIniPath === null) {
            $io->write(
                'Additional INI file path was not set - may not be Ondrej PHP repo',
                verbosity: IOInterface::VERBOSE,
            );

            return false;
        }

        // Cursory check for the expected PHP INI directory; this is another indication we're using the Ondrej repo
        if (preg_match('#/etc/php/\d\.\d/[a-z-_]+/conf.d#', $additionalPhpIniPath) !== 1) {
            $io->write(
                sprintf(
                    'Warning: additional INI file path was not in the expected format (/etc/php/{version}/{sapi}/conf.d). Path was: %s',
                    $additionalPhpIniPath,
                ),
                verbosity: IOInterface::VERY_VERBOSE,
            );
        }

        $expectedModsAvailablePath = sprintf($this->modsAvailablePath, $targetPlatform->phpBinaryPath->majorMinorVersion());

        if (! file_exists($expectedModsAvailablePath)) {
            $io->write(
                sprintf(
                    'Mods available path %s does not exist',
                    $expectedModsAvailablePath,
                ),
                verbosity: IOInterface::VERBOSE,
            );

            return false;
        }

        if (! is_dir($expectedModsAvailablePath)) {
            $io->write(
                sprintf(
                    'Mods available path %s is not a directory',
                    $expectedModsAvailablePath,
                ),
                verbosity: IOInterface::VERBOSE,
            );

            return false;
        }

        $needSudo = false;
        if (! is_writable($expectedModsAvailablePath)) {
            if (! Sudo::exists()) {
                $io->write(
                    sprintf(
                        'Mods available path %s is not writable',
                        $expectedModsAvailablePath,
                    ),
                    verbosity: IOInterface::VERBOSE,
                );

                return false;
            }

            $needSudo = true;
        }

        $expectedIniFile = sprintf(
            '%s%s%s.ini',
            rtrim($expectedModsAvailablePath, DIRECTORY_SEPARATOR),
            DIRECTORY_SEPARATOR,
            $downloadedPackage->package->extensionName()->name(),
        );

        $pieCreatedTheIniFile = false;
        if (! file_exists($expectedIniFile)) {
            $io->write(
                sprintf(
                    'Creating new INI file based on extension priority: %s',
                    $expectedIniFile,
                ),
                verbosity: IOInterface::VERY_VERBOSE,
            );
            $pieCreatedTheIniFile = true;
            SudoCreate::file($expectedIniFile);
        }

        $addingExtensionWasSuccessful = ($this->checkAndAddExtensionToIniIfNeeded)(
            $expectedIniFile,
            $targetPlatform,
            $downloadedPackage,
            $io,
            static function () use ($needSudo, $phpenmodPath, $targetPlatform, $downloadedPackage, $io): bool {
                try {
                    $processArgs = [
                        $phpenmodPath,
                        '-v',
                        $targetPlatform->phpBinaryPath->majorMinorVersion(),
                        '-s',
                        'ALL',
                        $downloadedPackage->package->extensionName()->name(),
                    ];

                    if ($needSudo && Sudo::exists()) {
                        $io->write(
                            'Using sudo to elevate privileges for phpenmod',
                            verbosity: IOInterface::VERBOSE,
                        );
                        array_unshift($processArgs, Sudo::find());
                    }

                    Process::run($processArgs, timeout: Process::SHORT_TIMEOUT);

                    return true;
                } catch (ProcessFailedException $processFailedException) {
                    $io->writeError(
                        sprintf(
                            'Failed to use %s to enable %s for PHP %s: %s',
                            $phpenmodPath,
                            $downloadedPackage->package->extensionName()->name(),
                            $targetPlatform->phpBinaryPath->majorMinorVersion(),
                            $processFailedException->getMessage(),
                        ),
                        verbosity: IOInterface::VERBOSE,
                    );

                    return false;
                }
            },
        );

        if (! $addingExtensionWasSuccessful && $pieCreatedTheIniFile) {
            SudoUnlink::singleFile($expectedIniFile);
        }

        return $addingExtensionWasSuccessful;
    }

    /** @return non-empty-string|null */
    private function phpenmodPath(): string|null
    {
        if (Platform::isWindows()) {
            return null;
        }

        try {
            $phpenmodPath = Process::run(['which', $this->phpenmod]);

            return $phpenmodPath !== '' ? $phpenmodPath : null;
        } catch (ProcessFailedException) {
            return null;
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\Ini;

use Composer\IO\IOInterface;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\ExtensionType;
use Php\Pie\File\Sudo;
use Php\Pie\File\SudoFilePut;
use Php\Pie\Platform\TargetPhp\PhpBinaryPath;
use Safe\Exceptions\FilesystemException;
use Throwable;

use function is_readable;
use function is_writable;
use function Safe\file_get_contents;
use function sprintf;

use const PHP_EOL;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class AddExtensionToTheIniFile
{
    /** @param callable():bool|null $additionalEnableStep */
    public function __invoke(
        string $ini,
        Package $package,
        PhpBinaryPath $phpBinaryPath,
        IOInterface $io,
        callable|null $additionalEnableStep,
    ): bool {
        if (! is_writable($ini) && ! Sudo::exists()) {
            $io->write(
                sprintf(
                    'PHP is configured to use %s, but it is not writable by PIE.',
                    $ini,
                ),
                verbosity: IOInterface::VERBOSE,
            );

            return false;
        }

        if (! is_readable($ini)) {
            $io->write(
                sprintf(
                    'Could not read %s to make a backup of it, aborting enablement of extension',
                    $ini,
                ),
                verbosity: IOInterface::VERBOSE,
            );

            return false;
        }

        try {
            $originalIniContent = file_get_contents($ini);
        } catch (FilesystemException $e) {
            $io->write(
                sprintf(
                    'Tried making a backup of %s but could not read it, aborting enablement of extension: %s',
                    $ini,
                    $e->getMessage(),
                ),
                verbosity: IOInterface::VERBOSE,
            );

            return false;
        }

        try {
            SudoFilePut::contents(
                $ini,
                $originalIniContent . $this->iniFileContent($package),
            );
            $io->write(
                sprintf(
                    'Enabled extension %s in the INI file %s',
                    $package->extensionName()->name(),
                    $ini,
                ),
                verbosity: IOInterface::VERBOSE,
            );

            if ($additionalEnableStep !== null && ! $additionalEnableStep()) {
                return false;
            }

            $phpBinaryPath->assertExtensionIsLoadedInRuntime($package->extensionName(), $io);

            return true;
        } catch (Throwable $anything) {
            SudoFilePut::contents($ini, $originalIniContent);

            $io->writeError(sprintf(
                '<error>Something went wrong enabling the %s extension: %s</error>',
                $package->extensionName()->name(),
                $anything->getMessage(),
            ));

            return false;
        }
    }

    /** @return non-empty-string */
    private function iniFileContent(Package $package): string
    {
        return PHP_EOL
            . '; PIE automatically added this to enable the ' . $package->name() . ' extension' . PHP_EOL
            . '; priority=' . $package->priority() . PHP_EOL
            . ($package->extensionType() === ExtensionType::PhpModule ? 'extension' : 'zend_extension')
            . '='
            . $package->extensionName()->name() . PHP_EOL;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\Ini;

use Composer\IO\IOInterface;
use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\File\BinaryFile;
use Php\Pie\Platform\TargetPhp\Exception\ExtensionIsNotLoaded;
use Php\Pie\Platform\TargetPlatform;
use Php\Pie\Util\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;

use function sprintf;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class DockerPhpExtEnable implements SetupIniApproach
{
    private const DOCKER_PHP_EXT_ENABLE = 'docker-php-ext-enable';

    public function __construct(private readonly string $dockerPhpExtEnableName = self::DOCKER_PHP_EXT_ENABLE)
    {
    }

    public function canBeUsed(TargetPlatform $targetPlatform): bool
    {
        return $this->dockerPhpExtEnablePath() !== null;
    }

    public function setup(
        TargetPlatform $targetPlatform,
        DownloadedPackage $downloadedPackage,
        BinaryFile $binaryFile,
        IOInterface $io,
    ): bool {
        $dockerPhpExtEnable = $this->dockerPhpExtEnablePath();

        if ($dockerPhpExtEnable === null) {
            return false;
        }

        try {
            $enableOutput = Process::run([$dockerPhpExtEnable, $downloadedPackage->package->extensionName()->name()]);
        } catch (ProcessFailedException $processFailed) {
            $io->writeError(
                sprintf(
                    'Could not enable extension %s using %s. Exception was: %s',
                    $downloadedPackage->package->extensionName()->name(),
                    $this->dockerPhpExtEnableName,
                    $processFailed->getMessage(),
                ),
                verbosity: IOInterface::VERBOSE,
            );

            return false;
        }

        try {
            $targetPlatform->phpBinaryPath->assertExtensionIsLoadedInRuntime(
                $downloadedPackage->package->extensionName(),
                $io,
            );

            return true;
        } catch (ExtensionIsNotLoaded) {
            $io->writeError(
                sprintf(
                    'Asserting that extension %s was enabled using %s failed. Output was: %s',
                    $downloadedPackage->package->extensionName()->name(),
                    $this->dockerPhpExtEnableName,
                    $enableOutput !== '' ? $enableOutput : '(empty)',
                ),
                verbosity: IOInterface::VERBOSE,
            );

            return false;
        }
    }

    private function dockerPhpExtEnablePath(): string|null
    {
        try {
            return Process::run(['which', $this->dockerPhpExtEnableName]);
        } catch (ProcessFailedException) {
            return null;
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\Ini;

use Composer\IO\IOInterface;
use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\File\BinaryFile;
use Php\Pie\Platform\TargetPlatform;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class StandardSinglePhpIni implements SetupIniApproach
{
    public function __construct(
        private readonly CheckAndAddExtensionToIniIfNeeded $checkAndAddExtensionToIniIfNeeded,
    ) {
    }

    public function canBeUsed(TargetPlatform $targetPlatform): bool
    {
        return $targetPlatform->phpBinaryPath->loadedIniConfigurationFile() !== null;
    }

    public function setup(
        TargetPlatform $targetPlatform,
        DownloadedPackage $downloadedPackage,
        BinaryFile $binaryFile,
        IOInterface $io,
    ): bool {
        $ini = $targetPlatform->phpBinaryPath->loadedIniConfigurationFile();

        /** In practice, this shouldn't happen since {@see canBeUsed()} checks this */
        if ($ini === null) {
            return false;
        }

        return ($this->checkAndAddExtensionToIniIfNeeded)(
            $ini,
            $targetPlatform,
            $downloadedPackage,
            $io,
            null,
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\Ini;

use Composer\IO\IOInterface;
use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\File\BinaryFile;
use Php\Pie\Platform\TargetPlatform;
use ReflectionClass;

use function array_filter;
use function array_values;
use function count;
use function sprintf;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class PickBestSetupIniApproach implements SetupIniApproach
{
    /** @var list<SetupIniApproach>|null */
    private array|null $memoizedApproachesThatCanBeUsed = null;

    /** @param list<SetupIniApproach> $possibleApproaches */
    public function __construct(
        private readonly array $possibleApproaches,
    ) {
    }

    /** @return list<SetupIniApproach> */
    private function approachesThatCanBeUsed(TargetPlatform $targetPlatform): array
    {
        if ($this->memoizedApproachesThatCanBeUsed === null) {
            $this->memoizedApproachesThatCanBeUsed = array_values(array_filter(
                $this->possibleApproaches,
                static fn (SetupIniApproach $approach) => $approach->canBeUsed($targetPlatform),
            ));
        }

        return $this->memoizedApproachesThatCanBeUsed;
    }

    public function canBeUsed(TargetPlatform $targetPlatform): bool
    {
        return count($this->approachesThatCanBeUsed($targetPlatform)) > 0;
    }

    public function setup(
        TargetPlatform $targetPlatform,
        DownloadedPackage $downloadedPackage,
        BinaryFile $binaryFile,
        IOInterface $io,
    ): bool {
        $approaches = $this->approachesThatCanBeUsed($targetPlatform);

        if (count($approaches) === 0) {
            $io->write(
                'No INI setup approaches can be used on this platform.',
                verbosity: IOInterface::VERBOSE,
            );

            return false;
        }

        foreach ($approaches as $approach) {
            $io->write(
                sprintf(
                    'Trying to enable extension using %s',
                    (new ReflectionClass($approach))->getShortName(),
                ),
                verbosity: IOInterface::VERBOSE,
            );
            if ($approach->setup($targetPlatform, $downloadedPackage, $binaryFile, $io)) {
                return true;
            }
        }

        $io->write(
            'None of the INI setup approaches succeeded.',
            verbosity: IOInterface::VERBOSE,
        );

        return false;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\Ini;

use Composer\IO\IOInterface;
use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\Platform\TargetPlatform;
use Throwable;

use function file_exists;
use function is_readable;
use function sprintf;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class CheckAndAddExtensionToIniIfNeeded
{
    public function __construct(
        private readonly IsExtensionAlreadyInTheIniFile $isExtensionAlreadyInTheIniFile,
        private readonly AddExtensionToTheIniFile $addExtensionToTheIniFile,
    ) {
    }

    /**
     * @param non-empty-string     $iniFile
     * @param callable():bool|null $additionalEnableStep
     */
    public function __invoke(
        string $iniFile,
        TargetPlatform $targetPlatform,
        DownloadedPackage $downloadedPackage,
        IOInterface $io,
        callable|null $additionalEnableStep,
    ): bool {
        if (! file_exists($iniFile) || ! is_readable($iniFile)) {
            $io->write(
                sprintf(
                    'PHP is configured to use %s, but it did not exist, or is not readable by PIE.',
                    $iniFile,
                ),
                verbosity: IOInterface::VERBOSE,
            );

            return false;
        }

        if (($this->isExtensionAlreadyInTheIniFile)($iniFile, $downloadedPackage->package->extensionName())) {
            $io->write(
                sprintf(
                    'Extension is already enabled in the INI file %s',
                    $iniFile,
                ),
                verbosity: IOInterface::VERBOSE,
            );

            if ($additionalEnableStep !== null && ! $additionalEnableStep()) {
                return false;
            }

            try {
                $targetPlatform->phpBinaryPath->assertExtensionIsLoadedInRuntime($downloadedPackage->package->extensionName(), $io);

                return true;
            } catch (Throwable $anything) {
                $io->writeError(sprintf(
                    '<error>Something went wrong verifying the %s extension is enabled: %s</error>',
                    $downloadedPackage->package->extensionName()->name(),
                    $anything->getMessage(),
                ));

                return false;
            }
        }

        return ($this->addExtensionToTheIniFile)(
            $iniFile,
            $downloadedPackage->package,
            $targetPlatform->phpBinaryPath,
            $io,
            $additionalEnableStep,
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\Ini;

use Composer\IO\IOInterface;
use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\File\BinaryFile;
use Php\Pie\Platform\TargetPlatform;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
interface SetupIniApproach
{
    /**
     * Preliminary indication on whether this approach can be used on the
     * target platform (returns `true`) or not (returns `false`).
     */
    public function canBeUsed(TargetPlatform $targetPlatform): bool;

    /**
     * Should return true if the INI approach successfully set up the extension,
     * or false otherwise.
     */
    public function setup(
        TargetPlatform $targetPlatform,
        DownloadedPackage $downloadedPackage,
        BinaryFile $binaryFile,
        IOInterface $io,
    ): bool;
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\Ini;

use Php\Pie\ExtensionName;
use Webmozart\Assert\Assert;

use function array_key_exists;
use function array_merge;
use function in_array;
use function is_string;
use function Safe\file;
use function Safe\parse_ini_string;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class IsExtensionAlreadyInTheIniFile
{
    public function __invoke(string $iniFilePath, ExtensionName $extensionName): bool
    {
        $loadedExts = $this->readIniFile($iniFilePath);

        return in_array($extensionName->name(), array_merge($loadedExts['extensions'], $loadedExts['zend_extensions']));
    }

    /** @return array{extensions: list<non-empty-string>, zend_extensions: list<non-empty-string>} */
    private function readIniFile(string $iniFilePath): array
    {
        $iniFileContentLines = file($iniFilePath);

        $extensions     = [];
        $zendExtensions = [];
        foreach ($iniFileContentLines as $line) {
            Assert::string($line);
            $lineIni = parse_ini_string($line);

            if (array_key_exists('extension', $lineIni) && is_string($lineIni['extension']) && $lineIni['extension'] !== '') {
                $extensions[] = $lineIni['extension'];
            }

            if (! array_key_exists('zend_extension', $lineIni) || ! is_string($lineIni['zend_extension']) || $lineIni['zend_extension'] === '') {
                continue;
            }

            $zendExtensions[] = $lineIni['zend_extension'];
        }

        return [
            'extensions' => $extensions,
            'zend_extensions' => $zendExtensions,
        ];
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\Ini;

use Composer\IO\IOInterface;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\Platform\TargetPlatform;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
interface RemoveIniEntry
{
    /** @return list<string> Returns a list of INI files that were updated to remove the extension */
    public function __invoke(Package $package, TargetPlatform $targetPlatform, IOInterface $io): array;
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing;

use Composer\IO\IOInterface;
use Composer\Util\Platform as ComposerPlatform;
use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\Downloading\DownloadUrlMethod;
use Php\Pie\File\BinaryFile;
use Php\Pie\File\Sudo;
use Php\Pie\Platform\TargetPlatform;
use Php\Pie\Util\Process;
use RuntimeException;
use Webmozart\Assert\Assert;

use function array_map;
use function array_merge;
use function file_exists;
use function implode;
use function is_writable;
use function sprintf;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class UnixInstall implements Install
{
    private const MAKE_INSTALL_TIMEOUT_SECS = 300; // 5 minutes

    public function __construct(private readonly SetupIniFile $setupIniFile)
    {
    }

    public function __invoke(
        DownloadedPackage $downloadedPackage,
        TargetPlatform $targetPlatform,
        BinaryFile|null $builtBinaryFile,
        IOInterface $io,
        bool $attemptToSetupIniFile,
    ): BinaryFile {
        $env         = [];
        $installRoot = (string) ComposerPlatform::getEnv('INSTALL_ROOT');
        if ($installRoot !== '') {
            $io->write(sprintf('<info>Using INSTALL_ROOT=%s</info>', $installRoot));
            $env['INSTALL_ROOT'] = $installRoot;
        }

        $targetExtensionPath = $targetPlatform->phpBinaryPath->extensionPath($installRoot);

        $sharedObjectName             = $downloadedPackage->package->extensionName()->name() . '.so';
        $expectedSharedObjectLocation = sprintf(
            '%s/%s',
            $targetExtensionPath,
            $sharedObjectName,
        );

        $installCommands = [];
        switch (DownloadUrlMethod::fromDownloadedPackage($downloadedPackage)) {
            case DownloadUrlMethod::PrePackagedBinary:
                Assert::notNull($builtBinaryFile);

                if (file_exists($expectedSharedObjectLocation)) {
                    $installCommands[] = [
                        'rm',
                        '-v',
                        $expectedSharedObjectLocation,
                    ];
                }

                $installCommands[] = [
                    'cp',
                    '-v',
                    $builtBinaryFile->filePath,
                    $targetExtensionPath,
                ];
                break;

            default:
                $installCommands[] = ['make', 'install'];
        }

        // If the target directory isn't writable, or a .so file already exists and isn't writable, try to use sudo
        if (
            (
                ! is_writable($targetExtensionPath)
                || (file_exists($expectedSharedObjectLocation) && ! is_writable($expectedSharedObjectLocation))
            )
            && Sudo::exists()
        ) {
            $io->write(sprintf(
                '<comment>Cannot write to %s, so using sudo to elevate privileges.</comment>',
                $targetExtensionPath,
            ));
            $installCommands = array_map(static fn (array $command) => array_merge(['sudo'], $command), $installCommands);
        }

        $io->write(sprintf('<info>Install commands are: %s</info>', implode(', ', array_map(static fn (array $command) => implode(' ', $command), $installCommands))), verbosity: IOInterface::VERY_VERBOSE);

        foreach ($installCommands as $installCommand) {
            $makeInstallOutput = Process::run(
                $installCommand,
                $downloadedPackage->extractedSourcePath,
                self::MAKE_INSTALL_TIMEOUT_SECS,
                env: $env,
            );

            $io->write($makeInstallOutput, verbosity: IOInterface::VERY_VERBOSE);
        }

        if (! file_exists($expectedSharedObjectLocation)) {
            throw new RuntimeException('Install failed, ' . $expectedSharedObjectLocation . ' was not installed.');
        }

        $io->write('<info>Install complete:</info> ' . $expectedSharedObjectLocation);

        $binaryFile = BinaryFile::fromFileWithSha256Checksum($expectedSharedObjectLocation);

        ($this->setupIniFile)(
            $targetPlatform,
            $downloadedPackage,
            $binaryFile,
            $io,
            $attemptToSetupIniFile,
        );

        return $binaryFile;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\File;

use RuntimeException;

use function array_key_exists;
use function is_string;
use function Safe\preg_match;
use function Safe\realpath;

use const DIRECTORY_SEPARATOR;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class FullPathToSelf
{
    public function __construct(private readonly string $originalCwd)
    {
    }

    /** @return non-empty-string */
    public function __invoke(): string
    {
        $phpSelf = array_key_exists('PHP_SELF', $_SERVER) && is_string($_SERVER['PHP_SELF']) ? $_SERVER['PHP_SELF'] : '';
        if ($phpSelf === '') {
            throw new RuntimeException('Could not find PHP_SELF');
        }

        return $this->isAbsolutePath($phpSelf)
            ? $phpSelf
            : ($this->originalCwd . DIRECTORY_SEPARATOR . $phpSelf);
    }

    private function isAbsolutePath(string $path): bool
    {
        if (realpath($path) === $path) {
            return true;
        }

        if ($path === '' || $path === '.') {
            return false;
        }

        if (preg_match('#^[a-zA-Z]:\\\\#', $path)) {
            return true;
        }

        return $path[0] === '/' || $path[0] === '\\';
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\File;

use Composer\Util\Platform;
use Php\Pie\Util\Process;
use Safe\Exceptions\FilesystemException;
use Symfony\Component\Process\Exception\ProcessFailedException;

use function file_exists;
use function is_writable;
use function Safe\unlink;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class SudoUnlink
{
    public static function singleFile(string $filename): void
    {
        if (! file_exists($filename)) {
            return;
        }

        if (! Platform::isWindows() && is_writable($filename)) {
            try {
                unlink($filename);
            } catch (FilesystemException $e) {
                throw FailedToUnlinkFile::fromUnlinkError($filename, $e);
            }

            return;
        }

        if (Platform::isWindows()) {
            WindowsDelete::usingMoveToTemp($filename);

            return;
        }

        if (! Sudo::exists()) {
            throw FailedToUnlinkFile::fromNoPermissions($filename);
        }

        try {
            Process::run([Sudo::find(), 'rm', '--', $filename], timeout: Process::SHORT_TIMEOUT);
        } catch (ProcessFailedException $processFailedException) {
            throw FailedToUnlinkFile::fromSudoRmProcessFailed($filename, $processFailedException);
        }

        if (! file_exists($filename)) {
            return;
        }

        throw FailedToUnlinkFile::fromNoPermissions($filename);
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\File;

use RuntimeException;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Throwable;

use function sprintf;

class FailedToCreateFile extends RuntimeException
{
    public static function fromTouchError(string $filename, Throwable $previous): self
    {
        return new self(
            sprintf(
                'Failed to create file %s: %s',
                $filename,
                $previous->getMessage(),
            ),
            previous: $previous,
        );
    }

    public static function fromNoPermissions(string $filename): self
    {
        return new self(sprintf(
            'Failed to create file %s as PIE does not have enough permissions',
            $filename,
        ));
    }

    public static function fromSudoTouchProcessFailed(string $filename, ProcessFailedException $processFailed): self
    {
        return new self(
            sprintf(
                'Failed to create file %s using sudo touch: %s',
                $filename,
                $processFailed->getMessage(),
            ),
            previous: $processFailed,
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\File;

use Php\Pie\Platform;
use Php\Pie\Util\Process;
use Safe\Exceptions\FilesystemException;
use Symfony\Component\Process\Exception\ProcessFailedException;

use function dirname;
use function file_exists;
use function is_dir;
use function is_writable;
use function Safe\file_put_contents;
use function Safe\mkdir;
use function Safe\preg_match;
use function Safe\tempnam;
use function sys_get_temp_dir;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class SudoFilePut
{
    public static function contents(string $filename, string $content): void
    {
        $fileWritable = file_exists($filename) && is_writable($filename);
        $pathWritable = ! file_exists($filename) && file_exists(dirname($filename)) && is_writable(dirname($filename));

        if ($fileWritable || $pathWritable) {
            try {
                file_put_contents($filename, $content);
            } catch (FilesystemException $e) {
                throw FailedToWriteFile::fromFilePutContentError($filename, $e);
            }

            return;
        }

        if (! Sudo::exists()) {
            throw FailedToWriteFile::fromNoPermissions($filename, null);
        }

        self::writeWithSudo($filename, $content);
    }

    private static function writeWithSudo(string $filename, string $content): void
    {
        $tempDir = Platform::getPieBaseWorkingDirectory() . '/tmp';
        if (! is_dir($tempDir)) {
            mkdir($tempDir, 0700, true);
        }

        try {
            $tempFilename = tempnam(sys_get_temp_dir(), 'pie_tmp_');
        } catch (FilesystemException $e) {
            throw FailedToWriteFile::fromNoPermissions($filename, $e);
        }

        try {
            file_put_contents($tempFilename, $content);
        } catch (FilesystemException $e) {
            throw FailedToWriteFile::fromFilePutContentError($tempFilename, $e);
        }

        if (file_exists($filename)) {
            self::copyOwnership($filename, $tempFilename);
        }

        Process::run([Sudo::find(), 'mv', $tempFilename, $filename], timeout: Process::SHORT_TIMEOUT);
    }

    /**
     * Attempt to copy the ownership details (uid/gid) from the source to the
     * given target file.
     */
    private static function copyOwnership(string $sourceFile, string $targetFile): void
    {
        try {
            // GNU chmod supports `--reference`, so try this first
            Process::run([Sudo::find(), 'chmod', '--reference=' . $sourceFile, $targetFile], timeout: Process::SHORT_TIMEOUT);

            return;
        } catch (ProcessFailedException) {
            // Fall back to using `stat` to determine uid/gid
            try {
                // Try using GNU stat (-c) first
                $userAndGroup = Process::run(['stat', '-c', '%u:%g', $sourceFile]);
            } catch (ProcessFailedException) {
                try {
                    // Fall back to using OSX stat (-f)
                    $userAndGroup = Process::run(['stat', '-f', '%u:%g', $sourceFile]);
                } catch (ProcessFailedException) {
                    return;
                }
            }

            if (empty($userAndGroup) || ! preg_match('/^\d+:\d+$/', $userAndGroup)) {
                return;
            }

            Process::run([Sudo::find(), 'chown', $userAndGroup, $targetFile], timeout: Process::SHORT_TIMEOUT);
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\File;

use RuntimeException;
use Throwable;

use function sprintf;

class FailedToWriteFile extends RuntimeException
{
    public static function fromFilePutContentError(string $filename, Throwable $previous): self
    {
        return new self(
            sprintf(
                'Failed to write file %s: %s',
                $filename,
                $previous->getMessage(),
            ),
            previous: $previous,
        );
    }

    public static function fromNoPermissions(string $filename, Throwable|null $previous): self
    {
        return new self(
            sprintf(
                'Failed to write file %s as PIE does not have enough permissions',
                $filename,
            ),
            previous: $previous,
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\File;

use Php\Pie\Util;

use function file_exists;
use function hash;
use function hash_equals;
use function Safe\hash_file;

/**
 * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
 *
 * @immutable
 */
final class BinaryFile
{
    private const HASH_TYPE_SHA256 = 'sha256';

    /**
     * @param non-empty-string $filePath
     * @param non-empty-string $checksum
     */
    public function __construct(
        public readonly string $filePath,
        public readonly string $checksum,
    ) {
    }

    /** @param non-empty-string $filePath */
    public static function fromFileWithSha256Checksum(string $filePath): self
    {
        return new self(
            $filePath,
            hash_file(self::HASH_TYPE_SHA256, $filePath),
        );
    }

    public function verify(): void
    {
        if (! file_exists($this->filePath)) {
            throw Util\FileNotFound::fromFilename($this->filePath);
        }

        $this->verifyAgainstOther(self::fromFileWithSha256Checksum($this->filePath));
    }

    /** @throws BinaryFileFailedVerification */
    public function verifyContent(string $content): void
    {
        $contentChecksum = hash(self::HASH_TYPE_SHA256, $content);

        if (! hash_equals($this->checksum, $contentChecksum)) {
            throw BinaryFileFailedVerification::fromChecksumMismatch($this, new self($this->filePath, $contentChecksum));
        }
    }

    /** @throws BinaryFileFailedVerification */
    public function verifyAgainstOther(self $other): void
    {
        if ($this->filePath !== $other->filePath) {
            throw BinaryFileFailedVerification::fromFilenameMismatch($this, $other);
        }

        if (! hash_equals($this->checksum, $other->checksum)) {
            throw BinaryFileFailedVerification::fromChecksumMismatch($this, $other);
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\File;

use Php\Pie\Platform;
use Php\Pie\Platform\TargetPlatform;
use Symfony\Component\Process\ExecutableFinder;

use function is_string;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class Sudo
{
    /** @var non-empty-string|null */
    private static string|null $memoizedSudo = null;

    /**
     * @return non-empty-string
     *
     * @throws SudoNotFoundOnSystem
     */
    public static function find(): string
    {
        if (! is_string(self::$memoizedSudo)) {
            $sudo = (new ExecutableFinder())->find('sudo');

            if ($sudo === null || $sudo === '') {
                throw SudoNotFoundOnSystem::new();
            }

            if (! TargetPlatform::isRunningAsRoot() && ! Platform::isInteractive()) {
                throw SudoRequiresInteractiveTerminal::fromSudo($sudo);
            }

            self::$memoizedSudo = $sudo;
        }

        return self::$memoizedSudo;
    }

    public static function exists(): bool
    {
        try {
            self::find();

            return true;
        } catch (SudoNotFoundOnSystem) {
            return false;
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\File;

use RuntimeException;

class SudoNotFoundOnSystem extends RuntimeException
{
    public static function new(): self
    {
        return new self('sudo command was not found on this system');
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\File;

use Php\Pie\Util\Process;
use RuntimeException;

use function file_exists;
use function Safe\tempnam;
use function sprintf;
use function sys_get_temp_dir;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class WindowsDelete
{
    /**
     * Windows will hold a lock on the DLL, so we can't actually replace or
     * delete the extension. However, Windows lets us move it out the way,
     * so move it to the temp directory. It can then be cleaned up later.
     */
    public static function usingMoveToTemp(string $filename): void
    {
        if (! file_exists($filename)) {
            return;
        }

        $newLockedExtFilename = tempnam(sys_get_temp_dir(), 'pie');

        Process::run(['move', $filename, $newLockedExtFilename]);

        if (file_exists($filename)) {
            throw new RuntimeException(sprintf(
                'Failed to move %s to %s',
                $filename,
                $newLockedExtFilename,
            ));
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\File;

use RuntimeException;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Throwable;

use function sprintf;

class FailedToUnlinkFile extends RuntimeException
{
    public static function fromUnlinkError(string $filename, Throwable $previous): self
    {
        return new self(
            sprintf(
                'Failed to unlink file %s: %s',
                $filename,
                $previous->getMessage(),
            ),
            previous: $previous,
        );
    }

    public static function fromNoPermissions(string $filename): self
    {
        return new self(sprintf(
            'Failed to unlink file %s as PIE does not have enough permissions',
            $filename,
        ));
    }

    public static function fromSudoRmProcessFailed(string $filename, ProcessFailedException $processFailed): self
    {
        return new self(
            sprintf(
                'Failed to unlink file %s using sudo rm: %s',
                $filename,
                $processFailed->getMessage(),
            ),
            previous: $processFailed,
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\File;

use RuntimeException;

use function sprintf;
use function substr;

class BinaryFileFailedVerification extends RuntimeException
{
    public static function fromFilenameMismatch(BinaryFile $expected, BinaryFile $actual): self
    {
        return new self(sprintf(
            'Expected file "%s" but actual file was "%s"',
            $expected->filePath,
            $actual->filePath,
        ));
    }

    public static function fromChecksumMismatch(BinaryFile $expected, BinaryFile $actual): self
    {
        return new self(sprintf(
            'File "%s" failed checksum verification. Expected %s..., was %s...',
            $expected->filePath,
            substr($expected->checksum, 0, 8),
            substr($actual->checksum, 0, 8),
        ));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\File;

use RuntimeException;

use function sprintf;

class SudoRequiresInteractiveTerminal extends RuntimeException
{
    public static function fromSudo(string $sudoPath): self
    {
        return new self(sprintf(
            'PIE needed to elevate privileges with %s, but you are running in a non-interactive terminal, so prompting for a password is not possible.',
            $sudoPath,
        ));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\File;

use Php\Pie\Util\Process;
use Safe\Exceptions\FilesystemException;
use Symfony\Component\Process\Exception\ProcessFailedException;

use function dirname;
use function file_exists;
use function is_writable;
use function Safe\touch;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class SudoCreate
{
    public static function file(string $filename): void
    {
        /**
         * Note: strictly speaking, `touch` is a command to:
         *
         * > Update the access and modification times of each FILE to the current time.
         *
         * But, the way we are using `touch` is to just create the file, hence
         * the class naming is to reflect that. So; if the file already exists
         * we can exit early (as we're not actually interested in updating the
         * access/modification times of the file currently).
         */
        if (file_exists($filename)) {
            return;
        }

        if (is_writable(dirname($filename))) {
            try {
                touch($filename);
            } catch (FilesystemException $e) {
                throw FailedToCreateFile::fromTouchError($filename, $e);
            }

            return;
        }

        if (! Sudo::exists()) {
            throw FailedToCreateFile::fromNoPermissions($filename);
        }

        try {
            Process::run([Sudo::find(), 'touch', $filename], timeout: Process::SHORT_TIMEOUT);
        } catch (ProcessFailedException $processFailedException) {
            throw FailedToCreateFile::fromSudoTouchProcessFailed($filename, $processFailedException);
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
enum ExtensionType: string
{
    case PhpModule     = 'php-ext';
    case ZendExtension = 'php-ext-zend';

    public static function isValid(string $toBeChecked): bool
    {
        return self::tryFrom($toBeChecked) !== null;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Composer\IO\IOInterface;
use Composer\IO\NullIO;
use Composer\Package\Link;
use Composer\Package\Version\VersionParser;
use OutOfRangeException;
use Php\Pie\ComposerIntegration\PieComposerFactory;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\ComposerIntegration\PieJsonEditor;
use Php\Pie\DependencyResolver\RequestedPackageAndVersion;
use Php\Pie\ExtensionName;
use Php\Pie\ExtensionType;
use Php\Pie\Installing\InstallForPhpProject\ComposerFactoryForProject;
use Php\Pie\Installing\InstallForPhpProject\DetermineExtensionsRequired;
use Php\Pie\Installing\InstallForPhpProject\FindMatchingPackages;
use Php\Pie\Installing\InstallForPhpProject\InstallPiePackageFromPath;
use Php\Pie\Installing\InstallForPhpProject\InstallSelectedPackage;
use Php\Pie\Platform;
use Php\Pie\Platform\InstalledPiePackages;
use Php\Pie\Util\Emoji;
use Psr\Container\ContainerInterface;
use Safe\Exceptions\DirException;
use Safe\Exceptions\FilesystemException;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Throwable;
use Webmozart\Assert\Assert;

use function array_column;
use function array_key_exists;
use function array_keys;
use function array_map;
use function array_merge;
use function array_walk;
use function assert;
use function count;
use function implode;
use function in_array;
use function is_dir;
use function Safe\chdir;
use function Safe\getcwd;
use function Safe\realpath;
use function sprintf;
use function strtolower;

use const PHP_EOL;

#[AsCommand(
    name: 'install-extensions-for-project',
    description: 'Check a project for its extension dependencies, and offers to install them',
)]
final class InstallExtensionsForProjectCommand extends Command
{
    public function __construct(
        private readonly ComposerFactoryForProject $composerFactoryForProject,
        private readonly DetermineExtensionsRequired $determineExtensionsRequired,
        private readonly InstalledPiePackages $installedPiePackages,
        private readonly FindMatchingPackages $findMatchingPackages,
        private readonly InstallSelectedPackage $installSelectedPackage,
        private readonly InstallPiePackageFromPath $installPiePackageFromPath,
        private readonly ContainerInterface $container,
        private readonly IOInterface $io,
    ) {
        parent::__construct();
    }

    public function configure(): void
    {
        parent::configure();

        CommandHelper::configureDownloadBuildInstallOptions($this, false);
    }

    public function execute(InputInterface $input, OutputInterface $output): int
    {
        $workingDirOption  = (string) $input->getOption(CommandHelper::OPTION_WORKING_DIRECTORY);
        $restoreWorkingDir = static function (): void {
        };
        if ($workingDirOption !== '' && is_dir($workingDirOption)) {
            $currentWorkingDir = getcwd();
            $restoreWorkingDir = function () use ($currentWorkingDir): void {
                chdir($currentWorkingDir);
                $this->io->write(
                    sprintf('Restored working directory to: %s', $currentWorkingDir),
                    verbosity: IOInterface::VERBOSE,
                );
            };

            chdir($workingDirOption);
            $this->io->write(
                sprintf('Changed working directory to: %s', $workingDirOption),
                verbosity: IOInterface::VERBOSE,
            );
        }

        CommandHelper::applyNoCacheOptionIfSet($input, $this->io);

        $rootPackage = $this->composerFactoryForProject->rootPackage($this->io);

        if (ExtensionType::isValid($rootPackage->getType())) {
            try {
                $cwd = realpath(getcwd());
            } catch (FilesystemException | DirException $e) {
                $this->io->writeError(sprintf(
                    '<error>Failed to determine current working directory: %s</error>',
                    $e->getMessage(),
                ));

                $restoreWorkingDir();

                return Command::FAILURE;
            }

            $exit = ($this->installPiePackageFromPath)(
                $this,
                $cwd,
                $rootPackage,
                PieJsonEditor::fromTargetPlatform(CommandHelper::determineTargetPlatformFromInputs($input, new NullIO())),
                $input,
                $this->io,
            );

            $restoreWorkingDir();

            return $exit;
        }

        $allowNonInteractive = $input->hasOption(CommandHelper::OPTION_ALLOW_NON_INTERACTIVE_PROJECT_INSTALL) && $input->getOption(CommandHelper::OPTION_ALLOW_NON_INTERACTIVE_PROJECT_INSTALL);
        if (! Platform::isInteractive() && ! $allowNonInteractive) {
            $this->io->writeError(sprintf(
                '<warning>Aborting! You are not running in interactive mode, and --%s was not specified.</warning>',
                CommandHelper::OPTION_ALLOW_NON_INTERACTIVE_PROJECT_INSTALL,
            ));

            return Command::FAILURE;
        }

        $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $this->io);

        $this->io->write(sprintf(
            'Checking extensions for your project <info>%s</info> (path: %s)',
            $rootPackage->getPrettyName(),
            getcwd(),
        ));

        $extensionsRequired = $this->determineExtensionsRequired->forProject($this->composerFactoryForProject->composer($this->io));

        $pieComposer = PieComposerFactory::createPieComposer(
            $this->container,
            PieComposerRequest::noOperation(
                new NullIO(),
                $targetPlatform,
            ),
        );

        $phpEnabledExtensions = array_map('strtolower', array_keys($targetPlatform->phpBinaryPath->extensions()));
        $installedPiePackages = $this->installedPiePackages->allPiePackages($pieComposer);

        $anyErrorsHappened = false;

        array_walk(
            $extensionsRequired,
            function (Link $link) use ($pieComposer, $phpEnabledExtensions, $installedPiePackages, $input, &$anyErrorsHappened, $targetPlatform): void {
                $extension              = ExtensionName::normaliseFromString($link->getTarget());
                $linkRequiresConstraint = $link->getPrettyConstraint();

                $piePackagesForExtension = $installedPiePackages
                    ->findByPhpFormattedExtensionName($extension->phpFormattedExtensionName())
                    ->onlyVerifiedFor($targetPlatform);

                $piePackageVersion = null;

                if (count($piePackagesForExtension) === 1) {
                    $piePackageVersion = $piePackagesForExtension->onlyOne()->version();
                }

                $piePackageVersionMatchesLinkConstraint = null;
                if ($piePackageVersion !== null) {
                    $piePackageVersionMatchesLinkConstraint = $link
                        ->getConstraint()
                        ->matches(
                            (new VersionParser())->parseConstraints($piePackageVersion),
                        );
                }

                if (in_array(strtolower($extension->name()), $phpEnabledExtensions)) {
                    if ($piePackageVersion !== null && $piePackageVersionMatchesLinkConstraint === false) {
                        $this->io->write(sprintf(
                            '%s: <comment>%s:%s</comment> %s Version %s is installed, but does not meet the version requirement %s',
                            $link->getDescription(),
                            $link->getTarget(),
                            $linkRequiresConstraint,
                            Emoji::WARNING,
                            $piePackageVersion,
                            $link->getConstraint()->getPrettyString(),
                        ));

                        return;
                    }

                    $this->io->write(sprintf(
                        '%s: <info>%s:%s</info> %s Already installed',
                        $link->getDescription(),
                        $link->getTarget(),
                        $linkRequiresConstraint,
                        Emoji::GREEN_CHECKMARK,
                    ));

                    return;
                }

                $this->io->write(sprintf(
                    '%s: <comment>%s:%s</comment> %s Missing',
                    $link->getDescription(),
                    $link->getTarget(),
                    $linkRequiresConstraint,
                    Emoji::PROHIBITED,
                ));

                try {
                    $matches = $this->findMatchingPackages->byProvider($pieComposer, $extension);
                } catch (OutOfRangeException) {
                    $anyErrorsHappened = true;

                    $this->io->writeError(sprintf(
                        '<error>No packages were found for %s</error>',
                        $extension->nameWithExtPrefix(),
                    ));

                    return;
                }

                if (! Platform::isInteractive() && count($matches) > 1) {
                    $anyErrorsHappened = true;

                    // @todo Figure out if there is a way to improve this, safely
                    $this->io->writeError(sprintf(
                        "<warning>Multiple packages were found for %s:</warning>\n  %s\n\n<warning>This means you cannot `pie install` this project interactively for now.</warning>",
                        $extension->nameWithExtPrefix(),
                        implode("\n  ", array_column($matches, 'name')),
                    ));

                    return;
                }

                if (Platform::isInteractive()) {
                    $selectedPackageAnswer = (int) $this->io->select(
                        "\nThe following packages may be suitable, which would you like to install: ",
                        array_merge(
                            ['None'],
                            array_map(
                                static function (array $match): string {
                                    return sprintf('%s: %s', $match['name'], $match['description'] ?? 'no description available');
                                },
                                $matches,
                            ),
                        ),
                        '0',
                    );

                    if ($selectedPackageAnswer === 0) {
                        $this->io->write('Okay I won\'t install anything for ' . $extension->name());
                        $anyErrorsHappened = true;

                        return;
                    }

                    $matchesKey = $selectedPackageAnswer - 1;
                    assert(array_key_exists($matchesKey, $matches));

                    $selectedPackageName = $matches[$matchesKey]['name'];
                } else {
                    $selectedPackageName = $matches[0]['name'];
                }

                assert($selectedPackageName !== '');
                $requestedPackageAndVersion = new RequestedPackageAndVersion(
                    $selectedPackageName,
                    $linkRequiresConstraint === '*' || $linkRequiresConstraint === '' ? null : $linkRequiresConstraint,
                );

                try {
                    $this->io->write(
                        sprintf('Invoking pie install of %s', $requestedPackageAndVersion->prettyNameAndVersion()),
                        verbosity: IOInterface::VERBOSE,
                    );
                    Assert::same(
                        0,
                        $this->installSelectedPackage->withSubCommand(
                            ExtensionName::normaliseFromString($link->getTarget()),
                            $requestedPackageAndVersion,
                            $this,
                            $input,
                        ),
                        'Non-zero exit code %s whilst installing ' . $requestedPackageAndVersion->package,
                    );
                } catch (Throwable $t) {
                    $anyErrorsHappened = true;

                    $this->io->writeError('<error>' . $t->getMessage() . '</error>');
                }
            },
        );

        $this->io->write(PHP_EOL . 'Finished checking extensions.');

        $restoreWorkingDir();

        return $anyErrorsHappened ? self::FAILURE : self::SUCCESS;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Composer\IO\IOInterface;
use Php\Pie\ComposerIntegration\ComposerIntegrationHandler;
use Php\Pie\ComposerIntegration\ComposerRunFailed;
use Php\Pie\ComposerIntegration\PieComposerFactory;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\ComposerIntegration\PieOperation;
use Php\Pie\DependencyResolver\BundledPhpExtensionRefusal;
use Php\Pie\DependencyResolver\DependencyResolver;
use Php\Pie\DependencyResolver\InvalidPackageName;
use Php\Pie\DependencyResolver\UnableToResolveRequirement;
use Php\Pie\Installing\InstallForPhpProject\FindMatchingPackages;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

use function sprintf;

#[AsCommand(
    name: 'download',
    description: 'Same behaviour as build, but puts the files in a local directory for manual building and installation.',
)]
final class DownloadCommand extends Command
{
    public function __construct(
        private readonly ContainerInterface $container,
        private readonly DependencyResolver $dependencyResolver,
        private readonly ComposerIntegrationHandler $composerIntegrationHandler,
        private readonly FindMatchingPackages $findMatchingPackages,
        private readonly IOInterface $io,
    ) {
        parent::__construct();
    }

    public function configure(): void
    {
        parent::configure();

        CommandHelper::configureDownloadBuildInstallOptions($this);
    }

    public function execute(InputInterface $input, OutputInterface $output): int
    {
        CommandHelper::validateInput($input, $this);

        $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $this->io);
        try {
            $requestedNameAndVersion = CommandHelper::requestedNameAndVersionPair($input);
        } catch (InvalidPackageName $invalidPackageName) {
            return CommandHelper::handlePackageNotFound(
                $invalidPackageName,
                $this->findMatchingPackages,
                $this->io,
                $targetPlatform,
                $this->container,
            );
        }

        $forceInstallPackageVersion = CommandHelper::determineForceInstallingPackageVersion($input);
        CommandHelper::applyNoCacheOptionIfSet($input, $this->io);

        $composer = PieComposerFactory::createPieComposer(
            $this->container,
            new PieComposerRequest(
                $this->io,
                $targetPlatform,
                $requestedNameAndVersion,
                PieOperation::Download,
                [], // Configure options are not needed for download only
                false, // setting up INI not needed for download
            ),
        );

        try {
            $package = ($this->dependencyResolver)(
                $composer,
                $targetPlatform,
                $requestedNameAndVersion,
                $forceInstallPackageVersion,
            );
        } catch (UnableToResolveRequirement $unableToResolveRequirement) {
            return CommandHelper::handlePackageNotFound(
                $unableToResolveRequirement,
                $this->findMatchingPackages,
                $this->io,
                $targetPlatform,
                $this->container,
            );
        } catch (BundledPhpExtensionRefusal $bundledPhpExtensionRefusal) {
            $this->io->writeError('');
            $this->io->writeError('<comment>' . $bundledPhpExtensionRefusal->getMessage() . '</comment>');

            return self::INVALID;
        }

        $this->io->write(sprintf('<info>Found package:</info> %s which provides <info>%s</info>', $package->prettyNameAndVersion(), $package->extensionName()->nameWithExtPrefix()));

        try {
            $this->composerIntegrationHandler->runInstall(
                $package,
                $composer,
                $targetPlatform,
                $requestedNameAndVersion,
                $forceInstallPackageVersion,
                false,
            );
        } catch (ComposerRunFailed $composerRunFailed) {
            $this->io->writeError('<error>' . $composerRunFailed->getMessage() . '</error>');

            return $composerRunFailed->getCode();
        }

        return Command::SUCCESS;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Composer\IO\IOInterface;
use Composer\IO\NullIO;
use Php\Pie\ComposerIntegration\PieComposerFactory;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand(
    name: 'repository:list',
    description: 'List the package repositories that PIE uses.',
)]
final class RepositoryListCommand extends Command
{
    public function __construct(
        private readonly ContainerInterface $container,
        private readonly IOInterface $io,
    ) {
        parent::__construct();
    }

    public function configure(): void
    {
        parent::configure();

        CommandHelper::configurePhpConfigOptions($this);
    }

    public function execute(InputInterface $input, OutputInterface $output): int
    {
        CommandHelper::applyNoCacheOptionIfSet($input, $this->io);

        CommandHelper::listRepositories(
            PieComposerFactory::createPieComposer(
                $this->container,
                PieComposerRequest::noOperation(
                    new NullIO(),
                    CommandHelper::determineTargetPlatformFromInputs($input, $this->io),
                ),
            ),
            $this->io,
        );

        return 0;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Composer\IO\IOInterface;
use Composer\IO\NullIO;
use Php\Pie\ComposerIntegration\PieComposerFactory;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\ComposerIntegration\PieJsonEditor;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Webmozart\Assert\Assert;

use function str_contains;

#[AsCommand(
    name: 'repository:remove',
    description: 'Remove a repository for packages that PIE can use.',
)]
final class RepositoryRemoveCommand extends Command
{
    private const ARG_URL = 'url';

    public function __construct(
        private readonly ContainerInterface $container,
        private readonly IOInterface $io,
    ) {
        parent::__construct();
    }

    public function configure(): void
    {
        parent::configure();

        CommandHelper::configurePhpConfigOptions($this);

        $this->addArgument(
            self::ARG_URL,
            InputArgument::REQUIRED,
            'Specify the URL of the repository, e.g. a Github/Gitlab URL, or a filesystem path',
        );
    }

    public function execute(InputInterface $input, OutputInterface $output): int
    {
        $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $this->io);
        $pieJsonEditor  = PieJsonEditor::fromTargetPlatform($targetPlatform);

        $url = (string) $input->getArgument(self::ARG_URL);
        Assert::stringNotEmpty($url);

        if (str_contains($url, 'packagist.org')) {
            // "removing packagist" is really just adding an exclusion
            $pieJsonEditor
                ->ensureExists()
                ->excludePackagistOrg();
        } else {
            $pieJsonEditor
                ->ensureExists()
                ->removeRepository($url);
        }

        CommandHelper::applyNoCacheOptionIfSet($input, $this->io);

        CommandHelper::listRepositories(
            PieComposerFactory::createPieComposer(
                $this->container,
                PieComposerRequest::noOperation(
                    new NullIO(),
                    $targetPlatform,
                ),
            ),
            $this->io,
        );

        return 0;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Composer\IO\IOInterface;
use Php\Pie\ComposerIntegration\PieComposerFactory;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\ComposerIntegration\PieOperation;
use Php\Pie\DependencyResolver\BundledPhpExtensionRefusal;
use Php\Pie\DependencyResolver\DependencyResolver;
use Php\Pie\DependencyResolver\FetchDependencyStatuses;
use Php\Pie\DependencyResolver\InvalidPackageName;
use Php\Pie\DependencyResolver\UnableToResolveRequirement;
use Php\Pie\Installing\InstallForPhpProject\FindMatchingPackages;
use Php\Pie\Platform\ThreadSafetyMode;
use Php\Pie\Util\Emoji;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

use function count;
use function in_array;
use function sprintf;

#[AsCommand(
    name: 'info',
    description: 'Show metadata about a given extension.',
)]
final class InfoCommand extends Command
{
    public function __construct(
        private readonly ContainerInterface $container,
        private readonly DependencyResolver $dependencyResolver,
        private readonly FetchDependencyStatuses $fetchDependencyStatuses,
        private readonly FindMatchingPackages $findMatchingPackages,
        private readonly IOInterface $io,
    ) {
        parent::__construct();
    }

    public function configure(): void
    {
        parent::configure();

        CommandHelper::configureDownloadBuildInstallOptions($this);
    }

    public function execute(InputInterface $input, OutputInterface $output): int
    {
        CommandHelper::validateInput($input, $this);

        $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $this->io);

        try {
            $requestedNameAndVersion = CommandHelper::requestedNameAndVersionPair($input);
        } catch (InvalidPackageName $invalidPackageName) {
            return CommandHelper::handlePackageNotFound(
                $invalidPackageName,
                $this->findMatchingPackages,
                $this->io,
                $targetPlatform,
                $this->container,
            );
        }

        CommandHelper::applyNoCacheOptionIfSet($input, $this->io);

        $composer = PieComposerFactory::createPieComposer(
            $this->container,
            new PieComposerRequest(
                $this->io,
                $targetPlatform,
                $requestedNameAndVersion,
                PieOperation::Resolve,
                [], // Configure options are not needed for resolve only
                false, // setting up INI not needed for info
            ),
        );

        try {
            $package = ($this->dependencyResolver)(
                $composer,
                $targetPlatform,
                $requestedNameAndVersion,
                true,
            );
        } catch (UnableToResolveRequirement $unableToResolveRequirement) {
            return CommandHelper::handlePackageNotFound(
                $unableToResolveRequirement,
                $this->findMatchingPackages,
                $this->io,
                $targetPlatform,
                $this->container,
            );
        } catch (BundledPhpExtensionRefusal $bundledPhpExtensionRefusal) {
            $this->io->writeError('');
            $this->io->writeError('<comment>' . $bundledPhpExtensionRefusal->getMessage() . '</comment>');

            return self::INVALID;
        }

        $this->io->write(sprintf('<info>Found package:</info> %s which provides <info>%s</info>', $package->prettyNameAndVersion(), $package->extensionName()->nameWithExtPrefix()));

        $this->io->write(sprintf('Extension name: %s', $package->extensionName()->name()));
        $this->io->write(sprintf('Extension type: %s (%s)', $package->extensionType()->value, $package->extensionType()->name));
        $this->io->write(sprintf('Composer package name: %s', $package->name()));
        $this->io->write(sprintf('Version: %s', $package->version()));
        $this->io->write(sprintf('Download URL: %s', $package->downloadUrl() ?? '(not specified)'));
        $this->io->write(sprintf(
            'TS/NTS: %s',
            ($targetPlatform->threadSafety === ThreadSafetyMode::NonThreadSafe && ! $package->supportNts())
                || ($targetPlatform->threadSafety === ThreadSafetyMode::ThreadSafe && ! $package->supportZts()) ? sprintf('%s (not supported on %s)', Emoji::PROHIBITED, $targetPlatform->threadSafety->asShort()) : Emoji::GREEN_CHECKMARK,
        ));

        $this->io->write(sprintf(
            'OS: %s',
            ($package->compatibleOsFamilies() === null || in_array($targetPlatform->operatingSystemFamily, $package->compatibleOsFamilies(), true))
            && ($package->incompatibleOsFamilies() === null || ! in_array($targetPlatform->operatingSystemFamily, $package->incompatibleOsFamilies(), true))
                ? Emoji::GREEN_CHECKMARK
                : sprintf('%s (not supported on %s)', Emoji::PROHIBITED, $targetPlatform->operatingSystemFamily->value),
        ));

        $this->io->write("\n<options=bold,underscore>Dependencies:</>");

        $dependencyStatuses = ($this->fetchDependencyStatuses)($targetPlatform, $composer, $package->composerPackage());
        if (count($dependencyStatuses) > 0) {
            foreach ($dependencyStatuses as $dependencyStatus) {
                $this->io->write('    ' . $dependencyStatus->asPrettyString());
            }
        } else {
            $this->io->write('    No dependencies.');
        }

        $this->io->write("\n<options=bold,underscore>Configure options:</>");
        if (count($package->configureOptions())) {
            foreach ($package->configureOptions() as $configureOption) {
                $this->io->write(sprintf('    --%s%s  (%s)', $configureOption->name, $configureOption->needsValue ? '=?' : '', $configureOption->description));
            }
        } else {
            $this->io->write('    No configure options are specified.');
        }

        return Command::SUCCESS;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Throwable;

class ArgvInput extends \Symfony\Component\Console\Input\ArgvInput
{
    private Throwable|null $exceptionThrown = null;

    /**
     * Wrap parent token parsing to collect and ignore exceptions during
     * parsing. This ensures that errors we meet mid-way through parsing don't
     * short-circuit processing the rest of the arguments. Without this, whilst
     * the following example works:
     *
     *     pie build asgrim/example-pie-extension --with-hello-name=sup
     *
     * This does not:
     *
     *     pie build --with-hello-name=sup asgrim/example-pie-extension
     *
     * This is because when Symfony tries to parse the `--with-hello-name`, it
     * hasn't loaded in the configure options for the package yet, and so
     * throws an exception and does not process the package name argument.
     *
     * Note, however, there is still a limitation, as this will not work:
     *
     *      pie build --with-hello-name sup asgrim/example-pie-extension
     */
    protected function parse(): void
    {
        $this->exceptionThrown = null;

        parent::parse();

        /** @phpstan-ignore notIdentical.alwaysFalse */
        if ($this->exceptionThrown !== null) {
            throw $this->exceptionThrown;
        }
    }

    protected function parseToken(string $token, bool $parseOptions): bool
    {
        try {
            return parent::parseToken($token, $parseOptions);
        } catch (Throwable $caught) {
            $this->exceptionThrown = $caught;

            // Ignore the error intentionally
            return $parseOptions;
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Composer\IO\IOInterface;
use Php\Pie\ComposerIntegration\ComposerIntegrationHandler;
use Php\Pie\ComposerIntegration\ComposerRunFailed;
use Php\Pie\ComposerIntegration\PieComposerFactory;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\ComposerIntegration\PieOperation;
use Php\Pie\DependencyResolver\BundledPhpExtensionRefusal;
use Php\Pie\DependencyResolver\DependencyInstaller\PrescanSystemDependencies;
use Php\Pie\DependencyResolver\DependencyResolver;
use Php\Pie\DependencyResolver\InvalidPackageName;
use Php\Pie\DependencyResolver\UnableToResolveRequirement;
use Php\Pie\Installing\InstallForPhpProject\FindMatchingPackages;
use Php\Pie\Platform\PackageManager;
use Php\Pie\SelfManage\BuildTools\CheckAllBuildTools;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Throwable;

use function sprintf;

#[AsCommand(
    name: 'build',
    description: 'Download and build a PIE-compatible PHP extension, without installing it.',
)]
final class BuildCommand extends Command
{
    public function __construct(
        private readonly ContainerInterface $container,
        private readonly DependencyResolver $dependencyResolver,
        private readonly PrescanSystemDependencies $prescanSystemDependencies,
        private readonly ComposerIntegrationHandler $composerIntegrationHandler,
        private readonly FindMatchingPackages $findMatchingPackages,
        private readonly IOInterface $io,
        private readonly CheckAllBuildTools $checkBuildTools,
    ) {
        parent::__construct();
    }

    public function configure(): void
    {
        parent::configure();

        CommandHelper::configureDownloadBuildInstallOptions($this);
    }

    public function execute(InputInterface $input, OutputInterface $output): int
    {
        $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $this->io);
        try {
            $requestedNameAndVersion = CommandHelper::requestedNameAndVersionPair($input);
        } catch (InvalidPackageName $invalidPackageName) {
            return CommandHelper::handlePackageNotFound(
                $invalidPackageName,
                $this->findMatchingPackages,
                $this->io,
                $targetPlatform,
                $this->container,
            );
        }

        $forceInstallPackageVersion = CommandHelper::determineForceInstallingPackageVersion($input);
        CommandHelper::applyNoCacheOptionIfSet($input, $this->io);

        if (CommandHelper::shouldCheckForBuildTools($input)) {
            $this->checkBuildTools->check(
                $this->io,
                PackageManager::detect(),
                $targetPlatform,
                CommandHelper::autoInstallBuildTools($input),
            );
        }

        $composer = PieComposerFactory::createPieComposer(
            $this->container,
            new PieComposerRequest(
                $this->io,
                $targetPlatform,
                $requestedNameAndVersion,
                PieOperation::Resolve,
                [], // Configure options are not needed for resolve only
                false, // setting up INI not needed for build
            ),
        );

        if (CommandHelper::shouldCheckSystemDependencies($input)) {
            try {
                ($this->prescanSystemDependencies)(
                    $composer,
                    $targetPlatform,
                    $requestedNameAndVersion,
                    CommandHelper::autoInstallSystemDependencies($input),
                );
            } catch (Throwable $anything) {
                $this->io->writeError(
                    '<comment>Skipping system dependency pre-scan due to exception:</comment> ' . $anything->getMessage(),
                    verbosity: IOInterface::VERBOSE,
                );
            }
        }

        try {
            $package = ($this->dependencyResolver)(
                $composer,
                $targetPlatform,
                $requestedNameAndVersion,
                $forceInstallPackageVersion,
            );
        } catch (UnableToResolveRequirement $unableToResolveRequirement) {
            return CommandHelper::handlePackageNotFound(
                $unableToResolveRequirement,
                $this->findMatchingPackages,
                $this->io,
                $targetPlatform,
                $this->container,
            );
        } catch (BundledPhpExtensionRefusal $bundledPhpExtensionRefusal) {
            $this->io->writeError('');
            $this->io->writeError('<comment>' . $bundledPhpExtensionRefusal->getMessage() . '</comment>');

            return self::INVALID;
        }

        $this->io->write(sprintf('<info>Found package:</info> %s which provides <info>%s</info>', $package->prettyNameAndVersion(), $package->extensionName()->nameWithExtPrefix()));

        // Now we know what package we have, we can validate the configure options for the command and re-create the
        // Composer instance with the populated configure options
        CommandHelper::bindConfigureOptionsFromPackage($this, $package, $input);
        $configureOptionsValues = CommandHelper::processConfigureOptionsFromInput($package, $input);

        $composer = PieComposerFactory::createPieComposer(
            $this->container,
            new PieComposerRequest(
                $this->io,
                $targetPlatform,
                $requestedNameAndVersion,
                PieOperation::Build,
                $configureOptionsValues,
                false, // setting up INI not needed for build
            ),
        );

        try {
            $this->composerIntegrationHandler->runInstall(
                $package,
                $composer,
                $targetPlatform,
                $requestedNameAndVersion,
                $forceInstallPackageVersion,
                false,
            );
        } catch (ComposerRunFailed $composerRunFailed) {
            $this->io->writeError('<error>' . $composerRunFailed->getMessage() . '</error>');

            return $composerRunFailed->getCode();
        }

        return Command::SUCCESS;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Composer\IO\IOInterface;
use Composer\IO\NullIO;
use Php\Pie\ComposerIntegration\PieComposerFactory;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\ComposerIntegration\PieJsonEditor;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Webmozart\Assert\Assert;

use function Safe\realpath;
use function str_contains;

#[AsCommand(
    name: 'repository:add',
    description: 'Add a new repository for packages that PIE can use.',
)]
final class RepositoryAddCommand extends Command
{
    private const ARG_TYPE = 'type';
    private const ARG_URL  = 'url';

    private const ALLOWED_TYPES = ['vcs', 'path', 'composer'];

    public function __construct(
        private readonly ContainerInterface $container,
        private readonly IOInterface $io,
    ) {
        parent::__construct();
    }

    public function configure(): void
    {
        parent::configure();

        CommandHelper::configurePhpConfigOptions($this);

        $this->addArgument(
            self::ARG_TYPE,
            InputArgument::REQUIRED,
            'Specify the type of the repository, e.g. vcs, path, composer',
        );
        $this->addArgument(
            self::ARG_URL,
            InputArgument::REQUIRED,
            'Specify the URL of the repository, e.g. a Github/Gitlab URL, a filesystem path, or Private Packagist URL',
        );
    }

    public function execute(InputInterface $input, OutputInterface $output): int
    {
        $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $this->io);
        $pieJsonEditor  = PieJsonEditor::fromTargetPlatform($targetPlatform);

        $type = (string) $input->getArgument(self::ARG_TYPE);
        Assert::inArray($type, self::ALLOWED_TYPES);

        $url = $originalUrl = (string) $input->getArgument(self::ARG_URL);

        if ($type === 'path') {
            $url = realpath($originalUrl);
        }

        if ($type === 'composer' && str_contains($url, 'packagist.org')) {
            // "adding packagist" is really just removing an exclusion
            $pieJsonEditor
                ->ensureExists()
                ->removeRepository('packagist.org');
        } else {
            Assert::stringNotEmpty($url, 'Could not resolve ' . $originalUrl . ' to a real path');

            $pieJsonEditor
                ->ensureExists()
                ->addRepository($type, $url);
        }

        CommandHelper::applyNoCacheOptionIfSet($input, $this->io);

        CommandHelper::listRepositories(
            PieComposerFactory::createPieComposer(
                $this->container,
                PieComposerRequest::noOperation(
                    new NullIO(),
                    $targetPlatform,
                ),
            ),
            $this->io,
        );

        return 0;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Webmozart\Assert\Assert;

use function array_combine;
use function array_filter;
use function array_keys;
use function array_map;
use function array_merge;
use function array_values;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class InvokeSubCommand
{
    public function __construct(private readonly OutputInterface $output)
    {
    }

    /** @param array<array-key, mixed> $subCommandInput */
    public function __invoke(
        Command $command,
        array $subCommandInput,
        InputInterface $originalCommandInput,
        OutputFormatter|null $formatter = null,
    ): int {
        $originalSuppliedOptions = array_filter($originalCommandInput->getOptions());
        $installForProjectInput  = new ArrayInput(array_merge(
            $subCommandInput,
            array_combine(
                array_map(static fn ($key) => '--' . $key, array_keys($originalSuppliedOptions)),
                array_values($originalSuppliedOptions),
            ),
        ));

        $application = $command->getApplication();
        Assert::notNull($application);

        if ($formatter instanceof OutputFormatter) {
            $oldFormatter = $this->output->getFormatter();
            $this->output->setFormatter($formatter);
        }

        try {
            $result = $application->doRun($installForProjectInput, $this->output);
        } finally {
            if ($formatter instanceof OutputFormatter) {
                $this->output->setFormatter($oldFormatter);
            }
        }

        return $result;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Composer\IO\IOInterface;
use Php\Pie\ComposerIntegration\ComposerIntegrationHandler;
use Php\Pie\ComposerIntegration\ComposerRunFailed;
use Php\Pie\ComposerIntegration\PieComposerFactory;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\ComposerIntegration\PieOperation;
use Php\Pie\DependencyResolver\BundledPhpExtensionRefusal;
use Php\Pie\DependencyResolver\DependencyInstaller\PrescanSystemDependencies;
use Php\Pie\DependencyResolver\DependencyResolver;
use Php\Pie\DependencyResolver\InvalidPackageName;
use Php\Pie\DependencyResolver\UnableToResolveRequirement;
use Php\Pie\Installing\InstallForPhpProject\FindMatchingPackages;
use Php\Pie\Platform\PackageManager;
use Php\Pie\Platform\TargetPlatform;
use Php\Pie\SelfManage\BuildTools\CheckAllBuildTools;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Throwable;

use function sprintf;

#[AsCommand(
    name: 'install',
    description: 'Download, build, and install a PIE-compatible PHP extension.',
)]
final class InstallCommand extends Command
{
    public function __construct(
        private readonly ContainerInterface $container,
        private readonly DependencyResolver $dependencyResolver,
        private readonly PrescanSystemDependencies $prescanSystemDependencies,
        private readonly ComposerIntegrationHandler $composerIntegrationHandler,
        private readonly InvokeSubCommand $invokeSubCommand,
        private readonly FindMatchingPackages $findMatchingPackages,
        private readonly IOInterface $io,
        private readonly CheckAllBuildTools $checkBuildTools,
    ) {
        parent::__construct();
    }

    public function configure(): void
    {
        parent::configure();

        CommandHelper::configureDownloadBuildInstallOptions($this);
    }

    public function execute(InputInterface $input, OutputInterface $output): int
    {
        if (! $input->getArgument(CommandHelper::ARG_REQUESTED_PACKAGE_AND_VERSION)) {
            return ($this->invokeSubCommand)(
                $this,
                ['command' => 'install-extensions-for-project'],
                $input,
            );
        }

        if (! TargetPlatform::isRunningAsRoot()) {
            $this->io->write('This command may need elevated privileges, and may prompt you for your password.');
        }

        $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $this->io);
        try {
            $requestedNameAndVersion = CommandHelper::requestedNameAndVersionPair($input);
        } catch (InvalidPackageName $invalidPackageName) {
            return CommandHelper::handlePackageNotFound(
                $invalidPackageName,
                $this->findMatchingPackages,
                $this->io,
                $targetPlatform,
                $this->container,
            );
        }

        $forceInstallPackageVersion = CommandHelper::determineForceInstallingPackageVersion($input);
        CommandHelper::applyNoCacheOptionIfSet($input, $this->io);

        if (CommandHelper::shouldCheckForBuildTools($input)) {
            $this->checkBuildTools->check(
                $this->io,
                PackageManager::detect(),
                $targetPlatform,
                CommandHelper::autoInstallBuildTools($input),
            );
        }

        $composer = PieComposerFactory::createPieComposer(
            $this->container,
            new PieComposerRequest(
                $this->io,
                $targetPlatform,
                $requestedNameAndVersion,
                PieOperation::Resolve,
                [], // Configure options are not needed for resolve only
                false, // setting up INI not needed for resolve step
            ),
        );

        if (CommandHelper::shouldCheckSystemDependencies($input)) {
            try {
                ($this->prescanSystemDependencies)(
                    $composer,
                    $targetPlatform,
                    $requestedNameAndVersion,
                    CommandHelper::autoInstallSystemDependencies($input),
                );
            } catch (Throwable $anything) {
                $this->io->writeError(
                    '<comment>Skipping system dependency pre-scan due to exception:</comment> ' . $anything->getMessage(),
                    verbosity: IOInterface::VERBOSE,
                );
            }
        }

        try {
            $package = ($this->dependencyResolver)(
                $composer,
                $targetPlatform,
                $requestedNameAndVersion,
                $forceInstallPackageVersion,
            );
        } catch (UnableToResolveRequirement $unableToResolveRequirement) {
            return CommandHelper::handlePackageNotFound(
                $unableToResolveRequirement,
                $this->findMatchingPackages,
                $this->io,
                $targetPlatform,
                $this->container,
            );
        } catch (BundledPhpExtensionRefusal $bundledPhpExtensionRefusal) {
            $this->io->writeError('');
            $this->io->writeError('<comment>' . $bundledPhpExtensionRefusal->getMessage() . '</comment>');

            return self::INVALID;
        }

        $this->io->write(sprintf('<info>Found package:</info> %s which provides <info>%s</info>', $package->prettyNameAndVersion(), $package->extensionName()->nameWithExtPrefix()));

        // Now we know what package we have, we can validate the configure options for the command and re-create the
        // Composer instance with the populated configure options
        CommandHelper::bindConfigureOptionsFromPackage($this, $package, $input);
        $configureOptionsValues = CommandHelper::processConfigureOptionsFromInput($package, $input);

        $composer = PieComposerFactory::createPieComposer(
            $this->container,
            new PieComposerRequest(
                $this->io,
                $targetPlatform,
                $requestedNameAndVersion,
                PieOperation::Install,
                $configureOptionsValues,
                CommandHelper::determineAttemptToSetupIniFile($input),
            ),
        );

        try {
            $this->composerIntegrationHandler->runInstall(
                $package,
                $composer,
                $targetPlatform,
                $requestedNameAndVersion,
                $forceInstallPackageVersion,
                true,
            );
        } catch (ComposerRunFailed $composerRunFailed) {
            $this->io->writeError('<error>' . $composerRunFailed->getMessage() . '</error>');

            return $composerRunFailed->getCode();
        }

        return Command::SUCCESS;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Composer\IO\IOInterface;
use Composer\IO\NullIO;
use Php\Pie\ComposerIntegration\PieComposerFactory;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\DependencyResolver\BundledPhpExtensionRefusal;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\DependencyResolver\RequestedPackageAndVersion;
use Php\Pie\DependencyResolver\ResolveDependencyWithComposer;
use Php\Pie\DependencyResolver\UnableToResolveRequirement;
use Php\Pie\Platform as PiePlatform;
use Php\Pie\Platform\InstalledPiePackages;
use Php\Pie\Util\Emoji;
use Php\Pie\Util\PackageVerificationStatus;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
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 Webmozart\Assert\Assert;

use function array_diff;
use function array_map;
use function array_walk;
use function count;
use function rtrim;
use function sprintf;

#[AsCommand(
    name: 'show',
    description: 'List the installed modules and their versions.',
)]
final class ShowCommand extends Command
{
    private const OPTION_ALL = 'all';

    public function __construct(
        private readonly InstalledPiePackages $installedPiePackages,
        private readonly ContainerInterface $container,
        private readonly ResolveDependencyWithComposer $resolveDependencyWithComposer,
        private readonly IOInterface $io,
    ) {
        parent::__construct();
    }

    public function configure(): void
    {
        parent::configure();

        CommandHelper::configurePhpConfigOptions($this);

        $this->addOption(
            self::OPTION_ALL,
            null,
            InputOption::VALUE_NONE,
            'Show all extensions for the target PHP installation, even those PIE does not manage.',
        );
    }

    public function execute(InputInterface $input, OutputInterface $output): int
    {
        $showAll        = $input->hasOption(self::OPTION_ALL) && $input->getOption(self::OPTION_ALL);
        $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $this->io);

        $this->io->write(
            sprintf(
                '<info>Using pie.json:</info> %s',
                PiePlatform::getPieJsonFilename($targetPlatform),
            ),
            verbosity: IOInterface::VERBOSE,
        );

        if (! $showAll) {
            $this->io->write('Tip: to include extensions in this list that PIE does not manage, use the --all flag.');
        }

        CommandHelper::applyNoCacheOptionIfSet($input, $this->io);

        $composer = PieComposerFactory::createPieComposer(
            $this->container,
            PieComposerRequest::noOperation(
                new NullIO(),
                $targetPlatform,
            ),
        );

        $piePackages          = $this->installedPiePackages->allPiePackages($composer);
        $phpEnabledExtensions = $targetPlatform->phpBinaryPath->extensions();
        $piePackagesMatched   = [];
        $rootPackageRequires  = $composer->getPackage()->getRequires();

        $this->io->write(sprintf(
            "\n" . '<options=bold,underscore>%s:</>',
            $showAll ? 'All loaded extensions' : 'Loaded PIE extensions',
        ));
        array_walk(
            $phpEnabledExtensions,
            function (string $version, string $phpExtensionName) use ($composer, $rootPackageRequires, $targetPlatform, $showAll, $piePackages, &$piePackagesMatched): void {
                $pieMatchesForExtension = $piePackages->findByPhpFormattedExtensionName($phpExtensionName);

                if (! count($pieMatchesForExtension)) {
                    if ($showAll) {
                        $this->io->write(sprintf('  <comment>%s:%s</comment>', $phpExtensionName, $version));
                    }

                    return;
                }

                foreach ($pieMatchesForExtension->packages() as $piePackage) {
                    $packageName        = $piePackage->name();
                    $verificationStatus = $piePackage->verifyPackageStatus($targetPlatform);
                    $packageRequirement = $rootPackageRequires[$packageName]->getPrettyConstraint();

                    if ($verificationStatus === PackageVerificationStatus::InstalledBinaryMetadataMissing) {
                        continue;
                    }

                    $piePackagesMatched[] = $packageName;

                    try {
                        // Don't check for updates for bundled PHP extensions
                        if ($piePackage->isBundledPhpExtension()) {
                            throw new BundledPhpExtensionRefusal();
                        }

                        Assert::stringNotEmpty($packageName);
                        Assert::stringNotEmpty($packageRequirement);

                        $latestConstrainedPackage = ($this->resolveDependencyWithComposer)(
                            $composer,
                            $targetPlatform,
                            new RequestedPackageAndVersion($packageName, $packageRequirement),
                            false,
                        );

                        $latestPackage = ($this->resolveDependencyWithComposer)(
                            $composer,
                            $targetPlatform,
                            new RequestedPackageAndVersion($packageName, '*'),
                            false,
                        );
                    } catch (UnableToResolveRequirement | BundledPhpExtensionRefusal) {
                        $latestConstrainedPackage = null;
                        $latestPackage            = null;
                    }

                    $updateNotice = '';
                    if ($latestConstrainedPackage !== null && $latestConstrainedPackage->version() !== $piePackage->version()) {
                        $updateNotice = sprintf(
                            ', upgradable to %s (within %s)',
                            $latestConstrainedPackage->version(),
                            $packageRequirement,
                        );
                    }

                    if ($latestPackage !== null && $latestPackage->version() !== $latestConstrainedPackage->version()) {
                        $updateNotice .= sprintf(', latest version is %s', $latestPackage->version());
                    }

                    $this->io->write(sprintf(
                        '  <info>%s:%s</info> (from 🥧 <info>%s</info> %s)%s',
                        $phpExtensionName,
                        $version,
                        $piePackage->prettyNameAndVersion(),
                        $verificationStatus->description(),
                        $updateNotice,
                    ));
                }
            },
        );

        if (! $showAll && ! count($piePackagesMatched)) {
            $this->io->write('(none)');
        }

        $unmatchedPiePackageNames = array_diff(array_map(static fn (Package $piePackage) => $piePackage->name(), $piePackages->packages()), $piePackagesMatched);

        if (count($unmatchedPiePackageNames)) {
            $this->io->write(sprintf(
                '%s %s <options=bold,underscore>PIE packages not loaded:</>',
                "\n",
                Emoji::WARNING,
            ));
            $this->io->write('These extensions were set up with PIE but are not currently enabled.' . "\n");

            foreach ($unmatchedPiePackageNames as $unmatchedPiePackageName) {
                $unmatchedPiePackage = $piePackages->findByPackageName($unmatchedPiePackageName);

                $message = match ($unmatchedPiePackage->verifyPackageStatus($targetPlatform)) {
                    PackageVerificationStatus::ChecksumMetadataMissing => '- was built but not installed yet.',
                    PackageVerificationStatus::InstalledBinaryMetadataMissing => '- was downloaded but has not been built yet.',
                    default => '- installed but not enabled in INI file',
                };
                $this->io->write(rtrim(sprintf(' - %s %s', $unmatchedPiePackage->prettyNameAndVersion(), $message)));
            }
        }

        return Command::SUCCESS;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\Package\CompletePackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\Repository\ComposerRepository;
use Composer\Repository\PathRepository;
use Composer\Repository\VcsRepository;
use Composer\Util\Platform;
use InvalidArgumentException;
use OutOfRangeException;
use Php\Pie\ComposerIntegration\PieComposerFactory;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\DependencyResolver\InvalidPackageName;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\DependencyResolver\RequestedPackageAndVersion;
use Php\Pie\DependencyResolver\UnableToResolveRequirement;
use Php\Pie\Installing\InstallForPhpProject\FindMatchingPackages;
use Php\Pie\Platform as PiePlatform;
use Php\Pie\Platform\OperatingSystem;
use Php\Pie\Platform\TargetPhp\PhpBinaryPath;
use Php\Pie\Platform\TargetPhp\PhpizePath;
use Php\Pie\Platform\TargetPlatform;
use Psr\Container\ContainerInterface;
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 Throwable;
use Webmozart\Assert\Assert;

use function array_key_exists;
use function array_map;
use function assert;
use function count;
use function is_array;
use function is_string;
use function reset;
use function sprintf;
use function str_starts_with;
use function strtolower;
use function substr;
use function trim;

use const PHP_VERSION;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class CommandHelper
{
    public const ARG_REQUESTED_PACKAGE_AND_VERSION            = 'requested-package-and-version';
    public const OPTION_WITH_PHP_CONFIG                       = 'with-php-config';
    public const OPTION_WITH_PHP_PATH                         = 'with-php-path';
    public const OPTION_WITH_PHPIZE_PATH                      = 'with-phpize-path';
    public const OPTION_WORKING_DIRECTORY                     = 'working-dir';
    public const OPTION_ALLOW_NON_INTERACTIVE_PROJECT_INSTALL = 'allow-non-interactive-project-install';
    private const OPTION_MAKE_PARALLEL_JOBS                   = 'make-parallel-jobs';
    private const OPTION_SKIP_ENABLE_EXTENSION                = 'skip-enable-extension';
    private const OPTION_FORCE                                = 'force';
    private const OPTION_NO_CACHE                             = 'no-cache';
    private const OPTION_AUTO_INSTALL_BUILD_TOOLS             = 'auto-install-build-tools';
    private const OPTION_SUPPRESS_BUILD_TOOLS_CHECK           = 'no-build-tools-check';
    private const OPTION_AUTO_INSTALL_SYSTEM_DEPENDENCIES     = 'auto-install-system-dependencies';
    private const OPTION_SUPPRESS_SYSTEM_DEPENDENCIES_CHECK   = 'no-system-dependencies-check';

    private function __construct()
    {
    }

    public static function configurePhpConfigOptions(Command $command): void
    {
        $command->addOption(
            self::OPTION_WITH_PHP_CONFIG,
            null,
            InputOption::VALUE_REQUIRED,
            'The path to the `php-config` binary to find the target PHP platform on ' . OperatingSystem::NonWindows->asFriendlyName() . ', e.g. --' . self::OPTION_WITH_PHP_CONFIG . '=/usr/bin/php-config7.4',
        );
        $command->addOption(
            self::OPTION_WITH_PHP_PATH,
            null,
            InputOption::VALUE_REQUIRED,
            'The path to the `php` binary to use as the target PHP platform on ' . OperatingSystem::Windows->asFriendlyName() . ', e.g. --' . self::OPTION_WITH_PHP_PATH . '=C:\usr\php7.4.33\php.exe',
        );
        $command->addOption(
            self::OPTION_WITH_PHPIZE_PATH,
            null,
            InputOption::VALUE_REQUIRED,
            'The path to the `phpize` binary to use as the target PHP platform, e.g. --' . self::OPTION_WITH_PHPIZE_PATH . '=/usr/bin/phpize7.4',
        );
        $command->addOption(
            self::OPTION_NO_CACHE,
            null,
            InputOption::VALUE_NONE,
            'Prevent the use of the Composer cache.',
        );
    }

    public static function configureDownloadBuildInstallOptions(Command $command, bool $withRequestedPackageAndVersion = true): void
    {
        if ($withRequestedPackageAndVersion) {
            $command->addArgument(
                self::ARG_REQUESTED_PACKAGE_AND_VERSION,
                InputArgument::OPTIONAL,
                'The PIE package name and version constraint to use, in the format {vendor/package}{?:{?version-constraint}{?@stability}}, for example `xdebug/xdebug:^3.4@alpha`, `xdebug/xdebug:@alpha`, `xdebug/xdebug:^3.4`, etc.',
            );
        }

        $command->addOption(
            self::OPTION_MAKE_PARALLEL_JOBS,
            'j',
            InputOption::VALUE_REQUIRED,
            'Override many jobs to run in parallel when running compiling (this is passed to "make -jN" during build). PIE will try to detect this by default.',
        );
        $command->addOption(
            self::OPTION_SKIP_ENABLE_EXTENSION,
            null,
            InputOption::VALUE_NONE,
            'Specify this to skip attempting to enable the extension in php.ini',
        );
        $command->addOption(
            self::OPTION_FORCE,
            null,
            InputOption::VALUE_NONE,
            'To attempt to install a version that doesn\'t match the version constraints from the meta-data, for instance to install an older version than recommended, or when the signature is not available.',
        );

        $command->addOption(
            self::OPTION_WORKING_DIRECTORY,
            'd',
            InputOption::VALUE_REQUIRED,
            'The working directory to use, where applicable. If not specified, the current working directory is used. Only used in certain contexts.',
        );

        self::configurePhpConfigOptions($command);

        $command->addOption(
            self::OPTION_ALLOW_NON_INTERACTIVE_PROJECT_INSTALL,
            null,
            InputOption::VALUE_NONE,
            'When installing a PHP project, allow non-interactive project installations. Only used in certain contexts.',
        );

        $command->addOption(
            self::OPTION_AUTO_INSTALL_BUILD_TOOLS,
            null,
            InputOption::VALUE_NONE,
            'If build tools are missing, automatically install them, instead of prompting.',
        );
        $command->addOption(
            self::OPTION_SUPPRESS_BUILD_TOOLS_CHECK,
            null,
            InputOption::VALUE_NONE,
            'Do not perform the check to see if build tools are present on the system.',
        );

        $command->addOption(
            self::OPTION_AUTO_INSTALL_SYSTEM_DEPENDENCIES,
            null,
            InputOption::VALUE_NONE,
            'If system dependencies missing, automatically install them, instead of prompting.',
        );
        $command->addOption(
            self::OPTION_SUPPRESS_SYSTEM_DEPENDENCIES_CHECK,
            null,
            InputOption::VALUE_NONE,
            'Do not perform the check to see if system dependencies are present on the system.',
        );

        /**
         * Allows additional options for the `./configure` command to be passed here.
         * Note, this means you probably need to call {@see self::validateInput()} to validate the input manually...
         */
        $command->ignoreValidationErrors();
    }

    public static function validateInput(InputInterface $input, Command $command): void
    {
        $input->bind($command->getDefinition());
    }

    public static function determineTargetPlatformFromInputs(InputInterface $input, IOInterface $io): TargetPlatform
    {
        $phpBinaryPath = PhpBinaryPath::fromCurrentProcess();

        /** @var mixed $withPhpConfig */
        $withPhpConfig          = $input->getOption(self::OPTION_WITH_PHP_CONFIG);
        $specifiedWithPhpConfig = is_string($withPhpConfig) && $withPhpConfig !== '';
        /** @var mixed $withPhpPath */
        $withPhpPath          = $input->getOption(self::OPTION_WITH_PHP_PATH);
        $specifiedWithPhpPath = is_string($withPhpPath) && $withPhpPath !== '';

        if (Platform::isWindows() && $specifiedWithPhpConfig) {
            throw new InvalidArgumentException('The --with-php-config=/path/to/php-config cannot be used on Windows, use --with-php-path=/path/to/php instead.');
        }

        if (! Platform::isWindows() && $specifiedWithPhpPath && ! $specifiedWithPhpConfig) {
            throw new InvalidArgumentException('The --with-php-path=/path/to/php cannot be used on non-Windows, use --with-php-config=/path/to/php-config instead.');
        }

        if ($specifiedWithPhpConfig) {
            $phpBinaryPath = PhpBinaryPath::fromPhpConfigExecutable($withPhpConfig);
        }

        if ($specifiedWithPhpPath) {
            $phpBinaryPath = PhpBinaryPath::fromPhpBinaryPath($withPhpPath);
        }

        $makeParallelJobs = null; /** `null` means {@see TargetPlatform} will try to auto-detect */
        if ($input->hasOption(self::OPTION_MAKE_PARALLEL_JOBS)) {
            $makeParallelJobsOptions = (int) $input->getOption(self::OPTION_MAKE_PARALLEL_JOBS);
            if ($makeParallelJobsOptions > 0) {
                $makeParallelJobs = $makeParallelJobsOptions;
            }
        }

        $phpizePath = null;
        if ($input->hasOption(self::OPTION_WITH_PHPIZE_PATH)) {
            $phpizePathOption = $input->getOption(self::OPTION_WITH_PHPIZE_PATH);
            if (is_string($phpizePathOption) && trim($phpizePathOption) !== '') {
                if (Platform::isWindows()) {
                    throw new InvalidArgumentException('The --with-phpize-path=/path/to/phpize cannot be used on Windows.');
                }

                $phpizePath = new PhpizePath($phpizePathOption);
            }
        }

        $targetPlatform = TargetPlatform::fromPhpBinaryPath($phpBinaryPath, $makeParallelJobs, $phpizePath);

        if (PiePlatform::isRunningStaticPhp()) {
            $io->write(sprintf('<info>You are running a PIE Static PHP %s build</info>', PHP_VERSION));
        } else {
            $io->write(sprintf('<info>You are running PHP %s</info>', PHP_VERSION));
        }

        $io->write(sprintf(
            '<info>Target PHP installation:</info> %s %s%s, on %s %s (from %s)',
            $phpBinaryPath->version(),
            $targetPlatform->threadSafety->asShort(),
            strtolower($targetPlatform->windowsCompiler !== null ? ', ' . $targetPlatform->windowsCompiler->name : ''),
            $targetPlatform->operatingSystem->asFriendlyName(),
            $targetPlatform->architecture->name,
            $phpBinaryPath->phpBinaryPath,
        ));
        $io->write(
            sprintf(
                '<info>Using pie.json:</info> %s',
                PiePlatform::getPieJsonFilename($targetPlatform),
            ),
            verbosity: IOInterface::VERBOSE,
        );

        return $targetPlatform;
    }

    public static function determineAttemptToSetupIniFile(InputInterface $input): bool
    {
        return ! $input->hasOption(self::OPTION_SKIP_ENABLE_EXTENSION) || ! $input->getOption(self::OPTION_SKIP_ENABLE_EXTENSION);
    }

    public static function determineForceInstallingPackageVersion(InputInterface $input): bool
    {
        return $input->hasOption(self::OPTION_FORCE) && $input->getOption(self::OPTION_FORCE);
    }

    public static function autoInstallBuildTools(InputInterface $input): bool
    {
        return $input->hasOption(self::OPTION_AUTO_INSTALL_BUILD_TOOLS)
            && $input->getOption(self::OPTION_AUTO_INSTALL_BUILD_TOOLS);
    }

    public static function shouldCheckForBuildTools(InputInterface $input): bool
    {
        if (Platform::isWindows()) {
            return false;
        }

        return ! $input->hasOption(self::OPTION_SUPPRESS_BUILD_TOOLS_CHECK)
            || ! $input->getOption(self::OPTION_SUPPRESS_BUILD_TOOLS_CHECK);
    }

    public static function autoInstallSystemDependencies(InputInterface $input): bool
    {
        return $input->hasOption(self::OPTION_AUTO_INSTALL_SYSTEM_DEPENDENCIES)
            && $input->getOption(self::OPTION_AUTO_INSTALL_SYSTEM_DEPENDENCIES);
    }

    public static function shouldCheckSystemDependencies(InputInterface $input): bool
    {
        if (Platform::isWindows()) {
            return false;
        }

        return ! $input->hasOption(self::OPTION_SUPPRESS_SYSTEM_DEPENDENCIES_CHECK)
            || ! $input->getOption(self::OPTION_SUPPRESS_SYSTEM_DEPENDENCIES_CHECK);
    }

    public static function requestedNameAndVersionPair(InputInterface $input): RequestedPackageAndVersion
    {
        $requestedPackageString = $input->getArgument(self::ARG_REQUESTED_PACKAGE_AND_VERSION);

        if (! is_string($requestedPackageString) || $requestedPackageString === '') {
            throw new InvalidArgumentException('No package was requested for installation');
        }

        $nameAndVersionPairs         = (new VersionParser())
            ->parseNameVersionPairs([$requestedPackageString]);
        $requestedNameAndVersionPair = reset($nameAndVersionPairs);

        if (! is_array($requestedNameAndVersionPair)) {
            throw new InvalidArgumentException('Failed to parse the name/version pair');
        }

        if (! array_key_exists('version', $requestedNameAndVersionPair)) {
            $requestedNameAndVersionPair['version'] = null;
        }

        Assert::stringNotEmpty($requestedNameAndVersionPair['name']);
        Assert::nullOrStringNotEmpty($requestedNameAndVersionPair['version']);

        return new RequestedPackageAndVersion(
            $requestedNameAndVersionPair['name'],
            $requestedNameAndVersionPair['version'],
        );
    }

    public static function bindConfigureOptionsFromPackage(Command $command, Package $package, InputInterface $input): void
    {
        foreach ($package->configureOptions() as $configureOption) {
            $command->addOption(
                $configureOption->name,
                null,
                $configureOption->needsValue ? InputOption::VALUE_REQUIRED : InputOption::VALUE_NONE,
                $configureOption->description,
            );
        }

        self::validateInput($input, $command);
    }

    /** @return list<non-empty-string> */
    public static function processConfigureOptionsFromInput(Package $package, InputInterface $input): array
    {
        $configureOptionsValues = [];
        foreach ($package->configureOptions() as $configureOption) {
            if (! $input->hasOption($configureOption->name)) {
                continue;
            }

            $value = $input->getOption($configureOption->name);

            if ($configureOption->needsValue) {
                if (is_string($value) && $value !== '') {
                    $configureOptionsValues[] = '--' . $configureOption->name . '=' . $value;
                }

                continue;
            }

            Assert::boolean($value);
            if ($value !== true) {
                continue;
            }

            $configureOptionsValues[] = '--' . $configureOption->name;
        }

        return $configureOptionsValues;
    }

    public static function listRepositories(Composer $composer, IOInterface $io): void
    {
        $io->write('The following repositories are in use for this Target PHP:');

        foreach ($composer->getRepositoryManager()->getRepositories() as $repo) {
            if ($repo instanceof ComposerRepository) {
                $repoConfig = $repo->getRepoConfig();

                $repoUrl = array_key_exists('url', $repoConfig) && is_string($repoConfig['url']) && $repoConfig['url'] !== '' ? $repoConfig['url'] : null;

                if ($repoUrl === 'https://repo.packagist.org') {
                    $io->write('  - Packagist');
                    continue;
                }

                $io->write(sprintf('  - Composer (%s)', $repoUrl ?? 'no url?'));
                continue;
            }

            if ($repo instanceof VcsRepository) {
                $io->write(sprintf(
                    '  - VCS Repository (%s)',
                    $repo->getDriver()?->getUrl() ?? 'no url?',
                ));
                continue;
            }

            if (! $repo instanceof PathRepository) {
                continue;
            }

            $repoConfig = $repo->getRepoConfig();
            $io->write(sprintf(
                '  - Path Repository (%s)',
                array_key_exists('url', $repoConfig) && is_string($repoConfig['url']) && $repoConfig['url'] !== '' ? $repoConfig['url'] : 'no path?',
            ));
        }
    }

    public static function handlePackageNotFound(
        InvalidPackageName|UnableToResolveRequirement $exception,
        FindMatchingPackages $findMatchingPackages,
        IOInterface $io,
        TargetPlatform $targetPlatform,
        ContainerInterface $container,
    ): int {
        $pieComposer = PieComposerFactory::createPieComposer(
            $container,
            PieComposerRequest::noOperation(
                $io,
                $targetPlatform,
            ),
        );

        $requestedPackageName = $exception->requestedPackageAndVersion->package;
        if (str_starts_with($requestedPackageName, 'ext-')) {
            $requestedPackageName = substr($requestedPackageName, 4);
        }

        assert($requestedPackageName !== '');

        $io->writeError('');
        $io->writeError(sprintf('<error>Could not install package: %s</error>', $requestedPackageName));
        $io->writeError($exception->getMessage());

        try {
            $matches = array_map(
                static function (array $match) use ($io, $pieComposer): array {
                    $composerMatchingPackage = $pieComposer->getRepositoryManager()->findPackage($match['name'], '*');

                    // Attempts to augment the Composer packages found with the PIE extension name
                    if ($composerMatchingPackage instanceof CompletePackageInterface) {
                        try {
                            $match['extension-name'] = Package
                                ::fromComposerCompletePackage($composerMatchingPackage)
                                ->extensionName()
                                ->name();
                        } catch (Throwable $t) {
                            $io->writeError(
                                sprintf(
                                    'Tried looking up extension name for %s, but failed: %s',
                                    $match['name'],
                                    $t->getMessage(),
                                ),
                                verbosity: IOInterface::VERY_VERBOSE,
                            );
                        }
                    }

                    return $match;
                },
                $findMatchingPackages->bySearching($pieComposer, $requestedPackageName),
            );

            if (count($matches)) {
                $io->write('');
                if (count($matches) === 1) {
                    $io->write('<info>Did you mean this?</info>');
                } else {
                    $io->write('<info>Did you mean one of these?</info>');
                }

                array_map(
                    static function (array $match) use ($io): void {
                        $io->write(sprintf(
                            ' - %s%s: %s',
                            $match['name'],
                            array_key_exists('extension-name', $match) && is_string($match['extension-name'])
                                ? ' (provides extension: ' . $match['extension-name'] . ')'
                                : '',
                            $match['description'] ?? 'no description available',
                        ));
                    },
                    $matches,
                );
            }
        } catch (OutOfRangeException) {
            $io->writeError(
                sprintf(
                    'Tried searching for "%s", but nothing was found.',
                    $requestedPackageName,
                ),
                verbosity: IOInterface::VERBOSE,
            );
        }

        return 1;
    }

    public static function applyNoCacheOptionIfSet(InputInterface $input, IOInterface $io): void
    {
        if (! $input->hasOption(self::OPTION_NO_CACHE) || ! $input->getOption(self::OPTION_NO_CACHE)) {
            return;
        }

        $io->writeError('Disabling cache usage', verbosity: IOInterface::DEBUG);
        Platform::putEnv('COMPOSER_CACHE_DIR', Platform::isWindows() ? 'nul' : '/dev/null');
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Composer\IO\IOInterface;
use Composer\IO\NullIO;
use Php\Pie\ComposerIntegration\PieComposerFactory;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\ComposerIntegration\QuieterConsoleIO;
use Php\Pie\File\BinaryFile;
use Php\Pie\File\FullPathToSelf;
use Php\Pie\SelfManage\Update\FetchPieReleaseFromGitHub;
use Php\Pie\SelfManage\Update\ReleaseMetadata;
use Php\Pie\SelfManage\Verify\FailedToVerifyRelease;
use Php\Pie\SelfManage\Verify\VerifyPieReleaseUsingAttestation;
use Php\Pie\Util\Emoji;
use Php\Pie\Util\PieVersion;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

use function sprintf;

#[AsCommand(
    name: 'self-verify',
    description: 'Self verify PIE',
)]
final class SelfVerifyCommand extends Command
{
    private const ARGUMENT_VERSION = 'version';

    /** @param non-empty-string $githubApiBaseUrl */
    public function __construct(
        private readonly string $githubApiBaseUrl,
        private readonly FullPathToSelf $fullPathToSelf,
        private readonly IOInterface $io,
        private readonly QuieterConsoleIO $quieterConsoleIo,
        private readonly ContainerInterface $container,
    ) {
        parent::__construct();
    }

    public function configure(): void
    {
        parent::configure();

        CommandHelper::configurePhpConfigOptions($this);
        $this->addArgument(
            self::ARGUMENT_VERSION,
            InputArgument::OPTIONAL,
            'The version of PIE you expect to be running (e.g. 1.4.4 or nightly)',
        );
    }

    public function execute(InputInterface $input, OutputInterface $output): int
    {
        if (! PieVersion::isPharBuild()) {
            $this->io->writeError('<comment>Aborting! You are not running a PHAR, cannot self-verify.</comment>');

            return Command::FAILURE;
        }

        $expectedVersion = (string) $input->getArgument(self::ARGUMENT_VERSION);

        if ($expectedVersion === '') {
            $expectedVersion = PieVersion::get();
            $this->io->write(sprintf('<comment>No version specified, verifying against the version this PHAR claims to be (%s).</comment>', $expectedVersion));
        }

        $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $this->io);

        CommandHelper::applyNoCacheOptionIfSet($input, $this->io);

        $composer = PieComposerFactory::createPieComposer(
            $this->container,
            PieComposerRequest::noOperation(
                new NullIO(),
                $targetPlatform,
            ),
        );

        $fetchLatestPieRelease = FetchPieReleaseFromGitHub::factory(
            $this->quieterConsoleIo,
            $composer->getConfig(),
            $this->githubApiBaseUrl,
        );

        $latestRelease = new ReleaseMetadata($expectedVersion, 'blah');
        $pharFilename  = BinaryFile::fromFileWithSha256Checksum(($this->fullPathToSelf)());
        $verifyPiePhar = VerifyPieReleaseUsingAttestation::factory($fetchLatestPieRelease);

        try {
            $verifyPiePhar->verify($latestRelease, $pharFilename, $this->io);
        } catch (FailedToVerifyRelease $failedToVerifyRelease) {
            $this->io->writeError(sprintf(
                '<error>❌ Failed to verify that this PIE binary is the authentic release %s: %s</error>',
                $latestRelease->tag,
                $failedToVerifyRelease->getMessage(),
            ));

            return Command::FAILURE;
        }

        $this->io->write(sprintf(
            '<info>%s This is an authentic PIE release for version %s.</info>',
            Emoji::GREEN_CHECKMARK,
            $latestRelease->tag,
        ));

        return Command::SUCCESS;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\IO\NullIO;
use Php\Pie\ComposerIntegration\ComposerIntegrationHandler;
use Php\Pie\ComposerIntegration\PieComposerFactory;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\ComposerIntegration\PieOperation;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\DependencyResolver\RequestedPackageAndVersion;
use Php\Pie\Platform\InstalledPiePackages;
use Php\Pie\Platform\TargetPlatform;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Webmozart\Assert\Assert;

#[AsCommand(
    name: 'uninstall',
    description: 'Disable and remove an extension that has been installed with PIE',
)]
final class UninstallCommand extends Command
{
    private const ARG_PACKAGE_NAME = 'package-name';

    public function __construct(
        private readonly InstalledPiePackages $installedPiePackages,
        private readonly ContainerInterface $container,
        private readonly ComposerIntegrationHandler $composerIntegrationHandler,
        private readonly IOInterface $io,
    ) {
        parent::__construct();
    }

    public function configure(): void
    {
        parent::configure();

        $this->addArgument(
            self::ARG_PACKAGE_NAME,
            InputArgument::REQUIRED,
            'The package name to remove, in the format {vendor/package}, for example `xdebug/xdebug`',
        );

        CommandHelper::configurePhpConfigOptions($this);
    }

    public function execute(InputInterface $input, OutputInterface $output): int
    {
        if (! TargetPlatform::isRunningAsRoot()) {
            $this->io->write('This command may need elevated privileges, and may prompt you for your password.');
        }

        $packageToRemove = (string) $input->getArgument(self::ARG_PACKAGE_NAME);
        Assert::stringNotEmpty($packageToRemove);
        $requestedPackageAndVersionToRemove = new RequestedPackageAndVersion($packageToRemove, null);

        $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $this->io);

        CommandHelper::applyNoCacheOptionIfSet($input, $this->io);

        $composer = PieComposerFactory::createPieComposer(
            $this->container,
            PieComposerRequest::noOperation(
                new NullIO(),
                $targetPlatform,
            ),
        );

        $piePackage = $this->findPiePackageByPackageName($packageToRemove, $composer);

        if ($piePackage === null) {
            $this->io->writeError('<error>No package found: ' . $packageToRemove . '</error>');

            return 1;
        }

        $composer = PieComposerFactory::createPieComposer(
            $this->container,
            new PieComposerRequest(
                $this->io,
                $targetPlatform,
                $requestedPackageAndVersionToRemove,
                PieOperation::Uninstall,
                [], // Configure options are not needed for uninstall
                true,
            ),
        );

        $this->composerIntegrationHandler->runUninstall(
            $piePackage,
            $composer,
            $targetPlatform,
            $requestedPackageAndVersionToRemove,
        );

        return 0;
    }

    private function findPiePackageByPackageName(string $packageToRemove, Composer $composer): Package|null
    {
        $piePackages = $this->installedPiePackages->allPiePackages($composer);

        foreach ($piePackages->packages() as $piePackage) {
            if ($piePackage->name() === $packageToRemove) {
                return $piePackage;
            }
        }

        return null;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Composer\IO\IOInterface;
use Composer\IO\NullIO;
use Php\Pie\ComposerIntegration\PieComposerFactory;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\ComposerIntegration\QuieterConsoleIO;
use Php\Pie\File\FullPathToSelf;
use Php\Pie\File\SudoFilePut;
use Php\Pie\Platform;
use Php\Pie\SelfManage\Update\Channel;
use Php\Pie\SelfManage\Update\FetchPieReleaseFromGitHub;
use Php\Pie\SelfManage\Update\ReleaseIsNewer;
use Php\Pie\SelfManage\Update\ReleaseMetadata;
use Php\Pie\SelfManage\Verify\FailedToVerifyRelease;
use Php\Pie\SelfManage\Verify\VerifyPieReleaseUsingAttestation;
use Php\Pie\Settings;
use Php\Pie\Util\Emoji;
use Php\Pie\Util\PieVersion;
use Psr\Container\ContainerInterface;
use Safe\Exceptions\FilesystemException;
use Symfony\Component\Console\Attribute\AsCommand;
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 Throwable;

use function Safe\file_get_contents;
use function Safe\unlink;
use function sprintf;

#[AsCommand(
    name: 'self-update',
    description: 'Self update PIE',
)]
final class SelfUpdateCommand extends Command
{
    private const OPTION_STABLE_UPDATE  = 'stable';
    private const OPTION_PREVIEW_UPDATE = 'preview';
    private const OPTION_NIGHTLY_UPDATE = 'nightly';

    /** @param non-empty-string $githubApiBaseUrl */
    public function __construct(
        private readonly string $githubApiBaseUrl,
        private readonly IOInterface $io,
        private readonly QuieterConsoleIO $quieterConsoleIo,
        private readonly ContainerInterface $container,
        private readonly FullPathToSelf $fullPathToSelf,
    ) {
        parent::__construct();
    }

    public function configure(): void
    {
        parent::configure();

        CommandHelper::configurePhpConfigOptions($this);
        $this->addOption(
            self::OPTION_NIGHTLY_UPDATE,
            null,
            InputOption::VALUE_NONE,
            'Update to the latest nightly version.',
        );
        $this->addOption(
            self::OPTION_PREVIEW_UPDATE,
            null,
            InputOption::VALUE_NONE,
            'Update to the latest preview version.',
        );
        $this->addOption(
            self::OPTION_STABLE_UPDATE,
            null,
            InputOption::VALUE_NONE,
            'Update to the latest stable version.',
        );
    }

    public function execute(InputInterface $input, OutputInterface $output): int
    {
        if (! PieVersion::isPharBuild() || Platform::isRunningStaticPhp()) {
            $this->io->writeError('<comment>Aborting! You are not running a PHAR, cannot self-update.</comment>');

            return Command::FAILURE;
        }

        $settings      = new Settings(Platform::getPieBaseWorkingDirectory());
        $updateChannel = $settings->updateChannel();

        if ($input->hasOption(self::OPTION_NIGHTLY_UPDATE) && $input->getOption(self::OPTION_NIGHTLY_UPDATE)) {
            $settings->changeUpdateChannel(Channel::Nightly);
            $updateChannel = Channel::Nightly;
        } elseif ($input->hasOption(self::OPTION_PREVIEW_UPDATE) && $input->getOption(self::OPTION_PREVIEW_UPDATE)) {
            $settings->changeUpdateChannel(Channel::Preview);
            $updateChannel = Channel::Preview;
        } elseif ($input->hasOption(self::OPTION_STABLE_UPDATE) && $input->getOption(self::OPTION_STABLE_UPDATE)) {
            $settings->changeUpdateChannel(Channel::Stable);
            $updateChannel = Channel::Stable;
        }

        $this->io->write(sprintf('Updating using the <info>%s</> channel.', $updateChannel->value));

        $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $this->io);

        CommandHelper::applyNoCacheOptionIfSet($input, $this->io);

        $composer = PieComposerFactory::createPieComposer(
            $this->container,
            PieComposerRequest::noOperation(
                new NullIO(),
                $targetPlatform,
            ),
        );

        $fetchLatestPieRelease = FetchPieReleaseFromGitHub::factory(
            $this->quieterConsoleIo,
            $composer->getConfig(),
            $this->githubApiBaseUrl,
        );
        $verifyPiePhar         = VerifyPieReleaseUsingAttestation::factory($fetchLatestPieRelease);

        if ($updateChannel === Channel::Nightly) {
            $latestRelease = new ReleaseMetadata(
                'nightly',
                'https://php.github.io/pie/pie-nightly.phar',
            );

            $this->io->write('Downloading the latest nightly release.');
        } else {
            try {
                $latestRelease = $fetchLatestPieRelease->latestReleaseMetadata($updateChannel);
            } catch (Throwable $throwable) {
                $this->io->writeError(sprintf('<error>%s</error>', $throwable->getMessage()));

                return Command::FAILURE;
            }

            $pieVersion = PieVersion::get();

            $this->io->write(sprintf('You are currently running PIE version %s', $pieVersion));

            if (! ReleaseIsNewer::forChannel($updateChannel, $pieVersion, $latestRelease)) {
                $this->io->write(sprintf(
                    '<info>You already have the latest version for the %s channel 😍</info>',
                    $updateChannel->value,
                ));

                return Command::SUCCESS;
            }

            $this->io->write(
                sprintf('Newer version %s found, going to update you... ⏳', $latestRelease->tag),
                verbosity: IOInterface::VERBOSE,
            );
        }

        $pharFilename = $fetchLatestPieRelease->downloadContent($latestRelease);

        $this->io->write(
            sprintf('Verifying release with digest sha256:%s...', $pharFilename->checksum),
            verbosity: IOInterface::VERBOSE,
        );

        try {
            $verifyPiePhar->verify($latestRelease, $pharFilename, $this->io);
        } catch (FailedToVerifyRelease $failedToVerifyRelease) {
            $this->io->writeError(sprintf(
                '<error>❌ Failed to verify the pie.phar release %s: %s</error>',
                $latestRelease->tag,
                $failedToVerifyRelease->getMessage(),
            ));

            $this->io->writeError('This means I could not verify that the PHAR we tried to update to was authentic, so I am aborting the self-update.');
            unlink($pharFilename->filePath);

            return Command::FAILURE;
        }

        try {
            $pharContents = file_get_contents($pharFilename->filePath);
        } catch (FilesystemException) {
            $this->io->writeError(sprintf('<error>%s Failed to read the downloaded PHAR file %s</error>', Emoji::CROSS, $pharFilename->filePath));
            unlink($pharFilename->filePath);

            return Command::FAILURE;
        }

        try {
            $pharFilename->verifyContent($pharContents);
        } catch (Throwable) {
            $this->io->writeError(sprintf('<error>%s PHAR contents changed after verification; aborting self-update</error>', Emoji::CROSS));
            unlink($pharFilename->filePath);

            return Command::FAILURE;
        }

        $fullPathToSelf = ($this->fullPathToSelf)();
        $this->io->write(
            sprintf('Writing new version to %s', $fullPathToSelf),
            verbosity: IOInterface::VERBOSE,
        );
        SudoFilePut::contents($fullPathToSelf, $pharContents);
        unlink($pharFilename->filePath);

        $this->io->write(sprintf(
            '<info>%s PIE has been upgraded to %s</info>',
            Emoji::GREEN_CHECKMARK,
            $latestRelease->tag,
        ));

        $this->exitSuccessfully();
    }

    /**
     * Exit is needed at the moment, as we have an EventDispatcher set on
     * the application, which means classes try to get loaded (such as
     * {@see \Symfony\Component\Console\Event\ConsoleTerminateEvent}), but
     * AFTER we've over-written the PHAR. This results in weird behaviour when
     * the class tries to get loaded, as the PHAR content changed. Not an
     * ideal approach, need to look into better ways of handling it, maybe
     * adding an event listener to overwrite the PHAR *after* the command runs.
     */
    private function exitSuccessfully(): never
    {
        exit(0);
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie;

use Composer\Factory;
use Composer\IO\BufferIO;
use Composer\IO\ConsoleIO;
use Composer\IO\IOInterface;
use Composer\Util\Platform as ComposerPlatform;
use Illuminate\Container\Container as IlluminateContainer;
use Php\Pie\Building\Build;
use Php\Pie\Building\UnixBuild;
use Php\Pie\Building\WindowsBuild;
use Php\Pie\Command\ArgvInput;
use Php\Pie\Command\BuildCommand;
use Php\Pie\Command\DownloadCommand;
use Php\Pie\Command\InfoCommand;
use Php\Pie\Command\InstallCommand;
use Php\Pie\Command\InstallExtensionsForProjectCommand;
use Php\Pie\Command\RepositoryAddCommand;
use Php\Pie\Command\RepositoryListCommand;
use Php\Pie\Command\RepositoryRemoveCommand;
use Php\Pie\Command\SelfUpdateCommand;
use Php\Pie\Command\SelfVerifyCommand;
use Php\Pie\Command\ShowCommand;
use Php\Pie\Command\UninstallCommand;
use Php\Pie\ComposerIntegration\MinimalHelperSet;
use Php\Pie\ComposerIntegration\QuieterConsoleIO;
use Php\Pie\DependencyResolver\DependencyInstaller\SystemDependenciesDefinition;
use Php\Pie\DependencyResolver\DependencyResolver;
use Php\Pie\DependencyResolver\ResolveDependencyWithComposer;
use Php\Pie\Downloading\GithubPackageReleaseAssets;
use Php\Pie\Downloading\PackageReleaseAssets;
use Php\Pie\File\FullPathToSelf;
use Php\Pie\Installing\Ini;
use Php\Pie\Installing\Install;
use Php\Pie\Installing\Uninstall;
use Php\Pie\Installing\UninstallUsingUnlink;
use Php\Pie\Installing\UnixInstall;
use Php\Pie\Installing\WindowsInstall;
use Php\Pie\Platform\PackageManager;
use Php\Pie\SelfManage\BuildTools\CheckAllBuildTools;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;

use function assert;
use function Safe\getcwd;
use function str_starts_with;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class Container
{
    private static BufferIO|null $testBuffer = null;

    public static function factory(): ContainerInterface
    {
        $container = new IlluminateContainer();
        $container->instance(ContainerInterface::class, $container);
        $container->singleton(
            InputInterface::class,
            static function () {
                $input = new ArgvInput();

                if (! Platform::isInteractive()) {
                    $input->setInteractive(false);
                }

                return $input;
            },
        );
        $container->singleton(
            OutputInterface::class,
            static function (): OutputInterface {
                return new ConsoleOutput(
                    ConsoleOutput::VERBOSITY_NORMAL,
                    null,
                    new OutputFormatter(false, Factory::createAdditionalStyles()),
                );
            },
        );
        $container->singleton(EventDispatcher::class, static function (ContainerInterface $container) {
            $io = $container->get(IOInterface::class);

            $displayedBanner = false;
            $eventDispatcher = new EventDispatcher();
            $eventDispatcher->addListener(
                ConsoleEvents::COMMAND,
                static function (ConsoleCommandEvent $event) use (&$displayedBanner, $io): void {
                    $command     = $event->getCommand();
                    $application = $command?->getApplication();

                    if ($displayedBanner || $command === null || ! str_starts_with($command::class, 'Php\Pie\Command') || $application === null) {
                        return;
                    }

                    $displayedBanner = true;
                    $io->write($application->getLongVersion() . ', from The PHP Foundation');
                },
            );

            return $eventDispatcher;
        });

        $container->singleton(DownloadCommand::class);
        $container->singleton(BuildCommand::class);
        $container->singleton(InstallCommand::class);
        $container->singleton(InfoCommand::class);
        $container->singleton(ShowCommand::class);
        $container->singleton(RepositoryListCommand::class);
        $container->singleton(RepositoryAddCommand::class);
        $container->singleton(RepositoryRemoveCommand::class);
        $container->singleton(UninstallCommand::class);
        $container->singleton(SelfUpdateCommand::class);
        $container->singleton(SelfVerifyCommand::class);
        $container->singleton(InstallExtensionsForProjectCommand::class);

        $container->singleton(IOInterface::class, static function (ContainerInterface $container): IOInterface {
            return new ConsoleIO(
                $container->get(InputInterface::class),
                $container->get(OutputInterface::class),
                new MinimalHelperSet(
                    [
                        'question' => new QuestionHelper(),
                    ],
                ),
            );
        });
        $container->singleton(QuieterConsoleIO::class, static function (ContainerInterface $container): QuieterConsoleIO {
            return new QuieterConsoleIO(
                $container->get(InputInterface::class),
                $container->get(OutputInterface::class),
                new MinimalHelperSet(
                    [
                        'question' => new QuestionHelper(),
                    ],
                ),
            );
        });

        $container->alias(ResolveDependencyWithComposer::class, DependencyResolver::class);

        $container->alias(GithubPackageReleaseAssets::class, PackageReleaseAssets::class);
        $container->when(GithubPackageReleaseAssets::class)
            ->needs('$githubApiBaseUrl')
            ->give('https://api.github.com');

        $container->when(SelfUpdateCommand::class)
            ->needs('$githubApiBaseUrl')
            ->give('https://api.github.com');

        $container->when(SelfVerifyCommand::class)
            ->needs('$githubApiBaseUrl')
            ->give('https://api.github.com');

        $container->when(FullPathToSelf::class)
            ->needs('$originalCwd')
            ->give(getcwd());

        $container->singleton(
            Build::class,
            static function (ContainerInterface $container): Build {
                if (ComposerPlatform::isWindows()) {
                    return $container->get(WindowsBuild::class);
                }

                return $container->get(UnixBuild::class);
            },
        );

        $container->singleton(
            Ini\SetupIniApproach::class,
            static function (ContainerInterface $container): Ini\SetupIniApproach {
                return new Ini\PickBestSetupIniApproach([
                    $container->get(Ini\PreCheckExtensionAlreadyLoaded::class),
                    $container->get(Ini\OndrejPhpenmod::class),
                    $container->get(Ini\DockerPhpExtEnable::class),
                    $container->get(Ini\StandardAdditionalPhpIniDirectory::class),
                    $container->get(Ini\StandardSinglePhpIni::class),
                ]);
            },
        );

        $container->singleton(
            Install::class,
            static function (ContainerInterface $container): Install {
                if (ComposerPlatform::isWindows()) {
                    return $container->get(WindowsInstall::class);
                }

                return $container->get(UnixInstall::class);
            },
        );

        $container->singleton(
            CheckAllBuildTools::class,
            static function (): CheckAllBuildTools {
                return CheckAllBuildTools::buildToolsFactory();
            },
        );

        $container->alias(UninstallUsingUnlink::class, Uninstall::class);

        $container->alias(Ini\RemoveIniEntryWithFileGetContents::class, Ini\RemoveIniEntry::class);

        $container->singleton(
            PackageManager::class,
            static function (): PackageManager|null {
                return PackageManager::detect();
            },
        );

        $container->singleton(
            SystemDependenciesDefinition::class,
            static function (): SystemDependenciesDefinition {
                return SystemDependenciesDefinition::default();
            },
        );

        return $container;
    }

    public static function testFactory(): ContainerInterface
    {
        self::$testBuffer ??= new BufferIO();

        $container = self::factory();
        assert($container instanceof IlluminateContainer);

        $container->singleton(IOInterface::class, static function () {
            return self::$testBuffer;
        });

        return $container;
    }

    public static function testBuffer(): BufferIO
    {
        assert(self::$testBuffer !== null);

        return self::$testBuffer;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie;

use Composer\Util\Platform as ComposerPlatform;
use Composer\Util\Silencer;
use Php\Pie\Platform\TargetPlatform;
use RuntimeException;
use Safe\Exceptions\FilesystemException;

use function array_keys;
use function defined;
use function implode;
use function md5;
use function rtrim;
use function Safe\fopen;
use function str_contains;
use function strpos;
use function strtr;

use const DIRECTORY_SEPARATOR;
use const PHP_BUILD_PROVIDER;
use const STDIN;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class Platform
{
    public static function isInteractive(): bool
    {
        try {
            $stdin = defined('STDIN') ? STDIN : fopen('php://stdin', 'r');
        } catch (FilesystemException) {
            $stdin = false;
        }

        return ComposerPlatform::getEnv('COMPOSER_NO_INTERACTION') !== '1'
            && $stdin !== false
            && ComposerPlatform::isTty($stdin);
    }

    private static function useXdg(): bool
    {
        foreach (array_keys($_SERVER) as $key) {
            if (strpos((string) $key, 'XDG_') === 0) {
                return true;
            }
        }

        return (bool) Silencer::call('is_dir', '/etc/xdg');
    }

    /** @throws RuntimeException */
    private static function getUserDir(): string
    {
        $home = ComposerPlatform::getEnv('HOME');
        if ($home === false || $home === '') {
            throw new RuntimeException('The HOME or PIE_WORKING_DIRECTORY environment variable must be set for PIE to run correctly');
        }

        return rtrim(strtr($home, '\\', '/'), '/');
    }

    public static function getPieBaseWorkingDirectory(): string
    {
        $home = ComposerPlatform::getEnv('PIE_WORKING_DIRECTORY');
        if ($home !== false && $home !== '') {
            return $home;
        }

        if (ComposerPlatform::isWindows()) {
            $appData = ComposerPlatform::getEnv('APPDATA');
            if ($appData === false || $appData === '') {
                throw new RuntimeException('The APPDATA or PIE_WORKING_DIRECTORY environment variable must be set for PIE to run correctly');
            }

            return rtrim(strtr($appData, '\\', '/'), '/') . '/PIE';
        }

        $userDir = self::getUserDir();
        $dirs    = [];

        if (self::useXdg()) {
            // XDG Base Directory Specifications
            $xdgConfig = ComposerPlatform::getEnv('XDG_CONFIG_HOME');
            if ($xdgConfig === false || $xdgConfig === '') {
                $xdgConfig = $userDir . '/.config';
            }

            $dirs[] = $xdgConfig . '/pie';
        }

        $dirs[] = $userDir . '/.pie';

        // select first dir which exists of: $XDG_CONFIG_HOME/pie or ~/.pie
        foreach ($dirs as $dir) {
            if (Silencer::call('is_dir', $dir)) {
                return $dir;
            }
        }

        // if none exists, we default to first defined one (XDG one if system uses it, or ~/.pie otherwise)
        return $dirs[0];
    }

    /**
     * This is essentially a Composer-controlled `vendor` directory that has downloaded sources
     *
     * @throws RuntimeException
     */
    public static function getPieWorkingDirectory(TargetPlatform $targetPlatform): string
    {
        // Simple hash of the target platform so we can build against different PHP installs on the same system
        $targetPlatformPath = DIRECTORY_SEPARATOR . 'php' . $targetPlatform->phpBinaryPath->majorMinorVersion() . '_' . md5(implode(
            '|',
            [
                $targetPlatform->operatingSystem->name,
                $targetPlatform->phpBinaryPath->phpBinaryPath,
                $targetPlatform->phpBinaryPath->version(),
                $targetPlatform->architecture->name,
                $targetPlatform->threadSafety->name,
                $targetPlatform->windowsCompiler?->name ?? 'x',
            ],
        ));

        return self::getPieBaseWorkingDirectory() . $targetPlatformPath;
    }

    /** @return non-empty-string */
    public static function getPieJsonFilename(TargetPlatform $targetPlatform): string
    {
        return self::getPieWorkingDirectory($targetPlatform) . '/pie.json';
    }

    public static function isRunningStaticPhp(): bool
    {
        return defined('PHP_BUILD_PROVIDER') && str_contains(PHP_BUILD_PROVIDER, 'static-php-cli');
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie;

use Composer\Json\JsonFile;
use Php\Pie\SelfManage\Update\Channel;

use function array_key_exists;
use function file_exists;
use function json_decode;
use function json_encode;
use function rtrim;
use function Safe\file_get_contents;
use function Safe\file_put_contents;
use function Safe\mkdir;

use const DIRECTORY_SEPARATOR;
use const JSON_PRETTY_PRINT;
use const JSON_THROW_ON_ERROR;

/**
 * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
 *
 * @phpstan-type PieSettings = array{
 *     channel?: non-empty-string,
 * }
 */
class Settings
{
    private const PIE_SETTINGS_SCHEMA_FILE_NAME = __DIR__ . '/../resources/pie-settings-schema.json';
    private const PIE_SETTINGS_FILE_NAME        = 'pie-settings.json';

    public function __construct(private readonly string $pieWorkingDirectory)
    {
    }

    private function pieSettingsFullPath(): string
    {
        $workDir = rtrim($this->pieWorkingDirectory, DIRECTORY_SEPARATOR);

        if (! file_exists($workDir)) {
            mkdir($workDir, recursive: true);
        }

        return $workDir . DIRECTORY_SEPARATOR . self::PIE_SETTINGS_FILE_NAME;
    }

    /** @phpstan-assert PieSettings $settingsBlob */
    private function validateSchema(mixed $settingsBlob): void
    {
        JsonFile::validateJsonSchema(
            self::PIE_SETTINGS_FILE_NAME,
            $settingsBlob,
            JsonFile::STRICT_SCHEMA,
            self::PIE_SETTINGS_SCHEMA_FILE_NAME,
        );
    }

    /** @phpstan-return PieSettings */
    private function read(): array
    {
        $pieSettingsFileName = $this->pieSettingsFullPath();
        if (! file_exists($pieSettingsFileName)) {
            return [];
        }

        $content = file_get_contents($pieSettingsFileName);
        if ($content === '') {
            return [];
        }

        $config = json_decode($content, true, flags: JSON_THROW_ON_ERROR);

        $this->validateSchema($config);

        return $config;
    }

    /** @param array<array-key, mixed> $config */
    private function write(array $config): void
    {
        $this->validateSchema($config);

        file_put_contents($this->pieSettingsFullPath(), json_encode($config, flags: JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));
    }

    public function updateChannel(): Channel
    {
        $config = $this->read();

        return array_key_exists('channel', $config)
            ? Channel::from($config['channel'])
            : Channel::Stable;
    }

    public function changeUpdateChannel(Channel $channel): void
    {
        $config = $this->read();

        $config['channel'] = $channel->value;

        $this->write($config);
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie;

use Webmozart\Assert\Assert;

use function array_key_exists;

/**
 * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
 *
 * @immutable
 */
final class ConfigureOption
{
    /** @param non-empty-string $name */
    private function __construct(
        public readonly string $name,
        public readonly bool $needsValue,
        public readonly string $description,
    ) {
    }

    /** @param array<array-key, mixed> $configureOptionDefinition */
    public static function fromComposerJsonDefinition(array $configureOptionDefinition): self
    {
        Assert::keyExists($configureOptionDefinition, 'name');
        Assert::stringNotEmpty($configureOptionDefinition['name']);
        // Restrict to identifier characters that match real ./configure flag
        // conventions. Whitespace and shell metacharacters in this field flow
        // verbatim into argv, log output, and installed.json metadata.
        Assert::regex(
            $configureOptionDefinition['name'],
            '/^[a-zA-Z][a-zA-Z0-9_-]*$/',
            'php-ext.configure-options[].name must be a configure-flag identifier (got %s)',
        );

        $needsValue = false;
        if (array_key_exists('needs-value', $configureOptionDefinition)) {
            Assert::boolean($configureOptionDefinition['needs-value']);
            $needsValue = $configureOptionDefinition['needs-value'];
        }

        $description = '';
        if (array_key_exists('description', $configureOptionDefinition)) {
            Assert::string($configureOptionDefinition['description']);
            $description = $configureOptionDefinition['description'];
        }

        return new self(
            $configureOptionDefinition['name'],
            $needsValue,
            $description,
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\SelfManage\BuildTools;

use Php\Pie\Platform\PackageManager;
use Php\Pie\Platform\TargetPhp\PhpizePath;
use Php\Pie\Platform\TargetPlatform;
use RuntimeException;
use Symfony\Component\Process\ExecutableFinder;

use function file_exists;
use function is_array;
use function is_executable;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class PhpizeBuildToolFinder extends BinaryBuildToolFinder
{
    /** @param array<PackageManager::*, non-empty-string|null> $packageManagerPackages */
    public function __construct(
        array $packageManagerPackages,
    ) {
        parent::__construct('phpize', $packageManagerPackages);
    }

    public function check(TargetPlatform $targetPlatform): bool
    {
        $tools = is_array($this->tool) ? $this->tool : [$this->tool];

        if ($targetPlatform->phpizePath !== null) {
            $tools[] = $targetPlatform->phpizePath->phpizeBinaryPath;
        }

        try {
            $tools[] = PhpizePath::guessFrom($targetPlatform->phpBinaryPath)->phpizeBinaryPath;
        } catch (RuntimeException) {
            // intentionally ignored - just don't try to use the guessed phpize path
        }

        $expectedApiVersion = $targetPlatform->phpBinaryPath->phpApiVersion();

        foreach ($tools as $tool) {
            if (file_exists($tool) && is_executable($tool) && PhpizePath::looksLikeValidPhpize($tool, $expectedApiVersion)) {
                return true;
            }

            $foundTool = (new ExecutableFinder())->find($tool);

            if ($foundTool !== null && PhpizePath::looksLikeValidPhpize($foundTool, $expectedApiVersion)) {
                return true;
            }
        }

        return false;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\SelfManage\BuildTools;

use Composer\IO\IOInterface;
use Php\Pie\Platform\PackageManager;
use Php\Pie\Platform\TargetPlatform;
use Throwable;

use function array_unique;
use function array_values;
use function count;
use function implode;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class CheckAllBuildTools
{
    public static function buildToolsFactory(): self
    {
        return new self([
            new BinaryBuildToolFinder(
                'gcc',
                [
                    PackageManager::Apt->value => 'gcc',
                    PackageManager::Apk->value => 'build-base',
                    PackageManager::Dnf->value => 'gcc',
                    PackageManager::Yum->value => 'gcc',
                    PackageManager::Brew->value => 'gcc',
                ],
            ),
            new BinaryBuildToolFinder(
                'make',
                [
                    PackageManager::Apt->value => 'make',
                    PackageManager::Apk->value => 'build-base',
                    PackageManager::Dnf->value => 'make',
                    PackageManager::Yum->value => 'make',
                    PackageManager::Brew->value => 'make',
                ],
            ),
            new BinaryBuildToolFinder(
                'autoconf',
                [
                    PackageManager::Apt->value => 'autoconf',
                    PackageManager::Apk->value => 'autoconf',
                    PackageManager::Dnf->value => 'autoconf',
                    PackageManager::Yum->value => 'autoconf',
                    PackageManager::Brew->value => 'autoconf',
                ],
            ),
            new BinaryBuildToolFinder(
                'pkg-config',
                [
                    PackageManager::Apt->value => 'pkg-config',
                    PackageManager::Apk->value => 'pkgconfig',
                    PackageManager::Dnf->value => 'pkgconf-pkg-config',
                    PackageManager::Yum->value => 'pkgconf-pkg-config',
                    PackageManager::Brew->value => 'pkgconf',
                ],
            ),
            new BinaryBuildToolFinder(
                ['libtoolize', 'glibtoolize'],
                [
                    PackageManager::Apt->value => 'libtool',
                    PackageManager::Apk->value => 'libtool',
                    PackageManager::Dnf->value => 'libtool',
                    PackageManager::Yum->value => 'libtool',
                    PackageManager::Brew->value => 'libtool',
                ],
            ),
            // Composer's archive downloader uses /usr/bin/unzip first
            // and falls back to git-source-cloning when it isn't
            // present (not to PHP's ZipArchive). Without unzip, a
            // pre-packaged-binary dist URL is silently swapped for a
            // git clone of the source tree and the .so the user paid
            // for in download time is never extracted, surfacing as
            // ExtensionBinaryNotFound when PIE's prePackagedBinary
            // check looks for it in the vendor dir. Bare php:X.Y-cli
            // Debian images do not ship /usr/bin/unzip.
            new BinaryBuildToolFinder(
                'unzip',
                [
                    PackageManager::Apt->value => 'unzip',
                    PackageManager::Apk->value => 'unzip',
                    PackageManager::Dnf->value => 'unzip',
                    PackageManager::Yum->value => 'unzip',
                    PackageManager::Brew->value => 'unzip',
                ],
            ),
            new PhpizeBuildToolFinder(
                [
                    PackageManager::Apt->value => 'php-dev',
                    PackageManager::Apk->value => 'php{major}{minor}-dev',
                    PackageManager::Dnf->value => 'php-devel',
                    PackageManager::Yum->value => 'php-devel',
                    PackageManager::Brew->value => 'php',
                ],
            ),
        ]);
    }

    /** @param list<BinaryBuildToolFinder> $buildTools */
    public function __construct(
        private readonly array $buildTools,
    ) {
    }

    public function check(IOInterface $io, PackageManager|null $packageManager, TargetPlatform $targetPlatform, bool $autoInstallIfMissing): void
    {
        $io->write('<info>Checking if all build tools are installed.</info>', verbosity: IOInterface::VERBOSE);
        /** @var list<string> $packagesToInstall */
        $packagesToInstall = [];
        $missingTools      = [];
        $allFound          = true;

        foreach ($this->buildTools as $buildTool) {
            if ($buildTool->check($targetPlatform) !== false) {
                $io->write('Build tool ' . $buildTool->toolNames() . ' is installed.', verbosity: IOInterface::VERY_VERBOSE);
                continue;
            }

            $allFound       = false;
            $missingTools[] = $buildTool->toolNames();

            if ($packageManager === null) {
                continue;
            }

            $packageName = $buildTool->packageNameFor($packageManager, $targetPlatform);

            if ($packageName === null) {
                $io->writeError('<warning>Could not find package name for build tool ' . $buildTool->toolNames() . '.</warning>', verbosity: IOInterface::VERBOSE);
                continue;
            }

            $packagesToInstall[] = $packageName;
        }

        if ($allFound) {
            $io->write('<info>All build tools found.</info>', verbosity: IOInterface::VERBOSE);

            return;
        }

        $io->write('<comment>The following build tools are missing: ' . implode(', ', $missingTools) . '</comment>');

        if ($packageManager === null) {
            $io->write('<warning>Could not find a package manager to install the missing build tools.</warning>');

            return;
        }

        if (! count($packagesToInstall)) {
            $io->write('<warning>Could not determine packages to install.</warning>');

            return;
        }

        $proposedInstallCommand = implode(' ', $packageManager->installCommand(array_values(array_unique($packagesToInstall))));

        if (! $io->isInteractive() && ! $autoInstallIfMissing) {
            $io->writeError('<warning>You are not running in interactive mode, and you did not provide the --auto-install-build-tools flag.');
            $io->writeError('You may need to run: ' . $proposedInstallCommand . '</warning>');
            $io->writeError('');

            return;
        }

        $io->write('The following command will be run: ' . $proposedInstallCommand, verbosity: IOInterface::VERBOSE);

        if ($io->isInteractive() && ! $autoInstallIfMissing) {
            if (! $io->askConfirmation('<question>Would you like to install them now?</question>', false)) {
                $io->write('<comment>Ok, but things might not work. Just so you know.</comment>');

                return;
            }
        }

        try {
            $packageManager->install(array_values(array_unique($packagesToInstall)));

            $io->write('<info>Missing build tools have been installed.</info>');
        } catch (Throwable $throwable) {
            $io->writeError('<error>Could not install the missing build tools. You may need to install them manually.</error>');
            $io->writeError($throwable->__toString(), verbosity: IOInterface::VERBOSE);
            $io->writeError('');

            return;
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\SelfManage\BuildTools;

use Php\Pie\Platform\PackageManager;
use Php\Pie\Platform\TargetPlatform;
use Symfony\Component\Process\ExecutableFinder;

use function array_key_exists;
use function implode;
use function is_array;
use function str_replace;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class BinaryBuildToolFinder
{
    /**
     * @param non-empty-string|array<non-empty-string>        $tool
     * @param array<PackageManager::*, non-empty-string|null> $packageManagerPackages
     */
    public function __construct(
        protected readonly string|array $tool,
        private readonly array $packageManagerPackages,
    ) {
    }

    public function toolNames(): string
    {
        return is_array($this->tool) ? implode('/', $this->tool) : $this->tool;
    }

    public function check(TargetPlatform $targetPlatform): bool
    {
        $tools = is_array($this->tool) ? $this->tool : [$this->tool];

        foreach ($tools as $tool) {
            if ((new ExecutableFinder())->find($tool) !== null) {
                return true;
            }
        }

        return false;
    }

    /** @return non-empty-string|null */
    public function packageNameFor(PackageManager $packageManager, TargetPlatform $targetPlatform): string|null
    {
        if (! array_key_exists($packageManager->value, $this->packageManagerPackages) || $this->packageManagerPackages[$packageManager->value] === null) {
            return null;
        }

        // If we need to customise specific package names depending on OS
        // specific parameters, this is likely the place to do it
        return str_replace(
            '{major}',
            (string) $targetPlatform->phpBinaryPath->majorVersion(),
            str_replace(
                '{minor}',
                (string) $targetPlatform->phpBinaryPath->minorVersion(),
                $this->packageManagerPackages[$packageManager->value],
            ),
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\SelfManage\Update;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class ReleaseMetadata
{
    /**
     * @param non-empty-string $tag
     * @param non-empty-string $downloadUrl
     */
    public function __construct(
        public readonly string $tag,
        public readonly string $downloadUrl,
    ) {
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\SelfManage\Update;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
enum Channel: string
{
    case Stable  = 'stable';
    case Preview = 'preview';
    case Nightly = 'nightly';
}
<?php

declare(strict_types=1);

namespace Php\Pie\SelfManage\Update;

use Composer\Config;
use Composer\Package\Version\VersionParser;
use Composer\Util\HttpDownloader;
use Php\Pie\ComposerIntegration\QuieterConsoleIO;
use Php\Pie\File\BinaryFile;
use RuntimeException;
use Safe\Exceptions\FilesystemException;
use Webmozart\Assert\Assert;

use function array_filter;
use function array_key_exists;
use function array_map;
use function count;
use function reset;
use function Safe\file_put_contents;
use function Safe\preg_match;
use function Safe\tempnam;
use function sprintf;
use function sys_get_temp_dir;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class FetchPieReleaseFromGitHub implements FetchPieRelease
{
    private const PIE_PHAR_NAME    = 'pie.phar';
    private const PIE_REPO_URL     = '/repos/php/pie';
    private const PIE_RELEASES_URL = '/repos/php/pie/releases';

    public function __construct(
        private readonly string $githubApiBaseUrl,
        private readonly HttpDownloader $httpDownloader,
    ) {
    }

    public static function factory(QuieterConsoleIO $io, Config $config, string $githubApiBaseUrl): self
    {
        return new self($githubApiBaseUrl, new HttpDownloader($io, $config));
    }

    public function trunkBranch(): string
    {
        $url = $this->githubApiBaseUrl . self::PIE_REPO_URL;

        $decodedResponse = $this->httpDownloader->get(
            $url,
            [
                'retry-auth-failure' => true,
                'http' => [
                    'method' => 'GET',
                    'header' => [],
                ],
            ],
        )->decodeJson();

        Assert::isArray($decodedResponse);
        Assert::keyExists($decodedResponse, 'default_branch');
        Assert::stringNotEmpty($decodedResponse['default_branch']);

        $branch = $decodedResponse['default_branch'];

        // Branch MUST match the N.N.x format
        if (preg_match('/^\d+\.\d+\.x$/', $branch) !== 1) {
            throw new RuntimeException(sprintf(
                'The default branch "%s" returned by GitHub is not in an expected format.',
                $branch,
            ));
        }

        return $branch;
    }

    public function latestReleaseMetadata(Channel $updateChannel): ReleaseMetadata
    {
        $url = $this->githubApiBaseUrl . self::PIE_RELEASES_URL;

        $decodedResponse = $this->httpDownloader->get(
            $url,
            [
                'retry-auth-failure' => true,
                'http' => [
                    'method' => 'GET',
                    'header' => [],
                ],
            ],
        )->decodeJson();

        Assert::isList($decodedResponse);
        Assert::allIsArray($decodedResponse);

        $releases = array_filter(
            array_map(
                static function (array $releaseResponse): ReleaseMetadata|null {
                    Assert::keyExists($releaseResponse, 'tag_name');
                    Assert::stringNotEmpty($releaseResponse['tag_name']);
                    Assert::keyExists($releaseResponse, 'assets');
                    Assert::isList($releaseResponse['assets']);
                    Assert::allIsArray($releaseResponse['assets']);

                    $assetsNamedPiePhar = array_filter(
                        array_map(
                            static function (array $asset): array {
                                Assert::keyExists($asset, 'name');
                                Assert::stringNotEmpty($asset['name']);
                                Assert::keyExists($asset, 'browser_download_url');
                                Assert::stringNotEmpty($asset['browser_download_url']);

                                return $asset;
                            },
                            $releaseResponse['assets'],
                        ),
                        static function (array $asset): bool {
                            return $asset['name'] === self::PIE_PHAR_NAME;
                        },
                    );

                    if (! count($assetsNamedPiePhar)) {
                        return null;
                    }

                    $firstAssetNamedPiePhar = reset($assetsNamedPiePhar);

                    return new ReleaseMetadata(
                        $releaseResponse['tag_name'],
                        $firstAssetNamedPiePhar['browser_download_url'],
                    );
                },
                array_filter(
                    $decodedResponse,
                    static fn (array $releaseResponse): bool => (! array_key_exists('draft', $releaseResponse) || ! $releaseResponse['draft']),
                ),
            ),
            static function (ReleaseMetadata|null $releaseMetadata) use ($updateChannel): bool {
                if ($releaseMetadata === null) {
                    return false;
                }

                $stability = VersionParser::parseStability($releaseMetadata->tag);

                return ($updateChannel === Channel::Stable && $stability === 'stable')
                    || $updateChannel === Channel::Preview;
            },
        );

        $first = reset($releases);

        if (! $first instanceof ReleaseMetadata) {
            throw new RuntimeException('No PIE release found for channel ' . $updateChannel->value);
        }

        return $first;
    }

    public function downloadContent(ReleaseMetadata $releaseMetadata): BinaryFile
    {
        $pharContent = $this->httpDownloader->get(
            $releaseMetadata->downloadUrl,
            [
                'retry-auth-failure' => true,
                'http' => [
                    'method' => 'GET',
                    'header' => [],
                ],
            ],
        )->getBody();
        Assert::stringNotEmpty($pharContent);

        $tempPharFilename = tempnam(sys_get_temp_dir(), 'pie_self_update_');

        try {
            file_put_contents($tempPharFilename, $pharContent);
        } catch (FilesystemException $previous) {
            throw new RuntimeException(
                sprintf(
                    'Failed to write downloaded PHAR to %s: %s',
                    $tempPharFilename,
                    $previous->getMessage(),
                ),
                previous: $previous,
            );
        }

        return BinaryFile::fromFileWithSha256Checksum($tempPharFilename);
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\SelfManage\Update;

use Php\Pie\File\BinaryFile;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
interface FetchPieRelease
{
    public function latestReleaseMetadata(Channel $updateChannel): ReleaseMetadata;

    /** @return non-empty-string */
    public function trunkBranch(): string;

    /** Download the given pie.phar and return the filename (should be a temp file) */
    public function downloadContent(ReleaseMetadata $releaseMetadata): BinaryFile;
}
<?php

declare(strict_types=1);

namespace Php\Pie\SelfManage\Update;

use Composer\Semver\Semver;
use Composer\Semver\VersionParser;

use function Safe\preg_match;
use function strtolower;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class ReleaseIsNewer
{
    /**
     * @param non-empty-string $stability
     * @phpstan-param 'stable'|'RC'|'beta'|'alpha'|'dev' $stability
     */
    private static function stabilityToChannel(string $stability): Channel
    {
        return match (strtolower($stability)) {
            'stable' => Channel::Stable,
            'rc', 'beta', 'alpha' => Channel::Preview,
            'dev' => Channel::Nightly,
        };
    }

    /** @param non-empty-string $currentPieVersion */
    public static function forChannel(
        Channel $updateChannel,
        string $currentPieVersion,
        ReleaseMetadata $newRelease,
    ): bool {
        if (preg_match('#(.*)@([a-f0-9]{7})$#', $currentPieVersion, $matches)) {
            $currentPieVersion = 'dev-main#' . $matches[2];
        }

        $newVersion   = $newRelease->tag;
        $newStability = self::stabilityToChannel(VersionParser::parseStability($newVersion));

        $currentStability = self::stabilityToChannel(VersionParser::parseStability($currentPieVersion));

        $currentIsStable  = $currentStability === Channel::Stable;
        $currentIsPreview = $currentStability === Channel::Preview;
        $currentIsNightly = $currentStability === Channel::Nightly;
        $newIsStable      = $newStability === Channel::Stable;
        $newIsPreview     = $newStability === Channel::Preview;
        $newIsNightly     = $newStability === Channel::Nightly;

        switch ($updateChannel) {
            case Channel::Stable:
                // Do not upgrade to preview or nightly
                if (! $newIsStable) {
                    return false;
                }

                // If current is nightly/preview, any stable version is an upgrade
                if ($currentIsNightly || $currentIsPreview) {
                    return true;
                }

                return Semver::satisfies($newVersion, '> ' . $currentPieVersion);

            case Channel::Preview:
                // Do not update to a nightly
                if ($newIsNightly) {
                    return false;
                }

                // If current is nightly, allow upgrade to stable/preview
                if ($currentIsNightly) {
                    return true;
                }

                // Compare versions normally for stable/preview
                return Semver::satisfies($newVersion, '> ' . $currentPieVersion);

            case Channel::Nightly:
                // Nightly channel: accept any newer version or nightly builds
                if ($newIsNightly) {
                    // For nightly builds, always update to nightly (or same nightly counts as update)
                    return true;
                }

                return Semver::satisfies($newVersion, '> ' . $currentPieVersion);
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\SelfManage\Update;

use RuntimeException;

use function sprintf;

/** @deprecated This exception is no longer thrown */
class PiePharMissingFromLatestRelease extends RuntimeException
{
    /** @param non-empty-string $tagName */
    public static function fromRelease(string $tagName): self
    {
        return new self(sprintf(
            'PIE release %s does not have a pie.phar attached yet, try again in a few minutes.',
            $tagName,
        ));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\SelfManage\Verify;

use RuntimeException;

use function sprintf;

class GithubCliNotAvailable extends RuntimeException
{
    public static function fromExpectedGhToolName(string $expectedGhToolName): self
    {
        return new self(sprintf('The GitHub "%s" CLI tool was not available.', $expectedGhToolName));
    }

    public static function withMissingAttestationCommand(string $expectedGhToolName): self
    {
        return new self(sprintf('The GitHub "%s" CLI tool was available, but the `gh attestation` command failed; perhaps this version is out of date.', $expectedGhToolName));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\SelfManage\Verify;

use Composer\IO\IOInterface;
use Php\Pie\File\BinaryFile;
use Php\Pie\SelfManage\Update\FetchPieRelease;
use Php\Pie\SelfManage\Update\ReleaseMetadata;
use Symfony\Component\Process\ExecutableFinder;
use ThePhpFoundation\Attestation\Verification\VerifyAttestationWithOpenSsl;

use function extension_loaded;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class VerifyPieReleaseUsingAttestation implements VerifyPiePhar
{
    public function __construct(
        private readonly GithubCliAttestationVerification $githubCliVerification,
        private readonly FallbackVerificationUsingOpenSsl $fallbackVerification,
    ) {
    }

    public static function factory(FetchPieRelease $fetchPieRelease): self
    {
        return new VerifyPieReleaseUsingAttestation(
            new GithubCliAttestationVerification(new ExecutableFinder(), $fetchPieRelease),
            new FallbackVerificationUsingOpenSsl(VerifyAttestationWithOpenSsl::factory(), $fetchPieRelease),
        );
    }

    public function verify(ReleaseMetadata $releaseMetadata, BinaryFile $pharFilename, IOInterface $io): void
    {
        try {
            $this->githubCliVerification->verify($releaseMetadata, $pharFilename, $io);
        } catch (GithubCliNotAvailable $githubCliNotAvailable) {
            $io->writeError($githubCliNotAvailable->getMessage(), verbosity: IOInterface::VERBOSE);

            if (! extension_loaded('openssl')) {
                throw FailedToVerifyRelease::fromNoOpenssl();
            }

            $this->fallbackVerification->verify($releaseMetadata, $pharFilename, $io);
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\SelfManage\Verify;

use Composer\IO\IOInterface;
use Php\Pie\File\BinaryFile;
use Php\Pie\SelfManage\Update\FetchPieRelease;
use Php\Pie\SelfManage\Update\ReleaseMetadata;
use Php\Pie\Util\Emoji;
use Php\Pie\Util\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\ExecutableFinder;

use function implode;
use function sprintf;
use function str_starts_with;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class GithubCliAttestationVerification implements VerifyPiePhar
{
    private const GH_CLI_NAME            = 'gh';
    private const GH_ATTESTATION_COMMAND = 'attestation';

    public function __construct(
        private readonly ExecutableFinder $executableFinder,
        private readonly FetchPieRelease $fetchPieRelease,
    ) {
    }

    public function verify(ReleaseMetadata $releaseMetadata, BinaryFile $pharFilename, IOInterface $io): void
    {
        $gh = $this->executableFinder->find(self::GH_CLI_NAME);

        if ($gh === null) {
            throw GithubCliNotAvailable::fromExpectedGhToolName(self::GH_CLI_NAME);
        }

        // Try to use `gh attestation --help` to ensure it is not an old `gh` cli version
        try {
            Process::run([$gh, self::GH_ATTESTATION_COMMAND, '--help']);
        } catch (ProcessFailedException $attestationCommandCheck) {
            if (str_starts_with($attestationCommandCheck->getProcess()->getErrorOutput(), sprintf('unknown command "%s" for "%s"', self::GH_ATTESTATION_COMMAND, self::GH_CLI_NAME))) {
                throw GithubCliNotAvailable::withMissingAttestationCommand(self::GH_CLI_NAME);
            }

            throw $attestationCommandCheck;
        }

        $verificationCommand = [
            $gh,
            self::GH_ATTESTATION_COMMAND,
            'verify',
            '--repo=php/pie',
        ];

        if ($releaseMetadata->tag === 'nightly') {
            $verificationCommand[] = '--signer-workflow=php/pie/.github/workflows/build-assets.yml';
            $verificationCommand[] = '--source-ref=refs/heads/' . $this->fetchPieRelease->trunkBranch();
        } else {
            $verificationCommand[] = '--source-ref=refs/tags/' . $releaseMetadata->tag;
        }

        $verificationCommand[] = $pharFilename->filePath;

        $io->write(
            'Verifying using: ' . implode(' ', $verificationCommand),
            verbosity: IOInterface::VERBOSE,
        );

        try {
            Process::run($verificationCommand);
        } catch (ProcessFailedException $processFailedException) {
            throw FailedToVerifyRelease::fromGhCliFailure($releaseMetadata, $processFailedException);
        }

        $io->write(sprintf('<info>%s Verified the new PIE version</info>', Emoji::GREEN_CHECKMARK));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\SelfManage\Verify;

use Composer\IO\IOInterface;
use Php\Pie\File\BinaryFile;
use Php\Pie\SelfManage\Update\ReleaseMetadata;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
interface VerifyPiePhar
{
    /** @throws FailedToVerifyRelease */
    public function verify(ReleaseMetadata $releaseMetadata, BinaryFile $pharFilename, IOInterface $io): void;
}
<?php

declare(strict_types=1);

namespace Php\Pie\SelfManage\Verify;

use Webmozart\Assert\Assert;

use function Safe\base64_decode;
use function wordwrap;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class Attestation
{
    /**
     * @param non-empty-string $certificate
     * @param non-empty-string $dsseEnvelopePayload
     * @param non-empty-string $dsseEnvelopePayloadType
     * @param non-empty-string $dsseEnvelopeSignature
     */
    private function __construct(
        public readonly string $certificate,
        public readonly string $dsseEnvelopePayload,
        public readonly string $dsseEnvelopePayloadType,
        public readonly string $dsseEnvelopeSignature,
    ) {
    }

    /** @param array<array-key, mixed> $attestation */
    public static function fromAttestationBundleWithDsseEnvelope(array $attestation): self
    {
        Assert::keyExists($attestation, 'bundle');
        Assert::isArray($attestation['bundle']);

        Assert::keyExists($attestation['bundle'], 'verificationMaterial');
        Assert::isArray($attestation['bundle']['verificationMaterial']);
        Assert::keyExists($attestation['bundle']['verificationMaterial'], 'certificate');
        Assert::isArray($attestation['bundle']['verificationMaterial']['certificate']);
        Assert::keyExists($attestation['bundle']['verificationMaterial']['certificate'], 'rawBytes');
        Assert::stringNotEmpty($attestation['bundle']['verificationMaterial']['certificate']['rawBytes']);

        Assert::keyExists($attestation['bundle'], 'dsseEnvelope');
        Assert::isArray($attestation['bundle']['dsseEnvelope']);
        Assert::keyExists($attestation['bundle']['dsseEnvelope'], 'payload');
        Assert::stringNotEmpty($attestation['bundle']['dsseEnvelope']['payload']);
        Assert::keyExists($attestation['bundle']['dsseEnvelope'], 'payloadType');
        Assert::stringNotEmpty($attestation['bundle']['dsseEnvelope']['payloadType']);
        Assert::keyExists($attestation['bundle']['dsseEnvelope'], 'signatures');
        Assert::isNonEmptyList($attestation['bundle']['dsseEnvelope']['signatures']);
        Assert::count($attestation['bundle']['dsseEnvelope']['signatures'], 1);
        Assert::keyExists($attestation['bundle']['dsseEnvelope']['signatures'], 0);
        Assert::isArray($attestation['bundle']['dsseEnvelope']['signatures'][0]);
        Assert::keyExists($attestation['bundle']['dsseEnvelope']['signatures'][0], 'sig');
        Assert::stringNotEmpty($attestation['bundle']['dsseEnvelope']['signatures'][0]['sig']);

        $decoratedCertificate = "-----BEGIN CERTIFICATE-----\n"
            . wordwrap($attestation['bundle']['verificationMaterial']['certificate']['rawBytes'], 67, "\n", true) . "\n"
            . "-----END CERTIFICATE-----\n";

        $decodedPayload = base64_decode($attestation['bundle']['dsseEnvelope']['payload']);
        Assert::stringNotEmpty($decodedPayload);

        $decodedSignature = base64_decode($attestation['bundle']['dsseEnvelope']['signatures'][0]['sig']);
        Assert::stringNotEmpty($decodedSignature);

        return new self(
            $decoratedCertificate,
            $decodedPayload,
            $attestation['bundle']['dsseEnvelope']['payloadType'],
            $decodedSignature,
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\SelfManage\Verify;

use Php\Pie\SelfManage\Update\ReleaseMetadata;
use RuntimeException;
use Symfony\Component\Process\Exception\ProcessFailedException;
use ThePhpFoundation\Attestation\Verification\Exception\FailedToVerifyArtifact;

use function sprintf;
use function trim;

class FailedToVerifyRelease extends RuntimeException
{
    public static function fromAttestationException(FailedToVerifyArtifact $failedToVerifyArtifact): self
    {
        return new self($failedToVerifyArtifact->getMessage(), 0, $failedToVerifyArtifact);
    }

    public static function fromNoOpenssl(): self
    {
        return new self('Unable to verify without `gh` CLI tool, or openssl extension.');
    }

    public static function fromGhCliFailure(ReleaseMetadata $releaseMetadata, ProcessFailedException $processFailedException): self
    {
        return new self(
            sprintf(
                "`gh` CLI tool could not verify release %s\n\nError: %s",
                $releaseMetadata->tag,
                trim($processFailedException->getProcess()->getErrorOutput()),
            ),
            previous: $processFailedException,
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\SelfManage\Verify;

use Composer\IO\IOInterface;
use Php\Pie\File\BinaryFile;
use Php\Pie\SelfManage\Update\FetchPieRelease;
use Php\Pie\SelfManage\Update\ReleaseMetadata;
use Php\Pie\Util\Emoji;
use ThePhpFoundation\Attestation\FilenameWithChecksum;
use ThePhpFoundation\Attestation\FulcioSigstoreOidExtensions;
use ThePhpFoundation\Attestation\Verification\Exception\FailedToVerifyArtifact;
use ThePhpFoundation\Attestation\Verification\VerifyAttestation;

use function sprintf;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class FallbackVerificationUsingOpenSsl implements VerifyPiePhar
{
    /** @link https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#136141572641--fulcio */
    private const ATTESTATION_CERTIFICATE_EXPECTED_EXTENSION_VALUES = [
        FulcioSigstoreOidExtensions::ISSUER_V2 => 'https://token.actions.githubusercontent.com',
        FulcioSigstoreOidExtensions::SOURCE_REPOSITORY_URI => 'https://github.com/php/pie',
        FulcioSigstoreOidExtensions::SOURCE_REPOSITORY_OWNER_URI => 'https://github.com/php',
    ];

    private const ORGANISATION = 'php';

    private const ARTIFACT_FILENAME = 'pie.phar';

    /** @link https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#13614157264114--source-repository-ref */
    private const SOURCE_REPOSITORY_REF = '1.3.6.1.4.1.57264.1.14';

    /** @link https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#1361415726419--build-signer-uri */
    private const BUILD_SIGNER_URI = '1.3.6.1.4.1.57264.1.9';

    public function __construct(
        private readonly VerifyAttestation $verifyAttestation,
        private readonly FetchPieRelease $fetchPieRelease,
    ) {
    }

    public function verify(ReleaseMetadata $releaseMetadata, BinaryFile $pharFilename, IOInterface $io): void
    {
        // The fallback verifier checks cert chain, cert extension claims, DSSE
        // subject digest, and DSSE signature, but does NOT validate Rekor
        // transparency-log inclusion. `gh attestation verify` does. Surface the
        // reduced guarantees so users on shared / air-gapped hosts know.
        $io->writeError(
            '<warning>Falling back to OpenSSL verification (no Rekor inclusion check). Install `gh` for full attestation verification.</warning>',
        );

        $expectedExtensions = self::ATTESTATION_CERTIFICATE_EXPECTED_EXTENSION_VALUES;

        if ($releaseMetadata->tag === 'nightly') {
            $expectedExtensions[self::BUILD_SIGNER_URI] = sprintf(
                'https://github.com/php/pie/.github/workflows/build-assets.yml@refs/heads/%s',
                $this->fetchPieRelease->trunkBranch(),
            );
        } else {
            $expectedExtensions[self::SOURCE_REPOSITORY_REF] = 'refs/tags/' . $releaseMetadata->tag;
        }

        try {
            /** @psalm-suppress InvalidArgument */
            $this->verifyAttestation->verify(
                FilenameWithChecksum::fromFilenameAndChecksum($pharFilename->filePath, $pharFilename->checksum),
                self::ORGANISATION,
                self::ARTIFACT_FILENAME,
                $expectedExtensions,
            );
        } catch (FailedToVerifyArtifact $failedToVerifyArtifact) {
            throw FailedToVerifyRelease::fromAttestationException($failedToVerifyArtifact);
        }

        $io->write(sprintf(
            '<info>%s Verified the new PIE version (using fallback verification)</info>',
            Emoji::GREEN_CHECKMARK,
        ));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Downloading;

use Composer\Util\HttpDownloader;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\Platform\TargetPlatform;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
interface PackageReleaseAssets
{
    /**
     * @param non-empty-list<non-empty-string> $possibleReleaseAssetNames
     *
     * @return non-empty-string
     */
    public function findMatchingReleaseAssetUrl(
        TargetPlatform $targetPlatform,
        Package $package,
        HttpDownloader $httpDownloader,
        DownloadUrlMethod $downloadUrlMethod,
        array $possibleReleaseAssetNames,
    ): string;
}
<?php

declare(strict_types=1);

namespace Php\Pie\Downloading;

use Php\Pie\DependencyResolver\Package;
use Php\Pie\Platform\PrePackagedSourceAssetName;
use RuntimeException;
use Safe\Exceptions\FilesystemException;

use function array_map;
use function array_unique;
use function file_exists;
use function is_dir;
use function pathinfo;
use function Safe\realpath;
use function sprintf;
use function str_replace;
use function str_starts_with;

use const DIRECTORY_SEPARATOR;
use const PATHINFO_FILENAME;

/**
 * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
 *
 * @immutable
 */
final class DownloadedPackage
{
    private function __construct(
        public readonly Package $package,
        public readonly string $extractedSourcePath,
    ) {
    }

    private static function unfoldUnarchivedSourcePaths(Package $package, string $extractedSourcePath): string
    {
        // There is already something buildable here, don't need to unfold
        if (
            file_exists($extractedSourcePath . DIRECTORY_SEPARATOR . 'config.m4')
            || file_exists($extractedSourcePath . DIRECTORY_SEPARATOR . 'config.w32')
        ) {
            return $extractedSourcePath;
        }

        $possibleAssetNames = array_unique(array_map(
            static fn (string $assetName): string => pathinfo($assetName, PATHINFO_FILENAME),
            PrePackagedSourceAssetName::packageNames($package),
        ));
        foreach ($possibleAssetNames as $possibleAssetName) {
            if (
                ! file_exists($extractedSourcePath . DIRECTORY_SEPARATOR . $possibleAssetName)
                || ! is_dir($extractedSourcePath . DIRECTORY_SEPARATOR . $possibleAssetName)
            ) {
                continue;
            }

            if (
                file_exists($extractedSourcePath . DIRECTORY_SEPARATOR . $possibleAssetName . DIRECTORY_SEPARATOR . 'config.m4')
                || file_exists($extractedSourcePath . DIRECTORY_SEPARATOR . $possibleAssetName . DIRECTORY_SEPARATOR . 'config.w32')
            ) {
                return $extractedSourcePath . DIRECTORY_SEPARATOR . $possibleAssetName;
            }
        }

        return $extractedSourcePath;
    }

    private static function overrideSourcePathUsingBuildPath(Package $package, string $extractedSourcePath): string
    {
        if ($package->buildPath() === null) {
            return $extractedSourcePath;
        }

        try {
            $candidate = realpath(
                $extractedSourcePath
                . DIRECTORY_SEPARATOR
                . str_replace('{version}', $package->version(), $package->buildPath()),
            );
        } catch (FilesystemException) {
            // Build path does not exist; likely a configuration error
            return $extractedSourcePath;
        }

        $extractedReal = realpath($extractedSourcePath);
        if (! str_starts_with($candidate . DIRECTORY_SEPARATOR, $extractedReal . DIRECTORY_SEPARATOR)) {
            throw new RuntimeException(sprintf(
                'php-ext.build-path %s resolved to %s, which is outside the extract directory %s',
                $package->buildPath(),
                $candidate,
                $extractedReal,
            ));
        }

        return $candidate;
    }

    public static function fromPackageAndExtractedPath(Package $package, string $extractedSourcePath): self
    {
        $sourcePath = self::unfoldUnarchivedSourcePaths($package, $extractedSourcePath);

        if ($package->buildPath() !== null) {
            $sourcePath = self::overrideSourcePathUsingBuildPath($package, $extractedSourcePath);
        }

        return new self($package, $sourcePath);
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Downloading;

use Composer\Downloader\TransportException;
use Composer\Util\HttpDownloader;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\Platform\TargetPlatform;
use Webmozart\Assert\Assert;

use function array_map;
use function in_array;
use function strtolower;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class GithubPackageReleaseAssets implements PackageReleaseAssets
{
    public function __construct(
        private readonly string $githubApiBaseUrl,
    ) {
    }

    /**
     * @param non-empty-list<non-empty-string> $possibleReleaseAssetNames
     *
     * @return non-empty-string
     */
    public function findMatchingReleaseAssetUrl(
        TargetPlatform $targetPlatform,
        Package $package,
        HttpDownloader $httpDownloader,
        DownloadUrlMethod $downloadUrlMethod,
        array $possibleReleaseAssetNames,
    ): string {
        $releaseAsset = $this->selectMatchingReleaseAsset(
            $targetPlatform,
            $package,
            $this->getReleaseAssetsForPackage($package, $httpDownloader, $downloadUrlMethod),
            $downloadUrlMethod,
            $possibleReleaseAssetNames,
        );

        return $releaseAsset['browser_download_url'];
    }

    /** @link https://github.com/squizlabs/PHP_CodeSniffer/issues/3734 */
    // phpcs:disable Squiz.Commenting.FunctionComment.MissingParamName
    /**
     * @param list<array{name: non-empty-string, browser_download_url: non-empty-string, ...}> $releaseAssets
     * @param non-empty-list<non-empty-string> $possibleReleaseAssetNames
     *
     * @return array{name: non-empty-string, browser_download_url: non-empty-string, ...}
     */
    // phpcs:enable
    private function selectMatchingReleaseAsset(
        TargetPlatform $targetPlatform,
        Package $package,
        array $releaseAssets,
        DownloadUrlMethod $downloadUrlMethod,
        array $possibleReleaseAssetNames,
    ): array {
        foreach ($releaseAssets as $releaseAsset) {
            if (in_array(strtolower($releaseAsset['name']), $possibleReleaseAssetNames, true)) {
                return $releaseAsset;
            }
        }

        throw Exception\CouldNotFindReleaseAsset::forPackage($targetPlatform, $package, $downloadUrlMethod, $possibleReleaseAssetNames);
    }

    /** @return list<array{name: non-empty-string, browser_download_url: non-empty-string, ...}> */
    private function getReleaseAssetsForPackage(
        Package $package,
        HttpDownloader $httpDownloader,
        DownloadUrlMethod $downloadUrlMethod,
    ): array {
        Assert::notNull($package->downloadUrl());

        try {
            $decodedResponse = $httpDownloader->get(
                $this->githubApiBaseUrl . '/repos/' . $package->githubOrgAndRepository() . '/releases/tags/' . $package->version(),
                [
                    'retry-auth-failure' => true,
                    'http' => [
                        'method' => 'GET',
                        'header' => [],
                    ],
                ],
            )->decodeJson();
        } catch (TransportException $t) {
            /** @link https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#get-a-release-by-tag-name */
            if ($t->getStatusCode() === 404) {
                throw Exception\CouldNotFindReleaseAsset::forPackageWithMissingTag($package, $downloadUrlMethod);
            }

            throw $t;
        }

        Assert::isArray($decodedResponse);
        Assert::keyExists($decodedResponse, 'assets');
        Assert::isList($decodedResponse['assets']);

        return array_map(
            static function (array $asset): array {
                Assert::keyExists($asset, 'name');
                Assert::stringNotEmpty($asset['name']);
                Assert::keyExists($asset, 'browser_download_url');
                Assert::stringNotEmpty($asset['browser_download_url']);

                return $asset;
            },
            $decodedResponse['assets'],
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Downloading;

use Composer\Package\CompletePackageInterface;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\Platform\OperatingSystem;
use Php\Pie\Platform\PrePackagedBinaryAssetName;
use Php\Pie\Platform\PrePackagedSourceAssetName;
use Php\Pie\Platform\TargetPlatform;
use Php\Pie\Platform\WindowsExtensionAssetName;

use function array_key_exists;
use function array_merge;
use function assert;
use function is_string;
use function method_exists;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
enum DownloadUrlMethod: string
{
    private const COMPOSER_PACKAGE_EXTRA_KEY = 'download-url-method';

    case ComposerDefaultDownload   = 'composer-default';
    case WindowsBinaryDownload     = 'windows-binary';
    case PrePackagedSourceDownload = 'pre-packaged-source';
    case PrePackagedBinary         = 'pre-packaged-binary';

    /** @return non-empty-list<non-empty-string>|null */
    public function possibleAssetNames(Package $package, TargetPlatform $targetPlatform): array|null
    {
        return match ($this) {
            self::WindowsBinaryDownload => WindowsExtensionAssetName::zipNames($targetPlatform, $package),
            self::PrePackagedSourceDownload => PrePackagedSourceAssetName::packageNames($package),
            self::ComposerDefaultDownload => null,
            self::PrePackagedBinary => PrePackagedBinaryAssetName::packageNames($targetPlatform, $package),
        };
    }

    public static function fromDownloadedPackage(DownloadedPackage $downloadedPackage): self
    {
        return self::fromComposerPackage($downloadedPackage->package->composerPackage());
    }

    public static function fromComposerPackage(CompletePackageInterface $completePackage): self
    {
        $extra = $completePackage->getExtra();

        return self::from(array_key_exists(self::COMPOSER_PACKAGE_EXTRA_KEY, $extra) && is_string($extra[self::COMPOSER_PACKAGE_EXTRA_KEY]) ? $extra[self::COMPOSER_PACKAGE_EXTRA_KEY] : '');
    }

    public function writeToComposerPackage(CompletePackageInterface $composerPackage): void
    {
        assert(method_exists($composerPackage, 'setExtra'));

        $composerPackage->setExtra(array_merge($composerPackage->getExtra(), [self::COMPOSER_PACKAGE_EXTRA_KEY => $this->value]));
    }

    /** @return non-empty-list<DownloadUrlMethod> */
    public static function possibleDownloadUrlMethodsForPackage(Package $package, TargetPlatform $targetPlatform): array
    {
        /**
         * PIE does not support building on Windows (yet, at least). Maintainers
         * should provide pre-built Windows binaries.
         */
        if ($targetPlatform->operatingSystem === OperatingSystem::Windows) {
            return [self::WindowsBinaryDownload];
        }

        $configuredSupportedMethods = $package->supportedDownloadUrlMethods();
        if ($configuredSupportedMethods === null) {
            return [self::ComposerDefaultDownload];
        }

        return $configuredSupportedMethods;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Downloading\Exception;

use Php\Pie\DependencyResolver\Package;
use Php\Pie\Downloading\DownloadUrlMethod;
use Php\Pie\Platform\TargetPlatform;
use RuntimeException;

use function implode;
use function sprintf;

use const PHP_EOL;

class CouldNotFindReleaseAsset extends RuntimeException
{
    /** @param non-empty-list<non-empty-string> $expectedAssetNames */
    public static function forPackage(TargetPlatform $targetPlatform, Package $package, DownloadUrlMethod $downloadUrlMethod, array $expectedAssetNames): self
    {
        if ($downloadUrlMethod === DownloadUrlMethod::WindowsBinaryDownload) {
            return new self(sprintf(
                'Windows archive with prebuilt extension for %s was not attached on release %s - looked for one of "%s"',
                $package->name(),
                $package->version(),
                implode(', ', $expectedAssetNames),
            ));
        }

        return new self(sprintf(
            'Could not find release asset for %s named one of "%s"',
            $package->prettyNameAndVersion(),
            implode(', ', $expectedAssetNames),
        ));
    }

    public static function forPackageWithMissingTag(Package $package, DownloadUrlMethod $downloadUrlMethod): self
    {
        if (
            $downloadUrlMethod === DownloadUrlMethod::PrePackagedSourceDownload
            && $package->composerPackage()->isDev()
        ) {
            return new self(sprintf(
                'The package %s uses pre-packaged source archives, which are not available for branch aliases such as %s. You should either omit the version constraint to use the latest compatible version, or use a tagged version instead. You can find a list of tagged versions on:%shttps://packagist.org/packages/%s',
                $package->name(),
                $package->version(),
                PHP_EOL . PHP_EOL,
                $package->name(),
            ));
        }

        return new self(sprintf(
            'Could not find release by tag name for %s',
            $package->prettyNameAndVersion(),
        ));
    }

    public static function forMissingWindowsCompiler(TargetPlatform $targetPlatform): self
    {
        return new self(sprintf(
            'Could not determine Windows Compiler for PHP %s on %s',
            $targetPlatform->phpBinaryPath->version(),
            $targetPlatform->operatingSystem->name,
        ));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Composer\Package\CompletePackage;
use Composer\Package\Link;
use Composer\Package\Package;
use Composer\Package\Version\VersionParser;
use Composer\Repository\ArrayRepository;
use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\ExtensionType;
use Php\Pie\Platform\OperatingSystemFamily;
use Php\Pie\Platform\TargetPlatform;
use Php\Pie\Util\Process;
use RuntimeException;
use Safe\Exceptions\FilesystemException;
use Symfony\Component\Process\Exception\ProcessFailedException;

use function array_combine;
use function array_key_exists;
use function array_keys;
use function array_map;
use function count;
use function implode;
use function in_array;
use function Safe\realpath;
use function sprintf;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class BundledPhpExtensionsRepository extends ArrayRepository
{
    /**
     * @var list<array{
     *     name: non-empty-string,
     *     require?: array<string, non-empty-string>,
     *     os-families?: non-empty-list<OperatingSystemFamily>,
     *     type?: ExtensionType,
     *     priority?: int,
     * }>
     */
    private static array $bundledPhpExtensions = [
        ['name' => 'bcmath'],
        ['name' => 'bz2'],
        ['name' => 'calendar'],
        ['name' => 'ctype'],
        [
            'name' => 'curl',
            'require' => ['lib-curl' => '*'],
        ],
        ['name' => 'dba'],
        [
            'name' => 'dom',
            'require' => [
                'php' => '>= 5.2.0',
                'ext-libxml' => '*',
            ],
        ],
        [
            'name' => 'enchant',
            'require' => ['php' => '>= 5.2.0'],
        ],
        ['name' => 'exif'],
        [
            'name' => 'ffi',
            'require' => [
                'php' => '>= 7.4.0',
                'lib-ffi' => '*',
            ],
        ],
        // ['name' => 'gd'], // build failure - ext/gd/gd.c:79:11: fatal error: ft2build.h: No such file or directory
        ['name' => 'gettext'],
        ['name' => 'gmp'],
        ['name' => 'iconv'],
        [
            'name' => 'intl',
            'require' => ['php' => '>= 5.3.0'],
        ],
        ['name' => 'ldap'],
        ['name' => 'mbstring'],
        [
            'name' => 'mysqlnd',
            'require' => [
                'php' => '>= 5.3.0',
                'ext-openssl' => '*',
            ],
        ],
        [
            'name' => 'mysqli',
            'priority' => 90, // must load after mysqlnd
            'require' => [
                'php' => '>= 5.3.0',
                /**
                 * Note: Whilst mysqli can be built without mysqlnd (you could
                 * specify `--with-mysqli=...`, we have to make installation
                 * with PIE practical at least to start with. We can look at
                 * improving this later, but for now something is better than
                 * nothing :)
                 */
                'ext-mysqlnd' => '*',
            ],
        ],
        [
            'name' => 'opcache',
            'type' => ExtensionType::ZendExtension,
            'require' => ['php' => '>= 5.5.0, < 8.5.0'],
        ],
//        ['name' => 'openssl'], // Not building in CI
        ['name' => 'pcntl'],
        [
            'name' => 'pdo',
            'require' => ['php' => '>= 5.1.0'],
        ],
        [
            'name' => 'pdo_mysql',
            'require' => [
                'php' => '>= 5.1.0',
                'ext-pdo' => '*',
            ],
        ],
        [
            'name' => 'pdo_pgsql',
            'require' => [
                'php' => '>= 5.1.0',
                'ext-pdo' => '*',
            ],
        ],
        [
            'name' => 'pdo_sqlite',
            'require' => [
                'php' => '>= 5.1.0',
                'ext-pdo' => '*',
            ],
        ],
        ['name' => 'pgsql'],
        ['name' => 'posix'],
        ['name' => 'readline'],
        ['name' => 'session'],
        ['name' => 'shmop'],
        [
            'name' => 'simplexml',
            'require' => [
                'php' => '>= 5.2.0',
                'ext-libxml' => '*',
            ],
        ],
        ['name' => 'snmp'],
        [
            'name' => 'soap',
            'require' => [
                'php' => '>= 5.2.0',
                'ext-libxml' => '*',
            ],
        ],
        ['name' => 'sockets'],
        [
            'name' => 'sodium',
            'require' => [
                'php' => '>= 7.2.0',
                'lib-sodium' => '*',
            ],
        ],
        [
            'name' => 'sqlite3',
            'require' => ['php' => '>= 5.3.0'],
        ],
        ['name' => 'sysvmsg'],
        ['name' => 'sysvsem'],
        ['name' => 'sysvshm'],
        ['name' => 'tidy'],
        [
            'name' => 'xml',
            'require' => [
                'php' => '>= 5.2.0',
                'ext-libxml' => '*',
            ],
        ],
        [
            'name' => 'xmlreader',
            'require' => [
                'php' => '>= 5.1.0',
                'ext-libxml' => '*',
                'ext-dom' => '*',
            ],
        ],
        [
            'name' => 'xmlwriter',
            'require' => [
                'php' => '>= 5.2.0',
                'ext-libxml' => '*',
            ],
        ],
        [
            'name' => 'xsl',
            'require' => [
                'php' => '>= 5.2.0',
                'ext-libxml' => '*',
                'lib-xslt' => '*',
            ],
        ],
        [
            'name' => 'zip',
            'require' => [
                'php' => '>= 5.2.0',
                'lib-zip' => '*',
            ],
        ],
        ['name' => 'zlib'],
    ];

    public static function forTargetPlatform(TargetPlatform $targetPlatform): self
    {
        $versionParser = new VersionParser();
        $phpVersion    = $targetPlatform->phpBinaryPath->version();

        return new self(array_map(
            static function (array $extension) use ($versionParser, $phpVersion): Package {
                if (! array_key_exists('require', $extension)) {
                    $extension['require'] = ['php' => $phpVersion];
                }

                $requireLinks = array_map(
                    static function (string $target, string $constraint) use ($extension, $versionParser): Link {
                        return new Link(
                            'php/' . $extension['name'],
                            $target,
                            $versionParser->parseConstraints($constraint),
                            'requires',
                            $constraint,
                        );
                    },
                    array_keys($extension['require']),
                    $extension['require'],
                );

                $package = new CompletePackage('php/' . $extension['name'], $phpVersion . '.0', $phpVersion);
                $package->setType(($extension['type'] ?? ExtensionType::PhpModule)->value);
                $package->setDistType('zip');
                $package->setRequires(array_combine(
                    array_map(static fn (Link $link) => $link->getTarget(), $requireLinks),
                    $requireLinks,
                ));
                $package->setDistUrl(sprintf('https://github.com/php/php-src/archive/refs/tags/php-%s.zip', $phpVersion));
                $package->setDistReference(sprintf('php-%s', $phpVersion));
                $phpExt = [
                    'extension-name' => $extension['name'],
                    'build-path' => 'ext/' . $extension['name'],
                ];

                if (array_key_exists('os-families', $extension)) {
                    $phpExt['os-families'] = array_map(
                        static fn (OperatingSystemFamily $osFamily) => $osFamily->value,
                        $extension['os-families'],
                    );
                }

                if (array_key_exists('priority', $extension)) {
                    $phpExt['priority'] = $extension['priority'];
                }

                $package->setPhpExt($phpExt);

                return $package;
            },
            self::$bundledPhpExtensions,
        ));
    }

    private static function findRe2c(): string
    {
        try {
            return Process::run(['which', 're2c']);
        } catch (ProcessFailedException $processFailed) {
            throw new RuntimeException('Unable to find re2c on the system', previous: $processFailed);
        }
    }

    /**
     * @param list<string> $makeCommand
     *
     * @return list<string>
     */
    public static function augmentMakeCommandForPhpBundledExtensions(array $makeCommand, DownloadedPackage $downloadedPackage): array
    {
        $extraCflags = [];
        if (
            in_array($downloadedPackage->package->name(), [
                'php/xmlreader',
                'php/dom',
            ])
        ) {
            try {
                $extraCflags[] = '-I' . realpath($downloadedPackage->extractedSourcePath . '/../..');
            } catch (FilesystemException) {
                // ignore; the path does not exist
            }
        }

        if ($downloadedPackage->package->name() === 'php/dom') {
            try {
                $extraCflags[] = '-I' . realpath($downloadedPackage->extractedSourcePath . '/../../ext/lexbor');
            } catch (FilesystemException) {
                // ignore; the lexbor path does not exist
            }
        }

        if (count($extraCflags)) {
            $makeCommand[] = 'EXTRA_CFLAGS=' . implode(' ', $extraCflags);
        }

        if (
            in_array($downloadedPackage->package->name(), [
                'php/pdo',
                'php/pdo_mysql',
                'php/pdo_pgsql',
                'php/pdo_sqlite',
            ])
        ) {
            $makeCommand[] = 'RE2C=' . self::findRe2c();
        }

        return $makeCommand;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Composer\Composer;
use Composer\Package\CompletePackage;
use Composer\Package\CompletePackageInterface;
use Composer\Pcre\Preg;
use Composer\Repository\PlatformRepository;
use Composer\Semver\VersionParser;
use Php\Pie\ExtensionName;
use Php\Pie\Platform\InstalledPiePackages;
use Php\Pie\Platform\TargetPhp\PhpBinaryPath;
use Php\Pie\Util\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;
use UnexpectedValueException;

use function explode;
use function in_array;
use function str_replace;
use function str_starts_with;
use function strlen;
use function strtolower;
use function substr;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class PhpBinaryPathBasedPlatformRepository extends PlatformRepository
{
    private VersionParser $versionParser;

    public function __construct(PhpBinaryPath $phpBinaryPath, Composer $composer, InstalledPiePackages $installedPiePackages, ExtensionName|null $extensionBeingInstalled)
    {
        $this->versionParser = new VersionParser();
        $this->packages      = [];

        $phpVersion = $phpBinaryPath->version();
        $php        = new CompletePackage('php', $this->versionParser->normalize($phpVersion), $phpVersion);
        $php->setDescription('The PHP interpreter');
        $this->addPackage($php);

        $extVersions = $phpBinaryPath->extensions();

        $piePackages                          = $installedPiePackages->allPiePackages($composer);
        $extensionsBeingReplacedByPiePackages = [];
        foreach ($piePackages->packages() as $piePackage) {
            foreach ($piePackage->composerPackage()->getReplaces() as $replaceLink) {
                $target = $replaceLink->getTarget();
                if (
                    ! str_starts_with($target, 'ext-')
                    || ! ExtensionName::isValidExtensionName(substr($target, strlen('ext-')))
                ) {
                    continue;
                }

                $extensionsBeingReplacedByPiePackages[] = ExtensionName::normaliseFromString($replaceLink->getTarget())->name();
            }
        }

        foreach ($extVersions as $extension => $extensionVersion) {
            /**
             * If the extension we're trying to exclude is not excluded from this list if it is already installed
             * and enabled, it conflicts when running {@see ComposerIntegrationHandler}.
             *
             * @link https://github.com/php/pie/issues/150
             */
            if ($extensionBeingInstalled !== null && $extension === $extensionBeingInstalled->name()) {
                continue;
            }

            /**
             * If any extensions present have `replaces`, we need to remove them otherwise it conflicts too
             *
             * @link https://github.com/php/pie/issues/161
             */
            if (in_array($extension, $extensionsBeingReplacedByPiePackages)) {
                continue;
            }

            $this->addPackage($this->packageForExtension($extension, $extensionVersion));
        }

        $this->addLibrariesUsingPkgConfig();

        parent::__construct();
    }

    private function packageForExtension(string $name, string $prettyVersion): CompletePackageInterface
    {
        $extraDescription = '';

        try {
            $version = $this->versionParser->normalize($prettyVersion);
        } catch (UnexpectedValueException) {
            $extraDescription = ' (actual version: ' . $prettyVersion . ')';
            if (Preg::isMatchStrictGroups('{^(\d+\.\d+\.\d+(?:\.\d+)?)}', $prettyVersion, $match)) {
                $prettyVersion = $match[1];
            } else {
                $prettyVersion = '0';
            }

            $version = $this->versionParser->normalize($prettyVersion);
        }

        $package = new CompletePackage(
            'ext-' . str_replace(' ', '-', strtolower($name)),
            $version,
            $prettyVersion,
        );
        $package->setDescription('The ' . $name . ' PHP extension' . $extraDescription);
        $package->setType('php-ext');

        return $package;
    }

    /**
     * The `$alias` parameter is the name of the dependency in `composer.json`,
     * but without the `lib-` prefix; e.g. `curl` would be `lib-curl` in the
     * `composer.json`.
     *
     * The `$library` parameter should be the name of the library to look up
     * using `pkg-config`.
     */
    private function detectLibraryWithPkgConfig(string $alias, string $library): void
    {
        try {
            $pkgConfigResult = Process::run(['pkg-config', '--print-provides', '--print-errors', $library]);
        } catch (ProcessFailedException) {
            return;
        }

        [$library, $prettyVersion] = explode('=', $pkgConfigResult);
        if (! $library || ! $prettyVersion) {
            return;
        }

        try {
            $version = $this->versionParser->normalize($prettyVersion);
        } catch (UnexpectedValueException) {
            $version = '*'; // @todo check this is the best way to handle unparsed versions?
        }

        $lib = new CompletePackage('lib-' . $alias, $version, $prettyVersion);
        $lib->setDescription('The ' . $alias . ' library, ' . $library);
        $this->addPackage($lib);
    }

    /**
     * Instructions for PIE to install these libraries, if they are missing, should be added
     * into {@see \Php\Pie\DependencyResolver\DependencyInstaller\SystemDependenciesDefinition::default()}
     */
    private function addLibrariesUsingPkgConfig(): void
    {
        $this->detectLibraryWithPkgConfig('curl', 'libcurl');
        $this->detectLibraryWithPkgConfig('enchant', 'enchant');
        $this->detectLibraryWithPkgConfig('enchant-2', 'enchant-2');
        $this->detectLibraryWithPkgConfig('sodium', 'libsodium');
        $this->detectLibraryWithPkgConfig('ffi', 'libffi');
        $this->detectLibraryWithPkgConfig('xslt', 'libxslt');
        $this->detectLibraryWithPkgConfig('zip', 'libzip');
        $this->detectLibraryWithPkgConfig('png', 'libpng');
        $this->detectLibraryWithPkgConfig('avif', 'libavif');
        $this->detectLibraryWithPkgConfig('webp', 'libwebp');
        $this->detectLibraryWithPkgConfig('jpeg', 'libjpeg');
        $this->detectLibraryWithPkgConfig('xpm', 'xpm');
        $this->detectLibraryWithPkgConfig('freetype2', 'freetype2');
        $this->detectLibraryWithPkgConfig('gdlib', 'gdlib');
        $this->detectLibraryWithPkgConfig('gmp', 'gmp');
        $this->detectLibraryWithPkgConfig('sasl', 'libsasl2');
        $this->detectLibraryWithPkgConfig('onig', 'oniguruma');
        $this->detectLibraryWithPkgConfig('odbc', 'libiodbc');
        $this->detectLibraryWithPkgConfig('capstone', 'capstone');
        $this->detectLibraryWithPkgConfig('pcre', 'libpcre2-8');
        $this->detectLibraryWithPkgConfig('edit', 'libedit');
        $this->detectLibraryWithPkgConfig('snmp', 'netsnmp');
        $this->detectLibraryWithPkgConfig('argon2', 'libargon2');
        $this->detectLibraryWithPkgConfig('uriparser', 'liburiparser');
        $this->detectLibraryWithPkgConfig('exslt', 'libexslt');
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Composer\Package\CompletePackageInterface;
use Composer\PartialComposer;
use Php\Pie\Building\Build;
use Php\Pie\Building\PlaceholderReplacer;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\Installing\Install;

use function sprintf;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class InstallAndBuildProcess
{
    public function __construct(
        private readonly Build $pieBuild,
        private readonly Install $pieInstall,
        private readonly AddInstalledJsonMetadata $addInstalledJsonMetadata,
        private readonly PlaceholderReplacer $placeholderReplacer,
    ) {
    }

    public function __invoke(
        PartialComposer $composer,
        PieComposerRequest $composerRequest,
        CompletePackageInterface $composerPackage,
        string $installPath,
    ): void {
        $io = $composerRequest->pieOutput;

        $downloadedPackage = DownloadedPackage::fromPackageAndExtractedPath(
            Package::fromComposerCompletePackage($composerPackage),
            $installPath,
        );

        $io->write(sprintf(
            '<info>Extracted %s source to:</info> %s',
            $downloadedPackage->package->prettyNameAndVersion(),
            $downloadedPackage->extractedSourcePath,
        ));

        $this->placeholderReplacer->replacePlaceholdersWithPlaceholderReplacements(
            $io,
            $composerRequest->targetPlatform,
            $downloadedPackage,
        );

        $this->addInstalledJsonMetadata->addDownloadMetadata(
            $composer,
            $composerRequest,
            $composerPackage,
        );

        $builtBinaryFile = null;
        if ($composerRequest->operation->shouldBuild()) {
            $builtBinaryFile = ($this->pieBuild)(
                $downloadedPackage,
                $composerRequest->targetPlatform,
                $composerRequest->configureOptions,
                $io,
            );

            $this->addInstalledJsonMetadata->addBuildMetadata(
                $composer,
                $composerRequest,
                $composerPackage,
                $builtBinaryFile,
            );
        }

        if (! $composerRequest->operation->shouldInstall()) {
            return;
        }

        $this->addInstalledJsonMetadata->addInstallMetadata(
            $composer,
            $composerPackage,
            ($this->pieInstall)(
                $downloadedPackage,
                $composerRequest->targetPlatform,
                $builtBinaryFile,
                $io,
                $composerRequest->attemptToSetupIniFile,
            ),
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Composer\Composer;
use Composer\Installer;
use Composer\IO\IOInterface;
use Composer\Repository\PlatformRepository;
use Php\Pie\ExtensionName;
use Php\Pie\Platform\InstalledPiePackages;
use Php\Pie\Platform\TargetPhp\PhpBinaryPath;
use Webmozart\Assert\Assert;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class PieComposerInstaller extends Installer
{
    private PhpBinaryPath|null $phpBinaryPath           = null;
    private ExtensionName|null $extensionBeingInstalled = null;
    private Composer|null $composer                     = null;

    protected function createPlatformRepo(bool $forUpdate): PlatformRepository
    {
        Assert::notNull($this->phpBinaryPath, '$phpBinaryPath was not set, maybe createWithPhpBinary was not used?');
        Assert::notNull($this->extensionBeingInstalled, '$extensionBeingInstalled was not set, maybe createWithPhpBinary was not used?');
        Assert::notNull($this->composer, '$composer was not set, maybe createWithPhpBinary was not used?');

        return new PhpBinaryPathBasedPlatformRepository($this->phpBinaryPath, $this->composer, new InstalledPiePackages(), $this->extensionBeingInstalled);
    }

    public static function createWithPhpBinary(
        PhpBinaryPath $php,
        ExtensionName $extensionBeingInstalled,
        IOInterface $io,
        Composer $composer,
    ): self {
        $composerInstaller = new self(
            $io,
            $composer->getConfig(),
            $composer->getPackage(),
            $composer->getDownloadManager(),
            $composer->getRepositoryManager(),
            $composer->getLocker(),
            $composer->getInstallationManager(),
            $composer->getEventDispatcher(),
            $composer->getAutoloadGenerator(),
        );

        $composerInstaller->phpBinaryPath           = $php;
        $composerInstaller->extensionBeingInstalled = $extensionBeingInstalled;
        $composerInstaller->composer                = $composer;

        return $composerInstaller;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Composer\Package\CompleteAliasPackage;
use Composer\Package\CompletePackageInterface;
use Composer\PartialComposer;
use Php\Pie\File\BinaryFile;
use Webmozart\Assert\Assert;

use function array_merge;
use function implode;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class AddInstalledJsonMetadata
{
    public function addDownloadMetadata(
        PartialComposer $composer,
        PieComposerRequest $composerRequest,
        CompletePackageInterface $composerPackage,
    ): void {
        $this->addPieMetadata(
            $composer,
            $composerPackage,
            InstalledJsonMetadata::KEY_TARGET_PLATFORM_PHP_PATH,
            $composerRequest->targetPlatform->phpBinaryPath->phpBinaryPath,
        );
        $this->addPieMetadata(
            $composer,
            $composerPackage,
            InstalledJsonMetadata::KEY_TARGET_PLATFORM_PHP_CONFIG_PATH,
            $composerRequest->targetPlatform->phpBinaryPath->phpConfigPath(),
        );
        $this->addPieMetadata(
            $composer,
            $composerPackage,
            InstalledJsonMetadata::KEY_TARGET_PLATFORM_PHP_VERSION,
            $composerRequest->targetPlatform->phpBinaryPath->version(),
        );
        $this->addPieMetadata(
            $composer,
            $composerPackage,
            InstalledJsonMetadata::KEY_TARGET_PLATFORM_PHP_THREAD_SAFETY,
            $composerRequest->targetPlatform->threadSafety->name,
        );
        $this->addPieMetadata(
            $composer,
            $composerPackage,
            InstalledJsonMetadata::KEY_TARGET_PLATFORM_PHP_WINDOWS_COMPILER,
            $composerRequest->targetPlatform->windowsCompiler?->name,
        );
        $this->addPieMetadata(
            $composer,
            $composerPackage,
            InstalledJsonMetadata::KEY_TARGET_PLATFORM_ARCHITECTURE,
            $composerRequest->targetPlatform->architecture->name,
        );
    }

    public function addBuildMetadata(
        PartialComposer $composer,
        PieComposerRequest $composerRequest,
        CompletePackageInterface $composerPackage,
        BinaryFile $builtBinary,
    ): void {
        $this->addPieMetadata(
            $composer,
            $composerPackage,
            InstalledJsonMetadata::KEY_CONFIGURE_OPTIONS,
            implode(' ', $composerRequest->configureOptions),
        );

        $this->addPieMetadata(
            $composer,
            $composerPackage,
            InstalledJsonMetadata::KEY_PHPIZE_BINARY,
            $composerRequest->targetPlatform->phpizePath->phpizeBinaryPath ?? null,
        );

        $this->addPieMetadata(
            $composer,
            $composerPackage,
            InstalledJsonMetadata::KEY_BUILT_BINARY,
            $builtBinary->filePath,
        );

        $this->addPieMetadata(
            $composer,
            $composerPackage,
            InstalledJsonMetadata::KEY_BINARY_CHECKSUM,
            $builtBinary->checksum,
        );
    }

    public function addInstallMetadata(
        PartialComposer $composer,
        CompletePackageInterface $composerPackage,
        BinaryFile $installedBinary,
    ): void {
        $this->addPieMetadata(
            $composer,
            $composerPackage,
            InstalledJsonMetadata::KEY_INSTALLED_BINARY,
            $installedBinary->filePath,
        );
    }

    /** @param InstalledJsonMetadata::KEY_* $key */
    private function addPieMetadata(
        PartialComposer $composer,
        CompletePackageInterface $composerPackage,
        string $key,
        string|null $value,
    ): void {
        $localRepositoryPackage = $composer
            ->getRepositoryManager()
            ->getLocalRepository()
            ->findPackages($composerPackage->getName())[0];

        if ($localRepositoryPackage instanceof CompleteAliasPackage) {
            $localRepositoryPackage = $localRepositoryPackage->getAliasOf();
        }

        Assert::methodExists($localRepositoryPackage, 'setExtra');

        $localRepositoryPackage->setExtra(array_merge($localRepositoryPackage->getExtra(), [$key => $value]));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
enum PieOperation
{
    case Resolve;
    case Download;
    case Build;
    case Install;
    case Uninstall;

    public function shouldBuild(): bool
    {
        return $this === PieOperation::Build || $this === PieOperation::Install;
    }

    public function shouldInstall(): bool
    {
        return $this === PieOperation::Install;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Composer\Composer;
use Composer\Factory;
use Composer\Installer;
use Composer\IO\IOInterface;
use Composer\PartialComposer;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Util\Filesystem;
use Composer\Util\ProcessExecutor;
use Php\Pie\ComposerIntegration\Listeners\OverrideDownloadUrlInstallListener;
use Php\Pie\ComposerIntegration\Listeners\RemoveUnrelatedInstallOperations;
use Php\Pie\ExtensionType;
use Php\Pie\Platform;
use Psr\Container\ContainerInterface;
use Webmozart\Assert\Assert;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class PieComposerFactory extends Factory
{
    public function __construct(
        private readonly ContainerInterface $container,
        private readonly PieComposerRequest $composerRequest,
    ) {
    }

    protected function createDefaultInstallers(Installer\InstallationManager $im, PartialComposer $composer, IOInterface $io, ProcessExecutor|null $process = null): void
    {
        $fs = new Filesystem($process);

        $installerFactory = function (ExtensionType $type) use ($io, $composer, $fs): PiePackageInstaller {
            return new PiePackageInstaller(
                $io,
                $composer,
                $type,
                $fs,
                $this->container->get(InstallAndBuildProcess::class),
                $this->container->get(UninstallProcess::class),
                $this->composerRequest,
            );
        };

        $im->addInstaller($installerFactory(ExtensionType::PhpModule));
        $im->addInstaller($installerFactory(ExtensionType::ZendExtension));
    }

    public static function createPieComposer(
        ContainerInterface $container,
        PieComposerRequest $composerRequest,
    ): Composer {
        $pieComposer = Platform::getPieJsonFilename($composerRequest->targetPlatform);

        PieJsonEditor::fromTargetPlatform($composerRequest->targetPlatform)->ensureExists();

        $io       = $container->get(QuieterConsoleIO::class);
        $composer = (new PieComposerFactory($container, $composerRequest))->createComposer(
            $io,
            $pieComposer,
            true,
        );

        $composer
            ->getRepositoryManager()
            ->addRepository(BundledPhpExtensionsRepository::forTargetPlatform(
                $composerRequest->targetPlatform,
            ));

        OverrideDownloadUrlInstallListener::selfRegister($composer, $io, $container, $composerRequest);
        RemoveUnrelatedInstallOperations::selfRegister($composer, $composerRequest);

        $composer->getConfig()->merge(['config' => ['__PIE_REQUEST__' => $composerRequest]]);
        $io->loadConfiguration($composer->getConfig());

        return $composer;
    }

    protected function purgePackages(InstalledRepositoryInterface $repo, Installer\InstallationManager $im): void
    {
        /**
         * This is intentionally a no-op in PIE....
         *
         * Why not purge packages?
         *
         * We have a post install job in {@see VendorCleanup} that cleans up the vendor directory to remove all the
         * actual package files; however, this means that Composer thinks they are not installed after that. When
         * creating the Composer instance, the last step is to purge packages from the
         * {@see InstalledRepositoryInterface} if they no longer exist on disk. But, that means we can't list the
         * packages installed with PIE any more! So, we override this method to become a no-op ✅
         */
    }

    public static function recreatePieComposer(
        ContainerInterface $container,
        Composer $existingComposer,
    ): Composer {
        $composerRequest = $existingComposer->getConfig()->get('__PIE_REQUEST__');

        Assert::isInstanceOf($composerRequest, PieComposerRequest::class);

        return self::createPieComposer($container, $composerRequest);
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\QuestionHelper;
use Webmozart\Assert\Assert;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class MinimalHelperSet extends HelperSet
{
    /** @param array{question?: QuestionHelper|mixed} $helpers */
    public function __construct(array $helpers)
    {
        Assert::isInstanceOf(
            $helpers['question'] ?? null,
            QuestionHelper::class,
            'The question option must be an instance of %2$s, got %s',
        );

        parent::__construct($helpers);
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Composer\Installer;
use RuntimeException;

use function max;
use function sprintf;

class ComposerRunFailed extends RuntimeException
{
    public static function fromExitCode(int $exitCode): self
    {
        return new self(
            sprintf(
                'PIE Composer run failed with error code %d%s',
                $exitCode,
                self::tryDetermineConstant($exitCode),
            ),
            max(1, $exitCode), // ensure we are always non-zero for exit codes
        );
    }

    private static function tryDetermineConstant(int $errorCode): string
    {
        switch ($errorCode) {
            case Installer::ERROR_GENERIC_FAILURE:
                return ' (ERROR_GENERIC_FAILURE)';

            case Installer::ERROR_NO_LOCK_FILE_FOR_PARTIAL_UPDATE:
                return ' (ERROR_NO_LOCK_FILE_FOR_PARTIAL_UPDATE)';

            case Installer::ERROR_LOCK_FILE_INVALID:
                return ' (ERROR_LOCK_FILE_INVALID)';

            case Installer::ERROR_DEPENDENCY_RESOLUTION_FAILED:
                return ' (ERROR_DEPENDENCY_RESOLUTION_FAILED)';

            case Installer::ERROR_AUDIT_FAILED:
                return ' (ERROR_AUDIT_FAILED)';

            case Installer::ERROR_TRANSPORT_EXCEPTION:
                return ' (ERROR_TRANSPORT_EXCEPTION)';

            default:
                return '';
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Composer\Package\CompletePackageInterface;

use function array_key_exists;
use function is_string;

/**
 * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
 *
 * @phpstan-type PieMetadata = array{
 *     pie-target-platform-php-path?: non-empty-string,
 *     pie-target-platform-php-config-path?: non-empty-string,
 *     pie-target-platform-php-version?: non-empty-string,
 *     pie-target-platform-php-thread-safety?: non-empty-string,
 *     pie-target-platform-php-windows-compiler?: non-empty-string,
 *     pie-target-platform-architecture?: non-empty-string,
 *     pie-configure-options?: non-empty-string,
 *     pie-built-binary?: non-empty-string,
 *     pie-installed-binary-checksum?: non-empty-string,
 *     pie-installed-binary?: non-empty-string,
 *     pie-phpize-binary?: non-empty-string,
 * }
 */
final class InstalledJsonMetadata
{
    public const KEY_TARGET_PLATFORM_PHP_PATH             = 'pie-target-platform-php-path';
    public const KEY_TARGET_PLATFORM_PHP_CONFIG_PATH      = 'pie-target-platform-php-config-path';
    public const KEY_TARGET_PLATFORM_PHP_VERSION          = 'pie-target-platform-php-version';
    public const KEY_TARGET_PLATFORM_PHP_THREAD_SAFETY    = 'pie-target-platform-php-thread-safety';
    public const KEY_TARGET_PLATFORM_PHP_WINDOWS_COMPILER = 'pie-target-platform-php-windows-compiler';
    public const KEY_TARGET_PLATFORM_ARCHITECTURE         = 'pie-target-platform-architecture';
    public const KEY_CONFIGURE_OPTIONS                    = 'pie-configure-options';
    public const KEY_BUILT_BINARY                         = 'pie-built-binary';
    public const KEY_BINARY_CHECKSUM                      = 'pie-installed-binary-checksum';
    public const KEY_INSTALLED_BINARY                     = 'pie-installed-binary';
    public const KEY_PHPIZE_BINARY                        = 'pie-phpize-binary';

    private const ALL_KEYS = [
        self::KEY_TARGET_PLATFORM_PHP_PATH,
        self::KEY_TARGET_PLATFORM_PHP_CONFIG_PATH,
        self::KEY_TARGET_PLATFORM_PHP_VERSION,
        self::KEY_TARGET_PLATFORM_PHP_THREAD_SAFETY,
        self::KEY_TARGET_PLATFORM_PHP_WINDOWS_COMPILER,
        self::KEY_TARGET_PLATFORM_ARCHITECTURE,
        self::KEY_CONFIGURE_OPTIONS,
        self::KEY_BUILT_BINARY,
        self::KEY_BINARY_CHECKSUM,
        self::KEY_INSTALLED_BINARY,
        self::KEY_PHPIZE_BINARY,
    ];

    /** @param PieMetadata $values */
    private function __construct(private readonly array $values)
    {
    }

    /** @param PieMetadata $values */
    public static function fromArray(array $values): self
    {
        return new self($values);
    }

    public static function fromComposerPackage(CompletePackageInterface $composerPackage): self
    {
        $composerPackageExtras = $composerPackage->getExtra();

        $onlyPieExtras = [];

        foreach (self::ALL_KEYS as $key) {
            if (
                ! array_key_exists($key, $composerPackageExtras)
                || ! is_string($composerPackageExtras[$key])
                || $composerPackageExtras[$key] === ''
            ) {
                continue;
            }

            $onlyPieExtras[$key] = $composerPackageExtras[$key];
        }

        return new self($onlyPieExtras);
    }

    /** @return PieMetadata */
    public function all(): array
    {
        return $this->values;
    }

    /** @return non-empty-string|null */
    private function nonEmptyStringOrNull(string $key): string|null
    {
        return array_key_exists($key, $this->values)
            ? $this->values[$key]
            : null;
    }

    /** @return non-empty-string|null */
    public function targetPlatformPhpPath(): string|null
    {
        return $this->nonEmptyStringOrNull(self::KEY_TARGET_PLATFORM_PHP_PATH);
    }

    /** @return non-empty-string|null */
    public function targetPlatformPhpConfigPath(): string|null
    {
        return $this->nonEmptyStringOrNull(self::KEY_TARGET_PLATFORM_PHP_CONFIG_PATH);
    }

    /** @return non-empty-string|null */
    public function targetPlatformPhpVersion(): string|null
    {
        return $this->nonEmptyStringOrNull(self::KEY_TARGET_PLATFORM_PHP_VERSION);
    }

    /** @return non-empty-string|null */
    public function targetPlatformPhpThreadSafety(): string|null
    {
        return $this->nonEmptyStringOrNull(self::KEY_TARGET_PLATFORM_PHP_THREAD_SAFETY);
    }

    /** @return non-empty-string|null */
    public function targetPlatformPhpWindowsCompiler(): string|null
    {
        return $this->nonEmptyStringOrNull(self::KEY_TARGET_PLATFORM_PHP_WINDOWS_COMPILER);
    }

    /** @return non-empty-string|null */
    public function targetPlatformArchitecture(): string|null
    {
        return $this->nonEmptyStringOrNull(self::KEY_TARGET_PLATFORM_ARCHITECTURE);
    }

    /** @return non-empty-string|null */
    public function configureOptions(): string|null
    {
        return $this->nonEmptyStringOrNull(self::KEY_CONFIGURE_OPTIONS);
    }

    /** @return non-empty-string|null */
    public function builtBinary(): string|null
    {
        return $this->nonEmptyStringOrNull(self::KEY_BUILT_BINARY);
    }

    /** @return non-empty-string|null */
    public function binaryChecksum(): string|null
    {
        return $this->nonEmptyStringOrNull(self::KEY_BINARY_CHECKSUM);
    }

    /** @return non-empty-string|null */
    public function installedBinary(): string|null
    {
        return $this->nonEmptyStringOrNull(self::KEY_INSTALLED_BINARY);
    }

    /** @return non-empty-string|null */
    public function phpizeBinary(): string|null
    {
        return $this->nonEmptyStringOrNull(self::KEY_PHPIZE_BINARY);
    }

    /** Has this package been downloaded, according to the metadata? (note: does not verifiy it is STILL downloaded - especially if vendor cleanup happened!) */
    public function isDownloaded(): bool
    {
        return $this->targetPlatformPhpVersion() !== null;
    }

    /** Has this package been built, according to the metadata? (note: does not verify it is STILL built) */
    public function isBuilt(): bool
    {
        return $this->isDownloaded() && $this->builtBinary() !== null;
    }

    /** Has this package been installed, according to the metadata (note: not verify it is STILL installed/verified) */
    public function isInstalled(): bool
    {
        return $this->isBuilt() && $this->installedBinary() !== null;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration\Listeners;

use Closure;
use Composer\Composer;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\DependencyResolver\Operation\UninstallOperation;
use Composer\DependencyResolver\Transaction;
use Composer\Installer\InstallerEvent;
use Composer\Installer\InstallerEvents;
use Composer\IO\IOInterface;
use Php\Pie\ComposerIntegration\PieComposerRequest;

use function array_filter;
use function assert;
use function sprintf;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class RemoveUnrelatedInstallOperations
{
    public function __construct(
        private readonly PieComposerRequest $composerRequest,
    ) {
    }

    public static function selfRegister(
        Composer $composer,
        PieComposerRequest $composerRequest,
    ): void {
        $composer
            ->getEventDispatcher()
            ->addListener(
                InstallerEvents::PRE_OPERATIONS_EXEC,
                new self($composerRequest),
            );
    }

    public function __invoke(InstallerEvent $installerEvent): void
    {
        $pieOutput = $this->composerRequest->pieOutput;

        $newOperations = array_filter(
            $installerEvent->getTransaction()?->getOperations() ?? [],
            function (OperationInterface $operation) use ($pieOutput): bool {
                if (! $operation instanceof InstallOperation && ! $operation instanceof UninstallOperation) {
                    $pieOutput->writeError(
                        sprintf(
                            'Unexpected operation during installer: %s',
                            $operation::class,
                        ),
                        verbosity: IOInterface::VERY_VERBOSE,
                    );

                    return false;
                }

                $isRequestedPiePackage = $this->composerRequest->requestedPackage->package === $operation->getPackage()->getName();

                if (! $isRequestedPiePackage) {
                    $pieOutput->writeError(
                        sprintf(
                            'Filtering package %s from install operations, as it was not the requested package',
                            $operation->getPackage()->getName(),
                        ),
                        verbosity: IOInterface::VERY_VERBOSE,
                    );
                }

                return $isRequestedPiePackage;
            },
        );

        $overrideOperations = Closure::Bind(
            static function (Transaction $transaction) use ($newOperations): void {
                $transaction->operations = $newOperations;
            },
            null,
            Transaction::class,
        );
        assert($overrideOperations !== null);
        $overrideOperations($installerEvent->getTransaction());
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration\Listeners;

use Composer\Composer;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\Installer\InstallerEvent;
use Composer\Installer\InstallerEvents;
use Composer\IO\IOInterface;
use Composer\Package\CompletePackageInterface;
use Composer\Util\HttpDownloader;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\Downloading\DownloadUrlMethod;
use Php\Pie\Downloading\PackageReleaseAssets;
use Psr\Container\ContainerInterface;
use Throwable;

use function array_walk;
use function pathinfo;

use const PATHINFO_EXTENSION;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class OverrideDownloadUrlInstallListener
{
    public function __construct(
        private readonly Composer $composer,
        private readonly IOInterface $io,
        private readonly ContainerInterface $container,
        private readonly PieComposerRequest $composerRequest,
    ) {
    }

    public static function selfRegister(
        Composer $composer,
        IOInterface $io,
        ContainerInterface $container,
        PieComposerRequest $composerRequest,
    ): void {
        $composer
            ->getEventDispatcher()
            ->addListener(
                InstallerEvents::PRE_OPERATIONS_EXEC,
                new self($composer, $io, $container, $composerRequest),
            );
    }

    public function __invoke(InstallerEvent $installerEvent): void
    {
        $operations = $installerEvent->getTransaction()?->getOperations() ?? [];

        array_walk(
            $operations,
            function (OperationInterface $operation): void {
                if (! $operation instanceof InstallOperation) {
                    return;
                }

                $composerPackage = $operation->getPackage();
                if (! $composerPackage instanceof CompletePackageInterface) {
                    return;
                }

                // Install requests for other packages than the one we want should be ignored
                if ($this->composerRequest->requestedPackage->package !== $composerPackage->getName()) {
                    return;
                }

                $piePackage         = Package::fromComposerCompletePackage($composerPackage);
                $targetPlatform     = $this->composerRequest->targetPlatform;
                $downloadUrlMethods = DownloadUrlMethod::possibleDownloadUrlMethodsForPackage($piePackage, $targetPlatform);

                $selectedDownloadUrlMethod = null;
                $downloadMethodFailures    = [];

                foreach ($downloadUrlMethods as $downloadUrlMethod) {
                    $this->io->write('Trying to download using: ' . $downloadUrlMethod->value, verbosity: IOInterface::VERY_VERBOSE);

                    if ($downloadUrlMethod === DownloadUrlMethod::PrePackagedBinary && $this->composerRequest->configureOptions !== []) {
                        $configureOptionsConflictMessage = 'Cannot use pre-packaged-binary download method, as configure options were passed.';

                        $downloadMethodFailures[$downloadUrlMethod->value] = $configureOptionsConflictMessage;
                        $this->io->write($configureOptionsConflictMessage, verbosity: IOInterface::VERBOSE);

                        continue;
                    }

                    // Exit early if we should just use Composer's normal download
                    if ($downloadUrlMethod === DownloadUrlMethod::ComposerDefaultDownload) {
                        $selectedDownloadUrlMethod = $downloadUrlMethod;
                        break;
                    }

                    try {
                        $possibleAssetNames = $downloadUrlMethod->possibleAssetNames($piePackage, $targetPlatform);
                    } catch (Throwable $t) {
                        $downloadMethodFailures[$downloadUrlMethod->value] = $t->getMessage();
                        $this->io->write('Failed fetching asset names [' . $downloadUrlMethod->value . ']: ' . $t->getMessage(), verbosity: IOInterface::VERBOSE);
                        continue;
                    }

                    if ($possibleAssetNames === null) {
                        $downloadMethodFailures[$downloadUrlMethod->value] = 'No asset names';
                        $this->io->write('Failed fetching asset names [' . $downloadUrlMethod->value . ']: No asset names', verbosity: IOInterface::VERBOSE);
                        continue;
                    }

                    // @todo https://github.com/php/pie/issues/138 will need to depend on the repo type (GH/GL/BB/etc.)
                    $packageReleaseAssets = $this->container->get(PackageReleaseAssets::class);

                    try {
                        $url = $packageReleaseAssets->findMatchingReleaseAssetUrl(
                            $targetPlatform,
                            $piePackage,
                            new HttpDownloader($this->io, $this->composer->getConfig()),
                            $downloadUrlMethod,
                            $possibleAssetNames,
                        );
                    } catch (Throwable $t) {
                        $downloadMethodFailures[$downloadUrlMethod->value] = $t->getMessage();
                        $this->io->write('Failed locating asset [' . $downloadUrlMethod->value . ']: ' . $t->getMessage(), verbosity: IOInterface::VERBOSE);
                        continue;
                    }

                    $this->composerRequest->pieOutput->write('Found prebuilt archive: ' . $url);
                    $composerPackage->setDistUrl($url);

                    // Composer's dist-sha was computed against the original
                    // Packagist URL; once we swap to a release-asset URL the
                    // FileDownloader has nothing to validate the new bytes
                    // against. Surface that so the caller knows HTTPS-to-origin
                    // is the only integrity guarantee left.
                    $this->composerRequest->pieOutput->write(
                        '<warning>Note: dist-sha integrity check is not available for prebuilt-binary URLs; HTTPS to the release-asset origin is the only integrity guarantee.</warning>',
                    );

                    if (pathinfo($url, PATHINFO_EXTENSION) === 'tgz') {
                        $composerPackage->setDistType('tar');
                    }

                    $selectedDownloadUrlMethod = $downloadUrlMethod;
                    break;
                }

                if ($selectedDownloadUrlMethod === null) {
                    throw CouldNotDetermineDownloadUrlMethod::fromDownloadUrlMethods($piePackage, $downloadUrlMethods, $downloadMethodFailures);
                }

                $selectedDownloadUrlMethod->writeToComposerPackage($composerPackage);
                $this->io->write('<info>Selected download URL method: ' . $selectedDownloadUrlMethod->value . '</info>', verbosity: IOInterface::VERBOSE);
            },
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration\Listeners;

use Php\Pie\DependencyResolver\Package;
use Php\Pie\Downloading\DownloadUrlMethod;
use RuntimeException;

use function array_key_first;
use function count;
use function sprintf;

use const PHP_EOL;

class CouldNotDetermineDownloadUrlMethod extends RuntimeException
{
    /**
     * @param non-empty-list<DownloadUrlMethod> $downloadMethods
     * @param array<string, string>             $failureReasons
     */
    public static function fromDownloadUrlMethods(Package $piePackage, array $downloadMethods, array $failureReasons): self
    {
        $message = sprintf('Could not download %s', $piePackage->name());

        if (count($downloadMethods) === 1) {
            $first    = array_key_first($downloadMethods);
            $message .= sprintf(' using %s method: %s', $downloadMethods[$first]->value, $failureReasons[$downloadMethods[$first]->value] ?? '(unknown failure)');

            return new self($message);
        }

        $message .= ' using the following methods:' . PHP_EOL;

        foreach ($downloadMethods as $downloadMethod) {
            $message .= sprintf(' - %s: %s%s', $downloadMethod->value, $failureReasons[$downloadMethod->value] ?? '(unknown failure)', PHP_EOL);
        }

        return new self($message);
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Composer\Composer;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Installer;
use Composer\IO\IOInterface;
use Composer\Package\CompleteAliasPackage;
use Composer\Package\CompletePackageInterface;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\DependencyResolver\RequestedPackageAndVersion;
use Php\Pie\ExtensionName;
use Php\Pie\Platform;
use Php\Pie\Platform\TargetPlatform;
use Php\Pie\Util\Emoji;
use Psr\Container\ContainerInterface;

use function assert;
use function file_exists;
use function sprintf;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class ComposerIntegrationHandler
{
    public function __construct(
        private readonly ContainerInterface $container,
        private readonly QuieterConsoleIO $arrayCollectionIo,
        private readonly VendorCleanup $vendorCleanup,
    ) {
    }

    public function runInstall(
        Package $package,
        Composer $composer,
        TargetPlatform $targetPlatform,
        RequestedPackageAndVersion $requestedPackageAndVersion,
        bool $forceInstallPackageVersion,
        bool $runCleanup,
    ): void {
        $versionSelector = VersionSelectorFactory::make($composer, $requestedPackageAndVersion, $targetPlatform);

        $recommendedRequireVersion = $requestedPackageAndVersion->version;

        // If user did not request a specific require version, use Composer to recommend one for the pie.json
        if ($recommendedRequireVersion === null) {
            $recommendedRequireVersion = $versionSelector->findRecommendedRequireVersion($package->composerPackage());
        }

        if ($package->isBundledPhpExtension()) {
            $stability       = $package->composerPackage()->getStability();
            $stabilitySuffix = '';
            if ($stability !== 'stable') {
                $stabilitySuffix = '@' . $stability;
            }

            $recommendedRequireVersion = '*' . $stabilitySuffix;
        }

        // Write the new requirement to pie.json; because we later essentially just do a `composer install` using that file
        $pieComposerJson        = Platform::getPieJsonFilename($targetPlatform);
        $pieJsonEditor          = PieJsonEditor::fromTargetPlatform($targetPlatform);
        $originalPieJsonContent = $pieJsonEditor->addRequire(
            $requestedPackageAndVersion->package,
            $recommendedRequireVersion !== '' ? $recommendedRequireVersion : '*',
        );

        // Refresh the Composer instance so it re-reads the updated pie.json
        $composer = PieComposerFactory::recreatePieComposer($this->container, $composer);

        foreach ($composer->getRepositoryManager()->getLocalRepository()->getPackages() as $localRepoPackage) {
            $extName = ExtensionName::determineFromComposerPackage($localRepoPackage);

            if ($localRepoPackage instanceof CompleteAliasPackage) {
                $localRepoPackage = $localRepoPackage->getAliasOf();
            }

            assert($localRepoPackage instanceof CompletePackageInterface);
            $piePackage            = Package::fromComposerCompletePackage($localRepoPackage);
            $installedJsonMetadata = $piePackage->installedJsonMetadata();
            $status                = $piePackage->verifyPackageStatus($targetPlatform);

            $this->arrayCollectionIo->write(sprintf(
                'Install status %s (%s) status=%s',
                $localRepoPackage->getName(),
                $extName->name(),
                $status->description(),
            ), verbosity: IOInterface::VERY_VERBOSE);

            if ($status->isVerified() && ! $forceInstallPackageVersion) {
                $this->arrayCollectionIo->write(sprintf(
                    '%s PIE package %s (%s) is already installed and verified.',
                    Emoji::GREEN_CHECKMARK,
                    $localRepoPackage->getName(),
                    $extName->name(),
                ), verbosity: IOInterface::QUIET);
                continue;
            }

            if (! $installedJsonMetadata->isInstalled() && $installedJsonMetadata->isBuilt()) {
                $this->arrayCollectionIo->write(sprintf(
                    '%s PIE package %s (%s) was previously built but not installed.',
                    Emoji::INFO,
                    $localRepoPackage->getName(),
                    $extName->name(),
                ), verbosity: IOInterface::VERBOSE);
            }

            if (! $installedJsonMetadata->isInstalled() && ! $installedJsonMetadata->isBuilt() && $installedJsonMetadata->isDownloaded()) {
                $this->arrayCollectionIo->write(sprintf(
                    '%s PIE package %s (%s) was previously downloaded but not built.',
                    Emoji::INFO,
                    $localRepoPackage->getName(),
                    $extName->name(),
                ), verbosity: IOInterface::VERBOSE);
            }

            $this->arrayCollectionIo->write(sprintf(
                '%s Package status of %s (%s) is not yet verified, adding to install candidates: %s',
                Emoji::WARNING,
                $localRepoPackage->getName(),
                $extName->name(),
                $status->description(),
            ));
            $composer->getRepositoryManager()->getLocalRepository()->removePackage($localRepoPackage);
        }

        $composerInstaller = PieComposerInstaller::createWithPhpBinary(
            $targetPlatform->phpBinaryPath,
            $package->extensionName(),
            $this->arrayCollectionIo,
            $composer,
        );
        $composerInstaller
            ->setAllowedTypes(['php-ext', 'php-ext-zend'])
            ->setInstall(true)
            ->setIgnoredTypes([])
            ->setDryRun(false)
            ->setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($forceInstallPackageVersion))
            ->setDownloadOnly(false);

        if (file_exists(PieComposerFactory::getLockFile($pieComposerJson))) {
            $composerInstaller->setUpdate(true);
            $composerInstaller->setUpdateAllowList([$requestedPackageAndVersion->package]);
        }

        $resultCode = $composerInstaller->run();

        if ($resultCode !== Installer::ERROR_NONE) {
            // Revert composer.json change
            $pieJsonEditor->revert($originalPieJsonContent);

            throw ComposerRunFailed::fromExitCode($resultCode);
        }

        if (! $runCleanup) {
            return;
        }

        ($this->vendorCleanup)($composer);
    }

    public function runUninstall(
        Package $packageToRemove,
        Composer $composer,
        TargetPlatform $targetPlatform,
        RequestedPackageAndVersion $requestedPackageAndVersionToRemove,
    ): void {
        // Write the new requirement to pie.json; because we later essentially just do a `composer install` using that file
        $pieComposerJson        = Platform::getPieJsonFilename($targetPlatform);
        $pieJsonEditor          = PieJsonEditor::fromTargetPlatform($targetPlatform);
        $originalPieJsonContent = $pieJsonEditor->removeRequire($requestedPackageAndVersionToRemove->package);

        // Refresh the Composer instance so it re-reads the updated pie.json
        $composer = PieComposerFactory::recreatePieComposer($this->container, $composer);

        $composerInstaller = PieComposerInstaller::createWithPhpBinary(
            $targetPlatform->phpBinaryPath,
            $packageToRemove->extensionName(),
            $this->arrayCollectionIo,
            $composer,
        );
        $composerInstaller
            ->setAllowedTypes(['php-ext', 'php-ext-zend'])
            ->setInstall(true)
            ->setIgnoredTypes([])
            ->setDryRun(false)
            ->setDownloadOnly(false);

        if (file_exists(PieComposerFactory::getLockFile($pieComposerJson))) {
            $composerInstaller->setUpdate(true);
            $composerInstaller->setUpdateAllowList([$requestedPackageAndVersionToRemove->package]);
        }

        $resultCode = $composerInstaller->run();

        if ($resultCode !== Installer::ERROR_NONE) {
            // Revert composer.json change
            $pieJsonEditor->revert($originalPieJsonContent);

            throw ComposerRunFailed::fromExitCode($resultCode);
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Composer\Config\JsonConfigSource;
use Composer\Json\JsonFile;
use Php\Pie\Platform;
use Php\Pie\Platform\TargetPlatform;
use RuntimeException;
use Safe\Exceptions\FilesystemException;

use function file_exists;
use function rtrim;
use function Safe\file_get_contents;
use function Safe\file_put_contents;
use function Safe\mkdir;
use function sprintf;
use function str_replace;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class PieJsonEditor
{
    public const PACKAGIST_ORG_KEY = 'packagist.org';

    public function __construct(
        private readonly string $pieJsonFilename,
        private readonly string $pieWorkingDirectory,
    ) {
    }

    public static function fromTargetPlatform(TargetPlatform $targetPlatform): self
    {
        return new self(
            Platform::getPieJsonFilename($targetPlatform),
            Platform::getPieWorkingDirectory($targetPlatform),
        );
    }

    /**
     * Ensure the given `pie.json` exists; if it does not exist, create an
     * empty but valid `pie.json`.
     */
    public function ensureExists(): self
    {
        if (file_exists($this->pieJsonFilename)) {
            return $this;
        }

        if (! file_exists($this->pieWorkingDirectory)) {
            mkdir($this->pieWorkingDirectory, recursive: true);
        }

        try {
            file_put_contents($this->pieJsonFilename, "{\n}\n");
        } catch (FilesystemException $previous) {
            throw new RuntimeException(
                sprintf(
                    'Failed to create pie.json in %s (working directory: %s)',
                    $this->pieJsonFilename,
                    $this->pieWorkingDirectory,
                ),
                previous: $previous,
            );
        }

        return $this;
    }

    /**
     * Add a package to the `require` section of the given `pie.json`. Returns
     * the original `pie.json` content, in case it needs to be restored later.
     *
     * @param non-empty-string $package
     * @param non-empty-string $version
     */
    public function addRequire(string $package, string $version): string
    {
        $originalPieJsonContent = file_get_contents($this->pieJsonFilename);

        (new JsonConfigSource(
            new JsonFile(
                $this->pieJsonFilename,
            ),
        ))->addLink('require', $package, $version);

        return $originalPieJsonContent;
    }

    /**
     * Remove a package from the `require` section of the given `pie.json`.
     * Returns the original `pie.json` content, in case it needs to be
     * restored later.
     *
     * @param non-empty-string $package
     */
    public function removeRequire(string $package): string
    {
        $originalPieJsonContent = file_get_contents($this->pieJsonFilename);

        (new JsonConfigSource(
            new JsonFile(
                $this->pieJsonFilename,
            ),
        ))->removeLink('require', $package);

        return $originalPieJsonContent;
    }

    public function revert(string $originalPieJsonContent): void
    {
        file_put_contents($this->pieJsonFilename, $originalPieJsonContent);
    }

    public function excludePackagistOrg(): string
    {
        $originalPieJsonContent = file_get_contents($this->pieJsonFilename);

        (new JsonConfigSource(
            new JsonFile(
                $this->pieJsonFilename,
            ),
        ))
            ->addRepository(self::PACKAGIST_ORG_KEY, false);

        return $originalPieJsonContent;
    }

    /**
     * Add a repository to the given `pie.json`. Returns the original
     * `pie.json` content, in case it needs to be restored later.
     *
     * @param 'vcs'|'path'|'composer' $type
     * @param non-empty-string        $url
     */
    public function addRepository(
        string $type,
        string $url,
    ): string {
        $originalPieJsonContent = file_get_contents($this->pieJsonFilename);

        (new JsonConfigSource(
            new JsonFile(
                $this->pieJsonFilename,
            ),
        ))
            ->addRepository($this->normaliseRepositoryName($url), [
                'type' => $type,
                'url' => $url,
            ]);

        return $originalPieJsonContent;
    }

    /**
     * Remove a repository from the given `pie.json`. Returns the original
     * `pie.json` content, in case it needs to be restored later.
     *
     * @param non-empty-string $name
     */
    public function removeRepository(
        string $name,
    ): string {
        $originalPieJsonContent = file_get_contents($this->pieJsonFilename);

        (new JsonConfigSource(
            new JsonFile(
                $this->pieJsonFilename,
            ),
        ))
            ->removeRepository($this->normaliseRepositoryName($name));

        return $originalPieJsonContent;
    }

    private function normaliseRepositoryName(string $url): string
    {
        return rtrim(str_replace('\\', '/', $url), '/');
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Composer\Composer;
use Composer\Package\Version\VersionSelector;
use Composer\Repository\CompositeRepository;
use Composer\Repository\RepositorySet;
use Php\Pie\DependencyResolver\DetermineMinimumStability;
use Php\Pie\DependencyResolver\RequestedPackageAndVersion;
use Php\Pie\Platform\InstalledPiePackages;
use Php\Pie\Platform\TargetPlatform;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class VersionSelectorFactory
{
    private function __construct()
    {
    }

    private static function factoryRepositorySet(Composer $composer, RequestedPackageAndVersion $requestedPackageAndVersion): RepositorySet
    {
        $repositorySet = new RepositorySet(DetermineMinimumStability::fromRequestedVersion($requestedPackageAndVersion));
        $repositorySet->addRepository(new CompositeRepository($composer->getRepositoryManager()->getRepositories()));

        return $repositorySet;
    }

    public static function make(
        Composer $composer,
        RequestedPackageAndVersion $requestedPackageAndVersion,
        TargetPlatform $targetPlatform,
    ): VersionSelector {
        return new VersionSelector(
            self::factoryRepositorySet($composer, $requestedPackageAndVersion),
            new PhpBinaryPathBasedPlatformRepository($targetPlatform->phpBinaryPath, $composer, new InstalledPiePackages(), null),
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Composer\IO\IOInterface;
use Composer\Package\CompletePackageInterface;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\Installing\Ini\RemoveIniEntry;
use Php\Pie\Installing\Uninstall;

use function array_walk;
use function count;
use function reset;
use function sprintf;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class UninstallProcess
{
    public function __construct(
        private readonly RemoveIniEntry $removeIniEntry,
        private readonly Uninstall $uninstall,
    ) {
    }

    public function __invoke(
        PieComposerRequest $composerRequest,
        CompletePackageInterface $composerPackage,
    ): void {
        $io             = $composerRequest->pieOutput;
        $targetPlatform = $composerRequest->targetPlatform;

        $piePackage = Package::fromComposerCompletePackage($composerPackage);

        $status = $piePackage->verifyPackageStatus($composerRequest->targetPlatform);

        if ($status->isVerified()) {
            $io->write(sprintf('👋 <info>Removed extension:</info> %s', ($this->uninstall)($targetPlatform, $piePackage)->filePath));
        } else {
            $io->writeError(sprintf('<warning>Did not remove extension file:</warning> %s', $status->description()));
        }

        $affectedIniFiles = ($this->removeIniEntry)($piePackage, $composerRequest->targetPlatform, $io);

        if (count($affectedIniFiles) === 1) {
            $io->write(
                sprintf('INI file "%s" was updated to remove the extension.', reset($affectedIniFiles)),
                verbosity: IOInterface::VERBOSE,
            );
        } elseif (count($affectedIniFiles) === 0) {
            $io->write(
                'No INI files were updated to remove the extension.',
                verbosity: IOInterface::VERBOSE,
            );
        } else {
            $io->write(
                'The following INI files were updated to remove the extnesion:',
                verbosity: IOInterface::VERBOSE,
            );
            array_walk($affectedIniFiles, static fn (string $ini) => $io->write(' - ' . $ini));
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Composer\IO\IOInterface;
use Php\Pie\DependencyResolver\RequestedPackageAndVersion;
use Php\Pie\Platform\TargetPlatform;

/**
 * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
 *
 * @immutable
 */
final class PieComposerRequest
{
    /** @param list<non-empty-string> $configureOptions */
    public function __construct(
        public readonly IOInterface $pieOutput,
        public readonly TargetPlatform $targetPlatform,
        public readonly RequestedPackageAndVersion $requestedPackage,
        public readonly PieOperation $operation,
        public readonly array $configureOptions,
        public readonly bool $attemptToSetupIniFile,
    ) {
    }

    /**
     * Useful for when we don't want to perform any "write" style operations;
     * for example just reading metadata about the installed system.
     */
    public static function noOperation(
        IOInterface $pieOutput,
        TargetPlatform $targetPlatform,
    ): self {
        return new PieComposerRequest(
            $pieOutput,
            $targetPlatform,
            new RequestedPackageAndVersion('null/null', null),
            PieOperation::Resolve,
            [],
            false,
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Closure;
use Composer\IO\ConsoleIO;
use Composer\IO\IOInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

use function array_merge;
use function assert;
use function is_array;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class QuieterConsoleIO extends ConsoleIO
{
    /** @var string[] */
    public array $errors = [];

    public function __construct(InputInterface $input, OutputInterface $output, MinimalHelperSet $helperSet)
    {
        parent::__construct($input, $output, $helperSet);

        /**
         * Shifts Composer's normal output level by one, e.g. what Composer
         * normally outputs, only output with `-v`
         */
        $this->overrideVerbosityMap([
            self::QUIET => OutputInterface::VERBOSITY_NORMAL,
            self::NORMAL => OutputInterface::VERBOSITY_VERBOSE,
            self::VERBOSE => OutputInterface::VERBOSITY_VERY_VERBOSE,
            self::VERY_VERBOSE => OutputInterface::VERBOSITY_DEBUG,
            self::DEBUG => OutputInterface::VERBOSITY_DEBUG,
        ]);
    }

    /** @param array<IOInterface::*, OutputInterface::VERBOSITY_*> $newVerbosityMap */
    private function overrideVerbosityMap(array $newVerbosityMap): void
    {
        $overrideFunction = Closure::bind(
            function (ConsoleIO $consoleIO) use ($newVerbosityMap): void {
                $consoleIO->verbosityMap = $newVerbosityMap;
            },
            null,
            ConsoleIO::class,
        );
        assert($overrideFunction !== null);
        $overrideFunction($this);
    }

    /** {@inheritDoc} */
    public function writeError($messages, bool $newline = true, int $verbosity = self::NORMAL): void
    {
        if ($verbosity <= self::NORMAL) {
            if (is_array($messages)) {
                $this->errors = array_merge($this->errors, $messages);

                parent::writeError($messages, $newline, $verbosity);

                return;
            }

            $this->errors[] = $messages;
        }

        parent::writeError($messages, $newline, $verbosity);
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\Util\Filesystem;
use Safe\Exceptions\DirException;
use Webmozart\Assert\Assert;

use function array_filter;
use function array_walk;
use function in_array;
use function Safe\scandir;
use function sprintf;

use const DIRECTORY_SEPARATOR;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class VendorCleanup
{
    public function __construct(
        private readonly IOInterface $io,
        private readonly Filesystem $filesystem,
    ) {
    }

    public function __invoke(Composer $composer): void
    {
        $vendorDir = (string) $composer->getConfig()->get('vendor-dir');

        try {
            $vendorContents = scandir($vendorDir);
            Assert::isList($vendorContents);
            Assert::allString($vendorContents);
        } catch (DirException $e) {
            $this->io->write(
                sprintf(
                    '<comment>Vendor directory (vendor-dir config) %s seemed invalid? %s</comment>',
                    $vendorDir,
                    $e->getMessage(),
                ),
                verbosity: IOInterface::VERY_VERBOSE,
            );

            return;
        }

        $toRemove = array_filter(
            $vendorContents,
            static function (string $path): bool {
                return ! in_array(
                    $path,
                    [
                        '.',
                        '..',
                        'autoload.php',
                        'composer',
                    ],
                );
            },
        );

        array_walk(
            $toRemove,
            function (string $pathToRemove) use ($vendorDir): void {
                $fullPathToRemove = $vendorDir . DIRECTORY_SEPARATOR . $pathToRemove;

                $this->io->write(
                    sprintf(
                        '<comment>Removing: %s</comment>',
                        $fullPathToRemove,
                    ),
                    verbosity: IOInterface::VERY_VERBOSE,
                );

                if ($this->filesystem->remove($fullPathToRemove)) {
                    return;
                }

                $this->io->write(
                    sprintf(
                        '<comment>Warning: failed to remove %s</comment>',
                        $fullPathToRemove,
                    ),
                    verbosity: IOInterface::VERBOSE,
                );
            },
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Composer\Installer\LibraryInstaller;
use Composer\IO\IOInterface;
use Composer\Package\CompletePackageInterface;
use Composer\Package\PackageInterface;
use Composer\PartialComposer;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Util\Filesystem;
use Php\Pie\ExtensionType;

use function sprintf;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class PiePackageInstaller extends LibraryInstaller
{
    public function __construct(
        IOInterface $io,
        PartialComposer $composer,
        ExtensionType $type,
        Filesystem $filesystem,
        private readonly InstallAndBuildProcess $installAndBuildProcess,
        private readonly UninstallProcess $uninstallProcess,
        private readonly PieComposerRequest $composerRequest,
    ) {
        parent::__construct($io, $composer, $type->value, $filesystem);
    }

    /** @inheritDoc */
    public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        $composerPackage = $package;

        return parent::install($repo, $composerPackage)
            ?->then(function () use ($composerPackage) {
                $io = $this->composerRequest->pieOutput;

                if ($this->composerRequest->requestedPackage->package !== $composerPackage->getName()) {
                    $io->write(
                        sprintf(
                            '<comment>Skipping %s install request from Composer as it was not the expected PIE package %s</comment>',
                            $composerPackage->getName(),
                            $this->composerRequest->requestedPackage->package,
                        ),
                        verbosity: IOInterface::VERY_VERBOSE,
                    );

                    return null;
                }

                if (! $composerPackage instanceof CompletePackageInterface) {
                    $io->writeError(sprintf(
                        '<error>Not using PIE to install %s as it was not a Complete Package</error>',
                        $composerPackage->getName(),
                    ));

                    return null;
                }

                ($this->installAndBuildProcess)(
                    $this->composer,
                    $this->composerRequest,
                    $composerPackage,
                    $this->getInstallPath($composerPackage),
                );

                return null;
            });
    }

    /** @inheritDoc */
    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        $composerPackage = $package;

        return parent::uninstall($repo, $composerPackage)
            ?->then(function () use ($composerPackage) {
                $io = $this->composerRequest->pieOutput;

                if ($this->composerRequest->requestedPackage->package !== $composerPackage->getName()) {
                    $io->write(
                        sprintf(
                            '<comment>Skipping %s uninstall request from Composer as it was not the expected PIE package %s</comment>',
                            $composerPackage->getName(),
                            $this->composerRequest->requestedPackage->package,
                        ),
                        verbosity: IOInterface::VERY_VERBOSE,
                    );

                    return null;
                }

                if (! $composerPackage instanceof CompletePackageInterface) {
                    $io->writeError(sprintf(
                        '<error>Not using PIE to install %s as it was not a Complete Package</error>',
                        $composerPackage->getName(),
                    ));

                    return null;
                }

                ($this->uninstallProcess)(
                    $this->composerRequest,
                    $composerPackage,
                );

                return null;
            });
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie;

use Composer\Package\PackageInterface;
use InvalidArgumentException;
use Webmozart\Assert\Assert;

use function array_key_exists;
use function explode;
use function is_string;
use function Safe\preg_match;
use function sprintf;
use function str_starts_with;
use function strlen;
use function substr;

/**
 * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
 *
 * @immutable
 */
final class ExtensionName
{
    /**
     * PECL extension names must contain only alphanumeric characters and underscores, and must start with an
     * alphabetical character. PIE does not change this requirement for consistency.
     *
     * @link https://github.com/pear/pear-core/blob/6f4c3a0b134626d238d75a44af01a2f7c4e688d9/PEAR/Common.php#L28
     */
    private const VALID_PACKAGE_NAME_REGEX = '#^[A-Za-z][a-zA-Z0-9_]+$#';

    // phpcs:disable SlevomatCodingStandard.Classes.RequireConstructorPropertyPromotion.RequiredConstructorPropertyPromotion
    /** @var non-empty-string */
    private readonly string $normalisedExtensionName;
    // phpcs:enable

    private function __construct(string $normalisedExtensionName)
    {
        if (! self::isValidExtensionName($normalisedExtensionName)) {
            throw new InvalidArgumentException(sprintf(
                'The value "%s" is not a valid extension name. An extension must start with a letter, and only contain alphanumeric characters or underscores',
                $normalisedExtensionName,
            ));
        }

        $this->normalisedExtensionName = $normalisedExtensionName;
    }

    /** @phpstan-assert-if-true non-empty-string $extensionName */
    public static function isValidExtensionName(string $extensionName): bool
    {
        return preg_match(self::VALID_PACKAGE_NAME_REGEX, $extensionName) >= 1;
    }

    public static function determineFromComposerPackage(PackageInterface $package): self
    {
        $phpExt = $package->getPhpExt();

        if (
            $phpExt === null
            || ! array_key_exists('extension-name', $phpExt)
            || ! is_string($phpExt['extension-name'])
            || $phpExt['extension-name'] === ''
        ) {
            $packageNameParts = explode('/', $package->getPrettyName());
            Assert::count($packageNameParts, 2, 'Expected a package name like vendor/package for ' . $package->getPrettyName());

            return self::normaliseFromString($packageNameParts[1]);
        }

        return self::normaliseFromString($phpExt['extension-name']);
    }

    public static function normaliseFromString(string $extensionName): self
    {
        if (str_starts_with($extensionName, 'ext-')) {
            return new self(substr($extensionName, strlen('ext-')));
        }

        return new self($extensionName);
    }

    /** @return non-empty-string */
    public function name(): string
    {
        return $this->normalisedExtensionName;
    }

    /** @return non-empty-string */
    public function nameWithExtPrefix(): string
    {
        return 'ext-' . $this->normalisedExtensionName;
    }

    /** @return non-empty-string */
    public function phpFormattedExtensionName(): string
    {
        return match ($this->name()) {
            'core' => 'Core',
            'spl' => 'SPL',
            'phar' => 'Phar',
            'reflection' => 'Reflection',
            'pdo' => 'PDO',
            'ffi' => 'FFI',
            'opcache' => 'Zend OPcache',
            'simplexml' => 'SimpleXML',
            default => $this->name(),
        };
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform;

use Php\Pie\DependencyResolver\Package;
use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\Downloading\Exception\CouldNotFindReleaseAsset;
use RuntimeException;

use function file_exists;
use function implode;
use function sprintf;
use function strtolower;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class WindowsExtensionAssetName
{
    private function __construct()
    {
    }

    /** @return non-empty-list<non-empty-string> */
    private static function assetNames(TargetPlatform $targetPlatform, Package $package, string $fileExtension): array
    {
        if ($targetPlatform->operatingSystem !== OperatingSystem::Windows || $targetPlatform->windowsCompiler === null) {
            throw CouldNotFindReleaseAsset::forMissingWindowsCompiler($targetPlatform);
        }

        /**
         * During development, we swapped compiler/ts around. It is fairly trivial to support both, so we can check
         * both formats pretty easily, just to avoid confusion for package maintainers...
         */
        return [
            strtolower(sprintf(
                'php_%s-%s-%s-%s-%s-%s.%s',
                $package->extensionName()->name(),
                $package->version(),
                $targetPlatform->phpBinaryPath->majorMinorVersion(),
                $targetPlatform->threadSafety->asShort(),
                strtolower($targetPlatform->windowsCompiler->name),
                $targetPlatform->architecture->name,
                $fileExtension,
            )),
            strtolower(sprintf(
                'php_%s-%s-%s-%s-%s-%s.%s',
                $package->extensionName()->name(),
                $package->version(),
                $targetPlatform->phpBinaryPath->majorMinorVersion(),
                strtolower($targetPlatform->windowsCompiler->name),
                $targetPlatform->threadSafety->asShort(),
                $targetPlatform->architecture->name,
                $fileExtension,
            )),
        ];
    }

    /** @return non-empty-list<non-empty-string> */
    public static function zipNames(TargetPlatform $targetPlatform, Package $package): array
    {
        return self::assetNames($targetPlatform, $package, 'zip');
    }

    /** @return non-empty-list<non-empty-string> */
    public static function dllNames(TargetPlatform $targetPlatform, Package $package): array
    {
        return self::assetNames($targetPlatform, $package, 'dll');
    }

    /** @return non-empty-string */
    public static function determineDllName(TargetPlatform $targetPlatform, DownloadedPackage $package): string
    {
        $possibleDllNames = self::dllNames($targetPlatform, $package->package);
        foreach ($possibleDllNames as $dllName) {
            $fullDllName = $package->extractedSourcePath . '/' . $dllName;
            if (file_exists($fullDllName)) {
                return $fullDllName;
            }
        }

        throw new RuntimeException('Unable to find DLL for package, checked: ' . implode(', ', $possibleDllNames));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform;

use Composer\Composer;
use Composer\Package\BasePackage;
use Composer\Package\CompletePackageInterface;
use InvalidArgumentException;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\ExtensionName;

use function array_filter;
use function array_map;
use function array_values;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class InstalledPiePackages
{
    /**
     * Returns a list of PIE packages according to PIE; this does NOT check if
     * the extension is actually enabled in the target PHP.
     */
    public function allPiePackages(Composer $composer): PiePackageList
    {
        return new PiePackageList(array_values(array_map(
            static function (CompletePackageInterface $package): Package {
                return Package::fromComposerCompletePackage($package);
            },
            array_filter(
                $composer
                    ->getRepositoryManager()
                    ->getLocalRepository()
                    ->getPackages(),
                static function (BasePackage $basePackage): bool {
                    try {
                        ExtensionName::determineFromComposerPackage($basePackage);
                    } catch (InvalidArgumentException) {
                        return false;
                    }

                    return $basePackage instanceof CompletePackageInterface;
                },
            ),
        )));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
enum Architecture
{
    case x86;
    case x86_64;
    case arm64;

    /**
     * Architecture naming is inconsistent across various platforms, so try to unify the various labels...
     *
     * @param non-empty-string $architecture
     */
    public static function parseArchitecture(string $architecture): self
    {
        return match ($architecture) {
            'x64', 'x86_64', 'AMD64' => self::x86_64,
            'arm64', 'aarch64' => self::arm64,
            default => self::x86,
        };
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform;

use Php\Pie\DependencyResolver\Package;

use function sprintf;
use function strtolower;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class PrePackagedSourceAssetName
{
    private function __construct()
    {
    }

    /** @return non-empty-list<non-empty-string> */
    public static function packageNames(Package $package): array
    {
        return [
            // Ideal format for new PIE packages
            strtolower(sprintf(
                'php_%s-%s-src.tgz',
                $package->extensionName()->name(),
                $package->version(),
            )),
            strtolower(sprintf(
                'php_%s-%s-src.zip',
                $package->extensionName()->name(),
                $package->version(),
            )),
            // Old PEAR/PECL convention
            strtolower(sprintf(
                '%s-%s.tgz',
                $package->extensionName()->name(),
                $package->version(),
            )),
        ];
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform;

use Fidry\CpuCoreCounter\CpuCoreCounter;
use Php\Pie\Platform\TargetPhp\PhpBinaryPath;
use Php\Pie\Platform\TargetPhp\PhpizePath;

use function explode;
use function function_exists;
use function posix_getuid;
use function Safe\preg_match;
use function trim;

/**
 * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
 *
 * @immutable
 */
class TargetPlatform
{
    private static LibcFlavour $libcFlavour;

    public function __construct(
        public readonly OperatingSystem $operatingSystem,
        public readonly OperatingSystemFamily $operatingSystemFamily,
        public readonly PhpBinaryPath $phpBinaryPath,
        public readonly Architecture $architecture,
        public readonly ThreadSafetyMode $threadSafety,
        public readonly int $makeParallelJobs,
        public readonly WindowsCompiler|null $windowsCompiler,
        public readonly PhpizePath|null $phpizePath,
    ) {
    }

    public function libcFlavour(): LibcFlavour
    {
        if (! isset(self::$libcFlavour)) {
            self::$libcFlavour = LibcFlavour::detect();
        }

        return self::$libcFlavour;
    }

    public static function isRunningAsRoot(): bool
    {
        return function_exists('posix_getuid') && posix_getuid() === 0;
    }

    public static function fromPhpBinaryPath(PhpBinaryPath $phpBinaryPath, int|null $makeParallelJobs, PhpizePath|null $phpizePath): self
    {
        $os              = $phpBinaryPath->operatingSystem();
        $osFamily        = $phpBinaryPath->operatingSystemFamily();
        $phpinfo         = $phpBinaryPath->phpinfo();
        $architecture    = $phpBinaryPath->machineType();
        $windowsCompiler = null;
        $threadSafety    = ThreadSafetyMode::ThreadSafe;

        /**
         * Based on xdebug.org wizard, copyright Derick Rethans, used under MIT licence
         *
         * @link https://github.com/xdebug/xdebug.org/blob/aff649f2c3ca303ad471e6ed9dd29c0db16d3e22/src/XdebugVersion.php#L276-L299
         */
        if (preg_match('/PHP Extension Build([ =>\t]+)(API.*)/', $phpinfo, $m)) {
            $parts = explode(',', trim($m[2]));
            foreach ($parts as $part) {
                switch ($part) {
                    case 'NTS':
                        $threadSafety = ThreadSafetyMode::NonThreadSafe;
                        break;
                    case 'TS':
                        $threadSafety = ThreadSafetyMode::ThreadSafe;
                        break;
                    case 'VC6':
                        $windowsCompiler = WindowsCompiler::VC6;
                        break;
                    case 'VC8':
                        $windowsCompiler = WindowsCompiler::VC8;
                        break;
                    case 'VC9':
                        $windowsCompiler = WindowsCompiler::VC9;
                        break;
                    case 'VC11':
                        $windowsCompiler = WindowsCompiler::VC11;
                        break;
                    case 'VC14':
                        $windowsCompiler = WindowsCompiler::VC14;
                        break;
                    case 'VC15':
                        $windowsCompiler = WindowsCompiler::VC15;
                        break;
                    case 'VS16':
                        $windowsCompiler = WindowsCompiler::VS16;
                        break;
                    case 'VS17':
                        $windowsCompiler = WindowsCompiler::VS17;
                        break;
                }
            }
        }

        if ($makeParallelJobs === null) {
            $makeParallelJobs = (new CpuCoreCounter())->getAvailableForParallelisation()->availableCpus;
        }

        return new self(
            $os,
            $osFamily,
            $phpBinaryPath,
            $architecture,
            $threadSafety,
            $makeParallelJobs,
            $windowsCompiler,
            $phpizePath,
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform;

use Php\Pie\DependencyResolver\Package;

use function array_unique;
use function array_values;
use function sprintf;
use function strtolower;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class PrePackagedBinaryAssetName
{
    private function __construct()
    {
    }

    /** @return non-empty-list<non-empty-string> */
    public static function packageNames(TargetPlatform $targetPlatform, Package $package): array
    {
        $debug        = $targetPlatform->phpBinaryPath->debugMode() === DebugBuild::Debug ? '-debug' : '';
        $tsNoSuffix   = $targetPlatform->threadSafety === ThreadSafetyMode::ThreadSafe ? '-zts' : '';
        $tsWithSuffix = $targetPlatform->threadSafety === ThreadSafetyMode::ThreadSafe ? '-zts' : '-nts';
        $libc         = $targetPlatform->libcFlavour()->value;

        $name    = $package->extensionName()->name();
        $version = $package->version();
        $phpVer  = $targetPlatform->phpBinaryPath->majorMinorVersion();
        $arch    = $targetPlatform->architecture->name;
        $os      = $targetPlatform->operatingSystemFamily->value;

        $names = [
            strtolower(sprintf('php_%s-%s_php%s-%s-%s-%s%s%s.zip', $name, $version, $phpVer, $arch, $os, $libc, $debug, $tsNoSuffix)),
            strtolower(sprintf('php_%s-%s_php%s-%s-%s-%s%s%s.tgz', $name, $version, $phpVer, $arch, $os, $libc, $debug, $tsNoSuffix)),
            strtolower(sprintf('php_%s-%s_php%s-%s-%s-%s%s%s.zip', $name, $version, $phpVer, $arch, $os, $libc, $debug, $tsWithSuffix)),
            strtolower(sprintf('php_%s-%s_php%s-%s-%s-%s%s%s.tgz', $name, $version, $phpVer, $arch, $os, $libc, $debug, $tsWithSuffix)),
        ];

        // Fallbacks with anylibc suffix, for unified binaries compatible with both glibc and musl (Linux only)
        if ($targetPlatform->operatingSystemFamily === OperatingSystemFamily::Linux) {
            $names[] = strtolower(sprintf('php_%s-%s_php%s-%s-%s-anylibc%s%s.zip', $name, $version, $phpVer, $arch, $os, $debug, $tsNoSuffix));
            $names[] = strtolower(sprintf('php_%s-%s_php%s-%s-%s-anylibc%s%s.tgz', $name, $version, $phpVer, $arch, $os, $debug, $tsNoSuffix));
            $names[] = strtolower(sprintf('php_%s-%s_php%s-%s-%s-anylibc%s%s.zip', $name, $version, $phpVer, $arch, $os, $debug, $tsWithSuffix));
            $names[] = strtolower(sprintf('php_%s-%s_php%s-%s-%s-anylibc%s%s.tgz', $name, $version, $phpVer, $arch, $os, $debug, $tsWithSuffix));
        }

        return array_values(array_unique($names));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform;

use Countable;
use OutOfRangeException;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\Util\PackageVerificationStatus;
use Webmozart\Assert\Assert;

use function array_filter;
use function array_key_first;
use function array_values;
use function count;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class PiePackageList implements Countable
{
    /** @param list<Package> $piePackages */
    public function __construct(private readonly array $piePackages)
    {
    }

    public function findByPhpFormattedExtensionName(string $phpFormattedExtensionName): PiePackageList
    {
        return new self(array_values(array_filter(
            $this->piePackages,
            static fn (Package $piePackage) => $piePackage->extensionName()->phpFormattedExtensionName() === $phpFormattedExtensionName,
        )));
    }

    public function findByPackageName(string $packageName): Package
    {
        foreach ($this->piePackages as $piePackage) {
            if ($piePackage->name() === $packageName) {
                return $piePackage;
            }
        }

        throw new OutOfRangeException('Package ' . $packageName . ' not in the list');
    }

    public function onlyVerifiedFor(TargetPlatform $targetPlatform): self
    {
        return new self(array_values(array_filter(
            $this->piePackages,
            static fn (Package $piePackage) => $piePackage->verifyPackageStatus($targetPlatform) === PackageVerificationStatus::Verified,
        )));
    }

    public function onlyOne(): Package
    {
        Assert::count($this->piePackages, 1);

        return $this->piePackages[array_key_first($this->piePackages)];
    }

    /** @return list<Package> */
    public function packages(): array
    {
        return $this->piePackages;
    }

    public function count(): int
    {
        return count($this->piePackages);
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
enum DebugBuild: string
{
    case Debug   = 'debug';
    case NoDebug = 'nodebug';
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
enum ThreadSafetyMode
{
    case ThreadSafe;
    case NonThreadSafe;

    public function asShort(): string
    {
        return match ($this) {
            self::ThreadSafe => 'ts',
            self::NonThreadSafe => 'nts',
        };
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform;

use Php\Pie\Util\Process;
use Symfony\Component\Process\ExecutableFinder;
use Throwable;

use function str_contains;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
enum LibcFlavour: string
{
    case Gnu  = 'glibc';
    case Musl = 'musl';
    case Bsd  = 'bsdlibc';

    public static function detect(): self
    {
        $executableFinder = new ExecutableFinder();

        $otool = $executableFinder->find('otool');
        if ($otool !== null) {
            return self::Bsd;
        }

        $lddPath = $executableFinder->find('ldd');
        $lsPath  = $executableFinder->find('ls');

        if ($lddPath === null || $lsPath === null) {
            return self::Gnu;
        }

        try {
            $linkResult = Process::run([$lddPath, $lsPath]);
        } catch (Throwable) {
            return self::Gnu;
        }

        return str_contains($linkResult, 'musl') ? self::Musl : self::Gnu;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform;

use Php\Pie\File\Sudo;
use Php\Pie\Platform;
use Php\Pie\Util\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\ExecutableFinder;

use function array_unshift;
use function implode;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
enum PackageManager: string
{
    case Test = 'test';
    case Apt  = 'apt-get';
    case Apk  = 'apk';
    case Dnf  = 'dnf';
    case Yum  = 'yum';
    case Brew = 'brew';

    public static function detect(): self|null
    {
        $executableFinder = new ExecutableFinder();

        foreach (self::cases() as $packageManager) {
            if ($packageManager === self::Test) {
                continue;
            }

            if ($executableFinder->find($packageManager->value) !== null) {
                return $packageManager;
            }
        }

        return null;
    }

    /**
     * @param list<string> $packages
     *
     * @return list<string>
     */
    public function installCommand(array $packages): array
    {
        return match ($this) {
            self::Test => ['echo', '"fake installing ' . implode(', ', $packages) . '"'],
            self::Apt => ['apt-get', 'install', '-y', '--no-install-recommends', '--no-install-suggests', ...$packages],
            self::Apk => ['apk', 'add', '--no-cache', '--virtual', '.php-pie-deps', ...$packages],
            self::Dnf => ['dnf', 'install', '-y', ...$packages],
            self::Yum => ['yum', 'install', '-y', ...$packages],
            self::Brew => ['brew', 'install', ...$packages],
        };
    }

    /** @param list<string> $packages */
    public function install(array $packages): void
    {
        $cmd = self::installCommand($packages);

        try {
            Process::run($cmd);

            return;
        } catch (ProcessFailedException $e) {
            if (Platform::isInteractive() && Process::processProbablyPermissionDenied($e)) {
                array_unshift($cmd, Sudo::find());

                Process::run($cmd);

                return;
            }

            throw $e;
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform\TargetPhp\Exception;

use Php\Pie\Platform\TargetPhp\PhpBinaryPath;
use RuntimeException;

use function file_exists;
use function is_dir;

class ExtensionPathProblem extends RuntimeException
{
    public static function new(PhpBinaryPath $php, string|null $extensionPath): self
    {
        $message = 'Could not determine extension path for ' . $php->phpBinaryPath;

        if ($extensionPath === null) {
            $message .= '; extension_dir => not set';
        } else {
            $message .= '; extension_dir => ' . $extensionPath;

            if (file_exists($extensionPath)) {
                $message .= '; exists';

                if (is_dir($extensionPath)) {
                    $message .= ', is a directory';
                } else {
                    $message .= ', not a directory';
                }
            } else {
                $message .= '; does not exist';
            }
        }

        return new self($message);
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform\TargetPhp\Exception;

use RuntimeException;

use function sprintf;

class InvalidPhpBinaryPath extends RuntimeException
{
    public static function fromNonExistentPhpBinary(string $phpBinaryPath): self
    {
        return new self(sprintf(
            'The php binary at "%s" does not exist',
            $phpBinaryPath,
        ));
    }

    public static function fromNonExecutablePhpBinary(string $phpBinaryPath): self
    {
        return new self(sprintf(
            'The php binary at "%s" is not executable',
            $phpBinaryPath,
        ));
    }

    public static function fromInvalidPhpBinary(string $phpBinaryPath): self
    {
        return new self(sprintf(
            'The php binary at "%s" does not appear to be a PHP binary',
            $phpBinaryPath,
        ));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform\TargetPhp\Exception;

use Php\Pie\ExtensionName;
use Php\Pie\Platform\TargetPhp\PhpBinaryPath;
use RuntimeException;

use function sprintf;

class ExtensionIsNotLoaded extends RuntimeException
{
    public static function fromExpectedExtension(PhpBinaryPath $php, ExtensionName $extension): self
    {
        return new self(sprintf(
            'Expected extension %s to be loaded in PHP %s, but it was not detected.',
            $extension->name(),
            $php->phpBinaryPath,
        ));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform\TargetPhp;

use Composer\IO\IOInterface;
use Composer\Semver\VersionParser;
use Composer\Util\Platform;
use Php\Pie\ExtensionName;
use Php\Pie\Platform\Architecture;
use Php\Pie\Platform\DebugBuild;
use Php\Pie\Platform\OperatingSystem;
use Php\Pie\Platform\OperatingSystemFamily;
use Php\Pie\Platform\TargetPhp\Exception\ExtensionPathProblem;
use Php\Pie\Util\Process;
use RuntimeException;
use Safe\Exceptions\FilesystemException;
use Symfony\Component\Process\PhpExecutableFinder;
use Webmozart\Assert\Assert;

use function array_combine;
use function array_filter;
use function array_key_exists;
use function array_keys;
use function array_map;
use function assert;
use function dirname;
use function explode;
use function file_exists;
use function implode;
use function in_array;
use function is_dir;
use function is_executable;
use function ltrim;
use function rtrim;
use function Safe\mkdir;
use function Safe\preg_match;
use function Safe\preg_replace;
use function sprintf;
use function strtolower;
use function trim;

use const DIRECTORY_SEPARATOR;

/**
 * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
 *
 * @immutable
 */
class PhpBinaryPath
{
    /**
     * @param non-empty-string      $phpBinaryPath
     * @param non-empty-string|null $phpConfigPath
     */
    private function __construct(
        public readonly string $phpBinaryPath,
        private readonly string|null $phpConfigPath,
    ) {
    }

    /** @param non-empty-string $phpBinaryPath */
    private static function assertValidLookingPhpBinary(string $phpBinaryPath): void
    {
        if (! file_exists($phpBinaryPath)) {
            throw Exception\InvalidPhpBinaryPath::fromNonExistentPhpBinary($phpBinaryPath);
        }

        if (! Platform::isWindows() && ! is_executable($phpBinaryPath)) {
            throw Exception\InvalidPhpBinaryPath::fromNonExecutablePhpBinary($phpBinaryPath);
        }

        // This is somewhat of a rudimentary check that the target PHP really is a PHP instance; not sure why you
        // WOULDN'T want to use a real PHP, but this should stop obvious hiccups at least (rather than for security)
        $testOutput = self::cleanWarningAndDeprecationsFromOutput(Process::run([$phpBinaryPath, '-r', 'echo "PHP";']));

        if ($testOutput !== 'PHP') {
            throw Exception\InvalidPhpBinaryPath::fromInvalidPhpBinary($phpBinaryPath);
        }
    }

    /** @return non-empty-string */
    public function phpApiVersion(): string
    {
        if (
            preg_match('/PHP API([ =>\t]*)(.*)/', $this->phpinfo(), $m)
            && array_key_exists(2, $m)
            && $m[2] !== ''
        ) {
            return $m[2];
        }

        throw new RuntimeException('Failed to find PHP API version...');
    }

    public function debugMode(): DebugBuild
    {
        if (
            preg_match('/Debug Build([ =>\t]*)(.*)/', $this->phpinfo(), $m)
            && $m[2] !== ''
        ) {
            return $m[2] === 'yes' ? DebugBuild::Debug : DebugBuild::NoDebug;
        }

        throw new RuntimeException('Failed to find PHP API version...');
    }

    /** @return non-empty-string */
    public function extensionPath(string|null $prefixInstallRoot = null): string
    {
        $phpinfo = $this->phpinfo();

        $extensionPath = null;

        if (
            preg_match('#^extension_dir\s+=>\s+([^=]+)\s+=>\s+([^=]+)$#m', $phpinfo, $matches)
            && trim($matches[1]) !== ''
            && trim($matches[1]) !== 'no value'
        ) {
            $extensionPath = trim($matches[1]);
            assert($extensionPath !== '');

            if (self::operatingSystem() !== OperatingSystem::Windows && $prefixInstallRoot !== null && $prefixInstallRoot !== '') {
                $extensionPath = rtrim($prefixInstallRoot, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . ltrim($extensionPath, DIRECTORY_SEPARATOR);
            }

            if (file_exists($extensionPath) && is_dir($extensionPath)) {
                return $extensionPath;
            }

            // `extension_dir` may be a relative URL on Windows, so resolve it according to the location of PHP
            if (self::operatingSystem() === OperatingSystem::Windows) {
                $phpPath              = dirname($this->phpBinaryPath);
                $attemptExtensionPath = $phpPath . DIRECTORY_SEPARATOR . $extensionPath;

                if (file_exists($attemptExtensionPath) && is_dir($attemptExtensionPath)) {
                    return $attemptExtensionPath;
                }
            }

            // if the path is absolute, try to create it
            try {
                mkdir($extensionPath, 0777, true);
            } catch (FilesystemException) {
                throw ExtensionPathProblem::new($this, $extensionPath);
            }

            if (file_exists($extensionPath) && is_dir($extensionPath)) {
                return $extensionPath;
            }
        }

        throw ExtensionPathProblem::new($this, $extensionPath);
    }

    public function assertExtensionIsLoadedInRuntime(ExtensionName $extension, IOInterface|null $io = null): void
    {
        if (! in_array(strtolower($extension->name()), array_map('strtolower', array_keys($this->extensions())))) {
            throw Exception\ExtensionIsNotLoaded::fromExpectedExtension(
                $this,
                $extension,
            );
        }

        if ($io === null) {
            return;
        }

        $io->write(
            sprintf(
                'Successfully asserted that extension %s is loaded in runtime.',
                $extension->name(),
            ),
            verbosity: IOInterface::VERBOSE,
        );
    }

    /** @return non-empty-string|null */
    public function additionalIniDirectory(): string|null
    {
        if (
            preg_match('/Scan this dir for additional \.ini files([ =>\t]*)(.*)/', $this->phpinfo(), $m)
            && array_key_exists(2, $m)
            && $m[2] !== ''
            && $m[2] !== '(none)'
        ) {
            return $m[2];
        }

        return null;
    }

    /** @return non-empty-string|null */
    public function loadedIniConfigurationFile(): string|null
    {
        if (
            preg_match('/Loaded Configuration File([ =>\t]*)(.*)/', $this->phpinfo(), $m)
            && array_key_exists(2, $m)
            && $m[2] !== ''
            && $m[2] !== '(none)'
        ) {
            return $m[2];
        }

        return null;
    }

    /** @return non-empty-string|null */
    public function buildProvider(): string|null
    {
        /**
         * Newer versions of PHP will have a `PHP_BUILD_PROVIDER` constant
         * defined - {@link https://github.com/php/php-src/pull/19157}
         */
        if (
            preg_match('/Build Provider([ =>\t]*)(.*)/', $this->phpinfo(), $m)
            && array_key_exists(2, $m)
            && $m[2] !== ''
            && $m[2] !== '(none)'
        ) {
            return $m[2];
        }

        return null;
    }

    /**
     * Returns a map where the key is the name of the extension and the value is the version ('0' if not defined)
     *
     * @return array<string, string>
     */
    public function extensions(): array
    {
        $extVersionsList = self::cleanWarningAndDeprecationsFromOutput(Process::run([
            $this->phpBinaryPath,
            '-r',
            <<<'PHP'
$exts = get_loaded_extensions();
$extVersions = array_map(
    static function ($extension) {
        $extVersion = phpversion($extension);
        if ($extVersion === false) {
            return '0';
        }
        return $extVersion;
    },
    $exts
);
echo implode("\n", array_map(
    static function ($k, $v) {
        return sprintf('%s:%s', $k, $v);
    },
    $exts,
    $extVersions
));
PHP,
        ]));

        $pairs = array_map(
            static fn (string $row) => explode(':', $row),
            explode("\n", $extVersionsList),
        );

        return array_combine(
            array_map(static fn (array $row) => $row[0], $pairs),
            array_map(static fn (array $row) => $row[1], $pairs),
        );
    }

    public function operatingSystem(): OperatingSystem
    {
        $winOrNot = self::cleanWarningAndDeprecationsFromOutput(Process::run([
            $this->phpBinaryPath,
            '-r',
            'echo \\defined(\'PHP_WINDOWS_VERSION_BUILD\') ? \'win\' : \'not\';',
        ]));
        Assert::stringNotEmpty($winOrNot, 'Could not determine PHP version');

        return $winOrNot === 'win' ? OperatingSystem::Windows : OperatingSystem::NonWindows;
    }

    public function operatingSystemFamily(): OperatingSystemFamily
    {
        /** @link https://github.com/sebastianbergmann/environment/commit/eb6dd721cfaca04c27ac61c7201493a8f62f7f1d */
        $osFamily = OperatingSystemFamily::tryFrom(strtolower(trim(
            self::cleanWarningAndDeprecationsFromOutput(Process::run([
                $this->phpBinaryPath,
                '-r',
                <<<'PHP'
                if (defined('PHP_OS_FAMILY')) {
                    echo PHP_OS_FAMILY;
                } elseif ('\\' === DIRECTORY_SEPARATOR) {
                    echo 'Windows';
                } else {
                    switch(PHP_OS) {
                        case 'Darwin':
                            echo 'Darwin';
                            break;
                        case 'DragonFly':
                        case 'FreeBSD':
                        case 'NetBSD':
                        case 'OpenBSD':
                            echo 'BSD';
                            break;
                        case 'Linux':
                            echo 'Linux';
                            break;
                        case 'SunOS':
                            echo 'Solaris';
                            break;
                        default:
                            echo 'Unknown';
                    }
                }
                PHP,
            ])),
        )));
        Assert::notNull($osFamily, 'Could not determine operating system family');

        return $osFamily;
    }

    /** @return non-empty-string */
    public function version(): string
    {
        $phpVersion = self::cleanWarningAndDeprecationsFromOutput(Process::run([
            $this->phpBinaryPath,
            '-r',
            'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION . "." . PHP_RELEASE_VERSION;',
        ]));
        Assert::stringNotEmpty($phpVersion, 'Could not determine PHP version');

        // normalizing the version will throw an exception if it is not a valid version
        (new VersionParser())->normalize($phpVersion);

        return $phpVersion;
    }

    /** @return non-empty-string */
    public function phpVersionWithExtra(): string
    {
        $phpVersionWithExtra = self::cleanWarningAndDeprecationsFromOutput(Process::run([
            $this->phpBinaryPath,
            '-r',
            'echo PHP_VERSION;',
        ]));
        Assert::stringNotEmpty($phpVersionWithExtra, 'Could not determine PHP_VERSION');

        return $phpVersionWithExtra;
    }

    /** @return non-empty-string */
    public function majorMinorVersion(): string
    {
        $phpVersion = self::cleanWarningAndDeprecationsFromOutput(Process::run([
            $this->phpBinaryPath,
            '-r',
            'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;',
        ]));
        Assert::stringNotEmpty($phpVersion, 'Could not determine PHP version');

        // normalizing the version will throw an exception if it is not a valid version
        (new VersionParser())->normalize($phpVersion);

        return $phpVersion;
    }

    public function majorVersion(): int
    {
        $phpVersion = self::cleanWarningAndDeprecationsFromOutput(Process::run([
            $this->phpBinaryPath,
            '-r',
            'echo PHP_MAJOR_VERSION;',
        ]));
        Assert::stringNotEmpty($phpVersion, 'Could not determine PHP version');

        return (int) $phpVersion;
    }

    public function minorVersion(): int
    {
        $phpVersion = self::cleanWarningAndDeprecationsFromOutput(Process::run([
            $this->phpBinaryPath,
            '-r',
            'echo PHP_MINOR_VERSION;',
        ]));
        Assert::stringNotEmpty($phpVersion, 'Could not determine PHP version');

        return (int) $phpVersion;
    }

    public function machineType(): Architecture
    {
        /**
         * On Windows, this will be x32 or x64; should not be used on other platforms
         *
         * Based on xdebug.org wizard, copyright Derick Rethans, used under MIT licence
         *
         * @link https://github.com/xdebug/xdebug.org/blob/aff649f2c3ca303ad471e6ed9dd29c0db16d3e22/src/XdebugVersion.php#L186-L190
         */
        if (
            $this->operatingSystem() === OperatingSystem::Windows
            && preg_match('/Architecture([ =>\t]*)(x[0-9]*)/', $this->phpinfo(), $m)
        ) {
            return Architecture::parseArchitecture($m[2]);
        }

        $phpMachineType = self::cleanWarningAndDeprecationsFromOutput(Process::run([
            $this->phpBinaryPath,
            '-r',
            'echo php_uname("m");',
        ]));
        Assert::stringNotEmpty($phpMachineType, 'Could not determine PHP machine type');

        $unameArchitecture = Architecture::parseArchitecture($phpMachineType);

        // If we're not on ARM, a more reliable way of determining 32-bit/64-bit is to use PHP_INT_SIZE
        if ($unameArchitecture !== Architecture::arm64) {
            return $this->phpIntSize() === 4 ? Architecture::x86 : Architecture::x86_64;
        }

        return $unameArchitecture;
    }

    public function phpIntSize(): int
    {
        $phpIntSize = self::cleanWarningAndDeprecationsFromOutput(Process::run([
            $this->phpBinaryPath,
            '-r',
            'echo PHP_INT_SIZE;',
        ]));
        Assert::stringNotEmpty($phpIntSize, 'Could not fetch PHP_INT_SIZE');
        Assert::same($phpIntSize, (string) (int) $phpIntSize, 'PHP_INT_SIZE was not an integer processed %2$s from %s');

        return (int) $phpIntSize;
    }

    /** @return non-empty-string */
    public function phpinfo(): string
    {
        $phpInfo = self::cleanWarningAndDeprecationsFromOutput(Process::run([
            $this->phpBinaryPath,
            '-i',
        ]));

        Assert::stringNotEmpty($phpInfo, sprintf('Could not run phpinfo using %s', $this->phpBinaryPath));

        return $phpInfo;
    }

    /**
     * This will only be set if {@see self::fromPhpConfigExecutable()} is used to create this {@see self}, otherwise
     * will return `null`.
     *
     * @return non-empty-string|null
     */
    public function phpConfigPath(): string|null
    {
        return $this->phpConfigPath;
    }

    /** @param non-empty-string $phpConfig */
    public static function fromPhpConfigExecutable(string $phpConfig): self
    {
        $phpExecutable = self::cleanWarningAndDeprecationsFromOutput(Process::run([$phpConfig, '--php-binary']));
        Assert::stringNotEmpty($phpExecutable, 'Could not find path to PHP executable.');

        self::assertValidLookingPhpBinary($phpExecutable);

        return new self($phpExecutable, $phpConfig);
    }

    /** @param non-empty-string $phpBinary */
    public static function fromPhpBinaryPath(string $phpBinary): self
    {
        self::assertValidLookingPhpBinary($phpBinary);

        return new self($phpBinary, null);
    }

    public static function fromCurrentProcess(): self
    {
        $phpExecutable = trim((string) (new PhpExecutableFinder())->find());
        Assert::stringNotEmpty($phpExecutable, 'Could not find path to PHP executable.');

        self::assertValidLookingPhpBinary($phpExecutable);

        return self::guessWithPhpConfig(new self($phpExecutable, null));
    }

    private static function guessWithPhpConfig(self $phpBinaryPath): self
    {
        $phpConfigAttempts = [];

        // Try to add `phpize` from path
        $whichPhpize = new \Symfony\Component\Process\Process(['which', 'php-config']);
        if ($whichPhpize->run() === 0) {
            $phpConfigAttempts[] = trim($whichPhpize->getOutput());
        }

        $phpConfigAttempts[] = preg_replace('((.*)php)', '$1php-config', $phpBinaryPath->phpBinaryPath);

        foreach ($phpConfigAttempts as $phpConfigAttempt) {
            assert($phpConfigAttempt !== '');
            if (! file_exists($phpConfigAttempt) || ! is_executable($phpConfigAttempt)) {
                continue;
            }

            $phpizeProcess = new \Symfony\Component\Process\Process([$phpConfigAttempt, '--php-binary']);
            if ($phpizeProcess->run() !== 0) {
                continue;
            }

            if (trim($phpizeProcess->getOutput()) !== $phpBinaryPath->phpBinaryPath) {
                continue;
            }

            $phpConfigApiVersionProcess = new \Symfony\Component\Process\Process([$phpConfigAttempt, '--phpapi']);

            // older versions of php-config did not have `--phpapi`, so we can't perform this validation
            if ($phpConfigApiVersionProcess->run() !== 0) {
                return new self($phpBinaryPath->phpBinaryPath, $phpConfigAttempt);
            }

            if (trim($phpConfigApiVersionProcess->getOutput()) === $phpBinaryPath->phpApiVersion()) {
                return new self($phpBinaryPath->phpBinaryPath, $phpConfigAttempt);
            }
        }

        return $phpBinaryPath;
    }

    private static function cleanWarningAndDeprecationsFromOutput(string $testOutput): string
    {
        // Note: xdebug can prefix `PHP ` onto warnings/deprecations, so filter them out too
        return trim(implode(
            "\n",
            array_filter(
                explode("\n", $testOutput),
                static fn (string $line) => ! preg_match('/^(Deprecated|Warning|PHP Warning|PHP Deprecated):/', $line),
            ),
        ));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform\TargetPhp;

use RuntimeException;
use Symfony\Component\Process\Process;

use function assert;
use function file_exists;
use function is_executable;
use function Safe\preg_match;
use function Safe\preg_replace;
use function trim;

/**
 * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
 *
 * @immutable
 */
final class PhpizePath
{
    /** @param non-empty-string $phpizeBinaryPath */
    public function __construct(public readonly string $phpizeBinaryPath)
    {
    }

    public static function looksLikeValidPhpize(string $phpizePathToCheck, string|null $forPhpApiVersion = null): bool
    {
        $phpizeAttempt = $phpizePathToCheck; // @todo
        if ($phpizeAttempt === '') {
            return false;
        }

        if (! file_exists($phpizeAttempt) || ! is_executable($phpizeAttempt)) {
            return false;
        }

        $phpizeProcess = new Process([$phpizeAttempt, '--version']);
        if ($phpizeProcess->run() !== 0) {
            return false;
        }

        if (
            ! preg_match('/PHP Api Version:\s*(.*)/', $phpizeProcess->getOutput(), $m)
            || $m[1] === ''
        ) {
            return false;
        }

        return $forPhpApiVersion === null || $forPhpApiVersion === $m[1];
    }

    public static function guessFrom(PhpBinaryPath $phpBinaryPath): self
    {
        $expectedApiVersion = $phpBinaryPath->phpApiVersion();

        $phpizeAttempts = [];

        // Try to add `phpize` from path
        $whichPhpize = new Process(['which', 'phpize']);
        if ($whichPhpize->run() === 0) {
            $phpizeAttempts[] = trim($whichPhpize->getOutput());
        }

        // Try to guess based on the `php` path itself
        $phpizeAttempts[] = preg_replace('((.*)php)', '$1phpize', $phpBinaryPath->phpBinaryPath);

        foreach ($phpizeAttempts as $phpizeAttempt) {
            assert($phpizeAttempt !== '');

            if (self::looksLikeValidPhpize($phpizeAttempt, $expectedApiVersion)) {
                return new self($phpizeAttempt);
            }
        }

        throw new RuntimeException('Could not find a suitable `phpize` binary, you may provide one using the "--with-phpize-path" option.');
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform;

use function array_map;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
enum OperatingSystemFamily: string
{
    case Windows = 'windows';
    case Bsd     = 'bsd';
    case Darwin  = 'darwin';
    case Solaris = 'solaris';
    case Linux   = 'linux';
    case Unknown = 'unknown';

    /** @return non-empty-list<non-empty-string> */
    public static function asValuesList(): array
    {
        return array_map(
            static fn (OperatingSystemFamily $osFamily): string => $osFamily->value,
            self::cases(),
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
enum WindowsCompiler
{
    case VC6;
    case VC8;
    case VC9;
    case VC11;
    case VC14;
    case VC15;
    case VS16;
    case VS17;
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
enum OperatingSystem
{
    case NonWindows;
    case Windows;

    public function asFriendlyName(): string
    {
        return match ($this) {
            self::Windows => 'Windows',
            self::NonWindows => 'Linux/OSX/etc',
        };
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie;

use Php\Pie\Command\BuildCommand;
use Php\Pie\Command\DownloadCommand;
use Php\Pie\Command\InfoCommand;
use Php\Pie\Command\InstallCommand;
use Php\Pie\Command\InstallExtensionsForProjectCommand;
use Php\Pie\Command\RepositoryAddCommand;
use Php\Pie\Command\RepositoryListCommand;
use Php\Pie\Command\RepositoryRemoveCommand;
use Php\Pie\Command\SelfUpdateCommand;
use Php\Pie\Command\SelfVerifyCommand;
use Php\Pie\Command\ShowCommand;
use Php\Pie\Command\UninstallCommand;
use Php\Pie\Util\PieVersion;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;

use function error_reporting;

use const E_DEPRECATED;

require_once __DIR__ . '/../src/Util/PieVersion.php';
if (PieVersion::isPharBuild()) {
    error_reporting(error_reporting() & ~E_DEPRECATED);
}

// phpcs:ignore Squiz.NamingConventions.ValidVariableName.NotCamelCaps
include $_composer_autoload_path ?? __DIR__ . '/../vendor/autoload.php';

$container = Container::factory();

$application = new Application('🥧 PHP Installer for Extensions (PIE)', PieVersion::get());

$application->setDispatcher($container->get(EventDispatcher::class));

$application->setCommandLoader(new ContainerCommandLoader(
    $container,
    [
        'download' => DownloadCommand::class,
        'build' => BuildCommand::class,
        'install' => InstallCommand::class,
        'info' => InfoCommand::class,
        'show' => ShowCommand::class,
        'repository:list' => RepositoryListCommand::class,
        'repository:add' => RepositoryAddCommand::class,
        'repository:remove' => RepositoryRemoveCommand::class,
        'uninstall' => UninstallCommand::class,
        'self-update' => SelfUpdateCommand::class,
        'self-verify' => SelfVerifyCommand::class,
        'install-extensions-for-project' => InstallExtensionsForProjectCommand::class,
    ],
));

$application->run($container->get(InputInterface::class), $container->get(OutputInterface::class));
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

use function getenv;
use function sprintf;

final class OnlyInPowerShellFinder implements CpuCoreFinder
{
    /**
     * @var CpuCoreFinder
     */
    private $decoratedFinder;

    public function __construct(CpuCoreFinder $decoratedFinder)
    {
        $this->decoratedFinder = $decoratedFinder;
    }

    public function diagnose(): string
    {
        $powerShellModulePath = getenv('PSModulePath');

        return $this->skip()
            ? sprintf(
                'Skipped; no power shell module path detected ("%s").',
                $powerShellModulePath
            )
            : $this->decoratedFinder->diagnose();
    }

    public function find(): ?int
    {
        return $this->skip()
            ? null
            : $this->decoratedFinder->find();
    }

    public function toString(): string
    {
        return sprintf(
            'OnlyInPowerShellFinder(%s)',
            $this->decoratedFinder->toString()
        );
    }

    private function skip(): bool
    {
        return false === getenv('PSModulePath');
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

use function preg_match;

/**
 * Find the number of logical CPU cores for Windows leveraging the Get-CimInstance
 * cmdlet, which is a newer version that is recommended over Get-WmiObject.
 */
final class CmiCmdletLogicalFinder extends ProcOpenBasedFinder
{
    private const CPU_CORE_COUNT_REGEX = '/NumberOfLogicalProcessors[\s\n]-+[\s\n]+(?<count>\d+)/';

    protected function getCommand(): string
    {
        return 'Get-CimInstance -ClassName Win32_ComputerSystem | Select-Object -Property NumberOfLogicalProcessors';
    }

    public function toString(): string
    {
        return 'CmiCmdletLogicalFinder';
    }

    protected function countCpuCores(string $process): ?int
    {
        if (0 === preg_match(self::CPU_CORE_COUNT_REGEX, $process, $matches)) {
            return parent::countCpuCores($process);
        }

        /** @phpstan-ignore offsetAccess.notFound */
        $count = $matches['count'];

        return parent::countCpuCores($count);
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

/**
 * Find the number of logical CPU cores for Linux, BSD and OSX.
 *
 * @see https://github.com/paratestphp/paratest/blob/c163539818fd96308ca8dc60f46088461e366ed4/src/Runners/PHPUnit/Options.php#L903-L909
 * @see https://opensource.apple.com/source/xnu/xnu-792.2.4/libkern/libkern/sysctl.h.auto.html
 */
final class HwLogicalFinder extends ProcOpenBasedFinder
{
    protected function getCommand(): string
    {
        return 'sysctl -n hw.logicalcpu';
    }

    public function toString(): string
    {
        return 'HwLogicalFinder';
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

use function implode;
use function in_array;
use function sprintf;

final class SkipOnOSFamilyFinder implements CpuCoreFinder
{
    /**
     * @var list<string>
     */
    private $skippedOSFamilies;

    /**
     * @var CpuCoreFinder
     */
    private $decoratedFinder;

    /**
     * @param string|list<string> $skippedOSFamilyOrFamilies
     */
    public function __construct(
        $skippedOSFamilyOrFamilies,
        CpuCoreFinder $decoratedFinder
    ) {
        $this->skippedOSFamilies = (array) $skippedOSFamilyOrFamilies;
        $this->decoratedFinder = $decoratedFinder;
    }

    public static function forWindows(CpuCoreFinder $decoratedFinder): self
    {
        return new self(
            'Windows',
            $decoratedFinder
        );
    }

    public static function forBSD(CpuCoreFinder $decoratedFinder): self
    {
        return new self(
            'BSD',
            $decoratedFinder
        );
    }

    public static function forDarwin(CpuCoreFinder $decoratedFinder): self
    {
        return new self(
            'Darwin',
            $decoratedFinder
        );
    }

    public static function forSolaris(CpuCoreFinder $decoratedFinder): self
    {
        return new self(
            'Solaris',
            $decoratedFinder
        );
    }

    public static function forLinux(CpuCoreFinder $decoratedFinder): self
    {
        return new self(
            'Linux',
            $decoratedFinder
        );
    }

    public function diagnose(): string
    {
        return $this->skip()
            ? sprintf(
                'Skipped platform detected ("%s").',
                PHP_OS_FAMILY
            )
            : $this->decoratedFinder->diagnose();
    }

    public function find(): ?int
    {
        return $this->skip()
            ? null
            : $this->decoratedFinder->find();
    }

    public function toString(): string
    {
        return sprintf(
            'SkipOnOSFamilyFinder(skip=(%s),%s)',
            implode(',', $this->skippedOSFamilies),
            $this->decoratedFinder->toString()
        );
    }

    private function skip(): bool
    {
        return in_array(PHP_OS_FAMILY, $this->skippedOSFamilies, true);
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

use function preg_match;

/**
 * Find the number of physical CPU cores for Windows.
 *
 * @see https://github.com/paratestphp/paratest/blob/c163539818fd96308ca8dc60f46088461e366ed4/src/Runners/PHPUnit/Options.php#L912-L916
 */
final class WmicPhysicalFinder extends ProcOpenBasedFinder
{
    private const CPU_CORE_COUNT_REGEX = '/NumberOfCores[\s\n]+(?<count>\d+)/';

    protected function getCommand(): string
    {
        return 'wmic cpu get NumberOfCores';
    }

    public function toString(): string
    {
        return 'WmicPhysicalFinder';
    }

    protected function countCpuCores(string $process): ?int
    {
        if (0 === preg_match(self::CPU_CORE_COUNT_REGEX, $process, $matches)) {
            return parent::countCpuCores($process);
        }

        /** @phpstan-ignore offsetAccess.notFound */
        $count = $matches['count'];

        return parent::countCpuCores($count);
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

final class FinderRegistry
{
    /**
     * @return list<CpuCoreFinder> List of all the known finders with all their variants.
     */
    public static function getAllVariants(): array
    {
        return [
            new CpuInfoFinder(),
            new DummyCpuCoreFinder(1),
            new HwLogicalFinder(),
            new HwPhysicalFinder(),
            new LscpuLogicalFinder(),
            new LscpuPhysicalFinder(),
            new _NProcessorFinder(),
            new NProcessorFinder(),
            new NProcFinder(true),
            new NProcFinder(false),
            new NullCpuCoreFinder(),
            SkipOnOSFamilyFinder::forWindows(
                new DummyCpuCoreFinder(1)
            ),
            OnlyOnOSFamilyFinder::forWindows(
                new DummyCpuCoreFinder(1)
            ),
            new OnlyInPowerShellFinder(new CmiCmdletLogicalFinder()),
            new OnlyInPowerShellFinder(new CmiCmdletPhysicalFinder()),
            new WindowsRegistryLogicalFinder(),
            new WmicPhysicalFinder(),
            new WmicLogicalFinder(),
        ];
    }

    /**
     * @return list<CpuCoreFinder>
     */
    public static function getDefaultLogicalFinders(): array
    {
        return [
            OnlyOnOSFamilyFinder::forWindows(
                new OnlyInPowerShellFinder(
                    new CmiCmdletLogicalFinder()
                )
            ),
            OnlyOnOSFamilyFinder::forWindows(new WindowsRegistryLogicalFinder()),
            OnlyOnOSFamilyFinder::forWindows(new WmicLogicalFinder()),
            new NProcFinder(),
            new HwLogicalFinder(),
            new _NProcessorFinder(),
            new NProcessorFinder(),
            new LscpuLogicalFinder(),
            new CpuInfoFinder(),
        ];
    }

    /**
     * @return list<CpuCoreFinder>
     */
    public static function getDefaultPhysicalFinders(): array
    {
        return [
            OnlyOnOSFamilyFinder::forWindows(
                new OnlyInPowerShellFinder(
                    new CmiCmdletPhysicalFinder()
                )
            ),
            OnlyOnOSFamilyFinder::forWindows(new WmicPhysicalFinder()),
            new HwPhysicalFinder(),
            new LscpuPhysicalFinder(),
        ];
    }

    private function __construct()
    {
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

/**
 * Find the number of logical CPU cores for Linux and the likes.
 *
 * @see https://twitter.com/freebsdfrau/status/1052016199452700678?s=20&t=M2pHkRqmmna-UF68lfL2hw
 */
final class _NProcessorFinder extends ProcOpenBasedFinder
{
    protected function getCommand(): string
    {
        return 'getconf _NPROCESSORS_ONLN';
    }

    public function toString(): string
    {
        return '_NProcessorFinder';
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

/**
 * This finder returns whatever value you gave to it. This is useful for testing.
 */
final class NullCpuCoreFinder implements CpuCoreFinder
{
    public function diagnose(): string
    {
        return 'Will return "null".';
    }

    public function find(): ?int
    {
        return null;
    }

    public function toString(): string
    {
        return 'NullCpuCoreFinder';
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

use Fidry\CpuCoreCounter\Executor\ProcessExecutor;
use Fidry\CpuCoreCounter\Executor\ProcOpenExecutor;
use function filter_var;
use function function_exists;
use function is_int;
use function sprintf;
use function trim;
use const FILTER_VALIDATE_INT;
use const PHP_EOL;

abstract class ProcOpenBasedFinder implements CpuCoreFinder
{
    /**
     * @var ProcessExecutor
     */
    private $executor;

    public function __construct(?ProcessExecutor $executor = null)
    {
        $this->executor = $executor ?? new ProcOpenExecutor();
    }

    public function diagnose(): string
    {
        if (!function_exists('proc_open')) {
            return 'The function "proc_open" is not available.';
        }

        $command = $this->getCommand();
        $output = $this->executor->execute($command);

        if (null === $output) {
            return sprintf(
                'Failed to execute the command "%s".',
                $command
            );
        }

        [$stdout, $stderr] = $output;
        $failed = '' !== trim($stderr);

        return $failed
            ? sprintf(
                'Executed the command "%s" which wrote the following output to the STDERR:%s%s%sWill return "null".',
                $command,
                PHP_EOL,
                $stderr,
                PHP_EOL
            )
            : sprintf(
                'Executed the command "%s" and got the following (STDOUT) output:%s%s%sWill return "%s".',
                $command,
                PHP_EOL,
                $stdout,
                PHP_EOL,
                $this->countCpuCores($stdout) ?? 'null'
            );
    }

    /**
     * @return positive-int|null
     */
    public function find(): ?int
    {
        $output = $this->executor->execute($this->getCommand());

        if (null === $output) {
            return null;
        }

        [$stdout, $stderr] = $output;
        $failed = '' !== trim($stderr);

        return $failed
            ? null
            : $this->countCpuCores($stdout);
    }

    /**
     * @internal
     *
     * @return positive-int|null
     */
    protected function countCpuCores(string $process): ?int
    {
        $cpuCount = filter_var($process, FILTER_VALIDATE_INT);

        return is_int($cpuCount) && $cpuCount > 0 ? $cpuCount : null;
    }

    abstract protected function getCommand(): string;
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

interface CpuCoreFinder
{
    /**
     * Provides an explanation which may offer some insight as to what the finder
     * will be able to find.
     *
     * This is practical to have an idea of what each finder will find collect
     * information for the unit tests, since integration tests are quite complicated
     * as dependent on complex infrastructures.
     */
    public function diagnose(): string;

    /**
     * Find the number of CPU cores. If it could not find it, returns null. The
     * means used to find the cores are at the implementation discretion.
     *
     * @return positive-int|null
     */
    public function find(): ?int;

    public function toString(): string;
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

use function count;
use function explode;
use function is_array;
use function preg_grep;
use const PHP_EOL;

/**
 * The number of logical cores.
 *
 * @see https://stackoverflow.com/a/23378780/5846754
 */
final class LscpuLogicalFinder extends ProcOpenBasedFinder
{
    public function getCommand(): string
    {
        return 'lscpu -p';
    }

    protected function countCpuCores(string $process): ?int
    {
        $lines = explode(PHP_EOL, $process);
        $actualLines = preg_grep('/^\d+,/', $lines);

        if (!is_array($actualLines)) {
            return null;
        }

        $count = count($actualLines);

        return 0 === $count ? null : $count;
    }

    public function toString(): string
    {
        return 'LscpuLogicalFinder';
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

use function implode;
use function sprintf;
use const PHP_OS_FAMILY;

final class OnlyOnOSFamilyFinder implements CpuCoreFinder
{
    /**
     * @var list<string>
     */
    private $skippedOSFamilies;

    /**
     * @var CpuCoreFinder
     */
    private $decoratedFinder;

    /**
     * @param string|list<string> $skippedOSFamilyOrFamilies
     */
    public function __construct(
        $skippedOSFamilyOrFamilies,
        CpuCoreFinder $decoratedFinder
    ) {
        $this->skippedOSFamilies = (array) $skippedOSFamilyOrFamilies;
        $this->decoratedFinder = $decoratedFinder;
    }

    public static function forWindows(CpuCoreFinder $decoratedFinder): self
    {
        return new self(
            'Windows',
            $decoratedFinder
        );
    }

    public static function forBSD(CpuCoreFinder $decoratedFinder): self
    {
        return new self(
            'BSD',
            $decoratedFinder
        );
    }

    public static function forDarwin(CpuCoreFinder $decoratedFinder): self
    {
        return new self(
            'Darwin',
            $decoratedFinder
        );
    }

    public static function forSolaris(CpuCoreFinder $decoratedFinder): self
    {
        return new self(
            'Solaris',
            $decoratedFinder
        );
    }

    public static function forLinux(CpuCoreFinder $decoratedFinder): self
    {
        return new self(
            'Linux',
            $decoratedFinder
        );
    }

    public function diagnose(): string
    {
        return $this->skip()
            ? sprintf(
                'Skipped platform detected ("%s").',
                PHP_OS_FAMILY
            )
            : $this->decoratedFinder->diagnose();
    }

    public function find(): ?int
    {
        return $this->skip()
            ? null
            : $this->decoratedFinder->find();
    }

    public function toString(): string
    {
        return sprintf(
            'OnlyOnOSFamilyFinder(only=(%s),%s)',
            implode(',', $this->skippedOSFamilies),
            $this->decoratedFinder->toString()
        );
    }

    private function skip(): bool
    {
        return !in_array(PHP_OS_FAMILY, $this->skippedOSFamilies, true);
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

use function getenv;
use function preg_match;
use function sprintf;
use function var_export;

final class EnvVariableFinder implements CpuCoreFinder
{
    /** @var string */
    private $environmentVariableName;

    public function __construct(string $environmentVariableName)
    {
        $this->environmentVariableName = $environmentVariableName;
    }

    public function diagnose(): string
    {
        $value = getenv($this->environmentVariableName);

        return sprintf(
            'parse(getenv(%s)=%s)=%s',
            $this->environmentVariableName,
            var_export($value, true),
            self::isPositiveInteger($value) ? $value : 'null'
        );
    }

    public function find(): ?int
    {
        $value = getenv($this->environmentVariableName);

        if (is_string($value) && 1 === preg_match('/^(\d+)m$/', $value, $matches)) {
            $millicores = $matches[1];
            $value = (string) floor($millicores / 1000);
        }

        return self::isPositiveInteger($value)
            ? (int) $value
            : null;
    }

    public function toString(): string
    {
        return sprintf(
            'getenv(%s)',
            $this->environmentVariableName
        );
    }

    /**
     * @param string|false $value
     */
    private static function isPositiveInteger($value): bool
    {
        return false !== $value
            && 1 === preg_match('/^\d+$/', $value)
            && (int) $value > 0;
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

use function count;
use function explode;
use function is_array;
use function preg_grep;
use function strtok;
use const PHP_EOL;

/**
 * The number of physical processors.
 *
 * @see https://stackoverflow.com/a/23378780/5846754
 */
final class LscpuPhysicalFinder extends ProcOpenBasedFinder
{
    public function toString(): string
    {
        return 'LscpuPhysicalFinder';
    }

    public function getCommand(): string
    {
        return 'lscpu -p';
    }

    protected function countCpuCores(string $process): ?int
    {
        $lines = explode(PHP_EOL, $process);
        /** @var string[]|false $actualLines */
        $actualLines = preg_grep('/^\d+/', $lines);

        if (!is_array($actualLines)) {
            return null;
        }

        $cores = [];
        foreach ($actualLines as $line) {
            strtok($line, ',');
            $core = strtok(',');

            if (false === $core) {
                continue;
            }

            $cores[$core] = true;
        }
        unset($cores['-']);

        $count = count($cores);

        return 0 === $count ? null : $count;
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

use function preg_match;

/**
 * Find the number of physical CPU cores for Windows.
 *
 * @see https://github.com/paratestphp/paratest/blob/c163539818fd96308ca8dc60f46088461e366ed4/src/Runners/PHPUnit/Options.php#L912-L916
 */
final class CmiCmdletPhysicalFinder extends ProcOpenBasedFinder
{
    private const CPU_CORE_COUNT_REGEX = '/NumberOfCores[\s\n]-+[\s\n]+(?<count>\d+)/';

    protected function getCommand(): string
    {
        return 'Get-CimInstance -ClassName Win32_Processor | Select-Object -Property NumberOfCores';
    }

    public function toString(): string
    {
        return 'CmiCmdletPhysicalFinder';
    }

    protected function countCpuCores(string $process): ?int
    {
        if (0 === preg_match(self::CPU_CORE_COUNT_REGEX, $process, $matches)) {
            return parent::countCpuCores($process);
        }

        /** @phpstan-ignore offsetAccess.notFound */
        $count = $matches['count'];

        return parent::countCpuCores($count);
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

use Fidry\CpuCoreCounter\Executor\ProcessExecutor;
use function sprintf;

/**
 * The number of (logical) cores.
 *
 * @see https://github.com/infection/infection/blob/fbd8c44/src/Resource/Processor/CpuCoresCountProvider.php#L69-L82
 * @see https://unix.stackexchange.com/questions/146051/number-of-processors-in-proc-cpuinfo
 */
final class NProcFinder extends ProcOpenBasedFinder
{
    /**
     * @var bool
     */
    private $all;

    /**
     * @param bool $all If disabled will give the number of cores available for the current process
     *                  only. This is disabled by default as it is known to be "buggy" on virtual
     *                  environments as the virtualization tool, e.g. VMWare, might over-commit
     *                  resources by default.
     */
    public function __construct(
        bool $all = false,
        ?ProcessExecutor $executor = null
    ) {
        parent::__construct($executor);

        $this->all = $all;
    }

    public function toString(): string
    {
        return sprintf(
            'NProcFinder(all=%s)',
            $this->all ? 'true' : 'false'
        );
    }

    protected function getCommand(): string
    {
        return 'nproc'.($this->all ? ' --all' : '');
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

use function file_get_contents;
use function is_file;
use function sprintf;
use function substr_count;
use const PHP_EOL;

/**
 * Find the number of CPU cores looking up at the cpuinfo file which is available
 * on Linux systems and Windows systems with a Linux sub-system.
 *
 * @see https://github.com/paratestphp/paratest/blob/c163539818fd96308ca8dc60f46088461e366ed4/src/Runners/PHPUnit/Options.php#L903-L909
 * @see https://unix.stackexchange.com/questions/146051/number-of-processors-in-proc-cpuinfo
 */
final class CpuInfoFinder implements CpuCoreFinder
{
    private const CPU_INFO_PATH = '/proc/cpuinfo';

    public function diagnose(): string
    {
        if (!is_file(self::CPU_INFO_PATH)) {
            return sprintf(
                'The file "%s" could not be found.',
                self::CPU_INFO_PATH
            );
        }

        $cpuInfo = file_get_contents(self::CPU_INFO_PATH);

        if (false === $cpuInfo) {
            return sprintf(
                'Could not get the content of the file "%s".',
                self::CPU_INFO_PATH
            );
        }

        return sprintf(
            'Found the file "%s" with the content:%s%s%sWill return "%s".',
            self::CPU_INFO_PATH,
            PHP_EOL,
            $cpuInfo,
            PHP_EOL,
            self::countCpuCores($cpuInfo)
        );
    }

    /**
     * @return positive-int|null
     */
    public function find(): ?int
    {
        $cpuInfo = self::getCpuInfo();

        return null === $cpuInfo ? null : self::countCpuCores($cpuInfo);
    }

    public function toString(): string
    {
        return 'CpuInfoFinder';
    }

    private static function getCpuInfo(): ?string
    {
        if (!@is_file(self::CPU_INFO_PATH)) {
            return null;
        }

        $cpuInfo = @file_get_contents(self::CPU_INFO_PATH);

        return false === $cpuInfo
            ? null
            : $cpuInfo;
    }

    /**
     * @internal
     *
     * @return positive-int|null
     */
    public static function countCpuCores(string $cpuInfo): ?int
    {
        $processorCount = substr_count($cpuInfo, 'processor');

        return $processorCount > 0 ? $processorCount : null;
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

use function sprintf;

/**
 * This finder returns whatever value you gave to it. This is useful for testing
 * or as a fallback to avoid to catch the NumberOfCpuCoreNotFound exception.
 */
final class DummyCpuCoreFinder implements CpuCoreFinder
{
    /**
     * @var positive-int
     */
    private $count;

    public function diagnose(): string
    {
        return sprintf(
            'Will return "%d".',
            $this->count
        );
    }

    /**
     * @param positive-int $count
     */
    public function __construct(int $count)
    {
        $this->count = $count;
    }

    /** @phpstan-ignore return.unusedType */
    public function find(): ?int
    {
        return $this->count;
    }

    public function toString(): string
    {
        return sprintf(
            'DummyCpuCoreFinder(value=%d)',
            $this->count
        );
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

use function preg_match;

/**
 * Find the number of logical CPU cores for Windows.
 *
 * @see https://github.com/paratestphp/paratest/blob/c163539818fd96308ca8dc60f46088461e366ed4/src/Runners/PHPUnit/Options.php#L912-L916
 */
final class WmicLogicalFinder extends ProcOpenBasedFinder
{
    private const CPU_CORE_COUNT_REGEX = '/NumberOfLogicalProcessors[\s\n]+(?<count>\d+)/';

    protected function getCommand(): string
    {
        return 'wmic cpu get NumberOfLogicalProcessors';
    }

    public function toString(): string
    {
        return 'WmicLogicalFinder';
    }

    protected function countCpuCores(string $process): ?int
    {
        if (0 === preg_match(self::CPU_CORE_COUNT_REGEX, $process, $matches)) {
            return parent::countCpuCores($process);
        }

        /** @phpstan-ignore offsetAccess.notFound */
        $count = $matches['count'];

        return parent::countCpuCores($count);
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

/**
 * Find the number of logical CPU cores for FreeSBD, Solaris and the likes.
 *
 * @see https://twitter.com/freebsdfrau/status/1052016199452700678?s=20&t=M2pHkRqmmna-UF68lfL2hw
 */
final class NProcessorFinder extends ProcOpenBasedFinder
{
    protected function getCommand(): string
    {
        return 'getconf NPROCESSORS_ONLN';
    }

    public function toString(): string
    {
        return 'NProcessorFinder';
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

/**
 * Find the number of physical CPU cores for Linux, BSD and OSX.
 *
 * @see https://github.com/paratestphp/paratest/blob/c163539818fd96308ca8dc60f46088461e366ed4/src/Runners/PHPUnit/Options.php#L903-L909
 * @see https://opensource.apple.com/source/xnu/xnu-792.2.4/libkern/libkern/sysctl.h.auto.html
 */
final class HwPhysicalFinder extends ProcOpenBasedFinder
{
    protected function getCommand(): string
    {
        return 'sysctl -n hw.physicalcpu';
    }

    public function toString(): string
    {
        return 'HwPhysicalFinder';
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

use function array_filter;
use function count;
use function explode;
use const PHP_EOL;

/**
 * Find the number of logical CPU cores for Windows.
 *
 * @see https://knowledge.informatica.com/s/article/151521
 */
final class WindowsRegistryLogicalFinder extends ProcOpenBasedFinder
{
    protected function getCommand(): string
    {
        return 'reg query HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\CentralProcessor';
    }

    public function toString(): string
    {
        return 'WindowsRegistryLogicalFinder';
    }

    protected function countCpuCores(string $process): ?int
    {
        $count = count(
            array_filter(
                explode(PHP_EOL, $process),
                static function (string $line): bool {
                    return '' !== trim($line);
                }
            )
        );

        return $count > 0 ? $count : null;
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter;

use Fidry\CpuCoreCounter\Finder\CpuCoreFinder;
use function array_map;
use function explode;
use function implode;
use function max;
use function str_repeat;
use const PHP_EOL;

/**
 * Utility to debug.
 *
 * @private
 */
final class Diagnoser
{
    /**
     * Provides an aggregated diagnosis based on each finders diagnosis.
     *
     * @param list<CpuCoreFinder> $finders
     */
    public static function diagnose(array $finders): string
    {
        $diagnoses = array_map(
            static function (CpuCoreFinder $finder): string {
                return self::diagnoseFinder($finder);
            },
            $finders
        );

        return implode(PHP_EOL, $diagnoses);
    }

    /**
     * Executes each finders.
     *
     * @param list<CpuCoreFinder> $finders
     */
    public static function execute(array $finders): string
    {
        $diagnoses = array_map(
            static function (CpuCoreFinder $finder): string {
                $coresCount = $finder->find();

                return implode(
                    '',
                    [
                        $finder->toString(),
                        ': ',
                        null === $coresCount ? 'NULL' : $coresCount,
                    ]
                );
            },
            $finders
        );

        return implode(PHP_EOL, $diagnoses);
    }

    private static function diagnoseFinder(CpuCoreFinder $finder): string
    {
        $diagnosis = $finder->diagnose();

        $maxLineLength = max(
            array_map(
                'strlen',
                explode(PHP_EOL, $diagnosis)
            )
        );

        $separator = str_repeat('-', $maxLineLength);

        return implode(
            '',
            [
                $finder->toString().':'.PHP_EOL,
                $separator.PHP_EOL,
                $diagnosis.PHP_EOL,
                $separator.PHP_EOL,
            ]
        );
    }

    private function __construct()
    {
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter;

use Fidry\CpuCoreCounter\Finder\CpuCoreFinder;
use Fidry\CpuCoreCounter\Finder\EnvVariableFinder;
use Fidry\CpuCoreCounter\Finder\FinderRegistry;
use InvalidArgumentException;
use function implode;
use function max;
use function sprintf;
use function sys_getloadavg;
use const PHP_EOL;

final class CpuCoreCounter
{
    /**
     * @var list<CpuCoreFinder>
     */
    private $finders;

    /**
     * @var positive-int|null
     */
    private $count;

    /**
     * @param list<CpuCoreFinder>|null $finders
     */
    public function __construct(?array $finders = null)
    {
        $this->finders = $finders ?? FinderRegistry::getDefaultLogicalFinders();
    }

    /**
     * @param positive-int|0    $reservedCpus      Number of CPUs to reserve. This is useful when you want
     *                                             to reserve some CPUs for other processes. If the main
     *                                             process is going to be busy still, you may want to set
     *                                             this value to 1.
     * @param non-zero-int|null $countLimit        The maximum number of CPUs to return. If not provided, it
     *                                             may look for a limit in the environment variables, e.g.
     *                                             KUBERNETES_CPU_LIMIT. If negative, the limit will be
     *                                             the total number of cores found minus the absolute value.
     *                                             For instance if the system has 10 cores and countLimit=-2,
     *                                             then the effective limit considered will be 8.
     * @param float|null        $loadLimit         Element of [0., 1.]. Percentage representing the
     *                                             amount of cores that should be used among the available
     *                                             resources. For instance, if set to 0.7, it will use 70%
     *                                             of the available cores, i.e. if 1 core is reserved, 11
     *                                             cores are available and 5 are busy, it will use 70%
     *                                             of (11-1-5)=5 cores, so 3 cores. Set this parameter to null
     *                                             to skip this check. Beware that 1 does not mean "no limit",
     *                                             but 100% of the _available_ resources, i.e. with the
     *                                             previous example, it will return 5 cores. How busy is
     *                                             the system is determined by the system load average
     *                                             (see $systemLoadAverage).
     * @param float|null        $systemLoadAverage The system load average. If passed, it will use
     *                                             this information to limit the available cores based
     *                                             on the _available_ resources. For instance, if there
     *                                             is 10 cores but 3 are busy, then only 7 cores will
     *                                             be considered for further calculation. If set to
     *                                             `null`, it will use `sys_getloadavg()` to check the
     *                                             load of the system in the past minute. You can
     *                                             otherwise pass an arbitrary value. Should be a
     *                                             positive float.
     *
     * @see https://php.net/manual/en/function.sys-getloadavg.php
     */
    public function getAvailableForParallelisation(
        int $reservedCpus = 0,
        ?int $countLimit = null,
        ?float $loadLimit = null,
        ?float $systemLoadAverage = 0.
    ): ParallelisationResult {
        self::checkCountLimit($countLimit);
        self::checkLoadLimit($loadLimit);
        self::checkSystemLoadAverage($systemLoadAverage);

        $totalCoreCount = $this->getCountWithFallback(1);
        $availableCores = max(1, $totalCoreCount - $reservedCpus);

        // Adjust available CPUs based on current load
        if (null !== $loadLimit) {
            // https://github.com/phpstan/phpstan/issues/13198
            /** @var float $correctedSystemLoadAverage */
            $correctedSystemLoadAverage = null === $systemLoadAverage
                ? sys_getloadavg()[0] ?? 0.
                : $systemLoadAverage;

            $availableCores = max(
                1,
                $loadLimit * ($availableCores - $correctedSystemLoadAverage)
            );
        }

        if (null === $countLimit) {
            $correctedCountLimit = self::getKubernetesLimit();
        } else {
            $correctedCountLimit = $countLimit > 0
                ? $countLimit
                : max(1, $totalCoreCount + $countLimit);
        }

        if (null !== $correctedCountLimit && $availableCores > $correctedCountLimit) {
            $availableCores = $correctedCountLimit;
        }

        return new ParallelisationResult(
            $reservedCpus,
            $countLimit,
            $loadLimit,
            $systemLoadAverage,
            $correctedCountLimit,
            $correctedSystemLoadAverage ?? $systemLoadAverage,
            $totalCoreCount,
            (int) $availableCores
        );
    }

    /**
     * @throws NumberOfCpuCoreNotFound
     *
     * @return positive-int
     */
    public function getCount(): int
    {
        // Memoize result
        if (null === $this->count) {
            $this->count = $this->findCount();
        }

        return $this->count;
    }

    /**
     * @param positive-int $fallback
     *
     * @return positive-int
     */
    public function getCountWithFallback(int $fallback): int
    {
        try {
            return $this->getCount();
        } catch (NumberOfCpuCoreNotFound $exception) {
            return $fallback;
        }
    }

    /**
     * This method is mostly for debugging purposes.
     */
    public function trace(): string
    {
        $output = [];

        foreach ($this->finders as $finder) {
            $output[] = sprintf(
                'Executing the finder "%s":',
                $finder->toString()
            );
            $output[] = $finder->diagnose();

            $cores = $finder->find();

            if (null !== $cores) {
                $output[] = 'Result found: '.$cores;

                break;
            }

            $output[] = '–––';
        }

        return implode(PHP_EOL, $output);
    }

    /**
     * @throws NumberOfCpuCoreNotFound
     *
     * @return positive-int
     */
    private function findCount(): int
    {
        foreach ($this->finders as $finder) {
            $cores = $finder->find();

            if (null !== $cores) {
                return $cores;
            }
        }

        throw NumberOfCpuCoreNotFound::create();
    }

    /**
     * @throws NumberOfCpuCoreNotFound
     *
     * @return array{CpuCoreFinder, positive-int}
     */
    public function getFinderAndCores(): array
    {
        foreach ($this->finders as $finder) {
            $cores = $finder->find();

            if (null !== $cores) {
                return [$finder, $cores];
            }
        }

        throw NumberOfCpuCoreNotFound::create();
    }

    /**
     * @return positive-int|null
     */
    public static function getKubernetesLimit(): ?int
    {
        $finder = new EnvVariableFinder('KUBERNETES_CPU_LIMIT');

        return $finder->find();
    }

    private static function checkCountLimit(?int $countLimit): void
    {
        if (0 === $countLimit) {
            throw new InvalidArgumentException(
                'The count limit must be a non zero integer. Got "0".'
            );
        }
    }

    private static function checkLoadLimit(?float $loadLimit): void
    {
        if (null === $loadLimit) {
            return;
        }

        if ($loadLimit < 0. || $loadLimit > 1.) {
            throw new InvalidArgumentException(
                sprintf(
                    'The load limit must be in the range [0., 1.], got "%s".',
                    $loadLimit
                )
            );
        }
    }

    private static function checkSystemLoadAverage(?float $systemLoadAverage): void
    {
        if (null !== $systemLoadAverage && $systemLoadAverage < 0.) {
            throw new InvalidArgumentException(
                sprintf(
                    'The system load average must be a positive float, got "%s".',
                    $systemLoadAverage
                )
            );
        }
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter;

use RuntimeException;

final class NumberOfCpuCoreNotFound extends RuntimeException
{
    public static function create(): self
    {
        return new self(
            'Could not find the number of CPU cores available.'
        );
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter;

/**
 * @readonly
 */
final class ParallelisationResult
{
    /**
     * @var positive-int|0
     */
    public $passedReservedCpus;

    /**
     * @var non-zero-int|null
     */
    public $passedCountLimit;

    /**
     * @var float|null
     */
    public $passedLoadLimit;

    /**
     * @var float|null
     */
    public $passedSystemLoadAverage;

    /**
     * @var non-zero-int|null
     */
    public $correctedCountLimit;

    /**
     * @var float|null
     */
    public $correctedSystemLoadAverage;

    /**
     * @var positive-int
     */
    public $totalCoresCount;

    /**
     * @var positive-int
     */
    public $availableCpus;

    /**
     * @param positive-int|0    $passedReservedCpus
     * @param non-zero-int|null $passedCountLimit
     * @param non-zero-int|null $correctedCountLimit
     * @param positive-int      $totalCoresCount
     * @param positive-int      $availableCpus
     */
    public function __construct(
        int $passedReservedCpus,
        ?int $passedCountLimit,
        ?float $passedLoadLimit,
        ?float $passedSystemLoadAverage,
        ?int $correctedCountLimit,
        ?float $correctedSystemLoadAverage,
        int $totalCoresCount,
        int $availableCpus
    ) {
        $this->passedReservedCpus = $passedReservedCpus;
        $this->passedCountLimit = $passedCountLimit;
        $this->passedLoadLimit = $passedLoadLimit;
        $this->passedSystemLoadAverage = $passedSystemLoadAverage;
        $this->correctedCountLimit = $correctedCountLimit;
        $this->correctedSystemLoadAverage = $correctedSystemLoadAverage;
        $this->totalCoresCount = $totalCoresCount;
        $this->availableCpus = $availableCpus;
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Executor;

interface ProcessExecutor
{
    /**
     * @return array{string, string}|null STDOUT & STDERR tuple
     */
    public function execute(string $command): ?array;
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Executor;

use function fclose;
use function function_exists;
use function is_resource;
use function proc_close;
use function proc_open;
use function stream_get_contents;

final class ProcOpenExecutor implements ProcessExecutor
{
    public function execute(string $command): ?array
    {
        if (!function_exists('proc_open')) {
            return null;
        }

        $pipes = [];

        $process = @proc_open(
            $command,
            [
                ['pipe', 'rb'],
                ['pipe', 'wb'], // stdout
                ['pipe', 'wb'], // stderr
            ],
            $pipes
        );
        // https://github.com/phpstan/phpstan/issues/13197
        /** @var array{resource, resource, resource} $pipes */
        if (!is_resource($process)) {
            return null;
        }

        fclose($pipes[0]);

        $stdout = stream_get_contents($pipes[1]);
        $stderr = stream_get_contents($pipes[2]);

        proc_close($process);

        return [$stdout, $stderr];
    }
}
#!/usr/bin/env php
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

use Fidry\CpuCoreCounter\Diagnoser;
use Fidry\CpuCoreCounter\Finder\FinderRegistry;

require_once __DIR__.'/../vendor/autoload.php';

echo 'Executing finders...'.PHP_EOL.PHP_EOL;
echo Diagnoser::execute(FinderRegistry::getAllVariants()).PHP_EOL;
#!/usr/bin/env php
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

use Fidry\CpuCoreCounter\CpuCoreCounter;
use Fidry\CpuCoreCounter\Finder\FinderRegistry;

require_once __DIR__.'/../vendor/autoload.php';

$separator = str_repeat('–', 80);

echo 'With all finders...'.PHP_EOL.PHP_EOL;
echo (new CpuCoreCounter(FinderRegistry::getAllVariants()))->trace().PHP_EOL;
echo $separator.PHP_EOL.PHP_EOL;

echo 'Logical CPU cores finders...'.PHP_EOL.PHP_EOL;
echo (new CpuCoreCounter(FinderRegistry::getDefaultLogicalFinders()))->trace().PHP_EOL;
echo $separator.PHP_EOL.PHP_EOL;

echo 'Physical CPU cores finders...'.PHP_EOL.PHP_EOL;
echo (new CpuCoreCounter(FinderRegistry::getDefaultPhysicalFinders()))->trace().PHP_EOL;
echo $separator.PHP_EOL.PHP_EOL;
#!/usr/bin/env php
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

use Fidry\CpuCoreCounter\Diagnoser;
use Fidry\CpuCoreCounter\Finder\FinderRegistry;

require_once __DIR__.'/../vendor/autoload.php';

echo 'Running diagnosis...'.PHP_EOL.PHP_EOL;
echo Diagnoser::diagnose(FinderRegistry::getAllVariants()).PHP_EOL;

echo 'Logical CPU cores finders...'.PHP_EOL.PHP_EOL;
echo Diagnoser::diagnose(FinderRegistry::getDefaultLogicalFinders()).PHP_EOL;

echo 'Physical CPU cores finders...'.PHP_EOL.PHP_EOL;
echo Diagnoser::diagnose(FinderRegistry::getDefaultPhysicalFinders()).PHP_EOL;
Copyright (c) 2015-present Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 (!function_exists('grapheme_str_split')) {
    function grapheme_str_split(string $string, int $length = 1): array|false { return p\Grapheme::grapheme_str_split($string, $length); }
}
if (!function_exists('grapheme_levenshtein')) {
    function grapheme_levenshtein(string $string1, string $string2, int $insertion_cost = 1, int $replacement_cost = 1, int $deletion_cost = 1, string $locale = ''): int|false { return p\Grapheme::grapheme_levenshtein($string1, $string2, $insertion_cost, $replacement_cost, $deletion_cost); }
}
if (!function_exists('grapheme_strrev')) {
    function grapheme_strrev(string $string): string|false { return p\Grapheme::grapheme_strrev($string); }
}

if (extension_loaded('intl')) {
    return;
}

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); }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 (\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); }
}
if (!function_exists('grapheme_str_split')) {
    function grapheme_str_split(string $string, int $length = 1) { return p\Grapheme::grapheme_str_split($string, $length); }
}
if (!function_exists('grapheme_levenshtein')) {
    function grapheme_levenshtein(string $string1, string $string2, int $insertion_cost = 1, int $replacement_cost = 1, int $deletion_cost = 1, string $locale = '') { return p\Php85::grapheme_levenshtein($string1, $string2, $insertion_cost, $replacement_cost, $deletion_cost); }
}
if (!function_exists('grapheme_strrev')) {
    function grapheme_strrev(string $string) { return p\Grapheme::grapheme_strrev($string); }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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
 * - grapheme_str_split - Splits a string into an array of individual or chunks of graphemes
 * - grapheme_levenshtein - Calculate the grapheme-unit Levenshtein distance between two strings
 * - grapheme_strrev - Reverse a string by grapheme clusters
 *
 * @author Nicolas Grekas <p@tchwork.com>
 *
 * @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(static 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');
    }

    public static function grapheme_str_split($s, $len = 1)
    {
        if (0 > $len || 1073741823 < $len) {
            if (80000 > \PHP_VERSION_ID) {
                return false;
            }

            throw new \ValueError('grapheme_str_split(): Argument #2 ($length) must be greater than 0 and less than or equal to 1073741823.');
        }

        if ('' === $s) {
            return [];
        }

        if (!preg_match_all('/('.SYMFONY_GRAPHEME_CLUSTER_RX.')/u', $s, $matches)) {
            return false;
        }

        if (1 === $len) {
            return $matches[0];
        }

        $chunks = array_chunk($matches[0], $len);

        foreach ($chunks as &$chunk) {
            $chunk = implode('', $chunk);
        }

        return $chunks;
    }

    public static function grapheme_levenshtein($s1, $s2, $insertion_cost = 1, $replacement_cost = 1, $deletion_cost = 1)
    {
        if (!preg_match('//u', $s1) || !preg_match('//u', $s2)) {
            return false;
        }

        if (0 > $insertion_cost || 0 > $replacement_cost || 0 > $deletion_cost) {
            if (80000 > \PHP_VERSION_ID) {
                return false;
            }

            throw new \ValueError('grapheme_levenshtein(): Argument #3 ($insertion_cost), #4 ($replacement_cost), and #5 ($deletion_cost) must be greater than or equal to 0');
        }

        preg_match_all('/'.SYMFONY_GRAPHEME_CLUSTER_RX.'/u', $s1, $s1);
        preg_match_all('/'.SYMFONY_GRAPHEME_CLUSTER_RX.'/u', $s2, $s2);

        $s1 = $s1[0];
        $s2 = $s2[0];
        $l1 = \count($s1);
        $l2 = \count($s2);

        if (0 === $l1) {
            return $l2 * $insertion_cost;
        }
        if (0 === $l2) {
            return $l1 * $deletion_cost;
        }

        $dp = array_fill(0, $l1 + 1, array_fill(0, $l2 + 1, 0));

        for ($i = 1; $i <= $l1; ++$i) {
            $dp[$i][0] = $dp[$i - 1][0] + $deletion_cost;
        }
        for ($j = 1; $j <= $l2; ++$j) {
            $dp[0][$j] = $dp[0][$j - 1] + $insertion_cost;
        }

        for ($i = 1; $i <= $l1; ++$i) {
            for ($j = 1; $j <= $l2; ++$j) {
                $cost = ($s1[$i - 1] === $s2[$j - 1]) ? 0 : $replacement_cost;
                $dp[$i][$j] = min($dp[$i - 1][$j] + $deletion_cost, $dp[$i][$j - 1] + $insertion_cost, $dp[$i - 1][$j - 1] + $cost);
            }
        }

        return $dp[$l1][$l2];
    }

    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;
    }

    public static function grapheme_strrev(string $string)
    {
        $units = grapheme_str_split($string);

        if (false === $units) {
            return false;
        }

        return implode('', array_reverse($units));
    }
}
Copyright (c) 2004-present Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

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;

/**
 * Finder allows to build rules to find files and directories.
 *
 * It is a thin wrapper around several specialized iterator classes.
 *
 * All rules may be invoked several times.
 *
 * All methods return the current Finder object to allow chaining:
 *
 *     $finder = Finder::create()->files()->name('*.php')->in(__DIR__);
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @implements \IteratorAggregate<string, SplFileInfo>
 */
class Finder implements \IteratorAggregate, \Countable
{
    public const IGNORE_VCS_FILES = 1;
    public const IGNORE_DOT_FILES = 2;
    public const IGNORE_VCS_IGNORED_FILES = 4;

    private int $mode = 0;
    private array $names = [];
    private array $notNames = [];
    private array $exclude = [];
    private array $filters = [];
    private array $pruneFilters = [];
    private array $depths = [];
    private array $sizes = [];
    private bool $followLinks = false;
    private bool $reverseSorting = false;
    private \Closure|int|false $sort = false;
    private int $ignore = 0;
    /** @var list<string> */
    private array $dirs = [];
    private array $dates = [];
    /** @var list<iterable<SplFileInfo|\SplFileInfo|string>> */
    private array $iterators = [];
    private array $contains = [];
    private array $notContains = [];
    private array $paths = [];
    private array $notPaths = [];
    private bool $ignoreUnreadableDirs = false;

    private static array $vcsPatterns = ['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg'];

    public function __construct()
    {
        $this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES;
    }

    /**
     * Creates a new Finder.
     */
    public static function create(): static
    {
        return new static();
    }

    /**
     * Restricts the matching to directories only.
     *
     * @return $this
     */
    public function directories(): static
    {
        $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES;

        return $this;
    }

    /**
     * Restricts the matching to files only.
     *
     * @return $this
     */
    public function files(): static
    {
        $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES;

        return $this;
    }

    /**
     * Adds tests for the directory depth.
     *
     * Usage:
     *
     *     $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|string[]|int[] $levels The depth level expression or an array of depth levels
     *
     * @return $this
     *
     * @see DepthRangeFilterIterator
     * @see NumberComparator
     */
    public function depth(string|int|array $levels): static
    {
        foreach ((array) $levels as $level) {
            $this->depths[] = new NumberComparator($level);
        }

        return $this;
    }

    /**
     * Adds tests for file dates (last modified).
     *
     * The date must be something that strtotime() is able to parse:
     *
     *     $finder->date('since yesterday');
     *     $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|string[] $dates A date range string or an array of date ranges
     *
     * @return $this
     *
     * @see strtotime
     * @see DateRangeFilterIterator
     * @see DateComparator
     */
    public function date(string|array $dates): static
    {
        foreach ((array) $dates as $date) {
            $this->dates[] = new DateComparator($date);
        }

        return $this;
    }

    /**
     * Adds rules that files must match.
     *
     * You can use patterns (delimited with / sign), globs or simple strings.
     *
     *     $finder->name('/\.php$/')
     *     $finder->name('*.php') // same as above, without dot files
     *     $finder->name('test.php')
     *     $finder->name(['test.py', 'test.php'])
     *
     * @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(string|array $patterns): static
    {
        $this->names = array_merge($this->names, (array) $patterns);

        return $this;
    }

    /**
     * Adds rules that files must not match.
     *
     * @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(string|array $patterns): static
    {
        $this->notNames = array_merge($this->notNames, (array) $patterns);

        return $this;
    }

    /**
     * Adds tests that file contents must match.
     *
     * Strings or PCRE patterns can be used:
     *
     *     $finder->contains('Lorem ipsum')
     *     $finder->contains('/Lorem ipsum/i')
     *     $finder->contains(['dolor', '/ipsum/i'])
     *
     * @param string|string[] $patterns A pattern (string or regexp) or an array of patterns
     *
     * @return $this
     *
     * @see FilecontentFilterIterator
     */
    public function contains(string|array $patterns): static
    {
        $this->contains = array_merge($this->contains, (array) $patterns);

        return $this;
    }

    /**
     * Adds tests that file contents must not match.
     *
     * Strings or PCRE patterns can be used:
     *
     *     $finder->notContains('Lorem ipsum')
     *     $finder->notContains('/Lorem ipsum/i')
     *     $finder->notContains(['lorem', '/dolor/i'])
     *
     * @param string|string[] $patterns A pattern (string or regexp) or an array of patterns
     *
     * @return $this
     *
     * @see FilecontentFilterIterator
     */
    public function notContains(string|array $patterns): static
    {
        $this->notContains = array_merge($this->notContains, (array) $patterns);

        return $this;
    }

    /**
     * Adds rules that filenames must match.
     *
     * You can use patterns (delimited with / sign) or simple strings.
     *
     *     $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|string[] $patterns A pattern (a regexp or a string) or an array of patterns
     *
     * @return $this
     *
     * @see FilenameFilterIterator
     */
    public function path(string|array $patterns): static
    {
        $this->paths = array_merge($this->paths, (array) $patterns);

        return $this;
    }

    /**
     * Adds rules that filenames must not match.
     *
     * You can use patterns (delimited with / sign) or simple strings.
     *
     *     $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|string[] $patterns A pattern (a regexp or a string) or an array of patterns
     *
     * @return $this
     *
     * @see FilenameFilterIterator
     */
    public function notPath(string|array $patterns): static
    {
        $this->notPaths = array_merge($this->notPaths, (array) $patterns);

        return $this;
    }

    /**
     * Adds tests for file sizes.
     *
     *     $finder->size('> 10K');
     *     $finder->size('<= 1Ki');
     *     $finder->size(4);
     *     $finder->size(['> 10K', '< 20K'])
     *
     * @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(string|int|array $sizes): static
    {
        foreach ((array) $sizes as $size) {
            $this->sizes[] = new NumberComparator($size);
        }

        return $this;
    }

    /**
     * Excludes directories.
     *
     * Directories passed as argument must be relative to the ones defined with the `in()` method. For example:
     *
     *     $finder->in(__DIR__)->exclude('ruby');
     *
     * @param string|array $dirs A directory path or an array of directories
     *
     * @return $this
     *
     * @see ExcludeDirectoryFilterIterator
     */
    public function exclude(string|array $dirs): static
    {
        $this->exclude = array_merge($this->exclude, (array) $dirs);

        return $this;
    }

    /**
     * Excludes "hidden" directories and files (starting with a dot).
     *
     * This option is enabled by default.
     *
     * @return $this
     *
     * @see ExcludeDirectoryFilterIterator
     */
    public function ignoreDotFiles(bool $ignoreDotFiles): static
    {
        if ($ignoreDotFiles) {
            $this->ignore |= static::IGNORE_DOT_FILES;
        } else {
            $this->ignore &= ~static::IGNORE_DOT_FILES;
        }

        return $this;
    }

    /**
     * Forces the finder to ignore version control directories.
     *
     * This option is enabled by default.
     *
     * @return $this
     *
     * @see ExcludeDirectoryFilterIterator
     */
    public function ignoreVCS(bool $ignoreVCS): static
    {
        if ($ignoreVCS) {
            $this->ignore |= static::IGNORE_VCS_FILES;
        } else {
            $this->ignore &= ~static::IGNORE_VCS_FILES;
        }

        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): static
    {
        if ($ignoreVCSIgnored) {
            $this->ignore |= static::IGNORE_VCS_IGNORED_FILES;
        } else {
            $this->ignore &= ~static::IGNORE_VCS_IGNORED_FILES;
        }

        return $this;
    }

    /**
     * Adds VCS patterns.
     *
     * @see ignoreVCS()
     *
     * @param string|string[] $pattern VCS patterns to ignore
     *
     * @return void
     */
    public static function addVCSPattern(string|array $pattern)
    {
        foreach ((array) $pattern as $p) {
            self::$vcsPatterns[] = $p;
        }

        self::$vcsPatterns = array_unique(self::$vcsPatterns);
    }

    /**
     * Sorts files and directories by an anonymous function.
     *
     * The anonymous function receives two \SplFileInfo instances to compare.
     *
     * This can be slow as all the matching files and directories must be retrieved for comparison.
     *
     * @return $this
     *
     * @see SortableIterator
     */
    public function sort(\Closure $closure): static
    {
        $this->sort = $closure;

        return $this;
    }

    /**
     * Sorts files and directories by extension.
     *
     * This can be slow as all the matching files and directories must be retrieved for comparison.
     *
     * @return $this
     *
     * @see SortableIterator
     */
    public function sortByExtension(): static
    {
        $this->sort = SortableIterator::SORT_BY_EXTENSION;

        return $this;
    }

    /**
     * Sorts files and directories by name.
     *
     * This can be slow as all the matching files and directories must be retrieved for comparison.
     *
     * @return $this
     *
     * @see SortableIterator
     */
    public function sortByName(bool $useNaturalSort = false): static
    {
        $this->sort = $useNaturalSort ? SortableIterator::SORT_BY_NAME_NATURAL : SortableIterator::SORT_BY_NAME;

        return $this;
    }

    /**
     * Sorts files and directories by name case insensitive.
     *
     * This can be slow as all the matching files and directories must be retrieved for comparison.
     *
     * @return $this
     *
     * @see SortableIterator
     */
    public function sortByCaseInsensitiveName(bool $useNaturalSort = false): static
    {
        $this->sort = $useNaturalSort ? SortableIterator::SORT_BY_NAME_NATURAL_CASE_INSENSITIVE : SortableIterator::SORT_BY_NAME_CASE_INSENSITIVE;

        return $this;
    }

    /**
     * Sorts files and directories by size.
     *
     * This can be slow as all the matching files and directories must be retrieved for comparison.
     *
     * @return $this
     *
     * @see SortableIterator
     */
    public function sortBySize(): static
    {
        $this->sort = SortableIterator::SORT_BY_SIZE;

        return $this;
    }

    /**
     * Sorts files and directories by type (directories before files), then by name.
     *
     * This can be slow as all the matching files and directories must be retrieved for comparison.
     *
     * @return $this
     *
     * @see SortableIterator
     */
    public function sortByType(): static
    {
        $this->sort = SortableIterator::SORT_BY_TYPE;

        return $this;
    }

    /**
     * Sorts files and directories by the last accessed time.
     *
     * This is the time that the file was last accessed, read or written to.
     *
     * This can be slow as all the matching files and directories must be retrieved for comparison.
     *
     * @return $this
     *
     * @see SortableIterator
     */
    public function sortByAccessedTime(): static
    {
        $this->sort = SortableIterator::SORT_BY_ACCESSED_TIME;

        return $this;
    }

    /**
     * Reverses the sorting.
     *
     * @return $this
     */
    public function reverseSorting(): static
    {
        $this->reverseSorting = true;

        return $this;
    }

    /**
     * Sorts files and directories by the last inode changed time.
     *
     * This is the time that the inode information was last modified (permissions, owner, group or other metadata).
     *
     * On Windows, since inode is not available, changed time is actually the file creation time.
     *
     * This can be slow as all the matching files and directories must be retrieved for comparison.
     *
     * @return $this
     *
     * @see SortableIterator
     */
    public function sortByChangedTime(): static
    {
        $this->sort = SortableIterator::SORT_BY_CHANGED_TIME;

        return $this;
    }

    /**
     * Sorts files and directories by the last modified time.
     *
     * This is the last time the actual contents of the file were last modified.
     *
     * This can be slow as all the matching files and directories must be retrieved for comparison.
     *
     * @return $this
     *
     * @see SortableIterator
     */
    public function sortByModifiedTime(): static
    {
        $this->sort = SortableIterator::SORT_BY_MODIFIED_TIME;

        return $this;
    }

    /**
     * Filters the iterator with an anonymous function.
     *
     * The anonymous function receives a \SplFileInfo and must return false
     * to remove files.
     *
     * @param \Closure(SplFileInfo): bool $closure
     * @param bool                        $prune   Whether to skip traversing directories further
     *
     * @return $this
     *
     * @see CustomFilterIterator
     */
    public function filter(\Closure $closure /* , bool $prune = false */): static
    {
        $prune = 1 < \func_num_args() ? func_get_arg(1) : false;
        $this->filters[] = $closure;

        if ($prune) {
            $this->pruneFilters[] = $closure;
        }

        return $this;
    }

    /**
     * Forces the following of symlinks.
     *
     * @return $this
     */
    public function followLinks(): static
    {
        $this->followLinks = true;

        return $this;
    }

    /**
     * Tells finder to ignore unreadable directories.
     *
     * By default, scanning unreadable directories content throws an AccessDeniedException.
     *
     * @return $this
     */
    public function ignoreUnreadableDirs(bool $ignore = true): static
    {
        $this->ignoreUnreadableDirs = $ignore;

        return $this;
    }

    /**
     * Searches files and directories which match defined rules.
     *
     * @param string|string[] $dirs A directory path or an array of directories
     *
     * @return $this
     *
     * @throws DirectoryNotFoundException if one of the directories does not exist
     */
    public function in(string|array $dirs): static
    {
        $resolvedDirs = [];

        foreach ((array) $dirs as $dir) {
            if (is_dir($dir)) {
                $resolvedDirs[] = [$this->normalizeDir($dir)];
            } elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? \GLOB_BRACE : 0) | \GLOB_ONLYDIR | \GLOB_NOSORT)) {
                sort($glob);
                $resolvedDirs[] = array_map($this->normalizeDir(...), $glob);
            } else {
                throw new DirectoryNotFoundException(\sprintf('The "%s" directory does not exist.', $dir));
            }
        }

        $this->dirs = array_merge($this->dirs, ...$resolvedDirs);

        return $this;
    }

    /**
     * Returns an Iterator for the current Finder configuration.
     *
     * This method implements the IteratorAggregate interface.
     *
     * @return \Iterator<string, SplFileInfo>
     *
     * @throws \LogicException if the in() method has not been called
     */
    public function getIterator(): \Iterator
    {
        if (!$this->dirs && !$this->iterators) {
            throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.');
        }

        if (1 === \count($this->dirs) && !$this->iterators) {
            $iterator = $this->searchInDirectory($this->dirs[0]);
        } else {
            $iterator = new \AppendIterator();
            foreach ($this->dirs as $dir) {
                $iterator->append(new \IteratorIterator(new LazyIterator(fn () => $this->searchInDirectory($dir))));
            }

            foreach ($this->iterators as $it) {
                $iterator->append(new \IteratorIterator(new LazyIterator(static function () use ($it) {
                    foreach ($it as $file) {
                        if (!$file instanceof \SplFileInfo) {
                            $file = new \SplFileInfo($file);
                        }
                        $key = $file->getPathname();
                        if (!$file instanceof SplFileInfo) {
                            $file = new SplFileInfo($key, $file->getPath(), $key);
                        }

                        yield $key => $file;
                    }
                })));
            }
        }

        if ($this->sort || $this->reverseSorting) {
            $iterator = (new SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator();
        }

        return $iterator;
    }

    /**
     * Appends an existing set of files/directories to the finder.
     *
     * The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array.
     *
     * @param iterable<SplFileInfo|\SplFileInfo|string> $iterator
     *
     * @return $this
     */
    public function append(iterable $iterator): static
    {
        $this->iterators[] = $iterator;

        return $this;
    }

    /**
     * Check if any results were found.
     */
    public function hasResults(): bool
    {
        foreach ($this->getIterator() as $_) {
            return true;
        }

        return false;
    }

    /**
     * Counts all the results collected by the iterators.
     */
    public function count(): int
    {
        return iterator_count($this->getIterator());
    }

    private function searchInDirectory(string $dir): \Iterator
    {
        $exclude = $this->exclude;
        $notPaths = $this->notPaths;

        if ($this->pruneFilters) {
            $exclude = array_merge($exclude, $this->pruneFilters);
        }

        if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) {
            $exclude = array_merge($exclude, self::$vcsPatterns);
        }

        if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) {
            $notPaths[] = '#(^|/)\..+(/|$)#';
        }

        $minDepth = 0;
        $maxDepth = \PHP_INT_MAX;

        foreach ($this->depths as $comparator) {
            switch ($comparator->getOperator()) {
                case '>':
                    $minDepth = $comparator->getTarget() + 1;
                    break;
                case '>=':
                    $minDepth = $comparator->getTarget();
                    break;
                case '<':
                    $maxDepth = $comparator->getTarget() - 1;
                    break;
                case '<=':
                    $maxDepth = $comparator->getTarget();
                    break;
                default:
                    $minDepth = $maxDepth = $comparator->getTarget();
            }
        }

        $flags = \RecursiveDirectoryIterator::SKIP_DOTS;

        if ($this->followLinks) {
            $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS;
        }

        $iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs);

        if ($exclude) {
            $iterator = new ExcludeDirectoryFilterIterator($iterator, $exclude);
        }

        $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST);

        if ($minDepth > 0 || $maxDepth < \PHP_INT_MAX) {
            $iterator = new DepthRangeFilterIterator($iterator, $minDepth, $maxDepth);
        }

        if ($this->mode) {
            $iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode);
        }

        if ($this->names || $this->notNames) {
            $iterator = new FilenameFilterIterator($iterator, $this->names, $this->notNames);
        }

        if ($this->contains || $this->notContains) {
            $iterator = new FilecontentFilterIterator($iterator, $this->contains, $this->notContains);
        }

        if ($this->sizes) {
            $iterator = new SizeRangeFilterIterator($iterator, $this->sizes);
        }

        if ($this->dates) {
            $iterator = new DateRangeFilterIterator($iterator, $this->dates);
        }

        if ($this->filters) {
            $iterator = new CustomFilterIterator($iterator, $this->filters);
        }

        if ($this->paths || $notPaths) {
            $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $notPaths);
        }

        if (static::IGNORE_VCS_IGNORED_FILES === (static::IGNORE_VCS_IGNORED_FILES & $this->ignore)) {
            $iterator = new Iterator\VcsIgnoredFilterIterator($iterator, $dir);
        }

        return $iterator;
    }

    /**
     * Normalizes given directory names by removing trailing slashes.
     *
     * Excluding: (s)ftp:// or ssh2.(s)ftp:// wrapper
     */
    private function normalizeDir(string $dir): string
    {
        if ('/' === $dir) {
            return $dir;
        }

        $dir = rtrim($dir, '/'.\DIRECTORY_SEPARATOR);

        if (preg_match('#^(ssh2\.)?s?ftp://#', $dir)) {
            $dir .= '/';
        }

        return $dir;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <vorismi3@fel.cvut.cz>
 * @author Ahmed Abdou <mail@ahmd.io>
 */
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('~(?<!\\\\)#[^\n\r]*~', '', $gitignoreFileContent);
        $gitignoreLines = preg_split('~\r\n?|\n~', $gitignoreFileContent);

        $res = self::lineToRegex('');
        foreach ($gitignoreLines as $line) {
            $line = preg_replace('~(?<!\\\\)[ \t]+$~', '', $line);

            if (str_starts_with($line, '!')) {
                $line = substr($line, 1);
                $isNegative = true;
            } else {
                $isNegative = false;
            }

            if ('' !== $line) {
                if ($isNegative xor $inverted) {
                    $res = '(?!'.self::lineToRegex($line).'$)'.$res;
                } else {
                    $res = '(?:'.$res.'|'.self::lineToRegex($line).')';
                }
            }
        }

        return '~^(?:'.$res.')~s';
    }

    private static function lineToRegex(string $gitignoreLine): string
    {
        if ('' === $gitignoreLine) {
            return '$f'; // always false
        }

        $slashPos = strpos($gitignoreLine, '/');
        if (false !== $slashPos && \strlen($gitignoreLine) - 1 !== $slashPos) {
            if (0 === $slashPos) {
                $gitignoreLine = substr($gitignoreLine, 1);
            }
            $isAbsolute = true;
        } else {
            $isAbsolute = false;
        }

        $regex = preg_quote(str_replace('\\', '', $gitignoreLine), '~');
        $regex = preg_replace_callback('~\\\\\[((?:\\\\!)?)([^\[\]]*)\\\\\]~', fn (array $matches): string => '['.('' !== $matches[1] ? '^' : '').str_replace('\\-', '-', $matches[2]).']', $regex);
        $regex = preg_replace('~(?:(?:\\\\\*){2,}(/?))+~', '(?:(?:(?!//).(?<!//))+$1)?', $regex);
        $regex = preg_replace('~\\\\\*~', '[^/]*', $regex);
        $regex = preg_replace('~\\\\\?~', '[^/]', $regex);

        return ($isAbsolute ? '' : '(?:[^/]+/)*')
            .$regex
            .(!str_ends_with($gitignoreLine, '/') ? '(?:$|/)' : '');
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder;

/**
 * Glob matches globbing patterns against text.
 *
 *     if match_glob("foo.*", "foo.bar") echo "matched\n";
 *
 *     // prints foo.bar and foo.baz
 *     $regex = glob_to_regex("foo.*");
 *     for (['foo.bar', 'foo.baz', 'foo', 'bar'] as $t)
 *     {
 *         if (/$regex/) echo "matched: $car\n";
 *     }
 *
 * Glob implements glob(3) style matching that can be used to match
 * against text, rather than fetching names from a filesystem.
 *
 * Based on the Perl Text::Glob module.
 *
 * @author Fabien Potencier <fabien@symfony.com> PHP port
 * @author     Richard Clamp <richardc@unixbeard.net> Perl version
 * @copyright  2004-2005 Fabien Potencier <fabien@symfony.com>
 * @copyright  2002 Richard Clamp <richardc@unixbeard.net>
 */
class Glob
{
    /**
     * Returns a regexp which is the equivalent of the glob pattern.
     */
    public static function toRegex(string $glob, bool $strictLeadingDot = true, bool $strictWildcardSlash = true, string $delimiter = '#'): string
    {
        $firstByte = true;
        $escaping = false;
        $inCurlies = 0;
        $regex = '';
        if ($unanchored = str_starts_with($glob, '**/')) {
            $glob = '/'.$glob;
        }
        $sizeGlob = \strlen($glob);
        for ($i = 0; $i < $sizeGlob; ++$i) {
            $car = $glob[$i];
            if ($firstByte && $strictLeadingDot && '.' !== $car) {
                $regex .= '(?=[^\.])';
            }

            $firstByte = '/' === $car;

            if ($firstByte && $strictWildcardSlash && isset($glob[$i + 2]) && '**' === $glob[$i + 1].$glob[$i + 2] && (!isset($glob[$i + 3]) || '/' === $glob[$i + 3])) {
                $car = '[^/]++/';
                if (!isset($glob[$i + 3])) {
                    $car .= '?';
                }

                if ($strictLeadingDot) {
                    $car = '(?=[^\.])'.$car;
                }

                $car = '/(?:'.$car.')*';
                $i += 2 + isset($glob[$i + 3]);

                if ('/' === $delimiter) {
                    $car = str_replace('/', '\\/', $car);
                }
            }

            if ($delimiter === $car || '.' === $car || '(' === $car || ')' === $car || '|' === $car || '+' === $car || '^' === $car || '$' === $car) {
                $regex .= "\\$car";
            } elseif ('*' === $car) {
                $regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*');
            } elseif ('?' === $car) {
                $regex .= $escaping ? '\\?' : ($strictWildcardSlash ? '[^/]' : '.');
            } elseif ('{' === $car) {
                $regex .= $escaping ? '\\{' : '(';
                if (!$escaping) {
                    ++$inCurlies;
                }
            } elseif ('}' === $car && $inCurlies) {
                $regex .= $escaping ? '}' : ')';
                if (!$escaping) {
                    --$inCurlies;
                }
            } elseif (',' === $car && $inCurlies) {
                $regex .= $escaping ? ',' : '|';
            } elseif ('\\' === $car) {
                if ($escaping) {
                    $regex .= '\\\\';
                    $escaping = false;
                } else {
                    $escaping = true;
                }

                continue;
            } else {
                $regex .= $car;
            }
            $escaping = false;
        }

        if ($unanchored) {
            $regex = substr_replace($regex, '?', 1 + ('/' === $delimiter) + ($strictLeadingDot ? \strlen('(?=[^\.])') : 0), 0);
        }

        return $delimiter.'^'.$regex.'$'.$delimiter;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <andreas.erhard@i-med.ac.at>
 */
class DirectoryNotFoundException extends \InvalidArgumentException
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <jeanfrancois.simon@sensiolabs.com>
 */
class AccessDeniedException extends \UnexpectedValueException
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder;

/**
 * Extends \SplFileInfo to support relative paths.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class SplFileInfo extends \SplFileInfo
{
    private string $relativePath;
    private string $relativePathname;

    /**
     * @param string $file             The file name
     * @param string $relativePath     The relative path
     * @param string $relativePathname The relative path name
     */
    public function __construct(string $file, string $relativePath, string $relativePathname)
    {
        parent::__construct($file);
        $this->relativePath = $relativePath;
        $this->relativePathname = $relativePathname;
    }

    /**
     * Returns the relative path.
     *
     * This path does not contain the file name.
     */
    public function getRelativePath(): string
    {
        return $this->relativePath;
    }

    /**
     * Returns the relative path name.
     *
     * This path contains the file name.
     */
    public function getRelativePathname(): string
    {
        return $this->relativePathname;
    }

    public function getFilenameWithoutExtension(): string
    {
        $filename = $this->getFilename();

        return pathinfo($filename, \PATHINFO_FILENAME);
    }

    /**
     * Returns the contents of the file.
     *
     * @throws \RuntimeException
     */
    public function getContents(): string
    {
        set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
        try {
            $content = file_get_contents($this->getPathname());
        } finally {
            restore_error_handler();
        }
        if (false === $content) {
            throw new \RuntimeException($error);
        }

        return $content;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Comparator;

/**
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Comparator
{
    private string $target;
    private string $operator;

    public function __construct(string $target, string $operator = '==')
    {
        if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) {
            throw new \InvalidArgumentException(\sprintf('Invalid operator "%s".', $operator));
        }

        $this->target = $target;
        $this->operator = $operator;
    }

    /**
     * Gets the target value.
     */
    public function getTarget(): string
    {
        return $this->target;
    }

    /**
     * Gets the comparison operator.
     */
    public function getOperator(): string
    {
        return $this->operator;
    }

    /**
     * Tests against the target.
     */
    public function test(mixed $test): bool
    {
        return match ($this->operator) {
            '>' => $test > $this->target,
            '>=' => $test >= $this->target,
            '<' => $test < $this->target,
            '<=' => $test <= $this->target,
            '!=' => $test != $this->target,
            default => $test == $this->target,
        };
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Comparator;

/**
 * NumberComparator compiles a simple comparison to an anonymous
 * subroutine, which you can call with a value to be tested again.
 *
 * Now this would be very pointless, if NumberCompare didn't understand
 * magnitudes.
 *
 * The target value may use magnitudes of kilobytes (k, ki),
 * megabytes (m, mi), or gigabytes (g, gi).  Those suffixed
 * with an i use the appropriate 2**n version in accordance with the
 * IEC standard: http://physics.nist.gov/cuu/Units/binary.html
 *
 * Based on the Perl Number::Compare module.
 *
 * @author    Fabien Potencier <fabien@symfony.com> PHP port
 * @author    Richard Clamp <richardc@unixbeard.net> Perl version
 * @copyright 2004-2005 Fabien Potencier <fabien@symfony.com>
 * @copyright 2002 Richard Clamp <richardc@unixbeard.net>
 *
 * @see http://physics.nist.gov/cuu/Units/binary.html
 */
class NumberComparator extends Comparator
{
    /**
     * @param string|null $test A comparison string or null
     *
     * @throws \InvalidArgumentException If the test is not understood
     */
    public function __construct(?string $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];
        if (!is_numeric($target)) {
            throw new \InvalidArgumentException(\sprintf('Invalid number "%s".', $target));
        }
        if (isset($matches[3])) {
            // magnitude
            switch (strtolower($matches[3])) {
                case 'k':
                    $target *= 1000;
                    break;
                case 'ki':
                    $target *= 1024;
                    break;
                case 'm':
                    $target *= 1000000;
                    break;
                case 'mi':
                    $target *= 1024 * 1024;
                    break;
                case 'g':
                    $target *= 1000000000;
                    break;
                case 'gi':
                    $target *= 1024 * 1024 * 1024;
                    break;
            }
        }

        parent::__construct($target, $matches[1] ?: '==');
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Comparator;

/**
 * DateCompare compiles date comparisons.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class DateComparator extends Comparator
{
    /**
     * @param string $test A comparison string
     *
     * @throws \InvalidArgumentException If the test is not understood
     */
    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));
        }

        try {
            $date = new \DateTimeImmutable($matches[2]);
            $target = $date->format('U');
        } catch (\Exception) {
            throw new \InvalidArgumentException(\sprintf('"%s" is not a valid date.', $matches[2]));
        }

        $operator = $matches[1] ?: '==';
        if ('since' === $operator || 'after' === $operator) {
            $operator = '>';
        }

        if ('until' === $operator || 'before' === $operator) {
            $operator = '<';
        }

        parent::__construct($target, $operator);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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\SplFileInfo;

/**
 * ExcludeDirectoryFilterIterator filters out directories.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @extends \FilterIterator<string, SplFileInfo>
 *
 * @implements \RecursiveIterator<string, SplFileInfo>
 */
class ExcludeDirectoryFilterIterator extends \FilterIterator implements \RecursiveIterator
{
    /** @var \Iterator<string, SplFileInfo> */
    private \Iterator $iterator;
    private bool $isRecursive;
    /** @var array<string, true> */
    private array $excludedDirs = [];
    private ?string $excludedPattern = null;
    /** @var list<callable(SplFileInfo):bool> */
    private array $pruneFilters = [];

    /**
     * @param \Iterator<string, SplFileInfo>          $iterator    The Iterator to filter
     * @param list<string|callable(SplFileInfo):bool> $directories An array of directories to exclude
     */
    public function __construct(\Iterator $iterator, array $directories)
    {
        $this->iterator = $iterator;
        $this->isRecursive = $iterator instanceof \RecursiveIterator;
        $patterns = [];
        foreach ($directories as $directory) {
            if (!\is_string($directory)) {
                if (!\is_callable($directory)) {
                    throw new \InvalidArgumentException('Invalid PHP callback.');
                }

                $this->pruneFilters[] = $directory;

                continue;
            }

            $directory = rtrim($directory, '/');
            if (!$this->isRecursive || str_contains($directory, '/')) {
                $patterns[] = preg_quote($directory, '#');
            } else {
                $this->excludedDirs[$directory] = true;
            }
        }
        if ($patterns) {
            $this->excludedPattern = '#(?:^|/)(?:'.implode('|', $patterns).')(?:/|$)#';
        }

        parent::__construct($iterator);
    }

    /**
     * Filters the iterator values.
     */
    public function accept(): bool
    {
        if ($this->isRecursive && isset($this->excludedDirs[$this->getFilename()]) && $this->isDir()) {
            return false;
        }

        if ($this->excludedPattern) {
            $path = $this->isDir() ? $this->current()->getRelativePathname() : $this->current()->getRelativePath();
            $path = str_replace('\\', '/', $path);

            return !preg_match($this->excludedPattern, $path);
        }

        if ($this->pruneFilters && $this->hasChildren()) {
            foreach ($this->pruneFilters as $pruneFilter) {
                if (!$pruneFilter($this->current())) {
                    return false;
                }
            }
        }

        return true;
    }

    public function hasChildren(): bool
    {
        return $this->isRecursive && $this->iterator->hasChildren();
    }

    public function getChildren(): self
    {
        $children = new self($this->iterator->getChildren(), []);
        $children->excludedDirs = $this->excludedDirs;
        $children->excludedPattern = $this->excludedPattern;

        return $children;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * DepthRangeFilterIterator limits the directory depth.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @template-covariant TKey
 * @template-covariant TValue
 *
 * @extends \FilterIterator<TKey, TValue>
 */
class DepthRangeFilterIterator extends \FilterIterator
{
    private int $minDepth = 0;

    /**
     * @param \RecursiveIteratorIterator<\RecursiveIterator<TKey, TValue>> $iterator The Iterator to filter
     * @param int                                                          $minDepth The min depth
     * @param int                                                          $maxDepth The max depth
     */
    public function __construct(\RecursiveIteratorIterator $iterator, int $minDepth = 0, int $maxDepth = \PHP_INT_MAX)
    {
        $this->minDepth = $minDepth;
        $iterator->setMaxDepth(\PHP_INT_MAX === $maxDepth ? -1 : $maxDepth);

        parent::__construct($iterator);
    }

    /**
     * Filters the iterator values.
     */
    public function accept(): bool
    {
        return $this->getInnerIterator()->getDepth() >= $this->minDepth;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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\SplFileInfo;

/**
 * PathFilterIterator filters files by path patterns (e.g. some/special/dir).
 *
 * @author Fabien Potencier  <fabien@symfony.com>
 * @author Włodzimierz Gajda <gajdaw@gajdaw.pl>
 *
 * @extends MultiplePcreFilterIterator<string, SplFileInfo>
 */
class PathFilterIterator extends MultiplePcreFilterIterator
{
    /**
     * Filters the iterator values.
     */
    public function accept(): bool
    {
        $filename = $this->current()->getRelativePathname();

        if ('\\' === \DIRECTORY_SEPARATOR) {
            $filename = str_replace('\\', '/', $filename);
        }

        return $this->isAccepted($filename);
    }

    /**
     * Converts strings to regexp.
     *
     * PCRE patterns are left unchanged.
     *
     * Default conversion:
     *     'lorem/ipsum/dolor' ==>  'lorem\/ipsum\/dolor/'
     *
     * Use only / as directory separator (on Windows also).
     *
     * @param string $str Pattern: regexp or dirname
     */
    protected function toRegex(string $str): string
    {
        return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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é <jeremy@derusse.com>
 *
 * @internal
 */
class LazyIterator implements \IteratorAggregate
{
    private \Closure $iteratorFactory;

    public function __construct(callable $iteratorFactory)
    {
        $this->iteratorFactory = $iteratorFactory(...);
    }

    public function getIterator(): \Traversable
    {
        yield from ($this->iteratorFactory)();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * FileTypeFilterIterator only keeps files, directories, or both.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @extends \FilterIterator<string, \SplFileInfo>
 */
class FileTypeFilterIterator extends \FilterIterator
{
    public const ONLY_FILES = 1;
    public const ONLY_DIRECTORIES = 2;

    private int $mode;

    /**
     * @param \Iterator<string, \SplFileInfo> $iterator The Iterator to filter
     * @param int                             $mode     The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES)
     */
    public function __construct(\Iterator $iterator, int $mode)
    {
        $this->mode = $mode;

        parent::__construct($iterator);
    }

    /**
     * Filters the iterator values.
     */
    public function accept(): bool
    {
        $fileinfo = $this->current();
        if (self::ONLY_DIRECTORIES === (self::ONLY_DIRECTORIES & $this->mode) && $fileinfo->isFile()) {
            return false;
        } elseif (self::ONLY_FILES === (self::ONLY_FILES & $this->mode) && $fileinfo->isDir()) {
            return false;
        }

        return true;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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\Comparator\DateComparator;

/**
 * DateRangeFilterIterator filters out files that are not in the given date range (last modified dates).
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @extends \FilterIterator<string, \SplFileInfo>
 */
class DateRangeFilterIterator extends \FilterIterator
{
    private array $comparators = [];

    /**
     * @param \Iterator<string, \SplFileInfo> $iterator
     * @param DateComparator[]                $comparators
     */
    public function __construct(\Iterator $iterator, array $comparators)
    {
        $this->comparators = $comparators;

        parent::__construct($iterator);
    }

    /**
     * Filters the iterator values.
     */
    public function accept(): bool
    {
        $fileinfo = $this->current();

        if (!file_exists($fileinfo->getPathname())) {
            return false;
        }

        $filedate = $fileinfo->getMTime();
        foreach ($this->comparators as $compare) {
            if (!$compare->test($filedate)) {
                return false;
            }
        }

        return true;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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\Exception\AccessDeniedException;
use Symfony\Component\Finder\SplFileInfo;

/**
 * Extends the \RecursiveDirectoryIterator to support relative paths.
 *
 * @author Victor Berchet <victor@suumit.com>
 *
 * @extends \RecursiveDirectoryIterator<string, SplFileInfo>
 */
class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator
{
    private bool $ignoreUnreadableDirs;
    private bool $ignoreFirstRewind = true;

    // these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations
    private string $rootPath;
    private string $subPath;
    private string $directorySeparator = '/';

    /**
     * @throws \RuntimeException
     */
    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.');
        }

        parent::__construct($path, $flags);
        $this->ignoreUnreadableDirs = $ignoreUnreadableDirs;
        $this->rootPath = $path;
        if ('/' !== \DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) {
            $this->directorySeparator = \DIRECTORY_SEPARATOR;
        }
    }

    /**
     * Return an instance of SplFileInfo with support for relative paths.
     */
    public function current(): SplFileInfo
    {
        // the logic here avoids redoing the same work in all iterations

        if (!isset($this->subPath)) {
            $this->subPath = $this->getSubPath();
        }
        $subPathname = $this->subPath;
        if ('' !== $subPathname) {
            $subPathname .= $this->directorySeparator;
        }
        $subPathname .= $this->getFilename();
        $basePath = $this->rootPath;

        if ('/' !== $basePath && !str_ends_with($basePath, $this->directorySeparator) && !str_ends_with($basePath, '/')) {
            $basePath .= $this->directorySeparator;
        }

        return new SplFileInfo($basePath.$subPathname, $this->subPath, $subPathname);
    }

    public function hasChildren(bool $allowLinks = false): bool
    {
        $hasChildren = parent::hasChildren($allowLinks);

        if (!$hasChildren || !$this->ignoreUnreadableDirs) {
            return $hasChildren;
        }

        try {
            parent::getChildren();

            return true;
        } catch (\UnexpectedValueException) {
            // If directory is unreadable and finder is set to ignore it, skip children
            return false;
        }
    }

    /**
     * @throws AccessDeniedException
     */
    public function getChildren(): \RecursiveDirectoryIterator
    {
        try {
            $children = parent::getChildren();

            if ($children instanceof self) {
                // parent method will call the constructor with default arguments, so unreadable dirs won't be ignored anymore
                $children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs;

                // performance optimization to avoid redoing the same work in all children
                $children->rootPath = $this->rootPath;
            }

            return $children;
        } catch (\UnexpectedValueException $e) {
            throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e);
        }
    }

    public function next(): void
    {
        $this->ignoreFirstRewind = false;

        parent::next();
    }

    public function rewind(): void
    {
        // some streams like FTP are not rewindable, ignore the first rewind after creation,
        // as newly created DirectoryIterator does not need to be rewound
        if ($this->ignoreFirstRewind) {
            $this->ignoreFirstRewind = false;

            return;
        }

        parent::rewind();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * CustomFilterIterator filters files by applying anonymous functions.
 *
 * The anonymous function receives a \SplFileInfo and must return false
 * to remove files.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @extends \FilterIterator<string, \SplFileInfo>
 */
class CustomFilterIterator extends \FilterIterator
{
    private array $filters = [];

    /**
     * @param \Iterator<string, \SplFileInfo> $iterator The Iterator to filter
     * @param callable[]                      $filters  An array of PHP callbacks
     *
     * @throws \InvalidArgumentException
     */
    public function __construct(\Iterator $iterator, array $filters)
    {
        foreach ($filters as $filter) {
            if (!\is_callable($filter)) {
                throw new \InvalidArgumentException('Invalid PHP callback.');
            }
        }
        $this->filters = $filters;

        parent::__construct($iterator);
    }

    /**
     * Filters the iterator values.
     */
    public function accept(): bool
    {
        $fileinfo = $this->current();

        foreach ($this->filters as $filter) {
            if (false === $filter($fileinfo)) {
                return false;
            }
        }

        return true;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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;

/**
 * @extends \FilterIterator<string, \SplFileInfo>
 */
final class VcsIgnoredFilterIterator extends \FilterIterator
{
    private string $baseDir;

    /**
     * @var array<string, array{0: string, 1: string}|null>
     */
    private array $gitignoreFilesCache = [];

    /**
     * @var array<string, bool>
     */
    private array $ignoredPathsCache = [];

    /**
     * @param \Iterator<string, \SplFileInfo> $iterator
     */
    public function __construct(\Iterator $iterator, string $baseDir)
    {
        $this->baseDir = $this->normalizePath($baseDir);

        foreach ([$this->baseDir, ...$this->parentDirectoriesUpwards($this->baseDir)] as $directory) {
            if (@is_dir("{$directory}/.git")) {
                $this->baseDir = $directory;
                break;
            }
        }

        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->parentDirectoriesDownwards($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<string>
     */
    private function parentDirectoriesUpwards(string $from): array
    {
        $parentDirectories = [];

        $parentDirectory = $from;

        while (true) {
            $newParentDirectory = \dirname($parentDirectory);

            // dirname('/') = '/'
            if ($newParentDirectory === $parentDirectory) {
                break;
            }

            $parentDirectories[] = $parentDirectory = $newParentDirectory;
        }

        return $parentDirectories;
    }

    private function parentDirectoriesUpTo(string $from, string $upTo): array
    {
        return array_filter(
            $this->parentDirectoriesUpwards($from),
            static fn (string $directory): bool => str_starts_with($directory, $upTo)
        );
    }

    /**
     * @return list<string>
     */
    private function parentDirectoriesDownwards(string $fileRealPath): array
    {
        return array_reverse(
            $this->parentDirectoriesUpTo($fileRealPath, $this->baseDir)
        );
    }

    /**
     * @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;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings).
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @template-covariant TKey
 * @template-covariant TValue
 *
 * @extends \FilterIterator<TKey, TValue>
 */
abstract class MultiplePcreFilterIterator extends \FilterIterator
{
    protected $matchRegexps = [];
    protected $noMatchRegexps = [];

    /**
     * @param \Iterator<TKey, TValue> $iterator        The Iterator to filter
     * @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)
    {
        foreach ($matchPatterns as $pattern) {
            $this->matchRegexps[] = $this->toRegex($pattern);
        }

        foreach ($noMatchPatterns as $pattern) {
            $this->noMatchRegexps[] = $this->toRegex($pattern);
        }

        parent::__construct($iterator);
    }

    /**
     * Checks whether the string is accepted by the regex filters.
     *
     * If there is no regexps defined in the class, this method will accept the string.
     * Such case can be handled by child classes before calling the method if they want to
     * apply a different behavior.
     */
    protected function isAccepted(string $string): bool
    {
        // should at least not match one rule to exclude
        foreach ($this->noMatchRegexps as $regex) {
            if (preg_match($regex, $string)) {
                return false;
            }
        }

        // should at least match one rule
        if ($this->matchRegexps) {
            foreach ($this->matchRegexps as $regex) {
                if (preg_match($regex, $string)) {
                    return true;
                }
            }

            return false;
        }

        // If there is no match rules, the file is accepted
        return true;
    }

    /**
     * Checks whether the string is a regex.
     */
    protected function isRegex(string $str): bool
    {
        $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);

            if ($start === $end) {
                return !preg_match('/[*?[:alnum:] \\\\]/', $start);
            }

            foreach ([['{', '}'], ['(', ')'], ['[', ']'], ['<', '>']] as $delimiters) {
                if ($start === $delimiters[0] && $end === $delimiters[1]) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Converts string into regexp.
     */
    abstract protected function toRegex(string $str): string;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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\Comparator\NumberComparator;

/**
 * SizeRangeFilterIterator filters out files that are not in the given size range.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @extends \FilterIterator<string, \SplFileInfo>
 */
class SizeRangeFilterIterator extends \FilterIterator
{
    private array $comparators = [];

    /**
     * @param \Iterator<string, \SplFileInfo> $iterator
     * @param NumberComparator[]              $comparators
     */
    public function __construct(\Iterator $iterator, array $comparators)
    {
        $this->comparators = $comparators;

        parent::__construct($iterator);
    }

    /**
     * Filters the iterator values.
     */
    public function accept(): bool
    {
        $fileinfo = $this->current();
        if (!$fileinfo->isFile()) {
            return true;
        }

        $filesize = $fileinfo->getSize();
        foreach ($this->comparators as $compare) {
            if (!$compare->test($filesize)) {
                return false;
            }
        }

        return true;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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\Glob;

/**
 * FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string).
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @extends MultiplePcreFilterIterator<string, \SplFileInfo>
 */
class FilenameFilterIterator extends MultiplePcreFilterIterator
{
    /**
     * Filters the iterator values.
     */
    public function accept(): bool
    {
        return $this->isAccepted($this->current()->getFilename());
    }

    /**
     * Converts glob to regexp.
     *
     * PCRE patterns are left unchanged.
     * Glob strings are transformed with Glob::toRegex().
     *
     * @param string $str Pattern: glob or regexp
     */
    protected function toRegex(string $str): string
    {
        return $this->isRegex($str) ? $str : Glob::toRegex($str);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * SortableIterator applies a sort on a given Iterator.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @implements \IteratorAggregate<string, \SplFileInfo>
 */
class SortableIterator implements \IteratorAggregate
{
    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;
    public const SORT_BY_NAME_CASE_INSENSITIVE = 7;
    public const SORT_BY_NAME_NATURAL_CASE_INSENSITIVE = 8;
    public const SORT_BY_EXTENSION = 9;
    public const SORT_BY_SIZE = 10;

    /** @var \Traversable<string, \SplFileInfo> */
    private \Traversable $iterator;
    private \Closure|int $sort;

    /**
     * @param \Traversable<string, \SplFileInfo> $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, int|callable $sort, bool $reverseOrder = false)
    {
        $this->iterator = $iterator;
        $order = $reverseOrder ? -1 : 1;

        if (self::SORT_BY_NAME === $sort) {
            $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
        } elseif (self::SORT_BY_NAME_NATURAL === $sort) {
            $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
        } elseif (self::SORT_BY_NAME_CASE_INSENSITIVE === $sort) {
            $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strcasecmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
        } elseif (self::SORT_BY_NAME_NATURAL_CASE_INSENSITIVE === $sort) {
            $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strnatcasecmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
        } elseif (self::SORT_BY_TYPE === $sort) {
            $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
                if ($a->isDir() && $b->isFile()) {
                    return -$order;
                } elseif ($a->isFile() && $b->isDir()) {
                    return $order;
                }

                return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
            };
        } elseif (self::SORT_BY_ACCESSED_TIME === $sort) {
            $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getATime() - $b->getATime());
        } elseif (self::SORT_BY_CHANGED_TIME === $sort) {
            $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getCTime() - $b->getCTime());
        } elseif (self::SORT_BY_MODIFIED_TIME === $sort) {
            $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getMTime() - $b->getMTime());
        } elseif (self::SORT_BY_EXTENSION === $sort) {
            $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strnatcmp($a->getExtension(), $b->getExtension());
        } elseif (self::SORT_BY_SIZE === $sort) {
            $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getSize() - $b->getSize());
        } elseif (self::SORT_BY_NONE === $sort) {
            $this->sort = $order;
        } elseif (\is_callable($sort)) {
            $this->sort = $reverseOrder ? static fn (\SplFileInfo $a, \SplFileInfo $b) => -$sort($a, $b) : $sort(...);
        } else {
            throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.');
        }
    }

    public function getIterator(): \Traversable
    {
        if (1 === $this->sort) {
            yield from $this->iterator;

            return;
        }

        $keys = $values = [];
        foreach ($this->iterator as $key => $value) {
            $keys[] = $key;
            $values[] = $value;
        }

        if (-1 === $this->sort) {
            for ($i = \count($values) - 1; $i >= 0; --$i) {
                yield $keys[$i] => $values[$i];
            }

            return;
        }

        uasort($values, $this->sort);

        foreach ($values as $i => $v) {
            yield $keys[$i] => $v;
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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\SplFileInfo;

/**
 * FilecontentFilterIterator filters files by their contents using patterns (regexps or strings).
 *
 * @author Fabien Potencier  <fabien@symfony.com>
 * @author Włodzimierz Gajda <gajdaw@gajdaw.pl>
 *
 * @extends MultiplePcreFilterIterator<string, SplFileInfo>
 */
class FilecontentFilterIterator extends MultiplePcreFilterIterator
{
    /**
     * Filters the iterator values.
     */
    public function accept(): bool
    {
        if (!$this->matchRegexps && !$this->noMatchRegexps) {
            return true;
        }

        $fileinfo = $this->current();

        if ($fileinfo->isDir() || !$fileinfo->isReadable()) {
            return false;
        }

        $content = $fileinfo->getContents();
        if (!$content) {
            return false;
        }

        return $this->isAccepted($content);
    }

    /**
     * Converts string to regexp if necessary.
     *
     * @param string $str Pattern: string or regexp
     */
    protected function toRegex(string $str): string
    {
        return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

if (\PHP_VERSION_ID < 80400) {
    #[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION | Attribute::TARGET_CLASS_CONSTANT)]
    final class Deprecated
    {
        public readonly ?string $message;
        public readonly ?string $since;

        public function __construct(?string $message = null, ?string $since = null)
        {
            $this->message = $message;
            $this->since = $since;
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

if (\PHP_VERSION_ID < 80400) {
    enum RoundingMode
    {
        case HalfAwayFromZero;
        case HalfTowardsZero;
        case HalfEven;
        case HalfOdd;
        case TowardsZero;
        case AwayFromZero;
        case NegativeInfinity;
        case PositiveInfinity;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

if (\PHP_VERSION_ID < 80100) {
    #[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_FUNCTION | Attribute::TARGET_CLASS_CONSTANT)]
    final class Deprecated
    {
        /**
         * @readonly
         */
        public ?string $message;

        /**
         * @readonly
         */
        public ?string $since;

        public function __construct(?string $message = null, ?string $since = null)
        {
            $this->message = $message;
            $this->since = $since;
        }
    }
} elseif (\PHP_VERSION_ID < 80400) {
    require dirname(__DIR__).'/Deprecated.php';
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

if (\PHP_VERSION_ID < 80400) {
    // @author Daniel Scherzer <daniel.e.scherzer@gmail.com>
    final class ReflectionConstant
    {
        /**
         * @var string
         *
         * @readonly
         */
        public $name;

        private $value;
        private $deprecated;

        private static $persistentConstants = [];

        public function __construct(string $name)
        {
            if (!defined($name) || false !== strpos($name, '::')) {
                throw new ReflectionException("Constant \"$name\" does not exist");
            }

            $this->name = ltrim($name, '\\');
            $deprecated = false;
            $eh = set_error_handler(static function ($type, $msg, $file, $line) use ($name, &$deprecated, &$eh) {
                if (\E_DEPRECATED === $type && "Constant $name is deprecated" === $msg) {
                    return $deprecated = true;
                }

                return $eh && $eh($type, $msg, $file, $line);
            });

            try {
                $this->value = constant($name);
                $this->deprecated = $deprecated;
            } finally {
                restore_error_handler();
            }
        }

        public function getName(): string
        {
            return $this->name;
        }

        public function getValue()
        {
            return $this->value;
        }

        public function getNamespaceName(): string
        {
            if (false === $slashPos = strrpos($this->name, '\\')) {
                return '';
            }

            return substr($this->name, 0, $slashPos);
        }

        public function getShortName(): string
        {
            if (false === $slashPos = strrpos($this->name, '\\')) {
                return $this->name;
            }

            return substr($this->name, $slashPos + 1);
        }

        public function isDeprecated(): bool
        {
            return $this->deprecated;
        }

        public function __toString(): string
        {
            // A constant is persistent if provided by PHP itself rather than
            // being defined by users. If we got here, we know that it *is*
            // defined, so we just need to figure out if it is defined by the
            // user or not
            if (!self::$persistentConstants) {
                $persistentConstants = get_defined_constants(true);
                unset($persistentConstants['user']);
                foreach ($persistentConstants as $constants) {
                    self::$persistentConstants += $constants;
                }
            }
            $persistent = array_key_exists($this->name, self::$persistentConstants);

            // Can't match the inclusion of `no_file_cache` but the rest is
            // possible to match
            $result = 'Constant [ ';
            if ($persistent || $this->deprecated) {
                $result .= '<';
                if ($persistent) {
                    $result .= 'persistent';
                    if ($this->deprecated) {
                        $result .= ', ';
                    }
                }
                if ($this->deprecated) {
                    $result .= 'deprecated';
                }
                $result .= '> ';
            }
            // Cannot just use gettype() to match zend_zval_type_name()
            if (is_object($this->value)) {
                $result .= \PHP_VERSION_ID >= 80000 ? get_debug_type($this->value) : gettype($this->value);
            } elseif (is_bool($this->value)) {
                $result .= 'bool';
            } elseif (is_int($this->value)) {
                $result .= 'int';
            } elseif (is_float($this->value)) {
                $result .= 'float';
            } elseif (null === $this->value) {
                $result .= 'null';
            } else {
                $result .= gettype($this->value);
            }
            $result .= ' ';
            $result .= $this->name;
            $result .= ' ] { ';
            if (is_array($this->value)) {
                $result .= 'Array';
            } else {
                // This will throw an exception if the value is an object that
                // cannot be converted to string; that is expected and matches
                // the behavior of zval_get_string_func()
                $result .= (string) $this->value;
            }
            $result .= " }\n";

            return $result;
        }

        public function __sleep(): array
        {
            throw new Exception("Serialization of 'ReflectionConstant' is not allowed");
        }

        public function __wakeup(): void
        {
            throw new Exception("Unserialization of 'ReflectionConstant' is not allowed");
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

if (\PHP_VERSION_ID < 80100) {
    final class RoundingMode
    {
        const HalfAwayFromZero = 0;
        const HalfTowardsZero = 1;
        const HalfEven = 2;
        const HalfOdd = 3;
        const TowardsZero = 4;
        const AwayFromZero = 5;
        const NegativeInfinity = 6;
        const PositiveInfinity = 7;

        private function __construct()
        {
        }

        public static function cases(): array
        {
            return [
                self::HalfAwayFromZero,
                self::HalfTowardsZero,
                self::HalfEven,
                self::HalfOdd,
                self::TowardsZero,
                self::AwayFromZero,
                self::NegativeInfinity,
                self::PositiveInfinity,
            ];
        }
    }
} elseif (\PHP_VERSION_ID < 80400) {
    require dirname(__DIR__).'/RoundingMode.php';
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Pdo;

if (\PHP_VERSION_ID < 80400 && \extension_loaded('pdo_pgsql')) {
    class Pgsql extends \PDO
    {
        public const ATTR_DISABLE_PREPARES = \PDO::PGSQL_ATTR_DISABLE_PREPARES;

        public function __construct(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null)
        {
            parent::__construct($dsn, $username, $password, $options);

            if ('pgsql' !== $driver = $this->getAttribute(\PDO::ATTR_DRIVER_NAME)) {
                throw new \PDOException(\sprintf('Pdo\Pgsql::__construct() cannot be used for connecting to the "%s" driver', $driver));
            }
        }

        public static function connect(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null): self
        {
            try {
                return new self($dsn, $username, $password, $options);
            } catch (\PDOException $e) {
                throw preg_match('/^Pdo\\\\Pgsql::__construct\(\) cannot be used for connecting to the "([a-z]+)" driver/', $e->getMessage(), $matches) ? new \PDOException(\sprintf('Pdo\Pgsql::connect() cannot be used for connecting to the "%s" driver', $matches[1])) : $e;
            }
        }

        public function copyFromArray(string $tableName, array $rows, string $separator = "\t", string $nullAs = '\\\\N', ?string $fields = null): bool
        {
            return $this->pgsqlCopyFromArray($tableName, $rows, $separator, $nullAs, $fields);
        }

        public function copyFromFile(string $tableName, string $filename, string $separator = "\t", string $nullAs = '\\\\N', ?string $fields = null): bool
        {
            return $this->pgsqlCopyFromFile($tableName, $filename, $separator, $nullAs, $fields);
        }

        /**
         * @return array|false
         */
        public function copyToArray(string $tableName, string $separator = "\t", string $nullAs = '\\\\N', ?string $fields = null)
        {
            return $this->pgsqlCopyToArray($tableName, $separator, $nullAs, $fields);
        }

        public function copyToFile(string $tableName, string $filename, string $separator = "\t", string $nullAs = '\\\\N', ?string $fields = null): bool
        {
            return $this->pgsqlCopyToFile($tableName, $filename, $separator, $nullAs, $fields);
        }

        /**
         * @return array|false
         */
        public function getNotify(int $fetchMode = \PDO::FETCH_DEFAULT, int $timeoutMilliseconds = 0)
        {
            return $this->pgsqlGetNotify($fetchMode, $timeoutMilliseconds);
        }

        public function getPid(): int
        {
            return $this->pgsqlGetPid();
        }

        /**
         * @return string|false
         */
        public function lobCreate()
        {
            return $this->pgsqlLOBCreate();
        }

        /**
         * @return resource|false
         */
        public function lobOpen(string $oid, string $mode = 'rb')
        {
            return $this->pgsqlLOBOpen($oid, $mode);
        }

        public function lobUnlink(string $oid): bool
        {
            return $this->pgsqlLOBUnlink($oid);
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Pdo;

if (\PHP_VERSION_ID < 80400 && \extension_loaded('pdo_odbc')) {
    class Odbc extends \PDO
    {
        public const ATTR_USE_CURSOR_LIBRARY = \PDO::ODBC_ATTR_USE_CURSOR_LIBRARY;
        public const ATTR_ASSUME_UTF8 = \PDO::ODBC_ATTR_ASSUME_UTF8;
        public const SQL_USE_IF_NEEDED = \PDO::ODBC_SQL_USE_IF_NEEDED;
        public const SQL_USE_DRIVER = \PDO::ODBC_SQL_USE_DRIVER;
        public const SQL_USE_ODBC = \PDO::ODBC_SQL_USE_ODBC;

        public function __construct(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null)
        {
            parent::__construct($dsn, $username, $password, $options);

            if ('odbc' !== $driver = $this->getAttribute(\PDO::ATTR_DRIVER_NAME)) {
                throw new \PDOException(\sprintf('Pdo\Odbc::__construct() cannot be used for connecting to the "%s" driver', $driver));
            }
        }

        public static function connect(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null): self
        {
            try {
                return new self($dsn, $username, $password, $options);
            } catch (\PDOException $e) {
                throw preg_match('/^Pdo\\\\Odbc::__construct\(\) cannot be used for connecting to the "([a-z]+)" driver/', $e->getMessage(), $matches) ? new \PDOException(\sprintf('Pdo\Odbc::connect() cannot be used for connecting to the "%s" driver', $matches[1])) : $e;
            }
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Pdo;

if (\PHP_VERSION_ID < 80400 && \extension_loaded('pdo_dblib')) {
    class Dblib extends \PDO
    {
        public const ATTR_CONNECTION_TIMEOUT = \PDO::DBLIB_ATTR_CONNECTION_TIMEOUT;
        public const ATTR_QUERY_TIMEOUT = \PDO::DBLIB_ATTR_QUERY_TIMEOUT;
        public const ATTR_STRINGIFY_UNIQUEIDENTIFIER = \PDO::DBLIB_ATTR_STRINGIFY_UNIQUEIDENTIFIER;
        public const ATTR_VERSION = \PDO::DBLIB_ATTR_VERSION;
        public const ATTR_TDS_VERSION = \PHP_VERSION_ID >= 70300 ? \PDO::DBLIB_ATTR_TDS_VERSION : 1004;
        public const ATTR_SKIP_EMPTY_ROWSETS = \PHP_VERSION_ID >= 70300 ? \PDO::DBLIB_ATTR_SKIP_EMPTY_ROWSETS : 1005;
        public const ATTR_DATETIME_CONVERT = \PHP_VERSION_ID >= 70300 ? \PDO::DBLIB_ATTR_DATETIME_CONVERT : 1006;

        public function __construct(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null)
        {
            parent::__construct($dsn, $username, $password, $options);

            if ('dblib' !== $driver = $this->getAttribute(\PDO::ATTR_DRIVER_NAME)) {
                throw new \PDOException(\sprintf('Pdo\Dblib::__construct() cannot be used for connecting to the "%s" driver', $driver));
            }
        }

        public static function connect(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null): self
        {
            try {
                return new self($dsn, $username, $password, $options);
            } catch (\PDOException $e) {
                throw preg_match('/^Pdo\\\\Dblib::__construct\(\) cannot be used for connecting to the "([a-z]+)" driver/', $e->getMessage(), $matches) ? new \PDOException(\sprintf('Pdo\Dblib::connect() cannot be used for connecting to the "%s" driver', $matches[1])) : $e;
            }
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Pdo;

if (\PHP_VERSION_ID < 80400 && \extension_loaded('pdo_firebird')) {
    class Firebird extends \PDO
    {
        public const ATTR_DATE_FORMAT = \PDO::FB_ATTR_DATE_FORMAT;
        public const ATTR_TIME_FORMAT = \PDO::FB_ATTR_TIME_FORMAT;
        public const ATTR_TIMESTAMP_FORMAT = \PDO::FB_ATTR_TIMESTAMP_FORMAT;

        public function __construct(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null)
        {
            parent::__construct($dsn, $username, $password, $options);

            if ('firebird' !== $driver = $this->getAttribute(\PDO::ATTR_DRIVER_NAME)) {
                throw new \PDOException(\sprintf('Pdo\Firebird::__construct() cannot be used for connecting to the "%s" driver', $driver));
            }
        }

        public static function connect(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null): self
        {
            try {
                return new self($dsn, $username, $password, $options);
            } catch (\PDOException $e) {
                throw preg_match('/^Pdo\\\\Firebird::__construct\(\) cannot be used for connecting to the "([a-z]+)" driver/', $e->getMessage(), $matches) ? new \PDOException(\sprintf('Pdo\Firebird::connect() cannot be used for connecting to the "%s" driver', $matches[1])) : $e;
            }
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Pdo;

use PDO;

if (\PHP_VERSION_ID < 80400 && \extension_loaded('pdo_mysql')) {
    // Feature detection for non-mysqlnd; see also https://www.php.net/manual/en/class.pdo-mysql.php#pdo-mysql.constants.attr-max-buffer-size
    if (\defined('PDO::MYSQL_ATTR_MAX_BUFFER_SIZE') && \defined('PDO::MYSQL_ATTR_READ_DEFAULT_FILE') && \defined('PDO::MYSQL_ATTR_READ_DEFAULT_GROUP')) {
        class Mysql extends \PDO
        {
            public const ATTR_COMPRESS = \PDO::MYSQL_ATTR_COMPRESS;
            public const ATTR_DIRECT_QUERY = \PDO::MYSQL_ATTR_DIRECT_QUERY;
            public const ATTR_FOUND_ROWS = \PDO::MYSQL_ATTR_FOUND_ROWS;
            public const ATTR_IGNORE_SPACE = \PDO::MYSQL_ATTR_IGNORE_SPACE;
            public const ATTR_INIT_COMMAND = \PDO::MYSQL_ATTR_INIT_COMMAND;
            public const ATTR_LOCAL_INFILE = \PDO::MYSQL_ATTR_LOCAL_INFILE;
            public const ATTR_LOCAL_INFILE_DIRECTORY = \PHP_VERSION_ID >= 80100 ? \PDO::MYSQL_ATTR_LOCAL_INFILE_DIRECTORY : 1015;
            public const ATTR_MAX_BUFFER_SIZE = \PDO::MYSQL_ATTR_MAX_BUFFER_SIZE;
            public const ATTR_MULTI_STATEMENTS = \PDO::MYSQL_ATTR_MULTI_STATEMENTS;
            public const ATTR_READ_DEFAULT_FILE = \PDO::MYSQL_ATTR_READ_DEFAULT_FILE;
            public const ATTR_READ_DEFAULT_GROUP = \PDO::MYSQL_ATTR_READ_DEFAULT_GROUP;
            public const ATTR_SERVER_PUBLIC_KEY = \PDO::MYSQL_ATTR_SERVER_PUBLIC_KEY;
            public const ATTR_SSL_CA = \PDO::MYSQL_ATTR_SSL_CA;
            public const ATTR_SSL_CAPATH = \PDO::MYSQL_ATTR_SSL_CAPATH;
            public const ATTR_SSL_CERT = \PDO::MYSQL_ATTR_SSL_CERT;
            public const ATTR_SSL_CIPHER = \PDO::MYSQL_ATTR_SSL_CIPHER;
            public const ATTR_SSL_KEY = \PDO::MYSQL_ATTR_SSL_KEY;
            public const ATTR_SSL_VERIFY_SERVER_CERT = \PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT;
            public const ATTR_USE_BUFFERED_QUERY = \PDO::MYSQL_ATTR_USE_BUFFERED_QUERY;

            public function __construct(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null)
            {
                parent::__construct($dsn, $username, $password, $options);

                if ('mysql' !== $driver = $this->getAttribute(\PDO::ATTR_DRIVER_NAME)) {
                    throw new \PDOException(\sprintf('Pdo\Mysql::__construct() cannot be used for connecting to the "%s" driver', $driver));
                }
            }

            public static function connect(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null): self
            {
                try {
                    return new self($dsn, $username, $password, $options);
                } catch (\PDOException $e) {
                    throw preg_match('/^Pdo\\\\Mysql::__construct\(\) cannot be used for connecting to the "([a-z]+)" driver/', $e->getMessage(), $matches) ? new \PDOException(\sprintf('Pdo\Mysql::connect() cannot be used for connecting to the "%s" driver', $matches[1])) : $e;
                }
            }
        }
    } else {
        class Mysql extends \PDO
        {
            public const ATTR_COMPRESS = \PDO::MYSQL_ATTR_COMPRESS;
            public const ATTR_DIRECT_QUERY = \PDO::MYSQL_ATTR_DIRECT_QUERY;
            public const ATTR_FOUND_ROWS = \PDO::MYSQL_ATTR_FOUND_ROWS;
            public const ATTR_IGNORE_SPACE = \PDO::MYSQL_ATTR_IGNORE_SPACE;
            public const ATTR_INIT_COMMAND = \PDO::MYSQL_ATTR_INIT_COMMAND;
            public const ATTR_LOCAL_INFILE = \PDO::MYSQL_ATTR_LOCAL_INFILE;
            public const ATTR_LOCAL_INFILE_DIRECTORY = \PHP_VERSION_ID >= 80100 ? \PDO::MYSQL_ATTR_LOCAL_INFILE_DIRECTORY : 1015;
            // public const ATTR_MAX_BUFFER_SIZE = PDO::MYSQL_ATTR_MAX_BUFFER_SIZE; // disabled for mysqlnd
            public const ATTR_MULTI_STATEMENTS = \PDO::MYSQL_ATTR_MULTI_STATEMENTS;
            // public const ATTR_READ_DEFAULT_FILE = PDO::MYSQL_ATTR_READ_DEFAULT_FILE; // disabled for mysqlnd
            // public const ATTR_READ_DEFAULT_GROUP = PDO::MYSQL_ATTR_READ_DEFAULT_GROUP; // disabled for mysqlnd
            public const ATTR_SERVER_PUBLIC_KEY = \PDO::MYSQL_ATTR_SERVER_PUBLIC_KEY;
            public const ATTR_SSL_CA = \PDO::MYSQL_ATTR_SSL_CA;
            public const ATTR_SSL_CAPATH = \PDO::MYSQL_ATTR_SSL_CAPATH;
            public const ATTR_SSL_CERT = \PDO::MYSQL_ATTR_SSL_CERT;
            public const ATTR_SSL_CIPHER = \PDO::MYSQL_ATTR_SSL_CIPHER;
            public const ATTR_SSL_KEY = \PDO::MYSQL_ATTR_SSL_KEY;
            public const ATTR_SSL_VERIFY_SERVER_CERT = \PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT;
            public const ATTR_USE_BUFFERED_QUERY = \PDO::MYSQL_ATTR_USE_BUFFERED_QUERY;

            public function __construct(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null)
            {
                parent::__construct($dsn, $username, $password, $options);

                if ('mysql' !== $driver = $this->getAttribute(\PDO::ATTR_DRIVER_NAME)) {
                    throw new \PDOException(\sprintf('Pdo\Mysql::__construct() cannot be used for connecting to the "%s" driver', $driver));
                }
            }

            public static function connect(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null): self
            {
                try {
                    return new self($dsn, $username, $password, $options);
                } catch (\PDOException $e) {
                    throw preg_match('/^Pdo\\\\Mysql::__construct\(\) cannot be used for connecting to the "([a-z]+)" driver/', $e->getMessage(), $matches) ? new \PDOException(\sprintf('Pdo\Mysql::connect() cannot be used for connecting to the "%s" driver', $matches[1])) : $e;
                }
            }
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Pdo;

if (\PHP_VERSION_ID < 80400 && \extension_loaded('pdo_sqlite')) {
    class Sqlite extends \PDO
    {
        public const ATTR_EXTENDED_RESULT_CODES = \PHP_VERSION_ID >= 70400 ? \PDO::SQLITE_ATTR_EXTENDED_RESULT_CODES : 1002;
        public const ATTR_OPEN_FLAGS = \PHP_VERSION_ID >= 70300 ? \PDO::SQLITE_ATTR_OPEN_FLAGS : 1000;
        public const ATTR_READONLY_STATEMENT = \PHP_VERSION_ID >= 70400 ? \PDO::SQLITE_ATTR_READONLY_STATEMENT : 1001;
        public const DETERMINISTIC = \PDO::SQLITE_DETERMINISTIC;
        public const OPEN_READONLY = \PHP_VERSION_ID >= 70300 ? \PDO::SQLITE_OPEN_READONLY : 1;
        public const OPEN_READWRITE = \PHP_VERSION_ID >= 70300 ? \PDO::SQLITE_OPEN_READWRITE : 2;
        public const OPEN_CREATE = \PHP_VERSION_ID >= 70300 ? \PDO::SQLITE_OPEN_CREATE : 4;

        public function __construct(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null)
        {
            parent::__construct($dsn, $username, $password, $options);

            if ('sqlite' !== $driver = $this->getAttribute(\PDO::ATTR_DRIVER_NAME)) {
                throw new \PDOException(\sprintf('Pdo\Sqlite::__construct() cannot be used for connecting to the "%s" driver', $driver));
            }
        }

        public static function connect(string $dsn, ?string $username = null, ?string $password = null, ?array $options = null): self
        {
            try {
                return new self($dsn, $username, $password, $options);
            } catch (\PDOException $e) {
                throw preg_match('/^Pdo\\\\Sqlite::__construct\(\) cannot be used for connecting to the "([a-z]+)" driver/', $e->getMessage(), $matches) ? new \PDOException(\sprintf('Pdo\Sqlite::connect() cannot be used for connecting to the "%s" driver', $matches[1])) : $e;
            }
        }

        public function createAggregate(string $name, callable $step, callable $finalize, int $numArgs = -1): bool
        {
            return $this->sqliteCreateAggregate($name, $step, $finalize, $numArgs);
        }

        public function createCollation(string $name, callable $callback): bool
        {
            return $this->sqliteCreateCollation($name, $callback);
        }

        public function createFunction(string $function_name, callable $callback, int $num_args = -1, int $flags = 0): bool
        {
            return $this->sqliteCreateFunction($function_name, $callback, $num_args, $flags);
        }
    }
}
Copyright (c) 2024-present Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use Symfony\Polyfill\Php84 as p;

if (\PHP_VERSION_ID >= 80400) {
    return;
}

if (extension_loaded('intl') && !function_exists('grapheme_str_split')) {
    function grapheme_str_split(string $string, int $length = 1): array|false { return p\Php84::grapheme_str_split($string, $length); }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use Symfony\Polyfill\Php84 as p;

if (extension_loaded('mbstring')) {
    if (!function_exists('mb_ucfirst')) {
        function mb_ucfirst(string $string, ?string $encoding = null): string { return p\Php84::mb_ucfirst($string, $encoding); }
    }

    if (!function_exists('mb_lcfirst')) {
        function mb_lcfirst(string $string, ?string $encoding = null): string { return p\Php84::mb_lcfirst($string, $encoding); }
    }

    if (!function_exists('mb_trim')) {
        function mb_trim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Php84::mb_trim($string, $characters, $encoding); }
    }

    if (!function_exists('mb_ltrim')) {
        function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Php84::mb_ltrim($string, $characters, $encoding); }
    }

    if (!function_exists('mb_rtrim')) {
        function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Php84::mb_rtrim($string, $characters, $encoding); }
    }
}

if (extension_loaded('bcmath')) {
    if (!function_exists('bcceil')) {
        function bcceil(string $num): string { return p\Php84::bcceil($num); }
    }
    if (!function_exists('bcdivmod')) {
        function bcdivmod(string $num1, string $num2, ?int $scale = null): ?array { return p\Php84::bcdivmod($num1, $num2, $scale); }
    }
    if (!function_exists('bcfloor')) {
        function bcfloor(string $num): string { return p\Php84::bcfloor($num); }
    }
    if (!function_exists('bcround')) {
        function bcround(string $num, int $precision = 0, $mode = RoundingMode::HalfAwayFromZero): string { return p\Php84::bcround($num, $precision, $mode); }
    }
}

if (\PHP_VERSION_ID >= 80200) {
    return require __DIR__.'/bootstrap82.php';
}

if (extension_loaded('intl') && !function_exists('grapheme_str_split')) {
    function grapheme_str_split(string $string, int $length = 1) { return p\Php84::grapheme_str_split($string, $length); }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Polyfill\Php84;

/**
 * @author Ayesh Karunaratne <ayesh@aye.sh>
 * @author Pierre Ambroise <pierre27.ambroise@gmail.com>
 *
 * @internal
 */
final class Php84
{
    /** @return string|false */
    public static function mb_ucfirst(string $string, ?string $encoding = null)
    {
        if (null === $encoding) {
            $encoding = mb_internal_encoding();
        }

        try {
            $validEncoding = @mb_check_encoding('', $encoding);
        } catch (\ValueError $e) {
            throw new \ValueError(\sprintf('mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given', $encoding));
        }

        if (!$validEncoding) {
            if (80000 > \PHP_VERSION_ID) {
                trigger_error(\sprintf('mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given', $encoding), \E_USER_WARNING);

                return false;
            }

            throw new \ValueError(\sprintf('mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given', $encoding));
        }

        $firstChar = mb_substr($string, 0, 1, $encoding);
        $firstChar = mb_convert_case($firstChar, \MB_CASE_TITLE, $encoding);

        return $firstChar.mb_substr($string, 1, null, $encoding);
    }

    /** @return string|false */
    public static function mb_lcfirst(string $string, ?string $encoding = null)
    {
        if (null === $encoding) {
            $encoding = mb_internal_encoding();
        }

        try {
            $validEncoding = @mb_check_encoding('', $encoding);
        } catch (\ValueError $e) {
            throw new \ValueError(\sprintf('mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given', $encoding));
        }

        if (!$validEncoding) {
            if (80000 > \PHP_VERSION_ID) {
                trigger_error(\sprintf('mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given', $encoding), \E_USER_WARNING);

                return false;
            }

            throw new \ValueError(\sprintf('mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given', $encoding));
        }

        $firstChar = mb_substr($string, 0, 1, $encoding);
        $firstChar = mb_convert_case($firstChar, \MB_CASE_LOWER, $encoding);

        return $firstChar.mb_substr($string, 1, null, $encoding);
    }

    public static function array_find(array $array, callable $callback)
    {
        foreach ($array as $key => $value) {
            if ($callback($value, $key)) {
                return $value;
            }
        }

        return null;
    }

    public static function array_find_key(array $array, callable $callback)
    {
        foreach ($array as $key => $value) {
            if ($callback($value, $key)) {
                return $key;
            }
        }

        return null;
    }

    public static function array_any(array $array, callable $callback): bool
    {
        foreach ($array as $key => $value) {
            if ($callback($value, $key)) {
                return true;
            }
        }

        return false;
    }

    public static function array_all(array $array, callable $callback): bool
    {
        foreach ($array as $key => $value) {
            if (!$callback($value, $key)) {
                return false;
            }
        }

        return true;
    }

    public static function fpow(float $num, float $exponent): float
    {
        return $num ** $exponent;
    }

    /** @return string|false */
    public static function mb_trim(string $string, ?string $characters = null, ?string $encoding = null)
    {
        return self::mb_internal_trim('{^[%s]+|[%1$s]+$}Du', $string, $characters, $encoding, __FUNCTION__);
    }

    /** @return string|false */
    public static function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null)
    {
        return self::mb_internal_trim('{^[%s]+}Du', $string, $characters, $encoding, __FUNCTION__);
    }

    /** @return string|false */
    public static function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null)
    {
        return self::mb_internal_trim('{[%s]+$}D', $string, $characters, $encoding, __FUNCTION__);
    }

    /** @return string|false */
    private static function mb_internal_trim(string $regex, string $string, ?string $characters, ?string $encoding, string $function)
    {
        if (null === $encoding) {
            $encoding = mb_internal_encoding();
        }

        try {
            $validEncoding = @mb_check_encoding('', $encoding);
        } catch (\ValueError $e) {
            throw new \ValueError(\sprintf('%s(): Argument #3 ($encoding) must be a valid encoding, "%s" given', $function, $encoding));
        }

        if (!$validEncoding) {
            if (80000 > \PHP_VERSION_ID) {
                trigger_error(\sprintf('%s(): Argument #3 ($encoding) must be a valid encoding, "%s" given', $function, $encoding), \E_USER_WARNING);

                return false;
            }

            throw new \ValueError(\sprintf('%s(): Argument #3 ($encoding) must be a valid encoding, "%s" given', $function, $encoding));
        }

        if ('' === $characters) {
            return null === $encoding ? $string : mb_convert_encoding($string, $encoding);
        }

        if ('UTF-8' === $encoding || \in_array(strtolower($encoding), ['utf-8', 'utf8'], true)) {
            $encoding = 'UTF-8';
        }

        $string = mb_convert_encoding($string, 'UTF-8', $encoding);

        if (null !== $characters) {
            $characters = mb_convert_encoding($characters, 'UTF-8', $encoding);
        }

        if (null === $characters) {
            $characters = "\\0 \f\n\r\t\v\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{2028}\u{2029}\u{202F}\u{205F}\u{3000}\u{0085}\u{180E}";
        } else {
            $characters = preg_quote($characters);
        }

        $string = preg_replace(\sprintf($regex, $characters), '', $string);

        if ('UTF-8' === $encoding) {
            return $string;
        }

        return mb_convert_encoding($string, $encoding, 'UTF-8');
    }

    public static function grapheme_str_split(string $string, int $length)
    {
        if (0 > $length || 1073741823 < $length) {
            throw new \ValueError('grapheme_str_split(): Argument #2 ($length) must be greater than 0 and less than or equal to 1073741823.');
        }

        if ('' === $string) {
            return [];
        }

        $regex = ((float) \PCRE_VERSION < 10 ? (float) \PCRE_VERSION >= 8.32 : (float) \PCRE_VERSION >= 10.39)
            ? '\X'
            : '(?:\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}])';

        if (!preg_match_all('/'.$regex.'/u', $string, $matches)) {
            return false;
        }

        if (1 === $length) {
            return $matches[0];
        }

        $chunks = array_chunk($matches[0], $length);
        foreach ($chunks as &$chunk) {
            $chunk = implode('', $chunk);
        }

        return $chunks;
    }

    public static function bcceil(string $num): string
    {
        if (!is_numeric($num)) {
            throw new \ValueError('bcceil(): Argument #1 ($num) is not well-formed');
        }

        return self::bcround($num, 0, \RoundingMode::PositiveInfinity);
    }

    public static function bcdivmod(string $num1, string $num2, ?int $scale = null): ?array
    {
        if (null === $quot = @bcdiv($num1, $num2, 0)) {
            throw new \DivisionByZeroError('Division by zero');
        }
        $scale = $scale ?? (\PHP_VERSION_ID >= 70300 ? bcscale() : (\ini_get('bcmath.scale') ?: 0));

        return [$quot, bcmod($num1, $num2, $scale)];
    }

    public static function bcfloor(string $num): string
    {
        if (!is_numeric($num)) {
            throw new \ValueError('bcfloor(): Argument #1 ($num) is not well-formed');
        }

        return self::bcround($num, 0, \RoundingMode::NegativeInfinity);
    }

    /**
     * @param \RoundingMode|\RoundingMode::* $mode
     */
    public static function bcround(string $num, int $precision = 0, $mode = \RoundingMode::HalfAwayFromZero): string
    {
        if (!is_numeric($num)) {
            throw new \ValueError('bcround(): Argument #1 ($num) is not well-formed');
        }

        $sign = 1;
        if ('' !== $num && ('-' === $num[0] || '+' === $num[0])) {
            if ('-' === $num[0]) {
                $sign = -1;
            }

            $num = substr($num, 1);
        }

        if (false !== strpos($num, '.')) {
            [$intPart, $fracPart] = array_pad(explode('.', $num, 2), 2, '');
        } else {
            $intPart = $num;
            $fracPart = '';
        }

        if ('' === $intPart) {
            $intPart = '0';
        }

        $intPart = self::trimLeadingZeros($intPart);
        $fracPart = (string) $fracPart;

        if ($precision >= 0) {
            $fracLength = \strlen($fracPart);

            if ($precision <= $fracLength) {
                $scaledInt = $intPart.(string) substr($fracPart, 0, $precision);
                $scaledFrac = (string) substr($fracPart, $precision);
            } else {
                $scaledInt = $intPart.$fracPart.str_repeat('0', $precision - $fracLength);
                $scaledFrac = '';
            }
        } else {
            $shift = -$precision;
            $intLength = \strlen($intPart);

            if ($shift <= $intLength) {
                $splitPos = $intLength - $shift;
                $scaledInt = substr($intPart, 0, $splitPos);
                $scaledInt = '' === $scaledInt ? '0' : $scaledInt;
                $scaledFrac = substr($intPart, $splitPos).$fracPart;
            } else {
                $scaledInt = '0';
                $scaledFrac = str_repeat('0', $shift - $intLength).$intPart.$fracPart;
            }
        }

        $roundedInt = self::roundIntegerPart($scaledInt, $scaledFrac, $sign, $mode);
        $isZero = '' === trim($roundedInt, '0');
        $absResult = self::formatRoundedDigits($roundedInt, $precision);

        if (-1 === $sign && !$isZero) {
            $absResult = '-'.$absResult;
        }

        return $absResult;
    }

    private static function roundIntegerPart(string $intPart, string $fracPart, int $sign, $mode): string
    {
        $intPart = self::trimLeadingZeros($intPart);

        if ('' === $fracPart || '' === trim($fracPart, '0')) {
            return $intPart;
        }

        $firstDigit = $fracPart[0];
        $tail = (string) substr($fracPart, 1);
        $tailNonZero = '' !== trim($tail, '0');
        $isGreaterThanHalf = $firstDigit > '5' || ('5' === $firstDigit && $tailNonZero);
        $isExactlyHalf = '5' === $firstDigit && !$tailNonZero;
        $shouldIncrease = false;

        switch ($mode) {
            case \RoundingMode::TowardsZero:
                break;

            case \RoundingMode::AwayFromZero:
                $shouldIncrease = true;
                break;

            case \RoundingMode::PositiveInfinity:
                $shouldIncrease = $sign > 0;
                break;

            case \RoundingMode::NegativeInfinity:
                $shouldIncrease = $sign < 0;
                break;

            case \RoundingMode::HalfAwayFromZero:
                $shouldIncrease = $isGreaterThanHalf || $isExactlyHalf;
                break;

            case \RoundingMode::HalfTowardsZero:
                $shouldIncrease = $isGreaterThanHalf;
                break;

            case \RoundingMode::HalfEven:
                if ($isGreaterThanHalf) {
                    $shouldIncrease = true;
                } elseif ($isExactlyHalf && 1 === self::lastDigit($intPart) % 2) {
                    $shouldIncrease = true;
                }
                break;

            case \RoundingMode::HalfOdd:
                if ($isGreaterThanHalf) {
                    $shouldIncrease = true;
                } elseif ($isExactlyHalf && 0 === self::lastDigit($intPart) % 2) {
                    $shouldIncrease = true;
                }
                break;
        }

        if ($shouldIncrease) {
            $intPart = self::incrementDigits($intPart);
        }

        return self::trimLeadingZeros($intPart);
    }

    private static function formatRoundedDigits(string $roundedInt, int $precision): string
    {
        if ($precision > 0) {
            if (\strlen($roundedInt) <= $precision) {
                $roundedInt = str_pad($roundedInt, $precision + 1, '0', \STR_PAD_LEFT);
            }

            $intDigits = substr($roundedInt, 0, -$precision);
            $fracDigits = substr($roundedInt, -$precision);

            $intDigits = self::trimLeadingZeros('' === $intDigits ? '0' : $intDigits);
            $fracDigits = str_pad($fracDigits, $precision, '0', \STR_PAD_LEFT);

            return $intDigits.'.'.$fracDigits;
        }

        if (0 === $precision) {
            return self::trimLeadingZeros($roundedInt);
        }

        $shift = -$precision;
        $digits = $roundedInt.str_repeat('0', $shift);

        return self::trimLeadingZeros($digits);
    }

    private static function incrementDigits(string $digits): string
    {
        $digits = '' === $digits ? '0' : $digits;
        $index = \strlen($digits) - 1;
        $result = $digits;
        $carry = 1;

        while ($index >= 0 && $carry) {
            $value = \ord($result[$index]) - 48 + $carry;
            $carry = $value >= 10 ? 1 : 0;
            $result[$index] = \chr(48 + ($value % 10));
            --$index;
        }

        return $carry ? '1'.$result : $result;
    }

    private static function trimLeadingZeros(string $digits): string
    {
        $digits = ltrim($digits, '0');

        return '' === $digits ? '0' : $digits;
    }

    private static function lastDigit(string $digits): int
    {
        $length = \strlen($digits);

        return $length ? \ord($digits[$length - 1]) - 48 : 0;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use Symfony\Polyfill\Php84 as p;

if (\PHP_VERSION_ID >= 80400) {
    return;
}

if (defined('CURL_VERSION_HTTP3') || \PHP_VERSION_ID < 80200 && function_exists('curl_version') && curl_version()['version'] >= 0x074200) { // libcurl >= 7.66.0
    if (!defined('CURL_HTTP_VERSION_3')) {
        define('CURL_HTTP_VERSION_3', 30);
    }

    if (!defined('CURL_HTTP_VERSION_3ONLY') && defined('CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256')) { // libcurl >= 7.80.0 (7.88 would be better but is slow to check)
        define('CURL_HTTP_VERSION_3ONLY', 31);
    }
}

if (!function_exists('array_find')) {
    function array_find(array $array, callable $callback) { return p\Php84::array_find($array, $callback); }
}

if (!function_exists('array_find_key')) {
    function array_find_key(array $array, callable $callback) { return p\Php84::array_find_key($array, $callback); }
}

if (!function_exists('array_any')) {
    function array_any(array $array, callable $callback): bool { return p\Php84::array_any($array, $callback); }
}

if (!function_exists('array_all')) {
    function array_all(array $array, callable $callback): bool { return p\Php84::array_all($array, $callback); }
}

if (!function_exists('fpow')) {
    function fpow(float $num, float $exponent): float { return p\Php84::fpow($num, $exponent); }
}

if (\PHP_VERSION_ID >= 80000) {
    return require __DIR__.'/bootstrap80.php';
}

if (extension_loaded('mbstring')) {
    if (!function_exists('mb_ucfirst')) {
        function mb_ucfirst(string $string, ?string $encoding = null) { return p\Php84::mb_ucfirst($string, $encoding); }
    }

    if (!function_exists('mb_lcfirst')) {
        function mb_lcfirst(string $string, ?string $encoding = null) { return p\Php84::mb_lcfirst($string, $encoding); }
    }

    if (!function_exists('mb_trim')) {
        function mb_trim(string $string, ?string $characters = null, ?string $encoding = null) { return p\Php84::mb_trim($string, $characters, $encoding); }
    }

    if (!function_exists('mb_ltrim')) {
        function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null) { return p\Php84::mb_ltrim($string, $characters, $encoding); }
    }

    if (!function_exists('mb_rtrim')) {
        function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null) { return p\Php84::mb_rtrim($string, $characters, $encoding); }
    }
}

if (extension_loaded('bcmath')) {
    if (!function_exists('bcceil')) {
        function bcceil(string $num): string { return p\Php84::bcceil($num); }
    }
    if (!function_exists('bcdivmod')) {
        function bcdivmod(string $num1, string $num2, ?int $scale = null): ?array { return p\Php84::bcdivmod($num1, $num2, $scale); }
    }
    if (!function_exists('bcfloor')) {
        function bcfloor(string $num): string { return p\Php84::bcfloor($num); }
    }
    if (!function_exists('bcround')) {
        function bcround(string $num, int $precision = 0, $mode = RoundingMode::HalfAwayFromZero): string { return p\Php84::bcround($num, $precision, $mode); }
    }
}

if (extension_loaded('intl') && !function_exists('grapheme_str_split')) {
    function grapheme_str_split(string $string, int $length = 1) { return p\Php84::grapheme_str_split($string, $length); }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process;

use Symfony\Component\Process\Exception\InvalidArgumentException;

/**
 * ProcessUtils is a bunch of utility methods.
 *
 * This class contains static methods only and is not meant to be instantiated.
 *
 * @author Martin Hasoň <martin.hason@gmail.com>
 */
class ProcessUtils
{
    /**
     * This class should not be instantiated.
     */
    private function __construct()
    {
    }

    /**
     * Validates and normalizes a Process input.
     *
     * @param string $caller The name of method call that validates the input
     * @param mixed  $input  The input to validate
     *
     * @throws InvalidArgumentException In case the input is not valid
     */
    public static function validateInput(string $caller, mixed $input): mixed
    {
        if (null !== $input) {
            if (\is_resource($input)) {
                return $input;
            }
            if (\is_scalar($input)) {
                return (string) $input;
            }
            if ($input instanceof Process) {
                return $input->getIterator($input::ITER_SKIP_ERR);
            }
            if ($input instanceof \Iterator) {
                return $input;
            }
            if ($input instanceof \Traversable) {
                return new \IteratorIterator($input);
            }

            throw new InvalidArgumentException(\sprintf('"%s" only accepts strings, Traversable objects or stream resources.', $caller));
        }

        return $input;
    }
}
Copyright (c) 2004-present Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process;

/**
 * Generic executable finder.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class ExecutableFinder
{
    private const CMD_BUILTINS = [
        'assoc', 'break', 'call', 'cd', 'chdir', 'cls', 'color', 'copy', 'date',
        'del', 'dir', 'echo', 'endlocal', 'erase', 'exit', 'for', 'ftype', 'goto',
        'help', 'if', 'label', 'md', 'mkdir', 'mklink', 'move', 'path', 'pause',
        'popd', 'prompt', 'pushd', 'rd', 'rem', 'ren', 'rename', 'rmdir', 'set',
        'setlocal', 'shift', 'start', 'time', 'title', 'type', 'ver', 'vol',
    ];

    private array $suffixes = [];

    /**
     * Replaces default suffixes of executable.
     *
     * @return void
     */
    public function setSuffixes(array $suffixes)
    {
        $this->suffixes = $suffixes;
    }

    /**
     * Adds new possible suffix to check for executable.
     *
     * @return void
     */
    public function addSuffix(string $suffix)
    {
        $this->suffixes[] = $suffix;
    }

    /**
     * Finds an executable by name.
     *
     * @param string      $name      The executable name (without the extension)
     * @param string|null $default   The default to return if no executable is found
     * @param array       $extraDirs Additional dirs to check into
     */
    public function find(string $name, ?string $default = null, array $extraDirs = []): ?string
    {
        // windows built-in commands that are present in cmd.exe should not be resolved using PATH as they do not exist as exes
        if ('\\' === \DIRECTORY_SEPARATOR && \in_array(strtolower($name), self::CMD_BUILTINS, true)) {
            return $name;
        }

        $dirs = array_merge(
            explode(\PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')),
            $extraDirs
        );

        $suffixes = [];
        if ('\\' === \DIRECTORY_SEPARATOR) {
            $pathExt = getenv('PATHEXT');
            $suffixes = $this->suffixes;
            $suffixes = array_merge($suffixes, $pathExt ? explode(\PATH_SEPARATOR, $pathExt) : ['.exe', '.bat', '.cmd', '.com']);
        }
        $suffixes = '' !== pathinfo($name, \PATHINFO_EXTENSION) ? array_merge([''], $suffixes) : array_merge($suffixes, ['']);
        foreach ($suffixes as $suffix) {
            foreach ($dirs as $dir) {
                if ('' === $dir) {
                    $dir = '.';
                }
                if (@is_file($file = $dir.\DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === \DIRECTORY_SEPARATOR || @is_executable($file))) {
                    return $file;
                }

                if (!@is_dir($dir) && basename($dir) === $name.$suffix && @is_executable($dir)) {
                    return $dir;
                }
            }
        }

        if ('\\' === \DIRECTORY_SEPARATOR || !\function_exists('exec') || \strlen($name) !== strcspn($name, '/'.\DIRECTORY_SEPARATOR)) {
            return $default;
        }

        $execResult = exec('command -v -- '.escapeshellarg($name));

        if (($executablePath = substr($execResult, 0, strpos($execResult, \PHP_EOL) ?: null)) && @is_executable($executablePath)) {
            return $executablePath;
        }

        return $default;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process;

use Symfony\Component\Process\Exception\RuntimeException;

/**
 * Provides a way to continuously write to the input of a Process until the InputStream is closed.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 *
 * @implements \IteratorAggregate<int, string>
 */
class InputStream implements \IteratorAggregate
{
    private ?\Closure $onEmpty = null;
    private array $input = [];
    private bool $open = true;

    /**
     * Sets a callback that is called when the write buffer becomes empty.
     *
     * @return void
     */
    public function onEmpty(?callable $onEmpty = null)
    {
        $this->onEmpty = null !== $onEmpty ? $onEmpty(...) : null;
    }

    /**
     * Appends an input to the write buffer.
     *
     * @param resource|string|int|float|bool|\Traversable|null $input The input to append as scalar,
     *                                                                stream resource or \Traversable
     *
     * @return void
     */
    public function write(mixed $input)
    {
        if (null === $input) {
            return;
        }
        if ($this->isClosed()) {
            throw new RuntimeException(\sprintf('"%s" is closed.', static::class));
        }
        $this->input[] = ProcessUtils::validateInput(__METHOD__, $input);
    }

    /**
     * Closes the write buffer.
     *
     * @return void
     */
    public function close()
    {
        $this->open = false;
    }

    /**
     * Tells whether the write buffer is closed or not.
     *
     * @return bool
     */
    public function isClosed()
    {
        return !$this->open;
    }

    public function getIterator(): \Traversable
    {
        $this->open = true;

        while ($this->open || $this->input) {
            if (!$this->input) {
                yield '';
                continue;
            }
            $current = array_shift($this->input);

            if ($current instanceof \Iterator) {
                yield from $current;
            } else {
                yield $current;
            }
            if (!$this->input && $this->open && null !== $onEmpty = $this->onEmpty) {
                $this->write($onEmpty($this));
            }
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process;

use Symfony\Component\Process\Exception\InvalidArgumentException;
use Symfony\Component\Process\Exception\LogicException;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Exception\ProcessSignaledException;
use Symfony\Component\Process\Exception\ProcessTimedOutException;
use Symfony\Component\Process\Exception\RuntimeException;
use Symfony\Component\Process\Pipes\UnixPipes;
use Symfony\Component\Process\Pipes\WindowsPipes;

/**
 * Process is a thin wrapper around proc_* functions to easily
 * start independent PHP processes.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Romain Neutron <imprec@gmail.com>
 *
 * @implements \IteratorAggregate<string, string>
 */
class Process implements \IteratorAggregate
{
    public const ERR = 'err';
    public const OUT = 'out';

    public const STATUS_READY = 'ready';
    public const STATUS_STARTED = 'started';
    public const STATUS_TERMINATED = 'terminated';

    public const STDIN = 0;
    public const STDOUT = 1;
    public const STDERR = 2;

    // Timeout Precision in seconds.
    public const TIMEOUT_PRECISION = 0.2;

    public const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking
    public const ITER_KEEP_OUTPUT = 2;  // By default, outputs are cleared while iterating, use this flag to keep them in memory
    public const ITER_SKIP_OUT = 4;     // Use this flag to skip STDOUT while iterating
    public const ITER_SKIP_ERR = 8;     // Use this flag to skip STDERR while iterating

    private ?\Closure $callback = null;
    private array|string $commandline;
    private ?string $cwd;
    private array $env = [];
    /** @var resource|string|\Iterator|null */
    private $input;
    private ?float $starttime = null;
    private ?float $lastOutputTime = null;
    private ?float $timeout = null;
    private ?float $idleTimeout = null;
    private ?int $exitcode = null;
    private array $fallbackStatus = [];
    private array $processInformation;
    private bool $outputDisabled = false;
    /** @var resource */
    private $stdout;
    /** @var resource */
    private $stderr;
    /** @var resource|null */
    private $process;
    private string $status = self::STATUS_READY;
    private int $incrementalOutputOffset = 0;
    private int $incrementalErrorOutputOffset = 0;
    private bool $tty = false;
    private bool $pty;
    private array $options = ['suppress_errors' => true, 'bypass_shell' => true];

    private WindowsPipes|UnixPipes $processPipes;

    private ?int $latestSignal = null;

    private static ?bool $sigchild = null;

    /**
     * Exit codes translation table.
     *
     * User-defined errors must use exit codes in the 64-113 range.
     */
    public static $exitCodes = [
        0 => 'OK',
        1 => 'General error',
        2 => 'Misuse of shell builtins',

        126 => 'Invoked command cannot execute',
        127 => 'Command not found',
        128 => 'Invalid exit argument',

        // signals
        129 => 'Hangup',
        130 => 'Interrupt',
        131 => 'Quit and dump core',
        132 => 'Illegal instruction',
        133 => 'Trace/breakpoint trap',
        134 => 'Process aborted',
        135 => 'Bus error: "access to undefined portion of memory object"',
        136 => 'Floating point exception: "erroneous arithmetic operation"',
        137 => 'Kill (terminate immediately)',
        138 => 'User-defined 1',
        139 => 'Segmentation violation',
        140 => 'User-defined 2',
        141 => 'Write to pipe with no one reading',
        142 => 'Signal raised by alarm',
        143 => 'Termination (request to terminate)',
        // 144 - not defined
        145 => 'Child process terminated, stopped (or continued*)',
        146 => 'Continue if stopped',
        147 => 'Stop executing temporarily',
        148 => 'Terminal stop signal',
        149 => 'Background process attempting to read from tty ("in")',
        150 => 'Background process attempting to write to tty ("out")',
        151 => 'Urgent data available on socket',
        152 => 'CPU time limit exceeded',
        153 => 'File size limit exceeded',
        154 => 'Signal raised by timer counting virtual time: "virtual timer expired"',
        155 => 'Profiling timer expired',
        // 156 - not defined
        157 => 'Pollable event',
        // 158 - not defined
        159 => 'Bad syscall',
    ];

    /**
     * @param array          $command The command to run and its arguments listed as separate entries
     * @param string|null    $cwd     The working directory or null to use the working dir of the current PHP process
     * @param array|null     $env     The environment variables or null to use the same environment as the current PHP process
     * @param mixed          $input   The input as stream resource, scalar or \Traversable, or null for no input
     * @param int|float|null $timeout The timeout in seconds or null to disable
     *
     * @throws LogicException When proc_open is not installed
     */
    public function __construct(array $command, ?string $cwd = null, ?array $env = null, mixed $input = null, ?float $timeout = 60)
    {
        if (!\function_exists('proc_open')) {
            throw new LogicException('The Process class relies on proc_open, which is not available on your PHP installation.');
        }

        $this->commandline = $command;
        $this->cwd = $cwd;

        // on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started
        // on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected
        // @see : https://bugs.php.net/51800
        // @see : https://bugs.php.net/50524
        if (null === $this->cwd && (\defined('ZEND_THREAD_SAFE') || '\\' === \DIRECTORY_SEPARATOR)) {
            $this->cwd = getcwd();
        }
        if (null !== $env) {
            $this->setEnv($env);
        }

        $this->setInput($input);
        $this->setTimeout($timeout);
        $this->pty = false;
    }

    /**
     * Creates a Process instance as a command-line to be run in a shell wrapper.
     *
     * Command-lines are parsed by the shell of your OS (/bin/sh on Unix-like, cmd.exe on Windows.)
     * This allows using e.g. pipes or conditional execution. In this mode, signals are sent to the
     * shell wrapper and not to your commands.
     *
     * In order to inject dynamic values into command-lines, we strongly recommend using placeholders.
     * This will save escaping values, which is not portable nor secure anyway:
     *
     *   $process = Process::fromShellCommandline('my_command "${:MY_VAR}"');
     *   $process->run(null, ['MY_VAR' => $theValue]);
     *
     * @param string         $command The command line to pass to the shell of the OS
     * @param string|null    $cwd     The working directory or null to use the working dir of the current PHP process
     * @param array|null     $env     The environment variables or null to use the same environment as the current PHP process
     * @param mixed          $input   The input as stream resource, scalar or \Traversable, or null for no input
     * @param int|float|null $timeout The timeout in seconds or null to disable
     *
     * @throws LogicException When proc_open is not installed
     */
    public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, mixed $input = null, ?float $timeout = 60): static
    {
        $process = new static([], $cwd, $env, $input, $timeout);
        $process->commandline = $command;

        return $process;
    }

    public function __serialize(): array
    {
        throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
    }

    public function __unserialize(array $data): void
    {
        throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
    }

    public function __destruct()
    {
        if ($this->options['create_new_console'] ?? false) {
            $this->processPipes->close();
        } else {
            $this->stop(0);
        }
    }

    public function __clone()
    {
        $this->resetProcessData();
    }

    /**
     * Runs the process.
     *
     * The callback receives the type of output (out or err) and
     * some bytes from the output in real-time. It allows to have feedback
     * from the independent process during execution.
     *
     * The STDOUT and STDERR are also available after the process is finished
     * via the getOutput() and getErrorOutput() methods.
     *
     * @param callable|null $callback A PHP callback to run whenever there is some
     *                                output available on STDOUT or STDERR
     *
     * @return int The exit status code
     *
     * @throws RuntimeException         When process can't be launched
     * @throws RuntimeException         When process is already running
     * @throws ProcessTimedOutException When process timed out
     * @throws ProcessSignaledException When process stopped after receiving signal
     * @throws LogicException           In case a callback is provided and output has been disabled
     *
     * @final
     */
    public function run(?callable $callback = null, array $env = []): int
    {
        $this->start($callback, $env);

        return $this->wait();
    }

    /**
     * Runs the process.
     *
     * This is identical to run() except that an exception is thrown if the process
     * exits with a non-zero exit code.
     *
     * @return $this
     *
     * @throws ProcessFailedException   When process didn't terminate successfully
     * @throws RuntimeException         When process can't be launched
     * @throws RuntimeException         When process is already running
     * @throws ProcessTimedOutException When process timed out
     * @throws ProcessSignaledException When process stopped after receiving signal
     * @throws LogicException           In case a callback is provided and output has been disabled
     *
     * @final
     */
    public function mustRun(?callable $callback = null, array $env = []): static
    {
        if (0 !== $this->run($callback, $env)) {
            throw new ProcessFailedException($this);
        }

        return $this;
    }

    /**
     * Starts the process and returns after writing the input to STDIN.
     *
     * This method blocks until all STDIN data is sent to the process then it
     * returns while the process runs in the background.
     *
     * The termination of the process can be awaited with wait().
     *
     * The callback receives the type of output (out or err) and some bytes from
     * the output in real-time while writing the standard input to the process.
     * It allows to have feedback from the independent process during execution.
     *
     * @param callable|null $callback A PHP callback to run whenever there is some
     *                                output available on STDOUT or STDERR
     *
     * @return void
     *
     * @throws RuntimeException When process can't be launched
     * @throws RuntimeException When process is already running
     * @throws LogicException   In case a callback is provided and output has been disabled
     */
    public function start(?callable $callback = null, array $env = [])
    {
        if ($this->isRunning()) {
            throw new RuntimeException('Process is already running.');
        }

        $this->resetProcessData();
        $this->starttime = $this->lastOutputTime = microtime(true);
        $this->callback = $this->buildCallback($callback);
        $descriptors = $this->getDescriptors(null !== $callback);

        if ($this->env) {
            $env += '\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($this->env, $env, 'strcasecmp') : $this->env;
        }

        $env += '\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($this->getDefaultEnv(), $env, 'strcasecmp') : $this->getDefaultEnv();

        if (\is_array($commandline = $this->commandline)) {
            $commandline = implode(' ', array_map($this->escapeArgument(...), $commandline));

            if ('\\' !== \DIRECTORY_SEPARATOR) {
                // exec is mandatory to deal with sending a signal to the process
                $commandline = 'exec '.$commandline;
            }
        } else {
            $commandline = $this->replacePlaceholders($commandline, $env);
        }

        if ('\\' === \DIRECTORY_SEPARATOR) {
            $commandline = $this->prepareWindowsCommandLine($commandline, $env);
        } elseif ($this->isSigchildEnabled()) {
            // last exit code is output on the fourth pipe and caught to work around --enable-sigchild
            $descriptors[3] = ['pipe', 'w'];

            // See https://unix.stackexchange.com/questions/71205/background-process-pipe-input
            $commandline = '{ ('.$commandline.') <&3 3<&- 3>/dev/null & } 3<&0;';
            $commandline .= 'pid=$!; echo $pid >&3; wait $pid 2>/dev/null; code=$?; echo $code >&3; exit $code';
        }

        $envPairs = [];
        foreach ($env as $k => $v) {
            if (false !== $v && !\in_array($k = (string) $k, ['', 'argc', 'argv', 'ARGC', 'ARGV'], true) && !str_contains($k, '=') && !str_contains($k, "\0")) {
                $envPairs[] = $k.'='.$v;
            }
        }

        if (!is_dir($this->cwd)) {
            throw new RuntimeException(\sprintf('The provided cwd "%s" does not exist.', $this->cwd));
        }

        $process = @proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $this->options);

        if (!$process) {
            throw new RuntimeException('Unable to launch a new process.');
        }
        $this->process = $process;
        $this->status = self::STATUS_STARTED;

        if (isset($descriptors[3])) {
            $this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]);
        }

        if ($this->tty) {
            return;
        }

        $this->updateStatus(false);
        $this->checkTimeout();
    }

    /**
     * Restarts the process.
     *
     * Be warned that the process is cloned before being started.
     *
     * @param callable|null $callback A PHP callback to run whenever there is some
     *                                output available on STDOUT or STDERR
     *
     * @throws RuntimeException When process can't be launched
     * @throws RuntimeException When process is already running
     *
     * @see start()
     *
     * @final
     */
    public function restart(?callable $callback = null, array $env = []): static
    {
        if ($this->isRunning()) {
            throw new RuntimeException('Process is already running.');
        }

        $process = clone $this;
        $process->start($callback, $env);

        return $process;
    }

    /**
     * Waits for the process to terminate.
     *
     * The callback receives the type of output (out or err) and some bytes
     * from the output in real-time while writing the standard input to the process.
     * It allows to have feedback from the independent process during execution.
     *
     * @param callable|null $callback A valid PHP callback
     *
     * @return int The exitcode of the process
     *
     * @throws ProcessTimedOutException When process timed out
     * @throws ProcessSignaledException When process stopped after receiving signal
     * @throws LogicException           When process is not yet started
     */
    public function wait(?callable $callback = null): int
    {
        $this->requireProcessIsStarted(__FUNCTION__);

        $this->updateStatus(false);

        if (null !== $callback) {
            if (!$this->processPipes->haveReadSupport()) {
                $this->stop(0);
                throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::wait".');
            }
            $this->callback = $this->buildCallback($callback);
        }

        do {
            $this->checkTimeout();
            $running = $this->isRunning() && ('\\' === \DIRECTORY_SEPARATOR || $this->processPipes->areOpen());
            $this->readPipes($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
        } while ($running);

        while ($this->isRunning()) {
            $this->checkTimeout();
            usleep(1000);
        }

        if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) {
            throw new ProcessSignaledException($this);
        }

        return $this->exitcode;
    }

    /**
     * Waits until the callback returns true.
     *
     * The callback receives the type of output (out or err) and some bytes
     * from the output in real-time while writing the standard input to the process.
     * It allows to have feedback from the independent process during execution.
     *
     * @throws RuntimeException         When process timed out
     * @throws LogicException           When process is not yet started
     * @throws ProcessTimedOutException In case the timeout was reached
     */
    public function waitUntil(callable $callback): bool
    {
        $this->requireProcessIsStarted(__FUNCTION__);
        $this->updateStatus(false);

        if (!$this->processPipes->haveReadSupport()) {
            $this->stop(0);
            throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::waitUntil".');
        }
        $callback = $this->buildCallback($callback);

        $ready = false;
        while (true) {
            $this->checkTimeout();
            $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
            $output = $this->processPipes->readAndWrite($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);

            foreach ($output as $type => $data) {
                if (3 !== $type) {
                    $ready = $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data) || $ready;
                } elseif (!isset($this->fallbackStatus['signaled'])) {
                    $this->fallbackStatus['exitcode'] = (int) $data;
                }
            }
            if ($ready) {
                return true;
            }
            if (!$running) {
                return false;
            }

            usleep(1000);
        }
    }

    /**
     * Returns the Pid (process identifier), if applicable.
     *
     * @return int|null The process id if running, null otherwise
     */
    public function getPid(): ?int
    {
        return $this->isRunning() ? $this->processInformation['pid'] : null;
    }

    /**
     * Sends a POSIX signal to the process.
     *
     * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants)
     *
     * @return $this
     *
     * @throws LogicException   In case the process is not running
     * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
     * @throws RuntimeException In case of failure
     */
    public function signal(int $signal): static
    {
        $this->doSignal($signal, true);

        return $this;
    }

    /**
     * Disables fetching output and error output from the underlying process.
     *
     * @return $this
     *
     * @throws RuntimeException In case the process is already running
     * @throws LogicException   if an idle timeout is set
     */
    public function disableOutput(): static
    {
        if ($this->isRunning()) {
            throw new RuntimeException('Disabling output while the process is running is not possible.');
        }
        if (null !== $this->idleTimeout) {
            throw new LogicException('Output cannot be disabled while an idle timeout is set.');
        }

        $this->outputDisabled = true;

        return $this;
    }

    /**
     * Enables fetching output and error output from the underlying process.
     *
     * @return $this
     *
     * @throws RuntimeException In case the process is already running
     */
    public function enableOutput(): static
    {
        if ($this->isRunning()) {
            throw new RuntimeException('Enabling output while the process is running is not possible.');
        }

        $this->outputDisabled = false;

        return $this;
    }

    /**
     * Returns true in case the output is disabled, false otherwise.
     */
    public function isOutputDisabled(): bool
    {
        return $this->outputDisabled;
    }

    /**
     * Returns the current output of the process (STDOUT).
     *
     * @throws LogicException in case the output has been disabled
     * @throws LogicException In case the process is not started
     */
    public function getOutput(): string
    {
        $this->readPipesForOutput(__FUNCTION__);

        if (false === $ret = stream_get_contents($this->stdout, -1, 0)) {
            return '';
        }

        return $ret;
    }

    /**
     * Returns the output incrementally.
     *
     * In comparison with the getOutput method which always return the whole
     * output, this one returns the new output since the last call.
     *
     * @throws LogicException in case the output has been disabled
     * @throws LogicException In case the process is not started
     */
    public function getIncrementalOutput(): string
    {
        $this->readPipesForOutput(__FUNCTION__);

        $latest = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
        $this->incrementalOutputOffset = ftell($this->stdout);

        if (false === $latest) {
            return '';
        }

        return $latest;
    }

    /**
     * Returns an iterator to the output of the process, with the output type as keys (Process::OUT/ERR).
     *
     * @param int $flags A bit field of Process::ITER_* flags
     *
     * @return \Generator<string, string>
     *
     * @throws LogicException in case the output has been disabled
     * @throws LogicException In case the process is not started
     */
    public function getIterator(int $flags = 0): \Generator
    {
        $this->readPipesForOutput(__FUNCTION__, false);

        $clearOutput = !(self::ITER_KEEP_OUTPUT & $flags);
        $blocking = !(self::ITER_NON_BLOCKING & $flags);
        $yieldOut = !(self::ITER_SKIP_OUT & $flags);
        $yieldErr = !(self::ITER_SKIP_ERR & $flags);

        while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) {
            if ($yieldOut) {
                $out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);

                if (isset($out[0])) {
                    if ($clearOutput) {
                        $this->clearOutput();
                    } else {
                        $this->incrementalOutputOffset = ftell($this->stdout);
                    }

                    yield self::OUT => $out;
                }
            }

            if ($yieldErr) {
                $err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);

                if (isset($err[0])) {
                    if ($clearOutput) {
                        $this->clearErrorOutput();
                    } else {
                        $this->incrementalErrorOutputOffset = ftell($this->stderr);
                    }

                    yield self::ERR => $err;
                }
            }

            if (!$blocking && !isset($out[0]) && !isset($err[0])) {
                yield self::OUT => '';
            }

            $this->checkTimeout();
            $this->readPipesForOutput(__FUNCTION__, $blocking);
        }
    }

    /**
     * Clears the process output.
     *
     * @return $this
     */
    public function clearOutput(): static
    {
        ftruncate($this->stdout, 0);
        fseek($this->stdout, 0);
        $this->incrementalOutputOffset = 0;

        return $this;
    }

    /**
     * Returns the current error output of the process (STDERR).
     *
     * @throws LogicException in case the output has been disabled
     * @throws LogicException In case the process is not started
     */
    public function getErrorOutput(): string
    {
        $this->readPipesForOutput(__FUNCTION__);

        if (false === $ret = stream_get_contents($this->stderr, -1, 0)) {
            return '';
        }

        return $ret;
    }

    /**
     * Returns the errorOutput incrementally.
     *
     * In comparison with the getErrorOutput method which always return the
     * whole error output, this one returns the new error output since the last
     * call.
     *
     * @throws LogicException in case the output has been disabled
     * @throws LogicException In case the process is not started
     */
    public function getIncrementalErrorOutput(): string
    {
        $this->readPipesForOutput(__FUNCTION__);

        $latest = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
        $this->incrementalErrorOutputOffset = ftell($this->stderr);

        if (false === $latest) {
            return '';
        }

        return $latest;
    }

    /**
     * Clears the process output.
     *
     * @return $this
     */
    public function clearErrorOutput(): static
    {
        ftruncate($this->stderr, 0);
        fseek($this->stderr, 0);
        $this->incrementalErrorOutputOffset = 0;

        return $this;
    }

    /**
     * Returns the exit code returned by the process.
     *
     * @return int|null The exit status code, null if the Process is not terminated
     */
    public function getExitCode(): ?int
    {
        $this->updateStatus(false);

        return $this->exitcode;
    }

    /**
     * Returns a string representation for the exit code returned by the process.
     *
     * This method relies on the Unix exit code status standardization
     * and might not be relevant for other operating systems.
     *
     * @return string|null A string representation for the exit status code, null if the Process is not terminated
     *
     * @see http://tldp.org/LDP/abs/html/exitcodes.html
     * @see http://en.wikipedia.org/wiki/Unix_signal
     */
    public function getExitCodeText(): ?string
    {
        if (null === $exitcode = $this->getExitCode()) {
            return null;
        }

        return self::$exitCodes[$exitcode] ?? 'Unknown error';
    }

    /**
     * Checks if the process ended successfully.
     */
    public function isSuccessful(): bool
    {
        return 0 === $this->getExitCode();
    }

    /**
     * Returns true if the child process has been terminated by an uncaught signal.
     *
     * It always returns false on Windows.
     *
     * @throws LogicException In case the process is not terminated
     */
    public function hasBeenSignaled(): bool
    {
        $this->requireProcessIsTerminated(__FUNCTION__);

        return $this->processInformation['signaled'];
    }

    /**
     * Returns the number of the signal that caused the child process to terminate its execution.
     *
     * It is only meaningful if hasBeenSignaled() returns true.
     *
     * @throws RuntimeException In case --enable-sigchild is activated
     * @throws LogicException   In case the process is not terminated
     */
    public function getTermSignal(): int
    {
        $this->requireProcessIsTerminated(__FUNCTION__);

        if ($this->isSigchildEnabled() && -1 === $this->processInformation['termsig']) {
            throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal cannot be retrieved.');
        }

        return $this->processInformation['termsig'];
    }

    /**
     * Returns true if the child process has been stopped by a signal.
     *
     * It always returns false on Windows.
     *
     * @throws LogicException In case the process is not terminated
     */
    public function hasBeenStopped(): bool
    {
        $this->requireProcessIsTerminated(__FUNCTION__);

        return $this->processInformation['stopped'];
    }

    /**
     * Returns the number of the signal that caused the child process to stop its execution.
     *
     * It is only meaningful if hasBeenStopped() returns true.
     *
     * @throws LogicException In case the process is not terminated
     */
    public function getStopSignal(): int
    {
        $this->requireProcessIsTerminated(__FUNCTION__);

        return $this->processInformation['stopsig'];
    }

    /**
     * Checks if the process is currently running.
     */
    public function isRunning(): bool
    {
        if (self::STATUS_STARTED !== $this->status) {
            return false;
        }

        $this->updateStatus(false);

        return $this->processInformation['running'];
    }

    /**
     * Checks if the process has been started with no regard to the current state.
     */
    public function isStarted(): bool
    {
        return self::STATUS_READY != $this->status;
    }

    /**
     * Checks if the process is terminated.
     */
    public function isTerminated(): bool
    {
        $this->updateStatus(false);

        return self::STATUS_TERMINATED == $this->status;
    }

    /**
     * Gets the process status.
     *
     * The status is one of: ready, started, terminated.
     */
    public function getStatus(): string
    {
        $this->updateStatus(false);

        return $this->status;
    }

    /**
     * Stops the process.
     *
     * @param int|float $timeout The timeout in seconds
     * @param int|null  $signal  A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9)
     *
     * @return int|null The exit-code of the process or null if it's not running
     */
    public function stop(float $timeout = 10, ?int $signal = null): ?int
    {
        $timeoutMicro = microtime(true) + $timeout;
        if ($this->isRunning()) {
            // given SIGTERM may not be defined and that "proc_terminate" uses the constant value and not the constant itself, we use the same here
            $this->doSignal(15, false);
            do {
                usleep(1000);
            } while ($this->isRunning() && microtime(true) < $timeoutMicro);

            if ($this->isRunning()) {
                // Avoid exception here: process is supposed to be running, but it might have stopped just
                // after this line. In any case, let's silently discard the error, we cannot do anything.
                $this->doSignal($signal ?: 9, false);
            }
        }

        if ($this->isRunning()) {
            if (isset($this->fallbackStatus['pid'])) {
                unset($this->fallbackStatus['pid']);

                return $this->stop(0, $signal);
            }
            $this->close();
        }

        return $this->exitcode;
    }

    /**
     * Adds a line to the STDOUT stream.
     *
     * @internal
     */
    public function addOutput(string $line): void
    {
        $this->lastOutputTime = microtime(true);

        fseek($this->stdout, 0, \SEEK_END);
        fwrite($this->stdout, $line);
        fseek($this->stdout, $this->incrementalOutputOffset);
    }

    /**
     * Adds a line to the STDERR stream.
     *
     * @internal
     */
    public function addErrorOutput(string $line): void
    {
        $this->lastOutputTime = microtime(true);

        fseek($this->stderr, 0, \SEEK_END);
        fwrite($this->stderr, $line);
        fseek($this->stderr, $this->incrementalErrorOutputOffset);
    }

    /**
     * Gets the last output time in seconds.
     */
    public function getLastOutputTime(): ?float
    {
        return $this->lastOutputTime;
    }

    /**
     * Gets the command line to be executed.
     */
    public function getCommandLine(): string
    {
        return \is_array($this->commandline) ? implode(' ', array_map($this->escapeArgument(...), $this->commandline)) : $this->commandline;
    }

    /**
     * Gets the process timeout in seconds (max. runtime).
     */
    public function getTimeout(): ?float
    {
        return $this->timeout;
    }

    /**
     * Gets the process idle timeout in seconds (max. time since last output).
     */
    public function getIdleTimeout(): ?float
    {
        return $this->idleTimeout;
    }

    /**
     * Sets the process timeout (max. runtime) in seconds.
     *
     * To disable the timeout, set this value to null.
     *
     * @return $this
     *
     * @throws InvalidArgumentException if the timeout is negative
     */
    public function setTimeout(?float $timeout): static
    {
        $this->timeout = $this->validateTimeout($timeout);

        return $this;
    }

    /**
     * Sets the process idle timeout (max. time since last output) in seconds.
     *
     * To disable the timeout, set this value to null.
     *
     * @return $this
     *
     * @throws LogicException           if the output is disabled
     * @throws InvalidArgumentException if the timeout is negative
     */
    public function setIdleTimeout(?float $timeout): static
    {
        if (null !== $timeout && $this->outputDisabled) {
            throw new LogicException('Idle timeout cannot be set while the output is disabled.');
        }

        $this->idleTimeout = $this->validateTimeout($timeout);

        return $this;
    }

    /**
     * Enables or disables the TTY mode.
     *
     * @return $this
     *
     * @throws RuntimeException In case the TTY mode is not supported
     */
    public function setTty(bool $tty): static
    {
        if ('\\' === \DIRECTORY_SEPARATOR && $tty) {
            throw new RuntimeException('TTY mode is not supported on Windows platform.');
        }

        if ($tty && !self::isTtySupported()) {
            throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.');
        }

        $this->tty = $tty;

        return $this;
    }

    /**
     * Checks if the TTY mode is enabled.
     */
    public function isTty(): bool
    {
        return $this->tty;
    }

    /**
     * Sets PTY mode.
     *
     * @return $this
     */
    public function setPty(bool $bool): static
    {
        $this->pty = $bool;

        return $this;
    }

    /**
     * Returns PTY state.
     */
    public function isPty(): bool
    {
        return $this->pty;
    }

    /**
     * Gets the working directory.
     */
    public function getWorkingDirectory(): ?string
    {
        if (null === $this->cwd) {
            // getcwd() will return false if any one of the parent directories does not have
            // the readable or search mode set, even if the current directory does
            return getcwd() ?: null;
        }

        return $this->cwd;
    }

    /**
     * Sets the current working directory.
     *
     * @return $this
     */
    public function setWorkingDirectory(string $cwd): static
    {
        $this->cwd = $cwd;

        return $this;
    }

    /**
     * Gets the environment variables.
     */
    public function getEnv(): array
    {
        return $this->env;
    }

    /**
     * Sets the environment variables.
     *
     * @param array<string|\Stringable> $env The new environment variables
     *
     * @return $this
     */
    public function setEnv(array $env): static
    {
        $this->env = $env;

        return $this;
    }

    /**
     * Gets the Process input.
     *
     * @return resource|string|\Iterator|null
     */
    public function getInput()
    {
        return $this->input;
    }

    /**
     * Sets the input.
     *
     * This content will be passed to the underlying process standard input.
     *
     * @param string|resource|\Traversable|self|null $input The content
     *
     * @return $this
     *
     * @throws LogicException In case the process is running
     */
    public function setInput(mixed $input): static
    {
        if ($this->isRunning()) {
            throw new LogicException('Input cannot be set while the process is running.');
        }

        $this->input = ProcessUtils::validateInput(__METHOD__, $input);

        return $this;
    }

    /**
     * Performs a check between the timeout definition and the time the process started.
     *
     * In case you run a background process (with the start method), you should
     * trigger this method regularly to ensure the process timeout
     *
     * @return void
     *
     * @throws ProcessTimedOutException In case the timeout was reached
     */
    public function checkTimeout()
    {
        if (self::STATUS_STARTED !== $this->status) {
            return;
        }

        if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) {
            $this->stop(0);

            throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL);
        }

        if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) {
            $this->stop(0);

            throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE);
        }
    }

    /**
     * @throws LogicException in case process is not started
     */
    public function getStartTime(): float
    {
        if (!$this->isStarted()) {
            throw new LogicException('Start time is only available after process start.');
        }

        return $this->starttime;
    }

    /**
     * Defines options to pass to the underlying proc_open().
     *
     * @see https://php.net/proc_open for the options supported by PHP.
     *
     * Enabling the "create_new_console" option allows a subprocess to continue
     * to run after the main process exited, on both Windows and *nix
     *
     * @return void
     */
    public function setOptions(array $options)
    {
        if ($this->isRunning()) {
            throw new RuntimeException('Setting options while the process is running is not possible.');
        }

        $defaultOptions = $this->options;
        $existingOptions = ['blocking_pipes', 'create_process_group', 'create_new_console'];

        foreach ($options as $key => $value) {
            if (!\in_array($key, $existingOptions)) {
                $this->options = $defaultOptions;
                throw new LogicException(\sprintf('Invalid option "%s" passed to "%s()". Supported options are "%s".', $key, __METHOD__, implode('", "', $existingOptions)));
            }
            $this->options[$key] = $value;
        }
    }

    /**
     * Returns whether TTY is supported on the current operating system.
     */
    public static function isTtySupported(): bool
    {
        static $isTtySupported;

        return $isTtySupported ??= ('/' === \DIRECTORY_SEPARATOR && stream_isatty(\STDOUT) && @is_writable('/dev/tty'));
    }

    /**
     * Returns whether PTY is supported on the current operating system.
     */
    public static function isPtySupported(): bool
    {
        static $result;

        if (null !== $result) {
            return $result;
        }

        if ('\\' === \DIRECTORY_SEPARATOR) {
            return $result = false;
        }

        return $result = (bool) @proc_open('echo 1 >/dev/null', [['pty'], ['pty'], ['pty']], $pipes);
    }

    /**
     * Creates the descriptors needed by the proc_open.
     */
    private function getDescriptors(bool $hasCallback): array
    {
        if ($this->input instanceof \Iterator) {
            $this->input->rewind();
        }
        if ('\\' === \DIRECTORY_SEPARATOR) {
            $this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $hasCallback);
        } else {
            $this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $hasCallback);
        }

        return $this->processPipes->getDescriptors();
    }

    /**
     * Builds up the callback used by wait().
     *
     * The callbacks adds all occurred output to the specific buffer and calls
     * the user callback (if present) with the received output.
     *
     * @param callable|null $callback The user defined PHP callback
     */
    protected function buildCallback(?callable $callback = null): \Closure
    {
        if ($this->outputDisabled) {
            return fn ($type, $data): bool => null !== $callback && $callback($type, $data);
        }

        $out = self::OUT;

        return function ($type, $data) use ($callback, $out): bool {
            if ($out == $type) {
                $this->addOutput($data);
            } else {
                $this->addErrorOutput($data);
            }

            return null !== $callback && $callback($type, $data);
        };
    }

    /**
     * Updates the status of the process, reads pipes.
     *
     * @param bool $blocking Whether to use a blocking read call
     *
     * @return void
     */
    protected function updateStatus(bool $blocking)
    {
        if (self::STATUS_STARTED !== $this->status) {
            return;
        }

        if ($this->processInformation['running'] ?? true) {
            $this->processInformation = proc_get_status($this->process);
        }
        $running = $this->processInformation['running'];

        $this->readPipes($running && $blocking, '\\' !== \DIRECTORY_SEPARATOR || !$running);

        if ($this->fallbackStatus && $this->isSigchildEnabled()) {
            $this->processInformation = $this->fallbackStatus + $this->processInformation;
        }

        if (!$running) {
            $this->close();
        }
    }

    /**
     * Returns whether PHP has been compiled with the '--enable-sigchild' option or not.
     */
    protected function isSigchildEnabled(): bool
    {
        if (null !== self::$sigchild) {
            return self::$sigchild;
        }

        if (!\function_exists('phpinfo')) {
            return self::$sigchild = false;
        }

        ob_start();
        phpinfo(\INFO_GENERAL);

        return self::$sigchild = str_contains(ob_get_clean(), '--enable-sigchild');
    }

    /**
     * Reads pipes for the freshest output.
     *
     * @param string $caller   The name of the method that needs fresh outputs
     * @param bool   $blocking Whether to use blocking calls or not
     *
     * @throws LogicException in case output has been disabled or process is not started
     */
    private function readPipesForOutput(string $caller, bool $blocking = false): void
    {
        if ($this->outputDisabled) {
            throw new LogicException('Output has been disabled.');
        }

        $this->requireProcessIsStarted($caller);

        $this->updateStatus($blocking);
    }

    /**
     * Validates and returns the filtered timeout.
     *
     * @throws InvalidArgumentException if the given timeout is a negative number
     */
    private function validateTimeout(?float $timeout): ?float
    {
        $timeout = (float) $timeout;

        if (0.0 === $timeout) {
            $timeout = null;
        } elseif ($timeout < 0) {
            throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
        }

        return $timeout;
    }

    /**
     * Reads pipes, executes callback.
     *
     * @param bool $blocking Whether to use blocking calls or not
     * @param bool $close    Whether to close file handles or not
     */
    private function readPipes(bool $blocking, bool $close): void
    {
        $result = $this->processPipes->readAndWrite($blocking, $close);

        $callback = $this->callback;
        foreach ($result as $type => $data) {
            if (3 !== $type) {
                $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data);
            } elseif (!isset($this->fallbackStatus['signaled'])) {
                $this->fallbackStatus['exitcode'] = (int) $data;
            }
        }
    }

    /**
     * Closes process resource, closes file handles, sets the exitcode.
     *
     * @return int The exitcode
     */
    private function close(): int
    {
        $this->processPipes->close();
        if ($this->process) {
            proc_close($this->process);
            $this->process = null;
        }
        $this->exitcode = $this->processInformation['exitcode'];
        $this->status = self::STATUS_TERMINATED;

        if (-1 === $this->exitcode) {
            if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) {
                // if process has been signaled, no exitcode but a valid termsig, apply Unix convention
                $this->exitcode = 128 + $this->processInformation['termsig'];
            } elseif ($this->isSigchildEnabled()) {
                $this->processInformation['signaled'] = true;
                $this->processInformation['termsig'] = -1;
            }
        }

        // Free memory from self-reference callback created by buildCallback
        // Doing so in other contexts like __destruct or by garbage collector is ineffective
        // Now pipes are closed, so the callback is no longer necessary
        $this->callback = null;

        return $this->exitcode;
    }

    /**
     * Resets data related to the latest run of the process.
     */
    private function resetProcessData(): void
    {
        $this->starttime = null;
        $this->callback = null;
        $this->exitcode = null;
        $this->fallbackStatus = [];
        $this->processInformation = [];
        $this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+');
        $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+');
        $this->process = null;
        $this->latestSignal = null;
        $this->status = self::STATUS_READY;
        $this->incrementalOutputOffset = 0;
        $this->incrementalErrorOutputOffset = 0;
    }

    /**
     * Sends a POSIX signal to the process.
     *
     * @param int  $signal         A valid POSIX signal (see https://php.net/pcntl.constants)
     * @param bool $throwException Whether to throw exception in case signal failed
     *
     * @throws LogicException   In case the process is not running
     * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
     * @throws RuntimeException In case of failure
     */
    private function doSignal(int $signal, bool $throwException): bool
    {
        if (null === $pid = $this->getPid()) {
            if ($throwException) {
                throw new LogicException('Cannot send signal on a non running process.');
            }

            return false;
        }

        if ('\\' === \DIRECTORY_SEPARATOR) {
            exec(\sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode);
            if ($exitCode && $this->isRunning()) {
                if ($throwException) {
                    throw new RuntimeException(\sprintf('Unable to kill the process (%s).', implode(' ', $output)));
                }

                return false;
            }
        } else {
            if (!$this->isSigchildEnabled()) {
                $ok = @proc_terminate($this->process, $signal);
            } elseif (\function_exists('posix_kill')) {
                $ok = @posix_kill($pid, $signal);
            } elseif ($ok = proc_open(\sprintf('kill -%d %d', $signal, $pid), [2 => ['pipe', 'w']], $pipes)) {
                $ok = false === fgets($pipes[2]);
            }
            if (!$ok) {
                if ($throwException) {
                    throw new RuntimeException(\sprintf('Error while sending signal "%s".', $signal));
                }

                return false;
            }
        }

        $this->latestSignal = $signal;
        $this->fallbackStatus['signaled'] = true;
        $this->fallbackStatus['exitcode'] = -1;
        $this->fallbackStatus['termsig'] = $this->latestSignal;

        return true;
    }

    private function prepareWindowsCommandLine(string $cmd, array &$env): string
    {
        $uid = uniqid('', true);
        $cmd = preg_replace_callback(
            '/"(?:(
                [^"%!^]*+
                (?:
                    (?: !LF! | "(?:\^[%!^])?+" )
                    [^"%!^]*+
                )++
            ) | [^"]*+ )"/x',
            function ($m) use (&$env, $uid) {
                static $varCount = 0;
                static $varCache = [];
                if (!isset($m[1])) {
                    return $m[0];
                }
                if (isset($varCache[$m[0]])) {
                    return $varCache[$m[0]];
                }
                if (str_contains($value = $m[1], "\0")) {
                    $value = str_replace("\0", '?', $value);
                }
                if (false === strpbrk($value, "\"%!\n")) {
                    return '"'.$value.'"';
                }

                $value = str_replace(['!LF!', '"^!"', '"^%"', '"^^"', '""'], ["\n", '!', '%', '^', '"'], $value);
                $value = '"'.preg_replace('/(\\\\*)"/', '$1$1\\"', $value).'"';
                $var = $uid.++$varCount;

                $env[$var] = $value;

                return $varCache[$m[0]] = '!'.$var.'!';
            },
            $cmd
        );

        static $comSpec;

        if (!$comSpec && $comSpec = (new ExecutableFinder())->find('cmd.exe')) {
            // Escape according to CommandLineToArgvW rules
            $comSpec = '"'.preg_replace('{(\\\\*+)"}', '$1$1\"', $comSpec).'"';
        }

        $cmd = ($comSpec ?? 'cmd').' /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')';
        foreach ($this->processPipes->getFiles() as $offset => $filename) {
            $cmd .= ' '.$offset.'>"'.$filename.'"';
        }

        return $cmd;
    }

    /**
     * Ensures the process is running or terminated, throws a LogicException if the process has a not started.
     *
     * @throws LogicException if the process has not run
     */
    private function requireProcessIsStarted(string $functionName): void
    {
        if (!$this->isStarted()) {
            throw new LogicException(\sprintf('Process must be started before calling "%s()".', $functionName));
        }
    }

    /**
     * Ensures the process is terminated, throws a LogicException if the process has a status different than "terminated".
     *
     * @throws LogicException if the process is not yet terminated
     */
    private function requireProcessIsTerminated(string $functionName): void
    {
        if (!$this->isTerminated()) {
            throw new LogicException(\sprintf('Process must be terminated before calling "%s()".', $functionName));
        }
    }

    /**
     * Escapes a string to be used as a shell argument.
     */
    private function escapeArgument(?string $argument): string
    {
        if ('' === $argument || null === $argument) {
            return '""';
        }
        if ('\\' !== \DIRECTORY_SEPARATOR) {
            return "'".str_replace("'", "'\\''", $argument)."'";
        }
        if (str_contains($argument, "\0")) {
            $argument = str_replace("\0", '?', $argument);
        }
        if (!preg_match('/[()%!^"<>&|\s[\]=;*?\'$]/', $argument)) {
            return $argument;
        }
        $argument = preg_replace('/(\\\\+)$/', '$1$1', $argument);

        return '"'.str_replace(['"', '^', '%', '!', "\n"], ['""', '"^^"', '"^%"', '"^!"', '!LF!'], $argument).'"';
    }

    private function replacePlaceholders(string $commandline, array $env): string
    {
        return preg_replace_callback('/"\$\{:([_a-zA-Z]++[_a-zA-Z0-9]*+)\}"/', function ($matches) use ($commandline, $env) {
            if (!isset($env[$matches[1]]) || false === $env[$matches[1]]) {
                throw new InvalidArgumentException(\sprintf('Command line is missing a value for parameter "%s": ', $matches[1]).$commandline);
            }

            return $this->escapeArgument($env[$matches[1]]);
        }, $commandline);
    }

    private function getDefaultEnv(): array
    {
        $env = getenv();
        $env = ('\\' === \DIRECTORY_SEPARATOR ? array_intersect_ukey($env, $_SERVER, 'strcasecmp') : array_intersect_key($env, $_SERVER)) ?: $env;

        return $_ENV + ('\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($env, $_ENV, 'strcasecmp') : $env);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Exception;

/**
 * Marker Interface for the Process Component.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
interface ExceptionInterface extends \Throwable
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Exception;

/**
 * RuntimeException for the Process Component.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Exception;

/**
 * LogicException for the Process Component.
 *
 * @author Romain Neutron <imprec@gmail.com>
 */
class LogicException extends \LogicException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Exception;

use Symfony\Component\Process\Process;

/**
 * Exception that is thrown when a process times out.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class ProcessTimedOutException extends RuntimeException
{
    public const TYPE_GENERAL = 1;
    public const TYPE_IDLE = 2;

    private Process $process;
    private int $timeoutType;

    public function __construct(Process $process, int $timeoutType)
    {
        $this->process = $process;
        $this->timeoutType = $timeoutType;

        parent::__construct(\sprintf(
            'The process "%s" exceeded the timeout of %s seconds.',
            $process->getCommandLine(),
            $this->getExceededTimeout()
        ));
    }

    /**
     * @return Process
     */
    public function getProcess()
    {
        return $this->process;
    }

    /**
     * @return bool
     */
    public function isGeneralTimeout()
    {
        return self::TYPE_GENERAL === $this->timeoutType;
    }

    /**
     * @return bool
     */
    public function isIdleTimeout()
    {
        return self::TYPE_IDLE === $this->timeoutType;
    }

    public function getExceededTimeout(): ?float
    {
        return match ($this->timeoutType) {
            self::TYPE_GENERAL => $this->process->getTimeout(),
            self::TYPE_IDLE => $this->process->getIdleTimeout(),
            default => throw new \LogicException(\sprintf('Unknown timeout type "%d".', $this->timeoutType)),
        };
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Exception;

use Symfony\Component\Process\Process;

/**
 * Exception that is thrown when a process has been signaled.
 *
 * @author Sullivan Senechal <soullivaneuh@gmail.com>
 */
final class ProcessSignaledException extends RuntimeException
{
    private Process $process;

    public function __construct(Process $process)
    {
        $this->process = $process;

        parent::__construct(\sprintf('The process has been signaled with signal "%s".', $process->getTermSignal()));
    }

    public function getProcess(): Process
    {
        return $this->process;
    }

    public function getSignal(): int
    {
        return $this->getProcess()->getTermSignal();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Exception;

/**
 * InvalidArgumentException for the Process Component.
 *
 * @author Romain Neutron <imprec@gmail.com>
 */
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Exception;

use Symfony\Component\Process\Process;

/**
 * Exception for failed processes.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class ProcessFailedException extends RuntimeException
{
    private Process $process;

    public function __construct(Process $process)
    {
        if ($process->isSuccessful()) {
            throw new InvalidArgumentException('Expected a failed process, but the given process was successful.');
        }

        $error = \sprintf('The command "%s" failed.'."\n\nExit Code: %s(%s)\n\nWorking directory: %s",
            $process->getCommandLine(),
            $process->getExitCode(),
            $process->getExitCodeText(),
            $process->getWorkingDirectory()
        );

        if (!$process->isOutputDisabled()) {
            $error .= \sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s",
                $process->getOutput(),
                $process->getErrorOutput()
            );
        }

        parent::__construct($error);

        $this->process = $process;
    }

    /**
     * @return Process
     */
    public function getProcess()
    {
        return $this->process;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Exception;

use Symfony\Component\Process\Messenger\RunProcessContext;

/**
 * @author Kevin Bond <kevinbond@gmail.com>
 */
final class RunProcessFailedException extends RuntimeException
{
    public function __construct(ProcessFailedException $exception, public readonly RunProcessContext $context)
    {
        parent::__construct($exception->getMessage(), $exception->getCode());
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Messenger;

use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Exception\RunProcessFailedException;
use Symfony\Component\Process\Process;

/**
 * @author Kevin Bond <kevinbond@gmail.com>
 */
final class RunProcessMessageHandler
{
    public function __invoke(RunProcessMessage $message): RunProcessContext
    {
        $process = new Process($message->command, $message->cwd, $message->env, $message->input, $message->timeout);

        try {
            return new RunProcessContext($message, $process->mustRun());
        } catch (ProcessFailedException $e) {
            throw new RunProcessFailedException($e, new RunProcessContext($message, $e->getProcess()));
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Messenger;

/**
 * @author Kevin Bond <kevinbond@gmail.com>
 */
class RunProcessMessage implements \Stringable
{
    public function __construct(
        public readonly array $command,
        public readonly ?string $cwd = null,
        public readonly ?array $env = null,
        public readonly mixed $input = null,
        public readonly ?float $timeout = 60.0,
    ) {
    }

    public function __toString(): string
    {
        return implode(' ', $this->command);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Messenger;

use Symfony\Component\Process\Process;

/**
 * @author Kevin Bond <kevinbond@gmail.com>
 */
final class RunProcessContext
{
    public readonly ?int $exitCode;
    public readonly ?string $output;
    public readonly ?string $errorOutput;

    public function __construct(
        public readonly RunProcessMessage $message,
        Process $process,
    ) {
        $this->exitCode = $process->getExitCode();
        $this->output = $process->isOutputDisabled() ? null : $process->getOutput();
        $this->errorOutput = $process->isOutputDisabled() ? null : $process->getErrorOutput();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Pipes;

use Symfony\Component\Process\Exception\RuntimeException;
use Symfony\Component\Process\Process;

/**
 * WindowsPipes implementation uses temporary files as handles.
 *
 * @see https://bugs.php.net/51800
 * @see https://bugs.php.net/65650
 *
 * @author Romain Neutron <imprec@gmail.com>
 *
 * @internal
 */
class WindowsPipes extends AbstractPipes
{
    private array $files = [];
    private array $fileHandles = [];
    private array $lockHandles = [];
    private array $readBytes = [
        Process::STDOUT => 0,
        Process::STDERR => 0,
    ];
    private bool $haveReadSupport;

    public function __construct(mixed $input, bool $haveReadSupport)
    {
        $this->haveReadSupport = $haveReadSupport;

        if ($this->haveReadSupport) {
            // Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.
            // Workaround for this problem is to use temporary files instead of pipes on Windows platform.
            //
            // @see https://bugs.php.net/51800
            $pipes = [
                Process::STDOUT => Process::OUT,
                Process::STDERR => Process::ERR,
            ];
            $tmpDir = sys_get_temp_dir();
            $lastError = 'unknown reason';
            set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; });
            for ($i = 0;; ++$i) {
                foreach ($pipes as $pipe => $name) {
                    $file = \sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name);

                    if (!$h = fopen($file.'.lock', 'w')) {
                        if (file_exists($file.'.lock')) {
                            continue 2;
                        }
                        restore_error_handler();
                        throw new RuntimeException('A temporary file could not be opened to write the process output: '.$lastError);
                    }
                    if (!flock($h, \LOCK_EX | \LOCK_NB)) {
                        continue 2;
                    }
                    if (isset($this->lockHandles[$pipe])) {
                        flock($this->lockHandles[$pipe], \LOCK_UN);
                        fclose($this->lockHandles[$pipe]);
                    }
                    $this->lockHandles[$pipe] = $h;

                    if (!($h = fopen($file, 'w')) || !fclose($h) || !$h = fopen($file, 'r')) {
                        flock($this->lockHandles[$pipe], \LOCK_UN);
                        fclose($this->lockHandles[$pipe]);
                        unset($this->lockHandles[$pipe]);
                        continue 2;
                    }
                    $this->fileHandles[$pipe] = $h;
                    $this->files[$pipe] = $file;
                }
                break;
            }
            restore_error_handler();
        }

        parent::__construct($input);
    }

    public function __serialize(): array
    {
        throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
    }

    public function __unserialize(array $data): void
    {
        throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
    }

    public function __destruct()
    {
        $this->close();
    }

    public function getDescriptors(): array
    {
        if (!$this->haveReadSupport) {
            $nullstream = fopen('NUL', 'c');

            return [
                ['pipe', 'r'],
                $nullstream,
                $nullstream,
            ];
        }

        // We're not using pipe on Windows platform as it hangs (https://bugs.php.net/51800)
        // We're not using file handles as it can produce corrupted output https://bugs.php.net/65650
        // So we redirect output within the commandline and pass the nul device to the process
        return [
            ['pipe', 'r'],
            ['file', 'NUL', 'w'],
            ['file', 'NUL', 'w'],
        ];
    }

    public function getFiles(): array
    {
        return $this->files;
    }

    public function readAndWrite(bool $blocking, bool $close = false): array
    {
        $this->unblock();
        $w = $this->write();
        $read = $r = $e = [];

        if ($blocking) {
            if ($w) {
                @stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6);
            } elseif ($this->fileHandles) {
                usleep((int) (Process::TIMEOUT_PRECISION * 1E6));
            }
        }
        foreach ($this->fileHandles as $type => $fileHandle) {
            $data = stream_get_contents($fileHandle, -1, $this->readBytes[$type]);

            if (isset($data[0])) {
                $this->readBytes[$type] += \strlen($data);
                $read[$type] = $data;
            }
            if ($close) {
                ftruncate($fileHandle, 0);
                fclose($fileHandle);
                flock($this->lockHandles[$type], \LOCK_UN);
                fclose($this->lockHandles[$type]);
                unset($this->fileHandles[$type], $this->lockHandles[$type]);
            }
        }

        return $read;
    }

    public function haveReadSupport(): bool
    {
        return $this->haveReadSupport;
    }

    public function areOpen(): bool
    {
        return $this->pipes && $this->fileHandles;
    }

    public function close(): void
    {
        parent::close();
        foreach ($this->fileHandles as $type => $handle) {
            ftruncate($handle, 0);
            fclose($handle);
            flock($this->lockHandles[$type], \LOCK_UN);
            fclose($this->lockHandles[$type]);
        }
        $this->fileHandles = $this->lockHandles = [];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Pipes;

use Symfony\Component\Process\Exception\InvalidArgumentException;

/**
 * @author Romain Neutron <imprec@gmail.com>
 *
 * @internal
 */
abstract class AbstractPipes implements PipesInterface
{
    public array $pipes = [];

    private string $inputBuffer = '';
    /** @var resource|string|\Iterator */
    private $input;
    private bool $blocked = true;
    private ?string $lastError = null;

    /**
     * @param resource|string|\Iterator $input
     */
    public function __construct($input)
    {
        if (\is_resource($input) || $input instanceof \Iterator) {
            $this->input = $input;
        } else {
            $this->inputBuffer = (string) $input;
        }
    }

    public function close(): void
    {
        foreach ($this->pipes as $pipe) {
            if (\is_resource($pipe)) {
                fclose($pipe);
            }
        }
        $this->pipes = [];
    }

    /**
     * Returns true if a system call has been interrupted.
     *
     * stream_select() returns false when the `select` system call is interrupted by an incoming signal.
     */
    protected function hasSystemCallBeenInterrupted(): bool
    {
        $lastError = $this->lastError;
        $this->lastError = null;

        if (null === $lastError) {
            return false;
        }

        if (false !== stripos($lastError, 'interrupted system call')) {
            return true;
        }

        // on applications with a different locale than english, the message above is not found because
        // it's translated. So we also check for the SOCKET_EINTR constant which is defined under
        // Windows and UNIX-like platforms (if available on the platform).
        return \defined('SOCKET_EINTR') && str_starts_with($lastError, 'stream_select(): Unable to select ['.\SOCKET_EINTR.']');
    }

    /**
     * Unblocks streams.
     */
    protected function unblock(): void
    {
        if (!$this->blocked) {
            return;
        }

        foreach ($this->pipes as $pipe) {
            stream_set_blocking($pipe, false);
        }
        if (\is_resource($this->input)) {
            stream_set_blocking($this->input, false);
        }

        $this->blocked = false;
    }

    /**
     * Writes input to stdin.
     *
     * @throws InvalidArgumentException When an input iterator yields a non supported value
     */
    protected function write(): ?array
    {
        if (!isset($this->pipes[0])) {
            return null;
        }
        $input = $this->input;

        if ($input instanceof \Iterator) {
            if (!$input->valid()) {
                $input = null;
            } elseif (\is_resource($input = $input->current())) {
                stream_set_blocking($input, false);
            } elseif (!isset($this->inputBuffer[0])) {
                if (!\is_string($input)) {
                    if (!\is_scalar($input)) {
                        throw new InvalidArgumentException(\sprintf('"%s" yielded a value of type "%s", but only scalars and stream resources are supported.', get_debug_type($this->input), get_debug_type($input)));
                    }
                    $input = (string) $input;
                }
                $this->inputBuffer = $input;
                $this->input->next();
                $input = null;
            } else {
                $input = null;
            }
        }

        $r = $e = [];
        $w = [$this->pipes[0]];

        // let's have a look if something changed in streams
        if (false === @stream_select($r, $w, $e, 0, 0)) {
            return null;
        }

        foreach ($w as $stdin) {
            if (isset($this->inputBuffer[0])) {
                if (false === $written = @fwrite($stdin, $this->inputBuffer)) {
                    return $this->closeBrokenInputPipe();
                }
                $this->inputBuffer = substr($this->inputBuffer, $written);
                if (isset($this->inputBuffer[0]) && isset($this->pipes[0])) {
                    return [$this->pipes[0]];
                }
            }

            if ($input) {
                while (true) {
                    $data = fread($input, self::CHUNK_SIZE);
                    if (!isset($data[0])) {
                        break;
                    }
                    if (false === $written = @fwrite($stdin, $data)) {
                        return $this->closeBrokenInputPipe();
                    }
                    $data = substr($data, $written);
                    if (isset($data[0])) {
                        $this->inputBuffer = $data;

                        return isset($this->pipes[0]) ? [$this->pipes[0]] : null;
                    }
                }
                if (feof($input)) {
                    if ($this->input instanceof \Iterator) {
                        $this->input->next();
                    } else {
                        $this->input = null;
                    }
                }
            }
        }

        // no input to read on resource, buffer is empty
        if (!isset($this->inputBuffer[0]) && !($this->input instanceof \Iterator ? $this->input->valid() : $this->input)) {
            $this->input = null;
            fclose($this->pipes[0]);
            unset($this->pipes[0]);
        } elseif (!$w) {
            return [$this->pipes[0]];
        }

        return null;
    }

    private function closeBrokenInputPipe(): void
    {
        $this->lastError = error_get_last()['message'] ?? null;
        if (\is_resource($this->pipes[0] ?? null)) {
            fclose($this->pipes[0]);
        }
        unset($this->pipes[0]);

        $this->input = null;
        $this->inputBuffer = '';
    }

    /**
     * @internal
     */
    public function handleError(int $type, string $msg): void
    {
        $this->lastError = $msg;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Pipes;

use Symfony\Component\Process\Process;

/**
 * UnixPipes implementation uses unix pipes as handles.
 *
 * @author Romain Neutron <imprec@gmail.com>
 *
 * @internal
 */
class UnixPipes extends AbstractPipes
{
    private ?bool $ttyMode;
    private bool $ptyMode;
    private bool $haveReadSupport;

    public function __construct(?bool $ttyMode, bool $ptyMode, mixed $input, bool $haveReadSupport)
    {
        $this->ttyMode = $ttyMode;
        $this->ptyMode = $ptyMode;
        $this->haveReadSupport = $haveReadSupport;

        parent::__construct($input);
    }

    public function __serialize(): array
    {
        throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
    }

    public function __unserialize(array $data): void
    {
        throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
    }

    public function __destruct()
    {
        $this->close();
    }

    public function getDescriptors(): array
    {
        if (!$this->haveReadSupport) {
            $nullstream = fopen('/dev/null', 'c');

            return [
                ['pipe', 'r'],
                $nullstream,
                $nullstream,
            ];
        }

        if ($this->ttyMode) {
            return [
                ['file', '/dev/tty', 'r'],
                ['file', '/dev/tty', 'w'],
                ['file', '/dev/tty', 'w'],
            ];
        }

        if ($this->ptyMode && Process::isPtySupported()) {
            return [
                ['pty'],
                ['pty'],
                ['pipe', 'w'], // stderr needs to be in a pipe to correctly split error and output, since PHP will use the same stream for both
            ];
        }

        return [
            ['pipe', 'r'],
            ['pipe', 'w'], // stdout
            ['pipe', 'w'], // stderr
        ];
    }

    public function getFiles(): array
    {
        return [];
    }

    public function readAndWrite(bool $blocking, bool $close = false): array
    {
        $this->unblock();
        $w = $this->write();

        $read = $e = [];
        $r = $this->pipes;
        unset($r[0]);

        // let's have a look if something changed in streams
        set_error_handler($this->handleError(...));
        if (($r || $w) && false === stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
            restore_error_handler();
            // if a system call has been interrupted, forget about it, let's try again
            // otherwise, an error occurred, let's reset pipes
            if (!$this->hasSystemCallBeenInterrupted()) {
                $this->pipes = [];
            }

            return $read;
        }
        restore_error_handler();

        foreach ($r as $pipe) {
            // prior PHP 5.4 the array passed to stream_select is modified and
            // lose key association, we have to find back the key
            $read[$type = array_search($pipe, $this->pipes, true)] = '';

            do {
                $data = @fread($pipe, self::CHUNK_SIZE);
                $read[$type] .= $data;
            } while (isset($data[0]) && ($close || isset($data[self::CHUNK_SIZE - 1])));

            if (!isset($read[$type][0])) {
                unset($read[$type]);
            }

            if ($close && feof($pipe)) {
                fclose($pipe);
                unset($this->pipes[$type]);
            }
        }

        return $read;
    }

    public function haveReadSupport(): bool
    {
        return $this->haveReadSupport;
    }

    public function areOpen(): bool
    {
        return (bool) $this->pipes;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Pipes;

/**
 * PipesInterface manages descriptors and pipes for the use of proc_open.
 *
 * @author Romain Neutron <imprec@gmail.com>
 *
 * @internal
 */
interface PipesInterface
{
    public const CHUNK_SIZE = 16384;

    /**
     * Returns an array of descriptors for the use of proc_open.
     */
    public function getDescriptors(): array;

    /**
     * Returns an array of filenames indexed by their related stream in case these pipes use temporary files.
     *
     * @return string[]
     */
    public function getFiles(): array;

    /**
     * Reads data in file handles and pipes.
     *
     * @param bool $blocking Whether to use blocking calls or not
     * @param bool $close    Whether to close pipes if they've reached EOF
     *
     * @return string[] An array of read data indexed by their fd
     */
    public function readAndWrite(bool $blocking, bool $close = false): array;

    /**
     * Returns if the current state has open file handles or pipes.
     */
    public function areOpen(): bool;

    /**
     * Returns if pipes are able to read output.
     */
    public function haveReadSupport(): bool;

    /**
     * Closes file handles and pipes.
     */
    public function close(): void;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process;

use Symfony\Component\Process\Exception\LogicException;
use Symfony\Component\Process\Exception\RuntimeException;

/**
 * PhpSubprocess runs a PHP command as a subprocess while keeping the original php.ini settings.
 *
 * For this, it generates a temporary php.ini file taking over all the current settings and disables
 * loading additional .ini files. Basically, your command gets prefixed using "php -n -c /tmp/temp.ini".
 *
 * Given your php.ini contains "memory_limit=-1" and you have a "MemoryTest.php" with the following content:
 *
 *     <?php var_dump(ini_get('memory_limit'));
 *
 * These are the differences between the regular Process and PhpSubprocess classes:
 *
 *     $p = new Process(['php', '-d', 'memory_limit=256M', 'MemoryTest.php']);
 *     $p->run();
 *     print $p->getOutput()."\n";
 *
 * This will output "string(2) "-1", because the process is started with the default php.ini settings.
 *
 *     $p = new PhpSubprocess(['MemoryTest.php'], null, null, 60, ['php', '-d', 'memory_limit=256M']);
 *     $p->run();
 *     print $p->getOutput()."\n";
 *
 * This will output "string(4) "256M"", because the process is started with the temporarily created php.ini settings.
 *
 * @author Yanick Witschi <yanick.witschi@terminal42.ch>
 * @author Partially copied and heavily inspired from composer/xdebug-handler by John Stevenson <john-stevenson@blueyonder.co.uk>
 */
class PhpSubprocess extends Process
{
    /**
     * @param array       $command The command to run and its arguments listed as separate entries. They will automatically
     *                             get prefixed with the PHP binary
     * @param string|null $cwd     The working directory or null to use the working dir of the current PHP process
     * @param array|null  $env     The environment variables or null to use the same environment as the current PHP process
     * @param int         $timeout The timeout in seconds
     * @param array|null  $php     Path to the PHP binary to use with any additional arguments
     */
    public function __construct(array $command, ?string $cwd = null, ?array $env = null, int $timeout = 60, ?array $php = null)
    {
        if (null === $php) {
            $executableFinder = new PhpExecutableFinder();
            $php = $executableFinder->find(false);
            $php = false === $php ? null : array_merge([$php], $executableFinder->findArguments());
        }

        if (null === $php) {
            throw new RuntimeException('Unable to find PHP binary.');
        }

        $tmpIni = $this->writeTmpIni($this->getAllIniFiles(), sys_get_temp_dir());

        $php = array_merge($php, ['-n', '-c', $tmpIni]);
        register_shutdown_function('unlink', $tmpIni);

        $command = array_merge($php, $command);

        parent::__construct($command, $cwd, $env, null, $timeout);
    }

    public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, mixed $input = null, ?float $timeout = 60): static
    {
        throw new LogicException(\sprintf('The "%s()" method cannot be called when using "%s".', __METHOD__, self::class));
    }

    public function start(?callable $callback = null, array $env = []): void
    {
        if (null === $this->getCommandLine()) {
            throw new RuntimeException('Unable to find the PHP executable.');
        }

        parent::start($callback, $env);
    }

    private function writeTmpIni(array $iniFiles, string $tmpDir): string
    {
        if (false === $tmpfile = @tempnam($tmpDir, '')) {
            throw new RuntimeException('Unable to create temporary ini file.');
        }

        // $iniFiles has at least one item and it may be empty
        if ('' === $iniFiles[0]) {
            array_shift($iniFiles);
        }

        $content = '';

        foreach ($iniFiles as $file) {
            // Check for inaccessible ini files
            if (($data = @file_get_contents($file)) === false) {
                throw new RuntimeException('Unable to read ini: '.$file);
            }
            // Check and remove directives after HOST and PATH sections
            if (preg_match('/^\s*\[(?:PATH|HOST)\s*=/mi', $data, $matches, \PREG_OFFSET_CAPTURE)) {
                $data = substr($data, 0, $matches[0][1]);
            }

            $content .= $data."\n";
        }

        // Merge loaded settings into our ini content, if it is valid
        $config = parse_ini_string($content);
        $loaded = ini_get_all(null, false);

        if (false === $config || false === $loaded) {
            throw new RuntimeException('Unable to parse ini data.');
        }

        $content .= $this->mergeLoadedConfig($loaded, $config);

        // Work-around for https://bugs.php.net/bug.php?id=75932
        $content .= "opcache.enable_cli=0\n";

        if (false === @file_put_contents($tmpfile, $content)) {
            throw new RuntimeException('Unable to write temporary ini file.');
        }

        return $tmpfile;
    }

    private function mergeLoadedConfig(array $loadedConfig, array $iniConfig): string
    {
        $content = '';

        foreach ($loadedConfig as $name => $value) {
            if (!\is_string($value)) {
                continue;
            }

            if (!isset($iniConfig[$name]) || $iniConfig[$name] !== $value) {
                // Double-quote escape each value
                $content .= $name.'="'.addcslashes($value, '\\"')."\"\n";
            }
        }

        return $content;
    }

    private function getAllIniFiles(): array
    {
        $paths = [(string) php_ini_loaded_file()];

        if (false !== $scanned = php_ini_scanned_files()) {
            $paths = array_merge($paths, array_map('trim', explode(',', $scanned)));
        }

        return $paths;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process;

use Symfony\Component\Process\Exception\LogicException;
use Symfony\Component\Process\Exception\RuntimeException;

/**
 * PhpProcess runs a PHP script in an independent process.
 *
 *     $p = new PhpProcess('<?php echo "foo"; ?>');
 *     $p->run();
 *     print $p->getOutput()."\n";
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class PhpProcess extends Process
{
    /**
     * @param string      $script  The PHP script to run (as a string)
     * @param string|null $cwd     The working directory or null to use the working dir of the current PHP process
     * @param array|null  $env     The environment variables or null to use the same environment as the current PHP process
     * @param int         $timeout The timeout in seconds
     * @param array|null  $php     Path to the PHP binary to use with any additional arguments
     */
    public function __construct(string $script, ?string $cwd = null, ?array $env = null, int $timeout = 60, ?array $php = null)
    {
        if (null === $php) {
            $executableFinder = new PhpExecutableFinder();
            $php = $executableFinder->find(false);
            $php = false === $php ? null : array_merge([$php], $executableFinder->findArguments());
        }
        if ('phpdbg' === \PHP_SAPI) {
            $file = tempnam(sys_get_temp_dir(), 'dbg');
            file_put_contents($file, $script);
            register_shutdown_function('unlink', $file);
            $php[] = $file;
            $script = null;
        }

        parent::__construct($php, $cwd, $env, $script, $timeout);
    }

    public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, mixed $input = null, ?float $timeout = 60): static
    {
        throw new LogicException(\sprintf('The "%s()" method cannot be called when using "%s".', __METHOD__, self::class));
    }

    /**
     * @return void
     */
    public function start(?callable $callback = null, array $env = [])
    {
        if (null === $this->getCommandLine()) {
            throw new RuntimeException('Unable to find the PHP executable.');
        }

        parent::start($callback, $env);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process;

/**
 * An executable finder specifically designed for the PHP executable.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class PhpExecutableFinder
{
    private ExecutableFinder $executableFinder;

    public function __construct()
    {
        $this->executableFinder = new ExecutableFinder();
    }

    /**
     * Finds The PHP executable.
     */
    public function find(bool $includeArgs = true): string|false
    {
        if ($php = getenv('PHP_BINARY')) {
            if (!is_executable($php) && !$php = $this->executableFinder->find($php)) {
                return false;
            }

            if (@is_dir($php)) {
                return false;
            }

            return $php;
        }

        $args = $this->findArguments();
        $args = $includeArgs && $args ? ' '.implode(' ', $args) : '';

        // PHP_BINARY return the current sapi executable
        if (\PHP_BINARY && \in_array(\PHP_SAPI, ['cli', 'cli-server', 'phpdbg'], true)) {
            return \PHP_BINARY.$args;
        }

        if ($php = getenv('PHP_PATH')) {
            if (!@is_executable($php) || @is_dir($php)) {
                return false;
            }

            return $php;
        }

        if ($php = getenv('PHP_PEAR_PHP_BIN')) {
            if (@is_executable($php) && !@is_dir($php)) {
                return $php;
            }
        }

        if (@is_executable($php = \PHP_BINDIR.('\\' === \DIRECTORY_SEPARATOR ? '\\php.exe' : '/php')) && !@is_dir($php)) {
            return $php;
        }

        $dirs = [\PHP_BINDIR];
        if ('\\' === \DIRECTORY_SEPARATOR) {
            $dirs[] = 'C:\xampp\php\\';
        }

        return $this->executableFinder->find('php', false, $dirs);
    }

    /**
     * Finds the PHP executable arguments.
     */
    public function findArguments(): array
    {
        $arguments = [];
        if ('phpdbg' === \PHP_SAPI) {
            $arguments[] = '-qrr';
        }

        return $arguments;
    }
}
Copyright (c) 2004-present Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\EventDispatcher;

/**
 * An EventSubscriber knows itself what events it is interested in.
 * If an EventSubscriber is added to an EventDispatcherInterface, the manager invokes
 * {@link getSubscribedEvents} and registers the subscriber as a listener for all
 * returned events.
 *
 * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
 * @author Jonathan Wage <jonwage@gmail.com>
 * @author Roman Borschel <roman@code-factory.org>
 * @author Bernhard Schussek <bschussek@gmail.com>
 */
interface EventSubscriberInterface
{
    /**
     * Returns an array of event names this subscriber wants to listen to.
     *
     * The array keys are event names and the value can be:
     *
     *  * The method name to call (priority defaults to 0)
     *  * An array composed of the method name to call and the priority
     *  * An array of arrays composed of the method names to call and respective
     *    priorities, or 0 if unset
     *
     * For instance:
     *
     *  * ['eventName' => 'methodName']
     *  * ['eventName' => ['methodName', $priority]]
     *  * ['eventName' => [['methodName1', $priority], ['methodName2']]]
     *
     * 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<string, string|array{0: string, 1: int}|list<array{0: string, 1?: int}>>
     */
    public static function getSubscribedEvents();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 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
 * manager.
 *
 * @author Bernhard Schussek <bschussek@gmail.com>
 */
interface EventDispatcherInterface extends ContractsEventDispatcherInterface
{
    /**
     * Adds an event listener that listens on the specified events.
     *
     * @param int $priority The higher this value, the earlier an event
     *                      listener will be triggered in the chain (defaults to 0)
     *
     * @return void
     */
    public function addListener(string $eventName, callable $listener, int $priority = 0);

    /**
     * Adds an event subscriber.
     *
     * The subscriber is asked for all the events it is
     * interested in and added as a listener for these events.
     *
     * @return void
     */
    public function addSubscriber(EventSubscriberInterface $subscriber);

    /**
     * Removes an event listener from the specified events.
     *
     * @return void
     */
    public function removeListener(string $eventName, callable $listener);

    /**
     * @return void
     */
    public function removeSubscriber(EventSubscriberInterface $subscriber);

    /**
     * Gets the listeners of a specific event or all listeners sorted by descending priority.
     *
     * @return array<callable[]|callable>
     */
    public function getListeners(?string $eventName = null): array;

    /**
     * Gets the listener priority for a specific event.
     *
     * Returns null if the event or the listener does not exist.
     */
    public function getListenerPriority(string $eventName, callable $listener): ?int;

    /**
     * Checks whether an event has any registered listeners.
     */
    public function hasListeners(?string $eventName = null): bool;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 Psr\EventDispatcher\StoppableEventInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Component\VarDumper\Caster\ClassStub;

/**
 * @author Fabien Potencier <fabien@symfony.com>
 */
final class WrappedListener
{
    private string|array|object $listener;
    private ?\Closure $optimizedListener;
    private string $name;
    private bool $called = false;
    private bool $stoppedPropagation = false;
    private Stopwatch $stopwatch;
    private ?EventDispatcherInterface $dispatcher;
    private string $pretty;
    private string $callableRef;
    private ClassStub|string $stub;
    private ?int $priority = null;
    private static bool $hasClassStub;

    public function __construct(callable|array $listener, ?string $name, Stopwatch $stopwatch, ?EventDispatcherInterface $dispatcher = null, ?int $priority = null)
    {
        $this->listener = $listener;
        $this->optimizedListener = $listener instanceof \Closure ? $listener : (\is_callable($listener) ? $listener(...) : null);
        $this->stopwatch = $stopwatch;
        $this->dispatcher = $dispatcher;
        $this->priority = $priority;

        if (\is_array($listener)) {
            [$this->name, $this->callableRef] = $this->parseListener($listener);
            $this->pretty = $this->name.'::'.$listener[1];
            $this->callableRef .= '::'.$listener[1];
        } elseif ($listener instanceof \Closure) {
            $r = new \ReflectionFunction($listener);
            if (str_contains($r->name, '{closure')) {
                $this->pretty = $this->name = 'closure';
            } elseif ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) {
                $this->name = $class->name;
                $this->pretty = $this->name.'::'.$r->name;
            } else {
                $this->pretty = $this->name = $r->name;
            }
        } elseif (\is_string($listener)) {
            $this->pretty = $this->name = $listener;
        } else {
            $this->name = get_debug_type($listener);
            $this->pretty = $this->name.'::__invoke';
            $this->callableRef = $listener::class.'::__invoke';
        }

        if (null !== $name) {
            $this->name = $name;
        }

        self::$hasClassStub ??= class_exists(ClassStub::class);
    }

    public function getWrappedListener(): callable|array
    {
        return $this->listener;
    }

    public function wasCalled(): bool
    {
        return $this->called;
    }

    public function stoppedPropagation(): bool
    {
        return $this->stoppedPropagation;
    }

    public function getPretty(): string
    {
        return $this->pretty;
    }

    public function getInfo(string $eventName): array
    {
        $this->stub ??= self::$hasClassStub ? new ClassStub($this->pretty.'()', $this->callableRef ?? $this->listener) : $this->pretty.'()';

        return [
            'event' => $eventName,
            'priority' => $this->priority ??= $this->dispatcher?->getListenerPriority($eventName, $this->listener),
            'pretty' => $this->pretty,
            'stub' => $this->stub,
        ];
    }

    public function __invoke(object $event, string $eventName, EventDispatcherInterface $dispatcher): void
    {
        $dispatcher = $this->dispatcher ?: $dispatcher;

        $this->called = true;
        $this->priority ??= $dispatcher->getListenerPriority($eventName, $this->listener);

        $e = $this->stopwatch->start($this->name, 'event_listener');

        try {
            ($this->optimizedListener ?? $this->listener)($event, $eventName, $dispatcher);
        } finally {
            if ($e->isStarted()) {
                $e->stop();
            }
        }

        if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
            $this->stoppedPropagation = true;
        }
    }

    private function parseListener(array $listener): array
    {
        if ($listener[0] instanceof \Closure) {
            foreach ((new \ReflectionFunction($listener[0]))->getAttributes(\Closure::class) as $attribute) {
                if ($name = $attribute->getArguments()['name'] ?? false) {
                    return [$name, $attribute->getArguments()['class'] ?? $name];
                }
            }
        }

        if (\is_object($listener[0])) {
            return [get_debug_type($listener[0]), $listener[0]::class];
        }

        return [$listener[0], $listener[0]];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 Psr\EventDispatcher\StoppableEventInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
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.
 *
 * This event dispatcher delegates the dispatching to another one.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterface
{
    protected $logger;
    protected $stopwatch;

    /**
     * @var \SplObjectStorage<WrappedListener, array{string, string}>|null
     */
    private ?\SplObjectStorage $callStack = null;
    private EventDispatcherInterface $dispatcher;
    private array $wrappedListeners = [];
    private array $orphanedEvents = [];
    private array $dispatchDepth = [];
    private array $calledListenerInfos = [];
    private array $calledOriginalListeners = [];
    private ?RequestStack $requestStack;
    private string $currentRequestHash = '';

    public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, ?LoggerInterface $logger = null, ?RequestStack $requestStack = null)
    {
        $this->dispatcher = $dispatcher;
        $this->stopwatch = $stopwatch;
        $this->logger = $logger;
        $this->requestStack = $requestStack;
    }

    /**
     * @return void
     */
    public function addListener(string $eventName, callable|array $listener, int $priority = 0)
    {
        $this->dispatcher->addListener($eventName, $listener, $priority);
    }

    /**
     * @return void
     */
    public function addSubscriber(EventSubscriberInterface $subscriber)
    {
        $this->dispatcher->addSubscriber($subscriber);
    }

    /**
     * @return void
     */
    public function removeListener(string $eventName, callable|array $listener)
    {
        if (isset($this->wrappedListeners[$eventName])) {
            foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
                if ($wrappedListener->getWrappedListener() === $listener || ($listener instanceof \Closure && $wrappedListener->getWrappedListener() == $listener)) {
                    $listener = $wrappedListener;
                    unset($this->wrappedListeners[$eventName][$index]);
                    break;
                }
            }
        }

        $this->dispatcher->removeListener($eventName, $listener);
    }

    /**
     * @return void
     */
    public function removeSubscriber(EventSubscriberInterface $subscriber)
    {
        $this->dispatcher->removeSubscriber($subscriber);
    }

    public function getListeners(?string $eventName = null): array
    {
        return $this->dispatcher->getListeners($eventName);
    }

    public function getListenerPriority(string $eventName, callable|array $listener): ?int
    {
        // 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 $wrappedListener) {
                if ($wrappedListener->getWrappedListener() === $listener || ($listener instanceof \Closure && $wrappedListener->getWrappedListener() == $listener)) {
                    return $this->dispatcher->getListenerPriority($eventName, $wrappedListener);
                }
            }
        }

        return $this->dispatcher->getListenerPriority($eventName, $listener);
    }

    public function hasListeners(?string $eventName = null): bool
    {
        return $this->dispatcher->hasListeners($eventName);
    }

    public function dispatch(object $event, ?string $eventName = null): object
    {
        $eventName ??= $event::class;

        $this->callStack ??= new \SplObjectStorage();

        $currentRequestHash = $this->currentRequestHash = $this->requestStack && ($request = $this->requestStack->getCurrentRequest()) ? spl_object_hash($request) : '';

        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->beforeDispatch($eventName, $event);
            try {
                $e = $this->stopwatch->start($eventName, 'section');
                try {
                    $this->dispatcher->dispatch($event, $eventName);
                } finally {
                    if ($e->isStarted()) {
                        $e->stop();
                    }
                }
            } finally {
                $this->afterDispatch($eventName, $event);
            }
        } finally {
            $this->currentRequestHash = $currentRequestHash;
            $this->postProcess($eventName);
        }

        return $event;
    }

    public function getCalledListeners(?Request $request = null): array
    {
        if (!$this->calledListenerInfos) {
            return [];
        }

        $hash = $request ? spl_object_hash($request) : null;
        $called = [];

        foreach ($this->calledListenerInfos as $requestHash => $infos) {
            if (null === $hash || $hash === $requestHash) {
                $called[] = $infos;
            }
        }

        return $called ? array_merge(...$called) : [];
    }

    public function getNotCalledListeners(?Request $request = null): array
    {
        try {
            $allListeners = $this->dispatcher instanceof EventDispatcher ? $this->getListenersWithPriority() : $this->getListenersWithoutPriority();
        } catch (\Exception $e) {
            $this->logger?->info('An exception was thrown while getting the uncalled listeners.', ['exception' => $e]);

            // unable to retrieve the uncalled listeners
            return [];
        }

        $hash = $request ? spl_object_hash($request) : null;
        $calledListeners = [];

        foreach ($this->calledOriginalListeners as $requestHash => $eventListeners) {
            if (null === $hash || $hash === $requestHash) {
                $calledListeners[] = array_merge(...array_values($eventListeners));
            }
        }

        $calledListeners = $calledListeners ? array_merge(...$calledListeners) : [];

        $notCalled = [];

        foreach ($allListeners as $eventName => $listeners) {
            foreach ($listeners as [$listener, $priority]) {
                if (!\in_array($listener, $calledListeners, true)) {
                    if (!$listener instanceof WrappedListener) {
                        $listener = new WrappedListener($listener, null, $this->stopwatch, $this, $priority);
                    }
                    $notCalled[] = $listener->getInfo($eventName);
                }
            }
        }

        uasort($notCalled, $this->sortNotCalledListeners(...));

        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));
    }

    /**
     * @return void
     */
    public function reset()
    {
        $this->callStack = null;
        $this->orphanedEvents = [];
        $this->currentRequestHash = '';
        $this->dispatchDepth = [];
        $this->calledListenerInfos = [];
        $this->calledOriginalListeners = [];
    }

    /**
     * Proxies all method calls to the original event dispatcher.
     *
     * @param string $method    The method name
     * @param array  $arguments The method arguments
     */
    public function __call(string $method, array $arguments): mixed
    {
        return $this->dispatcher->{$method}(...$arguments);
    }

    /**
     * Called before dispatching the event.
     *
     * @return void
     */
    protected function beforeDispatch(string $eventName, object $event)
    {
    }

    /**
     * Called after dispatching the event.
     *
     * @return void
     */
    protected function afterDispatch(string $eventName, object $event)
    {
    }

    private function preProcess(string $eventName): void
    {
        $this->dispatchDepth[$eventName] = ($this->dispatchDepth[$eventName] ?? 0) + 1;

        if (!$this->dispatcher->hasListeners($eventName)) {
            $this->orphanedEvents[$this->currentRequestHash][] = $eventName;

            return;
        }

        foreach ($this->dispatcher->getListeners($eventName) as $listener) {
            $priority = $this->getListenerPriority($eventName, $listener) ?? 0;
            $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[$wrappedListener] = [$eventName, $this->currentRequestHash];
        }
    }

    private function postProcess(string $eventName): void
    {
        if (null === $this->callStack) {
            return;
        }

        $this->dispatchDepth[$eventName] = ($this->dispatchDepth[$eventName] ?? 1) - 1;

        unset($this->wrappedListeners[$eventName]);
        $skipped = false;
        foreach ($this->dispatcher->getListeners($eventName) as $listener) {
            if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch.
                continue;
            }
            // Unwrap listener
            $priority = $this->getListenerPriority($eventName, $listener);
            $this->dispatcher->removeListener($eventName, $listener);
            $this->dispatcher->addListener($eventName, $listener->getWrappedListener(), $priority);

            if (null !== $this->logger) {
                $context = ['event' => $eventName, 'listener' => $listener->getPretty()];
            }

            if ($listener->wasCalled()) {
                $this->logger?->debug('Notified event "{event}" to listener "{listener}".', $context);

                $original = $listener->getWrappedListener();
                if (!\in_array($original, $this->calledOriginalListeners[$this->currentRequestHash][$eventName] ?? [], true)) {
                    $this->calledOriginalListeners[$this->currentRequestHash][$eventName][] = $original;
                    $this->calledListenerInfos[$this->currentRequestHash][] = $listener->getInfo($eventName);
                }
            }

            unset($this->callStack[$listener]);

            if (null !== $this->logger && $skipped) {
                $this->logger->debug('Listener "{listener}" was not called for event "{event}".', $context);
            }

            if ($listener->stoppedPropagation()) {
                $this->logger?->debug('Listener "{listener}" stopped propagation of the event "{event}".', $context);

                $skipped = true;
            }
        }

        if (0 < $this->dispatchDepth[$eventName]) {
            return;
        }

        // Clean up stale callStack entries left by nested same-event dispatches
        $stale = [];
        foreach ($this->callStack as $listener) {
            if ($this->callStack->getInfo()[0] === $eventName) {
                $stale[] = $listener;
            }
        }
        foreach ($stale as $listener) {
            if ($listener->wasCalled()) {
                $original = $listener->getWrappedListener();
                if (!\in_array($original, $this->calledOriginalListeners[$this->currentRequestHash][$eventName] ?? [], true)) {
                    $this->calledOriginalListeners[$this->currentRequestHash][$eventName][] = $original;
                    $this->calledListenerInfos[$this->currentRequestHash][] = $listener->getInfo($eventName);
                }
            }
            unset($this->callStack[$listener]);
        }
    }

    private function sortNotCalledListeners(array $a, array $b): int
    {
        if (0 !== $cmp = strcmp($a['event'], $b['event'])) {
            return $cmp;
        }

        if (\is_int($a['priority']) && !\is_int($b['priority'])) {
            return 1;
        }

        if (!\is_int($a['priority']) && \is_int($b['priority'])) {
            return -1;
        }

        if ($a['priority'] === $b['priority']) {
            return 0;
        }

        if ($a['priority'] > $b['priority']) {
            return -1;
        }

        return 1;
    }

    private function getListenersWithPriority(): array
    {
        $result = [];

        $allListeners = new \ReflectionProperty(EventDispatcher::class, 'listeners');

        foreach ($allListeners->getValue($this->dispatcher) as $eventName => $listenersByPriority) {
            foreach ($listenersByPriority as $priority => $listeners) {
                foreach ($listeners as $listener) {
                    $result[$eventName][] = [$listener, $priority];
                }
            }
        }

        return $result;
    }

    private function getListenersWithoutPriority(): array
    {
        $result = [];

        foreach ($this->getListeners() as $eventName => $listeners) {
            foreach ($listeners as $listener) {
                $result[$eventName][] = [$listener, null];
            }
        }

        return $result;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\EventDispatcher;

/**
 * A read-only proxy for an event dispatcher.
 *
 * @author Bernhard Schussek <bschussek@gmail.com>
 */
class ImmutableEventDispatcher implements EventDispatcherInterface
{
    private EventDispatcherInterface $dispatcher;

    public function __construct(EventDispatcherInterface $dispatcher)
    {
        $this->dispatcher = $dispatcher;
    }

    public function dispatch(object $event, ?string $eventName = null): object
    {
        return $this->dispatcher->dispatch($event, $eventName);
    }

    /**
     * @return never
     */
    public function addListener(string $eventName, callable|array $listener, int $priority = 0)
    {
        throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
    }

    /**
     * @return never
     */
    public function addSubscriber(EventSubscriberInterface $subscriber)
    {
        throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
    }

    /**
     * @return never
     */
    public function removeListener(string $eventName, callable|array $listener)
    {
        throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
    }

    /**
     * @return never
     */
    public function removeSubscriber(EventSubscriberInterface $subscriber)
    {
        throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
    }

    public function getListeners(?string $eventName = null): array
    {
        return $this->dispatcher->getListeners($eventName);
    }

    public function getListenerPriority(string $eventName, callable|array $listener): ?int
    {
        return $this->dispatcher->getListenerPriority($eventName, $listener);
    }

    public function hasListeners(?string $eventName = null): bool
    {
        return $this->dispatcher->hasListeners($eventName);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

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.
 *
 * Listeners are registered on the manager and events are dispatched through the
 * manager.
 *
 * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
 * @author Jonathan Wage <jonwage@gmail.com>
 * @author Roman Borschel <roman@code-factory.org>
 * @author Bernhard Schussek <bschussek@gmail.com>
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Jordan Alliot <jordan.alliot@gmail.com>
 * @author Nicolas Grekas <p@tchwork.com>
 */
class EventDispatcher implements EventDispatcherInterface
{
    private array $listeners = [];
    private array $sorted = [];
    private array $optimized;

    public function __construct()
    {
        if (__CLASS__ === static::class) {
            $this->optimized = [];
        }
    }

    public function dispatch(object $event, ?string $eventName = null): object
    {
        $eventName ??= $event::class;

        if (isset($this->optimized)) {
            $listeners = $this->optimized[$eventName] ?? (empty($this->listeners[$eventName]) ? [] : $this->optimizeListeners($eventName));
        } else {
            $listeners = $this->getListeners($eventName);
        }

        if ($listeners) {
            $this->callListeners($listeners, $eventName, $event);
        }

        return $event;
    }

    public function getListeners(?string $eventName = null): array
    {
        if (null !== $eventName) {
            if (empty($this->listeners[$eventName])) {
                return [];
            }

            if (!isset($this->sorted[$eventName])) {
                $this->sortListeners($eventName);
            }

            return $this->sorted[$eventName];
        }

        foreach ($this->listeners as $eventName => $eventListeners) {
            if (!isset($this->sorted[$eventName])) {
                $this->sortListeners($eventName);
            }
        }

        return array_filter($this->sorted);
    }

    public function getListenerPriority(string $eventName, callable|array $listener): ?int
    {
        if (empty($this->listeners[$eventName])) {
            return null;
        }

        if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
            $listener[0] = $listener[0]();
            $listener[1] ??= '__invoke';
        }

        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]();
                    $v[1] ??= '__invoke';
                }
                if ($v === $listener || ($listener instanceof \Closure && $v == $listener)) {
                    return $priority;
                }
            }
        }

        return null;
    }

    public function hasListeners(?string $eventName = null): bool
    {
        if (null !== $eventName) {
            return !empty($this->listeners[$eventName]);
        }

        foreach ($this->listeners as $eventListeners) {
            if ($eventListeners) {
                return true;
            }
        }

        return false;
    }

    /**
     * @return void
     */
    public function addListener(string $eventName, callable|array $listener, int $priority = 0)
    {
        $this->listeners[$eventName][$priority][] = $listener;
        unset($this->sorted[$eventName], $this->optimized[$eventName]);
    }

    /**
     * @return void
     */
    public function removeListener(string $eventName, callable|array $listener)
    {
        if (empty($this->listeners[$eventName])) {
            return;
        }

        if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
            $listener[0] = $listener[0]();
            $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 && 2 >= \count($v)) {
                    $v[0] = $v[0]();
                    $v[1] ??= '__invoke';
                }
                if ($v === $listener || ($listener instanceof \Closure && $v == $listener)) {
                    unset($listeners[$k], $this->sorted[$eventName], $this->optimized[$eventName]);
                }
            }

            if (!$listeners) {
                unset($this->listeners[$eventName][$priority]);
            }
        }
    }

    /**
     * @return void
     */
    public function addSubscriber(EventSubscriberInterface $subscriber)
    {
        foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
            if (\is_string($params)) {
                $this->addListener($eventName, [$subscriber, $params]);
            } elseif (\is_string($params[0])) {
                $this->addListener($eventName, [$subscriber, $params[0]], $params[1] ?? 0);
            } else {
                foreach ($params as $listener) {
                    $this->addListener($eventName, [$subscriber, $listener[0]], $listener[1] ?? 0);
                }
            }
        }
    }

    /**
     * @return void
     */
    public function removeSubscriber(EventSubscriberInterface $subscriber)
    {
        foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
            if (\is_array($params) && \is_array($params[0])) {
                foreach ($params as $listener) {
                    $this->removeListener($eventName, [$subscriber, $listener[0]]);
                }
            } else {
                $this->removeListener($eventName, [$subscriber, \is_string($params) ? $params : $params[0]]);
            }
        }
    }

    /**
     * Triggers the listeners of an event.
     *
     * This method can be overridden to add functionality that is executed
     * for each listener.
     *
     * @param callable[] $listeners The event listeners
     * @param string     $eventName The name of the event to dispatch
     * @param object     $event     The event object to pass to the event handlers/listeners
     *
     * @return void
     */
    protected function callListeners(iterable $listeners, string $eventName, object $event)
    {
        $stoppable = $event instanceof StoppableEventInterface;

        foreach ($listeners as $listener) {
            if ($stoppable && $event->isPropagationStopped()) {
                break;
            }
            $listener($event, $eventName, $this);
        }
    }

    /**
     * Sorts the internal list of listeners for the given event by priority.
     */
    private function sortListeners(string $eventName): void
    {
        krsort($this->listeners[$eventName]);
        $this->sorted[$eventName] = [];

        foreach ($this->listeners[$eventName] as &$listeners) {
            foreach ($listeners as &$listener) {
                if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
                    $listener[0] = $listener[0]();
                    $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] ??= '__invoke';
                        }
                        ($closure = $listener(...))(...$args);
                    };
                } else {
                    $closure = $listener instanceof WrappedListener ? $listener : $listener(...);
                }
            }
        }

        return $this->optimized[$eventName];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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\Event;

/**
 * Event encapsulation class.
 *
 * Encapsulates events thus decoupling the observer from the subject they encapsulate.
 *
 * @author Drak <drak@zikula.org>
 *
 * @implements \ArrayAccess<string, mixed>
 * @implements \IteratorAggregate<string, mixed>
 */
class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
{
    protected $subject;
    protected $arguments;

    /**
     * Encapsulate an event with $subject and $arguments.
     *
     * @param mixed $subject   The subject of the event, usually an object or a callable
     * @param array $arguments Arguments to store in the event
     */
    public function __construct(mixed $subject = null, array $arguments = [])
    {
        $this->subject = $subject;
        $this->arguments = $arguments;
    }

    /**
     * Getter for subject property.
     */
    public function getSubject(): mixed
    {
        return $this->subject;
    }

    /**
     * Get argument by key.
     *
     * @throws \InvalidArgumentException if key is not found
     */
    public function getArgument(string $key): mixed
    {
        if ($this->hasArgument($key)) {
            return $this->arguments[$key];
        }

        throw new \InvalidArgumentException(\sprintf('Argument "%s" not found.', $key));
    }

    /**
     * Add argument to event.
     *
     * @return $this
     */
    public function setArgument(string $key, mixed $value): static
    {
        $this->arguments[$key] = $value;

        return $this;
    }

    /**
     * Getter for all arguments.
     */
    public function getArguments(): array
    {
        return $this->arguments;
    }

    /**
     * Set args property.
     *
     * @return $this
     */
    public function setArguments(array $args = []): static
    {
        $this->arguments = $args;

        return $this;
    }

    /**
     * Has argument.
     */
    public function hasArgument(string $key): bool
    {
        return \array_key_exists($key, $this->arguments);
    }

    /**
     * ArrayAccess for argument getter.
     *
     * @param string $key Array key
     *
     * @throws \InvalidArgumentException if key does not exist in $this->args
     */
    public function offsetGet(mixed $key): mixed
    {
        return $this->getArgument($key);
    }

    /**
     * ArrayAccess for argument setter.
     *
     * @param string $key Array key to set
     */
    public function offsetSet(mixed $key, mixed $value): void
    {
        $this->setArgument($key, $value);
    }

    /**
     * ArrayAccess for unset argument.
     *
     * @param string $key Array key
     */
    public function offsetUnset(mixed $key): void
    {
        if ($this->hasArgument($key)) {
            unset($this->arguments[$key]);
        }
    }

    /**
     * ArrayAccess has argument.
     *
     * @param string $key Array key
     */
    public function offsetExists(mixed $key): bool
    {
        return $this->hasArgument($key);
    }

    /**
     * IteratorAggregate for iterating over the object like an array.
     *
     * @return \ArrayIterator<string, mixed>
     */
    public function getIterator(): \ArrayIterator
    {
        return new \ArrayIterator($this->arguments);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <me@derrabus.de>
 */
#[\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,
    ) {
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
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.
 */
class RegisterListenersPass implements CompilerPassInterface
{
    private array $hotPathEvents = [];
    private array $noPreloadEvents = [];

    /**
     * @return $this
     */
    public function setHotPathEvents(array $hotPathEvents): static
    {
        $this->hotPathEvents = array_flip($hotPathEvents);

        return $this;
    }

    /**
     * @return $this
     */
    public function setNoPreloadEvents(array $noPreloadEvents): static
    {
        $this->noPreloadEvents = array_flip($noPreloadEvents);

        return $this;
    }

    /**
     * @return void
     */
    public function process(ContainerBuilder $container)
    {
        if (!$container->hasDefinition('event_dispatcher') && !$container->hasAlias('event_dispatcher')) {
            return;
        }

        $aliases = [];

        if ($container->hasParameter('event_dispatcher.event_aliases')) {
            $aliases = $container->getParameter('event_dispatcher.event_aliases');
        }

        $globalDispatcherDefinition = $container->findDefinition('event_dispatcher');

        foreach ($container->findTaggedServiceIds('kernel.event_listener', true) as $id => $events) {
            $noPreload = 0;

            foreach ($events as $event) {
                $priority = $event['priority'] ?? 0;

                if (!isset($event['event'])) {
                    if ($container->getDefinition($id)->hasTag('kernel.event_subscriber')) {
                        continue;
                    }

                    $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',
                        '/[^a-z0-9]/i',
                    ], static fn ($matches) => 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'])) {
                        if (!$r->hasMethod('__invoke')) {
                            throw new InvalidArgumentException(\sprintf('None of the "%s" or "__invoke" methods exist for the service "%s". Please define the "method" attribute on "kernel.event_listener" tags.', $event['method'], $id));
                        }

                        $event['method'] = '__invoke';
                    }
                }

                $dispatcherDefinition = $globalDispatcherDefinition;
                if (isset($event['dispatcher'])) {
                    $dispatcherDefinition = $container->findDefinition($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('container.hot_path');
                } elseif (isset($this->noPreloadEvents[$event['event']])) {
                    ++$noPreload;
                }
            }

            if ($noPreload && \count($events) === $noPreload) {
                $container->getDefinition($id)->addTag('container.no_preload');
            }
        }

        $extractingDispatcher = new ExtractingEventDispatcher();

        foreach ($container->findTaggedServiceIds('kernel.event_subscriber', 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
            $class = $def->getClass();

            if (!$r = $container->getReflectionClass($class)) {
                throw new InvalidArgumentException(\sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
            }
            if (!$r->isSubclassOf(EventSubscriberInterface::class)) {
                throw new InvalidArgumentException(\sprintf('Service "%s" must implement interface "%s".', $id, EventSubscriberInterface::class));
            }
            $class = $r->name;

            $dispatcherDefinitions = [];
            foreach ($tags as $attributes) {
                if (!isset($attributes['dispatcher']) || isset($dispatcherDefinitions[$attributes['dispatcher']])) {
                    continue;
                }

                $dispatcherDefinitions[$attributes['dispatcher']] = $container->findDefinition($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]];
                foreach ($dispatcherDefinitions as $dispatcherDefinition) {
                    $dispatcherDefinition->addMethodCall('addListener', $args);
                }

                if (isset($this->hotPathEvents[$args[0]])) {
                    $container->getDefinition($id)->addTag('container.hot_path');
                } elseif (isset($this->noPreloadEvents[$args[0]])) {
                    ++$noPreload;
                }
            }
            if ($noPreload && \count($extractingDispatcher->listeners) === $noPreload) {
                $container->getDefinition($id)->addTag('container.no_preload');
            }
            $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 "kernel.event_listener" tags.', $id));
        }

        return $name;
    }
}

/**
 * @internal
 */
class ExtractingEventDispatcher extends EventDispatcher implements EventSubscriberInterface
{
    public array $listeners = [];

    public static array $aliases = [];
    public static string $subscriber;

    public function addListener(string $eventName, callable|array $listener, int $priority = 0): void
    {
        $this->listeners[] = [$eventName, $listener[1], $priority];
    }

    public static function getSubscribedEvents(): array
    {
        $events = [];

        foreach ([self::$subscriber, 'getSubscribedEvents']() as $eventName => $params) {
            $events[self::$aliases[$eventName] ?? $eventName] = $params;
        }

        return $events;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <me@derrabus.de>
 */
class AddEventAliasesPass implements CompilerPassInterface
{
    private array $eventAliases;

    public function __construct(array $eventAliases)
    {
        $this->eventAliases = $eventAliases;
    }

    public function process(ContainerBuilder $container): void
    {
        $eventAliases = $container->hasParameter('event_dispatcher.event_aliases') ? $container->getParameter('event_dispatcher.event_aliases') : [];

        $container->setParameter(
            'event_dispatcher.event_aliases',
            array_merge($eventAliases, $this->eventAliases)
        );
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <p@tchwork.com>
 * @author Hugo Hamon <hugohamon@neuf.fr>
 *
 * @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 ??= 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): static
    {
        $str = clone $this;
        $str->string .= 1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix);

        return $str;
    }

    public function camel(): static
    {
        $str = clone $this;

        $parts = explode(' ', trim(ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $this->string))));
        $parts[0] = 1 !== \strlen($parts[0]) && ctype_upper($parts[0]) ? $parts[0] : lcfirst($parts[0]);
        $str->string = implode('', $parts);

        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(string|iterable|AbstractString $suffix): bool
    {
        if ($suffix instanceof AbstractString) {
            $suffix = $suffix->string;
        } elseif (!\is_string($suffix)) {
            return parent::endsWith($suffix);
        }

        return '' !== $suffix && \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix), null, $this->ignoreCase);
    }

    public function equalsTo(string|iterable|AbstractString $string): bool
    {
        if ($string instanceof AbstractString) {
            $string = $string->string;
        } elseif (!\is_string($string)) {
            return parent::equalsTo($string);
        }

        if ('' !== $string && $this->ignoreCase) {
            return 0 === strcasecmp($string, $this->string);
        }

        return $string === $this->string;
    }

    public function folded(): static
    {
        $str = clone $this;
        $str->string = strtolower($str->string);

        return $str;
    }

    public function indexOf(string|iterable|AbstractString $needle, int $offset = 0): ?int
    {
        if ($needle instanceof AbstractString) {
            $needle = $needle->string;
        } elseif (!\is_string($needle)) {
            return parent::indexOf($needle, $offset);
        }

        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(string|iterable|AbstractString $needle, int $offset = 0): ?int
    {
        if ($needle instanceof AbstractString) {
            $needle = $needle->string;
        } elseif (!\is_string($needle)) {
            return parent::indexOfLast($needle, $offset);
        }

        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): static
    {
        $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(): static
    {
        $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 fn ($t, $m) => throw new InvalidArgumentException($m));

        try {
            if (false === $match($regexp, $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) {
                throw new RuntimeException('Matching failed with error: '.preg_last_error_msg());
            }
        } finally {
            restore_error_handler();
        }

        return $matches;
    }

    public function padBoth(int $length, string $padStr = ' '): static
    {
        $str = clone $this;
        $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_BOTH);

        return $str;
    }

    public function padEnd(int $length, string $padStr = ' '): static
    {
        $str = clone $this;
        $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_RIGHT);

        return $str;
    }

    public function padStart(int $length, string $padStr = ' '): static
    {
        $str = clone $this;
        $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_LEFT);

        return $str;
    }

    public function prepend(string ...$prefix): static
    {
        $str = clone $this;
        $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$str->string;

        return $str;
    }

    public function replace(string $from, string $to): static
    {
        $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, string|callable $to): static
    {
        if ($this->ignoreCase) {
            $fromRegexp .= 'i';
        }

        $replace = \is_array($to) || $to instanceof \Closure ? 'preg_replace_callback' : 'preg_replace';

        set_error_handler(static fn ($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 && str_ends_with($k, '_ERROR')) {
                        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(): static
    {
        $str = clone $this;
        $str->string = strrev($str->string);

        return $str;
    }

    public function slice(int $start = 0, ?int $length = null): static
    {
        $str = clone $this;
        $str->string = (string) substr($this->string, $start, $length ?? \PHP_INT_MAX);

        return $str;
    }

    public function snake(): static
    {
        $str = $this->camel();
        $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): static
    {
        $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 ??= \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(string|iterable|AbstractString $prefix): bool
    {
        if ($prefix instanceof AbstractString) {
            $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): static
    {
        $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 fn ($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"): static
    {
        $str = clone $this;
        $str->string = trim($str->string, $chars);

        return $str;
    }

    public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C"): static
    {
        $str = clone $this;
        $str->string = rtrim($str->string, $chars);

        return $str;
    }

    public function trimStart(string $chars = " \t\n\r\0\x0B\x0C"): static
    {
        $str = clone $this;
        $str->string = ltrim($str->string, $chars);

        return $str;
    }

    public function upper(): static
    {
        $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);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 ??= '';

        return preg_match('//u', $string) ? new UnicodeString($string) : new ByteString($string);
    }
}
<?php

/*
 * This file has been auto-generated by the Symfony String Component for internal use.
 *
 * Unicode version: 16.0.0
 * Date: 2024-09-11T08:21:22+00:00
 */

return [
    [
        4352,
        4447,
    ],
    [
        8986,
        8987,
    ],
    [
        9001,
        9001,
    ],
    [
        9002,
        9002,
    ],
    [
        9193,
        9196,
    ],
    [
        9200,
        9200,
    ],
    [
        9203,
        9203,
    ],
    [
        9725,
        9726,
    ],
    [
        9748,
        9749,
    ],
    [
        9776,
        9783,
    ],
    [
        9800,
        9811,
    ],
    [
        9855,
        9855,
    ],
    [
        9866,
        9871,
    ],
    [
        9875,
        9875,
    ],
    [
        9889,
        9889,
    ],
    [
        9898,
        9899,
    ],
    [
        9917,
        9918,
    ],
    [
        9924,
        9925,
    ],
    [
        9934,
        9934,
    ],
    [
        9940,
        9940,
    ],
    [
        9962,
        9962,
    ],
    [
        9970,
        9971,
    ],
    [
        9973,
        9973,
    ],
    [
        9978,
        9978,
    ],
    [
        9981,
        9981,
    ],
    [
        9989,
        9989,
    ],
    [
        9994,
        9995,
    ],
    [
        10024,
        10024,
    ],
    [
        10060,
        10060,
    ],
    [
        10062,
        10062,
    ],
    [
        10067,
        10069,
    ],
    [
        10071,
        10071,
    ],
    [
        10133,
        10135,
    ],
    [
        10160,
        10160,
    ],
    [
        10175,
        10175,
    ],
    [
        11035,
        11036,
    ],
    [
        11088,
        11088,
    ],
    [
        11093,
        11093,
    ],
    [
        11904,
        11929,
    ],
    [
        11931,
        12019,
    ],
    [
        12032,
        12245,
    ],
    [
        12272,
        12287,
    ],
    [
        12288,
        12288,
    ],
    [
        12289,
        12291,
    ],
    [
        12292,
        12292,
    ],
    [
        12293,
        12293,
    ],
    [
        12294,
        12294,
    ],
    [
        12295,
        12295,
    ],
    [
        12296,
        12296,
    ],
    [
        12297,
        12297,
    ],
    [
        12298,
        12298,
    ],
    [
        12299,
        12299,
    ],
    [
        12300,
        12300,
    ],
    [
        12301,
        12301,
    ],
    [
        12302,
        12302,
    ],
    [
        12303,
        12303,
    ],
    [
        12304,
        12304,
    ],
    [
        12305,
        12305,
    ],
    [
        12306,
        12307,
    ],
    [
        12308,
        12308,
    ],
    [
        12309,
        12309,
    ],
    [
        12310,
        12310,
    ],
    [
        12311,
        12311,
    ],
    [
        12312,
        12312,
    ],
    [
        12313,
        12313,
    ],
    [
        12314,
        12314,
    ],
    [
        12315,
        12315,
    ],
    [
        12316,
        12316,
    ],
    [
        12317,
        12317,
    ],
    [
        12318,
        12319,
    ],
    [
        12320,
        12320,
    ],
    [
        12321,
        12329,
    ],
    [
        12330,
        12333,
    ],
    [
        12334,
        12335,
    ],
    [
        12336,
        12336,
    ],
    [
        12337,
        12341,
    ],
    [
        12342,
        12343,
    ],
    [
        12344,
        12346,
    ],
    [
        12347,
        12347,
    ],
    [
        12348,
        12348,
    ],
    [
        12349,
        12349,
    ],
    [
        12350,
        12350,
    ],
    [
        12353,
        12438,
    ],
    [
        12441,
        12442,
    ],
    [
        12443,
        12444,
    ],
    [
        12445,
        12446,
    ],
    [
        12447,
        12447,
    ],
    [
        12448,
        12448,
    ],
    [
        12449,
        12538,
    ],
    [
        12539,
        12539,
    ],
    [
        12540,
        12542,
    ],
    [
        12543,
        12543,
    ],
    [
        12549,
        12591,
    ],
    [
        12593,
        12686,
    ],
    [
        12688,
        12689,
    ],
    [
        12690,
        12693,
    ],
    [
        12694,
        12703,
    ],
    [
        12704,
        12735,
    ],
    [
        12736,
        12773,
    ],
    [
        12783,
        12783,
    ],
    [
        12784,
        12799,
    ],
    [
        12800,
        12830,
    ],
    [
        12832,
        12841,
    ],
    [
        12842,
        12871,
    ],
    [
        12880,
        12880,
    ],
    [
        12881,
        12895,
    ],
    [
        12896,
        12927,
    ],
    [
        12928,
        12937,
    ],
    [
        12938,
        12976,
    ],
    [
        12977,
        12991,
    ],
    [
        12992,
        13055,
    ],
    [
        13056,
        13311,
    ],
    [
        13312,
        19903,
    ],
    [
        19904,
        19967,
    ],
    [
        19968,
        40959,
    ],
    [
        40960,
        40980,
    ],
    [
        40981,
        40981,
    ],
    [
        40982,
        42124,
    ],
    [
        42128,
        42182,
    ],
    [
        43360,
        43388,
    ],
    [
        44032,
        55203,
    ],
    [
        63744,
        64109,
    ],
    [
        64110,
        64111,
    ],
    [
        64112,
        64217,
    ],
    [
        64218,
        64255,
    ],
    [
        65040,
        65046,
    ],
    [
        65047,
        65047,
    ],
    [
        65048,
        65048,
    ],
    [
        65049,
        65049,
    ],
    [
        65072,
        65072,
    ],
    [
        65073,
        65074,
    ],
    [
        65075,
        65076,
    ],
    [
        65077,
        65077,
    ],
    [
        65078,
        65078,
    ],
    [
        65079,
        65079,
    ],
    [
        65080,
        65080,
    ],
    [
        65081,
        65081,
    ],
    [
        65082,
        65082,
    ],
    [
        65083,
        65083,
    ],
    [
        65084,
        65084,
    ],
    [
        65085,
        65085,
    ],
    [
        65086,
        65086,
    ],
    [
        65087,
        65087,
    ],
    [
        65088,
        65088,
    ],
    [
        65089,
        65089,
    ],
    [
        65090,
        65090,
    ],
    [
        65091,
        65091,
    ],
    [
        65092,
        65092,
    ],
    [
        65093,
        65094,
    ],
    [
        65095,
        65095,
    ],
    [
        65096,
        65096,
    ],
    [
        65097,
        65100,
    ],
    [
        65101,
        65103,
    ],
    [
        65104,
        65106,
    ],
    [
        65108,
        65111,
    ],
    [
        65112,
        65112,
    ],
    [
        65113,
        65113,
    ],
    [
        65114,
        65114,
    ],
    [
        65115,
        65115,
    ],
    [
        65116,
        65116,
    ],
    [
        65117,
        65117,
    ],
    [
        65118,
        65118,
    ],
    [
        65119,
        65121,
    ],
    [
        65122,
        65122,
    ],
    [
        65123,
        65123,
    ],
    [
        65124,
        65126,
    ],
    [
        65128,
        65128,
    ],
    [
        65129,
        65129,
    ],
    [
        65130,
        65131,
    ],
    [
        65281,
        65283,
    ],
    [
        65284,
        65284,
    ],
    [
        65285,
        65287,
    ],
    [
        65288,
        65288,
    ],
    [
        65289,
        65289,
    ],
    [
        65290,
        65290,
    ],
    [
        65291,
        65291,
    ],
    [
        65292,
        65292,
    ],
    [
        65293,
        65293,
    ],
    [
        65294,
        65295,
    ],
    [
        65296,
        65305,
    ],
    [
        65306,
        65307,
    ],
    [
        65308,
        65310,
    ],
    [
        65311,
        65312,
    ],
    [
        65313,
        65338,
    ],
    [
        65339,
        65339,
    ],
    [
        65340,
        65340,
    ],
    [
        65341,
        65341,
    ],
    [
        65342,
        65342,
    ],
    [
        65343,
        65343,
    ],
    [
        65344,
        65344,
    ],
    [
        65345,
        65370,
    ],
    [
        65371,
        65371,
    ],
    [
        65372,
        65372,
    ],
    [
        65373,
        65373,
    ],
    [
        65374,
        65374,
    ],
    [
        65375,
        65375,
    ],
    [
        65376,
        65376,
    ],
    [
        65504,
        65505,
    ],
    [
        65506,
        65506,
    ],
    [
        65507,
        65507,
    ],
    [
        65508,
        65508,
    ],
    [
        65509,
        65510,
    ],
    [
        94176,
        94177,
    ],
    [
        94178,
        94178,
    ],
    [
        94179,
        94179,
    ],
    [
        94180,
        94180,
    ],
    [
        94192,
        94193,
    ],
    [
        94208,
        100343,
    ],
    [
        100352,
        101119,
    ],
    [
        101120,
        101589,
    ],
    [
        101631,
        101631,
    ],
    [
        101632,
        101640,
    ],
    [
        110576,
        110579,
    ],
    [
        110581,
        110587,
    ],
    [
        110589,
        110590,
    ],
    [
        110592,
        110847,
    ],
    [
        110848,
        110882,
    ],
    [
        110898,
        110898,
    ],
    [
        110928,
        110930,
    ],
    [
        110933,
        110933,
    ],
    [
        110948,
        110951,
    ],
    [
        110960,
        111355,
    ],
    [
        119552,
        119638,
    ],
    [
        119648,
        119670,
    ],
    [
        126980,
        126980,
    ],
    [
        127183,
        127183,
    ],
    [
        127374,
        127374,
    ],
    [
        127377,
        127386,
    ],
    [
        127488,
        127490,
    ],
    [
        127504,
        127547,
    ],
    [
        127552,
        127560,
    ],
    [
        127568,
        127569,
    ],
    [
        127584,
        127589,
    ],
    [
        127744,
        127776,
    ],
    [
        127789,
        127797,
    ],
    [
        127799,
        127868,
    ],
    [
        127870,
        127891,
    ],
    [
        127904,
        127946,
    ],
    [
        127951,
        127955,
    ],
    [
        127968,
        127984,
    ],
    [
        127988,
        127988,
    ],
    [
        127992,
        127994,
    ],
    [
        127995,
        127999,
    ],
    [
        128000,
        128062,
    ],
    [
        128064,
        128064,
    ],
    [
        128066,
        128252,
    ],
    [
        128255,
        128317,
    ],
    [
        128331,
        128334,
    ],
    [
        128336,
        128359,
    ],
    [
        128378,
        128378,
    ],
    [
        128405,
        128406,
    ],
    [
        128420,
        128420,
    ],
    [
        128507,
        128511,
    ],
    [
        128512,
        128591,
    ],
    [
        128640,
        128709,
    ],
    [
        128716,
        128716,
    ],
    [
        128720,
        128722,
    ],
    [
        128725,
        128727,
    ],
    [
        128732,
        128735,
    ],
    [
        128747,
        128748,
    ],
    [
        128756,
        128764,
    ],
    [
        128992,
        129003,
    ],
    [
        129008,
        129008,
    ],
    [
        129292,
        129338,
    ],
    [
        129340,
        129349,
    ],
    [
        129351,
        129535,
    ],
    [
        129648,
        129660,
    ],
    [
        129664,
        129673,
    ],
    [
        129679,
        129734,
    ],
    [
        129742,
        129756,
    ],
    [
        129759,
        129769,
    ],
    [
        129776,
        129784,
    ],
    [
        131072,
        173791,
    ],
    [
        173792,
        173823,
    ],
    [
        173824,
        177977,
    ],
    [
        177978,
        177983,
    ],
    [
        177984,
        178205,
    ],
    [
        178206,
        178207,
    ],
    [
        178208,
        183969,
    ],
    [
        183970,
        183983,
    ],
    [
        183984,
        191456,
    ],
    [
        191457,
        191471,
    ],
    [
        191472,
        192093,
    ],
    [
        192094,
        194559,
    ],
    [
        194560,
        195101,
    ],
    [
        195102,
        195103,
    ],
    [
        195104,
        196605,
    ],
    [
        196608,
        201546,
    ],
    [
        201547,
        201551,
    ],
    [
        201552,
        205743,
    ],
    [
        205744,
        262141,
    ],
];
<?php

/*
 * This file has been auto-generated by the Symfony String Component for internal use.
 *
 * Unicode version: 16.0.0
 * Date: 2024-09-11T08:21:22+00:00
 */

return [
    [
        768,
        879,
    ],
    [
        1155,
        1159,
    ],
    [
        1160,
        1161,
    ],
    [
        1425,
        1469,
    ],
    [
        1471,
        1471,
    ],
    [
        1473,
        1474,
    ],
    [
        1476,
        1477,
    ],
    [
        1479,
        1479,
    ],
    [
        1552,
        1562,
    ],
    [
        1611,
        1631,
    ],
    [
        1648,
        1648,
    ],
    [
        1750,
        1756,
    ],
    [
        1759,
        1764,
    ],
    [
        1767,
        1768,
    ],
    [
        1770,
        1773,
    ],
    [
        1809,
        1809,
    ],
    [
        1840,
        1866,
    ],
    [
        1958,
        1968,
    ],
    [
        2027,
        2035,
    ],
    [
        2045,
        2045,
    ],
    [
        2070,
        2073,
    ],
    [
        2075,
        2083,
    ],
    [
        2085,
        2087,
    ],
    [
        2089,
        2093,
    ],
    [
        2137,
        2139,
    ],
    [
        2199,
        2207,
    ],
    [
        2250,
        2273,
    ],
    [
        2275,
        2306,
    ],
    [
        2362,
        2362,
    ],
    [
        2364,
        2364,
    ],
    [
        2369,
        2376,
    ],
    [
        2381,
        2381,
    ],
    [
        2385,
        2391,
    ],
    [
        2402,
        2403,
    ],
    [
        2433,
        2433,
    ],
    [
        2492,
        2492,
    ],
    [
        2497,
        2500,
    ],
    [
        2509,
        2509,
    ],
    [
        2530,
        2531,
    ],
    [
        2558,
        2558,
    ],
    [
        2561,
        2562,
    ],
    [
        2620,
        2620,
    ],
    [
        2625,
        2626,
    ],
    [
        2631,
        2632,
    ],
    [
        2635,
        2637,
    ],
    [
        2641,
        2641,
    ],
    [
        2672,
        2673,
    ],
    [
        2677,
        2677,
    ],
    [
        2689,
        2690,
    ],
    [
        2748,
        2748,
    ],
    [
        2753,
        2757,
    ],
    [
        2759,
        2760,
    ],
    [
        2765,
        2765,
    ],
    [
        2786,
        2787,
    ],
    [
        2810,
        2815,
    ],
    [
        2817,
        2817,
    ],
    [
        2876,
        2876,
    ],
    [
        2879,
        2879,
    ],
    [
        2881,
        2884,
    ],
    [
        2893,
        2893,
    ],
    [
        2901,
        2902,
    ],
    [
        2914,
        2915,
    ],
    [
        2946,
        2946,
    ],
    [
        3008,
        3008,
    ],
    [
        3021,
        3021,
    ],
    [
        3072,
        3072,
    ],
    [
        3076,
        3076,
    ],
    [
        3132,
        3132,
    ],
    [
        3134,
        3136,
    ],
    [
        3142,
        3144,
    ],
    [
        3146,
        3149,
    ],
    [
        3157,
        3158,
    ],
    [
        3170,
        3171,
    ],
    [
        3201,
        3201,
    ],
    [
        3260,
        3260,
    ],
    [
        3263,
        3263,
    ],
    [
        3270,
        3270,
    ],
    [
        3276,
        3277,
    ],
    [
        3298,
        3299,
    ],
    [
        3328,
        3329,
    ],
    [
        3387,
        3388,
    ],
    [
        3393,
        3396,
    ],
    [
        3405,
        3405,
    ],
    [
        3426,
        3427,
    ],
    [
        3457,
        3457,
    ],
    [
        3530,
        3530,
    ],
    [
        3538,
        3540,
    ],
    [
        3542,
        3542,
    ],
    [
        3633,
        3633,
    ],
    [
        3636,
        3642,
    ],
    [
        3655,
        3662,
    ],
    [
        3761,
        3761,
    ],
    [
        3764,
        3772,
    ],
    [
        3784,
        3790,
    ],
    [
        3864,
        3865,
    ],
    [
        3893,
        3893,
    ],
    [
        3895,
        3895,
    ],
    [
        3897,
        3897,
    ],
    [
        3953,
        3966,
    ],
    [
        3968,
        3972,
    ],
    [
        3974,
        3975,
    ],
    [
        3981,
        3991,
    ],
    [
        3993,
        4028,
    ],
    [
        4038,
        4038,
    ],
    [
        4141,
        4144,
    ],
    [
        4146,
        4151,
    ],
    [
        4153,
        4154,
    ],
    [
        4157,
        4158,
    ],
    [
        4184,
        4185,
    ],
    [
        4190,
        4192,
    ],
    [
        4209,
        4212,
    ],
    [
        4226,
        4226,
    ],
    [
        4229,
        4230,
    ],
    [
        4237,
        4237,
    ],
    [
        4253,
        4253,
    ],
    [
        4957,
        4959,
    ],
    [
        5906,
        5908,
    ],
    [
        5938,
        5939,
    ],
    [
        5970,
        5971,
    ],
    [
        6002,
        6003,
    ],
    [
        6068,
        6069,
    ],
    [
        6071,
        6077,
    ],
    [
        6086,
        6086,
    ],
    [
        6089,
        6099,
    ],
    [
        6109,
        6109,
    ],
    [
        6155,
        6157,
    ],
    [
        6159,
        6159,
    ],
    [
        6277,
        6278,
    ],
    [
        6313,
        6313,
    ],
    [
        6432,
        6434,
    ],
    [
        6439,
        6440,
    ],
    [
        6450,
        6450,
    ],
    [
        6457,
        6459,
    ],
    [
        6679,
        6680,
    ],
    [
        6683,
        6683,
    ],
    [
        6742,
        6742,
    ],
    [
        6744,
        6750,
    ],
    [
        6752,
        6752,
    ],
    [
        6754,
        6754,
    ],
    [
        6757,
        6764,
    ],
    [
        6771,
        6780,
    ],
    [
        6783,
        6783,
    ],
    [
        6832,
        6845,
    ],
    [
        6846,
        6846,
    ],
    [
        6847,
        6862,
    ],
    [
        6912,
        6915,
    ],
    [
        6964,
        6964,
    ],
    [
        6966,
        6970,
    ],
    [
        6972,
        6972,
    ],
    [
        6978,
        6978,
    ],
    [
        7019,
        7027,
    ],
    [
        7040,
        7041,
    ],
    [
        7074,
        7077,
    ],
    [
        7080,
        7081,
    ],
    [
        7083,
        7085,
    ],
    [
        7142,
        7142,
    ],
    [
        7144,
        7145,
    ],
    [
        7149,
        7149,
    ],
    [
        7151,
        7153,
    ],
    [
        7212,
        7219,
    ],
    [
        7222,
        7223,
    ],
    [
        7376,
        7378,
    ],
    [
        7380,
        7392,
    ],
    [
        7394,
        7400,
    ],
    [
        7405,
        7405,
    ],
    [
        7412,
        7412,
    ],
    [
        7416,
        7417,
    ],
    [
        7616,
        7679,
    ],
    [
        8400,
        8412,
    ],
    [
        8413,
        8416,
    ],
    [
        8417,
        8417,
    ],
    [
        8418,
        8420,
    ],
    [
        8421,
        8432,
    ],
    [
        11503,
        11505,
    ],
    [
        11647,
        11647,
    ],
    [
        11744,
        11775,
    ],
    [
        12330,
        12333,
    ],
    [
        12441,
        12442,
    ],
    [
        42607,
        42607,
    ],
    [
        42608,
        42610,
    ],
    [
        42612,
        42621,
    ],
    [
        42654,
        42655,
    ],
    [
        42736,
        42737,
    ],
    [
        43010,
        43010,
    ],
    [
        43014,
        43014,
    ],
    [
        43019,
        43019,
    ],
    [
        43045,
        43046,
    ],
    [
        43052,
        43052,
    ],
    [
        43204,
        43205,
    ],
    [
        43232,
        43249,
    ],
    [
        43263,
        43263,
    ],
    [
        43302,
        43309,
    ],
    [
        43335,
        43345,
    ],
    [
        43392,
        43394,
    ],
    [
        43443,
        43443,
    ],
    [
        43446,
        43449,
    ],
    [
        43452,
        43453,
    ],
    [
        43493,
        43493,
    ],
    [
        43561,
        43566,
    ],
    [
        43569,
        43570,
    ],
    [
        43573,
        43574,
    ],
    [
        43587,
        43587,
    ],
    [
        43596,
        43596,
    ],
    [
        43644,
        43644,
    ],
    [
        43696,
        43696,
    ],
    [
        43698,
        43700,
    ],
    [
        43703,
        43704,
    ],
    [
        43710,
        43711,
    ],
    [
        43713,
        43713,
    ],
    [
        43756,
        43757,
    ],
    [
        43766,
        43766,
    ],
    [
        44005,
        44005,
    ],
    [
        44008,
        44008,
    ],
    [
        44013,
        44013,
    ],
    [
        64286,
        64286,
    ],
    [
        65024,
        65039,
    ],
    [
        65056,
        65071,
    ],
    [
        66045,
        66045,
    ],
    [
        66272,
        66272,
    ],
    [
        66422,
        66426,
    ],
    [
        68097,
        68099,
    ],
    [
        68101,
        68102,
    ],
    [
        68108,
        68111,
    ],
    [
        68152,
        68154,
    ],
    [
        68159,
        68159,
    ],
    [
        68325,
        68326,
    ],
    [
        68900,
        68903,
    ],
    [
        68969,
        68973,
    ],
    [
        69291,
        69292,
    ],
    [
        69372,
        69375,
    ],
    [
        69446,
        69456,
    ],
    [
        69506,
        69509,
    ],
    [
        69633,
        69633,
    ],
    [
        69688,
        69702,
    ],
    [
        69744,
        69744,
    ],
    [
        69747,
        69748,
    ],
    [
        69759,
        69761,
    ],
    [
        69811,
        69814,
    ],
    [
        69817,
        69818,
    ],
    [
        69826,
        69826,
    ],
    [
        69888,
        69890,
    ],
    [
        69927,
        69931,
    ],
    [
        69933,
        69940,
    ],
    [
        70003,
        70003,
    ],
    [
        70016,
        70017,
    ],
    [
        70070,
        70078,
    ],
    [
        70089,
        70092,
    ],
    [
        70095,
        70095,
    ],
    [
        70191,
        70193,
    ],
    [
        70196,
        70196,
    ],
    [
        70198,
        70199,
    ],
    [
        70206,
        70206,
    ],
    [
        70209,
        70209,
    ],
    [
        70367,
        70367,
    ],
    [
        70371,
        70378,
    ],
    [
        70400,
        70401,
    ],
    [
        70459,
        70460,
    ],
    [
        70464,
        70464,
    ],
    [
        70502,
        70508,
    ],
    [
        70512,
        70516,
    ],
    [
        70587,
        70592,
    ],
    [
        70606,
        70606,
    ],
    [
        70608,
        70608,
    ],
    [
        70610,
        70610,
    ],
    [
        70625,
        70626,
    ],
    [
        70712,
        70719,
    ],
    [
        70722,
        70724,
    ],
    [
        70726,
        70726,
    ],
    [
        70750,
        70750,
    ],
    [
        70835,
        70840,
    ],
    [
        70842,
        70842,
    ],
    [
        70847,
        70848,
    ],
    [
        70850,
        70851,
    ],
    [
        71090,
        71093,
    ],
    [
        71100,
        71101,
    ],
    [
        71103,
        71104,
    ],
    [
        71132,
        71133,
    ],
    [
        71219,
        71226,
    ],
    [
        71229,
        71229,
    ],
    [
        71231,
        71232,
    ],
    [
        71339,
        71339,
    ],
    [
        71341,
        71341,
    ],
    [
        71344,
        71349,
    ],
    [
        71351,
        71351,
    ],
    [
        71453,
        71453,
    ],
    [
        71455,
        71455,
    ],
    [
        71458,
        71461,
    ],
    [
        71463,
        71467,
    ],
    [
        71727,
        71735,
    ],
    [
        71737,
        71738,
    ],
    [
        71995,
        71996,
    ],
    [
        71998,
        71998,
    ],
    [
        72003,
        72003,
    ],
    [
        72148,
        72151,
    ],
    [
        72154,
        72155,
    ],
    [
        72160,
        72160,
    ],
    [
        72193,
        72202,
    ],
    [
        72243,
        72248,
    ],
    [
        72251,
        72254,
    ],
    [
        72263,
        72263,
    ],
    [
        72273,
        72278,
    ],
    [
        72281,
        72283,
    ],
    [
        72330,
        72342,
    ],
    [
        72344,
        72345,
    ],
    [
        72752,
        72758,
    ],
    [
        72760,
        72765,
    ],
    [
        72767,
        72767,
    ],
    [
        72850,
        72871,
    ],
    [
        72874,
        72880,
    ],
    [
        72882,
        72883,
    ],
    [
        72885,
        72886,
    ],
    [
        73009,
        73014,
    ],
    [
        73018,
        73018,
    ],
    [
        73020,
        73021,
    ],
    [
        73023,
        73029,
    ],
    [
        73031,
        73031,
    ],
    [
        73104,
        73105,
    ],
    [
        73109,
        73109,
    ],
    [
        73111,
        73111,
    ],
    [
        73459,
        73460,
    ],
    [
        73472,
        73473,
    ],
    [
        73526,
        73530,
    ],
    [
        73536,
        73536,
    ],
    [
        73538,
        73538,
    ],
    [
        73562,
        73562,
    ],
    [
        78912,
        78912,
    ],
    [
        78919,
        78933,
    ],
    [
        90398,
        90409,
    ],
    [
        90413,
        90415,
    ],
    [
        92912,
        92916,
    ],
    [
        92976,
        92982,
    ],
    [
        94031,
        94031,
    ],
    [
        94095,
        94098,
    ],
    [
        94180,
        94180,
    ],
    [
        113821,
        113822,
    ],
    [
        118528,
        118573,
    ],
    [
        118576,
        118598,
    ],
    [
        119143,
        119145,
    ],
    [
        119163,
        119170,
    ],
    [
        119173,
        119179,
    ],
    [
        119210,
        119213,
    ],
    [
        119362,
        119364,
    ],
    [
        121344,
        121398,
    ],
    [
        121403,
        121452,
    ],
    [
        121461,
        121461,
    ],
    [
        121476,
        121476,
    ],
    [
        121499,
        121503,
    ],
    [
        121505,
        121519,
    ],
    [
        122880,
        122886,
    ],
    [
        122888,
        122904,
    ],
    [
        122907,
        122913,
    ],
    [
        122915,
        122916,
    ],
    [
        122918,
        122922,
    ],
    [
        123023,
        123023,
    ],
    [
        123184,
        123190,
    ],
    [
        123566,
        123566,
    ],
    [
        123628,
        123631,
    ],
    [
        124140,
        124143,
    ],
    [
        124398,
        124399,
    ],
    [
        125136,
        125142,
    ],
    [
        125252,
        125258,
    ],
    [
        917760,
        917999,
    ],
];
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <galopintitouan@gmail.com>
 */
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;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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\Intl\Transliterator\EmojiTransliterator;
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 <galopintitouan@gmail.com>
 */
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 ?string $defaultLocale;
    private \Closure|array $symbolsMap = [
        'en' => ['@' => 'at', '&' => 'and'],
    ];
    private bool|string $emoji = false;

    /**
     * Cache of transliterators per locale.
     *
     * @var \Transliterator[]
     */
    private array $transliterators = [];

    public function __construct(?string $defaultLocale = null, array|\Closure|null $symbolsMap = null)
    {
        $this->defaultLocale = $defaultLocale;
        $this->symbolsMap = $symbolsMap ?? $this->symbolsMap;
    }

    /**
     * @return void
     */
    public function setLocale(string $locale)
    {
        $this->defaultLocale = $locale;
    }

    public function getLocale(): string
    {
        return $this->defaultLocale;
    }

    /**
     * @param bool|string $emoji true will use the same locale,
     *                           false will disable emoji,
     *                           and a string to use a specific locale
     */
    public function withEmoji(bool|string $emoji = true): static
    {
        if (false !== $emoji && !class_exists(EmojiTransliterator::class)) {
            throw new \LogicException(\sprintf('You cannot use the "%s()" method as the "symfony/intl" package is not installed. Try running "composer require symfony/intl".', __METHOD__));
        }

        $new = clone $this;
        $new->emoji = $emoji;

        return $new;
    }

    public function slug(string $string, string $separator = '-', ?string $locale = null): AbstractUnicodeString
    {
        $locale ??= $this->defaultLocale;

        $transliterator = [];
        if ($locale && ('de' === $locale || str_starts_with($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 ($emojiTransliterator = $this->createEmojiTransliterator($locale)) {
            $transliterator[] = $emojiTransliterator;
        }

        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 fn ($s) => $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 function createEmojiTransliterator(?string $locale): ?EmojiTransliterator
    {
        if (\is_string($this->emoji)) {
            $locale = $this->emoji;
        } elseif (!$this->emoji) {
            return null;
        }

        while (null !== $locale) {
            try {
                return EmojiTransliterator::create("emoji-$locale");
            } catch (\IntlException) {
                $locale = self::getParentLocale($locale);
            }
        }

        return 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));
    }
}
Copyright (c) 2019-present Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <p@tchwork.com>
 * @author Hugo Hamon <hugohamon@neuf.fr>
 *
 * @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): static
    {
        $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(string|iterable|AbstractString $suffix): bool
    {
        if ($suffix instanceof AbstractString) {
            $suffix = $suffix->string;
        } elseif (!\is_string($suffix)) {
            return parent::endsWith($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|iterable|AbstractString $string): bool
    {
        if ($string instanceof AbstractString) {
            $string = $string->string;
        } elseif (!\is_string($string)) {
            return parent::equalsTo($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(string|iterable|AbstractString $needle, int $offset = 0): ?int
    {
        if ($needle instanceof AbstractString) {
            $needle = $needle->string;
        } elseif (!\is_string($needle)) {
            return parent::indexOf($needle, $offset);
        }

        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(string|iterable|AbstractString $needle, int $offset = 0): ?int
    {
        if ($needle instanceof AbstractString) {
            $needle = $needle->string;
        } elseif (!\is_string($needle)) {
            return parent::indexOfLast($needle, $offset);
        }

        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): static
    {
        $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): static
    {
        $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): static
    {
        $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): static
    {
        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 ??= \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(string|iterable|AbstractString $prefix): bool
    {
        if ($prefix instanceof AbstractString) {
            $prefix = $prefix->string;
        } elseif (!\is_string($prefix)) {
            return parent::startsWith($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));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <p@tchwork.com>
 * @author Hugo Hamon <hugohamon@neuf.fr>
 *
 * @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 ??= 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
     */
    public function after(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): static
    {
        $str = clone $this;
        $i = \PHP_INT_MAX;

        if (\is_string($needle)) {
            $needle = [$needle];
        }

        foreach ($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
     */
    public function afterLast(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): static
    {
        $str = clone $this;
        $i = null;

        if (\is_string($needle)) {
            $needle = [$needle];
        }

        foreach ($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);
    }

    abstract public function append(string ...$suffix): static;

    /**
     * @param string|string[] $needle
     */
    public function before(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): static
    {
        $str = clone $this;
        $i = \PHP_INT_MAX;

        if (\is_string($needle)) {
            $needle = [$needle];
        }

        foreach ($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
     */
    public function beforeLast(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): static
    {
        $str = clone $this;
        $i = null;

        if (\is_string($needle)) {
            $needle = [$needle];
        }

        foreach ($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));
    }

    abstract public function camel(): static;

    /**
     * @return static[]
     */
    abstract public function chunk(int $length = 1): array;

    public function collapseWhitespace(): static
    {
        $str = clone $this;
        $str->string = trim(preg_replace("/(?:[ \n\r\t\x0C]{2,}+|[\n\r\t\x0C])/", ' ', $str->string), " \n\r\t\x0C");

        return $str;
    }

    /**
     * @param string|string[] $needle
     */
    public function containsAny(string|iterable $needle): bool
    {
        return null !== $this->indexOf($needle);
    }

    /**
     * @param string|string[] $suffix
     */
    public function endsWith(string|iterable $suffix): bool
    {
        if (\is_string($suffix)) {
            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;
    }

    public function ensureEnd(string $suffix): static
    {
        if (!$this->endsWith($suffix)) {
            return $this->append($suffix);
        }

        $suffix = preg_quote($suffix);
        $regex = '{('.$suffix.')(?:'.$suffix.')++$}D';

        return $this->replaceMatches($regex.($this->ignoreCase ? 'i' : ''), '$1');
    }

    public function ensureStart(string $prefix): static
    {
        $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|iterable $string): bool
    {
        if (\is_string($string)) {
            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;
    }

    abstract public function folded(): static;

    public function ignoreCase(): static
    {
        $str = clone $this;
        $str->ignoreCase = true;

        return $str;
    }

    /**
     * @param string|string[] $needle
     */
    public function indexOf(string|iterable $needle, int $offset = 0): ?int
    {
        if (\is_string($needle)) {
            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(string|iterable $needle, int $offset = 0): ?int
    {
        if (\is_string($needle)) {
            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;
    }

    abstract public function join(array $strings, ?string $lastGlue = null): static;

    public function jsonSerialize(): string
    {
        return $this->string;
    }

    abstract public function length(): int;

    abstract public function lower(): static;

    /**
     * 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;

    abstract public function padBoth(int $length, string $padStr = ' '): static;

    abstract public function padEnd(int $length, string $padStr = ' '): static;

    abstract public function padStart(int $length, string $padStr = ' '): static;

    abstract public function prepend(string ...$prefix): static;

    public function repeat(int $multiplier): static
    {
        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;
    }

    abstract public function replace(string $from, string $to): static;

    abstract public function replaceMatches(string $fromRegexp, string|callable $to): static;

    abstract public function reverse(): static;

    abstract public function slice(int $start = 0, ?int $length = null): static;

    abstract public function snake(): static;

    abstract public function splice(string $replacement, int $start = 0, ?int $length = null): static;

    /**
     * @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 fn ($t, $m) => throw new InvalidArgumentException($m));

        try {
            if (false === $chunks = preg_split($delimiter, $this->string, $limit, $flags)) {
                throw new RuntimeException('Splitting failed with error: '.preg_last_error_msg());
            }
        } 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(string|iterable $prefix): bool
    {
        if (\is_string($prefix)) {
            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;
    }

    abstract public function title(bool $allWords = false): static;

    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;
        }

        try {
            $b->string = mb_convert_encoding($this->string, $toEncoding, 'UTF-8');
        } catch (\ValueError $e) {
            if (!\function_exists('iconv')) {
                throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
            }

            $b->string = iconv('UTF-8', $toEncoding, $this->string);
        }

        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);
    }

    abstract public function trim(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static;

    abstract public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static;

    /**
     * @param string|string[] $prefix
     */
    public function trimPrefix($prefix): static
    {
        if (\is_array($prefix) || $prefix instanceof \Traversable) { // don't use is_iterable(), it's slow
            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;
    }

    abstract public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static;

    /**
     * @param string|string[] $suffix
     */
    public function trimSuffix($suffix): static
    {
        if (\is_array($suffix) || $suffix instanceof \Traversable) { // don't use is_iterable(), it's slow
            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;
    }

    public function truncate(int $length, string $ellipsis = '', bool $cut = true): static
    {
        $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;
    }

    abstract public function upper(): static;

    /**
     * Returns the printable length on a terminal.
     */
    abstract public function width(bool $ignoreAnsiDecoration = true): int;

    public function wordwrap(int $width = 75, string $break = "\n", bool $cut = false): static
    {
        $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;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <p@tchwork.com>
 * @author Hugo Hamon <hugohamon@neuf.fr>
 *
 * @throws ExceptionInterface
 */
class UnicodeString extends AbstractUnicodeString
{
    public function __construct(string $string = '')
    {
        if ('' === $string || normalizer_is_normalized($this->string = $string)) {
            return;
        }

        if (false === $string = normalizer_normalize($string)) {
            throw new InvalidArgumentException('Invalid UTF-8 string.');
        }

        $this->string = $string;
    }

    public function append(string ...$suffix): static
    {
        $str = clone $this;
        $str->string = $this->string.(1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix));

        if (normalizer_is_normalized($str->string)) {
            return $str;
        }

        if (false === $string = normalizer_normalize($str->string)) {
            throw new InvalidArgumentException('Invalid UTF-8 string.');
        }

        $str->string = $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(string|iterable|AbstractString $suffix): bool
    {
        if ($suffix instanceof AbstractString) {
            $suffix = $suffix->string;
        } elseif (!\is_string($suffix)) {
            return parent::endsWith($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|iterable|AbstractString $string): bool
    {
        if ($string instanceof AbstractString) {
            $string = $string->string;
        } elseif (!\is_string($string)) {
            return parent::equalsTo($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(string|iterable|AbstractString $needle, int $offset = 0): ?int
    {
        if ($needle instanceof AbstractString) {
            $needle = $needle->string;
        } elseif (!\is_string($needle)) {
            return parent::indexOf($needle, $offset);
        }

        $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) {
            return null;
        }

        return false === $i ? null : $i;
    }

    public function indexOfLast(string|iterable|AbstractString $needle, int $offset = 0): ?int
    {
        if ($needle instanceof AbstractString) {
            $needle = $needle->string;
        } elseif (!\is_string($needle)) {
            return parent::indexOfLast($needle, $offset);
        }

        $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): static
    {
        $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);
    }

    public function normalize(int $form = self::NFC): static
    {
        $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): static
    {
        $str = clone $this;
        $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$this->string;

        if (normalizer_is_normalized($str->string)) {
            return $str;
        }

        if (false === $string = normalizer_normalize($str->string)) {
            throw new InvalidArgumentException('Invalid UTF-8 string.');
        }

        $str->string = $string;

        return $str;
    }

    public function replace(string $from, string $to): static
    {
        $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;

            if (normalizer_is_normalized($str->string)) {
                return $str;
            }

            if (false === $string = normalizer_normalize($str->string)) {
                throw new InvalidArgumentException('Invalid UTF-8 string.');
            }

            $str->string = $string;
        }

        return $str;
    }

    public function replaceMatches(string $fromRegexp, string|callable $to): static
    {
        $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): static
    {
        $str = clone $this;

        $str->string = (string) grapheme_substr($this->string, $start, $length ?? 2147483647);

        return $str;
    }

    public function splice(string $replacement, int $start = 0, ?int $length = null): static
    {
        $str = clone $this;

        $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);

        if (normalizer_is_normalized($str->string)) {
            return $str;
        }

        if (false === $string = normalizer_normalize($str->string)) {
            throw new InvalidArgumentException('Invalid UTF-8 string.');
        }

        $str->string = $string;

        return $str;
    }

    public function split(string $delimiter, ?int $limit = null, ?int $flags = null): array
    {
        if (1 > $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(string|iterable|AbstractString $prefix): bool
    {
        if ($prefix instanceof AbstractString) {
            $prefix = $prefix->string;
        } elseif (!\is_string($prefix)) {
            return parent::startsWith($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 trimPrefix($prefix): static
    {
        if (\is_array($prefix) || $prefix instanceof \Traversable) {
            return parent::trimPrefix($prefix);
        }

        if ($prefix instanceof AbstractString) {
            $prefix = $prefix->string;
        } else {
            $prefix = (string) $prefix;
        }

        if (!normalizer_is_normalized($prefix, \Normalizer::NFC)) {
            $prefix = normalizer_normalize($prefix, \Normalizer::NFC);
        }

        return parent::trimPrefix($prefix);
    }

    public function trimSuffix($suffix): static
    {
        if (\is_array($suffix) || $suffix instanceof \Traversable) {
            return parent::trimSuffix($suffix);
        }

        if ($suffix instanceof AbstractString) {
            $suffix = $suffix->string;
        } else {
            $suffix = (string) $suffix;
        }

        if (!normalizer_is_normalized($suffix, \Normalizer::NFC)) {
            $suffix = normalizer_normalize($suffix, \Normalizer::NFC);
        }

        return parent::trimSuffix($suffix);
    }

    /**
     * @return void
     */
    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;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <p@tchwork.com>
 *
 * @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", 'ß', 'ŉ', 'ǰ', 'ΐ', 'ΰ', 'և', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'ẚ', 'ẞ', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'ᾐ', 'ᾑ', 'ᾒ', 'ᾓ', 'ᾔ', 'ᾕ', 'ᾖ', 'ᾗ', 'ᾘ', 'ᾙ', 'ᾚ', 'ᾛ', 'ᾜ', 'ᾝ', 'ᾞ', 'ᾟ', 'ᾠ', 'ᾡ', 'ᾢ', 'ᾣ', 'ᾤ', 'ᾥ', 'ᾦ', 'ᾧ', 'ᾨ', 'ᾩ', 'ᾪ', 'ᾫ', 'ᾬ', 'ᾭ', 'ᾮ', 'ᾯ', 'ᾲ', 'ᾳ', 'ᾴ', 'ᾶ', 'ᾷ', 'ᾼ', 'ῂ', 'ῃ', 'ῄ', 'ῆ', 'ῇ', 'ῌ', 'ῒ', 'ῖ', 'ῗ', 'ῢ', 'ῤ', 'ῦ', 'ῧ', 'ῲ', 'ῳ', 'ῴ', 'ῶ', 'ῷ', 'ῼ', 'ﬀ', 'ﬁ', 'ﬂ', 'ﬃ', 'ﬄ', 'ﬅ', 'ﬆ', 'ﬓ', 'ﬔ', 'ﬕ', 'ﬖ', 'ﬗ'];
    private const FOLD_TO = ['i̇', 'μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', 'ṡ', 'ι', 'ss', 'ʼn', 'ǰ', 'ΐ', 'ΰ', 'եւ', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'aʾ', 'ss', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὰι', 'αι', 'άι', 'ᾶ', 'ᾶι', 'αι', 'ὴι', 'ηι', 'ήι', 'ῆ', 'ῆι', 'ηι', 'ῒ', 'ῖ', 'ῗ', 'ῢ', 'ῤ', 'ῦ', 'ῧ', 'ὼι', 'ωι', 'ώι', 'ῶ', 'ῶι', 'ωι', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'st', 'st', 'մն', 'մե', 'մի', 'վն', 'մխ'];

    // 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 = ['Æ', 'Ð', 'Ø', 'Þ', 'ß', 'æ', 'ð', 'ø', 'þ', 'Đ', 'đ', 'Ħ', 'ħ', 'ı', 'ĸ', 'Ŀ', 'ŀ', 'Ł', 'ł', 'ŉ', 'Ŋ', 'ŋ', 'Œ', 'œ', 'Ŧ', 'ŧ', 'ƀ', 'Ɓ', 'Ƃ', 'ƃ', 'Ƈ', 'ƈ', 'Ɖ', 'Ɗ', 'Ƌ', 'ƌ', 'Ɛ', 'Ƒ', 'ƒ', 'Ɠ', 'ƕ', 'Ɩ', 'Ɨ', 'Ƙ', 'ƙ', 'ƚ', 'Ɲ', 'ƞ', 'Ƣ', 'ƣ', 'Ƥ', 'ƥ', 'ƫ', 'Ƭ', 'ƭ', 'Ʈ', 'Ʋ', 'Ƴ', 'ƴ', 'Ƶ', 'ƶ', 'Ǆ', 'ǅ', 'ǆ', 'Ǥ', 'ǥ', 'ȡ', 'Ȥ', 'ȥ', 'ȴ', 'ȵ', 'ȶ', 'ȷ', 'ȸ', 'ȹ', 'Ⱥ', 'Ȼ', 'ȼ', 'Ƚ', 'Ⱦ', 'ȿ', 'ɀ', 'Ƀ', 'Ʉ', 'Ɇ', 'ɇ', 'Ɉ', 'ɉ', 'Ɍ', 'ɍ', 'Ɏ', 'ɏ', 'ɓ', 'ɕ', 'ɖ', 'ɗ', 'ɛ', 'ɟ', 'ɠ', 'ɡ', 'ɢ', 'ɦ', 'ɧ', 'ɨ', 'ɪ', 'ɫ', 'ɬ', 'ɭ', 'ɱ', 'ɲ', 'ɳ', 'ɴ', 'ɶ', 'ɼ', 'ɽ', 'ɾ', 'ʀ', 'ʂ', 'ʈ', 'ʉ', 'ʋ', 'ʏ', 'ʐ', 'ʑ', 'ʙ', 'ʛ', 'ʜ', 'ʝ', 'ʟ', 'ʠ', 'ʣ', 'ʥ', 'ʦ', 'ʪ', 'ʫ', 'ᴀ', 'ᴁ', 'ᴃ', 'ᴄ', 'ᴅ', 'ᴆ', 'ᴇ', 'ᴊ', 'ᴋ', 'ᴌ', 'ᴍ', 'ᴏ', 'ᴘ', 'ᴛ', 'ᴜ', 'ᴠ', 'ᴡ', 'ᴢ', 'ᵫ', 'ᵬ', 'ᵭ', 'ᵮ', 'ᵯ', 'ᵰ', 'ᵱ', 'ᵲ', 'ᵳ', 'ᵴ', 'ᵵ', 'ᵶ', 'ᵺ', 'ᵻ', 'ᵽ', 'ᵾ', 'ᶀ', 'ᶁ', 'ᶂ', 'ᶃ', 'ᶄ', 'ᶅ', 'ᶆ', 'ᶇ', 'ᶈ', 'ᶉ', 'ᶊ', 'ᶌ', 'ᶍ', 'ᶎ', 'ᶏ', 'ᶑ', 'ᶒ', 'ᶓ', 'ᶖ', 'ᶙ', 'ẚ', 'ẜ', 'ẝ', 'ẞ', 'Ỻ', 'ỻ', 'Ỽ', 'ỽ', 'Ỿ', 'ỿ', '©', '®', '₠', '₢', '₣', '₤', '₧', '₺', '₹', 'ℌ', '℞', '㎧', '㎮', '㏆', '㏗', '㏞', '㏟', '¼', '½', '¾', '⅓', '⅔', '⅕', '⅖', '⅗', '⅘', '⅙', '⅚', '⅛', '⅜', '⅝', '⅞', '⅟', '〇', '‘', '’', '‚', '‛', '“', '”', '„', '‟', '′', '″', '〝', '〞', '«', '»', '‹', '›', '‐', '‑', '‒', '–', '—', '―', '︱', '︲', '﹘', '‖', '⁄', '⁅', '⁆', '⁎', '、', '。', '〈', '〉', '《', '》', '〔', '〕', '〘', '〙', '〚', '〛', '︑', '︒', '︹', '︺', '︽', '︾', '︿', '﹀', '﹑', '﹝', '﹞', '｟', '｠', '｡', '､', '×', '÷', '−', '∕', '∖', '∣', '∥', '≪', '≫', '⦅', '⦆'];
    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 array $transliterators = [];
    private static array $tableZero;
    private static array $tableWide;

    public static function fromCodePoints(int ...$codes): static
    {
        $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] ??= \Transliterator::create($rule)) {
                        if ('any-latin/bgn' === $rule) {
                            $rule = 'any-latin';
                            $transliterator = 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 {
                $previousLocale = setlocale(\LC_CTYPE, 0);
                try {
                    setlocale(\LC_CTYPE, 'C');
                    $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);
                } finally {
                    setlocale(\LC_CTYPE, $previousLocale);
                }
            }
        }

        $str->string .= $s;

        return $str;
    }

    public function camel(): static
    {
        $str = clone $this;
        $str->string = str_replace(' ', '', preg_replace_callback('/\b.(?!\p{Lu})/u', static function ($m) {
            static $i = 0;

            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): static
    {
        $str = clone $this;

        if (!$compat || !\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, $str->string), 'UTF-8');
        } else {
            $str->string = normalizer_normalize($str->string, \Normalizer::NFKC_CF);
        }

        return $str;
    }

    public function join(array $strings, ?string $lastGlue = null): static
    {
        $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(): static
    {
        $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 fn ($t, $m) => throw new InvalidArgumentException($m));

        try {
            if (false === $match($regexp.'u', $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) {
                throw new RuntimeException('Matching failed with error: '.preg_last_error_msg());
            }
        } finally {
            restore_error_handler();
        }

        return $matches;
    }

    public function normalize(int $form = self::NFC): static
    {
        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 = ' '): static
    {
        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 = ' '): static
    {
        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 = ' '): static
    {
        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, string|callable $to): static
    {
        if ($this->ignoreCase) {
            $fromRegexp .= 'i';
        }

        if (\is_array($to) || $to instanceof \Closure) {
            $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 fn ($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 && str_ends_with($k, '_ERROR')) {
                        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(): static
    {
        $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(): static
    {
        $str = $this->camel();
        $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): static
    {
        $str = clone $this;

        $limit = $allWords ? -1 : 1;

        $str->string = preg_replace_callback('/\b./u', static fn (array $m): string => 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}"): static
    {
        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}"): static
    {
        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): static
    {
        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}"): static
    {
        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): static
    {
        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(): static
    {
        $str = clone $this;
        $str->string = mb_strtoupper($str->string, 'UTF-8');

        return $str;
    }

    public function width(bool $ignoreAnsiDecoration = true): int
    {
        $width = 0;
        $s = str_replace(["\x00", "\x05", "\x07"], '', $this->string);

        if (str_contains($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;
    }

    private function pad(int $len, self $pad, int $type): static
    {
        $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;
        $lastChar = null;
        $lastWidth = null;

        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 (0xFE0F === $codePoint) {
                if (\PCRE_VERSION_MAJOR < 10 || \PCRE_VERSION_MAJOR === 10 && \PCRE_VERSION_MINOR < 40) {
                    $regex = '/\p{So}/u';
                } else {
                    $regex = '/\p{Emoji}/u';
                }
                if (null !== $lastChar && 1 === $lastWidth && preg_match($regex, $lastChar)) {
                    ++$width;
                    $lastWidth = 2;
                }

                continue;
            }

            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;
                    }
                }
            }

            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;
                        $lastChar = $c;
                        $lastWidth = 2;

                        continue 2;
                    }
                }
            }

            ++$width;
            $lastChar = $c;
            $lastWidth = 1;
        }

        return $width;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <p@tchwork.com>
 */
class LazyString implements \Stringable, \JsonSerializable
{
    private \Closure|string $value;

    /**
     * @param callable|array $callback A callable or a [Closure, method] lazy-callable
     */
    public static function fromCallable(callable|array $callback, mixed ...$arguments): static
    {
        if (\is_array($callback) && !\is_callable($callback) && !(($callback[0] ?? null) 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__, '['.implode(', ', array_map('get_debug_type', $callback)).']'));
        }

        $lazyString = new static();
        $lazyString->value = static function () use (&$callback, &$arguments): string {
            static $value;

            if (null !== $arguments) {
                if (!\is_callable($callback)) {
                    $callback[0] = $callback[0]();
                    $callback[1] ??= '__invoke';
                }
                $value = $callback(...$arguments);
                $callback = !\is_scalar($value) && !$value instanceof \Stringable ? self::getPrettyName($callback) : 'callable';
                $arguments = null;
            }

            return $value ?? '';
        };

        return $lazyString;
    }

    public static function fromStringable(string|int|float|bool|\Stringable $value): static
    {
        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(mixed $value): bool
    {
        return \is_string($value) || $value instanceof \Stringable || \is_scalar($value);
    }

    /**
     * Casts scalars and stringable objects to strings.
     *
     * @throws \TypeError When the provided value is not stringable
     */
    final public static function resolve(\Stringable|string|int|float|bool $value): string
    {
        return $value;
    }

    public function __toString(): string
    {
        if (\is_string($this->value)) {
            return $this->value;
        }

        try {
            return $this->value = ($this->value)();
        } catch (\Throwable $e) {
            if (\TypeError::class === $e::class && __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));
            }

            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 (str_contains($r->name, '{closure') || !$class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) {
                return $r->name;
            }

            $class = $class->name;
            $method = $r->name;
        } else {
            $class = get_debug_type($callback);
            $method = '__invoke';
        }

        return $class.'::'.$method;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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';

    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];
    }

    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);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 vowel
        // Fourth entry: Whether the suffix may succeed a consonant
        // Fifth entry: singular suffix, normal

        // insignias (insigne), insignia (insigne)
        ['saingisni', 9, true, true, 'insigne'],
        ['aingisni', 8, true, true, 'insigne'],

        // passersby (passerby)
        ['ybsressap', 9, true, true, 'passerby'],

        // nodes (node)
        ['sedon', 5, true, true, 'node'],

        // bacteria (bacterium)
        ['airetcab', 8, true, true, 'bacterium'],

        // issues (issue)
        ['seussi', 6, true, true, 'issue'],

        // corpora (corpus)
        ['aroproc', 7, true, true, 'corpus'],

        // criteria (criterion)
        ['airetirc', 8, true, true, 'criterion'],

        // curricula (curriculum)
        ['alucirruc', 9, true, true, 'curriculum'],

        // quora (quorum)
        ['arouq', 5, true, true, 'quorum'],

        // genera (genus)
        ['areneg', 6, true, true, 'genus'],

        // media (medium)
        ['aidem', 5, true, true, 'medium'],

        // memoranda (memorandum)
        ['adnaromem', 9, true, true, 'memorandum'],

        // phenomena (phenomenon)
        ['anemonehp', 9, true, true, 'phenomenon'],

        // strata (stratum)
        ['atarts', 6, true, true, 'stratum'],

        // 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']],

        // codes (code)
        ['sedoc', 5, false, true, 'code'],

        // selfies (selfie)
        ['seifles', 7, true, true, 'selfie'],

        // zombies (zombie)
        ['seibmoz', 7, true, true, 'zombie'],

        // movies (movie)
        ['seivom', 6, true, true, 'movie'],

        // names (name)
        ['seman', 5, true, false, 'name'],

        // 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'],

        // statuses (status)
        ['sesutats', 8, true, true, 'status'],

        // article (articles), ancle (ancles)
        ['sel', 3, true, true, 'le'],

        // 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'],

        // outages (outage) - specific fix to avoid 'outag'
        ['segatuo', 7, true, true, 'outage'],

        // roses (rose), garages (garage), cassettes (cassette),
        // waltzes (waltz), heroes (hero), bushes (bush), arches (arch),
        // shoes (shoe)
        ['se', 2, true, true, ['', 'e']],

        // status (status)
        ['sutats', 6, true, true, 'status'],

        // 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 vowel
        // Fourth entry: Whether the suffix may succeed a consonant
        // Fifth entry: plural suffix, normal

        // passerby (passersby)
        ['ybressap', 8, true, true, 'passersby'],

        // insigne (insignia, insignias)
        ['engisni', 7, true, true, ['insignia', 'insignias']],

        // nodes (node)
        ['edon', 4, true, true, 'nodes'],

        // axes (axis)
        ['sixa', 4, false, false, 'axes'],

        // 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'],

        // albums (album)
        ['mubla', 5, true, true, 'albums'],

        // quorums (quorum)
        ['murouq', 6, true, true, ['quora', 'quorums']],

        // bacteria (bacterium), curricula (curriculum), media (medium), memoranda (memorandum), phenomena (phenomenon), strata (stratum)
        ['mu', 2, true, true, 'a'],

        // men (man), women (woman)
        ['nam', 3, true, true, 'men'],

        // people (person)
        ['nosrep', 6, true, true, ['persons', 'people']],

        // criteria (criterion)
        ['noiretirc', 9, true, true, 'criteria'],

        // phenomena (phenomenon)
        ['nonemonehp', 10, true, true, 'phenomena'],

        // echoes (echo)
        ['ohce', 4, true, true, 'echoes'],

        // heroes (hero)
        ['oreh', 4, true, true, 'heroes'],

        // atlases (atlas)
        ['salta', 5, true, true, 'atlases'],

        // aliases (alias)
        ['saila', 5, true, true, 'aliases'],

        // 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'],

        // hippocampi (hippocampus)
        ['supmacoppih', 11, false, false, 'hippocampi'],

        // campuses (campus)
        ['sup', 3, true, true, 'puses'],

        // status (status)
        ['sutats', 6, true, true, ['status', 'statuses']],

        // conspectuses (conspectus), prospectuses (prospectus)
        ['sutcep', 6, true, true, 'pectuses'],

        // nexuses (nexus)
        ['suxen', 5, false, false, 'nexuses'],

        // 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']],

        // fax (faxes, faxxes)
        ['xaf', 3, true, true, ['faxes', 'faxxes']],

        // boxes (box)
        ['xo', 2, false, true, 'oxes'],

        // indexes (index), matrixes (matrix), appendices (appendix)
        ['x', 1, true, false, ['ces', 'xes']],

        // 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',

        // equipment
        'tnempiuqe',

        // feedback
        'kcabdeef',

        // fish
        'hsif',

        // health
        'htlaeh',

        // history
        'yrotsih',

        // info
        'ofni',

        // information
        'noitamrofni',

        // money
        'yenom',

        // moose
        'esoom',

        // series
        'seires',

        // sheep
        'peehs',

        // species
        'seiceps',

        // traffic
        'ciffart',

        // aircraft
        'tfarcria',

        // hardware
        'erawdrah',
    ];

    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) {
                        $nextIsVowel = str_contains('aeiou', $lowerPluralRev[$j]);

                        if (!$map[2] && $nextIsVowel) {
                            // suffix may not succeed a vowel but next char is one
                            break;
                        }

                        if (!$map[3] && !$nextIsVowel) {
                            // 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];
    }

    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) {
                        $nextIsVowel = str_contains('aeiou', $lowerSingularRev[$j]);

                        if (!$map[2] && $nextIsVowel) {
                            // suffix may not succeed a vowel but next char is one
                            break;
                        }

                        if (!$map[3] && !$nextIsVowel) {
                            // 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'];
    }
}
Copyright (c) 2018-present Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
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.
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Polyfill\Ctype;

/**
 * Ctype implementation through regex.
 *
 * @internal
 *
 * @author Gert de Pagter <BackEndTea@gmail.com>
 */
final class Ctype
{
    /**
     * Returns TRUE if every character in text is either a letter or a digit, FALSE otherwise.
     *
     * @see https://php.net/ctype-alnum
     *
     * @param mixed $text
     *
     * @return bool
     */
    public static function ctype_alnum($text)
    {
        $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);

        return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z0-9]/', $text);
    }

    /**
     * Returns TRUE if every character in text is a letter, FALSE otherwise.
     *
     * @see https://php.net/ctype-alpha
     *
     * @param mixed $text
     *
     * @return bool
     */
    public static function ctype_alpha($text)
    {
        $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);

        return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z]/', $text);
    }

    /**
     * Returns TRUE if every character in text is a control character from the current locale, FALSE otherwise.
     *
     * @see https://php.net/ctype-cntrl
     *
     * @param mixed $text
     *
     * @return bool
     */
    public static function ctype_cntrl($text)
    {
        $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);

        return \is_string($text) && '' !== $text && !preg_match('/[^\x00-\x1f\x7f]/', $text);
    }

    /**
     * Returns TRUE if every character in the string text is a decimal digit, FALSE otherwise.
     *
     * @see https://php.net/ctype-digit
     *
     * @param mixed $text
     *
     * @return bool
     */
    public static function ctype_digit($text)
    {
        $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);

        return \is_string($text) && '' !== $text && !preg_match('/[^0-9]/', $text);
    }

    /**
     * Returns TRUE if every character in text is printable and actually creates visible output (no white space), FALSE otherwise.
     *
     * @see https://php.net/ctype-graph
     *
     * @param mixed $text
     *
     * @return bool
     */
    public static function ctype_graph($text)
    {
        $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);

        return \is_string($text) && '' !== $text && !preg_match('/[^!-~]/', $text);
    }

    /**
     * Returns TRUE if every character in text is a lowercase letter.
     *
     * @see https://php.net/ctype-lower
     *
     * @param mixed $text
     *
     * @return bool
     */
    public static function ctype_lower($text)
    {
        $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);

        return \is_string($text) && '' !== $text && !preg_match('/[^a-z]/', $text);
    }

    /**
     * Returns TRUE if every character in text will actually create output (including blanks). Returns FALSE if text contains control characters or characters that do not have any output or control function at all.
     *
     * @see https://php.net/ctype-print
     *
     * @param mixed $text
     *
     * @return bool
     */
    public static function ctype_print($text)
    {
        $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);

        return \is_string($text) && '' !== $text && !preg_match('/[^ -~]/', $text);
    }

    /**
     * Returns TRUE if every character in text is printable, but neither letter, digit or blank, FALSE otherwise.
     *
     * @see https://php.net/ctype-punct
     *
     * @param mixed $text
     *
     * @return bool
     */
    public static function ctype_punct($text)
    {
        $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);

        return \is_string($text) && '' !== $text && !preg_match('/[^!-\/\:-@\[-`\{-~]/', $text);
    }

    /**
     * Returns TRUE if every character in text creates some sort of white space, FALSE otherwise. Besides the blank character this also includes tab, vertical tab, line feed, carriage return and form feed characters.
     *
     * @see https://php.net/ctype-space
     *
     * @param mixed $text
     *
     * @return bool
     */
    public static function ctype_space($text)
    {
        $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);

        return \is_string($text) && '' !== $text && !preg_match('/[^\s]/', $text);
    }

    /**
     * Returns TRUE if every character in text is an uppercase letter.
     *
     * @see https://php.net/ctype-upper
     *
     * @param mixed $text
     *
     * @return bool
     */
    public static function ctype_upper($text)
    {
        $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);

        return \is_string($text) && '' !== $text && !preg_match('/[^A-Z]/', $text);
    }

    /**
     * Returns TRUE if every character in text is a hexadecimal 'digit', that is a decimal digit or a character from [A-Fa-f] , FALSE otherwise.
     *
     * @see https://php.net/ctype-xdigit
     *
     * @param mixed $text
     *
     * @return bool
     */
    public static function ctype_xdigit($text)
    {
        $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);

        return \is_string($text) && '' !== $text && !preg_match('/[^A-Fa-f0-9]/', $text);
    }

    /**
     * Converts integers to their char versions according to normal ctype behaviour, if needed.
     *
     * If an integer between -128 and 255 inclusive is provided,
     * it is interpreted as the ASCII value of a single character
     * (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 mixed  $int
     * @param string $function
     *
     * @return mixed
     */
    private static function convert_int_to_char_for_ctype($int, $function)
    {
        if (\PHP_VERSION_ID >= 80100 && !\is_string($int)) {
            @trigger_error($function.'(): Argument of type '.get_debug_type($int).' will be interpreted as string in the future', \E_USER_DEPRECATED);
        }

        if (!\is_int($int)) {
            return $int;
        }

        if ($int < -128 || $int > 255) {
            return (string) $int;
        }

        if ($int < 0) {
            $int += 256;
        }

        return \chr($int);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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); }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 (\PHP_VERSION_ID >= 80000) {
    return require __DIR__.'/bootstrap80.php';
}

if (!function_exists('ctype_alnum')) {
    function ctype_alnum($text) { return p\Ctype::ctype_alnum($text); }
}
if (!function_exists('ctype_alpha')) {
    function ctype_alpha($text) { return p\Ctype::ctype_alpha($text); }
}
if (!function_exists('ctype_cntrl')) {
    function ctype_cntrl($text) { return p\Ctype::ctype_cntrl($text); }
}
if (!function_exists('ctype_digit')) {
    function ctype_digit($text) { return p\Ctype::ctype_digit($text); }
}
if (!function_exists('ctype_graph')) {
    function ctype_graph($text) { return p\Ctype::ctype_graph($text); }
}
if (!function_exists('ctype_lower')) {
    function ctype_lower($text) { return p\Ctype::ctype_lower($text); }
}
if (!function_exists('ctype_print')) {
    function ctype_print($text) { return p\Ctype::ctype_print($text); }
}
if (!function_exists('ctype_punct')) {
    function ctype_punct($text) { return p\Ctype::ctype_punct($text); }
}
if (!function_exists('ctype_space')) {
    function ctype_space($text) { return p\Ctype::ctype_space($text); }
}
if (!function_exists('ctype_upper')) {
    function ctype_upper($text) { return p\Ctype::ctype_upper($text); }
}
if (!function_exists('ctype_xdigit')) {
    function ctype_xdigit($text) { return p\Ctype::ctype_xdigit($text); }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Contracts\Service\Test;

/**
 * @deprecated since PHPUnit 9.6
 */
class ServiceLocatorTest extends ServiceLocatorTestCase
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Contracts\Service\Test;

use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use Symfony\Contracts\Service\ServiceLocatorTrait;

abstract class ServiceLocatorTestCase extends TestCase
{
    /**
     * @param array<string, callable> $factories
     */
    protected function getServiceLocator(array $factories): ContainerInterface
    {
        return new class($factories) implements ContainerInterface {
            use ServiceLocatorTrait;
        };
    }

    public function testHas()
    {
        $locator = $this->getServiceLocator([
            'foo' => static fn () => 'bar',
            'bar' => static fn () => 'baz',
            static fn () => 'dummy',
        ]);

        $this->assertTrue($locator->has('foo'));
        $this->assertTrue($locator->has('bar'));
        $this->assertFalse($locator->has('dummy'));
    }

    public function testGet()
    {
        $locator = $this->getServiceLocator([
            'foo' => static fn () => 'bar',
            'bar' => static fn () => 'baz',
        ]);

        $this->assertSame('bar', $locator->get('foo'));
        $this->assertSame('baz', $locator->get('bar'));
    }

    public function testGetDoesNotMemoize()
    {
        $i = 0;
        $locator = $this->getServiceLocator([
            'foo' => static function () use (&$i) {
                ++$i;

                return 'bar';
            },
        ]);

        $this->assertSame('bar', $locator->get('foo'));
        $this->assertSame('bar', $locator->get('foo'));
        $this->assertSame(2, $i);
    }

    public function testThrowsOnUndefinedInternalService()
    {
        $locator = $this->getServiceLocator([
            'foo' => static function () use (&$locator) { return $locator->get('bar'); },
        ]);

        $this->expectException(NotFoundExceptionInterface::class);
        $this->expectExceptionMessage('The service "foo" has a dependency on a non-existent service "bar". This locator only knows about the "foo" service.');

        $locator->get('foo');
    }

    public function testThrowsOnCircularReference()
    {
        $locator = $this->getServiceLocator([
            'foo' => static function () use (&$locator) { return $locator->get('bar'); },
            'bar' => static function () use (&$locator) { return $locator->get('baz'); },
            'baz' => static function () use (&$locator) { return $locator->get('bar'); },
        ]);

        $this->expectException(ContainerExceptionInterface::class);
        $this->expectExceptionMessage('Circular reference detected for service "bar", path: "bar -> baz -> bar".');

        $locator->get('foo');
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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\Required;
use Symfony\Contracts\Service\Attribute\SubscribedService;

trigger_deprecation('symfony/contracts', 'v3.5', '"%s" is deprecated, use "ServiceMethodsSubscriberTrait" instead.', ServiceSubscriberTrait::class);

/**
 * Implementation of ServiceSubscriberInterface that determines subscribed services
 * from methods that have the #[SubscribedService] attribute.
 *
 * Service ids are available as "ClassName::methodName" so that the implementation
 * of subscriber methods can be just `return $this->container->get(__METHOD__);`.
 *
 * @property ContainerInterface $container
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 *
 * @deprecated since symfony/contracts v3.5, use ServiceMethodsSubscriberTrait instead
 */
trait ServiceSubscriberTrait
{
    public static function getSubscribedServices(): array
    {
        $services = method_exists(get_parent_class(self::class) ?: '', __FUNCTION__) ? parent::getSubscribedServices() : [];

        foreach ((new \ReflectionClass(self::class))->getMethods() as $method) {
            if (self::class !== $method->class) {
                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));
            }

            /** @var SubscribedService $attribute */
            $attribute = $attribute->newInstance();
            $attribute->key ??= self::class.'::'.$method->name;
            $attribute->type ??= $returnType instanceof \ReflectionNamedType ? $returnType->getName() : (string) $returnType;
            $attribute->nullable = $attribute->nullable ?: $returnType->allowsNull();

            if ($attribute->attributes) {
                $services[] = $attribute;
            } else {
                $services[$attribute->key] = ($attribute->nullable ? '?' : '').$attribute->type;
            }
        }

        return $services;
    }

    #[Required]
    public function setContainer(ContainerInterface $container): ?ContainerInterface
    {
        $ret = null;
        if (method_exists(get_parent_class(self::class) ?: '', __FUNCTION__)) {
            $ret = parent::setContainer($container);
        }

        $this->container = $container;

        return $ret;
    }
}
Copyright (c) 2018-present Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
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.
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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\Required;
use Symfony\Contracts\Service\Attribute\SubscribedService;

/**
 * Implementation of ServiceSubscriberInterface that determines subscribed services
 * from methods that have the #[SubscribedService] attribute.
 *
 * Service ids are available as "ClassName::methodName" so that the implementation
 * of subscriber methods can be just `return $this->container->get(__METHOD__);`.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
trait ServiceMethodsSubscriberTrait
{
    protected ContainerInterface $container;

    public static function getSubscribedServices(): array
    {
        $services = method_exists(get_parent_class(self::class) ?: '', __FUNCTION__) ? parent::getSubscribedServices() : [];

        $reflectors = [
            ...(new \ReflectionClass(self::class))->getMethods(),
            ...(new \ReflectionClass(self::class))->getProperties(),
        ];

        foreach ($reflectors as $reflector) {
            if (self::class !== $reflector->class) {
                continue;
            }

            if (!$reflectionAttribute = $reflector->getAttributes(SubscribedService::class)[0] ?? null) {
                continue;
            }

            if ($reflector instanceof \ReflectionMethod) {
                if ($reflector->isStatic() || $reflector->isAbstract() || $reflector->isGenerator() || $reflector->isInternal() || $reflector->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, $reflector->name));
                }

                if (!$autowireType = $reflector->getReturnType()) {
                    throw new \LogicException(\sprintf('Cannot use "%s" on methods without a return type in "%s::%s()".', SubscribedService::class, $reflector->name, self::class));
                }

                $defaultKey = self::class.'::'.$reflector->name;
            } elseif ($reflector instanceof \ReflectionProperty) {
                if (\PHP_VERSION_ID < 80400 || !$reflector->hasHook(\PropertyHookType::Get)) {
                    throw new \LogicException(\sprintf('Cannot use "%s" on property "%s::$%s" (can only be used on properties with a get hook).', SubscribedService::class, self::class, $reflector->name));
                }

                if (!$autowireType = $reflector->getType()) {
                    throw new \LogicException(\sprintf('Cannot use "%s" on properties without a type in "%s::$%s".', SubscribedService::class, $reflector->name, self::class));
                }

                $defaultKey = self::class.'::$'.$reflector->name.'::get';
            } else {
                throw new \LogicException('Unexpected reflector: '.get_debug_type($reflector));
            }

            /* @var SubscribedService $attribute */
            $attribute = $reflectionAttribute->newInstance();
            $attribute->key ??= $defaultKey;
            $attribute->type ??= $autowireType instanceof \ReflectionNamedType ? $autowireType->getName() : (string) $autowireType;
            $attribute->nullable = $attribute->nullable ?: $autowireType->allowsNull();

            if ($attribute->attributes) {
                $services[] = $attribute;
            } else {
                $services[$attribute->key] = ($attribute->nullable ? '?' : '').$attribute->type;
            }
        }

        return $services;
    }

    #[Required]
    public function setContainer(ContainerInterface $container): ?ContainerInterface
    {
        $ret = null;
        if (method_exists(get_parent_class(self::class) ?: '', __FUNCTION__)) {
            $ret = parent::setContainer($container);
        }

        $this->container = $container;

        return $ret;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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
{
    /**
     * @return void
     */
    public function reset();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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;

/**
 * Implemented by objects that expose a service container.
 */
interface ContainerAwareInterface
{
    public function getContainer(): ContainerInterface;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Contracts\Service;

/**
 * A ServiceProviderInterface that is also countable and iterable.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 *
 * @template-covariant T of mixed
 *
 * @extends ServiceProviderInterface<T>
 * @extends \IteratorAggregate<string, T>
 */
interface ServiceCollectionInterface extends ServiceProviderInterface, \Countable, \IteratorAggregate
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Contracts\Service;

use Symfony\Contracts\Service\Attribute\SubscribedService;

/**
 * A ServiceSubscriber exposes its dependencies via the static {@link getSubscribedServices} method.
 *
 * The getSubscribedServices method returns an array of service types required by such instances,
 * optionally keyed by the service names used internally. Service types that start with an interrogation
 * mark "?" are optional, while the other ones are mandatory service dependencies.
 *
 * The injected service locators SHOULD NOT allow access to any other services not specified by the method.
 *
 * It is expected that ServiceSubscriber instances consume PSR-11-based service locators internally.
 * This interface does not dictate any injection method for these service locators, although constructor
 * injection is recommended.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
interface ServiceSubscriberInterface
{
    /**
     * Returns an array of service types (or {@see SubscribedService} objects) required
     * by such instances, optionally keyed by the service names used internally.
     *
     * For mandatory dependencies:
     *
     *  * ['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']
     *
     * additionally, an array of {@see SubscribedService}'s can be returned:
     *
     *  * [new SubscribedService('logger', Psr\Log\LoggerInterface::class)]
     *  * [new SubscribedService(type: Psr\Log\LoggerInterface::class, nullable: true)]
     *  * [new SubscribedService('http_client', HttpClientInterface::class, attributes: new Target('githubApi'))]
     *
     * @return string[]|SubscribedService[] The required service types, optionally keyed by service names
     */
    public static function getSubscribedServices(): array;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <p@tchwork.com>
 * @author Mateusz Sip <mateusz.sip@gmail.com>
 *
 * @template-covariant T of mixed
 */
interface ServiceProviderInterface extends ContainerInterface
{
    /**
     * @return T
     */
    public function get(string $id): mixed;

    public function has(string $id): bool;

    /**
     * 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 array<string, string> The provided service types, keyed by service names
     */
    public function getProvidedServices(): array;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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\ServiceMethodsSubscriberTrait;
use Symfony\Contracts\Service\ServiceSubscriberInterface;

/**
 * For use as the return value for {@see ServiceSubscriberInterface}.
 *
 * @example new SubscribedService('http_client', HttpClientInterface::class, false, new Target('githubApi'))
 *
 * Use with {@see ServiceMethodsSubscriberTrait} to mark a method's return type
 * as a subscribed service.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)]
final class SubscribedService
{
    /** @var object[] */
    public array $attributes;

    /**
     * @param string|null     $key        The key to use for the service
     * @param string|null     $type       The service type (a class name, "string", "iterable", etc.)
     * @param bool            $nullable   Whether the service is optional
     * @param object|object[] $attributes One or more dependency injection attributes to use
     */
    public function __construct(
        public ?string $key = null,
        public ?string $type = null,
        public bool $nullable = false,
        array|object $attributes = [],
    ) {
        $this->attributes = \is_array($attributes) ? $attributes : [$attributes];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <me@derrabus.de>
 */
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)]
final class Required
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <robin.chalas@gmail.com>
 * @author Nicolas Grekas <p@tchwork.com>
 */
trait ServiceLocatorTrait
{
    private array $loading = [];
    private array $providedTypes;

    /**
     * @param array<string, callable> $factories
     */
    public function __construct(
        private array $factories,
    ) {
    }

    public function has(string $id): bool
    {
        return isset($this->factories[$id]);
    }

    public function get(string $id): mixed
    {
        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]);
        }
    }

    public function getProvidedServices(): array
    {
        if (!isset($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 {
        };
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <p@tchwork.com>
     */
    function trigger_deprecation(string $package, string $version, string $message, mixed ...$args): void
    {
        @trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED);
    }
}
Copyright (c) 2020-present Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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.
 * On Windows, backward slashes are normalized to forward slashes. On UNIX,
 * backward slashes are treated as valid filename characters and are not replaced.
 * All methods return normalized parts with no excess "." and ".." segments.
 *
 * @author Bernhard Schussek <bschussek@gmail.com>
 * @author Thomas Schulz <mail@king2500.net>
 * @author Théo Fidry <theo.fidry@gmail.com>
 */
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<string, string>
     */
    private static array $buffer = [];

    private static int $bufferSize = 0;

    /**
     * Canonicalizes the given path.
     *
     * During normalization, all "." and ".." segments are removed as far as
     * possible. ".." segments at the beginning of relative paths are not removed.
     * On Windows, backward slashes are replaced by forward slashes ("/").
     *
     * ```php
     * 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().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.
     *
     * On Windows, backward slashes are replaced by forward slashes ("/").
     * On UNIX, backward slashes are preserved as they are valid filename characters.
     * 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.
     */
    public static function normalize(string $path): string
    {
        return '\\' === \DIRECTORY_SEPARATOR ? str_replace('\\', '/', $path) : $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 = strpos($path, '://')) {
            $scheme = substr($path, 0, $schemeSeparatorPosition + 3);
            $path = 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.substr($path, 0, 3);
        }

        return $scheme.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];

        if ('/' === $firstCharacter) {
            return $scheme.'/';
        }

        if ('\\' !== \DIRECTORY_SEPARATOR) {
            return '';
        }

        if ('\\' === $firstCharacter) {
            return $scheme.'/';
        }

        $length = \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 (str_ends_with($path, '/')) {
            return $path;
        }

        // No actual extension in path
        if (!$actualExtension) {
            return $path.(str_ends_with($path, '.') ? '' : '.').$extension;
        }

        return substr($path, 0, -\strlen($actualExtension)).$extension;
    }

    /**
     * Returns whether the given path is absolute.
     */
    public static function isAbsolute(string $path): bool
    {
        if ('' === $path) {
            return false;
        }

        // URLs and stream wrappers are considered absolute
        if (str_contains($path, '://') && null !== parse_url($path, \PHP_URL_SCHEME)) {
            return true;
        }

        if ('/' === $path[0]) {
            return true;
        }

        if ('\\' !== \DIRECTORY_SEPARATOR) {
            return false;
        }

        if ('\\' === $path[0]) {
            return true;
        }

        // Windows root
        if (\strlen($path) > 1 && ctype_alpha($path[0]) && ':' === $path[1]) {
            // Special case: "C:"
            if (2 === \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 = strpos($basePath, '://')) {
            $scheme = substr($basePath, 0, $schemeSeparatorPosition + 3);
            $basePath = substr($basePath, $schemeSeparatorPosition + 3);
        } else {
            $scheme = '';
        }

        return $scheme.self::canonicalize(rtrim($basePath, '/'.\DIRECTORY_SEPARATOR).'/'.$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, './'.\DIRECTORY_SEPARATOR);
            }

            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 && !str_contains($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 (str_starts_with($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 = str_contains($path, '://');
                continue;
            }

            // Only add slash if previous part didn't end with '/' or '\' (Windows)
            if ('/' !== substr($finalPath, -1) && \DIRECTORY_SEPARATOR !== 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 str_starts_with($ofPath.'/', rtrim($basePath, '/').'/');
    }

    /**
     * @return 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 = strpos($path, '://')) {
            $root = substr($path, 0, $schemeSeparatorPosition + 3);
            $path = substr($path, $schemeSeparatorPosition + 3);
        } else {
            $root = '';
        }

        $length = \strlen($path);

        // Remove and remember root directory
        if (str_starts_with($path, '/')) {
            $root .= '/';
            $path = $length > 1 ? substr($path, 1) : '';
        } elseif ('\\' === \DIRECTORY_SEPARATOR && $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 .= substr($path, 0, 3);
                $path = $length > 3 ? substr($path, 3) : '';
            }
        }

        return [$root, $path];
    }

    private static function toLower(string $string): string
    {
        if (false !== $encoding = mb_detect_encoding($string, null, true)) {
            return mb_strtolower($string, $encoding);
        }

        return strtolower($string);
    }

    private function __construct()
    {
    }
}
Copyright (c) 2004-present Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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\FileNotFoundException;
use Symfony\Component\Filesystem\Exception\InvalidArgumentException;
use Symfony\Component\Filesystem\Exception\IOException;

/**
 * Provides basic utility to manipulate the file system.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Filesystem
{
    private static ?string $lastError = null;

    /**
     * Copies a file.
     *
     * If the target file is older than the origin file, it's always overwritten.
     * If the target file is newer, it is overwritten only when the
     * $overwriteNewerFiles option is set to true.
     *
     * @return void
     *
     * @throws FileNotFoundException When originFile doesn't exist
     * @throws IOException           When copy fails
     */
    public function copy(string $originFile, string $targetFile, bool $overwriteNewerFiles = false)
    {
        $originIsLocal = stream_is_local($originFile) || 0 === stripos($originFile, 'file://');
        if ($originIsLocal && !is_file($originFile)) {
            throw new FileNotFoundException(\sprintf('Failed to copy "%s" because file does not exist.', $originFile), 0, null, $originFile);
        }

        $this->mkdir(\dirname($targetFile));

        $doCopy = true;
        if (!$overwriteNewerFiles && !parse_url($originFile, \PHP_URL_HOST) && is_file($targetFile)) {
            $doCopy = filemtime($originFile) > filemtime($targetFile);
        }

        if ($doCopy) {
            // https://bugs.php.net/64634
            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 (!$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);
            fclose($source);
            fclose($target);
            unset($source, $target);

            if (!is_file($targetFile)) {
                throw new IOException(\sprintf('Failed to copy "%s" to "%s".', $originFile, $targetFile), 0, null, $originFile);
            }

            if ($originIsLocal) {
                // Like `cp`, preserve executable permission bits
                self::box('chmod', $targetFile, fileperms($targetFile) | (fileperms($originFile) & 0o111));

                // Like `cp`, preserve the file modification time
                self::box('touch', $targetFile, filemtime($originFile));

                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);
                }
            }
        }
    }

    /**
     * Creates a directory recursively.
     *
     * @return void
     *
     * @throws IOException On any directory creation failure
     */
    public function mkdir(string|iterable $dirs, int $mode = 0o777)
    {
        foreach ($this->toIterable($dirs) as $dir) {
            if (is_dir($dir)) {
                continue;
            }

            if (!self::box('mkdir', $dir, $mode, true) && !is_dir($dir)) {
                throw new IOException(\sprintf('Failed to create "%s": ', $dir).self::$lastError, 0, null, $dir);
            }
        }
    }

    /**
     * Checks the existence of files or directories.
     */
    public function exists(string|iterable $files): bool
    {
        $maxPathLength = \PHP_MAXPATHLEN - 2;

        foreach ($this->toIterable($files) as $file) {
            if (\strlen($file) > $maxPathLength) {
                throw new IOException(\sprintf('Could not check if file exist because path length exceeds %d characters.', $maxPathLength), 0, null, $file);
            }

            if (!file_exists($file)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Sets access and modification time of file.
     *
     * @param int|null $time  The touch time as a Unix timestamp, if not supplied the current system time is used
     * @param int|null $atime The access time as a Unix timestamp, if not supplied the current system time is used
     *
     * @return void
     *
     * @throws IOException When touch fails
     */
    public function touch(string|iterable $files, ?int $time = null, ?int $atime = null)
    {
        foreach ($this->toIterable($files) as $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);
            }
        }
    }

    /**
     * Removes files or directories.
     *
     * @return void
     *
     * @throws IOException When removal fails
     */
    public function remove(string|iterable $files)
    {
        if ($files instanceof \Traversable) {
            $files = iterator_to_array($files, false);
        } 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)) {
                // See https://bugs.php.net/52176
                if (!(self::box('unlink', $file) || '\\' !== \DIRECTORY_SEPARATOR || self::box('rmdir', $file)) && file_exists($file)) {
                    throw new IOException(\sprintf('Failed to remove symlink "%s": ', $file).self::$lastError);
                }
            } elseif (is_dir($file)) {
                if (!$isRecursive) {
                    $tmpName = \dirname(realpath($file)).'/.!'.strrev(strtr(base64_encode(random_bytes(2)), '/=', '-!'));

                    if (file_exists($tmpName)) {
                        try {
                            self::doRemove([$tmpName], true);
                        } catch (IOException) {
                        }
                    }

                    if (!file_exists($tmpName) && self::box('rename', $file, $tmpName)) {
                        $origFile = $file;
                        $file = $tmpName;
                    } else {
                        $origFile = null;
                    }
                }

                $filesystemIterator = new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS);
                self::doRemove(iterator_to_array($filesystemIterator, 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) && ((self::$lastError && str_contains(self::$lastError, 'Permission denied')) || file_exists($file))) {
                throw new IOException(\sprintf('Failed to remove file "%s": ', $file).self::$lastError);
            }
        }
    }

    /**
     * Change mode for an array of files or directories.
     *
     * @param int  $mode      The new mode (octal)
     * @param int  $umask     The mode mask (octal)
     * @param bool $recursive Whether change the mod recursively or not
     *
     * @return void
     *
     * @throws IOException When the change fails
     */
    public function chmod(string|iterable $files, int $mode, int $umask = 0o000, bool $recursive = false)
    {
        foreach ($this->toIterable($files) as $file) {
            if (!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);
            }
        }
    }

    /**
     * Change the owner of an array of files or directories.
     *
     * This method always throws on Windows, as the underlying PHP function is not supported.
     *
     * @see https://php.net/chown
     *
     * @param string|int $user      A user name or number
     * @param bool       $recursive Whether change the owner recursively or not
     *
     * @return void
     *
     * @throws IOException When the change fails
     */
    public function chown(string|iterable $files, string|int $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 (!self::box('lchown', $file, $user)) {
                    throw new IOException(\sprintf('Failed to chown file "%s": ', $file).self::$lastError, 0, null, $file);
                }
            } else {
                if (!self::box('chown', $file, $user)) {
                    throw new IOException(\sprintf('Failed to chown file "%s": ', $file).self::$lastError, 0, null, $file);
                }
            }
        }
    }

    /**
     * Change the group of an array of files or directories.
     *
     * This method always throws on Windows, as the underlying PHP function is not supported.
     *
     * @see https://php.net/chgrp
     *
     * @param string|int $group     A group name or number
     * @param bool       $recursive Whether change the group recursively or not
     *
     * @return void
     *
     * @throws IOException When the change fails
     */
    public function chgrp(string|iterable $files, string|int $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 (!self::box('lchgrp', $file, $group)) {
                    throw new IOException(\sprintf('Failed to chgrp file "%s": ', $file).self::$lastError, 0, null, $file);
                }
            } else {
                if (!self::box('chgrp', $file, $group)) {
                    throw new IOException(\sprintf('Failed to chgrp file "%s": ', $file).self::$lastError, 0, null, $file);
                }
            }
        }
    }

    /**
     * Renames a file or a directory.
     *
     * @return void
     *
     * @throws IOException When target file or directory already exists
     * @throws IOException When origin cannot be renamed
     */
    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 (!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]);
                $this->remove($origin);

                return;
            }
            throw new IOException(\sprintf('Cannot rename "%s" to "%s": ', $origin, $target).self::$lastError, 0, null, $target);
        }
    }

    /**
     * Tells whether a file exists and is readable.
     *
     * @throws IOException When windows path is longer than 258 characters
     */
    private function isReadable(string $filename): bool
    {
        $maxPathLength = \PHP_MAXPATHLEN - 2;

        if (\strlen($filename) > $maxPathLength) {
            throw new IOException(\sprintf('Could not check if file is readable because path length exceeds %d characters.', $maxPathLength), 0, null, $filename);
        }

        return is_readable($filename);
    }

    /**
     * Creates a symbolic link or copy a directory.
     *
     * @return void
     *
     * @throws IOException When symlink fails
     */
    public function symlink(string $originDir, string $targetDir, bool $copyOnWindows = false)
    {
        self::assertFunctionExists('symlink');

        if ('\\' === \DIRECTORY_SEPARATOR) {
            $originDir = strtr($originDir, '/', '\\');
            $targetDir = strtr($targetDir, '/', '\\');

            if ($copyOnWindows) {
                $this->mirror($originDir, $targetDir);

                return;
            }
        }

        $this->mkdir(\dirname($targetDir));

        if (is_link($targetDir)) {
            if (readlink($targetDir) === $originDir) {
                return;
            }
            $this->remove($targetDir);
        }

        if (!self::box('symlink', $originDir, $targetDir)) {
            $this->linkException($originDir, $targetDir, 'symbolic');
        }
    }

    /**
     * Creates a hard link, or several hard links to a file.
     *
     * @param string|string[] $targetFiles The target file(s)
     *
     * @return void
     *
     * @throws FileNotFoundException When original file is missing or not a file
     * @throws IOException           When link fails, including if link already exists
     */
    public function hardlink(string $originFile, string|iterable $targetFiles)
    {
        self::assertFunctionExists('link');

        if (!$this->exists($originFile)) {
            throw new FileNotFoundException(null, 0, null, $originFile);
        }

        if (!is_file($originFile)) {
            throw new FileNotFoundException(\sprintf('Origin file "%s" is not a file.', $originFile));
        }

        foreach ($this->toIterable($targetFiles) as $targetFile) {
            if (is_file($targetFile)) {
                if (fileinode($originFile) === fileinode($targetFile)) {
                    continue;
                }
                $this->remove($targetFile);
            }

            if (!self::box('link', $originFile, $targetFile)) {
                $this->linkException($originFile, $targetFile, 'hard');
            }
        }
    }

    /**
     * @param string $linkType Name of the link type, typically 'symbolic' or 'hard'
     */
    private function linkException(string $origin, string $target, string $linkType): never
    {
        if (self::$lastError) {
            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).self::$lastError, 0, null, $target);
    }

    /**
     * Resolves links in paths.
     *
     * With $canonicalize = false (default)
     *      - if $path does not exist or is not a link, returns null
     *      - if $path is a link, returns the next direct target of the link without considering the existence of the target
     *
     * With $canonicalize = true
     *      - if $path does not exist, returns null
     *      - if $path exists, returns its absolute fully resolved final version
     */
    public function readlink(string $path, bool $canonicalize = false): ?string
    {
        if (!$canonicalize && !is_link($path)) {
            return null;
        }

        if ($canonicalize) {
            if (!$this->exists($path)) {
                return null;
            }

            return realpath($path);
        }

        return readlink($path);
    }

    /**
     * Given an existing path, convert it to a path relative to a given starting path.
     */
    public function makePathRelative(string $endPath, string $startPath): string
    {
        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));
        }

        $originalEndPath = $endPath;

        // Normalize separators on Windows
        if ('\\' === \DIRECTORY_SEPARATOR) {
            $endPath = str_replace('\\', '/', $endPath);
            $startPath = str_replace('\\', '/', $startPath);
        }

        $splitDriveLetter = static fn ($path) => (\strlen($path) > 2 && ':' === $path[1] && '/' === $path[2] && ctype_alpha($path[0]))
            ? [substr($path, 2), strtoupper($path[0])]
            : [$path, null];

        $splitPath = static function ($path) {
            $result = [];

            foreach (explode('/', trim($path, '/')) as $segment) {
                if ('..' === $segment) {
                    array_pop($result);
                } elseif ('.' !== $segment && '' !== $segment) {
                    $result[] = $segment;
                }
            }

            return $result;
        };

        [$endPath, $endDriveLetter] = $splitDriveLetter($endPath);
        [$startPath, $startDriveLetter] = $splitDriveLetter($startPath);

        $startPathArr = $splitPath($startPath);
        $endPathArr = $splitPath($endPath);

        if ($endDriveLetter && $startDriveLetter && $endDriveLetter != $startDriveLetter) {
            // End path is on another drive, so no relative path exists
            return $endDriveLetter.':/'.($endPathArr ? implode('/', $endPathArr).'/' : '');
        }

        // Find for which directory the common path stops
        $index = 0;
        while (isset($startPathArr[$index]) && isset($endPathArr[$index]) && $startPathArr[$index] === $endPathArr[$index]) {
            ++$index;
        }

        // Determine how deep the start path is relative to the common path (ie, "web/bundles" = 2 levels)
        if (1 === \count($startPathArr) && '' === $startPathArr[0]) {
            $depth = 0;
        } else {
            $depth = \count($startPathArr) - $index;
        }

        // Repeated "../" for each level need to reach the common path
        $traverser = str_repeat('../', $depth);

        $endPathRemainder = implode('/', \array_slice($endPathArr, $index));

        // Construct $endPath from traversing to the common path, then to the remaining $endPath
        $relativePath = $traverser.('' !== $endPathRemainder ? $endPathRemainder.'/' : '');

        // Remove ending "/" if $endPath points to an existing file
        if (str_ends_with($relativePath, '/') && is_file($originalEndPath)) {
            $relativePath = substr($relativePath, 0, -1);
        }

        return '' === $relativePath ? './' : $relativePath;
    }

    /**
     * Mirrors a directory to another.
     *
     * Copies files and directories from the origin directory into the target directory. By default:
     *
     *  - 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 \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)
     *
     * @return void
     *
     * @throws IOException When file type is unknown
     */
    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;
            if (null === $deleteIterator) {
                $flags = \FilesystemIterator::SKIP_DOTS;
                $deleteIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($targetDir, $flags), \RecursiveIteratorIterator::CHILD_FIRST);
            }
            $targetDirLen = \strlen($targetDir);
            foreach ($deleteIterator as $file) {
                $origin = $originDir.substr($file->getPathname(), $targetDirLen);
                if (!$this->exists($origin)) {
                    $this->remove($file);
                }
            }
        }

        $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);
        }

        $this->mkdir($targetDir);
        $filesCreatedWhileMirroring = [];

        foreach ($iterator as $file) {
            if ($file->getPathname() === $targetDir || $file->getRealPath() === $targetDir || isset($filesCreatedWhileMirroring[$file->getRealPath()])) {
                continue;
            }

            $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 {
                throw new IOException(\sprintf('Unable to guess "%s" file type.', $file), 0, null, $file);
            }
        }
    }

    /**
     * Returns whether the file path is an absolute path.
     */
    public function isAbsolutePath(string $file): bool
    {
        return '' !== $file && (strspn($file, '/\\', 0, 1)
            || (\strlen($file) > 3 && ctype_alpha($file[0])
                && ':' === $file[1]
                && strspn($file, '/\\', 2, 1)
            )
            || null !== parse_url($file, \PHP_URL_SCHEME)
        );
    }

    /**
     * Creates a temporary file with support for custom stream wrappers.
     *
     * @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(string $dir, string $prefix, string $suffix = ''): string
    {
        [$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) && '' === $suffix) {
            // If tempnam failed or no scheme return the filename otherwise prepend the scheme
            if ($tmpFile = self::box('tempnam', $hierarchy, $prefix)) {
                if (null !== $scheme && 'gs' !== $scheme) {
                    return $scheme.'://'.$tmpFile;
                }

                return $tmpFile;
            }

            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).$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
            if (!$handle = self::box('fopen', $tmpFile, 'x+')) {
                continue;
            }

            // Close the file if it was successfully opened
            self::box('fclose', $handle);

            return $tmpFile;
        }

        throw new IOException('A temporary file could not be created: '.self::$lastError);
    }

    /**
     * Atomically dumps content into a file.
     *
     * @param string|resource $content The data to write into the file
     *
     * @return void
     *
     * @throws IOException if the file cannot be written to
     */
    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_link($filename) && $linkTarget = $this->readlink($filename)) {
            $this->dumpFile(Path::makeAbsolute($linkTarget, $dir), $content);

            return;
        }

        if (!is_dir($dir)) {
            $this->mkdir($dir);
        }

        // Will create a temp file with 0600 access rights
        // when the filesystem supports chmod.
        $tmpFile = $this->tempnam($dir, basename($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, self::box('fileperms', $filename) ?: 0o666 & ~umask());

            $this->rename($tmpFile, $filename, true);
        } finally {
            if (file_exists($tmpFile)) {
                if ('\\' === \DIRECTORY_SEPARATOR && !is_writable($tmpFile)) {
                    self::box('chmod', $tmpFile, self::box('fileperms', $tmpFile) | 0o200);
                }

                self::box('unlink', $tmpFile);
            }
        }
    }

    /**
     * Appends content to an existing file.
     *
     * @param string|resource $content The content to append
     * @param bool            $lock    Whether the file should be locked when writing to it
     *
     * @return void
     *
     * @throws IOException If the file is not writable
     */
    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);
        }

        $lock = \func_num_args() > 2 && func_get_arg(2);

        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);
        }
    }

    private function toIterable(string|iterable $files): iterable
    {
        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]).
     */
    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));
        }
    }

    private static function box(string $func, mixed ...$args): mixed
    {
        self::assertFunctionExists($func);

        self::$lastError = null;
        set_error_handler(self::handleError(...));
        try {
            return $func(...$args);
        } finally {
            restore_error_handler();
        }
    }

    /**
     * @internal
     */
    public static function handleError(int $type, string $msg): void
    {
        self::$lastError = $msg;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Filesystem\Exception;

/**
 * Exception interface for all exceptions thrown by the component.
 *
 * @author Romain Neutron <imprec@gmail.com>
 */
interface ExceptionInterface extends \Throwable
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <theo.fidry@gmail.com>
 */
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Filesystem\Exception;

/**
 * Exception class thrown when a file couldn't be found.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Christian Gärtner <christiangaertner.film@googlemail.com>
 */
class FileNotFoundException extends IOException
{
    public function __construct(?string $message = null, int $code = 0, ?\Throwable $previous = null, ?string $path = null)
    {
        if (null === $message) {
            if (null === $path) {
                $message = 'File could not be found.';
            } else {
                $message = \sprintf('File "%s" could not be found.', $path);
            }
        }

        parent::__construct($message, $code, $previous, $path);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Filesystem\Exception;

/**
 * IOException interface for file and input/output stream related exceptions thrown by the component.
 *
 * @author Christian Gärtner <christiangaertner.film@googlemail.com>
 */
interface IOExceptionInterface extends ExceptionInterface
{
    /**
     * Returns the associated path for the exception.
     */
    public function getPath(): ?string;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Filesystem\Exception;

/**
 * Exception class thrown when a filesystem operation failure happens.
 *
 * @author Romain Neutron <imprec@gmail.com>
 * @author Christian Gärtner <christiangaertner.film@googlemail.com>
 * @author Fabien Potencier <fabien@symfony.com>
 */
class IOException extends \RuntimeException implements IOExceptionInterface
{
    private ?string $path;

    public function __construct(string $message, int $code = 0, ?\Throwable $previous = null, ?string $path = null)
    {
        $this->path = $path;

        parent::__construct($message, $code, $previous);
    }

    public function getPath(): ?string
    {
        return $this->path;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <christian.flothmann@sensiolabs.de>
 */
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php

return array (
  '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',
  'À' => 'à',
  'Á' => 'á',
  'Â' => 'â',
  'Ã' => 'ã',
  'Ä' => 'ä',
  'Å' => 'å',
  'Æ' => 'æ',
  'Ç' => 'ç',
  'È' => 'è',
  'É' => 'é',
  'Ê' => 'ê',
  'Ë' => 'ë',
  'Ì' => 'ì',
  'Í' => 'í',
  'Î' => 'î',
  'Ï' => 'ï',
  'Ð' => 'ð',
  'Ñ' => 'ñ',
  'Ò' => 'ò',
  'Ó' => 'ó',
  'Ô' => 'ô',
  'Õ' => 'õ',
  'Ö' => 'ö',
  'Ø' => 'ø',
  'Ù' => 'ù',
  'Ú' => 'ú',
  'Û' => 'û',
  'Ü' => 'ü',
  'Ý' => 'ý',
  'Þ' => 'þ',
  'Ā' => 'ā',
  'Ă' => 'ă',
  'Ą' => 'ą',
  'Ć' => 'ć',
  'Ĉ' => 'ĉ',
  'Ċ' => 'ċ',
  'Č' => 'č',
  'Ď' => 'ď',
  'Đ' => 'đ',
  'Ē' => 'ē',
  'Ĕ' => 'ĕ',
  'Ė' => 'ė',
  'Ę' => 'ę',
  'Ě' => 'ě',
  'Ĝ' => 'ĝ',
  'Ğ' => 'ğ',
  'Ġ' => 'ġ',
  'Ģ' => 'ģ',
  'Ĥ' => 'ĥ',
  'Ħ' => 'ħ',
  'Ĩ' => 'ĩ',
  'Ī' => 'ī',
  'Ĭ' => 'ĭ',
  'Į' => 'į',
  'İ' => 'i̇',
  'Ĳ' => 'ĳ',
  'Ĵ' => 'ĵ',
  'Ķ' => 'ķ',
  'Ĺ' => 'ĺ',
  'Ļ' => 'ļ',
  'Ľ' => 'ľ',
  'Ŀ' => 'ŀ',
  'Ł' => 'ł',
  'Ń' => 'ń',
  'Ņ' => 'ņ',
  'Ň' => 'ň',
  'Ŋ' => 'ŋ',
  'Ō' => 'ō',
  'Ŏ' => 'ŏ',
  'Ő' => 'ő',
  'Œ' => 'œ',
  'Ŕ' => 'ŕ',
  'Ŗ' => 'ŗ',
  'Ř' => 'ř',
  'Ś' => 'ś',
  'Ŝ' => 'ŝ',
  'Ş' => 'ş',
  'Š' => 'š',
  'Ţ' => 'ţ',
  'Ť' => 'ť',
  'Ŧ' => 'ŧ',
  'Ũ' => 'ũ',
  'Ū' => 'ū',
  'Ŭ' => 'ŭ',
  'Ů' => 'ů',
  'Ű' => 'ű',
  'Ų' => 'ų',
  'Ŵ' => 'ŵ',
  'Ŷ' => 'ŷ',
  'Ÿ' => 'ÿ',
  'Ź' => 'ź',
  'Ż' => 'ż',
  'Ž' => 'ž',
  'Ɓ' => 'ɓ',
  'Ƃ' => 'ƃ',
  'Ƅ' => 'ƅ',
  'Ɔ' => 'ɔ',
  'Ƈ' => 'ƈ',
  'Ɖ' => 'ɖ',
  'Ɗ' => 'ɗ',
  'Ƌ' => 'ƌ',
  'Ǝ' => 'ǝ',
  'Ə' => 'ə',
  'Ɛ' => 'ɛ',
  'Ƒ' => 'ƒ',
  'Ɠ' => 'ɠ',
  'Ɣ' => 'ɣ',
  'Ɩ' => 'ɩ',
  'Ɨ' => 'ɨ',
  'Ƙ' => 'ƙ',
  'Ɯ' => 'ɯ',
  'Ɲ' => 'ɲ',
  'Ɵ' => 'ɵ',
  'Ơ' => 'ơ',
  'Ƣ' => 'ƣ',
  'Ƥ' => 'ƥ',
  'Ʀ' => 'ʀ',
  'Ƨ' => 'ƨ',
  'Ʃ' => 'ʃ',
  'Ƭ' => 'ƭ',
  'Ʈ' => 'ʈ',
  'Ư' => 'ư',
  'Ʊ' => 'ʊ',
  'Ʋ' => 'ʋ',
  'Ƴ' => 'ƴ',
  'Ƶ' => 'ƶ',
  'Ʒ' => 'ʒ',
  'Ƹ' => 'ƹ',
  'Ƽ' => 'ƽ',
  'Ǆ' => 'ǆ',
  'ǅ' => 'ǆ',
  'Ǉ' => 'ǉ',
  'ǈ' => 'ǉ',
  'Ǌ' => 'ǌ',
  'ǋ' => 'ǌ',
  'Ǎ' => 'ǎ',
  'Ǐ' => 'ǐ',
  'Ǒ' => 'ǒ',
  'Ǔ' => 'ǔ',
  'Ǖ' => 'ǖ',
  'Ǘ' => 'ǘ',
  'Ǚ' => 'ǚ',
  'Ǜ' => 'ǜ',
  'Ǟ' => 'ǟ',
  'Ǡ' => 'ǡ',
  'Ǣ' => 'ǣ',
  'Ǥ' => 'ǥ',
  'Ǧ' => 'ǧ',
  'Ǩ' => 'ǩ',
  'Ǫ' => 'ǫ',
  'Ǭ' => 'ǭ',
  'Ǯ' => 'ǯ',
  'Ǳ' => 'ǳ',
  'ǲ' => 'ǳ',
  'Ǵ' => 'ǵ',
  'Ƕ' => 'ƕ',
  'Ƿ' => 'ƿ',
  'Ǹ' => 'ǹ',
  'Ǻ' => 'ǻ',
  'Ǽ' => 'ǽ',
  'Ǿ' => 'ǿ',
  'Ȁ' => 'ȁ',
  'Ȃ' => 'ȃ',
  'Ȅ' => 'ȅ',
  'Ȇ' => 'ȇ',
  'Ȉ' => 'ȉ',
  'Ȋ' => 'ȋ',
  'Ȍ' => 'ȍ',
  'Ȏ' => 'ȏ',
  'Ȑ' => 'ȑ',
  'Ȓ' => 'ȓ',
  'Ȕ' => 'ȕ',
  'Ȗ' => 'ȗ',
  'Ș' => 'ș',
  'Ț' => 'ț',
  'Ȝ' => 'ȝ',
  'Ȟ' => 'ȟ',
  'Ƞ' => 'ƞ',
  'Ȣ' => 'ȣ',
  'Ȥ' => 'ȥ',
  'Ȧ' => 'ȧ',
  'Ȩ' => 'ȩ',
  'Ȫ' => 'ȫ',
  'Ȭ' => 'ȭ',
  'Ȯ' => 'ȯ',
  'Ȱ' => 'ȱ',
  'Ȳ' => 'ȳ',
  'Ⱥ' => 'ⱥ',
  'Ȼ' => 'ȼ',
  'Ƚ' => 'ƚ',
  'Ⱦ' => 'ⱦ',
  'Ɂ' => 'ɂ',
  'Ƀ' => 'ƀ',
  'Ʉ' => 'ʉ',
  'Ʌ' => 'ʌ',
  'Ɇ' => 'ɇ',
  'Ɉ' => 'ɉ',
  'Ɋ' => 'ɋ',
  'Ɍ' => 'ɍ',
  'Ɏ' => 'ɏ',
  'Ͱ' => 'ͱ',
  'Ͳ' => 'ͳ',
  'Ͷ' => 'ͷ',
  'Ϳ' => 'ϳ',
  'Ά' => 'ά',
  'Έ' => 'έ',
  'Ή' => 'ή',
  'Ί' => 'ί',
  'Ό' => 'ό',
  'Ύ' => 'ύ',
  'Ώ' => 'ώ',
  'Α' => 'α',
  'Β' => 'β',
  'Γ' => 'γ',
  'Δ' => 'δ',
  'Ε' => 'ε',
  'Ζ' => 'ζ',
  'Η' => 'η',
  'Θ' => 'θ',
  'Ι' => 'ι',
  'Κ' => 'κ',
  'Λ' => 'λ',
  'Μ' => 'μ',
  'Ν' => 'ν',
  'Ξ' => 'ξ',
  'Ο' => 'ο',
  'Π' => 'π',
  'Ρ' => 'ρ',
  'Σ' => 'σ',
  'Τ' => 'τ',
  'Υ' => 'υ',
  'Φ' => 'φ',
  'Χ' => 'χ',
  'Ψ' => 'ψ',
  'Ω' => 'ω',
  'Ϊ' => 'ϊ',
  'Ϋ' => 'ϋ',
  'Ϗ' => 'ϗ',
  'Ϙ' => 'ϙ',
  'Ϛ' => 'ϛ',
  'Ϝ' => 'ϝ',
  'Ϟ' => 'ϟ',
  'Ϡ' => 'ϡ',
  'Ϣ' => 'ϣ',
  'Ϥ' => 'ϥ',
  'Ϧ' => 'ϧ',
  'Ϩ' => 'ϩ',
  'Ϫ' => 'ϫ',
  'Ϭ' => 'ϭ',
  'Ϯ' => 'ϯ',
  'ϴ' => 'θ',
  'Ϸ' => 'ϸ',
  'Ϲ' => 'ϲ',
  'Ϻ' => 'ϻ',
  'Ͻ' => 'ͻ',
  'Ͼ' => 'ͼ',
  'Ͽ' => 'ͽ',
  'Ѐ' => 'ѐ',
  'Ё' => 'ё',
  'Ђ' => 'ђ',
  'Ѓ' => 'ѓ',
  'Є' => 'є',
  'Ѕ' => 'ѕ',
  'І' => 'і',
  'Ї' => 'ї',
  'Ј' => 'ј',
  'Љ' => 'љ',
  'Њ' => 'њ',
  'Ћ' => 'ћ',
  'Ќ' => 'ќ',
  'Ѝ' => 'ѝ',
  'Ў' => 'ў',
  'Џ' => 'џ',
  'А' => 'а',
  'Б' => 'б',
  'В' => 'в',
  'Г' => 'г',
  'Д' => 'д',
  'Е' => 'е',
  'Ж' => 'ж',
  'З' => 'з',
  'И' => 'и',
  'Й' => 'й',
  'К' => 'к',
  'Л' => 'л',
  'М' => 'м',
  'Н' => 'н',
  'О' => 'о',
  'П' => 'п',
  'Р' => 'р',
  'С' => 'с',
  'Т' => 'т',
  'У' => 'у',
  'Ф' => 'ф',
  'Х' => 'х',
  'Ц' => 'ц',
  'Ч' => 'ч',
  'Ш' => 'ш',
  'Щ' => 'щ',
  'Ъ' => 'ъ',
  'Ы' => 'ы',
  'Ь' => 'ь',
  'Э' => 'э',
  'Ю' => 'ю',
  'Я' => 'я',
  'Ѡ' => 'ѡ',
  'Ѣ' => 'ѣ',
  'Ѥ' => 'ѥ',
  'Ѧ' => 'ѧ',
  'Ѩ' => 'ѩ',
  'Ѫ' => 'ѫ',
  'Ѭ' => 'ѭ',
  'Ѯ' => 'ѯ',
  'Ѱ' => 'ѱ',
  'Ѳ' => 'ѳ',
  'Ѵ' => 'ѵ',
  'Ѷ' => 'ѷ',
  'Ѹ' => 'ѹ',
  'Ѻ' => 'ѻ',
  'Ѽ' => 'ѽ',
  'Ѿ' => 'ѿ',
  'Ҁ' => 'ҁ',
  'Ҋ' => 'ҋ',
  'Ҍ' => 'ҍ',
  'Ҏ' => 'ҏ',
  'Ґ' => 'ґ',
  'Ғ' => 'ғ',
  'Ҕ' => 'ҕ',
  'Җ' => 'җ',
  'Ҙ' => 'ҙ',
  'Қ' => 'қ',
  'Ҝ' => 'ҝ',
  'Ҟ' => 'ҟ',
  'Ҡ' => 'ҡ',
  'Ң' => 'ң',
  'Ҥ' => 'ҥ',
  'Ҧ' => 'ҧ',
  'Ҩ' => 'ҩ',
  'Ҫ' => 'ҫ',
  'Ҭ' => 'ҭ',
  'Ү' => 'ү',
  'Ұ' => 'ұ',
  'Ҳ' => 'ҳ',
  'Ҵ' => 'ҵ',
  'Ҷ' => 'ҷ',
  'Ҹ' => 'ҹ',
  'Һ' => 'һ',
  'Ҽ' => 'ҽ',
  'Ҿ' => 'ҿ',
  'Ӏ' => 'ӏ',
  'Ӂ' => 'ӂ',
  'Ӄ' => 'ӄ',
  'Ӆ' => 'ӆ',
  'Ӈ' => 'ӈ',
  'Ӊ' => 'ӊ',
  'Ӌ' => 'ӌ',
  'Ӎ' => 'ӎ',
  'Ӑ' => 'ӑ',
  'Ӓ' => 'ӓ',
  'Ӕ' => 'ӕ',
  'Ӗ' => 'ӗ',
  'Ә' => 'ә',
  'Ӛ' => 'ӛ',
  'Ӝ' => 'ӝ',
  'Ӟ' => 'ӟ',
  'Ӡ' => 'ӡ',
  'Ӣ' => 'ӣ',
  'Ӥ' => 'ӥ',
  'Ӧ' => 'ӧ',
  'Ө' => 'ө',
  'Ӫ' => 'ӫ',
  'Ӭ' => 'ӭ',
  'Ӯ' => 'ӯ',
  'Ӱ' => 'ӱ',
  'Ӳ' => 'ӳ',
  'Ӵ' => 'ӵ',
  'Ӷ' => 'ӷ',
  'Ӹ' => 'ӹ',
  'Ӻ' => 'ӻ',
  'Ӽ' => 'ӽ',
  'Ӿ' => 'ӿ',
  'Ԁ' => 'ԁ',
  'Ԃ' => 'ԃ',
  'Ԅ' => 'ԅ',
  'Ԇ' => 'ԇ',
  'Ԉ' => 'ԉ',
  'Ԋ' => 'ԋ',
  'Ԍ' => 'ԍ',
  'Ԏ' => 'ԏ',
  'Ԑ' => 'ԑ',
  'Ԓ' => 'ԓ',
  'Ԕ' => 'ԕ',
  'Ԗ' => 'ԗ',
  'Ԙ' => 'ԙ',
  'Ԛ' => 'ԛ',
  'Ԝ' => 'ԝ',
  'Ԟ' => 'ԟ',
  'Ԡ' => 'ԡ',
  'Ԣ' => 'ԣ',
  'Ԥ' => 'ԥ',
  'Ԧ' => 'ԧ',
  'Ԩ' => 'ԩ',
  'Ԫ' => 'ԫ',
  'Ԭ' => 'ԭ',
  'Ԯ' => 'ԯ',
  'Ա' => 'ա',
  'Բ' => 'բ',
  'Գ' => 'գ',
  'Դ' => 'դ',
  'Ե' => 'ե',
  'Զ' => 'զ',
  'Է' => 'է',
  'Ը' => 'ը',
  'Թ' => 'թ',
  'Ժ' => 'ժ',
  'Ի' => 'ի',
  'Լ' => 'լ',
  'Խ' => 'խ',
  'Ծ' => 'ծ',
  'Կ' => 'կ',
  'Հ' => 'հ',
  'Ձ' => 'ձ',
  'Ղ' => 'ղ',
  'Ճ' => 'ճ',
  'Մ' => 'մ',
  'Յ' => 'յ',
  'Ն' => 'ն',
  'Շ' => 'շ',
  'Ո' => 'ո',
  'Չ' => 'չ',
  'Պ' => 'պ',
  'Ջ' => 'ջ',
  'Ռ' => 'ռ',
  'Ս' => 'ս',
  'Վ' => 'վ',
  'Տ' => 'տ',
  'Ր' => 'ր',
  'Ց' => 'ց',
  'Ւ' => 'ւ',
  'Փ' => 'փ',
  'Ք' => 'ք',
  'Օ' => 'օ',
  'Ֆ' => 'ֆ',
  'Ⴀ' => 'ⴀ',
  'Ⴁ' => 'ⴁ',
  'Ⴂ' => 'ⴂ',
  'Ⴃ' => 'ⴃ',
  'Ⴄ' => 'ⴄ',
  'Ⴅ' => 'ⴅ',
  'Ⴆ' => 'ⴆ',
  'Ⴇ' => 'ⴇ',
  'Ⴈ' => 'ⴈ',
  'Ⴉ' => 'ⴉ',
  'Ⴊ' => 'ⴊ',
  'Ⴋ' => 'ⴋ',
  'Ⴌ' => 'ⴌ',
  'Ⴍ' => 'ⴍ',
  'Ⴎ' => 'ⴎ',
  'Ⴏ' => 'ⴏ',
  'Ⴐ' => 'ⴐ',
  'Ⴑ' => 'ⴑ',
  'Ⴒ' => 'ⴒ',
  'Ⴓ' => 'ⴓ',
  'Ⴔ' => 'ⴔ',
  'Ⴕ' => 'ⴕ',
  'Ⴖ' => 'ⴖ',
  'Ⴗ' => 'ⴗ',
  'Ⴘ' => 'ⴘ',
  'Ⴙ' => 'ⴙ',
  'Ⴚ' => 'ⴚ',
  'Ⴛ' => 'ⴛ',
  'Ⴜ' => 'ⴜ',
  'Ⴝ' => 'ⴝ',
  'Ⴞ' => 'ⴞ',
  'Ⴟ' => 'ⴟ',
  'Ⴠ' => 'ⴠ',
  'Ⴡ' => 'ⴡ',
  'Ⴢ' => 'ⴢ',
  'Ⴣ' => 'ⴣ',
  'Ⴤ' => 'ⴤ',
  'Ⴥ' => 'ⴥ',
  'Ⴧ' => 'ⴧ',
  'Ⴭ' => 'ⴭ',
  'Ꭰ' => 'ꭰ',
  'Ꭱ' => 'ꭱ',
  'Ꭲ' => 'ꭲ',
  'Ꭳ' => 'ꭳ',
  'Ꭴ' => 'ꭴ',
  'Ꭵ' => 'ꭵ',
  'Ꭶ' => 'ꭶ',
  'Ꭷ' => 'ꭷ',
  'Ꭸ' => 'ꭸ',
  'Ꭹ' => 'ꭹ',
  'Ꭺ' => 'ꭺ',
  'Ꭻ' => 'ꭻ',
  'Ꭼ' => 'ꭼ',
  'Ꭽ' => 'ꭽ',
  'Ꭾ' => 'ꭾ',
  'Ꭿ' => 'ꭿ',
  'Ꮀ' => 'ꮀ',
  'Ꮁ' => 'ꮁ',
  'Ꮂ' => 'ꮂ',
  'Ꮃ' => 'ꮃ',
  'Ꮄ' => 'ꮄ',
  'Ꮅ' => 'ꮅ',
  'Ꮆ' => 'ꮆ',
  'Ꮇ' => 'ꮇ',
  'Ꮈ' => 'ꮈ',
  'Ꮉ' => 'ꮉ',
  'Ꮊ' => 'ꮊ',
  'Ꮋ' => 'ꮋ',
  'Ꮌ' => 'ꮌ',
  'Ꮍ' => 'ꮍ',
  'Ꮎ' => 'ꮎ',
  'Ꮏ' => 'ꮏ',
  'Ꮐ' => 'ꮐ',
  'Ꮑ' => 'ꮑ',
  'Ꮒ' => 'ꮒ',
  'Ꮓ' => 'ꮓ',
  'Ꮔ' => 'ꮔ',
  'Ꮕ' => 'ꮕ',
  'Ꮖ' => 'ꮖ',
  'Ꮗ' => 'ꮗ',
  'Ꮘ' => 'ꮘ',
  'Ꮙ' => 'ꮙ',
  'Ꮚ' => 'ꮚ',
  'Ꮛ' => 'ꮛ',
  'Ꮜ' => 'ꮜ',
  'Ꮝ' => 'ꮝ',
  'Ꮞ' => 'ꮞ',
  'Ꮟ' => 'ꮟ',
  'Ꮠ' => 'ꮠ',
  'Ꮡ' => 'ꮡ',
  'Ꮢ' => 'ꮢ',
  'Ꮣ' => 'ꮣ',
  'Ꮤ' => 'ꮤ',
  'Ꮥ' => 'ꮥ',
  'Ꮦ' => 'ꮦ',
  'Ꮧ' => 'ꮧ',
  'Ꮨ' => 'ꮨ',
  'Ꮩ' => 'ꮩ',
  'Ꮪ' => 'ꮪ',
  'Ꮫ' => 'ꮫ',
  'Ꮬ' => 'ꮬ',
  'Ꮭ' => 'ꮭ',
  'Ꮮ' => 'ꮮ',
  'Ꮯ' => 'ꮯ',
  'Ꮰ' => 'ꮰ',
  'Ꮱ' => 'ꮱ',
  'Ꮲ' => 'ꮲ',
  'Ꮳ' => 'ꮳ',
  'Ꮴ' => 'ꮴ',
  'Ꮵ' => 'ꮵ',
  'Ꮶ' => 'ꮶ',
  'Ꮷ' => 'ꮷ',
  'Ꮸ' => 'ꮸ',
  'Ꮹ' => 'ꮹ',
  'Ꮺ' => 'ꮺ',
  'Ꮻ' => 'ꮻ',
  'Ꮼ' => 'ꮼ',
  'Ꮽ' => 'ꮽ',
  'Ꮾ' => 'ꮾ',
  'Ꮿ' => 'ꮿ',
  'Ᏸ' => 'ᏸ',
  'Ᏹ' => 'ᏹ',
  'Ᏺ' => 'ᏺ',
  'Ᏻ' => 'ᏻ',
  'Ᏼ' => 'ᏼ',
  'Ᏽ' => 'ᏽ',
  'Ა' => 'ა',
  'Ბ' => 'ბ',
  'Გ' => 'გ',
  'Დ' => 'დ',
  'Ე' => 'ე',
  'Ვ' => 'ვ',
  'Ზ' => 'ზ',
  'Თ' => 'თ',
  'Ი' => 'ი',
  'Კ' => 'კ',
  'Ლ' => 'ლ',
  'Მ' => 'მ',
  'Ნ' => 'ნ',
  'Ო' => 'ო',
  'Პ' => 'პ',
  'Ჟ' => 'ჟ',
  'Რ' => 'რ',
  'Ს' => 'ს',
  'Ტ' => 'ტ',
  'Უ' => 'უ',
  'Ფ' => 'ფ',
  'Ქ' => 'ქ',
  'Ღ' => 'ღ',
  'Ყ' => 'ყ',
  'Შ' => 'შ',
  'Ჩ' => 'ჩ',
  'Ც' => 'ც',
  'Ძ' => 'ძ',
  'Წ' => 'წ',
  'Ჭ' => 'ჭ',
  'Ხ' => 'ხ',
  'Ჯ' => 'ჯ',
  'Ჰ' => 'ჰ',
  'Ჱ' => 'ჱ',
  'Ჲ' => 'ჲ',
  'Ჳ' => 'ჳ',
  'Ჴ' => 'ჴ',
  'Ჵ' => 'ჵ',
  'Ჶ' => 'ჶ',
  'Ჷ' => 'ჷ',
  'Ჸ' => 'ჸ',
  'Ჹ' => 'ჹ',
  'Ჺ' => 'ჺ',
  'Ჽ' => 'ჽ',
  'Ჾ' => 'ჾ',
  'Ჿ' => 'ჿ',
  'Ḁ' => 'ḁ',
  'Ḃ' => 'ḃ',
  'Ḅ' => 'ḅ',
  'Ḇ' => 'ḇ',
  'Ḉ' => 'ḉ',
  'Ḋ' => 'ḋ',
  'Ḍ' => 'ḍ',
  'Ḏ' => 'ḏ',
  'Ḑ' => 'ḑ',
  'Ḓ' => 'ḓ',
  'Ḕ' => 'ḕ',
  'Ḗ' => 'ḗ',
  'Ḙ' => 'ḙ',
  'Ḛ' => 'ḛ',
  'Ḝ' => 'ḝ',
  'Ḟ' => 'ḟ',
  'Ḡ' => 'ḡ',
  'Ḣ' => 'ḣ',
  'Ḥ' => 'ḥ',
  'Ḧ' => 'ḧ',
  'Ḩ' => 'ḩ',
  'Ḫ' => 'ḫ',
  'Ḭ' => 'ḭ',
  'Ḯ' => 'ḯ',
  'Ḱ' => 'ḱ',
  'Ḳ' => 'ḳ',
  'Ḵ' => 'ḵ',
  'Ḷ' => 'ḷ',
  'Ḹ' => 'ḹ',
  'Ḻ' => 'ḻ',
  'Ḽ' => 'ḽ',
  'Ḿ' => 'ḿ',
  'Ṁ' => 'ṁ',
  'Ṃ' => 'ṃ',
  'Ṅ' => 'ṅ',
  'Ṇ' => 'ṇ',
  'Ṉ' => 'ṉ',
  'Ṋ' => 'ṋ',
  'Ṍ' => 'ṍ',
  'Ṏ' => 'ṏ',
  'Ṑ' => 'ṑ',
  'Ṓ' => 'ṓ',
  'Ṕ' => 'ṕ',
  'Ṗ' => 'ṗ',
  'Ṙ' => 'ṙ',
  'Ṛ' => 'ṛ',
  'Ṝ' => 'ṝ',
  'Ṟ' => 'ṟ',
  'Ṡ' => 'ṡ',
  'Ṣ' => 'ṣ',
  'Ṥ' => 'ṥ',
  'Ṧ' => 'ṧ',
  'Ṩ' => 'ṩ',
  'Ṫ' => 'ṫ',
  'Ṭ' => 'ṭ',
  'Ṯ' => 'ṯ',
  'Ṱ' => 'ṱ',
  'Ṳ' => 'ṳ',
  'Ṵ' => 'ṵ',
  'Ṷ' => 'ṷ',
  'Ṹ' => 'ṹ',
  'Ṻ' => 'ṻ',
  'Ṽ' => 'ṽ',
  'Ṿ' => 'ṿ',
  'Ẁ' => 'ẁ',
  'Ẃ' => 'ẃ',
  'Ẅ' => 'ẅ',
  'Ẇ' => 'ẇ',
  'Ẉ' => 'ẉ',
  'Ẋ' => 'ẋ',
  'Ẍ' => 'ẍ',
  'Ẏ' => 'ẏ',
  'Ẑ' => 'ẑ',
  'Ẓ' => 'ẓ',
  'Ẕ' => 'ẕ',
  'ẞ' => 'ß',
  'Ạ' => 'ạ',
  'Ả' => 'ả',
  'Ấ' => 'ấ',
  'Ầ' => 'ầ',
  'Ẩ' => 'ẩ',
  'Ẫ' => 'ẫ',
  'Ậ' => 'ậ',
  'Ắ' => 'ắ',
  'Ằ' => 'ằ',
  'Ẳ' => 'ẳ',
  'Ẵ' => 'ẵ',
  'Ặ' => 'ặ',
  'Ẹ' => 'ẹ',
  'Ẻ' => 'ẻ',
  'Ẽ' => 'ẽ',
  'Ế' => 'ế',
  'Ề' => 'ề',
  'Ể' => 'ể',
  'Ễ' => 'ễ',
  'Ệ' => 'ệ',
  'Ỉ' => 'ỉ',
  'Ị' => 'ị',
  'Ọ' => 'ọ',
  'Ỏ' => 'ỏ',
  'Ố' => 'ố',
  'Ồ' => 'ồ',
  'Ổ' => 'ổ',
  'Ỗ' => 'ỗ',
  'Ộ' => 'ộ',
  'Ớ' => 'ớ',
  'Ờ' => 'ờ',
  'Ở' => 'ở',
  'Ỡ' => 'ỡ',
  'Ợ' => 'ợ',
  'Ụ' => 'ụ',
  'Ủ' => 'ủ',
  'Ứ' => 'ứ',
  'Ừ' => 'ừ',
  'Ử' => 'ử',
  'Ữ' => 'ữ',
  'Ự' => 'ự',
  'Ỳ' => 'ỳ',
  'Ỵ' => 'ỵ',
  'Ỷ' => 'ỷ',
  'Ỹ' => 'ỹ',
  'Ỻ' => 'ỻ',
  'Ỽ' => 'ỽ',
  'Ỿ' => 'ỿ',
  'Ἀ' => 'ἀ',
  'Ἁ' => 'ἁ',
  'Ἂ' => 'ἂ',
  'Ἃ' => 'ἃ',
  'Ἄ' => 'ἄ',
  'Ἅ' => 'ἅ',
  'Ἆ' => 'ἆ',
  'Ἇ' => 'ἇ',
  'Ἐ' => 'ἐ',
  'Ἑ' => 'ἑ',
  'Ἒ' => 'ἒ',
  'Ἓ' => 'ἓ',
  'Ἔ' => 'ἔ',
  'Ἕ' => 'ἕ',
  'Ἠ' => 'ἠ',
  'Ἡ' => 'ἡ',
  'Ἢ' => 'ἢ',
  'Ἣ' => 'ἣ',
  'Ἤ' => 'ἤ',
  'Ἥ' => 'ἥ',
  'Ἦ' => 'ἦ',
  'Ἧ' => 'ἧ',
  'Ἰ' => 'ἰ',
  'Ἱ' => 'ἱ',
  'Ἲ' => 'ἲ',
  'Ἳ' => 'ἳ',
  'Ἴ' => 'ἴ',
  'Ἵ' => 'ἵ',
  'Ἶ' => 'ἶ',
  'Ἷ' => 'ἷ',
  'Ὀ' => 'ὀ',
  'Ὁ' => 'ὁ',
  'Ὂ' => 'ὂ',
  'Ὃ' => 'ὃ',
  'Ὄ' => 'ὄ',
  'Ὅ' => 'ὅ',
  'Ὑ' => 'ὑ',
  'Ὓ' => 'ὓ',
  'Ὕ' => 'ὕ',
  'Ὗ' => 'ὗ',
  'Ὠ' => 'ὠ',
  'Ὡ' => 'ὡ',
  'Ὢ' => 'ὢ',
  'Ὣ' => 'ὣ',
  'Ὤ' => 'ὤ',
  'Ὥ' => 'ὥ',
  'Ὦ' => 'ὦ',
  'Ὧ' => 'ὧ',
  'ᾈ' => 'ᾀ',
  'ᾉ' => 'ᾁ',
  'ᾊ' => 'ᾂ',
  'ᾋ' => 'ᾃ',
  'ᾌ' => 'ᾄ',
  'ᾍ' => 'ᾅ',
  'ᾎ' => 'ᾆ',
  'ᾏ' => 'ᾇ',
  'ᾘ' => 'ᾐ',
  'ᾙ' => 'ᾑ',
  'ᾚ' => 'ᾒ',
  'ᾛ' => 'ᾓ',
  'ᾜ' => 'ᾔ',
  'ᾝ' => 'ᾕ',
  'ᾞ' => 'ᾖ',
  'ᾟ' => 'ᾗ',
  'ᾨ' => 'ᾠ',
  'ᾩ' => 'ᾡ',
  'ᾪ' => 'ᾢ',
  'ᾫ' => 'ᾣ',
  'ᾬ' => 'ᾤ',
  'ᾭ' => 'ᾥ',
  'ᾮ' => 'ᾦ',
  'ᾯ' => 'ᾧ',
  'Ᾰ' => 'ᾰ',
  'Ᾱ' => 'ᾱ',
  'Ὰ' => 'ὰ',
  'Ά' => 'ά',
  'ᾼ' => 'ᾳ',
  'Ὲ' => 'ὲ',
  'Έ' => 'έ',
  'Ὴ' => 'ὴ',
  'Ή' => 'ή',
  'ῌ' => 'ῃ',
  'Ῐ' => 'ῐ',
  'Ῑ' => 'ῑ',
  'Ὶ' => 'ὶ',
  'Ί' => 'ί',
  'Ῠ' => 'ῠ',
  'Ῡ' => 'ῡ',
  'Ὺ' => 'ὺ',
  'Ύ' => 'ύ',
  'Ῥ' => 'ῥ',
  'Ὸ' => 'ὸ',
  'Ό' => 'ό',
  'Ὼ' => 'ὼ',
  'Ώ' => 'ώ',
  'ῼ' => 'ῳ',
  'Ω' => 'ω',
  'K' => 'k',
  'Å' => 'å',
  'Ⅎ' => 'ⅎ',
  'Ⅰ' => 'ⅰ',
  'Ⅱ' => 'ⅱ',
  'Ⅲ' => 'ⅲ',
  'Ⅳ' => 'ⅳ',
  'Ⅴ' => 'ⅴ',
  'Ⅵ' => 'ⅵ',
  'Ⅶ' => 'ⅶ',
  'Ⅷ' => 'ⅷ',
  'Ⅸ' => 'ⅸ',
  'Ⅹ' => 'ⅹ',
  'Ⅺ' => 'ⅺ',
  'Ⅻ' => 'ⅻ',
  'Ⅼ' => 'ⅼ',
  'Ⅽ' => 'ⅽ',
  'Ⅾ' => 'ⅾ',
  'Ⅿ' => 'ⅿ',
  'Ↄ' => 'ↄ',
  'Ⓐ' => 'ⓐ',
  'Ⓑ' => 'ⓑ',
  'Ⓒ' => 'ⓒ',
  'Ⓓ' => 'ⓓ',
  'Ⓔ' => 'ⓔ',
  'Ⓕ' => 'ⓕ',
  'Ⓖ' => 'ⓖ',
  'Ⓗ' => 'ⓗ',
  'Ⓘ' => 'ⓘ',
  'Ⓙ' => 'ⓙ',
  'Ⓚ' => 'ⓚ',
  'Ⓛ' => 'ⓛ',
  'Ⓜ' => 'ⓜ',
  'Ⓝ' => 'ⓝ',
  'Ⓞ' => 'ⓞ',
  'Ⓟ' => 'ⓟ',
  'Ⓠ' => 'ⓠ',
  'Ⓡ' => 'ⓡ',
  'Ⓢ' => 'ⓢ',
  'Ⓣ' => 'ⓣ',
  'Ⓤ' => 'ⓤ',
  'Ⓥ' => 'ⓥ',
  'Ⓦ' => 'ⓦ',
  'Ⓧ' => 'ⓧ',
  'Ⓨ' => 'ⓨ',
  'Ⓩ' => 'ⓩ',
  'Ⰰ' => 'ⰰ',
  'Ⰱ' => 'ⰱ',
  'Ⰲ' => 'ⰲ',
  'Ⰳ' => 'ⰳ',
  'Ⰴ' => 'ⰴ',
  'Ⰵ' => 'ⰵ',
  'Ⰶ' => 'ⰶ',
  'Ⰷ' => 'ⰷ',
  'Ⰸ' => 'ⰸ',
  'Ⰹ' => 'ⰹ',
  'Ⰺ' => 'ⰺ',
  'Ⰻ' => 'ⰻ',
  'Ⰼ' => 'ⰼ',
  'Ⰽ' => 'ⰽ',
  'Ⰾ' => 'ⰾ',
  'Ⰿ' => 'ⰿ',
  'Ⱀ' => 'ⱀ',
  'Ⱁ' => 'ⱁ',
  'Ⱂ' => 'ⱂ',
  'Ⱃ' => 'ⱃ',
  'Ⱄ' => 'ⱄ',
  'Ⱅ' => 'ⱅ',
  'Ⱆ' => 'ⱆ',
  'Ⱇ' => 'ⱇ',
  'Ⱈ' => 'ⱈ',
  'Ⱉ' => 'ⱉ',
  'Ⱊ' => 'ⱊ',
  'Ⱋ' => 'ⱋ',
  'Ⱌ' => 'ⱌ',
  'Ⱍ' => 'ⱍ',
  'Ⱎ' => 'ⱎ',
  'Ⱏ' => 'ⱏ',
  'Ⱐ' => 'ⱐ',
  'Ⱑ' => 'ⱑ',
  'Ⱒ' => 'ⱒ',
  'Ⱓ' => 'ⱓ',
  'Ⱔ' => 'ⱔ',
  'Ⱕ' => 'ⱕ',
  'Ⱖ' => 'ⱖ',
  'Ⱗ' => 'ⱗ',
  'Ⱘ' => 'ⱘ',
  'Ⱙ' => 'ⱙ',
  'Ⱚ' => 'ⱚ',
  'Ⱛ' => 'ⱛ',
  'Ⱜ' => 'ⱜ',
  'Ⱝ' => 'ⱝ',
  'Ⱞ' => 'ⱞ',
  'Ⱡ' => 'ⱡ',
  'Ɫ' => 'ɫ',
  'Ᵽ' => 'ᵽ',
  'Ɽ' => 'ɽ',
  'Ⱨ' => 'ⱨ',
  'Ⱪ' => 'ⱪ',
  'Ⱬ' => 'ⱬ',
  'Ɑ' => 'ɑ',
  'Ɱ' => 'ɱ',
  'Ɐ' => 'ɐ',
  'Ɒ' => 'ɒ',
  'Ⱳ' => 'ⱳ',
  'Ⱶ' => 'ⱶ',
  'Ȿ' => 'ȿ',
  'Ɀ' => 'ɀ',
  'Ⲁ' => 'ⲁ',
  'Ⲃ' => 'ⲃ',
  'Ⲅ' => 'ⲅ',
  'Ⲇ' => 'ⲇ',
  'Ⲉ' => 'ⲉ',
  'Ⲋ' => 'ⲋ',
  'Ⲍ' => 'ⲍ',
  'Ⲏ' => 'ⲏ',
  'Ⲑ' => 'ⲑ',
  'Ⲓ' => 'ⲓ',
  'Ⲕ' => 'ⲕ',
  'Ⲗ' => 'ⲗ',
  'Ⲙ' => 'ⲙ',
  'Ⲛ' => 'ⲛ',
  'Ⲝ' => 'ⲝ',
  'Ⲟ' => 'ⲟ',
  'Ⲡ' => 'ⲡ',
  'Ⲣ' => 'ⲣ',
  'Ⲥ' => 'ⲥ',
  'Ⲧ' => 'ⲧ',
  'Ⲩ' => 'ⲩ',
  'Ⲫ' => 'ⲫ',
  'Ⲭ' => 'ⲭ',
  'Ⲯ' => 'ⲯ',
  'Ⲱ' => 'ⲱ',
  'Ⲳ' => 'ⲳ',
  'Ⲵ' => 'ⲵ',
  'Ⲷ' => 'ⲷ',
  'Ⲹ' => 'ⲹ',
  'Ⲻ' => 'ⲻ',
  'Ⲽ' => 'ⲽ',
  'Ⲿ' => 'ⲿ',
  'Ⳁ' => 'ⳁ',
  'Ⳃ' => 'ⳃ',
  'Ⳅ' => 'ⳅ',
  'Ⳇ' => 'ⳇ',
  'Ⳉ' => 'ⳉ',
  'Ⳋ' => 'ⳋ',
  'Ⳍ' => 'ⳍ',
  'Ⳏ' => 'ⳏ',
  'Ⳑ' => 'ⳑ',
  'Ⳓ' => 'ⳓ',
  'Ⳕ' => 'ⳕ',
  'Ⳗ' => 'ⳗ',
  'Ⳙ' => 'ⳙ',
  'Ⳛ' => 'ⳛ',
  'Ⳝ' => 'ⳝ',
  'Ⳟ' => 'ⳟ',
  'Ⳡ' => 'ⳡ',
  'Ⳣ' => 'ⳣ',
  'Ⳬ' => 'ⳬ',
  'Ⳮ' => 'ⳮ',
  'Ⳳ' => 'ⳳ',
  'Ꙁ' => 'ꙁ',
  'Ꙃ' => 'ꙃ',
  'Ꙅ' => 'ꙅ',
  'Ꙇ' => 'ꙇ',
  'Ꙉ' => 'ꙉ',
  'Ꙋ' => 'ꙋ',
  'Ꙍ' => 'ꙍ',
  'Ꙏ' => 'ꙏ',
  'Ꙑ' => 'ꙑ',
  'Ꙓ' => 'ꙓ',
  'Ꙕ' => 'ꙕ',
  'Ꙗ' => 'ꙗ',
  'Ꙙ' => 'ꙙ',
  'Ꙛ' => 'ꙛ',
  'Ꙝ' => 'ꙝ',
  'Ꙟ' => 'ꙟ',
  'Ꙡ' => 'ꙡ',
  'Ꙣ' => 'ꙣ',
  'Ꙥ' => 'ꙥ',
  'Ꙧ' => 'ꙧ',
  'Ꙩ' => 'ꙩ',
  'Ꙫ' => 'ꙫ',
  'Ꙭ' => 'ꙭ',
  'Ꚁ' => 'ꚁ',
  'Ꚃ' => 'ꚃ',
  'Ꚅ' => 'ꚅ',
  'Ꚇ' => 'ꚇ',
  'Ꚉ' => 'ꚉ',
  'Ꚋ' => 'ꚋ',
  'Ꚍ' => 'ꚍ',
  'Ꚏ' => 'ꚏ',
  'Ꚑ' => 'ꚑ',
  'Ꚓ' => 'ꚓ',
  'Ꚕ' => 'ꚕ',
  'Ꚗ' => 'ꚗ',
  'Ꚙ' => 'ꚙ',
  'Ꚛ' => 'ꚛ',
  'Ꜣ' => 'ꜣ',
  'Ꜥ' => 'ꜥ',
  'Ꜧ' => 'ꜧ',
  'Ꜩ' => 'ꜩ',
  'Ꜫ' => 'ꜫ',
  'Ꜭ' => 'ꜭ',
  'Ꜯ' => 'ꜯ',
  'Ꜳ' => 'ꜳ',
  'Ꜵ' => 'ꜵ',
  'Ꜷ' => 'ꜷ',
  'Ꜹ' => 'ꜹ',
  'Ꜻ' => 'ꜻ',
  'Ꜽ' => 'ꜽ',
  'Ꜿ' => 'ꜿ',
  'Ꝁ' => 'ꝁ',
  'Ꝃ' => 'ꝃ',
  'Ꝅ' => 'ꝅ',
  'Ꝇ' => 'ꝇ',
  'Ꝉ' => 'ꝉ',
  'Ꝋ' => 'ꝋ',
  'Ꝍ' => 'ꝍ',
  'Ꝏ' => 'ꝏ',
  'Ꝑ' => 'ꝑ',
  'Ꝓ' => 'ꝓ',
  'Ꝕ' => 'ꝕ',
  'Ꝗ' => 'ꝗ',
  'Ꝙ' => 'ꝙ',
  'Ꝛ' => 'ꝛ',
  'Ꝝ' => 'ꝝ',
  'Ꝟ' => 'ꝟ',
  'Ꝡ' => 'ꝡ',
  'Ꝣ' => 'ꝣ',
  'Ꝥ' => 'ꝥ',
  'Ꝧ' => 'ꝧ',
  'Ꝩ' => 'ꝩ',
  'Ꝫ' => 'ꝫ',
  'Ꝭ' => 'ꝭ',
  'Ꝯ' => 'ꝯ',
  'Ꝺ' => 'ꝺ',
  'Ꝼ' => 'ꝼ',
  'Ᵹ' => 'ᵹ',
  'Ꝿ' => 'ꝿ',
  'Ꞁ' => 'ꞁ',
  'Ꞃ' => 'ꞃ',
  'Ꞅ' => 'ꞅ',
  'Ꞇ' => 'ꞇ',
  'Ꞌ' => 'ꞌ',
  'Ɥ' => 'ɥ',
  'Ꞑ' => 'ꞑ',
  'Ꞓ' => 'ꞓ',
  'Ꞗ' => 'ꞗ',
  'Ꞙ' => 'ꞙ',
  'Ꞛ' => 'ꞛ',
  'Ꞝ' => 'ꞝ',
  'Ꞟ' => 'ꞟ',
  'Ꞡ' => 'ꞡ',
  'Ꞣ' => 'ꞣ',
  'Ꞥ' => 'ꞥ',
  'Ꞧ' => 'ꞧ',
  'Ꞩ' => 'ꞩ',
  'Ɦ' => 'ɦ',
  'Ɜ' => 'ɜ',
  'Ɡ' => 'ɡ',
  'Ɬ' => 'ɬ',
  'Ɪ' => 'ɪ',
  'Ʞ' => 'ʞ',
  'Ʇ' => 'ʇ',
  'Ʝ' => 'ʝ',
  'Ꭓ' => 'ꭓ',
  'Ꞵ' => 'ꞵ',
  'Ꞷ' => 'ꞷ',
  'Ꞹ' => 'ꞹ',
  'Ꞻ' => 'ꞻ',
  'Ꞽ' => 'ꞽ',
  'Ꞿ' => 'ꞿ',
  'Ꟃ' => 'ꟃ',
  'Ꞔ' => 'ꞔ',
  'Ʂ' => 'ʂ',
  'Ᶎ' => 'ᶎ',
  'Ꟈ' => 'ꟈ',
  'Ꟊ' => 'ꟊ',
  'Ꟶ' => 'ꟶ',
  'Ａ' => 'ａ',
  'Ｂ' => 'ｂ',
  'Ｃ' => 'ｃ',
  'Ｄ' => 'ｄ',
  'Ｅ' => 'ｅ',
  'Ｆ' => 'ｆ',
  'Ｇ' => 'ｇ',
  'Ｈ' => 'ｈ',
  'Ｉ' => 'ｉ',
  'Ｊ' => 'ｊ',
  'Ｋ' => 'ｋ',
  'Ｌ' => 'ｌ',
  'Ｍ' => 'ｍ',
  'Ｎ' => 'ｎ',
  'Ｏ' => 'ｏ',
  'Ｐ' => 'ｐ',
  'Ｑ' => 'ｑ',
  'Ｒ' => 'ｒ',
  'Ｓ' => 'ｓ',
  'Ｔ' => 'ｔ',
  'Ｕ' => 'ｕ',
  'Ｖ' => 'ｖ',
  'Ｗ' => 'ｗ',
  'Ｘ' => 'ｘ',
  'Ｙ' => 'ｙ',
  'Ｚ' => 'ｚ',
  '𐐀' => '𐐨',
  '𐐁' => '𐐩',
  '𐐂' => '𐐪',
  '𐐃' => '𐐫',
  '𐐄' => '𐐬',
  '𐐅' => '𐐭',
  '𐐆' => '𐐮',
  '𐐇' => '𐐯',
  '𐐈' => '𐐰',
  '𐐉' => '𐐱',
  '𐐊' => '𐐲',
  '𐐋' => '𐐳',
  '𐐌' => '𐐴',
  '𐐍' => '𐐵',
  '𐐎' => '𐐶',
  '𐐏' => '𐐷',
  '𐐐' => '𐐸',
  '𐐑' => '𐐹',
  '𐐒' => '𐐺',
  '𐐓' => '𐐻',
  '𐐔' => '𐐼',
  '𐐕' => '𐐽',
  '𐐖' => '𐐾',
  '𐐗' => '𐐿',
  '𐐘' => '𐑀',
  '𐐙' => '𐑁',
  '𐐚' => '𐑂',
  '𐐛' => '𐑃',
  '𐐜' => '𐑄',
  '𐐝' => '𐑅',
  '𐐞' => '𐑆',
  '𐐟' => '𐑇',
  '𐐠' => '𐑈',
  '𐐡' => '𐑉',
  '𐐢' => '𐑊',
  '𐐣' => '𐑋',
  '𐐤' => '𐑌',
  '𐐥' => '𐑍',
  '𐐦' => '𐑎',
  '𐐧' => '𐑏',
  '𐒰' => '𐓘',
  '𐒱' => '𐓙',
  '𐒲' => '𐓚',
  '𐒳' => '𐓛',
  '𐒴' => '𐓜',
  '𐒵' => '𐓝',
  '𐒶' => '𐓞',
  '𐒷' => '𐓟',
  '𐒸' => '𐓠',
  '𐒹' => '𐓡',
  '𐒺' => '𐓢',
  '𐒻' => '𐓣',
  '𐒼' => '𐓤',
  '𐒽' => '𐓥',
  '𐒾' => '𐓦',
  '𐒿' => '𐓧',
  '𐓀' => '𐓨',
  '𐓁' => '𐓩',
  '𐓂' => '𐓪',
  '𐓃' => '𐓫',
  '𐓄' => '𐓬',
  '𐓅' => '𐓭',
  '𐓆' => '𐓮',
  '𐓇' => '𐓯',
  '𐓈' => '𐓰',
  '𐓉' => '𐓱',
  '𐓊' => '𐓲',
  '𐓋' => '𐓳',
  '𐓌' => '𐓴',
  '𐓍' => '𐓵',
  '𐓎' => '𐓶',
  '𐓏' => '𐓷',
  '𐓐' => '𐓸',
  '𐓑' => '𐓹',
  '𐓒' => '𐓺',
  '𐓓' => '𐓻',
  '𐲀' => '𐳀',
  '𐲁' => '𐳁',
  '𐲂' => '𐳂',
  '𐲃' => '𐳃',
  '𐲄' => '𐳄',
  '𐲅' => '𐳅',
  '𐲆' => '𐳆',
  '𐲇' => '𐳇',
  '𐲈' => '𐳈',
  '𐲉' => '𐳉',
  '𐲊' => '𐳊',
  '𐲋' => '𐳋',
  '𐲌' => '𐳌',
  '𐲍' => '𐳍',
  '𐲎' => '𐳎',
  '𐲏' => '𐳏',
  '𐲐' => '𐳐',
  '𐲑' => '𐳑',
  '𐲒' => '𐳒',
  '𐲓' => '𐳓',
  '𐲔' => '𐳔',
  '𐲕' => '𐳕',
  '𐲖' => '𐳖',
  '𐲗' => '𐳗',
  '𐲘' => '𐳘',
  '𐲙' => '𐳙',
  '𐲚' => '𐳚',
  '𐲛' => '𐳛',
  '𐲜' => '𐳜',
  '𐲝' => '𐳝',
  '𐲞' => '𐳞',
  '𐲟' => '𐳟',
  '𐲠' => '𐳠',
  '𐲡' => '𐳡',
  '𐲢' => '𐳢',
  '𐲣' => '𐳣',
  '𐲤' => '𐳤',
  '𐲥' => '𐳥',
  '𐲦' => '𐳦',
  '𐲧' => '𐳧',
  '𐲨' => '𐳨',
  '𐲩' => '𐳩',
  '𐲪' => '𐳪',
  '𐲫' => '𐳫',
  '𐲬' => '𐳬',
  '𐲭' => '𐳭',
  '𐲮' => '𐳮',
  '𐲯' => '𐳯',
  '𐲰' => '𐳰',
  '𐲱' => '𐳱',
  '𐲲' => '𐳲',
  '𑢠' => '𑣀',
  '𑢡' => '𑣁',
  '𑢢' => '𑣂',
  '𑢣' => '𑣃',
  '𑢤' => '𑣄',
  '𑢥' => '𑣅',
  '𑢦' => '𑣆',
  '𑢧' => '𑣇',
  '𑢨' => '𑣈',
  '𑢩' => '𑣉',
  '𑢪' => '𑣊',
  '𑢫' => '𑣋',
  '𑢬' => '𑣌',
  '𑢭' => '𑣍',
  '𑢮' => '𑣎',
  '𑢯' => '𑣏',
  '𑢰' => '𑣐',
  '𑢱' => '𑣑',
  '𑢲' => '𑣒',
  '𑢳' => '𑣓',
  '𑢴' => '𑣔',
  '𑢵' => '𑣕',
  '𑢶' => '𑣖',
  '𑢷' => '𑣗',
  '𑢸' => '𑣘',
  '𑢹' => '𑣙',
  '𑢺' => '𑣚',
  '𑢻' => '𑣛',
  '𑢼' => '𑣜',
  '𑢽' => '𑣝',
  '𑢾' => '𑣞',
  '𑢿' => '𑣟',
  '𖹀' => '𖹠',
  '𖹁' => '𖹡',
  '𖹂' => '𖹢',
  '𖹃' => '𖹣',
  '𖹄' => '𖹤',
  '𖹅' => '𖹥',
  '𖹆' => '𖹦',
  '𖹇' => '𖹧',
  '𖹈' => '𖹨',
  '𖹉' => '𖹩',
  '𖹊' => '𖹪',
  '𖹋' => '𖹫',
  '𖹌' => '𖹬',
  '𖹍' => '𖹭',
  '𖹎' => '𖹮',
  '𖹏' => '𖹯',
  '𖹐' => '𖹰',
  '𖹑' => '𖹱',
  '𖹒' => '𖹲',
  '𖹓' => '𖹳',
  '𖹔' => '𖹴',
  '𖹕' => '𖹵',
  '𖹖' => '𖹶',
  '𖹗' => '𖹷',
  '𖹘' => '𖹸',
  '𖹙' => '𖹹',
  '𖹚' => '𖹺',
  '𖹛' => '𖹻',
  '𖹜' => '𖹼',
  '𖹝' => '𖹽',
  '𖹞' => '𖹾',
  '𖹟' => '𖹿',
  '𞤀' => '𞤢',
  '𞤁' => '𞤣',
  '𞤂' => '𞤤',
  '𞤃' => '𞤥',
  '𞤄' => '𞤦',
  '𞤅' => '𞤧',
  '𞤆' => '𞤨',
  '𞤇' => '𞤩',
  '𞤈' => '𞤪',
  '𞤉' => '𞤫',
  '𞤊' => '𞤬',
  '𞤋' => '𞤭',
  '𞤌' => '𞤮',
  '𞤍' => '𞤯',
  '𞤎' => '𞤰',
  '𞤏' => '𞤱',
  '𞤐' => '𞤲',
  '𞤑' => '𞤳',
  '𞤒' => '𞤴',
  '𞤓' => '𞤵',
  '𞤔' => '𞤶',
  '𞤕' => '𞤷',
  '𞤖' => '𞤸',
  '𞤗' => '𞤹',
  '𞤘' => '𞤺',
  '𞤙' => '𞤻',
  '𞤚' => '𞤼',
  '𞤛' => '𞤽',
  '𞤜' => '𞤾',
  '𞤝' => '𞤿',
  '𞤞' => '𞥀',
  '𞤟' => '𞥁',
  '𞤠' => '𞥂',
  '𞤡' => '𞥃',
);
<?php

return [
    'İ' => 'i̇',
    'µ' => 'μ',
    'ſ' => 's',
    'ͅ' => 'ι',
    'ς' => 'σ',
    'ϐ' => 'β',
    'ϑ' => 'θ',
    'ϕ' => 'φ',
    'ϖ' => 'π',
    'ϰ' => 'κ',
    'ϱ' => 'ρ',
    'ϵ' => 'ε',
    'ẛ' => 'ṡ',
    'ι' => 'ι',
    'ß' => 'ss',
    'ŉ' => 'ʼn',
    'ǰ' => 'ǰ',
    'ΐ' => 'ΐ',
    'ΰ' => 'ΰ',
    'և' => 'եւ',
    'ẖ' => 'ẖ',
    'ẗ' => 'ẗ',
    'ẘ' => 'ẘ',
    'ẙ' => 'ẙ',
    'ẚ' => 'aʾ',
    'ẞ' => 'ss',
    'ὐ' => 'ὐ',
    'ὒ' => 'ὒ',
    'ὔ' => 'ὔ',
    'ὖ' => 'ὖ',
    'ᾀ' => 'ἀι',
    'ᾁ' => 'ἁι',
    'ᾂ' => 'ἂι',
    'ᾃ' => 'ἃι',
    'ᾄ' => 'ἄι',
    'ᾅ' => 'ἅι',
    'ᾆ' => 'ἆι',
    'ᾇ' => 'ἇι',
    'ᾈ' => 'ἀι',
    'ᾉ' => 'ἁι',
    'ᾊ' => 'ἂι',
    'ᾋ' => 'ἃι',
    'ᾌ' => 'ἄι',
    'ᾍ' => 'ἅι',
    'ᾎ' => 'ἆι',
    'ᾏ' => 'ἇι',
    'ᾐ' => 'ἠι',
    'ᾑ' => 'ἡι',
    'ᾒ' => 'ἢι',
    'ᾓ' => 'ἣι',
    'ᾔ' => 'ἤι',
    'ᾕ' => 'ἥι',
    'ᾖ' => 'ἦι',
    'ᾗ' => 'ἧι',
    'ᾘ' => 'ἠι',
    'ᾙ' => 'ἡι',
    'ᾚ' => 'ἢι',
    'ᾛ' => 'ἣι',
    'ᾜ' => 'ἤι',
    'ᾝ' => 'ἥι',
    'ᾞ' => 'ἦι',
    'ᾟ' => 'ἧι',
    'ᾠ' => 'ὠι',
    'ᾡ' => 'ὡι',
    'ᾢ' => 'ὢι',
    'ᾣ' => 'ὣι',
    'ᾤ' => 'ὤι',
    'ᾥ' => 'ὥι',
    'ᾦ' => 'ὦι',
    'ᾧ' => 'ὧι',
    'ᾨ' => 'ὠι',
    'ᾩ' => 'ὡι',
    'ᾪ' => 'ὢι',
    'ᾫ' => 'ὣι',
    'ᾬ' => 'ὤι',
    'ᾭ' => 'ὥι',
    'ᾮ' => 'ὦι',
    'ᾯ' => 'ὧι',
    'ᾲ' => 'ὰι',
    'ᾳ' => 'αι',
    'ᾴ' => 'άι',
    'ᾶ' => 'ᾶ',
    'ᾷ' => 'ᾶι',
    'ᾼ' => 'αι',
    'ῂ' => 'ὴι',
    'ῃ' => 'ηι',
    'ῄ' => 'ήι',
    'ῆ' => 'ῆ',
    'ῇ' => 'ῆι',
    'ῌ' => 'ηι',
    'ῒ' => 'ῒ',
    'ῖ' => 'ῖ',
    'ῗ' => 'ῗ',
    'ῢ' => 'ῢ',
    'ῤ' => 'ῤ',
    'ῦ' => 'ῦ',
    'ῧ' => 'ῧ',
    'ῲ' => 'ὼι',
    'ῳ' => 'ωι',
    'ῴ' => 'ώι',
    'ῶ' => 'ῶ',
    'ῷ' => 'ῶι',
    'ῼ' => 'ωι',
    'ﬀ' => 'ff',
    'ﬁ' => 'fi',
    'ﬂ' => 'fl',
    'ﬃ' => 'ffi',
    'ﬄ' => 'ffl',
    'ﬅ' => 'st',
    'ﬆ' => 'st',
    'ﬓ' => 'մն',
    'ﬔ' => 'մե',
    'ﬕ' => 'մի',
    'ﬖ' => 'վն',
    'ﬗ' => 'մխ',
];
<?php

return array (
  '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',
  'µ' => 'Μ',
  'à' => 'À',
  'á' => 'Á',
  'â' => 'Â',
  'ã' => 'Ã',
  'ä' => 'Ä',
  'å' => 'Å',
  'æ' => 'Æ',
  'ç' => 'Ç',
  'è' => 'È',
  'é' => 'É',
  'ê' => 'Ê',
  'ë' => 'Ë',
  'ì' => 'Ì',
  'í' => 'Í',
  'î' => 'Î',
  'ï' => 'Ï',
  'ð' => 'Ð',
  'ñ' => 'Ñ',
  'ò' => 'Ò',
  'ó' => 'Ó',
  'ô' => 'Ô',
  'õ' => 'Õ',
  'ö' => 'Ö',
  'ø' => 'Ø',
  'ù' => 'Ù',
  'ú' => 'Ú',
  'û' => 'Û',
  'ü' => 'Ü',
  'ý' => 'Ý',
  'þ' => 'Þ',
  'ÿ' => 'Ÿ',
  'ā' => 'Ā',
  'ă' => 'Ă',
  'ą' => 'Ą',
  'ć' => 'Ć',
  'ĉ' => 'Ĉ',
  'ċ' => 'Ċ',
  'č' => 'Č',
  'ď' => 'Ď',
  'đ' => 'Đ',
  'ē' => 'Ē',
  'ĕ' => 'Ĕ',
  'ė' => 'Ė',
  'ę' => 'Ę',
  'ě' => 'Ě',
  'ĝ' => 'Ĝ',
  'ğ' => 'Ğ',
  'ġ' => 'Ġ',
  'ģ' => 'Ģ',
  'ĥ' => 'Ĥ',
  'ħ' => 'Ħ',
  'ĩ' => 'Ĩ',
  'ī' => 'Ī',
  'ĭ' => 'Ĭ',
  'į' => 'Į',
  'ı' => 'I',
  'ĳ' => 'Ĳ',
  'ĵ' => 'Ĵ',
  'ķ' => 'Ķ',
  'ĺ' => 'Ĺ',
  'ļ' => 'Ļ',
  'ľ' => 'Ľ',
  'ŀ' => 'Ŀ',
  'ł' => 'Ł',
  'ń' => 'Ń',
  'ņ' => 'Ņ',
  'ň' => 'Ň',
  'ŋ' => 'Ŋ',
  'ō' => 'Ō',
  'ŏ' => 'Ŏ',
  'ő' => 'Ő',
  'œ' => 'Œ',
  'ŕ' => 'Ŕ',
  'ŗ' => 'Ŗ',
  'ř' => 'Ř',
  'ś' => 'Ś',
  'ŝ' => 'Ŝ',
  'ş' => 'Ş',
  'š' => 'Š',
  'ţ' => 'Ţ',
  'ť' => 'Ť',
  'ŧ' => 'Ŧ',
  'ũ' => 'Ũ',
  'ū' => 'Ū',
  'ŭ' => 'Ŭ',
  'ů' => 'Ů',
  'ű' => 'Ű',
  'ų' => 'Ų',
  'ŵ' => 'Ŵ',
  'ŷ' => 'Ŷ',
  'ź' => 'Ź',
  'ż' => 'Ż',
  'ž' => 'Ž',
  'ſ' => 'S',
  'ƀ' => 'Ƀ',
  'ƃ' => 'Ƃ',
  'ƅ' => 'Ƅ',
  'ƈ' => 'Ƈ',
  'ƌ' => 'Ƌ',
  'ƒ' => 'Ƒ',
  'ƕ' => 'Ƕ',
  'ƙ' => 'Ƙ',
  'ƚ' => 'Ƚ',
  'ƞ' => 'Ƞ',
  'ơ' => 'Ơ',
  'ƣ' => 'Ƣ',
  'ƥ' => 'Ƥ',
  'ƨ' => 'Ƨ',
  'ƭ' => 'Ƭ',
  'ư' => 'Ư',
  'ƴ' => 'Ƴ',
  'ƶ' => 'Ƶ',
  'ƹ' => 'Ƹ',
  'ƽ' => 'Ƽ',
  'ƿ' => 'Ƿ',
  'ǅ' => 'Ǆ',
  'ǆ' => 'Ǆ',
  'ǈ' => 'Ǉ',
  'ǉ' => 'Ǉ',
  'ǋ' => 'Ǌ',
  'ǌ' => 'Ǌ',
  'ǎ' => 'Ǎ',
  'ǐ' => 'Ǐ',
  'ǒ' => 'Ǒ',
  'ǔ' => 'Ǔ',
  'ǖ' => 'Ǖ',
  'ǘ' => 'Ǘ',
  'ǚ' => 'Ǚ',
  'ǜ' => 'Ǜ',
  'ǝ' => 'Ǝ',
  'ǟ' => 'Ǟ',
  'ǡ' => 'Ǡ',
  'ǣ' => 'Ǣ',
  'ǥ' => 'Ǥ',
  'ǧ' => 'Ǧ',
  'ǩ' => 'Ǩ',
  'ǫ' => 'Ǫ',
  'ǭ' => 'Ǭ',
  'ǯ' => 'Ǯ',
  'ǲ' => 'Ǳ',
  'ǳ' => 'Ǳ',
  'ǵ' => 'Ǵ',
  'ǹ' => 'Ǹ',
  'ǻ' => 'Ǻ',
  'ǽ' => 'Ǽ',
  'ǿ' => 'Ǿ',
  'ȁ' => 'Ȁ',
  'ȃ' => 'Ȃ',
  'ȅ' => 'Ȅ',
  'ȇ' => 'Ȇ',
  'ȉ' => 'Ȉ',
  'ȋ' => 'Ȋ',
  'ȍ' => 'Ȍ',
  'ȏ' => 'Ȏ',
  'ȑ' => 'Ȑ',
  'ȓ' => 'Ȓ',
  'ȕ' => 'Ȕ',
  'ȗ' => 'Ȗ',
  'ș' => 'Ș',
  'ț' => 'Ț',
  'ȝ' => 'Ȝ',
  'ȟ' => 'Ȟ',
  'ȣ' => 'Ȣ',
  'ȥ' => 'Ȥ',
  'ȧ' => 'Ȧ',
  'ȩ' => 'Ȩ',
  'ȫ' => 'Ȫ',
  'ȭ' => 'Ȭ',
  'ȯ' => 'Ȯ',
  'ȱ' => 'Ȱ',
  'ȳ' => 'Ȳ',
  'ȼ' => 'Ȼ',
  'ȿ' => 'Ȿ',
  'ɀ' => 'Ɀ',
  'ɂ' => 'Ɂ',
  'ɇ' => 'Ɇ',
  'ɉ' => 'Ɉ',
  'ɋ' => 'Ɋ',
  'ɍ' => 'Ɍ',
  'ɏ' => 'Ɏ',
  'ɐ' => 'Ɐ',
  'ɑ' => 'Ɑ',
  'ɒ' => 'Ɒ',
  'ɓ' => 'Ɓ',
  'ɔ' => 'Ɔ',
  'ɖ' => 'Ɖ',
  'ɗ' => 'Ɗ',
  'ə' => 'Ə',
  'ɛ' => 'Ɛ',
  'ɜ' => 'Ɜ',
  'ɠ' => 'Ɠ',
  'ɡ' => 'Ɡ',
  'ɣ' => 'Ɣ',
  'ɥ' => 'Ɥ',
  'ɦ' => 'Ɦ',
  'ɨ' => 'Ɨ',
  'ɩ' => 'Ɩ',
  'ɪ' => 'Ɪ',
  'ɫ' => 'Ɫ',
  'ɬ' => 'Ɬ',
  'ɯ' => 'Ɯ',
  'ɱ' => 'Ɱ',
  'ɲ' => 'Ɲ',
  'ɵ' => 'Ɵ',
  'ɽ' => 'Ɽ',
  'ʀ' => 'Ʀ',
  'ʂ' => 'Ʂ',
  'ʃ' => 'Ʃ',
  'ʇ' => 'Ʇ',
  'ʈ' => 'Ʈ',
  'ʉ' => 'Ʉ',
  'ʊ' => 'Ʊ',
  'ʋ' => 'Ʋ',
  'ʌ' => 'Ʌ',
  'ʒ' => 'Ʒ',
  'ʝ' => 'Ʝ',
  'ʞ' => 'Ʞ',
  'ͅ' => 'Ι',
  'ͱ' => 'Ͱ',
  'ͳ' => 'Ͳ',
  'ͷ' => 'Ͷ',
  'ͻ' => 'Ͻ',
  'ͼ' => 'Ͼ',
  'ͽ' => 'Ͽ',
  'ά' => 'Ά',
  'έ' => 'Έ',
  'ή' => 'Ή',
  'ί' => 'Ί',
  'α' => 'Α',
  'β' => 'Β',
  'γ' => 'Γ',
  'δ' => 'Δ',
  'ε' => 'Ε',
  'ζ' => 'Ζ',
  'η' => 'Η',
  'θ' => 'Θ',
  'ι' => 'Ι',
  'κ' => 'Κ',
  'λ' => 'Λ',
  'μ' => 'Μ',
  'ν' => 'Ν',
  'ξ' => 'Ξ',
  'ο' => 'Ο',
  'π' => 'Π',
  'ρ' => 'Ρ',
  'ς' => 'Σ',
  'σ' => 'Σ',
  'τ' => 'Τ',
  'υ' => 'Υ',
  'φ' => 'Φ',
  'χ' => 'Χ',
  'ψ' => 'Ψ',
  'ω' => 'Ω',
  'ϊ' => 'Ϊ',
  'ϋ' => 'Ϋ',
  'ό' => 'Ό',
  'ύ' => 'Ύ',
  'ώ' => 'Ώ',
  'ϐ' => 'Β',
  'ϑ' => 'Θ',
  'ϕ' => 'Φ',
  'ϖ' => 'Π',
  'ϗ' => 'Ϗ',
  'ϙ' => 'Ϙ',
  'ϛ' => 'Ϛ',
  'ϝ' => 'Ϝ',
  'ϟ' => 'Ϟ',
  'ϡ' => 'Ϡ',
  'ϣ' => 'Ϣ',
  'ϥ' => 'Ϥ',
  'ϧ' => 'Ϧ',
  'ϩ' => 'Ϩ',
  'ϫ' => 'Ϫ',
  'ϭ' => 'Ϭ',
  'ϯ' => 'Ϯ',
  'ϰ' => 'Κ',
  'ϱ' => 'Ρ',
  'ϲ' => 'Ϲ',
  'ϳ' => 'Ϳ',
  'ϵ' => 'Ε',
  'ϸ' => 'Ϸ',
  'ϻ' => 'Ϻ',
  'а' => 'А',
  'б' => 'Б',
  'в' => 'В',
  'г' => 'Г',
  'д' => 'Д',
  'е' => 'Е',
  'ж' => 'Ж',
  'з' => 'З',
  'и' => 'И',
  'й' => 'Й',
  'к' => 'К',
  'л' => 'Л',
  'м' => 'М',
  'н' => 'Н',
  'о' => 'О',
  'п' => 'П',
  'р' => 'Р',
  'с' => 'С',
  'т' => 'Т',
  'у' => 'У',
  'ф' => 'Ф',
  'х' => 'Х',
  'ц' => 'Ц',
  'ч' => 'Ч',
  'ш' => 'Ш',
  'щ' => 'Щ',
  'ъ' => 'Ъ',
  'ы' => 'Ы',
  'ь' => 'Ь',
  'э' => 'Э',
  'ю' => 'Ю',
  'я' => 'Я',
  'ѐ' => 'Ѐ',
  'ё' => 'Ё',
  'ђ' => 'Ђ',
  'ѓ' => 'Ѓ',
  'є' => 'Є',
  'ѕ' => 'Ѕ',
  'і' => 'І',
  'ї' => 'Ї',
  'ј' => 'Ј',
  'љ' => 'Љ',
  'њ' => 'Њ',
  'ћ' => 'Ћ',
  'ќ' => 'Ќ',
  'ѝ' => 'Ѝ',
  'ў' => 'Ў',
  'џ' => 'Џ',
  'ѡ' => 'Ѡ',
  'ѣ' => 'Ѣ',
  'ѥ' => 'Ѥ',
  'ѧ' => 'Ѧ',
  'ѩ' => 'Ѩ',
  'ѫ' => 'Ѫ',
  'ѭ' => 'Ѭ',
  'ѯ' => 'Ѯ',
  'ѱ' => 'Ѱ',
  'ѳ' => 'Ѳ',
  'ѵ' => 'Ѵ',
  'ѷ' => 'Ѷ',
  'ѹ' => 'Ѹ',
  'ѻ' => 'Ѻ',
  'ѽ' => 'Ѽ',
  'ѿ' => 'Ѿ',
  'ҁ' => 'Ҁ',
  'ҋ' => 'Ҋ',
  'ҍ' => 'Ҍ',
  'ҏ' => 'Ҏ',
  'ґ' => 'Ґ',
  'ғ' => 'Ғ',
  'ҕ' => 'Ҕ',
  'җ' => 'Җ',
  'ҙ' => 'Ҙ',
  'қ' => 'Қ',
  'ҝ' => 'Ҝ',
  'ҟ' => 'Ҟ',
  'ҡ' => 'Ҡ',
  'ң' => 'Ң',
  'ҥ' => 'Ҥ',
  'ҧ' => 'Ҧ',
  'ҩ' => 'Ҩ',
  'ҫ' => 'Ҫ',
  'ҭ' => 'Ҭ',
  'ү' => 'Ү',
  'ұ' => 'Ұ',
  'ҳ' => 'Ҳ',
  'ҵ' => 'Ҵ',
  'ҷ' => 'Ҷ',
  'ҹ' => 'Ҹ',
  'һ' => 'Һ',
  'ҽ' => 'Ҽ',
  'ҿ' => 'Ҿ',
  'ӂ' => 'Ӂ',
  'ӄ' => 'Ӄ',
  'ӆ' => 'Ӆ',
  'ӈ' => 'Ӈ',
  'ӊ' => 'Ӊ',
  'ӌ' => 'Ӌ',
  'ӎ' => 'Ӎ',
  'ӏ' => 'Ӏ',
  'ӑ' => 'Ӑ',
  'ӓ' => 'Ӓ',
  'ӕ' => 'Ӕ',
  'ӗ' => 'Ӗ',
  'ә' => 'Ә',
  'ӛ' => 'Ӛ',
  'ӝ' => 'Ӝ',
  'ӟ' => 'Ӟ',
  'ӡ' => 'Ӡ',
  'ӣ' => 'Ӣ',
  'ӥ' => 'Ӥ',
  'ӧ' => 'Ӧ',
  'ө' => 'Ө',
  'ӫ' => 'Ӫ',
  'ӭ' => 'Ӭ',
  'ӯ' => 'Ӯ',
  'ӱ' => 'Ӱ',
  'ӳ' => 'Ӳ',
  'ӵ' => 'Ӵ',
  'ӷ' => 'Ӷ',
  'ӹ' => 'Ӹ',
  'ӻ' => 'Ӻ',
  'ӽ' => 'Ӽ',
  'ӿ' => 'Ӿ',
  'ԁ' => 'Ԁ',
  'ԃ' => 'Ԃ',
  'ԅ' => 'Ԅ',
  'ԇ' => 'Ԇ',
  'ԉ' => 'Ԉ',
  'ԋ' => 'Ԋ',
  'ԍ' => 'Ԍ',
  'ԏ' => 'Ԏ',
  'ԑ' => 'Ԑ',
  'ԓ' => 'Ԓ',
  'ԕ' => 'Ԕ',
  'ԗ' => 'Ԗ',
  'ԙ' => 'Ԙ',
  'ԛ' => 'Ԛ',
  'ԝ' => 'Ԝ',
  'ԟ' => 'Ԟ',
  'ԡ' => 'Ԡ',
  'ԣ' => 'Ԣ',
  'ԥ' => 'Ԥ',
  'ԧ' => 'Ԧ',
  'ԩ' => 'Ԩ',
  'ԫ' => 'Ԫ',
  'ԭ' => 'Ԭ',
  'ԯ' => 'Ԯ',
  'ա' => 'Ա',
  'բ' => 'Բ',
  'գ' => 'Գ',
  'դ' => 'Դ',
  'ե' => 'Ե',
  'զ' => 'Զ',
  'է' => 'Է',
  'ը' => 'Ը',
  'թ' => 'Թ',
  'ժ' => 'Ժ',
  'ի' => 'Ի',
  'լ' => 'Լ',
  'խ' => 'Խ',
  'ծ' => 'Ծ',
  'կ' => 'Կ',
  'հ' => 'Հ',
  'ձ' => 'Ձ',
  'ղ' => 'Ղ',
  'ճ' => 'Ճ',
  'մ' => 'Մ',
  'յ' => 'Յ',
  'ն' => 'Ն',
  'շ' => 'Շ',
  'ո' => 'Ո',
  'չ' => 'Չ',
  'պ' => 'Պ',
  'ջ' => 'Ջ',
  'ռ' => 'Ռ',
  'ս' => 'Ս',
  'վ' => 'Վ',
  'տ' => 'Տ',
  'ր' => 'Ր',
  'ց' => 'Ց',
  'ւ' => 'Ւ',
  'փ' => 'Փ',
  'ք' => 'Ք',
  'օ' => 'Օ',
  'ֆ' => 'Ֆ',
  'ა' => 'Ა',
  'ბ' => 'Ბ',
  'გ' => 'Გ',
  'დ' => 'Დ',
  'ე' => 'Ე',
  'ვ' => 'Ვ',
  'ზ' => 'Ზ',
  'თ' => 'Თ',
  'ი' => 'Ი',
  'კ' => 'Კ',
  'ლ' => 'Ლ',
  'მ' => 'Მ',
  'ნ' => 'Ნ',
  'ო' => 'Ო',
  'პ' => 'Პ',
  'ჟ' => 'Ჟ',
  'რ' => 'Რ',
  'ს' => 'Ს',
  'ტ' => 'Ტ',
  'უ' => 'Უ',
  'ფ' => 'Ფ',
  'ქ' => 'Ქ',
  'ღ' => 'Ღ',
  'ყ' => 'Ყ',
  'შ' => 'Შ',
  'ჩ' => 'Ჩ',
  'ც' => 'Ც',
  'ძ' => 'Ძ',
  'წ' => 'Წ',
  'ჭ' => 'Ჭ',
  'ხ' => 'Ხ',
  'ჯ' => 'Ჯ',
  'ჰ' => 'Ჰ',
  'ჱ' => 'Ჱ',
  'ჲ' => 'Ჲ',
  'ჳ' => 'Ჳ',
  'ჴ' => 'Ჴ',
  'ჵ' => 'Ჵ',
  'ჶ' => 'Ჶ',
  'ჷ' => 'Ჷ',
  'ჸ' => 'Ჸ',
  'ჹ' => 'Ჹ',
  'ჺ' => 'Ჺ',
  'ჽ' => 'Ჽ',
  'ჾ' => 'Ჾ',
  'ჿ' => 'Ჿ',
  'ᏸ' => 'Ᏸ',
  'ᏹ' => 'Ᏹ',
  'ᏺ' => 'Ᏺ',
  'ᏻ' => 'Ᏻ',
  'ᏼ' => 'Ᏼ',
  'ᏽ' => 'Ᏽ',
  'ᲀ' => 'В',
  'ᲁ' => 'Д',
  'ᲂ' => 'О',
  'ᲃ' => 'С',
  'ᲄ' => 'Т',
  'ᲅ' => 'Т',
  'ᲆ' => 'Ъ',
  'ᲇ' => 'Ѣ',
  'ᲈ' => 'Ꙋ',
  'ᵹ' => 'Ᵹ',
  'ᵽ' => 'Ᵽ',
  'ᶎ' => 'Ᶎ',
  'ḁ' => 'Ḁ',
  'ḃ' => 'Ḃ',
  'ḅ' => 'Ḅ',
  'ḇ' => 'Ḇ',
  'ḉ' => 'Ḉ',
  'ḋ' => 'Ḋ',
  'ḍ' => 'Ḍ',
  'ḏ' => 'Ḏ',
  'ḑ' => 'Ḑ',
  'ḓ' => 'Ḓ',
  'ḕ' => 'Ḕ',
  'ḗ' => 'Ḗ',
  'ḙ' => 'Ḙ',
  'ḛ' => 'Ḛ',
  'ḝ' => 'Ḝ',
  'ḟ' => 'Ḟ',
  'ḡ' => 'Ḡ',
  'ḣ' => 'Ḣ',
  'ḥ' => 'Ḥ',
  'ḧ' => 'Ḧ',
  'ḩ' => 'Ḩ',
  'ḫ' => 'Ḫ',
  'ḭ' => 'Ḭ',
  'ḯ' => 'Ḯ',
  'ḱ' => 'Ḱ',
  'ḳ' => 'Ḳ',
  'ḵ' => 'Ḵ',
  'ḷ' => 'Ḷ',
  'ḹ' => 'Ḹ',
  'ḻ' => 'Ḻ',
  'ḽ' => 'Ḽ',
  'ḿ' => 'Ḿ',
  'ṁ' => 'Ṁ',
  'ṃ' => 'Ṃ',
  'ṅ' => 'Ṅ',
  'ṇ' => 'Ṇ',
  'ṉ' => 'Ṉ',
  'ṋ' => 'Ṋ',
  'ṍ' => 'Ṍ',
  'ṏ' => 'Ṏ',
  'ṑ' => 'Ṑ',
  'ṓ' => 'Ṓ',
  'ṕ' => 'Ṕ',
  'ṗ' => 'Ṗ',
  'ṙ' => 'Ṙ',
  'ṛ' => 'Ṛ',
  'ṝ' => 'Ṝ',
  'ṟ' => 'Ṟ',
  'ṡ' => 'Ṡ',
  'ṣ' => 'Ṣ',
  'ṥ' => 'Ṥ',
  'ṧ' => 'Ṧ',
  'ṩ' => 'Ṩ',
  'ṫ' => 'Ṫ',
  'ṭ' => 'Ṭ',
  'ṯ' => 'Ṯ',
  'ṱ' => 'Ṱ',
  'ṳ' => 'Ṳ',
  'ṵ' => 'Ṵ',
  'ṷ' => 'Ṷ',
  'ṹ' => 'Ṹ',
  'ṻ' => 'Ṻ',
  'ṽ' => 'Ṽ',
  'ṿ' => 'Ṿ',
  'ẁ' => 'Ẁ',
  'ẃ' => 'Ẃ',
  'ẅ' => 'Ẅ',
  'ẇ' => 'Ẇ',
  'ẉ' => 'Ẉ',
  'ẋ' => 'Ẋ',
  'ẍ' => 'Ẍ',
  'ẏ' => 'Ẏ',
  'ẑ' => 'Ẑ',
  'ẓ' => 'Ẓ',
  'ẕ' => 'Ẕ',
  'ẛ' => 'Ṡ',
  'ạ' => 'Ạ',
  'ả' => 'Ả',
  'ấ' => 'Ấ',
  'ầ' => 'Ầ',
  'ẩ' => 'Ẩ',
  'ẫ' => 'Ẫ',
  'ậ' => 'Ậ',
  'ắ' => 'Ắ',
  'ằ' => 'Ằ',
  'ẳ' => 'Ẳ',
  'ẵ' => 'Ẵ',
  'ặ' => 'Ặ',
  'ẹ' => 'Ẹ',
  'ẻ' => 'Ẻ',
  'ẽ' => 'Ẽ',
  'ế' => 'Ế',
  'ề' => 'Ề',
  'ể' => 'Ể',
  'ễ' => 'Ễ',
  'ệ' => 'Ệ',
  'ỉ' => 'Ỉ',
  'ị' => 'Ị',
  'ọ' => 'Ọ',
  'ỏ' => 'Ỏ',
  'ố' => 'Ố',
  'ồ' => 'Ồ',
  'ổ' => 'Ổ',
  'ỗ' => 'Ỗ',
  'ộ' => 'Ộ',
  'ớ' => 'Ớ',
  'ờ' => 'Ờ',
  'ở' => 'Ở',
  'ỡ' => 'Ỡ',
  'ợ' => 'Ợ',
  'ụ' => 'Ụ',
  'ủ' => 'Ủ',
  'ứ' => 'Ứ',
  'ừ' => 'Ừ',
  'ử' => 'Ử',
  'ữ' => 'Ữ',
  'ự' => 'Ự',
  'ỳ' => 'Ỳ',
  'ỵ' => 'Ỵ',
  'ỷ' => 'Ỷ',
  'ỹ' => 'Ỹ',
  'ỻ' => 'Ỻ',
  'ỽ' => 'Ỽ',
  'ỿ' => 'Ỿ',
  'ἀ' => 'Ἀ',
  'ἁ' => 'Ἁ',
  'ἂ' => 'Ἂ',
  'ἃ' => 'Ἃ',
  'ἄ' => 'Ἄ',
  'ἅ' => 'Ἅ',
  'ἆ' => 'Ἆ',
  'ἇ' => 'Ἇ',
  'ἐ' => 'Ἐ',
  'ἑ' => 'Ἑ',
  'ἒ' => 'Ἒ',
  'ἓ' => 'Ἓ',
  'ἔ' => 'Ἔ',
  'ἕ' => 'Ἕ',
  'ἠ' => 'Ἠ',
  'ἡ' => 'Ἡ',
  'ἢ' => 'Ἢ',
  'ἣ' => 'Ἣ',
  'ἤ' => 'Ἤ',
  'ἥ' => 'Ἥ',
  'ἦ' => 'Ἦ',
  'ἧ' => 'Ἧ',
  'ἰ' => 'Ἰ',
  'ἱ' => 'Ἱ',
  'ἲ' => 'Ἲ',
  'ἳ' => 'Ἳ',
  'ἴ' => 'Ἴ',
  'ἵ' => 'Ἵ',
  'ἶ' => 'Ἶ',
  'ἷ' => 'Ἷ',
  'ὀ' => 'Ὀ',
  'ὁ' => 'Ὁ',
  'ὂ' => 'Ὂ',
  'ὃ' => 'Ὃ',
  'ὄ' => 'Ὄ',
  'ὅ' => 'Ὅ',
  'ὑ' => 'Ὑ',
  'ὓ' => 'Ὓ',
  'ὕ' => 'Ὕ',
  'ὗ' => 'Ὗ',
  'ὠ' => 'Ὠ',
  'ὡ' => 'Ὡ',
  'ὢ' => 'Ὢ',
  'ὣ' => 'Ὣ',
  'ὤ' => 'Ὤ',
  'ὥ' => 'Ὥ',
  'ὦ' => 'Ὦ',
  'ὧ' => 'Ὧ',
  'ὰ' => 'Ὰ',
  'ά' => 'Ά',
  'ὲ' => 'Ὲ',
  'έ' => 'Έ',
  'ὴ' => 'Ὴ',
  'ή' => 'Ή',
  'ὶ' => 'Ὶ',
  'ί' => 'Ί',
  'ὸ' => 'Ὸ',
  'ό' => 'Ό',
  'ὺ' => 'Ὺ',
  'ύ' => 'Ύ',
  'ὼ' => 'Ὼ',
  'ώ' => 'Ώ',
  'ᾀ' => 'ἈΙ',
  'ᾁ' => 'ἉΙ',
  'ᾂ' => 'ἊΙ',
  'ᾃ' => 'ἋΙ',
  'ᾄ' => 'ἌΙ',
  'ᾅ' => 'ἍΙ',
  'ᾆ' => 'ἎΙ',
  'ᾇ' => 'ἏΙ',
  'ᾐ' => 'ἨΙ',
  'ᾑ' => 'ἩΙ',
  'ᾒ' => 'ἪΙ',
  'ᾓ' => 'ἫΙ',
  'ᾔ' => 'ἬΙ',
  'ᾕ' => 'ἭΙ',
  'ᾖ' => 'ἮΙ',
  'ᾗ' => 'ἯΙ',
  'ᾠ' => 'ὨΙ',
  'ᾡ' => 'ὩΙ',
  'ᾢ' => 'ὪΙ',
  'ᾣ' => 'ὫΙ',
  'ᾤ' => 'ὬΙ',
  'ᾥ' => 'ὭΙ',
  'ᾦ' => 'ὮΙ',
  'ᾧ' => 'ὯΙ',
  'ᾰ' => 'Ᾰ',
  'ᾱ' => 'Ᾱ',
  'ᾳ' => 'ΑΙ',
  'ι' => 'Ι',
  'ῃ' => 'ΗΙ',
  'ῐ' => 'Ῐ',
  'ῑ' => 'Ῑ',
  'ῠ' => 'Ῠ',
  'ῡ' => 'Ῡ',
  'ῥ' => 'Ῥ',
  'ῳ' => 'ΩΙ',
  'ⅎ' => 'Ⅎ',
  'ⅰ' => 'Ⅰ',
  'ⅱ' => 'Ⅱ',
  'ⅲ' => 'Ⅲ',
  'ⅳ' => 'Ⅳ',
  'ⅴ' => 'Ⅴ',
  'ⅵ' => 'Ⅵ',
  'ⅶ' => 'Ⅶ',
  'ⅷ' => 'Ⅷ',
  'ⅸ' => 'Ⅸ',
  'ⅹ' => 'Ⅹ',
  'ⅺ' => 'Ⅺ',
  'ⅻ' => 'Ⅻ',
  'ⅼ' => 'Ⅼ',
  'ⅽ' => 'Ⅽ',
  'ⅾ' => 'Ⅾ',
  'ⅿ' => 'Ⅿ',
  'ↄ' => 'Ↄ',
  'ⓐ' => 'Ⓐ',
  'ⓑ' => 'Ⓑ',
  'ⓒ' => 'Ⓒ',
  'ⓓ' => 'Ⓓ',
  'ⓔ' => 'Ⓔ',
  'ⓕ' => 'Ⓕ',
  'ⓖ' => 'Ⓖ',
  'ⓗ' => 'Ⓗ',
  'ⓘ' => 'Ⓘ',
  'ⓙ' => 'Ⓙ',
  'ⓚ' => 'Ⓚ',
  'ⓛ' => 'Ⓛ',
  'ⓜ' => 'Ⓜ',
  'ⓝ' => 'Ⓝ',
  'ⓞ' => 'Ⓞ',
  'ⓟ' => 'Ⓟ',
  'ⓠ' => 'Ⓠ',
  'ⓡ' => 'Ⓡ',
  'ⓢ' => 'Ⓢ',
  'ⓣ' => 'Ⓣ',
  'ⓤ' => 'Ⓤ',
  'ⓥ' => 'Ⓥ',
  'ⓦ' => 'Ⓦ',
  'ⓧ' => 'Ⓧ',
  'ⓨ' => 'Ⓨ',
  'ⓩ' => 'Ⓩ',
  'ⰰ' => 'Ⰰ',
  'ⰱ' => 'Ⰱ',
  'ⰲ' => 'Ⰲ',
  'ⰳ' => 'Ⰳ',
  'ⰴ' => 'Ⰴ',
  'ⰵ' => 'Ⰵ',
  'ⰶ' => 'Ⰶ',
  'ⰷ' => 'Ⰷ',
  'ⰸ' => 'Ⰸ',
  'ⰹ' => 'Ⰹ',
  'ⰺ' => 'Ⰺ',
  'ⰻ' => 'Ⰻ',
  'ⰼ' => 'Ⰼ',
  'ⰽ' => 'Ⰽ',
  'ⰾ' => 'Ⰾ',
  'ⰿ' => 'Ⰿ',
  'ⱀ' => 'Ⱀ',
  'ⱁ' => 'Ⱁ',
  'ⱂ' => 'Ⱂ',
  'ⱃ' => 'Ⱃ',
  'ⱄ' => 'Ⱄ',
  'ⱅ' => 'Ⱅ',
  'ⱆ' => 'Ⱆ',
  'ⱇ' => 'Ⱇ',
  'ⱈ' => 'Ⱈ',
  'ⱉ' => 'Ⱉ',
  'ⱊ' => 'Ⱊ',
  'ⱋ' => 'Ⱋ',
  'ⱌ' => 'Ⱌ',
  'ⱍ' => 'Ⱍ',
  'ⱎ' => 'Ⱎ',
  'ⱏ' => 'Ⱏ',
  'ⱐ' => 'Ⱐ',
  'ⱑ' => 'Ⱑ',
  'ⱒ' => 'Ⱒ',
  'ⱓ' => 'Ⱓ',
  'ⱔ' => 'Ⱔ',
  'ⱕ' => 'Ⱕ',
  'ⱖ' => 'Ⱖ',
  'ⱗ' => 'Ⱗ',
  'ⱘ' => 'Ⱘ',
  'ⱙ' => 'Ⱙ',
  'ⱚ' => 'Ⱚ',
  'ⱛ' => 'Ⱛ',
  'ⱜ' => 'Ⱜ',
  'ⱝ' => 'Ⱝ',
  'ⱞ' => 'Ⱞ',
  'ⱡ' => 'Ⱡ',
  'ⱥ' => 'Ⱥ',
  'ⱦ' => 'Ⱦ',
  'ⱨ' => 'Ⱨ',
  'ⱪ' => 'Ⱪ',
  'ⱬ' => 'Ⱬ',
  'ⱳ' => 'Ⱳ',
  'ⱶ' => 'Ⱶ',
  'ⲁ' => 'Ⲁ',
  'ⲃ' => 'Ⲃ',
  'ⲅ' => 'Ⲅ',
  'ⲇ' => 'Ⲇ',
  'ⲉ' => 'Ⲉ',
  'ⲋ' => 'Ⲋ',
  'ⲍ' => 'Ⲍ',
  'ⲏ' => 'Ⲏ',
  'ⲑ' => 'Ⲑ',
  'ⲓ' => 'Ⲓ',
  'ⲕ' => 'Ⲕ',
  'ⲗ' => 'Ⲗ',
  'ⲙ' => 'Ⲙ',
  'ⲛ' => 'Ⲛ',
  'ⲝ' => 'Ⲝ',
  'ⲟ' => 'Ⲟ',
  'ⲡ' => 'Ⲡ',
  'ⲣ' => 'Ⲣ',
  'ⲥ' => 'Ⲥ',
  'ⲧ' => 'Ⲧ',
  'ⲩ' => 'Ⲩ',
  'ⲫ' => 'Ⲫ',
  'ⲭ' => 'Ⲭ',
  'ⲯ' => 'Ⲯ',
  'ⲱ' => 'Ⲱ',
  'ⲳ' => 'Ⲳ',
  'ⲵ' => 'Ⲵ',
  'ⲷ' => 'Ⲷ',
  'ⲹ' => 'Ⲹ',
  'ⲻ' => 'Ⲻ',
  'ⲽ' => 'Ⲽ',
  'ⲿ' => 'Ⲿ',
  'ⳁ' => 'Ⳁ',
  'ⳃ' => 'Ⳃ',
  'ⳅ' => 'Ⳅ',
  'ⳇ' => 'Ⳇ',
  'ⳉ' => 'Ⳉ',
  'ⳋ' => 'Ⳋ',
  'ⳍ' => 'Ⳍ',
  'ⳏ' => 'Ⳏ',
  'ⳑ' => 'Ⳑ',
  'ⳓ' => 'Ⳓ',
  'ⳕ' => 'Ⳕ',
  'ⳗ' => 'Ⳗ',
  'ⳙ' => 'Ⳙ',
  'ⳛ' => 'Ⳛ',
  'ⳝ' => 'Ⳝ',
  'ⳟ' => 'Ⳟ',
  'ⳡ' => 'Ⳡ',
  'ⳣ' => 'Ⳣ',
  'ⳬ' => 'Ⳬ',
  'ⳮ' => 'Ⳮ',
  'ⳳ' => 'Ⳳ',
  'ⴀ' => 'Ⴀ',
  'ⴁ' => 'Ⴁ',
  'ⴂ' => 'Ⴂ',
  'ⴃ' => 'Ⴃ',
  'ⴄ' => 'Ⴄ',
  'ⴅ' => 'Ⴅ',
  'ⴆ' => 'Ⴆ',
  'ⴇ' => 'Ⴇ',
  'ⴈ' => 'Ⴈ',
  'ⴉ' => 'Ⴉ',
  'ⴊ' => 'Ⴊ',
  'ⴋ' => 'Ⴋ',
  'ⴌ' => 'Ⴌ',
  'ⴍ' => 'Ⴍ',
  'ⴎ' => 'Ⴎ',
  'ⴏ' => 'Ⴏ',
  'ⴐ' => 'Ⴐ',
  'ⴑ' => 'Ⴑ',
  'ⴒ' => 'Ⴒ',
  'ⴓ' => 'Ⴓ',
  'ⴔ' => 'Ⴔ',
  'ⴕ' => 'Ⴕ',
  'ⴖ' => 'Ⴖ',
  'ⴗ' => 'Ⴗ',
  'ⴘ' => 'Ⴘ',
  'ⴙ' => 'Ⴙ',
  'ⴚ' => 'Ⴚ',
  'ⴛ' => 'Ⴛ',
  'ⴜ' => 'Ⴜ',
  'ⴝ' => 'Ⴝ',
  'ⴞ' => 'Ⴞ',
  'ⴟ' => 'Ⴟ',
  'ⴠ' => 'Ⴠ',
  'ⴡ' => 'Ⴡ',
  'ⴢ' => 'Ⴢ',
  'ⴣ' => 'Ⴣ',
  'ⴤ' => 'Ⴤ',
  'ⴥ' => 'Ⴥ',
  'ⴧ' => 'Ⴧ',
  'ⴭ' => 'Ⴭ',
  'ꙁ' => 'Ꙁ',
  'ꙃ' => 'Ꙃ',
  'ꙅ' => 'Ꙅ',
  'ꙇ' => 'Ꙇ',
  'ꙉ' => 'Ꙉ',
  'ꙋ' => 'Ꙋ',
  'ꙍ' => 'Ꙍ',
  'ꙏ' => 'Ꙏ',
  'ꙑ' => 'Ꙑ',
  'ꙓ' => 'Ꙓ',
  'ꙕ' => 'Ꙕ',
  'ꙗ' => 'Ꙗ',
  'ꙙ' => 'Ꙙ',
  'ꙛ' => 'Ꙛ',
  'ꙝ' => 'Ꙝ',
  'ꙟ' => 'Ꙟ',
  'ꙡ' => 'Ꙡ',
  'ꙣ' => 'Ꙣ',
  'ꙥ' => 'Ꙥ',
  'ꙧ' => 'Ꙧ',
  'ꙩ' => 'Ꙩ',
  'ꙫ' => 'Ꙫ',
  'ꙭ' => 'Ꙭ',
  'ꚁ' => 'Ꚁ',
  'ꚃ' => 'Ꚃ',
  'ꚅ' => 'Ꚅ',
  'ꚇ' => 'Ꚇ',
  'ꚉ' => 'Ꚉ',
  'ꚋ' => 'Ꚋ',
  'ꚍ' => 'Ꚍ',
  'ꚏ' => 'Ꚏ',
  'ꚑ' => 'Ꚑ',
  'ꚓ' => 'Ꚓ',
  'ꚕ' => 'Ꚕ',
  'ꚗ' => 'Ꚗ',
  'ꚙ' => 'Ꚙ',
  'ꚛ' => 'Ꚛ',
  'ꜣ' => 'Ꜣ',
  'ꜥ' => 'Ꜥ',
  'ꜧ' => 'Ꜧ',
  'ꜩ' => 'Ꜩ',
  'ꜫ' => 'Ꜫ',
  'ꜭ' => 'Ꜭ',
  'ꜯ' => 'Ꜯ',
  'ꜳ' => 'Ꜳ',
  'ꜵ' => 'Ꜵ',
  'ꜷ' => 'Ꜷ',
  'ꜹ' => 'Ꜹ',
  'ꜻ' => 'Ꜻ',
  'ꜽ' => 'Ꜽ',
  'ꜿ' => 'Ꜿ',
  'ꝁ' => 'Ꝁ',
  'ꝃ' => 'Ꝃ',
  'ꝅ' => 'Ꝅ',
  'ꝇ' => 'Ꝇ',
  'ꝉ' => 'Ꝉ',
  'ꝋ' => 'Ꝋ',
  'ꝍ' => 'Ꝍ',
  'ꝏ' => 'Ꝏ',
  'ꝑ' => 'Ꝑ',
  'ꝓ' => 'Ꝓ',
  'ꝕ' => 'Ꝕ',
  'ꝗ' => 'Ꝗ',
  'ꝙ' => 'Ꝙ',
  'ꝛ' => 'Ꝛ',
  'ꝝ' => 'Ꝝ',
  'ꝟ' => 'Ꝟ',
  'ꝡ' => 'Ꝡ',
  'ꝣ' => 'Ꝣ',
  'ꝥ' => 'Ꝥ',
  'ꝧ' => 'Ꝧ',
  'ꝩ' => 'Ꝩ',
  'ꝫ' => 'Ꝫ',
  'ꝭ' => 'Ꝭ',
  'ꝯ' => 'Ꝯ',
  'ꝺ' => 'Ꝺ',
  'ꝼ' => 'Ꝼ',
  'ꝿ' => 'Ꝿ',
  'ꞁ' => 'Ꞁ',
  'ꞃ' => 'Ꞃ',
  'ꞅ' => 'Ꞅ',
  'ꞇ' => 'Ꞇ',
  'ꞌ' => 'Ꞌ',
  'ꞑ' => 'Ꞑ',
  'ꞓ' => 'Ꞓ',
  'ꞔ' => 'Ꞔ',
  'ꞗ' => 'Ꞗ',
  'ꞙ' => 'Ꞙ',
  'ꞛ' => 'Ꞛ',
  'ꞝ' => 'Ꞝ',
  'ꞟ' => 'Ꞟ',
  'ꞡ' => 'Ꞡ',
  'ꞣ' => 'Ꞣ',
  'ꞥ' => 'Ꞥ',
  'ꞧ' => 'Ꞧ',
  'ꞩ' => 'Ꞩ',
  'ꞵ' => 'Ꞵ',
  'ꞷ' => 'Ꞷ',
  'ꞹ' => 'Ꞹ',
  'ꞻ' => 'Ꞻ',
  'ꞽ' => 'Ꞽ',
  'ꞿ' => 'Ꞿ',
  'ꟃ' => 'Ꟃ',
  'ꟈ' => 'Ꟈ',
  'ꟊ' => 'Ꟊ',
  'ꟶ' => 'Ꟶ',
  'ꭓ' => 'Ꭓ',
  'ꭰ' => 'Ꭰ',
  'ꭱ' => 'Ꭱ',
  'ꭲ' => 'Ꭲ',
  'ꭳ' => 'Ꭳ',
  'ꭴ' => 'Ꭴ',
  'ꭵ' => 'Ꭵ',
  'ꭶ' => 'Ꭶ',
  'ꭷ' => 'Ꭷ',
  'ꭸ' => 'Ꭸ',
  'ꭹ' => 'Ꭹ',
  'ꭺ' => 'Ꭺ',
  'ꭻ' => 'Ꭻ',
  'ꭼ' => 'Ꭼ',
  'ꭽ' => 'Ꭽ',
  'ꭾ' => 'Ꭾ',
  'ꭿ' => 'Ꭿ',
  'ꮀ' => 'Ꮀ',
  'ꮁ' => 'Ꮁ',
  'ꮂ' => 'Ꮂ',
  'ꮃ' => 'Ꮃ',
  'ꮄ' => 'Ꮄ',
  'ꮅ' => 'Ꮅ',
  'ꮆ' => 'Ꮆ',
  'ꮇ' => 'Ꮇ',
  'ꮈ' => 'Ꮈ',
  'ꮉ' => 'Ꮉ',
  'ꮊ' => 'Ꮊ',
  'ꮋ' => 'Ꮋ',
  'ꮌ' => 'Ꮌ',
  'ꮍ' => 'Ꮍ',
  'ꮎ' => 'Ꮎ',
  'ꮏ' => 'Ꮏ',
  'ꮐ' => 'Ꮐ',
  'ꮑ' => 'Ꮑ',
  'ꮒ' => 'Ꮒ',
  'ꮓ' => 'Ꮓ',
  'ꮔ' => 'Ꮔ',
  'ꮕ' => 'Ꮕ',
  'ꮖ' => 'Ꮖ',
  'ꮗ' => 'Ꮗ',
  'ꮘ' => 'Ꮘ',
  'ꮙ' => 'Ꮙ',
  'ꮚ' => 'Ꮚ',
  'ꮛ' => 'Ꮛ',
  'ꮜ' => 'Ꮜ',
  'ꮝ' => 'Ꮝ',
  'ꮞ' => 'Ꮞ',
  'ꮟ' => 'Ꮟ',
  'ꮠ' => 'Ꮠ',
  'ꮡ' => 'Ꮡ',
  'ꮢ' => 'Ꮢ',
  'ꮣ' => 'Ꮣ',
  'ꮤ' => 'Ꮤ',
  'ꮥ' => 'Ꮥ',
  'ꮦ' => 'Ꮦ',
  'ꮧ' => 'Ꮧ',
  'ꮨ' => 'Ꮨ',
  'ꮩ' => 'Ꮩ',
  'ꮪ' => 'Ꮪ',
  'ꮫ' => 'Ꮫ',
  'ꮬ' => 'Ꮬ',
  'ꮭ' => 'Ꮭ',
  'ꮮ' => 'Ꮮ',
  'ꮯ' => 'Ꮯ',
  'ꮰ' => 'Ꮰ',
  'ꮱ' => 'Ꮱ',
  'ꮲ' => 'Ꮲ',
  'ꮳ' => 'Ꮳ',
  'ꮴ' => 'Ꮴ',
  'ꮵ' => 'Ꮵ',
  'ꮶ' => 'Ꮶ',
  'ꮷ' => 'Ꮷ',
  'ꮸ' => 'Ꮸ',
  'ꮹ' => 'Ꮹ',
  'ꮺ' => 'Ꮺ',
  'ꮻ' => 'Ꮻ',
  'ꮼ' => 'Ꮼ',
  'ꮽ' => 'Ꮽ',
  'ꮾ' => 'Ꮾ',
  'ꮿ' => 'Ꮿ',
  'ａ' => 'Ａ',
  'ｂ' => 'Ｂ',
  'ｃ' => 'Ｃ',
  'ｄ' => 'Ｄ',
  'ｅ' => 'Ｅ',
  'ｆ' => 'Ｆ',
  'ｇ' => 'Ｇ',
  'ｈ' => 'Ｈ',
  'ｉ' => 'Ｉ',
  'ｊ' => 'Ｊ',
  'ｋ' => 'Ｋ',
  'ｌ' => 'Ｌ',
  'ｍ' => 'Ｍ',
  'ｎ' => 'Ｎ',
  'ｏ' => 'Ｏ',
  'ｐ' => 'Ｐ',
  'ｑ' => 'Ｑ',
  'ｒ' => 'Ｒ',
  'ｓ' => 'Ｓ',
  'ｔ' => 'Ｔ',
  'ｕ' => 'Ｕ',
  'ｖ' => 'Ｖ',
  'ｗ' => 'Ｗ',
  'ｘ' => 'Ｘ',
  'ｙ' => 'Ｙ',
  'ｚ' => 'Ｚ',
  '𐐨' => '𐐀',
  '𐐩' => '𐐁',
  '𐐪' => '𐐂',
  '𐐫' => '𐐃',
  '𐐬' => '𐐄',
  '𐐭' => '𐐅',
  '𐐮' => '𐐆',
  '𐐯' => '𐐇',
  '𐐰' => '𐐈',
  '𐐱' => '𐐉',
  '𐐲' => '𐐊',
  '𐐳' => '𐐋',
  '𐐴' => '𐐌',
  '𐐵' => '𐐍',
  '𐐶' => '𐐎',
  '𐐷' => '𐐏',
  '𐐸' => '𐐐',
  '𐐹' => '𐐑',
  '𐐺' => '𐐒',
  '𐐻' => '𐐓',
  '𐐼' => '𐐔',
  '𐐽' => '𐐕',
  '𐐾' => '𐐖',
  '𐐿' => '𐐗',
  '𐑀' => '𐐘',
  '𐑁' => '𐐙',
  '𐑂' => '𐐚',
  '𐑃' => '𐐛',
  '𐑄' => '𐐜',
  '𐑅' => '𐐝',
  '𐑆' => '𐐞',
  '𐑇' => '𐐟',
  '𐑈' => '𐐠',
  '𐑉' => '𐐡',
  '𐑊' => '𐐢',
  '𐑋' => '𐐣',
  '𐑌' => '𐐤',
  '𐑍' => '𐐥',
  '𐑎' => '𐐦',
  '𐑏' => '𐐧',
  '𐓘' => '𐒰',
  '𐓙' => '𐒱',
  '𐓚' => '𐒲',
  '𐓛' => '𐒳',
  '𐓜' => '𐒴',
  '𐓝' => '𐒵',
  '𐓞' => '𐒶',
  '𐓟' => '𐒷',
  '𐓠' => '𐒸',
  '𐓡' => '𐒹',
  '𐓢' => '𐒺',
  '𐓣' => '𐒻',
  '𐓤' => '𐒼',
  '𐓥' => '𐒽',
  '𐓦' => '𐒾',
  '𐓧' => '𐒿',
  '𐓨' => '𐓀',
  '𐓩' => '𐓁',
  '𐓪' => '𐓂',
  '𐓫' => '𐓃',
  '𐓬' => '𐓄',
  '𐓭' => '𐓅',
  '𐓮' => '𐓆',
  '𐓯' => '𐓇',
  '𐓰' => '𐓈',
  '𐓱' => '𐓉',
  '𐓲' => '𐓊',
  '𐓳' => '𐓋',
  '𐓴' => '𐓌',
  '𐓵' => '𐓍',
  '𐓶' => '𐓎',
  '𐓷' => '𐓏',
  '𐓸' => '𐓐',
  '𐓹' => '𐓑',
  '𐓺' => '𐓒',
  '𐓻' => '𐓓',
  '𐳀' => '𐲀',
  '𐳁' => '𐲁',
  '𐳂' => '𐲂',
  '𐳃' => '𐲃',
  '𐳄' => '𐲄',
  '𐳅' => '𐲅',
  '𐳆' => '𐲆',
  '𐳇' => '𐲇',
  '𐳈' => '𐲈',
  '𐳉' => '𐲉',
  '𐳊' => '𐲊',
  '𐳋' => '𐲋',
  '𐳌' => '𐲌',
  '𐳍' => '𐲍',
  '𐳎' => '𐲎',
  '𐳏' => '𐲏',
  '𐳐' => '𐲐',
  '𐳑' => '𐲑',
  '𐳒' => '𐲒',
  '𐳓' => '𐲓',
  '𐳔' => '𐲔',
  '𐳕' => '𐲕',
  '𐳖' => '𐲖',
  '𐳗' => '𐲗',
  '𐳘' => '𐲘',
  '𐳙' => '𐲙',
  '𐳚' => '𐲚',
  '𐳛' => '𐲛',
  '𐳜' => '𐲜',
  '𐳝' => '𐲝',
  '𐳞' => '𐲞',
  '𐳟' => '𐲟',
  '𐳠' => '𐲠',
  '𐳡' => '𐲡',
  '𐳢' => '𐲢',
  '𐳣' => '𐲣',
  '𐳤' => '𐲤',
  '𐳥' => '𐲥',
  '𐳦' => '𐲦',
  '𐳧' => '𐲧',
  '𐳨' => '𐲨',
  '𐳩' => '𐲩',
  '𐳪' => '𐲪',
  '𐳫' => '𐲫',
  '𐳬' => '𐲬',
  '𐳭' => '𐲭',
  '𐳮' => '𐲮',
  '𐳯' => '𐲯',
  '𐳰' => '𐲰',
  '𐳱' => '𐲱',
  '𐳲' => '𐲲',
  '𑣀' => '𑢠',
  '𑣁' => '𑢡',
  '𑣂' => '𑢢',
  '𑣃' => '𑢣',
  '𑣄' => '𑢤',
  '𑣅' => '𑢥',
  '𑣆' => '𑢦',
  '𑣇' => '𑢧',
  '𑣈' => '𑢨',
  '𑣉' => '𑢩',
  '𑣊' => '𑢪',
  '𑣋' => '𑢫',
  '𑣌' => '𑢬',
  '𑣍' => '𑢭',
  '𑣎' => '𑢮',
  '𑣏' => '𑢯',
  '𑣐' => '𑢰',
  '𑣑' => '𑢱',
  '𑣒' => '𑢲',
  '𑣓' => '𑢳',
  '𑣔' => '𑢴',
  '𑣕' => '𑢵',
  '𑣖' => '𑢶',
  '𑣗' => '𑢷',
  '𑣘' => '𑢸',
  '𑣙' => '𑢹',
  '𑣚' => '𑢺',
  '𑣛' => '𑢻',
  '𑣜' => '𑢼',
  '𑣝' => '𑢽',
  '𑣞' => '𑢾',
  '𑣟' => '𑢿',
  '𖹠' => '𖹀',
  '𖹡' => '𖹁',
  '𖹢' => '𖹂',
  '𖹣' => '𖹃',
  '𖹤' => '𖹄',
  '𖹥' => '𖹅',
  '𖹦' => '𖹆',
  '𖹧' => '𖹇',
  '𖹨' => '𖹈',
  '𖹩' => '𖹉',
  '𖹪' => '𖹊',
  '𖹫' => '𖹋',
  '𖹬' => '𖹌',
  '𖹭' => '𖹍',
  '𖹮' => '𖹎',
  '𖹯' => '𖹏',
  '𖹰' => '𖹐',
  '𖹱' => '𖹑',
  '𖹲' => '𖹒',
  '𖹳' => '𖹓',
  '𖹴' => '𖹔',
  '𖹵' => '𖹕',
  '𖹶' => '𖹖',
  '𖹷' => '𖹗',
  '𖹸' => '𖹘',
  '𖹹' => '𖹙',
  '𖹺' => '𖹚',
  '𖹻' => '𖹛',
  '𖹼' => '𖹜',
  '𖹽' => '𖹝',
  '𖹾' => '𖹞',
  '𖹿' => '𖹟',
  '𞤢' => '𞤀',
  '𞤣' => '𞤁',
  '𞤤' => '𞤂',
  '𞤥' => '𞤃',
  '𞤦' => '𞤄',
  '𞤧' => '𞤅',
  '𞤨' => '𞤆',
  '𞤩' => '𞤇',
  '𞤪' => '𞤈',
  '𞤫' => '𞤉',
  '𞤬' => '𞤊',
  '𞤭' => '𞤋',
  '𞤮' => '𞤌',
  '𞤯' => '𞤍',
  '𞤰' => '𞤎',
  '𞤱' => '𞤏',
  '𞤲' => '𞤐',
  '𞤳' => '𞤑',
  '𞤴' => '𞤒',
  '𞤵' => '𞤓',
  '𞤶' => '𞤔',
  '𞤷' => '𞤕',
  '𞤸' => '𞤖',
  '𞤹' => '𞤗',
  '𞤺' => '𞤘',
  '𞤻' => '𞤙',
  '𞤼' => '𞤚',
  '𞤽' => '𞤛',
  '𞤾' => '𞤜',
  '𞤿' => '𞤝',
  '𞥀' => '𞤞',
  '𞥁' => '𞤟',
  '𞥂' => '𞤠',
  '𞥃' => '𞤡',
  'ß' => 'SS',
  'ﬀ' => 'FF',
  'ﬁ' => 'FI',
  'ﬂ' => 'FL',
  'ﬃ' => 'FFI',
  'ﬄ' => 'FFL',
  'ﬅ' => 'ST',
  'ﬆ' => 'ST',
  'և' => 'ԵՒ',
  'ﬓ' => 'ՄՆ',
  'ﬔ' => 'ՄԵ',
  'ﬕ' => 'ՄԻ',
  'ﬖ' => 'ՎՆ',
  'ﬗ' => 'ՄԽ',
  'ŉ' => 'ʼN',
  'ΐ' => 'Ϊ́',
  'ΰ' => 'Ϋ́',
  'ǰ' => 'J̌',
  'ẖ' => 'H̱',
  'ẗ' => 'T̈',
  'ẘ' => 'W̊',
  'ẙ' => 'Y̊',
  'ẚ' => 'Aʾ',
  'ὐ' => 'Υ̓',
  'ὒ' => 'Υ̓̀',
  'ὔ' => 'Υ̓́',
  'ὖ' => 'Υ̓͂',
  'ᾶ' => 'Α͂',
  'ῆ' => 'Η͂',
  'ῒ' => 'Ϊ̀',
  'ΐ' => 'Ϊ́',
  'ῖ' => 'Ι͂',
  'ῗ' => 'Ϊ͂',
  'ῢ' => 'Ϋ̀',
  'ΰ' => 'Ϋ́',
  'ῤ' => 'Ρ̓',
  'ῦ' => 'Υ͂',
  'ῧ' => 'Ϋ͂',
  'ῶ' => 'Ω͂',
  'ᾈ' => 'ἈΙ',
  'ᾉ' => 'ἉΙ',
  'ᾊ' => 'ἊΙ',
  'ᾋ' => 'ἋΙ',
  'ᾌ' => 'ἌΙ',
  'ᾍ' => 'ἍΙ',
  'ᾎ' => 'ἎΙ',
  'ᾏ' => 'ἏΙ',
  'ᾘ' => 'ἨΙ',
  'ᾙ' => 'ἩΙ',
  'ᾚ' => 'ἪΙ',
  'ᾛ' => 'ἫΙ',
  'ᾜ' => 'ἬΙ',
  'ᾝ' => 'ἭΙ',
  'ᾞ' => 'ἮΙ',
  'ᾟ' => 'ἯΙ',
  'ᾨ' => 'ὨΙ',
  'ᾩ' => 'ὩΙ',
  'ᾪ' => 'ὪΙ',
  'ᾫ' => 'ὫΙ',
  'ᾬ' => 'ὬΙ',
  'ᾭ' => 'ὭΙ',
  'ᾮ' => 'ὮΙ',
  'ᾯ' => 'ὯΙ',
  'ᾼ' => 'ΑΙ',
  'ῌ' => 'ΗΙ',
  'ῼ' => 'ΩΙ',
  'ᾲ' => 'ᾺΙ',
  'ᾴ' => 'ΆΙ',
  'ῂ' => 'ῊΙ',
  'ῄ' => 'ΉΙ',
  'ῲ' => 'ῺΙ',
  'ῴ' => 'ΏΙ',
  'ᾷ' => 'Α͂Ι',
  'ῇ' => 'Η͂Ι',
  'ῷ' => 'Ω͂Ι',
);
<?php

// from Case_Ignorable in https://unicode.org/Public/UNIDATA/DerivedCoreProperties.txt

return '/(?<![\x{0027}\x{002E}\x{003A}\x{005E}\x{0060}\x{00A8}\x{00AD}\x{00AF}\x{00B4}\x{00B7}\x{00B8}\x{02B0}-\x{02C1}\x{02C2}-\x{02C5}\x{02C6}-\x{02D1}\x{02D2}-\x{02DF}\x{02E0}-\x{02E4}\x{02E5}-\x{02EB}\x{02EC}\x{02ED}\x{02EE}\x{02EF}-\x{02FF}\x{0300}-\x{036F}\x{0374}\x{0375}\x{037A}\x{0384}-\x{0385}\x{0387}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{0559}\x{0591}-\x{05BD}\x{05BF}\x{05C1}-\x{05C2}\x{05C4}-\x{05C5}\x{05C7}\x{05F4}\x{0600}-\x{0605}\x{0610}-\x{061A}\x{061C}\x{0640}\x{064B}-\x{065F}\x{0670}\x{06D6}-\x{06DC}\x{06DD}\x{06DF}-\x{06E4}\x{06E5}-\x{06E6}\x{06E7}-\x{06E8}\x{06EA}-\x{06ED}\x{070F}\x{0711}\x{0730}-\x{074A}\x{07A6}-\x{07B0}\x{07EB}-\x{07F3}\x{07F4}-\x{07F5}\x{07FA}\x{07FD}\x{0816}-\x{0819}\x{081A}\x{081B}-\x{0823}\x{0824}\x{0825}-\x{0827}\x{0828}\x{0829}-\x{082D}\x{0859}-\x{085B}\x{08D3}-\x{08E1}\x{08E2}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0971}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0C00}\x{0C04}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C81}\x{0CBC}\x{0CBF}\x{0CC6}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E46}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EB9}\x{0EBB}-\x{0EBC}\x{0EC6}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{10FC}\x{135D}-\x{135F}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17D7}\x{17DD}\x{180B}-\x{180D}\x{180E}\x{1843}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AA7}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1C78}-\x{1C7D}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1D2C}-\x{1D6A}\x{1D78}\x{1D9B}-\x{1DBF}\x{1DC0}-\x{1DF9}\x{1DFB}-\x{1DFF}\x{1FBD}\x{1FBF}-\x{1FC1}\x{1FCD}-\x{1FCF}\x{1FDD}-\x{1FDF}\x{1FED}-\x{1FEF}\x{1FFD}-\x{1FFE}\x{200B}-\x{200F}\x{2018}\x{2019}\x{2024}\x{2027}\x{202A}-\x{202E}\x{2060}-\x{2064}\x{2066}-\x{206F}\x{2071}\x{207F}\x{2090}-\x{209C}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2C7C}-\x{2C7D}\x{2CEF}-\x{2CF1}\x{2D6F}\x{2D7F}\x{2DE0}-\x{2DFF}\x{2E2F}\x{3005}\x{302A}-\x{302D}\x{3031}-\x{3035}\x{303B}\x{3099}-\x{309A}\x{309B}-\x{309C}\x{309D}-\x{309E}\x{30FC}-\x{30FE}\x{A015}\x{A4F8}-\x{A4FD}\x{A60C}\x{A66F}\x{A670}-\x{A672}\x{A674}-\x{A67D}\x{A67F}\x{A69C}-\x{A69D}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A700}-\x{A716}\x{A717}-\x{A71F}\x{A720}-\x{A721}\x{A770}\x{A788}\x{A789}-\x{A78A}\x{A7F8}-\x{A7F9}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}\x{A9CF}\x{A9E5}\x{A9E6}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA70}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AADD}\x{AAEC}-\x{AAED}\x{AAF3}-\x{AAF4}\x{AAF6}\x{AB5B}\x{AB5C}-\x{AB5F}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1E}\x{FBB2}-\x{FBC1}\x{FE00}-\x{FE0F}\x{FE13}\x{FE20}-\x{FE2F}\x{FE52}\x{FE55}\x{FEFF}\x{FF07}\x{FF0E}\x{FF1A}\x{FF3E}\x{FF40}\x{FF70}\x{FF9E}-\x{FF9F}\x{FFE3}\x{FFF9}-\x{FFFB}\x{101FD}\x{102E0}\x{10376}-\x{1037A}\x{10A01}-\x{10A03}\x{10A05}-\x{10A06}\x{10A0C}-\x{10A0F}\x{10A38}-\x{10A3A}\x{10A3F}\x{10AE5}-\x{10AE6}\x{10D24}-\x{10D27}\x{10F46}-\x{10F50}\x{11001}\x{11038}-\x{11046}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{110BD}\x{110CD}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{11A01}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C3F}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16B40}-\x{16B43}\x{16F8F}-\x{16F92}\x{16F93}-\x{16F9F}\x{16FE0}-\x{16FE1}\x{1BC9D}-\x{1BC9E}\x{1BCA0}-\x{1BCA3}\x{1D167}-\x{1D169}\x{1D173}-\x{1D17A}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D242}-\x{1D244}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E8D0}-\x{1E8D6}\x{1E944}-\x{1E94A}\x{1F3FB}-\x{1F3FF}\x{E0001}\x{E0020}-\x{E007F}\x{E0100}-\x{E01EF}])(\pL)(\pL*+)/u';
Copyright (c) 2015-present Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Polyfill\Mbstring;

/**
 * Partial mbstring implementation in PHP, iconv based, UTF-8 centric.
 *
 * Implemented:
 * - mb_chr                  - Returns a specific character from its Unicode code point
 * - mb_convert_encoding     - Convert character encoding
 * - mb_convert_variables    - Convert character code in variable(s)
 * - mb_decode_mimeheader    - Decode string in MIME header field
 * - mb_encode_mimeheader    - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED
 * - mb_decode_numericentity - Decode HTML numeric string reference to character
 * - mb_encode_numericentity - Encode character to HTML numeric string reference
 * - mb_convert_case         - Perform case folding on a string
 * - mb_detect_encoding      - Detect character encoding
 * - mb_get_info             - Get internal settings of mbstring
 * - mb_http_input           - Detect HTTP input character encoding
 * - mb_http_output          - Set/Get HTTP output character encoding
 * - mb_internal_encoding    - Set/Get internal character encoding
 * - mb_list_encodings       - Returns an array of all supported encodings
 * - mb_ord                  - Returns the Unicode code point of a character
 * - mb_output_handler       - Callback function converts character encoding in output buffer
 * - mb_scrub                - Replaces ill-formed byte sequences with substitute characters
 * - mb_strlen               - Get string length
 * - mb_strpos               - Find position of first occurrence of string in a string
 * - mb_strrpos              - Find position of last occurrence of a string in a string
 * - mb_str_split            - Convert a string to an array
 * - mb_strtolower           - Make a string lowercase
 * - mb_strtoupper           - Make a string uppercase
 * - mb_substitute_character - Set/Get substitution character
 * - mb_substr               - Get part of string
 * - mb_stripos              - Finds position of first occurrence of a string within another, case insensitive
 * - mb_stristr              - Finds first occurrence of a string within another, case insensitive
 * - mb_strrchr              - Finds the last occurrence of a character in a string within another
 * - mb_strrichr             - Finds the last occurrence of a character in a string within another, case insensitive
 * - mb_strripos             - Finds position of last occurrence of a string within another, case insensitive
 * - mb_strstr               - Finds first occurrence of a string within another
 * - mb_strwidth             - Return width of string
 * - mb_substr_count         - Count the number of substring occurrences
 * - mb_ucfirst              - Make a string's first character uppercase
 * - mb_lcfirst              - Make a string's first character lowercase
 * - mb_trim                 - Strip whitespace (or other characters) from the beginning and end of a string
 * - mb_ltrim                - Strip whitespace (or other characters) from the beginning of a string
 * - mb_rtrim                - Strip whitespace (or other characters) from the end of a string
 *
 * Not implemented:
 * - mb_convert_kana         - Convert "kana" one from another ("zen-kaku", "han-kaku" and more)
 * - mb_ereg_*               - Regular expression with multibyte support
 * - mb_parse_str            - Parse GET/POST/COOKIE data and set global variable
 * - mb_preferred_mime_name  - Get MIME charset string
 * - mb_regex_encoding       - Returns current encoding for multibyte regex as string
 * - mb_regex_set_options    - Set/Get the default options for mbregex functions
 * - mb_send_mail            - Send encoded mail
 * - mb_split                - Split multibyte string using regular expression
 * - mb_strcut               - Get part of string
 * - mb_strimwidth           - Get truncated string with specified width
 *
 * @author Nicolas Grekas <p@tchwork.com>
 *
 * @internal
 */
final class Mbstring
{
    public const MB_CASE_FOLD = \PHP_INT_MAX;

    private const SIMPLE_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';

    public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null)
    {
        if (\is_array($s)) {
            $r = [];
            foreach ($s as $str) {
                $r[] = self::mb_convert_encoding($str, $toEncoding, $fromEncoding);
            }

            return $r;
        }

        if (\is_array($fromEncoding) || (null !== $fromEncoding && false !== strpos($fromEncoding, ','))) {
            $fromEncoding = self::mb_detect_encoding($s, $fromEncoding);
        } else {
            $fromEncoding = self::getEncoding($fromEncoding);
        }

        $toEncoding = self::getEncoding($toEncoding);

        if ('BASE64' === $fromEncoding) {
            $s = base64_decode($s);
            $fromEncoding = $toEncoding;
        }

        if ('BASE64' === $toEncoding) {
            return base64_encode($s);
        }

        if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) {
            if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) {
                $fromEncoding = 'Windows-1252';
            }
            if ('UTF-8' !== $fromEncoding) {
                $s = iconv($fromEncoding, 'UTF-8//IGNORE', $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');
            $fromEncoding = 'UTF-8';
        }

        return iconv($fromEncoding, $toEncoding.'//IGNORE', $s);
    }

    public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars)
    {
        $ok = true;
        array_walk_recursive($vars, static function (&$v) use (&$ok, $toEncoding, $fromEncoding) {
            if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) {
                $ok = false;
            }
        });

        return $ok ? $fromEncoding : false;
    }

    public static function mb_decode_mimeheader($s)
    {
        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);
    }

    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);

            return null;
        }

        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);

            return '';  // Instead of null (cf. mb_encode_numericentity).
        }

        $s = (string) $s;
        if ('' === $s) {
            return '';
        }

        $encoding = self::getEncoding($encoding);

        if ('UTF-8' === $encoding) {
            $encoding = null;
            if (!preg_match('//u', $s)) {
                $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
            }
        } else {
            $s = iconv($encoding, 'UTF-8//IGNORE', $s);
        }

        $cnt = floor(\count($convmap) / 4) * 4;

        for ($i = 0; $i < $cnt; $i += 4) {
            // collector_decode_htmlnumericentity ignores $convmap[$i + 3]
            $convmap[$i] += $convmap[$i + 2];
            $convmap[$i + 1] += $convmap[$i + 2];
        }

        $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))'.(\PHP_VERSION_ID >= 80200 ? '' : '(?!&)').';?/', static function (array $m) use ($cnt, $convmap) {
            $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1];
            for ($i = 0; $i < $cnt; $i += 4) {
                if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) {
                    return self::mb_chr($c - $convmap[$i + 2]);
                }
            }

            return $m[0];
        }, $s);

        if (null === $encoding) {
            return $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);

            return null;
        }

        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);

            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);

            return null;
        }

        $s = (string) $s;
        if ('' === $s) {
            return '';
        }

        $encoding = self::getEncoding($encoding);

        if ('UTF-8' === $encoding) {
            $encoding = null;
            if (!preg_match('//u', $s)) {
                $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
            }
        } else {
            $s = iconv($encoding, 'UTF-8//IGNORE', $s);
        }

        static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4];

        $cnt = floor(\count($convmap) / 4) * 4;
        $i = 0;
        $len = \strlen($s);
        $result = '';

        while ($i < $len) {
            $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
            $uchr = substr($s, $i, $ulen);
            $i += $ulen;
            $c = self::mb_ord($uchr);

            for ($j = 0; $j < $cnt; $j += 4) {
                if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) {
                    $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3];
                    $result .= $is_hex ? \sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';';
                    continue 2;
                }
            }
            $result .= $uchr;
        }

        if (null === $encoding) {
            return $result;
        }

        return iconv('UTF-8', $encoding.'//IGNORE', $result);
    }

    public static function mb_convert_case($s, $mode, $encoding = null)
    {
        $s = (string) $s;
        if ('' === $s) {
            return '';
        }

        $encoding = self::getEncoding($encoding);

        if ('UTF-8' === $encoding) {
            $encoding = null;
            if (!preg_match('//u', $s)) {
                $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
            }
        } else {
            $s = iconv($encoding, 'UTF-8//IGNORE', $s);
        }

        if (\MB_CASE_TITLE == $mode) {
            static $titleRegexp = null;
            if (null === $titleRegexp) {
                $titleRegexp = self::getData('titleCaseRegexp');
            }
            $s = preg_replace_callback($titleRegexp, [__CLASS__, 'title_case'], $s);
        } else {
            if (\MB_CASE_UPPER == $mode) {
                static $upper = null;
                if (null === $upper) {
                    $upper = self::getData('upperCase');
                }
                $map = $upper;
            } else {
                if (self::MB_CASE_FOLD === $mode) {
                    static $caseFolding = null;
                    if (null === $caseFolding) {
                        $caseFolding = self::getData('caseFolding');
                    }
                    $s = strtr($s, $caseFolding);
                }

                static $lower = null;
                if (null === $lower) {
                    $lower = self::getData('lowerCase');
                }
                $map = $lower;
            }

            static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4];

            $i = 0;
            $len = \strlen($s);

            while ($i < $len) {
                $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
                $uchr = substr($s, $i, $ulen);
                $i += $ulen;

                if (isset($map[$uchr])) {
                    $uchr = $map[$uchr];
                    $nlen = \strlen($uchr);

                    if ($nlen == $ulen) {
                        $nlen = $i;
                        do {
                            $s[--$nlen] = $uchr[--$ulen];
                        } while ($ulen);
                    } else {
                        $s = substr_replace($s, $uchr, $i - $ulen, $ulen);
                        $len += $nlen - $ulen;
                        $i += $nlen - $ulen;
                    }
                }
            }
        }

        if (null === $encoding) {
            return $s;
        }

        return iconv('UTF-8', $encoding.'//IGNORE', $s);
    }

    public static function mb_internal_encoding($encoding = null)
    {
        if (null === $encoding) {
            return self::$internalEncoding;
        }

        $normalizedEncoding = self::getEncoding($encoding);

        if ('UTF-8' === $normalizedEncoding || false !== @iconv($normalizedEncoding, $normalizedEncoding, ' ')) {
            self::$internalEncoding = $normalizedEncoding;

            return true;
        }

        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)
    {
        if (null === $lang) {
            return self::$language;
        }

        switch ($normalizedLang = strtolower($lang)) {
            case 'uni':
            case 'neutral':
                self::$language = $normalizedLang;

                return true;
        }

        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 ['UTF-8'];
    }

    public static function mb_encoding_aliases($encoding)
    {
        switch (strtoupper($encoding)) {
            case 'UTF8':
            case 'UTF-8':
                return ['utf8'];
        }

        return false;
    }

    public static function mb_check_encoding($var = null, $encoding = null)
    {
        if (null === $encoding) {
            if (null === $var) {
                return false;
            }
            $encoding = self::$internalEncoding;
        }

        if (!\is_array($var)) {
            return self::mb_detect_encoding($var, [$encoding]) || false !== @iconv($encoding, $encoding, $var);
        }

        foreach ($var as $key => $value) {
            if (!self::mb_check_encoding($key, $encoding)) {
                return false;
            }
            if (!self::mb_check_encoding($value, $encoding)) {
                return false;
            }
        }

        return true;
    }

    public static function mb_detect_encoding($str, $encodingList = null, $strict = false)
    {
        if (null === $encodingList) {
            $encodingList = self::$encodingList;
        } else {
            if (!\is_array($encodingList)) {
                $encodingList = array_map('trim', explode(',', $encodingList));
            }
            $encodingList = array_map('strtoupper', $encodingList);
        }

        foreach ($encodingList as $enc) {
            switch ($enc) {
                case 'ASCII':
                    if (!preg_match('/[\x80-\xFF]/', $str)) {
                        return $enc;
                    }
                    break;

                case 'UTF8':
                case 'UTF-8':
                    if (preg_match('//u', $str)) {
                        return 'UTF-8';
                    }
                    break;

                default:
                    if (0 === strncmp($enc, 'ISO-8859-', 9)) {
                        return $enc;
                    }
            }
        }

        return false;
    }

    public static function mb_detect_order($encodingList = null)
    {
        if (null === $encodingList) {
            return self::$encodingList;
        }

        if (!\is_array($encodingList)) {
            $encodingList = array_map('trim', explode(',', $encodingList));
        }
        $encodingList = array_map('strtoupper', $encodingList);

        foreach ($encodingList as $enc) {
            switch ($enc) {
                default:
                    if (strncmp($enc, 'ISO-8859-', 9)) {
                        return false;
                    }
                    // no break
                case 'ASCII':
                case 'UTF8':
                case 'UTF-8':
            }
        }

        self::$encodingList = $encodingList;

        return true;
    }

    public static function mb_strlen($s, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);
        if ('CP850' === $encoding || 'ASCII' === $encoding) {
            return \strlen($s);
        }

        return @iconv_strlen($s, $encoding);
    }

    public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);
        if ('CP850' === $encoding || 'ASCII' === $encoding) {
            return strpos($haystack, $needle, $offset);
        }

        $needle = (string) $needle;
        if ('' === $needle) {
            if (80000 > \PHP_VERSION_ID) {
                trigger_error(__METHOD__.': Empty delimiter', \E_USER_WARNING);

                return false;
            }

            return 0;
        }

        return iconv_strpos($haystack, $needle, $offset, $encoding);
    }

    public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);
        if ('CP850' === $encoding || 'ASCII' === $encoding) {
            return strrpos($haystack, $needle, $offset);
        }

        if ($offset != (int) $offset) {
            $offset = 0;
        } elseif ($offset = (int) $offset) {
            if ($offset < 0) {
                if (0 > $offset += self::mb_strlen($needle)) {
                    $haystack = self::mb_substr($haystack, 0, $offset, $encoding);
                }
                $offset = 0;
            } else {
                $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding);
            }
        }

        $pos = '' !== $needle || 80000 > \PHP_VERSION_ID
            ? iconv_strrpos($haystack, $needle, $encoding)
            : self::mb_strlen($haystack, $encoding);

        return false !== $pos ? $offset + $pos : false;
    }

    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);

            return null;
        }

        if (1 > $split_length = (int) $split_length) {
            if (80000 > \PHP_VERSION_ID) {
                trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING);

                return false;
            }

            throw new \ValueError('Argument #2 ($length) must be greater than 0');
        }

        if (null === $encoding) {
            $encoding = mb_internal_encoding();
        }

        if ('UTF-8' === $encoding = self::getEncoding($encoding)) {
            $rx = '/(';
            while (65535 < $split_length) {
                $rx .= '.{65535}';
                $split_length -= 65535;
            }
            $rx .= '.{'.$split_length.'})/us';

            return preg_split($rx, $string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY);
        }

        $result = [];
        $length = mb_strlen($string, $encoding);

        for ($i = 0; $i < $length; $i += $split_length) {
            $result[] = mb_substr($string, $i, $split_length, $encoding);
        }

        return $result;
    }

    public static function mb_strtolower($s, $encoding = null)
    {
        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);
    }

    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;
        }

        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)
    {
        $encoding = self::getEncoding($encoding);
        if ('CP850' === $encoding || 'ASCII' === $encoding) {
            return (string) substr($s, $start, null === $length ? 2147483647 : $length);
        }

        if ($start < 0) {
            $start = iconv_strlen($s, $encoding) + $start;
            if ($start < 0) {
                $start = 0;
            }
        }

        if (null === $length) {
            $length = 2147483647;
        } elseif ($length < 0) {
            $length = iconv_strlen($s, $encoding) + $length - $start;
            if ($length < 0) {
                return '';
            }
        }

        return (string) iconv_substr($s, $start, $length, $encoding);
    }

    public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null)
    {
        [$haystack, $needle] = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], [
            self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding),
            self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding),
        ]);

        return self::mb_strpos($haystack, $needle, $offset, $encoding);
    }

    public static function mb_stristr($haystack, $needle, $part = false, $encoding = null)
    {
        $pos = self::mb_stripos($haystack, $needle, 0, $encoding);

        return self::getSubpart($pos, $part, $haystack, $encoding);
    }

    public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);
        if ('CP850' === $encoding || 'ASCII' === $encoding) {
            $pos = strrpos($haystack, $needle);
        } else {
            $needle = self::mb_substr($needle, 0, 1, $encoding);
            $pos = iconv_strrpos($haystack, $needle, $encoding);
        }

        return self::getSubpart($pos, $part, $haystack, $encoding);
    }

    public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null)
    {
        $needle = self::mb_substr($needle, 0, 1, $encoding);
        $pos = self::mb_strripos($haystack, $needle, $encoding);

        return self::getSubpart($pos, $part, $haystack, $encoding);
    }

    public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null)
    {
        $haystack = self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding);
        $needle = self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding);

        $haystack = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $haystack);
        $needle = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $needle);

        return self::mb_strrpos($haystack, $needle, $offset, $encoding);
    }

    public static function mb_strstr($haystack, $needle, $part = false, $encoding = null)
    {
        $pos = strpos($haystack, $needle);
        if (false === $pos) {
            return false;
        }
        if ($part) {
            return substr($haystack, 0, $pos);
        }

        return substr($haystack, $pos);
    }

    public static function mb_get_info($type = 'all')
    {
        $info = [
            'internal_encoding' => self::$internalEncoding,
            'http_output' => 'pass',
            'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)',
            'func_overload' => 0,
            'func_overload_list' => 'no overload',
            'mail_charset' => 'UTF-8',
            'mail_header_encoding' => 'BASE64',
            'mail_body_encoding' => 'BASE64',
            'illegal_chars' => 0,
            'encoding_translation' => 'Off',
            'language' => self::$language,
            'detect_order' => self::$encodingList,
            'substitute_character' => 'none',
            'strict_detection' => 'Off',
        ];

        if ('all' === $type) {
            return $info;
        }
        if (isset($info[$type])) {
            return $info[$type];
        }

        return false;
    }

    public static function mb_http_input($type = '')
    {
        return false;
    }

    public static function mb_http_output($encoding = null)
    {
        return null !== $encoding ? 'pass' === $encoding : 'pass';
    }

    public static function mb_strwidth($s, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);

        if ('UTF-8' !== $encoding) {
            $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');
    }

    public static function mb_substr_count($haystack, $needle, $encoding = null)
    {
        return substr_count($haystack, $needle);
    }

    public static function mb_output_handler($contents, $status)
    {
        return $contents;
    }

    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 = self::getEncoding($encoding)) {
            $s = mb_convert_encoding($s, $encoding, 'UTF-8');
        }

        return $s;
    }

    public static function mb_ord($s, $encoding = null)
    {
        if ('UTF-8' !== $encoding = self::getEncoding($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;
    }

    /** @return string|false */
    public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null)
    {
        if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], true)) {
            if (\PHP_VERSION_ID < 80000) {
                trigger_error('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH', \E_USER_WARNING);

                return false;
            }

            throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH');
        }

        if (null === $encoding) {
            $encoding = self::mb_internal_encoding();
        } elseif (!self::assertEncoding($encoding, 'mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given')) {
            return false;
        }

        if (self::mb_strlen($pad_string, $encoding) <= 0) {
            if (\PHP_VERSION_ID < 80000) {
                trigger_error('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string', \E_USER_WARNING);

                return false;
            }

            throw new \ValueError('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string');
        }

        $paddingRequired = $length - self::mb_strlen($string, $encoding);

        if ($paddingRequired < 1) {
            return $string;
        }

        switch ($pad_type) {
            case \STR_PAD_LEFT:
                return self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding).$string;
            case \STR_PAD_RIGHT:
                return $string.self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding);
            default:
                $leftPaddingLength = floor($paddingRequired / 2);
                $rightPaddingLength = $paddingRequired - $leftPaddingLength;

                return self::mb_substr(str_repeat($pad_string, $leftPaddingLength), 0, $leftPaddingLength, $encoding).$string.self::mb_substr(str_repeat($pad_string, $rightPaddingLength), 0, $rightPaddingLength, $encoding);
        }
    }

    /** @return string|false */
    public static function mb_ucfirst(string $string, ?string $encoding = null)
    {
        if (null === $encoding) {
            $encoding = self::mb_internal_encoding();
        } elseif (!self::assertEncoding($encoding, 'mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given')) {
            return false;
        }

        $firstChar = mb_substr($string, 0, 1, $encoding);
        $firstChar = mb_convert_case($firstChar, \MB_CASE_TITLE, $encoding);

        return $firstChar.mb_substr($string, 1, null, $encoding);
    }

    /** @return string|false */
    public static function mb_lcfirst(string $string, ?string $encoding = null)
    {
        if (null === $encoding) {
            $encoding = self::mb_internal_encoding();
        } elseif (!self::assertEncoding($encoding, 'mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given')) {
            return false;
        }

        $firstChar = mb_substr($string, 0, 1, $encoding);
        $firstChar = mb_convert_case($firstChar, \MB_CASE_LOWER, $encoding);

        return $firstChar.mb_substr($string, 1, null, $encoding);
    }

    private static function getSubpart($pos, $part, $haystack, $encoding)
    {
        if (false === $pos) {
            return false;
        }
        if ($part) {
            return self::mb_substr($haystack, 0, $pos, $encoding);
        }

        return self::mb_substr($haystack, $pos, null, $encoding);
    }

    private static function html_encoding_callback(array $m)
    {
        $i = 1;
        $entities = '';
        $m = unpack('C*', htmlentities($m[0], \ENT_COMPAT, 'UTF-8'));

        while (isset($m[$i])) {
            if (0x80 > $m[$i]) {
                $entities .= \chr($m[$i++]);
                continue;
            }
            if (0xF0 <= $m[$i]) {
                $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
            } elseif (0xE0 <= $m[$i]) {
                $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
            } else {
                $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80;
            }

            $entities .= '&#'.$c.';';
        }

        return $entities;
    }

    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');
    }

    private static function getData($file)
    {
        if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) {
            return require $file;
        }

        return false;
    }

    private static function getEncoding($encoding)
    {
        if (null === $encoding) {
            return self::$internalEncoding;
        }

        if ('UTF-8' === $encoding) {
            return 'UTF-8';
        }

        $encoding = strtoupper($encoding);

        if ('8BIT' === $encoding || 'BINARY' === $encoding) {
            return 'CP850';
        }

        if ('UTF8' === $encoding) {
            return 'UTF-8';
        }

        if ('UTF-32' === $encoding) {
            return 'UTF-32BE';
        }

        if ('UTF-16' === $encoding) {
            return 'UTF-16BE';
        }

        return $encoding;
    }

    /** @return string|false */
    public static function mb_trim(string $string, ?string $characters = null, ?string $encoding = null)
    {
        return self::mb_internal_trim('{^[%s]+|[%1$s]+$}Du', $string, $characters, $encoding, __FUNCTION__);
    }

    /** @return string|false */
    public static function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null)
    {
        return self::mb_internal_trim('{^[%s]+}Du', $string, $characters, $encoding, __FUNCTION__);
    }

    /** @return string|false */
    public static function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null)
    {
        return self::mb_internal_trim('{[%s]+$}Du', $string, $characters, $encoding, __FUNCTION__);
    }

    /** @return string|false */
    private static function mb_internal_trim(string $regex, string $string, ?string $characters, ?string $encoding, string $function)
    {
        if (null === $encoding) {
            $encoding = self::mb_internal_encoding();
        } elseif (!self::assertEncoding($encoding, $function.'(): Argument #3 ($encoding) must be a valid encoding, "%s" given')) {
            return false;
        }

        if ('' === $characters) {
            return null === $encoding ? $string : self::mb_convert_encoding($string, $encoding);
        }

        if ('UTF-8' === $encoding) {
            $encoding = null;
            if (!preg_match('//u', $string)) {
                $string = @iconv('UTF-8', 'UTF-8//IGNORE', $string);
            }
            if (null !== $characters && !preg_match('//u', $characters)) {
                $characters = @iconv('UTF-8', 'UTF-8//IGNORE', $characters);
            }
        } else {
            $string = iconv($encoding, 'UTF-8//IGNORE', $string);

            if (null !== $characters) {
                $characters = iconv($encoding, 'UTF-8//IGNORE', $characters);
            }
        }

        if (null === $characters) {
            $characters = "\\0 \f\n\r\t\v\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{2028}\u{2029}\u{202F}\u{205F}\u{3000}\u{0085}\u{180E}";
        } else {
            $characters = preg_quote($characters);
        }

        $string = preg_replace(\sprintf($regex, $characters), '', $string);

        if (null === $encoding) {
            return $string;
        }

        return iconv('UTF-8', $encoding.'//IGNORE', $string);
    }

    private static function assertEncoding(string $encoding, string $errorFormat): bool
    {
        try {
            $validEncoding = @self::mb_check_encoding('', $encoding);
        } catch (\ValueError $e) {
            throw new \ValueError(\sprintf($errorFormat, $encoding));
        }

        if (!$validEncoding) {
            if (80000 > \PHP_VERSION_ID) {
                trigger_error(\sprintf($errorFormat, $encoding), \E_USER_WARNING);
            } else {
                throw new \ValueError(\sprintf($errorFormat, $encoding));
            }
        }

        return $validEncoding;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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|null { return p\Mbstring::mb_get_info((string) $type); }
}
if (!function_exists('mb_http_output')) {
    function mb_http_output(?string $encoding = null): string|bool { return p\Mbstring::mb_http_output($encoding); }
}
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 (!function_exists('mb_str_pad')) {
    function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); }
}

if (!function_exists('mb_ucfirst')) {
    function mb_ucfirst(string $string, ?string $encoding = null): string { return p\Mbstring::mb_ucfirst($string, $encoding); }
}

if (!function_exists('mb_lcfirst')) {
    function mb_lcfirst(string $string, ?string $encoding = null): string { return p\Mbstring::mb_lcfirst($string, $encoding); }
}

if (!function_exists('mb_trim')) {
    function mb_trim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_trim($string, $characters, $encoding); }
}

if (!function_exists('mb_ltrim')) {
    function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_ltrim($string, $characters, $encoding); }
}

if (!function_exists('mb_rtrim')) {
    function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_rtrim($string, $characters, $encoding); }
}

if (extension_loaded('mbstring')) {
    return;
}

if (!defined('MB_CASE_UPPER')) {
    define('MB_CASE_UPPER', 0);
}
if (!defined('MB_CASE_LOWER')) {
    define('MB_CASE_LOWER', 1);
}
if (!defined('MB_CASE_TITLE')) {
    define('MB_CASE_TITLE', 2);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 (\PHP_VERSION_ID >= 80000) {
    return require __DIR__.'/bootstrap80.php';
}

if (!function_exists('mb_convert_encoding')) {
    function mb_convert_encoding($string, $to_encoding, $from_encoding = null) { return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); }
}
if (!function_exists('mb_decode_mimeheader')) {
    function mb_decode_mimeheader($string) { return p\Mbstring::mb_decode_mimeheader($string); }
}
if (!function_exists('mb_encode_mimeheader')) {
    function mb_encode_mimeheader($string, $charset = null, $transfer_encoding = null, $newline = "\r\n", $indent = 0) { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); }
}
if (!function_exists('mb_decode_numericentity')) {
    function mb_decode_numericentity($string, $map, $encoding = null) { return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); }
}
if (!function_exists('mb_encode_numericentity')) {
    function mb_encode_numericentity($string, $map, $encoding = null, $hex = false) { return p\Mbstring::mb_encode_numericentity($string, $map, $encoding, $hex); }
}
if (!function_exists('mb_convert_case')) {
    function mb_convert_case($string, $mode, $encoding = null) { return p\Mbstring::mb_convert_case($string, $mode, $encoding); }
}
if (!function_exists('mb_internal_encoding')) {
    function mb_internal_encoding($encoding = null) { return p\Mbstring::mb_internal_encoding($encoding); }
}
if (!function_exists('mb_language')) {
    function mb_language($language = null) { return p\Mbstring::mb_language($language); }
}
if (!function_exists('mb_list_encodings')) {
    function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); }
}
if (!function_exists('mb_encoding_aliases')) {
    function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); }
}
if (!function_exists('mb_check_encoding')) {
    function mb_check_encoding($value = null, $encoding = null) { return p\Mbstring::mb_check_encoding($value, $encoding); }
}
if (!function_exists('mb_detect_encoding')) {
    function mb_detect_encoding($string, $encodings = null, $strict = false) { return p\Mbstring::mb_detect_encoding($string, $encodings, $strict); }
}
if (!function_exists('mb_detect_order')) {
    function mb_detect_order($encoding = null) { return p\Mbstring::mb_detect_order($encoding); }
}
if (!function_exists('mb_parse_str')) {
    function mb_parse_str($string, &$result = []) { parse_str($string, $result); return (bool) $result; }
}
if (!function_exists('mb_strlen')) {
    function mb_strlen($string, $encoding = null) { return p\Mbstring::mb_strlen($string, $encoding); }
}
if (!function_exists('mb_strpos')) {
    function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strpos($haystack, $needle, $offset, $encoding); }
}
if (!function_exists('mb_strtolower')) {
    function mb_strtolower($string, $encoding = null) { return p\Mbstring::mb_strtolower($string, $encoding); }
}
if (!function_exists('mb_strtoupper')) {
    function mb_strtoupper($string, $encoding = null) { return p\Mbstring::mb_strtoupper($string, $encoding); }
}
if (!function_exists('mb_substitute_character')) {
    function mb_substitute_character($substitute_character = null) { return p\Mbstring::mb_substitute_character($substitute_character); }
}
if (!function_exists('mb_substr')) {
    function mb_substr($string, $start, $length = 2147483647, $encoding = null) { return p\Mbstring::mb_substr($string, $start, $length, $encoding); }
}
if (!function_exists('mb_stripos')) {
    function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_stripos($haystack, $needle, $offset, $encoding); }
}
if (!function_exists('mb_stristr')) {
    function mb_stristr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_stristr($haystack, $needle, $before_needle, $encoding); }
}
if (!function_exists('mb_strrchr')) {
    function mb_strrchr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrchr($haystack, $needle, $before_needle, $encoding); }
}
if (!function_exists('mb_strrichr')) {
    function mb_strrichr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrichr($haystack, $needle, $before_needle, $encoding); }
}
if (!function_exists('mb_strripos')) {
    function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strripos($haystack, $needle, $offset, $encoding); }
}
if (!function_exists('mb_strrpos')) {
    function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strrpos($haystack, $needle, $offset, $encoding); }
}
if (!function_exists('mb_strstr')) {
    function mb_strstr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strstr($haystack, $needle, $before_needle, $encoding); }
}
if (!function_exists('mb_get_info')) {
    function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); }
}
if (!function_exists('mb_http_output')) {
    function mb_http_output($encoding = null) { return p\Mbstring::mb_http_output($encoding); }
}
if (!function_exists('mb_strwidth')) {
    function mb_strwidth($string, $encoding = null) { return p\Mbstring::mb_strwidth($string, $encoding); }
}
if (!function_exists('mb_substr_count')) {
    function mb_substr_count($haystack, $needle, $encoding = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $encoding); }
}
if (!function_exists('mb_output_handler')) {
    function mb_output_handler($string, $status) { return p\Mbstring::mb_output_handler($string, $status); }
}
if (!function_exists('mb_http_input')) {
    function mb_http_input($type = null) { return p\Mbstring::mb_http_input($type); }
}

if (!function_exists('mb_convert_variables')) {
    function mb_convert_variables($to_encoding, $from_encoding, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, ...$vars); }
}

if (!function_exists('mb_ord')) {
    function mb_ord($string, $encoding = null) { return p\Mbstring::mb_ord($string, $encoding); }
}
if (!function_exists('mb_chr')) {
    function mb_chr($codepoint, $encoding = null) { return p\Mbstring::mb_chr($codepoint, $encoding); }
}
if (!function_exists('mb_scrub')) {
    function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? mb_internal_encoding() : $encoding; return mb_convert_encoding($string, $encoding, $encoding); }
}
if (!function_exists('mb_str_split')) {
    function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $length, $encoding); }
}

if (!function_exists('mb_str_pad')) {
    function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null) { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); }
}

if (!function_exists('mb_ucfirst')) {
    function mb_ucfirst(string $string, ?string $encoding = null) { return p\Mbstring::mb_ucfirst($string, $encoding); }
}

if (!function_exists('mb_lcfirst')) {
    function mb_lcfirst(string $string, ?string $encoding = null) { return p\Mbstring::mb_lcfirst($string, $encoding); }
}

if (!function_exists('mb_trim')) {
    function mb_trim(string $string, ?string $characters = null, ?string $encoding = null) { return p\Mbstring::mb_trim($string, $characters, $encoding); }
}

if (!function_exists('mb_ltrim')) {
    function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null) { return p\Mbstring::mb_ltrim($string, $characters, $encoding); }
}

if (!function_exists('mb_rtrim')) {
    function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null) { return p\Mbstring::mb_rtrim($string, $characters, $encoding); }
}

if (extension_loaded('mbstring')) {
    return;
}

if (!defined('MB_CASE_UPPER')) {
    define('MB_CASE_UPPER', 0);
}
if (!defined('MB_CASE_LOWER')) {
    define('MB_CASE_LOWER', 1);
}
if (!defined('MB_CASE_TITLE')) {
    define('MB_CASE_TITLE', 2);
}
Copyright (c) 2018-present Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
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.
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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.
     *
     * @template T of object
     *
     * @param T           $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 T The passed $event MUST be returned
     */
    public function dispatch(object $event, ?string $eventName = null): object;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <guilhermeblanco@hotmail.com>
 * @author Jonathan Wage <jonwage@gmail.com>
 * @author Roman Borschel <roman@code-factory.org>
 * @author Bernhard Schussek <bschussek@gmail.com>
 * @author Nicolas Grekas <p@tchwork.com>
 */
class Event implements StoppableEventInterface
{
    private bool $propagationStopped = false;

    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;
    }
}
<?php

return array (
  ' ' => ' ',
  '¨' => ' ̈',
  'ª' => 'a',
  '¯' => ' ̄',
  '²' => '2',
  '³' => '3',
  '´' => ' ́',
  'µ' => 'μ',
  '¸' => ' ̧',
  '¹' => '1',
  'º' => 'o',
  '¼' => '1⁄4',
  '½' => '1⁄2',
  '¾' => '3⁄4',
  'Ĳ' => 'IJ',
  'ĳ' => 'ij',
  'Ŀ' => 'L·',
  'ŀ' => 'l·',
  'ŉ' => 'ʼn',
  'ſ' => 's',
  'Ǆ' => 'DŽ',
  'ǅ' => 'Dž',
  'ǆ' => 'dž',
  'Ǉ' => 'LJ',
  'ǈ' => 'Lj',
  'ǉ' => 'lj',
  'Ǌ' => 'NJ',
  'ǋ' => 'Nj',
  'ǌ' => 'nj',
  'Ǳ' => '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ʾ',
  'ẛ' => 'ṡ',
  '᾽' => ' ̓',
  '᾿' => ' ̓',
  '῀' => ' ͂',
  '῁' => ' ̈͂',
  '῍' => ' ̓̀',
  '῎' => ' ̓́',
  '῏' => ' ̓͂',
  '῝' => ' ̔̀',
  '῞' => ' ̔́',
  '῟' => ' ̔͂',
  '῭' => ' ̈̀',
  '΅' => ' ̈́',
  '´' => ' ́',
  '῾' => ' ̔',
  ' ' => ' ',
  ' ' => ' ',
  ' ' => ' ',
  ' ' => ' ',
  ' ' => ' ',
  ' ' => ' ',
  ' ' => ' ',
  ' ' => ' ',
  ' ' => ' ',
  ' ' => ' ',
  ' ' => ' ',
  '‑' => '‐',
  '‗' => ' ̳',
  '․' => '.',
  '‥' => '..',
  '…' => '...',
  ' ' => ' ',
  '″' => '′′',
  '‴' => '′′′',
  '‶' => '‵‵',
  '‷' => '‵‵‵',
  '‼' => '!!',
  '‾' => ' ̅',
  '⁇' => '??',
  '⁈' => '?!',
  '⁉' => '!?',
  '⁗' => '′′′′',
  ' ' => ' ',
  '⁰' => '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',
  'ﬁ' => 'fi',
  'ﬂ' => 'fl',
  'ﬃ' => 'ffi',
  'ﬄ' => 'ffl',
  'ﬅ' => 'st',
  'ﬆ' => 'st',
  'ﬓ' => 'մն',
  'ﬔ' => 'մե',
  'ﬕ' => 'մի',
  'ﬖ' => 'վն',
  'ﬗ' => 'մխ',
  'ﬠ' => 'ע',
  'ﬡ' => 'א',
  'ﬢ' => 'ד',
  'ﬣ' => 'ה',
  'ﬤ' => 'כ',
  'ﬥ' => 'ל',
  'ﬦ' => 'ם',
  'ﬧ' => 'ר',
  'ﬨ' => 'ת',
  '﬩' => '+',
  'ﭏ' => 'אל',
  'ﭐ' => 'ٱ',
  'ﭑ' => 'ٱ',
  'ﭒ' => 'ٻ',
  'ﭓ' => 'ٻ',
  'ﭔ' => 'ٻ',
  'ﭕ' => 'ٻ',
  'ﭖ' => 'پ',
  'ﭗ' => 'پ',
  'ﭘ' => 'پ',
  'ﭙ' => 'پ',
  'ﭚ' => 'ڀ',
  'ﭛ' => 'ڀ',
  'ﭜ' => 'ڀ',
  'ﭝ' => 'ڀ',
  'ﭞ' => 'ٺ',
  'ﭟ' => 'ٺ',
  'ﭠ' => 'ٺ',
  'ﭡ' => 'ٺ',
  'ﭢ' => 'ٿ',
  'ﭣ' => 'ٿ',
  'ﭤ' => 'ٿ',
  'ﭥ' => 'ٿ',
  'ﭦ' => 'ٹ',
  'ﭧ' => 'ٹ',
  'ﭨ' => 'ٹ',
  'ﭩ' => 'ٹ',
  'ﭪ' => 'ڤ',
  'ﭫ' => 'ڤ',
  'ﭬ' => 'ڤ',
  'ﭭ' => 'ڤ',
  'ﭮ' => 'ڦ',
  'ﭯ' => 'ڦ',
  'ﭰ' => 'ڦ',
  'ﭱ' => 'ڦ',
  'ﭲ' => 'ڄ',
  'ﭳ' => 'ڄ',
  'ﭴ' => 'ڄ',
  'ﭵ' => 'ڄ',
  'ﭶ' => 'ڃ',
  'ﭷ' => 'ڃ',
  'ﭸ' => 'ڃ',
  'ﭹ' => 'ڃ',
  'ﭺ' => 'چ',
  'ﭻ' => 'چ',
  'ﭼ' => 'چ',
  'ﭽ' => 'چ',
  'ﭾ' => 'ڇ',
  'ﭿ' => 'ڇ',
  'ﮀ' => 'ڇ',
  'ﮁ' => 'ڇ',
  'ﮂ' => 'ڍ',
  'ﮃ' => 'ڍ',
  'ﮄ' => 'ڌ',
  'ﮅ' => 'ڌ',
  'ﮆ' => 'ڎ',
  'ﮇ' => 'ڎ',
  'ﮈ' => 'ڈ',
  'ﮉ' => 'ڈ',
  'ﮊ' => 'ژ',
  'ﮋ' => 'ژ',
  'ﮌ' => 'ڑ',
  'ﮍ' => 'ڑ',
  'ﮎ' => 'ک',
  'ﮏ' => 'ک',
  'ﮐ' => 'ک',
  'ﮑ' => 'ک',
  'ﮒ' => 'گ',
  'ﮓ' => 'گ',
  'ﮔ' => 'گ',
  'ﮕ' => 'گ',
  'ﮖ' => 'ڳ',
  'ﮗ' => 'ڳ',
  'ﮘ' => 'ڳ',
  'ﮙ' => 'ڳ',
  'ﮚ' => 'ڱ',
  'ﮛ' => 'ڱ',
  'ﮜ' => 'ڱ',
  'ﮝ' => 'ڱ',
  'ﮞ' => 'ں',
  'ﮟ' => 'ں',
  'ﮠ' => 'ڻ',
  'ﮡ' => 'ڻ',
  'ﮢ' => 'ڻ',
  'ﮣ' => 'ڻ',
  'ﮤ' => 'ۀ',
  'ﮥ' => 'ۀ',
  'ﮦ' => 'ہ',
  'ﮧ' => 'ہ',
  'ﮨ' => 'ہ',
  'ﮩ' => 'ہ',
  'ﮪ' => 'ھ',
  'ﮫ' => 'ھ',
  'ﮬ' => 'ھ',
  'ﮭ' => 'ھ',
  'ﮮ' => 'ے',
  'ﮯ' => 'ے',
  'ﮰ' => 'ۓ',
  'ﮱ' => 'ۓ',
  'ﯓ' => 'ڭ',
  'ﯔ' => 'ڭ',
  'ﯕ' => 'ڭ',
  'ﯖ' => 'ڭ',
  'ﯗ' => 'ۇ',
  'ﯘ' => 'ۇ',
  'ﯙ' => 'ۆ',
  'ﯚ' => 'ۆ',
  'ﯛ' => 'ۈ',
  'ﯜ' => 'ۈ',
  'ﯝ' => 'ۇٴ',
  'ﯞ' => 'ۋ',
  'ﯟ' => 'ۋ',
  'ﯠ' => 'ۅ',
  'ﯡ' => 'ۅ',
  'ﯢ' => 'ۉ',
  'ﯣ' => 'ۉ',
  'ﯤ' => 'ې',
  'ﯥ' => 'ې',
  'ﯦ' => 'ې',
  'ﯧ' => 'ې',
  'ﯨ' => 'ى',
  'ﯩ' => 'ى',
  'ﯪ' => 'ئا',
  'ﯫ' => 'ئا',
  'ﯬ' => 'ئە',
  'ﯭ' => 'ئە',
  'ﯮ' => 'ئو',
  'ﯯ' => 'ئو',
  'ﯰ' => 'ئۇ',
  'ﯱ' => 'ئۇ',
  'ﯲ' => 'ئۆ',
  'ﯳ' => 'ئۆ',
  'ﯴ' => 'ئۈ',
  'ﯵ' => 'ئۈ',
  'ﯶ' => 'ئې',
  'ﯷ' => 'ئې',
  'ﯸ' => 'ئې',
  'ﯹ' => 'ئى',
  'ﯺ' => 'ئى',
  'ﯻ' => 'ئى',
  'ﯼ' => 'ی',
  'ﯽ' => 'ی',
  'ﯾ' => 'ی',
  'ﯿ' => 'ی',
  'ﰀ' => 'ئج',
  'ﰁ' => 'ئح',
  'ﰂ' => 'ئم',
  'ﰃ' => 'ئى',
  'ﰄ' => 'ئي',
  'ﰅ' => 'بج',
  'ﰆ' => 'بح',
  'ﰇ' => 'بخ',
  'ﰈ' => 'بم',
  'ﰉ' => 'بى',
  'ﰊ' => 'بي',
  'ﰋ' => 'تج',
  'ﰌ' => 'تح',
  'ﰍ' => 'تخ',
  'ﰎ' => 'تم',
  'ﰏ' => 'تى',
  'ﰐ' => 'تي',
  'ﰑ' => 'ثج',
  'ﰒ' => 'ثم',
  'ﰓ' => 'ثى',
  'ﰔ' => 'ثي',
  'ﰕ' => 'جح',
  'ﰖ' => 'جم',
  'ﰗ' => 'حج',
  'ﰘ' => 'حم',
  'ﰙ' => 'خج',
  'ﰚ' => 'خح',
  'ﰛ' => 'خم',
  'ﰜ' => 'سج',
  'ﰝ' => 'سح',
  'ﰞ' => 'سخ',
  'ﰟ' => 'سم',
  'ﰠ' => 'صح',
  'ﰡ' => 'صم',
  'ﰢ' => 'ضج',
  'ﰣ' => 'ضح',
  'ﰤ' => 'ضخ',
  'ﰥ' => 'ضم',
  'ﰦ' => 'طح',
  'ﰧ' => 'طم',
  'ﰨ' => 'ظم',
  'ﰩ' => 'عج',
  'ﰪ' => 'عم',
  'ﰫ' => 'غج',
  'ﰬ' => 'غم',
  'ﰭ' => 'فج',
  'ﰮ' => 'فح',
  'ﰯ' => 'فخ',
  'ﰰ' => 'فم',
  'ﰱ' => 'فى',
  'ﰲ' => 'في',
  'ﰳ' => 'قح',
  'ﰴ' => 'قم',
  'ﰵ' => 'قى',
  'ﰶ' => 'قي',
  'ﰷ' => 'كا',
  'ﰸ' => 'كج',
  'ﰹ' => 'كح',
  'ﰺ' => 'كخ',
  'ﰻ' => 'كل',
  'ﰼ' => 'كم',
  'ﰽ' => 'كى',
  'ﰾ' => 'كي',
  'ﰿ' => 'لج',
  'ﱀ' => 'لح',
  'ﱁ' => 'لخ',
  'ﱂ' => 'لم',
  'ﱃ' => 'لى',
  'ﱄ' => 'لي',
  'ﱅ' => 'مج',
  'ﱆ' => 'مح',
  'ﱇ' => 'مخ',
  'ﱈ' => 'مم',
  'ﱉ' => 'مى',
  'ﱊ' => 'مي',
  'ﱋ' => 'نج',
  'ﱌ' => 'نح',
  'ﱍ' => 'نخ',
  'ﱎ' => 'نم',
  'ﱏ' => 'نى',
  'ﱐ' => 'ني',
  'ﱑ' => 'هج',
  'ﱒ' => 'هم',
  'ﱓ' => 'هى',
  'ﱔ' => 'هي',
  'ﱕ' => 'يج',
  'ﱖ' => 'يح',
  'ﱗ' => 'يخ',
  'ﱘ' => 'يم',
  'ﱙ' => 'يى',
  'ﱚ' => 'يي',
  'ﱛ' => 'ذٰ',
  'ﱜ' => 'رٰ',
  'ﱝ' => 'ىٰ',
  'ﱞ' => ' ٌّ',
  'ﱟ' => ' ٍّ',
  'ﱠ' => ' َّ',
  'ﱡ' => ' ُّ',
  'ﱢ' => ' ِّ',
  'ﱣ' => ' ّٰ',
  'ﱤ' => 'ئر',
  'ﱥ' => 'ئز',
  'ﱦ' => 'ئم',
  'ﱧ' => 'ئن',
  'ﱨ' => 'ئى',
  'ﱩ' => 'ئي',
  'ﱪ' => 'بر',
  'ﱫ' => 'بز',
  'ﱬ' => 'بم',
  'ﱭ' => 'بن',
  'ﱮ' => 'بى',
  'ﱯ' => 'بي',
  'ﱰ' => 'تر',
  'ﱱ' => 'تز',
  'ﱲ' => 'تم',
  'ﱳ' => 'تن',
  'ﱴ' => 'تى',
  'ﱵ' => 'تي',
  'ﱶ' => 'ثر',
  'ﱷ' => 'ثز',
  'ﱸ' => 'ثم',
  'ﱹ' => 'ثن',
  'ﱺ' => 'ثى',
  'ﱻ' => 'ثي',
  'ﱼ' => 'فى',
  'ﱽ' => 'في',
  'ﱾ' => 'قى',
  'ﱿ' => 'قي',
  'ﲀ' => 'كا',
  'ﲁ' => 'كل',
  'ﲂ' => 'كم',
  'ﲃ' => 'كى',
  'ﲄ' => 'كي',
  'ﲅ' => 'لم',
  'ﲆ' => 'لى',
  'ﲇ' => 'لي',
  'ﲈ' => 'ما',
  'ﲉ' => 'مم',
  'ﲊ' => 'نر',
  'ﲋ' => 'نز',
  'ﲌ' => 'نم',
  'ﲍ' => 'نن',
  'ﲎ' => 'نى',
  'ﲏ' => 'ني',
  'ﲐ' => 'ىٰ',
  'ﲑ' => 'ير',
  'ﲒ' => 'يز',
  'ﲓ' => 'يم',
  'ﲔ' => 'ين',
  'ﲕ' => 'يى',
  'ﲖ' => 'يي',
  'ﲗ' => 'ئج',
  'ﲘ' => 'ئح',
  'ﲙ' => 'ئخ',
  'ﲚ' => 'ئم',
  'ﲛ' => 'ئه',
  'ﲜ' => 'بج',
  'ﲝ' => 'بح',
  'ﲞ' => 'بخ',
  'ﲟ' => 'بم',
  'ﲠ' => 'به',
  'ﲡ' => 'تج',
  'ﲢ' => 'تح',
  'ﲣ' => 'تخ',
  'ﲤ' => 'تم',
  'ﲥ' => 'ته',
  'ﲦ' => 'ثم',
  'ﲧ' => 'جح',
  'ﲨ' => 'جم',
  'ﲩ' => 'حج',
  'ﲪ' => 'حم',
  'ﲫ' => 'خج',
  'ﲬ' => 'خم',
  'ﲭ' => 'سج',
  'ﲮ' => 'سح',
  'ﲯ' => 'سخ',
  'ﲰ' => 'سم',
  'ﲱ' => 'صح',
  'ﲲ' => 'صخ',
  'ﲳ' => 'صم',
  'ﲴ' => 'ضج',
  'ﲵ' => 'ضح',
  'ﲶ' => 'ضخ',
  'ﲷ' => 'ضم',
  'ﲸ' => 'طح',
  'ﲹ' => 'ظم',
  'ﲺ' => 'عج',
  'ﲻ' => 'عم',
  'ﲼ' => 'غج',
  'ﲽ' => 'غم',
  'ﲾ' => 'فج',
  'ﲿ' => 'فح',
  'ﳀ' => 'فخ',
  'ﳁ' => 'فم',
  'ﳂ' => 'قح',
  'ﳃ' => 'قم',
  'ﳄ' => 'كج',
  'ﳅ' => 'كح',
  'ﳆ' => 'كخ',
  'ﳇ' => 'كل',
  'ﳈ' => 'كم',
  'ﳉ' => 'لج',
  'ﳊ' => 'لح',
  'ﳋ' => 'لخ',
  'ﳌ' => 'لم',
  'ﳍ' => 'له',
  'ﳎ' => 'مج',
  'ﳏ' => 'مح',
  'ﳐ' => 'مخ',
  'ﳑ' => 'مم',
  'ﳒ' => 'نج',
  'ﳓ' => 'نح',
  'ﳔ' => 'نخ',
  'ﳕ' => 'نم',
  'ﳖ' => 'نه',
  'ﳗ' => 'هج',
  'ﳘ' => 'هم',
  'ﳙ' => 'هٰ',
  'ﳚ' => 'يج',
  'ﳛ' => 'يح',
  'ﳜ' => 'يخ',
  'ﳝ' => 'يم',
  'ﳞ' => 'يه',
  'ﳟ' => 'ئم',
  'ﳠ' => 'ئه',
  'ﳡ' => 'بم',
  'ﳢ' => 'به',
  'ﳣ' => 'تم',
  'ﳤ' => 'ته',
  'ﳥ' => 'ثم',
  'ﳦ' => 'ثه',
  'ﳧ' => 'سم',
  'ﳨ' => 'سه',
  'ﳩ' => 'شم',
  'ﳪ' => 'شه',
  'ﳫ' => 'كل',
  'ﳬ' => 'كم',
  'ﳭ' => 'لم',
  'ﳮ' => 'نم',
  'ﳯ' => 'نه',
  'ﳰ' => 'يم',
  'ﳱ' => 'يه',
  'ﳲ' => 'ـَّ',
  'ﳳ' => 'ـُّ',
  'ﳴ' => 'ـِّ',
  'ﳵ' => 'طى',
  'ﳶ' => 'طي',
  'ﳷ' => 'عى',
  'ﳸ' => 'عي',
  'ﳹ' => 'غى',
  'ﳺ' => 'غي',
  'ﳻ' => 'سى',
  'ﳼ' => 'سي',
  'ﳽ' => 'شى',
  'ﳾ' => 'شي',
  'ﳿ' => 'حى',
  'ﴀ' => 'حي',
  'ﴁ' => 'جى',
  'ﴂ' => 'جي',
  'ﴃ' => 'خى',
  'ﴄ' => 'خي',
  'ﴅ' => 'صى',
  'ﴆ' => 'صي',
  'ﴇ' => 'ضى',
  'ﴈ' => 'ضي',
  'ﴉ' => 'شج',
  'ﴊ' => 'شح',
  'ﴋ' => 'شخ',
  'ﴌ' => 'شم',
  'ﴍ' => 'شر',
  'ﴎ' => 'سر',
  'ﴏ' => 'صر',
  'ﴐ' => 'ضر',
  'ﴑ' => 'طى',
  'ﴒ' => 'طي',
  'ﴓ' => 'عى',
  'ﴔ' => 'عي',
  'ﴕ' => 'غى',
  'ﴖ' => 'غي',
  'ﴗ' => 'سى',
  'ﴘ' => 'سي',
  'ﴙ' => 'شى',
  'ﴚ' => 'شي',
  'ﴛ' => 'حى',
  'ﴜ' => 'حي',
  'ﴝ' => 'جى',
  'ﴞ' => 'جي',
  'ﴟ' => 'خى',
  'ﴠ' => 'خي',
  'ﴡ' => 'صى',
  'ﴢ' => 'صي',
  'ﴣ' => 'ضى',
  'ﴤ' => 'ضي',
  'ﴥ' => 'شج',
  'ﴦ' => 'شح',
  'ﴧ' => 'شخ',
  'ﴨ' => 'شم',
  'ﴩ' => 'شر',
  'ﴪ' => 'سر',
  'ﴫ' => 'صر',
  'ﴬ' => 'ضر',
  'ﴭ' => 'شج',
  'ﴮ' => 'شح',
  'ﴯ' => 'شخ',
  'ﴰ' => 'شم',
  'ﴱ' => 'سه',
  'ﴲ' => 'شه',
  'ﴳ' => 'طم',
  'ﴴ' => 'سج',
  'ﴵ' => 'سح',
  'ﴶ' => 'سخ',
  'ﴷ' => 'شج',
  'ﴸ' => 'شح',
  'ﴹ' => 'شخ',
  'ﴺ' => 'طم',
  'ﴻ' => 'ظم',
  'ﴼ' => 'اً',
  'ﴽ' => 'اً',
  'ﵐ' => 'تجم',
  'ﵑ' => 'تحج',
  'ﵒ' => 'تحج',
  'ﵓ' => 'تحم',
  'ﵔ' => 'تخم',
  'ﵕ' => 'تمج',
  'ﵖ' => 'تمح',
  'ﵗ' => 'تمخ',
  'ﵘ' => 'جمح',
  'ﵙ' => 'جمح',
  'ﵚ' => 'حمي',
  'ﵛ' => 'حمى',
  'ﵜ' => 'سحج',
  'ﵝ' => 'سجح',
  'ﵞ' => 'سجى',
  'ﵟ' => 'سمح',
  'ﵠ' => 'سمح',
  'ﵡ' => 'سمج',
  'ﵢ' => 'سمم',
  'ﵣ' => 'سمم',
  'ﵤ' => 'صحح',
  'ﵥ' => 'صحح',
  'ﵦ' => 'صمم',
  'ﵧ' => 'شحم',
  'ﵨ' => 'شحم',
  'ﵩ' => 'شجي',
  'ﵪ' => 'شمخ',
  'ﵫ' => 'شمخ',
  'ﵬ' => 'شمم',
  'ﵭ' => 'شمم',
  'ﵮ' => 'ضحى',
  'ﵯ' => 'ضخم',
  'ﵰ' => 'ضخم',
  'ﵱ' => 'طمح',
  'ﵲ' => 'طمح',
  'ﵳ' => 'طمم',
  'ﵴ' => 'طمي',
  'ﵵ' => 'عجم',
  'ﵶ' => 'عمم',
  'ﵷ' => 'عمم',
  'ﵸ' => 'عمى',
  'ﵹ' => 'غمم',
  'ﵺ' => 'غمي',
  'ﵻ' => 'غمى',
  'ﵼ' => 'فخم',
  'ﵽ' => 'فخم',
  'ﵾ' => 'قمح',
  'ﵿ' => 'قمم',
  'ﶀ' => 'لحم',
  'ﶁ' => 'لحي',
  'ﶂ' => 'لحى',
  'ﶃ' => 'لجج',
  'ﶄ' => 'لجج',
  'ﶅ' => 'لخم',
  'ﶆ' => 'لخم',
  'ﶇ' => 'لمح',
  'ﶈ' => 'لمح',
  'ﶉ' => 'محج',
  'ﶊ' => 'محم',
  'ﶋ' => 'محي',
  'ﶌ' => 'مجح',
  'ﶍ' => 'مجم',
  'ﶎ' => 'مخج',
  'ﶏ' => 'مخم',
  'ﶒ' => 'مجخ',
  'ﶓ' => 'همج',
  'ﶔ' => 'همم',
  'ﶕ' => 'نحم',
  'ﶖ' => 'نحى',
  'ﶗ' => 'نجم',
  'ﶘ' => 'نجم',
  'ﶙ' => 'نجى',
  'ﶚ' => 'نمي',
  'ﶛ' => 'نمى',
  'ﶜ' => 'يمم',
  'ﶝ' => 'يمم',
  'ﶞ' => 'بخي',
  'ﶟ' => 'تجي',
  'ﶠ' => 'تجى',
  'ﶡ' => 'تخي',
  'ﶢ' => 'تخى',
  'ﶣ' => 'تمي',
  'ﶤ' => 'تمى',
  'ﶥ' => 'جمي',
  'ﶦ' => 'جحى',
  'ﶧ' => 'جمى',
  'ﶨ' => 'سخى',
  'ﶩ' => 'صحي',
  'ﶪ' => 'شحي',
  'ﶫ' => 'ضحي',
  'ﶬ' => 'لجي',
  'ﶭ' => 'لمي',
  'ﶮ' => 'يحي',
  'ﶯ' => 'يجي',
  'ﶰ' => 'يمي',
  'ﶱ' => 'ممي',
  'ﶲ' => 'قمي',
  'ﶳ' => 'نحي',
  'ﶴ' => 'قمح',
  'ﶵ' => 'لحم',
  'ﶶ' => 'عمي',
  'ﶷ' => 'كمي',
  'ﶸ' => 'نجح',
  'ﶹ' => 'مخي',
  'ﶺ' => 'لجم',
  'ﶻ' => 'كمم',
  'ﶼ' => 'لجم',
  'ﶽ' => 'نجح',
  'ﶾ' => 'جحي',
  'ﶿ' => 'حجي',
  'ﷀ' => 'مجي',
  'ﷁ' => 'فمي',
  'ﷂ' => 'بحي',
  'ﷃ' => 'كمم',
  'ﷄ' => 'عجم',
  'ﷅ' => 'صمم',
  'ﷆ' => 'سخي',
  'ﷇ' => 'نجي',
  'ﷰ' => 'صلے',
  'ﷱ' => 'قلے',
  'ﷲ' => 'الله',
  'ﷳ' => 'اكبر',
  'ﷴ' => 'محمد',
  'ﷵ' => 'صلعم',
  'ﷶ' => 'رسول',
  'ﷷ' => 'عليه',
  'ﷸ' => 'وسلم',
  'ﷹ' => 'صلى',
  'ﷺ' => 'صلى الله عليه وسلم',
  'ﷻ' => 'جل جلاله',
  '﷼' => 'ریال',
  '︐' => ',',
  '︑' => '、',
  '︒' => '。',
  '︓' => ':',
  '︔' => ';',
  '︕' => '!',
  '︖' => '?',
  '︗' => '〖',
  '︘' => '〗',
  '︙' => '...',
  '︰' => '..',
  '︱' => '—',
  '︲' => '–',
  '︳' => '_',
  '︴' => '_',
  '︵' => '(',
  '︶' => ')',
  '︷' => '{',
  '︸' => '}',
  '︹' => '〔',
  '︺' => '〕',
  '︻' => '【',
  '︼' => '】',
  '︽' => '《',
  '︾' => '》',
  '︿' => '〈',
  '﹀' => '〉',
  '﹁' => '「',
  '﹂' => '」',
  '﹃' => '『',
  '﹄' => '』',
  '﹇' => '[',
  '﹈' => ']',
  '﹉' => ' ̅',
  '﹊' => ' ̅',
  '﹋' => ' ̅',
  '﹌' => ' ̅',
  '﹍' => '_',
  '﹎' => '_',
  '﹏' => '_',
  '﹐' => ',',
  '﹑' => '、',
  '﹒' => '.',
  '﹔' => ';',
  '﹕' => ':',
  '﹖' => '?',
  '﹗' => '!',
  '﹘' => '—',
  '﹙' => '(',
  '﹚' => ')',
  '﹛' => '{',
  '﹜' => '}',
  '﹝' => '〔',
  '﹞' => '〕',
  '﹟' => '#',
  '﹠' => '&',
  '﹡' => '*',
  '﹢' => '+',
  '﹣' => '-',
  '﹤' => '<',
  '﹥' => '>',
  '﹦' => '=',
  '﹨' => '\\',
  '﹩' => '$',
  '﹪' => '%',
  '﹫' => '@',
  'ﹰ' => ' ً',
  'ﹱ' => 'ـً',
  'ﹲ' => ' ٌ',
  'ﹴ' => ' ٍ',
  'ﹶ' => ' َ',
  'ﹷ' => 'ـَ',
  'ﹸ' => ' ُ',
  'ﹹ' => 'ـُ',
  'ﹺ' => ' ِ',
  'ﹻ' => 'ـِ',
  'ﹼ' => ' ّ',
  'ﹽ' => 'ـّ',
  'ﹾ' => ' ْ',
  'ﹿ' => 'ـْ',
  'ﺀ' => 'ء',
  'ﺁ' => 'آ',
  'ﺂ' => 'آ',
  'ﺃ' => 'أ',
  'ﺄ' => 'أ',
  'ﺅ' => 'ؤ',
  'ﺆ' => 'ؤ',
  'ﺇ' => 'إ',
  'ﺈ' => 'إ',
  'ﺉ' => 'ئ',
  'ﺊ' => 'ئ',
  'ﺋ' => 'ئ',
  'ﺌ' => 'ئ',
  'ﺍ' => 'ا',
  'ﺎ' => 'ا',
  'ﺏ' => 'ب',
  'ﺐ' => 'ب',
  'ﺑ' => 'ب',
  'ﺒ' => 'ب',
  'ﺓ' => 'ة',
  'ﺔ' => 'ة',
  'ﺕ' => 'ت',
  'ﺖ' => 'ت',
  'ﺗ' => 'ت',
  'ﺘ' => 'ت',
  'ﺙ' => 'ث',
  'ﺚ' => 'ث',
  'ﺛ' => 'ث',
  'ﺜ' => 'ث',
  'ﺝ' => 'ج',
  'ﺞ' => 'ج',
  'ﺟ' => 'ج',
  'ﺠ' => 'ج',
  'ﺡ' => 'ح',
  'ﺢ' => 'ح',
  'ﺣ' => 'ح',
  'ﺤ' => 'ح',
  'ﺥ' => 'خ',
  'ﺦ' => 'خ',
  'ﺧ' => 'خ',
  'ﺨ' => 'خ',
  'ﺩ' => 'د',
  'ﺪ' => 'د',
  'ﺫ' => 'ذ',
  'ﺬ' => 'ذ',
  'ﺭ' => 'ر',
  'ﺮ' => 'ر',
  'ﺯ' => 'ز',
  'ﺰ' => 'ز',
  'ﺱ' => 'س',
  'ﺲ' => 'س',
  'ﺳ' => 'س',
  'ﺴ' => 'س',
  'ﺵ' => 'ش',
  'ﺶ' => 'ش',
  'ﺷ' => 'ش',
  'ﺸ' => 'ش',
  'ﺹ' => 'ص',
  'ﺺ' => 'ص',
  'ﺻ' => 'ص',
  'ﺼ' => 'ص',
  'ﺽ' => 'ض',
  'ﺾ' => 'ض',
  'ﺿ' => 'ض',
  'ﻀ' => 'ض',
  'ﻁ' => 'ط',
  'ﻂ' => 'ط',
  'ﻃ' => 'ط',
  'ﻄ' => 'ط',
  'ﻅ' => 'ظ',
  'ﻆ' => 'ظ',
  'ﻇ' => 'ظ',
  'ﻈ' => 'ظ',
  'ﻉ' => 'ع',
  'ﻊ' => 'ع',
  'ﻋ' => 'ع',
  'ﻌ' => 'ع',
  'ﻍ' => 'غ',
  'ﻎ' => 'غ',
  'ﻏ' => 'غ',
  'ﻐ' => 'غ',
  'ﻑ' => 'ف',
  'ﻒ' => 'ف',
  'ﻓ' => 'ف',
  'ﻔ' => 'ف',
  'ﻕ' => 'ق',
  'ﻖ' => 'ق',
  'ﻗ' => 'ق',
  'ﻘ' => 'ق',
  'ﻙ' => 'ك',
  'ﻚ' => 'ك',
  'ﻛ' => 'ك',
  'ﻜ' => 'ك',
  'ﻝ' => 'ل',
  'ﻞ' => 'ل',
  'ﻟ' => 'ل',
  'ﻠ' => 'ل',
  'ﻡ' => 'م',
  'ﻢ' => 'م',
  'ﻣ' => 'م',
  'ﻤ' => 'م',
  'ﻥ' => 'ن',
  'ﻦ' => 'ن',
  'ﻧ' => 'ن',
  'ﻨ' => 'ن',
  'ﻩ' => 'ه',
  'ﻪ' => 'ه',
  'ﻫ' => 'ه',
  'ﻬ' => 'ه',
  'ﻭ' => 'و',
  'ﻮ' => 'و',
  'ﻯ' => 'ى',
  'ﻰ' => 'ى',
  'ﻱ' => 'ي',
  'ﻲ' => 'ي',
  'ﻳ' => 'ي',
  'ﻴ' => 'ي',
  'ﻵ' => 'لآ',
  'ﻶ' => 'لآ',
  'ﻷ' => 'لأ',
  'ﻸ' => 'لأ',
  'ﻹ' => 'لإ',
  'ﻺ' => 'لإ',
  'ﻻ' => 'لا',
  'ﻼ' => 'لا',
  '！' => '!',
  '＂' => '"',
  '＃' => '#',
  '＄' => '$',
  '％' => '%',
  '＆' => '&',
  '＇' => '\'',
  '（' => '(',
  '）' => ')',
  '＊' => '*',
  '＋' => '+',
  '，' => ',',
  '－' => '-',
  '．' => '.',
  '／' => '/',
  '０' => '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',
  '［' => '[',
  '＼' => '\\',
  '］' => ']',
  '＾' => '^',
  '＿' => '_',
  '｀' => '`',
  'ａ' => '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',
  '𝑖' => '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',
);
<?php

return array (
  '̀' => 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,
);
<?php

return array (
  'À' => 'À',
  'Á' => 'Á',
  'Â' => 'Â',
  'Ã' => 'Ã',
  'Ä' => 'Ä',
  'Å' => 'Å',
  'Ç' => 'Ç',
  'È' => 'È',
  'É' => 'É',
  'Ê' => 'Ê',
  'Ë' => 'Ë',
  'Ì' => 'Ì',
  'Í' => 'Í',
  'Î' => 'Î',
  'Ï' => 'Ï',
  'Ñ' => 'Ñ',
  'Ò' => 'Ò',
  'Ó' => 'Ó',
  'Ô' => 'Ô',
  'Õ' => 'Õ',
  'Ö' => 'Ö',
  'Ù' => 'Ù',
  'Ú' => 'Ú',
  'Û' => 'Û',
  'Ü' => 'Ü',
  'Ý' => 'Ý',
  'à' => 'à',
  'á' => 'á',
  'â' => 'â',
  'ã' => 'ã',
  'ä' => 'ä',
  'å' => 'å',
  'ç' => 'ç',
  'è' => 'è',
  'é' => 'é',
  'ê' => 'ê',
  'ë' => 'ë',
  'ì' => 'ì',
  'í' => 'í',
  'î' => 'î',
  'ï' => 'ï',
  'ñ' => 'ñ',
  'ò' => 'ò',
  'ó' => 'ó',
  'ô' => 'ô',
  'õ' => 'õ',
  'ö' => 'ö',
  'ù' => 'ù',
  'ú' => 'ú',
  'û' => 'û',
  'ü' => 'ü',
  'ý' => 'ý',
  'ÿ' => 'ÿ',
  'Ā' => 'Ā',
  'ā' => 'ā',
  'Ă' => 'Ă',
  'ă' => 'ă',
  'Ą' => 'Ą',
  'ą' => 'ą',
  'Ć' => 'Ć',
  'ć' => 'ć',
  'Ĉ' => 'Ĉ',
  'ĉ' => 'ĉ',
  'Ċ' => 'Ċ',
  'ċ' => 'ċ',
  'Č' => 'Č',
  'č' => 'č',
  'Ď' => 'Ď',
  'ď' => 'ď',
  'Ē' => 'Ē',
  'ē' => 'ē',
  'Ĕ' => 'Ĕ',
  'ĕ' => 'ĕ',
  'Ė' => 'Ė',
  'ė' => 'ė',
  'Ę' => 'Ę',
  'ę' => 'ę',
  'Ě' => 'Ě',
  'ě' => 'ě',
  'Ĝ' => 'Ĝ',
  'ĝ' => 'ĝ',
  'Ğ' => 'Ğ',
  'ğ' => 'ğ',
  'Ġ' => 'Ġ',
  'ġ' => 'ġ',
  'Ģ' => 'Ģ',
  'ģ' => 'ģ',
  'Ĥ' => 'Ĥ',
  'ĥ' => 'ĥ',
  'Ĩ' => 'Ĩ',
  'ĩ' => 'ĩ',
  'Ī' => 'Ī',
  'ī' => 'ī',
  'Ĭ' => 'Ĭ',
  'ĭ' => 'ĭ',
  'Į' => 'Į',
  'į' => 'į',
  'İ' => 'İ',
  'Ĵ' => 'Ĵ',
  'ĵ' => 'ĵ',
  'Ķ' => 'Ķ',
  'ķ' => 'ķ',
  'Ĺ' => 'Ĺ',
  'ĺ' => 'ĺ',
  'Ļ' => 'Ļ',
  'ļ' => 'ļ',
  'Ľ' => 'Ľ',
  'ľ' => 'ľ',
  'Ń' => 'Ń',
  'ń' => 'ń',
  'Ņ' => 'Ņ',
  'ņ' => 'ņ',
  'Ň' => 'Ň',
  'ň' => 'ň',
  'Ō' => 'Ō',
  'ō' => 'ō',
  'Ŏ' => 'Ŏ',
  'ŏ' => 'ŏ',
  'Ő' => 'Ő',
  'ő' => 'ő',
  'Ŕ' => 'Ŕ',
  'ŕ' => 'ŕ',
  'Ŗ' => 'Ŗ',
  'ŗ' => 'ŗ',
  'Ř' => 'Ř',
  'ř' => 'ř',
  'Ś' => 'Ś',
  'ś' => 'ś',
  'Ŝ' => 'Ŝ',
  'ŝ' => 'ŝ',
  'Ş' => 'Ş',
  'ş' => 'ş',
  'Š' => 'Š',
  'š' => 'š',
  'Ţ' => 'Ţ',
  'ţ' => 'ţ',
  'Ť' => 'Ť',
  'ť' => 'ť',
  'Ũ' => 'Ũ',
  'ũ' => 'ũ',
  'Ū' => 'Ū',
  'ū' => 'ū',
  'Ŭ' => 'Ŭ',
  'ŭ' => 'ŭ',
  'Ů' => 'Ů',
  'ů' => 'ů',
  'Ű' => 'Ű',
  'ű' => 'ű',
  'Ų' => 'Ų',
  'ų' => 'ų',
  'Ŵ' => 'Ŵ',
  'ŵ' => 'ŵ',
  'Ŷ' => 'Ŷ',
  'ŷ' => 'ŷ',
  'Ÿ' => 'Ÿ',
  'Ź' => 'Ź',
  'ź' => 'ź',
  'Ż' => 'Ż',
  'ż' => 'ż',
  'Ž' => 'Ž',
  'ž' => 'ž',
  'Ơ' => 'Ơ',
  'ơ' => 'ơ',
  'Ư' => 'Ư',
  'ư' => 'ư',
  'Ǎ' => 'Ǎ',
  'ǎ' => 'ǎ',
  'Ǐ' => 'Ǐ',
  'ǐ' => 'ǐ',
  'Ǒ' => 'Ǒ',
  'ǒ' => 'ǒ',
  'Ǔ' => 'Ǔ',
  'ǔ' => 'ǔ',
  'Ǖ' => 'Ǖ',
  'ǖ' => 'ǖ',
  'Ǘ' => 'Ǘ',
  'ǘ' => 'ǘ',
  'Ǚ' => 'Ǚ',
  'ǚ' => 'ǚ',
  'Ǜ' => 'Ǜ',
  'ǜ' => 'ǜ',
  'Ǟ' => 'Ǟ',
  'ǟ' => 'ǟ',
  'Ǡ' => 'Ǡ',
  'ǡ' => 'ǡ',
  'Ǣ' => 'Ǣ',
  'ǣ' => 'ǣ',
  'Ǧ' => 'Ǧ',
  'ǧ' => 'ǧ',
  'Ǩ' => 'Ǩ',
  'ǩ' => 'ǩ',
  'Ǫ' => 'Ǫ',
  'ǫ' => 'ǫ',
  'Ǭ' => 'Ǭ',
  'ǭ' => 'ǭ',
  'Ǯ' => 'Ǯ',
  'ǯ' => 'ǯ',
  'ǰ' => 'ǰ',
  'Ǵ' => 'Ǵ',
  'ǵ' => 'ǵ',
  'Ǹ' => 'Ǹ',
  'ǹ' => 'ǹ',
  'Ǻ' => 'Ǻ',
  'ǻ' => 'ǻ',
  'Ǽ' => 'Ǽ',
  'ǽ' => 'ǽ',
  'Ǿ' => 'Ǿ',
  'ǿ' => 'ǿ',
  'Ȁ' => 'Ȁ',
  'ȁ' => 'ȁ',
  'Ȃ' => 'Ȃ',
  'ȃ' => 'ȃ',
  'Ȅ' => 'Ȅ',
  'ȅ' => 'ȅ',
  'Ȇ' => 'Ȇ',
  'ȇ' => 'ȇ',
  'Ȉ' => 'Ȉ',
  'ȉ' => 'ȉ',
  'Ȋ' => 'Ȋ',
  'ȋ' => 'ȋ',
  'Ȍ' => 'Ȍ',
  'ȍ' => 'ȍ',
  'Ȏ' => 'Ȏ',
  'ȏ' => 'ȏ',
  'Ȑ' => 'Ȑ',
  'ȑ' => 'ȑ',
  'Ȓ' => 'Ȓ',
  'ȓ' => 'ȓ',
  'Ȕ' => 'Ȕ',
  'ȕ' => 'ȕ',
  'Ȗ' => 'Ȗ',
  'ȗ' => 'ȗ',
  'Ș' => 'Ș',
  'ș' => 'ș',
  'Ț' => 'Ț',
  'ț' => 'ț',
  'Ȟ' => 'Ȟ',
  'ȟ' => 'ȟ',
  'Ȧ' => 'Ȧ',
  'ȧ' => 'ȧ',
  'Ȩ' => 'Ȩ',
  'ȩ' => 'ȩ',
  'Ȫ' => 'Ȫ',
  'ȫ' => 'ȫ',
  'Ȭ' => 'Ȭ',
  'ȭ' => 'ȭ',
  'Ȯ' => 'Ȯ',
  'ȯ' => 'ȯ',
  'Ȱ' => 'Ȱ',
  'ȱ' => 'ȱ',
  'Ȳ' => 'Ȳ',
  'ȳ' => 'ȳ',
  '΅' => '΅',
  'Ά' => 'Ά',
  'Έ' => 'Έ',
  'Ή' => 'Ή',
  'Ί' => 'Ί',
  'Ό' => 'Ό',
  'Ύ' => 'Ύ',
  'Ώ' => 'Ώ',
  'ΐ' => 'ΐ',
  'Ϊ' => 'Ϊ',
  'Ϋ' => 'Ϋ',
  'ά' => 'ά',
  'έ' => 'έ',
  'ή' => 'ή',
  'ί' => 'ί',
  'ΰ' => 'ΰ',
  'ϊ' => 'ϊ',
  'ϋ' => 'ϋ',
  'ό' => 'ό',
  'ύ' => 'ύ',
  'ώ' => 'ώ',
  'ϓ' => 'ϓ',
  'ϔ' => 'ϔ',
  'Ѐ' => 'Ѐ',
  'Ё' => 'Ё',
  'Ѓ' => 'Ѓ',
  'Ї' => 'Ї',
  'Ќ' => 'Ќ',
  'Ѝ' => 'Ѝ',
  'Ў' => 'Ў',
  'Й' => 'Й',
  'й' => 'й',
  'ѐ' => 'ѐ',
  'ё' => 'ё',
  'ѓ' => 'ѓ',
  'ї' => 'ї',
  'ќ' => 'ќ',
  'ѝ' => 'ѝ',
  'ў' => 'ў',
  'Ѷ' => 'Ѷ',
  'ѷ' => 'ѷ',
  'Ӂ' => 'Ӂ',
  'ӂ' => 'ӂ',
  'Ӑ' => 'Ӑ',
  'ӑ' => 'ӑ',
  'Ӓ' => 'Ӓ',
  'ӓ' => 'ӓ',
  'Ӗ' => 'Ӗ',
  'ӗ' => 'ӗ',
  'Ӛ' => 'Ӛ',
  'ӛ' => 'ӛ',
  'Ӝ' => 'Ӝ',
  'ӝ' => 'ӝ',
  'Ӟ' => 'Ӟ',
  'ӟ' => 'ӟ',
  'Ӣ' => 'Ӣ',
  'ӣ' => 'ӣ',
  'Ӥ' => 'Ӥ',
  'ӥ' => 'ӥ',
  'Ӧ' => 'Ӧ',
  'ӧ' => 'ӧ',
  'Ӫ' => 'Ӫ',
  'ӫ' => 'ӫ',
  'Ӭ' => 'Ӭ',
  'ӭ' => 'ӭ',
  'Ӯ' => 'Ӯ',
  'ӯ' => 'ӯ',
  'Ӱ' => 'Ӱ',
  'ӱ' => 'ӱ',
  'Ӳ' => 'Ӳ',
  'ӳ' => 'ӳ',
  'Ӵ' => 'Ӵ',
  'ӵ' => 'ӵ',
  'Ӹ' => 'Ӹ',
  'ӹ' => 'ӹ',
  'آ' => 'آ',
  'أ' => 'أ',
  'ؤ' => 'ؤ',
  'إ' => 'إ',
  'ئ' => 'ئ',
  'ۀ' => 'ۀ',
  'ۂ' => 'ۂ',
  'ۓ' => 'ۓ',
  'ऩ' => 'ऩ',
  'ऱ' => 'ऱ',
  'ऴ' => 'ऴ',
  'ো' => 'ো',
  'ৌ' => 'ৌ',
  'ୈ' => 'ୈ',
  'ୋ' => 'ୋ',
  'ୌ' => 'ୌ',
  'ஔ' => 'ஔ',
  'ொ' => 'ொ',
  'ோ' => 'ோ',
  'ௌ' => 'ௌ',
  'ై' => 'ై',
  'ೀ' => 'ೀ',
  'ೇ' => 'ೇ',
  'ೈ' => 'ೈ',
  'ೊ' => 'ೊ',
  'ೋ' => 'ೋ',
  'ൊ' => 'ൊ',
  'ോ' => 'ോ',
  'ൌ' => 'ൌ',
  'ේ' => 'ේ',
  'ො' => 'ො',
  'ෝ' => 'ෝ',
  'ෞ' => 'ෞ',
  'ဦ' => 'ဦ',
  'ᬆ' => 'ᬆ',
  'ᬈ' => 'ᬈ',
  'ᬊ' => 'ᬊ',
  'ᬌ' => 'ᬌ',
  'ᬎ' => 'ᬎ',
  'ᬒ' => 'ᬒ',
  'ᬻ' => 'ᬻ',
  'ᬽ' => 'ᬽ',
  'ᭀ' => 'ᭀ',
  'ᭁ' => 'ᭁ',
  'ᭃ' => 'ᭃ',
  'Ḁ' => 'Ḁ',
  'ḁ' => 'ḁ',
  'Ḃ' => 'Ḃ',
  'ḃ' => 'ḃ',
  'Ḅ' => 'Ḅ',
  'ḅ' => 'ḅ',
  'Ḇ' => 'Ḇ',
  'ḇ' => 'ḇ',
  'Ḉ' => 'Ḉ',
  'ḉ' => 'ḉ',
  'Ḋ' => 'Ḋ',
  'ḋ' => 'ḋ',
  'Ḍ' => 'Ḍ',
  'ḍ' => 'ḍ',
  'Ḏ' => 'Ḏ',
  'ḏ' => 'ḏ',
  'Ḑ' => 'Ḑ',
  'ḑ' => 'ḑ',
  'Ḓ' => 'Ḓ',
  'ḓ' => 'ḓ',
  'Ḕ' => 'Ḕ',
  'ḕ' => 'ḕ',
  'Ḗ' => 'Ḗ',
  'ḗ' => 'ḗ',
  'Ḙ' => 'Ḙ',
  'ḙ' => 'ḙ',
  'Ḛ' => 'Ḛ',
  'ḛ' => 'ḛ',
  'Ḝ' => 'Ḝ',
  'ḝ' => 'ḝ',
  'Ḟ' => 'Ḟ',
  'ḟ' => 'ḟ',
  'Ḡ' => 'Ḡ',
  'ḡ' => 'ḡ',
  'Ḣ' => 'Ḣ',
  'ḣ' => 'ḣ',
  'Ḥ' => 'Ḥ',
  'ḥ' => 'ḥ',
  'Ḧ' => 'Ḧ',
  'ḧ' => 'ḧ',
  'Ḩ' => 'Ḩ',
  'ḩ' => 'ḩ',
  'Ḫ' => 'Ḫ',
  'ḫ' => 'ḫ',
  'Ḭ' => 'Ḭ',
  'ḭ' => 'ḭ',
  'Ḯ' => 'Ḯ',
  'ḯ' => 'ḯ',
  'Ḱ' => 'Ḱ',
  'ḱ' => 'ḱ',
  'Ḳ' => 'Ḳ',
  'ḳ' => 'ḳ',
  'Ḵ' => 'Ḵ',
  'ḵ' => 'ḵ',
  'Ḷ' => 'Ḷ',
  'ḷ' => 'ḷ',
  'Ḹ' => 'Ḹ',
  'ḹ' => 'ḹ',
  'Ḻ' => 'Ḻ',
  'ḻ' => 'ḻ',
  'Ḽ' => 'Ḽ',
  'ḽ' => 'ḽ',
  'Ḿ' => 'Ḿ',
  'ḿ' => 'ḿ',
  'Ṁ' => 'Ṁ',
  'ṁ' => 'ṁ',
  'Ṃ' => 'Ṃ',
  'ṃ' => 'ṃ',
  'Ṅ' => 'Ṅ',
  'ṅ' => 'ṅ',
  'Ṇ' => 'Ṇ',
  'ṇ' => 'ṇ',
  'Ṉ' => 'Ṉ',
  'ṉ' => 'ṉ',
  'Ṋ' => 'Ṋ',
  'ṋ' => 'ṋ',
  'Ṍ' => 'Ṍ',
  'ṍ' => 'ṍ',
  'Ṏ' => 'Ṏ',
  'ṏ' => 'ṏ',
  'Ṑ' => 'Ṑ',
  'ṑ' => 'ṑ',
  'Ṓ' => 'Ṓ',
  'ṓ' => 'ṓ',
  'Ṕ' => 'Ṕ',
  'ṕ' => 'ṕ',
  'Ṗ' => 'Ṗ',
  'ṗ' => 'ṗ',
  'Ṙ' => 'Ṙ',
  'ṙ' => 'ṙ',
  'Ṛ' => 'Ṛ',
  'ṛ' => 'ṛ',
  'Ṝ' => 'Ṝ',
  'ṝ' => 'ṝ',
  'Ṟ' => 'Ṟ',
  'ṟ' => 'ṟ',
  'Ṡ' => 'Ṡ',
  'ṡ' => 'ṡ',
  'Ṣ' => 'Ṣ',
  'ṣ' => 'ṣ',
  'Ṥ' => 'Ṥ',
  'ṥ' => 'ṥ',
  'Ṧ' => 'Ṧ',
  'ṧ' => 'ṧ',
  'Ṩ' => 'Ṩ',
  'ṩ' => 'ṩ',
  'Ṫ' => 'Ṫ',
  'ṫ' => 'ṫ',
  'Ṭ' => 'Ṭ',
  'ṭ' => 'ṭ',
  'Ṯ' => 'Ṯ',
  'ṯ' => 'ṯ',
  'Ṱ' => 'Ṱ',
  'ṱ' => 'ṱ',
  'Ṳ' => 'Ṳ',
  'ṳ' => 'ṳ',
  'Ṵ' => 'Ṵ',
  'ṵ' => 'ṵ',
  'Ṷ' => 'Ṷ',
  'ṷ' => 'ṷ',
  'Ṹ' => 'Ṹ',
  'ṹ' => 'ṹ',
  'Ṻ' => 'Ṻ',
  'ṻ' => 'ṻ',
  'Ṽ' => 'Ṽ',
  'ṽ' => 'ṽ',
  'Ṿ' => 'Ṿ',
  'ṿ' => 'ṿ',
  'Ẁ' => 'Ẁ',
  'ẁ' => 'ẁ',
  'Ẃ' => 'Ẃ',
  'ẃ' => 'ẃ',
  'Ẅ' => 'Ẅ',
  'ẅ' => 'ẅ',
  'Ẇ' => 'Ẇ',
  'ẇ' => 'ẇ',
  'Ẉ' => 'Ẉ',
  'ẉ' => 'ẉ',
  'Ẋ' => 'Ẋ',
  'ẋ' => 'ẋ',
  'Ẍ' => 'Ẍ',
  'ẍ' => 'ẍ',
  'Ẏ' => 'Ẏ',
  'ẏ' => 'ẏ',
  'Ẑ' => 'Ẑ',
  'ẑ' => 'ẑ',
  'Ẓ' => 'Ẓ',
  'ẓ' => 'ẓ',
  'Ẕ' => 'Ẕ',
  'ẕ' => 'ẕ',
  'ẖ' => 'ẖ',
  'ẗ' => 'ẗ',
  'ẘ' => 'ẘ',
  'ẙ' => 'ẙ',
  'ẛ' => 'ẛ',
  'Ạ' => 'Ạ',
  'ạ' => 'ạ',
  'Ả' => 'Ả',
  'ả' => 'ả',
  'Ấ' => 'Ấ',
  'ấ' => 'ấ',
  'Ầ' => 'Ầ',
  'ầ' => 'ầ',
  'Ẩ' => 'Ẩ',
  'ẩ' => 'ẩ',
  'Ẫ' => 'Ẫ',
  'ẫ' => 'ẫ',
  'Ậ' => 'Ậ',
  'ậ' => 'ậ',
  'Ắ' => 'Ắ',
  'ắ' => 'ắ',
  'Ằ' => 'Ằ',
  'ằ' => 'ằ',
  'Ẳ' => 'Ẳ',
  'ẳ' => 'ẳ',
  'Ẵ' => 'Ẵ',
  'ẵ' => 'ẵ',
  'Ặ' => 'Ặ',
  'ặ' => 'ặ',
  'Ẹ' => 'Ẹ',
  'ẹ' => 'ẹ',
  'Ẻ' => 'Ẻ',
  'ẻ' => 'ẻ',
  'Ẽ' => 'Ẽ',
  'ẽ' => 'ẽ',
  'Ế' => 'Ế',
  'ế' => 'ế',
  'Ề' => 'Ề',
  'ề' => 'ề',
  'Ể' => 'Ể',
  'ể' => 'ể',
  'Ễ' => 'Ễ',
  'ễ' => 'ễ',
  'Ệ' => 'Ệ',
  'ệ' => 'ệ',
  'Ỉ' => 'Ỉ',
  'ỉ' => 'ỉ',
  'Ị' => 'Ị',
  'ị' => 'ị',
  'Ọ' => 'Ọ',
  'ọ' => 'ọ',
  'Ỏ' => 'Ỏ',
  'ỏ' => 'ỏ',
  'Ố' => 'Ố',
  'ố' => 'ố',
  'Ồ' => 'Ồ',
  'ồ' => 'ồ',
  'Ổ' => 'Ổ',
  'ổ' => 'ổ',
  'Ỗ' => 'Ỗ',
  'ỗ' => 'ỗ',
  'Ộ' => 'Ộ',
  'ộ' => 'ộ',
  'Ớ' => 'Ớ',
  'ớ' => 'ớ',
  'Ờ' => 'Ờ',
  'ờ' => 'ờ',
  'Ở' => 'Ở',
  'ở' => 'ở',
  'Ỡ' => 'Ỡ',
  'ỡ' => 'ỡ',
  'Ợ' => 'Ợ',
  'ợ' => 'ợ',
  'Ụ' => 'Ụ',
  'ụ' => 'ụ',
  'Ủ' => 'Ủ',
  'ủ' => 'ủ',
  'Ứ' => 'Ứ',
  'ứ' => 'ứ',
  'Ừ' => 'Ừ',
  'ừ' => 'ừ',
  'Ử' => 'Ử',
  'ử' => 'ử',
  'Ữ' => 'Ữ',
  'ữ' => 'ữ',
  'Ự' => 'Ự',
  'ự' => 'ự',
  'Ỳ' => 'Ỳ',
  'ỳ' => 'ỳ',
  'Ỵ' => 'Ỵ',
  'ỵ' => 'ỵ',
  'Ỷ' => 'Ỷ',
  'ỷ' => 'ỷ',
  'Ỹ' => 'Ỹ',
  'ỹ' => 'ỹ',
  'ἀ' => 'ἀ',
  'ἁ' => 'ἁ',
  'ἂ' => 'ἂ',
  'ἃ' => 'ἃ',
  'ἄ' => 'ἄ',
  'ἅ' => 'ἅ',
  'ἆ' => 'ἆ',
  'ἇ' => 'ἇ',
  'Ἀ' => 'Ἀ',
  'Ἁ' => 'Ἁ',
  'Ἂ' => 'Ἂ',
  'Ἃ' => 'Ἃ',
  'Ἄ' => 'Ἄ',
  'Ἅ' => 'Ἅ',
  'Ἆ' => 'Ἆ',
  'Ἇ' => 'Ἇ',
  'ἐ' => 'ἐ',
  'ἑ' => 'ἑ',
  'ἒ' => 'ἒ',
  'ἓ' => 'ἓ',
  'ἔ' => 'ἔ',
  'ἕ' => 'ἕ',
  'Ἐ' => 'Ἐ',
  'Ἑ' => 'Ἑ',
  'Ἒ' => 'Ἒ',
  'Ἓ' => 'Ἓ',
  'Ἔ' => 'Ἔ',
  'Ἕ' => 'Ἕ',
  'ἠ' => 'ἠ',
  'ἡ' => 'ἡ',
  'ἢ' => 'ἢ',
  'ἣ' => 'ἣ',
  'ἤ' => 'ἤ',
  'ἥ' => 'ἥ',
  'ἦ' => 'ἦ',
  'ἧ' => 'ἧ',
  'Ἠ' => 'Ἠ',
  'Ἡ' => 'Ἡ',
  'Ἢ' => 'Ἢ',
  'Ἣ' => 'Ἣ',
  'Ἤ' => 'Ἤ',
  'Ἥ' => 'Ἥ',
  'Ἦ' => 'Ἦ',
  'Ἧ' => 'Ἧ',
  'ἰ' => 'ἰ',
  'ἱ' => 'ἱ',
  'ἲ' => 'ἲ',
  'ἳ' => 'ἳ',
  'ἴ' => 'ἴ',
  'ἵ' => 'ἵ',
  'ἶ' => 'ἶ',
  'ἷ' => 'ἷ',
  'Ἰ' => 'Ἰ',
  'Ἱ' => 'Ἱ',
  'Ἲ' => 'Ἲ',
  'Ἳ' => 'Ἳ',
  'Ἴ' => 'Ἴ',
  'Ἵ' => 'Ἵ',
  'Ἶ' => 'Ἶ',
  'Ἷ' => 'Ἷ',
  'ὀ' => 'ὀ',
  'ὁ' => 'ὁ',
  'ὂ' => 'ὂ',
  'ὃ' => 'ὃ',
  'ὄ' => 'ὄ',
  'ὅ' => 'ὅ',
  'Ὀ' => 'Ὀ',
  'Ὁ' => 'Ὁ',
  'Ὂ' => 'Ὂ',
  'Ὃ' => 'Ὃ',
  'Ὄ' => 'Ὄ',
  'Ὅ' => 'Ὅ',
  'ὐ' => 'ὐ',
  'ὑ' => 'ὑ',
  'ὒ' => 'ὒ',
  'ὓ' => 'ὓ',
  'ὔ' => 'ὔ',
  'ὕ' => 'ὕ',
  'ὖ' => 'ὖ',
  'ὗ' => 'ὗ',
  'Ὑ' => 'Ὑ',
  'Ὓ' => 'Ὓ',
  'Ὕ' => 'Ὕ',
  'Ὗ' => 'Ὗ',
  'ὠ' => 'ὠ',
  'ὡ' => 'ὡ',
  'ὢ' => 'ὢ',
  'ὣ' => 'ὣ',
  'ὤ' => 'ὤ',
  'ὥ' => 'ὥ',
  'ὦ' => 'ὦ',
  'ὧ' => 'ὧ',
  'Ὠ' => 'Ὠ',
  'Ὡ' => 'Ὡ',
  'Ὢ' => 'Ὢ',
  'Ὣ' => 'Ὣ',
  'Ὤ' => 'Ὤ',
  'Ὥ' => 'Ὥ',
  'Ὦ' => 'Ὦ',
  'Ὧ' => 'Ὧ',
  'ὰ' => 'ὰ',
  'ὲ' => 'ὲ',
  'ὴ' => 'ὴ',
  'ὶ' => 'ὶ',
  'ὸ' => 'ὸ',
  'ὺ' => 'ὺ',
  'ὼ' => 'ὼ',
  'ᾀ' => 'ᾀ',
  'ᾁ' => 'ᾁ',
  'ᾂ' => 'ᾂ',
  'ᾃ' => 'ᾃ',
  'ᾄ' => 'ᾄ',
  'ᾅ' => 'ᾅ',
  'ᾆ' => 'ᾆ',
  'ᾇ' => 'ᾇ',
  'ᾈ' => 'ᾈ',
  'ᾉ' => 'ᾉ',
  'ᾊ' => 'ᾊ',
  'ᾋ' => 'ᾋ',
  'ᾌ' => 'ᾌ',
  'ᾍ' => 'ᾍ',
  'ᾎ' => 'ᾎ',
  'ᾏ' => 'ᾏ',
  'ᾐ' => 'ᾐ',
  'ᾑ' => 'ᾑ',
  'ᾒ' => 'ᾒ',
  'ᾓ' => 'ᾓ',
  'ᾔ' => 'ᾔ',
  'ᾕ' => 'ᾕ',
  'ᾖ' => 'ᾖ',
  'ᾗ' => 'ᾗ',
  'ᾘ' => 'ᾘ',
  'ᾙ' => 'ᾙ',
  'ᾚ' => 'ᾚ',
  'ᾛ' => 'ᾛ',
  'ᾜ' => 'ᾜ',
  'ᾝ' => 'ᾝ',
  'ᾞ' => 'ᾞ',
  'ᾟ' => 'ᾟ',
  'ᾠ' => 'ᾠ',
  'ᾡ' => 'ᾡ',
  'ᾢ' => 'ᾢ',
  'ᾣ' => 'ᾣ',
  'ᾤ' => 'ᾤ',
  'ᾥ' => 'ᾥ',
  'ᾦ' => 'ᾦ',
  'ᾧ' => 'ᾧ',
  'ᾨ' => 'ᾨ',
  'ᾩ' => 'ᾩ',
  'ᾪ' => 'ᾪ',
  'ᾫ' => 'ᾫ',
  'ᾬ' => 'ᾬ',
  'ᾭ' => 'ᾭ',
  'ᾮ' => 'ᾮ',
  'ᾯ' => 'ᾯ',
  'ᾰ' => 'ᾰ',
  'ᾱ' => 'ᾱ',
  'ᾲ' => 'ᾲ',
  'ᾳ' => 'ᾳ',
  'ᾴ' => 'ᾴ',
  'ᾶ' => 'ᾶ',
  'ᾷ' => 'ᾷ',
  'Ᾰ' => 'Ᾰ',
  'Ᾱ' => 'Ᾱ',
  'Ὰ' => 'Ὰ',
  'ᾼ' => 'ᾼ',
  '῁' => '῁',
  'ῂ' => 'ῂ',
  'ῃ' => 'ῃ',
  'ῄ' => 'ῄ',
  'ῆ' => 'ῆ',
  'ῇ' => 'ῇ',
  'Ὲ' => 'Ὲ',
  'Ὴ' => 'Ὴ',
  'ῌ' => 'ῌ',
  '῍' => '῍',
  '῎' => '῎',
  '῏' => '῏',
  'ῐ' => 'ῐ',
  'ῑ' => 'ῑ',
  'ῒ' => 'ῒ',
  'ῖ' => 'ῖ',
  'ῗ' => 'ῗ',
  'Ῐ' => 'Ῐ',
  'Ῑ' => 'Ῑ',
  'Ὶ' => 'Ὶ',
  '῝' => '῝',
  '῞' => '῞',
  '῟' => '῟',
  'ῠ' => 'ῠ',
  'ῡ' => 'ῡ',
  'ῢ' => 'ῢ',
  'ῤ' => 'ῤ',
  'ῥ' => 'ῥ',
  'ῦ' => 'ῦ',
  'ῧ' => 'ῧ',
  'Ῠ' => 'Ῠ',
  'Ῡ' => 'Ῡ',
  'Ὺ' => 'Ὺ',
  'Ῥ' => 'Ῥ',
  '῭' => '῭',
  'ῲ' => 'ῲ',
  'ῳ' => 'ῳ',
  'ῴ' => 'ῴ',
  'ῶ' => 'ῶ',
  'ῷ' => 'ῷ',
  'Ὸ' => 'Ὸ',
  'Ὼ' => 'Ὼ',
  'ῼ' => 'ῼ',
  '↚' => '↚',
  '↛' => '↛',
  '↮' => '↮',
  '⇍' => '⇍',
  '⇎' => '⇎',
  '⇏' => '⇏',
  '∄' => '∄',
  '∉' => '∉',
  '∌' => '∌',
  '∤' => '∤',
  '∦' => '∦',
  '≁' => '≁',
  '≄' => '≄',
  '≇' => '≇',
  '≉' => '≉',
  '≠' => '≠',
  '≢' => '≢',
  '≭' => '≭',
  '≮' => '≮',
  '≯' => '≯',
  '≰' => '≰',
  '≱' => '≱',
  '≴' => '≴',
  '≵' => '≵',
  '≸' => '≸',
  '≹' => '≹',
  '⊀' => '⊀',
  '⊁' => '⊁',
  '⊄' => '⊄',
  '⊅' => '⊅',
  '⊈' => '⊈',
  '⊉' => '⊉',
  '⊬' => '⊬',
  '⊭' => '⊭',
  '⊮' => '⊮',
  '⊯' => '⊯',
  '⋠' => '⋠',
  '⋡' => '⋡',
  '⋢' => '⋢',
  '⋣' => '⋣',
  '⋪' => '⋪',
  '⋫' => '⋫',
  '⋬' => '⋬',
  '⋭' => '⋭',
  'が' => 'が',
  'ぎ' => 'ぎ',
  'ぐ' => 'ぐ',
  'げ' => 'げ',
  'ご' => 'ご',
  'ざ' => 'ざ',
  'じ' => 'じ',
  'ず' => 'ず',
  'ぜ' => 'ぜ',
  'ぞ' => 'ぞ',
  'だ' => 'だ',
  'ぢ' => 'ぢ',
  'づ' => 'づ',
  'で' => 'で',
  'ど' => 'ど',
  'ば' => 'ば',
  'ぱ' => 'ぱ',
  'び' => 'び',
  'ぴ' => 'ぴ',
  'ぶ' => 'ぶ',
  'ぷ' => 'ぷ',
  'べ' => 'べ',
  'ぺ' => 'ぺ',
  'ぼ' => 'ぼ',
  'ぽ' => 'ぽ',
  'ゔ' => 'ゔ',
  'ゞ' => 'ゞ',
  'ガ' => 'ガ',
  'ギ' => 'ギ',
  'グ' => 'グ',
  'ゲ' => 'ゲ',
  'ゴ' => 'ゴ',
  'ザ' => 'ザ',
  'ジ' => 'ジ',
  'ズ' => 'ズ',
  'ゼ' => 'ゼ',
  'ゾ' => 'ゾ',
  'ダ' => 'ダ',
  'ヂ' => 'ヂ',
  'ヅ' => 'ヅ',
  'デ' => 'デ',
  'ド' => 'ド',
  'バ' => 'バ',
  'パ' => 'パ',
  'ビ' => 'ビ',
  'ピ' => 'ピ',
  'ブ' => 'ブ',
  'プ' => 'プ',
  'ベ' => 'ベ',
  'ペ' => 'ペ',
  'ボ' => 'ボ',
  'ポ' => 'ポ',
  'ヴ' => 'ヴ',
  'ヷ' => 'ヷ',
  'ヸ' => 'ヸ',
  'ヹ' => 'ヹ',
  'ヺ' => 'ヺ',
  'ヾ' => 'ヾ',
  '𑂚' => '𑂚',
  '𑂜' => '𑂜',
  '𑂫' => '𑂫',
  '𑄮' => '𑄮',
  '𑄯' => '𑄯',
  '𑍋' => '𑍋',
  '𑍌' => '𑍌',
  '𑒻' => '𑒻',
  '𑒼' => '𑒼',
  '𑒾' => '𑒾',
  '𑖺' => '𑖺',
  '𑖻' => '𑖻',
  '𑤸' => '𑤸',
);
<?php

return array (
  'À' => 'À',
  'Á' => 'Á',
  'Â' => 'Â',
  'Ã' => 'Ã',
  'Ä' => 'Ä',
  'Å' => 'Å',
  'Ç' => 'Ç',
  'È' => 'È',
  'É' => 'É',
  'Ê' => 'Ê',
  'Ë' => 'Ë',
  'Ì' => 'Ì',
  'Í' => 'Í',
  'Î' => 'Î',
  'Ï' => 'Ï',
  'Ñ' => 'Ñ',
  'Ò' => 'Ò',
  'Ó' => 'Ó',
  'Ô' => 'Ô',
  'Õ' => 'Õ',
  'Ö' => 'Ö',
  'Ù' => 'Ù',
  'Ú' => 'Ú',
  'Û' => 'Û',
  'Ü' => 'Ü',
  'Ý' => 'Ý',
  'à' => 'à',
  'á' => 'á',
  'â' => 'â',
  'ã' => 'ã',
  'ä' => 'ä',
  'å' => 'å',
  'ç' => 'ç',
  'è' => 'è',
  'é' => 'é',
  'ê' => 'ê',
  'ë' => 'ë',
  'ì' => 'ì',
  'í' => 'í',
  'î' => 'î',
  'ï' => 'ï',
  'ñ' => 'ñ',
  'ò' => 'ò',
  'ó' => 'ó',
  'ô' => 'ô',
  'õ' => 'õ',
  'ö' => 'ö',
  'ù' => 'ù',
  'ú' => 'ú',
  'û' => 'û',
  'ü' => 'ü',
  'ý' => 'ý',
  'ÿ' => 'ÿ',
  'Ā' => 'Ā',
  'ā' => 'ā',
  'Ă' => 'Ă',
  'ă' => 'ă',
  'Ą' => 'Ą',
  'ą' => 'ą',
  'Ć' => 'Ć',
  'ć' => 'ć',
  'Ĉ' => 'Ĉ',
  'ĉ' => 'ĉ',
  'Ċ' => 'Ċ',
  'ċ' => 'ċ',
  'Č' => 'Č',
  'č' => 'č',
  'Ď' => 'Ď',
  'ď' => 'ď',
  'Ē' => 'Ē',
  'ē' => 'ē',
  'Ĕ' => 'Ĕ',
  'ĕ' => 'ĕ',
  'Ė' => 'Ė',
  'ė' => 'ė',
  'Ę' => 'Ę',
  'ę' => 'ę',
  'Ě' => 'Ě',
  'ě' => 'ě',
  'Ĝ' => 'Ĝ',
  'ĝ' => 'ĝ',
  'Ğ' => 'Ğ',
  'ğ' => 'ğ',
  'Ġ' => 'Ġ',
  'ġ' => 'ġ',
  'Ģ' => 'Ģ',
  'ģ' => 'ģ',
  'Ĥ' => 'Ĥ',
  'ĥ' => 'ĥ',
  'Ĩ' => 'Ĩ',
  'ĩ' => 'ĩ',
  'Ī' => 'Ī',
  'ī' => 'ī',
  'Ĭ' => 'Ĭ',
  'ĭ' => 'ĭ',
  'Į' => 'Į',
  'į' => 'į',
  'İ' => 'İ',
  'Ĵ' => 'Ĵ',
  'ĵ' => 'ĵ',
  'Ķ' => 'Ķ',
  'ķ' => 'ķ',
  'Ĺ' => 'Ĺ',
  'ĺ' => 'ĺ',
  'Ļ' => 'Ļ',
  'ļ' => 'ļ',
  'Ľ' => 'Ľ',
  'ľ' => 'ľ',
  'Ń' => 'Ń',
  'ń' => 'ń',
  'Ņ' => 'Ņ',
  'ņ' => 'ņ',
  'Ň' => 'Ň',
  'ň' => 'ň',
  'Ō' => 'Ō',
  'ō' => 'ō',
  'Ŏ' => 'Ŏ',
  'ŏ' => 'ŏ',
  'Ő' => 'Ő',
  'ő' => 'ő',
  'Ŕ' => 'Ŕ',
  'ŕ' => 'ŕ',
  'Ŗ' => 'Ŗ',
  'ŗ' => 'ŗ',
  'Ř' => 'Ř',
  'ř' => 'ř',
  'Ś' => 'Ś',
  'ś' => 'ś',
  'Ŝ' => 'Ŝ',
  'ŝ' => 'ŝ',
  'Ş' => 'Ş',
  'ş' => 'ş',
  'Š' => 'Š',
  'š' => 'š',
  'Ţ' => 'Ţ',
  'ţ' => 'ţ',
  'Ť' => 'Ť',
  'ť' => 'ť',
  'Ũ' => 'Ũ',
  'ũ' => 'ũ',
  'Ū' => 'Ū',
  'ū' => 'ū',
  'Ŭ' => 'Ŭ',
  'ŭ' => 'ŭ',
  'Ů' => 'Ů',
  'ů' => 'ů',
  'Ű' => 'Ű',
  'ű' => 'ű',
  'Ų' => 'Ų',
  'ų' => 'ų',
  'Ŵ' => 'Ŵ',
  'ŵ' => 'ŵ',
  'Ŷ' => 'Ŷ',
  'ŷ' => 'ŷ',
  'Ÿ' => 'Ÿ',
  'Ź' => 'Ź',
  'ź' => 'ź',
  'Ż' => 'Ż',
  'ż' => 'ż',
  'Ž' => 'Ž',
  'ž' => 'ž',
  'Ơ' => 'Ơ',
  'ơ' => 'ơ',
  'Ư' => 'Ư',
  'ư' => 'ư',
  'Ǎ' => 'Ǎ',
  'ǎ' => 'ǎ',
  'Ǐ' => 'Ǐ',
  'ǐ' => 'ǐ',
  'Ǒ' => 'Ǒ',
  'ǒ' => 'ǒ',
  'Ǔ' => 'Ǔ',
  'ǔ' => 'ǔ',
  'Ǖ' => 'Ǖ',
  'ǖ' => 'ǖ',
  'Ǘ' => 'Ǘ',
  'ǘ' => 'ǘ',
  'Ǚ' => 'Ǚ',
  'ǚ' => 'ǚ',
  'Ǜ' => 'Ǜ',
  'ǜ' => 'ǜ',
  'Ǟ' => 'Ǟ',
  'ǟ' => 'ǟ',
  'Ǡ' => 'Ǡ',
  'ǡ' => 'ǡ',
  'Ǣ' => 'Ǣ',
  'ǣ' => 'ǣ',
  'Ǧ' => 'Ǧ',
  'ǧ' => 'ǧ',
  'Ǩ' => 'Ǩ',
  'ǩ' => 'ǩ',
  'Ǫ' => 'Ǫ',
  'ǫ' => 'ǫ',
  'Ǭ' => 'Ǭ',
  'ǭ' => 'ǭ',
  'Ǯ' => 'Ǯ',
  'ǯ' => 'ǯ',
  'ǰ' => 'ǰ',
  'Ǵ' => 'Ǵ',
  'ǵ' => 'ǵ',
  'Ǹ' => 'Ǹ',
  'ǹ' => 'ǹ',
  'Ǻ' => 'Ǻ',
  'ǻ' => 'ǻ',
  'Ǽ' => 'Ǽ',
  'ǽ' => 'ǽ',
  'Ǿ' => 'Ǿ',
  'ǿ' => 'ǿ',
  'Ȁ' => 'Ȁ',
  'ȁ' => 'ȁ',
  'Ȃ' => 'Ȃ',
  'ȃ' => 'ȃ',
  'Ȅ' => 'Ȅ',
  'ȅ' => 'ȅ',
  'Ȇ' => 'Ȇ',
  'ȇ' => 'ȇ',
  'Ȉ' => 'Ȉ',
  'ȉ' => 'ȉ',
  'Ȋ' => 'Ȋ',
  'ȋ' => 'ȋ',
  'Ȍ' => 'Ȍ',
  'ȍ' => 'ȍ',
  'Ȏ' => 'Ȏ',
  'ȏ' => 'ȏ',
  'Ȑ' => 'Ȑ',
  'ȑ' => 'ȑ',
  'Ȓ' => 'Ȓ',
  'ȓ' => 'ȓ',
  'Ȕ' => 'Ȕ',
  'ȕ' => 'ȕ',
  'Ȗ' => 'Ȗ',
  'ȗ' => 'ȗ',
  'Ș' => 'Ș',
  'ș' => 'ș',
  'Ț' => 'Ț',
  'ț' => 'ț',
  'Ȟ' => 'Ȟ',
  'ȟ' => 'ȟ',
  'Ȧ' => 'Ȧ',
  'ȧ' => 'ȧ',
  'Ȩ' => 'Ȩ',
  'ȩ' => 'ȩ',
  'Ȫ' => 'Ȫ',
  'ȫ' => 'ȫ',
  'Ȭ' => 'Ȭ',
  'ȭ' => 'ȭ',
  'Ȯ' => 'Ȯ',
  'ȯ' => 'ȯ',
  'Ȱ' => 'Ȱ',
  'ȱ' => 'ȱ',
  'Ȳ' => 'Ȳ',
  'ȳ' => 'ȳ',
  '̀' => '̀',
  '́' => '́',
  '̓' => '̓',
  '̈́' => '̈́',
  'ʹ' => 'ʹ',
  ';' => ';',
  '΅' => '΅',
  'Ά' => 'Ά',
  '·' => '·',
  'Έ' => 'Έ',
  'Ή' => 'Ή',
  'Ί' => 'Ί',
  'Ό' => 'Ό',
  'Ύ' => 'Ύ',
  'Ώ' => 'Ώ',
  'ΐ' => 'ΐ',
  'Ϊ' => 'Ϊ',
  'Ϋ' => 'Ϋ',
  'ά' => 'ά',
  'έ' => 'έ',
  'ή' => 'ή',
  'ί' => 'ί',
  'ΰ' => 'ΰ',
  'ϊ' => 'ϊ',
  'ϋ' => 'ϋ',
  'ό' => 'ό',
  'ύ' => 'ύ',
  'ώ' => 'ώ',
  'ϓ' => 'ϓ',
  'ϔ' => 'ϔ',
  'Ѐ' => 'Ѐ',
  'Ё' => 'Ё',
  'Ѓ' => 'Ѓ',
  'Ї' => 'Ї',
  'Ќ' => 'Ќ',
  'Ѝ' => 'Ѝ',
  'Ў' => 'Ў',
  'Й' => 'Й',
  'й' => 'й',
  'ѐ' => 'ѐ',
  'ё' => 'ё',
  'ѓ' => 'ѓ',
  'ї' => 'ї',
  'ќ' => 'ќ',
  'ѝ' => 'ѝ',
  'ў' => 'ў',
  'Ѷ' => 'Ѷ',
  'ѷ' => 'ѷ',
  'Ӂ' => 'Ӂ',
  'ӂ' => 'ӂ',
  'Ӑ' => 'Ӑ',
  'ӑ' => 'ӑ',
  'Ӓ' => 'Ӓ',
  'ӓ' => 'ӓ',
  'Ӗ' => 'Ӗ',
  'ӗ' => 'ӗ',
  'Ӛ' => 'Ӛ',
  'ӛ' => 'ӛ',
  'Ӝ' => 'Ӝ',
  'ӝ' => 'ӝ',
  'Ӟ' => 'Ӟ',
  'ӟ' => 'ӟ',
  'Ӣ' => 'Ӣ',
  'ӣ' => 'ӣ',
  'Ӥ' => 'Ӥ',
  'ӥ' => 'ӥ',
  'Ӧ' => 'Ӧ',
  'ӧ' => 'ӧ',
  'Ӫ' => 'Ӫ',
  'ӫ' => 'ӫ',
  'Ӭ' => 'Ӭ',
  'ӭ' => 'ӭ',
  'Ӯ' => 'Ӯ',
  'ӯ' => 'ӯ',
  'Ӱ' => 'Ӱ',
  'ӱ' => 'ӱ',
  'Ӳ' => 'Ӳ',
  'ӳ' => 'ӳ',
  'Ӵ' => 'Ӵ',
  'ӵ' => 'ӵ',
  'Ӹ' => 'Ӹ',
  'ӹ' => 'ӹ',
  'آ' => 'آ',
  'أ' => 'أ',
  'ؤ' => 'ؤ',
  'إ' => 'إ',
  'ئ' => 'ئ',
  'ۀ' => 'ۀ',
  'ۂ' => 'ۂ',
  'ۓ' => 'ۓ',
  'ऩ' => 'ऩ',
  'ऱ' => 'ऱ',
  'ऴ' => 'ऴ',
  'क़' => 'क़',
  'ख़' => 'ख़',
  'ग़' => 'ग़',
  'ज़' => 'ज़',
  'ड़' => 'ड़',
  'ढ़' => 'ढ़',
  'फ़' => 'फ़',
  'य़' => 'य़',
  'ো' => 'ো',
  'ৌ' => 'ৌ',
  'ড়' => 'ড়',
  'ঢ়' => 'ঢ়',
  'য়' => 'য়',
  'ਲ਼' => 'ਲ਼',
  'ਸ਼' => 'ਸ਼',
  'ਖ਼' => 'ਖ਼',
  'ਗ਼' => 'ਗ਼',
  'ਜ਼' => 'ਜ਼',
  'ਫ਼' => 'ਫ਼',
  'ୈ' => 'ୈ',
  'ୋ' => 'ୋ',
  'ୌ' => 'ୌ',
  'ଡ଼' => 'ଡ଼',
  'ଢ଼' => 'ଢ଼',
  'ஔ' => 'ஔ',
  'ொ' => 'ொ',
  'ோ' => 'ோ',
  'ௌ' => 'ௌ',
  'ై' => 'ై',
  'ೀ' => 'ೀ',
  'ೇ' => 'ೇ',
  'ೈ' => 'ೈ',
  'ೊ' => 'ೊ',
  'ೋ' => 'ೋ',
  'ൊ' => 'ൊ',
  'ോ' => 'ോ',
  'ൌ' => 'ൌ',
  'ේ' => 'ේ',
  'ො' => 'ො',
  'ෝ' => 'ෝ',
  'ෞ' => 'ෞ',
  'གྷ' => 'གྷ',
  'ཌྷ' => 'ཌྷ',
  'དྷ' => 'དྷ',
  'བྷ' => 'བྷ',
  'ཛྷ' => 'ཛྷ',
  'ཀྵ' => 'ཀྵ',
  'ཱི' => 'ཱི',
  'ཱུ' => 'ཱུ',
  'ྲྀ' => 'ྲྀ',
  'ླྀ' => 'ླྀ',
  'ཱྀ' => 'ཱྀ',
  'ྒྷ' => 'ྒྷ',
  'ྜྷ' => 'ྜྷ',
  'ྡྷ' => 'ྡྷ',
  'ྦྷ' => 'ྦྷ',
  'ྫྷ' => 'ྫྷ',
  'ྐྵ' => 'ྐྵ',
  'ဦ' => 'ဦ',
  'ᬆ' => 'ᬆ',
  'ᬈ' => 'ᬈ',
  'ᬊ' => 'ᬊ',
  'ᬌ' => 'ᬌ',
  'ᬎ' => 'ᬎ',
  'ᬒ' => 'ᬒ',
  'ᬻ' => 'ᬻ',
  'ᬽ' => 'ᬽ',
  'ᭀ' => 'ᭀ',
  'ᭁ' => 'ᭁ',
  'ᭃ' => 'ᭃ',
  'Ḁ' => 'Ḁ',
  'ḁ' => 'ḁ',
  'Ḃ' => 'Ḃ',
  'ḃ' => 'ḃ',
  'Ḅ' => 'Ḅ',
  'ḅ' => 'ḅ',
  'Ḇ' => 'Ḇ',
  'ḇ' => 'ḇ',
  'Ḉ' => 'Ḉ',
  'ḉ' => 'ḉ',
  'Ḋ' => 'Ḋ',
  'ḋ' => 'ḋ',
  'Ḍ' => 'Ḍ',
  'ḍ' => 'ḍ',
  'Ḏ' => 'Ḏ',
  'ḏ' => 'ḏ',
  'Ḑ' => 'Ḑ',
  'ḑ' => 'ḑ',
  'Ḓ' => 'Ḓ',
  'ḓ' => 'ḓ',
  'Ḕ' => 'Ḕ',
  'ḕ' => 'ḕ',
  'Ḗ' => 'Ḗ',
  'ḗ' => 'ḗ',
  'Ḙ' => 'Ḙ',
  'ḙ' => 'ḙ',
  'Ḛ' => 'Ḛ',
  'ḛ' => 'ḛ',
  'Ḝ' => 'Ḝ',
  'ḝ' => 'ḝ',
  'Ḟ' => 'Ḟ',
  'ḟ' => 'ḟ',
  'Ḡ' => 'Ḡ',
  'ḡ' => 'ḡ',
  'Ḣ' => 'Ḣ',
  'ḣ' => 'ḣ',
  'Ḥ' => 'Ḥ',
  'ḥ' => 'ḥ',
  'Ḧ' => 'Ḧ',
  'ḧ' => 'ḧ',
  'Ḩ' => 'Ḩ',
  'ḩ' => 'ḩ',
  'Ḫ' => 'Ḫ',
  'ḫ' => 'ḫ',
  'Ḭ' => 'Ḭ',
  'ḭ' => 'ḭ',
  'Ḯ' => 'Ḯ',
  'ḯ' => 'ḯ',
  'Ḱ' => 'Ḱ',
  'ḱ' => 'ḱ',
  'Ḳ' => 'Ḳ',
  'ḳ' => 'ḳ',
  'Ḵ' => 'Ḵ',
  'ḵ' => 'ḵ',
  'Ḷ' => 'Ḷ',
  'ḷ' => 'ḷ',
  'Ḹ' => 'Ḹ',
  'ḹ' => 'ḹ',
  'Ḻ' => 'Ḻ',
  'ḻ' => 'ḻ',
  'Ḽ' => 'Ḽ',
  'ḽ' => 'ḽ',
  'Ḿ' => 'Ḿ',
  'ḿ' => 'ḿ',
  'Ṁ' => 'Ṁ',
  'ṁ' => 'ṁ',
  'Ṃ' => 'Ṃ',
  'ṃ' => 'ṃ',
  'Ṅ' => 'Ṅ',
  'ṅ' => 'ṅ',
  'Ṇ' => 'Ṇ',
  'ṇ' => 'ṇ',
  'Ṉ' => 'Ṉ',
  'ṉ' => 'ṉ',
  'Ṋ' => 'Ṋ',
  'ṋ' => 'ṋ',
  'Ṍ' => 'Ṍ',
  'ṍ' => 'ṍ',
  'Ṏ' => 'Ṏ',
  'ṏ' => 'ṏ',
  'Ṑ' => 'Ṑ',
  'ṑ' => 'ṑ',
  'Ṓ' => 'Ṓ',
  'ṓ' => 'ṓ',
  'Ṕ' => 'Ṕ',
  'ṕ' => 'ṕ',
  'Ṗ' => 'Ṗ',
  'ṗ' => 'ṗ',
  'Ṙ' => 'Ṙ',
  'ṙ' => 'ṙ',
  'Ṛ' => 'Ṛ',
  'ṛ' => 'ṛ',
  'Ṝ' => 'Ṝ',
  'ṝ' => 'ṝ',
  'Ṟ' => 'Ṟ',
  'ṟ' => 'ṟ',
  'Ṡ' => 'Ṡ',
  'ṡ' => 'ṡ',
  'Ṣ' => 'Ṣ',
  'ṣ' => 'ṣ',
  'Ṥ' => 'Ṥ',
  'ṥ' => 'ṥ',
  'Ṧ' => 'Ṧ',
  'ṧ' => 'ṧ',
  'Ṩ' => 'Ṩ',
  'ṩ' => 'ṩ',
  'Ṫ' => 'Ṫ',
  'ṫ' => 'ṫ',
  'Ṭ' => 'Ṭ',
  'ṭ' => 'ṭ',
  'Ṯ' => 'Ṯ',
  'ṯ' => 'ṯ',
  'Ṱ' => 'Ṱ',
  'ṱ' => 'ṱ',
  'Ṳ' => 'Ṳ',
  'ṳ' => 'ṳ',
  'Ṵ' => 'Ṵ',
  'ṵ' => 'ṵ',
  'Ṷ' => 'Ṷ',
  'ṷ' => 'ṷ',
  'Ṹ' => 'Ṹ',
  'ṹ' => 'ṹ',
  'Ṻ' => 'Ṻ',
  'ṻ' => 'ṻ',
  'Ṽ' => 'Ṽ',
  'ṽ' => 'ṽ',
  'Ṿ' => 'Ṿ',
  'ṿ' => 'ṿ',
  'Ẁ' => 'Ẁ',
  'ẁ' => 'ẁ',
  'Ẃ' => 'Ẃ',
  'ẃ' => 'ẃ',
  'Ẅ' => 'Ẅ',
  'ẅ' => 'ẅ',
  'Ẇ' => 'Ẇ',
  'ẇ' => 'ẇ',
  'Ẉ' => 'Ẉ',
  'ẉ' => 'ẉ',
  'Ẋ' => 'Ẋ',
  'ẋ' => 'ẋ',
  'Ẍ' => 'Ẍ',
  'ẍ' => 'ẍ',
  'Ẏ' => 'Ẏ',
  'ẏ' => 'ẏ',
  'Ẑ' => 'Ẑ',
  'ẑ' => 'ẑ',
  'Ẓ' => 'Ẓ',
  'ẓ' => 'ẓ',
  'Ẕ' => 'Ẕ',
  'ẕ' => 'ẕ',
  'ẖ' => 'ẖ',
  'ẗ' => 'ẗ',
  'ẘ' => 'ẘ',
  'ẙ' => 'ẙ',
  'ẛ' => 'ẛ',
  'Ạ' => 'Ạ',
  'ạ' => 'ạ',
  'Ả' => 'Ả',
  'ả' => 'ả',
  'Ấ' => 'Ấ',
  'ấ' => 'ấ',
  'Ầ' => 'Ầ',
  'ầ' => 'ầ',
  'Ẩ' => 'Ẩ',
  'ẩ' => 'ẩ',
  'Ẫ' => 'Ẫ',
  'ẫ' => 'ẫ',
  'Ậ' => 'Ậ',
  'ậ' => 'ậ',
  'Ắ' => 'Ắ',
  'ắ' => 'ắ',
  'Ằ' => 'Ằ',
  'ằ' => 'ằ',
  'Ẳ' => 'Ẳ',
  'ẳ' => 'ẳ',
  'Ẵ' => 'Ẵ',
  'ẵ' => 'ẵ',
  'Ặ' => 'Ặ',
  'ặ' => 'ặ',
  'Ẹ' => 'Ẹ',
  'ẹ' => 'ẹ',
  'Ẻ' => 'Ẻ',
  'ẻ' => 'ẻ',
  'Ẽ' => 'Ẽ',
  'ẽ' => 'ẽ',
  'Ế' => 'Ế',
  'ế' => 'ế',
  'Ề' => 'Ề',
  'ề' => 'ề',
  'Ể' => 'Ể',
  'ể' => 'ể',
  'Ễ' => 'Ễ',
  'ễ' => 'ễ',
  'Ệ' => 'Ệ',
  'ệ' => 'ệ',
  'Ỉ' => 'Ỉ',
  'ỉ' => 'ỉ',
  'Ị' => 'Ị',
  'ị' => 'ị',
  'Ọ' => 'Ọ',
  'ọ' => 'ọ',
  'Ỏ' => 'Ỏ',
  'ỏ' => 'ỏ',
  'Ố' => 'Ố',
  'ố' => 'ố',
  'Ồ' => 'Ồ',
  'ồ' => 'ồ',
  'Ổ' => 'Ổ',
  'ổ' => 'ổ',
  'Ỗ' => 'Ỗ',
  'ỗ' => 'ỗ',
  'Ộ' => 'Ộ',
  'ộ' => 'ộ',
  'Ớ' => 'Ớ',
  'ớ' => 'ớ',
  'Ờ' => 'Ờ',
  'ờ' => 'ờ',
  'Ở' => 'Ở',
  'ở' => 'ở',
  'Ỡ' => 'Ỡ',
  'ỡ' => 'ỡ',
  'Ợ' => 'Ợ',
  'ợ' => 'ợ',
  'Ụ' => 'Ụ',
  'ụ' => 'ụ',
  'Ủ' => 'Ủ',
  'ủ' => 'ủ',
  'Ứ' => 'Ứ',
  'ứ' => 'ứ',
  'Ừ' => 'Ừ',
  'ừ' => 'ừ',
  'Ử' => 'Ử',
  'ử' => 'ử',
  'Ữ' => 'Ữ',
  'ữ' => 'ữ',
  'Ự' => 'Ự',
  'ự' => 'ự',
  'Ỳ' => 'Ỳ',
  'ỳ' => 'ỳ',
  'Ỵ' => 'Ỵ',
  'ỵ' => 'ỵ',
  'Ỷ' => 'Ỷ',
  'ỷ' => 'ỷ',
  'Ỹ' => 'Ỹ',
  'ỹ' => 'ỹ',
  'ἀ' => 'ἀ',
  'ἁ' => 'ἁ',
  'ἂ' => 'ἂ',
  'ἃ' => 'ἃ',
  'ἄ' => 'ἄ',
  'ἅ' => 'ἅ',
  'ἆ' => 'ἆ',
  'ἇ' => 'ἇ',
  'Ἀ' => 'Ἀ',
  'Ἁ' => 'Ἁ',
  'Ἂ' => 'Ἂ',
  'Ἃ' => 'Ἃ',
  'Ἄ' => 'Ἄ',
  'Ἅ' => 'Ἅ',
  'Ἆ' => 'Ἆ',
  'Ἇ' => 'Ἇ',
  'ἐ' => 'ἐ',
  'ἑ' => 'ἑ',
  'ἒ' => 'ἒ',
  'ἓ' => 'ἓ',
  'ἔ' => 'ἔ',
  'ἕ' => 'ἕ',
  'Ἐ' => 'Ἐ',
  'Ἑ' => 'Ἑ',
  'Ἒ' => 'Ἒ',
  'Ἓ' => 'Ἓ',
  'Ἔ' => 'Ἔ',
  'Ἕ' => 'Ἕ',
  'ἠ' => 'ἠ',
  'ἡ' => 'ἡ',
  'ἢ' => 'ἢ',
  'ἣ' => 'ἣ',
  'ἤ' => 'ἤ',
  'ἥ' => 'ἥ',
  'ἦ' => 'ἦ',
  'ἧ' => 'ἧ',
  'Ἠ' => 'Ἠ',
  'Ἡ' => 'Ἡ',
  'Ἢ' => 'Ἢ',
  'Ἣ' => 'Ἣ',
  'Ἤ' => 'Ἤ',
  'Ἥ' => 'Ἥ',
  'Ἦ' => 'Ἦ',
  'Ἧ' => 'Ἧ',
  'ἰ' => 'ἰ',
  'ἱ' => 'ἱ',
  'ἲ' => 'ἲ',
  'ἳ' => 'ἳ',
  'ἴ' => 'ἴ',
  'ἵ' => 'ἵ',
  'ἶ' => 'ἶ',
  'ἷ' => 'ἷ',
  'Ἰ' => 'Ἰ',
  'Ἱ' => 'Ἱ',
  'Ἲ' => 'Ἲ',
  'Ἳ' => 'Ἳ',
  'Ἴ' => 'Ἴ',
  'Ἵ' => 'Ἵ',
  'Ἶ' => 'Ἶ',
  'Ἷ' => 'Ἷ',
  'ὀ' => 'ὀ',
  'ὁ' => 'ὁ',
  'ὂ' => 'ὂ',
  'ὃ' => 'ὃ',
  'ὄ' => 'ὄ',
  'ὅ' => 'ὅ',
  'Ὀ' => 'Ὀ',
  'Ὁ' => 'Ὁ',
  'Ὂ' => 'Ὂ',
  'Ὃ' => 'Ὃ',
  'Ὄ' => 'Ὄ',
  'Ὅ' => 'Ὅ',
  'ὐ' => 'ὐ',
  'ὑ' => 'ὑ',
  'ὒ' => 'ὒ',
  'ὓ' => 'ὓ',
  'ὔ' => 'ὔ',
  'ὕ' => 'ὕ',
  'ὖ' => 'ὖ',
  'ὗ' => 'ὗ',
  'Ὑ' => 'Ὑ',
  'Ὓ' => 'Ὓ',
  'Ὕ' => 'Ὕ',
  'Ὗ' => 'Ὗ',
  'ὠ' => 'ὠ',
  'ὡ' => 'ὡ',
  'ὢ' => 'ὢ',
  'ὣ' => 'ὣ',
  'ὤ' => 'ὤ',
  'ὥ' => 'ὥ',
  'ὦ' => 'ὦ',
  'ὧ' => 'ὧ',
  'Ὠ' => 'Ὠ',
  'Ὡ' => 'Ὡ',
  'Ὢ' => 'Ὢ',
  'Ὣ' => 'Ὣ',
  'Ὤ' => 'Ὤ',
  'Ὥ' => 'Ὥ',
  'Ὦ' => 'Ὦ',
  'Ὧ' => 'Ὧ',
  'ὰ' => 'ὰ',
  'ά' => 'ά',
  'ὲ' => 'ὲ',
  'έ' => 'έ',
  'ὴ' => 'ὴ',
  'ή' => 'ή',
  'ὶ' => 'ὶ',
  'ί' => 'ί',
  'ὸ' => 'ὸ',
  'ό' => 'ό',
  'ὺ' => 'ὺ',
  'ύ' => 'ύ',
  'ὼ' => 'ὼ',
  'ώ' => 'ώ',
  'ᾀ' => 'ᾀ',
  'ᾁ' => 'ᾁ',
  'ᾂ' => 'ᾂ',
  'ᾃ' => 'ᾃ',
  'ᾄ' => 'ᾄ',
  'ᾅ' => 'ᾅ',
  'ᾆ' => 'ᾆ',
  'ᾇ' => 'ᾇ',
  'ᾈ' => 'ᾈ',
  'ᾉ' => 'ᾉ',
  'ᾊ' => 'ᾊ',
  'ᾋ' => 'ᾋ',
  'ᾌ' => 'ᾌ',
  'ᾍ' => 'ᾍ',
  'ᾎ' => 'ᾎ',
  'ᾏ' => 'ᾏ',
  'ᾐ' => 'ᾐ',
  'ᾑ' => 'ᾑ',
  'ᾒ' => 'ᾒ',
  'ᾓ' => 'ᾓ',
  'ᾔ' => 'ᾔ',
  'ᾕ' => 'ᾕ',
  'ᾖ' => 'ᾖ',
  'ᾗ' => 'ᾗ',
  'ᾘ' => 'ᾘ',
  'ᾙ' => 'ᾙ',
  'ᾚ' => 'ᾚ',
  'ᾛ' => 'ᾛ',
  'ᾜ' => 'ᾜ',
  'ᾝ' => 'ᾝ',
  'ᾞ' => 'ᾞ',
  'ᾟ' => 'ᾟ',
  'ᾠ' => 'ᾠ',
  'ᾡ' => 'ᾡ',
  'ᾢ' => 'ᾢ',
  'ᾣ' => 'ᾣ',
  'ᾤ' => 'ᾤ',
  'ᾥ' => 'ᾥ',
  'ᾦ' => 'ᾦ',
  'ᾧ' => 'ᾧ',
  'ᾨ' => 'ᾨ',
  'ᾩ' => 'ᾩ',
  'ᾪ' => 'ᾪ',
  'ᾫ' => 'ᾫ',
  'ᾬ' => 'ᾬ',
  'ᾭ' => 'ᾭ',
  'ᾮ' => 'ᾮ',
  'ᾯ' => 'ᾯ',
  'ᾰ' => 'ᾰ',
  'ᾱ' => 'ᾱ',
  'ᾲ' => 'ᾲ',
  'ᾳ' => 'ᾳ',
  'ᾴ' => 'ᾴ',
  'ᾶ' => 'ᾶ',
  'ᾷ' => 'ᾷ',
  'Ᾰ' => 'Ᾰ',
  'Ᾱ' => 'Ᾱ',
  'Ὰ' => 'Ὰ',
  'Ά' => 'Ά',
  'ᾼ' => 'ᾼ',
  'ι' => 'ι',
  '῁' => '῁',
  'ῂ' => 'ῂ',
  'ῃ' => 'ῃ',
  'ῄ' => 'ῄ',
  'ῆ' => 'ῆ',
  'ῇ' => 'ῇ',
  'Ὲ' => 'Ὲ',
  'Έ' => 'Έ',
  'Ὴ' => 'Ὴ',
  'Ή' => 'Ή',
  'ῌ' => 'ῌ',
  '῍' => '῍',
  '῎' => '῎',
  '῏' => '῏',
  'ῐ' => 'ῐ',
  'ῑ' => 'ῑ',
  'ῒ' => 'ῒ',
  'ΐ' => 'ΐ',
  'ῖ' => 'ῖ',
  'ῗ' => 'ῗ',
  'Ῐ' => 'Ῐ',
  'Ῑ' => 'Ῑ',
  'Ὶ' => 'Ὶ',
  'Ί' => 'Ί',
  '῝' => '῝',
  '῞' => '῞',
  '῟' => '῟',
  'ῠ' => 'ῠ',
  'ῡ' => 'ῡ',
  'ῢ' => 'ῢ',
  'ΰ' => 'ΰ',
  'ῤ' => 'ῤ',
  'ῥ' => 'ῥ',
  'ῦ' => 'ῦ',
  'ῧ' => 'ῧ',
  'Ῠ' => 'Ῠ',
  'Ῡ' => 'Ῡ',
  'Ὺ' => 'Ὺ',
  'Ύ' => 'Ύ',
  'Ῥ' => 'Ῥ',
  '῭' => '῭',
  '΅' => '΅',
  '`' => '`',
  'ῲ' => 'ῲ',
  'ῳ' => 'ῳ',
  'ῴ' => 'ῴ',
  'ῶ' => 'ῶ',
  'ῷ' => 'ῷ',
  'Ὸ' => 'Ὸ',
  'Ό' => 'Ό',
  'Ὼ' => 'Ὼ',
  'Ώ' => 'Ώ',
  'ῼ' => 'ῼ',
  '´' => '´',
  ' ' => ' ',
  ' ' => ' ',
  'Ω' => 'Ω',
  'K' => 'K',
  'Å' => 'Å',
  '↚' => '↚',
  '↛' => '↛',
  '↮' => '↮',
  '⇍' => '⇍',
  '⇎' => '⇎',
  '⇏' => '⇏',
  '∄' => '∄',
  '∉' => '∉',
  '∌' => '∌',
  '∤' => '∤',
  '∦' => '∦',
  '≁' => '≁',
  '≄' => '≄',
  '≇' => '≇',
  '≉' => '≉',
  '≠' => '≠',
  '≢' => '≢',
  '≭' => '≭',
  '≮' => '≮',
  '≯' => '≯',
  '≰' => '≰',
  '≱' => '≱',
  '≴' => '≴',
  '≵' => '≵',
  '≸' => '≸',
  '≹' => '≹',
  '⊀' => '⊀',
  '⊁' => '⊁',
  '⊄' => '⊄',
  '⊅' => '⊅',
  '⊈' => '⊈',
  '⊉' => '⊉',
  '⊬' => '⊬',
  '⊭' => '⊭',
  '⊮' => '⊮',
  '⊯' => '⊯',
  '⋠' => '⋠',
  '⋡' => '⋡',
  '⋢' => '⋢',
  '⋣' => '⋣',
  '⋪' => '⋪',
  '⋫' => '⋫',
  '⋬' => '⋬',
  '⋭' => '⋭',
  '〈' => '〈',
  '〉' => '〉',
  '⫝̸' => '⫝̸',
  'が' => 'が',
  'ぎ' => 'ぎ',
  'ぐ' => 'ぐ',
  'げ' => 'げ',
  'ご' => 'ご',
  'ざ' => 'ざ',
  'じ' => 'じ',
  'ず' => 'ず',
  'ぜ' => 'ぜ',
  'ぞ' => 'ぞ',
  'だ' => 'だ',
  'ぢ' => 'ぢ',
  'づ' => 'づ',
  'で' => 'で',
  'ど' => 'ど',
  'ば' => 'ば',
  'ぱ' => 'ぱ',
  'び' => 'び',
  'ぴ' => 'ぴ',
  'ぶ' => 'ぶ',
  'ぷ' => 'ぷ',
  'べ' => 'べ',
  'ぺ' => 'ぺ',
  'ぼ' => 'ぼ',
  'ぽ' => 'ぽ',
  'ゔ' => 'ゔ',
  'ゞ' => 'ゞ',
  'ガ' => 'ガ',
  'ギ' => 'ギ',
  'グ' => 'グ',
  'ゲ' => 'ゲ',
  'ゴ' => 'ゴ',
  'ザ' => 'ザ',
  'ジ' => 'ジ',
  'ズ' => 'ズ',
  'ゼ' => 'ゼ',
  'ゾ' => 'ゾ',
  'ダ' => 'ダ',
  'ヂ' => 'ヂ',
  'ヅ' => 'ヅ',
  'デ' => 'デ',
  'ド' => 'ド',
  'バ' => 'バ',
  'パ' => 'パ',
  'ビ' => 'ビ',
  'ピ' => 'ピ',
  'ブ' => 'ブ',
  'プ' => 'プ',
  'ベ' => 'ベ',
  'ペ' => 'ペ',
  'ボ' => 'ボ',
  'ポ' => 'ポ',
  'ヴ' => 'ヴ',
  'ヷ' => 'ヷ',
  'ヸ' => 'ヸ',
  'ヹ' => 'ヹ',
  'ヺ' => 'ヺ',
  'ヾ' => 'ヾ',
  '豈' => '豈',
  '更' => '更',
  '車' => '車',
  '賈' => '賈',
  '滑' => '滑',
  '串' => '串',
  '句' => '句',
  '龜' => '龜',
  '龜' => '龜',
  '契' => '契',
  '金' => '金',
  '喇' => '喇',
  '奈' => '奈',
  '懶' => '懶',
  '癩' => '癩',
  '羅' => '羅',
  '蘿' => '蘿',
  '螺' => '螺',
  '裸' => '裸',
  '邏' => '邏',
  '樂' => '樂',
  '洛' => '洛',
  '烙' => '烙',
  '珞' => '珞',
  '落' => '落',
  '酪' => '酪',
  '駱' => '駱',
  '亂' => '亂',
  '卵' => '卵',
  '欄' => '欄',
  '爛' => '爛',
  '蘭' => '蘭',
  '鸞' => '鸞',
  '嵐' => '嵐',
  '濫' => '濫',
  '藍' => '藍',
  '襤' => '襤',
  '拉' => '拉',
  '臘' => '臘',
  '蠟' => '蠟',
  '廊' => '廊',
  '朗' => '朗',
  '浪' => '浪',
  '狼' => '狼',
  '郎' => '郎',
  '來' => '來',
  '冷' => '冷',
  '勞' => '勞',
  '擄' => '擄',
  '櫓' => '櫓',
  '爐' => '爐',
  '盧' => '盧',
  '老' => '老',
  '蘆' => '蘆',
  '虜' => '虜',
  '路' => '路',
  '露' => '露',
  '魯' => '魯',
  '鷺' => '鷺',
  '碌' => '碌',
  '祿' => '祿',
  '綠' => '綠',
  '菉' => '菉',
  '錄' => '錄',
  '鹿' => '鹿',
  '論' => '論',
  '壟' => '壟',
  '弄' => '弄',
  '籠' => '籠',
  '聾' => '聾',
  '牢' => '牢',
  '磊' => '磊',
  '賂' => '賂',
  '雷' => '雷',
  '壘' => '壘',
  '屢' => '屢',
  '樓' => '樓',
  '淚' => '淚',
  '漏' => '漏',
  '累' => '累',
  '縷' => '縷',
  '陋' => '陋',
  '勒' => '勒',
  '肋' => '肋',
  '凜' => '凜',
  '凌' => '凌',
  '稜' => '稜',
  '綾' => '綾',
  '菱' => '菱',
  '陵' => '陵',
  '讀' => '讀',
  '拏' => '拏',
  '樂' => '樂',
  '諾' => '諾',
  '丹' => '丹',
  '寧' => '寧',
  '怒' => '怒',
  '率' => '率',
  '異' => '異',
  '北' => '北',
  '磻' => '磻',
  '便' => '便',
  '復' => '復',
  '不' => '不',
  '泌' => '泌',
  '數' => '數',
  '索' => '索',
  '參' => '參',
  '塞' => '塞',
  '省' => '省',
  '葉' => '葉',
  '說' => '說',
  '殺' => '殺',
  '辰' => '辰',
  '沈' => '沈',
  '拾' => '拾',
  '若' => '若',
  '掠' => '掠',
  '略' => '略',
  '亮' => '亮',
  '兩' => '兩',
  '凉' => '凉',
  '梁' => '梁',
  '糧' => '糧',
  '良' => '良',
  '諒' => '諒',
  '量' => '量',
  '勵' => '勵',
  '呂' => '呂',
  '女' => '女',
  '廬' => '廬',
  '旅' => '旅',
  '濾' => '濾',
  '礪' => '礪',
  '閭' => '閭',
  '驪' => '驪',
  '麗' => '麗',
  '黎' => '黎',
  '力' => '力',
  '曆' => '曆',
  '歷' => '歷',
  '轢' => '轢',
  '年' => '年',
  '憐' => '憐',
  '戀' => '戀',
  '撚' => '撚',
  '漣' => '漣',
  '煉' => '煉',
  '璉' => '璉',
  '秊' => '秊',
  '練' => '練',
  '聯' => '聯',
  '輦' => '輦',
  '蓮' => '蓮',
  '連' => '連',
  '鍊' => '鍊',
  '列' => '列',
  '劣' => '劣',
  '咽' => '咽',
  '烈' => '烈',
  '裂' => '裂',
  '說' => '說',
  '廉' => '廉',
  '念' => '念',
  '捻' => '捻',
  '殮' => '殮',
  '簾' => '簾',
  '獵' => '獵',
  '令' => '令',
  '囹' => '囹',
  '寧' => '寧',
  '嶺' => '嶺',
  '怜' => '怜',
  '玲' => '玲',
  '瑩' => '瑩',
  '羚' => '羚',
  '聆' => '聆',
  '鈴' => '鈴',
  '零' => '零',
  '靈' => '靈',
  '領' => '領',
  '例' => '例',
  '禮' => '禮',
  '醴' => '醴',
  '隸' => '隸',
  '惡' => '惡',
  '了' => '了',
  '僚' => '僚',
  '寮' => '寮',
  '尿' => '尿',
  '料' => '料',
  '樂' => '樂',
  '燎' => '燎',
  '療' => '療',
  '蓼' => '蓼',
  '遼' => '遼',
  '龍' => '龍',
  '暈' => '暈',
  '阮' => '阮',
  '劉' => '劉',
  '杻' => '杻',
  '柳' => '柳',
  '流' => '流',
  '溜' => '溜',
  '琉' => '琉',
  '留' => '留',
  '硫' => '硫',
  '紐' => '紐',
  '類' => '類',
  '六' => '六',
  '戮' => '戮',
  '陸' => '陸',
  '倫' => '倫',
  '崙' => '崙',
  '淪' => '淪',
  '輪' => '輪',
  '律' => '律',
  '慄' => '慄',
  '栗' => '栗',
  '率' => '率',
  '隆' => '隆',
  '利' => '利',
  '吏' => '吏',
  '履' => '履',
  '易' => '易',
  '李' => '李',
  '梨' => '梨',
  '泥' => '泥',
  '理' => '理',
  '痢' => '痢',
  '罹' => '罹',
  '裏' => '裏',
  '裡' => '裡',
  '里' => '里',
  '離' => '離',
  '匿' => '匿',
  '溺' => '溺',
  '吝' => '吝',
  '燐' => '燐',
  '璘' => '璘',
  '藺' => '藺',
  '隣' => '隣',
  '鱗' => '鱗',
  '麟' => '麟',
  '林' => '林',
  '淋' => '淋',
  '臨' => '臨',
  '立' => '立',
  '笠' => '笠',
  '粒' => '粒',
  '狀' => '狀',
  '炙' => '炙',
  '識' => '識',
  '什' => '什',
  '茶' => '茶',
  '刺' => '刺',
  '切' => '切',
  '度' => '度',
  '拓' => '拓',
  '糖' => '糖',
  '宅' => '宅',
  '洞' => '洞',
  '暴' => '暴',
  '輻' => '輻',
  '行' => '行',
  '降' => '降',
  '見' => '見',
  '廓' => '廓',
  '兀' => '兀',
  '嗀' => '嗀',
  '塚' => '塚',
  '晴' => '晴',
  '凞' => '凞',
  '猪' => '猪',
  '益' => '益',
  '礼' => '礼',
  '神' => '神',
  '祥' => '祥',
  '福' => '福',
  '靖' => '靖',
  '精' => '精',
  '羽' => '羽',
  '蘒' => '蘒',
  '諸' => '諸',
  '逸' => '逸',
  '都' => '都',
  '飯' => '飯',
  '飼' => '飼',
  '館' => '館',
  '鶴' => '鶴',
  '郞' => '郞',
  '隷' => '隷',
  '侮' => '侮',
  '僧' => '僧',
  '免' => '免',
  '勉' => '勉',
  '勤' => '勤',
  '卑' => '卑',
  '喝' => '喝',
  '嘆' => '嘆',
  '器' => '器',
  '塀' => '塀',
  '墨' => '墨',
  '層' => '層',
  '屮' => '屮',
  '悔' => '悔',
  '慨' => '慨',
  '憎' => '憎',
  '懲' => '懲',
  '敏' => '敏',
  '既' => '既',
  '暑' => '暑',
  '梅' => '梅',
  '海' => '海',
  '渚' => '渚',
  '漢' => '漢',
  '煮' => '煮',
  '爫' => '爫',
  '琢' => '琢',
  '碑' => '碑',
  '社' => '社',
  '祉' => '祉',
  '祈' => '祈',
  '祐' => '祐',
  '祖' => '祖',
  '祝' => '祝',
  '禍' => '禍',
  '禎' => '禎',
  '穀' => '穀',
  '突' => '突',
  '節' => '節',
  '練' => '練',
  '縉' => '縉',
  '繁' => '繁',
  '署' => '署',
  '者' => '者',
  '臭' => '臭',
  '艹' => '艹',
  '艹' => '艹',
  '著' => '著',
  '褐' => '褐',
  '視' => '視',
  '謁' => '謁',
  '謹' => '謹',
  '賓' => '賓',
  '贈' => '贈',
  '辶' => '辶',
  '逸' => '逸',
  '難' => '難',
  '響' => '響',
  '頻' => '頻',
  '恵' => '恵',
  '𤋮' => '𤋮',
  '舘' => '舘',
  '並' => '並',
  '况' => '况',
  '全' => '全',
  '侀' => '侀',
  '充' => '充',
  '冀' => '冀',
  '勇' => '勇',
  '勺' => '勺',
  '喝' => '喝',
  '啕' => '啕',
  '喙' => '喙',
  '嗢' => '嗢',
  '塚' => '塚',
  '墳' => '墳',
  '奄' => '奄',
  '奔' => '奔',
  '婢' => '婢',
  '嬨' => '嬨',
  '廒' => '廒',
  '廙' => '廙',
  '彩' => '彩',
  '徭' => '徭',
  '惘' => '惘',
  '慎' => '慎',
  '愈' => '愈',
  '憎' => '憎',
  '慠' => '慠',
  '懲' => '懲',
  '戴' => '戴',
  '揄' => '揄',
  '搜' => '搜',
  '摒' => '摒',
  '敖' => '敖',
  '晴' => '晴',
  '朗' => '朗',
  '望' => '望',
  '杖' => '杖',
  '歹' => '歹',
  '殺' => '殺',
  '流' => '流',
  '滛' => '滛',
  '滋' => '滋',
  '漢' => '漢',
  '瀞' => '瀞',
  '煮' => '煮',
  '瞧' => '瞧',
  '爵' => '爵',
  '犯' => '犯',
  '猪' => '猪',
  '瑱' => '瑱',
  '甆' => '甆',
  '画' => '画',
  '瘝' => '瘝',
  '瘟' => '瘟',
  '益' => '益',
  '盛' => '盛',
  '直' => '直',
  '睊' => '睊',
  '着' => '着',
  '磌' => '磌',
  '窱' => '窱',
  '節' => '節',
  '类' => '类',
  '絛' => '絛',
  '練' => '練',
  '缾' => '缾',
  '者' => '者',
  '荒' => '荒',
  '華' => '華',
  '蝹' => '蝹',
  '襁' => '襁',
  '覆' => '覆',
  '視' => '視',
  '調' => '調',
  '諸' => '諸',
  '請' => '請',
  '謁' => '謁',
  '諾' => '諾',
  '諭' => '諭',
  '謹' => '謹',
  '變' => '變',
  '贈' => '贈',
  '輸' => '輸',
  '遲' => '遲',
  '醙' => '醙',
  '鉶' => '鉶',
  '陼' => '陼',
  '難' => '難',
  '靖' => '靖',
  '韛' => '韛',
  '響' => '響',
  '頋' => '頋',
  '頻' => '頻',
  '鬒' => '鬒',
  '龜' => '龜',
  '𢡊' => '𢡊',
  '𢡄' => '𢡄',
  '𣏕' => '𣏕',
  '㮝' => '㮝',
  '䀘' => '䀘',
  '䀹' => '䀹',
  '𥉉' => '𥉉',
  '𥳐' => '𥳐',
  '𧻓' => '𧻓',
  '齃' => '齃',
  '龎' => '龎',
  'יִ' => 'יִ',
  'ײַ' => 'ײַ',
  'שׁ' => 'שׁ',
  'שׂ' => 'שׂ',
  'שּׁ' => 'שּׁ',
  'שּׂ' => 'שּׂ',
  'אַ' => 'אַ',
  'אָ' => 'אָ',
  'אּ' => 'אּ',
  'בּ' => 'בּ',
  'גּ' => 'גּ',
  'דּ' => 'דּ',
  'הּ' => 'הּ',
  'וּ' => 'וּ',
  'זּ' => 'זּ',
  'טּ' => 'טּ',
  'יּ' => 'יּ',
  'ךּ' => 'ךּ',
  'כּ' => 'כּ',
  'לּ' => 'לּ',
  'מּ' => 'מּ',
  'נּ' => 'נּ',
  'סּ' => 'סּ',
  'ףּ' => 'ףּ',
  'פּ' => 'פּ',
  'צּ' => 'צּ',
  'קּ' => 'קּ',
  'רּ' => 'רּ',
  'שּ' => 'שּ',
  'תּ' => 'תּ',
  'וֹ' => 'וֹ',
  'בֿ' => 'בֿ',
  'כֿ' => 'כֿ',
  'פֿ' => 'פֿ',
  '𑂚' => '𑂚',
  '𑂜' => '𑂜',
  '𑂫' => '𑂫',
  '𑄮' => '𑄮',
  '𑄯' => '𑄯',
  '𑍋' => '𑍋',
  '𑍌' => '𑍌',
  '𑒻' => '𑒻',
  '𑒼' => '𑒼',
  '𑒾' => '𑒾',
  '𑖺' => '𑖺',
  '𑖻' => '𑖻',
  '𑤸' => '𑤸',
  '𝅗𝅥' => '𝅗𝅥',
  '𝅘𝅥' => '𝅘𝅥',
  '𝅘𝅥𝅮' => '𝅘𝅥𝅮',
  '𝅘𝅥𝅯' => '𝅘𝅥𝅯',
  '𝅘𝅥𝅰' => '𝅘𝅥𝅰',
  '𝅘𝅥𝅱' => '𝅘𝅥𝅱',
  '𝅘𝅥𝅲' => '𝅘𝅥𝅲',
  '𝆹𝅥' => '𝆹𝅥',
  '𝆺𝅥' => '𝆺𝅥',
  '𝆹𝅥𝅮' => '𝆹𝅥𝅮',
  '𝆺𝅥𝅮' => '𝆺𝅥𝅮',
  '𝆹𝅥𝅯' => '𝆹𝅥𝅯',
  '𝆺𝅥𝅯' => '𝆺𝅥𝅯',
  '丽' => '丽',
  '丸' => '丸',
  '乁' => '乁',
  '𠄢' => '𠄢',
  '你' => '你',
  '侮' => '侮',
  '侻' => '侻',
  '倂' => '倂',
  '偺' => '偺',
  '備' => '備',
  '僧' => '僧',
  '像' => '像',
  '㒞' => '㒞',
  '𠘺' => '𠘺',
  '免' => '免',
  '兔' => '兔',
  '兤' => '兤',
  '具' => '具',
  '𠔜' => '𠔜',
  '㒹' => '㒹',
  '內' => '內',
  '再' => '再',
  '𠕋' => '𠕋',
  '冗' => '冗',
  '冤' => '冤',
  '仌' => '仌',
  '冬' => '冬',
  '况' => '况',
  '𩇟' => '𩇟',
  '凵' => '凵',
  '刃' => '刃',
  '㓟' => '㓟',
  '刻' => '刻',
  '剆' => '剆',
  '割' => '割',
  '剷' => '剷',
  '㔕' => '㔕',
  '勇' => '勇',
  '勉' => '勉',
  '勤' => '勤',
  '勺' => '勺',
  '包' => '包',
  '匆' => '匆',
  '北' => '北',
  '卉' => '卉',
  '卑' => '卑',
  '博' => '博',
  '即' => '即',
  '卽' => '卽',
  '卿' => '卿',
  '卿' => '卿',
  '卿' => '卿',
  '𠨬' => '𠨬',
  '灰' => '灰',
  '及' => '及',
  '叟' => '叟',
  '𠭣' => '𠭣',
  '叫' => '叫',
  '叱' => '叱',
  '吆' => '吆',
  '咞' => '咞',
  '吸' => '吸',
  '呈' => '呈',
  '周' => '周',
  '咢' => '咢',
  '哶' => '哶',
  '唐' => '唐',
  '啓' => '啓',
  '啣' => '啣',
  '善' => '善',
  '善' => '善',
  '喙' => '喙',
  '喫' => '喫',
  '喳' => '喳',
  '嗂' => '嗂',
  '圖' => '圖',
  '嘆' => '嘆',
  '圗' => '圗',
  '噑' => '噑',
  '噴' => '噴',
  '切' => '切',
  '壮' => '壮',
  '城' => '城',
  '埴' => '埴',
  '堍' => '堍',
  '型' => '型',
  '堲' => '堲',
  '報' => '報',
  '墬' => '墬',
  '𡓤' => '𡓤',
  '売' => '売',
  '壷' => '壷',
  '夆' => '夆',
  '多' => '多',
  '夢' => '夢',
  '奢' => '奢',
  '𡚨' => '𡚨',
  '𡛪' => '𡛪',
  '姬' => '姬',
  '娛' => '娛',
  '娧' => '娧',
  '姘' => '姘',
  '婦' => '婦',
  '㛮' => '㛮',
  '㛼' => '㛼',
  '嬈' => '嬈',
  '嬾' => '嬾',
  '嬾' => '嬾',
  '𡧈' => '𡧈',
  '寃' => '寃',
  '寘' => '寘',
  '寧' => '寧',
  '寳' => '寳',
  '𡬘' => '𡬘',
  '寿' => '寿',
  '将' => '将',
  '当' => '当',
  '尢' => '尢',
  '㞁' => '㞁',
  '屠' => '屠',
  '屮' => '屮',
  '峀' => '峀',
  '岍' => '岍',
  '𡷤' => '𡷤',
  '嵃' => '嵃',
  '𡷦' => '𡷦',
  '嵮' => '嵮',
  '嵫' => '嵫',
  '嵼' => '嵼',
  '巡' => '巡',
  '巢' => '巢',
  '㠯' => '㠯',
  '巽' => '巽',
  '帨' => '帨',
  '帽' => '帽',
  '幩' => '幩',
  '㡢' => '㡢',
  '𢆃' => '𢆃',
  '㡼' => '㡼',
  '庰' => '庰',
  '庳' => '庳',
  '庶' => '庶',
  '廊' => '廊',
  '𪎒' => '𪎒',
  '廾' => '廾',
  '𢌱' => '𢌱',
  '𢌱' => '𢌱',
  '舁' => '舁',
  '弢' => '弢',
  '弢' => '弢',
  '㣇' => '㣇',
  '𣊸' => '𣊸',
  '𦇚' => '𦇚',
  '形' => '形',
  '彫' => '彫',
  '㣣' => '㣣',
  '徚' => '徚',
  '忍' => '忍',
  '志' => '志',
  '忹' => '忹',
  '悁' => '悁',
  '㤺' => '㤺',
  '㤜' => '㤜',
  '悔' => '悔',
  '𢛔' => '𢛔',
  '惇' => '惇',
  '慈' => '慈',
  '慌' => '慌',
  '慎' => '慎',
  '慌' => '慌',
  '慺' => '慺',
  '憎' => '憎',
  '憲' => '憲',
  '憤' => '憤',
  '憯' => '憯',
  '懞' => '懞',
  '懲' => '懲',
  '懶' => '懶',
  '成' => '成',
  '戛' => '戛',
  '扝' => '扝',
  '抱' => '抱',
  '拔' => '拔',
  '捐' => '捐',
  '𢬌' => '𢬌',
  '挽' => '挽',
  '拼' => '拼',
  '捨' => '捨',
  '掃' => '掃',
  '揤' => '揤',
  '𢯱' => '𢯱',
  '搢' => '搢',
  '揅' => '揅',
  '掩' => '掩',
  '㨮' => '㨮',
  '摩' => '摩',
  '摾' => '摾',
  '撝' => '撝',
  '摷' => '摷',
  '㩬' => '㩬',
  '敏' => '敏',
  '敬' => '敬',
  '𣀊' => '𣀊',
  '旣' => '旣',
  '書' => '書',
  '晉' => '晉',
  '㬙' => '㬙',
  '暑' => '暑',
  '㬈' => '㬈',
  '㫤' => '㫤',
  '冒' => '冒',
  '冕' => '冕',
  '最' => '最',
  '暜' => '暜',
  '肭' => '肭',
  '䏙' => '䏙',
  '朗' => '朗',
  '望' => '望',
  '朡' => '朡',
  '杞' => '杞',
  '杓' => '杓',
  '𣏃' => '𣏃',
  '㭉' => '㭉',
  '柺' => '柺',
  '枅' => '枅',
  '桒' => '桒',
  '梅' => '梅',
  '𣑭' => '𣑭',
  '梎' => '梎',
  '栟' => '栟',
  '椔' => '椔',
  '㮝' => '㮝',
  '楂' => '楂',
  '榣' => '榣',
  '槪' => '槪',
  '檨' => '檨',
  '𣚣' => '𣚣',
  '櫛' => '櫛',
  '㰘' => '㰘',
  '次' => '次',
  '𣢧' => '𣢧',
  '歔' => '歔',
  '㱎' => '㱎',
  '歲' => '歲',
  '殟' => '殟',
  '殺' => '殺',
  '殻' => '殻',
  '𣪍' => '𣪍',
  '𡴋' => '𡴋',
  '𣫺' => '𣫺',
  '汎' => '汎',
  '𣲼' => '𣲼',
  '沿' => '沿',
  '泍' => '泍',
  '汧' => '汧',
  '洖' => '洖',
  '派' => '派',
  '海' => '海',
  '流' => '流',
  '浩' => '浩',
  '浸' => '浸',
  '涅' => '涅',
  '𣴞' => '𣴞',
  '洴' => '洴',
  '港' => '港',
  '湮' => '湮',
  '㴳' => '㴳',
  '滋' => '滋',
  '滇' => '滇',
  '𣻑' => '𣻑',
  '淹' => '淹',
  '潮' => '潮',
  '𣽞' => '𣽞',
  '𣾎' => '𣾎',
  '濆' => '濆',
  '瀹' => '瀹',
  '瀞' => '瀞',
  '瀛' => '瀛',
  '㶖' => '㶖',
  '灊' => '灊',
  '災' => '災',
  '灷' => '灷',
  '炭' => '炭',
  '𠔥' => '𠔥',
  '煅' => '煅',
  '𤉣' => '𤉣',
  '熜' => '熜',
  '𤎫' => '𤎫',
  '爨' => '爨',
  '爵' => '爵',
  '牐' => '牐',
  '𤘈' => '𤘈',
  '犀' => '犀',
  '犕' => '犕',
  '𤜵' => '𤜵',
  '𤠔' => '𤠔',
  '獺' => '獺',
  '王' => '王',
  '㺬' => '㺬',
  '玥' => '玥',
  '㺸' => '㺸',
  '㺸' => '㺸',
  '瑇' => '瑇',
  '瑜' => '瑜',
  '瑱' => '瑱',
  '璅' => '璅',
  '瓊' => '瓊',
  '㼛' => '㼛',
  '甤' => '甤',
  '𤰶' => '𤰶',
  '甾' => '甾',
  '𤲒' => '𤲒',
  '異' => '異',
  '𢆟' => '𢆟',
  '瘐' => '瘐',
  '𤾡' => '𤾡',
  '𤾸' => '𤾸',
  '𥁄' => '𥁄',
  '㿼' => '㿼',
  '䀈' => '䀈',
  '直' => '直',
  '𥃳' => '𥃳',
  '𥃲' => '𥃲',
  '𥄙' => '𥄙',
  '𥄳' => '𥄳',
  '眞' => '眞',
  '真' => '真',
  '真' => '真',
  '睊' => '睊',
  '䀹' => '䀹',
  '瞋' => '瞋',
  '䁆' => '䁆',
  '䂖' => '䂖',
  '𥐝' => '𥐝',
  '硎' => '硎',
  '碌' => '碌',
  '磌' => '磌',
  '䃣' => '䃣',
  '𥘦' => '𥘦',
  '祖' => '祖',
  '𥚚' => '𥚚',
  '𥛅' => '𥛅',
  '福' => '福',
  '秫' => '秫',
  '䄯' => '䄯',
  '穀' => '穀',
  '穊' => '穊',
  '穏' => '穏',
  '𥥼' => '𥥼',
  '𥪧' => '𥪧',
  '𥪧' => '𥪧',
  '竮' => '竮',
  '䈂' => '䈂',
  '𥮫' => '𥮫',
  '篆' => '篆',
  '築' => '築',
  '䈧' => '䈧',
  '𥲀' => '𥲀',
  '糒' => '糒',
  '䊠' => '䊠',
  '糨' => '糨',
  '糣' => '糣',
  '紀' => '紀',
  '𥾆' => '𥾆',
  '絣' => '絣',
  '䌁' => '䌁',
  '緇' => '緇',
  '縂' => '縂',
  '繅' => '繅',
  '䌴' => '䌴',
  '𦈨' => '𦈨',
  '𦉇' => '𦉇',
  '䍙' => '䍙',
  '𦋙' => '𦋙',
  '罺' => '罺',
  '𦌾' => '𦌾',
  '羕' => '羕',
  '翺' => '翺',
  '者' => '者',
  '𦓚' => '𦓚',
  '𦔣' => '𦔣',
  '聠' => '聠',
  '𦖨' => '𦖨',
  '聰' => '聰',
  '𣍟' => '𣍟',
  '䏕' => '䏕',
  '育' => '育',
  '脃' => '脃',
  '䐋' => '䐋',
  '脾' => '脾',
  '媵' => '媵',
  '𦞧' => '𦞧',
  '𦞵' => '𦞵',
  '𣎓' => '𣎓',
  '𣎜' => '𣎜',
  '舁' => '舁',
  '舄' => '舄',
  '辞' => '辞',
  '䑫' => '䑫',
  '芑' => '芑',
  '芋' => '芋',
  '芝' => '芝',
  '劳' => '劳',
  '花' => '花',
  '芳' => '芳',
  '芽' => '芽',
  '苦' => '苦',
  '𦬼' => '𦬼',
  '若' => '若',
  '茝' => '茝',
  '荣' => '荣',
  '莭' => '莭',
  '茣' => '茣',
  '莽' => '莽',
  '菧' => '菧',
  '著' => '著',
  '荓' => '荓',
  '菊' => '菊',
  '菌' => '菌',
  '菜' => '菜',
  '𦰶' => '𦰶',
  '𦵫' => '𦵫',
  '𦳕' => '𦳕',
  '䔫' => '䔫',
  '蓱' => '蓱',
  '蓳' => '蓳',
  '蔖' => '蔖',
  '𧏊' => '𧏊',
  '蕤' => '蕤',
  '𦼬' => '𦼬',
  '䕝' => '䕝',
  '䕡' => '䕡',
  '𦾱' => '𦾱',
  '𧃒' => '𧃒',
  '䕫' => '䕫',
  '虐' => '虐',
  '虜' => '虜',
  '虧' => '虧',
  '虩' => '虩',
  '蚩' => '蚩',
  '蚈' => '蚈',
  '蜎' => '蜎',
  '蛢' => '蛢',
  '蝹' => '蝹',
  '蜨' => '蜨',
  '蝫' => '蝫',
  '螆' => '螆',
  '䗗' => '䗗',
  '蟡' => '蟡',
  '蠁' => '蠁',
  '䗹' => '䗹',
  '衠' => '衠',
  '衣' => '衣',
  '𧙧' => '𧙧',
  '裗' => '裗',
  '裞' => '裞',
  '䘵' => '䘵',
  '裺' => '裺',
  '㒻' => '㒻',
  '𧢮' => '𧢮',
  '𧥦' => '𧥦',
  '䚾' => '䚾',
  '䛇' => '䛇',
  '誠' => '誠',
  '諭' => '諭',
  '變' => '變',
  '豕' => '豕',
  '𧲨' => '𧲨',
  '貫' => '貫',
  '賁' => '賁',
  '贛' => '贛',
  '起' => '起',
  '𧼯' => '𧼯',
  '𠠄' => '𠠄',
  '跋' => '跋',
  '趼' => '趼',
  '跰' => '跰',
  '𠣞' => '𠣞',
  '軔' => '軔',
  '輸' => '輸',
  '𨗒' => '𨗒',
  '𨗭' => '𨗭',
  '邔' => '邔',
  '郱' => '郱',
  '鄑' => '鄑',
  '𨜮' => '𨜮',
  '鄛' => '鄛',
  '鈸' => '鈸',
  '鋗' => '鋗',
  '鋘' => '鋘',
  '鉼' => '鉼',
  '鏹' => '鏹',
  '鐕' => '鐕',
  '𨯺' => '𨯺',
  '開' => '開',
  '䦕' => '䦕',
  '閷' => '閷',
  '𨵷' => '𨵷',
  '䧦' => '䧦',
  '雃' => '雃',
  '嶲' => '嶲',
  '霣' => '霣',
  '𩅅' => '𩅅',
  '𩈚' => '𩈚',
  '䩮' => '䩮',
  '䩶' => '䩶',
  '韠' => '韠',
  '𩐊' => '𩐊',
  '䪲' => '䪲',
  '𩒖' => '𩒖',
  '頋' => '頋',
  '頋' => '頋',
  '頩' => '頩',
  '𩖶' => '𩖶',
  '飢' => '飢',
  '䬳' => '䬳',
  '餩' => '餩',
  '馧' => '馧',
  '駂' => '駂',
  '駾' => '駾',
  '䯎' => '䯎',
  '𩬰' => '𩬰',
  '鬒' => '鬒',
  '鱀' => '鱀',
  '鳽' => '鳽',
  '䳎' => '䳎',
  '䳭' => '䳭',
  '鵧' => '鵧',
  '𪃎' => '𪃎',
  '䳸' => '䳸',
  '𪄅' => '𪄅',
  '𪈎' => '𪈎',
  '𪊑' => '𪊑',
  '麻' => '麻',
  '䵖' => '䵖',
  '黹' => '黹',
  '黾' => '黾',
  '鼅' => '鼅',
  '鼏' => '鼏',
  '鼖' => '鼖',
  '鼻' => '鼻',
  '𪘀' => '𪘀',
);
<?php

class Normalizer extends Symfony\Polyfill\Intl\Normalizer\Normalizer
{
    /**
     * @deprecated since ICU 56 and removed in PHP 8
     */
    public const NONE = 2;
    public const FORM_D = 4;
    public const FORM_KD = 8;
    public const FORM_C = 16;
    public const FORM_KC = 32;
    public const NFD = 4;
    public const NFKD = 8;
    public const NFC = 16;
    public const NFKC = 32;
}
Copyright (c) 2015-present Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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); }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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); }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <p@tchwork.com>
 *
 * @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;
    }
}
# This file is part of the Symfony package.
#
# (c) Fabien Potencier <fabien@symfony.com>
#
# For the full copyright and license information, please view
# https://symfony.com/doc/current/contributing/code/license.html

function _sf_{{ COMMAND_NAME }}
    set sf_cmd (commandline -o)
    set c (count (commandline -oc))

    set completecmd "$sf_cmd[1]" "_complete" "--no-interaction" "-sfish" "-a{{ VERSION }}"

    for i in $sf_cmd
        if [ $i != "" ]
            set completecmd $completecmd "-i$i"
        end
    end

    set completecmd $completecmd "-c$c"

    set sfcomplete (env SHELL_VERBOSITY=0 $completecmd)

    for i in $sfcomplete
        echo $i
    end
end

complete -c '{{ COMMAND_NAME }}' -a '(_sf_{{ COMMAND_NAME }})' -f
#compdef {{ COMMAND_NAME }}

# This file is part of the Symfony package.
#
# (c) Fabien Potencier <fabien@symfony.com>
#
# For the full copyright and license information, please view
# https://symfony.com/doc/current/contributing/code/license.html

#
# zsh completions for {{ COMMAND_NAME }}
#
# References:
#   - https://github.com/spf13/cobra/blob/master/zsh_completions.go
#   - https://github.com/symfony/symfony/blob/5.4/src/Symfony/Component/Console/Resources/completion.bash
#
_sf_{{ COMMAND_NAME }}() {
    local lastParam flagPrefix requestComp out comp
    local -a completions

    # The user could have moved the cursor backwards on the command-line.
    # We need to trigger completion from the $CURRENT location, so we need
    # to truncate the command-line ($words) up to the $CURRENT location.
    # (We cannot use $CURSOR as its value does not work when a command is an alias.)
    words=("${=words[1,CURRENT]}") lastParam=${words[-1]}

    # For zsh, when completing a flag with an = (e.g., {{ COMMAND_NAME }} -n=<TAB>)
    # completions must be prefixed with the flag
    setopt local_options BASH_REMATCH
    if [[ "${lastParam}" =~ '-.*=' ]]; then
        # We are dealing with a flag with an =
        flagPrefix="-P ${BASH_REMATCH}"
    fi

    # Prepare the command to obtain completions
    requestComp="${words[0]} ${words[1]} _complete --no-interaction -szsh -a{{ VERSION }} -c$((CURRENT-1))" i=""
    for w in ${words[@]}; do
        w=$(printf -- '%b' "$w")
        # remove quotes from typed values
        quote="${w:0:1}"
        if [ "$quote" = \' ]; then
            w="${w%\'}"
            w="${w#\'}"
        elif [ "$quote" = \" ]; then
            w="${w%\"}"
            w="${w#\"}"
        fi
        # empty values are ignored
        if [ ! -z "$w" ]; then
            i="${i}-i${w} "
        fi
    done

    # Ensure at least 1 input
    if [ "${i}" = "" ]; then
        requestComp="${requestComp} -i\" \""
    else
        requestComp="${requestComp} ${i}"
    fi

    # Use eval to handle any environment variables and such
    out=$(eval SHELL_VERBOSITY=0 ${requestComp} 2>/dev/null)

    while IFS='\n' read -r comp; do
        if [ -n "$comp" ]; then
            # If requested, completions are returned with a description.
            # The description is preceded by a TAB character.
            # For zsh's _describe, we need to use a : instead of a TAB.
            # We first need to escape any : as part of the completion itself.
            comp=${comp//:/\\:}
            local tab=$(printf '\t')
            comp=${comp//$tab/:}
            completions+=${comp}
        fi
    done < <(printf "%s\n" "${out[@]}")

    # Let inbuilt _describe handle completions
    eval _describe "completions" completions $flagPrefix
    return $?
}

compdef _sf_{{ COMMAND_NAME }} {{ COMMAND_NAME }}
MZ                @                                       	!L!This program cannot be run in DOS mode.
$       ,;B;B;B2מ:B2-B2ƞ9B2ў?Ba98B;CB2Ȟ:B2֞:B2Ӟ:BRich;B        PE  L MoO         	  
         8           @                      `     ?   @                           "  P    @                      P  p   !                             8!  @                                          .text   	      
                    `.rdata  	       
                 @  @.data      0                    @  .rsrc       @                    @  @.reloc     P      "              @  B                                                                                                                                                                                                                                                                                                                                                        j$@ x  j @ e EPV  @ EЃPV @ MX @ e EP5H @ L @ YY5\ @ EP5` @ D @ YYP @ MMT @ 3H  ; 0@ u  h@   l3@ $40@ 5h3@ 40@ h$0@ h(0@ h 0@  @ 00@ }j  Yjh"@   3ۉ]d   p]俀3@ SVW0 @ ;t;u3Fuh  4 @ 3F|3@ ;u
j\  Y;|3@ u,5|3@ h @ h @   YYtE      5<0@ |3@ ;uh @ h @ l  YY|3@    9]uSW8 @ 93@ th3@   Yt
SjS3@ $0@  @ 5$0@ 5(0@ 5 0@ 80@ 9,0@ u7P @ E	MPQ  YYËeE80@ 39,0@ uPh @ 9<0@ u @ E80@   øMZ  f9  @ t3M< @   @ 8PE  uH  t  uՃ   v39   xtv39   j,0@ p @ jl @ YY3@ 3@  @ t3@  @ p3@  @  x3@ V    =0@  uh@  @ Yg  =0@ u	j @ Y3{  U(  H1@ D1@ @1@ <1@ 581@ =41@ f`1@ fT1@ f01@ f,1@ f%(1@ f-$1@ X1@ E L1@ EP1@ E\1@ 0@   P1@ L0@ @0@ 	 D0@     0@ 0@  @ 0@ j?  Yj   @ h!@ $ @ =0@  uj  Yh	 ( @ P, @ ËUE 8csmu*xu$@= t=!t="t= @u  3] hH@   @ 3% @ jh("@ b  53@ 5 @ YEuu @ Ygj  Ye 53@ ։E53@ YYEEPEPu5l @ YPU  Eu֣3@ uփ3@ E	   E  j  YËUuNYH]ËV!@ !@ W;stЃ;r_^ËV"@ "@ W;stЃ;r_^% @ ̋UMMZ  f9t3]ËA<8PE  u3ҹ  f9H]̋UEH<ASVq3WDv}H;r	X;r
B(;r3_^[]̋UjhH"@ he@ d    PSVW 0@ 1E3PEd    eE    h  @ *tUE-  @ Ph  @ Pt;@$ЃEMd    Y_^[]ËE3=  ËeE3Md    Y_^[]% @ % @ he@ d5    D$l$l$+SVW 0@ 1E3PeuEEEEd    ËMd    Y__^[]QËUuuuuh@ h 0@    ]ËVh   h   3V   tVVVVV   ^3ËU 0@ e e SWN@  ;tt	У0@ `VEP< @ u3u @ 3 @ 3 @ 3EP @ E3E3;uO@u5 0@ ։50@ ^_[%t @ %x @ %| @ % @ % @ % @ % @ % @ % @ Pd5    D$+d$SVW( 0@ 3PEuEEd    ËMd    Y__^[]QËM3M%T @ T$BJ3J3l"@ s                                                                                                                                                                                                                                                     #  #  #  )  r)  b)  H)  4)  )  (  (  (  (  (  (  )      #  $  %  %  &  d&  &  $      ('  '  '  '  '  (  ((  6(  '  H(  Z(  t(  (  '  '   '  '  '  l'  ^'  R'  F'  >'  >(  0'  '  )          @         W@ @                     MoO       l   !    @0@ 0@ bad allocation      H                                                            0@ !@    RSDSьJ!LZ    c:\users\seld\documents\visual studio 2010\Projects\hiddeninp\Release\hiddeninp.pdb     e                            @ @                 :@             @ @ @ "   d"@                        "          #      $#          &  D   H#          (  h                       #  #  #  )  r)  b)  H)  4)  )  (  (  (  (  (  (  )      #  $  %  %  &  d&  &  $      ('  '  '  '  '  (  ((  6(  '  H(  Z(  t(  (  '  '   '  '  '  l'  ^'  R'  F'  >'  >(  0'  '  )      GetConsoleMode  SetConsoleMode  ;GetStdHandle  KERNEL32.dll   ??$?6DU?$char_traits@D@std@@V?$allocator@D@1@@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@0@AAV10@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@0@@Z ?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A  J?cin@std@@3V?$basic_istream@DU?$char_traits@D@std@@@1@A  ??$getline@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@YAAAV?$basic_istream@DU?$char_traits@D@std@@@0@AAV10@AAV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@0@@Z ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@P6AAAV01@AAV01@@Z@Z  _??1?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAE@XZ  {??0?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAE@XZ  ?endl@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@1@AAV21@@Z  MSVCP90.dll _amsg_exit   __getmainargs ,_cexit  |_exit f _XcptFilter exit   __initenv _initterm _initterm_e <_configthreadlocale  __setusermatherr  _adjust_fdiv   __p__commode   __p__fmode  j_encode_pointer  __set_app_type  K_crt_debugger_hook  C ?terminate@@YAXXZ MSVCR90.dll _unlock  __dllonexit v_lock _onexit `_decode_pointer s_except_handler4_common _invoke_watson  ?_controlfp_s  InterlockedExchange !Sleep InterlockedCompareExchange  -TerminateProcess  GetCurrentProcess >UnhandledExceptionFilter  SetUnhandledExceptionFilter IsDebuggerPresent TQueryPerformanceCounter fGetTickCount  GetCurrentThreadId  GetCurrentProcessId OGetSystemTimeAsFileTime s __CxxFrameHandler3                                                    N@D   $!@                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            8                   P                   h                	                   	     @  (        C  V        (4   V S _ V E R S I O N _ I N F O                                                  S t r i n g F i l e I n f o   b   0 4 0 9 0 4 b 0    Q  F i l e D e s c r i p t i o n     R e a d s   f r o m   s t d i n   w i t h o u t   l e a k i n g   i n f o   t o   t h e   t e r m i n a l   a n d   o u t p u t s   b a c k   t o   s t d o u t     6   F i l e V e r s i o n     1 ,   0 ,   0 ,   0     8   I n t e r n a l N a m e   h i d d e n i n p u t   P   L e g a l C o p y r i g h t   J o r d i   B o g g i a n o   -   2 0 1 2   H   O r i g i n a l F i l e n a m e   h i d d e n i n p u t . e x e   :   P r o d u c t N a m e     H i d d e n   I n p u t     :   P r o d u c t V e r s i o n   1 ,   0 ,   0 ,   0     D    V a r F i l e I n f o     $    T r a n s l a t i o n     	<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
    <security>
      <requestedPrivileges>
        <requestedExecutionLevel level="asInvoker" uiAccess="false"></requestedExecutionLevel>
      </requestedPrivileges>
    </security>
  </trustInfo>
  <dependency>
    <dependentAssembly>
      <assemblyIdentity type="win32" name="Microsoft.VC90.CRT" version="9.0.21022.8" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>
    </dependentAssembly>
  </dependency>
</assembly>PAPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDING   @  00!0/080F0L0T0^0d0n0{000000000000001#1-1@1J1O1T1v1{1111111111111112"2*23292A2M2_2j2p222222222222333%303N3T3Z3`3f3l3s3z333333333333333334444%4;4B444444444445!5^5c5555H6M6_6}666 777*7w7|77777888=8E8P8V8\8b8h8n8t8z88889      $   0001 1t1x12 2@2\2`2h2t2 0     0                                                                                                                                                  # This file is part of the Symfony package.
#
# (c) Fabien Potencier <fabien@symfony.com>
#
# For the full copyright and license information, please view
# https://symfony.com/doc/current/contributing/code/license.html

_sf_{{ COMMAND_NAME }}() {

    # Use the default completion for shell redirect operators.
    for w in '>' '>>' '&>' '<'; do
        if [[ $w = "${COMP_WORDS[COMP_CWORD-1]}" ]]; then
            compopt -o filenames
            COMPREPLY=($(compgen -f -- "${COMP_WORDS[COMP_CWORD]}"))
            return 0
        fi
    done

    # Use newline as only separator to allow space in completion values
    local IFS=$'\n'
    local sf_cmd="${COMP_WORDS[0]}"

    # for an alias, get the real script behind it
    sf_cmd_type=$(type -t $sf_cmd)
    if [[ $sf_cmd_type == "alias" ]]; then
        sf_cmd=$(alias $sf_cmd | sed -E "s/alias $sf_cmd='(.*)'/\1/")
    elif [[ $sf_cmd_type == "file" ]]; then
        sf_cmd=$(type -p $sf_cmd)
    fi

    if [[ $sf_cmd_type != "function" && ! -x $sf_cmd ]]; then
        return 1
    fi

    local cur prev words cword
    _get_comp_words_by_ref -n := cur prev words cword

    local completecmd=("$sf_cmd" "_complete" "--no-interaction" "-sbash" "-c$cword" "-a{{ VERSION }}")
    for w in ${words[@]}; do
        w="${w//\\\\/\\}"
        # remove quotes from typed values
        quote="${w:0:1}"
        if [ "$quote" == \' ]; then
            w="${w%\'}"
            w="${w#\'}"
        elif [ "$quote" == \" ]; then
            w="${w%\"}"
            w="${w#\"}"
        fi
        # empty values are ignored
        if [ ! -z "$w" ]; then
            completecmd+=("-i$w")
        fi
    done

    local sfcomplete
    if sfcomplete=$(SHELL_VERBOSITY=0 ${completecmd[@]} 2>&1); then
        local quote suggestions
        quote=${cur:0:1}

        # Use single quotes by default if suggestions contains backslash (FQCN)
        if [ "$quote" == '' ] && [[ "$sfcomplete" =~ \\ ]]; then
            quote=\'
        fi

        if [ "$quote" == \' ]; then
            # single quotes: no additional escaping (does not accept ' in values)
            suggestions=$(for s in $sfcomplete; do printf $'%q%q%q\n' "$quote" "$s" "$quote"; done)
        elif [ "$quote" == \" ]; then
            # double quotes: double escaping for \ $ ` "
            suggestions=$(for s in $sfcomplete; do
                s=${s//\\/\\\\}
                s=${s//\$/\\\$}
                s=${s//\`/\\\`}
                s=${s//\"/\\\"}
                printf $'%q%q%q\n' "$quote" "$s" "$quote";
            done)
        else
            # no quotes: double escaping
            suggestions=$(for s in $sfcomplete; do printf $'%q\n' $(printf '%q' "$s"); done)
        fi
        COMPREPLY=($(IFS=$'\n' compgen -W "$suggestions" -- $(printf -- "%q" "$cur")))
        __ltrim_colon_completions "$cur"
    else
        if [[ "$sfcomplete" != *"Command \"_complete\" is not defined."* ]]; then
            >&2 echo
            >&2 echo $sfcomplete
        fi

        return 1
    fi
}

complete -F _sf_{{ COMMAND_NAME }} {{ COMMAND_NAME }}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Descriptor\DescriptorInterface;
use Symfony\Component\Console\Descriptor\JsonDescriptor;
use Symfony\Component\Console\Descriptor\MarkdownDescriptor;
use Symfony\Component\Console\Descriptor\ReStructuredTextDescriptor;
use Symfony\Component\Console\Descriptor\TextDescriptor;
use Symfony\Component\Console\Descriptor\XmlDescriptor;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * This class adds helper method to describe objects in various formats.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class DescriptorHelper extends Helper
{
    /**
     * @var DescriptorInterface[]
     */
    private array $descriptors = [];

    public function __construct()
    {
        $this
            ->register('txt', new TextDescriptor())
            ->register('xml', new XmlDescriptor())
            ->register('json', new JsonDescriptor())
            ->register('md', new MarkdownDescriptor())
            ->register('rst', new ReStructuredTextDescriptor())
        ;
    }

    /**
     * Describes an object if supported.
     *
     * Available options are:
     * * format: string, the output format name
     * * raw_text: boolean, sets output type as raw
     *
     * @return void
     *
     * @throws InvalidArgumentException when the given format is not supported
     */
    public function describe(OutputInterface $output, ?object $object, array $options = [])
    {
        $options = array_merge([
            'raw_text' => false,
            'format' => 'txt',
        ], $options);

        if (!isset($this->descriptors[$options['format']])) {
            throw new InvalidArgumentException(\sprintf('Unsupported format "%s".', $options['format']));
        }

        $descriptor = $this->descriptors[$options['format']];
        $descriptor->describe($output, $object, $options);
    }

    /**
     * Registers a descriptor.
     *
     * @return $this
     */
    public function register(string $format, DescriptorInterface $descriptor): static
    {
        $this->descriptors[$format] = $descriptor;

        return $this;
    }

    public function getName(): string
    {
        return 'descriptor';
    }

    public function getFormats(): array
    {
        return array_keys($this->descriptors);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

/**
 * Marks a row as being a separator.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class TableSeparator extends TableCell
{
    public function __construct(array $options = [])
    {
        parent::__construct('', $options);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\WrappableOutputFormatterInterface;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Provides helpers to display a table.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Саша Стаменковић <umpirsky@gmail.com>
 * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
 * @author Max Grigorian <maxakawizard@gmail.com>
 * @author Dany Maillard <danymaillard93b@gmail.com>
 */
class Table
{
    private const SEPARATOR_TOP = 0;
    private const SEPARATOR_TOP_BOTTOM = 1;
    private const SEPARATOR_MID = 2;
    private const SEPARATOR_BOTTOM = 3;
    private const BORDER_OUTSIDE = 0;
    private const BORDER_INSIDE = 1;
    private const DISPLAY_ORIENTATION_DEFAULT = 'default';
    private const DISPLAY_ORIENTATION_HORIZONTAL = 'horizontal';
    private const DISPLAY_ORIENTATION_VERTICAL = 'vertical';

    private ?string $headerTitle = null;
    private ?string $footerTitle = null;
    private array $headers = [];
    private array $rows = [];
    private array $effectiveColumnWidths = [];
    private int $numberOfColumns;
    private OutputInterface $output;
    private TableStyle $style;
    private array $columnStyles = [];
    private array $columnWidths = [];
    private array $columnMaxWidths = [];
    private bool $rendered = false;
    private string $displayOrientation = self::DISPLAY_ORIENTATION_DEFAULT;

    private static array $styles;

    public function __construct(OutputInterface $output)
    {
        $this->output = $output;

        self::$styles ??= self::initStyles();

        $this->setStyle('default');
    }

    /**
     * Sets a style definition.
     *
     * @return void
     */
    public static function setStyleDefinition(string $name, TableStyle $style)
    {
        self::$styles ??= self::initStyles();

        self::$styles[$name] = $style;
    }

    /**
     * Gets a style definition by name.
     */
    public static function getStyleDefinition(string $name): TableStyle
    {
        self::$styles ??= self::initStyles();

        return self::$styles[$name] ?? throw new InvalidArgumentException(\sprintf('Style "%s" is not defined.', $name));
    }

    /**
     * Sets table style.
     *
     * @return $this
     */
    public function setStyle(TableStyle|string $name): static
    {
        $this->style = $this->resolveStyle($name);

        return $this;
    }

    /**
     * Gets the current table style.
     */
    public function getStyle(): TableStyle
    {
        return $this->style;
    }

    /**
     * Sets table column style.
     *
     * @param TableStyle|string $name The style name or a TableStyle instance
     *
     * @return $this
     */
    public function setColumnStyle(int $columnIndex, TableStyle|string $name): static
    {
        $this->columnStyles[$columnIndex] = $this->resolveStyle($name);

        return $this;
    }

    /**
     * Gets the current style for a column.
     *
     * If style was not set, it returns the global table style.
     */
    public function getColumnStyle(int $columnIndex): TableStyle
    {
        return $this->columnStyles[$columnIndex] ?? $this->getStyle();
    }

    /**
     * Sets the minimum width of a column.
     *
     * @return $this
     */
    public function setColumnWidth(int $columnIndex, int $width): static
    {
        $this->columnWidths[$columnIndex] = $width;

        return $this;
    }

    /**
     * Sets the minimum width of all columns.
     *
     * @return $this
     */
    public function setColumnWidths(array $widths): static
    {
        $this->columnWidths = [];
        foreach ($widths as $index => $width) {
            $this->setColumnWidth($index, $width);
        }

        return $this;
    }

    /**
     * Sets the maximum width of a column.
     *
     * Any cell within this column which contents exceeds the specified width will be wrapped into multiple lines, while
     * formatted strings are preserved.
     *
     * @return $this
     */
    public function setColumnMaxWidth(int $columnIndex, int $width): static
    {
        if (!$this->output->getFormatter() instanceof WrappableOutputFormatterInterface) {
            throw new \LogicException(\sprintf('Setting a maximum column width is only supported when using a "%s" formatter, got "%s".', WrappableOutputFormatterInterface::class, get_debug_type($this->output->getFormatter())));
        }

        $this->columnMaxWidths[$columnIndex] = $width;

        return $this;
    }

    /**
     * @return $this
     */
    public function setHeaders(array $headers): static
    {
        $headers = array_values($headers);
        if ($headers && !\is_array($headers[0])) {
            $headers = [$headers];
        }

        $this->headers = $headers;

        return $this;
    }

    /**
     * @return $this
     */
    public function setRows(array $rows)
    {
        $this->rows = [];

        return $this->addRows($rows);
    }

    /**
     * @return $this
     */
    public function addRows(array $rows): static
    {
        foreach ($rows as $row) {
            $this->addRow($row);
        }

        return $this;
    }

    /**
     * @return $this
     */
    public function addRow(TableSeparator|array $row): static
    {
        if ($row instanceof TableSeparator) {
            $this->rows[] = $row;

            return $this;
        }

        $this->rows[] = array_values($row);

        return $this;
    }

    /**
     * Adds a row to the table, and re-renders the table.
     *
     * @return $this
     */
    public function appendRow(TableSeparator|array $row): static
    {
        if (!$this->output instanceof ConsoleSectionOutput) {
            throw new RuntimeException(\sprintf('Output should be an instance of "%s" when calling "%s".', ConsoleSectionOutput::class, __METHOD__));
        }

        if ($this->rendered) {
            $this->output->clear($this->calculateRowCount());
        }

        $this->addRow($row);
        $this->render();

        return $this;
    }

    /**
     * @return $this
     */
    public function setRow(int|string $column, array $row): static
    {
        $this->rows[$column] = $row;

        return $this;
    }

    /**
     * @return $this
     */
    public function setHeaderTitle(?string $title): static
    {
        $this->headerTitle = $title;

        return $this;
    }

    /**
     * @return $this
     */
    public function setFooterTitle(?string $title): static
    {
        $this->footerTitle = $title;

        return $this;
    }

    /**
     * @return $this
     */
    public function setHorizontal(bool $horizontal = true): static
    {
        $this->displayOrientation = $horizontal ? self::DISPLAY_ORIENTATION_HORIZONTAL : self::DISPLAY_ORIENTATION_DEFAULT;

        return $this;
    }

    /**
     * @return $this
     */
    public function setVertical(bool $vertical = true): static
    {
        $this->displayOrientation = $vertical ? self::DISPLAY_ORIENTATION_VERTICAL : self::DISPLAY_ORIENTATION_DEFAULT;

        return $this;
    }

    /**
     * Renders table to output.
     *
     * Example:
     *
     *     +---------------+-----------------------+------------------+
     *     | ISBN          | Title                 | Author           |
     *     +---------------+-----------------------+------------------+
     *     | 99921-58-10-7 | Divine Comedy         | Dante Alighieri  |
     *     | 9971-5-0210-0 | A Tale of Two Cities  | Charles Dickens  |
     *     | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
     *     +---------------+-----------------------+------------------+
     *
     * @return void
     */
    public function render()
    {
        $divider = new TableSeparator();
        $isCellWithColspan = static fn ($cell) => $cell instanceof TableCell && $cell->getColspan() >= 2;

        $horizontal = self::DISPLAY_ORIENTATION_HORIZONTAL === $this->displayOrientation;
        $vertical = self::DISPLAY_ORIENTATION_VERTICAL === $this->displayOrientation;

        $rows = [];
        if ($horizontal) {
            foreach ($this->headers[0] ?? [] as $i => $header) {
                $rows[$i] = [$header];
                foreach ($this->rows as $row) {
                    if ($row instanceof TableSeparator) {
                        continue;
                    }
                    if (isset($row[$i])) {
                        $rows[$i][] = $row[$i];
                    } elseif ($isCellWithColspan($rows[$i][0])) {
                        // Noop, there is a "title"
                    } else {
                        $rows[$i][] = null;
                    }
                }
            }
        } elseif ($vertical) {
            $formatter = $this->output->getFormatter();
            $maxHeaderLength = array_reduce($this->headers[0] ?? [], static fn ($max, $header) => max($max, Helper::width(Helper::removeDecoration($formatter, $header))), 0);

            foreach ($this->rows as $row) {
                if ($row instanceof TableSeparator) {
                    continue;
                }

                if ($rows) {
                    $rows[] = [$divider];
                }

                $containsColspan = false;
                foreach ($row as $cell) {
                    if ($containsColspan = $isCellWithColspan($cell)) {
                        break;
                    }
                }

                $headers = $this->headers[0] ?? [];
                $maxRows = max(\count($headers), \count($row));
                for ($i = 0; $i < $maxRows; ++$i) {
                    $cell = (string) ($row[$i] ?? '');

                    $eol = str_contains($cell, "\r\n") ? "\r\n" : "\n";
                    $parts = explode($eol, $cell);
                    foreach ($parts as $idx => $part) {
                        if ($headers && !$containsColspan) {
                            if (0 === $idx) {
                                $rows[] = [\sprintf(
                                    '<comment>%s%s</>: %s',
                                    str_repeat(' ', $maxHeaderLength - Helper::width(Helper::removeDecoration($formatter, $headers[$i] ?? ''))),
                                    $headers[$i] ?? '',
                                    $part
                                )];
                            } else {
                                $rows[] = [\sprintf(
                                    '%s  %s',
                                    str_pad('', $maxHeaderLength, ' ', \STR_PAD_LEFT),
                                    $part
                                )];
                            }
                        } elseif ('' !== $cell) {
                            $rows[] = [$part];
                        }
                    }
                }
            }
        } else {
            $rows = array_merge($this->headers, [$divider], $this->rows);
        }

        $this->calculateNumberOfColumns($rows);

        $rowGroups = $this->buildTableRows($rows);
        $this->calculateColumnsWidth($rowGroups);

        $isHeader = !$horizontal;
        $isFirstRow = $horizontal;
        $hasTitle = (bool) $this->headerTitle;

        foreach ($rowGroups as $rowGroup) {
            $isHeaderSeparatorRendered = false;

            foreach ($rowGroup as $row) {
                if ($divider === $row) {
                    $isHeader = false;
                    $isFirstRow = true;

                    continue;
                }

                if ($row instanceof TableSeparator) {
                    $this->renderRowSeparator();

                    continue;
                }

                if (!$row) {
                    continue;
                }

                if ($isHeader && !$isHeaderSeparatorRendered) {
                    $this->renderRowSeparator(
                        self::SEPARATOR_TOP,
                        $hasTitle ? $this->headerTitle : null,
                        $hasTitle ? $this->style->getHeaderTitleFormat() : null
                    );
                    $hasTitle = false;
                    $isHeaderSeparatorRendered = true;
                }

                if ($isFirstRow) {
                    $this->renderRowSeparator(
                        $horizontal ? self::SEPARATOR_TOP : self::SEPARATOR_TOP_BOTTOM,
                        $hasTitle ? $this->headerTitle : null,
                        $hasTitle ? $this->style->getHeaderTitleFormat() : null
                    );
                    $isFirstRow = false;
                    $hasTitle = false;
                }

                if ($vertical) {
                    $isHeader = false;
                    $isFirstRow = false;
                }

                if ($horizontal) {
                    $this->renderRow($row, $this->style->getCellRowFormat(), $this->style->getCellHeaderFormat());
                } else {
                    $this->renderRow($row, $isHeader ? $this->style->getCellHeaderFormat() : $this->style->getCellRowFormat());
                }
            }
        }
        $this->renderRowSeparator(self::SEPARATOR_BOTTOM, $this->footerTitle, $this->style->getFooterTitleFormat());

        $this->cleanup();
        $this->rendered = true;
    }

    /**
     * Renders horizontal header separator.
     *
     * Example:
     *
     *     +-----+-----------+-------+
     */
    private function renderRowSeparator(int $type = self::SEPARATOR_MID, ?string $title = null, ?string $titleFormat = null): void
    {
        if (!$count = $this->numberOfColumns) {
            return;
        }

        $borders = $this->style->getBorderChars();
        if (!$borders[0] && !$borders[2] && !$this->style->getCrossingChar()) {
            return;
        }

        $crossings = $this->style->getCrossingChars();
        if (self::SEPARATOR_MID === $type) {
            [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[2], $crossings[8], $crossings[0], $crossings[4]];
        } elseif (self::SEPARATOR_TOP === $type) {
            [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[1], $crossings[2], $crossings[3]];
        } elseif (self::SEPARATOR_TOP_BOTTOM === $type) {
            [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[9], $crossings[10], $crossings[11]];
        } else {
            [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[7], $crossings[6], $crossings[5]];
        }

        $markup = $leftChar;
        for ($column = 0; $column < $count; ++$column) {
            $markup .= str_repeat($horizontal, $this->effectiveColumnWidths[$column]);
            $markup .= $column === $count - 1 ? $rightChar : $midChar;
        }

        if (null !== $title) {
            $titleLength = Helper::width(Helper::removeDecoration($formatter = $this->output->getFormatter(), $formattedTitle = \sprintf($titleFormat, $title)));
            $markupLength = Helper::width($markup);
            if ($titleLength > $limit = $markupLength - 4) {
                $titleLength = $limit;
                $formatLength = Helper::width(Helper::removeDecoration($formatter, \sprintf($titleFormat, '')));
                $formattedTitle = \sprintf($titleFormat, Helper::substr($title, 0, $limit - $formatLength - 3).'...');
            }

            $titleStart = intdiv($markupLength - $titleLength, 2);
            if (false === mb_detect_encoding($markup, null, true)) {
                $markup = substr_replace($markup, $formattedTitle, $titleStart, $titleLength);
            } else {
                $markup = mb_substr($markup, 0, $titleStart).$formattedTitle.mb_substr($markup, $titleStart + $titleLength);
            }
        }

        $this->output->writeln(\sprintf($this->style->getBorderFormat(), $markup));
    }

    /**
     * Renders vertical column separator.
     */
    private function renderColumnSeparator(int $type = self::BORDER_OUTSIDE): string
    {
        $borders = $this->style->getBorderChars();

        return \sprintf($this->style->getBorderFormat(), self::BORDER_OUTSIDE === $type ? $borders[1] : $borders[3]);
    }

    /**
     * Renders table row.
     *
     * Example:
     *
     *     | 9971-5-0210-0 | A Tale of Two Cities  | Charles Dickens  |
     */
    private function renderRow(array $row, string $cellFormat, ?string $firstCellFormat = null): void
    {
        $rowContent = $this->renderColumnSeparator(self::BORDER_OUTSIDE);
        $columns = $this->getRowColumns($row);
        $last = \count($columns) - 1;
        foreach ($columns as $i => $column) {
            if ($firstCellFormat && 0 === $i) {
                $rowContent .= $this->renderCell($row, $column, $firstCellFormat);
            } else {
                $rowContent .= $this->renderCell($row, $column, $cellFormat);
            }
            $rowContent .= $this->renderColumnSeparator($last === $i ? self::BORDER_OUTSIDE : self::BORDER_INSIDE);
        }
        $this->output->writeln($rowContent);
    }

    /**
     * Renders table cell with padding.
     */
    private function renderCell(array $row, int $column, string $cellFormat): string
    {
        $cell = $row[$column] ?? '';
        $width = $this->effectiveColumnWidths[$column];
        if ($cell instanceof TableCell && $cell->getColspan() > 1) {
            // add the width of the following columns(numbers of colspan).
            foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) {
                $width += $this->getColumnSeparatorWidth() + $this->effectiveColumnWidths[$nextColumn];
            }
        }

        // str_pad won't work properly with multi-byte strings, we need to fix the padding
        $width += \strlen($cell) - Helper::width($cell) - substr_count($cell, "\0");
        $style = $this->getColumnStyle($column);

        if ($cell instanceof TableSeparator) {
            return \sprintf($style->getBorderFormat(), str_repeat($style->getBorderChars()[2], $width));
        }

        $width += Helper::length($cell) - Helper::length(Helper::removeDecoration($this->output->getFormatter(), $cell));
        $content = \sprintf($style->getCellRowContentFormat(), $cell);

        $padType = $style->getPadType();
        if ($cell instanceof TableCell && $cell->getStyle() instanceof TableCellStyle) {
            $isNotStyledByTag = !preg_match('/^<(\w+|(\w+=[\w,]+;?)*)>.+<\/(\w+|(\w+=\w+;?)*)?>$/', $cell);
            if ($isNotStyledByTag) {
                $cellFormat = $cell->getStyle()->getCellFormat();
                if (!\is_string($cellFormat)) {
                    $tag = http_build_query($cell->getStyle()->getTagOptions(), '', ';');
                    $cellFormat = '<'.$tag.'>%s</>';
                }

                if (str_contains($content, '</>')) {
                    $content = str_replace('</>', '', $content);
                    $width -= 3;
                }
                if (str_contains($content, '<fg=default;bg=default>')) {
                    $content = str_replace('<fg=default;bg=default>', '', $content);
                    $width -= \strlen('<fg=default;bg=default>');
                }
            }

            $padType = $cell->getStyle()->getPadByAlign();
        }

        return \sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $padType));
    }

    /**
     * Calculate number of columns for this table.
     */
    private function calculateNumberOfColumns(array $rows): void
    {
        $columns = [0];
        foreach ($rows as $row) {
            if ($row instanceof TableSeparator) {
                continue;
            }

            $columns[] = $this->getNumberOfColumns($row);
        }

        $this->numberOfColumns = max($columns);
    }

    private function buildTableRows(array $rows): TableRows
    {
        /** @var WrappableOutputFormatterInterface $formatter */
        $formatter = $this->output->getFormatter();
        $unmergedRows = [];
        for ($rowKey = 0; $rowKey < \count($rows); ++$rowKey) {
            $rows = $this->fillNextRows($rows, $rowKey);

            // Remove any new line breaks and replace it with a new line
            foreach ($rows[$rowKey] as $column => $cell) {
                $colspan = $cell instanceof TableCell ? $cell->getColspan() : 1;

                $minWrappedWidth = 0;
                $widthApplied = [];
                $lengthColumnBorder = $this->getColumnSeparatorWidth() + Helper::width($this->style->getCellRowContentFormat()) - 2;
                for ($i = $column; $i < ($column + $colspan); ++$i) {
                    if (isset($this->columnMaxWidths[$i])) {
                        $minWrappedWidth += $this->columnMaxWidths[$i];
                        $widthApplied[] = ['type' => 'max', 'column' => $i];
                    } elseif (($this->columnWidths[$i] ?? 0) > 0 && $colspan > 1) {
                        $minWrappedWidth += $this->columnWidths[$i];
                        $widthApplied[] = ['type' => 'min', 'column' => $i];
                    }
                }
                if (1 === \count($widthApplied)) {
                    if ($colspan > 1) {
                        $minWrappedWidth *= $colspan;  // previous logic
                    }
                } elseif (\count($widthApplied) > 1) {
                    $minWrappedWidth += (\count($widthApplied) - 1) * $lengthColumnBorder;
                }

                $cellWidth = Helper::width(Helper::removeDecoration($formatter, $cell));
                if ($minWrappedWidth && $cellWidth > $minWrappedWidth) {
                    $cell = $formatter->formatAndWrap($cell, $minWrappedWidth);
                }
                // update minimal columnWidths for spanned columns
                if ($colspan > 1 && $minWrappedWidth > 0) {
                    $columnsMinWidthProcessed = [];
                    $cellWidth = min($cellWidth, $minWrappedWidth);
                    foreach ($widthApplied as $item) {
                        if ('max' === $item['type'] && $cellWidth >= $this->columnMaxWidths[$item['column']]) {
                            $minWidthColumn = $this->columnMaxWidths[$item['column']];
                            $this->columnWidths[$item['column']] = $minWidthColumn;
                            $columnsMinWidthProcessed[$item['column']] = true;
                            $cellWidth -= $minWidthColumn + $lengthColumnBorder;
                        }
                    }
                    for ($i = $column; $i < ($column + $colspan); ++$i) {
                        if (isset($columnsMinWidthProcessed[$i])) {
                            continue;
                        }
                        $this->columnWidths[$i] = $cellWidth + $lengthColumnBorder;
                    }
                }
                if (!str_contains($cell ?? '', "\n")) {
                    continue;
                }
                $eol = str_contains($cell ?? '', "\r\n") ? "\r\n" : "\n";
                $escaped = implode($eol, array_map(OutputFormatter::escapeTrailingBackslash(...), explode($eol, $cell)));
                $cell = $cell instanceof TableCell ? new TableCell($escaped, ['colspan' => $cell->getColspan()]) : $escaped;
                $lines = explode($eol, str_replace($eol, '<fg=default;bg=default></>'.$eol, $cell));
                foreach ($lines as $lineKey => $line) {
                    if ($colspan > 1) {
                        $line = new TableCell($line, ['colspan' => $colspan]);
                    }
                    if (0 === $lineKey) {
                        $rows[$rowKey][$column] = $line;
                    } else {
                        if (!\array_key_exists($rowKey, $unmergedRows) || !\array_key_exists($lineKey, $unmergedRows[$rowKey])) {
                            $unmergedRows[$rowKey][$lineKey] = $this->copyRow($rows, $rowKey);
                        }
                        $unmergedRows[$rowKey][$lineKey][$column] = $line;
                    }
                }
            }
        }

        return new TableRows(function () use ($rows, $unmergedRows): \Traversable {
            foreach ($rows as $rowKey => $row) {
                $rowGroup = [$row instanceof TableSeparator ? $row : $this->fillCells($row)];

                if (isset($unmergedRows[$rowKey])) {
                    foreach ($unmergedRows[$rowKey] as $row) {
                        $rowGroup[] = $row instanceof TableSeparator ? $row : $this->fillCells($row);
                    }
                }
                yield $rowGroup;
            }
        });
    }

    private function calculateRowCount(): int
    {
        $numberOfRows = \count(iterator_to_array($this->buildTableRows(array_merge($this->headers, [new TableSeparator()], $this->rows))));

        if ($this->headers) {
            ++$numberOfRows; // Add row for header separator
        }

        if ($this->rows) {
            ++$numberOfRows; // Add row for footer separator
        }

        return $numberOfRows;
    }

    /**
     * fill rows that contains rowspan > 1.
     *
     * @throws InvalidArgumentException
     */
    private function fillNextRows(array $rows, int $line): array
    {
        $unmergedRows = [];
        foreach ($rows[$line] as $column => $cell) {
            if (null !== $cell && !$cell instanceof TableCell && !\is_scalar($cell) && !$cell instanceof \Stringable) {
                throw new InvalidArgumentException(\sprintf('A cell must be a TableCell, a scalar or an object implementing "__toString()", "%s" given.', get_debug_type($cell)));
            }
            if ($cell instanceof TableCell && $cell->getRowspan() > 1) {
                $nbLines = $cell->getRowspan() - 1;
                $lines = [$cell];
                if (str_contains($cell, "\n")) {
                    $eol = str_contains($cell, "\r\n") ? "\r\n" : "\n";
                    $lines = explode($eol, str_replace($eol, '<fg=default;bg=default>'.$eol.'</>', $cell));
                    $nbLines = \count($lines) > $nbLines ? substr_count($cell, $eol) : $nbLines;

                    $rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]);
                    unset($lines[0]);
                }

                // create a two dimensional array (rowspan x colspan)
                $unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, []), $unmergedRows);
                foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
                    $value = $lines[$unmergedRowKey - $line] ?? '';
                    $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]);
                    if ($nbLines === $unmergedRowKey - $line) {
                        break;
                    }
                }
            }
        }

        foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
            // we need to know if $unmergedRow will be merged or inserted into $rows
            if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) {
                foreach ($unmergedRow as $cellKey => $cell) {
                    // insert cell into row at cellKey position
                    array_splice($rows[$unmergedRowKey], $cellKey, 0, [$cell]);
                }
            } else {
                $row = $this->copyRow($rows, $unmergedRowKey - 1);
                foreach ($unmergedRow as $column => $cell) {
                    if (!empty($cell)) {
                        $row[$column] = $unmergedRow[$column];
                    }
                }
                array_splice($rows, $unmergedRowKey, 0, [$row]);
            }
        }

        return $rows;
    }

    /**
     * fill cells for a row that contains colspan > 1.
     */
    private function fillCells(iterable $row): iterable
    {
        $newRow = [];

        foreach ($row as $column => $cell) {
            $newRow[] = $cell;
            if ($cell instanceof TableCell && $cell->getColspan() > 1) {
                foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) {
                    // insert empty value at column position
                    $newRow[] = '';
                }
            }
        }

        return $newRow ?: $row;
    }

    private function copyRow(array $rows, int $line): array
    {
        $row = $rows[$line];
        foreach ($row as $cellKey => $cellValue) {
            $row[$cellKey] = '';
            if ($cellValue instanceof TableCell) {
                $row[$cellKey] = new TableCell('', ['colspan' => $cellValue->getColspan()]);
            }
        }

        return $row;
    }

    /**
     * Gets number of columns by row.
     */
    private function getNumberOfColumns(array $row): int
    {
        $columns = \count($row);
        foreach ($row as $column) {
            $columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0;
        }

        return $columns;
    }

    /**
     * Gets list of columns for the given row.
     */
    private function getRowColumns(array $row): array
    {
        $columns = range(0, $this->numberOfColumns - 1);
        foreach ($row as $cellKey => $cell) {
            if ($cell instanceof TableCell && $cell->getColspan() > 1) {
                // exclude grouped columns.
                $columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1));
            }
        }

        return $columns;
    }

    /**
     * Calculates columns widths.
     */
    private function calculateColumnsWidth(iterable $groups): void
    {
        for ($column = 0; $column < $this->numberOfColumns; ++$column) {
            $lengths = [];
            foreach ($groups as $group) {
                foreach ($group as $row) {
                    if ($row instanceof TableSeparator) {
                        continue;
                    }

                    foreach ($row as $i => $cell) {
                        if ($cell instanceof TableCell) {
                            $textContent = Helper::removeDecoration($this->output->getFormatter(), $cell);
                            $textLength = Helper::width($textContent);
                            if ($textLength > 0) {
                                $contentColumns = mb_str_split($textContent, ceil($textLength / $cell->getColspan()));
                                foreach ($contentColumns as $position => $content) {
                                    $row[$i + $position] = $content;
                                }
                            }
                        }
                    }

                    $lengths[] = $this->getCellWidth($row, $column);
                }
            }

            $this->effectiveColumnWidths[$column] = max($lengths) + Helper::width($this->style->getCellRowContentFormat()) - 2;
        }
    }

    private function getColumnSeparatorWidth(): int
    {
        return Helper::width(\sprintf($this->style->getBorderFormat(), $this->style->getBorderChars()[3]));
    }

    private function getCellWidth(array $row, int $column): int
    {
        $cellWidth = 0;

        if (isset($row[$column])) {
            $cell = $row[$column];
            $cellWidth = Helper::width(Helper::removeDecoration($this->output->getFormatter(), $cell));
        }

        $columnWidth = $this->columnWidths[$column] ?? 0;
        $cellWidth = max($cellWidth, $columnWidth);

        return isset($this->columnMaxWidths[$column]) ? min($this->columnMaxWidths[$column], $cellWidth) : $cellWidth;
    }

    /**
     * Called after rendering to cleanup cache data.
     */
    private function cleanup(): void
    {
        $this->effectiveColumnWidths = [];
        unset($this->numberOfColumns);
    }

    /**
     * @return array<string, TableStyle>
     */
    private static function initStyles(): array
    {
        $borderless = new TableStyle();
        $borderless
            ->setHorizontalBorderChars('=')
            ->setVerticalBorderChars(' ')
            ->setDefaultCrossingChar(' ')
        ;

        $compact = new TableStyle();
        $compact
            ->setHorizontalBorderChars('')
            ->setVerticalBorderChars('')
            ->setDefaultCrossingChar('')
            ->setCellRowContentFormat('%s ')
        ;

        $styleGuide = new TableStyle();
        $styleGuide
            ->setHorizontalBorderChars('-')
            ->setVerticalBorderChars(' ')
            ->setDefaultCrossingChar(' ')
            ->setCellHeaderFormat('%s')
        ;

        $box = (new TableStyle())
            ->setHorizontalBorderChars('─')
            ->setVerticalBorderChars('│')
            ->setCrossingChars('┼', '┌', '┬', '┐', '┤', '┘', '┴', '└', '├')
        ;

        $boxDouble = (new TableStyle())
            ->setHorizontalBorderChars('═', '─')
            ->setVerticalBorderChars('║', '│')
            ->setCrossingChars('┼', '╔', '╤', '╗', '╢', '╝', '╧', '╚', '╟', '╠', '╪', '╣')
        ;

        return [
            'default' => new TableStyle(),
            'borderless' => $borderless,
            'compact' => $compact,
            'symfony-style-guide' => $styleGuide,
            'box' => $box,
            'box-double' => $boxDouble,
        ];
    }

    private function resolveStyle(TableStyle|string $name): TableStyle
    {
        if ($name instanceof TableStyle) {
            return $name;
        }

        return self::$styles[$name] ?? throw new InvalidArgumentException(\sprintf('Style "%s" is not defined.', $name));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Input\InputAwareInterface;
use Symfony\Component\Console\Input\InputInterface;

/**
 * An implementation of InputAwareInterface for Helpers.
 *
 * @author Wouter J <waldio.webdesign@gmail.com>
 */
abstract class InputAwareHelper extends Helper implements InputAwareInterface
{
    protected $input;

    /**
     * @return void
     */
    public function setInput(InputInterface $input)
    {
        $this->input = $input;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * @author Yewhen Khoptynskyi <khoptynskyi@gmail.com>
 */
class TableCellStyle
{
    public const DEFAULT_ALIGN = 'left';

    private const TAG_OPTIONS = [
        'fg',
        'bg',
        'options',
    ];

    private const ALIGN_MAP = [
        'left' => \STR_PAD_RIGHT,
        'center' => \STR_PAD_BOTH,
        'right' => \STR_PAD_LEFT,
    ];

    private array $options = [
        'fg' => 'default',
        'bg' => 'default',
        'options' => null,
        'align' => self::DEFAULT_ALIGN,
        'cellFormat' => null,
    ];

    public function __construct(array $options = [])
    {
        if ($diff = array_diff(array_keys($options), array_keys($this->options))) {
            throw new InvalidArgumentException(\sprintf('The TableCellStyle does not support the following options: \'%s\'.', implode('\', \'', $diff)));
        }

        if (isset($options['align']) && !\array_key_exists($options['align'], self::ALIGN_MAP)) {
            throw new InvalidArgumentException(\sprintf('Wrong align value. Value must be following: \'%s\'.', implode('\', \'', array_keys(self::ALIGN_MAP))));
        }

        $this->options = array_merge($this->options, $options);
    }

    public function getOptions(): array
    {
        return $this->options;
    }

    /**
     * Gets options we need for tag for example fg, bg.
     *
     * @return string[]
     */
    public function getTagOptions(): array
    {
        return array_filter(
            $this->getOptions(),
            fn ($key) => \in_array($key, self::TAG_OPTIONS) && isset($this->options[$key]),
            \ARRAY_FILTER_USE_KEY
        );
    }

    public function getPadByAlign(): int
    {
        return self::ALIGN_MAP[$this->getOptions()['align']];
    }

    public function getCellFormat(): ?string
    {
        return $this->getOptions()['cellFormat'];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

/**
 * Helps outputting debug information when running an external program from a command.
 *
 * An external program can be a Process, an HTTP request, or anything else.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class DebugFormatterHelper extends Helper
{
    private const COLORS = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default'];
    private array $started = [];
    private int $count = -1;

    /**
     * Starts a debug formatting session.
     */
    public function start(string $id, string $message, string $prefix = 'RUN'): string
    {
        $this->started[$id] = ['border' => ++$this->count % \count(self::COLORS)];

        return \sprintf("%s<bg=blue;fg=white> %s </> <fg=blue>%s</>\n", $this->getBorder($id), $prefix, $message);
    }

    /**
     * Adds progress to a formatting session.
     */
    public function progress(string $id, string $buffer, bool $error = false, string $prefix = 'OUT', string $errorPrefix = 'ERR'): string
    {
        $message = '';

        if ($error) {
            if (isset($this->started[$id]['out'])) {
                $message .= "\n";
                unset($this->started[$id]['out']);
            }
            if (!isset($this->started[$id]['err'])) {
                $message .= \sprintf('%s<bg=red;fg=white> %s </> ', $this->getBorder($id), $errorPrefix);
                $this->started[$id]['err'] = true;
            }

            $message .= str_replace("\n", \sprintf("\n%s<bg=red;fg=white> %s </> ", $this->getBorder($id), $errorPrefix), $buffer);
        } else {
            if (isset($this->started[$id]['err'])) {
                $message .= "\n";
                unset($this->started[$id]['err']);
            }
            if (!isset($this->started[$id]['out'])) {
                $message .= \sprintf('%s<bg=green;fg=white> %s </> ', $this->getBorder($id), $prefix);
                $this->started[$id]['out'] = true;
            }

            $message .= str_replace("\n", \sprintf("\n%s<bg=green;fg=white> %s </> ", $this->getBorder($id), $prefix), $buffer);
        }

        return $message;
    }

    /**
     * Stops a formatting session.
     */
    public function stop(string $id, string $message, bool $successful, string $prefix = 'RES'): string
    {
        $trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : '';

        if ($successful) {
            return \sprintf("%s%s<bg=green;fg=white> %s </> <fg=green>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
        }

        $message = \sprintf("%s%s<bg=red;fg=white> %s </> <fg=red>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);

        unset($this->started[$id]['out'], $this->started[$id]['err']);

        return $message;
    }

    private function getBorder(string $id): string
    {
        return \sprintf('<bg=%s> </>', self::COLORS[$this->started[$id]['border']]);
    }

    public function getName(): string
    {
        return 'debug_formatter';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Kevin Bond <kevinbond@gmail.com>
 */
class ProgressIndicator
{
    private const FORMATS = [
        'normal' => ' %indicator% %message%',
        'normal_no_ansi' => ' %message%',

        'verbose' => ' %indicator% %message% (%elapsed:6s%)',
        'verbose_no_ansi' => ' %message% (%elapsed:6s%)',

        'very_verbose' => ' %indicator% %message% (%elapsed:6s%, %memory:6s%)',
        'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)',
    ];

    private OutputInterface $output;
    private int $startTime;
    private ?string $format = null;
    private ?string $message = null;
    private array $indicatorValues;
    private int $indicatorCurrent;
    private int $indicatorChangeInterval;
    private float $indicatorUpdateTime;
    private bool $started = false;

    /**
     * @var array<string, callable>
     */
    private static array $formatters;

    /**
     * @param int        $indicatorChangeInterval Change interval in milliseconds
     * @param array|null $indicatorValues         Animated indicator characters
     */
    public function __construct(OutputInterface $output, ?string $format = null, int $indicatorChangeInterval = 100, ?array $indicatorValues = null)
    {
        $this->output = $output;

        $format ??= $this->determineBestFormat();
        $indicatorValues ??= ['-', '\\', '|', '/'];
        $indicatorValues = array_values($indicatorValues);

        if (2 > \count($indicatorValues)) {
            throw new InvalidArgumentException('Must have at least 2 indicator value characters.');
        }

        $this->format = self::getFormatDefinition($format);
        $this->indicatorChangeInterval = $indicatorChangeInterval;
        $this->indicatorValues = $indicatorValues;
        $this->startTime = time();
    }

    /**
     * Sets the current indicator message.
     *
     * @return void
     */
    public function setMessage(?string $message)
    {
        $this->message = $message;

        $this->display();
    }

    /**
     * Starts the indicator output.
     *
     * @return void
     */
    public function start(string $message)
    {
        if ($this->started) {
            throw new LogicException('Progress indicator already started.');
        }

        $this->message = $message;
        $this->started = true;
        $this->startTime = time();
        $this->indicatorUpdateTime = $this->getCurrentTimeInMilliseconds() + $this->indicatorChangeInterval;
        $this->indicatorCurrent = 0;

        $this->display();
    }

    /**
     * Advances the indicator.
     *
     * @return void
     */
    public function advance()
    {
        if (!$this->started) {
            throw new LogicException('Progress indicator has not yet been started.');
        }

        if (!$this->output->isDecorated()) {
            return;
        }

        $currentTime = $this->getCurrentTimeInMilliseconds();

        if ($currentTime < $this->indicatorUpdateTime) {
            return;
        }

        $this->indicatorUpdateTime = $currentTime + $this->indicatorChangeInterval;
        ++$this->indicatorCurrent;

        $this->display();
    }

    /**
     * Finish the indicator with message.
     *
     * @return void
     */
    public function finish(string $message)
    {
        if (!$this->started) {
            throw new LogicException('Progress indicator has not yet been started.');
        }

        $this->message = $message;
        $this->display();
        if (!$this->output instanceof ConsoleSectionOutput) {
            $this->output->writeln('');
        }
        $this->started = false;
    }

    /**
     * Gets the format for a given name.
     */
    public static function getFormatDefinition(string $name): ?string
    {
        return self::FORMATS[$name] ?? null;
    }

    /**
     * Sets a placeholder formatter for a given name.
     *
     * This method also allow you to override an existing placeholder.
     *
     * @return void
     */
    public static function setPlaceholderFormatterDefinition(string $name, callable $callable)
    {
        self::$formatters ??= self::initPlaceholderFormatters();

        self::$formatters[$name] = $callable;
    }

    /**
     * Gets the placeholder formatter for a given name (including the delimiter char like %).
     */
    public static function getPlaceholderFormatterDefinition(string $name): ?callable
    {
        self::$formatters ??= self::initPlaceholderFormatters();

        return self::$formatters[$name] ?? null;
    }

    private function display(): void
    {
        if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
            return;
        }

        $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) {
            if ($formatter = self::getPlaceholderFormatterDefinition($matches[1])) {
                return $formatter($this);
            }

            return $matches[0];
        }, $this->format ?? ''));
    }

    private function determineBestFormat(): string
    {
        return match ($this->output->getVerbosity()) {
            // OutputInterface::VERBOSITY_QUIET: display is disabled anyway
            OutputInterface::VERBOSITY_VERBOSE => $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi',
            OutputInterface::VERBOSITY_VERY_VERBOSE,
            OutputInterface::VERBOSITY_DEBUG => $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi',
            default => $this->output->isDecorated() ? 'normal' : 'normal_no_ansi',
        };
    }

    /**
     * Overwrites a previous message to the output.
     */
    private function overwrite(string $message): void
    {
        if ($this->output instanceof ConsoleSectionOutput) {
            $this->output->overwrite($message);
        } elseif ($this->output->isDecorated()) {
            $this->output->write("\x0D\x1B[2K");
            $this->output->write($message);
        } else {
            $this->output->writeln($message);
        }
    }

    private function getCurrentTimeInMilliseconds(): float
    {
        return round(microtime(true) * 1000);
    }

    /**
     * @return array<string, \Closure>
     */
    private static function initPlaceholderFormatters(): array
    {
        return [
            'indicator' => static fn (self $indicator) => $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)],
            'message' => static fn (self $indicator) => $indicator->message,
            'elapsed' => static fn (self $indicator) => Helper::formatTime(time() - $indicator->startTime, 2),
            'memory' => static fn () => Helper::formatMemory(memory_get_usage(true)),
        ];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

/**
 * TerminalInputHelper stops Ctrl-C and similar signals from leaving the terminal in
 * an unusable state if its settings have been modified when reading user input.
 * This can be an issue on non-Windows platforms.
 *
 * Usage:
 *
 *     $inputHelper = new TerminalInputHelper($inputStream);
 *
 *     ...change terminal settings
 *
 *     // Wait for input before all input reads
 *     $inputHelper->waitForInput();
 *
 *     ...read input
 *
 *     // Call finish to restore terminal settings and signal handlers
 *     $inputHelper->finish()
 *
 * @internal
 */
final class TerminalInputHelper
{
    /** @var resource */
    private $inputStream;
    private bool $isStdin;
    private string $initialState = '';
    private int $signalToKill = 0;
    private array $signalHandlers = [];
    private array $targetSignals = [];
    private bool $withStty;

    /**
     * @param resource $inputStream
     *
     * @throws \RuntimeException If unable to read terminal settings
     */
    public function __construct($inputStream, bool $withStty = true)
    {
        $this->inputStream = $inputStream;
        $this->isStdin = 'php://stdin' === stream_get_meta_data($inputStream)['uri'];
        $this->withStty = $withStty;

        if ($withStty) {
            if (!\is_string($state = shell_exec('stty -g'))) {
                throw new \RuntimeException('Unable to read the terminal settings.');
            }

            $this->initialState = $state;

            $this->createSignalHandlers();
        }
    }

    /**
     * Waits for input.
     */
    public function waitForInput(): void
    {
        if ($this->isStdin) {
            $r = [$this->inputStream];
            $w = [];

            // Allow signal handlers to run
            while (0 === @stream_select($r, $w, $w, 0, 100)) {
                $r = [$this->inputStream];
            }
        }

        if ($this->withStty) {
            $this->checkForKillSignal();
        }
    }

    /**
     * Restores terminal state and signal handlers.
     */
    public function finish(): void
    {
        if (!$this->withStty) {
            return;
        }

        // Safeguard in case an unhandled kill signal exists
        $this->checkForKillSignal();
        shell_exec('stty '.$this->initialState);
        $this->signalToKill = 0;

        foreach ($this->signalHandlers as $signal => $originalHandler) {
            pcntl_signal($signal, $originalHandler);
        }
        $this->signalHandlers = [];
        $this->targetSignals = [];
    }

    private function createSignalHandlers(): void
    {
        if (!\function_exists('pcntl_async_signals') || !\function_exists('pcntl_signal')) {
            return;
        }

        pcntl_async_signals(true);
        $this->targetSignals = [\SIGINT, \SIGQUIT, \SIGTERM];

        foreach ($this->targetSignals as $signal) {
            $this->signalHandlers[$signal] = pcntl_signal_get_handler($signal);

            pcntl_signal($signal, function ($signal) {
                // Save current state, then restore to initial state
                $currentState = shell_exec('stty -g');
                shell_exec('stty '.$this->initialState);
                $originalHandler = $this->signalHandlers[$signal];

                if (\is_callable($originalHandler)) {
                    $originalHandler($signal);
                    // Handler did not exit, so restore to current state
                    shell_exec('stty '.$currentState);

                    return;
                }

                // Not a callable, so SIG_DFL or SIG_IGN
                if (\SIG_DFL === $originalHandler) {
                    $this->signalToKill = $signal;
                }
            });
        }
    }

    private function checkForKillSignal(): void
    {
        if (\in_array($this->signalToKill, $this->targetSignals, true)) {
            // Try posix_kill
            if (\function_exists('posix_kill')) {
                pcntl_signal($this->signalToKill, \SIG_DFL);
                posix_kill(getmypid(), $this->signalToKill);
            }

            // Best attempt fallback
            exit(128 + $this->signalToKill);
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Cursor;
use Symfony\Component\Console\Exception\MissingInputException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\StreamableInputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Terminal;

use function Symfony\Component\String\s;

/**
 * The QuestionHelper class provides helpers to interact with the user.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class QuestionHelper extends Helper
{
    /**
     * @var resource|null
     */
    private $inputStream;

    private static bool $stty = true;
    private static bool $stdinIsInteractive;

    /**
     * Asks a question to the user.
     *
     * @return mixed The user answer
     *
     * @throws RuntimeException If there is no data to read in the input stream
     */
    public function ask(InputInterface $input, OutputInterface $output, Question $question): mixed
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        if (!$input->isInteractive()) {
            return $this->getDefaultAnswer($question);
        }

        if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) {
            $this->inputStream = $stream;
        }

        try {
            if (!$question->getValidator()) {
                return $this->doAsk($output, $question);
            }

            $interviewer = fn () => $this->doAsk($output, $question);

            return $this->validateAttempts($interviewer, $output, $question);
        } catch (MissingInputException $exception) {
            $input->setInteractive(false);

            if (null === $fallbackOutput = $this->getDefaultAnswer($question)) {
                throw $exception;
            }

            return $fallbackOutput;
        }
    }

    public function getName(): string
    {
        return 'question';
    }

    /**
     * Prevents usage of stty.
     *
     * @return void
     */
    public static function disableStty()
    {
        self::$stty = false;
    }

    /**
     * Asks the question to the user.
     *
     * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
     */
    private function doAsk(OutputInterface $output, Question $question): mixed
    {
        $this->writePrompt($output, $question);

        $inputStream = $this->inputStream ?: \STDIN;
        $autocomplete = $question->getAutocompleterCallback();

        if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) {
            $ret = false;
            if ($question->isHidden()) {
                try {
                    $hiddenResponse = $this->getHiddenResponse($output, $inputStream, $question->isTrimmable());
                    $ret = $question->isTrimmable() ? trim($hiddenResponse) : $hiddenResponse;
                } catch (RuntimeException $e) {
                    if (!$question->isHiddenFallback()) {
                        throw $e;
                    }
                }
            }

            if (false === $ret) {
                $isBlocked = stream_get_meta_data($inputStream)['blocked'] ?? true;

                if (!$isBlocked) {
                    stream_set_blocking($inputStream, true);
                }

                $ret = $this->readInput($inputStream, $question);

                if (!$isBlocked) {
                    stream_set_blocking($inputStream, false);
                }

                if (false === $ret) {
                    throw new MissingInputException('Aborted.');
                }
                if ($question->isTrimmable()) {
                    $ret = trim($ret);
                }
            }
        } else {
            $autocomplete = $this->autocomplete($output, $question, $inputStream, $autocomplete);
            $ret = $question->isTrimmable() ? trim($autocomplete) : $autocomplete;
        }

        if ($output instanceof ConsoleSectionOutput) {
            $output->addContent(''); // add EOL to the question
            $output->addContent($ret);
        }

        $ret = \strlen($ret) > 0 ? $ret : $question->getDefault();

        if ($normalizer = $question->getNormalizer()) {
            return $normalizer($ret);
        }

        return $ret;
    }

    private function getDefaultAnswer(Question $question): mixed
    {
        $default = $question->getDefault();

        if (null === $default) {
            return $default;
        }

        if ($validator = $question->getValidator()) {
            return \call_user_func($validator, $default);
        } elseif ($question instanceof ChoiceQuestion) {
            $choices = $question->getChoices();

            if (!$question->isMultiselect()) {
                return $choices[$default] ?? $default;
            }

            $default = explode(',', $default);
            foreach ($default as $k => $v) {
                $v = $question->isTrimmable() ? trim($v) : $v;
                $default[$k] = $choices[$v] ?? $v;
            }
        }

        return $default;
    }

    /**
     * Outputs the question prompt.
     *
     * @return void
     */
    protected function writePrompt(OutputInterface $output, Question $question)
    {
        $message = $question->getQuestion();

        if ($question instanceof ChoiceQuestion) {
            $output->writeln(array_merge([
                $question->getQuestion(),
            ], $this->formatChoiceQuestionChoices($question, 'info')));

            $message = $question->getPrompt();
        }

        $output->write($message);
    }

    /**
     * @return string[]
     */
    protected function formatChoiceQuestionChoices(ChoiceQuestion $question, string $tag): array
    {
        $messages = [];

        $maxWidth = max(array_map([__CLASS__, 'width'], array_keys($choices = $question->getChoices())));

        foreach ($choices as $key => $value) {
            $padding = str_repeat(' ', $maxWidth - self::width($key));

            $messages[] = \sprintf("  [<$tag>%s$padding</$tag>] %s", $key, $value);
        }

        return $messages;
    }

    /**
     * Outputs an error message.
     *
     * @return void
     */
    protected function writeError(OutputInterface $output, \Exception $error)
    {
        if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) {
            $message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error');
        } else {
            $message = '<error>'.$error->getMessage().'</error>';
        }

        $output->writeln($message);
    }

    /**
     * Autocompletes a question.
     *
     * @param resource $inputStream
     */
    private function autocomplete(OutputInterface $output, Question $question, $inputStream, callable $autocomplete): string
    {
        $cursor = new Cursor($output, $inputStream);

        $fullChoice = '';
        $ret = '';

        $i = 0;
        $ofs = -1;
        $matches = $autocomplete($ret);
        $numMatches = \count($matches);
        $inputHelper = new TerminalInputHelper($inputStream);

        // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead)
        shell_exec('stty -icanon -echo');

        // Add highlighted text style
        $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white'));

        // Read a keypress
        while (!feof($inputStream)) {
            $inputHelper->waitForInput();
            $c = fread($inputStream, 1);

            // as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false.
            if (false === $c || ('' === $ret && '' === $c && null === $question->getDefault())) {
                // Restore the terminal so it behaves normally again
                $inputHelper->finish();
                throw new MissingInputException('Aborted.');
            } elseif ("\177" === $c) { // Backspace Character
                if (0 === $numMatches && 0 !== $i) {
                    --$i;
                    $cursor->moveLeft(s($fullChoice)->slice(-1)->width(false));

                    $fullChoice = self::substr($fullChoice, 0, $i);
                }

                if (0 === $i) {
                    $ofs = -1;
                    $matches = $autocomplete($ret);
                    $numMatches = \count($matches);
                } else {
                    $numMatches = 0;
                }

                // Pop the last character off the end of our string
                $ret = self::substr($ret, 0, $i);
            } elseif ("\033" === $c) {
                // Did we read an escape sequence?
                $c .= fread($inputStream, 2);

                // A = Up Arrow. B = Down Arrow
                if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) {
                    if ('A' === $c[2] && -1 === $ofs) {
                        $ofs = 0;
                    }

                    if (0 === $numMatches) {
                        continue;
                    }

                    $ofs += ('A' === $c[2]) ? -1 : 1;
                    $ofs = ($numMatches + $ofs) % $numMatches;
                }
            } elseif ('' === $c || \ord($c) < 32) {
                if ("\t" === $c || "\n" === $c) {
                    if ($numMatches > 0 && -1 !== $ofs) {
                        $ret = (string) $matches[$ofs];
                        // Echo out remaining chars for current match
                        $remainingCharacters = substr($ret, \strlen($this->mostRecentlyEnteredValue($fullChoice)));
                        $output->write($remainingCharacters);
                        $fullChoice .= $remainingCharacters;
                        $i = (false === $encoding = mb_detect_encoding($fullChoice, null, true)) ? \strlen($fullChoice) : mb_strlen($fullChoice, $encoding);

                        $matches = array_filter(
                            $autocomplete($ret),
                            static fn ($match) => '' === $ret || str_starts_with($match, $ret)
                        );
                        $numMatches = \count($matches);
                        $ofs = -1;
                    }

                    if ("\n" === $c) {
                        $output->write($c);
                        break;
                    }

                    $numMatches = 0;
                }

                continue;
            } else {
                if ("\x80" <= $c) {
                    $c .= fread($inputStream, ["\xC0" => 1, "\xD0" => 1, "\xE0" => 2, "\xF0" => 3][$c & "\xF0"]);
                }

                $output->write($c);
                $ret .= $c;
                $fullChoice .= $c;
                ++$i;

                $tempRet = $ret;

                if ($question instanceof ChoiceQuestion && $question->isMultiselect()) {
                    $tempRet = $this->mostRecentlyEnteredValue($fullChoice);
                }

                $numMatches = 0;
                $ofs = 0;

                foreach ($autocomplete($ret) as $value) {
                    // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
                    if (str_starts_with($value, $tempRet)) {
                        $matches[$numMatches++] = $value;
                    }
                }
            }

            $cursor->clearLineAfter();

            if ($numMatches > 0 && -1 !== $ofs) {
                $cursor->savePosition();
                // Write highlighted text, complete the partially entered response
                $charactersEntered = \strlen($this->mostRecentlyEnteredValue($fullChoice));
                $output->write('<hl>'.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $charactersEntered)).'</hl>');
                $cursor->restorePosition();
            }
        }

        // Restore the terminal so it behaves normally again
        $inputHelper->finish();

        return $fullChoice;
    }

    private function mostRecentlyEnteredValue(string $entered): string
    {
        // Determine the most recent value that the user entered
        if (!str_contains($entered, ',')) {
            return $entered;
        }

        $choices = explode(',', $entered);
        if ('' !== $lastChoice = trim($choices[\count($choices) - 1])) {
            return $lastChoice;
        }

        return $entered;
    }

    /**
     * Gets a hidden response from user.
     *
     * @param resource $inputStream The handler resource
     * @param bool     $trimmable   Is the answer trimmable
     *
     * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
     */
    private function getHiddenResponse(OutputInterface $output, $inputStream, bool $trimmable = true): string
    {
        if ('\\' === \DIRECTORY_SEPARATOR) {
            $exe = __DIR__.'/../Resources/bin/hiddeninput.exe';

            // handle code running from a phar
            if (str_starts_with(__FILE__, 'phar:')) {
                $tmpExe = sys_get_temp_dir().'/hiddeninput.exe';
                copy($exe, $tmpExe);
                $exe = $tmpExe;
            }

            $sExec = shell_exec('"'.$exe.'"');
            $value = $trimmable ? rtrim($sExec) : $sExec;
            $output->writeln('');

            if (isset($tmpExe)) {
                unlink($tmpExe);
            }

            return $value;
        }

        $inputHelper = null;

        if (self::$stty && Terminal::hasSttyAvailable()) {
            $inputHelper = new TerminalInputHelper($inputStream);
            shell_exec('stty -echo');
        } elseif ($this->isInteractiveInput($inputStream)) {
            throw new RuntimeException('Unable to hide the response.');
        }

        $value = $this->doReadInput($inputStream, helper: $inputHelper);

        if (4095 === \strlen($value)) {
            $errOutput = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output;
            $errOutput->warning('The value was possibly truncated by your shell or terminal emulator');
        }

        // Restore the terminal so it behaves normally again
        $inputHelper?->finish();

        if ($trimmable) {
            $value = trim($value);
        }
        $output->writeln('');

        return $value;
    }

    /**
     * Validates an attempt.
     *
     * @param callable $interviewer A callable that will ask for a question and return the result
     *
     * @throws \Exception In case the max number of attempts has been reached and no valid response has been given
     */
    private function validateAttempts(callable $interviewer, OutputInterface $output, Question $question): mixed
    {
        $error = null;
        $attempts = $question->getMaxAttempts();

        while (null === $attempts || $attempts--) {
            if (null !== $error) {
                $this->writeError($output, $error);
            }

            try {
                return $question->getValidator()($interviewer());
            } catch (MissingInputException $e) {
                throw $error ?? $e;
            } catch (RuntimeException $e) {
                throw $e;
            } catch (\Exception $error) {
            }
        }

        throw $error;
    }

    private function isInteractiveInput($inputStream): bool
    {
        if ('php://stdin' !== (stream_get_meta_data($inputStream)['uri'] ?? null)) {
            return false;
        }

        if (isset(self::$stdinIsInteractive)) {
            return self::$stdinIsInteractive;
        }

        return self::$stdinIsInteractive = @stream_isatty(fopen('php://stdin', 'r'));
    }

    /**
     * Reads one or more lines of input and returns what is read.
     *
     * @param resource $inputStream The handler resource
     * @param Question $question    The question being asked
     */
    private function readInput($inputStream, Question $question): string|false
    {
        if (!$question->isMultiline()) {
            $cp = $this->setIOCodepage();
            $ret = $this->doReadInput($inputStream);

            return $this->resetIOCodepage($cp, $ret);
        }

        $multiLineStreamReader = $this->cloneInputStream($inputStream);
        if (null === $multiLineStreamReader) {
            return false;
        }

        $cp = $this->setIOCodepage();
        $ret = $this->doReadInput($multiLineStreamReader, "\x4");

        if (stream_get_meta_data($inputStream)['seekable']) {
            fseek($inputStream, ftell($multiLineStreamReader));
        }

        return $this->resetIOCodepage($cp, $ret);
    }

    private function setIOCodepage(): int
    {
        if (\function_exists('sapi_windows_cp_set')) {
            $cp = sapi_windows_cp_get();
            sapi_windows_cp_set(sapi_windows_cp_get('oem'));

            return $cp;
        }

        return 0;
    }

    /**
     * Sets console I/O to the specified code page and converts the user input.
     */
    private function resetIOCodepage(int $cp, string|false $input): string|false
    {
        if (0 !== $cp) {
            sapi_windows_cp_set($cp);

            if (false !== $input && '' !== $input) {
                $input = sapi_windows_cp_conv(sapi_windows_cp_get('oem'), $cp, $input);
            }
        }

        return $input;
    }

    /**
     * Clones an input stream in order to act on one instance of the same
     * stream without affecting the other instance.
     *
     * @param resource $inputStream The handler resource
     *
     * @return resource|null The cloned resource, null in case it could not be cloned
     */
    private function cloneInputStream($inputStream)
    {
        $streamMetaData = stream_get_meta_data($inputStream);
        $seekable = $streamMetaData['seekable'] ?? false;
        $mode = $streamMetaData['mode'] ?? 'rb';
        $uri = $streamMetaData['uri'] ?? null;

        if (null === $uri) {
            return null;
        }

        $cloneStream = fopen($uri, $mode);

        // For seekable and writable streams, add all the same data to the
        // cloned stream and then seek to the same offset.
        if (true === $seekable && !\in_array($mode, ['r', 'rb', 'rt'])) {
            $offset = ftell($inputStream);
            rewind($inputStream);
            stream_copy_to_stream($inputStream, $cloneStream);
            fseek($inputStream, $offset);
            fseek($cloneStream, $offset);
        }

        return $cloneStream;
    }

    /**
     * @param resource $inputStream
     */
    private function doReadInput($inputStream, ?string $exitChar = null, ?TerminalInputHelper $helper = null): string
    {
        $ret = '';
        $helper ??= new TerminalInputHelper($inputStream, false);

        while (!feof($inputStream)) {
            $helper->waitForInput();
            $char = fread($inputStream, 1);

            // as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false.
            if (false === $char || ('' === $ret && '' === $char)) {
                throw new MissingInputException('Aborted.');
            }

            if (\PHP_EOL === "{$ret}{$char}" || $exitChar === $char) {
                break;
            }

            $ret .= $char;

            if (null === $exitChar && "\n" === $char) {
                break;
            }
        }

        return $ret;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;

/**
 * The ProcessHelper class provides helpers to run external processes.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @final
 */
class ProcessHelper extends Helper
{
    /**
     * Runs an external process.
     *
     * @param array|Process $cmd      An instance of Process or an array of the command and arguments
     * @param callable|null $callback A PHP callback to run whenever there is some
     *                                output available on STDOUT or STDERR
     */
    public function run(OutputInterface $output, array|Process $cmd, ?string $error = null, ?callable $callback = null, int $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE): Process
    {
        if (!class_exists(Process::class)) {
            throw new \LogicException('The ProcessHelper cannot be run as the Process component is not installed. Try running "compose require symfony/process".');
        }

        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        $formatter = $this->getHelperSet()->get('debug_formatter');

        if ($cmd instanceof Process) {
            $cmd = [$cmd];
        }

        if (\is_string($cmd[0] ?? null)) {
            $process = new Process($cmd);
            $cmd = [];
        } elseif (($cmd[0] ?? null) instanceof Process) {
            $process = $cmd[0];
            unset($cmd[0]);
        } else {
            throw new \InvalidArgumentException(\sprintf('Invalid command provided to "%s()": the command should be an array whose first element is either the path to the binary to run or a "Process" object.', __METHOD__));
        }

        if ($verbosity <= $output->getVerbosity()) {
            $output->write($formatter->start(spl_object_hash($process), $this->escapeString($process->getCommandLine())));
        }

        if ($output->isDebug()) {
            $callback = $this->wrapCallback($output, $process, $callback);
        }

        $process->run($callback, $cmd);

        if ($verbosity <= $output->getVerbosity()) {
            $message = $process->isSuccessful() ? 'Command ran successfully' : \sprintf('%s Command did not run successfully', $process->getExitCode());
            $output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful()));
        }

        if (!$process->isSuccessful() && null !== $error) {
            $output->writeln(\sprintf('<error>%s</error>', $this->escapeString($error)));
        }

        return $process;
    }

    /**
     * Runs the process.
     *
     * This is identical to run() except that an exception is thrown if the process
     * exits with a non-zero exit code.
     *
     * @param array|Process $cmd      An instance of Process or a command to run
     * @param callable|null $callback A PHP callback to run whenever there is some
     *                                output available on STDOUT or STDERR
     *
     * @throws ProcessFailedException
     *
     * @see run()
     */
    public function mustRun(OutputInterface $output, array|Process $cmd, ?string $error = null, ?callable $callback = null): Process
    {
        $process = $this->run($output, $cmd, $error, $callback);

        if (!$process->isSuccessful()) {
            throw new ProcessFailedException($process);
        }

        return $process;
    }

    /**
     * Wraps a Process callback to add debugging output.
     */
    public function wrapCallback(OutputInterface $output, Process $process, ?callable $callback = null): callable
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        $formatter = $this->getHelperSet()->get('debug_formatter');

        return function ($type, $buffer) use ($output, $process, $callback, $formatter) {
            $output->write($formatter->progress(spl_object_hash($process), $this->escapeString($buffer), Process::ERR === $type));

            if (null !== $callback) {
                $callback($type, $buffer);
            }
        };
    }

    private function escapeString(string $str): string
    {
        return str_replace('<', '\\<', $str);
    }

    public function getName(): string
    {
        return 'process';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Formatter\OutputFormatter;

/**
 * The Formatter class provides helpers to format messages.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class FormatterHelper extends Helper
{
    /**
     * Formats a message within a section.
     */
    public function formatSection(string $section, string $message, string $style = 'info'): string
    {
        return \sprintf('<%s>[%s]</%s> %s', $style, $section, $style, $message);
    }

    /**
     * Formats a message as a block of text.
     */
    public function formatBlock(string|array $messages, string $style, bool $large = false): string
    {
        if (!\is_array($messages)) {
            $messages = [$messages];
        }

        $len = 0;
        $lines = [];
        foreach ($messages as $message) {
            $message = OutputFormatter::escape($message);
            $lines[] = \sprintf($large ? '  %s  ' : ' %s ', $message);
            $len = max(self::width($message) + ($large ? 4 : 2), $len);
        }

        $messages = $large ? [str_repeat(' ', $len)] : [];
        for ($i = 0; isset($lines[$i]); ++$i) {
            $messages[] = $lines[$i].str_repeat(' ', $len - self::width($lines[$i]));
        }
        if ($large) {
            $messages[] = str_repeat(' ', $len);
        }

        for ($i = 0; isset($messages[$i]); ++$i) {
            $messages[$i] = \sprintf('<%s>%s</%s>', $style, $messages[$i], $style);
        }

        return implode("\n", $messages);
    }

    /**
     * Truncates a message to the given length.
     */
    public function truncate(string $message, int $length, string $suffix = '...'): string
    {
        $computedLength = $length - self::width($suffix);

        if ($computedLength > self::width($message)) {
            return $message;
        }

        return self::substr($message, 0, $length).$suffix;
    }

    public function getName(): string
    {
        return 'formatter';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\String\UnicodeString;

/**
 * Helper is the base class for all helper classes.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
abstract class Helper implements HelperInterface
{
    protected $helperSet;

    /**
     * @return void
     */
    public function setHelperSet(?HelperSet $helperSet = null)
    {
        if (1 > \func_num_args()) {
            trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
        }
        $this->helperSet = $helperSet;
    }

    public function getHelperSet(): ?HelperSet
    {
        return $this->helperSet;
    }

    /**
     * Returns the width of a string, using mb_strwidth if it is available.
     * The width is how many characters positions the string will use.
     */
    public static function width(?string $string): int
    {
        $string ??= '';

        if (preg_match('//u', $string)) {
            $string = preg_replace('/[\p{Cc}\x7F]++/u', '', $string, -1, $count);

            return (new UnicodeString($string))->width(false) + $count;
        }

        if (false === $encoding = mb_detect_encoding($string, null, true)) {
            return \strlen($string);
        }

        return mb_strwidth($string, $encoding);
    }

    /**
     * Returns the length of a string, using mb_strlen if it is available.
     * The length is related to how many bytes the string will use.
     */
    public static function length(?string $string): int
    {
        $string ??= '';

        if (preg_match('//u', $string)) {
            return (new UnicodeString($string))->length();
        }

        if (false === $encoding = mb_detect_encoding($string, null, true)) {
            return \strlen($string);
        }

        return mb_strlen($string, $encoding);
    }

    /**
     * Returns the subset of a string, using mb_substr if it is available.
     */
    public static function substr(?string $string, int $from, ?int $length = null): string
    {
        $string ??= '';

        if (preg_match('//u', $string)) {
            return (new UnicodeString($string))->slice($from, $length);
        }

        if (false === $encoding = mb_detect_encoding($string, null, true)) {
            return substr($string, $from, $length);
        }

        return mb_substr($string, $from, $length, $encoding);
    }

    /**
     * @return string
     */
    public static function formatTime(int|float $secs, int $precision = 1)
    {
        $secs = (int) floor($secs);

        if (0 === $secs) {
            return '< 1 sec';
        }

        static $timeFormats = [
            [1, '1 sec', 'secs'],
            [60, '1 min', 'mins'],
            [3600, '1 hr', 'hrs'],
            [86400, '1 day', 'days'],
        ];

        $times = [];
        foreach ($timeFormats as $index => $format) {
            $seconds = isset($timeFormats[$index + 1]) ? $secs % $timeFormats[$index + 1][0] : $secs;

            if (isset($times[$index - $precision])) {
                unset($times[$index - $precision]);
            }

            if (0 === $seconds) {
                continue;
            }

            $unitCount = ($seconds / $format[0]);
            $times[$index] = 1 === $unitCount ? $format[1] : $unitCount.' '.$format[2];

            if ($secs === $seconds) {
                break;
            }

            $secs -= $seconds;
        }

        return implode(', ', array_reverse($times));
    }

    /**
     * @return string
     */
    public static function formatMemory(int $memory)
    {
        if ($memory >= 1024 * 1024 * 1024) {
            return \sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024);
        }

        if ($memory >= 1024 * 1024) {
            return \sprintf('%.1f MiB', $memory / 1024 / 1024);
        }

        if ($memory >= 1024) {
            return \sprintf('%d KiB', $memory / 1024);
        }

        return \sprintf('%d B', $memory);
    }

    /**
     * @return string
     */
    public static function removeDecoration(OutputFormatterInterface $formatter, ?string $string)
    {
        $isDecorated = $formatter->isDecorated();
        $formatter->setDecorated(false);
        // remove <...> formatting
        $string = $formatter->format($string ?? '');
        // remove already formatted characters
        $string = preg_replace("/\033\[[^m]*m/", '', $string ?? '');
        // remove terminal hyperlinks
        $string = preg_replace('/\\033]8;[^;]*;[^\\033]*\\033\\\\/', '', $string ?? '');
        $formatter->setDecorated($isDecorated);

        return $string;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

/**
 * HelperInterface is the interface all helpers must implement.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
interface HelperInterface
{
    /**
     * Sets the helper set associated with this helper.
     *
     * @return void
     */
    public function setHelperSet(?HelperSet $helperSet);

    /**
     * Gets the helper set associated with this helper.
     */
    public function getHelperSet(): ?HelperSet;

    /**
     * Returns the canonical name of this helper.
     *
     * @return string
     */
    public function getName();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Cursor;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Terminal;

/**
 * The ProgressBar provides helpers to display progress output.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Chris Jones <leeked@gmail.com>
 */
final class ProgressBar
{
    public const FORMAT_VERBOSE = 'verbose';
    public const FORMAT_VERY_VERBOSE = 'very_verbose';
    public const FORMAT_DEBUG = 'debug';
    public const FORMAT_NORMAL = 'normal';

    private const FORMAT_VERBOSE_NOMAX = 'verbose_nomax';
    private const FORMAT_VERY_VERBOSE_NOMAX = 'very_verbose_nomax';
    private const FORMAT_DEBUG_NOMAX = 'debug_nomax';
    private const FORMAT_NORMAL_NOMAX = 'normal_nomax';

    private int $barWidth = 28;
    private string $barChar;
    private string $emptyBarChar = '-';
    private string $progressChar = '>';
    private ?string $format = null;
    private ?string $internalFormat = null;
    private ?int $redrawFreq = 1;
    private int $writeCount = 0;
    private float $lastWriteTime = 0;
    private float $minSecondsBetweenRedraws = 0;
    private float $maxSecondsBetweenRedraws = 1;
    private OutputInterface $output;
    private int $step = 0;
    private int $startingStep = 0;
    private ?int $max = null;
    private int $startTime;
    private int $stepWidth;
    private float $percent = 0.0;
    private array $messages = [];
    private bool $overwrite = true;
    private Terminal $terminal;
    private ?string $previousMessage = null;
    private Cursor $cursor;
    private array $placeholders = [];

    private static array $formatters;
    private static array $formats;

    /**
     * @param int $max Maximum steps (0 if unknown)
     */
    public function __construct(OutputInterface $output, int $max = 0, float $minSecondsBetweenRedraws = 1 / 25)
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        $this->output = $output;
        $this->setMaxSteps($max);
        $this->terminal = new Terminal();

        if (0 < $minSecondsBetweenRedraws) {
            $this->redrawFreq = null;
            $this->minSecondsBetweenRedraws = $minSecondsBetweenRedraws;
        }

        if (!$this->output->isDecorated()) {
            // disable overwrite when output does not support ANSI codes.
            $this->overwrite = false;

            // set a reasonable redraw frequency so output isn't flooded
            $this->redrawFreq = null;
        }

        $this->startTime = time();
        $this->cursor = new Cursor($output);
    }

    /**
     * Sets a placeholder formatter for a given name, globally for all instances of ProgressBar.
     *
     * This method also allow you to override an existing placeholder.
     *
     * @param string                       $name     The placeholder name (including the delimiter char like %)
     * @param callable(ProgressBar):string $callable A PHP callable
     */
    public static function setPlaceholderFormatterDefinition(string $name, callable $callable): void
    {
        self::$formatters ??= self::initPlaceholderFormatters();

        self::$formatters[$name] = $callable;
    }

    /**
     * Gets the placeholder formatter for a given name.
     *
     * @param string $name The placeholder name (including the delimiter char like %)
     */
    public static function getPlaceholderFormatterDefinition(string $name): ?callable
    {
        self::$formatters ??= self::initPlaceholderFormatters();

        return self::$formatters[$name] ?? null;
    }

    /**
     * Sets a placeholder formatter for a given name, for this instance only.
     *
     * @param callable(ProgressBar):string $callable A PHP callable
     */
    public function setPlaceholderFormatter(string $name, callable $callable): void
    {
        $this->placeholders[$name] = $callable;
    }

    /**
     * Gets the placeholder formatter for a given name.
     *
     * @param string $name The placeholder name (including the delimiter char like %)
     */
    public function getPlaceholderFormatter(string $name): ?callable
    {
        return $this->placeholders[$name] ?? $this::getPlaceholderFormatterDefinition($name);
    }

    /**
     * Sets a format for a given name.
     *
     * This method also allow you to override an existing format.
     *
     * @param string $name   The format name
     * @param string $format A format string
     */
    public static function setFormatDefinition(string $name, string $format): void
    {
        self::$formats ??= self::initFormats();

        self::$formats[$name] = $format;
    }

    /**
     * Gets the format for a given name.
     *
     * @param string $name The format name
     */
    public static function getFormatDefinition(string $name): ?string
    {
        self::$formats ??= self::initFormats();

        return self::$formats[$name] ?? null;
    }

    /**
     * Associates a text with a named placeholder.
     *
     * The text is displayed when the progress bar is rendered but only
     * when the corresponding placeholder is part of the custom format line
     * (by wrapping the name with %).
     *
     * @param string $message The text to associate with the placeholder
     * @param string $name    The name of the placeholder
     */
    public function setMessage(string $message, string $name = 'message'): void
    {
        $this->messages[$name] = $message;
    }

    public function getMessage(string $name = 'message'): ?string
    {
        return $this->messages[$name] ?? null;
    }

    public function getStartTime(): int
    {
        return $this->startTime;
    }

    public function getMaxSteps(): int
    {
        return $this->max;
    }

    public function getProgress(): int
    {
        return $this->step;
    }

    private function getStepWidth(): int
    {
        return $this->stepWidth;
    }

    public function getProgressPercent(): float
    {
        return $this->percent;
    }

    public function getBarOffset(): float
    {
        return floor($this->max ? $this->percent * $this->barWidth : (null === $this->redrawFreq ? (int) (min(5, $this->barWidth / 15) * $this->writeCount) : $this->step) % $this->barWidth);
    }

    public function getEstimated(): float
    {
        if (0 === $this->step || $this->step === $this->startingStep) {
            return 0;
        }

        return round((time() - $this->startTime) / ($this->step - $this->startingStep) * $this->max);
    }

    public function getRemaining(): float
    {
        if (0 === $this->step || $this->step === $this->startingStep) {
            return 0;
        }

        return round((time() - $this->startTime) / ($this->step - $this->startingStep) * ($this->max - $this->step));
    }

    public function setBarWidth(int $size): void
    {
        $this->barWidth = max(1, $size);
    }

    public function getBarWidth(): int
    {
        return $this->barWidth;
    }

    public function setBarCharacter(string $char): void
    {
        $this->barChar = $char;
    }

    public function getBarCharacter(): string
    {
        return $this->barChar ?? ($this->max ? '=' : $this->emptyBarChar);
    }

    public function setEmptyBarCharacter(string $char): void
    {
        $this->emptyBarChar = $char;
    }

    public function getEmptyBarCharacter(): string
    {
        return $this->emptyBarChar;
    }

    public function setProgressCharacter(string $char): void
    {
        $this->progressChar = $char;
    }

    public function getProgressCharacter(): string
    {
        return $this->progressChar;
    }

    public function setFormat(string $format): void
    {
        $this->format = null;
        $this->internalFormat = $format;
    }

    /**
     * Sets the redraw frequency.
     *
     * @param int|null $freq The frequency in steps
     */
    public function setRedrawFrequency(?int $freq): void
    {
        $this->redrawFreq = null !== $freq ? max(1, $freq) : null;
    }

    public function minSecondsBetweenRedraws(float $seconds): void
    {
        $this->minSecondsBetweenRedraws = $seconds;
    }

    public function maxSecondsBetweenRedraws(float $seconds): void
    {
        $this->maxSecondsBetweenRedraws = $seconds;
    }

    /**
     * Returns an iterator that will automatically update the progress bar when iterated.
     *
     * @template TKey
     * @template TValue
     *
     * @param iterable<TKey, TValue> $iterable
     * @param int|null               $max      Number of steps to complete the bar (0 if indeterminate), if null it will be inferred from $iterable
     *
     * @return iterable<TKey, TValue>
     */
    public function iterate(iterable $iterable, ?int $max = null): iterable
    {
        $this->start($max ?? (is_countable($iterable) ? \count($iterable) : 0));

        foreach ($iterable as $key => $value) {
            yield $key => $value;

            $this->advance();
        }

        $this->finish();
    }

    /**
     * Starts the progress output.
     *
     * @param int|null $max     Number of steps to complete the bar (0 if indeterminate), null to leave unchanged
     * @param int      $startAt The starting point of the bar (useful e.g. when resuming a previously started bar)
     */
    public function start(?int $max = null, int $startAt = 0): void
    {
        $this->startTime = time();
        $this->step = $startAt;
        $this->startingStep = $startAt;

        $startAt > 0 ? $this->setProgress($startAt) : $this->percent = 0.0;

        if (null !== $max) {
            $this->setMaxSteps($max);
        }

        $this->display();
    }

    /**
     * Advances the progress output X steps.
     *
     * @param int $step Number of steps to advance
     */
    public function advance(int $step = 1): void
    {
        $this->setProgress($this->step + $step);
    }

    /**
     * Sets whether to overwrite the progressbar, false for new line.
     */
    public function setOverwrite(bool $overwrite): void
    {
        $this->overwrite = $overwrite;
    }

    public function setProgress(int $step): void
    {
        if ($this->max && $step > $this->max) {
            $this->max = $step;
        } elseif ($step < 0) {
            $step = 0;
        }

        $redrawFreq = $this->redrawFreq ?? (($this->max ?: 10) / 10);
        $prevPeriod = (int) ($this->step / $redrawFreq);
        $currPeriod = (int) ($step / $redrawFreq);
        $this->step = $step;
        $this->percent = $this->max ? (float) $this->step / $this->max : 0;
        $timeInterval = microtime(true) - $this->lastWriteTime;

        // Draw regardless of other limits
        if ($this->max === $step) {
            $this->display();

            return;
        }

        // Throttling
        if ($timeInterval < $this->minSecondsBetweenRedraws) {
            return;
        }

        // Draw each step period, but not too late
        if ($prevPeriod !== $currPeriod || $timeInterval >= $this->maxSecondsBetweenRedraws) {
            $this->display();
        }
    }

    public function setMaxSteps(int $max): void
    {
        $this->format = null;
        $this->max = max(0, $max);
        $this->stepWidth = $this->max ? Helper::width((string) $this->max) : 4;
    }

    /**
     * Finishes the progress output.
     */
    public function finish(): void
    {
        if (!$this->max) {
            $this->max = $this->step;
        }

        if ($this->step === $this->max && !$this->overwrite) {
            // prevent double 100% output
            return;
        }

        $this->setProgress($this->max);
    }

    /**
     * Outputs the current progress string.
     */
    public function display(): void
    {
        if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
            return;
        }

        if (null === $this->format) {
            $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
        }

        $this->overwrite($this->buildLine());
    }

    /**
     * Removes the progress bar from the current line.
     *
     * This is useful if you wish to write some output
     * while a progress bar is running.
     * Call display() to show the progress bar again.
     */
    public function clear(): void
    {
        if (!$this->overwrite) {
            return;
        }

        if (null === $this->format) {
            $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
        }

        $this->overwrite('');
    }

    private function setRealFormat(string $format): void
    {
        // try to use the _nomax variant if available
        if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) {
            $this->format = self::getFormatDefinition($format.'_nomax');
        } elseif (null !== self::getFormatDefinition($format)) {
            $this->format = self::getFormatDefinition($format);
        } else {
            $this->format = $format;
        }
    }

    /**
     * Overwrites a previous message to the output.
     */
    private function overwrite(string $message): void
    {
        if ($this->previousMessage === $message) {
            return;
        }

        $originalMessage = $message;

        if ($this->overwrite) {
            if (null !== $this->previousMessage) {
                if ($this->output instanceof ConsoleSectionOutput) {
                    $messageLines = explode("\n", $this->previousMessage);
                    $lineCount = \count($messageLines);

                    $lastLineWithoutDecoration = Helper::removeDecoration($this->output->getFormatter(), end($messageLines) ?? '');

                    // When the last previous line is empty (without formatting) it is already cleared by the section output, so we don't need to clear it again
                    if ('' === $lastLineWithoutDecoration) {
                        --$lineCount;
                    }

                    foreach ($messageLines as $messageLine) {
                        $messageLineLength = Helper::width(Helper::removeDecoration($this->output->getFormatter(), $messageLine));
                        if ($messageLineLength > $this->terminal->getWidth()) {
                            $lineCount += floor($messageLineLength / $this->terminal->getWidth());
                        }
                    }

                    $this->output->clear($lineCount);
                } else {
                    $lineCount = substr_count($this->previousMessage, "\n");
                    for ($i = 0; $i < $lineCount; ++$i) {
                        $this->cursor->moveToColumn(1);
                        $this->cursor->clearLine();
                        $this->cursor->moveUp();
                    }

                    $this->cursor->moveToColumn(1);
                    $this->cursor->clearLine();
                }
            }
        } elseif ($this->step > 0) {
            $message = \PHP_EOL.$message;
        }

        $this->previousMessage = $originalMessage;
        $this->lastWriteTime = microtime(true);

        $this->output->write($message);
        ++$this->writeCount;
    }

    private function determineBestFormat(): string
    {
        return match ($this->output->getVerbosity()) {
            // OutputInterface::VERBOSITY_QUIET: display is disabled anyway
            OutputInterface::VERBOSITY_VERBOSE => $this->max ? self::FORMAT_VERBOSE : self::FORMAT_VERBOSE_NOMAX,
            OutputInterface::VERBOSITY_VERY_VERBOSE => $this->max ? self::FORMAT_VERY_VERBOSE : self::FORMAT_VERY_VERBOSE_NOMAX,
            OutputInterface::VERBOSITY_DEBUG => $this->max ? self::FORMAT_DEBUG : self::FORMAT_DEBUG_NOMAX,
            default => $this->max ? self::FORMAT_NORMAL : self::FORMAT_NORMAL_NOMAX,
        };
    }

    private static function initPlaceholderFormatters(): array
    {
        return [
            'bar' => static function (self $bar, OutputInterface $output) {
                $completeBars = $bar->getBarOffset();
                $display = str_repeat($bar->getBarCharacter(), $completeBars);
                if ($completeBars < $bar->getBarWidth()) {
                    $emptyBars = $bar->getBarWidth() - $completeBars - Helper::length(Helper::removeDecoration($output->getFormatter(), $bar->getProgressCharacter()));
                    $display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars);
                }

                return $display;
            },
            'elapsed' => static fn (self $bar) => Helper::formatTime(time() - $bar->getStartTime(), 2),
            'remaining' => static function (self $bar) {
                if (!$bar->getMaxSteps()) {
                    throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.');
                }

                return Helper::formatTime($bar->getRemaining(), 2);
            },
            'estimated' => static function (self $bar) {
                if (!$bar->getMaxSteps()) {
                    throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.');
                }

                return Helper::formatTime($bar->getEstimated(), 2);
            },
            'memory' => static fn (self $bar) => Helper::formatMemory(memory_get_usage(true)),
            'current' => static fn (self $bar) => str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', \STR_PAD_LEFT),
            'max' => static fn (self $bar) => $bar->getMaxSteps(),
            'percent' => static fn (self $bar) => floor($bar->getProgressPercent() * 100),
        ];
    }

    private static function initFormats(): array
    {
        return [
            self::FORMAT_NORMAL => ' %current%/%max% [%bar%] %percent:3s%%',
            self::FORMAT_NORMAL_NOMAX => ' %current% [%bar%]',

            self::FORMAT_VERBOSE => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%',
            self::FORMAT_VERBOSE_NOMAX => ' %current% [%bar%] %elapsed:6s%',

            self::FORMAT_VERY_VERBOSE => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%',
            self::FORMAT_VERY_VERBOSE_NOMAX => ' %current% [%bar%] %elapsed:6s%',

            self::FORMAT_DEBUG => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%',
            self::FORMAT_DEBUG_NOMAX => ' %current% [%bar%] %elapsed:6s% %memory:6s%',
        ];
    }

    private function buildLine(): string
    {
        \assert(null !== $this->format);

        $regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i";
        $callback = function ($matches) {
            if ($formatter = $this->getPlaceholderFormatter($matches[1])) {
                $text = $formatter($this, $this->output);
            } elseif (isset($this->messages[$matches[1]])) {
                $text = $this->messages[$matches[1]];
            } else {
                return $matches[0];
            }

            if (isset($matches[2])) {
                $text = \sprintf('%'.$matches[2], $text);
            }

            return $text;
        };
        $line = preg_replace_callback($regex, $callback, $this->format);

        // gets string length for each sub line with multiline format
        $linesLength = array_map(fn ($subLine) => Helper::width(Helper::removeDecoration($this->output->getFormatter(), rtrim($subLine, "\r"))), explode("\n", $line));

        $linesWidth = max($linesLength);

        $terminalWidth = $this->terminal->getWidth();
        if ($linesWidth <= $terminalWidth) {
            return $line;
        }

        $this->setBarWidth($this->barWidth - $linesWidth + $terminalWidth);

        return preg_replace_callback($regex, $callback, $this->format);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\VarDumper\Cloner\ClonerInterface;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;

/**
 * @author Roland Franssen <franssen.roland@gmail.com>
 */
final class Dumper
{
    private OutputInterface $output;
    private ?CliDumper $dumper;
    private ?ClonerInterface $cloner;
    private \Closure $handler;

    public function __construct(OutputInterface $output, ?CliDumper $dumper = null, ?ClonerInterface $cloner = null)
    {
        $this->output = $output;
        $this->dumper = $dumper;
        $this->cloner = $cloner;

        if (class_exists(CliDumper::class)) {
            $this->handler = function ($var): string {
                $dumper = $this->dumper ??= new CliDumper(null, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR);
                $dumper->setColors($this->output->isDecorated());

                return rtrim($dumper->dump(($this->cloner ??= new VarCloner())->cloneVar($var)->withRefHandles(false), true));
            };
        } else {
            $this->handler = static fn ($var): string => match (true) {
                null === $var => 'null',
                true === $var => 'true',
                false === $var => 'false',
                \is_string($var) => '"'.$var.'"',
                default => rtrim(print_r($var, true)),
            };
        }
    }

    public function __invoke(mixed $var): string
    {
        return ($this->handler)($var);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

/**
 * @internal
 */
class TableRows implements \IteratorAggregate
{
    private \Closure $generator;

    public function __construct(\Closure $generator)
    {
        $this->generator = $generator;
    }

    public function getIterator(): \Traversable
    {
        return ($this->generator)();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
 */
class TableCell
{
    private string $value;
    private array $options = [
        'rowspan' => 1,
        'colspan' => 1,
        'style' => null,
    ];

    public function __construct(string $value = '', array $options = [])
    {
        $this->value = $value;

        // check option names
        if ($diff = array_diff(array_keys($options), array_keys($this->options))) {
            throw new InvalidArgumentException(\sprintf('The TableCell does not support the following options: \'%s\'.', implode('\', \'', $diff)));
        }

        if (isset($options['style']) && !$options['style'] instanceof TableCellStyle) {
            throw new InvalidArgumentException('The style option must be an instance of "TableCellStyle".');
        }

        $this->options = array_merge($this->options, $options);
    }

    /**
     * Returns the cell value.
     */
    public function __toString(): string
    {
        return $this->value;
    }

    /**
     * Gets number of colspan.
     */
    public function getColspan(): int
    {
        return (int) $this->options['colspan'];
    }

    /**
     * Gets number of rowspan.
     */
    public function getRowspan(): int
    {
        return (int) $this->options['rowspan'];
    }

    public function getStyle(): ?TableCellStyle
    {
        return $this->options['style'];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

/**
 * Simple output wrapper for "tagged outputs" instead of wordwrap(). This solution is based on a StackOverflow
 * answer: https://stackoverflow.com/a/20434776/1476819 from user557597 (alias SLN).
 *
 *  (?:
 *       # -- Words/Characters
 *       (                       # (1 start)
 *            (?>                     # Atomic Group - Match words with valid breaks
 *                 .{1,16}                 #  1-N characters
 *                                         #  Followed by one of 4 prioritized, non-linebreak whitespace
 *                 (?:                     #  break types:
 *                      (?<= [^\S\r\n] )        # 1. - Behind a non-linebreak whitespace
 *                      [^\S\r\n]?              #      ( optionally accept an extra non-linebreak whitespace )
 *                   |  (?= \r? \n )            # 2. - Ahead a linebreak
 *                   |  $                       # 3. - EOS
 *                   |  [^\S\r\n]               # 4. - Accept an extra non-linebreak whitespace
 *                 )
 *            )                       # End atomic group
 *         |
 *            .{1,16}                 # No valid word breaks, just break on the N'th character
 *       )                       # (1 end)
 *       (?: \r? \n )?           # Optional linebreak after Words/Characters
 *    |
 *       # -- Or, Linebreak
 *       (?: \r? \n | $ )        # Stand alone linebreak or at EOS
 *  )
 *
 * @author Krisztián Ferenczi <ferenczi.krisztian@gmail.com>
 *
 * @see https://stackoverflow.com/a/20434776/1476819
 */
final class OutputWrapper
{
    private const TAG_OPEN_REGEX_SEGMENT = '[a-z](?:[^\\\\<>]*+ | \\\\.)*';
    private const TAG_CLOSE_REGEX_SEGMENT = '[a-z][^<>]*+';
    private const URL_PATTERN = 'https?://\S+';

    public function __construct(
        private bool $allowCutUrls = false,
    ) {
    }

    public function wrap(string $text, int $width, string $break = "\n"): string
    {
        if (!$width) {
            return $text;
        }

        $tagPattern = \sprintf('<(?:(?:%s)|/(?:%s)?)>', self::TAG_OPEN_REGEX_SEGMENT, self::TAG_CLOSE_REGEX_SEGMENT);
        $limitPattern = "{1,$width}";
        $patternBlocks = [$tagPattern];
        if (!$this->allowCutUrls) {
            $patternBlocks[] = self::URL_PATTERN;
        }
        $patternBlocks[] = '.';
        $blocks = implode('|', $patternBlocks);
        $rowPattern = "(?:$blocks)$limitPattern";
        $pattern = \sprintf('#(?:((?>(%1$s)((?<=[^\S\r\n])[^\S\r\n]?|(?=\r?\n)|$|[^\S\r\n]))|(%1$s))(?:\r?\n)?|(?:\r?\n|$))#imux', $rowPattern);
        $output = rtrim(preg_replace($pattern, '\\1'.$break, $text), $break);

        return str_replace(' '.$break, $break, $output);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;

/**
 * Defines the styles for a Table.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Саша Стаменковић <umpirsky@gmail.com>
 * @author Dany Maillard <danymaillard93b@gmail.com>
 */
class TableStyle
{
    private string $paddingChar = ' ';
    private string $horizontalOutsideBorderChar = '-';
    private string $horizontalInsideBorderChar = '-';
    private string $verticalOutsideBorderChar = '|';
    private string $verticalInsideBorderChar = '|';
    private string $crossingChar = '+';
    private string $crossingTopRightChar = '+';
    private string $crossingTopMidChar = '+';
    private string $crossingTopLeftChar = '+';
    private string $crossingMidRightChar = '+';
    private string $crossingBottomRightChar = '+';
    private string $crossingBottomMidChar = '+';
    private string $crossingBottomLeftChar = '+';
    private string $crossingMidLeftChar = '+';
    private string $crossingTopLeftBottomChar = '+';
    private string $crossingTopMidBottomChar = '+';
    private string $crossingTopRightBottomChar = '+';
    private string $headerTitleFormat = '<fg=black;bg=white;options=bold> %s </>';
    private string $footerTitleFormat = '<fg=black;bg=white;options=bold> %s </>';
    private string $cellHeaderFormat = '<info>%s</info>';
    private string $cellRowFormat = '%s';
    private string $cellRowContentFormat = ' %s ';
    private string $borderFormat = '%s';
    private int $padType = \STR_PAD_RIGHT;

    /**
     * Sets padding character, used for cell padding.
     *
     * @return $this
     */
    public function setPaddingChar(string $paddingChar): static
    {
        if (!$paddingChar) {
            throw new LogicException('The padding char must not be empty.');
        }

        $this->paddingChar = $paddingChar;

        return $this;
    }

    /**
     * Gets padding character, used for cell padding.
     */
    public function getPaddingChar(): string
    {
        return $this->paddingChar;
    }

    /**
     * Sets horizontal border characters.
     *
     * <code>
     * ╔═══════════════╤══════════════════════════╤══════════════════╗
     * ║ ISBN          │ Title                    │ Author           ║
     * ╠═══════1═══════╪══════════════════════════╪══════════════════╣
     * ║ 99921-58-10-7 │ Divine Comedy            │ Dante Alighieri  ║
     * ║ 9971-5-0210-0 │ A Tale of Two Cities     │ Charles Dickens  ║
     * ╟───────2───────┼──────────────────────────┼──────────────────╢
     * ║ 960-425-059-0 │ The Lord of the Rings    │ J. R. R. Tolkien ║
     * ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie  ║
     * ╚═══════════════╧══════════════════════════╧══════════════════╝
     * </code>
     *
     * @return $this
     */
    public function setHorizontalBorderChars(string $outside, ?string $inside = null): static
    {
        $this->horizontalOutsideBorderChar = $outside;
        $this->horizontalInsideBorderChar = $inside ?? $outside;

        return $this;
    }

    /**
     * Sets vertical border characters.
     *
     * <code>
     * ╔═══════════════╤══════════════════════════╤══════════════════╗
     * 1 ISBN          2 Title                    │ Author           ║
     * ╠═══════════════╪══════════════════════════╪══════════════════╣
     * ║ 99921-58-10-7 │ Divine Comedy            │ Dante Alighieri  ║
     * ║ 9971-5-0210-0 │ A Tale of Two Cities     │ Charles Dickens  ║
     * ║ 960-425-059-0 │ The Lord of the Rings    │ J. R. R. Tolkien ║
     * ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie  ║
     * ╚═══════════════╧══════════════════════════╧══════════════════╝
     * </code>
     *
     * @return $this
     */
    public function setVerticalBorderChars(string $outside, ?string $inside = null): static
    {
        $this->verticalOutsideBorderChar = $outside;
        $this->verticalInsideBorderChar = $inside ?? $outside;

        return $this;
    }

    /**
     * Gets border characters.
     *
     * @internal
     */
    public function getBorderChars(): array
    {
        return [
            $this->horizontalOutsideBorderChar,
            $this->verticalOutsideBorderChar,
            $this->horizontalInsideBorderChar,
            $this->verticalInsideBorderChar,
        ];
    }

    /**
     * Sets crossing characters.
     *
     * Example:
     * <code>
     * 1═══════════════2══════════════════════════2══════════════════3
     * ║ ISBN          │ Title                    │ Author           ║
     * 8'══════════════0'═════════════════════════0'═════════════════4'
     * ║ 99921-58-10-7 │ Divine Comedy            │ Dante Alighieri  ║
     * ║ 9971-5-0210-0 │ A Tale of Two Cities     │ Charles Dickens  ║
     * 8───────────────0──────────────────────────0──────────────────4
     * ║ 960-425-059-0 │ The Lord of the Rings    │ J. R. R. Tolkien ║
     * ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie  ║
     * 7═══════════════6══════════════════════════6══════════════════5
     * </code>
     *
     * @param string      $cross          Crossing char (see #0 of example)
     * @param string      $topLeft        Top left char (see #1 of example)
     * @param string      $topMid         Top mid char (see #2 of example)
     * @param string      $topRight       Top right char (see #3 of example)
     * @param string      $midRight       Mid right char (see #4 of example)
     * @param string      $bottomRight    Bottom right char (see #5 of example)
     * @param string      $bottomMid      Bottom mid char (see #6 of example)
     * @param string      $bottomLeft     Bottom left char (see #7 of example)
     * @param string      $midLeft        Mid left char (see #8 of example)
     * @param string|null $topLeftBottom  Top left bottom char (see #8' of example), equals to $midLeft if null
     * @param string|null $topMidBottom   Top mid bottom char (see #0' of example), equals to $cross if null
     * @param string|null $topRightBottom Top right bottom char (see #4' of example), equals to $midRight if null
     *
     * @return $this
     */
    public function setCrossingChars(string $cross, string $topLeft, string $topMid, string $topRight, string $midRight, string $bottomRight, string $bottomMid, string $bottomLeft, string $midLeft, ?string $topLeftBottom = null, ?string $topMidBottom = null, ?string $topRightBottom = null): static
    {
        $this->crossingChar = $cross;
        $this->crossingTopLeftChar = $topLeft;
        $this->crossingTopMidChar = $topMid;
        $this->crossingTopRightChar = $topRight;
        $this->crossingMidRightChar = $midRight;
        $this->crossingBottomRightChar = $bottomRight;
        $this->crossingBottomMidChar = $bottomMid;
        $this->crossingBottomLeftChar = $bottomLeft;
        $this->crossingMidLeftChar = $midLeft;
        $this->crossingTopLeftBottomChar = $topLeftBottom ?? $midLeft;
        $this->crossingTopMidBottomChar = $topMidBottom ?? $cross;
        $this->crossingTopRightBottomChar = $topRightBottom ?? $midRight;

        return $this;
    }

    /**
     * Sets default crossing character used for each cross.
     *
     * @see {@link setCrossingChars()} for setting each crossing individually.
     */
    public function setDefaultCrossingChar(string $char): self
    {
        return $this->setCrossingChars($char, $char, $char, $char, $char, $char, $char, $char, $char);
    }

    /**
     * Gets crossing character.
     */
    public function getCrossingChar(): string
    {
        return $this->crossingChar;
    }

    /**
     * Gets crossing characters.
     *
     * @internal
     */
    public function getCrossingChars(): array
    {
        return [
            $this->crossingChar,
            $this->crossingTopLeftChar,
            $this->crossingTopMidChar,
            $this->crossingTopRightChar,
            $this->crossingMidRightChar,
            $this->crossingBottomRightChar,
            $this->crossingBottomMidChar,
            $this->crossingBottomLeftChar,
            $this->crossingMidLeftChar,
            $this->crossingTopLeftBottomChar,
            $this->crossingTopMidBottomChar,
            $this->crossingTopRightBottomChar,
        ];
    }

    /**
     * Sets header cell format.
     *
     * @return $this
     */
    public function setCellHeaderFormat(string $cellHeaderFormat): static
    {
        $this->cellHeaderFormat = $cellHeaderFormat;

        return $this;
    }

    /**
     * Gets header cell format.
     */
    public function getCellHeaderFormat(): string
    {
        return $this->cellHeaderFormat;
    }

    /**
     * Sets row cell format.
     *
     * @return $this
     */
    public function setCellRowFormat(string $cellRowFormat): static
    {
        $this->cellRowFormat = $cellRowFormat;

        return $this;
    }

    /**
     * Gets row cell format.
     */
    public function getCellRowFormat(): string
    {
        return $this->cellRowFormat;
    }

    /**
     * Sets row cell content format.
     *
     * @return $this
     */
    public function setCellRowContentFormat(string $cellRowContentFormat): static
    {
        $this->cellRowContentFormat = $cellRowContentFormat;

        return $this;
    }

    /**
     * Gets row cell content format.
     */
    public function getCellRowContentFormat(): string
    {
        return $this->cellRowContentFormat;
    }

    /**
     * Sets table border format.
     *
     * @return $this
     */
    public function setBorderFormat(string $borderFormat): static
    {
        $this->borderFormat = $borderFormat;

        return $this;
    }

    /**
     * Gets table border format.
     */
    public function getBorderFormat(): string
    {
        return $this->borderFormat;
    }

    /**
     * Sets cell padding type.
     *
     * @return $this
     */
    public function setPadType(int $padType): static
    {
        if (!\in_array($padType, [\STR_PAD_LEFT, \STR_PAD_RIGHT, \STR_PAD_BOTH], true)) {
            throw new InvalidArgumentException('Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH).');
        }

        $this->padType = $padType;

        return $this;
    }

    /**
     * Gets cell padding type.
     */
    public function getPadType(): int
    {
        return $this->padType;
    }

    public function getHeaderTitleFormat(): string
    {
        return $this->headerTitleFormat;
    }

    /**
     * @return $this
     */
    public function setHeaderTitleFormat(string $format): static
    {
        $this->headerTitleFormat = $format;

        return $this;
    }

    public function getFooterTitleFormat(): string
    {
        return $this->footerTitleFormat;
    }

    /**
     * @return $this
     */
    public function setFooterTitleFormat(string $format): static
    {
        $this->footerTitleFormat = $format;

        return $this;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * HelperSet represents a set of helpers to be used with a command.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @implements \IteratorAggregate<string, HelperInterface>
 */
class HelperSet implements \IteratorAggregate
{
    /** @var array<string, HelperInterface> */
    private array $helpers = [];

    /**
     * @param HelperInterface[] $helpers
     */
    public function __construct(array $helpers = [])
    {
        foreach ($helpers as $alias => $helper) {
            $this->set($helper, \is_int($alias) ? null : $alias);
        }
    }

    /**
     * @return void
     */
    public function set(HelperInterface $helper, ?string $alias = null)
    {
        $this->helpers[$helper->getName()] = $helper;
        if (null !== $alias) {
            $this->helpers[$alias] = $helper;
        }

        $helper->setHelperSet($this);
    }

    /**
     * Returns true if the helper if defined.
     */
    public function has(string $name): bool
    {
        return isset($this->helpers[$name]);
    }

    /**
     * Gets a helper value.
     *
     * @throws InvalidArgumentException if the helper is not defined
     */
    public function get(string $name): HelperInterface
    {
        if (!$this->has($name)) {
            throw new InvalidArgumentException(\sprintf('The helper "%s" is not defined.', $name));
        }

        return $this->helpers[$name];
    }

    public function getIterator(): \Traversable
    {
        return new \ArrayIterator($this->helpers);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Style\SymfonyStyle;

/**
 * Symfony Style Guide compliant question helper.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
class SymfonyQuestionHelper extends QuestionHelper
{
    /**
     * @return void
     */
    protected function writePrompt(OutputInterface $output, Question $question)
    {
        $text = OutputFormatter::escapeTrailingBackslash($question->getQuestion());
        $default = $question->getDefault();

        if ($question->isMultiline()) {
            $text .= \sprintf(' (press %s to continue)', $this->getEofShortcut($output));
        }

        switch (true) {
            case null === $default:
                $text = \sprintf(' <info>%s</info>:', $text);

                break;

            case $question instanceof ConfirmationQuestion:
                $text = \sprintf(' <info>%s (yes/no)</info> [<comment>%s</comment>]:', $text, $default ? 'yes' : 'no');

                break;

            case $question instanceof ChoiceQuestion && $question->isMultiselect():
                $choices = $question->getChoices();
                $default = explode(',', $default);

                foreach ($default as $key => $value) {
                    $default[$key] = $choices[trim($value)];
                }

                $text = \sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape(implode(', ', $default)));

                break;

            case $question instanceof ChoiceQuestion:
                $choices = $question->getChoices();
                $text = \sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($choices[$default] ?? $default));

                break;

            default:
                $text = \sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($default));
        }

        $output->writeln($text);

        $prompt = ' > ';

        if ($question instanceof ChoiceQuestion) {
            $output->writeln($this->formatChoiceQuestionChoices($question, 'comment'));

            $prompt = $question->getPrompt();
        }

        $output->write($prompt);
    }

    /**
     * @return void
     */
    protected function writeError(OutputInterface $output, \Exception $error)
    {
        if ($output instanceof SymfonyStyle) {
            $output->newLine();
            $output->error($error->getMessage());

            return;
        }

        parent::writeError($output, $error);
    }

    private function getEofShortcut(OutputInterface $output): string
    {
        if ('\\' === \DIRECTORY_SEPARATOR && !$output->isDecorated()) {
            return '<comment>Ctrl+Z</comment> then <comment>Enter</comment>';
        }

        return '<comment>Ctrl+D</comment>';
    }
}
Copyright (c) 2004-present Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Style;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Decorates output to add console style guide helpers.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
abstract class OutputStyle implements OutputInterface, StyleInterface
{
    private OutputInterface $output;

    public function __construct(OutputInterface $output)
    {
        $this->output = $output;
    }

    /**
     * @return void
     */
    public function newLine(int $count = 1)
    {
        $this->output->write(str_repeat(\PHP_EOL, $count));
    }

    public function createProgressBar(int $max = 0): ProgressBar
    {
        return new ProgressBar($this->output, $max);
    }

    /**
     * @return void
     */
    public function write(string|iterable $messages, bool $newline = false, int $type = self::OUTPUT_NORMAL)
    {
        $this->output->write($messages, $newline, $type);
    }

    /**
     * @return void
     */
    public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORMAL)
    {
        $this->output->writeln($messages, $type);
    }

    /**
     * @return void
     */
    public function setVerbosity(int $level)
    {
        $this->output->setVerbosity($level);
    }

    public function getVerbosity(): int
    {
        return $this->output->getVerbosity();
    }

    /**
     * @return void
     */
    public function setDecorated(bool $decorated)
    {
        $this->output->setDecorated($decorated);
    }

    public function isDecorated(): bool
    {
        return $this->output->isDecorated();
    }

    /**
     * @return void
     */
    public function setFormatter(OutputFormatterInterface $formatter)
    {
        $this->output->setFormatter($formatter);
    }

    public function getFormatter(): OutputFormatterInterface
    {
        return $this->output->getFormatter();
    }

    public function isQuiet(): bool
    {
        return $this->output->isQuiet();
    }

    public function isVerbose(): bool
    {
        return $this->output->isVerbose();
    }

    public function isVeryVerbose(): bool
    {
        return $this->output->isVeryVerbose();
    }

    public function isDebug(): bool
    {
        return $this->output->isDebug();
    }

    /**
     * @return OutputInterface
     */
    protected function getErrorOutput()
    {
        if (!$this->output instanceof ConsoleOutputInterface) {
            return $this->output;
        }

        return $this->output->getErrorOutput();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Style;

/**
 * Output style helpers.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
interface StyleInterface
{
    /**
     * Formats a command title.
     *
     * @return void
     */
    public function title(string $message);

    /**
     * Formats a section title.
     *
     * @return void
     */
    public function section(string $message);

    /**
     * Formats a list.
     *
     * @return void
     */
    public function listing(array $elements);

    /**
     * Formats informational text.
     *
     * @return void
     */
    public function text(string|array $message);

    /**
     * Formats a success result bar.
     *
     * @return void
     */
    public function success(string|array $message);

    /**
     * Formats an error result bar.
     *
     * @return void
     */
    public function error(string|array $message);

    /**
     * Formats an warning result bar.
     *
     * @return void
     */
    public function warning(string|array $message);

    /**
     * Formats a note admonition.
     *
     * @return void
     */
    public function note(string|array $message);

    /**
     * Formats a caution admonition.
     *
     * @return void
     */
    public function caution(string|array $message);

    /**
     * Formats a table.
     *
     * @return void
     */
    public function table(array $headers, array $rows);

    /**
     * Asks a question.
     */
    public function ask(string $question, ?string $default = null, ?callable $validator = null): mixed;

    /**
     * Asks a question with the user input hidden.
     */
    public function askHidden(string $question, ?callable $validator = null): mixed;

    /**
     * Asks for confirmation.
     */
    public function confirm(string $question, bool $default = true): bool;

    /**
     * Asks a choice question.
     */
    public function choice(string $question, array $choices, mixed $default = null): mixed;

    /**
     * Add newline(s).
     *
     * @return void
     */
    public function newLine(int $count = 1);

    /**
     * Starts the progress output.
     *
     * @return void
     */
    public function progressStart(int $max = 0);

    /**
     * Advances the progress output X steps.
     *
     * @return void
     */
    public function progressAdvance(int $step = 1);

    /**
     * Finishes the progress output.
     *
     * @return void
     */
    public function progressFinish();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Style;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Helper\OutputWrapper;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Helper\SymfonyQuestionHelper;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableCell;
use Symfony\Component\Console\Helper\TableSeparator;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\TrimmedBufferOutput;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Terminal;

/**
 * Output decorator helpers for the Symfony Style Guide.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
class SymfonyStyle extends OutputStyle
{
    public const MAX_LINE_LENGTH = 120;

    private InputInterface $input;
    private OutputInterface $output;
    private SymfonyQuestionHelper $questionHelper;
    private ProgressBar $progressBar;
    private int $lineLength;
    private TrimmedBufferOutput $bufferedOutput;

    public function __construct(InputInterface $input, OutputInterface $output)
    {
        $this->input = $input;
        $this->bufferedOutput = new TrimmedBufferOutput(\DIRECTORY_SEPARATOR === '\\' ? 4 : 2, $output->getVerbosity(), false, clone $output->getFormatter());
        // Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not.
        $width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH;
        $this->lineLength = min($width - (int) (\DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH);

        parent::__construct($this->output = $output);
    }

    /**
     * Formats a message as a block of text.
     *
     * @return void
     */
    public function block(string|array $messages, ?string $type = null, ?string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = true)
    {
        $messages = \is_array($messages) ? array_values($messages) : [$messages];

        $this->autoPrependBlock();
        $this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding, $escape));
        $this->newLine();
    }

    /**
     * @return void
     */
    public function title(string $message)
    {
        $this->autoPrependBlock();
        $this->writeln([
            \sprintf('<comment>%s</>', OutputFormatter::escapeTrailingBackslash($message)),
            \sprintf('<comment>%s</>', str_repeat('=', Helper::width(Helper::removeDecoration($this->getFormatter(), $message)))),
        ]);
        $this->newLine();
    }

    /**
     * @return void
     */
    public function section(string $message)
    {
        $this->autoPrependBlock();
        $this->writeln([
            \sprintf('<comment>%s</>', OutputFormatter::escapeTrailingBackslash($message)),
            \sprintf('<comment>%s</>', str_repeat('-', Helper::width(Helper::removeDecoration($this->getFormatter(), $message)))),
        ]);
        $this->newLine();
    }

    /**
     * @return void
     */
    public function listing(array $elements)
    {
        $this->autoPrependText();
        $elements = array_map(static fn ($element) => \sprintf(' * %s', $element), $elements);

        $this->writeln($elements);
        $this->newLine();
    }

    /**
     * @return void
     */
    public function text(string|array $message)
    {
        $this->autoPrependText();

        $messages = \is_array($message) ? array_values($message) : [$message];
        foreach ($messages as $message) {
            $this->writeln(\sprintf(' %s', $message));
        }
    }

    /**
     * Formats a command comment.
     *
     * @return void
     */
    public function comment(string|array $message)
    {
        $this->block($message, null, null, '<fg=default;bg=default> // </>', false, false);
    }

    /**
     * @return void
     */
    public function success(string|array $message)
    {
        $this->block($message, 'OK', 'fg=black;bg=green', ' ', true);
    }

    /**
     * @return void
     */
    public function error(string|array $message)
    {
        $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true);
    }

    /**
     * @return void
     */
    public function warning(string|array $message)
    {
        $this->block($message, 'WARNING', 'fg=black;bg=yellow', ' ', true);
    }

    /**
     * @return void
     */
    public function note(string|array $message)
    {
        $this->block($message, 'NOTE', 'fg=yellow', ' ! ');
    }

    /**
     * Formats an info message.
     *
     * @return void
     */
    public function info(string|array $message)
    {
        $this->block($message, 'INFO', 'fg=green', ' ', true);
    }

    /**
     * @return void
     */
    public function caution(string|array $message)
    {
        $this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true);
    }

    /**
     * @return void
     */
    public function table(array $headers, array $rows)
    {
        $this->createTable()
            ->setHeaders($headers)
            ->setRows($rows)
            ->render()
        ;

        $this->newLine();
    }

    /**
     * Formats a horizontal table.
     *
     * @return void
     */
    public function horizontalTable(array $headers, array $rows)
    {
        $this->createTable()
            ->setHorizontal(true)
            ->setHeaders($headers)
            ->setRows($rows)
            ->render()
        ;

        $this->newLine();
    }

    /**
     * Formats a list of key/value horizontally.
     *
     * Each row can be one of:
     * * 'A title'
     * * ['key' => 'value']
     * * new TableSeparator()
     *
     * @return void
     */
    public function definitionList(string|array|TableSeparator ...$list)
    {
        $headers = [];
        $row = [];
        foreach ($list as $value) {
            if ($value instanceof TableSeparator) {
                $headers[] = $value;
                $row[] = $value;
                continue;
            }
            if (\is_string($value)) {
                $headers[] = new TableCell($value, ['colspan' => 2]);
                $row[] = null;
                continue;
            }
            if (!\is_array($value)) {
                throw new InvalidArgumentException('Value should be an array, string, or an instance of TableSeparator.');
            }
            $headers[] = key($value);
            $row[] = current($value);
        }

        $this->horizontalTable($headers, [$row]);
    }

    public function ask(string $question, ?string $default = null, ?callable $validator = null): mixed
    {
        $question = new Question($question, $default);
        $question->setValidator($validator);

        return $this->askQuestion($question);
    }

    public function askHidden(string $question, ?callable $validator = null): mixed
    {
        $question = new Question($question);

        $question->setHidden(true);
        $question->setValidator($validator);

        return $this->askQuestion($question);
    }

    public function confirm(string $question, bool $default = true): bool
    {
        return $this->askQuestion(new ConfirmationQuestion($question, $default));
    }

    public function choice(string $question, array $choices, mixed $default = null, bool $multiSelect = false): mixed
    {
        if (null !== $default) {
            $values = array_flip($choices);
            $default = $values[$default] ?? $default;
        }

        $questionChoice = new ChoiceQuestion($question, $choices, $default);
        $questionChoice->setMultiselect($multiSelect);

        return $this->askQuestion($questionChoice);
    }

    /**
     * @return void
     */
    public function progressStart(int $max = 0)
    {
        $this->progressBar = $this->createProgressBar($max);
        $this->progressBar->start();
    }

    /**
     * @return void
     */
    public function progressAdvance(int $step = 1)
    {
        $this->getProgressBar()->advance($step);
    }

    /**
     * @return void
     */
    public function progressFinish()
    {
        $this->getProgressBar()->finish();
        $this->newLine(2);
        unset($this->progressBar);
    }

    public function createProgressBar(int $max = 0): ProgressBar
    {
        $progressBar = parent::createProgressBar($max);

        if ('\\' !== \DIRECTORY_SEPARATOR || 'Hyper' === getenv('TERM_PROGRAM')) {
            $progressBar->setEmptyBarCharacter('░'); // light shade character \u2591
            $progressBar->setProgressCharacter('');
            $progressBar->setBarCharacter('▓'); // dark shade character \u2593
        }

        return $progressBar;
    }

    /**
     * @see ProgressBar::iterate()
     *
     * @template TKey
     * @template TValue
     *
     * @param iterable<TKey, TValue> $iterable
     * @param int|null               $max      Number of steps to complete the bar (0 if indeterminate), if null it will be inferred from $iterable
     *
     * @return iterable<TKey, TValue>
     */
    public function progressIterate(iterable $iterable, ?int $max = null): iterable
    {
        yield from $this->createProgressBar()->iterate($iterable, $max);

        $this->newLine(2);
    }

    public function askQuestion(Question $question): mixed
    {
        if ($this->input->isInteractive()) {
            $this->autoPrependBlock();
        }

        $this->questionHelper ??= new SymfonyQuestionHelper();

        $answer = $this->questionHelper->ask($this->input, $this, $question);

        if ($this->input->isInteractive()) {
            if ($this->output instanceof ConsoleSectionOutput) {
                // add the new line of the `return` to submit the input to ConsoleSectionOutput, because ConsoleSectionOutput is holding all it's lines.
                // this is relevant when a `ConsoleSectionOutput::clear` is called.
                $this->output->addNewLineOfInputSubmit();
            }
            $this->newLine();
            $this->bufferedOutput->write("\n");
        }

        return $answer;
    }

    /**
     * @return void
     */
    public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORMAL)
    {
        if (!is_iterable($messages)) {
            $messages = [$messages];
        }

        foreach ($messages as $message) {
            parent::writeln($message, $type);
            $this->writeBuffer($message, true, $type);
        }
    }

    /**
     * @return void
     */
    public function write(string|iterable $messages, bool $newline = false, int $type = self::OUTPUT_NORMAL)
    {
        if (!is_iterable($messages)) {
            $messages = [$messages];
        }

        foreach ($messages as $message) {
            parent::write($message, $newline, $type);
            $this->writeBuffer($message, $newline, $type);
        }
    }

    /**
     * @return void
     */
    public function newLine(int $count = 1)
    {
        parent::newLine($count);
        $this->bufferedOutput->write(str_repeat("\n", $count));
    }

    /**
     * Returns a new instance which makes use of stderr if available.
     */
    public function getErrorStyle(): self
    {
        return new self($this->input, $this->getErrorOutput());
    }

    public function createTable(): Table
    {
        $output = $this->output instanceof ConsoleOutputInterface ? $this->output->section() : $this->output;
        $style = clone Table::getStyleDefinition('symfony-style-guide');
        $style->setCellHeaderFormat('<info>%s</info>');

        return (new Table($output))->setStyle($style);
    }

    private function getProgressBar(): ProgressBar
    {
        return $this->progressBar
            ?? throw new RuntimeException('The ProgressBar is not started.');
    }

    private function autoPrependBlock(): void
    {
        $chars = substr(str_replace(\PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2);

        if (!isset($chars[0])) {
            $this->newLine(); // empty history, so we should start with a new line.

            return;
        }
        // Prepend new line for each non LF chars (This means no blank line was output before)
        $this->newLine(2 - substr_count($chars, "\n"));
    }

    private function autoPrependText(): void
    {
        $fetched = $this->bufferedOutput->fetch();
        // Prepend new line if last char isn't EOL:
        if ($fetched && !str_ends_with($fetched, "\n")) {
            $this->newLine();
        }
    }

    private function writeBuffer(string $message, bool $newLine, int $type): void
    {
        // We need to know if the last chars are PHP_EOL
        $this->bufferedOutput->write($message, $newLine, $type);
    }

    private function createBlock(iterable $messages, ?string $type = null, ?string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = false): array
    {
        $indentLength = 0;
        $prefixLength = Helper::width(Helper::removeDecoration($this->getFormatter(), $prefix));
        $lines = [];

        if (null !== $type) {
            $type = \sprintf('[%s] ', $type);
            $indentLength = Helper::width($type);
            $lineIndentation = str_repeat(' ', $indentLength);
        }

        // wrap and add newlines for each element
        $outputWrapper = new OutputWrapper();
        foreach ($messages as $key => $message) {
            if ($escape) {
                $message = OutputFormatter::escape($message);
            }

            $message = str_replace("\r\n", "\n", $message);

            $lines = array_merge(
                $lines,
                explode("\n", $outputWrapper->wrap(
                    $message,
                    $this->lineLength - $prefixLength - $indentLength,
                    "\n"
                ))
            );

            if (\count($messages) > 1 && $key < \count($messages) - 1) {
                $lines[] = '';
            }
        }

        $firstLineIndex = 0;
        if ($padding && $this->isDecorated()) {
            $firstLineIndex = 1;
            array_unshift($lines, '');
            $lines[] = '';
        }

        foreach ($lines as $i => &$line) {
            if (null !== $type) {
                $line = $firstLineIndex === $i ? $type.$line : $lineIndentation.$line;
            }

            $line = $prefix.$line;
            $line .= str_repeat(' ', max($this->lineLength - Helper::width(Helper::removeDecoration($this->getFormatter(), $line)), 0));

            if ($style) {
                $line = \sprintf('<%s>%s</>', $style, $line);
            }
        }

        return $lines;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console;

use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Pierre du Plessis <pdples@gmail.com>
 */
final class Cursor
{
    private OutputInterface $output;
    /** @var resource */
    private $input;

    /**
     * @param resource|null $input
     */
    public function __construct(OutputInterface $output, $input = null)
    {
        $this->output = $output;
        $this->input = $input ?? (\defined('STDIN') ? \STDIN : fopen('php://input', 'r+'));
    }

    /**
     * @return $this
     */
    public function moveUp(int $lines = 1): static
    {
        $this->output->write(\sprintf("\x1b[%dA", $lines));

        return $this;
    }

    /**
     * @return $this
     */
    public function moveDown(int $lines = 1): static
    {
        $this->output->write(\sprintf("\x1b[%dB", $lines));

        return $this;
    }

    /**
     * @return $this
     */
    public function moveRight(int $columns = 1): static
    {
        $this->output->write(\sprintf("\x1b[%dC", $columns));

        return $this;
    }

    /**
     * @return $this
     */
    public function moveLeft(int $columns = 1): static
    {
        $this->output->write(\sprintf("\x1b[%dD", $columns));

        return $this;
    }

    /**
     * @return $this
     */
    public function moveToColumn(int $column): static
    {
        $this->output->write(\sprintf("\x1b[%dG", $column));

        return $this;
    }

    /**
     * @return $this
     */
    public function moveToPosition(int $column, int $row): static
    {
        $this->output->write(\sprintf("\x1b[%d;%dH", $row + 1, $column));

        return $this;
    }

    /**
     * @return $this
     */
    public function savePosition(): static
    {
        $this->output->write("\x1b7");

        return $this;
    }

    /**
     * @return $this
     */
    public function restorePosition(): static
    {
        $this->output->write("\x1b8");

        return $this;
    }

    /**
     * @return $this
     */
    public function hide(): static
    {
        $this->output->write("\x1b[?25l");

        return $this;
    }

    /**
     * @return $this
     */
    public function show(): static
    {
        $this->output->write("\x1b[?25h\x1b[?0c");

        return $this;
    }

    /**
     * Clears all the output from the current line.
     *
     * @return $this
     */
    public function clearLine(): static
    {
        $this->output->write("\x1b[2K");

        return $this;
    }

    /**
     * Clears all the output from the current line after the current position.
     */
    public function clearLineAfter(): self
    {
        $this->output->write("\x1b[K");

        return $this;
    }

    /**
     * Clears all the output from the cursors' current position to the end of the screen.
     *
     * @return $this
     */
    public function clearOutput(): static
    {
        $this->output->write("\x1b[0J");

        return $this;
    }

    /**
     * Clears the entire screen.
     *
     * @return $this
     */
    public function clearScreen(): static
    {
        $this->output->write("\x1b[2J");

        return $this;
    }

    /**
     * Returns the current cursor position as x,y coordinates.
     */
    public function getCurrentPosition(): array
    {
        static $isTtySupported;

        if (!$isTtySupported ??= '/' === \DIRECTORY_SEPARATOR && stream_isatty(\STDOUT)) {
            return [1, 1];
        }

        $sttyMode = shell_exec('stty -g');
        shell_exec('stty -icanon -echo');

        @fwrite($this->input, "\033[6n");

        $code = trim(fread($this->input, 1024));

        shell_exec(\sprintf('stty %s', $sttyMode));

        sscanf($code, "\033[%d;%dR", $row, $col);

        return [$col, $row];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\SignalRegistry;

/**
 * @author Grégoire Pineau <lyrixx@lyrixx.info>
 */
class SignalMap
{
    private static array $map;

    public static function getSignalName(int $signal): ?string
    {
        if (!\extension_loaded('pcntl')) {
            return null;
        }

        if (!isset(self::$map)) {
            $r = new \ReflectionExtension('pcntl');
            $c = $r->getConstants();
            $map = array_filter($c, static fn ($k) => str_starts_with($k, 'SIG') && !str_starts_with($k, 'SIG_') && 'SIGBABY' !== $k, \ARRAY_FILTER_USE_KEY);
            self::$map = array_flip($map);
        }

        return self::$map[$signal] ?? null;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\SignalRegistry;

final class SignalRegistry
{
    /**
     * @var array<int, array<callable>>
     */
    private array $signalHandlers = [];

    /**
     * @var array<array<int, array<callable>>>
     */
    private array $stack = [];

    /**
     * @var array<int, callable|int|string>
     */
    private array $originalHandlers = [];

    public function __construct()
    {
        if (\function_exists('pcntl_async_signals')) {
            pcntl_async_signals(true);
        }
    }

    public function register(int $signal, callable $signalHandler): void
    {
        $previous = pcntl_signal_get_handler($signal);

        if (!isset($this->originalHandlers[$signal])) {
            $this->originalHandlers[$signal] = $previous;
        }

        if (!isset($this->signalHandlers[$signal])) {
            if (\is_callable($previous) && [$this, 'handle'] !== $previous) {
                $this->signalHandlers[$signal][] = $previous;
            }
        }

        $this->signalHandlers[$signal][] = $signalHandler;

        pcntl_signal($signal, [$this, 'handle']);
    }

    public static function isSupported(): bool
    {
        return \function_exists('pcntl_signal');
    }

    /**
     * @internal
     */
    public function handle(int $signal): void
    {
        $count = \count($this->signalHandlers[$signal]);

        foreach ($this->signalHandlers[$signal] as $i => $signalHandler) {
            $hasNext = $i !== $count - 1;
            $signalHandler($signal, $hasNext);
        }
    }

    /**
     * Pushes the current active handlers onto the stack and clears the active list.
     *
     * This prepares the registry for a new set of handlers within a specific scope.
     *
     * @internal
     */
    public function pushCurrentHandlers(): void
    {
        $this->stack[] = $this->signalHandlers;
        $this->signalHandlers = [];
    }

    /**
     * Restores the previous handlers from the stack, making them active.
     *
     * This also restores the original OS-level signal handler if no
     * more handlers are registered for a signal that was just popped.
     *
     * @internal
     */
    public function popPreviousHandlers(): void
    {
        $popped = $this->signalHandlers;
        $this->signalHandlers = array_pop($this->stack) ?? [];

        // Restore OS handler if no more Symfony handlers for this signal
        foreach ($popped as $signal => $handlers) {
            if (!($this->signalHandlers[$signal] ?? false) && isset($this->originalHandlers[$signal])) {
                pcntl_signal($signal, $this->originalHandlers[$signal]);
            }
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Completion;

/**
 * Represents a single suggested value.
 *
 * @author Wouter de Jong <wouter@wouterj.nl>
 */
class Suggestion implements \Stringable
{
    public function __construct(
        private readonly string $value,
        private readonly string $description = '',
    ) {
    }

    public function getValue(): string
    {
        return $this->value;
    }

    public function getDescription(): string
    {
        return $this->description;
    }

    public function __toString(): string
    {
        return $this->getValue();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Completion;

use Symfony\Component\Console\Input\InputOption;

/**
 * Stores all completion suggestions for the current input.
 *
 * @author Wouter de Jong <wouter@wouterj.nl>
 */
final class CompletionSuggestions
{
    private array $valueSuggestions = [];
    private array $optionSuggestions = [];

    /**
     * Add a suggested value for an input option or argument.
     *
     * @return $this
     */
    public function suggestValue(string|Suggestion $value): static
    {
        $this->valueSuggestions[] = !$value instanceof Suggestion ? new Suggestion($value) : $value;

        return $this;
    }

    /**
     * Add multiple suggested values at once for an input option or argument.
     *
     * @param list<string|Suggestion> $values
     *
     * @return $this
     */
    public function suggestValues(array $values): static
    {
        foreach ($values as $value) {
            $this->suggestValue($value);
        }

        return $this;
    }

    /**
     * Add a suggestion for an input option name.
     *
     * @return $this
     */
    public function suggestOption(InputOption $option): static
    {
        $this->optionSuggestions[] = $option;

        return $this;
    }

    /**
     * Add multiple suggestions for input option names at once.
     *
     * @param InputOption[] $options
     *
     * @return $this
     */
    public function suggestOptions(array $options): static
    {
        foreach ($options as $option) {
            $this->suggestOption($option);
        }

        return $this;
    }

    /**
     * @return InputOption[]
     */
    public function getOptionSuggestions(): array
    {
        return $this->optionSuggestions;
    }

    /**
     * @return Suggestion[]
     */
    public function getValueSuggestions(): array
    {
        return $this->valueSuggestions;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Completion\Output;

use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Wouter de Jong <wouter@wouterj.nl>
 */
class BashCompletionOutput implements CompletionOutputInterface
{
    public function write(CompletionSuggestions $suggestions, OutputInterface $output): void
    {
        $values = $suggestions->getValueSuggestions();
        foreach ($suggestions->getOptionSuggestions() as $option) {
            $values[] = '--'.$option->getName();
            if ($option->isNegatable()) {
                $values[] = '--no-'.$option->getName();
            }
        }
        $output->writeln(implode("\n", $values));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Completion\Output;

use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jitendra A <adhocore@gmail.com>
 */
class ZshCompletionOutput implements CompletionOutputInterface
{
    public function write(CompletionSuggestions $suggestions, OutputInterface $output): void
    {
        $values = [];
        foreach ($suggestions->getValueSuggestions() as $value) {
            $values[] = $value->getValue().($value->getDescription() ? "\t".$value->getDescription() : '');
        }
        foreach ($suggestions->getOptionSuggestions() as $option) {
            $values[] = '--'.$option->getName().($option->getDescription() ? "\t".$option->getDescription() : '');
            if ($option->isNegatable()) {
                $values[] = '--no-'.$option->getName().($option->getDescription() ? "\t".$option->getDescription() : '');
            }
        }
        $output->write(implode("\n", $values)."\n");
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Completion\Output;

use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Guillaume Aveline <guillaume.aveline@pm.me>
 */
class FishCompletionOutput implements CompletionOutputInterface
{
    public function write(CompletionSuggestions $suggestions, OutputInterface $output): void
    {
        $values = $suggestions->getValueSuggestions();
        foreach ($suggestions->getOptionSuggestions() as $option) {
            $values[] = '--'.$option->getName();
            if ($option->isNegatable()) {
                $values[] = '--no-'.$option->getName();
            }
        }
        $output->write(implode("\n", $values));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Completion\Output;

use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Transforms the {@see CompletionSuggestions} object into output readable by the shell completion.
 *
 * @author Wouter de Jong <wouter@wouterj.nl>
 */
interface CompletionOutputInterface
{
    public function write(CompletionSuggestions $suggestions, OutputInterface $output): void;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Completion;

use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;

/**
 * An input specialized for shell completion.
 *
 * This input allows unfinished option names or values and exposes what kind of
 * completion is expected.
 *
 * @author Wouter de Jong <wouter@wouterj.nl>
 */
final class CompletionInput extends ArgvInput
{
    public const TYPE_ARGUMENT_VALUE = 'argument_value';
    public const TYPE_OPTION_VALUE = 'option_value';
    public const TYPE_OPTION_NAME = 'option_name';
    public const TYPE_NONE = 'none';

    private array $tokens;
    private int $currentIndex;
    private string $completionType;
    private ?string $completionName = null;
    private string $completionValue = '';

    /**
     * Converts a terminal string into tokens.
     *
     * This is required for shell completions without COMP_WORDS support.
     */
    public static function fromString(string $inputStr, int $currentIndex): self
    {
        preg_match_all('/(?<=^|\s)([\'"]?)(.+?)(?<!\\\\)\1(?=$|\s)/', $inputStr, $tokens);

        return self::fromTokens($tokens[0], $currentIndex);
    }

    /**
     * Create an input based on an COMP_WORDS token list.
     *
     * @param string[] $tokens       the set of split tokens (e.g. COMP_WORDS or argv)
     * @param int      $currentIndex the index of the cursor (e.g. COMP_CWORD)
     */
    public static function fromTokens(array $tokens, int $currentIndex): self
    {
        $input = new self($tokens);
        $input->tokens = $tokens;
        $input->currentIndex = $currentIndex;

        return $input;
    }

    public function bind(InputDefinition $definition): void
    {
        parent::bind($definition);

        $relevantToken = $this->getRelevantToken();
        if ('-' === $relevantToken[0]) {
            // the current token is an input option: complete either option name or option value
            [$optionToken, $optionValue] = explode('=', $relevantToken, 2) + ['', ''];

            $option = $this->getOptionFromToken($optionToken);
            if (null === $option && !$this->isCursorFree()) {
                $this->completionType = self::TYPE_OPTION_NAME;
                $this->completionValue = $relevantToken;

                return;
            }

            if ($option?->acceptValue()) {
                $this->completionType = self::TYPE_OPTION_VALUE;
                $this->completionName = $option->getName();
                $this->completionValue = $optionValue ?: (!str_starts_with($optionToken, '--') ? substr($optionToken, 2) : '');

                return;
            }
        }

        $previousToken = $this->tokens[$this->currentIndex - 1];
        if ('-' === $previousToken[0] && '' !== trim($previousToken, '-')) {
            // check if previous option accepted a value
            $previousOption = $this->getOptionFromToken($previousToken);
            if ($previousOption?->acceptValue()) {
                $this->completionType = self::TYPE_OPTION_VALUE;
                $this->completionName = $previousOption->getName();
                $this->completionValue = $relevantToken;

                return;
            }
        }

        // complete argument value
        $this->completionType = self::TYPE_ARGUMENT_VALUE;

        foreach ($this->definition->getArguments() as $argumentName => $argument) {
            if (!isset($this->arguments[$argumentName])) {
                break;
            }

            $argumentValue = $this->arguments[$argumentName];
            $this->completionName = $argumentName;
            if (\is_array($argumentValue)) {
                $this->completionValue = $argumentValue ? $argumentValue[array_key_last($argumentValue)] : null;
            } else {
                $this->completionValue = $argumentValue;
            }
        }

        if ($this->currentIndex >= \count($this->tokens)) {
            if (!isset($this->arguments[$argumentName]) || $this->definition->getArgument($argumentName)->isArray()) {
                $this->completionName = $argumentName;
                $this->completionValue = '';
            } else {
                // we've reached the end
                $this->completionType = self::TYPE_NONE;
                $this->completionName = null;
                $this->completionValue = '';
            }
        }
    }

    /**
     * Returns the type of completion required.
     *
     * TYPE_ARGUMENT_VALUE when completing the value of an input argument
     * TYPE_OPTION_VALUE   when completing the value of an input option
     * TYPE_OPTION_NAME    when completing the name of an input option
     * TYPE_NONE           when nothing should be completed
     *
     * TYPE_OPTION_NAME and TYPE_NONE are already implemented by the Console component.
     *
     * @return self::TYPE_*
     */
    public function getCompletionType(): string
    {
        return $this->completionType;
    }

    /**
     * The name of the input option or argument when completing a value.
     *
     * @return string|null returns null when completing an option name
     */
    public function getCompletionName(): ?string
    {
        return $this->completionName;
    }

    /**
     * The value already typed by the user (or empty string).
     */
    public function getCompletionValue(): string
    {
        return $this->completionValue;
    }

    public function mustSuggestOptionValuesFor(string $optionName): bool
    {
        return self::TYPE_OPTION_VALUE === $this->getCompletionType() && $optionName === $this->getCompletionName();
    }

    public function mustSuggestArgumentValuesFor(string $argumentName): bool
    {
        return self::TYPE_ARGUMENT_VALUE === $this->getCompletionType() && $argumentName === $this->getCompletionName();
    }

    protected function parseToken(string $token, bool $parseOptions): bool
    {
        try {
            return parent::parseToken($token, $parseOptions);
        } catch (RuntimeException) {
            // suppress errors, completed input is almost never valid
        }

        return $parseOptions;
    }

    private function getOptionFromToken(string $optionToken): ?InputOption
    {
        $optionName = ltrim($optionToken, '-');
        if (!$optionName) {
            return null;
        }

        if ('-' === ($optionToken[1] ?? ' ')) {
            // long option name
            return $this->definition->hasOption($optionName) ? $this->definition->getOption($optionName) : null;
        }

        // short option name
        return $this->definition->hasShortcut($optionName[0]) ? $this->definition->getOptionForShortcut($optionName[0]) : null;
    }

    /**
     * The token of the cursor, or the last token if the cursor is at the end of the input.
     */
    private function getRelevantToken(): string
    {
        return $this->tokens[$this->isCursorFree() ? $this->currentIndex - 1 : $this->currentIndex];
    }

    /**
     * Whether the cursor is "free" (i.e. at the end of the input preceded by a space).
     */
    private function isCursorFree(): bool
    {
        $nrOfTokens = \count($this->tokens);
        if ($this->currentIndex > $nrOfTokens) {
            throw new \LogicException('Current index is invalid, it must be the number of input tokens or one more.');
        }

        return $this->currentIndex >= $nrOfTokens;
    }

    public function __toString()
    {
        $str = '';
        foreach ($this->tokens as $i => $token) {
            $str .= $token;

            if ($this->currentIndex === $i) {
                $str .= '|';
            }

            $str .= ' ';
        }

        if ($this->currentIndex > $i) {
            $str .= '|';
        }

        return rtrim($str);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * @author Fabien Potencier <fabien@symfony.com>
 */
final class Color
{
    private const COLORS = [
        'black' => 0,
        'red' => 1,
        'green' => 2,
        'yellow' => 3,
        'blue' => 4,
        'magenta' => 5,
        'cyan' => 6,
        'white' => 7,
        'default' => 9,
    ];

    private const BRIGHT_COLORS = [
        'gray' => 0,
        'bright-red' => 1,
        'bright-green' => 2,
        'bright-yellow' => 3,
        'bright-blue' => 4,
        'bright-magenta' => 5,
        'bright-cyan' => 6,
        'bright-white' => 7,
    ];

    private const AVAILABLE_OPTIONS = [
        'bold' => ['set' => 1, 'unset' => 22],
        'underscore' => ['set' => 4, 'unset' => 24],
        'blink' => ['set' => 5, 'unset' => 25],
        'reverse' => ['set' => 7, 'unset' => 27],
        'conceal' => ['set' => 8, 'unset' => 28],
    ];

    private string $foreground;
    private string $background;
    private array $options = [];

    public function __construct(string $foreground = '', string $background = '', array $options = [])
    {
        $this->foreground = $this->parseColor($foreground);
        $this->background = $this->parseColor($background, true);

        foreach ($options as $option) {
            if (!isset(self::AVAILABLE_OPTIONS[$option])) {
                throw new InvalidArgumentException(\sprintf('Invalid option specified: "%s". Expected one of (%s).', $option, implode(', ', array_keys(self::AVAILABLE_OPTIONS))));
            }

            $this->options[$option] = self::AVAILABLE_OPTIONS[$option];
        }
    }

    public function apply(string $text): string
    {
        return $this->set().$text.$this->unset();
    }

    public function set(): string
    {
        $setCodes = [];
        if ('' !== $this->foreground) {
            $setCodes[] = $this->foreground;
        }
        if ('' !== $this->background) {
            $setCodes[] = $this->background;
        }
        foreach ($this->options as $option) {
            $setCodes[] = $option['set'];
        }
        if (0 === \count($setCodes)) {
            return '';
        }

        return \sprintf("\033[%sm", implode(';', $setCodes));
    }

    public function unset(): string
    {
        $unsetCodes = [];
        if ('' !== $this->foreground) {
            $unsetCodes[] = 39;
        }
        if ('' !== $this->background) {
            $unsetCodes[] = 49;
        }
        foreach ($this->options as $option) {
            $unsetCodes[] = $option['unset'];
        }
        if (0 === \count($unsetCodes)) {
            return '';
        }

        return \sprintf("\033[%sm", implode(';', $unsetCodes));
    }

    private function parseColor(string $color, bool $background = false): string
    {
        if ('' === $color) {
            return '';
        }

        if ('#' === $color[0]) {
            return ($background ? '4' : '3').Terminal::getColorMode()->convertFromHexToAnsiColorCode($color);
        }

        if (isset(self::COLORS[$color])) {
            return ($background ? '4' : '3').self::COLORS[$color];
        }

        if (isset(self::BRIGHT_COLORS[$color])) {
            return ($background ? '10' : '9').self::BRIGHT_COLORS[$color];
        }

        throw new InvalidArgumentException(\sprintf('Invalid "%s" color; expected one of (%s).', $color, implode(', ', array_merge(array_keys(self::COLORS), array_keys(self::BRIGHT_COLORS)))));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Completion\Output\BashCompletionOutput;
use Symfony\Component\Console\Completion\Output\CompletionOutputInterface;
use Symfony\Component\Console\Completion\Output\FishCompletionOutput;
use Symfony\Component\Console\Completion\Output\ZshCompletionOutput;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Responsible for providing the values to the shell completion.
 *
 * @author Wouter de Jong <wouter@wouterj.nl>
 */
#[AsCommand(name: '|_complete', description: 'Internal command to provide shell completion suggestions')]
final class CompleteCommand extends Command
{
    public const COMPLETION_API_VERSION = '1';

    /**
     * @deprecated since Symfony 6.1
     */
    protected static $defaultName = '|_complete';

    /**
     * @deprecated since Symfony 6.1
     */
    protected static $defaultDescription = 'Internal command to provide shell completion suggestions';

    private array $completionOutputs;

    private bool $isDebug = false;

    /**
     * @param array<string, class-string<CompletionOutputInterface>> $completionOutputs A list of additional completion outputs, with shell name as key and FQCN as value
     */
    public function __construct(array $completionOutputs = [])
    {
        // must be set before the parent constructor, as the property value is used in configure()
        $this->completionOutputs = $completionOutputs + [
            'bash' => BashCompletionOutput::class,
            'fish' => FishCompletionOutput::class,
            'zsh' => ZshCompletionOutput::class,
        ];

        parent::__construct();
    }

    protected function configure(): void
    {
        $this
            ->addOption('shell', 's', InputOption::VALUE_REQUIRED, 'The shell type ("'.implode('", "', array_keys($this->completionOutputs)).'")')
            ->addOption('input', 'i', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'An array of input tokens (e.g. COMP_WORDS or argv)')
            ->addOption('current', 'c', InputOption::VALUE_REQUIRED, 'The index of the "input" array that the cursor is in (e.g. COMP_CWORD)')
            ->addOption('api-version', 'a', InputOption::VALUE_REQUIRED, 'The API version of the completion script')
            ->addOption('symfony', 'S', InputOption::VALUE_REQUIRED, 'deprecated')
        ;
    }

    protected function initialize(InputInterface $input, OutputInterface $output): void
    {
        $this->isDebug = filter_var(getenv('SYMFONY_COMPLETION_DEBUG'), \FILTER_VALIDATE_BOOL);
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        try {
            // "symfony" must be kept for compat with the shell scripts generated by Symfony Console 5.4 - 6.1
            $version = $input->getOption('symfony') ? '1' : $input->getOption('api-version');
            if ($version && version_compare($version, self::COMPLETION_API_VERSION, '<')) {
                $message = \sprintf('Completion script version is not supported ("%s" given, ">=%s" required).', $version, self::COMPLETION_API_VERSION);
                $this->log($message);

                $output->writeln($message.' Install the Symfony completion script again by using the "completion" command.');

                return 126;
            }

            $shell = $input->getOption('shell');
            if (!$shell) {
                throw new \RuntimeException('The "--shell" option must be set.');
            }

            if (!$completionOutput = $this->completionOutputs[$shell] ?? false) {
                throw new \RuntimeException(\sprintf('Shell completion is not supported for your shell: "%s" (supported: "%s").', $shell, implode('", "', array_keys($this->completionOutputs))));
            }

            $completionInput = $this->createCompletionInput($input);
            $suggestions = new CompletionSuggestions();

            $this->log([
                '',
                '<comment>'.date('Y-m-d H:i:s').'</>',
                '<info>Input:</> <comment>("|" indicates the cursor position)</>',
                '  '.(string) $completionInput,
                '<info>Command:</>',
                '  '.(string) implode(' ', $_SERVER['argv']),
                '<info>Messages:</>',
            ]);

            if ($command = $this->findCommand($completionInput, $output)) {
                $command->mergeApplicationDefinition();
                $completionInput->bind($command->getDefinition());
            }

            if (null === $command) {
                $this->log('  No command found, completing using the Application class.');

                $this->getApplication()->complete($completionInput, $suggestions);
            } elseif (
                $completionInput->mustSuggestArgumentValuesFor('command')
            ) {
                $this->log('  Command found, completing command name.');

                // expand shortcut names ("cache:cl<TAB>") into their full name ("cache:clear")
                $commandNames = array_filter(array_merge([$command->getName()], $command->getAliases()));
                foreach ($commandNames as $name) {
                    if (str_starts_with($name, $completionInput->getCompletionValue())) {
                        $commandNames = [$name];
                        break;
                    }
                }
                $suggestions->suggestValues($commandNames);
            } else {
                if (CompletionInput::TYPE_OPTION_NAME === $completionInput->getCompletionType()) {
                    $this->log('  Completing option names for the <comment>'.($command instanceof LazyCommand ? $command->getCommand() : $command)::class.'</> command.');

                    $suggestions->suggestOptions($command->getDefinition()->getOptions());
                } else {
                    $this->log([
                        '  Completing using the <comment>'.($command instanceof LazyCommand ? $command->getCommand() : $command)::class.'</> class.',
                        '  Completing <comment>'.$completionInput->getCompletionType().'</> for <comment>'.$completionInput->getCompletionName().'</>',
                    ]);
                    if (null !== $compval = $completionInput->getCompletionValue()) {
                        $this->log('  Current value: <comment>'.$compval.'</>');
                    }

                    $command->complete($completionInput, $suggestions);
                }
            }

            /** @var CompletionOutputInterface $completionOutput */
            $completionOutput = new $completionOutput();

            $this->log('<info>Suggestions:</>');
            if ($options = $suggestions->getOptionSuggestions()) {
                $this->log('  --'.implode(' --', array_map(static fn ($o) => $o->getName(), $options)));
            } elseif ($values = $suggestions->getValueSuggestions()) {
                $this->log('  '.implode(' ', $values));
            } else {
                $this->log('  <comment>No suggestions were provided</>');
            }

            $completionOutput->write($suggestions, $output);
        } catch (\Throwable $e) {
            $this->log([
                '<error>Error!</error>',
                (string) $e,
            ]);

            if ($output->isDebug()) {
                throw $e;
            }

            return 2;
        }

        return 0;
    }

    private function createCompletionInput(InputInterface $input): CompletionInput
    {
        $currentIndex = $input->getOption('current');
        if (!$currentIndex || !ctype_digit($currentIndex)) {
            throw new \RuntimeException('The "--current" option must be set and it must be an integer.');
        }

        $completionInput = CompletionInput::fromTokens($input->getOption('input'), (int) $currentIndex);

        try {
            $completionInput->bind($this->getApplication()->getDefinition());
        } catch (ExceptionInterface) {
        }

        return $completionInput;
    }

    private function findCommand(CompletionInput $completionInput, OutputInterface $output): ?Command
    {
        try {
            $inputName = $completionInput->getFirstArgument();
            if (null === $inputName) {
                return null;
            }

            return $this->getApplication()->find($inputName);
        } catch (CommandNotFoundException) {
        }

        return null;
    }

    private function log($messages): void
    {
        if (!$this->isDebug) {
            return;
        }

        $commandName = basename($_SERVER['argv'][0]);
        file_put_contents(sys_get_temp_dir().'/sf_'.$commandName.'.log', implode(\PHP_EOL, (array) $messages).\PHP_EOL, \FILE_APPEND);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Completion\Suggestion;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Helper\HelperInterface;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Base class for all commands.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Command
{
    // see https://tldp.org/LDP/abs/html/exitcodes.html
    public const SUCCESS = 0;
    public const FAILURE = 1;
    public const INVALID = 2;

    /**
     * @var string|null The default command name
     *
     * @deprecated since Symfony 6.1, use the AsCommand attribute instead
     */
    protected static $defaultName;

    /**
     * @var string|null The default command description
     *
     * @deprecated since Symfony 6.1, use the AsCommand attribute instead
     */
    protected static $defaultDescription;

    private ?Application $application = null;
    private ?string $name = null;
    private ?string $processTitle = null;
    private array $aliases = [];
    private InputDefinition $definition;
    private bool $hidden = false;
    private string $help = '';
    private string $description = '';
    private ?InputDefinition $fullDefinition = null;
    private bool $ignoreValidationErrors = false;
    private ?\Closure $code = null;
    private array $synopsis = [];
    private array $usages = [];
    private ?HelperSet $helperSet = null;

    public static function getDefaultName(): ?string
    {
        $class = static::class;

        if ($attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class)) {
            return $attribute[0]->newInstance()->name;
        }

        $r = new \ReflectionProperty($class, 'defaultName');

        if ($class !== $r->class || null === static::$defaultName) {
            return null;
        }

        trigger_deprecation('symfony/console', '6.1', 'Relying on the static property "$defaultName" for setting a command name is deprecated. Add the "%s" attribute to the "%s" class instead.', AsCommand::class, static::class);

        return static::$defaultName;
    }

    public static function getDefaultDescription(): ?string
    {
        $class = static::class;

        if ($attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class)) {
            return $attribute[0]->newInstance()->description;
        }

        $r = new \ReflectionProperty($class, 'defaultDescription');

        if ($class !== $r->class || null === static::$defaultDescription) {
            return null;
        }

        trigger_deprecation('symfony/console', '6.1', 'Relying on the static property "$defaultDescription" for setting a command description is deprecated. Add the "%s" attribute to the "%s" class instead.', AsCommand::class, static::class);

        return static::$defaultDescription;
    }

    /**
     * @param string|null $name The name of the command; passing null means it must be set in configure()
     *
     * @throws LogicException When the command name is empty
     */
    public function __construct(?string $name = null)
    {
        $this->definition = new InputDefinition();

        if (null === $name && null !== $name = static::getDefaultName()) {
            $aliases = explode('|', $name);

            if ('' === $name = array_shift($aliases)) {
                $this->setHidden(true);
                $name = array_shift($aliases);
            }

            $this->setAliases($aliases);
        }

        if (null !== $name) {
            $this->setName($name);
        }

        if ('' === $this->description) {
            $this->setDescription(static::getDefaultDescription() ?? '');
        }

        $this->configure();
    }

    /**
     * Ignores validation errors.
     *
     * This is mainly useful for the help command.
     *
     * @return void
     */
    public function ignoreValidationErrors()
    {
        $this->ignoreValidationErrors = true;
    }

    /**
     * @return void
     */
    public function setApplication(?Application $application = null)
    {
        if (1 > \func_num_args()) {
            trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
        }
        $this->application = $application;
        if ($application) {
            $this->setHelperSet($application->getHelperSet());
        } else {
            $this->helperSet = null;
        }

        $this->fullDefinition = null;
    }

    /**
     * @return void
     */
    public function setHelperSet(HelperSet $helperSet)
    {
        $this->helperSet = $helperSet;
    }

    /**
     * Gets the helper set.
     */
    public function getHelperSet(): ?HelperSet
    {
        return $this->helperSet;
    }

    /**
     * Gets the application instance for this command.
     */
    public function getApplication(): ?Application
    {
        return $this->application;
    }

    /**
     * Checks whether the command is enabled or not in the current environment.
     *
     * Override this to check for x or y and return false if the command cannot
     * run properly under the current conditions.
     *
     * @return bool
     */
    public function isEnabled()
    {
        return true;
    }

    /**
     * Configures the current command.
     *
     * @return void
     */
    protected function configure()
    {
    }

    /**
     * Executes the current command.
     *
     * This method is not abstract because you can use this class
     * as a concrete class. In this case, instead of defining the
     * execute() method, you set the code to execute by passing
     * a Closure to the setCode() method.
     *
     * @return int 0 if everything went fine, or an exit code
     *
     * @throws LogicException When this abstract method is not implemented
     *
     * @see setCode()
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        throw new LogicException('You must override the execute() method in the concrete command class.');
    }

    /**
     * Interacts with the user.
     *
     * This method is executed before the InputDefinition is validated.
     * This means that this is the only place where the command can
     * interactively ask for values of missing required arguments.
     *
     * @return void
     */
    protected function interact(InputInterface $input, OutputInterface $output)
    {
    }

    /**
     * Initializes the command after the input has been bound and before the input
     * is validated.
     *
     * This is mainly useful when a lot of commands extends one main command
     * where some things need to be initialized based on the input arguments and options.
     *
     * @see InputInterface::bind()
     * @see InputInterface::validate()
     *
     * @return void
     */
    protected function initialize(InputInterface $input, OutputInterface $output)
    {
    }

    /**
     * Runs the command.
     *
     * The code to execute is either defined directly with the
     * setCode() method or by overriding the execute() method
     * in a sub-class.
     *
     * @return int The command exit code
     *
     * @throws ExceptionInterface When input binding fails. Bypass this by calling {@link ignoreValidationErrors()}.
     *
     * @see setCode()
     * @see execute()
     */
    public function run(InputInterface $input, OutputInterface $output): int
    {
        // add the application arguments and options
        $this->mergeApplicationDefinition();

        // bind the input against the command specific arguments/options
        try {
            $input->bind($this->getDefinition());
        } catch (ExceptionInterface $e) {
            if (!$this->ignoreValidationErrors) {
                throw $e;
            }
        }

        $this->initialize($input, $output);

        if (null !== $this->processTitle) {
            if (\function_exists('cli_set_process_title')) {
                if (!@cli_set_process_title($this->processTitle)) {
                    if ('Darwin' === \PHP_OS) {
                        $output->writeln('<comment>Running "cli_set_process_title" as an unprivileged user is not supported on MacOS.</comment>', OutputInterface::VERBOSITY_VERY_VERBOSE);
                    } else {
                        cli_set_process_title($this->processTitle);
                    }
                }
            } elseif (\function_exists('setproctitle')) {
                setproctitle($this->processTitle);
            } elseif (OutputInterface::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) {
                $output->writeln('<comment>Install the proctitle PECL to be able to change the process title.</comment>');
            }
        }

        if ($input->isInteractive()) {
            $this->interact($input, $output);
        }

        // The command name argument is often omitted when a command is executed directly with its run() method.
        // It would fail the validation if we didn't make sure the command argument is present,
        // since it's required by the application.
        if ($input->hasArgument('command') && null === $input->getArgument('command')) {
            $input->setArgument('command', $this->getName());
        }

        $input->validate();

        if ($this->code) {
            $statusCode = ($this->code)($input, $output);
        } else {
            $statusCode = $this->execute($input, $output);

            if (!\is_int($statusCode)) {
                throw new \TypeError(\sprintf('Return value of "%s::execute()" must be of the type int, "%s" returned.', static::class, get_debug_type($statusCode)));
            }
        }

        return is_numeric($statusCode) ? (int) $statusCode : 0;
    }

    /**
     * Adds suggestions to $suggestions for the current completion input (e.g. option or argument).
     */
    public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
    {
        $definition = $this->getDefinition();
        if (CompletionInput::TYPE_OPTION_VALUE === $input->getCompletionType() && $definition->hasOption($input->getCompletionName())) {
            $definition->getOption($input->getCompletionName())->complete($input, $suggestions);
        } elseif (CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType() && $definition->hasArgument($input->getCompletionName())) {
            $definition->getArgument($input->getCompletionName())->complete($input, $suggestions);
        }
    }

    /**
     * Sets the code to execute when running this command.
     *
     * If this method is used, it overrides the code defined
     * in the execute() method.
     *
     * @param callable $code A callable(InputInterface $input, OutputInterface $output)
     *
     * @return $this
     *
     * @throws InvalidArgumentException
     *
     * @see execute()
     */
    public function setCode(callable $code): static
    {
        if ($code instanceof \Closure) {
            $r = new \ReflectionFunction($code);
            if (null === $r->getClosureThis()) {
                set_error_handler(static function () {});
                try {
                    if ($c = \Closure::bind($code, $this)) {
                        $code = $c;
                    }
                } finally {
                    restore_error_handler();
                }
            }
        } else {
            $code = $code(...);
        }

        $this->code = $code;

        return $this;
    }

    /**
     * Merges the application definition with the command definition.
     *
     * This method is not part of public API and should not be used directly.
     *
     * @param bool $mergeArgs Whether to merge or not the Application definition arguments to Command definition arguments
     *
     * @internal
     */
    public function mergeApplicationDefinition(bool $mergeArgs = true): void
    {
        if (null === $this->application) {
            return;
        }

        $this->fullDefinition = new InputDefinition();
        $this->fullDefinition->setOptions($this->definition->getOptions());
        $this->fullDefinition->addOptions($this->application->getDefinition()->getOptions());

        if ($mergeArgs) {
            $this->fullDefinition->setArguments($this->application->getDefinition()->getArguments());
            $this->fullDefinition->addArguments($this->definition->getArguments());
        } else {
            $this->fullDefinition->setArguments($this->definition->getArguments());
        }
    }

    /**
     * Sets an array of argument and option instances.
     *
     * @return $this
     */
    public function setDefinition(array|InputDefinition $definition): static
    {
        if ($definition instanceof InputDefinition) {
            $this->definition = $definition;
        } else {
            $this->definition->setDefinition($definition);
        }

        $this->fullDefinition = null;

        return $this;
    }

    /**
     * Gets the InputDefinition attached to this Command.
     */
    public function getDefinition(): InputDefinition
    {
        return $this->fullDefinition ?? $this->getNativeDefinition();
    }

    /**
     * Gets the InputDefinition to be used to create representations of this Command.
     *
     * Can be overridden to provide the original command representation when it would otherwise
     * be changed by merging with the application InputDefinition.
     *
     * This method is not part of public API and should not be used directly.
     */
    public function getNativeDefinition(): InputDefinition
    {
        return $this->definition ?? throw new LogicException(\sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', static::class));
    }

    /**
     * Adds an argument.
     *
     * @param $mode    The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL
     * @param $default The default value (for InputArgument::OPTIONAL mode only)
     * @param array|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion> $suggestedValues The values used for input completion
     *
     * @return $this
     *
     * @throws InvalidArgumentException When argument mode is not valid
     */
    public function addArgument(string $name, ?int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = null */): static
    {
        $suggestedValues = 5 <= \func_num_args() ? func_get_arg(4) : [];
        if (!\is_array($suggestedValues) && !$suggestedValues instanceof \Closure) {
            throw new \TypeError(\sprintf('Argument 5 passed to "%s()" must be array or \Closure, "%s" given.', __METHOD__, get_debug_type($suggestedValues)));
        }
        $this->definition->addArgument(new InputArgument($name, $mode, $description, $default, $suggestedValues));
        $this->fullDefinition?->addArgument(new InputArgument($name, $mode, $description, $default, $suggestedValues));

        return $this;
    }

    /**
     * Adds an option.
     *
     * @param $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
     * @param $mode     The option mode: One of the InputOption::VALUE_* constants
     * @param $default  The default value (must be null for InputOption::VALUE_NONE)
     * @param array|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion> $suggestedValues The values used for input completion
     *
     * @return $this
     *
     * @throws InvalidArgumentException If option mode is invalid or incompatible
     */
    public function addOption(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = [] */): static
    {
        $suggestedValues = 6 <= \func_num_args() ? func_get_arg(5) : [];
        if (!\is_array($suggestedValues) && !$suggestedValues instanceof \Closure) {
            throw new \TypeError(\sprintf('Argument 5 passed to "%s()" must be array or \Closure, "%s" given.', __METHOD__, get_debug_type($suggestedValues)));
        }
        $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default, $suggestedValues));
        $this->fullDefinition?->addOption(new InputOption($name, $shortcut, $mode, $description, $default, $suggestedValues));

        return $this;
    }

    /**
     * Sets the name of the command.
     *
     * This method can set both the namespace and the name if
     * you separate them by a colon (:)
     *
     *     $command->setName('foo:bar');
     *
     * @return $this
     *
     * @throws InvalidArgumentException When the name is invalid
     */
    public function setName(string $name): static
    {
        $this->validateName($name);

        $this->name = $name;

        return $this;
    }

    /**
     * Sets the process title of the command.
     *
     * This feature should be used only when creating a long process command,
     * like a daemon.
     *
     * @return $this
     */
    public function setProcessTitle(string $title): static
    {
        $this->processTitle = $title;

        return $this;
    }

    /**
     * Returns the command name.
     */
    public function getName(): ?string
    {
        return $this->name;
    }

    /**
     * @param bool $hidden Whether or not the command should be hidden from the list of commands
     *
     * @return $this
     */
    public function setHidden(bool $hidden = true): static
    {
        $this->hidden = $hidden;

        return $this;
    }

    /**
     * @return bool whether the command should be publicly shown or not
     */
    public function isHidden(): bool
    {
        return $this->hidden;
    }

    /**
     * Sets the description for the command.
     *
     * @return $this
     */
    public function setDescription(string $description): static
    {
        $this->description = $description;

        return $this;
    }

    /**
     * Returns the description for the command.
     */
    public function getDescription(): string
    {
        return $this->description;
    }

    /**
     * Sets the help for the command.
     *
     * @return $this
     */
    public function setHelp(string $help): static
    {
        $this->help = $help;

        return $this;
    }

    /**
     * Returns the help for the command.
     */
    public function getHelp(): string
    {
        return $this->help;
    }

    /**
     * Returns the processed help for the command replacing the %command.name% and
     * %command.full_name% patterns with the real values dynamically.
     */
    public function getProcessedHelp(): string
    {
        $name = $this->name;
        $isSingleCommand = $this->application?->isSingleCommand();

        $placeholders = [
            '%command.name%',
            '%command.full_name%',
        ];
        $replacements = [
            $name,
            $isSingleCommand ? $_SERVER['PHP_SELF'] : $_SERVER['PHP_SELF'].' '.$name,
        ];

        return str_replace($placeholders, $replacements, $this->getHelp() ?: $this->getDescription());
    }

    /**
     * Sets the aliases for the command.
     *
     * @param string[] $aliases An array of aliases for the command
     *
     * @return $this
     *
     * @throws InvalidArgumentException When an alias is invalid
     */
    public function setAliases(iterable $aliases): static
    {
        $list = [];

        foreach ($aliases as $alias) {
            $this->validateName($alias);
            $list[] = $alias;
        }

        $this->aliases = \is_array($aliases) ? $aliases : $list;

        return $this;
    }

    /**
     * Returns the aliases for the command.
     */
    public function getAliases(): array
    {
        return $this->aliases;
    }

    /**
     * Returns the synopsis for the command.
     *
     * @param bool $short Whether to show the short version of the synopsis (with options folded) or not
     */
    public function getSynopsis(bool $short = false): string
    {
        $key = $short ? 'short' : 'long';

        if (!isset($this->synopsis[$key])) {
            $this->synopsis[$key] = trim(\sprintf('%s %s', $this->name, $this->definition->getSynopsis($short)));
        }

        return $this->synopsis[$key];
    }

    /**
     * Add a command usage example, it'll be prefixed with the command name.
     *
     * @return $this
     */
    public function addUsage(string $usage): static
    {
        if (!str_starts_with($usage, $this->name)) {
            $usage = \sprintf('%s %s', $this->name, $usage);
        }

        $this->usages[] = $usage;

        return $this;
    }

    /**
     * Returns alternative usages of the command.
     */
    public function getUsages(): array
    {
        return $this->usages;
    }

    /**
     * Gets a helper instance by name.
     *
     * @return HelperInterface
     *
     * @throws LogicException           if no HelperSet is defined
     * @throws InvalidArgumentException if the helper is not defined
     */
    public function getHelper(string $name): mixed
    {
        if (null === $this->helperSet) {
            throw new LogicException(\sprintf('Cannot retrieve helper "%s" because there is no HelperSet defined. Did you forget to add your command to the application or to set the application on the command using the setApplication() method? You can also set the HelperSet directly using the setHelperSet() method.', $name));
        }

        return $this->helperSet->get($name);
    }

    /**
     * Validates a command name.
     *
     * It must be non-empty and parts can optionally be separated by ":".
     *
     * @throws InvalidArgumentException When the name is invalid
     */
    private function validateName(string $name): void
    {
        if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) {
            throw new InvalidArgumentException(\sprintf('Command name "%s" is invalid.', $name));
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Descriptor\ApplicationDescription;
use Symfony\Component\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * ListCommand displays the list of all available commands for the application.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ListCommand extends Command
{
    /**
     * @return void
     */
    protected function configure()
    {
        $this
            ->setName('list')
            ->setDefinition([
                new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name', null, fn () => array_keys((new ApplicationDescription($this->getApplication()))->getNamespaces())),
                new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'),
                new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', static fn () => (new DescriptorHelper())->getFormats()),
                new InputOption('short', null, InputOption::VALUE_NONE, 'To skip describing commands\' arguments'),
            ])
            ->setDescription('List commands')
            ->setHelp(<<<'EOF'
                The <info>%command.name%</info> command lists all commands:

                  <info>%command.full_name%</info>

                You can also display the commands for a specific namespace:

                  <info>%command.full_name% test</info>

                You can also output the information in other formats by using the <comment>--format</comment> option:

                  <info>%command.full_name% --format=xml</info>

                It's also possible to get raw list of commands (useful for embedding command runner):

                  <info>%command.full_name% --raw</info>
                EOF
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $helper = new DescriptorHelper();
        $helper->describe($output, $this->getApplication(), [
            'format' => $input->getOption('format'),
            'raw_text' => $input->getOption('raw'),
            'namespace' => $input->getArgument('namespace'),
            'short' => $input->getOption('short'),
        ]);

        return 0;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\LockInterface;
use Symfony\Component\Lock\Store\FlockStore;
use Symfony\Component\Lock\Store\SemaphoreStore;

/**
 * Basic lock feature for commands.
 *
 * @author Geoffrey Brier <geoffrey.brier@gmail.com>
 */
trait LockableTrait
{
    private ?LockInterface $lock = null;

    /**
     * Locks a command.
     */
    private function lock(?string $name = null, bool $blocking = false): bool
    {
        if (!class_exists(SemaphoreStore::class)) {
            throw new LogicException('To enable the locking feature you must install the symfony/lock component. Try running "composer require symfony/lock".');
        }

        if (null !== $this->lock) {
            throw new LogicException('A lock is already in place.');
        }

        if (SemaphoreStore::isSupported()) {
            $store = new SemaphoreStore();
        } else {
            $store = new FlockStore();
        }

        $this->lock = (new LockFactory($store))->createLock($name ?: $this->getName());
        if (!$this->lock->acquire($blocking)) {
            $this->lock = null;

            return false;
        }

        return true;
    }

    /**
     * Releases the command lock if there is one.
     */
    private function release(): void
    {
        if ($this->lock) {
            $this->lock->release();
            $this->lock = null;
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

/**
 * Interface for command reacting to signal.
 *
 * @author Grégoire Pineau <lyrixx@lyrix.info>
 */
interface SignalableCommandInterface
{
    /**
     * Returns the list of signals to subscribe.
     */
    public function getSubscribedSignals(): array;

    /**
     * The method will be called when the application is signaled.
     *
     * @param int|false $previousExitCode
     *
     * @return int|false The exit code to return or false to continue the normal execution
     */
    public function handleSignal(int $signal/* , int|false $previousExitCode = 0 */);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Completion\Suggestion;
use Symfony\Component\Console\Helper\HelperInterface;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Nicolas Grekas <p@tchwork.com>
 */
final class LazyCommand extends Command
{
    private \Closure|Command $command;
    private ?bool $isEnabled;

    public function __construct(string $name, array $aliases, string $description, bool $isHidden, \Closure $commandFactory, ?bool $isEnabled = true)
    {
        $this->setName($name)
            ->setAliases($aliases)
            ->setHidden($isHidden)
            ->setDescription($description);

        $this->command = $commandFactory;
        $this->isEnabled = $isEnabled;
    }

    public function ignoreValidationErrors(): void
    {
        $this->getCommand()->ignoreValidationErrors();
    }

    public function setApplication(?Application $application = null): void
    {
        if (1 > \func_num_args()) {
            trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
        }
        if ($this->command instanceof parent) {
            $this->command->setApplication($application);
        }

        parent::setApplication($application);
    }

    public function setHelperSet(HelperSet $helperSet): void
    {
        if ($this->command instanceof parent) {
            $this->command->setHelperSet($helperSet);
        }

        parent::setHelperSet($helperSet);
    }

    public function isEnabled(): bool
    {
        return $this->isEnabled ?? $this->getCommand()->isEnabled();
    }

    public function run(InputInterface $input, OutputInterface $output): int
    {
        return $this->getCommand()->run($input, $output);
    }

    public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
    {
        $this->getCommand()->complete($input, $suggestions);
    }

    public function setCode(callable $code): static
    {
        $this->getCommand()->setCode($code);

        return $this;
    }

    /**
     * @internal
     */
    public function mergeApplicationDefinition(bool $mergeArgs = true): void
    {
        $this->getCommand()->mergeApplicationDefinition($mergeArgs);
    }

    public function setDefinition(array|InputDefinition $definition): static
    {
        $this->getCommand()->setDefinition($definition);

        return $this;
    }

    public function getDefinition(): InputDefinition
    {
        return $this->getCommand()->getDefinition();
    }

    public function getNativeDefinition(): InputDefinition
    {
        return $this->getCommand()->getNativeDefinition();
    }

    /**
     * @param array|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion> $suggestedValues The values used for input completion
     */
    public function addArgument(string $name, ?int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = [] */): static
    {
        $suggestedValues = 5 <= \func_num_args() ? func_get_arg(4) : [];
        $this->getCommand()->addArgument($name, $mode, $description, $default, $suggestedValues);

        return $this;
    }

    /**
     * @param array|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion> $suggestedValues The values used for input completion
     */
    public function addOption(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = [] */): static
    {
        $suggestedValues = 6 <= \func_num_args() ? func_get_arg(5) : [];
        $this->getCommand()->addOption($name, $shortcut, $mode, $description, $default, $suggestedValues);

        return $this;
    }

    public function setProcessTitle(string $title): static
    {
        $this->getCommand()->setProcessTitle($title);

        return $this;
    }

    public function setHelp(string $help): static
    {
        $this->getCommand()->setHelp($help);

        return $this;
    }

    public function getHelp(): string
    {
        return $this->getCommand()->getHelp();
    }

    public function getProcessedHelp(): string
    {
        return $this->getCommand()->getProcessedHelp();
    }

    public function getSynopsis(bool $short = false): string
    {
        return $this->getCommand()->getSynopsis($short);
    }

    public function addUsage(string $usage): static
    {
        $this->getCommand()->addUsage($usage);

        return $this;
    }

    public function getUsages(): array
    {
        return $this->getCommand()->getUsages();
    }

    public function getHelper(string $name): HelperInterface
    {
        return $this->getCommand()->getHelper($name);
    }

    public function getCommand(): parent
    {
        if (!$this->command instanceof \Closure) {
            return $this->command;
        }

        $command = $this->command = ($this->command)();
        $command->setApplication($this->getApplication());

        if (null !== $this->getHelperSet()) {
            $command->setHelperSet($this->getHelperSet());
        }

        $command->setName($this->getName())
            ->setAliases($this->getAliases())
            ->setHidden($this->isHidden())
            ->setDescription($this->getDescription());

        // Will throw if the command is not correctly initialized.
        $command->getDefinition();

        return $command;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Helper\HelperInterface;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Stopwatch\Stopwatch;

/**
 * @internal
 *
 * @author Jules Pietri <jules@heahprod.com>
 */
final class TraceableCommand extends Command implements SignalableCommandInterface
{
    public readonly Command $command;
    public int $exitCode;
    public ?int $interruptedBySignal = null;
    public bool $ignoreValidation;
    public bool $isInteractive = false;
    public string $duration = 'n/a';
    public string $maxMemoryUsage = 'n/a';
    public InputInterface $input;
    public OutputInterface $output;
    /** @var array<string, mixed> */
    public array $arguments;
    /** @var array<string, mixed> */
    public array $options;
    /** @var array<string, mixed> */
    public array $interactiveInputs = [];
    public array $handledSignals = [];

    public function __construct(
        Command $command,
        private readonly Stopwatch $stopwatch,
    ) {
        if ($command instanceof LazyCommand) {
            $command = $command->getCommand();
        }

        $this->command = $command;

        // prevent call to self::getDefaultDescription()
        $this->setDescription($command->getDescription());

        parent::__construct($command->getName());

        // init below enables calling {@see parent::run()}
        [$code, $processTitle, $ignoreValidationErrors] = \Closure::bind(fn () => [$this->code, $this->processTitle, $this->ignoreValidationErrors], $command, Command::class)();

        if (\is_callable($code)) {
            $this->setCode($code);
        }

        if ($processTitle) {
            parent::setProcessTitle($processTitle);
        }

        if ($ignoreValidationErrors) {
            parent::ignoreValidationErrors();
        }

        $this->ignoreValidation = $ignoreValidationErrors;
    }

    public function __call(string $name, array $arguments): mixed
    {
        return $this->command->{$name}(...$arguments);
    }

    public function getSubscribedSignals(): array
    {
        return $this->command instanceof SignalableCommandInterface ? $this->command->getSubscribedSignals() : [];
    }

    public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false
    {
        if (!$this->command instanceof SignalableCommandInterface) {
            return false;
        }

        $event = $this->stopwatch->start($this->getName().'.handle_signal');

        $exit = $this->command->handleSignal($signal, $previousExitCode);

        $event->stop();

        if (!isset($this->handledSignals[$signal])) {
            $this->handledSignals[$signal] = [
                'handled' => 0,
                'duration' => 0,
                'memory' => 0,
            ];
        }

        ++$this->handledSignals[$signal]['handled'];
        $this->handledSignals[$signal]['duration'] += $event->getDuration();
        $this->handledSignals[$signal]['memory'] = max(
            $this->handledSignals[$signal]['memory'],
            $event->getMemory() >> 20
        );

        return $exit;
    }

    /**
     * {@inheritdoc}
     *
     * Calling parent method is required to be used in {@see parent::run()}.
     */
    public function ignoreValidationErrors(): void
    {
        $this->ignoreValidation = true;
        $this->command->ignoreValidationErrors();

        parent::ignoreValidationErrors();
    }

    public function setApplication(?Application $application = null): void
    {
        $this->command->setApplication($application);
    }

    public function getApplication(): ?Application
    {
        return $this->command->getApplication();
    }

    public function setHelperSet(HelperSet $helperSet): void
    {
        $this->command->setHelperSet($helperSet);
    }

    public function getHelperSet(): ?HelperSet
    {
        return $this->command->getHelperSet();
    }

    public function isEnabled(): bool
    {
        return $this->command->isEnabled();
    }

    public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
    {
        $this->command->complete($input, $suggestions);
    }

    /**
     * {@inheritdoc}
     *
     * Calling parent method is required to be used in {@see parent::run()}.
     */
    public function setCode(callable $code): static
    {
        $this->command->setCode($code);

        return parent::setCode(function (InputInterface $input, OutputInterface $output) use ($code): int {
            $event = $this->stopwatch->start($this->getName().'.code');

            $this->exitCode = $code($input, $output);

            $event->stop();

            return $this->exitCode;
        });
    }

    /**
     * @internal
     */
    public function mergeApplicationDefinition(bool $mergeArgs = true): void
    {
        $this->command->mergeApplicationDefinition($mergeArgs);
    }

    public function setDefinition(array|InputDefinition $definition): static
    {
        $this->command->setDefinition($definition);

        return $this;
    }

    public function getDefinition(): InputDefinition
    {
        return $this->command->getDefinition();
    }

    public function getNativeDefinition(): InputDefinition
    {
        return $this->command->getNativeDefinition();
    }

    public function addArgument(string $name, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static
    {
        $this->command->addArgument($name, $mode, $description, $default, $suggestedValues);

        return $this;
    }

    public function addOption(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static
    {
        $this->command->addOption($name, $shortcut, $mode, $description, $default, $suggestedValues);

        return $this;
    }

    /**
     * {@inheritdoc}
     *
     * Calling parent method is required to be used in {@see parent::run()}.
     */
    public function setProcessTitle(string $title): static
    {
        $this->command->setProcessTitle($title);

        return parent::setProcessTitle($title);
    }

    public function setHelp(string $help): static
    {
        $this->command->setHelp($help);

        return $this;
    }

    public function getHelp(): string
    {
        return $this->command->getHelp();
    }

    public function getProcessedHelp(): string
    {
        return $this->command->getProcessedHelp();
    }

    public function getSynopsis(bool $short = false): string
    {
        return $this->command->getSynopsis($short);
    }

    public function addUsage(string $usage): static
    {
        $this->command->addUsage($usage);

        return $this;
    }

    public function getUsages(): array
    {
        return $this->command->getUsages();
    }

    public function getHelper(string $name): HelperInterface
    {
        return $this->command->getHelper($name);
    }

    public function run(InputInterface $input, OutputInterface $output): int
    {
        $this->input = $input;
        $this->output = $output;
        $this->arguments = $input->getArguments();
        $this->options = $input->getOptions();
        $event = $this->stopwatch->start($this->getName(), 'command');

        try {
            $this->exitCode = $this->command->run($input, $output);
        } finally {
            $event->stop();

            if ($output instanceof ConsoleOutputInterface && $output->isDebug()) {
                $output->getErrorOutput()->writeln((string) $event);
            }

            $this->duration = $event->getDuration().' ms';
            $this->maxMemoryUsage = ($event->getMemory() >> 20).' MiB';

            if ($this->isInteractive) {
                $this->extractInteractiveInputs($input->getArguments(), $input->getOptions());
            }
        }

        return $this->exitCode;
    }

    protected function initialize(InputInterface $input, OutputInterface $output): void
    {
        $event = $this->stopwatch->start($this->getName().'.init', 'command');

        $this->command->initialize($input, $output);

        $event->stop();
    }

    protected function interact(InputInterface $input, OutputInterface $output): void
    {
        if (!$this->isInteractive = Command::class !== (new \ReflectionMethod($this->command, 'interact'))->getDeclaringClass()->getName()) {
            return;
        }

        $event = $this->stopwatch->start($this->getName().'.interact', 'command');

        $this->command->interact($input, $output);

        $event->stop();
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $event = $this->stopwatch->start($this->getName().'.execute', 'command');

        $exitCode = $this->command->execute($input, $output);

        $event->stop();

        return $exitCode;
    }

    private function extractInteractiveInputs(array $arguments, array $options): void
    {
        foreach ($arguments as $argName => $argValue) {
            if (\array_key_exists($argName, $this->arguments) && $this->arguments[$argName] === $argValue) {
                continue;
            }

            $this->interactiveInputs[$argName] = $argValue;
        }

        foreach ($options as $optName => $optValue) {
            if (\array_key_exists($optName, $this->options) && $this->options[$optName] === $optValue) {
                continue;
            }

            $this->interactiveInputs['--'.$optName] = $optValue;
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Descriptor\ApplicationDescription;
use Symfony\Component\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * HelpCommand displays the help for a given command.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class HelpCommand extends Command
{
    private Command $command;

    /**
     * @return void
     */
    protected function configure()
    {
        $this->ignoreValidationErrors();

        $this
            ->setName('help')
            ->setDefinition([
                new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help', fn () => array_keys((new ApplicationDescription($this->getApplication()))->getCommands())),
                new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', static fn () => (new DescriptorHelper())->getFormats()),
                new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'),
            ])
            ->setDescription('Display help for a command')
            ->setHelp(<<<'EOF'
                The <info>%command.name%</info> command displays help for a given command:

                  <info>%command.full_name% list</info>

                You can also output the help in other formats by using the <comment>--format</comment> option:

                  <info>%command.full_name% --format=xml list</info>

                To display the list of available commands, please use the <info>list</info> command.
                EOF
            )
        ;
    }

    /**
     * @return void
     */
    public function setCommand(Command $command)
    {
        $this->command = $command;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $this->command ??= $this->getApplication()->find($input->getArgument('command_name'));

        $helper = new DescriptorHelper();
        $helper->describe($output, $this->command, [
            'format' => $input->getOption('format'),
            'raw_text' => $input->getOption('raw'),
        ]);

        unset($this->command);

        return 0;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Attribute\AsCommand;
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\Process\Process;

/**
 * Dumps the completion script for the current shell.
 *
 * @author Wouter de Jong <wouter@wouterj.nl>
 */
#[AsCommand(name: 'completion', description: 'Dump the shell completion script')]
final class DumpCompletionCommand extends Command
{
    /**
     * @deprecated since Symfony 6.1
     */
    protected static $defaultName = 'completion';

    /**
     * @deprecated since Symfony 6.1
     */
    protected static $defaultDescription = 'Dump the shell completion script';

    private array $supportedShells;

    protected function configure(): void
    {
        $fullCommand = $_SERVER['PHP_SELF'];
        $commandName = basename($fullCommand);
        $fullCommand = @realpath($fullCommand) ?: $fullCommand;

        $shell = $this->guessShell();
        [$rcFile, $completionFile] = match ($shell) {
            'fish' => ['~/.config/fish/config.fish', "/etc/fish/completions/$commandName.fish"],
            'zsh' => ['~/.zshrc', '$fpath[1]/_'.$commandName],
            default => ['~/.bashrc', "/etc/bash_completion.d/$commandName"],
        };

        $supportedShells = implode(', ', $this->getSupportedShells());

        $this
            ->setHelp(<<<EOH
                The <info>%command.name%</> command dumps the shell completion script required
                to use shell autocompletion (currently, {$supportedShells} completion are supported).

                <comment>Static installation
                -------------------</>

                Dump the script to a global completion file and restart your shell:

                    <info>%command.full_name% {$shell} | sudo tee {$completionFile}</>

                Or dump the script to a local file and source it:

                    <info>%command.full_name% {$shell} > completion.sh</>

                    <comment># source the file whenever you use the project</>
                    <info>source completion.sh</>

                    <comment># or add this line at the end of your "{$rcFile}" file:</>
                    <info>source /path/to/completion.sh</>

                <comment>Dynamic installation
                --------------------</>

                Add this to the end of your shell configuration file (e.g. <info>"{$rcFile}"</>):

                    <info>eval "$({$fullCommand} completion {$shell})"</>
                EOH
            )
            ->addArgument('shell', InputArgument::OPTIONAL, 'The shell type (e.g. "bash"), the value of the "$SHELL" env var will be used if this is not given', null, $this->getSupportedShells(...))
            ->addOption('debug', null, InputOption::VALUE_NONE, 'Tail the completion debug log')
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $commandName = basename($_SERVER['argv'][0]);

        if ($input->getOption('debug')) {
            $this->tailDebugLog($commandName, $output);

            return 0;
        }

        $shell = $input->getArgument('shell') ?? self::guessShell();
        $completionFile = __DIR__.'/../Resources/completion.'.$shell;
        if (!file_exists($completionFile)) {
            $supportedShells = $this->getSupportedShells();

            if ($output instanceof ConsoleOutputInterface) {
                $output = $output->getErrorOutput();
            }
            if ($shell) {
                $output->writeln(\sprintf('<error>Detected shell "%s", which is not supported by Symfony shell completion (supported shells: "%s").</>', $shell, implode('", "', $supportedShells)));
            } else {
                $output->writeln(\sprintf('<error>Shell not detected, Symfony shell completion only supports "%s").</>', implode('", "', $supportedShells)));
            }

            return 2;
        }

        $output->write(str_replace(['{{ COMMAND_NAME }}', '{{ VERSION }}'], [$commandName, CompleteCommand::COMPLETION_API_VERSION], file_get_contents($completionFile)));

        return 0;
    }

    private static function guessShell(): string
    {
        return basename($_SERVER['SHELL'] ?? '');
    }

    private function tailDebugLog(string $commandName, OutputInterface $output): void
    {
        $debugFile = sys_get_temp_dir().'/sf_'.$commandName.'.log';
        if (!file_exists($debugFile)) {
            touch($debugFile);
        }
        $process = new Process(['tail', '-f', $debugFile], null, null, null, 0);
        $process->run(static function (string $type, string $line) use ($output): void {
            $output->write($line);
        });
    }

    /**
     * @return string[]
     */
    private function getSupportedShells(): array
    {
        if (isset($this->supportedShells)) {
            return $this->supportedShells;
        }

        $shells = [];

        foreach (new \DirectoryIterator(__DIR__.'/../Resources/') as $file) {
            if (str_starts_with($file->getBasename(), 'completion.') && $file->isFile()) {
                $shells[] = $file->getExtension();
            }
        }
        sort($shells);

        return $this->supportedShells = $shells;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Debug;

use Symfony\Component\Console\Command\TraceableCommand;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

/**
 * @internal
 */
final class CliRequest extends Request
{
    public function __construct(
        public readonly TraceableCommand $command,
    ) {
        parent::__construct(
            attributes: ['_controller' => \get_class($command->command), '_virtual_type' => 'command'],
            server: $_SERVER,
        );
    }

    // Methods below allow to populate a profile, thus enable search and filtering
    public function getUri(): string
    {
        if ($this->server->has('SYMFONY_CLI_BINARY_NAME')) {
            $binary = $this->server->get('SYMFONY_CLI_BINARY_NAME').' console';
        } else {
            $binary = $this->server->get('argv')[0];
        }

        return $binary.' '.$this->command->input;
    }

    public function getMethod(): string
    {
        return $this->command->isInteractive ? 'INTERACTIVE' : 'BATCH';
    }

    public function getResponse(): Response
    {
        return new class($this->command->exitCode) extends Response {
            public function __construct(private readonly int $exitCode)
            {
                parent::__construct();
            }

            public function getStatusCode(): int
            {
                return $this->exitCode;
            }
        };
    }

    public function getClientIp(): string
    {
        $application = $this->command->getApplication();

        return $application->getName().' '.$application->getVersion();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Event;

/**
 * Allows to do things before the command is executed, like skipping the command or executing code before the command is
 * going to be executed.
 *
 * Changing the input arguments will have no effect.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
final class ConsoleCommandEvent extends ConsoleEvent
{
    /**
     * The return code for skipped commands, this will also be passed into the terminate event.
     */
    public const RETURN_CODE_DISABLED = 113;

    /**
     * Indicates if the command should be run or skipped.
     */
    private bool $commandShouldRun = true;

    /**
     * Disables the command, so it won't be run.
     */
    public function disableCommand(): bool
    {
        return $this->commandShouldRun = false;
    }

    public function enableCommand(): bool
    {
        return $this->commandShouldRun = true;
    }

    /**
     * Returns true if the command is runnable, false otherwise.
     */
    public function commandShouldRun(): bool
    {
        return $this->commandShouldRun;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Event;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Contracts\EventDispatcher\Event;

/**
 * Allows to inspect input and output of a command.
 *
 * @author Francesco Levorato <git@flevour.net>
 */
class ConsoleEvent extends Event
{
    protected $command;

    private InputInterface $input;
    private OutputInterface $output;

    public function __construct(?Command $command, InputInterface $input, OutputInterface $output)
    {
        $this->command = $command;
        $this->input = $input;
        $this->output = $output;
    }

    /**
     * Gets the command that is executed.
     */
    public function getCommand(): ?Command
    {
        return $this->command;
    }

    /**
     * Gets the input instance.
     */
    public function getInput(): InputInterface
    {
        return $this->input;
    }

    /**
     * Gets the output instance.
     */
    public function getOutput(): OutputInterface
    {
        return $this->output;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Event;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Allows to manipulate the exit code of a command after its execution.
 *
 * @author Francesco Levorato <git@flevour.net>
 * @author Jules Pietri <jules@heahprod.com>
 */
final class ConsoleTerminateEvent extends ConsoleEvent
{
    public function __construct(
        Command $command,
        InputInterface $input,
        OutputInterface $output,
        private int $exitCode,
        private readonly ?int $interruptingSignal = null,
    ) {
        parent::__construct($command, $input, $output);
    }

    public function setExitCode(int $exitCode): void
    {
        $this->exitCode = $exitCode;
    }

    public function getExitCode(): int
    {
        return $this->exitCode;
    }

    public function getInterruptingSignal(): ?int
    {
        return $this->interruptingSignal;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Event;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Allows to handle throwables thrown while running a command.
 *
 * @author Wouter de Jong <wouter@wouterj.nl>
 */
final class ConsoleErrorEvent extends ConsoleEvent
{
    private \Throwable $error;
    private int $exitCode;

    public function __construct(InputInterface $input, OutputInterface $output, \Throwable $error, ?Command $command = null)
    {
        parent::__construct($command, $input, $output);

        $this->error = $error;
    }

    public function getError(): \Throwable
    {
        return $this->error;
    }

    public function setError(\Throwable $error): void
    {
        $this->error = $error;
    }

    public function setExitCode(int $exitCode): void
    {
        $this->exitCode = $exitCode;

        $r = new \ReflectionProperty($this->error, 'code');
        $r->setValue($this->error, $this->exitCode);
    }

    public function getExitCode(): int
    {
        return $this->exitCode ?? (\is_int($this->error->getCode()) && 0 !== $this->error->getCode() ? $this->error->getCode() : 1);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Event;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author marie <marie@users.noreply.github.com>
 */
final class ConsoleSignalEvent extends ConsoleEvent
{
    private int $handlingSignal;
    private int|false $exitCode;

    public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $handlingSignal, int|false $exitCode = 0)
    {
        parent::__construct($command, $input, $output);
        $this->handlingSignal = $handlingSignal;
        $this->exitCode = $exitCode;
    }

    public function getHandlingSignal(): int
    {
        return $this->handlingSignal;
    }

    public function setExitCode(int $exitCode): void
    {
        if ($exitCode < 0 || $exitCode > 255) {
            throw new \InvalidArgumentException('Exit code must be between 0 and 255.');
        }

        $this->exitCode = $exitCode;
    }

    public function abortExit(): void
    {
        $this->exitCode = false;
    }

    public function getExitCode(): int|false
    {
        return $this->exitCode;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console;

use Symfony\Component\Console\Output\AnsiColorMode;

class Terminal
{
    public const DEFAULT_COLOR_MODE = AnsiColorMode::Ansi4;

    private static ?AnsiColorMode $colorMode = null;
    private static ?int $width = null;
    private static ?int $height = null;
    private static ?bool $stty = null;

    /**
     * About Ansi color types: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
     * For more information about true color support with terminals https://github.com/termstandard/colors/.
     */
    public static function getColorMode(): AnsiColorMode
    {
        // Use Cache from previous run (or user forced mode)
        if (null !== self::$colorMode) {
            return self::$colorMode;
        }

        // Try with $COLORTERM first
        if (\is_string($colorterm = getenv('COLORTERM'))) {
            $colorterm = strtolower($colorterm);

            if (str_contains($colorterm, 'truecolor')) {
                self::setColorMode(AnsiColorMode::Ansi24);

                return self::$colorMode;
            }

            if (str_contains($colorterm, '256color')) {
                self::setColorMode(AnsiColorMode::Ansi8);

                return self::$colorMode;
            }
        }

        // Try with $TERM
        if (\is_string($term = getenv('TERM'))) {
            $term = strtolower($term);

            if (str_contains($term, 'truecolor')) {
                self::setColorMode(AnsiColorMode::Ansi24);

                return self::$colorMode;
            }

            if (str_contains($term, '256color')) {
                self::setColorMode(AnsiColorMode::Ansi8);

                return self::$colorMode;
            }
        }

        self::setColorMode(self::DEFAULT_COLOR_MODE);

        return self::$colorMode;
    }

    /**
     * Force a terminal color mode rendering.
     */
    public static function setColorMode(?AnsiColorMode $colorMode): void
    {
        self::$colorMode = $colorMode;
    }

    /**
     * Gets the terminal width.
     */
    public function getWidth(): int
    {
        $width = getenv('COLUMNS');
        if (false !== $width) {
            return (int) trim($width);
        }

        if (null === self::$width) {
            self::initDimensions();
        }

        return self::$width ?: 80;
    }

    /**
     * Gets the terminal height.
     */
    public function getHeight(): int
    {
        $height = getenv('LINES');
        if (false !== $height) {
            return (int) trim($height);
        }

        if (null === self::$height) {
            self::initDimensions();
        }

        return self::$height ?: 50;
    }

    /**
     * @internal
     */
    public static function hasSttyAvailable(): bool
    {
        if (null !== self::$stty) {
            return self::$stty;
        }

        // skip check if shell_exec function is disabled
        if (!\function_exists('shell_exec')) {
            return false;
        }

        return self::$stty = (bool) @shell_exec('stty 2> '.('\\' === \DIRECTORY_SEPARATOR ? 'NUL' : '/dev/null'));
    }

    private static function initDimensions(): void
    {
        if ('\\' === \DIRECTORY_SEPARATOR) {
            $ansicon = getenv('ANSICON');
            if (false !== $ansicon && preg_match('/^(\d+)x(\d+)(?: \((\d+)x(\d+)\))?$/', trim($ansicon), $matches)) {
                // extract [w, H] from "wxh (WxH)"
                // or [w, h] from "wxh"
                self::$width = (int) $matches[1];
                self::$height = isset($matches[4]) ? (int) $matches[4] : (int) $matches[2];
            } elseif (!self::hasVt100Support() && self::hasSttyAvailable()) {
                // only use stty on Windows if the terminal does not support vt100 (e.g. Windows 7 + git-bash)
                // testing for stty in a Windows 10 vt100-enabled console will implicitly disable vt100 support on STDOUT
                self::initDimensionsUsingStty();
            } elseif (null !== $dimensions = self::getConsoleMode()) {
                // extract [w, h] from "wxh"
                self::$width = (int) $dimensions[0];
                self::$height = (int) $dimensions[1];
            }
        } else {
            self::initDimensionsUsingStty();
        }
    }

    /**
     * Returns whether STDOUT has vt100 support (some Windows 10+ configurations).
     */
    private static function hasVt100Support(): bool
    {
        return \function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support(fopen('php://stdout', 'w'));
    }

    /**
     * Initializes dimensions using the output of an stty columns line.
     */
    private static function initDimensionsUsingStty(): void
    {
        if ($sttyString = self::getSttyColumns()) {
            if (preg_match('/rows.(\d+);.columns.(\d+);/is', $sttyString, $matches)) {
                // extract [w, h] from "rows h; columns w;"
                self::$width = (int) $matches[2];
                self::$height = (int) $matches[1];
            } elseif (preg_match('/;.(\d+).rows;.(\d+).columns/is', $sttyString, $matches)) {
                // extract [w, h] from "; h rows; w columns"
                self::$width = (int) $matches[2];
                self::$height = (int) $matches[1];
            }
        }
    }

    /**
     * Runs and parses mode CON if it's available, suppressing any error output.
     *
     * @return int[]|null An array composed of the width and the height or null if it could not be parsed
     */
    private static function getConsoleMode(): ?array
    {
        $info = self::readFromProcess('mode CON');

        if (null === $info || !preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
            return null;
        }

        return [(int) $matches[2], (int) $matches[1]];
    }

    /**
     * Runs and parses stty -a if it's available, suppressing any error output.
     */
    private static function getSttyColumns(): ?string
    {
        return self::readFromProcess(['stty', '-a']);
    }

    private static function readFromProcess(string|array $command): ?string
    {
        if (!\function_exists('proc_open')) {
            return null;
        }

        $descriptorspec = [
            1 => ['pipe', 'w'],
            2 => ['pipe', 'w'],
        ];

        $cp = \function_exists('sapi_windows_cp_set') ? sapi_windows_cp_get() : 0;

        if (!$process = @proc_open($command, $descriptorspec, $pipes, null, null, ['suppress_errors' => true])) {
            return null;
        }

        $info = stream_get_contents($pipes[1]);
        fclose($pipes[1]);
        fclose($pipes[2]);
        proc_close($process);

        if ($cp) {
            sapi_windows_cp_set($cp);
        }

        return $info;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Question;

/**
 * Represents a yes/no question.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ConfirmationQuestion extends Question
{
    private string $trueAnswerRegex;

    /**
     * @param string $question        The question to ask to the user
     * @param bool   $default         The default answer to return, true or false
     * @param string $trueAnswerRegex A regex to match the "yes" answer
     */
    public function __construct(string $question, bool $default = true, string $trueAnswerRegex = '/^y/i')
    {
        parent::__construct($question, $default);

        $this->trueAnswerRegex = $trueAnswerRegex;
        $this->setNormalizer($this->getDefaultNormalizer());
    }

    /**
     * Returns the default answer normalizer.
     */
    private function getDefaultNormalizer(): callable
    {
        $default = $this->getDefault();
        $regex = $this->trueAnswerRegex;

        return static function ($answer) use ($default, $regex) {
            if (\is_bool($answer)) {
                return $answer;
            }

            $answerIsTrue = (bool) preg_match($regex, $answer);
            if (false === $default) {
                return $answer && $answerIsTrue;
            }

            return '' === $answer || $answerIsTrue;
        };
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Question;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;

/**
 * Represents a Question.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Question
{
    private string $question;
    private ?int $attempts = null;
    private bool $hidden = false;
    private bool $hiddenFallback = true;
    private ?\Closure $autocompleterCallback = null;
    private ?\Closure $validator = null;
    private string|int|bool|float|null $default;
    private ?\Closure $normalizer = null;
    private bool $trimmable = true;
    private bool $multiline = false;

    /**
     * @param string                     $question The question to ask to the user
     * @param string|bool|int|float|null $default  The default answer to return if the user enters nothing
     */
    public function __construct(string $question, string|bool|int|float|null $default = null)
    {
        $this->question = $question;
        $this->default = $default;
    }

    /**
     * Returns the question.
     */
    public function getQuestion(): string
    {
        return $this->question;
    }

    /**
     * Returns the default answer.
     */
    public function getDefault(): string|bool|int|float|null
    {
        return $this->default;
    }

    /**
     * Returns whether the user response accepts newline characters.
     */
    public function isMultiline(): bool
    {
        return $this->multiline;
    }

    /**
     * Sets whether the user response should accept newline characters.
     *
     * @return $this
     */
    public function setMultiline(bool $multiline): static
    {
        $this->multiline = $multiline;

        return $this;
    }

    /**
     * Returns whether the user response must be hidden.
     */
    public function isHidden(): bool
    {
        return $this->hidden;
    }

    /**
     * Sets whether the user response must be hidden or not.
     *
     * @return $this
     *
     * @throws LogicException In case the autocompleter is also used
     */
    public function setHidden(bool $hidden): static
    {
        if ($this->autocompleterCallback) {
            throw new LogicException('A hidden question cannot use the autocompleter.');
        }

        $this->hidden = $hidden;

        return $this;
    }

    /**
     * In case the response cannot be hidden, whether to fallback on non-hidden question or not.
     */
    public function isHiddenFallback(): bool
    {
        return $this->hiddenFallback;
    }

    /**
     * Sets whether to fallback on non-hidden question if the response cannot be hidden.
     *
     * @return $this
     */
    public function setHiddenFallback(bool $fallback): static
    {
        $this->hiddenFallback = $fallback;

        return $this;
    }

    /**
     * Gets values for the autocompleter.
     */
    public function getAutocompleterValues(): ?iterable
    {
        $callback = $this->getAutocompleterCallback();

        return $callback ? $callback('') : null;
    }

    /**
     * Sets values for the autocompleter.
     *
     * @return $this
     *
     * @throws LogicException
     */
    public function setAutocompleterValues(?iterable $values): static
    {
        if (\is_array($values)) {
            $values = $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values);

            $callback = static fn () => $values;
        } elseif ($values instanceof \Traversable) {
            $callback = static function () use ($values) {
                static $valueCache;

                return $valueCache ??= iterator_to_array($values, false);
            };
        } else {
            $callback = null;
        }

        return $this->setAutocompleterCallback($callback);
    }

    /**
     * Gets the callback function used for the autocompleter.
     */
    public function getAutocompleterCallback(): ?callable
    {
        return $this->autocompleterCallback;
    }

    /**
     * Sets the callback function used for the autocompleter.
     *
     * The callback is passed the user input as argument and should return an iterable of corresponding suggestions.
     *
     * @return $this
     */
    public function setAutocompleterCallback(?callable $callback = null): static
    {
        if (1 > \func_num_args()) {
            trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
        }
        if ($this->hidden && null !== $callback) {
            throw new LogicException('A hidden question cannot use the autocompleter.');
        }

        $this->autocompleterCallback = null === $callback ? null : $callback(...);

        return $this;
    }

    /**
     * Sets a validator for the question.
     *
     * @return $this
     */
    public function setValidator(?callable $validator = null): static
    {
        if (1 > \func_num_args()) {
            trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
        }
        $this->validator = null === $validator ? null : $validator(...);

        return $this;
    }

    /**
     * Gets the validator for the question.
     */
    public function getValidator(): ?callable
    {
        return $this->validator;
    }

    /**
     * Sets the maximum number of attempts.
     *
     * Null means an unlimited number of attempts.
     *
     * @return $this
     *
     * @throws InvalidArgumentException in case the number of attempts is invalid
     */
    public function setMaxAttempts(?int $attempts): static
    {
        if (null !== $attempts && $attempts < 1) {
            throw new InvalidArgumentException('Maximum number of attempts must be a positive value.');
        }

        $this->attempts = $attempts;

        return $this;
    }

    /**
     * Gets the maximum number of attempts.
     *
     * Null means an unlimited number of attempts.
     */
    public function getMaxAttempts(): ?int
    {
        return $this->attempts;
    }

    /**
     * Sets a normalizer for the response.
     *
     * The normalizer can be a callable (a string), a closure or a class implementing __invoke.
     *
     * @return $this
     */
    public function setNormalizer(callable $normalizer): static
    {
        $this->normalizer = $normalizer(...);

        return $this;
    }

    /**
     * Gets the normalizer for the response.
     *
     * The normalizer can ba a callable (a string), a closure or a class implementing __invoke.
     */
    public function getNormalizer(): ?callable
    {
        return $this->normalizer;
    }

    /**
     * @return bool
     */
    protected function isAssoc(array $array)
    {
        return (bool) \count(array_filter(array_keys($array), 'is_string'));
    }

    public function isTrimmable(): bool
    {
        return $this->trimmable;
    }

    /**
     * @return $this
     */
    public function setTrimmable(bool $trimmable): static
    {
        $this->trimmable = $trimmable;

        return $this;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Question;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * Represents a choice question.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ChoiceQuestion extends Question
{
    private array $choices;
    private bool $multiselect = false;
    private string $prompt = ' > ';
    private string $errorMessage = 'Value "%s" is invalid';

    /**
     * @param string                                   $question The question to ask to the user
     * @param array<string|bool|int|float|\Stringable> $choices  The list of available choices
     * @param string|bool|int|float|null               $default  The default answer to return
     */
    public function __construct(string $question, array $choices, string|bool|int|float|null $default = null)
    {
        if (!$choices) {
            throw new \LogicException('Choice question must have at least 1 choice available.');
        }

        parent::__construct($question, $default);

        $this->choices = $choices;
        $this->setValidator($this->getDefaultValidator());
        $this->setAutocompleterValues($choices);
    }

    /**
     * @return array<string|bool|int|float|\Stringable>
     */
    public function getChoices(): array
    {
        return $this->choices;
    }

    /**
     * Sets multiselect option.
     *
     * When multiselect is set to true, multiple choices can be answered.
     *
     * @return $this
     */
    public function setMultiselect(bool $multiselect): static
    {
        $this->multiselect = $multiselect;
        $this->setValidator($this->getDefaultValidator());

        return $this;
    }

    /**
     * Returns whether the choices are multiselect.
     */
    public function isMultiselect(): bool
    {
        return $this->multiselect;
    }

    /**
     * Gets the prompt for choices.
     */
    public function getPrompt(): string
    {
        return $this->prompt;
    }

    /**
     * Sets the prompt for choices.
     *
     * @return $this
     */
    public function setPrompt(string $prompt): static
    {
        $this->prompt = $prompt;

        return $this;
    }

    /**
     * Sets the error message for invalid values.
     *
     * The error message has a string placeholder (%s) for the invalid value.
     *
     * @return $this
     */
    public function setErrorMessage(string $errorMessage): static
    {
        $this->errorMessage = $errorMessage;
        $this->setValidator($this->getDefaultValidator());

        return $this;
    }

    private function getDefaultValidator(): callable
    {
        $choices = $this->choices;
        $errorMessage = $this->errorMessage;
        $multiselect = $this->multiselect;
        $isAssoc = $this->isAssoc($choices);

        return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) {
            if ($multiselect) {
                // Check for a separated comma values
                if (!preg_match('/^[^,]+(?:,[^,]+)*$/', (string) $selected, $matches)) {
                    throw new InvalidArgumentException(\sprintf($errorMessage, $selected));
                }

                $selectedChoices = explode(',', (string) $selected);
            } else {
                $selectedChoices = [$selected];
            }

            if ($this->isTrimmable()) {
                foreach ($selectedChoices as $k => $v) {
                    $selectedChoices[$k] = trim((string) $v);
                }
            }

            $multiselectChoices = [];
            foreach ($selectedChoices as $value) {
                $results = [];
                foreach ($choices as $key => $choice) {
                    if ($choice === $value) {
                        $results[] = $key;
                    }
                }

                if (\count($results) > 1) {
                    throw new InvalidArgumentException(\sprintf('The provided answer is ambiguous. Value should be one of "%s".', implode('" or "', $results)));
                }

                $result = array_search($value, $choices);

                if (!$isAssoc) {
                    if (false !== $result) {
                        $result = $choices[$result];
                    } elseif (isset($choices[$value])) {
                        $result = $choices[$value];
                    }
                } elseif (false === $result && isset($choices[$value])) {
                    $result = $value;
                }

                if (false === $result) {
                    throw new InvalidArgumentException(\sprintf($errorMessage, $value));
                }

                // For associative choices, consistently return the key as string:
                $multiselectChoices[] = $isAssoc ? (string) $result : $result;
            }

            if ($multiselect) {
                return $multiselectChoices;
            }

            return current($multiselectChoices);
        };
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Command\CompleteCommand;
use Symfony\Component\Console\Command\DumpCompletionCommand;
use Symfony\Component\Console\Command\HelpCommand;
use Symfony\Component\Console\Command\LazyCommand;
use Symfony\Component\Console\Command\ListCommand;
use Symfony\Component\Console\Command\SignalableCommandInterface;
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Completion\Suggestion;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleSignalEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Exception\NamespaceNotFoundException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\DebugFormatterHelper;
use Symfony\Component\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\ProcessHelper;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputAwareInterface;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\SignalRegistry\SignalRegistry;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\ErrorHandler\ErrorHandler;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\Service\ResetInterface;

/**
 * An Application is the container for a collection of commands.
 *
 * It is the main entry point of a Console application.
 *
 * This class is optimized for a standard CLI environment.
 *
 * Usage:
 *
 *     $app = new Application('myapp', '1.0 (stable)');
 *     $app->add(new SimpleCommand());
 *     $app->run();
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Application implements ResetInterface
{
    private array $commands = [];
    private bool $wantHelps = false;
    private ?Command $runningCommand = null;
    private string $name;
    private string $version;
    private ?CommandLoaderInterface $commandLoader = null;
    private bool $catchExceptions = true;
    private bool $catchErrors = false;
    private bool $autoExit = true;
    private InputDefinition $definition;
    private HelperSet $helperSet;
    private ?EventDispatcherInterface $dispatcher = null;
    private Terminal $terminal;
    private string $defaultCommand;
    private bool $singleCommand = false;
    private bool $initialized = false;
    private ?SignalRegistry $signalRegistry = null;
    private array $signalsToDispatchEvent = [];

    public function __construct(string $name = 'UNKNOWN', string $version = 'UNKNOWN')
    {
        $this->name = $name;
        $this->version = $version;
        $this->terminal = new Terminal();
        $this->defaultCommand = 'list';
        if (\defined('SIGINT') && SignalRegistry::isSupported()) {
            $this->signalRegistry = new SignalRegistry();
            $this->signalsToDispatchEvent = [\SIGINT, \SIGTERM, \SIGUSR1, \SIGUSR2];
        }
    }

    /**
     * @final
     */
    public function setDispatcher(EventDispatcherInterface $dispatcher): void
    {
        $this->dispatcher = $dispatcher;
    }

    /**
     * @return void
     */
    public function setCommandLoader(CommandLoaderInterface $commandLoader)
    {
        $this->commandLoader = $commandLoader;
    }

    public function getSignalRegistry(): SignalRegistry
    {
        if (!$this->signalRegistry) {
            throw new RuntimeException('Signals are not supported. Make sure that the "pcntl" extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.');
        }

        return $this->signalRegistry;
    }

    /**
     * @return void
     */
    public function setSignalsToDispatchEvent(int ...$signalsToDispatchEvent)
    {
        $this->signalsToDispatchEvent = $signalsToDispatchEvent;
    }

    /**
     * Runs the current application.
     *
     * @return int 0 if everything went fine, or an error code
     *
     * @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}.
     */
    public function run(?InputInterface $input = null, ?OutputInterface $output = null): int
    {
        if (\function_exists('putenv')) {
            @putenv('LINES='.$this->terminal->getHeight());
            @putenv('COLUMNS='.$this->terminal->getWidth());
        }

        $input ??= new ArgvInput();
        $output ??= new ConsoleOutput();

        $renderException = function (\Throwable $e) use ($output) {
            if ($output instanceof ConsoleOutputInterface) {
                $this->renderThrowable($e, $output->getErrorOutput());
            } else {
                $this->renderThrowable($e, $output);
            }
        };
        if ($phpHandler = set_exception_handler($renderException)) {
            restore_exception_handler();
            if (!\is_array($phpHandler) || !$phpHandler[0] instanceof ErrorHandler) {
                $errorHandler = true;
            } elseif ($errorHandler = $phpHandler[0]->setExceptionHandler($renderException)) {
                $phpHandler[0]->setExceptionHandler($errorHandler);
            }
        }

        try {
            $this->configureIO($input, $output);

            $exitCode = $this->doRun($input, $output);
        } catch (\Throwable $e) {
            if ($e instanceof \Exception && !$this->catchExceptions) {
                throw $e;
            }
            if (!$e instanceof \Exception && !$this->catchErrors) {
                throw $e;
            }

            $renderException($e);

            $exitCode = $e->getCode();
            if (is_numeric($exitCode)) {
                $exitCode = (int) $exitCode;
                if ($exitCode <= 0) {
                    $exitCode = 1;
                }
            } else {
                $exitCode = 1;
            }
        } finally {
            // if the exception handler changed, keep it
            // otherwise, unregister $renderException
            if (!$phpHandler) {
                if (set_exception_handler($renderException) === $renderException) {
                    restore_exception_handler();
                }
                restore_exception_handler();
            } elseif (!$errorHandler) {
                $finalHandler = $phpHandler[0]->setExceptionHandler(null);
                if ($finalHandler !== $renderException) {
                    $phpHandler[0]->setExceptionHandler($finalHandler);
                }
            }
        }

        if ($this->autoExit) {
            if ($exitCode > 255) {
                $exitCode = 255;
            }

            exit($exitCode);
        }

        return $exitCode;
    }

    /**
     * Runs the current application.
     *
     * @return int 0 if everything went fine, or an error code
     */
    public function doRun(InputInterface $input, OutputInterface $output)
    {
        if (true === $input->hasParameterOption(['--version', '-V'], true)) {
            $output->writeln($this->getLongVersion());

            return 0;
        }

        try {
            // Makes ArgvInput::getFirstArgument() able to distinguish an option from an argument.
            $input->bind($this->getDefinition());
        } catch (ExceptionInterface) {
            // Errors must be ignored, full binding/validation happens later when the command is known.
        }

        $name = $this->getCommandName($input);
        if (true === $input->hasParameterOption(['--help', '-h'], true)) {
            if (!$name) {
                $name = 'help';
                $input = new ArrayInput(['command_name' => $this->defaultCommand]);
            } else {
                $this->wantHelps = true;
            }
        }

        if (!$name) {
            $name = $this->defaultCommand;
            $definition = $this->getDefinition();
            $definition->setArguments(array_merge(
                $definition->getArguments(),
                [
                    'command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name),
                ]
            ));
        }

        try {
            $this->runningCommand = null;
            // the command name MUST be the first element of the input
            $command = $this->find($name);
        } catch (\Throwable $e) {
            if (($e instanceof CommandNotFoundException && !$e instanceof NamespaceNotFoundException) && 1 === \count($alternatives = $e->getAlternatives()) && $input->isInteractive()) {
                $alternative = $alternatives[0];

                $style = new SymfonyStyle($input, $output);
                $output->writeln('');
                $formattedBlock = (new FormatterHelper())->formatBlock(\sprintf('Command "%s" is not defined.', $name), 'error', true);
                $output->writeln($formattedBlock);
                if (!$style->confirm(\sprintf('Do you want to run "%s" instead? ', $alternative), false)) {
                    if (null !== $this->dispatcher) {
                        $event = new ConsoleErrorEvent($input, $output, $e);
                        $this->dispatcher->dispatch($event, ConsoleEvents::ERROR);

                        return $event->getExitCode();
                    }

                    return 1;
                }

                $command = $this->find($alternative);
            } else {
                if (null !== $this->dispatcher) {
                    $event = new ConsoleErrorEvent($input, $output, $e);
                    $this->dispatcher->dispatch($event, ConsoleEvents::ERROR);

                    if (0 === $event->getExitCode()) {
                        return 0;
                    }

                    $e = $event->getError();
                }

                try {
                    if ($e instanceof CommandNotFoundException && $namespace = $this->findNamespace($name)) {
                        $helper = new DescriptorHelper();
                        $helper->describe($output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output, $this, [
                            'format' => 'txt',
                            'raw_text' => false,
                            'namespace' => $namespace,
                            'short' => false,
                        ]);

                        return isset($event) ? $event->getExitCode() : 1;
                    }

                    throw $e;
                } catch (NamespaceNotFoundException) {
                    throw $e;
                }
            }
        }

        if ($command instanceof LazyCommand) {
            $command = $command->getCommand();
        }

        $this->runningCommand = $command;
        $exitCode = $this->doRunCommand($command, $input, $output);
        $this->runningCommand = null;

        return $exitCode;
    }

    /**
     * @return void
     */
    public function reset()
    {
    }

    /**
     * @return void
     */
    public function setHelperSet(HelperSet $helperSet)
    {
        $this->helperSet = $helperSet;
    }

    /**
     * Get the helper set associated with the command.
     */
    public function getHelperSet(): HelperSet
    {
        return $this->helperSet ??= $this->getDefaultHelperSet();
    }

    /**
     * @return void
     */
    public function setDefinition(InputDefinition $definition)
    {
        $this->definition = $definition;
    }

    /**
     * Gets the InputDefinition related to this Application.
     */
    public function getDefinition(): InputDefinition
    {
        $this->definition ??= $this->getDefaultInputDefinition();

        if ($this->singleCommand) {
            $inputDefinition = $this->definition;
            $inputDefinition->setArguments();

            return $inputDefinition;
        }

        return $this->definition;
    }

    /**
     * Adds suggestions to $suggestions for the current completion input (e.g. option or argument).
     */
    public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
    {
        if (
            CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType()
            && 'command' === $input->getCompletionName()
        ) {
            foreach ($this->all() as $name => $command) {
                // skip hidden commands and aliased commands as they already get added below
                if ($command->isHidden() || $command->getName() !== $name) {
                    continue;
                }
                $suggestions->suggestValue(new Suggestion($command->getName(), $command->getDescription()));
                foreach ($command->getAliases() as $name) {
                    $suggestions->suggestValue(new Suggestion($name, $command->getDescription()));
                }
            }

            return;
        }

        if (CompletionInput::TYPE_OPTION_NAME === $input->getCompletionType()) {
            $suggestions->suggestOptions($this->getDefinition()->getOptions());

            return;
        }

        if (
            CompletionInput::TYPE_OPTION_VALUE === $input->getCompletionType()
            && ($definition = $this->getDefinition())->hasOption($input->getCompletionName())
        ) {
            $definition->getOption($input->getCompletionName())->complete($input, $suggestions);

            return;
        }
    }

    /**
     * Gets the help message.
     */
    public function getHelp(): string
    {
        return $this->getLongVersion();
    }

    /**
     * Gets whether to catch exceptions or not during commands execution.
     */
    public function areExceptionsCaught(): bool
    {
        return $this->catchExceptions;
    }

    /**
     * Sets whether to catch exceptions or not during commands execution.
     *
     * @return void
     */
    public function setCatchExceptions(bool $boolean)
    {
        $this->catchExceptions = $boolean;
    }

    /**
     * Sets whether to catch errors or not during commands execution.
     */
    public function setCatchErrors(bool $catchErrors = true): void
    {
        $this->catchErrors = $catchErrors;
    }

    /**
     * Gets whether to automatically exit after a command execution or not.
     */
    public function isAutoExitEnabled(): bool
    {
        return $this->autoExit;
    }

    /**
     * Sets whether to automatically exit after a command execution or not.
     *
     * @return void
     */
    public function setAutoExit(bool $boolean)
    {
        $this->autoExit = $boolean;
    }

    /**
     * Gets the name of the application.
     */
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * Sets the application name.
     *
     * @return void
     */
    public function setName(string $name)
    {
        $this->name = $name;
    }

    /**
     * Gets the application version.
     */
    public function getVersion(): string
    {
        return $this->version;
    }

    /**
     * Sets the application version.
     *
     * @return void
     */
    public function setVersion(string $version)
    {
        $this->version = $version;
    }

    /**
     * Returns the long version of the application.
     *
     * @return string
     */
    public function getLongVersion()
    {
        if ('UNKNOWN' !== $this->getName()) {
            if ('UNKNOWN' !== $this->getVersion()) {
                return \sprintf('%s <info>%s</info>', $this->getName(), $this->getVersion());
            }

            return $this->getName();
        }

        return 'Console Tool';
    }

    /**
     * Registers a new command.
     */
    public function register(string $name): Command
    {
        return $this->add(new Command($name));
    }

    /**
     * Adds an array of command objects.
     *
     * If a Command is not enabled it will not be added.
     *
     * @param Command[] $commands An array of commands
     *
     * @return void
     */
    public function addCommands(array $commands)
    {
        foreach ($commands as $command) {
            $this->add($command);
        }
    }

    /**
     * Adds a command object.
     *
     * If a command with the same name already exists, it will be overridden.
     * If the command is not enabled it will not be added.
     *
     * @return Command|null
     */
    public function add(Command $command)
    {
        $this->init();

        $command->setApplication($this);

        if (!$command->isEnabled()) {
            $command->setApplication(null);

            return null;
        }

        if (!$command instanceof LazyCommand) {
            // Will throw if the command is not correctly initialized.
            $command->getDefinition();
        }

        if (!$command->getName()) {
            throw new LogicException(\sprintf('The command defined in "%s" cannot have an empty name.', get_debug_type($command)));
        }

        $this->commands[$command->getName()] = $command;

        foreach ($command->getAliases() as $alias) {
            $this->commands[$alias] = $command;
        }

        return $command;
    }

    /**
     * Returns a registered command by name or alias.
     *
     * @return Command
     *
     * @throws CommandNotFoundException When given command name does not exist
     */
    public function get(string $name)
    {
        $this->init();

        if (!$this->has($name)) {
            throw new CommandNotFoundException(\sprintf('The command "%s" does not exist.', $name));
        }

        // When the command has a different name than the one used at the command loader level
        if (!isset($this->commands[$name])) {
            throw new CommandNotFoundException(\sprintf('The "%s" command cannot be found because it is registered under multiple names. Make sure you don\'t set a different name via constructor or "setName()".', $name));
        }

        $command = $this->commands[$name];

        if ($this->wantHelps) {
            $this->wantHelps = false;

            $helpCommand = $this->get('help');
            $helpCommand->setCommand($command);

            return $helpCommand;
        }

        return $command;
    }

    /**
     * Returns true if the command exists, false otherwise.
     */
    public function has(string $name): bool
    {
        $this->init();

        return isset($this->commands[$name]) || ($this->commandLoader?->has($name) && $this->add($this->commandLoader->get($name)));
    }

    /**
     * Returns an array of all unique namespaces used by currently registered commands.
     *
     * It does not return the global namespace which always exists.
     *
     * @return string[]
     */
    public function getNamespaces(): array
    {
        $namespaces = [];
        foreach ($this->all() as $command) {
            if ($command->isHidden()) {
                continue;
            }

            $namespaces[] = $this->extractAllNamespaces($command->getName());

            foreach ($command->getAliases() as $alias) {
                $namespaces[] = $this->extractAllNamespaces($alias);
            }
        }

        return array_values(array_unique(array_filter(array_merge([], ...$namespaces))));
    }

    /**
     * Finds a registered namespace by a name or an abbreviation.
     *
     * @throws NamespaceNotFoundException When namespace is incorrect or ambiguous
     */
    public function findNamespace(string $namespace): string
    {
        $allNamespaces = $this->getNamespaces();
        $expr = implode('[^:]*:', array_map('preg_quote', explode(':', $namespace))).'[^:]*';
        $namespaces = preg_grep('{^'.$expr.'}', $allNamespaces);

        if (empty($namespaces)) {
            $message = \sprintf('There are no commands defined in the "%s" namespace.', $namespace);

            if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) {
                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);
            }

            throw new NamespaceNotFoundException($message, $alternatives);
        }

        $exact = \in_array($namespace, $namespaces, true);
        if (\count($namespaces) > 1 && !$exact) {
            throw new NamespaceNotFoundException(\sprintf("The namespace \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces));
        }

        return $exact ? $namespace : reset($namespaces);
    }

    /**
     * Finds a command by name or alias.
     *
     * Contrary to get, this command tries to find the best
     * match if you give it an abbreviation of a name or alias.
     *
     * @return Command
     *
     * @throws CommandNotFoundException When command name is incorrect or ambiguous
     */
    public function find(string $name)
    {
        $this->init();

        $aliases = [];

        foreach ($this->commands as $command) {
            foreach ($command->getAliases() as $alias) {
                if (!$this->has($alias)) {
                    $this->commands[$alias] = $command;
                }
            }
        }

        if ($this->has($name)) {
            return $this->get($name);
        }

        $allCommands = $this->commandLoader ? array_merge($this->commandLoader->getNames(), array_keys($this->commands)) : array_keys($this->commands);
        $expr = implode('[^:]*:', array_map('preg_quote', explode(':', $name))).'[^:]*';
        $commands = preg_grep('{^'.$expr.'}', $allCommands);

        if (empty($commands)) {
            $commands = preg_grep('{^'.$expr.'}i', $allCommands);
        }

        // if no commands matched or we just matched namespaces
        if (empty($commands) || \count(preg_grep('{^'.$expr.'$}i', $commands)) < 1) {
            if (false !== $pos = strrpos($name, ':')) {
                // check if a namespace exists and contains commands
                $this->findNamespace(substr($name, 0, $pos));
            }

            $message = \sprintf('Command "%s" is not defined.', $name);

            if ($alternatives = $this->findAlternatives($name, $allCommands)) {
                $wantHelps = $this->wantHelps;
                $this->wantHelps = false;

                // remove hidden commands
                if ($alternatives = array_filter($alternatives, fn ($name) => !$this->get($name)->isHidden())) {
                    $message .= \sprintf("\n\nDid you mean %s?\n    %s", 1 === \count($alternatives) ? 'this' : 'one of these', implode("\n    ", $alternatives));
                }
                $this->wantHelps = $wantHelps;
            }

            throw new CommandNotFoundException($message, array_values($alternatives));
        }

        // filter out aliases for commands which are already on the list
        if (\count($commands) > 1) {
            $commandList = $this->commandLoader ? array_merge(array_flip($this->commandLoader->getNames()), $this->commands) : $this->commands;
            $commands = array_unique(array_filter($commands, function ($nameOrAlias) use (&$commandList, $commands, &$aliases) {
                if (!$commandList[$nameOrAlias] instanceof Command) {
                    $commandList[$nameOrAlias] = $this->commandLoader->get($nameOrAlias);
                }

                $commandName = $commandList[$nameOrAlias]->getName();

                $aliases[$nameOrAlias] = $commandName;

                return $commandName === $nameOrAlias || !\in_array($commandName, $commands);
            }));
        }

        // check whether all commands left are aliases to the same one
        if (\count($commands) > 1) {
            $uniqueCommands = array_unique(array_map(function ($nameOrAlias) use (&$commandList) {
                if (!$commandList[$nameOrAlias] instanceof Command) {
                    $commandList[$nameOrAlias] = $this->commandLoader->get($nameOrAlias);
                }

                return $commandList[$nameOrAlias]->getName();
            }, $commands));

            if (1 === \count($uniqueCommands)) {
                $commands = [reset($uniqueCommands)];
            }
        }

        if (\count($commands) > 1) {
            $usableWidth = $this->terminal->getWidth() - 10;
            $abbrevs = array_values($commands);
            $maxLen = 0;
            foreach ($abbrevs as $abbrev) {
                $maxLen = max(Helper::width($abbrev), $maxLen);
            }
            $abbrevs = array_map(static function ($cmd) use ($commandList, $usableWidth, $maxLen, &$commands) {
                if ($commandList[$cmd]->isHidden()) {
                    unset($commands[array_search($cmd, $commands)]);

                    return false;
                }

                $abbrev = str_pad($cmd, $maxLen, ' ').' '.$commandList[$cmd]->getDescription();

                return Helper::width($abbrev) > $usableWidth ? Helper::substr($abbrev, 0, $usableWidth - 3).'...' : $abbrev;
            }, array_values($commands));

            if (\count($commands) > 1) {
                $suggestions = $this->getAbbreviationSuggestions(array_filter($abbrevs));

                throw new CommandNotFoundException(\sprintf("Command \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $name, $suggestions), array_values($commands));
            }
        }

        $command = $commands ? $this->get(reset($commands)) : null;

        if (!$command || $command->isHidden()) {
            throw new CommandNotFoundException(\sprintf('The command "%s" does not exist.', $name));
        }

        return $command;
    }

    /**
     * Gets the commands (registered in the given namespace if provided).
     *
     * The array keys are the full names and the values the command instances.
     *
     * @return Command[]
     */
    public function all(?string $namespace = null)
    {
        $this->init();

        if (null === $namespace) {
            if (!$this->commandLoader) {
                return $this->commands;
            }

            $commands = $this->commands;
            foreach ($this->commandLoader->getNames() as $name) {
                if (!isset($commands[$name]) && $this->has($name)) {
                    $commands[$name] = $this->get($name);
                }
            }

            return $commands;
        }

        $commands = [];
        foreach ($this->commands as $name => $command) {
            if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) {
                $commands[$name] = $command;
            }
        }

        if ($this->commandLoader) {
            foreach ($this->commandLoader->getNames() as $name) {
                if (!isset($commands[$name]) && $namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1) && $this->has($name)) {
                    $commands[$name] = $this->get($name);
                }
            }
        }

        return $commands;
    }

    /**
     * Returns an array of possible abbreviations given a set of names.
     *
     * @return string[][]
     */
    public static function getAbbreviations(array $names): array
    {
        $abbrevs = [];
        foreach ($names as $name) {
            for ($len = \strlen($name); $len > 0; --$len) {
                $abbrev = substr($name, 0, $len);
                $abbrevs[$abbrev][] = $name;
            }
        }

        return $abbrevs;
    }

    public function renderThrowable(\Throwable $e, OutputInterface $output): void
    {
        $output->writeln('', OutputInterface::VERBOSITY_QUIET);

        $this->doRenderThrowable($e, $output);

        if (null !== $this->runningCommand) {
            $output->writeln(\sprintf('<info>%s</info>', OutputFormatter::escape(\sprintf($this->runningCommand->getSynopsis(), $this->getName()))), OutputInterface::VERBOSITY_QUIET);
            $output->writeln('', OutputInterface::VERBOSITY_QUIET);
        }
    }

    protected function doRenderThrowable(\Throwable $e, OutputInterface $output): void
    {
        do {
            $message = trim($e->getMessage());
            if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
                $class = get_debug_type($e);
                $title = \sprintf('  [%s%s]  ', $class, 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : '');
                $len = Helper::width($title);
            } else {
                $len = 0;
            }

            if (str_contains($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]++/', static fn ($m) => class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0], $message);
            }

            $width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : \PHP_INT_MAX;
            $lines = [];
            foreach ('' !== $message ? preg_split('/\r?\n/', $message) : [] as $line) {
                foreach ($this->splitStringByWidth($line, $width - 4) as $line) {
                    // pre-format lines to get the right string length
                    $lineLength = Helper::width($line) + 4;
                    $lines[] = [$line, $lineLength];

                    $len = max($lineLength, $len);
                }
            }

            $messages = [];
            if (!$e instanceof ExceptionInterface || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
                $messages[] = \sprintf('<comment>%s</comment>', OutputFormatter::escape(\sprintf('In %s line %s:', basename($e->getFile()) ?: 'n/a', $e->getLine() ?: 'n/a')));
            }
            $messages[] = $emptyLine = \sprintf('<error>%s</error>', str_repeat(' ', $len));
            if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
                $messages[] = \sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - Helper::width($title))));
            }
            foreach ($lines as $line) {
                $messages[] = \sprintf('<error>  %s  %s</error>', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1]));
            }
            $messages[] = $emptyLine;
            $messages[] = '';

            $output->writeln($messages, OutputInterface::VERBOSITY_QUIET);

            if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
                $output->writeln('<comment>Exception trace:</comment>', OutputInterface::VERBOSITY_QUIET);

                // exception related properties
                $trace = $e->getTrace();

                array_unshift($trace, [
                    'function' => '',
                    'file' => $e->getFile() ?: 'n/a',
                    'line' => $e->getLine() ?: 'n/a',
                    'args' => [],
                ]);

                for ($i = 0, $count = \count($trace); $i < $count; ++$i) {
                    $class = $trace[$i]['class'] ?? '';
                    $type = $trace[$i]['type'] ?? '';
                    $function = $trace[$i]['function'] ?? '';
                    $file = $trace[$i]['file'] ?? 'n/a';
                    $line = $trace[$i]['line'] ?? 'n/a';

                    $output->writeln(\sprintf(' %s%s at <info>%s:%s</info>', $class, $function ? $type.$function.'()' : '', $file, $line), OutputInterface::VERBOSITY_QUIET);
                }

                $output->writeln('', OutputInterface::VERBOSITY_QUIET);
            }
        } while ($e = $e->getPrevious());
    }

    /**
     * Configures the input and output instances based on the user arguments and options.
     *
     * @return void
     */
    protected function configureIO(InputInterface $input, OutputInterface $output)
    {
        if (true === $input->hasParameterOption(['--ansi'], true)) {
            $output->setDecorated(true);
        } elseif (true === $input->hasParameterOption(['--no-ansi'], true)) {
            $output->setDecorated(false);
        }

        if (true === $input->hasParameterOption(['--no-interaction', '-n'], true)) {
            $input->setInteractive(false);
        }

        switch ($shellVerbosity = (int) getenv('SHELL_VERBOSITY')) {
            case -1:
                $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
                break;
            case 1:
                $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
                break;
            case 2:
                $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
                break;
            case 3:
                $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
                break;
            default:
                $shellVerbosity = 0;
                break;
        }

        if (true === $input->hasParameterOption(['--quiet', '-q'], true)) {
            $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
            $shellVerbosity = -1;
        } else {
            if ($input->hasParameterOption('-vvv', true) || $input->hasParameterOption('--verbose=3', true) || 3 === $input->getParameterOption('--verbose', false, true)) {
                $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
                $shellVerbosity = 3;
            } elseif ($input->hasParameterOption('-vv', true) || $input->hasParameterOption('--verbose=2', true) || 2 === $input->getParameterOption('--verbose', false, true)) {
                $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
                $shellVerbosity = 2;
            } elseif ($input->hasParameterOption('-v', true) || $input->hasParameterOption('--verbose=1', true) || $input->hasParameterOption('--verbose', true) || $input->getParameterOption('--verbose', false, true)) {
                $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
                $shellVerbosity = 1;
            }
        }

        if (-1 === $shellVerbosity) {
            $input->setInteractive(false);
        }

        if (\function_exists('putenv')) {
            @putenv('SHELL_VERBOSITY='.$shellVerbosity);
        }
        $_ENV['SHELL_VERBOSITY'] = $shellVerbosity;
        $_SERVER['SHELL_VERBOSITY'] = $shellVerbosity;
    }

    /**
     * Runs the current command.
     *
     * If an event dispatcher has been attached to the application,
     * events are also dispatched during the life-cycle of the command.
     *
     * @return int 0 if everything went fine, or an error code
     */
    protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
    {
        foreach ($command->getHelperSet() as $helper) {
            if ($helper instanceof InputAwareInterface) {
                $helper->setInput($input);
            }
        }

        $registeredSignals = false;
        $commandSignals = $command instanceof SignalableCommandInterface ? $command->getSubscribedSignals() : [];
        if ($commandSignals || $this->dispatcher && $this->signalsToDispatchEvent) {
            if (!$this->signalRegistry) {
                throw new RuntimeException('Unable to subscribe to signal events. Make sure that the "pcntl" extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.');
            }

            $registeredSignals = true;
            $this->getSignalRegistry()->pushCurrentHandlers();

            if ($this->dispatcher) {
                // We register application signals, so that we can dispatch the event
                foreach ($this->signalsToDispatchEvent as $signal) {
                    $event = new ConsoleSignalEvent($command, $input, $output, $signal);

                    $this->signalRegistry->register($signal, function ($signal) use ($event, $command, $commandSignals) {
                        $this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL);
                        $exitCode = $event->getExitCode();

                        // If the command is signalable, we call the handleSignal() method
                        if (\in_array($signal, $commandSignals, true)) {
                            $exitCode = $command->handleSignal($signal, $exitCode);
                            // BC layer for Symfony <= 5
                            if (null === $exitCode) {
                                trigger_deprecation('symfony/console', '6.3', 'Not returning an exit code from "%s::handleSignal()" is deprecated, return "false" to keep the command running or "0" to exit successfully.', get_debug_type($command));
                                $exitCode = 0;
                            }
                        }

                        if (false !== $exitCode) {
                            $event = new ConsoleTerminateEvent($command, $event->getInput(), $event->getOutput(), $exitCode, $signal);
                            $this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE);

                            exit($event->getExitCode());
                        }
                    });
                }

                // then we register command signals, but not if already handled after the dispatcher
                $commandSignals = array_diff($commandSignals, $this->signalsToDispatchEvent);
            }

            foreach ($commandSignals as $signal) {
                $this->signalRegistry->register($signal, static function (int $signal) use ($command): void {
                    $exitCode = $command->handleSignal($signal);
                    // BC layer for Symfony <= 5
                    if (null === $exitCode) {
                        trigger_deprecation('symfony/console', '6.3', 'Not returning an exit code from "%s::handleSignal()" is deprecated, return "false" to keep the command running or "0" to exit successfully.', get_debug_type($command));
                        $exitCode = 0;
                    }

                    if (false !== $exitCode) {
                        exit($exitCode);
                    }
                });
            }
        }

        if (null === $this->dispatcher) {
            try {
                return $command->run($input, $output);
            } finally {
                if ($registeredSignals) {
                    $this->getSignalRegistry()->popPreviousHandlers();
                }
            }
        }

        // bind before the console.command event, so the listeners have access to input options/arguments
        try {
            $command->mergeApplicationDefinition();
            $input->bind($command->getDefinition());
        } catch (ExceptionInterface) {
            // ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition
        }

        $event = new ConsoleCommandEvent($command, $input, $output);
        $e = null;

        try {
            $this->dispatcher->dispatch($event, ConsoleEvents::COMMAND);

            if ($event->commandShouldRun()) {
                $exitCode = $command->run($input, $output);
            } else {
                $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED;
            }
        } catch (\Throwable $e) {
            $event = new ConsoleErrorEvent($input, $output, $e, $command);
            $this->dispatcher->dispatch($event, ConsoleEvents::ERROR);
            $e = $event->getError();

            if (0 === $exitCode = $event->getExitCode()) {
                $e = null;
            }
        } finally {
            if ($registeredSignals) {
                $this->getSignalRegistry()->popPreviousHandlers();
            }
        }

        $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode);
        $this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE);

        if (null !== $e) {
            throw $e;
        }

        return $event->getExitCode();
    }

    /**
     * Gets the name of the command based on input.
     */
    protected function getCommandName(InputInterface $input): ?string
    {
        return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument();
    }

    /**
     * Gets the default input definition.
     */
    protected function getDefaultInputDefinition(): InputDefinition
    {
        return new InputDefinition([
            new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
            new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display help for the given command. When no command is given display help for the <info>'.$this->defaultCommand.'</info> command'),
            new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'),
            new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'),
            new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'),
            new InputOption('--ansi', '', InputOption::VALUE_NEGATABLE, 'Force (or disable --no-ansi) ANSI output', null),
            new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'),
        ]);
    }

    /**
     * Gets the default commands that should always be available.
     *
     * @return Command[]
     */
    protected function getDefaultCommands(): array
    {
        return [new HelpCommand(), new ListCommand(), new CompleteCommand(), new DumpCompletionCommand()];
    }

    /**
     * Gets the default helper set with the helpers that should always be available.
     */
    protected function getDefaultHelperSet(): HelperSet
    {
        return new HelperSet([
            new FormatterHelper(),
            new DebugFormatterHelper(),
            new ProcessHelper(),
            new QuestionHelper(),
        ]);
    }

    /**
     * Returns abbreviated suggestions in string format.
     */
    private function getAbbreviationSuggestions(array $abbrevs): string
    {
        return '    '.implode("\n    ", $abbrevs);
    }

    /**
     * Returns the namespace part of the command name.
     *
     * This method is not part of public API and should not be used directly.
     */
    public function extractNamespace(string $name, ?int $limit = null): string
    {
        $parts = explode(':', $name, -1);

        return implode(':', null === $limit ? $parts : \array_slice($parts, 0, $limit));
    }

    /**
     * Finds alternative of $name among $collection,
     * if nothing is found in $collection, try in $abbrevs.
     *
     * @return string[]
     */
    private function findAlternatives(string $name, iterable $collection): array
    {
        $threshold = 1e3;
        $alternatives = [];

        $collectionParts = [];
        foreach ($collection as $item) {
            $collectionParts[$item] = explode(':', $item);
        }

        foreach (explode(':', $name) as $i => $subname) {
            foreach ($collectionParts as $collectionName => $parts) {
                $exists = isset($alternatives[$collectionName]);
                if (!isset($parts[$i]) && $exists) {
                    $alternatives[$collectionName] += $threshold;
                    continue;
                } elseif (!isset($parts[$i])) {
                    continue;
                }

                $lev = levenshtein($subname, $parts[$i]);
                if ($lev <= \strlen($subname) / 3 || '' !== $subname && str_contains($parts[$i], $subname)) {
                    $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev;
                } elseif ($exists) {
                    $alternatives[$collectionName] += $threshold;
                }
            }
        }

        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;
            }
        }

        $alternatives = array_filter($alternatives, static fn ($lev) => $lev < 2 * $threshold);
        ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE);

        return array_keys($alternatives);
    }

    /**
     * Sets the default Command name.
     *
     * @return $this
     */
    public function setDefaultCommand(string $commandName, bool $isSingleCommand = false): static
    {
        $this->defaultCommand = explode('|', ltrim($commandName, '|'))[0];

        if ($isSingleCommand) {
            // Ensure the command exist
            $this->find($commandName);

            $this->singleCommand = true;
        }

        return $this;
    }

    /**
     * @internal
     */
    public function isSingleCommand(): bool
    {
        return $this->singleCommand;
    }

    private function splitStringByWidth(string $string, int $width): array
    {
        // str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly.
        // additionally, array_slice() is not enough as some character has doubled width.
        // we need a function to split string not by character count but by string width
        if (false === $encoding = mb_detect_encoding($string, null, true)) {
            return str_split($string, $width);
        }

        $utf8String = mb_convert_encoding($string, 'utf8', $encoding);
        $lines = [];
        $line = '';

        $offset = 0;
        while (preg_match('/.{1,10000}/u', $utf8String, $m, 0, $offset)) {
            $offset += \strlen($m[0]);

            foreach (preg_split('//u', $m[0]) as $char) {
                // test if $char could be appended to current line
                if (Helper::width($line.$char) <= $width) {
                    $line .= $char;
                    continue;
                }
                // if not, push current line to array and make new line
                $lines[] = str_pad($line, $width);
                $line = $char;
            }
        }

        $lines[] = \count($lines) ? str_pad($line, $width) : $line;

        mb_convert_variables($encoding, 'utf8', $lines);

        return $lines;
    }

    /**
     * Returns all namespaces of the command name.
     *
     * @return string[]
     */
    private function extractAllNamespaces(string $name): array
    {
        // -1 as third argument is needed to skip the command short name when exploding
        $parts = explode(':', $name, -1);
        $namespaces = [];

        foreach ($parts as $part) {
            if (\count($namespaces)) {
                $namespaces[] = end($namespaces).':'.$part;
            } else {
                $namespaces[] = $part;
            }
        }

        return $namespaces;
    }

    private function init(): void
    {
        if ($this->initialized) {
            return;
        }
        $this->initialized = true;

        foreach ($this->getDefaultCommands() as $command) {
            $this->add($command);
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\EventListener;

use Psr\Log\LoggerInterface;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * @author James Halsall <james.t.halsall@googlemail.com>
 * @author Robin Chalas <robin.chalas@gmail.com>
 */
class ErrorListener implements EventSubscriberInterface
{
    private ?LoggerInterface $logger;

    public function __construct(?LoggerInterface $logger = null)
    {
        $this->logger = $logger;
    }

    /**
     * @return void
     */
    public function onConsoleError(ConsoleErrorEvent $event)
    {
        if (null === $this->logger) {
            return;
        }

        $error = $event->getError();

        if (!$inputString = $this->getInputString($event)) {
            $this->logger->critical('An error occurred while using the console. Message: "{message}"', ['exception' => $error, 'message' => $error->getMessage()]);

            return;
        }

        $this->logger->critical('Error thrown while running command "{command}". Message: "{message}"', ['exception' => $error, 'command' => $inputString, 'message' => $error->getMessage()]);
    }

    /**
     * @return void
     */
    public function onConsoleTerminate(ConsoleTerminateEvent $event)
    {
        if (null === $this->logger) {
            return;
        }

        $exitCode = $event->getExitCode();

        if (0 === $exitCode) {
            return;
        }

        if (!$inputString = $this->getInputString($event)) {
            $this->logger->debug('The console exited with code "{code}"', ['code' => $exitCode]);

            return;
        }

        $this->logger->debug('Command "{command}" exited with code "{code}"', ['command' => $inputString, 'code' => $exitCode]);
    }

    public static function getSubscribedEvents(): array
    {
        return [
            ConsoleEvents::ERROR => ['onConsoleError', -128],
            ConsoleEvents::TERMINATE => ['onConsoleTerminate', -128],
        ];
    }

    private static function getInputString(ConsoleEvent $event): ?string
    {
        $commandName = $event->getCommand()?->getName();
        $input = $event->getInput();

        if ($input instanceof \Stringable) {
            if ($commandName) {
                return str_replace(["'$commandName'", "\"$commandName\""], $commandName, (string) $input);
            }

            return (string) $input;
        }

        return $commandName;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * ExceptionInterface.
 *
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
interface ExceptionInterface extends \Throwable
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * Represents an incorrect namespace typed in the console.
 *
 * @author Pierre du Plessis <pdples@gmail.com>
 */
class NamespaceNotFoundException extends CommandNotFoundException
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class LogicException extends \LogicException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * Represents failure to read input from stdin.
 *
 * @author Gabriel Ostrolucký <gabriel.ostrolucky@gmail.com>
 */
class MissingInputException extends RuntimeException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

use Symfony\Component\Console\Messenger\RunCommandContext;

/**
 * @author Kevin Bond <kevinbond@gmail.com>
 */
final class RunCommandFailedException extends RuntimeException
{
    public function __construct(\Throwable|string $exception, public readonly RunCommandContext $context)
    {
        parent::__construct(
            $exception instanceof \Throwable ? $exception->getMessage() : $exception,
            $exception instanceof \Throwable && \is_int($exception->getCode()) ? $exception->getCode() : 0,
            $exception instanceof \Throwable ? $exception : null,
        );
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * Represents an incorrect command name typed in the console.
 *
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class CommandNotFoundException extends \InvalidArgumentException implements ExceptionInterface
{
    private array $alternatives;

    /**
     * @param string          $message      Exception message to throw
     * @param string[]        $alternatives List of similar defined names
     * @param int             $code         Exception code
     * @param \Throwable|null $previous     Previous exception used for the exception chaining
     */
    public function __construct(string $message, array $alternatives = [], int $code = 0, ?\Throwable $previous = null)
    {
        parent::__construct($message, $code, $previous);

        $this->alternatives = $alternatives;
    }

    /**
     * @return string[]
     */
    public function getAlternatives(): array
    {
        return $this->alternatives;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * Represents an incorrect option name or value typed in the console.
 *
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class InvalidOptionException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

use Symfony\Component\Console\Color;

/**
 * Formatter style class for defining styles.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class OutputFormatterStyle implements OutputFormatterStyleInterface
{
    private Color $color;
    private string $foreground;
    private string $background;
    private array $options;
    private ?string $href = null;
    private bool $handlesHrefGracefully;

    /**
     * Initializes output formatter style.
     *
     * @param string|null $foreground The style foreground color name
     * @param string|null $background The style background color name
     */
    public function __construct(?string $foreground = null, ?string $background = null, array $options = [])
    {
        $this->color = new Color($this->foreground = $foreground ?: '', $this->background = $background ?: '', $this->options = $options);
    }

    /**
     * @return void
     */
    public function setForeground(?string $color = null)
    {
        if (1 > \func_num_args()) {
            trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
        }
        $this->color = new Color($this->foreground = $color ?: '', $this->background, $this->options);
    }

    /**
     * @return void
     */
    public function setBackground(?string $color = null)
    {
        if (1 > \func_num_args()) {
            trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
        }
        $this->color = new Color($this->foreground, $this->background = $color ?: '', $this->options);
    }

    public function setHref(string $url): void
    {
        $this->href = $url;
    }

    /**
     * @return void
     */
    public function setOption(string $option)
    {
        $this->options[] = $option;
        $this->color = new Color($this->foreground, $this->background, $this->options);
    }

    /**
     * @return void
     */
    public function unsetOption(string $option)
    {
        $pos = array_search($option, $this->options);
        if (false !== $pos) {
            unset($this->options[$pos]);
        }

        $this->color = new Color($this->foreground, $this->background, $this->options);
    }

    /**
     * @return void
     */
    public function setOptions(array $options)
    {
        $this->color = new Color($this->foreground, $this->background, $this->options = $options);
    }

    public function apply(string $text): string
    {
        $this->handlesHrefGracefully ??= 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR')
            && (!getenv('KONSOLE_VERSION') || (int) getenv('KONSOLE_VERSION') > 201100)
            && !isset($_SERVER['IDEA_INITIAL_DIRECTORY']);

        if (null !== $this->href && $this->handlesHrefGracefully) {
            $text = "\033]8;;$this->href\033\\$text\033]8;;\033\\";
        }

        return $this->color->apply($text);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

/**
 * Formatter interface for console output.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
interface OutputFormatterInterface
{
    /**
     * Sets the decorated flag.
     *
     * @return void
     */
    public function setDecorated(bool $decorated);

    /**
     * Whether the output will decorate messages.
     */
    public function isDecorated(): bool;

    /**
     * Sets a new style.
     *
     * @return void
     */
    public function setStyle(string $name, OutputFormatterStyleInterface $style);

    /**
     * Checks if output formatter has style with specified name.
     */
    public function hasStyle(string $name): bool;

    /**
     * Gets style options from style with specified name.
     *
     * @throws \InvalidArgumentException When style isn't defined
     */
    public function getStyle(string $name): OutputFormatterStyleInterface;

    /**
     * Formats a message according to the given styles.
     */
    public function format(?string $message): ?string;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Helper\Helper;

use function Symfony\Component\String\b;

/**
 * Formatter class for console output.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Roland Franssen <franssen.roland@gmail.com>
 */
class OutputFormatter implements WrappableOutputFormatterInterface
{
    private bool $decorated;
    private array $styles = [];
    private OutputFormatterStyleStack $styleStack;

    public function __clone()
    {
        $this->styleStack = clone $this->styleStack;
        foreach ($this->styles as $key => $value) {
            $this->styles[$key] = clone $value;
        }
    }

    /**
     * Escapes "<" and ">" special chars in given text.
     */
    public static function escape(string $text): string
    {
        $text = preg_replace('/([^\\\\]|^)([<>])/', '$1\\\\$2', $text);

        return self::escapeTrailingBackslash($text);
    }

    /**
     * Escapes trailing "\" in given text.
     *
     * @internal
     */
    public static function escapeTrailingBackslash(string $text): string
    {
        if (str_ends_with($text, '\\')) {
            $len = \strlen($text);
            $text = rtrim($text, '\\');
            $text = str_replace("\0", '', $text);
            $text .= str_repeat("\0", $len - \strlen($text));
        }

        return $text;
    }

    /**
     * Initializes console output formatter.
     *
     * @param OutputFormatterStyleInterface[] $styles Array of "name => FormatterStyle" instances
     */
    public function __construct(bool $decorated = false, array $styles = [])
    {
        $this->decorated = $decorated;

        $this->setStyle('error', new OutputFormatterStyle('white', 'red'));
        $this->setStyle('info', new OutputFormatterStyle('green'));
        $this->setStyle('comment', new OutputFormatterStyle('yellow'));
        $this->setStyle('question', new OutputFormatterStyle('black', 'cyan'));

        foreach ($styles as $name => $style) {
            $this->setStyle($name, $style);
        }

        $this->styleStack = new OutputFormatterStyleStack();
    }

    /**
     * @return void
     */
    public function setDecorated(bool $decorated)
    {
        $this->decorated = $decorated;
    }

    public function isDecorated(): bool
    {
        return $this->decorated;
    }

    /**
     * @return void
     */
    public function setStyle(string $name, OutputFormatterStyleInterface $style)
    {
        $this->styles[strtolower($name)] = $style;
    }

    public function hasStyle(string $name): bool
    {
        return isset($this->styles[strtolower($name)]);
    }

    public function getStyle(string $name): OutputFormatterStyleInterface
    {
        if (!$this->hasStyle($name)) {
            throw new InvalidArgumentException(\sprintf('Undefined style: "%s".', $name));
        }

        return $this->styles[strtolower($name)];
    }

    public function format(?string $message): ?string
    {
        return $this->formatAndWrap($message, 0);
    }

    /**
     * @return string
     */
    public function formatAndWrap(?string $message, int $width)
    {
        if (null === $message) {
            return '';
        }

        // For ASCII-only strings, byte positions equal character positions,
        // so we can use native strlen/substr which is much faster than Helper::length/substr.
        $isAscii = !preg_match('/[\x80-\xFF]/', $message);

        $offset = 0;
        $output = '';
        $openTagRegex = '[a-z](?:[^\\\\<>]*+ | \\\\.)*';
        $closeTagRegex = '[a-z][^<>]*+';
        $currentLineLength = 0;
        preg_match_all("#<(($openTagRegex) | /($closeTagRegex)?)>#ix", $message, $matches, \PREG_OFFSET_CAPTURE);
        foreach ($matches[0] as $i => $match) {
            $pos = $match[1];
            $text = $match[0];

            if (0 != $pos && '\\' == $message[$pos - 1]) {
                continue;
            }

            if ($isAscii) {
                // For ASCII, byte position = character position, no conversion needed
                $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset), $output, $width, $currentLineLength);
                $offset = $pos + \strlen($text);
            } else {
                // convert byte position to character position.
                $pos = Helper::length(substr($message, 0, $pos));
                // add the text up to the next tag
                $output .= $this->applyCurrentStyle(Helper::substr($message, $offset, $pos - $offset), $output, $width, $currentLineLength);
                $offset = $pos + Helper::length($text);
            }

            // opening tag?
            if ($open = '/' !== $text[1]) {
                $tag = $matches[1][$i][0];
            } else {
                $tag = $matches[3][$i][0] ?? '';
            }

            if (!$open && !$tag) {
                // </>
                $this->styleStack->pop();
            } elseif (null === $style = $this->createStyleFromString($tag)) {
                $output .= $this->applyCurrentStyle($text, $output, $width, $currentLineLength);
            } elseif ($open) {
                $this->styleStack->push($style);
            } else {
                $this->styleStack->pop($style);
            }
        }

        $output .= $this->applyCurrentStyle($isAscii ? substr($message, $offset) : Helper::substr($message, $offset), $output, $width, $currentLineLength);

        return strtr($output, ["\0" => '\\', '\\<' => '<', '\\>' => '>']);
    }

    public function getStyleStack(): OutputFormatterStyleStack
    {
        return $this->styleStack;
    }

    /**
     * Tries to create new style instance from string.
     */
    private function createStyleFromString(string $string): ?OutputFormatterStyleInterface
    {
        if (isset($this->styles[$string])) {
            return $this->styles[$string];
        }

        if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', $string, $matches, \PREG_SET_ORDER)) {
            return null;
        }

        $style = new OutputFormatterStyle();
        foreach ($matches as $match) {
            array_shift($match);
            $match[0] = strtolower($match[0]);

            if ('fg' == $match[0]) {
                $style->setForeground(strtolower($match[1]));
            } elseif ('bg' == $match[0]) {
                $style->setBackground(strtolower($match[1]));
            } elseif ('href' === $match[0]) {
                $url = preg_replace('{\\\\([<>])}', '$1', $match[1]);
                $style->setHref($url);
            } elseif ('options' === $match[0]) {
                preg_match_all('([^,;]+)', strtolower($match[1]), $options);
                $options = array_shift($options);
                foreach ($options as $option) {
                    $style->setOption($option);
                }
            } else {
                return null;
            }
        }

        return $style;
    }

    /**
     * Applies current style from stack to text, if must be applied.
     */
    private function applyCurrentStyle(string $text, string $current, int $width, int &$currentLineLength): string
    {
        if ('' === $text) {
            return '';
        }

        if (!$width) {
            return $this->isDecorated() ? $this->styleStack->getCurrent()->apply($text) : $text;
        }

        if (!$currentLineLength && '' !== $current) {
            $text = ltrim($text);
        }

        if ($currentLineLength) {
            $lines = explode("\n", $text, 2);
            $prefix = Helper::substr($lines[0], 0, $i = $width - $currentLineLength)."\n";
            $text = Helper::substr($lines[0], $i);

            if (isset($lines[1])) {
                // $prefix may contain the full first line in which the \n is already a part of $prefix.
                if ('' !== $text) {
                    $text .= "\n";
                }

                $text .= $lines[1];
            }
        } else {
            $prefix = '';
        }

        preg_match('~(\\n)$~', $text, $matches);
        $text = $prefix.$this->addLineBreaks($text, $width);
        $text = rtrim($text, "\n").($matches[1] ?? '');

        if (!$currentLineLength && '' !== $current && !str_ends_with($current, "\n")) {
            $text = "\n".$text;
        }

        $lines = explode("\n", $text);

        foreach ($lines as $i => $line) {
            $currentLineLength = 0 === $i ? $currentLineLength + Helper::length($line) : Helper::length($line);
            if ($width <= $currentLineLength) {
                $currentLineLength = 0;
            }
        }

        if ($this->isDecorated()) {
            foreach ($lines as $i => $line) {
                $lines[$i] = $this->styleStack->getCurrent()->apply($line);
            }
        }

        return implode("\n", $lines);
    }

    private function addLineBreaks(string $text, int $width): string
    {
        $encoding = mb_detect_encoding($text, null, true) ?: 'UTF-8';

        return b($text)->toUnicodeString($encoding)->wordwrap($width, "\n", true)->toByteString($encoding);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

/**
 * @author Tien Xuan Vo <tien.xuan.vo@gmail.com>
 */
final class NullOutputFormatter implements OutputFormatterInterface
{
    private NullOutputFormatterStyle $style;

    public function format(?string $message): ?string
    {
        return null;
    }

    public function getStyle(string $name): OutputFormatterStyleInterface
    {
        // to comply with the interface we must return a OutputFormatterStyleInterface
        return $this->style ??= new NullOutputFormatterStyle();
    }

    public function hasStyle(string $name): bool
    {
        return false;
    }

    public function isDecorated(): bool
    {
        return false;
    }

    public function setDecorated(bool $decorated): void
    {
        // do nothing
    }

    public function setStyle(string $name, OutputFormatterStyleInterface $style): void
    {
        // do nothing
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

/**
 * Formatter style interface for defining styles.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
interface OutputFormatterStyleInterface
{
    /**
     * Sets style foreground color.
     *
     * @return void
     */
    public function setForeground(?string $color);

    /**
     * Sets style background color.
     *
     * @return void
     */
    public function setBackground(?string $color);

    /**
     * Sets some specific style option.
     *
     * @return void
     */
    public function setOption(string $option);

    /**
     * Unsets some specific style option.
     *
     * @return void
     */
    public function unsetOption(string $option);

    /**
     * Sets multiple style options at once.
     *
     * @return void
     */
    public function setOptions(array $options);

    /**
     * Applies the style to a given text.
     */
    public function apply(string $text): string;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

/**
 * @author Tien Xuan Vo <tien.xuan.vo@gmail.com>
 */
final class NullOutputFormatterStyle implements OutputFormatterStyleInterface
{
    public function apply(string $text): string
    {
        return $text;
    }

    public function setBackground(?string $color = null): void
    {
        if (1 > \func_num_args()) {
            trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
        }
        // do nothing
    }

    public function setForeground(?string $color = null): void
    {
        if (1 > \func_num_args()) {
            trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
        }
        // do nothing
    }

    public function setOption(string $option): void
    {
        // do nothing
    }

    public function setOptions(array $options): void
    {
        // do nothing
    }

    public function unsetOption(string $option): void
    {
        // do nothing
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

/**
 * Formatter interface for console output that supports word wrapping.
 *
 * @author Roland Franssen <franssen.roland@gmail.com>
 */
interface WrappableOutputFormatterInterface extends OutputFormatterInterface
{
    /**
     * Formats a message according to the given styles, wrapping at `$width` (0 means no wrapping).
     *
     * @return string
     */
    public function formatAndWrap(?string $message, int $width);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Contracts\Service\ResetInterface;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class OutputFormatterStyleStack implements ResetInterface
{
    /**
     * @var OutputFormatterStyleInterface[]
     */
    private array $styles = [];

    private OutputFormatterStyleInterface $emptyStyle;

    public function __construct(?OutputFormatterStyleInterface $emptyStyle = null)
    {
        $this->emptyStyle = $emptyStyle ?? new OutputFormatterStyle();
        $this->reset();
    }

    /**
     * Resets stack (ie. empty internal arrays).
     *
     * @return void
     */
    public function reset()
    {
        $this->styles = [];
    }

    /**
     * Pushes a style in the stack.
     *
     * @return void
     */
    public function push(OutputFormatterStyleInterface $style)
    {
        $this->styles[] = $style;
    }

    /**
     * Pops a style from the stack.
     *
     * @throws InvalidArgumentException When style tags incorrectly nested
     */
    public function pop(?OutputFormatterStyleInterface $style = null): OutputFormatterStyleInterface
    {
        if (!$this->styles) {
            return $this->emptyStyle;
        }

        if (null === $style) {
            return array_pop($this->styles);
        }

        foreach (array_reverse($this->styles, true) as $index => $stackedStyle) {
            if ($style->apply('') === $stackedStyle->apply('')) {
                $this->styles = \array_slice($this->styles, 0, $index);

                return $stackedStyle;
            }
        }

        throw new InvalidArgumentException('Incorrectly nested style tag found.');
    }

    /**
     * Computes current style with stacks top codes.
     */
    public function getCurrent(): OutputFormatterStyleInterface
    {
        if (!$this->styles) {
            return $this->emptyStyle;
        }

        return $this->styles[\count($this->styles) - 1];
    }

    /**
     * @return $this
     */
    public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle): static
    {
        $this->emptyStyle = $emptyStyle;

        return $this;
    }

    public function getEmptyStyle(): OutputFormatterStyleInterface
    {
        return $this->emptyStyle;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Tester;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;

/**
 * Eases the testing of command completion.
 *
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class CommandCompletionTester
{
    private Command $command;

    public function __construct(Command $command)
    {
        $this->command = $command;
    }

    /**
     * Create completion suggestions from input tokens.
     */
    public function complete(array $input): array
    {
        $currentIndex = \count($input);
        if ('' === end($input)) {
            array_pop($input);
        }
        array_unshift($input, $this->command->getName());

        $completionInput = CompletionInput::fromTokens($input, $currentIndex);
        $completionInput->bind($this->command->getDefinition());
        $suggestions = new CompletionSuggestions();

        $this->command->complete($completionInput, $suggestions);

        $options = [];
        foreach ($suggestions->getOptionSuggestions() as $option) {
            $options[] = '--'.$option->getName();
        }

        return array_map('strval', array_merge($options, $suggestions->getValueSuggestions()));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Tester;

use PHPUnit\Framework\Assert;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\StreamOutput;
use Symfony\Component\Console\Tester\Constraint\CommandIsSuccessful;

/**
 * @author Amrouche Hamza <hamza.simperfit@gmail.com>
 */
trait TesterTrait
{
    private StreamOutput $output;
    private array $inputs = [];
    private bool $captureStreamsIndependently = false;
    private InputInterface $input;
    private int $statusCode;

    /**
     * Gets the display returned by the last execution of the command or application.
     *
     * @throws \RuntimeException If it's called before the execute method
     */
    public function getDisplay(bool $normalize = false): string
    {
        if (!isset($this->output)) {
            throw new \RuntimeException('Output not initialized, did you execute the command before requesting the display?');
        }

        rewind($this->output->getStream());

        $display = stream_get_contents($this->output->getStream());

        if ($normalize) {
            $display = str_replace(\PHP_EOL, "\n", $display);
        }

        return $display;
    }

    /**
     * Gets the output written to STDERR by the application.
     *
     * @param bool $normalize Whether to normalize end of lines to \n or not
     */
    public function getErrorOutput(bool $normalize = false): string
    {
        if (!$this->captureStreamsIndependently) {
            throw new \LogicException('The error output is not available when the tester is run without "capture_stderr_separately" option set.');
        }

        rewind($this->output->getErrorOutput()->getStream());

        $display = stream_get_contents($this->output->getErrorOutput()->getStream());

        if ($normalize) {
            $display = str_replace(\PHP_EOL, "\n", $display);
        }

        return $display;
    }

    /**
     * Gets the input instance used by the last execution of the command or application.
     */
    public function getInput(): InputInterface
    {
        return $this->input;
    }

    /**
     * Gets the output instance used by the last execution of the command or application.
     */
    public function getOutput(): OutputInterface
    {
        return $this->output;
    }

    /**
     * Gets the status code returned by the last execution of the command or application.
     *
     * @throws \RuntimeException If it's called before the execute method
     */
    public function getStatusCode(): int
    {
        return $this->statusCode ?? throw new \RuntimeException('Status code not initialized, did you execute the command before requesting the status code?');
    }

    public function assertCommandIsSuccessful(string $message = ''): void
    {
        Assert::assertThat($this->statusCode, new CommandIsSuccessful(), $message);
    }

    /**
     * Sets the user inputs.
     *
     * @param array $inputs An array of strings representing each input
     *                      passed to the command input stream
     *
     * @return $this
     */
    public function setInputs(array $inputs): static
    {
        $this->inputs = $inputs;

        return $this;
    }

    /**
     * Initializes the output property.
     *
     * Available options:
     *
     *  * decorated:                 Sets the output decorated flag
     *  * verbosity:                 Sets the output verbosity flag
     *  * capture_stderr_separately: Make output of stdOut and stdErr separately available
     */
    private function initOutput(array $options): void
    {
        $this->captureStreamsIndependently = $options['capture_stderr_separately'] ?? false;
        if (!$this->captureStreamsIndependently) {
            $this->output = new StreamOutput(fopen('php://memory', 'w', false));
            if (isset($options['decorated'])) {
                $this->output->setDecorated($options['decorated']);
            }
            if (isset($options['verbosity'])) {
                $this->output->setVerbosity($options['verbosity']);
            }
        } else {
            $this->output = new ConsoleOutput(
                $options['verbosity'] ?? ConsoleOutput::VERBOSITY_NORMAL,
                $options['decorated'] ?? null
            );

            $errorOutput = new StreamOutput(fopen('php://memory', 'w', false));
            $errorOutput->setFormatter($this->output->getFormatter());
            $errorOutput->setVerbosity($this->output->getVerbosity());
            $errorOutput->setDecorated($this->output->isDecorated());

            $reflectedOutput = new \ReflectionObject($this->output);
            $strErrProperty = $reflectedOutput->getProperty('stderr');
            $strErrProperty->setValue($this->output, $errorOutput);

            $reflectedParent = $reflectedOutput->getParentClass();
            $streamProperty = $reflectedParent->getProperty('stream');
            $streamProperty->setValue($this->output, fopen('php://memory', 'w', false));
        }
    }

    /**
     * @return resource
     */
    private static function createStream(array $inputs)
    {
        $stream = fopen('php://memory', 'r+', false);

        foreach ($inputs as $input) {
            fwrite($stream, $input);

            if (!str_ends_with($input, "\x4")) {
                fwrite($stream, \PHP_EOL);
            }
        }

        rewind($stream);

        return $stream;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Tester;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;

/**
 * Eases the testing of console commands.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Robin Chalas <robin.chalas@gmail.com>
 */
class CommandTester
{
    use TesterTrait;

    private Command $command;

    public function __construct(Command $command)
    {
        $this->command = $command;
    }

    /**
     * Executes the command.
     *
     * Available execution options:
     *
     *  * interactive:               Sets the input interactive flag
     *  * decorated:                 Sets the output decorated flag
     *  * verbosity:                 Sets the output verbosity flag
     *  * capture_stderr_separately: Make output of stdOut and stdErr separately available
     *
     * @param array $input   An array of command arguments and options
     * @param array $options An array of execution options
     *
     * @return int The command exit code
     */
    public function execute(array $input, array $options = []): int
    {
        // set the command name automatically if the application requires
        // this argument and no command name was passed
        if (!isset($input['command'])
            && (null !== $application = $this->command->getApplication())
            && $application->getDefinition()->hasArgument('command')
        ) {
            $input = array_merge(['command' => $this->command->getName()], $input);
        }

        $this->input = new ArrayInput($input);
        // Use an in-memory input stream even if no inputs are set so that QuestionHelper::ask() does not rely on the blocking STDIN.
        $this->input->setStream(self::createStream($this->inputs));

        if (isset($options['interactive'])) {
            $this->input->setInteractive($options['interactive']);
        }

        if (!isset($options['decorated'])) {
            $options['decorated'] = false;
        }

        $this->initOutput($options);

        return $this->statusCode = $this->command->run($this->input, $this->output);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Tester\Constraint;

use PHPUnit\Framework\Constraint\Constraint;
use Symfony\Component\Console\Command\Command;

final class CommandIsSuccessful extends Constraint
{
    public function toString(): string
    {
        return 'is successful';
    }

    protected function matches($other): bool
    {
        return Command::SUCCESS === $other;
    }

    protected function failureDescription($other): string
    {
        return 'the command '.$this->toString();
    }

    protected function additionalFailureDescription($other): string
    {
        $mapping = [
            Command::FAILURE => 'Command failed.',
            Command::INVALID => 'Command was invalid.',
        ];

        return $mapping[$other] ?? \sprintf('Command returned exit status %d.', $other);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Tester;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\ArrayInput;

/**
 * Eases the testing of console applications.
 *
 * When testing an application, don't forget to disable the auto exit flag:
 *
 *     $application = new Application();
 *     $application->setAutoExit(false);
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ApplicationTester
{
    use TesterTrait;

    private Application $application;

    public function __construct(Application $application)
    {
        $this->application = $application;
    }

    /**
     * Executes the application.
     *
     * Available options:
     *
     *  * interactive:               Sets the input interactive flag
     *  * decorated:                 Sets the output decorated flag
     *  * verbosity:                 Sets the output verbosity flag
     *  * capture_stderr_separately: Make output of stdOut and stdErr separately available
     *
     * @return int The command exit code
     */
    public function run(array $input, array $options = []): int
    {
        $prevShellVerbosity = [getenv('SHELL_VERBOSITY'), $_ENV['SHELL_VERBOSITY'] ?? false, $_SERVER['SHELL_VERBOSITY'] ?? false];

        try {
            $this->input = new ArrayInput($input);
            if (isset($options['interactive'])) {
                $this->input->setInteractive($options['interactive']);
            }

            if ($this->inputs) {
                $this->input->setStream(self::createStream($this->inputs));
            }

            $this->initOutput($options);

            // Temporarily clear SHELL_VERBOSITY to prevent Application::configureIO
            // from overriding the interactive and verbosity settings set above
            if (\function_exists('putenv')) {
                @putenv('SHELL_VERBOSITY');
            }
            unset($_ENV['SHELL_VERBOSITY'], $_SERVER['SHELL_VERBOSITY']);

            return $this->statusCode = $this->application->run($this->input, $this->output);
        } finally {
            // SHELL_VERBOSITY is set by Application::configureIO so we need to unset/reset it
            // to its previous value to avoid one test's verbosity to spread to the following tests
            if (false === $prevShellVerbosity[0]) {
                if (\function_exists('putenv')) {
                    @putenv('SHELL_VERBOSITY');
                }
            } else {
                if (\function_exists('putenv')) {
                    @putenv('SHELL_VERBOSITY='.$prevShellVerbosity[0]);
                }
            }
            if (false === $prevShellVerbosity[1]) {
                unset($_ENV['SHELL_VERBOSITY']);
            } else {
                $_ENV['SHELL_VERBOSITY'] = $prevShellVerbosity[1];
            }
            if (false === $prevShellVerbosity[2]) {
                unset($_SERVER['SHELL_VERBOSITY']);
            } else {
                $_SERVER['SHELL_VERBOSITY'] = $prevShellVerbosity[2];
            }
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\DataCollector;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Debug\CliRequest;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\SignalRegistry\SignalMap;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
use Symfony\Component\VarDumper\Cloner\Data;

/**
 * @internal
 *
 * @author Jules Pietri <jules@heahprod.com>
 */
final class CommandDataCollector extends DataCollector
{
    public function collect(Request $request, Response $response, ?\Throwable $exception = null): void
    {
        if (!$request instanceof CliRequest) {
            return;
        }

        $command = $request->command;
        $application = $command->getApplication();

        $this->data = [
            'command' => $this->cloneVar($command->command),
            'exit_code' => $command->exitCode,
            'interrupted_by_signal' => $command->interruptedBySignal,
            'duration' => $command->duration,
            'max_memory_usage' => $command->maxMemoryUsage,
            'verbosity_level' => match ($command->output->getVerbosity()) {
                OutputInterface::VERBOSITY_QUIET => 'quiet',
                OutputInterface::VERBOSITY_NORMAL => 'normal',
                OutputInterface::VERBOSITY_VERBOSE => 'verbose',
                OutputInterface::VERBOSITY_VERY_VERBOSE => 'very verbose',
                OutputInterface::VERBOSITY_DEBUG => 'debug',
            },
            'interactive' => $command->isInteractive,
            'validate_input' => !$command->ignoreValidation,
            'enabled' => $command->isEnabled(),
            'visible' => !$command->isHidden(),
            'input' => $this->cloneVar($command->input),
            'output' => $this->cloneVar($command->output),
            'interactive_inputs' => array_map($this->cloneVar(...), $command->interactiveInputs),
            'signalable' => $command->getSubscribedSignals(),
            'handled_signals' => $command->handledSignals,
            'helper_set' => array_map($this->cloneVar(...), iterator_to_array($command->getHelperSet())),
        ];

        $baseDefinition = $application->getDefinition();

        foreach ($command->arguments as $argName => $argValue) {
            if ($baseDefinition->hasArgument($argName)) {
                $this->data['application_inputs'][$argName] = $this->cloneVar($argValue);
            } else {
                $this->data['arguments'][$argName] = $this->cloneVar($argValue);
            }
        }

        foreach ($command->options as $optName => $optValue) {
            if ($baseDefinition->hasOption($optName)) {
                $this->data['application_inputs']['--'.$optName] = $this->cloneVar($optValue);
            } else {
                $this->data['options'][$optName] = $this->cloneVar($optValue);
            }
        }
    }

    public function getName(): string
    {
        return 'command';
    }

    /**
     * @return array{
     *     class?: class-string,
     *     executor?: string,
     *     file: string,
     *     line: int,
     * }
     */
    public function getCommand(): array
    {
        $class = $this->data['command']->getType();
        $r = new \ReflectionMethod($class, 'execute');

        if (Command::class !== $r->getDeclaringClass()) {
            return [
                'executor' => $class.'::'.$r->name,
                'file' => $r->getFileName(),
                'line' => $r->getStartLine(),
            ];
        }

        $r = new \ReflectionClass($class);

        return [
            'class' => $class,
            'file' => $r->getFileName(),
            'line' => $r->getStartLine(),
        ];
    }

    public function getInterruptedBySignal(): ?string
    {
        if (isset($this->data['interrupted_by_signal'])) {
            return \sprintf('%s (%d)', SignalMap::getSignalName($this->data['interrupted_by_signal']), $this->data['interrupted_by_signal']);
        }

        return null;
    }

    public function getDuration(): string
    {
        return $this->data['duration'];
    }

    public function getMaxMemoryUsage(): string
    {
        return $this->data['max_memory_usage'];
    }

    public function getVerbosityLevel(): string
    {
        return $this->data['verbosity_level'];
    }

    public function getInteractive(): bool
    {
        return $this->data['interactive'];
    }

    public function getValidateInput(): bool
    {
        return $this->data['validate_input'];
    }

    public function getEnabled(): bool
    {
        return $this->data['enabled'];
    }

    public function getVisible(): bool
    {
        return $this->data['visible'];
    }

    public function getInput(): Data
    {
        return $this->data['input'];
    }

    public function getOutput(): Data
    {
        return $this->data['output'];
    }

    /**
     * @return Data[]
     */
    public function getArguments(): array
    {
        return $this->data['arguments'] ?? [];
    }

    /**
     * @return Data[]
     */
    public function getOptions(): array
    {
        return $this->data['options'] ?? [];
    }

    /**
     * @return Data[]
     */
    public function getApplicationInputs(): array
    {
        return $this->data['application_inputs'] ?? [];
    }

    /**
     * @return Data[]
     */
    public function getInteractiveInputs(): array
    {
        return $this->data['interactive_inputs'] ?? [];
    }

    public function getSignalable(): array
    {
        return array_map(
            static fn (int $signal): string => \sprintf('%s (%d)', SignalMap::getSignalName($signal), $signal),
            $this->data['signalable']
        );
    }

    public function getHandledSignals(): array
    {
        $keys = array_map(
            static fn (int $signal): string => \sprintf('%s (%d)', SignalMap::getSignalName($signal), $signal),
            array_keys($this->data['handled_signals'])
        );

        return array_combine($keys, array_values($this->data['handled_signals']));
    }

    /**
     * @return Data[]
     */
    public function getHelperSet(): array
    {
        return $this->data['helper_set'] ?? [];
    }

    public function reset(): void
    {
        $this->data = [];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console;

use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleSignalEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;

/**
 * Contains all events dispatched by an Application.
 *
 * @author Francesco Levorato <git@flevour.net>
 */
final class ConsoleEvents
{
    /**
     * The COMMAND event allows you to attach listeners before any command is
     * executed by the console. It also allows you to modify the command, input and output
     * before they are handed to the command.
     *
     * @Event("Symfony\Component\Console\Event\ConsoleCommandEvent")
     */
    public const COMMAND = 'console.command';

    /**
     * The SIGNAL event allows you to perform some actions
     * after the command execution was interrupted.
     *
     * @Event("Symfony\Component\Console\Event\ConsoleSignalEvent")
     */
    public const SIGNAL = 'console.signal';

    /**
     * The TERMINATE event allows you to attach listeners after a command is
     * executed by the console.
     *
     * @Event("Symfony\Component\Console\Event\ConsoleTerminateEvent")
     */
    public const TERMINATE = 'console.terminate';

    /**
     * The ERROR event occurs when an uncaught exception or error appears.
     *
     * This event allows you to deal with the exception/error or
     * to modify the thrown exception.
     *
     * @Event("Symfony\Component\Console\Event\ConsoleErrorEvent")
     */
    public const ERROR = 'console.error';

    /**
     * Event aliases.
     *
     * These aliases can be consumed by RegisterListenersPass.
     */
    public const ALIASES = [
        ConsoleCommandEvent::class => self::COMMAND,
        ConsoleErrorEvent::class => self::ERROR,
        ConsoleSignalEvent::class => self::SIGNAL,
        ConsoleTerminateEvent::class => self::TERMINATE,
    ];
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * A BufferedOutput that keeps only the last N chars.
 *
 * @author Jérémy Derussé <jeremy@derusse.com>
 */
class TrimmedBufferOutput extends Output
{
    private int $maxLength;
    private string $buffer = '';

    public function __construct(int $maxLength, ?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, ?OutputFormatterInterface $formatter = null)
    {
        if ($maxLength <= 0) {
            throw new InvalidArgumentException(\sprintf('"%s()" expects a strictly positive maxLength. Got %d.', __METHOD__, $maxLength));
        }

        parent::__construct($verbosity, $decorated, $formatter);
        $this->maxLength = $maxLength;
    }

    /**
     * Empties buffer and returns its content.
     */
    public function fetch(): string
    {
        $content = $this->buffer;
        $this->buffer = '';

        return $content;
    }

    /**
     * @return void
     */
    protected function doWrite(string $message, bool $newline)
    {
        $this->buffer .= $message;

        if ($newline) {
            $this->buffer .= \PHP_EOL;
        }

        $this->buffer = substr($this->buffer, 0 - $this->maxLength);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * StreamOutput writes the output to a given stream.
 *
 * Usage:
 *
 *     $output = new StreamOutput(fopen('php://stdout', 'w'));
 *
 * As `StreamOutput` can use any stream, you can also use a file:
 *
 *     $output = new StreamOutput(fopen('/path/to/output.log', 'a', false));
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class StreamOutput extends Output
{
    /** @var resource */
    private $stream;

    /**
     * @param resource                      $stream    A stream resource
     * @param int                           $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
     * @param bool|null                     $decorated Whether to decorate messages (null for auto-guessing)
     * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
     *
     * @throws InvalidArgumentException When first argument is not a real stream
     */
    public function __construct($stream, int $verbosity = self::VERBOSITY_NORMAL, ?bool $decorated = null, ?OutputFormatterInterface $formatter = null)
    {
        if (!\is_resource($stream) || 'stream' !== get_resource_type($stream)) {
            throw new InvalidArgumentException('The StreamOutput class needs a stream as its first argument.');
        }

        $this->stream = $stream;

        $decorated ??= $this->hasColorSupport();

        parent::__construct($verbosity, $decorated, $formatter);
    }

    /**
     * Gets the stream attached to this StreamOutput instance.
     *
     * @return resource
     */
    public function getStream()
    {
        return $this->stream;
    }

    /**
     * @return void
     */
    protected function doWrite(string $message, bool $newline)
    {
        if ($newline) {
            $message .= \PHP_EOL;
        }

        @fwrite($this->stream, $message);

        fflush($this->stream);
    }

    /**
     * Returns true if the stream supports colorization.
     *
     * Colorization is disabled if not supported by the stream:
     *
     * This is tricky on Windows, because Cygwin, Msys2 etc emulate pseudo
     * terminals via named pipes, so we can only check the environment.
     *
     * Reference: Composer\XdebugHandler\Process::supportsColor
     * https://github.com/composer/xdebug-handler
     *
     * @return bool true if the stream supports colorization, false otherwise
     */
    protected function hasColorSupport(): bool
    {
        // Follow https://no-color.org/
        if ('' !== (($_SERVER['NO_COLOR'] ?? getenv('NO_COLOR'))[0] ?? '')) {
            return false;
        }

        // Detect msysgit/mingw and assume this is a tty because detection
        // does not work correctly, see https://github.com/composer/composer/issues/9690
        if (!@stream_isatty($this->stream) && !\in_array(strtoupper((string) getenv('MSYSTEM')), ['MINGW32', 'MINGW64'], true)) {
            return false;
        }

        if ('\\' === \DIRECTORY_SEPARATOR && @sapi_windows_vt100_support($this->stream)) {
            return true;
        }

        if ('Hyper' === getenv('TERM_PROGRAM')
            || false !== getenv('COLORTERM')
            || false !== getenv('ANSICON')
            || 'ON' === getenv('ConEmuANSI')
        ) {
            return true;
        }

        if ('dumb' === $term = (string) getenv('TERM')) {
            return false;
        }

        // See https://github.com/chalk/supports-color/blob/d4f413efaf8da045c5ab440ed418ef02dbb28bf1/index.js#L157
        return preg_match('/^((screen|xterm|vt100|vt220|putty|rxvt|ansi|cygwin|linux).*)|(.*-256(color)?(-bce)?)$/', $term);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Terminal;

/**
 * @author Pierre du Plessis <pdples@gmail.com>
 * @author Gabriel Ostrolucký <gabriel.ostrolucky@gmail.com>
 */
class ConsoleSectionOutput extends StreamOutput
{
    private array $content = [];
    private int $lines = 0;
    private array $sections;
    private Terminal $terminal;
    private int $maxHeight = 0;

    /**
     * @param resource               $stream
     * @param ConsoleSectionOutput[] $sections
     */
    public function __construct($stream, array &$sections, int $verbosity, bool $decorated, OutputFormatterInterface $formatter)
    {
        parent::__construct($stream, $verbosity, $decorated, $formatter);
        array_unshift($sections, $this);
        $this->sections = &$sections;
        $this->terminal = new Terminal();
    }

    /**
     * Defines a maximum number of lines for this section.
     *
     * When more lines are added, the section will automatically scroll to the
     * end (i.e. remove the first lines to comply with the max height).
     */
    public function setMaxHeight(int $maxHeight): void
    {
        // when changing max height, clear output of current section and redraw again with the new height
        $previousMaxHeight = $this->maxHeight;
        $this->maxHeight = $maxHeight;
        $existingContent = $this->popStreamContentUntilCurrentSection($previousMaxHeight ? min($previousMaxHeight, $this->lines) : $this->lines);

        parent::doWrite($this->getVisibleContent(), false);
        parent::doWrite($existingContent, false);
    }

    /**
     * Clears previous output for this section.
     *
     * @param int $lines Number of lines to clear. If null, then the entire output of this section is cleared
     *
     * @return void
     */
    public function clear(?int $lines = null)
    {
        if (empty($this->content) || !$this->isDecorated()) {
            return;
        }

        if ($lines) {
            array_splice($this->content, -$lines);
        } else {
            $lines = $this->lines;
            $this->content = [];
        }

        $this->lines -= $lines;

        parent::doWrite($this->popStreamContentUntilCurrentSection($this->maxHeight ? min($this->maxHeight, $lines) : $lines), false);
    }

    /**
     * Overwrites the previous output with a new message.
     *
     * @return void
     */
    public function overwrite(string|iterable $message)
    {
        $this->clear();
        $this->writeln($message);
    }

    public function getContent(): string
    {
        return implode('', $this->content);
    }

    public function getVisibleContent(): string
    {
        if (0 === $this->maxHeight) {
            return $this->getContent();
        }

        return implode('', \array_slice($this->content, -$this->maxHeight));
    }

    /**
     * @internal
     */
    public function addContent(string $input, bool $newline = true): int
    {
        $width = $this->terminal->getWidth();
        $lines = explode(\PHP_EOL, $input);
        $linesAdded = 0;
        $count = \count($lines) - 1;
        foreach ($lines as $i => $lineContent) {
            // re-add the line break (that has been removed in the above `explode()` for
            // - every line that is not the last line
            // - if $newline is required, also add it to the last line
            if ($i < $count || $newline) {
                $lineContent .= \PHP_EOL;
            }

            // skip line if there is no text (or newline for that matter)
            if ('' === $lineContent) {
                continue;
            }

            // For the first line, check if the previous line (last entry of `$this->content`)
            // needs to be continued (i.e. does not end with a line break).
            if (0 === $i
                && (false !== $lastLine = end($this->content))
                && !str_ends_with($lastLine, \PHP_EOL)
            ) {
                // deduct the line count of the previous line
                $this->lines -= (int) ceil($this->getDisplayLength($lastLine) / $width) ?: 1;
                // concatenate previous and new line
                $lineContent = $lastLine.$lineContent;
                // replace last entry of `$this->content` with the new expanded line
                array_splice($this->content, -1, 1, $lineContent);
            } else {
                // otherwise just add the new content
                $this->content[] = $lineContent;
            }

            $linesAdded += (int) ceil($this->getDisplayLength($lineContent) / $width) ?: 1;
        }

        $this->lines += $linesAdded;

        return $linesAdded;
    }

    /**
     * @internal
     */
    public function addNewLineOfInputSubmit(): void
    {
        $this->content[] = \PHP_EOL;
        ++$this->lines;
    }

    /**
     * @return void
     */
    protected function doWrite(string $message, bool $newline)
    {
        // Simulate newline behavior for consistent output formatting, avoiding extra logic
        if (!$newline && str_ends_with($message, \PHP_EOL)) {
            $message = substr($message, 0, -\strlen(\PHP_EOL));
            $newline = true;
        }

        if (!$this->isDecorated()) {
            parent::doWrite($message, $newline);

            return;
        }

        // Check if the previous line (last entry of `$this->content`) needs to be continued
        // (i.e. does not end with a line break). In which case, it needs to be erased first.
        $linesToClear = $deleteLastLine = ($lastLine = end($this->content) ?: '') && !str_ends_with($lastLine, \PHP_EOL) ? 1 : 0;

        $linesAdded = $this->addContent($message, $newline);

        if ($lineOverflow = $this->maxHeight > 0 && $this->lines > $this->maxHeight) {
            // on overflow, clear the whole section and redraw again (to remove the first lines)
            $linesToClear = $this->maxHeight;
        }

        $erasedContent = $this->popStreamContentUntilCurrentSection($linesToClear);

        if ($lineOverflow) {
            // redraw existing lines of the section
            $previousLinesOfSection = \array_slice($this->content, $this->lines - $this->maxHeight, $this->maxHeight - $linesAdded);
            parent::doWrite(implode('', $previousLinesOfSection), false);
        }

        // if the last line was removed, re-print its content together with the new content.
        // otherwise, just print the new content.
        parent::doWrite($deleteLastLine ? $lastLine.$message : $message, true);
        parent::doWrite($erasedContent, false);
    }

    /**
     * At initial stage, cursor is at the end of stream output. This method makes cursor crawl upwards until it hits
     * current section. Then it erases content it crawled through. Optionally, it erases part of current section too.
     */
    private function popStreamContentUntilCurrentSection(int $numberOfLinesToClearFromCurrentSection = 0): string
    {
        $numberOfLinesToClear = $numberOfLinesToClearFromCurrentSection;
        $erasedContent = [];

        foreach ($this->sections as $section) {
            if ($section === $this) {
                break;
            }

            $numberOfLinesToClear += $section->maxHeight ? min($section->lines, $section->maxHeight) : $section->lines;
            if ('' !== $sectionContent = $section->getVisibleContent()) {
                if (!str_ends_with($sectionContent, \PHP_EOL)) {
                    $sectionContent .= \PHP_EOL;
                }
                $erasedContent[] = $sectionContent;
            }
        }

        if ($numberOfLinesToClear > 0) {
            // move cursor up n lines
            parent::doWrite(\sprintf("\x1b[%dA", $numberOfLinesToClear), false);
            // erase to end of screen
            parent::doWrite("\x1b[0J", false);
        }

        return implode('', array_reverse($erasedContent));
    }

    private function getDisplayLength(string $text): int
    {
        return Helper::width(Helper::removeDecoration($this->getFormatter(), str_replace("\t", '        ', $text)));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * ConsoleOutput is the default class for all CLI output. It uses STDOUT and STDERR.
 *
 * This class is a convenient wrapper around `StreamOutput` for both STDOUT and STDERR.
 *
 *     $output = new ConsoleOutput();
 *
 * This is equivalent to:
 *
 *     $output = new StreamOutput(fopen('php://stdout', 'w'));
 *     $stdErr = new StreamOutput(fopen('php://stderr', 'w'));
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
{
    private OutputInterface $stderr;
    private array $consoleSectionOutputs = [];

    /**
     * @param int                           $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
     * @param bool|null                     $decorated Whether to decorate messages (null for auto-guessing)
     * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
     */
    public function __construct(int $verbosity = self::VERBOSITY_NORMAL, ?bool $decorated = null, ?OutputFormatterInterface $formatter = null)
    {
        parent::__construct($this->openOutputStream(), $verbosity, $decorated, $formatter);

        if (null === $formatter) {
            // for BC reasons, stdErr has it own Formatter only when user don't inject a specific formatter.
            $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated);

            return;
        }

        $actualDecorated = $this->isDecorated();
        $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated, $this->getFormatter());

        if (null === $decorated) {
            $this->setDecorated($actualDecorated && $this->stderr->isDecorated());
        }
    }

    /**
     * Creates a new output section.
     */
    public function section(): ConsoleSectionOutput
    {
        return new ConsoleSectionOutput($this->getStream(), $this->consoleSectionOutputs, $this->getVerbosity(), $this->isDecorated(), $this->getFormatter());
    }

    /**
     * @return void
     */
    public function setDecorated(bool $decorated)
    {
        parent::setDecorated($decorated);
        $this->stderr->setDecorated($decorated);
    }

    /**
     * @return void
     */
    public function setFormatter(OutputFormatterInterface $formatter)
    {
        parent::setFormatter($formatter);
        $this->stderr->setFormatter($formatter);
    }

    /**
     * @return void
     */
    public function setVerbosity(int $level)
    {
        parent::setVerbosity($level);
        $this->stderr->setVerbosity($level);
    }

    public function getErrorOutput(): OutputInterface
    {
        return $this->stderr;
    }

    /**
     * @return void
     */
    public function setErrorOutput(OutputInterface $error)
    {
        $this->stderr = $error;
    }

    /**
     * Returns true if current environment supports writing console output to
     * STDOUT.
     */
    protected function hasStdoutSupport(): bool
    {
        return false === $this->isRunningOS400();
    }

    /**
     * Returns true if current environment supports writing console output to
     * STDERR.
     */
    protected function hasStderrSupport(): bool
    {
        return false === $this->isRunningOS400();
    }

    /**
     * Checks if current executing environment is IBM iSeries (OS400), which
     * doesn't properly convert character-encodings between ASCII to EBCDIC.
     */
    private function isRunningOS400(): bool
    {
        $checks = [
            \function_exists('php_uname') ? php_uname('s') : '',
            getenv('OSTYPE'),
            \PHP_OS,
        ];

        return false !== stripos(implode(';', $checks), 'OS400');
    }

    /**
     * @return resource
     */
    private function openOutputStream()
    {
        static $stdout;

        if ($stdout) {
            return $stdout;
        }

        if (!$this->hasStdoutSupport()) {
            return $stdout = fopen('php://output', 'w');
        }

        // Use STDOUT when possible to prevent from opening too many file descriptors
        if (!\defined('STDOUT')) {
            return $stdout = @fopen('php://stdout', 'w') ?: fopen('php://output', 'w');
        }

        // On Windows, STDOUT is opened in text mode; reopen in binary mode to prevent \n to \r\n conversion
        if ('\\' === \DIRECTORY_SEPARATOR) {
            return $stdout = @fopen('php://stdout', 'w') ?: \STDOUT;
        }

        return $stdout = \STDOUT;
    }

    /**
     * @return resource
     */
    private function openErrorStream()
    {
        static $stderr;

        if ($stderr) {
            return $stderr;
        }

        if (!$this->hasStderrSupport()) {
            return $stderr = fopen('php://output', 'w');
        }

        // Use STDERR when possible to prevent from opening too many file descriptors
        if (!\defined('STDERR')) {
            return $stderr = @fopen('php://stderr', 'w') ?: fopen('php://output', 'w');
        }

        // On Windows, STDERR is opened in text mode; reopen in binary mode to prevent \n → \r\n conversion
        if ('\\' === \DIRECTORY_SEPARATOR) {
            return $stderr = @fopen('php://stderr', 'w') ?: \STDERR;
        }

        return $stderr ??= \STDERR;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * Base class for output classes.
 *
 * There are five levels of verbosity:
 *
 *  * normal: no option passed (normal output)
 *  * verbose: -v (more output)
 *  * very verbose: -vv (highly extended output)
 *  * debug: -vvv (all debug output)
 *  * quiet: -q (no output)
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
abstract class Output implements OutputInterface
{
    private int $verbosity;
    private OutputFormatterInterface $formatter;

    /**
     * @param int|null                      $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
     * @param bool                          $decorated Whether to decorate messages
     * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
     */
    public function __construct(?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, ?OutputFormatterInterface $formatter = null)
    {
        $this->verbosity = $verbosity ?? self::VERBOSITY_NORMAL;
        $this->formatter = $formatter ?? new OutputFormatter();
        $this->formatter->setDecorated($decorated);
    }

    /**
     * @return void
     */
    public function setFormatter(OutputFormatterInterface $formatter)
    {
        $this->formatter = $formatter;
    }

    public function getFormatter(): OutputFormatterInterface
    {
        return $this->formatter;
    }

    /**
     * @return void
     */
    public function setDecorated(bool $decorated)
    {
        $this->formatter->setDecorated($decorated);
    }

    public function isDecorated(): bool
    {
        return $this->formatter->isDecorated();
    }

    /**
     * @return void
     */
    public function setVerbosity(int $level)
    {
        $this->verbosity = $level;
    }

    public function getVerbosity(): int
    {
        return $this->verbosity;
    }

    public function isQuiet(): bool
    {
        return self::VERBOSITY_QUIET === $this->verbosity;
    }

    public function isVerbose(): bool
    {
        return self::VERBOSITY_VERBOSE <= $this->verbosity;
    }

    public function isVeryVerbose(): bool
    {
        return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity;
    }

    public function isDebug(): bool
    {
        return self::VERBOSITY_DEBUG <= $this->verbosity;
    }

    /**
     * @return void
     */
    public function writeln(string|iterable $messages, int $options = self::OUTPUT_NORMAL)
    {
        $this->write($messages, true, $options);
    }

    /**
     * @return void
     */
    public function write(string|iterable $messages, bool $newline = false, int $options = self::OUTPUT_NORMAL)
    {
        if (!is_iterable($messages)) {
            $messages = [$messages];
        }

        $types = self::OUTPUT_NORMAL | self::OUTPUT_RAW | self::OUTPUT_PLAIN;
        $type = $types & $options ?: self::OUTPUT_NORMAL;

        $verbosities = self::VERBOSITY_QUIET | self::VERBOSITY_NORMAL | self::VERBOSITY_VERBOSE | self::VERBOSITY_VERY_VERBOSE | self::VERBOSITY_DEBUG;
        $verbosity = $verbosities & $options ?: self::VERBOSITY_NORMAL;

        if ($verbosity > $this->getVerbosity()) {
            return;
        }

        foreach ($messages as $message) {
            switch ($type) {
                case OutputInterface::OUTPUT_NORMAL:
                    $message = $this->formatter->format($message);
                    break;
                case OutputInterface::OUTPUT_RAW:
                    break;
                case OutputInterface::OUTPUT_PLAIN:
                    $message = strip_tags($this->formatter->format($message));
                    break;
            }

            $this->doWrite($message ?? '', $newline);
        }
    }

    /**
     * Writes a message to the output.
     *
     * @return void
     */
    abstract protected function doWrite(string $message, bool $newline);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Julien Boudry <julien@condorcet.vote>
 */
enum AnsiColorMode
{
    /*
     * Classical 4-bit Ansi colors, including 8 classical colors and 8 bright color. Output syntax is "ESC[${foreGroundColorcode};${backGroundColorcode}m"
     * Must be compatible with all terminals and it's the minimal version supported.
     */
    case Ansi4;

    /*
     * 8-bit Ansi colors (240 different colors + 16 duplicate color codes, ensuring backward compatibility).
     * Output syntax is: "ESC[38;5;${foreGroundColorcode};48;5;${backGroundColorcode}m"
     * Should be compatible with most terminals.
     */
    case Ansi8;

    /*
     * 24-bit Ansi colors (RGB).
     * Output syntax is: "ESC[38;2;${foreGroundColorcodeRed};${foreGroundColorcodeGreen};${foreGroundColorcodeBlue};48;2;${backGroundColorcodeRed};${backGroundColorcodeGreen};${backGroundColorcodeBlue}m"
     * May be compatible with many modern terminals.
     */
    case Ansi24;

    /**
     * Converts an RGB hexadecimal color to the corresponding Ansi code.
     */
    public function convertFromHexToAnsiColorCode(string $hexColor): string
    {
        $hexColor = str_replace('#', '', $hexColor);

        if (3 === \strlen($hexColor)) {
            $hexColor = $hexColor[0].$hexColor[0].$hexColor[1].$hexColor[1].$hexColor[2].$hexColor[2];
        }

        if (6 !== \strlen($hexColor)) {
            throw new InvalidArgumentException(\sprintf('Invalid "#%s" color.', $hexColor));
        }

        $color = hexdec($hexColor);

        $r = ($color >> 16) & 255;
        $g = ($color >> 8) & 255;
        $b = $color & 255;

        return match ($this) {
            self::Ansi4 => (string) $this->convertFromRGB($r, $g, $b),
            self::Ansi8 => '8;5;'.((string) $this->convertFromRGB($r, $g, $b)),
            self::Ansi24 => \sprintf('8;2;%d;%d;%d', $r, $g, $b),
        };
    }

    private function convertFromRGB(int $r, int $g, int $b): int
    {
        return match ($this) {
            self::Ansi4 => $this->degradeHexColorToAnsi4($r, $g, $b),
            self::Ansi8 => $this->degradeHexColorToAnsi8($r, $g, $b),
            default => throw new InvalidArgumentException("RGB cannot be converted to {$this->name}."),
        };
    }

    private function degradeHexColorToAnsi4(int $r, int $g, int $b): int
    {
        return round($b / 255) << 2 | (round($g / 255) << 1) | round($r / 255);
    }

    /**
     * Inspired from https://github.com/ajalt/colormath/blob/e464e0da1b014976736cf97250063248fc77b8e7/colormath/src/commonMain/kotlin/com/github/ajalt/colormath/model/Ansi256.kt code (MIT license).
     */
    private function degradeHexColorToAnsi8(int $r, int $g, int $b): int
    {
        if ($r === $g && $g === $b) {
            if ($r < 8) {
                return 16;
            }

            if ($r > 248) {
                return 231;
            }

            return (int) round(($r - 8) / 247 * 24) + 232;
        }

        return 16 +
                (36 * (int) round($r / 255 * 5)) +
                (6 * (int) round($g / 255 * 5)) +
                (int) round($b / 255 * 5);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Formatter\NullOutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * NullOutput suppresses all output.
 *
 *     $output = new NullOutput();
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Tobias Schultze <http://tobion.de>
 */
class NullOutput implements OutputInterface
{
    private NullOutputFormatter $formatter;

    /**
     * @return void
     */
    public function setFormatter(OutputFormatterInterface $formatter)
    {
        // do nothing
    }

    public function getFormatter(): OutputFormatterInterface
    {
        // to comply with the interface we must return a OutputFormatterInterface
        return $this->formatter ??= new NullOutputFormatter();
    }

    /**
     * @return void
     */
    public function setDecorated(bool $decorated)
    {
        // do nothing
    }

    public function isDecorated(): bool
    {
        return false;
    }

    /**
     * @return void
     */
    public function setVerbosity(int $level)
    {
        // do nothing
    }

    public function getVerbosity(): int
    {
        return self::VERBOSITY_QUIET;
    }

    public function isQuiet(): bool
    {
        return true;
    }

    public function isVerbose(): bool
    {
        return false;
    }

    public function isVeryVerbose(): bool
    {
        return false;
    }

    public function isDebug(): bool
    {
        return false;
    }

    /**
     * @return void
     */
    public function writeln(string|iterable $messages, int $options = self::OUTPUT_NORMAL)
    {
        // do nothing
    }

    /**
     * @return void
     */
    public function write(string|iterable $messages, bool $newline = false, int $options = self::OUTPUT_NORMAL)
    {
        // do nothing
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class BufferedOutput extends Output
{
    private string $buffer = '';

    /**
     * Empties buffer and returns its content.
     */
    public function fetch(): string
    {
        $content = $this->buffer;
        $this->buffer = '';

        return $content;
    }

    /**
     * @return void
     */
    protected function doWrite(string $message, bool $newline)
    {
        $this->buffer .= $message;

        if ($newline) {
            $this->buffer .= \PHP_EOL;
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

/**
 * ConsoleOutputInterface is the interface implemented by ConsoleOutput class.
 * This adds information about stderr and section output stream.
 *
 * @author Dariusz Górecki <darek.krk@gmail.com>
 */
interface ConsoleOutputInterface extends OutputInterface
{
    /**
     * Gets the OutputInterface for errors.
     */
    public function getErrorOutput(): OutputInterface;

    /**
     * @return void
     */
    public function setErrorOutput(OutputInterface $error);

    public function section(): ConsoleSectionOutput;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * OutputInterface is the interface implemented by all Output classes.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
interface OutputInterface
{
    public const VERBOSITY_QUIET = 16;
    public const VERBOSITY_NORMAL = 32;
    public const VERBOSITY_VERBOSE = 64;
    public const VERBOSITY_VERY_VERBOSE = 128;
    public const VERBOSITY_DEBUG = 256;

    public const OUTPUT_NORMAL = 1;
    public const OUTPUT_RAW = 2;
    public const OUTPUT_PLAIN = 4;

    /**
     * Writes a message to the output.
     *
     * @param bool $newline Whether to add a newline
     * @param int  $options A bitmask of options (one of the OUTPUT or VERBOSITY constants),
     *                      0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
     *
     * @return void
     */
    public function write(string|iterable $messages, bool $newline = false, int $options = 0);

    /**
     * Writes a message to the output and adds a newline at the end.
     *
     * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants),
     *                     0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
     *
     * @return void
     */
    public function writeln(string|iterable $messages, int $options = 0);

    /**
     * Sets the verbosity of the output.
     *
     * @param self::VERBOSITY_* $level
     *
     * @return void
     */
    public function setVerbosity(int $level);

    /**
     * Gets the current verbosity of the output.
     *
     * @return self::VERBOSITY_*
     */
    public function getVerbosity(): int;

    /**
     * Returns whether verbosity is quiet (-q).
     */
    public function isQuiet(): bool;

    /**
     * Returns whether verbosity is verbose (-v).
     */
    public function isVerbose(): bool;

    /**
     * Returns whether verbosity is very verbose (-vv).
     */
    public function isVeryVerbose(): bool;

    /**
     * Returns whether verbosity is debug (-vvv).
     */
    public function isDebug(): bool;

    /**
     * Sets the decorated flag.
     *
     * @return void
     */
    public function setDecorated(bool $decorated);

    /**
     * Gets the decorated flag.
     */
    public function isDecorated(): bool;

    /**
     * @return void
     */
    public function setFormatter(OutputFormatterInterface $formatter);

    /**
     * Returns current output formatter instance.
     */
    public function getFormatter(): OutputFormatterInterface;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Logger;

use Psr\Log\AbstractLogger;
use Psr\Log\InvalidArgumentException;
use Psr\Log\LogLevel;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * PSR-3 compliant console logger.
 *
 * @author Kévin Dunglas <dunglas@gmail.com>
 *
 * @see https://www.php-fig.org/psr/psr-3/
 */
class ConsoleLogger extends AbstractLogger
{
    public const INFO = 'info';
    public const ERROR = 'error';

    private OutputInterface $output;
    private array $verbosityLevelMap = [
        LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL,
        LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL,
        LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL,
        LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL,
        LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL,
        LogLevel::NOTICE => OutputInterface::VERBOSITY_VERBOSE,
        LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE,
        LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG,
    ];
    private array $formatLevelMap = [
        LogLevel::EMERGENCY => self::ERROR,
        LogLevel::ALERT => self::ERROR,
        LogLevel::CRITICAL => self::ERROR,
        LogLevel::ERROR => self::ERROR,
        LogLevel::WARNING => self::INFO,
        LogLevel::NOTICE => self::INFO,
        LogLevel::INFO => self::INFO,
        LogLevel::DEBUG => self::INFO,
    ];
    private bool $errored = false;

    public function __construct(OutputInterface $output, array $verbosityLevelMap = [], array $formatLevelMap = [])
    {
        $this->output = $output;
        $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap;
        $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap;
    }

    public function log($level, $message, array $context = []): void
    {
        if (!isset($this->verbosityLevelMap[$level])) {
            throw new InvalidArgumentException(\sprintf('The log level "%s" does not exist.', $level));
        }

        $output = $this->output;

        // Write to the error output if necessary and available
        if (self::ERROR === $this->formatLevelMap[$level]) {
            if ($this->output instanceof ConsoleOutputInterface) {
                $output = $output->getErrorOutput();
            }
            $this->errored = true;
        }

        // the if condition check isn't necessary -- it's the same one that $output will do internally anyway.
        // We only do it for efficiency here as the message formatting is relatively expensive.
        if ($output->getVerbosity() >= $this->verbosityLevelMap[$level]) {
            $output->writeln(\sprintf('<%1$s>[%2$s] %3$s</%1$s>', $this->formatLevelMap[$level], $level, $this->interpolate($message, $context)), $this->verbosityLevelMap[$level]);
        }
    }

    /**
     * Returns true when any messages have been logged at error levels.
     */
    public function hasErrored(): bool
    {
        return $this->errored;
    }

    /**
     * Interpolates context values into the message placeholders.
     *
     * @author PHP Framework Interoperability Group
     */
    private function interpolate(string $message, array $context): string
    {
        if (!str_contains($message, '{')) {
            return $message;
        }

        $replacements = [];
        foreach ($context as $key => $val) {
            if (null === $val || \is_scalar($val) || $val instanceof \Stringable) {
                $replacements["{{$key}}"] = $val;
            } elseif ($val instanceof \DateTimeInterface) {
                $replacements["{{$key}}"] = $val->format(\DateTimeInterface::RFC3339);
            } elseif (\is_object($val)) {
                $replacements["{{$key}}"] = '[object '.$val::class.']';
            } else {
                $replacements["{{$key}}"] = '['.\gettype($val).']';
            }
        }

        return strtr($message, $replacements);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Messenger;

use Symfony\Component\Console\Exception\RunCommandFailedException;

/**
 * @author Kevin Bond <kevinbond@gmail.com>
 */
class RunCommandMessage implements \Stringable
{
    /**
     * @param bool $throwOnFailure  If the command has a non-zero exit code, throw {@see RunCommandFailedException}
     * @param bool $catchExceptions @see Application::setCatchExceptions()
     */
    public function __construct(
        public readonly string $input,
        public readonly bool $throwOnFailure = true,
        public readonly bool $catchExceptions = false,
    ) {
    }

    public function __toString(): string
    {
        return $this->input;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Messenger;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\RunCommandFailedException;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Messenger\Exception\RecoverableExceptionInterface;
use Symfony\Component\Messenger\Exception\UnrecoverableExceptionInterface;

/**
 * @author Kevin Bond <kevinbond@gmail.com>
 */
final class RunCommandMessageHandler
{
    public function __construct(private readonly Application $application)
    {
    }

    public function __invoke(RunCommandMessage $message): RunCommandContext
    {
        $input = new StringInput($message->input);
        $output = new BufferedOutput();

        $this->application->setCatchExceptions($message->catchExceptions);

        try {
            $exitCode = $this->application->run($input, $output);
        } catch (UnrecoverableExceptionInterface|RecoverableExceptionInterface $e) {
            throw $e;
        } catch (\Throwable $e) {
            throw new RunCommandFailedException($e, new RunCommandContext($message, Command::FAILURE, $output->fetch()));
        }

        if ($message->throwOnFailure && Command::SUCCESS !== $exitCode) {
            throw new RunCommandFailedException(\sprintf('Command "%s" exited with code "%s".', $message->input, $exitCode), new RunCommandContext($message, $exitCode, $output->fetch()));
        }

        return new RunCommandContext($message, $exitCode, $output->fetch());
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Messenger;

/**
 * @author Kevin Bond <kevinbond@gmail.com>
 */
final class RunCommandContext
{
    public function __construct(
        public readonly RunCommandMessage $message,
        public readonly int $exitCode,
        public readonly string $output,
    ) {
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\CI;

use Symfony\Component\Console\Output\OutputInterface;

/**
 * Utility class for Github actions.
 *
 * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
 */
class GithubActionReporter
{
    private OutputInterface $output;

    /**
     * @see https://github.com/actions/toolkit/blob/5e5e1b7aacba68a53836a34db4a288c3c1c1585b/packages/core/src/command.ts#L80-L85
     */
    private const ESCAPED_DATA = [
        '%' => '%25',
        "\r" => '%0D',
        "\n" => '%0A',
    ];

    /**
     * @see https://github.com/actions/toolkit/blob/5e5e1b7aacba68a53836a34db4a288c3c1c1585b/packages/core/src/command.ts#L87-L94
     */
    private const ESCAPED_PROPERTIES = [
        '%' => '%25',
        "\r" => '%0D',
        "\n" => '%0A',
        ':' => '%3A',
        ',' => '%2C',
    ];

    public function __construct(OutputInterface $output)
    {
        $this->output = $output;
    }

    public static function isGithubActionEnvironment(): bool
    {
        return false !== getenv('GITHUB_ACTIONS');
    }

    /**
     * Output an error using the Github annotations format.
     *
     * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-error-message
     */
    public function error(string $message, ?string $file = null, ?int $line = null, ?int $col = null): void
    {
        $this->log('error', $message, $file, $line, $col);
    }

    /**
     * Output a warning using the Github annotations format.
     *
     * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message
     */
    public function warning(string $message, ?string $file = null, ?int $line = null, ?int $col = null): void
    {
        $this->log('warning', $message, $file, $line, $col);
    }

    /**
     * Output a debug log using the Github annotations format.
     *
     * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-a-debug-message
     */
    public function debug(string $message, ?string $file = null, ?int $line = null, ?int $col = null): void
    {
        $this->log('debug', $message, $file, $line, $col);
    }

    private function log(string $type, string $message, ?string $file = null, ?int $line = null, ?int $col = null): void
    {
        // Some values must be encoded.
        $message = strtr($message, self::ESCAPED_DATA);

        if (!$file) {
            // No file provided, output the message solely:
            $this->output->writeln(\sprintf('::%s::%s', $type, $message));

            return;
        }

        $this->output->writeln(\sprintf('::%s file=%s,line=%s,col=%s::%s', $type, strtr($file, self::ESCAPED_PROPERTIES), strtr($line ?? 1, self::ESCAPED_PROPERTIES), strtr($col ?? 0, self::ESCAPED_PROPERTIES), $message));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Attribute;

/**
 * Service tag to autoconfigure commands.
 */
#[\Attribute(\Attribute::TARGET_CLASS)]
class AsCommand
{
    public function __construct(
        public string $name,
        public ?string $description = null,
        array $aliases = [],
        bool $hidden = false,
    ) {
        if (!$hidden && !$aliases) {
            return;
        }

        $name = explode('|', $name);
        $name = array_merge($name, $aliases);

        if ($hidden && '' !== $name[0]) {
            array_unshift($name, '');
        }

        $this->name = implode('|', $name);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Grégoire Pineau <lyrixx@lyrixx.info>
 */
class SingleCommandApplication extends Command
{
    private string $version = 'UNKNOWN';
    private bool $autoExit = true;
    private bool $running = false;

    /**
     * @return $this
     */
    public function setVersion(string $version): static
    {
        $this->version = $version;

        return $this;
    }

    /**
     * @final
     *
     * @return $this
     */
    public function setAutoExit(bool $autoExit): static
    {
        $this->autoExit = $autoExit;

        return $this;
    }

    public function run(?InputInterface $input = null, ?OutputInterface $output = null): int
    {
        if ($this->running) {
            return parent::run($input, $output);
        }

        // We use the command name as the application name
        $application = new Application($this->getName() ?: 'UNKNOWN', $this->version);
        $application->setAutoExit($this->autoExit);
        // Fix the usage of the command displayed with "--help"
        $this->setName($_SERVER['argv'][0]);
        $application->add($this);
        $application->setDefaultCommand($this->getName(), true);

        $this->running = true;
        try {
            $ret = $application->run($input, $output);
        } finally {
            $this->running = false;
        }

        return $ret ?? 1;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;

/**
 * A InputDefinition represents a set of valid command line arguments and options.
 *
 * Usage:
 *
 *     $definition = new InputDefinition([
 *         new InputArgument('name', InputArgument::REQUIRED),
 *         new InputOption('foo', 'f', InputOption::VALUE_REQUIRED),
 *     ]);
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class InputDefinition
{
    private array $arguments = [];
    private int $requiredCount = 0;
    private ?InputArgument $lastArrayArgument = null;
    private ?InputArgument $lastOptionalArgument = null;
    private array $options = [];
    private array $negations = [];
    private array $shortcuts = [];

    /**
     * @param array $definition An array of InputArgument and InputOption instance
     */
    public function __construct(array $definition = [])
    {
        $this->setDefinition($definition);
    }

    /**
     * Sets the definition of the input.
     *
     * @return void
     */
    public function setDefinition(array $definition)
    {
        $arguments = [];
        $options = [];
        foreach ($definition as $item) {
            if ($item instanceof InputOption) {
                $options[] = $item;
            } else {
                $arguments[] = $item;
            }
        }

        $this->setArguments($arguments);
        $this->setOptions($options);
    }

    /**
     * Sets the InputArgument objects.
     *
     * @param InputArgument[] $arguments An array of InputArgument objects
     *
     * @return void
     */
    public function setArguments(array $arguments = [])
    {
        $this->arguments = [];
        $this->requiredCount = 0;
        $this->lastOptionalArgument = null;
        $this->lastArrayArgument = null;
        $this->addArguments($arguments);
    }

    /**
     * Adds an array of InputArgument objects.
     *
     * @param InputArgument[] $arguments An array of InputArgument objects
     *
     * @return void
     */
    public function addArguments(?array $arguments = [])
    {
        if (null !== $arguments) {
            foreach ($arguments as $argument) {
                $this->addArgument($argument);
            }
        }
    }

    /**
     * @return void
     *
     * @throws LogicException When incorrect argument is given
     */
    public function addArgument(InputArgument $argument)
    {
        if (isset($this->arguments[$argument->getName()])) {
            throw new LogicException(\sprintf('An argument with name "%s" already exists.', $argument->getName()));
        }

        if (null !== $this->lastArrayArgument) {
            throw new LogicException(\sprintf('Cannot add a required argument "%s" after an array argument "%s".', $argument->getName(), $this->lastArrayArgument->getName()));
        }

        if ($argument->isRequired() && null !== $this->lastOptionalArgument) {
            throw new LogicException(\sprintf('Cannot add a required argument "%s" after an optional one "%s".', $argument->getName(), $this->lastOptionalArgument->getName()));
        }

        if ($argument->isArray()) {
            $this->lastArrayArgument = $argument;
        }

        if ($argument->isRequired()) {
            ++$this->requiredCount;
        } else {
            $this->lastOptionalArgument = $argument;
        }

        $this->arguments[$argument->getName()] = $argument;
    }

    /**
     * Returns an InputArgument by name or by position.
     *
     * @throws InvalidArgumentException When argument given doesn't exist
     */
    public function getArgument(string|int $name): InputArgument
    {
        if (!$this->hasArgument($name)) {
            throw new InvalidArgumentException(\sprintf('The "%s" argument does not exist.', $name));
        }

        $arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments;

        return $arguments[$name];
    }

    /**
     * Returns true if an InputArgument object exists by name or position.
     */
    public function hasArgument(string|int $name): bool
    {
        $arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments;

        return isset($arguments[$name]);
    }

    /**
     * Gets the array of InputArgument objects.
     *
     * @return InputArgument[]
     */
    public function getArguments(): array
    {
        return $this->arguments;
    }

    /**
     * Returns the number of InputArguments.
     */
    public function getArgumentCount(): int
    {
        return null !== $this->lastArrayArgument ? \PHP_INT_MAX : \count($this->arguments);
    }

    /**
     * Returns the number of required InputArguments.
     */
    public function getArgumentRequiredCount(): int
    {
        return $this->requiredCount;
    }

    /**
     * @return array<string|bool|int|float|array|null>
     */
    public function getArgumentDefaults(): array
    {
        $values = [];
        foreach ($this->arguments as $argument) {
            $values[$argument->getName()] = $argument->getDefault();
        }

        return $values;
    }

    /**
     * Sets the InputOption objects.
     *
     * @param InputOption[] $options An array of InputOption objects
     *
     * @return void
     */
    public function setOptions(array $options = [])
    {
        $this->options = [];
        $this->shortcuts = [];
        $this->negations = [];
        $this->addOptions($options);
    }

    /**
     * Adds an array of InputOption objects.
     *
     * @param InputOption[] $options An array of InputOption objects
     *
     * @return void
     */
    public function addOptions(array $options = [])
    {
        foreach ($options as $option) {
            $this->addOption($option);
        }
    }

    /**
     * @return void
     *
     * @throws LogicException When option given already exist
     */
    public function addOption(InputOption $option)
    {
        if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) {
            throw new LogicException(\sprintf('An option named "%s" already exists.', $option->getName()));
        }
        if (isset($this->negations[$option->getName()])) {
            throw new LogicException(\sprintf('An option named "%s" already exists.', $option->getName()));
        }

        if ($option->getShortcut()) {
            foreach (explode('|', $option->getShortcut()) as $shortcut) {
                if (isset($this->shortcuts[$shortcut]) && !$option->equals($this->options[$this->shortcuts[$shortcut]])) {
                    throw new LogicException(\sprintf('An option with shortcut "%s" already exists.', $shortcut));
                }
            }
        }

        $this->options[$option->getName()] = $option;
        if ($option->getShortcut()) {
            foreach (explode('|', $option->getShortcut()) as $shortcut) {
                $this->shortcuts[$shortcut] = $option->getName();
            }
        }

        if ($option->isNegatable()) {
            $negatedName = 'no-'.$option->getName();
            if (isset($this->options[$negatedName])) {
                throw new LogicException(\sprintf('An option named "%s" already exists.', $negatedName));
            }
            $this->negations[$negatedName] = $option->getName();
        }
    }

    /**
     * Returns an InputOption by name.
     *
     * @throws InvalidArgumentException When option given doesn't exist
     */
    public function getOption(string $name): InputOption
    {
        if (!$this->hasOption($name)) {
            throw new InvalidArgumentException(\sprintf('The "--%s" option does not exist.', $name));
        }

        return $this->options[$name];
    }

    /**
     * Returns true if an InputOption object exists by name.
     *
     * This method can't be used to check if the user included the option when
     * executing the command (use getOption() instead).
     */
    public function hasOption(string $name): bool
    {
        return isset($this->options[$name]);
    }

    /**
     * Gets the array of InputOption objects.
     *
     * @return InputOption[]
     */
    public function getOptions(): array
    {
        return $this->options;
    }

    /**
     * Returns true if an InputOption object exists by shortcut.
     */
    public function hasShortcut(string $name): bool
    {
        return isset($this->shortcuts[$name]);
    }

    /**
     * Returns true if an InputOption object exists by negated name.
     */
    public function hasNegation(string $name): bool
    {
        return isset($this->negations[$name]);
    }

    /**
     * Gets an InputOption by shortcut.
     */
    public function getOptionForShortcut(string $shortcut): InputOption
    {
        return $this->getOption($this->shortcutToName($shortcut));
    }

    /**
     * @return array<string|bool|int|float|array|null>
     */
    public function getOptionDefaults(): array
    {
        $values = [];
        foreach ($this->options as $option) {
            $values[$option->getName()] = $option->getDefault();
        }

        return $values;
    }

    /**
     * Returns the InputOption name given a shortcut.
     *
     * @throws InvalidArgumentException When option given does not exist
     *
     * @internal
     */
    public function shortcutToName(string $shortcut): string
    {
        if (!isset($this->shortcuts[$shortcut])) {
            throw new InvalidArgumentException(\sprintf('The "-%s" option does not exist.', $shortcut));
        }

        return $this->shortcuts[$shortcut];
    }

    /**
     * Returns the InputOption name given a negation.
     *
     * @throws InvalidArgumentException When option given does not exist
     *
     * @internal
     */
    public function negationToName(string $negation): string
    {
        if (!isset($this->negations[$negation])) {
            throw new InvalidArgumentException(\sprintf('The "--%s" option does not exist.', $negation));
        }

        return $this->negations[$negation];
    }

    /**
     * Gets the synopsis.
     */
    public function getSynopsis(bool $short = false): string
    {
        $elements = [];

        if ($short && $this->getOptions()) {
            $elements[] = '[options]';
        } elseif (!$short) {
            foreach ($this->getOptions() as $option) {
                $value = '';
                if ($option->acceptValue()) {
                    $value = \sprintf(
                        ' %s%s%s',
                        $option->isValueOptional() ? '[' : '',
                        strtoupper($option->getName()),
                        $option->isValueOptional() ? ']' : ''
                    );
                }

                $shortcut = $option->getShortcut() ? \sprintf('-%s|', $option->getShortcut()) : '';
                $negation = $option->isNegatable() ? \sprintf('|--no-%s', $option->getName()) : '';
                $elements[] = \sprintf('[%s--%s%s%s]', $shortcut, $option->getName(), $value, $negation);
            }
        }

        if (\count($elements) && $this->getArguments()) {
            $elements[] = '[--]';
        }

        $tail = '';
        foreach ($this->getArguments() as $argument) {
            $element = '<'.$argument->getName().'>';
            if ($argument->isArray()) {
                $element .= '...';
            }

            if (!$argument->isRequired()) {
                $element = '['.$element;
                $tail .= ']';
            }

            $elements[] = $element;
        }

        return implode(' ', $elements).$tail;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;

/**
 * Input is the base class for all concrete Input classes.
 *
 * Three concrete classes are provided by default:
 *
 *  * `ArgvInput`: The input comes from the CLI arguments (argv)
 *  * `StringInput`: The input is provided as a string
 *  * `ArrayInput`: The input is provided as an array
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
abstract class Input implements InputInterface, StreamableInputInterface
{
    protected $definition;
    /** @var resource */
    protected $stream;
    protected $options = [];
    protected $arguments = [];
    protected $interactive = true;

    public function __construct(?InputDefinition $definition = null)
    {
        if (null === $definition) {
            $this->definition = new InputDefinition();
        } else {
            $this->bind($definition);
            $this->validate();
        }
    }

    /**
     * @return void
     */
    public function bind(InputDefinition $definition)
    {
        $this->arguments = [];
        $this->options = [];
        $this->definition = $definition;

        $this->parse();
    }

    /**
     * Processes command line arguments.
     *
     * @return void
     */
    abstract protected function parse();

    /**
     * @return void
     */
    public function validate()
    {
        $definition = $this->definition;
        $givenArguments = $this->arguments;

        $missingArguments = array_filter(array_keys($definition->getArguments()), static fn ($argument) => !\array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired());

        if (\count($missingArguments) > 0) {
            throw new RuntimeException(\sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArguments)));
        }
    }

    public function isInteractive(): bool
    {
        return $this->interactive;
    }

    /**
     * @return void
     */
    public function setInteractive(bool $interactive)
    {
        $this->interactive = $interactive;
    }

    public function getArguments(): array
    {
        return array_merge($this->definition->getArgumentDefaults(), $this->arguments);
    }

    public function getArgument(string $name): mixed
    {
        if (!$this->definition->hasArgument($name)) {
            throw new InvalidArgumentException(\sprintf('The "%s" argument does not exist.', $name));
        }

        return $this->arguments[$name] ?? $this->definition->getArgument($name)->getDefault();
    }

    /**
     * @return void
     */
    public function setArgument(string $name, mixed $value)
    {
        if (!$this->definition->hasArgument($name)) {
            throw new InvalidArgumentException(\sprintf('The "%s" argument does not exist.', $name));
        }

        $this->arguments[$name] = $value;
    }

    public function hasArgument(string $name): bool
    {
        return $this->definition->hasArgument($name);
    }

    public function getOptions(): array
    {
        return array_merge($this->definition->getOptionDefaults(), $this->options);
    }

    public function getOption(string $name): mixed
    {
        if ($this->definition->hasNegation($name)) {
            if (null === $value = $this->getOption($this->definition->negationToName($name))) {
                return $value;
            }

            return !$value;
        }

        if (!$this->definition->hasOption($name)) {
            throw new InvalidArgumentException(\sprintf('The "%s" option does not exist.', $name));
        }

        return \array_key_exists($name, $this->options) ? $this->options[$name] : $this->definition->getOption($name)->getDefault();
    }

    /**
     * @return void
     */
    public function setOption(string $name, mixed $value)
    {
        if ($this->definition->hasNegation($name)) {
            $this->options[$this->definition->negationToName($name)] = !$value;

            return;
        } elseif (!$this->definition->hasOption($name)) {
            throw new InvalidArgumentException(\sprintf('The "%s" option does not exist.', $name));
        }

        $this->options[$name] = $value;
    }

    public function hasOption(string $name): bool
    {
        return $this->definition->hasOption($name) || $this->definition->hasNegation($name);
    }

    /**
     * Escapes a token through escapeshellarg if it contains unsafe chars.
     */
    public function escapeToken(string $token): string
    {
        return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token);
    }

    /**
     * @param resource $stream
     *
     * @return void
     */
    public function setStream($stream)
    {
        $this->stream = $stream;
    }

    /**
     * @return resource
     */
    public function getStream()
    {
        return $this->stream;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\RuntimeException;

/**
 * ArgvInput represents an input coming from the CLI arguments.
 *
 * Usage:
 *
 *     $input = new ArgvInput();
 *
 * By default, the `$_SERVER['argv']` array is used for the input values.
 *
 * This can be overridden by explicitly passing the input values in the constructor:
 *
 *     $input = new ArgvInput($_SERVER['argv']);
 *
 * If you pass it yourself, don't forget that the first element of the array
 * is the name of the running application.
 *
 * When passing an argument to the constructor, be sure that it respects
 * the same rules as the argv one. It's almost always better to use the
 * `StringInput` when you want to provide your own input.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html
 * @see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02
 */
class ArgvInput extends Input
{
    private array $tokens;
    private array $parsed;

    public function __construct(?array $argv = null, ?InputDefinition $definition = null)
    {
        $argv ??= $_SERVER['argv'] ?? [];

        // strip the application name
        array_shift($argv);

        $this->tokens = $argv;

        parent::__construct($definition);
    }

    /**
     * @return void
     */
    protected function setTokens(array $tokens)
    {
        $this->tokens = $tokens;
    }

    /**
     * @return void
     */
    protected function parse()
    {
        $parseOptions = true;
        $this->parsed = $this->tokens;
        while (null !== $token = array_shift($this->parsed)) {
            $parseOptions = $this->parseToken($token, $parseOptions);
        }
    }

    protected function parseToken(string $token, bool $parseOptions): bool
    {
        if ($parseOptions && '' == $token) {
            $this->parseArgument($token);
        } elseif ($parseOptions && '--' == $token) {
            return false;
        } elseif ($parseOptions && str_starts_with($token, '--')) {
            $this->parseLongOption($token);
        } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) {
            $this->parseShortOption($token);
        } else {
            $this->parseArgument($token);
        }

        return $parseOptions;
    }

    /**
     * Parses a short option.
     */
    private function parseShortOption(string $token): void
    {
        $name = substr($token, 1);

        if (\strlen($name) > 1) {
            if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) {
                // an option with a value (with no space)
                $this->addShortOption($name[0], substr($name, 1));
            } else {
                $this->parseShortOptionSet($name);
            }
        } else {
            $this->addShortOption($name, null);
        }
    }

    /**
     * Parses a short option set.
     *
     * @throws RuntimeException When option given doesn't exist
     */
    private function parseShortOptionSet(string $name): void
    {
        $len = \strlen($name);
        for ($i = 0; $i < $len; ++$i) {
            if (!$this->definition->hasShortcut($name[$i])) {
                $encoding = mb_detect_encoding($name, null, true);
                throw new RuntimeException(\sprintf('The "-%s" option does not exist.', false === $encoding ? $name[$i] : mb_substr($name, $i, 1, $encoding)));
            }

            $option = $this->definition->getOptionForShortcut($name[$i]);
            if ($option->acceptValue()) {
                $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1));

                break;
            }
            $this->addLongOption($option->getName(), null);
        }
    }

    /**
     * Parses a long option.
     */
    private function parseLongOption(string $token): void
    {
        $name = substr($token, 2);

        if (false !== $pos = strpos($name, '=')) {
            if ('' === $value = substr($name, $pos + 1)) {
                array_unshift($this->parsed, $value);
            }
            $this->addLongOption(substr($name, 0, $pos), $value);
        } else {
            $this->addLongOption($name, null);
        }
    }

    /**
     * Parses an argument.
     *
     * @throws RuntimeException When too many arguments are given
     */
    private function parseArgument(string $token): void
    {
        $c = \count($this->arguments);

        // if input is expecting another argument, add it
        if ($this->definition->hasArgument($c)) {
            $arg = $this->definition->getArgument($c);
            $this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token;

        // if last argument isArray(), append token to last argument
        } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) {
            $arg = $this->definition->getArgument($c - 1);
            $this->arguments[$arg->getName()][] = $token;

        // unexpected argument
        } else {
            $all = $this->definition->getArguments();
            $symfonyCommandName = null;
            if (($inputArgument = $all[$key = array_key_first($all) ?? ''] ?? null) && 'command' === $inputArgument->getName()) {
                $symfonyCommandName = $this->arguments['command'] ?? null;
                unset($all[$key]);
            }

            if (\count($all)) {
                if ($symfonyCommandName) {
                    $message = \sprintf('Too many arguments to "%s" command, expected arguments "%s".', $symfonyCommandName, implode('" "', array_keys($all)));
                } else {
                    $message = \sprintf('Too many arguments, expected arguments "%s".', implode('" "', array_keys($all)));
                }
            } elseif ($symfonyCommandName) {
                $message = \sprintf('No arguments expected for "%s" command, got "%s".', $symfonyCommandName, $token);
            } else {
                $message = \sprintf('No arguments expected, got "%s".', $token);
            }

            throw new RuntimeException($message);
        }
    }

    /**
     * Adds a short option value.
     *
     * @throws RuntimeException When option given doesn't exist
     */
    private function addShortOption(string $shortcut, mixed $value): void
    {
        if (!$this->definition->hasShortcut($shortcut)) {
            throw new RuntimeException(\sprintf('The "-%s" option does not exist.', $shortcut));
        }

        $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value);
    }

    /**
     * Adds a long option value.
     *
     * @throws RuntimeException When option given doesn't exist
     */
    private function addLongOption(string $name, mixed $value): void
    {
        if (!$this->definition->hasOption($name)) {
            if (!$this->definition->hasNegation($name)) {
                throw new RuntimeException(\sprintf('The "--%s" option does not exist.', $name));
            }

            $optionName = $this->definition->negationToName($name);
            if (null !== $value) {
                throw new RuntimeException(\sprintf('The "--%s" option does not accept a value.', $name));
            }
            $this->options[$optionName] = false;

            return;
        }

        $option = $this->definition->getOption($name);

        if (null !== $value && !$option->acceptValue()) {
            throw new RuntimeException(\sprintf('The "--%s" option does not accept a value.', $name));
        }

        if (\in_array($value, ['', null], true) && $option->acceptValue() && \count($this->parsed)) {
            // if option accepts an optional or mandatory argument
            // let's see if there is one provided
            $next = array_shift($this->parsed);
            if ((isset($next[0]) && '-' !== $next[0]) || \in_array($next, ['', null], true)) {
                $value = $next;
            } else {
                array_unshift($this->parsed, $next);
            }
        }

        if (null === $value) {
            if ($option->isValueRequired()) {
                throw new RuntimeException(\sprintf('The "--%s" option requires a value.', $name));
            }

            if (!$option->isArray() && !$option->isValueOptional()) {
                $value = true;
            }
        }

        if ($option->isArray()) {
            $this->options[$name][] = $value;
        } else {
            $this->options[$name] = $value;
        }
    }

    public function getFirstArgument(): ?string
    {
        $isOption = false;
        foreach ($this->tokens as $i => $token) {
            if ($token && '-' === $token[0]) {
                if (str_contains($token, '=') || !isset($this->tokens[$i + 1])) {
                    continue;
                }

                // If it's a long option, consider that everything after "--" is the option name.
                // Otherwise, use the last char (if it's a short option set, only the last one can take a value with space separator)
                $name = '-' === $token[1] ? substr($token, 2) : substr($token, -1);
                if (!isset($this->options[$name]) && !$this->definition->hasShortcut($name)) {
                    // noop
                } elseif ((isset($this->options[$name]) || isset($this->options[$name = $this->definition->shortcutToName($name)])) && $this->tokens[$i + 1] === $this->options[$name]) {
                    $isOption = true;
                }

                continue;
            }

            if ($isOption) {
                $isOption = false;
                continue;
            }

            return $token;
        }

        return null;
    }

    public function hasParameterOption(string|array $values, bool $onlyParams = false): bool
    {
        $values = (array) $values;

        foreach ($this->tokens as $token) {
            if ($onlyParams && '--' === $token) {
                return false;
            }
            foreach ($values as $value) {
                // Options with values:
                //   For long options, test for '--option=' at beginning
                //   For short options, test for '-o' at beginning
                $leading = str_starts_with($value, '--') ? $value.'=' : $value;
                if ($token === $value || '' !== $leading && str_starts_with($token, $leading)) {
                    return true;
                }
            }
        }

        return false;
    }

    public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false): mixed
    {
        $values = (array) $values;
        $tokens = $this->tokens;

        while (0 < \count($tokens)) {
            $token = array_shift($tokens);
            if ($onlyParams && '--' === $token) {
                return $default;
            }

            foreach ($values as $value) {
                if ($token === $value) {
                    return array_shift($tokens);
                }
                // Options with values:
                //   For long options, test for '--option=' at beginning
                //   For short options, test for '-o' at beginning
                $leading = str_starts_with($value, '--') ? $value.'=' : $value;
                if ('' !== $leading && str_starts_with($token, $leading)) {
                    return substr($token, \strlen($leading));
                }
            }
        }

        return $default;
    }

    /**
     * Returns a stringified representation of the args passed to the command.
     */
    public function __toString(): string
    {
        $tokens = array_map(function ($token) {
            if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) {
                return $match[1].$this->escapeToken($match[2]);
            }

            if ($token && '-' !== $token[0]) {
                return $this->escapeToken($token);
            }

            return $token;
        }, $this->tokens);

        return implode(' ', $tokens);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

/**
 * InputAwareInterface should be implemented by classes that depends on the
 * Console Input.
 *
 * @author Wouter J <waldio.webdesign@gmail.com>
 */
interface InputAwareInterface
{
    /**
     * Sets the Console Input.
     *
     * @return void
     */
    public function setInput(InputInterface $input);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Completion\Suggestion;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;

/**
 * Represents a command line option.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class InputOption
{
    /**
     * Do not accept input for the option (e.g. --yell). This is the default behavior of options.
     */
    public const VALUE_NONE = 1;

    /**
     * A value must be passed when the option is used (e.g. --iterations=5 or -i5).
     */
    public const VALUE_REQUIRED = 2;

    /**
     * The option may or may not have a value (e.g. --yell or --yell=loud).
     */
    public const VALUE_OPTIONAL = 4;

    /**
     * The option accepts multiple values (e.g. --dir=/foo --dir=/bar).
     */
    public const VALUE_IS_ARRAY = 8;

    /**
     * The option may have either positive or negative value (e.g. --ansi or --no-ansi).
     */
    public const VALUE_NEGATABLE = 16;

    private string $name;
    private string|array|null $shortcut;
    private int $mode;
    private string|int|bool|array|float|null $default;
    private array|\Closure $suggestedValues;
    private string $description;

    /**
     * @param string|array|null                                                             $shortcut        The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
     * @param int|null                                                                      $mode            The option mode: One of the VALUE_* constants
     * @param string|bool|int|float|array|null                                              $default         The default value (must be null for self::VALUE_NONE)
     * @param array|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion> $suggestedValues The values used for input completion
     *
     * @throws InvalidArgumentException If option mode is invalid or incompatible
     */
    public function __construct(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', string|bool|int|float|array|null $default = null, array|\Closure $suggestedValues = [])
    {
        if (str_starts_with($name, '--')) {
            $name = substr($name, 2);
        }

        if (empty($name)) {
            throw new InvalidArgumentException('An option name cannot be empty.');
        }

        if ('' === $shortcut || [] === $shortcut || false === $shortcut) {
            $shortcut = null;
        }

        if (null !== $shortcut) {
            if (\is_array($shortcut)) {
                $shortcut = implode('|', $shortcut);
            }
            $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-'));
            $shortcuts = array_filter($shortcuts, 'strlen');
            $shortcut = implode('|', $shortcuts);

            if ('' === $shortcut) {
                throw new InvalidArgumentException('An option shortcut cannot be empty.');
            }
        }

        if (null === $mode) {
            $mode = self::VALUE_NONE;
        } elseif ($mode >= (self::VALUE_NEGATABLE << 1) || $mode < 1) {
            throw new InvalidArgumentException(\sprintf('Option mode "%s" is not valid.', $mode));
        }

        $this->name = $name;
        $this->shortcut = $shortcut;
        $this->mode = $mode;
        $this->description = $description;
        $this->suggestedValues = $suggestedValues;

        if ($suggestedValues && !$this->acceptValue()) {
            throw new LogicException('Cannot set suggested values if the option does not accept a value.');
        }
        if ($this->isArray() && !$this->acceptValue()) {
            throw new InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.');
        }
        if ($this->isNegatable() && $this->acceptValue()) {
            throw new InvalidArgumentException('Impossible to have an option mode VALUE_NEGATABLE if the option also accepts a value.');
        }

        $this->setDefault($default);
    }

    /**
     * Returns the option shortcut.
     */
    public function getShortcut(): ?string
    {
        return $this->shortcut;
    }

    /**
     * Returns the option name.
     */
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * Returns true if the option accepts a value.
     *
     * @return bool true if value mode is not self::VALUE_NONE, false otherwise
     */
    public function acceptValue(): bool
    {
        return $this->isValueRequired() || $this->isValueOptional();
    }

    /**
     * Returns true if the option requires a value.
     *
     * @return bool true if value mode is self::VALUE_REQUIRED, false otherwise
     */
    public function isValueRequired(): bool
    {
        return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode);
    }

    /**
     * Returns true if the option takes an optional value.
     *
     * @return bool true if value mode is self::VALUE_OPTIONAL, false otherwise
     */
    public function isValueOptional(): bool
    {
        return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode);
    }

    /**
     * Returns true if the option can take multiple values.
     *
     * @return bool true if mode is self::VALUE_IS_ARRAY, false otherwise
     */
    public function isArray(): bool
    {
        return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode);
    }

    public function isNegatable(): bool
    {
        return self::VALUE_NEGATABLE === (self::VALUE_NEGATABLE & $this->mode);
    }

    /**
     * @return void
     */
    public function setDefault(string|bool|int|float|array|null $default = null)
    {
        if (1 > \func_num_args()) {
            trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
        }
        if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) {
            throw new LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.');
        }

        if ($this->isArray()) {
            if (null === $default) {
                $default = [];
            } elseif (!\is_array($default)) {
                throw new LogicException('A default value for an array option must be an array.');
            }
        }

        $this->default = $this->acceptValue() || $this->isNegatable() ? $default : false;
    }

    /**
     * Returns the default value.
     */
    public function getDefault(): string|bool|int|float|array|null
    {
        return $this->default;
    }

    /**
     * Returns the description text.
     */
    public function getDescription(): string
    {
        return $this->description;
    }

    public function hasCompletion(): bool
    {
        return [] !== $this->suggestedValues;
    }

    /**
     * Adds suggestions to $suggestions for the current completion input.
     *
     * @see Command::complete()
     */
    public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
    {
        $values = $this->suggestedValues;
        if ($values instanceof \Closure && !\is_array($values = $values($input))) {
            throw new LogicException(\sprintf('Closure for option "%s" must return an array. Got "%s".', $this->name, get_debug_type($values)));
        }
        if ($values) {
            $suggestions->suggestValues($values);
        }
    }

    /**
     * Checks whether the given option equals this one.
     */
    public function equals(self $option): bool
    {
        return $option->getName() === $this->getName()
            && $option->getShortcut() === $this->getShortcut()
            && $option->getDefault() === $this->getDefault()
            && $option->isNegatable() === $this->isNegatable()
            && $option->isArray() === $this->isArray()
            && $option->isValueRequired() === $this->isValueRequired()
            && $option->isValueOptional() === $this->isValueOptional()
        ;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\InvalidOptionException;

/**
 * ArrayInput represents an input provided as an array.
 *
 * Usage:
 *
 *     $input = new ArrayInput(['command' => 'foo:bar', 'foo' => 'bar', '--bar' => 'foobar']);
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ArrayInput extends Input
{
    private array $parameters;

    public function __construct(array $parameters, ?InputDefinition $definition = null)
    {
        $this->parameters = $parameters;

        parent::__construct($definition);
    }

    public function getFirstArgument(): ?string
    {
        foreach ($this->parameters as $param => $value) {
            if ($param && \is_string($param) && '-' === $param[0]) {
                continue;
            }

            return $value;
        }

        return null;
    }

    public function hasParameterOption(string|array $values, bool $onlyParams = false): bool
    {
        $values = (array) $values;

        foreach ($this->parameters as $k => $v) {
            if (!\is_int($k)) {
                $v = $k;
            }

            if ($onlyParams && '--' === $v) {
                return false;
            }

            if (\in_array($v, $values)) {
                return true;
            }
        }

        return false;
    }

    public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false): mixed
    {
        $values = (array) $values;

        foreach ($this->parameters as $k => $v) {
            if ($onlyParams && ('--' === $k || (\is_int($k) && '--' === $v))) {
                return $default;
            }

            if (\is_int($k)) {
                if (\in_array($v, $values)) {
                    return true;
                }
            } elseif (\in_array($k, $values)) {
                return $v;
            }
        }

        return $default;
    }

    /**
     * Returns a stringified representation of the args passed to the command.
     */
    public function __toString(): string
    {
        $params = [];
        foreach ($this->parameters as $param => $val) {
            if ($param && \is_string($param) && '-' === $param[0]) {
                $glue = ('-' === $param[1]) ? '=' : ' ';
                if (\is_array($val)) {
                    foreach ($val as $v) {
                        $params[] = $param.('' != $v ? $glue.$this->escapeToken($v) : '');
                    }
                } else {
                    $params[] = $param.('' != $val ? $glue.$this->escapeToken($val) : '');
                }
            } else {
                $params[] = \is_array($val) ? implode(' ', array_map($this->escapeToken(...), $val)) : $this->escapeToken($val);
            }
        }

        return implode(' ', $params);
    }

    /**
     * @return void
     */
    protected function parse()
    {
        foreach ($this->parameters as $key => $value) {
            if ('--' === $key) {
                return;
            }
            if (str_starts_with($key, '--')) {
                $this->addLongOption(substr($key, 2), $value);
            } elseif (str_starts_with($key, '-')) {
                $this->addShortOption(substr($key, 1), $value);
            } else {
                $this->addArgument($key, $value);
            }
        }
    }

    /**
     * Adds a short option value.
     *
     * @throws InvalidOptionException When option given doesn't exist
     */
    private function addShortOption(string $shortcut, mixed $value): void
    {
        if (!$this->definition->hasShortcut($shortcut)) {
            throw new InvalidOptionException(\sprintf('The "-%s" option does not exist.', $shortcut));
        }

        $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value);
    }

    /**
     * Adds a long option value.
     *
     * @throws InvalidOptionException When option given doesn't exist
     * @throws InvalidOptionException When a required value is missing
     */
    private function addLongOption(string $name, mixed $value): void
    {
        if (!$this->definition->hasOption($name)) {
            if (!$this->definition->hasNegation($name)) {
                throw new InvalidOptionException(\sprintf('The "--%s" option does not exist.', $name));
            }

            $optionName = $this->definition->negationToName($name);
            $this->options[$optionName] = false;

            return;
        }

        $option = $this->definition->getOption($name);

        if (null === $value) {
            if ($option->isValueRequired()) {
                throw new InvalidOptionException(\sprintf('The "--%s" option requires a value.', $name));
            }

            if (!$option->isValueOptional()) {
                $value = true;
            }
        }

        $this->options[$name] = $value;
    }

    /**
     * Adds an argument value.
     *
     * @throws InvalidArgumentException When argument given doesn't exist
     */
    private function addArgument(string|int $name, mixed $value): void
    {
        if (!$this->definition->hasArgument($name)) {
            throw new InvalidArgumentException(\sprintf('The "%s" argument does not exist.', $name));
        }

        $this->arguments[$name] = $value;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;

/**
 * InputInterface is the interface implemented by all input classes.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @method string __toString() Returns a stringified representation of the args passed to the command.
 *                             InputArguments MUST be escaped as well as the InputOption values passed to the command.
 */
interface InputInterface
{
    /**
     * Returns the first argument from the raw parameters (not parsed).
     */
    public function getFirstArgument(): ?string;

    /**
     * Returns true if the raw parameters (not parsed) contain a value.
     *
     * This method is to be used to introspect the input parameters
     * before they have been validated. It must be used carefully.
     * Does not necessarily return the correct result for short options
     * when multiple flags are combined in the same option.
     *
     * @param string|array $values     The values to look for in the raw parameters (can be an array)
     * @param bool         $onlyParams Only check real parameters, skip those following an end of options (--) signal
     */
    public function hasParameterOption(string|array $values, bool $onlyParams = false): bool;

    /**
     * Returns the value of a raw option (not parsed).
     *
     * This method is to be used to introspect the input parameters
     * before they have been validated. It must be used carefully.
     * Does not necessarily return the correct result for short options
     * when multiple flags are combined in the same option.
     *
     * @param string|array                     $values     The value(s) to look for in the raw parameters (can be an array)
     * @param string|bool|int|float|array|null $default    The default value to return if no result is found
     * @param bool                             $onlyParams Only check real parameters, skip those following an end of options (--) signal
     *
     * @return mixed
     */
    public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false);

    /**
     * Binds the current Input instance with the given arguments and options.
     *
     * @return void
     *
     * @throws RuntimeException
     */
    public function bind(InputDefinition $definition);

    /**
     * Validates the input.
     *
     * @return void
     *
     * @throws RuntimeException When not enough arguments are given
     */
    public function validate();

    /**
     * Returns all the given arguments merged with the default values.
     *
     * @return array<string|bool|int|float|array|null>
     */
    public function getArguments(): array;

    /**
     * Returns the argument value for a given argument name.
     *
     * @return mixed
     *
     * @throws InvalidArgumentException When argument given doesn't exist
     */
    public function getArgument(string $name);

    /**
     * Sets an argument value by name.
     *
     * @return void
     *
     * @throws InvalidArgumentException When argument given doesn't exist
     */
    public function setArgument(string $name, mixed $value);

    /**
     * Returns true if an InputArgument object exists by name or position.
     */
    public function hasArgument(string $name): bool;

    /**
     * Returns all the given options merged with the default values.
     *
     * @return array<string|bool|int|float|array|null>
     */
    public function getOptions(): array;

    /**
     * Returns the option value for a given option name.
     *
     * @return mixed
     *
     * @throws InvalidArgumentException When option given doesn't exist
     */
    public function getOption(string $name);

    /**
     * Sets an option value by name.
     *
     * @return void
     *
     * @throws InvalidArgumentException When option given doesn't exist
     */
    public function setOption(string $name, mixed $value);

    /**
     * Returns true if an InputOption object exists by name.
     */
    public function hasOption(string $name): bool;

    /**
     * Is this input means interactive?
     */
    public function isInteractive(): bool;

    /**
     * Sets the input interactivity.
     *
     * @return void
     */
    public function setInteractive(bool $interactive);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

/**
 * StreamableInputInterface is the interface implemented by all input classes
 * that have an input stream.
 *
 * @author Robin Chalas <robin.chalas@gmail.com>
 */
interface StreamableInputInterface extends InputInterface
{
    /**
     * Sets the input stream to read from when interacting with the user.
     *
     * This is mainly useful for testing purpose.
     *
     * @param resource $stream The input stream
     *
     * @return void
     */
    public function setStream($stream);

    /**
     * Returns the input stream.
     *
     * @return resource|null
     */
    public function getStream();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Completion\Suggestion;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;

/**
 * Represents a command line argument.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class InputArgument
{
    public const REQUIRED = 1;
    public const OPTIONAL = 2;
    public const IS_ARRAY = 4;

    private string $name;
    private int $mode;
    private string|int|bool|array|float|null $default;
    private array|\Closure $suggestedValues;
    private string $description;

    /**
     * @param string                                                                        $name            The argument name
     * @param int|null                                                                      $mode            The argument mode: a bit mask of self::REQUIRED, self::OPTIONAL and self::IS_ARRAY
     * @param string                                                                        $description     A description text
     * @param string|bool|int|float|array|null                                              $default         The default value (for self::OPTIONAL mode only)
     * @param array|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion> $suggestedValues The values used for input completion
     *
     * @throws InvalidArgumentException When argument mode is not valid
     */
    public function __construct(string $name, ?int $mode = null, string $description = '', string|bool|int|float|array|null $default = null, \Closure|array $suggestedValues = [])
    {
        if (null === $mode) {
            $mode = self::OPTIONAL;
        } elseif ($mode > 7 || $mode < 1) {
            throw new InvalidArgumentException(\sprintf('Argument mode "%s" is not valid.', $mode));
        }

        $this->name = $name;
        $this->mode = $mode;
        $this->description = $description;
        $this->suggestedValues = $suggestedValues;

        $this->setDefault($default);
    }

    /**
     * Returns the argument name.
     */
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * Returns true if the argument is required.
     *
     * @return bool true if parameter mode is self::REQUIRED, false otherwise
     */
    public function isRequired(): bool
    {
        return self::REQUIRED === (self::REQUIRED & $this->mode);
    }

    /**
     * Returns true if the argument can take multiple values.
     *
     * @return bool true if mode is self::IS_ARRAY, false otherwise
     */
    public function isArray(): bool
    {
        return self::IS_ARRAY === (self::IS_ARRAY & $this->mode);
    }

    /**
     * Sets the default value.
     *
     * @return void
     *
     * @throws LogicException When incorrect default value is given
     */
    public function setDefault(string|bool|int|float|array|null $default = null)
    {
        if (1 > \func_num_args()) {
            trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
        }
        if ($this->isRequired() && null !== $default) {
            throw new LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.');
        }

        if ($this->isArray()) {
            if (null === $default) {
                $default = [];
            } elseif (!\is_array($default)) {
                throw new LogicException('A default value for an array argument must be an array.');
            }
        }

        $this->default = $default;
    }

    /**
     * Returns the default value.
     */
    public function getDefault(): string|bool|int|float|array|null
    {
        return $this->default;
    }

    public function hasCompletion(): bool
    {
        return [] !== $this->suggestedValues;
    }

    /**
     * Adds suggestions to $suggestions for the current completion input.
     *
     * @see Command::complete()
     */
    public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
    {
        $values = $this->suggestedValues;
        if ($values instanceof \Closure && !\is_array($values = $values($input))) {
            throw new LogicException(\sprintf('Closure for argument "%s" must return an array. Got "%s".', $this->name, get_debug_type($values)));
        }
        if ($values) {
            $suggestions->suggestValues($values);
        }
    }

    /**
     * Returns the description text.
     */
    public function getDescription(): string
    {
        return $this->description;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * StringInput represents an input provided as a string.
 *
 * Usage:
 *
 *     $input = new StringInput('foo --bar="foobar"');
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class StringInput extends ArgvInput
{
    /**
     * @deprecated since Symfony 6.1
     */
    public const REGEX_STRING = '([^\s]+?)(?:\s|(?<!\\\\)"|(?<!\\\\)\'|$)';
    public const REGEX_UNQUOTED_STRING = '([^\s\\\\]+?)';
    public const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')';

    /**
     * @param string $input A string representing the parameters from the CLI
     */
    public function __construct(string $input)
    {
        parent::__construct([]);

        $this->setTokens($this->tokenize($input));
    }

    /**
     * Tokenizes a string.
     *
     * @throws InvalidArgumentException When unable to parse input (should never happen)
     */
    private function tokenize(string $input): array
    {
        $tokens = [];
        $length = \strlen($input);
        $cursor = 0;
        $token = null;
        while ($cursor < $length) {
            if ('\\' === $input[$cursor]) {
                $token .= $input[++$cursor] ?? '';
                ++$cursor;
                continue;
            }

            if (preg_match('/\s+/A', $input, $match, 0, $cursor)) {
                if (null !== $token) {
                    $tokens[] = $token;
                    $token = null;
                }
            } elseif (preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, 0, $cursor)) {
                $token .= $match[1].$match[2].stripcslashes(str_replace(['"\'', '\'"', '\'\'', '""'], '', substr($match[3], 1, -1)));
            } elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, 0, $cursor)) {
                $token .= stripcslashes(substr($match[0], 1, -1));
            } elseif (preg_match('/'.self::REGEX_UNQUOTED_STRING.'/A', $input, $match, 0, $cursor)) {
                $token .= $match[1];
            } else {
                // should never happen
                throw new InvalidArgumentException(\sprintf('Unable to parse input near "... %s ...".', substr($input, $cursor, 10)));
            }

            $cursor += \strlen($match[0]);
        }

        if (null !== $token) {
            $tokens[] = $token;
        }

        return $tokens;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\DependencyInjection;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Command\LazyCommand;
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\TypedReference;

/**
 * Registers console commands.
 *
 * @author Grégoire Pineau <lyrixx@lyrixx.info>
 */
class AddConsoleCommandPass implements CompilerPassInterface
{
    /**
     * @return void
     */
    public function process(ContainerBuilder $container)
    {
        $commandServices = $container->findTaggedServiceIds('console.command', true);
        $lazyCommandMap = [];
        $lazyCommandRefs = [];
        $serviceIds = [];

        foreach ($commandServices as $id => $tags) {
            $definition = $container->getDefinition($id);
            $definition->addTag('container.no_preload');
            $class = $container->getParameterBag()->resolveValue($definition->getClass());

            if (isset($tags[0]['command'])) {
                $aliases = $tags[0]['command'];
            } else {
                if (!$r = $container->getReflectionClass($class)) {
                    throw new InvalidArgumentException(\sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
                }
                if (!$r->isSubclassOf(Command::class)) {
                    throw new InvalidArgumentException(\sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, 'console.command', Command::class));
                }
                $aliases = str_replace('%', '%%', $class::getDefaultName() ?? '');
            }

            $aliases = explode('|', $aliases ?? '');
            $commandName = array_shift($aliases);

            if ($isHidden = '' === $commandName) {
                $commandName = array_shift($aliases);
            }

            if (null === $commandName) {
                if (!$definition->isPublic() || $definition->isPrivate() || $definition->hasTag('container.private')) {
                    $commandId = 'console.command.public_alias.'.$id;
                    $container->setAlias($commandId, $id)->setPublic(true);
                    $id = $commandId;
                }
                $serviceIds[] = $id;

                continue;
            }

            $description = $tags[0]['description'] ?? null;

            unset($tags[0]);
            $lazyCommandMap[$commandName] = $id;
            $lazyCommandRefs[$id] = new TypedReference($id, $class);

            foreach ($aliases as $alias) {
                $lazyCommandMap[$alias] = $id;
            }

            foreach ($tags as $tag) {
                if (isset($tag['command'])) {
                    $aliases[] = $tag['command'];
                    $lazyCommandMap[$tag['command']] = $id;
                }

                $description ??= $tag['description'] ?? null;
            }

            $definition->addMethodCall('setName', [$commandName]);

            if ($aliases) {
                $definition->addMethodCall('setAliases', [$aliases]);
            }

            if ($isHidden) {
                $definition->addMethodCall('setHidden', [true]);
            }

            if (!$description) {
                if (!$r = $container->getReflectionClass($class)) {
                    throw new InvalidArgumentException(\sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
                }
                if (!$r->isSubclassOf(Command::class)) {
                    throw new InvalidArgumentException(\sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, 'console.command', Command::class));
                }
                $description = str_replace('%', '%%', $class::getDefaultDescription() ?? '');
            }

            if ($description) {
                $definition->addMethodCall('setDescription', [$description]);

                $container->register('.'.$id.'.lazy', LazyCommand::class)
                    ->setArguments([$commandName, $aliases, $description, $isHidden, new ServiceClosureArgument($lazyCommandRefs[$id])]);

                $lazyCommandRefs[$id] = new Reference('.'.$id.'.lazy');
            }
        }

        $container
            ->register('console.command_loader', ContainerCommandLoader::class)
            ->setPublic(true)
            ->addTag('container.no_preload')
            ->setArguments([ServiceLocatorTagPass::register($container, $lazyCommandRefs), $lazyCommandMap]);

        $container->setParameter('console.command.ids', $serviceIds);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\CommandLoader;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\CommandNotFoundException;

/**
 * @author Robin Chalas <robin.chalas@gmail.com>
 */
interface CommandLoaderInterface
{
    /**
     * Loads a command.
     *
     * @throws CommandNotFoundException
     */
    public function get(string $name): Command;

    /**
     * Checks if a command exists.
     */
    public function has(string $name): bool;

    /**
     * @return string[]
     */
    public function getNames(): array;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\CommandLoader;

use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\CommandNotFoundException;

/**
 * Loads commands from a PSR-11 container.
 *
 * @author Robin Chalas <robin.chalas@gmail.com>
 */
class ContainerCommandLoader implements CommandLoaderInterface
{
    private ContainerInterface $container;
    private array $commandMap;

    /**
     * @param array $commandMap An array with command names as keys and service ids as values
     */
    public function __construct(ContainerInterface $container, array $commandMap)
    {
        $this->container = $container;
        $this->commandMap = $commandMap;
    }

    public function get(string $name): Command
    {
        if (!$this->has($name)) {
            throw new CommandNotFoundException(\sprintf('Command "%s" does not exist.', $name));
        }

        return $this->container->get($this->commandMap[$name]);
    }

    public function has(string $name): bool
    {
        return isset($this->commandMap[$name]) && $this->container->has($this->commandMap[$name]);
    }

    public function getNames(): array
    {
        return array_keys($this->commandMap);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\CommandLoader;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\CommandNotFoundException;

/**
 * A simple command loader using factories to instantiate commands lazily.
 *
 * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
 */
class FactoryCommandLoader implements CommandLoaderInterface
{
    private array $factories;

    /**
     * @param callable[] $factories Indexed by command names
     */
    public function __construct(array $factories)
    {
        $this->factories = $factories;
    }

    public function has(string $name): bool
    {
        return isset($this->factories[$name]);
    }

    public function get(string $name): Command
    {
        if (!isset($this->factories[$name])) {
            throw new CommandNotFoundException(\sprintf('Command "%s" does not exist.', $name));
        }

        $factory = $this->factories[$name];

        return $factory();
    }

    public function getNames(): array
    {
        return array_keys($this->factories);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
 *
 * @internal
 */
abstract class Descriptor implements DescriptorInterface
{
    protected OutputInterface $output;

    public function describe(OutputInterface $output, object $object, array $options = []): void
    {
        $this->output = $output;

        match (true) {
            $object instanceof InputArgument => $this->describeInputArgument($object, $options),
            $object instanceof InputOption => $this->describeInputOption($object, $options),
            $object instanceof InputDefinition => $this->describeInputDefinition($object, $options),
            $object instanceof Command => $this->describeCommand($object, $options),
            $object instanceof Application => $this->describeApplication($object, $options),
            default => throw new InvalidArgumentException(\sprintf('Object of type "%s" is not describable.', get_debug_type($object))),
        };
    }

    protected function write(string $content, bool $decorated = false): void
    {
        $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW);
    }

    /**
     * Describes an InputArgument instance.
     */
    abstract protected function describeInputArgument(InputArgument $argument, array $options = []): void;

    /**
     * Describes an InputOption instance.
     */
    abstract protected function describeInputOption(InputOption $option, array $options = []): void;

    /**
     * Describes an InputDefinition instance.
     */
    abstract protected function describeInputDefinition(InputDefinition $definition, array $options = []): void;

    /**
     * Describes a Command instance.
     */
    abstract protected function describeCommand(Command $command, array $options = []): void;

    /**
     * Describes an Application instance.
     */
    abstract protected function describeApplication(Application $application, array $options = []): void;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;

/**
 * JSON descriptor.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @internal
 */
class JsonDescriptor extends Descriptor
{
    protected function describeInputArgument(InputArgument $argument, array $options = []): void
    {
        $this->writeData($this->getInputArgumentData($argument), $options);
    }

    protected function describeInputOption(InputOption $option, array $options = []): void
    {
        $this->writeData($this->getInputOptionData($option), $options);
        if ($option->isNegatable()) {
            $this->writeData($this->getInputOptionData($option, true), $options);
        }
    }

    protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
    {
        $this->writeData($this->getInputDefinitionData($definition), $options);
    }

    protected function describeCommand(Command $command, array $options = []): void
    {
        $this->writeData($this->getCommandData($command, $options['short'] ?? false), $options);
    }

    protected function describeApplication(Application $application, array $options = []): void
    {
        $describedNamespace = $options['namespace'] ?? null;
        $description = new ApplicationDescription($application, $describedNamespace, true);
        $commands = [];

        foreach ($description->getCommands() as $command) {
            $commands[] = $this->getCommandData($command, $options['short'] ?? false);
        }

        $data = [];
        if ('UNKNOWN' !== $application->getName()) {
            $data['application']['name'] = $application->getName();
            if ('UNKNOWN' !== $application->getVersion()) {
                $data['application']['version'] = $application->getVersion();
            }
        }

        $data['commands'] = $commands;

        if ($describedNamespace) {
            $data['namespace'] = $describedNamespace;
        } else {
            $data['namespaces'] = array_values($description->getNamespaces());
        }

        $this->writeData($data, $options);
    }

    /**
     * Writes data as json.
     */
    private function writeData(array $data, array $options): void
    {
        $flags = $options['json_encoding'] ?? 0;

        $this->write(json_encode($data, $flags));
    }

    private function getInputArgumentData(InputArgument $argument): array
    {
        return [
            'name' => $argument->getName(),
            'is_required' => $argument->isRequired(),
            'is_array' => $argument->isArray(),
            'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $argument->getDescription()),
            'default' => \INF === $argument->getDefault() ? 'INF' : $argument->getDefault(),
        ];
    }

    private function getInputOptionData(InputOption $option, bool $negated = false): array
    {
        return $negated ? [
            'name' => '--no-'.$option->getName(),
            'shortcut' => '',
            'accept_value' => false,
            'is_value_required' => false,
            'is_multiple' => false,
            'description' => 'Negate the "--'.$option->getName().'" option',
            'default' => null === $option->getDefault() ? null : !$option->getDefault(),
        ] : [
            'name' => '--'.$option->getName(),
            'shortcut' => $option->getShortcut() ? '-'.str_replace('|', '|-', $option->getShortcut()) : '',
            'accept_value' => $option->acceptValue(),
            'is_value_required' => $option->isValueRequired(),
            'is_multiple' => $option->isArray(),
            'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $option->getDescription()),
            'default' => \INF === $option->getDefault() ? 'INF' : $option->getDefault(),
        ];
    }

    private function getInputDefinitionData(InputDefinition $definition): array
    {
        $inputArguments = [];
        foreach ($definition->getArguments() as $name => $argument) {
            $inputArguments[$name] = $this->getInputArgumentData($argument);
        }

        $inputOptions = [];
        foreach ($definition->getOptions() as $name => $option) {
            $inputOptions[$name] = $this->getInputOptionData($option);
            if ($option->isNegatable()) {
                $inputOptions['no-'.$name] = $this->getInputOptionData($option, true);
            }
        }

        return ['arguments' => $inputArguments, 'options' => $inputOptions];
    }

    private function getCommandData(Command $command, bool $short = false): array
    {
        $data = [
            'name' => $command->getName(),
            'description' => $command->getDescription(),
        ];

        if ($short) {
            $data += [
                'usage' => $command->getAliases(),
            ];
        } else {
            $command->mergeApplicationDefinition(false);

            $data += [
                'usage' => array_merge([$command->getSynopsis()], $command->getUsages(), $command->getAliases()),
                'help' => $command->getProcessedHelp(),
                'definition' => $this->getInputDefinitionData($command->getDefinition()),
            ];
        }

        $data['hidden'] = $command->isHidden();

        return $data;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Markdown descriptor.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @internal
 */
class MarkdownDescriptor extends Descriptor
{
    public function describe(OutputInterface $output, object $object, array $options = []): void
    {
        $decorated = $output->isDecorated();
        $output->setDecorated(false);

        parent::describe($output, $object, $options);

        $output->setDecorated($decorated);
    }

    protected function write(string $content, bool $decorated = true): void
    {
        parent::write($content, $decorated);
    }

    protected function describeInputArgument(InputArgument $argument, array $options = []): void
    {
        $this->write(
            '#### `'.($argument->getName() ?: '<none>')."`\n\n"
            .($argument->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $argument->getDescription())."\n\n" : '')
            .'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n"
            .'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n"
            .'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`'
        );
    }

    protected function describeInputOption(InputOption $option, array $options = []): void
    {
        $name = '--'.$option->getName();
        if ($option->isNegatable()) {
            $name .= '|--no-'.$option->getName();
        }
        if ($option->getShortcut()) {
            $name .= '|-'.str_replace('|', '|-', $option->getShortcut()).'';
        }

        $this->write(
            '#### `'.$name.'`'."\n\n"
            .($option->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $option->getDescription())."\n\n" : '')
            .'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n"
            .'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n"
            .'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n"
            .'* Is negatable: '.($option->isNegatable() ? 'yes' : 'no')."\n"
            .'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`'
        );
    }

    protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
    {
        if ($showArguments = \count($definition->getArguments()) > 0) {
            $this->write('### Arguments');
            foreach ($definition->getArguments() as $argument) {
                $this->write("\n\n");
                $this->describeInputArgument($argument);
            }
        }

        if (\count($definition->getOptions()) > 0) {
            if ($showArguments) {
                $this->write("\n\n");
            }

            $this->write('### Options');
            foreach ($definition->getOptions() as $option) {
                $this->write("\n\n");
                $this->describeInputOption($option);
            }
        }
    }

    protected function describeCommand(Command $command, array $options = []): void
    {
        if ($options['short'] ?? false) {
            $this->write(
                '`'.$command->getName()."`\n"
                .str_repeat('-', Helper::width($command->getName()) + 2)."\n\n"
                .($command->getDescription() ? $command->getDescription()."\n\n" : '')
                .'### Usage'."\n\n"
                .array_reduce($command->getAliases(), static fn ($carry, $usage) => $carry.'* `'.$usage.'`'."\n")
            );

            return;
        }

        $command->mergeApplicationDefinition(false);

        $this->write(
            '`'.$command->getName()."`\n"
            .str_repeat('-', Helper::width($command->getName()) + 2)."\n\n"
            .($command->getDescription() ? $command->getDescription()."\n\n" : '')
            .'### Usage'."\n\n"
            .array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), static fn ($carry, $usage) => $carry.'* `'.$usage.'`'."\n")
        );

        if ($help = $command->getProcessedHelp()) {
            $this->write("\n");
            $this->write($help);
        }

        $definition = $command->getDefinition();
        if ($definition->getOptions() || $definition->getArguments()) {
            $this->write("\n\n");
            $this->describeInputDefinition($definition);
        }
    }

    protected function describeApplication(Application $application, array $options = []): void
    {
        $describedNamespace = $options['namespace'] ?? null;
        $description = new ApplicationDescription($application, $describedNamespace);
        $title = $this->getApplicationTitle($application);

        $this->write($title."\n".str_repeat('=', Helper::width($title)));

        foreach ($description->getNamespaces() as $namespace) {
            if (ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
                $this->write("\n\n");
                $this->write('**'.$namespace['id'].':**');
            }

            $this->write("\n\n");
            $this->write(implode("\n", array_map(static fn ($commandName) => \sprintf('* [`%s`](#%s)', $commandName, str_replace(':', '', $description->getCommand($commandName)->getName())), $namespace['commands'])));
        }

        foreach ($description->getCommands() as $command) {
            $this->write("\n\n");
            $this->describeCommand($command, $options);
        }
    }

    private function getApplicationTitle(Application $application): string
    {
        if ('UNKNOWN' !== $application->getName()) {
            if ('UNKNOWN' !== $application->getVersion()) {
                return \sprintf('%s %s', $application->getName(), $application->getVersion());
            }

            return $application->getName();
        }

        return 'Console Tool';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;

/**
 * Text descriptor.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @internal
 */
class TextDescriptor extends Descriptor
{
    protected function describeInputArgument(InputArgument $argument, array $options = []): void
    {
        if (null !== $argument->getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) {
            $default = \sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($argument->getDefault()));
        } else {
            $default = '';
        }

        $totalWidth = $options['total_width'] ?? Helper::width($argument->getName());
        $spacingWidth = $totalWidth - \strlen($argument->getName());

        $this->writeText(\sprintf('  <info>%s</info>  %s%s%s',
            $argument->getName(),
            str_repeat(' ', $spacingWidth),
            // + 4 = 2 spaces before <info>, 2 spaces after </info>
            preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $argument->getDescription()),
            $default
        ), $options);
    }

    protected function describeInputOption(InputOption $option, array $options = []): void
    {
        if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) {
            $default = \sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($option->getDefault()));
        } else {
            $default = '';
        }

        $value = '';
        if ($option->acceptValue()) {
            $value = '='.strtoupper($option->getName());

            if ($option->isValueOptional()) {
                $value = '['.$value.']';
            }
        }

        $totalWidth = $options['total_width'] ?? $this->calculateTotalWidthForOptions([$option]);
        $synopsis = \sprintf('%s%s',
            $option->getShortcut() ? \sprintf('-%s, ', $option->getShortcut()) : '    ',
            \sprintf($option->isNegatable() ? '--%1$s|--no-%1$s' : '--%1$s%2$s', $option->getName(), $value)
        );

        $spacingWidth = $totalWidth - Helper::width($synopsis);

        $this->writeText(\sprintf('  <info>%s</info>  %s%s%s%s',
            $synopsis,
            str_repeat(' ', $spacingWidth),
            // + 4 = 2 spaces before <info>, 2 spaces after </info>
            preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $option->getDescription()),
            $default,
            $option->isArray() ? '<comment> (multiple values allowed)</comment>' : ''
        ), $options);
    }

    protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
    {
        $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions());
        foreach ($definition->getArguments() as $argument) {
            $totalWidth = max($totalWidth, Helper::width($argument->getName()));
        }

        if ($definition->getArguments()) {
            $this->writeText('<comment>Arguments:</comment>', $options);
            $this->writeText("\n");
            foreach ($definition->getArguments() as $argument) {
                $this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth]));
                $this->writeText("\n");
            }
        }

        if ($definition->getArguments() && $definition->getOptions()) {
            $this->writeText("\n");
        }

        if ($definition->getOptions()) {
            $laterOptions = [];

            $this->writeText('<comment>Options:</comment>', $options);
            foreach ($definition->getOptions() as $option) {
                if (\strlen($option->getShortcut() ?? '') > 1) {
                    $laterOptions[] = $option;
                    continue;
                }
                $this->writeText("\n");
                $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
            }
            foreach ($laterOptions as $option) {
                $this->writeText("\n");
                $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
            }
        }
    }

    protected function describeCommand(Command $command, array $options = []): void
    {
        $command->mergeApplicationDefinition(false);

        if ($description = $command->getDescription()) {
            $this->writeText('<comment>Description:</comment>', $options);
            $this->writeText("\n");
            $this->writeText('  '.$description);
            $this->writeText("\n\n");
        }

        $this->writeText('<comment>Usage:</comment>', $options);
        foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) {
            $this->writeText("\n");
            $this->writeText('  '.OutputFormatter::escape($usage), $options);
        }
        $this->writeText("\n");

        $definition = $command->getDefinition();
        if ($definition->getOptions() || $definition->getArguments()) {
            $this->writeText("\n");
            $this->describeInputDefinition($definition, $options);
            $this->writeText("\n");
        }

        $help = $command->getProcessedHelp();
        if ($help && $help !== $description) {
            $this->writeText("\n");
            $this->writeText('<comment>Help:</comment>', $options);
            $this->writeText("\n");
            $this->writeText('  '.str_replace("\n", "\n  ", $help), $options);
            $this->writeText("\n");
        }
    }

    protected function describeApplication(Application $application, array $options = []): void
    {
        $describedNamespace = $options['namespace'] ?? null;
        $description = new ApplicationDescription($application, $describedNamespace);

        if (isset($options['raw_text']) && $options['raw_text']) {
            $width = $this->getColumnWidth($description->getCommands());

            foreach ($description->getCommands() as $command) {
                $this->writeText(\sprintf("%-{$width}s %s", $command->getName(), $command->getDescription()), $options);
                $this->writeText("\n");
            }
        } else {
            if ('' != $help = $application->getHelp()) {
                $this->writeText("$help\n\n", $options);
            }

            $this->writeText("<comment>Usage:</comment>\n", $options);
            $this->writeText("  command [options] [arguments]\n\n", $options);

            $this->describeInputDefinition(new InputDefinition($application->getDefinition()->getOptions()), $options);

            $this->writeText("\n");
            $this->writeText("\n");

            $commands = $description->getCommands();
            $namespaces = $description->getNamespaces();
            if ($describedNamespace && $namespaces) {
                // make sure all alias commands are included when describing a specific namespace
                $describedNamespaceInfo = reset($namespaces);
                foreach ($describedNamespaceInfo['commands'] as $name) {
                    $commands[$name] = $description->getCommand($name);
                }
            }

            // calculate max. width based on available commands per namespace
            $width = $this->getColumnWidth(array_merge(...array_values(array_map(static fn ($namespace) => array_intersect($namespace['commands'], array_keys($commands)), array_values($namespaces)))));

            if ($describedNamespace) {
                $this->writeText(\sprintf('<comment>Available commands for the "%s" namespace:</comment>', $describedNamespace), $options);
            } else {
                $this->writeText('<comment>Available commands:</comment>', $options);
            }

            foreach ($namespaces as $namespace) {
                $namespace['commands'] = array_filter($namespace['commands'], static fn ($name) => isset($commands[$name]));

                if (!$namespace['commands']) {
                    continue;
                }

                if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
                    $this->writeText("\n");
                    $this->writeText(' <comment>'.$namespace['id'].'</comment>', $options);
                }

                foreach ($namespace['commands'] as $name) {
                    $this->writeText("\n");
                    $spacingWidth = $width - Helper::width($name);
                    $command = $commands[$name];
                    $commandAliases = $name === $command->getName() ? $this->getCommandAliasesText($command) : '';
                    $this->writeText(\sprintf('  <info>%s</info>%s%s', $name, str_repeat(' ', $spacingWidth), $commandAliases.$command->getDescription()), $options);
                }
            }

            $this->writeText("\n");
        }
    }

    private function writeText(string $content, array $options = []): void
    {
        $this->write(
            isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content,
            isset($options['raw_output']) ? !$options['raw_output'] : true
        );
    }

    /**
     * Formats command aliases to show them in the command description.
     */
    private function getCommandAliasesText(Command $command): string
    {
        $text = '';
        $aliases = $command->getAliases();

        if ($aliases) {
            $text = '['.implode('|', $aliases).'] ';
        }

        return $text;
    }

    /**
     * Formats input option/argument default value.
     */
    private function formatDefaultValue(mixed $default): string
    {
        if (\INF === $default) {
            return 'INF';
        }

        if (\is_string($default)) {
            $default = OutputFormatter::escape($default);
        } elseif (\is_array($default)) {
            foreach ($default as $key => $value) {
                if (\is_string($value)) {
                    $default[$key] = OutputFormatter::escape($value);
                }
            }
        }

        return str_replace('\\\\', '\\', json_encode($default, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE));
    }

    /**
     * @param array<Command|string> $commands
     */
    private function getColumnWidth(array $commands): int
    {
        $widths = [];

        foreach ($commands as $command) {
            if ($command instanceof Command) {
                $widths[] = Helper::width($command->getName());
                foreach ($command->getAliases() as $alias) {
                    $widths[] = Helper::width($alias);
                }
            } else {
                $widths[] = Helper::width($command);
            }
        }

        return $widths ? max($widths) + 2 : 0;
    }

    /**
     * @param InputOption[] $options
     */
    private function calculateTotalWidthForOptions(array $options): int
    {
        $totalWidth = 0;
        foreach ($options as $option) {
            // "-" + shortcut + ", --" + name
            $nameLength = 1 + max(Helper::width($option->getShortcut()), 1) + 4 + Helper::width($option->getName());
            if ($option->isNegatable()) {
                $nameLength += 6 + Helper::width($option->getName()); // |--no- + name
            } elseif ($option->acceptValue()) {
                $valueLength = 1 + Helper::width($option->getName()); // = + value
                $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ]

                $nameLength += $valueLength;
            }
            $totalWidth = max($totalWidth, $nameLength);
        }

        return $totalWidth;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Output\OutputInterface;

/**
 * Descriptor interface.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
interface DescriptorInterface
{
    /**
     * @return void
     */
    public function describe(OutputInterface $output, object $object, array $options = []);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\CommandNotFoundException;

/**
 * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
 *
 * @internal
 */
class ApplicationDescription
{
    public const GLOBAL_NAMESPACE = '_global';

    private Application $application;
    private ?string $namespace;
    private bool $showHidden;
    private array $namespaces;

    /**
     * @var array<string, Command>
     */
    private array $commands;

    /**
     * @var array<string, Command>
     */
    private array $aliases = [];

    public function __construct(Application $application, ?string $namespace = null, bool $showHidden = false)
    {
        $this->application = $application;
        $this->namespace = $namespace;
        $this->showHidden = $showHidden;
    }

    public function getNamespaces(): array
    {
        if (!isset($this->namespaces)) {
            $this->inspectApplication();
        }

        return $this->namespaces;
    }

    /**
     * @return Command[]
     */
    public function getCommands(): array
    {
        if (!isset($this->commands)) {
            $this->inspectApplication();
        }

        return $this->commands;
    }

    /**
     * @throws CommandNotFoundException
     */
    public function getCommand(string $name): Command
    {
        if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) {
            throw new CommandNotFoundException(\sprintf('Command "%s" does not exist.', $name));
        }

        return $this->commands[$name] ?? $this->aliases[$name];
    }

    private function inspectApplication(): void
    {
        $this->commands = [];
        $this->namespaces = [];

        $all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null);
        foreach ($this->sortCommands($all) as $namespace => $commands) {
            $names = [];

            /** @var Command $command */
            foreach ($commands as $name => $command) {
                if (!$command->getName() || (!$this->showHidden && $command->isHidden())) {
                    continue;
                }

                if ($command->getName() === $name) {
                    $this->commands[$name] = $command;
                } else {
                    $this->aliases[$name] = $command;
                }

                $names[] = $name;
            }

            $this->namespaces[$namespace] = ['id' => $namespace, 'commands' => $names];
        }
    }

    private function sortCommands(array $commands): array
    {
        $namespacedCommands = [];
        $globalCommands = [];
        $sortedCommands = [];
        foreach ($commands as $name => $command) {
            $key = $this->application->extractNamespace($name, 1);
            if (\in_array($key, ['', self::GLOBAL_NAMESPACE], true)) {
                $globalCommands[$name] = $command;
            } else {
                $namespacedCommands[$key][$name] = $command;
            }
        }

        if ($globalCommands) {
            ksort($globalCommands);
            $sortedCommands[self::GLOBAL_NAMESPACE] = $globalCommands;
        }

        if ($namespacedCommands) {
            ksort($namespacedCommands, \SORT_STRING);
            foreach ($namespacedCommands as $key => $commandsSet) {
                ksort($commandsSet);
                $sortedCommands[$key] = $commandsSet;
            }
        }

        return $sortedCommands;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\String\UnicodeString;

class ReStructuredTextDescriptor extends Descriptor
{
    // <h1>
    private string $partChar = '=';
    // <h2>
    private string $chapterChar = '-';
    // <h3>
    private string $sectionChar = '~';
    // <h4>
    private string $subsectionChar = '.';
    // <h5>
    private string $subsubsectionChar = '^';
    // <h6>
    private string $paragraphsChar = '"';

    private array $visibleNamespaces = [];

    public function describe(OutputInterface $output, object $object, array $options = []): void
    {
        $decorated = $output->isDecorated();
        $output->setDecorated(false);

        parent::describe($output, $object, $options);

        $output->setDecorated($decorated);
    }

    /**
     * Override parent method to set $decorated = true.
     */
    protected function write(string $content, bool $decorated = true): void
    {
        parent::write($content, $decorated);
    }

    protected function describeInputArgument(InputArgument $argument, array $options = []): void
    {
        $this->write(
            $argument->getName() ?: '<none>'."\n".str_repeat($this->paragraphsChar, Helper::width($argument->getName()))."\n\n"
                .($argument->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $argument->getDescription())."\n\n" : '')
                .'- **Is required**: '.($argument->isRequired() ? 'yes' : 'no')."\n"
                .'- **Is array**: '.($argument->isArray() ? 'yes' : 'no')."\n"
                .'- **Default**: ``'.str_replace("\n", '', var_export($argument->getDefault(), true)).'``'
        );
    }

    protected function describeInputOption(InputOption $option, array $options = []): void
    {
        $name = '\-\-'.$option->getName();
        if ($option->isNegatable()) {
            $name .= '|\-\-no-'.$option->getName();
        }
        if ($option->getShortcut()) {
            $name .= '|-'.str_replace('|', '|-', $option->getShortcut());
        }

        $optionDescription = $option->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n\n", $option->getDescription())."\n\n" : '';
        $optionDescription = (new UnicodeString($optionDescription))->ascii();
        $this->write(
            $name."\n".str_repeat($this->paragraphsChar, Helper::width($name))."\n\n"
            .$optionDescription
            .'- **Accept value**: '.($option->acceptValue() ? 'yes' : 'no')."\n"
            .'- **Is value required**: '.($option->isValueRequired() ? 'yes' : 'no')."\n"
            .'- **Is multiple**: '.($option->isArray() ? 'yes' : 'no')."\n"
            .'- **Is negatable**: '.($option->isNegatable() ? 'yes' : 'no')."\n"
            .'- **Default**: ``'.str_replace("\n", '', var_export($option->getDefault(), true)).'``'."\n"
        );
    }

    protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
    {
        if ($showArguments = ((bool) $definition->getArguments())) {
            $this->write("Arguments\n".str_repeat($this->subsubsectionChar, 9))."\n\n";
            foreach ($definition->getArguments() as $argument) {
                $this->write("\n\n");
                $this->describeInputArgument($argument);
            }
        }

        if ($nonDefaultOptions = $this->getNonDefaultOptions($definition)) {
            if ($showArguments) {
                $this->write("\n\n");
            }

            $this->write("Options\n".str_repeat($this->subsubsectionChar, 7)."\n\n");
            foreach ($nonDefaultOptions as $option) {
                $this->describeInputOption($option);
                $this->write("\n");
            }
        }
    }

    protected function describeCommand(Command $command, array $options = []): void
    {
        if ($options['short'] ?? false) {
            $this->write(
                '``'.$command->getName()."``\n"
                .str_repeat($this->subsectionChar, Helper::width($command->getName()))."\n\n"
                .($command->getDescription() ? $command->getDescription()."\n\n" : '')
                ."Usage\n".str_repeat($this->paragraphsChar, 5)."\n\n"
                .array_reduce($command->getAliases(), static fn ($carry, $usage) => $carry.'- ``'.$usage.'``'."\n")
            );

            return;
        }

        $command->mergeApplicationDefinition(false);

        foreach ($command->getAliases() as $alias) {
            $this->write('.. _'.$alias.":\n\n");
        }
        $this->write(
            $command->getName()."\n"
            .str_repeat($this->subsectionChar, Helper::width($command->getName()))."\n\n"
            .($command->getDescription() ? $command->getDescription()."\n\n" : '')
            ."Usage\n".str_repeat($this->subsubsectionChar, 5)."\n\n"
            .array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), static fn ($carry, $usage) => $carry.'- ``'.$usage.'``'."\n")
        );

        if ($help = $command->getProcessedHelp()) {
            $this->write("\n");
            $this->write($help);
        }

        $definition = $command->getDefinition();
        if ($definition->getOptions() || $definition->getArguments()) {
            $this->write("\n\n");
            $this->describeInputDefinition($definition);
        }
    }

    protected function describeApplication(Application $application, array $options = []): void
    {
        $description = new ApplicationDescription($application, $options['namespace'] ?? null);
        $title = $this->getApplicationTitle($application);

        $this->write($title."\n".str_repeat($this->partChar, Helper::width($title)));
        $this->createTableOfContents($description, $application);
        $this->describeCommands($application, $options);
    }

    private function getApplicationTitle(Application $application): string
    {
        if ('UNKNOWN' === $application->getName()) {
            return 'Console Tool';
        }
        if ('UNKNOWN' !== $application->getVersion()) {
            return \sprintf('%s %s', $application->getName(), $application->getVersion());
        }

        return $application->getName();
    }

    private function describeCommands($application, array $options): void
    {
        $title = 'Commands';
        $this->write("\n\n$title\n".str_repeat($this->chapterChar, Helper::width($title))."\n\n");
        foreach ($this->visibleNamespaces as $namespace) {
            if ('_global' === $namespace) {
                $commands = $application->all('');
                $this->write('Global'."\n".str_repeat($this->sectionChar, Helper::width('Global'))."\n\n");
            } else {
                $commands = $application->all($namespace);
                $this->write($namespace."\n".str_repeat($this->sectionChar, Helper::width($namespace))."\n\n");
            }

            foreach ($this->removeAliasesAndHiddenCommands($commands) as $command) {
                $this->describeCommand($command, $options);
                $this->write("\n\n");
            }
        }
    }

    private function createTableOfContents(ApplicationDescription $description, Application $application): void
    {
        $this->setVisibleNamespaces($description);
        $chapterTitle = 'Table of Contents';
        $this->write("\n\n$chapterTitle\n".str_repeat($this->chapterChar, Helper::width($chapterTitle))."\n\n");
        foreach ($this->visibleNamespaces as $namespace) {
            if ('_global' === $namespace) {
                $commands = $application->all('');
            } else {
                $commands = $application->all($namespace);
                $this->write("\n\n");
                $this->write($namespace."\n".str_repeat($this->sectionChar, Helper::width($namespace))."\n\n");
            }
            $commands = $this->removeAliasesAndHiddenCommands($commands);

            $this->write("\n\n");
            $this->write(implode("\n", array_map(static fn ($commandName) => \sprintf('- `%s`_', $commandName), array_keys($commands))));
        }
    }

    private function getNonDefaultOptions(InputDefinition $definition): array
    {
        $globalOptions = [
            'help',
            'quiet',
            'verbose',
            'version',
            'ansi',
            'no-interaction',
        ];
        $nonDefaultOptions = [];
        foreach ($definition->getOptions() as $option) {
            // Skip global options.
            if (!\in_array($option->getName(), $globalOptions)) {
                $nonDefaultOptions[] = $option;
            }
        }

        return $nonDefaultOptions;
    }

    private function setVisibleNamespaces(ApplicationDescription $description): void
    {
        $commands = $description->getCommands();
        foreach ($description->getNamespaces() as $namespace) {
            try {
                $namespaceCommands = $namespace['commands'];
                foreach ($namespaceCommands as $key => $commandName) {
                    if (!\array_key_exists($commandName, $commands)) {
                        // If the array key does not exist, then this is an alias.
                        unset($namespaceCommands[$key]);
                    } elseif ($commands[$commandName]->isHidden()) {
                        unset($namespaceCommands[$key]);
                    }
                }
                if (!$namespaceCommands) {
                    // If the namespace contained only aliases or hidden commands, skip the namespace.
                    continue;
                }
            } catch (\Exception) {
            }
            $this->visibleNamespaces[] = $namespace['id'];
        }
    }

    private function removeAliasesAndHiddenCommands(array $commands): array
    {
        foreach ($commands as $key => $command) {
            if ($command->isHidden() || \in_array($key, $command->getAliases(), true)) {
                unset($commands[$key]);
            }
        }
        unset($commands['completion']);

        return $commands;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;

/**
 * XML descriptor.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @internal
 */
class XmlDescriptor extends Descriptor
{
    public function getInputDefinitionDocument(InputDefinition $definition): \DOMDocument
    {
        $dom = new \DOMDocument('1.0', 'UTF-8');
        $dom->appendChild($definitionXML = $dom->createElement('definition'));

        $definitionXML->appendChild($argumentsXML = $dom->createElement('arguments'));
        foreach ($definition->getArguments() as $argument) {
            $this->appendDocument($argumentsXML, $this->getInputArgumentDocument($argument));
        }

        $definitionXML->appendChild($optionsXML = $dom->createElement('options'));
        foreach ($definition->getOptions() as $option) {
            $this->appendDocument($optionsXML, $this->getInputOptionDocument($option));
        }

        return $dom;
    }

    public function getCommandDocument(Command $command, bool $short = false): \DOMDocument
    {
        $dom = new \DOMDocument('1.0', 'UTF-8');
        $dom->appendChild($commandXML = $dom->createElement('command'));

        $commandXML->setAttribute('id', $command->getName());
        $commandXML->setAttribute('name', $command->getName());
        $commandXML->setAttribute('hidden', $command->isHidden() ? 1 : 0);

        $commandXML->appendChild($usagesXML = $dom->createElement('usages'));

        $commandXML->appendChild($descriptionXML = $dom->createElement('description'));
        $descriptionXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getDescription())));

        if ($short) {
            foreach ($command->getAliases() as $usage) {
                $usagesXML->appendChild($dom->createElement('usage', $usage));
            }
        } else {
            $command->mergeApplicationDefinition(false);

            foreach (array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()) as $usage) {
                $usagesXML->appendChild($dom->createElement('usage', $usage));
            }

            $commandXML->appendChild($helpXML = $dom->createElement('help'));
            $helpXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getProcessedHelp())));

            $definitionXML = $this->getInputDefinitionDocument($command->getDefinition());
            $this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0));
        }

        return $dom;
    }

    public function getApplicationDocument(Application $application, ?string $namespace = null, bool $short = false): \DOMDocument
    {
        $dom = new \DOMDocument('1.0', 'UTF-8');
        $dom->appendChild($rootXml = $dom->createElement('symfony'));

        if ('UNKNOWN' !== $application->getName()) {
            $rootXml->setAttribute('name', $application->getName());
            if ('UNKNOWN' !== $application->getVersion()) {
                $rootXml->setAttribute('version', $application->getVersion());
            }
        }

        $rootXml->appendChild($commandsXML = $dom->createElement('commands'));

        $description = new ApplicationDescription($application, $namespace, true);

        if ($namespace) {
            $commandsXML->setAttribute('namespace', $namespace);
        }

        foreach ($description->getCommands() as $command) {
            $this->appendDocument($commandsXML, $this->getCommandDocument($command, $short));
        }

        if (!$namespace) {
            $rootXml->appendChild($namespacesXML = $dom->createElement('namespaces'));

            foreach ($description->getNamespaces() as $namespaceDescription) {
                $namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace'));
                $namespaceArrayXML->setAttribute('id', $namespaceDescription['id']);

                foreach ($namespaceDescription['commands'] as $name) {
                    $namespaceArrayXML->appendChild($commandXML = $dom->createElement('command'));
                    $commandXML->appendChild($dom->createTextNode($name));
                }
            }
        }

        return $dom;
    }

    protected function describeInputArgument(InputArgument $argument, array $options = []): void
    {
        $this->writeDocument($this->getInputArgumentDocument($argument));
    }

    protected function describeInputOption(InputOption $option, array $options = []): void
    {
        $this->writeDocument($this->getInputOptionDocument($option));
    }

    protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
    {
        $this->writeDocument($this->getInputDefinitionDocument($definition));
    }

    protected function describeCommand(Command $command, array $options = []): void
    {
        $this->writeDocument($this->getCommandDocument($command, $options['short'] ?? false));
    }

    protected function describeApplication(Application $application, array $options = []): void
    {
        $this->writeDocument($this->getApplicationDocument($application, $options['namespace'] ?? null, $options['short'] ?? false));
    }

    /**
     * Appends document children to parent node.
     */
    private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent): void
    {
        foreach ($importedParent->childNodes as $childNode) {
            $parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true));
        }
    }

    /**
     * Writes DOM document.
     */
    private function writeDocument(\DOMDocument $dom): void
    {
        $dom->formatOutput = true;
        $this->write($dom->saveXML());
    }

    private function getInputArgumentDocument(InputArgument $argument): \DOMDocument
    {
        $dom = new \DOMDocument('1.0', 'UTF-8');

        $dom->appendChild($objectXML = $dom->createElement('argument'));
        $objectXML->setAttribute('name', $argument->getName());
        $objectXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0);
        $objectXML->setAttribute('is_array', $argument->isArray() ? 1 : 0);
        $objectXML->appendChild($descriptionXML = $dom->createElement('description'));
        $descriptionXML->appendChild($dom->createTextNode($argument->getDescription()));

        $objectXML->appendChild($defaultsXML = $dom->createElement('defaults'));
        $defaults = \is_array($argument->getDefault()) ? $argument->getDefault() : (\is_bool($argument->getDefault()) ? [var_export($argument->getDefault(), true)] : ($argument->getDefault() ? [$argument->getDefault()] : []));
        foreach ($defaults as $default) {
            $defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
            $defaultXML->appendChild($dom->createTextNode($default));
        }

        return $dom;
    }

    private function getInputOptionDocument(InputOption $option): \DOMDocument
    {
        $dom = new \DOMDocument('1.0', 'UTF-8');

        $dom->appendChild($objectXML = $dom->createElement('option'));
        $objectXML->setAttribute('name', '--'.$option->getName());
        $pos = strpos($option->getShortcut() ?? '', '|');
        if (false !== $pos) {
            $objectXML->setAttribute('shortcut', '-'.substr($option->getShortcut(), 0, $pos));
            $objectXML->setAttribute('shortcuts', '-'.str_replace('|', '|-', $option->getShortcut()));
        } else {
            $objectXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : '');
        }
        $objectXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0);
        $objectXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0);
        $objectXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0);
        $objectXML->appendChild($descriptionXML = $dom->createElement('description'));
        $descriptionXML->appendChild($dom->createTextNode($option->getDescription()));

        if ($option->acceptValue()) {
            $defaults = \is_array($option->getDefault()) ? $option->getDefault() : (\is_bool($option->getDefault()) ? [var_export($option->getDefault(), true)] : ($option->getDefault() ? [$option->getDefault()] : []));
            $objectXML->appendChild($defaultsXML = $dom->createElement('defaults'));

            if (!empty($defaults)) {
                foreach ($defaults as $default) {
                    $defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
                    $defaultXML->appendChild($dom->createTextNode($default));
                }
            }
        }

        if ($option->isNegatable()) {
            $dom->appendChild($objectXML = $dom->createElement('option'));
            $objectXML->setAttribute('name', '--no-'.$option->getName());
            $objectXML->setAttribute('shortcut', '');
            $objectXML->setAttribute('accept_value', 0);
            $objectXML->setAttribute('is_value_required', 0);
            $objectXML->setAttribute('is_multiple', 0);
            $objectXML->appendChild($descriptionXML = $dom->createElement('description'));
            $descriptionXML->appendChild($dom->createTextNode('Negate the "--'.$option->getName().'" option'));
        }

        return $dom;
    }
}
<?php

// autoload.php @generated by Composer

if (PHP_VERSION_ID < 50600) {
    if (!headers_sent()) {
        header('HTTP/1.1 500 Internal Server Error');
    }
    $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
    if (!ini_get('display_errors')) {
        if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
            fwrite(STDERR, $err);
        } elseif (!headers_sent()) {
            echo $err;
        }
    }
    throw new RuntimeException($err);
}

require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInit020913853b4b516248bbc0df3c21c772::getLoader();
MIT License

Copyright (c) 2016

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.
<?php

declare(strict_types=1);

namespace JsonSchema;

use JsonSchema\Constraints\BaseConstraint;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Exception\UnresolvableJsonPointerException;
use JsonSchema\Uri\UriResolver;
use JsonSchema\Uri\UriRetriever;

class SchemaStorage implements SchemaStorageInterface
{
    public const INTERNAL_PROVIDED_SCHEMA_URI = 'internal://provided-schema/';

    protected $uriRetriever;
    protected $uriResolver;
    protected $schemas = [];

    public function __construct(
        ?UriRetrieverInterface $uriRetriever = null,
        ?UriResolverInterface $uriResolver = null
    ) {
        $this->uriRetriever = $uriRetriever ?: new UriRetriever();
        $this->uriResolver = $uriResolver ?: new UriResolver();
    }

    /**
     * @return UriRetrieverInterface
     */
    public function getUriRetriever()
    {
        return $this->uriRetriever;
    }

    /**
     * @return UriResolverInterface
     */
    public function getUriResolver()
    {
        return $this->uriResolver;
    }

    /**
     * {@inheritdoc}
     */
    public function addSchema(string $id, $schema = null): void
    {
        if (is_null($schema) && $id !== self::INTERNAL_PROVIDED_SCHEMA_URI) {
            // if the schema was user-provided to Validator and is still null, then assume this is
            // what the user intended, as there's no way for us to retrieve anything else. User-supplied
            // schemas do not have an associated URI when passed via Validator::validate().
            $schema = $this->uriRetriever->retrieve($id);
        }

        // cast array schemas to object
        if (is_array($schema)) {
            $schema = BaseConstraint::arrayToObjectRecursive($schema);
        }

        // workaround for bug in draft-03 & draft-04 meta-schemas (id & $ref defined with incorrect format)
        // see https://github.com/json-schema-org/JSON-Schema-Test-Suite/issues/177#issuecomment-293051367
        if (is_object($schema) && property_exists($schema, 'id')) {
            if ($schema->id === DraftIdentifiers::DRAFT_4) {
                $schema->properties->id->format = 'uri-reference';
            } elseif ($schema->id === DraftIdentifiers::DRAFT_3) {
                $schema->properties->id->format = 'uri-reference';
                $schema->properties->{'$ref'}->format = 'uri-reference';
            }
        }

        $this->scanForSubschemas($schema, $id);

        // resolve references
        $this->expandRefs($schema, $id);

        $this->schemas[$id] = $schema;
    }

    /**
     * Recursively resolve all references against the provided base
     *
     * @param mixed        $schema
     * @param list<string> $propertyStack
     */
    private function expandRefs(&$schema, ?string $parentId = null, array $propertyStack = []): void
    {
        if (!is_object($schema)) {
            if (is_array($schema)) {
                foreach ($schema as &$member) {
                    $this->expandRefs($member, $parentId);
                }
            }

            return;
        }

        if (property_exists($schema, '$ref') && is_string($schema->{'$ref'})) {
            $refPointer = new JsonPointer($this->uriResolver->resolve($schema->{'$ref'}, $parentId));
            $schema->{'$ref'} = (string) $refPointer;
        }

        $parentProperty = array_slice($propertyStack, -1)[0] ?? '';
        foreach ($schema as $propertyName => &$member) {
            if ($parentProperty !== 'properties' && in_array($propertyName, ['enum', 'const'])) {
                // Enum and const don't allow $ref as a keyword, see https://github.com/json-schema-org/JSON-Schema-Test-Suite/pull/445
                continue;
            }

            $schemaId = $this->findSchemaIdInObject($schema);
            $childId = $parentId;
            if (is_string($schemaId) && $childId !== $schemaId) {
                $childId = $this->uriResolver->resolve($schemaId, $childId);
            }

            $clonedPropertyStack = $propertyStack;
            $clonedPropertyStack[] = $propertyName;
            $this->expandRefs($member, $childId, $clonedPropertyStack);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getSchema(string $id)
    {
        if (!array_key_exists($id, $this->schemas)) {
            $this->addSchema($id);
        }

        return $this->schemas[$id];
    }

    /**
     * {@inheritdoc}
     */
    public function resolveRef(string $ref, $resolveStack = [])
    {
        $jsonPointer = new JsonPointer($ref);

        // resolve filename for pointer
        $fileName = $jsonPointer->getFilename();
        if (!strlen($fileName)) {
            throw new UnresolvableJsonPointerException(sprintf(
                "Could not resolve fragment '%s': no file is defined",
                $jsonPointer->getPropertyPathAsString()
            ));
        }

        // get & process the schema
        $refSchema = $this->getSchema($fileName);
        foreach ($jsonPointer->getPropertyPaths() as $path) {
            $path = urldecode($path);
            if (is_object($refSchema) && property_exists($refSchema, $path)) {
                $refSchema = $this->resolveRefSchema($refSchema->{$path}, $resolveStack);
            } elseif (is_array($refSchema) && array_key_exists($path, $refSchema)) {
                $refSchema = $this->resolveRefSchema($refSchema[$path], $resolveStack);
            } else {
                throw new UnresolvableJsonPointerException(sprintf(
                    'File: %s is found, but could not resolve fragment: %s',
                    $jsonPointer->getFilename(),
                    $jsonPointer->getPropertyPathAsString()
                ));
            }
        }

        return $refSchema;
    }

    /**
     * {@inheritdoc}
     */
    public function resolveRefSchema($refSchema, $resolveStack = [])
    {
        if (is_object($refSchema) && property_exists($refSchema, '$ref') && is_string($refSchema->{'$ref'})) {
            if (in_array($refSchema, $resolveStack, true)) {
                throw new UnresolvableJsonPointerException(sprintf(
                    'Dereferencing a pointer to %s results in an infinite loop',
                    $refSchema->{'$ref'}
                ));
            }
            $resolveStack[] = $refSchema;

            return $this->resolveRef($refSchema->{'$ref'}, $resolveStack);
        }

        if (is_object($refSchema) && array_keys(get_object_vars($refSchema)) === ['']) {
            $refSchema = get_object_vars($refSchema)[''];
        }

        return $refSchema;
    }

    /**
     * @param mixed $schema
     */
    private function scanForSubschemas($schema, string $parentId): void
    {
        if (!$schema instanceof \stdClass  && !is_array($schema)) {
            return;
        }

        foreach ($schema as $propertyName => $potentialSubSchema) {
            if (!is_object($potentialSubSchema)) {
                if (is_array($potentialSubSchema)) {
                    foreach ($potentialSubSchema as $potentialSubSchemaItem) {
                        $this->scanForSubschemas($potentialSubSchemaItem, $parentId);
                    }
                }
                continue;
            }

            $potentialSubSchemaId = $this->findSchemaIdInObject($potentialSubSchema);
            if (is_string($potentialSubSchemaId) && property_exists($potentialSubSchema, 'type')) {
                // Enum and const don't allow id as a keyword, see https://github.com/json-schema-org/JSON-Schema-Test-Suite/pull/471
                if (in_array($propertyName, ['enum', 'const'])) {
                    continue;
                }

                // $id in unknow keywords is not valid
                if (in_array($propertyName, [])) {
                    continue;
                }

                // Found sub schema
                $this->addSchema($this->uriResolver->resolve($potentialSubSchemaId, $parentId), $potentialSubSchema);
            }

            $this->scanForSubschemas($potentialSubSchema, $parentId);
        }
    }

    private function findSchemaIdInObject(object $schema): ?string
    {
        if (property_exists($schema, 'id') && is_string($schema->id)) {
            return $schema->id;
        }
        if (property_exists($schema, '$id') && is_string($schema->{'$id'})) {
            return $schema->{'$id'};
        }

        return null;
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema;

/**
 * @method static DraftIdentifiers DRAFT_3()
 * @method static DraftIdentifiers DRAFT_4()
 * @method static DraftIdentifiers DRAFT_6()
 * @method static DraftIdentifiers DRAFT_7()
 * @method static DraftIdentifiers DRAFT_2019_09()
 * @method static DraftIdentifiers DRAFT_2020_12()
 */
class DraftIdentifiers extends Enum
{
    public const DRAFT_3 = 'http://json-schema.org/draft-03/schema#';
    public const DRAFT_4 = 'http://json-schema.org/draft-04/schema#';
    public const DRAFT_6 = 'http://json-schema.org/draft-06/schema#';
    public const DRAFT_7 = 'http://json-schema.org/draft-07/schema#';
    public const DRAFT_2019_09 = 'https://json-schema.org/draft/2019-09/schema';
    public const DRAFT_2020_12 = 'https://json-schema.org/draft/2020-12/schema';

    /** @var array<DraftIdentifiers::DRAFT_*, string> */
    private const MAPPING = [
        self::DRAFT_3 => 'draft03',
        self::DRAFT_4 => 'draft04',
        self::DRAFT_6 => 'draft06',
        self::DRAFT_7 => 'draft07',
        self::DRAFT_2019_09 => 'draft2019-09',
        self::DRAFT_2020_12 => 'draft2020-12',
    ];

    private const FALLBACK_MAPPING = [
        'draft3' => self::DRAFT_3,
        'draft4' => self::DRAFT_4,
        'draft6' => self::DRAFT_6,
        'draft7' => self::DRAFT_7,
    ];

    public function toConstraintName(): string
    {
        return self::MAPPING[$this->getValue()];
    }

    public static function fromConstraintName(string $name): DraftIdentifiers
    {
        $reverseMap = array_flip(self::MAPPING);
        if (!array_key_exists($name, $reverseMap)) {
            if (array_key_exists($name, self::FALLBACK_MAPPING)) {
                return DraftIdentifiers::byValue(self::FALLBACK_MAPPING[$name]);
            }
            throw new \InvalidArgumentException("$name is not a valid constraint name.");
        }

        return DraftIdentifiers::byValue($reverseMap[$name]);
    }

    public function withoutFragment(): string
    {
        return rtrim($this->getValue(), '#');
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema;

use JsonSchema\Constraints\BaseConstraint;
use JsonSchema\Constraints\Constraint;
use JsonSchema\Constraints\TypeCheck\LooseTypeCheck;

/**
 * A JsonSchema Constraint
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 *
 * @see    README.md
 */
class Validator extends BaseConstraint
{
    public const SCHEMA_MEDIA_TYPE = 'application/schema+json';

    public const ERROR_NONE                    = 0;
    public const ERROR_ALL                     = -1;
    public const ERROR_DOCUMENT_VALIDATION     = 1;
    public const ERROR_SCHEMA_VALIDATION       = 2;

    /**
     * Validates the given data against the schema and returns an object containing the results
     * Both the php object and the schema are supposed to be a result of a json_decode call.
     * The validation works as defined by the schema proposal in http://json-schema.org.
     *
     * Note that the first argument is passed by reference, so you must pass in a variable.
     *
     * @param mixed $value
     * @param mixed $schema
     *
     * @phpstan-param int-mask-of<Constraint::CHECK_MODE_*> $checkMode
     * @phpstan-return int-mask-of<Validator::ERROR_*>
     */
    public function validate(&$value, $schema = null, ?int $checkMode = null): int
    {
        // reset errors prior to validation
        $this->reset();

        // set checkMode
        $initialCheckMode = $this->factory->getConfig();
        if ($checkMode !== null) {
            $this->factory->setConfig($checkMode);
        }

        // add provided schema to SchemaStorage with internal URI to allow internal $ref resolution
        $schemaURI = SchemaStorage::INTERNAL_PROVIDED_SCHEMA_URI;
        if (LooseTypeCheck::propertyExists($schema, 'id')) {
            $schemaURI = LooseTypeCheck::propertyGet($schema, 'id');
        }
        if (LooseTypeCheck::propertyExists($schema, '$id')) {
            $schemaURI = LooseTypeCheck::propertyGet($schema, '$id');
        }
        $this->factory->getSchemaStorage()->addSchema($schemaURI, $schema);

        $validator = $this->factory->createInstanceFor('schema');
        $schema = $this->factory->getSchemaStorage()->getSchema($schemaURI);

        // Boolean schema requires no further validation
        if (is_bool($schema)) {
            if ($schema === false) {
                $this->addError(ConstraintError::FALSE());
            }

            return $this->getErrorMask();
        }

        if ($this->factory->getConfig(Constraint::CHECK_MODE_STRICT)) {
            $dialect = $this->factory->getDefaultDialect();
            if (property_exists($schema, '$schema')) {
                $dialect = $schema->{'$schema'};
            }

            $validator = $this->factory->createInstanceFor(
                DraftIdentifiers::byValue($dialect)->toConstraintName()
            );
        }

        $validator->check($value, $schema);

        $this->factory->setConfig($initialCheckMode);

        $this->addErrors(array_unique($validator->getErrors(), SORT_REGULAR));

        return $validator->getErrorMask();
    }

    /**
     * Alias to validate(), to maintain backwards-compatibility with the previous API
     *
     * @deprecated since 6.0.0, use Validator::validate() instead, to be removed in 7.0
     *
     * @param mixed $value
     * @param mixed $schema
     *
     * @phpstan-return int-mask-of<Validator::ERROR_*>
     */
    public function check($value, $schema): int
    {
        return $this->validate($value, $schema);
    }

    /**
     * Alias to validate(), to maintain backwards-compatibility with the previous API
     *
     * @deprecated since 6.0.0, use Validator::validate() instead, to be removed in 7.0
     *
     * @param mixed $value
     * @param mixed $schema
     *
     * @phpstan-return int-mask-of<Validator::ERROR_*>
     */
    public function coerce(&$value, $schema): int
    {
        return $this->validate($value, $schema, Constraint::CHECK_MODE_COERCE_TYPES);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints;

use JsonSchema\Entity\JsonPointer;

abstract class Constraint extends BaseConstraint implements ConstraintInterface
{
    /** @var string */
    protected $inlineSchemaProperty = '$schema';

    /** @deprecated CHECK_MODE_NONE is unused and will be removed in the next major release (7.0.0) */
    public const CHECK_MODE_NONE =             0x00000000;
    public const CHECK_MODE_NORMAL =           0x00000001;
    public const CHECK_MODE_TYPE_CAST =        0x00000002;
    public const CHECK_MODE_COERCE_TYPES =     0x00000004;
    public const CHECK_MODE_APPLY_DEFAULTS =   0x00000008;
    public const CHECK_MODE_EXCEPTIONS =       0x00000010;
    public const CHECK_MODE_DISABLE_FORMAT =   0x00000020;
    public const CHECK_MODE_EARLY_COERCE =     0x00000040;
    public const CHECK_MODE_ONLY_REQUIRED_DEFAULTS   = 0x00000080;
    public const CHECK_MODE_VALIDATE_SCHEMA =  0x00000100;
    public const CHECK_MODE_STRICT =  0x00000200;

    /**
     * Bubble down the path
     *
     * @param JsonPointer|null $path Current path
     * @param mixed            $i    What to append to the path
     */
    protected function incrementPath(?JsonPointer $path, $i): JsonPointer
    {
        $path = $path ?? new JsonPointer('');

        if ($i === null || $i === '') {
            return $path;
        }

        return $path->withPropertyPaths(
            array_merge(
                $path->getPropertyPaths(),
                [$i]
            )
        );
    }

    /**
     * Validates an array
     *
     * @param mixed $value
     * @param mixed $schema
     * @param mixed $i
     */
    protected function checkArray(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        $validator = $this->factory->createInstanceFor('collection');
        $validator->check($value, $schema, $path, $i);

        $this->addErrors($validator->getErrors());
    }

    /**
     * Validates an object
     *
     * @param mixed         $value
     * @param mixed         $schema
     * @param mixed         $properties
     * @param mixed         $additionalProperties
     * @param mixed         $patternProperties
     * @param array<string> $appliedDefaults
     */
    protected function checkObject(
        &$value,
        $schema = null,
        ?JsonPointer $path = null,
        $properties = null,
        $additionalProperties = null,
        $patternProperties = null,
        array $appliedDefaults = []
    ): void {
        /** @var ObjectConstraint $validator */
        $validator = $this->factory->createInstanceFor('object');
        $validator->check($value, $schema, $path, $properties, $additionalProperties, $patternProperties, $appliedDefaults);

        $this->addErrors($validator->getErrors());
    }

    /**
     * Validates the type of the value
     *
     * @param mixed $value
     * @param mixed $schema
     * @param mixed $i
     */
    protected function checkType(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        $validator = $this->factory->createInstanceFor('type');
        $validator->check($value, $schema, $path, $i);

        $this->addErrors($validator->getErrors());
    }

    /**
     * Checks a undefined element
     *
     * @param mixed $value
     * @param mixed $schema
     * @param mixed $i
     */
    protected function checkUndefined(&$value, $schema = null, ?JsonPointer $path = null, $i = null, bool $fromDefault = false): void
    {
        /** @var UndefinedConstraint $validator */
        $validator = $this->factory->createInstanceFor('undefined');

        $validator->check($value, $this->factory->getSchemaStorage()->resolveRefSchema($schema), $path, $i, $fromDefault);

        $this->addErrors($validator->getErrors());
    }

    /**
     * Checks a string element
     *
     * @param mixed $value
     * @param mixed $schema
     * @param mixed $i
     */
    protected function checkString($value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        $validator = $this->factory->createInstanceFor('string');
        $validator->check($value, $schema, $path, $i);

        $this->addErrors($validator->getErrors());
    }

    /**
     * Checks a number element
     *
     * @param mixed $value
     * @param mixed $schema
     * @param mixed $i
     */
    protected function checkNumber($value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        $validator = $this->factory->createInstanceFor('number');
        $validator->check($value, $schema, $path, $i);

        $this->addErrors($validator->getErrors());
    }

    /**
     * Checks a enum element
     *
     * @param mixed $value
     * @param mixed $schema
     * @param mixed $i
     */
    protected function checkEnum($value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        $validator = $this->factory->createInstanceFor('enum');
        $validator->check($value, $schema, $path, $i);

        $this->addErrors($validator->getErrors());
    }

    /**
     * Checks a const element
     *
     * @param mixed $value
     * @param mixed $schema
     * @param mixed $i
     */
    protected function checkConst($value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        $validator = $this->factory->createInstanceFor('const');
        $validator->check($value, $schema, $path, $i);

        $this->addErrors($validator->getErrors());
    }

    /**
     * Checks format of an element
     *
     * @param mixed $value
     * @param mixed $schema
     * @param mixed $i
     */
    protected function checkFormat($value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        $validator = $this->factory->createInstanceFor('format');
        $validator->check($value, $schema, $path, $i);

        $this->addErrors($validator->getErrors());
    }

    /**
     * Get the type check based on the set check mode.
     */
    protected function getTypeCheck(): TypeCheck\TypeCheckInterface
    {
        return $this->factory->getTypeCheck();
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints;

use JsonSchema\ConstraintError;
use JsonSchema\Entity\JsonPointer;

class ObjectConstraint extends Constraint
{
    /**
     * @var list<string> List of properties to which a default value has been applied
     */
    protected $appliedDefaults = [];

    /**
     * {@inheritdoc}
     *
     * @param list<string> $appliedDefaults
     */
    public function check(
        &$element,
        $schema = null,
        ?JsonPointer $path = null,
        $properties = null,
        $additionalProp = null,
        $patternProperties = null,
        $appliedDefaults = []
    ): void {
        if ($element instanceof UndefinedConstraint) {
            return;
        }

        $this->appliedDefaults = $appliedDefaults;

        $matches = [];
        if ($patternProperties) {
            // validate the element pattern properties
            $matches = $this->validatePatternProperties($element, $path, $patternProperties);
        }

        if ($properties) {
            // validate the element properties
            $this->validateProperties($element, $properties, $path);
        }

        // validate additional element properties & constraints
        $this->validateElement($element, $matches, $schema, $path, $properties, $additionalProp);
    }

    public function validatePatternProperties($element, ?JsonPointer $path, $patternProperties)
    {
        $matches = [];
        foreach ($patternProperties as $pregex => $schema) {
            $fullRegex = self::jsonPatternToPhpRegex($pregex);

            // Validate the pattern before using it to test for matches
            if (@preg_match($fullRegex, '') === false) {
                $this->addError(ConstraintError::PREGEX_INVALID(), $path, ['pregex' => $pregex]);
                continue;
            }
            foreach ($element as $i => $value) {
                if (preg_match($fullRegex, (string) $i)) {
                    $matches[] = $i;
                    $this->checkUndefined($value, $schema ?: new \stdClass(), $path, $i, in_array($i, $this->appliedDefaults));
                }
            }
        }

        return $matches;
    }

    /**
     * Validates the element properties
     *
     * @param \StdClass        $element        Element to validate
     * @param array            $matches        Matches from patternProperties (if any)
     * @param \StdClass        $schema         ObjectConstraint definition
     * @param JsonPointer|null $path           Current test path
     * @param \StdClass        $properties     Properties
     * @param mixed            $additionalProp Additional properties
     */
    public function validateElement($element, $matches, $schema = null, ?JsonPointer $path = null,
        $properties = null, $additionalProp = null)
    {
        $this->validateMinMaxConstraint($element, $schema, $path);

        foreach ($element as $i => $value) {
            $definition = $this->getProperty($properties, $i);

            // no additional properties allowed
            if (!in_array($i, $matches) && $additionalProp === false && $this->inlineSchemaProperty !== $i && !$definition) {
                $this->addError(ConstraintError::ADDITIONAL_PROPERTIES(), $path, ['property' => $i]);
            }

            // additional properties defined
            if (!in_array($i, $matches) && $additionalProp && !$definition) {
                if ($additionalProp === true) {
                    $this->checkUndefined($value, null, $path, $i, in_array($i, $this->appliedDefaults));
                } else {
                    $this->checkUndefined($value, $additionalProp, $path, $i, in_array($i, $this->appliedDefaults));
                }
            }

            // property requires presence of another
            $require = $this->getProperty($definition, 'requires');
            if ($require && !$this->getProperty($element, $require)) {
                $this->addError(ConstraintError::REQUIRES(), $path, [
                    'property' => $i,
                    'requiredProperty' => $require
                ]);
            }

            $property = $this->getProperty($element, $i, $this->factory->createInstanceFor('undefined'));
            if (is_object($property)) {
                $this->validateMinMaxConstraint(!($property instanceof UndefinedConstraint) ? $property : $element, $definition, $path);
            }
        }
    }

    /**
     * Validates the definition properties
     *
     * @param \stdClass        $element    Element to validate
     * @param \stdClass        $properties Property definitions
     * @param JsonPointer|null $path       Path?
     */
    public function validateProperties(&$element, $properties = null, ?JsonPointer $path = null)
    {
        $undefinedConstraint = $this->factory->createInstanceFor('undefined');

        foreach ($properties as $i => $value) {
            $property = &$this->getProperty($element, $i, $undefinedConstraint);
            $definition = $this->getProperty($properties, $i);

            if (is_object($definition)) {
                // Undefined constraint will check for is_object() and quit if is not - so why pass it?
                $this->checkUndefined($property, $definition, $path, $i, in_array($i, $this->appliedDefaults));
            }
        }
    }

    /**
     * retrieves a property from an object or array
     *
     * @param mixed  $element  Element to validate
     * @param string $property Property to retrieve
     * @param mixed  $fallback Default value if property is not found
     *
     * @return mixed
     */
    protected function &getProperty(&$element, $property, $fallback = null)
    {
        if (is_array($element) && (isset($element[$property]) || array_key_exists($property, $element)) /*$this->checkMode == self::CHECK_MODE_TYPE_CAST*/) {
            return $element[$property];
        } elseif (is_object($element) && property_exists($element, (string) $property)) {
            return $element->$property;
        }

        return $fallback;
    }

    /**
     * validating minimum and maximum property constraints (if present) against an element
     *
     * @param \stdClass        $element          Element to validate
     * @param \stdClass        $objectDefinition ObjectConstraint definition
     * @param JsonPointer|null $path             Path to test?
     */
    protected function validateMinMaxConstraint($element, $objectDefinition, ?JsonPointer $path = null)
    {
        if (!$this->getTypeCheck()::isObject($element)) {
            return;
        }

        // Verify minimum number of properties
        if (isset($objectDefinition->minProperties) && is_int($objectDefinition->minProperties)) {
            if ($this->getTypeCheck()->propertyCount($element) < max(0, $objectDefinition->minProperties)) {
                $this->addError(ConstraintError::PROPERTIES_MIN(), $path, ['minProperties' => $objectDefinition->minProperties]);
            }
        }
        // Verify maximum number of properties
        if (isset($objectDefinition->maxProperties) && is_int($objectDefinition->maxProperties)) {
            if ($this->getTypeCheck()->propertyCount($element) > max(0, $objectDefinition->maxProperties)) {
                $this->addError(ConstraintError::PROPERTIES_MAX(), $path, ['maxProperties' => $objectDefinition->maxProperties]);
            }
        }
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\ConstraintError;
use JsonSchema\Entity\JsonPointer;

/**
 * The NumberConstraint Constraints, validates an number against a given schema
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 */
class NumberConstraint extends Constraint
{
    /**
     * {@inheritdoc}
     */
    public function check(&$element, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        // Verify minimum
        if (isset($schema->exclusiveMinimum)) {
            if (isset($schema->minimum)) {
                if ($schema->exclusiveMinimum && $element <= $schema->minimum) {
                    $this->addError(ConstraintError::EXCLUSIVE_MINIMUM(), $path, ['minimum' => $schema->minimum]);
                } elseif ($element < $schema->minimum) {
                    $this->addError(ConstraintError::MINIMUM(), $path, ['minimum' => $schema->minimum]);
                }
            } else {
                $this->addError(ConstraintError::MISSING_MINIMUM(), $path);
            }
        } elseif (isset($schema->minimum) && $element < $schema->minimum) {
            $this->addError(ConstraintError::MINIMUM(), $path, ['minimum' => $schema->minimum]);
        }

        // Verify maximum
        if (isset($schema->exclusiveMaximum)) {
            if (isset($schema->maximum)) {
                if ($schema->exclusiveMaximum && $element >= $schema->maximum) {
                    $this->addError(ConstraintError::EXCLUSIVE_MAXIMUM(), $path, ['maximum' => $schema->maximum]);
                } elseif ($element > $schema->maximum) {
                    $this->addError(ConstraintError::MAXIMUM(), $path, ['maximum' => $schema->maximum]);
                }
            } else {
                $this->addError(ConstraintError::MISSING_MAXIMUM(), $path);
            }
        } elseif (isset($schema->maximum) && $element > $schema->maximum) {
            $this->addError(ConstraintError::MAXIMUM(), $path, ['maximum' => $schema->maximum]);
        }

        // Verify divisibleBy - Draft v3
        if (isset($schema->divisibleBy) && $this->fmod($element, $schema->divisibleBy) != 0) {
            $this->addError(ConstraintError::DIVISIBLE_BY(), $path, ['divisibleBy' => $schema->divisibleBy]);
        }

        // Verify multipleOf - Draft v4
        if (isset($schema->multipleOf) && $this->fmod($element, $schema->multipleOf) != 0) {
            $this->addError(ConstraintError::MULTIPLE_OF(), $path, ['multipleOf' => $schema->multipleOf]);
        }

        $this->checkFormat($element, $schema, $path, $i);
    }

    private function fmod($number1, $number2)
    {
        $modulus = ($number1 - round($number1 / $number2) * $number2);
        $precision = 0.0000000001;

        if (-$precision < $modulus && $modulus < $precision) {
            return 0.0;
        }

        return $modulus;
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\DraftIdentifiers;
use JsonSchema\Exception\InvalidArgumentException;
use JsonSchema\SchemaStorage;
use JsonSchema\SchemaStorageInterface;
use JsonSchema\Uri\UriRetriever;
use JsonSchema\UriRetrieverInterface;
use JsonSchema\Validator;

/**
 * Factory for centralize constraint initialization.
 */
class Factory
{
    /**
     * @var SchemaStorageInterface
     */
    protected $schemaStorage;

    /**
     * @var UriRetriever
     */
    protected $uriRetriever;

    /**
     * @var int
     * @phpstan-var int-mask-of<Constraint::CHECK_MODE_*>
     */
    private $checkMode = Constraint::CHECK_MODE_NORMAL;

    /**
     * @var array<int, TypeCheck\TypeCheckInterface>
     * @phpstan-var array<int-mask-of<Constraint::CHECK_MODE_*>, TypeCheck\TypeCheckInterface>
     */
    private $typeCheck = [];

    /**
     * @var int-mask-of<Validator::ERROR_*> Validation context
     */
    protected $errorContext = Validator::ERROR_DOCUMENT_VALIDATION;

    /**
     * The default dialect used for strict mode (Constraint::CHECK_MODE_STRICT) when the schema is without a schema property
     *
     * @var string
     */
    private $defaultDialect = DraftIdentifiers::DRAFT_6;

    /**
     * @var array
     */
    protected $constraintMap = [
        'array' => 'JsonSchema\Constraints\CollectionConstraint',
        'collection' => 'JsonSchema\Constraints\CollectionConstraint',
        'object' => 'JsonSchema\Constraints\ObjectConstraint',
        'type' => 'JsonSchema\Constraints\TypeConstraint',
        'undefined' => 'JsonSchema\Constraints\UndefinedConstraint',
        'string' => 'JsonSchema\Constraints\StringConstraint',
        'number' => 'JsonSchema\Constraints\NumberConstraint',
        'enum' => 'JsonSchema\Constraints\EnumConstraint',
        'const' => 'JsonSchema\Constraints\ConstConstraint',
        'format' => 'JsonSchema\Constraints\FormatConstraint',
        'schema' => 'JsonSchema\Constraints\SchemaConstraint',
        'validator' => 'JsonSchema\Validator',
        'draft06' => Drafts\Draft06\Draft06Constraint::class,
        'draft07' => Drafts\Draft07\Draft07Constraint::class,
    ];

    /**
     * @var array<ConstraintInterface>
     */
    private $instanceCache = [];

    /**
     * @phpstan-param int-mask-of<Constraint::CHECK_MODE_*> $checkMode
     */
    public function __construct(
        ?SchemaStorageInterface $schemaStorage = null,
        ?UriRetrieverInterface $uriRetriever = null,
        int $checkMode = Constraint::CHECK_MODE_NORMAL
    ) {
        // set provided config options
        $this->setConfig($checkMode);

        $this->uriRetriever = $uriRetriever ?: new UriRetriever();
        $this->schemaStorage = $schemaStorage ?: new SchemaStorage($this->uriRetriever);
    }

    /**
     * Set config values
     *
     * @param int $checkMode Set checkMode options - does not preserve existing flags
     * @phpstan-param int-mask-of<Constraint::CHECK_MODE_*> $checkMode
     */
    public function setConfig(int $checkMode = Constraint::CHECK_MODE_NORMAL): void
    {
        $this->checkMode = $checkMode;
    }

    /**
     * Enable checkMode flags
     *
     * @phpstan-param int-mask-of<Constraint::CHECK_MODE_*> $options
     */
    public function addConfig(int $options): void
    {
        $this->checkMode |= $options;
    }

    /**
     * Disable checkMode flags
     *
     * @phpstan-param int-mask-of<Constraint::CHECK_MODE_*> $options
     */
    public function removeConfig(int $options): void
    {
        $this->checkMode &= ~$options;
    }

    /**
     * Get checkMode option
     *
     * @param int|null $options Options to get, if null then return entire bitmask
     * @phpstan-param int-mask-of<Constraint::CHECK_MODE_*>|null $options Options to get, if null then return entire bitmask
     *
     * @phpstan-return int-mask-of<Constraint::CHECK_MODE_*>
     */
    public function getConfig(?int $options = null): int
    {
        if ($options === null) {
            return $this->checkMode;
        }

        return $this->checkMode & $options;
    }

    public function getUriRetriever(): UriRetrieverInterface
    {
        return $this->uriRetriever;
    }

    public function getSchemaStorage(): SchemaStorageInterface
    {
        return $this->schemaStorage;
    }

    public function getTypeCheck(): TypeCheck\TypeCheckInterface
    {
        if (!isset($this->typeCheck[$this->checkMode])) {
            $this->typeCheck[$this->checkMode] = ($this->checkMode & Constraint::CHECK_MODE_TYPE_CAST)
                ? new TypeCheck\LooseTypeCheck()
                : new TypeCheck\StrictTypeCheck();
        }

        return $this->typeCheck[$this->checkMode];
    }

    public function setConstraintClass(string $name, string $class): Factory
    {
        // Ensure class exists
        if (!class_exists($class)) {
            throw new InvalidArgumentException('Unknown constraint ' . $name);
        }
        // Ensure class is appropriate
        if (!in_array('JsonSchema\Constraints\ConstraintInterface', class_implements($class))) {
            throw new InvalidArgumentException('Invalid class ' . $name);
        }
        $this->constraintMap[$name] = $class;

        return $this;
    }

    /**
     * Create a constraint instance for the given constraint name.
     *
     * @param string $constraintName
     *
     * @throws InvalidArgumentException if is not possible create the constraint instance
     *
     * @return ConstraintInterface&BaseConstraint
     * @phpstan-return ConstraintInterface&BaseConstraint
     */
    public function createInstanceFor($constraintName)
    {
        if (!isset($this->constraintMap[$constraintName])) {
            throw new InvalidArgumentException('Unknown constraint ' . $constraintName);
        }

        if (!isset($this->instanceCache[$constraintName])) {
            $this->instanceCache[$constraintName] = new $this->constraintMap[$constraintName]($this);
        }

        return clone $this->instanceCache[$constraintName];
    }

    /**
     * Get the error context
     *
     * @return int-mask-of<Validator::ERROR_*>
     */
    public function getErrorContext(): int
    {
        return $this->errorContext;
    }

    /**
     * Set the error context
     *
     * @param int-mask-of<Validator::ERROR_*> $errorContext
     */
    public function setErrorContext(int $errorContext): void
    {
        $this->errorContext = $errorContext;
    }

    public function getDefaultDialect(): string
    {
        return $this->defaultDialect;
    }

    public function setDefaultDialect(string $defaultDialect): void
    {
        $this->defaultDialect = $defaultDialect;
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\TypeCheck;

class LooseTypeCheck implements TypeCheckInterface
{
    public static function isObject($value)
    {
        return
            is_object($value) ||
            (is_array($value) && (count($value) == 0 || self::isAssociativeArray($value)));
    }

    public static function isArray($value)
    {
        return
            is_array($value) &&
            (count($value) == 0 || !self::isAssociativeArray($value));
    }

    public static function propertyGet($value, $property)
    {
        if (is_object($value)) {
            return $value->{$property};
        }

        return $value[$property];
    }

    public static function propertySet(&$value, $property, $data)
    {
        if (is_object($value)) {
            $value->{$property} = $data;
        } else {
            $value[$property] = $data;
        }
    }

    public static function propertyExists($value, $property)
    {
        if (is_object($value)) {
            return property_exists($value, $property);
        }

        return is_array($value) && array_key_exists($property, $value);
    }

    public static function propertyCount($value)
    {
        if (is_object($value)) {
            return count(get_object_vars($value));
        }

        return count($value);
    }

    /**
     * Check if the provided array is associative or not
     *
     * @param array $arr
     *
     * @return bool
     */
    private static function isAssociativeArray($arr)
    {
        return array_keys($arr) !== range(0, count($arr) - 1);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\TypeCheck;

interface TypeCheckInterface
{
    public static function isObject($value);

    public static function isArray($value);

    public static function propertyGet($value, $property);

    public static function propertySet(&$value, $property, $data);

    public static function propertyExists($value, $property);

    public static function propertyCount($value);
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\TypeCheck;

class StrictTypeCheck implements TypeCheckInterface
{
    public static function isObject($value)
    {
        return is_object($value);
    }

    public static function isArray($value)
    {
        return is_array($value);
    }

    public static function propertyGet($value, $property)
    {
        return $value->{$property};
    }

    public static function propertySet(&$value, $property, $data)
    {
        $value->{$property} = $data;
    }

    public static function propertyExists($value, $property)
    {
        return property_exists($value, $property);
    }

    public static function propertyCount($value)
    {
        if (!is_object($value)) {
            return 0;
        }

        return count(get_object_vars($value));
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\ConstraintError;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Tool\DeepComparer;

/**
 * The ConstConstraint Constraints, validates an element against a constant value
 *
 * @author Martin Helmich <martin@helmich.me>
 */
class ConstConstraint extends Constraint
{
    /**
     * {@inheritdoc}
     */
    public function check(&$element, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        // Only validate const if the attribute exists
        if ($element instanceof UndefinedConstraint && (!isset($schema->required) || !$schema->required)) {
            return;
        }
        $const = $schema->const;

        $type = gettype($element);
        $constType = gettype($const);

        if ($this->factory->getConfig(self::CHECK_MODE_TYPE_CAST) && $type === 'array' && $constType === 'object') {
            if (DeepComparer::isEqual((object) $element, $const)) {
                return;
            }
        }

        if (DeepComparer::isEqual($element, $const)) {
            return;
        }

        $this->addError(ConstraintError::CONSTANT(), $path, ['const' => $schema->const]);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class RefConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    /** @var Factory */
    private $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
        $this->initialiseErrorBag($this->factory);
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, '$ref')) {
            return;
        }

        try {
            $refSchema = $this->factory->getSchemaStorage()->resolveRefSchema($schema);
        } catch (\Exception $e) {
            return;
        }

        $schemaConstraint = $this->factory->createInstanceFor('schema');
        $schemaConstraint->check($value, $refSchema, $path, $i);

        if ($schemaConstraint->isValid()) {
            return;
        }

        $this->addErrors($schemaConstraint->getErrors());
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class MinLengthConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'minLength')) {
            return;
        }

        if (!is_string($value)) {
            return;
        }

        $length = mb_strlen($value);
        if ($length >= $schema->minLength) {
            return;
        }

        $this->addError(ConstraintError::LENGTH_MIN(), $path, ['minLength' => $schema->minLength, 'found' => $length]);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class AllOfConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    /** @var Factory */
    private $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
        $this->initialiseErrorBag($this->factory);
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'allOf')) {
            return;
        }

        foreach ($schema->allOf as $allOfSchema) {
            $schemaConstraint = $this->factory->createInstanceFor('schema');
            $schemaConstraint->check($value, $allOfSchema, $path, $i);

            if ($schemaConstraint->isValid()) {
                continue;
            }
            $this->addError(ConstraintError::ALL_OF(), $path);
            $this->addErrors($schemaConstraint->getErrors());
        }
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class RequiredConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'required')) {
            return;
        }

        if (!is_object($value)) {
            return;
        }

        foreach ($schema->required as $required) {
            if (property_exists($value, $required)) {
                continue;
            }

            $this->addError(ConstraintError::REQUIRED(), $this->incrementPath($path, $required), ['property' => $required]);
        }
    }

    /**
     * @todo refactor as this was only copied from UndefinedConstraint
     * Bubble down the path
     *
     * @param JsonPointer|null $path Current path
     * @param mixed            $i    What to append to the path
     */
    protected function incrementPath(?JsonPointer $path, $i): JsonPointer
    {
        $path = $path ?? new JsonPointer('');

        if ($i === null || $i === '') {
            return $path;
        }

        return $path->withPropertyPaths(array_merge($path->getPropertyPaths(), [$i]));
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Exception\ValidationException;

class AnyOfConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    /** @var Factory */
    private $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
        $this->initialiseErrorBag($this->factory);
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'anyOf')) {
            return;
        }

        foreach ($schema->anyOf as $anyOfSchema) {
            $schemaConstraint = $this->factory->createInstanceFor('schema');

            try {
                $schemaConstraint->check($value, $anyOfSchema, $path, $i);

                if ($schemaConstraint->isValid()) {
                    $this->errorBag()->reset();

                    return;
                }

                $this->addErrors($schemaConstraint->getErrors());
            } catch (ValidationException $e) {
            }
        }

        $this->addError(ConstraintError::ANY_OF(), $path);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class ItemsConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    /** @var Factory */
    private $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
        $this->initialiseErrorBag($this->factory);
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'items')) {
            return;
        }

        if (!is_array($value)) {
            return;
        }

        foreach ($value as $propertyName => $propertyValue) {
            $itemSchema = $schema->items;
            if (is_array($itemSchema)) {
                if (!array_key_exists($propertyName, $itemSchema)) {
                    continue;
                }

                $itemSchema  = $itemSchema[$propertyName];
            }
            $schemaConstraint = $this->factory->createInstanceFor('schema');
            $schemaConstraint->check($propertyValue, $itemSchema, $path, $i);
            if ($schemaConstraint->isValid()) {
                continue;
            }

            $this->addErrors($schemaConstraint->getErrors());
        }
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

class Factory extends \JsonSchema\Constraints\Factory
{
    /**
     * @var array<string, class-string>
     */
    protected $constraintMap = [
        'schema' => Draft07Constraint::class,
        'additionalProperties' => AdditionalPropertiesConstraint::class,
        'additionalItems' => AdditionalItemsConstraint::class,
        'dependencies' => DependenciesConstraint::class,
        'type' => TypeConstraint::class,
        'const' => ConstConstraint::class,
        'enum' => EnumConstraint::class,
        'uniqueItems' => UniqueItemsConstraint::class,
        'minItems' => MinItemsConstraint::class,
        'minProperties' => MinPropertiesConstraint::class,
        'maxProperties' => MaxPropertiesConstraint::class,
        'minimum' => MinimumConstraint::class,
        'maximum' => MaximumConstraint::class,
        'exclusiveMinimum' => ExclusiveMinimumConstraint::class,
        'minLength' => MinLengthConstraint::class,
        'maxLength' => MaxLengthConstraint::class,
        'maxItems' => MaxItemsConstraint::class,
        'exclusiveMaximum' => ExclusiveMaximumConstraint::class,
        'multipleOf' => MultipleOfConstraint::class,
        'required' => RequiredConstraint::class,
        'format' => FormatConstraint::class,
        'anyOf' => AnyOfConstraint::class,
        'allOf' => AllOfConstraint::class,
        'oneOf' => OneOfConstraint::class,
        'not' => NotConstraint::class,
        'ifThenElse' => IfThenElseConstraint::class,
        'contains' => ContainsConstraint::class,
        'propertyNames' => PropertiesNamesConstraint::class,
        'patternProperties' => PatternPropertiesConstraint::class,
        'pattern' => PatternConstraint::class,
        'properties' => PropertiesConstraint::class,
        'items' => ItemsConstraint::class,
        'ref' => RefConstraint::class,
        'content' => ContentConstraint::class,
    ];
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class MinItemsConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'minItems')) {
            return;
        }

        if (!is_array($value)) {
            return;
        }

        $count = count($value);
        if ($count >= $schema->minItems) {
            return;
        }

        $this->addError(ConstraintError::MIN_ITEMS(), $path, ['minItems' => $schema->minItems, 'found' => $count]);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Tool\DeepComparer;

class ConstConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'const')) {
            return;
        }

        if (DeepComparer::isEqual($value, $schema->const)) {
            return;
        }

        $this->addError(ConstraintError::CONSTANT(), $path, ['const' => $schema->const]);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class DependenciesConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    /** @var Factory */
    private $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
        $this->initialiseErrorBag($this->factory);
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'dependencies')) {
            return;
        }

        if (!is_object($value)) {
            return;
        }

        foreach ($schema->dependencies as $dependant => $dependencies) {
            if (!property_exists($value, $dependant)) {
                continue;
            }
            if ($dependencies === true) {
                continue;
            }
            if ($dependencies === false) {
                $this->addError(ConstraintError::FALSE(), $path, ['dependant' => $dependant]);
                continue;
            }

            if (is_array($dependencies)) {
                foreach ($dependencies as $dependency) {
                    if (property_exists($value, $dependant) && !property_exists($value, $dependency)) {
                        $this->addError(ConstraintError::DEPENDENCIES(), $path, ['dependant' => $dependant, 'dependency' => $dependency]);
                    }
                }
            }

            if (is_object($dependencies)) {
                $schemaConstraint = $this->factory->createInstanceFor('schema');
                $schemaConstraint->check($value, $dependencies, $path, $i);
                if (!$schemaConstraint->isValid()) {
                    $this->addErrors($schemaConstraint->getErrors());
                }
            }
        }
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class ExclusiveMaximumConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'exclusiveMaximum')) {
            return;
        }

        if (!is_numeric($value)) {
            return;
        }

        if ($value < $schema->exclusiveMaximum) {
            return;
        }

        $this->addError(ConstraintError::EXCLUSIVE_MAXIMUM(), $path, ['exclusiveMaximum' => $schema->exclusiveMaximum, 'found' => $value]);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class ExclusiveMinimumConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'exclusiveMinimum')) {
            return;
        }

        if (!is_numeric($value)) {
            return;
        }

        if ($value > $schema->exclusiveMinimum) {
            return;
        }

        $this->addError(ConstraintError::EXCLUSIVE_MINIMUM(), $path, ['exclusiveMinimum' => $schema->exclusiveMinimum, 'found' => $value]);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class NotConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    /** @var Factory */
    private $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
        $this->initialiseErrorBag($this->factory);
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'not')) {
            return;
        }

        $schemaConstraint = $this->factory->createInstanceFor('schema');
        $schemaConstraint->check($value, $schema->not, $path, $i);

        if (!$schemaConstraint->isValid()) {
            return;
        }

        $this->addError(ConstraintError::NOT(), $path);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class IfThenElseConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    /** @var Factory */
    private $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
        $this->initialiseErrorBag($this->factory);
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'if')) {
            return;
        }

        $schemaConstraint = $this->factory->createInstanceFor('schema');
        $ifSchema = $schema->if;

        if (!is_bool($ifSchema)) {
            $schemaConstraint->check($value, $ifSchema, $path, $i);
            $meetsIfConditions = $schemaConstraint->isValid();
            $schemaConstraint->reset();
        } else {
            $meetsIfConditions = $ifSchema;
        }

        if ($meetsIfConditions) {
            if (!property_exists($schema, 'then')) {
                return;
            }

            $schemaConstraint->check($value, $schema->then, $path, $i);
            if ($schemaConstraint->isValid()) {
                return;
            }

            $this->addErrors($schemaConstraint->getErrors());

            return;
        }

        if (!property_exists($schema, 'else')) {
            return;
        }

        $schemaConstraint->check($value, $schema->else, $path, $i);
        if ($schemaConstraint->isValid()) {
            return;
        }

        $this->addErrors($schemaConstraint->getErrors());
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class MaxPropertiesConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'maxProperties')) {
            return;
        }

        if (!is_object($value)) {
            return;
        }

        $count = count(get_object_vars($value));
        if ($count <= $schema->maxProperties) {
            return;
        }

        $this->addError(ConstraintError::PROPERTIES_MAX(), $path, ['maxProperties' => $schema->maxProperties, 'found' => $count]);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class MinPropertiesConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'minProperties')) {
            return;
        }

        if (!is_object($value)) {
            return;
        }

        $count = count(get_object_vars($value));
        if ($count >= $schema->minProperties) {
            return;
        }

        $this->addError(ConstraintError::PROPERTIES_MIN(), $path, ['minProperties' => $schema->minProperties, 'found' => $count]);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Tool\DeepComparer;

class UniqueItemsConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'uniqueItems')) {
            return;
        }
        if (!is_array($value)) {
            return;
        }

        if ($schema->uniqueItems !== true) {
            // If unique items not is true duplicates are allowed.
            return;
        }

        $count = count($value);
        for ($x = 0; $x < $count - 1; $x++) {
            for ($y = $x + 1; $y < $count; $y++) {
                if (DeepComparer::isEqual($value[$x], $value[$y])) {
                    $this->addError(ConstraintError::UNIQUE_ITEMS(), $path);

                    return;
                }
            }
        }
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class PropertiesConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    /** @var Factory */
    private $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
        $this->initialiseErrorBag($this->factory);
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'properties')) {
            return;
        }

        if (!is_object($value)) {
            return;
        }

        foreach ($schema->properties as $propertyName => $propertySchema) {
            $schemaConstraint = $this->factory->createInstanceFor('schema');
            if (!property_exists($value, $propertyName)) {
                continue;
            }

            $schemaConstraint->check($value->{$propertyName}, $propertySchema, $path, $i);
            if ($schemaConstraint->isValid()) {
                continue;
            }

            $this->addErrors($schemaConstraint->getErrors());
        }
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\Constraint;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\SchemaStorage;
use JsonSchema\Uri\UriRetriever;

class Draft07Constraint extends Constraint
{
    public function __construct(?\JsonSchema\Constraints\Factory $factory = null)
    {
        parent::__construct(new Factory(
            $factory ? $factory->getSchemaStorage() : new SchemaStorage(),
            $factory ? $factory->getUriRetriever() : new UriRetriever(),
            $factory ? $factory->getConfig() : Constraint::CHECK_MODE_NORMAL
        ));
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (is_bool($schema)) {
            if ($schema === false) {
                $this->addError(ConstraintError::FALSE(), $path, []);
            }

            return;
        }

        // Apply defaults
        $this->checkForKeyword('ref', $value, $schema, $path, $i);
        $this->checkForKeyword('required', $value, $schema, $path, $i);
        $this->checkForKeyword('contains', $value, $schema, $path, $i);
        $this->checkForKeyword('properties', $value, $schema, $path, $i);
        $this->checkForKeyword('propertyNames', $value, $schema, $path, $i);
        $this->checkForKeyword('patternProperties', $value, $schema, $path, $i);
        $this->checkForKeyword('type', $value, $schema, $path, $i);
        $this->checkForKeyword('not', $value, $schema, $path, $i);
        $this->checkForKeyword('dependencies', $value, $schema, $path, $i);
        $this->checkForKeyword('allOf', $value, $schema, $path, $i);
        $this->checkForKeyword('anyOf', $value, $schema, $path, $i);
        $this->checkForKeyword('oneOf', $value, $schema, $path, $i);
        $this->checkForKeyword('ifThenElse', $value, $schema, $path, $i);

        $this->checkForKeyword('additionalProperties', $value, $schema, $path, $i);
        $this->checkForKeyword('items', $value, $schema, $path, $i);
        $this->checkForKeyword('additionalItems', $value, $schema, $path, $i);
        $this->checkForKeyword('uniqueItems', $value, $schema, $path, $i);
        $this->checkForKeyword('minItems', $value, $schema, $path, $i);
        $this->checkForKeyword('minProperties', $value, $schema, $path, $i);
        $this->checkForKeyword('maxProperties', $value, $schema, $path, $i);
        $this->checkForKeyword('minimum', $value, $schema, $path, $i);
        $this->checkForKeyword('maximum', $value, $schema, $path, $i);
        $this->checkForKeyword('minLength', $value, $schema, $path, $i);
        $this->checkForKeyword('exclusiveMinimum', $value, $schema, $path, $i);
        $this->checkForKeyword('maxItems', $value, $schema, $path, $i);
        $this->checkForKeyword('maxLength', $value, $schema, $path, $i);
        $this->checkForKeyword('exclusiveMaximum', $value, $schema, $path, $i);
        $this->checkForKeyword('enum', $value, $schema, $path, $i);
        $this->checkForKeyword('const', $value, $schema, $path, $i);
        $this->checkForKeyword('multipleOf', $value, $schema, $path, $i);
        $this->checkForKeyword('format', $value, $schema, $path, $i);
        $this->checkForKeyword('pattern', $value, $schema, $path, $i);
        $this->checkForKeyword('content', $value, $schema, $path, $i);
    }

    /**
     * @param mixed $value
     * @param mixed $schema
     * @param mixed $i
     */
    protected function checkForKeyword(string $keyword, $value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        $validator = $this->factory->createInstanceFor($keyword);
        $validator->check($value, $schema, $path, $i);

        $this->addErrors($validator->getErrors());
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class ContentConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'contentMediaType') && !property_exists($schema, 'contentEncoding')) {
            return;
        }
        if (!is_string($value)) {
            return;
        }

        $decodedValue = $value;

        if (property_exists($schema, 'contentEncoding')) {
            if ($schema->contentEncoding === 'base64') {
                if (!preg_match('/^[A-Za-z0-9+\/=]+$/', $decodedValue)) {
                    $this->addError(ConstraintError::CONTENT_ENCODING(), $path, ['contentEncoding' => $schema->contentEncoding]);

                    return;
                }
                $decodedValue = base64_decode($decodedValue);
            }
        }

        if (property_exists($schema, 'contentMediaType')) {
            if ($schema->contentMediaType === 'application/json') {
                json_decode($decodedValue, false);
                if (json_last_error() === JSON_ERROR_NONE) {
                    return;
                }
            }

            $this->addError(ConstraintError::CONTENT_MEDIA_TYPE(), $path, ['contentMediaType' => $schema->contentMediaType]);
        }
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class ContainsConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    /** @var Factory */
    private $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
        $this->initialiseErrorBag($this->factory);
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'contains')) {
            return;
        }

        $properties = [];
        if (!is_array($value)) {
            return;
        }

        foreach ($value as $propertyName => $propertyValue) {
            $schemaConstraint = $this->factory->createInstanceFor('schema');

            $schemaConstraint->check($propertyValue, $schema->contains, $path, $i);
            if ($schemaConstraint->isValid()) {
                return;
            }
        }

        $this->addError(ConstraintError::CONTAINS(), $path, ['contains' => $schema->contains]);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class MaxItemsConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'maxItems')) {
            return;
        }

        if (!is_array($value)) {
            return;
        }

        $count = count($value);
        if ($count <= $schema->maxItems) {
            return;
        }

        $this->addError(ConstraintError::MAX_ITEMS(), $path, ['maxItems' => $schema->maxItems, 'found' => $count]);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class TypeConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'type')) {
            return;
        }

        $schemaTypes = (array) $schema->type;
        $valueType = strtolower(gettype($value));
        // All specific number types are a number
        $valueIsNumber = $valueType === 'double' ||  $valueType === 'integer';
        // A float with zero fractional part is an integer
        $isInteger = $valueIsNumber && fmod($value, 1.0) === 0.0;

        foreach ($schemaTypes as $type) {
            if ($valueType === $type) {
                return;
            }

            if ($type === 'number' && $valueIsNumber) {
                return;
            }
            if ($type === 'integer' && $isInteger) {
                return;
            }
        }

        $this->addError(ConstraintError::TYPE(), $path, ['found' => $valueType, 'expected' => implode(', ', $schemaTypes)]);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class PropertiesNamesConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    /** @var Factory */
    private $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
        $this->initialiseErrorBag($this->factory);
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'propertyNames')) {
            return;
        }

        if (!is_object($value)) {
            return;
        }
        if ($schema->propertyNames === true) {
            return;
        }

        $propertyNames = get_object_vars($value);

        if ($schema->propertyNames === false) {
            foreach ($propertyNames as $propertyName => $_) {
                $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'false', 'name' => $propertyName]);
            }

            return;
        }

        if (property_exists($schema->propertyNames, 'maxLength')) {
            foreach ($propertyNames as $propertyName => $_) {
                $length = mb_strlen($propertyName);
                if ($length > $schema->propertyNames->maxLength) {
                    $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'maxLength', 'length' => $length, 'name' => $propertyName]);
                }
            }
        }

        if (property_exists($schema->propertyNames, 'pattern')) {
            foreach ($propertyNames as $propertyName => $_) {
                if (!preg_match('/' . str_replace('/', '\/', $schema->propertyNames->pattern) . '/', $propertyName)) {
                    $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'pattern', 'name' => $propertyName]);
                }
            }
        }

        if (property_exists($schema->propertyNames, 'const')) {
            foreach ($propertyNames as $propertyName => $_) {
                if ($propertyName !== $schema->propertyNames->const) {
                    $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'const', 'name' => $propertyName]);
                }
            }
        }

        if (property_exists($schema->propertyNames, 'enum')) {
            $diff = array_diff(array_keys($propertyNames), $schema->propertyNames->enum);
            foreach ($diff as $propertyName) {
                $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'enum', 'name' => $propertyName]);
            }
        }
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class OneOfConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    /** @var Factory */
    private $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
        $this->initialiseErrorBag($this->factory);
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'oneOf')) {
            return;
        }

        $matchedSchema = 0;
        foreach ($schema->oneOf as $oneOfSchema) {
            $schemaConstraint = $this->factory->createInstanceFor('schema');
            $schemaConstraint->check($value, $oneOfSchema, $path, $i);

            if ($schemaConstraint->isValid()) {
                $matchedSchema++;
                continue;
            }

            $this->addErrors($schemaConstraint->getErrors());
        }

        if ($matchedSchema !== 1) {
            $this->addError(ConstraintError::ONE_OF(), $path);
        } else {
            $this->errorBag()->reset();
        }
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class AdditionalPropertiesConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    /** @var Factory */
    private $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
        $this->initialiseErrorBag($this->factory);
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'additionalProperties')) {
            return;
        }

        if ($schema->additionalProperties === true) {
            return;
        }

        if (!is_object($value)) {
            return;
        }

        $additionalProperties = get_object_vars($value);

        if (isset($schema->properties)) {
            $additionalProperties = array_diff_key($additionalProperties, (array) $schema->properties);
        }

        if (isset($schema->patternProperties)) {
            $patterns = array_keys(get_object_vars($schema->patternProperties));

            foreach ($additionalProperties as $key => $_) {
                foreach ($patterns as $pattern) {
                    if (preg_match($this->createPregMatchPattern($pattern), (string) $key)) {
                        unset($additionalProperties[$key]);
                        break;
                    }
                }
            }
        }

        if (is_object($schema->additionalProperties)) {
            foreach ($additionalProperties as $key => $additionalPropertiesValue) {
                $schemaConstraint = $this->factory->createInstanceFor('schema');
                $schemaConstraint->check($additionalPropertiesValue, $schema->additionalProperties, $path, $i); // @todo increment path
                if ($schemaConstraint->isValid()) {
                    unset($additionalProperties[$key]);
                }
            }
        }

        foreach ($additionalProperties as $key => $additionalPropertiesValue) {
            $this->addError(ConstraintError::ADDITIONAL_PROPERTIES(), $path, ['found' => $additionalPropertiesValue]);
        }
    }

    private function createPregMatchPattern(string $pattern): string
    {
        $replacements = [
//            '\D' => '[^0-9]',
//            '\d' => '[0-9]',
            '\p{digit}' => '\p{Nd}',
//            '\w' => '[A-Za-z0-9_]',
//            '\W' => '[^A-Za-z0-9_]',
//            '\s' => '[\s\x{200B}]' // Explicitly include zero width white space,
            '\p{Letter}' => '\p{L}', // Map ECMA long property name to PHP (PCRE) Unicode property abbreviations
        ];

        $pattern = str_replace(
            array_keys($replacements),
            array_values($replacements),
            $pattern
        );

        return '/' . str_replace('/', '\/', $pattern) . '/u';
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class MinimumConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'minimum')) {
            return;
        }

        if (!is_numeric($value)) {
            return;
        }

        if ($value >= $schema->minimum) {
            return;
        }

        $this->addError(ConstraintError::MINIMUM(), $path, ['minimum' => $schema->minimum, 'found' => $value]);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class PatternConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    /** @var Factory */
    private $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
        $this->initialiseErrorBag($this->factory);
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'pattern')) {
            return;
        }

        if (!is_string($value)) {
            return;
        }

        $matchPattern = $this->createPregMatchPattern($schema->pattern);
        if (preg_match($matchPattern, $value) === 1) {
            return;
        }

        $this->addError(ConstraintError::PATTERN(), $path, ['found' => $value, 'pattern' => $schema->pattern]);
    }

    private function createPregMatchPattern(string $pattern): string
    {
        $replacements = [
            '\D' => '[^0-9]',
            '\d' => '[0-9]',
            '\p{digit}' => '[0-9]',
            '\w' => '[A-Za-z0-9_]',
            '\W' => '[^A-Za-z0-9_]',
            '\s' => '[\s\x{200B}]', // Explicitly include zero width white space
            '\p{Letter}' => '\p{L}', // Map ECMA long property name to PHP (PCRE) Unicode property abbreviations
        ];

        $pattern = str_replace(
            array_keys($replacements),
            array_values($replacements),
            $pattern
        );

        return '/' . str_replace('/', '\/', $pattern) . '/u';
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class PatternPropertiesConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    /** @var Factory */
    private $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
        $this->initialiseErrorBag($this->factory);
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'patternProperties')) {
            return;
        }

        if (!is_object($value)) {
            return;
        }

        $properties = get_object_vars($value);

        foreach ($properties as $propertyName => $propertyValue) {
            foreach ($schema->patternProperties as $patternPropertyRegex => $patternPropertySchema) {
                $matchPattern = $this->createPregMatchPattern($patternPropertyRegex);
                if (preg_match($matchPattern, (string) $propertyName)) {
                    $schemaConstraint = $this->factory->createInstanceFor('schema');
                    $schemaConstraint->check($propertyValue, $patternPropertySchema, $path, $i);
                    if ($schemaConstraint->isValid()) {
                        continue;
                    }

                    $this->addErrors($schemaConstraint->getErrors());
                }
            }
        }
    }

    private function createPregMatchPattern(string $pattern): string
    {
        $replacements = [
//            '\D' => '[^0-9]',
            '\d' => '[0-9]',
            '\p{digit}' => '[0-9]',
//            '\w' => '[A-Za-z0-9_]',
//            '\W' => '[^A-Za-z0-9_]',
//            '\s' => '[\s\x{200B}]' // Explicitly include zero width white space
            '\p{Letter}' => '\p{L}', // Map ECMA long property name to PHP (PCRE) Unicode property abbreviations
        ];

        $pattern = str_replace(
            array_keys($replacements),
            array_values($replacements),
            $pattern
        );

        return '/' . str_replace('/', '\/', $pattern) . '/u';
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class MaxLengthConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'maxLength')) {
            return;
        }

        if (!is_string($value)) {
            return;
        }

        $length = mb_strlen($value);
        if ($length <= $schema->maxLength) {
            return;
        }

        $this->addError(ConstraintError::LENGTH_MAX(), $path, ['maxLength' => $schema->maxLength, 'found' => $length]);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class MaximumConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'maximum')) {
            return;
        }

        if (!is_numeric($value)) {
            return;
        }

        if ($value <= $schema->maximum) {
            return;
        }

        $this->addError(ConstraintError::MAXIMUM(), $path, ['maximum' => $schema->maximum, 'found' => $value]);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Tool\DeepComparer;

class EnumConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'enum')) {
            return;
        }

        foreach ($schema->enum as $enumCase) {
            if (DeepComparer::isEqual($value, $enumCase)) {
                return;
            }

            if (is_numeric($value) && is_numeric($enumCase) && DeepComparer::isEqual((float) $value, (float) $enumCase)) {
                return;
            }
        }

        $this->addError(ConstraintError::ENUM(), $path, ['enum' => $schema->enum]);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class AdditionalItemsConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    /** @var Factory */
    private $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
        $this->initialiseErrorBag($this->factory);
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'additionalItems')) {
            return;
        }

        if ($schema->additionalItems === true) {
            return;
        }
        if ($schema->additionalItems === false && !property_exists($schema, 'items')) {
            return;
        }

        if (!is_array($value)) {
            return;
        }
        if (!property_exists($schema, 'items')) {
            return;
        }
        if (property_exists($schema, 'items') && is_object($schema->items)) {
            return;
        }

        $additionalItems = array_diff_key($value, property_exists($schema, 'items') ? $schema->items : []);

        foreach ($additionalItems as $propertyName => $propertyValue) {
            $schemaConstraint = $this->factory->createInstanceFor('schema');
            $schemaConstraint->check($propertyValue, $schema->additionalItems, $path, $i);

            if ($schemaConstraint->isValid()) {
                continue;
            }

            $this->addError(ConstraintError::ADDITIONAL_ITEMS(), $path, ['item' => $i, 'property' => $propertyName, 'additionalItems' => $schema->additionalItems]);
        }
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class MultipleOfConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'multipleOf')) {
            return;
        }

        if (!is_int($schema->multipleOf) && !is_float($schema->multipleOf) && $schema->multipleOf <= 0.0) {
            return;
        }

        if (!is_int($value) && !is_float($value)) {
            return;
        }

        if ($this->isMultipleOf($value, $schema->multipleOf)) {
            return;
        }

        $this->addError(ConstraintError::MULTIPLE_OF(), $path, ['multipleOf' => $schema->multipleOf, 'found' => $value]);
    }

    /**
     * @param int|float $number1
     * @param int|float $number2
     */
    private function isMultipleOf($number1, $number2): bool
    {
        $modulus = ($number1 - round($number1 / $number2) * $number2);
        $precision = 0.0000000001;

        return -$precision < $modulus && $modulus < $precision;
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft07;

use DateTimeZone;
use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Rfc3339;
use JsonSchema\Tool\Validator\RelativeReferenceValidator;
use JsonSchema\Tool\Validator\UriValidator;

class FormatConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'format')) {
            return;
        }

        if (!is_string($value)) {
            return;
        }

        switch ($schema->format) {
            case 'date':
                if (!$this->validateDateTime($value, 'Y-m-d')) {
                    $this->addError(ConstraintError::FORMAT_DATE(), $path, ['date' => $value, 'format' => $schema->format]);
                }
                break;
            case 'time':
                if (!$this->validateDateTime($value, 'H:i:sp') && !$this->validateDateTime($value, 'H:i:s.up')) {
                    $this->addError(ConstraintError::FORMAT_TIME(), $path, ['time' => $value, 'format' => $schema->format]);
                }
                break;
            case 'date-time':
                if (!$this->validateRfc3339DateTime($value)) {
                    $this->addError(ConstraintError::FORMAT_DATE_TIME(), $path, ['dateTime' => $value, 'format' => $schema->format]);
                }
                break;
            case 'utc-millisec':
                if (!$this->validateDateTime($value, 'U')) {
                    $this->addError(ConstraintError::FORMAT_DATE_UTC(), $path, ['value' => $value, 'format' => $schema->format]);
                }
                break;
            case 'regex':
                if (!$this->validateRegex($value)) {
                    $this->addError(ConstraintError::FORMAT_REGEX(), $path, ['value' => $value, 'format' => $schema->format]);
                }
                break;
            case 'ip-address':
            case 'ipv4':
                if (filter_var($value, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4) === null) {
                    $this->addError(ConstraintError::FORMAT_IP(), $path, ['format' => $schema->format]);
                }
                break;
            case 'ipv6':
                if (filter_var($value, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV6) === null) {
                    $this->addError(ConstraintError::FORMAT_IP(), $path, ['format' => $schema->format]);
                }
                break;
            case 'color':
                if (!$this->validateColor($value)) {
                    $this->addError(ConstraintError::FORMAT_COLOR(), $path, ['format' => $schema->format]);
                }
                break;
            case 'style':
                if (!$this->validateStyle($value)) {
                    $this->addError(ConstraintError::FORMAT_STYLE(), $path, ['format' => $schema->format]);
                }
                break;
            case 'phone':
                if (!$this->validatePhone($value)) {
                    $this->addError(ConstraintError::FORMAT_PHONE(), $path, ['format' => $schema->format]);
                }
                break;
            case 'uri':
                if (!UriValidator::isValid($value)) {
                    $this->addError(ConstraintError::FORMAT_URL(), $path, ['format' => $schema->format]);
                }
                break;

            case 'uriref':
            case 'uri-reference':
                if (!(UriValidator::isValid($value) || RelativeReferenceValidator::isValid($value))) {
                    $this->addError(ConstraintError::FORMAT_URL(), $path, ['format' => $schema->format]);
                }
                break;
            case 'uri-template':
                if (!$this->validateUriTemplate($value)) {
                    $this->addError(ConstraintError::FORMAT_URI_TEMPLATE(), $path, ['format' => $schema->format]);
                }
                break;

            case 'email':
                if (filter_var($value, FILTER_VALIDATE_EMAIL, FILTER_NULL_ON_FAILURE | FILTER_FLAG_EMAIL_UNICODE) === null) {
                    $this->addError(ConstraintError::FORMAT_EMAIL(), $path, ['format' => $schema->format]);
                }
                break;
            case 'host-name':
            case 'hostname':
                if (!$this->validateHostname($value)) {
                    $this->addError(ConstraintError::FORMAT_HOSTNAME(), $path, ['format' => $schema->format]);
                }
                break;
            case 'idn-hostname':
                if (!$this->validateInternationalizedHostname($value)) {
                    $this->addError(ConstraintError::FORMAT_HOSTNAME(), $path, ['format' => $schema->format]);
                }
                break;
            case 'json-pointer':
                if (!$this->validateJsonPointer($value)) {
                    $this->addError(ConstraintError::FORMAT_JSON_POINTER(), $path, ['format' => $schema->format]);
                }
                break;
            default:
                break;
        }
    }

    private function validateDateTime(string $datetime, string $format): bool
    {
        $datetime = strtoupper($datetime); // Cleanup for lowercase z
        $isLeap = substr($datetime, 6, 2) === '60';
        $input = $datetime;

        // Correct for leap second
        if ($isLeap) {
            $input = sprintf('%s59%s', substr($datetime, 0, 6), substr($datetime, 8));
        }

        $dt = \DateTimeImmutable::createFromFormat($format, $input);
        if (!$dt) {
            return false;
        }

        // Handle invalid timezone offsets
        $timezoneOffset = $dt->getTimezone()->getOffset($dt);
        if ($timezoneOffset >= 86400 || $timezoneOffset <= -86400) {
            return false;
        }

        $expected = $dt->format($format);
        // Correct for trailing zeros on microseconds
        if ($format === 'H:i:s.up') {
            $expected = sprintf(
                '%s%s',
                rtrim($dt->format('H:i:s.u'), '0'),
                $dt->format('p')
            );
        }
        // Correct back for leap seconds
        if ($isLeap) {
            // Only when 23:59:59 in UTC
            $utcDT = $dt->setTimezone(new DateTimeZone('UTC'));
            if ($utcDT->format('H:i:s') !== '23:59:59') {
                return false;
            }

            $expected = sprintf('%s60%s', substr($expected, 0, 6), substr($expected, 8));
        }

        return $datetime === $expected;
    }

    private function validateRegex(string $regex): bool
    {
        return preg_match(self::jsonPatternToPhpRegex($regex), '') !== false;
    }

    /**
     * Transform a JSON pattern into a PCRE regex
     */
    private static function jsonPatternToPhpRegex(string $pattern): string
    {
        return '~' . str_replace('~', '\\~', $pattern) . '~u';
    }

    private function validateColor(string $color): bool
    {
        if (in_array(strtolower($color), ['aqua', 'black', 'blue', 'fuchsia',
            'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'orange', 'purple',
            'red', 'silver', 'teal', 'white', 'yellow'])) {
            return true;
        }

        return preg_match('/^#([a-f0-9]{3}|[a-f0-9]{6})$/i', $color) !== false;
    }

    private function validateStyle(string $style): bool
    {
        $properties     = explode(';', rtrim($style, ';'));
        $invalidEntries = preg_grep('/^\s*[-a-z]+\s*:\s*.+$/i', $properties, PREG_GREP_INVERT);

        return empty($invalidEntries);
    }

    private function validatePhone(string $phone): bool
    {
        return preg_match('/^\+?(\(\d{3}\)|\d{3}) \d{3} \d{4}$/', $phone) !== false;
    }

    private function validateHostname(string $host): bool
    {
        $hostnameRegex = '/^(?!-)(?!.*?[^A-Za-z0-9\-\.])(?:(?!-)[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?\.)*(?!-)[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?$/';

        return preg_match($hostnameRegex, $host) === 1;
    }

    private function validateInternationalizedHostname(string $host): bool
    {
        if ($host === '') {
            return false;
        }
        $host = rtrim($host, '.');
        $labels = explode('.', $host);
        $asciiLabels = [];

        if ($labels === false) {
            return false;
        }

        foreach ($labels as $label) {
            if ($label === '') {
                return false;
            }

            // CONTEXTJ / CONTEXTO checks
            if (
                // Greek KERAIA U+0375
                preg_match('/\x{0375}/u', $label) &&
                !preg_match('/\x{0375}[\x{0370}-\x{03FF}]/u', $label)
            ) {
                return false;
            }

            // Hebrew GERESH / GERSHAYIM U+05F3 / U+05F4
            if (preg_match('/[\x{05F3}\x{05F4}]/u', $label) &&
                !preg_match('/[\x{0590}-\x{05FF}][\x{05F3}\x{05F4}]/u', $label)
            ) {
                return false;
            }

            // Katakana middle dot U+30FB
            if (str_contains($label, "\u{30FB}") &&
                !preg_match('/[\x{30A0}-\x{30FF}]/u', $label)
            ) {
                return false;
            }

            // Arabic digit mixing
            $hasArabicIndic = preg_match('/[\x{0660}-\x{0669}]/u', $label);
            $hasExtArabicIndic = preg_match('/[\x{06F0}-\x{06F9}]/u', $label);
            if ($hasArabicIndic && $hasExtArabicIndic) {
                return false;
            }

            // Devanagari danda U+0964 / U+0965
            if (preg_match('/[\x{0964}\x{0965}]/u', $label) &&
                !preg_match('/[\x{0900}-\x{097F}]/u', $label)
            ) {
                return false;
            }

            // ZWNJ / ZWJ U+200C / U+200D
            if (preg_match('/[\x{200C}\x{200D}]/u', $label)) {
                return false;
            }

            $ascii = idn_to_ascii($label, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46);
            if ($ascii === false) {
                return false;
            }
            // DNS label length
            if (strlen($ascii) > 63) {
                return false;
            }
            // LDH rule (after IDNA)
            if (!preg_match('/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i', $ascii)) {
                return false;
            }
            $asciiLabels[] = $ascii;
        }

        // Total hostname length (ASCII)
        $asciiHost = implode('.', $asciiLabels);

        return strlen($asciiHost) <= 253;
    }

    private function validateJsonPointer(string $value): bool
    {
        // Must be empty or start with a forward slash
        if ($value !== '' && $value[0] !== '/') {
            return false;
        }

        // Split into reference tokens and check for invalid escape sequences
        $tokens = explode('/', $value);
        array_shift($tokens); // remove leading empty part due to leading slash

        foreach ($tokens as $token) {
            // "~" must only be followed by "0" or "1"
            if (preg_match('/~(?![01])/', $token)) {
                return false;
            }
        }

        return true;
    }

    private function validateRfc3339DateTime(string $value): bool
    {
        $dateTime = Rfc3339::createFromString($value);
        if (is_null($dateTime)) {
            return false;
        }

        // Compare value and date result to be equal
        return true;
    }

    private function validateUriTemplate(string $value): bool
    {
        return preg_match(
            '/^(?:[^\{\}]*|\{[a-zA-Z0-9_:%\/\.~\-\+\*]+\})*$/',
            $value
        ) === 1;
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class RefConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    /** @var Factory */
    private $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
        $this->initialiseErrorBag($this->factory);
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, '$ref')) {
            return;
        }

        try {
            $refSchema = $this->factory->getSchemaStorage()->resolveRefSchema($schema);
        } catch (\Exception $e) {
            return;
        }

        $schemaConstraint = $this->factory->createInstanceFor('schema');
        $schemaConstraint->check($value, $refSchema, $path, $i);

        if ($schemaConstraint->isValid()) {
            return;
        }

        $this->addErrors($schemaConstraint->getErrors());
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class MinLengthConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'minLength')) {
            return;
        }

        if (!is_string($value)) {
            return;
        }

        $length = mb_strlen($value);
        if ($length >= $schema->minLength) {
            return;
        }

        $this->addError(ConstraintError::LENGTH_MIN(), $path, ['minLength' => $schema->minLength, 'found' => $length]);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class AllOfConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    /** @var Factory */
    private $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
        $this->initialiseErrorBag($this->factory);
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'allOf')) {
            return;
        }

        foreach ($schema->allOf as $allOfSchema) {
            $schemaConstraint = $this->factory->createInstanceFor('schema');
            $schemaConstraint->check($value, $allOfSchema, $path, $i);

            if ($schemaConstraint->isValid()) {
                continue;
            }
            $this->addError(ConstraintError::ALL_OF(), $path);
            $this->addErrors($schemaConstraint->getErrors());
        }
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class RequiredConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'required')) {
            return;
        }

        if (!is_object($value)) {
            return;
        }

        foreach ($schema->required as $required) {
            if (property_exists($value, $required)) {
                continue;
            }

            $this->addError(ConstraintError::REQUIRED(), $this->incrementPath($path, $required), ['property' => $required]);
        }
    }

    /**
     * @todo refactor as this was only copied from UndefinedConstraint
     * Bubble down the path
     *
     * @param JsonPointer|null $path Current path
     * @param mixed            $i    What to append to the path
     */
    protected function incrementPath(?JsonPointer $path, $i): JsonPointer
    {
        $path = $path ?? new JsonPointer('');

        if ($i === null || $i === '') {
            return $path;
        }

        return $path->withPropertyPaths(array_merge($path->getPropertyPaths(), [$i]));
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Exception\ValidationException;

class AnyOfConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    /** @var Factory */
    private $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
        $this->initialiseErrorBag($this->factory);
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'anyOf')) {
            return;
        }

        foreach ($schema->anyOf as $anyOfSchema) {
            $schemaConstraint = $this->factory->createInstanceFor('schema');

            try {
                $schemaConstraint->check($value, $anyOfSchema, $path, $i);

                if ($schemaConstraint->isValid()) {
                    $this->errorBag()->reset();

                    return;
                }

                $this->addErrors($schemaConstraint->getErrors());
            } catch (ValidationException $e) {
            }
        }

        $this->addError(ConstraintError::ANY_OF(), $path);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class ItemsConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    /** @var Factory */
    private $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
        $this->initialiseErrorBag($this->factory);
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'items')) {
            return;
        }

        if (!is_array($value)) {
            return;
        }

        foreach ($value as $propertyName => $propertyValue) {
            $itemSchema = $schema->items;
            if (is_array($itemSchema)) {
                if (!array_key_exists($propertyName, $itemSchema)) {
                    continue;
                }

                $itemSchema  = $itemSchema[$propertyName];
            }
            $schemaConstraint = $this->factory->createInstanceFor('schema');
            $schemaConstraint->check($propertyValue, $itemSchema, $path, $i);
            if ($schemaConstraint->isValid()) {
                continue;
            }

            $this->addErrors($schemaConstraint->getErrors());
        }
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

class Factory extends \JsonSchema\Constraints\Factory
{
    /**
     * @var array<string, class-string>
     */
    protected $constraintMap = [
        'schema' => Draft06Constraint::class,
        'additionalProperties' => AdditionalPropertiesConstraint::class,
        'additionalItems' => AdditionalItemsConstraint::class,
        'dependencies' => DependenciesConstraint::class,
        'type' => TypeConstraint::class,
        'const' => ConstConstraint::class,
        'enum' => EnumConstraint::class,
        'uniqueItems' => UniqueItemsConstraint::class,
        'minItems' => MinItemsConstraint::class,
        'minProperties' => MinPropertiesConstraint::class,
        'maxProperties' => MaxPropertiesConstraint::class,
        'minimum' => MinimumConstraint::class,
        'maximum' => MaximumConstraint::class,
        'exclusiveMinimum' => ExclusiveMinimumConstraint::class,
        'minLength' => MinLengthConstraint::class,
        'maxLength' => MaxLengthConstraint::class,
        'maxItems' => MaxItemsConstraint::class,
        'exclusiveMaximum' => ExclusiveMaximumConstraint::class,
        'multipleOf' => MultipleOfConstraint::class,
        'required' => RequiredConstraint::class,
        'format' => FormatConstraint::class,
        'anyOf' => AnyOfConstraint::class,
        'allOf' => AllOfConstraint::class,
        'oneOf' => OneOfConstraint::class,
        'not' => NotConstraint::class,
        'contains' => ContainsConstraint::class,
        'propertyNames' => PropertiesNamesConstraint::class,
        'patternProperties' => PatternPropertiesConstraint::class,
        'pattern' => PatternConstraint::class,
        'properties' => PropertiesConstraint::class,
        'items' => ItemsConstraint::class,
        'ref' => RefConstraint::class,
    ];
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class MinItemsConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'minItems')) {
            return;
        }

        if (!is_array($value)) {
            return;
        }

        $count = count($value);
        if ($count >= $schema->minItems) {
            return;
        }

        $this->addError(ConstraintError::MIN_ITEMS(), $path, ['minItems' => $schema->minItems, 'found' => $count]);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Tool\DeepComparer;

class ConstConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'const')) {
            return;
        }

        if (DeepComparer::isEqual($value, $schema->const)) {
            return;
        }

        $this->addError(ConstraintError::CONSTANT(), $path, ['const' => $schema->const]);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class DependenciesConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    /** @var Factory */
    private $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
        $this->initialiseErrorBag($this->factory);
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'dependencies')) {
            return;
        }

        if (!is_object($value)) {
            return;
        }

        foreach ($schema->dependencies as $dependant => $dependencies) {
            if (!property_exists($value, $dependant)) {
                continue;
            }
            if ($dependencies === true) {
                continue;
            }
            if ($dependencies === false) {
                $this->addError(ConstraintError::FALSE(), $path, ['dependant' => $dependant]);
                continue;
            }

            if (is_array($dependencies)) {
                foreach ($dependencies as $dependency) {
                    if (property_exists($value, $dependant) && !property_exists($value, $dependency)) {
                        $this->addError(ConstraintError::DEPENDENCIES(), $path, ['dependant' => $dependant, 'dependency' => $dependency]);
                    }
                }
            }

            if (is_object($dependencies)) {
                $schemaConstraint = $this->factory->createInstanceFor('schema');
                $schemaConstraint->check($value, $dependencies, $path, $i);
                if (!$schemaConstraint->isValid()) {
                    $this->addErrors($schemaConstraint->getErrors());
                }
            }
        }
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class ExclusiveMaximumConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'exclusiveMaximum')) {
            return;
        }

        if (!is_numeric($value)) {
            return;
        }

        if ($value < $schema->exclusiveMaximum) {
            return;
        }

        $this->addError(ConstraintError::EXCLUSIVE_MAXIMUM(), $path, ['exclusiveMaximum' => $schema->exclusiveMaximum, 'found' => $value]);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class ExclusiveMinimumConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'exclusiveMinimum')) {
            return;
        }

        if (!is_numeric($value)) {
            return;
        }

        if ($value > $schema->exclusiveMinimum) {
            return;
        }

        $this->addError(ConstraintError::EXCLUSIVE_MINIMUM(), $path, ['exclusiveMinimum' => $schema->exclusiveMinimum, 'found' => $value]);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class NotConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    /** @var Factory */
    private $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
        $this->initialiseErrorBag($this->factory);
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'not')) {
            return;
        }

        $schemaConstraint = $this->factory->createInstanceFor('schema');
        $schemaConstraint->check($value, $schema->not, $path, $i);

        if (!$schemaConstraint->isValid()) {
            return;
        }

        $this->addError(ConstraintError::NOT(), $path);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class MaxPropertiesConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'maxProperties')) {
            return;
        }

        if (!is_object($value)) {
            return;
        }

        $count = count(get_object_vars($value));
        if ($count <= $schema->maxProperties) {
            return;
        }

        $this->addError(ConstraintError::PROPERTIES_MAX(), $path, ['maxProperties' => $schema->maxProperties, 'found' => $count]);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class MinPropertiesConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'minProperties')) {
            return;
        }

        if (!is_object($value)) {
            return;
        }

        $count = count(get_object_vars($value));
        if ($count >= $schema->minProperties) {
            return;
        }

        $this->addError(ConstraintError::PROPERTIES_MIN(), $path, ['minProperties' => $schema->minProperties, 'found' => $count]);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Tool\DeepComparer;

class UniqueItemsConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'uniqueItems')) {
            return;
        }
        if (!is_array($value)) {
            return;
        }

        if ($schema->uniqueItems !== true) {
            // If unique items not is true duplicates are allowed.
            return;
        }

        $count = count($value);
        for ($x = 0; $x < $count - 1; $x++) {
            for ($y = $x + 1; $y < $count; $y++) {
                if (DeepComparer::isEqual($value[$x], $value[$y])) {
                    $this->addError(ConstraintError::UNIQUE_ITEMS(), $path);

                    return;
                }
            }
        }
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class PropertiesConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    /** @var Factory */
    private $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
        $this->initialiseErrorBag($this->factory);
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'properties')) {
            return;
        }

        if (!is_object($value)) {
            return;
        }

        foreach ($schema->properties as $propertyName => $propertySchema) {
            $schemaConstraint = $this->factory->createInstanceFor('schema');
            if (!property_exists($value, $propertyName)) {
                continue;
            }

            $schemaConstraint->check($value->{$propertyName}, $propertySchema, $path, $i);
            if ($schemaConstraint->isValid()) {
                continue;
            }

            $this->addErrors($schemaConstraint->getErrors());
        }
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class ContainsConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    /** @var Factory */
    private $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
        $this->initialiseErrorBag($this->factory);
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'contains')) {
            return;
        }

        $properties = [];
        if (!is_array($value)) {
            return;
        }

        foreach ($value as $propertyName => $propertyValue) {
            $schemaConstraint = $this->factory->createInstanceFor('schema');

            $schemaConstraint->check($propertyValue, $schema->contains, $path, $i);
            if ($schemaConstraint->isValid()) {
                return;
            }
        }

        $this->addError(ConstraintError::CONTAINS(), $path, ['contains' => $schema->contains]);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class MaxItemsConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'maxItems')) {
            return;
        }

        if (!is_array($value)) {
            return;
        }

        $count = count($value);
        if ($count <= $schema->maxItems) {
            return;
        }

        $this->addError(ConstraintError::MAX_ITEMS(), $path, ['maxItems' => $schema->maxItems, 'found' => $count]);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class TypeConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'type')) {
            return;
        }

        $schemaTypes = (array) $schema->type;
        $valueType = strtolower(gettype($value));
        // All specific number types are a number
        $valueIsNumber = $valueType === 'double' ||  $valueType === 'integer';
        // A float with zero fractional part is an integer
        $isInteger = $valueIsNumber && fmod($value, 1.0) === 0.0;

        foreach ($schemaTypes as $type) {
            if ($valueType === $type) {
                return;
            }

            if ($type === 'number' && $valueIsNumber) {
                return;
            }
            if ($type === 'integer' && $isInteger) {
                return;
            }
        }

        $this->addError(ConstraintError::TYPE(), $path, ['found' => $valueType, 'expected' => implode(', ', $schemaTypes)]);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class PropertiesNamesConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    /** @var Factory */
    private $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
        $this->initialiseErrorBag($this->factory);
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'propertyNames')) {
            return;
        }

        if (!is_object($value)) {
            return;
        }
        if ($schema->propertyNames === true) {
            return;
        }

        $propertyNames = get_object_vars($value);

        if ($schema->propertyNames === false) {
            foreach ($propertyNames as $propertyName => $_) {
                $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'false', 'name' => $propertyName]);
            }

            return;
        }

        if (property_exists($schema->propertyNames, 'maxLength')) {
            foreach ($propertyNames as $propertyName => $_) {
                $length = mb_strlen($propertyName);
                if ($length > $schema->propertyNames->maxLength) {
                    $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'maxLength', 'length' => $length, 'name' => $propertyName]);
                }
            }
        }

        if (property_exists($schema->propertyNames, 'pattern')) {
            foreach ($propertyNames as $propertyName => $_) {
                if (!preg_match('/' . str_replace('/', '\/', $schema->propertyNames->pattern) . '/', $propertyName)) {
                    $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'pattern', 'name' => $propertyName]);
                }
            }
        }

        if (property_exists($schema->propertyNames, 'const')) {
            foreach ($propertyNames as $propertyName => $_) {
                if ($propertyName !== $schema->propertyNames->const) {
                    $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'const', 'name' => $propertyName]);
                }
            }
        }

        if (property_exists($schema->propertyNames, 'enum')) {
            $diff = array_diff(array_keys($propertyNames), $schema->propertyNames->enum);
            foreach ($diff as $propertyName) {
                $this->addError(ConstraintError::PROPERTY_NAMES(), $path, ['propertyNames' => $schema->propertyNames, 'violating' => 'enum', 'name' => $propertyName]);
            }
        }
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class OneOfConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    /** @var Factory */
    private $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
        $this->initialiseErrorBag($this->factory);
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'oneOf')) {
            return;
        }

        $matchedSchema = 0;
        foreach ($schema->oneOf as $oneOfSchema) {
            $schemaConstraint = $this->factory->createInstanceFor('schema');
            $schemaConstraint->check($value, $oneOfSchema, $path, $i);

            if ($schemaConstraint->isValid()) {
                $matchedSchema++;
                continue;
            }

            $this->addErrors($schemaConstraint->getErrors());
        }

        if ($matchedSchema !== 1) {
            $this->addError(ConstraintError::ONE_OF(), $path);
        } else {
            $this->errorBag()->reset();
        }
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class AdditionalPropertiesConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    /** @var Factory */
    private $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
        $this->initialiseErrorBag($this->factory);
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'additionalProperties')) {
            return;
        }

        if ($schema->additionalProperties === true) {
            return;
        }

        if (!is_object($value)) {
            return;
        }

        $additionalProperties = get_object_vars($value);

        if (isset($schema->properties)) {
            $additionalProperties = array_diff_key($additionalProperties, (array) $schema->properties);
        }

        if (isset($schema->patternProperties)) {
            $patterns = array_keys(get_object_vars($schema->patternProperties));

            foreach ($additionalProperties as $key => $_) {
                foreach ($patterns as $pattern) {
                    if (preg_match($this->createPregMatchPattern($pattern), (string) $key)) {
                        unset($additionalProperties[$key]);
                        break;
                    }
                }
            }
        }

        if (is_object($schema->additionalProperties)) {
            foreach ($additionalProperties as $key => $additionalPropertiesValue) {
                $schemaConstraint = $this->factory->createInstanceFor('schema');
                $schemaConstraint->check($additionalPropertiesValue, $schema->additionalProperties, $path, $i); // @todo increment path
                if ($schemaConstraint->isValid()) {
                    unset($additionalProperties[$key]);
                }
            }
        }

        foreach ($additionalProperties as $key => $additionalPropertiesValue) {
            $this->addError(ConstraintError::ADDITIONAL_PROPERTIES(), $path, ['found' => $additionalPropertiesValue]);
        }
    }

    private function createPregMatchPattern(string $pattern): string
    {
        $replacements = [
//            '\D' => '[^0-9]',
//            '\d' => '[0-9]',
            '\p{digit}' => '\p{Nd}',
//            '\w' => '[A-Za-z0-9_]',
//            '\W' => '[^A-Za-z0-9_]',
//            '\s' => '[\s\x{200B}]' // Explicitly include zero width white space,
            '\p{Letter}' => '\p{L}', // Map ECMA long property name to PHP (PCRE) Unicode property abbreviations
        ];

        $pattern = str_replace(
            array_keys($replacements),
            array_values($replacements),
            $pattern
        );

        return '/' . str_replace('/', '\/', $pattern) . '/u';
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class MinimumConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'minimum')) {
            return;
        }

        if (!is_numeric($value)) {
            return;
        }

        if ($value >= $schema->minimum) {
            return;
        }

        $this->addError(ConstraintError::MINIMUM(), $path, ['minimum' => $schema->minimum, 'found' => $value]);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class PatternConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    /** @var Factory */
    private $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
        $this->initialiseErrorBag($this->factory);
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'pattern')) {
            return;
        }

        if (!is_string($value)) {
            return;
        }

        $matchPattern = $this->createPregMatchPattern($schema->pattern);
        if (preg_match($matchPattern, $value) === 1) {
            return;
        }

        $this->addError(ConstraintError::PATTERN(), $path, ['found' => $value, 'pattern' => $schema->pattern]);
    }

    private function createPregMatchPattern(string $pattern): string
    {
        $replacements = [
            '\D' => '[^0-9]',
            '\d' => '[0-9]',
            '\p{digit}' => '[0-9]',
            '\w' => '[A-Za-z0-9_]',
            '\W' => '[^A-Za-z0-9_]',
            '\s' => '[\s\x{200B}]', // Explicitly include zero width white space
            '\p{Letter}' => '\p{L}', // Map ECMA long property name to PHP (PCRE) Unicode property abbreviations
        ];

        $pattern = str_replace(
            array_keys($replacements),
            array_values($replacements),
            $pattern
        );

        return '/' . str_replace('/', '\/', $pattern) . '/u';
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class PatternPropertiesConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    /** @var Factory */
    private $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
        $this->initialiseErrorBag($this->factory);
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'patternProperties')) {
            return;
        }

        if (!is_object($value)) {
            return;
        }

        $properties = get_object_vars($value);

        foreach ($properties as $propertyName => $propertyValue) {
            foreach ($schema->patternProperties as $patternPropertyRegex => $patternPropertySchema) {
                $matchPattern = $this->createPregMatchPattern($patternPropertyRegex);
                if (preg_match($matchPattern, (string) $propertyName)) {
                    $schemaConstraint = $this->factory->createInstanceFor('schema');
                    $schemaConstraint->check($propertyValue, $patternPropertySchema, $path, $i);
                    if ($schemaConstraint->isValid()) {
                        continue;
                    }

                    $this->addErrors($schemaConstraint->getErrors());
                }
            }
        }
    }

    private function createPregMatchPattern(string $pattern): string
    {
        $replacements = [
//            '\D' => '[^0-9]',
            '\d' => '[0-9]',
            '\p{digit}' => '[0-9]',
//            '\w' => '[A-Za-z0-9_]',
//            '\W' => '[^A-Za-z0-9_]',
//            '\s' => '[\s\x{200B}]' // Explicitly include zero width white space
            '\p{Letter}' => '\p{L}', // Map ECMA long property name to PHP (PCRE) Unicode property abbreviations
        ];

        $pattern = str_replace(
            array_keys($replacements),
            array_values($replacements),
            $pattern
        );

        return '/' . str_replace('/', '\/', $pattern) . '/u';
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class MaxLengthConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'maxLength')) {
            return;
        }

        if (!is_string($value)) {
            return;
        }

        $length = mb_strlen($value);
        if ($length <= $schema->maxLength) {
            return;
        }

        $this->addError(ConstraintError::LENGTH_MAX(), $path, ['maxLength' => $schema->maxLength, 'found' => $length]);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class MaximumConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'maximum')) {
            return;
        }

        if (!is_numeric($value)) {
            return;
        }

        if ($value <= $schema->maximum) {
            return;
        }

        $this->addError(ConstraintError::MAXIMUM(), $path, ['maximum' => $schema->maximum, 'found' => $value]);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Tool\DeepComparer;

class EnumConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'enum')) {
            return;
        }

        foreach ($schema->enum as $enumCase) {
            if (DeepComparer::isEqual($value, $enumCase)) {
                return;
            }

            if (is_numeric($value) && is_numeric($enumCase) && DeepComparer::isEqual((float) $value, (float) $enumCase)) {
                return;
            }
        }

        $this->addError(ConstraintError::ENUM(), $path, ['enum' => $schema->enum]);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\Constraint;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\SchemaStorage;
use JsonSchema\Uri\UriRetriever;

class Draft06Constraint extends Constraint
{
    public function __construct(?\JsonSchema\Constraints\Factory $factory = null)
    {
        parent::__construct(new Factory(
            $factory ? $factory->getSchemaStorage() : new SchemaStorage(),
            $factory ? $factory->getUriRetriever() : new UriRetriever(),
            $factory ? $factory->getConfig() : Constraint::CHECK_MODE_NORMAL
        ));
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (is_bool($schema)) {
            if ($schema === false) {
                $this->addError(ConstraintError::FALSE(), $path, []);
            }

            return;
        }

        // Apply defaults
        $this->checkForKeyword('ref', $value, $schema, $path, $i);
        $this->checkForKeyword('required', $value, $schema, $path, $i);
        $this->checkForKeyword('contains', $value, $schema, $path, $i);
        $this->checkForKeyword('properties', $value, $schema, $path, $i);
        $this->checkForKeyword('propertyNames', $value, $schema, $path, $i);
        $this->checkForKeyword('patternProperties', $value, $schema, $path, $i);
        $this->checkForKeyword('type', $value, $schema, $path, $i);
        $this->checkForKeyword('not', $value, $schema, $path, $i);
        $this->checkForKeyword('dependencies', $value, $schema, $path, $i);
        $this->checkForKeyword('allOf', $value, $schema, $path, $i);
        $this->checkForKeyword('anyOf', $value, $schema, $path, $i);
        $this->checkForKeyword('oneOf', $value, $schema, $path, $i);

        $this->checkForKeyword('additionalProperties', $value, $schema, $path, $i);
        $this->checkForKeyword('items', $value, $schema, $path, $i);
        $this->checkForKeyword('additionalItems', $value, $schema, $path, $i);
        $this->checkForKeyword('uniqueItems', $value, $schema, $path, $i);
        $this->checkForKeyword('minItems', $value, $schema, $path, $i);
        $this->checkForKeyword('minProperties', $value, $schema, $path, $i);
        $this->checkForKeyword('maxProperties', $value, $schema, $path, $i);
        $this->checkForKeyword('minimum', $value, $schema, $path, $i);
        $this->checkForKeyword('maximum', $value, $schema, $path, $i);
        $this->checkForKeyword('minLength', $value, $schema, $path, $i);
        $this->checkForKeyword('exclusiveMinimum', $value, $schema, $path, $i);
        $this->checkForKeyword('maxItems', $value, $schema, $path, $i);
        $this->checkForKeyword('maxLength', $value, $schema, $path, $i);
        $this->checkForKeyword('exclusiveMaximum', $value, $schema, $path, $i);
        $this->checkForKeyword('enum', $value, $schema, $path, $i);
        $this->checkForKeyword('const', $value, $schema, $path, $i);
        $this->checkForKeyword('multipleOf', $value, $schema, $path, $i);
        $this->checkForKeyword('format', $value, $schema, $path, $i);
        $this->checkForKeyword('pattern', $value, $schema, $path, $i);
    }

    /**
     * @param mixed $value
     * @param mixed $schema
     * @param mixed $i
     */
    protected function checkForKeyword(string $keyword, $value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        $validator = $this->factory->createInstanceFor($keyword);
        $validator->check($value, $schema, $path, $i);

        $this->addErrors($validator->getErrors());
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class AdditionalItemsConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    /** @var Factory */
    private $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
        $this->initialiseErrorBag($this->factory);
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'additionalItems')) {
            return;
        }

        if ($schema->additionalItems === true) {
            return;
        }
        if ($schema->additionalItems === false && !property_exists($schema, 'items')) {
            return;
        }

        if (!is_array($value)) {
            return;
        }
        if (!property_exists($schema, 'items')) {
            return;
        }
        if (property_exists($schema, 'items') && is_object($schema->items)) {
            return;
        }

        $additionalItems = array_diff_key($value, property_exists($schema, 'items') ? $schema->items : []);

        foreach ($additionalItems as $propertyName => $propertyValue) {
            $schemaConstraint = $this->factory->createInstanceFor('schema');
            $schemaConstraint->check($propertyValue, $schema->additionalItems, $path, $i);

            if ($schemaConstraint->isValid()) {
                continue;
            }

            $this->addError(ConstraintError::ADDITIONAL_ITEMS(), $path, ['item' => $i, 'property' => $propertyName, 'additionalItems' => $schema->additionalItems]);
        }
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;

class MultipleOfConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'multipleOf')) {
            return;
        }

        if (!is_int($schema->multipleOf) && !is_float($schema->multipleOf) && $schema->multipleOf <= 0.0) {
            return;
        }

        if (!is_int($value) && !is_float($value)) {
            return;
        }

        if ($this->isMultipleOf($value, $schema->multipleOf)) {
            return;
        }

        $this->addError(ConstraintError::MULTIPLE_OF(), $path, ['multipleOf' => $schema->multipleOf, 'found' => $value]);
    }

    /**
     * @param int|float $number1
     * @param int|float $number2
     */
    private function isMultipleOf($number1, $number2): bool
    {
        $modulus = ($number1 - round($number1 / $number2) * $number2);
        $precision = 0.0000000001;

        return -$precision < $modulus && $modulus < $precision;
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\Drafts\Draft06;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\ConstraintInterface;
use JsonSchema\Constraints\Factory;
use JsonSchema\Entity\ErrorBagProxy;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Rfc3339;
use JsonSchema\Tool\Validator\RelativeReferenceValidator;
use JsonSchema\Tool\Validator\UriValidator;

class FormatConstraint implements ConstraintInterface
{
    use ErrorBagProxy;

    public function __construct(?Factory $factory = null)
    {
        $this->initialiseErrorBag($factory ?: new Factory());
    }

    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!property_exists($schema, 'format')) {
            return;
        }

        if (!is_string($value)) {
            return;
        }

        switch ($schema->format) {
            case 'date':
                if (!$this->validateDateTime($value, 'Y-m-d')) {
                    $this->addError(ConstraintError::FORMAT_DATE(), $path, ['date' => $value, 'format' => $schema->format]);
                }
                break;
            case 'time':
                if (!$this->validateDateTime($value, 'H:i:s')) {
                    $this->addError(ConstraintError::FORMAT_TIME(), $path, ['time' => $value, 'format' => $schema->format]);
                }
                break;
            case 'date-time':
                if (!$this->validateRfc3339DateTime($value)) {
                    $this->addError(ConstraintError::FORMAT_DATE_TIME(), $path, ['dateTime' => $value, 'format' => $schema->format]);
                }
                break;
            case 'utc-millisec':
                if (!$this->validateDateTime($value, 'U')) {
                    $this->addError(ConstraintError::FORMAT_DATE_UTC(), $path, ['value' => $value, 'format' => $schema->format]);
                }
                break;
            case 'regex':
                if (!$this->validateRegex($value)) {
                    $this->addError(ConstraintError::FORMAT_REGEX(), $path, ['value' => $value, 'format' => $schema->format]);
                }
                break;
            case 'ip-address':
            case 'ipv4':
                if (filter_var($value, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4) === null) {
                    $this->addError(ConstraintError::FORMAT_IP(), $path, ['format' => $schema->format]);
                }
                break;
            case 'ipv6':
                if (filter_var($value, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV6) === null) {
                    $this->addError(ConstraintError::FORMAT_IP(), $path, ['format' => $schema->format]);
                }
                break;
            case 'color':
                if (!$this->validateColor($value)) {
                    $this->addError(ConstraintError::FORMAT_COLOR(), $path, ['format' => $schema->format]);
                }
                break;
            case 'style':
                if (!$this->validateStyle($value)) {
                    $this->addError(ConstraintError::FORMAT_STYLE(), $path, ['format' => $schema->format]);
                }
                break;
            case 'phone':
                if (!$this->validatePhone($value)) {
                    $this->addError(ConstraintError::FORMAT_PHONE(), $path, ['format' => $schema->format]);
                }
                break;
            case 'uri':
                if (!UriValidator::isValid($value)) {
                    $this->addError(ConstraintError::FORMAT_URL(), $path, ['format' => $schema->format]);
                }
                break;

            case 'uriref':
            case 'uri-reference':
                if (!(UriValidator::isValid($value) || RelativeReferenceValidator::isValid($value))) {
                    $this->addError(ConstraintError::FORMAT_URL(), $path, ['format' => $schema->format]);
                }
                break;
            case 'uri-template':
                if (!$this->validateUriTemplate($value)) {
                    $this->addError(ConstraintError::FORMAT_URI_TEMPLATE(), $path, ['format' => $schema->format]);
                }
                break;

            case 'email':
                if (filter_var($value, FILTER_VALIDATE_EMAIL, FILTER_NULL_ON_FAILURE | FILTER_FLAG_EMAIL_UNICODE) === null) {
                    $this->addError(ConstraintError::FORMAT_EMAIL(), $path, ['format' => $schema->format]);
                }
                break;
            case 'host-name':
            case 'hostname':
                if (!$this->validateHostname($value)) {
                    $this->addError(ConstraintError::FORMAT_HOSTNAME(), $path, ['format' => $schema->format]);
                }
                break;
            case 'json-pointer':
                if (!$this->validateJsonPointer($value)) {
                    $this->addError(ConstraintError::FORMAT_JSON_POINTER(), $path, ['format' => $schema->format]);
                }
                break;
            default:
                break;
        }
    }

    private function validateDateTime(string $datetime, string $format): bool
    {
        $dt = \DateTime::createFromFormat($format, $datetime);

        if (!$dt) {
            return false;
        }

        return $datetime === $dt->format($format);
    }

    private function validateRegex(string $regex): bool
    {
        return preg_match(self::jsonPatternToPhpRegex($regex), '') !== false;
    }

    /**
     * Transform a JSON pattern into a PCRE regex
     */
    private static function jsonPatternToPhpRegex(string $pattern): string
    {
        return '~' . str_replace('~', '\\~', $pattern) . '~u';
    }

    private function validateColor(string $color): bool
    {
        if (in_array(strtolower($color), ['aqua', 'black', 'blue', 'fuchsia',
            'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'orange', 'purple',
            'red', 'silver', 'teal', 'white', 'yellow'])) {
            return true;
        }

        return preg_match('/^#([a-f0-9]{3}|[a-f0-9]{6})$/i', $color) !== false;
    }

    private function validateStyle(string $style): bool
    {
        $properties     = explode(';', rtrim($style, ';'));
        $invalidEntries = preg_grep('/^\s*[-a-z]+\s*:\s*.+$/i', $properties, PREG_GREP_INVERT);

        return empty($invalidEntries);
    }

    private function validatePhone(string $phone): bool
    {
        return preg_match('/^\+?(\(\d{3}\)|\d{3}) \d{3} \d{4}$/', $phone) !== false;
    }

    private function validateHostname(string $host): bool
    {
        $hostnameRegex = '/^(?!-)(?!.*?[^A-Za-z0-9\-\.])(?:(?!-)[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?\.)*(?!-)[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?$/';

        return preg_match($hostnameRegex, $host) === 1;
    }

    private function validateJsonPointer(string $value): bool
    {
        // Must be empty or start with a forward slash
        if ($value !== '' && $value[0] !== '/') {
            return false;
        }

        // Split into reference tokens and check for invalid escape sequences
        $tokens = explode('/', $value);
        array_shift($tokens); // remove leading empty part due to leading slash

        foreach ($tokens as $token) {
            // "~" must only be followed by "0" or "1"
            if (preg_match('/~(?![01])/', $token)) {
                return false;
            }
        }

        return true;
    }

    private function validateRfc3339DateTime(string $value): bool
    {
        $dateTime = Rfc3339::createFromString($value);
        if (is_null($dateTime)) {
            return false;
        }

        // Compare value and date result to be equal
        return true;
    }

    private function validateUriTemplate(string $value): bool
    {
        return preg_match(
            '/^(?:[^\{\}]*|\{[a-zA-Z0-9_:%\/\.~\-\+\*]+\})*$/',
            $value
        ) === 1;
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints;

use const JSON_ERROR_NONE;
use JsonSchema\ConstraintError;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Exception\InvalidArgumentException;
use JsonSchema\Exception\ValidationException;
use JsonSchema\Validator;

/**
 * A more basic constraint definition - used for the public
 * interface to avoid exposing library internals.
 */
class BaseConstraint
{
    /**
     * @var array Errors
     */
    protected $errors = [];

    /**
     * @var int All error types which have occurred
     * @phpstan-var int-mask-of<Validator::ERROR_*>
     */
    protected $errorMask = Validator::ERROR_NONE;

    /**
     * @var Factory
     */
    protected $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
    }

    public function addError(ConstraintError $constraint, ?JsonPointer $path = null, array $more = []): void
    {
        $message = $constraint->getMessage();
        $name = $constraint->getValue();
        $error = [
            'property' => $this->convertJsonPointerIntoPropertyPath($path ?: new JsonPointer('')),
            'pointer' => ltrim((string) ($path ?: new JsonPointer('')), '#'),
            'message' => ucfirst(vsprintf($message, array_map(static function ($val) {
                if (is_scalar($val)) {
                    return is_bool($val) ? var_export($val, true) : $val;
                }

                return json_encode($val);
            }, array_values($more)))),
            'constraint' => [
                'name' => $name,
                'params' => $more
            ],
            'context' => $this->factory->getErrorContext(),
        ];

        if ($this->factory->getConfig(Constraint::CHECK_MODE_EXCEPTIONS)) {
            throw new ValidationException(sprintf('Error validating %s: %s', $error['pointer'], $error['message']));
        }

        $this->errors[] = $error;
        $this->errorMask |= $error['context'];
    }

    public function addErrors(array $errors): void
    {
        if ($errors) {
            $this->errors = array_merge($this->errors, $errors);
            $errorMask = &$this->errorMask;
            array_walk($errors, static function ($error) use (&$errorMask) {
                if (isset($error['context'])) {
                    $errorMask |= $error['context'];
                }
            });
        }
    }

    /**
     * @phpstan-param int-mask-of<Validator::ERROR_*> $errorContext
     */
    public function getErrors(int $errorContext = Validator::ERROR_ALL): array
    {
        if ($errorContext === Validator::ERROR_ALL) {
            return $this->errors;
        }

        return array_filter($this->errors, static function ($error) use ($errorContext) {
            return (bool) ($errorContext & $error['context']);
        });
    }

    /**
     * @phpstan-param int-mask-of<Validator::ERROR_*> $errorContext
     */
    public function numErrors(int $errorContext = Validator::ERROR_ALL): int
    {
        if ($errorContext === Validator::ERROR_ALL) {
            return count($this->errors);
        }

        return count($this->getErrors($errorContext));
    }

    public function isValid(): bool
    {
        return !$this->getErrors();
    }

    /**
     * Clears any reported errors. Should be used between
     * multiple validation checks.
     */
    public function reset(): void
    {
        $this->errors = [];
        $this->errorMask = Validator::ERROR_NONE;
    }

    /**
     * Get the error mask
     *
     * @phpstan-return int-mask-of<Validator::ERROR_*>
     */
    public function getErrorMask(): int
    {
        return $this->errorMask;
    }

    /**
     * Recursively cast an associative array to an object
     */
    public static function arrayToObjectRecursive(array $array): object
    {
        $json = json_encode($array);
        if (json_last_error() !== JSON_ERROR_NONE) {
            $message = 'Unable to encode schema array as JSON';
            if (function_exists('json_last_error_msg')) {
                $message .= ': ' . json_last_error_msg();
            }
            throw new InvalidArgumentException($message);
        }

        return (object) json_decode($json, false);
    }

    /**
     * Transform a JSON pattern into a PCRE regex
     */
    public static function jsonPatternToPhpRegex(string $pattern): string
    {
        return '~' . str_replace('~', '\\~', $pattern) . '~u';
    }

    protected function convertJsonPointerIntoPropertyPath(JsonPointer $pointer): string
    {
        $result = array_map(
            static function ($path) {
                return sprintf(is_numeric($path) ? '[%d]' : '.%s', $path);
            },
            $pointer->getPropertyPaths()
        );

        return trim(implode('', $result), '.');
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\ConstraintError;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Exception\InvalidArgumentException;
use UnexpectedValueException as StandardUnexpectedValueException;

/**
 * The TypeConstraint Constraints, validates an element against a given type
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 */
class TypeConstraint extends Constraint
{
    /**
     * @var array|string[] type wordings for validation error messages
     */
    public static $wording = [
        'integer' => 'an integer',
        'number'  => 'a number',
        'boolean' => 'a boolean',
        'object'  => 'an object',
        'array'   => 'an array',
        'string'  => 'a string',
        'null'    => 'a null',
        'any'     => null, // validation of 'any' is always true so is not needed in message wording
        0         => null, // validation of a false-y value is always true, so not needed as well
    ];

    /**
     * {@inheritdoc}
     */
    public function check(&$value = null, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        $type = isset($schema->type) ? $schema->type : null;
        $isValid = false;
        $coerce = $this->factory->getConfig(self::CHECK_MODE_COERCE_TYPES);
        $earlyCoerce = $this->factory->getConfig(self::CHECK_MODE_EARLY_COERCE);
        $wording = [];

        if (is_array($type)) {
            $this->validateTypesArray($value, $type, $wording, $isValid, $path, $coerce && $earlyCoerce);
            if (!$isValid && $coerce && !$earlyCoerce) {
                $this->validateTypesArray($value, $type, $wording, $isValid, $path, true);
            }
        } elseif (is_object($type)) {
            $this->checkUndefined($value, $type, $path);

            return;
        } else {
            $isValid = $this->validateType($value, $type, $coerce && $earlyCoerce);
            if (!$isValid && $coerce && !$earlyCoerce) {
                $isValid = $this->validateType($value, $type, true);
            }
        }

        if ($isValid === false) {
            if (!is_array($type)) {
                $this->validateTypeNameWording($type);
                $wording[] = self::$wording[$type];
            }
            $this->addError(ConstraintError::TYPE(), $path, [
                    'found' => gettype($value),
                    'expected' => $this->implodeWith($wording, ', ', 'or')
            ]);
        }
    }

    /**
     * Validates the given $value against the array of types in $type. Sets the value
     * of $isValid to true, if at least one $type mateches the type of $value or the value
     * passed as $isValid is already true.
     *
     * @param mixed        $value             Value to validate
     * @param array        $type              TypeConstraints to check against
     * @param array        $validTypesWording An array of wordings of the valid types of the array $type
     * @param bool         $isValid           The current validation value
     * @param ?JsonPointer $path
     * @param bool         $coerce
     */
    protected function validateTypesArray(&$value, array $type, &$validTypesWording, &$isValid, $path, $coerce = false)
    {
        foreach ($type as $tp) {
            // already valid, so no need to waste cycles looping over everything
            if ($isValid) {
                return;
            }

            // $tp can be an object, if it's a schema instead of a simple type, validate it
            // with a new type constraint
            if (is_object($tp)) {
                if (!$isValid) {
                    $validator = $this->factory->createInstanceFor('type');
                    $subSchema = new \stdClass();
                    $subSchema->type = $tp;
                    $validator->check($value, $subSchema, $path, null);
                    $error = $validator->getErrors();
                    $isValid = !(bool) $error;
                    $validTypesWording[] = self::$wording['object'];
                }
            } else {
                $this->validateTypeNameWording($tp);
                $validTypesWording[] = self::$wording[$tp];
                if (!$isValid) {
                    $isValid = $this->validateType($value, $tp, $coerce);
                }
            }
        }
    }

    /**
     * Implodes the given array like implode() with turned around parameters and with the
     * difference, that, if $listEnd isn't false, the last element delimiter is $listEnd instead of
     * $delimiter.
     *
     * @param array  $elements  The elements to implode
     * @param string $delimiter The delimiter to use
     * @param bool   $listEnd   The last delimiter to use (defaults to $delimiter)
     *
     * @return string
     */
    protected function implodeWith(array $elements, $delimiter = ', ', $listEnd = false)
    {
        if ($listEnd === false || !isset($elements[1])) {
            return implode($delimiter, $elements);
        }
        $lastElement  = array_slice($elements, -1);
        $firsElements = join($delimiter, array_slice($elements, 0, -1));
        $implodedElements = array_merge([$firsElements], $lastElement);

        return join(" $listEnd ", $implodedElements);
    }

    /**
     * Validates the given $type, if there's an associated self::$wording. If not, throws an
     * exception.
     *
     * @param string $type The type to validate
     *
     * @throws StandardUnexpectedValueException
     */
    protected function validateTypeNameWording($type)
    {
        if (!array_key_exists($type, self::$wording)) {
            throw new StandardUnexpectedValueException(
                sprintf(
                    'No wording for %s available, expected wordings are: [%s]',
                    var_export($type, true),
                    implode(', ', array_filter(self::$wording)))
            );
        }
    }

    /**
     * Verifies that a given value is of a certain type
     *
     * @param mixed  $value Value to validate
     * @param string $type  TypeConstraint to check against
     *
     * @throws InvalidArgumentException
     *
     * @return bool
     */
    protected function validateType(&$value, $type, $coerce = false)
    {
        //mostly the case for inline schema
        if (!$type) {
            return true;
        }

        if ('any' === $type) {
            return true;
        }

        if ('object' === $type) {
            return $this->getTypeCheck()->isObject($value);
        }

        if ('array' === $type) {
            if ($coerce) {
                $value = $this->toArray($value);
            }

            return $this->getTypeCheck()->isArray($value);
        }

        if ('integer' === $type) {
            if ($coerce) {
                $value = $this->toInteger($value);
            }

            return is_int($value);
        }

        if ('number' === $type) {
            if ($coerce) {
                $value = $this->toNumber($value);
            }

            return is_numeric($value) && !is_string($value);
        }

        if ('boolean' === $type) {
            if ($coerce) {
                $value = $this->toBoolean($value);
            }

            return is_bool($value);
        }

        if ('string' === $type) {
            if ($coerce) {
                $value = $this->toString($value);
            }

            return is_string($value);
        }

        if ('null' === $type) {
            if ($coerce) {
                $value = $this->toNull($value);
            }

            return is_null($value);
        }

        throw new InvalidArgumentException((is_object($value) ? 'object' : $value) . ' is an invalid type for ' . $type);
    }

    /**
     * Converts a value to boolean. For example, "true" becomes true.
     *
     * @param mixed $value The value to convert to boolean
     *
     * @return bool|mixed
     */
    protected function toBoolean($value)
    {
        if ($value === 1 || $value === 'true') {
            return true;
        }
        if (is_null($value) || $value === 0 || $value === 'false') {
            return false;
        }
        if ($this->getTypeCheck()->isArray($value) && count($value) === 1) {
            return $this->toBoolean(reset($value));
        }

        return $value;
    }

    /**
     * Converts a value to a number. For example, "4.5" becomes 4.5.
     *
     * @param mixed $value the value to convert to a number
     *
     * @return int|float|mixed
     */
    protected function toNumber($value)
    {
        if (is_numeric($value)) {
            return $value + 0; // cast to number
        }
        if (is_bool($value) || is_null($value)) {
            return (int) $value;
        }
        if ($this->getTypeCheck()->isArray($value) && count($value) === 1) {
            return $this->toNumber(reset($value));
        }

        return $value;
    }

    /**
     * Converts a value to an integer. For example, "4" becomes 4.
     *
     * @param mixed $value
     *
     * @return int|mixed
     */
    protected function toInteger($value)
    {
        $numberValue = $this->toNumber($value);
        if (is_numeric($numberValue) && (int) $numberValue == $numberValue) {
            return (int) $numberValue; // cast to number
        }

        return $value;
    }

    /**
     * Converts a value to an array containing that value. For example, [4] becomes 4.
     *
     * @param mixed $value
     *
     * @return array|mixed
     */
    protected function toArray($value)
    {
        if (is_scalar($value) || is_null($value)) {
            return [$value];
        }

        return $value;
    }

    /**
     * Convert a value to a string representation of that value. For example, null becomes "".
     *
     * @param mixed $value
     *
     * @return string|mixed
     */
    protected function toString($value)
    {
        if (is_numeric($value)) {
            return "$value";
        }
        if ($value === true) {
            return 'true';
        }
        if ($value === false) {
            return 'false';
        }
        if (is_null($value)) {
            return '';
        }
        if ($this->getTypeCheck()->isArray($value) && count($value) === 1) {
            return $this->toString(reset($value));
        }

        return $value;
    }

    /**
     * Convert a value to a null. For example, 0 becomes null.
     *
     * @param mixed $value
     *
     * @return null|mixed
     */
    protected function toNull($value)
    {
        if ($value === 0 || $value === false || $value === '') {
            return null;
        }
        if ($this->getTypeCheck()->isArray($value) && count($value) === 1) {
            return $this->toNull(reset($value));
        }

        return $value;
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\ConstraintError;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Tool\DeepComparer;

/**
 * The CollectionConstraint Constraints, validates an array against a given schema
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 */
class CollectionConstraint extends Constraint
{
    /**
     * {@inheritdoc}
     */
    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        // Verify minItems
        if (isset($schema->minItems) && count($value) < $schema->minItems) {
            $this->addError(ConstraintError::MIN_ITEMS(), $path, ['minItems' => $schema->minItems, 'found' => count($value)]);
        }

        // Verify maxItems
        if (isset($schema->maxItems) && count($value) > $schema->maxItems) {
            $this->addError(ConstraintError::MAX_ITEMS(), $path, ['maxItems' => $schema->maxItems, 'found' => count($value)]);
        }

        // Verify uniqueItems
        if (isset($schema->uniqueItems) && $schema->uniqueItems) {
            $count = count($value);
            for ($x = 0; $x < $count - 1; $x++) {
                for ($y = $x + 1; $y < $count; $y++) {
                    if (DeepComparer::isEqual($value[$x], $value[$y])) {
                        $this->addError(ConstraintError::UNIQUE_ITEMS(), $path);
                        break 2;
                    }
                }
            }
        }

        $this->validateItems($value, $schema, $path, $i);
    }

    /**
     * Validates the items
     *
     * @param array     $value
     * @param \stdClass $schema
     * @param string    $i
     */
    protected function validateItems(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (\is_null($schema) || !isset($schema->items)) {
            return;
        }

        if ($schema->items === true) {
            return;
        }

        if (is_object($schema->items)) {
            // just one type definition for the whole array
            foreach ($value as $k => &$v) {
                $initErrors = $this->getErrors();

                // First check if its defined in "items"
                $this->checkUndefined($v, $schema->items, $path, $k);

                // Recheck with "additionalItems" if the first test fails
                if (count($initErrors) < count($this->getErrors()) && (isset($schema->additionalItems) && $schema->additionalItems !== false)) {
                    $secondErrors = $this->getErrors();
                    $this->checkUndefined($v, $schema->additionalItems, $path, $k);
                }

                // Reset errors if needed
                if (isset($secondErrors) && count($secondErrors) < count($this->getErrors())) {
                    $this->errors = $secondErrors;
                } elseif (isset($secondErrors) && count($secondErrors) === count($this->getErrors())) {
                    $this->errors = $initErrors;
                }
            }
            unset($v); /* remove dangling reference to prevent any future bugs
                        * caused by accidentally using $v elsewhere */
        } else {
            // Defined item type definitions
            foreach ($value as $k => &$v) {
                if (array_key_exists($k, $schema->items)) {
                    $this->checkUndefined($v, $schema->items[$k], $path, $k);
                } else {
                    // Additional items
                    if (property_exists($schema, 'additionalItems')) {
                        if ($schema->additionalItems !== false) {
                            $this->checkUndefined($v, $schema->additionalItems, $path, $k);
                        } else {
                            $this->addError(
                                ConstraintError::ADDITIONAL_ITEMS(),
                                $path,
                                [
                                    'item' => $i,
                                    'property' => $k,
                                    'additionalItems' => $schema->additionalItems
                                ]
                            );
                        }
                    } else {
                        // Should be valid against an empty schema
                        $this->checkUndefined($v, new \stdClass(), $path, $k);
                    }
                }
            }
            unset($v); /* remove dangling reference to prevent any future bugs
                        * caused by accidentally using $v elsewhere */

            // Treat when we have more schema definitions than values, not for empty arrays
            if (count($value) > 0) {
                for ($k = count($value); $k < count($schema->items); $k++) {
                    $undefinedInstance = $this->factory->createInstanceFor('undefined');
                    $this->checkUndefined($undefinedInstance, $schema->items[$k], $path, $k);
                }
            }
        }
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\ConstraintError;
use JsonSchema\DraftIdentifiers;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Exception\InvalidArgumentException;
use JsonSchema\Exception\InvalidSchemaException;
use JsonSchema\Exception\RuntimeException;
use JsonSchema\Validator;

/**
 * The SchemaConstraint Constraints, validates an element against a given schema
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 */
class SchemaConstraint extends Constraint
{
    private const DEFAULT_SCHEMA_SPEC = DraftIdentifiers::DRAFT_4;

    /**
     * {@inheritdoc}
     */
    public function check(&$element, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if ($schema !== null) {
            // passed schema
            $validationSchema = $schema;
        } elseif ($this->getTypeCheck()->propertyExists($element, $this->inlineSchemaProperty)) {
            // inline schema
            $validationSchema = $this->getTypeCheck()->propertyGet($element, $this->inlineSchemaProperty);
        } else {
            throw new InvalidArgumentException('no schema found to verify against');
        }

        // cast array schemas to object
        if (is_array($validationSchema)) {
            $validationSchema = BaseConstraint::arrayToObjectRecursive($validationSchema);
        }

        // validate schema against whatever is defined in $validationSchema->$schema. If no
        // schema is defined, assume self::DEFAULT_SCHEMA_SPEC (currently draft-04).
        if ($this->factory->getConfig(self::CHECK_MODE_VALIDATE_SCHEMA)) {
            if (!$this->getTypeCheck()->isObject($validationSchema)) {
                throw new RuntimeException('Cannot validate the schema of a non-object');
            }
            if ($this->getTypeCheck()->propertyExists($validationSchema, '$schema')) {
                $schemaSpec = $this->getTypeCheck()->propertyGet($validationSchema, '$schema');
            } else {
                $schemaSpec = self::DEFAULT_SCHEMA_SPEC;
            }

            // get the spec schema
            $schemaStorage = $this->factory->getSchemaStorage();
            if (!$this->getTypeCheck()->isObject($schemaSpec)) {
                $schemaSpec = $schemaStorage->getSchema($schemaSpec);
            }

            // save error count, config & subtract CHECK_MODE_VALIDATE_SCHEMA
            $initialErrorCount = $this->numErrors();
            $initialConfig = $this->factory->getConfig();
            $initialContext = $this->factory->getErrorContext();
            $this->factory->removeConfig(self::CHECK_MODE_VALIDATE_SCHEMA | self::CHECK_MODE_APPLY_DEFAULTS);
            $this->factory->addConfig(self::CHECK_MODE_TYPE_CAST);
            $this->factory->setErrorContext(Validator::ERROR_SCHEMA_VALIDATION);

            // validate schema
            try {
                $this->check($validationSchema, $schemaSpec);
            } catch (\Exception $e) {
                if ($this->factory->getConfig(self::CHECK_MODE_EXCEPTIONS)) {
                    throw new InvalidSchemaException('Schema did not pass validation', 0, $e);
                }
            }
            if ($this->numErrors() > $initialErrorCount) {
                $this->addError(ConstraintError::INVALID_SCHEMA(), $path);
            }

            // restore the initial config
            $this->factory->setConfig($initialConfig);
            $this->factory->setErrorContext($initialContext);
        }

        // validate element against $validationSchema
        $this->checkUndefined($element, $validationSchema, $path, $i);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\TypeCheck\LooseTypeCheck;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Exception\ValidationException;
use JsonSchema\Tool\DeepCopy;
use JsonSchema\Uri\UriResolver;

#[\AllowDynamicProperties]
class UndefinedConstraint extends Constraint
{
    /**
     * @var list<string> List of properties to which a default value has been applied
     */
    protected $appliedDefaults = [];

    /**
     * {@inheritdoc}
     * */
    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null, bool $fromDefault = false): void
    {
        if (is_null($schema) || !is_object($schema)) {
            return;
        }

        $path = $this->incrementPath($path, $i);
        if ($fromDefault) {
            $path->setFromDefault();
        }

        // check special properties
        $this->validateCommonProperties($value, $schema, $path, $i);

        // check allOf, anyOf, and oneOf properties
        $this->validateOfProperties($value, $schema, $path, '');

        // check known types
        $this->validateTypes($value, $schema, $path, $i);
    }

    /**
     * Validates the value against the types
     *
     * @param mixed       $value
     * @param mixed       $schema
     * @param JsonPointer $path
     * @param string      $i
     */
    public function validateTypes(&$value, $schema, JsonPointer $path, $i = null)
    {
        // check array
        if ($this->getTypeCheck()->isArray($value)) {
            $this->checkArray($value, $schema, $path, $i);
        }

        // check object
        if (LooseTypeCheck::isObject($value)) {
            // object processing should always be run on assoc arrays,
            // so use LooseTypeCheck here even if CHECK_MODE_TYPE_CAST
            // is not set (i.e. don't use $this->getTypeCheck() here).
            $this->checkObject(
                $value,
                $schema,
                $path,
                isset($schema->properties) ? $schema->properties : null,
                isset($schema->additionalProperties) ? $schema->additionalProperties : null,
                isset($schema->patternProperties) ? $schema->patternProperties : null,
                $this->appliedDefaults
            );
        }

        // check string
        if (is_string($value)) {
            $this->checkString($value, $schema, $path, $i);
        }

        // check numeric
        if (is_numeric($value)) {
            $this->checkNumber($value, $schema, $path, $i);
        }

        // check enum
        if (isset($schema->enum)) {
            $this->checkEnum($value, $schema, $path, $i);
        }

        // check const
        if (isset($schema->const)) {
            $this->checkConst($value, $schema, $path, $i);
        }
    }

    /**
     * Validates common properties
     *
     * @param mixed       $value
     * @param mixed       $schema
     * @param JsonPointer $path
     * @param string      $i
     */
    protected function validateCommonProperties(&$value, $schema, JsonPointer $path, $i = '')
    {
        // if it extends another schema, it must pass that schema as well
        if (isset($schema->extends)) {
            if (is_string($schema->extends)) {
                $schema->extends = $this->validateUri($schema, $schema->extends);
            }
            if (is_array($schema->extends)) {
                foreach ($schema->extends as $extends) {
                    $this->checkUndefined($value, $extends, $path, $i);
                }
            } else {
                $this->checkUndefined($value, $schema->extends, $path, $i);
            }
        }

        // Apply default values from schema
        if (!$path->fromDefault()) {
            $this->applyDefaultValues($value, $schema, $path);
        }

        // Verify required values
        if ($this->getTypeCheck()->isObject($value)) {
            if (!($value instanceof self) && isset($schema->required) && is_array($schema->required)) {
                // Draft 4 - Required is an array of strings - e.g. "required": ["foo", ...]
                foreach ($schema->required as $required) {
                    if (!$this->getTypeCheck()->propertyExists($value, $required)) {
                        $this->addError(
                            ConstraintError::REQUIRED(),
                            $this->incrementPath($path, $required), [
                                'property' => $required
                            ]
                        );
                    }
                }
            } elseif (isset($schema->required) && !is_array($schema->required)) {
                // Draft 3 - Required attribute - e.g. "foo": {"type": "string", "required": true}
                if ($schema->required && $value instanceof self) {
                    $propertyPaths = $path->getPropertyPaths();
                    $propertyName = end($propertyPaths);
                    $this->addError(ConstraintError::REQUIRED(), $path, ['property' => $propertyName]);
                }
            } else {
                // if the value is both undefined and not required, skip remaining checks
                // in this method which assume an actual, defined instance when validating.
                if ($value instanceof self) {
                    return;
                }
            }
        }

        // Verify type
        if (!($value instanceof self)) {
            $this->checkType($value, $schema, $path, $i);
        }

        // Verify disallowed items
        if (isset($schema->disallow)) {
            $initErrors = $this->getErrors();

            $typeSchema = new \stdClass();
            $typeSchema->type = $schema->disallow;
            $this->checkType($value, $typeSchema, $path);

            // if no new errors were raised it must be a disallowed value
            if (count($this->getErrors()) == count($initErrors)) {
                $this->addError(ConstraintError::DISALLOW(), $path);
            } else {
                $this->errors = $initErrors;
            }
        }

        if (isset($schema->not)) {
            $initErrors = $this->getErrors();
            $this->checkUndefined($value, $schema->not, $path, $i);

            // if no new errors were raised then the instance validated against the "not" schema
            if (count($this->getErrors()) == count($initErrors)) {
                $this->addError(ConstraintError::NOT(), $path);
            } else {
                $this->errors = $initErrors;
            }
        }

        // Verify that dependencies are met
        if (isset($schema->dependencies) && $this->getTypeCheck()->isObject($value)) {
            $this->validateDependencies($value, $schema->dependencies, $path);
        }
    }

    /**
     * Check whether a default should be applied for this value
     *
     * @param mixed $schema
     * @param mixed $parentSchema
     * @param bool  $requiredOnly
     *
     * @return bool
     */
    private function shouldApplyDefaultValue($requiredOnly, $schema, $name = null, $parentSchema = null)
    {
        // required-only mode is off
        if (!$requiredOnly) {
            return true;
        }
        // draft-04 required is set
        if (
            $name !== null
            && isset($parentSchema->required)
            && is_array($parentSchema->required)
            && in_array($name, $parentSchema->required)
        ) {
            return true;
        }
        // draft-03 required is set
        if (isset($schema->required) && !is_array($schema->required) && $schema->required) {
            return true;
        }
        // default case
        return false;
    }

    /**
     * Apply default values
     *
     * @param mixed       $value
     * @param mixed       $schema
     * @param JsonPointer $path
     */
    protected function applyDefaultValues(&$value, $schema, $path): void
    {
        // only apply defaults if feature is enabled
        if (!$this->factory->getConfig(self::CHECK_MODE_APPLY_DEFAULTS)) {
            return;
        }

        if (is_bool($schema)) {
            return;
        }

        // apply defaults if appropriate
        $requiredOnly = (bool) $this->factory->getConfig(self::CHECK_MODE_ONLY_REQUIRED_DEFAULTS);
        if (isset($schema->properties) && LooseTypeCheck::isObject($value)) {
            // $value is an object or assoc array, and properties are defined - treat as an object
            foreach ($schema->properties as $currentProperty => $propertyDefinition) {
                $propertyDefinition = $this->factory->getSchemaStorage()->resolveRefSchema($propertyDefinition);
                if (is_bool($propertyDefinition)) {
                    continue;
                }
                if (
                    !LooseTypeCheck::propertyExists($value, $currentProperty)
                    && property_exists($propertyDefinition, 'default')
                    && $this->shouldApplyDefaultValue($requiredOnly, $propertyDefinition, $currentProperty, $schema)
                ) {
                    // assign default value
                    if (is_object($propertyDefinition->default)) {
                        LooseTypeCheck::propertySet($value, $currentProperty, clone $propertyDefinition->default);
                    } else {
                        LooseTypeCheck::propertySet($value, $currentProperty, $propertyDefinition->default);
                    }
                    $this->appliedDefaults[] = $currentProperty;
                }
            }
        } elseif (isset($schema->items) && LooseTypeCheck::isArray($value)) {
            $items = [];
            if (LooseTypeCheck::isArray($schema->items)) {
                $items = $schema->items;
            } elseif (isset($schema->minItems) && count($value) < $schema->minItems) {
                $items = array_fill(count($value), $schema->minItems - count($value), $schema->items);
            }
            // $value is an array, and items are defined - treat as plain array
            foreach ($items as $currentItem => $itemDefinition) {
                $itemDefinition = $this->factory->getSchemaStorage()->resolveRefSchema($itemDefinition);
                if (is_bool($itemDefinition)) {
                    continue;
                }

                if (
                    !array_key_exists($currentItem, $value)
                    && property_exists($itemDefinition, 'default')
                    && $this->shouldApplyDefaultValue($requiredOnly, $itemDefinition)) {
                    if (is_object($itemDefinition->default)) {
                        $value[$currentItem] = clone $itemDefinition->default;
                    } else {
                        $value[$currentItem] = $itemDefinition->default;
                    }
                }
                $path->setFromDefault();
            }
        } elseif (
            $value instanceof self
            && property_exists($schema, 'default')
            && $this->shouldApplyDefaultValue($requiredOnly, $schema)) {
            // $value is a leaf, not a container - apply the default directly
            $value = is_object($schema->default) ? clone $schema->default : $schema->default;
            $path->setFromDefault();
        }
    }

    /**
     * Validate allOf, anyOf, and oneOf properties
     *
     * @param mixed       $value
     * @param mixed       $schema
     * @param JsonPointer $path
     * @param string      $i
     */
    protected function validateOfProperties(&$value, $schema, JsonPointer $path, $i = '')
    {
        // Verify type
        if ($value instanceof self) {
            return;
        }

        if (isset($schema->allOf)) {
            $isValid = true;
            foreach ($schema->allOf as $allOf) {
                $initErrors = $this->getErrors();
                $this->checkUndefined($value, $allOf, $path, $i);
                $isValid = $isValid && (count($this->getErrors()) == count($initErrors));
            }
            if (!$isValid) {
                $this->addError(ConstraintError::ALL_OF(), $path);
            }
        }

        if (isset($schema->anyOf)) {
            $isValid = false;
            $startErrors = $this->getErrors();
            $coerceOrDefaults = $this->factory->getConfig(self::CHECK_MODE_COERCE_TYPES | self::CHECK_MODE_APPLY_DEFAULTS);

            foreach ($schema->anyOf as $anyOf) {
                $initErrors = $this->getErrors();
                try {
                    $anyOfValue = $coerceOrDefaults ? DeepCopy::copyOf($value) : $value;
                    $this->checkUndefined($anyOfValue, $anyOf, $path, $i);
                    if ($isValid = (count($this->getErrors()) === count($initErrors))) {
                        $value = $anyOfValue;
                        break;
                    }
                } catch (ValidationException $e) {
                    $isValid = false;
                }
            }
            if (!$isValid) {
                $this->addError(ConstraintError::ANY_OF(), $path);
            } else {
                $this->errors = $startErrors;
            }
        }

        if (isset($schema->oneOf)) {
            $allErrors = [];
            $matchedSchemas = [];
            $startErrors = $this->getErrors();
            $coerceOrDefaults = $this->factory->getConfig(self::CHECK_MODE_COERCE_TYPES | self::CHECK_MODE_APPLY_DEFAULTS);

            foreach ($schema->oneOf as $oneOf) {
                try {
                    $this->errors = [];

                    $oneOfValue = $coerceOrDefaults ? DeepCopy::copyOf($value) : $value;
                    $this->checkUndefined($oneOfValue, $oneOf, $path, $i);
                    if (count($this->getErrors()) === 0) {
                        $matchedSchemas[] = ['schema' => $oneOf, 'value' => $oneOfValue];
                    }
                    $allErrors = array_merge($allErrors, array_values($this->getErrors()));
                } catch (ValidationException $e) {
                    // deliberately do nothing here - validation failed, but we want to check
                    // other schema options in the OneOf field.
                }
            }
            if (count($matchedSchemas) !== 1) {
                $this->addErrors(array_merge($allErrors, $startErrors));
                $this->addError(ConstraintError::ONE_OF(), $path);
            } else {
                $this->errors = $startErrors;
                $value = $matchedSchemas[0]['value'];
            }
        }
    }

    /**
     * Validate dependencies
     *
     * @param mixed       $value
     * @param mixed       $dependencies
     * @param JsonPointer $path
     * @param string      $i
     */
    protected function validateDependencies($value, $dependencies, JsonPointer $path, $i = '')
    {
        foreach ($dependencies as $key => $dependency) {
            if ($this->getTypeCheck()->propertyExists($value, $key)) {
                if (is_string($dependency)) {
                    // Draft 3 string is allowed - e.g. "dependencies": {"bar": "foo"}
                    if (!$this->getTypeCheck()->propertyExists($value, $dependency)) {
                        $this->addError(ConstraintError::DEPENDENCIES(), $path, [
                            'key' => $key,
                            'dependency' => $dependency
                        ]);
                    }
                } elseif (is_array($dependency)) {
                    // Draft 4 must be an array - e.g. "dependencies": {"bar": ["foo"]}
                    foreach ($dependency as $d) {
                        if (!$this->getTypeCheck()->propertyExists($value, $d)) {
                            $this->addError(ConstraintError::DEPENDENCIES(), $path, [
                                'key' => $key,
                                'dependency' => $dependency
                            ]);
                        }
                    }
                } elseif (is_object($dependency)) {
                    // Schema - e.g. "dependencies": {"bar": {"properties": {"foo": {...}}}}
                    $this->checkUndefined($value, $dependency, $path, $i);
                }
            }
        }
    }

    protected function validateUri($schema, $schemaUri = null)
    {
        $resolver = new UriResolver();
        $retriever = $this->factory->getUriRetriever();

        $jsonSchema = null;
        if ($resolver->isValid($schemaUri)) {
            $schemaId = property_exists($schema, 'id') ? $schema->id : null;
            $jsonSchema = $retriever->retrieve($schemaId, $schemaUri);
        }

        return $jsonSchema;
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\ConstraintError;
use JsonSchema\Entity\JsonPointer;

/**
 * The Constraints Interface
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 */
interface ConstraintInterface
{
    /**
     * returns all collected errors
     */
    public function getErrors(): array;

    /**
     * adds errors to this validator
     */
    public function addErrors(array $errors): void;

    /**
     * adds an error
     *
     * @param ConstraintError $constraint the constraint/rule that is broken, e.g.: ConstraintErrors::LENGTH_MIN()
     * @param array           $more       more array elements to add to the error
     */
    public function addError(ConstraintError $constraint, ?JsonPointer $path = null, array $more = []): void;

    /**
     * checks if the validator has not raised errors
     */
    public function isValid(): bool;

    /**
     * invokes the validation of an element
     *
     * @abstract
     *
     * @param mixed $value
     * @param mixed $schema
     * @param mixed $i
     *
     * @throws \JsonSchema\Exception\ExceptionInterface
     */
    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void;
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\ConstraintError;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Tool\DeepComparer;

/**
 * The EnumConstraint Constraints, validates an element against a given set of possibilities
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 */
class EnumConstraint extends Constraint
{
    /**
     * {@inheritdoc}
     */
    public function check(&$element, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        // Only validate enum if the attribute exists
        if ($element instanceof UndefinedConstraint && (!isset($schema->required) || !$schema->required)) {
            return;
        }
        $type = gettype($element);

        foreach ($schema->enum as $enum) {
            $enumType = gettype($enum);

            if ($enumType === 'object'
                && $type === 'array'
                && $this->factory->getConfig(self::CHECK_MODE_TYPE_CAST)
                && DeepComparer::isEqual((object) $element, $enum)
            ) {
                return;
            }

            if (($type === $enumType) && DeepComparer::isEqual($element, $enum)) {
                return;
            }

            if (is_numeric($element) && is_numeric($enum) && DeepComparer::isEqual((float) $element, (float) $enum)) {
                return;
            }
        }

        $this->addError(ConstraintError::ENUM(), $path, ['enum' => $schema->enum]);
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\ConstraintError;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Rfc3339;
use JsonSchema\Tool\Validator\RelativeReferenceValidator;
use JsonSchema\Tool\Validator\UriValidator;

/**
 * Validates against the "format" property
 *
 * @author Justin Rainbow <justin.rainbow@gmail.com>
 *
 * @see   http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.23
 */
class FormatConstraint extends Constraint
{
    /**
     * {@inheritdoc}
     */
    public function check(&$element, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!isset($schema->format) || $this->factory->getConfig(self::CHECK_MODE_DISABLE_FORMAT)) {
            return;
        }

        switch ($schema->format) {
            case 'date':
                if (is_string($element) && !$date = $this->validateDateTime($element, 'Y-m-d')) {
                    $this->addError(ConstraintError::FORMAT_DATE(), $path, [
                            'date' => $element,
                            'format' => $schema->format
                        ]
                    );
                }
                break;

            case 'time':
                if (is_string($element) && !$this->validateDateTime($element, 'H:i:s')) {
                    $this->addError(ConstraintError::FORMAT_TIME(), $path, [
                            'time' => json_encode($element),
                            'format' => $schema->format,
                        ]
                    );
                }
                break;

            case 'date-time':
                if (is_string($element) && null === Rfc3339::createFromString($element)) {
                    $this->addError(ConstraintError::FORMAT_DATE_TIME(), $path, [
                            'dateTime' => json_encode($element),
                            'format' => $schema->format
                        ]
                    );
                }
                break;

            case 'utc-millisec':
                if (!$this->validateDateTime($element, 'U')) {
                    $this->addError(ConstraintError::FORMAT_DATE_UTC(), $path, [
                        'value' => $element,
                        'format' => $schema->format]);
                }
                break;

            case 'regex':
                if (!$this->validateRegex($element)) {
                    $this->addError(ConstraintError::FORMAT_REGEX(), $path, [
                            'value' => $element,
                            'format' => $schema->format
                        ]
                    );
                }
                break;

            case 'color':
                if (!$this->validateColor($element)) {
                    $this->addError(ConstraintError::FORMAT_COLOR(), $path, ['format' => $schema->format]);
                }
                break;

            case 'style':
                if (!$this->validateStyle($element)) {
                    $this->addError(ConstraintError::FORMAT_STYLE(), $path, ['format' => $schema->format]);
                }
                break;

            case 'phone':
                if (!$this->validatePhone($element)) {
                    $this->addError(ConstraintError::FORMAT_PHONE(), $path, ['format' => $schema->format]);
                }
                break;

            case 'uri':
                if (is_string($element) && !UriValidator::isValid($element)) {
                    $this->addError(ConstraintError::FORMAT_URL(), $path, ['format' => $schema->format]);
                }
                break;

            case 'uriref':
            case 'uri-reference':
                if (is_string($element) && !(UriValidator::isValid($element) || RelativeReferenceValidator::isValid($element))) {
                    $this->addError(ConstraintError::FORMAT_URL(), $path, ['format' => $schema->format]);
                }
                break;

            case 'email':
                if (is_string($element) && null === filter_var($element, FILTER_VALIDATE_EMAIL, FILTER_NULL_ON_FAILURE | FILTER_FLAG_EMAIL_UNICODE)) {
                    $this->addError(ConstraintError::FORMAT_EMAIL(), $path, ['format' => $schema->format]);
                }
                break;

            case 'ip-address':
            case 'ipv4':
                if (is_string($element) && null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4)) {
                    $this->addError(ConstraintError::FORMAT_IP(), $path, ['format' => $schema->format]);
                }
                break;

            case 'ipv6':
                if (is_string($element) && null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV6)) {
                    $this->addError(ConstraintError::FORMAT_IP(), $path, ['format' => $schema->format]);
                }
                break;

            case 'host-name':
            case 'hostname':
                if (!$this->validateHostname($element)) {
                    $this->addError(ConstraintError::FORMAT_HOSTNAME(), $path, ['format' => $schema->format]);
                }
                break;

            default:
                // Empty as it should be:
                // The value of this keyword is called a format attribute. It MUST be a string.
                // A format attribute can generally only validate a given set of instance types.
                // If the type of the instance to validate is not in this set, validation for
                // this format attribute and instance SHOULD succeed.
                // http://json-schema.org/latest/json-schema-validation.html#anchor105
                break;
        }
    }

    protected function validateDateTime($datetime, $format)
    {
        $dt = \DateTime::createFromFormat($format, (string) $datetime);

        if (!$dt) {
            return false;
        }

        if ($datetime === $dt->format($format)) {
            return true;
        }

        return false;
    }

    protected function validateRegex($regex)
    {
        if (!is_string($regex)) {
            return true;
        }

        return false !== @preg_match(self::jsonPatternToPhpRegex($regex), '');
    }

    protected function validateColor($color)
    {
        if (!is_string($color)) {
            return true;
        }

        if (in_array(strtolower($color), ['aqua', 'black', 'blue', 'fuchsia',
            'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'orange', 'purple',
            'red', 'silver', 'teal', 'white', 'yellow'])) {
            return true;
        }

        return preg_match('/^#([a-f0-9]{3}|[a-f0-9]{6})$/i', $color);
    }

    protected function validateStyle($style)
    {
        $properties     = explode(';', rtrim($style, ';'));
        $invalidEntries = preg_grep('/^\s*[-a-z]+\s*:\s*.+$/i', $properties, PREG_GREP_INVERT);

        return empty($invalidEntries);
    }

    protected function validatePhone($phone)
    {
        return preg_match('/^\+?(\(\d{3}\)|\d{3}) \d{3} \d{4}$/', $phone);
    }

    protected function validateHostname($host)
    {
        if (!is_string($host)) {
            return true;
        }

        // RFC 1035: labels are max 63 chars (1 start + 0-61 middle + 1 end)
        $hostnameRegex = '/^(?!-)(?!.*?[^A-Za-z0-9\-\.])(?:(?!-)[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?\.)*(?!-)[A-Za-z0-9](?:[A-Za-z0-9\-]{0,61}[A-Za-z0-9])?$/';

        return preg_match($hostnameRegex, $host);
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\ConstraintError;
use JsonSchema\Entity\JsonPointer;

/**
 * The StringConstraint Constraints, validates an string against a given schema
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 */
class StringConstraint extends Constraint
{
    /**
     * {@inheritdoc}
     */
    public function check(&$element, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        // Verify maxLength
        if (isset($schema->maxLength) && $this->strlen($element) > $schema->maxLength) {
            $this->addError(ConstraintError::LENGTH_MAX(), $path, [
                'maxLength' => $schema->maxLength,
            ]);
        }

        //verify minLength
        if (isset($schema->minLength) && $this->strlen($element) < $schema->minLength) {
            $this->addError(ConstraintError::LENGTH_MIN(), $path, [
                'minLength' => $schema->minLength,
            ]);
        }

        // Verify a regex pattern
        if (isset($schema->pattern) && !preg_match(self::jsonPatternToPhpRegex($schema->pattern), $element)) {
            $this->addError(ConstraintError::PATTERN(), $path, [
                'pattern' => $schema->pattern,
            ]);
        }

        $this->checkFormat($element, $schema, $path, $i);
    }

    private function strlen($string)
    {
        if (extension_loaded('mbstring')) {
            return mb_strlen($string, mb_detect_encoding($string));
        }

        // mbstring is present on all test platforms, so strlen() can be ignored for coverage
        return strlen($string); // @codeCoverageIgnore
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema;

/**
 * @package JsonSchema
 */
interface UriRetrieverInterface
{
    /**
     * Retrieve a URI
     *
     * @param string      $uri     JSON Schema URI
     * @param null|string $baseUri
     *
     * @return object JSON Schema contents
     */
    public function retrieve($uri, $baseUri = null);
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema;

/**
 * @package JsonSchema
 */
interface UriResolverInterface
{
    /**
     * Resolves a URI
     *
     * @param string      $uri     Absolute or relative
     * @param null|string $baseUri Optional base URI
     *
     * @return string Absolute URI
     */
    public function resolve($uri, $baseUri = null);
}
<?php

declare(strict_types=1);

namespace JsonSchema;

interface SchemaStorageInterface
{
    /**
     * Adds schema with given identifier
     *
     * @param object|bool $schema
     */
    public function addSchema(string $id, $schema = null): void;

    /**
     * Returns schema for given identifier, or null if it does not exist
     *
     * @return object|bool
     */
    public function getSchema(string $id);

    /**
     * Returns schema for given reference with all sub-references resolved
     *
     * @return object|bool
     */
    public function resolveRef(string $ref);

    /**
     * Returns schema referenced by '$ref' property
     *
     * @param mixed $refSchema
     *
     * @return object|bool
     */
    public function resolveRefSchema($refSchema);
}
<?php

declare(strict_types=1);

namespace JsonSchema\Entity;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\Constraint;
use JsonSchema\Constraints\Factory;
use JsonSchema\Exception\ValidationException;
use JsonSchema\Validator;

/**
 * @phpstan-type Error array{
 *     "property": string,
 *     "pointer": string,
 *     "message": string,
 *     "constraint": array{"name": string, "params": array<string, mixed>},
 *     "context": int-mask-of<Validator::ERROR_*>
 * }
 * @phpstan-type ErrorList list<Error>
 */
class ErrorBag
{
    /** @var Factory */
    private $factory;

    /** @var ErrorList */
    private $errors = [];

    /**
     * @var int-mask-of<Validator::ERROR_*> All error types that have occurred
     */
    protected $errorMask = Validator::ERROR_NONE;

    public function __construct(Factory $factory)
    {
        $this->factory = $factory;
    }

    public function reset(): void
    {
        $this->errors = [];
        $this->errorMask = Validator::ERROR_NONE;
    }

    /** @return ErrorList */
    public function getErrors(): array
    {
        return $this->errors;
    }

    /** @param array<string, mixed> $more */
    public function addError(ConstraintError $constraint, ?JsonPointer $path = null, array $more = []): void
    {
        $message = $constraint->getMessage();
        $name = $constraint->getValue();
        /** @var Error $error */
        $error = [
            'property' => $this->convertJsonPointerIntoPropertyPath($path ?: new JsonPointer('')),
            'pointer' => ltrim((string) ($path ?: new JsonPointer('')), '#'),
            'message' => ucfirst(vsprintf($message, array_map(static function ($val) {
                if (is_scalar($val)) {
                    return is_bool($val) ? var_export($val, true) : $val;
                }

                return json_encode($val);
            }, array_values($more)))),
            'constraint' => [
                'name' => $name,
                'params' => $more
            ],
            'context' => $this->factory->getErrorContext(),
        ];

        if ($this->factory->getConfig(Constraint::CHECK_MODE_EXCEPTIONS)) {
            throw new ValidationException(sprintf('Error validating %s: %s', $error['pointer'], $error['message']));
        }
        $this->errors[] = $error;
        /* @see https://github.com/phpstan/phpstan/issues/9384 */
        $this->errorMask |= $error['context']; // @phpstan-ignore assign.propertyType
    }

    /** @param ErrorList $errors */
    public function addErrors(array $errors): void
    {
        if (!$errors) {
            return;
        }

        $this->errors = array_merge($this->errors, $errors);
        $errorMask = &$this->errorMask;
        array_walk($errors, static function ($error) use (&$errorMask) {
            $errorMask |= $error['context'];
        });
    }

    private function convertJsonPointerIntoPropertyPath(JsonPointer $pointer): string
    {
        $result = array_map(
            static function ($path) {
                return sprintf(is_numeric($path) ? '[%d]' : '.%s', $path);
            },
            $pointer->getPropertyPaths()
        );

        return trim(implode('', $result), '.');
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Entity;

use JsonSchema\Exception\InvalidArgumentException;

/**
 * @package JsonSchema\Entity
 *
 * @author Joost Nijhuis <jnijhuis81@gmail.com>
 */
class JsonPointer
{
    /** @var string */
    private $filename;

    /** @var string[] */
    private $propertyPaths = [];

    /**
     * @var bool Whether the value at this path was set from a schema default
     */
    private $fromDefault = false;

    /**
     * @param string $value
     *
     * @throws InvalidArgumentException when $value is not a string
     */
    public function __construct($value)
    {
        if (!is_string($value)) {
            throw new InvalidArgumentException('Ref value must be a string');
        }

        $splitRef = explode('#', $value, 2);
        $this->filename = $splitRef[0];
        if (array_key_exists(1, $splitRef)) {
            $this->propertyPaths = $this->decodePropertyPaths($splitRef[1]);
        }
    }

    /**
     * @param string $propertyPathString
     *
     * @return string[]
     */
    private function decodePropertyPaths($propertyPathString)
    {
        $paths = [];
        foreach (explode('/', trim($propertyPathString, '/')) as $path) {
            $path = $this->decodePath($path);
            if (is_string($path) && '' !== $path) {
                $paths[] = $path;
            }
        }

        return $paths;
    }

    /**
     * @return array
     */
    private function encodePropertyPaths()
    {
        return array_map(
            [$this, 'encodePath'],
            $this->getPropertyPaths()
        );
    }

    /**
     * @param string $path
     *
     * @return string
     */
    private function decodePath($path)
    {
        return strtr($path, ['~1' => '/', '~0' => '~', '%25' => '%']);
    }

    /**
     * @param string $path
     *
     * @return string
     */
    private function encodePath($path)
    {
        return strtr($path, ['/' => '~1', '~' => '~0', '%' => '%25']);
    }

    /**
     * @return string
     */
    public function getFilename()
    {
        return $this->filename;
    }

    /**
     * @return string[]
     */
    public function getPropertyPaths()
    {
        return $this->propertyPaths;
    }

    /**
     * @param array $propertyPaths
     *
     * @return JsonPointer
     */
    public function withPropertyPaths(array $propertyPaths)
    {
        $new = clone $this;
        $new->propertyPaths = array_map(function ($p): string { return (string) $p; }, $propertyPaths);

        return $new;
    }

    /**
     * @return string
     */
    public function getPropertyPathAsString()
    {
        return rtrim('#/' . implode('/', $this->encodePropertyPaths()), '/');
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return $this->getFilename() . $this->getPropertyPathAsString();
    }

    /**
     * Mark the value at this path as being set from a schema default
     */
    public function setFromDefault(): void
    {
        $this->fromDefault = true;
    }

    /**
     * Check whether the value at this path was set from a schema default
     *
     * @return bool
     */
    public function fromDefault()
    {
        return $this->fromDefault;
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Entity;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\Factory;

/**
 * @phpstan-import-type Error from ErrorBag
 * @phpstan-import-type ErrorList from ErrorBag
 */
trait ErrorBagProxy
{
    /** @var ?ErrorBag */
    protected $errorBag = null;

    /** @return ErrorList */
    public function getErrors(): array
    {
        return $this->errorBag()->getErrors();
    }

    /** @param ErrorList $errors */
    public function addErrors(array $errors): void
    {
        $this->errorBag()->addErrors($errors);
    }

    /**
     * @param array<string, mixed> $more more array elements to add to the error
     */
    public function addError(ConstraintError $constraint, ?JsonPointer $path = null, array $more = []): void
    {
        $this->errorBag()->addError($constraint, $path, $more);
    }

    public function isValid(): bool
    {
        return $this->errorBag()->getErrors() === [];
    }

    protected function initialiseErrorBag(Factory $factory): ErrorBag
    {
        if (is_null($this->errorBag)) {
            $this->errorBag = new ErrorBag($factory);
        }

        return $this->errorBag;
    }

    protected function errorBag(): ErrorBag
    {
        if (is_null($this->errorBag)) {
            throw new \RuntimeException('ErrorBag not initialized');
        }

        return $this->errorBag;
    }

    public function __clone()
    {
        $this->errorBag()->reset();
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Tool;

class DeepComparer
{
    /**
     * @param mixed $left
     * @param mixed $right
     */
    public static function isEqual($left, $right): bool
    {
        if ($left === null && $right === null) {
            return true;
        }

        $isLeftScalar = is_scalar($left);
        $isLeftNumber = is_int($left) || is_float($left);
        $isRightScalar = is_scalar($right);
        $isRightNumber = is_int($right) || is_float($right);

        if ($isLeftScalar && $isRightScalar) {
            /*
             * In Json-Schema mathematically equal numbers are compared equal
             */
            if ($isLeftNumber && $isRightNumber && (float) $left === (float) $right) {
                return true;
            }

            return $left === $right;
        }

        if ($isLeftScalar !== $isRightScalar) {
            return false;
        }

        if (is_array($left) && is_array($right)) {
            return self::isArrayEqual($left, $right);
        }

        if ($left instanceof \stdClass && $right instanceof \stdClass) {
            return self::isArrayEqual((array) $left, (array) $right);
        }

        return false;
    }

    /**
     * @param array<string|int, mixed> $left
     * @param array<string|int, mixed> $right
     */
    private static function isArrayEqual(array $left, array $right): bool
    {
        if (count($left) !== count($right)) {
            return false;
        }
        foreach ($left as $key => $value) {
            if (!array_key_exists($key, $right)) {
                return false;
            }

            if (!self::isEqual($value, $right[$key])) {
                return false;
            }
        }

        return true;
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Tool\Validator;

class RelativeReferenceValidator
{
    public static function isValid(string $ref): bool
    {
        // Relative reference pattern as per RFC 3986, Section 4.1
        $pattern = '/^(([^\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/';

        if (preg_match($pattern, $ref) !== 1) {
            return false;
        }

        // Additional checks for invalid cases
        if (strpos($ref, '\\') !== false) {
            return false; // Backslashes are not allowed in URI references
        }

        if (preg_match('/^(http|https):\/\//', $ref)) {
            return false; // Absolute URI
        }

        if (preg_match('/^:\/\//', $ref)) {
            return false; // Missing scheme in authority
        }

        if (preg_match('/^:\//', $ref)) {
            return false; // Invalid scheme separator
        }

        if (preg_match('/^\/\/$/', $ref)) {
            return false; // Empty authority
        }

        if (preg_match('/^\/\/\/[^\/]/', $ref)) {
            return false; // Invalid authority with three slashes
        }

        if (preg_match('/\s/', $ref)) {
            return false; // Spaces are not allowed in URIs
        }

        if (preg_match('/^\?#|^#$/', $ref)) {
            return false; // Missing path but having query and fragment
        }

        if ($ref === '#' || $ref === '?') {
            return false; // Missing path and having only fragment or query
        }

        return true;
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Tool\Validator;

class UriValidator
{
    public static function isValid(string $uri): bool
    {
        // RFC 3986: Hierarchical URIs (http, https, ftp, etc.)
        $hierarchicalPattern = '/^
            ([a-z][a-z0-9+\-.]*):\/\/                # Scheme (http, https, ftp, etc.)
            (?:([^:@\/?#]+)(?::([^@\/?#]*))?@)?      # Optional userinfo (user:pass@)
            ([a-z0-9._~-]+|\[[a-f0-9:.]+\])          # Hostname or IPv6 in brackets
            (?::(\d{1,5}))?                          # Optional port
            (\/[a-zA-Z0-9._~!$&\'()*+,;=:@\/%-]*)*   # Path (valid characters only)
            (\?([^#]*))?                             # Optional query
            (\#(.*))?                                # Optional fragment
        $/ix';

        // RFC 3986: Non-Hierarchical URIs (mailto, data, urn, news)
        $nonHierarchicalPattern = '/^
                (mailto|data|urn|news|tel|file):     # Only allow known non-hierarchical schemes
                (.+)                                 # Must contain at least one character after scheme
        $/ix';

        // Validation for newsgroup name (alphanumeric + dots, no empty segments)
        $newsGroupPattern = '/^[a-z0-9]+(\.[a-z0-9]+)*$/i';
        $telPattern = '/^\+?[0-9.\-() ]+$/'; // Allows +, digits, separators

        // RFC 5322-compliant email validation for `mailto:` URIs
        $emailPattern = '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/';

        // First, check if it's a valid hierarchical URI
        if (preg_match($hierarchicalPattern, $uri, $matches) === 1) {
            // Validate domain name (no double dots like example..com)
            if (!empty($matches[4]) && preg_match('/\.\./', $matches[4])) {
                return false;
            }

            // Validate port (should be between 1 and 65535 if specified)
            if (!empty($matches[5]) && ($matches[5] < 1 || $matches[5] > 65535)) {
                return false;
            }

            // Validate the path (reject illegal characters: < > { } | \ ^ `)
            if (!empty($matches[6]) && preg_match('/[<>{}|\\\^`]/', $matches[6])) {
                return false;
            }

            return true;
        }

        // If not hierarchical, check non-hierarchical URIs
        if (preg_match($nonHierarchicalPattern, $uri, $matches) === 1) {
            $scheme = strtolower($matches[1]); // Extract the scheme

            // Special case: `mailto:` must contain a **valid email address**
            if ($scheme === 'mailto') {
                return preg_match($emailPattern, $matches[2]) === 1;
            }

            if ($scheme === 'news') {
                return preg_match($newsGroupPattern, $matches[2]) === 1;
            }

            if ($scheme === 'tel') {
                return preg_match($telPattern, $matches[2]) === 1;
            }

            return true; // Valid non-hierarchical URI
        }

        return false;
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Tool;

use JsonSchema\Exception\JsonDecodingException;
use JsonSchema\Exception\RuntimeException;

class DeepCopy
{
    /**
     * @param mixed $input
     *
     * @return mixed
     */
    public static function copyOf($input)
    {
        $json = json_encode($input);
        if (JSON_ERROR_NONE < $error = json_last_error()) {
            throw new JsonDecodingException($error);
        }

        if ($json === false) {
            throw new RuntimeException('Failed to encode input to JSON: ' . json_last_error_msg());
        }

        return json_decode($json, self::isAssociativeArray($input));
    }

    /**
     * @param mixed $input
     */
    private static function isAssociativeArray($input): bool
    {
        return is_array($input) && array_keys($input) !== range(0, count($input) - 1);
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

/**
 * Wrapper for the JsonDecodingException
 */
class JsonDecodingException extends RuntimeException
{
    public function __construct($code = JSON_ERROR_NONE, ?\Exception $previous = null)
    {
        switch ($code) {
            case JSON_ERROR_DEPTH:
                $message = 'The maximum stack depth has been exceeded';
                break;
            case JSON_ERROR_STATE_MISMATCH:
                $message = 'Invalid or malformed JSON';
                break;
            case JSON_ERROR_CTRL_CHAR:
                $message = 'Control character error, possibly incorrectly encoded';
                break;
            case JSON_ERROR_UTF8:
                $message = 'Malformed UTF-8 characters, possibly incorrectly encoded';
                break;
            case JSON_ERROR_SYNTAX:
                $message = 'JSON syntax is malformed';
                break;
            default:
                $message = 'Syntax error';
        }
        parent::__construct($message, $code, $previous);
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

/**
 * Wrapper for the InvalidSchemaMediaType
 */
class InvalidSchemaException extends RuntimeException
{
}
<?php

declare(strict_types=1);

namespace JsonSchema\Exception;

interface ExceptionInterface
{
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

/**
 * Wrapper for the RuntimeException
 */
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

/**
 * Wrapper for the ResourceNotFoundException
 */
class InvalidConfigException extends RuntimeException
{
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

/**
 * @package JsonSchema\Exception
 *
 * @author Joost Nijhuis <jnijhuis81@gmail.com>
 */
class UnresolvableJsonPointerException extends InvalidArgumentException
{
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

class ValidationException extends RuntimeException
{
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

/**
 * Wrapper for the ResourceNotFoundException
 */
class ResourceNotFoundException extends RuntimeException
{
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

/**
 * Wrapper for the InvalidSchemaMediaType
 */
class InvalidSchemaMediaTypeException extends RuntimeException
{
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

/**
 * Wrapper for the InvalidArgumentException
 */
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

/**
 * Wrapper for the InvalidSourceUriException
 */
class InvalidSourceUriException extends InvalidArgumentException
{
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

/**
 * Wrapper for the UriResolverException
 */
class UriResolverException extends RuntimeException
{
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Uri;

use JsonSchema\Exception\UriResolverException;
use JsonSchema\UriResolverInterface;

/**
 * Resolves JSON Schema URIs
 *
 * @author Sander Coolen <sander@jibber.nl>
 */
class UriResolver implements UriResolverInterface
{
    /**
     * Parses a URI into five main components
     *
     * @param string $uri
     *
     * @return array
     */
    public function parse($uri)
    {
        preg_match('|^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?|', (string) $uri, $match);

        $components = [];
        if (5 < count($match)) {
            $components =  [
                'scheme'    => $match[2],
                'authority' => $match[4],
                'path'      => $match[5]
            ];
        }
        if (7 < count($match)) {
            $components['query'] = $match[7];
        }
        if (9 < count($match)) {
            $components['fragment'] = $match[9];
        }

        return $components;
    }

    /**
     * Builds a URI based on n array with the main components
     *
     * @param array $components
     *
     * @return string
     */
    public function generate(array $components)
    {
        $uri = $components['scheme'] . '://'
             . $components['authority']
             . $components['path'];

        if (array_key_exists('query', $components) && strlen($components['query'])) {
            $uri .= '?' . $components['query'];
        }
        if (array_key_exists('fragment', $components)) {
            $uri .= '#' . $components['fragment'];
        }

        return $uri;
    }

    /**
     * {@inheritdoc}
     */
    public function resolve($uri, $baseUri = null)
    {
        // treat non-uri base as local file path
        if (
            !is_null($baseUri) &&
            !filter_var($baseUri, \FILTER_VALIDATE_URL) &&
            !preg_match('|^[^/]+://|u', $baseUri)
        ) {
            if (is_file($baseUri)) {
                $baseUri = 'file://' . realpath($baseUri);
            } elseif (is_dir($baseUri)) {
                $baseUri = 'file://' . realpath($baseUri) . '/';
            } else {
                $baseUri = 'file://' . getcwd() . '/' . $baseUri;
            }
        }

        if ($uri == '') {
            return $baseUri;
        }

        $components = $this->parse($uri);
        $path = $components['path'];

        if (!empty($components['scheme'])) {
            return $uri;
        }
        $baseComponents = $this->parse($baseUri);
        $basePath = $baseComponents['path'];

        $baseComponents['path'] = self::combineRelativePathWithBasePath($path, $basePath);
        if (isset($components['fragment'])) {
            $baseComponents['fragment'] = $components['fragment'];
        }

        return $this->generate($baseComponents);
    }

    /**
     * Tries to glue a relative path onto an absolute one
     *
     * @param string $relativePath
     * @param string $basePath
     *
     * @throws UriResolverException
     *
     * @return string Merged path
     */
    public static function combineRelativePathWithBasePath($relativePath, $basePath)
    {
        $relativePath = self::normalizePath($relativePath);
        if (!$relativePath) {
            return $basePath;
        }
        if ($relativePath[0] === '/') {
            return $relativePath;
        }
        if (!$basePath) {
            throw new UriResolverException(sprintf("Unable to resolve URI '%s' from base '%s'", $relativePath, $basePath));
        }

        $dirname = $basePath[strlen($basePath) - 1] === '/' ? $basePath : dirname($basePath);
        $combined = rtrim($dirname, '/') . '/' . ltrim($relativePath, '/');
        $combinedSegments = explode('/', $combined);
        $collapsedSegments = [];
        while ($combinedSegments) {
            $segment = array_shift($combinedSegments);
            if ($segment === '..') {
                if (count($collapsedSegments) <= 1) {
                    // Do not remove the top level (domain)
                    // This is not ideal - the domain should not be part of the path here. parse() and generate()
                    // should handle the "domain" separately, like the schema.
                    // Then the if-condition here would be `if (!$collapsedSegments) {`.
                    throw new UriResolverException(sprintf("Unable to resolve URI '%s' from base '%s'", $relativePath, $basePath));
                }
                array_pop($collapsedSegments);
            } else {
                $collapsedSegments[] = $segment;
            }
        }

        return implode('/', $collapsedSegments);
    }

    /**
     * Normalizes a URI path component by removing dot-slash and double slashes
     *
     * @param string $path
     *
     * @return string
     */
    private static function normalizePath($path)
    {
        $path = preg_replace('|((?<!\.)\./)*|', '', $path);
        $path = preg_replace('|//|', '/', $path);

        return $path;
    }

    /**
     * @param string $uri
     *
     * @return bool
     */
    public function isValid($uri)
    {
        $components = $this->parse($uri);

        return !empty($components);
    }
}
<?php

declare(strict_types=1);

/**
 * JsonSchema
 *
 * @filesource
 */

namespace JsonSchema\Uri\Retrievers;

/**
 * AbstractRetriever implements the default shared behavior
 * that all descendant Retrievers should inherit
 *
 * @author Steven Garcia <webwhammy@gmail.com>
 */
abstract class AbstractRetriever implements UriRetrieverInterface
{
    /**
     * Media content type
     *
     * @var string
     */
    protected $contentType;

    /**
     * {@inheritdoc}
     *
     * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::getContentType()
     */
    public function getContentType()
    {
        return $this->contentType;
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Uri\Retrievers;

/**
 * Interface for URI retrievers
 *
 * @author Sander Coolen <sander@jibber.nl>
 */
interface UriRetrieverInterface
{
    /**
     * Retrieve a schema from the specified URI
     *
     * @param string $uri URI that resolves to a JSON schema
     *
     * @throws \JsonSchema\Exception\ResourceNotFoundException
     *
     * @return mixed string|null
     */
    public function retrieve($uri);

    /**
     * Get media content type
     *
     * @return string
     */
    public function getContentType();
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Uri\Retrievers;

use JsonSchema\Exception\RuntimeException;
use JsonSchema\Validator;

/**
 * Tries to retrieve JSON schemas from a URI using cURL library
 *
 * @author Sander Coolen <sander@jibber.nl>
 */
class Curl extends AbstractRetriever
{
    protected $messageBody;

    public function __construct()
    {
        if (!function_exists('curl_init')) {
            // Cannot test this, because curl_init is present on all test platforms plus mock
            throw new RuntimeException('cURL not installed'); // @codeCoverageIgnore
        }
    }

    /**
     * {@inheritdoc}
     *
     * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve()
     */
    public function retrieve($uri)
    {
        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, $uri);
        curl_setopt($ch, CURLOPT_HEADER, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, ['Accept: ' . Validator::SCHEMA_MEDIA_TYPE]);

        $response = curl_exec($ch);
        if (false === $response) {
            throw new \JsonSchema\Exception\ResourceNotFoundException('JSON schema not found');
        }

        $this->fetchMessageBody($response);
        $this->fetchContentType($response);

        if (PHP_VERSION_ID < 80000) {
            curl_close($ch);
        }

        return $this->messageBody;
    }

    /**
     * @param string $response cURL HTTP response
     */
    private function fetchMessageBody($response)
    {
        preg_match("/(?:\r\n){2}(.*)$/ms", $response, $match);
        $this->messageBody = $match[1];
    }

    /**
     * @param string $response cURL HTTP response
     *
     * @return bool Whether the Content-Type header was found or not
     */
    protected function fetchContentType($response)
    {
        if (0 < preg_match("/Content-Type:(\V*)/ims", $response, $match)) {
            $this->contentType = trim($match[1]);

            return true;
        }

        return false;
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Uri\Retrievers;

use JsonSchema\Validator;

/**
 * URI retrieved based on a predefined array of schemas
 *
 * @example
 *
 *      $retriever = new PredefinedArray(array(
 *          'http://acme.com/schemas/person#'  => '{ ... }',
 *          'http://acme.com/schemas/address#' => '{ ... }',
 *      ))
 *
 *      $schema = $retriever->retrieve('http://acme.com/schemas/person#');
 */
class PredefinedArray extends AbstractRetriever
{
    /**
     * Contains schemas as URI => JSON
     *
     * @var array
     */
    private $schemas;

    /**
     * Constructor
     *
     * @param array  $schemas
     * @param string $contentType
     */
    public function __construct(array $schemas, $contentType = Validator::SCHEMA_MEDIA_TYPE)
    {
        $this->schemas     = $schemas;
        $this->contentType = $contentType;
    }

    /**
     * {@inheritdoc}
     *
     * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve()
     */
    public function retrieve($uri)
    {
        if (!array_key_exists($uri, $this->schemas)) {
            throw new \JsonSchema\Exception\ResourceNotFoundException(sprintf(
                'The JSON schema "%s" was not found.',
                $uri
            ));
        }

        return $this->schemas[$uri];
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Uri\Retrievers;

use JsonSchema\Exception\ResourceNotFoundException;

/**
 * Tries to retrieve JSON schemas from a URI using file_get_contents()
 *
 * @author Sander Coolen <sander@jibber.nl>
 */
class FileGetContents extends AbstractRetriever
{
    protected $messageBody;

    /**
     * {@inheritdoc}
     *
     * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve()
     */
    public function retrieve($uri)
    {
        if (function_exists('http_clear_last_response_headers')) {
            http_clear_last_response_headers();
        }

        $errorMessage = null;
        set_error_handler(function ($errno, $errstr) use (&$errorMessage) {
            $errorMessage = $errstr;
        });
        $response = file_get_contents($uri);
        restore_error_handler();

        if ($errorMessage) {
            throw new ResourceNotFoundException($errorMessage);
        }

        if (false === $response) {
            throw new ResourceNotFoundException('JSON schema not found at ' . $uri);
        }

        if ($response == ''
            && substr($uri, 0, 7) == 'file://' && substr($uri, -1) == '/'
        ) {
            throw new ResourceNotFoundException('JSON schema not found at ' . $uri);
        }

        $this->messageBody = $response;

        if (function_exists('http_get_last_response_headers')) {
            // Use http_get_last_response_headers() for compatibility with PHP 8.5+
            // where $http_response_header is deprecated.
            $httpResponseHeaders = http_get_last_response_headers();
        } else {
            /** @phpstan-ignore nullCoalesce.variable ($http_response_header can non-existing when no http request was done) */
            $httpResponseHeaders = $http_response_header ?? [];
        }

        if (!empty($httpResponseHeaders)) {
            $this->fetchContentType($httpResponseHeaders);
        } else {
            $this->contentType = null;
        }

        return $this->messageBody;
    }

    /**
     * @param array $headers HTTP Response Headers
     *
     * @return bool Whether the Content-Type header was found or not
     */
    private function fetchContentType(array $headers): bool
    {
        foreach (array_reverse($headers) as $header) {
            if ($this->contentType = self::getContentTypeMatchInHeader($header)) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param string $header
     *
     * @return string|null
     */
    protected static function getContentTypeMatchInHeader($header)
    {
        if (0 < preg_match("/Content-Type:(\V*)/ims", $header, $match)) {
            return trim($match[1]);
        }

        return null;
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Uri;

use JsonSchema\Exception\InvalidSchemaMediaTypeException;
use JsonSchema\Exception\JsonDecodingException;
use JsonSchema\Exception\ResourceNotFoundException;
use JsonSchema\Uri\Retrievers\FileGetContents;
use JsonSchema\Uri\Retrievers\UriRetrieverInterface;
use JsonSchema\UriRetrieverInterface as BaseUriRetrieverInterface;
use JsonSchema\Validator;

/**
 * Retrieves JSON Schema URIs
 *
 * @author Tyler Akins <fidian@rumkin.com>
 */
class UriRetriever implements BaseUriRetrieverInterface
{
    /**
     * @var array Map of URL translations
     */
    protected $translationMap = [
        // use local copies of the spec schemas
        '|^https?://json-schema.org/draft-(0[3467])/schema#?|' => 'package://dist/schema/json-schema-draft-$1.json'
    ];

    /**
     * @var array A list of endpoints for media type check exclusion
     */
    protected $allowedInvalidContentTypeEndpoints = [
        'http://json-schema.org/',
        'https://json-schema.org/'
    ];

    /**
     * @var null|UriRetrieverInterface
     */
    protected $uriRetriever = null;

    /**
     * @var array|object[]
     *
     * @see loadSchema
     */
    private $schemaCache = [];

    /**
     * Adds an endpoint to the media type validation exclusion list
     *
     * @param string $endpoint
     */
    public function addInvalidContentTypeEndpoint($endpoint)
    {
        $this->allowedInvalidContentTypeEndpoints[] = $endpoint;
    }

    /**
     * Guarantee the correct media type was encountered
     *
     * @param UriRetrieverInterface $uriRetriever
     * @param string                $uri
     *
     * @return bool|void
     */
    public function confirmMediaType($uriRetriever, $uri)
    {
        $contentType = $uriRetriever->getContentType();

        if (is_null($contentType)) {
            // Well, we didn't get an invalid one
            return;
        }

        if (in_array($contentType, [Validator::SCHEMA_MEDIA_TYPE, 'application/json'])) {
            return;
        }

        foreach ($this->allowedInvalidContentTypeEndpoints as $endpoint) {
            if (!\is_null($uri) && strpos($uri, $endpoint) === 0) {
                return true;
            }
        }

        throw new InvalidSchemaMediaTypeException(sprintf('Media type %s expected, but %s given', Validator::SCHEMA_MEDIA_TYPE, $contentType));
    }

    /**
     * Get a URI Retriever
     *
     * If none is specified, sets a default FileGetContents retriever and
     * returns that object.
     *
     * @return UriRetrieverInterface
     */
    public function getUriRetriever()
    {
        if (is_null($this->uriRetriever)) {
            $this->setUriRetriever(new FileGetContents());
        }

        return $this->uriRetriever;
    }

    /**
     * Resolve a schema based on pointer
     *
     * URIs can have a fragment at the end in the format of
     * #/path/to/object and we are to look up the 'path' property of
     * the first object then the 'to' and 'object' properties.
     *
     * @param object $jsonSchema JSON Schema contents
     * @param string $uri        JSON Schema URI
     *
     * @throws ResourceNotFoundException
     *
     * @return object JSON Schema after walking down the fragment pieces
     */
    public function resolvePointer($jsonSchema, $uri)
    {
        $resolver = new UriResolver();
        $parsed = $resolver->parse($uri);
        if (empty($parsed['fragment'])) {
            return $jsonSchema;
        }

        $path = explode('/', $parsed['fragment']);
        while ($path) {
            $pathElement = array_shift($path);
            if (!empty($pathElement)) {
                $pathElement = str_replace('~1', '/', $pathElement);
                $pathElement = str_replace('~0', '~', $pathElement);
                if (!empty($jsonSchema->$pathElement)) {
                    $jsonSchema = $jsonSchema->$pathElement;
                } else {
                    throw new ResourceNotFoundException(
                        'Fragment "' . $parsed['fragment'] . '" not found'
                        . ' in ' . $uri
                    );
                }

                if (!is_object($jsonSchema)) {
                    throw new ResourceNotFoundException(
                        'Fragment part "' . $pathElement . '" is no object '
                        . ' in ' . $uri
                    );
                }
            }
        }

        return $jsonSchema;
    }

    /**
     * {@inheritdoc}
     */
    public function retrieve($uri, $baseUri = null, $translate = true)
    {
        $resolver = new UriResolver();
        $resolvedUri = $fetchUri = $resolver->resolve($uri, $baseUri);

        //fetch URL without #fragment
        $arParts = $resolver->parse($resolvedUri);
        if (isset($arParts['fragment'])) {
            unset($arParts['fragment']);
            $fetchUri = $resolver->generate($arParts);
        }

        // apply URI translations
        if ($translate) {
            $fetchUri = $this->translate($fetchUri);
        }

        $jsonSchema = $this->loadSchema($fetchUri);

        // Use the JSON pointer if specified
        $jsonSchema = $this->resolvePointer($jsonSchema, $resolvedUri);

        if ($jsonSchema instanceof \stdClass) {
            $jsonSchema->id = $resolvedUri;
        }

        return $jsonSchema;
    }

    /**
     * Fetch a schema from the given URI, json-decode it and return it.
     * Caches schema objects.
     *
     * @param string $fetchUri Absolute URI
     *
     * @return object JSON schema object
     */
    protected function loadSchema($fetchUri)
    {
        if (isset($this->schemaCache[$fetchUri])) {
            return $this->schemaCache[$fetchUri];
        }

        $uriRetriever = $this->getUriRetriever();
        $contents = $this->uriRetriever->retrieve($fetchUri);
        $this->confirmMediaType($uriRetriever, $fetchUri);
        $jsonSchema = json_decode($contents);

        if (JSON_ERROR_NONE < $error = json_last_error()) {
            throw new JsonDecodingException($error);
        }

        $this->schemaCache[$fetchUri] = $jsonSchema;

        return $jsonSchema;
    }

    /**
     * Set the URI Retriever
     *
     * @param UriRetrieverInterface $uriRetriever
     *
     * @return $this for chaining
     */
    public function setUriRetriever(UriRetrieverInterface $uriRetriever)
    {
        $this->uriRetriever = $uriRetriever;

        return $this;
    }

    /**
     * Parses a URI into five main components
     *
     * @param string $uri
     *
     * @return array
     */
    public function parse($uri)
    {
        preg_match('|^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?|', $uri, $match);

        $components = [];
        if (5 < count($match)) {
            $components =  [
                'scheme'    => $match[2],
                'authority' => $match[4],
                'path'      => $match[5]
            ];
        }

        if (7 < count($match)) {
            $components['query'] = $match[7];
        }

        if (9 < count($match)) {
            $components['fragment'] = $match[9];
        }

        return $components;
    }

    /**
     * Builds a URI based on n array with the main components
     *
     * @param array $components
     *
     * @return string
     */
    public function generate(array $components)
    {
        $uri = $components['scheme'] . '://'
             . $components['authority']
             . $components['path'];

        if (array_key_exists('query', $components)) {
            $uri .= $components['query'];
        }

        if (array_key_exists('fragment', $components)) {
            $uri .= $components['fragment'];
        }

        return $uri;
    }

    /**
     * Resolves a URI
     *
     * @param string $uri     Absolute or relative
     * @param string $baseUri Optional base URI
     *
     * @return string
     */
    public function resolve($uri, $baseUri = null)
    {
        $components = $this->parse($uri);
        $path = $components['path'];

        if ((array_key_exists('scheme', $components)) && ('http' === $components['scheme'])) {
            return $uri;
        }

        $baseComponents = $this->parse($baseUri);
        $basePath = $baseComponents['path'];

        $baseComponents['path'] = UriResolver::combineRelativePathWithBasePath($path, $basePath);

        return $this->generate($baseComponents);
    }

    /**
     * @param string $uri
     *
     * @return bool
     */
    public function isValid($uri)
    {
        $components = $this->parse($uri);

        return !empty($components);
    }

    /**
     * Set a URL translation rule
     */
    public function setTranslation($from, $to)
    {
        $this->translationMap[$from] = $to;
    }

    /**
     * Apply URI translation rules
     */
    public function translate($uri)
    {
        foreach ($this->translationMap as $from => $to) {
            $uri = preg_replace($from, $to, $uri);
        }

        // translate references to local files within the json-schema package
        $uri = preg_replace('|^package://|', sprintf('file://%s/', realpath(__DIR__ . '/../../..')), $uri);

        return $uri;
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema;

use JsonSchema\Exception\InvalidArgumentException;

class ConstraintError extends Enum
{
    public const ADDITIONAL_ITEMS = 'additionalItems';
    public const ADDITIONAL_PROPERTIES = 'additionalProp';
    public const ALL_OF = 'allOf';
    public const ANY_OF = 'anyOf';
    public const DEPENDENCIES = 'dependencies';
    public const DISALLOW = 'disallow';
    public const DIVISIBLE_BY = 'divisibleBy';
    public const ENUM = 'enum';
    public const CONSTANT = 'const';
    public const CONTAINS = 'contains';
    public const EXCLUSIVE_MINIMUM = 'exclusiveMinimum';
    public const EXCLUSIVE_MAXIMUM = 'exclusiveMaximum';
    public const FALSE = 'false';
    public const FORMAT_COLOR = 'colorFormat';
    public const FORMAT_DATE = 'dateFormat';
    public const FORMAT_DATE_TIME = 'dateTimeFormat';
    public const FORMAT_DATE_UTC = 'dateUtcFormat';
    public const FORMAT_EMAIL = 'emailFormat';
    public const FORMAT_HOSTNAME = 'styleHostName';
    public const FORMAT_IP = 'ipFormat';
    public const FORMAT_JSON_POINTER = 'jsonPointerFormat';
    public const FORMAT_PHONE = 'phoneFormat';
    public const FORMAT_REGEX= 'regexFormat';
    public const FORMAT_STYLE = 'styleFormat';
    public const FORMAT_TIME = 'timeFormat';
    public const FORMAT_URI_TEMPLATE = 'uriTemplateFormat';
    public const FORMAT_URL = 'urlFormat';
    public const FORMAT_URL_REF = 'urlRefFormat';
    public const INVALID_SCHEMA = 'invalidSchema';
    public const LENGTH_MAX = 'maxLength';
    public const LENGTH_MIN = 'minLength';
    public const MAXIMUM = 'maximum';
    public const MIN_ITEMS = 'minItems';
    public const MINIMUM = 'minimum';
    public const MISSING_ERROR = 'missingError';
    public const MISSING_MAXIMUM = 'missingMaximum';
    public const MISSING_MINIMUM = 'missingMinimum';
    public const MAX_ITEMS = 'maxItems';
    public const MULTIPLE_OF = 'multipleOf';
    public const NOT = 'not';
    public const ONE_OF = 'oneOf';
    public const REQUIRED = 'required';
    public const REQUIRES = 'requires';
    public const PATTERN = 'pattern';
    public const PREGEX_INVALID = 'pregrex';
    public const PROPERTIES_MIN = 'minProperties';
    public const PROPERTIES_MAX = 'maxProperties';
    public const PROPERTY_NAMES = 'propertyNames';
    public const TYPE = 'type';
    public const UNIQUE_ITEMS = 'uniqueItems';
    public const CONTENT_MEDIA_TYPE = 'contentMediaType';
    public const CONTENT_ENCODING = 'contentEncoding';

    /**
     * @return string
     */
    public function getMessage()
    {
        $name = $this->getValue();
        static $messages = [
            self::ADDITIONAL_ITEMS => 'The item %s[%s] is not defined and the definition does not allow additional items',
            self::ADDITIONAL_PROPERTIES => 'The property %s is not defined and the definition does not allow additional properties',
            self::ALL_OF => 'Failed to match all schemas',
            self::ANY_OF => 'Failed to match at least one schema',
            self::DEPENDENCIES => '%s depends on %s, which is missing',
            self::DISALLOW => 'Disallowed value was matched',
            self::DIVISIBLE_BY => 'Is not divisible by %d',
            self::ENUM => 'Does not have a value in the enumeration %s',
            self::CONSTANT => 'Does not have a value equal to %s',
            self::CONTAINS => 'Does not have a value valid to contains schema',
            self::EXCLUSIVE_MINIMUM => 'Must have a minimum value greater than %d',
            self::EXCLUSIVE_MAXIMUM => 'Must have a maximum value less than %d',
            self::FALSE => 'Boolean schema false',
            self::FORMAT_COLOR => 'Invalid color',
            self::FORMAT_DATE => 'Invalid date %s, expected format YYYY-MM-DD',
            self::FORMAT_DATE_TIME => 'Invalid date-time %s, expected format YYYY-MM-DDThh:mm:ssZ or YYYY-MM-DDThh:mm:ss+hh:mm',
            self::FORMAT_DATE_UTC => 'Invalid time %s, expected integer of milliseconds since Epoch',
            self::FORMAT_EMAIL => 'Invalid email',
            self::FORMAT_HOSTNAME => 'Invalid hostname',
            self::FORMAT_IP => 'Invalid IP address',
            self::FORMAT_JSON_POINTER => 'Invalid JSON pointer',
            self::FORMAT_PHONE => 'Invalid phone number',
            self::FORMAT_REGEX=> 'Invalid regex format %s',
            self::FORMAT_STYLE => 'Invalid style',
            self::FORMAT_TIME => 'Invalid time %s, expected format hh:mm:ss',
            self::FORMAT_URI_TEMPLATE => 'Invalid URI template format',
            self::FORMAT_URL => 'Invalid URL format',
            self::FORMAT_URL_REF => 'Invalid URL reference format',
            self::LENGTH_MAX => 'Must be at most %d characters long',
            self::INVALID_SCHEMA => 'Schema is not valid',
            self::LENGTH_MIN => 'Must be at least %d characters long',
            self::MAX_ITEMS => 'There must be a maximum of %d items in the array, %d found',
            self::MAXIMUM => 'Must have a maximum value less than or equal to %d',
            self::MIN_ITEMS => 'There must be a minimum of %d items in the array, %d found',
            self::MINIMUM => 'Must have a minimum value greater than or equal to %d',
            self::MISSING_MAXIMUM => 'Use of exclusiveMaximum requires presence of maximum',
            self::MISSING_MINIMUM => 'Use of exclusiveMinimum requires presence of minimum',
            /*self::MISSING_ERROR => 'Used for tests; this error is deliberately commented out',*/
            self::MULTIPLE_OF => 'Must be a multiple of %s',
            self::NOT => 'Matched a schema which it should not',
            self::ONE_OF => 'Failed to match exactly one schema',
            self::REQUIRED => 'The property %s is required',
            self::REQUIRES => 'The presence of the property %s requires that %s also be present',
            self::PATTERN => 'Does not match the regex pattern %s',
            self::PREGEX_INVALID => 'The pattern %s is invalid',
            self::PROPERTIES_MIN => 'Must contain a minimum of %d properties',
            self::PROPERTIES_MAX => 'Must contain no more than %d properties',
            self::PROPERTY_NAMES => 'Property name %s is invalid',
            self::TYPE => '%s value found, but %s is required',
            self::UNIQUE_ITEMS => 'There are no duplicates allowed in the array',
            self::CONTENT_MEDIA_TYPE => 'Value is not valid with content media type',
            self::CONTENT_ENCODING => 'Value is not valid with content encoding',
        ];

        if (!isset($messages[$name])) {
            throw new InvalidArgumentException('Missing error message for ' . $name);
        }

        return $messages[$name];
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema;

abstract class Enum extends \MabeEnum\Enum
{
}
<?php

declare(strict_types=1);

namespace JsonSchema;

class Rfc3339
{
    private const REGEX = '/^(\d{4}-\d{2}-\d{2}[T ](0[0-9]|1[0-9]|2[0-3]):([0-5][0-9]):((?:[0-5][0-9]|60)))(\.\d+)?(Z|([+-](0[0-9]|1[0-9]|2[0-3]))(:)?([0-5][0-9]))$/';

    /**
     * Try creating a DateTime instance
     *
     * @param string $input
     *
     * @return \DateTime|null
     */
    public static function createFromString($input): ?\DateTime
    {
        if (!preg_match(self::REGEX, strtoupper($input), $matches)) {
            return null;
        }

        $input = strtoupper($input); // Cleanup for lowercase t and z
        $inputHasTSeparator = strpos($input, 'T');

        $dateAndTime = $matches[1];
        $microseconds = $matches[5] ?: '.000000';
        $timeZone = 'Z' !== $matches[6] ? $matches[6] : '+00:00';
        $dateFormat = $inputHasTSeparator === false ? 'Y-m-d H:i:s.uP' : 'Y-m-d\TH:i:s.uP';
        $dateTime = \DateTimeImmutable::createFromFormat($dateFormat, $dateAndTime . $microseconds . $timeZone, new \DateTimeZone('UTC'));

        if ($dateTime === false) {
            return null;
        }

        $utcDateTime = $dateTime->setTimezone(new \DateTimeZone('+00:00'));
        $oneSecond = new \DateInterval('PT1S');

        // handle leap seconds
        if ($matches[4] === '60' && $utcDateTime->sub($oneSecond)->format('H:i:s') === '23:59:59') {
            $dateTime = $dateTime->sub($oneSecond);
            $matches[1] = str_replace(':60', ':59', $matches[1]);
        }

        // Ensure we still have the same year, month, day, hour, minutes and seconds to ensure no rollover took place.
        if ($dateTime->format($inputHasTSeparator ? 'Y-m-d\TH:i:s' : 'Y-m-d H:i:s') !== $matches[1]) {
            return null;
        }

        $mutable = \DateTime::createFromFormat('U.u', $dateTime->format('U.u'));
        if ($mutable === false) {
            throw new \RuntimeException('Unable to create DateTime from DateTimeImmutable');
        }

        $mutable->setTimezone($dateTime->getTimezone());

        return $mutable;
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Iterator;

/**
 * @package JsonSchema\Iterator
 *
 * @author Joost Nijhuis <jnijhuis81@gmail.com>
 */
class ObjectIterator implements \Iterator, \Countable
{
    /** @var object */
    private $object;

    /** @var int */
    private $position = 0;

    /** @var array */
    private $data = [];

    /** @var bool */
    private $initialized = false;

    /**
     * @param object $object
     */
    public function __construct($object)
    {
        $this->object = $object;
    }

    /**
     * {@inheritdoc}
     */
    #[\ReturnTypeWillChange]
    public function current()
    {
        $this->initialize();

        return $this->data[$this->position];
    }

    /**
     * {@inheritdoc}
     */
    public function next(): void
    {
        $this->initialize();
        $this->position++;
    }

    /**
     * {@inheritdoc}
     */
    public function key(): int
    {
        $this->initialize();

        return $this->position;
    }

    /**
     * {@inheritdoc}
     */
    public function valid(): bool
    {
        $this->initialize();

        return isset($this->data[$this->position]);
    }

    /**
     * {@inheritdoc}
     */
    public function rewind(): void
    {
        $this->initialize();
        $this->position = 0;
    }

    /**
     * {@inheritdoc}
     */
    public function count(): int
    {
        $this->initialize();

        return count($this->data);
    }

    /**
     * Initializer
     */
    private function initialize()
    {
        if (!$this->initialized) {
            $this->data = $this->buildDataFromObject($this->object);
            $this->initialized = true;
        }
    }

    /**
     * @param object $object
     *
     * @return array
     */
    private function buildDataFromObject($object)
    {
        $result = [];

        $stack = new \SplStack();
        $stack->push($object);

        while (!$stack->isEmpty()) {
            $current = $stack->pop();
            if (is_object($current)) {
                array_push($result, $current);
            }

            foreach ($this->getDataFromItem($current) as $propertyName => $propertyValue) {
                if (is_object($propertyValue) || is_array($propertyValue)) {
                    $stack->push($propertyValue);
                }
            }
        }

        return $result;
    }

    /**
     * @param object|array $item
     *
     * @return array
     */
    private function getDataFromItem($item)
    {
        if (!is_object($item) && !is_array($item)) {
            return [];
        }

        return is_object($item) ? get_object_vars($item) : $item;
    }
}
#!/usr/bin/env bash
set -e

# Script to update CHANGELOG.md with a new entry
# Usage: ./bin/update-changelog.sh "PR Title" "123" "Fixed"
#
# Arguments:
#   $1 - PR title (the changelog entry text)
#   $2 - PR number
#   $3 - Category (Added, Changed, Fixed, Deprecated, Removed, Security)
#
# Example:
#   ./bin/update-changelog.sh "Fix bug in validation" "456" "Fixed"

if [ $# -lt 3 ]; then
  echo "Usage: $0 <pr_title> <pr_number> <category>"
  echo ""
  echo "Categories: Added, Changed, Fixed, Deprecated, Removed, Security"
  echo ""
  echo "Example: $0 'Fix bug in validation' '456' 'Fixed'"
  exit 1
fi

PR_TITLE="$1"
PR_NUMBER="$2"
CATEGORY="$3"
REPO_URL="${GITHUB_REPOSITORY_URL:-https://github.com/jsonrainbow/json-schema}"

# Remove trailing .git if present
REPO_URL="${REPO_URL%.git}"

# Create the changelog entry
ENTRY="- ${PR_TITLE} ([#${PR_NUMBER}](${REPO_URL}/pull/${PR_NUMBER}))"

echo "Adding entry: $ENTRY"
echo "Under category: ### $CATEGORY"

# Check if CHANGELOG.md exists
if [ ! -f CHANGELOG.md ]; then
  echo "Error: CHANGELOG.md not found in current directory"
  exit 1
fi

# Use awk to insert the entry under the correct category in the Unreleased section
if ! awk -v entry="$ENTRY" -v category="### $CATEGORY" '
BEGIN { in_unreleased=0; found_category=0; added=0 }

# Detect Unreleased section
/^## \[Unreleased\]/ { in_unreleased=1; print; next }

# Detect next version section (end of Unreleased)
/^## \[/ {
  if (in_unreleased && !added) {
    # If we are leaving Unreleased and haven'\''t added entry yet
    # Add category and entry before this line
    if (!found_category) {
      print category
    }
    print entry
    print ""
    added=1
  }
  in_unreleased=0
  found_category=0
  print
  next
}

# If in Unreleased section, look for matching category
in_unreleased && $0 == category {
  found_category=1
  print
  # Add entry right after category header
  print entry
  added=1
  next
}

# Always print the current line
{ print }

# At end of file, if still in unreleased and not added
END {
  if (in_unreleased && !added) {
    if (!found_category) {
      print category
    }
    print entry
  }
}
' CHANGELOG.md > CHANGELOG.md.tmp; then
  echo "Error: Failed to update CHANGELOG.md"
  rm -f CHANGELOG.md.tmp
  exit 1
fi

mv CHANGELOG.md.tmp CHANGELOG.md

echo "CHANGELOG.md updated successfully"
#!/usr/bin/env php
<?php
/**
 * JSON schema validator
 *
 * @author Christian Weiske <christian.weiske@netresearch.de>
 */

// support running this tool from git checkout
$projectDirectory = dirname(__DIR__);
if (is_dir($projectDirectory . DIRECTORY_SEPARATOR . 'vendor')) {
    set_include_path($projectDirectory . PATH_SEPARATOR . get_include_path());
}

// autoload composer classes
$composerAutoload = stream_resolve_include_path('vendor/autoload.php');
if (!$composerAutoload) {
    echo("Cannot load json-schema library\n");
    exit(1);
}
require($composerAutoload);

$arOptions = [];
$arArgs = [];
array_shift($argv);//script itself
foreach ($argv as $arg) {
    if ($arg[0] == '-') {
        $arOptions[$arg] = true;
    } else {
        $arArgs[] = $arg;
    }
}

if (count($arArgs) == 0
    || isset($arOptions['--help']) || isset($arOptions['-h'])
) {
    echo <<<HLP
Validate schema
Usage: validate-json data.json
   or: validate-json data.json schema.json

Options:
      --dump-schema     Output full schema and exit
      --dump-schema-url Output URL of schema
      --verbose         Show additional output
      --quiet           Suppress all output
   -h --help            Show this help

HLP;
    exit(1);
}

if (count($arArgs) == 1) {
    $pathData   = $arArgs[0];
    $pathSchema = null;
} else {
    $pathData   = $arArgs[0];
    $pathSchema = getUrlFromPath($arArgs[1]);
}

/**
 * Show the json parse error that happened last
 */
function showJsonError(): void
{
    $constants = get_defined_constants(true);
    $json_errors = [];
    foreach ($constants['json'] as $name => $value) {
        if (!strncmp($name, 'JSON_ERROR_', 11)) {
            $json_errors[$value] = $name;
        }
    }

    output('JSON parse error: ' . $json_errors[json_last_error()] . "\n");
}

function getUrlFromPath($path)
{
    if (parse_url($path, PHP_URL_SCHEME) !== null) {
        //already an URL
        return $path;
    }
    if ($path[0] == '/') {
        //absolute path
        return 'file://' . $path;
    }

    //relative path: make absolute
    return 'file://' . getcwd() . '/' . $path;
}

/**
 * Take a HTTP header value and split it up into parts.
 *
 * @param $headerValue
 * @return array Key "_value" contains the main value, all others
 *               as given in the header value
 */
function parseHeaderValue($headerValue)
{
    if (strpos($headerValue, ';') === false) {
        return ['_value' => $headerValue];
    }

    $parts = explode(';', $headerValue);
    $arData = ['_value' => array_shift($parts)];
    foreach ($parts as $part) {
        list($name, $value) = explode('=', $part);
        $arData[$name] = trim($value, ' "\'');
    }
    return $arData;
}

/**
 * Send a string to the output stream, but only if --quiet is not enabled
 *
 * @param $str string A string output
 */
function output($str) {
    global $arOptions;
    if (!isset($arOptions['--quiet'])) {
        echo $str;
    }
}

$urlData = getUrlFromPath($pathData);

$context = stream_context_create(
    [
        'http' => [
            'header'        => [
                'Accept: */*',
                'Connection: Close'
            ],
            'max_redirects' => 5
        ]
    ]
);
$dataString = file_get_contents($pathData, false, $context);
if ($dataString == '') {
    output("Data file is not readable or empty.\n");
    exit(3);
}

$data = json_decode($dataString);
unset($dataString);
if ($data === null) {
    output("Error loading JSON data file\n");
    showJsonError();
    exit(5);
}

if ($pathSchema === null) {
    if (isset($http_response_header)) {
        array_shift($http_response_header);//HTTP/1.0 line
        foreach ($http_response_header as $headerLine) {
            list($hName, $hValue) = explode(':', $headerLine, 2);
            $hName = strtolower($hName);
            if ($hName == 'link') {
                //Link: <http://example.org/schema#>; rel="describedBy"
                $hParts = parseHeaderValue($hValue);
                if (isset($hParts['rel']) && $hParts['rel'] == 'describedBy') {
                    $pathSchema = trim($hParts['_value'], ' <>');
                }
            } else if ($hName == 'content-type') {
                //Content-Type: application/my-media-type+json;
                //              profile=http://example.org/schema#
                $hParts = parseHeaderValue($hValue);
                if (isset($hParts['profile'])) {
                    $pathSchema = $hParts['profile'];
                }

            }
        }
    }
    if (is_object($data) && property_exists($data, '$schema')) {
        $pathSchema = $data->{'$schema'};
    }

    //autodetect schema
    if ($pathSchema === null) {
        output("JSON data must be an object and have a \$schema property.\n");
        output("You can pass the schema file on the command line as well.\n");
        output("Schema autodetection failed.\n");
        exit(6);
    }
}
if ($pathSchema[0] == '/') {
    $pathSchema = 'file://' . $pathSchema;
}

$resolver = new JsonSchema\Uri\UriResolver();
$retriever = new JsonSchema\Uri\UriRetriever();
try {
    $urlSchema = $resolver->resolve($pathSchema, $urlData);

    if (isset($arOptions['--dump-schema-url'])) {
        echo $urlSchema . "\n";
        exit();
    }
} catch (Exception $e) {
    output("Error loading JSON schema file\n");
    output($urlSchema . "\n");
    output($e->getMessage() . "\n");
    exit(2);
}
$refResolver = new JsonSchema\SchemaStorage($retriever, $resolver);
$schema = $refResolver->resolveRef($urlSchema);

if (isset($arOptions['--dump-schema'])) {
    $options = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0;
    echo json_encode($schema, $options) . "\n";
    exit();
}

try {
    $validator = new JsonSchema\Validator();
    $validator->validate($data, $schema);

    if ($validator->isValid()) {
        if(isset($arOptions['--verbose'])) {
            output("OK. The supplied JSON validates against the schema.\n");
        }
    } else {
        output("JSON does not validate. Violations:\n");
        foreach ($validator->getErrors() as $error) {
            output(sprintf("[%s] %s\n", $error['property'], $error['message']));
        }
        exit(23);
    }
} catch (Exception $e) {
    output("JSON does not validate. Error:\n");
    output($e->getMessage() . "\n");
    output("Error code: " . $e->getCode() . "\n");
    exit(24);
}
#!/usr/bin/env bash
set -e

# Script to prepare a release by updating CHANGELOG.md
# Usage: ./bin/prepare-release.sh "6.7.0"
#
# Arguments:
#   $1 - Version number (e.g., "6.7.0")
#
# This script:
#   1. Moves all entries from [Unreleased] to a new version section
#   2. Adds the release date
#   3. Leaves an empty [Unreleased] section

if [ $# -lt 1 ]; then
  echo "Usage: $0 <version>"
  echo ""
  echo "Example: $0 '6.7.0'"
  exit 1
fi

VERSION="$1"
RELEASE_DATE=$(date +%Y-%m-%d)

if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
    echo "Error: Version must be in format X.Y.Z (e.g., 6.7.0)"
    exit 1
fi

echo "Preparing release for version: $VERSION"
echo "Release date: $RELEASE_DATE"

# Check if CHANGELOG.md exists
if [ ! -f CHANGELOG.md ]; then
  echo "Error: CHANGELOG.md not found in current directory"
  exit 1
fi

# Check if there is content in the Unreleased section
# Extract content between [Unreleased] and the next version header
UNRELEASED_CONTENT=$(awk '/^## \[Unreleased\]/ {flag=1; next} /^## \[/ {flag=0} flag' CHANGELOG.md)
if ! echo "$UNRELEASED_CONTENT" | grep -q "^### "; then
  echo "Error: No changes found in [Unreleased] section"
  exit 1
fi

# Use awk to process the changelog
awk -v version="$VERSION" -v date="$RELEASE_DATE" '
BEGIN {
  in_unreleased = 0
  buf = ""
}

/^## \[Unreleased\]/ {
  print
  print ""
  print "## [" version "] - " date
  in_unreleased = 1
  next
}

/^## \[/ {
  if (in_unreleased) {
    print buf
    in_unreleased = 0
  }
  print
  next
}

in_unreleased {
  buf = buf $0 "\n"
  next
}

{ print }

END {
  if (in_unreleased) {
    print buf
  }
}
' CHANGELOG.md > CHANGELOG.md.tmp

if [ ! -s CHANGELOG.md.tmp ]; then
  echo "Error: Failed to update CHANGELOG.md"
  rm -f CHANGELOG.md.tmp
  exit 1
fi

mv CHANGELOG.md.tmp CHANGELOG.md

echo "CHANGELOG.md updated successfully"
echo "Created release section for version $VERSION"
#!/usr/bin/env bash
set -e

# Script to extract release notes for a specific version from CHANGELOG.md
# Usage: ./bin/extract-release-notes.sh "6.7.0"
#
# Arguments:
#   $1 - Version number (e.g., "6.7.0")
#
# This script extracts all the content between the specified version header
# and the next version header

if [ $# -lt 1 ]; then
  echo "Usage: $0 <version>"
  echo ""
  echo "Example: $0 '6.7.0'"
  exit 1
fi

VERSION="$1"

if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
    echo "Error: Version must be in format X.Y.Z (e.g., 6.7.0)"
    exit 1
fi

# Check if CHANGELOG.md exists
if [ ! -f CHANGELOG.md ]; then
  echo "Error: CHANGELOG.md not found in current directory"
  exit 1
fi

# Use awk to extract the release notes for the specified version
awk -v version="$VERSION" '
BEGIN { in_version = 0; found = 0 }

# Match the version header
$0 ~ "^## \\[" version "\\]" {
  in_version = 1
  found = 1
  next
}

# Match any other version header
/^## \[/ {
  if (in_version) {
    exit
  }
  next
}

# Print content when in the correct version section
in_version {
  print
}

END {
  if (!found) {
    print "Error: Version " version " not found in CHANGELOG.md" > "/dev/stderr"
    exit 1
  }
}
' CHANGELOG.md
Copyright (C) 2022 Composer

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.
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\ClassMapGenerator;

use Composer\Pcre\Preg;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @internal
 */
class PhpFileCleaner
{
    /** @var array<array{name: string, length: int, pattern: non-empty-string}> */
    private static $typeConfig;

    /** @var non-empty-string */
    private static $rejectChars;

    /**
     * @readonly
     * @var string
     */
    private $contents;

    /**
     * @readonly
     * @var int
     */
    private $len;

    /**
     * @readonly
     * @var int
     */
    private $maxMatches;

    /** @var int */
    private $index = 0;

    /**
     * @param string[] $types
     */
    public static function setTypeConfig(array $types): void
    {
        foreach ($types as $type) {
            self::$typeConfig[$type[0]] = [
                'name' => $type,
                'length' => \strlen($type),
                'pattern' => '{.\b(?<![\$:>])'.$type.'\s++[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+}Ais',
            ];
        }

        self::$rejectChars = '?"\'</'.implode('', array_keys(self::$typeConfig));
    }

    public function __construct(string $contents, int $maxMatches)
    {
        $this->contents = $contents;
        $this->len = \strlen($this->contents);
        $this->maxMatches = $maxMatches;
    }

    public function clean(): string
    {
        $clean = '';

        while ($this->index < $this->len) {
            $this->skipToPhp();
            $clean .= '<?';

            while ($this->index < $this->len) {
                $char = $this->contents[$this->index];
                if ($char === '?' && $this->peek('>')) {
                    $clean .= '?>';
                    $this->index += 2;
                    continue 2;
                }

                if ($char === '"') {
                    $this->skipString('"');
                    $clean .= 'null';
                    continue;
                }

                if ($char === "'") {
                    $this->skipString("'");
                    $clean .= 'null';
                    continue;
                }

                if ($char === "<" && $this->peek('<') && $this->match('{<<<[ \t]*+([\'"]?)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*+)\\1(?:\r\n|\n|\r)}A', $match)) {
                    $this->index += \strlen($match[0]);
                    $this->skipHeredoc($match[2]);
                    $clean .= 'null';
                    continue;
                }

                if ($char === '/') {
                    if ($this->peek('/')) {
                        $this->skipToNewline();
                        continue;
                    }

                    if ($this->peek('*')) {
                        $this->skipComment();
                        continue;
                    }
                }

                if ($this->maxMatches === 1 && isset(self::$typeConfig[$char])) {
                    $type = self::$typeConfig[$char];
                    if (
                        substr($this->contents, $this->index, $type['length']) === $type['name']
                        && Preg::isMatch($type['pattern'], $this->contents, $match, 0, $this->index - 1)
                    ) {
                        return $clean . $match[0];
                    }
                }

                $this->index += 1;
                $skip = strcspn($this->contents, self::$rejectChars, $this->index);
                if ($skip > 0) {
                    $clean .= $char . substr($this->contents, $this->index, $skip);
                    $this->index += $skip;
                } else {
                    $clean .= $char;
                }
            }
        }

        return $clean;
    }

    private function skipToPhp(): void
    {
        while ($this->index < $this->len) {
            if ($this->contents[$this->index] === '<' && $this->peek('?')) {
                $this->index += 2;
                break;
            }

            $this->index += 1;
        }
    }

    private function skipString(string $delimiter): void
    {
        $rejectChars = '\\' . $delimiter;
        $this->index += 1;
        while ($this->index < $this->len) {
            $this->index += strcspn($this->contents, $rejectChars, $this->index);
            if ($this->index >= $this->len) {
                break;
            }

            if ($this->contents[$this->index] === '\\' && ($this->peek('\\') || $this->peek($delimiter))) {
                $this->index += 2;
                continue;
            }

            if ($this->contents[$this->index] === $delimiter) {
                $this->index += 1;
                break;
            }

            $this->index += 1;
        }
    }

    private function skipComment(): void
    {
        $this->index += 2;
        while ($this->index < $this->len) {
            $this->index += strcspn($this->contents, '*', $this->index);

            if ($this->peek('/')) {
                $this->index += 2;
                break;
            }

            $this->index += 1;
        }
    }

    private function skipToNewline(): void
    {
        $this->index += strcspn($this->contents, "\r\n", $this->index);
    }

    private function skipHeredoc(string $delimiter): void
    {
        $firstDelimiterChar = $delimiter[0];
        $delimiterLength = \strlen($delimiter);
        $delimiterPattern = '{'.preg_quote($delimiter).'(?![a-zA-Z0-9_\x80-\xff])}A';

        while ($this->index < $this->len) {
            // check if we find the delimiter after some spaces/tabs
            switch ($this->contents[$this->index]) {
                case "\t":
                case " ":
                    $this->index += 1;
                    continue 2;
                case $firstDelimiterChar:
                    if (
                        substr($this->contents, $this->index, $delimiterLength) === $delimiter
                        && $this->match($delimiterPattern)
                    ) {
                        $this->index += $delimiterLength;

                        return;
                    }

                    break;
            }

            // skip the rest of the line
            $this->skipToNewline();

            // skip newlines
            $this->index += strspn($this->contents, "\r\n", $this->index);
        }
    }

    private function peek(string $char): bool
    {
        return $this->index + 1 < $this->len && $this->contents[$this->index + 1] === $char;
    }

    /**
     * @param non-empty-string $regex
     * @param null|array<mixed> $match
     * @param-out array<int|string, string> $match
     */
    private function match(string $regex, ?array &$match = null): bool
    {
        return Preg::isMatchStrictGroups($regex, $this->contents, $match, 0, $this->index);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/*
 * This file was initially based on a version from the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 */

namespace Composer\ClassMapGenerator;

use Composer\Pcre\Preg;
use Symfony\Component\Finder\Finder;

/**
 * ClassMapGenerator
 *
 * @author Gyula Sallai <salla016@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ClassMapGenerator
{
    /**
     * @var list<string>
     */
    private $extensions;

    /**
     * @var FileList|null
     */
    private $scannedFiles = null;

    /**
     * @var ClassMap
     */
    private $classMap;

    /**
     * @var non-empty-string
     */
    private $streamWrappersRegex;

    /**
     * @param list<string> $extensions File extensions to scan for classes in the given paths
     */
    public function __construct(array $extensions = ['php', 'inc'])
    {
        $this->extensions = $extensions;
        $this->classMap = new ClassMap;
        $this->streamWrappersRegex = \sprintf('{^(?:%s)://}', implode('|', array_map('preg_quote', stream_get_wrappers())));
    }

    /**
     * When calling scanPaths repeatedly with paths that may overlap, calling this will ensure that the same class is never scanned twice
     *
     * You can provide your own FileList instance or use the default one if you pass no argument
     *
     * @return $this
     */
    public function avoidDuplicateScans(?FileList $scannedFiles = null): self
    {
        $this->scannedFiles = $scannedFiles ?? new FileList;

        return $this;
    }

    /**
     * Iterate over all files in the given directory searching for classes
     *
     * @param string|\Traversable<\SplFileInfo>|array<\SplFileInfo> $path The path to search in or an array/traversable of SplFileInfo (e.g. symfony/finder instance)
     * @return array<class-string, non-empty-string> A class map array
     *
     * @throws \RuntimeException When the path is neither an existing file nor directory
     */
    public static function createMap($path): array
    {
        $generator = new self();

        $generator->scanPaths($path);

        return $generator->getClassMap()->getMap();
    }

    public function getClassMap(): ClassMap
    {
        return $this->classMap;
    }

    /**
     * Iterate over all files in the given directory searching for classes
     *
     * @param string|\Traversable<\SplFileInfo>|array<\SplFileInfo> $path         The path to search in or an array/traversable of SplFileInfo (e.g. symfony/finder instance)
     * @param non-empty-string|null                                 $excluded     Regex that matches file paths to be excluded from the classmap
     * @param 'classmap'|'psr-0'|'psr-4'                            $autoloadType Optional autoload standard to use mapping rules with the namespace instead of purely doing a classmap
     * @param string|null                                           $namespace    Optional namespace prefix to filter by, only for psr-0/psr-4 autoloading
     * @param array<string>                                         $excludedDirs Optional dirs to exclude from search relative to $path
     *
     * @throws \RuntimeException When the path is neither an existing file nor directory
     */
    public function scanPaths($path, ?string $excluded = null, string $autoloadType = 'classmap', ?string $namespace = null, array $excludedDirs = []): void
    {
        if (!\in_array($autoloadType, ['psr-0', 'psr-4', 'classmap'], true)) {
            throw new \InvalidArgumentException('$autoloadType must be one of: "psr-0", "psr-4" or "classmap"');
        }

        if ('classmap' !== $autoloadType) {
            if (!\is_string($path)) {
                throw new \InvalidArgumentException('$path must be a string when specifying a psr-0 or psr-4 autoload type');
            }
            if (!\is_string($namespace)) {
                throw new \InvalidArgumentException('$namespace must be given (even if it is an empty string if you do not want to filter) when specifying a psr-0 or psr-4 autoload type');
            }
            $basePath = $path;
        }

        if (\is_string($path)) {
            if (is_file($path)) {
                $path = [new \SplFileInfo($path)];
            } elseif (is_dir($path) || strpos($path, '*') !== false) {
                $path = Finder::create()
                    ->files()
                    ->followLinks()
                    ->name('/\.(?:'.implode('|', array_map('preg_quote', $this->extensions)).')$/')
                    ->in($path)
                    ->exclude($excludedDirs);
            } else {
                throw new \RuntimeException(
                    'Could not scan for classes inside "'.$path.'" which does not appear to be a file nor a folder'
                );
            }
        }

        $cwd = realpath(self::getCwd());

        foreach ($path as $file) {
            $filePath = $file->getPathname();
            if (!\in_array(pathinfo($filePath, PATHINFO_EXTENSION), $this->extensions, true)) {
                continue;
            }

            $isStreamWrapperPath = Preg::isMatch($this->streamWrappersRegex, $filePath);
            if (!self::isAbsolutePath($filePath) && !$isStreamWrapperPath) {
                $filePath = $cwd . '/' . $filePath;
                $filePath = self::normalizePath($filePath);
            } else {
                $filePath = Preg::replace('{(?<!:)[\\\\/]{2,}}', '/', $filePath);
            }

            if ('' === $filePath) {
                throw new \LogicException('Got an empty $filePath for '.$file->getPathname());
            }

            $realPath = $isStreamWrapperPath
                ? $filePath
                : realpath($filePath);

            // fallback just in case but this really should not happen
            if (false === $realPath) {
                throw new \RuntimeException('realpath of '.$filePath.' failed to resolve, got false');
            }

            // if a list of scanned files is given, avoid scanning twice the same file to save cycles and avoid generating warnings
            // in case a PSR-0/4 declaration follows another more specific one, or a classmap declaration, which covered this file already
            if ($this->scannedFiles !== null && $this->scannedFiles->contains($realPath)) {
                continue;
            }

            // check the realpath of the file against the excluded paths as the path might be a symlink and the excluded path is realpath'd so symlink are resolved
            if (null !== $excluded && Preg::isMatch($excluded, strtr($realPath, '\\', '/'))) {
                continue;
            }
            // check non-realpath of file for directories symlink in project dir
            if (null !== $excluded && Preg::isMatch($excluded, strtr($filePath, '\\', '/'))) {
                continue;
            }

            $classes = PhpFileParser::findClasses($filePath);
            if ('classmap' !== $autoloadType && isset($namespace)) {
                $classes = $this->filterByNamespace($classes, $filePath, $namespace, $autoloadType, $basePath);

                // if no valid class was found in the file then we do not mark it as scanned as it might still be matched by another rule later
                if (\count($classes) > 0 && $this->scannedFiles !== null) {
                    $this->scannedFiles->add($realPath);
                }
            } elseif ($this->scannedFiles !== null) {
                // classmap autoload rules always collect all classes so for these we definitely do not want to scan again
                $this->scannedFiles->add($realPath);
            }

            foreach ($classes as $class) {
                if (!$this->classMap->hasClass($class)) {
                    $this->classMap->addClass($class, $filePath);
                } elseif ($filePath !== $this->classMap->getClassPath($class)) {
                    $this->classMap->addAmbiguousClass($class, $filePath);
                }
            }
        }
    }

    /**
     * Remove classes which could not have been loaded by namespace autoloaders
     *
     * @param  array<int, class-string> $classes       found classes in given file
     * @param  string                   $filePath      current file
     * @param  string                   $baseNamespace prefix of given autoload mapping
     * @param  'psr-0'|'psr-4'          $namespaceType
     * @param  string                   $basePath      root directory of given autoload mapping
     * @return array<int, class-string> valid classes
     *
     * @throws \InvalidArgumentException When namespaceType is neither psr-0 nor psr-4
     */
    private function filterByNamespace(array $classes, string $filePath, string $baseNamespace, string $namespaceType, string $basePath): array
    {
        $validClasses = [];
        $rejectedClasses = [];

        $realSubPath = substr($filePath, \strlen($basePath) + 1);
        $dotPosition = strrpos($realSubPath, '.');
        $realSubPath = substr($realSubPath, 0, $dotPosition === false ? PHP_INT_MAX : $dotPosition);

        foreach ($classes as $class) {
            // transform class name to file path and validate
            if ('psr-0' === $namespaceType) {
                if ('' !== $baseNamespace && !str_starts_with($class, $baseNamespace)) {
                    $rejectedClasses[] = $class;
                    continue;
                }

                $namespaceLength = strrpos($class, '\\');
                if (false !== $namespaceLength) {
                    $namespace = substr($class, 0, $namespaceLength + 1);
                    $className = substr($class, $namespaceLength + 1);
                    $subPath = str_replace('\\', DIRECTORY_SEPARATOR, $namespace)
                        . str_replace('_', DIRECTORY_SEPARATOR, $className);
                } else {
                    $subPath = str_replace('_', DIRECTORY_SEPARATOR, $class);
                }
            } elseif ('psr-4' === $namespaceType) {
                $subNamespace = ('' !== $baseNamespace) ? substr($class, \strlen($baseNamespace)) : $class;
                $subPath = str_replace('\\', DIRECTORY_SEPARATOR, $subNamespace);
            } else {
                throw new \InvalidArgumentException('$namespaceType must be "psr-0" or "psr-4"');
            }
            if ($subPath === $realSubPath) {
                $validClasses[] = $class;
            } else {
                $rejectedClasses[] = $class;
            }
        }
        // warn only if no valid classes, else silently skip invalid
        if (\count($validClasses) === 0) {
            $cwd = realpath(self::getCwd());
            if ($cwd === false) {
                $cwd = self::getCwd();
            }
            $cwd = self::normalizePath($cwd);
            $shortPath = Preg::replace('{^'.preg_quote($cwd).'}', '.', self::normalizePath($filePath), 1);
            $shortBasePath = Preg::replace('{^'.preg_quote($cwd).'}', '.', self::normalizePath($basePath), 1);

            foreach ($rejectedClasses as $class) {
                $this->classMap->addPsrViolation("Class $class located in $shortPath does not comply with $namespaceType autoloading standard (rule: $baseNamespace => $shortBasePath). Skipping.", $class, $filePath);
            }

            return [];
        }

        return $validClasses;
    }

    /**
     * Checks if the given path is absolute
     *
     * @see Composer\Util\Filesystem::isAbsolutePath
     */
    private static function isAbsolutePath(string $path): bool
    {
        return strpos($path, '/') === 0 || substr($path, 1, 1) === ':' || strpos($path, '\\\\') === 0;
    }

    /**
     * Normalize a path. This replaces backslashes with slashes, removes ending
     * slash and collapses redundant separators and up-level references.
     *
     * @see Composer\Util\Filesystem::normalizePath
     *
     * @param  string $path Path to the file or directory
     */
    private static function normalizePath(string $path): string
    {
        $parts = [];
        $path = strtr($path, '\\', '/');
        $prefix = '';
        $absolute = '';

        // extract windows UNC paths e.g. \\foo\bar
        if (strpos($path, '//') === 0 && \strlen($path) > 2) {
            $absolute = '//';
            $path = substr($path, 2);
        }

        // extract a prefix being a protocol://, protocol:, protocol://drive: or simply drive:
        if (Preg::isMatchStrictGroups('{^( [0-9a-z]{2,}+: (?: // (?: [a-z]: )? )? | [a-z]: )}ix', $path, $match)) {
            $prefix = $match[1];
            $path = substr($path, \strlen($prefix));
        }

        if (strpos($path, '/') === 0) {
            $absolute = '/';
            $path = substr($path, 1);
        }

        $up = false;
        foreach (explode('/', $path) as $chunk) {
            if ('..' === $chunk && (\strlen($absolute) > 0 || $up)) {
                array_pop($parts);
                $up = !(\count($parts) === 0 || '..' === end($parts));
            } elseif ('.' !== $chunk && '' !== $chunk) {
                $parts[] = $chunk;
                $up = '..' !== $chunk;
            }
        }

        // ensure c: is normalized to C:
        $prefix = Preg::replaceCallback('{(?:^|://)[a-z]:$}i', static function (array $m) { return strtoupper((string) $m[0]); }, $prefix);

        return $prefix.$absolute.implode('/', $parts);
    }

    /**
     * @see Composer\Util\Platform::getCwd
     */
    private static function getCwd(): string
    {
        $cwd = getcwd();

        if (false === $cwd) {
            throw new \RuntimeException('Could not determine the current working directory');
        }

        return $cwd;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\ClassMapGenerator;

use RuntimeException;
use Composer\Pcre\Preg;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PhpFileParser
{
    /**
     * Extract the classes in the given file
     *
     * @param  string            $path The file to check
     * @throws RuntimeException
     * @return list<class-string> The found classes
     */
    public static function findClasses(string $path): array
    {
        $extraTypes = self::getExtraTypes();

        if (!\function_exists('php_strip_whitespace')) {
            throw new RuntimeException('Classmap generation relies on the php_strip_whitespace function, but it has been disabled by the disable_functions directive.');
        }

        // Use @ here instead of Silencer to actively suppress 'unhelpful' output
        // @link https://github.com/composer/composer/pull/4886
        $contents = @php_strip_whitespace($path);
        if ('' === $contents) {
            if (!file_exists($path)) {
                $message = 'File at "%s" does not exist, check your classmap definitions';
            } elseif (!self::isReadable($path)) {
                $message = 'File at "%s" is not readable, check its permissions';
            } elseif ('' === trim((string) file_get_contents($path))) {
                // The input file was really empty and thus contains no classes
                return [];
            } else {
                $message = 'File at "%s" could not be parsed as PHP, it may be binary or corrupted';
            }

            $error = error_get_last();
            if (isset($error['message'])) {
                $message .= PHP_EOL . 'The following message may be helpful:' . PHP_EOL . $error['message'];
            }

            throw new RuntimeException(\sprintf($message, $path));
        }

        // return early if there is no chance of matching anything in this file
        Preg::matchAllStrictGroups('{\b(?:class|interface|trait'.$extraTypes.')\s}i', $contents, $matches);
        if ([] === $matches[0]) {
            return [];
        }

        $p = new PhpFileCleaner($contents, \count($matches[0]));
        $contents = $p->clean();
        unset($p);

        Preg::matchAll('{
            (?:
                 \b(?<![\\\\$:>])(?P<type>class|interface|trait'.$extraTypes.') \s++ (?P<name>[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+)
               | \b(?<![\\\\$:>])(?P<ns>namespace) (?P<nsname>\s++[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\s*+\\\\\s*+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+)? \s*+ [\{;]
            )
        }ix', $contents, $matches);

        $classes = [];
        $namespace = '';

        for ($i = 0, $len = \count($matches['type']); $i < $len; ++$i) {
            if (isset($matches['ns'][$i]) && $matches['ns'][$i] !== '') {
                $namespace = str_replace([' ', "\t", "\r", "\n"], '', (string) $matches['nsname'][$i]) . '\\';
            } else {
                $name = $matches['name'][$i];
                \assert(\is_string($name));
                // skip anon classes extending/implementing
                if ($name === 'extends') {
                    continue;
                }
                if ($name === 'implements') {
                    continue;
                }

                if ($name[0] === ':') {
                    // This is an XHP class, https://github.com/facebook/xhp
                    $name = 'xhp'.substr(str_replace(['-', ':'], ['_', '__'], $name), 1);
                } elseif (strtolower((string) $matches['type'][$i]) === 'enum') {
                    // something like:
                    //   enum Foo: int { HERP = '123'; }
                    // The regex above captures the colon, which isn't part of
                    // the class name.
                    // or:
                    //   enum Foo:int { HERP = '123'; }
                    // The regex above captures the colon and type, which isn't part of
                    // the class name.
                    $colonPos = strrpos($name, ':');
                    if (false !== $colonPos) {
                        $name = substr($name, 0, $colonPos);
                    }
                }

                /** @var class-string */
                $className = ltrim($namespace . $name, '\\');
                $classes[] = $className;
            }
        }

        return $classes;
    }

    private static function getExtraTypes(): string
    {
        static $extraTypes = null;

        if (null === $extraTypes) {
            $extraTypes = '';
            $extraTypesArray = [];
            if (PHP_VERSION_ID >= 80100 || (\defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.3', '>='))) {
                $extraTypes .= '|enum';
                $extraTypesArray = ['enum'];
            }

            PhpFileCleaner::setTypeConfig(array_merge(['class', 'interface', 'trait'], $extraTypesArray));
        }

        return $extraTypes;
    }

    /**
     * Cross-platform safe version of is_readable()
     *
     * This will also check for readability by reading the file as is_readable can not be trusted on network-mounts
     * and \\wsl$ paths. See https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926
     *
     * @see Composer\Util\Filesystem::isReadable
     *
     * @return bool
     */
    private static function isReadable(string $path)
    {
        if (is_readable($path)) {
            return true;
        }

        if (is_file($path)) {
            return false !== @file_get_contents($path, false, null, 0, 1);
        }

        // assume false otherwise
        return false;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\ClassMapGenerator;

use Composer\Pcre\Preg;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ClassMap implements \Countable
{
    /**
     * @var array<class-string, non-empty-string>
     */
    public $map = [];

    /**
     * @var array<class-string, array<non-empty-string>>
     */
    private $ambiguousClasses = [];

    /**
     * @var array<string, array<array{warning: string, className: string}>>
     */
    private $psrViolations = [];

    /**
     * Returns the class map, which is a list of paths indexed by class name
     *
     * @return array<class-string, non-empty-string>
     */
    public function getMap(): array
    {
        return $this->map;
    }

    /**
     * Returns warning strings containing details about PSR-0/4 violations that were detected
     *
     * Violations are for ex a class which is in the wrong file/directory and thus should not be
     * found using psr-0/psr-4 autoloading but was found by the ClassMapGenerator as it scans all files.
     *
     * This is only happening when scanning paths using psr-0/psr-4 autoload type. Classmap type
     * always accepts every class as it finds it.
     *
     * @return string[]
     */
    public function getPsrViolations(): array
    {
        if (\count($this->psrViolations) === 0) {
            return [];
        }

        return array_map(static function (array $violation): string {
            return $violation['warning'];
        }, array_merge(...array_values($this->psrViolations)));
    }

    /**
     * A map of class names to their list of ambiguous paths
     *
     * This occurs when the same class can be found in several files
     *
     * To get the path the class is being mapped to, call getClassPath
     *
     * By default, paths that contain test(s), fixture(s), example(s) or stub(s) are ignored
     * as those are typically not problematic when they're dummy classes in the tests folder.
     * If you want to get these back as well you can pass false to $duplicatesFilter. Or
     * you can pass your own pattern to exclude if you need to change the default.
     *
     * @param non-empty-string|false $duplicatesFilter
     *
     * @return array<class-string, array<non-empty-string>>
     */
    public function getAmbiguousClasses($duplicatesFilter = '{/(test|fixture|example|stub)s?/}i'): array
    {
        if (false === $duplicatesFilter) {
            return $this->ambiguousClasses;
        }

        if (true === $duplicatesFilter) {
            throw new \InvalidArgumentException('$duplicatesFilter should be false or a string with a valid regex, got true.');
        }

        $ambiguousClasses = [];
        foreach ($this->ambiguousClasses as $class => $paths) {
            $paths = array_filter($paths, static function ($path) use ($duplicatesFilter): bool {
                return !Preg::isMatch($duplicatesFilter, strtr($path, '\\', '/'));
            });
            if (\count($paths) > 0) {
                $ambiguousClasses[$class] = array_values($paths);
            }
        }

        return $ambiguousClasses;
    }

    /**
     * Sorts the class map alphabetically by class names
     */
    public function sort(): void
    {
        ksort($this->map);
    }

    /**
     * @param class-string $className
     * @param non-empty-string $path
     */
    public function addClass(string $className, string $path): void
    {
        unset($this->psrViolations[strtr($path, '\\', '/')]);

        $this->map[$className] = $path;
    }

    /**
     * @param class-string $className
     * @return non-empty-string
     */
    public function getClassPath(string $className): string
    {
        if (!isset($this->map[$className])) {
            throw new \OutOfBoundsException('Class '.$className.' is not present in the map');
        }

        return $this->map[$className];
    }

    /**
     * @param class-string $className
     */
    public function hasClass(string $className): bool
    {
        return isset($this->map[$className]);
    }

    public function addPsrViolation(string $warning, string $className, string $path): void
    {
        $path = rtrim(strtr($path, '\\', '/'), '/');

        $this->psrViolations[$path][] = ['warning' => $warning, 'className' => $className];
    }

    public function clearPsrViolationsByPath(string $pathPrefix): void
    {
        $pathPrefix = rtrim(strtr($pathPrefix, '\\', '/'), '/');

        foreach ($this->psrViolations as $path => $violations) {
            if ($path === $pathPrefix || 0 === strpos($path, $pathPrefix.'/')) {
                unset($this->psrViolations[$path]);
            }
        }
    }

    /**
     * @param class-string $className
     * @param non-empty-string $path
     */
    public function addAmbiguousClass(string $className, string $path): void
    {
        $this->ambiguousClasses[$className][] = $path;
    }

    public function count(): int
    {
        return \count($this->map);
    }

    /**
     * Get the raw psr violations
     *
     * This is a map of filepath to an associative array of the warning string
     * and the offending class name.
     * @return array<string, array<array{warning: string, className: string}>>
     */
    public function getRawPsrViolations(): array
    {
        return $this->psrViolations;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\ClassMapGenerator;

/**
 * Contains a list of files which were scanned to generate a classmap
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class FileList
{
    /**
     * @var array<non-empty-string, true>
     */
    public $files = [];

    /**
     * @param non-empty-string $path
     */
    public function add(string $path): void
    {
        $this->files[$path] = true;
    }

    /**
     * @param non-empty-string $path
     */
    public function contains(string $path): bool
    {
        return isset($this->files[$path]);
    }
}
<?php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Autoload;

/**
 * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
 *
 *     $loader = new \Composer\Autoload\ClassLoader();
 *
 *     // register classes with namespaces
 *     $loader->add('Symfony\Component', __DIR__.'/component');
 *     $loader->add('Symfony',           __DIR__.'/framework');
 *
 *     // activate the autoloader
 *     $loader->register();
 *
 *     // to enable searching the include path (eg. for PEAR packages)
 *     $loader->setUseIncludePath(true);
 *
 * In this example, if you try to use a class in the Symfony\Component
 * namespace or one of its children (Symfony\Component\Console for instance),
 * the autoloader will first look for the class under the component/
 * directory, and it will then fallback to the framework/ directory if not
 * found before giving up.
 *
 * This class is loosely based on the Symfony UniversalClassLoader.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @see    https://www.php-fig.org/psr/psr-0/
 * @see    https://www.php-fig.org/psr/psr-4/
 */
class ClassLoader
{
    /** @var \Closure(string):void */
    private static $includeFile;

    /** @var string|null */
    private $vendorDir;

    // PSR-4
    /**
     * @var array<string, array<string, int>>
     */
    private $prefixLengthsPsr4 = array();
    /**
     * @var array<string, list<string>>
     */
    private $prefixDirsPsr4 = array();
    /**
     * @var list<string>
     */
    private $fallbackDirsPsr4 = array();

    // PSR-0
    /**
     * List of PSR-0 prefixes
     *
     * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
     *
     * @var array<string, array<string, list<string>>>
     */
    private $prefixesPsr0 = array();
    /**
     * @var list<string>
     */
    private $fallbackDirsPsr0 = array();

    /** @var bool */
    private $useIncludePath = false;

    /**
     * @var array<string, string>
     */
    private $classMap = array();

    /** @var bool */
    private $classMapAuthoritative = false;

    /**
     * @var array<string, bool>
     */
    private $missingClasses = array();

    /** @var string|null */
    private $apcuPrefix;

    /**
     * @var array<string, self>
     */
    private static $registeredLoaders = array();

    /**
     * @param string|null $vendorDir
     */
    public function __construct($vendorDir = null)
    {
        $this->vendorDir = $vendorDir;
        self::initializeIncludeClosure();
    }

    /**
     * @return array<string, list<string>>
     */
    public function getPrefixes()
    {
        if (!empty($this->prefixesPsr0)) {
            return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
        }

        return array();
    }

    /**
     * @return array<string, list<string>>
     */
    public function getPrefixesPsr4()
    {
        return $this->prefixDirsPsr4;
    }

    /**
     * @return list<string>
     */
    public function getFallbackDirs()
    {
        return $this->fallbackDirsPsr0;
    }

    /**
     * @return list<string>
     */
    public function getFallbackDirsPsr4()
    {
        return $this->fallbackDirsPsr4;
    }

    /**
     * @return array<string, string> Array of classname => path
     */
    public function getClassMap()
    {
        return $this->classMap;
    }

    /**
     * @param array<string, string> $classMap Class to filename map
     *
     * @return void
     */
    public function addClassMap(array $classMap)
    {
        if ($this->classMap) {
            $this->classMap = array_merge($this->classMap, $classMap);
        } else {
            $this->classMap = $classMap;
        }
    }

    /**
     * Registers a set of PSR-0 directories for a given prefix, either
     * appending or prepending to the ones previously set for this prefix.
     *
     * @param string              $prefix  The prefix
     * @param list<string>|string $paths   The PSR-0 root directories
     * @param bool                $prepend Whether to prepend the directories
     *
     * @return void
     */
    public function add($prefix, $paths, $prepend = false)
    {
        $paths = (array) $paths;
        if (!$prefix) {
            if ($prepend) {
                $this->fallbackDirsPsr0 = array_merge(
                    $paths,
                    $this->fallbackDirsPsr0
                );
            } else {
                $this->fallbackDirsPsr0 = array_merge(
                    $this->fallbackDirsPsr0,
                    $paths
                );
            }

            return;
        }

        $first = $prefix[0];
        if (!isset($this->prefixesPsr0[$first][$prefix])) {
            $this->prefixesPsr0[$first][$prefix] = $paths;

            return;
        }
        if ($prepend) {
            $this->prefixesPsr0[$first][$prefix] = array_merge(
                $paths,
                $this->prefixesPsr0[$first][$prefix]
            );
        } else {
            $this->prefixesPsr0[$first][$prefix] = array_merge(
                $this->prefixesPsr0[$first][$prefix],
                $paths
            );
        }
    }

    /**
     * Registers a set of PSR-4 directories for a given namespace, either
     * appending or prepending to the ones previously set for this namespace.
     *
     * @param string              $prefix  The prefix/namespace, with trailing '\\'
     * @param list<string>|string $paths   The PSR-4 base directories
     * @param bool                $prepend Whether to prepend the directories
     *
     * @throws \InvalidArgumentException
     *
     * @return void
     */
    public function addPsr4($prefix, $paths, $prepend = false)
    {
        $paths = (array) $paths;
        if (!$prefix) {
            // Register directories for the root namespace.
            if ($prepend) {
                $this->fallbackDirsPsr4 = array_merge(
                    $paths,
                    $this->fallbackDirsPsr4
                );
            } else {
                $this->fallbackDirsPsr4 = array_merge(
                    $this->fallbackDirsPsr4,
                    $paths
                );
            }
        } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
            // Register directories for a new namespace.
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            $this->prefixDirsPsr4[$prefix] = $paths;
        } elseif ($prepend) {
            // Prepend directories for an already registered namespace.
            $this->prefixDirsPsr4[$prefix] = array_merge(
                $paths,
                $this->prefixDirsPsr4[$prefix]
            );
        } else {
            // Append directories for an already registered namespace.
            $this->prefixDirsPsr4[$prefix] = array_merge(
                $this->prefixDirsPsr4[$prefix],
                $paths
            );
        }
    }

    /**
     * Registers a set of PSR-0 directories for a given prefix,
     * replacing any others previously set for this prefix.
     *
     * @param string              $prefix The prefix
     * @param list<string>|string $paths  The PSR-0 base directories
     *
     * @return void
     */
    public function set($prefix, $paths)
    {
        if (!$prefix) {
            $this->fallbackDirsPsr0 = (array) $paths;
        } else {
            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
        }
    }

    /**
     * Registers a set of PSR-4 directories for a given namespace,
     * replacing any others previously set for this namespace.
     *
     * @param string              $prefix The prefix/namespace, with trailing '\\'
     * @param list<string>|string $paths  The PSR-4 base directories
     *
     * @throws \InvalidArgumentException
     *
     * @return void
     */
    public function setPsr4($prefix, $paths)
    {
        if (!$prefix) {
            $this->fallbackDirsPsr4 = (array) $paths;
        } else {
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            $this->prefixDirsPsr4[$prefix] = (array) $paths;
        }
    }

    /**
     * Turns on searching the include path for class files.
     *
     * @param bool $useIncludePath
     *
     * @return void
     */
    public function setUseIncludePath($useIncludePath)
    {
        $this->useIncludePath = $useIncludePath;
    }

    /**
     * Can be used to check if the autoloader uses the include path to check
     * for classes.
     *
     * @return bool
     */
    public function getUseIncludePath()
    {
        return $this->useIncludePath;
    }

    /**
     * Turns off searching the prefix and fallback directories for classes
     * that have not been registered with the class map.
     *
     * @param bool $classMapAuthoritative
     *
     * @return void
     */
    public function setClassMapAuthoritative($classMapAuthoritative)
    {
        $this->classMapAuthoritative = $classMapAuthoritative;
    }

    /**
     * Should class lookup fail if not found in the current class map?
     *
     * @return bool
     */
    public function isClassMapAuthoritative()
    {
        return $this->classMapAuthoritative;
    }

    /**
     * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
     *
     * @param string|null $apcuPrefix
     *
     * @return void
     */
    public function setApcuPrefix($apcuPrefix)
    {
        $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
    }

    /**
     * The APCu prefix in use, or null if APCu caching is not enabled.
     *
     * @return string|null
     */
    public function getApcuPrefix()
    {
        return $this->apcuPrefix;
    }

    /**
     * Registers this instance as an autoloader.
     *
     * @param bool $prepend Whether to prepend the autoloader or not
     *
     * @return void
     */
    public function register($prepend = false)
    {
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);

        if (null === $this->vendorDir) {
            return;
        }

        if ($prepend) {
            self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
        } else {
            unset(self::$registeredLoaders[$this->vendorDir]);
            self::$registeredLoaders[$this->vendorDir] = $this;
        }
    }

    /**
     * Unregisters this instance as an autoloader.
     *
     * @return void
     */
    public function unregister()
    {
        spl_autoload_unregister(array($this, 'loadClass'));

        if (null !== $this->vendorDir) {
            unset(self::$registeredLoaders[$this->vendorDir]);
        }
    }

    /**
     * Loads the given class or interface.
     *
     * @param  string    $class The name of the class
     * @return true|null True if loaded, null otherwise
     */
    public function loadClass($class)
    {
        if ($file = $this->findFile($class)) {
            $includeFile = self::$includeFile;
            $includeFile($file);

            return true;
        }

        return null;
    }

    /**
     * Finds the path to the file where the class is defined.
     *
     * @param string $class The name of the class
     *
     * @return string|false The path if found, false otherwise
     */
    public function findFile($class)
    {
        // class map lookup
        if (isset($this->classMap[$class])) {
            return $this->classMap[$class];
        }
        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
            return false;
        }
        if (null !== $this->apcuPrefix) {
            $file = apcu_fetch($this->apcuPrefix.$class, $hit);
            if ($hit) {
                return $file;
            }
        }

        $file = $this->findFileWithExtension($class, '.php');

        // Search for Hack files if we are running on HHVM
        if (false === $file && defined('HHVM_VERSION')) {
            $file = $this->findFileWithExtension($class, '.hh');
        }

        if (null !== $this->apcuPrefix) {
            apcu_add($this->apcuPrefix.$class, $file);
        }

        if (false === $file) {
            // Remember that this class does not exist.
            $this->missingClasses[$class] = true;
        }

        return $file;
    }

    /**
     * Returns the currently registered loaders keyed by their corresponding vendor directories.
     *
     * @return array<string, self>
     */
    public static function getRegisteredLoaders()
    {
        return self::$registeredLoaders;
    }

    /**
     * @param  string       $class
     * @param  string       $ext
     * @return string|false
     */
    private function findFileWithExtension($class, $ext)
    {
        // PSR-4 lookup
        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

        $first = $class[0];
        if (isset($this->prefixLengthsPsr4[$first])) {
            $subPath = $class;
            while (false !== $lastPos = strrpos($subPath, '\\')) {
                $subPath = substr($subPath, 0, $lastPos);
                $search = $subPath . '\\';
                if (isset($this->prefixDirsPsr4[$search])) {
                    $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
                    foreach ($this->prefixDirsPsr4[$search] as $dir) {
                        if (file_exists($file = $dir . $pathEnd)) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-4 fallback dirs
        foreach ($this->fallbackDirsPsr4 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
                return $file;
            }
        }

        // PSR-0 lookup
        if (false !== $pos = strrpos($class, '\\')) {
            // namespaced class name
            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
        } else {
            // PEAR-like class name
            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
        }

        if (isset($this->prefixesPsr0[$first])) {
            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
                if (0 === strpos($class, $prefix)) {
                    foreach ($dirs as $dir) {
                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-0 fallback dirs
        foreach ($this->fallbackDirsPsr0 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                return $file;
            }
        }

        // PSR-0 include paths.
        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
            return $file;
        }

        return false;
    }

    /**
     * @return void
     */
    private static function initializeIncludeClosure()
    {
        if (self::$includeFile !== null) {
            return;
        }

        /**
         * Scope isolated include.
         *
         * Prevents access to $this/self from included files.
         *
         * @param  string $file
         * @return void
         */
        self::$includeFile = \Closure::bind(static function($file) {
            include $file;
        }, null, null);
    }
}
<?php

// autoload_namespaces.php @generated by Composer

$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);

return array(
);
<?php

// autoload_psr4.php @generated by Composer

$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);

return array(
    'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'),
    'ThePhpFoundation\\Attestation\\' => array($vendorDir . '/thephpf/attestation/src'),
    'Symfony\\Polyfill\\Php84\\' => array($vendorDir . '/symfony/polyfill-php84'),
    'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
    'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'),
    'Symfony\\Polyfill\\Intl\\Grapheme\\' => array($vendorDir . '/symfony/polyfill-intl-grapheme'),
    'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
    'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'),
    'Symfony\\Contracts\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher-contracts'),
    'Symfony\\Component\\String\\' => array($vendorDir . '/symfony/string'),
    'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'),
    'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'),
    'Symfony\\Component\\Filesystem\\' => array($vendorDir . '/symfony/filesystem'),
    'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'),
    'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'),
    'Seld\\Signal\\' => array($vendorDir . '/seld/signal-handler/src'),
    'Seld\\PharUtils\\' => array($vendorDir . '/seld/phar-utils/src'),
    'Seld\\JsonLint\\' => array($vendorDir . '/seld/jsonlint/src/Seld/JsonLint'),
    'React\\Promise\\' => array($vendorDir . '/react/promise/src'),
    'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
    'Psr\\Log\\' => array($vendorDir . '/psr/log/src'),
    'Psr\\EventDispatcher\\' => array($vendorDir . '/psr/event-dispatcher/src'),
    'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
    'Php\\Pie\\' => array($baseDir . '/src'),
    'MabeEnum\\' => array($vendorDir . '/marc-mabe/php-enum/src'),
    'JsonSchema\\' => array($vendorDir . '/justinrainbow/json-schema/src/JsonSchema'),
    'Illuminate\\Contracts\\' => array($vendorDir . '/illuminate/contracts'),
    'Illuminate\\Container\\' => array($vendorDir . '/illuminate/container'),
    'Fidry\\CpuCoreCounter\\' => array($vendorDir . '/fidry/cpu-core-counter/src'),
    'Composer\\XdebugHandler\\' => array($vendorDir . '/composer/xdebug-handler/src'),
    'Composer\\Spdx\\' => array($vendorDir . '/composer/spdx-licenses/src'),
    'Composer\\Semver\\' => array($vendorDir . '/composer/semver/src'),
    'Composer\\Pcre\\' => array($vendorDir . '/composer/pcre/src'),
    'Composer\\MetadataMinifier\\' => array($vendorDir . '/composer/metadata-minifier/src'),
    'Composer\\ClassMapGenerator\\' => array($vendorDir . '/composer/class-map-generator/src'),
    'Composer\\CaBundle\\' => array($vendorDir . '/composer/ca-bundle/src'),
    'Composer\\' => array($vendorDir . '/composer/composer/src/Composer'),
);

Copyright (c) Nils Adermann, Jordi Boggiano

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.

<?php

// autoload_static.php @generated by Composer

namespace Composer\Autoload;

class ComposerStaticInit020913853b4b516248bbc0df3c21c772
{
    public static $files = array (
        '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
        '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
        '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
        '8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php',
        'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php',
        'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php',
        'ad155f8f1cf0d418fe49e248db8c661b' => __DIR__ . '/..' . '/react/promise/src/functions_include.php',
        '9d2b9fc6db0f153a0a149fefb182415e' => __DIR__ . '/..' . '/symfony/polyfill-php84/bootstrap.php',
        '72243e5536b63e298acb6476f01f1aff' => __DIR__ . '/..' . '/thecodingmachine/safe/lib/special_cases.php',
        '3f648889e687f31c52f949ba8a9d0873' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/apache.php',
        'eeb4581d958421a4244aaa4167c6a575' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/apcu.php',
        '04cb0b3c1dac5b5ddb23c14e3d66dbe9' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/array.php',
        '450b332a74a9a21e043c5e953485a791' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/bzip2.php',
        '6e9b7954ecfd7cbb9ca239319d1acdb6' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/calendar.php',
        '2c6d7e8bd2de9a272a9d4d43b0a4304a' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/classobj.php',
        '0b8231c1ad0865447c988a4c16b4001f' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/com.php',
        '7643a71fe1c3256058c8fee234cb86e5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/cubrid.php',
        '68e1365710575942efc1d55000032cee' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/curl.php',
        '02fd26bca803106c5b942a7197c3ad8b' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/datetime.php',
        'f4817dcbd956cd221b1c31f6fbd5749c' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/dir.php',
        '51c3f2d10ca61a70dbcea0e38d8e902d' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/eio.php',
        '1d34f34327ca3e81535963016e3be2c3' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/errorfunc.php',
        '4fd0ba2d3717b0424d474bebfdafa2b4' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/exec.php',
        '98f4dae054bc7fb19c13be14935cbdd3' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/fileinfo.php',
        '5530ae063ba88323eaf0a07904efdf85' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/filesystem.php',
        '633f4f134975d70e97bddad83348e91a' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/filter.php',
        'fbd163fc68c5faf73d5ed4002ffd836d' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/fpm.php',
        '21b511999d61411fab0692ff8795bbed' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ftp.php',
        '85fbd73fc92365cd90526b0ea03cae3a' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/funchand.php',
        'a2e4c6dfdbf36f56f1945ddcbd54e289' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/gettext.php',
        '51df9c146e0b7dcbdf358d8abd24dbdc' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/gmp.php',
        '93bb7fe678d7dcfb1322f8e3475a48b0' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/gnupg.php',
        'c171ba99cf316379ff66468392bf4950' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/hash.php',
        '5ab4aad4c28e468209fbfcceb2e5e6a5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ibase.php',
        '4d57409c5e8e576b0c64c08d9d731cfb' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ibmDb2.php',
        'eeb246d5403972a9d62106e4a4883496' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/iconv.php',
        'c28a05f498c01b810a714f7214b7a8da' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/image.php',
        '8063cd92acdf00fd978b5599eb7cc142' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/imap.php',
        '8bd26dbe768e9c9599edad7b198e5446' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/info.php',
        'd4362910bde43c0f956b52527effd7d4' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/inotify.php',
        '696ba49197d9b55f0428a12bb5a818e1' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/json.php',
        '9818aaa99c8647c63f8ef62b7a368160' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ldap.php',
        'bcf523ff2a195eb08e0fbb668ed784d0' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/libxml.php',
        '68be68a9a8b95bb56cab6109ff03bc88' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/lzf.php',
        'bdca804bb0904ea9f53f328dfc0bb8a5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/mailparse.php',
        'b0a3fcac3eaf55445796d6af26b89366' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/mbstring.php',
        '98de16b8db03eb0cb4d318b4402215a6' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/misc.php',
        '7cefd81607cd21b8b3a15656eb6465f5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/mysql.php',
        'aaf438b080089c6d0686679cd34aa72e' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/mysqli.php',
        'cbac956836b72483dcff1ac39d5c0a0f' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/network.php',
        '6c8f89dfbdc117d7871f572269363f25' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/oci8.php',
        '169a669966a45c06bf55ed029122729b' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/opcache.php',
        'def61bf4fecd4d4bca7354919cd69302' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/openssl.php',
        '26bb010649a6d32d4120181458aa6ef2' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/outcontrol.php',
        '002ebcb842e2c0d5b7f67fe64cc93158' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/pcntl.php',
        '86df38612982dade72c7085ce7eca81f' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/pcre.php',
        '1fc22f445c69ea8706e82fce301c0831' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/pgsql.php',
        'c70b42561584f7144bff38cd63c4eef3' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/posix.php',
        '9923214639c32ca5173db03a177d3b63' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ps.php',
        '7e9c3f8eae2b5bf42205c4f1295cb7a7' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/pspell.php',
        '91aa91f6245c349c2e2e88bd0025f199' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/readline.php',
        'ccf972e9e1fd8935f45f95e13b54ad59' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/rnp.php',
        'd43773cacb9e5e8e897aa255e32007d1' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/rpminfo.php',
        'f053a3849e9e8383762b34b91db0320b' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/rrd.php',
        '775b964f72f827a1bf87c65ab5b10800' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/sem.php',
        '816428bd69c29ab5e1ed622af5dca0cd' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/session.php',
        '5093e233bedbefaef0df262bfbab0a5c' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/shmop.php',
        'b080617b1d949683c2e37f8f01dc0e15' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/sockets.php',
        '2708aa182ddcfe6ce27c96acaaa40f69' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/sodium.php',
        'f1b96cb260a5baeea9a7285cda82a1ec' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/solr.php',
        '3fd8853757d0fe3557c179efb807afeb' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/spl.php',
        '9312ce96a51c846913fcda5f186d58dd' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/sqlsrv.php',
        'd3eb383ad0b8b962b29dc4afd29d6715' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ssdeep.php',
        '42a09bc448f441a0b9f9367ea975c0bf' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/ssh2.php',
        'ef711077d356d1b33ca0b10b67b0be8f' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/stream.php',
        '764b09f6df081cbb2807b97c6ace3866' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/strings.php',
        'ef241678769fee4a44aaa288f3b78aa1' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/swoole.php',
        '0efc8f6778cba932b9e2a89e28de2452' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/uodbc.php',
        'd383d32907b98af53ee9208c62204fd0' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/uopz.php',
        '2fd2e4060f7fe772660f002ce38f0b71' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/url.php',
        '782249e03deebeaf57b9991ff5493aa0' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/var.php',
        '344440cd1cd7200fdb4f12af0d3c587f' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/xdiff.php',
        '3599f369219c658a5fb6c4fe66832f62' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/xml.php',
        '7fcd313da9fae337051b091b3492c21b' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/xmlrpc.php',
        'd668c74cfa92d893b582356733d9a80e' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/yaml.php',
        '4af1dca6db8c527c6eed27bff85ff0e5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/yaz.php',
        'fe43ca06499ac37bc2dedd823af71eb5' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/zip.php',
        '356736db98a6834f0a886b8d509b0ecd' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/zlib.php',
    );

    public static $prefixLengthsPsr4 = array (
        'W' =>
        array (
            'Webmozart\\Assert\\' => 17,
        ),
        'T' =>
        array (
            'ThePhpFoundation\\Attestation\\' => 29,
        ),
        'S' =>
        array (
            'Symfony\\Polyfill\\Php84\\' => 23,
            'Symfony\\Polyfill\\Mbstring\\' => 26,
            'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33,
            'Symfony\\Polyfill\\Intl\\Grapheme\\' => 31,
            'Symfony\\Polyfill\\Ctype\\' => 23,
            'Symfony\\Contracts\\Service\\' => 26,
            'Symfony\\Contracts\\EventDispatcher\\' => 34,
            'Symfony\\Component\\String\\' => 25,
            'Symfony\\Component\\Process\\' => 26,
            'Symfony\\Component\\Finder\\' => 25,
            'Symfony\\Component\\Filesystem\\' => 29,
            'Symfony\\Component\\EventDispatcher\\' => 34,
            'Symfony\\Component\\Console\\' => 26,
            'Seld\\Signal\\' => 12,
            'Seld\\PharUtils\\' => 15,
            'Seld\\JsonLint\\' => 14,
        ),
        'R' =>
        array (
            'React\\Promise\\' => 14,
        ),
        'P' =>
        array (
            'Psr\\SimpleCache\\' => 16,
            'Psr\\Log\\' => 8,
            'Psr\\EventDispatcher\\' => 20,
            'Psr\\Container\\' => 14,
            'Php\\Pie\\' => 8,
        ),
        'M' =>
        array (
            'MabeEnum\\' => 9,
        ),
        'J' =>
        array (
            'JsonSchema\\' => 11,
        ),
        'I' =>
        array (
            'Illuminate\\Contracts\\' => 21,
            'Illuminate\\Container\\' => 21,
        ),
        'F' =>
        array (
            'Fidry\\CpuCoreCounter\\' => 21,
        ),
        'C' =>
        array (
            'Composer\\XdebugHandler\\' => 23,
            'Composer\\Spdx\\' => 14,
            'Composer\\Semver\\' => 16,
            'Composer\\Pcre\\' => 14,
            'Composer\\MetadataMinifier\\' => 26,
            'Composer\\ClassMapGenerator\\' => 27,
            'Composer\\CaBundle\\' => 18,
            'Composer\\' => 9,
        ),
    );

    public static $prefixDirsPsr4 = array (
        'Webmozart\\Assert\\' =>
        array (
            0 => __DIR__ . '/..' . '/webmozart/assert/src',
        ),
        'ThePhpFoundation\\Attestation\\' =>
        array (
            0 => __DIR__ . '/..' . '/thephpf/attestation/src',
        ),
        'Symfony\\Polyfill\\Php84\\' =>
        array (
            0 => __DIR__ . '/..' . '/symfony/polyfill-php84',
        ),
        'Symfony\\Polyfill\\Mbstring\\' =>
        array (
            0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
        ),
        'Symfony\\Polyfill\\Intl\\Normalizer\\' =>
        array (
            0 => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer',
        ),
        'Symfony\\Polyfill\\Intl\\Grapheme\\' =>
        array (
            0 => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme',
        ),
        'Symfony\\Polyfill\\Ctype\\' =>
        array (
            0 => __DIR__ . '/..' . '/symfony/polyfill-ctype',
        ),
        'Symfony\\Contracts\\Service\\' =>
        array (
            0 => __DIR__ . '/..' . '/symfony/service-contracts',
        ),
        'Symfony\\Contracts\\EventDispatcher\\' =>
        array (
            0 => __DIR__ . '/..' . '/symfony/event-dispatcher-contracts',
        ),
        'Symfony\\Component\\String\\' =>
        array (
            0 => __DIR__ . '/..' . '/symfony/string',
        ),
        'Symfony\\Component\\Process\\' =>
        array (
            0 => __DIR__ . '/..' . '/symfony/process',
        ),
        'Symfony\\Component\\Finder\\' =>
        array (
            0 => __DIR__ . '/..' . '/symfony/finder',
        ),
        'Symfony\\Component\\Filesystem\\' =>
        array (
            0 => __DIR__ . '/..' . '/symfony/filesystem',
        ),
        'Symfony\\Component\\EventDispatcher\\' =>
        array (
            0 => __DIR__ . '/..' . '/symfony/event-dispatcher',
        ),
        'Symfony\\Component\\Console\\' =>
        array (
            0 => __DIR__ . '/..' . '/symfony/console',
        ),
        'Seld\\Signal\\' =>
        array (
            0 => __DIR__ . '/..' . '/seld/signal-handler/src',
        ),
        'Seld\\PharUtils\\' =>
        array (
            0 => __DIR__ . '/..' . '/seld/phar-utils/src',
        ),
        'Seld\\JsonLint\\' =>
        array (
            0 => __DIR__ . '/..' . '/seld/jsonlint/src/Seld/JsonLint',
        ),
        'React\\Promise\\' =>
        array (
            0 => __DIR__ . '/..' . '/react/promise/src',
        ),
        'Psr\\SimpleCache\\' =>
        array (
            0 => __DIR__ . '/..' . '/psr/simple-cache/src',
        ),
        'Psr\\Log\\' =>
        array (
            0 => __DIR__ . '/..' . '/psr/log/src',
        ),
        'Psr\\EventDispatcher\\' =>
        array (
            0 => __DIR__ . '/..' . '/psr/event-dispatcher/src',
        ),
        'Psr\\Container\\' =>
        array (
            0 => __DIR__ . '/..' . '/psr/container/src',
        ),
        'Php\\Pie\\' =>
        array (
            0 => __DIR__ . '/../..' . '/src',
        ),
        'MabeEnum\\' =>
        array (
            0 => __DIR__ . '/..' . '/marc-mabe/php-enum/src',
        ),
        'JsonSchema\\' =>
        array (
            0 => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema',
        ),
        'Illuminate\\Contracts\\' =>
        array (
            0 => __DIR__ . '/..' . '/illuminate/contracts',
        ),
        'Illuminate\\Container\\' =>
        array (
            0 => __DIR__ . '/..' . '/illuminate/container',
        ),
        'Fidry\\CpuCoreCounter\\' =>
        array (
            0 => __DIR__ . '/..' . '/fidry/cpu-core-counter/src',
        ),
        'Composer\\XdebugHandler\\' =>
        array (
            0 => __DIR__ . '/..' . '/composer/xdebug-handler/src',
        ),
        'Composer\\Spdx\\' =>
        array (
            0 => __DIR__ . '/..' . '/composer/spdx-licenses/src',
        ),
        'Composer\\Semver\\' =>
        array (
            0 => __DIR__ . '/..' . '/composer/semver/src',
        ),
        'Composer\\Pcre\\' =>
        array (
            0 => __DIR__ . '/..' . '/composer/pcre/src',
        ),
        'Composer\\MetadataMinifier\\' =>
        array (
            0 => __DIR__ . '/..' . '/composer/metadata-minifier/src',
        ),
        'Composer\\ClassMapGenerator\\' =>
        array (
            0 => __DIR__ . '/..' . '/composer/class-map-generator/src',
        ),
        'Composer\\CaBundle\\' =>
        array (
            0 => __DIR__ . '/..' . '/composer/ca-bundle/src',
        ),
        'Composer\\' =>
        array (
            0 => __DIR__ . '/..' . '/composer/composer/src/Composer',
        ),
    );

    public static $classMap = array (
        'Composer\\Advisory\\AuditConfig' => __DIR__ . '/..' . '/composer/composer/src/Composer/Advisory/AuditConfig.php',
        'Composer\\Advisory\\Auditor' => __DIR__ . '/..' . '/composer/composer/src/Composer/Advisory/Auditor.php',
        'Composer\\Advisory\\IgnoredSecurityAdvisory' => __DIR__ . '/..' . '/composer/composer/src/Composer/Advisory/IgnoredSecurityAdvisory.php',
        'Composer\\Advisory\\PartialSecurityAdvisory' => __DIR__ . '/..' . '/composer/composer/src/Composer/Advisory/PartialSecurityAdvisory.php',
        'Composer\\Advisory\\SecurityAdvisory' => __DIR__ . '/..' . '/composer/composer/src/Composer/Advisory/SecurityAdvisory.php',
        'Composer\\Autoload\\AutoloadGenerator' => __DIR__ . '/..' . '/composer/composer/src/Composer/Autoload/AutoloadGenerator.php',
        'Composer\\Autoload\\ClassLoader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Autoload/ClassLoader.php',
        'Composer\\Autoload\\ClassMapGenerator' => __DIR__ . '/..' . '/composer/composer/src/Composer/Autoload/ClassMapGenerator.php',
        'Composer\\CaBundle\\CaBundle' => __DIR__ . '/..' . '/composer/ca-bundle/src/CaBundle.php',
        'Composer\\Cache' => __DIR__ . '/..' . '/composer/composer/src/Composer/Cache.php',
        'Composer\\ClassMapGenerator\\ClassMap' => __DIR__ . '/..' . '/composer/class-map-generator/src/ClassMap.php',
        'Composer\\ClassMapGenerator\\ClassMapGenerator' => __DIR__ . '/..' . '/composer/class-map-generator/src/ClassMapGenerator.php',
        'Composer\\ClassMapGenerator\\FileList' => __DIR__ . '/..' . '/composer/class-map-generator/src/FileList.php',
        'Composer\\ClassMapGenerator\\PhpFileCleaner' => __DIR__ . '/..' . '/composer/class-map-generator/src/PhpFileCleaner.php',
        'Composer\\ClassMapGenerator\\PhpFileParser' => __DIR__ . '/..' . '/composer/class-map-generator/src/PhpFileParser.php',
        'Composer\\Command\\AboutCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/AboutCommand.php',
        'Composer\\Command\\ArchiveCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ArchiveCommand.php',
        'Composer\\Command\\AuditCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/AuditCommand.php',
        'Composer\\Command\\BaseCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/BaseCommand.php',
        'Composer\\Command\\BaseConfigCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/BaseConfigCommand.php',
        'Composer\\Command\\BaseDependencyCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/BaseDependencyCommand.php',
        'Composer\\Command\\BumpCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/BumpCommand.php',
        'Composer\\Command\\CheckPlatformReqsCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/CheckPlatformReqsCommand.php',
        'Composer\\Command\\ClearCacheCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ClearCacheCommand.php',
        'Composer\\Command\\CompletionTrait' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/CompletionTrait.php',
        'Composer\\Command\\ConfigCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ConfigCommand.php',
        'Composer\\Command\\CreateProjectCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/CreateProjectCommand.php',
        'Composer\\Command\\DependsCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/DependsCommand.php',
        'Composer\\Command\\DiagnoseCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/DiagnoseCommand.php',
        'Composer\\Command\\DumpAutoloadCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/DumpAutoloadCommand.php',
        'Composer\\Command\\ExecCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ExecCommand.php',
        'Composer\\Command\\FundCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/FundCommand.php',
        'Composer\\Command\\GlobalCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/GlobalCommand.php',
        'Composer\\Command\\HomeCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/HomeCommand.php',
        'Composer\\Command\\InitCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/InitCommand.php',
        'Composer\\Command\\InstallCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/InstallCommand.php',
        'Composer\\Command\\LicensesCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/LicensesCommand.php',
        'Composer\\Command\\OutdatedCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/OutdatedCommand.php',
        'Composer\\Command\\PackageDiscoveryTrait' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/PackageDiscoveryTrait.php',
        'Composer\\Command\\ProhibitsCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ProhibitsCommand.php',
        'Composer\\Command\\ReinstallCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ReinstallCommand.php',
        'Composer\\Command\\RemoveCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/RemoveCommand.php',
        'Composer\\Command\\RepositoryCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/RepositoryCommand.php',
        'Composer\\Command\\RequireCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/RequireCommand.php',
        'Composer\\Command\\RunScriptCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/RunScriptCommand.php',
        'Composer\\Command\\ScriptAliasCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ScriptAliasCommand.php',
        'Composer\\Command\\SearchCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/SearchCommand.php',
        'Composer\\Command\\SelfUpdateCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/SelfUpdateCommand.php',
        'Composer\\Command\\ShowCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ShowCommand.php',
        'Composer\\Command\\StatusCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/StatusCommand.php',
        'Composer\\Command\\SuggestsCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/SuggestsCommand.php',
        'Composer\\Command\\UpdateCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/UpdateCommand.php',
        'Composer\\Command\\ValidateCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ValidateCommand.php',
        'Composer\\Compiler' => __DIR__ . '/..' . '/composer/composer/src/Composer/Compiler.php',
        'Composer\\Composer' => __DIR__ . '/..' . '/composer/composer/src/Composer/Composer.php',
        'Composer\\Config' => __DIR__ . '/..' . '/composer/composer/src/Composer/Config.php',
        'Composer\\Config\\ConfigSourceInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Config/ConfigSourceInterface.php',
        'Composer\\Config\\JsonConfigSource' => __DIR__ . '/..' . '/composer/composer/src/Composer/Config/JsonConfigSource.php',
        'Composer\\Console\\Application' => __DIR__ . '/..' . '/composer/composer/src/Composer/Console/Application.php',
        'Composer\\Console\\GithubActionError' => __DIR__ . '/..' . '/composer/composer/src/Composer/Console/GithubActionError.php',
        'Composer\\Console\\HtmlOutputFormatter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Console/HtmlOutputFormatter.php',
        'Composer\\Console\\Input\\InputArgument' => __DIR__ . '/..' . '/composer/composer/src/Composer/Console/Input/InputArgument.php',
        'Composer\\Console\\Input\\InputOption' => __DIR__ . '/..' . '/composer/composer/src/Composer/Console/Input/InputOption.php',
        'Composer\\DependencyResolver\\Decisions' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Decisions.php',
        'Composer\\DependencyResolver\\DefaultPolicy' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/DefaultPolicy.php',
        'Composer\\DependencyResolver\\GenericRule' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/GenericRule.php',
        'Composer\\DependencyResolver\\LocalRepoTransaction' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/LocalRepoTransaction.php',
        'Composer\\DependencyResolver\\LockTransaction' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/LockTransaction.php',
        'Composer\\DependencyResolver\\MultiConflictRule' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/MultiConflictRule.php',
        'Composer\\DependencyResolver\\Operation\\InstallOperation' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Operation/InstallOperation.php',
        'Composer\\DependencyResolver\\Operation\\MarkAliasInstalledOperation' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php',
        'Composer\\DependencyResolver\\Operation\\MarkAliasUninstalledOperation' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php',
        'Composer\\DependencyResolver\\Operation\\OperationInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Operation/OperationInterface.php',
        'Composer\\DependencyResolver\\Operation\\SolverOperation' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Operation/SolverOperation.php',
        'Composer\\DependencyResolver\\Operation\\UninstallOperation' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Operation/UninstallOperation.php',
        'Composer\\DependencyResolver\\Operation\\UpdateOperation' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Operation/UpdateOperation.php',
        'Composer\\DependencyResolver\\PolicyInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/PolicyInterface.php',
        'Composer\\DependencyResolver\\Pool' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Pool.php',
        'Composer\\DependencyResolver\\PoolBuilder' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/PoolBuilder.php',
        'Composer\\DependencyResolver\\PoolOptimizer' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/PoolOptimizer.php',
        'Composer\\DependencyResolver\\Problem' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Problem.php',
        'Composer\\DependencyResolver\\Request' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Request.php',
        'Composer\\DependencyResolver\\Rule' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Rule.php',
        'Composer\\DependencyResolver\\Rule2Literals' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Rule2Literals.php',
        'Composer\\DependencyResolver\\RuleSet' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/RuleSet.php',
        'Composer\\DependencyResolver\\RuleSetGenerator' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/RuleSetGenerator.php',
        'Composer\\DependencyResolver\\RuleSetIterator' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/RuleSetIterator.php',
        'Composer\\DependencyResolver\\RuleWatchChain' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/RuleWatchChain.php',
        'Composer\\DependencyResolver\\RuleWatchGraph' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/RuleWatchGraph.php',
        'Composer\\DependencyResolver\\RuleWatchNode' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/RuleWatchNode.php',
        'Composer\\DependencyResolver\\SecurityAdvisoryPoolFilter' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/SecurityAdvisoryPoolFilter.php',
        'Composer\\DependencyResolver\\Solver' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Solver.php',
        'Composer\\DependencyResolver\\SolverBugException' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/SolverBugException.php',
        'Composer\\DependencyResolver\\SolverProblemsException' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/SolverProblemsException.php',
        'Composer\\DependencyResolver\\Transaction' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Transaction.php',
        'Composer\\Downloader\\ArchiveDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/ArchiveDownloader.php',
        'Composer\\Downloader\\ChangeReportInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/ChangeReportInterface.php',
        'Composer\\Downloader\\DownloadManager' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/DownloadManager.php',
        'Composer\\Downloader\\DownloaderInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/DownloaderInterface.php',
        'Composer\\Downloader\\DvcsDownloaderInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/DvcsDownloaderInterface.php',
        'Composer\\Downloader\\FileDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/FileDownloader.php',
        'Composer\\Downloader\\FilesystemException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/FilesystemException.php',
        'Composer\\Downloader\\FossilDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/FossilDownloader.php',
        'Composer\\Downloader\\GitDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/GitDownloader.php',
        'Composer\\Downloader\\GzipDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/GzipDownloader.php',
        'Composer\\Downloader\\HgDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/HgDownloader.php',
        'Composer\\Downloader\\MaxFileSizeExceededException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/MaxFileSizeExceededException.php',
        'Composer\\Downloader\\PathDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/PathDownloader.php',
        'Composer\\Downloader\\PerforceDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/PerforceDownloader.php',
        'Composer\\Downloader\\PharDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/PharDownloader.php',
        'Composer\\Downloader\\RarDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/RarDownloader.php',
        'Composer\\Downloader\\SvnDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/SvnDownloader.php',
        'Composer\\Downloader\\TarDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/TarDownloader.php',
        'Composer\\Downloader\\TransportException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/TransportException.php',
        'Composer\\Downloader\\VcsCapableDownloaderInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/VcsCapableDownloaderInterface.php',
        'Composer\\Downloader\\VcsDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/VcsDownloader.php',
        'Composer\\Downloader\\XzDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/XzDownloader.php',
        'Composer\\Downloader\\ZipDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/ZipDownloader.php',
        'Composer\\EventDispatcher\\Event' => __DIR__ . '/..' . '/composer/composer/src/Composer/EventDispatcher/Event.php',
        'Composer\\EventDispatcher\\EventDispatcher' => __DIR__ . '/..' . '/composer/composer/src/Composer/EventDispatcher/EventDispatcher.php',
        'Composer\\EventDispatcher\\EventSubscriberInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/EventDispatcher/EventSubscriberInterface.php',
        'Composer\\EventDispatcher\\ScriptExecutionException' => __DIR__ . '/..' . '/composer/composer/src/Composer/EventDispatcher/ScriptExecutionException.php',
        'Composer\\Exception\\IrrecoverableDownloadException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Exception/IrrecoverableDownloadException.php',
        'Composer\\Exception\\NoSslException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Exception/NoSslException.php',
        'Composer\\Factory' => __DIR__ . '/..' . '/composer/composer/src/Composer/Factory.php',
        'Composer\\Filter\\PlatformRequirementFilter\\IgnoreAllPlatformRequirementFilter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreAllPlatformRequirementFilter.php',
        'Composer\\Filter\\PlatformRequirementFilter\\IgnoreListPlatformRequirementFilter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilter.php',
        'Composer\\Filter\\PlatformRequirementFilter\\IgnoreNothingPlatformRequirementFilter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilter.php',
        'Composer\\Filter\\PlatformRequirementFilter\\PlatformRequirementFilterFactory' => __DIR__ . '/..' . '/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterFactory.php',
        'Composer\\Filter\\PlatformRequirementFilter\\PlatformRequirementFilterInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterInterface.php',
        'Composer\\IO\\BaseIO' => __DIR__ . '/..' . '/composer/composer/src/Composer/IO/BaseIO.php',
        'Composer\\IO\\BufferIO' => __DIR__ . '/..' . '/composer/composer/src/Composer/IO/BufferIO.php',
        'Composer\\IO\\ConsoleIO' => __DIR__ . '/..' . '/composer/composer/src/Composer/IO/ConsoleIO.php',
        'Composer\\IO\\IOInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/IO/IOInterface.php',
        'Composer\\IO\\NullIO' => __DIR__ . '/..' . '/composer/composer/src/Composer/IO/NullIO.php',
        'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
        'Composer\\Installer' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer.php',
        'Composer\\Installer\\BinaryInstaller' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/BinaryInstaller.php',
        'Composer\\Installer\\BinaryPresenceInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/BinaryPresenceInterface.php',
        'Composer\\Installer\\InstallationManager' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/InstallationManager.php',
        'Composer\\Installer\\InstallerEvent' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/InstallerEvent.php',
        'Composer\\Installer\\InstallerEvents' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/InstallerEvents.php',
        'Composer\\Installer\\InstallerInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/InstallerInterface.php',
        'Composer\\Installer\\LibraryInstaller' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/LibraryInstaller.php',
        'Composer\\Installer\\MetapackageInstaller' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/MetapackageInstaller.php',
        'Composer\\Installer\\NoopInstaller' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/NoopInstaller.php',
        'Composer\\Installer\\PackageEvent' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/PackageEvent.php',
        'Composer\\Installer\\PackageEvents' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/PackageEvents.php',
        'Composer\\Installer\\PluginInstaller' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/PluginInstaller.php',
        'Composer\\Installer\\ProjectInstaller' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/ProjectInstaller.php',
        'Composer\\Installer\\SuggestedPackagesReporter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/SuggestedPackagesReporter.php',
        'Composer\\Json\\JsonFile' => __DIR__ . '/..' . '/composer/composer/src/Composer/Json/JsonFile.php',
        'Composer\\Json\\JsonFormatter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Json/JsonFormatter.php',
        'Composer\\Json\\JsonManipulator' => __DIR__ . '/..' . '/composer/composer/src/Composer/Json/JsonManipulator.php',
        'Composer\\Json\\JsonValidationException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Json/JsonValidationException.php',
        'Composer\\MetadataMinifier\\MetadataMinifier' => __DIR__ . '/..' . '/composer/metadata-minifier/src/MetadataMinifier.php',
        'Composer\\PHPStan\\ConfigReturnTypeExtension' => __DIR__ . '/..' . '/composer/composer/src/Composer/PHPStan/ConfigReturnTypeExtension.php',
        'Composer\\PHPStan\\RuleReasonDataReturnTypeExtension' => __DIR__ . '/..' . '/composer/composer/src/Composer/PHPStan/RuleReasonDataReturnTypeExtension.php',
        'Composer\\Package\\AliasPackage' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/AliasPackage.php',
        'Composer\\Package\\Archiver\\ArchivableFilesFilter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFilter.php',
        'Composer\\Package\\Archiver\\ArchivableFilesFinder' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFinder.php',
        'Composer\\Package\\Archiver\\ArchiveManager' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/ArchiveManager.php',
        'Composer\\Package\\Archiver\\ArchiverInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/ArchiverInterface.php',
        'Composer\\Package\\Archiver\\BaseExcludeFilter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/BaseExcludeFilter.php',
        'Composer\\Package\\Archiver\\ComposerExcludeFilter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/ComposerExcludeFilter.php',
        'Composer\\Package\\Archiver\\GitExcludeFilter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/GitExcludeFilter.php',
        'Composer\\Package\\Archiver\\PharArchiver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/PharArchiver.php',
        'Composer\\Package\\Archiver\\ZipArchiver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/ZipArchiver.php',
        'Composer\\Package\\BasePackage' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/BasePackage.php',
        'Composer\\Package\\Comparer\\Comparer' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Comparer/Comparer.php',
        'Composer\\Package\\CompleteAliasPackage' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/CompleteAliasPackage.php',
        'Composer\\Package\\CompletePackage' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/CompletePackage.php',
        'Composer\\Package\\CompletePackageInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/CompletePackageInterface.php',
        'Composer\\Package\\Dumper\\ArrayDumper' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Dumper/ArrayDumper.php',
        'Composer\\Package\\Link' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Link.php',
        'Composer\\Package\\Loader\\ArrayLoader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Loader/ArrayLoader.php',
        'Composer\\Package\\Loader\\InvalidPackageException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Loader/InvalidPackageException.php',
        'Composer\\Package\\Loader\\JsonLoader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Loader/JsonLoader.php',
        'Composer\\Package\\Loader\\LoaderInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Loader/LoaderInterface.php',
        'Composer\\Package\\Loader\\RootPackageLoader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Loader/RootPackageLoader.php',
        'Composer\\Package\\Loader\\ValidatingArrayLoader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Loader/ValidatingArrayLoader.php',
        'Composer\\Package\\Locker' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Locker.php',
        'Composer\\Package\\Package' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Package.php',
        'Composer\\Package\\PackageInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/PackageInterface.php',
        'Composer\\Package\\RootAliasPackage' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/RootAliasPackage.php',
        'Composer\\Package\\RootPackage' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/RootPackage.php',
        'Composer\\Package\\RootPackageInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/RootPackageInterface.php',
        'Composer\\Package\\Version\\StabilityFilter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Version/StabilityFilter.php',
        'Composer\\Package\\Version\\VersionBumper' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Version/VersionBumper.php',
        'Composer\\Package\\Version\\VersionGuesser' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Version/VersionGuesser.php',
        'Composer\\Package\\Version\\VersionParser' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Version/VersionParser.php',
        'Composer\\Package\\Version\\VersionSelector' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Version/VersionSelector.php',
        'Composer\\PartialComposer' => __DIR__ . '/..' . '/composer/composer/src/Composer/PartialComposer.php',
        'Composer\\Pcre\\MatchAllResult' => __DIR__ . '/..' . '/composer/pcre/src/MatchAllResult.php',
        'Composer\\Pcre\\MatchAllStrictGroupsResult' => __DIR__ . '/..' . '/composer/pcre/src/MatchAllStrictGroupsResult.php',
        'Composer\\Pcre\\MatchAllWithOffsetsResult' => __DIR__ . '/..' . '/composer/pcre/src/MatchAllWithOffsetsResult.php',
        'Composer\\Pcre\\MatchResult' => __DIR__ . '/..' . '/composer/pcre/src/MatchResult.php',
        'Composer\\Pcre\\MatchStrictGroupsResult' => __DIR__ . '/..' . '/composer/pcre/src/MatchStrictGroupsResult.php',
        'Composer\\Pcre\\MatchWithOffsetsResult' => __DIR__ . '/..' . '/composer/pcre/src/MatchWithOffsetsResult.php',
        'Composer\\Pcre\\PHPStan\\InvalidRegexPatternRule' => __DIR__ . '/..' . '/composer/pcre/src/PHPStan/InvalidRegexPatternRule.php',
        'Composer\\Pcre\\PHPStan\\PregMatchFlags' => __DIR__ . '/..' . '/composer/pcre/src/PHPStan/PregMatchFlags.php',
        'Composer\\Pcre\\PHPStan\\PregMatchParameterOutTypeExtension' => __DIR__ . '/..' . '/composer/pcre/src/PHPStan/PregMatchParameterOutTypeExtension.php',
        'Composer\\Pcre\\PHPStan\\PregMatchTypeSpecifyingExtension' => __DIR__ . '/..' . '/composer/pcre/src/PHPStan/PregMatchTypeSpecifyingExtension.php',
        'Composer\\Pcre\\PHPStan\\PregReplaceCallbackClosureTypeExtension' => __DIR__ . '/..' . '/composer/pcre/src/PHPStan/PregReplaceCallbackClosureTypeExtension.php',
        'Composer\\Pcre\\PHPStan\\UnsafeStrictGroupsCallRule' => __DIR__ . '/..' . '/composer/pcre/src/PHPStan/UnsafeStrictGroupsCallRule.php',
        'Composer\\Pcre\\PcreException' => __DIR__ . '/..' . '/composer/pcre/src/PcreException.php',
        'Composer\\Pcre\\Preg' => __DIR__ . '/..' . '/composer/pcre/src/Preg.php',
        'Composer\\Pcre\\Regex' => __DIR__ . '/..' . '/composer/pcre/src/Regex.php',
        'Composer\\Pcre\\ReplaceResult' => __DIR__ . '/..' . '/composer/pcre/src/ReplaceResult.php',
        'Composer\\Pcre\\UnexpectedNullMatchException' => __DIR__ . '/..' . '/composer/pcre/src/UnexpectedNullMatchException.php',
        'Composer\\Platform\\HhvmDetector' => __DIR__ . '/..' . '/composer/composer/src/Composer/Platform/HhvmDetector.php',
        'Composer\\Platform\\Runtime' => __DIR__ . '/..' . '/composer/composer/src/Composer/Platform/Runtime.php',
        'Composer\\Platform\\Version' => __DIR__ . '/..' . '/composer/composer/src/Composer/Platform/Version.php',
        'Composer\\Plugin\\Capability\\Capability' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/Capability/Capability.php',
        'Composer\\Plugin\\Capability\\CommandProvider' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/Capability/CommandProvider.php',
        'Composer\\Plugin\\Capable' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/Capable.php',
        'Composer\\Plugin\\CommandEvent' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/CommandEvent.php',
        'Composer\\Plugin\\PluginBlockedException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/PluginBlockedException.php',
        'Composer\\Plugin\\PluginEvents' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/PluginEvents.php',
        'Composer\\Plugin\\PluginInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/PluginInterface.php',
        'Composer\\Plugin\\PluginManager' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/PluginManager.php',
        'Composer\\Plugin\\PostFileDownloadEvent' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/PostFileDownloadEvent.php',
        'Composer\\Plugin\\PreCommandRunEvent' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/PreCommandRunEvent.php',
        'Composer\\Plugin\\PreFileDownloadEvent' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/PreFileDownloadEvent.php',
        'Composer\\Plugin\\PrePoolCreateEvent' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/PrePoolCreateEvent.php',
        'Composer\\Question\\StrictConfirmationQuestion' => __DIR__ . '/..' . '/composer/composer/src/Composer/Question/StrictConfirmationQuestion.php',
        'Composer\\Repository\\AdvisoryProviderInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/AdvisoryProviderInterface.php',
        'Composer\\Repository\\ArrayRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/ArrayRepository.php',
        'Composer\\Repository\\ArtifactRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/ArtifactRepository.php',
        'Composer\\Repository\\CanonicalPackagesTrait' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/CanonicalPackagesTrait.php',
        'Composer\\Repository\\ComposerRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/ComposerRepository.php',
        'Composer\\Repository\\CompositeRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/CompositeRepository.php',
        'Composer\\Repository\\ConfigurableRepositoryInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/ConfigurableRepositoryInterface.php',
        'Composer\\Repository\\FilesystemRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/FilesystemRepository.php',
        'Composer\\Repository\\FilterRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/FilterRepository.php',
        'Composer\\Repository\\InstalledArrayRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/InstalledArrayRepository.php',
        'Composer\\Repository\\InstalledFilesystemRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/InstalledFilesystemRepository.php',
        'Composer\\Repository\\InstalledRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/InstalledRepository.php',
        'Composer\\Repository\\InstalledRepositoryInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/InstalledRepositoryInterface.php',
        'Composer\\Repository\\InvalidRepositoryException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/InvalidRepositoryException.php',
        'Composer\\Repository\\LockArrayRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/LockArrayRepository.php',
        'Composer\\Repository\\PackageRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/PackageRepository.php',
        'Composer\\Repository\\PathRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/PathRepository.php',
        'Composer\\Repository\\PearRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/PearRepository.php',
        'Composer\\Repository\\PlatformRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/PlatformRepository.php',
        'Composer\\Repository\\RepositoryFactory' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/RepositoryFactory.php',
        'Composer\\Repository\\RepositoryInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/RepositoryInterface.php',
        'Composer\\Repository\\RepositoryManager' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/RepositoryManager.php',
        'Composer\\Repository\\RepositorySecurityException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/RepositorySecurityException.php',
        'Composer\\Repository\\RepositorySet' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/RepositorySet.php',
        'Composer\\Repository\\RepositoryUtils' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/RepositoryUtils.php',
        'Composer\\Repository\\RootPackageRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/RootPackageRepository.php',
        'Composer\\Repository\\VcsRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/VcsRepository.php',
        'Composer\\Repository\\Vcs\\ForgejoDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/ForgejoDriver.php',
        'Composer\\Repository\\Vcs\\FossilDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/FossilDriver.php',
        'Composer\\Repository\\Vcs\\GitBitbucketDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/GitBitbucketDriver.php',
        'Composer\\Repository\\Vcs\\GitDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/GitDriver.php',
        'Composer\\Repository\\Vcs\\GitHubDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/GitHubDriver.php',
        'Composer\\Repository\\Vcs\\GitLabDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/GitLabDriver.php',
        'Composer\\Repository\\Vcs\\HgDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/HgDriver.php',
        'Composer\\Repository\\Vcs\\PerforceDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/PerforceDriver.php',
        'Composer\\Repository\\Vcs\\SvnDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/SvnDriver.php',
        'Composer\\Repository\\Vcs\\VcsDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/VcsDriver.php',
        'Composer\\Repository\\Vcs\\VcsDriverInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/VcsDriverInterface.php',
        'Composer\\Repository\\VersionCacheInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/VersionCacheInterface.php',
        'Composer\\Repository\\WritableArrayRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/WritableArrayRepository.php',
        'Composer\\Repository\\WritableRepositoryInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/WritableRepositoryInterface.php',
        'Composer\\Script\\Event' => __DIR__ . '/..' . '/composer/composer/src/Composer/Script/Event.php',
        'Composer\\Script\\ScriptEvents' => __DIR__ . '/..' . '/composer/composer/src/Composer/Script/ScriptEvents.php',
        'Composer\\SelfUpdate\\Keys' => __DIR__ . '/..' . '/composer/composer/src/Composer/SelfUpdate/Keys.php',
        'Composer\\SelfUpdate\\Versions' => __DIR__ . '/..' . '/composer/composer/src/Composer/SelfUpdate/Versions.php',
        'Composer\\Semver\\Comparator' => __DIR__ . '/..' . '/composer/semver/src/Comparator.php',
        'Composer\\Semver\\CompilingMatcher' => __DIR__ . '/..' . '/composer/semver/src/CompilingMatcher.php',
        'Composer\\Semver\\Constraint\\Bound' => __DIR__ . '/..' . '/composer/semver/src/Constraint/Bound.php',
        'Composer\\Semver\\Constraint\\Constraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/Constraint.php',
        'Composer\\Semver\\Constraint\\ConstraintInterface' => __DIR__ . '/..' . '/composer/semver/src/Constraint/ConstraintInterface.php',
        'Composer\\Semver\\Constraint\\MatchAllConstraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/MatchAllConstraint.php',
        'Composer\\Semver\\Constraint\\MatchNoneConstraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/MatchNoneConstraint.php',
        'Composer\\Semver\\Constraint\\MultiConstraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/MultiConstraint.php',
        'Composer\\Semver\\Interval' => __DIR__ . '/..' . '/composer/semver/src/Interval.php',
        'Composer\\Semver\\Intervals' => __DIR__ . '/..' . '/composer/semver/src/Intervals.php',
        'Composer\\Semver\\Semver' => __DIR__ . '/..' . '/composer/semver/src/Semver.php',
        'Composer\\Semver\\VersionParser' => __DIR__ . '/..' . '/composer/semver/src/VersionParser.php',
        'Composer\\Spdx\\SpdxLicenses' => __DIR__ . '/..' . '/composer/spdx-licenses/src/SpdxLicenses.php',
        'Composer\\Util\\AuthHelper' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/AuthHelper.php',
        'Composer\\Util\\Bitbucket' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Bitbucket.php',
        'Composer\\Util\\ComposerMirror' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/ComposerMirror.php',
        'Composer\\Util\\ConfigValidator' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/ConfigValidator.php',
        'Composer\\Util\\ErrorHandler' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/ErrorHandler.php',
        'Composer\\Util\\Filesystem' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Filesystem.php',
        'Composer\\Util\\Forgejo' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Forgejo.php',
        'Composer\\Util\\ForgejoRepositoryData' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/ForgejoRepositoryData.php',
        'Composer\\Util\\ForgejoUrl' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/ForgejoUrl.php',
        'Composer\\Util\\Git' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Git.php',
        'Composer\\Util\\GitHub' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/GitHub.php',
        'Composer\\Util\\GitLab' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/GitLab.php',
        'Composer\\Util\\Hg' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Hg.php',
        'Composer\\Util\\HttpDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/HttpDownloader.php',
        'Composer\\Util\\Http\\CurlDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Http/CurlDownloader.php',
        'Composer\\Util\\Http\\CurlResponse' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Http/CurlResponse.php',
        'Composer\\Util\\Http\\ProxyItem' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Http/ProxyItem.php',
        'Composer\\Util\\Http\\ProxyManager' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Http/ProxyManager.php',
        'Composer\\Util\\Http\\RequestProxy' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Http/RequestProxy.php',
        'Composer\\Util\\Http\\Response' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Http/Response.php',
        'Composer\\Util\\IniHelper' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/IniHelper.php',
        'Composer\\Util\\Loop' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Loop.php',
        'Composer\\Util\\MetadataMinifier' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/MetadataMinifier.php',
        'Composer\\Util\\NoProxyPattern' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/NoProxyPattern.php',
        'Composer\\Util\\PackageInfo' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/PackageInfo.php',
        'Composer\\Util\\PackageSorter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/PackageSorter.php',
        'Composer\\Util\\Perforce' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Perforce.php',
        'Composer\\Util\\Platform' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Platform.php',
        'Composer\\Util\\ProcessExecutor' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/ProcessExecutor.php',
        'Composer\\Util\\RemoteFilesystem' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/RemoteFilesystem.php',
        'Composer\\Util\\Silencer' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Silencer.php',
        'Composer\\Util\\StreamContextFactory' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/StreamContextFactory.php',
        'Composer\\Util\\Svn' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Svn.php',
        'Composer\\Util\\SyncHelper' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/SyncHelper.php',
        'Composer\\Util\\Tar' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Tar.php',
        'Composer\\Util\\TlsHelper' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/TlsHelper.php',
        'Composer\\Util\\Url' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Url.php',
        'Composer\\Util\\Zip' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Zip.php',
        'Composer\\XdebugHandler\\PhpConfig' => __DIR__ . '/..' . '/composer/xdebug-handler/src/PhpConfig.php',
        'Composer\\XdebugHandler\\Process' => __DIR__ . '/..' . '/composer/xdebug-handler/src/Process.php',
        'Composer\\XdebugHandler\\Status' => __DIR__ . '/..' . '/composer/xdebug-handler/src/Status.php',
        'Composer\\XdebugHandler\\XdebugHandler' => __DIR__ . '/..' . '/composer/xdebug-handler/src/XdebugHandler.php',
        'Deprecated' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/Deprecated.php',
        'Fidry\\CpuCoreCounter\\CpuCoreCounter' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/CpuCoreCounter.php',
        'Fidry\\CpuCoreCounter\\Diagnoser' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Diagnoser.php',
        'Fidry\\CpuCoreCounter\\Executor\\ProcOpenExecutor' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Executor/ProcOpenExecutor.php',
        'Fidry\\CpuCoreCounter\\Executor\\ProcessExecutor' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Executor/ProcessExecutor.php',
        'Fidry\\CpuCoreCounter\\Finder\\CmiCmdletLogicalFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/CmiCmdletLogicalFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\CmiCmdletPhysicalFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/CmiCmdletPhysicalFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\CpuCoreFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/CpuCoreFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\CpuInfoFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/CpuInfoFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\DummyCpuCoreFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/DummyCpuCoreFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\EnvVariableFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/EnvVariableFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\FinderRegistry' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/FinderRegistry.php',
        'Fidry\\CpuCoreCounter\\Finder\\HwLogicalFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/HwLogicalFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\HwPhysicalFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/HwPhysicalFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\LscpuLogicalFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/LscpuLogicalFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\LscpuPhysicalFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/LscpuPhysicalFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\NProcFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/NProcFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\NProcessorFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/NProcessorFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\NullCpuCoreFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/NullCpuCoreFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\OnlyInPowerShellFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/OnlyInPowerShellFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\OnlyOnOSFamilyFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/OnlyOnOSFamilyFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\ProcOpenBasedFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/ProcOpenBasedFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\SkipOnOSFamilyFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/SkipOnOSFamilyFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\WindowsRegistryLogicalFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/WindowsRegistryLogicalFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\WmicLogicalFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/WmicLogicalFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\WmicPhysicalFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/WmicPhysicalFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\_NProcessorFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/_NProcessorFinder.php',
        'Fidry\\CpuCoreCounter\\NumberOfCpuCoreNotFound' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/NumberOfCpuCoreNotFound.php',
        'Fidry\\CpuCoreCounter\\ParallelisationResult' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/ParallelisationResult.php',
        'Illuminate\\Container\\BoundMethod' => __DIR__ . '/..' . '/illuminate/container/BoundMethod.php',
        'Illuminate\\Container\\Container' => __DIR__ . '/..' . '/illuminate/container/Container.php',
        'Illuminate\\Container\\ContextualBindingBuilder' => __DIR__ . '/..' . '/illuminate/container/ContextualBindingBuilder.php',
        'Illuminate\\Container\\EntryNotFoundException' => __DIR__ . '/..' . '/illuminate/container/EntryNotFoundException.php',
        'Illuminate\\Container\\RewindableGenerator' => __DIR__ . '/..' . '/illuminate/container/RewindableGenerator.php',
        'Illuminate\\Container\\Util' => __DIR__ . '/..' . '/illuminate/container/Util.php',
        'Illuminate\\Contracts\\Auth\\Access\\Authorizable' => __DIR__ . '/..' . '/illuminate/contracts/Auth/Access/Authorizable.php',
        'Illuminate\\Contracts\\Auth\\Access\\Gate' => __DIR__ . '/..' . '/illuminate/contracts/Auth/Access/Gate.php',
        'Illuminate\\Contracts\\Auth\\Authenticatable' => __DIR__ . '/..' . '/illuminate/contracts/Auth/Authenticatable.php',
        'Illuminate\\Contracts\\Auth\\CanResetPassword' => __DIR__ . '/..' . '/illuminate/contracts/Auth/CanResetPassword.php',
        'Illuminate\\Contracts\\Auth\\Factory' => __DIR__ . '/..' . '/illuminate/contracts/Auth/Factory.php',
        'Illuminate\\Contracts\\Auth\\Guard' => __DIR__ . '/..' . '/illuminate/contracts/Auth/Guard.php',
        'Illuminate\\Contracts\\Auth\\Middleware\\AuthenticatesRequests' => __DIR__ . '/..' . '/illuminate/contracts/Auth/Middleware/AuthenticatesRequests.php',
        'Illuminate\\Contracts\\Auth\\MustVerifyEmail' => __DIR__ . '/..' . '/illuminate/contracts/Auth/MustVerifyEmail.php',
        'Illuminate\\Contracts\\Auth\\PasswordBroker' => __DIR__ . '/..' . '/illuminate/contracts/Auth/PasswordBroker.php',
        'Illuminate\\Contracts\\Auth\\PasswordBrokerFactory' => __DIR__ . '/..' . '/illuminate/contracts/Auth/PasswordBrokerFactory.php',
        'Illuminate\\Contracts\\Auth\\StatefulGuard' => __DIR__ . '/..' . '/illuminate/contracts/Auth/StatefulGuard.php',
        'Illuminate\\Contracts\\Auth\\SupportsBasicAuth' => __DIR__ . '/..' . '/illuminate/contracts/Auth/SupportsBasicAuth.php',
        'Illuminate\\Contracts\\Auth\\UserProvider' => __DIR__ . '/..' . '/illuminate/contracts/Auth/UserProvider.php',
        'Illuminate\\Contracts\\Broadcasting\\Broadcaster' => __DIR__ . '/..' . '/illuminate/contracts/Broadcasting/Broadcaster.php',
        'Illuminate\\Contracts\\Broadcasting\\Factory' => __DIR__ . '/..' . '/illuminate/contracts/Broadcasting/Factory.php',
        'Illuminate\\Contracts\\Broadcasting\\HasBroadcastChannel' => __DIR__ . '/..' . '/illuminate/contracts/Broadcasting/HasBroadcastChannel.php',
        'Illuminate\\Contracts\\Broadcasting\\ShouldBeUnique' => __DIR__ . '/..' . '/illuminate/contracts/Broadcasting/ShouldBeUnique.php',
        'Illuminate\\Contracts\\Broadcasting\\ShouldBroadcast' => __DIR__ . '/..' . '/illuminate/contracts/Broadcasting/ShouldBroadcast.php',
        'Illuminate\\Contracts\\Broadcasting\\ShouldBroadcastNow' => __DIR__ . '/..' . '/illuminate/contracts/Broadcasting/ShouldBroadcastNow.php',
        'Illuminate\\Contracts\\Bus\\Dispatcher' => __DIR__ . '/..' . '/illuminate/contracts/Bus/Dispatcher.php',
        'Illuminate\\Contracts\\Bus\\QueueingDispatcher' => __DIR__ . '/..' . '/illuminate/contracts/Bus/QueueingDispatcher.php',
        'Illuminate\\Contracts\\Cache\\Factory' => __DIR__ . '/..' . '/illuminate/contracts/Cache/Factory.php',
        'Illuminate\\Contracts\\Cache\\Lock' => __DIR__ . '/..' . '/illuminate/contracts/Cache/Lock.php',
        'Illuminate\\Contracts\\Cache\\LockProvider' => __DIR__ . '/..' . '/illuminate/contracts/Cache/LockProvider.php',
        'Illuminate\\Contracts\\Cache\\LockTimeoutException' => __DIR__ . '/..' . '/illuminate/contracts/Cache/LockTimeoutException.php',
        'Illuminate\\Contracts\\Cache\\Repository' => __DIR__ . '/..' . '/illuminate/contracts/Cache/Repository.php',
        'Illuminate\\Contracts\\Cache\\Store' => __DIR__ . '/..' . '/illuminate/contracts/Cache/Store.php',
        'Illuminate\\Contracts\\Config\\Repository' => __DIR__ . '/..' . '/illuminate/contracts/Config/Repository.php',
        'Illuminate\\Contracts\\Console\\Application' => __DIR__ . '/..' . '/illuminate/contracts/Console/Application.php',
        'Illuminate\\Contracts\\Console\\Isolatable' => __DIR__ . '/..' . '/illuminate/contracts/Console/Isolatable.php',
        'Illuminate\\Contracts\\Console\\Kernel' => __DIR__ . '/..' . '/illuminate/contracts/Console/Kernel.php',
        'Illuminate\\Contracts\\Console\\PromptsForMissingInput' => __DIR__ . '/..' . '/illuminate/contracts/Console/PromptsForMissingInput.php',
        'Illuminate\\Contracts\\Container\\BindingResolutionException' => __DIR__ . '/..' . '/illuminate/contracts/Container/BindingResolutionException.php',
        'Illuminate\\Contracts\\Container\\CircularDependencyException' => __DIR__ . '/..' . '/illuminate/contracts/Container/CircularDependencyException.php',
        'Illuminate\\Contracts\\Container\\Container' => __DIR__ . '/..' . '/illuminate/contracts/Container/Container.php',
        'Illuminate\\Contracts\\Container\\ContextualBindingBuilder' => __DIR__ . '/..' . '/illuminate/contracts/Container/ContextualBindingBuilder.php',
        'Illuminate\\Contracts\\Cookie\\Factory' => __DIR__ . '/..' . '/illuminate/contracts/Cookie/Factory.php',
        'Illuminate\\Contracts\\Cookie\\QueueingFactory' => __DIR__ . '/..' . '/illuminate/contracts/Cookie/QueueingFactory.php',
        'Illuminate\\Contracts\\Database\\Eloquent\\Builder' => __DIR__ . '/..' . '/illuminate/contracts/Database/Eloquent/Builder.php',
        'Illuminate\\Contracts\\Database\\Eloquent\\Castable' => __DIR__ . '/..' . '/illuminate/contracts/Database/Eloquent/Castable.php',
        'Illuminate\\Contracts\\Database\\Eloquent\\CastsAttributes' => __DIR__ . '/..' . '/illuminate/contracts/Database/Eloquent/CastsAttributes.php',
        'Illuminate\\Contracts\\Database\\Eloquent\\CastsInboundAttributes' => __DIR__ . '/..' . '/illuminate/contracts/Database/Eloquent/CastsInboundAttributes.php',
        'Illuminate\\Contracts\\Database\\Eloquent\\DeviatesCastableAttributes' => __DIR__ . '/..' . '/illuminate/contracts/Database/Eloquent/DeviatesCastableAttributes.php',
        'Illuminate\\Contracts\\Database\\Eloquent\\SerializesCastableAttributes' => __DIR__ . '/..' . '/illuminate/contracts/Database/Eloquent/SerializesCastableAttributes.php',
        'Illuminate\\Contracts\\Database\\Eloquent\\SupportsPartialRelations' => __DIR__ . '/..' . '/illuminate/contracts/Database/Eloquent/SupportsPartialRelations.php',
        'Illuminate\\Contracts\\Database\\Events\\MigrationEvent' => __DIR__ . '/..' . '/illuminate/contracts/Database/Events/MigrationEvent.php',
        'Illuminate\\Contracts\\Database\\ModelIdentifier' => __DIR__ . '/..' . '/illuminate/contracts/Database/ModelIdentifier.php',
        'Illuminate\\Contracts\\Database\\Query\\Builder' => __DIR__ . '/..' . '/illuminate/contracts/Database/Query/Builder.php',
        'Illuminate\\Contracts\\Database\\Query\\ConditionExpression' => __DIR__ . '/..' . '/illuminate/contracts/Database/Query/ConditionExpression.php',
        'Illuminate\\Contracts\\Database\\Query\\Expression' => __DIR__ . '/..' . '/illuminate/contracts/Database/Query/Expression.php',
        'Illuminate\\Contracts\\Debug\\ExceptionHandler' => __DIR__ . '/..' . '/illuminate/contracts/Debug/ExceptionHandler.php',
        'Illuminate\\Contracts\\Encryption\\DecryptException' => __DIR__ . '/..' . '/illuminate/contracts/Encryption/DecryptException.php',
        'Illuminate\\Contracts\\Encryption\\EncryptException' => __DIR__ . '/..' . '/illuminate/contracts/Encryption/EncryptException.php',
        'Illuminate\\Contracts\\Encryption\\Encrypter' => __DIR__ . '/..' . '/illuminate/contracts/Encryption/Encrypter.php',
        'Illuminate\\Contracts\\Encryption\\StringEncrypter' => __DIR__ . '/..' . '/illuminate/contracts/Encryption/StringEncrypter.php',
        'Illuminate\\Contracts\\Events\\Dispatcher' => __DIR__ . '/..' . '/illuminate/contracts/Events/Dispatcher.php',
        'Illuminate\\Contracts\\Events\\ShouldDispatchAfterCommit' => __DIR__ . '/..' . '/illuminate/contracts/Events/ShouldDispatchAfterCommit.php',
        'Illuminate\\Contracts\\Events\\ShouldHandleEventsAfterCommit' => __DIR__ . '/..' . '/illuminate/contracts/Events/ShouldHandleEventsAfterCommit.php',
        'Illuminate\\Contracts\\Filesystem\\Cloud' => __DIR__ . '/..' . '/illuminate/contracts/Filesystem/Cloud.php',
        'Illuminate\\Contracts\\Filesystem\\Factory' => __DIR__ . '/..' . '/illuminate/contracts/Filesystem/Factory.php',
        'Illuminate\\Contracts\\Filesystem\\FileNotFoundException' => __DIR__ . '/..' . '/illuminate/contracts/Filesystem/FileNotFoundException.php',
        'Illuminate\\Contracts\\Filesystem\\Filesystem' => __DIR__ . '/..' . '/illuminate/contracts/Filesystem/Filesystem.php',
        'Illuminate\\Contracts\\Filesystem\\LockTimeoutException' => __DIR__ . '/..' . '/illuminate/contracts/Filesystem/LockTimeoutException.php',
        'Illuminate\\Contracts\\Foundation\\Application' => __DIR__ . '/..' . '/illuminate/contracts/Foundation/Application.php',
        'Illuminate\\Contracts\\Foundation\\CachesConfiguration' => __DIR__ . '/..' . '/illuminate/contracts/Foundation/CachesConfiguration.php',
        'Illuminate\\Contracts\\Foundation\\CachesRoutes' => __DIR__ . '/..' . '/illuminate/contracts/Foundation/CachesRoutes.php',
        'Illuminate\\Contracts\\Foundation\\ExceptionRenderer' => __DIR__ . '/..' . '/illuminate/contracts/Foundation/ExceptionRenderer.php',
        'Illuminate\\Contracts\\Foundation\\MaintenanceMode' => __DIR__ . '/..' . '/illuminate/contracts/Foundation/MaintenanceMode.php',
        'Illuminate\\Contracts\\Hashing\\Hasher' => __DIR__ . '/..' . '/illuminate/contracts/Hashing/Hasher.php',
        'Illuminate\\Contracts\\Http\\Kernel' => __DIR__ . '/..' . '/illuminate/contracts/Http/Kernel.php',
        'Illuminate\\Contracts\\Mail\\Attachable' => __DIR__ . '/..' . '/illuminate/contracts/Mail/Attachable.php',
        'Illuminate\\Contracts\\Mail\\Factory' => __DIR__ . '/..' . '/illuminate/contracts/Mail/Factory.php',
        'Illuminate\\Contracts\\Mail\\MailQueue' => __DIR__ . '/..' . '/illuminate/contracts/Mail/MailQueue.php',
        'Illuminate\\Contracts\\Mail\\Mailable' => __DIR__ . '/..' . '/illuminate/contracts/Mail/Mailable.php',
        'Illuminate\\Contracts\\Mail\\Mailer' => __DIR__ . '/..' . '/illuminate/contracts/Mail/Mailer.php',
        'Illuminate\\Contracts\\Notifications\\Dispatcher' => __DIR__ . '/..' . '/illuminate/contracts/Notifications/Dispatcher.php',
        'Illuminate\\Contracts\\Notifications\\Factory' => __DIR__ . '/..' . '/illuminate/contracts/Notifications/Factory.php',
        'Illuminate\\Contracts\\Pagination\\CursorPaginator' => __DIR__ . '/..' . '/illuminate/contracts/Pagination/CursorPaginator.php',
        'Illuminate\\Contracts\\Pagination\\LengthAwarePaginator' => __DIR__ . '/..' . '/illuminate/contracts/Pagination/LengthAwarePaginator.php',
        'Illuminate\\Contracts\\Pagination\\Paginator' => __DIR__ . '/..' . '/illuminate/contracts/Pagination/Paginator.php',
        'Illuminate\\Contracts\\Pipeline\\Hub' => __DIR__ . '/..' . '/illuminate/contracts/Pipeline/Hub.php',
        'Illuminate\\Contracts\\Pipeline\\Pipeline' => __DIR__ . '/..' . '/illuminate/contracts/Pipeline/Pipeline.php',
        'Illuminate\\Contracts\\Process\\InvokedProcess' => __DIR__ . '/..' . '/illuminate/contracts/Process/InvokedProcess.php',
        'Illuminate\\Contracts\\Process\\ProcessResult' => __DIR__ . '/..' . '/illuminate/contracts/Process/ProcessResult.php',
        'Illuminate\\Contracts\\Queue\\ClearableQueue' => __DIR__ . '/..' . '/illuminate/contracts/Queue/ClearableQueue.php',
        'Illuminate\\Contracts\\Queue\\EntityNotFoundException' => __DIR__ . '/..' . '/illuminate/contracts/Queue/EntityNotFoundException.php',
        'Illuminate\\Contracts\\Queue\\EntityResolver' => __DIR__ . '/..' . '/illuminate/contracts/Queue/EntityResolver.php',
        'Illuminate\\Contracts\\Queue\\Factory' => __DIR__ . '/..' . '/illuminate/contracts/Queue/Factory.php',
        'Illuminate\\Contracts\\Queue\\Job' => __DIR__ . '/..' . '/illuminate/contracts/Queue/Job.php',
        'Illuminate\\Contracts\\Queue\\Monitor' => __DIR__ . '/..' . '/illuminate/contracts/Queue/Monitor.php',
        'Illuminate\\Contracts\\Queue\\Queue' => __DIR__ . '/..' . '/illuminate/contracts/Queue/Queue.php',
        'Illuminate\\Contracts\\Queue\\QueueableCollection' => __DIR__ . '/..' . '/illuminate/contracts/Queue/QueueableCollection.php',
        'Illuminate\\Contracts\\Queue\\QueueableEntity' => __DIR__ . '/..' . '/illuminate/contracts/Queue/QueueableEntity.php',
        'Illuminate\\Contracts\\Queue\\ShouldBeEncrypted' => __DIR__ . '/..' . '/illuminate/contracts/Queue/ShouldBeEncrypted.php',
        'Illuminate\\Contracts\\Queue\\ShouldBeUnique' => __DIR__ . '/..' . '/illuminate/contracts/Queue/ShouldBeUnique.php',
        'Illuminate\\Contracts\\Queue\\ShouldBeUniqueUntilProcessing' => __DIR__ . '/..' . '/illuminate/contracts/Queue/ShouldBeUniqueUntilProcessing.php',
        'Illuminate\\Contracts\\Queue\\ShouldQueue' => __DIR__ . '/..' . '/illuminate/contracts/Queue/ShouldQueue.php',
        'Illuminate\\Contracts\\Queue\\ShouldQueueAfterCommit' => __DIR__ . '/..' . '/illuminate/contracts/Queue/ShouldQueueAfterCommit.php',
        'Illuminate\\Contracts\\Redis\\Connection' => __DIR__ . '/..' . '/illuminate/contracts/Redis/Connection.php',
        'Illuminate\\Contracts\\Redis\\Connector' => __DIR__ . '/..' . '/illuminate/contracts/Redis/Connector.php',
        'Illuminate\\Contracts\\Redis\\Factory' => __DIR__ . '/..' . '/illuminate/contracts/Redis/Factory.php',
        'Illuminate\\Contracts\\Redis\\LimiterTimeoutException' => __DIR__ . '/..' . '/illuminate/contracts/Redis/LimiterTimeoutException.php',
        'Illuminate\\Contracts\\Routing\\BindingRegistrar' => __DIR__ . '/..' . '/illuminate/contracts/Routing/BindingRegistrar.php',
        'Illuminate\\Contracts\\Routing\\Registrar' => __DIR__ . '/..' . '/illuminate/contracts/Routing/Registrar.php',
        'Illuminate\\Contracts\\Routing\\ResponseFactory' => __DIR__ . '/..' . '/illuminate/contracts/Routing/ResponseFactory.php',
        'Illuminate\\Contracts\\Routing\\UrlGenerator' => __DIR__ . '/..' . '/illuminate/contracts/Routing/UrlGenerator.php',
        'Illuminate\\Contracts\\Routing\\UrlRoutable' => __DIR__ . '/..' . '/illuminate/contracts/Routing/UrlRoutable.php',
        'Illuminate\\Contracts\\Session\\Middleware\\AuthenticatesSessions' => __DIR__ . '/..' . '/illuminate/contracts/Session/Middleware/AuthenticatesSessions.php',
        'Illuminate\\Contracts\\Session\\Session' => __DIR__ . '/..' . '/illuminate/contracts/Session/Session.php',
        'Illuminate\\Contracts\\Support\\Arrayable' => __DIR__ . '/..' . '/illuminate/contracts/Support/Arrayable.php',
        'Illuminate\\Contracts\\Support\\CanBeEscapedWhenCastToString' => __DIR__ . '/..' . '/illuminate/contracts/Support/CanBeEscapedWhenCastToString.php',
        'Illuminate\\Contracts\\Support\\DeferrableProvider' => __DIR__ . '/..' . '/illuminate/contracts/Support/DeferrableProvider.php',
        'Illuminate\\Contracts\\Support\\DeferringDisplayableValue' => __DIR__ . '/..' . '/illuminate/contracts/Support/DeferringDisplayableValue.php',
        'Illuminate\\Contracts\\Support\\Htmlable' => __DIR__ . '/..' . '/illuminate/contracts/Support/Htmlable.php',
        'Illuminate\\Contracts\\Support\\Jsonable' => __DIR__ . '/..' . '/illuminate/contracts/Support/Jsonable.php',
        'Illuminate\\Contracts\\Support\\MessageBag' => __DIR__ . '/..' . '/illuminate/contracts/Support/MessageBag.php',
        'Illuminate\\Contracts\\Support\\MessageProvider' => __DIR__ . '/..' . '/illuminate/contracts/Support/MessageProvider.php',
        'Illuminate\\Contracts\\Support\\Renderable' => __DIR__ . '/..' . '/illuminate/contracts/Support/Renderable.php',
        'Illuminate\\Contracts\\Support\\Responsable' => __DIR__ . '/..' . '/illuminate/contracts/Support/Responsable.php',
        'Illuminate\\Contracts\\Support\\ValidatedData' => __DIR__ . '/..' . '/illuminate/contracts/Support/ValidatedData.php',
        'Illuminate\\Contracts\\Translation\\HasLocalePreference' => __DIR__ . '/..' . '/illuminate/contracts/Translation/HasLocalePreference.php',
        'Illuminate\\Contracts\\Translation\\Loader' => __DIR__ . '/..' . '/illuminate/contracts/Translation/Loader.php',
        'Illuminate\\Contracts\\Translation\\Translator' => __DIR__ . '/..' . '/illuminate/contracts/Translation/Translator.php',
        'Illuminate\\Contracts\\Validation\\DataAwareRule' => __DIR__ . '/..' . '/illuminate/contracts/Validation/DataAwareRule.php',
        'Illuminate\\Contracts\\Validation\\Factory' => __DIR__ . '/..' . '/illuminate/contracts/Validation/Factory.php',
        'Illuminate\\Contracts\\Validation\\ImplicitRule' => __DIR__ . '/..' . '/illuminate/contracts/Validation/ImplicitRule.php',
        'Illuminate\\Contracts\\Validation\\InvokableRule' => __DIR__ . '/..' . '/illuminate/contracts/Validation/InvokableRule.php',
        'Illuminate\\Contracts\\Validation\\Rule' => __DIR__ . '/..' . '/illuminate/contracts/Validation/Rule.php',
        'Illuminate\\Contracts\\Validation\\UncompromisedVerifier' => __DIR__ . '/..' . '/illuminate/contracts/Validation/UncompromisedVerifier.php',
        'Illuminate\\Contracts\\Validation\\ValidatesWhenResolved' => __DIR__ . '/..' . '/illuminate/contracts/Validation/ValidatesWhenResolved.php',
        'Illuminate\\Contracts\\Validation\\ValidationRule' => __DIR__ . '/..' . '/illuminate/contracts/Validation/ValidationRule.php',
        'Illuminate\\Contracts\\Validation\\Validator' => __DIR__ . '/..' . '/illuminate/contracts/Validation/Validator.php',
        'Illuminate\\Contracts\\Validation\\ValidatorAwareRule' => __DIR__ . '/..' . '/illuminate/contracts/Validation/ValidatorAwareRule.php',
        'Illuminate\\Contracts\\View\\Engine' => __DIR__ . '/..' . '/illuminate/contracts/View/Engine.php',
        'Illuminate\\Contracts\\View\\Factory' => __DIR__ . '/..' . '/illuminate/contracts/View/Factory.php',
        'Illuminate\\Contracts\\View\\View' => __DIR__ . '/..' . '/illuminate/contracts/View/View.php',
        'Illuminate\\Contracts\\View\\ViewCompilationException' => __DIR__ . '/..' . '/illuminate/contracts/View/ViewCompilationException.php',
        'JsonSchema\\ConstraintError' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/ConstraintError.php',
        'JsonSchema\\Constraints\\BaseConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/BaseConstraint.php',
        'JsonSchema\\Constraints\\CollectionConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/CollectionConstraint.php',
        'JsonSchema\\Constraints\\ConstConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstConstraint.php',
        'JsonSchema\\Constraints\\Constraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Constraint.php',
        'JsonSchema\\Constraints\\ConstraintInterface' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstraintInterface.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\AdditionalItemsConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AdditionalItemsConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\AdditionalPropertiesConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AdditionalPropertiesConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\AllOfConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AllOfConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\AnyOfConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AnyOfConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\ConstConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ConstConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\ContainsConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ContainsConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\DependenciesConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/DependenciesConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\Draft06Constraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/Draft06Constraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\EnumConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/EnumConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\ExclusiveMaximumConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ExclusiveMaximumConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\ExclusiveMinimumConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ExclusiveMinimumConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\Factory' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/Factory.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\FormatConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/FormatConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\ItemsConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ItemsConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\MaxItemsConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MaxItemsConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\MaxLengthConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MaxLengthConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\MaxPropertiesConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MaxPropertiesConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\MaximumConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MaximumConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\MinItemsConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MinItemsConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\MinLengthConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MinLengthConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\MinPropertiesConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MinPropertiesConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\MinimumConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MinimumConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\MultipleOfConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MultipleOfConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\NotConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/NotConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\OneOfConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/OneOfConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\PatternConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PatternConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\PatternPropertiesConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PatternPropertiesConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\PropertiesConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PropertiesConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\PropertiesNamesConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PropertiesNamesConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\RefConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/RefConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\RequiredConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/RequiredConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\TypeConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/TypeConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft06\\UniqueItemsConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/UniqueItemsConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\AdditionalItemsConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AdditionalItemsConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\AdditionalPropertiesConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AdditionalPropertiesConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\AllOfConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AllOfConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\AnyOfConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AnyOfConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\ConstConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ConstConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\ContainsConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ContainsConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\ContentConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ContentConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\DependenciesConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/DependenciesConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\Draft07Constraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/Draft07Constraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\EnumConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/EnumConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\ExclusiveMaximumConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ExclusiveMaximumConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\ExclusiveMinimumConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ExclusiveMinimumConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\Factory' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/Factory.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\FormatConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/FormatConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\IfThenElseConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/IfThenElseConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\ItemsConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ItemsConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\MaxItemsConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxItemsConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\MaxLengthConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxLengthConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\MaxPropertiesConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxPropertiesConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\MaximumConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaximumConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\MinItemsConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinItemsConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\MinLengthConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinLengthConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\MinPropertiesConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinPropertiesConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\MinimumConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinimumConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\MultipleOfConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MultipleOfConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\NotConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/NotConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\OneOfConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/OneOfConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\PatternConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PatternConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\PatternPropertiesConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PatternPropertiesConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\PropertiesConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PropertiesConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\PropertiesNamesConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PropertiesNamesConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\RefConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/RefConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\RequiredConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/RequiredConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\TypeConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/TypeConstraint.php',
        'JsonSchema\\Constraints\\Drafts\\Draft07\\UniqueItemsConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/UniqueItemsConstraint.php',
        'JsonSchema\\Constraints\\EnumConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/EnumConstraint.php',
        'JsonSchema\\Constraints\\Factory' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.php',
        'JsonSchema\\Constraints\\FormatConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php',
        'JsonSchema\\Constraints\\NumberConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/NumberConstraint.php',
        'JsonSchema\\Constraints\\ObjectConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/ObjectConstraint.php',
        'JsonSchema\\Constraints\\SchemaConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/SchemaConstraint.php',
        'JsonSchema\\Constraints\\StringConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/StringConstraint.php',
        'JsonSchema\\Constraints\\TypeCheck\\LooseTypeCheck' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/LooseTypeCheck.php',
        'JsonSchema\\Constraints\\TypeCheck\\StrictTypeCheck' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/StrictTypeCheck.php',
        'JsonSchema\\Constraints\\TypeCheck\\TypeCheckInterface' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/TypeCheckInterface.php',
        'JsonSchema\\Constraints\\TypeConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeConstraint.php',
        'JsonSchema\\Constraints\\UndefinedConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/UndefinedConstraint.php',
        'JsonSchema\\DraftIdentifiers' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/DraftIdentifiers.php',
        'JsonSchema\\Entity\\ErrorBag' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Entity/ErrorBag.php',
        'JsonSchema\\Entity\\ErrorBagProxy' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Entity/ErrorBagProxy.php',
        'JsonSchema\\Entity\\JsonPointer' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Entity/JsonPointer.php',
        'JsonSchema\\Enum' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Enum.php',
        'JsonSchema\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/ExceptionInterface.php',
        'JsonSchema\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidArgumentException.php',
        'JsonSchema\\Exception\\InvalidConfigException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidConfigException.php',
        'JsonSchema\\Exception\\InvalidSchemaException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaException.php',
        'JsonSchema\\Exception\\InvalidSchemaMediaTypeException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaMediaTypeException.php',
        'JsonSchema\\Exception\\InvalidSourceUriException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSourceUriException.php',
        'JsonSchema\\Exception\\JsonDecodingException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/JsonDecodingException.php',
        'JsonSchema\\Exception\\ResourceNotFoundException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/ResourceNotFoundException.php',
        'JsonSchema\\Exception\\RuntimeException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/RuntimeException.php',
        'JsonSchema\\Exception\\UnresolvableJsonPointerException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/UnresolvableJsonPointerException.php',
        'JsonSchema\\Exception\\UriResolverException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/UriResolverException.php',
        'JsonSchema\\Exception\\ValidationException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/ValidationException.php',
        'JsonSchema\\Iterator\\ObjectIterator' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Iterator/ObjectIterator.php',
        'JsonSchema\\Rfc3339' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Rfc3339.php',
        'JsonSchema\\SchemaStorage' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/SchemaStorage.php',
        'JsonSchema\\SchemaStorageInterface' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/SchemaStorageInterface.php',
        'JsonSchema\\Tool\\DeepComparer' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Tool/DeepComparer.php',
        'JsonSchema\\Tool\\DeepCopy' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Tool/DeepCopy.php',
        'JsonSchema\\Tool\\Validator\\RelativeReferenceValidator' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Tool/Validator/RelativeReferenceValidator.php',
        'JsonSchema\\Tool\\Validator\\UriValidator' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Tool/Validator/UriValidator.php',
        'JsonSchema\\UriResolverInterface' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/UriResolverInterface.php',
        'JsonSchema\\UriRetrieverInterface' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/UriRetrieverInterface.php',
        'JsonSchema\\Uri\\Retrievers\\AbstractRetriever' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/AbstractRetriever.php',
        'JsonSchema\\Uri\\Retrievers\\Curl' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/Curl.php',
        'JsonSchema\\Uri\\Retrievers\\FileGetContents' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/FileGetContents.php',
        'JsonSchema\\Uri\\Retrievers\\PredefinedArray' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/PredefinedArray.php',
        'JsonSchema\\Uri\\Retrievers\\UriRetrieverInterface' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/UriRetrieverInterface.php',
        'JsonSchema\\Uri\\UriResolver' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/UriResolver.php',
        'JsonSchema\\Uri\\UriRetriever' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php',
        'JsonSchema\\Validator' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Validator.php',
        'MabeEnum\\Enum' => __DIR__ . '/..' . '/marc-mabe/php-enum/src/Enum.php',
        'MabeEnum\\EnumMap' => __DIR__ . '/..' . '/marc-mabe/php-enum/src/EnumMap.php',
        'MabeEnum\\EnumSerializableTrait' => __DIR__ . '/..' . '/marc-mabe/php-enum/src/EnumSerializableTrait.php',
        'MabeEnum\\EnumSet' => __DIR__ . '/..' . '/marc-mabe/php-enum/src/EnumSet.php',
        'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
        'Pdo\\Dblib' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/Pdo/Dblib.php',
        'Pdo\\Firebird' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/Pdo/Firebird.php',
        'Pdo\\Mysql' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/Pdo/Mysql.php',
        'Pdo\\Odbc' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/Pdo/Odbc.php',
        'Pdo\\Pgsql' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/Pdo/Pgsql.php',
        'Pdo\\Sqlite' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/Pdo/Sqlite.php',
        'Php\\Pie\\Building\\Build' => __DIR__ . '/../..' . '/src/Building/Build.php',
        'Php\\Pie\\Building\\ExtensionBinaryNotFound' => __DIR__ . '/../..' . '/src/Building/ExtensionBinaryNotFound.php',
        'Php\\Pie\\Building\\PlaceholderReplacer' => __DIR__ . '/../..' . '/src/Building/PlaceholderReplacer.php',
        'Php\\Pie\\Building\\UnixBuild' => __DIR__ . '/../..' . '/src/Building/UnixBuild.php',
        'Php\\Pie\\Building\\WindowsBuild' => __DIR__ . '/../..' . '/src/Building/WindowsBuild.php',
        'Php\\Pie\\Command\\ArgvInput' => __DIR__ . '/../..' . '/src/Command/ArgvInput.php',
        'Php\\Pie\\Command\\BuildCommand' => __DIR__ . '/../..' . '/src/Command/BuildCommand.php',
        'Php\\Pie\\Command\\CommandHelper' => __DIR__ . '/../..' . '/src/Command/CommandHelper.php',
        'Php\\Pie\\Command\\DownloadCommand' => __DIR__ . '/../..' . '/src/Command/DownloadCommand.php',
        'Php\\Pie\\Command\\InfoCommand' => __DIR__ . '/../..' . '/src/Command/InfoCommand.php',
        'Php\\Pie\\Command\\InstallCommand' => __DIR__ . '/../..' . '/src/Command/InstallCommand.php',
        'Php\\Pie\\Command\\InstallExtensionsForProjectCommand' => __DIR__ . '/../..' . '/src/Command/InstallExtensionsForProjectCommand.php',
        'Php\\Pie\\Command\\InvokeSubCommand' => __DIR__ . '/../..' . '/src/Command/InvokeSubCommand.php',
        'Php\\Pie\\Command\\RepositoryAddCommand' => __DIR__ . '/../..' . '/src/Command/RepositoryAddCommand.php',
        'Php\\Pie\\Command\\RepositoryListCommand' => __DIR__ . '/../..' . '/src/Command/RepositoryListCommand.php',
        'Php\\Pie\\Command\\RepositoryRemoveCommand' => __DIR__ . '/../..' . '/src/Command/RepositoryRemoveCommand.php',
        'Php\\Pie\\Command\\SelfUpdateCommand' => __DIR__ . '/../..' . '/src/Command/SelfUpdateCommand.php',
        'Php\\Pie\\Command\\SelfVerifyCommand' => __DIR__ . '/../..' . '/src/Command/SelfVerifyCommand.php',
        'Php\\Pie\\Command\\ShowCommand' => __DIR__ . '/../..' . '/src/Command/ShowCommand.php',
        'Php\\Pie\\Command\\UninstallCommand' => __DIR__ . '/../..' . '/src/Command/UninstallCommand.php',
        'Php\\Pie\\ComposerIntegration\\AddInstalledJsonMetadata' => __DIR__ . '/../..' . '/src/ComposerIntegration/AddInstalledJsonMetadata.php',
        'Php\\Pie\\ComposerIntegration\\BundledPhpExtensionsRepository' => __DIR__ . '/../..' . '/src/ComposerIntegration/BundledPhpExtensionsRepository.php',
        'Php\\Pie\\ComposerIntegration\\ComposerIntegrationHandler' => __DIR__ . '/../..' . '/src/ComposerIntegration/ComposerIntegrationHandler.php',
        'Php\\Pie\\ComposerIntegration\\ComposerRunFailed' => __DIR__ . '/../..' . '/src/ComposerIntegration/ComposerRunFailed.php',
        'Php\\Pie\\ComposerIntegration\\InstallAndBuildProcess' => __DIR__ . '/../..' . '/src/ComposerIntegration/InstallAndBuildProcess.php',
        'Php\\Pie\\ComposerIntegration\\InstalledJsonMetadata' => __DIR__ . '/../..' . '/src/ComposerIntegration/InstalledJsonMetadata.php',
        'Php\\Pie\\ComposerIntegration\\Listeners\\CouldNotDetermineDownloadUrlMethod' => __DIR__ . '/../..' . '/src/ComposerIntegration/Listeners/CouldNotDetermineDownloadUrlMethod.php',
        'Php\\Pie\\ComposerIntegration\\Listeners\\OverrideDownloadUrlInstallListener' => __DIR__ . '/../..' . '/src/ComposerIntegration/Listeners/OverrideDownloadUrlInstallListener.php',
        'Php\\Pie\\ComposerIntegration\\Listeners\\RemoveUnrelatedInstallOperations' => __DIR__ . '/../..' . '/src/ComposerIntegration/Listeners/RemoveUnrelatedInstallOperations.php',
        'Php\\Pie\\ComposerIntegration\\MinimalHelperSet' => __DIR__ . '/../..' . '/src/ComposerIntegration/MinimalHelperSet.php',
        'Php\\Pie\\ComposerIntegration\\PhpBinaryPathBasedPlatformRepository' => __DIR__ . '/../..' . '/src/ComposerIntegration/PhpBinaryPathBasedPlatformRepository.php',
        'Php\\Pie\\ComposerIntegration\\PieComposerFactory' => __DIR__ . '/../..' . '/src/ComposerIntegration/PieComposerFactory.php',
        'Php\\Pie\\ComposerIntegration\\PieComposerInstaller' => __DIR__ . '/../..' . '/src/ComposerIntegration/PieComposerInstaller.php',
        'Php\\Pie\\ComposerIntegration\\PieComposerRequest' => __DIR__ . '/../..' . '/src/ComposerIntegration/PieComposerRequest.php',
        'Php\\Pie\\ComposerIntegration\\PieJsonEditor' => __DIR__ . '/../..' . '/src/ComposerIntegration/PieJsonEditor.php',
        'Php\\Pie\\ComposerIntegration\\PieOperation' => __DIR__ . '/../..' . '/src/ComposerIntegration/PieOperation.php',
        'Php\\Pie\\ComposerIntegration\\PiePackageInstaller' => __DIR__ . '/../..' . '/src/ComposerIntegration/PiePackageInstaller.php',
        'Php\\Pie\\ComposerIntegration\\QuieterConsoleIO' => __DIR__ . '/../..' . '/src/ComposerIntegration/QuieterConsoleIO.php',
        'Php\\Pie\\ComposerIntegration\\UninstallProcess' => __DIR__ . '/../..' . '/src/ComposerIntegration/UninstallProcess.php',
        'Php\\Pie\\ComposerIntegration\\VendorCleanup' => __DIR__ . '/../..' . '/src/ComposerIntegration/VendorCleanup.php',
        'Php\\Pie\\ComposerIntegration\\VersionSelectorFactory' => __DIR__ . '/../..' . '/src/ComposerIntegration/VersionSelectorFactory.php',
        'Php\\Pie\\ConfigureOption' => __DIR__ . '/../..' . '/src/ConfigureOption.php',
        'Php\\Pie\\Container' => __DIR__ . '/../..' . '/src/Container.php',
        'Php\\Pie\\DependencyResolver\\BundledPhpExtensionRefusal' => __DIR__ . '/../..' . '/src/DependencyResolver/BundledPhpExtensionRefusal.php',
        'Php\\Pie\\DependencyResolver\\DependencyInstaller\\PrescanSystemDependencies' => __DIR__ . '/../..' . '/src/DependencyResolver/DependencyInstaller/PrescanSystemDependencies.php',
        'Php\\Pie\\DependencyResolver\\DependencyInstaller\\SystemDependenciesDefinition' => __DIR__ . '/../..' . '/src/DependencyResolver/DependencyInstaller/SystemDependenciesDefinition.php',
        'Php\\Pie\\DependencyResolver\\DependencyResolver' => __DIR__ . '/../..' . '/src/DependencyResolver/DependencyResolver.php',
        'Php\\Pie\\DependencyResolver\\DependencyStatus' => __DIR__ . '/../..' . '/src/DependencyResolver/DependencyStatus.php',
        'Php\\Pie\\DependencyResolver\\DetermineMinimumStability' => __DIR__ . '/../..' . '/src/DependencyResolver/DetermineMinimumStability.php',
        'Php\\Pie\\DependencyResolver\\FetchDependencyStatuses' => __DIR__ . '/../..' . '/src/DependencyResolver/FetchDependencyStatuses.php',
        'Php\\Pie\\DependencyResolver\\IncompatibleOperatingSystemFamily' => __DIR__ . '/../..' . '/src/DependencyResolver/IncompatibleOperatingSystemFamily.php',
        'Php\\Pie\\DependencyResolver\\IncompatibleThreadSafetyMode' => __DIR__ . '/../..' . '/src/DependencyResolver/IncompatibleThreadSafetyMode.php',
        'Php\\Pie\\DependencyResolver\\InvalidPackageName' => __DIR__ . '/../..' . '/src/DependencyResolver/InvalidPackageName.php',
        'Php\\Pie\\DependencyResolver\\Package' => __DIR__ . '/../..' . '/src/DependencyResolver/Package.php',
        'Php\\Pie\\DependencyResolver\\RequestedPackageAndVersion' => __DIR__ . '/../..' . '/src/DependencyResolver/RequestedPackageAndVersion.php',
        'Php\\Pie\\DependencyResolver\\ResolveDependencyWithComposer' => __DIR__ . '/../..' . '/src/DependencyResolver/ResolveDependencyWithComposer.php',
        'Php\\Pie\\DependencyResolver\\UnableToResolveRequirement' => __DIR__ . '/../..' . '/src/DependencyResolver/UnableToResolveRequirement.php',
        'Php\\Pie\\Downloading\\DownloadUrlMethod' => __DIR__ . '/../..' . '/src/Downloading/DownloadUrlMethod.php',
        'Php\\Pie\\Downloading\\DownloadedPackage' => __DIR__ . '/../..' . '/src/Downloading/DownloadedPackage.php',
        'Php\\Pie\\Downloading\\Exception\\CouldNotFindReleaseAsset' => __DIR__ . '/../..' . '/src/Downloading/Exception/CouldNotFindReleaseAsset.php',
        'Php\\Pie\\Downloading\\GithubPackageReleaseAssets' => __DIR__ . '/../..' . '/src/Downloading/GithubPackageReleaseAssets.php',
        'Php\\Pie\\Downloading\\PackageReleaseAssets' => __DIR__ . '/../..' . '/src/Downloading/PackageReleaseAssets.php',
        'Php\\Pie\\ExtensionName' => __DIR__ . '/../..' . '/src/ExtensionName.php',
        'Php\\Pie\\ExtensionType' => __DIR__ . '/../..' . '/src/ExtensionType.php',
        'Php\\Pie\\File\\BinaryFile' => __DIR__ . '/../..' . '/src/File/BinaryFile.php',
        'Php\\Pie\\File\\BinaryFileFailedVerification' => __DIR__ . '/../..' . '/src/File/BinaryFileFailedVerification.php',
        'Php\\Pie\\File\\FailedToCreateFile' => __DIR__ . '/../..' . '/src/File/FailedToCreateFile.php',
        'Php\\Pie\\File\\FailedToUnlinkFile' => __DIR__ . '/../..' . '/src/File/FailedToUnlinkFile.php',
        'Php\\Pie\\File\\FailedToWriteFile' => __DIR__ . '/../..' . '/src/File/FailedToWriteFile.php',
        'Php\\Pie\\File\\FullPathToSelf' => __DIR__ . '/../..' . '/src/File/FullPathToSelf.php',
        'Php\\Pie\\File\\Sudo' => __DIR__ . '/../..' . '/src/File/Sudo.php',
        'Php\\Pie\\File\\SudoCreate' => __DIR__ . '/../..' . '/src/File/SudoCreate.php',
        'Php\\Pie\\File\\SudoFilePut' => __DIR__ . '/../..' . '/src/File/SudoFilePut.php',
        'Php\\Pie\\File\\SudoNotFoundOnSystem' => __DIR__ . '/../..' . '/src/File/SudoNotFoundOnSystem.php',
        'Php\\Pie\\File\\SudoRequiresInteractiveTerminal' => __DIR__ . '/../..' . '/src/File/SudoRequiresInteractiveTerminal.php',
        'Php\\Pie\\File\\SudoUnlink' => __DIR__ . '/../..' . '/src/File/SudoUnlink.php',
        'Php\\Pie\\File\\WindowsDelete' => __DIR__ . '/../..' . '/src/File/WindowsDelete.php',
        'Php\\Pie\\Installing\\FailedToRemoveExtension' => __DIR__ . '/../..' . '/src/Installing/FailedToRemoveExtension.php',
        'Php\\Pie\\Installing\\Ini\\AddExtensionToTheIniFile' => __DIR__ . '/../..' . '/src/Installing/Ini/AddExtensionToTheIniFile.php',
        'Php\\Pie\\Installing\\Ini\\CheckAndAddExtensionToIniIfNeeded' => __DIR__ . '/../..' . '/src/Installing/Ini/CheckAndAddExtensionToIniIfNeeded.php',
        'Php\\Pie\\Installing\\Ini\\DockerPhpExtEnable' => __DIR__ . '/../..' . '/src/Installing/Ini/DockerPhpExtEnable.php',
        'Php\\Pie\\Installing\\Ini\\IsExtensionAlreadyInTheIniFile' => __DIR__ . '/../..' . '/src/Installing/Ini/IsExtensionAlreadyInTheIniFile.php',
        'Php\\Pie\\Installing\\Ini\\OndrejPhpenmod' => __DIR__ . '/../..' . '/src/Installing/Ini/OndrejPhpenmod.php',
        'Php\\Pie\\Installing\\Ini\\PickBestSetupIniApproach' => __DIR__ . '/../..' . '/src/Installing/Ini/PickBestSetupIniApproach.php',
        'Php\\Pie\\Installing\\Ini\\PreCheckExtensionAlreadyLoaded' => __DIR__ . '/../..' . '/src/Installing/Ini/PreCheckExtensionAlreadyLoaded.php',
        'Php\\Pie\\Installing\\Ini\\RemoveIniEntry' => __DIR__ . '/../..' . '/src/Installing/Ini/RemoveIniEntry.php',
        'Php\\Pie\\Installing\\Ini\\RemoveIniEntryWithFileGetContents' => __DIR__ . '/../..' . '/src/Installing/Ini/RemoveIniEntryWithFileGetContents.php',
        'Php\\Pie\\Installing\\Ini\\SetupIniApproach' => __DIR__ . '/../..' . '/src/Installing/Ini/SetupIniApproach.php',
        'Php\\Pie\\Installing\\Ini\\StandardAdditionalPhpIniDirectory' => __DIR__ . '/../..' . '/src/Installing/Ini/StandardAdditionalPhpIniDirectory.php',
        'Php\\Pie\\Installing\\Ini\\StandardSinglePhpIni' => __DIR__ . '/../..' . '/src/Installing/Ini/StandardSinglePhpIni.php',
        'Php\\Pie\\Installing\\Install' => __DIR__ . '/../..' . '/src/Installing/Install.php',
        'Php\\Pie\\Installing\\InstallForPhpProject\\ComposerFactoryForProject' => __DIR__ . '/../..' . '/src/Installing/InstallForPhpProject/ComposerFactoryForProject.php',
        'Php\\Pie\\Installing\\InstallForPhpProject\\DetermineExtensionsRequired' => __DIR__ . '/../..' . '/src/Installing/InstallForPhpProject/DetermineExtensionsRequired.php',
        'Php\\Pie\\Installing\\InstallForPhpProject\\FindMatchingPackages' => __DIR__ . '/../..' . '/src/Installing/InstallForPhpProject/FindMatchingPackages.php',
        'Php\\Pie\\Installing\\InstallForPhpProject\\InstallPiePackageFromPath' => __DIR__ . '/../..' . '/src/Installing/InstallForPhpProject/InstallPiePackageFromPath.php',
        'Php\\Pie\\Installing\\InstallForPhpProject\\InstallSelectedPackage' => __DIR__ . '/../..' . '/src/Installing/InstallForPhpProject/InstallSelectedPackage.php',
        'Php\\Pie\\Installing\\PackageMetadataMissing' => __DIR__ . '/../..' . '/src/Installing/PackageMetadataMissing.php',
        'Php\\Pie\\Installing\\SetupIniFile' => __DIR__ . '/../..' . '/src/Installing/SetupIniFile.php',
        'Php\\Pie\\Installing\\Uninstall' => __DIR__ . '/../..' . '/src/Installing/Uninstall.php',
        'Php\\Pie\\Installing\\UninstallUsingUnlink' => __DIR__ . '/../..' . '/src/Installing/UninstallUsingUnlink.php',
        'Php\\Pie\\Installing\\UnixInstall' => __DIR__ . '/../..' . '/src/Installing/UnixInstall.php',
        'Php\\Pie\\Installing\\WindowsInstall' => __DIR__ . '/../..' . '/src/Installing/WindowsInstall.php',
        'Php\\Pie\\Platform' => __DIR__ . '/../..' . '/src/Platform.php',
        'Php\\Pie\\Platform\\Architecture' => __DIR__ . '/../..' . '/src/Platform/Architecture.php',
        'Php\\Pie\\Platform\\DebugBuild' => __DIR__ . '/../..' . '/src/Platform/DebugBuild.php',
        'Php\\Pie\\Platform\\InstalledPiePackages' => __DIR__ . '/../..' . '/src/Platform/InstalledPiePackages.php',
        'Php\\Pie\\Platform\\LibcFlavour' => __DIR__ . '/../..' . '/src/Platform/LibcFlavour.php',
        'Php\\Pie\\Platform\\OperatingSystem' => __DIR__ . '/../..' . '/src/Platform/OperatingSystem.php',
        'Php\\Pie\\Platform\\OperatingSystemFamily' => __DIR__ . '/../..' . '/src/Platform/OperatingSystemFamily.php',
        'Php\\Pie\\Platform\\PackageManager' => __DIR__ . '/../..' . '/src/Platform/PackageManager.php',
        'Php\\Pie\\Platform\\PiePackageList' => __DIR__ . '/../..' . '/src/Platform/PiePackageList.php',
        'Php\\Pie\\Platform\\PrePackagedBinaryAssetName' => __DIR__ . '/../..' . '/src/Platform/PrePackagedBinaryAssetName.php',
        'Php\\Pie\\Platform\\PrePackagedSourceAssetName' => __DIR__ . '/../..' . '/src/Platform/PrePackagedSourceAssetName.php',
        'Php\\Pie\\Platform\\TargetPhp\\Exception\\ExtensionIsNotLoaded' => __DIR__ . '/../..' . '/src/Platform/TargetPhp/Exception/ExtensionIsNotLoaded.php',
        'Php\\Pie\\Platform\\TargetPhp\\Exception\\ExtensionPathProblem' => __DIR__ . '/../..' . '/src/Platform/TargetPhp/Exception/ExtensionPathProblem.php',
        'Php\\Pie\\Platform\\TargetPhp\\Exception\\InvalidPhpBinaryPath' => __DIR__ . '/../..' . '/src/Platform/TargetPhp/Exception/InvalidPhpBinaryPath.php',
        'Php\\Pie\\Platform\\TargetPhp\\PhpBinaryPath' => __DIR__ . '/../..' . '/src/Platform/TargetPhp/PhpBinaryPath.php',
        'Php\\Pie\\Platform\\TargetPhp\\PhpizePath' => __DIR__ . '/../..' . '/src/Platform/TargetPhp/PhpizePath.php',
        'Php\\Pie\\Platform\\TargetPlatform' => __DIR__ . '/../..' . '/src/Platform/TargetPlatform.php',
        'Php\\Pie\\Platform\\ThreadSafetyMode' => __DIR__ . '/../..' . '/src/Platform/ThreadSafetyMode.php',
        'Php\\Pie\\Platform\\WindowsCompiler' => __DIR__ . '/../..' . '/src/Platform/WindowsCompiler.php',
        'Php\\Pie\\Platform\\WindowsExtensionAssetName' => __DIR__ . '/../..' . '/src/Platform/WindowsExtensionAssetName.php',
        'Php\\Pie\\SelfManage\\BuildTools\\BinaryBuildToolFinder' => __DIR__ . '/../..' . '/src/SelfManage/BuildTools/BinaryBuildToolFinder.php',
        'Php\\Pie\\SelfManage\\BuildTools\\CheckAllBuildTools' => __DIR__ . '/../..' . '/src/SelfManage/BuildTools/CheckAllBuildTools.php',
        'Php\\Pie\\SelfManage\\BuildTools\\PhpizeBuildToolFinder' => __DIR__ . '/../..' . '/src/SelfManage/BuildTools/PhpizeBuildToolFinder.php',
        'Php\\Pie\\SelfManage\\Update\\Channel' => __DIR__ . '/../..' . '/src/SelfManage/Update/Channel.php',
        'Php\\Pie\\SelfManage\\Update\\FetchPieRelease' => __DIR__ . '/../..' . '/src/SelfManage/Update/FetchPieRelease.php',
        'Php\\Pie\\SelfManage\\Update\\FetchPieReleaseFromGitHub' => __DIR__ . '/../..' . '/src/SelfManage/Update/FetchPieReleaseFromGitHub.php',
        'Php\\Pie\\SelfManage\\Update\\PiePharMissingFromLatestRelease' => __DIR__ . '/../..' . '/src/SelfManage/Update/PiePharMissingFromLatestRelease.php',
        'Php\\Pie\\SelfManage\\Update\\ReleaseIsNewer' => __DIR__ . '/../..' . '/src/SelfManage/Update/ReleaseIsNewer.php',
        'Php\\Pie\\SelfManage\\Update\\ReleaseMetadata' => __DIR__ . '/../..' . '/src/SelfManage/Update/ReleaseMetadata.php',
        'Php\\Pie\\SelfManage\\Verify\\Attestation' => __DIR__ . '/../..' . '/src/SelfManage/Verify/Attestation.php',
        'Php\\Pie\\SelfManage\\Verify\\FailedToVerifyRelease' => __DIR__ . '/../..' . '/src/SelfManage/Verify/FailedToVerifyRelease.php',
        'Php\\Pie\\SelfManage\\Verify\\FallbackVerificationUsingOpenSsl' => __DIR__ . '/../..' . '/src/SelfManage/Verify/FallbackVerificationUsingOpenSsl.php',
        'Php\\Pie\\SelfManage\\Verify\\GithubCliAttestationVerification' => __DIR__ . '/../..' . '/src/SelfManage/Verify/GithubCliAttestationVerification.php',
        'Php\\Pie\\SelfManage\\Verify\\GithubCliNotAvailable' => __DIR__ . '/../..' . '/src/SelfManage/Verify/GithubCliNotAvailable.php',
        'Php\\Pie\\SelfManage\\Verify\\VerifyPiePhar' => __DIR__ . '/../..' . '/src/SelfManage/Verify/VerifyPiePhar.php',
        'Php\\Pie\\SelfManage\\Verify\\VerifyPieReleaseUsingAttestation' => __DIR__ . '/../..' . '/src/SelfManage/Verify/VerifyPieReleaseUsingAttestation.php',
        'Php\\Pie\\Settings' => __DIR__ . '/../..' . '/src/Settings.php',
        'Php\\Pie\\Util\\Emoji' => __DIR__ . '/../..' . '/src/Util/Emoji.php',
        'Php\\Pie\\Util\\FileNotFound' => __DIR__ . '/../..' . '/src/Util/FileNotFound.php',
        'Php\\Pie\\Util\\OutputFormatterWithPrefix' => __DIR__ . '/../..' . '/src/Util/OutputFormatterWithPrefix.php',
        'Php\\Pie\\Util\\PackageVerificationStatus' => __DIR__ . '/../..' . '/src/Util/PackageVerificationStatus.php',
        'Php\\Pie\\Util\\PieVersion' => __DIR__ . '/../..' . '/src/Util/PieVersion.php',
        'Php\\Pie\\Util\\Process' => __DIR__ . '/../..' . '/src/Util/Process.php',
        'Php\\Pie\\Util\\ProcessFailedWithLimitedOutput' => __DIR__ . '/../..' . '/src/Util/ProcessFailedWithLimitedOutput.php',
        '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\\Log\\AbstractLogger' => __DIR__ . '/..' . '/psr/log/src/AbstractLogger.php',
        'Psr\\Log\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/log/src/InvalidArgumentException.php',
        'Psr\\Log\\LogLevel' => __DIR__ . '/..' . '/psr/log/src/LogLevel.php',
        'Psr\\Log\\LoggerAwareInterface' => __DIR__ . '/..' . '/psr/log/src/LoggerAwareInterface.php',
        'Psr\\Log\\LoggerAwareTrait' => __DIR__ . '/..' . '/psr/log/src/LoggerAwareTrait.php',
        'Psr\\Log\\LoggerInterface' => __DIR__ . '/..' . '/psr/log/src/LoggerInterface.php',
        'Psr\\Log\\LoggerTrait' => __DIR__ . '/..' . '/psr/log/src/LoggerTrait.php',
        'Psr\\Log\\NullLogger' => __DIR__ . '/..' . '/psr/log/src/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',
        'React\\Promise\\Deferred' => __DIR__ . '/..' . '/react/promise/src/Deferred.php',
        'React\\Promise\\Exception\\CompositeException' => __DIR__ . '/..' . '/react/promise/src/Exception/CompositeException.php',
        'React\\Promise\\Exception\\LengthException' => __DIR__ . '/..' . '/react/promise/src/Exception/LengthException.php',
        'React\\Promise\\Internal\\CancellationQueue' => __DIR__ . '/..' . '/react/promise/src/Internal/CancellationQueue.php',
        'React\\Promise\\Internal\\FulfilledPromise' => __DIR__ . '/..' . '/react/promise/src/Internal/FulfilledPromise.php',
        'React\\Promise\\Internal\\RejectedPromise' => __DIR__ . '/..' . '/react/promise/src/Internal/RejectedPromise.php',
        'React\\Promise\\Promise' => __DIR__ . '/..' . '/react/promise/src/Promise.php',
        'React\\Promise\\PromiseInterface' => __DIR__ . '/..' . '/react/promise/src/PromiseInterface.php',
        'ReflectionConstant' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/ReflectionConstant.php',
        'RoundingMode' => __DIR__ . '/..' . '/symfony/polyfill-php84/Resources/stubs/RoundingMode.php',
        'Safe\\DateTime' => __DIR__ . '/..' . '/thecodingmachine/safe/lib/DateTime.php',
        'Safe\\DateTimeImmutable' => __DIR__ . '/..' . '/thecodingmachine/safe/lib/DateTimeImmutable.php',
        'Safe\\Exceptions\\ApacheException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/ApacheException.php',
        'Safe\\Exceptions\\ApcuException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/ApcuException.php',
        'Safe\\Exceptions\\ArrayException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/ArrayException.php',
        'Safe\\Exceptions\\Bzip2Exception' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/Bzip2Exception.php',
        'Safe\\Exceptions\\CalendarException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/CalendarException.php',
        'Safe\\Exceptions\\ClassobjException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/ClassobjException.php',
        'Safe\\Exceptions\\ComException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/ComException.php',
        'Safe\\Exceptions\\CubridException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/CubridException.php',
        'Safe\\Exceptions\\CurlException' => __DIR__ . '/..' . '/thecodingmachine/safe/lib/Exceptions/CurlException.php',
        'Safe\\Exceptions\\DatetimeException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/DatetimeException.php',
        'Safe\\Exceptions\\DirException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/DirException.php',
        'Safe\\Exceptions\\EioException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/EioException.php',
        'Safe\\Exceptions\\ErrorfuncException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/ErrorfuncException.php',
        'Safe\\Exceptions\\ExecException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/ExecException.php',
        'Safe\\Exceptions\\FileinfoException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/FileinfoException.php',
        'Safe\\Exceptions\\FilesystemException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/FilesystemException.php',
        'Safe\\Exceptions\\FilterException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/FilterException.php',
        'Safe\\Exceptions\\FpmException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/FpmException.php',
        'Safe\\Exceptions\\FtpException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/FtpException.php',
        'Safe\\Exceptions\\FunchandException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/FunchandException.php',
        'Safe\\Exceptions\\GettextException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/GettextException.php',
        'Safe\\Exceptions\\GmpException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/GmpException.php',
        'Safe\\Exceptions\\GnupgException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/GnupgException.php',
        'Safe\\Exceptions\\HashException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/HashException.php',
        'Safe\\Exceptions\\IbaseException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/IbaseException.php',
        'Safe\\Exceptions\\IbmDb2Exception' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/IbmDb2Exception.php',
        'Safe\\Exceptions\\IconvException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/IconvException.php',
        'Safe\\Exceptions\\ImageException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/ImageException.php',
        'Safe\\Exceptions\\ImapException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/ImapException.php',
        'Safe\\Exceptions\\InfoException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/InfoException.php',
        'Safe\\Exceptions\\InotifyException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/InotifyException.php',
        'Safe\\Exceptions\\JsonException' => __DIR__ . '/..' . '/thecodingmachine/safe/lib/Exceptions/JsonException.php',
        'Safe\\Exceptions\\LdapException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/LdapException.php',
        'Safe\\Exceptions\\LibxmlException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/LibxmlException.php',
        'Safe\\Exceptions\\LzfException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/LzfException.php',
        'Safe\\Exceptions\\MailparseException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/MailparseException.php',
        'Safe\\Exceptions\\MbstringException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/MbstringException.php',
        'Safe\\Exceptions\\MiscException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/MiscException.php',
        'Safe\\Exceptions\\MysqlException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/MysqlException.php',
        'Safe\\Exceptions\\MysqliException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/MysqliException.php',
        'Safe\\Exceptions\\NetworkException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/NetworkException.php',
        'Safe\\Exceptions\\Oci8Exception' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/Oci8Exception.php',
        'Safe\\Exceptions\\OpcacheException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/OpcacheException.php',
        'Safe\\Exceptions\\OpensslException' => __DIR__ . '/..' . '/thecodingmachine/safe/lib/Exceptions/OpensslException.php',
        'Safe\\Exceptions\\OutcontrolException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/OutcontrolException.php',
        'Safe\\Exceptions\\PcntlException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/PcntlException.php',
        'Safe\\Exceptions\\PcreException' => __DIR__ . '/..' . '/thecodingmachine/safe/lib/Exceptions/PcreException.php',
        'Safe\\Exceptions\\PgsqlException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/PgsqlException.php',
        'Safe\\Exceptions\\PosixException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/PosixException.php',
        'Safe\\Exceptions\\PsException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/PsException.php',
        'Safe\\Exceptions\\PspellException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/PspellException.php',
        'Safe\\Exceptions\\ReadlineException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/ReadlineException.php',
        'Safe\\Exceptions\\RnpException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/RnpException.php',
        'Safe\\Exceptions\\RpminfoException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/RpminfoException.php',
        'Safe\\Exceptions\\RrdException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/RrdException.php',
        'Safe\\Exceptions\\SafeExceptionInterface' => __DIR__ . '/..' . '/thecodingmachine/safe/lib/Exceptions/SafeExceptionInterface.php',
        'Safe\\Exceptions\\SemException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/SemException.php',
        'Safe\\Exceptions\\SessionException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/SessionException.php',
        'Safe\\Exceptions\\ShmopException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/ShmopException.php',
        'Safe\\Exceptions\\SimplexmlException' => __DIR__ . '/..' . '/thecodingmachine/safe/lib/Exceptions/SimplexmlException.php',
        'Safe\\Exceptions\\SocketsException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/SocketsException.php',
        'Safe\\Exceptions\\SodiumException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/SodiumException.php',
        'Safe\\Exceptions\\SolrException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/SolrException.php',
        'Safe\\Exceptions\\SplException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/SplException.php',
        'Safe\\Exceptions\\SqlsrvException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/SqlsrvException.php',
        'Safe\\Exceptions\\SsdeepException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/SsdeepException.php',
        'Safe\\Exceptions\\Ssh2Exception' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/Ssh2Exception.php',
        'Safe\\Exceptions\\StreamException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/StreamException.php',
        'Safe\\Exceptions\\StringsException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/StringsException.php',
        'Safe\\Exceptions\\SwooleException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/SwooleException.php',
        'Safe\\Exceptions\\UodbcException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/UodbcException.php',
        'Safe\\Exceptions\\UopzException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/UopzException.php',
        'Safe\\Exceptions\\UrlException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/UrlException.php',
        'Safe\\Exceptions\\VarException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/VarException.php',
        'Safe\\Exceptions\\XdiffException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/XdiffException.php',
        'Safe\\Exceptions\\XmlException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/XmlException.php',
        'Safe\\Exceptions\\XmlrpcException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/XmlrpcException.php',
        'Safe\\Exceptions\\YamlException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/YamlException.php',
        'Safe\\Exceptions\\YazException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/YazException.php',
        'Safe\\Exceptions\\ZipException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/ZipException.php',
        'Safe\\Exceptions\\ZlibException' => __DIR__ . '/..' . '/thecodingmachine/safe/generated/Exceptions/ZlibException.php',
        'Seld\\JsonLint\\DuplicateKeyException' => __DIR__ . '/..' . '/seld/jsonlint/src/Seld/JsonLint/DuplicateKeyException.php',
        'Seld\\JsonLint\\JsonParser' => __DIR__ . '/..' . '/seld/jsonlint/src/Seld/JsonLint/JsonParser.php',
        'Seld\\JsonLint\\Lexer' => __DIR__ . '/..' . '/seld/jsonlint/src/Seld/JsonLint/Lexer.php',
        'Seld\\JsonLint\\ParsingException' => __DIR__ . '/..' . '/seld/jsonlint/src/Seld/JsonLint/ParsingException.php',
        'Seld\\JsonLint\\Undefined' => __DIR__ . '/..' . '/seld/jsonlint/src/Seld/JsonLint/Undefined.php',
        'Seld\\PharUtils\\Linter' => __DIR__ . '/..' . '/seld/phar-utils/src/Linter.php',
        'Seld\\PharUtils\\Timestamps' => __DIR__ . '/..' . '/seld/phar-utils/src/Timestamps.php',
        'Seld\\Signal\\SignalHandler' => __DIR__ . '/..' . '/seld/signal-handler/src/SignalHandler.php',
        'Stringable' => __DIR__ . '/..' . '/marc-mabe/php-enum/stubs/Stringable.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\\Command\\TraceableCommand' => __DIR__ . '/..' . '/symfony/console/Command/TraceableCommand.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\\Output\\FishCompletionOutput' => __DIR__ . '/..' . '/symfony/console/Completion/Output/FishCompletionOutput.php',
        'Symfony\\Component\\Console\\Completion\\Output\\ZshCompletionOutput' => __DIR__ . '/..' . '/symfony/console/Completion/Output/ZshCompletionOutput.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\\DataCollector\\CommandDataCollector' => __DIR__ . '/..' . '/symfony/console/DataCollector/CommandDataCollector.php',
        'Symfony\\Component\\Console\\Debug\\CliRequest' => __DIR__ . '/..' . '/symfony/console/Debug/CliRequest.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',
        'Symfony\\Component\\Console\\Descriptor\\DescriptorInterface' => __DIR__ . '/..' . '/symfony/console/Descriptor/DescriptorInterface.php',
        'Symfony\\Component\\Console\\Descriptor\\JsonDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/JsonDescriptor.php',
        'Symfony\\Component\\Console\\Descriptor\\MarkdownDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/MarkdownDescriptor.php',
        'Symfony\\Component\\Console\\Descriptor\\ReStructuredTextDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/ReStructuredTextDescriptor.php',
        'Symfony\\Component\\Console\\Descriptor\\TextDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/TextDescriptor.php',
        'Symfony\\Component\\Console\\Descriptor\\XmlDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/XmlDescriptor.php',
        'Symfony\\Component\\Console\\EventListener\\ErrorListener' => __DIR__ . '/..' . '/symfony/console/EventListener/ErrorListener.php',
        '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\\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\\RunCommandFailedException' => __DIR__ . '/..' . '/symfony/console/Exception/RunCommandFailedException.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',
        'Symfony\\Component\\Console\\Helper\\HelperSet' => __DIR__ . '/..' . '/symfony/console/Helper/HelperSet.php',
        'Symfony\\Component\\Console\\Helper\\InputAwareHelper' => __DIR__ . '/..' . '/symfony/console/Helper/InputAwareHelper.php',
        'Symfony\\Component\\Console\\Helper\\OutputWrapper' => __DIR__ . '/..' . '/symfony/console/Helper/OutputWrapper.php',
        'Symfony\\Component\\Console\\Helper\\ProcessHelper' => __DIR__ . '/..' . '/symfony/console/Helper/ProcessHelper.php',
        'Symfony\\Component\\Console\\Helper\\ProgressBar' => __DIR__ . '/..' . '/symfony/console/Helper/ProgressBar.php',
        'Symfony\\Component\\Console\\Helper\\ProgressIndicator' => __DIR__ . '/..' . '/symfony/console/Helper/ProgressIndicator.php',
        'Symfony\\Component\\Console\\Helper\\QuestionHelper' => __DIR__ . '/..' . '/symfony/console/Helper/QuestionHelper.php',
        '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\\Helper\\TerminalInputHelper' => __DIR__ . '/..' . '/symfony/console/Helper/TerminalInputHelper.php',
        'Symfony\\Component\\Console\\Input\\ArgvInput' => __DIR__ . '/..' . '/symfony/console/Input/ArgvInput.php',
        'Symfony\\Component\\Console\\Input\\ArrayInput' => __DIR__ . '/..' . '/symfony/console/Input/ArrayInput.php',
        'Symfony\\Component\\Console\\Input\\Input' => __DIR__ . '/..' . '/symfony/console/Input/Input.php',
        'Symfony\\Component\\Console\\Input\\InputArgument' => __DIR__ . '/..' . '/symfony/console/Input/InputArgument.php',
        'Symfony\\Component\\Console\\Input\\InputAwareInterface' => __DIR__ . '/..' . '/symfony/console/Input/InputAwareInterface.php',
        'Symfony\\Component\\Console\\Input\\InputDefinition' => __DIR__ . '/..' . '/symfony/console/Input/InputDefinition.php',
        'Symfony\\Component\\Console\\Input\\InputInterface' => __DIR__ . '/..' . '/symfony/console/Input/InputInterface.php',
        'Symfony\\Component\\Console\\Input\\InputOption' => __DIR__ . '/..' . '/symfony/console/Input/InputOption.php',
        'Symfony\\Component\\Console\\Input\\StreamableInputInterface' => __DIR__ . '/..' . '/symfony/console/Input/StreamableInputInterface.php',
        'Symfony\\Component\\Console\\Input\\StringInput' => __DIR__ . '/..' . '/symfony/console/Input/StringInput.php',
        'Symfony\\Component\\Console\\Logger\\ConsoleLogger' => __DIR__ . '/..' . '/symfony/console/Logger/ConsoleLogger.php',
        'Symfony\\Component\\Console\\Messenger\\RunCommandContext' => __DIR__ . '/..' . '/symfony/console/Messenger/RunCommandContext.php',
        'Symfony\\Component\\Console\\Messenger\\RunCommandMessage' => __DIR__ . '/..' . '/symfony/console/Messenger/RunCommandMessage.php',
        'Symfony\\Component\\Console\\Messenger\\RunCommandMessageHandler' => __DIR__ . '/..' . '/symfony/console/Messenger/RunCommandMessageHandler.php',
        'Symfony\\Component\\Console\\Output\\AnsiColorMode' => __DIR__ . '/..' . '/symfony/console/Output/AnsiColorMode.php',
        '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\\SignalMap' => __DIR__ . '/..' . '/symfony/console/SignalRegistry/SignalMap.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\\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\\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\\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\\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\\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\\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',
        'Symfony\\Component\\Finder\\Iterator\\DepthRangeFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/DepthRangeFilterIterator.php',
        'Symfony\\Component\\Finder\\Iterator\\ExcludeDirectoryFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php',
        '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\\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\\Process\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/process/Exception/ExceptionInterface.php',
        'Symfony\\Component\\Process\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/process/Exception/InvalidArgumentException.php',
        'Symfony\\Component\\Process\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/process/Exception/LogicException.php',
        'Symfony\\Component\\Process\\Exception\\ProcessFailedException' => __DIR__ . '/..' . '/symfony/process/Exception/ProcessFailedException.php',
        'Symfony\\Component\\Process\\Exception\\ProcessSignaledException' => __DIR__ . '/..' . '/symfony/process/Exception/ProcessSignaledException.php',
        'Symfony\\Component\\Process\\Exception\\ProcessTimedOutException' => __DIR__ . '/..' . '/symfony/process/Exception/ProcessTimedOutException.php',
        'Symfony\\Component\\Process\\Exception\\RunProcessFailedException' => __DIR__ . '/..' . '/symfony/process/Exception/RunProcessFailedException.php',
        'Symfony\\Component\\Process\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/process/Exception/RuntimeException.php',
        'Symfony\\Component\\Process\\ExecutableFinder' => __DIR__ . '/..' . '/symfony/process/ExecutableFinder.php',
        'Symfony\\Component\\Process\\InputStream' => __DIR__ . '/..' . '/symfony/process/InputStream.php',
        'Symfony\\Component\\Process\\Messenger\\RunProcessContext' => __DIR__ . '/..' . '/symfony/process/Messenger/RunProcessContext.php',
        'Symfony\\Component\\Process\\Messenger\\RunProcessMessage' => __DIR__ . '/..' . '/symfony/process/Messenger/RunProcessMessage.php',
        'Symfony\\Component\\Process\\Messenger\\RunProcessMessageHandler' => __DIR__ . '/..' . '/symfony/process/Messenger/RunProcessMessageHandler.php',
        'Symfony\\Component\\Process\\PhpExecutableFinder' => __DIR__ . '/..' . '/symfony/process/PhpExecutableFinder.php',
        'Symfony\\Component\\Process\\PhpProcess' => __DIR__ . '/..' . '/symfony/process/PhpProcess.php',
        'Symfony\\Component\\Process\\PhpSubprocess' => __DIR__ . '/..' . '/symfony/process/PhpSubprocess.php',
        'Symfony\\Component\\Process\\Pipes\\AbstractPipes' => __DIR__ . '/..' . '/symfony/process/Pipes/AbstractPipes.php',
        'Symfony\\Component\\Process\\Pipes\\PipesInterface' => __DIR__ . '/..' . '/symfony/process/Pipes/PipesInterface.php',
        'Symfony\\Component\\Process\\Pipes\\UnixPipes' => __DIR__ . '/..' . '/symfony/process/Pipes/UnixPipes.php',
        'Symfony\\Component\\Process\\Pipes\\WindowsPipes' => __DIR__ . '/..' . '/symfony/process/Pipes/WindowsPipes.php',
        'Symfony\\Component\\Process\\Process' => __DIR__ . '/..' . '/symfony/process/Process.php',
        'Symfony\\Component\\Process\\ProcessUtils' => __DIR__ . '/..' . '/symfony/process/ProcessUtils.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\\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\\ContainerAwareInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ContainerAwareInterface.php',
        'Symfony\\Contracts\\Service\\ResetInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ResetInterface.php',
        'Symfony\\Contracts\\Service\\ServiceCollectionInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceCollectionInterface.php',
        'Symfony\\Contracts\\Service\\ServiceLocatorTrait' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceLocatorTrait.php',
        'Symfony\\Contracts\\Service\\ServiceMethodsSubscriberTrait' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceMethodsSubscriberTrait.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\\Polyfill\\Ctype\\Ctype' => __DIR__ . '/..' . '/symfony/polyfill-ctype/Ctype.php',
        'Symfony\\Polyfill\\Intl\\Grapheme\\Grapheme' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/Grapheme.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\\Php84\\Php84' => __DIR__ . '/..' . '/symfony/polyfill-php84/Php84.php',
        'ThePhpFoundation\\Attestation\\Attestation' => __DIR__ . '/..' . '/thephpf/attestation/src/Attestation.php',
        'ThePhpFoundation\\Attestation\\AttestationException' => __DIR__ . '/..' . '/thephpf/attestation/src/AttestationException.php',
        'ThePhpFoundation\\Attestation\\FilenameWithChecksum' => __DIR__ . '/..' . '/thephpf/attestation/src/FilenameWithChecksum.php',
        'ThePhpFoundation\\Attestation\\FulcioSigstoreOidExtensions' => __DIR__ . '/..' . '/thephpf/attestation/src/FulcioSigstoreOidExtensions.php',
        'ThePhpFoundation\\Attestation\\Verification\\Exception\\DigestMismatch' => __DIR__ . '/..' . '/thephpf/attestation/src/Verification/Exception/DigestMismatch.php',
        'ThePhpFoundation\\Attestation\\Verification\\Exception\\FailedToVerifyArtifact' => __DIR__ . '/..' . '/thephpf/attestation/src/Verification/Exception/FailedToVerifyArtifact.php',
        'ThePhpFoundation\\Attestation\\Verification\\Exception\\InvalidDerEncodedStringLength' => __DIR__ . '/..' . '/thephpf/attestation/src/Verification/Exception/InvalidDerEncodedStringLength.php',
        'ThePhpFoundation\\Attestation\\Verification\\Exception\\InvalidSubjectDefinition' => __DIR__ . '/..' . '/thephpf/attestation/src/Verification/Exception/InvalidSubjectDefinition.php',
        'ThePhpFoundation\\Attestation\\Verification\\Exception\\IssuerCertificateVerificationFailed' => __DIR__ . '/..' . '/thephpf/attestation/src/Verification/Exception/IssuerCertificateVerificationFailed.php',
        'ThePhpFoundation\\Attestation\\Verification\\Exception\\MismatchingExtensionValues' => __DIR__ . '/..' . '/thephpf/attestation/src/Verification/Exception/MismatchingExtensionValues.php',
        'ThePhpFoundation\\Attestation\\Verification\\Exception\\MissingAttestation' => __DIR__ . '/..' . '/thephpf/attestation/src/Verification/Exception/MissingAttestation.php',
        'ThePhpFoundation\\Attestation\\Verification\\Exception\\NoIssuerCertificateInTrustedRoot' => __DIR__ . '/..' . '/thephpf/attestation/src/Verification/Exception/NoIssuerCertificateInTrustedRoot.php',
        'ThePhpFoundation\\Attestation\\Verification\\Exception\\NoOpenSsl' => __DIR__ . '/..' . '/thephpf/attestation/src/Verification/Exception/NoOpenSsl.php',
        'ThePhpFoundation\\Attestation\\Verification\\Exception\\SignatureVerificationFailed' => __DIR__ . '/..' . '/thephpf/attestation/src/Verification/Exception/SignatureVerificationFailed.php',
        'ThePhpFoundation\\Attestation\\Verification\\VerifyAttestation' => __DIR__ . '/..' . '/thephpf/attestation/src/Verification/VerifyAttestation.php',
        'ThePhpFoundation\\Attestation\\Verification\\VerifyAttestationWithOpenSsl' => __DIR__ . '/..' . '/thephpf/attestation/src/Verification/VerifyAttestationWithOpenSsl.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',
    );

    public static function getInitializer(ClassLoader $loader)
    {
        return \Closure::bind(function () use ($loader) {
            $loader->prefixLengthsPsr4 = ComposerStaticInit020913853b4b516248bbc0df3c21c772::$prefixLengthsPsr4;
            $loader->prefixDirsPsr4 = ComposerStaticInit020913853b4b516248bbc0df3c21c772::$prefixDirsPsr4;
            $loader->classMap = ComposerStaticInit020913853b4b516248bbc0df3c21c772::$classMap;

        }, null, ClassLoader::class);
    }
}
{
    "$schema": "https://json-schema.org/draft-04/schema#",
    "title": "Composer Package Repository",
    "type": "object",
    "oneOf": [
        { "required": [ "packages" ] },
        { "required": [ "providers" ] },
        { "required": [ "provider-includes", "providers-url" ] },
        { "required": [ "metadata-url" ] }
    ],
    "properties": {
        "packages": {
            "type": ["object", "array"],
            "description": "A hashmap of package names in the form of <vendor>/<name>.",
            "additionalProperties": { "$ref": "#/definitions/versions" }
        },
        "metadata-url": {
            "type": "string",
            "description": "Endpoint to retrieve package metadata data from, in Composer v2 format, e.g. '/p2/%package%.json'."
        },
        "available-packages": {
            "type": "array",
            "items": {
                "type": "string"
            },
            "description": "If your repository only has a small number of packages, and you want to avoid serving many 404s, specify all the package names that your repository contains here."
        },
        "available-package-patterns": {
            "type": "array",
            "items": {
                "type": "string"
            },
            "description": "If your repository only has a small number of packages, and you want to avoid serving many 404s, specify package name patterns containing wildcards (*) that your repository contains here."
        },
        "security-advisories": {
            "type": "array",
            "items": {
                "type": "object",
                "required": ["metadata", "api-url"],
                "properties": {
                    "metadata": {
                        "type": "boolean",
                        "description": "Whether metadata files contain security advisory data or whether it should always be queried using the API URL."
                    },
                    "api-url": {
                        "type": "string",
                        "description": "Endpoint to call to retrieve security advisories data."
                    }
                }
            }
        },
        "metadata-changes-url": {
            "type": "string",
            "description": "Endpoint to retrieve package metadata updates from. This should receive a timestamp since last call to be able to return new changes. e.g. '/metadata/changes.json'."
        },
        "providers-api": {
            "type": "string",
            "description": "Endpoint to retrieve package names providing a given name from, e.g. '/providers/%package%.json'."
        },
        "notify-batch": {
            "type": "string",
            "description": "Endpoint to call after multiple packages have been installed, e.g. '/downloads/'."
        },
        "search": {
            "type": "string",
            "description": "Endpoint that provides search capabilities, e.g. '/search.json?q=%query%&type=%type%'."
        },
        "list": {
            "type": "string",
            "description": "Endpoint that provides a full list of packages present in the repository. It should accept an optional `?filter=xx` query param, which can contain `*` as wildcards matching any substring. e.g. '/list.json'."
        },
        "warnings": {
            "type": "array",
            "items": {
                "type": "object",
                "required": ["message", "versions"],
                "properties": {
                    "message": {
                        "type": "string",
                        "description": "A message that will be output by Composer as a warning when this source is consulted."
                    },
                    "versions": {
                        "type": "string",
                        "description": "A version constraint to limit to which Composer versions the warning should be shown."
                    }
                }
            }
        },
        "infos": {
            "type": "array",
            "items": {
                "type": "object",
                "required": ["message", "versions"],
                "properties": {
                    "message": {
                        "type": "string",
                        "description": "A message that will be output by Composer as info when this source is consulted."
                    },
                    "versions": {
                        "type": "string",
                        "description": "A version constraint to limit to which Composer versions the info should be shown."
                    }
                }
            }
        },
        "providers-url": {
            "type": "string",
            "description": "DEPRECATED: Endpoint to retrieve provider data from, e.g. '/p/%package%$%hash%.json'."
        },
        "provider-includes": {
            "type": "object",
            "description": "DEPRECATED: A hashmap of provider listings.",
            "additionalProperties": { "$ref": "#/definitions/provider" }
        },
        "providers": {
            "type": "object",
            "description": "DEPRECATED: A hashmap of package names in the form of <vendor>/<name>.",
            "additionalProperties": { "$ref": "#/definitions/provider" }
        },
        "warning": {
            "type": "string",
            "description": "DEPRECATED: A message that will be output by Composer as a warning when this source is consulted."
        },
        "warning-versions": {
            "type": "string",
            "description": "DEPRECATED: A version constraint to limit to which Composer versions the warning should be shown."
        },
        "info": {
            "type": "string",
            "description": "DEPRECATED: A message that will be output by Composer as a info when this source is consulted."
        },
        "info-versions": {
            "type": "string",
            "description": "DEPRECATED: A version constraint to limit to which Composer versions the info should be shown."
        }
    },
    "definitions": {
        "versions": {
            "type": "object",
            "description": "A hashmap of versions and their metadata.",
            "additionalProperties": { "$ref": "#/definitions/version" }
        },
        "version": {
            "type": "object",
            "oneOf": [
                { "$ref": "#/definitions/package" },
                { "$ref": "#/definitions/metapackage" }
            ]
        },
        "package-base": {
            "properties": {
                "name": { "type": "string" },
                "type": { "type": "string" },
                "version": { "type": "string" },
                "version_normalized": {
                    "type": "string",
                    "description": "Normalized version, optional but can save computational time on client side."
                },
                "autoload": { "type": "object" },
                "require": { "type": "object" },
                "replace": { "type": "object" },
                "conflict": { "type": "object" },
                "provide": { "type": "object" },
                "time": { "type": "string" }
            },
            "additionalProperties": true
        },
        "package": {
            "allOf": [
                { "$ref": "#/definitions/package-base" },
                {
                    "properties": {
                        "dist": { "type": "object" },
                        "source": { "type": "object" }
                    }
                },
                { "oneOf": [
                    { "required": [ "name", "version", "source" ] },
                    { "required": [ "name", "version", "dist" ] }
                ] }
            ]
        },
        "metapackage": {
            "allOf": [
                { "$ref": "#/definitions/package-base" },
                {
                    "properties": {
                        "type": { "type": "string", "enum": [ "metapackage" ] }
                    },
                    "required": [ "name", "version", "type" ]
                }
            ]
        },
        "provider": {
            "type": "object",
            "properties": {
                "sha256": {
                    "type": "string",
                    "description": "Hash value that can be used to validate the resource."
                }
            }
        }
    }
}
{
    "$schema": "https://json-schema.org/draft-04/schema#",
    "title": "Composer Package",
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "description": "Package name, including 'vendor-name/' prefix.",
            "pattern": "^[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9](([_.]|-{1,2})?[a-z0-9]+)*$"
        },
        "description": {
            "type": "string",
            "description": "Short package description."
        },
        "license": {
            "type": ["string", "array"],
            "description": "License name. Or an array of license names."
        },
        "type": {
            "description": "Package type, either 'library' for common packages, 'composer-plugin' for plugins, 'metapackage' for empty packages, or a custom type ([a-z0-9-]+) defined by whatever project this package applies to.",
            "type": "string",
            "pattern": "^[a-z0-9-]+$"
        },
        "abandoned": {
            "type": ["boolean", "string"],
            "description": "Indicates whether this package has been abandoned, it can be boolean or a package name/URL pointing to a recommended alternative. Defaults to false."
        },
        "version": {
            "type": "string",
            "description": "Package version, see https://getcomposer.org/doc/04-schema.md#version for more info on valid schemes.",
            "pattern": "^[vV]?\\d+(?:[.-]\\d+){0,3}[._-]?(?:(?:[sS][tT][aA][bB][lL][eE]|[bB][eE][tT][aA]|[bB]|[rR][cC]|[aA][lL][pP][hH][aA]|[aA]|[pP][aA][tT][cC][hH]|[pP][lL]|[pP])(?:(?:[.-]?\\d+)*)?)?(?:[.-]?[dD][eE][vV]|\\.x-dev)?(?:\\+.*)?$|^dev-.*$"
        },
        "default-branch": {
            "type": ["boolean"],
            "description": "Internal use only, do not specify this in composer.json. Indicates whether this version is the default branch of the linked VCS repository. Defaults to false."
        },
        "non-feature-branches": {
            "type": ["array"],
            "description": "A set of string or regex patterns for non-numeric branch names that will not be handled as feature branches.",
            "items": {
                "type": "string"
            }
        },
        "keywords": {
            "type": "array",
            "items": {
                "type": "string",
                "description": "A tag/keyword that this package relates to."
            }
        },
        "readme": {
            "type": "string",
            "description": "Relative path to the readme document."
        },
        "time": {
            "type": "string",
            "description": "Package release date, in 'YYYY-MM-DD', 'YYYY-MM-DD HH:MM:SS' or 'YYYY-MM-DDTHH:MM:SSZ' format."
        },
        "authors": {
            "$ref": "#/definitions/authors"
        },
        "homepage": {
            "type": "string",
            "description": "Homepage URL for the project.",
            "format": "uri"
        },
        "support": {
            "type": "object",
            "properties": {
                "email": {
                    "type": "string",
                    "description": "Email address for support.",
                    "format": "email"
                },
                "issues": {
                    "type": "string",
                    "description": "URL to the issue tracker.",
                    "format": "uri"
                },
                "forum": {
                    "type": "string",
                    "description": "URL to the forum.",
                    "format": "uri"
                },
                "wiki": {
                    "type": "string",
                    "description": "URL to the wiki.",
                    "format": "uri"
                },
                "irc": {
                    "type": "string",
                    "description": "IRC channel for support, as irc://server/channel.",
                    "format": "uri"
                },
                "chat": {
                    "type": "string",
                    "description": "URL to the support chat.",
                    "format": "uri"
                },
                "source": {
                    "type": "string",
                    "description": "URL to browse or download the sources.",
                    "format": "uri"
                },
                "docs": {
                    "type": "string",
                    "description": "URL to the documentation.",
                    "format": "uri"
                },
                "rss": {
                    "type": "string",
                    "description": "URL to the RSS feed.",
                    "format": "uri"
                },
                "security": {
                    "type": "string",
                    "description": "URL to the vulnerability disclosure policy (VDP).",
                    "format": "uri"
                }
            }
        },
        "funding": {
            "type": "array",
            "description": "A list of options to fund the development and maintenance of the package.",
            "items": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "description": "Type of funding or platform through which funding is possible."
                    },
                    "url": {
                        "type": "string",
                        "description": "URL to a website with details on funding and a way to fund the package.",
                        "format": "uri"
                    }
                }
            }
        },
        "source": {
            "$ref": "#/definitions/source"
        },
        "dist": {
            "$ref": "#/definitions/dist"
        },
        "_comment": {
            "type": ["array", "string"],
            "description": "A key to store comments in"
        },
        "require": {
            "type": "object",
            "description": "This is an object of package name (keys) and version constraints (values) that are required to run this package.",
            "additionalProperties": {
                "type": "string"
            }
        },
        "require-dev": {
            "type": "object",
            "description": "This is an object of package name (keys) and version constraints (values) that this package requires for developing it (testing tools and such).",
            "additionalProperties": {
                "type": "string"
            }
        },
        "replace": {
            "type": "object",
            "description": "This is an object of package name (keys) and version constraints (values) that can be replaced by this package.",
            "additionalProperties": {
                "type": "string"
            }
        },
        "conflict": {
            "type": "object",
            "description": "This is an object of package name (keys) and version constraints (values) that conflict with this package.",
            "additionalProperties": {
                "type": "string"
            }
        },
        "provide": {
            "type": "object",
            "description": "This is an object of package name (keys) and version constraints (values) that this package provides in addition to this package's name.",
            "additionalProperties": {
                "type": "string"
            }
        },
        "suggest": {
            "type": "object",
            "description": "This is an object of package name (keys) and descriptions (values) that this package suggests work well with it (this will be suggested to the user during installation).",
            "additionalProperties": {
                "type": "string"
            }
        },
        "repositories": {
            "type": ["object", "array"],
            "description": "A set of additional repositories where packages can be found.",
            "additionalProperties": {
                "anyOf": [
                    { "$ref": "#/definitions/anonymous-repository" },
                    { "type": "boolean", "enum": [false] }
                ]
            },
            "items": {
                "anyOf": [
                    { "$ref": "#/definitions/repository" },
                    {
                        "type": "object",
                        "additionalProperties": { "type": "boolean", "enum": [false] },
                        "minProperties": 1,
                        "maxProperties": 1
                    }
                ]
            }
        },
        "minimum-stability": {
            "type": ["string"],
            "description": "The minimum stability the packages must have to be install-able. Possible values are: dev, alpha, beta, RC, stable.",
            "enum": ["dev", "alpha", "beta", "rc", "RC", "stable"]
        },
        "prefer-stable": {
            "type": ["boolean"],
            "description": "If set to true, stable packages will be preferred to dev packages when possible, even if the minimum-stability allows unstable packages."
        },
        "autoload": {
            "$ref": "#/definitions/autoload"
        },
        "autoload-dev": {
            "type": "object",
            "description": "Description of additional autoload rules for development purpose (eg. a test suite).",
            "properties": {
                "psr-0": {
                    "type": "object",
                    "description": "This is an object of namespaces (keys) and the directories they can be found into (values, can be arrays of paths) by the autoloader.",
                    "additionalProperties": {
                        "type": ["string", "array"],
                        "items": {
                            "type": "string"
                        }
                    }
                },
                "psr-4": {
                    "type": "object",
                    "description": "This is an object of namespaces (keys) and the PSR-4 directories they can map to (values, can be arrays of paths) by the autoloader.",
                    "additionalProperties": {
                        "type": ["string", "array"],
                        "items": {
                            "type": "string"
                        }
                    }
                },
                "classmap": {
                    "type": "array",
                    "description": "This is an array of paths that contain classes to be included in the class-map generation process."
                },
                "files": {
                    "type": "array",
                    "description": "This is an array of files that are always required on every request."
                }
            }
        },
        "target-dir": {
            "description": "DEPRECATED: Forces the package to be installed into the given subdirectory path. This is used for autoloading PSR-0 packages that do not contain their full path. Use forward slashes for cross-platform compatibility.",
            "type": "string"
        },
        "include-path": {
            "type": ["array"],
            "description": "DEPRECATED: A list of directories which should get added to PHP's include path. This is only present to support legacy projects, and all new code should preferably use autoloading.",
            "items": {
                "type": "string"
            }
        },
        "bin": {
            "type": ["string", "array"],
            "description": "A set of files, or a single file, that should be treated as binaries and symlinked into bin-dir (from config).",
            "items": {
                "type": "string"
            }
        },
        "archive": {
            "type": ["object"],
            "description": "Options for creating package archives for distribution.",
            "properties": {
                "name": {
                    "type": "string",
                    "description": "A base name for archive."
                },
                "exclude": {
                    "type": "array",
                    "description": "A list of patterns for paths to exclude or include if prefixed with an exclamation mark."
                }
            }
        },
        "php-ext": {
            "type": "object",
            "description": "Settings for PHP extension packages.",
            "properties": {
                "extension-name": {
                    "type": "string",
                    "description": "If specified, this will be used as the name of the extension, where needed by tooling. If this is not specified, the extension name will be derived from the Composer package name (e.g. `vendor/name` would become `ext-name`). The extension name may be specified with or without the `ext-` prefix, and tools that use this must normalise this appropriately.",
                    "example": "ext-xdebug"
                },
                "priority": {
                    "type": "integer",
                    "description": "This is used to add a prefix to the INI file, e.g. `90-xdebug.ini` which affects the loading order. The priority is a number in the range 10-99 inclusive, with 10 being the highest priority (i.e. will be processed first), and 99 being the lowest priority (i.e. will be processed last). There are two digits so that the files sort correctly on any platform, whether the sorting is natural or not.",
                    "minimum": 10,
                    "maximum": 99,
                    "example": 80,
                    "default": 80
                },
                "support-zts": {
                    "type": "boolean",
                    "description": "Does this package support Zend Thread Safety",
                    "example": false,
                    "default": true
                },
                "support-nts": {
                    "type": "boolean",
                    "description": "Does this package support non-Thread Safe mode",
                    "example": false,
                    "default": true
                },
                "build-path": {
                    "type": ["string", "null"],
                    "description": "If specified, this is the subdirectory that will be used to build the extension instead of the root of the project.",
                    "example": "my-extension-source",
                    "default": null
                },
                "download-url-method": {
                    "oneOf": [
                        {
                            "type": "string",
                            "description": "If specified, this technique will be used to override the URL that PIE uses to download the asset. The default, if not specified, is composer-default.",
                            "deprecated": true,
                            "enum": ["composer-default", "pre-packaged-source", "pre-packaged-binary"],
                            "example": "composer-default",
                            "default": "composer-default"
                        },
                        {
                            "type": "array",
                            "description": "Multiple techniques can be specified, in which case PIE will try each in turn until one succeeds. The first technique that succeeds will be used.",
                            "items": {
                                "type": "string",
                                "description": "If specified, this technique will be used to override the URL that PIE uses to download the asset. The default, if not specified, is composer-default.",
                                "enum": ["composer-default", "pre-packaged-source", "pre-packaged-binary"],
                                "example": ["pre-packaged-binary", "composer-default"]
                            },
                            "minItems": 1,
                            "default": ["composer-default"]
                        }
                    ]
                },
                "os-families": {
                    "type": "array",
                    "minItems": 1,
                    "description": "An array of OS families to mark as compatible with the extension. Specifying this property will mean this package is not installable with PIE on any OS family not listed here. Must not be specified alongside os-families-exclude.",
                    "items": {
                        "type": "string",
                        "enum": ["windows", "bsd", "darwin", "solaris", "linux", "unknown"],
                        "description": "The name of the OS family to mark as compatible."
                    }
                },
                "os-families-exclude": {
                    "type": "array",
                    "minItems": 1,
                    "description": "An array of OS families to mark as incompatible with the extension. Specifying this property will mean this package is installable on any OS family except those listed here. Must not be specified alongside os-families.",
                    "items": {
                        "type": "string",
                        "enum": ["windows", "bsd", "darwin", "solaris", "linux", "unknown"],
                        "description": "The name of the OS family to exclude."
                    }
                },
                "configure-options": {
                    "type": "array",
                    "description": "These configure options make up the flags that can be passed to ./configure when installing the extension.",
                    "items": {
                        "type": "object",
                        "required": ["name"],
                        "properties": {
                            "name": {
                                "type": "string",
                                "description": "The name of the flag, this would typically be prefixed with `--`, for example, the value 'the-flag' would be passed as `./configure --the-flag`.",
                                "example": "without-xdebug-compression",
                                "pattern": "^[a-zA-Z0-9][a-zA-Z0-9-_]*$"
                            },
                            "needs-value": {
                                "type": "boolean",
                                "description": "If this is set to true, the flag needs a value (e.g. --with-somelib=<path>), otherwise it is a flag without a value (e.g. --enable-some-feature).",
                                "example": false,
                                "default": false
                            },
                            "description": {
                                "type": "string",
                                "description": "The description of what the flag does or means.",
                                "example": "Disable compression through zlib"
                            }
                        }
                    }
                }
            },
            "allOf": [
                {
                    "not": {
                        "required": ["os-families", "os-families-exclude"]
                    }
                }
            ]
        },
        "config": {
            "type": "object",
            "description": "Composer options.",
            "properties": {
                "platform": {
                    "type": "object",
                    "description": "This is an object of package name (keys) and version (values) that will be used to mock the platform packages on this machine, the version can be set to false to make it appear like the package is not present.",
                    "additionalProperties": {
                        "type": ["string", "boolean"]
                    }
                },
                "allow-plugins": {
                    "type": ["object", "boolean"],
                    "description": "This is an object of {\"pattern\": true|false} with packages which are allowed to be loaded as plugins, or true to allow all, false to allow none. Defaults to {} which prompts when an unknown plugin is added.",
                    "additionalProperties": {
                        "type": ["boolean"]
                    }
                },
                "process-timeout": {
                    "type": "integer",
                    "description": "The timeout in seconds for process executions, defaults to 300 (5mins)."
                },
                "use-include-path": {
                    "type": "boolean",
                    "description": "If true, the Composer autoloader will also look for classes in the PHP include path."
                },
                "use-parent-dir": {
                    "type": ["string", "boolean"],
                    "description": "When running Composer in a directory where there is no composer.json, if there is one present in a directory above Composer will by default ask you whether you want to use that directory's composer.json instead. One of: true (always use parent if needed), false (never ask or use it) or \"prompt\" (ask every time), defaults to prompt."
                },
                "preferred-install": {
                    "type": ["string", "object"],
                    "description": "The install method Composer will prefer to use, defaults to auto and can be any of source, dist, auto, or an object of {\"pattern\": \"preference\"}.",
                    "additionalProperties": {
                        "type": ["string"]
                    }
                },
                "audit": {
                    "type": "object",
                    "description": "Security audit and version blocking configuration options",
                    "properties": {
                        "ignore": {
                            "anyOf": [
                                {
                                    "type": "object",
                                    "description": "A list of advisory ids, remote ids, CVE ids or package names (keys) with either explanations (string values) or detailed configuration (object values with 'apply' and 'reason' fields) for why they're ignored.",
                                    "additionalProperties": {
                                        "anyOf": [
                                            {
                                                "type": "string",
                                                "description": "Explanation for ignoring this advisory (applies to audit reports and version blocking)"
                                            },
                                            {
                                                "type": "object",
                                                "description": "Detailed configuration for ignoring this advisory",
                                                "properties": {
                                                    "apply": {
                                                        "type": "string",
                                                        "enum": ["audit", "block", "all"],
                                                        "description": "Where to apply this ignore: 'audit' (only audit reports), 'block' (only version blocking), or 'all' (both)"
                                                    },
                                                    "reason": {
                                                        "type": "string",
                                                        "description": "Explanation for ignoring this advisory, e.g. vulnerable code path not reachable in application"
                                                    }
                                                }
                                            },
                                            {
                                                "type": "null",
                                                "description": "Ignore without explanation (applies to audit reports and version blocking)"
                                            }
                                        ]
                                    }
                                },
                                {
                                    "type": "array",
                                    "description": "A set of advisory ids, remote ids, CVE ids or package names that are reported but let the audit command pass. Applies to audit reports and version blocking.",
                                    "items": {
                                        "type": "string"
                                    }
                                }
                            ]
                        },
                        "abandoned": {
                            "enum": ["ignore", "report", "fail"],
                            "description": "Whether abandoned packages should be ignored, reported as problems or cause an audit failure. Applies only to audit reports, not to version blocking."
                        },
                        "ignore-severity": {
                            "anyOf": [
                                {
                                    "type": "object",
                                    "description": "A list of severities (keys) with either explanations (string values) or detailed configuration (object values with 'apply' fields) for why they're ignored.",
                                    "additionalProperties": {
                                        "anyOf": [
                                            {
                                                "type": "string",
                                                "description": "Explanation for ignoring this severity (applies to audit reports and version blocking)"
                                            },
                                            {
                                                "type": "object",
                                                "description": "Detailed configuration for ignoring this severity",
                                                "properties": {
                                                    "apply": {
                                                        "type": "string",
                                                        "enum": ["audit", "block", "all"],
                                                        "description": "Where to apply this ignore: 'audit' (only audit reports), 'block' (only version blocking), or 'all' (both)"
                                                    }
                                                }
                                            },
                                            {
                                                "type": "null",
                                                "description": "Ignore without explanation (applies to audit reports and version blocking)"
                                            }
                                        ]
                                    }
                                },
                                {
                                    "type": "array",
                                    "description": "A list of severities for which advisories with matching severity will be ignored, e.g. critical, high, medium, low, or none. Applies to audit reports and version blocking.",
                                    "items": {
                                        "type": "string"
                                    }
                                }
                            ]
                        },
                        "ignore-unreachable": {
                            "type": "boolean",
                            "description": "Whether repositories that are unreachable or return a non-200 status code should be ignored or not. Applies only to the composer audit command, does not affect audit report summaries in other commands or version blocking."
                        },
                        "block-insecure": {
                            "type": "boolean",
                            "description": "Whether insecure versions should be blocked during a composer update/require command or not.",
                            "default": true
                        },
                        "block-abandoned": {
                            "type": "boolean",
                            "description": "Whether abandoned packages should be blocked during a composer update/require command or not. Applies only if blocking of insecure versions is enabled.",
                            "default": false
                        },
                        "ignore-abandoned": {
                            "anyOf": [
                                {
                                    "type": "object",
                                    "description": "A list of abandoned package names (keys) with either explanations (string values) or detailed configuration (object values with 'apply' and 'reason' fields) for why they're ignored.",
                                    "additionalProperties": {
                                        "anyOf": [
                                            {
                                                "type": "string",
                                                "description": "Explanation for ignoring this abandoned package (applies to audit reports and version blocking)"
                                            },
                                            {
                                                "type": "object",
                                                "description": "Detailed configuration for ignoring this abandoned package",
                                                "properties": {
                                                    "apply": {
                                                        "type": "string",
                                                        "enum": ["audit", "block", "all"],
                                                        "description": "Where to apply this ignore: 'audit' (only audit reports), 'block' (only version blocking), or 'all' (both)"
                                                    },
                                                    "reason": {
                                                        "type": "string",
                                                        "description": "Explanation for ignoring this abandoned package"
                                                    }
                                                }
                                            },
                                            {
                                                "type": "null",
                                                "description": "Ignore without explanation (applies to audit reports and version blocking)"
                                            }
                                        ]
                                    }
                                },
                                {
                                    "type": "array",
                                    "description": "A set of abandoned package names that are reported but let the audit command pass and that are not blocked during updates/requires.",
                                    "items": {
                                        "type": "string"
                                    }
                                }
                            ]
                        }
                    }
                },
                "notify-on-install": {
                    "type": "boolean",
                    "description": "Composer allows repositories to define a notification URL, so that they get notified whenever a package from that repository is installed. This option allows you to disable that behaviour, defaults to true."
                },
                "github-protocols": {
                    "type": "array",
                    "description": "A list of protocols to use for github.com clones, in priority order, defaults to [\"https\", \"ssh\", \"git\"].",
                    "items": {
                        "type": "string"
                    }
                },
                "github-oauth": {
                    "type": "object",
                    "description": "An object of domain name => github API oauth tokens, typically {\"github.com\":\"<token>\"}.",
                    "additionalProperties": {
                        "type": "string"
                    }
                },
                "gitlab-oauth": {
                    "type": "object",
                    "description": "An object of domain name => gitlab API oauth tokens, typically {\"gitlab.com\":{\"expires-at\":\"<expiration date>\", \"refresh-token\":\"<refresh token>\", \"token\":\"<token>\"}}.",
                    "additionalProperties": {
                        "type": ["string", "object"],
                        "required": [ "token"],
                        "properties": {
                            "expires-at": {
                                "type": "integer",
                                "description": "The expiration date for this GitLab token"
                            },
                            "refresh-token": {
                                "type": "string",
                                "description": "The refresh token used for GitLab authentication"
                            },
                            "token": {
                                "type": "string",
                                "description": "The token used for GitLab authentication"
                            }
                        }
                    }
                },
                "gitlab-token": {
                    "type": "object",
                    "description": "An object of domain name => gitlab private tokens, typically {\"gitlab.com\":\"<token>\"}, or an object with username and token keys.",
                    "additionalProperties": {
                        "type": ["string", "object"],
                        "required": ["username", "token"],
                        "properties": {
                            "username": {
                                "type": "string",
                                "description": "The username used for GitLab authentication"
                            },
                            "token": {
                                "type": "string",
                                "description": "The token used for GitLab authentication"
                            }
                        }
                    }
                },
                "gitlab-protocol": {
                    "enum": ["git", "http", "https"],
                    "description": "A protocol to force use of when creating a repository URL for the `source` value of the package metadata. One of `git` or `http`. By default, Composer will generate a git URL for private repositories and http one for public repos."
                },
                "bearer": {
                    "type": "object",
                    "description": "An object of domain name => bearer authentication token, for example {\"example.com\":\"<token>\"}.",
                    "additionalProperties": {
                        "type": "string"
                    }
                },
                "custom-headers": {
                    "type": "object",
                    "description": "Custom HTTP headers for specific domains.",
                    "additionalProperties": {
                        "type": "array",
                        "items": {
                            "type": "string",
                            "description": "A header in format 'Header-Name: Header-Value'"
                        }
                    }
                },
                "forgejo-token": {
                    "type": "object",
                    "description": "An object of domain name => forgejo username/access token, typically {\"codeberg.org\":{\"username\": \"<username>\", \"token\": \"<access-token>\"}}.",
                    "additionalProperties": {
                        "type": ["object"],
                        "required": ["username", "token"],
                        "properties": {
                            "username": {
                                "type": "string",
                                "description": "The username used for Forgejo authentication"
                            },
                            "token": {
                                "type": "string",
                                "description": "The access token used for Forgejo authentication"
                            }
                        }
                    }
                },
                "disable-tls": {
                    "type": "boolean",
                    "description": "Defaults to `false`. If set to true all HTTPS URLs will be tried with HTTP instead and no network level encryption is performed. Enabling this is a security risk and is NOT recommended. The better way is to enable the php_openssl extension in php.ini."
                },
                "secure-http": {
                    "type": "boolean",
                    "description": "Defaults to `true`. If set to true only HTTPS URLs are allowed to be downloaded via Composer. If you really absolutely need HTTP access to something then you can disable it, but using \"Let's Encrypt\" to get a free SSL certificate is generally a better alternative."
                },
                "secure-svn-domains": {
                    "type": "array",
                    "description": "A list of domains which should be trusted/marked as using a secure Subversion/SVN transport. By default svn:// protocol is seen as insecure and will throw. This is a better/safer alternative to disabling `secure-http` altogether.",
                    "items": {
                        "type": "string"
                    }
                },
                "cafile": {
                    "type": "string",
                    "description": "A way to set the path to the openssl CA file. In PHP 5.6+ you should rather set this via openssl.cafile in php.ini, although PHP 5.6+ should be able to detect your system CA file automatically."
                },
                "capath": {
                    "type": "string",
                    "description": "If cafile is not specified or if the certificate is not found there, the directory pointed to by capath is searched for a suitable certificate. capath must be a correctly hashed certificate directory."
                },
                "http-basic": {
                    "type": "object",
                    "description": "An object of domain name => {\"username\": \"...\", \"password\": \"...\"}.",
                    "additionalProperties": {
                        "type": "object",
                        "required": ["username", "password"],
                        "properties": {
                            "username": {
                                "type": "string",
                                "description": "The username used for HTTP Basic authentication"
                            },
                            "password": {
                                "type": "string",
                                "description": "The password used for HTTP Basic authentication"
                            }
                        }
                    }
                },
                "client-certificate": {
                    "type": "object",
                    "description": "An object of domain name => {\"local_cert\": \"...\", \"local_pk\"?: \"...\", \"passphrase\"?: \"...\"} to provide client certificate.",
                    "additionalProperties": {
                        "type": "object",
                        "required": ["local_cert"],
                        "properties": {
                            "local_cert": {
                                "type": "string",
                                "description": "Path to a certificate (pem) or pair certificate+key (pem)"
                            },
                            "local_pk": {
                                "type": "string",
                                "description": "Path to a private key file (pem)"
                            },
                            "passphrase": {
                                "type": "string",
                                "description": "Passphrase for private key"
                            }
                        }
                    }
                },
                "store-auths": {
                    "type": ["string", "boolean"],
                    "description": "What to do after prompting for authentication, one of: true (store), false (do not store) or \"prompt\" (ask every time), defaults to prompt."
                },
                "vendor-dir": {
                    "type": "string",
                    "description": "The location where all packages are installed, defaults to \"vendor\"."
                },
                "bin-dir": {
                    "type": "string",
                    "description": "The location where all binaries are linked, defaults to \"vendor/bin\"."
                },
                "data-dir": {
                    "type": "string",
                    "description": "The location where old phar files are stored, defaults to \"$home\" except on XDG Base Directory compliant unixes."
                },
                "cache-dir": {
                    "type": "string",
                    "description": "The location where all caches are located, defaults to \"~/.composer/cache\" on *nix and \"%LOCALAPPDATA%\\Composer\" on windows."
                },
                "cache-files-dir": {
                    "type": "string",
                    "description": "The location where files (zip downloads) are cached, defaults to \"{$cache-dir}/files\"."
                },
                "cache-repo-dir": {
                    "type": "string",
                    "description": "The location where repo (git/hg repo clones) are cached, defaults to \"{$cache-dir}/repo\"."
                },
                "cache-vcs-dir": {
                    "type": "string",
                    "description": "The location where vcs infos (git clones, github api calls, etc. when reading vcs repos) are cached, defaults to \"{$cache-dir}/vcs\"."
                },
                "cache-ttl": {
                    "type": "integer",
                    "description": "The default cache time-to-live, defaults to 15552000 (6 months)."
                },
                "cache-files-ttl": {
                    "type": "integer",
                    "description": "The cache time-to-live for files, defaults to the value of cache-ttl."
                },
                "cache-files-maxsize": {
                    "type": ["string", "integer"],
                    "description": "The cache max size for the files cache, defaults to \"300MiB\"."
                },
                "cache-read-only": {
                    "type": ["boolean"],
                    "description": "Whether to use the Composer cache in read-only mode."
                },
                "bin-compat": {
                    "enum": ["auto", "full", "proxy", "symlink"],
                    "description": "The compatibility of the binaries, defaults to \"auto\" (automatically guessed), can be \"full\" (compatible with both Windows and Unix-based systems) and \"proxy\" (only bash-style proxy)."
                },
                "discard-changes": {
                    "type": ["string", "boolean"],
                    "description": "The default style of handling dirty updates, defaults to false and can be any of true, false or \"stash\"."
                },
                "autoloader-suffix": {
                    "type": "string",
                    "description": "Optional string to be used as a suffix for the generated Composer autoloader. When null a random one will be generated."
                },
                "optimize-autoloader": {
                    "type": "boolean",
                    "description": "Always optimize when dumping the autoloader."
                },
                "prepend-autoloader": {
                    "type": "boolean",
                    "description": "If false, the composer autoloader will not be prepended to existing autoloaders, defaults to true."
                },
                "classmap-authoritative": {
                    "type": "boolean",
                    "description": "If true, the composer autoloader will not scan the filesystem for classes that are not found in the class map, defaults to false."
                },
                "apcu-autoloader": {
                    "type": "boolean",
                    "description": "If true, the Composer autoloader will check for APCu and use it to cache found/not-found classes when the extension is enabled, defaults to false."
                },
                "github-domains": {
                    "type": "array",
                    "description": "A list of domains to use in github mode. This is used for GitHub Enterprise setups, defaults to [\"github.com\"].",
                    "items": {
                        "type": "string"
                    }
                },
                "github-expose-hostname": {
                    "type": "boolean",
                    "description": "Defaults to true. If set to false, the OAuth tokens created to access the github API will have a date instead of the machine hostname."
                },
                "gitlab-domains": {
                    "type": "array",
                    "description": "A list of domains to use in gitlab mode. This is used for custom GitLab setups, defaults to [\"gitlab.com\"].",
                    "items": {
                        "type": "string"
                    }
                },
                "forgejo-domains": {
                    "type": "array",
                    "description": "A list of domains to use in forgejo mode. This is used for custom Forgejo setups, defaults to [\"codeberg.org\"].",
                    "items": {
                        "type": "string"
                    }
                },
                "bitbucket-oauth": {
                    "type": "object",
                    "description": "An object of domain name => {\"consumer-key\": \"...\", \"consumer-secret\": \"...\"}.",
                    "additionalProperties": {
                        "type": "object",
                        "required": ["consumer-key", "consumer-secret"],
                        "properties": {
                            "consumer-key": {
                                "type": "string",
                                "description": "The consumer-key used for OAuth authentication"
                            },
                            "consumer-secret": {
                                "type": "string",
                                "description": "The consumer-secret used for OAuth authentication"
                            },
                            "access-token": {
                                "type": "string",
                                "description": "The OAuth token retrieved from Bitbucket's API, this is written by Composer and you should not set it nor modify it."
                            },
                            "access-token-expiration": {
                                "type": "integer",
                                "description": "The generated token's expiration timestamp, this is written by Composer and you should not set it nor modify it."
                            }
                        }
                    }
                },
                "use-github-api": {
                    "type": "boolean",
                    "description": "Defaults to true.  If set to false, globally disables the use of the GitHub API for all GitHub repositories and clones the repository as it would for any other repository."
                },
                "archive-format": {
                    "type": "string",
                    "description": "The default archiving format when not provided on cli, defaults to \"tar\"."
                },
                "archive-dir": {
                    "type": "string",
                    "description": "The default archive path when not provided on cli, defaults to \".\"."
                },
                "htaccess-protect": {
                    "type": "boolean",
                    "description": "Defaults to true. If set to false, Composer will not create .htaccess files in the composer home, cache, and data directories."
                },
                "sort-packages": {
                    "type": "boolean",
                    "description": "Defaults to false. If set to true, Composer will sort packages when adding/updating a new dependency."
                },
                "lock": {
                    "type": "boolean",
                    "description": "Defaults to true. If set to false, Composer will not create a composer.lock file."
                },
                "platform-check": {
                    "type": ["boolean", "string"],
                    "enum": ["php-only", true, false],
                    "description": "Defaults to \"php-only\" which checks only the PHP version. Setting to true will also check the presence of required PHP extensions. If set to false, Composer will not create and require a platform_check.php file as part of the autoloader bootstrap."
                },
                "bump-after-update": {
                    "type": ["string", "boolean"],
                    "enum": ["dev", "no-dev", true, false],
                    "description": "Defaults to false and can be any of true, false, \"dev\"` or \"no-dev\"`. If set to true, Composer will run the bump command after running the update command. If set to \"dev\" or \"no-dev\" then only the corresponding dependencies will be bumped."
                },
                "allow-missing-requirements": {
                    "type": ["boolean"],
                    "description": "Defaults to false. If set to true, Composer will allow install when lock file is not up to date with the latest changes in composer.json."
                },
                "update-with-minimal-changes": {
                    "type": ["boolean"],
                    "description": "Defaults to false. If set to true, Composer will only perform absolutely necessary changes to transitive dependencies during update."
                }
            }
        },
        "extra": {
            "type": ["object", "array"],
            "description": "Arbitrary extra data that can be used by plugins, for example, package of type composer-plugin may have a 'class' key defining an installer class name.",
            "additionalProperties": true
        },
        "scripts": {
            "type": ["object"],
            "description": "Script listeners that will be executed before/after some events.",
            "properties": {
                "pre-install-cmd": {
                    "type": ["array", "string"],
                    "description": "Occurs before the install command is executed, contains one or more Class::method callables or shell commands."
                },
                "post-install-cmd": {
                    "type": ["array", "string"],
                    "description": "Occurs after the install command is executed, contains one or more Class::method callables or shell commands."
                },
                "pre-update-cmd": {
                    "type": ["array", "string"],
                    "description": "Occurs before the update command is executed, contains one or more Class::method callables or shell commands."
                },
                "post-update-cmd": {
                    "type": ["array", "string"],
                    "description": "Occurs after the update command is executed, contains one or more Class::method callables or shell commands."
                },
                "pre-status-cmd": {
                    "type": ["array", "string"],
                    "description": "Occurs before the status command is executed, contains one or more Class::method callables or shell commands."
                },
                "post-status-cmd": {
                    "type": ["array", "string"],
                    "description": "Occurs after the status command is executed, contains one or more Class::method callables or shell commands."
                },
                "pre-package-install": {
                    "type": ["array", "string"],
                    "description": "Occurs before a package is installed, contains one or more Class::method callables or shell commands."
                },
                "post-package-install": {
                    "type": ["array", "string"],
                    "description": "Occurs after a package is installed, contains one or more Class::method callables or shell commands."
                },
                "pre-package-update": {
                    "type": ["array", "string"],
                    "description": "Occurs before a package is updated, contains one or more Class::method callables or shell commands."
                },
                "post-package-update": {
                    "type": ["array", "string"],
                    "description": "Occurs after a package is updated, contains one or more Class::method callables or shell commands."
                },
                "pre-package-uninstall": {
                    "type": ["array", "string"],
                    "description": "Occurs before a package has been uninstalled, contains one or more Class::method callables or shell commands."
                },
                "post-package-uninstall": {
                    "type": ["array", "string"],
                    "description": "Occurs after a package has been uninstalled, contains one or more Class::method callables or shell commands."
                },
                "pre-autoload-dump": {
                    "type": ["array", "string"],
                    "description": "Occurs before the autoloader is dumped, contains one or more Class::method callables or shell commands."
                },
                "post-autoload-dump": {
                    "type": ["array", "string"],
                    "description": "Occurs after the autoloader is dumped, contains one or more Class::method callables or shell commands."
                },
                "post-root-package-install": {
                    "type": ["array", "string"],
                    "description": "Occurs after the root-package is installed, contains one or more Class::method callables or shell commands."
                },
                "post-create-project-cmd": {
                    "type": ["array", "string"],
                    "description": "Occurs after the create-project command is executed, contains one or more Class::method callables or shell commands."
                }
            }
        },
        "scripts-descriptions": {
            "type": ["object"],
            "description": "Descriptions for custom commands, shown in console help.",
            "additionalProperties": {
                "type": "string"
            }
        },
        "scripts-aliases": {
            "type": ["object"],
            "description": "Aliases for custom commands.",
            "additionalProperties": {
                "type": "array"
            }
        }
    },
    "definitions": {
        "authors": {
            "type": "array",
            "description": "List of authors that contributed to the package. This is typically the main maintainers, not the full list.",
            "items": {
                "type": "object",
                "additionalProperties": false,
                "required": [ "name"],
                "properties": {
                    "name": {
                        "type": "string",
                        "description": "Full name of the author."
                    },
                    "email": {
                        "type": "string",
                        "description": "Email address of the author.",
                        "format": "email"
                    },
                    "homepage": {
                        "type": "string",
                        "description": "Homepage URL for the author.",
                        "format": "uri"
                    },
                    "role": {
                        "type": "string",
                        "description": "Author's role in the project."
                    }
                }
            }
        },
        "autoload": {
            "type": "object",
            "description": "Description of how the package can be autoloaded.",
            "properties": {
                "psr-0": {
                    "type": "object",
                    "description": "This is an object of namespaces (keys) and the directories they can be found in (values, can be arrays of paths) by the autoloader.",
                    "additionalProperties": {
                        "type": ["string", "array"],
                        "items": {
                            "type": "string"
                        }
                    }
                },
                "psr-4": {
                    "type": "object",
                    "description": "This is an object of namespaces (keys) and the PSR-4 directories they can map to (values, can be arrays of paths) by the autoloader.",
                    "additionalProperties": {
                        "type": ["string", "array"],
                        "items": {
                            "type": "string"
                        }
                    }
                },
                "classmap": {
                    "type": "array",
                    "description": "This is an array of paths that contain classes to be included in the class-map generation process."
                },
                "files": {
                    "type": "array",
                    "description": "This is an array of files that are always required on every request."
                },
                "exclude-from-classmap": {
                    "type": "array",
                    "description": "This is an array of patterns to exclude from autoload classmap generation. (e.g. \"exclude-from-classmap\": [\"/test/\", \"/tests/\", \"/Tests/\"]"
                }
            }
        },
        "repository": {
            "type": "object",
            "anyOf": [
                { "$ref": "#/definitions/composer-repository" },
                { "$ref": "#/definitions/vcs-repository" },
                { "$ref": "#/definitions/path-repository" },
                { "$ref": "#/definitions/artifact-repository" },
                { "$ref": "#/definitions/pear-repository" },
                { "$ref": "#/definitions/package-repository" }
            ]
        },
        "anonymous-repository": {
            "allOf": [
                { "$ref": "#/definitions/repository" },
                { "not": { "required": ["name"] } }
            ]
        },
        "composer-repository": {
            "type": "object",
            "required": ["type", "url"],
            "properties": {
                "name": { "type":  "string" },
                "type": { "type": "string", "enum": ["composer"] },
                "url": { "type": "string" },
                "canonical": { "type": "boolean" },
                "only": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                },
                "exclude": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                },
                "options": {
                    "type": "object",
                    "additionalProperties": true
                },
                "allow_ssl_downgrade": { "type": "boolean" },
                "force-lazy-providers": { "type": "boolean" }
            }
        },
        "vcs-repository": {
            "type": "object",
            "required": ["type", "url"],
            "properties": {
                "name": { "type":  "string" },
                "type": { "type": "string", "enum": ["vcs", "github", "git", "gitlab", "bitbucket", "git-bitbucket", "hg", "fossil", "perforce", "svn", "forgejo"] },
                "url": { "type": "string" },
                "canonical": { "type": "boolean" },
                "only": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                },
                "exclude": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                },
                "no-api": { "type": "boolean" },
                "secure-http": { "type": "boolean" },
                "svn-cache-credentials": { "type": "boolean" },
                "trunk-path": { "type": ["string", "boolean"] },
                "branches-path": { "type": ["string", "boolean"] },
                "tags-path": { "type": ["string", "boolean"] },
                "package-path": { "type": "string" },
                "depot": { "type": "string" },
                "branch": { "type": "string" },
                "unique_perforce_client_name": { "type": "string" },
                "p4user": { "type": "string" },
                "p4password": { "type": "string" }
            }
        },
        "path-repository": {
            "type": "object",
            "required": ["type", "url"],
            "properties": {
                "name": { "type":  "string" },
                "type": { "type": "string", "enum": ["path"] },
                "url": { "type": "string" },
                "canonical": { "type": "boolean" },
                "only": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                },
                "exclude": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                },
                "options": {
                    "type": "object",
                    "properties": {
                        "reference": { "type": ["string"], "enum": ["none", "config", "auto"] },
                        "symlink": { "type": ["boolean", "null"] },
                        "relative": { "type": ["boolean"] },
                        "versions": { "type": "object", "additionalProperties": { "type": "string" } }
                    },
                    "additionalProperties": true
                }
            }
        },
        "artifact-repository": {
            "type": "object",
            "required": ["type", "url"],
            "properties": {
                "name": { "type":  "string" },
                "type": { "type": "string", "enum": ["artifact"] },
                "url": { "type": "string" },
                "canonical": { "type": "boolean" },
                "only": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                },
                "exclude": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                }
            }
        },
        "pear-repository": {
            "type": "object",
            "required": ["type", "url"],
            "properties": {
                "name": { "type":  "string" },
                "type": { "type": "string", "enum": ["pear"] },
                "url": { "type": "string" },
                "canonical": { "type": "boolean" },
                "only": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                },
                "exclude": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                },
                "vendor-alias": { "type": "string" }
            }
        },
        "package-repository": {
            "type": "object",
            "required": ["type", "package"],
            "properties": {
                "name": { "type":  "string" },
                "type": { "type": "string", "enum": ["package"] },
                "canonical": { "type": "boolean" },
                "only": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                },
                "exclude": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                },
                "package": {
                    "oneOf": [
                        { "$ref": "#/definitions/inline-package" },
                        {
                            "type": "array",
                            "items": { "$ref": "#/definitions/inline-package" }
                        }
                    ]
                }
            }
        },
        "inline-package": {
            "type": "object",
            "required": ["name", "version"],
            "properties": {
                "name": {
                    "type": "string",
                    "description": "Package name, including 'vendor-name/' prefix."
                },
                "type": {
                    "type": "string"
                },
                "target-dir": {
                    "description": "DEPRECATED: Forces the package to be installed into the given subdirectory path. This is used for autoloading PSR-0 packages that do not contain their full path. Use forward slashes for cross-platform compatibility.",
                    "type": "string"
                },
                "description": {
                    "type": "string"
                },
                "keywords": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                },
                "homepage": {
                    "type": "string",
                    "format": "uri"
                },
                "version": {
                    "type": "string"
                },
                "time": {
                    "type": "string"
                },
                "license": {
                    "type": [
                        "string",
                        "array"
                    ]
                },
                "authors": {
                    "$ref": "#/definitions/authors"
                },
                "require": {
                    "type": "object",
                    "additionalProperties": {
                        "type": "string"
                    }
                },
                "replace": {
                    "type": "object",
                    "additionalProperties": {
                        "type": "string"
                    }
                },
                "conflict": {
                    "type": "object",
                    "additionalProperties": {
                        "type": "string"
                    }
                },
                "provide": {
                    "type": "object",
                    "additionalProperties": {
                        "type": "string"
                    }
                },
                "require-dev": {
                    "type": "object",
                    "additionalProperties": {
                        "type": "string"
                    }
                },
                "suggest": {
                    "type": "object",
                    "additionalProperties": {
                        "type": "string"
                    }
                },
                "extra": {
                    "type": ["object", "array"],
                    "additionalProperties": true
                },
                "autoload": {
                    "$ref": "#/definitions/autoload"
                },
                "archive": {
                    "type": ["object"],
                    "properties": {
                        "exclude": {
                            "type": "array"
                        }
                    }
                },
                "bin": {
                    "type": ["string", "array"],
                    "description": "A set of files, or a single file, that should be treated as binaries and symlinked into bin-dir (from config).",
                    "items": {
                        "type": "string"
                    }
                },
                "include-path": {
                    "type": ["array"],
                    "description": "DEPRECATED: A list of directories which should get added to PHP's include path. This is only present to support legacy projects, and all new code should preferably use autoloading.",
                    "items": {
                        "type": "string"
                    }
                },
                "source": {
                    "$ref": "#/definitions/source"
                },
                "dist": {
                    "$ref": "#/definitions/dist"
                }
            },
            "additionalProperties": true
        },
        "source": {
            "type": "object",
            "required": ["type", "url", "reference"],
            "properties": {
                "type": {
                    "type": "string"
                },
                "url": {
                    "type": "string"
                },
                "reference": {
                    "type": "string"
                },
                "mirrors": {
                    "type": "array"
                }
            }
        },
        "dist": {
            "type": "object",
            "required": ["type", "url"],
            "properties": {
                "type": {
                    "type": "string"
                },
                "url": {
                    "type": "string"
                },
                "reference": {
                    "type": "string"
                },
                "shasum": {
                    "type": "string"
                },
                "mirrors": {
                    "type": "array"
                }
            }
        }
    }
}
{
    "$schema": "https://json-schema.org/draft-04/schema#",
    "title": "Composer Lock File",
    "type": "object",
    "required": [ "content-hash", "packages", "packages-dev" ],
    "additionalProperties": true,
    "properties": {
        "_readme": {
            "type": "array",
            "items": {
                "type": "string"
            },
            "description": "Informational text for humans reading the file"
        },
        "content-hash": {
            "type": "string",
            "description": "Hash of all relevant properties of the composer.json that was used to create this lock file."
        },
        "packages": {
            "type": "array",
            "description": "An array of packages that are required.",
            "items": {
                "$ref": "./composer-schema.json",
                "required": ["name", "version"]
            }
        },
        "packages-dev": {
            "type": "array",
            "description": "An array of packages that are required in require-dev.",
            "items": {
                "$ref": "./composer-schema.json"
            }
        },
        "aliases": {
            "type": "array",
            "description": "Inline aliases defined in the root package.",
            "items": {
                "type": "object",
                "required": [ "package", "version", "alias", "alias_normalized" ],
                "properties": {
                    "package": {
                        "type": "string"
                    },
                    "version": {
                        "type": "string"
                    },
                    "alias": {
                        "type": "string"
                    },
                    "alias_normalized": {
                        "type": "string"
                    }
                }
            }
        },
        "minimum-stability": {
            "type": "string",
            "description": "The minimum-stability used to generate this lock file."
        },
        "stability-flags": {
            "type": "object",
            "description": "Root package stability flags changing the minimum-stability for specific packages.",
            "additionalProperties": {
                "type": "integer"
            }
        },
        "prefer-stable": {
            "type": "boolean",
            "description": "Whether the --prefer-stable flag was used when building this lock file."
        },
        "prefer-lowest": {
            "type": "boolean",
            "description": "Whether the --prefer-lowest flag was used when building this lock file."
        },
        "platform": {
            "type": "object",
            "description": "Platform requirements of the root package.",
            "additionalProperties": {
                "type": "string"
            }
        },
        "platform-dev": {
            "type": "object",
            "description": "Platform dev-requirements of the root package.",
            "additionalProperties": {
                "type": "string"
            }
        },
        "platform-overrides": {
            "type": "object",
            "description": "Platform config overrides of the root package.",
            "additionalProperties": {
                "type": "string"
            }
        },
        "plugin-api-version": {
            "type": "string",
            "description": "The composer-plugin-api version that was used to generate this lock file."
        }
    }
}
Copyright (c) Nils Adermann, Jordi Boggiano

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.
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use Composer\Autoload\ClassLoader;

function includeIfExists(string $file): ?ClassLoader
{
    return file_exists($file) ? include $file : null;
}

if ((!$loader = includeIfExists(__DIR__.'/../vendor/autoload.php')) && (!$loader = includeIfExists(__DIR__.'/../../../autoload.php'))) {
    echo 'You must set up the project dependencies using `composer install`'.PHP_EOL.
        'See https://getcomposer.org/download/ for instructions on installing Composer'.PHP_EOL;
    exit(1);
}

return $loader;
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

/**
 * Temporarily suppress PHP error reporting, usually warnings and below.
 *
 * @author Niels Keurentjes <niels.keurentjes@omines.com>
 */
class Silencer
{
    /**
     * @var int[] Unpop stack
     */
    private static $stack = [];

    /**
     * Suppresses given mask or errors.
     *
     * @param  int|null $mask Error levels to suppress, default value NULL indicates all warnings and below.
     * @return int      The old error reporting level.
     */
    public static function suppress(?int $mask = null): int
    {
        if (!isset($mask)) {
            $mask = E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE | E_DEPRECATED | E_USER_DEPRECATED;
        }
        $old = error_reporting();
        self::$stack[] = $old;
        error_reporting($old & ~$mask);

        return $old;
    }

    /**
     * Restores a single state.
     */
    public static function restore(): void
    {
        if (!empty(self::$stack)) {
            error_reporting(array_pop(self::$stack));
        }
    }

    /**
     * Calls a specified function while silencing warnings and below.
     *
     * @param  callable   $callable Function to execute.
     * @param  mixed      $parameters Function to execute.
     * @throws \Exception Any exceptions from the callback are rethrown.
     * @return mixed      Return value of the callback.
     */
    public static function call(callable $callable, ...$parameters)
    {
        try {
            self::suppress();
            $result = $callable(...$parameters);
            self::restore();

            return $result;
        } catch (\Exception $e) {
            // Use a finally block for this when requirements are raised to PHP 5.5
            self::restore();
            throw $e;
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

/**
 * @author Wissem Riahi <wissemr@gmail.com>
 */
class Tar
{
    public static function getComposerJson(string $pathToArchive): ?string
    {
        $phar = new \PharData($pathToArchive);

        if (!$phar->valid()) {
            return null;
        }

        return self::extractComposerJsonFromFolder($phar);
    }

    /**
     * @throws \RuntimeException
     */
    private static function extractComposerJsonFromFolder(\PharData $phar): string
    {
        if (isset($phar['composer.json'])) {
            return $phar['composer.json']->getContent();
        }

        $topLevelPaths = [];
        foreach ($phar as $folderFile) {
            $name = $folderFile->getBasename();

            if ($folderFile->isDir()) {
                $topLevelPaths[$name] = true;
                if (\count($topLevelPaths) > 1) {
                    throw new \RuntimeException('Archive has more than one top level directories, and no composer.json was found on the top level, so it\'s an invalid archive. Top level paths found were: '.implode(',', array_keys($topLevelPaths)));
                }
            }
        }

        $composerJsonPath = key($topLevelPaths).'/composer.json';
        if (\count($topLevelPaths) > 0 && isset($phar[$composerJsonPath])) {
            return $phar[$composerJsonPath]->getContent();
        }

        throw new \RuntimeException('No composer.json found either at the top level or within the topmost directory');
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

/**
 * @author Andreas Schempp <andreas.schempp@terminal42.ch>
 */
class Zip
{
    /**
     * Gets content of the root composer.json inside a ZIP archive.
     */
    public static function getComposerJson(string $pathToZip): ?string
    {
        if (!extension_loaded('zip')) {
            throw new \RuntimeException('The Zip Util requires PHP\'s zip extension');
        }

        $zip = new \ZipArchive();
        if ($zip->open($pathToZip) !== true) {
            return null;
        }

        if (0 === $zip->numFiles) {
            $zip->close();

            return null;
        }

        $foundFileIndex = self::locateFile($zip, 'composer.json');

        $content = null;
        $configurationFileName = $zip->getNameIndex($foundFileIndex);
        $stream = $zip->getStream($configurationFileName);

        if (false !== $stream) {
            $content = stream_get_contents($stream);
        }

        $zip->close();

        return $content;
    }

    /**
     * Find a file by name, returning the one that has the shortest path.
     *
     * @throws \RuntimeException
     */
    private static function locateFile(\ZipArchive $zip, string $filename): int
    {
        // return root composer.json if it is there and is a file
        if (false !== ($index = $zip->locateName($filename)) && $zip->getFromIndex($index) !== false) {
            return $index;
        }

        $topLevelPaths = [];
        for ($i = 0; $i < $zip->numFiles; $i++) {
            $name = $zip->getNameIndex($i);
            $dirname = dirname($name);

            // ignore OSX specific resource fork folder
            if (strpos($name, '__MACOSX') !== false) {
                continue;
            }

            // handle archives with proper TOC
            if ($dirname === '.') {
                $topLevelPaths[$name] = true;
                if (\count($topLevelPaths) > 1) {
                    throw new \RuntimeException('Archive has more than one top level directories, and no composer.json was found on the top level, so it\'s an invalid archive. Top level paths found were: '.implode(',', array_keys($topLevelPaths)));
                }
                continue;
            }

            // handle archives which do not have a TOC record for the directory itself
            if (false === strpos($dirname, '\\') && false === strpos($dirname, '/')) {
                $topLevelPaths[$dirname.'/'] = true;
                if (\count($topLevelPaths) > 1) {
                    throw new \RuntimeException('Archive has more than one top level directories, and no composer.json was found on the top level, so it\'s an invalid archive. Top level paths found were: '.implode(',', array_keys($topLevelPaths)));
                }
            }
        }

        if ($topLevelPaths && false !== ($index = $zip->locateName(key($topLevelPaths).$filename)) && $zip->getFromIndex($index) !== false) {
            return $index;
        }

        throw new \RuntimeException('No composer.json found either at the top level or within the topmost directory');
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Composer;
use Composer\CaBundle\CaBundle;
use Composer\Downloader\TransportException;
use Composer\Repository\PlatformRepository;
use Composer\Util\Http\ProxyManager;
use Psr\Log\LoggerInterface;

/**
 * Allows the creation of a basic context supporting http proxy
 *
 * @author Jordan Alliot <jordan.alliot@gmail.com>
 * @author Markus Tacker <m@coderbyheart.de>
 */
final class StreamContextFactory
{
    /**
     * Creates a context supporting HTTP proxies
     *
     * @param non-empty-string $url URL the context is to be used for
     * @phpstan-param array{http?: array{follow_location?: int, max_redirects?: int, header?: string|array<string>}} $defaultOptions
     * @param  mixed[]           $defaultOptions Options to merge with the default
     * @param  mixed[]           $defaultParams  Parameters to specify on the context
     * @throws \RuntimeException if https proxy required and OpenSSL uninstalled
     * @return resource          Default context
     */
    public static function getContext(string $url, array $defaultOptions = [], array $defaultParams = [])
    {
        $options = ['http' => [
            // specify defaults again to try and work better with curlwrappers enabled
            'follow_location' => 1,
            'max_redirects' => 20,
        ]];

        $options = array_replace_recursive($options, self::initOptions($url, $defaultOptions));
        unset($defaultOptions['http']['header']);
        $options = array_replace_recursive($options, $defaultOptions);

        if (isset($options['http']['header'])) {
            $options['http']['header'] = self::fixHttpHeaderField($options['http']['header']);
        }

        return stream_context_create($options, $defaultParams);
    }

    /**
     * @param non-empty-string $url
     * @param mixed[] $options
     * @param bool    $forCurl When true, will not add proxy values as these are handled separately
     * @phpstan-return array{http: array{header: string[], proxy?: string, request_fulluri: bool}, ssl?: mixed[]}
     * @return array formatted as a stream context array
     */
    public static function initOptions(string $url, array $options, bool $forCurl = false): array
    {
        // Make sure the headers are in an array form
        if (!isset($options['http']['header'])) {
            $options['http']['header'] = [];
        }
        if (is_string($options['http']['header'])) {
            $options['http']['header'] = explode("\r\n", $options['http']['header']);
        }

        // Add stream proxy options if there is a proxy
        if (!$forCurl) {
            $proxy = ProxyManager::getInstance()->getProxyForRequest($url);
            $proxyOptions = $proxy->getContextOptions();
            if ($proxyOptions !== null) {
                $isHttpsRequest = 0 === strpos($url, 'https://');

                if ($proxy->isSecure()) {
                    if (!extension_loaded('openssl')) {
                        throw new TransportException('You must enable the openssl extension to use a secure proxy.');
                    }
                    if ($isHttpsRequest) {
                        throw new TransportException('You must enable the curl extension to make https requests through a secure proxy.');
                    }
                } elseif ($isHttpsRequest && !extension_loaded('openssl')) {
                    throw new TransportException('You must enable the openssl extension to make https requests through a proxy.');
                }

                // Header will be a Proxy-Authorization string or not set
                if (isset($proxyOptions['http']['header'])) {
                    $options['http']['header'][] = $proxyOptions['http']['header'];
                    unset($proxyOptions['http']['header']);
                }
                $options = array_replace_recursive($options, $proxyOptions);
            }
        }

        if (defined('HHVM_VERSION')) {
            $phpVersion = 'HHVM ' . HHVM_VERSION;
        } else {
            $phpVersion = 'PHP ' . PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION;
        }

        if ($forCurl) {
            $curl = curl_version();
            $httpVersion = 'cURL '.$curl['version'];
        } else {
            $httpVersion = 'streams';
        }

        if (!isset($options['http']['header']) || false === stripos(implode('', $options['http']['header']), 'user-agent')) {
            $platformPhpVersion = PlatformRepository::getPlatformPhpVersion();
            $options['http']['header'][] = sprintf(
                'User-Agent: Composer/%s (%s; %s; %s; %s%s%s)',
                Composer::getVersion(),
                function_exists('php_uname') ? php_uname('s') : 'Unknown',
                function_exists('php_uname') ? php_uname('r') : 'Unknown',
                $phpVersion,
                $httpVersion,
                $platformPhpVersion ? '; Platform-PHP '.$platformPhpVersion : '',
                Platform::getEnv('CI') ? '; CI' : ''
            );
        }

        return $options;
    }

    /**
     * @param mixed[] $options
     *
     * @return mixed[]
     */
    public static function getTlsDefaults(array $options, ?LoggerInterface $logger = null): array
    {
        $ciphers = implode(':', [
            'ECDHE-RSA-AES128-GCM-SHA256',
            'ECDHE-ECDSA-AES128-GCM-SHA256',
            'ECDHE-RSA-AES256-GCM-SHA384',
            'ECDHE-ECDSA-AES256-GCM-SHA384',
            'DHE-RSA-AES128-GCM-SHA256',
            'DHE-DSS-AES128-GCM-SHA256',
            'kEDH+AESGCM',
            'ECDHE-RSA-AES128-SHA256',
            'ECDHE-ECDSA-AES128-SHA256',
            'ECDHE-RSA-AES128-SHA',
            'ECDHE-ECDSA-AES128-SHA',
            'ECDHE-RSA-AES256-SHA384',
            'ECDHE-ECDSA-AES256-SHA384',
            'ECDHE-RSA-AES256-SHA',
            'ECDHE-ECDSA-AES256-SHA',
            'DHE-RSA-AES128-SHA256',
            'DHE-RSA-AES128-SHA',
            'DHE-DSS-AES128-SHA256',
            'DHE-RSA-AES256-SHA256',
            'DHE-DSS-AES256-SHA',
            'DHE-RSA-AES256-SHA',
            'AES128-GCM-SHA256',
            'AES256-GCM-SHA384',
            'AES128-SHA256',
            'AES256-SHA256',
            'AES128-SHA',
            'AES256-SHA',
            'AES',
            'CAMELLIA',
            '!aNULL',
            '!eNULL',
            '!EXPORT',
            '!DES',
            '!3DES',
            '!RC4',
            '!MD5',
            '!PSK',
            '!aECDH',
            '!EDH-DSS-DES-CBC3-SHA',
            '!EDH-RSA-DES-CBC3-SHA',
            '!KRB5-DES-CBC3-SHA',
        ]);

        /**
         * CN_match and SNI_server_name are only known once a URL is passed.
         * They will be set in the getOptionsForUrl() method which receives a URL.
         *
         * cafile or capath can be overridden by passing in those options to constructor.
         */
        $defaults = [
            'ssl' => [
                'ciphers' => $ciphers,
                'verify_peer' => true,
                'verify_depth' => 7,
                'SNI_enabled' => true,
                'capture_peer_cert' => true,
            ],
        ];

        if (isset($options['ssl'])) {
            $defaults['ssl'] = array_replace_recursive($defaults['ssl'], $options['ssl']);
        }

        /**
         * Attempt to find a local cafile or throw an exception if none pre-set
         * The user may go download one if this occurs.
         */
        if (!isset($defaults['ssl']['cafile']) && !isset($defaults['ssl']['capath'])) {
            $result = CaBundle::getSystemCaRootBundlePath($logger);

            if (is_dir($result)) {
                $defaults['ssl']['capath'] = $result;
            } else {
                $defaults['ssl']['cafile'] = $result;
            }
        }

        if (isset($defaults['ssl']['cafile']) && (!Filesystem::isReadable($defaults['ssl']['cafile']) || !CaBundle::validateCaFile($defaults['ssl']['cafile'], $logger))) {
            throw new TransportException('The configured cafile was not valid or could not be read.');
        }

        if (isset($defaults['ssl']['capath']) && (!is_dir($defaults['ssl']['capath']) || !Filesystem::isReadable($defaults['ssl']['capath']))) {
            throw new TransportException('The configured capath was not valid or could not be read.');
        }

        /**
         * Disable TLS compression to prevent CRIME attacks where supported.
         */
        $defaults['ssl']['disable_compression'] = true;

        return $defaults;
    }

    /**
     * A bug in PHP prevents the headers from correctly being sent when a content-type header is present and
     * NOT at the end of the array
     *
     * This method fixes the array by moving the content-type header to the end
     *
     * @link https://bugs.php.net/bug.php?id=61548
     * @param  string|string[] $header
     * @return string[]
     */
    private static function fixHttpHeaderField($header): array
    {
        if (!is_array($header)) {
            $header = explode("\r\n", $header);
        }
        uasort($header, static function ($el): int {
            return stripos($el, 'content-type') === 0 ? 1 : -1;
        });

        return $header;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Package\PackageInterface;
use Composer\Package\RootPackageInterface;

class PackageSorter
{
    /**
     * Returns the most recent version of a set of packages
     *
     * This is ideally the default branch version, or failing that it will return the package with the highest version
     *
     * @template T of PackageInterface
     * @param array<T> $packages
     * @return ($packages is non-empty-array<T> ? T : T|null)
     */
    public static function getMostCurrentVersion(array $packages): ?PackageInterface
    {
        if (count($packages) === 0) {
            return null;
        }

        $highest = reset($packages);
        foreach ($packages as $candidate) {
            if ($candidate->isDefaultBranch()) {
                return $candidate;
            }

            if (version_compare($highest->getVersion(), $candidate->getVersion(), '<')) {
                $highest = $candidate;
            }
        }

        return $highest;
    }

    /**
     * Sorts packages by name
     *
     * @template T of PackageInterface
     * @param array<T> $packages
     * @return array<T>
     */
    public static function sortPackagesAlphabetically(array $packages): array
    {
        usort($packages, static function (PackageInterface $a, PackageInterface $b) {
            return $a->getName() <=> $b->getName();
        });

        return $packages;
    }

    /**
     * Sorts packages by dependency weight
     *
     * Packages of equal weight are sorted alphabetically
     *
     * @param  PackageInterface[] $packages
     * @param  array<string, int> $weights Pre-set weights for some packages to give them more (negative number) or less (positive) weight offsets
     * @return PackageInterface[] sorted array
     */
    public static function sortPackages(array $packages, array $weights = []): array
    {
        $usageList = [];

        foreach ($packages as $package) {
            $links = $package->getRequires();
            if ($package instanceof RootPackageInterface) {
                $links = array_merge($links, $package->getDevRequires());
            }
            foreach ($links as $link) {
                $target = $link->getTarget();
                $usageList[$target][] = $package->getName();
            }
        }
        $computing = [];
        $computed = [];
        $computeImportance = static function ($name) use (&$computeImportance, &$computing, &$computed, $usageList, $weights) {
            // reusing computed importance
            if (isset($computed[$name])) {
                return $computed[$name];
            }

            // canceling circular dependency
            if (isset($computing[$name])) {
                return 0;
            }

            $computing[$name] = true;
            $weight = $weights[$name] ?? 0;

            if (isset($usageList[$name])) {
                foreach ($usageList[$name] as $user) {
                    $weight -= 1 - $computeImportance($user);
                }
            }

            unset($computing[$name]);
            $computed[$name] = $weight;

            return $weight;
        };

        $weightedPackages = [];

        foreach ($packages as $index => $package) {
            $name = $package->getName();
            $weight = $computeImportance($name);
            $weightedPackages[] = ['name' => $name, 'weight' => $weight, 'index' => $index];
        }

        usort($weightedPackages, static function (array $a, array $b): int {
            if ($a['weight'] !== $b['weight']) {
                return $a['weight'] - $b['weight'];
            }

            return strnatcasecmp($a['name'], $b['name']);
        });

        $sortedPackages = [];

        foreach ($weightedPackages as $pkg) {
            $sortedPackages[] = $packages[$pkg['index']];
        }

        return $sortedPackages;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Pcre\Preg;
use stdClass;

/**
 * Tests URLs against NO_PROXY patterns
 */
class NoProxyPattern
{
    /**
     * @var string[]
     */
    protected $hostNames = [];

    /**
     * @var (null|object)[]
     */
    protected $rules = [];

    /**
     * @var bool
     */
    protected $noproxy;

    /**
     * @param string $pattern NO_PROXY pattern
     */
    public function __construct(string $pattern)
    {
        $this->hostNames = Preg::split('{[\s,]+}', $pattern, -1, PREG_SPLIT_NO_EMPTY);
        $this->noproxy = empty($this->hostNames) || '*' === $this->hostNames[0];
    }

    /**
     * Returns true if a URL matches the NO_PROXY pattern
     */
    public function test(string $url): bool
    {
        if ($this->noproxy) {
            return true;
        }

        if (!$urlData = $this->getUrlData($url)) {
            return false;
        }

        foreach ($this->hostNames as $index => $hostName) {
            if ($this->match($index, $hostName, $urlData)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Returns false is the url cannot be parsed, otherwise a data object
     *
     * @return bool|stdClass
     */
    protected function getUrlData(string $url)
    {
        if (!$host = parse_url($url, PHP_URL_HOST)) {
            return false;
        }

        $port = parse_url($url, PHP_URL_PORT);

        if (empty($port)) {
            switch (parse_url($url, PHP_URL_SCHEME)) {
                case 'http':
                    $port = 80;
                    break;
                case 'https':
                    $port = 443;
                    break;
            }
        }

        $hostName = $host . ($port ? ':' . $port : '');
        [$host, $port, $err] = $this->splitHostPort($hostName);

        if ($err || !$this->ipCheckData($host, $ipdata)) {
            return false;
        }

        return $this->makeData($host, $port, $ipdata);
    }

    /**
     * Returns true if the url is matched by a rule
     */
    protected function match(int $index, string $hostName, stdClass $url): bool
    {
        if (!$rule = $this->getRule($index, $hostName)) {
            // Data must have been misformatted
            return false;
        }

        if ($rule->ipdata) {
            // Match ipdata first
            if (!$url->ipdata) {
                return false;
            }

            if ($rule->ipdata->netmask) {
                return $this->matchRange($rule->ipdata, $url->ipdata);
            }

            $match = $rule->ipdata->ip === $url->ipdata->ip;
        } else {
            // Match host and port
            $haystack = substr($url->name, -strlen($rule->name));
            $match = stripos($haystack, $rule->name) === 0;
        }

        if ($match && $rule->port) {
            $match = $rule->port === $url->port;
        }

        return $match;
    }

    /**
     * Returns true if the target ip is in the network range
     */
    protected function matchRange(stdClass $network, stdClass $target): bool
    {
        $net = unpack('C*', $network->ip);
        $mask = unpack('C*', $network->netmask);
        $ip = unpack('C*', $target->ip);
        if (false === $net) {
            throw new \RuntimeException('Could not parse network IP '.$network->ip);
        }
        if (false === $mask) {
            throw new \RuntimeException('Could not parse netmask '.$network->netmask);
        }
        if (false === $ip) {
            throw new \RuntimeException('Could not parse target IP '.$target->ip);
        }

        for ($i = 1; $i < 17; ++$i) {
            if (($net[$i] & $mask[$i]) !== ($ip[$i] & $mask[$i])) {
                return false;
            }
        }

        return true;
    }

    /**
     * Finds or creates rule data for a hostname
     *
     * @return null|stdClass Null if the hostname is invalid
     */
    private function getRule(int $index, string $hostName): ?stdClass
    {
        if (array_key_exists($index, $this->rules)) {
            return $this->rules[$index];
        }

        $this->rules[$index] = null;
        [$host, $port, $err] = $this->splitHostPort($hostName);

        if ($err || !$this->ipCheckData($host, $ipdata, true)) {
            return null;
        }

        $this->rules[$index] = $this->makeData($host, $port, $ipdata);

        return $this->rules[$index];
    }

    /**
     * Creates an object containing IP data if the host is an IP address
     *
     * @param null|stdClass $ipdata      Set by method if IP address found
     * @param bool          $allowPrefix Whether a CIDR prefix-length is expected
     *
     * @return bool False if the host contains invalid data
     */
    private function ipCheckData(string $host, ?stdClass &$ipdata, bool $allowPrefix = false): bool
    {
        $ipdata = null;
        $netmask = null;
        $prefix = null;
        $modified = false;

        // Check for a CIDR prefix-length
        if (strpos($host, '/') !== false) {
            [$host, $prefix] = explode('/', $host);

            if (!$allowPrefix || !$this->validateInt($prefix, 0, 128)) {
                return false;
            }
            $prefix = (int) $prefix;
            $modified = true;
        }

        // See if this is an ip address
        if (!filter_var($host, FILTER_VALIDATE_IP)) {
            return !$modified;
        }

        [$ip, $size] = $this->ipGetAddr($host);

        if ($prefix !== null) {
            // Check for a valid prefix
            if ($prefix > $size * 8) {
                return false;
            }

            [$ip, $netmask] = $this->ipGetNetwork($ip, $size, $prefix);
        }

        $ipdata = $this->makeIpData($ip, $size, $netmask);

        return true;
    }

    /**
     * Returns an array of the IP in_addr and its byte size
     *
     * IPv4 addresses are always mapped to IPv6, which simplifies handling
     * and comparison.
     *
     * @return mixed[] in_addr, size
     */
    private function ipGetAddr(string $host): array
    {
        $ip = inet_pton($host);
        $size = strlen($ip);
        $mapped = $this->ipMapTo6($ip, $size);

        return [$mapped, $size];
    }

    /**
     * Returns the binary network mask mapped to IPv6
     *
     * @param int $prefix CIDR prefix-length
     * @param int $size   Byte size of in_addr
     */
    private function ipGetMask(int $prefix, int $size): string
    {
        $mask = '';

        if ($ones = floor($prefix / 8)) {
            $mask = str_repeat(chr(255), (int) $ones);
        }

        if ($remainder = $prefix % 8) {
            $mask .= chr(0xff ^ (0xff >> $remainder));
        }

        $mask = str_pad($mask, $size, chr(0));

        return $this->ipMapTo6($mask, $size);
    }

    /**
     * Calculates and returns the network and mask
     *
     * @param string $rangeIp IP in_addr
     * @param int    $size    Byte size of in_addr
     * @param int    $prefix  CIDR prefix-length
     *
     * @return string[] network in_addr, binary mask
     */
    private function ipGetNetwork(string $rangeIp, int $size, int $prefix): array
    {
        $netmask = $this->ipGetMask($prefix, $size);

        // Get the network from the address and mask
        $mask = unpack('C*', $netmask);
        $ip = unpack('C*', $rangeIp);
        $net = '';
        if (false === $mask) {
            throw new \RuntimeException('Could not parse netmask '.$netmask);
        }
        if (false === $ip) {
            throw new \RuntimeException('Could not parse range IP '.$rangeIp);
        }

        for ($i = 1; $i < 17; ++$i) {
            $net .= chr($ip[$i] & $mask[$i]);
        }

        return [$net, $netmask];
    }

    /**
     * Maps an IPv4 address to IPv6
     *
     * @param string $binary in_addr
     * @param int    $size   Byte size of in_addr
     *
     * @return string Mapped or existing in_addr
     */
    private function ipMapTo6(string $binary, int $size): string
    {
        if ($size === 4) {
            $prefix = str_repeat(chr(0), 10) . str_repeat(chr(255), 2);
            $binary = $prefix . $binary;
        }

        return $binary;
    }

    /**
     * Creates a rule data object
     */
    private function makeData(string $host, int $port, ?stdClass $ipdata): stdClass
    {
        return (object) [
            'host' => $host,
            'name' => '.' . ltrim($host, '.'),
            'port' => $port,
            'ipdata' => $ipdata,
        ];
    }

    /**
     * Creates an ip data object
     *
     * @param string      $ip      in_addr
     * @param int         $size    Byte size of in_addr
     * @param null|string $netmask Network mask
     */
    private function makeIpData(string $ip, int $size, ?string $netmask): stdClass
    {
        return (object) [
            'ip' => $ip,
            'size' => $size,
            'netmask' => $netmask,
        ];
    }

    /**
     * Splits the hostname into host and port components
     *
     * @return mixed[] host, port, if there was error
     */
    private function splitHostPort(string $hostName): array
    {
        // host, port, err
        $error = ['', '', true];
        $port = 0;
        $ip6 = '';

        // Check for square-bracket notation
        if ($hostName[0] === '[') {
            $index = strpos($hostName, ']');

            // The smallest ip6 address is ::
            if (false === $index || $index < 3) {
                return $error;
            }

            $ip6 = substr($hostName, 1, $index - 1);
            $hostName = substr($hostName, $index + 1);

            if (strpbrk($hostName, '[]') !== false || substr_count($hostName, ':') > 1) {
                return $error;
            }
        }

        if (substr_count($hostName, ':') === 1) {
            $index = strpos($hostName, ':');
            $port = substr($hostName, $index + 1);
            $hostName = substr($hostName, 0, $index);

            if (!$this->validateInt($port, 1, 65535)) {
                return $error;
            }

            $port = (int) $port;
        }

        $host = $ip6 . $hostName;

        return [$host, $port, false];
    }

    /**
     * Wrapper around filter_var FILTER_VALIDATE_INT
     */
    private function validateInt(string $int, int $min, int $max): bool
    {
        $options = [
            'options' => [
                'min_range' => $min,
                'max_range' => $max,
            ],
        ];

        return false !== filter_var($int, FILTER_VALIDATE_INT, $options);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Pcre\Preg;

/**
 * Composer mirror utilities
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ComposerMirror
{
    /**
     * @param non-empty-string $mirrorUrl
     * @return non-empty-string
     */
    public static function processUrl(string $mirrorUrl, string $packageName, string $version, ?string $reference, ?string $type, ?string $prettyVersion = null): string
    {
        if ($reference) {
            $reference = Preg::isMatch('{^([a-f0-9]*|%reference%)$}', $reference) ? $reference : hash('md5', $reference);
        }
        $version = strpos($version, '/') === false ? $version : hash('md5', $version);

        $from = ['%package%', '%version%', '%reference%', '%type%'];
        $to = [$packageName, $version, $reference, $type];
        if (null !== $prettyVersion) {
            $from[] = '%prettyVersion%';
            $to[] = $prettyVersion;
        }

        $url = str_replace($from, $to, $mirrorUrl);
        assert($url !== '');

        return $url;
    }

    /**
     * @param non-empty-string $mirrorUrl
     */
    public static function processGitUrl(string $mirrorUrl, string $packageName, string $url, ?string $type): string
    {
        if (Preg::isMatch('#^(?:(?:https?|git)://github\.com/|git@github\.com:)([^/]+)/(.+?)(?:\.git)?$#', $url, $match)) {
            $url = 'gh-'.$match[1].'/'.$match[2];
        } elseif (Preg::isMatch('#^https://bitbucket\.org/([^/]+)/(.+?)(?:\.git)?/?$#', $url, $match)) {
            $url = 'bb-'.$match[1].'/'.$match[2];
        } else {
            $url = Preg::replace('{[^a-z0-9_.-]}i', '-', trim($url, '/'));
        }

        return str_replace(
            ['%package%', '%normalizedUrl%', '%type%'],
            [$packageName, $url, $type],
            $mirrorUrl
        );
    }

    /**
     * @param non-empty-string $mirrorUrl
     */
    public static function processHgUrl(string $mirrorUrl, string $packageName, string $url, string $type): string
    {
        return self::processGitUrl($mirrorUrl, $packageName, $url, $type);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\IO\IOInterface;
use Composer\Pcre\Preg;
use Seld\Signal\SignalHandler;
use Symfony\Component\Process\Exception\ProcessSignaledException;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\RuntimeException;
use React\Promise\Promise;
use React\Promise\PromiseInterface;
use Symfony\Component\Process\ExecutableFinder;

/**
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ProcessExecutor
{
    private const STATUS_QUEUED = 1;
    private const STATUS_STARTED = 2;
    private const STATUS_COMPLETED = 3;
    private const STATUS_FAILED = 4;
    private const STATUS_ABORTED = 5;

    private const BUILTIN_CMD_COMMANDS = [
        'assoc', 'break', 'call', 'cd', 'chdir', 'cls', 'color', 'copy', 'date',
        'del', 'dir', 'echo', 'endlocal', 'erase', 'exit', 'for', 'ftype', 'goto',
        'help', 'if', 'label', 'md', 'mkdir', 'mklink', 'move', 'path', 'pause',
        'popd', 'prompt', 'pushd', 'rd', 'rem', 'ren', 'rename', 'rmdir', 'set',
        'setlocal', 'shift', 'start', 'time', 'title', 'type', 'ver', 'vol',
    ];

    private const GIT_CMDS_NEED_GIT_DIR = [
        ['show'],
        ['log'],
        ['branch'],
        ['remote', 'set-url'],
    ];

    /** @var int */
    protected static $timeout = 300;

    /** @var bool */
    protected $captureOutput = false;
    /** @var string */
    protected $errorOutput = '';
    /** @var ?IOInterface */
    protected $io;

    /**
     * @phpstan-var array<int, array<string, mixed>>
     */
    private $jobs = [];
    /** @var int */
    private $runningJobs = 0;
    /** @var int */
    private $maxJobs = 10;
    /** @var int */
    private $idGen = 0;
    /** @var bool */
    private $allowAsync = false;

    /** @var array<string, string> */
    private static $executables = [];

    public function __construct(?IOInterface $io = null)
    {
        $this->io = $io;
        $this->resetMaxJobs();
    }

    /**
     * runs a process on the commandline
     *
     * @param  string|non-empty-list<string> $command the command to execute
     * @param  mixed   $output  the output will be written into this var if passed by ref
     *                          if a callable is passed it will be used as output handler
     * @param  null|string $cwd     the working directory
     * @return int     statuscode
     */
    public function execute($command, &$output = null, ?string $cwd = null): int
    {
        if (func_num_args() > 1) {
            return $this->doExecute($command, $cwd, false, $output);
        }

        return $this->doExecute($command, $cwd, false);
    }

    /**
     * runs a process on the commandline in TTY mode
     *
     * @param  string|non-empty-list<string>  $command the command to execute
     * @param  null|string $cwd     the working directory
     * @return int     statuscode
     */
    public function executeTty($command, ?string $cwd = null): int
    {
        if (Platform::isTty()) {
            return $this->doExecute($command, $cwd, true);
        }

        return $this->doExecute($command, $cwd, false);
    }

    /**
     * @param  string|non-empty-list<string> $command
     * @param  array<string, string>|null $env
     * @param  mixed   $output
     */
    private function runProcess($command, ?string $cwd, ?array $env, bool $tty, &$output = null): ?int
    {
        // On Windows, we don't rely on the OS to find the executable if possible to avoid lookups
        // in the current directory which could be untrusted. Instead we use the ExecutableFinder.

        if (is_string($command)) {
            if (Platform::isWindows() && Preg::isMatch('{^([^:/\\\\]++) }', $command, $match)) {
                $command = substr_replace($command, self::escape(self::getExecutable($match[1])), 0, strlen($match[1]));
            }

            $process = Process::fromShellCommandline($command, $cwd, $env, null, static::getTimeout());
        } else {
            if (Platform::isWindows() && \strlen($command[0]) === strcspn($command[0], ':/\\')) {
                $command[0] = self::getExecutable($command[0]);
            }

            $process = new Process($command, $cwd, $env, null, static::getTimeout());
        }

        if (!Platform::isWindows() && $tty) {
            try {
                $process->setTty(true);
            } catch (RuntimeException $e) {
                // ignore TTY enabling errors
            }
        }

        $callback = is_callable($output) ? $output : function (string $type, string $buffer): void {
            $this->outputHandler($type, $buffer);
        };

        $signalHandler = SignalHandler::create(
            [SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP],
            function (string $signal) {
                if ($this->io !== null) {
                    $this->io->writeError(
                        'Received '.$signal.', aborting when child process is done',
                        true,
                        IOInterface::DEBUG
                    );
                }
            }
        );

        try {
            $process->run($callback);

            if ($this->captureOutput && !is_callable($output)) {
                $output = $process->getOutput();
            }

            $this->errorOutput = $process->getErrorOutput();
        } catch (ProcessSignaledException $e) {
            if ($signalHandler->isTriggered()) {
                // exiting as we were signaled and the child process exited too due to the signal
                $signalHandler->exitWithLastSignal();
            }
        } finally {
            $signalHandler->unregister();
        }

        return $process->getExitCode();
    }

    /**
     * @param  string|non-empty-list<string> $command
     * @param  mixed   $output
     */
    private function doExecute($command, ?string $cwd, bool $tty, &$output = null): int
    {
        $this->outputCommandRun($command, $cwd, false);

        $this->captureOutput = func_num_args() > 3;
        $this->errorOutput = '';

        $env = null;

        $requiresGitDirEnv = $this->requiresGitDirEnv($command);
        if ($cwd !== null && $requiresGitDirEnv) {
            $isBareRepository = !is_dir(sprintf('%s/.git', rtrim($cwd, '/')));
            if ($isBareRepository) {
                $configValue = '';
                $this->runProcess(['git', 'config', 'safe.bareRepository'], $cwd, ['GIT_DIR' => $cwd], $tty, $configValue);
                $configValue = trim($configValue);
                if ($configValue === 'explicit') {
                    $env = ['GIT_DIR' => $cwd];
                }
            }
        }

        return $this->runProcess($command, $cwd, $env, $tty, $output);
    }

    /**
     * starts a process on the commandline in async mode
     *
     * @param  string|list<string> $command the command to execute
     * @param  string              $cwd     the working directory
     * @phpstan-return PromiseInterface<Process>
     */
    public function executeAsync($command, ?string $cwd = null): PromiseInterface
    {
        if (!$this->allowAsync) {
            throw new \LogicException('You must use the ProcessExecutor instance which is part of a Composer\Loop instance to be able to run async processes');
        }

        $job = [
            'id' => $this->idGen++,
            'status' => self::STATUS_QUEUED,
            'command' => $command,
            'cwd' => $cwd,
        ];

        $resolver = static function ($resolve, $reject) use (&$job): void {
            $job['status'] = ProcessExecutor::STATUS_QUEUED;
            $job['resolve'] = $resolve;
            $job['reject'] = $reject;
        };

        $canceler = static function () use (&$job): void {
            if ($job['status'] === ProcessExecutor::STATUS_QUEUED) {
                $job['status'] = ProcessExecutor::STATUS_ABORTED;
            }
            if ($job['status'] !== ProcessExecutor::STATUS_STARTED) {
                return;
            }
            $job['status'] = ProcessExecutor::STATUS_ABORTED;
            try {
                if (defined('SIGINT')) {
                    $job['process']->signal(SIGINT);
                }
            } catch (\Exception $e) {
                // signal can throw in various conditions, but we don't care if it fails
            }
            $job['process']->stop(1);

            throw new \RuntimeException('Aborted process');
        };

        $promise = new Promise($resolver, $canceler);
        $promise = $promise->then(function () use (&$job) {
            if ($job['process']->isSuccessful()) {
                $job['status'] = ProcessExecutor::STATUS_COMPLETED;
            } else {
                $job['status'] = ProcessExecutor::STATUS_FAILED;
            }

            $this->markJobDone();

            return $job['process'];
        }, function ($e) use (&$job): void {
            $job['status'] = ProcessExecutor::STATUS_FAILED;

            $this->markJobDone();

            throw $e;
        });
        $this->jobs[$job['id']] = &$job;

        if ($this->runningJobs < $this->maxJobs) {
            $this->startJob($job['id']);
        }

        return $promise;
    }

    protected function outputHandler(string $type, string $buffer): void
    {
        if ($this->captureOutput) {
            return;
        }

        if (null === $this->io) {
            echo $buffer;

            return;
        }

        if (Process::ERR === $type) {
            $this->io->writeErrorRaw($buffer, false);
        } else {
            $this->io->writeRaw($buffer, false);
        }
    }

    private function startJob(int $id): void
    {
        $job = &$this->jobs[$id];
        if ($job['status'] !== self::STATUS_QUEUED) {
            return;
        }

        // start job
        $job['status'] = self::STATUS_STARTED;
        $this->runningJobs++;

        $command = $job['command'];
        $cwd = $job['cwd'];

        $this->outputCommandRun($command, $cwd, true);

        try {
            if (is_string($command)) {
                $process = Process::fromShellCommandline($command, $cwd, null, null, static::getTimeout());
            } else {
                $process = new Process($command, $cwd, null, null, static::getTimeout());
            }
        } catch (\Throwable $e) {
            $job['reject']($e);

            return;
        }

        $job['process'] = $process;

        try {
            $process->start();
        } catch (\Throwable $e) {
            $job['reject']($e);

            return;
        }
    }

    public function setMaxJobs(int $maxJobs): void
    {
        $this->maxJobs = $maxJobs;
    }

    public function resetMaxJobs(): void
    {
        if (is_numeric($maxJobs = Platform::getEnv('COMPOSER_MAX_PARALLEL_PROCESSES'))) {
            $this->maxJobs = max(1, min(50, (int) $maxJobs));
        } else {
            $this->maxJobs = 10;
        }
    }

    /**
     * @param  ?int $index job id
     */
    public function wait($index = null): void
    {
        while (true) {
            if (0 === $this->countActiveJobs($index)) {
                return;
            }

            usleep(1000);
        }
    }

    /**
     * @internal
     */
    public function enableAsync(): void
    {
        $this->allowAsync = true;
    }

    /**
     * @internal
     *
     * @param  ?int $index job id
     * @return int         number of active (queued or started) jobs
     */
    public function countActiveJobs($index = null): int
    {
        // tick
        foreach ($this->jobs as $job) {
            if ($job['status'] === self::STATUS_STARTED) {
                if (!$job['process']->isRunning()) {
                    call_user_func($job['resolve'], $job['process']);
                }

                $job['process']->checkTimeout();
            }

            if ($this->runningJobs < $this->maxJobs) {
                if ($job['status'] === self::STATUS_QUEUED) {
                    $this->startJob($job['id']);
                }
            }
        }

        if (null !== $index) {
            return $this->jobs[$index]['status'] < self::STATUS_COMPLETED ? 1 : 0;
        }

        $active = 0;
        foreach ($this->jobs as $job) {
            if ($job['status'] < self::STATUS_COMPLETED) {
                $active++;
            } else {
                unset($this->jobs[$job['id']]);
            }
        }

        return $active;
    }

    private function markJobDone(): void
    {
        $this->runningJobs--;
    }

    /**
     * @return string[]
     */
    public function splitLines(?string $output): array
    {
        $output = trim((string) $output);

        return $output === '' ? [] : Preg::split('{\r?\n}', $output);
    }

    /**
     * Get any error output from the last command
     */
    public function getErrorOutput(): string
    {
        return $this->errorOutput;
    }

    /**
     * @return int the timeout in seconds
     */
    public static function getTimeout(): int
    {
        return static::$timeout;
    }

    /**
     * @param  int  $timeout the timeout in seconds
     */
    public static function setTimeout(int $timeout): void
    {
        static::$timeout = $timeout;
    }

    /**
     * Escapes a string to be used as a shell argument.
     *
     * @param string|false|null $argument The argument that will be escaped
     *
     * @return string The escaped argument
     */
    public static function escape($argument): string
    {
        return self::escapeArgument($argument);
    }

    /**
     * @param string|list<string> $command
     */
    private function outputCommandRun($command, ?string $cwd, bool $async): void
    {
        if (null === $this->io || !$this->io->isDebug()) {
            return;
        }

        $commandString = is_string($command) ? $command : implode(' ', array_map(self::class.'::escape', $command));
        $safeCommand = Preg::replaceCallback('{://(?P<user>[^:/\s]+):(?P<password>[^@\s/]+)@}i', static function ($m): string {
            // if the username looks like a long (12char+) hex string, or a modern github token (e.g. ghp_xxx, github_pat_xxx) we obfuscate that
            if (Preg::isMatch(GitHub::GITHUB_TOKEN_REGEX, $m['user'])) {
                return '://***:***@';
            }
            if (Preg::isMatch('{^[a-f0-9]{12,}$}', $m['user'])) {
                return '://***:***@';
            }

            return '://'.$m['user'].':***@';
        }, $commandString);
        $safeCommand = Preg::replace("{--password (.*[^\\\\]\') }", '--password \'***\' ', $safeCommand);
        $this->io->writeError('Executing'.($async ? ' async' : '').' command ('.($cwd ?: 'CWD').'): '.$safeCommand);
    }

    /**
     * Escapes a string to be used as a shell argument for Symfony Process.
     *
     * This method expects cmd.exe to be started with the /V:ON option, which
     * enables delayed environment variable expansion using ! as the delimiter.
     * If this is not the case, any escaped ^^!var^^! will be transformed to
     * ^!var^! and introduce two unintended carets.
     *
     * Modified from https://github.com/johnstevenson/winbox-args
     * MIT Licensed (c) John Stevenson <john-stevenson@blueyonder.co.uk>
     *
     * @param string|false|null $argument
     */
    private static function escapeArgument($argument): string
    {
        if ('' === ($argument = (string) $argument)) {
            return escapeshellarg($argument);
        }

        if (!Platform::isWindows()) {
            return "'".str_replace("'", "'\\''", $argument)."'";
        }

        // New lines break cmd.exe command parsing
        // and special chars like the fullwidth quote can be used to break out
        // of parameter encoding via "Best Fit" encoding conversion
        $argument = strtr($argument, [
            "\n" => ' ',
            "\u{ff02}" => '"',
            "\u{02ba}" => '"',
            "\u{301d}" => '"',
            "\u{301e}" => '"',
            "\u{030e}" => '"',
            "\u{ff1a}" => ':',
            "\u{0589}" => ':',
            "\u{2236}" => ':',
            "\u{ff0f}" => '/',
            "\u{2044}" => '/',
            "\u{2215}" => '/',
            "\u{00b4}" => '/',
        ]);

        // In addition to whitespace, commas need quoting to preserve paths
        $quote = strpbrk($argument, " \t,") !== false;
        $argument = Preg::replace('/(\\\\*)"/', '$1$1\\"', $argument, -1, $dquotes);
        $meta = $dquotes > 0 || Preg::isMatch('/%[^%]+%|![^!]+!/', $argument);

        if (!$meta && !$quote) {
            $quote = strpbrk($argument, '^&|<>()') !== false;
        }

        if ($quote) {
            $argument = '"'.Preg::replace('/(\\\\*)$/', '$1$1', $argument).'"';
        }

        if ($meta) {
            $argument = Preg::replace('/(["^&|<>()%])/', '^$1', $argument);
            $argument = Preg::replace('/(!)/', '^^$1', $argument);
        }

        return $argument;
    }

    /**
     * @param string[]|string $command
     */
    public function requiresGitDirEnv($command): bool
    {
        $cmd = !is_array($command) ? explode(' ', $command) : $command;
        if ($cmd[0] !== 'git') {
            return false;
        }

        foreach (self::GIT_CMDS_NEED_GIT_DIR as $gitCmd) {
            if (array_intersect($cmd, $gitCmd) === $gitCmd) {
                return true;
            }
        }

        return false;
    }

    /**
     * Resolves executable paths on Windows
     */
    private static function getExecutable(string $name): string
    {
        if (\in_array(strtolower($name), self::BUILTIN_CMD_COMMANDS, true)) {
            return $name;
        }

        if (!isset(self::$executables[$name])) {
            $path = (new ExecutableFinder())->find($name, $name);
            if ($path !== null) {
                self::$executables[$name] = $path;
            }
        }

        return self::$executables[$name] ?? $name;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\IO\IOInterface;
use Composer\Config;
use Composer\Factory;
use Composer\Downloader\TransportException;
use Composer\Pcre\Preg;

/**
 * @author Roshan Gautam <roshan.gautam@hotmail.com>
 */
class GitLab
{
    /** @var IOInterface */
    protected $io;
    /** @var Config */
    protected $config;
    /** @var ProcessExecutor */
    protected $process;
    /** @var HttpDownloader */
    protected $httpDownloader;

    /**
     * Constructor.
     *
     * @param IOInterface     $io             The IO instance
     * @param Config          $config         The composer configuration
     * @param ProcessExecutor $process        Process instance, injectable for mocking
     * @param HttpDownloader  $httpDownloader Remote Filesystem, injectable for mocking
     */
    public function __construct(IOInterface $io, Config $config, ?ProcessExecutor $process = null, ?HttpDownloader $httpDownloader = null)
    {
        $this->io = $io;
        $this->config = $config;
        $this->process = $process ?: new ProcessExecutor($io);
        $this->httpDownloader = $httpDownloader ?: Factory::createHttpDownloader($this->io, $config);
    }

    /**
     * Attempts to authorize a GitLab domain via OAuth.
     *
     * @param string $originUrl The host this GitLab instance is located at
     *
     * @return bool true on success
     */
    public function authorizeOAuth(string $originUrl): bool
    {
        // before composer 1.9, origin URLs had no port number in them
        $bcOriginUrl = Preg::replace('{:\d+}', '', $originUrl);

        if (!in_array($originUrl, $this->config->get('gitlab-domains'), true) && !in_array($bcOriginUrl, $this->config->get('gitlab-domains'), true)) {
            return false;
        }

        // if available use token from git config
        if (0 === $this->process->execute(['git', 'config', 'gitlab.accesstoken'], $output)) {
            $this->io->setAuthentication($originUrl, trim($output), 'oauth2');

            return true;
        }

        // if available use deploy token from git config
        if (0 === $this->process->execute(['git', 'config', 'gitlab.deploytoken.user'], $tokenUser) && 0 === $this->process->execute(['git', 'config', 'gitlab.deploytoken.token'], $tokenPassword)) {
            $this->io->setAuthentication($originUrl, trim($tokenUser), trim($tokenPassword));

            return true;
        }

        // if available use token from composer config
        $authTokens = $this->config->get('gitlab-token');

        if (isset($authTokens[$originUrl])) {
            $token = $authTokens[$originUrl];
        }

        if (isset($authTokens[$bcOriginUrl])) {
            $token = $authTokens[$bcOriginUrl];
        }

        if (isset($token)) {
            $username = is_array($token) ? $token["username"] : $token;
            $password = is_array($token) ? $token["token"] : 'private-token';

            // Composer expects the GitLab token to be stored as username and 'private-token' or 'gitlab-ci-token' to be stored as password
            // Detect cases where this is reversed and resolve automatically resolve it
            if (in_array($username, ['private-token', 'gitlab-ci-token',  'oauth2'], true)) {
                $this->io->setAuthentication($originUrl, $password, $username);
            } else {
                $this->io->setAuthentication($originUrl, $username, $password);
            }

            return true;
        }

        return false;
    }

    /**
     * Authorizes a GitLab domain interactively via OAuth.
     *
     * @param string $scheme    Scheme used in the origin URL
     * @param string $originUrl The host this GitLab instance is located at
     * @param string $message   The reason this authorization is required
     *
     * @throws \RuntimeException
     * @throws TransportException|\Exception
     *
     * @return bool true on success
     */
    public function authorizeOAuthInteractively(string $scheme, string $originUrl, ?string $message = null): bool
    {
        if ($message) {
            $this->io->writeError($message);
        }

        $localAuthConfig = $this->config->getLocalAuthConfigSource();
        $personalAccessTokenLink = $scheme.'://'.$originUrl.'/-/user_settings/personal_access_tokens';
        $revokeLink = $scheme.'://'.$originUrl.'/-/user_settings/applications';
        $this->io->writeError(sprintf('A token will be created and stored in "%s", your password will never be stored', ($localAuthConfig !== null ? $localAuthConfig->getName() . ' OR ' : '') . $this->config->getAuthConfigSource()->getName()));
        $this->io->writeError('To revoke access to this token you can visit:');
        $this->io->writeError($revokeLink);
        $this->io->writeError('Alternatively you can setup an personal access token on:');
        $this->io->writeError($personalAccessTokenLink);
        $this->io->writeError('and store it under "gitlab-token" see https://getcomposer.org/doc/articles/authentication-for-private-packages.md#gitlab-token for more details.');
        $this->io->writeError('https://getcomposer.org/doc/articles/authentication-for-private-packages.md#gitlab-token');
        $this->io->writeError('for more details.');

        $storeInLocalAuthConfig = false;
        if ($localAuthConfig !== null) {
            $storeInLocalAuthConfig = $this->io->askConfirmation('A local auth config source was found, do you want to store the token there?', true);
        }

        $attemptCounter = 0;

        while ($attemptCounter++ < 5) {
            try {
                $response = $this->createToken($scheme, $originUrl);
            } catch (TransportException $e) {
                // 401 is bad credentials,
                // 403 is max login attempts exceeded
                if (in_array($e->getCode(), [403, 401])) {
                    if (401 === $e->getCode()) {
                        $response = json_decode($e->getResponse(), true);
                        if (isset($response['error']) && $response['error'] === 'invalid_grant') {
                            $this->io->writeError('Bad credentials. If you have two factor authentication enabled you will have to manually create a personal access token');
                        } else {
                            $this->io->writeError('Bad credentials.');
                        }
                    } else {
                        $this->io->writeError('Maximum number of login attempts exceeded. Please try again later.');
                    }

                    $this->io->writeError('You can also manually create a personal access token enabling the "read_api" scope at:');
                    $this->io->writeError($personalAccessTokenLink);
                    $this->io->writeError('Add it using "composer config --global --auth gitlab-token.'.$originUrl.' <token>"');

                    continue;
                }

                throw $e;
            }

            $this->io->setAuthentication($originUrl, $response['access_token'], 'oauth2');

            $authConfigSource = $storeInLocalAuthConfig && $localAuthConfig !== null ? $localAuthConfig : $this->config->getAuthConfigSource();
            // store value in user config in auth file
            if (isset($response['expires_in'])) {
                $authConfigSource->addConfigSetting(
                    'gitlab-oauth.'.$originUrl,
                    [
                        'expires-at' => intval($response['created_at']) + intval($response['expires_in']),
                        'refresh-token' => $response['refresh_token'],
                        'token' => $response['access_token'],
                    ]
                );
            } else {
                $authConfigSource->addConfigSetting('gitlab-oauth.'.$originUrl, $response['access_token']);
            }

            return true;
        }

        throw new \RuntimeException('Invalid GitLab credentials 5 times in a row, aborting.');
    }

    /**
     * Authorizes a GitLab domain interactively via OAuth.
     *
     * @param string $scheme    Scheme used in the origin URL
     * @param string $originUrl The host this GitLab instance is located at
     *
     * @throws \RuntimeException
     * @throws TransportException|\Exception
     *
     * @return bool true on success
     */
    public function authorizeOAuthRefresh(string $scheme, string $originUrl): bool
    {
        try {
            $response = $this->refreshToken($scheme, $originUrl);
        } catch (TransportException $e) {
            $this->io->writeError("Couldn't refresh access token: ".$e->getMessage());

            return false;
        }

        $this->io->setAuthentication($originUrl, $response['access_token'], 'oauth2');

        // store value in user config in auth file
        $this->config->getAuthConfigSource()->addConfigSetting(
            'gitlab-oauth.'.$originUrl,
            [
                'expires-at' => intval($response['created_at']) + intval($response['expires_in']),
                'refresh-token' => $response['refresh_token'],
                'token' => $response['access_token'],
            ]
        );

        return true;
    }

    /**
     * @return array{access_token: non-empty-string, refresh_token: non-empty-string, token_type: non-empty-string, expires_in?: positive-int, created_at: positive-int}
     *
     * @see https://docs.gitlab.com/ee/api/oauth2.html#resource-owner-password-credentials-flow
     */
    private function createToken(string $scheme, string $originUrl): array
    {
        $username = $this->io->ask('Username: ');
        $password = $this->io->askAndHideAnswer('Password: ');

        $headers = ['Content-Type: application/x-www-form-urlencoded'];

        $apiUrl = $originUrl;
        $data = http_build_query([
            'username' => $username,
            'password' => $password,
            'grant_type' => 'password',
        ], '', '&');
        $options = [
            'retry-auth-failure' => false,
            'http' => [
                'method' => 'POST',
                'header' => $headers,
                'content' => $data,
            ],
        ];

        $token = $this->httpDownloader->get($scheme.'://'.$apiUrl.'/oauth/token', $options)->decodeJson();

        $this->io->writeError('Token successfully created');

        return $token;
    }

    /**
     * Is the OAuth access token expired?
     *
     * @return bool true on expired token, false if token is fresh or expiration date is not set
     */
    public function isOAuthExpired(string $originUrl): bool
    {
        $authTokens = $this->config->get('gitlab-oauth');
        if (isset($authTokens[$originUrl]['expires-at'])) {
            if ($authTokens[$originUrl]['expires-at'] < time()) {
                return true;
            }
        }

        return false;
    }

    /**
     * @return array{access_token: non-empty-string, refresh_token: non-empty-string, token_type: non-empty-string, expires_in: positive-int, created_at: positive-int}
     *
     * @see https://docs.gitlab.com/ee/api/oauth2.html#resource-owner-password-credentials-flow
     */
    private function refreshToken(string $scheme, string $originUrl): array
    {
        $authTokens = $this->config->get('gitlab-oauth');
        if (!isset($authTokens[$originUrl]['refresh-token'])) {
            throw new \RuntimeException('No GitLab refresh token present for '.$originUrl.'.');
        }

        $refreshToken = $authTokens[$originUrl]['refresh-token'];
        $headers = ['Content-Type: application/x-www-form-urlencoded'];

        $data = http_build_query([
            'refresh_token' => $refreshToken,
            'grant_type' => 'refresh_token',
        ], '', '&');
        $options = [
            'retry-auth-failure' => false,
            'http' => [
                'method' => 'POST',
                'header' => $headers,
                'content' => $data,
            ],
        ];

        $token = $this->httpDownloader->get($scheme.'://'.$originUrl.'/oauth/token', $options)->decodeJson();
        $this->io->writeError('GitLab token successfully refreshed', true, IOInterface::VERY_VERBOSE);
        $this->io->writeError('To revoke access to this token you can visit '.$scheme.'://'.$originUrl.'/-/user_settings/applications', true, IOInterface::VERY_VERBOSE);

        return $token;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

/**
 * @internal
 * @readonly
 */
final class ForgejoRepositoryData
{
    /** @var string */
    public $htmlUrl;
    /** @var string */
    public $sshUrl;
    /** @var string */
    public $httpCloneUrl;
    /** @var bool */
    public $isPrivate;
    /** @var string */
    public $defaultBranch;
    /** @var bool */
    public $hasIssues;
    /** @var bool */
    public $isArchived;

    public function __construct(
        string $htmlUrl,
        string $httpCloneUrl,
        string $sshUrl,
        bool $isPrivate,
        string $defaultBranch,
        bool $hasIssues,
        bool $isArchived
    ) {
        $this->htmlUrl = $htmlUrl;
        $this->httpCloneUrl = $httpCloneUrl;
        $this->sshUrl = $sshUrl;
        $this->isPrivate = $isPrivate;
        $this->defaultBranch = $defaultBranch;
        $this->hasIssues = $hasIssues;
        $this->isArchived = $isArchived;
    }

    /**
     * @param array<string, mixed> $data
     */
    public static function fromRemoteData(array $data): self
    {
        return new self(
            $data['html_url'],
            $data['clone_url'],
            $data['ssh_url'],
            $data['private'],
            $data['default_branch'],
            $data['has_issues'],
            $data['archived']
        );
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Pcre\Preg;

/**
 * @internal
 * @readonly
 */
final class ForgejoUrl
{
    public const URL_REGEX = '{^(?:(?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/([^/]+?)(?:\.git|/)?$}';

    /** @var string */
    public $owner;
    /** @var string */
    public $repository;
    /** @var string */
    public $originUrl;
    /** @var string */
    public $apiUrl;

    private function __construct(
        string $owner,
        string $repository,
        string $originUrl,
        string $apiUrl
    ) {
        $this->owner = $owner;
        $this->repository = $repository;
        $this->originUrl = $originUrl;
        $this->apiUrl = $apiUrl;
    }

    public static function create(string $repoUrl): self
    {
        $url = self::tryFrom($repoUrl);
        if ($url !== null) {
            return $url;
        }

        throw new \InvalidArgumentException('This is not a valid Forgejo URL: ' . $repoUrl);
    }

    public static function tryFrom(?string $repoUrl): ?self
    {
        if ($repoUrl === null || !Preg::isMatch(self::URL_REGEX, $repoUrl, $match)) {
            return null;
        }

        $originUrl = strtolower($match[1] ?? (string) $match[2]);
        $apiBase = $originUrl . '/api/v1';

        return new self(
            $match[3],
            $match[4],
            $originUrl,
            sprintf('https://%s/repos/%s/%s', $apiBase, $match[3], $match[4])
        );
    }

    public function generateSshUrl(): string
    {
        return 'git@' . $this->originUrl . ':'.$this->owner.'/'.$this->repository.'.git';
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Pcre\Preg;
use ErrorException;
use React\Promise\PromiseInterface;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Finder\Finder;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class Filesystem
{
    /** @var ?ProcessExecutor */
    private $processExecutor;

    public function __construct(?ProcessExecutor $executor = null)
    {
        $this->processExecutor = $executor;
    }

    /**
     * @return bool
     */
    public function remove(string $file)
    {
        if (is_dir($file)) {
            return $this->removeDirectory($file);
        }

        if (file_exists($file)) {
            return $this->unlink($file);
        }

        return false;
    }

    /**
     * Checks if a directory is empty
     *
     * @return bool
     */
    public function isDirEmpty(string $dir)
    {
        $finder = Finder::create()
            ->ignoreVCS(false)
            ->ignoreDotFiles(false)
            ->depth(0)
            ->in($dir);

        return \count($finder) === 0;
    }

    /**
     * @return void
     */
    public function emptyDirectory(string $dir, bool $ensureDirectoryExists = true)
    {
        if (is_link($dir) && file_exists($dir)) {
            $this->unlink($dir);
        }

        if ($ensureDirectoryExists) {
            $this->ensureDirectoryExists($dir);
        }

        if (is_dir($dir)) {
            $finder = Finder::create()
                ->ignoreVCS(false)
                ->ignoreDotFiles(false)
                ->depth(0)
                ->in($dir);

            foreach ($finder as $path) {
                $this->remove((string) $path);
            }
        }
    }

    /**
     * Recursively remove a directory
     *
     * Uses the process component if proc_open is enabled on the PHP
     * installation.
     *
     * @throws \RuntimeException
     * @return bool
     */
    public function removeDirectory(string $directory)
    {
        $edgeCaseResult = $this->removeEdgeCases($directory);
        if ($edgeCaseResult !== null) {
            return $edgeCaseResult;
        }

        if (Platform::isWindows()) {
            $cmd = ['rmdir', '/S', '/Q', Platform::realpath($directory)];
        } else {
            $cmd = ['rm', '-rf', $directory];
        }

        $result = $this->getProcess()->execute($cmd, $output) === 0;

        // clear stat cache because external processes aren't tracked by the php stat cache
        clearstatcache();

        if ($result && !is_dir($directory)) {
            return true;
        }

        return $this->removeDirectoryPhp($directory);
    }

    /**
     * Recursively remove a directory asynchronously
     *
     * Uses the process component if proc_open is enabled on the PHP
     * installation.
     *
     * @throws \RuntimeException
     * @return PromiseInterface
     * @phpstan-return PromiseInterface<bool>
     */
    public function removeDirectoryAsync(string $directory)
    {
        $edgeCaseResult = $this->removeEdgeCases($directory);
        if ($edgeCaseResult !== null) {
            return \React\Promise\resolve($edgeCaseResult);
        }

        if (Platform::isWindows()) {
            $cmd = ['rmdir', '/S', '/Q', Platform::realpath($directory)];
        } else {
            $cmd = ['rm', '-rf', $directory];
        }

        $promise = $this->getProcess()->executeAsync($cmd);

        return $promise->then(function ($process) use ($directory) {
            // clear stat cache because external processes aren't tracked by the php stat cache
            clearstatcache();

            if ($process->isSuccessful()) {
                if (!is_dir($directory)) {
                    return \React\Promise\resolve(true);
                }
            }

            return \React\Promise\resolve($this->removeDirectoryPhp($directory));
        });
    }

    /**
     * @return bool|null Returns null, when no edge case was hit. Otherwise a bool whether removal was successful
     */
    private function removeEdgeCases(string $directory, bool $fallbackToPhp = true): ?bool
    {
        if ($this->isSymlinkedDirectory($directory)) {
            return $this->unlinkSymlinkedDirectory($directory);
        }

        if ($this->isJunction($directory)) {
            return $this->removeJunction($directory);
        }

        if (is_link($directory)) {
            return unlink($directory);
        }

        if (!is_dir($directory) || !file_exists($directory)) {
            return true;
        }

        if (Preg::isMatch('{^(?:[a-z]:)?[/\\\\]+$}i', $directory)) {
            throw new \RuntimeException('Aborting an attempted deletion of '.$directory.', this was probably not intended, if it is a real use case please report it.');
        }

        if (!\function_exists('proc_open') && $fallbackToPhp) {
            return $this->removeDirectoryPhp($directory);
        }

        return null;
    }

    /**
     * Recursively delete directory using PHP iterators.
     *
     * Uses a CHILD_FIRST RecursiveIteratorIterator to sort files
     * before directories, creating a single non-recursive loop
     * to delete files/directories in the correct order.
     *
     * @return bool
     */
    public function removeDirectoryPhp(string $directory)
    {
        $edgeCaseResult = $this->removeEdgeCases($directory, false);
        if ($edgeCaseResult !== null) {
            return $edgeCaseResult;
        }

        try {
            $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS);
        } catch (\UnexpectedValueException $e) {
            // re-try once after clearing the stat cache if it failed as it
            // sometimes fails without apparent reason, see https://github.com/composer/composer/issues/4009
            clearstatcache();
            usleep(100000);
            if (!is_dir($directory)) {
                return true;
            }
            $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS);
        }
        $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);

        foreach ($ri as $file) {
            if ($file->isDir()) {
                $this->rmdir($file->getPathname());
            } else {
                $this->unlink($file->getPathname());
            }
        }

        // release locks on the directory, see https://github.com/composer/composer/issues/9945
        unset($ri, $it, $file);

        return $this->rmdir($directory);
    }

    /**
     * @return void
     */
    public function ensureDirectoryExists(string $directory)
    {
        if (!is_dir($directory)) {
            if (file_exists($directory)) {
                throw new \RuntimeException(
                    $directory.' exists and is not a directory.'
                );
            }

            if (is_link($directory) && !@$this->unlinkImplementation($directory)) {
                throw new \RuntimeException('Could not delete symbolic link '.$directory.': '.(error_get_last()['message'] ?? ''));
            }

            if (!@mkdir($directory, 0777, true)) {
                $e = new \RuntimeException($directory.' does not exist and could not be created: '.(error_get_last()['message'] ?? ''));

                // in pathological cases with paths like path/to/broken-symlink/../foo is_dir will fail to detect path/to/foo
                // but normalizing the ../ away first makes it work so we attempt this just in case, and if it still fails we
                // report the initial error we had with the original path, and ignore the normalized path exception
                // see https://github.com/composer/composer/issues/11864
                $normalized = $this->normalizePath($directory);
                if ($normalized !== $directory) {
                    try {
                        $this->ensureDirectoryExists($normalized);

                        return;
                    } catch (\Throwable $ignoredEx) {
                    }
                }

                throw $e;
            }
        }
    }

    /**
     * Attempts to unlink a file and in case of failure retries after 350ms on windows
     *
     * @throws \RuntimeException
     * @return bool
     */
    public function unlink(string $path)
    {
        $unlinked = @$this->unlinkImplementation($path);
        if (!$unlinked) {
            // retry after a bit on windows since it tends to be touchy with mass removals
            if (Platform::isWindows()) {
                usleep(350000);
                $unlinked = @$this->unlinkImplementation($path);
            }

            if (!$unlinked) {
                $error = error_get_last();
                $message = 'Could not delete '.$path.': ' . ($error['message'] ?? '');
                if (Platform::isWindows()) {
                    $message .= "\nThis can be due to an antivirus or the Windows Search Indexer locking the file while they are analyzed";
                }

                throw new \RuntimeException($message);
            }
        }

        return true;
    }

    /**
     * Attempts to rmdir a file and in case of failure retries after 350ms on windows
     *
     * @throws \RuntimeException
     * @return bool
     */
    public function rmdir(string $path)
    {
        $deleted = @rmdir($path);
        if (!$deleted) {
            // retry after a bit on windows since it tends to be touchy with mass removals
            if (Platform::isWindows()) {
                usleep(350000);
                $deleted = @rmdir($path);
            }

            if (!$deleted) {
                $error = error_get_last();
                $message = 'Could not delete '.$path.': ' . ($error['message'] ?? '');
                if (Platform::isWindows()) {
                    $message .= "\nThis can be due to an antivirus or the Windows Search Indexer locking the file while they are analyzed";
                }

                throw new \RuntimeException($message);
            }
        }

        return true;
    }

    /**
     * Copy then delete is a non-atomic version of {@link rename}.
     *
     * Some systems can't rename and also don't have proc_open,
     * which requires this solution.
     *
     * @return void
     */
    public function copyThenRemove(string $source, string $target)
    {
        $this->copy($source, $target);
        if (!is_dir($source)) {
            $this->unlink($source);

            return;
        }

        $this->removeDirectoryPhp($source);
    }

    /**
     * Copies a file or directory from $source to $target.
     *
     * @return bool
     */
    public function copy(string $source, string $target)
    {
        // refs https://github.com/composer/composer/issues/11864
        $target = $this->normalizePath($target);

        if (!is_dir($source)) {
            try {
                return copy($source, $target);
            } catch (ErrorException $e) {
                // if copy fails we attempt to copy it manually as this can help bypass issues with VirtualBox shared folders
                // see https://github.com/composer/composer/issues/12057
                if (str_contains($e->getMessage(), 'Bad address')) {
                    $sourceHandle = fopen($source, 'r');
                    $targetHandle = fopen($target, 'w');
                    if (false === $sourceHandle || false === $targetHandle) {
                        throw $e;
                    }
                    while (!feof($sourceHandle)) {
                        if (false === fwrite($targetHandle, (string) fread($sourceHandle, 1024 * 1024))) {
                            throw $e;
                        }
                    }
                    fclose($sourceHandle);
                    fclose($targetHandle);

                    return true;
                }
                throw $e;
            }
        }

        $it = new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS);
        $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::SELF_FIRST);
        $this->ensureDirectoryExists($target);

        $result = true;
        foreach ($ri as $file) {
            $targetPath = $target . DIRECTORY_SEPARATOR . $ri->getSubPathname();
            if ($file->isDir()) {
                $this->ensureDirectoryExists($targetPath);
            } else {
                $result = $result && copy($file->getPathname(), $targetPath);
            }
        }

        return $result;
    }

    /**
     * @return void
     */
    public function rename(string $source, string $target)
    {
        if (true === @rename($source, $target)) {
            return;
        }

        if (!\function_exists('proc_open')) {
            $this->copyThenRemove($source, $target);

            return;
        }

        if (Platform::isWindows()) {
            // Try to copy & delete - this is a workaround for random "Access denied" errors.
            $result = $this->getProcess()->execute(['xcopy', $source, $target, '/E', '/I', '/Q', '/Y'], $output);

            // clear stat cache because external processes aren't tracked by the php stat cache
            clearstatcache();

            if (0 === $result) {
                $this->remove($source);

                return;
            }
        } else {
            // We do not use PHP's "rename" function here since it does not support
            // the case where $source, and $target are located on different partitions.
            $result = $this->getProcess()->execute(['mv', $source, $target], $output);

            // clear stat cache because external processes aren't tracked by the php stat cache
            clearstatcache();

            if (0 === $result) {
                return;
            }
        }

        $this->copyThenRemove($source, $target);
    }

    /**
     * Returns the shortest path from $from to $to
     *
     * @param  bool                      $directories if true, the source/target are considered to be directories
     * @param  bool                      $preferRelative if true, relative paths will be preferred even if longer
     * @throws \InvalidArgumentException
     * @return string
     */
    public function findShortestPath(string $from, string $to, bool $directories = false, bool $preferRelative = false)
    {
        if (!$this->isAbsolutePath($from) || !$this->isAbsolutePath($to)) {
            throw new \InvalidArgumentException(sprintf('$from (%s) and $to (%s) must be absolute paths.', $from, $to));
        }

        $from = $this->normalizePath($from);
        $to = $this->normalizePath($to);

        if ($directories) {
            $from = rtrim($from, '/') . '/dummy_file';
        }

        if (\dirname($from) === \dirname($to)) {
            return './'.basename($to);
        }

        $commonPath = $to;
        while (strpos($from.'/', $commonPath.'/') !== 0 && '/' !== $commonPath && !Preg::isMatch('{^[A-Z]:/?$}i', $commonPath)) {
            $commonPath = strtr(\dirname($commonPath), '\\', '/');
        }

        // no commonality at all
        if (0 !== strpos($from, $commonPath)) {
            return $to;
        }

        $commonPath = rtrim($commonPath, '/') . '/';
        $sourcePathDepth = substr_count((string) substr($from, \strlen($commonPath)), '/');
        $commonPathCode = str_repeat('../', $sourcePathDepth);

        // allow top level /foo & /bar dirs to be addressed relatively as this is common in Docker setups
        if (!$preferRelative && '/' === $commonPath && $sourcePathDepth > 1) {
            return $to;
        }

        $result = $commonPathCode . substr($to, \strlen($commonPath));
        if (\strlen($result) === 0) {
            return './';
        }

        return $result;
    }

    /**
     * Returns PHP code that, when executed in $from, will return the path to $to
     *
     * @param  bool                      $directories if true, the source/target are considered to be directories
     * @param  bool                      $preferRelative if true, relative paths will be preferred even if longer
     * @throws \InvalidArgumentException
     * @return string
     */
    public function findShortestPathCode(string $from, string $to, bool $directories = false, bool $staticCode = false, bool $preferRelative = false)
    {
        if (!$this->isAbsolutePath($from) || !$this->isAbsolutePath($to)) {
            throw new \InvalidArgumentException(sprintf('$from (%s) and $to (%s) must be absolute paths.', $from, $to));
        }

        $from = $this->normalizePath($from);
        $to = $this->normalizePath($to);

        if ($from === $to) {
            return $directories ? '__DIR__' : '__FILE__';
        }

        $commonPath = $to;
        while (strpos($from.'/', $commonPath.'/') !== 0 && '/' !== $commonPath && !Preg::isMatch('{^[A-Z]:/?$}i', $commonPath) && '.' !== $commonPath) {
            $commonPath = strtr(\dirname($commonPath), '\\', '/');
        }

        // no commonality at all
        if (0 !== strpos($from, $commonPath) || '.' === $commonPath) {
            return var_export($to, true);
        }

        $commonPath = rtrim($commonPath, '/') . '/';
        if (str_starts_with($to, $from.'/')) {
            return '__DIR__ . '.var_export((string) substr($to, \strlen($from)), true);
        }
        $sourcePathDepth = substr_count((string) substr($from, \strlen($commonPath)), '/') + (int) $directories;

        // allow top level /foo & /bar dirs to be addressed relatively as this is common in Docker setups
        if (!$preferRelative && '/' === $commonPath && $sourcePathDepth > 1) {
            return var_export($to, true);
        }

        if ($staticCode) {
            $commonPathCode = "__DIR__ . '".str_repeat('/..', $sourcePathDepth)."'";
        } else {
            $commonPathCode = str_repeat('dirname(', $sourcePathDepth).'__DIR__'.str_repeat(')', $sourcePathDepth);
        }
        $relTarget = (string) substr($to, \strlen($commonPath));

        return $commonPathCode . (\strlen($relTarget) > 0 ? '.' . var_export('/' . $relTarget, true) : '');
    }

    /**
     * Checks if the given path is absolute
     *
     * @return bool
     */
    public function isAbsolutePath(string $path)
    {
        return strpos($path, '/') === 0 || substr($path, 1, 1) === ':' || strpos($path, '\\\\') === 0;
    }

    /**
     * Returns size of a file or directory specified by path. If a directory is
     * given, its size will be computed recursively.
     *
     * @param  string            $path Path to the file or directory
     * @throws \RuntimeException
     * @return int
     */
    public function size(string $path)
    {
        if (!file_exists($path)) {
            throw new \RuntimeException("$path does not exist.");
        }
        if (is_dir($path)) {
            return $this->directorySize($path);
        }

        return (int) filesize($path);
    }

    /**
     * Normalize a path. This replaces backslashes with slashes, removes ending
     * slash and collapses redundant separators and up-level references.
     *
     * @param  string $path Path to the file or directory
     * @return string
     */
    public function normalizePath(string $path)
    {
        $parts = [];
        $path = strtr($path, '\\', '/');
        $prefix = '';
        $absolute = '';

        // extract windows UNC paths e.g. \\foo\bar
        if (strpos($path, '//') === 0 && \strlen($path) > 2) {
            $absolute = '//';
            $path = substr($path, 2);
        }

        // extract a prefix being a protocol://, protocol:, protocol://drive: or simply drive:
        if (Preg::isMatchStrictGroups('{^( [0-9a-z]{2,}+: (?: // (?: [a-z]: )? )? | [a-z]: )}ix', $path, $match)) {
            $prefix = $match[1];
            $path = substr($path, \strlen($prefix));
        }

        if (strpos($path, '/') === 0) {
            $absolute = '/';
            $path = substr($path, 1);
        }

        $up = false;
        foreach (explode('/', $path) as $chunk) {
            if ('..' === $chunk && (\strlen($absolute) > 0 || $up)) {
                array_pop($parts);
                $up = !(\count($parts) === 0 || '..' === end($parts));
            } elseif ('.' !== $chunk && '' !== $chunk) {
                $parts[] = $chunk;
                $up = '..' !== $chunk;
            }
        }

        // ensure c: is normalized to C:
        $prefix = Preg::replaceCallback('{(^|://)[a-z]:$}i', static function (array $m) {
            return strtoupper($m[0]);
        }, $prefix);

        return $prefix.$absolute.implode('/', $parts);
    }

    /**
     * Remove trailing slashes if present to avoid issues with symlinks
     *
     * And other possible unforeseen disasters, see https://github.com/composer/composer/pull/9422
     *
     * @return string
     */
    public static function trimTrailingSlash(string $path)
    {
        if (!Preg::isMatch('{^[/\\\\]+$}', $path)) {
            $path = rtrim($path, '/\\');
        }

        return $path;
    }

    /**
     * Return if the given path is local
     *
     * @return bool
     */
    public static function isLocalPath(string $path)
    {
        // on windows, \\foo indicates network paths so we exclude those from local paths, however it is unsafe
        // on linux as file:////foo (which would be a network path \\foo on windows) will resolve to /foo which could be a local path
        if (Platform::isWindows()) {
            return Preg::isMatch('{^(file://(?!//)|/(?!/)|/?[a-z]:[\\\\/]|\.\.[\\\\/]|[a-z0-9_.-]+[\\\\/])}i', $path);
        }

        return Preg::isMatch('{^(file://|/|/?[a-z]:[\\\\/]|\.\.[\\\\/]|[a-z0-9_.-]+[\\\\/])}i', $path);
    }

    /**
     * @return string
     */
    public static function getPlatformPath(string $path)
    {
        if (Platform::isWindows()) {
            $path = Preg::replace('{^(?:file:///([a-z]):?/)}i', 'file://$1:/', $path);
        }

        return Preg::replace('{^file://}i', '', $path);
    }

    /**
     * Cross-platform safe version of is_readable()
     *
     * This will also check for readability by reading the file as is_readable can not be trusted on network-mounts
     * and \\wsl$ paths. See https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926
     *
     * @return bool
     */
    public static function isReadable(string $path)
    {
        if (is_readable($path)) {
            return true;
        }

        if (is_file($path)) {
            return false !== Silencer::call('file_get_contents', $path, false, null, 0, 1);
        }

        if (is_dir($path)) {
            return false !== Silencer::call('opendir', $path);
        }

        // assume false otherwise
        return false;
    }

    /**
     * @return int
     */
    protected function directorySize(string $directory)
    {
        $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS);
        $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);

        $size = 0;
        foreach ($ri as $file) {
            if ($file->isFile()) {
                $size += $file->getSize();
            }
        }

        return $size;
    }

    /**
     * @return ProcessExecutor
     */
    protected function getProcess()
    {
        if (null === $this->processExecutor) {
            $this->processExecutor = new ProcessExecutor();
        }

        return $this->processExecutor;
    }

    /**
     * delete symbolic link implementation (commonly known as "unlink()")
     *
     * symbolic links on windows which link to directories need rmdir instead of unlink
     */
    private function unlinkImplementation(string $path): bool
    {
        if (Platform::isWindows() && is_dir($path) && is_link($path)) {
            return rmdir($path);
        }

        return unlink($path);
    }

    /**
     * Creates a relative symlink from $link to $target
     *
     * @param  string $target The path of the binary file to be symlinked
     * @param  string $link   The path where the symlink should be created
     * @return bool
     */
    public function relativeSymlink(string $target, string $link)
    {
        if (!function_exists('symlink')) {
            return false;
        }

        $cwd = Platform::getCwd();

        $relativePath = $this->findShortestPath($link, $target);
        chdir(\dirname($link));
        $result = @symlink($relativePath, $link);

        chdir($cwd);

        return $result;
    }

    /**
     * return true if that directory is a symlink.
     *
     * @return bool
     */
    public function isSymlinkedDirectory(string $directory)
    {
        if (!is_dir($directory)) {
            return false;
        }

        $resolved = $this->resolveSymlinkedDirectorySymlink($directory);

        return is_link($resolved);
    }

    private function unlinkSymlinkedDirectory(string $directory): bool
    {
        $resolved = $this->resolveSymlinkedDirectorySymlink($directory);

        return $this->unlink($resolved);
    }

    /**
     * resolve pathname to symbolic link of a directory
     *
     * @param string $pathname directory path to resolve
     *
     * @return string resolved path to symbolic link or original pathname (unresolved)
     */
    private function resolveSymlinkedDirectorySymlink(string $pathname): string
    {
        if (!is_dir($pathname)) {
            return $pathname;
        }

        $resolved = rtrim($pathname, '/');

        if (0 === \strlen($resolved)) {
            return $pathname;
        }

        return $resolved;
    }

    /**
     * Creates an NTFS junction.
     *
     * @return void
     */
    public function junction(string $target, string $junction)
    {
        if (!Platform::isWindows()) {
            throw new \LogicException(sprintf('Function %s is not available on non-Windows platform', __CLASS__));
        }
        if (!is_dir($target)) {
            throw new IOException(sprintf('Cannot junction to "%s" as it is not a directory.', $target), 0, null, $target);
        }

        // Removing any previously junction to ensure clean execution.
        if (!is_dir($junction) || $this->isJunction($junction)) {
            @rmdir($junction);
        }

        $cmd = ['mklink', '/J', str_replace('/', DIRECTORY_SEPARATOR, $junction), Platform::realpath($target)];
        if ($this->getProcess()->execute($cmd, $output) !== 0) {
            throw new IOException(sprintf('Failed to create junction to "%s" at "%s".', $target, $junction), 0, null, $target);
        }
        clearstatcache(true, $junction);
    }

    /**
     * Returns whether the target directory is a Windows NTFS Junction.
     *
     * We test if the path is a directory and not an ordinary link, then check
     * that the mode value returned from lstat (which gives the status of the
     * link itself) is not a directory, by replicating the POSIX S_ISDIR test.
     *
     * This logic works because PHP does not set the mode value for a junction,
     * since there is no universal file type flag for it. Unfortunately an
     * uninitialized variable in PHP prior to 7.2.16 and 7.3.3 may cause a
     * random value to be returned. See https://bugs.php.net/bug.php?id=77552
     *
     * If this random value passes the S_ISDIR test, then a junction will not be
     * detected and a recursive delete operation could lead to loss of data in
     * the target directory. Note that Windows rmdir can handle this situation
     * and will only delete the junction (from Windows 7 onwards).
     *
     * @param  string $junction Path to check.
     * @return bool
     */
    public function isJunction(string $junction)
    {
        if (!Platform::isWindows()) {
            return false;
        }

        // Important to clear all caches first
        clearstatcache(true, $junction);

        if (!is_dir($junction) || is_link($junction)) {
            return false;
        }

        $stat = lstat($junction);

        // S_ISDIR test (S_IFDIR is 0x4000, S_IFMT is 0xF000 bitmask)
        return is_array($stat) ? 0x4000 !== ($stat['mode'] & 0xF000) : false;
    }

    /**
     * Removes a Windows NTFS junction.
     *
     * @return bool
     */
    public function removeJunction(string $junction)
    {
        if (!Platform::isWindows()) {
            return false;
        }
        $junction = rtrim(str_replace('/', DIRECTORY_SEPARATOR, $junction), DIRECTORY_SEPARATOR);
        if (!$this->isJunction($junction)) {
            throw new IOException(sprintf('%s is not a junction and thus cannot be removed as one', $junction));
        }

        return $this->rmdir($junction);
    }

    /**
     * @return int|false
     */
    public function filePutContentsIfModified(string $path, string $content)
    {
        $currentContent = Silencer::call('file_get_contents', $path);
        if (false === $currentContent || $currentContent !== $content) {
            return file_put_contents($path, $content);
        }

        return 0;
    }

    /**
     * Copy file using stream_copy_to_stream to work around https://bugs.php.net/bug.php?id=6463
     */
    public function safeCopy(string $source, string $target): void
    {
        if (!file_exists($target) || !file_exists($source) || !$this->filesAreEqual($source, $target)) {
            $sourceHandle = fopen($source, 'r');
            assert($sourceHandle !== false, 'Could not open "'.$source.'" for reading.');
            $targetHandle = fopen($target, 'w+');
            assert($targetHandle !== false, 'Could not open "'.$target.'" for writing.');

            stream_copy_to_stream($sourceHandle, $targetHandle);
            fclose($sourceHandle);
            fclose($targetHandle);

            touch($target, (int) filemtime($source), (int) fileatime($source));
        }
    }

    /**
     * compare 2 files
     * https://stackoverflow.com/questions/3060125/can-i-use-file-get-contents-to-compare-two-files
     */
    private function filesAreEqual(string $a, string $b): bool
    {
        // Check if filesize is different
        if (filesize($a) !== filesize($b)) {
            return false;
        }

        // Check if content is different
        $aHandle = fopen($a, 'rb');
        assert($aHandle !== false, 'Could not open "'.$a.'" for reading.');
        $bHandle = fopen($b, 'rb');
        assert($bHandle !== false, 'Could not open "'.$b.'" for reading.');

        $result = true;
        while (!feof($aHandle)) {
            if (fread($aHandle, 8192) !== fread($bHandle, 8192)) {
                $result = false;
                break;
            }
        }

        fclose($aHandle);
        fclose($bHandle);

        return $result;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Pcre\Preg;

/**
 * @author Till Klampaeckel <till@php.net>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Svn
{
    private const MAX_QTY_AUTH_TRIES = 5;

    /**
     * @var ?array{username: string, password: string}
     */
    protected $credentials;

    /**
     * @var bool
     */
    protected $hasAuth;

    /**
     * @var IOInterface
     */
    protected $io;

    /**
     * @var string
     */
    protected $url;

    /**
     * @var bool
     */
    protected $cacheCredentials = true;

    /**
     * @var ProcessExecutor
     */
    protected $process;

    /**
     * @var int
     */
    protected $qtyAuthTries = 0;

    /**
     * @var Config
     */
    protected $config;

    /**
     * @var string|null
     */
    private static $version;

    public function __construct(string $url, IOInterface $io, Config $config, ?ProcessExecutor $process = null)
    {
        $this->url = $url;
        $this->io = $io;
        $this->config = $config;
        $this->process = $process ?: new ProcessExecutor($io);
    }

    public static function cleanEnv(): void
    {
        // clean up env for OSX, see https://github.com/composer/composer/issues/2146#issuecomment-35478940
        Platform::clearEnv('DYLD_LIBRARY_PATH');
    }

    /**
     * Execute an SVN remote command and try to fix up the process with credentials
     * if necessary.
     *
     * @param non-empty-list<string> $command SVN command to run
     * @param string  $url     SVN url
     * @param ?string $cwd     Working directory
     * @param ?string $path    Target for a checkout
     * @param bool    $verbose Output all output to the user
     *
     * @throws \RuntimeException
     */
    public function execute(array $command, string $url, ?string $cwd = null, ?string $path = null, bool $verbose = false): string
    {
        // Ensure we are allowed to use this URL by config
        $this->config->prohibitUrlByConfig($url, $this->io);

        return $this->executeWithAuthRetry($command, $cwd, $url, $path, $verbose);
    }

    /**
     * Execute an SVN local command and try to fix up the process with credentials
     * if necessary.
     *
     * @param non-empty-list<string> $command SVN command to run
     * @param string $path    Path argument passed thru to the command
     * @param string $cwd     Working directory
     * @param bool   $verbose Output all output to the user
     *
     * @throws \RuntimeException
     */
    public function executeLocal(array $command, string $path, ?string $cwd = null, bool $verbose = false): string
    {
        // A local command has no remote url
        return $this->executeWithAuthRetry($command, $cwd, '', $path, $verbose);
    }

    /**
     * @param non-empty-list<string> $svnCommand
     */
    private function executeWithAuthRetry(array $svnCommand, ?string $cwd, string $url, ?string $path, bool $verbose): ?string
    {
        // Regenerate the command at each try, to use the newly user-provided credentials
        $command = $this->getCommand($svnCommand, $url, $path);

        $output = null;
        $io = $this->io;
        $handler = static function ($type, $buffer) use (&$output, $io, $verbose) {
            if ($type !== 'out') {
                return null;
            }
            if (strpos($buffer, 'Redirecting to URL ') === 0) {
                return null;
            }
            $output .= $buffer;
            if ($verbose) {
                $io->writeError($buffer, false);
            }
        };
        $status = $this->process->execute($command, $handler, $cwd);
        if (0 === $status) {
            return $output;
        }

        $errorOutput = $this->process->getErrorOutput();
        $fullOutput = trim(implode("\n", [$output, $errorOutput]));

        // the error is not auth-related
        if (false === stripos($fullOutput, 'Could not authenticate to server:')
            && false === stripos($fullOutput, 'authorization failed')
            && false === stripos($fullOutput, 'svn: E170001:')
            && false === stripos($fullOutput, 'svn: E215004:')) {
            throw new \RuntimeException($fullOutput);
        }

        if (!$this->hasAuth()) {
            $this->doAuthDance();
        }

        // try to authenticate if maximum quantity of tries not reached
        if ($this->qtyAuthTries++ < self::MAX_QTY_AUTH_TRIES) {
            // restart the process
            return $this->executeWithAuthRetry($svnCommand, $cwd, $url, $path, $verbose);
        }

        throw new \RuntimeException(
            'wrong credentials provided ('.$fullOutput.')'
        );
    }

    public function setCacheCredentials(bool $cacheCredentials): void
    {
        $this->cacheCredentials = $cacheCredentials;
    }

    /**
     * Repositories requests credentials, let's put them in.
     *
     * @throws \RuntimeException
     */
    protected function doAuthDance(): Svn
    {
        // cannot ask for credentials in non interactive mode
        if (!$this->io->isInteractive()) {
            throw new \RuntimeException(
                'can not ask for authentication in non interactive mode'
            );
        }

        $this->io->writeError("The Subversion server ({$this->url}) requested credentials:");

        $this->hasAuth = true;
        $this->credentials = [
            'username' => (string) $this->io->ask("Username: ", ''),
            'password' => (string) $this->io->askAndHideAnswer("Password: "),
        ];

        $this->cacheCredentials = $this->io->askConfirmation("Should Subversion cache these credentials? (yes/no) ");

        return $this;
    }

    /**
     * A method to create the svn commands run.
     *
     * @param non-empty-list<string> $cmd  Usually 'svn ls' or something like that.
     * @param string $url  Repo URL.
     * @param string $path Target for a checkout
     *
     * @return non-empty-list<string>
     */
    protected function getCommand(array $cmd, string $url, ?string $path = null): array
    {
        $cmd = array_merge(
            $cmd,
            ['--non-interactive'],
            $this->getCredentialArgs(),
            ['--', $url]
        );

        if ($path !== null) {
            $cmd[] = $path;
        }

        return $cmd;
    }

    /**
     * Return the credential string for the svn command.
     *
     * Adds --no-auth-cache when credentials are present.
     *
     * @return list<string>
     */
    protected function getCredentialArgs(): array
    {
        if (!$this->hasAuth()) {
            return [];
        }

        return array_merge(
            $this->getAuthCacheArgs(),
            ['--username', $this->getUsername(), '--password', $this->getPassword()]
        );
    }

    /**
     * Get the password for the svn command. Can be empty.
     *
     * @throws \LogicException
     */
    protected function getPassword(): string
    {
        if ($this->credentials === null) {
            throw new \LogicException("No svn auth detected.");
        }

        return $this->credentials['password'];
    }

    /**
     * Get the username for the svn command.
     *
     * @throws \LogicException
     */
    protected function getUsername(): string
    {
        if ($this->credentials === null) {
            throw new \LogicException("No svn auth detected.");
        }

        return $this->credentials['username'];
    }

    /**
     * Detect Svn Auth.
     */
    protected function hasAuth(): bool
    {
        if (null !== $this->hasAuth) {
            return $this->hasAuth;
        }

        if (false === $this->createAuthFromConfig()) {
            $this->createAuthFromUrl();
        }

        return (bool) $this->hasAuth;
    }

    /**
     * Return the no-auth-cache switch.
     *
     * @return list<string>
     */
    protected function getAuthCacheArgs(): array
    {
        return $this->cacheCredentials ? [] : ['--no-auth-cache'];
    }

    /**
     * Create the auth params from the configuration file.
     */
    private function createAuthFromConfig(): bool
    {
        if (!$this->config->has('http-basic')) {
            return $this->hasAuth = false;
        }

        $authConfig = $this->config->get('http-basic');

        $host = parse_url($this->url, PHP_URL_HOST);
        if (isset($authConfig[$host])) {
            $this->credentials = [
                'username' => $authConfig[$host]['username'],
                'password' => $authConfig[$host]['password'],
            ];

            return $this->hasAuth = true;
        }

        return $this->hasAuth = false;
    }

    /**
     * Create the auth params from the url
     */
    private function createAuthFromUrl(): bool
    {
        $uri = parse_url($this->url);
        if (empty($uri['user'])) {
            return $this->hasAuth = false;
        }

        $this->credentials = [
            'username' => $uri['user'],
            'password' => !empty($uri['pass']) ? $uri['pass'] : '',
        ];

        return $this->hasAuth = true;
    }

    /**
     * Returns the version of the svn binary contained in PATH
     */
    public function binaryVersion(): ?string
    {
        if (!self::$version) {
            if (0 === $this->process->execute(['svn', '--version'], $output)) {
                if (Preg::isMatch('{(\d+(?:\.\d+)+)}', $output, $match)) {
                    self::$version = $match[1];
                }
            }
        }

        return self::$version;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Downloader\TransportException;
use Composer\Pcre\Preg;
use Composer\Util\Http\Response;
use Composer\Util\Http\CurlDownloader;
use Composer\Composer;
use Composer\Package\Version\VersionParser;
use Composer\Semver\Constraint\Constraint;
use Composer\Exception\IrrecoverableDownloadException;
use React\Promise\Promise;
use React\Promise\PromiseInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @phpstan-type Request array{url: non-empty-string, options: mixed[], copyTo: string|null}
 * @phpstan-type Job array{id: int, status: int, request: Request, sync: bool, origin: string, resolve?: callable, reject?: callable, curl_id?: int, response?: Response, exception?: \Throwable}
 */
class HttpDownloader
{
    private const STATUS_QUEUED = 1;
    private const STATUS_STARTED = 2;
    private const STATUS_COMPLETED = 3;
    private const STATUS_FAILED = 4;
    private const STATUS_ABORTED = 5;

    /** @var IOInterface */
    private $io;
    /** @var Config */
    private $config;
    /** @var array<Job> */
    private $jobs = [];
    /** @var mixed[] */
    private $options = [];
    /** @var int */
    private $runningJobs = 0;
    /** @var int */
    private $maxJobs = 12;
    /** @var ?CurlDownloader */
    private $curl;
    /** @var ?RemoteFilesystem */
    private $rfs;
    /** @var int */
    private $idGen = 0;
    /** @var bool */
    private $disabled;
    /** @var bool */
    private $allowAsync = false;

    /**
     * @param IOInterface $io         The IO instance
     * @param Config      $config     The config
     * @param mixed[]     $options    The options
     */
    public function __construct(IOInterface $io, Config $config, array $options = [], bool $disableTls = false)
    {
        $this->io = $io;

        $this->disabled = (bool) Platform::getEnv('COMPOSER_DISABLE_NETWORK');

        // Setup TLS options
        // The cafile option can be set via config.json
        if ($disableTls === false) {
            $this->options = StreamContextFactory::getTlsDefaults($options, $io);
        }

        // handle the other externally set options normally.
        $this->options = array_replace_recursive($this->options, $options);
        $this->config = $config;

        if (self::isCurlEnabled()) {
            $this->curl = new CurlDownloader($io, $config, $options, $disableTls);
        }

        $this->rfs = new RemoteFilesystem($io, $config, $options, $disableTls);

        if (is_numeric($maxJobs = Platform::getEnv('COMPOSER_MAX_PARALLEL_HTTP'))) {
            $this->maxJobs = max(1, min(50, (int) $maxJobs));
        }
    }

    /**
     * Download a file synchronously
     *
     * @param  string             $url     URL to download
     * @param  mixed[]            $options Stream context options e.g. https://www.php.net/manual/en/context.http.php
     *                                     although not all options are supported when using the default curl downloader
     * @throws TransportException
     * @return Response
     */
    public function get(string $url, array $options = [])
    {
        if ('' === $url) {
            throw new \InvalidArgumentException('$url must not be an empty string');
        }
        [$job, $promise] = $this->addJob(['url' => $url, 'options' => $options, 'copyTo' => null], true);
        $promise->then(null, static function (\Throwable $e) {
            // suppress error as it is rethrown to the caller by getResponse() a few lines below
        });
        $this->wait($job['id']);

        $response = $this->getResponse($job['id']);

        return $response;
    }

    /**
     * Create an async download operation
     *
     * @param  string             $url     URL to download
     * @param  mixed[]            $options Stream context options e.g. https://www.php.net/manual/en/context.http.php
     *                                     although not all options are supported when using the default curl downloader
     * @throws TransportException
     * @return PromiseInterface
     * @phpstan-return PromiseInterface<Response>
     */
    public function add(string $url, array $options = [])
    {
        if ('' === $url) {
            throw new \InvalidArgumentException('$url must not be an empty string');
        }
        [, $promise] = $this->addJob(['url' => $url, 'options' => $options, 'copyTo' => null]);

        return $promise;
    }

    /**
     * Copy a file synchronously
     *
     * @param  string             $url     URL to download
     * @param  string             $to      Path to copy to
     * @param  mixed[]            $options Stream context options e.g. https://www.php.net/manual/en/context.http.php
     *                                     although not all options are supported when using the default curl downloader
     * @throws TransportException
     * @return Response
     */
    public function copy(string $url, string $to, array $options = [])
    {
        if ('' === $url) {
            throw new \InvalidArgumentException('$url must not be an empty string');
        }
        [$job] = $this->addJob(['url' => $url, 'options' => $options, 'copyTo' => $to], true);
        $this->wait($job['id']);

        return $this->getResponse($job['id']);
    }

    /**
     * Create an async copy operation
     *
     * @param  string             $url     URL to download
     * @param  string             $to      Path to copy to
     * @param  mixed[]            $options Stream context options e.g. https://www.php.net/manual/en/context.http.php
     *                                     although not all options are supported when using the default curl downloader
     * @throws TransportException
     * @return PromiseInterface
     * @phpstan-return PromiseInterface<Response>
     */
    public function addCopy(string $url, string $to, array $options = [])
    {
        if ('' === $url) {
            throw new \InvalidArgumentException('$url must not be an empty string');
        }
        [, $promise] = $this->addJob(['url' => $url, 'options' => $options, 'copyTo' => $to]);

        return $promise;
    }

    /**
     * Retrieve the options set in the constructor
     *
     * @return mixed[] Options
     */
    public function getOptions()
    {
        return $this->options;
    }

    /**
     * Merges new options
     *
     * @param  mixed[] $options
     * @return void
     */
    public function setOptions(array $options)
    {
        $this->options = array_replace_recursive($this->options, $options);
    }

    /**
     * @phpstan-param Request $request
     * @return array{Job, PromiseInterface}
     * @phpstan-return array{Job, PromiseInterface<Response>}
     */
    private function addJob(array $request, bool $sync = false): array
    {
        $request['options'] = array_replace_recursive($this->options, $request['options']);

        /** @var Job */
        $job = [
            'id' => $this->idGen++,
            'status' => self::STATUS_QUEUED,
            'request' => $request,
            'sync' => $sync,
            'origin' => Url::getOrigin($this->config, $request['url']),
        ];

        if (!$sync && !$this->allowAsync) {
            throw new \LogicException('You must use the HttpDownloader instance which is part of a Composer\Loop instance to be able to run async http requests');
        }

        // capture username/password from URL if there is one
        if (Preg::isMatchStrictGroups('{^https?://([^:/]+):([^@/]+)@([^/]+)}i', $request['url'], $match)) {
            $this->io->setAuthentication($job['origin'], rawurldecode($match[1]), rawurldecode($match[2]));
        }

        $rfs = $this->rfs;

        if ($this->canUseCurl($job)) {
            $resolver = static function ($resolve, $reject) use (&$job): void {
                $job['status'] = HttpDownloader::STATUS_QUEUED;
                $job['resolve'] = $resolve;
                $job['reject'] = $reject;
            };
        } else {
            $resolver = static function ($resolve, $reject) use (&$job, $rfs): void {
                // start job
                $url = $job['request']['url'];
                $options = $job['request']['options'];

                $job['status'] = HttpDownloader::STATUS_STARTED;

                if ($job['request']['copyTo']) {
                    $rfs->copy($job['origin'], $url, $job['request']['copyTo'], false /* TODO progress */, $options);

                    $headers = $rfs->getLastHeaders();
                    $response = new Response($job['request'], $rfs->findStatusCode($headers), $headers, $job['request']['copyTo'].'~');

                    $resolve($response);
                } else {
                    $body = $rfs->getContents($job['origin'], $url, false /* TODO progress */, $options);
                    $headers = $rfs->getLastHeaders();
                    $response = new Response($job['request'], $rfs->findStatusCode($headers), $headers, $body);

                    $resolve($response);
                }
            };
        }

        $curl = $this->curl;

        $canceler = static function () use (&$job, $curl): void {
            if ($job['status'] === HttpDownloader::STATUS_QUEUED) {
                $job['status'] = HttpDownloader::STATUS_ABORTED;
            }
            if ($job['status'] !== HttpDownloader::STATUS_STARTED) {
                return;
            }
            $job['status'] = HttpDownloader::STATUS_ABORTED;
            if (isset($job['curl_id'])) {
                $curl->abortRequest($job['curl_id']);
            }
            throw new IrrecoverableDownloadException('Download of ' . Url::sanitize($job['request']['url']) . ' canceled');
        };

        $promise = new Promise($resolver, $canceler);
        $promise = $promise->then(function ($response) use (&$job) {
            $job['status'] = HttpDownloader::STATUS_COMPLETED;
            $job['response'] = $response;

            $this->markJobDone();

            return $response;
        }, function ($e) use (&$job): void {
            $job['status'] = HttpDownloader::STATUS_FAILED;
            $job['exception'] = $e;

            $this->markJobDone();

            throw $e;
        });
        $this->jobs[$job['id']] = &$job;

        if ($this->runningJobs < $this->maxJobs) {
            $this->startJob($job['id']);
        }

        return [$job, $promise];
    }

    private function startJob(int $id): void
    {
        $job = &$this->jobs[$id];
        if ($job['status'] !== self::STATUS_QUEUED) {
            return;
        }

        // start job
        $job['status'] = self::STATUS_STARTED;
        $this->runningJobs++;

        assert(isset($job['resolve']));
        assert(isset($job['reject']));

        $resolve = $job['resolve'];
        $reject = $job['reject'];
        $url = $job['request']['url'];
        $options = $job['request']['options'];
        $origin = $job['origin'];

        if ($this->disabled) {
            if (isset($job['request']['options']['http']['header']) && false !== stripos(implode('', $job['request']['options']['http']['header']), 'if-modified-since')) {
                $resolve(new Response(['url' => $url], 304, [], ''));
            } else {
                $e = new TransportException('Network disabled, request canceled: '.Url::sanitize($url), 499);
                $e->setStatusCode(499);
                $reject($e);
            }

            return;
        }

        try {
            if ($job['request']['copyTo']) {
                $job['curl_id'] = $this->curl->download($resolve, $reject, $origin, $url, $options, $job['request']['copyTo']);
            } else {
                $job['curl_id'] = $this->curl->download($resolve, $reject, $origin, $url, $options);
            }
        } catch (\Exception $exception) {
            $reject($exception);
        }
    }

    private function markJobDone(): void
    {
        $this->runningJobs--;
    }

    /**
     * Wait for current async download jobs to complete
     *
     * @param int|null $index For internal use only, the job id
     *
     * @return void
     */
    public function wait(?int $index = null)
    {
        do {
            $jobCount = $this->countActiveJobs($index);
        } while ($jobCount);
    }

    /**
     * @internal
     */
    public function enableAsync(): void
    {
        $this->allowAsync = true;
    }

    /**
     * @internal
     *
     * @param  int|null $index For internal use only, the job id
     * @return int      number of active (queued or started) jobs
     */
    public function countActiveJobs(?int $index = null): int
    {
        if ($this->runningJobs < $this->maxJobs) {
            foreach ($this->jobs as $job) {
                if ($job['status'] === self::STATUS_QUEUED && $this->runningJobs < $this->maxJobs) {
                    $this->startJob($job['id']);
                }
            }
        }

        if ($this->curl) {
            $this->curl->tick();
        }

        if (null !== $index) {
            return $this->jobs[$index]['status'] < self::STATUS_COMPLETED ? 1 : 0;
        }

        $active = 0;
        foreach ($this->jobs as $job) {
            if ($job['status'] < self::STATUS_COMPLETED) {
                $active++;
            } elseif (!$job['sync']) {
                unset($this->jobs[$job['id']]);
            }
        }

        return $active;
    }

    /**
     * @param  int $index Job id
     */
    private function getResponse(int $index): Response
    {
        if (!isset($this->jobs[$index])) {
            throw new \LogicException('Invalid request id');
        }

        if ($this->jobs[$index]['status'] === self::STATUS_FAILED) {
            assert(isset($this->jobs[$index]['exception']));
            throw $this->jobs[$index]['exception'];
        }

        if (!isset($this->jobs[$index]['response'])) {
            throw new \LogicException('Response not available yet, call wait() first');
        }

        $resp = $this->jobs[$index]['response'];

        unset($this->jobs[$index]);

        return $resp;
    }

    /**
     * @internal
     *
     * @param  array{warning?: string, info?: string, warning-versions?: string, info-versions?: string, warnings?: array<array{versions: string, message: string}>, infos?: array<array{versions: string, message: string}>} $data
     */
    public static function outputWarnings(IOInterface $io, string $url, $data): void
    {
        $cleanMessage = static function ($msg) use ($io) {
            if (!$io->isDecorated()) {
                $msg = Preg::replace('{'.chr(27).'\\[[;\d]*m}u', '', $msg);
            }

            return $msg;
        };

        // legacy warning/info keys
        foreach (['warning', 'info'] as $type) {
            if (empty($data[$type])) {
                continue;
            }

            if (!empty($data[$type . '-versions'])) {
                $versionParser = new VersionParser();
                $constraint = $versionParser->parseConstraints($data[$type . '-versions']);
                $composer = new Constraint('==', $versionParser->normalize(Composer::getVersion()));
                if (!$constraint->matches($composer)) {
                    continue;
                }
            }

            $io->writeError('<'.$type.'>'.ucfirst($type).' from '.Url::sanitize($url).': '.$cleanMessage($data[$type]).'</'.$type.'>');
        }

        // modern Composer 2.2+ format with support for multiple warning/info messages
        foreach (['warnings', 'infos'] as $key) {
            if (empty($data[$key])) {
                continue;
            }

            $versionParser = new VersionParser();
            foreach ($data[$key] as $spec) {
                $type = substr($key, 0, -1);
                $constraint = $versionParser->parseConstraints($spec['versions']);
                $composer = new Constraint('==', $versionParser->normalize(Composer::getVersion()));
                if (!$constraint->matches($composer)) {
                    continue;
                }

                $io->writeError('<'.$type.'>'.ucfirst($type).' from '.Url::sanitize($url).': '.$cleanMessage($spec['message']).'</'.$type.'>');
            }
        }
    }

    /**
     * @internal
     *
     * @return ?string[]
     */
    public static function getExceptionHints(\Throwable $e): ?array
    {
        if (!$e instanceof TransportException) {
            return null;
        }

        if (
            false !== strpos($e->getMessage(), 'Resolving timed out')
            || false !== strpos($e->getMessage(), 'Could not resolve host')
        ) {
            Silencer::suppress();
            $testConnectivity = file_get_contents('https://8.8.8.8', false, stream_context_create([
                'ssl' => ['verify_peer' => false],
                'http' => ['follow_location' => false, 'ignore_errors' => true],
            ]));
            Silencer::restore();
            if (false !== $testConnectivity) {
                return [
                    '<error>The following exception probably indicates you have misconfigured DNS resolver(s)</error>',
                ];
            }

            return [
                '<error>The following exception probably indicates you are offline or have misconfigured DNS resolver(s)</error>',
            ];
        }

        return null;
    }

    /**
     * @param  Job  $job
     */
    private function canUseCurl(array $job): bool
    {
        if (!$this->curl) {
            return false;
        }

        if (!Preg::isMatch('{^https?://}i', $job['request']['url'])) {
            return false;
        }

        if (!empty($job['request']['options']['ssl']['allow_self_signed'])) {
            return false;
        }

        return true;
    }

    /**
     * @internal
     */
    public static function isCurlEnabled(): bool
    {
        return \extension_loaded('curl') && \function_exists('curl_multi_exec') && \function_exists('curl_multi_init');
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Downloader\TransportException;
use Composer\Pcre\Preg;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class AuthHelper
{
    /** @var IOInterface */
    protected $io;
    /** @var Config */
    protected $config;
    /** @var array<string, string> Map of origins to message displayed */
    private $displayedOriginAuthentications = [];
    /** @var array<string, bool> Map of URLs and whether they already retried with authentication from Bitbucket */
    private $bitbucketRetry = [];

    public function __construct(IOInterface $io, Config $config)
    {
        $this->io = $io;
        $this->config = $config;
    }

    /**
     * @param 'prompt'|bool $storeAuth
     */
    public function storeAuth(string $origin, $storeAuth): void
    {
        $store = false;
        $configSource = $this->config->getAuthConfigSource();
        if ($storeAuth === true) {
            $store = $configSource;
        } elseif ($storeAuth === 'prompt') {
            $answer = $this->io->askAndValidate(
                'Do you want to store credentials for '.$origin.' in '.$configSource->getName().' ? [Yn] ',
                static function ($value): string {
                    $input = strtolower(substr(trim($value), 0, 1));
                    if (in_array($input, ['y','n'])) {
                        return $input;
                    }
                    throw new \RuntimeException('Please answer (y)es or (n)o');
                },
                null,
                'y'
            );

            if ($answer === 'y') {
                $store = $configSource;
            }
        }
        if ($store) {
            $store->addConfigSetting(
                'http-basic.'.$origin,
                $this->io->getAuthentication($origin)
            );
        }
    }

    /**
     * @param  int         $statusCode HTTP status code that triggered this call
     * @param  string|null $reason     a message/description explaining why this was called
     * @param  string[]    $headers
     * @param  int         $retryCount the amount of retries already done on this URL
     * @return array       containing retry (bool) and storeAuth (string|bool) keys, if retry is true the request should be
     *                                retried, if storeAuth is true then on a successful retry the authentication should be persisted to auth.json
     * @phpstan-return array{retry: bool, storeAuth: 'prompt'|bool}
     */
    public function promptAuthIfNeeded(string $url, string $origin, int $statusCode, ?string $reason = null, array $headers = [], int $retryCount = 0, ?string $responseBody = null): array
    {
        $storeAuth = false;

        if (in_array($origin, $this->config->get('github-domains'), true)) {
            $gitHubUtil = new GitHub($this->io, $this->config, null);
            $message = "\n";

            $rateLimited = $gitHubUtil->isRateLimited($headers);
            $requiresSso = $gitHubUtil->requiresSso($headers);

            if ($requiresSso) {
                $ssoUrl = $gitHubUtil->getSsoUrl($headers);
                $message = 'GitHub API token requires SSO authorization. Authorize this token at ' . $ssoUrl . "\n";
                $this->io->writeError($message);
                if (!$this->io->isInteractive()) {
                    throw new TransportException('Could not authenticate against ' . $origin, 403);
                }
                $this->io->ask('After authorizing your token, confirm that you would like to retry the request');

                return ['retry' => true, 'storeAuth' => $storeAuth];
            }

            if ($rateLimited) {
                $rateLimit = $gitHubUtil->getRateLimit($headers);
                if ($this->io->hasAuthentication($origin)) {
                    $message = 'Review your configured GitHub OAuth token or enter a new one to go over the API rate limit.';
                } else {
                    $message = 'Create a GitHub OAuth token to go over the API rate limit.';
                }

                $message = sprintf(
                    'GitHub API limit (%d calls/hr) is exhausted, could not fetch '.$url.'. '.$message.' You can also wait until %s for the rate limit to reset.',
                    $rateLimit['limit'],
                    $rateLimit['reset']
                )."\n";
            } else {
                // Try to extract a more specific error message from GitHub's API response
                $gitHubApiMessage = null;
                if ($responseBody !== null) {
                    $decoded = json_decode($responseBody, true);
                    if (is_array($decoded) && isset($decoded['message']) && is_string($decoded['message'])) {
                        $gitHubApiMessage = $decoded['message'];
                    }
                }

                if ($gitHubApiMessage !== null) {
                    $message .= 'Could not fetch '.$url.': '.$gitHubApiMessage;
                } else {
                    $message .= 'Could not fetch '.$url.', please ';
                    if ($this->io->hasAuthentication($origin)) {
                        $message .= 'review your configured GitHub OAuth token or enter a new one to access private repos';
                    } else {
                        $message .= 'create a GitHub OAuth token to access private repos';
                    }
                }
            }

            if (!$gitHubUtil->authorizeOAuth($origin)
                && (!$this->io->isInteractive() || !$gitHubUtil->authorizeOAuthInteractively($origin, $message))
            ) {
                throw new TransportException('Could not authenticate against '.$origin, 401);
            }
        } elseif (in_array($origin, $this->config->get('gitlab-domains'), true)) {
            $message = "\n".'Could not fetch '.$url.', enter your ' . $origin . ' credentials ' .($statusCode === 401 ? 'to access private repos' : 'to go over the API rate limit');
            $gitLabUtil = new GitLab($this->io, $this->config, null);

            $auth = null;
            if ($this->io->hasAuthentication($origin)) {
                $auth = $this->io->getAuthentication($origin);
                if (in_array($auth['password'], ['gitlab-ci-token', 'private-token', 'oauth2'], true)) {
                    throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $statusCode);
                }
            }

            if (!$gitLabUtil->authorizeOAuth($origin)
                && (!$this->io->isInteractive() || !$gitLabUtil->authorizeOAuthInteractively(parse_url($url, PHP_URL_SCHEME), $origin, $message))
            ) {
                throw new TransportException('Could not authenticate against '.$origin, 401);
            }

            if ($auth !== null && $this->io->hasAuthentication($origin)) {
                if ($auth === $this->io->getAuthentication($origin)) {
                    throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $statusCode);
                }
            }
        } elseif ($origin === 'bitbucket.org' || $origin === 'api.bitbucket.org') {
            $askForOAuthToken = true;
            $origin = 'bitbucket.org';
            if ($this->io->hasAuthentication($origin)) {
                $auth = $this->io->getAuthentication($origin);
                if ($auth['username'] !== 'x-token-auth') {
                    $bitbucketUtil = new Bitbucket($this->io, $this->config);
                    $accessToken = $bitbucketUtil->requestToken($origin, $auth['username'], $auth['password']);
                    if (!empty($accessToken)) {
                        $this->io->setAuthentication($origin, 'x-token-auth', $accessToken);
                        $askForOAuthToken = false;
                    }
                } elseif (!isset($this->bitbucketRetry[$url])) {
                    // when multiple requests fire at the same time, they will all fail and the first one resets the token to be correct above but then the others
                    // reach the code path and without this fallback they would end up throwing below
                    // see https://github.com/composer/composer/pull/11464 for more details
                    $askForOAuthToken = false;
                    $this->bitbucketRetry[$url] = true;
                } else {
                    throw new TransportException('Could not authenticate against ' . $origin, 401);
                }
            }

            if ($askForOAuthToken) {
                $message = "\n".'Could not fetch ' . $url . ', please create a bitbucket OAuth token to ' . (($statusCode === 401 || $statusCode === 403) ? 'access private repos' : 'go over the API rate limit');
                $bitBucketUtil = new Bitbucket($this->io, $this->config);
                if (!$bitBucketUtil->authorizeOAuth($origin)
                    && (!$this->io->isInteractive() || !$bitBucketUtil->authorizeOAuthInteractively($origin, $message))
                ) {
                    throw new TransportException('Could not authenticate against ' . $origin, 401);
                }
            }
        } else {
            // 404s are only handled for github
            if ($statusCode === 404) {
                return ['retry' => false, 'storeAuth' => false];
            }

            // fail if the console is not interactive
            if (!$this->io->isInteractive()) {
                if ($statusCode === 401) {
                    $message = "The '" . $url . "' URL required authentication (HTTP 401).\nYou must be using the interactive console to authenticate";
                } elseif ($statusCode === 403) {
                    $message = "The '" . $url . "' URL could not be accessed (HTTP 403): " . $reason;
                } else {
                    $message = "Unknown error code '" . $statusCode . "', reason: " . $reason;
                }

                throw new TransportException($message, $statusCode);
            }

            // fail if we already have auth
            if ($this->io->hasAuthentication($origin)) {
                // if two or more requests are started together for the same host, and the first
                // received authentication already, we let the others retry before failing them
                if ($retryCount === 0) {
                    return ['retry' => true, 'storeAuth' => false];
                }

                throw new TransportException("Invalid credentials (HTTP $statusCode) for '$url', aborting.", $statusCode);
            }

            $this->io->writeError('    Authentication required (<info>'.$origin.'</info>):');
            $username = $this->io->ask('      Username: ');
            $password = $this->io->askAndHideAnswer('      Password: ');
            $this->io->setAuthentication($origin, $username, $password);
            $storeAuth = $this->config->get('store-auths');
        }

        return ['retry' => true, 'storeAuth' => $storeAuth];
    }

    /**
     * @deprecated use addAuthenticationOptions instead
     *
     * @param string[] $headers
     *
     * @return string[] updated headers array
     */
    public function addAuthenticationHeader(array $headers, string $origin, string $url): array
    {
        trigger_error('AuthHelper::addAuthenticationHeader is deprecated since Composer 2.9 use addAuthenticationOptions instead.', E_USER_DEPRECATED);

        $options = ['http' => ['header' => &$headers]];
        $options = $this->addAuthenticationOptions($options, $origin, $url);

        return $options['http']['header'];
    }

    /**
     * @param array<string, mixed> $options
     *
     * @return array<string, mixed> updated options
     */
    public function addAuthenticationOptions(array $options, string $origin, string $url): array
    {
        if (!isset($options['http'])) {
            $options['http'] = [];
        }
        if (!isset($options['http']['header'])) {
            $options['http']['header'] = [];
        }
        $headers = &$options['http']['header'];
        if ($this->io->hasAuthentication($origin)) {
            $authenticationDisplayMessage = null;
            $auth = $this->io->getAuthentication($origin);
            if ($auth['password'] === 'bearer') {
                $headers[] = 'Authorization: Bearer '.$auth['username'];
            } elseif ($auth['password'] === 'custom-headers') {
                // Handle custom HTTP headers from auth.json
                $customHeaders = null;
                if (is_string($auth['username'])) {
                    $customHeaders = json_decode($auth['username'], true);
                }
                if (is_array($customHeaders)) {
                    foreach ($customHeaders as $header) {
                        $headers[] = $header;
                    }
                    $authenticationDisplayMessage = 'Using custom HTTP headers for authentication';
                }
            } elseif ('github.com' === $origin && 'x-oauth-basic' === $auth['password']) {
                // only add the access_token if it is actually a github API URL
                if (Preg::isMatch('{^https?://api\.github\.com/}', $url)) {
                    $headers[] = 'Authorization: token '.$auth['username'];
                    $authenticationDisplayMessage = 'Using GitHub token authentication';
                }
            } elseif (
                in_array($auth['password'], ['oauth2', 'private-token', 'gitlab-ci-token'], true)
                && in_array($origin, $this->config->get('gitlab-domains'), true)
            ) {
                if ($auth['password'] === 'oauth2') {
                    $headers[] = 'Authorization: Bearer '.$auth['username'];
                    $authenticationDisplayMessage = 'Using GitLab OAuth token authentication';
                } else {
                    $headers[] = 'PRIVATE-TOKEN: '.$auth['username'];
                    $authenticationDisplayMessage = 'Using GitLab private token authentication';
                }
            } elseif (
                'bitbucket.org' === $origin
                && $url !== Bitbucket::OAUTH2_ACCESS_TOKEN_URL
                && 'x-token-auth' === $auth['username']
            ) {
                if (!$this->isPublicBitBucketDownload($url)) {
                    $headers[] = 'Authorization: Bearer ' . $auth['password'];
                    $authenticationDisplayMessage = 'Using Bitbucket OAuth token authentication';
                }
            } elseif ('client-certificate' === $auth['username']) {
                $options['ssl'] = array_merge($options['ssl'] ?? [], json_decode((string) $auth['password'], true));
                $authenticationDisplayMessage = 'Using SSL client certificate';
            } else {
                $authStr = base64_encode($auth['username'] . ':' . $auth['password']);
                $headers[] = 'Authorization: Basic '.$authStr;
                $authenticationDisplayMessage = 'Using HTTP basic authentication with username "' . $auth['username'] . '"';
            }

            if ($authenticationDisplayMessage && (!isset($this->displayedOriginAuthentications[$origin]) || $this->displayedOriginAuthentications[$origin] !== $authenticationDisplayMessage)) {
                $this->io->writeError($authenticationDisplayMessage, true, IOInterface::DEBUG);
                $this->displayedOriginAuthentications[$origin] = $authenticationDisplayMessage;
            }
        } elseif (in_array($origin, ['api.bitbucket.org', 'api.github.com'], true)) {
            return $this->addAuthenticationOptions($options, str_replace('api.', '', $origin), $url);
        }

        return $options;
    }

    /**
     * @link https://github.com/composer/composer/issues/5584
     *
     * @param string $urlToBitBucketFile URL to a file at bitbucket.org.
     *
     * @return bool Whether the given URL is a public BitBucket download which requires no authentication.
     */
    public function isPublicBitBucketDownload(string $urlToBitBucketFile): bool
    {
        $domain = parse_url($urlToBitBucketFile, PHP_URL_HOST);
        if (strpos($domain, 'bitbucket.org') === false) {
            // Bitbucket downloads are hosted on amazonaws.
            // We do not need to authenticate there at all
            return true;
        }

        $path = parse_url($urlToBitBucketFile, PHP_URL_PATH);

        // Path for a public download follows this pattern /{user}/{repo}/downloads/{whatever}
        // {@link https://blog.bitbucket.org/2009/04/12/new-feature-downloads/}
        $pathParts = explode('/', $path);

        return count($pathParts) >= 4 && $pathParts[3] === 'downloads';
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Symfony\Component\Console\Helper\ProgressBar;
use React\Promise\PromiseInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Loop
{
    /** @var HttpDownloader */
    private $httpDownloader;
    /** @var ProcessExecutor|null */
    private $processExecutor;
    /** @var array<int, array<PromiseInterface<mixed>>> */
    private $currentPromises = [];
    /** @var int */
    private $waitIndex = 0;

    public function __construct(HttpDownloader $httpDownloader, ?ProcessExecutor $processExecutor = null)
    {
        $this->httpDownloader = $httpDownloader;
        $this->httpDownloader->enableAsync();

        $this->processExecutor = $processExecutor;
        if ($this->processExecutor) {
            $this->processExecutor->enableAsync();
        }
    }

    public function getHttpDownloader(): HttpDownloader
    {
        return $this->httpDownloader;
    }

    public function getProcessExecutor(): ?ProcessExecutor
    {
        return $this->processExecutor;
    }

    /**
     * @param array<PromiseInterface<mixed>> $promises
     */
    public function wait(array $promises, ?ProgressBar $progress = null): void
    {
        $uncaught = null;

        \React\Promise\all($promises)->then(
            static function (): void {
            },
            static function (\Throwable $e) use (&$uncaught): void {
                $uncaught = $e;
            }
        );

        // keep track of every group of promises that is waited on, so abortJobs can
        // cancel them all, even if wait() was called within a wait()
        $waitIndex = $this->waitIndex++;
        $this->currentPromises[$waitIndex] = $promises;

        if ($progress) {
            $totalJobs = 0;
            $totalJobs += $this->httpDownloader->countActiveJobs();
            if ($this->processExecutor) {
                $totalJobs += $this->processExecutor->countActiveJobs();
            }
            $progress->start($totalJobs);
        }

        $lastUpdate = 0;
        while (true) {
            $activeJobs = 0;

            $activeJobs += $this->httpDownloader->countActiveJobs();
            if ($this->processExecutor) {
                $activeJobs += $this->processExecutor->countActiveJobs();
            }

            if ($progress && microtime(true) - $lastUpdate > 0.1) {
                $lastUpdate = microtime(true);
                $progress->setProgress($progress->getMaxSteps() - $activeJobs);
            }

            if (!$activeJobs) {
                break;
            }
        }

        // as we skip progress updates if they are too quick, make sure we do one last one here at 100%
        if ($progress) {
            $progress->finish();
        }

        unset($this->currentPromises[$waitIndex]);
        if (null !== $uncaught) {
            throw $uncaught;
        }
    }

    public function abortJobs(): void
    {
        foreach ($this->currentPromises as $promiseGroup) {
            foreach ($promiseGroup as $promise) {
                // to support react/promise 2.x we wrap the promise in a resolve() call for safety
                \React\Promise\resolve($promise)->cancel();
            }
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\IO\IOInterface;

/**
 * Convert PHP errors into exceptions
 *
 * @author Artem Lopata <biozshock@gmail.com>
 */
class ErrorHandler
{
    /** @var ?IOInterface */
    private static $io;

    /** @var int<0, 2> */
    private static $hasShownDeprecationNotice = 0;

    /**
     * Error handler
     *
     * @param int    $level   Level of the error raised
     * @param string $message Error message
     * @param string $file    Filename that the error was raised in
     * @param int    $line    Line number the error was raised at
     *
     * @static
     * @throws \ErrorException
     */
    public static function handle(int $level, string $message, string $file, int $line): bool
    {
        $isDeprecationNotice = $level === E_DEPRECATED || $level === E_USER_DEPRECATED;

        // error code is not included in error_reporting
        if (!$isDeprecationNotice && 0 === (error_reporting() & $level)) {
            return true;
        }

        if (filter_var(ini_get('xdebug.scream'), FILTER_VALIDATE_BOOLEAN)) {
            $message .= "\n\nWarning: You have xdebug.scream enabled, the warning above may be".
            "\na legitimately suppressed error that you were not supposed to see.";
        }

        if (!$isDeprecationNotice) {
            // ignore some newly introduced warnings in new php versions until dependencies
            // can be fixed as we do not want to abort execution for those
            if (in_array($level, [E_WARNING, E_USER_WARNING], true) && str_contains($message, 'should either be used or intentionally ignored by casting it as (void)')) {
                self::outputWarning('Ignored new PHP warning but it should be reported and fixed: '.$message.' in '.$file.':'.$line, true);

                return true;
            }

            throw new \ErrorException($message, 0, $level, $file, $line);
        }

        if (self::$io !== null) {
            if (self::$hasShownDeprecationNotice > 0 && !self::$io->isVerbose()) {
                if (self::$hasShownDeprecationNotice === 1) {
                    self::$io->writeError('<warning>More deprecation notices were hidden, run again with `-v` to show them.</warning>');
                    self::$hasShownDeprecationNotice = 2;
                }

                return true;
            }
            self::$hasShownDeprecationNotice = 1;
            self::outputWarning('Deprecation Notice: '.$message.' in '.$file.':'.$line);
        }

        return true;
    }

    /**
     * Register error handler.
     */
    public static function register(?IOInterface $io = null): void
    {
        set_error_handler([__CLASS__, 'handle']);
        error_reporting(E_ALL);
        self::$io = $io;
    }

    private static function outputWarning(string $message, bool $outputEvenWithoutIO = false): void
    {
        if (self::$io !== null) {
            self::$io->writeError('<warning>'.$message.'</warning>');
            if (self::$io->isVerbose()) {
                self::$io->writeError('<warning>Stack trace:</warning>');
                self::$io->writeError(array_filter(array_map(static function ($a): ?string {
                    if (isset($a['line'], $a['file'])) {
                        return '<warning> '.$a['file'].':'.$a['line'].'</warning>';
                    }

                    return null;
                }, array_slice(debug_backtrace(), 2)), static function (?string $line) {
                    return $line !== null;
                }));
            }

            return;
        }

        if ($outputEvenWithoutIO) {
            if (defined('STDERR') && is_resource(STDERR)) {
                fwrite(STDERR, 'Warning: '.$message.PHP_EOL);
            } else {
                echo 'Warning: '.$message.PHP_EOL;
            }
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

@trigger_error('Composer\Util\MetadataMinifier is deprecated, use Composer\MetadataMinifier\MetadataMinifier from composer/metadata-minifier instead.', E_USER_DEPRECATED);

/**
 * @deprecated Use Composer\MetadataMinifier\MetadataMinifier instead
 */
class MetadataMinifier extends \Composer\MetadataMinifier\MetadataMinifier
{
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util\Http;

use Composer\Downloader\TransportException;

/**
 * @internal
 * @author John Stevenson <john-stevenson@blueyonder.co.uk>
 *
 * @phpstan-type contextOptions array{http: array{proxy: string, header?: string, request_fulluri?: bool}}
 */
class RequestProxy
{
    /** @var ?contextOptions */
    private $contextOptions;
    /** @var ?non-empty-string */
    private $status;
    /** @var ?non-empty-string */
    private $url;
    /** @var ?non-empty-string */
    private $auth;

    /**
     * @param ?non-empty-string $url The proxy url, without authorization
     * @param ?non-empty-string $auth Authorization for curl
     * @param ?contextOptions $contextOptions
     * @param ?non-empty-string $status
     */
    public function __construct(?string $url, ?string $auth, ?array $contextOptions, ?string $status)
    {
        $this->url = $url;
        $this->auth = $auth;
        $this->contextOptions = $contextOptions;
        $this->status = $status;
    }

    public static function none(): RequestProxy
    {
        return new self(null, null, null, null);
    }

    public static function noProxy(): RequestProxy
    {
        return new self(null, null, null, 'excluded by no_proxy');
    }

    /**
     * Returns the context options to use for this request, otherwise null
     *
     * @return ?contextOptions
     */
    public function getContextOptions(): ?array
    {
        return $this->contextOptions;
    }

    /**
     * Returns an array of curl proxy options
     *
     * @param array<string, string|int> $sslOptions
     * @return array<int, string|int>
     */
    public function getCurlOptions(array $sslOptions): array
    {
        if ($this->isSecure() && !$this->supportsSecureProxy()) {
            throw new TransportException('Cannot use an HTTPS proxy. PHP >= 7.3 and cUrl >= 7.52.0 are required.');
        }

        // Always set a proxy url, even an empty value, because it tells curl
        // to ignore proxy environment variables
        $options = [CURLOPT_PROXY => (string) $this->url];

        // If using a proxy, tell curl to ignore no_proxy environment variables
        if ($this->url !== null) {
            $options[CURLOPT_NOPROXY] = '';
        }

        // Set any authorization
        if ($this->auth !== null) {
            $options[CURLOPT_PROXYAUTH] = CURLAUTH_BASIC;
            $options[CURLOPT_PROXYUSERPWD] = $this->auth;
        }

        if ($this->isSecure()) {
            if (isset($sslOptions['cafile'])) {
                $options[CURLOPT_PROXY_CAINFO] = $sslOptions['cafile'];
            }
            if (isset($sslOptions['capath'])) {
                $options[CURLOPT_PROXY_CAPATH] = $sslOptions['capath'];
            }
        }

        return $options;
    }

    /**
     * Returns proxy info associated with this request
     *
     * An empty return value means that the user has not set a proxy.
     * A non-empty value will either be the sanitized proxy url if a proxy is
     * required, or a message indicating that a no_proxy value has disabled the
     * proxy.
     *
     * @param ?string $format Output format specifier
     */
    public function getStatus(?string $format = null): string
    {
        if ($this->status === null) {
            return '';
        }

        $format = $format ?? '%s';
        if (strpos($format, '%s') !== false) {
            return sprintf($format, $this->status);
        }

        throw new \InvalidArgumentException('String format specifier is missing');
    }

    /**
     * Returns true if the request url has been excluded by a no_proxy value
     *
     * A false value can also mean that the user has not set a proxy.
     */
    public function isExcludedByNoProxy(): bool
    {
        return $this->status !== null && $this->url === null;
    }

    /**
     * Returns true if this is a secure (HTTPS) proxy
     *
     * A false value means that this is either an HTTP proxy, or that a proxy
     * is not required for this request, or that the user has not set a proxy.
     */
    public function isSecure(): bool
    {
        return 0 === strpos((string) $this->url, 'https://');
    }

    /**
     * Returns true if an HTTPS proxy can be used.
     *
     * This depends on PHP7.3+ for CURL_VERSION_HTTPS_PROXY
     * and curl including the feature (from version 7.52.0)
     */
    public function supportsSecureProxy(): bool
    {
        if (false === ($version = curl_version()) || !defined('CURL_VERSION_HTTPS_PROXY')) {
            return false;
        }

        $features = $version['features'];

        return (bool) ($features & CURL_VERSION_HTTPS_PROXY);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util\Http;

/**
 * @internal
 * @author John Stevenson <john-stevenson@blueyonder.co.uk>
 */
class ProxyItem
{
    /** @var non-empty-string */
    private $url;
    /** @var non-empty-string */
    private $safeUrl;
    /** @var ?non-empty-string */
    private $curlAuth;
    /** @var string */
    private $optionsProxy;
    /** @var ?non-empty-string */
    private $optionsAuth;

    /**
     * @param string $proxyUrl The value from the environment
     * @param string $envName The name of the environment variable
     * @throws \RuntimeException If the proxy url is invalid
     */
    public function __construct(string $proxyUrl, string $envName)
    {
        $syntaxError = sprintf('unsupported `%s` syntax', $envName);

        if (strpbrk($proxyUrl, "\r\n\t") !== false) {
            throw new \RuntimeException($syntaxError);
        }
        if (false === ($proxy = parse_url($proxyUrl))) {
            throw new \RuntimeException($syntaxError);
        }
        if (!isset($proxy['host'])) {
            throw new \RuntimeException('unable to find proxy host in ' . $envName);
        }

        $scheme = isset($proxy['scheme']) ? strtolower($proxy['scheme']) . '://' : 'http://';
        $safe = '';

        if (isset($proxy['user'])) {
            $safe = '***';
            $user = $proxy['user'];
            $auth = rawurldecode($proxy['user']);

            if (isset($proxy['pass'])) {
                $safe .= ':***';
                $user .= ':' . $proxy['pass'];
                $auth .= ':' . rawurldecode($proxy['pass']);
            }

            $safe .= '@';

            if (strlen($user) > 0) {
                $this->curlAuth = $user;
                $this->optionsAuth = 'Proxy-Authorization: Basic ' . base64_encode($auth);
            }
        }

        $host = $proxy['host'];
        $port = null;

        if (isset($proxy['port'])) {
            $port = $proxy['port'];
        } elseif ($scheme === 'http://') {
            $port = 80;
        } elseif ($scheme === 'https://') {
            $port = 443;
        }

        // We need a port because curl uses 1080 for http. Port 0 is reserved,
        // but is considered valid depending on the PHP or Curl version.
        if ($port === null) {
            throw new \RuntimeException('unable to find proxy port in ' . $envName);
        }
        if ($port === 0) {
            throw new \RuntimeException('port 0 is reserved in ' . $envName);
        }

        $this->url = sprintf('%s%s:%d', $scheme, $host, $port);
        $this->safeUrl = sprintf('%s%s%s:%d', $scheme, $safe, $host, $port);

        $scheme = str_replace(['http://', 'https://'], ['tcp://', 'ssl://'], $scheme);
        $this->optionsProxy = sprintf('%s%s:%d', $scheme, $host, $port);
    }

    /**
     * Returns a RequestProxy instance for the scheme of the request url
     *
     * @param string $scheme The scheme of the request url
     */
    public function toRequestProxy(string $scheme): RequestProxy
    {
        $options = ['http' => ['proxy' => $this->optionsProxy]];

        if ($this->optionsAuth !== null) {
            $options['http']['header'] = $this->optionsAuth;
        }

        if ($scheme === 'http') {
            $options['http']['request_fulluri'] = true;
        }

        return new RequestProxy($this->url, $this->curlAuth, $options, $this->safeUrl);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util\Http;

use Composer\Downloader\TransportException;
use Composer\Util\NoProxyPattern;

/**
 * @internal
 * @author John Stevenson <john-stevenson@blueyonder.co.uk>
 */
class ProxyManager
{
    /** @var ?string */
    private $error = null;
    /** @var ?ProxyItem */
    private $httpProxy = null;
    /** @var ?ProxyItem */
    private $httpsProxy = null;
    /** @var ?NoProxyPattern */
    private $noProxyHandler = null;

    /** @var ?self */
    private static $instance = null;

    private function __construct()
    {
        try {
            $this->getProxyData();
        } catch (\RuntimeException $e) {
            $this->error = $e->getMessage();
        }
    }

    public static function getInstance(): ProxyManager
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }

        return self::$instance;
    }

    /**
     * Clears the persistent instance
     */
    public static function reset(): void
    {
        self::$instance = null;
    }

    public function hasProxy(): bool
    {
        return $this->httpProxy !== null || $this->httpsProxy !== null;
    }

    /**
     * Returns a RequestProxy instance for the request url
     *
     * @param non-empty-string $requestUrl
     */
    public function getProxyForRequest(string $requestUrl): RequestProxy
    {
        if ($this->error !== null) {
            throw new TransportException('Unable to use a proxy: '.$this->error);
        }

        $scheme = (string) parse_url($requestUrl, PHP_URL_SCHEME);
        $proxy = $this->getProxyForScheme($scheme);

        if ($proxy === null) {
            return RequestProxy::none();
        }

        if ($this->noProxy($requestUrl)) {
            return RequestProxy::noProxy();
        }

        return $proxy->toRequestProxy($scheme);
    }

    /**
     * Returns a ProxyItem if one is set for the scheme, otherwise null
     */
    private function getProxyForScheme(string $scheme): ?ProxyItem
    {
        if ($scheme === 'http') {
            return $this->httpProxy;
        }

        if ($scheme === 'https') {
            return $this->httpsProxy;
        }

        return null;
    }

    /**
     * Finds proxy values from the environment and sets class properties
     */
    private function getProxyData(): void
    {
        // Handle http_proxy/HTTP_PROXY on CLI only for security reasons
        if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
            [$env, $name] = $this->getProxyEnv('http_proxy');
            if ($env !== null) {
                $this->httpProxy = new ProxyItem($env, $name);
            }
        }

        // Handle cgi_http_proxy/CGI_HTTP_PROXY if needed
        if ($this->httpProxy === null) {
            [$env, $name] = $this->getProxyEnv('cgi_http_proxy');
            if ($env !== null) {
                $this->httpProxy = new ProxyItem($env, $name);
            }
        }

        // Handle https_proxy/HTTPS_PROXY
        [$env, $name] = $this->getProxyEnv('https_proxy');
        if ($env !== null) {
            $this->httpsProxy = new ProxyItem($env, $name);
        }

        // Handle no_proxy/NO_PROXY
        [$env, $name] = $this->getProxyEnv('no_proxy');
        if ($env !== null) {
            $this->noProxyHandler = new NoProxyPattern($env);
        }
    }

    /**
     * Searches $_SERVER for case-sensitive values
     *
     * @return array{0: string|null, 1: string} value, name
     */
    private function getProxyEnv(string $envName): array
    {
        $names = [strtolower($envName), strtoupper($envName)];

        foreach ($names as $name) {
            if (is_string($_SERVER[$name] ?? null)) {
                if ($_SERVER[$name] !== '') {
                    return [$_SERVER[$name], $name];
                }
            }
        }

        return [null, ''];
    }

    /**
     * Returns true if a url matches no_proxy value
     */
    private function noProxy(string $requestUrl): bool
    {
        if ($this->noProxyHandler === null) {
            return false;
        }

        return $this->noProxyHandler->test($requestUrl);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util\Http;

use Composer\Config;
use Composer\Downloader\MaxFileSizeExceededException;
use Composer\IO\IOInterface;
use Composer\Downloader\TransportException;
use Composer\Pcre\Preg;
use Composer\Util\Platform;
use Composer\Util\StreamContextFactory;
use Composer\Util\AuthHelper;
use Composer\Util\Url;
use Composer\Util\HttpDownloader;
use React\Promise\Promise;

/**
 * @internal
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Nicolas Grekas <p@tchwork.com>
 * @phpstan-type Attributes array{retryAuthFailure: bool, redirects: int<0, max>, retries: int<0, max>, storeAuth: 'prompt'|bool, ipResolve: 4|6|null}
 * @phpstan-type Job array{url: non-empty-string, origin: string, attributes: Attributes, options: mixed[], progress: mixed[], curlHandle: \CurlHandle, filename: string|null, headerHandle: resource, bodyHandle: resource, resolve: callable, reject: callable, primaryIp: string}
 */
class CurlDownloader
{
    /**
     * Known libcurl's broken versions when proxy is in use with HTTP/2
     * multiplexing.
     *
     * @var list<non-empty-string>
     */
    private const BAD_MULTIPLEXING_CURL_VERSIONS = ['7.87.0', '7.88.0', '7.88.1'];

    /** @var \CurlMultiHandle */
    private $multiHandle;
    /** @var \CurlShareHandle */
    private $shareHandle;
    /** @var Job[] */
    private $jobs = [];
    /** @var IOInterface */
    private $io;
    /** @var Config */
    private $config;
    /** @var AuthHelper */
    private $authHelper;
    /** @var float */
    private $selectTimeout = 5.0;
    /** @var int */
    private $maxRedirects = 20;
    /** @var int */
    private $maxRetries = 3;
    /** @var array<int, string[]> */
    protected $multiErrors = [
        CURLM_BAD_HANDLE => ['CURLM_BAD_HANDLE', 'The passed-in handle is not a valid CURLM handle.'],
        CURLM_BAD_EASY_HANDLE => ['CURLM_BAD_EASY_HANDLE', "An easy handle was not good/valid. It could mean that it isn't an easy handle at all, or possibly that the handle already is in used by this or another multi handle."],
        CURLM_OUT_OF_MEMORY => ['CURLM_OUT_OF_MEMORY', 'You are doomed.'],
        CURLM_INTERNAL_ERROR => ['CURLM_INTERNAL_ERROR', 'This can only be returned if libcurl bugs. Please report it to us!'],
    ];

    /** @var mixed[] */
    private static $options = [
        'http' => [
            'method' => CURLOPT_CUSTOMREQUEST,
            'content' => CURLOPT_POSTFIELDS,
            'header' => CURLOPT_HTTPHEADER,
            'timeout' => CURLOPT_TIMEOUT,
        ],
        'ssl' => [
            'cafile' => CURLOPT_CAINFO,
            'capath' => CURLOPT_CAPATH,
            'verify_peer' => CURLOPT_SSL_VERIFYPEER,
            'verify_peer_name' => CURLOPT_SSL_VERIFYHOST,
            'local_cert' => CURLOPT_SSLCERT,
            'local_pk' => CURLOPT_SSLKEY,
            'passphrase' => CURLOPT_SSLKEYPASSWD,
        ],
    ];

    /** @var array<string, true> */
    private static $timeInfo = [
        'total_time' => true,
        'namelookup_time' => true,
        'connect_time' => true,
        'pretransfer_time' => true,
        'starttransfer_time' => true,
        'redirect_time' => true,
    ];

    /**
     * @param mixed[] $options
     */
    public function __construct(IOInterface $io, Config $config, array $options = [], bool $disableTls = false)
    {
        $this->io = $io;
        $this->config = $config;

        $this->multiHandle = $mh = curl_multi_init();
        if (function_exists('curl_multi_setopt')) {
            if (ProxyManager::getInstance()->hasProxy() && ($version = curl_version()) !== false && in_array($version['version'], self::BAD_MULTIPLEXING_CURL_VERSIONS, true)) {
                /**
                 * Disable HTTP/2 multiplexing for some broken versions of libcurl.
                 *
                 * In certain versions of libcurl when proxy is in use with HTTP/2
                 * multiplexing, connections will continue stacking up. This was
                 * fixed in libcurl 8.0.0 in curl/curl@821f6e2a89de8aec1c7da3c0f381b92b2b801efc
                 */
                curl_multi_setopt($mh, CURLMOPT_PIPELINING, /* CURLPIPE_NOTHING */ 0);
            } else {
                curl_multi_setopt($mh, CURLMOPT_PIPELINING, \PHP_VERSION_ID >= 70400 ? /* CURLPIPE_MULTIPLEX */ 2 : /*CURLPIPE_HTTP1 | CURLPIPE_MULTIPLEX*/ 3);
            }
            if (defined('CURLMOPT_MAX_HOST_CONNECTIONS') && !defined('HHVM_VERSION')) {
                curl_multi_setopt($mh, CURLMOPT_MAX_HOST_CONNECTIONS, 8);
            }
        }

        if (function_exists('curl_share_init')) {
            $this->shareHandle = $sh = curl_share_init();
            curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
            curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
            curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
        }

        $this->authHelper = new AuthHelper($io, $config);
    }

    /**
     * @param mixed[]  $options
     * @param non-empty-string $url
     *
     * @return int internal job id
     */
    public function download(callable $resolve, callable $reject, string $origin, string $url, array $options, ?string $copyTo = null): int
    {
        $attributes = [];
        if (isset($options['retry-auth-failure'])) {
            $attributes['retryAuthFailure'] = $options['retry-auth-failure'];
            unset($options['retry-auth-failure']);
        }

        return $this->initDownload($resolve, $reject, $origin, $url, $options, $copyTo, $attributes);
    }

    /**
     * @param mixed[]  $options
     *
     * @param array{retryAuthFailure?: bool, redirects?: int<0, max>, retries?: int<0, max>, storeAuth?: 'prompt'|bool, ipResolve?: 4|6|null} $attributes
     * @param non-empty-string $url
     *
     * @return int internal job id
     */
    private function initDownload(callable $resolve, callable $reject, string $origin, string $url, array $options, ?string $copyTo = null, array $attributes = []): int
    {
        $attributes = array_merge([
            'retryAuthFailure' => true,
            'redirects' => 0,
            'retries' => 0,
            'storeAuth' => false,
            'ipResolve' => null,
        ], $attributes);

        if ($attributes['ipResolve'] === null && Platform::getEnv('COMPOSER_IPRESOLVE') === '4') {
            $attributes['ipResolve'] = 4;
        } elseif ($attributes['ipResolve'] === null && Platform::getEnv('COMPOSER_IPRESOLVE') === '6') {
            $attributes['ipResolve'] = 6;
        }

        $originalOptions = $options;

        // check URL can be accessed (i.e. is not insecure), but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256
        if (!Preg::isMatch('{^http://(repo\.)?packagist\.org/p/}', $url) || (false === strpos($url, '$') && false === strpos($url, '%24'))) {
            $this->config->prohibitUrlByConfig($url, $this->io, $options);
        }

        $curlHandle = curl_init();
        $headerHandle = fopen('php://temp/maxmemory:32768', 'w+b');
        if (false === $headerHandle) {
            throw new \RuntimeException('Failed to open a temp stream to store curl headers');
        }

        if ($copyTo !== null) {
            $bodyTarget = $copyTo.'~';
        } else {
            $bodyTarget = 'php://temp/maxmemory:524288';
        }

        $errorMessage = '';
        set_error_handler(static function (int $code, string $msg) use (&$errorMessage): bool {
            if ($errorMessage) {
                $errorMessage .= "\n";
            }
            $errorMessage .= Preg::replace('{^fopen\(.*?\): }', '', $msg);

            return true;
        });
        $bodyHandle = fopen($bodyTarget, 'w+b');
        restore_error_handler();
        if (false === $bodyHandle) {
            throw new TransportException('The "'.$url.'" file could not be written to '.($copyTo ?? 'a temporary file').': '.$errorMessage);
        }

        curl_setopt($curlHandle, CURLOPT_URL, $url);
        curl_setopt($curlHandle, CURLOPT_FOLLOWLOCATION, false);
        curl_setopt($curlHandle, CURLOPT_CONNECTTIMEOUT, 10);
        curl_setopt($curlHandle, CURLOPT_TIMEOUT, max((int) ini_get("default_socket_timeout"), 300));
        curl_setopt($curlHandle, CURLOPT_WRITEHEADER, $headerHandle);
        curl_setopt($curlHandle, CURLOPT_FILE, $bodyHandle);
        curl_setopt($curlHandle, CURLOPT_ENCODING, ""); // let cURL set the Accept-Encoding header to what it supports
        curl_setopt($curlHandle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);

        if ($attributes['ipResolve'] === 4) {
            curl_setopt($curlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
        } elseif ($attributes['ipResolve'] === 6) {
            curl_setopt($curlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6);
        }

        if (function_exists('curl_share_init')) {
            curl_setopt($curlHandle, CURLOPT_SHARE, $this->shareHandle);
        }

        if (!isset($options['http']['header'])) {
            $options['http']['header'] = [];
        }

        $options['http']['header'] = array_diff($options['http']['header'], ['Connection: close']);
        $options['http']['header'][] = 'Connection: keep-alive';

        $version = curl_version();
        $features = $version['features'];

        $proxy = ProxyManager::getInstance()->getProxyForRequest($url);

        if (0 === strpos($url, 'https://')) {
            $willUseProxy = $proxy->getStatus() !== '' && !$proxy->isExcludedByNoProxy();

            if (!$willUseProxy && \defined('CURL_VERSION_HTTP3') && \defined('CURL_HTTP_VERSION_3') && (CURL_VERSION_HTTP3 & $features) !== 0) {
                curl_setopt($curlHandle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3);
            } elseif (\defined('CURL_VERSION_HTTP2') && \defined('CURL_HTTP_VERSION_2_0') && (CURL_VERSION_HTTP2 & $features) !== 0) {
                curl_setopt($curlHandle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
            }
        }

        // curl 8.7.0 - 8.7.1 has a bug whereas automatic accept-encoding header results in an error when reading the response
        // https://github.com/composer/composer/issues/11913
        if (isset($version['version']) && in_array($version['version'], ['8.7.0', '8.7.1'], true) && \defined('CURL_VERSION_LIBZ') && (CURL_VERSION_LIBZ & $features) !== 0) {
            curl_setopt($curlHandle, CURLOPT_ENCODING, "gzip");
        }

        $options = $this->authHelper->addAuthenticationOptions($options, $origin, $url);
        $options = StreamContextFactory::initOptions($url, $options, true);

        foreach (self::$options as $type => $curlOptions) {
            foreach ($curlOptions as $name => $curlOption) {
                if (isset($options[$type][$name])) {
                    if ($type === 'ssl' && $name === 'verify_peer_name') {
                        curl_setopt($curlHandle, $curlOption, $options[$type][$name] === true ? 2 : $options[$type][$name]);
                    } else {
                        curl_setopt($curlHandle, $curlOption, $options[$type][$name]);
                    }
                }
            }
        }

        curl_setopt_array($curlHandle, $proxy->getCurlOptions($options['ssl'] ?? []));

        $progress = array_diff_key(curl_getinfo($curlHandle), self::$timeInfo);

        $this->jobs[(int) $curlHandle] = [
            'url' => $url,
            'origin' => $origin,
            'attributes' => $attributes,
            'options' => $originalOptions,
            'progress' => $progress,
            'curlHandle' => $curlHandle,
            'filename' => $copyTo,
            'headerHandle' => $headerHandle,
            'bodyHandle' => $bodyHandle,
            'resolve' => $resolve,
            'reject' => $reject,
            'primaryIp' => '',
        ];

        $usingProxy = $proxy->getStatus(' using proxy (%s)');
        $ifModified = false !== stripos(implode(',', $options['http']['header']), 'if-modified-since:') ? ' if modified' : '';
        if ($attributes['redirects'] === 0 && $attributes['retries'] === 0) {
            $this->io->writeError('Downloading ' . Url::sanitize($url) . $usingProxy . $ifModified, true, IOInterface::DEBUG);
        }

        $this->checkCurlResult(curl_multi_add_handle($this->multiHandle, $curlHandle));
        // TODO progress

        return (int) $curlHandle;
    }

    public function abortRequest(int $id): void
    {
        if (isset($this->jobs[$id], $this->jobs[$id]['curlHandle'])) {
            $job = $this->jobs[$id];
            curl_multi_remove_handle($this->multiHandle, $job['curlHandle']);
            if (\PHP_VERSION_ID < 80000) {
                curl_close($job['curlHandle']);
            }
            if (is_resource($job['headerHandle'])) {
                fclose($job['headerHandle']);
            }
            if (is_resource($job['bodyHandle'])) {
                fclose($job['bodyHandle']);
            }
            if (null !== $job['filename']) {
                @unlink($job['filename'].'~');
            }
            unset($this->jobs[$id]);
        }
    }

    public function tick(): void
    {
        static $timeoutWarning = false;

        if (count($this->jobs) === 0) {
            return;
        }

        $active = true;
        $this->checkCurlResult(curl_multi_exec($this->multiHandle, $active));
        if (-1 === curl_multi_select($this->multiHandle, $this->selectTimeout)) {
            // sleep in case select returns -1 as it can happen on old php versions or some platforms where curl does not manage to do the select
            usleep(150);
        }

        while ($progress = curl_multi_info_read($this->multiHandle)) {
            $curlHandle = $progress['handle'];
            $result = $progress['result'];
            $i = (int) $curlHandle;
            if (!isset($this->jobs[$i])) {
                continue;
            }

            $progress = curl_getinfo($curlHandle);
            if (false === $progress) {
                throw new \RuntimeException('Failed getting info from curl handle '.$i.' ('.$this->jobs[$i]['url'].')');
            }
            $job = $this->jobs[$i];
            unset($this->jobs[$i]);
            $error = curl_error($curlHandle);
            $errno = curl_errno($curlHandle);
            curl_multi_remove_handle($this->multiHandle, $curlHandle);
            if (\PHP_VERSION_ID < 80000) {
                curl_close($curlHandle);
            }

            $headers = null;
            $statusCode = null;
            $response = null;
            try {
                // TODO progress
                if (CURLE_OK !== $errno || $error || $result !== CURLE_OK) {
                    $errno = $errno ?: $result;
                    if (!$error && function_exists('curl_strerror')) {
                        $error = curl_strerror($errno);
                    }
                    $progress['error_code'] = $errno;

                    if ($errno === 28 /* CURLE_OPERATION_TIMEDOUT */ && \PHP_VERSION_ID >= 70300 && $progress['namelookup_time'] === 0.0 && !$timeoutWarning) {
                        $timeoutWarning = true;
                        $this->io->writeError('<warning>A connection timeout was encountered. If you intend to run Composer without connecting to the internet, run the command again prefixed with COMPOSER_DISABLE_NETWORK=1 to make Composer run in offline mode.</warning>');
                    }

                    if (
                        (!isset($job['options']['http']['method']) || $job['options']['http']['method'] === 'GET')
                        && (
                            in_array($errno, [7 /* CURLE_COULDNT_CONNECT */, 16 /* CURLE_HTTP2 */, 92 /* CURLE_HTTP2_STREAM */, 6 /* CURLE_COULDNT_RESOLVE_HOST */, 28 /* CURLE_OPERATION_TIMEDOUT */], true)
                            || (in_array($errno, [56 /* CURLE_RECV_ERROR */, 35 /* CURLE_SSL_CONNECT_ERROR */], true) && str_contains((string) $error, 'Connection reset by peer'))
                        ) && $job['attributes']['retries'] < $this->maxRetries
                    ) {
                        $attributes = ['retries' => $job['attributes']['retries'] + 1];
                        if ($errno === 7 && !isset($job['attributes']['ipResolve'])) { // CURLE_COULDNT_CONNECT, retry forcing IPv4 if no IP stack was selected
                            $attributes['ipResolve'] = 4;
                        }
                        $this->io->writeError('Retrying ('.($job['attributes']['retries'] + 1).') ' . Url::sanitize($job['url']) . ' due to curl error '. $errno, true, IOInterface::DEBUG);
                        $this->restartJobWithDelay($job, $job['url'], $attributes);
                        continue;
                    }

                    // TODO: Remove this as soon as https://github.com/curl/curl/issues/10591 is resolved
                    if ($errno === 55 /* CURLE_SEND_ERROR */) {
                        $this->io->writeError('Retrying ('.($job['attributes']['retries'] + 1).') ' . Url::sanitize($job['url']) . ' due to curl error '. $errno, true, IOInterface::DEBUG);
                        $this->restartJobWithDelay($job, $job['url'], ['retries' => $job['attributes']['retries'] + 1]);
                        continue;
                    }

                    throw new TransportException('curl error '.$errno.' while downloading '.Url::sanitize($progress['url']).': '.$error);
                }
                $statusCode = $progress['http_code'];
                rewind($job['headerHandle']);
                $headers = explode("\r\n", rtrim(stream_get_contents($job['headerHandle'])));
                fclose($job['headerHandle']);

                if ($statusCode === 0) {
                    throw new \LogicException('Received unexpected http status code 0 without error for '.Url::sanitize($progress['url']).': headers '.var_export($headers, true).' curl info '.var_export($progress, true));
                }

                // prepare response object
                if (null !== $job['filename']) {
                    $contents = $job['filename'].'~';
                    if ($statusCode >= 300) {
                        rewind($job['bodyHandle']);
                        $contents = stream_get_contents($job['bodyHandle']);
                    }
                    $response = new CurlResponse(['url' => $job['url']], $statusCode, $headers, $contents, $progress);
                    $this->io->writeError('['.$statusCode.'] '.Url::sanitize($job['url']), true, IOInterface::DEBUG);
                } else {
                    $maxFileSize = $job['options']['max_file_size'] ?? null;
                    rewind($job['bodyHandle']);
                    if ($maxFileSize !== null) {
                        $contents = stream_get_contents($job['bodyHandle'], $maxFileSize);
                        // Gzipped responses with missing Content-Length header cannot be detected during the file download
                        // because $progress['size_download'] refers to the gzipped size downloaded, not the actual file size
                        if ($contents !== false && Platform::strlen($contents) >= $maxFileSize) {
                            throw new MaxFileSizeExceededException('Maximum allowed download size reached. Downloaded ' . Platform::strlen($contents) . ' of allowed ' .  $maxFileSize . ' bytes');
                        }
                    } else {
                        $contents = stream_get_contents($job['bodyHandle']);
                    }

                    $response = new CurlResponse(['url' => $job['url']], $statusCode, $headers, $contents, $progress);
                    $this->io->writeError('['.$statusCode.'] '.Url::sanitize($job['url']), true, IOInterface::DEBUG);
                }
                fclose($job['bodyHandle']);

                if ($response->getStatusCode() >= 300 && $response->getHeader('content-type') === 'application/json') {
                    HttpDownloader::outputWarnings($this->io, $job['origin'], json_decode($response->getBody(), true));
                }

                $result = $this->isAuthenticatedRetryNeeded($job, $response);
                if ($result['retry']) {
                    $this->restartJob($job, $job['url'], ['storeAuth' => $result['storeAuth'], 'retries' => $job['attributes']['retries'] + 1]);
                    continue;
                }

                // handle 3xx redirects, 304 Not Modified is excluded
                if ($statusCode >= 300 && $statusCode <= 399 && $statusCode !== 304 && $job['attributes']['redirects'] < $this->maxRedirects) {
                    $location = $this->handleRedirect($job, $response);
                    if ($location) {
                        $this->restartJob($job, $location, ['redirects' => $job['attributes']['redirects'] + 1]);
                        continue;
                    }
                }

                // fail 4xx and 5xx responses and capture the response
                if ($statusCode >= 400 && $statusCode <= 599) {
                    if (
                        (!isset($job['options']['http']['method']) || $job['options']['http']['method'] === 'GET')
                        && in_array($statusCode, [423, 425, 500, 502, 503, 504, 507, 510], true)
                        && $job['attributes']['retries'] < $this->maxRetries
                    ) {
                        $this->io->writeError('Retrying ('.($job['attributes']['retries'] + 1).') ' . Url::sanitize($job['url']) . ' due to status code '. $statusCode, true, IOInterface::DEBUG);
                        $this->restartJobWithDelay($job, $job['url'], ['retries' => $job['attributes']['retries'] + 1]);
                        continue;
                    }

                    throw $this->failResponse($job, $response, $response->getStatusMessage());
                }

                if ($job['attributes']['storeAuth'] !== false) {
                    $this->authHelper->storeAuth($job['origin'], $job['attributes']['storeAuth']);
                }

                // resolve promise
                if (null !== $job['filename']) {
                    rename($job['filename'].'~', $job['filename']);
                    $job['resolve']($response);
                } else {
                    $job['resolve']($response);
                }
            } catch (\Exception $e) {
                if ($e instanceof TransportException) {
                    if (null !== $headers) {
                        $e->setHeaders($headers);
                        $e->setStatusCode($statusCode);
                    }
                    if (null !== $response) {
                        $e->setResponse($response->getBody());
                    }
                    $e->setResponseInfo($progress);
                }

                $this->rejectJob($job, $e);
            }
        }

        foreach ($this->jobs as $i => $curlHandle) {
            $curlHandle = $this->jobs[$i]['curlHandle'];
            $progress = array_diff_key(curl_getinfo($curlHandle), self::$timeInfo);

            if ($this->jobs[$i]['progress'] !== $progress) {
                $this->jobs[$i]['progress'] = $progress;

                if (isset($this->jobs[$i]['options']['max_file_size'])) {
                    // Compare max_file_size with the content-length header this value will be -1 until the header is parsed
                    if ($this->jobs[$i]['options']['max_file_size'] < $progress['download_content_length']) {
                        $this->rejectJob($this->jobs[$i], new MaxFileSizeExceededException('Maximum allowed download size reached. Content-length header indicates ' . $progress['download_content_length'] . ' bytes. Allowed ' .  $this->jobs[$i]['options']['max_file_size'] . ' bytes'));
                    }

                    // Compare max_file_size with the download size in bytes
                    if ($this->jobs[$i]['options']['max_file_size'] < $progress['size_download']) {
                        $this->rejectJob($this->jobs[$i], new MaxFileSizeExceededException('Maximum allowed download size reached. Downloaded ' . $progress['size_download'] . ' of allowed ' .  $this->jobs[$i]['options']['max_file_size'] . ' bytes'));
                    }
                }

                if (isset($progress['primary_ip']) && $progress['primary_ip'] !== $this->jobs[$i]['primaryIp']) {
                    if (
                        isset($this->jobs[$i]['options']['prevent_ip_access_callable']) &&
                        is_callable($this->jobs[$i]['options']['prevent_ip_access_callable']) &&
                        $this->jobs[$i]['options']['prevent_ip_access_callable']($progress['primary_ip'])
                    ) {
                        $this->rejectJob($this->jobs[$i], new TransportException(sprintf('IP "%s" is blocked for "%s".', $progress['primary_ip'], $progress['url'])));
                    }

                    $this->jobs[$i]['primaryIp'] = (string) $progress['primary_ip'];
                }

                // TODO progress
            }
        }
    }

    /**
     * @param  Job    $job
     */
    private function handleRedirect(array $job, Response $response): string
    {
        if ($locationHeader = $response->getHeader('location')) {
            if (parse_url($locationHeader, PHP_URL_SCHEME)) {
                // Absolute URL; e.g. https://example.com/composer
                $targetUrl = $locationHeader;
            } elseif (parse_url($locationHeader, PHP_URL_HOST)) {
                // Scheme relative; e.g. //example.com/foo
                $targetUrl = parse_url($job['url'], PHP_URL_SCHEME).':'.$locationHeader;
            } elseif ('/' === $locationHeader[0]) {
                // Absolute path; e.g. /foo
                $urlHost = parse_url($job['url'], PHP_URL_HOST);

                // Replace path using hostname as an anchor.
                $targetUrl = Preg::replace('{^(.+(?://|@)'.preg_quote($urlHost).'(?::\d+)?)(?:[/\?].*)?$}', '\1'.$locationHeader, $job['url']);
            } else {
                // Relative path; e.g. foo
                // This actually differs from PHP which seems to add duplicate slashes.
                $targetUrl = Preg::replace('{^(.+/)[^/?]*(?:\?.*)?$}', '\1'.$locationHeader, $job['url']);
            }
        }

        if (!empty($targetUrl)) {
            $this->io->writeError(sprintf('Following redirect (%u) %s', $job['attributes']['redirects'] + 1, Url::sanitize($targetUrl)), true, IOInterface::DEBUG);

            return $targetUrl;
        }

        throw new TransportException('The "'.$job['url'].'" file could not be downloaded, got redirect without Location ('.$response->getStatusMessage().')');
    }

    /**
     * @param  Job                                          $job
     * @return array{retry: bool, storeAuth: 'prompt'|bool}
     */
    private function isAuthenticatedRetryNeeded(array $job, Response $response): array
    {
        if (in_array($response->getStatusCode(), [401, 403]) && $job['attributes']['retryAuthFailure']) {
            $result = $this->authHelper->promptAuthIfNeeded($job['url'], $job['origin'], $response->getStatusCode(), $response->getStatusMessage(), $response->getHeaders(), $job['attributes']['retries'], $response->getBody());

            if ($result['retry']) {
                return $result;
            }
        }

        $locationHeader = $response->getHeader('location');
        $needsAuthRetry = false;

        // check for bitbucket login page asking to authenticate
        if (
            $job['origin'] === 'bitbucket.org'
            && !$this->authHelper->isPublicBitBucketDownload($job['url'])
            && substr($job['url'], -4) === '.zip'
            && (!$locationHeader || substr($locationHeader, -4) !== '.zip')
            && Preg::isMatch('{^text/html\b}i', $response->getHeader('content-type'))
        ) {
            $needsAuthRetry = 'Bitbucket requires authentication and it was not provided';
        }

        // check for gitlab 404 when downloading archives
        if (
            $response->getStatusCode() === 404
            && in_array($job['origin'], $this->config->get('gitlab-domains'), true)
            && false !== strpos($job['url'], 'archive.zip')
        ) {
            $needsAuthRetry = 'GitLab requires authentication and it was not provided';
        }

        if ($needsAuthRetry) {
            if ($job['attributes']['retryAuthFailure']) {
                $result = $this->authHelper->promptAuthIfNeeded($job['url'], $job['origin'], 401, null, [], $job['attributes']['retries']);
                if ($result['retry']) {
                    return $result;
                }
            }

            throw $this->failResponse($job, $response, $needsAuthRetry);
        }

        return ['retry' => false, 'storeAuth' => false];
    }

    /**
     * @param  Job    $job
     * @param non-empty-string $url
     *
     * @param  array{retryAuthFailure?: bool, redirects?: int<0, max>, storeAuth?: 'prompt'|bool, retries?: int<1, max>, ipResolve?: 4|6} $attributes
     */
    private function restartJob(array $job, string $url, array $attributes = []): void
    {
        if (null !== $job['filename']) {
            @unlink($job['filename'].'~');
        }

        $attributes = array_merge($job['attributes'], $attributes);
        $origin = Url::getOrigin($this->config, $url);

        $this->initDownload($job['resolve'], $job['reject'], $origin, $url, $job['options'], $job['filename'], $attributes);
    }

    /**
     * @param  Job    $job
     * @param non-empty-string $url
     *
     * @param  array{retryAuthFailure?: bool, redirects?: int<0, max>, storeAuth?: 'prompt'|bool, retries: int<1, max>, ipResolve?: 4|6} $attributes
     */
    private function restartJobWithDelay(array $job, string $url, array $attributes): void
    {
        if ($attributes['retries'] >= 3) {
            usleep(500000); // half a second delay for 3rd retry and beyond
        } elseif ($attributes['retries'] >= 2) {
            usleep(100000); // 100ms delay for 2nd retry
        } // no sleep for the first retry

        $this->restartJob($job, $url, $attributes);
    }

    /**
     * @param  Job                $job
     */
    private function failResponse(array $job, Response $response, string $errorMessage): TransportException
    {
        if (null !== $job['filename']) {
            @unlink($job['filename'].'~');
        }

        $details = '';
        if (in_array(strtolower((string) $response->getHeader('content-type')), ['application/json', 'application/json; charset=utf-8'], true)) {
            $details = ':'.PHP_EOL.substr($response->getBody(), 0, 200).(strlen($response->getBody()) > 200 ? '...' : '');
        }

        return new TransportException('The "'.$job['url'].'" file could not be downloaded ('.$errorMessage.')' . $details, $response->getStatusCode());
    }

    /**
     * @param  Job                $job
     */
    private function rejectJob(array $job, \Exception $e): void
    {
        if (is_resource($job['headerHandle'])) {
            fclose($job['headerHandle']);
        }
        if (is_resource($job['bodyHandle'])) {
            fclose($job['bodyHandle']);
        }
        if (null !== $job['filename']) {
            @unlink($job['filename'].'~');
        }
        $job['reject']($e);
    }

    private function checkCurlResult(int $code): void
    {
        if ($code !== CURLM_OK && $code !== CURLM_CALL_MULTI_PERFORM) {
            throw new \RuntimeException(
                isset($this->multiErrors[$code])
                ? "cURL error: {$code} ({$this->multiErrors[$code][0]}): cURL message: {$this->multiErrors[$code][1]}"
                : 'Unexpected cURL error: ' . $code
            );
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util\Http;

use Composer\Json\JsonFile;
use Composer\Pcre\Preg;

/**
 * @phpstan-type Request array{url: non-empty-string, options?: mixed[], copyTo?: string|null}
 */
class Response
{
    /** @var Request */
    private $request;
    /** @var int */
    private $code;
    /** @var list<string> */
    private $headers;
    /** @var ?string */
    private $body;

    /**
     * @param Request $request
     * @param list<string> $headers
     */
    public function __construct(array $request, ?int $code, array $headers, ?string $body)
    {
        if (!isset($request['url'])) {
            throw new \LogicException('url key missing from request array');
        }
        $this->request = $request;
        $this->code = (int) $code;
        $this->headers = $headers;
        $this->body = $body;
    }

    public function getStatusCode(): int
    {
        return $this->code;
    }

    public function getStatusMessage(): ?string
    {
        $value = null;
        foreach ($this->headers as $header) {
            if (Preg::isMatch('{^HTTP/\S+ \d+}i', $header)) {
                // In case of redirects, headers contain the headers of all responses
                // so we can not return directly and need to keep iterating
                $value = $header;
            }
        }

        return $value;
    }

    /**
     * @return string[]
     */
    public function getHeaders(): array
    {
        return $this->headers;
    }

    public function getHeader(string $name): ?string
    {
        return self::findHeaderValue($this->headers, $name);
    }

    public function getBody(): ?string
    {
        return $this->body;
    }

    /**
     * @return mixed
     */
    public function decodeJson()
    {
        return JsonFile::parseJson($this->body, $this->request['url']);
    }

    /**
     * @phpstan-impure
     */
    public function collect(): void
    {
        unset($this->request, $this->code, $this->headers, $this->body);
    }

    /**
     * @param  string[]    $headers array of returned headers like from getLastHeaders()
     * @param  string      $name    header name (case insensitive)
     */
    public static function findHeaderValue(array $headers, string $name): ?string
    {
        $value = null;
        foreach ($headers as $header) {
            if (Preg::isMatch('{^'.preg_quote($name).':\s*(.+?)\s*$}i', $header, $match)) {
                $value = $match[1];
            }
        }

        return $value;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util\Http;

/**
 * @phpstan-type CurlInfo array{url: mixed, content_type: mixed, http_code: mixed, header_size: mixed, request_size: mixed, filetime: mixed, ssl_verify_result: mixed, redirect_count: mixed, total_time: mixed, namelookup_time: mixed, connect_time: mixed, pretransfer_time: mixed, size_upload: mixed, size_download: mixed, speed_download: mixed, speed_upload: mixed, download_content_length: mixed, upload_content_length: mixed, starttransfer_time: mixed, redirect_time: mixed, certinfo: mixed, primary_ip: mixed, primary_port: mixed, local_ip: mixed, local_port: mixed, redirect_url: mixed}
 */
class CurlResponse extends Response
{
    /**
     * @see https://www.php.net/curl_getinfo
     * @var array
     * @phpstan-var CurlInfo
     */
    private $curlInfo;

    /**
     * @phpstan-param CurlInfo $curlInfo
     */
    public function __construct(array $request, ?int $code, array $headers, ?string $body, array $curlInfo)
    {
        parent::__construct($request, $code, $headers, $body);
        $this->curlInfo = $curlInfo;
    }

    /**
     * @phpstan-return CurlInfo
     */
    public function getCurlInfo(): array
    {
        return $this->curlInfo;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Pcre\Preg;

/**
 * Platform helper for uniform platform-specific tests.
 *
 * @author Niels Keurentjes <niels.keurentjes@omines.com>
 */
class Platform
{
    /** @var ?bool */
    private static $isVirtualBoxGuest = null;
    /** @var ?bool */
    private static $isWindowsSubsystemForLinux = null;
    /** @var ?bool */
    private static $isDocker = null;

    /**
     * getcwd() equivalent which always returns a string
     *
     * @throws \RuntimeException
     */
    public static function getCwd(bool $allowEmpty = false): string
    {
        $cwd = getcwd();

        // fallback to realpath('') just in case this works but odds are it would break as well if we are in a case where getcwd fails
        if (false === $cwd) {
            $cwd = realpath('');
        }

        // crappy state, assume '' and hopefully relative paths allow things to continue
        if (false === $cwd) {
            if ($allowEmpty) {
                return '';
            }

            throw new \RuntimeException('Could not determine the current working directory');
        }

        return $cwd;
    }

    /**
     * Infallible realpath version that falls back on the given $path if realpath is not working
     */
    public static function realpath(string $path): string
    {
        $realPath = realpath($path);
        if ($realPath === false) {
            return $path;
        }

        return $realPath;
    }

    /**
     * getenv() equivalent but reads from the runtime global variables first
     *
     * @param non-empty-string $name
     *
     * @return string|false
     */
    public static function getEnv(string $name)
    {
        if (array_key_exists($name, $_SERVER)) {
            return (string) $_SERVER[$name];
        }
        if (array_key_exists($name, $_ENV)) {
            return (string) $_ENV[$name];
        }

        return getenv($name);
    }

    /**
     * putenv() equivalent but updates the runtime global variables too
     */
    public static function putEnv(string $name, string $value): void
    {
        putenv($name . '=' . $value);
        $_SERVER[$name] = $_ENV[$name] = $value;
    }

    /**
     * putenv('X') equivalent but updates the runtime global variables too
     */
    public static function clearEnv(string $name): void
    {
        putenv($name);
        unset($_SERVER[$name], $_ENV[$name]);
    }

    /**
     * Parses tildes and environment variables in paths.
     */
    public static function expandPath(string $path): string
    {
        if (Preg::isMatch('#^~[\\/]#', $path)) {
            return self::getUserDirectory() . substr($path, 1);
        }

        return Preg::replaceCallback('#^(\$|(?P<percent>%))(?P<var>\w++)(?(percent)%)(?P<path>.*)#', static function ($matches): string {
            // Treat HOME as an alias for USERPROFILE on Windows for legacy reasons
            if (Platform::isWindows() && $matches['var'] === 'HOME') {
                if ((bool) Platform::getEnv('HOME')) {
                    return Platform::getEnv('HOME') . $matches['path'];
                }

                return Platform::getEnv('USERPROFILE') . $matches['path'];
            }

            return Platform::getEnv($matches['var']) . $matches['path'];
        }, $path);
    }

    /**
     * @throws \RuntimeException If the user home could not reliably be determined
     * @return string            The formal user home as detected from environment parameters
     */
    public static function getUserDirectory(): string
    {
        if (false !== ($home = self::getEnv('HOME'))) {
            return $home;
        }

        if (self::isWindows() && false !== ($home = self::getEnv('USERPROFILE'))) {
            return $home;
        }

        if (\function_exists('posix_getuid') && \function_exists('posix_getpwuid')) {
            $info = posix_getpwuid(posix_getuid());

            if (is_array($info)) {
                return $info['dir'];
            }
        }

        throw new \RuntimeException('Could not determine user directory');
    }

    /**
     * @return bool Whether the host machine is running on the Windows Subsystem for Linux (WSL)
     */
    public static function isWindowsSubsystemForLinux(): bool
    {
        if (null === self::$isWindowsSubsystemForLinux) {
            self::$isWindowsSubsystemForLinux = false;

            // while WSL will be hosted within windows, WSL itself cannot be windows based itself.
            if (self::isWindows()) {
                return self::$isWindowsSubsystemForLinux = false;
            }

            if (
                !(bool) ini_get('open_basedir')
                && is_readable('/proc/version')
                && false !== stripos((string) Silencer::call('file_get_contents', '/proc/version'), 'microsoft')
                && !self::isDocker() // Docker and Podman running inside WSL should not be seen as WSL
            ) {
                return self::$isWindowsSubsystemForLinux = true;
            }
        }

        return self::$isWindowsSubsystemForLinux;
    }

    /**
     * @return bool Whether the host machine is running a Windows OS
     */
    public static function isWindows(): bool
    {
        return \defined('PHP_WINDOWS_VERSION_BUILD');
    }

    public static function isDocker(): bool
    {
        if (null !== self::$isDocker) {
            return self::$isDocker;
        }

        // cannot check so assume no
        if ((bool) ini_get('open_basedir')) {
            return self::$isDocker = false;
        }

        // .dockerenv and .containerenv are present in some cases but not reliably
        if (file_exists('/.dockerenv') || file_exists('/run/.containerenv') || file_exists('/var/run/.containerenv')) {
            return self::$isDocker = true;
        }

        // see https://www.baeldung.com/linux/is-process-running-inside-container
        $cgroups = [
            '/proc/self/mountinfo', // cgroup v2
            '/proc/1/cgroup', // cgroup v1
        ];
        foreach ($cgroups as $cgroup) {
            if (!is_readable($cgroup)) {
                continue;
            }
            // suppress errors as some environments have these files as readable but system restrictions prevent the read from succeeding
            // see https://github.com/composer/composer/issues/12095
            try {
                $data = @file_get_contents($cgroup);
            } catch (\Throwable $e) {
                break;
            }
            if (!is_string($data)) {
                continue;
            }
            // detect default mount points created by Docker/containerd
            if (str_contains($data, '/var/lib/docker/') || str_contains($data, '/io.containerd.snapshotter')) {
                return self::$isDocker = true;
            }
        }

        return self::$isDocker = false;
    }

    /**
     * @return int    return a guaranteed binary length of the string, regardless of silly mbstring configs
     */
    public static function strlen(string $str): int
    {
        static $useMbString = null;
        if (null === $useMbString) {
            $useMbString = \function_exists('mb_strlen') && (bool) ini_get('mbstring.func_overload');
        }

        if ($useMbString) {
            return mb_strlen($str, '8bit');
        }

        return \strlen($str);
    }

    /**
     * @param  ?resource $fd Open file descriptor or null to default to STDOUT
     */
    public static function isTty($fd = null): bool
    {
        if ($fd === null) {
            $fd = defined('STDOUT') ? STDOUT : fopen('php://stdout', 'w');
            if ($fd === false) {
                return false;
            }
        }

        // detect msysgit/mingw and assume this is a tty because detection
        // does not work correctly, see https://github.com/composer/composer/issues/9690
        if (in_array(strtoupper((string) self::getEnv('MSYSTEM')), ['MINGW32', 'MINGW64'], true)) {
            return true;
        }

        // modern cross-platform function, includes the fstat
        // fallback so if it is present we trust it
        if (function_exists('stream_isatty')) {
            return stream_isatty($fd);
        }

        // only trusting this if it is positive, otherwise prefer fstat fallback
        if (function_exists('posix_isatty') && posix_isatty($fd)) {
            return true;
        }

        $stat = @fstat($fd);
        if ($stat === false) {
            return false;
        }

        // Check if formatted mode is S_IFCHR
        return 0020000 === ($stat['mode'] & 0170000);
    }

    /**
     * @return bool Whether the current command is for bash completion
     */
    public static function isInputCompletionProcess(): bool
    {
        return '_complete' === ($_SERVER['argv'][1] ?? null);
    }

    public static function workaroundFilesystemIssues(): void
    {
        if (self::isVirtualBoxGuest()) {
            usleep(200000);
        }
    }

    /**
     * Attempts detection of VirtualBox guest VMs
     *
     * This works based on the process' user being "vagrant", the COMPOSER_RUNTIME_ENV env var being set to "virtualbox", or lsmod showing the virtualbox guest additions are loaded
     */
    private static function isVirtualBoxGuest(): bool
    {
        if (null === self::$isVirtualBoxGuest) {
            self::$isVirtualBoxGuest = false;
            if (self::isWindows()) {
                return self::$isVirtualBoxGuest;
            }

            if (function_exists('posix_getpwuid') && function_exists('posix_geteuid')) {
                $processUser = posix_getpwuid(posix_geteuid());
                if (is_array($processUser) && $processUser['name'] === 'vagrant') {
                    return self::$isVirtualBoxGuest = true;
                }
            }

            if (self::getEnv('COMPOSER_RUNTIME_ENV') === 'virtualbox') {
                return self::$isVirtualBoxGuest = true;
            }

            if (defined('PHP_OS_FAMILY') && PHP_OS_FAMILY === 'Linux') {
                $process = new ProcessExecutor();
                try {
                    if (0 === $process->execute(['lsmod'], $output) && str_contains($output, 'vboxguest')) {
                        return self::$isVirtualBoxGuest = true;
                    }
                } catch (\Exception $e) {
                    // noop
                }
            }
        }

        return self::$isVirtualBoxGuest;
    }

    /**
     * @return 'NUL'|'/dev/null'
     */
    public static function getDevNull(): string
    {
        if (self::isWindows()) {
            return 'NUL';
        }

        return '/dev/null';
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Pcre\Preg;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Git
{
    /** @var string|false|null */
    private static $version = false;

    /** @var IOInterface */
    protected $io;
    /** @var Config */
    protected $config;
    /** @var ProcessExecutor */
    protected $process;
    /** @var Filesystem */
    protected $filesystem;
    /** @var HttpDownloader */
    protected $httpDownloader;

    public function __construct(IOInterface $io, Config $config, ProcessExecutor $process, Filesystem $fs)
    {
        $this->io = $io;
        $this->config = $config;
        $this->process = $process;
        $this->filesystem = $fs;
    }

    /**
     * @param IOInterface|null $io If present, a warning is output there instead of throwing, so pass this in only for cases where this is a soft failure
     */
    public static function checkForRepoOwnershipError(string $output, string $path, ?IOInterface $io = null): void
    {
        if (str_contains($output, 'fatal: detected dubious ownership')) {
            $msg = 'The repository at "' . $path . '" does not have the correct ownership and git refuses to use it:' . PHP_EOL . PHP_EOL . $output;
            if ($io === null) {
                throw new \RuntimeException($msg);
            }
            $io->writeError('<warning>'.$msg.'</warning>');
        }
    }

    public function setHttpDownloader(HttpDownloader $httpDownloader): void
    {
        $this->httpDownloader = $httpDownloader;
    }

    /**
     * Runs a set of commands using the $url or a variation of it (with auth, ssh, ..)
     *
     * Commands should use %url% placeholders for the URL instead of inlining it to allow this function to do its job
     * %sanitizedUrl% is also automatically replaced by the url without user/pass
     *
     * As soon as a single command fails it will halt, so assume the commands are run as && in bash
     *
     * @param non-empty-array<non-empty-list<string>> $commands
     * @param mixed $commandOutput  the output will be written into this var if passed by ref
     *                              if a callable is passed it will be used as output handler
     */
    public function runCommands(array $commands, string $url, ?string $cwd, bool $initialClone = false, &$commandOutput = null): void
    {
        $callables = [];
        foreach ($commands as $cmd) {
            $callables[] = static function (string $url) use ($cmd): array {
                $map = [
                    '%url%' => $url,
                    '%sanitizedUrl%' => Preg::replace('{://([^@]+?):(.+?)@}', '://', $url),
                ];

                return array_map(static function ($value) use ($map): string {
                    return $map[$value] ?? $value;
                }, $cmd);
            };
        }

        // @phpstan-ignore method.deprecated
        $this->runCommand($callables, $url, $cwd, $initialClone, $commandOutput);
    }

    /**
     * @param callable|array<callable> $commandCallable
     * @param mixed       $commandOutput  the output will be written into this var if passed by ref
     *                                    if a callable is passed it will be used as output handler
     * @deprecated Use runCommands with placeholders instead of callbacks for simplicity
     */
    public function runCommand($commandCallable, string $url, ?string $cwd, bool $initialClone = false, &$commandOutput = null): void
    {
        $commandCallables = is_callable($commandCallable) ? [$commandCallable] : $commandCallable;
        $lastCommand = '';

        // Ensure we are allowed to use this URL by config
        $this->config->prohibitUrlByConfig($url, $this->io);

        if ($initialClone) {
            $origCwd = $cwd;
        }

        $runCommands = function ($url) use ($commandCallables, $cwd, &$commandOutput, &$lastCommand, $initialClone) {
            $collectOutputs = !is_callable($commandOutput);
            $outputs = [];

            $status = 0;
            $counter = 0;
            foreach ($commandCallables as $callable) {
                $lastCommand = $callable($url);
                if ($collectOutputs) {
                    $outputs[] = '';
                    $output = &$outputs[count($outputs) - 1];
                } else {
                    $output = &$commandOutput;
                }
                $status = $this->process->execute($lastCommand, $output, $initialClone && $counter === 0 ? null : $cwd);
                if ($status !== 0) {
                    break;
                }
                $counter++;
            }

            if ($collectOutputs) {
                $commandOutput = implode('', $outputs);
            }

            return $status;
        };

        if (Preg::isMatch('{^ssh://[^@]+@[^:]+:[^0-9]+}', $url)) {
            throw new \InvalidArgumentException('The source URL ' . $url . ' is invalid, ssh URLs should have a port number after ":".' . "\n" . 'Use ssh://git@example.com:22/path or just git@example.com:path if you do not want to provide a password or custom port.');
        }

        if (!$initialClone) {
            // capture username/password from URL if there is one and we have no auth configured yet
            $this->process->execute(['git', 'remote', '-v'], $output, $cwd);
            if (Preg::isMatchStrictGroups('{^(?:composer|origin)\s+https?://(.+):(.+)@([^/]+)}im', $output, $match) && !$this->io->hasAuthentication($match[3])) {
                $this->io->setAuthentication($match[3], rawurldecode($match[1]), rawurldecode($match[2]));
            }
        }

        $protocols = $this->config->get('github-protocols');
        // public github, autoswitch protocols
        // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups
        if (Preg::isMatchStrictGroups('{^(?:https?|git)://' . self::getGitHubDomainsRegex($this->config) . '/(.*)}', $url, $match)) {
            $messages = [];
            foreach ($protocols as $protocol) {
                if ('ssh' === $protocol) {
                    $protoUrl = "git@" . $match[1] . ":" . $match[2];
                } else {
                    $protoUrl = $protocol . "://" . $match[1] . "/" . $match[2];
                }

                if (0 === $runCommands($protoUrl)) {
                    return;
                }
                $messages[] = '- ' . $protoUrl . "\n" . Preg::replace('#^#m', '  ', $this->process->getErrorOutput());

                if ($initialClone && isset($origCwd)) {
                    $this->filesystem->removeDirectory($origCwd);
                }
            }

            // failed to checkout, first check git accessibility
            if (!$this->io->hasAuthentication($match[1]) && !$this->io->isInteractive()) {
                $this->throwException('Failed to clone ' . $url . ' via ' . implode(', ', $protocols) . ' protocols, aborting.' . "\n\n" . implode("\n", $messages), $url);
            }
        }

        // if we have a private github url and the ssh protocol is disabled then we skip it and directly fallback to https
        $bypassSshForGitHub = Preg::isMatch('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url) && !in_array('ssh', $protocols, true);

        $auth = null;
        $credentials = [];
        if ($bypassSshForGitHub || 0 !== $runCommands($url)) {
            $errorMsg = $this->process->getErrorOutput();
            // private github repository without ssh key access, try https with auth
            // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups
            if (Preg::isMatchStrictGroups('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url, $match)
                // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups
                || Preg::isMatchStrictGroups('{^https?://' . self::getGitHubDomainsRegex($this->config) . '/(.*?)(?:\.git)?$}i', $url, $match)
            ) {
                if (!$this->io->hasAuthentication($match[1])) {
                    $gitHubUtil = new GitHub($this->io, $this->config, $this->process);
                    $message = 'Cloning failed using an ssh key for authentication, enter your GitHub credentials to access private repos';

                    if (!$gitHubUtil->authorizeOAuth($match[1]) && $this->io->isInteractive()) {
                        $gitHubUtil->authorizeOAuthInteractively($match[1], $message);
                    }
                }

                if ($this->io->hasAuthentication($match[1])) {
                    $auth = $this->io->getAuthentication($match[1]);
                    $authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[1] . '/' . $match[2] . '.git';
                    if (0 === $runCommands($authUrl)) {
                        return;
                    }

                    $credentials = [rawurlencode($auth['username']), rawurlencode($auth['password'])];
                    $errorMsg = $this->process->getErrorOutput();
                }
            // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups
            } elseif (
                Preg::isMatchStrictGroups('{^(https?)://(bitbucket\.org)/(.*?)(?:\.git)?$}i', $url, $match)
                || Preg::isMatchStrictGroups('{^(git)@(bitbucket\.org):(.+?\.git)$}i', $url, $match)
            ) { //bitbucket either through oauth or app password, with fallback to ssh.
                $bitbucketUtil = new Bitbucket($this->io, $this->config, $this->process, $this->httpDownloader);

                $domain = $match[2];
                $repo_with_git_part = $match[3];
                if (!str_ends_with($repo_with_git_part, '.git')) {
                    $repo_with_git_part .= '.git';
                }
                if (!$this->io->hasAuthentication($domain)) {
                    $message = 'Enter your Bitbucket credentials to access private repos';

                    if (!$bitbucketUtil->authorizeOAuth($domain) && $this->io->isInteractive()) {
                        $bitbucketUtil->authorizeOAuthInteractively($domain, $message);
                        $accessToken = $bitbucketUtil->getToken();
                        $this->io->setAuthentication($domain, 'x-token-auth', $accessToken);
                    }
                }

                // First we try to authenticate with whatever we have stored.
                // This will be successful if there is for example an app
                // password in there.
                if ($this->io->hasAuthentication($domain)) {
                    $auth = $this->io->getAuthentication($domain);

                    // Bitbucket API tokens use the email address as the username for HTTP API calls and
                    // either the Bitbucket username or 'x-bitbucket-api-token-auth' as the username for git operations.
                    if (strpos((string) $auth['password'], 'ATAT') === 0) {
                        $auth['username'] = 'x-bitbucket-api-token-auth';
                    }

                    $authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $domain . '/' . $repo_with_git_part;

                    if (0 === $runCommands($authUrl)) {
                        // Well if that succeeded on our first try, let's just
                        // take the win.
                        return;
                    }

                    //We already have an access_token from a previous request.
                    if ($auth['username'] !== 'x-token-auth') {
                        $accessToken = $bitbucketUtil->requestToken($domain, $auth['username'], $auth['password']);
                        if (!empty($accessToken)) {
                            $this->io->setAuthentication($domain, 'x-token-auth', $accessToken);
                        }
                    }
                }

                if ($this->io->hasAuthentication($domain)) {
                    $auth = $this->io->getAuthentication($domain);
                    $authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $domain . '/' . $repo_with_git_part;
                    if (0 === $runCommands($authUrl)) {
                        return;
                    }

                    $credentials = [rawurlencode($auth['username']), rawurlencode($auth['password'])];
                }
                //Falling back to ssh
                $sshUrl = 'git@bitbucket.org:' . $repo_with_git_part;
                $this->io->writeError('    No bitbucket authentication configured. Falling back to ssh.');
                if (0 === $runCommands($sshUrl)) {
                    return;
                }

                $errorMsg = $this->process->getErrorOutput();
            } elseif (
                // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups
                Preg::isMatchStrictGroups('{^(git)@' . self::getGitLabDomainsRegex($this->config) . ':(.+?\.git)$}i', $url, $match)
                // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups
                || Preg::isMatchStrictGroups('{^(https?)://' . self::getGitLabDomainsRegex($this->config) . '/(.*)}i', $url, $match)
            ) {
                if ($match[1] === 'git') {
                    $match[1] = 'https';
                }

                if (!$this->io->hasAuthentication($match[2])) {
                    $gitLabUtil = new GitLab($this->io, $this->config, $this->process);
                    $message = 'Cloning failed, enter your GitLab credentials to access private repos';

                    if (!$gitLabUtil->authorizeOAuth($match[2]) && $this->io->isInteractive()) {
                        $gitLabUtil->authorizeOAuthInteractively($match[1], $match[2], $message);
                    }
                }

                if ($this->io->hasAuthentication($match[2])) {
                    $auth = $this->io->getAuthentication($match[2]);
                    if ($auth['password'] === 'private-token' || $auth['password'] === 'oauth2' || $auth['password'] === 'gitlab-ci-token') {
                        $authUrl = $match[1] . '://' . rawurlencode($auth['password']) . ':' . rawurlencode((string) $auth['username']) . '@' . $match[2] . '/' . $match[3]; // swap username and password
                    } else {
                        $authUrl = $match[1] . '://' . rawurlencode((string) $auth['username']) . ':' . rawurlencode((string) $auth['password']) . '@' . $match[2] . '/' . $match[3];
                    }

                    if (0 === $runCommands($authUrl)) {
                        return;
                    }

                    $credentials = [rawurlencode((string) $auth['username']), rawurlencode((string) $auth['password'])];
                    $errorMsg = $this->process->getErrorOutput();
                }
            } elseif (null !== ($match = $this->getAuthenticationFailure($url))) { // private non-github/gitlab/bitbucket repo that failed to authenticate
                if (str_contains($match[2], '@')) {
                    [$authParts, $match[2]] = explode('@', $match[2], 2);
                }

                $storeAuth = false;
                if ($this->io->hasAuthentication($match[2])) {
                    $auth = $this->io->getAuthentication($match[2]);
                } elseif ($this->io->isInteractive()) {
                    $defaultUsername = null;
                    if (isset($authParts) && $authParts !== '') {
                        if (str_contains($authParts, ':')) {
                            [$defaultUsername] = explode(':', $authParts, 2);
                        } else {
                            $defaultUsername = $authParts;
                        }
                    }

                    $this->io->writeError('    Authentication required (<info>' . $match[2] . '</info>):');
                    $this->io->writeError('<warning>' . trim($errorMsg) . '</warning>', true, IOInterface::VERBOSE);
                    $auth = [
                        'username' => $this->io->ask('      Username: ', $defaultUsername),
                        'password' => $this->io->askAndHideAnswer('      Password: '),
                    ];
                    $storeAuth = $this->config->get('store-auths');
                }

                if (null !== $auth) {
                    $authUrl = $match[1] . rawurlencode((string) $auth['username']) . ':' . rawurlencode((string) $auth['password']) . '@' . $match[2] . $match[3];

                    if (0 === $runCommands($authUrl)) {
                        $this->io->setAuthentication($match[2], $auth['username'], $auth['password']);
                        $authHelper = new AuthHelper($this->io, $this->config);
                        $authHelper->storeAuth($match[2], $storeAuth);

                        return;
                    }

                    $credentials = [rawurlencode((string) $auth['username']), rawurlencode((string) $auth['password'])];
                    $errorMsg = $this->process->getErrorOutput();
                }
            }

            if ($initialClone && isset($origCwd)) {
                $this->filesystem->removeDirectory($origCwd);
            }

            if (\is_array($lastCommand)) {
                $lastCommand = implode(' ', $lastCommand);
            }
            if (count($credentials) > 0) {
                $lastCommand = $this->maskCredentials($lastCommand, $credentials);
                $errorMsg = $this->maskCredentials($errorMsg, $credentials);
            }
            $this->throwException('Failed to execute ' . $lastCommand . "\n\n" . $errorMsg, $url);
        }
    }

    public function syncMirror(string $url, string $dir): bool
    {
        if ((bool) Platform::getEnv('COMPOSER_DISABLE_NETWORK') && Platform::getEnv('COMPOSER_DISABLE_NETWORK') !== 'prime') {
            $this->io->writeError('<warning>Aborting git mirror sync of '.$url.' as network is disabled</warning>');

            return false;
        }

        // update the repo if it is a valid git repository
        if (is_dir($dir) && 0 === $this->process->execute(['git', 'rev-parse', '--git-dir'], $output, $dir) && trim($output) === '.') {
            try {
                try {
                    $commands = [
                        ['git', 'remote', 'set-url', 'origin', '--', '%url%'],
                        ['git', 'remote', 'update', '--prune', 'origin'],
                        ['git', 'gc', '--auto'],
                    ];

                    $this->runCommands($commands, $url, $dir);
                } finally {
                    $this->runCommands([['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%']], $url, $dir);
                }
            } catch (\Exception $e) {
                $this->io->writeError('<error>Sync mirror failed: ' . $e->getMessage() . '</error>', true, IOInterface::DEBUG);

                return false;
            }

            return true;
        }
        self::checkForRepoOwnershipError($this->process->getErrorOutput(), $dir);

        // clean up directory and do a fresh clone into it
        $this->filesystem->removeDirectory($dir);

        $this->runCommands([['git', 'clone', '--mirror', '--', '%url%', $dir]], $url, $dir, true);

        $this->runCommands([['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%']], $url, $dir);

        return true;
    }

    public function fetchRefOrSyncMirror(string $url, string $dir, string $ref, ?string $prettyVersion = null): bool
    {
        if ($this->checkRefIsInMirror($dir, $ref)) {
            if (Preg::isMatch('{^[a-f0-9]{40}$}', $ref) && $prettyVersion !== null) {
                $branch = Preg::replace('{(?:^dev-|(?:\.x)?-dev$)}i', '', $prettyVersion);
                $branches = null;
                $tags = null;
                if (0 === $this->process->execute(['git', 'branch'], $output, $dir)) {
                    $branches = $output;
                }
                if (0 === $this->process->execute(['git', 'tag'], $output, $dir)) {
                    $tags = $output;
                }

                // if the pretty version cannot be found as a branch (nor branch with 'v' in front of the branch as it may have been stripped when generating pretty name),
                // nor as a tag, then we sync the mirror as otherwise it will likely fail during install.
                // this can occur if a git tag gets created *after* the reference is already put into the cache, as the ref check above will then not sync the new tags
                // see https://github.com/composer/composer/discussions/11002
                if (null !== $branches && !Preg::isMatch('{^[\s*]*v?'.preg_quote($branch).'$}m', $branches)
                    && null !== $tags && !Preg::isMatch('{^[\s*]*'.preg_quote($branch).'$}m', $tags)
                ) {
                    $this->syncMirror($url, $dir);
                }
            }

            return true;
        }

        if ($this->syncMirror($url, $dir)) {
            return $this->checkRefIsInMirror($dir, $ref);
        }

        return false;
    }

    public static function getNoShowSignatureFlag(ProcessExecutor $process): string
    {
        $gitVersion = self::getVersion($process);
        if ($gitVersion !== null && version_compare($gitVersion, '2.10.0-rc0', '>=')) {
            return ' --no-show-signature';
        }

        return '';
    }

    /**
     * @return list<string>
     */
    public static function getNoShowSignatureFlags(ProcessExecutor $process): array
    {
        $flags = static::getNoShowSignatureFlag($process);
        if ('' === $flags) {
            return [];
        }

        return explode(' ', substr($flags, 1));
    }

    /**
     * Checks if git version supports --no-commit-header flag (git 2.33+)
     *
     * @internal
     */
    public static function supportsNoCommitHeaderFlag(ProcessExecutor $process): bool
    {
        $gitVersion = self::getVersion($process);

        return $gitVersion !== null && version_compare($gitVersion, '2.33.0-rc0', '>=');
    }

    /**
     * Builds a git rev-list command with --no-commit-header flag when supported (git 2.33+)
     *
     * @internal
     * @param list<string> $arguments Additional arguments for git rev-list
     * @return non-empty-list<string>
     */
    public static function buildRevListCommand(ProcessExecutor $process, array $arguments): array
    {
        $command = ['git', 'rev-list'];
        if (self::supportsNoCommitHeaderFlag($process)) {
            $command[] = '--no-commit-header';
        }

        return array_merge($command, $arguments);
    }

    /**
     * Parses git rev-list output, removing 'commit <hash>' header lines for git < 2.33.
     *
     * When --no-commit-header is not available (git < 2.33), git rev-list --format outputs
     * "commit <hash>" before formatted output. This removes those lines.
     *
     * @internal
     */
    public static function parseRevListOutput(string $output, ProcessExecutor $process): string
    {
        // If git supports --no-commit-header, output is already clean
        if (self::supportsNoCommitHeaderFlag($process)) {
            return $output;
        }

        // Filter out "commit <hash>" lines for older git versions
        return Preg::replace('{^commit [a-f0-9]{40}\n?}m', '', $output);
    }

    private function checkRefIsInMirror(string $dir, string $ref): bool
    {
        if (is_dir($dir) && 0 === $this->process->execute(['git', 'rev-parse', '--git-dir'], $output, $dir) && trim($output) === '.') {
            $exitCode = $this->process->execute(['git', 'rev-parse', '--quiet', '--verify', $ref.'^{commit}'], $ignoredOutput, $dir);
            if ($exitCode === 0) {
                return true;
            }
        }
        self::checkForRepoOwnershipError($this->process->getErrorOutput(), $dir);

        return false;
    }

    /**
     * @return array<int, string>|null
     */
    private function getAuthenticationFailure(string $url): ?array
    {
        if (!Preg::isMatchStrictGroups('{^(https?://)([^/]+)(.*)$}i', $url, $match)) {
            return null;
        }

        $authFailures = [
            'fatal: Authentication failed',
            'remote error: Invalid username or password.',
            'error: 401 Unauthorized',
            'fatal: unable to access',
            'fatal: could not read Username',
        ];

        $errorOutput = $this->process->getErrorOutput();
        foreach ($authFailures as $authFailure) {
            if (strpos($errorOutput, $authFailure) !== false) {
                return $match;
            }
        }

        return null;
    }

    public function getMirrorDefaultBranch(string $url, string $dir, bool $isLocalPathRepository): ?string
    {
        if ((bool) Platform::getEnv('COMPOSER_DISABLE_NETWORK')) {
            return null;
        }

        try {
            if ($isLocalPathRepository) {
                $this->process->execute(['git', 'remote', 'show', 'origin'], $output, $dir);
            } else {
                $commands = [
                    ['git', 'remote', 'set-url', 'origin', '--', '%url%'],
                    ['git', 'remote', 'show', 'origin'],
                    ['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%'],
                ];

                $this->runCommands($commands, $url, $dir, false, $output);
            }

            $lines = $this->process->splitLines($output);
            foreach ($lines as $line) {
                if (Preg::isMatch('{^\s*HEAD branch:\s(.+)\s*$}m', $line, $matches)) {
                    return $matches[1];
                }
            }
        } catch (\Exception $e) {
            $this->io->writeError('<error>Failed to fetch root identifier from remote: ' . $e->getMessage() . '</error>', true, IOInterface::DEBUG);
        }

        return null;
    }

    public static function cleanEnv(?ProcessExecutor $process = null): void
    {
        $gitVersion = self::getVersion($process ?? new ProcessExecutor());
        if ($gitVersion !== null && version_compare($gitVersion, '2.3.0', '>=')) {
            // added in git 2.3.0, prevents prompting the user for username/password
            if (Platform::getEnv('GIT_TERMINAL_PROMPT') !== '0') {
                Platform::putEnv('GIT_TERMINAL_PROMPT', '0');
            }
        } else {
            // added in git 1.7.1, prevents prompting the user for username/password
            if (Platform::getEnv('GIT_ASKPASS') !== 'echo') {
                Platform::putEnv('GIT_ASKPASS', 'echo');
            }
        }

        // clean up rogue git env vars in case this is running in a git hook
        if (Platform::getEnv('GIT_DIR')) {
            Platform::clearEnv('GIT_DIR');
        }
        if (Platform::getEnv('GIT_WORK_TREE')) {
            Platform::clearEnv('GIT_WORK_TREE');
        }

        // Run processes with predictable LANGUAGE
        if (Platform::getEnv('LANGUAGE') !== 'C') {
            Platform::putEnv('LANGUAGE', 'C');
        }

        // clean up env for OSX, see https://github.com/composer/composer/issues/2146#issuecomment-35478940
        Platform::clearEnv('DYLD_LIBRARY_PATH');
    }

    /**
     * @return non-empty-string
     */
    public static function getGitHubDomainsRegex(Config $config): string
    {
        return '(' . implode('|', array_map('preg_quote', $config->get('github-domains'))) . ')';
    }

    /**
     * @return non-empty-string
     */
    public static function getGitLabDomainsRegex(Config $config): string
    {
        return '(' . implode('|', array_map('preg_quote', $config->get('gitlab-domains'))) . ')';
    }

    /**
     * @param non-empty-string $message
     *
     * @return never
     */
    private function throwException($message, string $url): void
    {
        // git might delete a directory when it fails and php will not know
        clearstatcache();

        if (0 !== $this->process->execute(['git', '--version'], $ignoredOutput)) {
            throw new \RuntimeException(Url::sanitize('Failed to clone ' . $url . ', git was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput()));
        }

        throw new \RuntimeException(Url::sanitize($message));
    }

    /**
     * Retrieves the current git version.
     *
     * @return string|null The git version number, if present.
     */
    public static function getVersion(ProcessExecutor $process): ?string
    {
        if (false === self::$version) {
            self::$version = null;
            if (0 === $process->execute(['git', '--version'], $output) && Preg::isMatch('/^git version (\d+(?:\.\d+)+)/m', $output, $matches)) {
                self::$version = $matches[1];
            }
        }

        return self::$version;
    }

    /**
     * @param string[] $credentials
     */
    private function maskCredentials(string $error, array $credentials): string
    {
        $maskedCredentials = [];

        foreach ($credentials as $credential) {
            if (in_array($credential, ['private-token', 'x-token-auth', 'oauth2', 'gitlab-ci-token', 'x-oauth-basic'])) {
                $maskedCredentials[] = $credential;
            } elseif (strlen($credential) > 6) {
                $maskedCredentials[] = substr($credential, 0, 3) . '...' . substr($credential, -3);
            } elseif (strlen($credential) > 3) {
                $maskedCredentials[] = substr($credential, 0, 3) . '...';
            } else {
                $maskedCredentials[] = 'XXX';
            }
        }

        return str_replace($credentials, $maskedCredentials, $error);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\IO\IOInterface;
use Composer\Pcre\Preg;
use Symfony\Component\Process\ExecutableFinder;
use Symfony\Component\Process\Process;

/**
 * @author Matt Whittom <Matt.Whittom@veteransunited.com>
 *
 * @phpstan-type RepoConfig array{unique_perforce_client_name?: string, depot?: string, branch?: string, p4user?: string, p4password?: string}
 */
class Perforce
{
    /** @var string */
    protected $path;
    /** @var ?string */
    protected $p4Depot;
    /** @var ?string */
    protected $p4Client;
    /** @var ?string */
    protected $p4User;
    /** @var ?string */
    protected $p4Password;
    /** @var string */
    protected $p4Port;
    /** @var ?string */
    protected $p4Stream;
    /** @var string */
    protected $p4ClientSpec;
    /** @var ?string */
    protected $p4DepotType;
    /** @var ?string */
    protected $p4Branch;
    /** @var ProcessExecutor */
    protected $process;
    /** @var string */
    protected $uniquePerforceClientName;
    /** @var bool */
    protected $windowsFlag;
    /** @var string */
    protected $commandResult;

    /** @var IOInterface */
    protected $io;

    /** @var ?Filesystem */
    protected $filesystem;

    /**
     * @phpstan-param RepoConfig $repoConfig
     */
    public function __construct($repoConfig, string $port, string $path, ProcessExecutor $process, bool $isWindows, IOInterface $io)
    {
        $this->windowsFlag = $isWindows;
        $this->p4Port = $port;
        $this->initializePath($path);
        $this->process = $process;
        $this->initialize($repoConfig);
        $this->io = $io;
    }

    /**
     * @phpstan-param RepoConfig $repoConfig
     */
    public static function create($repoConfig, string $port, string $path, ProcessExecutor $process, IOInterface $io): self
    {
        return new Perforce($repoConfig, $port, $path, $process, Platform::isWindows(), $io);
    }

    public static function checkServerExists(string $url, ProcessExecutor $processExecutor): bool
    {
        return 0 === $processExecutor->execute(['p4', '-p', $url, 'info', '-s'], $ignoredOutput);
    }

    /**
     * @phpstan-param RepoConfig $repoConfig
     */
    public function initialize($repoConfig): void
    {
        $this->uniquePerforceClientName = $this->generateUniquePerforceClientName();
        if (!$repoConfig) {
            return;
        }
        if (isset($repoConfig['unique_perforce_client_name'])) {
            $this->uniquePerforceClientName = $repoConfig['unique_perforce_client_name'];
        }

        if (isset($repoConfig['depot'])) {
            $this->p4Depot = $repoConfig['depot'];
        }
        if (isset($repoConfig['branch'])) {
            $this->p4Branch = $repoConfig['branch'];
        }
        if (isset($repoConfig['p4user'])) {
            $this->p4User = $repoConfig['p4user'];
        } else {
            $this->p4User = $this->getP4variable('P4USER');
        }
        if (isset($repoConfig['p4password'])) {
            $this->p4Password = $repoConfig['p4password'];
        }
    }

    public function initializeDepotAndBranch(?string $depot, ?string $branch): void
    {
        if (isset($depot)) {
            $this->p4Depot = $depot;
        }
        if (isset($branch)) {
            $this->p4Branch = $branch;
        }
    }

    /**
     * @return non-empty-string
     */
    public function generateUniquePerforceClientName(): string
    {
        return gethostname() . "_" . time();
    }

    public function cleanupClientSpec(): void
    {
        $client = $this->getClient();
        $task = ['client', '-d', $client];
        $useP4Client = false;
        $command = $this->generateP4Command($task, $useP4Client);
        $this->executeCommand($command);
        $clientSpec = $this->getP4ClientSpec();
        $fileSystem = $this->getFilesystem();
        $fileSystem->remove($clientSpec);
    }

    /**
     * @param non-empty-string|non-empty-list<string> $command
     */
    protected function executeCommand($command): int
    {
        $this->commandResult = '';

        return $this->process->execute($command, $this->commandResult);
    }

    public function getClient(): string
    {
        if (!isset($this->p4Client)) {
            $cleanStreamName = str_replace(['//', '/', '@'], ['', '_', ''], $this->getStream());
            $this->p4Client = 'composer_perforce_' . $this->uniquePerforceClientName . '_' . $cleanStreamName;
        }

        return $this->p4Client;
    }

    protected function getPath(): string
    {
        return $this->path;
    }

    public function initializePath(string $path): void
    {
        $this->path = $path;
        $fs = $this->getFilesystem();
        $fs->ensureDirectoryExists($path);
    }

    protected function getPort(): string
    {
        return $this->p4Port;
    }

    public function setStream(string $stream): void
    {
        $this->p4Stream = $stream;
        $index = strrpos($stream, '/');
        //Stream format is //depot/stream, while non-streaming depot is //depot
        if ($index > 2) {
            $this->p4DepotType = 'stream';
        }
    }

    public function isStream(): bool
    {
        return is_string($this->p4DepotType) && (strcmp($this->p4DepotType, 'stream') === 0);
    }

    public function getStream(): string
    {
        if (!isset($this->p4Stream)) {
            if ($this->isStream()) {
                $this->p4Stream = '//' . $this->p4Depot . '/' . $this->p4Branch;
            } else {
                $this->p4Stream = '//' . $this->p4Depot;
            }
        }

        return $this->p4Stream;
    }

    public function getStreamWithoutLabel(string $stream): string
    {
        $index = strpos($stream, '@');
        if ($index === false) {
            return $stream;
        }

        return substr($stream, 0, $index);
    }

    /**
     * @return non-empty-string
     */
    public function getP4ClientSpec(): string
    {
        return $this->path . '/' . $this->getClient() . '.p4.spec';
    }

    public function getUser(): ?string
    {
        return $this->p4User;
    }

    public function setUser(?string $user): void
    {
        $this->p4User = $user;
    }

    public function queryP4User(): void
    {
        $this->getUser();
        if (strlen((string) $this->p4User) > 0) {
            return;
        }
        $this->p4User = $this->getP4variable('P4USER');
        if (strlen((string) $this->p4User) > 0) {
            return;
        }
        $this->p4User = $this->io->ask('Enter P4 User:');
        if ($this->windowsFlag) {
            $command = $this->getP4Executable().' set P4USER=' . ProcessExecutor::escape($this->p4User);
        } else {
            $command = 'export P4USER=' . ProcessExecutor::escape($this->p4User);
        }
        $this->executeCommand($command);
    }

    protected function getP4variable(string $name): ?string
    {
        if ($this->windowsFlag) {
            $command = $this->getP4Executable().' set';
            $this->executeCommand($command);
            $result = trim($this->commandResult);
            $resArray = explode(PHP_EOL, $result);
            foreach ($resArray as $line) {
                $fields = explode('=', $line);
                if (strcmp($name, $fields[0]) === 0) {
                    $index = strpos($fields[1], ' ');
                    if ($index === false) {
                        $value = $fields[1];
                    } else {
                        $value = substr($fields[1], 0, $index);
                    }
                    $value = trim($value);

                    return $value;
                }
            }

            return null;
        }

        $command = 'echo $' . $name;
        $this->executeCommand($command);
        $result = trim($this->commandResult);

        return $result;
    }

    public function queryP4Password(): ?string
    {
        if (isset($this->p4Password)) {
            return $this->p4Password;
        }
        $password = $this->getP4variable('P4PASSWD');
        if (strlen((string) $password) <= 0) {
            $password = $this->io->askAndHideAnswer('Enter password for Perforce user ' . $this->getUser() . ': ');
        }
        $this->p4Password = $password;

        return $password;
    }

    /**
     * @internal
     * @param non-empty-list<string> $arguments Additional arguments for git rev-list
     * @return non-empty-list<string>
     */
    public function generateP4Command(array $arguments, bool $useClient = true): array
    {
        $p4Command = [$this->getP4Executable()];
        if ($this->getUser() !== null) {
            $p4Command[] = '-u';
            $p4Command[] = $this->getUser();
        }
        if ($useClient) {
            $p4Command[] = '-c';
            $p4Command[] = $this->getClient();
        }
        $p4Command[] = '-p';
        $p4Command[] = $this->getPort();

        return array_merge($p4Command, $arguments);
    }

    public function isLoggedIn(): bool
    {
        $command = $this->generateP4Command(['login', '-s'], false);
        $exitCode = $this->executeCommand($command);
        if ($exitCode) {
            $errorOutput = $this->process->getErrorOutput();
            $index = strpos($errorOutput, $this->getUser());
            if ($index === false) {
                $index = strpos($errorOutput, 'p4');
                if ($index === false) {
                    return false;
                }
                throw new \Exception('p4 command not found in path: ' . $errorOutput);
            }
            throw new \Exception('Invalid user name: ' . $this->getUser());
        }

        return true;
    }

    public function connectClient(): void
    {
        $p4CreateClientCommand = $this->generateP4Command(['client', '-i']);

        $process = new Process($p4CreateClientCommand, null, null, file_get_contents($this->getP4ClientSpec()));
        $process->run();
    }

    public function syncCodeBase(?string $sourceReference): void
    {
        $prevDir = Platform::getCwd();
        chdir($this->path);
        $p4SyncCommand = $this->generateP4Command(['sync', '-f']);
        if (null !== $sourceReference) {
            $p4SyncCommand[] = '@' . $sourceReference;
        }
        $this->executeCommand($p4SyncCommand);
        chdir($prevDir);
    }

    /**
     * @param resource|false $spec
     */
    public function writeClientSpecToFile($spec): void
    {
        fwrite($spec, 'Client: ' . $this->getClient() . PHP_EOL . PHP_EOL);
        fwrite($spec, 'Update: ' . date('Y/m/d H:i:s') . PHP_EOL . PHP_EOL);
        fwrite($spec, 'Access: ' . date('Y/m/d H:i:s') . PHP_EOL);
        fwrite($spec, 'Owner:  ' . $this->getUser() . PHP_EOL . PHP_EOL);
        fwrite($spec, 'Description:' . PHP_EOL);
        fwrite($spec, '  Created by ' . $this->getUser() . ' from composer.' . PHP_EOL . PHP_EOL);
        fwrite($spec, 'Root: ' . $this->getPath() . PHP_EOL . PHP_EOL);
        fwrite($spec, 'Options:  noallwrite noclobber nocompress unlocked modtime rmdir' . PHP_EOL . PHP_EOL);
        fwrite($spec, 'SubmitOptions:  revertunchanged' . PHP_EOL . PHP_EOL);
        fwrite($spec, 'LineEnd:  local' . PHP_EOL . PHP_EOL);
        if ($this->isStream()) {
            fwrite($spec, 'Stream:' . PHP_EOL);
            fwrite($spec, '  ' . $this->getStreamWithoutLabel($this->p4Stream) . PHP_EOL);
        } else {
            fwrite(
                $spec,
                'View:  ' . $this->getStream() . '/...  //' . $this->getClient() . '/... ' . PHP_EOL
            );
        }
    }

    public function writeP4ClientSpec(): void
    {
        $clientSpec = $this->getP4ClientSpec();
        $spec = fopen($clientSpec, 'w');
        try {
            $this->writeClientSpecToFile($spec);
        } catch (\Exception $e) {
            fclose($spec);
            throw $e;
        }
        fclose($spec);
    }

    /**
     * @param resource $pipe
     * @param mixed    $name
     */
    protected function read($pipe, $name): void
    {
        if (feof($pipe)) {
            return;
        }
        $line = fgets($pipe);
        while ($line !== false) {
            $line = fgets($pipe);
        }
    }

    public function windowsLogin(?string $password): int
    {
        $command = $this->generateP4Command(['login', '-a']);

        $process = new Process($command, null, null, $password);

        return $process->run();
    }

    public function p4Login(): void
    {
        $this->queryP4User();
        if (!$this->isLoggedIn()) {
            $password = $this->queryP4Password();
            if ($this->windowsFlag) {
                $this->windowsLogin($password);
            } else {
                $command = $this->generateP4Command(['login', '-a'], false);

                $process = new Process($command, null, null, $password);
                $process->run();

                if (!$process->isSuccessful()) {
                    throw new \Exception("Error logging in:" . $this->process->getErrorOutput());
                }
            }
        }
    }

    /**
     * @return mixed[]|null
     */
    public function getComposerInformation(string $identifier): ?array
    {
        $composerFileContent = $this->getFileContent('composer.json', $identifier);

        if (!$composerFileContent) {
            return null;
        }

        return json_decode($composerFileContent, true);
    }

    public function getFileContent(string $file, string $identifier): ?string
    {
        $path = $this->getFilePath($file, $identifier);
        if ($path === null) {
            return null;
        }

        $command = $this->generateP4Command(['print', $path]);
        $this->executeCommand($command);
        $result = $this->commandResult;

        if (!trim($result)) {
            return null;
        }

        return $result;
    }

    public function getFilePath(string $file, string $identifier): ?string
    {
        $index = strpos($identifier, '@');
        if ($index === false) {
            return $identifier. '/' . $file;
        }

        $path = substr($identifier, 0, $index) . '/' . $file . substr($identifier, $index);
        $command = $this->generateP4Command(['files', $path], false);
        $this->executeCommand($command);
        $result = $this->commandResult;
        $index2 = strpos($result, 'no such file(s).');
        if ($index2 === false) {
            $index3 = strpos($result, 'change');
            if ($index3 !== false) {
                $phrase = trim(substr($result, $index3));
                $fields = explode(' ', $phrase);

                return substr($identifier, 0, $index) . '/' . $file . '@' . $fields[1];
            }
        }

        return null;
    }

    /**
     * @return array{master: string}
     */
    public function getBranches(): array
    {
        $possibleBranches = [];
        if (!$this->isStream()) {
            $possibleBranches[$this->p4Branch] = $this->getStream();
        } else {
            $command = $this->generateP4Command(['streams', '//' . $this->p4Depot . '/...']);
            $this->executeCommand($command);
            $result = $this->commandResult;
            $resArray = explode(PHP_EOL, $result);
            foreach ($resArray as $line) {
                $resBits = explode(' ', $line);
                if (count($resBits) > 4) {
                    $branch = Preg::replace('/[^A-Za-z0-9 ]/', '', $resBits[4]);
                    $possibleBranches[$branch] = $resBits[1];
                }
            }
        }
        $command = $this->generateP4Command(['changes', $this->getStream() . '/...'], false);
        $this->executeCommand($command);
        $result = $this->commandResult;
        $resArray = explode(PHP_EOL, $result);
        $lastCommit = $resArray[0];
        $lastCommitArr = explode(' ', $lastCommit);
        $lastCommitNum = $lastCommitArr[1];

        return ['master' => $possibleBranches[$this->p4Branch] . '@'. $lastCommitNum];
    }

    /**
     * @return array<string, string>
     */
    public function getTags(): array
    {
        $command = $this->generateP4Command(['labels']);
        $this->executeCommand($command);
        $result = $this->commandResult;
        $resArray = explode(PHP_EOL, $result);
        $tags = [];
        foreach ($resArray as $line) {
            if (strpos($line, 'Label') !== false) {
                $fields = explode(' ', $line);
                $tags[$fields[1]] = $this->getStream() . '@' . $fields[1];
            }
        }

        return $tags;
    }

    public function checkStream(): bool
    {
        $command = $this->generateP4Command(['depots'], false);
        $this->executeCommand($command);
        $result = $this->commandResult;
        $resArray = explode(PHP_EOL, $result);
        foreach ($resArray as $line) {
            if (strpos($line, 'Depot') !== false) {
                $fields = explode(' ', $line);
                if (strcmp($this->p4Depot, $fields[1]) === 0) {
                    $this->p4DepotType = $fields[3];

                    return $this->isStream();
                }
            }
        }

        return false;
    }

    /**
     * @return mixed|null
     */
    protected function getChangeList(string $reference): mixed
    {
        $index = strpos($reference, '@');
        if ($index === false) {
            return null;
        }
        $label = substr($reference, $index);
        $command = $this->generateP4Command(['changes', '-m1', $label]);
        $this->executeCommand($command);
        $changes = $this->commandResult;
        if (strpos($changes, 'Change') !== 0) {
            return null;
        }
        $fields = explode(' ', $changes);

        return $fields[1];
    }

    /**
     * @return mixed|null
     */
    public function getCommitLogs(string $fromReference, string $toReference): mixed
    {
        $fromChangeList = $this->getChangeList($fromReference);
        if ($fromChangeList === null) {
            return null;
        }
        $toChangeList = $this->getChangeList($toReference);
        if ($toChangeList === null) {
            return null;
        }
        $index = strpos($fromReference, '@');
        $main = substr($fromReference, 0, $index) . '/...';
        $command = $this->generateP4Command(['filelog', $main . '@' . $fromChangeList . ',' . $toChangeList]);
        $this->executeCommand($command);

        return $this->commandResult;
    }

    public function getFilesystem(): Filesystem
    {
        if (null === $this->filesystem) {
            $this->filesystem = new Filesystem($this->process);
        }

        return $this->filesystem;
    }

    public function setFilesystem(Filesystem $fs): void
    {
        $this->filesystem = $fs;
    }

    private function getP4Executable(): string
    {
        static $p4Executable;

        if ($p4Executable) {
            return $p4Executable;
        }

        $finder = new ExecutableFinder();

        return $p4Executable = $finder->find('p4') ?? 'p4';
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Pcre\Preg;

/**
 * @author Jonas Renaudot <jonas.renaudot@gmail.com>
 */
class Hg
{
    /** @var string|false|null */
    private static $version = false;

    /**
     * @var IOInterface
     */
    private $io;

    /**
     * @var Config
     */
    private $config;

    /**
     * @var ProcessExecutor
     */
    private $process;

    public function __construct(IOInterface $io, Config $config, ProcessExecutor $process)
    {
        $this->io = $io;
        $this->config = $config;
        $this->process = $process;
    }

    public function runCommand(callable $commandCallable, string $url, ?string $cwd): void
    {
        $this->config->prohibitUrlByConfig($url, $this->io);

        // Try as is
        $command = $commandCallable($url);

        if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
            return;
        }

        // Try with the authentication information available
        if (
            Preg::isMatch('{^(?P<proto>ssh|https?)://(?:(?P<user>[^:@]+)(?::(?P<pass>[^:@]+))?@)?(?P<host>[^/]+)(?P<path>/.*)?}mi', $url, $matches)
            && $this->io->hasAuthentication($matches['host'])
        ) {
            if ($matches['proto'] === 'ssh') {
                $user = '';
                if ($matches['user'] !== null) {
                    $user = rawurlencode($matches['user']) . '@';
                }
                $authenticatedUrl = $matches['proto'] . '://' . $user . $matches['host'] . $matches['path'];
            } else {
                $auth = $this->io->getAuthentication($matches['host']);
                $authenticatedUrl = $matches['proto'] . '://' . rawurlencode((string) $auth['username']) . ':' . rawurlencode((string) $auth['password']) . '@' . $matches['host'] . $matches['path'];
            }
            $command = $commandCallable($authenticatedUrl);

            if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
                return;
            }

            $error = $this->process->getErrorOutput();
        } else {
            $error = 'The given URL (' .$url. ') does not match the required format (ssh|http(s)://(username:password@)example.com/path-to-repository)';
        }

        $this->throwException("Failed to clone $url, \n\n" . $error, $url);
    }

    /**
     * @param non-empty-string $message
     *
     * @return never
     */
    private function throwException($message, string $url): void
    {
        if (null === self::getVersion($this->process)) {
            throw new \RuntimeException(Url::sanitize(
                'Failed to clone ' . $url . ', hg was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput()
            ));
        }

        throw new \RuntimeException(Url::sanitize($message));
    }

    /**
     * Retrieves the current hg version.
     *
     * @return string|null The hg version number, if present.
     */
    public static function getVersion(ProcessExecutor $process): ?string
    {
        if (false === self::$version) {
            self::$version = null;
            if (0 === $process->execute(['hg', '--version'], $output) && Preg::isMatch('/^.+? (\d+(?:\.\d+)+)(?:\+.*?)?\)?\r?\n/', $output, $matches)) {
                self::$version = $matches[1];
            }
        }

        return self::$version;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\CaBundle\CaBundle;
use Composer\Pcre\Preg;

/**
 * @author Chris Smith <chris@cs278.org>
 * @deprecated Use composer/ca-bundle and composer/composer 2.2 if you still need PHP 5 compatibility, this class will be removed in Composer 3.0
 */
final class TlsHelper
{
    /**
     * Match hostname against a certificate.
     *
     * @param mixed  $certificate X.509 certificate
     * @param string $hostname    Hostname in the URL
     * @param string $cn          Set to the common name of the certificate iff match found
     */
    public static function checkCertificateHost($certificate, string $hostname, ?string &$cn = null): bool
    {
        $names = self::getCertificateNames($certificate);

        if (empty($names)) {
            return false;
        }

        $combinedNames = array_merge($names['san'], [$names['cn']]);
        $hostname = strtolower($hostname);

        foreach ($combinedNames as $certName) {
            $matcher = self::certNameMatcher($certName);

            if ($matcher && $matcher($hostname)) {
                $cn = $names['cn'];

                return true;
            }
        }

        return false;
    }

    /**
     * Extract DNS names out of an X.509 certificate.
     *
     * @param mixed $certificate X.509 certificate
     *
     * @return array{cn: string, san: string[]}|null
     */
    public static function getCertificateNames($certificate): ?array
    {
        if (is_array($certificate)) {
            $info = $certificate;
        } elseif (CaBundle::isOpensslParseSafe()) {
            $info = openssl_x509_parse($certificate, false);
        }

        if (!isset($info['subject']['commonName'])) {
            return null;
        }

        $commonName = strtolower($info['subject']['commonName']);
        $subjectAltNames = [];

        if (isset($info['extensions']['subjectAltName'])) {
            $subjectAltNames = Preg::split('{\s*,\s*}', $info['extensions']['subjectAltName']);
            $subjectAltNames = array_filter(
                array_map(static function ($name): ?string {
                    if (0 === strpos($name, 'DNS:')) {
                        return strtolower(ltrim(substr($name, 4)));
                    }

                    return null;
                }, $subjectAltNames),
                static function (?string $san) {
                    return $san !== null;
                }
            );
            $subjectAltNames = array_values($subjectAltNames);
        }

        return [
            'cn' => $commonName,
            'san' => $subjectAltNames,
        ];
    }

    /**
     * Get the certificate pin.
     *
     * By Kevin McArthur of StormTide Digital Studios Inc.
     * @KevinSMcArthur / https://github.com/StormTide
     *
     * See https://tools.ietf.org/html/draft-ietf-websec-key-pinning-02
     *
     * This method was adapted from Sslurp.
     * https://github.com/EvanDotPro/Sslurp
     *
     * (c) Evan Coury <me@evancoury.com>
     *
     * For the full copyright and license information, please see below:
     *
     * Copyright (c) 2013, Evan Coury
     * 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.
     */
    public static function getCertificateFingerprint(string $certificate): string
    {
        $pubkey = openssl_get_publickey($certificate);
        if ($pubkey === false) {
            throw new \RuntimeException('Failed to retrieve the public key from certificate');
        }
        $pubkeydetails = openssl_pkey_get_details($pubkey);
        $pubkeypem = $pubkeydetails['key'];
        //Convert PEM to DER before SHA1'ing
        $start = '-----BEGIN PUBLIC KEY-----';
        $end = '-----END PUBLIC KEY-----';
        $pemtrim = substr($pubkeypem, strpos($pubkeypem, $start) + strlen($start), (strlen($pubkeypem) - strpos($pubkeypem, $end)) * (-1));
        $der = base64_decode($pemtrim);

        return hash('sha1', $der);
    }

    /**
     * Test if it is safe to use the PHP function openssl_x509_parse().
     *
     * This checks if OpenSSL extensions is vulnerable to remote code execution
     * via the exploit documented as CVE-2013-6420.
     */
    public static function isOpensslParseSafe(): bool
    {
        return CaBundle::isOpensslParseSafe();
    }

    /**
     * Convert certificate name into matching function.
     *
     * @param string $certName CN/SAN
     */
    private static function certNameMatcher(string $certName): ?callable
    {
        $wildcards = substr_count($certName, '*');

        if (0 === $wildcards) {
            // Literal match.
            return static function ($hostname) use ($certName): bool {
                return $hostname === $certName;
            };
        }

        if (1 === $wildcards) {
            $components = explode('.', $certName);

            if (3 > count($components)) {
                // Must have 3+ components
                return null;
            }

            $firstComponent = $components[0];

            // Wildcard must be the last character.
            if ('*' !== $firstComponent[strlen($firstComponent) - 1]) {
                return null;
            }

            $wildcardRegex = preg_quote($certName);
            $wildcardRegex = str_replace('\\*', '[a-z0-9-]+', $wildcardRegex);
            $wildcardRegex = "{^{$wildcardRegex}$}";

            return static function ($hostname) use ($wildcardRegex): bool {
                return Preg::isMatch($wildcardRegex, $hostname);
            };
        }

        return null;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\XdebugHandler\XdebugHandler;

/**
 * Provides ini file location functions that work with and without a restart.
 * When the process has restarted it uses a tmp ini and stores the original
 * ini locations in an environment variable.
 *
 * @author John Stevenson <john-stevenson@blueyonder.co.uk>
 */
class IniHelper
{
    /**
     * Returns an array of php.ini locations with at least one entry
     *
     * The equivalent of calling php_ini_loaded_file then php_ini_scanned_files.
     * The loaded ini location is the first entry and may be empty.
     *
     * @return string[]
     */
    public static function getAll(): array
    {
        return XdebugHandler::getAllIniFiles();
    }

    /**
     * Describes the location of the loaded php.ini file(s)
     */
    public static function getMessage(): string
    {
        $paths = self::getAll();

        if (empty($paths[0])) {
            array_shift($paths);
        }

        $ini = array_shift($paths);

        if (empty($ini)) {
            return 'A php.ini file does not exist. You will have to create one.';
        }

        if (!empty($paths)) {
            return 'Your command-line PHP is using multiple ini files. Run `php --ini` to show them.';
        }

        return 'The php.ini used by your command-line PHP is: '.$ini;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\Config;
use Composer\Downloader\TransportException;

/**
 * @author Paul Wenke <wenke.paul@gmail.com>
 */
class Bitbucket
{
    /** @var IOInterface */
    private $io;
    /** @var Config */
    private $config;
    /** @var ProcessExecutor */
    private $process;
    /** @var HttpDownloader */
    private $httpDownloader;
    /** @var array{access_token: string, expires_in?: int}|null */
    private $token = null;
    /** @var int|null */
    private $time;

    public const OAUTH2_ACCESS_TOKEN_URL = 'https://bitbucket.org/site/oauth2/access_token';

    /**
     * Constructor.
     *
     * @param IOInterface     $io             The IO instance
     * @param Config          $config         The composer configuration
     * @param ProcessExecutor $process        Process instance, injectable for mocking
     * @param HttpDownloader  $httpDownloader Remote Filesystem, injectable for mocking
     * @param int             $time           Timestamp, injectable for mocking
     */
    public function __construct(IOInterface $io, Config $config, ?ProcessExecutor $process = null, ?HttpDownloader $httpDownloader = null, ?int $time = null)
    {
        $this->io = $io;
        $this->config = $config;
        $this->process = $process ?: new ProcessExecutor($io);
        $this->httpDownloader = $httpDownloader ?: Factory::createHttpDownloader($this->io, $config);
        $this->time = $time;
    }

    public function getToken(): string
    {
        if (!isset($this->token['access_token'])) {
            return '';
        }

        return $this->token['access_token'];
    }

    /**
     * Attempts to authorize a Bitbucket domain via OAuth
     *
     * @param  string $originUrl The host this Bitbucket instance is located at
     * @return bool   true on success
     */
    public function authorizeOAuth(string $originUrl): bool
    {
        if ($originUrl !== 'bitbucket.org') {
            return false;
        }

        // if available use token from git config
        if (0 === $this->process->execute(['git', 'config', 'bitbucket.accesstoken'], $output)) {
            $this->io->setAuthentication($originUrl, 'x-token-auth', trim($output));

            return true;
        }

        return false;
    }

    private function requestAccessToken(): bool
    {
        try {
            $response = $this->httpDownloader->get(self::OAUTH2_ACCESS_TOKEN_URL, [
                'retry-auth-failure' => false,
                'http' => [
                    'method' => 'POST',
                    'content' => 'grant_type=client_credentials',
                ],
            ]);

            $token = $response->decodeJson();
            if (!isset($token['expires_in']) || !isset($token['access_token'])) {
                throw new \LogicException('Expected a token configured with expires_in and access_token present, got '.json_encode($token));
            }

            $this->token = $token;
        } catch (TransportException $e) {
            if ($e->getCode() === 400) {
                $this->io->writeError('<error>Invalid OAuth consumer provided.</error>');
                $this->io->writeError('This can have three reasons:');
                $this->io->writeError('1. You are authenticating with a bitbucket username/password combination');
                $this->io->writeError('2. You are using an OAuth consumer, but didn\'t configure a (dummy) callback url');
                $this->io->writeError('3. You are using an OAuth consumer, but didn\'t configure it as private consumer');

                return false;
            }
            if (in_array($e->getCode(), [403, 401])) {
                $this->io->writeError('<error>Invalid OAuth consumer provided.</error>');
                $this->io->writeError('You can also add it manually later by using "composer config --global --auth bitbucket-oauth.bitbucket.org <consumer-key> <consumer-secret>"');

                return false;
            }

            throw $e;
        }

        return true;
    }

    /**
     * Authorizes a Bitbucket domain interactively via OAuth
     *
     * @param  string                        $originUrl The host this Bitbucket instance is located at
     * @param  string                        $message   The reason this authorization is required
     * @throws \RuntimeException
     * @throws TransportException|\Exception
     * @return bool                          true on success
     */
    public function authorizeOAuthInteractively(string $originUrl, ?string $message = null): bool
    {
        if ($message) {
            $this->io->writeError($message);
        }

        $localAuthConfig = $this->config->getLocalAuthConfigSource();
        $url = 'https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/';
        $this->io->writeError('Follow the instructions here:');
        $this->io->writeError($url);
        $this->io->writeError(sprintf('to create a consumer. It will be stored in "%s" for future use by Composer.', ($localAuthConfig !== null ? $localAuthConfig->getName() . ' OR ' : '') . $this->config->getAuthConfigSource()->getName()));
        $this->io->writeError('Ensure you enter a "Callback URL" (http://example.com is fine) or it will not be possible to create an Access Token (this callback url will not be used by composer)');

        $storeInLocalAuthConfig = false;
        if ($localAuthConfig !== null) {
            $storeInLocalAuthConfig = $this->io->askConfirmation('A local auth config source was found, do you want to store the token there?', true);
        }

        $consumerKey = trim((string) $this->io->askAndHideAnswer('Consumer Key (hidden): '));

        if (!$consumerKey) {
            $this->io->writeError('<warning>No consumer key given, aborting.</warning>');
            $this->io->writeError('You can also add it manually later by using "composer config --global --auth bitbucket-oauth.bitbucket.org <consumer-key> <consumer-secret>"');

            return false;
        }

        $consumerSecret = trim((string) $this->io->askAndHideAnswer('Consumer Secret (hidden): '));

        if (!$consumerSecret) {
            $this->io->writeError('<warning>No consumer secret given, aborting.</warning>');
            $this->io->writeError('You can also add it manually later by using "composer config --global --auth bitbucket-oauth.bitbucket.org <consumer-key> <consumer-secret>"');

            return false;
        }

        $this->io->setAuthentication($originUrl, $consumerKey, $consumerSecret);

        if (!$this->requestAccessToken()) {
            return false;
        }

        // store value in user config
        $authConfigSource = $storeInLocalAuthConfig && $localAuthConfig !== null ? $localAuthConfig : $this->config->getAuthConfigSource();
        $this->storeInAuthConfig($authConfigSource, $originUrl, $consumerKey, $consumerSecret);

        // Remove conflicting basic auth credentials (if available)
        $this->config->getAuthConfigSource()->removeConfigSetting('http-basic.' . $originUrl);

        $this->io->writeError('<info>Consumer stored successfully.</info>');

        return true;
    }

    /**
     * Retrieves an access token from Bitbucket.
     */
    public function requestToken(string $originUrl, string $consumerKey, string $consumerSecret): string
    {
        if ($this->token !== null || $this->getTokenFromConfig($originUrl)) {
            return $this->token['access_token'];
        }

        $this->io->setAuthentication($originUrl, $consumerKey, $consumerSecret);
        if (!$this->requestAccessToken()) {
            return '';
        }

        $this->storeInAuthConfig($this->config->getLocalAuthConfigSource() ?? $this->config->getAuthConfigSource(), $originUrl, $consumerKey, $consumerSecret);

        if (!isset($this->token['access_token'])) {
            throw new \LogicException('Failed to initialize token above');
        }

        return $this->token['access_token'];
    }

    /**
     * Store the new/updated credentials to the configuration
     */
    private function storeInAuthConfig(Config\ConfigSourceInterface $authConfigSource, string $originUrl, string $consumerKey, string $consumerSecret): void
    {
        $this->config->getConfigSource()->removeConfigSetting('bitbucket-oauth.'.$originUrl);

        if (null === $this->token || !isset($this->token['expires_in'])) {
            throw new \LogicException('Expected a token configured with expires_in present, got '.json_encode($this->token));
        }

        $time = null === $this->time ? time() : $this->time;
        $consumer = [
            "consumer-key" => $consumerKey,
            "consumer-secret" => $consumerSecret,
            "access-token" => $this->token['access_token'],
            "access-token-expiration" => $time + $this->token['expires_in'],
        ];

        $this->config->getAuthConfigSource()->addConfigSetting('bitbucket-oauth.'.$originUrl, $consumer);
    }

    /**
     * @phpstan-assert-if-true array{access_token: string} $this->token
     */
    private function getTokenFromConfig(string $originUrl): bool
    {
        $authConfig = $this->config->get('bitbucket-oauth');

        if (
            !isset($authConfig[$originUrl]['access-token'], $authConfig[$originUrl]['access-token-expiration'])
            || time() > $authConfig[$originUrl]['access-token-expiration']
        ) {
            return false;
        }

        $this->token = [
            'access_token' => $authConfig[$originUrl]['access-token'],
        ];

        return true;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\Config;
use Composer\Downloader\TransportException;
use Composer\Pcre\Preg;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class GitHub
{
    public const GITHUB_TOKEN_REGEX = '{^([a-f0-9]{12,}|gh[a-z]_[a-zA-Z0-9_.-]+|github_pat_[a-zA-Z0-9_]+)$}';

    /** @var IOInterface */
    protected $io;
    /** @var Config */
    protected $config;
    /** @var ProcessExecutor */
    protected $process;
    /** @var HttpDownloader */
    protected $httpDownloader;

    /**
     * Constructor.
     *
     * @param IOInterface     $io             The IO instance
     * @param Config          $config         The composer configuration
     * @param ProcessExecutor $process        Process instance, injectable for mocking
     * @param HttpDownloader  $httpDownloader Remote Filesystem, injectable for mocking
     */
    public function __construct(IOInterface $io, Config $config, ?ProcessExecutor $process = null, ?HttpDownloader $httpDownloader = null)
    {
        $this->io = $io;
        $this->config = $config;
        $this->process = $process ?: new ProcessExecutor($io);
        $this->httpDownloader = $httpDownloader ?: Factory::createHttpDownloader($this->io, $config);
    }

    /**
     * Attempts to authorize a GitHub domain via OAuth
     *
     * @param  string $originUrl The host this GitHub instance is located at
     * @return bool   true on success
     */
    public function authorizeOAuth(string $originUrl): bool
    {
        if (!in_array($originUrl, $this->config->get('github-domains'))) {
            return false;
        }

        // if available use token from git config
        if (0 === $this->process->execute(['git', 'config', 'github.accesstoken'], $output)) {
            $this->io->setAuthentication($originUrl, trim($output), 'x-oauth-basic');

            return true;
        }

        return false;
    }

    /**
     * Authorizes a GitHub domain interactively via OAuth
     *
     * @param  string                        $originUrl The host this GitHub instance is located at
     * @param  string                        $message   The reason this authorization is required
     * @throws \RuntimeException
     * @throws TransportException|\Exception
     * @return bool                          true on success
     */
    public function authorizeOAuthInteractively(string $originUrl, ?string $message = null): bool
    {
        if ($message) {
            $this->io->writeError($message);
        }

        $note = 'Composer';
        if ($this->config->get('github-expose-hostname') === true && 0 === $this->process->execute(['hostname'], $output)) {
            $note .= ' on ' . trim($output);
        }
        $note .= ' ' . date('Y-m-d Hi');

        $localAuthConfig = $this->config->getLocalAuthConfigSource();

        $this->io->writeError([
            'You need to provide a GitHub access token.',
            sprintf('Tokens will be stored in plain text in "%s" for future use by Composer.', ($localAuthConfig !== null ? $localAuthConfig->getName() . ' OR ' : '') . $this->config->getAuthConfigSource()->getName()),
            'Due to the security risk of tokens being exfiltrated, use tokens with short expiration times and only the minimum permissions necessary.',
            '',
            'Carefully consider the following options in order:',
            '',
        ]);

        $this->io->writeError([
            '1. When you don\'t use \'vcs\'  type \'repositories\'  in composer.json and do not need to clone source or download dist files',
            'from private GitHub repositories over HTTPS, use a fine-grained token with read-only access to public information.',
            'Use the following URL to create such a token:',
            'https://'.$originUrl.'/settings/personal-access-tokens/new?name=' . str_replace('%20', '+', rawurlencode($note)),
            '',
        ]);

        $this->io->writeError([
            '2. When all relevant _private_ GitHub repositories belong to a single user or organisation, use a fine-grained token with',
            'repository "content" read-only permissions. You can start with the following URL, but you may need to change the resource owner',
            'to the right user or organisation. Additionally, you can scope permissions down to apply only to selected repositories.',
            'https://'.$originUrl.'/settings/personal-access-tokens/new?contents=read&name=' . str_replace('%20', '+', rawurlencode($note)),
            '',
        ]);

        $this->io->writeError([
            '3. A "classic" token grants broad permissions on your behalf to all repositories accessible by you.',
            'This may include write permissions, even though not needed by Composer. Use it only when you need to access',
            'private repositories across multiple organisations at the same time and using directory-specific authentication sources',
            'is not an option. You can generate a classic token here:',
            'https://'.$originUrl.'/settings/tokens/new?scopes=repo&description=' . str_replace('%20', '+', rawurlencode($note)),
            '',
        ]);

        $this->io->writeError('For additional information, check https://getcomposer.org/doc/articles/authentication-for-private-packages.md#github-oauth');

        $storeInLocalAuthConfig = false;
        if ($localAuthConfig !== null) {
            $storeInLocalAuthConfig = $this->io->askConfirmation('A local auth config source was found, do you want to store the token there?', true);
        }

        $token = trim((string) $this->io->askAndHideAnswer('Token (hidden): '));

        if ($token === '') {
            $this->io->writeError('<warning>No token given, aborting.</warning>');
            $this->io->writeError('You can also add it manually later by using "composer config --global --auth github-oauth.github.com <token>"');

            return false;
        }

        $this->io->setAuthentication($originUrl, $token, 'x-oauth-basic');

        try {
            $apiUrl = ('github.com' === $originUrl) ? 'api.github.com/' : $originUrl . '/api/v3/';

            $this->httpDownloader->get('https://'. $apiUrl, [
                'retry-auth-failure' => false,
            ]);
        } catch (TransportException $e) {
            if (in_array($e->getCode(), [403, 401])) {
                $this->io->writeError('<error>Invalid token provided.</error>');
                $this->io->writeError('You can also add it manually later by using "composer config --global --auth github-oauth.github.com <token>"');

                return false;
            }

            throw $e;
        }

        // store value in local/user config
        $authConfigSource = $storeInLocalAuthConfig && $localAuthConfig !== null ? $localAuthConfig : $this->config->getAuthConfigSource();
        $this->config->getConfigSource()->removeConfigSetting('github-oauth.'.$originUrl);
        $authConfigSource->addConfigSetting('github-oauth.'.$originUrl, $token);

        $this->io->writeError('<info>Token stored successfully.</info>');

        return true;
    }

    /**
     * Extract rate limit from response.
     *
     * @param string[] $headers Headers from Composer\Downloader\TransportException.
     *
     * @return array{limit: int|'?', reset: string}
     */
    public function getRateLimit(array $headers): array
    {
        $rateLimit = [
            'limit' => '?',
            'reset' => '?',
        ];

        foreach ($headers as $header) {
            $header = trim($header);
            if (false === stripos($header, 'x-ratelimit-')) {
                continue;
            }
            [$type, $value] = explode(':', $header, 2);
            switch (strtolower($type)) {
                case 'x-ratelimit-limit':
                    $rateLimit['limit'] = (int) trim($value);
                    break;
                case 'x-ratelimit-reset':
                    $rateLimit['reset'] = date('Y-m-d H:i:s', (int) trim($value));
                    break;
            }
        }

        return $rateLimit;
    }

    /**
     * Extract SSO URL from response.
     *
     * @param string[] $headers Headers from Composer\Downloader\TransportException.
     */
    public function getSsoUrl(array $headers): ?string
    {
        foreach ($headers as $header) {
            $header = trim($header);
            if (false === stripos($header, 'x-github-sso: required')) {
                continue;
            }
            if (Preg::isMatch('{\burl=(?P<url>[^\s;]+)}', $header, $match)) {
                return $match['url'];
            }
        }

        return null;
    }

    /**
     * Finds whether a request failed due to rate limiting
     *
     * @param string[] $headers Headers from Composer\Downloader\TransportException.
     */
    public function isRateLimited(array $headers): bool
    {
        foreach ($headers as $header) {
            if (Preg::isMatch('{^x-ratelimit-remaining: *0$}i', trim($header))) {
                return true;
            }
        }

        return false;
    }

    /**
     * Finds whether a request failed due to lacking SSO authorization
     *
     * @see https://docs.github.com/en/rest/overview/other-authentication-methods#authenticating-for-saml-sso
     *
     * @param string[] $headers Headers from Composer\Downloader\TransportException.
     */
    public function requiresSso(array $headers): bool
    {
        foreach ($headers as $header) {
            if (Preg::isMatch('{^x-github-sso: required}i', trim($header))) {
                return true;
            }
        }

        return false;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Loader\ValidatingArrayLoader;
use Composer\Package\Loader\InvalidPackageException;
use Composer\Json\JsonValidationException;
use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
use Composer\Pcre\Preg;
use Composer\Spdx\SpdxLicenses;
use Seld\JsonLint\DuplicateKeyException;
use Seld\JsonLint\JsonParser;

/**
 * Validates a composer configuration.
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ConfigValidator
{
    public const CHECK_VERSION = 1;

    /** @var IOInterface */
    private $io;

    public function __construct(IOInterface $io)
    {
        $this->io = $io;
    }

    /**
     * Validates the config, and returns the result.
     *
     * @param string $file                       The path to the file
     * @param int    $arrayLoaderValidationFlags Flags for ArrayLoader validation
     * @param int    $flags                      Flags for validation
     *
     * @return array{list<string>, list<string>, list<string>} a triple containing the errors, publishable errors, and warnings
     */
    public function validate(string $file, int $arrayLoaderValidationFlags = ValidatingArrayLoader::CHECK_ALL, int $flags = self::CHECK_VERSION): array
    {
        $errors = [];
        $publishErrors = [];
        $warnings = [];

        // validate json schema
        $laxValid = false;
        $manifest = null;
        try {
            $json = new JsonFile($file, null, $this->io);
            $manifest = $json->read();

            $json->validateSchema(JsonFile::LAX_SCHEMA);
            $laxValid = true;
            $json->validateSchema();
        } catch (JsonValidationException $e) {
            foreach ($e->getErrors() as $message) {
                if ($laxValid) {
                    $publishErrors[] = $message;
                } else {
                    $errors[] = $message;
                }
            }
        } catch (\Exception $e) {
            $errors[] = $e->getMessage();

            return [$errors, $publishErrors, $warnings];
        }

        if (is_array($manifest)) {
            $jsonParser = new JsonParser();
            try {
                $jsonParser->parse((string) file_get_contents($file), JsonParser::DETECT_KEY_CONFLICTS);
            } catch (DuplicateKeyException $e) {
                $details = $e->getDetails();
                $warnings[] = 'Key '.$details['key'].' is a duplicate in '.$file.' at line '.$details['line'];
            }
        }

        // validate actual data
        if (empty($manifest['license'])) {
            $warnings[] = 'No license specified, it is recommended to do so. For closed-source software you may use "proprietary" as license.';
        } else {
            $licenses = (array) $manifest['license'];

            // strip proprietary since it's not a valid SPDX identifier, but is accepted by composer
            foreach ($licenses as $key => $license) {
                if ('proprietary' === $license) {
                    unset($licenses[$key]);
                }
            }

            $licenseValidator = new SpdxLicenses();
            foreach ($licenses as $license) {
                $spdxLicense = $licenseValidator->getLicenseByIdentifier($license);
                if ($spdxLicense && $spdxLicense[3]) {
                    if (Preg::isMatch('{^[AL]?GPL-[123](\.[01])?\+$}i', $license)) {
                        $warnings[] = sprintf(
                            'License "%s" is a deprecated SPDX license identifier, use "'.str_replace('+', '', $license).'-or-later" instead',
                            $license
                        );
                    } elseif (Preg::isMatch('{^[AL]?GPL-[123](\.[01])?$}i', $license)) {
                        $warnings[] = sprintf(
                            'License "%s" is a deprecated SPDX license identifier, use "'.$license.'-only" or "'.$license.'-or-later" instead',
                            $license
                        );
                    } else {
                        $warnings[] = sprintf(
                            'License "%s" is a deprecated SPDX license identifier, see https://spdx.org/licenses/',
                            $license
                        );
                    }
                }
            }
        }

        if (($flags & self::CHECK_VERSION) && isset($manifest['version'])) {
            $warnings[] = 'The version field is present, it is recommended to leave it out if the package is published on Packagist.';
        }

        if (!empty($manifest['name']) && Preg::isMatch('{[A-Z]}', $manifest['name'])) {
            $suggestName = Preg::replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '\\1\\3-\\2\\4', $manifest['name']);
            $suggestName = strtolower($suggestName);

            $publishErrors[] = sprintf(
                'Name "%s" does not match the best practice (e.g. lower-cased/with-dashes). We suggest using "%s" instead. As such you will not be able to submit it to Packagist.',
                $manifest['name'],
                $suggestName
            );
        }

        if (!empty($manifest['type']) && $manifest['type'] === 'composer-installer') {
            $warnings[] = "The package type 'composer-installer' is deprecated. Please distribute your custom installers as plugins from now on. See https://getcomposer.org/doc/articles/plugins.md for plugin documentation.";
        }

        // check for require-dev overrides
        if (isset($manifest['require'], $manifest['require-dev'])) {
            $requireOverrides = array_intersect_key($manifest['require'], $manifest['require-dev']);

            if (!empty($requireOverrides)) {
                $plural = (count($requireOverrides) > 1) ? 'are' : 'is';
                $warnings[] = implode(', ', array_keys($requireOverrides)). " {$plural} required both in require and require-dev, this can lead to unexpected behavior";
            }
        }

        // check for meaningless provide/replace satisfying requirements
        foreach (['provide', 'replace'] as $linkType) {
            if (isset($manifest[$linkType])) {
                foreach (['require', 'require-dev'] as $requireType) {
                    if (isset($manifest[$requireType])) {
                        foreach ($manifest[$linkType] as $provide => $constraint) {
                            if (isset($manifest[$requireType][$provide])) {
                                $warnings[] = 'The package ' . $provide . ' in '.$requireType.' is also listed in '.$linkType.' which satisfies the requirement. Remove it from '.$linkType.' if you wish to install it.';
                            }
                        }
                    }
                }
            }
        }

        // check for commit references
        $require = $manifest['require'] ?? [];
        $requireDev = $manifest['require-dev'] ?? [];
        $packages = array_merge($require, $requireDev);
        foreach ($packages as $package => $version) {
            if (Preg::isMatch('/#/', $version)) {
                $warnings[] = sprintf(
                    'The package "%s" is pointing to a commit-ref, this is bad practice and can cause unforeseen issues.',
                    $package
                );
            }
        }

        // report scripts-descriptions for non-existent scripts
        $scriptsDescriptions = $manifest['scripts-descriptions'] ?? [];
        $scripts = $manifest['scripts'] ?? [];
        foreach ($scriptsDescriptions as $scriptName => $scriptDescription) {
            if (!array_key_exists($scriptName, $scripts)) {
                $warnings[] = sprintf(
                    'Description for non-existent script "%s" found in "scripts-descriptions"',
                    $scriptName
                );
            }
        }

        // report scripts-aliases for non-existent scripts
        $scriptAliases = $manifest['scripts-aliases'] ?? [];
        foreach ($scriptAliases as $scriptName => $scriptAlias) {
            if (!array_key_exists($scriptName, $scripts)) {
                $warnings[] = sprintf(
                    'Aliases for non-existent script "%s" found in "scripts-aliases"',
                    $scriptName
                );
            }
        }

        // check for empty psr-0/psr-4 namespace prefixes
        if (isset($manifest['autoload']['psr-0'][''])) {
            $warnings[] = "Defining autoload.psr-0 with an empty namespace prefix is a bad idea for performance";
        }
        if (isset($manifest['autoload']['psr-4'][''])) {
            $warnings[] = "Defining autoload.psr-4 with an empty namespace prefix is a bad idea for performance";
        }

        $loader = new ValidatingArrayLoader(new ArrayLoader(), true, null, $arrayLoaderValidationFlags);
        try {
            if (!isset($manifest['version'])) {
                $manifest['version'] = '1.0.0';
            }
            if (!isset($manifest['name'])) {
                $manifest['name'] = 'dummy/dummy';
            }
            $loader->load($manifest);
        } catch (InvalidPackageException $e) {
            $errors = array_merge($errors, $e->getErrors());
        }

        $warnings = array_merge($warnings, $loader->getWarnings());

        return [$errors, $publishErrors, $warnings];
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Package\CompletePackageInterface;
use Composer\Package\PackageInterface;

class PackageInfo
{
    public static function getViewSourceUrl(PackageInterface $package): ?string
    {
        if ($package instanceof CompletePackageInterface && isset($package->getSupport()['source']) && '' !== $package->getSupport()['source']) {
            return $package->getSupport()['source'];
        }

        return $package->getSourceUrl();
    }

    public static function getViewSourceOrHomepageUrl(PackageInterface $package): ?string
    {
        $url = self::getViewSourceUrl($package) ?? ($package instanceof CompletePackageInterface ? $package->getHomepage() : null);

        if ($url === '') {
            return null;
        }

        return $url;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Downloader\DownloaderInterface;
use Composer\Downloader\DownloadManager;
use Composer\Package\PackageInterface;
use React\Promise\PromiseInterface;

class SyncHelper
{
    /**
     * Helps you download + install a single package in a synchronous way
     *
     * This executes all the required steps and waits for promises to complete
     *
     * @param Loop                                $loop        Loop instance which you can get from $composer->getLoop()
     * @param DownloaderInterface|DownloadManager $downloader  DownloadManager instance or Downloader instance you can get from $composer->getDownloadManager()->getDownloader('zip') for example
     * @param string                              $path        The installation path for the package
     * @param PackageInterface                    $package     The package to install
     * @param PackageInterface|null               $prevPackage The previous package if this is an update and not an initial installation
     */
    public static function downloadAndInstallPackageSync(Loop $loop, $downloader, string $path, PackageInterface $package, ?PackageInterface $prevPackage = null): void
    {
        assert($downloader instanceof DownloaderInterface || $downloader instanceof DownloadManager);

        $type = $prevPackage !== null ? 'update' : 'install';

        try {
            self::await($loop, $downloader->download($package, $path, $prevPackage));

            self::await($loop, $downloader->prepare($type, $package, $path, $prevPackage));

            if ($type === 'update' && $prevPackage !== null) {
                self::await($loop, $downloader->update($package, $prevPackage, $path));
            } else {
                self::await($loop, $downloader->install($package, $path));
            }
        } catch (\Exception $e) {
            self::await($loop, $downloader->cleanup($type, $package, $path, $prevPackage));
            throw $e;
        }

        self::await($loop, $downloader->cleanup($type, $package, $path, $prevPackage));
    }

    /**
     * Waits for a promise to resolve
     *
     * @param Loop                  $loop    Loop instance which you can get from $composer->getLoop()
     * @phpstan-param PromiseInterface<mixed>|null $promise
     */
    public static function await(Loop $loop, ?PromiseInterface $promise = null): void
    {
        if ($promise !== null) {
            $loop->wait([$promise]);
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Closure;
use Composer\Config;
use Composer\Downloader\MaxFileSizeExceededException;
use Composer\IO\IOInterface;
use Composer\Downloader\TransportException;
use Composer\Pcre\Preg;
use Composer\Util\Http\Response;
use Composer\Util\Http\ProxyManager;

/**
 * @internal
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Nils Adermann <naderman@naderman.de>
 */
class RemoteFilesystem
{
    /** @var IOInterface */
    private $io;
    /** @var Config */
    private $config;
    /** @var string */
    private $scheme;
    /** @var int */
    private $bytesMax;
    /** @var string */
    private $originUrl;
    /** @var non-empty-string */
    private $fileUrl;
    /** @var ?string */
    private $fileName;
    /** @var bool */
    private $retry = false;
    /** @var bool */
    private $progress;
    /** @var ?int */
    private $lastProgress;
    /** @var mixed[] */
    private $options = [];
    /** @var bool */
    private $disableTls = false;
    /** @var list<string> */
    private $lastHeaders;
    /** @var bool */
    private $storeAuth = false;
    /** @var AuthHelper */
    private $authHelper;
    /** @var bool */
    private $degradedMode = false;
    /** @var int */
    private $redirects;
    /** @var int */
    private $maxRedirects = 20;

    /**
     * Constructor.
     *
     * @param IOInterface $io         The IO instance
     * @param Config      $config     The config
     * @param mixed[]     $options    The options
     */
    public function __construct(IOInterface $io, Config $config, array $options = [], bool $disableTls = false, ?AuthHelper $authHelper = null)
    {
        $this->io = $io;

        // Setup TLS options
        // The cafile option can be set via config.json
        if ($disableTls === false) {
            $this->options = StreamContextFactory::getTlsDefaults($options, $io);
        } else {
            $this->disableTls = true;
        }

        // handle the other externally set options normally.
        $this->options = array_replace_recursive($this->options, $options);
        $this->config = $config;
        $this->authHelper = $authHelper ?? new AuthHelper($io, $config);
    }

    /**
     * Copy the remote file in local.
     *
     * @param string  $originUrl The origin URL
     * @param non-empty-string $fileUrl   The file URL
     * @param string  $fileName  the local filename
     * @param bool    $progress  Display the progression
     * @param mixed[] $options   Additional context options
     *
     * @return bool true
     */
    public function copy(string $originUrl, string $fileUrl, string $fileName, bool $progress = true, array $options = [])
    {
        return $this->get($originUrl, $fileUrl, $options, $fileName, $progress);
    }

    /**
     * Get the content.
     *
     * @param string  $originUrl The origin URL
     * @param non-empty-string $fileUrl   The file URL
     * @param bool    $progress  Display the progression
     * @param mixed[] $options   Additional context options
     *
     * @return bool|string The content
     */
    public function getContents(string $originUrl, string $fileUrl, bool $progress = true, array $options = [])
    {
        return $this->get($originUrl, $fileUrl, $options, null, $progress);
    }

    /**
     * Retrieve the options set in the constructor
     *
     * @return mixed[] Options
     */
    public function getOptions()
    {
        return $this->options;
    }

    /**
     * Merges new options
     *
     * @param  mixed[] $options
     * @return void
     */
    public function setOptions(array $options)
    {
        $this->options = array_replace_recursive($this->options, $options);
    }

    /**
     * Check is disable TLS.
     *
     * @return bool
     */
    public function isTlsDisabled()
    {
        return $this->disableTls === true;
    }

    /**
     * Returns the headers of the last request
     *
     * @return list<string>
     */
    public function getLastHeaders()
    {
        return $this->lastHeaders;
    }

    /**
     * @param  string[] $headers array of returned headers like from getLastHeaders()
     * @return int|null
     */
    public static function findStatusCode(array $headers)
    {
        $value = null;
        foreach ($headers as $header) {
            if (Preg::isMatch('{^HTTP/\S+ (\d+)}i', $header, $match)) {
                // In case of redirects, http_response_headers contains the headers of all responses
                // so we can not return directly and need to keep iterating
                $value = (int) $match[1];
            }
        }

        return $value;
    }

    /**
     * @param  string[]    $headers array of returned headers like from getLastHeaders()
     * @return string|null
     */
    public function findStatusMessage(array $headers)
    {
        $value = null;
        foreach ($headers as $header) {
            if (Preg::isMatch('{^HTTP/\S+ \d+}i', $header)) {
                // In case of redirects, http_response_headers contains the headers of all responses
                // so we can not return directly and need to keep iterating
                $value = $header;
            }
        }

        return $value;
    }

    /**
     * Get file content or copy action.
     *
     * @param string  $originUrl         The origin URL
     * @param non-empty-string $fileUrl  The file URL
     * @param mixed[] $additionalOptions context options
     * @param string  $fileName          the local filename
     * @param bool    $progress          Display the progression
     *
     * @throws TransportException|\Exception
     * @throws TransportException            When the file could not be downloaded
     *
     * @return bool|string
     */
    protected function get(string $originUrl, string $fileUrl, array $additionalOptions = [], ?string $fileName = null, bool $progress = true)
    {
        $this->scheme = parse_url(strtr($fileUrl, '\\', '/'), PHP_URL_SCHEME);
        $this->bytesMax = 0;
        $this->originUrl = $originUrl;
        $this->fileUrl = $fileUrl;
        $this->fileName = $fileName;
        $this->progress = $progress;
        $this->lastProgress = null;
        $retryAuthFailure = true;
        $this->lastHeaders = [];
        $this->redirects = 1; // The first request counts.

        $tempAdditionalOptions = $additionalOptions;
        if (isset($tempAdditionalOptions['retry-auth-failure'])) {
            $retryAuthFailure = (bool) $tempAdditionalOptions['retry-auth-failure'];

            unset($tempAdditionalOptions['retry-auth-failure']);
        }

        $isRedirect = false;
        if (isset($tempAdditionalOptions['redirects'])) {
            $this->redirects = $tempAdditionalOptions['redirects'];
            $isRedirect = true;

            unset($tempAdditionalOptions['redirects']);
        }

        $options = $this->getOptionsForUrl($originUrl, $tempAdditionalOptions);
        unset($tempAdditionalOptions);

        $origFileUrl = $fileUrl;

        if (isset($options['prevent_ip_access_callable'])) {
            throw new \RuntimeException("RemoteFilesystem doesn't support the 'prevent_ip_access_callable' config.");
        }

        if (isset($options['gitlab-token'])) {
            $fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token='.$options['gitlab-token'];
            unset($options['gitlab-token']);
        }

        if (isset($options['http'])) {
            $options['http']['ignore_errors'] = true;
        }

        if ($this->degradedMode && strpos($fileUrl, 'http://repo.packagist.org/') === 0) {
            // access packagist using the resolved IPv4 instead of the hostname to force IPv4 protocol
            $fileUrl = 'http://' . gethostbyname('repo.packagist.org') . substr($fileUrl, 20);
            $degradedPackagist = true;
        }

        $maxFileSize = null;
        if (isset($options['max_file_size'])) {
            $maxFileSize = $options['max_file_size'];
            unset($options['max_file_size']);
        }

        $ctx = StreamContextFactory::getContext($fileUrl, $options, ['notification' => Closure::fromCallable([$this, 'callbackGet'])]);

        $proxy = ProxyManager::getInstance()->getProxyForRequest($fileUrl);
        $usingProxy = $proxy->getStatus(' using proxy (%s)');
        $this->io->writeError((strpos($origFileUrl, 'http') === 0 ? 'Downloading ' : 'Reading ') . Url::sanitize($origFileUrl) . $usingProxy, true, IOInterface::DEBUG);
        unset($origFileUrl, $proxy, $usingProxy);

        // Check for secure HTTP, but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256
        if ((!Preg::isMatch('{^http://(repo\.)?packagist\.org/p/}', $fileUrl) || (false === strpos($fileUrl, '$') && false === strpos($fileUrl, '%24'))) && empty($degradedPackagist)) {
            $this->config->prohibitUrlByConfig($fileUrl, $this->io);
        }

        if ($this->progress && !$isRedirect) {
            $this->io->writeError("Downloading (<comment>connecting...</comment>)", false);
        }

        $errorMessage = '';
        $errorCode = 0;
        $result = false;
        set_error_handler(static function ($code, $msg) use (&$errorMessage): bool {
            if ($errorMessage) {
                $errorMessage .= "\n";
            }
            $errorMessage .= Preg::replace('{^file_get_contents\(.*?\): }', '', $msg);

            return true;
        });
        $http_response_header = [];
        try {
            $result = $this->getRemoteContents($originUrl, $fileUrl, $ctx, $http_response_header, $maxFileSize);

            if (!empty($http_response_header[0])) {
                $statusCode = self::findStatusCode($http_response_header);
                if ($statusCode >= 300 && Response::findHeaderValue($http_response_header, 'content-type') === 'application/json') {
                    HttpDownloader::outputWarnings($this->io, $originUrl, json_decode($result, true));
                }

                if (in_array($statusCode, [401, 403]) && $retryAuthFailure) {
                    $this->promptAuthAndRetry($statusCode, $this->findStatusMessage($http_response_header), $http_response_header);
                }
            }

            $contentLength = !empty($http_response_header[0]) ? Response::findHeaderValue($http_response_header, 'content-length') : null;
            if ($contentLength && Platform::strlen($result) < $contentLength) {
                // alas, this is not possible via the stream callback because STREAM_NOTIFY_COMPLETED is documented, but not implemented anywhere in PHP
                $e = new TransportException('Content-Length mismatch, received '.Platform::strlen($result).' bytes out of the expected '.$contentLength);
                $e->setHeaders($http_response_header);
                $e->setStatusCode(self::findStatusCode($http_response_header));
                try {
                    $e->setResponse($this->decodeResult($result, $http_response_header));
                } catch (\Exception $discarded) {
                    $e->setResponse($this->normalizeResult($result));
                }

                $this->io->writeError('Content-Length mismatch, received '.Platform::strlen($result).' out of '.$contentLength.' bytes: (' . base64_encode($result).')', true, IOInterface::DEBUG);

                throw $e;
            }
        } catch (\Exception $e) {
            if ($e instanceof TransportException && !empty($http_response_header[0])) {
                $e->setHeaders($http_response_header);
                $e->setStatusCode(self::findStatusCode($http_response_header));
            }
            if ($e instanceof TransportException && $result !== false) {
                $e->setResponse($this->decodeResult($result, $http_response_header));
            }
            $result = false;
        }
        if ($errorMessage && !filter_var(ini_get('allow_url_fopen'), FILTER_VALIDATE_BOOLEAN)) {
            $errorMessage = 'allow_url_fopen must be enabled in php.ini ('.$errorMessage.')';
        }
        restore_error_handler();
        if (isset($e) && !$this->retry) {
            if (!$this->degradedMode && false !== strpos($e->getMessage(), 'Operation timed out')) {
                $this->degradedMode = true;
                $this->io->writeError('');
                $this->io->writeError([
                    '<error>'.$e->getMessage().'</error>',
                    '<error>Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info</error>',
                ]);

                return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress);
            }

            throw $e;
        }

        $statusCode = null;
        $contentType = null;
        $locationHeader = null;
        if (!empty($http_response_header[0])) {
            $statusCode = self::findStatusCode($http_response_header);
            $contentType = Response::findHeaderValue($http_response_header, 'content-type');
            $locationHeader = Response::findHeaderValue($http_response_header, 'location');
        }

        // check for bitbucket login page asking to authenticate
        if ($originUrl === 'bitbucket.org'
            && !$this->authHelper->isPublicBitBucketDownload($fileUrl)
            && substr($fileUrl, -4) === '.zip'
            && (!$locationHeader || substr(parse_url($locationHeader, PHP_URL_PATH), -4) !== '.zip')
            && $contentType && Preg::isMatch('{^text/html\b}i', $contentType)
        ) {
            $result = false;
            if ($retryAuthFailure) {
                $this->promptAuthAndRetry(401);
            }
        }

        // check for gitlab 404 when downloading archives
        if ($statusCode === 404
            && in_array($originUrl, $this->config->get('gitlab-domains'), true)
            && false !== strpos($fileUrl, 'archive.zip')
        ) {
            $result = false;
            if ($retryAuthFailure) {
                $this->promptAuthAndRetry(401);
            }
        }

        // handle 3xx redirects, 304 Not Modified is excluded
        $hasFollowedRedirect = false;
        if ($statusCode >= 300 && $statusCode <= 399 && $statusCode !== 304 && $this->redirects < $this->maxRedirects) {
            $hasFollowedRedirect = true;
            $result = $this->handleRedirect($http_response_header, $additionalOptions, $result);
        }

        // fail 4xx and 5xx responses and capture the response
        if ($statusCode && $statusCode >= 400 && $statusCode <= 599) {
            if (!$this->retry) {
                if ($this->progress && !$isRedirect) {
                    $this->io->overwriteError("Downloading (<error>failed</error>)", false);
                }

                $e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded ('.$http_response_header[0].')', $statusCode);
                $e->setHeaders($http_response_header);
                $e->setResponse($this->decodeResult($result, $http_response_header));
                $e->setStatusCode($statusCode);
                throw $e;
            }
            $result = false;
        }

        if ($this->progress && !$this->retry && !$isRedirect) {
            $this->io->overwriteError("Downloading (".($result === false ? '<error>failed</error>' : '<comment>100%</comment>').")", false);
        }

        // decode gzip
        if ($result && extension_loaded('zlib') && strpos($fileUrl, 'http') === 0 && !$hasFollowedRedirect) {
            try {
                $result = $this->decodeResult($result, $http_response_header);
            } catch (\Exception $e) {
                if ($this->degradedMode) {
                    throw $e;
                }

                $this->degradedMode = true;
                $this->io->writeError([
                    '',
                    '<error>Failed to decode response: '.$e->getMessage().'</error>',
                    '<error>Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info</error>',
                ]);

                return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress);
            }
        }

        // handle copy command if download was successful
        if (false !== $result && null !== $fileName && !$isRedirect) {
            if ('' === $result) {
                throw new TransportException('"'.$this->fileUrl.'" appears broken, and returned an empty 200 response');
            }

            $errorMessage = '';
            set_error_handler(static function ($code, $msg) use (&$errorMessage): bool {
                if ($errorMessage) {
                    $errorMessage .= "\n";
                }
                $errorMessage .= Preg::replace('{^file_put_contents\(.*?\): }', '', $msg);

                return true;
            });
            $result = (bool) file_put_contents($fileName, $result);
            restore_error_handler();
            if (false === $result) {
                throw new TransportException('The "'.$this->fileUrl.'" file could not be written to '.$fileName.': '.$errorMessage);
            }
        }

        if ($this->retry) {
            $this->retry = false;

            $result = $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress);

            if ($this->storeAuth) {
                $this->authHelper->storeAuth($this->originUrl, $this->storeAuth);
                $this->storeAuth = false;
            }

            return $result;
        }

        if (false === $result) {
            $e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded: '.$errorMessage, $errorCode);
            if (!empty($http_response_header[0])) {
                $e->setHeaders($http_response_header);
            }

            if (!$this->degradedMode && false !== strpos($e->getMessage(), 'Operation timed out')) {
                $this->degradedMode = true;
                $this->io->writeError('');
                $this->io->writeError([
                    '<error>'.$e->getMessage().'</error>',
                    '<error>Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info</error>',
                ]);

                return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress);
            }

            throw $e;
        }

        if (!empty($http_response_header[0])) {
            $this->lastHeaders = $http_response_header;
        }

        return $result;
    }

    /**
     * Get contents of remote URL.
     *
     * @param string   $originUrl   The origin URL
     * @param string   $fileUrl     The file URL
     * @param resource $context     The stream context
     * @param string[] $responseHeaders
     * @param int      $maxFileSize The maximum allowed file size
     *
     * @return string|false The response contents or false on failure
     *
     * @param-out list<string> $responseHeaders
     */
    protected function getRemoteContents(string $originUrl, string $fileUrl, $context, ?array &$responseHeaders = null, ?int $maxFileSize = null)
    {
        $result = false;

        if (\PHP_VERSION_ID >= 80400) {
            http_clear_last_response_headers();
        }

        try {
            $e = null;
            if ($maxFileSize !== null) {
                $result = file_get_contents($fileUrl, false, $context, 0, $maxFileSize);
            } else {
                // passing `null` to file_get_contents will convert `null` to `0` and return 0 bytes
                $result = file_get_contents($fileUrl, false, $context);
            }
        } catch (\Throwable $e) {
        }

        if ($result !== false && $maxFileSize !== null && Platform::strlen($result) >= $maxFileSize) {
            throw new MaxFileSizeExceededException('Maximum allowed download size reached. Downloaded ' . Platform::strlen($result) . ' of allowed ' .  $maxFileSize . ' bytes');
        }

        // https://www.php.net/manual/en/reserved.variables.httpresponseheader.php
        if (\PHP_VERSION_ID >= 80400) {
            $responseHeaders = http_get_last_response_headers() ?? [];
            http_clear_last_response_headers();
        } else {
            $responseHeaders = $http_response_header ?? [];
        }

        if (null !== $e) {
            throw $e;
        }

        return $result;
    }

    /**
     * Get notification action.
     *
     * @param int    $notificationCode The notification code
     * @param int    $severity         The severity level
     * @param string $message          The message
     * @param int    $messageCode      The message code
     * @param int    $bytesTransferred The loaded size
     * @param int    $bytesMax         The total size
     *
     * @return void
     *
     * @throws TransportException
     */
    protected function callbackGet(int $notificationCode, int $severity, ?string $message, int $messageCode, int $bytesTransferred, int $bytesMax)
    {
        switch ($notificationCode) {
            case STREAM_NOTIFY_FAILURE:
                if (400 === $messageCode) {
                    // This might happen if your host is secured by ssl client certificate authentication
                    // but you do not send an appropriate certificate
                    throw new TransportException("The '" . $this->fileUrl . "' URL could not be accessed: " . $message, $messageCode);
                }
                break;

            case STREAM_NOTIFY_FILE_SIZE_IS:
                $this->bytesMax = $bytesMax;
                break;

            case STREAM_NOTIFY_PROGRESS:
                if ($this->bytesMax > 0 && $this->progress) {
                    $progression = min(100, (int) round($bytesTransferred / $this->bytesMax * 100));

                    if ((0 === $progression % 5) && 100 !== $progression && $progression !== $this->lastProgress) {
                        $this->lastProgress = $progression;
                        $this->io->overwriteError("Downloading (<comment>$progression%</comment>)", false);
                    }
                }
                break;

            default:
                break;
        }
    }

    /**
     * @param positive-int $httpStatus
     * @param string[]     $headers
     *
     * @return void
     */
    protected function promptAuthAndRetry($httpStatus, ?string $reason = null, array $headers = [])
    {
        $result = $this->authHelper->promptAuthIfNeeded($this->fileUrl, $this->originUrl, $httpStatus, $reason, $headers, 1 /** always pass 1 as RemoteFilesystem is single threaded there is no race condition possible */);

        $this->storeAuth = $result['storeAuth'];
        $this->retry = $result['retry'];

        if ($this->retry) {
            throw new TransportException('RETRY');
        }
    }

    /**
     * @param mixed[] $additionalOptions
     *
     * @return mixed[]
     */
    protected function getOptionsForUrl(string $originUrl, array $additionalOptions)
    {
        $tlsOptions = [];
        $headers = [];

        if (extension_loaded('zlib')) {
            $headers[] = 'Accept-Encoding: gzip';
        }

        $options = array_replace_recursive($this->options, $tlsOptions, $additionalOptions);
        if (!$this->degradedMode) {
            // degraded mode disables HTTP/1.1 which causes issues with some bad
            // proxies/software due to the use of chunked encoding
            $options['http']['protocol_version'] = 1.1;
            $headers[] = 'Connection: close';
        }

        if (isset($options['http']['header']) && !is_array($options['http']['header'])) {
            $options['http']['header'] = explode("\r\n", trim($options['http']['header'], "\r\n"));
        }
        $options = $this->authHelper->addAuthenticationOptions($options, $originUrl, $this->fileUrl);

        $options['http']['follow_location'] = 0;

        foreach ($headers as $header) {
            $options['http']['header'][] = $header;
        }

        return $options;
    }

    /**
     * @param string[]     $responseHeaders
     * @param mixed[]      $additionalOptions
     * @param string|false $result
     *
     * @return bool|string
     */
    private function handleRedirect(array $responseHeaders, array $additionalOptions, $result)
    {
        if ($locationHeader = Response::findHeaderValue($responseHeaders, 'location')) {
            if (parse_url($locationHeader, PHP_URL_SCHEME)) {
                // Absolute URL; e.g. https://example.com/composer
                $targetUrl = $locationHeader;
            } elseif (parse_url($locationHeader, PHP_URL_HOST)) {
                // Scheme relative; e.g. //example.com/foo
                $targetUrl = $this->scheme.':'.$locationHeader;
            } elseif ('/' === $locationHeader[0]) {
                // Absolute path; e.g. /foo
                $urlHost = parse_url($this->fileUrl, PHP_URL_HOST);

                // Replace path using hostname as an anchor.
                $targetUrl = Preg::replace('{^(.+(?://|@)'.preg_quote($urlHost).'(?::\d+)?)(?:[/\?].*)?$}', '\1'.$locationHeader, $this->fileUrl);
            } else {
                // Relative path; e.g. foo
                // This actually differs from PHP which seems to add duplicate slashes.
                $targetUrl = Preg::replace('{^(.+/)[^/?]*(?:\?.*)?$}', '\1'.$locationHeader, $this->fileUrl);
            }
        }

        if (!empty($targetUrl)) {
            $this->redirects++;

            $this->io->writeError('', true, IOInterface::DEBUG);
            $this->io->writeError(sprintf('Following redirect (%u) %s', $this->redirects, Url::sanitize($targetUrl)), true, IOInterface::DEBUG);

            $additionalOptions['redirects'] = $this->redirects;

            return $this->get(parse_url($targetUrl, PHP_URL_HOST), $targetUrl, $additionalOptions, $this->fileName, $this->progress);
        }

        if (!$this->retry) {
            $e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded, got redirect without Location ('.$responseHeaders[0].')');
            $e->setHeaders($responseHeaders);
            $e->setResponse($this->decodeResult($result, $responseHeaders));

            throw $e;
        }

        return false;
    }

    /**
     * @param string|false $result
     * @param string[]     $responseHeaders
     */
    private function decodeResult($result, array $responseHeaders): ?string
    {
        // decode gzip
        if ($result && extension_loaded('zlib')) {
            $contentEncoding = Response::findHeaderValue($responseHeaders, 'content-encoding');
            $decode = $contentEncoding && 'gzip' === strtolower($contentEncoding);

            if ($decode) {
                $result = zlib_decode($result);

                if ($result === false) {
                    throw new TransportException('Failed to decode zlib stream');
                }
            }
        }

        return $this->normalizeResult($result);
    }

    /**
     * @param string|false $result
     */
    private function normalizeResult($result): ?string
    {
        if ($result === false) {
            return null;
        }

        return $result;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Config;
use Composer\Pcre\Preg;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Url
{
    /**
     * @param non-empty-string $url
     * @return non-empty-string the updated URL
     */
    public static function updateDistReference(Config $config, string $url, string $ref): string
    {
        $host = parse_url($url, PHP_URL_HOST);

        if ($host === 'api.github.com' || $host === 'github.com' || $host === 'www.github.com') {
            if (Preg::isMatch('{^https?://(?:www\.)?github\.com/([^/]+)/([^/]+)/(zip|tar)ball/(.+)$}i', $url, $match)) {
                // update legacy github archives to API calls with the proper reference
                $url = 'https://api.github.com/repos/' . $match[1] . '/'. $match[2] . '/' . $match[3] . 'ball/' . $ref;
            } elseif (Preg::isMatch('{^https?://(?:www\.)?github\.com/([^/]+)/([^/]+)/archive/.+\.(zip|tar)(?:\.gz)?$}i', $url, $match)) {
                // update current github web archives to API calls with the proper reference
                $url = 'https://api.github.com/repos/' . $match[1] . '/'. $match[2] . '/' . $match[3] . 'ball/' . $ref;
            } elseif (Preg::isMatch('{^https?://api\.github\.com/repos/([^/]+)/([^/]+)/(zip|tar)ball(?:/.+)?$}i', $url, $match)) {
                // update api archives to the proper reference
                $url = 'https://api.github.com/repos/' . $match[1] . '/'. $match[2] . '/' . $match[3] . 'ball/' . $ref;
            }
        } elseif ($host === 'bitbucket.org' || $host === 'www.bitbucket.org') {
            if (Preg::isMatch('{^https?://(?:www\.)?bitbucket\.org/([^/]+)/([^/]+)/get/(.+)\.(zip|tar\.gz|tar\.bz2)$}i', $url, $match)) {
                // update Bitbucket archives to the proper reference
                $url = 'https://bitbucket.org/' . $match[1] . '/'. $match[2] . '/get/' . $ref . '.' . $match[4];
            }
        } elseif ($host === 'gitlab.com' || $host === 'www.gitlab.com') {
            if (Preg::isMatch('{^https?://(?:www\.)?gitlab\.com/api/v[34]/projects/([^/]+)/repository/archive\.(zip|tar\.gz|tar\.bz2|tar)\?sha=.+$}i', $url, $match)) {
                // update Gitlab archives to the proper reference
                $url = 'https://gitlab.com/api/v4/projects/' . $match[1] . '/repository/archive.' . $match[2] . '?sha=' . $ref;
            }
        } elseif (in_array($host, $config->get('github-domains'), true)) {
            $url = Preg::replace('{(/repos/[^/]+/[^/]+/(zip|tar)ball)(?:/.+)?$}i', '$1/'.$ref, $url);
        } elseif (in_array($host, $config->get('gitlab-domains'), true)) {
            $url = Preg::replace('{(/api/v[34]/projects/[^/]+/repository/archive\.(?:zip|tar\.gz|tar\.bz2|tar)\?sha=).+$}i', '${1}'.$ref, $url);
        }

        assert($url !== '');

        return $url;
    }

    /**
     * @param non-empty-string $url
     * @return non-empty-string
     */
    public static function getOrigin(Config $config, string $url): string
    {
        if (0 === strpos($url, 'file://')) {
            return $url;
        }

        $origin = (string) parse_url($url, PHP_URL_HOST);
        if ($port = parse_url($url, PHP_URL_PORT)) {
            $origin .= ':'.$port;
        }

        if (str_ends_with($origin, '.github.com') && $origin !== 'codeload.github.com') {
            return 'github.com';
        }

        if ($origin === 'repo.packagist.org') {
            return 'packagist.org';
        }

        if ($origin === '') {
            $origin = $url;
        }

        // Gitlab can be installed in a non-root context (i.e. gitlab.com/foo). When downloading archives the originUrl
        // is the host without the path, so we look for the registered gitlab-domains matching the host here
        if (
            false === strpos($origin, '/')
            && !in_array($origin, $config->get('gitlab-domains'), true)
        ) {
            foreach ($config->get('gitlab-domains') as $gitlabDomain) {
                if ($gitlabDomain !== '' && str_starts_with($gitlabDomain, $origin)) {
                    return $gitlabDomain;
                }
            }
        }

        return $origin;
    }

    public static function sanitize(string $url): string
    {
        // GitHub repository rename result in redirect locations containing the access_token as GET parameter
        // e.g. https://api.github.com/repositories/9999999999?access_token=github_token
        $url = Preg::replace('{([&?]access_token=)[^&]+}', '$1***', $url);

        $url = Preg::replaceCallback('{^(?P<prefix>[a-z0-9]+://)?(?P<user>[^:/\s@]+):(?P<password>[^@\s/]+)@}i', static function ($m): string {
            // if the username looks like a long (12char+) hex string, or a modern github token (e.g. ghp_xxx, github_pat_xxx) we obfuscate that
            if (Preg::isMatch(GitHub::GITHUB_TOKEN_REGEX, $m['user'])) {
                return $m['prefix'].'***:***@';
            }

            return $m['prefix'].$m['user'].':***@';
        }, $url);

        return $url;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Config;
use Composer\Downloader\TransportException;
use Composer\IO\IOInterface;

/**
 * @internal
 * @readonly
 */
final class Forgejo
{
    /** @var IOInterface */
    private $io;
    /** @var Config */
    private $config;
    /** @var HttpDownloader */
    private $httpDownloader;

    public function __construct(IOInterface $io, Config $config, HttpDownloader $httpDownloader)
    {
        $this->io = $io;
        $this->config = $config;
        $this->httpDownloader = $httpDownloader;
    }

    /**
     * Authorizes a Forgejo domain interactively
     *
     * @param  string                        $originUrl The host this Forgejo instance is located at
     * @param  string                        $message   The reason this authorization is required
     * @throws \RuntimeException
     * @throws TransportException|\Exception
     * @return bool                          true on success
     */
    public function authorizeOAuthInteractively(string $originUrl, ?string $message = null): bool
    {
        if ($message !== null) {
            $this->io->writeError($message);
        }

        $url = 'https://'.$originUrl.'/user/settings/applications';
        $this->io->writeError('Setup a personal access token with repository:read permissions on:');
        $this->io->writeError($url);
        $localAuthConfig = $this->config->getLocalAuthConfigSource();
        $this->io->writeError(sprintf('Tokens will be stored in plain text in "%s" for future use by Composer.', ($localAuthConfig !== null ? $localAuthConfig->getName() . ' OR ' : '') . $this->config->getAuthConfigSource()->getName()));
        $this->io->writeError('For additional information, check https://getcomposer.org/doc/articles/authentication-for-private-packages.md#forgejo-token');

        $storeInLocalAuthConfig = false;
        if ($localAuthConfig !== null) {
            $storeInLocalAuthConfig = $this->io->askConfirmation('A local auth config source was found, do you want to store the token there?', true);
        }

        $username = trim((string) $this->io->ask('Username: '));
        $token = trim((string) $this->io->askAndHideAnswer('Token (hidden): '));

        $addTokenManually = sprintf('You can also add it manually later by using "composer config --global --auth forgejo-token.%s <username> <token>"', $originUrl);
        if ($token === '' || $username === '') {
            $this->io->writeError('<warning>No username/token given, aborting.</warning>');
            $this->io->writeError($addTokenManually);

            return false;
        }

        $this->io->setAuthentication($originUrl, $username, $token);

        try {
            $this->httpDownloader->get('https://'. $originUrl . '/api/v1/version', [
                'retry-auth-failure' => false,
            ]);
        } catch (TransportException $e) {
            if (in_array($e->getCode(), [403, 401, 404], true)) {
                $this->io->writeError('<error>Invalid access token provided.</error>');
                $this->io->writeError($addTokenManually);

                return false;
            }

            throw $e;
        }

        // store value in local/user config
        $authConfigSource = $storeInLocalAuthConfig && $localAuthConfig !== null ? $localAuthConfig : $this->config->getAuthConfigSource();
        $this->config->getConfigSource()->removeConfigSetting('forgejo-token.'.$originUrl);
        $authConfigSource->addConfigSetting('forgejo-token.'.$originUrl, ['username' => $username, 'token' => $token]);

        $this->io->writeError('<info>Token stored successfully.</info>');

        return true;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Filter\PlatformRequirementFilter;

final class IgnoreNothingPlatformRequirementFilter implements PlatformRequirementFilterInterface
{
    /**
     * @return false
     */
    public function isIgnored(string $req): bool
    {
        return false;
    }

    /**
     * @return false
     */
    public function isUpperBoundIgnored(string $req): bool
    {
        return false;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Filter\PlatformRequirementFilter;

interface PlatformRequirementFilterInterface
{
    public function isIgnored(string $req): bool;

    public function isUpperBoundIgnored(string $req): bool;
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Filter\PlatformRequirementFilter;

use Composer\Package\BasePackage;
use Composer\Pcre\Preg;
use Composer\Repository\PlatformRepository;
use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\MatchAllConstraint;
use Composer\Semver\Constraint\MultiConstraint;
use Composer\Semver\Interval;
use Composer\Semver\Intervals;

final class IgnoreListPlatformRequirementFilter implements PlatformRequirementFilterInterface
{
    /**
     * @var non-empty-string
     */
    private $ignoreRegex;

    /**
     * @var non-empty-string
     */
    private $ignoreUpperBoundRegex;

    /**
     * @param string[] $reqList
     */
    public function __construct(array $reqList)
    {
        $ignoreAll = $ignoreUpperBound = [];
        foreach ($reqList as $req) {
            if (substr($req, -1) === '+') {
                $ignoreUpperBound[] = substr($req, 0, -1);
            } else {
                $ignoreAll[] = $req;
            }
        }
        $this->ignoreRegex = BasePackage::packageNamesToRegexp($ignoreAll);
        $this->ignoreUpperBoundRegex = BasePackage::packageNamesToRegexp($ignoreUpperBound);
    }

    public function isIgnored(string $req): bool
    {
        if (!PlatformRepository::isPlatformPackage($req)) {
            return false;
        }

        return Preg::isMatch($this->ignoreRegex, $req);
    }

    public function isUpperBoundIgnored(string $req): bool
    {
        if (!PlatformRepository::isPlatformPackage($req)) {
            return false;
        }

        return $this->isIgnored($req) || Preg::isMatch($this->ignoreUpperBoundRegex, $req);
    }

    /**
     * @param bool $allowUpperBoundOverride For conflicts we do not want the upper bound to be skipped
     */
    public function filterConstraint(string $req, ConstraintInterface $constraint, bool $allowUpperBoundOverride = true): ConstraintInterface
    {
        if (!PlatformRepository::isPlatformPackage($req)) {
            return $constraint;
        }

        if (!$allowUpperBoundOverride || !Preg::isMatch($this->ignoreUpperBoundRegex, $req)) {
            return $constraint;
        }

        if (Preg::isMatch($this->ignoreRegex, $req)) {
            return new MatchAllConstraint;
        }

        $intervals = Intervals::get($constraint);
        $last = end($intervals['numeric']);
        if ($last !== false && (string) $last->getEnd() !== (string) Interval::untilPositiveInfinity()) {
            $constraint = new MultiConstraint([$constraint, new Constraint('>=', $last->getEnd()->getVersion())], false);
        }

        return $constraint;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Filter\PlatformRequirementFilter;

final class PlatformRequirementFilterFactory
{
    /**
     * @param mixed $boolOrList
     */
    public static function fromBoolOrList($boolOrList): PlatformRequirementFilterInterface
    {
        if (is_bool($boolOrList)) {
            return $boolOrList ? self::ignoreAll() : self::ignoreNothing();
        }

        if (is_array($boolOrList)) {
            return new IgnoreListPlatformRequirementFilter($boolOrList);
        }

        throw new \InvalidArgumentException(
            sprintf(
                'PlatformRequirementFilter: Unknown $boolOrList parameter %s. Please report at https://github.com/composer/composer/issues/new.',
                get_debug_type($boolOrList)
            )
        );
    }

    public static function ignoreAll(): PlatformRequirementFilterInterface
    {
        return new IgnoreAllPlatformRequirementFilter();
    }

    public static function ignoreNothing(): PlatformRequirementFilterInterface
    {
        return new IgnoreNothingPlatformRequirementFilter();
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Filter\PlatformRequirementFilter;

use Composer\Repository\PlatformRepository;

final class IgnoreAllPlatformRequirementFilter implements PlatformRequirementFilterInterface
{
    public function isIgnored(string $req): bool
    {
        return PlatformRepository::isPlatformPackage($req);
    }

    public function isUpperBoundIgnored(string $req): bool
    {
        return $this->isIgnored($req);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\EventDispatcher\EventDispatcher;
use Composer\IO\IOInterface;
use Composer\Package\AliasPackage;
use Composer\Package\BasePackage;
use Composer\Package\CompleteAliasPackage;
use Composer\Package\CompletePackage;
use Composer\Package\PackageInterface;
use Composer\Package\Version\StabilityFilter;
use Composer\Pcre\Preg;
use Composer\Plugin\PluginEvents;
use Composer\Plugin\PrePoolCreateEvent;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\RootPackageRepository;
use Composer\Semver\CompilingMatcher;
use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\MatchAllConstraint;
use Composer\Semver\Constraint\MultiConstraint;
use Composer\Semver\Intervals;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
class PoolBuilder
{
    private const LOAD_BATCH_SIZE = 50;

    /**
     * @var int[]
     * @phpstan-var array<key-of<BasePackage::STABILITIES>, BasePackage::STABILITY_*>
     */
    private $acceptableStabilities;
    /**
     * @var int[]
     * @phpstan-var array<string, BasePackage::STABILITY_*>
     */
    private $stabilityFlags;
    /**
     * @var array[]
     * @phpstan-var array<string, array<string, array{alias: string, alias_normalized: string}>>
     */
    private $rootAliases;
    /**
     * @var string[]
     * @phpstan-var array<string, string>
     */
    private $rootReferences;
    /**
     * @var array<string, ConstraintInterface>
     */
    private $temporaryConstraints;
    /**
     * @var ?EventDispatcher
     */
    private $eventDispatcher;
    /**
     * @var PoolOptimizer|null
     */
    private $poolOptimizer;
    /**
     * @var IOInterface
     */
    private $io;
    /**
     * @var array[]
     * @phpstan-var array<string, AliasPackage[]>
     */
    private $aliasMap = [];
    /**
     * @var ConstraintInterface[]
     * @phpstan-var array<string, ConstraintInterface>
     */
    private $packagesToLoad = [];
    /**
     * @var ConstraintInterface[]
     * @phpstan-var array<string, ConstraintInterface>
     */
    private $loadedPackages = [];
    /**
     * @var array[]
     * @phpstan-var array<int, array<string, array<string, PackageInterface>>>
     */
    private $loadedPerRepo = [];
    /**
     * @var array<int, BasePackage>
     */
    private $packages = [];
    /**
     * @var BasePackage[]
     */
    private $unacceptableFixedOrLockedPackages = [];
    /** @var array<string> */
    private $updateAllowList = [];
    /** @var array<string, array<PackageInterface>> */
    private $skippedLoad = [];
    /** @var list<string> */
    private $ignoredTypes = [];
    /** @var list<string>|null */
    private $allowedTypes = null;

    /**
     * If provided, only these package names are loaded
     *
     * This is a special-use functionality of the Request class to optimize the pool creation process
     * when only a minimal subset of packages is needed and we do not need their dependencies.
     *
     * @var array<string, int>|null
     */
    private $restrictedPackagesList = null;

    /**
     * Keeps a list of dependencies which are locked but were auto-unlocked as they are path repositories
     *
     * This half-unlocked state means the package itself will update but the UPDATE_LISTED_WITH_TRANSITIVE_DEPS*
     * flags will not apply until the package really gets unlocked in some other way than being a path repo
     *
     * @var array<string, true>
     */
    private $pathRepoUnlocked = [];

    /**
     * Keeps a list of dependencies which are root requirements, and as such
     * have already their maximum required range loaded and can not be
     * extended by markPackageNameForLoading
     *
     * Packages get cleared from this list if they get unlocked as in that case
     * we need to actually load them
     *
     * @var array<string, true>
     */
    private $maxExtendedReqs = [];
    /**
     * @var array
     * @phpstan-var array<string, bool>
     */
    private $updateAllowWarned = [];

    /** @var int */
    private $indexCounter = 0;

    /** @var ?SecurityAdvisoryPoolFilter */
    private $securityAdvisoryPoolFilter;

    /**
     * @param int[] $acceptableStabilities array of stability => BasePackage::STABILITY_* value
     * @phpstan-param array<key-of<BasePackage::STABILITIES>, BasePackage::STABILITY_*> $acceptableStabilities
     * @param int[] $stabilityFlags an array of package name => BasePackage::STABILITY_* value
     * @phpstan-param array<string, BasePackage::STABILITY_*> $stabilityFlags
     * @param array[] $rootAliases
     * @phpstan-param array<string, array<string, array{alias: string, alias_normalized: string}>> $rootAliases
     * @param string[] $rootReferences an array of package name => source reference
     * @phpstan-param array<string, string> $rootReferences
     * @param array<string, ConstraintInterface> $temporaryConstraints Runtime temporary constraints that will be used to filter packages
     */
    public function __construct(array $acceptableStabilities, array $stabilityFlags, array $rootAliases, array $rootReferences, IOInterface $io, ?EventDispatcher $eventDispatcher = null, ?PoolOptimizer $poolOptimizer = null, array $temporaryConstraints = [], ?SecurityAdvisoryPoolFilter $securityAdvisoryPoolFilter = null)
    {
        $this->acceptableStabilities = $acceptableStabilities;
        $this->stabilityFlags = $stabilityFlags;
        $this->rootAliases = $rootAliases;
        $this->rootReferences = $rootReferences;
        $this->eventDispatcher = $eventDispatcher;
        $this->poolOptimizer = $poolOptimizer;
        $this->io = $io;
        $this->temporaryConstraints = $temporaryConstraints;
        $this->securityAdvisoryPoolFilter = $securityAdvisoryPoolFilter;
    }

    /**
     * Packages of those types are ignored
     *
     * @param list<string> $types
     */
    public function setIgnoredTypes(array $types): void
    {
        $this->ignoredTypes = $types;
    }

    /**
     * Only packages of those types are allowed if set to non-null
     *
     * @param list<string>|null $types
     */
    public function setAllowedTypes(?array $types): void
    {
        $this->allowedTypes = $types;
    }

    /**
     * @param RepositoryInterface[] $repositories
     */
    public function buildPool(array $repositories, Request $request): Pool
    {
        $this->restrictedPackagesList = $request->getRestrictedPackages() !== null ? array_flip($request->getRestrictedPackages()) : null;

        if (\count($request->getUpdateAllowList()) > 0) {
            $this->updateAllowList = $request->getUpdateAllowList();
            $this->warnAboutNonMatchingUpdateAllowList($request);

            if (null === $request->getLockedRepository()) {
                throw new \LogicException('No lock repo present and yet a partial update was requested.');
            }

            foreach ($request->getLockedRepository()->getPackages() as $lockedPackage) {
                if (!$this->isUpdateAllowed($lockedPackage)) {
                    // remember which packages we skipped loading remote content for in this partial update
                    $this->skippedLoad[$lockedPackage->getName()][] = $lockedPackage;
                    foreach ($lockedPackage->getReplaces() as $link) {
                        $this->skippedLoad[$link->getTarget()][] = $lockedPackage;
                    }

                    // Path repo packages are never loaded from lock, to force them to always remain in sync
                    // unless symlinking is disabled in which case we probably should rather treat them like
                    // regular packages. We mark them specially so they can be reloaded fully including update propagation
                    // if they do get unlocked, but by default they are unlocked without update propagation.
                    if ($lockedPackage->getDistType() === 'path') {
                        $transportOptions = $lockedPackage->getTransportOptions();
                        if (!isset($transportOptions['symlink']) || $transportOptions['symlink'] !== false) {
                            $this->pathRepoUnlocked[$lockedPackage->getName()] = true;
                            continue;
                        }
                    }

                    $request->lockPackage($lockedPackage);
                }
            }
        }

        foreach ($request->getFixedOrLockedPackages() as $package) {
            // using MatchAllConstraint here because fixed packages do not need to retrigger
            // loading any packages
            $this->loadedPackages[$package->getName()] = new MatchAllConstraint();

            // replace means conflict, so if a fixed package replaces a name, no need to load that one, packages would conflict anyways
            foreach ($package->getReplaces() as $link) {
                $this->loadedPackages[$link->getTarget()] = new MatchAllConstraint();
            }

            // TODO in how far can we do the above for conflicts? It's more tricky cause conflicts can be limited to
            // specific versions while replace is a conflict with all versions of the name

            if (
                $package->getRepository() instanceof RootPackageRepository
                || $package->getRepository() instanceof PlatformRepository
                || StabilityFilter::isPackageAcceptable($this->acceptableStabilities, $this->stabilityFlags, $package->getNames(), $package->getStability())
            ) {
                $this->loadPackage($request, $repositories, $package, false);
            } else {
                $this->unacceptableFixedOrLockedPackages[] = $package;
            }
        }

        foreach ($request->getRequires() as $packageName => $constraint) {
            // fixed and locked packages have already been added, so if a root require needs one of them, no need to do anything
            if (isset($this->loadedPackages[$packageName])) {
                continue;
            }

            $this->packagesToLoad[$packageName] = $constraint;
            $this->maxExtendedReqs[$packageName] = true;
        }

        // clean up packagesToLoad for anything we manually marked loaded above
        foreach ($this->packagesToLoad as $name => $constraint) {
            if (isset($this->loadedPackages[$name])) {
                unset($this->packagesToLoad[$name]);
            }
        }

        while (\count($this->packagesToLoad) > 0) {
            $this->loadPackagesMarkedForLoading($request, $repositories);
        }

        if (\count($this->temporaryConstraints) > 0) {
            foreach ($this->packages as $i => $package) {
                // we check all alias related packages at once, so no need to check individual aliases
                if ($package instanceof AliasPackage) {
                    continue;
                }

                foreach ($package->getNames() as $packageName) {
                    if (!isset($this->temporaryConstraints[$packageName])) {
                        continue;
                    }

                    $constraint = $this->temporaryConstraints[$packageName];
                    $packageAndAliases = [$i => $package];
                    if (isset($this->aliasMap[spl_object_hash($package)])) {
                        $packageAndAliases += $this->aliasMap[spl_object_hash($package)];
                    }

                    $found = false;
                    foreach ($packageAndAliases as $packageOrAlias) {
                        if (CompilingMatcher::match($constraint, Constraint::OP_EQ, $packageOrAlias->getVersion())) {
                            $found = true;
                        }
                    }

                    if (!$found) {
                        foreach ($packageAndAliases as $index => $packageOrAlias) {
                            unset($this->packages[$index]);
                        }
                    }
                }
            }
        }

        if ($this->eventDispatcher !== null) {
            $prePoolCreateEvent = new PrePoolCreateEvent(
                PluginEvents::PRE_POOL_CREATE,
                $repositories,
                $request,
                $this->acceptableStabilities,
                $this->stabilityFlags,
                $this->rootAliases,
                $this->rootReferences,
                $this->packages,
                $this->unacceptableFixedOrLockedPackages
            );
            $this->eventDispatcher->dispatch($prePoolCreateEvent->getName(), $prePoolCreateEvent);
            $this->packages = $prePoolCreateEvent->getPackages();
            $this->unacceptableFixedOrLockedPackages = $prePoolCreateEvent->getUnacceptableFixedPackages();
        }

        $pool = new Pool($this->packages, $this->unacceptableFixedOrLockedPackages);

        $this->aliasMap = [];
        $this->packagesToLoad = [];
        $this->loadedPackages = [];
        $this->loadedPerRepo = [];
        $this->packages = [];
        $this->unacceptableFixedOrLockedPackages = [];
        $this->maxExtendedReqs = [];
        $this->skippedLoad = [];
        $this->indexCounter = 0;

        $this->io->debug('Built pool.');

        // filter vulnerable packages before optimizing the pool otherwise we may end up with inconsistent state where the optimizer took away versions
        // that were not vulnerable and now suddenly the vulnerable ones are removed and we are missing some versions to make it solvable
        $pool = $this->runSecurityAdvisoryFilter($pool, $repositories, $request);
        $pool = $this->runOptimizer($request, $pool);

        Intervals::clear();

        return $pool;
    }

    private function markPackageNameForLoading(Request $request, string $name, ConstraintInterface $constraint): void
    {
        // Skip platform requires at this stage
        if (PlatformRepository::isPlatformPackage($name)) {
            return;
        }

        // Root require (which was not unlocked) already loaded the maximum range so no
        // need to check anything here
        if (isset($this->maxExtendedReqs[$name])) {
            return;
        }

        // Root requires can not be overruled by dependencies so there is no point in
        // extending the loaded constraint for those.
        // This is triggered when loading a root require which was locked but got unlocked, then
        // we make sure that we load at most the intervals covered by the root constraint.
        $rootRequires = $request->getRequires();
        if (isset($rootRequires[$name]) && !Intervals::isSubsetOf($constraint, $rootRequires[$name])) {
            $constraint = $rootRequires[$name];
        }

        // Not yet loaded or already marked for a reload, set the constraint to be loaded
        if (!isset($this->loadedPackages[$name])) {
            // Maybe it was already marked before but not loaded yet. In that case
            // we have to extend the constraint (we don't check if they are identical because
            // MultiConstraint::create() will optimize anyway)
            if (isset($this->packagesToLoad[$name])) {
                // Already marked for loading and this does not expand the constraint to be loaded, nothing to do
                if (Intervals::isSubsetOf($constraint, $this->packagesToLoad[$name])) {
                    return;
                }

                // extend the constraint to be loaded
                $constraint = Intervals::compactConstraint(MultiConstraint::create([$this->packagesToLoad[$name], $constraint], false));
            }

            $this->packagesToLoad[$name] = $constraint;

            return;
        }

        // No need to load this package with this constraint because it is
        // a subset of the constraint with which we have already loaded packages
        if (Intervals::isSubsetOf($constraint, $this->loadedPackages[$name])) {
            return;
        }

        // We have already loaded that package but not in the constraint that's
        // required. We extend the constraint and mark that package as not being loaded
        // yet so we get the required package versions
        $this->packagesToLoad[$name] = Intervals::compactConstraint(MultiConstraint::create([$this->loadedPackages[$name], $constraint], false));
        unset($this->loadedPackages[$name]);
    }

    /**
     * @param RepositoryInterface[] $repositories
     */
    private function loadPackagesMarkedForLoading(Request $request, array $repositories): void
    {
        foreach ($this->packagesToLoad as $name => $constraint) {
            if ($this->restrictedPackagesList !== null && !isset($this->restrictedPackagesList[$name])) {
                unset($this->packagesToLoad[$name]);
                continue;
            }
            $this->loadedPackages[$name] = $constraint;
        }

        // Load packages in chunks of 50 to prevent memory usage build-up due to caches of all sorts
        $packageBatches = array_chunk($this->packagesToLoad, self::LOAD_BATCH_SIZE, true);
        $this->packagesToLoad = [];

        foreach ($repositories as $repoIndex => $repository) {
            // these repos have their packages fixed or locked if they need to be loaded so we
            // never need to load anything else from them
            if ($repository instanceof PlatformRepository || $repository === $request->getLockedRepository()) {
                continue;
            }

            if (0 === \count($packageBatches)) {
                break;
            }

            foreach ($packageBatches as $batchIndex => $packageBatch) {
                $result = $repository->loadPackages($packageBatch, $this->acceptableStabilities, $this->stabilityFlags, $this->loadedPerRepo[$repoIndex] ?? []);

                foreach ($result['namesFound'] as $name) {
                    // avoid loading the same package again from other repositories once it has been found
                    unset($packageBatches[$batchIndex][$name]);
                }
                foreach ($result['packages'] as $package) {
                    $this->loadedPerRepo[$repoIndex][$package->getName()][$package->getVersion()] = $package;

                    if (in_array($package->getType(), $this->ignoredTypes, true) || ($this->allowedTypes !== null && !in_array($package->getType(), $this->allowedTypes, true))) {
                        continue;
                    }
                    $this->loadPackage($request, $repositories, $package, !isset($this->pathRepoUnlocked[$package->getName()]));
                }
            }

            $packageBatches = array_chunk(array_merge(...$packageBatches), self::LOAD_BATCH_SIZE, true);
        }
    }

    /**
     * @param RepositoryInterface[] $repositories
     */
    private function loadPackage(Request $request, array $repositories, BasePackage $package, bool $propagateUpdate): void
    {
        $index = $this->indexCounter++;
        $this->packages[$index] = $package;

        if ($package instanceof AliasPackage) {
            $this->aliasMap[spl_object_hash($package->getAliasOf())][$index] = $package;
        }

        $name = $package->getName();

        // we're simply setting the root references on all versions for a name here and rely on the solver to pick the
        // right version. It'd be more work to figure out which versions and which aliases of those versions this may
        // apply to
        if (isset($this->rootReferences[$name])) {
            // do not modify the references on already locked or fixed packages
            if (!$request->isLockedPackage($package) && !$request->isFixedPackage($package)) {
                $package->setSourceDistReferences($this->rootReferences[$name]);
            }
        }

        // if propagateUpdate is false we are loading a fixed or locked package, root aliases do not apply as they are
        // manually loaded as separate packages in this case
        //
        // packages in pathRepoUnlocked however need to also load root aliases, they have propagateUpdate set to
        // false because their deps should not be unlocked, but that is irrelevant for root aliases
        if (($propagateUpdate || isset($this->pathRepoUnlocked[$package->getName()])) && isset($this->rootAliases[$name][$package->getVersion()])) {
            $alias = $this->rootAliases[$name][$package->getVersion()];
            if ($package instanceof AliasPackage) {
                $basePackage = $package->getAliasOf();
            } else {
                $basePackage = $package;
            }
            if ($basePackage instanceof CompletePackage) {
                $aliasPackage = new CompleteAliasPackage($basePackage, $alias['alias_normalized'], $alias['alias']);
            } else {
                $aliasPackage = new AliasPackage($basePackage, $alias['alias_normalized'], $alias['alias']);
            }
            $aliasPackage->setRootPackageAlias(true);

            $newIndex = $this->indexCounter++;
            $this->packages[$newIndex] = $aliasPackage;
            $this->aliasMap[spl_object_hash($aliasPackage->getAliasOf())][$newIndex] = $aliasPackage;
        }

        foreach ($package->getRequires() as $link) {
            $require = $link->getTarget();
            $linkConstraint = $link->getConstraint();

            // if the required package is loaded as a locked package only and hasn't had its deps analyzed
            if (isset($this->skippedLoad[$require])) {
                // if we're doing a full update or this is a partial update with transitive deps and we're currently
                // looking at a package which needs to be updated we need to unlock the package we now know is a
                // dependency of another package which we are trying to update, and then attempt to load it again
                if ($propagateUpdate && $request->getUpdateAllowTransitiveDependencies()) {
                    $skippedRootRequires = $this->getSkippedRootRequires($request, $require);

                    if ($request->getUpdateAllowTransitiveRootDependencies() || 0 === \count($skippedRootRequires)) {
                        $this->unlockPackage($request, $repositories, $require);
                        $this->markPackageNameForLoading($request, $require, $linkConstraint);
                    } else {
                        foreach ($skippedRootRequires as $rootRequire) {
                            if (!isset($this->updateAllowWarned[$rootRequire])) {
                                $this->updateAllowWarned[$rootRequire] = true;
                                $this->io->writeError('<warning>Dependency '.$rootRequire.' is also a root requirement. Package has not been listed as an update argument, so keeping locked at old version. Use --with-all-dependencies (-W) to include root dependencies.</warning>');
                            }
                        }
                    }
                } elseif (isset($this->pathRepoUnlocked[$require]) && !isset($this->loadedPackages[$require])) {
                    // if doing a partial update and a package depends on a path-repo-unlocked package which is not referenced by the root, we need to ensure it gets loaded as it was not loaded by the request's root requirements
                    // and would not be loaded above if update propagation is not allowed (which happens if the requirer is itself a path-repo-unlocked package) or if transitive deps are not allowed to be unlocked
                    $this->markPackageNameForLoading($request, $require, $linkConstraint);
                }
            } else {
                $this->markPackageNameForLoading($request, $require, $linkConstraint);
            }
        }

        // if we're doing a partial update with deps we also need to unlock packages which are being replaced in case
        // they are currently locked and thus prevent this updateable package from being installable/updateable
        if ($propagateUpdate && $request->getUpdateAllowTransitiveDependencies()) {
            foreach ($package->getReplaces() as $link) {
                $replace = $link->getTarget();
                if (isset($this->loadedPackages[$replace], $this->skippedLoad[$replace])) {
                    $skippedRootRequires = $this->getSkippedRootRequires($request, $replace);

                    if ($request->getUpdateAllowTransitiveRootDependencies() || 0 === \count($skippedRootRequires)) {
                        $this->unlockPackage($request, $repositories, $replace);
                        // the replaced package only needs to be loaded if something else requires it
                        $this->markPackageNameForLoadingIfRequired($request, $replace);
                    } else {
                        foreach ($skippedRootRequires as $rootRequire) {
                            if (!isset($this->updateAllowWarned[$rootRequire])) {
                                $this->updateAllowWarned[$rootRequire] = true;
                                $this->io->writeError('<warning>Dependency '.$rootRequire.' is also a root requirement. Package has not been listed as an update argument, so keeping locked at old version. Use --with-all-dependencies (-W) to include root dependencies.</warning>');
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * Checks if a particular name is required directly in the request
     *
     * @param string $name packageName
     */
    private function isRootRequire(Request $request, string $name): bool
    {
        $rootRequires = $request->getRequires();

        return isset($rootRequires[$name]);
    }

    /**
     * @return string[]
     */
    private function getSkippedRootRequires(Request $request, string $name): array
    {
        if (!isset($this->skippedLoad[$name])) {
            return [];
        }

        $rootRequires = $request->getRequires();
        $matches = [];

        if (isset($rootRequires[$name])) {
            return array_map(static function (PackageInterface $package) use ($name): string {
                if ($name !== $package->getName()) {
                    return $package->getName() .' (via replace of '.$name.')';
                }

                return $package->getName();
            }, $this->skippedLoad[$name]);
        }

        foreach ($this->skippedLoad[$name] as $packageOrReplacer) {
            if (isset($rootRequires[$packageOrReplacer->getName()])) {
                $matches[] = $packageOrReplacer->getName();
            }
            foreach ($packageOrReplacer->getReplaces() as $link) {
                if (isset($rootRequires[$link->getTarget()])) {
                    if ($name !== $packageOrReplacer->getName()) {
                        $matches[] = $packageOrReplacer->getName() .' (via replace of '.$name.')';
                    } else {
                        $matches[] = $packageOrReplacer->getName();
                    }
                    break;
                }
            }
        }

        return $matches;
    }

    /**
     * Checks whether the update allow list allows this package in the lock file to be updated
     */
    private function isUpdateAllowed(BasePackage $package): bool
    {
        foreach ($this->updateAllowList as $pattern) {
            $patternRegexp = BasePackage::packageNameToRegexp($pattern);
            if (Preg::isMatch($patternRegexp, $package->getName())) {
                return true;
            }
        }

        return false;
    }

    private function warnAboutNonMatchingUpdateAllowList(Request $request): void
    {
        if (null === $request->getLockedRepository()) {
            throw new \LogicException('No lock repo present and yet a partial update was requested.');
        }

        foreach ($this->updateAllowList as $pattern) {
            $matchedPlatformPackage = false;

            $patternRegexp = BasePackage::packageNameToRegexp($pattern);
            // update pattern matches a locked package? => all good
            foreach ($request->getLockedRepository()->getPackages() as $package) {
                if (Preg::isMatch($patternRegexp, $package->getName())) {
                    continue 2;
                }
            }
            // update pattern matches a root require? => all good, probably a new package
            foreach ($request->getRequires() as $packageName => $constraint) {
                if (Preg::isMatch($patternRegexp, $packageName)) {
                    if (PlatformRepository::isPlatformPackage($packageName)) {
                        $matchedPlatformPackage = true;
                        continue;
                    }
                    continue 2;
                }
            }
            if ($matchedPlatformPackage) {
                $this->io->writeError('<warning>Pattern "' . $pattern . '" listed for update matches platform packages, but these cannot be updated by Composer.</warning>');
            } elseif (strpos($pattern, '*') !== false) {
                $this->io->writeError('<warning>Pattern "' . $pattern . '" listed for update does not match any locked packages.</warning>');
            } else {
                $this->io->writeError('<warning>Package "' . $pattern . '" listed for update is not locked.</warning>');
            }
        }
    }

    /**
     * Reverts the decision to use a locked package if a partial update with transitive dependencies
     * found that this package actually needs to be updated
     *
     * @param RepositoryInterface[] $repositories
     */
    private function unlockPackage(Request $request, array $repositories, string $name): void
    {
        foreach ($this->skippedLoad[$name] as $packageOrReplacer) {
            // if we unfixed a replaced package name, we also need to unfix the replacer itself
            // as long as it was not unfixed yet
            if ($packageOrReplacer->getName() !== $name && isset($this->skippedLoad[$packageOrReplacer->getName()])) {
                $replacerName = $packageOrReplacer->getName();
                if ($request->getUpdateAllowTransitiveRootDependencies() || (!$this->isRootRequire($request, $name) && !$this->isRootRequire($request, $replacerName))) {
                    $this->unlockPackage($request, $repositories, $replacerName);

                    if ($this->isRootRequire($request, $replacerName)) {
                        $this->markPackageNameForLoading($request, $replacerName, new MatchAllConstraint);
                    } else {
                        foreach ($this->packages as $loadedPackage) {
                            $requires = $loadedPackage->getRequires();
                            if (isset($requires[$replacerName])) {
                                $this->markPackageNameForLoading($request, $replacerName, $requires[$replacerName]->getConstraint());
                            }
                        }
                    }
                }
            }
        }

        if (isset($this->pathRepoUnlocked[$name])) {
            foreach ($this->packages as $index => $package) {
                if ($package->getName() === $name) {
                    $this->removeLoadedPackage($request, $repositories, $package, $index);
                }
            }
        }

        unset($this->skippedLoad[$name], $this->loadedPackages[$name], $this->maxExtendedReqs[$name], $this->pathRepoUnlocked[$name]);

        // remove locked package by this name which was already initialized
        foreach ($request->getLockedPackages() as $lockedPackage) {
            if (!($lockedPackage instanceof AliasPackage) && $lockedPackage->getName() === $name) {
                if (false !== $index = array_search($lockedPackage, $this->packages, true)) {
                    $request->unlockPackage($lockedPackage);
                    $this->removeLoadedPackage($request, $repositories, $lockedPackage, $index);

                    // make sure that any requirements for this package by other locked or fixed packages are now
                    // also loaded, as they were previously ignored because the locked (now unlocked) package already
                    // satisfied their requirements
                    // and if this package is replacing another that is required by a locked or fixed package, ensure
                    // that we load that replaced package in case an update to this package removes the replacement
                    foreach ($request->getFixedOrLockedPackages() as $fixedOrLockedPackage) {
                        if ($fixedOrLockedPackage === $lockedPackage) {
                            continue;
                        }

                        if (isset($this->skippedLoad[$fixedOrLockedPackage->getName()])) {
                            $requires = $fixedOrLockedPackage->getRequires();
                            if (isset($requires[$lockedPackage->getName()])) {
                                $this->markPackageNameForLoading($request, $lockedPackage->getName(), $requires[$lockedPackage->getName()]->getConstraint());
                            }

                            foreach ($lockedPackage->getReplaces() as $replace) {
                                if (isset($requires[$replace->getTarget()], $this->skippedLoad[$replace->getTarget()])) {
                                    $this->unlockPackage($request, $repositories, $replace->getTarget());
                                    // this package is in $requires so no need to call markPackageNameForLoadingIfRequired
                                    $this->markPackageNameForLoading($request, $replace->getTarget(), $replace->getConstraint());
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    private function markPackageNameForLoadingIfRequired(Request $request, string $name): void
    {
        if ($this->isRootRequire($request, $name)) {
            $this->markPackageNameForLoading($request, $name, $request->getRequires()[$name]);
        }

        foreach ($this->packages as $package) {
            foreach ($package->getRequires() as $link) {
                if ($name === $link->getTarget()) {
                    $this->markPackageNameForLoading($request, $link->getTarget(), $link->getConstraint());
                }
            }
        }
    }

    /**
     * @param RepositoryInterface[] $repositories
     */
    private function removeLoadedPackage(Request $request, array $repositories, BasePackage $package, int $index): void
    {
        $repoIndex = array_search($package->getRepository(), $repositories, true);

        unset($this->loadedPerRepo[$repoIndex][$package->getName()][$package->getVersion()]);
        unset($this->packages[$index]);
        if (isset($this->aliasMap[spl_object_hash($package)])) {
            foreach ($this->aliasMap[spl_object_hash($package)] as $aliasIndex => $aliasPackage) {
                unset($this->loadedPerRepo[$repoIndex][$aliasPackage->getName()][$aliasPackage->getVersion()]);
                unset($this->packages[$aliasIndex]);
            }
            unset($this->aliasMap[spl_object_hash($package)]);
        }
    }

    private function runOptimizer(Request $request, Pool $pool): Pool
    {
        if (null === $this->poolOptimizer) {
            return $pool;
        }

        $this->io->debug('Running pool optimizer.');

        $before = microtime(true);
        $total = \count($pool->getPackages());

        $pool = $this->poolOptimizer->optimize($request, $pool);

        $filtered = $total - \count($pool->getPackages());

        if (0 === $filtered) {
            return $pool;
        }

        $this->io->write(sprintf('Pool optimizer completed in %.3f seconds', microtime(true) - $before), true, IOInterface::VERY_VERBOSE);
        $this->io->write(sprintf(
            '<info>Found %s package versions referenced in your dependency graph. %s (%d%%) were optimized away.</info>',
            number_format($total),
            number_format($filtered),
            round(100 / $total * $filtered)
        ), true, IOInterface::VERY_VERBOSE);

        return $pool;
    }

    /**
     * @param RepositoryInterface[] $repositories
     */
    private function runSecurityAdvisoryFilter(Pool $pool, array $repositories, Request $request): Pool
    {
        if (null === $this->securityAdvisoryPoolFilter) {
            return $pool;
        }

        $this->io->debug('Running security advisory pool filter.');

        $before = microtime(true);
        $total = \count($pool->getPackages());

        $pool = $this->securityAdvisoryPoolFilter->filter($pool, $repositories, $request);

        $filtered = $total - \count($pool->getPackages());

        if (0 === $filtered) {
            return $pool;
        }

        $this->io->write(sprintf('Security advisory pool filter completed in %.3f seconds', microtime(true) - $before), true, IOInterface::VERY_VERBOSE);
        $this->io->write(sprintf(
            '<info>Found %s package versions referenced in your dependency graph. %s (%d%%) were filtered away.</info>',
            number_format($total),
            number_format($filtered),
            round(100 / $total * $filtered)
        ), true, IOInterface::VERY_VERBOSE);

        return $pool;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

/**
 * Stores decisions on installing, removing or keeping packages
 *
 * @author Nils Adermann <naderman@naderman.de>
 * @implements \Iterator<array{0: int, 1: Rule}>
 */
class Decisions implements \Iterator, \Countable
{
    public const DECISION_LITERAL = 0;
    public const DECISION_REASON = 1;

    /** @var Pool */
    protected $pool;
    /** @var array<int, int> */
    protected $decisionMap;
    /**
     * @var array<int, array{0: int, 1: Rule}>
     */
    protected $decisionQueue = [];

    public function __construct(Pool $pool)
    {
        $this->pool = $pool;
        $this->decisionMap = [];
    }

    public function decide(int $literal, int $level, Rule $why): void
    {
        $this->addDecision($literal, $level);
        $this->decisionQueue[] = [
            self::DECISION_LITERAL => $literal,
            self::DECISION_REASON => $why,
        ];
    }

    public function satisfy(int $literal): bool
    {
        $packageId = abs($literal);

        return (
            $literal > 0 && isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0 ||
            $literal < 0 && isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] < 0
        );
    }

    public function conflict(int $literal): bool
    {
        $packageId = abs($literal);

        return (
            (isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0 && $literal < 0) ||
            (isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] < 0 && $literal > 0)
        );
    }

    public function decided(int $literalOrPackageId): bool
    {
        return ($this->decisionMap[abs($literalOrPackageId)] ?? 0) !== 0;
    }

    public function undecided(int $literalOrPackageId): bool
    {
        return ($this->decisionMap[abs($literalOrPackageId)] ?? 0) === 0;
    }

    public function decidedInstall(int $literalOrPackageId): bool
    {
        $packageId = abs($literalOrPackageId);

        return isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0;
    }

    public function decisionLevel(int $literalOrPackageId): int
    {
        $packageId = abs($literalOrPackageId);
        if (isset($this->decisionMap[$packageId])) {
            return abs($this->decisionMap[$packageId]);
        }

        return 0;
    }

    public function decisionRule(int $literalOrPackageId): Rule
    {
        $packageId = abs($literalOrPackageId);

        foreach ($this->decisionQueue as $decision) {
            if ($packageId === abs($decision[self::DECISION_LITERAL])) {
                return $decision[self::DECISION_REASON];
            }
        }

        throw new \LogicException('Did not find a decision rule using '.$literalOrPackageId);
    }

    /**
     * @return array{0: int, 1: Rule} a literal and decision reason
     */
    public function atOffset(int $queueOffset): array
    {
        return $this->decisionQueue[$queueOffset];
    }

    public function validOffset(int $queueOffset): bool
    {
        return $queueOffset >= 0 && $queueOffset < \count($this->decisionQueue);
    }

    public function lastReason(): Rule
    {
        return $this->decisionQueue[\count($this->decisionQueue) - 1][self::DECISION_REASON];
    }

    public function lastLiteral(): int
    {
        return $this->decisionQueue[\count($this->decisionQueue) - 1][self::DECISION_LITERAL];
    }

    public function reset(): void
    {
        while ($decision = array_pop($this->decisionQueue)) {
            $this->decisionMap[abs($decision[self::DECISION_LITERAL])] = 0;
        }
    }

    /**
     * @param int<-1, max> $offset
     */
    public function resetToOffset(int $offset): void
    {
        while (\count($this->decisionQueue) > $offset + 1) {
            $decision = array_pop($this->decisionQueue);
            $this->decisionMap[abs($decision[self::DECISION_LITERAL])] = 0;
        }
    }

    public function revertLast(): void
    {
        $this->decisionMap[abs($this->lastLiteral())] = 0;
        array_pop($this->decisionQueue);
    }

    public function count(): int
    {
        return \count($this->decisionQueue);
    }

    public function rewind(): void
    {
        end($this->decisionQueue);
    }

    /**
     * @return array{0: int, 1: Rule}|false
     */
    #[\ReturnTypeWillChange]
    public function current()
    {
        return current($this->decisionQueue);
    }

    public function key(): ?int
    {
        return key($this->decisionQueue);
    }

    public function next(): void
    {
        prev($this->decisionQueue);
    }

    public function valid(): bool
    {
        return false !== current($this->decisionQueue);
    }

    public function isEmpty(): bool
    {
        return \count($this->decisionQueue) === 0;
    }

    protected function addDecision(int $literal, int $level): void
    {
        $packageId = abs($literal);

        $previousDecision = $this->decisionMap[$packageId] ?? 0;
        if ($previousDecision !== 0) {
            $literalString = $this->pool->literalToPrettyString($literal, []);
            $package = $this->pool->literalToPackage($literal);
            throw new SolverBugException(
                "Trying to decide $literalString on level $level, even though $package was previously decided as ".$previousDecision."."
            );
        }

        if ($literal > 0) {
            $this->decisionMap[$packageId] = $level;
        } else {
            $this->decisionMap[$packageId] = -$level;
        }
    }

    public function toString(?Pool $pool = null): string
    {
        $decisionMap = $this->decisionMap;
        ksort($decisionMap);
        $str = '[';
        foreach ($decisionMap as $packageId => $level) {
            $str .= ($pool !== null ? $pool->literalToPackage($packageId) : $packageId).':'.$level.',';
        }
        $str .= ']';

        return $str;
    }

    public function __toString(): string
    {
        return $this->toString();
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
class GenericRule extends Rule
{
    /** @var list<int> */
    protected $literals;

    /**
     * @param list<int> $literals
     */
    public function __construct(array $literals, $reason, $reasonData)
    {
        parent::__construct($reason, $reasonData);

        // sort all packages ascending by id
        sort($literals);

        $this->literals = $literals;
    }

    /**
     * @return list<int>
     */
    public function getLiterals(): array
    {
        return $this->literals;
    }

    /**
     * @inheritDoc
     */
    public function getHash()
    {
        $data = unpack('ihash', (string) hash(\PHP_VERSION_ID > 80100 ? 'xxh3' : 'sha1', implode(',', $this->literals), true));
        if (false === $data) {
            throw new \RuntimeException('Failed unpacking: '.implode(', ', $this->literals));
        }

        return $data['hash'];
    }

    /**
     * Checks if this rule is equal to another one
     *
     * Ignores whether either of the rules is disabled.
     *
     * @param  Rule $rule The rule to check against
     * @return bool Whether the rules are equal
     */
    public function equals(Rule $rule): bool
    {
        return $this->literals === $rule->getLiterals();
    }

    public function isAssertion(): bool
    {
        return 1 === \count($this->literals);
    }

    /**
     * Formats a rule as a string of the format (Literal1|Literal2|...)
     */
    public function __toString(): string
    {
        $result = $this->isDisabled() ? 'disabled(' : '(';

        foreach ($this->literals as $i => $literal) {
            if ($i !== 0) {
                $result .= '|';
            }
            $result .= $literal;
        }

        $result .= ')';

        return $result;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Advisory\PartialSecurityAdvisory;
use Composer\Advisory\SecurityAdvisory;
use Composer\Package\CompletePackageInterface;
use Composer\Package\AliasPackage;
use Composer\Package\BasePackage;
use Composer\Package\Link;
use Composer\Package\PackageInterface;
use Composer\Package\RootPackageInterface;
use Composer\Pcre\Preg;
use Composer\Repository\RepositorySet;
use Composer\Repository\LockArrayRepository;
use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Package\Version\VersionParser;
use Composer\Repository\PlatformRepository;
use Composer\Semver\Constraint\MultiConstraint;
use Symfony\Component\Console\Formatter\OutputFormatter;

/**
 * Represents a problem detected while solving dependencies
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class Problem
{
    /**
     * A map containing the id of each rule part of this problem as a key
     * @var array<string, true>
     */
    protected $reasonSeen;

    /**
     * A set of reasons for the problem, each is a rule or a root require and a rule
     * @var array<int, array<int, Rule>>
     */
    protected $reasons = [];

    /** @var int */
    protected $section = 0;

    /**
     * Add a rule as a reason
     *
     * @param Rule $rule A rule which is a reason for this problem
     */
    public function addRule(Rule $rule): void
    {
        $this->addReason(spl_object_hash($rule), $rule);
    }

    /**
     * Retrieve all reasons for this problem
     *
     * @return array<int, array<int, Rule>> The problem's reasons
     */
    public function getReasons(): array
    {
        return $this->reasons;
    }

    /**
     * A human readable textual representation of the problem's reasons
     *
     * @param array<int|string, BasePackage> $installedMap A map of all present packages
     * @param array<Rule[]> $learnedPool
     */
    public function getPrettyString(RepositorySet $repositorySet, Request $request, Pool $pool, bool $isVerbose, array $installedMap = [], array $learnedPool = []): string
    {
        // TODO doesn't this entirely defeat the purpose of the problem sections? what's the point of sections?
        $reasons = array_merge(...array_reverse($this->reasons));

        if (\count($reasons) === 1) {
            reset($reasons);
            $rule = current($reasons);

            if ($rule->getReason() !== Rule::RULE_ROOT_REQUIRE) {
                throw new \LogicException("Single reason problems must contain a root require rule.");
            }

            $reasonData = $rule->getReasonData();
            $packageName = $reasonData['packageName'];
            $constraint = $reasonData['constraint'];

            $packages = $pool->whatProvides($packageName, $constraint);
            if (\count($packages) === 0) {
                return "\n    ".implode(self::getMissingPackageReason($repositorySet, $request, $pool, $isVerbose, $packageName, $constraint));
            }
        }

        usort($reasons, function (Rule $rule1, Rule $rule2) use ($pool) {
            $rule1Prio = $this->getRulePriority($rule1);
            $rule2Prio = $this->getRulePriority($rule2);
            if ($rule1Prio !== $rule2Prio) {
                return $rule2Prio - $rule1Prio;
            }

            return $this->getSortableString($pool, $rule1) <=> $this->getSortableString($pool, $rule2);
        });

        return self::formatDeduplicatedRules($reasons, '    ', $repositorySet, $request, $pool, $isVerbose, $installedMap, $learnedPool);
    }

    private function getSortableString(Pool $pool, Rule $rule): string
    {
        switch ($rule->getReason()) {
            case Rule::RULE_ROOT_REQUIRE:
                return $rule->getReasonData()['packageName'];
            case Rule::RULE_FIXED:
                return (string) $rule->getReasonData()['package'];
            case Rule::RULE_PACKAGE_CONFLICT:
            case Rule::RULE_PACKAGE_REQUIRES:
                return $rule->getSourcePackage($pool) . '//' . $rule->getReasonData()->getPrettyString($rule->getSourcePackage($pool));
            case Rule::RULE_PACKAGE_SAME_NAME:
            case Rule::RULE_PACKAGE_ALIAS:
            case Rule::RULE_PACKAGE_INVERSE_ALIAS:
                return (string) $rule->getReasonData();
            case Rule::RULE_LEARNED:
                return implode('-', $rule->getLiterals());
        }

        // @phpstan-ignore deadCode.unreachable
        throw new \LogicException('Unknown rule type: '.$rule->getReason());
    }

    private function getRulePriority(Rule $rule): int
    {
        switch ($rule->getReason()) {
            case Rule::RULE_FIXED:
                return 3;
            case Rule::RULE_ROOT_REQUIRE:
                return 2;
            case Rule::RULE_PACKAGE_CONFLICT:
            case Rule::RULE_PACKAGE_REQUIRES:
                return 1;
            case Rule::RULE_PACKAGE_SAME_NAME:
            case Rule::RULE_LEARNED:
            case Rule::RULE_PACKAGE_ALIAS:
            case Rule::RULE_PACKAGE_INVERSE_ALIAS:
                return 0;
        }

        // @phpstan-ignore deadCode.unreachable
        throw new \LogicException('Unknown rule type: '.$rule->getReason());
    }

    /**
     * @param Rule[] $rules
     * @param array<int|string, BasePackage> $installedMap A map of all present packages
     * @param array<Rule[]> $learnedPool
     * @internal
     */
    public static function formatDeduplicatedRules(array $rules, string $indent, RepositorySet $repositorySet, Request $request, Pool $pool, bool $isVerbose, array $installedMap = [], array $learnedPool = []): string
    {
        $messages = [];
        $templates = [];
        $parser = new VersionParser;
        $deduplicatableRuleTypes = [Rule::RULE_PACKAGE_REQUIRES, Rule::RULE_PACKAGE_CONFLICT];
        foreach ($rules as $rule) {
            $message = $rule->getPrettyString($repositorySet, $request, $pool, $isVerbose, $installedMap, $learnedPool);
            if (in_array($rule->getReason(), $deduplicatableRuleTypes, true) && Preg::isMatchStrictGroups('{^(?P<package>\S+) (?P<version>\S+) (?P<type>requires|conflicts)}', $message, $m)) {
                $message = str_replace('%', '%%', $message);
                $template = Preg::replace('{^\S+ \S+ }', '%s%s ', $message);
                $messages[] = $template;
                $templates[$template][$m[1]][$parser->normalize($m[2])] = $m[2];
                $sourcePackage = $rule->getSourcePackage($pool);
                foreach ($pool->getRemovedVersionsByPackage(spl_object_hash($sourcePackage)) as $version => $prettyVersion) {
                    $templates[$template][$m[1]][$version] = $prettyVersion;
                }
            } elseif ($message !== '') {
                $messages[] = $message;
            }
        }

        $result = [];
        foreach (array_unique($messages) as $message) {
            if (isset($templates[$message])) {
                foreach ($templates[$message] as $package => $versions) {
                    uksort($versions, 'version_compare');
                    if (!$isVerbose) {
                        $versions = self::condenseVersionList($versions, 1);
                    }
                    if (\count($versions) > 1) {
                        // remove the s from requires/conflicts to correct grammar
                        $message = Preg::replace('{^(%s%s (?:require|conflict))s}', '$1', $message);
                        $result[] = sprintf($message, $package, '['.implode(', ', $versions).']');
                    } else {
                        $result[] = sprintf($message, $package, ' '.reset($versions));
                    }
                }
            } else {
                $result[] = $message;
            }
        }

        return "\n$indent- ".implode("\n$indent- ", $result);
    }

    public function isCausedByLock(RepositorySet $repositorySet, Request $request, Pool $pool): bool
    {
        foreach ($this->reasons as $sectionRules) {
            foreach ($sectionRules as $rule) {
                if ($rule->isCausedByLock($repositorySet, $request, $pool)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Store a reason descriptor but ignore duplicates
     *
     * @param string $id     A canonical identifier for the reason
     * @param Rule   $reason The reason descriptor
     */
    protected function addReason(string $id, Rule $reason): void
    {
        // TODO: if a rule is part of a problem description in two sections, isn't this going to remove a message
        // that is important to understand the issue?

        if (!isset($this->reasonSeen[$id])) {
            $this->reasonSeen[$id] = true;
            $this->reasons[$this->section][] = $reason;
        }
    }

    public function nextSection(): void
    {
        $this->section++;
    }

    /**
     * @internal
     * @return array{0: string, 1: string}
     */
    public static function getMissingPackageReason(RepositorySet $repositorySet, Request $request, Pool $pool, bool $isVerbose, string $packageName, ?ConstraintInterface $constraint = null): array
    {
        if (PlatformRepository::isPlatformPackage($packageName)) {
            // handle php/php-*/hhvm
            if (0 === stripos($packageName, 'php') || $packageName === 'hhvm') {
                $version = self::getPlatformPackageVersion($pool, $packageName, phpversion());

                $msg = "- Root composer.json requires ".$packageName.self::constraintToText($constraint).' but ';

                if (defined('HHVM_VERSION') || ($packageName === 'hhvm' && count($pool->whatProvides($packageName)) > 0)) {
                    return [$msg, 'your HHVM version does not satisfy that requirement.'];
                }

                if ($packageName === 'hhvm') {
                    return [$msg, 'HHVM was not detected on this machine, make sure it is in your PATH.'];
                }

                if (null === $version) {
                    return [$msg, 'the '.$packageName.' package is disabled by your platform config. Enable it again with "composer config platform.'.$packageName.' --unset".'];
                }

                return [$msg, 'your '.$packageName.' version ('. $version .') does not satisfy that requirement.'];
            }

            // handle php extensions
            if (0 === stripos($packageName, 'ext-')) {
                if (false !== strpos($packageName, ' ')) {
                    return ['- ', "PHP extension ".$packageName.' should be required as '.str_replace(' ', '-', $packageName).'.'];
                }

                $ext = substr($packageName, 4);
                $msg = "- Root composer.json requires PHP extension ".$packageName.self::constraintToText($constraint).' but ';

                $version = self::getPlatformPackageVersion($pool, $packageName, phpversion($ext) === false ? '0' : phpversion($ext));
                if (null === $version) {
                    $providersStr = self::getProvidersList($repositorySet, $packageName, 5);
                    if ($providersStr !== null) {
                        $providersStr = "\n\n      Alternatively you can require one of these packages that provide the extension (or parts of it):\n".
                            "      <warning>Keep in mind that the suggestions are automated and may not be valid or safe to use</warning>\n$providersStr";
                    }

                    if (extension_loaded($ext)) {
                        return [
                            $msg,
                            'the '.$packageName.' package is disabled by your platform config. Enable it again with "composer config platform.'.$packageName.' --unset".' . $providersStr,
                        ];
                    }

                    return [$msg, 'it is missing from your system. Install or enable PHP\'s '.$ext.' extension.' . $providersStr];
                }

                return [$msg, 'it has the wrong version installed ('.$version.').'];
            }

            // handle linked libs
            if (0 === stripos($packageName, 'lib-')) {
                if (strtolower($packageName) === 'lib-icu') {
                    $error = extension_loaded('intl') ? 'it has the wrong version installed, try upgrading the intl extension.' : 'it is missing from your system, make sure the intl extension is loaded.';

                    return ["- Root composer.json requires linked library ".$packageName.self::constraintToText($constraint).' but ', $error];
                }

                $providersStr = self::getProvidersList($repositorySet, $packageName, 5);
                if ($providersStr !== null) {
                    $providersStr = "\n\n      Alternatively you can require one of these packages that provide the library (or parts of it):\n".
                    "      <warning>Keep in mind that the suggestions are automated and may not be valid or safe to use</warning>\n$providersStr";
                }

                return ["- Root composer.json requires linked library ".$packageName.self::constraintToText($constraint).' but ', 'it has the wrong version installed or is missing from your system, make sure to load the extension providing it.'.$providersStr];
            }
        }

        $lockedPackage = null;
        foreach ($request->getLockedPackages() as $package) {
            if ($package->getName() === $packageName) {
                $lockedPackage = $package;
                if ($pool->isUnacceptableFixedOrLockedPackage($package)) {
                    return ["- ", $package->getPrettyName().' is fixed to '.$package->getPrettyVersion().' (lock file version) by a partial update but that version is rejected by your minimum-stability. Make sure you list it as an argument for the update command.'];
                }
                break;
            }
        }

        if ($constraint instanceof Constraint && $constraint->getOperator() === Constraint::STR_OP_EQ && Preg::isMatch('{^dev-.*#.*}', $constraint->getPrettyString())) {
            $newConstraint = Preg::replace('{ +as +([^,\s|]+)$}', '', $constraint->getPrettyString());
            $packages = $repositorySet->findPackages($packageName, new MultiConstraint([
                new Constraint(Constraint::STR_OP_EQ, $newConstraint),
                new Constraint(Constraint::STR_OP_EQ, str_replace('#', '+', $newConstraint)),
            ], false));
            if (\count($packages) > 0) {
                return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).'. The # character in branch names is replaced by a + character. Make sure to require it as "'.str_replace('#', '+', $constraint->getPrettyString()).'".'];
            }
        }

        // first check if the actual requested package is found in normal conditions
        // if so it must mean it is rejected by another constraint than the one given here
        $packages = $repositorySet->findPackages($packageName, $constraint);
        if (\count($packages) > 0) {
            $rootReqs = $repositorySet->getRootRequires();
            if (isset($rootReqs[$packageName])) {
                $filtered = array_filter($packages, static function ($p) use ($rootReqs, $packageName): bool {
                    return $rootReqs[$packageName]->matches(new Constraint('==', $p->getVersion()));
                });
                if (0 === count($filtered)) {
                    return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but '.(self::hasMultipleNames($packages) ? 'these conflict' : 'it conflicts').' with your root composer.json require ('.$rootReqs[$packageName]->getPrettyString().').'];
                }
            }

            $tempReqs = $repositorySet->getTemporaryConstraints();
            foreach (reset($packages)->getNames() as $name) {
                if (isset($tempReqs[$name])) {
                    $filtered = array_filter($packages, static function ($p) use ($tempReqs, $name): bool {
                        return $tempReqs[$name]->matches(new Constraint('==', $p->getVersion()));
                    });
                    if (0 === count($filtered)) {
                        return ["- Root composer.json requires $name".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but '.(self::hasMultipleNames($packages) ? 'these conflict' : 'it conflicts').' with your temporary update constraint ('.$name.':'.$tempReqs[$name]->getPrettyString().').'];
                    }
                }
            }

            if ($lockedPackage !== null) {
                $fixedConstraint = new Constraint('==', $lockedPackage->getVersion());
                $filtered = array_filter($packages, static function ($p) use ($fixedConstraint): bool {
                    return $fixedConstraint->matches(new Constraint('==', $p->getVersion()));
                });
                if (0 === count($filtered)) {
                    return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but the package is fixed to '.$lockedPackage->getPrettyVersion().' (lock file version) by a partial update and that version does not match. Make sure you list it as an argument for the update command.'];
                }
            }

            $nonLockedPackages = array_filter($packages, static function ($p): bool {
                return !$p->getRepository() instanceof LockArrayRepository;
            });

            if (0 === \count($nonLockedPackages)) {
                return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' in the lock file but not in remote repositories, make sure you avoid updating this package to keep the one from the lock file.'];
            }

            if ($pool->isAbandonedRemovedPackageVersion($packageName, $constraint)) {
                return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but these were not loaded, because they are abandoned and you configured "block-abandoned" to true in your "audit" config.'];
            }

            if ($pool->isSecurityRemovedPackageVersion($packageName, $constraint)) {
                $advisories = $repositorySet->getMatchingSecurityAdvisories($packages, false, true);
                if (isset($advisories['advisories'][$packageName]) && \count($advisories['advisories'][$packageName]) > 0) {
                    $advisoriesList = array_map(static function (SecurityAdvisory $advisory): string {
                        if ($advisory->link !== null && $advisory->link !== '') {
                            return '<href='.OutputFormatter::escape($advisory->link).'>'.$advisory->advisoryId.'</>';
                        }

                        if (str_starts_with($advisory->advisoryId, 'PKSA-')) {
                            return '<href='.OutputFormatter::escape('https://packagist.org/security-advisories/'.$advisory->advisoryId).'>'.$advisory->advisoryId.'</>';
                        }

                        return $advisory->advisoryId;
                    }, $advisories['advisories'][$packageName]);
                } else {
                    $advisoriesList = array_map(static function (string $advisoryId): string {
                        if (str_starts_with($advisoryId, 'PKSA-')) {
                            return '<href='.OutputFormatter::escape('https://packagist.org/security-advisories/'.$advisoryId).'>'.$advisoryId.'</>';
                        }

                        return $advisoryId;
                    }, $pool->getSecurityAdvisoryIdentifiersForPackageVersion($packageName, $constraint));
                }

                return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but these were not loaded, because they are affected by security advisories ("' . implode('", "', $advisoriesList). '"). Go to https://packagist.org/security-advisories/ to find advisory details. To ignore the advisories, add them to the audit "ignore" config. To turn the feature off entirely, you can set "block-insecure" to false in your "audit" config.'];
            }

            return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but these were not loaded, likely because '.(self::hasMultipleNames($packages) ? 'they conflict' : 'it conflicts').' with another require.'];
        }

        // check if the package is found when bypassing stability checks
        $packages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES);
        if (\count($packages) > 0) {
            // we must first verify if a valid package would be found in a lower priority repository
            $allReposPackages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_SHADOWED_REPOSITORIES);
            if (\count($allReposPackages) > 0) {
                return self::computeCheckForLowerPrioRepo($pool, $isVerbose, $packageName, $packages, $allReposPackages, 'minimum-stability', $constraint);
            }

            return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but '.(self::hasMultipleNames($packages) ? 'these do' : 'it does').' not match your minimum-stability.'];
        }

        // check if the package is found when bypassing the constraint and stability checks
        $packages = $repositorySet->findPackages($packageName, null, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES);
        if (\count($packages) > 0) {
            // we must first verify if a valid package would be found in a lower priority repository
            $allReposPackages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_SHADOWED_REPOSITORIES);
            if (\count($allReposPackages) > 0) {
                return self::computeCheckForLowerPrioRepo($pool, $isVerbose, $packageName, $packages, $allReposPackages, 'constraint', $constraint);
            }

            $suffix = '';
            if ($constraint instanceof Constraint && $constraint->getVersion() === 'dev-master') {
                foreach ($packages as $candidate) {
                    if (in_array($candidate->getVersion(), ['dev-default', 'dev-main'], true)) {
                        $suffix = ' Perhaps dev-master was renamed to '.$candidate->getPrettyVersion().'?';
                        break;
                    }
                }
            }

            // check if the root package is a name match and hint the dependencies on root troubleshooting article
            $allReposPackages = $packages;
            $topPackage = reset($allReposPackages);
            if ($topPackage instanceof RootPackageInterface) {
                $suffix = ' See https://getcomposer.org/dep-on-root for details and assistance.';
            }

            return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but '.(self::hasMultipleNames($packages) ? 'these do' : 'it does').' not match the constraint.' . $suffix];
        }

        if (!Preg::isMatch('{^[A-Za-z0-9_./-]+$}', $packageName)) {
            $illegalChars = Preg::replace('{[A-Za-z0-9_./-]+}', '', $packageName);

            return ["- Root composer.json requires $packageName, it ", 'could not be found, it looks like its name is invalid, "'.$illegalChars.'" is not allowed in package names.'];
        }

        $providersStr = self::getProvidersList($repositorySet, $packageName, 15);
        if ($providersStr !== null) {
            return ["- Root composer.json requires $packageName".self::constraintToText($constraint).", it ", "could not be found in any version, but the following packages provide it:\n".$providersStr."      Consider requiring one of these to satisfy the $packageName requirement."];
        }

        return ["- Root composer.json requires $packageName, it ", "could not be found in any version, there may be a typo in the package name."];
    }

    /**
     * @internal
     * @param PackageInterface[] $packages
     */
    public static function getPackageList(array $packages, bool $isVerbose, ?Pool $pool = null, ?ConstraintInterface $constraint = null, bool $useRemovedVersionGroup = false): string
    {
        $prepared = [];
        $hasDefaultBranch = [];
        foreach ($packages as $package) {
            $prepared[$package->getName()]['name'] = $package->getPrettyName();
            $prepared[$package->getName()]['versions'][$package->getVersion()] = $package->getPrettyVersion().($package instanceof AliasPackage ? ' (alias of '.$package->getAliasOf()->getPrettyVersion().')' : '');
            if ($pool !== null && $constraint !== null) {
                foreach ($pool->getRemovedVersions($package->getName(), $constraint) as $version => $prettyVersion) {
                    $prepared[$package->getName()]['versions'][$version] = $prettyVersion;
                }
            }
            if ($pool !== null && $useRemovedVersionGroup) {
                foreach ($pool->getRemovedVersionsByPackage(spl_object_hash($package)) as $version => $prettyVersion) {
                    $prepared[$package->getName()]['versions'][$version] = $prettyVersion;
                }
            }
            if ($package->isDefaultBranch()) {
                $hasDefaultBranch[$package->getName()] = true;
            }
        }

        $preparedStrings = [];
        foreach ($prepared as $name => $package) {
            // remove the implicit default branch alias to avoid cruft in the display
            if (isset($package['versions'][VersionParser::DEFAULT_BRANCH_ALIAS], $hasDefaultBranch[$name])) {
                unset($package['versions'][VersionParser::DEFAULT_BRANCH_ALIAS]);
            }

            uksort($package['versions'], 'version_compare');

            if (!$isVerbose) {
                $package['versions'] = self::condenseVersionList($package['versions'], 4);
            }
            $preparedStrings[] = $package['name'].'['.implode(', ', $package['versions']).']';
        }

        return implode(', ', $preparedStrings);
    }

    /**
     * @param  string $version the effective runtime version of the platform package
     * @return ?string a version string or null if it appears the package was artificially disabled
     */
    private static function getPlatformPackageVersion(Pool $pool, string $packageName, string $version): ?string
    {
        $available = $pool->whatProvides($packageName);

        if (\count($available) > 0) {
            $selected = null;
            foreach ($available as $pkg) {
                if ($pkg->getRepository() instanceof PlatformRepository) {
                    $selected = $pkg;
                    break;
                }
            }
            if ($selected === null) {
                $selected = reset($available);
            }

            // must be a package providing/replacing and not a real platform package
            if ($selected->getName() !== $packageName) {
                /** @var Link $link */
                foreach (array_merge(array_values($selected->getProvides()), array_values($selected->getReplaces())) as $link) {
                    if ($link->getTarget() === $packageName) {
                        return $link->getPrettyConstraint().' '.substr($link->getDescription(), 0, -1).'d by '.$selected->getPrettyString();
                    }
                }
            }

            $version = $selected->getPrettyVersion();
            $extra = $selected->getExtra();
            if ($selected instanceof CompletePackageInterface && isset($extra['config.platform']) && $extra['config.platform'] === true) {
                $version .= '; ' . str_replace('Package ', '', (string) $selected->getDescription());
            }
        } else {
            return null;
        }

        return $version;
    }

    /**
     * @param array<string|int, string> $versions an array of pretty versions, with normalized versions as keys
     * @return list<string> a list of pretty versions and '...' where versions were removed
     */
    private static function condenseVersionList(array $versions, int $max, int $maxDev = 16): array
    {
        if (count($versions) <= $max) {
            return array_values($versions);
        }

        $filtered = [];
        $byMajor = [];
        foreach ($versions as $version => $pretty) {
            if (0 === stripos((string) $version, 'dev-')) {
                $byMajor['dev'][] = $pretty;
            } else {
                $byMajor[Preg::replace('{^(\d+)\..*}', '$1', (string) $version)][] = $pretty;
            }
        }
        foreach ($byMajor as $majorVersion => $versionsForMajor) {
            $maxVersions = $majorVersion === 'dev' ? $maxDev : $max;
            if (count($versionsForMajor) > $maxVersions) {
                // output only 1st and last versions
                $filtered[] = $versionsForMajor[0];
                $filtered[] = '...';
                $filtered[] = $versionsForMajor[count($versionsForMajor) - 1];
            } else {
                $filtered = array_merge($filtered, $versionsForMajor);
            }
        }

        return $filtered;
    }

    /**
     * @param PackageInterface[] $packages
     */
    private static function hasMultipleNames(array $packages): bool
    {
        $name = null;
        foreach ($packages as $package) {
            if ($name === null || $name === $package->getName()) {
                $name = $package->getName();
            } else {
                return true;
            }
        }

        return false;
    }

    /**
     * @param non-empty-array<PackageInterface> $higherRepoPackages
     * @param non-empty-array<PackageInterface> $allReposPackages
     * @return array{0: string, 1: string}
     */
    private static function computeCheckForLowerPrioRepo(Pool $pool, bool $isVerbose, string $packageName, array $higherRepoPackages, array $allReposPackages, string $reason, ?ConstraintInterface $constraint = null): array
    {
        $nextRepoPackages = [];
        $nextRepo = null;

        foreach ($allReposPackages as $package) {
            if ($nextRepo === null || $nextRepo === $package->getRepository()) {
                $nextRepoPackages[] = $package;
                $nextRepo = $package->getRepository();
            } else {
                break;
            }
        }

        assert(null !== $nextRepo);

        if (\count($higherRepoPackages) > 0) {
            $topPackage = reset($higherRepoPackages);
            if ($topPackage instanceof RootPackageInterface) {
                return [
                    "- Root composer.json requires $packageName".self::constraintToText($constraint).', it is ',
                    'satisfiable by '.self::getPackageList($nextRepoPackages, $isVerbose, $pool, $constraint).' from '.$nextRepo->getRepoName().' but '.$topPackage->getPrettyName().' '.$topPackage->getPrettyVersion().' is the root package and cannot be modified. See https://getcomposer.org/dep-on-root for details and assistance.',
                ];
            }
        }

        if ($nextRepo instanceof LockArrayRepository) {
            $singular = count($higherRepoPackages) === 1;

            $suggestion = 'Make sure you either fix the '.$reason.' or avoid updating this package to keep the one present in the lock file ('.self::getPackageList($nextRepoPackages, $isVerbose, $pool, $constraint).').';
            // symlinked path repos cannot be locked so do not suggest keeping it locked
            if ($nextRepoPackages[0]->getDistType() === 'path') {
                $transportOptions = $nextRepoPackages[0]->getTransportOptions();
                if (!isset($transportOptions['symlink']) || $transportOptions['symlink'] !== false) {
                    $suggestion = 'Make sure you fix the '.$reason.' as packages installed from symlinked path repos are updated even in partial updates and the one from the lock file can thus not be used.';
                }
            }

            return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ',
                'found ' . self::getPackageList($higherRepoPackages, $isVerbose, $pool, $constraint).' but ' . ($singular ? 'it does' : 'these do') . ' not match your '.$reason.' and ' . ($singular ? 'is' : 'are') . ' therefore not installable. '.$suggestion,
            ];
        }

        return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', it is ', 'satisfiable by '.self::getPackageList($nextRepoPackages, $isVerbose, $pool, $constraint).' from '.$nextRepo->getRepoName().' but '.self::getPackageList($higherRepoPackages, $isVerbose, $pool, $constraint).' from '.reset($higherRepoPackages)->getRepository()->getRepoName().' has higher repository priority. The packages from the higher priority repository do not match your '.$reason.' and are therefore not installable. That repository is canonical so the lower priority repo\'s packages are not installable. See https://getcomposer.org/repoprio for details and assistance.'];
    }

    /**
     * Turns a constraint into text usable in a sentence describing a request
     */
    protected static function constraintToText(?ConstraintInterface $constraint = null): string
    {
        if ($constraint instanceof Constraint && $constraint->getOperator() === Constraint::STR_OP_EQ && !str_starts_with($constraint->getVersion(), 'dev-')) {
            if (!Preg::isMatch('{^\d+(?:\.\d+)*$}', $constraint->getPrettyString())) {
                return ' '.$constraint->getPrettyString() .' (exact version match)';
            }

            $versions = [$constraint->getPrettyString()];
            for ($i = 3 - substr_count($versions[0], '.'); $i > 0; $i--) {
                $versions[] = end($versions) . '.0';
            }

            return ' ' . $constraint->getPrettyString() . ' (exact version match: ' . (count($versions) > 1 ? implode(', ', array_slice($versions, 0, -1)) . ' or ' . end($versions) : $versions[0]) . ')';
        }

        return $constraint !== null ? ' '.$constraint->getPrettyString() : '';
    }

    private static function getProvidersList(RepositorySet $repositorySet, string $packageName, int $maxProviders): ?string
    {
        $providers = $repositorySet->getProviders($packageName);
        if (\count($providers) > 0) {
            $providersStr = implode(array_map(static function ($p): string {
                $description = $p['description'] !== '' && $p['description'] !== null ? ' '.substr($p['description'], 0, 100) : '';

                return '      - '.$p['name'].$description."\n";
            }, count($providers) > $maxProviders + 1 ? array_slice($providers, 0, $maxProviders) : $providers));
            if (count($providers) > $maxProviders + 1) {
                $providersStr .= '      ... and '.(count($providers) - $maxProviders).' more.'."\n";
            }

            return $providersStr;
        }

        return null;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Filter\PlatformRequirementFilter\IgnoreListPlatformRequirementFilter;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
use Composer\IO\IOInterface;
use Composer\Package\BasePackage;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
class Solver
{
    private const BRANCH_LITERALS = 0;
    private const BRANCH_LEVEL = 1;

    /** @var PolicyInterface */
    protected $policy;
    /** @var Pool */
    protected $pool;

    /** @var RuleSet */
    protected $rules;

    /** @var RuleWatchGraph */
    protected $watchGraph;
    /** @var Decisions */
    protected $decisions;
    /** @var BasePackage[] */
    protected $fixedMap;

    /** @var int */
    protected $propagateIndex;
    /** @var array<int, array{array<int, int>, int}> */
    protected $branches = [];
    /** @var Problem[] */
    protected $problems = [];
    /** @var array<Rule[]> */
    protected $learnedPool = [];
    /** @var array<string, int> */
    protected $learnedWhy = [];

    /** @var bool */
    public $testFlagLearnedPositiveLiteral = false;

    /** @var IOInterface */
    protected $io;

    public function __construct(PolicyInterface $policy, Pool $pool, IOInterface $io)
    {
        $this->io = $io;
        $this->policy = $policy;
        $this->pool = $pool;
    }

    public function getRuleSetSize(): int
    {
        return \count($this->rules);
    }

    public function getPool(): Pool
    {
        return $this->pool;
    }

    // aka solver_makeruledecisions

    private function makeAssertionRuleDecisions(): void
    {
        $decisionStart = \count($this->decisions) - 1;

        $rulesCount = \count($this->rules);
        for ($ruleIndex = 0; $ruleIndex < $rulesCount; $ruleIndex++) {
            $rule = $this->rules->ruleById[$ruleIndex];

            if (!$rule->isAssertion() || $rule->isDisabled()) {
                continue;
            }

            $literals = $rule->getLiterals();
            $literal = $literals[0];

            if (!$this->decisions->decided($literal)) {
                $this->decisions->decide($literal, 1, $rule);
                continue;
            }

            if ($this->decisions->satisfy($literal)) {
                continue;
            }

            // found a conflict
            if (RuleSet::TYPE_LEARNED === $rule->getType()) {
                $rule->disable();
                continue;
            }

            $conflict = $this->decisions->decisionRule($literal);

            if (RuleSet::TYPE_PACKAGE === $conflict->getType()) {
                $problem = new Problem();

                $problem->addRule($rule);
                $problem->addRule($conflict);
                $rule->disable();
                $this->problems[] = $problem;
                continue;
            }

            // conflict with another root require/fixed package
            $problem = new Problem();
            $problem->addRule($rule);
            $problem->addRule($conflict);

            // push all of our rules (can only be root require/fixed package rules)
            // asserting this literal on the problem stack
            foreach ($this->rules->getIteratorFor(RuleSet::TYPE_REQUEST) as $assertRule) {
                if ($assertRule->isDisabled() || !$assertRule->isAssertion()) {
                    continue;
                }

                $assertRuleLiterals = $assertRule->getLiterals();
                $assertRuleLiteral = $assertRuleLiterals[0];

                if (abs($literal) !== abs($assertRuleLiteral)) {
                    continue;
                }
                $problem->addRule($assertRule);
                $assertRule->disable();
            }
            $this->problems[] = $problem;

            $this->decisions->resetToOffset($decisionStart);
            $ruleIndex = -1;
        }
    }

    protected function setupFixedMap(Request $request): void
    {
        $this->fixedMap = [];
        foreach ($request->getFixedPackages() as $package) {
            $this->fixedMap[$package->id] = $package;
        }
    }

    protected function checkForRootRequireProblems(Request $request, PlatformRequirementFilterInterface $platformRequirementFilter): void
    {
        foreach ($request->getRequires() as $packageName => $constraint) {
            if ($platformRequirementFilter->isIgnored($packageName)) {
                continue;
            } elseif ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) {
                $constraint = $platformRequirementFilter->filterConstraint($packageName, $constraint);
            }

            if (0 === \count($this->pool->whatProvides($packageName, $constraint))) {
                $problem = new Problem();
                $problem->addRule(new GenericRule([], Rule::RULE_ROOT_REQUIRE, ['packageName' => $packageName, 'constraint' => $constraint]));
                $this->problems[] = $problem;
            }
        }
    }

    public function solve(Request $request, ?PlatformRequirementFilterInterface $platformRequirementFilter = null): LockTransaction
    {
        $platformRequirementFilter = $platformRequirementFilter ?? PlatformRequirementFilterFactory::ignoreNothing();

        $this->setupFixedMap($request);

        $this->io->writeError('Generating rules', true, IOInterface::DEBUG);
        $ruleSetGenerator = new RuleSetGenerator($this->policy, $this->pool);
        $this->rules = $ruleSetGenerator->getRulesFor($request, $platformRequirementFilter);
        unset($ruleSetGenerator);
        $this->checkForRootRequireProblems($request, $platformRequirementFilter);
        $this->decisions = new Decisions($this->pool);
        $this->watchGraph = new RuleWatchGraph;

        foreach ($this->rules as $rule) {
            $this->watchGraph->insert(new RuleWatchNode($rule));
        }

        /* make decisions based on root require/fix assertions */
        $this->makeAssertionRuleDecisions();

        $this->io->writeError('Resolving dependencies through SAT', true, IOInterface::DEBUG);
        $before = microtime(true);
        $this->runSat();
        $this->io->writeError('', true, IOInterface::DEBUG);
        $this->io->writeError(sprintf('Dependency resolution completed in %.3f seconds', microtime(true) - $before), true, IOInterface::VERBOSE);

        if (\count($this->problems) > 0) {
            throw new SolverProblemsException($this->problems, $this->learnedPool);
        }

        return new LockTransaction($this->pool, $request->getPresentMap(), $request->getFixedPackagesMap(), $this->decisions);
    }

    /**
     * Makes a decision and propagates it to all rules.
     *
     * Evaluates each term affected by the decision (linked through watches)
     * If we find unit rules we make new decisions based on them
     *
     * @return Rule|null A rule on conflict, otherwise null.
     */
    protected function propagate(int $level): ?Rule
    {
        while ($this->decisions->validOffset($this->propagateIndex)) {
            $decision = $this->decisions->atOffset($this->propagateIndex);

            $conflict = $this->watchGraph->propagateLiteral(
                $decision[Decisions::DECISION_LITERAL],
                $level,
                $this->decisions
            );

            $this->propagateIndex++;

            if ($conflict !== null) {
                return $conflict;
            }
        }

        return null;
    }

    /**
     * Reverts a decision at the given level.
     */
    private function revert(int $level): void
    {
        while (!$this->decisions->isEmpty()) {
            $literal = $this->decisions->lastLiteral();

            if ($this->decisions->undecided($literal)) {
                break;
            }

            $decisionLevel = $this->decisions->decisionLevel($literal);

            if ($decisionLevel <= $level) {
                break;
            }

            $this->decisions->revertLast();
            $this->propagateIndex = \count($this->decisions);
        }

        while (\count($this->branches) > 0 && $this->branches[\count($this->branches) - 1][self::BRANCH_LEVEL] >= $level) {
            array_pop($this->branches);
        }
    }

    /**
     * setpropagatelearn
     *
     * add free decision (a positive literal) to decision queue
     * increase level and propagate decision
     * return if no conflict.
     *
     * in conflict case, analyze conflict rule, add resulting
     * rule to learnt rule set, make decision from learnt
     * rule (always unit) and re-propagate.
     *
     * returns the new solver level or 0 if unsolvable
     */
    private function setPropagateLearn(int $level, int $literal, Rule $rule): int
    {
        $level++;

        $this->decisions->decide($literal, $level, $rule);

        while (true) {
            $rule = $this->propagate($level);

            if (null === $rule) {
                break;
            }

            if ($level === 1) {
                $this->analyzeUnsolvable($rule);

                return 0;
            }

            // conflict
            [$learnLiteral, $newLevel, $newRule, $why] = $this->analyze($level, $rule);

            if ($newLevel <= 0 || $newLevel >= $level) {
                throw new SolverBugException(
                    "Trying to revert to invalid level ".$newLevel." from level ".$level."."
                );
            }

            $level = $newLevel;

            $this->revert($level);

            $this->rules->add($newRule, RuleSet::TYPE_LEARNED);

            $this->learnedWhy[spl_object_hash($newRule)] = $why;

            $ruleNode = new RuleWatchNode($newRule);
            $ruleNode->watch2OnHighest($this->decisions);
            $this->watchGraph->insert($ruleNode);

            $this->decisions->decide($learnLiteral, $level, $newRule);
        }

        return $level;
    }

    /**
     * @param non-empty-list<int> $decisionQueue
     */
    private function selectAndInstall(int $level, array $decisionQueue, Rule $rule): int
    {
        // choose best package to install from decisionQueue
        $literals = $this->policy->selectPreferredPackages($this->pool, $decisionQueue, $rule->getRequiredPackage());

        $selectedLiteral = array_shift($literals);

        // if there are multiple candidates, then branch
        if (\count($literals) > 0) {
            $this->branches[] = [$literals, $level];
        }

        return $this->setPropagateLearn($level, $selectedLiteral, $rule);
    }

    /**
     * @return array{int, int, GenericRule, int}
     */
    protected function analyze(int $level, Rule $rule): array
    {
        $analyzedRule = $rule;
        $ruleLevel = 1;
        $num = 0;
        $l1num = 0;
        $seen = [];
        $learnedLiteral = null;
        $otherLearnedLiterals = [];

        $decisionId = \count($this->decisions);

        $this->learnedPool[] = [];

        while (true) {
            $this->learnedPool[\count($this->learnedPool) - 1][] = $rule;

            foreach ($rule->getLiterals() as $literal) {
                // multiconflictrule is really a bunch of rules in one, so some may not have finished propagating yet
                if ($rule instanceof MultiConflictRule && !$this->decisions->decided($literal)) {
                    continue;
                }

                // skip the one true literal
                if ($this->decisions->satisfy($literal)) {
                    continue;
                }

                if (isset($seen[abs($literal)])) {
                    continue;
                }
                $seen[abs($literal)] = true;

                $l = $this->decisions->decisionLevel($literal);

                if (1 === $l) {
                    $l1num++;
                } elseif ($level === $l) {
                    $num++;
                } else {
                    // not level1 or conflict level, add to new rule
                    $otherLearnedLiterals[] = $literal;

                    if ($l > $ruleLevel) {
                        $ruleLevel = $l;
                    }
                }
            }
            unset($literal);

            $l1retry = true;
            while ($l1retry) {
                $l1retry = false;

                if (0 === $num && 0 === --$l1num) {
                    // all level 1 literals done
                    break 2;
                }

                while (true) {
                    if ($decisionId <= 0) {
                        throw new SolverBugException(
                            "Reached invalid decision id $decisionId while looking through $rule for a literal present in the analyzed rule $analyzedRule."
                        );
                    }

                    $decisionId--;

                    $decision = $this->decisions->atOffset($decisionId);
                    $literal = $decision[Decisions::DECISION_LITERAL];

                    if (isset($seen[abs($literal)])) {
                        break;
                    }
                }

                unset($seen[abs($literal)]);

                if (0 !== $num && 0 === --$num) {
                    if ($literal < 0) {
                        $this->testFlagLearnedPositiveLiteral = true;
                    }
                    $learnedLiteral = -$literal;

                    if (0 === $l1num) {
                        break 2;
                    }

                    foreach ($otherLearnedLiterals as $otherLiteral) {
                        unset($seen[abs($otherLiteral)]);
                    }
                    // only level 1 marks left
                    $l1num++;
                    $l1retry = true;
                } else {
                    $decision = $this->decisions->atOffset($decisionId);
                    $rule = $decision[Decisions::DECISION_REASON];

                    if ($rule instanceof MultiConflictRule) {
                        // there is only ever exactly one positive decision in a MultiConflictRule
                        foreach ($rule->getLiterals() as $ruleLiteral) {
                            if (!isset($seen[abs($ruleLiteral)]) && $this->decisions->satisfy(-$ruleLiteral)) {
                                $this->learnedPool[\count($this->learnedPool) - 1][] = $rule;
                                $l = $this->decisions->decisionLevel($ruleLiteral);
                                if (1 === $l) {
                                    $l1num++;
                                } elseif ($level === $l) {
                                    $num++;
                                } else {
                                    // not level1 or conflict level, add to new rule
                                    $otherLearnedLiterals[] = $ruleLiteral;

                                    if ($l > $ruleLevel) {
                                        $ruleLevel = $l;
                                    }
                                }
                                $seen[abs($ruleLiteral)] = true;
                                break;
                            }
                        }

                        $l1retry = true;
                    }
                }
            }

            $decision = $this->decisions->atOffset($decisionId);
            $rule = $decision[Decisions::DECISION_REASON];
        }

        $why = \count($this->learnedPool) - 1;

        if (null === $learnedLiteral) {
            throw new SolverBugException(
                "Did not find a learnable literal in analyzed rule $analyzedRule."
            );
        }

        array_unshift($otherLearnedLiterals, $learnedLiteral);
        $newRule = new GenericRule($otherLearnedLiterals, Rule::RULE_LEARNED, $why);

        return [$learnedLiteral, $ruleLevel, $newRule, $why];
    }

    /**
     * @param array<string, true> $ruleSeen
     */
    private function analyzeUnsolvableRule(Problem $problem, Rule $conflictRule, array &$ruleSeen): void
    {
        $why = spl_object_hash($conflictRule);
        $ruleSeen[$why] = true;

        if ($conflictRule->getType() === RuleSet::TYPE_LEARNED) {
            $learnedWhy = $this->learnedWhy[$why];
            $problemRules = $this->learnedPool[$learnedWhy];

            foreach ($problemRules as $problemRule) {
                if (!isset($ruleSeen[spl_object_hash($problemRule)])) {
                    $this->analyzeUnsolvableRule($problem, $problemRule, $ruleSeen);
                }
            }

            return;
        }

        if ($conflictRule->getType() === RuleSet::TYPE_PACKAGE) {
            // package rules cannot be part of a problem
            return;
        }

        $problem->nextSection();
        $problem->addRule($conflictRule);
    }

    private function analyzeUnsolvable(Rule $conflictRule): void
    {
        $problem = new Problem();
        $problem->addRule($conflictRule);

        $ruleSeen = [];

        $this->analyzeUnsolvableRule($problem, $conflictRule, $ruleSeen);

        $this->problems[] = $problem;

        $seen = [];
        $literals = $conflictRule->getLiterals();

        foreach ($literals as $literal) {
            // skip the one true literal
            if ($this->decisions->satisfy($literal)) {
                continue;
            }
            $seen[abs($literal)] = true;
        }

        foreach ($this->decisions as $decision) {
            $decisionLiteral = $decision[Decisions::DECISION_LITERAL];

            // skip literals that are not in this rule
            if (!isset($seen[abs($decisionLiteral)])) {
                continue;
            }

            $why = $decision[Decisions::DECISION_REASON];

            $problem->addRule($why);
            $this->analyzeUnsolvableRule($problem, $why, $ruleSeen);

            $literals = $why->getLiterals();
            foreach ($literals as $literal) {
                // skip the one true literal
                if ($this->decisions->satisfy($literal)) {
                    continue;
                }
                $seen[abs($literal)] = true;
            }
        }
    }

    private function runSat(): void
    {
        $this->propagateIndex = 0;

        /*
         * here's the main loop:
         * 1) propagate new decisions (only needed once)
         * 2) fulfill root requires/fixed packages
         * 3) fulfill all unresolved rules
         * 4) minimalize solution if we had choices
         * if we encounter a problem, we rewind to a safe level and restart
         * with step 1
         */

        $level = 1;
        $systemLevel = $level + 1;

        while (true) {
            if (1 === $level) {
                $conflictRule = $this->propagate($level);
                if (null !== $conflictRule) {
                    $this->analyzeUnsolvable($conflictRule);

                    return;
                }
            }

            // handle root require/fixed package rules
            if ($level < $systemLevel) {
                $iterator = $this->rules->getIteratorFor(RuleSet::TYPE_REQUEST);
                foreach ($iterator as $rule) {
                    if ($rule->isEnabled()) {
                        $decisionQueue = [];
                        $noneSatisfied = true;

                        foreach ($rule->getLiterals() as $literal) {
                            if ($this->decisions->satisfy($literal)) {
                                $noneSatisfied = false;
                                break;
                            }
                            if ($literal > 0 && $this->decisions->undecided($literal)) {
                                $decisionQueue[] = $literal;
                            }
                        }

                        if ($noneSatisfied && \count($decisionQueue) > 0) {
                            // if any of the options in the decision queue are fixed, only use those
                            $prunedQueue = [];
                            foreach ($decisionQueue as $literal) {
                                if (isset($this->fixedMap[abs($literal)])) {
                                    $prunedQueue[] = $literal;
                                }
                            }
                            if (\count($prunedQueue) > 0) {
                                $decisionQueue = $prunedQueue;
                            }
                        }

                        if ($noneSatisfied && \count($decisionQueue) > 0) {
                            $oLevel = $level;
                            $level = $this->selectAndInstall($level, $decisionQueue, $rule);

                            if (0 === $level) {
                                return;
                            }
                            if ($level <= $oLevel) {
                                break;
                            }
                        }
                    }
                }

                $systemLevel = $level + 1;

                // root requires/fixed packages left
                $iterator->next();
                if ($iterator->valid()) {
                    continue;
                }
            }

            if ($level < $systemLevel) {
                $systemLevel = $level;
            }

            $rulesCount = \count($this->rules);
            $pass = 1;

            $this->io->writeError('Looking at all rules.', true, IOInterface::DEBUG);
            for ($i = 0, $n = 0; $n < $rulesCount; $i++, $n++) {
                if ($i === $rulesCount) {
                    if (1 === $pass) {
                        $this->io->writeError("Something's changed, looking at all rules again (pass #$pass)", false, IOInterface::DEBUG);
                    } else {
                        $this->io->overwriteError("Something's changed, looking at all rules again (pass #$pass)", false, null, IOInterface::DEBUG);
                    }

                    $i = 0;
                    $pass++;
                }

                $rule = $this->rules->ruleById[$i];
                $literals = $rule->getLiterals();

                if ($rule->isDisabled()) {
                    continue;
                }

                $decisionQueue = [];

                // make sure that
                // * all negative literals are installed
                // * no positive literal is installed
                // i.e. the rule is not fulfilled and we
                // just need to decide on the positive literals
                //
                foreach ($literals as $literal) {
                    if ($literal <= 0) {
                        if (!$this->decisions->decidedInstall($literal)) {
                            continue 2; // next rule
                        }
                    } else {
                        if ($this->decisions->decidedInstall($literal)) {
                            continue 2; // next rule
                        }
                        if ($this->decisions->undecided($literal)) {
                            $decisionQueue[] = $literal;
                        }
                    }
                }

                // need to have at least 2 item to pick from
                if (\count($decisionQueue) < 2) {
                    continue;
                }

                $level = $this->selectAndInstall($level, $decisionQueue, $rule);

                if (0 === $level) {
                    return;
                }

                // something changed, so look at all rules again
                $rulesCount = \count($this->rules);
                $n = -1;
            }

            if ($level < $systemLevel) {
                continue;
            }

            // minimization step
            if (\count($this->branches) > 0) {
                $lastLiteral = null;
                $lastLevel = null;
                $lastBranchIndex = 0;
                $lastBranchOffset = 0;

                for ($i = \count($this->branches) - 1; $i >= 0; $i--) {
                    [$literals, $l] = $this->branches[$i];

                    foreach ($literals as $offset => $literal) {
                        if ($literal > 0 && $this->decisions->decisionLevel($literal) > $l + 1) {
                            $lastLiteral = $literal;
                            $lastBranchIndex = $i;
                            $lastBranchOffset = $offset;
                            $lastLevel = $l;
                        }
                    }
                }

                if ($lastLiteral !== null) {
                    assert($lastLevel !== null);
                    unset($this->branches[$lastBranchIndex][self::BRANCH_LITERALS][$lastBranchOffset]);

                    $level = $lastLevel;
                    $this->revert($level);

                    $why = $this->decisions->lastReason();

                    $level = $this->setPropagateLearn($level, $lastLiteral, $why);

                    if ($level === 0) {
                        return;
                    }

                    continue;
                }
            }

            break;
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

/**
 * @author Nils Adermann <naderman@naderman.de>
 * @phpstan-import-type ReasonData from Rule
 */
class Rule2Literals extends Rule
{
    /** @var int */
    protected $literal1;
    /** @var int */
    protected $literal2;

    /**
     * @param Rule::RULE_* $reason A RULE_* constant
     * @param mixed $reasonData
     *
     * @phpstan-param ReasonData $reasonData
     */
    public function __construct(int $literal1, int $literal2, $reason, $reasonData)
    {
        parent::__construct($reason, $reasonData);

        if ($literal1 < $literal2) {
            $this->literal1 = $literal1;
            $this->literal2 = $literal2;
        } else {
            $this->literal1 = $literal2;
            $this->literal2 = $literal1;
        }
    }

    /**
     * @return non-empty-list<int>
     */
    public function getLiterals(): array
    {
        return [$this->literal1, $this->literal2];
    }

    /**
     * @inheritDoc
     */
    public function getHash()
    {
        return $this->literal1.','.$this->literal2;
    }

    /**
     * Checks if this rule is equal to another one
     *
     * Ignores whether either of the rules is disabled.
     *
     * @param  Rule $rule The rule to check against
     * @return bool Whether the rules are equal
     */
    public function equals(Rule $rule): bool
    {
        // specialized fast-case
        if ($rule instanceof self) {
            if ($this->literal1 !== $rule->literal1) {
                return false;
            }

            if ($this->literal2 !== $rule->literal2) {
                return false;
            }

            return true;
        }

        $literals = $rule->getLiterals();
        if (2 !== \count($literals)) {
            return false;
        }

        if ($this->literal1 !== $literals[0]) {
            return false;
        }

        if ($this->literal2 !== $literals[1]) {
            return false;
        }

        return true;
    }

    /** @return false */
    public function isAssertion(): bool
    {
        return false;
    }

    /**
     * Formats a rule as a string of the format (Literal1|Literal2|...)
     */
    public function __toString(): string
    {
        $result = $this->isDisabled() ? 'disabled(' : '(';

        $result .= $this->literal1 . '|' . $this->literal2 . ')';

        return $result;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

/**
 * An extension of SplDoublyLinkedList with seek and removal of current element
 *
 * SplDoublyLinkedList only allows deleting a particular offset and has no
 * method to set the internal iterator to a particular offset.
 *
 * @author Nils Adermann <naderman@naderman.de>
 * @extends \SplDoublyLinkedList<RuleWatchNode>
 */
class RuleWatchChain extends \SplDoublyLinkedList
{
    /**
     * Moves the internal iterator to the specified offset
     *
     * @param int $offset The offset to seek to.
     */
    public function seek(int $offset): void
    {
        $this->rewind();
        for ($i = 0; $i < $offset; $i++, $this->next());
    }

    /**
     * Removes the current element from the list
     *
     * As SplDoublyLinkedList only allows deleting a particular offset and
     * incorrectly sets the internal iterator if you delete the current value
     * this method sets the internal iterator back to the following element
     * using the seek method.
     */
    public function remove(): void
    {
        $offset = $this->key();
        $this->offsetUnset($offset);
        $this->seek($offset);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Package\AliasPackage;
use Composer\Package\BasePackage;
use Composer\Package\Link;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositorySet;
use Composer\Package\Version\VersionParser;
use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\ConstraintInterface;

/**
 * @author Nils Adermann <naderman@naderman.de>
 * @author Ruben Gonzalez <rubenrua@gmail.com>
 * @phpstan-type ReasonData Link|BasePackage|string|int|array{packageName: string, constraint: ConstraintInterface}|array{package: BasePackage}
 */
abstract class Rule
{
    // reason constants and // their reason data contents
    public const RULE_ROOT_REQUIRE = 2; // array{packageName: string, constraint: ConstraintInterface}
    public const RULE_FIXED = 3; // array{package: BasePackage}
    public const RULE_PACKAGE_CONFLICT = 6; // Link
    public const RULE_PACKAGE_REQUIRES = 7; // Link
    public const RULE_PACKAGE_SAME_NAME = 10; // string (package name)
    public const RULE_LEARNED = 12; // int (rule id)
    public const RULE_PACKAGE_ALIAS = 13; // BasePackage
    public const RULE_PACKAGE_INVERSE_ALIAS = 14; // BasePackage

    // bitfield defs
    private const BITFIELD_TYPE = 0;
    private const BITFIELD_REASON = 8;
    private const BITFIELD_DISABLED = 16;

    /** @var int */
    protected $bitfield;
    /** @var Request */
    protected $request;
    /**
     * @var Link|BasePackage|ConstraintInterface|string
     * @phpstan-var ReasonData
     */
    protected $reasonData;

    /**
     * @param self::RULE_* $reason     A RULE_* constant describing the reason for generating this rule
     * @param mixed        $reasonData
     *
     * @phpstan-param ReasonData $reasonData
     */
    public function __construct($reason, $reasonData)
    {
        $this->reasonData = $reasonData;

        $this->bitfield = (0 << self::BITFIELD_DISABLED) |
            ($reason << self::BITFIELD_REASON) |
            (255 << self::BITFIELD_TYPE);
    }

    /**
     * @return list<int>
     */
    abstract public function getLiterals(): array;

    /**
     * @return int|string
     */
    abstract public function getHash();

    abstract public function __toString(): string;

    abstract public function equals(Rule $rule): bool;

    /**
     * @return self::RULE_*
     */
    public function getReason(): int
    {
        return ($this->bitfield & (255 << self::BITFIELD_REASON)) >> self::BITFIELD_REASON;
    }

    /**
     * @phpstan-return ReasonData
     */
    public function getReasonData()
    {
        return $this->reasonData;
    }

    public function getRequiredPackage(): ?string
    {
        switch ($this->getReason()) {
            case self::RULE_ROOT_REQUIRE:
                return $this->getReasonData()['packageName'];
            case self::RULE_FIXED:
                return $this->getReasonData()['package']->getName();
            case self::RULE_PACKAGE_REQUIRES:
                return $this->getReasonData()->getTarget();
        }

        return null;
    }

    /**
     * @param RuleSet::TYPE_* $type
     */
    public function setType($type): void
    {
        $this->bitfield = ($this->bitfield & ~(255 << self::BITFIELD_TYPE)) | ((255 & $type) << self::BITFIELD_TYPE);
    }

    public function getType(): int
    {
        return ($this->bitfield & (255 << self::BITFIELD_TYPE)) >> self::BITFIELD_TYPE;
    }

    public function disable(): void
    {
        $this->bitfield = ($this->bitfield & ~(255 << self::BITFIELD_DISABLED)) | (1 << self::BITFIELD_DISABLED);
    }

    public function enable(): void
    {
        $this->bitfield &= ~(255 << self::BITFIELD_DISABLED);
    }

    public function isDisabled(): bool
    {
        return 0 !== (($this->bitfield & (255 << self::BITFIELD_DISABLED)) >> self::BITFIELD_DISABLED);
    }

    public function isEnabled(): bool
    {
        return 0 === (($this->bitfield & (255 << self::BITFIELD_DISABLED)) >> self::BITFIELD_DISABLED);
    }

    abstract public function isAssertion(): bool;

    public function isCausedByLock(RepositorySet $repositorySet, Request $request, Pool $pool): bool
    {
        if ($this->getReason() === self::RULE_PACKAGE_REQUIRES) {
            if (PlatformRepository::isPlatformPackage($this->getReasonData()->getTarget())) {
                return false;
            }
            if ($request->getLockedRepository() !== null) {
                foreach ($request->getLockedRepository()->getPackages() as $package) {
                    if ($package->getName() === $this->getReasonData()->getTarget()) {
                        if ($pool->isUnacceptableFixedOrLockedPackage($package)) {
                            return true;
                        }
                        if (!$this->getReasonData()->getConstraint()->matches(new Constraint('=', $package->getVersion()))) {
                            return true;
                        }
                        // required package was locked but has been unlocked and still matches
                        if (!$request->isLockedPackage($package)) {
                            return true;
                        }
                        break;
                    }
                }
            }
        }

        if ($this->getReason() === self::RULE_ROOT_REQUIRE) {
            if (PlatformRepository::isPlatformPackage($this->getReasonData()['packageName'])) {
                return false;
            }
            if ($request->getLockedRepository() !== null) {
                foreach ($request->getLockedRepository()->getPackages() as $package) {
                    if ($package->getName() === $this->getReasonData()['packageName']) {
                        if ($pool->isUnacceptableFixedOrLockedPackage($package)) {
                            return true;
                        }
                        if (!$this->getReasonData()['constraint']->matches(new Constraint('=', $package->getVersion()))) {
                            return true;
                        }
                        break;
                    }
                }
            }
        }

        return false;
    }

    /**
     * @internal
     */
    public function getSourcePackage(Pool $pool): BasePackage
    {
        $literals = $this->getLiterals();

        switch ($this->getReason()) {
            case self::RULE_PACKAGE_CONFLICT:
                $package1 = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[0]));
                $package2 = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[1]));

                $reasonData = $this->getReasonData();
                // swap literals if they are not in the right order with package2 being the conflicter
                if ($reasonData->getSource() === $package1->getName()) {
                    [$package2, $package1] = [$package1, $package2];
                }

                return $package2;

            case self::RULE_PACKAGE_REQUIRES:
                $sourceLiteral = $literals[0];
                $sourcePackage = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($sourceLiteral));

                return $sourcePackage;

            default:
                throw new \LogicException('Not implemented');
        }
    }

    /**
     * @param BasePackage[] $installedMap
     * @param array<Rule[]> $learnedPool
     */
    public function getPrettyString(RepositorySet $repositorySet, Request $request, Pool $pool, bool $isVerbose, array $installedMap = [], array $learnedPool = []): string
    {
        $literals = $this->getLiterals();

        switch ($this->getReason()) {
            case self::RULE_ROOT_REQUIRE:
                $reasonData = $this->getReasonData();
                $packageName = $reasonData['packageName'];
                $constraint = $reasonData['constraint'];

                $packages = $pool->whatProvides($packageName, $constraint);
                if (0 === \count($packages)) {
                    return 'No package found to satisfy root composer.json require '.$packageName.' '.$constraint->getPrettyString();
                }

                $packagesNonAlias = array_values(array_filter($packages, static function ($p): bool {
                    return !($p instanceof AliasPackage);
                }));
                if (\count($packagesNonAlias) === 1) {
                    $package = $packagesNonAlias[0];
                    if ($request->isLockedPackage($package)) {
                        return $package->getPrettyName().' is locked to version '.$package->getPrettyVersion()." and an update of this package was not requested.";
                    }
                }

                return 'Root composer.json requires '.$packageName.' '.$constraint->getPrettyString().' -> satisfiable by '.$this->formatPackagesUnique($pool, $packages, $isVerbose, $constraint).'.';

            case self::RULE_FIXED:
                $package = $this->deduplicateDefaultBranchAlias($this->getReasonData()['package']);

                if ($request->isLockedPackage($package)) {
                    return $package->getPrettyName().' is locked to version '.$package->getPrettyVersion().' and an update of this package was not requested.';
                }

                return $package->getPrettyName().' is present at version '.$package->getPrettyVersion() . ' and cannot be modified by Composer';

            case self::RULE_PACKAGE_CONFLICT:
                $package1 = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[0]));
                $package2 = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[1]));

                $conflictTarget = $package1->getPrettyString();
                $reasonData = $this->getReasonData();

                // swap literals if they are not in the right order with package2 being the conflicter
                if ($reasonData->getSource() === $package1->getName()) {
                    [$package2, $package1] = [$package1, $package2];
                    $conflictTarget = $package1->getPrettyName().' '.$reasonData->getPrettyConstraint();
                }

                // if the conflict is not directly against the package but something it provides/replaces,
                // we try to find that link to display a better message
                if ($reasonData->getTarget() !== $package1->getName()) {
                    $provideType = null;
                    $provided = null;
                    foreach ($package1->getProvides() as $provide) {
                        if ($provide->getTarget() === $reasonData->getTarget()) {
                            $provideType = 'provides';
                            $provided = $provide->getPrettyConstraint();
                            break;
                        }
                    }
                    foreach ($package1->getReplaces() as $replace) {
                        if ($replace->getTarget() === $reasonData->getTarget()) {
                            $provideType = 'replaces';
                            $provided = $replace->getPrettyConstraint();
                            break;
                        }
                    }
                    if (null !== $provideType) {
                        $conflictTarget = $reasonData->getTarget().' '.$reasonData->getPrettyConstraint().' ('.$package1->getPrettyString().' '.$provideType.' '.$reasonData->getTarget().' '.$provided.')';
                    }
                }

                return $package2->getPrettyString().' conflicts with '.$conflictTarget.'.';

            case self::RULE_PACKAGE_REQUIRES:
                assert(\count($literals) > 0);
                $sourceLiteral = array_shift($literals);
                $sourcePackage = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($sourceLiteral));
                $reasonData = $this->getReasonData();

                $requires = [];
                foreach ($literals as $literal) {
                    $requires[] = $pool->literalToPackage($literal);
                }

                $text = $reasonData->getPrettyString($sourcePackage);
                if (\count($requires) > 0) {
                    $text .= ' -> satisfiable by ' . $this->formatPackagesUnique($pool, $requires, $isVerbose, $reasonData->getConstraint()) . '.';
                } else {
                    $targetName = $reasonData->getTarget();

                    $reason = Problem::getMissingPackageReason($repositorySet, $request, $pool, $isVerbose, $targetName, $reasonData->getConstraint());

                    return $text . ' -> ' . $reason[1];
                }

                return $text;

            case self::RULE_PACKAGE_SAME_NAME:
                $packageNames = [];
                foreach ($literals as $literal) {
                    $package = $pool->literalToPackage($literal);
                    $packageNames[$package->getName()] = true;
                }
                unset($literal);
                $replacedName = $this->getReasonData();

                if (\count($packageNames) > 1) {
                    if (!isset($packageNames[$replacedName])) {
                        $reason = 'They '.(\count($literals) === 2 ? 'both' : 'all').' replace '.$replacedName.' and thus cannot coexist.';
                    } else {
                        $replacerNames = $packageNames;
                        unset($replacerNames[$replacedName]);
                        $replacerNames = array_keys($replacerNames);

                        if (\count($replacerNames) === 1) {
                            $reason = $replacerNames[0] . ' replaces ';
                        } else {
                            $reason = '['.implode(', ', $replacerNames).'] replace ';
                        }
                        $reason .= $replacedName.' and thus cannot coexist with it.';
                    }

                    $installedPackages = [];
                    $removablePackages = [];
                    foreach ($literals as $literal) {
                        if (isset($installedMap[abs($literal)])) {
                            $installedPackages[] = $pool->literalToPackage($literal);
                        } else {
                            $removablePackages[] = $pool->literalToPackage($literal);
                        }
                    }

                    if (\count($installedPackages) > 0 && \count($removablePackages) > 0) {
                        return $this->formatPackagesUnique($pool, $removablePackages, $isVerbose, null, true).' cannot be installed as that would require removing '.$this->formatPackagesUnique($pool, $installedPackages, $isVerbose, null, true).'. '.$reason;
                    }

                    return 'Only one of these can be installed: '.$this->formatPackagesUnique($pool, $literals, $isVerbose, null, true).'. '.$reason;
                }

                return 'You can only install one version of a package, so only one of these can be installed: ' . $this->formatPackagesUnique($pool, $literals, $isVerbose, null, true) . '.';
            case self::RULE_LEARNED:
                /** @TODO currently still generates way too much output to be helpful, and in some cases can even lead to endless recursion */
                // if (isset($learnedPool[$this->getReasonData()])) {
                //     echo $this->getReasonData()."\n";
                //     $learnedString = ', learned rules:' . Problem::formatDeduplicatedRules($learnedPool[$this->getReasonData()], '        ', $repositorySet, $request, $pool, $isVerbose, $installedMap, $learnedPool);
                // } else {
                //     $learnedString = ' (reasoning unavailable)';
                // }
                $learnedString = ' (conflict analysis result)';

                if (\count($literals) === 1) {
                    $ruleText = $pool->literalToPrettyString($literals[0], $installedMap);
                } else {
                    $groups = [];
                    foreach ($literals as $literal) {
                        $package = $pool->literalToPackage($literal);
                        if (isset($installedMap[$package->id])) {
                            $group = $literal > 0 ? 'keep' : 'remove';
                        } else {
                            $group = $literal > 0 ? 'install' : 'don\'t install';
                        }

                        $groups[$group][] = $this->deduplicateDefaultBranchAlias($package);
                    }
                    $ruleTexts = [];
                    foreach ($groups as $group => $packages) {
                        $ruleTexts[] = $group . (\count($packages) > 1 ? ' one of' : '').' ' . $this->formatPackagesUnique($pool, $packages, $isVerbose);
                    }

                    $ruleText = implode(' | ', $ruleTexts);
                }

                return 'Conclusion: '.$ruleText.$learnedString;
            case self::RULE_PACKAGE_ALIAS:
                $aliasPackage = $pool->literalToPackage($literals[0]);

                // avoid returning content like "9999999-dev is an alias of dev-master" as it is useless
                if ($aliasPackage->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) {
                    return '';
                }
                $package = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[1]));

                return $aliasPackage->getPrettyString() .' is an alias of '.$package->getPrettyString().' and thus requires it to be installed too.';
            case self::RULE_PACKAGE_INVERSE_ALIAS:
                // inverse alias rules work the other way around than above
                $aliasPackage = $pool->literalToPackage($literals[1]);

                // avoid returning content like "9999999-dev is an alias of dev-master" as it is useless
                if ($aliasPackage->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) {
                    return '';
                }
                $package = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[0]));

                return $aliasPackage->getPrettyString() .' is an alias of '.$package->getPrettyString().' and must be installed with it.';
            default:
                $ruleText = '';
                foreach ($literals as $i => $literal) {
                    if ($i !== 0) {
                        $ruleText .= '|';
                    }
                    $ruleText .= $pool->literalToPrettyString($literal, $installedMap);
                }

                return '('.$ruleText.')';
        }
    }

    /**
     * @param array<int|BasePackage> $literalsOrPackages An array containing packages or literals
     */
    protected function formatPackagesUnique(Pool $pool, array $literalsOrPackages, bool $isVerbose, ?ConstraintInterface $constraint = null, bool $useRemovedVersionGroup = false): string
    {
        $packages = [];
        foreach ($literalsOrPackages as $package) {
            $packages[] = \is_object($package) ? $package : $pool->literalToPackage($package);
        }

        return Problem::getPackageList($packages, $isVerbose, $pool, $constraint, $useRemovedVersionGroup);
    }

    private function deduplicateDefaultBranchAlias(BasePackage $package): BasePackage
    {
        if ($package instanceof AliasPackage && $package->getPrettyVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) {
            $package = $package->getAliasOf();
        }

        return $package;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Package\BasePackage;
use Composer\Package\PackageInterface;
use Composer\Repository\LockArrayRepository;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\MatchAllConstraint;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
class Request
{
    /**
     * Identifies a partial update for listed packages only, all dependencies will remain at locked versions
     */
    public const UPDATE_ONLY_LISTED = 0;

    /**
     * Identifies a partial update for listed packages and recursively all their dependencies, however dependencies
     * also directly required by the root composer.json and their dependencies will remain at the locked version.
     */
    public const UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE = 1;

    /**
     * Identifies a partial update for listed packages and recursively all their dependencies, even dependencies
     * also directly required by the root composer.json will be updated.
     */
    public const UPDATE_LISTED_WITH_TRANSITIVE_DEPS = 2;

    /** @var ?LockArrayRepository */
    protected $lockedRepository;
    /** @var array<string, ConstraintInterface> */
    protected $requires = [];
    /** @var array<string, BasePackage> */
    protected $fixedPackages = [];
    /** @var array<string, BasePackage> */
    protected $lockedPackages = [];
    /** @var array<string, BasePackage> */
    protected $fixedLockedPackages = [];
    /** @var array<string> */
    protected $updateAllowList = [];
    /** @var false|self::UPDATE_* */
    protected $updateAllowTransitiveDependencies = false;
    /** @var non-empty-list<string>|null */
    private $restrictedPackages = null;

    public function __construct(?LockArrayRepository $lockedRepository = null)
    {
        $this->lockedRepository = $lockedRepository;
    }

    public function requireName(string $packageName, ?ConstraintInterface $constraint = null): void
    {
        $packageName = strtolower($packageName);

        if ($constraint === null) {
            $constraint = new MatchAllConstraint();
        }
        if (isset($this->requires[$packageName])) {
            throw new \LogicException('Overwriting requires seems like a bug ('.$packageName.' '.$this->requires[$packageName]->getPrettyString().' => '.$constraint->getPrettyString().', check why it is happening, might be a root alias');
        }
        $this->requires[$packageName] = $constraint;
    }

    /**
     * Mark a package as currently present and having to remain installed
     *
     * This is used for platform packages which cannot be modified by Composer. A rule enforcing their installation is
     * generated for dependency resolution. Partial updates with dependencies cannot in any way modify these packages.
     */
    public function fixPackage(BasePackage $package): void
    {
        $this->fixedPackages[spl_object_hash($package)] = $package;
    }

    /**
     * Mark a package as locked to a specific version but removable
     *
     * This is used for lock file packages which need to be treated similar to fixed packages by the pool builder in
     * that by default they should really only have the currently present version loaded and no remote alternatives.
     *
     * However unlike fixed packages there will not be a special rule enforcing their installation for the solver, so
     * if nothing requires these packages they will be removed. Additionally in a partial update these packages can be
     * unlocked, meaning other versions can be installed if explicitly requested as part of the update.
     */
    public function lockPackage(BasePackage $package): void
    {
        $this->lockedPackages[spl_object_hash($package)] = $package;
    }

    /**
     * Marks a locked package fixed. So it's treated irremovable like a platform package.
     *
     * This is necessary for the composer install step which verifies the lock file integrity and should not allow
     * removal of any packages. At the same time lock packages there cannot simply be marked fixed, as error reporting
     * would then report them as platform packages, so this still marks them as locked packages at the same time.
     */
    public function fixLockedPackage(BasePackage $package): void
    {
        $this->fixedPackages[spl_object_hash($package)] = $package;
        $this->fixedLockedPackages[spl_object_hash($package)] = $package;
    }

    public function unlockPackage(BasePackage $package): void
    {
        unset($this->lockedPackages[spl_object_hash($package)]);
    }

    /**
     * @param array<string> $updateAllowList
     * @param false|self::UPDATE_* $updateAllowTransitiveDependencies
     */
    public function setUpdateAllowList(array $updateAllowList, $updateAllowTransitiveDependencies): void
    {
        $this->updateAllowList = $updateAllowList;
        $this->updateAllowTransitiveDependencies = $updateAllowTransitiveDependencies;
    }

    /**
     * @return array<string>
     */
    public function getUpdateAllowList(): array
    {
        return $this->updateAllowList;
    }

    public function getUpdateAllowTransitiveDependencies(): bool
    {
        return $this->updateAllowTransitiveDependencies !== self::UPDATE_ONLY_LISTED;
    }

    public function getUpdateAllowTransitiveRootDependencies(): bool
    {
        return $this->updateAllowTransitiveDependencies === self::UPDATE_LISTED_WITH_TRANSITIVE_DEPS;
    }

    /**
     * @return array<string, ConstraintInterface>
     */
    public function getRequires(): array
    {
        return $this->requires;
    }

    /**
     * @return array<string, BasePackage>
     */
    public function getFixedPackages(): array
    {
        return $this->fixedPackages;
    }

    public function isFixedPackage(BasePackage $package): bool
    {
        return isset($this->fixedPackages[spl_object_hash($package)]);
    }

    /**
     * @return array<string, BasePackage>
     */
    public function getLockedPackages(): array
    {
        return $this->lockedPackages;
    }

    public function isLockedPackage(PackageInterface $package): bool
    {
        return isset($this->lockedPackages[spl_object_hash($package)]) || isset($this->fixedLockedPackages[spl_object_hash($package)]);
    }

    /**
     * @return array<string, BasePackage>
     */
    public function getFixedOrLockedPackages(): array
    {
        return array_merge($this->fixedPackages, $this->lockedPackages);
    }

    /**
     * @return ($packageIds is true ? array<int, BasePackage> : array<string, BasePackage>)
     *
     * @TODO look into removing the packageIds option, the only place true is used
     *       is for the installed map in the solver problems.
     *       Some locked packages may not be in the pool,
     *       so they have a package->id of -1
     */
    public function getPresentMap(bool $packageIds = false): array
    {
        $presentMap = [];

        if ($this->lockedRepository !== null) {
            foreach ($this->lockedRepository->getPackages() as $package) {
                $presentMap[$packageIds ? $package->getId() : spl_object_hash($package)] = $package;
            }
        }

        foreach ($this->fixedPackages as $package) {
            $presentMap[$packageIds ? $package->getId() : spl_object_hash($package)] = $package;
        }

        return $presentMap;
    }

    /**
     * @return array<int, BasePackage>
     */
    public function getFixedPackagesMap(): array
    {
        $fixedPackagesMap = [];

        foreach ($this->fixedPackages as $package) {
            $fixedPackagesMap[$package->getId()] = $package;
        }

        return $fixedPackagesMap;
    }

    public function getLockedRepository(): ?LockArrayRepository
    {
        return $this->lockedRepository;
    }

    /**
     * Restricts the pool builder from loading other packages than those listed here
     *
     * @param non-empty-list<string> $names
     */
    public function restrictPackages(array $names): void
    {
        $this->restrictedPackages = $names;
    }

    /**
     * @return list<string>
     */
    public function getRestrictedPackages(): ?array
    {
        return $this->restrictedPackages;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
class SolverBugException extends \RuntimeException
{
    public function __construct(string $message)
    {
        parent::__construct(
            $message."\nThis exception was most likely caused by a bug in Composer.\n".
            "Please report the command you ran, the exact error you received, and your composer.json on https://github.com/composer/composer/issues - thank you!\n"
        );
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

/**
 * @author Nils Adermann <naderman@naderman.de>
 *
 * MultiConflictRule([A, B, C]) acts as Rule([-A, -B]), Rule([-A, -C]), Rule([-B, -C])
 */
class MultiConflictRule extends Rule
{
    /** @var non-empty-list<int> */
    protected $literals;

    /**
     * @param non-empty-list<int> $literals
     */
    public function __construct(array $literals, $reason, $reasonData)
    {
        parent::__construct($reason, $reasonData);

        if (\count($literals) < 3) {
            throw new \RuntimeException("multi conflict rule requires at least 3 literals");
        }

        // sort all packages ascending by id
        sort($literals);

        $this->literals = $literals;
    }

    /**
     * @return non-empty-list<int>
     */
    public function getLiterals(): array
    {
        return $this->literals;
    }

    /**
     * @inheritDoc
     */
    public function getHash()
    {
        $data = unpack('ihash', (string) hash(\PHP_VERSION_ID > 80100 ? 'xxh3' : 'sha1', 'c:'.implode(',', $this->literals), true));
        if (false === $data) {
            throw new \RuntimeException('Failed unpacking: '.implode(', ', $this->literals));
        }

        return $data['hash'];
    }

    /**
     * Checks if this rule is equal to another one
     *
     * Ignores whether either of the rules is disabled.
     *
     * @param  Rule $rule The rule to check against
     * @return bool Whether the rules are equal
     */
    public function equals(Rule $rule): bool
    {
        if ($rule instanceof MultiConflictRule) {
            return $this->literals === $rule->getLiterals();
        }

        return false;
    }

    public function isAssertion(): bool
    {
        return false;
    }

    /**
     * @return never
     * @throws \RuntimeException
     */
    public function disable(): void
    {
        throw new \RuntimeException("Disabling multi conflict rules is not possible. Please contact composer at https://github.com/composer/composer to let us debug what lead to this situation.");
    }

    /**
     * Formats a rule as a string of the format (Literal1|Literal2|...)
     */
    public function __toString(): string
    {
        // TODO multi conflict?
        $result = $this->isDisabled() ? 'disabled(multi(' : '(multi(';

        foreach ($this->literals as $i => $literal) {
            if ($i !== 0) {
                $result .= '|';
            }
            $result .= $literal;
        }

        $result .= '))';

        return $result;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Advisory\PartialSecurityAdvisory;
use Composer\Advisory\SecurityAdvisory;
use Composer\Package\BasePackage;
use Composer\Package\Version\VersionParser;
use Composer\Semver\CompilingMatcher;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\Constraint;

/**
 * A package pool contains all packages for dependency resolution
 *
 * @author Nils Adermann <naderman@naderman.de>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Pool implements \Countable
{
    /** @var BasePackage[] */
    protected $packages = [];
    /** @var array<string, BasePackage[]> */
    protected $packageByName = [];
    /** @var VersionParser */
    protected $versionParser;
    /** @var array<string, array<string, BasePackage[]>> */
    protected $providerCache = [];
    /** @var BasePackage[] */
    protected $unacceptableFixedOrLockedPackages;
    /** @var array<string, array<string, string>> Map of package name => normalized version => pretty version */
    protected $removedVersions = [];
    /** @var array<string, array<string, string>> Map of package object hash => removed normalized versions => removed pretty version */
    protected $removedVersionsByPackage = [];
    /** @var array<string, array<string, array<SecurityAdvisory|PartialSecurityAdvisory>>> Map of package name => normalized version => security advisories */
    private $securityRemovedVersions = [];
    /** @var array<string, array<string, string>> Map of package name => normalized version => pretty version */
    private $abandonedRemovedVersions = [];

    /**
     * @param BasePackage[] $packages
     * @param BasePackage[] $unacceptableFixedOrLockedPackages
     * @param array<string, array<string, string>> $removedVersions
     * @param array<string, array<string, string>> $removedVersionsByPackage
     * @param array<string, array<string, array<SecurityAdvisory|PartialSecurityAdvisory>>> $securityRemovedVersions
     * @param array<string, array<string, string>> $abandonedRemovedVersions
     */
    public function __construct(array $packages = [], array $unacceptableFixedOrLockedPackages = [], array $removedVersions = [], array $removedVersionsByPackage = [], array $securityRemovedVersions = [], array $abandonedRemovedVersions = [])
    {
        $this->versionParser = new VersionParser;
        $this->setPackages($packages);
        $this->unacceptableFixedOrLockedPackages = $unacceptableFixedOrLockedPackages;
        $this->removedVersions = $removedVersions;
        $this->removedVersionsByPackage = $removedVersionsByPackage;
        $this->securityRemovedVersions = $securityRemovedVersions;
        $this->abandonedRemovedVersions = $abandonedRemovedVersions;
    }

    /**
     * @return array<string, string>
     */
    public function getRemovedVersions(string $name, ConstraintInterface $constraint): array
    {
        if (!isset($this->removedVersions[$name])) {
            return [];
        }

        $result = [];
        foreach ($this->removedVersions[$name] as $version => $prettyVersion) {
            if ($constraint->matches(new Constraint('==', $version))) {
                $result[$version] = $prettyVersion;
            }
        }

        return $result;
    }

    /**
     * @return array<string, array<string, string>>
     */
    public function getAllRemovedVersions(): array
    {
        return $this->removedVersions;
    }

    /**
     * @return array<string, string>
     */
    public function getRemovedVersionsByPackage(string $objectHash): array
    {
        if (!isset($this->removedVersionsByPackage[$objectHash])) {
            return [];
        }

        return $this->removedVersionsByPackage[$objectHash];
    }

    /**
     * @return array<string, array<string, string>>
     */
    public function getAllRemovedVersionsByPackage(): array
    {
        return $this->removedVersionsByPackage;
    }

    public function isSecurityRemovedPackageVersion(string $packageName, ?ConstraintInterface $constraint): bool
    {
        foreach ($this->securityRemovedVersions[$packageName] ?? [] as $version => $packageWithSecurityAdvisories) {
            if ($constraint !== null && $constraint->matches(new Constraint('==', $version))) {
                return true;
            }
        }

        return false;
    }

    /**
     * @return string[]
     */
    public function getSecurityAdvisoryIdentifiersForPackageVersion(string $packageName, ?ConstraintInterface $constraint): array
    {
        foreach ($this->securityRemovedVersions[$packageName] ?? [] as $version => $packageWithSecurityAdvisories) {
            if ($constraint !== null && $constraint->matches(new Constraint('==', $version))) {
                return array_map(static function ($advisory) {
                    return $advisory->advisoryId;
                }, $packageWithSecurityAdvisories);
            }
        }

        return [];
    }

    public function isAbandonedRemovedPackageVersion(string $packageName, ?ConstraintInterface $constraint): bool
    {
        foreach ($this->abandonedRemovedVersions[$packageName] ?? [] as $version => $prettyVersion) {
            if ($constraint !== null && $constraint->matches(new Constraint('==', $version))) {
                return true;
            }
        }

        return false;
    }

    /**
     * @return array<string, array<string, array<SecurityAdvisory|PartialSecurityAdvisory>>>
     */
    public function getAllSecurityRemovedPackageVersions(): array
    {
        return $this->securityRemovedVersions;
    }

    /**
     * @return array<string, array<string, string>>
     */
    public function getAllAbandonedRemovedPackageVersions(): array
    {
        return $this->abandonedRemovedVersions;
    }

    /**
     * @param BasePackage[] $packages
     */
    private function setPackages(array $packages): void
    {
        $id = 1;

        foreach ($packages as $package) {
            $this->packages[] = $package;

            $package->id = $id++;

            foreach ($package->getNames() as $provided) {
                $this->packageByName[$provided][] = $package;
            }
        }
    }

    /**
     * @return BasePackage[]
     */
    public function getPackages(): array
    {
        return $this->packages;
    }

    /**
     * Retrieves the package object for a given package id.
     */
    public function packageById(int $id): BasePackage
    {
        return $this->packages[$id - 1];
    }

    /**
     * Returns how many packages have been loaded into the pool
     */
    public function count(): int
    {
        return \count($this->packages);
    }

    /**
     * Searches all packages providing the given package name and match the constraint
     *
     * @param string $name The package name to be searched for
     * @param ?ConstraintInterface $constraint A constraint that all returned
     *                                         packages must match or null to return all
     * @return BasePackage[] A set of packages
     */
    public function whatProvides(string $name, ?ConstraintInterface $constraint = null): array
    {
        $key = (string) $constraint;
        if (isset($this->providerCache[$name][$key])) {
            return $this->providerCache[$name][$key];
        }

        return $this->providerCache[$name][$key] = $this->computeWhatProvides($name, $constraint);
    }

    /**
     * @param  string               $name       The package name to be searched for
     * @param  ?ConstraintInterface $constraint A constraint that all returned
     *                                          packages must match or null to return all
     * @return BasePackage[]
     */
    private function computeWhatProvides(string $name, ?ConstraintInterface $constraint = null): array
    {
        if (!isset($this->packageByName[$name])) {
            return [];
        }

        $matches = [];

        foreach ($this->packageByName[$name] as $candidate) {
            if ($this->match($candidate, $name, $constraint)) {
                $matches[] = $candidate;
            }
        }

        return $matches;
    }

    public function literalToPackage(int $literal): BasePackage
    {
        $packageId = abs($literal);

        return $this->packageById($packageId);
    }

    /**
     * @param array<int, BasePackage> $installedMap
     */
    public function literalToPrettyString(int $literal, array $installedMap): string
    {
        $package = $this->literalToPackage($literal);

        if (isset($installedMap[$package->id])) {
            $prefix = ($literal > 0 ? 'keep' : 'remove');
        } else {
            $prefix = ($literal > 0 ? 'install' : 'don\'t install');
        }

        return $prefix.' '.$package->getPrettyString();
    }

    /**
     * Checks if the package matches the given constraint directly or through
     * provided or replaced packages
     *
     * @param  string              $name       Name of the package to be matched
     */
    public function match(BasePackage $candidate, string $name, ?ConstraintInterface $constraint = null): bool
    {
        $candidateName = $candidate->getName();
        $candidateVersion = $candidate->getVersion();

        if ($candidateName === $name) {
            return $constraint === null || CompilingMatcher::match($constraint, Constraint::OP_EQ, $candidateVersion);
        }

        $provides = $candidate->getProvides();
        $replaces = $candidate->getReplaces();

        // aliases create multiple replaces/provides for one target so they can not use the shortcut below
        if (isset($replaces[0]) || isset($provides[0])) {
            foreach ($provides as $link) {
                if ($link->getTarget() === $name && ($constraint === null || $constraint->matches($link->getConstraint()))) {
                    return true;
                }
            }

            foreach ($replaces as $link) {
                if ($link->getTarget() === $name && ($constraint === null || $constraint->matches($link->getConstraint()))) {
                    return true;
                }
            }

            return false;
        }

        if (isset($provides[$name]) && ($constraint === null || $constraint->matches($provides[$name]->getConstraint()))) {
            return true;
        }

        if (isset($replaces[$name]) && ($constraint === null || $constraint->matches($replaces[$name]->getConstraint()))) {
            return true;
        }

        return false;
    }

    public function isUnacceptableFixedOrLockedPackage(BasePackage $package): bool
    {
        return \in_array($package, $this->unacceptableFixedOrLockedPackages, true);
    }

    /**
     * @return BasePackage[]
     */
    public function getUnacceptableFixedOrLockedPackages(): array
    {
        return $this->unacceptableFixedOrLockedPackages;
    }

    public function __toString(): string
    {
        $str = "Pool:\n";

        foreach ($this->packages as $package) {
            $str .= '- '.str_pad((string) $package->id, 6, ' ', STR_PAD_LEFT).': '.$package->getName()."\n";
        }

        return $str;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Package\AliasPackage;
use Composer\Package\BasePackage;
use Composer\Package\PackageInterface;
use Composer\Semver\CompilingMatcher;
use Composer\Semver\Constraint\Constraint;
use Composer\Util\Platform;

/**
 * @author Nils Adermann <naderman@naderman.de>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class DefaultPolicy implements PolicyInterface
{
    /** @var bool */
    private $preferStable;
    /** @var bool */
    private $preferLowest;
    /** @var bool */
    private $preferDevOverPrerelease;
    /** @var array<string, string>|null */
    private $preferredVersions;
    /** @var array<int, array<string, non-empty-list<int>>> */
    private $preferredPackageResultCachePerPool;
    /** @var array<int, array<string, int>> */
    private $sortingCachePerPool;

    /**
     * @param array<string, string>|null $preferredVersions Must be an array of package name => normalized version
     */
    public function __construct(bool $preferStable = false, bool $preferLowest = false, ?array $preferredVersions = null)
    {
        $this->preferStable = $preferStable;
        $this->preferLowest = $preferLowest;
        $this->preferredVersions = $preferredVersions;
        $this->preferDevOverPrerelease = (bool) Platform::getEnv('COMPOSER_PREFER_DEV_OVER_PRERELEASE');
    }

    /**
     * @param string $operator One of Constraint::STR_OP_*
     *
     * @phpstan-param Constraint::STR_OP_* $operator
     */
    public function versionCompare(PackageInterface $a, PackageInterface $b, string $operator): bool
    {
        if ($this->preferStable && ($stabA = $a->getStability()) !== ($stabB = $b->getStability())) {
            if ($this->preferLowest && $this->preferDevOverPrerelease && 'stable' !== $stabA && 'stable' !== $stabB) {
                // When COMPOSER_PREFER_DEV_OVER_PRERELEASE is set and no stable version has been
                // released, "dev" should be considered more stable than "alpha", "beta" or "RC";
                // this allows testing lowest versions with potential fixes applied
                $stabA = 'dev' === $stabA ? 'stable' : $stabA;
                $stabB = 'dev' === $stabB ? 'stable' : $stabB;
            }

            return BasePackage::STABILITIES[$stabA] < BasePackage::STABILITIES[$stabB];
        }

        // dev versions need to be compared as branches via matchSpecific's special treatment, the rest can be optimized with compiling matcher
        if (($a->isDev() && str_starts_with($a->getVersion(), 'dev-')) || ($b->isDev() && str_starts_with($b->getVersion(), 'dev-'))) {
            $constraint = new Constraint($operator, $b->getVersion());
            $version = new Constraint('==', $a->getVersion());

            return $constraint->matchSpecific($version, true);
        }

        return CompilingMatcher::match(new Constraint($operator, $b->getVersion()), Constraint::OP_EQ, $a->getVersion());
    }

    /**
     * @param  non-empty-list<int>  $literals
     * @return non-empty-list<int>
     */
    public function selectPreferredPackages(Pool $pool, array $literals, ?string $requiredPackage = null): array
    {
        sort($literals);
        $resultCacheKey = implode(',', $literals).$requiredPackage;
        $poolId = spl_object_id($pool);

        if (isset($this->preferredPackageResultCachePerPool[$poolId][$resultCacheKey])) {
            return $this->preferredPackageResultCachePerPool[$poolId][$resultCacheKey];
        }

        $packages = $this->groupLiteralsByName($pool, $literals);

        foreach ($packages as &$nameLiterals) {
            usort($nameLiterals, function ($a, $b) use ($pool, $requiredPackage, $poolId): int {
                $cacheKey = 'i'.$a.'.'.$b.$requiredPackage; // i prefix -> ignoreReplace = true

                if (isset($this->sortingCachePerPool[$poolId][$cacheKey])) {
                    return $this->sortingCachePerPool[$poolId][$cacheKey];
                }

                return $this->sortingCachePerPool[$poolId][$cacheKey] = $this->compareByPriority($pool, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage, true);
            });
        }

        foreach ($packages as &$sortedLiterals) {
            $sortedLiterals = $this->pruneToBestVersion($pool, $sortedLiterals);
            $sortedLiterals = $this->pruneRemoteAliases($pool, $sortedLiterals);
        }

        $selected = array_merge(...array_values($packages));

        // now sort the result across all packages to respect replaces across packages
        usort($selected, function ($a, $b) use ($pool, $requiredPackage, $poolId): int {
            $cacheKey = $a.'.'.$b.$requiredPackage; // no i prefix -> ignoreReplace = false

            if (isset($this->sortingCachePerPool[$poolId][$cacheKey])) {
                return $this->sortingCachePerPool[$poolId][$cacheKey];
            }

            return $this->sortingCachePerPool[$poolId][$cacheKey] = $this->compareByPriority($pool, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage);
        });

        return $this->preferredPackageResultCachePerPool[$poolId][$resultCacheKey] = $selected;
    }

    /**
     * @param  non-empty-list<int> $literals
     * @return non-empty-array<string, non-empty-list<int>>
     */
    protected function groupLiteralsByName(Pool $pool, array $literals): array
    {
        $packages = [];
        foreach ($literals as $literal) {
            $packageName = $pool->literalToPackage($literal)->getName();

            if (!isset($packages[$packageName])) {
                $packages[$packageName] = [];
            }
            $packages[$packageName][] = $literal;
        }

        return $packages;
    }

    /**
     * @protected
     */
    public function compareByPriority(Pool $pool, BasePackage $a, BasePackage $b, ?string $requiredPackage = null, bool $ignoreReplace = false): int
    {
        // prefer aliases to the original package
        if ($a->getName() === $b->getName()) {
            $aAliased = $a instanceof AliasPackage;
            $bAliased = $b instanceof AliasPackage;
            if ($aAliased && !$bAliased) {
                return -1; // use a
            }
            if (!$aAliased && $bAliased) {
                return 1; // use b
            }
        }

        if (!$ignoreReplace) {
            // return original, not replaced
            if ($this->replaces($a, $b)) {
                return 1; // use b
            }
            if ($this->replaces($b, $a)) {
                return -1; // use a
            }

            // for replacers not replacing each other, put a higher prio on replacing
            // packages with the same vendor as the required package
            if ($requiredPackage !== null && false !== ($pos = strpos($requiredPackage, '/'))) {
                $requiredVendor = substr($requiredPackage, 0, $pos);

                $aIsSameVendor = strpos($a->getName(), $requiredVendor) === 0;
                $bIsSameVendor = strpos($b->getName(), $requiredVendor) === 0;

                if ($bIsSameVendor !== $aIsSameVendor) {
                    return $aIsSameVendor ? -1 : 1;
                }
            }
        }

        // priority equal, sort by package id to make reproducible
        if ($a->id === $b->id) {
            return 0;
        }

        return ($a->id < $b->id) ? -1 : 1;
    }

    /**
     * Checks if source replaces a package with the same name as target.
     *
     * Replace constraints are ignored. This method should only be used for
     * prioritisation, not for actual constraint verification.
     */
    protected function replaces(BasePackage $source, BasePackage $target): bool
    {
        foreach ($source->getReplaces() as $link) {
            if ($link->getTarget() === $target->getName()
                // && (null === $link->getConstraint() ||
                // $link->getConstraint()->matches(new Constraint('==', $target->getVersion())))) {
            ) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param  list<int> $literals
     * @return list<int>
     */
    protected function pruneToBestVersion(Pool $pool, array $literals): array
    {
        if ($this->preferredVersions !== null) {
            $name = $pool->literalToPackage($literals[0])->getName();
            if (isset($this->preferredVersions[$name])) {
                $preferredVersion = $this->preferredVersions[$name];
                $bestLiterals = [];
                foreach ($literals as $literal) {
                    if ($pool->literalToPackage($literal)->getVersion() === $preferredVersion) {
                        $bestLiterals[] = $literal;
                    }
                }
                if (\count($bestLiterals) > 0) {
                    return $bestLiterals;
                }
            }
        }

        $operator = $this->preferLowest ? '<' : '>';
        $bestLiterals = [$literals[0]];
        $bestPackage = $pool->literalToPackage($literals[0]);
        foreach ($literals as $i => $literal) {
            if (0 === $i) {
                continue;
            }

            $package = $pool->literalToPackage($literal);

            if ($this->versionCompare($package, $bestPackage, $operator)) {
                $bestPackage = $package;
                $bestLiterals = [$literal];
            } elseif ($this->versionCompare($package, $bestPackage, '==')) {
                $bestLiterals[] = $literal;
            }
        }

        return $bestLiterals;
    }

    /**
     * Assumes that locally aliased (in root package requires) packages take priority over branch-alias ones
     *
     * If no package is a local alias, nothing happens
     *
     * @param  list<int> $literals
     * @return list<int>
     */
    protected function pruneRemoteAliases(Pool $pool, array $literals): array
    {
        $hasLocalAlias = false;

        foreach ($literals as $literal) {
            $package = $pool->literalToPackage($literal);

            if ($package instanceof AliasPackage && $package->isRootPackageAlias()) {
                $hasLocalAlias = true;
                break;
            }
        }

        if (!$hasLocalAlias) {
            return $literals;
        }

        $selected = [];
        foreach ($literals as $literal) {
            $package = $pool->literalToPackage($literal);

            if ($package instanceof AliasPackage && $package->isRootPackageAlias()) {
                $selected[] = $literal;
            }
        }

        return $selected;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Repository\InstalledRepositoryInterface;
use Composer\Repository\RepositoryInterface;

/**
 * @author Nils Adermann <naderman@naderman.de>
 * @internal
 */
class LocalRepoTransaction extends Transaction
{
    public function __construct(RepositoryInterface $lockedRepository, InstalledRepositoryInterface $localRepository)
    {
        parent::__construct(
            $localRepository->getPackages(),
            $lockedRepository->getPackages()
        );
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Package\AliasPackage;
use Composer\Package\BasePackage;
use Composer\Package\Version\VersionParser;
use Composer\Semver\CompilingMatcher;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\MultiConstraint;
use Composer\Semver\Intervals;

/**
 * Optimizes a given pool
 *
 * @author Yanick Witschi <yanick.witschi@terminal42.ch>
 */
class PoolOptimizer
{
    /**
     * @var PolicyInterface
     */
    private $policy;

    /**
     * @var array<int, true>
     */
    private $irremovablePackages = [];

    /**
     * @var array<string, array<string, ConstraintInterface>>
     */
    private $requireConstraintsPerPackage = [];

    /**
     * @var array<string, array<string, ConstraintInterface>>
     */
    private $conflictConstraintsPerPackage = [];

    /**
     * @var array<int, true>
     */
    private $packagesToRemove = [];

    /**
     * @var array<int, BasePackage[]>
     */
    private $aliasesPerPackage = [];

    /**
     * @var array<string, array<string, string>>
     */
    private $removedVersionsByPackage = [];

    public function __construct(PolicyInterface $policy)
    {
        $this->policy = $policy;
    }

    public function optimize(Request $request, Pool $pool): Pool
    {
        $this->prepare($request, $pool);

        $this->optimizeByIdenticalDependencies($request, $pool);

        $this->optimizeImpossiblePackagesAway($request, $pool);

        $optimizedPool = $this->applyRemovalsToPool($pool);

        // No need to run this recursively at the moment
        // because the current optimizations cannot provide
        // even more gains when ran again. Might change
        // in the future with additional optimizations.

        $this->irremovablePackages = [];
        $this->requireConstraintsPerPackage = [];
        $this->conflictConstraintsPerPackage = [];
        $this->packagesToRemove = [];
        $this->aliasesPerPackage = [];
        $this->removedVersionsByPackage = [];

        return $optimizedPool;
    }

    private function prepare(Request $request, Pool $pool): void
    {
        $irremovablePackageConstraintGroups = [];

        // Mark fixed or locked packages as irremovable
        foreach ($request->getFixedOrLockedPackages() as $package) {
            $irremovablePackageConstraintGroups[$package->getName()][] = new Constraint('==', $package->getVersion());
        }

        // Extract requested package requirements
        foreach ($request->getRequires() as $require => $constraint) {
            $this->extractRequireConstraintsPerPackage($require, $constraint);
        }

        // First pass over all packages to extract information and mark package constraints irremovable
        foreach ($pool->getPackages() as $package) {
            // Extract package requirements
            foreach ($package->getRequires() as $link) {
                $this->extractRequireConstraintsPerPackage($link->getTarget(), $link->getConstraint());
            }
            // Extract package conflicts
            foreach ($package->getConflicts() as $link) {
                $this->extractConflictConstraintsPerPackage($link->getTarget(), $link->getConstraint());
            }

            // Keep track of alias packages for every package so if either the alias or aliased is kept
            // we keep the others as they are a unit of packages really
            if ($package instanceof AliasPackage) {
                $this->aliasesPerPackage[$package->getAliasOf()->id][] = $package;
            }
        }

        $irremovablePackageConstraints = [];
        foreach ($irremovablePackageConstraintGroups as $packageName => $constraints) {
            $irremovablePackageConstraints[$packageName] = 1 === \count($constraints) ? $constraints[0] : new MultiConstraint($constraints, false);
        }
        unset($irremovablePackageConstraintGroups);

        // Mark the packages as irremovable based on the constraints
        foreach ($pool->getPackages() as $package) {
            if (!isset($irremovablePackageConstraints[$package->getName()])) {
                continue;
            }

            if (CompilingMatcher::match($irremovablePackageConstraints[$package->getName()], Constraint::OP_EQ, $package->getVersion())) {
                $this->markPackageIrremovable($package);
            }
        }
    }

    private function markPackageIrremovable(BasePackage $package): void
    {
        $this->irremovablePackages[$package->id] = true;
        if ($package instanceof AliasPackage) {
            // recursing here so aliasesPerPackage for the aliasOf can be checked
            // and all its aliases marked as irremovable as well
            $this->markPackageIrremovable($package->getAliasOf());
        }
        if (isset($this->aliasesPerPackage[$package->id])) {
            foreach ($this->aliasesPerPackage[$package->id] as $aliasPackage) {
                $this->irremovablePackages[$aliasPackage->id] = true;
            }
        }
    }

    /**
     * @return Pool Optimized pool
     */
    private function applyRemovalsToPool(Pool $pool): Pool
    {
        $packages = [];
        $removedVersions = [];
        foreach ($pool->getPackages() as $package) {
            if (!isset($this->packagesToRemove[$package->id])) {
                $packages[] = $package;
            } else {
                $removedVersions[$package->getName()][$package->getVersion()] = $package->getPrettyVersion();
            }
        }

        $optimizedPool = new Pool($packages, $pool->getUnacceptableFixedOrLockedPackages(), $removedVersions, $this->removedVersionsByPackage, $pool->getAllSecurityRemovedPackageVersions(), $pool->getAllAbandonedRemovedPackageVersions());

        return $optimizedPool;
    }

    private function optimizeByIdenticalDependencies(Request $request, Pool $pool): void
    {
        $identicalDefinitionsPerPackage = [];
        $packageIdenticalDefinitionLookup = [];

        foreach ($pool->getPackages() as $package) {

            // If that package was already marked irremovable, we can skip
            // the entire process for it
            if (isset($this->irremovablePackages[$package->id])) {
                continue;
            }

            $this->markPackageForRemoval($package->id);

            $dependencyHash = $this->calculateDependencyHash($package);

            foreach ($package->getNames(false) as $packageName) {
                if (!isset($this->requireConstraintsPerPackage[$packageName])) {
                    continue;
                }

                foreach ($this->requireConstraintsPerPackage[$packageName] as $requireConstraint) {
                    $groupHashParts = [];

                    if (CompilingMatcher::match($requireConstraint, Constraint::OP_EQ, $package->getVersion())) {
                        $groupHashParts[] = 'require:' . (string) $requireConstraint;
                    }

                    if (\count($package->getReplaces()) > 0) {
                        foreach ($package->getReplaces() as $link) {
                            if (CompilingMatcher::match($link->getConstraint(), Constraint::OP_EQ, $package->getVersion())) {
                                // Use the same hash part as the regular require hash because that's what the replacement does
                                $groupHashParts[] = 'require:' . (string) $link->getConstraint();
                            }
                        }
                    }

                    if (isset($this->conflictConstraintsPerPackage[$packageName])) {
                        foreach ($this->conflictConstraintsPerPackage[$packageName] as $conflictConstraint) {
                            if (CompilingMatcher::match($conflictConstraint, Constraint::OP_EQ, $package->getVersion())) {
                                $groupHashParts[] = 'conflict:' . (string) $conflictConstraint;
                            }
                        }
                    }

                    if (0 === \count($groupHashParts)) {
                        continue;
                    }

                    $groupHash = implode('', $groupHashParts);
                    $identicalDefinitionsPerPackage[$packageName][$groupHash][$dependencyHash][] = $package;
                    $packageIdenticalDefinitionLookup[$package->id][$packageName] = ['groupHash' => $groupHash, 'dependencyHash' => $dependencyHash];
                }
            }
        }

        foreach ($identicalDefinitionsPerPackage as $constraintGroups) {
            foreach ($constraintGroups as $constraintGroup) {
                foreach ($constraintGroup as $packages) {
                    // Only one package in this constraint group has the same requirements, we're not allowed to remove that package
                    if (1 === \count($packages)) {
                        $this->keepPackage($packages[0], $identicalDefinitionsPerPackage, $packageIdenticalDefinitionLookup);
                        continue;
                    }

                    // Otherwise we find out which one is the preferred package in this constraint group which is
                    // then not allowed to be removed either
                    $literals = [];

                    foreach ($packages as $package) {
                        $literals[] = $package->id;
                    }

                    foreach ($this->policy->selectPreferredPackages($pool, $literals) as $preferredLiteral) {
                        $this->keepPackage($pool->literalToPackage($preferredLiteral), $identicalDefinitionsPerPackage, $packageIdenticalDefinitionLookup);
                    }
                }
            }
        }
    }

    private function calculateDependencyHash(BasePackage $package): string
    {
        $hash = '';

        $hashRelevantLinks = [
            'requires' => $package->getRequires(),
            'conflicts' => $package->getConflicts(),
            'replaces' => $package->getReplaces(),
            'provides' => $package->getProvides(),
        ];

        foreach ($hashRelevantLinks as $key => $links) {
            if (0 === \count($links)) {
                continue;
            }

            // start new hash section
            $hash .= $key . ':';

            $subhash = [];

            foreach ($links as $link) {
                // To get the best dependency hash matches we should use Intervals::compactConstraint() here.
                // However, the majority of projects are going to specify their constraints already pretty
                // much in the best variant possible. In other words, we'd be wasting time here and it would actually hurt
                // performance more than the additional few packages that could be filtered out would benefit the process.
                $subhash[$link->getTarget()] = (string) $link->getConstraint();
            }

            // Sort for best result
            ksort($subhash);

            foreach ($subhash as $target => $constraint) {
                $hash .= $target . '@' . $constraint;
            }
        }

        return $hash;
    }

    private function markPackageForRemoval(int $id): void
    {
        // We are not allowed to remove packages if they have been marked as irremovable
        if (isset($this->irremovablePackages[$id])) {
            throw new \LogicException('Attempted removing a package which was previously marked irremovable');
        }

        $this->packagesToRemove[$id] = true;
    }

    /**
     * @param array<string, array<string, array<string, list<BasePackage>>>> $identicalDefinitionsPerPackage
     * @param array<int, array<string, array{groupHash: string, dependencyHash: string}>> $packageIdenticalDefinitionLookup
     */
    private function keepPackage(BasePackage $package, array $identicalDefinitionsPerPackage, array $packageIdenticalDefinitionLookup): void
    {
        // Already marked to keep
        if (!isset($this->packagesToRemove[$package->id])) {
            return;
        }

        unset($this->packagesToRemove[$package->id]);

        if ($package instanceof AliasPackage) {
            // recursing here so aliasesPerPackage for the aliasOf can be checked
            // and all its aliases marked to be kept as well
            $this->keepPackage($package->getAliasOf(), $identicalDefinitionsPerPackage, $packageIdenticalDefinitionLookup);
        }

        // record all the versions of the package group so we can list them later in Problem output
        foreach ($package->getNames(false) as $name) {
            if (isset($packageIdenticalDefinitionLookup[$package->id][$name])) {
                $packageGroupPointers = $packageIdenticalDefinitionLookup[$package->id][$name];
                $packageGroup = $identicalDefinitionsPerPackage[$name][$packageGroupPointers['groupHash']][$packageGroupPointers['dependencyHash']];
                foreach ($packageGroup as $pkg) {
                    if ($pkg instanceof AliasPackage && $pkg->getPrettyVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) {
                        $pkg = $pkg->getAliasOf();
                    }
                    $this->removedVersionsByPackage[spl_object_hash($package)][$pkg->getVersion()] = $pkg->getPrettyVersion();
                }
            }
        }

        if (isset($this->aliasesPerPackage[$package->id])) {
            foreach ($this->aliasesPerPackage[$package->id] as $aliasPackage) {
                unset($this->packagesToRemove[$aliasPackage->id]);

                // record all the versions of the package group so we can list them later in Problem output
                foreach ($aliasPackage->getNames(false) as $name) {
                    if (isset($packageIdenticalDefinitionLookup[$aliasPackage->id][$name])) {
                        $packageGroupPointers = $packageIdenticalDefinitionLookup[$aliasPackage->id][$name];
                        $packageGroup = $identicalDefinitionsPerPackage[$name][$packageGroupPointers['groupHash']][$packageGroupPointers['dependencyHash']];
                        foreach ($packageGroup as $pkg) {
                            if ($pkg instanceof AliasPackage && $pkg->getPrettyVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) {
                                $pkg = $pkg->getAliasOf();
                            }
                            $this->removedVersionsByPackage[spl_object_hash($aliasPackage)][$pkg->getVersion()] = $pkg->getPrettyVersion();
                        }
                    }
                }
            }
        }
    }

    /**
     * Use the list of locked packages to constrain the loaded packages
     * This will reduce packages with significant numbers of historical versions to a smaller number
     * and reduce the resulting rule set that is generated
     */
    private function optimizeImpossiblePackagesAway(Request $request, Pool $pool): void
    {
        if (\count($request->getLockedPackages()) === 0) {
            return;
        }

        $packageIndex = [];

        foreach ($pool->getPackages() as $package) {
            $id = $package->id;

            // Do not remove irremovable packages
            if (isset($this->irremovablePackages[$id])) {
                continue;
            }
            // Do not remove a package aliased by another package, nor aliases
            if (isset($this->aliasesPerPackage[$id]) || $package instanceof AliasPackage) {
                continue;
            }
            // Do not remove locked packages
            if ($request->isFixedPackage($package) || $request->isLockedPackage($package)) {
                continue;
            }

            $packageIndex[$package->getName()][$package->id] = $package;
        }

        foreach ($request->getLockedPackages() as $package) {
            // If this locked package is no longer required by root or anything in the pool, it may get uninstalled so do not apply its requirements
            // In a case where a requirement WERE to appear in the pool by a package that would not be used, it would've been unlocked and so not filtered still
            $isUnusedPackage = true;
            foreach ($package->getNames(false) as $packageName) {
                if (isset($this->requireConstraintsPerPackage[$packageName])) {
                    $isUnusedPackage = false;
                    break;
                }
            }

            if ($isUnusedPackage) {
                continue;
            }

            foreach ($package->getRequires() as $link) {
                $require = $link->getTarget();
                if (!isset($packageIndex[$require])) {
                    continue;
                }

                $linkConstraint = $link->getConstraint();
                foreach ($packageIndex[$require] as $id => $requiredPkg) {
                    if (false === CompilingMatcher::match($linkConstraint, Constraint::OP_EQ, $requiredPkg->getVersion())) {
                        $this->markPackageForRemoval($id);
                        unset($packageIndex[$require][$id]);
                    }
                }
            }
        }
    }

    /**
     * Disjunctive require constraints need to be considered in their own group. E.g. "^2.14 || ^3.3" needs to generate
     * two require constraint groups in order for us to keep the best matching package for "^2.14" AND "^3.3" as otherwise, we'd
     * only keep either one which can cause trouble (e.g. when using --prefer-lowest).
     *
     * @return void
     */
    private function extractRequireConstraintsPerPackage(string $package, ConstraintInterface $constraint)
    {
        foreach ($this->expandDisjunctiveMultiConstraints($constraint) as $expanded) {
            $this->requireConstraintsPerPackage[$package][(string) $expanded] = $expanded;
        }
    }

    /**
     * Disjunctive conflict constraints need to be considered in their own group. E.g. "^2.14 || ^3.3" needs to generate
     * two conflict constraint groups in order for us to keep the best matching package for "^2.14" AND "^3.3" as otherwise, we'd
     * only keep either one which can cause trouble (e.g. when using --prefer-lowest).
     *
     * @return void
     */
    private function extractConflictConstraintsPerPackage(string $package, ConstraintInterface $constraint)
    {
        foreach ($this->expandDisjunctiveMultiConstraints($constraint) as $expanded) {
            $this->conflictConstraintsPerPackage[$package][(string) $expanded] = $expanded;
        }
    }

    /**
     * @return ConstraintInterface[]
     */
    private function expandDisjunctiveMultiConstraints(ConstraintInterface $constraint)
    {
        $constraint = Intervals::compactConstraint($constraint);

        if ($constraint instanceof MultiConstraint && $constraint->isDisjunctive()) {
            // No need to call ourselves recursively here because Intervals::compactConstraint() ensures that there
            // are no nested disjunctive MultiConstraint instances possible
            return $constraint->getConstraints();
        }

        // Regular constraints and conjunctive MultiConstraints
        return [$constraint];
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Package\AliasPackage;
use Composer\Package\BasePackage;
use Composer\Package\Package;
use Composer\Pcre\Preg;

/**
 * @author Nils Adermann <naderman@naderman.de>
 * @internal
 */
class LockTransaction extends Transaction
{
    /**
     * packages in current lock file, platform repo or otherwise present
     *
     * Indexed by spl_object_hash
     *
     * @var array<string, BasePackage>
     */
    protected $presentMap;

    /**
     * Packages which cannot be mapped, platform repo, root package, other fixed repos
     *
     * Indexed by package id
     *
     * @var array<int, BasePackage>
     */
    protected $unlockableMap;

    /**
     * @var array{dev: BasePackage[], non-dev: BasePackage[], all: BasePackage[]}
     */
    protected $resultPackages;

    /**
     * @param array<string, BasePackage> $presentMap
     * @param array<int, BasePackage> $unlockableMap
     */
    public function __construct(Pool $pool, array $presentMap, array $unlockableMap, Decisions $decisions)
    {
        $this->presentMap = $presentMap;
        $this->unlockableMap = $unlockableMap;

        $this->setResultPackages($pool, $decisions);
        parent::__construct($this->presentMap, $this->resultPackages['all']);
    }

    // TODO make this a bit prettier instead of the two text indexes?

    public function setResultPackages(Pool $pool, Decisions $decisions): void
    {
        $this->resultPackages = ['all' => [], 'non-dev' => [], 'dev' => []];
        foreach ($decisions as $i => $decision) {
            $literal = $decision[Decisions::DECISION_LITERAL];

            if ($literal > 0) {
                $package = $pool->literalToPackage($literal);

                $this->resultPackages['all'][] = $package;
                if (!isset($this->unlockableMap[$package->id])) {
                    $this->resultPackages['non-dev'][] = $package;
                }
            }
        }
    }

    public function setNonDevPackages(LockTransaction $extractionResult): void
    {
        $packages = $extractionResult->getNewLockPackages(false);

        $this->resultPackages['dev'] = $this->resultPackages['non-dev'];
        $this->resultPackages['non-dev'] = [];

        foreach ($packages as $package) {
            foreach ($this->resultPackages['dev'] as $i => $resultPackage) {
                // TODO this comparison is probably insufficient, aliases, what about modified versions? I guess they aren't possible?
                if ($package->getName() === $resultPackage->getName()) {
                    $this->resultPackages['non-dev'][] = $resultPackage;
                    unset($this->resultPackages['dev'][$i]);
                }
            }
        }
    }

    // TODO additionalFixedRepository needs to be looked at here as well?
    /**
     * @return BasePackage[]
     */
    public function getNewLockPackages(bool $devMode, bool $updateMirrors = false): array
    {
        $packages = [];
        foreach ($this->resultPackages[$devMode ? 'dev' : 'non-dev'] as $package) {
            if ($package instanceof AliasPackage) {
                continue;
            }

            // if we're just updating mirrors we need to reset everything to the same as currently "present" packages' references to keep the lock file as-is
            if ($updateMirrors === true && !array_key_exists(spl_object_hash($package), $this->presentMap)) {
                $package = $this->updateMirrorAndUrls($package);
            }

            $packages[] = $package;
        }

        return $packages;
    }

    /**
     * Try to return the original package from presentMap with updated URLs/mirrors
     *
     * If the type of source/dist changed, then we do not update those and keep them as they were
     */
    private function updateMirrorAndUrls(BasePackage $package): BasePackage
    {
        foreach ($this->presentMap as $presentPackage) {
            if ($package->getName() !== $presentPackage->getName()) {
                continue;
            }

            if ($package->getVersion() !== $presentPackage->getVersion()) {
                continue;
            }

            if ($presentPackage->getSourceReference() === null) {
                continue;
            }

            if ($presentPackage->getSourceType() !== $package->getSourceType()) {
                continue;
            }

            if ($presentPackage instanceof Package) {
                $presentPackage->setSourceUrl($package->getSourceUrl());
                $presentPackage->setSourceMirrors($package->getSourceMirrors());
            }

            // if the dist type changed, we only update the source url/mirrors
            if ($presentPackage->getDistType() !== $package->getDistType()) {
                return $presentPackage;
            }

            // update dist url if it is in a known format
            if (
                $package->getDistUrl() !== null
                && $presentPackage->getDistReference() !== null
                && Preg::isMatch('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com|(?:www\.)?gitlab\.com)/}i', $package->getDistUrl())
            ) {
                $presentPackage->setDistUrl(Preg::replace('{(?<=/|sha=)[a-f0-9]{40}(?=/|$)}i', $presentPackage->getDistReference(), $package->getDistUrl()));
            }
            $presentPackage->setDistMirrors($package->getDistMirrors());

            return $presentPackage;
        }

        return $package;
    }

    /**
     * Checks which of the given aliases from composer.json are actually in use for the lock file
     * @param list<array{package: string, version: string, alias: string, alias_normalized: string}> $aliases
     * @return list<array{package: string, version: string, alias: string, alias_normalized: string}>
     */
    public function getAliases(array $aliases): array
    {
        $usedAliases = [];

        foreach ($this->resultPackages['all'] as $package) {
            if ($package instanceof AliasPackage) {
                foreach ($aliases as $index => $alias) {
                    if ($alias['package'] === $package->getName()) {
                        $usedAliases[] = $alias;
                        unset($aliases[$index]);
                    }
                }
            }
        }

        usort($usedAliases, static function ($a, $b): int {
            return strcmp($a['package'], $b['package']);
        });

        return $usedAliases;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Util\IniHelper;
use Composer\Repository\RepositorySet;

/**
 * @author Nils Adermann <naderman@naderman.de>
 *
 * @method self::ERROR_DEPENDENCY_RESOLUTION_FAILED getCode()
 */
class SolverProblemsException extends \RuntimeException
{
    public const ERROR_DEPENDENCY_RESOLUTION_FAILED = 2;

    /** @var Problem[] */
    protected $problems;
    /** @var array<Rule[]> */
    protected $learnedPool;

    /**
     * @param Problem[] $problems
     * @param array<Rule[]> $learnedPool
     */
    public function __construct(array $problems, array $learnedPool)
    {
        $this->problems = $problems;
        $this->learnedPool = $learnedPool;

        parent::__construct('Failed resolving dependencies with '.\count($problems).' problems, call getPrettyString to get formatted details', self::ERROR_DEPENDENCY_RESOLUTION_FAILED);
    }

    public function getPrettyString(RepositorySet $repositorySet, Request $request, Pool $pool, bool $isVerbose, bool $isDevExtraction = false): string
    {
        $installedMap = $request->getPresentMap(true);
        $missingExtensions = [];
        $isCausedByLock = false;

        $problems = [];
        foreach ($this->problems as $problem) {
            $problems[] = $problem->getPrettyString($repositorySet, $request, $pool, $isVerbose, $installedMap, $this->learnedPool)."\n";

            $missingExtensions = array_merge($missingExtensions, $this->getExtensionProblems($problem->getReasons()));

            $isCausedByLock = $isCausedByLock || $problem->isCausedByLock($repositorySet, $request, $pool);
        }

        $i = 1;
        $text = "\n";
        foreach (array_unique($problems) as $problem) {
            $text .= "  Problem ".($i++).$problem;
        }

        $hints = [];
        if (!$isDevExtraction && (str_contains($text, 'could not be found') || str_contains($text, 'no matching package found'))) {
            $hints[] = "Potential causes:\n - A typo in the package name\n - The package is not available in a stable-enough version according to your minimum-stability setting\n   see <https://getcomposer.org/doc/04-schema.md#minimum-stability> for more details.\n - It's a private package and you forgot to add a custom repository to find it\n\nRead <https://getcomposer.org/doc/articles/troubleshooting.md> for further common problems.";
        }

        if (\count($missingExtensions) > 0) {
            $hints[] = $this->createExtensionHint($missingExtensions);
        }

        if ($isCausedByLock && !$isDevExtraction && !$request->getUpdateAllowTransitiveRootDependencies()) {
            $hints[] = "Use the option --with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions.";
        }

        if (str_contains($text, 'found composer-plugin-api[2.0.0] but it does not match') && str_contains($text, '- ocramius/package-versions')) {
            $hints[] = "<warning>ocramius/package-versions only provides support for Composer 2 in 1.8+, which requires PHP 7.4.</warning>\nIf you can not upgrade PHP you can require <info>composer/package-versions-deprecated</info> to resolve this with PHP 7.0+.";
        }

        if (!class_exists('PHPUnit\Framework\TestCase', false)) {
            if (str_contains($text, 'found composer-plugin-api[2.0.0] but it does not match')) {
                $hints[] = "You are using Composer 2, which some of your plugins seem to be incompatible with. Make sure you update your plugins or report a plugin-issue to ask them to support Composer 2.";
            }
        }

        if (\count($hints) > 0) {
            $text .= "\n" . implode("\n\n", $hints);
        }

        return $text;
    }

    /**
     * @return Problem[]
     */
    public function getProblems(): array
    {
        return $this->problems;
    }

    /**
     * @param string[] $missingExtensions
     */
    private function createExtensionHint(array $missingExtensions): string
    {
        $paths = IniHelper::getAll();

        if ('' === $paths[0]) {
            if (count($paths) === 1) {
                return '';
            }

            array_shift($paths);
        }

        $ignoreExtensionsArguments = implode(" ", array_map(static function ($extension) {
            return "--ignore-platform-req=$extension";
        }, array_unique($missingExtensions)));

        $text = "To enable extensions, verify that they are enabled in your .ini files:\n    - ";
        $text .= implode("\n    - ", $paths);
        $text .= "\nYou can also run `php --ini` in a terminal to see which files are used by PHP in CLI mode.";
        $text .= "\nAlternatively, you can run Composer with `$ignoreExtensionsArguments` to temporarily ignore these required extensions.";

        return $text;
    }

    /**
     * @param Rule[][] $reasonSets
     * @return string[]
     */
    private function getExtensionProblems(array $reasonSets): array
    {
        $missingExtensions = [];
        foreach ($reasonSets as $reasonSet) {
            foreach ($reasonSet as $rule) {
                $required = $rule->getRequiredPackage();
                if (null !== $required && 0 === strpos($required, 'ext-')) {
                    $missingExtensions[$required] = 1;
                }
            }
        }

        return array_keys($missingExtensions);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Repository\RepositorySet;

/**
 * @author Nils Adermann <naderman@naderman.de>
 * @implements \IteratorAggregate<Rule>
 * @internal
 * @final
 */
class RuleSet implements \IteratorAggregate, \Countable
{
    // highest priority => lowest number
    public const TYPE_PACKAGE = 0;
    public const TYPE_REQUEST = 1;
    public const TYPE_LEARNED = 4;

    /**
     * READ-ONLY: Lookup table for rule id to rule object
     *
     * @var array<int, Rule>
     */
    public $ruleById = [];

    public const TYPES = [
        self::TYPE_PACKAGE => 'PACKAGE',
        self::TYPE_REQUEST => 'REQUEST',
        self::TYPE_LEARNED => 'LEARNED',
    ];

    /** @var array<self::TYPE_*, Rule[]> */
    protected $rules;

    /** @var 0|positive-int */
    protected $nextRuleId = 0;

    /** @var array<int|string, Rule|Rule[]> */
    protected $rulesByHash = [];

    public function __construct()
    {
        foreach ($this->getTypes() as $type) {
            $this->rules[$type] = [];
        }
    }

    /**
     * @param self::TYPE_* $type
     */
    public function add(Rule $rule, $type): void
    {
        if (!isset(self::TYPES[$type])) {
            throw new \OutOfBoundsException('Unknown rule type: ' . $type);
        }

        $hash = $rule->getHash();

        // Do not add if rule already exists
        if (isset($this->rulesByHash[$hash])) {
            $potentialDuplicates = $this->rulesByHash[$hash];
            if (\is_array($potentialDuplicates)) {
                foreach ($potentialDuplicates as $potentialDuplicate) {
                    if ($rule->equals($potentialDuplicate)) {
                        return;
                    }
                }
            } else {
                if ($rule->equals($potentialDuplicates)) {
                    return;
                }
            }
        }

        if (!isset($this->rules[$type])) {
            $this->rules[$type] = [];
        }

        $this->rules[$type][] = $rule;
        $this->ruleById[$this->nextRuleId] = $rule;
        $rule->setType($type);

        $this->nextRuleId++;

        if (!isset($this->rulesByHash[$hash])) {
            $this->rulesByHash[$hash] = $rule;
        } elseif (\is_array($this->rulesByHash[$hash])) {
            $this->rulesByHash[$hash][] = $rule;
        } else {
            $originalRule = $this->rulesByHash[$hash];
            $this->rulesByHash[$hash] = [$originalRule, $rule];
        }
    }

    public function count(): int
    {
        return $this->nextRuleId;
    }

    public function ruleById(int $id): Rule
    {
        return $this->ruleById[$id];
    }

    /** @return array<self::TYPE_*, Rule[]> */
    public function getRules(): array
    {
        return $this->rules;
    }

    public function getIterator(): RuleSetIterator
    {
        return new RuleSetIterator($this->getRules());
    }

    /**
     * @param  self::TYPE_*|array<self::TYPE_*> $types
     */
    public function getIteratorFor($types): RuleSetIterator
    {
        if (!\is_array($types)) {
            $types = [$types];
        }

        $allRules = $this->getRules();

        /** @var array<self::TYPE_*, Rule[]> $rules */
        $rules = [];

        foreach ($types as $type) {
            $rules[$type] = $allRules[$type];
        }

        return new RuleSetIterator($rules);
    }

    /**
     * @param array<self::TYPE_*>|self::TYPE_* $types
     */
    public function getIteratorWithout($types): RuleSetIterator
    {
        if (!\is_array($types)) {
            $types = [$types];
        }

        $rules = $this->getRules();

        foreach ($types as $type) {
            unset($rules[$type]);
        }

        return new RuleSetIterator($rules);
    }

    /**
     * @return array{self::TYPE_PACKAGE, self::TYPE_REQUEST, self::TYPE_LEARNED}
     */
    public function getTypes(): array
    {
        $types = self::TYPES;

        return array_keys($types);
    }

    public function getPrettyString(?RepositorySet $repositorySet = null, ?Request $request = null, ?Pool $pool = null, bool $isVerbose = false): string
    {
        $string = "\n";
        foreach ($this->rules as $type => $rules) {
            $string .= str_pad(self::TYPES[$type], 8, ' ') . ": ";
            foreach ($rules as $rule) {
                $string .= ($repositorySet !== null && $request !== null && $pool !== null ? $rule->getPrettyString($repositorySet, $request, $pool, $isVerbose) : $rule)."\n";
            }
            $string .= "\n\n";
        }

        return $string;
    }

    public function __toString(): string
    {
        return $this->getPrettyString();
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Package\PackageInterface;
use Composer\Semver\Constraint\Constraint;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
interface PolicyInterface
{
    /**
     * @phpstan-param Constraint::STR_OP_* $operator
     */
    public function versionCompare(PackageInterface $a, PackageInterface $b, string $operator): bool;

    /**
     * @param  non-empty-list<int>   $literals
     * @return non-empty-list<int>
     */
    public function selectPreferredPackages(Pool $pool, array $literals, ?string $requiredPackage = null): array;
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

/**
 * @author Nils Adermann <naderman@naderman.de>
 * @implements \Iterator<RuleSet::TYPE_*|-1, Rule>
 */
class RuleSetIterator implements \Iterator
{
    /** @var array<RuleSet::TYPE_*, Rule[]> */
    protected $rules;
    /** @var array<RuleSet::TYPE_*> */
    protected $types;

    /** @var int */
    protected $currentOffset;
    /** @var RuleSet::TYPE_*|-1 */
    protected $currentType;
    /** @var int */
    protected $currentTypeOffset;

    /**
     * @param array<RuleSet::TYPE_*, Rule[]> $rules
     */
    public function __construct(array $rules)
    {
        $this->rules = $rules;
        $this->types = array_keys($rules);
        sort($this->types);

        $this->rewind();
    }

    public function current(): Rule
    {
        return $this->rules[$this->currentType][$this->currentOffset];
    }

    /**
     * @return RuleSet::TYPE_*|-1
     */
    public function key(): int
    {
        return $this->currentType;
    }

    public function next(): void
    {
        $this->currentOffset++;

        if (!isset($this->rules[$this->currentType])) {
            return;
        }

        if ($this->currentOffset >= \count($this->rules[$this->currentType])) {
            $this->currentOffset = 0;

            do {
                $this->currentTypeOffset++;

                if (!isset($this->types[$this->currentTypeOffset])) {
                    $this->currentType = -1;
                    break;
                }

                $this->currentType = $this->types[$this->currentTypeOffset];
            } while (0 === \count($this->rules[$this->currentType]));
        }
    }

    public function rewind(): void
    {
        $this->currentOffset = 0;

        $this->currentTypeOffset = -1;
        $this->currentType = -1;

        do {
            $this->currentTypeOffset++;

            if (!isset($this->types[$this->currentTypeOffset])) {
                $this->currentType = -1;
                break;
            }

            $this->currentType = $this->types[$this->currentTypeOffset];
        } while (0 === \count($this->rules[$this->currentType]));
    }

    public function valid(): bool
    {
        return isset($this->rules[$this->currentType], $this->rules[$this->currentType][$this->currentOffset]);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Filter\PlatformRequirementFilter\IgnoreListPlatformRequirementFilter;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
use Composer\Package\BasePackage;
use Composer\Package\AliasPackage;

/**
 * @author Nils Adermann <naderman@naderman.de>
 * @phpstan-import-type ReasonData from Rule
 */
class RuleSetGenerator
{
    /** @var PolicyInterface */
    protected $policy;
    /** @var Pool */
    protected $pool;
    /** @var RuleSet */
    protected $rules;
    /** @var array<int, BasePackage> */
    protected $addedMap = [];
    /** @var array<string, BasePackage[]> */
    protected $addedPackagesByNames = [];

    public function __construct(PolicyInterface $policy, Pool $pool)
    {
        $this->policy = $policy;
        $this->pool = $pool;
        $this->rules = new RuleSet;
    }

    /**
     * Creates a new rule for the requirements of a package
     *
     * This rule is of the form (-A|B|C), where B and C are the providers of
     * one requirement of the package A.
     *
     * @param  BasePackage $package The package with a requirement
     * @param  BasePackage[] $providers The providers of the requirement
     * @param  Rule::RULE_* $reason A RULE_* constant describing the reason for generating this rule
     * @param  mixed $reasonData Any data, e.g. the requirement name, that goes with the reason
     * @return Rule|null The generated rule or null if tautological
     *
     * @phpstan-param ReasonData $reasonData
     */
    protected function createRequireRule(BasePackage $package, array $providers, $reason, $reasonData): ?Rule
    {
        $literals = [-$package->id];

        foreach ($providers as $provider) {
            // self fulfilling rule?
            if ($provider === $package) {
                return null;
            }
            $literals[] = $provider->id;
        }

        return new GenericRule($literals, $reason, $reasonData);
    }

    /**
     * Creates a rule to install at least one of a set of packages
     *
     * The rule is (A|B|C) with A, B and C different packages. If the given
     * set of packages is empty an impossible rule is generated.
     *
     * @param  non-empty-array<BasePackage> $packages   The set of packages to choose from
     * @param  Rule::RULE_*  $reason     A RULE_* constant describing the reason for
     *                                   generating this rule
     * @param  mixed         $reasonData Additional data like the root require or fix request info
     * @return Rule          The generated rule
     *
     * @phpstan-param ReasonData $reasonData
     */
    protected function createInstallOneOfRule(array $packages, $reason, $reasonData): Rule
    {
        $literals = [];
        foreach ($packages as $package) {
            $literals[] = $package->id;
        }

        return new GenericRule($literals, $reason, $reasonData);
    }

    /**
     * Creates a rule for two conflicting packages
     *
     * The rule for conflicting packages A and B is (-A|-B). A is called the issuer
     * and B the provider.
     *
     * @param BasePackage $issuer The package declaring the conflict
     * @param BasePackage $provider The package causing the conflict
     * @param Rule::RULE_* $reason A RULE_* constant describing the reason for generating this rule
     * @param mixed $reasonData Any data, e.g. the package name, that goes with the reason
     * @return ?Rule The generated rule
     *
     * @phpstan-param ReasonData $reasonData
     */
    protected function createRule2Literals(BasePackage $issuer, BasePackage $provider, $reason, $reasonData): ?Rule
    {
        // ignore self conflict
        if ($issuer === $provider) {
            return null;
        }

        return new Rule2Literals(-$issuer->id, -$provider->id, $reason, $reasonData);
    }

    /**
     * @param non-empty-array<BasePackage> $packages
     * @param Rule::RULE_* $reason A RULE_* constant
     * @param mixed $reasonData
     *
     * @phpstan-param ReasonData $reasonData
     */
    protected function createMultiConflictRule(array $packages, $reason, $reasonData): Rule
    {
        $literals = [];
        foreach ($packages as $package) {
            $literals[] = -$package->id;
        }

        if (\count($literals) === 2) {
            return new Rule2Literals($literals[0], $literals[1], $reason, $reasonData);
        }

        return new MultiConflictRule($literals, $reason, $reasonData);
    }

    /**
     * Adds a rule unless it duplicates an existing one of any type
     *
     * To be able to directly pass in the result of one of the rule creation
     * methods null is allowed which will not insert a rule.
     *
     * @param RuleSet::TYPE_* $type A TYPE_* constant defining the rule type
     * @param Rule $newRule The rule about to be added
     */
    private function addRule($type, ?Rule $newRule = null): void
    {
        if (null === $newRule) {
            return;
        }

        $this->rules->add($newRule, $type);
    }

    protected function addRulesForPackage(BasePackage $package, PlatformRequirementFilterInterface $platformRequirementFilter): void
    {
        /** @var \SplQueue<BasePackage> */
        $workQueue = new \SplQueue;
        $workQueue->enqueue($package);

        while (!$workQueue->isEmpty()) {
            $package = $workQueue->dequeue();
            if (isset($this->addedMap[$package->id])) {
                continue;
            }

            $this->addedMap[$package->id] = $package;

            if (!$package instanceof AliasPackage) {
                foreach ($package->getNames(false) as $name) {
                    $this->addedPackagesByNames[$name][] = $package;
                }
            } else {
                $workQueue->enqueue($package->getAliasOf());
                $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package, [$package->getAliasOf()], Rule::RULE_PACKAGE_ALIAS, $package));

                // aliases must be installed with their main package, so create a rule the other way around as well
                $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package->getAliasOf(), [$package], Rule::RULE_PACKAGE_INVERSE_ALIAS, $package->getAliasOf()));

                // if alias package has no self.version requires, its requirements do not
                // need to be added as the aliased package processing will take care of it
                if (!$package->hasSelfVersionRequires()) {
                    continue;
                }
            }

            foreach ($package->getRequires() as $link) {
                $constraint = $link->getConstraint();
                if ($platformRequirementFilter->isIgnored($link->getTarget())) {
                    continue;
                } elseif ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) {
                    $constraint = $platformRequirementFilter->filterConstraint($link->getTarget(), $constraint);
                }

                $possibleRequires = $this->pool->whatProvides($link->getTarget(), $constraint);

                $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package, $possibleRequires, Rule::RULE_PACKAGE_REQUIRES, $link));

                foreach ($possibleRequires as $require) {
                    $workQueue->enqueue($require);
                }
            }
        }
    }

    protected function addConflictRules(PlatformRequirementFilterInterface $platformRequirementFilter): void
    {
        /** @var BasePackage $package */
        foreach ($this->addedMap as $package) {
            foreach ($package->getConflicts() as $link) {
                // even if conflict ends up being with an alias, there would be at least one actual package by this name
                if (!isset($this->addedPackagesByNames[$link->getTarget()])) {
                    continue;
                }

                $constraint = $link->getConstraint();
                if ($platformRequirementFilter->isIgnored($link->getTarget())) {
                    continue;
                } elseif ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) {
                    $constraint = $platformRequirementFilter->filterConstraint($link->getTarget(), $constraint, false);
                }

                $conflicts = $this->pool->whatProvides($link->getTarget(), $constraint);

                foreach ($conflicts as $conflict) {
                    // define the conflict rule for regular packages, for alias packages it's only needed if the name
                    // matches the conflict exactly, otherwise the name match is by provide/replace which means the
                    // package which this is an alias of will conflict anyway, so no need to create additional rules
                    if (!$conflict instanceof AliasPackage || $conflict->getName() === $link->getTarget()) {
                        $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $conflict, Rule::RULE_PACKAGE_CONFLICT, $link));
                    }
                }
            }
        }

        foreach ($this->addedPackagesByNames as $name => $packages) {
            if (\count($packages) > 1) {
                $reason = Rule::RULE_PACKAGE_SAME_NAME;
                $this->addRule(RuleSet::TYPE_PACKAGE, $this->createMultiConflictRule($packages, $reason, $name));
            }
        }
    }

    protected function addRulesForRequest(Request $request, PlatformRequirementFilterInterface $platformRequirementFilter): void
    {
        foreach ($request->getFixedPackages() as $package) {
            if ($package->id === -1) {
                // fixed package was not added to the pool as it did not pass the stability requirements, this is fine
                if ($this->pool->isUnacceptableFixedOrLockedPackage($package)) {
                    continue;
                }

                // otherwise, looks like a bug
                throw new \LogicException("Fixed package ".$package->getPrettyString()." was not added to solver pool.");
            }

            $this->addRulesForPackage($package, $platformRequirementFilter);

            $rule = $this->createInstallOneOfRule([$package], Rule::RULE_FIXED, [
                'package' => $package,
            ]);
            $this->addRule(RuleSet::TYPE_REQUEST, $rule);
        }

        foreach ($request->getRequires() as $packageName => $constraint) {
            if ($platformRequirementFilter->isIgnored($packageName)) {
                continue;
            } elseif ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) {
                $constraint = $platformRequirementFilter->filterConstraint($packageName, $constraint);
            }

            $packages = $this->pool->whatProvides($packageName, $constraint);
            if (\count($packages) > 0) {
                foreach ($packages as $package) {
                    $this->addRulesForPackage($package, $platformRequirementFilter);
                }

                $rule = $this->createInstallOneOfRule($packages, Rule::RULE_ROOT_REQUIRE, [
                    'packageName' => $packageName,
                    'constraint' => $constraint,
                ]);
                $this->addRule(RuleSet::TYPE_REQUEST, $rule);
            }
        }
    }

    protected function addRulesForRootAliases(PlatformRequirementFilterInterface $platformRequirementFilter): void
    {
        foreach ($this->pool->getPackages() as $package) {
            // ensure that rules for root alias packages and aliases of packages which were loaded are also loaded
            // even if the alias itself isn't required, otherwise a package could be installed without its alias which
            // leads to unexpected behavior
            if (!isset($this->addedMap[$package->id]) &&
                $package instanceof AliasPackage &&
                ($package->isRootPackageAlias() || isset($this->addedMap[$package->getAliasOf()->id]))
            ) {
                $this->addRulesForPackage($package, $platformRequirementFilter);
            }
        }
    }

    public function getRulesFor(Request $request, ?PlatformRequirementFilterInterface $platformRequirementFilter = null): RuleSet
    {
        $platformRequirementFilter = $platformRequirementFilter ?? PlatformRequirementFilterFactory::ignoreNothing();

        $this->addRulesForRequest($request, $platformRequirementFilter);

        $this->addRulesForRootAliases($platformRequirementFilter);

        $this->addConflictRules($platformRequirementFilter);

        // Remove references to packages
        $this->addedMap = $this->addedPackagesByNames = [];

        $rules = $this->rules;

        $this->rules = new RuleSet;

        return $rules;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

/**
 * The RuleWatchGraph efficiently propagates decisions to other rules
 *
 * All rules generated for solving a SAT problem should be inserted into the
 * graph. When a decision on a literal is made, the graph can be used to
 * propagate the decision to all other rules involving the literal, leading to
 * other trivial decisions resulting from unit clauses.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class RuleWatchGraph
{
    /** @var array<int, RuleWatchChain> */
    protected $watchChains = [];

    /**
     * Inserts a rule node into the appropriate chains within the graph
     *
     * The node is prepended to the watch chains for each of the two literals it
     * watches.
     *
     * Assertions are skipped because they only depend on a single package and
     * have no alternative literal that could be true, so there is no need to
     * watch changes in any literals.
     *
     * @param RuleWatchNode $node The rule node to be inserted into the graph
     */
    public function insert(RuleWatchNode $node): void
    {
        if ($node->getRule()->isAssertion()) {
            return;
        }

        if (!$node->getRule() instanceof MultiConflictRule) {
            foreach ([$node->watch1, $node->watch2] as $literal) {
                if (!isset($this->watchChains[$literal])) {
                    $this->watchChains[$literal] = new RuleWatchChain;
                }

                $this->watchChains[$literal]->unshift($node);
            }
        } else {
            foreach ($node->getRule()->getLiterals() as $literal) {
                if (!isset($this->watchChains[$literal])) {
                    $this->watchChains[$literal] = new RuleWatchChain;
                }

                $this->watchChains[$literal]->unshift($node);
            }
        }
    }

    /**
     * Propagates a decision on a literal to all rules watching the literal
     *
     * If a decision, e.g. +A has been made, then all rules containing -A, e.g.
     * (-A|+B|+C) now need to satisfy at least one of the other literals, so
     * that the rule as a whole becomes true, since with +A applied the rule
     * is now (false|+B|+C) so essentially (+B|+C).
     *
     * This means that all rules watching the literal -A need to be updated to
     * watch 2 other literals which can still be satisfied instead. So literals
     * that conflict with previously made decisions are not an option.
     *
     * Alternatively it can occur that a unit clause results: e.g. if in the
     * above example the rule was (-A|+B), then A turning true means that
     * B must now be decided true as well.
     *
     * @param  int       $decidedLiteral The literal which was decided (A in our example)
     * @param  int       $level          The level at which the decision took place and at which
     *                                   all resulting decisions should be made.
     * @param  Decisions $decisions      Used to check previous decisions and to
     *                                   register decisions resulting from propagation
     * @return Rule|null If a conflict is found the conflicting rule is returned
     */
    public function propagateLiteral(int $decidedLiteral, int $level, Decisions $decisions): ?Rule
    {
        // we invert the decided literal here, example:
        // A was decided => (-A|B) now requires B to be true, so we look for
        // rules which are fulfilled by -A, rather than A.
        $literal = -$decidedLiteral;

        if (!isset($this->watchChains[$literal])) {
            return null;
        }

        $chain = $this->watchChains[$literal];

        $chain->rewind();
        while ($chain->valid()) {
            $node = $chain->current();
            if (!$node->getRule() instanceof MultiConflictRule) {
                $otherWatch = $node->getOtherWatch($literal);

                if (!$node->getRule()->isDisabled() && !$decisions->satisfy($otherWatch)) {
                    $ruleLiterals = $node->getRule()->getLiterals();

                    $alternativeLiterals = array_filter($ruleLiterals, static function ($ruleLiteral) use ($literal, $otherWatch, $decisions): bool {
                        return $literal !== $ruleLiteral &&
                            $otherWatch !== $ruleLiteral &&
                            !$decisions->conflict($ruleLiteral);
                    });

                    if (\count($alternativeLiterals) > 0) {
                        reset($alternativeLiterals);
                        $this->moveWatch($literal, current($alternativeLiterals), $node);
                        continue;
                    }

                    if ($decisions->conflict($otherWatch)) {
                        return $node->getRule();
                    }

                    $decisions->decide($otherWatch, $level, $node->getRule());
                }
            } else {
                foreach ($node->getRule()->getLiterals() as $otherLiteral) {
                    if ($literal !== $otherLiteral && !$decisions->satisfy($otherLiteral)) {
                        if ($decisions->conflict($otherLiteral)) {
                            return $node->getRule();
                        }

                        $decisions->decide($otherLiteral, $level, $node->getRule());
                    }
                }
            }

            $chain->next();
        }

        return null;
    }

    /**
     * Moves a rule node from one watch chain to another
     *
     * The rule node's watched literals are updated accordingly.
     *
     * @param int           $fromLiteral A literal the node used to watch
     * @param int           $toLiteral   A literal the node should watch now
     * @param RuleWatchNode $node        The rule node to be moved
     */
    protected function moveWatch(int $fromLiteral, int $toLiteral, RuleWatchNode $node): void
    {
        if (!isset($this->watchChains[$toLiteral])) {
            $this->watchChains[$toLiteral] = new RuleWatchChain;
        }

        $node->moveWatch($fromLiteral, $toLiteral);
        $this->watchChains[$fromLiteral]->remove();
        $this->watchChains[$toLiteral]->unshift($node);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Package\AliasPackage;
use Composer\Package\CompletePackageInterface;
use Composer\Package\Link;
use Composer\Package\PackageInterface;
use Composer\Repository\PlatformRepository;
use Composer\DependencyResolver\Operation\OperationInterface;

/**
 * @author Nils Adermann <naderman@naderman.de>
 * @internal
 */
class Transaction
{
    /**
     * @var OperationInterface[]
     */
    protected $operations;

    /**
     * Packages present at the beginning of the transaction
     * @var PackageInterface[]
     */
    protected $presentPackages;

    /**
     * Package set resulting from this transaction
     * @var array<string, PackageInterface>
     */
    protected $resultPackageMap;

    /**
     * @var array<string, PackageInterface[]>
     */
    protected $resultPackagesByName = [];

    /**
     * @param PackageInterface[] $presentPackages
     * @param PackageInterface[] $resultPackages
     */
    public function __construct(array $presentPackages, array $resultPackages)
    {
        $this->presentPackages = $presentPackages;
        $this->setResultPackageMaps($resultPackages);
        $this->operations = $this->calculateOperations();
    }

    /**
     * @return OperationInterface[]
     */
    public function getOperations(): array
    {
        return $this->operations;
    }

    /**
     * @param PackageInterface[] $resultPackages
     */
    private function setResultPackageMaps(array $resultPackages): void
    {
        $packageSort = static function (PackageInterface $a, PackageInterface $b): int {
            // sort alias packages by the same name behind their non alias version
            if ($a->getName() === $b->getName()) {
                if ($a instanceof AliasPackage !== $b instanceof AliasPackage) {
                    return $a instanceof AliasPackage ? -1 : 1;
                }

                // if names are the same, compare version, e.g. to sort aliases reliably, actual order does not matter
                return strcmp($b->getVersion(), $a->getVersion());
            }

            return strcmp($b->getName(), $a->getName());
        };

        $this->resultPackageMap = [];
        foreach ($resultPackages as $package) {
            $this->resultPackageMap[spl_object_hash($package)] = $package;
            foreach ($package->getNames() as $name) {
                $this->resultPackagesByName[$name][] = $package;
            }
        }

        uasort($this->resultPackageMap, $packageSort);
        foreach ($this->resultPackagesByName as $name => $packages) {
            uasort($this->resultPackagesByName[$name], $packageSort);
        }
    }

    /**
     * @return OperationInterface[]
     */
    protected function calculateOperations(): array
    {
        $operations = [];

        $presentPackageMap = [];
        $removeMap = [];
        $presentAliasMap = [];
        $removeAliasMap = [];
        foreach ($this->presentPackages as $package) {
            if ($package instanceof AliasPackage) {
                $presentAliasMap[$package->getName().'::'.$package->getVersion()] = $package;
                $removeAliasMap[$package->getName().'::'.$package->getVersion()] = $package;
            } else {
                $presentPackageMap[$package->getName()] = $package;
                $removeMap[$package->getName()] = $package;
            }
        }

        $stack = $this->getRootPackages();

        $visited = [];
        $processed = [];

        while (\count($stack) > 0) {
            $package = array_pop($stack);

            if (isset($processed[spl_object_hash($package)])) {
                continue;
            }

            if (!isset($visited[spl_object_hash($package)])) {
                $visited[spl_object_hash($package)] = true;

                $stack[] = $package;
                if ($package instanceof AliasPackage) {
                    $stack[] = $package->getAliasOf();
                } else {
                    foreach ($package->getRequires() as $link) {
                        $possibleRequires = $this->getProvidersInResult($link);

                        foreach ($possibleRequires as $require) {
                            $stack[] = $require;
                        }
                    }
                }
            } elseif (!isset($processed[spl_object_hash($package)])) {
                $processed[spl_object_hash($package)] = true;

                if ($package instanceof AliasPackage) {
                    $aliasKey = $package->getName().'::'.$package->getVersion();
                    if (isset($presentAliasMap[$aliasKey])) {
                        unset($removeAliasMap[$aliasKey]);
                    } else {
                        $operations[] = new Operation\MarkAliasInstalledOperation($package);
                    }
                } else {
                    if (isset($presentPackageMap[$package->getName()])) {
                        $source = $presentPackageMap[$package->getName()];

                        // do we need to update?
                        // TODO different for lock?
                        if ($package->getVersion() !== $presentPackageMap[$package->getName()]->getVersion() ||
                            $package->getDistReference() !== $presentPackageMap[$package->getName()]->getDistReference() ||
                            $package->getSourceReference() !== $presentPackageMap[$package->getName()]->getSourceReference() ||
                            (
                                $package instanceof CompletePackageInterface
                                && $presentPackageMap[$package->getName()] instanceof CompletePackageInterface
                                && (
                                    $package->isAbandoned() !== $presentPackageMap[$package->getName()]->isAbandoned()
                                    || $package->getReplacementPackage() !== $presentPackageMap[$package->getName()]->getReplacementPackage()
                                )
                            )
                        ) {
                            $operations[] = new Operation\UpdateOperation($source, $package);
                        }
                        unset($removeMap[$package->getName()]);
                    } else {
                        $operations[] = new Operation\InstallOperation($package);
                        unset($removeMap[$package->getName()]);
                    }
                }
            }
        }

        foreach ($removeMap as $name => $package) {
            array_unshift($operations, new Operation\UninstallOperation($package));
        }
        foreach ($removeAliasMap as $nameVersion => $package) {
            $operations[] = new Operation\MarkAliasUninstalledOperation($package);
        }

        $operations = $this->movePluginsToFront($operations);
        // TODO fix this:
        // we have to do this again here even though the above stack code did it because moving plugins moves them before uninstalls
        $operations = $this->moveUninstallsToFront($operations);

        // TODO skip updates which don't update? is this needed? we shouldn't schedule this update in the first place?
        /*
        if ('update' === $opType) {
            $targetPackage = $operation->getTargetPackage();
            if ($targetPackage->isDev()) {
                $initialPackage = $operation->getInitialPackage();
                if ($targetPackage->getVersion() === $initialPackage->getVersion()
                    && (!$targetPackage->getSourceReference() || $targetPackage->getSourceReference() === $initialPackage->getSourceReference())
                    && (!$targetPackage->getDistReference() || $targetPackage->getDistReference() === $initialPackage->getDistReference())
                ) {
                    $this->io->writeError('  - Skipping update of ' . $targetPackage->getPrettyName() . ' to the same reference-locked version', true, IOInterface::DEBUG);
                    $this->io->writeError('', true, IOInterface::DEBUG);

                    continue;
                }
            }
        }*/

        return $this->operations = $operations;
    }

    /**
     * Determine which packages in the result are not required by any other packages in it.
     *
     * These serve as a starting point to enumerate packages in a topological order despite potential cycles.
     * If there are packages with a cycle on the top level the package with the lowest name gets picked
     *
     * @return array<string, PackageInterface>
     */
    protected function getRootPackages(): array
    {
        $roots = $this->resultPackageMap;

        foreach ($this->resultPackageMap as $packageHash => $package) {
            if (!isset($roots[$packageHash])) {
                continue;
            }

            foreach ($package->getRequires() as $link) {
                $possibleRequires = $this->getProvidersInResult($link);

                foreach ($possibleRequires as $require) {
                    if ($require !== $package) {
                        unset($roots[spl_object_hash($require)]);
                    }
                }
            }
        }

        return $roots;
    }

    /**
     * @return PackageInterface[]
     */
    protected function getProvidersInResult(Link $link): array
    {
        if (!isset($this->resultPackagesByName[$link->getTarget()])) {
            return [];
        }

        return $this->resultPackagesByName[$link->getTarget()];
    }

    /**
     * Workaround: if your packages depend on plugins, we must be sure
     * that those are installed / updated first; else it would lead to packages
     * being installed multiple times in different folders, when running Composer
     * twice.
     *
     * While this does not fix the root-causes of https://github.com/composer/composer/issues/1147,
     * it at least fixes the symptoms and makes usage of composer possible (again)
     * in such scenarios.
     *
     * @param  OperationInterface[] $operations
     * @return OperationInterface[] reordered operation list
     */
    private function movePluginsToFront(array $operations): array
    {
        $dlModifyingPluginsNoDeps = [];
        $dlModifyingPluginsWithDeps = [];
        $dlModifyingPluginRequires = [];
        $pluginsNoDeps = [];
        $pluginsWithDeps = [];
        $pluginRequires = [];

        foreach (array_reverse($operations, true) as $idx => $op) {
            if ($op instanceof Operation\InstallOperation) {
                $package = $op->getPackage();
            } elseif ($op instanceof Operation\UpdateOperation) {
                $package = $op->getTargetPackage();
            } else {
                continue;
            }

            $extra = $package->getExtra();
            $isDownloadsModifyingPlugin = $package->getType() === 'composer-plugin' && isset($extra['plugin-modifies-downloads']) && $extra['plugin-modifies-downloads'] === true;

            // is this a downloads modifying plugin or a dependency of one?
            if ($isDownloadsModifyingPlugin || \count(array_intersect($package->getNames(), $dlModifyingPluginRequires)) > 0) {
                // get the package's requires, but filter out any platform requirements
                $requires = array_filter(array_keys($package->getRequires()), static function ($req): bool {
                    return !PlatformRepository::isPlatformPackage($req);
                });

                // is this a plugin with no meaningful dependencies?
                if ($isDownloadsModifyingPlugin && 0 === \count($requires)) {
                    // plugins with no dependencies go to the very front
                    array_unshift($dlModifyingPluginsNoDeps, $op);
                } else {
                    // capture the requirements for this package so those packages will be moved up as well
                    $dlModifyingPluginRequires = array_merge($dlModifyingPluginRequires, $requires);
                    // move the operation to the front
                    array_unshift($dlModifyingPluginsWithDeps, $op);
                }

                unset($operations[$idx]);
                continue;
            }

            // is this package a plugin?
            $isPlugin = $package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer';

            // is this a plugin or a dependency of a plugin?
            if ($isPlugin || \count(array_intersect($package->getNames(), $pluginRequires)) > 0) {
                // get the package's requires, but filter out any platform requirements
                $requires = array_filter(array_keys($package->getRequires()), static function ($req): bool {
                    return !PlatformRepository::isPlatformPackage($req);
                });

                // is this a plugin with no meaningful dependencies?
                if ($isPlugin && 0 === \count($requires)) {
                    // plugins with no dependencies go to the very front
                    array_unshift($pluginsNoDeps, $op);
                } else {
                    // capture the requirements for this package so those packages will be moved up as well
                    $pluginRequires = array_merge($pluginRequires, $requires);
                    // move the operation to the front
                    array_unshift($pluginsWithDeps, $op);
                }

                unset($operations[$idx]);
            }
        }

        return array_merge($dlModifyingPluginsNoDeps, $dlModifyingPluginsWithDeps, $pluginsNoDeps, $pluginsWithDeps, $operations);
    }

    /**
     * Removals of packages should be executed before installations in
     * case two packages resolve to the same path (due to custom installers)
     *
     * @param  OperationInterface[] $operations
     * @return OperationInterface[] reordered operation list
     */
    private function moveUninstallsToFront(array $operations): array
    {
        $uninstOps = [];
        foreach ($operations as $idx => $op) {
            if ($op instanceof Operation\UninstallOperation || $op instanceof Operation\MarkAliasUninstalledOperation) {
                $uninstOps[] = $op;
                unset($operations[$idx]);
            }
        }

        return array_merge($uninstOps, $operations);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Advisory\AuditConfig;
use Composer\Advisory\Auditor;
use Composer\Advisory\PartialSecurityAdvisory;
use Composer\Advisory\SecurityAdvisory;
use Composer\Package\PackageInterface;
use Composer\Package\RootPackageInterface;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\RepositorySet;
use Composer\Semver\Constraint\Constraint;

/**
 * @internal
 */
class SecurityAdvisoryPoolFilter
{
    /** @var Auditor */
    private $auditor;
    /** @var AuditConfig $auditConfig */
    private $auditConfig;

    public function __construct(
        Auditor $auditor,
        AuditConfig $auditConfig
    ) {
        $this->auditor = $auditor;
        $this->auditConfig = $auditConfig;
    }

    /**
     * @param array<RepositoryInterface> $repositories
     */
    public function filter(Pool $pool, array $repositories, Request $request): Pool
    {
        if (!$this->auditConfig->blockInsecure) {
            return $pool;
        }

        $repoSet = new RepositorySet();
        foreach ($repositories as $repo) {
            $repoSet->addRepository($repo);
        }

        $packagesForAdvisories = [];
        foreach ($pool->getPackages() as $package) {
            if (!$package instanceof RootPackageInterface && !PlatformRepository::isPlatformPackage($package->getName()) && !$request->isLockedPackage($package)) {
                $packagesForAdvisories[] = $package;
            }
        }

        $allAdvisories = $repoSet->getMatchingSecurityAdvisories($packagesForAdvisories, true, true);
        if ($this->auditor->needsCompleteAdvisoryLoad($allAdvisories['advisories'], $this->auditConfig->ignoreListForBlocking)) {
            $allAdvisories = $repoSet->getMatchingSecurityAdvisories($packagesForAdvisories, false, true);
        }

        $advisoryMap = $this->auditor->processAdvisories($allAdvisories['advisories'], $this->auditConfig->ignoreListForBlocking, $this->auditConfig->ignoreSeverityForBlocking)['advisories'];

        $packages = [];
        $securityRemovedVersions = [];
        $abandonedRemovedVersions = [];
        foreach ($pool->getPackages() as $package) {
            if ($this->auditConfig->blockAbandoned && count($this->auditor->filterAbandonedPackages([$package], $this->auditConfig->ignoreAbandonedForBlocking)) !== 0) {
                foreach ($package->getNames(false) as $packageName) {
                    $abandonedRemovedVersions[$packageName][$package->getVersion()] = $package->getPrettyVersion();
                }
                continue;
            }

            $matchingAdvisories = $this->getMatchingAdvisories($package, $advisoryMap);
            if (count($matchingAdvisories) > 0) {
                foreach ($package->getNames(false) as $packageName) {
                    $securityRemovedVersions[$packageName][$package->getVersion()] = $matchingAdvisories;
                }

                continue;
            }

            $packages[] = $package;
        }

        return new Pool($packages, $pool->getUnacceptableFixedOrLockedPackages(), $pool->getAllRemovedVersions(), $pool->getAllRemovedVersionsByPackage(), $securityRemovedVersions, $abandonedRemovedVersions);
    }

    /**
     * @param array<string, array<PartialSecurityAdvisory|SecurityAdvisory>> $advisoryMap
     * @return list<PartialSecurityAdvisory|SecurityAdvisory>
     */
    private function getMatchingAdvisories(PackageInterface $package, array $advisoryMap): array
    {
        if ($package->isDev()) {
            return [];
        }

        $matchingAdvisories = [];
        foreach ($package->getNames(false) as $packageName) {
            if (!isset($advisoryMap[$packageName])) {
                continue;
            }

            $packageConstraint = new Constraint(Constraint::STR_OP_EQ, $package->getVersion());
            foreach ($advisoryMap[$packageName] as $advisory) {
                if ($advisory->affectedVersions->matches($packageConstraint)) {
                    $matchingAdvisories[] = $advisory;
                }
            }
        }

        return $matchingAdvisories;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

/**
 * Wrapper around a Rule which keeps track of the two literals it watches
 *
 * Used by RuleWatchGraph to store rules in two RuleWatchChains.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class RuleWatchNode
{
    /** @var int */
    public $watch1;
    /** @var int */
    public $watch2;

    /** @var Rule */
    protected $rule;

    /**
     * Creates a new node watching the first and second literals of the rule.
     *
     * @param Rule $rule The rule to wrap
     */
    public function __construct(Rule $rule)
    {
        $this->rule = $rule;

        $literals = $rule->getLiterals();

        $literalCount = \count($literals);
        $this->watch1 = $literalCount > 0 ? $literals[0] : 0;
        $this->watch2 = $literalCount > 1 ? $literals[1] : 0;
    }

    /**
     * Places the second watch on the rule's literal, decided at the highest level
     *
     * Useful for learned rules where the literal for the highest rule is most
     * likely to quickly lead to further decisions.
     *
     * @param Decisions $decisions The decisions made so far by the solver
     */
    public function watch2OnHighest(Decisions $decisions): void
    {
        $literals = $this->rule->getLiterals();

        // if there are only 2 elements, both are being watched anyway
        if (\count($literals) < 3 || $this->rule instanceof MultiConflictRule) {
            return;
        }

        $watchLevel = 0;

        foreach ($literals as $literal) {
            $level = $decisions->decisionLevel($literal);

            if ($level > $watchLevel) {
                $this->watch2 = $literal;
                $watchLevel = $level;
            }
        }
    }

    /**
     * Returns the rule this node wraps
     */
    public function getRule(): Rule
    {
        return $this->rule;
    }

    /**
     * Given one watched literal, this method returns the other watched literal
     *
     * @param  int $literal The watched literal that should not be returned
     * @return int A literal
     */
    public function getOtherWatch(int $literal): int
    {
        if ($this->watch1 === $literal) {
            return $this->watch2;
        }

        return $this->watch1;
    }

    /**
     * Moves a watch from one literal to another
     *
     * @param int $from The previously watched literal
     * @param int $to   The literal to be watched now
     */
    public function moveWatch(int $from, int $to): void
    {
        if ($this->watch1 === $from) {
            $this->watch1 = $to;
        } else {
            $this->watch2 = $to;
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver\Operation;

use Composer\Package\AliasPackage;

/**
 * Solver install operation.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class MarkAliasInstalledOperation extends SolverOperation implements OperationInterface
{
    protected const TYPE = 'markAliasInstalled';

    /**
     * @var AliasPackage
     */
    protected $package;

    public function __construct(AliasPackage $package)
    {
        $this->package = $package;
    }

    /**
     * Returns package instance.
     */
    public function getPackage(): AliasPackage
    {
        return $this->package;
    }

    /**
     * @inheritDoc
     */
    public function show($lock): string
    {
        return 'Marking <info>'.$this->package->getPrettyName().'</info> (<comment>'.$this->package->getFullPrettyVersion().'</comment>) as installed, alias of <info>'.$this->package->getAliasOf()->getPrettyName().'</info> (<comment>'.$this->package->getAliasOf()->getFullPrettyVersion().'</comment>)';
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver\Operation;

/**
 * Abstract operation class.
 *
 * @author Aleksandr Bezpiatov <aleksandr.bezpiatov@spryker.com>
 */
abstract class SolverOperation implements OperationInterface
{
    /**
     * @abstract must be redefined by extending classes
     */
    protected const TYPE = '';

    /**
     * Returns operation type.
     */
    public function getOperationType(): string
    {
        return static::TYPE;
    }

    /**
     * @inheritDoc
     */
    public function __toString()
    {
        return $this->show(false);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver\Operation;

use Composer\Package\PackageInterface;

/**
 * Solver install operation.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class InstallOperation extends SolverOperation implements OperationInterface
{
    protected const TYPE = 'install';

    /**
     * @var PackageInterface
     */
    protected $package;

    public function __construct(PackageInterface $package)
    {
        $this->package = $package;
    }

    /**
     * Returns package instance.
     */
    public function getPackage(): PackageInterface
    {
        return $this->package;
    }

    /**
     * @inheritDoc
     */
    public function show($lock): string
    {
        return self::format($this->package, $lock);
    }

    public static function format(PackageInterface $package, bool $lock = false): string
    {
        return ($lock ? 'Locking ' : 'Installing ').'<info>'.$package->getPrettyName().'</info> (<comment>'.$package->getFullPrettyVersion().'</comment>)';
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver\Operation;

/**
 * Solver operation interface.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
interface OperationInterface
{
    /**
     * Returns operation type.
     *
     * @return string
     */
    public function getOperationType();

    /**
     * Serializes the operation in a human readable format
     *
     * @param  bool   $lock Whether this is an operation on the lock file
     * @return string
     */
    public function show(bool $lock);

    /**
     * Serializes the operation in a human readable format
     *
     * @return string
     */
    public function __toString();
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver\Operation;

use Composer\Package\AliasPackage;

/**
 * Solver install operation.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class MarkAliasUninstalledOperation extends SolverOperation implements OperationInterface
{
    protected const TYPE = 'markAliasUninstalled';

    /**
     * @var AliasPackage
     */
    protected $package;

    public function __construct(AliasPackage $package)
    {
        $this->package = $package;
    }

    /**
     * Returns package instance.
     */
    public function getPackage(): AliasPackage
    {
        return $this->package;
    }

    /**
     * @inheritDoc
     */
    public function show($lock): string
    {
        return 'Marking <info>'.$this->package->getPrettyName().'</info> (<comment>'.$this->package->getFullPrettyVersion().'</comment>) as uninstalled, alias of <info>'.$this->package->getAliasOf()->getPrettyName().'</info> (<comment>'.$this->package->getAliasOf()->getFullPrettyVersion().'</comment>)';
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver\Operation;

use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionParser;

/**
 * Solver update operation.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class UpdateOperation extends SolverOperation implements OperationInterface
{
    protected const TYPE = 'update';

    /**
     * @var PackageInterface
     */
    protected $initialPackage;

    /**
     * @var PackageInterface
     */
    protected $targetPackage;

    /**
     * @param PackageInterface $initial initial package
     * @param PackageInterface $target  target package (updated)
     */
    public function __construct(PackageInterface $initial, PackageInterface $target)
    {
        $this->initialPackage = $initial;
        $this->targetPackage = $target;
    }

    /**
     * Returns initial package.
     */
    public function getInitialPackage(): PackageInterface
    {
        return $this->initialPackage;
    }

    /**
     * Returns target package.
     */
    public function getTargetPackage(): PackageInterface
    {
        return $this->targetPackage;
    }

    /**
     * @inheritDoc
     */
    public function show($lock): string
    {
        return self::format($this->initialPackage, $this->targetPackage, $lock);
    }

    public static function format(PackageInterface $initialPackage, PackageInterface $targetPackage, bool $lock = false): string
    {
        $fromVersion = $initialPackage->getFullPrettyVersion();
        $toVersion = $targetPackage->getFullPrettyVersion();

        if ($fromVersion === $toVersion && $initialPackage->getSourceReference() !== $targetPackage->getSourceReference()) {
            $fromVersion = $initialPackage->getFullPrettyVersion(true, PackageInterface::DISPLAY_SOURCE_REF);
            $toVersion = $targetPackage->getFullPrettyVersion(true, PackageInterface::DISPLAY_SOURCE_REF);
        } elseif ($fromVersion === $toVersion && $initialPackage->getDistReference() !== $targetPackage->getDistReference()) {
            $fromVersion = $initialPackage->getFullPrettyVersion(true, PackageInterface::DISPLAY_DIST_REF);
            $toVersion = $targetPackage->getFullPrettyVersion(true, PackageInterface::DISPLAY_DIST_REF);
        }

        $actionName = VersionParser::isUpgrade($initialPackage->getVersion(), $targetPackage->getVersion()) ? 'Upgrading' : 'Downgrading';

        return $actionName.' <info>'.$initialPackage->getPrettyName().'</info> (<comment>'.$fromVersion.'</comment> => <comment>'.$toVersion.'</comment>)';
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver\Operation;

use Composer\Package\PackageInterface;

/**
 * Solver uninstall operation.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class UninstallOperation extends SolverOperation implements OperationInterface
{
    protected const TYPE = 'uninstall';

    /**
     * @var PackageInterface
     */
    protected $package;

    public function __construct(PackageInterface $package)
    {
        $this->package = $package;
    }

    /**
     * Returns package instance.
     */
    public function getPackage(): PackageInterface
    {
        return $this->package;
    }

    /**
     * @inheritDoc
     */
    public function show($lock): string
    {
        return self::format($this->package, $lock);
    }

    public static function format(PackageInterface $package, bool $lock = false): string
    {
        return 'Removing <info>'.$package->getPrettyName().'</info> (<comment>'.$package->getFullPrettyVersion().'</comment>)';
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer;

use Composer\Advisory\Auditor;
use Composer\Config\ConfigSourceInterface;
use Composer\Downloader\TransportException;
use Composer\IO\IOInterface;
use Composer\Pcre\Preg;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Config
{
    public const SOURCE_DEFAULT = 'default';
    public const SOURCE_COMMAND = 'command';
    public const SOURCE_UNKNOWN = 'unknown';

    public const RELATIVE_PATHS = 1;

    /** @var array<string, mixed> */
    public static $defaultConfig = [
        'process-timeout' => 300,
        'use-include-path' => false,
        'allow-plugins' => [],
        'use-parent-dir' => 'prompt',
        'preferred-install' => 'dist',
        'audit' => ['ignore' => [], 'abandoned' => Auditor::ABANDONED_FAIL],
        'notify-on-install' => true,
        'github-protocols' => ['https', 'ssh', 'git'],
        'gitlab-protocol' => null,
        'vendor-dir' => 'vendor',
        'bin-dir' => '{$vendor-dir}/bin',
        'cache-dir' => '{$home}/cache',
        'data-dir' => '{$home}',
        'cache-files-dir' => '{$cache-dir}/files',
        'cache-repo-dir' => '{$cache-dir}/repo',
        'cache-vcs-dir' => '{$cache-dir}/vcs',
        'cache-ttl' => 15552000, // 6 months
        'cache-files-ttl' => null, // fallback to cache-ttl
        'cache-files-maxsize' => '300MiB',
        'cache-read-only' => false,
        'bin-compat' => 'auto',
        'discard-changes' => false,
        'autoloader-suffix' => null,
        'sort-packages' => false,
        'optimize-autoloader' => false,
        'classmap-authoritative' => false,
        'apcu-autoloader' => false,
        'prepend-autoloader' => true,
        'update-with-minimal-changes' => false,
        'github-domains' => ['github.com'],
        'bitbucket-expose-hostname' => true,
        'disable-tls' => false,
        'secure-http' => true,
        'secure-svn-domains' => [],
        'cafile' => null,
        'capath' => null,
        'github-expose-hostname' => true,
        'gitlab-domains' => ['gitlab.com'],
        'store-auths' => 'prompt',
        'platform' => [],
        'archive-format' => 'tar',
        'archive-dir' => '.',
        'htaccess-protect' => true,
        'use-github-api' => true,
        'lock' => true,
        'platform-check' => 'php-only',
        'bitbucket-oauth' => [],
        'github-oauth' => [],
        'gitlab-oauth' => [],
        'gitlab-token' => [],
        'http-basic' => [],
        'bearer' => [],
        'custom-headers' => [],
        'bump-after-update' => false,
        'allow-missing-requirements' => false,
        'client-certificate' => [],
        'forgejo-domains' => ['codeberg.org'],
        'forgejo-token' => [],
    ];

    /** @var array<string, mixed> */
    public static $defaultRepositories = [
        'packagist.org' => [
            'type' => 'composer',
            'url' => 'https://repo.packagist.org',
        ],
    ];

    /** @var array<string, mixed> */
    private $config;
    /** @var ?non-empty-string */
    private $baseDir;
    /** @var array<int|string, mixed> */
    private $repositories;
    /** @var ConfigSourceInterface */
    private $configSource;
    /** @var ConfigSourceInterface */
    private $authConfigSource;
    /** @var ConfigSourceInterface|null */
    private $localAuthConfigSource = null;
    /** @var bool */
    private $useEnvironment;
    /** @var array<string, true> */
    private $warnedHosts = [];
    /** @var array<string, true> */
    private $sslVerifyWarnedHosts = [];
    /** @var array<string, string> */
    private $sourceOfConfigValue = [];

    /**
     * @param bool    $useEnvironment Use COMPOSER_ environment variables to replace config settings
     * @param ?string $baseDir        Optional base directory of the config
     */
    public function __construct(bool $useEnvironment = true, ?string $baseDir = null)
    {
        // load defaults
        $this->config = static::$defaultConfig;

        $this->repositories = static::$defaultRepositories;
        $this->useEnvironment = $useEnvironment;
        $this->baseDir = is_string($baseDir) && '' !== $baseDir ? $baseDir : null;

        foreach ($this->config as $configKey => $configValue) {
            $this->setSourceOfConfigValue($configValue, $configKey, self::SOURCE_DEFAULT);
        }

        foreach ($this->repositories as $configKey => $configValue) {
            $this->setSourceOfConfigValue($configValue, 'repositories.' . $configKey, self::SOURCE_DEFAULT);
        }
    }

    /**
     * Changing this can break path resolution for relative config paths so do not call this without knowing what you are doing
     *
     * The $baseDir should be an absolute path and without trailing slash
     *
     * @param non-empty-string|null $baseDir
     */
    public function setBaseDir(?string $baseDir): void
    {
        $this->baseDir = $baseDir;
    }

    public function setConfigSource(ConfigSourceInterface $source): void
    {
        $this->configSource = $source;
    }

    public function getConfigSource(): ConfigSourceInterface
    {
        return $this->configSource;
    }

    public function setAuthConfigSource(ConfigSourceInterface $source): void
    {
        $this->authConfigSource = $source;
    }

    public function getAuthConfigSource(): ConfigSourceInterface
    {
        return $this->authConfigSource;
    }

    public function setLocalAuthConfigSource(ConfigSourceInterface $source): void
    {
        $this->localAuthConfigSource = $source;
    }

    public function getLocalAuthConfigSource(): ?ConfigSourceInterface
    {
        return $this->localAuthConfigSource;
    }

    /**
     * Merges new config values with the existing ones (overriding)
     *
     * @param array{config?: array<string, mixed>, repositories?: array<mixed>} $config
     */
    public function merge(array $config, string $source = self::SOURCE_UNKNOWN): void
    {
        // override defaults with given config
        if (!empty($config['config']) && is_array($config['config'])) {
            foreach ($config['config'] as $key => $val) {
                if (in_array($key, ['bitbucket-oauth', 'github-oauth', 'gitlab-oauth', 'gitlab-token', 'http-basic', 'bearer', 'client-certificate', 'forgejo-token'], true) && isset($this->config[$key])) {
                    $this->config[$key] = array_merge($this->config[$key], $val);
                    $this->setSourceOfConfigValue($val, $key, $source);
                } elseif (in_array($key, ['allow-plugins'], true) && isset($this->config[$key]) && is_array($this->config[$key]) && is_array($val)) {
                    // merging $val first to get the local config on top of the global one, then appending the global config,
                    // then merging local one again to make sure the values from local win over global ones for keys present in both
                    $this->config[$key] = array_merge($val, $this->config[$key], $val);
                    $this->setSourceOfConfigValue($val, $key, $source);
                } elseif (in_array($key, ['gitlab-domains', 'github-domains'], true) && isset($this->config[$key])) {
                    $this->config[$key] = array_unique(array_merge($this->config[$key], $val));
                    $this->setSourceOfConfigValue($val, $key, $source);
                } elseif ('preferred-install' === $key && isset($this->config[$key])) {
                    if (is_array($val) || is_array($this->config[$key])) {
                        if (is_string($val)) {
                            $val = ['*' => $val];
                        }
                        if (is_string($this->config[$key])) {
                            $this->config[$key] = ['*' => $this->config[$key]];
                            $this->sourceOfConfigValue[$key . '*'] = $source;
                        }
                        $this->config[$key] = array_merge($this->config[$key], $val);
                        $this->setSourceOfConfigValue($val, $key, $source);
                        // the full match pattern needs to be last
                        if (isset($this->config[$key]['*'])) {
                            $wildcard = $this->config[$key]['*'];
                            unset($this->config[$key]['*']);
                            $this->config[$key]['*'] = $wildcard;
                        }
                    } else {
                        $this->config[$key] = $val;
                        $this->setSourceOfConfigValue($val, $key, $source);
                    }
                } elseif ('audit' === $key) {
                    $currentIgnores = $this->config['audit']['ignore'];
                    $this->config[$key] = array_merge($this->config['audit'], $val);
                    $this->setSourceOfConfigValue($val, $key, $source);
                    $this->config['audit']['ignore'] = array_merge($currentIgnores, $val['ignore'] ?? []);
                } else {
                    $this->config[$key] = $val;
                    $this->setSourceOfConfigValue($val, $key, $source);
                }
            }
        }

        if (!empty($config['repositories']) && is_array($config['repositories'])) {
            $this->repositories = array_reverse($this->repositories, true);
            $newRepos = array_reverse($config['repositories'], true);
            foreach ($newRepos as $name => $repository) {
                // disable a repository by name
                // this is a code path, that will be used less as the next check will be preferred
                if (false === $repository) {
                    $this->disableRepoByName((string) $name);
                    continue;
                }

                // disable a repository with an anonymous {"name": false} repo
                if (is_array($repository) && 1 === count($repository) && false === current($repository)) {
                    $this->disableRepoByName((string) key($repository));
                    continue;
                }

                // auto-deactivate the default packagist.org repo if it gets redefined
                if (isset($repository['type'], $repository['url']) && $repository['type'] === 'composer' && Preg::isMatch('{^https?://(?:[a-z0-9-.]+\.)?packagist.org(/|$)}', $repository['url'])) {
                    $this->disableRepoByName('packagist.org');
                }

                // store repo
                if (is_int($name)) {
                    if (!isset($this->repositories[$name])) {
                        $this->repositories[$name] = $repository;
                    } else {
                        $this->repositories[] = $repository;
                    }
                    $this->setSourceOfConfigValue($repository, 'repositories.' . array_search($repository, $this->repositories, true), $source);
                } else {
                    if ($name === 'packagist') { // BC support for default "packagist" named repo
                        $this->repositories[$name . '.org'] = $repository;
                        $this->setSourceOfConfigValue($repository, 'repositories.' . $name . '.org', $source);
                    } else {
                        $this->repositories[$name] = $repository;
                        $this->setSourceOfConfigValue($repository, 'repositories.' . $name, $source);
                    }
                }
            }
            $this->repositories = array_reverse($this->repositories, true);
        }
    }

    /**
     * @return array<int|string, mixed>
     */
    public function getRepositories(): array
    {
        return $this->repositories;
    }

    /**
     * Returns a setting
     *
     * @param  int               $flags Options (see class constants)
     * @throws \RuntimeException
     *
     * @return mixed
     */
    public function get(string $key, int $flags = 0)
    {
        switch ($key) {
            // strings/paths with env var and {$refs} support
            case 'vendor-dir':
            case 'bin-dir':
            case 'process-timeout':
            case 'data-dir':
            case 'cache-dir':
            case 'cache-files-dir':
            case 'cache-repo-dir':
            case 'cache-vcs-dir':
            case 'cafile':
            case 'capath':
                // convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config
                $env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_'));

                $val = $this->getComposerEnv($env);
                if ($val !== false) {
                    $this->setSourceOfConfigValue($val, $key, $env);
                }

                if ($key === 'process-timeout') {
                    return max(0, false !== $val ? (int) $val : $this->config[$key]);
                }

                $val = rtrim((string) $this->process(false !== $val ? $val : $this->config[$key], $flags), '/\\');
                $val = Platform::expandPath($val);

                if (substr($key, -4) !== '-dir') {
                    return $val;
                }

                return (($flags & self::RELATIVE_PATHS) === self::RELATIVE_PATHS) ? $val : $this->realpath($val);

            // booleans with env var support
            case 'cache-read-only':
            case 'htaccess-protect':
                // convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config
                $env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_'));

                $val = $this->getComposerEnv($env);
                if (false === $val) {
                    $val = $this->config[$key];
                } else {
                    $this->setSourceOfConfigValue($val, $key, $env);
                }

                return $val !== 'false' && (bool) $val;

            // booleans without env var support
            case 'disable-tls':
            case 'secure-http':
            case 'use-github-api':
            case 'lock':
                // special case for secure-http
                if ($key === 'secure-http' && $this->get('disable-tls') === true) {
                    return false;
                }

                return $this->config[$key] !== 'false' && (bool) $this->config[$key];

            // ints without env var support
            case 'cache-ttl':
                return max(0, (int) $this->config[$key]);

            // numbers with kb/mb/gb support, without env var support
            case 'cache-files-maxsize':
                if (!Preg::isMatch('/^\s*([0-9.]+)\s*(?:([kmg])(?:i?b)?)?\s*$/i', (string) $this->config[$key], $matches)) {
                    throw new \RuntimeException(
                        "Could not parse the value of '$key': {$this->config[$key]}"
                    );
                }
                $size = (float) $matches[1];
                if (isset($matches[2])) {
                    switch (strtolower($matches[2])) {
                        case 'g':
                            $size *= 1024;
                            // intentional fallthrough
                            // no break
                        case 'm':
                            $size *= 1024;
                            // intentional fallthrough
                            // no break
                        case 'k':
                            $size *= 1024;
                            break;
                    }
                }

                return max(0, (int) $size);

            // special cases below
            case 'cache-files-ttl':
                if (isset($this->config[$key])) {
                    return max(0, (int) $this->config[$key]);
                }

                return $this->get('cache-ttl');

            case 'home':
                return rtrim($this->process(Platform::expandPath($this->config[$key]), $flags), '/\\');

            case 'bin-compat':
                $value = $this->getComposerEnv('COMPOSER_BIN_COMPAT') ?: $this->config[$key];

                if (!in_array($value, ['auto', 'full', 'proxy', 'symlink'])) {
                    throw new \RuntimeException(
                        "Invalid value for 'bin-compat': {$value}. Expected auto, full or proxy"
                    );
                }

                if ($value === 'symlink') {
                    trigger_error('config.bin-compat "symlink" is deprecated since Composer 2.2, use auto, full (for Windows compatibility) or proxy instead.', E_USER_DEPRECATED);
                }

                return $value;

            case 'discard-changes':
                $env = $this->getComposerEnv('COMPOSER_DISCARD_CHANGES');
                if ($env !== false) {
                    if (!in_array($env, ['stash', 'true', 'false', '1', '0'], true)) {
                        throw new \RuntimeException(
                            "Invalid value for COMPOSER_DISCARD_CHANGES: {$env}. Expected 1, 0, true, false or stash"
                        );
                    }
                    if ('stash' === $env) {
                        return 'stash';
                    }

                    // convert string value to bool
                    return $env !== 'false' && (bool) $env;
                }

                if (!in_array($this->config[$key], [true, false, 'stash'], true)) {
                    throw new \RuntimeException(
                        "Invalid value for 'discard-changes': {$this->config[$key]}. Expected true, false or stash"
                    );
                }

                return $this->config[$key];

            case 'github-protocols':
                $protos = $this->config['github-protocols'];
                if ($this->config['secure-http'] && false !== ($index = array_search('git', $protos))) {
                    unset($protos[$index]);
                }
                if (reset($protos) === 'http') {
                    throw new \RuntimeException('The http protocol for github is not available anymore, update your config\'s github-protocols to use "https", "git" or "ssh"');
                }

                return $protos;

            case 'autoloader-suffix':
                if ($this->config[$key] === '') { // we need to guarantee null or non-empty-string
                    return null;
                }

                return $this->process($this->config[$key], $flags);

            case 'audit':
                $result = $this->config[$key];
                $abandonedEnv = $this->getComposerEnv('COMPOSER_AUDIT_ABANDONED');
                if (false !== $abandonedEnv) {
                    if (!in_array($abandonedEnv, $validChoices = Auditor::ABANDONEDS, true)) {
                        throw new \RuntimeException(
                            "Invalid value for COMPOSER_AUDIT_ABANDONED: {$abandonedEnv}. Expected one of ".implode(', ', Auditor::ABANDONEDS)."."
                        );
                    }
                    $result['abandoned'] = $abandonedEnv;
                }

                $blockAbandonedEnv = $this->getComposerEnv('COMPOSER_SECURITY_BLOCKING_ABANDONED');
                if (false !== $blockAbandonedEnv) {
                    if (!in_array($blockAbandonedEnv, ['0', '1'], true)) {
                        throw new \RuntimeException(
                            "Invalid value for COMPOSER_SECURITY_BLOCKING_ABANDONED: {$blockAbandonedEnv}. Expected 0 or 1."
                        );
                    }
                    $result['block-abandoned'] = (bool) (int) $blockAbandonedEnv;
                }

                return $result;

            default:
                if (!isset($this->config[$key])) {
                    return null;
                }

                return $this->process($this->config[$key], $flags);
        }
    }

    /**
     * @return array<string, mixed[]>
     */
    public function all(int $flags = 0): array
    {
        $all = [
            'repositories' => $this->getRepositories(),
        ];
        foreach (array_keys($this->config) as $key) {
            $all['config'][$key] = $this->get($key, $flags);
        }

        return $all;
    }

    public function getSourceOfValue(string $key): string
    {
        $this->get($key);

        return $this->sourceOfConfigValue[$key] ?? self::SOURCE_UNKNOWN;
    }

    /**
     * @param mixed  $configValue
     */
    private function setSourceOfConfigValue($configValue, string $path, string $source): void
    {
        $this->sourceOfConfigValue[$path] = $source;

        if (is_array($configValue)) {
            foreach ($configValue as $key => $value) {
                $this->setSourceOfConfigValue($value, $path . '.' . $key, $source);
            }
        }
    }

    /**
     * @return array<string, mixed[]>
     */
    public function raw(): array
    {
        return [
            'repositories' => $this->getRepositories(),
            'config' => $this->config,
        ];
    }

    /**
     * Checks whether a setting exists
     */
    public function has(string $key): bool
    {
        return array_key_exists($key, $this->config);
    }

    /**
     * Replaces {$refs} inside a config string
     *
     * @param  string|mixed $value a config string that can contain {$refs-to-other-config}
     * @param  int          $flags Options (see class constants)
     *
     * @return string|mixed
     */
    private function process($value, int $flags)
    {
        if (!is_string($value)) {
            return $value;
        }

        return Preg::replaceCallback('#\{\$(.+)\}#', function ($match) use ($flags) {
            return $this->get($match[1], $flags);
        }, $value);
    }

    /**
     * Turns relative paths in absolute paths without realpath()
     *
     * Since the dirs might not exist yet we can not call realpath or it will fail.
     */
    private function realpath(string $path): string
    {
        if (Preg::isMatch('{^(?:/|[a-z]:|[a-z0-9.]+://|\\\\\\\\)}i', $path)) {
            return $path;
        }

        return $this->baseDir !== null ? $this->baseDir . '/' . $path : $path;
    }

    /**
     * Reads the value of a Composer environment variable
     *
     * This should be used to read COMPOSER_ environment variables
     * that overload config values.
     *
     * @param non-empty-string $var
     *
     * @return string|false
     */
    private function getComposerEnv(string $var)
    {
        if ($this->useEnvironment) {
            return Platform::getEnv($var);
        }

        return false;
    }

    private function disableRepoByName(string $name): void
    {
        if (isset($this->repositories[$name])) {
            unset($this->repositories[$name]);
        } elseif ($name === 'packagist') { // BC support for default "packagist" named repo
            unset($this->repositories['packagist.org']);
        }
    }

    /**
     * Validates that the passed URL is allowed to be used by current config, or throws an exception.
     *
     * @param mixed[]     $repoOptions
     */
    public function prohibitUrlByConfig(string $url, ?IOInterface $io = null, array $repoOptions = []): void
    {
        // Return right away if the URL is malformed or custom (see issue #5173), but only for non-HTTP(S) URLs
        if (false === filter_var($url, FILTER_VALIDATE_URL) && !Preg::isMatch('{^https?://}', $url)) {
            return;
        }

        // Extract scheme and throw exception on known insecure protocols
        $scheme = parse_url($url, PHP_URL_SCHEME);
        $hostname = parse_url($url, PHP_URL_HOST);
        if (in_array($scheme, ['http', 'git', 'ftp', 'svn'])) {
            if ($this->get('secure-http')) {
                if ($scheme === 'svn') {
                    if (in_array($hostname, $this->get('secure-svn-domains'), true)) {
                        return;
                    }

                    throw new TransportException("Your configuration does not allow connections to $url. See https://getcomposer.org/doc/06-config.md#secure-svn-domains for details.");
                }

                throw new TransportException("Your configuration does not allow connections to $url. See https://getcomposer.org/doc/06-config.md#secure-http for details.");
            }
            if ($io !== null) {
                if (is_string($hostname)) {
                    if (!isset($this->warnedHosts[$hostname])) {
                        $io->writeError("<warning>Warning: Accessing $hostname over $scheme which is an insecure protocol.</warning>");
                    }
                    $this->warnedHosts[$hostname] = true;
                }
            }
        }

        if ($io !== null && is_string($hostname) && !isset($this->sslVerifyWarnedHosts[$hostname])) {
            $warning = null;
            if (isset($repoOptions['ssl']['verify_peer']) && !(bool) $repoOptions['ssl']['verify_peer']) {
                $warning = 'verify_peer';
            }

            if (isset($repoOptions['ssl']['verify_peer_name']) && !(bool) $repoOptions['ssl']['verify_peer_name']) {
                $warning = $warning === null ? 'verify_peer_name' : $warning . ' and verify_peer_name';
            }

            if ($warning !== null) {
                $io->writeError("<warning>Warning: Accessing $hostname with $warning disabled.</warning>");
                $this->sslVerifyWarnedHosts[$hostname] = true;
            }
        }
    }

    /**
     * Used by long-running custom scripts in composer.json
     *
     * "scripts": {
     *   "watch": [
     *     "Composer\\Config::disableProcessTimeout",
     *     "vendor/bin/long-running-script --watch"
     *   ]
     * }
     */
    public static function disableProcessTimeout(): void
    {
        // Override global timeout set earlier by environment or config
        ProcessExecutor::setTimeout(0);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer;

use Composer\Config\JsonConfigSource;
use Composer\Json\JsonFile;
use Composer\IO\IOInterface;
use Composer\Package\Archiver;
use Composer\Package\Version\VersionGuesser;
use Composer\Package\RootPackageInterface;
use Composer\Repository\FilesystemRepository;
use Composer\Repository\RepositoryManager;
use Composer\Repository\RepositoryFactory;
use Composer\Util\Filesystem;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Util\HttpDownloader;
use Composer\Util\Loop;
use Composer\Util\Silencer;
use Composer\Plugin\PluginEvents;
use Composer\EventDispatcher\Event;
use Phar;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Output\ConsoleOutput;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Autoload\AutoloadGenerator;
use Composer\Package\Version\VersionParser;
use Composer\Downloader\TransportException;
use Composer\Json\JsonValidationException;
use Composer\Repository\InstalledRepositoryInterface;
use UnexpectedValueException;
use ZipArchive;

/**
 * Creates a configured instance of composer.
 *
 * @author Ryan Weaver <ryan@knplabs.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Igor Wiedler <igor@wiedler.ch>
 * @author Nils Adermann <naderman@naderman.de>
 */
class Factory
{
    /**
     * @throws \RuntimeException
     */
    protected static function getHomeDir(): string
    {
        $home = Platform::getEnv('COMPOSER_HOME');
        if ($home) {
            return $home;
        }

        if (Platform::isWindows()) {
            if (!Platform::getEnv('APPDATA')) {
                throw new \RuntimeException('The APPDATA or COMPOSER_HOME environment variable must be set for composer to run correctly');
            }

            return rtrim(strtr(Platform::getEnv('APPDATA'), '\\', '/'), '/') . '/Composer';
        }

        $userDir = self::getUserDir();
        $dirs = [];

        if (self::useXdg()) {
            // XDG Base Directory Specifications
            $xdgConfig = Platform::getEnv('XDG_CONFIG_HOME');
            if (!$xdgConfig) {
                $xdgConfig = $userDir . '/.config';
            }

            $dirs[] = $xdgConfig . '/composer';
        }

        $dirs[] = $userDir . '/.composer';

        // select first dir which exists of: $XDG_CONFIG_HOME/composer or ~/.composer
        foreach ($dirs as $dir) {
            if (Silencer::call('is_dir', $dir)) {
                return $dir;
            }
        }

        // if none exists, we default to first defined one (XDG one if system uses it, or ~/.composer otherwise)
        return $dirs[0];
    }

    protected static function getCacheDir(string $home): string
    {
        $cacheDir = Platform::getEnv('COMPOSER_CACHE_DIR');
        if ($cacheDir) {
            return $cacheDir;
        }

        $homeEnv = Platform::getEnv('COMPOSER_HOME');
        if ($homeEnv) {
            return $homeEnv . '/cache';
        }

        if (Platform::isWindows()) {
            if ($cacheDir = Platform::getEnv('LOCALAPPDATA')) {
                $cacheDir .= '/Composer';
            } else {
                $cacheDir = $home . '/cache';
            }

            return rtrim(strtr($cacheDir, '\\', '/'), '/');
        }

        $userDir = self::getUserDir();
        if (PHP_OS === 'Darwin') {
            // Migrate existing cache dir in old location if present
            if (is_dir($home . '/cache') && !is_dir($userDir . '/Library/Caches/composer')) {
                Silencer::call('rename', $home . '/cache', $userDir . '/Library/Caches/composer');
            }

            return $userDir . '/Library/Caches/composer';
        }

        if ($home === $userDir . '/.composer' && is_dir($home . '/cache')) {
            return $home . '/cache';
        }

        if (self::useXdg()) {
            $xdgCache = Platform::getEnv('XDG_CACHE_HOME') ?: $userDir . '/.cache';

            return $xdgCache . '/composer';
        }

        return $home . '/cache';
    }

    protected static function getDataDir(string $home): string
    {
        $homeEnv = Platform::getEnv('COMPOSER_HOME');
        if ($homeEnv) {
            return $homeEnv;
        }

        if (Platform::isWindows()) {
            return strtr($home, '\\', '/');
        }

        $userDir = self::getUserDir();
        if ($home !== $userDir . '/.composer' && self::useXdg()) {
            $xdgData = Platform::getEnv('XDG_DATA_HOME') ?: $userDir . '/.local/share';

            return $xdgData . '/composer';
        }

        return $home;
    }

    public static function createConfig(?IOInterface $io = null, ?string $cwd = null): Config
    {
        $cwd = $cwd ?? Platform::getCwd(true);

        $config = new Config(true, $cwd);

        // determine and add main dirs to the config
        $home = self::getHomeDir();
        $config->merge([
            'config' => [
                'home' => $home,
                'cache-dir' => self::getCacheDir($home),
                'data-dir' => self::getDataDir($home),
            ],
        ], Config::SOURCE_DEFAULT);

        // load global config
        $file = new JsonFile($config->get('home').'/config.json');
        if ($file->exists()) {
            if ($io instanceof IOInterface) {
                $io->writeError('Loading config file ' . $file->getPath(), true, IOInterface::DEBUG);
            }
            self::validateJsonSchema($io, $file);
            $config->merge($file->read(), $file->getPath());
        }
        $config->setConfigSource(new JsonConfigSource($file));

        $htaccessProtect = $config->get('htaccess-protect');
        if ($htaccessProtect) {
            // Protect directory against web access. Since HOME could be
            // the www-data's user home and be web-accessible it is a
            // potential security risk
            $dirs = [$config->get('home'), $config->get('cache-dir'), $config->get('data-dir')];
            foreach ($dirs as $dir) {
                if (!file_exists($dir . '/.htaccess')) {
                    if (!is_dir($dir)) {
                        Silencer::call('mkdir', $dir, 0777, true);
                    }
                    Silencer::call('file_put_contents', $dir . '/.htaccess', 'Deny from all');
                }
            }
        }

        // load global auth file
        $file = new JsonFile($config->get('home').'/auth.json');
        if ($file->exists()) {
            if ($io instanceof IOInterface) {
                $io->writeError('Loading config file ' . $file->getPath(), true, IOInterface::DEBUG);
            }
            self::validateJsonSchema($io, $file, JsonFile::AUTH_SCHEMA);
            $config->merge(['config' => $file->read()], $file->getPath());
        }
        $config->setAuthConfigSource(new JsonConfigSource($file, true));

        self::loadComposerAuthEnv($config, $io);

        return $config;
    }

    public static function getComposerFile(): string
    {
        $env = Platform::getEnv('COMPOSER');
        if (is_string($env)) {
            $env = trim($env);
            if ('' !== $env) {
                if (is_dir($env)) {
                    throw new \RuntimeException('The COMPOSER environment variable is set to '.$env.' which is a directory, this variable should point to a composer.json or be left unset.');
                }

                return $env;
            }
        }

        return './composer.json';
    }

    public static function getLockFile(string $composerFile): string
    {
        return "json" === pathinfo($composerFile, PATHINFO_EXTENSION)
                ? substr($composerFile, 0, -4).'lock'
                : $composerFile . '.lock';
    }

    /**
     * @return array{highlight: OutputFormatterStyle, warning: OutputFormatterStyle}
     */
    public static function createAdditionalStyles(): array
    {
        return [
            'highlight' => new OutputFormatterStyle('red'),
            'warning' => new OutputFormatterStyle('black', 'yellow'),
        ];
    }

    public static function createOutput(): ConsoleOutput
    {
        $styles = self::createAdditionalStyles();
        $formatter = new OutputFormatter(false, $styles);

        return new ConsoleOutput(ConsoleOutput::VERBOSITY_NORMAL, null, $formatter);
    }

    /**
     * Creates a Composer instance
     *
     * @param  IOInterface                       $io             IO instance
     * @param  array<string, mixed>|string|null  $localConfig    either a configuration array or a filename to read from, if null it will
     *                                                           read from the default filename
     * @param  bool|'local'|'global'             $disablePlugins Whether plugins should not be loaded, can be set to local or global to only disable local/global plugins
     * @param  bool                              $disableScripts Whether scripts should not be run
     * @param  bool                              $fullLoad       Whether to initialize everything or only main project stuff (used when loading the global composer)
     * @throws \InvalidArgumentException
     * @throws UnexpectedValueException
     * @return Composer|PartialComposer Composer if $fullLoad is true, otherwise PartialComposer
     * @phpstan-return ($fullLoad is true ? Composer : PartialComposer)
     */
    public function createComposer(IOInterface $io, $localConfig = null, $disablePlugins = false, ?string $cwd = null, bool $fullLoad = true, bool $disableScripts = false)
    {
        // if a custom composer.json path is given, we change the default cwd to be that file's directory
        if (is_string($localConfig) && is_file($localConfig) && null === $cwd) {
            $cwd = dirname($localConfig);
        }

        $cwd = $cwd ?? Platform::getCwd(true);

        // load Composer configuration
        if (null === $localConfig) {
            $localConfig = static::getComposerFile();
        }

        $localConfigSource = Config::SOURCE_UNKNOWN;
        if (is_string($localConfig)) {
            $composerFile = $localConfig;

            $file = new JsonFile($localConfig, null, $io);

            if (!$file->exists()) {
                if ($localConfig === './composer.json' || $localConfig === 'composer.json') {
                    $message = 'Composer could not find a composer.json file in '.$cwd;
                } else {
                    $message = 'Composer could not find the config file: '.$localConfig;
                }
                $instructions = $fullLoad ? 'To initialize a project, please create a composer.json file. See https://getcomposer.org/basic-usage' : '';
                throw new \InvalidArgumentException($message.PHP_EOL.$instructions);
            }

            if (!Platform::isInputCompletionProcess()) {
                try {
                    $file->validateSchema(JsonFile::LAX_SCHEMA);
                } catch (JsonValidationException $e) {
                    $errors = ' - ' . implode(PHP_EOL . ' - ', $e->getErrors());
                    $message = $e->getMessage() . ':' . PHP_EOL . $errors;
                    throw new JsonValidationException($message);
                }
            }

            $localConfig = $file->read();
            $localConfigSource = $file->getPath();
        }

        // Load config and override with local config/auth config
        $config = static::createConfig($io, $cwd);
        $isGlobal = $localConfigSource !== Config::SOURCE_UNKNOWN && realpath($config->get('home')) === realpath(dirname($localConfigSource));
        $config->merge($localConfig, $localConfigSource);

        if (isset($composerFile)) {
            $io->writeError('Loading config file ' . $composerFile .' ('.realpath($composerFile).')', true, IOInterface::DEBUG);
            $config->setConfigSource(new JsonConfigSource(new JsonFile(realpath($composerFile), null, $io)));

            $localAuthFile = new JsonFile(dirname(realpath($composerFile)) . '/auth.json', null, $io);
            if ($localAuthFile->exists()) {
                $io->writeError('Loading config file ' . $localAuthFile->getPath(), true, IOInterface::DEBUG);
                self::validateJsonSchema($io, $localAuthFile, JsonFile::AUTH_SCHEMA);
                $config->merge(['config' => $localAuthFile->read()], $localAuthFile->getPath());
                $config->setLocalAuthConfigSource(new JsonConfigSource($localAuthFile, true));
            }
        }

        // make sure we load the auth env again over the local auth.json + composer.json config
        self::loadComposerAuthEnv($config, $io);

        $vendorDir = $config->get('vendor-dir');

        // initialize composer
        $composer = $fullLoad ? new Composer() : new PartialComposer();
        $composer->setConfig($config);
        if ($isGlobal) {
            $composer->setGlobal();
        }

        if ($fullLoad) {
            // load auth configs into the IO instance
            $io->loadConfiguration($config);

            // load existing Composer\InstalledVersions instance if available and scripts/plugins are allowed, as they might need it
            // we only load if the InstalledVersions class wasn't defined yet so that this is only loaded once
            if (false === $disablePlugins && false === $disableScripts && !class_exists('Composer\InstalledVersions', false) && file_exists($installedVersionsPath = $config->get('vendor-dir').'/composer/installed.php')) {
                // force loading the class at this point so it is loaded from the composer phar and not from the vendor dir
                // as we cannot guarantee integrity of that file
                if (class_exists('Composer\InstalledVersions')) {
                    FilesystemRepository::safelyLoadInstalledVersions($installedVersionsPath);
                }
            }
        }

        $httpDownloader = self::createHttpDownloader($io, $config);
        $process = new ProcessExecutor($io);
        $loop = new Loop($httpDownloader, $process);
        $composer->setLoop($loop);

        // initialize event dispatcher
        $dispatcher = new EventDispatcher($composer, $io, $process);
        $dispatcher->setRunScripts(!$disableScripts);
        $composer->setEventDispatcher($dispatcher);

        // initialize repository manager
        $rm = RepositoryFactory::manager($io, $config, $httpDownloader, $dispatcher, $process);
        $composer->setRepositoryManager($rm);

        // force-set the version of the global package if not defined as
        // guessing it adds no value and only takes time
        if (!$fullLoad && !isset($localConfig['version'])) {
            $localConfig['version'] = '1.0.0';
        }

        // load package
        $parser = new VersionParser;
        $guesser = new VersionGuesser($config, $process, $parser, $io);
        $loader = $this->loadRootPackage($rm, $config, $parser, $guesser, $io);
        $package = $loader->load($localConfig, 'Composer\Package\RootPackage', $cwd);
        $composer->setPackage($package);

        // load local repository
        $this->addLocalRepository($io, $rm, $vendorDir, $package, $process);

        // initialize installation manager
        $im = $this->createInstallationManager($loop, $io, $dispatcher);
        $composer->setInstallationManager($im);

        if ($composer instanceof Composer) {
            // initialize download manager
            $dm = $this->createDownloadManager($io, $config, $httpDownloader, $process, $dispatcher);
            $composer->setDownloadManager($dm);

            // initialize autoload generator
            $generator = new AutoloadGenerator($dispatcher, $io);
            $composer->setAutoloadGenerator($generator);

            // initialize archive manager
            $am = $this->createArchiveManager($config, $dm, $loop);
            $composer->setArchiveManager($am);
        }

        // add installers to the manager (must happen after download manager is created since they read it out of $composer)
        $this->createDefaultInstallers($im, $composer, $io, $process);

        // init locker if possible
        if ($composer instanceof Composer && isset($composerFile)) {
            $lockFile = self::getLockFile($composerFile);
            if (!$config->get('lock') && file_exists($lockFile)) {
                $io->writeError('<warning>'.$lockFile.' is present but ignored as the "lock" config option is disabled.</warning>');
            }

            $locker = new Package\Locker($io, new JsonFile($config->get('lock') ? $lockFile : Platform::getDevNull(), null, $io), $im, file_get_contents($composerFile), $process);
            $composer->setLocker($locker);
        } elseif ($composer instanceof Composer) {
            $locker = new Package\Locker($io, new JsonFile(Platform::getDevNull(), null, $io), $im, JsonFile::encode($localConfig), $process);
            $composer->setLocker($locker);
        }

        if ($composer instanceof Composer) {
            $globalComposer = null;
            if (!$composer->isGlobal()) {
                $globalComposer = $this->createGlobalComposer($io, $config, $disablePlugins, $disableScripts);
            }

            $pm = $this->createPluginManager($io, $composer, $globalComposer, $disablePlugins);
            $composer->setPluginManager($pm);

            if ($composer->isGlobal()) {
                $pm->setRunningInGlobalDir(true);
            }

            $pm->loadInstalledPlugins();
        }

        if ($fullLoad) {
            $initEvent = new Event(PluginEvents::INIT);
            $composer->getEventDispatcher()->dispatch($initEvent->getName(), $initEvent);

            // once everything is initialized we can
            // purge packages from local repos if they have been deleted on the filesystem
            $this->purgePackages($rm->getLocalRepository(), $im);
        }

        return $composer;
    }

    /**
     * @param  bool          $disablePlugins Whether plugins should not be loaded
     * @param  bool          $disableScripts Whether scripts should not be executed
     */
    public static function createGlobal(IOInterface $io, bool $disablePlugins = false, bool $disableScripts = false): ?Composer
    {
        $factory = new static();

        return $factory->createGlobalComposer($io, static::createConfig($io), $disablePlugins, $disableScripts, true);
    }

    protected function addLocalRepository(IOInterface $io, RepositoryManager $rm, string $vendorDir, RootPackageInterface $rootPackage, ?ProcessExecutor $process = null): void
    {
        $fs = null;
        if ($process) {
            $fs = new Filesystem($process);
        }

        $rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/composer/installed.json', null, $io), true, $rootPackage, $fs));
    }

    /**
     * @param bool|'local'|'global' $disablePlugins Whether plugins should not be loaded, can be set to local or global to only disable local/global plugins
     * @return PartialComposer|Composer|null By default PartialComposer, but Composer if $fullLoad is set to true
     * @phpstan-return ($fullLoad is true ? Composer|null : PartialComposer|null)
     */
    protected function createGlobalComposer(IOInterface $io, Config $config, $disablePlugins, bool $disableScripts, bool $fullLoad = false): ?PartialComposer
    {
        // make sure if disable plugins was 'local' it is now turned off
        $disablePlugins = $disablePlugins === 'global' || $disablePlugins === true;

        $composer = null;
        try {
            $composer = $this->createComposer($io, $config->get('home') . '/composer.json', $disablePlugins, $config->get('home'), $fullLoad, $disableScripts);
        } catch (\Exception $e) {
            $io->writeError('Failed to initialize global composer: '.$e->getMessage(), true, IOInterface::DEBUG);
        }

        return $composer;
    }

    public function createDownloadManager(IOInterface $io, Config $config, HttpDownloader $httpDownloader, ProcessExecutor $process, ?EventDispatcher $eventDispatcher = null): Downloader\DownloadManager
    {
        $cache = null;
        if ($config->get('cache-files-ttl') > 0) {
            $cache = new Cache($io, $config->get('cache-files-dir'), 'a-z0-9_./');
            $cache->setReadOnly($config->get('cache-read-only'));
        }

        $fs = new Filesystem($process);

        $dm = new Downloader\DownloadManager($io, false, $fs);
        switch ($preferred = $config->get('preferred-install')) {
            case 'dist':
                $dm->setPreferDist(true);
                break;
            case 'source':
                $dm->setPreferSource(true);
                break;
            case 'auto':
            default:
                // noop
                break;
        }

        if (is_array($preferred)) {
            $dm->setPreferences($preferred);
        }

        $dm->setDownloader('git', new Downloader\GitDownloader($io, $config, $process, $fs));
        $dm->setDownloader('svn', new Downloader\SvnDownloader($io, $config, $process, $fs));
        $dm->setDownloader('fossil', new Downloader\FossilDownloader($io, $config, $process, $fs));
        $dm->setDownloader('hg', new Downloader\HgDownloader($io, $config, $process, $fs));
        $dm->setDownloader('perforce', new Downloader\PerforceDownloader($io, $config, $process, $fs));
        $dm->setDownloader('zip', new Downloader\ZipDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process));
        $dm->setDownloader('rar', new Downloader\RarDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process));
        $dm->setDownloader('tar', new Downloader\TarDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process));
        $dm->setDownloader('gzip', new Downloader\GzipDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process));
        $dm->setDownloader('xz', new Downloader\XzDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process));
        $dm->setDownloader('phar', new Downloader\PharDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process));
        $dm->setDownloader('file', new Downloader\FileDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process));
        $dm->setDownloader('path', new Downloader\PathDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process));

        return $dm;
    }

    /**
     * @param  Config                     $config The configuration
     * @param  Downloader\DownloadManager $dm     Manager use to download sources
     * @return Archiver\ArchiveManager
     */
    public function createArchiveManager(Config $config, Downloader\DownloadManager $dm, Loop $loop)
    {
        $am = new Archiver\ArchiveManager($dm, $loop);
        if (class_exists(ZipArchive::class)) {
            $am->addArchiver(new Archiver\ZipArchiver);
        }
        if (class_exists(Phar::class)) {
            $am->addArchiver(new Archiver\PharArchiver);
        }

        return $am;
    }

    /**
     * @param  bool|'local'|'global' $disablePlugins Whether plugins should not be loaded, can be set to local or global to only disable local/global plugins
     */
    protected function createPluginManager(IOInterface $io, Composer $composer, ?PartialComposer $globalComposer = null, $disablePlugins = false): Plugin\PluginManager
    {
        return new Plugin\PluginManager($io, $composer, $globalComposer, $disablePlugins);
    }

    public function createInstallationManager(Loop $loop, IOInterface $io, ?EventDispatcher $eventDispatcher = null): Installer\InstallationManager
    {
        return new Installer\InstallationManager($loop, $io, $eventDispatcher);
    }

    protected function createDefaultInstallers(Installer\InstallationManager $im, PartialComposer $composer, IOInterface $io, ?ProcessExecutor $process = null): void
    {
        $fs = new Filesystem($process);
        $binaryInstaller = new Installer\BinaryInstaller($io, rtrim($composer->getConfig()->get('bin-dir'), '/'), $composer->getConfig()->get('bin-compat'), $fs, rtrim($composer->getConfig()->get('vendor-dir'), '/'));

        $im->addInstaller(new Installer\LibraryInstaller($io, $composer, null, $fs, $binaryInstaller));
        $im->addInstaller(new Installer\PluginInstaller($io, $composer, $fs, $binaryInstaller));
        $im->addInstaller(new Installer\MetapackageInstaller($io));
    }

    /**
     * @param InstalledRepositoryInterface   $repo repository to purge packages from
     * @param Installer\InstallationManager  $im   manager to check whether packages are still installed
     */
    protected function purgePackages(InstalledRepositoryInterface $repo, Installer\InstallationManager $im): void
    {
        foreach ($repo->getPackages() as $package) {
            if (!$im->isPackageInstalled($repo, $package)) {
                $repo->removePackage($package);
            }
        }
    }

    protected function loadRootPackage(RepositoryManager $rm, Config $config, VersionParser $parser, VersionGuesser $guesser, IOInterface $io): Package\Loader\RootPackageLoader
    {
        return new Package\Loader\RootPackageLoader($rm, $config, $parser, $guesser, $io);
    }

    /**
     * @param  IOInterface $io             IO instance
     * @param  mixed       $config         either a configuration array or a filename to read from, if null it will read from
     *                                     the default filename
     * @param  bool|'local'|'global' $disablePlugins Whether plugins should not be loaded, can be set to local or global to only disable local/global plugins
     * @param  bool        $disableScripts Whether scripts should not be run
     */
    public static function create(IOInterface $io, $config = null, $disablePlugins = false, bool $disableScripts = false): Composer
    {
        $factory = new static();

        // for BC reasons, if a config is passed in either as array or a path that is not the default composer.json path
        // we disable local plugins as they really should not be loaded from CWD
        // If you want to avoid this behavior, you should be calling createComposer directly with a $cwd arg set correctly
        // to the path where the composer.json being loaded resides
        if ($config !== null && $config !== self::getComposerFile() && $disablePlugins === false) {
            $disablePlugins = 'local';
        }

        return $factory->createComposer($io, $config, $disablePlugins, null, true, $disableScripts);
    }

    /**
     * If you are calling this in a plugin, you probably should instead use $composer->getLoop()->getHttpDownloader()
     *
     * @param  IOInterface    $io      IO instance
     * @param  Config         $config  Config instance
     * @param  mixed[]        $options Array of options passed directly to HttpDownloader constructor
     */
    public static function createHttpDownloader(IOInterface $io, Config $config, array $options = []): HttpDownloader
    {
        static $warned = false;
        $disableTls = false;
        // allow running the config command if disable-tls is in the arg list, even if openssl is missing, to allow disabling it via the config command
        if (isset($_SERVER['argv']) && in_array('disable-tls', $_SERVER['argv']) && (in_array('conf', $_SERVER['argv']) || in_array('config', $_SERVER['argv']))) {
            $warned = true;
            $disableTls = !extension_loaded('openssl');
        } elseif ($config->get('disable-tls') === true) {
            if (!$warned) {
                $io->writeError('<warning>You are running Composer with SSL/TLS protection disabled.</warning>');
            }
            $warned = true;
            $disableTls = true;
        } elseif (!extension_loaded('openssl')) {
            throw new Exception\NoSslException('The openssl extension is required for SSL/TLS protection but is not available. '
                . 'If you can not enable the openssl extension, you can disable this error, at your own risk, by setting the \'disable-tls\' option to true.');
        }
        $httpDownloaderOptions = [];
        if ($disableTls === false) {
            if ('' !== $config->get('cafile')) {
                $httpDownloaderOptions['ssl']['cafile'] = $config->get('cafile');
            }
            if ('' !== $config->get('capath')) {
                $httpDownloaderOptions['ssl']['capath'] = $config->get('capath');
            }
            $httpDownloaderOptions = array_replace_recursive($httpDownloaderOptions, $options);
        }
        try {
            $httpDownloader = new HttpDownloader($io, $config, $httpDownloaderOptions, $disableTls);
        } catch (TransportException $e) {
            if (false !== strpos($e->getMessage(), 'cafile')) {
                $io->write('<error>Unable to locate a valid CA certificate file. You must set a valid \'cafile\' option.</error>');
                $io->write('<error>A valid CA certificate file is required for SSL/TLS protection.</error>');
                $io->write('<error>You can disable this error, at your own risk, by setting the \'disable-tls\' option to true.</error>');
            }
            throw $e;
        }

        return $httpDownloader;
    }

    private static function loadComposerAuthEnv(Config $config, ?IOInterface $io): void
    {
        $composerAuthEnv = Platform::getEnv('COMPOSER_AUTH');
        if (false === $composerAuthEnv || '' === $composerAuthEnv) {
            return;
        }

        $authData = json_decode($composerAuthEnv);
        if (null === $authData) {
            throw new UnexpectedValueException('COMPOSER_AUTH environment variable is malformed, should be a valid JSON object');
        }

        if ($io instanceof IOInterface) {
            $io->writeError('Loading auth config from COMPOSER_AUTH', true, IOInterface::DEBUG);
        }
        self::validateJsonSchema($io, $authData, JsonFile::AUTH_SCHEMA, 'COMPOSER_AUTH');
        $authData = json_decode($composerAuthEnv, true);
        if (null !== $authData) {
            $config->merge(['config' => $authData], 'COMPOSER_AUTH');
        }
    }

    private static function useXdg(): bool
    {
        foreach (array_keys($_SERVER) as $key) {
            if (strpos((string) $key, 'XDG_') === 0) {
                return true;
            }
        }

        if (Silencer::call('is_dir', '/etc/xdg')) {
            return true;
        }

        return false;
    }

    /**
     * @throws \RuntimeException
     */
    private static function getUserDir(): string
    {
        $home = Platform::getEnv('HOME');
        if (!$home) {
            throw new \RuntimeException('The HOME or COMPOSER_HOME environment variable must be set for composer to run correctly');
        }

        return rtrim(strtr($home, '\\', '/'), '/');
    }

    /**
     * @param mixed $fileOrData
     * @param JsonFile::*_SCHEMA $schema
     */
    private static function validateJsonSchema(?IOInterface $io, $fileOrData, int $schema = JsonFile::LAX_SCHEMA, ?string $source = null): void
    {
        if (Platform::isInputCompletionProcess()) {
            return;
        }

        try {
            if ($fileOrData instanceof JsonFile) {
                $fileOrData->validateSchema($schema);
            } else {
                if (null === $source) {
                    throw new \InvalidArgumentException('$source is required to be provided if $fileOrData is arbitrary data');
                }
                JsonFile::validateJsonSchema($source, $fileOrData, $schema);
            }
        } catch (JsonValidationException $e) {
            $msg = $e->getMessage().', this may result in errors and should be resolved:'.PHP_EOL.' - '.implode(PHP_EOL.' - ', $e->getErrors());
            if ($io instanceof IOInterface) {
                $io->writeError('<warning>'.$msg.'</>');
            } else {
                throw new UnexpectedValueException($msg);
            }
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Json;

use Exception;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class JsonValidationException extends Exception
{
    /**
     * @var string[]
     */
    protected $errors;

    /**
     * @param string[] $errors
     */
    public function __construct(string $message, array $errors = [], ?Exception $previous = null)
    {
        $this->errors = $errors;
        parent::__construct((string) $message, 0, $previous);
    }

    /**
     * @return string[]
     */
    public function getErrors(): array
    {
        return $this->errors;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Json;

use Composer\Pcre\Preg;

/**
 * Formats json strings used for php < 5.4 because the json_encode doesn't
 * supports the flags JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE
 * in these versions
 *
 * @author Konstantin Kudryashiv <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 *
 * @deprecated Use json_encode or JsonFile::encode() with modern JSON_* flags to configure formatting - this class will be removed in 3.0
 */
class JsonFormatter
{
    /**
     * This code is based on the function found at:
     *  http://recursive-design.com/blog/2008/03/11/format-json-with-php/
     *
     * Originally licensed under MIT by Dave Perrett <mail@recursive-design.com>
     *
     * @param  bool   $unescapeUnicode Un escape unicode
     * @param  bool   $unescapeSlashes Un escape slashes
     */
    public static function format(string $json, bool $unescapeUnicode, bool $unescapeSlashes): string
    {
        $result = '';
        $pos = 0;
        $strLen = strlen($json);
        $indentStr = '    ';
        $newLine = "\n";
        $outOfQuotes = true;
        $buffer = '';
        $noescape = true;

        for ($i = 0; $i < $strLen; $i++) {
            // Grab the next character in the string
            $char = substr($json, $i, 1);

            // Are we inside a quoted string?
            if ('"' === $char && $noescape) {
                $outOfQuotes = !$outOfQuotes;
            }

            if (!$outOfQuotes) {
                $buffer .= $char;
                $noescape = '\\' === $char ? !$noescape : true;
                continue;
            }
            if ('' !== $buffer) {
                if ($unescapeSlashes) {
                    $buffer = str_replace('\\/', '/', $buffer);
                }

                if ($unescapeUnicode && function_exists('mb_convert_encoding')) {
                    // https://stackoverflow.com/questions/2934563/how-to-decode-unicode-escape-sequences-like-u00ed-to-proper-utf-8-encoded-cha
                    $buffer = Preg::replaceCallback('/(\\\\+)u([0-9a-f]{4})/i', static function ($match): string {
                        $l = strlen($match[1]);

                        if ($l % 2) {
                            $code = hexdec($match[2]);
                            // 0xD800..0xDFFF denotes UTF-16 surrogate pair which won't be unescaped
                            // see https://github.com/composer/composer/issues/7510
                            if (0xD800 <= $code && 0xDFFF >= $code) {
                                return $match[0];
                            }

                            return str_repeat('\\', $l - 1) . mb_convert_encoding(
                                pack('H*', $match[2]),
                                'UTF-8',
                                'UCS-2BE'
                            );
                        }

                        return $match[0];
                    }, $buffer);
                }

                $result .= $buffer.$char;
                $buffer = '';
                continue;
            }

            if (':' === $char) {
                // Add a space after the : character
                $char .= ' ';
            } elseif ('}' === $char || ']' === $char) {
                $pos--;
                $prevChar = substr($json, $i - 1, 1);

                if ('{' !== $prevChar && '[' !== $prevChar) {
                    // If this character is the end of an element,
                    // output a new line and indent the next line
                    $result .= $newLine;
                    $result .= str_repeat($indentStr, $pos);
                } else {
                    // Collapse empty {} and []
                    $result = rtrim($result);
                }
            }

            $result .= $char;

            // If the last character was the beginning of an element,
            // output a new line and indent the next line
            if (',' === $char || '{' === $char || '[' === $char) {
                $result .= $newLine;

                if ('{' === $char || '[' === $char) {
                    $pos++;
                }

                $result .= str_repeat($indentStr, $pos);
            }
        }

        return $result;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Json;

use Composer\Pcre\Preg;
use Composer\Repository\PlatformRepository;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class JsonManipulator
{
    /** @var string */
    private const DEFINES = '(?(DEFINE)
       (?<number>    -? (?= [1-9]|0(?!\d) ) \d++ (?:\.\d++)? (?:[eE] [+-]?+ \d++)? )
       (?<boolean>   true | false | null )
       (?<string>    " (?:[^"\\\\]*+ | \\\\ ["\\\\bfnrt\/] | \\\\ u [0-9A-Fa-f]{4} )* " )
       (?<array>     \[  (?:  (?&json) \s*+ (?: , (?&json) \s*+ )*+  )?+  \s*+ \] )
       (?<pair>      \s*+ (?&string) \s*+ : (?&json) \s*+ )
       (?<object>    \{  (?:  (?&pair)  (?: , (?&pair)  )*+  )?+  \s*+ \} )
       (?<json>      \s*+ (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) ) )
    )';

    /** @var string */
    private $contents;
    /** @var string */
    private $newline;
    /** @var string */
    private $indent;

    public function __construct(string $contents)
    {
        $contents = trim($contents);
        if ($contents === '') {
            $contents = '{}';
        }
        if (!Preg::isMatch('#^\{(.*)\}$#s', $contents)) {
            throw new \InvalidArgumentException('The json file must be an object ({})');
        }
        $this->newline = false !== strpos($contents, "\r\n") ? "\r\n" : "\n";
        $this->contents = $contents === '{}' ? '{' . $this->newline . '}' : $contents;
        $this->detectIndenting();
    }

    public function getContents(): string
    {
        return $this->contents . $this->newline;
    }

    public function addLink(string $type, string $package, string $constraint, bool $sortPackages = false): bool
    {
        $decoded = JsonFile::parseJson($this->contents);

        // no link of that type yet
        if (!isset($decoded[$type])) {
            return $this->addMainKey($type, [$package => $constraint]);
        }

        $regex = '{'.self::DEFINES.'^(?P<start>\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?)'.
            '(?P<property>'.preg_quote(JsonFile::encode($type)).'\s*:\s*)(?P<value>(?&json))(?P<end>.*)}sx';
        if (!Preg::isMatch($regex, $this->contents, $matches)) {
            return false;
        }
        assert(is_string($matches['start']));
        assert(is_string($matches['value']));
        assert(is_string($matches['end']));

        $links = $matches['value'];

        // try to find existing link
        $packageRegex = str_replace('/', '\\\\?/', preg_quote($package));
        $regex = '{'.self::DEFINES.'"(?P<package>'.$packageRegex.')"(\s*:\s*)(?&string)}ix';
        if (Preg::isMatch($regex, $links, $packageMatches)) {
            assert(is_string($packageMatches['package']));
            // update existing link
            $existingPackage = $packageMatches['package'];
            $packageRegex = str_replace('/', '\\\\?/', preg_quote($existingPackage));
            $links = Preg::replaceCallback('{'.self::DEFINES.'"'.$packageRegex.'"(?P<separator>\s*:\s*)(?&string)}ix', static function ($m) use ($existingPackage, $constraint): string {
                return JsonFile::encode(str_replace('\\/', '/', $existingPackage)) . $m['separator'] . '"' . $constraint . '"';
            }, $links);
        } else {
            if (Preg::isMatchStrictGroups('#^\s*\{\s*\S+.*?(\s*\}\s*)$#s', $links, $match)) {
                // link missing but non empty links
                $links = Preg::replace(
                    '{'.preg_quote($match[1]).'$}',
                    // addcslashes is used to double up backslashes/$ since preg_replace resolves them as back references otherwise, see #1588
                    addcslashes(',' . $this->newline . $this->indent . $this->indent . JsonFile::encode($package).': '.JsonFile::encode($constraint) . $match[1], '\\$'),
                    $links
                );
            } else {
                // links empty
                $links = '{' . $this->newline .
                    $this->indent . $this->indent . JsonFile::encode($package).': '.JsonFile::encode($constraint) . $this->newline .
                    $this->indent . '}';
            }
        }

        if (true === $sortPackages) {
            $requirements = json_decode($links, true);
            $this->sortPackages($requirements);
            $links = $this->format($requirements);
        }

        $this->contents = $matches['start'] . $matches['property'] . $links . $matches['end'];

        return true;
    }

    /**
     * Sorts packages by importance (platform packages first, then PHP dependencies) and alphabetically.
     *
     * @link https://getcomposer.org/doc/02-libraries.md#platform-packages
     *
     * @param array<string> $packages
     */
    private function sortPackages(array &$packages = []): void
    {
        $prefix = static function ($requirement): string {
            if (PlatformRepository::isPlatformPackage($requirement)) {
                return Preg::replace(
                    [
                        '/^php/',
                        '/^hhvm/',
                        '/^ext/',
                        '/^lib/',
                        '/^\D/',
                    ],
                    [
                        '0-$0',
                        '1-$0',
                        '2-$0',
                        '3-$0',
                        '4-$0',
                    ],
                    $requirement
                );
            }

            return '5-'.$requirement;
        };

        uksort($packages, static function ($a, $b) use ($prefix): int {
            return strnatcmp($prefix($a), $prefix($b));
        });
    }

    /**
     * @param array<string, mixed>|false $config
     */
    public function addRepository(string $name, $config, bool $append = true): bool
    {
        if ("" !== $name && !$this->doRemoveRepository($name)) {
            return false;
        }

        if (!$this->doConvertRepositoriesFromAssocToList()) {
            return false;
        }

        if (is_array($config) && !is_numeric($name) && '' !== $name) {
            $config = ['name' => $name] + $config;
        } elseif ($config === false) {
            $config = [$name => $config];
        }

        return $this->addListItem('repositories', $config, $append);
    }

    private function doConvertRepositoriesFromAssocToList(): bool
    {
        $decoded = json_decode($this->contents, false);

        if (($decoded->repositories ?? null) instanceof \stdClass) {
            // delete from bottom to top, to ensure keys stay the same
            $entriesToRevert = array_reverse(array_keys((array) $decoded->repositories));

            foreach ($entriesToRevert as $entryKey) {
                if (!$this->removeSubNode('repositories', (string) $entryKey)) {
                    return false;
                }
            }

            $this->changeEmptyMainKeyFromAssocToList('repositories');

            // re-add in order
            foreach (((array) $decoded->repositories) as $repositoryName => $repository) {
                if (!$repository instanceof \stdClass) {
                    if (!$this->addListItem('repositories', [$repositoryName => $repository], true)) {
                        return false;
                    }
                } elseif (is_numeric($repositoryName)) {
                    if (!$this->addListItem('repositories', $repository, true)) {
                        return false;
                    }
                } else {
                    $repository = (array) $repository;
                    // prepend name property
                    $repository = ['name' => $repositoryName] + $repository;
                    if (!$this->addListItem('repositories', $repository, true)) {
                        return false;
                    }
                }
            }
        }

        return true;
    }

    public function setRepositoryUrl(string $name, string $url): bool
    {
        $decoded = JsonFile::parseJson($this->contents);
        $repositoryIndex = null;

        foreach ($decoded['repositories'] ?? [] as $index => $repository) {
            if ($name === $index) {
                $repositoryIndex = $index;
                break;
            }

            if ($name === ($repository['name'] ?? null)) {
                $repositoryIndex = $index;
                break;
            }
        }

        if (null === $repositoryIndex) {
            return false;
        }

        $listRegex = null;

        if (is_int($repositoryIndex)) {
            $listRegex = '{'.self::DEFINES.'^(?P<start>\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?"repositories"\s*:\s*\[\s*((?&json)\s*+,\s*+){' . max(0, $repositoryIndex) . '})(?P<repository>(?&object))(?P<end>.*)}sx';
        }

        $objectRegex = '{'.self::DEFINES.'^(?P<start>\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?"repositories"\s*:\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?' . preg_quote(JsonFile::encode($repositoryIndex)) . '\s*:\s*)(?P<repository>(?&object))(?P<end>.*)}sx';
        $matches = null;

        if (($listRegex !== null && Preg::isMatch($listRegex, $this->contents, $matches)) || Preg::isMatch($objectRegex, $this->contents, $matches)) {
            assert(isset($matches['start']) && is_string($matches['start']));
            assert(isset($matches['repository']) && is_string($matches['repository']));
            assert(isset($matches['end']) && is_string($matches['end']));

            // invalid match due to un-regexable content, abort
            if (false === @json_decode($matches['repository'])) {
                return false;
            }

            $repositoryRegex = '{'.self::DEFINES.'^(?P<start>\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?"url"\s*:\s*)(?P<url>(?&string))(?P<end>.*)}sx';

            $this->contents = $matches['start'] . Preg::replaceCallback($repositoryRegex, static function (array $repositoryMatches) use ($url): string {
                return $repositoryMatches['start'] . JsonFile::encode($url) . $repositoryMatches['end'];
            }, $matches['repository']) . $matches['end'];

            return true;
        }

        return false;
    }

    /**
     * @param array<string, mixed>|false $config
     */
    public function insertRepository(string $name, $config, string $referenceName, int $offset = 0): bool
    {
        if ("" !== $name && !$this->doRemoveRepository($name)) {
            return false;
        }

        if (!$this->doConvertRepositoriesFromAssocToList()) {
            return false;
        }

        $indexToInsert = null;
        $decoded = JsonFile::parseJson($this->contents);

        foreach ($decoded['repositories'] as $repositoryIndex => $repository) {
            if (($repository['name'] ?? null) === $referenceName) {
                $indexToInsert = $repositoryIndex;
                break;
            }

            if ($repositoryIndex === $referenceName) {
                $indexToInsert = $repositoryIndex;
                break;
            }

            if ([$referenceName => false] === $repository) {
                $indexToInsert = $repositoryIndex;
                break;
            }
        }

        if ($indexToInsert === null) {
            return false;
        }

        if (is_array($config) && !is_numeric($name) && '' !== $name) {
            $config = ['name' => $name] + $config;
        } elseif ($config === false) {
            $config = ['name' => $config];
        }

        return $this->insertListItem('repositories', $config, $indexToInsert + $offset);
    }

    public function removeRepository(string $name): bool
    {
        return $this->doRemoveRepository($name) && $this->removeMainKeyIfEmpty('repositories');
    }

    private function doRemoveRepository(string $name): bool
    {
        $decoded = json_decode($this->contents, false);
        $isAssoc = ($decoded->repositories ?? null) instanceof \stdClass;

        foreach ((array) ($decoded->repositories ?? []) as $repositoryIndex => $repository) {
            if ($repositoryIndex === $name && $isAssoc) {
                if (!$this->removeSubNode('repositories', $repositoryIndex)) {
                    return false;
                }

                break;
            }

            if (($repository->name ?? null) === $name) {
                if ($isAssoc) {
                    if (!$this->removeSubNode('repositories', (string) $repositoryIndex)) {
                        return false;
                    }
                } else {
                    if (!$this->removeListItem('repositories', (int) $repositoryIndex)) {
                        return false;
                    }
                }

                break;
            }

            if ($isAssoc) {
                if ($name === $repositoryIndex && false === $repository) {
                    if (!$this->removeSubNode('repositories', $repositoryIndex)) {
                        return false;
                    }

                    return true;
                }
            } else {
                $repositoryAsArray = (array) $repository;

                if (false === ($repositoryAsArray[$name] ?? null) && 1 === count($repositoryAsArray)) {
                    if (!$this->removeListItem('repositories', (int) $repositoryIndex)) {
                        return false;
                    }

                    return true;
                }
            }
        }

        return true;
    }

    /**
     * @param mixed  $value
     */
    public function addConfigSetting(string $name, $value): bool
    {
        return $this->addSubNode('config', $name, $value);
    }

    public function removeConfigSetting(string $name): bool
    {
        return $this->removeSubNode('config', $name);
    }

    /**
     * @param mixed $value
     */
    public function addProperty(string $name, $value): bool
    {
        if (strpos($name, 'suggest.') === 0) {
            return $this->addSubNode('suggest', substr($name, 8), $value);
        }

        if (strpos($name, 'extra.') === 0) {
            return $this->addSubNode('extra', substr($name, 6), $value);
        }

        if (strpos($name, 'scripts.') === 0) {
            return $this->addSubNode('scripts', substr($name, 8), $value);
        }

        return $this->addMainKey($name, $value);
    }

    public function removeProperty(string $name): bool
    {
        if (strpos($name, 'suggest.') === 0) {
            return $this->removeSubNode('suggest', substr($name, 8));
        }

        if (strpos($name, 'extra.') === 0) {
            return $this->removeSubNode('extra', substr($name, 6));
        }

        if (strpos($name, 'scripts.') === 0) {
            return $this->removeSubNode('scripts', substr($name, 8));
        }

        if (strpos($name, 'autoload.') === 0) {
            return $this->removeSubNode('autoload', substr($name, 9));
        }

        if (strpos($name, 'autoload-dev.') === 0) {
            return $this->removeSubNode('autoload-dev', substr($name, 13));
        }

        return $this->removeMainKey($name);
    }

    /**
     * @param mixed  $value
     */
    public function addSubNode(string $mainNode, string $name, $value, bool $append = true): bool
    {
        $decoded = JsonFile::parseJson($this->contents);

        $subName = null;
        if (in_array($mainNode, ['config', 'extra', 'scripts']) && false !== strpos($name, '.')) {
            [$name, $subName] = explode('.', $name, 2);
        }

        // no main node yet
        if (!isset($decoded[$mainNode])) {
            if ($subName !== null) {
                $this->addMainKey($mainNode, [$name => [$subName => $value]]);
            } else {
                $this->addMainKey($mainNode, [$name => $value]);
            }

            return true;
        }

        // main node content not match-able
        $nodeRegex = '{'.self::DEFINES.'^(?P<start> \s* \{ \s* (?: (?&string) \s* : (?&json) \s* , \s* )*?'.
            preg_quote(JsonFile::encode($mainNode)).'\s*:\s*)(?P<content>(?&object))(?P<end>.*)}sx';

        try {
            if (!Preg::isMatch($nodeRegex, $this->contents, $match)) {
                return false;
            }
        } catch (\RuntimeException $e) {
            if ($e->getCode() === PREG_BACKTRACK_LIMIT_ERROR) {
                return false;
            }
            throw $e;
        }

        assert(is_string($match['start']));
        assert(is_string($match['content']));
        assert(is_string($match['end']));

        $children = $match['content'];
        // invalid match due to un-regexable content, abort
        if (!@json_decode($children)) {
            return false;
        }

        // child exists
        $childRegex = '{'.self::DEFINES.'(?P<start>"'.preg_quote($name).'"\s*:\s*)(?P<content>(?&json))(?P<end>,?)}x';
        if (Preg::isMatch($childRegex, $children, $matches)) {
            $children = Preg::replaceCallback($childRegex, function ($matches) use ($subName, $value): string {
                if ($subName !== null && is_string($matches['content'])) {
                    $curVal = json_decode($matches['content'], true);
                    if (!is_array($curVal)) {
                        $curVal = [];
                    }
                    $curVal[$subName] = $value;
                    $value = $curVal;
                }

                return $matches['start'] . $this->format($value, 1) . $matches['end'];
            }, $children);
        } elseif (Preg::isMatch('#^\{(?P<leadingspace>\s*?)(?P<content>\S+.*?)?(?P<trailingspace>\s*)\}$#s', $children, $match)) {
            $whitespace = $match['trailingspace'];
            if (null !== $match['content']) {
                if ($subName !== null) {
                    $value = [$subName => $value];
                }

                // child missing but non empty children
                if ($append) {
                    $children = Preg::replace(
                        '#'.$whitespace.'}$#',
                        addcslashes(',' . $this->newline . $this->indent . $this->indent . JsonFile::encode($name).': '.$this->format($value, 1) . $whitespace . '}', '\\$'),
                        $children
                    );
                } else {
                    $whitespace = $match['leadingspace'];
                    $children = Preg::replace(
                        '#^{'.$whitespace.'#',
                        addcslashes('{' . $whitespace . JsonFile::encode($name).': '.$this->format($value, 1) . ',' . $this->newline . $this->indent . $this->indent, '\\$'),
                        $children
                    );
                }
            } else {
                if ($subName !== null) {
                    $value = [$subName => $value];
                }

                // children present but empty
                $children = '{' . $this->newline . $this->indent . $this->indent . JsonFile::encode($name).': '.$this->format($value, 1) . $whitespace . '}';
            }
        } else {
            throw new \LogicException('Nothing matched above for: '.$children);
        }

        $this->contents = Preg::replaceCallback($nodeRegex, static function ($m) use ($children): string {
            return $m['start'] . $children . $m['end'];
        }, $this->contents);

        return true;
    }

    public function removeSubNode(string $mainNode, string $name): bool
    {
        $decoded = JsonFile::parseJson($this->contents);

        // no node or empty node
        if (empty($decoded[$mainNode])) {
            return true;
        }

        // no node content match-able
        $nodeRegex = '{'.self::DEFINES.'^(?P<start> \s* \{ \s* (?: (?&string) \s* : (?&json) \s* , \s* )*?'.
            preg_quote(JsonFile::encode($mainNode)).'\s*:\s*)(?P<content>(?&object))(?P<end>.*)}sx';
        try {
            if (!Preg::isMatch($nodeRegex, $this->contents, $match)) {
                return false;
            }
        } catch (\RuntimeException $e) {
            if ($e->getCode() === PREG_BACKTRACK_LIMIT_ERROR) {
                return false;
            }
            throw $e;
        }

        assert(is_string($match['start']));
        assert(is_string($match['content']));
        assert(is_string($match['end']));

        $children = $match['content'];

        // invalid match due to un-regexable content, abort
        if (!@json_decode($children, true)) {
            return false;
        }

        $subName = null;
        if (in_array($mainNode, ['config', 'extra', 'scripts']) && false !== strpos($name, '.')) {
            [$name, $subName] = explode('.', $name, 2);
        }

        // no node to remove
        if (!isset($decoded[$mainNode][$name]) || ($subName && !isset($decoded[$mainNode][$name][$subName]))) {
            return true;
        }

        // try and find a match for the subkey
        $keyRegex = str_replace('/', '\\\\?/', preg_quote($name));
        if (Preg::isMatch('{"'.$keyRegex.'"\s*:}i', $children)) {
            // find best match for the value of "name"
            if (Preg::isMatchAll('{'.self::DEFINES.'"'.$keyRegex.'"\s*:\s*(?:(?&json))}x', $children, $matches)) {
                $bestMatch = '';
                foreach ($matches[0] as $match) {
                    assert(is_string($match));
                    if (strlen($bestMatch) < strlen($match)) {
                        $bestMatch = $match;
                    }
                }
                $childrenClean = Preg::replace('{,\s*'.preg_quote($bestMatch).'}i', '', $children, -1, $count);
                if (1 !== $count) {
                    $childrenClean = Preg::replace('{'.preg_quote($bestMatch).'\s*,?\s*}i', '', $childrenClean, -1, $count);
                    if (1 !== $count) {
                        return false;
                    }
                }
            }
        } else {
            $childrenClean = $children;
        }

        if (!isset($childrenClean)) {
            throw new \InvalidArgumentException("JsonManipulator: \$childrenClean is not defined. Please report at https://github.com/composer/composer/issues/new.");
        }

        // no child data left, $name was the only key in
        unset($match);
        if (Preg::isMatch('#^\{\s*?(?P<content>\S+.*?)?(?P<trailingspace>\s*)\}$#s', $childrenClean, $match)) {
            if (null === $match['content']) {
                $newline = $this->newline;
                $indent = $this->indent;

                $this->contents = Preg::replaceCallback($nodeRegex, static function ($matches) use ($indent, $newline): string {
                    return $matches['start'] . '{' . $newline . $indent . '}' . $matches['end'];
                }, $this->contents);

                // we have a subname, so we restore the rest of $name
                if ($subName !== null) {
                    $curVal = json_decode($children, true);
                    unset($curVal[$name][$subName]);
                    if ($curVal[$name] === []) {
                        $curVal[$name] = new \ArrayObject();
                    }
                    $this->addSubNode($mainNode, $name, $curVal[$name]);
                }

                return true;
            }
        }

        $this->contents = Preg::replaceCallback($nodeRegex, function ($matches) use ($name, $subName, $childrenClean): string {
            assert(is_string($matches['content']));
            if ($subName !== null) {
                $curVal = json_decode($matches['content'], true);
                unset($curVal[$name][$subName]);
                if ($curVal[$name] === []) {
                    $curVal[$name] = new \ArrayObject();
                }
                $childrenClean = $this->format($curVal, 0, true);
            }

            return $matches['start'] . $childrenClean . $matches['end'];
        }, $this->contents);

        return true;
    }

    /**
     * @param mixed $value
     */
    public function addListItem(string $mainNode, $value, bool $append = true): bool
    {
        $decoded = JsonFile::parseJson($this->contents);

        // no main node yet
        if (!isset($decoded[$mainNode])) {
            if (!$this->addMainKey($mainNode, [])) {
                return false;
            }
        }

        // main node content not match-able
        $nodeRegex = '{'.self::DEFINES.'^(?P<start> \s* \{ \s* (?: (?&string) \s* : (?&json) \s* , \s* )*?'.
            preg_quote(JsonFile::encode($mainNode)).'\s*:\s*)(?P<content>(?&array))(?P<end>.*)}sx';

        try {
            if (!Preg::isMatch($nodeRegex, $this->contents, $match)) {
                return false;
            }
        } catch (\RuntimeException $e) {
            if ($e->getCode() === PREG_BACKTRACK_LIMIT_ERROR) {
                return false;
            }
            throw $e;
        }

        assert(is_string($match['start']));
        assert(is_string($match['content']));
        assert(is_string($match['end']));

        $children = $match['content'];
        // invalid match due to un-regexable content, abort
        if (false === @json_decode($children)) {
            return false;
        }

        if (Preg::isMatch('#^\[(?P<leadingspace>\s*?)(?P<content>\S+.*?)?(?P<trailingspace>\s*)\]$#s', $children, $match)) {
            $leadingWhitespace = $match['leadingspace'];
            $whitespace = $match['trailingspace'];
            $leadingItemWhitespace = $this->newline . $this->indent . $this->indent;
            $trailingItemWhitespace = $whitespace;
            $itemDepth = 1;

            // keep oneline lists as one line
            if (!str_contains($whitespace, $this->newline)) {
                $leadingItemWhitespace = $leadingWhitespace;
                $trailingItemWhitespace = $leadingWhitespace;
                $itemDepth = 0;
            }

            if (null !== $match['content']) {
                // child missing but non empty children
                if ($append) {
                    $children = Preg::replace(
                        '#'.$whitespace.']$#',
                        addcslashes(',' . $leadingItemWhitespace . $this->format($value, $itemDepth) . $trailingItemWhitespace . ']', '\\$'),
                        $children
                    );
                } else {
                    $whitespace = $match['leadingspace'];
                    $children = Preg::replace(
                        '#^\['.$whitespace.'#',
                        addcslashes('[' . $whitespace . $this->format($value, $itemDepth) . ',' . $leadingItemWhitespace, '\\$'),
                        $children
                    );
                }
            } else {
                // children present but empty
                $children = '[' . $leadingItemWhitespace . $this->format($value, $itemDepth) . $trailingItemWhitespace . ']';
            }
        } else {
            throw new \LogicException('Nothing matched above for: '.$children);
        }

        $this->contents = Preg::replaceCallback($nodeRegex, static function ($m) use ($children): string {
            return $m['start'] . $children . $m['end'];
        }, $this->contents);

        return true;
    }

    /**
     * @param mixed $value
     */
    public function insertListItem(string $mainNode, $value, int $index): bool
    {
        if ($index < 0) {
            throw new \InvalidArgumentException('Index can only be positive integer');
        }

        if ($index === 0) {
            return $this->addListItem($mainNode, $value, false);
        }

        $decoded = JsonFile::parseJson($this->contents);

        // no main node yet
        if (!isset($decoded[$mainNode])) {
            if (!$this->addMainKey($mainNode, [])) {
                return false;
            }
        }

        if (count($decoded[$mainNode]) === $index) {
            return $this->addListItem($mainNode, $value, true);
        }

        // main node content not match-able
        $nodeRegex = '{'.self::DEFINES.'^(?P<start> \s* \{ \s* (?: (?&string) \s* : (?&json) \s* , \s* )*?'.
            preg_quote(JsonFile::encode($mainNode)).'\s*:\s*)(?P<content>(?&array))(?P<end>.*)}sx';

        try {
            if (!Preg::isMatch($nodeRegex, $this->contents, $match)) {
                return false;
            }
        } catch (\RuntimeException $e) {
            if ($e->getCode() === PREG_BACKTRACK_LIMIT_ERROR) {
                return false;
            }
            throw $e;
        }

        assert(is_string($match['start']));
        assert(is_string($match['content']));
        assert(is_string($match['end']));

        $children = $match['content'];
        // invalid match due to un-regexable content, abort
        if (false === @json_decode($children)) {
            return false;
        }

        $listSkipToItemRegex = '{'.self::DEFINES.'^(?P<start>\[\s*((?&json)\s*+,\s*?){' . max(0, $index) . '})(?P<space_before_item>(\s*))(?P<end>.*)}sx';

        $children = Preg::replaceCallback($listSkipToItemRegex, function ($m) use ($value): string {
            return $m['start'] . $m['space_before_item'] . $this->format($value, 1) . ',' . $m['space_before_item'] . $m['end'];
        }, $children);

        $this->contents = Preg::replaceCallback($nodeRegex, static function ($m) use ($children): string {
            return $m['start'] . $children . $m['end'];
        }, $this->contents);

        return true;
    }

    public function removeListItem(string $mainNode, int $nodeIndex): bool
    {
        // invalid index, that cannot be removed anyway
        if ($nodeIndex < 0) {
            return true;
        }

        $decoded = JsonFile::parseJson($this->contents);

        // no node or empty node
        if ([] === $decoded[$mainNode]) {
            return true;
        }

        // no node content match-able
        $nodeRegex = '{'.self::DEFINES.'^(?P<start> \s* \{ \s* (?: (?&string) \s* : (?&json) \s* , \s* )*?'.
            preg_quote(JsonFile::encode($mainNode)).'\s*:\s*)(?P<content>(?&array))(?P<end>.*)}sx';
        try {
            if (!Preg::isMatch($nodeRegex, $this->contents, $match)) {
                return false;
            }
        } catch (\RuntimeException $e) {
            if ($e->getCode() === PREG_BACKTRACK_LIMIT_ERROR) {
                return false;
            }
            throw $e;
        }

        assert(is_string($match['start']));
        assert(is_string($match['content']));
        assert(is_string($match['end']));

        $children = $match['content'];

        // invalid match due to un-regexable content, abort
        if (false === @json_decode($children, true)) {
            return false;
        }

        // no node to remove
        if (!isset($decoded[$mainNode][$nodeIndex])) {
            return true;
        }

        $contentRegex = '(?&json)';

        if ($nodeIndex > 1) {
            $startRegex = '(?&json)\s*+(?:,(?&json)\s*+){' . ($nodeIndex - 1) . '}';
            // remove leading array separator in case we might remove the last
            $contentRegex = '\s*+,?\s*+' . $contentRegex;
            $endRegex = '(?:(\s*+,\s*+(?&json))*(?:\s*+(?&json))?)\s*+';
        } elseif ($nodeIndex > 0) {
            $startRegex = '(?&json)\s*+';
            // remove leading array separator in case we might remove the last
            $contentRegex = '\s*+,?\s*+' . $contentRegex;
            $endRegex = '(?:(\s*+,\s*+(?&json))*(?:\s*+(?&json))?)\s*+';
        } else {
            $startRegex = '\s*+';
            // remove trailing array separator when we delete first
            $contentRegex = $contentRegex . '\s*+,?\s*+';
            $endRegex = '(?:((?&json)\s*+,\s*+)*(?:\s*+(?&json))?)\s*+';
        }

        if (Preg::isMatch('{'.self::DEFINES.'(?P<start>\[' . $startRegex . ')(?P<content>' . $contentRegex . ')(?P<end>' . $endRegex . '\])}sx', $children, $childMatch)) {
            $this->contents = $match['start'] . $childMatch['start'] . $childMatch['end'] . $match['end'];

            return true;
        }

        return false;
    }

    /**
     * @param mixed  $content
     */
    public function addMainKey(string $key, $content): bool
    {
        $decoded = JsonFile::parseJson($this->contents);
        $content = $this->format($content);

        // key exists already
        $regex = '{'.self::DEFINES.'^(?P<start>\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?)'.
            '(?P<key>'.preg_quote(JsonFile::encode($key)).'\s*:\s*(?&json))(?P<end>.*)}sx';
        if (isset($decoded[$key]) && Preg::isMatch($regex, $this->contents, $matches)) {
            // invalid match due to un-regexable content, abort
            if (!@json_decode('{'.$matches['key'].'}')) {
                return false;
            }

            $this->contents = $matches['start'] . JsonFile::encode($key).': '.$content . $matches['end'];

            return true;
        }

        // append at the end of the file and keep whitespace
        if (Preg::isMatch('#[^{\s](\s*)\}$#', $this->contents, $match)) {
            $this->contents = Preg::replace(
                '#'.$match[1].'\}$#',
                addcslashes(',' . $this->newline . $this->indent . JsonFile::encode($key). ': '. $content . $this->newline . '}', '\\$'),
                $this->contents
            );

            return true;
        }

        // append at the end of the file
        $this->contents = Preg::replace(
            '#\}$#',
            addcslashes($this->indent . JsonFile::encode($key). ': '.$content . $this->newline . '}', '\\$'),
            $this->contents
        );

        return true;
    }

    public function removeMainKey(string $key): bool
    {
        $decoded = JsonFile::parseJson($this->contents);

        if (!array_key_exists($key, $decoded)) {
            return true;
        }

        // key exists already
        $regex = '{'.self::DEFINES.'^(?P<start>\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?)'.
            '(?P<removal>'.preg_quote(JsonFile::encode($key)).'\s*:\s*(?&json))\s*,?\s*(?P<end>.*)}sx';
        if (Preg::isMatch($regex, $this->contents, $matches)) {
            assert(is_string($matches['start']));
            assert(is_string($matches['removal']));
            assert(is_string($matches['end']));

            // invalid match due to un-regexable content, abort
            if (!@json_decode('{'.$matches['removal'].'}')) {
                return false;
            }

            // check that we are not leaving a dangling comma on the previous line if the last line was removed
            if (Preg::isMatchStrictGroups('#,\s*$#', $matches['start']) && Preg::isMatch('#^\}$#', $matches['end'])) {
                $matches['start'] = rtrim(Preg::replace('#,(\s*)$#', '$1', $matches['start']), $this->indent);
            }

            $this->contents = $matches['start'] . $matches['end'];
            if (Preg::isMatch('#^\{\s*\}\s*$#', $this->contents)) {
                $this->contents = "{\n}";
            }

            return true;
        }

        return false;
    }

    public function changeEmptyMainKeyFromAssocToList(string $key): bool
    {
        $decoded = JsonFile::parseJson($this->contents);

        if (!array_key_exists($key, $decoded)) {
            return true;
        }

        $regex = '{'.self::DEFINES.'^(?P<start>\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?'.preg_quote(JsonFile::encode($key)).'\s*:\s*)(?P<removal>\{(?P<removal_space>\s*+)\})(?P<end>\s*,?\s*.*)}sx';
        if (Preg::isMatch($regex, $this->contents, $matches)) {
            assert(is_string($matches['start']));
            assert(is_string($matches['removal']));
            assert(is_string($matches['end']));

            // invalid match due to un-regexable content, abort
            if (false === @json_decode($matches['removal'])) {
                return false;
            }

            $this->contents = $matches['start'] . '[' . $matches['removal_space'] . ']' . $matches['end'];

            return true;
        }

        return false;
    }

    public function removeMainKeyIfEmpty(string $key): bool
    {
        $decoded = JsonFile::parseJson($this->contents);

        if (!array_key_exists($key, $decoded)) {
            return true;
        }

        if (is_array($decoded[$key]) && count($decoded[$key]) === 0) {
            return $this->removeMainKey($key);
        }

        return true;
    }

    /**
     * @param mixed $data
     */
    public function format($data, int $depth = 0, bool $wasObject = false): string
    {
        if ($data instanceof \stdClass || $data instanceof \ArrayObject) {
            $data = (array) $data;
            $wasObject = true;
        }

        if (is_array($data)) {
            if (\count($data) === 0) {
                return $wasObject ? '{' . $this->newline . str_repeat($this->indent, $depth + 1) . '}' : '[]';
            }

            if (array_is_list($data)) {
                foreach ($data as $key => $val) {
                    $data[$key] = $this->format($val, $depth + 1);
                }

                return '['.implode(', ', $data).']';
            }

            $out = '{' . $this->newline;
            $elems = [];
            foreach ($data as $key => $val) {
                $elems[] = str_repeat($this->indent, $depth + 2) . JsonFile::encode((string) $key). ': '.$this->format($val, $depth + 1);
            }

            return $out . implode(','.$this->newline, $elems) . $this->newline . str_repeat($this->indent, $depth + 1) . '}';
        }

        return JsonFile::encode($data);
    }

    protected function detectIndenting(): void
    {
        $this->indent = JsonFile::detectIndenting($this->contents);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Json;

use Composer\Pcre\Preg;
use Composer\Util\Filesystem;
use JsonSchema\Validator;
use Seld\JsonLint\JsonParser;
use Seld\JsonLint\ParsingException;
use Composer\Util\HttpDownloader;
use Composer\IO\IOInterface;
use Composer\Downloader\TransportException;

/**
 * Reads/writes json files.
 *
 * @author Konstantin Kudryashiv <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class JsonFile
{
    public const LAX_SCHEMA = 1;
    public const STRICT_SCHEMA = 2;
    public const AUTH_SCHEMA = 3;
    public const LOCK_SCHEMA = 4;

    /** @deprecated Use \JSON_UNESCAPED_SLASHES */
    public const JSON_UNESCAPED_SLASHES = 64;
    /** @deprecated Use \JSON_PRETTY_PRINT */
    public const JSON_PRETTY_PRINT = 128;
    /** @deprecated Use \JSON_UNESCAPED_UNICODE */
    public const JSON_UNESCAPED_UNICODE = 256;

    public const COMPOSER_SCHEMA_PATH = __DIR__ . '/../../../res/composer-schema.json';
    public const LOCK_SCHEMA_PATH = __DIR__ . '/../../../res/composer-lock-schema.json';

    public const INDENT_DEFAULT = '    ';

    /** @var string */
    private $path;
    /** @var ?HttpDownloader */
    private $httpDownloader;
    /** @var ?IOInterface */
    private $io;
    /** @var string */
    private $indent = self::INDENT_DEFAULT;

    /**
     * Initializes json file reader/parser.
     *
     * @param  string                    $path           path to a lockfile
     * @param  ?HttpDownloader           $httpDownloader required for loading http/https json files
     * @throws \InvalidArgumentException
     */
    public function __construct(string $path, ?HttpDownloader $httpDownloader = null, ?IOInterface $io = null)
    {
        $this->path = $path;

        if (null === $httpDownloader && Preg::isMatch('{^https?://}i', $path)) {
            throw new \InvalidArgumentException('http urls require a HttpDownloader instance to be passed');
        }
        $this->httpDownloader = $httpDownloader;
        $this->io = $io;
    }

    public function getPath(): string
    {
        return $this->path;
    }

    /**
     * Checks whether json file exists.
     */
    public function exists(): bool
    {
        return is_file($this->path);
    }

    /**
     * Reads json file.
     *
     * @throws ParsingException
     * @throws \RuntimeException
     * @return mixed
     */
    public function read()
    {
        try {
            if ($this->httpDownloader) {
                $json = $this->httpDownloader->get($this->path)->getBody();
            } else {
                if (!Filesystem::isReadable($this->path)) {
                    throw new \RuntimeException('The file "'.$this->path.'" is not readable.');
                }
                if ($this->io && $this->io->isDebug()) {
                    $realpathInfo = '';
                    $realpath = realpath($this->path);
                    if (false !== $realpath && $realpath !== $this->path) {
                        $realpathInfo = ' (' . $realpath . ')';
                    }
                    $this->io->writeError('Reading ' . $this->path . $realpathInfo);
                }
                $json = file_get_contents($this->path);
            }
        } catch (TransportException $e) {
            throw new \RuntimeException($e->getMessage(), 0, $e);
        } catch (\Exception $e) {
            throw new \RuntimeException('Could not read '.$this->path."\n\n".$e->getMessage());
        }

        if ($json === false) {
            throw new \RuntimeException('Could not read '.$this->path);
        }

        $this->indent = self::detectIndenting($json);

        return static::parseJson($json, $this->path);
    }

    /**
     * Writes json file.
     *
     * @param  mixed[]                              $hash    writes hash into json file
     * @param  int                                  $options json_encode options
     * @throws \UnexpectedValueException|\Exception
     * @return void
     */
    public function write(array $hash, int $options = JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
    {
        if ($this->path === 'php://memory') {
            file_put_contents($this->path, static::encode($hash, $options, $this->indent));

            return;
        }

        $dir = dirname($this->path);
        if (!is_dir($dir)) {
            if (file_exists($dir)) {
                throw new \UnexpectedValueException(
                    realpath($dir).' exists and is not a directory.'
                );
            }
            if (!@mkdir($dir, 0777, true)) {
                throw new \UnexpectedValueException(
                    $dir.' does not exist and could not be created.'
                );
            }
        }

        $retries = 3;
        while ($retries--) {
            try {
                $this->filePutContentsIfModified($this->path, static::encode($hash, $options, $this->indent). ($options & JSON_PRETTY_PRINT ? "\n" : ''));
                break;
            } catch (\Exception $e) {
                if ($retries > 0) {
                    usleep(500000);
                    continue;
                }

                throw $e;
            }
        }
    }

    /**
     * Modify file properties only if content modified
     *
     * @return int|false
     */
    private function filePutContentsIfModified(string $path, string $content)
    {
        $currentContent = @file_get_contents($path);
        if (false === $currentContent || $currentContent !== $content) {
            return file_put_contents($path, $content);
        }

        return 0;
    }

    /**
     * Validates the schema of the current json file according to composer-schema.json rules
     *
     * @param  int                     $schema     a JsonFile::*_SCHEMA constant
     * @param  string|null             $schemaFile a path to the schema file
     * @throws JsonValidationException
     * @throws ParsingException
     * @return true                    true on success
     *
     * @phpstan-param self::*_SCHEMA $schema
     */
    public function validateSchema(int $schema = self::STRICT_SCHEMA, ?string $schemaFile = null): bool
    {
        if (!Filesystem::isReadable($this->path)) {
            throw new \RuntimeException('The file "'.$this->path.'" is not readable.');
        }
        $content = file_get_contents($this->path);
        $data = json_decode($content);

        if (null === $data && 'null' !== $content) {
            self::validateSyntax($content, $this->path);
        }

        return self::validateJsonSchema($this->path, $data, $schema, $schemaFile);
    }

    /**
     * Validates the schema of the current json file according to composer-schema.json rules
     *
     * @param  mixed                   $data       Decoded JSON data to validate
     * @param  int                     $schema     a JsonFile::*_SCHEMA constant
     * @param  string|null             $schemaFile a path to the schema file
     * @throws JsonValidationException
     * @return true                    true on success
     *
     * @phpstan-param self::*_SCHEMA $schema
     */
    public static function validateJsonSchema(string $source, $data, int $schema, ?string $schemaFile = null): bool
    {
        $isComposerSchemaFile = false;
        if (null === $schemaFile) {
            if ($schema === self::LOCK_SCHEMA) {
                $schemaFile = self::LOCK_SCHEMA_PATH;
            } else {
                $isComposerSchemaFile = true;
                $schemaFile = self::COMPOSER_SCHEMA_PATH;
            }
        }

        // Prepend with file:// only when not using a special schema already (e.g. in the phar)
        if (false === strpos($schemaFile, '://')) {
            $schemaFile = 'file://' . $schemaFile;
        }

        $schemaData = (object) ['$ref' => $schemaFile, '$schema' => "https://json-schema.org/draft-04/schema#"];

        if ($schema === self::STRICT_SCHEMA && $isComposerSchemaFile) {
            $schemaData = json_decode((string) file_get_contents($schemaFile));
            $schemaData->additionalProperties = false;
            $schemaData->required = ['name', 'description'];
        } elseif ($schema === self::AUTH_SCHEMA && $isComposerSchemaFile) {
            $schemaData = (object) ['$ref' => $schemaFile.'#/properties/config', '$schema' => "https://json-schema.org/draft-04/schema#"];
        }

        $validator = new Validator();
        // convert assoc arrays to objects
        $data = json_decode((string) json_encode($data));
        $validator->validate($data, $schemaData);

        if (!$validator->isValid()) {
            $errors = [];
            foreach ($validator->getErrors() as $error) {
                $errors[] = ($error['property'] ? $error['property'].' : ' : '').$error['message'];
            }
            throw new JsonValidationException('"'.$source.'" does not match the expected JSON schema', $errors);
        }

        return true;
    }

    /**
     * Encodes an array into (optionally pretty-printed) JSON
     *
     * @param  mixed  $data    Data to encode into a formatted JSON string
     * @param  int    $options json_encode options (defaults to JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
     * @param  string $indent  Indentation string
     * @return string Encoded json
     */
    public static function encode($data, int $options = \JSON_UNESCAPED_SLASHES | \JSON_PRETTY_PRINT | \JSON_UNESCAPED_UNICODE, string $indent = self::INDENT_DEFAULT): string
    {
        $json = json_encode($data, $options);

        if (false === $json) {
            self::throwEncodeError(json_last_error());
        }

        if (($options & JSON_PRETTY_PRINT) > 0 && $indent !== self::INDENT_DEFAULT) {
            // Pretty printing and not using default indentation
            return Preg::replaceCallback(
                '#^ {4,}#m',
                static function ($match) use ($indent): string {
                    return str_repeat($indent, (int) (strlen($match[0]) / 4));
                },
                $json
            );
        }

        return $json;
    }

    /**
     * Throws an exception according to a given code with a customized message
     *
     * @param  int               $code return code of json_last_error function
     * @throws \RuntimeException
     * @return never
     */
    private static function throwEncodeError(int $code): void
    {
        switch ($code) {
            case JSON_ERROR_DEPTH:
                $msg = 'Maximum stack depth exceeded';
                break;
            case JSON_ERROR_STATE_MISMATCH:
                $msg = 'Underflow or the modes mismatch';
                break;
            case JSON_ERROR_CTRL_CHAR:
                $msg = 'Unexpected control character found';
                break;
            case JSON_ERROR_UTF8:
                $msg = 'Malformed UTF-8 characters, possibly incorrectly encoded';
                break;
            default:
                $msg = 'Unknown error';
        }

        throw new \RuntimeException('JSON encoding failed: '.$msg);
    }

    /**
     * Parses json string and returns hash.
     *
     * @param null|string $json json string
     * @param string $file the json file
     *
     * @throws ParsingException
     * @return mixed
     */
    public static function parseJson(?string $json, ?string $file = null)
    {
        if (null === $json) {
            return null;
        }
        $data = json_decode($json, true);
        if (null === $data && JSON_ERROR_NONE !== json_last_error()) {
            // attempt resolving simple conflicts in lock files so that one can run `composer update --lock` and get a valid lock file
            if ($file !== null && str_ends_with($file, '.lock') && str_contains($json, '"content-hash"')) {
                $replaced = Preg::replace(
                    '{\r?\n<<<<<<< [^\r\n]+\r?\n\s+"content-hash": *"[0-9a-f]+", *\r?\n(?:\|{7} [^\r\n]+\r?\n\s+"content-hash": *"[0-9a-f]+", *\r?\n)?=======\r?\n\s+"content-hash": *"[0-9a-f]+", *\r?\n>>>>>>> [^\r\n]+(\r?\n)}',
                    '    "content-hash": "VCS merge conflict detected. Please run `composer update --lock`.",$1',
                    $json,
                    1,
                    $count
                );
                if ($count === 1) {
                    $data = json_decode($replaced, true);
                    if ($data !== null) {
                        return $data;
                    }
                }
            }

            self::validateSyntax($json, $file);
        }

        return $data;
    }

    /**
     * Validates the syntax of a JSON string
     *
     * @throws \UnexpectedValueException
     * @throws ParsingException
     * @return bool                      true on success
     */
    protected static function validateSyntax(string $json, ?string $file = null): bool
    {
        $parser = new JsonParser();
        $result = $parser->lint($json);
        if (null === $result) {
            if (defined('JSON_ERROR_UTF8') && JSON_ERROR_UTF8 === json_last_error()) {
                if ($file === null) {
                    throw new \UnexpectedValueException('The input is not UTF-8, could not parse as JSON');
                } else {
                    throw new \UnexpectedValueException('"' . $file . '" is not UTF-8, could not parse as JSON');
                }
            }

            return true;
        }

        if ($file === null) {
            throw new ParsingException(
                'The input does not contain valid JSON' . "\n" . $result->getMessage(),
                $result->getDetails()
            );
        } else {
            throw new ParsingException(
                '"' . $file . '" does not contain valid JSON' . "\n" . $result->getMessage(),
                $result->getDetails()
            );
        }
    }

    public static function detectIndenting(?string $json): string
    {
        if (Preg::isMatchStrictGroups('#^([ \t]+)"#m', $json ?? '', $match)) {
            return $match[1];
        }

        return self::INDENT_DEFAULT;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer;

use Composer\Package\Locker;
use Composer\Pcre\Preg;
use Composer\Plugin\PluginManager;
use Composer\Downloader\DownloadManager;
use Composer\Autoload\AutoloadGenerator;
use Composer\Package\Archiver\ArchiveManager;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Konstantin Kudryashiv <ever.zet@gmail.com>
 * @author Nils Adermann <naderman@naderman.de>
 */
class Composer extends PartialComposer
{
    /*
     * Examples of the following constants in the various configurations they can be in
     *
     * You are probably better off using Composer::getVersion() though as that will always return something usable
     *
     * releases (phar):
     * const VERSION = '1.8.2';
     * const BRANCH_ALIAS_VERSION = '';
     * const RELEASE_DATE = '2019-01-29 15:00:53';
     * const SOURCE_VERSION = '';
     *
     * snapshot builds (phar):
     * const VERSION = 'd3873a05650e168251067d9648845c220c50e2d7';
     * const BRANCH_ALIAS_VERSION = '1.9-dev';
     * const RELEASE_DATE = '2019-02-20 07:43:56';
     * const SOURCE_VERSION = '';
     *
     * source (git clone):
     * const VERSION = '@package_version@';
     * const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@';
     * const RELEASE_DATE = '@release_date@';
     * const SOURCE_VERSION = '1.8-dev+source';
     *
     * @see getVersion()
     */
    public const VERSION = '2.9.8';
    public const BRANCH_ALIAS_VERSION = '';
    public const RELEASE_DATE = '2026-05-13 09:28:38';
    public const SOURCE_VERSION = '';

    /**
     * Version number of the internal composer-runtime-api package
     *
     * This is used to version features available to projects at runtime
     * like the platform-check file, the Composer\InstalledVersions class
     * and possibly others in the future.
     *
     * @var string
     */
    public const RUNTIME_API_VERSION = '2.2.2';

    public static function getVersion(): string
    {
        // no replacement done, this must be a source checkout
        if (self::VERSION === '@package_version'.'@') {
            return self::SOURCE_VERSION;
        }

        // we have a branch alias and version is a commit id, this must be a snapshot build
        if (self::BRANCH_ALIAS_VERSION !== '' && Preg::isMatch('{^[a-f0-9]{40}$}', self::VERSION)) {
            return self::BRANCH_ALIAS_VERSION.'+'.self::VERSION;
        }

        return self::VERSION;
    }

    /**
     * @var Locker
     */
    private $locker;

    /**
     * @var DownloadManager
     */
    private $downloadManager;

    /**
     * @var PluginManager
     */
    private $pluginManager;

    /**
     * @var AutoloadGenerator
     */
    private $autoloadGenerator;

    /**
     * @var ArchiveManager
     */
    private $archiveManager;

    public function setLocker(Locker $locker): void
    {
        $this->locker = $locker;
    }

    public function getLocker(): Locker
    {
        return $this->locker;
    }

    public function setDownloadManager(DownloadManager $manager): void
    {
        $this->downloadManager = $manager;
    }

    public function getDownloadManager(): DownloadManager
    {
        return $this->downloadManager;
    }

    public function setArchiveManager(ArchiveManager $manager): void
    {
        $this->archiveManager = $manager;
    }

    public function getArchiveManager(): ArchiveManager
    {
        return $this->archiveManager;
    }

    public function setPluginManager(PluginManager $manager): void
    {
        $this->pluginManager = $manager;
    }

    public function getPluginManager(): PluginManager
    {
        return $this->pluginManager;
    }

    public function setAutoloadGenerator(AutoloadGenerator $autoloadGenerator): void
    {
        $this->autoloadGenerator = $autoloadGenerator;
    }

    public function getAutoloadGenerator(): AutoloadGenerator
    {
        return $this->autoloadGenerator;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Config;

use Composer\Json\JsonFile;
use Composer\Json\JsonManipulator;
use Composer\Json\JsonValidationException;
use Composer\Pcre\Preg;
use Composer\Util\Filesystem;
use Composer\Util\Silencer;

/**
 * JSON Configuration Source
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Beau Simensen <beau@dflydev.com>
 */
class JsonConfigSource implements ConfigSourceInterface
{
    /**
     * @var JsonFile
     */
    private $file;

    /**
     * @var bool
     */
    private $authConfig;

    /**
     * Constructor
     */
    public function __construct(JsonFile $file, bool $authConfig = false)
    {
        $this->file = $file;
        $this->authConfig = $authConfig;
    }

    /**
     * @inheritDoc
     */
    public function getName(): string
    {
        return $this->file->getPath();
    }

    /**
     * @inheritDoc
     */
    public function addRepository(string $name, $config, bool $append = true): void
    {
        $this->manipulateJson('addRepository', static function (&$config, $repo, $repoConfig) use ($append): void {
            if (!array_is_list($config['repositories'] ?? [])) {
                $list = [];

                foreach ($config['repositories'] as $repositoryIndex => $repository) {
                    if (is_string($repositoryIndex) && is_array($repository)) {
                        // convert to list entry with name
                        if (!isset($repository['name'])) {
                            $repository = ['name' => $repositoryIndex] + $repository;
                        }
                        $list[] = $repository;
                    } elseif (is_string($repositoryIndex)) {
                        // keep boolean entries (e.g. 'packagist.org' => false)
                        $list[] = [$repositoryIndex => $repository];
                    } else {
                        $list[] = $repository;
                    }
                }

                $config['repositories'] = $list;
            }

            if ($repoConfig === false) {
                if (isset($config['repositories'])) {
                    foreach ($config['repositories'] as &$repository) {
                        if (($repository['name'] ?? null) === $repo) {
                            $repository = [$repo => $repoConfig];

                            return;
                        }

                        if ($repository === [$repo => false]) {
                            return;
                        }
                    }

                    unset($repository);
                } else {
                    $config['repositories'] = [];
                }

                $config['repositories'][] = [$repo => $repoConfig];

                return;
            }

            if (is_array($repoConfig) && $repo !== '' && !isset($repoConfig['name'])) {
                $repoConfig = ['name' => $repo] + $repoConfig;
            }

            // ensure uniqueness by removing any existing entries which use the same name
            $config['repositories'] = array_values(array_filter($config['repositories'] ?? [], static function ($val) use ($repo) {
                return !isset($val['name']) || $val['name'] !== $repo || $val !== [$repo => false];
            }));

            if ($append) {
                $config['repositories'][] = $repoConfig;
            } else {
                array_unshift($config['repositories'], $repoConfig);
            }
        }, $name, $config, $append);
    }

    /**
     * @inheritDoc
     */
    public function insertRepository(string $name, $config, string $referenceName, int $offset = 0): void
    {
        $this->manipulateJson('insertRepository', static function (&$config, string $name, $repoConfig, string $referenceName, int $offset): void {
            if (!array_is_list($config['repositories'] ?? [])) {
                $list = [];

                foreach ($config['repositories'] as $repositoryIndex => $repository) {
                    if (is_string($repositoryIndex) && is_array($repository)) {
                        // convert to list entry with name
                        if (!isset($repository['name'])) {
                            $repository = ['name' => $repositoryIndex] + $repository;
                        }
                        $list[] = $repository;
                    } elseif (is_string($repositoryIndex)) {
                        // keep boolean entries (e.g. 'packagist.org' => false)
                        $list[] = [$repositoryIndex => $repository];
                    } else {
                        $list[] = $repository;
                    }
                }

                $config['repositories'] = $list;
            }

            // ensure uniqueness by removing any existing entries which use the same name
            $config['repositories'] = array_values(array_filter($config['repositories'] ?? [], static function ($val) use ($name) {
                return !isset($val['name']) || $val['name'] !== $name || $val !== [$name => false];
            }));

            $indexToInsert = null;

            foreach ($config['repositories'] as $repositoryIndex => $repository) {
                if (($repository['name'] ?? null) === $referenceName) {
                    $indexToInsert = $repositoryIndex;
                    break;
                }

                if ([$referenceName => false] === $repository) {
                    $indexToInsert = $repositoryIndex;
                    break;
                }
            }

            if ($indexToInsert === null) {
                throw new \RuntimeException(sprintf('The referenced repository "%s" does not exist.', $referenceName));
            }

            if (is_array($repoConfig) && $name !== '' && !isset($repoConfig['name'])) {
                $repoConfig = ['name' => $name] + $repoConfig;
            }

            array_splice($config['repositories'], $indexToInsert + $offset, 0, [$repoConfig]);
        }, $name, $config, $referenceName, $offset);
    }

    /**
     * @inheritDoc
     */
    public function setRepositoryUrl(string $name, string $url): void
    {
        $this->manipulateJson('setRepositoryUrl', static function (&$config, $name, $url): void {
            foreach ($config['repositories'] ?? [] as $index => $repository) {
                if ($name === $index) {
                    $config['repositories'][$index]['url'] = $url;

                    return;
                }

                if ($name === ($repository['name'] ?? null)) {
                    $config['repositories'][$index]['url'] = $url;

                    return;
                }
            }
        }, $name, $url);
    }

    /**
     * @inheritDoc
     */
    public function removeRepository(string $name): void
    {
        $this->manipulateJson('removeRepository', static function (&$config, $repo): void {
            if (isset($config['repositories'][$repo])) {
                unset($config['repositories'][$repo]);
            } else {
                $config['repositories'] = array_values(array_filter($config['repositories'] ?? [], static function ($val) use ($repo) {
                    return !isset($val['name']) || $val['name'] !== $repo || $val !== [$repo => false];
                }));
            }

            if ([] === $config['repositories']) {
                unset($config['repositories']);
            }
        }, $name);
    }

    /**
     * @inheritDoc
     */
    public function addConfigSetting(string $name, $value): void
    {
        $authConfig = $this->authConfig;
        $this->manipulateJson('addConfigSetting', static function (&$config, $key, $val) use ($authConfig): void {
            if (Preg::isMatch('{^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|bearer|http-basic|custom-headers|forgejo-token|platform)\.}', $key)) {
                [$key, $host] = explode('.', $key, 2);
                if ($authConfig) {
                    $config[$key][$host] = $val;
                } else {
                    $config['config'][$key][$host] = $val;
                }
            } else {
                $config['config'][$key] = $val;
            }
        }, $name, $value);
    }

    /**
     * @inheritDoc
     */
    public function removeConfigSetting(string $name): void
    {
        $authConfig = $this->authConfig;
        $this->manipulateJson('removeConfigSetting', static function (&$config, $key) use ($authConfig): void {
            if (Preg::isMatch('{^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|bearer|http-basic|custom-headers|forgejo-token|platform)\.}', $key)) {
                [$key, $host] = explode('.', $key, 2);
                if ($authConfig) {
                    unset($config[$key][$host]);
                } else {
                    unset($config['config'][$key][$host]);
                }
            } else {
                unset($config['config'][$key]);
            }
        }, $name);
    }

    /**
     * @inheritDoc
     */
    public function addProperty(string $name, $value): void
    {
        $this->manipulateJson('addProperty', static function (&$config, $key, $val): void {
            if (strpos($key, 'extra.') === 0 || strpos($key, 'scripts.') === 0) {
                $bits = explode('.', $key);
                $last = array_pop($bits);
                $arr = &$config[reset($bits)];
                foreach ($bits as $bit) {
                    if (!isset($arr[$bit])) {
                        $arr[$bit] = [];
                    }
                    $arr = &$arr[$bit];
                }
                $arr[$last] = $val;
            } else {
                $config[$key] = $val;
            }
        }, $name, $value);
    }

    /**
     * @inheritDoc
     */
    public function removeProperty(string $name): void
    {
        $this->manipulateJson('removeProperty', static function (&$config, $key): void {
            if (strpos($key, 'extra.') === 0 || strpos($key, 'scripts.') === 0 || stripos($key, 'autoload.') === 0 || stripos($key, 'autoload-dev.') === 0) {
                $bits = explode('.', $key);
                $last = array_pop($bits);
                $arr = &$config[reset($bits)];
                foreach ($bits as $bit) {
                    if (!isset($arr[$bit])) {
                        return;
                    }
                    $arr = &$arr[$bit];
                }
                unset($arr[$last]);
            } else {
                unset($config[$key]);
            }
        }, $name);
    }

    /**
     * @inheritDoc
     */
    public function addLink(string $type, string $name, string $value): void
    {
        $this->manipulateJson('addLink', static function (&$config, $type, $name, $value): void {
            $config[$type][$name] = $value;
        }, $type, $name, $value);
    }

    /**
     * @inheritDoc
     */
    public function removeLink(string $type, string $name): void
    {
        $this->manipulateJson('removeSubNode', static function (&$config, $type, $name): void {
            unset($config[$type][$name]);
        }, $type, $name);
        $this->manipulateJson('removeMainKeyIfEmpty', static function (&$config, $type): void {
            if (0 === count($config[$type])) {
                unset($config[$type]);
            }
        }, $type);
    }

    /**
     * @param mixed ...$args
     */
    private function manipulateJson(string $method, callable $fallback, ...$args): void
    {
        if ($this->file->exists()) {
            if (!is_writable($this->file->getPath())) {
                throw new \RuntimeException(sprintf('The file "%s" is not writable.', $this->file->getPath()));
            }

            if (!Filesystem::isReadable($this->file->getPath())) {
                throw new \RuntimeException(sprintf('The file "%s" is not readable.', $this->file->getPath()));
            }

            $contents = file_get_contents($this->file->getPath());
        } elseif ($this->authConfig) {
            $contents = "{\n}\n";
        } else {
            $contents = "{\n    \"config\": {\n    }\n}\n";
        }

        $manipulator = new JsonManipulator($contents);

        $newFile = !$this->file->exists();

        // override manipulator method for auth config files
        if ($this->authConfig && $method === 'addConfigSetting') {
            $method = 'addSubNode';
            [$mainNode, $name] = explode('.', $args[0], 2);
            $args = [$mainNode, $name, $args[1]];
        } elseif ($this->authConfig && $method === 'removeConfigSetting') {
            $method = 'removeSubNode';
            [$mainNode, $name] = explode('.', $args[0], 2);
            $args = [$mainNode, $name];
        }

        // try to update cleanly
        if (call_user_func_array([$manipulator, $method], $args)) {
            file_put_contents($this->file->getPath(), $manipulator->getContents());
        } else {
            // on failed clean update, call the fallback and rewrite the whole file
            $config = $this->file->read();
            $this->arrayUnshiftRef($args, $config);
            $fallback(...$args);
            // avoid ending up with arrays for keys that should be objects
            foreach (['require', 'require-dev', 'conflict', 'provide', 'replace', 'suggest', 'config', 'autoload', 'autoload-dev', 'scripts', 'scripts-descriptions', 'scripts-aliases', 'support'] as $prop) {
                if (isset($config[$prop]) && $config[$prop] === []) {
                    $config[$prop] = new \stdClass;
                }
            }
            foreach (['psr-0', 'psr-4'] as $prop) {
                if (isset($config['autoload'][$prop]) && $config['autoload'][$prop] === []) {
                    $config['autoload'][$prop] = new \stdClass;
                }
                if (isset($config['autoload-dev'][$prop]) && $config['autoload-dev'][$prop] === []) {
                    $config['autoload-dev'][$prop] = new \stdClass;
                }
            }
            foreach (['platform', 'http-basic', 'bearer', 'gitlab-token', 'gitlab-oauth', 'github-oauth', 'custom-headers', 'forgejo-token', 'preferred-install'] as $prop) {
                if (isset($config['config'][$prop]) && $config['config'][$prop] === []) {
                    $config['config'][$prop] = new \stdClass;
                }
            }
            $this->file->write($config);
        }

        try {
            $this->file->validateSchema(JsonFile::LAX_SCHEMA);
        } catch (JsonValidationException $e) {
            // restore contents to the original state
            file_put_contents($this->file->getPath(), $contents);
            throw new \RuntimeException('Failed to update composer.json with a valid format, reverting to the original content. Please report an issue to us with details (command you run and a copy of your composer.json). '.PHP_EOL.implode(PHP_EOL, $e->getErrors()), 0, $e);
        }

        if ($newFile) {
            Silencer::call('chmod', $this->file->getPath(), 0600);
        }
    }

    /**
     * Prepend a reference to an element to the beginning of an array.
     *
     * @param  mixed[] $array
     * @param  mixed $value
     */
    private function arrayUnshiftRef(array &$array, &$value): int
    {
        $return = array_unshift($array, '');
        $array[0] = &$value;

        return $return;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Config;

/**
 * Configuration Source Interface
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Beau Simensen <beau@dflydev.com>
 */
interface ConfigSourceInterface
{
    /**
     * Add a repository
     *
     * @param string        $name   Name
     * @param mixed[]|false $config Configuration
     * @param bool          $append Whether the repo should be appended (true) or prepended (false)
     */
    public function addRepository(string $name, $config, bool $append = true): void;

    /**
     * Inserts a repository before/after another repository by name
     *
     * @param string        $name          Name
     * @param mixed[]|false $config        Configuration
     * @param string        $referenceName The referenced repository to search for and insert next to
     * @param int           $offset        The offset to use for insert in reference to the looked-up repository
     */
    public function insertRepository(string $name, $config, string $referenceName, int $offset = 0): void;

    /**
     * Changes the URL of the referenced repository by name
     */
    public function setRepositoryUrl(string $name, string $url): void;

    /**
     * Remove a repository
     */
    public function removeRepository(string $name): void;

    /**
     * Add a config setting
     *
     * @param string $name  Name
     * @param mixed  $value Value
     */
    public function addConfigSetting(string $name, $value): void;

    /**
     * Remove a config setting
     */
    public function removeConfigSetting(string $name): void;

    /**
     * Add a property
     *
     * @param string $name  Name
     * @param string|string[] $value Value
     */
    public function addProperty(string $name, $value): void;

    /**
     * Remove a property
     */
    public function removeProperty(string $name): void;

    /**
     * Add a package link
     *
     * @param string $type  Type (require, require-dev, provide, suggest, replace, conflict)
     * @param string $name  Name
     * @param string $value Value
     */
    public function addLink(string $type, string $name, string $value): void;

    /**
     * Remove a package link
     *
     * @param string $type Type (require, require-dev, provide, suggest, replace, conflict)
     * @param string $name Name
     */
    public function removeLink(string $type, string $name): void;

    /**
     * Gives a user-friendly name to this source (file path or so)
     */
    public function getName(): string;
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Plugin;

use Composer\Composer;
use Composer\IO\IOInterface;

/**
 * Plugin interface
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
interface PluginInterface
{
    /**
     * Version number of the internal composer-plugin-api package
     *
     * This is used to denote the API version of Plugin specific
     * features, but is also bumped to a new major if Composer
     * includes a major break in internal APIs which are susceptible
     * to be used by plugins.
     *
     * @var string
     */
    public const PLUGIN_API_VERSION = '2.9.0';

    /**
     * Apply plugin modifications to Composer
     *
     * @return void
     */
    public function activate(Composer $composer, IOInterface $io);

    /**
     * Remove any hooks from Composer
     *
     * This will be called when a plugin is deactivated before being
     * uninstalled, but also before it gets upgraded to a new version
     * so the old one can be deactivated and the new one activated.
     *
     * @return void
     */
    public function deactivate(Composer $composer, IOInterface $io);

    /**
     * Prepare the plugin to be uninstalled
     *
     * This will be called after deactivate.
     *
     * @return void
     */
    public function uninstall(Composer $composer, IOInterface $io);
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Plugin\Capability;

/**
 * Commands Provider Interface
 *
 * This capability will receive an array with 'composer' and 'io' keys as
 * constructor argument. Those contain Composer\Composer and Composer\IO\IOInterface
 * instances. It also contains a 'plugin' key containing the plugin instance that
 * created the capability.
 *
 * @author Jérémy Derussé <jeremy@derusse.com>
 */
interface CommandProvider extends Capability
{
    /**
     * Retrieves an array of commands
     *
     * @return \Composer\Command\BaseCommand[]
     */
    public function getCommands();
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Plugin\Capability;

/**
 * Marker interface for Plugin capabilities.
 * Every new Capability which is added to the Plugin API must implement this interface.
 *
 * @api
 */
interface Capability
{
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Plugin;

use Composer\EventDispatcher\Event;
use Composer\Util\HttpDownloader;

/**
 * The pre file download event.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class PreFileDownloadEvent extends Event
{
    /**
     * @var HttpDownloader
     */
    private $httpDownloader;

    /**
     * @var non-empty-string
     */
    private $processedUrl;

    /**
     * @var string|null
     */
    private $customCacheKey;

    /**
     * @var string
     */
    private $type;

    /**
     * @var mixed
     */
    private $context;

    /**
     * @var mixed[]
     */
    private $transportOptions = [];

    /**
     * Constructor.
     *
     * @param string           $name           The event name
     * @param mixed            $context
     * @param non-empty-string $processedUrl
     */
    public function __construct(string $name, HttpDownloader $httpDownloader, string $processedUrl, string $type, $context = null)
    {
        parent::__construct($name);
        $this->httpDownloader = $httpDownloader;
        $this->processedUrl = $processedUrl;
        $this->type = $type;
        $this->context = $context;
    }

    public function getHttpDownloader(): HttpDownloader
    {
        return $this->httpDownloader;
    }

    /**
     * Retrieves the processed URL that will be downloaded.
     *
     * @return non-empty-string
     */
    public function getProcessedUrl(): string
    {
        return $this->processedUrl;
    }

    /**
     * Sets the processed URL that will be downloaded.
     *
     * @param non-empty-string $processedUrl New processed URL
     */
    public function setProcessedUrl(string $processedUrl): void
    {
        $this->processedUrl = $processedUrl;
    }

    /**
     * Retrieves a custom package cache key for this download.
     */
    public function getCustomCacheKey(): ?string
    {
        return $this->customCacheKey;
    }

    /**
     * Sets a custom package cache key for this download.
     *
     * @param string|null $customCacheKey New cache key
     */
    public function setCustomCacheKey(?string $customCacheKey): void
    {
        $this->customCacheKey = $customCacheKey;
    }

    /**
     * Returns the type of this download (package, metadata).
     */
    public function getType(): string
    {
        return $this->type;
    }

    /**
     * Returns the context of this download, if any.
     *
     * If this download is of type package, the package object is returned.
     * If the type is metadata, an array{repository: RepositoryInterface} is returned.
     *
     * @return mixed
     */
    public function getContext()
    {
        return $this->context;
    }

    /**
     * Returns transport options for the download.
     *
     * Only available for events with type metadata, for packages set the transport options on the package itself.
     *
     * @return mixed[]
     */
    public function getTransportOptions(): array
    {
        return $this->transportOptions;
    }

    /**
     * Sets transport options for the download.
     *
     * Only available for events with type metadata, for packages set the transport options on the package itself.
     *
     * @param mixed[] $options
     */
    public function setTransportOptions(array $options): void
    {
        $this->transportOptions = $options;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Plugin;

use Composer\EventDispatcher\Event;
use Composer\Repository\RepositoryInterface;
use Composer\DependencyResolver\Request;
use Composer\Package\BasePackage;

/**
 * The pre command run event.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PrePoolCreateEvent extends Event
{
    /**
     * @var RepositoryInterface[]
     */
    private $repositories;
    /**
     * @var Request
     */
    private $request;
    /**
     * @var int[] array of stability => BasePackage::STABILITY_* value
     * @phpstan-var array<string, BasePackage::STABILITY_*>
     */
    private $acceptableStabilities;
    /**
     * @var int[] array of package name => BasePackage::STABILITY_* value
     * @phpstan-var array<string, BasePackage::STABILITY_*>
     */
    private $stabilityFlags;
    /**
     * @var array[] of package => version => [alias, alias_normalized]
     * @phpstan-var array<string, array<string, array{alias: string, alias_normalized: string}>>
     */
    private $rootAliases;
    /**
     * @var string[]
     * @phpstan-var array<string, string>
     */
    private $rootReferences;
    /**
     * @var BasePackage[]
     */
    private $packages;
    /**
     * @var BasePackage[]
     */
    private $unacceptableFixedPackages;

    /**
     * @param string                $name                   The event name
     * @param RepositoryInterface[] $repositories
     * @param int[]                 $acceptableStabilities  array of stability => BasePackage::STABILITY_* value
     * @param int[]                 $stabilityFlags         array of package name => BasePackage::STABILITY_* value
     * @param array[]               $rootAliases            array of package => version => [alias, alias_normalized]
     * @param string[]              $rootReferences
     * @param BasePackage[]         $packages
     * @param BasePackage[]         $unacceptableFixedPackages
     *
     * @phpstan-param array<string, BasePackage::STABILITY_*> $acceptableStabilities
     * @phpstan-param array<string, BasePackage::STABILITY_*> $stabilityFlags
     * @phpstan-param array<string, array<string, array{alias: string, alias_normalized: string}>> $rootAliases
     * @phpstan-param array<string, string> $rootReferences
     */
    public function __construct(string $name, array $repositories, Request $request, array $acceptableStabilities, array $stabilityFlags, array $rootAliases, array $rootReferences, array $packages, array $unacceptableFixedPackages)
    {
        parent::__construct($name);

        $this->repositories = $repositories;
        $this->request = $request;
        $this->acceptableStabilities = $acceptableStabilities;
        $this->stabilityFlags = $stabilityFlags;
        $this->rootAliases = $rootAliases;
        $this->rootReferences = $rootReferences;
        $this->packages = $packages;
        $this->unacceptableFixedPackages = $unacceptableFixedPackages;
    }

    /**
     * @return RepositoryInterface[]
     */
    public function getRepositories(): array
    {
        return $this->repositories;
    }

    public function getRequest(): Request
    {
        return $this->request;
    }

    /**
     * @return int[] array of stability => BasePackage::STABILITY_* value
     * @phpstan-return array<string, BasePackage::STABILITY_*>
     */
    public function getAcceptableStabilities(): array
    {
        return $this->acceptableStabilities;
    }

    /**
     * @return int[] array of package name => BasePackage::STABILITY_* value
     * @phpstan-return array<string, BasePackage::STABILITY_*>
     */
    public function getStabilityFlags(): array
    {
        return $this->stabilityFlags;
    }

    /**
     * @return array[] of package => version => [alias, alias_normalized]
     * @phpstan-return array<string, array<string, array{alias: string, alias_normalized: string}>>
     */
    public function getRootAliases(): array
    {
        return $this->rootAliases;
    }

    /**
     * @return string[]
     * @phpstan-return array<string, string>
     */
    public function getRootReferences(): array
    {
        return $this->rootReferences;
    }

    /**
     * @return BasePackage[]
     */
    public function getPackages(): array
    {
        return $this->packages;
    }

    /**
     * @return BasePackage[]
     */
    public function getUnacceptableFixedPackages(): array
    {
        return $this->unacceptableFixedPackages;
    }

    /**
     * @param BasePackage[] $packages
     */
    public function setPackages(array $packages): void
    {
        $this->packages = $packages;
    }

    /**
     * @param BasePackage[] $packages
     */
    public function setUnacceptableFixedPackages(array $packages): void
    {
        $this->unacceptableFixedPackages = $packages;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Plugin;

use Composer\EventDispatcher\Event;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * An event for all commands.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class CommandEvent extends Event
{
    /**
     * @var string
     */
    private $commandName;

    /**
     * @var InputInterface
     */
    private $input;

    /**
     * @var OutputInterface
     */
    private $output;

    /**
     * Constructor.
     *
     * @param string          $name        The event name
     * @param string          $commandName The command name
     * @param mixed[]         $args        Arguments passed by the user
     * @param mixed[]         $flags       Optional flags to pass data not as argument
     */
    public function __construct(string $name, string $commandName, InputInterface $input, OutputInterface $output, array $args = [], array $flags = [])
    {
        parent::__construct($name, $args, $flags);
        $this->commandName = $commandName;
        $this->input = $input;
        $this->output = $output;
    }

    /**
     * Returns the command input interface
     */
    public function getInput(): InputInterface
    {
        return $this->input;
    }

    /**
     * Retrieves the command output interface
     */
    public function getOutput(): OutputInterface
    {
        return $this->output;
    }

    /**
     * Retrieves the name of the command being run
     */
    public function getCommandName(): string
    {
        return $this->commandName;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Plugin;

/**
 * Plugins which need to expose various implementations
 * of the Composer Plugin Capabilities must have their
 * declared Plugin class implementing this interface.
 *
 * @api
 */
interface Capable
{
    /**
     * Method by which a Plugin announces its API implementations, through an array
     * with a special structure.
     *
     * The key must be a string, representing a fully qualified class/interface name
     * which Composer Plugin API exposes.
     * The value must be a string as well, representing the fully qualified class name
     * of the implementing class.
     *
     * @tutorial
     *
     * return array(
     *     'Composer\Plugin\Capability\CommandProvider' => 'My\CommandProvider',
     *     'Composer\Plugin\Capability\Validator'       => 'My\Validator',
     * );
     *
     * @return string[]
     */
    public function getCapabilities();
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Plugin;

/**
 * The Plugin Events.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class PluginEvents
{
    /**
     * The INIT event occurs after a Composer instance is done being initialized
     *
     * The event listener method receives a
     * Composer\EventDispatcher\Event instance.
     *
     * @var string
     */
    public const INIT = 'init';

    /**
     * The COMMAND event occurs as a command begins
     *
     * The event listener method receives a
     * Composer\Plugin\CommandEvent instance.
     *
     * @var string
     */
    public const COMMAND = 'command';

    /**
     * The PRE_FILE_DOWNLOAD event occurs before downloading a file
     *
     * The event listener method receives a
     * Composer\Plugin\PreFileDownloadEvent instance.
     *
     * @var string
     */
    public const PRE_FILE_DOWNLOAD = 'pre-file-download';

    /**
     * The POST_FILE_DOWNLOAD event occurs after downloading a package dist file
     *
     * The event listener method receives a
     * Composer\Plugin\PostFileDownloadEvent instance.
     *
     * @var string
     */
    public const POST_FILE_DOWNLOAD = 'post-file-download';

    /**
     * The PRE_COMMAND_RUN event occurs before a command is executed and lets you modify the input arguments/options
     *
     * The event listener method receives a
     * Composer\Plugin\PreCommandRunEvent instance.
     *
     * @var string
     */
    public const PRE_COMMAND_RUN = 'pre-command-run';

    /**
     * The PRE_POOL_CREATE event occurs before the Pool of packages is created, and lets
     * you filter the list of packages which is going to enter the Solver
     *
     * The event listener method receives a
     * Composer\Plugin\PrePoolCreateEvent instance.
     *
     * @var string
     */
    public const PRE_POOL_CREATE = 'pre-pool-create';
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Plugin;

use Composer\Composer;
use Composer\EventDispatcher\EventSubscriberInterface;
use Composer\Installer\InstallerInterface;
use Composer\IO\IOInterface;
use Composer\Package\BasePackage;
use Composer\Package\CompletePackage;
use Composer\Package\Locker;
use Composer\Package\Package;
use Composer\Package\RootPackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\PartialComposer;
use Composer\Pcre\Preg;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\InstalledRepository;
use Composer\Repository\RepositoryUtils;
use Composer\Repository\RootPackageRepository;
use Composer\Package\PackageInterface;
use Composer\Package\Link;
use Composer\Semver\Constraint\Constraint;
use Composer\Plugin\Capability\Capability;
use Composer\Util\PackageSorter;

/**
 * Plugin manager
 *
 * @author Nils Adermann <naderman@naderman.de>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PluginManager
{
    /** @var Composer */
    protected $composer;
    /** @var IOInterface */
    protected $io;
    /** @var PartialComposer|null */
    protected $globalComposer;
    /** @var VersionParser */
    protected $versionParser;
    /** @var bool|'local'|'global' */
    protected $disablePlugins = false;

    /** @var array<PluginInterface> */
    protected $plugins = [];
    /** @var array<string, array<PluginInterface|InstallerInterface>> */
    protected $registeredPlugins = [];

    /**
     * @var array<non-empty-string, bool>|null
     */
    private $allowPluginRules;

    /**
     * @var array<non-empty-string, bool>|null
     */
    private $allowGlobalPluginRules;

    /** @var bool */
    private $runningInGlobalDir = false;

    /** @var int */
    private static $classCounter = 0;

    /**
     * @param bool|'local'|'global' $disablePlugins Whether plugins should not be loaded, can be set to local or global to only disable local/global plugins
     */
    public function __construct(IOInterface $io, Composer $composer, ?PartialComposer $globalComposer = null, $disablePlugins = false)
    {
        $this->io = $io;
        $this->composer = $composer;
        $this->globalComposer = $globalComposer;
        $this->versionParser = new VersionParser();
        $this->disablePlugins = $disablePlugins;
        $this->allowPluginRules = $this->parseAllowedPlugins($composer->getConfig()->get('allow-plugins'), $composer->getLocker());
        $this->allowGlobalPluginRules = $this->parseAllowedPlugins($globalComposer !== null ? $globalComposer->getConfig()->get('allow-plugins') : false);
    }

    public function setRunningInGlobalDir(bool $runningInGlobalDir): void
    {
        $this->runningInGlobalDir = $runningInGlobalDir;
    }

    /**
     * Loads all plugins from currently installed plugin packages
     */
    public function loadInstalledPlugins(): void
    {
        if (!$this->arePluginsDisabled('local')) {
            $repo = $this->composer->getRepositoryManager()->getLocalRepository();
            $this->loadRepository($repo, false, $this->composer->getPackage());
        }

        if ($this->globalComposer !== null && !$this->arePluginsDisabled('global')) {
            $this->loadRepository($this->globalComposer->getRepositoryManager()->getLocalRepository(), true);
        }
    }

    /**
     * Deactivate all plugins from currently installed plugin packages
     */
    public function deactivateInstalledPlugins(): void
    {
        if (!$this->arePluginsDisabled('local')) {
            $repo = $this->composer->getRepositoryManager()->getLocalRepository();
            $this->deactivateRepository($repo, false);
        }

        if ($this->globalComposer !== null && !$this->arePluginsDisabled('global')) {
            $this->deactivateRepository($this->globalComposer->getRepositoryManager()->getLocalRepository(), true);
        }
    }

    /**
     * Gets all currently active plugin instances
     *
     * @return array<PluginInterface> plugins
     */
    public function getPlugins(): array
    {
        return $this->plugins;
    }

    /**
     * Gets all currently active plugin instances
     *
     * @internal
     * @return array<string> Plugin package names which are currently active
     */
    public function getRegisteredPlugins(): array
    {
        return array_keys($this->registeredPlugins);
    }

    /**
     * Gets global composer or null when main composer is not fully loaded
     */
    public function getGlobalComposer(): ?PartialComposer
    {
        return $this->globalComposer;
    }

    /**
     * Register a plugin package, activate it etc.
     *
     * If it's of type composer-installer it is registered as an installer
     * instead for BC
     *
     * @param bool             $failOnMissingClasses By default this silently skips plugins that can not be found, but if set to true it fails with an exception
     * @param bool             $isGlobalPlugin       Set to true to denote plugins which are installed in the global Composer directory
     *
     * @throws \UnexpectedValueException
     */
    public function registerPackage(PackageInterface $package, bool $failOnMissingClasses = false, bool $isGlobalPlugin = false): void
    {
        if ($this->arePluginsDisabled($isGlobalPlugin ? 'global' : 'local')) {
            $this->io->writeError('<warning>The "'.$package->getName().'" plugin was not loaded as plugins are disabled.</warning>');

            return;
        }

        if ($package->getType() === 'composer-plugin') {
            $requiresComposer = null;
            foreach ($package->getRequires() as $link) { /** @var Link $link */
                if ('composer-plugin-api' === $link->getTarget()) {
                    $requiresComposer = $link->getConstraint();
                    break;
                }
            }

            if (!$requiresComposer) {
                throw new \RuntimeException("Plugin ".$package->getName()." is missing a require statement for a version of the composer-plugin-api package.");
            }

            $currentPluginApiVersion = $this->getPluginApiVersion();
            $currentPluginApiConstraint = new Constraint('==', $this->versionParser->normalize($currentPluginApiVersion));

            if ($requiresComposer->getPrettyString() === $this->getPluginApiVersion()) {
                $this->io->writeError('<warning>The "' . $package->getName() . '" plugin requires composer-plugin-api '.$this->getPluginApiVersion().', this *WILL* break in the future and it should be fixed ASAP (require ^'.$this->getPluginApiVersion().' instead for example).</warning>');
            } elseif (!$requiresComposer->matches($currentPluginApiConstraint)) {
                $this->io->writeError('<warning>The "' . $package->getName() . '" plugin '.($isGlobalPlugin || $this->runningInGlobalDir ? '(installed globally) ' : '').'was skipped because it requires a Plugin API version ("' . $requiresComposer->getPrettyString() . '") that does not match your Composer installation ("' . $currentPluginApiVersion . '"). You may need to run composer update with the "--no-plugins" option.</warning>');

                return;
            }

            if ($package->getName() === 'symfony/flex' && Preg::isMatch('{^[0-9.]+$}', $package->getVersion()) && version_compare($package->getVersion(), '1.9.8', '<')) {
                $this->io->writeError('<warning>The "' . $package->getName() . '" plugin '.($isGlobalPlugin || $this->runningInGlobalDir ? '(installed globally) ' : '').'was skipped because it is not compatible with Composer 2+. Make sure to update it to version 1.9.8 or greater.</warning>');

                return;
            }
        }

        if (!$this->isPluginAllowed($package->getName(), $isGlobalPlugin, true === ($package->getExtra()['plugin-optional'] ?? false))) {
            $this->io->writeError('Skipped loading "'.$package->getName() . '" '.($isGlobalPlugin || $this->runningInGlobalDir ? '(installed globally) ' : '').'as it is not in config.allow-plugins', true, IOInterface::DEBUG);

            return;
        }

        $oldInstallerPlugin = ($package->getType() === 'composer-installer');

        if (isset($this->registeredPlugins[$package->getName()])) {
            return;
        }

        $extra = $package->getExtra();
        if (empty($extra['class'])) {
            throw new \UnexpectedValueException('Error while installing '.$package->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.');
        }
        $classes = is_array($extra['class']) ? $extra['class'] : [$extra['class']];

        $localRepo = $this->composer->getRepositoryManager()->getLocalRepository();
        $globalRepo = $this->globalComposer !== null ? $this->globalComposer->getRepositoryManager()->getLocalRepository() : null;

        $rootPackage = clone $this->composer->getPackage();

        // clear files autoload rules from the root package as the root dependencies are not
        // necessarily all present yet when booting this runtime autoloader
        $rootPackageAutoloads = $rootPackage->getAutoload();
        $rootPackageAutoloads['files'] = [];
        $rootPackage->setAutoload($rootPackageAutoloads);
        $rootPackageAutoloads = $rootPackage->getDevAutoload();
        $rootPackageAutoloads['files'] = [];
        $rootPackage->setDevAutoload($rootPackageAutoloads);
        unset($rootPackageAutoloads);

        $rootPackageRepo = new RootPackageRepository($rootPackage);
        $installedRepo = new InstalledRepository([$localRepo, $rootPackageRepo]);
        if ($globalRepo) {
            $installedRepo->addRepository($globalRepo);
        }

        $autoloadPackages = [$package->getName() => $package];
        $autoloadPackages = $this->collectDependencies($installedRepo, $autoloadPackages, $package);

        $generator = $this->composer->getAutoloadGenerator();
        $autoloads = [[$rootPackage, '']];
        foreach ($autoloadPackages as $autoloadPackage) {
            if ($autoloadPackage === $rootPackage) {
                continue;
            }

            $installPath = $this->getInstallPath($autoloadPackage, $globalRepo && $globalRepo->hasPackage($autoloadPackage));
            if ($installPath === null) {
                continue;
            }
            $autoloads[] = [$autoloadPackage, $installPath];
        }

        $map = $generator->parseAutoloads($autoloads, $rootPackage);
        $classLoader = $generator->createLoader($map, $this->composer->getConfig()->get('vendor-dir'));
        $classLoader->register(false);

        foreach ($map['files'] as $fileIdentifier => $file) {
            // exclude laminas/laminas-zendframework-bridge:src/autoload.php as it breaks Composer in some conditions
            // see https://github.com/composer/composer/issues/10349 and https://github.com/composer/composer/issues/10401
            // this hack can be removed once this deprecated package stop being installed
            if ($fileIdentifier === '7e9bd612cc444b3eed788ebbe46263a0') {
                continue;
            }
            \Composer\Autoload\composerRequire($fileIdentifier, $file);
        }

        foreach ($classes as $class) {
            if (class_exists($class, false)) {
                $class = trim($class, '\\');
                $path = $classLoader->findFile($class);
                $code = file_get_contents($path);
                $separatorPos = strrpos($class, '\\');
                $className = $class;
                if ($separatorPos) {
                    $className = substr($class, $separatorPos + 1);
                }
                $code = Preg::replace('{^((?:(?:final|readonly)\s+)*(?:\s*))class\s+('.preg_quote($className).')}mi', '$1class $2_composer_tmp'.self::$classCounter, $code, 1);
                $code = strtr($code, [
                    '__FILE__' => var_export($path, true),
                    '__DIR__' => var_export(dirname($path), true),
                    '__CLASS__' => var_export($class, true),
                ]);
                $code = Preg::replace('/^\s*<\?(php)?/i', '', $code, 1);
                eval($code);
                $class .= '_composer_tmp'.self::$classCounter;
                self::$classCounter++;
            }

            if ($oldInstallerPlugin) {
                if (!is_a($class, 'Composer\Installer\InstallerInterface', true)) {
                    throw new \RuntimeException('Could not activate plugin "'.$package->getName().'" as "'.$class.'" does not implement Composer\Installer\InstallerInterface');
                }
                $this->io->writeError('<warning>Loading "'.$package->getName() . '" '.($isGlobalPlugin || $this->runningInGlobalDir ? '(installed globally) ' : '').'which is a legacy composer-installer built for Composer 1.x, it is likely to cause issues as you are running Composer 2.x.</warning>');
                $installer = new $class($this->io, $this->composer);
                $this->composer->getInstallationManager()->addInstaller($installer);
                $this->registeredPlugins[$package->getName()][] = $installer;
            } elseif (class_exists($class)) {
                if (!is_a($class, 'Composer\Plugin\PluginInterface', true)) {
                    throw new \RuntimeException('Could not activate plugin "'.$package->getName().'" as "'.$class.'" does not implement Composer\Plugin\PluginInterface');
                }
                $plugin = new $class();
                $this->addPlugin($plugin, $isGlobalPlugin, $package);
                $this->registeredPlugins[$package->getName()][] = $plugin;
            } elseif ($failOnMissingClasses) {
                throw new \UnexpectedValueException('Plugin '.$package->getName().' could not be initialized, class not found: '.$class);
            }
        }
    }

    /**
     * Deactivates a plugin package
     *
     * If it's of type composer-installer it is unregistered from the installers
     * instead for BC
     *
     * @throws \UnexpectedValueException
     */
    public function deactivatePackage(PackageInterface $package): void
    {
        if (!isset($this->registeredPlugins[$package->getName()])) {
            return;
        }

        $plugins = $this->registeredPlugins[$package->getName()];
        foreach ($plugins as $plugin) {
            if ($plugin instanceof InstallerInterface) {
                $this->composer->getInstallationManager()->removeInstaller($plugin);
            } else {
                $this->removePlugin($plugin);
            }
        }
        unset($this->registeredPlugins[$package->getName()]);
    }

    /**
     * Uninstall a plugin package
     *
     * If it's of type composer-installer it is unregistered from the installers
     * instead for BC
     *
     * @throws \UnexpectedValueException
     */
    public function uninstallPackage(PackageInterface $package): void
    {
        if (!isset($this->registeredPlugins[$package->getName()])) {
            return;
        }

        $plugins = $this->registeredPlugins[$package->getName()];
        foreach ($plugins as $plugin) {
            if ($plugin instanceof InstallerInterface) {
                $this->composer->getInstallationManager()->removeInstaller($plugin);
            } else {
                $this->removePlugin($plugin);
                $this->uninstallPlugin($plugin);
            }
        }
        unset($this->registeredPlugins[$package->getName()]);
    }

    /**
     * Returns the version of the internal composer-plugin-api package.
     */
    protected function getPluginApiVersion(): string
    {
        return PluginInterface::PLUGIN_API_VERSION;
    }

    /**
     * Adds a plugin, activates it and registers it with the event dispatcher
     *
     * Ideally plugin packages should be registered via registerPackage, but if you use Composer
     * programmatically and want to register a plugin class directly this is a valid way
     * to do it.
     *
     * @param PluginInterface   $plugin        plugin instance
     * @param ?PackageInterface $sourcePackage Package from which the plugin comes from
     */
    public function addPlugin(PluginInterface $plugin, bool $isGlobalPlugin = false, ?PackageInterface $sourcePackage = null): void
    {
        if ($this->arePluginsDisabled($isGlobalPlugin ? 'global' : 'local')) {
            return;
        }

        if ($sourcePackage === null) {
            trigger_error('Calling PluginManager::addPlugin without $sourcePackage is deprecated, if you are using this please get in touch with us to explain the use case', E_USER_DEPRECATED);
        } elseif (!$this->isPluginAllowed($sourcePackage->getName(), $isGlobalPlugin, true === ($sourcePackage->getExtra()['plugin-optional'] ?? false))) {
            $this->io->writeError('Skipped loading "'.get_class($plugin).' from '.$sourcePackage->getName() . '" '.($isGlobalPlugin || $this->runningInGlobalDir ? '(installed globally) ' : '').' as it is not in config.allow-plugins', true, IOInterface::DEBUG);

            return;
        }

        $details = [];
        if ($sourcePackage) {
            $details[] = 'from '.$sourcePackage->getName();
        }
        if ($isGlobalPlugin || $this->runningInGlobalDir) {
            $details[] = 'installed globally';
        }
        $this->io->writeError('Loading plugin '.get_class($plugin).($details ? ' ('.implode(', ', $details).')' : ''), true, IOInterface::DEBUG);
        $this->plugins[] = $plugin;
        $plugin->activate($this->composer, $this->io);

        if ($plugin instanceof EventSubscriberInterface) {
            $this->composer->getEventDispatcher()->addSubscriber($plugin);
        }
    }

    /**
     * Removes a plugin, deactivates it and removes any listener the plugin has set on the plugin instance
     *
     * Ideally plugin packages should be deactivated via deactivatePackage, but if you use Composer
     * programmatically and want to deregister a plugin class directly this is a valid way
     * to do it.
     *
     * @param PluginInterface $plugin plugin instance
     */
    public function removePlugin(PluginInterface $plugin): void
    {
        $index = array_search($plugin, $this->plugins, true);
        if ($index === false) {
            return;
        }

        $this->io->writeError('Unloading plugin '.get_class($plugin), true, IOInterface::DEBUG);
        unset($this->plugins[$index]);
        $plugin->deactivate($this->composer, $this->io);

        $this->composer->getEventDispatcher()->removeListener($plugin);
    }

    /**
     * Notifies a plugin it is being uninstalled and should clean up
     *
     * Ideally plugin packages should be uninstalled via uninstallPackage, but if you use Composer
     * programmatically and want to deregister a plugin class directly this is a valid way
     * to do it.
     *
     * @param PluginInterface $plugin plugin instance
     */
    public function uninstallPlugin(PluginInterface $plugin): void
    {
        $this->io->writeError('Uninstalling plugin '.get_class($plugin), true, IOInterface::DEBUG);
        $plugin->uninstall($this->composer, $this->io);
    }

    /**
     * Load all plugins and installers from a repository
     *
     * If a plugin requires another plugin, the required one will be loaded first
     *
     * Note that plugins in the specified repository that rely on events that
     * have fired prior to loading will be missed. This means you likely want to
     * call this method as early as possible.
     *
     * @param RepositoryInterface $repo Repository to scan for plugins to install
     *
     * @phpstan-param ($isGlobalRepo is true ? null : RootPackageInterface) $rootPackage
     *
     * @throws \RuntimeException
     */
    private function loadRepository(RepositoryInterface $repo, bool $isGlobalRepo, ?RootPackageInterface $rootPackage = null): void
    {
        $packages = $repo->getPackages();

        $weights = [];
        foreach ($packages as $package) {
            if ($package->getType() === 'composer-plugin') {
                $extra = $package->getExtra();
                if ($package->getName() === 'composer/installers' || true === ($extra['plugin-modifies-install-path'] ?? false)) {
                    $weights[$package->getName()] = -10000;
                }
            }
        }

        $sortedPackages = PackageSorter::sortPackages($packages, $weights);
        if (!$isGlobalRepo) {
            $requiredPackages = RepositoryUtils::filterRequiredPackages($packages, $rootPackage, true);
        }

        foreach ($sortedPackages as $package) {
            if (!($package instanceof CompletePackage)) {
                continue;
            }

            if (!in_array($package->getType(), ['composer-plugin', 'composer-installer'], true)) {
                continue;
            }

            if (
                !$isGlobalRepo
                && !in_array($package, $requiredPackages, true)
                && !$this->isPluginAllowed($package->getName(), false, true, false)
            ) {
                $this->io->writeError('<warning>The "'.$package->getName().'" plugin was not loaded as it is not listed in allow-plugins and is not required by the root package anymore.</warning>');
                continue;
            }

            if ('composer-plugin' === $package->getType()) {
                $this->registerPackage($package, false, $isGlobalRepo);
            // Backward compatibility
            } elseif ('composer-installer' === $package->getType()) {
                $this->registerPackage($package, false, $isGlobalRepo);
            }
        }
    }

    /**
     * Deactivate all plugins and installers from a repository
     *
     * If a plugin requires another plugin, the required one will be deactivated last
     *
     * @param RepositoryInterface $repo Repository to scan for plugins to install
     */
    private function deactivateRepository(RepositoryInterface $repo, bool $isGlobalRepo): void
    {
        $packages = $repo->getPackages();
        $sortedPackages = array_reverse(PackageSorter::sortPackages($packages));

        foreach ($sortedPackages as $package) {
            if (!($package instanceof CompletePackage)) {
                continue;
            }
            if ('composer-plugin' === $package->getType()) {
                $this->deactivatePackage($package);
            // Backward compatibility
            } elseif ('composer-installer' === $package->getType()) {
                $this->deactivatePackage($package);
            }
        }
    }

    /**
     * Recursively generates a map of package names to packages for all deps
     *
     * @param InstalledRepository             $installedRepo Set of local repos
     * @param array<string, PackageInterface> $collected     Current state of the map for recursion
     * @param PackageInterface                $package       The package to analyze
     *
     * @return array<string, PackageInterface> Map of package names to packages
     */
    private function collectDependencies(InstalledRepository $installedRepo, array $collected, PackageInterface $package): array
    {
        foreach ($package->getRequires() as $requireLink) {
            foreach ($installedRepo->findPackagesWithReplacersAndProviders($requireLink->getTarget()) as $requiredPackage) {
                if (!isset($collected[$requiredPackage->getName()])) {
                    $collected[$requiredPackage->getName()] = $requiredPackage;
                    $collected = $this->collectDependencies($installedRepo, $collected, $requiredPackage);
                }
            }
        }

        return $collected;
    }

    /**
     * Retrieves the path a package is installed to.
     *
     * @param bool             $global  Whether this is a global package
     *
     * @return string|null Install path
     */
    private function getInstallPath(PackageInterface $package, bool $global = false): ?string
    {
        if (!$global) {
            return $this->composer->getInstallationManager()->getInstallPath($package);
        }

        assert(null !== $this->globalComposer);

        return $this->globalComposer->getInstallationManager()->getInstallPath($package);
    }

    /**
     * @throws \RuntimeException On empty or non-string implementation class name value
     * @return null|string       The fully qualified class of the implementation or null if Plugin is not of Capable type or does not provide it
     */
    protected function getCapabilityImplementationClassName(PluginInterface $plugin, string $capability): ?string
    {
        if (!($plugin instanceof Capable)) {
            return null;
        }

        $capabilities = (array) $plugin->getCapabilities();

        if (!empty($capabilities[$capability]) && is_string($capabilities[$capability]) && trim($capabilities[$capability])) {
            return trim($capabilities[$capability]);
        }

        if (
            array_key_exists($capability, $capabilities)
            && (empty($capabilities[$capability]) || !is_string($capabilities[$capability]) || !trim($capabilities[$capability]))
        ) {
            throw new \UnexpectedValueException('Plugin '.get_class($plugin).' provided invalid capability class name(s), got '.var_export($capabilities[$capability], true));
        }

        return null;
    }

    /**
     * @template CapabilityClass of Capability
     * @param  class-string<CapabilityClass> $capabilityClassName The fully qualified name of the API interface which the plugin may provide
     *                                                            an implementation of.
     * @param  array<mixed>                  $ctorArgs            Arguments passed to Capability's constructor.
     *                                                            Keeping it an array will allow future values to be passed w\o changing the signature.
     * @phpstan-param class-string<CapabilityClass> $capabilityClassName
     * @phpstan-return null|CapabilityClass
     */
    public function getPluginCapability(PluginInterface $plugin, $capabilityClassName, array $ctorArgs = []): ?Capability
    {
        if ($capabilityClass = $this->getCapabilityImplementationClassName($plugin, $capabilityClassName)) {
            if (!class_exists($capabilityClass)) {
                throw new \RuntimeException("Cannot instantiate Capability, as class $capabilityClass from plugin ".get_class($plugin)." does not exist.");
            }

            $ctorArgs['plugin'] = $plugin;
            $capabilityObj = new $capabilityClass($ctorArgs);

            // FIXME these could use is_a and do the check *before* instantiating once drop support for php<5.3.9
            if (!$capabilityObj instanceof Capability || !$capabilityObj instanceof $capabilityClassName) {
                throw new \RuntimeException(
                    'Class ' . $capabilityClass . ' must implement both Composer\Plugin\Capability\Capability and '. $capabilityClassName . '.'
                );
            }

            return $capabilityObj;
        }

        return null;
    }

    /**
     * @template CapabilityClass of Capability
     * @param  class-string<CapabilityClass> $capabilityClassName The fully qualified name of the API interface which the plugin may provide
     *                                                            an implementation of.
     * @param  array<mixed>                  $ctorArgs            Arguments passed to Capability's constructor.
     *                                                            Keeping it an array will allow future values to be passed w\o changing the signature.
     * @return CapabilityClass[]
     */
    public function getPluginCapabilities($capabilityClassName, array $ctorArgs = []): array
    {
        $capabilities = [];
        foreach ($this->getPlugins() as $plugin) {
            $capability = $this->getPluginCapability($plugin, $capabilityClassName, $ctorArgs);
            if (null !== $capability) {
                $capabilities[] = $capability;
            }
        }

        return $capabilities;
    }

    /**
     * @param  array<string, bool>|bool $allowPluginsConfig
     * @return array<non-empty-string, bool>|null
     */
    private function parseAllowedPlugins($allowPluginsConfig, ?Locker $locker = null): ?array
    {
        if ([] === $allowPluginsConfig && $locker !== null && $locker->isLocked() && version_compare($locker->getPluginApi(), '2.2.0', '<')) {
            return null;
        }

        if (true === $allowPluginsConfig) {
            return ['{}' => true];
        }

        if (false === $allowPluginsConfig) {
            return ['{}' => false];
        }

        $rules = [];
        foreach ($allowPluginsConfig as $pattern => $allow) {
            $rules[BasePackage::packageNameToRegexp($pattern)] = $allow;
        }

        return $rules;
    }

    /**
     * @internal
     *
     * @param 'local'|'global' $type
     * @return bool
     */
    public function arePluginsDisabled($type)
    {
        return $this->disablePlugins === true || $this->disablePlugins === $type;
    }

    /**
     * @internal
     */
    public function disablePlugins(): void
    {
        $this->disablePlugins = true;
    }

    /**
     * @internal
     */
    public function isPluginAllowed(string $package, bool $isGlobalPlugin, bool $optional = false, bool $prompt = true): bool
    {
        if ($isGlobalPlugin) {
            $rules = &$this->allowGlobalPluginRules;
        } else {
            $rules = &$this->allowPluginRules;
        }

        // This is a BC mode for lock files created pre-Composer-2.2 where the expectation of
        // an allow-plugins config being present cannot be made.
        if ($rules === null) {
            if (!$this->io->isInteractive()) {
                $this->io->writeError('<warning>For additional security you should declare the allow-plugins config with a list of packages names that are allowed to run code. See https://getcomposer.org/allow-plugins</warning>');
                $this->io->writeError('<warning>This warning will become an exception once you run composer update!</warning>');

                $rules = ['{}' => true];

                // if no config is defined we allow all plugins for BC
                return true;
            }

            // keep going and prompt the user
            $rules = [];
        }

        foreach ($rules as $pattern => $allow) {
            if (Preg::isMatch($pattern, $package)) {
                return $allow === true;
            }
        }

        if ($package === 'composer/package-versions-deprecated') {
            return false;
        }

        if ($this->io->isInteractive() && $prompt) {
            $composer = $isGlobalPlugin && $this->globalComposer !== null ? $this->globalComposer : $this->composer;

            $this->io->writeError('<warning>'.$package.($isGlobalPlugin || $this->runningInGlobalDir ? ' (installed globally)' : '').' contains a Composer plugin which is currently not in your allow-plugins config. See https://getcomposer.org/allow-plugins</warning>');
            $attempts = 0;
            while (true) {
                // do not allow more than 5 prints of the help message, at some point assume the
                // input is not interactive and bail defaulting to a disabled plugin
                $default = '?';
                if ($attempts > 5) {
                    $this->io->writeError('Too many failed prompts, aborting.');
                    break;
                }

                switch ($answer = $this->io->ask('Do you trust "<fg=green;options=bold>'.$package.'</>" to execute code and wish to enable it now? (writes "allow-plugins" to composer.json) [<comment>y,n,d,?</comment>] ', $default)) {
                    case 'y':
                    case 'n':
                    case 'd':
                        $allow = $answer === 'y';

                        // persist answer in current rules to avoid prompting again if the package gets reloaded
                        $rules[BasePackage::packageNameToRegexp($package)] = $allow;

                        // persist answer in composer.json if it wasn't simply discarded
                        if ($answer === 'y' || $answer === 'n') {
                            $allowPlugins = $composer->getConfig()->get('allow-plugins');
                            if (is_array($allowPlugins)) {
                                $allowPlugins[$package] = $allow;
                                if ($composer->getConfig()->get('sort-packages')) {
                                    ksort($allowPlugins);
                                }
                                $composer->getConfig()->getConfigSource()->addConfigSetting('allow-plugins', $allowPlugins);
                                $composer->getConfig()->merge(['config' => ['allow-plugins' => $allowPlugins]]);
                            }
                        }

                        return $allow;

                    case '?':
                    default:
                        $attempts++;
                        $this->io->writeError([
                            'y - add package to allow-plugins in composer.json and let it run immediately',
                            'n - add package (as disallowed) to allow-plugins in composer.json to suppress further prompts',
                            'd - discard this, do not change composer.json and do not allow the plugin to run',
                            '? - print help',
                        ]);
                        break;
                }
            }
        } elseif ($optional) {
            return false;
        }

        throw new PluginBlockedException(
            $package.($isGlobalPlugin || $this->runningInGlobalDir ? ' (installed globally)' : '').' contains a Composer plugin which is blocked by your allow-plugins config. You may add it to the list if you consider it safe.'.PHP_EOL.
            'You can run "composer '.($isGlobalPlugin || $this->runningInGlobalDir ? 'global ' : '').'config --no-plugins allow-plugins.'.$package.' [true|false]" to enable it (true) or disable it explicitly and suppress this exception (false)'.PHP_EOL.
            'See https://getcomposer.org/allow-plugins'
        );
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Plugin;

use UnexpectedValueException;

class PluginBlockedException extends UnexpectedValueException
{
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Plugin;

use Composer\EventDispatcher\Event;
use Symfony\Component\Console\Input\InputInterface;

/**
 * The pre command run event.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PreCommandRunEvent extends Event
{
    /**
     * @var InputInterface
     */
    private $input;

    /**
     * @var string
     */
    private $command;

    /**
     * Constructor.
     *
     * @param string         $name    The event name
     * @param string         $command The command about to be executed
     */
    public function __construct(string $name, InputInterface $input, string $command)
    {
        parent::__construct($name);
        $this->input = $input;
        $this->command = $command;
    }

    /**
     * Returns the console input
     */
    public function getInput(): InputInterface
    {
        return $this->input;
    }

    /**
     * Returns the command about to be executed
     */
    public function getCommand(): string
    {
        return $this->command;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Plugin;

use Composer\EventDispatcher\Event;
use Composer\Package\PackageInterface;

/**
 * The post file download event.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class PostFileDownloadEvent extends Event
{
    /**
     * @var string
     */
    private $fileName;

    /**
     * @var string|null
     */
    private $checksum;

    /**
     * @var string
     */
    private $url;

    /**
     * @var mixed
     */
    private $context;

    /**
     * @var string
     */
    private $type;

    /**
     * Constructor.
     *
     * @param string      $name     The event name
     * @param string|null $fileName The file name
     * @param string|null $checksum The checksum
     * @param string      $url      The processed url
     * @param string      $type     The type (package or metadata).
     * @param mixed       $context  Additional context for the download.
     */
    public function __construct(string $name, ?string $fileName, ?string $checksum, string $url, string $type, $context = null)
    {
        /** @phpstan-ignore instanceof.alwaysFalse, booleanAnd.alwaysFalse */
        if ($context === null && $type instanceof PackageInterface) {
            $context = $type;
            $type = 'package';
            trigger_error('PostFileDownloadEvent::__construct should receive a $type=package and the package object in $context since Composer 2.1.', E_USER_DEPRECATED);
        }

        parent::__construct($name);
        $this->fileName = $fileName;
        $this->checksum = $checksum;
        $this->url = $url;
        $this->context = $context;
        $this->type = $type;
    }

    /**
     * Retrieves the target file name location.
     *
     * If this download is of type metadata, null is returned.
     */
    public function getFileName(): ?string
    {
        return $this->fileName;
    }

    /**
     * Gets the checksum.
     */
    public function getChecksum(): ?string
    {
        return $this->checksum;
    }

    /**
     * Gets the processed URL.
     */
    public function getUrl(): string
    {
        return $this->url;
    }

    /**
     * Returns the context of this download, if any.
     *
     * If this download is of type package, the package object is returned. If
     * this download is of type metadata, an array{response: Response, repository: RepositoryInterface} is returned.
     *
     * @return mixed
     */
    public function getContext()
    {
        return $this->context;
    }

    /**
     * Get the package.
     *
     * If this download is of type metadata, null is returned.
     *
     * @return PackageInterface|null The package.
     * @deprecated Use getContext instead
     */
    public function getPackage(): ?PackageInterface
    {
        trigger_error('PostFileDownloadEvent::getPackage is deprecated since Composer 2.1, use getContext instead.', E_USER_DEPRECATED);
        $context = $this->getContext();

        return $context instanceof PackageInterface ? $context : null;
    }

    /**
     * Returns the type of this download (package, metadata).
     */
    public function getType(): string
    {
        return $this->type;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer;

use Composer\IO\IOInterface;
use Composer\Pcre\Preg;
use Composer\Util\Filesystem;
use Composer\Util\Platform;
use Composer\Util\Silencer;
use Symfony\Component\Finder\Finder;

/**
 * Reads/writes to a filesystem cache
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Cache
{
    /** @var bool|null */
    private static $cacheCollected = null;
    /** @var IOInterface */
    private $io;
    /** @var string */
    private $root;
    /** @var ?bool */
    private $enabled = null;
    /** @var string */
    private $allowlist;
    /** @var Filesystem */
    private $filesystem;
    /** @var bool */
    private $readOnly;

    /**
     * @param string      $cacheDir   location of the cache
     * @param string      $allowlist  List of characters that are allowed in path names (used in a regex character class)
     * @param Filesystem  $filesystem optional filesystem instance
     * @param bool        $readOnly   whether the cache is in readOnly mode
     */
    public function __construct(IOInterface $io, string $cacheDir, string $allowlist = 'a-z0-9._', ?Filesystem $filesystem = null, bool $readOnly = false)
    {
        $this->io = $io;
        $this->root = rtrim($cacheDir, '/\\') . '/';
        $this->allowlist = $allowlist;
        $this->filesystem = $filesystem ?: new Filesystem();
        $this->readOnly = $readOnly;

        if (!self::isUsable($cacheDir)) {
            $this->enabled = false;
        }
    }

    /**
     * @return void
     */
    public function setReadOnly(bool $readOnly)
    {
        $this->readOnly = $readOnly;
    }

    /**
     * @return bool
     */
    public function isReadOnly()
    {
        return $this->readOnly;
    }

    /**
     * @return bool
     */
    public static function isUsable(string $path)
    {
        return !Preg::isMatch('{(^|[\\\\/])(\$null|nul|NUL|/dev/null)([\\\\/]|$)}', $path);
    }

    /**
     * @return bool
     */
    public function isEnabled()
    {
        if ($this->enabled === null) {
            $this->enabled = true;

            if (
                !$this->readOnly
                && (
                    (!is_dir($this->root) && !Silencer::call('mkdir', $this->root, 0777, true))
                    || !is_writable($this->root)
                )
            ) {
                $this->io->writeError('<warning>Cannot create cache directory ' . $this->root . ', or directory is not writable. Proceeding without cache. See also cache-read-only config if your filesystem is read-only.</warning>');
                $this->enabled = false;
            }
        }

        return $this->enabled;
    }

    /**
     * @return string
     */
    public function getRoot()
    {
        return $this->root;
    }

    /**
     * @return string|false
     */
    public function read(string $file)
    {
        if ($this->isEnabled()) {
            $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file);
            if (file_exists($this->root . $file)) {
                $this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG);

                return file_get_contents($this->root . $file);
            }
        }

        return false;
    }

    /**
     * @return bool
     */
    public function write(string $file, string $contents)
    {
        $wasEnabled = $this->enabled === true;

        if ($this->isEnabled() && !$this->readOnly) {
            $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file);

            $this->io->writeError('Writing '.$this->root . $file.' into cache', true, IOInterface::DEBUG);

            $tempFileName = $this->root . $file . bin2hex(random_bytes(5)) . '.tmp';
            try {
                return file_put_contents($tempFileName, $contents) !== false && rename($tempFileName, $this->root . $file);
            } catch (\ErrorException $e) {
                // If the write failed despite isEnabled checks passing earlier, rerun the isEnabled checks to
                // see if they are still current and recreate the cache dir if needed. Refs https://github.com/composer/composer/issues/11076
                if ($wasEnabled) {
                    clearstatcache();
                    $this->enabled = null;

                    return $this->write($file, $contents);
                }

                $this->io->writeError('<warning>Failed to write into cache: '.$e->getMessage().'</warning>', true, IOInterface::DEBUG);
                if (Preg::isMatch('{^file_put_contents\(\): Only ([0-9]+) of ([0-9]+) bytes written}', $e->getMessage(), $m)) {
                    // Remove partial file.
                    unlink($tempFileName);

                    $message = sprintf(
                        '<warning>Writing %1$s into cache failed after %2$u of %3$u bytes written, only %4$s bytes of free space available</warning>',
                        $tempFileName,
                        $m[1],
                        $m[2],
                        function_exists('disk_free_space') ? @disk_free_space(dirname($tempFileName)) : 'unknown'
                    );

                    $this->io->writeError($message);

                    return false;
                }

                throw $e;
            }
        }

        return false;
    }

    /**
     * Copy a file into the cache
     *
     * @return bool
     */
    public function copyFrom(string $file, string $source)
    {
        if ($this->isEnabled() && !$this->readOnly) {
            $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file);
            $this->filesystem->ensureDirectoryExists(dirname($this->root . $file));

            if (!file_exists($source)) {
                $this->io->writeError('<error>'.$source.' does not exist, can not write into cache</error>');
            } elseif ($this->io->isDebug()) {
                $this->io->writeError('Writing '.$this->root . $file.' into cache from '.$source);
            }

            return $this->filesystem->copy($source, $this->root . $file);
        }

        return false;
    }

    /**
     * Copy a file out of the cache
     *
     * @return bool
     */
    public function copyTo(string $file, string $target)
    {
        if ($this->isEnabled()) {
            $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file);
            if (file_exists($this->root . $file)) {
                try {
                    touch($this->root . $file, (int) filemtime($this->root . $file), time());
                } catch (\ErrorException $e) {
                    // fallback in case the above failed due to incorrect ownership
                    // see https://github.com/composer/composer/issues/4070
                    Silencer::call('touch', $this->root . $file);
                }

                $this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG);

                return $this->filesystem->copy($this->root . $file, $target);
            }
        }

        return false;
    }

    /**
     * @return bool
     */
    public function gcIsNecessary()
    {
        if (self::$cacheCollected) {
            return false;
        }

        self::$cacheCollected = true;
        if (Platform::getEnv('COMPOSER_TEST_SUITE')) {
            return false;
        }

        if (Platform::isInputCompletionProcess()) {
            return false;
        }

        return !random_int(0, 50);
    }

    /**
     * @return bool
     */
    public function remove(string $file)
    {
        if ($this->isEnabled() && !$this->readOnly) {
            $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file);
            if (file_exists($this->root . $file)) {
                return $this->filesystem->unlink($this->root . $file);
            }
        }

        return false;
    }

    /**
     * @return bool
     */
    public function clear()
    {
        if ($this->isEnabled() && !$this->readOnly) {
            $this->filesystem->emptyDirectory($this->root);

            return true;
        }

        return false;
    }

    /**
     * @return int|false
     * @phpstan-return int<0, max>|false
     */
    public function getAge(string $file)
    {
        if ($this->isEnabled()) {
            $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file);
            if (file_exists($this->root . $file) && ($mtime = filemtime($this->root . $file)) !== false) {
                return abs(time() - $mtime);
            }
        }

        return false;
    }

    /**
     * @return bool
     */
    public function gc(int $ttl, int $maxSize)
    {
        if ($this->isEnabled() && !$this->readOnly) {
            $expire = new \DateTime();
            $expire->modify('-'.$ttl.' seconds');

            $finder = $this->getFinder()->date('until '.$expire->format('Y-m-d H:i:s'));
            foreach ($finder as $file) {
                $this->filesystem->unlink($file->getPathname());
            }

            $totalSize = $this->filesystem->size($this->root);
            if ($totalSize > $maxSize) {
                $iterator = $this->getFinder()->sortByAccessedTime()->getIterator();
                while ($totalSize > $maxSize && $iterator->valid()) {
                    $filepath = $iterator->current()->getPathname();
                    $totalSize -= $this->filesystem->size($filepath);
                    $this->filesystem->unlink($filepath);
                    $iterator->next();
                }
            }

            self::$cacheCollected = true;

            return true;
        }

        return false;
    }

    public function gcVcsCache(int $ttl): bool
    {
        if ($this->isEnabled()) {
            $expire = new \DateTime();
            $expire->modify('-'.$ttl.' seconds');

            $finder = Finder::create()->in($this->root)->directories()->depth(0)->date('until '.$expire->format('Y-m-d H:i:s'));
            foreach ($finder as $file) {
                $this->filesystem->removeDirectory($file->getPathname());
            }

            self::$cacheCollected = true;

            return true;
        }

        return false;
    }

    /**
     * @return string|false
     */
    public function sha1(string $file)
    {
        if ($this->isEnabled()) {
            $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file);
            if (file_exists($this->root . $file)) {
                return hash_file('sha1', $this->root . $file);
            }
        }

        return false;
    }

    /**
     * @return string|false
     */
    public function sha256(string $file)
    {
        if ($this->isEnabled()) {
            $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file);
            if (file_exists($this->root . $file)) {
                return hash_file('sha256', $this->root . $file);
            }
        }

        return false;
    }

    /**
     * @return Finder
     */
    protected function getFinder()
    {
        return Finder::create()->in($this->root)->files();
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputArgument;
use Symfony\Component\Console\Input\ArrayInput;
use Composer\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class OutdatedCommand extends BaseCommand
{
    use CompletionTrait;

    protected function configure(): void
    {
        $this
            ->setName('outdated')
            ->setDescription('Shows a list of installed packages that have updates available, including their latest version')
            ->setDefinition([
                new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect. Or a name including a wildcard (*) to filter lists of packages instead.', null, $this->suggestInstalledPackage(false)),
                new InputOption('outdated', 'o', InputOption::VALUE_NONE, 'Show only packages that are outdated (this is the default, but present here for compat with `show`'),
                new InputOption('all', 'a', InputOption::VALUE_NONE, 'Show all installed packages with their latest versions'),
                new InputOption('locked', null, InputOption::VALUE_NONE, 'Shows updates for packages from the lock file, regardless of what is currently in vendor dir'),
                new InputOption('direct', 'D', InputOption::VALUE_NONE, 'Shows only packages that are directly required by the root package'),
                new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'),
                new InputOption('major-only', 'M', InputOption::VALUE_NONE, 'Show only packages that have major SemVer-compatible updates.'),
                new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates.'),
                new InputOption('patch-only', 'p', InputOption::VALUE_NONE, 'Show only packages that have patch SemVer-compatible updates.'),
                new InputOption('sort-by-age', 'A', InputOption::VALUE_NONE, 'Displays the installed version\'s age, and sorts packages oldest first.'),
                new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']),
                new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Can contain wildcards (*). Use it if you don\'t want to be informed about new versions of some packages.', null, $this->suggestInstalledPackage(false)),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'),
                new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages). Use with the --outdated option'),
                new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages). Use with the --outdated option'),
            ])
            ->setHelp(
                <<<EOT
The outdated command is just a proxy for `composer show -l`

The color coding (or signage if you have ANSI colors disabled) for dependency versions is as such:

- <info>green</info> (=): Dependency is in the latest version and is up to date.
- <comment>yellow</comment> (~): Dependency has a new version available that includes backwards
  compatibility breaks according to semver, so upgrade when you can but it
  may involve work.
- <highlight>red</highlight> (!): Dependency has a new version that is semver-compatible and you should upgrade it.

Read more at https://getcomposer.org/doc/03-cli.md#outdated
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $args = [
            'command' => 'show',
            '--latest' => true,
        ];
        if ($input->getOption('no-interaction')) {
            $args['--no-interaction'] = true;
        }
        if ($input->getOption('no-plugins')) {
            $args['--no-plugins'] = true;
        }
        if ($input->getOption('no-scripts')) {
            $args['--no-scripts'] = true;
        }
        if ($input->getOption('no-cache')) {
            $args['--no-cache'] = true;
        }
        if (!$input->getOption('all')) {
            $args['--outdated'] = true;
        }
        if ($input->getOption('direct')) {
            $args['--direct'] = true;
        }
        if (null !== $input->getArgument('package')) {
            $args['package'] = $input->getArgument('package');
        }
        if ($input->getOption('strict')) {
            $args['--strict'] = true;
        }
        if ($input->getOption('major-only')) {
            $args['--major-only'] = true;
        }
        if ($input->getOption('minor-only')) {
            $args['--minor-only'] = true;
        }
        if ($input->getOption('patch-only')) {
            $args['--patch-only'] = true;
        }
        if ($input->getOption('locked')) {
            $args['--locked'] = true;
        }
        if ($input->getOption('no-dev')) {
            $args['--no-dev'] = true;
        }
        if ($input->getOption('sort-by-age')) {
            $args['--sort-by-age'] = true;
        }
        $args['--ignore-platform-req'] = $input->getOption('ignore-platform-req');
        if ($input->getOption('ignore-platform-reqs')) {
            $args['--ignore-platform-reqs'] = true;
        }
        $args['--format'] = $input->getOption('format');
        $args['--ignore'] = $input->getOption('ignore');

        $input = new ArrayInput($args);

        return $this->getApplication()->run($input, $output);
    }

    /**
     * @inheritDoc
     */
    public function isProxyCommand(): bool
    {
        return true;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class DumpAutoloadCommand extends BaseCommand
{
    protected function configure(): void
    {
        $this
            ->setName('dump-autoload')
            ->setAliases(['dumpautoload'])
            ->setDescription('Dumps the autoloader')
            ->setDefinition([
                new InputOption('optimize', 'o', InputOption::VALUE_NONE, 'Optimizes PSR0 and PSR4 packages to be loaded with classmaps too, good for production.'),
                new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize`.'),
                new InputOption('apcu', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'),
                new InputOption('apcu-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu'),
                new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything.'),
                new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables autoload-dev rules. Composer will by default infer this automatically according to the last install or update --no-dev state.'),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables autoload-dev rules. Composer will by default infer this automatically according to the last install or update --no-dev state.'),
                new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'),
                new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'),
                new InputOption('strict-psr', null, InputOption::VALUE_NONE, 'Return a failed status code (1) if PSR-4 or PSR-0 mapping errors are present. Requires --optimize to work.'),
                new InputOption('strict-ambiguous', null, InputOption::VALUE_NONE, 'Return a failed status code (2) if the same class is found in multiple files. Requires --optimize to work.'),
            ])
            ->setHelp(
                <<<EOT
<info>php composer.phar dump-autoload</info>

Read more at https://getcomposer.org/doc/03-cli.md#dump-autoload-dumpautoload
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $composer = $this->requireComposer();

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'dump-autoload', $input, $output);
        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        $installationManager = $composer->getInstallationManager();
        $localRepo = $composer->getRepositoryManager()->getLocalRepository();
        $package = $composer->getPackage();
        $config = $composer->getConfig();

        $missingDependencies = false;
        foreach ($localRepo->getCanonicalPackages() as $localPkg) {
            $installPath = $installationManager->getInstallPath($localPkg);
            if ($installPath !== null && file_exists($installPath) === false) {
                $missingDependencies = true;
                $this->getIO()->write('<warning>Not all dependencies are installed. Make sure to run a "composer install" to install missing dependencies</warning>');

                break;
            }
        }

        $optimize = $input->getOption('optimize') || $config->get('optimize-autoloader');
        $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative');
        $apcuPrefix = $input->getOption('apcu-prefix');
        $apcu = $apcuPrefix !== null || $input->getOption('apcu') || $config->get('apcu-autoloader');

        if ($input->getOption('strict-psr') && !$optimize && !$authoritative) {
            throw new \InvalidArgumentException('--strict-psr mode only works with optimized autoloader, use --optimize or --classmap-authoritative if you want a strict return value.');
        }
        if ($input->getOption('strict-ambiguous') && !$optimize && !$authoritative) {
            throw new \InvalidArgumentException('--strict-ambiguous mode only works with optimized autoloader, use --optimize or --classmap-authoritative if you want a strict return value.');
        }

        if ($authoritative) {
            $this->getIO()->write('<info>Generating optimized autoload files (authoritative)</info>');
        } elseif ($optimize) {
            $this->getIO()->write('<info>Generating optimized autoload files</info>');
        } else {
            $this->getIO()->write('<info>Generating autoload files</info>');
        }

        $generator = $composer->getAutoloadGenerator();
        if ($input->getOption('dry-run')) {
            $generator->setDryRun(true);
        }
        if ($input->getOption('no-dev')) {
            $generator->setDevMode(false);
        }
        if ($input->getOption('dev')) {
            if ($input->getOption('no-dev')) {
                throw new \InvalidArgumentException('You can not use both --no-dev and --dev as they conflict with each other.');
            }
            $generator->setDevMode(true);
        }
        $generator->setClassMapAuthoritative($authoritative);
        $generator->setRunScripts(true);
        $generator->setApcu($apcu, $apcuPrefix);
        $generator->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input));
        $classMap = $generator->dump(
            $config,
            $localRepo,
            $package,
            $installationManager,
            'composer',
            $optimize,
            null,
            $composer->getLocker(),
            $input->getOption('strict-ambiguous')
        );
        $numberOfClasses = count($classMap);

        if ($authoritative) {
            $this->getIO()->write('<info>Generated optimized autoload files (authoritative) containing '. $numberOfClasses .' classes</info>');
        } elseif ($optimize) {
            $this->getIO()->write('<info>Generated optimized autoload files containing '. $numberOfClasses .' classes</info>');
        } else {
            $this->getIO()->write('<info>Generated autoload files</info>');
        }

        if ($missingDependencies || ($input->getOption('strict-psr') && count($classMap->getPsrViolations()) > 0)) {
            return 1;
        }

        if ($input->getOption('strict-ambiguous') && count($classMap->getAmbiguousClasses(false)) > 0) {
            return 2;
        }

        return 0;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Advisory\Auditor;
use Composer\Composer;
use Composer\Factory;
use Composer\Config;
use Composer\Downloader\TransportException;
use Composer\IO\BufferIO;
use Composer\Json\JsonFile;
use Composer\Json\JsonValidationException;
use Composer\Package\Locker;
use Composer\Package\RootPackage;
use Composer\Package\Version\VersionParser;
use Composer\Pcre\Preg;
use Composer\Repository\ComposerRepository;
use Composer\Repository\FilesystemRepository;
use Composer\Repository\PlatformRepository;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Repository\RepositorySet;
use Composer\Util\ConfigValidator;
use Composer\Util\Git;
use Composer\Util\IniHelper;
use Composer\Util\ProcessExecutor;
use Composer\Util\HttpDownloader;
use Composer\Util\Platform;
use Composer\SelfUpdate\Keys;
use Composer\SelfUpdate\Versions;
use Composer\IO\NullIO;
use Composer\Package\CompletePackageInterface;
use Composer\XdebugHandler\XdebugHandler;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\ExecutableFinder;
use Composer\Util\Http\ProxyManager;
use Composer\Util\Http\RequestProxy;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class DiagnoseCommand extends BaseCommand
{
    /** @var HttpDownloader */
    protected $httpDownloader;

    /** @var ProcessExecutor */
    protected $process;

    /** @var int */
    protected $exitCode = 0;

    protected function configure(): void
    {
        $this
            ->setName('diagnose')
            ->setDescription('Diagnoses the system to identify common errors')
            ->setHelp(
                <<<EOT
The <info>diagnose</info> command checks common errors to help debugging problems.

The process exit code will be 1 in case of warnings and 2 for errors.

Read more at https://getcomposer.org/doc/03-cli.md#diagnose
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $composer = $this->tryComposer();
        $io = $this->getIO();

        if ($composer) {
            $config = $composer->getConfig();

            $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'diagnose', $input, $output);
            $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
            $this->process = $composer->getLoop()->getProcessExecutor() ?? new ProcessExecutor($io);
       } else {
            $config = Factory::createConfig();

            $this->process = new ProcessExecutor($io);
        }

        $config->merge(['config' => ['secure-http' => false]], Config::SOURCE_COMMAND);
        $config->prohibitUrlByConfig('http://repo.packagist.org', new NullIO);

        $this->httpDownloader = Factory::createHttpDownloader($io, $config);

        if (strpos(__FILE__, 'phar:') === 0) {
            $io->write('Checking pubkeys: ', false);
            $this->outputResult($this->checkPubKeys($config));

            $io->write('Checking Composer version: ', false);
            $this->outputResult($this->checkVersion($config));
        }

        $io->write(sprintf('Composer version: <comment>%s</comment>', Composer::getVersion()));

        $io->write('Checking Composer and its dependencies for vulnerabilities: ', false);
        $this->outputResult($this->checkComposerAudit($config));

        $platformOverrides = $config->get('platform') ?: [];
        $platformRepo = new PlatformRepository([], $platformOverrides);
        $phpPkg = $platformRepo->findPackage('php', '*');
        $phpVersion = $phpPkg->getPrettyVersion();
        if ($phpPkg instanceof CompletePackageInterface && str_contains((string) $phpPkg->getDescription(), 'overridden')) {
            $phpVersion .= ' - ' . $phpPkg->getDescription();
        }

        $io->write(sprintf('PHP version: <comment>%s</comment>', $phpVersion));

        if (defined('PHP_BINARY')) {
            $io->write(sprintf('PHP binary path: <comment>%s</comment>', PHP_BINARY));
        }

        $io->write('OpenSSL version: ' . (defined('OPENSSL_VERSION_TEXT') ? '<comment>'.OPENSSL_VERSION_TEXT.'</comment>' : '<error>missing</error>'));
        $io->write('curl version: ' . $this->getCurlVersion());

        $finder = new ExecutableFinder;
        $hasSystemUnzip = (bool) $finder->find('unzip');
        $bin7zip = '';
        if ($hasSystem7zip = (bool) $finder->find('7z', null, ['C:\Program Files\7-Zip'])) {
            $bin7zip = '7z';
        } elseif (!Platform::isWindows() && $hasSystem7zip = (bool) $finder->find('7zz')) {
            $bin7zip = '7zz';
        } elseif (!Platform::isWindows() && $hasSystem7zip = (bool) $finder->find('7za')) {
            $bin7zip = '7za';
        }

        $io->write(
            'zip: ' . (extension_loaded('zip') ? '<comment>extension present</comment>' : '<comment>extension not loaded</comment>')
            . ', ' . ($hasSystemUnzip ? '<comment>unzip present</comment>' : '<comment>unzip not available</comment>')
            . ', ' . ($hasSystem7zip ? '<comment>7-Zip present ('.$bin7zip.')</comment>' : '<comment>7-Zip not available</comment>')
            . (($hasSystem7zip || $hasSystemUnzip) && !function_exists('proc_open') ? ', <warning>proc_open is disabled or not present, unzip/7-z will not be usable</warning>' : '')
        );

        if ($composer) {
            $io->write('Active plugins: '.implode(', ', $composer->getPluginManager()->getRegisteredPlugins()));

            $io->write('Checking composer.json: ', false);
            $this->outputResult($this->checkComposerSchema());

            if ($composer->getLocker()->isLocked()) {
                $io->write('Checking composer.lock: ', false);
                $this->outputResult($this->checkComposerLockSchema($composer->getLocker()));
            }
        }

        $io->write('Checking platform settings: ', false);
        $this->outputResult($this->checkPlatform());

        $io->write('Checking git settings: ', false);
        $this->outputResult($this->checkGit());

        $io->write('Checking http connectivity to packagist: ', false);
        $this->outputResult($this->checkHttp('http', $config));

        $io->write('Checking https connectivity to packagist: ', false);
        $this->outputResult($this->checkHttp('https', $config));

        foreach ($config->getRepositories() as $repo) {
            if (($repo['type'] ?? null) === 'composer' && isset($repo['url'])) {
                $composerRepo = new ComposerRepository($repo, $this->getIO(), $config, $this->httpDownloader);
                $reflMethod = new \ReflectionMethod($composerRepo, 'getPackagesJsonUrl');
                if (PHP_VERSION_ID < 80100) {
                    $reflMethod->setAccessible(true);
                }
                $url = $reflMethod->invoke($composerRepo);
                if (!str_starts_with($url, 'http')) {
                    continue;
                }
                if (str_starts_with($url, 'https://repo.packagist.org')) {
                    continue;
                }
                $io->write('Checking connectivity to ' . $repo['url'].': ', false);
                $this->outputResult($this->checkComposerRepo($url, $config));
            }
        }

        $proxyManager = ProxyManager::getInstance();
        $protos = $config->get('disable-tls') === true ? ['http'] : ['http', 'https'];
        try {
            foreach ($protos as $proto) {
                $proxy = $proxyManager->getProxyForRequest($proto.'://repo.packagist.org');
                if ($proxy->getStatus() !== '') {
                    $type = $proxy->isSecure() ? 'HTTPS' : 'HTTP';
                    $io->write('Checking '.$type.' proxy with '.$proto.': ', false);
                    $this->outputResult($this->checkHttpProxy($proxy, $proto));
                }
            }
        } catch (TransportException $e) {
            $io->write('Checking HTTP proxy: ', false);
            $status = $this->checkConnectivityAndComposerNetworkHttpEnablement();
            $this->outputResult(is_string($status) ? $status : $e);
        }

        if (count($oauth = $config->get('github-oauth')) > 0) {
            foreach ($oauth as $domain => $token) {
                $io->write('Checking '.$domain.' oauth access: ', false);
                $this->outputResult($this->checkGithubOauth($domain, $token));
            }
        } else {
            $io->write('Checking github.com rate limit: ', false);
            try {
                $rate = $this->getGithubRateLimit('github.com');
                if (!is_array($rate)) {
                    $this->outputResult($rate);
                } elseif (10 > $rate['remaining']) {
                    $io->write('<warning>WARNING</warning>');
                    $io->write(sprintf(
                        '<comment>GitHub has a rate limit on their API. '
                        . 'You currently have <options=bold>%u</options=bold> '
                        . 'out of <options=bold>%u</options=bold> requests left.' . PHP_EOL
                        . 'See https://developer.github.com/v3/#rate-limiting and also' . PHP_EOL
                        . '    https://getcomposer.org/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens</comment>',
                        $rate['remaining'],
                        $rate['limit']
                    ));
                } else {
                    $this->outputResult(true);
                }
            } catch (\Exception $e) {
                if ($e instanceof TransportException && $e->getCode() === 401) {
                    $this->outputResult('<comment>The oauth token for github.com seems invalid, run "composer config --global --unset github-oauth.github.com" to remove it</comment>');
                } else {
                    $this->outputResult($e);
                }
            }
        }

        $io->write('Checking disk free space: ', false);
        $this->outputResult($this->checkDiskSpace($config));

        return $this->exitCode;
    }

    /**
     * @return string|true
     */
    private function checkComposerSchema()
    {
        $validator = new ConfigValidator($this->getIO());
        [$errors, , $warnings] = $validator->validate(Factory::getComposerFile());

        if ($errors || $warnings) {
            $messages = [
                'error' => $errors,
                'warning' => $warnings,
            ];

            $output = '';
            foreach ($messages as $style => $msgs) {
                foreach ($msgs as $msg) {
                    $output .= '<' . $style . '>' . $msg . '</' . $style . '>' . PHP_EOL;
                }
            }

            return rtrim($output);
        }

        return true;
    }

    /**
     * @return string|true
     */
    private function checkComposerLockSchema(Locker $locker)
    {
        $json = $locker->getJsonFile();

        try {
            $json->validateSchema(JsonFile::LOCK_SCHEMA);
        } catch (JsonValidationException $e) {
            $output = '';
            foreach ($e->getErrors() as $error) {
                $output .= '<error>'.$error.'</error>'.PHP_EOL;
            }

            return trim($output);
        }

        return true;
    }

    private function checkGit(): string
    {
        if (!function_exists('proc_open')) {
            return '<comment>proc_open is not available, git cannot be used</comment>';
        }

        $this->process->execute(['git', 'config', 'color.ui'], $output);
        if (strtolower(trim($output)) === 'always') {
            return '<comment>Your git color.ui setting is set to always, this is known to create issues. Use "git config --global color.ui true" to set it correctly.</comment>';
        }

        $gitVersion = Git::getVersion($this->process);
        if (null === $gitVersion) {
            return '<comment>No git process found</>';
        }

        if (version_compare('2.24.0', $gitVersion, '>')) {
            return '<warning>Your git version ('.$gitVersion.') is too old and possibly will cause issues. Please upgrade to git 2.24 or above</>';
        }

        return '<info>OK</> <comment>git version '.$gitVersion.'</>';
    }

    /**
     * @return string|string[]|true
     */
    private function checkHttp(string $proto, Config $config)
    {
        $result = $this->checkConnectivityAndComposerNetworkHttpEnablement();
        if ($result !== true) {
            return $result;
        }

        $result = [];
        if ($proto === 'https' && $config->get('disable-tls') === true) {
            $tlsWarning = '<warning>Composer is configured to disable SSL/TLS protection. This will leave remote HTTPS requests vulnerable to Man-In-The-Middle attacks.</warning>';
        }

        try {
            $this->httpDownloader->get($proto . '://repo.packagist.org/packages.json');
        } catch (TransportException $e) {
            $hints = HttpDownloader::getExceptionHints($e);
            if (null !== $hints && count($hints) > 0) {
                foreach ($hints as $hint) {
                    $result[] = $hint;
                }
            }

            $result[] = '<error>[' . get_class($e) . '] ' . $e->getMessage() . '</error>';
        }

        if (isset($tlsWarning)) {
            $result[] = $tlsWarning;
        }

        if (count($result) > 0) {
            return $result;
        }

        return true;
    }

    /**
     * @return string|string[]|true
     */
    private function checkComposerRepo(string $url, Config $config)
    {
        $result = $this->checkConnectivityAndComposerNetworkHttpEnablement();
        if ($result !== true) {
            return $result;
        }

        $result = [];
        if (str_starts_with($url, 'https://') && $config->get('disable-tls') === true) {
            $tlsWarning = '<warning>Composer is configured to disable SSL/TLS protection. This will leave remote HTTPS requests vulnerable to Man-In-The-Middle attacks.</warning>';
        }

        try {
            $this->httpDownloader->get($url);
        } catch (TransportException $e) {
            $hints = HttpDownloader::getExceptionHints($e);
            if (null !== $hints && count($hints) > 0) {
                foreach ($hints as $hint) {
                    $result[] = $hint;
                }
            }

            $result[] = '<error>[' . get_class($e) . '] ' . $e->getMessage() . '</error>';
        }

        if (isset($tlsWarning)) {
            $result[] = $tlsWarning;
        }

        if (count($result) > 0) {
            return $result;
        }

        return true;
    }

    /**
     * @return string|\Exception
     */
    private function checkHttpProxy(RequestProxy $proxy, string $protocol)
    {
        $result = $this->checkConnectivityAndComposerNetworkHttpEnablement();
        if ($result !== true) {
            return $result;
        }

        try {
            $proxyStatus = $proxy->getStatus();

            if ($proxy->isExcludedByNoProxy()) {
                return '<info>SKIP</> <comment>Because repo.packagist.org is '.$proxyStatus.'</>';
            }

            $json = $this->httpDownloader->get($protocol.'://repo.packagist.org/packages.json')->decodeJson();
            if (isset($json['provider-includes'])) {
                $hash = reset($json['provider-includes']);
                $hash = $hash['sha256'];
                $path = str_replace('%hash%', $hash, key($json['provider-includes']));
                $provider = $this->httpDownloader->get($protocol.'://repo.packagist.org/'.$path)->getBody();

                if (hash('sha256', $provider) !== $hash) {
                    return '<warning>It seems that your proxy ('.$proxyStatus.') is modifying '.$protocol.' traffic on the fly</>';
                }
            }

            return '<info>OK</> <comment>'.$proxyStatus.'</>';
        } catch (\Exception $e) {
            return $e;
        }
    }

    /**
     * @return string|\Exception
     */
    private function checkGithubOauth(string $domain, string $token)
    {
        $result = $this->checkConnectivityAndComposerNetworkHttpEnablement();
        if ($result !== true) {
            return $result;
        }

        $this->getIO()->setAuthentication($domain, $token, 'x-oauth-basic');
        try {
            $url = $domain === 'github.com' ? 'https://api.'.$domain.'/' : 'https://'.$domain.'/api/v3/';

            $response = $this->httpDownloader->get($url, [
                'retry-auth-failure' => false,
            ]);

            $expiration = $response->getHeader('github-authentication-token-expiration');

            if ($expiration === null) {
                return '<info>OK</> <comment>does not expire</>';
            }

            return '<info>OK</> <comment>expires on '. $expiration .'</>';
        } catch (\Exception $e) {
            if ($e instanceof TransportException && $e->getCode() === 401) {
                return '<comment>The oauth token for '.$domain.' seems invalid, run "composer config --global --unset github-oauth.'.$domain.'" to remove it</comment>';
            }

            return $e;
        }
    }

    /**
     * @throws TransportException
     * @return mixed|string
     */
    private function getGithubRateLimit(string $domain, ?string $token = null)
    {
        $result = $this->checkConnectivityAndComposerNetworkHttpEnablement();
        if ($result !== true) {
            return $result;
        }

        if ($token) {
            $this->getIO()->setAuthentication($domain, $token, 'x-oauth-basic');
        }

        $url = $domain === 'github.com' ? 'https://api.'.$domain.'/rate_limit' : 'https://'.$domain.'/api/rate_limit';
        $data = $this->httpDownloader->get($url, ['retry-auth-failure' => false])->decodeJson();

        return $data['resources']['core'];
    }

    /**
     * @return string|true
     */
    private function checkDiskSpace(Config $config)
    {
        if (!function_exists('disk_free_space')) {
            return true;
        }

        $minSpaceFree = 1024 * 1024;
        if ((($df = @disk_free_space($dir = $config->get('home'))) !== false && $df < $minSpaceFree)
            || (($df = @disk_free_space($dir = $config->get('vendor-dir'))) !== false && $df < $minSpaceFree)
        ) {
            return '<error>The disk hosting '.$dir.' is full</error>';
        }

        return true;
    }

    /**
     * @return string[]|true
     */
    private function checkPubKeys(Config $config)
    {
        $home = $config->get('home');
        $errors = [];
        $io = $this->getIO();

        if (file_exists($home.'/keys.tags.pub') && file_exists($home.'/keys.dev.pub')) {
            $io->write('');
        }

        if (file_exists($home.'/keys.tags.pub')) {
            $io->write('Tags Public Key Fingerprint: ' . Keys::fingerprint($home.'/keys.tags.pub'));
        } else {
            $errors[] = '<error>Missing pubkey for tags verification</error>';
        }

        if (file_exists($home.'/keys.dev.pub')) {
            $io->write('Dev Public Key Fingerprint: ' . Keys::fingerprint($home.'/keys.dev.pub'));
        } else {
            $errors[] = '<error>Missing pubkey for dev verification</error>';
        }

        if ($errors) {
            $errors[] = '<error>Run composer self-update --update-keys to set them up</error>';
        }

        return $errors ?: true;
    }

    /**
     * @return string|\Exception|true
     */
    private function checkVersion(Config $config)
    {
        $result = $this->checkConnectivityAndComposerNetworkHttpEnablement();
        if ($result !== true) {
            return $result;
        }

        $versionsUtil = new Versions($config, $this->httpDownloader);
        try {
            $latest = $versionsUtil->getLatest();
        } catch (\Exception $e) {
            return $e;
        }

        if (Composer::VERSION !== $latest['version'] && Composer::VERSION !== '@package_version@') {
            return '<comment>You are not running the latest '.$versionsUtil->getChannel().' version, run `composer self-update` to update ('.Composer::VERSION.' => '.$latest['version'].')</comment>';
        }

        return true;
    }

    /**
     * @return string|true
     */
    private function checkComposerAudit(Config $config)
    {
        $result = $this->checkConnectivityAndComposerNetworkHttpEnablement();
        if ($result !== true) {
            return $result;
        }

        $auditor = new Auditor();
        $repoSet = new RepositorySet();
        $installedJson = new JsonFile(__DIR__ . '/../../../vendor/composer/installed.json');
        if (!$installedJson->exists()) {
            return '<warning>Could not find Composer\'s installed.json, this must be a non-standard Composer installation.</>';
        }

        $localRepo = new FilesystemRepository($installedJson);
        $version = Composer::getVersion();
        $packages = $localRepo->getCanonicalPackages();
        if ($version !== '@package_version@') {
            $versionParser = new VersionParser();
            $normalizedVersion = $versionParser->normalize($version);
            $rootPkg = new RootPackage('composer/composer', $normalizedVersion, $version);
            $packages[] = $rootPkg;
        }
        $repoSet->addRepository(new ComposerRepository(['type' => 'composer', 'url' => 'https://packagist.org'], new NullIO(), $config, $this->httpDownloader));

        try {
            $io = new BufferIO();
            $result = $auditor->audit($io, $repoSet, $packages, Auditor::FORMAT_TABLE, true, [], Auditor::ABANDONED_IGNORE);
        } catch (\Throwable $e) {
            return '<highlight>Failed performing audit: '.$e->getMessage().'</>';
        }

        if ($result > 0) {
            return '<highlight>Audit found some issues:</>' . PHP_EOL . $io->getOutput();
        }

        return true;
    }

    private function getCurlVersion(): string
    {
        if (extension_loaded('curl')) {
            if (!HttpDownloader::isCurlEnabled()) {
                return '<error>disabled via disable_functions, using php streams fallback, which reduces performance</error>';
            }

            $version = curl_version();
            $libzVersion = isset($version['libz_version']) && $version['libz_version'] !== '' ? $version['libz_version'] : 'missing';
            $brotliVersion = isset($version['brotli_version']) && $version['brotli_version'] !== '' ? $version['brotli_version'] : 'missing';
            $sslVersion = isset($version['ssl_version']) && $version['ssl_version'] !== '' ? $version['ssl_version'] : 'missing';
            $hasZstd = isset($version['features']) && defined('CURL_VERSION_ZSTD') && 0 !== ($version['features'] & CURL_VERSION_ZSTD);
            $httpVersions = '1.0, 1.1';
            if (isset($version['features']) && \defined('CURL_VERSION_HTTP2') && \defined('CURL_HTTP_VERSION_2_0') && (CURL_VERSION_HTTP2 & $version['features']) !== 0) {
                $httpVersions .= ', 2';
            }
            if (isset($version['features']) && \defined('CURL_VERSION_HTTP3') && ($version['features'] & CURL_VERSION_HTTP3) !== 0) {
                $httpVersions .= ', 3';
            }

            return '<comment>'.$version['version'].'</comment> '.
                'libz <comment>'.$libzVersion.'</comment> '.
                'brotli <comment>'.$brotliVersion.'</comment> '.
                'zstd <comment>'.($hasZstd ? 'supported' : 'missing').'</comment> '.
                'ssl <comment>'.$sslVersion.'</comment> '.
                'HTTP <comment>'.$httpVersions.'</comment>';
        }

        return '<error>missing, using php streams fallback, which reduces performance</error>';
    }

    /**
     * @param bool|string|string[]|\Exception $result
     */
    private function outputResult($result): void
    {
        $io = $this->getIO();
        if (true === $result) {
            $io->write('<info>OK</info>');

            return;
        }

        $hadError = false;
        $hadWarning = false;
        if ($result instanceof \Exception) {
            $result = '<error>['.get_class($result).'] '.$result->getMessage().'</error>';
        }

        if (!$result) {
            // falsey results should be considered as an error, even if there is nothing to output
            $hadError = true;
        } else {
            if (!is_array($result)) {
                $result = [$result];
            }
            foreach ($result as $message) {
                if (false !== strpos($message, '<error>')) {
                    $hadError = true;
                } elseif (false !== strpos($message, '<warning>')) {
                    $hadWarning = true;
                }
            }
        }

        if ($hadError) {
            $io->write('<error>FAIL</error>');
            $this->exitCode = max($this->exitCode, 2);
        } elseif ($hadWarning) {
            $io->write('<warning>WARNING</warning>');
            $this->exitCode = max($this->exitCode, 1);
        }

        if ($result) {
            foreach ($result as $message) {
                $io->write(trim($message));
            }
        }
    }

    /**
     * @return string|true
     */
    private function checkPlatform()
    {
        $output = '';
        $out = static function ($msg, $style) use (&$output): void {
            $output .= '<'.$style.'>'.$msg.'</'.$style.'>'.PHP_EOL;
        };

        // code below taken from getcomposer.org/installer, any changes should be made there and replicated here
        $errors = [];
        $warnings = [];
        $displayIniMessage = false;

        $iniMessage = PHP_EOL.PHP_EOL.IniHelper::getMessage();
        $iniMessage .= PHP_EOL.'If you can not modify the ini file, you can also run `php -d option=value` to modify ini values on the fly. You can use -d multiple times.';

        if (!function_exists('json_decode')) {
            $errors['json'] = true;
        }

        if (!extension_loaded('Phar')) {
            $errors['phar'] = true;
        }

        if (!extension_loaded('filter')) {
            $errors['filter'] = true;
        }

        if (!extension_loaded('hash')) {
            $errors['hash'] = true;
        }

        if (!extension_loaded('iconv') && !extension_loaded('mbstring')) {
            $errors['iconv_mbstring'] = true;
        }

        if (!filter_var(ini_get('allow_url_fopen'), FILTER_VALIDATE_BOOLEAN)) {
            $errors['allow_url_fopen'] = true;
        }

        if (extension_loaded('ionCube Loader') && ioncube_loader_iversion() < 40009) {
            $errors['ioncube'] = ioncube_loader_version();
        }

        if (\PHP_VERSION_ID < 70205) {
            $errors['php'] = PHP_VERSION;
        }

        if (!extension_loaded('openssl')) {
            $errors['openssl'] = true;
        }

        if (extension_loaded('openssl') && OPENSSL_VERSION_NUMBER < 0x1000100f) {
            $warnings['openssl_version'] = true;
        }

        if (!defined('HHVM_VERSION') && !extension_loaded('apcu') && filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN)) {
            $warnings['apc_cli'] = true;
        }

        if (!extension_loaded('zlib')) {
            $warnings['zlib'] = true;
        }

        ob_start();
        phpinfo(INFO_GENERAL);
        $phpinfo = ob_get_clean();
        if (is_string($phpinfo) && Preg::isMatchStrictGroups('{Configure Command(?: *</td><td class="v">| *=> *)(.*?)(?:</td>|$)}m', $phpinfo, $match)) {
            $configure = $match[1];

            if (str_contains($configure, '--enable-sigchild')) {
                $warnings['sigchild'] = true;
            }

            if (str_contains($configure, '--with-curlwrappers')) {
                $warnings['curlwrappers'] = true;
            }
        }

        if (filter_var(ini_get('xdebug.profiler_enabled'), FILTER_VALIDATE_BOOLEAN)) {
            $warnings['xdebug_profile'] = true;
        } elseif (XdebugHandler::isXdebugActive()) {
            $warnings['xdebug_loaded'] = true;
        }

        if (defined('PHP_WINDOWS_VERSION_BUILD')
            && (version_compare(PHP_VERSION, '7.2.23', '<')
            || (version_compare(PHP_VERSION, '7.3.0', '>=')
            && version_compare(PHP_VERSION, '7.3.10', '<')))) {
            $warnings['onedrive'] = PHP_VERSION;
        }

        if (extension_loaded('uopz')
            && !(filter_var(ini_get('uopz.disable'), FILTER_VALIDATE_BOOLEAN)
            || filter_var(ini_get('uopz.exit'), FILTER_VALIDATE_BOOLEAN))) {
            $warnings['uopz'] = true;
        }

        if (!empty($errors)) {
            foreach ($errors as $error => $current) {
                switch ($error) {
                    case 'json':
                        $text = PHP_EOL."The json extension is missing.".PHP_EOL;
                        $text .= "Install it or recompile php without --disable-json";
                        break;

                    case 'phar':
                        $text = PHP_EOL."The phar extension is missing.".PHP_EOL;
                        $text .= "Install it or recompile php without --disable-phar";
                        break;

                    case 'filter':
                        $text = PHP_EOL."The filter extension is missing.".PHP_EOL;
                        $text .= "Install it or recompile php without --disable-filter";
                        break;

                    case 'hash':
                        $text = PHP_EOL."The hash extension is missing.".PHP_EOL;
                        $text .= "Install it or recompile php without --disable-hash";
                        break;

                    case 'iconv_mbstring':
                        $text = PHP_EOL."The iconv OR mbstring extension is required and both are missing.".PHP_EOL;
                        $text .= "Install either of them or recompile php without --disable-iconv";
                        break;

                    case 'php':
                        $text = PHP_EOL."Your PHP ({$current}) is too old, you must upgrade to PHP 7.2.5 or higher.";
                        break;

                    case 'allow_url_fopen':
                        $text = PHP_EOL."The allow_url_fopen setting is incorrect.".PHP_EOL;
                        $text .= "Add the following to the end of your `php.ini`:".PHP_EOL;
                        $text .= "    allow_url_fopen = On";
                        $displayIniMessage = true;
                        break;

                    case 'ioncube':
                        $text = PHP_EOL."Your ionCube Loader extension ($current) is incompatible with Phar files.".PHP_EOL;
                        $text .= "Upgrade to ionCube 4.0.9 or higher or remove this line (path may be different) from your `php.ini` to disable it:".PHP_EOL;
                        $text .= "    zend_extension = /usr/lib/php5/20090626+lfs/ioncube_loader_lin_5.3.so";
                        $displayIniMessage = true;
                        break;

                    case 'openssl':
                        $text = PHP_EOL."The openssl extension is missing, which means that secure HTTPS transfers are impossible.".PHP_EOL;
                        $text .= "If possible you should enable it or recompile php with --with-openssl";
                        break;

                    default:
                        throw new \InvalidArgumentException(sprintf("DiagnoseCommand: Unknown error type \"%s\". Please report at https://github.com/composer/composer/issues/new.", $error));
                }
                $out($text, 'error');
            }

            $output .= PHP_EOL;
        }

        if (!empty($warnings)) {
            foreach ($warnings as $warning => $current) {
                switch ($warning) {
                    case 'apc_cli':
                        $text = "The apc.enable_cli setting is incorrect.".PHP_EOL;
                        $text .= "Add the following to the end of your `php.ini`:".PHP_EOL;
                        $text .= "  apc.enable_cli = Off";
                        $displayIniMessage = true;
                        break;

                    case 'zlib':
                        $text = 'The zlib extension is not loaded, this can slow down Composer a lot.'.PHP_EOL;
                        $text .= 'If possible, enable it or recompile php with --with-zlib'.PHP_EOL;
                        $displayIniMessage = true;
                        break;

                    case 'sigchild':
                        $text = "PHP was compiled with --enable-sigchild which can cause issues on some platforms.".PHP_EOL;
                        $text .= "Recompile it without this flag if possible, see also:".PHP_EOL;
                        $text .= "  https://bugs.php.net/bug.php?id=22999";
                        break;

                    case 'curlwrappers':
                        $text = "PHP was compiled with --with-curlwrappers which will cause issues with HTTP authentication and GitHub.".PHP_EOL;
                        $text .= " Recompile it without this flag if possible";
                        break;

                    case 'openssl_version':
                        // Attempt to parse version number out, fallback to whole string value.
                        $opensslVersion = strstr(trim(strstr(OPENSSL_VERSION_TEXT, ' ')), ' ', true);
                        $opensslVersion = $opensslVersion ?: OPENSSL_VERSION_TEXT;

                        $text = "The OpenSSL library ({$opensslVersion}) used by PHP does not support TLSv1.2 or TLSv1.1.".PHP_EOL;
                        $text .= "If possible you should upgrade OpenSSL to version 1.0.1 or above.";
                        break;

                    case 'xdebug_loaded':
                        $text = "The xdebug extension is loaded, this can slow down Composer a little.".PHP_EOL;
                        $text .= " Disabling it when using Composer is recommended.";
                        break;

                    case 'xdebug_profile':
                        $text = "The xdebug.profiler_enabled setting is enabled, this can slow down Composer a lot.".PHP_EOL;
                        $text .= "Add the following to the end of your `php.ini` to disable it:".PHP_EOL;
                        $text .= "  xdebug.profiler_enabled = 0";
                        $displayIniMessage = true;
                        break;

                    case 'onedrive':
                        $text = "The Windows OneDrive folder is not supported on PHP versions below 7.2.23 and 7.3.10.".PHP_EOL;
                        $text .= "Upgrade your PHP ({$current}) to use this location with Composer.".PHP_EOL;
                        break;

                    case 'uopz':
                        $text = "The uopz extension ignores exit calls and may not work with all Composer commands.".PHP_EOL;
                        $text .= "Disabling it when using Composer is recommended.";
                        break;

                    default:
                        throw new \InvalidArgumentException(sprintf("DiagnoseCommand: Unknown warning type \"%s\". Please report at https://github.com/composer/composer/issues/new.", $warning));
                }
                $out($text, 'comment');
            }
        }

        if ($displayIniMessage) {
            $out($iniMessage, 'comment');
        }

        if (in_array(Platform::getEnv('COMPOSER_IPRESOLVE'), ['4', '6'], true)) {
            $warnings['ipresolve'] = true;
            $out('The COMPOSER_IPRESOLVE env var is set to ' . Platform::getEnv('COMPOSER_IPRESOLVE') .' which may result in network failures below.', 'comment');
        }

        return count($warnings) === 0 && count($errors) === 0 ? true : $output;
    }

    /**
     * Check if allow_url_fopen is ON
     *
     * @return string|true
     */
    private function checkConnectivity()
    {
        if (!ini_get('allow_url_fopen')) {
            return '<info>SKIP</> <comment>Because allow_url_fopen is missing.</>';
        }

        return true;
    }

    /**
     * @return string|true
     */
    private function checkConnectivityAndComposerNetworkHttpEnablement()
    {
        $result = $this->checkConnectivity();
        if ($result !== true) {
            return $result;
        }

        $result = $this->checkComposerNetworkHttpEnablement();
        if ($result !== true) {
            return $result;
        }

        return true;
    }

    /**
     * Check if Composer network is enabled for HTTP/S
     *
     * @return string|true
     */
    private function checkComposerNetworkHttpEnablement()
    {
        if ((bool) Platform::getEnv('COMPOSER_DISABLE_NETWORK')) {
            return '<info>SKIP</> <comment>Network is disabled by COMPOSER_DISABLE_NETWORK.</>';
        }

        return true;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Script\Event as ScriptEvent;
use Composer\Script\ScriptEvents;
use Composer\Util\ProcessExecutor;
use Composer\Util\Platform;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Composer\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Fabien Potencier <fabien.potencier@gmail.com>
 */
class RunScriptCommand extends BaseCommand
{
    /**
     * @var string[] Array with command events
     */
    protected $scriptEvents = [
        ScriptEvents::PRE_INSTALL_CMD,
        ScriptEvents::POST_INSTALL_CMD,
        ScriptEvents::PRE_UPDATE_CMD,
        ScriptEvents::POST_UPDATE_CMD,
        ScriptEvents::PRE_STATUS_CMD,
        ScriptEvents::POST_STATUS_CMD,
        ScriptEvents::POST_ROOT_PACKAGE_INSTALL,
        ScriptEvents::POST_CREATE_PROJECT_CMD,
        ScriptEvents::PRE_ARCHIVE_CMD,
        ScriptEvents::POST_ARCHIVE_CMD,
        ScriptEvents::PRE_AUTOLOAD_DUMP,
        ScriptEvents::POST_AUTOLOAD_DUMP,
    ];

    protected function configure(): void
    {
        $this
            ->setName('run-script')
            ->setAliases(['run'])
            ->setDescription('Runs the scripts defined in composer.json')
            ->setDefinition([
                new InputArgument('script', InputArgument::OPTIONAL, 'Script name to run.', null, function () {
                    return array_map(static function ($script) { return $script['name']; }, $this->getScripts());
                }),
                new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''),
                new InputOption('timeout', null, InputOption::VALUE_REQUIRED, 'Sets script timeout in seconds, or 0 for never.'),
                new InputOption('dev', null, InputOption::VALUE_NONE, 'Sets the dev mode.'),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables the dev mode.'),
                new InputOption('list', 'l', InputOption::VALUE_NONE, 'List scripts.'),
            ])
            ->setHelp(
                <<<EOT
The <info>run-script</info> command runs scripts defined in composer.json:

<info>php composer.phar run-script post-update-cmd</info>

Read more at https://getcomposer.org/doc/03-cli.md#run-script-run
EOT
            )
        ;
    }

    protected function interact(InputInterface $input, OutputInterface $output): void
    {
        $scripts = $this->getScripts();
        if (count($scripts) === 0) {
            return;
        }

        if ($input->getArgument('script') !== null || $input->getOption('list')) {
            return;
        }

        $options = [];
        foreach ($scripts as $script) {
            $options[$script['name']] = $script['description'];
        }
        $io = $this->getIO();
        $script = $io->select(
            'Script to run: ',
            $options,
            '',
            1,
            'Invalid script name "%s"'
        );

        $input->setArgument('script', $script);
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        if ($input->getOption('list')) {
            return $this->listScripts($output);
        }

        $script = $input->getArgument('script');
        if ($script === null) {
            throw new \RuntimeException('Missing required argument "script"');
        }

        if (!in_array($script, $this->scriptEvents)) {
            if (defined('Composer\Script\ScriptEvents::'.str_replace('-', '_', strtoupper($script)))) {
                throw new \InvalidArgumentException(sprintf('Script "%s" cannot be run with this command', $script));
            }
        }

        $composer = $this->requireComposer();
        $devMode = $input->getOption('dev') || !$input->getOption('no-dev');
        $event = new ScriptEvent($script, $composer, $this->getIO(), $devMode);
        $hasListeners = $composer->getEventDispatcher()->hasEventListeners($event);
        if (!$hasListeners) {
            throw new \InvalidArgumentException(sprintf('Script "%s" is not defined in this package', $script));
        }

        $args = $input->getArgument('args');

        if (null !== $timeout = $input->getOption('timeout')) {
            if (!ctype_digit($timeout)) {
                throw new \RuntimeException('Timeout value must be numeric and positive if defined, or 0 for forever');
            }
            // Override global timeout set before in Composer by environment or config
            ProcessExecutor::setTimeout((int) $timeout);
        }

        Platform::putEnv('COMPOSER_DEV_MODE', $devMode ? '1' : '0');

        return $composer->getEventDispatcher()->dispatchScript($script, $devMode, $args);
    }

    protected function listScripts(OutputInterface $output): int
    {
        $scripts = $this->getScripts();
        if (count($scripts) === 0) {
            return 0;
        }

        $io = $this->getIO();
        $io->writeError('<info>scripts:</info>');
        $table = [];
        foreach ($scripts as $script) {
            $table[] = ['  '.$script['name'], $script['description']];
        }

        $this->renderTable($table, $output);

        return 0;
    }

    /**
     * @return list<array{name: string, description: string}>
     */
    private function getScripts(): array
    {
        $scripts = $this->requireComposer()->getPackage()->getScripts();
        if (count($scripts) === 0) {
            return [];
        }

        $result = [];
        foreach ($scripts as $name => $script) {
            $description = '';
            try {
                $cmd = $this->getApplication()->find($name);
                $description = $cmd->getDescription();
            } catch (\Symfony\Component\Console\Exception\CommandNotFoundException $e) {
                // ignore scripts that have no command associated, like native Composer script listeners
            }
            $result[] = ['name' => $name, 'description' => $description];
        }

        return $result;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\Package\Loader\ValidatingArrayLoader;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Util\ConfigValidator;
use Composer\Util\Filesystem;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * ValidateCommand
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ValidateCommand extends BaseCommand
{
    /**
     * configure
     */
    protected function configure(): void
    {
        $this
            ->setName('validate')
            ->setDescription('Validates a composer.json and composer.lock')
            ->setDefinition([
                new InputOption('no-check-all', null, InputOption::VALUE_NONE, 'Do not validate requires for overly strict/loose constraints'),
                new InputOption('check-lock', null, InputOption::VALUE_NONE, 'Check if lock file is up to date (even when config.lock is false)'),
                new InputOption('no-check-lock', null, InputOption::VALUE_NONE, 'Do not check if lock file is up to date'),
                new InputOption('no-check-publish', null, InputOption::VALUE_NONE, 'Do not check for publish errors'),
                new InputOption('no-check-version', null, InputOption::VALUE_NONE, 'Do not report a warning if the version field is present'),
                new InputOption('with-dependencies', 'A', InputOption::VALUE_NONE, 'Also validate the composer.json of all installed dependencies'),
                new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code for warnings as well as errors'),
                new InputArgument('file', InputArgument::OPTIONAL, 'path to composer.json file'),
            ])
            ->setHelp(
                <<<EOT
The validate command validates a given composer.json and composer.lock

Exit codes in case of errors are:
1 validation warning(s), only when --strict is given
2 validation error(s)
3 file unreadable or missing

Read more at https://getcomposer.org/doc/03-cli.md#validate
EOT
            );
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $file = $input->getArgument('file') ?? Factory::getComposerFile();
        $io = $this->getIO();

        if (!file_exists($file)) {
            $io->writeError('<error>' . $file . ' not found.</error>');

            return 3;
        }
        if (!Filesystem::isReadable($file)) {
            $io->writeError('<error>' . $file . ' is not readable.</error>');

            return 3;
        }

        $validator = new ConfigValidator($io);
        $checkAll = $input->getOption('no-check-all') ? 0 : ValidatingArrayLoader::CHECK_ALL;
        $checkPublish = !$input->getOption('no-check-publish');
        $checkLock = !$input->getOption('no-check-lock');
        $checkVersion = $input->getOption('no-check-version') ? 0 : ConfigValidator::CHECK_VERSION;
        $isStrict = $input->getOption('strict');
        [$errors, $publishErrors, $warnings] = $validator->validate($file, $checkAll, $checkVersion);

        $lockErrors = [];
        $composer = $this->createComposerInstance($input, $io, $file);
        // config.lock = false ~= implicit --no-check-lock; --check-lock overrides
        $checkLock = ($checkLock && $composer->getConfig()->get('lock')) || $input->getOption('check-lock');
        $locker = $composer->getLocker();
        if ($locker->isLocked() && !$locker->isFresh()) {
            $lockErrors[] = '- The lock file is not up to date with the latest changes in composer.json, it is recommended that you run `composer update` or `composer update <package name>`.';
        }

        if ($locker->isLocked()) {
            $lockErrors = array_merge($lockErrors, $locker->getMissingRequirementInfo($composer->getPackage(), true));
        }

        $this->outputResult($io, $file, $errors, $warnings, $checkPublish, $publishErrors, $checkLock, $lockErrors, true);

        // $errors include publish and lock errors when exists
        $exitCode = count($errors) > 0 ? 2 : (($isStrict && count($warnings) > 0) ? 1 : 0);

        if ($input->getOption('with-dependencies')) {
            $localRepo = $composer->getRepositoryManager()->getLocalRepository();
            foreach ($localRepo->getPackages() as $package) {
                $path = $composer->getInstallationManager()->getInstallPath($package);
                if (null === $path) {
                    continue;
                }
                $file = $path . '/composer.json';
                if (is_dir($path) && file_exists($file)) {
                    [$errors, $publishErrors, $warnings] = $validator->validate($file, $checkAll, $checkVersion);

                    $this->outputResult($io, $package->getPrettyName(), $errors, $warnings, $checkPublish, $publishErrors);

                    // $errors include publish errors when exists
                    $depCode = count($errors) > 0 ? 2 : (($isStrict && count($warnings) > 0) ? 1 : 0);
                    $exitCode = max($depCode, $exitCode);
                }
            }
        }

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'validate', $input, $output);
        $eventCode = $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        return max($eventCode, $exitCode);
    }

    /**
     * @param string[] $errors
     * @param string[] $warnings
     * @param string[] $publishErrors
     * @param string[] $lockErrors
     */
    private function outputResult(IOInterface $io, string $name, array &$errors, array &$warnings, bool $checkPublish = false, array $publishErrors = [], bool $checkLock = false, array $lockErrors = [], bool $printSchemaUrl = false): void
    {
        $doPrintSchemaUrl = false;

        if (\count($errors) > 0) {
            $io->writeError('<error>' . $name . ' is invalid, the following errors/warnings were found:</error>');
        } elseif (\count($publishErrors) > 0 && $checkPublish) {
            $io->writeError('<info>' . $name . ' is valid for simple usage with Composer but has</info>');
            $io->writeError('<info>strict errors that make it unable to be published as a package</info>');
            $doPrintSchemaUrl = $printSchemaUrl;
        } elseif (\count($warnings) > 0) {
            $io->writeError('<info>' . $name . ' is valid, but with a few warnings</info>');
            $doPrintSchemaUrl = $printSchemaUrl;
        } elseif (\count($lockErrors) > 0) {
            $io->write('<info>' . $name . ' is valid but your composer.lock has some '.($checkLock ? 'errors' : 'warnings').'</info>');
        } else {
            $io->write('<info>' . $name . ' is valid</info>');
        }

        if ($doPrintSchemaUrl) {
            $io->writeError('<warning>See https://getcomposer.org/doc/04-schema.md for details on the schema</warning>');
        }

        if (\count($errors) > 0) {
            $errors = array_map(static function ($err): string {
                return '- ' . $err;
            }, $errors);
            array_unshift($errors, '# General errors');
        }
        if (\count($warnings) > 0) {
            $warnings = array_map(static function ($err): string {
                return '- ' . $err;
            }, $warnings);
            array_unshift($warnings, '# General warnings');
        }

        // Avoid setting the exit code to 1 in case --strict and --no-check-publish/--no-check-lock are combined
        $extraWarnings = [];

        // If checking publish errors, display them as errors, otherwise just show them as warnings
        if (\count($publishErrors) > 0 && $checkPublish) {
            $publishErrors = array_map(static function ($err): string {
                return '- ' . $err;
            }, $publishErrors);

            array_unshift($publishErrors, '# Publish errors');
            $errors = array_merge($errors, $publishErrors);
        }

        // If checking lock errors, display them as errors, otherwise just show them as warnings
        if (\count($lockErrors) > 0) {
            if ($checkLock) {
                array_unshift($lockErrors, '# Lock file errors');
                $errors = array_merge($errors, $lockErrors);
            } else {
                array_unshift($lockErrors, '# Lock file warnings');
                $extraWarnings = array_merge($extraWarnings, $lockErrors);
            }
        }

        $messages = [
            'error' => $errors,
            'warning' => array_merge($warnings, $extraWarnings),
        ];

        foreach ($messages as $style => $msgs) {
            foreach ($msgs as $msg) {
                if (strpos($msg, '#') === 0) {
                    $io->writeError('<' . $style . '>' . $msg . '</' . $style . '>');
                } else {
                    $io->writeError($msg);
                }
            }
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Config;
use Composer\Factory;
use Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
use Composer\Installer;
use Composer\Installer\ProjectInstaller;
use Composer\Installer\SuggestedPackagesReporter;
use Composer\IO\IOInterface;
use Composer\Package\BasePackage;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\Package\Version\VersionSelector;
use Composer\Package\AliasPackage;
use Composer\Pcre\Preg;
use Composer\Plugin\PluginBlockedException;
use Composer\Repository\RepositoryFactory;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\InstalledArrayRepository;
use Composer\Repository\RepositorySet;
use Composer\Script\ScriptEvents;
use Composer\Console\Input\InputArgument;
use Seld\Signal\SignalHandler;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Finder\Finder;
use Composer\Json\JsonFile;
use Composer\Config\JsonConfigSource;
use Composer\Util\Filesystem;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Package\Version\VersionParser;
use Composer\Advisory\Auditor;

/**
 * Install a package as new project into new directory.
 *
 * @author Benjamin Eberlei <kontakt@beberlei.de>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Tobias Munk <schmunk@usrbin.de>
 * @author Nils Adermann <naderman@naderman.de>
 */
class CreateProjectCommand extends BaseCommand
{
    use CompletionTrait;

    /**
     * @var SuggestedPackagesReporter
     */
    protected $suggestedPackagesReporter;

    protected function configure(): void
    {
        $this
            ->setName('create-project')
            ->setDescription('Creates new project from a package into given directory')
            ->setDefinition([
                new InputArgument('package', InputArgument::OPTIONAL, 'Package name to be installed', null, $this->suggestAvailablePackage()),
                new InputArgument('directory', InputArgument::OPTIONAL, 'Directory where the files should be created'),
                new InputArgument('version', InputArgument::OPTIONAL, 'Version, will default to latest'),
                new InputOption('stability', 's', InputOption::VALUE_REQUIRED, 'Minimum-stability allowed (unless a version is specified).'),
                new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
                new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'),
                new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()),
                new InputOption('repository', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Add custom repositories to look the package up, either by URL or using JSON arrays'),
                new InputOption('repository-url', null, InputOption::VALUE_REQUIRED, 'DEPRECATED: Use --repository instead.'),
                new InputOption('add-repository', null, InputOption::VALUE_NONE, 'Add the custom repository in the composer.json. If a lock file is present it will be deleted and an update will be run instead of install.'),
                new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of require-dev packages (enabled by default, only present for BC).'),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'),
                new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'),
                new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Whether to prevent execution of all defined scripts in the root package.'),
                new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                new InputOption('no-secure-http', null, InputOption::VALUE_NONE, 'Disable the secure-http config option temporarily while installing the root package. Use at your own risk. Using this flag is a bad idea.'),
                new InputOption('keep-vcs', null, InputOption::VALUE_NONE, 'Whether to prevent deleting the vcs folder.'),
                new InputOption('remove-vcs', null, InputOption::VALUE_NONE, 'Whether to force deletion of the vcs folder without prompting.'),
                new InputOption('no-install', null, InputOption::VALUE_NONE, 'Whether to skip installation of the package dependencies.'),
                new InputOption('no-audit', null, InputOption::VALUE_NONE, 'Whether to skip auditing of the installed package dependencies (can also be set via the COMPOSER_NO_AUDIT=1 env var).'),
                new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", "json" or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS),
                new InputOption('no-security-blocking', null, InputOption::VALUE_NONE, 'Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var).'),
                new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'),
                new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'),
                new InputOption('ask', null, InputOption::VALUE_NONE, 'Whether to ask for project directory.'),
            ])
            ->setHelp(
                <<<EOT
The <info>create-project</info> command creates a new project from a given
package into a new directory. If executed without params and in a directory
with a composer.json file it installs the packages for the current project.

You can use this command to bootstrap new projects or setup a clean
version-controlled installation for developers of your project.

<info>php composer.phar create-project vendor/project target-directory [version]</info>

You can also specify the version with the package name using = or : as separator.

<info>php composer.phar create-project vendor/project:version target-directory</info>

To install unstable packages, either specify the version you want, or use the
--stability=dev (where dev can be one of RC, beta, alpha or dev).

To setup a developer workable version you should create the project using the source
controlled code by appending the <info>'--prefer-source'</info> flag.

To install a package from another repository than the default one you
can pass the <info>'--repository=https://myrepository.org'</info> flag.

Read more at https://getcomposer.org/doc/03-cli.md#create-project
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $config = Factory::createConfig();
        $io = $this->getIO();

        [$preferSource, $preferDist] = $this->getPreferredInstallOptions($config, $input, true);

        if ($input->getOption('dev')) {
            $io->writeError('<warning>You are using the deprecated option "dev". Dev packages are installed by default now.</warning>');
        }
        if ($input->getOption('no-custom-installers')) {
            $io->writeError('<warning>You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.</warning>');
            $input->setOption('no-plugins', true);
        }

        if ($input->isInteractive() && $input->getOption('ask')) {
            $package = $input->getArgument('package');
            if (null === $package) {
                throw new \RuntimeException('Not enough arguments (missing: "package").');
            }
            $parts = explode("/", strtolower($package), 2);
            $input->setArgument('directory', $io->ask('New project directory [<comment>'.array_pop($parts).'</comment>]: '));
        }

        return $this->installProject(
            $io,
            $config,
            $input,
            $input->getArgument('package'),
            $input->getArgument('directory'),
            $input->getArgument('version'),
            $input->getOption('stability'),
            $preferSource,
            $preferDist,
            !$input->getOption('no-dev'),
            \count($input->getOption('repository')) > 0 ? $input->getOption('repository') : $input->getOption('repository-url'),
            $input->getOption('no-plugins'),
            $input->getOption('no-scripts'),
            $input->getOption('no-progress'),
            $input->getOption('no-install'),
            $this->getPlatformRequirementFilter($input),
            !$input->getOption('no-secure-http'),
            $input->getOption('add-repository')
        );
    }

    /**
     * @param string|array<string>|null $repositories
     *
     * @throws \Exception
     */
    public function installProject(IOInterface $io, Config $config, InputInterface $input, ?string $packageName = null, ?string $directory = null, ?string $packageVersion = null, ?string $stability = 'stable', bool $preferSource = false, bool $preferDist = false, bool $installDevPackages = false, $repositories = null, bool $disablePlugins = false, bool $disableScripts = false, bool $noProgress = false, bool $noInstall = false, ?PlatformRequirementFilterInterface $platformRequirementFilter = null, bool $secureHttp = true, bool $addRepository = false): int
    {
        $oldCwd = Platform::getCwd();

        if ($repositories !== null && !is_array($repositories)) {
            $repositories = (array) $repositories;
        }

        $platformRequirementFilter = $platformRequirementFilter ?? PlatformRequirementFilterFactory::ignoreNothing();

        // we need to manually load the configuration to pass the auth credentials to the io interface!
        $io->loadConfiguration($config);

        $this->suggestedPackagesReporter = new SuggestedPackagesReporter($io);

        if ($packageName !== null) {
            $installedFromVcs = $this->installRootPackage($input, $io, $config, $packageName, $platformRequirementFilter, $directory, $packageVersion, $stability, $preferSource, $preferDist, $installDevPackages, $repositories, $disablePlugins, $disableScripts, $noProgress, $secureHttp);
        } else {
            $installedFromVcs = false;
        }

        if ($repositories !== null && $addRepository && is_file('composer.lock')) {
            unlink('composer.lock');
        }

        $composer = $this->createComposerInstance($input, $io, null, $disablePlugins, $disableScripts);

        // add the repository to the composer.json and use it for the install run later
        if ($repositories !== null && $addRepository) {
            foreach ($repositories as $index => $repo) {
                $repoConfig = RepositoryFactory::configFromString($io, $composer->getConfig(), $repo, true);
                $composerJsonRepositoriesConfig = $composer->getConfig()->getRepositories();
                $name = RepositoryFactory::generateRepositoryName($index, $repoConfig, $composerJsonRepositoriesConfig);
                $configSource = new JsonConfigSource(new JsonFile('composer.json'));

                if (
                    (isset($repoConfig['packagist']) && $repoConfig === ['packagist' => false])
                    || (isset($repoConfig['packagist.org']) && $repoConfig === ['packagist.org' => false])
                ) {
                    $configSource->addRepository('packagist.org', false);
                } else {
                    $configSource->addRepository($name, $repoConfig, false);
                }

                $composer = $this->createComposerInstance($input, $io, null, $disablePlugins);
            }
        }

        $process = $composer->getLoop()->getProcessExecutor();
        $fs = new Filesystem($process);

        // dispatch event
        $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_ROOT_PACKAGE_INSTALL, $installDevPackages);

        // use the new config including the newly installed project
        $config = $composer->getConfig();
        [$preferSource, $preferDist] = $this->getPreferredInstallOptions($config, $input);

        // install dependencies of the created project
        if ($noInstall === false) {
            $composer->getInstallationManager()->setOutputProgress(!$noProgress);

            $installer = Installer::create($io, $composer);
            $installer->setPreferSource($preferSource)
                ->setPreferDist($preferDist)
                ->setDevMode($installDevPackages)
                ->setPlatformRequirementFilter($platformRequirementFilter)
                ->setSuggestedPackagesReporter($this->suggestedPackagesReporter)
                ->setOptimizeAutoloader($config->get('optimize-autoloader'))
                ->setClassMapAuthoritative($config->get('classmap-authoritative'))
                ->setApcuAutoloader($config->get('apcu-autoloader'))
                ->setAuditConfig($this->createAuditConfig($config, $input));

            if (!$composer->getLocker()->isLocked()) {
                $installer->setUpdate(true);
            }

            if ($disablePlugins) {
                $installer->disablePlugins();
            }

            try {
                $status = $installer->run();
                if (0 !== $status) {
                    return $status;
                }
            } catch (PluginBlockedException $e) {
                $io->writeError('<error>Hint: To allow running the config command recommended below before dependencies are installed, run create-project with --no-install.</error>');
                $io->writeError('<error>You can then cd into '.getcwd().', configure allow-plugins, and finally run a composer install to complete the process.</error>');
                throw $e;
            }
        }

        $hasVcs = $installedFromVcs;
        if (
            !$input->getOption('keep-vcs')
            && $installedFromVcs
            && (
                $input->getOption('remove-vcs')
                || !$io->isInteractive()
                || $io->askConfirmation('<info>Do you want to remove the existing VCS (.git, .svn..) history?</info> [<comment>y,n</comment>]? ')
            )
        ) {
            $finder = new Finder();
            $finder->depth(0)->directories()->in(Platform::getCwd())->ignoreVCS(false)->ignoreDotFiles(false);
            foreach (['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg', '.fslckout', '_FOSSIL_'] as $vcsName) {
                $finder->name($vcsName);
            }

            try {
                $dirs = iterator_to_array($finder);
                unset($finder);
                foreach ($dirs as $dir) {
                    if (!$fs->removeDirectory((string) $dir)) {
                        throw new \RuntimeException('Could not remove '.$dir);
                    }
                }
            } catch (\Exception $e) {
                $io->writeError('<error>An error occurred while removing the VCS metadata: '.$e->getMessage().'</error>');
            }

            $hasVcs = false;
        }

        // rewriting self.version dependencies with explicit version numbers if the package's vcs metadata is gone
        if (!$hasVcs) {
            $package = $composer->getPackage();
            $configSource = new JsonConfigSource(new JsonFile('composer.json'));
            foreach (BasePackage::$supportedLinkTypes as $type => $meta) {
                foreach ($package->{'get'.$meta['method']}() as $link) {
                    if ($link->getPrettyConstraint() === 'self.version') {
                        $configSource->addLink($type, $link->getTarget(), $package->getPrettyVersion());
                    }
                }
            }
        }

        // dispatch event
        $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_CREATE_PROJECT_CMD, $installDevPackages);

        chdir($oldCwd);

        return 0;
    }

    /**
     * @param array<string>|null $repositories
     *
     * @throws \Exception
     */
    protected function installRootPackage(InputInterface $input, IOInterface $io, Config $config, string $packageName, PlatformRequirementFilterInterface $platformRequirementFilter, ?string $directory = null, ?string $packageVersion = null, ?string $stability = 'stable', bool $preferSource = false, bool $preferDist = false, bool $installDevPackages = false, ?array $repositories = null, bool $disablePlugins = false, bool $disableScripts = false, bool $noProgress = false, bool $secureHttp = true): bool
    {
        $parser = new VersionParser();
        $requirements = $parser->parseNameVersionPairs([$packageName]);
        $name = strtolower($requirements[0]['name']);
        if (!$packageVersion && isset($requirements[0]['version'])) {
            $packageVersion = $requirements[0]['version'];
        }

        // if no directory was specified, use the 2nd part of the package name
        if (null === $directory) {
            $parts = explode("/", $name, 2);
            $directory = Platform::getCwd() . DIRECTORY_SEPARATOR . array_pop($parts);
        }
        $directory = rtrim($directory, '/\\');

        $process = new ProcessExecutor($io);
        $fs = new Filesystem($process);
        if (!$fs->isAbsolutePath($directory)) {
            $directory = Platform::getCwd() . DIRECTORY_SEPARATOR . $directory;
        }
        if ('' === $directory) {
            throw new \UnexpectedValueException('Got an empty target directory, something went wrong');
        }

        // set the base dir to ensure $config->all() below resolves the correct absolute paths to vendor-dir etc
        $config->setBaseDir($directory);
        if (!$secureHttp) {
            $config->merge(['config' => ['secure-http' => false]], Config::SOURCE_COMMAND);
        }

        $io->writeError('<info>Creating a "' . $packageName . '" project at "' . $fs->findShortestPath(Platform::getCwd(), $directory, true) . '"</info>');

        if (file_exists($directory)) {
            if (!is_dir($directory)) {
                throw new \InvalidArgumentException('Cannot create project directory at "'.$directory.'", it exists as a file.');
            }
            if (!$fs->isDirEmpty($directory)) {
                throw new \InvalidArgumentException('Project directory "'.$directory.'" is not empty.');
            }
        }

        if (null === $stability) {
            if (null === $packageVersion) {
                $stability = 'stable';
            } elseif (Preg::isMatchStrictGroups('{^[^,\s]*?@('.implode('|', array_keys(BasePackage::STABILITIES)).')$}i', $packageVersion, $match)) {
                $stability = $match[1];
            } else {
                $stability = VersionParser::parseStability($packageVersion);
            }
        }

        $stability = VersionParser::normalizeStability($stability);

        if (!isset(BasePackage::STABILITIES[$stability])) {
            throw new \InvalidArgumentException('Invalid stability provided ('.$stability.'), must be one of: '.implode(', ', array_keys(BasePackage::STABILITIES)));
        }

        $composer = $this->createComposerInstance($input, $io, $config->all(), $disablePlugins, $disableScripts);
        $config = $composer->getConfig();
        // set the base dir here again on the new config instance, as otherwise in case the vendor dir is defined in an env var for example it would still override the value set above by $config->all()
        $config->setBaseDir($directory);
        $rm = $composer->getRepositoryManager();

        $repositorySet = new RepositorySet($stability);
        if (null === $repositories) {
            $repositorySet->addRepository(new CompositeRepository(RepositoryFactory::defaultRepos($io, $config, $rm)));
        } else {
            foreach ($repositories as $repo) {
                $repoConfig = RepositoryFactory::configFromString($io, $config, $repo, true);
                if (
                    (isset($repoConfig['packagist']) && $repoConfig === ['packagist' => false])
                    || (isset($repoConfig['packagist.org']) && $repoConfig === ['packagist.org' => false])
                ) {
                    continue;
                }

                // disable symlinking for the root package by default as that most likely makes no sense
                if (($repoConfig['type'] ?? null) === 'path' && !isset($repoConfig['options']['symlink'])) {
                    $repoConfig['options']['symlink'] = false;
                }

                $repositorySet->addRepository(RepositoryFactory::createRepo($io, $config, $repoConfig, $rm));
            }
        }

        $platformOverrides = $config->get('platform');
        $platformRepo = new PlatformRepository([], $platformOverrides);

        // find the latest version if there are multiple
        $versionSelector = new VersionSelector($repositorySet, $platformRepo);
        $package = $versionSelector->findBestCandidate($name, $packageVersion, $stability, $platformRequirementFilter, 0, $io);

        if (!$package) {
            $errorMessage = "Could not find package $name with " . ($packageVersion ? "version $packageVersion" : "stability $stability");
            if (!($platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter) && $versionSelector->findBestCandidate($name, $packageVersion, $stability, PlatformRequirementFilterFactory::ignoreAll())) {
                throw new \InvalidArgumentException($errorMessage .' in a version installable using your PHP version, PHP extensions and Composer version.');
            }

            throw new \InvalidArgumentException($errorMessage .'.');
        }

        // handler Ctrl+C aborts gracefully
        @mkdir($directory, 0777, true);
        if (false !== ($realDir = realpath($directory))) {
            $signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) use ($realDir) {
                $this->getIO()->writeError('Received '.$signal.', aborting', true, IOInterface::DEBUG);
                $fs = new Filesystem();
                $fs->removeDirectory($realDir);
                $handler->exitWithLastSignal();
            });
        }

        // avoid displaying 9999999-dev as version if default-branch was selected
        if ($package instanceof AliasPackage && $package->getPrettyVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) {
            $package = $package->getAliasOf();
        }

        $io->writeError('<info>Installing ' . $package->getName() . ' (' . $package->getFullPrettyVersion(false) . ')</info>');

        if ($disablePlugins) {
            $io->writeError('<info>Plugins have been disabled.</info>');
        }

        if ($package instanceof AliasPackage) {
            $package = $package->getAliasOf();
        }

        $dm = $composer->getDownloadManager();
        $dm->setPreferSource($preferSource)
            ->setPreferDist($preferDist);

        $projectInstaller = new ProjectInstaller($directory, $dm, $fs);
        $im = $composer->getInstallationManager();
        $im->setOutputProgress(!$noProgress);
        $im->addInstaller($projectInstaller);
        $im->execute(new InstalledArrayRepository(), [new InstallOperation($package)]);
        $im->notifyInstalls($io);

        // collect suggestions
        $this->suggestedPackagesReporter->addSuggestionsFromPackage($package);

        $installedFromVcs = 'source' === $package->getInstallationSource();

        $io->writeError('<info>Created project in ' . $directory . '</info>');
        chdir($directory);

        // ensure that the env var being set does not interfere with create-project
        // as it is probably not meant to be used here, so we do not use it if a composer.json can be found
        // in the project
        if (file_exists($directory.'/composer.json') && Platform::getEnv('COMPOSER') !== false) {
            Platform::clearEnv('COMPOSER');
        }

        Platform::putEnv('COMPOSER_ROOT_VERSION', $package->getPrettyVersion());

        // once the root project is fully initialized, we do not need to wipe everything on user abort anymore even if it happens during deps install
        if (isset($signalHandler)) {
            $signalHandler->unregister();
        }

        return $installedFromVcs;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Console\Input\InputArgument;
use Composer\Console\Input\InputOption;

/**
 * @author Niels Keurentjes <niels.keurentjes@omines.com>
 */
class ProhibitsCommand extends BaseDependencyCommand
{
    use CompletionTrait;

    /**
     * Configure command metadata.
     */
    protected function configure(): void
    {
        $this
            ->setName('prohibits')
            ->setAliases(['why-not'])
            ->setDescription('Shows which packages prevent the given package from being installed')
            ->setDefinition([
                new InputArgument(self::ARGUMENT_PACKAGE, InputArgument::REQUIRED, 'Package to inspect', null, $this->suggestAvailablePackage()),
                new InputArgument(self::ARGUMENT_CONSTRAINT, InputArgument::REQUIRED, 'Version constraint, which version you expected to be installed'),
                new InputOption(self::OPTION_RECURSIVE, 'r', InputOption::VALUE_NONE, 'Recursively resolves up to the root package'),
                new InputOption(self::OPTION_TREE, 't', InputOption::VALUE_NONE, 'Prints the results as a nested tree'),
                new InputOption('locked', null, InputOption::VALUE_NONE, 'Read dependency information from composer.lock'),
            ])
            ->setHelp(
                <<<EOT
Displays detailed information about why a package cannot be installed.

<info>php composer.phar prohibits composer/composer</info>

Read more at https://getcomposer.org/doc/03-cli.md#prohibits-why-not
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        return parent::doExecute($input, $output, true);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Factory;
use Composer\Json\JsonFile;
use Composer\Pcre\Preg;
use Composer\Console\Input\InputArgument;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Manage repositories
 *
 * Examples:
 *  - composer repo list
 *  - composer repo add foo vcs https://github.com/acme/foo
 *  - composer repo remove foo
 *  - composer repo set-url foo https://git.example.org/acme/foo
 *  - composer repo get-url foo
 *  - composer repo disable packagist.org
 *  - composer repo enable packagist.org
 */
class RepositoryCommand extends BaseConfigCommand
{
    protected function configure(): void
    {
        $this
            ->setName('repository')
            ->setAliases(['repo'])
            ->setDescription('Manages repositories')
            ->setDefinition([
                new InputOption('global', 'g', InputOption::VALUE_NONE, 'Apply command to the global config file'),
                new InputOption('file', 'f', InputOption::VALUE_REQUIRED, 'If you want to choose a different composer.json or config.json'),
                new InputOption('append', null, InputOption::VALUE_NONE, 'When adding a repository, append it (lower priority) instead of prepending it'),
                new InputOption('before', null, InputOption::VALUE_REQUIRED, 'When adding a repository, insert it before the given repository name', null, $this->suggestRepoNames()),
                new InputOption('after', null, InputOption::VALUE_REQUIRED, 'When adding a repository, insert it after the given repository name', null, $this->suggestRepoNames()),
                new InputArgument('action', InputArgument::OPTIONAL, 'Action to perform: list, add, remove, set-url, get-url, enable, disable', 'list', ['list', 'add', 'remove', 'set-url', 'get-url', 'enable', 'disable']),
                new InputArgument('name', InputArgument::OPTIONAL, 'Repository name (or special name packagist.org for enable/disable)', null, $this->suggestRepoNames()),
                new InputArgument('arg1', InputArgument::OPTIONAL, 'Type for add, or new URL for set-url, or JSON config for add', null, $this->suggestTypeForAdd()),
                new InputArgument('arg2', InputArgument::OPTIONAL, 'URL for add (if not using JSON)'),
            ])
            ->setHelp(<<<EOT
This command lets you manage repositories in your composer.json.

Examples:
  composer repo list
  composer repo add foo vcs https://github.com/acme/foo
  composer repo add bar composer https://repo.packagist.com/bar
  composer repo add zips '{"type":"artifact","url":"/path/to/dir/with/zips"}'
  composer repo add baz vcs https://example.org --before foo
  composer repo add qux vcs https://example.org --after bar
  composer repo remove foo
  composer repo set-url foo https://git.example.org/acme/foo
  composer repo get-url foo
  composer repo disable packagist.org
  composer repo enable packagist.org

Use --global/-g to alter the global config.json instead.
Use --file to alter a specific file.
EOT
            );
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $action = strtolower((string) $input->getArgument('action'));
        $name = $input->getArgument('name');
        $arg1 = $input->getArgument('arg1');
        $arg2 = $input->getArgument('arg2');

        $this->config->merge($this->configFile->read(), $this->configFile->getPath());
        $repos = $this->config->getRepositories();

        switch ($action) {
            case 'list':
            case 'ls':
            case 'show':
                $this->listRepositories($repos);

                return 0;

            case 'add':
                if ($name === null) {
                    throw new \RuntimeException('You must pass a repository name. Example: composer repo add foo vcs https://example.org');
                }
                if ($arg1 === null) {
                    throw new \RuntimeException('You must pass the type and a url, or a JSON string.');
                }
                if (is_string($arg1) && Preg::isMatch('{^\s*\{}', $arg1)) {
                    // JSON config
                    $repoConfig = JsonFile::parseJson($arg1);
                } else {
                    if ($arg2 === null) {
                        throw new \RuntimeException('You must pass the type and a url. Example: composer repo add foo vcs https://example.org');
                    }
                    $repoConfig = ['type' => (string) $arg1, 'url' => (string) $arg2];
                }

                // ordering options
                $before = $input->getOption('before');
                $after = $input->getOption('after');
                if ($before !== null && $after !== null) {
                    throw new \RuntimeException('You can not combine --before and --after');
                }

                if ($before !== null || $after !== null) {
                    if ($repoConfig === false) {
                        throw new \RuntimeException('Cannot use --before/--after with boolean repository values');
                    }

                    $this->configSource->insertRepository((string) $name, $repoConfig, $before ?? $after, $after !== null ? 1 : 0);

                    return 0;
                }

                $this->configSource->addRepository((string) $name, $repoConfig, (bool) $input->getOption('append'));

                return 0;

            case 'remove':
            case 'rm':
            case 'delete':
                if ($name === null) {
                    throw new \RuntimeException('You must pass the repository name to remove.');
                }
                $this->configSource->removeRepository((string) $name);
                if (in_array($name, ['packagist', 'packagist.org'], true)) {
                    $this->configSource->addRepository('packagist.org', false);
                }

                return 0;

            case 'set-url':
            case 'seturl':
                if ($name === null || $arg1 === null) {
                    throw new \RuntimeException('Usage: composer repo set-url <name> <new-url>');
                }

                $this->configSource->setRepositoryUrl($name, $arg1);

                return 0;

            case 'get-url':
            case 'geturl':
                if ($name === null) {
                    throw new \RuntimeException('Usage: composer repo get-url <name>');
                }
                if (isset($repos[$name]) && is_array($repos[$name])) {
                    $url = $repos[$name]['url'] ?? null;
                    if (!is_string($url)) {
                        throw new \InvalidArgumentException('The '.$name.' repository does not have a URL');
                    }
                    $this->getIO()->write($url);

                    return 0;
                }
                // try named-list: find entry with matching name
                if (is_array($repos)) {
                    foreach ($repos as $val) {
                        if (is_array($val) && isset($val['name']) && $val['name'] === $name) {
                            $url = $val['url'] ?? null;
                            if (!is_string($url)) {
                                throw new \InvalidArgumentException('The '.$name.' repository does not have a URL');
                            }
                            $this->getIO()->write($url);

                            return 0;
                        }
                    }
                }

                throw new \InvalidArgumentException('There is no '.$name.' repository defined');

            case 'disable':
                if ($name === null) {
                    throw new \RuntimeException('Usage: composer repo disable packagist.org');
                }
                if (in_array($name, ['packagist', 'packagist.org'], true)) {
                    // special handling mirrors ConfigCommand behavior
                    $this->configSource->addRepository('packagist.org', false, (bool) $input->getOption('append'));

                    return 0;
                }
                throw new \RuntimeException('Only packagist.org can be enabled/disabled using this command. Use add/remove for other repositories.');

            case 'enable':
                if ($name === null) {
                    throw new \RuntimeException('Usage: composer repo enable packagist.org');
                }
                if (in_array($name, ['packagist', 'packagist.org'], true)) {
                    // Remove a false flag by setting packagist.org to true via removing the key
                    // Here we re-add the default by removing overrides
                    $this->configSource->removeRepository('packagist.org');

                    return 0;
                }
                throw new \RuntimeException('Only packagist.org can be enabled/disabled using this command.');

            default:
                throw new \InvalidArgumentException('Unknown action "'.$action.'". Use list, add, remove, set-url, get-url, enable, disable');
        }
    }

    /**
     * @param array<int|string, mixed> $repos
     */
    private function listRepositories(array $repos): void
    {
        $io = $this->getIO();

        $packagistPresent = false;
        foreach ($repos as $key => $repo) {
            if (isset($repo['type'], $repo['url']) && $repo['type'] === 'composer' && str_ends_with((string) parse_url($repo['url'], PHP_URL_HOST), 'packagist.org')) {
                $packagistPresent = true;
                break;
            }
        }
        if (!$packagistPresent) {
            $repos[] = ['packagist.org' => false];
        }

        if ($repos === []) {
            $io->write('No repositories configured');

            return;
        }

        foreach ($repos as $key => $repo) {
            if ($repo === false) {
                $io->write('['.$key.'] <info>disabled</info>');
                continue;
            }

            if (is_array($repo)) {
                if (1 === \count($repo) && false === current($repo)) {
                    $io->write('['.array_key_first($repo).'] <info>disabled</info>');
                    continue;
                }

                $name = $repo['name'] ?? $key;
                $type = $repo['type'] ?? 'unknown';
                $url = $repo['url'] ?? JsonFile::encode($repo);
                $io->write('['.$name.'] <info>'.$type.'</info> '.$url);
            }
        }
    }

    private function suggestTypeForAdd(): \Closure
    {
        return static function (CompletionInput $input): array {
            if ($input->getArgument('action') === 'add') {
                return ['composer', 'vcs', 'artifact', 'path'];
            }

            return [];
        };
    }

    private function suggestRepoNames(): \Closure
    {
        return function (CompletionInput $input): array {
            if (in_array($input->getArgument('action'), ['enable', 'disable'], true)) {
                return ['packagist.org'];
            }

            if (!in_array($input->getArgument('action'), ['remove', 'set-url', 'get-url'], true)) {
                return [];
            }

            $config = Factory::createConfig();
            $configFile = new JsonFile($this->getComposerConfigFile($input, $config));

            $data = $configFile->read();
            $repos = [];

            foreach (($data['repositories'] ?? []) as $repo) {
                if (isset($repo['name'])) {
                    $repos[] = $repo['name'];
                }
            }

            sort($repos);

            return $repos;
        };
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Package\Link;
use Composer\Package\Package;
use Composer\Package\PackageInterface;
use Composer\Package\CompletePackageInterface;
use Composer\Package\RootPackage;
use Composer\Repository\InstalledArrayRepository;
use Composer\Repository\CompositeRepository;
use Composer\Repository\RootPackageRepository;
use Composer\Repository\InstalledRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryFactory;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Semver\Constraint\Bound;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Composer\Package\Version\VersionParser;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Util\PackageInfo;

/**
 * Base implementation for commands mapping dependency relationships.
 *
 * @author Niels Keurentjes <niels.keurentjes@omines.com>
 */
abstract class BaseDependencyCommand extends BaseCommand
{
    protected const ARGUMENT_PACKAGE = 'package';
    protected const ARGUMENT_CONSTRAINT = 'version';
    protected const OPTION_RECURSIVE = 'recursive';
    protected const OPTION_TREE = 'tree';

    /** @var string[] */
    protected $colors;

    /**
     * Execute the command.
     *
     * @param  bool            $inverted Whether to invert matching process (why-not vs why behaviour)
     * @return int             Exit code of the operation.
     */
    protected function doExecute(InputInterface $input, OutputInterface $output, bool $inverted = false): int
    {
        // Emit command event on startup
        $composer = $this->requireComposer();
        $commandEvent = new CommandEvent(PluginEvents::COMMAND, $this->getName(), $input, $output);
        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        $repos = [];

        $repos[] = new RootPackageRepository(clone $composer->getPackage());

        if ($input->getOption('locked')) {
            $locker = $composer->getLocker();

            if (!$locker->isLocked()) {
                throw new \UnexpectedValueException('A valid composer.lock file is required to run this command with --locked');
            }

            $repos[] = $locker->getLockedRepository(true);
            $repos[] = new PlatformRepository([], $locker->getPlatformOverrides());
        } else {
            $localRepo = $composer->getRepositoryManager()->getLocalRepository();
            $rootPkg = $composer->getPackage();

            if (count($localRepo->getPackages()) === 0 && (count($rootPkg->getRequires()) > 0 || count($rootPkg->getDevRequires()) > 0)) {
                $output->writeln('<warning>No dependencies installed. Try running composer install or update, or use --locked.</warning>');

                return 1;
            }

            $repos[] = $localRepo;

            $platformOverrides = $composer->getConfig()->get('platform') ?: [];
            $repos[] = new PlatformRepository([], $platformOverrides);
        }

        $installedRepo = new InstalledRepository($repos);

        // Parse package name and constraint
        $needle = $input->getArgument(self::ARGUMENT_PACKAGE);
        $textConstraint = $input->hasArgument(self::ARGUMENT_CONSTRAINT) ? $input->getArgument(self::ARGUMENT_CONSTRAINT) : '*';

        // Find packages that are or provide the requested package first
        $packages = $installedRepo->findPackagesWithReplacersAndProviders($needle);
        if (empty($packages)) {
            throw new \InvalidArgumentException(sprintf('Could not find package "%s" in your project', $needle));
        }

        // If the version we ask for is not installed then we need to locate it in remote repos and add it.
        // This is needed for why-not to resolve conflicts from an uninstalled version against installed packages.
        $matchedPackage = $installedRepo->findPackage($needle, $textConstraint);
        if (!$matchedPackage) {
            $defaultRepos = new CompositeRepository(RepositoryFactory::defaultRepos($this->getIO(), $composer->getConfig(), $composer->getRepositoryManager()));
            if ($match = $defaultRepos->findPackage($needle, $textConstraint)) {
                $installedRepo->addRepository(new InstalledArrayRepository([clone $match]));
            } elseif (PlatformRepository::isPlatformPackage($needle)) {
                $parser = new VersionParser();
                $constraint = $parser->parseConstraints($textConstraint);
                if ($constraint->getLowerBound() !== Bound::zero()) {
                    $tempPlatformPkg = new Package($needle, $constraint->getLowerBound()->getVersion(), $constraint->getLowerBound()->getVersion());
                    $installedRepo->addRepository(new InstalledArrayRepository([$tempPlatformPkg]));
                }
            } else {
                $this->getIO()->writeError('<error>Package "'.$needle.'" could not be found with constraint "'.$textConstraint.'", results below will most likely be incomplete.</error>');
            }
        } elseif (PlatformRepository::isPlatformPackage($needle)) {
            $extraNotice = '';
            if (($matchedPackage->getExtra()['config.platform'] ?? false) === true) {
                $extraNotice = ' (version provided by config.platform)';
            }
            $this->getIO()->writeError('<info>Package "'.$needle.' '.$textConstraint.'" found in version "'.$matchedPackage->getPrettyVersion().'"'.$extraNotice.'.</info>');
        } elseif ($inverted) {
            $this->getIO()->write('<comment>Package "'.$needle.'" '.$matchedPackage->getPrettyVersion().' is already installed! To find out why, run `composer why '.$needle.'`</comment>');

            return 0;
        }

        // Include replaced packages for inverted lookups as they are then the actual starting point to consider
        $needles = [$needle];
        if ($inverted) {
            foreach ($packages as $package) {
                $needles = array_merge($needles, array_map(static function (Link $link): string {
                    return $link->getTarget();
                }, $package->getReplaces()));
            }
        }

        // Parse constraint if one was supplied
        if ('*' !== $textConstraint) {
            $versionParser = new VersionParser();
            $constraint = $versionParser->parseConstraints($textConstraint);
        } else {
            $constraint = null;
        }

        // Parse rendering options
        $renderTree = $input->getOption(self::OPTION_TREE);
        $recursive = $renderTree || $input->getOption(self::OPTION_RECURSIVE);

        $return = $inverted ? 1 : 0;

        // Resolve dependencies
        $results = $installedRepo->getDependents($needles, $constraint, $inverted, $recursive);
        if (empty($results)) {
            $extra = (null !== $constraint) ? sprintf(' in versions %smatching %s', $inverted ? 'not ' : '', $textConstraint) : '';
            $this->getIO()->writeError(sprintf(
                '<info>There is no installed package depending on "%s"%s</info>',
                $needle,
                $extra
            ));
            $return = $inverted ? 0 : 1;
        } elseif ($renderTree) {
            $this->initStyles($output);
            $root = $packages[0];
            $this->getIO()->write(sprintf('<info>%s</info> %s %s', $root->getPrettyName(), $root->getPrettyVersion(), $root instanceof CompletePackageInterface ? $root->getDescription() : ''));
            $this->printTree($results);
        } else {
            $this->printTable($output, $results);
        }

        if ($inverted && $input->hasArgument(self::ARGUMENT_CONSTRAINT) && !PlatformRepository::isPlatformPackage($needle)) {
            $composerCommand = 'update';

            foreach ($composer->getPackage()->getRequires() as $rootRequirement) {
                if ($rootRequirement->getTarget() === $needle) {
                    $composerCommand = 'require';
                    break;
                }
            }

            foreach ($composer->getPackage()->getDevRequires() as $rootRequirement) {
                if ($rootRequirement->getTarget() === $needle) {
                    $composerCommand = 'require --dev';
                    break;
                }
            }

            $this->getIO()->writeError('Not finding what you were looking for? Try calling `composer '.$composerCommand.' "'.$needle.':'.$textConstraint.'" --dry-run` to get another view on the problem.');
        }

        return $return;
    }

    /**
     * Assembles and prints a bottom-up table of the dependencies.
     *
     * @param array{PackageInterface, Link, array<mixed>|false}[] $results
     */
    protected function printTable(OutputInterface $output, array $results): void
    {
        $table = [];
        $doubles = [];
        do {
            $queue = [];
            $rows = [];
            foreach ($results as $result) {
                /**
                 * @var PackageInterface $package
                 * @var Link             $link
                 */
                [$package, $link, $children] = $result;
                $unique = (string) $link;
                if (isset($doubles[$unique])) {
                    continue;
                }
                $doubles[$unique] = true;
                $version = $package->getPrettyVersion() === RootPackage::DEFAULT_PRETTY_VERSION ? '-' : $package->getPrettyVersion();
                $packageUrl = PackageInfo::getViewSourceOrHomepageUrl($package);
                $nameWithLink = $packageUrl !== null ? '<href=' . OutputFormatter::escape($packageUrl) . '>' . $package->getPrettyName() . '</>' : $package->getPrettyName();
                $rows[] = [$nameWithLink, $version, $link->getDescription(), sprintf('%s (%s)', $link->getTarget(), $link->getPrettyConstraint())];
                if (is_array($children)) {
                    $queue = array_merge($queue, $children);
                }
            }
            $results = $queue;
            $table = array_merge($rows, $table);
        } while (\count($results) > 0);

        $this->renderTable($table, $output);
    }

    /**
     * Init styles for tree
     */
    protected function initStyles(OutputInterface $output): void
    {
        $this->colors = [
            'green',
            'yellow',
            'cyan',
            'magenta',
            'blue',
        ];

        foreach ($this->colors as $color) {
            $style = new OutputFormatterStyle($color);
            $output->getFormatter()->setStyle($color, $style);
        }
    }

    /**
     * Recursively prints a tree of the selected results.
     *
     * @param array{PackageInterface, Link, array<mixed>|false}[] $results Results to be printed at this level.
     * @param string  $prefix  Prefix of the current tree level.
     * @param int     $level   Current level of recursion.
     */
    protected function printTree(array $results, string $prefix = '', int $level = 1): void
    {
        $count = count($results);
        $idx = 0;
        foreach ($results as $result) {
            [$package, $link, $children] = $result;

            $color = $this->colors[$level % count($this->colors)];
            $prevColor = $this->colors[($level - 1) % count($this->colors)];
            $isLast = (++$idx === $count);
            $versionText = $package->getPrettyVersion() === RootPackage::DEFAULT_PRETTY_VERSION ? '' : $package->getPrettyVersion();
            $packageUrl = PackageInfo::getViewSourceOrHomepageUrl($package);
            $nameWithLink = $packageUrl !== null ? '<href=' . OutputFormatter::escape($packageUrl) . '>' . $package->getPrettyName() . '</>' : $package->getPrettyName();
            $packageText = rtrim(sprintf('<%s>%s</%1$s> %s', $color, $nameWithLink, $versionText));
            $linkText = sprintf('%s <%s>%s</%2$s> %s', $link->getDescription(), $prevColor, $link->getTarget(), $link->getPrettyConstraint());
            $circularWarn = $children === false ? '(circular dependency aborted here)' : '';
            $this->writeTreeLine(rtrim(sprintf("%s%s%s (%s) %s", $prefix, $isLast ? '└──' : '├──', $packageText, $linkText, $circularWarn)));
            if (is_array($children)) {
                $this->printTree($children, $prefix . ($isLast ? '   ' : '│  '), $level + 1);
            }
        }
    }

    private function writeTreeLine(string $line): void
    {
        $io = $this->getIO();
        if (!$io->isDecorated()) {
            $line = str_replace(['└', '├', '──', '│'], ['`-', '|-', '-', '|'], $line);
        }

        $io->write($line);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Config;
use Composer\Config\JsonConfigSource;
use Composer\Json\JsonFile;
use Composer\Factory;
use Composer\Util\Platform;
use Composer\Util\Silencer;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

abstract class BaseConfigCommand extends BaseCommand
{
    /**
     * @var Config
     */
    protected $config;

    /**
     * @var JsonFile
     */
    protected $configFile;

    /**
     * @var JsonConfigSource
     */
    protected $configSource;

    protected function initialize(InputInterface $input, OutputInterface $output): void
    {
        parent::initialize($input, $output);

        if ($input->getOption('global') && null !== $input->getOption('file')) {
            throw new \RuntimeException('--file and --global can not be combined');
        }

        $io = $this->getIO();
        $this->config = Factory::createConfig($io);

        // When using --global flag, set baseDir to home directory for correct absolute path resolution
        if ($input->getOption('global')) {
            $this->config->setBaseDir($this->config->get('home'));
        }

        $configFile = $this->getComposerConfigFile($input, $this->config);

        // Create global composer.json if invoked using `composer global [config-cmd]`
        if (
            ($configFile === 'composer.json' || $configFile === './composer.json')
            && !file_exists($configFile)
            && realpath(Platform::getCwd()) === realpath($this->config->get('home'))
        ) {
            file_put_contents($configFile, "{\n}\n");
        }

        $this->configFile = new JsonFile($configFile, null, $io);
        $this->configSource = new JsonConfigSource($this->configFile);

        // Initialize the global file if it's not there, ignoring any warnings or notices
        if ($input->getOption('global') && !$this->configFile->exists()) {
            touch($this->configFile->getPath());
            $this->configFile->write(['config' => new \ArrayObject]);
            Silencer::call('chmod', $this->configFile->getPath(), 0600);
        }

        if (!$this->configFile->exists()) {
            throw new \RuntimeException(sprintf('File "%s" cannot be found in the current directory', $configFile));
        }
    }

    /**
     * Get the local composer.json, global config.json, or the file passed by the user
     */
    protected function getComposerConfigFile(InputInterface $input, Config $config): string
    {
        return $input->getOption('global')
            ? ($config->get('home') . '/config.json')
            : ($input->getOption('file') ?? Factory::getComposerFile())
        ;
    }

    /**
     * Get the local auth.json or global auth.json, or if the user passed in a file to use,
     * the corresponding auth.json
     */
    protected function getAuthConfigFile(InputInterface $input, Config $config): string
    {
        return $input->getOption('global')
            ? ($config->get('home') . '/auth.json')
            : dirname($this->getComposerConfigFile($input, $config)) . '/auth.json'
        ;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Composer;
use Composer\Config;
use Composer\Console\Application;
use Composer\Console\Input\InputArgument;
use Composer\Console\Input\InputOption;
use Composer\Factory;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
use Composer\IO\IOInterface;
use Composer\IO\NullIO;
use Composer\Plugin\PreCommandRunEvent;
use Composer\Package\Version\VersionParser;
use Composer\Plugin\PluginEvents;
use Composer\Advisory\Auditor;
use Composer\Advisory\AuditConfig;
use Composer\Util\Platform;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableSeparator;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Terminal;

/**
 * Base class for Composer commands
 *
 * @author Ryan Weaver <ryan@knplabs.com>
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
abstract class BaseCommand extends Command
{
    /**
     * @var Composer|null
     */
    private $composer;

    /**
     * @var IOInterface
     */
    private $io;

    /**
     * Gets the application instance for this command.
     */
    public function getApplication(): Application
    {
        $application = parent::getApplication();
        if (!$application instanceof Application) {
            throw new \RuntimeException('Composer commands can only work with an '.Application::class.' instance set');
        }

        return $application;
    }

    /**
     * @param  bool              $required       Should be set to false, or use `requireComposer` instead
     * @param  bool|null         $disablePlugins If null, reads --no-plugins as default
     * @param  bool|null         $disableScripts If null, reads --no-scripts as default
     * @throws \RuntimeException
     * @return Composer|null
     * @deprecated since Composer 2.3.0 use requireComposer or tryComposer depending on whether you have $required set to true or false
     */
    public function getComposer(bool $required = true, ?bool $disablePlugins = null, ?bool $disableScripts = null)
    {
        if ($required) {
            return $this->requireComposer($disablePlugins, $disableScripts);
        }

        return $this->tryComposer($disablePlugins, $disableScripts);
    }

    /**
     * Retrieves the default Composer\Composer instance or throws
     *
     * Use this instead of getComposer if you absolutely need an instance
     *
     * @param bool|null $disablePlugins If null, reads --no-plugins as default
     * @param bool|null $disableScripts If null, reads --no-scripts as default
     * @throws \RuntimeException
     */
    public function requireComposer(?bool $disablePlugins = null, ?bool $disableScripts = null): Composer
    {
        if (null === $this->composer) {
            $application = parent::getApplication();
            if ($application instanceof Application) {
                $this->composer = $application->getComposer(true, $disablePlugins, $disableScripts);
                assert($this->composer instanceof Composer);
            } else {
                throw new \RuntimeException(
                    'Could not create a Composer\Composer instance, you must inject '.
                    'one if this command is not used with a Composer\Console\Application instance'
                );
            }
        }

        return $this->composer;
    }

    /**
     * Retrieves the default Composer\Composer instance or null
     *
     * Use this instead of getComposer(false)
     *
     * @param bool|null $disablePlugins If null, reads --no-plugins as default
     * @param bool|null $disableScripts If null, reads --no-scripts as default
     */
    public function tryComposer(?bool $disablePlugins = null, ?bool $disableScripts = null): ?Composer
    {
        if (null === $this->composer) {
            $application = parent::getApplication();
            if ($application instanceof Application) {
                $this->composer = $application->getComposer(false, $disablePlugins, $disableScripts);
            }
        }

        return $this->composer;
    }

    /**
     * @return void
     */
    public function setComposer(Composer $composer)
    {
        $this->composer = $composer;
    }

    /**
     * Removes the cached composer instance
     *
     * @return void
     */
    public function resetComposer()
    {
        $this->composer = null;
        $this->getApplication()->resetComposer();
    }

    /**
     * Whether or not this command is meant to call another command.
     *
     * This is mainly needed to avoid duplicated warnings messages.
     *
     * @return bool
     */
    public function isProxyCommand()
    {
        return false;
    }

    /**
     * @return IOInterface
     */
    public function getIO()
    {
        if (null === $this->io) {
            $application = parent::getApplication();
            if ($application instanceof Application) {
                $this->io = $application->getIO();
            } else {
                $this->io = new NullIO();
            }
        }

        return $this->io;
    }

    /**
     * @return void
     */
    public function setIO(IOInterface $io)
    {
        $this->io = $io;
    }

    /**
     * @inheritdoc
     *
     * Backport suggested values definition from symfony/console 6.1+
     *
     * TODO drop when PHP 8.1 / symfony 6.1+ can be required
     */
    public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
    {
        $definition = $this->getDefinition();
        $name = (string) $input->getCompletionName();
        if (CompletionInput::TYPE_OPTION_VALUE === $input->getCompletionType()
            && $definition->hasOption($name)
            && ($option = $definition->getOption($name)) instanceof InputOption
        ) {
            $option->complete($input, $suggestions);
        } elseif (CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType()
            && $definition->hasArgument($name)
            && ($argument = $definition->getArgument($name)) instanceof InputArgument
        ) {
            $argument->complete($input, $suggestions);
        } else {
            parent::complete($input, $suggestions);
        }
    }

    /**
     * @inheritDoc
     */
    protected function initialize(InputInterface $input, OutputInterface $output): void
    {
        // initialize a plugin-enabled Composer instance, either local or global
        $disablePlugins = $input->hasParameterOption('--no-plugins');
        $disableScripts = $input->hasParameterOption('--no-scripts');

        $application = parent::getApplication();
        if ($application instanceof Application && $application->getDisablePluginsByDefault()) {
            $disablePlugins = true;
        }
        if ($application instanceof Application && $application->getDisableScriptsByDefault()) {
            $disableScripts = true;
        }

        if ($this instanceof SelfUpdateCommand) {
            $disablePlugins = true;
            $disableScripts = true;
        }

        $composer = $this->tryComposer($disablePlugins, $disableScripts);
        $io = $this->getIO();

        if (null === $composer) {
            $composer = Factory::createGlobal($this->getIO(), $disablePlugins, $disableScripts);
        }
        if ($composer) {
            $preCommandRunEvent = new PreCommandRunEvent(PluginEvents::PRE_COMMAND_RUN, $input, $this->getName());
            $composer->getEventDispatcher()->dispatch($preCommandRunEvent->getName(), $preCommandRunEvent);
        }

        if (true === $input->hasParameterOption(['--no-ansi']) && $input->hasOption('no-progress')) {
            $input->setOption('no-progress', true);
        }

        $envOptions = [
            'COMPOSER_NO_AUDIT' => ['no-audit'],
            'COMPOSER_NO_DEV' => ['no-dev', 'update-no-dev'],
            'COMPOSER_PREFER_STABLE' => ['prefer-stable'],
            'COMPOSER_PREFER_LOWEST' => ['prefer-lowest'],
            'COMPOSER_MINIMAL_CHANGES' => ['minimal-changes'],
            'COMPOSER_WITH_DEPENDENCIES' => ['with-dependencies'],
            'COMPOSER_WITH_ALL_DEPENDENCIES' => ['with-all-dependencies'],
            'COMPOSER_NO_SECURITY_BLOCKING' => ['no-security-blocking'],
        ];
        foreach ($envOptions as $envName => $optionNames) {
            foreach ($optionNames as $optionName) {
                if (true === $input->hasOption($optionName)) {
                    if (false === $input->getOption($optionName) && (bool) Platform::getEnv($envName)) {
                        $input->setOption($optionName, true);
                    }
                }
            }
        }

        if (true === $input->hasOption('ignore-platform-reqs')) {
            if (!$input->getOption('ignore-platform-reqs') && (bool) Platform::getEnv('COMPOSER_IGNORE_PLATFORM_REQS')) {
                $input->setOption('ignore-platform-reqs', true);

                $io->writeError('<warning>COMPOSER_IGNORE_PLATFORM_REQS is set. You may experience unexpected errors.</warning>');
            }
        }

        if (true === $input->hasOption('ignore-platform-req') && (!$input->hasOption('ignore-platform-reqs') || !$input->getOption('ignore-platform-reqs'))) {
            $ignorePlatformReqEnv = Platform::getEnv('COMPOSER_IGNORE_PLATFORM_REQ');
            if (0 === count($input->getOption('ignore-platform-req')) && is_string($ignorePlatformReqEnv) && '' !== $ignorePlatformReqEnv) {
                $input->setOption('ignore-platform-req', explode(',', $ignorePlatformReqEnv));

                $io->writeError('<warning>COMPOSER_IGNORE_PLATFORM_REQ is set to ignore '.$ignorePlatformReqEnv.'. You may experience unexpected errors.</warning>');
            }
        }

        parent::initialize($input, $output);
    }

    /**
     * Calls {@see Factory::create()} with the given arguments, taking into account flags and default states for disabling scripts and plugins
     *
     * @param  mixed    $config either a configuration array or a filename to read from, if null it will read from
     *                          the default filename
     */
    protected function createComposerInstance(InputInterface $input, IOInterface $io, $config = null, ?bool $disablePlugins = null, ?bool $disableScripts = null): Composer
    {
        $disablePlugins = $disablePlugins === true || $input->hasParameterOption('--no-plugins');
        $disableScripts = $disableScripts === true || $input->hasParameterOption('--no-scripts');

        $application = parent::getApplication();
        if ($application instanceof Application && $application->getDisablePluginsByDefault()) {
            $disablePlugins = true;
        }
        if ($application instanceof Application && $application->getDisableScriptsByDefault()) {
            $disableScripts = true;
        }

        return Factory::create($io, $config, $disablePlugins, $disableScripts);
    }

    /**
     * Returns preferSource and preferDist values based on the configuration.
     *
     * @return bool[] An array composed of the preferSource and preferDist values
     */
    protected function getPreferredInstallOptions(Config $config, InputInterface $input, bool $keepVcsRequiresPreferSource = false)
    {
        $preferSource = false;
        $preferDist = false;

        switch ($config->get('preferred-install')) {
            case 'source':
                $preferSource = true;
                break;
            case 'dist':
                $preferDist = true;
                break;
            case 'auto':
            default:
                // noop
                break;
        }

        if (!$input->hasOption('prefer-dist') || !$input->hasOption('prefer-source')) {
            return [$preferSource, $preferDist];
        }

        if ($input->hasOption('prefer-install') && is_string($input->getOption('prefer-install'))) {
            if ($input->getOption('prefer-source')) {
                throw new \InvalidArgumentException('--prefer-source can not be used together with --prefer-install');
            }
            if ($input->getOption('prefer-dist')) {
                throw new \InvalidArgumentException('--prefer-dist can not be used together with --prefer-install');
            }
            switch ($input->getOption('prefer-install')) {
                case 'dist':
                    $input->setOption('prefer-dist', true);
                    break;
                case 'source':
                    $input->setOption('prefer-source', true);
                    break;
                case 'auto':
                    $preferDist = false;
                    $preferSource = false;
                    break;
                default:
                    throw new \UnexpectedValueException('--prefer-install accepts one of "dist", "source" or "auto", got '.$input->getOption('prefer-install'));
            }
        }

        if ($input->getOption('prefer-source') || $input->getOption('prefer-dist') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs'))) {
            $preferSource = $input->getOption('prefer-source') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs'));
            $preferDist = $input->getOption('prefer-dist');
        }

        return [$preferSource, $preferDist];
    }

    protected function getPlatformRequirementFilter(InputInterface $input): PlatformRequirementFilterInterface
    {
        if (!$input->hasOption('ignore-platform-reqs') || !$input->hasOption('ignore-platform-req')) {
            throw new \LogicException('Calling getPlatformRequirementFilter from a command which does not define the --ignore-platform-req[s] flags is not permitted.');
        }

        if (true === $input->getOption('ignore-platform-reqs')) {
            return PlatformRequirementFilterFactory::ignoreAll();
        }

        $ignores = $input->getOption('ignore-platform-req');
        if (count($ignores) > 0) {
            return PlatformRequirementFilterFactory::fromBoolOrList($ignores);
        }

        return PlatformRequirementFilterFactory::ignoreNothing();
    }

    /**
     * @param array<string> $requirements
     *
     * @return array<string, string>
     */
    protected function formatRequirements(array $requirements)
    {
        $requires = [];
        $requirements = $this->normalizeRequirements($requirements);
        foreach ($requirements as $requirement) {
            if (!isset($requirement['version'])) {
                throw new \UnexpectedValueException('Option '.$requirement['name'] .' is missing a version constraint, use e.g. '.$requirement['name'].':^1.0');
            }
            $requires[$requirement['name']] = $requirement['version'];
        }

        return $requires;
    }

    /**
     * @param array<string> $requirements
     *
     * @return list<array{name: string, version?: string}>
     */
    protected function normalizeRequirements(array $requirements)
    {
        $parser = new VersionParser();

        return $parser->parseNameVersionPairs($requirements);
    }

    /**
     * @param array<TableSeparator|mixed[]> $table
     *
     * @return void
     */
    protected function renderTable(array $table, OutputInterface $output)
    {
        $renderer = new Table($output);
        $renderer->setStyle('compact');
        $renderer->setRows($table)->render();
    }

    /**
     * @return int
     */
    protected function getTerminalWidth()
    {
        $terminal = new Terminal();
        $width = $terminal->getWidth();

        if (Platform::isWindows()) {
            $width--;
        } else {
            $width = max(80, $width);
        }

        return $width;
    }

    /**
     * @internal
     * @param 'format'|'audit-format' $optName
     * @return Auditor::FORMAT_*
     */
    protected function getAuditFormat(InputInterface $input, string $optName = 'audit-format'): string
    {
        if (!$input->hasOption($optName)) {
            throw new \LogicException('This should not be called on a Command which has no '.$optName.' option defined.');
        }

        $val = $input->getOption($optName);
        if (!in_array($val, Auditor::FORMATS, true)) {
            throw new \InvalidArgumentException('--'.$optName.' must be one of '.implode(', ', Auditor::FORMATS).'.');
        }

        return $val;
    }

    /**
     * Creates an AuditConfig from the Config object, optionally overriding security blocking based on input options
     *
     * @internal
     */
    protected function createAuditConfig(Config $config, InputInterface $input): AuditConfig
    {
        // Handle both --audit and --no-audit flags
        if ($input->hasOption('audit')) {
            $audit = (bool) $input->getOption('audit');
        } else {
            $audit = !($input->hasOption('no-audit') && $input->getOption('no-audit'));
        }
        $auditFormat = $input->hasOption('audit-format') ? $this->getAuditFormat($input) : Auditor::FORMAT_SUMMARY;

        $auditConfig = AuditConfig::fromConfig($config, $audit, $auditFormat);

        if ((bool) Platform::getEnv('COMPOSER_NO_SECURITY_BLOCKING') || ($input->hasOption('no-security-blocking') && $input->getOption('no-security-blocking'))) {
            $auditConfig = new AuditConfig(
                $auditConfig->audit,
                $auditConfig->auditFormat,
                $auditConfig->auditAbandoned,
                false, // blockInsecure
                $auditConfig->blockAbandoned,
                $auditConfig->ignoreUnreachable,
                $auditConfig->ignoreListForAudit,
                $auditConfig->ignoreListForBlocking,
                $auditConfig->ignoreSeverityForAudit,
                $auditConfig->ignoreSeverityForBlocking,
                $auditConfig->ignoreAbandonedForAudit,
                $auditConfig->ignoreAbandonedForBlocking
            );
        }

        return $auditConfig;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Repository\PlatformRepository;
use Composer\Repository\RootPackageRepository;
use Composer\Repository\InstalledRepository;
use Composer\Installer\SuggestedPackagesReporter;
use Composer\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class SuggestsCommand extends BaseCommand
{
    use CompletionTrait;

    protected function configure(): void
    {
        $this
            ->setName('suggests')
            ->setDescription('Shows package suggestions')
            ->setDefinition([
                new InputOption('by-package', null, InputOption::VALUE_NONE, 'Groups output by suggesting package (default)'),
                new InputOption('by-suggestion', null, InputOption::VALUE_NONE, 'Groups output by suggested package'),
                new InputOption('all', 'a', InputOption::VALUE_NONE, 'Show suggestions from all dependencies, including transitive ones'),
                new InputOption('list', null, InputOption::VALUE_NONE, 'Show only list of suggested package names'),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Exclude suggestions from require-dev packages'),
                new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Packages that you want to list suggestions from.', null, $this->suggestInstalledPackage()),
            ])
            ->setHelp(
                <<<EOT

The <info>%command.name%</info> command shows a sorted list of suggested packages.

Read more at https://getcomposer.org/doc/03-cli.md#suggests
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $composer = $this->requireComposer();

        $installedRepos = [
            new RootPackageRepository(clone $composer->getPackage()),
        ];

        $locker = $composer->getLocker();
        if ($locker->isLocked()) {
            $installedRepos[] = new PlatformRepository([], $locker->getPlatformOverrides());
            $installedRepos[] = $locker->getLockedRepository(!$input->getOption('no-dev'));
        } else {
            $installedRepos[] = new PlatformRepository([], $composer->getConfig()->get('platform'));
            $installedRepos[] = $composer->getRepositoryManager()->getLocalRepository();
        }

        $installedRepo = new InstalledRepository($installedRepos);
        $reporter = new SuggestedPackagesReporter($this->getIO());

        $filter = $input->getArgument('packages');
        $packages = $installedRepo->getPackages();
        $packages[] = $composer->getPackage();
        foreach ($packages as $package) {
            if (!empty($filter) && !in_array($package->getName(), $filter)) {
                continue;
            }

            $reporter->addSuggestionsFromPackage($package);
        }

        // Determine output mode, default is by-package
        $mode = SuggestedPackagesReporter::MODE_BY_PACKAGE;

        // if by-suggestion is given we override the default
        if ($input->getOption('by-suggestion')) {
            $mode = SuggestedPackagesReporter::MODE_BY_SUGGESTION;
        }
        // unless by-package is also present then we enable both
        if ($input->getOption('by-package')) {
            $mode |= SuggestedPackagesReporter::MODE_BY_PACKAGE;
        }
        // list is exclusive and overrides everything else
        if ($input->getOption('list')) {
            $mode = SuggestedPackagesReporter::MODE_LIST;
        }

        $reporter->output($mode, $installedRepo, empty($filter) && !$input->getOption('all') ? $composer->getPackage() : null);

        return 0;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\DependencyResolver\Request;
use Composer\Package\AliasPackage;
use Composer\Package\CompletePackageInterface;
use Composer\Package\Loader\RootPackageLoader;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionSelector;
use Composer\Pcre\Preg;
use Composer\Repository\RepositorySet;
use Composer\Util\Filesystem;
use Composer\Util\PackageSorter;
use Seld\Signal\SignalHandler;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputArgument;
use Composer\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Factory;
use Composer\Installer;
use Composer\Installer\InstallerEvents;
use Composer\Json\JsonFile;
use Composer\Json\JsonManipulator;
use Composer\Package\Version\VersionParser;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\BasePackage;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\IO\IOInterface;
use Composer\Advisory\Auditor;
use Composer\Util\Silencer;

/**
 * @author Jérémy Romey <jeremy@free-agent.fr>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class RequireCommand extends BaseCommand
{
    use CompletionTrait;
    use PackageDiscoveryTrait;

    /** @var bool */
    private $newlyCreated;
    /** @var bool */
    private $firstRequire;
    /** @var JsonFile */
    private $json;
    /** @var string */
    private $file;
    /** @var string */
    private $composerBackup;
    /** @var string file name */
    private $lock;
    /** @var ?string contents before modification if the lock file exists */
    private $lockBackup;
    /** @var bool */
    private $dependencyResolutionCompleted = false;

    protected function configure(): void
    {
        $this
            ->setName('require')
            ->setAliases(['r'])
            ->setDescription('Adds required packages to your composer.json and installs them')
            ->setDefinition([
                new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Optional package name can also include a version constraint, e.g. foo/bar or foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"', null, $this->suggestAvailablePackageInclPlatform()),
                new InputOption('dev', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'),
                new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'),
                new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
                new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'),
                new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()),
                new InputOption('fixed', null, InputOption::VALUE_NONE, 'Write fixed version to the composer.json.'),
                new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'DEPRECATED: This flag does not exist anymore.'),
                new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies (implies --no-install).'),
                new InputOption('no-install', null, InputOption::VALUE_NONE, 'Skip the install step after updating the composer.lock file.'),
                new InputOption('no-audit', null, InputOption::VALUE_NONE, 'Skip the audit step after updating the composer.lock file (can also be set via the COMPOSER_NO_AUDIT=1 env var).'),
                new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS),
                new InputOption('no-security-blocking', null, InputOption::VALUE_NONE, 'Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var).'),
                new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'),
                new InputOption('update-with-dependencies', 'w', InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated, except those that are root requirements (can also be set via the COMPOSER_WITH_DEPENDENCIES=1 env var).'),
                new InputOption('update-with-all-dependencies', 'W', InputOption::VALUE_NONE, 'Allows all inherited dependencies to be updated, including those that are root requirements (can also be set via the COMPOSER_WITH_ALL_DEPENDENCIES=1 env var).'),
                new InputOption('with-dependencies', null, InputOption::VALUE_NONE, 'Alias for --update-with-dependencies'),
                new InputOption('with-all-dependencies', null, InputOption::VALUE_NONE, 'Alias for --update-with-all-dependencies'),
                new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'),
                new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'),
                new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies (can also be set via the COMPOSER_PREFER_STABLE=1 env var).'),
                new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies (can also be set via the COMPOSER_PREFER_LOWEST=1 env var).'),
                new InputOption('minimal-changes', 'm', InputOption::VALUE_NONE, 'During an update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).'),
                new InputOption('sort-packages', null, InputOption::VALUE_NONE, 'Sorts packages when adding/updating a new dependency'),
                new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'),
                new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'),
                new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'),
                new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'),
            ])
            ->setHelp(
                <<<EOT
The require command adds required packages to your composer.json and installs them.

If you do not specify a package, composer will prompt you to search for a package, and given results, provide a list of
matches to require.

If you do not specify a version constraint, composer will choose a suitable one based on the available package versions.

If you do not want to install the new dependencies immediately you can call it with --no-update

Read more at https://getcomposer.org/doc/03-cli.md#require-r
EOT
            )
        ;
    }

    /**
     * @throws \Seld\JsonLint\ParsingException
     */
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $this->file = Factory::getComposerFile();
        $io = $this->getIO();

        if ($input->getOption('no-suggest')) {
            $io->writeError('<warning>You are using the deprecated option "--no-suggest". It has no effect and will break in Composer 3.</warning>');
        }

        $this->newlyCreated = !file_exists($this->file);
        if ($this->newlyCreated && !file_put_contents($this->file, "{\n}\n")) {
            $io->writeError('<error>'.$this->file.' could not be created.</error>');

            return 1;
        }
        if (!Filesystem::isReadable($this->file)) {
            $io->writeError('<error>'.$this->file.' is not readable.</error>');

            return 1;
        }

        if (filesize($this->file) === 0) {
            file_put_contents($this->file, "{\n}\n");
        }

        $this->json = new JsonFile($this->file);
        $this->lock = Factory::getLockFile($this->file);
        $this->composerBackup = file_get_contents($this->json->getPath());
        $this->lockBackup = file_exists($this->lock) ? file_get_contents($this->lock) : null;

        $signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) {
            $this->getIO()->writeError('Received '.$signal.', aborting', true, IOInterface::DEBUG);
            $this->revertComposerFile();
            $handler->exitWithLastSignal();
        });

        // check for writability by writing to the file as is_writable can not be trusted on network-mounts
        // see https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926
        if (!is_writable($this->file) && false === Silencer::call('file_put_contents', $this->file, $this->composerBackup)) {
            $io->writeError('<error>'.$this->file.' is not writable.</error>');

            return 1;
        }

        if ($input->getOption('fixed') === true) {
            $config = $this->json->read();

            $packageType = empty($config['type']) ? 'library' : $config['type'];

            /**
             * @see https://github.com/composer/composer/pull/8313#issuecomment-532637955
             */
            if ($packageType !== 'project' && !$input->getOption('dev')) {
                $io->writeError('<error>The "--fixed" option is only allowed for packages with a "project" type or for dev dependencies to prevent possible misuses.</error>');

                if (!isset($config['type'])) {
                    $io->writeError('<error>If your package is not a library, you can explicitly specify the "type" by using "composer config type project".</error>');
                }

                return 1;
            }
        }

        $composer = $this->requireComposer();
        $repos = $composer->getRepositoryManager()->getRepositories();

        $platformOverrides = $composer->getConfig()->get('platform');
        // initialize $this->repos as it is used by the PackageDiscoveryTrait
        $this->repos = new CompositeRepository(array_merge(
            [$platformRepo = new PlatformRepository([], $platformOverrides)],
            $repos
        ));

        if ($composer->getPackage()->getPreferStable()) {
            $preferredStability = 'stable';
        } else {
            $preferredStability = $composer->getPackage()->getMinimumStability();
        }

        try {
            $requirements = $this->determineRequirements(
                $input,
                $output,
                $input->getArgument('packages'),
                $platformRepo,
                $preferredStability,
                $input->getOption('no-update'), // if there is no update, we need to use the best possible version constraint directly as we cannot rely on the solver to guess the best constraint
                $input->getOption('fixed')
            );
        } catch (\Exception $e) {
            if ($this->newlyCreated) {
                $this->revertComposerFile();

                throw new \RuntimeException('No composer.json present in the current directory ('.$this->file.'), this may be the cause of the following exception.', 0, $e);
            }

            throw $e;
        }

        $requirements = $this->formatRequirements($requirements);

        if (!$input->getOption('dev') && $io->isInteractive() && !$composer->isGlobal()) {
            $devPackages = [];
            $devTags = ['dev', 'testing', 'static analysis'];
            $currentRequiresByKey = $this->getPackagesByRequireKey();
            foreach ($requirements as $name => $version) {
                // skip packages which are already in the composer.json as those have already been decided
                if (isset($currentRequiresByKey[$name])) {
                    continue;
                }

                $pkg = PackageSorter::getMostCurrentVersion($this->getRepos()->findPackages($name));
                if ($pkg instanceof CompletePackageInterface) {
                    $pkgDevTags = array_intersect($devTags, array_map('strtolower', $pkg->getKeywords()));
                    if (count($pkgDevTags) > 0) {
                        $devPackages[] = $pkgDevTags;
                    }
                }
            }

            if (count($devPackages) === count($requirements)) {
                $plural = count($requirements) > 1 ? 's' : '';
                $plural2 = count($requirements) > 1 ? 'are' : 'is';
                $plural3 = count($requirements) > 1 ? 'they are' : 'it is';
                $pkgDevTags = array_unique(array_merge(...$devPackages));
                $io->warning('The package'.$plural.' you required '.$plural2.' recommended to be placed in require-dev (because '.$plural3.' tagged as "'.implode('", "', $pkgDevTags).'") but you did not use --dev.');
                if ($io->askConfirmation('<info>Do you want to re-run the command with --dev?</> [<comment>yes</>]? ')) {
                    $input->setOption('dev', true);
                }
            }

            unset($devPackages, $pkgDevTags);
        }

        $requireKey = $input->getOption('dev') ? 'require-dev' : 'require';
        $removeKey = $input->getOption('dev') ? 'require' : 'require-dev';

        // check which requirements need the version guessed
        $requirementsToGuess = [];
        foreach ($requirements as $package => $constraint) {
            if ($constraint === 'guess') {
                $requirements[$package] = '*';
                $requirementsToGuess[] = $package;
            }
        }

        // validate requirements format
        $versionParser = new VersionParser();
        foreach ($requirements as $package => $constraint) {
            if (strtolower($package) === $composer->getPackage()->getName()) {
                $io->writeError(sprintf('<error>Root package \'%s\' cannot require itself in its composer.json</error>', $package));

                return 1;
            }
            if ($constraint === 'self.version') {
                continue;
            }
            $versionParser->parseConstraints($constraint);
        }

        $inconsistentRequireKeys = $this->getInconsistentRequireKeys($requirements, $requireKey);
        if (count($inconsistentRequireKeys) > 0) {
            foreach ($inconsistentRequireKeys as $package) {
                $io->warning(sprintf(
                    '%s is currently present in the %s key and you ran the command %s the --dev flag, which will move it to the %s key.',
                    $package,
                    $removeKey,
                    $input->getOption('dev') ? 'with' : 'without',
                    $requireKey
                ));
            }

            if ($io->isInteractive()) {
                if (!$io->askConfirmation(sprintf('<info>Do you want to move %s?</info> [<comment>no</comment>]? ', count($inconsistentRequireKeys) > 1 ? 'these requirements' : 'this requirement'), false)) {
                    if (!$io->askConfirmation(sprintf('<info>Do you want to re-run the command %s --dev?</info> [<comment>yes</comment>]? ', $input->getOption('dev') ? 'without' : 'with'), true)) {
                        return 0;
                    }

                    $input->setOption('dev', true);
                    [$requireKey, $removeKey] = [$removeKey, $requireKey];
                }
            }
        }

        $sortPackages = $input->getOption('sort-packages') || $composer->getConfig()->get('sort-packages');

        $this->firstRequire = $this->newlyCreated;
        if (!$this->firstRequire) {
            $composerDefinition = $this->json->read();
            if (count($composerDefinition['require'] ?? []) === 0 && count($composerDefinition['require-dev'] ?? []) === 0) {
                $this->firstRequire = true;
            }
        }

        if (!$input->getOption('dry-run')) {
            $this->updateFile($this->json, $requirements, $requireKey, $removeKey, $sortPackages);
        }

        $io->writeError('<info>'.$this->file.' has been '.($this->newlyCreated ? 'created' : 'updated').'</info>');

        if ($input->getOption('no-update')) {
            return 0;
        }

        $composer->getPluginManager()->deactivateInstalledPlugins();

        try {
            $result = $this->doUpdate($input, $output, $io, $requirements, $requireKey, $removeKey);
            if ($result === 0 && count($requirementsToGuess) > 0) {
                $result = $this->updateRequirementsAfterResolution($requirementsToGuess, $requireKey, $removeKey, $sortPackages, $input->getOption('dry-run'), $input->getOption('fixed'));
            }

            return $result;
        } catch (\Exception $e) {
            if (!$this->dependencyResolutionCompleted) {
                $this->revertComposerFile();
            }
            throw $e;
        } finally {
            if ($input->getOption('dry-run') && $this->newlyCreated) {
                @unlink($this->json->getPath());
            }

            $signalHandler->unregister();
        }
    }

    /**
     * @param array<string, string> $newRequirements
     * @return string[]
     */
    private function getInconsistentRequireKeys(array $newRequirements, string $requireKey): array
    {
        $requireKeys = $this->getPackagesByRequireKey();
        $inconsistentRequirements = [];
        foreach ($requireKeys as $package => $packageRequireKey) {
            if (!isset($newRequirements[$package])) {
                continue;
            }
            if ($requireKey !== $packageRequireKey) {
                $inconsistentRequirements[] = $package;
            }
        }

        return $inconsistentRequirements;
    }

    /**
     * @return array<string, string>
     */
    private function getPackagesByRequireKey(): array
    {
        $composerDefinition = $this->json->read();
        $require = [];
        $requireDev = [];

        if (isset($composerDefinition['require'])) {
            $require = $composerDefinition['require'];
        }

        if (isset($composerDefinition['require-dev'])) {
            $requireDev = $composerDefinition['require-dev'];
        }

        return array_merge(
            array_fill_keys(array_keys($require), 'require'),
            array_fill_keys(array_keys($requireDev), 'require-dev')
        );
    }

    /**
     * @param array<string, string> $requirements
     * @param 'require'|'require-dev' $requireKey
     * @param 'require'|'require-dev' $removeKey
     * @throws \Exception
     */
    private function doUpdate(InputInterface $input, OutputInterface $output, IOInterface $io, array $requirements, string $requireKey, string $removeKey): int
    {
        // Update packages
        $this->resetComposer();
        $composer = $this->requireComposer();

        $this->dependencyResolutionCompleted = false;
        $composer->getEventDispatcher()->addListener(InstallerEvents::PRE_OPERATIONS_EXEC, function (): void {
            $this->dependencyResolutionCompleted = true;
        }, 10000);

        if ($input->getOption('dry-run')) {
            $rootPackage = $composer->getPackage();
            $links = [
                'require' => $rootPackage->getRequires(),
                'require-dev' => $rootPackage->getDevRequires(),
            ];
            $loader = new ArrayLoader();
            $newLinks = $loader->parseLinks($rootPackage->getName(), $rootPackage->getPrettyVersion(), BasePackage::$supportedLinkTypes[$requireKey]['method'], $requirements);
            $links[$requireKey] = array_merge($links[$requireKey], $newLinks);
            foreach ($requirements as $package => $constraint) {
                unset($links[$removeKey][$package]);
            }
            $rootPackage->setRequires($links['require']);
            $rootPackage->setDevRequires($links['require-dev']);

            // extract stability flags & references as they weren't present when loading the unmodified composer.json
            $references = $rootPackage->getReferences();
            $references = RootPackageLoader::extractReferences($requirements, $references);
            $rootPackage->setReferences($references);
            $stabilityFlags = $rootPackage->getStabilityFlags();
            $stabilityFlags = RootPackageLoader::extractStabilityFlags($requirements, $rootPackage->getMinimumStability(), $stabilityFlags);
            $rootPackage->setStabilityFlags($stabilityFlags);
            unset($stabilityFlags, $references);
        }

        $updateDevMode = !$input->getOption('update-no-dev');
        $optimize = $input->getOption('optimize-autoloader') || $composer->getConfig()->get('optimize-autoloader');
        $authoritative = $input->getOption('classmap-authoritative') || $composer->getConfig()->get('classmap-authoritative');
        $apcuPrefix = $input->getOption('apcu-autoloader-prefix');
        $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $composer->getConfig()->get('apcu-autoloader');
        $minimalChanges = $input->getOption('minimal-changes') || $composer->getConfig()->get('update-with-minimal-changes');

        $updateAllowTransitiveDependencies = Request::UPDATE_ONLY_LISTED;
        $flags = '';
        if ($input->getOption('update-with-all-dependencies') || $input->getOption('with-all-dependencies')) {
            $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS;
            $flags .= ' --with-all-dependencies';
        } elseif ($input->getOption('update-with-dependencies') || $input->getOption('with-dependencies')) {
            $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE;
            $flags .= ' --with-dependencies';
        }

        $io->writeError('<info>Running composer update '.implode(' ', array_keys($requirements)).$flags.'</info>');

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'require', $input, $output);
        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        $composer->getInstallationManager()->setOutputProgress(!$input->getOption('no-progress'));

        $install = Installer::create($io, $composer);

        [$preferSource, $preferDist] = $this->getPreferredInstallOptions($composer->getConfig(), $input);

        $install
            ->setDryRun($input->getOption('dry-run'))
            ->setVerbose($input->getOption('verbose'))
            ->setPreferSource($preferSource)
            ->setPreferDist($preferDist)
            ->setDevMode($updateDevMode)
            ->setOptimizeAutoloader($optimize)
            ->setClassMapAuthoritative($authoritative)
            ->setApcuAutoloader($apcu, $apcuPrefix)
            ->setUpdate(true)
            ->setInstall(!$input->getOption('no-install'))
            ->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies)
            ->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input))
            ->setPreferStable($input->getOption('prefer-stable'))
            ->setPreferLowest($input->getOption('prefer-lowest'))
            ->setAuditConfig($this->createAuditConfig($composer->getConfig(), $input))
            ->setMinimalUpdate($minimalChanges)
        ;

        // if no lock is present, or the file is brand new, we do not do a
        // partial update as this is not supported by the Installer
        if (!$this->firstRequire && $composer->getLocker()->isLocked()) {
            $install->setUpdateAllowList(array_keys($requirements));
        }

        $status = $install->run();
        if ($status !== 0 && $status !== Installer::ERROR_AUDIT_FAILED) {
            if ($status === Installer::ERROR_DEPENDENCY_RESOLUTION_FAILED) {
                foreach ($this->normalizeRequirements($input->getArgument('packages')) as $req) {
                    if (!isset($req['version'])) {
                        $io->writeError('You can also try re-running composer require with an explicit version constraint, e.g. "composer require '.$req['name'].':*" to figure out if any version is installable, or "composer require '.$req['name'].':^2.1" if you know which you need.');
                        break;
                    }
                }
            }
            $this->revertComposerFile();
        }

        return $status;
    }

    /**
     * @param list<string> $requirementsToUpdate
     */
    private function updateRequirementsAfterResolution(array $requirementsToUpdate, string $requireKey, string $removeKey, bool $sortPackages, bool $dryRun, bool $fixed): int
    {
        $composer = $this->requireComposer();
        $locker = $composer->getLocker();
        $requirements = [];
        $versionSelector = new VersionSelector(new RepositorySet());
        $repo = $locker->isLocked() ? $composer->getLocker()->getLockedRepository(true) : $composer->getRepositoryManager()->getLocalRepository();
        foreach ($requirementsToUpdate as $packageName) {
            $package = $repo->findPackage($packageName, '*');
            while ($package instanceof AliasPackage) {
                $package = $package->getAliasOf();
            }

            if (!$package instanceof PackageInterface) {
                continue;
            }

            if ($fixed) {
                $requirements[$packageName] = $package->getPrettyVersion();
            } else {
                $requirements[$packageName] = $versionSelector->findRecommendedRequireVersion($package);
            }
            $this->getIO()->writeError(sprintf(
                'Using version <info>%s</info> for <info>%s</info>',
                $requirements[$packageName],
                $packageName
            ));

            if (Preg::isMatch('{^dev-(?!main$|master$|trunk$|latest$)}', $requirements[$packageName])) {
                $this->getIO()->warning('Version '.$requirements[$packageName].' looks like it may be a feature branch which is unlikely to keep working in the long run and may be in an unstable state');
                if ($this->getIO()->isInteractive() && !$this->getIO()->askConfirmation('Are you sure you want to use this constraint (<comment>y</comment>) or would you rather abort (<comment>n</comment>) the whole operation [<comment>y,n</comment>]? ')) {
                    $this->revertComposerFile();

                    return 1;
                }
            }
        }

        if (!$dryRun) {
            $this->updateFile($this->json, $requirements, $requireKey, $removeKey, $sortPackages);
            if ($locker->isLocked() && $composer->getConfig()->get('lock')) {
                $stabilityFlags = RootPackageLoader::extractStabilityFlags($requirements, $composer->getPackage()->getMinimumStability(), []);
                $locker->updateHash($this->json, static function (array $lockData) use ($stabilityFlags) {
                    foreach ($stabilityFlags as $packageName => $flag) {
                        $lockData['stability-flags'][$packageName] = $flag;
                    }

                    return $lockData;
                });
            }
        }

        return 0;
    }

    /**
     * @param array<string, string> $new
     */
    private function updateFile(JsonFile $json, array $new, string $requireKey, string $removeKey, bool $sortPackages): void
    {
        if ($this->updateFileCleanly($json, $new, $requireKey, $removeKey, $sortPackages)) {
            return;
        }

        $composerDefinition = $this->json->read();
        foreach ($new as $package => $version) {
            $composerDefinition[$requireKey][$package] = $version;
            unset($composerDefinition[$removeKey][$package]);
            if (isset($composerDefinition[$removeKey]) && count($composerDefinition[$removeKey]) === 0) {
                unset($composerDefinition[$removeKey]);
            }
        }
        $this->json->write($composerDefinition);
    }

    /**
     * @param array<string, string> $new
     */
    private function updateFileCleanly(JsonFile $json, array $new, string $requireKey, string $removeKey, bool $sortPackages): bool
    {
        $contents = file_get_contents($json->getPath());

        $manipulator = new JsonManipulator($contents);

        foreach ($new as $package => $constraint) {
            if (!$manipulator->addLink($requireKey, $package, $constraint, $sortPackages)) {
                return false;
            }
            if (!$manipulator->removeSubNode($removeKey, $package)) {
                return false;
            }
        }

        $manipulator->removeMainKeyIfEmpty($removeKey);

        file_put_contents($json->getPath(), $manipulator->getContents());

        return true;
    }

    protected function interact(InputInterface $input, OutputInterface $output): void
    {
    }

    private function revertComposerFile(): void
    {
        $io = $this->getIO();

        if ($this->newlyCreated) {
            $io->writeError("\n".'<error>Installation failed, deleting '.$this->file.'.</error>');
            unlink($this->json->getPath());
            if (file_exists($this->lock)) {
                unlink($this->lock);
            }
        } else {
            $msg = ' to its ';
            if ($this->lockBackup) {
                $msg = ' and '.$this->lock.' to their ';
            }
            $io->writeError("\n".'<error>Installation failed, reverting '.$this->file.$msg.'original content.</error>');
            file_put_contents($this->json->getPath(), $this->composerBackup);
            if ($this->lockBackup) {
                file_put_contents($this->lock, $this->lockBackup);
            }
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Console\Input\InputOption;
use Composer\Json\JsonFile;
use Composer\Package\CompletePackageInterface;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Repository\RepositoryUtils;
use Composer\Util\PackageInfo;
use Composer\Util\PackageSorter;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

/**
 * @author Benoît Merlet <benoit.merlet@gmail.com>
 */
class LicensesCommand extends BaseCommand
{
    protected function configure(): void
    {
        $this
            ->setName('licenses')
            ->setDescription('Shows information about licenses of dependencies')
            ->setDefinition([
                new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text, json or summary', 'text', ['text', 'json', 'summary']),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'),
                new InputOption('locked', null, InputOption::VALUE_NONE, 'Shows licenses from the lock file instead of installed packages.'),
            ])
            ->setHelp(
                <<<EOT
The license command displays detailed information about the licenses of
the installed dependencies.

Use --locked to show licenses from composer.lock instead of what's currently
installed in the vendor directory.

Read more at https://getcomposer.org/doc/03-cli.md#licenses
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $composer = $this->requireComposer();

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'licenses', $input, $output);
        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        $root = $composer->getPackage();

        if ($input->getOption('locked')) {
            if (!$composer->getLocker()->isLocked()) {
                throw new \UnexpectedValueException('Valid composer.json and composer.lock files are required to run this command with --locked');
            }
            $locker = $composer->getLocker();
            $repo = $locker->getLockedRepository(!$input->getOption('no-dev'));
            $packages = $repo->getPackages();
        } else {
            $repo = $composer->getRepositoryManager()->getLocalRepository();

            if ($input->getOption('no-dev')) {
                $packages = RepositoryUtils::filterRequiredPackages($repo->getPackages(), $root);
            } else {
                $packages = $repo->getPackages();
            }
        }

        $packages = PackageSorter::sortPackagesAlphabetically($packages);
        $io = $this->getIO();

        switch ($format = $input->getOption('format')) {
            case 'text':
                $io->write('Name: <comment>'.$root->getPrettyName().'</comment>');
                $io->write('Version: <comment>'.$root->getFullPrettyVersion().'</comment>');
                $io->write('Licenses: <comment>'.(implode(', ', $root->getLicense()) ?: 'none').'</comment>');
                $io->write('Dependencies:');
                $io->write('');

                $table = new Table($output);
                $table->setStyle('compact');
                $table->setHeaders(['Name', 'Version', 'Licenses']);
                foreach ($packages as $package) {
                    $link = PackageInfo::getViewSourceOrHomepageUrl($package);
                    if ($link !== null) {
                        $name = '<href='.OutputFormatter::escape($link).'>'.$package->getPrettyName().'</>';
                    } else {
                        $name = $package->getPrettyName();
                    }

                    $table->addRow([
                        $name,
                        $package->getFullPrettyVersion(),
                        implode(', ', $package instanceof CompletePackageInterface ? $package->getLicense() : []) ?: 'none',
                    ]);
                }
                $table->render();
                break;

            case 'json':
                $dependencies = [];
                foreach ($packages as $package) {
                    $dependencies[$package->getPrettyName()] = [
                        'version' => $package->getFullPrettyVersion(),
                        'license' => $package instanceof CompletePackageInterface ? $package->getLicense() : [],
                    ];
                }

                $io->write(JsonFile::encode([
                    'name' => $root->getPrettyName(),
                    'version' => $root->getFullPrettyVersion(),
                    'license' => $root->getLicense(),
                    'dependencies' => $dependencies,
                ]));
                break;

            case 'summary':
                $usedLicenses = [];
                foreach ($packages as $package) {
                    $licenses = $package instanceof CompletePackageInterface ? $package->getLicense() : [];
                    if (count($licenses) === 0) {
                        $licenses[] = 'none';
                    }
                    foreach ($licenses as $licenseName) {
                        if (!isset($usedLicenses[$licenseName])) {
                            $usedLicenses[$licenseName] = 0;
                        }
                        $usedLicenses[$licenseName]++;
                    }
                }

                // Sort licenses so that the most used license will appear first
                arsort($usedLicenses, SORT_NUMERIC);

                $rows = [];
                foreach ($usedLicenses as $usedLicense => $numberOfDependencies) {
                    $rows[] = [$usedLicense, $numberOfDependencies];
                }

                $symfonyIo = new SymfonyStyle($input, $output);
                $symfonyIo->table(
                    ['License', 'Number of dependencies'],
                    $rows
                );
                break;
            default:
                throw new \RuntimeException(sprintf('Unsupported format "%s".  See help for supported formats.', $format));
        }

        return 0;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Package\CompletePackageInterface;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\RootPackageRepository;
use Composer\Repository\RepositoryFactory;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Console\Input\InputArgument;
use Composer\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Robert Schönthal <seroscho@googlemail.com>
 */
class HomeCommand extends BaseCommand
{
    use CompletionTrait;

    /**
     * @inheritDoc
     */
    protected function configure(): void
    {
        $this
            ->setName('browse')
            ->setAliases(['home'])
            ->setDescription('Opens the package\'s repository URL or homepage in your browser')
            ->setDefinition([
                new InputArgument('packages', InputArgument::IS_ARRAY, 'Package(s) to browse to.', null, $this->suggestInstalledPackage()),
                new InputOption('homepage', 'H', InputOption::VALUE_NONE, 'Open the homepage instead of the repository URL.'),
                new InputOption('show', 's', InputOption::VALUE_NONE, 'Only show the homepage or repository URL.'),
            ])
            ->setHelp(
                <<<EOT
The home command opens or shows a package's repository URL or
homepage in your default browser.

To open the homepage by default, use -H or --homepage.
To show instead of open the repository or homepage URL, use -s or --show.

Read more at https://getcomposer.org/doc/03-cli.md#browse-home
EOT
            );
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $repos = $this->initializeRepos();
        $io = $this->getIO();
        $return = 0;

        $packages = $input->getArgument('packages');
        if (count($packages) === 0) {
            $io->writeError('No package specified, opening homepage for the root package');
            $packages = [$this->requireComposer()->getPackage()->getName()];
        }

        foreach ($packages as $packageName) {
            $handled = false;
            $packageExists = false;
            foreach ($repos as $repo) {
                foreach ($repo->findPackages($packageName) as $package) {
                    $packageExists = true;
                    if ($package instanceof CompletePackageInterface && $this->handlePackage($package, $input->getOption('homepage'), $input->getOption('show'))) {
                        $handled = true;
                        break 2;
                    }
                }
            }

            if (!$packageExists) {
                $return = 1;
                $io->writeError('<warning>Package '.$packageName.' not found</warning>');
            }

            if (!$handled) {
                $return = 1;
                $io->writeError('<warning>'.($input->getOption('homepage') ? 'Invalid or missing homepage' : 'Invalid or missing repository URL').' for '.$packageName.'</warning>');
            }
        }

        return $return;
    }

    private function handlePackage(CompletePackageInterface $package, bool $showHomepage, bool $showOnly): bool
    {
        $support = $package->getSupport();
        $url = $support['source'] ?? $package->getSourceUrl();
        if (!$url || $showHomepage) {
            $url = $package->getHomepage();
        }

        if (!$url || !filter_var($url, FILTER_VALIDATE_URL)) {
            return false;
        }

        if ($showOnly) {
            $this->getIO()->write(sprintf('<info>%s</info>', $url));
        } else {
            $this->openBrowser($url);
        }

        return true;
    }

    /**
     * opens a url in your system default browser
     */
    private function openBrowser(string $url): void
    {
        $process = new ProcessExecutor($this->getIO());
        if (Platform::isWindows()) {
            $process->execute(['start', '"web"', 'explorer', $url], $output);

            return;
        }

        $linux = $process->execute(['which', 'xdg-open'], $output);
        $osx = $process->execute(['which', 'open'], $output);

        if (0 === $linux) {
            $process->execute(['xdg-open', $url], $output);
        } elseif (0 === $osx) {
            $process->execute(['open', $url], $output);
        } else {
            $this->getIO()->writeError('No suitable browser opening command found, open yourself: ' . $url);
        }
    }

    /**
     * Initializes repositories
     *
     * Returns an array of repos in order they should be checked in
     *
     * @return RepositoryInterface[]
     */
    private function initializeRepos(): array
    {
        $composer = $this->tryComposer();

        if ($composer) {
            return array_merge(
                [new RootPackageRepository(clone $composer->getPackage())], // root package
                [$composer->getRepositoryManager()->getLocalRepository()], // installed packages
                $composer->getRepositoryManager()->getRepositories() // remotes
            );
        }

        return RepositoryFactory::defaultReposWithDefaultManager($this->getIO());
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Advisory\Auditor;
use Composer\Pcre\Preg;
use Composer\Util\Filesystem;
use Composer\Util\Platform;
use Composer\Util\Silencer;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputArgument;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Config;
use Composer\Config\JsonConfigSource;
use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
use Composer\Semver\VersionParser;
use Composer\Package\BasePackage;

/**
 * @author Joshua Estes <Joshua.Estes@iostudio.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ConfigCommand extends BaseConfigCommand
{
    /**
     * List of additional configurable package-properties
     *
     * @var string[]
     */
    protected const CONFIGURABLE_PACKAGE_PROPERTIES = [
        'name',
        'type',
        'description',
        'homepage',
        'version',
        'minimum-stability',
        'prefer-stable',
        'keywords',
        'license',
        'repositories',
        'suggest',
        'extra',
    ];

    /**
     * @var JsonFile
     */
    protected $authConfigFile;

    /**
     * @var JsonConfigSource
     */
    protected $authConfigSource;

    protected function configure(): void
    {
        $this
            ->setName('config')
            ->setDescription('Sets config options')
            ->setDefinition([
                new InputOption('global', 'g', InputOption::VALUE_NONE, 'Apply command to the global config file'),
                new InputOption('editor', 'e', InputOption::VALUE_NONE, 'Open editor'),
                new InputOption('auth', 'a', InputOption::VALUE_NONE, 'Affect auth config file (only used for --editor)'),
                new InputOption('unset', null, InputOption::VALUE_NONE, 'Unset the given setting-key'),
                new InputOption('list', 'l', InputOption::VALUE_NONE, 'List configuration settings'),
                new InputOption('file', 'f', InputOption::VALUE_REQUIRED, 'If you want to choose a different composer.json or config.json'),
                new InputOption('absolute', null, InputOption::VALUE_NONE, 'Returns absolute paths when fetching *-dir config values instead of relative'),
                new InputOption('json', 'j', InputOption::VALUE_NONE, 'JSON decode the setting value, to be used with extra.* keys'),
                new InputOption('merge', 'm', InputOption::VALUE_NONE, 'Merge the setting value with the current value, to be used with extra.* or audit.ignore[-abandoned] keys in combination with --json'),
                new InputOption('append', null, InputOption::VALUE_NONE, 'When adding a repository, append it (lowest priority) to the existing ones instead of prepending it (highest priority)'),
                new InputOption('source', null, InputOption::VALUE_NONE, 'Display where the config value is loaded from'),
                new InputArgument('setting-key', null, 'Setting key', null, $this->suggestSettingKeys()),
                new InputArgument('setting-value', InputArgument::IS_ARRAY, 'Setting value'),
            ])
            ->setHelp(
                <<<EOT
This command allows you to edit composer config settings and repositories
in either the local composer.json file or the global config.json file.

Additionally it lets you edit most properties in the local composer.json.

To set a config setting:

    <comment>%command.full_name% bin-dir bin/</comment>

To read a config setting:

    <comment>%command.full_name% bin-dir</comment>
    Outputs: <info>bin</info>

To edit the global config.json file:

    <comment>%command.full_name% --global</comment>

To add a repository:

    <comment>%command.full_name% repositories.foo vcs https://bar.com</comment>

To remove a repository (repo is a short alias for repositories):

    <comment>%command.full_name% --unset repo.foo</comment>

To disable packagist.org:

    <comment>%command.full_name% repo.packagist.org false</comment>

You can alter repositories in the global config.json file by passing in the
<info>--global</info> option.

To add or edit suggested packages you can use:

    <comment>%command.full_name% suggest.package reason for the suggestion</comment>

To add or edit extra properties you can use:

    <comment>%command.full_name% extra.property value</comment>

Or to add a complex value you can use json with:

    <comment>%command.full_name% extra.property --json '{"foo":true, "bar": []}'</comment>

To edit the file in an external editor:

    <comment>%command.full_name% --editor</comment>

To choose your editor you can set the "EDITOR" env variable.

To get a list of configuration values in the file:

    <comment>%command.full_name% --list</comment>

You can always pass more than one option. As an example, if you want to edit the
global config.json file.

    <comment>%command.full_name% --editor --global</comment>

Read more at https://getcomposer.org/doc/03-cli.md#config
EOT
            )
        ;
    }

    /**
     * @throws \Exception
     */
    protected function initialize(InputInterface $input, OutputInterface $output): void
    {
        parent::initialize($input, $output);

        $authConfigFile = $this->getAuthConfigFile($input, $this->config);

        $this->authConfigFile = new JsonFile($authConfigFile, null, $this->getIO());
        $this->authConfigSource = new JsonConfigSource($this->authConfigFile, true);

        // Initialize the global file if it's not there, ignoring any warnings or notices
        if ($input->getOption('global') && !$this->authConfigFile->exists()) {
            touch($this->authConfigFile->getPath());
            $this->authConfigFile->write(['bitbucket-oauth' => new \ArrayObject, 'github-oauth' => new \ArrayObject, 'gitlab-oauth' => new \ArrayObject, 'gitlab-token' => new \ArrayObject, 'http-basic' => new \ArrayObject, 'bearer' => new \ArrayObject, 'forgejo-token' => new \ArrayObject()]);
            Silencer::call('chmod', $this->authConfigFile->getPath(), 0600);
        }
    }

    /**
     * @throws \Seld\JsonLint\ParsingException
     */
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        // Open file in editor
        if (true === $input->getOption('editor')) {
            $editor = Platform::getEnv('EDITOR');
            if (false === $editor || '' === $editor) {
                if (Platform::isWindows()) {
                    $editor = 'notepad';
                } else {
                    foreach (['editor', 'vim', 'vi', 'nano', 'pico', 'ed'] as $candidate) {
                        if (exec('which '.$candidate)) {
                            $editor = $candidate;
                            break;
                        }
                    }
                }
            } else {
                $editor = escapeshellcmd($editor);
            }

            $file = $input->getOption('auth') ? $this->authConfigFile->getPath() : $this->configFile->getPath();
            system($editor . ' ' . $file . (Platform::isWindows() ? '' : ' > `tty`'));

            return 0;
        }

        if (false === $input->getOption('global')) {
            $this->config->merge($this->configFile->read(), $this->configFile->getPath());
            $this->config->merge(['config' => $this->authConfigFile->exists() ? $this->authConfigFile->read() : []], $this->authConfigFile->getPath());
        }

        $this->getIO()->loadConfiguration($this->config);

        // List the configuration of the file settings
        if (true === $input->getOption('list')) {
            $this->listConfiguration($this->config->all(), $this->config->raw(), $output, null, $input->getOption('source'));

            return 0;
        }

        $settingKey = $input->getArgument('setting-key');
        if (!is_string($settingKey)) {
            return 0;
        }

        // If the user enters in a config variable, parse it and save to file
        if ([] !== $input->getArgument('setting-value') && $input->getOption('unset')) {
            throw new \RuntimeException('You can not combine a setting value with --unset');
        }

        // show the value if no value is provided
        if ([] === $input->getArgument('setting-value') && !$input->getOption('unset')) {
            $properties = self::CONFIGURABLE_PACKAGE_PROPERTIES;
            $propertiesDefaults = [
                'type' => 'library',
                'description' => '',
                'homepage' => '',
                'minimum-stability' => 'stable',
                'prefer-stable' => false,
                'keywords' => [],
                'license' => [],
                'suggest' => [],
                'extra' => [],
            ];
            $rawData = $this->configFile->read();
            $data = $this->config->all();
            $source = $this->config->getSourceOfValue($settingKey);

            if (Preg::isMatch('/^repos?(?:itories)?(?:\.(.+))?/', $settingKey, $matches)) {
                if (!isset($matches[1])) {
                    $value = $data['repositories'] ?? [];
                } else {
                    if (!isset($data['repositories'][$matches[1]])) {
                        throw new \InvalidArgumentException('There is no '.$matches[1].' repository defined');
                    }

                    $value = $data['repositories'][$matches[1]];
                }
            } elseif (strpos($settingKey, '.')) {
                $bits = explode('.', $settingKey);
                if ($bits[0] === 'extra' || $bits[0] === 'suggest') {
                    $data = $rawData;
                } else {
                    $data = $data['config'];
                }
                $match = false;
                foreach ($bits as $bit) {
                    $key = isset($key) ? $key.'.'.$bit : $bit;
                    $match = false;
                    if (isset($data[$key])) {
                        $match = true;
                        $data = $data[$key];
                        unset($key);
                    }
                }

                if (!$match) {
                    throw new \RuntimeException($settingKey.' is not defined.');
                }

                $value = $data;
            } elseif (isset($data['config'][$settingKey])) {
                $value = $this->config->get($settingKey, $input->getOption('absolute') ? 0 : Config::RELATIVE_PATHS);
                // ensure we get {} output for properties which are objects
                if ($value === []) {
                    $schema = JsonFile::parseJson((string) file_get_contents(JsonFile::COMPOSER_SCHEMA_PATH));
                    if (
                        isset($schema['properties']['config']['properties'][$settingKey]['type'])
                        && in_array('object', (array) $schema['properties']['config']['properties'][$settingKey]['type'], true)
                    ) {
                        $value = new \stdClass;
                    }
                }
            } elseif (isset($rawData[$settingKey]) && in_array($settingKey, $properties, true)) {
                $value = $rawData[$settingKey];
                $source = $this->configFile->getPath();
            } elseif (isset($propertiesDefaults[$settingKey])) {
                $value = $propertiesDefaults[$settingKey];
                $source = 'defaults';
            } else {
                throw new \RuntimeException($settingKey.' is not defined');
            }

            if (is_array($value) || is_object($value) || is_bool($value)) {
                $value = JsonFile::encode($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
            }

            $sourceOfConfigValue = '';
            if ($input->getOption('source')) {
                $sourceOfConfigValue = ' (' . $source . ')';
            }

            $this->getIO()->write($value . $sourceOfConfigValue, true, IOInterface::QUIET);

            return 0;
        }

        $values = $input->getArgument('setting-value'); // what the user is trying to add/change

        $booleanValidator = static function ($val): bool {
            return in_array($val, ['true', 'false', '1', '0'], true);
        };
        $booleanNormalizer = static function ($val): bool {
            return $val !== 'false' && (bool) $val;
        };

        // handle config values
        $uniqueConfigValues = [
            'process-timeout' => ['is_numeric', 'intval'],
            'use-include-path' => [$booleanValidator, $booleanNormalizer],
            'use-github-api' => [$booleanValidator, $booleanNormalizer],
            'preferred-install' => [
                static function ($val): bool {
                    return in_array($val, ['auto', 'source', 'dist'], true);
                },
                static function ($val) {
                    return $val;
                },
            ],
            'gitlab-protocol' => [
                static function ($val): bool {
                    return in_array($val, ['git', 'http', 'https'], true);
                },
                static function ($val) {
                    return $val;
                },
            ],
            'store-auths' => [
                static function ($val): bool {
                    return in_array($val, ['true', 'false', 'prompt'], true);
                },
                static function ($val) {
                    if ('prompt' === $val) {
                        return 'prompt';
                    }

                    return $val !== 'false' && (bool) $val;
                },
            ],
            'notify-on-install' => [$booleanValidator, $booleanNormalizer],
            'vendor-dir' => ['is_string', static function ($val) {
                return $val;
            }],
            'bin-dir' => ['is_string', static function ($val) {
                return $val;
            }],
            'archive-dir' => ['is_string', static function ($val) {
                return $val;
            }],
            'archive-format' => ['is_string', static function ($val) {
                return $val;
            }],
            'data-dir' => ['is_string', static function ($val) {
                return $val;
            }],
            'cache-dir' => ['is_string', static function ($val) {
                return $val;
            }],
            'cache-files-dir' => ['is_string', static function ($val) {
                return $val;
            }],
            'cache-repo-dir' => ['is_string', static function ($val) {
                return $val;
            }],
            'cache-vcs-dir' => ['is_string', static function ($val) {
                return $val;
            }],
            'cache-ttl' => ['is_numeric', 'intval'],
            'cache-files-ttl' => ['is_numeric', 'intval'],
            'cache-files-maxsize' => [
                static function ($val): bool {
                    return Preg::isMatch('/^\s*([0-9.]+)\s*(?:([kmg])(?:i?b)?)?\s*$/i', $val);
                },
                static function ($val) {
                    return $val;
                },
            ],
            'bin-compat' => [
                static function ($val): bool {
                    return in_array($val, ['auto', 'full', 'proxy', 'symlink']);
                },
                static function ($val) {
                    return $val;
                },
            ],
            'discard-changes' => [
                static function ($val): bool {
                    return in_array($val, ['stash', 'true', 'false', '1', '0'], true);
                },
                static function ($val) {
                    if ('stash' === $val) {
                        return 'stash';
                    }

                    return $val !== 'false' && (bool) $val;
                },
            ],
            'autoloader-suffix' => ['is_string', static function ($val) {
                return $val === 'null' ? null : $val;
            }],
            'sort-packages' => [$booleanValidator, $booleanNormalizer],
            'optimize-autoloader' => [$booleanValidator, $booleanNormalizer],
            'classmap-authoritative' => [$booleanValidator, $booleanNormalizer],
            'apcu-autoloader' => [$booleanValidator, $booleanNormalizer],
            'prepend-autoloader' => [$booleanValidator, $booleanNormalizer],
            'update-with-minimal-changes' => [$booleanValidator, $booleanNormalizer],
            'disable-tls' => [$booleanValidator, $booleanNormalizer],
            'secure-http' => [$booleanValidator, $booleanNormalizer],
            'bump-after-update' => [
                static function ($val): bool {
                    return in_array($val, ['dev', 'no-dev', 'true', 'false', '1', '0'], true);
                },
                static function ($val) {
                    if ('dev' === $val || 'no-dev' === $val) {
                        return $val;
                    }

                    return $val !== 'false' && (bool) $val;
                },
            ],
            'cafile' => [
                static function ($val): bool {
                    return file_exists($val) && Filesystem::isReadable($val);
                },
                static function ($val) {
                    return $val === 'null' ? null : $val;
                },
            ],
            'capath' => [
                static function ($val): bool {
                    return is_dir($val) && Filesystem::isReadable($val);
                },
                static function ($val) {
                    return $val === 'null' ? null : $val;
                },
            ],
            'github-expose-hostname' => [$booleanValidator, $booleanNormalizer],
            'htaccess-protect' => [$booleanValidator, $booleanNormalizer],
            'lock' => [$booleanValidator, $booleanNormalizer],
            'allow-plugins' => [$booleanValidator, $booleanNormalizer],
            'platform-check' => [
                static function ($val): bool {
                    return in_array($val, ['php-only', 'true', 'false', '1', '0'], true);
                },
                static function ($val) {
                    if ('php-only' === $val) {
                        return 'php-only';
                    }

                    return $val !== 'false' && (bool) $val;
                },
            ],
            'use-parent-dir' => [
                static function ($val): bool {
                    return in_array($val, ['true', 'false', 'prompt'], true);
                },
                static function ($val) {
                    if ('prompt' === $val) {
                        return 'prompt';
                    }

                    return $val !== 'false' && (bool) $val;
                },
            ],
            'audit.abandoned' => [
                static function ($val): bool {
                    return in_array($val, [Auditor::ABANDONED_IGNORE, Auditor::ABANDONED_REPORT, Auditor::ABANDONED_FAIL], true);
                },
                static function ($val) {
                    return $val;
                },
            ],
            'audit.ignore-unreachable' => [$booleanValidator, $booleanNormalizer],
            'audit.block-insecure' => [$booleanValidator, $booleanNormalizer],
            'audit.block-abandoned' => [$booleanValidator, $booleanNormalizer],
        ];
        $multiConfigValues = [
            'github-protocols' => [
                static function ($vals) {
                    if (!is_array($vals)) {
                        return 'array expected';
                    }

                    foreach ($vals as $val) {
                        if (!in_array($val, ['git', 'https', 'ssh'])) {
                            return 'valid protocols include: git, https, ssh';
                        }
                    }

                    return true;
                },
                static function ($vals) {
                    return $vals;
                },
            ],
            'github-domains' => [
                static function ($vals) {
                    if (!is_array($vals)) {
                        return 'array expected';
                    }

                    return true;
                },
                static function ($vals) {
                    return $vals;
                },
            ],
            'gitlab-domains' => [
                static function ($vals) {
                    if (!is_array($vals)) {
                        return 'array expected';
                    }

                    return true;
                },
                static function ($vals) {
                    return $vals;
                },
            ],
            'audit.ignore-severity' => [
                static function ($vals) {
                    if (!is_array($vals)) {
                        return 'array expected';
                    }

                    foreach ($vals as $val) {
                        if (!in_array($val, ['low', 'medium', 'high', 'critical'], true)) {
                            return 'valid severities include: low, medium, high, critical';
                        }
                    }

                    return true;
                },
                static function ($vals) {
                    return $vals;
                },
            ],
        ];

        // allow unsetting audit config entirely
        if ($input->getOption('unset') && $settingKey === 'audit') {
            $this->configSource->removeConfigSetting($settingKey);

            return 0;
        }

        if ($input->getOption('unset') && (isset($uniqueConfigValues[$settingKey]) || isset($multiConfigValues[$settingKey]))) {
            if ($settingKey === 'disable-tls' && $this->config->get('disable-tls')) {
                $this->getIO()->writeError('<info>You are now running Composer with SSL/TLS protection enabled.</info>');
            }

            $this->configSource->removeConfigSetting($settingKey);

            return 0;
        }
        if (isset($uniqueConfigValues[$settingKey])) {
            $this->handleSingleValue($settingKey, $uniqueConfigValues[$settingKey], $values, 'addConfigSetting');

            return 0;
        }
        if (isset($multiConfigValues[$settingKey])) {
            $this->handleMultiValue($settingKey, $multiConfigValues[$settingKey], $values, 'addConfigSetting');

            return 0;
        }
        // handle preferred-install per-package config
        if (Preg::isMatch('/^preferred-install\.(.+)/', $settingKey, $matches)) {
            if ($input->getOption('unset')) {
                $this->configSource->removeConfigSetting($settingKey);

                return 0;
            }

            [$validator] = $uniqueConfigValues['preferred-install'];
            if (!$validator($values[0])) {
                throw new \RuntimeException('Invalid value for '.$settingKey.'. Should be one of: auto, source, or dist');
            }

            $this->configSource->addConfigSetting($settingKey, $values[0]);

            return 0;
        }

        // handle allow-plugins config setting elements true or false to add/remove
        if (Preg::isMatch('{^allow-plugins\.([a-zA-Z0-9/*-]+)}', $settingKey, $matches)) {
            if ($input->getOption('unset')) {
                $this->configSource->removeConfigSetting($settingKey);

                return 0;
            }

            if (true !== $booleanValidator($values[0])) {
                throw new \RuntimeException(sprintf(
                    '"%s" is an invalid value',
                    $values[0]
                ));
            }

            $normalizedValue = $booleanNormalizer($values[0]);

            $this->configSource->addConfigSetting($settingKey, $normalizedValue);

            return 0;
        }

        // handle properties
        $uniqueProps = [
            'name' => ['is_string', static function ($val) {
                return $val;
            }],
            'type' => ['is_string', static function ($val) {
                return $val;
            }],
            'description' => ['is_string', static function ($val) {
                return $val;
            }],
            'homepage' => ['is_string', static function ($val) {
                return $val;
            }],
            'version' => ['is_string', static function ($val) {
                return $val;
            }],
            'minimum-stability' => [
                static function ($val): bool {
                    return isset(BasePackage::STABILITIES[VersionParser::normalizeStability($val)]);
                },
                static function ($val): string {
                    return VersionParser::normalizeStability($val);
                },
            ],
            'prefer-stable' => [$booleanValidator, $booleanNormalizer],
        ];
        $multiProps = [
            'keywords' => [
                static function ($vals) {
                    if (!is_array($vals)) {
                        return 'array expected';
                    }

                    return true;
                },
                static function ($vals) {
                    return $vals;
                },
            ],
            'license' => [
                static function ($vals) {
                    if (!is_array($vals)) {
                        return 'array expected';
                    }

                    return true;
                },
                static function ($vals) {
                    return $vals;
                },
            ],
        ];

        if ($input->getOption('global') && (isset($uniqueProps[$settingKey]) || isset($multiProps[$settingKey]) || strpos($settingKey, 'extra.') === 0)) {
            throw new \InvalidArgumentException('The ' . $settingKey . ' property can not be set in the global config.json file. Use `composer global config` to apply changes to the global composer.json');
        }
        if ($input->getOption('unset') && (isset($uniqueProps[$settingKey]) || isset($multiProps[$settingKey]))) {
            $this->configSource->removeProperty($settingKey);

            return 0;
        }
        if (isset($uniqueProps[$settingKey])) {
            $this->handleSingleValue($settingKey, $uniqueProps[$settingKey], $values, 'addProperty');

            return 0;
        }
        if (isset($multiProps[$settingKey])) {
            $this->handleMultiValue($settingKey, $multiProps[$settingKey], $values, 'addProperty');

            return 0;
        }

        // handle repositories
        if (Preg::isMatchStrictGroups('/^repos?(?:itories)?\.(.+)/', $settingKey, $matches)) {
            if ($input->getOption('unset')) {
                $this->configSource->removeRepository($matches[1]);

                return 0;
            }

            if (2 === count($values)) {
                $this->configSource->addRepository($matches[1], [
                    'type' => $values[0],
                    'url' => $values[1],
                ], $input->getOption('append'));

                return 0;
            }

            if (1 === count($values)) {
                $value = strtolower($values[0]);
                if (true === $booleanValidator($value)) {
                    if (false === $booleanNormalizer($value)) {
                        $this->configSource->addRepository($matches[1], false, $input->getOption('append'));

                        return 0;
                    }
                } else {
                    $value = JsonFile::parseJson($values[0]);
                    $this->configSource->addRepository($matches[1], $value, $input->getOption('append'));

                    return 0;
                }
            }

            throw new \RuntimeException('You must pass the type and a url. Example: php composer.phar config repositories.foo vcs https://bar.com');
        }

        // handle extra
        if (Preg::isMatch('/^extra\.(.+)/', $settingKey, $matches)) {
            if ($input->getOption('unset')) {
                $this->configSource->removeProperty($settingKey);

                return 0;
            }

            $value = $values[0];
            if ($input->getOption('json')) {
                $value = JsonFile::parseJson($value);
                if ($input->getOption('merge')) {
                    $currentValue = $this->configFile->read();
                    $bits = explode('.', $settingKey);
                    foreach ($bits as $bit) {
                        $currentValue = $currentValue[$bit] ?? null;
                    }
                    if (is_array($currentValue) && is_array($value)) {
                        if (array_is_list($currentValue) && array_is_list($value)) {
                            $value = array_merge($currentValue, $value);
                        } else {
                            $value = $value + $currentValue;
                        }
                    }
                }
            }
            $this->configSource->addProperty($settingKey, $value);

            return 0;
        }

        // handle suggest
        if (Preg::isMatch('/^suggest\.(.+)/', $settingKey, $matches)) {
            if ($input->getOption('unset')) {
                $this->configSource->removeProperty($settingKey);

                return 0;
            }

            $this->configSource->addProperty($settingKey, implode(' ', $values));

            return 0;
        }

        // handle unsetting extra/suggest
        if (in_array($settingKey, ['suggest', 'extra'], true) && $input->getOption('unset')) {
            $this->configSource->removeProperty($settingKey);

            return 0;
        }

        // handle platform
        if (Preg::isMatch('/^platform\.(.+)/', $settingKey, $matches)) {
            if ($input->getOption('unset')) {
                $this->configSource->removeConfigSetting($settingKey);

                return 0;
            }

            $this->configSource->addConfigSetting($settingKey, $values[0] === 'false' ? false : $values[0]);

            return 0;
        }

        // handle unsetting platform
        if ($settingKey === 'platform' && $input->getOption('unset')) {
            $this->configSource->removeConfigSetting($settingKey);

            return 0;
        }

        // handle audit.ignore and audit.ignore-abandoned with --merge support
        if (in_array($settingKey, ['audit.ignore', 'audit.ignore-abandoned'], true)) {
            if ($input->getOption('unset')) {
                $this->configSource->removeConfigSetting($settingKey);

                return 0;
            }

            $value = $values;
            if ($input->getOption('json')) {
                $value = JsonFile::parseJson($values[0]);
                if (!is_array($value)) {
                    throw new \RuntimeException('Expected an array or object for '.$settingKey);
                }
            }

            if ($input->getOption('merge')) {
                $currentConfig = $this->configFile->read();
                $currentValue = $currentConfig['config']['audit'][str_replace('audit.', '', $settingKey)] ?? null;

                if ($currentValue !== null && is_array($currentValue) && is_array($value)) {
                    if (array_is_list($currentValue) && array_is_list($value)) {
                        // Both are lists, merge them
                        $value = array_merge($currentValue, $value);
                    } elseif (!array_is_list($currentValue) && !array_is_list($value)) {
                        // Both are associative arrays (objects), merge them
                        $value = $value + $currentValue;
                    } else {
                        throw new \RuntimeException('Cannot merge array and object for '.$settingKey);
                    }
                }
            }

            $this->configSource->addConfigSetting($settingKey, $value);

            return 0;
        }

        // handle auth
        if (Preg::isMatch('/^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|http-basic|custom-headers|bearer|forgejo-token)\.(.+)/', $settingKey, $matches)) {
            if ($input->getOption('unset')) {
                $this->authConfigSource->removeConfigSetting($matches[1].'.'.$matches[2]);
                $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);

                return 0;
            }

            if ($matches[1] === 'bitbucket-oauth') {
                if (2 !== count($values)) {
                    throw new \RuntimeException('Expected two arguments (consumer-key, consumer-secret), got '.count($values));
                }
                $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
                $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], ['consumer-key' => $values[0], 'consumer-secret' => $values[1]]);
            } elseif ($matches[1] === 'gitlab-token' && 2 === count($values)) {
                $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
                $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], ['username' => $values[0], 'token' => $values[1]]);
            } elseif (in_array($matches[1], ['github-oauth', 'gitlab-oauth', 'gitlab-token', 'bearer'], true)) {
                if (1 !== count($values)) {
                    throw new \RuntimeException('Too many arguments, expected only one token');
                }
                $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
                $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], $values[0]);
            } elseif ($matches[1] === 'http-basic') {
                if (2 !== count($values)) {
                    throw new \RuntimeException('Expected two arguments (username, password), got '.count($values));
                }
                $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
                $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], ['username' => $values[0], 'password' => $values[1]]);
            } elseif ($matches[1] === 'custom-headers') {
                if (count($values) === 0) {
                    throw new \RuntimeException('Expected at least one argument (header), got none');
                }

                // Validate headers format
                $formattedHeaders = [];
                foreach ($values as $header) {
                    if (!is_string($header)) {
                        throw new \RuntimeException('Headers must be strings in "Header-Name: Header-Value" format');
                    }

                    // Check if the header is in correct "Name: Value" format
                    if (!Preg::isMatch('/^[^:]+:\s*.+$/', $header, $headerParts)) {
                        throw new \RuntimeException('Header "' . $header . '" is not in "Header-Name: Header-Value" format');
                    }

                    $formattedHeaders[] = $header;
                }

                $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
                $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], $formattedHeaders);
            } elseif ($matches[1] === 'forgejo-token') {
                if (2 !== count($values)) {
                    throw new \RuntimeException('Expected two arguments (username, access token), got '.count($values));
                }
                $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
                $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], ['username' => $values[0], 'token' => $values[1]]);
            }

            return 0;
        }

        // handle script
        if (Preg::isMatch('/^scripts\.(.+)/', $settingKey, $matches)) {
            if ($input->getOption('unset')) {
                $this->configSource->removeProperty($settingKey);

                return 0;
            }

            $this->configSource->addProperty($settingKey, count($values) > 1 ? $values : $values[0]);

            return 0;
        }

        // handle unsetting other top level properties
        if ($input->getOption('unset')) {
            $this->configSource->removeProperty($settingKey);

            return 0;
        }

        throw new \InvalidArgumentException('Setting '.$settingKey.' does not exist or is not supported by this command');
    }

    /**
     * @param array{callable, callable} $callbacks Validator and normalizer callbacks
     * @param array<string> $values
     */
    protected function handleSingleValue(string $key, array $callbacks, array $values, string $method): void
    {
        [$validator, $normalizer] = $callbacks;
        if (1 !== count($values)) {
            throw new \RuntimeException('You can only pass one value. Example: php composer.phar config process-timeout 300');
        }

        if (true !== $validation = $validator($values[0])) {
            throw new \RuntimeException(sprintf(
                '"%s" is an invalid value'.($validation ? ' ('.$validation.')' : ''),
                $values[0]
            ));
        }

        $normalizedValue = $normalizer($values[0]);

        if ($key === 'disable-tls') {
            if (!$normalizedValue && $this->config->get('disable-tls')) {
                $this->getIO()->writeError('<info>You are now running Composer with SSL/TLS protection enabled.</info>');
            } elseif ($normalizedValue && !$this->config->get('disable-tls')) {
                $this->getIO()->writeError('<warning>You are now running Composer with SSL/TLS protection disabled.</warning>');
            }
        }

        call_user_func([$this->configSource, $method], $key, $normalizedValue);
    }

    /**
     * @param array{callable, callable} $callbacks Validator and normalizer callbacks
     * @param array<string> $values
     */
    protected function handleMultiValue(string $key, array $callbacks, array $values, string $method): void
    {
        [$validator, $normalizer] = $callbacks;
        if (true !== $validation = $validator($values)) {
            throw new \RuntimeException(sprintf(
                '%s is an invalid value'.($validation ? ' ('.$validation.')' : ''),
                json_encode($values)
            ));
        }

        call_user_func([$this->configSource, $method], $key, $normalizer($values));
    }

    /**
     * Display the contents of the file in a pretty formatted way
     *
     * @param array<mixed[]|bool|string> $contents
     * @param array<mixed[]|string>      $rawContents
     */
    protected function listConfiguration(array $contents, array $rawContents, OutputInterface $output, ?string $k = null, bool $showSource = false): void
    {
        $origK = $k;
        $io = $this->getIO();
        foreach ($contents as $key => $value) {
            if ($k === null && !in_array($key, ['config', 'repositories'])) {
                continue;
            }

            $rawVal = $rawContents[$key] ?? null;

            if (is_array($value) && (!is_numeric(key($value)) || ($key === 'repositories' && null === $k))) {
                $k .= Preg::replace('{^config\.}', '', $key . '.');
                $this->listConfiguration($value, $rawVal, $output, $k, $showSource);
                $k = $origK;

                continue;
            }

            if (is_array($value)) {
                $value = array_map(static function ($val) {
                    return is_array($val) ? json_encode($val) : $val;
                }, $value);

                $value = '['.implode(', ', $value).']';
            }

            if (is_bool($value)) {
                $value = var_export($value, true);
            }

            $source = '';
            if ($showSource) {
                $source = ' (' . $this->config->getSourceOfValue($k . $key) . ')';
            }

            if (null !== $k && 0 === strpos($k, 'repositories')) {
                $link = 'https://getcomposer.org/doc/05-repositories.md';
            } else {
                $id = Preg::replace('{\..*$}', '', $k === '' || $k === null ? (string) $key : $k);
                $id = Preg::replace('{[^a-z0-9]}i', '-', strtolower(trim($id)));
                $id = Preg::replace('{-+}', '-', $id);
                $link = 'https://getcomposer.org/doc/06-config.md#' . $id;
            }
            if (is_string($rawVal) && $rawVal !== $value) {
                $io->write('[<fg=yellow;href=' . $link .'>' . $k . $key . '</>] <info>' . $rawVal . ' (' . $value . ')</info>' . $source, true, IOInterface::QUIET);
            } else {
                $io->write('[<fg=yellow;href=' . $link .'>' . $k . $key . '</>] <info>' . $value . '</info>' . $source, true, IOInterface::QUIET);
            }
        }
    }

    /**
     * Suggest setting-keys, while taking given options in account.
     */
    private function suggestSettingKeys(): \Closure
    {
        return function (CompletionInput $input): array {
            if ($input->getOption('list') || $input->getOption('editor') || $input->getOption('auth')) {
                return [];
            }

            // initialize configuration
            $config = Factory::createConfig();

            // load configuration
            $configFile = new JsonFile($this->getComposerConfigFile($input, $config));
            if ($configFile->exists()) {
                $config->merge($configFile->read(), $configFile->getPath());
            }

            // load auth-configuration
            $authConfigFile = new JsonFile($this->getAuthConfigFile($input, $config));
            if ($authConfigFile->exists()) {
                $config->merge(['config' => $authConfigFile->read()], $authConfigFile->getPath());
            }

            // collect all configuration setting-keys
            $rawConfig = $config->raw();
            $keys = array_merge(
                $this->flattenSettingKeys($rawConfig['config']),
                $this->flattenSettingKeys($rawConfig['repositories'], 'repositories.')
            );

            // if unsetting …
            if ($input->getOption('unset')) {
                // … keep only the currently customized setting-keys …
                $sources = [$configFile->getPath(), $authConfigFile->getPath()];
                $keys = array_filter(
                    $keys,
                    static function (string $key) use ($config, $sources): bool {
                        return in_array($config->getSourceOfValue($key), $sources, true);
                    }
                );

            // … else if showing or setting a value …
            } else {
                // … add all configurable package-properties, no matter if it exist
                $keys = array_merge($keys, self::CONFIGURABLE_PACKAGE_PROPERTIES);

                // it would be nice to distinguish between showing and setting
                // a value, but that makes the implementation much more complex
                // and partially impossible because symfony's implementation
                // does not complete arguments followed by other arguments
            }

            // add all existing configurable package-properties
            if ($configFile->exists()) {
                $properties = array_filter(
                    $configFile->read(),
                    static function (string $key): bool {
                        return in_array($key, self::CONFIGURABLE_PACKAGE_PROPERTIES, true);
                    },
                    ARRAY_FILTER_USE_KEY
                );

                $keys = array_merge(
                    $keys,
                    $this->flattenSettingKeys($properties)
                );
            }

            // filter settings-keys by completion value
            $completionValue = $input->getCompletionValue();

            if ($completionValue !== '') {
                $keys = array_filter(
                    $keys,
                    static function (string $key) use ($completionValue): bool {
                        return str_starts_with($key, $completionValue);
                    }
                );
            }

            sort($keys);

            return array_unique($keys);
        };
    }

    /**
     * build a flat list of dot-separated setting-keys from given config
     *
     * @param array<mixed[]|string>  $config
     * @return string[]
     */
    private function flattenSettingKeys(array $config, string $prefix = ''): array
    {
        $keys = [];
        foreach ($config as $key => $value) {
            $keys[] = [$prefix . $key];
            // array-lists must not be added to completion
            // sub-keys of repository-keys must not be added to completion
            if (is_array($value) && !array_is_list($value) && $prefix !== 'repositories.') {
                $keys[] = $this->flattenSettingKeys($value, $prefix . $key . '.');
            }
        }

        return array_merge(...$keys);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\IO\IOInterface;
use Composer\Package\AliasPackage;
use Composer\Package\BasePackage;
use Composer\Package\Version\VersionBumper;
use Composer\Pcre\Preg;
use Composer\Util\Filesystem;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputArgument;
use Composer\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Factory;
use Composer\Json\JsonFile;
use Composer\Json\JsonManipulator;
use Composer\Repository\PlatformRepository;
use Composer\Util\Silencer;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
final class BumpCommand extends BaseCommand
{
    private const ERROR_GENERIC = 1;
    private const ERROR_LOCK_OUTDATED = 2;

    use CompletionTrait;

    protected function configure(): void
    {
        $this
            ->setName('bump')
            ->setDescription('Increases the lower limit of your composer.json requirements to the currently installed versions')
            ->setDefinition([
                new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Optional package name(s) to restrict which packages are bumped.', null, $this->suggestRootRequirement()),
                new InputOption('dev-only', 'D', InputOption::VALUE_NONE, 'Only bump requirements in "require-dev".'),
                new InputOption('no-dev-only', 'R', InputOption::VALUE_NONE, 'Only bump requirements in "require".'),
                new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the packages to bump, but will not execute anything.'),
            ])
            ->setHelp(
                <<<EOT
The <info>bump</info> command increases the lower limit of your composer.json requirements
to the currently installed versions. This helps to ensure your dependencies do not
accidentally get downgraded due to some other conflict, and can slightly improve
dependency resolution performance as it limits the amount of package versions
Composer has to look at.

Running this blindly on libraries is **NOT** recommended as it will narrow down
your allowed dependencies, which may cause dependency hell for your users.
Running it with <info>--dev-only</info> on libraries may be fine however as dev requirements
are local to the library and do not affect consumers of the package.

EOT
            )
        ;
    }

    /**
     * @throws \Seld\JsonLint\ParsingException
     */
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        return $this->doBump(
            $this->getIO(),
            $input->getOption('dev-only'),
            $input->getOption('no-dev-only'),
            $input->getOption('dry-run'),
            $input->getArgument('packages')
        );
    }

    /**
     * @internal
     * @param string[] $packagesFilter
     * @throws \Seld\JsonLint\ParsingException
     */
    public function doBump(
        IOInterface $io,
        bool $devOnly,
        bool $noDevOnly,
        bool $dryRun,
        array $packagesFilter,
        string $devOnlyFlagHint = '--dev-only'
    ): int {
        /** @readonly */
        $composerJsonPath = Factory::getComposerFile();

        if (!Filesystem::isReadable($composerJsonPath)) {
            $io->writeError('<error>'.$composerJsonPath.' is not readable.</error>');

            return self::ERROR_GENERIC;
        }

        $composerJson = new JsonFile($composerJsonPath);
        $contents = file_get_contents($composerJson->getPath());
        if (false === $contents) {
            $io->writeError('<error>'.$composerJsonPath.' is not readable.</error>');

            return self::ERROR_GENERIC;
        }

        // check for writability by writing to the file as is_writable can not be trusted on network-mounts
        // see https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926
        if (!is_writable($composerJsonPath) && false === Silencer::call('file_put_contents', $composerJsonPath, $contents)) {
            $io->writeError('<error>'.$composerJsonPath.' is not writable.</error>');

            return self::ERROR_GENERIC;
        }
        unset($contents);

        $composer = $this->requireComposer();
        $hasLockfileDisabled = !$composer->getConfig()->has('lock') || $composer->getConfig()->get('lock');
        if (!$hasLockfileDisabled) {
            $repo = $composer->getLocker()->getLockedRepository(true);
        } elseif ($composer->getLocker()->isLocked()) {
            if (!$composer->getLocker()->isFresh()) {
                $io->writeError('<error>The lock file is not up to date with the latest changes in composer.json. Run the appropriate `update` to fix that before you use the `bump` command.</error>');

                return self::ERROR_LOCK_OUTDATED;
            }

            $repo = $composer->getLocker()->getLockedRepository(true);
        } else {
            $repo = $composer->getRepositoryManager()->getLocalRepository();
        }

        if ($composer->getPackage()->getType() !== 'project' && !$devOnly) {
            $io->writeError('<warning>Warning: Bumping dependency constraints is not recommended for libraries as it will narrow down your dependencies and may cause problems for your users.</warning>');

            $contents = $composerJson->read();
            if (!isset($contents['type'])) {
                $io->writeError('<warning>If your package is not a library, you can explicitly specify the "type" by using "composer config type project".</warning>');
                $io->writeError('<warning>Alternatively you can use '.$devOnlyFlagHint.' to only bump dependencies within "require-dev".</warning>');
            }
            unset($contents);
        }

        $bumper = new VersionBumper();
        $tasks = [];
        if (!$devOnly) {
            $tasks['require'] = $composer->getPackage()->getRequires();
        }
        if (!$noDevOnly) {
            $tasks['require-dev'] = $composer->getPackage()->getDevRequires();
        }

        if (count($packagesFilter) > 0) {
            // support proxied args from the update command that contain constraints together with the package names
            $packagesFilter = array_map(static function ($constraint) {
                return Preg::replace('{[:= ].+}', '', $constraint);
            }, $packagesFilter);
            $pattern = BasePackage::packageNamesToRegexp(array_unique(array_map('strtolower', $packagesFilter)));
            foreach ($tasks as $key => $reqs) {
                foreach ($reqs as $pkgName => $link) {
                    if (!Preg::isMatch($pattern, $pkgName)) {
                        unset($tasks[$key][$pkgName]);
                    }
                }
            }
        }

        $updates = [];
        foreach ($tasks as $key => $reqs) {
            foreach ($reqs as $pkgName => $link) {
                if (PlatformRepository::isPlatformPackage($pkgName)) {
                    continue;
                }
                $currentConstraint = $link->getPrettyConstraint();

                $package = $repo->findPackage($pkgName, '*');
                // name must be provided or replaced
                if (null === $package) {
                    continue;
                }
                while ($package instanceof AliasPackage) {
                    $package = $package->getAliasOf();
                }

                $bumped = $bumper->bumpRequirement($link->getConstraint(), $package);

                if ($bumped === $currentConstraint) {
                    continue;
                }

                $updates[$key][$pkgName] = $bumped;
            }
        }

        if (!$dryRun && !$this->updateFileCleanly($composerJson, $updates)) {
            $composerDefinition = $composerJson->read();
            foreach ($updates as $key => $packages) {
                foreach ($packages as $package => $version) {
                    $composerDefinition[$key][$package] = $version;
                }
            }
            $composerJson->write($composerDefinition);
        }

        $changeCount = array_sum(array_map('count', $updates));
        if ($changeCount > 0) {
            if ($dryRun) {
                $io->write('<info>' . $composerJsonPath . ' would be updated with:</info>');
                foreach ($updates as $requireType => $packages) {
                    foreach ($packages as $package => $version) {
                        $io->write(sprintf('<info> - %s.%s: %s</info>', $requireType, $package, $version));
                    }
                }
            } else {
                $io->write('<info>' . $composerJsonPath . ' has been updated (' . $changeCount . ' changes).</info>');
            }
        } else {
            $io->write('<info>No requirements to update in '.$composerJsonPath.'.</info>');
        }

        if (!$dryRun && $composer->getLocker()->isLocked() && $composer->getConfig()->get('lock') && $changeCount > 0) {
            $composer->getLocker()->updateHash($composerJson);
        }

        if ($dryRun && $changeCount > 0) {
            return self::ERROR_GENERIC;
        }

        return 0;
    }

    /**
     * @param array<'require'|'require-dev', array<string, string>> $updates
     */
    private function updateFileCleanly(JsonFile $json, array $updates): bool
    {
        $contents = file_get_contents($json->getPath());
        if (false === $contents) {
            throw new \RuntimeException('Unable to read '.$json->getPath().' contents.');
        }

        $manipulator = new JsonManipulator($contents);

        foreach ($updates as $key => $packages) {
            foreach ($packages as $package => $version) {
                if (!$manipulator->addLink($key, $package, $version)) {
                    return false;
                }
            }
        }

        if (false === file_put_contents($json->getPath(), $manipulator->getContents())) {
            throw new \RuntimeException('Unable to write new '.$json->getPath().' contents.');
        }

        return true;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Config\JsonConfigSource;
use Composer\DependencyResolver\Request;
use Composer\Installer;
use Composer\Pcre\Preg;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Json\JsonFile;
use Composer\Factory;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Composer\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Package\BasePackage;
use Composer\Advisory\Auditor;

/**
 * @author Pierre du Plessis <pdples@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class RemoveCommand extends BaseCommand
{
    use CompletionTrait;

    protected function configure(): void
    {
        $this
            ->setName('remove')
            ->setAliases(['rm', 'uninstall'])
            ->setDescription('Removes a package from the require or require-dev')
            ->setDefinition([
                new InputArgument('packages', InputArgument::IS_ARRAY, 'Packages that should be removed.', null, $this->suggestRootRequirement()),
                new InputOption('dev', null, InputOption::VALUE_NONE, 'Removes a package from the require-dev section.'),
                new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'),
                new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies (implies --no-install).'),
                new InputOption('no-install', null, InputOption::VALUE_NONE, 'Skip the install step after updating the composer.lock file.'),
                new InputOption('no-audit', null, InputOption::VALUE_NONE, 'Skip the audit step after updating the composer.lock file (can also be set via the COMPOSER_NO_AUDIT=1 env var).'),
                new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS),
                new InputOption('no-security-blocking', null, InputOption::VALUE_NONE, 'Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var).'),
                new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'),
                new InputOption('update-with-dependencies', 'w', InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated with explicit dependencies (can also be set via the COMPOSER_WITH_DEPENDENCIES=1 env var). (Deprecated, is now default behavior)'),
                new InputOption('update-with-all-dependencies', 'W', InputOption::VALUE_NONE, 'Allows all inherited dependencies to be updated, including those that are root requirements (can also be set via the COMPOSER_WITH_ALL_DEPENDENCIES=1 env var).'),
                new InputOption('with-all-dependencies', null, InputOption::VALUE_NONE, 'Alias for --update-with-all-dependencies'),
                new InputOption('no-update-with-dependencies', null, InputOption::VALUE_NONE, 'Does not allow inherited dependencies to be updated with explicit dependencies.'),
                new InputOption('minimal-changes', 'm', InputOption::VALUE_NONE, 'During an update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).'),
                new InputOption('unused', null, InputOption::VALUE_NONE, 'Remove all packages which are locked but not required by any other package.'),
                new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'),
                new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'),
                new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'),
                new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'),
                new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'),
                new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'),
            ])
            ->setHelp(
                <<<EOT
The <info>remove</info> command removes a package from the current
list of installed packages

<info>php composer.phar remove</info>

Read more at https://getcomposer.org/doc/03-cli.md#remove-rm
EOT
            )
        ;
    }

    /**
     * @throws \Seld\JsonLint\ParsingException
     */
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        if ($input->getArgument('packages') === [] && !$input->getOption('unused')) {
            throw new InvalidArgumentException('Not enough arguments (missing: "packages").');
        }

        $packages = $input->getArgument('packages');
        $packages = array_map('strtolower', $packages);

        if ($input->getOption('unused')) {
            $composer = $this->requireComposer();
            $locker = $composer->getLocker();
            if (!$locker->isLocked()) {
                throw new \UnexpectedValueException('A valid composer.lock file is required to run this command with --unused');
            }

            $lockedPackages = $locker->getLockedRepository()->getPackages();

            $required = [];
            foreach (array_merge($composer->getPackage()->getRequires(), $composer->getPackage()->getDevRequires()) as $link) {
                $required[$link->getTarget()] = true;
            }

            do {
                $found = false;
                foreach ($lockedPackages as $index => $package) {
                    foreach ($package->getNames() as $name) {
                        if (isset($required[$name])) {
                            foreach ($package->getRequires() as $link) {
                                $required[$link->getTarget()] = true;
                            }
                            $found = true;
                            unset($lockedPackages[$index]);
                            break;
                        }
                    }
                }
            } while ($found);

            $unused = [];
            foreach ($lockedPackages as $package) {
                $unused[] = $package->getName();
            }
            $packages = array_merge($packages, $unused);

            if (count($packages) === 0) {
                $this->getIO()->writeError('<info>No unused packages to remove</info>');

                return 0;
            }
        }

        $file = Factory::getComposerFile();

        $jsonFile = new JsonFile($file);
        /** @var array{require?: array<string, string>, require-dev?: array<string, string>} $composer */
        $composer = $jsonFile->read();
        $composerBackup = file_get_contents($jsonFile->getPath());

        $json = new JsonConfigSource($jsonFile);

        $type = $input->getOption('dev') ? 'require-dev' : 'require';
        $altType = !$input->getOption('dev') ? 'require-dev' : 'require';
        $io = $this->getIO();

        if ($input->getOption('update-with-dependencies')) {
            $io->writeError('<warning>You are using the deprecated option "update-with-dependencies". This is now default behaviour. The --no-update-with-dependencies option can be used to remove a package without its dependencies.</warning>');
        }

        // make sure name checks are done case insensitively
        foreach (['require', 'require-dev'] as $linkType) {
            if (isset($composer[$linkType])) {
                foreach ($composer[$linkType] as $name => $version) {
                    $composer[$linkType][strtolower($name)] = $name;
                }
            }
        }

        $dryRun = $input->getOption('dry-run');
        $toRemove = [];
        foreach ($packages as $package) {
            if (isset($composer[$type][$package])) {
                if ($dryRun) {
                    $toRemove[$type][] = $composer[$type][$package];
                } else {
                    $json->removeLink($type, $composer[$type][$package]);
                }
            } elseif (isset($composer[$altType][$package])) {
                $io->writeError('<warning>' . $composer[$altType][$package] . ' could not be found in ' . $type . ' but it is present in ' . $altType . '</warning>');
                if ($io->isInteractive()) {
                    if ($io->askConfirmation('Do you want to remove it from ' . $altType . ' [<comment>yes</comment>]? ')) {
                        if ($dryRun) {
                            $toRemove[$altType][] = $composer[$altType][$package];
                        } else {
                            $json->removeLink($altType, $composer[$altType][$package]);
                        }
                    }
                }
            } elseif (isset($composer[$type]) && count($matches = Preg::grep(BasePackage::packageNameToRegexp($package), array_keys($composer[$type]))) > 0) {
                foreach ($matches as $matchedPackage) {
                    if ($dryRun) {
                        $toRemove[$type][] = $matchedPackage;
                    } else {
                        $json->removeLink($type, $matchedPackage);
                    }
                }
            } elseif (isset($composer[$altType]) && count($matches = Preg::grep(BasePackage::packageNameToRegexp($package), array_keys($composer[$altType]))) > 0) {
                foreach ($matches as $matchedPackage) {
                    $io->writeError('<warning>' . $matchedPackage . ' could not be found in ' . $type . ' but it is present in ' . $altType . '</warning>');
                    if ($io->isInteractive()) {
                        if ($io->askConfirmation('Do you want to remove it from ' . $altType . ' [<comment>yes</comment>]? ')) {
                            if ($dryRun) {
                                $toRemove[$altType][] = $matchedPackage;
                            } else {
                                $json->removeLink($altType, $matchedPackage);
                            }
                        }
                    }
                }
            } else {
                $io->writeError('<warning>'.$package.' is not required in your composer.json and has not been removed</warning>');
            }
        }

        $io->writeError('<info>'.$file.' has been updated</info>');

        if ($input->getOption('no-update')) {
            return 0;
        }

        if ($composer = $this->tryComposer()) {
            $composer->getPluginManager()->deactivateInstalledPlugins();
        }

        // Update packages
        $this->resetComposer();
        $composer = $this->requireComposer();

        if ($dryRun) {
            $rootPackage = $composer->getPackage();
            $links = [
                'require' => $rootPackage->getRequires(),
                'require-dev' => $rootPackage->getDevRequires(),
            ];
            foreach ($toRemove as $type => $names) {
                foreach ($names as $name) {
                    unset($links[$type][$name]);
                }
            }
            $rootPackage->setRequires($links['require']);
            $rootPackage->setDevRequires($links['require-dev']);
        }

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'remove', $input, $output);
        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        $allowPlugins = $composer->getConfig()->get('allow-plugins');
        $removedPlugins = is_array($allowPlugins) ? array_intersect(array_keys($allowPlugins), $packages) : [];
        if (!$dryRun && is_array($allowPlugins) && count($removedPlugins) > 0) {
            if (count($allowPlugins) === count($removedPlugins)) {
                $json->removeConfigSetting('allow-plugins');
            } else {
                foreach ($removedPlugins as $plugin) {
                    $json->removeConfigSetting('allow-plugins.'.$plugin);
                }
            }
        }

        $composer->getInstallationManager()->setOutputProgress(!$input->getOption('no-progress'));

        $install = Installer::create($io, $composer);

        $updateDevMode = !$input->getOption('update-no-dev');
        $optimize = $input->getOption('optimize-autoloader') || $composer->getConfig()->get('optimize-autoloader');
        $authoritative = $input->getOption('classmap-authoritative') || $composer->getConfig()->get('classmap-authoritative');
        $apcuPrefix = $input->getOption('apcu-autoloader-prefix');
        $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $composer->getConfig()->get('apcu-autoloader');
        $minimalChanges = $input->getOption('minimal-changes') || $composer->getConfig()->get('update-with-minimal-changes');

        $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE;
        $flags = '';
        if ($input->getOption('update-with-all-dependencies') || $input->getOption('with-all-dependencies')) {
            $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS;
            $flags .= ' --with-all-dependencies';
        } elseif ($input->getOption('no-update-with-dependencies')) {
            $updateAllowTransitiveDependencies = Request::UPDATE_ONLY_LISTED;
            $flags .= ' --with-dependencies';
        }

        $io->writeError('<info>Running composer update '.implode(' ', $packages).$flags.'</info>');

        $install
            ->setVerbose($input->getOption('verbose'))
            ->setDevMode($updateDevMode)
            ->setOptimizeAutoloader($optimize)
            ->setClassMapAuthoritative($authoritative)
            ->setApcuAutoloader($apcu, $apcuPrefix)
            ->setUpdate(true)
            ->setInstall(!$input->getOption('no-install'))
            ->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies)
            ->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input))
            ->setDryRun($dryRun)
            ->setAuditConfig($this->createAuditConfig($composer->getConfig(), $input))
            ->setMinimalUpdate($minimalChanges)
        ;

        // if no lock is present, we do not do a partial update as
        // this is not supported by the Installer
        if ($composer->getLocker()->isLocked()) {
            $install->setUpdateAllowList($packages);
        }

        $status = $install->run();
        if ($status !== 0) {
            $io->writeError("\n".'<error>Removal failed, reverting '.$file.' to its original content.</error>');
            file_put_contents($jsonFile->getPath(), $composerBackup);
        }

        if (!$dryRun) {
            foreach ($packages as $package) {
                if ($composer->getRepositoryManager()->getLocalRepository()->findPackages($package)) {
                    $io->writeError('<error>Removal failed, '.$package.' is still present, it may be required by another package. See `composer why '.$package.'`.</error>');

                    return 2;
                }
            }
        }

        return $status;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\Config;
use Composer\Composer;
use Composer\Package\BasePackage;
use Composer\Package\CompletePackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\Package\Version\VersionSelector;
use Composer\Pcre\Preg;
use Composer\Repository\CompositeRepository;
use Composer\Repository\RepositoryFactory;
use Composer\Repository\RepositorySet;
use Composer\Script\ScriptEvents;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Util\Filesystem;
use Composer\Util\Loop;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Creates an archive of a package for distribution.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class ArchiveCommand extends BaseCommand
{
    use CompletionTrait;

    private const FORMATS = ['tar', 'tar.gz', 'tar.bz2', 'zip'];

    protected function configure(): void
    {
        $this
            ->setName('archive')
            ->setDescription('Creates an archive of this composer package')
            ->setDefinition([
                new InputArgument('package', InputArgument::OPTIONAL, 'The package to archive instead of the current project', null, $this->suggestAvailablePackage()),
                new InputArgument('version', InputArgument::OPTIONAL, 'A version constraint to find the package to archive'),
                new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the resulting archive: tar, tar.gz, tar.bz2 or zip (default tar)', null, self::FORMATS),
                new InputOption('dir', null, InputOption::VALUE_REQUIRED, 'Write the archive to this directory'),
                new InputOption('file', null, InputOption::VALUE_REQUIRED, 'Write the archive with the given file name.'
                    .' Note that the format will be appended.'),
                new InputOption('ignore-filters', null, InputOption::VALUE_NONE, 'Ignore filters when saving package'),
            ])
            ->setHelp(
                <<<EOT
The <info>archive</info> command creates an archive of the specified format
containing the files and directories of the Composer project or the specified
package in the specified version and writes it to the specified directory.

<info>php composer.phar archive [--format=zip] [--dir=/foo] [--file=filename] [package [version]]</info>

Read more at https://getcomposer.org/doc/03-cli.md#archive
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $composer = $this->tryComposer();
        $config = null;

        if ($composer) {
            $config = $composer->getConfig();
            $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'archive', $input, $output);
            $eventDispatcher = $composer->getEventDispatcher();
            $eventDispatcher->dispatch($commandEvent->getName(), $commandEvent);
            $eventDispatcher->dispatchScript(ScriptEvents::PRE_ARCHIVE_CMD);
        }

        if (!$config) {
            $config = Factory::createConfig();
        }

        $format = $input->getOption('format') ?? $config->get('archive-format');
        $dir = $input->getOption('dir') ?? $config->get('archive-dir');

        $returnCode = $this->archive(
            $this->getIO(),
            $config,
            $input->getArgument('package'),
            $input->getArgument('version'),
            $format,
            $dir,
            $input->getOption('file'),
            $input->getOption('ignore-filters'),
            $composer
        );

        if (0 === $returnCode && $composer) {
            $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_ARCHIVE_CMD);
        }

        return $returnCode;
    }

    /**
     * @throws \Exception
     */
    protected function archive(IOInterface $io, Config $config, ?string $packageName, ?string $version, string $format, string $dest, ?string $fileName, bool $ignoreFilters, ?Composer $composer): int
    {
        if ($composer) {
            $archiveManager = $composer->getArchiveManager();
        } else {
            $factory = new Factory;
            $process = new ProcessExecutor();
            $httpDownloader = Factory::createHttpDownloader($io, $config);
            $downloadManager = $factory->createDownloadManager($io, $config, $httpDownloader, $process);
            $archiveManager = $factory->createArchiveManager($config, $downloadManager, new Loop($httpDownloader, $process));
        }

        if ($packageName) {
            $package = $this->selectPackage($io, $packageName, $version);

            if (!$package) {
                return 1;
            }
        } else {
            $package = $this->requireComposer()->getPackage();
        }

        $io->writeError('<info>Creating the archive into "'.$dest.'".</info>');
        $packagePath = $archiveManager->archive($package, $format, $dest, $fileName, $ignoreFilters);
        $fs = new Filesystem;
        $shortPath = $fs->findShortestPath(Platform::getCwd(), $packagePath, true);

        $io->writeError('Created: ', false);
        $io->write(strlen($shortPath) < strlen($packagePath) ? $shortPath : $packagePath);

        return 0;
    }

    /**
     * @return (BasePackage&CompletePackageInterface)|false
     */
    protected function selectPackage(IOInterface $io, string $packageName, ?string $version = null)
    {
        $io->writeError('<info>Searching for the specified package.</info>');

        if ($composer = $this->tryComposer()) {
            $localRepo = $composer->getRepositoryManager()->getLocalRepository();
            $repo = new CompositeRepository(array_merge([$localRepo], $composer->getRepositoryManager()->getRepositories()));
            $minStability = $composer->getPackage()->getMinimumStability();
        } else {
            $defaultRepos = RepositoryFactory::defaultReposWithDefaultManager($io);
            $io->writeError('No composer.json found in the current directory, searching packages from ' . implode(', ', array_keys($defaultRepos)));
            $repo = new CompositeRepository($defaultRepos);
            $minStability = 'stable';
        }

        if ($version !== null && Preg::isMatchStrictGroups('{@(stable|RC|beta|alpha|dev)$}i', $version, $match)) {
            $minStability = VersionParser::normalizeStability($match[1]);
            $version = (string) substr($version, 0, -strlen($match[0]));
        }

        $repoSet = new RepositorySet($minStability);
        $repoSet->addRepository($repo);
        $parser = new VersionParser();
        $constraint = $version !== null ? $parser->parseConstraints($version) : null;
        $packages = $repoSet->findPackages(strtolower($packageName), $constraint);

        if (count($packages) > 1) {
            $versionSelector = new VersionSelector($repoSet);
            $package = $versionSelector->findBestCandidate(strtolower($packageName), $version, $minStability);
            if ($package === false) {
                $package = reset($packages);
            }

            $io->writeError('<info>Found multiple matches, selected '.$package->getPrettyString().'.</info>');
            $io->writeError('Alternatives were '.implode(', ', array_map(static function ($p): string {
                return $p->getPrettyString();
            }, $packages)).'.');
            $io->writeError('<comment>Please use a more specific constraint to pick a different package.</comment>');
        } elseif (count($packages) === 1) {
            $package = reset($packages);
            $io->writeError('<info>Found an exact match '.$package->getPrettyString().'.</info>');
        } else {
            $io->writeError('<error>Could not find a package matching '.$packageName.'.</error>');

            return false;
        }

        if (!$package instanceof CompletePackageInterface) {
            throw new \LogicException('Expected a CompletePackageInterface instance but found '.get_class($package));
        }
        if (!$package instanceof BasePackage) {
            throw new \LogicException('Expected a BasePackage instance but found '.get_class($package));
        }

        return $package;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Composer;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class AboutCommand extends BaseCommand
{
    protected function configure(): void
    {
        $this
            ->setName('about')
            ->setDescription('Shows a short information about Composer')
            ->setHelp(
                <<<EOT
<info>php composer.phar about</info>
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $composerVersion = Composer::getVersion();

        $this->getIO()->write(
            <<<EOT
<info>Composer - Dependency Manager for PHP - version $composerVersion</info>
<comment>Composer is a dependency manager tracking local dependencies of your projects and libraries.
See https://getcomposer.org/ for more information.</comment>
EOT
        );

        return 0;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Downloader\ChangeReportInterface;
use Composer\Downloader\DvcsDownloaderInterface;
use Composer\Downloader\VcsCapableDownloaderInterface;
use Composer\Package\Dumper\ArrayDumper;
use Composer\Package\Version\VersionGuesser;
use Composer\Package\Version\VersionParser;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Script\ScriptEvents;
use Composer\Util\ProcessExecutor;

/**
 * @author Tiago Ribeiro <tiago.ribeiro@seegno.com>
 * @author Rui Marinho <rui.marinho@seegno.com>
 */
class StatusCommand extends BaseCommand
{
    private const EXIT_CODE_ERRORS = 1;
    private const EXIT_CODE_UNPUSHED_CHANGES = 2;
    private const EXIT_CODE_VERSION_CHANGES = 4;

    /**
     * @throws \Symfony\Component\Console\Exception\InvalidArgumentException
     */
    protected function configure(): void
    {
        $this
            ->setName('status')
            ->setDescription('Shows a list of locally modified packages')
            ->setDefinition([
                new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Show modified files for each directory that contains changes.'),
            ])
            ->setHelp(
                <<<EOT
The status command displays a list of dependencies that have
been modified locally.

Read more at https://getcomposer.org/doc/03-cli.md#status
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $composer = $this->requireComposer();

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'status', $input, $output);
        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        // Dispatch pre-status-command
        $composer->getEventDispatcher()->dispatchScript(ScriptEvents::PRE_STATUS_CMD, true);

        $exitCode = $this->doExecute($input);

        // Dispatch post-status-command
        $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_STATUS_CMD, true);

        return $exitCode;
    }

    private function doExecute(InputInterface $input): int
    {
        // init repos
        $composer = $this->requireComposer();

        $installedRepo = $composer->getRepositoryManager()->getLocalRepository();

        $dm = $composer->getDownloadManager();
        $im = $composer->getInstallationManager();

        $errors = [];
        $io = $this->getIO();
        $unpushedChanges = [];
        $vcsVersionChanges = [];

        $parser = new VersionParser;
        $guesser = new VersionGuesser($composer->getConfig(), $composer->getLoop()->getProcessExecutor() ?? new ProcessExecutor($io), $parser, $io);
        $dumper = new ArrayDumper;

        // list packages
        foreach ($installedRepo->getCanonicalPackages() as $package) {
            $downloader = $dm->getDownloaderForPackage($package);
            $targetDir = $im->getInstallPath($package);
            if ($targetDir === null) {
                continue;
            }

            if ($downloader instanceof ChangeReportInterface) {
                if (is_link($targetDir)) {
                    $errors[$targetDir] = $targetDir . ' is a symbolic link.';
                }

                if (null !== ($changes = $downloader->getLocalChanges($package, $targetDir))) {
                    $errors[$targetDir] = $changes;
                }
            }

            if ($downloader instanceof VcsCapableDownloaderInterface) {
                if ($downloader->getVcsReference($package, $targetDir)) {
                    switch ($package->getInstallationSource()) {
                        case 'source':
                            $previousRef = $package->getSourceReference();
                            break;
                        case 'dist':
                            $previousRef = $package->getDistReference();
                            break;
                        default:
                            $previousRef = null;
                    }

                    $currentVersion = $guesser->guessVersion($dumper->dump($package), $targetDir);

                    if ($previousRef && $currentVersion && $currentVersion['commit'] !== $previousRef && $currentVersion['pretty_version'] !== $previousRef) {
                        $vcsVersionChanges[$targetDir] = [
                            'previous' => [
                                'version' => $package->getPrettyVersion(),
                                'ref' => $previousRef,
                            ],
                            'current' => [
                                'version' => $currentVersion['pretty_version'],
                                'ref' => $currentVersion['commit'],
                            ],
                        ];
                    }
                }
            }

            if ($downloader instanceof DvcsDownloaderInterface) {
                if ($unpushed = $downloader->getUnpushedChanges($package, $targetDir)) {
                    $unpushedChanges[$targetDir] = $unpushed;
                }
            }
        }

        // output errors/warnings
        if (!$errors && !$unpushedChanges && !$vcsVersionChanges) {
            $io->writeError('<info>No local changes</info>');

            return 0;
        }

        if ($errors) {
            $io->writeError('<error>You have changes in the following dependencies:</error>');

            foreach ($errors as $path => $changes) {
                if ($input->getOption('verbose')) {
                    $indentedChanges = implode("\n", array_map(static function ($line): string {
                        return '    ' . ltrim($line);
                    }, explode("\n", $changes)));
                    $io->write('<info>'.$path.'</info>:');
                    $io->write($indentedChanges);
                } else {
                    $io->write($path);
                }
            }
        }

        if ($unpushedChanges) {
            $io->writeError('<warning>You have unpushed changes on the current branch in the following dependencies:</warning>');

            foreach ($unpushedChanges as $path => $changes) {
                if ($input->getOption('verbose')) {
                    $indentedChanges = implode("\n", array_map(static function ($line): string {
                        return '    ' . ltrim($line);
                    }, explode("\n", $changes)));
                    $io->write('<info>'.$path.'</info>:');
                    $io->write($indentedChanges);
                } else {
                    $io->write($path);
                }
            }
        }

        if ($vcsVersionChanges) {
            $io->writeError('<warning>You have version variations in the following dependencies:</warning>');

            foreach ($vcsVersionChanges as $path => $changes) {
                if ($input->getOption('verbose')) {
                    // If we don't can't find a version, use the ref instead.
                    $currentVersion = $changes['current']['version'] ?: $changes['current']['ref'];
                    $previousVersion = $changes['previous']['version'] ?: $changes['previous']['ref'];

                    if ($io->isVeryVerbose()) {
                        // Output the ref regardless of whether or not it's being used as the version
                        $currentVersion .= sprintf(' (%s)', $changes['current']['ref']);
                        $previousVersion .= sprintf(' (%s)', $changes['previous']['ref']);
                    }

                    $io->write('<info>'.$path.'</info>:');
                    $io->write(sprintf('    From <comment>%s</comment> to <comment>%s</comment>', $previousVersion, $currentVersion));
                } else {
                    $io->write($path);
                }
            }
        }

        if (($errors || $unpushedChanges || $vcsVersionChanges) && !$input->getOption('verbose')) {
            $io->writeError('Use --verbose (-v) to see a list of files');
        }

        return ($errors ? self::EXIT_CODE_ERRORS : 0) + ($unpushedChanges ? self::EXIT_CODE_UNPUSHED_CHANGES : 0) + ($vcsVersionChanges ? self::EXIT_CODE_VERSION_CHANGES : 0);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\UninstallOperation;
use Composer\DependencyResolver\Transaction;
use Composer\Package\AliasPackage;
use Composer\Package\BasePackage;
use Composer\Pcre\Preg;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Script\ScriptEvents;
use Composer\Util\Platform;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Composer\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ReinstallCommand extends BaseCommand
{
    use CompletionTrait;

    protected function configure(): void
    {
        $this
            ->setName('reinstall')
            ->setDescription('Uninstalls and reinstalls the given package names')
            ->setDefinition([
                new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
                new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'),
                new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()),
                new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'),
                new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'),
                new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'),
                new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'),
                new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'),
                new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'),
                new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'),
                new InputOption('type', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Filter packages to reinstall by type(s)', null, $this->suggestInstalledPackageTypes(false)),
                new InputArgument('packages', InputArgument::IS_ARRAY, 'List of package names to reinstall, can include a wildcard (*) to match any substring.', null, $this->suggestInstalledPackage(false)),
            ])
            ->setHelp(
                <<<EOT
The <info>reinstall</info> command looks up installed packages by name,
uninstalls them and reinstalls them. This lets you do a clean install
of a package if you messed with its files, or if you wish to change
the installation type using --prefer-install.

<info>php composer.phar reinstall acme/foo "acme/bar-*"</info>

Read more at https://getcomposer.org/doc/03-cli.md#reinstall
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = $this->getIO();

        $composer = $this->requireComposer();

        $localRepo = $composer->getRepositoryManager()->getLocalRepository();
        $packagesToReinstall = [];
        $packageNamesToReinstall = [];
        if (\count($input->getOption('type')) > 0) {
            if (\count($input->getArgument('packages')) > 0) {
                throw new \InvalidArgumentException('You cannot specify package names and filter by type at the same time.');
            }
            foreach ($localRepo->getCanonicalPackages() as $package) {
                if (in_array($package->getType(), $input->getOption('type'), true)) {
                    $packagesToReinstall[] = $package;
                    $packageNamesToReinstall[] = $package->getName();
                }
            }
        } else {
            if (\count($input->getArgument('packages')) === 0) {
                throw new \InvalidArgumentException('You must pass one or more package names to be reinstalled.');
            }
            foreach ($input->getArgument('packages') as $pattern) {
                $patternRegexp = BasePackage::packageNameToRegexp($pattern);
                $matched = false;
                foreach ($localRepo->getCanonicalPackages() as $package) {
                    if (Preg::isMatch($patternRegexp, $package->getName())) {
                        $matched = true;
                        $packagesToReinstall[] = $package;
                        $packageNamesToReinstall[] = $package->getName();
                    }
                }

                if (!$matched) {
                    $io->writeError('<warning>Pattern "' . $pattern . '" does not match any currently installed packages.</warning>');
                }
            }
        }

        if (0 === \count($packagesToReinstall)) {
            $io->writeError('<warning>Found no packages to reinstall, aborting.</warning>');

            return 1;
        }

        $uninstallOperations = [];
        foreach ($packagesToReinstall as $package) {
            $uninstallOperations[] = new UninstallOperation($package);
        }

        // make sure we have a list of install operations ordered by dependency/plugins
        $presentPackages = $localRepo->getPackages();
        $resultPackages = $presentPackages;
        foreach ($presentPackages as $index => $package) {
            if (in_array($package->getName(), $packageNamesToReinstall, true)) {
                unset($presentPackages[$index]);
            }
        }
        $transaction = new Transaction($presentPackages, $resultPackages);
        $installOperations = $transaction->getOperations();

        // reverse-sort the uninstalls based on the install order
        $installOrder = [];
        foreach ($installOperations as $index => $op) {
            if ($op instanceof InstallOperation && !$op->getPackage() instanceof AliasPackage) {
                $installOrder[$op->getPackage()->getName()] = $index;
            }
        }
        usort($uninstallOperations, static function ($a, $b) use ($installOrder): int {
            return $installOrder[$b->getPackage()->getName()] - $installOrder[$a->getPackage()->getName()];
        });

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'reinstall', $input, $output);
        $eventDispatcher = $composer->getEventDispatcher();
        $eventDispatcher->dispatch($commandEvent->getName(), $commandEvent);

        $config = $composer->getConfig();
        [$preferSource, $preferDist] = $this->getPreferredInstallOptions($config, $input);

        $installationManager = $composer->getInstallationManager();
        $downloadManager = $composer->getDownloadManager();
        $package = $composer->getPackage();

        $installationManager->setOutputProgress(!$input->getOption('no-progress'));
        if ($input->getOption('no-plugins')) {
            $installationManager->disablePlugins();
        }

        $downloadManager->setPreferSource($preferSource);
        $downloadManager->setPreferDist($preferDist);

        $devMode = $localRepo->getDevMode() !== null ? $localRepo->getDevMode() : true;

        Platform::putEnv('COMPOSER_DEV_MODE', $devMode ? '1' : '0');
        $eventDispatcher->dispatchScript(ScriptEvents::PRE_INSTALL_CMD, $devMode);

        $installationManager->execute($localRepo, $uninstallOperations, $devMode);
        $installationManager->execute($localRepo, $installOperations, $devMode);

        if (!$input->getOption('no-autoloader')) {
            $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader');
            $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative');
            $apcuPrefix = $input->getOption('apcu-autoloader-prefix');
            $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader');

            $generator = $composer->getAutoloadGenerator();
            $generator->setClassMapAuthoritative($authoritative);
            $generator->setApcu($apcu, $apcuPrefix);
            $generator->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input));
            $generator->dump(
                $config,
                $localRepo,
                $package,
                $installationManager,
                'composer',
                $optimize,
                null,
                $composer->getLocker()
            );
        }

        $eventDispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, $devMode);

        return 0;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Pcre\Preg;
use Composer\Util\Platform;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Composer\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ScriptAliasCommand extends BaseCommand
{
    /** @var string */
    private $script;
    /** @var string */
    private $description;
    /** @var string[] */
    private $aliases;

    /**
     * @param string[] $aliases
     */
    public function __construct(string $script, ?string $description, array $aliases = [])
    {
        $this->script = $script;
        $this->description = $description ?? 'Runs the '.$script.' script as defined in composer.json';
        $this->aliases = $aliases;

        foreach ($this->aliases as $alias) {
            if (!is_string($alias)) {
                throw new \InvalidArgumentException('"scripts-aliases" element array values should contain only strings');
            }
        }

        $this->ignoreValidationErrors();

        parent::__construct();
    }

    protected function configure(): void
    {
        $this
            ->setName($this->script)
            ->setDescription($this->description)
            ->setAliases($this->aliases)
            ->setDefinition([
                new InputOption('dev', null, InputOption::VALUE_NONE, 'Sets the dev mode.'),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables the dev mode.'),
                new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''),
            ])
            ->setHelp(
                <<<EOT
The <info>run-script</info> command runs scripts defined in composer.json:

<info>php composer.phar run-script post-update-cmd</info>

Read more at https://getcomposer.org/doc/03-cli.md#run-script-run
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $composer = $this->requireComposer();

        $args = $input->getArguments();

        // TODO remove for Symfony 6+ as it is then in the interface
        if (!method_exists($input, '__toString')) { // @phpstan-ignore-line
            throw new \LogicException('Expected an Input instance that is stringable, got '.get_class($input));
        }

        $devMode = $input->getOption('dev') || !$input->getOption('no-dev');

        Platform::putEnv('COMPOSER_DEV_MODE', $devMode ? '1' : '0');

        return $composer->getEventDispatcher()->dispatchScript($this->script, $devMode, $args['args'], ['script-alias-input' => Preg::replace('{^\S+ ?}', '', $input->__toString(), 1)]);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Composer;
use Composer\DependencyResolver\Request;
use Composer\Installer;
use Composer\IO\IOInterface;
use Composer\Package\BasePackage;
use Composer\Package\Loader\RootPackageLoader;
use Composer\Package\Version\VersionSelector;
use Composer\Pcre\Preg;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Package\Version\VersionParser;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\RepositorySet;
use Composer\Semver\Constraint\MultiConstraint;
use Composer\Semver\Intervals;
use Composer\Util\HttpDownloader;
use Composer\Advisory\Auditor;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Composer\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Nils Adermann <naderman@naderman.de>
 */
class UpdateCommand extends BaseCommand
{
    use CompletionTrait;

    protected function configure(): void
    {
        $this
            ->setName('update')
            ->setAliases(['u', 'upgrade'])
            ->setDescription('Updates your dependencies to the latest version according to composer.json, and updates the composer.lock file')
            ->setDefinition([
                new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Packages that should be updated, if not provided all packages are.', null, $this->suggestInstalledPackage(false)),
                new InputOption('with', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Temporary version constraint to add, e.g. foo/bar:1.0.0 or foo/bar=1.0.0'),
                new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
                new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'),
                new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()),
                new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'),
                new InputOption('dev', null, InputOption::VALUE_NONE, 'DEPRECATED: Enables installation of require-dev packages (enabled by default, only present for BC).'),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'),
                new InputOption('lock', null, InputOption::VALUE_NONE, 'Overwrites the lock file hash to suppress warning about the lock file being out of date without updating package versions. Package metadata like mirrors and URLs are updated if they changed.'),
                new InputOption('no-install', null, InputOption::VALUE_NONE, 'Skip the install step after updating the composer.lock file.'),
                new InputOption('no-audit', null, InputOption::VALUE_NONE, 'Skip the audit step after updating the composer.lock file (can also be set via the COMPOSER_NO_AUDIT=1 env var).'),
                new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS),
                new InputOption('no-security-blocking', null, InputOption::VALUE_NONE, 'Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var).'),
                new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'),
                new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'DEPRECATED: This flag does not exist anymore.'),
                new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                new InputOption('with-dependencies', 'w', InputOption::VALUE_NONE, 'Update also dependencies of packages in the argument list, except those which are root requirements (can also be set via the COMPOSER_WITH_DEPENDENCIES=1 env var).'),
                new InputOption('with-all-dependencies', 'W', InputOption::VALUE_NONE, 'Update also dependencies of packages in the argument list, including those which are root requirements (can also be set via the COMPOSER_WITH_ALL_DEPENDENCIES=1 env var).'),
                new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'),
                new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump.'),
                new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'),
                new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'),
                new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'),
                new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'),
                new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'),
                new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies (can also be set via the COMPOSER_PREFER_STABLE=1 env var).'),
                new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies (can also be set via the COMPOSER_PREFER_LOWEST=1 env var).'),
                new InputOption('minimal-changes', 'm', InputOption::VALUE_NONE, 'Only perform absolutely necessary changes to dependencies. If packages cannot be kept at their currently locked version they are updated. For partial updates the allow-listed packages are always updated fully. (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).'),
                new InputOption('patch-only', null, InputOption::VALUE_NONE, 'Only allow patch version updates for currently installed dependencies.'),
                new InputOption('interactive', 'i', InputOption::VALUE_NONE, 'Interactive interface with autocompletion to select the packages to update.'),
                new InputOption('root-reqs', null, InputOption::VALUE_NONE, 'Restricts the update to your first degree dependencies.'),
                new InputOption('bump-after-update', null, InputOption::VALUE_OPTIONAL, 'Runs bump after performing the update.', false, ['dev', 'no-dev', 'all']),
            ])
            ->setHelp(
                <<<EOT
The <info>update</info> command reads the composer.json file from the
current directory, processes it, and updates, removes or installs all the
dependencies.

<info>php composer.phar update</info>

To limit the update operation to a few packages, you can list the package(s)
you want to update as such:

<info>php composer.phar update vendor/package1 foo/mypackage [...]</info>

You may also use an asterisk (*) pattern to limit the update operation to package(s)
from a specific vendor:

<info>php composer.phar update vendor/package1 foo/* [...]</info>

To run an update with more restrictive constraints you can use:

<info>php composer.phar update --with vendor/package:1.0.*</info>

To run a partial update with more restrictive constraints you can use the shorthand:

<info>php composer.phar update vendor/package:1.0.*</info>

To select packages names interactively with auto-completion use <info>-i</info>.

Read more at https://getcomposer.org/doc/03-cli.md#update-u-upgrade
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = $this->getIO();
        if ($input->getOption('dev')) {
            $io->writeError('<warning>You are using the deprecated option "--dev". It has no effect and will break in Composer 3.</warning>');
        }
        if ($input->getOption('no-suggest')) {
            $io->writeError('<warning>You are using the deprecated option "--no-suggest". It has no effect and will break in Composer 3.</warning>');
        }

        $composer = $this->requireComposer();

        if (!HttpDownloader::isCurlEnabled()) {
            $io->writeError('<warning>Composer is operating significantly slower than normal because you do not have the PHP curl extension enabled.</warning>');
        }

        $packages = $input->getArgument('packages');
        $reqs = $this->formatRequirements($input->getOption('with'));

        // extract --with shorthands from the allowlist
        if (count($packages) > 0) {
            $allowlistPackagesWithRequirements = array_filter($packages, static function ($pkg): bool {
                return Preg::isMatch('{\S+[ =:]\S+}', $pkg);
            });
            foreach ($this->formatRequirements($allowlistPackagesWithRequirements) as $package => $constraint) {
                $reqs[$package] = $constraint;
            }

            // replace the foo/bar:req by foo/bar in the allowlist
            foreach ($allowlistPackagesWithRequirements as $package) {
                $packageName = Preg::replace('{^([^ =:]+)[ =:].*$}', '$1', $package);
                $index = array_search($package, $packages);
                $packages[$index] = $packageName;
            }
        }

        $rootPackage = $composer->getPackage();
        $rootPackage->setReferences(RootPackageLoader::extractReferences($reqs, $rootPackage->getReferences()));
        $rootPackage->setStabilityFlags(RootPackageLoader::extractStabilityFlags($reqs, $rootPackage->getMinimumStability(), $rootPackage->getStabilityFlags()));

        $parser = new VersionParser;
        $temporaryConstraints = [];
        $rootRequirements = array_merge($rootPackage->getRequires(), $rootPackage->getDevRequires());
        foreach ($reqs as $package => $constraint) {
            $package = strtolower($package);
            $parsedConstraint = $parser->parseConstraints($constraint);
            $temporaryConstraints[$package] = $parsedConstraint;
            if (isset($rootRequirements[$package]) && !Intervals::haveIntersections($parsedConstraint, $rootRequirements[$package]->getConstraint())) {
                $io->writeError('<error>The temporary constraint "'.$constraint.'" for "'.$package.'" must be a subset of the constraint in your composer.json ('.$rootRequirements[$package]->getPrettyConstraint().')</error>');
                $io->write('<info>Run `composer require '.$package.'` or `composer require '.$package.':'.$constraint.'` instead to replace the constraint</info>');

                return self::FAILURE;
            }
        }

        if ($input->getOption('patch-only')) {
            if (!$composer->getLocker()->isLocked()) {
                throw new \InvalidArgumentException('patch-only can only be used with a lock file present');
            }
            foreach ($composer->getLocker()->getLockedRepository(true)->getCanonicalPackages() as $package) {
                if ($package->isDev()) {
                    continue;
                }
                if (!Preg::isMatch('{^(\d+\.\d+\.\d+)}', $package->getVersion(), $match)) {
                    continue;
                }
                $constraint = $parser->parseConstraints('~'.$match[1]);
                if (isset($temporaryConstraints[$package->getName()])) {
                    $temporaryConstraints[$package->getName()] = MultiConstraint::create([$temporaryConstraints[$package->getName()], $constraint], true);
                } else {
                    $temporaryConstraints[$package->getName()] = $constraint;
                }
            }
        }

        if ($input->getOption('interactive')) {
            $packages = $this->getPackagesInteractively($io, $input, $output, $composer, $packages);
        }

        if ($input->getOption('root-reqs')) {
            $requires = array_keys($rootPackage->getRequires());
            if (!$input->getOption('no-dev')) {
                $requires = array_merge($requires, array_keys($rootPackage->getDevRequires()));
            }

            if (!empty($packages)) {
                $packages = array_intersect($packages, $requires);
            } else {
                $packages = $requires;
            }
        }

        // the arguments lock/nothing/mirrors are not package names but trigger a mirror update instead
        // they are further mutually exclusive with listing actual package names
        $filteredPackages = array_filter($packages, static function ($package): bool {
            return !in_array($package, ['lock', 'nothing', 'mirrors'], true);
        });
        $updateMirrors = $input->getOption('lock') || count($filteredPackages) !== count($packages);
        $packages = $filteredPackages;

        if ($updateMirrors && !empty($packages)) {
            $io->writeError('<error>You cannot simultaneously update only a selection of packages and regenerate the lock file metadata.</error>');

            return -1;
        }

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'update', $input, $output);
        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        $composer->getInstallationManager()->setOutputProgress(!$input->getOption('no-progress'));

        $install = Installer::create($io, $composer);

        $config = $composer->getConfig();
        [$preferSource, $preferDist] = $this->getPreferredInstallOptions($config, $input);

        $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader');
        $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative');
        $apcuPrefix = $input->getOption('apcu-autoloader-prefix');
        $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader');
        $minimalChanges = $input->getOption('minimal-changes') || $config->get('update-with-minimal-changes');

        $updateAllowTransitiveDependencies = Request::UPDATE_ONLY_LISTED;
        if ($input->getOption('with-all-dependencies')) {
            $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS;
        } elseif ($input->getOption('with-dependencies')) {
            $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE;
        }

        $install
            ->setDryRun($input->getOption('dry-run'))
            ->setVerbose($input->getOption('verbose'))
            ->setPreferSource($preferSource)
            ->setPreferDist($preferDist)
            ->setDevMode(!$input->getOption('no-dev'))
            ->setDumpAutoloader(!$input->getOption('no-autoloader'))
            ->setOptimizeAutoloader($optimize)
            ->setClassMapAuthoritative($authoritative)
            ->setApcuAutoloader($apcu, $apcuPrefix)
            ->setUpdate(true)
            ->setInstall(!$input->getOption('no-install'))
            ->setUpdateMirrors($updateMirrors)
            ->setUpdateAllowList($packages)
            ->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies)
            ->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input))
            ->setPreferStable($input->getOption('prefer-stable'))
            ->setPreferLowest($input->getOption('prefer-lowest'))
            ->setTemporaryConstraints($temporaryConstraints)
            ->setAuditConfig($this->createAuditConfig($composer->getConfig(), $input))
            ->setMinimalUpdate($minimalChanges)
        ;

        if ($input->getOption('no-plugins')) {
            $install->disablePlugins();
        }

        $result = $install->run();

        if ($result === 0 && !$input->getOption('lock')) {
            $bumpAfterUpdate = $input->getOption('bump-after-update');
            if (false === $bumpAfterUpdate) {
                $bumpAfterUpdate = $composer->getConfig()->get('bump-after-update');
            }

            if (false !== $bumpAfterUpdate) {
                $io->writeError('<info>Bumping dependencies</info>');
                $bumpCommand = new BumpCommand();
                $bumpCommand->setComposer($composer);
                $result = $bumpCommand->doBump(
                    $io,
                    $bumpAfterUpdate === 'dev',
                    $bumpAfterUpdate === 'no-dev',
                    $input->getOption('dry-run'),
                    $input->getArgument('packages'),
                    '--bump-after-update=dev'
                );
            }
        }

        return $result;
    }

    /**
     * @param array<string> $packages
     * @return array<string>
     */
    private function getPackagesInteractively(IOInterface $io, InputInterface $input, OutputInterface $output, Composer $composer, array $packages): array
    {
        if (!$input->isInteractive()) {
            throw new \InvalidArgumentException('--interactive cannot be used in non-interactive terminals.');
        }

        $platformReqFilter = $this->getPlatformRequirementFilter($input);
        $stabilityFlags = $composer->getPackage()->getStabilityFlags();
        $requires = array_merge(
            $composer->getPackage()->getRequires(),
            $composer->getPackage()->getDevRequires()
        );

        $filter = \count($packages) > 0 ? BasePackage::packageNamesToRegexp($packages) : null;

        $io->writeError('<info>Loading packages that can be updated...</info>');
        $autocompleterValues = [];
        $installedPackages = $composer->getLocker()->isLocked() ? $composer->getLocker()->getLockedRepository(true)->getPackages() : $composer->getRepositoryManager()->getLocalRepository()->getPackages();
        $versionSelector = $this->createVersionSelector($composer);
        foreach ($installedPackages as $package) {
            if ($filter !== null && !Preg::isMatch($filter, $package->getName())) {
                continue;
            }
            $currentVersion = $package->getPrettyVersion();
            $constraint = isset($requires[$package->getName()]) ? $requires[$package->getName()]->getPrettyConstraint() : null;
            $stability = isset($stabilityFlags[$package->getName()]) ? (string) array_search($stabilityFlags[$package->getName()], BasePackage::STABILITIES, true) : $composer->getPackage()->getMinimumStability();
            $latestVersion = $versionSelector->findBestCandidate($package->getName(), $constraint, $stability, $platformReqFilter);
            if ($latestVersion !== false && ($package->getVersion() !== $latestVersion->getVersion() || $latestVersion->isDev())) {
                $autocompleterValues[$package->getName()] = '<comment>' . $currentVersion . '</comment> => <comment>' . $latestVersion->getPrettyVersion() . '</comment>';
            }
        }
        if (0 === \count($installedPackages)) {
            foreach ($requires as $req => $constraint) {
                if (PlatformRepository::isPlatformPackage($req)) {
                    continue;
                }
                $autocompleterValues[$req] = '';
            }
        }

        if (0 === \count($autocompleterValues)) {
            throw new \RuntimeException('Could not find any package with new versions available');
        }

        $packages = $io->select(
            'Select packages: (Select more than one value separated by comma) ',
            $autocompleterValues,
            false,
            1,
            'No package named "%s" is installed.',
            true
        );

        $table = new Table($output);
        $table->setHeaders(['Selected packages']);
        foreach ($packages as $package) {
            $table->addRow([$package]);
        }
        $table->render();

        if ($io->askConfirmation(sprintf(
            'Would you like to continue and update the above package%s [<comment>yes</comment>]? ',
            1 === count($packages) ? '' : 's'
        ))) {
            return $packages;
        }

        throw new \RuntimeException('Installation aborted.');
    }

    private function createVersionSelector(Composer $composer): VersionSelector
    {
        $repositorySet = new RepositorySet();
        $repositorySet->addRepository(new CompositeRepository(array_filter($composer->getRepositoryManager()->getRepositories(), static function (RepositoryInterface $repository) {
            return !$repository instanceof PlatformRepository;
        })));

        return new VersionSelector($repositorySet);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Factory;
use Composer\Pcre\Preg;
use Composer\Util\Filesystem;
use Composer\Util\Platform;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputArgument;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class GlobalCommand extends BaseCommand
{
    public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
    {
        $application = $this->getApplication();
        if ($input->mustSuggestArgumentValuesFor('command-name')) {
            $suggestions->suggestValues(array_values(array_filter(
                array_map(static function (Command $command) {
                    return $command->isHidden() ? null : $command->getName();
                }, $application->all()),
                static function (?string $cmd) {
                    return $cmd !== null;
                }
            )));

            return;
        }

        if ($application->has($commandName = $input->getArgument('command-name'))) {
            $input = $this->prepareSubcommandInput($input, true);
            $input = CompletionInput::fromString($input->__toString(), 2);
            $command = $application->find($commandName);
            $command->mergeApplicationDefinition();

            $input->bind($command->getDefinition());
            $command->complete($input, $suggestions);
        }
    }

    protected function configure(): void
    {
        $this
            ->setName('global')
            ->setDescription('Allows running commands in the global composer dir ($COMPOSER_HOME)')
            ->setDefinition([
                new InputArgument('command-name', InputArgument::REQUIRED, ''),
                new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''),
            ])
            ->setHelp(
                <<<EOT
Use this command as a wrapper to run other Composer commands
within the global context of COMPOSER_HOME.

You can use this to install CLI utilities globally, all you need
is to add the COMPOSER_HOME/vendor/bin dir to your PATH env var.

COMPOSER_HOME is c:\Users\<user>\AppData\Roaming\Composer on Windows
and /home/<user>/.composer on unix systems.

If your system uses freedesktop.org standards, then it will first check
XDG_CONFIG_HOME or default to /home/<user>/.config/composer

Note: This path may vary depending on customizations to bin-dir in
composer.json or the environmental variable COMPOSER_BIN_DIR.

Read more at https://getcomposer.org/doc/03-cli.md#global
EOT
            )
        ;
    }

    /**
     * @throws \Symfony\Component\Console\Exception\ExceptionInterface
     */
    public function run(InputInterface $input, OutputInterface $output): int
    {
        // TODO remove for Symfony 6+ as it is then in the interface
        if (!method_exists($input, '__toString')) { // @phpstan-ignore-line
            throw new \LogicException('Expected an Input instance that is stringable, got '.get_class($input));
        }

        // extract real command name
        $tokens = Preg::split('{\s+}', $input->__toString());
        $args = [];
        foreach ($tokens as $token) {
            if ($token && $token[0] !== '-') {
                $args[] = $token;
                if (count($args) >= 2) {
                    break;
                }
            }
        }

        // show help for this command if no command was found
        if (count($args) < 2) {
            return parent::run($input, $output);
        }

        $input = $this->prepareSubcommandInput($input);

        return $this->getApplication()->run($input, $output);
    }

    private function prepareSubcommandInput(InputInterface $input, bool $quiet = false): StringInput
    {
        // TODO remove for Symfony 6+ as it is then in the interface
        if (!method_exists($input, '__toString')) { // @phpstan-ignore-line
            throw new \LogicException('Expected an Input instance that is stringable, got '.get_class($input));
        }

        // The COMPOSER env var should not apply to the global execution scope
        if (Platform::getEnv('COMPOSER')) {
            Platform::clearEnv('COMPOSER');
        }

        // change to global dir
        $config = Factory::createConfig();
        $home = $config->get('home');

        if (!is_dir($home)) {
            $fs = new Filesystem();
            $fs->ensureDirectoryExists($home);
            if (!is_dir($home)) {
                throw new \RuntimeException('Could not create home directory');
            }
        }

        try {
            chdir($home);
        } catch (\Exception $e) {
            throw new \RuntimeException('Could not switch to home directory "'.$home.'"', 0, $e);
        }
        if (!$quiet) {
            $this->getIO()->writeError('<info>Changed current directory to '.$home.'</info>');
        }

        // create new input without "global" command prefix
        $input = new StringInput(Preg::replace('{\bg(?:l(?:o(?:b(?:a(?:l)?)?)?)?)?\b}', '', $input->__toString(), 1));
        $this->getApplication()->resetComposer();

        return $input;
    }

    /**
     * @inheritDoc
     */
    public function isProxyCommand(): bool
    {
        return true;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Installer;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Advisory\Auditor;
use Composer\Util\HttpDownloader;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Composer\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Ryan Weaver <ryan@knplabs.com>
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Nils Adermann <naderman@naderman.de>
 */
class InstallCommand extends BaseCommand
{
    use CompletionTrait;

    protected function configure(): void
    {
        $this
            ->setName('install')
            ->setAliases(['i'])
            ->setDescription('Installs the project dependencies from the composer.lock file if present, or falls back on the composer.json')
            ->setDefinition([
                new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
                new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'),
                new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()),
                new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'),
                new InputOption('download-only', null, InputOption::VALUE_NONE, 'Download only, do not install packages.'),
                new InputOption('dev', null, InputOption::VALUE_NONE, 'DEPRECATED: Enables installation of require-dev packages (enabled by default, only present for BC).'),
                new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'DEPRECATED: This flag does not exist anymore.'),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'),
                new InputOption('no-security-blocking', null, InputOption::VALUE_NONE, 'Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var). Only applies when no lock file is present.'),
                new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'),
                new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                new InputOption('no-install', null, InputOption::VALUE_NONE, 'Do not use, only defined here to catch misuse of the install command.'),
                new InputOption('audit', null, InputOption::VALUE_NONE, 'Run an audit after installation is complete.'),
                new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS),
                new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'),
                new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'),
                new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'),
                new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'),
                new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'),
                new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'),
                new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'),
                new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Should not be provided, use composer require instead to add a given package to composer.json.'),
            ])
            ->setHelp(
                <<<EOT
The <info>install</info> command reads the composer.lock file from
the current directory, processes it, and downloads and installs all the
libraries and dependencies outlined in that file. If the file does not
exist it will look for composer.json and do the same.

<info>php composer.phar install</info>

Read more at https://getcomposer.org/doc/03-cli.md#install-i
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = $this->getIO();
        if ($input->getOption('dev')) {
            $io->writeError('<warning>You are using the deprecated option "--dev". It has no effect and will break in Composer 3.</warning>');
        }
        if ($input->getOption('no-suggest')) {
            $io->writeError('<warning>You are using the deprecated option "--no-suggest". It has no effect and will break in Composer 3.</warning>');
        }

        $args = $input->getArgument('packages');
        if (count($args) > 0) {
            $io->writeError('<error>Invalid argument '.implode(' ', $args).'. Use "composer require '.implode(' ', $args).'" instead to add packages to your composer.json.</error>');

            return 1;
        }

        if ($input->getOption('no-install')) {
            $io->writeError('<error>Invalid option "--no-install". Use "composer update --no-install" instead if you are trying to update the composer.lock file.</error>');

            return 1;
        }

        $composer = $this->requireComposer();

        if (!$composer->getLocker()->isLocked() && !HttpDownloader::isCurlEnabled()) {
            $io->writeError('<warning>Composer is operating significantly slower than normal because you do not have the PHP curl extension enabled.</warning>');
        }

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'install', $input, $output);
        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        $install = Installer::create($io, $composer);

        $config = $composer->getConfig();
        [$preferSource, $preferDist] = $this->getPreferredInstallOptions($config, $input);

        $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader');
        $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative');
        $apcuPrefix = $input->getOption('apcu-autoloader-prefix');
        $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader');

        $composer->getInstallationManager()->setOutputProgress(!$input->getOption('no-progress'));

        $install
            ->setDryRun($input->getOption('dry-run'))
            ->setDownloadOnly($input->getOption('download-only'))
            ->setVerbose($input->getOption('verbose'))
            ->setPreferSource($preferSource)
            ->setPreferDist($preferDist)
            ->setDevMode(!$input->getOption('no-dev'))
            ->setDumpAutoloader(!$input->getOption('no-autoloader'))
            ->setOptimizeAutoloader($optimize)
            ->setClassMapAuthoritative($authoritative)
            ->setApcuAutoloader($apcu, $apcuPrefix)
            ->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input))
            ->setAuditConfig($this->createAuditConfig($composer->getConfig(), $input))
            ->setErrorOnAudit($input->getOption('audit'))
        ;

        if ($input->getOption('no-plugins')) {
            $install->disablePlugins();
        }

        return $install->run();
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Composer;
use Composer\DependencyResolver\DefaultPolicy;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
use Composer\Package\BasePackage;
use Composer\Package\CompletePackageInterface;
use Composer\Package\Link;
use Composer\Package\AliasPackage;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\Package\Version\VersionSelector;
use Composer\Pcre\Preg;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Repository\InstalledArrayRepository;
use Composer\Repository\ComposerRepository;
use Composer\Repository\CompositeRepository;
use Composer\Repository\FilterRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryFactory;
use Composer\Repository\InstalledRepository;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\RepositorySet;
use Composer\Repository\RepositoryUtils;
use Composer\Repository\RootPackageRepository;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Semver;
use Composer\Spdx\SpdxLicenses;
use Composer\Util\PackageInfo;
use DateTimeInterface;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Composer\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Jérémy Romey <jeremyFreeAgent>
 * @author Mihai Plasoianu <mihai@plasoianu.de>
 *
 * @phpstan-import-type AutoloadRules from PackageInterface
 * @phpstan-type JsonStructure array<string, null|string|array<string|null>|AutoloadRules>
 */
class ShowCommand extends BaseCommand
{
    use CompletionTrait;

    /** @var VersionParser */
    protected $versionParser;
    /** @var string[] */
    protected $colors;

    /** @var ?RepositorySet */
    private $repositorySet;

    protected function configure(): void
    {
        $this
            ->setName('show')
            ->setAliases(['info'])
            ->setDescription('Shows information about packages')
            ->setDefinition([
                new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect. Or a name including a wildcard (*) to filter lists of packages instead.', null, $this->suggestPackageBasedOnMode()),
                new InputArgument('version', InputArgument::OPTIONAL, 'Version or version constraint to inspect'),
                new InputOption('all', null, InputOption::VALUE_NONE, 'List all packages'),
                new InputOption('locked', null, InputOption::VALUE_NONE, 'List all locked packages'),
                new InputOption('installed', 'i', InputOption::VALUE_NONE, 'List installed packages only (enabled by default, only present for BC).'),
                new InputOption('platform', 'p', InputOption::VALUE_NONE, 'List platform packages only'),
                new InputOption('available', 'a', InputOption::VALUE_NONE, 'List available packages only'),
                new InputOption('self', 's', InputOption::VALUE_NONE, 'Show the root package information'),
                new InputOption('name-only', 'N', InputOption::VALUE_NONE, 'List package names only'),
                new InputOption('path', 'P', InputOption::VALUE_NONE, 'Show package paths'),
                new InputOption('tree', 't', InputOption::VALUE_NONE, 'List the dependencies as a tree'),
                new InputOption('latest', 'l', InputOption::VALUE_NONE, 'Show the latest version'),
                new InputOption('outdated', 'o', InputOption::VALUE_NONE, 'Show the latest version but only for packages that are outdated'),
                new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Can contain wildcards (*). Use it with the --outdated option if you don\'t want to be informed about new versions of some packages.', null, $this->suggestInstalledPackage(false)),
                new InputOption('major-only', 'M', InputOption::VALUE_NONE, 'Show only packages that have major SemVer-compatible updates. Use with the --latest or --outdated option.'),
                new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates. Use with the --latest or --outdated option.'),
                new InputOption('patch-only', null, InputOption::VALUE_NONE, 'Show only packages that have patch SemVer-compatible updates. Use with the --latest or --outdated option.'),
                new InputOption('sort-by-age', 'A', InputOption::VALUE_NONE, 'Displays the installed version\'s age, and sorts packages oldest first. Use with the --latest or --outdated option.'),
                new InputOption('direct', 'D', InputOption::VALUE_NONE, 'Shows only packages that are directly required by the root package'),
                new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'),
                new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'),
                new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages). Use with the --outdated option'),
                new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages). Use with the --outdated option'),
            ])
            ->setHelp(
                <<<EOT
The show command displays detailed information about a package, or
lists all packages available.

Read more at https://getcomposer.org/doc/03-cli.md#show-info
EOT
            )
        ;
    }

    protected function suggestPackageBasedOnMode(): \Closure
    {
        return function (CompletionInput $input) {
            if ($input->getOption('available') || $input->getOption('all')) {
                return $this->suggestAvailablePackageInclPlatform()($input);
            }

            if ($input->getOption('platform')) {
                return $this->suggestPlatformPackage()($input);
            }

            return $this->suggestInstalledPackage(false)($input);
        };
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $this->versionParser = new VersionParser;
        if ($input->getOption('tree')) {
            $this->initStyles($output);
        }

        $composer = $this->tryComposer();
        $io = $this->getIO();

        if ($input->getOption('installed') && !$input->getOption('self')) {
            $io->writeError('<warning>You are using the deprecated option "installed". Only installed packages are shown by default now. The --all option can be used to show all packages.</warning>');
        }

        if ($input->getOption('outdated')) {
            $input->setOption('latest', true);
        } elseif (count($input->getOption('ignore')) > 0) {
            $io->writeError('<warning>You are using the option "ignore" for action other than "outdated", it will be ignored.</warning>');
        }

        if ($input->getOption('direct') && ($input->getOption('all') || $input->getOption('available') || $input->getOption('platform'))) {
            $io->writeError('The --direct (-D) option is not usable in combination with --all, --platform (-p) or --available (-a)');

            return 1;
        }

        if ($input->getOption('tree') && ($input->getOption('all') || $input->getOption('available'))) {
            $io->writeError('The --tree (-t) option is not usable in combination with --all or --available (-a)');

            return 1;
        }

        if (count(array_filter([$input->getOption('patch-only'), $input->getOption('minor-only'), $input->getOption('major-only')])) > 1) {
            $io->writeError('Only one of --major-only, --minor-only or --patch-only can be used at once');

            return 1;
        }

        if ($input->getOption('tree') && $input->getOption('latest')) {
            $io->writeError('The --tree (-t) option is not usable in combination with --latest (-l)');

            return 1;
        }

        if ($input->getOption('tree') && $input->getOption('path')) {
            $io->writeError('The --tree (-t) option is not usable in combination with --path (-P)');

            return 1;
        }

        $format = $input->getOption('format');
        if (!in_array($format, ['text', 'json'])) {
            $io->writeError(sprintf('Unsupported format "%s". See help for supported formats.', $format));

            return 1;
        }

        $platformReqFilter = $this->getPlatformRequirementFilter($input);

        // init repos
        $platformOverrides = [];
        if ($composer) {
            $platformOverrides = $composer->getConfig()->get('platform');
        }
        $platformRepo = new PlatformRepository([], $platformOverrides);
        $lockedRepo = null;

        if ($input->getOption('self') && !$input->getOption('installed') && !$input->getOption('locked')) {
            $package = clone $this->requireComposer()->getPackage();
            if ($input->getOption('name-only')) {
                $io->write($package->getName());

                return 0;
            }
            if ($input->getArgument('package')) {
                throw new \InvalidArgumentException('You cannot use --self together with a package name');
            }
            $repos = $installedRepo = new InstalledRepository([new RootPackageRepository($package)]);
        } elseif ($input->getOption('platform')) {
            $repos = $installedRepo = new InstalledRepository([$platformRepo]);
        } elseif ($input->getOption('available')) {
            $installedRepo = new InstalledRepository([$platformRepo]);
            if ($composer) {
                $repos = new CompositeRepository($composer->getRepositoryManager()->getRepositories());
                $installedRepo->addRepository($composer->getRepositoryManager()->getLocalRepository());
            } else {
                $defaultRepos = RepositoryFactory::defaultReposWithDefaultManager($io);
                $repos = new CompositeRepository($defaultRepos);
                $io->writeError('No composer.json found in the current directory, showing available packages from ' . implode(', ', array_keys($defaultRepos)));
            }
        } elseif ($input->getOption('all') && $composer) {
            $localRepo = $composer->getRepositoryManager()->getLocalRepository();
            $locker = $composer->getLocker();
            if ($locker->isLocked()) {
                $lockedRepo = $locker->getLockedRepository(true);
                $installedRepo = new InstalledRepository([$lockedRepo, $localRepo, $platformRepo]);
            } else {
                $installedRepo = new InstalledRepository([$localRepo, $platformRepo]);
            }
            $repos = new CompositeRepository(array_merge([new FilterRepository($installedRepo, ['canonical' => false])], $composer->getRepositoryManager()->getRepositories()));
        } elseif ($input->getOption('all')) {
            $defaultRepos = RepositoryFactory::defaultReposWithDefaultManager($io);
            $io->writeError('No composer.json found in the current directory, showing available packages from ' . implode(', ', array_keys($defaultRepos)));
            $installedRepo = new InstalledRepository([$platformRepo]);
            $repos = new CompositeRepository(array_merge([$installedRepo], $defaultRepos));
        } elseif ($input->getOption('locked')) {
            if (!$composer || !$composer->getLocker()->isLocked()) {
                throw new \UnexpectedValueException('A valid composer.json and composer.lock files is required to run this command with --locked');
            }
            $locker = $composer->getLocker();
            $lockedRepo = $locker->getLockedRepository(!$input->getOption('no-dev'));
            if ($input->getOption('self')) {
                $lockedRepo->addPackage(clone $composer->getPackage());
            }
            $repos = $installedRepo = new InstalledRepository([$lockedRepo]);
        } else {
            // --installed / default case
            if (!$composer) {
                $composer = $this->requireComposer();
            }
            $rootPkg = $composer->getPackage();

            $rootRepo = new InstalledArrayRepository();
            if ($input->getOption('self')) {
                $rootRepo = new RootPackageRepository(clone $rootPkg);
            }
            if ($input->getOption('no-dev')) {
                $packages = RepositoryUtils::filterRequiredPackages($composer->getRepositoryManager()->getLocalRepository()->getPackages(), $rootPkg);
                $repos = $installedRepo = new InstalledRepository([$rootRepo, new InstalledArrayRepository(array_map(static function ($pkg): PackageInterface {
                    return clone $pkg;
                }, $packages))]);
            } else {
                $repos = $installedRepo = new InstalledRepository([$rootRepo, $composer->getRepositoryManager()->getLocalRepository()]);
            }

            if (!$installedRepo->getPackages()) {
                $hasNonPlatformReqs = static function (array $reqs): bool {
                    return (bool) array_filter(array_keys($reqs), static function (string $name) {
                        return !PlatformRepository::isPlatformPackage($name);
                    });
                };

                if ($hasNonPlatformReqs($rootPkg->getRequires()) || $hasNonPlatformReqs($rootPkg->getDevRequires())) {
                    $io->writeError('<warning>No dependencies installed. Try running composer install or update.</warning>');
                }
            }
        }

        if ($composer) {
            $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'show', $input, $output);
            $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
        }

        if ($input->getOption('latest') && null === $composer) {
            $io->writeError('No composer.json found in the current directory, disabling "latest" option');
            $input->setOption('latest', false);
        }

        $packageFilter = $input->getArgument('package');

        // show single package or single version
        if (isset($package)) {
            $versions = [$package->getPrettyVersion() => $package->getVersion()];
        } elseif (null !== $packageFilter && !str_contains($packageFilter, '*')) {
            [$package, $versions] = $this->getPackage($installedRepo, $repos, $packageFilter, $input->getArgument('version'));

            if (isset($package) && $input->getOption('direct')) {
                if (!in_array($package->getName(), $this->getRootRequires(), true)) {
                    throw new \InvalidArgumentException('Package "' . $package->getName() . '" is installed but not a direct dependent of the root package.');
                }
            }

            if (!isset($package)) {
                $options = $input->getOptions();
                $hint = '';
                if ($input->getOption('locked')) {
                    $hint .= ' in lock file';
                }
                if (isset($options['working-dir'])) {
                    $hint .= ' in ' . $options['working-dir'] . '/composer.json';
                }
                if (PlatformRepository::isPlatformPackage($packageFilter) && !$input->getOption('platform')) {
                    $hint .= ', try using --platform (-p) to show platform packages';
                }
                if (!$input->getOption('all') && !$input->getOption('available')) {
                    $hint .= ', try using --available (-a) to show all available packages';
                }

                throw new \InvalidArgumentException('Package "' . $packageFilter . '" not found'.$hint.'.');
            }
        }

        if (isset($package)) {
            assert(isset($versions));

            $exitCode = 0;
            if ($input->getOption('tree')) {
                $arrayTree = $this->generatePackageTree($package, $installedRepo, $repos);

                if ('json' === $format) {
                    $io->write(JsonFile::encode(['installed' => [$arrayTree]]));
                } else {
                    $this->displayPackageTree([$arrayTree]);
                }

                return $exitCode;
            }

            $latestPackage = null;
            if ($input->getOption('latest')) {
                $latestPackage = $this->findLatestPackage($package, $composer, $platformRepo, $input->getOption('major-only'), $input->getOption('minor-only'), $input->getOption('patch-only'), $platformReqFilter);
            }
            if (
                $input->getOption('outdated')
                && $input->getOption('strict')
                && null !== $latestPackage
                && $latestPackage->getFullPrettyVersion() !== $package->getFullPrettyVersion()
                && (!$latestPackage instanceof CompletePackageInterface || !$latestPackage->isAbandoned())
            ) {
                $exitCode = 1;
            }
            if ($input->getOption('path')) {
                $io->write($package->getName(), false);
                $path = $composer->getInstallationManager()->getInstallPath($package);
                if (is_string($path)) {
                    $io->write(' ' . strtok(realpath($path), "\r\n"));
                } else {
                    $io->write(' null');
                }

                return $exitCode;
            }

            if ('json' === $format) {
                $this->printPackageInfoAsJson($package, $versions, $installedRepo, $latestPackage ?: null);
            } else {
                $this->printPackageInfo($package, $versions, $installedRepo, $latestPackage ?: null);
            }

            return $exitCode;
        }

        // show tree view if requested
        if ($input->getOption('tree')) {
            $rootRequires = $this->getRootRequires();
            $packages = $installedRepo->getPackages();
            usort($packages, static function (BasePackage $a, BasePackage $b): int {
                return strcmp((string) $a, (string) $b);
            });
            $arrayTree = [];
            foreach ($packages as $package) {
                if (in_array($package->getName(), $rootRequires, true)) {
                    $arrayTree[] = $this->generatePackageTree($package, $installedRepo, $repos);
                }
            }

            if ('json' === $format) {
                $io->write(JsonFile::encode(['installed' => $arrayTree]));
            } else {
                $this->displayPackageTree($arrayTree);
            }

            return 0;
        }

        // list packages
        /** @var array<string, array<string, string|CompletePackageInterface>> $packages */
        $packages = [];
        $packageFilterRegex = null;
        if (null !== $packageFilter) {
            $packageFilterRegex = '{^'.str_replace('\\*', '.*?', preg_quote($packageFilter)).'$}i';
        }

        $packageListFilter = null;
        if ($input->getOption('direct')) {
            $packageListFilter = $this->getRootRequires();
        }

        if ($input->getOption('path') && null === $composer) {
            $io->writeError('No composer.json found in the current directory, disabling "path" option');
            $input->setOption('path', false);
        }

        foreach (RepositoryUtils::flattenRepositories($repos) as $repo) {
            if ($repo === $platformRepo) {
                $type = 'platform';
            } elseif ($lockedRepo !== null && $repo === $lockedRepo) {
                $type = 'locked';
            } elseif ($repo === $installedRepo || in_array($repo, $installedRepo->getRepositories(), true)) {
                $type = 'installed';
            } else {
                $type = 'available';
            }
            if ($repo instanceof ComposerRepository) {
                foreach ($repo->getPackageNames($packageFilter) as $name) {
                    $packages[$type][$name] = $name;
                }
            } else {
                foreach ($repo->getPackages() as $package) {
                    if (!isset($packages[$type][$package->getName()])
                        || !is_object($packages[$type][$package->getName()])
                        || version_compare($packages[$type][$package->getName()]->getVersion(), $package->getVersion(), '<')
                    ) {
                        while ($package instanceof AliasPackage) {
                            $package = $package->getAliasOf();
                        }
                        if (!$packageFilterRegex || Preg::isMatch($packageFilterRegex, $package->getName())) {
                            if (null === $packageListFilter || in_array($package->getName(), $packageListFilter, true)) {
                                $packages[$type][$package->getName()] = $package;
                            }
                        }
                    }
                }
                if ($repo === $platformRepo) {
                    foreach ($platformRepo->getDisabledPackages() as $name => $package) {
                        $packages[$type][$name] = $package;
                    }
                }
            }
        }

        $showAllTypes = $input->getOption('all');
        $showLatest = $input->getOption('latest');
        $showMajorOnly = $input->getOption('major-only');
        $showMinorOnly = $input->getOption('minor-only');
        $showPatchOnly = $input->getOption('patch-only');
        $ignoredPackagesRegex = BasePackage::packageNamesToRegexp(array_map('strtolower', $input->getOption('ignore')));
        $indent = $showAllTypes ? '  ' : '';
        /** @var PackageInterface[] $latestPackages */
        $latestPackages = [];
        $exitCode = 0;
        $viewData = [];
        $viewMetaData = [];

        $writeVersion = false;
        $writeDescription = false;

        foreach (['platform' => true, 'locked' => true, 'available' => false, 'installed' => true] as $type => $showVersion) {
            if (isset($packages[$type])) {
                ksort($packages[$type]);

                $nameLength = $versionLength = $latestLength = $releaseDateLength = 0;

                if ($showLatest && $showVersion) {
                    foreach ($packages[$type] as $package) {
                        if (is_object($package) && !Preg::isMatch($ignoredPackagesRegex, $package->getPrettyName())) {
                            $latestPackage = $this->findLatestPackage($package, $composer, $platformRepo, $showMajorOnly, $showMinorOnly, $showPatchOnly, $platformReqFilter);
                            if ($latestPackage === null) {
                                continue;
                            }

                            $latestPackages[$package->getPrettyName()] = $latestPackage;
                        }
                    }
                }

                $writePath = !$input->getOption('name-only') && $input->getOption('path');
                $writeVersion = !$input->getOption('name-only') && !$input->getOption('path') && $showVersion;
                $writeLatest = $writeVersion && $showLatest;
                $writeDescription = !$input->getOption('name-only') && !$input->getOption('path');
                $writeReleaseDate = $writeLatest && ($input->getOption('sort-by-age') || $format === 'json');

                $hasOutdatedPackages = false;

                if ($input->getOption('sort-by-age')) {
                    usort($packages[$type], static function ($a, $b) {
                        if (is_object($a) && is_object($b)) {
                            return $a->getReleaseDate() <=> $b->getReleaseDate();
                        }

                        return 0;
                    });
                }

                $viewData[$type] = [];
                foreach ($packages[$type] as $package) {
                    $packageViewData = [];
                    if (is_object($package)) {
                        $latestPackage = null;
                        if ($showLatest && isset($latestPackages[$package->getPrettyName()])) {
                            $latestPackage = $latestPackages[$package->getPrettyName()];
                        }

                        // Determine if Composer is checking outdated dependencies and if current package should trigger non-default exit code
                        $packageIsUpToDate = $latestPackage && $latestPackage->getFullPrettyVersion() === $package->getFullPrettyVersion() && (!$latestPackage instanceof CompletePackageInterface || !$latestPackage->isAbandoned());
                        // When using --major-only, and no bigger version than current major is found then it is considered up to date
                        $packageIsUpToDate = $packageIsUpToDate || ($latestPackage === null && $showMajorOnly);
                        $packageIsIgnored = Preg::isMatch($ignoredPackagesRegex, $package->getPrettyName());
                        if ($input->getOption('outdated') && ($packageIsUpToDate || $packageIsIgnored)) {
                            continue;
                        }

                        if ($input->getOption('outdated') || $input->getOption('strict')) {
                            $hasOutdatedPackages = true;
                        }

                        $packageViewData['name'] = $package->getPrettyName();
                        $packageViewData['direct-dependency'] = in_array($package->getName(), $this->getRootRequires(), true);
                        if ($format !== 'json' || true !== $input->getOption('name-only')) {
                            $packageViewData['homepage'] = $package instanceof CompletePackageInterface ? $package->getHomepage() : null;
                            $packageViewData['source'] = PackageInfo::getViewSourceUrl($package);
                        }
                        $nameLength = max($nameLength, strlen($packageViewData['name']));
                        if ($writeVersion) {
                            $packageViewData['version'] = $package->getFullPrettyVersion();
                            if ($format === 'text') {
                                $packageViewData['version'] = ltrim($packageViewData['version'], 'v');
                            }
                            $versionLength = max($versionLength, strlen($packageViewData['version']));
                        }
                        if ($writeReleaseDate) {
                            if ($package->getReleaseDate() !== null) {
                                $packageViewData['release-age'] = str_replace(' ago', ' old', $this->getRelativeTime($package->getReleaseDate()));
                                if (!str_contains($packageViewData['release-age'], ' old')) {
                                    $packageViewData['release-age'] = 'from '.$packageViewData['release-age'];
                                }
                                $releaseDateLength = max($releaseDateLength, strlen($packageViewData['release-age']));
                                $packageViewData['release-date'] = $package->getReleaseDate()->format(DateTimeInterface::ATOM);
                            } else {
                                $packageViewData['release-age'] = '';
                                $packageViewData['release-date'] = '';
                            }
                        }
                        if ($writeLatest && $latestPackage) {
                            $packageViewData['latest'] = $latestPackage->getFullPrettyVersion();
                            if ($format === 'text') {
                                $packageViewData['latest'] = ltrim($packageViewData['latest'], 'v');
                            }
                            $packageViewData['latest-status'] = $this->getUpdateStatus($latestPackage, $package);
                            $latestLength = max($latestLength, strlen($packageViewData['latest']));

                            if ($latestPackage->getReleaseDate() !== null) {
                                $packageViewData['latest-release-date'] = $latestPackage->getReleaseDate()->format(DateTimeInterface::ATOM);
                            } else {
                                $packageViewData['latest-release-date'] = '';
                            }
                        } elseif ($writeLatest) {
                            $packageViewData['latest'] = '[none matched]';
                            $packageViewData['latest-status'] = 'up-to-date';
                            $latestLength = max($latestLength, strlen($packageViewData['latest']));
                        }
                        if ($writeDescription && $package instanceof CompletePackageInterface) {
                            $packageViewData['description'] = $package->getDescription();
                        }
                        if ($writePath) {
                            $path = $composer->getInstallationManager()->getInstallPath($package);
                            if (is_string($path)) {
                                $packageViewData['path'] = strtok(realpath($path), "\r\n");
                            } else {
                                $packageViewData['path'] = null;
                            }
                        }

                        $packageIsAbandoned = false;
                        if ($latestPackage instanceof CompletePackageInterface && $latestPackage->isAbandoned()) {
                            $replacementPackageName = $latestPackage->getReplacementPackage();
                            $replacement = $replacementPackageName !== null
                                ? 'Use ' . $latestPackage->getReplacementPackage() . ' instead'
                                : 'No replacement was suggested';
                            $packageWarning = sprintf(
                                'Package %s is abandoned, you should avoid using it. %s.',
                                $package->getPrettyName(),
                                $replacement
                            );
                            $packageViewData['warning'] = $packageWarning;
                            $packageIsAbandoned = $replacementPackageName ?? true;
                        }

                        $packageViewData['abandoned'] = $packageIsAbandoned;
                    } else {
                        $packageViewData['name'] = $package;
                        $nameLength = max($nameLength, strlen($package));
                    }
                    $viewData[$type][] = $packageViewData;
                }
                $viewMetaData[$type] = [
                    'nameLength' => $nameLength,
                    'versionLength' => $versionLength,
                    'latestLength' => $latestLength,
                    'releaseDateLength' => $releaseDateLength,
                    'writeLatest' => $writeLatest,
                    'writeReleaseDate' => $writeReleaseDate,
                ];
                if ($input->getOption('strict') && $hasOutdatedPackages) {
                    $exitCode = 1;
                    break;
                }
            }
        }

        if ('json' === $format) {
            $io->write(JsonFile::encode($viewData));
        } else {
            if ($input->getOption('latest') && array_filter($viewData)) {
                if (!$io->isDecorated()) {
                    $io->writeError('Legend:');
                    $io->writeError('! patch or minor release available - update recommended');
                    $io->writeError('~ major release available - update possible');
                    if (!$input->getOption('outdated')) {
                        $io->writeError('= up to date version');
                    }
                } else {
                    $io->writeError('<info>Color legend:</info>');
                    $io->writeError('- <highlight>patch or minor</highlight> release available - update recommended');
                    $io->writeError('- <comment>major</comment> release available - update possible');
                    if (!$input->getOption('outdated')) {
                        $io->writeError('- <info>up to date</info> version');
                    }
                }
            }

            $width = $this->getTerminalWidth();

            foreach ($viewData as $type => $packages) {
                $nameLength = $viewMetaData[$type]['nameLength'];
                $versionLength = $viewMetaData[$type]['versionLength'];
                $latestLength = $viewMetaData[$type]['latestLength'];
                $releaseDateLength = $viewMetaData[$type]['releaseDateLength'];
                $writeLatest = $viewMetaData[$type]['writeLatest'];
                $writeReleaseDate = $viewMetaData[$type]['writeReleaseDate'];

                $versionFits = $nameLength + $versionLength + 3 <= $width;
                $latestFits = $nameLength + $versionLength + $latestLength + 3 <= $width;
                $releaseDateFits = $nameLength + $versionLength + $latestLength + $releaseDateLength + 3 <= $width;
                $descriptionFits = $nameLength + $versionLength + $latestLength + $releaseDateLength + 24 <= $width;

                if ($latestFits && !$io->isDecorated()) {
                    $latestLength += 2;
                }

                if ($showAllTypes) {
                    if ('available' === $type) {
                        $io->write('<comment>' . $type . '</comment>:');
                    } else {
                        $io->write('<info>' . $type . '</info>:');
                    }
                }

                if ($writeLatest && !$input->getOption('direct')) {
                    $directDeps = [];
                    $transitiveDeps = [];
                    foreach ($packages as $pkg) {
                        if ($pkg['direct-dependency'] ?? false) {
                            $directDeps[] = $pkg;
                        } else {
                            $transitiveDeps[] = $pkg;
                        }
                    }

                    $io->writeError('');
                    $io->writeError('<info>Direct dependencies required in composer.json:</>');
                    if (\count($directDeps) > 0) {
                        $this->printPackages($io, $directDeps, $indent, $writeVersion && $versionFits, $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength, $writeReleaseDate && $releaseDateFits, $releaseDateLength);
                    } else {
                        $io->writeError('Everything up to date');
                    }
                    $io->writeError('');
                    $io->writeError('<info>Transitive dependencies not required in composer.json:</>');
                    if (\count($transitiveDeps) > 0) {
                        $this->printPackages($io, $transitiveDeps, $indent, $writeVersion && $versionFits, $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength, $writeReleaseDate && $releaseDateFits, $releaseDateLength);
                    } else {
                        $io->writeError('Everything up to date');
                    }
                } else {
                    if ($writeLatest && \count($packages) === 0) {
                        $io->writeError('All your direct dependencies are up to date');
                    } else {
                        $this->printPackages($io, $packages, $indent, $writeVersion && $versionFits, $writeLatest && $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength, $writeReleaseDate && $releaseDateFits, $releaseDateLength);
                    }
                }

                if ($showAllTypes) {
                    $io->write('');
                }
            }
        }

        return $exitCode;
    }

    /**
     * @param array<array{name: string, direct-dependency?: bool, version?: string, latest?: string, latest-status?: string, description?: string|null, path?: string|null, source?: string|null, homepage?: string|null, warning?: string, abandoned?: bool|string}> $packages
     */
    private function printPackages(IOInterface $io, array $packages, string $indent, bool $writeVersion, bool $writeLatest, bool $writeDescription, int $width, int $versionLength, int $nameLength, int $latestLength, bool $writeReleaseDate, int $releaseDateLength): void
    {
        $padName = $writeVersion || $writeLatest || $writeReleaseDate || $writeDescription;
        $padVersion = $writeLatest || $writeReleaseDate || $writeDescription;
        $padLatest = $writeDescription || $writeReleaseDate;
        $padReleaseDate = $writeDescription;
        foreach ($packages as $package) {
            $link = $package['source'] ?? $package['homepage'] ?? '';
            if ($link !== '') {
                $io->write($indent . '<href='.OutputFormatter::escape($link).'>'.$package['name'].'</>'. str_repeat(' ', ($padName ? $nameLength - strlen($package['name']) : 0)), false);
            } else {
                $io->write($indent . str_pad($package['name'], ($padName ? $nameLength : 0), ' '), false);
            }
            if (isset($package['version']) && $writeVersion) {
                $io->write(' ' . str_pad($package['version'], ($padVersion ? $versionLength : 0), ' '), false);
            }
            if (isset($package['latest']) && isset($package['latest-status']) && $writeLatest) {
                $latestVersion = $package['latest'];
                $updateStatus = $package['latest-status'];
                $style = $this->updateStatusToVersionStyle($updateStatus);
                if (!$io->isDecorated()) {
                    $latestVersion = str_replace(['up-to-date', 'semver-safe-update', 'update-possible'], ['=', '!', '~'], $updateStatus) . ' ' . $latestVersion;
                }
                $io->write(' <' . $style . '>' . str_pad($latestVersion, ($padLatest ? $latestLength : 0), ' ') . '</' . $style . '>', false);
                if ($writeReleaseDate && isset($package['release-age'])) {
                    $io->write(' '.str_pad($package['release-age'], ($padReleaseDate ? $releaseDateLength : 0), ' '), false);
                }
            }
            if (isset($package['description']) && $writeDescription) {
                $description = (string) strtok($package['description'], "\r\n");

                // Compute remaining width available for the description.
                $remaining = $width - $nameLength - $versionLength - $releaseDateLength - 4;
                if ($writeLatest) {
                    $remaining -= $latestLength;
                }

                // If nothing fits, clear the description.
                if ($remaining <= 0) {
                    $description = '';
                } elseif (extension_loaded('mbstring')) {
                    // Use mb_strwidth/mb_strimwidth to measure and trim by display width
                    // (CJK characters count as width 2). mb_strimwidth counts the trim
                    // marker ('...') in the width parameter, so pass $remaining directly.
                    if (mb_strwidth($description, 'UTF-8') > $remaining) {
                        $description = mb_strimwidth($description, 0, $remaining, '...', 'UTF-8');
                    }
                } else {
                    // Fallback when mbstring is not available: do a conservative byte-based cut.
                    // Ensure cut length is non-negative and leave room for the ellipsis.
                    $cut = max(0, $remaining - 3);
                    if (strlen($description) > $cut) {
                        $description = substr($description, 0, $cut) . '...';
                    }
                }

                $io->write(' ' . $description, false);
            }
            if (array_key_exists('path', $package)) {
                $io->write(' '.(is_string($package['path']) ? $package['path'] : 'null'), false);
            }
            $io->write('');
            if (isset($package['warning'])) {
                $io->write('<warning>' . $package['warning'] . '</warning>');
            }
        }
    }

    /**
     * @return string[]
     */
    protected function getRootRequires(): array
    {
        $composer = $this->tryComposer();
        if ($composer === null) {
            return [];
        }

        $rootPackage = $composer->getPackage();

        return array_map(
            'strtolower',
            array_keys(array_merge($rootPackage->getRequires(), $rootPackage->getDevRequires()))
        );
    }

    /**
     * @return array|string|string[]
     */
    protected function getVersionStyle(PackageInterface $latestPackage, PackageInterface $package)
    {
        return $this->updateStatusToVersionStyle($this->getUpdateStatus($latestPackage, $package));
    }

    /**
     * finds a package by name and version if provided
     *
     * @param  ConstraintInterface|string $version
     * @throws \InvalidArgumentException
     * @return array{CompletePackageInterface|null, array<string, string>}
     */
    protected function getPackage(InstalledRepository $installedRepo, RepositoryInterface $repos, string $name, $version = null): array
    {
        $name = strtolower($name);
        $constraint = is_string($version) ? $this->versionParser->parseConstraints($version) : $version;

        $policy = new DefaultPolicy();
        $repositorySet = new RepositorySet('dev');
        $repositorySet->allowInstalledRepositories();
        $repositorySet->addRepository($repos);

        $matchedPackage = null;
        $versions = [];
        if (PlatformRepository::isPlatformPackage($name)) {
            $pool = $repositorySet->createPoolWithAllPackages();
        } else {
            $pool = $repositorySet->createPoolForPackage($name);
        }
        $matches = $pool->whatProvides($name, $constraint);
        $literals = [];
        foreach ($matches as $package) {
            // avoid showing the 9999999-dev alias if the default branch has no branch-alias set
            if ($package instanceof AliasPackage && $package->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) {
                $package = $package->getAliasOf();
            }

            // select an exact match if it is in the installed repo and no specific version was required
            if (null === $version && $installedRepo->hasPackage($package)) {
                $matchedPackage = $package;
            }

            $versions[$package->getPrettyVersion()] = $package->getVersion();
            $literals[] = $package->getId();
        }

        // select preferred package according to policy rules
        if (null === $matchedPackage && \count($literals) > 0) {
            $preferred = $policy->selectPreferredPackages($pool, $literals);
            $matchedPackage = $pool->literalToPackage($preferred[0]);
        }

        if ($matchedPackage !== null && !$matchedPackage instanceof CompletePackageInterface) {
            throw new \LogicException('ShowCommand::getPackage can only work with CompletePackageInterface, but got '.get_class($matchedPackage));
        }

        return [$matchedPackage, $versions];
    }

    /**
     * Prints package info.
     *
     * @param array<string, string>    $versions
     */
    protected function printPackageInfo(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo, ?PackageInterface $latestPackage = null): void
    {
        $io = $this->getIO();

        $this->printMeta($package, $versions, $installedRepo, $latestPackage ?: null);
        $this->printLinks($package, Link::TYPE_REQUIRE);
        $this->printLinks($package, Link::TYPE_DEV_REQUIRE, 'requires (dev)');

        if ($package->getSuggests()) {
            $io->write("\n<info>suggests</info>");
            foreach ($package->getSuggests() as $suggested => $reason) {
                $io->write($suggested . ' <comment>' . $reason . '</comment>');
            }
        }

        $this->printLinks($package, Link::TYPE_PROVIDE);
        $this->printLinks($package, Link::TYPE_CONFLICT);
        $this->printLinks($package, Link::TYPE_REPLACE);
    }

    /**
     * Prints package metadata.
     *
     * @param array<string, string>    $versions
     */
    protected function printMeta(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo, ?PackageInterface $latestPackage = null): void
    {
        $isInstalledPackage = !PlatformRepository::isPlatformPackage($package->getName()) && $installedRepo->hasPackage($package);

        $io = $this->getIO();
        $io->write('<info>name</info>     : ' . $package->getPrettyName());
        $io->write('<info>descrip.</info> : ' . $package->getDescription());
        $io->write('<info>keywords</info> : ' . implode(', ', $package->getKeywords() ?: []));
        $this->printVersions($package, $versions, $installedRepo);
        if ($isInstalledPackage && $package->getReleaseDate() !== null) {
            $io->write('<info>released</info> : ' . $package->getReleaseDate()->format('Y-m-d') . ', ' . $this->getRelativeTime($package->getReleaseDate()));
        }
        if ($latestPackage) {
            $style = $this->getVersionStyle($latestPackage, $package);
            $releasedTime = $latestPackage->getReleaseDate() === null ? '' : ' released ' . $latestPackage->getReleaseDate()->format('Y-m-d') . ', ' . $this->getRelativeTime($latestPackage->getReleaseDate());
            $io->write('<info>latest</info>   : <'.$style.'>' . $latestPackage->getPrettyVersion() . '</'.$style.'>' . $releasedTime);
        } else {
            $latestPackage = $package;
        }
        $io->write('<info>type</info>     : ' . $package->getType());
        $this->printLicenses($package);
        $io->write('<info>homepage</info> : ' . $package->getHomepage());
        $io->write('<info>source</info>   : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getSourceType(), $package->getSourceUrl(), $package->getSourceReference()));
        $io->write('<info>dist</info>     : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference()));
        if ($isInstalledPackage) {
            $path = $this->requireComposer()->getInstallationManager()->getInstallPath($package);
            if (is_string($path)) {
                $io->write('<info>path</info>     : ' . realpath($path));
            } else {
                $io->write('<info>path</info>     : null');
            }
        }
        $io->write('<info>names</info>    : ' . implode(', ', $package->getNames()));

        if ($latestPackage instanceof CompletePackageInterface && $latestPackage->isAbandoned()) {
            $replacement = ($latestPackage->getReplacementPackage() !== null)
                ? ' The author suggests using the ' . $latestPackage->getReplacementPackage(). ' package instead.'
                : null;

            $io->writeError(
                sprintf('<warning>Attention: This package is abandoned and no longer maintained.%s</warning>', $replacement)
            );
        }

        if ($package->getSupport()) {
            $io->write("\n<info>support</info>");
            foreach ($package->getSupport() as $type => $value) {
                $io->write('<comment>' . $type . '</comment> : '.$value);
            }
        }

        if (\count($package->getAutoload()) > 0) {
            $io->write("\n<info>autoload</info>");
            $autoloadConfig = $package->getAutoload();
            foreach ($autoloadConfig as $type => $autoloads) {
                $io->write('<comment>' . $type . '</comment>');

                if ($type === 'psr-0' || $type === 'psr-4') {
                    foreach ($autoloads as $name => $path) {
                        $io->write(($name ?: '*') . ' => ' . (is_array($path) ? implode(', ', $path) : ($path ?: '.')));
                    }
                } elseif ($type === 'classmap') {
                    $io->write(implode(', ', $autoloadConfig[$type]));
                }
            }
            if ($package->getIncludePaths()) {
                $io->write('<comment>include-path</comment>');
                $io->write(implode(', ', $package->getIncludePaths()));
            }
        }
    }

    /**
     * Prints all available versions of this package and highlights the installed one if any.
     *
     * @param array<string, string> $versions
     */
    protected function printVersions(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo): void
    {
        $versions = array_keys($versions);
        $versions = Semver::rsort($versions);

        // highlight installed version
        if ($installedPackages = $installedRepo->findPackages($package->getName())) {
            foreach ($installedPackages as $installedPackage) {
                $installedVersion = $installedPackage->getPrettyVersion();
                $key = array_search($installedVersion, $versions);
                if (false !== $key) {
                    $versions[$key] = '<info>* ' . $installedVersion . '</info>';
                }
            }
        }

        $versions = implode(', ', $versions);

        $this->getIO()->write('<info>versions</info> : ' . $versions);
    }

    /**
     * print link objects
     */
    protected function printLinks(CompletePackageInterface $package, string $linkType, ?string $title = null): void
    {
        $title = $title ?: $linkType;
        $io = $this->getIO();
        if ($links = $package->{'get'.ucfirst($linkType)}()) {
            $io->write("\n<info>" . $title . "</info>");

            foreach ($links as $link) {
                $io->write($link->getTarget() . ' <comment>' . $link->getPrettyConstraint() . '</comment>');
            }
        }
    }

    /**
     * Prints the licenses of a package with metadata
     */
    protected function printLicenses(CompletePackageInterface $package): void
    {
        $spdxLicenses = new SpdxLicenses();

        $licenses = $package->getLicense();
        $io = $this->getIO();

        foreach ($licenses as $licenseId) {
            $license = $spdxLicenses->getLicenseByIdentifier($licenseId); // keys: 0 fullname, 1 osi, 2 url

            if (!$license) {
                $out = $licenseId;
            } else {
                // is license OSI approved?
                if ($license[1] === true) {
                    $out = sprintf('%s (%s) (OSI approved) %s', $license[0], $licenseId, $license[2]);
                } else {
                    $out = sprintf('%s (%s) %s', $license[0], $licenseId, $license[2]);
                }
            }

            $io->write('<info>license</info>  : ' . $out);
        }
    }

    /**
     * Prints package info in JSON format.
     *
     * @param array<string, string>    $versions
     */
    protected function printPackageInfoAsJson(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo, ?PackageInterface $latestPackage = null): void
    {
        $json = [
            'name' => $package->getPrettyName(),
            'description' => $package->getDescription(),
            'keywords' => $package->getKeywords() ?: [],
            'type' => $package->getType(),
            'homepage' => $package->getHomepage(),
            'names' => $package->getNames(),
        ];

        $json = $this->appendVersions($json, $versions);
        $json = $this->appendLicenses($json, $package);

        if ($latestPackage) {
            $json['latest'] = $latestPackage->getPrettyVersion();
        } else {
            $latestPackage = $package;
        }

        if (null !== $package->getSourceType()) {
            $json['source'] = [
                'type' => $package->getSourceType(),
                'url' => $package->getSourceUrl(),
                'reference' => $package->getSourceReference(),
            ];
        }

        if (null !== $package->getDistType()) {
            $json['dist'] = [
                'type' => $package->getDistType(),
                'url' => $package->getDistUrl(),
                'reference' => $package->getDistReference(),
            ];
        }

        if (!PlatformRepository::isPlatformPackage($package->getName()) && $installedRepo->hasPackage($package)) {
            $path = $this->requireComposer()->getInstallationManager()->getInstallPath($package);
            if (is_string($path)) {
                $path = realpath($path);
                if ($path !== false) {
                    $json['path'] = $path;
                }
            } else {
                $json['path'] = null;
            }

            if ($package->getReleaseDate() !== null) {
                $json['released'] = $package->getReleaseDate()->format(DATE_ATOM);
            }
        }

        if ($latestPackage instanceof CompletePackageInterface && $latestPackage->isAbandoned()) {
            $json['replacement'] = $latestPackage->getReplacementPackage();
        }

        if ($package->getSuggests()) {
            $json['suggests'] = $package->getSuggests();
        }

        if ($package->getSupport()) {
            $json['support'] = $package->getSupport();
        }

        $json = $this->appendAutoload($json, $package);

        if ($package->getIncludePaths()) {
            $json['include_path'] = $package->getIncludePaths();
        }

        $json = $this->appendLinks($json, $package);

        $this->getIO()->write(JsonFile::encode($json));
    }

    /**
     * @param JsonStructure $json
     * @param array<string, string> $versions
     * @return JsonStructure
     */
    private function appendVersions(array $json, array $versions): array
    {
        uasort($versions, 'version_compare');
        $versions = array_keys(array_reverse($versions));
        $json['versions'] = $versions;

        return $json;
    }

    /**
     * @param JsonStructure $json
     * @return JsonStructure
     */
    private function appendLicenses(array $json, CompletePackageInterface $package): array
    {
        if ($licenses = $package->getLicense()) {
            $spdxLicenses = new SpdxLicenses();

            $json['licenses'] = array_map(static function ($licenseId) use ($spdxLicenses) {
                $license = $spdxLicenses->getLicenseByIdentifier($licenseId); // keys: 0 fullname, 1 osi, 2 url

                if (!$license) {
                    return $licenseId;
                }

                return [
                    'name' => $license[0],
                    'osi' => $licenseId,
                    'url' => $license[2],
                ];
            }, $licenses);
        }

        return $json;
    }

    /**
     * @param JsonStructure $json
     * @return JsonStructure
     */
    private function appendAutoload(array $json, CompletePackageInterface $package): array
    {
        if (\count($package->getAutoload()) > 0) {
            $autoload = [];

            foreach ($package->getAutoload() as $type => $autoloads) {
                if ($type === 'psr-0' || $type === 'psr-4') {
                    $psr = [];

                    foreach ($autoloads as $name => $path) {
                        if (!$path) {
                            $path = '.';
                        }

                        $psr[$name ?: '*'] = $path;
                    }

                    $autoload[$type] = $psr;
                } elseif ($type === 'classmap') {
                    $autoload['classmap'] = $autoloads;
                }
            }

            $json['autoload'] = $autoload;
        }

        return $json;
    }

    /**
     * @param JsonStructure $json
     * @return JsonStructure
     */
    private function appendLinks(array $json, CompletePackageInterface $package): array
    {
        foreach (Link::$TYPES as $linkType) {
            $json = $this->appendLink($json, $package, $linkType);
        }

        return $json;
    }

    /**
     * @param JsonStructure $json
     * @return JsonStructure
     */
    private function appendLink(array $json, CompletePackageInterface $package, string $linkType): array
    {
        $links = $package->{'get' . ucfirst($linkType)}();

        if ($links) {
            $json[$linkType] = [];

            foreach ($links as $link) {
                $json[$linkType][$link->getTarget()] = $link->getPrettyConstraint();
            }
        }

        return $json;
    }

    /**
     * Init styles for tree
     */
    protected function initStyles(OutputInterface $output): void
    {
        $this->colors = [
            'green',
            'yellow',
            'cyan',
            'magenta',
            'blue',
        ];

        foreach ($this->colors as $color) {
            $style = new OutputFormatterStyle($color);
            $output->getFormatter()->setStyle($color, $style);
        }
    }

    /**
     * Display the tree
     *
     * @param array<int, array<string, string|mixed[]>> $arrayTree
     */
    protected function displayPackageTree(array $arrayTree): void
    {
        $io = $this->getIO();
        foreach ($arrayTree as $package) {
            $io->write(sprintf('<info>%s</info>', $package['name']), false);
            $io->write(' ' . $package['version'], false);
            if (isset($package['description'])) {
                $io->write(' ' . strtok($package['description'], "\r\n"));
            } else {
                // output newline
                $io->write('');
            }

            if (isset($package['requires'])) {
                $requires = $package['requires'];
                $treeBar = '├';
                $j = 0;
                $total = count($requires);
                foreach ($requires as $require) {
                    $requireName = $require['name'];
                    $j++;
                    if ($j === $total) {
                        $treeBar = '└';
                    }
                    $level = 1;
                    $color = $this->colors[$level];
                    $info = sprintf(
                        '%s──<%s>%s</%s> %s',
                        $treeBar,
                        $color,
                        $requireName,
                        $color,
                        $require['version']
                    );
                    $this->writeTreeLine($info);

                    $treeBar = str_replace('└', ' ', $treeBar);
                    $packagesInTree = [$package['name'], $requireName];

                    $this->displayTree($require, $packagesInTree, $treeBar, $level + 1);
                }
            }
        }
    }

    /**
     * Generate the package tree
     *
     * @return array<string, array<int, array<string, mixed[]|string>>|string|null>
     */
    protected function generatePackageTree(
        PackageInterface $package,
        InstalledRepository $installedRepo,
        RepositoryInterface $remoteRepos
    ): array {
        $requires = $package->getRequires();
        ksort($requires);
        $children = [];
        foreach ($requires as $requireName => $require) {
            $packagesInTree = [$package->getName(), $requireName];

            $treeChildDesc = [
                'name' => $requireName,
                'version' => $require->getPrettyConstraint(),
            ];

            $deepChildren = $this->addTree($requireName, $require, $installedRepo, $remoteRepos, $packagesInTree);

            if ($deepChildren) {
                $treeChildDesc['requires'] = $deepChildren;
            }

            $children[] = $treeChildDesc;
        }
        $tree = [
            'name' => $package->getPrettyName(),
            'version' => $package->getPrettyVersion(),
            'description' => $package instanceof CompletePackageInterface ? $package->getDescription() : '',
        ];

        if ($children) {
            $tree['requires'] = $children;
        }

        return $tree;
    }

    /**
     * Display a package tree
     *
     * @param array<string, array<int, array<string, mixed[]|string>>|string|null>|string $package
     * @param array<int, string|mixed[]> $packagesInTree
     */
    protected function displayTree(
        $package,
        array $packagesInTree,
        string $previousTreeBar = '├',
        int $level = 1
    ): void {
        $previousTreeBar = str_replace('├', '│', $previousTreeBar);
        if (is_array($package) && isset($package['requires'])) {
            $requires = $package['requires'];
            $treeBar = $previousTreeBar . '  ├';
            $i = 0;
            $total = count($requires);
            foreach ($requires as $require) {
                $currentTree = $packagesInTree;
                $i++;
                if ($i === $total) {
                    $treeBar = $previousTreeBar . '  └';
                }
                $colorIdent = $level % count($this->colors);
                $color = $this->colors[$colorIdent];

                assert(is_string($require['name']));
                assert(is_string($require['version']));

                $circularWarn = in_array(
                    $require['name'],
                    $currentTree,
                    true
                ) ? '(circular dependency aborted here)' : '';
                $info = rtrim(sprintf(
                    '%s──<%s>%s</%s> %s %s',
                    $treeBar,
                    $color,
                    $require['name'],
                    $color,
                    $require['version'],
                    $circularWarn
                ));
                $this->writeTreeLine($info);

                $treeBar = str_replace('└', ' ', $treeBar);

                $currentTree[] = $require['name'];
                $this->displayTree($require, $currentTree, $treeBar, $level + 1);
            }
        }
    }

    /**
     * Display a package tree
     *
     * @param  string[] $packagesInTree
     * @return array<int, array<string, array<int, array<string, string>>|string>>
     */
    protected function addTree(
        string $name,
        Link $link,
        InstalledRepository $installedRepo,
        RepositoryInterface $remoteRepos,
        array $packagesInTree
    ): array {
        $children = [];
        [$package] = $this->getPackage(
            $installedRepo,
            $remoteRepos,
            $name,
            $link->getPrettyConstraint() === 'self.version' ? $link->getConstraint() : $link->getPrettyConstraint()
        );
        if (is_object($package)) {
            $requires = $package->getRequires();
            ksort($requires);
            foreach ($requires as $requireName => $require) {
                $currentTree = $packagesInTree;

                $treeChildDesc = [
                    'name' => $requireName,
                    'version' => $require->getPrettyConstraint(),
                ];

                if (!in_array($requireName, $currentTree, true)) {
                    $currentTree[] = $requireName;
                    $deepChildren = $this->addTree($requireName, $require, $installedRepo, $remoteRepos, $currentTree);
                    if ($deepChildren) {
                        $treeChildDesc['requires'] = $deepChildren;
                    }
                }

                $children[] = $treeChildDesc;
            }
        }

        return $children;
    }

    private function updateStatusToVersionStyle(string $updateStatus): string
    {
        // 'up-to-date' is printed green
        // 'semver-safe-update' is printed red
        // 'update-possible' is printed yellow
        return str_replace(['up-to-date', 'semver-safe-update', 'update-possible'], ['info', 'highlight', 'comment'], $updateStatus);
    }

    private function getUpdateStatus(PackageInterface $latestPackage, PackageInterface $package): string
    {
        if ($latestPackage->getFullPrettyVersion() === $package->getFullPrettyVersion()) {
            return 'up-to-date';
        }

        $constraint = $package->getVersion();
        if (0 !== strpos($constraint, 'dev-')) {
            $constraint = '^'.$constraint;
        }
        if ($latestPackage->getVersion() && Semver::satisfies($latestPackage->getVersion(), $constraint)) {
            // it needs an immediate semver-compliant upgrade
            return 'semver-safe-update';
        }

        // it needs an upgrade but has potential BC breaks so is not urgent
        return 'update-possible';
    }

    private function writeTreeLine(string $line): void
    {
        $io = $this->getIO();
        if (!$io->isDecorated()) {
            $line = str_replace(['└', '├', '──', '│'], ['`-', '|-', '-', '|'], $line);
        }

        $io->write($line);
    }

    /**
     * Given a package, this finds the latest package matching it
     */
    private function findLatestPackage(PackageInterface $package, Composer $composer, PlatformRepository $platformRepo, bool $majorOnly, bool $minorOnly, bool $patchOnly, PlatformRequirementFilterInterface $platformReqFilter): ?PackageInterface
    {
        // find the latest version allowed in this repo set
        $name = $package->getName();
        $versionSelector = new VersionSelector($this->getRepositorySet($composer), $platformRepo);
        $stability = $composer->getPackage()->getMinimumStability();
        $flags = $composer->getPackage()->getStabilityFlags();
        if (isset($flags[$name])) {
            $stability = array_search($flags[$name], BasePackage::STABILITIES, true);
        }

        $bestStability = $stability;
        if ($composer->getPackage()->getPreferStable()) {
            $bestStability = $package->getStability();
        }

        $targetVersion = null;
        if (0 === strpos($package->getVersion(), 'dev-')) {
            $targetVersion = $package->getVersion();

            // dev-x branches are considered to be on the latest major version always, do not look up for a new commit as that is deemed a minor upgrade (albeit risky)
            if ($majorOnly) {
                return null;
            }
        }

        if ($targetVersion === null) {
            if ($majorOnly && Preg::isMatch('{^(?P<zero_major>(?:0\.)+)?(?P<first_meaningful>\d+)\.}', $package->getVersion(), $match)) {
                $targetVersion = '>='.$match['zero_major'].(((int) $match['first_meaningful']) + 1).',<9999999-dev';
            }

            if ($minorOnly) {
                $targetVersion = '^'.$package->getVersion();
            }

            if ($patchOnly) {
                $trimmedVersion = Preg::replace('{(\.0)+$}D', '', $package->getVersion());
                $partsNeeded = substr($trimmedVersion, 0, 1) === '0' ? 4 : 3;
                while (substr_count($trimmedVersion, '.') + 1 < $partsNeeded) {
                    $trimmedVersion .= '.0';
                }
                $targetVersion = '~'.$trimmedVersion;
            }
        }

        if ($this->getIO()->isVerbose()) {
            $showWarnings = true;
        } else {
            $showWarnings = static function (PackageInterface $candidate) use ($package): bool {
                if (str_starts_with($candidate->getVersion(), 'dev-') || str_starts_with($package->getVersion(), 'dev-')) {
                    return false;
                }

                return version_compare($candidate->getVersion(), $package->getVersion(), '<=');
            };
        }
        $candidate = $versionSelector->findBestCandidate($name, $targetVersion, $bestStability, $platformReqFilter, 0, $this->getIO(), $showWarnings);
        while ($candidate instanceof AliasPackage) {
            $candidate = $candidate->getAliasOf();
        }

        return $candidate !== false ? $candidate : null;
    }

    private function getRepositorySet(Composer $composer): RepositorySet
    {
        if (!$this->repositorySet) {
            $this->repositorySet = new RepositorySet($composer->getPackage()->getMinimumStability(), $composer->getPackage()->getStabilityFlags());
            $this->repositorySet->addRepository(new CompositeRepository($composer->getRepositoryManager()->getRepositories()));
        }

        return $this->repositorySet;
    }

    private function getRelativeTime(DateTimeInterface $releaseDate): string
    {
        if ($releaseDate->format('Ymd') === date('Ymd')) {
            return 'today';
        }

        $diff = $releaseDate->diff(new \DateTimeImmutable());
        if ($diff->days < 7) {
            return 'this week';
        }

        if ($diff->days < 14) {
            return 'last week';
        }

        if ($diff->m < 1 && $diff->days < 31) {
            return floor($diff->days / 7) . ' weeks ago';
        }

        if ($diff->y < 1) {
            return $diff->m . ' month' . ($diff->m > 1 ? 's' : '') . ' ago';
        }

        return $diff->y . ' year' . ($diff->y > 1 ? 's' : '') . ' ago';
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Console\Input\InputArgument;
use Composer\Console\Input\InputOption;

/**
 * @author Niels Keurentjes <niels.keurentjes@omines.com>
 */
class DependsCommand extends BaseDependencyCommand
{
    use CompletionTrait;

    /**
     * Configure command metadata.
     */
    protected function configure(): void
    {
        $this
            ->setName('depends')
            ->setAliases(['why'])
            ->setDescription('Shows which packages cause the given package to be installed')
            ->setDefinition([
                new InputArgument(self::ARGUMENT_PACKAGE, InputArgument::REQUIRED, 'Package to inspect', null, $this->suggestInstalledPackage(true, true)),
                new InputOption(self::OPTION_RECURSIVE, 'r', InputOption::VALUE_NONE, 'Recursively resolves up to the root package'),
                new InputOption(self::OPTION_TREE, 't', InputOption::VALUE_NONE, 'Prints the results as a nested tree'),
                new InputOption('locked', null, InputOption::VALUE_NONE, 'Read dependency information from composer.lock'),
            ])
            ->setHelp(
                <<<EOT
Displays detailed information about where a package is referenced.

<info>php composer.phar depends composer/composer</info>

Read more at https://getcomposer.org/doc/03-cli.md#depends-why
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        return parent::doExecute($input, $output);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Factory;
use Composer\Json\JsonFile;
use Composer\Json\JsonValidationException;
use Composer\Package\BasePackage;
use Composer\Pcre\Preg;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryFactory;
use Composer\Spdx\SpdxLicenses;
use Composer\Util\Filesystem;
use Composer\Util\Silencer;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Composer\Util\ProcessExecutor;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Helper\FormatterHelper;

/**
 * @author Justin Rainbow <justin.rainbow@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class InitCommand extends BaseCommand
{
    use CompletionTrait;
    use PackageDiscoveryTrait;

    /** @var array<string, string> */
    private $gitConfig;

    /**
     * @inheritDoc
     */
    protected function configure(): void
    {
        $this
            ->setName('init')
            ->setDescription('Creates a basic composer.json file in current directory')
            ->setDefinition([
                new InputOption('name', null, InputOption::VALUE_REQUIRED, 'Name of the package'),
                new InputOption('description', null, InputOption::VALUE_REQUIRED, 'Description of package'),
                new InputOption('author', null, InputOption::VALUE_REQUIRED, 'Author name of package'),
                new InputOption('type', null, InputOption::VALUE_REQUIRED, 'Type of package (e.g. library, project, metapackage, composer-plugin)'),
                new InputOption('homepage', null, InputOption::VALUE_REQUIRED, 'Homepage of package'),
                new InputOption('require', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"', null, $this->suggestAvailablePackageInclPlatform()),
                new InputOption('require-dev', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require for development with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"', null, $this->suggestAvailablePackageInclPlatform()),
                new InputOption('stability', 's', InputOption::VALUE_REQUIRED, 'Minimum stability (empty or one of: '.implode(', ', array_keys(BasePackage::STABILITIES)).')'),
                new InputOption('license', 'l', InputOption::VALUE_REQUIRED, 'License of package'),
                new InputOption('repository', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Add custom repositories, either by URL or using JSON arrays'),
                new InputOption('autoload', 'a', InputOption::VALUE_REQUIRED, 'Add PSR-4 autoload mapping. Maps your package\'s namespace to the provided directory. (Expects a relative path, e.g. src/)'),
            ])
            ->setHelp(
                <<<EOT
The <info>init</info> command creates a basic composer.json file
in the current directory.

<info>php composer.phar init</info>

Read more at https://getcomposer.org/doc/03-cli.md#init
EOT
            )
        ;
    }

    /**
     * @throws \Seld\JsonLint\ParsingException
     */
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = $this->getIO();

        $allowlist = ['name', 'description', 'author', 'type', 'homepage', 'require', 'require-dev', 'stability', 'license', 'autoload'];
        $options = array_filter(array_intersect_key($input->getOptions(), array_flip($allowlist)), static function ($val) { return $val !== null && $val !== []; });

        if (isset($options['name']) && !Preg::isMatch('{^[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9](([_.]|-{1,2})?[a-z0-9]+)*$}D', $options['name'])) {
            throw new \InvalidArgumentException(
                'The package name '.$options['name'].' is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name, matching: [a-z0-9_.-]+/[a-z0-9_.-]+'
            );
        }

        if (isset($options['author'])) {
            $options['authors'] = $this->formatAuthors($options['author']);
            unset($options['author']);
        }

        $repositories = $input->getOption('repository');
        if (count($repositories) > 0) {
            $config = Factory::createConfig($io);
            foreach ($repositories as $repo) {
                $options['repositories'][] = RepositoryFactory::configFromString($io, $config, $repo, true);
            }
        }

        if (isset($options['stability'])) {
            $options['minimum-stability'] = $options['stability'];
            unset($options['stability']);
        }

        $options['require'] = isset($options['require']) ? $this->formatRequirements($options['require']) : new \stdClass;
        if ([] === $options['require']) {
            $options['require'] = new \stdClass;
        }

        if (isset($options['require-dev'])) {
            $options['require-dev'] = $this->formatRequirements($options['require-dev']);
            if ([] === $options['require-dev']) {
                $options['require-dev'] = new \stdClass;
            }
        }

        // --autoload - create autoload object
        $autoloadPath = null;
        if (isset($options['autoload'])) {
            $autoloadPath = $options['autoload'];
            $namespace = $this->namespaceFromPackageName((string) $input->getOption('name'));
            $options['autoload'] = (object) [
                'psr-4' => [
                    $namespace . '\\' => $autoloadPath,
                ],
            ];
        }

        $file = new JsonFile(Factory::getComposerFile());
        $json = JsonFile::encode($options);

        if ($input->isInteractive()) {
            $io->writeError(['', $json, '']);
            if (!$io->askConfirmation('Do you confirm generation [<comment>yes</comment>]? ')) {
                $io->writeError('<error>Command aborted</error>');

                return 1;
            }
        } else {
            $io->writeError('Writing '.$file->getPath());
        }

        $file->write($options);
        try {
            $file->validateSchema(JsonFile::LAX_SCHEMA);
        } catch (JsonValidationException $e) {
            $io->writeError('<error>Schema validation error, aborting</error>');
            $errors = ' - ' . implode(PHP_EOL . ' - ', $e->getErrors());
            $io->writeError($e->getMessage() . ':' . PHP_EOL . $errors);
            Silencer::call('unlink', $file->getPath());

            return 1;
        }

        // --autoload - Create src folder
        if ($autoloadPath) {
            $filesystem = new Filesystem();
            $filesystem->ensureDirectoryExists($autoloadPath);

            // dump-autoload only for projects without added dependencies.
            if (!$this->hasDependencies($options)) {
                $this->runDumpAutoloadCommand($output);
            }
        }

        if ($input->isInteractive() && is_dir('.git')) {
            $ignoreFile = realpath('.gitignore');

            if (false === $ignoreFile) {
                $ignoreFile = realpath('.') . '/.gitignore';
            }

            if (!$this->hasVendorIgnore($ignoreFile)) {
                $question = 'Would you like the <info>vendor</info> directory added to your <info>.gitignore</info> [<comment>yes</comment>]? ';

                if ($io->askConfirmation($question)) {
                    $this->addVendorIgnore($ignoreFile);
                }
            }
        }

        $question = 'Would you like to install dependencies now [<comment>yes</comment>]? ';
        if ($input->isInteractive() && $this->hasDependencies($options) && $io->askConfirmation($question)) {
            $this->updateDependencies($output);
        }

        // --autoload - Show post-install configuration info
        if ($autoloadPath) {
            $namespace = $this->namespaceFromPackageName((string) $input->getOption('name'));

            $io->writeError('PSR-4 autoloading configured. Use "<comment>namespace '.$namespace.';</comment>" in '.$autoloadPath);
            $io->writeError('Include the Composer autoloader with: <comment>require \'vendor/autoload.php\';</comment>');
        }

        return 0;
    }

    protected function initialize(InputInterface $input, OutputInterface $output): void
    {
        parent::initialize($input, $output);

        if (!$input->isInteractive()) {
            if ($input->getOption('name') === null) {
                $input->setOption('name', $this->getDefaultPackageName());
            }

            if ($input->getOption('author') === null) {
                $input->setOption('author', $this->getDefaultAuthor());
            }
        }
    }

    /**
     * @inheritDoc
     */
    protected function interact(InputInterface $input, OutputInterface $output): void
    {
        $io = $this->getIO();
        /** @var FormatterHelper $formatter */
        $formatter = $this->getHelperSet()->get('formatter');

        // initialize repos if configured
        $repositories = $input->getOption('repository');
        if (count($repositories) > 0) {
            $config = Factory::createConfig($io);
            $io->loadConfiguration($config);
            $repoManager = RepositoryFactory::manager($io, $config);

            $repos = [new PlatformRepository];
            $createDefaultPackagistRepo = true;
            foreach ($repositories as $repo) {
                $repoConfig = RepositoryFactory::configFromString($io, $config, $repo, true);
                if (
                    (isset($repoConfig['packagist']) && $repoConfig === ['packagist' => false])
                    || (isset($repoConfig['packagist.org']) && $repoConfig === ['packagist.org' => false])
                ) {
                    $createDefaultPackagistRepo = false;
                    continue;
                }
                $repos[] = RepositoryFactory::createRepo($io, $config, $repoConfig, $repoManager);
            }

            if ($createDefaultPackagistRepo) {
                $repos[] = RepositoryFactory::createRepo($io, $config, [
                    'type' => 'composer',
                    'url' => 'https://repo.packagist.org',
                ], $repoManager);
            }

            $this->repos = new CompositeRepository($repos);
            unset($repos, $config, $repositories);
        }

        $io->writeError([
            '',
            $formatter->formatBlock('Welcome to the Composer config generator', 'bg=blue;fg=white', true),
            '',
        ]);

        // namespace
        $io->writeError([
            '',
            'This command will guide you through creating your composer.json config.',
            '',
        ]);

        $name = $input->getOption('name') ?? $this->getDefaultPackageName();

        $name = $io->askAndValidate(
            'Package name (<vendor>/<name>) [<comment>'.$name.'</comment>]: ',
            static function ($value) use ($name) {
                if (null === $value) {
                    return $name;
                }

                if (!Preg::isMatch('{^[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9](([_.]|-{1,2})?[a-z0-9]+)*$}D', $value)) {
                    throw new \InvalidArgumentException(
                        'The package name '.$value.' is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name, matching: [a-z0-9_.-]+/[a-z0-9_.-]+'
                    );
                }

                return $value;
            },
            null,
            $name
        );
        $input->setOption('name', $name);

        $description = $input->getOption('description') ?: null;
        $description = $io->ask(
            'Description [<comment>'.$description.'</comment>]: ',
            $description
        );
        $input->setOption('description', $description);

        $author = $input->getOption('author') ?? $this->getDefaultAuthor();

        $author = $io->askAndValidate(
            'Author ['.(is_string($author) ? '<comment>'.$author.'</comment>, ' : '') . 'n to skip]: ',
            function ($value) use ($author) {
                if ($value === 'n' || $value === 'no') {
                    return;
                }
                $value = $value ?: $author;
                $author = $this->parseAuthorString($value ?? '');

                if ($author['email'] === null) {
                    return $author['name'];
                }

                return sprintf('%s <%s>', $author['name'], $author['email']);
            },
            null,
            $author
        );
        $input->setOption('author', $author);

        $minimumStability = $input->getOption('stability') ?: null;
        $minimumStability = $io->askAndValidate(
            'Minimum Stability [<comment>'.$minimumStability.'</comment>]: ',
            static function ($value) use ($minimumStability) {
                if (null === $value) {
                    return $minimumStability;
                }

                if (!isset(BasePackage::STABILITIES[$value])) {
                    throw new \InvalidArgumentException(
                        'Invalid minimum stability "'.$value.'". Must be empty or one of: '.
                        implode(', ', array_keys(BasePackage::STABILITIES))
                    );
                }

                return $value;
            },
            null,
            $minimumStability
        );
        $input->setOption('stability', $minimumStability);

        $type = $input->getOption('type');
        $type = $io->ask(
            'Package Type (e.g. library, project, metapackage, composer-plugin) [<comment>'.$type.'</comment>]: ',
            $type
        );
        if ($type === '' || $type === false) {
            $type = null;
        }
        $input->setOption('type', $type);

        if (null === $license = $input->getOption('license')) {
            if (!empty($_SERVER['COMPOSER_DEFAULT_LICENSE'])) {
                $license = $_SERVER['COMPOSER_DEFAULT_LICENSE'];
            }
        }

        $license = $io->ask(
            'License [<comment>'.$license.'</comment>]: ',
            $license
        );
        $spdx = new SpdxLicenses();
        if (null !== $license && !$spdx->validate($license) && $license !== 'proprietary') {
            throw new \InvalidArgumentException('Invalid license provided: '.$license.'. Only SPDX license identifiers (https://spdx.org/licenses/) or "proprietary" are accepted.');
        }
        $input->setOption('license', $license);

        $io->writeError(['', 'Define your dependencies.', '']);

        // prepare to resolve dependencies
        $repos = $this->getRepos();
        $preferredStability = $minimumStability ?: 'stable';
        $platformRepo = null;
        if ($repos instanceof CompositeRepository) {
            foreach ($repos->getRepositories() as $candidateRepo) {
                if ($candidateRepo instanceof PlatformRepository) {
                    $platformRepo = $candidateRepo;
                    break;
                }
            }
        }

        $question = 'Would you like to define your dependencies (require) interactively [<comment>yes</comment>]? ';
        $require = $input->getOption('require');
        $requirements = [];
        if (count($require) > 0 || $io->askConfirmation($question)) {
            $requirements = $this->determineRequirements($input, $output, $require, $platformRepo, $preferredStability);
        }
        $input->setOption('require', $requirements);

        $question = 'Would you like to define your dev dependencies (require-dev) interactively [<comment>yes</comment>]? ';
        $requireDev = $input->getOption('require-dev');
        $devRequirements = [];
        if (count($requireDev) > 0 || $io->askConfirmation($question)) {
            $devRequirements = $this->determineRequirements($input, $output, $requireDev, $platformRepo, $preferredStability);
        }
        $input->setOption('require-dev', $devRequirements);

        // --autoload - input and validation
        $autoload = $input->getOption('autoload') ?: 'src/';
        $namespace = $this->namespaceFromPackageName((string) $input->getOption('name'));
        $autoload = $io->askAndValidate(
            'Add PSR-4 autoload mapping? Maps namespace "'.$namespace.'" to the entered relative path. [<comment>'.$autoload.'</comment>, n to skip]: ',
            static function ($value) use ($autoload) {
                if (null === $value) {
                    return $autoload;
                }

                if ($value === 'n' || $value === 'no') {
                    return;
                }

                $value = $value ?: $autoload;

                if (!Preg::isMatch('{^[^/][A-Za-z0-9\-_/]+/$}', $value)) {
                    throw new \InvalidArgumentException(sprintf(
                        'The src folder name "%s" is invalid. Please add a relative path with tailing forward slash. [A-Za-z0-9_-/]+/',
                        $value
                    ));
                }

                return $value;
            },
            null,
            $autoload
        );
        $input->setOption('autoload', $autoload);
    }

    /**
     * @return array{name: string, email: string|null}
     */
    private function parseAuthorString(string $author): array
    {
        if (Preg::isMatch('/^(?P<name>[- .,\p{L}\p{N}\p{Mn}\'’"()]+)(?:\s+<(?P<email>.+?)>)?$/u', $author, $match)) {
            if (null !== $match['email'] && !$this->isValidEmail($match['email'])) {
                throw new \InvalidArgumentException('Invalid email "'.$match['email'].'"');
            }

            return [
                'name' => trim($match['name']),
                'email' => $match['email'],
            ];
        }

        throw new \InvalidArgumentException(
            'Invalid author string.  Must be in the formats: '.
            'Jane Doe or John Smith <john@example.com>'
        );
    }

    /**
     * @return array<int, array{name: string, email?: string}>
     */
    protected function formatAuthors(string $author): array
    {
        $author = $this->parseAuthorString($author);
        if (null === $author['email']) {
            unset($author['email']);
        }

        return [$author];
    }

    /**
     * Extract namespace from package's vendor name.
     *
     * new_projects.acme-extra/package-name becomes "NewProjectsAcmeExtra\PackageName"
     */
    public function namespaceFromPackageName(string $packageName): ?string
    {
        if (!$packageName || strpos($packageName, '/') === false) {
            return null;
        }

        $namespace = array_map(
            static function ($part): string {
                $part = Preg::replace('/[^a-z0-9]/i', ' ', $part);
                $part = ucwords($part);

                return str_replace(' ', '', $part);
            },
            explode('/', $packageName)
        );

        return implode('\\', $namespace);
    }

    /**
     * @return array<string, string>
     */
    protected function getGitConfig(): array
    {
        if (null !== $this->gitConfig) {
            return $this->gitConfig;
        }

        $process = new ProcessExecutor($this->getIO());

        if (0 === $process->execute(['git', 'config', '-l'], $output)) {
            $this->gitConfig = [];
            Preg::matchAllStrictGroups('{^([^=]+)=(.*)$}m', $output, $matches);
            foreach ($matches[1] as $key => $match) {
                $this->gitConfig[$match] = $matches[2][$key];
            }

            return $this->gitConfig;
        }

        return $this->gitConfig = [];
    }

    /**
     * Checks the local .gitignore file for the Composer vendor directory.
     *
     * Tested patterns include:
     *  "/$vendor"
     *  "$vendor"
     *  "$vendor/"
     *  "/$vendor/"
     *  "/$vendor/*"
     *  "$vendor/*"
     */
    protected function hasVendorIgnore(string $ignoreFile, string $vendor = 'vendor'): bool
    {
        if (!file_exists($ignoreFile)) {
            return false;
        }

        $pattern = sprintf('{^/?%s(/\*?)?$}', preg_quote($vendor));

        $lines = file($ignoreFile, FILE_IGNORE_NEW_LINES);
        foreach ($lines as $line) {
            if (Preg::isMatch($pattern, $line)) {
                return true;
            }
        }

        return false;
    }

    protected function addVendorIgnore(string $ignoreFile, string $vendor = '/vendor/'): void
    {
        $contents = "";
        if (file_exists($ignoreFile)) {
            $contents = file_get_contents($ignoreFile);

            if (strpos($contents, "\n") !== 0) {
                $contents .= "\n";
            }
        }

        file_put_contents($ignoreFile, $contents . $vendor. "\n");
    }

    protected function isValidEmail(string $email): bool
    {
        // assume it's valid if we can't validate it
        if (!function_exists('filter_var')) {
            return true;
        }

        return false !== filter_var($email, FILTER_VALIDATE_EMAIL);
    }

    private function updateDependencies(OutputInterface $output): void
    {
        try {
            $updateCommand = $this->getApplication()->find('update');
            $this->getApplication()->resetComposer();
            $updateCommand->run(new ArrayInput([]), $output);
        } catch (\Exception $e) {
            $this->getIO()->writeError('Could not update dependencies. Run `composer update` to see more information.');
        }
    }

    private function runDumpAutoloadCommand(OutputInterface $output): void
    {
        try {
            $command = $this->getApplication()->find('dump-autoload');
            $this->getApplication()->resetComposer();
            $command->run(new ArrayInput([]), $output);
        } catch (\Exception $e) {
            $this->getIO()->writeError('Could not run dump-autoload.');
        }
    }

    /**
     * @param array<string, string|array<string>> $options
     */
    private function hasDependencies(array $options): bool
    {
        $requires = (array) $options['require'];
        $devRequires = isset($options['require-dev']) ? (array) $options['require-dev'] : [];

        return !empty($requires) || !empty($devRequires);
    }

    private function sanitizePackageNameComponent(string $name): string
    {
        $name = Preg::replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '\\1\\3-\\2\\4', $name);
        $name = strtolower($name);
        $name = Preg::replace('{^[_.-]+|[_.-]+$|[^a-z0-9_.-]}u', '', $name);
        $name = Preg::replace('{([_.-]){2,}}u', '$1', $name);

        return $name;
    }

    private function getDefaultPackageName(): string
    {
        $git = $this->getGitConfig();
        $cwd = realpath(".");
        $name = basename($cwd);
        $name = $this->sanitizePackageNameComponent($name);

        $vendor = $name;
        if (!empty($_SERVER['COMPOSER_DEFAULT_VENDOR'])) {
            $vendor = $_SERVER['COMPOSER_DEFAULT_VENDOR'];
        } elseif (isset($git['github.user'])) {
            $vendor = $git['github.user'];
        } elseif (!empty($_SERVER['USERNAME'])) {
            $vendor = $_SERVER['USERNAME'];
        } elseif (!empty($_SERVER['USER'])) {
            $vendor = $_SERVER['USER'];
        } elseif (get_current_user()) {
            $vendor = get_current_user();
        }

        $vendor = $this->sanitizePackageNameComponent($vendor);

        return $vendor . '/' . $name;
    }

    private function getDefaultAuthor(): ?string
    {
        $git = $this->getGitConfig();

        if (!empty($_SERVER['COMPOSER_DEFAULT_AUTHOR'])) {
            $author_name = $_SERVER['COMPOSER_DEFAULT_AUTHOR'];
        } elseif (isset($git['user.name'])) {
            $author_name = $git['user.name'];
        }

        if (!empty($_SERVER['COMPOSER_DEFAULT_EMAIL'])) {
            $author_email = $_SERVER['COMPOSER_DEFAULT_EMAIL'];
        } elseif (isset($git['user.email'])) {
            $author_email = $git['user.email'];
        }

        if (isset($author_name, $author_email)) {
            return sprintf('%s <%s>', $author_name, $author_email);
        }

        return null;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Cache;
use Composer\Factory;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author David Neilsen <petah.p@gmail.com>
 */
class ClearCacheCommand extends BaseCommand
{
    protected function configure(): void
    {
        $this
            ->setName('clear-cache')
            ->setAliases(['clearcache', 'cc'])
            ->setDescription('Clears composer\'s internal package cache')
            ->setDefinition([
                new InputOption('gc', null, InputOption::VALUE_NONE, 'Only run garbage collection, not a full cache clear'),
            ])
            ->setHelp(
                <<<EOT
The <info>clear-cache</info> deletes all cached packages from composer's
cache directory.

Read more at https://getcomposer.org/doc/03-cli.md#clear-cache-clearcache-cc
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $composer = $this->tryComposer();
        if ($composer !== null) {
            $config = $composer->getConfig();
        } else {
            $config = Factory::createConfig();
        }

        $io = $this->getIO();

        $cachePaths = [
            'cache-vcs-dir' => $config->get('cache-vcs-dir'),
            'cache-repo-dir' => $config->get('cache-repo-dir'),
            'cache-files-dir' => $config->get('cache-files-dir'),
            'cache-dir' => $config->get('cache-dir'),
        ];

        foreach ($cachePaths as $key => $cachePath) {
            // only individual dirs get garbage collected
            if ($key === 'cache-dir' && $input->getOption('gc')) {
                continue;
            }

            $cachePath = realpath($cachePath);
            if (!$cachePath) {
                $io->writeError("<info>Cache directory does not exist ($key): $cachePath</info>");

                continue;
            }
            $cache = new Cache($io, $cachePath);
            $cache->setReadOnly($config->get('cache-read-only'));
            if (!$cache->isEnabled()) {
                $io->writeError("<info>Cache is not enabled ($key): $cachePath</info>");

                continue;
            }

            if ($input->getOption('gc')) {
                $io->writeError("<info>Garbage-collecting cache ($key): $cachePath</info>");
                if ($key === 'cache-files-dir') {
                    $cache->gc($config->get('cache-files-ttl'), $config->get('cache-files-maxsize'));
                } elseif ($key === 'cache-repo-dir') {
                    $cache->gc($config->get('cache-ttl'), 1024 * 1024 * 1024 /* 1GB, this should almost never clear anything that is not outdated */);
                } elseif ($key === 'cache-vcs-dir') {
                    $cache->gcVcsCache($config->get('cache-ttl'));
                }
            } else {
                $io->writeError("<info>Clearing cache ($key): $cachePath</info>");
                $cache->clear();
            }
        }

        if ($input->getOption('gc')) {
            $io->writeError('<info>All caches garbage-collected.</info>');
        } else {
            $io->writeError('<info>All caches cleared.</info>');
        }

        return 0;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Advisory\AuditConfig;
use Composer\Composer;
use Composer\Repository\RepositorySet;
use Composer\Repository\RepositoryUtils;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Package\PackageInterface;
use Composer\Repository\InstalledRepository;
use Composer\Advisory\Auditor;
use Composer\Console\Input\InputOption;

class AuditCommand extends BaseCommand
{
    protected function configure(): void
    {
        $this
            ->setName('audit')
            ->setDescription('Checks for security vulnerability advisories for installed packages')
            ->setDefinition([
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables auditing of require-dev packages.'),
                new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_TABLE, Auditor::FORMATS),
                new InputOption('locked', null, InputOption::VALUE_NONE, 'Audit based on the lock file instead of the installed packages.'),
                new InputOption('abandoned', null, InputOption::VALUE_REQUIRED, 'Behavior on abandoned packages. Must be "ignore", "report", or "fail".', null, Auditor::ABANDONEDS),
                new InputOption('ignore-severity', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Ignore advisories of a certain severity level.', [], ['low', 'medium', 'high', 'critical']),
                new InputOption('ignore-unreachable', null, InputOption::VALUE_NONE, 'Ignore repositories that are unreachable or return a non-200 status code.'),
            ])
            ->setHelp(
                <<<EOT
The <info>audit</info> command checks for security vulnerability advisories for installed packages.

If you do not want to include dev dependencies in the audit you can omit them with --no-dev

If you want to ignore repositories that are unreachable or return a non-200 status code, use --ignore-unreachable

Read more at https://getcomposer.org/doc/03-cli.md#audit
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $composer = $this->requireComposer();
        $packages = $this->getPackages($composer, $input);

        if (count($packages) === 0) {
            $this->getIO()->writeError('No packages - skipping audit.');

            return 0;
        }

        $auditor = new Auditor();
        $repoSet = new RepositorySet();
        foreach ($composer->getRepositoryManager()->getRepositories() as $repo) {
            $repoSet->addRepository($repo);
        }

        $auditConfig = AuditConfig::fromConfig($composer->getConfig());

        $abandoned = $input->getOption('abandoned');
        if ($abandoned !== null && !in_array($abandoned, Auditor::ABANDONEDS, true)) {
            throw new \InvalidArgumentException('--abandoned must be one of '.implode(', ', Auditor::ABANDONEDS).'.');
        }

        $abandoned = $abandoned ?? $auditConfig->auditAbandoned;

        $ignoreSeverities = array_merge(array_fill_keys($input->getOption('ignore-severity'), null), $auditConfig->ignoreSeverityForAudit);
        $ignoreUnreachable = $input->getOption('ignore-unreachable') || $auditConfig->ignoreUnreachable;

        return min(255, $auditor->audit(
            $this->getIO(),
            $repoSet,
            $packages,
            $this->getAuditFormat($input, 'format'),
            false,
            $auditConfig->ignoreListForAudit,
            $abandoned,
            $ignoreSeverities,
            $ignoreUnreachable,
            $auditConfig->ignoreAbandonedForAudit
        ));

    }

    /**
     * @return PackageInterface[]
     */
    private function getPackages(Composer $composer, InputInterface $input): array
    {
        if ($input->getOption('locked')) {
            if (!$composer->getLocker()->isLocked()) {
                throw new \UnexpectedValueException('Valid composer.json and composer.lock files are required to run this command with --locked');
            }
            $locker = $composer->getLocker();

            return $locker->getLockedRepository(!$input->getOption('no-dev'))->getPackages();
        }

        $rootPkg = $composer->getPackage();
        $installedRepo = new InstalledRepository([$composer->getRepositoryManager()->getLocalRepository()]);

        if ($input->getOption('no-dev')) {
            return RepositoryUtils::filterRequiredPackages($installedRepo->getPackages(), $rootPkg);
        }

        return $installedRepo->getPackages();
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Package\Link;
use Composer\Semver\Constraint\Constraint;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RootPackageRepository;
use Composer\Repository\InstalledRepository;
use Composer\Json\JsonFile;

class CheckPlatformReqsCommand extends BaseCommand
{
    protected function configure(): void
    {
        $this->setName('check-platform-reqs')
            ->setDescription('Check that platform requirements are satisfied')
            ->setDefinition([
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables checking of require-dev packages requirements.'),
                new InputOption('lock', null, InputOption::VALUE_NONE, 'Checks requirements only from the lock file, not from installed packages.'),
                new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']),
            ])
            ->setHelp(
                <<<EOT
Checks that your PHP and extensions versions match the platform requirements of the installed packages.

Unlike update/install, this command will ignore config.platform settings and check the real platform packages so you can be certain you have the required platform dependencies.

<info>php composer.phar check-platform-reqs</info>

EOT
            );
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $composer = $this->requireComposer();

        $requires = [];
        $removePackages = [];
        if ($input->getOption('lock')) {
            $this->getIO()->writeError('<info>Checking '.($input->getOption('no-dev') ? 'non-dev ' : '').'platform requirements using the lock file</info>');
            $installedRepo = $composer->getLocker()->getLockedRepository(!$input->getOption('no-dev'));
        } else {
            $installedRepo = $composer->getRepositoryManager()->getLocalRepository();
            // fallback to lockfile if installed repo is empty
            if (!$installedRepo->getPackages()) {
                $this->getIO()->writeError('<warning>No vendor dir present, checking '.($input->getOption('no-dev') ? 'non-dev ' : '').'platform requirements from the lock file</warning>');
                $installedRepo = $composer->getLocker()->getLockedRepository(!$input->getOption('no-dev'));
            } else {
                if ($input->getOption('no-dev')) {
                    $removePackages = $installedRepo->getDevPackageNames();
                }

                $this->getIO()->writeError('<info>Checking '.($input->getOption('no-dev') ? 'non-dev ' : '').'platform requirements for packages in the vendor dir</info>');
            }
        }
        if (!$input->getOption('no-dev')) {
            foreach ($composer->getPackage()->getDevRequires() as $require => $link) {
                $requires[$require] = [$link];
            }
        }

        $installedRepo = new InstalledRepository([$installedRepo, new RootPackageRepository(clone $composer->getPackage())]);
        foreach ($installedRepo->getPackages() as $package) {
            if (in_array($package->getName(), $removePackages, true)) {
                continue;
            }
            foreach ($package->getRequires() as $require => $link) {
                $requires[$require][] = $link;
            }
        }

        ksort($requires);

        $installedRepo->addRepository(new PlatformRepository([], []));

        $results = [];
        $exitCode = 0;

        /**
         * @var Link[] $links
         */
        foreach ($requires as $require => $links) {
            if (PlatformRepository::isPlatformPackage($require)) {
                $candidates = $installedRepo->findPackagesWithReplacersAndProviders($require);
                if ($candidates) {
                    $reqResults = [];
                    foreach ($candidates as $candidate) {
                        $candidateConstraint = null;
                        if ($candidate->getName() === $require) {
                            $candidateConstraint = new Constraint('=', $candidate->getVersion());
                            $candidateConstraint->setPrettyString($candidate->getPrettyVersion());
                        } else {
                            foreach (array_merge($candidate->getProvides(), $candidate->getReplaces()) as $link) {
                                if ($link->getTarget() === $require) {
                                    $candidateConstraint = $link->getConstraint();
                                    break;
                                }
                            }
                        }

                        // safety check for phpstan, but it should not be possible to get a candidate out of findPackagesWithReplacersAndProviders without a constraint matching $require
                        if (!$candidateConstraint) {
                            continue;
                        }

                        foreach ($links as $link) {
                            if (!$link->getConstraint()->matches($candidateConstraint)) {
                                $reqResults[] = [
                                    $candidate->getName() === $require ? $candidate->getPrettyName() : $require,
                                    $candidateConstraint->getPrettyString(),
                                    $link,
                                    '<error>failed</error>',
                                    $candidate->getName() === $require ? '' : '<comment>provided by '.$candidate->getPrettyName().'</comment>',
                                ];

                                // skip to next candidate
                                continue 2;
                            }
                        }

                        $results[] = [
                            $candidate->getName() === $require ? $candidate->getPrettyName() : $require,
                            $candidateConstraint->getPrettyString(),
                            null,
                            '<info>success</info>',
                            $candidate->getName() === $require ? '' : '<comment>provided by '.$candidate->getPrettyName().'</comment>',
                        ];

                        // candidate matched, skip to next requirement
                        continue 2;
                    }

                    // show the first error from every failed candidate
                    $results = array_merge($results, $reqResults);
                    $exitCode = max($exitCode, 1);

                    continue;
                }

                $results[] = [
                    $require,
                    'n/a',
                    $links[0],
                    '<error>missing</error>',
                    '',
                ];

                $exitCode = max($exitCode, 2);
            }
        }

        $this->printTable($output, $results, $input->getOption('format'));

        return $exitCode;
    }

    /**
     * @param mixed[] $results
     */
    protected function printTable(OutputInterface $output, array $results, string $format): void
    {
        $rows = [];
        foreach ($results as $result) {
            /**
             * @var Link|null $link
             */
            [$platformPackage, $version, $link, $status, $provider] = $result;

            if ('json' === $format) {
                $rows[] = [
                    "name" => $platformPackage,
                    "version" => $version,
                    "status" => strip_tags($status),
                    "failed_requirement" => $link instanceof Link ? [
                        'source' => $link->getSource(),
                        'type' => $link->getDescription(),
                        'target' => $link->getTarget(),
                        'constraint' => $link->getPrettyConstraint(),
                    ] : null,
                    "provider" => $provider === '' ? null : strip_tags($provider),
                ];
            } else {
                $rows[] = [
                    $platformPackage,
                    $version,
                    $link,
                    $link ? sprintf('%s %s %s (%s)', $link->getSource(), $link->getDescription(), $link->getTarget(), $link->getPrettyConstraint()) : '',
                    rtrim($status.' '.$provider),
                ];
            }
        }

        if ('json' === $format) {
            $this->getIO()->write(JsonFile::encode($rows));
        } else {
            $this->renderTable($rows, $output);
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Json\JsonFile;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputArgument;
use Composer\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryInterface;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;

/**
 * @author Robert Schönthal <seroscho@googlemail.com>
 */
class SearchCommand extends BaseCommand
{
    protected function configure(): void
    {
        $this
            ->setName('search')
            ->setDescription('Searches for packages')
            ->setDefinition([
                new InputOption('only-name', 'N', InputOption::VALUE_NONE, 'Search only in package names'),
                new InputOption('only-vendor', 'O', InputOption::VALUE_NONE, 'Search only for vendor / organization names, returns only "vendor" as result'),
                new InputOption('type', 't', InputOption::VALUE_REQUIRED, 'Search for a specific package type'),
                new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']),
                new InputArgument('tokens', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'tokens to search for'),
            ])
            ->setHelp(
                <<<EOT
The search command searches for packages by its name
<info>php composer.phar search symfony composer</info>

Read more at https://getcomposer.org/doc/03-cli.md#search
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        // init repos
        $platformRepo = new PlatformRepository;
        $io = $this->getIO();

        $format = $input->getOption('format');
        if (!in_array($format, ['text', 'json'])) {
            $io->writeError(sprintf('Unsupported format "%s". See help for supported formats.', $format));

            return 1;
        }

        if (!($composer = $this->tryComposer())) {
            $composer = $this->createComposerInstance($input, $this->getIO(), []);
        }
        $localRepo = $composer->getRepositoryManager()->getLocalRepository();
        $installedRepo = new CompositeRepository([$localRepo, $platformRepo]);
        $repos = new CompositeRepository(array_merge([$installedRepo], $composer->getRepositoryManager()->getRepositories()));

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'search', $input, $output);
        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        $mode = RepositoryInterface::SEARCH_FULLTEXT;
        if ($input->getOption('only-name') === true) {
            if ($input->getOption('only-vendor') === true) {
                throw new \InvalidArgumentException('--only-name and --only-vendor cannot be used together');
            }
            $mode = RepositoryInterface::SEARCH_NAME;
        } elseif ($input->getOption('only-vendor') === true) {
            $mode = RepositoryInterface::SEARCH_VENDOR;
        }

        $type = $input->getOption('type');

        $query = implode(' ', $input->getArgument('tokens'));
        if ($mode !== RepositoryInterface::SEARCH_FULLTEXT) {
            $query = preg_quote($query);
        }

        $results = $repos->search($query, $mode, $type);

        if (\count($results) > 0 && $format === 'text') {
            $width = $this->getTerminalWidth();

            $nameLength = 0;
            foreach ($results as $result) {
                $nameLength = max(strlen($result['name']), $nameLength);
            }
            $nameLength += 1;
            foreach ($results as $result) {
                $description = $result['description'] ?? '';
                $warning = !empty($result['abandoned']) ? '<warning>! Abandoned !</warning> ' : '';
                $remaining = $width - $nameLength - strlen($warning) - 2;
                if (strlen($description) > $remaining) {
                    $description = substr($description, 0, $remaining - 3) . '...';
                }

                $link = $result['url'] ?? null;
                if ($link !== null) {
                    $io->write('<href='.OutputFormatter::escape($link).'>'.$result['name'].'</>'. str_repeat(' ', $nameLength - strlen($result['name'])) . $warning . $description);
                } else {
                    $io->write(str_pad($result['name'], $nameLength, ' ') . $warning . $description);
                }
            }
        } elseif ($format === 'json') {
            $io->write(JsonFile::encode($results));
        }

        return 0;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Json\JsonFile;
use Composer\Package\AliasPackage;
use Composer\Package\BasePackage;
use Composer\Package\CompletePackageInterface;
use Composer\Pcre\Preg;
use Composer\Repository\CompositeRepository;
use Composer\Semver\Constraint\MatchAllConstraint;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Nicolas Grekas <p@tchwork.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class FundCommand extends BaseCommand
{
    protected function configure(): void
    {
        $this->setName('fund')
            ->setDescription('Discover how to help fund the maintenance of your dependencies')
            ->setDefinition([
                new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['text', 'json']),
            ])
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $composer = $this->requireComposer();

        $repo = $composer->getRepositoryManager()->getLocalRepository();
        $remoteRepos = new CompositeRepository($composer->getRepositoryManager()->getRepositories());
        $fundings = [];

        $packagesToLoad = [];
        foreach ($repo->getPackages() as $package) {
            if ($package instanceof AliasPackage) {
                continue;
            }
            $packagesToLoad[$package->getName()] = new MatchAllConstraint();
        }

        // load all packages dev versions in parallel
        $result = $remoteRepos->loadPackages($packagesToLoad, ['dev' => BasePackage::STABILITY_DEV], []);

        // collect funding data from default branches
        foreach ($result['packages'] as $package) {
            if (
                !$package instanceof AliasPackage
                && $package instanceof CompletePackageInterface
                && $package->isDefaultBranch()
                && $package->getFunding()
                && isset($packagesToLoad[$package->getName()])
            ) {
                $fundings = $this->insertFundingData($fundings, $package);
                unset($packagesToLoad[$package->getName()]);
            }
        }

        // collect funding from installed packages if none was found in the default branch above
        foreach ($repo->getPackages() as $package) {
            if ($package instanceof AliasPackage || !isset($packagesToLoad[$package->getName()])) {
                continue;
            }

            if ($package instanceof CompletePackageInterface && $package->getFunding()) {
                $fundings = $this->insertFundingData($fundings, $package);
            }
        }

        ksort($fundings);

        $io = $this->getIO();

        $format = $input->getOption('format');
        if (!in_array($format, ['text', 'json'])) {
            $io->writeError(sprintf('Unsupported format "%s". See help for supported formats.', $format));

            return 1;
        }

        if ($fundings && $format === 'text') {
            $prev = null;

            $io->write('The following packages were found in your dependencies which publish funding information:');

            foreach ($fundings as $vendor => $links) {
                $io->write('');
                $io->write(sprintf("<comment>%s</comment>", $vendor));
                foreach ($links as $url => $packages) {
                    $line = sprintf('  <info>%s</info>', implode(', ', $packages));

                    if ($prev !== $line) {
                        $io->write($line);
                        $prev = $line;
                    }

                    $io->write(sprintf('    <href=%s>%s</>', OutputFormatter::escape($url), $url));
                }
            }

            $io->write("");
            $io->write("Please consider following these links and sponsoring the work of package authors!");
            $io->write("Thank you!");
        } elseif ($format === 'json') {
            $io->write(JsonFile::encode($fundings));
        } else {
            $io->write("No funding links were found in your package dependencies. This doesn't mean they don't need your support!");
        }

        return 0;
    }

    /**
     * @param mixed[] $fundings
     * @return mixed[]
     */
    private function insertFundingData(array $fundings, CompletePackageInterface $package): array
    {
        foreach ($package->getFunding() as $fundingOption) {
            [$vendor, $packageName] = explode('/', $package->getPrettyName());
            // ignore malformed funding entries
            if (empty($fundingOption['url'])) {
                continue;
            }
            $url = $fundingOption['url'];
            if (!empty($fundingOption['type']) && $fundingOption['type'] === 'github' && Preg::isMatch('{^https://github.com/([^/]+)$}', $url, $match)) {
                $url = 'https://github.com/sponsors/'.$match[1];
            }
            $fundings[$vendor][$url][] = $packageName;
        }

        return $fundings;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Composer;
use Composer\Package\BasePackage;
use Composer\Package\PackageInterface;
use Composer\Pcre\Preg;
use Composer\Repository\CompositeRepository;
use Composer\Repository\InstalledRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\RootPackageRepository;
use Symfony\Component\Console\Completion\CompletionInput;

/**
 * Adds completion to arguments and options.
 *
 * @internal
 */
trait CompletionTrait
{
    /**
     * @see BaseCommand::requireComposer()
     */
    abstract public function requireComposer(?bool $disablePlugins = null, ?bool $disableScripts = null): Composer;

    /**
     * Suggestion values for "prefer-install" option
     *
     * @return list<string>
     */
    private function suggestPreferInstall(): array
    {
        return ['dist', 'source', 'auto'];
    }

    /**
     * Suggest package names from root requirements.
     */
    private function suggestRootRequirement(): \Closure
    {
        return function (CompletionInput $input): array {
            $composer = $this->requireComposer();

            return array_merge(array_keys($composer->getPackage()->getRequires()), array_keys($composer->getPackage()->getDevRequires()));
        };
    }

    /**
     * Suggest package names from installed.
     */
    private function suggestInstalledPackage(bool $includeRootPackage = true, bool $includePlatformPackages = false): \Closure
    {
        return function (CompletionInput $input) use ($includeRootPackage, $includePlatformPackages): array {
            $composer = $this->requireComposer();
            $installedRepos = [];

            if ($includeRootPackage) {
                $installedRepos[] = new RootPackageRepository(clone $composer->getPackage());
            }

            $locker = $composer->getLocker();
            if ($locker->isLocked()) {
                $installedRepos[] = $locker->getLockedRepository(true);
            } else {
                $installedRepos[] = $composer->getRepositoryManager()->getLocalRepository();
            }

            $platformHint = [];
            if ($includePlatformPackages) {
                if ($locker->isLocked()) {
                    $platformRepo = new PlatformRepository([], $locker->getPlatformOverrides());
                } else {
                    $platformRepo = new PlatformRepository([], $composer->getConfig()->get('platform'));
                }
                if ($input->getCompletionValue() === '') {
                    // to reduce noise, when no text is yet entered we list only two entries for ext- and lib- prefixes
                    $hintsToFind = ['ext-' => 0, 'lib-' => 0, 'php' => 99, 'composer' => 99];
                    foreach ($platformRepo->getPackages() as $pkg) {
                        foreach ($hintsToFind as $hintPrefix => $hintCount) {
                            if (str_starts_with($pkg->getName(), $hintPrefix)) {
                                if ($hintCount === 0 || $hintCount >= 99) {
                                    $platformHint[] = $pkg->getName();
                                    $hintsToFind[$hintPrefix]++;
                                } elseif ($hintCount === 1) {
                                    unset($hintsToFind[$hintPrefix]);
                                    $platformHint[] = substr($pkg->getName(), 0, max(strlen($pkg->getName()) - 3, strlen($hintPrefix) + 1)).'...';
                                }
                                continue 2;
                            }
                        }
                    }
                } else {
                    $installedRepos[] = $platformRepo;
                }
            }

            $installedRepo = new InstalledRepository($installedRepos);

            return array_merge(
                array_map(static function (PackageInterface $package) {
                    return $package->getName();
                }, $installedRepo->getPackages()),
                $platformHint
            );
        };
    }

    /**
     * Suggest package names from installed.
     */
    private function suggestInstalledPackageTypes(bool $includeRootPackage = true): \Closure
    {
        return function (CompletionInput $input) use ($includeRootPackage): array {
            $composer = $this->requireComposer();
            $installedRepos = [];

            if ($includeRootPackage) {
                $installedRepos[] = new RootPackageRepository(clone $composer->getPackage());
            }

            $locker = $composer->getLocker();
            if ($locker->isLocked()) {
                $installedRepos[] = $locker->getLockedRepository(true);
            } else {
                $installedRepos[] = $composer->getRepositoryManager()->getLocalRepository();
            }

            $installedRepo = new InstalledRepository($installedRepos);

            return array_values(array_unique(
                array_map(static function (PackageInterface $package) {
                    return $package->getType();
                }, $installedRepo->getPackages())
            ));
        };
    }

    /**
     * Suggest package names available on all configured repositories.
     */
    private function suggestAvailablePackage(int $max = 99): \Closure
    {
        return function (CompletionInput $input) use ($max): array {
            if ($max < 1) {
                return [];
            }

            $composer = $this->requireComposer();
            $repos = new CompositeRepository($composer->getRepositoryManager()->getRepositories());

            $results = [];
            $showVendors = false;
            if (!str_contains($input->getCompletionValue(), '/')) {
                $results = $repos->search('^' . preg_quote($input->getCompletionValue()), RepositoryInterface::SEARCH_VENDOR);
                $showVendors = true;
            }

            // if we get a single vendor, we expand it into its contents already
            if (\count($results) <= 1) {
                $results = $repos->search('^'.preg_quote($input->getCompletionValue()), RepositoryInterface::SEARCH_NAME);
                $showVendors = false;
            }

            $results = array_column($results, 'name');

            if ($showVendors) {
                $results = array_map(static function (string $name): string {
                    return $name.'/';
                }, $results);

                // sort shorter results first to avoid auto-expanding the completion to a longer string than needed
                usort($results, static function (string $a, string $b) {
                    $lenA = \strlen($a);
                    $lenB = \strlen($b);
                    if ($lenA === $lenB) {
                        return $a <=> $b;
                    }

                    return $lenA - $lenB;
                });

                $pinned = [];

                // ensure if the input is an exact match that it is always in the result set
                $completionInput = $input->getCompletionValue().'/';
                if (false !== ($exactIndex = array_search($completionInput, $results, true))) {
                    $pinned[] = $completionInput;
                    array_splice($results, $exactIndex, 1);
                }

                return array_merge($pinned, array_slice($results, 0, $max - \count($pinned)));
            }

            return array_slice($results, 0, $max);
        };
    }

    /**
     * Suggest package names available on all configured repositories or
     * platform packages from the ones available on the currently-running PHP
     */
    private function suggestAvailablePackageInclPlatform(): \Closure
    {
        return function (CompletionInput $input): array {
            if (Preg::isMatch('{^(ext|lib|php)(-|$)|^com}', $input->getCompletionValue())) {
                $matches = $this->suggestPlatformPackage()($input);
            } else {
                $matches = [];
            }

            return array_merge($matches, $this->suggestAvailablePackage(99 - \count($matches))($input));
        };
    }

    /**
     * Suggest platform packages from the ones available on the currently-running PHP
     */
    private function suggestPlatformPackage(): \Closure
    {
        return function (CompletionInput $input): array {
            $repos = new PlatformRepository([], $this->requireComposer()->getConfig()->get('platform'));

            $pattern = BasePackage::packageNameToRegexp($input->getCompletionValue().'*');

            return array_filter(array_map(static function (PackageInterface $package) {
                return $package->getName();
            }, $repos->getPackages()), static function (string $name) use ($pattern): bool {
                return Preg::isMatch($pattern, $name);
            });
        };
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Console\Input\InputArgument;

/**
 * @author Davey Shafik <me@daveyshafik.com>
 */
class ExecCommand extends BaseCommand
{
    protected function configure(): void
    {
        $this
            ->setName('exec')
            ->setDescription('Executes a vendored binary/script')
            ->setDefinition([
                new InputOption('list', 'l', InputOption::VALUE_NONE),
                new InputArgument('binary', InputArgument::OPTIONAL, 'The binary to run, e.g. phpunit', null, function () {
                    return $this->getBinaries(false);
                }),
                new InputArgument(
                    'args',
                    InputArgument::IS_ARRAY | InputArgument::OPTIONAL,
                    'Arguments to pass to the binary. Use <info>--</info> to separate from composer arguments'
                ),
            ])
            ->setHelp(
                <<<EOT
Executes a vendored binary/script.

Read more at https://getcomposer.org/doc/03-cli.md#exec
EOT
            )
        ;
    }

    protected function interact(InputInterface $input, OutputInterface $output): void
    {
        $binaries = $this->getBinaries(false);
        if (count($binaries) === 0) {
            return;
        }

        if ($input->getArgument('binary') !== null || $input->getOption('list')) {
            return;
        }

        $io = $this->getIO();
        /** @var int $binary */
        $binary = $io->select(
            'Binary to run: ',
            $binaries,
            '',
            1,
            'Invalid binary name "%s"'
        );

        $input->setArgument('binary', $binaries[$binary]);
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $composer = $this->requireComposer();
        if ($input->getOption('list') || null === $input->getArgument('binary')) {
            $bins = $this->getBinaries(true);
            if ([] === $bins) {
                $binDir = $composer->getConfig()->get('bin-dir');

                throw new \RuntimeException("No binaries found in composer.json or in bin-dir ($binDir)");
            }

            $this->getIO()->write(
                <<<EOT
<comment>Available binaries:</comment>
EOT
            );

            foreach ($bins as $bin) {
                $this->getIO()->write(
                    <<<EOT
<info>- $bin</info>
EOT
                );
            }

            return 0;
        }

        $binary = $input->getArgument('binary');

        $dispatcher = $composer->getEventDispatcher();
        $dispatcher->addListener('__exec_command', $binary);

        // If the CWD was modified, we restore it to what it was initially, as it was
        // most likely modified by the global command, and we want exec to run in the local working directory
        // not the global one
        if (getcwd() !== $this->getApplication()->getInitialWorkingDirectory() && $this->getApplication()->getInitialWorkingDirectory() !== false) {
            try {
                chdir($this->getApplication()->getInitialWorkingDirectory());
            } catch (\Exception $e) {
                throw new \RuntimeException('Could not switch back to working directory "'.$this->getApplication()->getInitialWorkingDirectory().'"', 0, $e);
            }
        }

        return $dispatcher->dispatchScript('__exec_command', true, $input->getArgument('args'));
    }

    /**
     * @return list<string>
     */
    private function getBinaries(bool $forDisplay): array
    {
        $composer = $this->requireComposer();
        $binDir = $composer->getConfig()->get('bin-dir');
        $bins = glob($binDir . '/*');
        $localBins = $composer->getPackage()->getBinaries();
        if ($forDisplay) {
            $localBins = array_map(static function ($e) {
                return "$e (local)";
            }, $localBins);
        }

        $binaries = [];
        foreach (array_merge($bins, $localBins) as $bin) {
            // skip .bat copies
            if (isset($previousBin) && $bin === $previousBin.'.bat') {
                continue;
            }

            $previousBin = $bin;
            $binaries[] = basename($bin);
        }

        return $binaries;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Composer;
use Composer\Factory;
use Composer\Config;
use Composer\Pcre\Preg;
use Composer\Util\Filesystem;
use Composer\Util\Platform;
use Composer\SelfUpdate\Keys;
use Composer\SelfUpdate\Versions;
use Composer\IO\IOInterface;
use Composer\Downloader\FilesystemException;
use Composer\Downloader\TransportException;
use Phar;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Composer\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Finder\Finder;

/**
 * @author Igor Wiedler <igor@wiedler.ch>
 * @author Kevin Ran <kran@adobe.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class SelfUpdateCommand extends BaseCommand
{
    private const HOMEPAGE = 'getcomposer.org';
    private const OLD_INSTALL_EXT = '-old.phar';

    protected function configure(): void
    {
        $this
            ->setName('self-update')
            ->setAliases(['selfupdate'])
            ->setDescription('Updates composer.phar to the latest version')
            ->setDefinition([
                new InputOption('rollback', 'r', InputOption::VALUE_NONE, 'Revert to an older installation of composer'),
                new InputOption('clean-backups', null, InputOption::VALUE_NONE, 'Delete old backups during an update. This makes the current version of composer the only backup available after the update'),
                new InputArgument('version', InputArgument::OPTIONAL, 'The version to update to'),
                new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                new InputOption('update-keys', null, InputOption::VALUE_NONE, 'Prompt user for a key update'),
                new InputOption('stable', null, InputOption::VALUE_NONE, 'Force an update to the stable channel'),
                new InputOption('preview', null, InputOption::VALUE_NONE, 'Force an update to the preview channel'),
                new InputOption('snapshot', null, InputOption::VALUE_NONE, 'Force an update to the snapshot channel'),
                new InputOption('1', null, InputOption::VALUE_NONE, 'Force an update to the stable channel, but only use 1.x versions'),
                new InputOption('2', null, InputOption::VALUE_NONE, 'Force an update to the stable channel, but only use 2.x versions'),
                new InputOption('2.2', null, InputOption::VALUE_NONE, 'Force an update to the stable channel, but only use 2.2.x LTS versions'),
                new InputOption('set-channel-only', null, InputOption::VALUE_NONE, 'Only store the channel as the default one and then exit'),
            ])
            ->setHelp(
                <<<EOT
The <info>self-update</info> command checks getcomposer.org for newer
versions of composer and if found, installs the latest.

<info>php composer.phar self-update</info>

Read more at https://getcomposer.org/doc/03-cli.md#self-update-selfupdate
EOT
            )
        ;
    }

    /**
     * @throws FilesystemException
     */
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        if (strpos(__FILE__, 'phar:') !== 0) {
            if (str_contains(strtr(__DIR__, '\\', '/'), 'vendor/composer/composer')) {
                $projDir = dirname(__DIR__, 6);
                $output->writeln('<error>This instance of Composer does not have the self-update command.</error>');
                $output->writeln('<comment>You are running Composer installed as a package in your current project ("'.$projDir.'").</comment>');
                $output->writeln('<comment>To update Composer, download a composer.phar from https://getcomposer.org and then run `composer.phar update composer/composer` in your project.</comment>');
            } else {
                $output->writeln('<error>This instance of Composer does not have the self-update command.</error>');
                $output->writeln('<comment>This could be due to a number of reasons, such as Composer being installed as a system package on your OS, or Composer being installed as a package in the current project.</comment>');
            }

            return 1;
        }

        if ($_SERVER['argv'][0] === 'Standard input code') {
            return 1;
        }

        // trigger autoloading of a few classes which may be needed when verifying/swapping the phar file
        // to ensure we do not try to load them from the new phar, see https://github.com/composer/composer/issues/10252
        class_exists('Composer\Util\Platform');
        class_exists('Composer\Downloader\FilesystemException');

        $config = Factory::createConfig();

        if ($config->get('disable-tls') === true) {
            $baseUrl = 'http://' . self::HOMEPAGE;
        } else {
            $baseUrl = 'https://' . self::HOMEPAGE;
        }

        $io = $this->getIO();
        $httpDownloader = Factory::createHttpDownloader($io, $config);

        $versionsUtil = new Versions($config, $httpDownloader);

        // switch channel if requested
        $requestedChannel = null;
        foreach (Versions::CHANNELS as $channel) {
            if ($input->getOption($channel)) {
                $requestedChannel = $channel;
                $versionsUtil->setChannel($channel, $io);
                break;
            }
        }

        if ($input->getOption('set-channel-only')) {
            return 0;
        }

        $cacheDir = $config->get('cache-dir');
        $rollbackDir = $config->get('data-dir');
        $home = $config->get('home');
        $localFilename = Phar::running(false);
        if ('' === $localFilename) {
            throw new \RuntimeException('Could not determine the location of the composer.phar file as it appears you are not running this code from a phar archive.');
        }

        if ($input->getOption('update-keys')) {
            $this->fetchKeys($io, $config);

            return 0;
        }

        // ensure composer.phar location is accessible
        if (!file_exists($localFilename)) {
            throw new FilesystemException('Composer update failed: the "'.$localFilename.'" is not accessible');
        }

        // check if current dir is writable and if not try the cache dir from settings
        $tmpDir = is_writable(dirname($localFilename)) ? dirname($localFilename) : $cacheDir;

        // check for permissions in local filesystem before start connection process
        if (!is_writable($tmpDir)) {
            throw new FilesystemException('Composer update failed: the "'.$tmpDir.'" directory used to download the temp file could not be written');
        }

        // check if composer is running as the same user that owns the directory root, only if POSIX is defined and callable
        if (function_exists('posix_getpwuid') && function_exists('posix_geteuid')) {
            $composerUser = posix_getpwuid(posix_geteuid());
            $homeDirOwnerId = fileowner($home);
            if (is_array($composerUser) && $homeDirOwnerId !== false) {
                $homeOwner = posix_getpwuid($homeDirOwnerId);
                if (is_array($homeOwner) && $composerUser['name'] !== $homeOwner['name']) {
                    $io->writeError('<warning>You are running Composer as "'.$composerUser['name'].'", while "'.$home.'" is owned by "'.$homeOwner['name'].'"</warning>');
                }
            }
        }

        if ($input->getOption('rollback')) {
            return $this->rollback($output, $rollbackDir, $localFilename);
        }

        if ($input->getArgument('command') === 'self' && $input->getArgument('version') === 'update') {
            $input->setArgument('version', null);
        }

        $latest = $versionsUtil->getLatest();
        $latestStable = $versionsUtil->getLatest('stable');
        try {
            $latestPreview = $versionsUtil->getLatest('preview');
        } catch (\UnexpectedValueException $e) {
            $latestPreview = $latestStable;
        }
        $latestVersion = $latest['version'];
        $updateVersion = $input->getArgument('version') ?? $latestVersion;
        $currentMajorVersion = Preg::replace('{^(\d+).*}', '$1', Composer::getVersion());
        $updateMajorVersion = Preg::replace('{^(\d+).*}', '$1', $updateVersion);
        $previewMajorVersion = Preg::replace('{^(\d+).*}', '$1', $latestPreview['version']);

        if ($versionsUtil->getChannel() === 'stable' && null === $input->getArgument('version')) {
            // if requesting stable channel and no specific version, avoid automatically upgrading to the next major
            // simply output a warning that the next major stable is available and let users upgrade to it manually
            if ($currentMajorVersion < $updateMajorVersion) {
                $skippedVersion = $updateVersion;

                $versionsUtil->setChannel($currentMajorVersion);

                $latest = $versionsUtil->getLatest();
                $latestStable = $versionsUtil->getLatest('stable');
                $latestVersion = $latest['version'];
                $updateVersion = $latestVersion;

                $io->writeError('<warning>A new stable major version of Composer is available ('.$skippedVersion.'), run "composer self-update --'.$updateMajorVersion.'" to update to it. See also https://getcomposer.org/'.$updateMajorVersion.'</warning>');
            } elseif ($currentMajorVersion < $previewMajorVersion) {
                // promote next major version if available in preview
                $io->writeError('<warning>A preview release of the next major version of Composer is available ('.$latestPreview['version'].'), run "composer self-update --preview" to give it a try. See also https://github.com/composer/composer/releases for changelogs.</warning>');
            }
        }

        $effectiveChannel = $requestedChannel === null ? $versionsUtil->getChannel() : $requestedChannel;
        if (is_numeric($effectiveChannel) && strpos($latestStable['version'], $effectiveChannel) !== 0) {
            $io->writeError('<warning>Warning: You forced the install of '.$latestVersion.' via --'.$effectiveChannel.', but '.$latestStable['version'].' is the latest stable version. Updating to it via composer self-update --stable is recommended.</warning>');
        }
        if (isset($latest['eol'])) {
            $io->writeError('<warning>Warning: Version '.$latestVersion.' is EOL / End of Life. '.$latestStable['version'].' is the latest stable version. Updating to it via composer self-update --stable is recommended.</warning>');
        }

        if (Preg::isMatch('{^[0-9a-f]{40}$}', $updateVersion) && $updateVersion !== $latestVersion) {
            $io->writeError('<error>You can not update to a specific SHA-1 as those phars are not available for download</error>');

            return 1;
        }

        $channelString = $versionsUtil->getChannel();
        if (is_numeric($channelString)) {
            $channelString .= '.x';
        }

        if (Composer::VERSION === $updateVersion) {
            $io->writeError(
                sprintf(
                    '<info>You are already using the latest available Composer version %s (%s channel).</info>',
                    $updateVersion,
                    $channelString
                )
            );

            // remove all backups except for the most recent, if any
            if ($input->getOption('clean-backups')) {
                $this->cleanBackups($rollbackDir, $this->getLastBackupVersion($rollbackDir));
            }

            return 0;
        }

        $tempFilename = $tmpDir . '/' . basename($localFilename, '.phar').'-temp'.random_int(0, 10000000).'.phar';
        $backupFile = sprintf(
            '%s/%s-%s%s',
            $rollbackDir,
            strtr(Composer::RELEASE_DATE, ' :', '_-'),
            Preg::replace('{^([0-9a-f]{7})[0-9a-f]{33}$}', '$1', Composer::VERSION),
            self::OLD_INSTALL_EXT
        );

        $updatingToTag = !Preg::isMatch('{^[0-9a-f]{40}$}', $updateVersion);

        $io->write(sprintf("Upgrading to version <info>%s</info> (%s channel).", $updateVersion, $channelString));
        $remoteFilename = $baseUrl . ($updatingToTag ? "/download/{$updateVersion}/composer.phar" : '/composer.phar');
        try {
            $signature = $httpDownloader->get($remoteFilename.'.sig')->getBody();
        } catch (TransportException $e) {
            if ($e->getStatusCode() === 404) {
                throw new \InvalidArgumentException('Version "'.$updateVersion.'" could not be found.', 0, $e);
            }
            throw $e;
        }
        $io->writeError('   ', false);
        $httpDownloader->copy($remoteFilename, $tempFilename);
        $io->writeError('');

        if (!file_exists($tempFilename) || null === $signature || '' === $signature) {
            $io->writeError('<error>The download of the new composer version failed for an unexpected reason</error>');

            return 1;
        }

        // verify phar signature
        if (!extension_loaded('openssl') && $config->get('disable-tls')) {
            $io->writeError('<warning>Skipping phar signature verification as you have disabled OpenSSL via config.disable-tls</warning>');
        } else {
            if (!extension_loaded('openssl')) {
                throw new \RuntimeException('The openssl extension is required for phar signatures to be verified but it is not available. '
                . 'If you can not enable the openssl extension, you can disable this error, at your own risk, by setting the \'disable-tls\' option to true.');
            }

            $sigFile = 'file://'.$home.'/' . ($updatingToTag ? 'keys.tags.pub' : 'keys.dev.pub');
            if (!file_exists($sigFile)) {
                file_put_contents(
                    $home.'/keys.dev.pub',
                    <<<DEVPUBKEY
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnBDHjZS6e0ZMoK3xTD7f
FNCzlXjX/Aie2dit8QXA03pSrOTbaMnxON3hUL47Lz3g1SC6YJEMVHr0zYq4elWi
i3ecFEgzLcj+pZM5X6qWu2Ozz4vWx3JYo1/a/HYdOuW9e3lwS8VtS0AVJA+U8X0A
hZnBmGpltHhO8hPKHgkJtkTUxCheTcbqn4wGHl8Z2SediDcPTLwqezWKUfrYzu1f
o/j3WFwFs6GtK4wdYtiXr+yspBZHO3y1udf8eFFGcb2V3EaLOrtfur6XQVizjOuk
8lw5zzse1Qp/klHqbDRsjSzJ6iL6F4aynBc6Euqt/8ccNAIz0rLjLhOraeyj4eNn
8iokwMKiXpcrQLTKH+RH1JCuOVxQ436bJwbSsp1VwiqftPQieN+tzqy+EiHJJmGf
TBAbWcncicCk9q2md+AmhNbvHO4PWbbz9TzC7HJb460jyWeuMEvw3gNIpEo2jYa9
pMV6cVqnSa+wOc0D7pC9a6bne0bvLcm3S+w6I5iDB3lZsb3A9UtRiSP7aGSo7D72
8tC8+cIgZcI7k9vjvOqH+d7sdOU2yPCnRY6wFh62/g8bDnUpr56nZN1G89GwM4d4
r/TU7BQQIzsZgAiqOGXvVklIgAMiV0iucgf3rNBLjjeNEwNSTTG9F0CtQ+7JLwaE
wSEuAuRm+pRqi8BRnQ/GKUcCAwEAAQ==
-----END PUBLIC KEY-----
DEVPUBKEY
                );

                file_put_contents(
                    $home.'/keys.tags.pub',
                    <<<TAGSPUBKEY
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0Vi/2K6apCVj76nCnCl2
MQUPdK+A9eqkYBacXo2wQBYmyVlXm2/n/ZsX6pCLYPQTHyr5jXbkQzBw8SKqPdlh
vA7NpbMeNCz7wP/AobvUXM8xQuXKbMDTY2uZ4O7sM+PfGbptKPBGLe8Z8d2sUnTO
bXtX6Lrj13wkRto7st/w/Yp33RHe9SlqkiiS4MsH1jBkcIkEHsRaveZzedUaxY0M
mba0uPhGUInpPzEHwrYqBBEtWvP97t2vtfx8I5qv28kh0Y6t+jnjL1Urid2iuQZf
noCMFIOu4vksK5HxJxxrN0GOmGmwVQjOOtxkwikNiotZGPR4KsVj8NnBrLX7oGuM
nQvGciiu+KoC2r3HDBrpDeBVdOWxDzT5R4iI0KoLzFh2pKqwbY+obNPS2bj+2dgJ
rV3V5Jjry42QOCBN3c88wU1PKftOLj2ECpewY6vnE478IipiEu7EAdK8Zwj2LmTr
RKQUSa9k7ggBkYZWAeO/2Ag0ey3g2bg7eqk+sHEq5ynIXd5lhv6tC5PBdHlWipDK
tl2IxiEnejnOmAzGVivE1YGduYBjN+mjxDVy8KGBrjnz1JPgAvgdwJ2dYw4Rsc/e
TzCFWGk/HM6a4f0IzBWbJ5ot0PIi4amk07IotBXDWwqDiQTwyuGCym5EqWQ2BD95
RGv89BPD+2DLnJysngsvVaUCAwEAAQ==
-----END PUBLIC KEY-----
TAGSPUBKEY
                );
            }

            $pubkeyid = openssl_pkey_get_public($sigFile);
            if (false === $pubkeyid) {
                throw new \RuntimeException('Failed loading the public key from '.$sigFile);
            }
            $algo = defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'SHA384';
            if (!in_array('sha384', array_map('strtolower', openssl_get_md_methods()), true)) {
                throw new \RuntimeException('SHA384 is not supported by your openssl extension, could not verify the phar file integrity');
            }
            $signatureData = json_decode($signature, true);
            $signatureSha384 = base64_decode($signatureData['sha384'], true);
            if (false === $signatureSha384) {
                throw new \RuntimeException('Failed loading the phar signature from '.$remoteFilename.'.sig, got '.$signature);
            }
            $verified = 1 === openssl_verify((string) file_get_contents($tempFilename), $signatureSha384, $pubkeyid, $algo);

            // PHP 8 automatically frees the key instance and deprecates the function
            if (\PHP_VERSION_ID < 80000) {
                // @phpstan-ignore function.deprecated
                openssl_free_key($pubkeyid);
            }

            if (!$verified) {
                throw new \RuntimeException('The phar signature did not match the file you downloaded, this means your public keys are outdated or that the phar file is corrupt/has been modified');
            }
        }

        // remove saved installations of composer
        if ($input->getOption('clean-backups')) {
            $this->cleanBackups($rollbackDir);
        }

        if (!$this->setLocalPhar($localFilename, $tempFilename, $backupFile)) {
            @unlink($tempFilename);

            return 1;
        }

        if (file_exists($backupFile)) {
            $io->writeError(sprintf(
                'Use <info>composer self-update --rollback</info> to return to version <comment>%s</comment>',
                Composer::VERSION
            ));
        } else {
            $io->writeError('<warning>A backup of the current version could not be written to '.$backupFile.', no rollback possible</warning>');
        }

        return 0;
    }

    /**
     * @throws \Exception
     */
    protected function fetchKeys(IOInterface $io, Config $config): void
    {
        if (!$io->isInteractive()) {
            throw new \RuntimeException('Public keys can not be fetched in non-interactive mode, please run Composer interactively');
        }

        $io->write('Open <info>https://composer.github.io/pubkeys.html</info> to find the latest keys');

        $validator = static function ($value): string {
            $value = (string) $value;
            if (!Preg::isMatch('{^-----BEGIN PUBLIC KEY-----$}', trim($value))) {
                throw new \UnexpectedValueException('Invalid input');
            }

            return trim($value)."\n";
        };

        $devKey = '';
        while (!Preg::isMatch('{(-----BEGIN PUBLIC KEY-----.+?-----END PUBLIC KEY-----)}s', $devKey, $match)) {
            $devKey = $io->askAndValidate('Enter Dev / Snapshot Public Key (including lines with -----): ', $validator);
            while ($line = $io->ask('', '')) {
                $devKey .= trim($line)."\n";
                if (trim($line) === '-----END PUBLIC KEY-----') {
                    break;
                }
            }
        }
        file_put_contents($keyPath = $config->get('home').'/keys.dev.pub', $match[0]);
        $io->write('Stored key with fingerprint: ' . Keys::fingerprint($keyPath));

        $tagsKey = '';
        while (!Preg::isMatch('{(-----BEGIN PUBLIC KEY-----.+?-----END PUBLIC KEY-----)}s', $tagsKey, $match)) {
            $tagsKey = $io->askAndValidate('Enter Tags Public Key (including lines with -----): ', $validator);
            while ($line = $io->ask('', '')) {
                $tagsKey .= trim($line)."\n";
                if (trim($line) === '-----END PUBLIC KEY-----') {
                    break;
                }
            }
        }
        file_put_contents($keyPath = $config->get('home').'/keys.tags.pub', $match[0]);
        $io->write('Stored key with fingerprint: ' . Keys::fingerprint($keyPath));

        $io->write('Public keys stored in '.$config->get('home'));
    }

    /**
     * @throws FilesystemException
     */
    protected function rollback(OutputInterface $output, string $rollbackDir, string $localFilename): int
    {
        $rollbackVersion = $this->getLastBackupVersion($rollbackDir);
        if (null === $rollbackVersion) {
            throw new \UnexpectedValueException('Composer rollback failed: no installation to roll back to in "'.$rollbackDir.'"');
        }

        $oldFile = $rollbackDir . '/' . $rollbackVersion . self::OLD_INSTALL_EXT;

        if (!is_file($oldFile)) {
            throw new FilesystemException('Composer rollback failed: "'.$oldFile.'" could not be found');
        }
        if (!Filesystem::isReadable($oldFile)) {
            throw new FilesystemException('Composer rollback failed: "'.$oldFile.'" could not be read');
        }

        $io = $this->getIO();
        $io->writeError(sprintf("Rolling back to version <info>%s</info>.", $rollbackVersion));
        if (!$this->setLocalPhar($localFilename, $oldFile)) {
            return 1;
        }

        return 0;
    }

    /**
     * Checks if the downloaded/rollback phar is valid then moves it
     *
     * @param  string              $localFilename The composer.phar location
     * @param  string              $newFilename   The downloaded or backup phar
     * @param  string              $backupTarget  The filename to use for the backup
     * @throws FilesystemException If the file cannot be moved
     * @return bool                Whether the phar is valid and has been moved
     */
    protected function setLocalPhar(string $localFilename, string $newFilename, ?string $backupTarget = null): bool
    {
        $io = $this->getIO();
        $perms = @fileperms($localFilename);
        if ($perms !== false) {
            @chmod($newFilename, $perms);
        }

        // check phar validity
        if (!$this->validatePhar($newFilename, $error)) {
            $io->writeError('<error>The '.($backupTarget !== null ? 'update' : 'backup').' file is corrupted ('.$error.')</error>');

            if ($backupTarget !== null) {
                $io->writeError('<error>Please re-run the self-update command to try again.</error>');
            }

            return false;
        }

        // copy current file into backups dir
        if ($backupTarget !== null) {
            @copy($localFilename, $backupTarget);
        }

        try {
            if (Platform::isWindows()) {
                // use copy to apply permissions from the destination directory
                // as rename uses source permissions and may block other users
                copy($newFilename, $localFilename);
                @unlink($newFilename);
            } else {
                rename($newFilename, $localFilename);
            }

            return true;
        } catch (\Exception $e) {
            // see if we can run this operation as an Admin on Windows
            if (!is_writable(dirname($localFilename))
                && $io->isInteractive()
                && $this->isWindowsNonAdminUser()) {
                return $this->tryAsWindowsAdmin($localFilename, $newFilename);
            }

            @unlink($newFilename);
            $action = 'Composer '.($backupTarget !== null ? 'update' : 'rollback');
            throw new FilesystemException($action.' failed: "'.$localFilename.'" could not be written.'.PHP_EOL.$e->getMessage());
        }
    }

    protected function cleanBackups(string $rollbackDir, ?string $except = null): void
    {
        $finder = $this->getOldInstallationFinder($rollbackDir);
        $io = $this->getIO();
        $fs = new Filesystem;

        foreach ($finder as $file) {
            if ($file->getBasename(self::OLD_INSTALL_EXT) === $except) {
                continue;
            }
            $file = (string) $file;
            $io->writeError('<info>Removing: '.$file.'</info>');
            $fs->remove($file);
        }
    }

    protected function getLastBackupVersion(string $rollbackDir): ?string
    {
        $finder = $this->getOldInstallationFinder($rollbackDir);
        $finder->sortByName();
        $files = iterator_to_array($finder);

        if (count($files) > 0) {
            return end($files)->getBasename(self::OLD_INSTALL_EXT);
        }

        return null;
    }

    protected function getOldInstallationFinder(string $rollbackDir): Finder
    {
        return Finder::create()
            ->depth(0)
            ->files()
            ->name('*' . self::OLD_INSTALL_EXT)
            ->in($rollbackDir);
    }

    /**
     * Validates the downloaded/backup phar file
     *
     * @param string      $pharFile The downloaded or backup phar
     * @param null|string $error    Set by method on failure
     *
     * Code taken from getcomposer.org/installer. Any changes should be made
     * there and replicated here
     *
     * @throws \Exception
     * @return bool       If the operation succeeded
     */
    protected function validatePhar(string $pharFile, ?string &$error): bool
    {
        if ((bool) ini_get('phar.readonly')) {
            return true;
        }

        try {
            // Test the phar validity
            $phar = new Phar($pharFile);
            // Free the variable to unlock the file
            unset($phar);
            $result = true;
        } catch (\Exception $e) {
            if (!$e instanceof \UnexpectedValueException && !$e instanceof \PharException) {
                throw $e;
            }
            $error = $e->getMessage();
            $result = false;
        }

        return $result;
    }

    /**
     * Returns true if this is a non-admin Windows user account
     */
    protected function isWindowsNonAdminUser(): bool
    {
        if (!Platform::isWindows()) {
            return false;
        }

        // fltmc.exe manages filter drivers and errors without admin privileges
        exec('fltmc.exe filters', $output, $exitCode);

        return $exitCode !== 0;
    }

    /**
     * Invokes a UAC prompt to update composer.phar as an admin
     *
     * Uses either sudo.exe or VBScript to elevate and run cmd.exe move.
     *
     * @param  string $localFilename The composer.phar location
     * @param  string $newFilename   The downloaded or backup phar
     * @return bool   Whether composer.phar has been updated
     */
    protected function tryAsWindowsAdmin(string $localFilename, string $newFilename): bool
    {
        $io = $this->getIO();

        $io->writeError('<error>Unable to write "'.$localFilename.'". Access is denied.</error>');
        $helpMessage = 'Please run the self-update command as an Administrator.';
        $question = 'Complete this operation with Administrator privileges [<comment>Y,n</comment>]? ';

        if (!$io->askConfirmation($question, true)) {
            $io->writeError('<warning>Operation cancelled. '.$helpMessage.'</warning>');

            return false;
        }

        $tmpFile = tempnam(sys_get_temp_dir(), '');
        if (false === $tmpFile) {
            $io->writeError('<error>Operation failed. '.$helpMessage.'</error>');

            return false;
        }

        exec('sudo config 2> NUL', $output, $exitCode);
        $usingSudo = $exitCode === 0;

        $script = $usingSudo ? $tmpFile.'.bat' : $tmpFile.'.vbs';
        rename($tmpFile, $script);

        $checksum = hash_file('sha256', $newFilename);

        // cmd's internal move is fussy about backslashes
        $source = str_replace('/', '\\', $newFilename);
        $destination = str_replace('/', '\\', $localFilename);

        if ($usingSudo) {
            $code = sprintf('move "%s" "%s"', $source, $destination);
        } else {
            $code = <<<EOT
Set UAC = CreateObject("Shell.Application")
UAC.ShellExecute "cmd.exe", "/c move /y ""$source"" ""$destination""", "", "runas", 0
EOT;
        }

        file_put_contents($script, $code);
        $command = $usingSudo ? sprintf('sudo "%s"', $script) : sprintf('"%s"', $script);
        exec($command);

        // Allow time for the operation to complete
        usleep(300000);
        @unlink($script);

        // see if the file was moved and is still accessible
        if ($result = Filesystem::isReadable($localFilename) && (hash_file('sha256', $localFilename) === $checksum)) {
            $io->writeError('<info>Operation succeeded.</info>');
        } else {
            $io->writeError('<error>Operation failed. '.$helpMessage.'</error>');
        }

        return $result;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Factory;
use Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\IO\IOInterface;
use Composer\Package\BasePackage;
use Composer\Package\CompletePackageInterface;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\Package\Version\VersionSelector;
use Composer\Pcre\Preg;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryFactory;
use Composer\Repository\RepositorySet;
use Composer\Semver\Constraint\Constraint;
use Composer\Util\Filesystem;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @internal
 */
trait PackageDiscoveryTrait
{
    /** @var ?CompositeRepository */
    private $repos;
    /** @var RepositorySet[] */
    private $repositorySets;

    protected function getRepos(): CompositeRepository
    {
        if (null === $this->repos) {
            $this->repos = new CompositeRepository(array_merge(
                [new PlatformRepository],
                RepositoryFactory::defaultReposWithDefaultManager($this->getIO())
            ));
        }

        return $this->repos;
    }

    /**
     * @param key-of<BasePackage::STABILITIES>|null $minimumStability
     */
    private function getRepositorySet(InputInterface $input, ?string $minimumStability = null): RepositorySet
    {
        $key = $minimumStability ?? 'default';

        if (!isset($this->repositorySets[$key])) {
            $this->repositorySets[$key] = $repositorySet = new RepositorySet($minimumStability ?? $this->getMinimumStability($input));
            $repositorySet->addRepository($this->getRepos());
        }

        return $this->repositorySets[$key];
    }

    /**
     * @return key-of<BasePackage::STABILITIES>
     */
    private function getMinimumStability(InputInterface $input): string
    {
        if ($input->hasOption('stability')) { // @phpstan-ignore-line as InitCommand does have this option but not all classes using this trait do
            return VersionParser::normalizeStability($input->getOption('stability') ?? 'stable');
        }

        // @phpstan-ignore-next-line as RequireCommand does not have the option above so this code is reachable there
        $file = Factory::getComposerFile();
        if (is_file($file) && Filesystem::isReadable($file) && is_array($composer = json_decode((string) file_get_contents($file), true))) {
            if (isset($composer['minimum-stability'])) {
                return VersionParser::normalizeStability($composer['minimum-stability']);
            }
        }

        return 'stable';
    }

    /**
     * @param array<string> $requires
     *
     * @return array<string>
     * @throws \Exception
     */
    final protected function determineRequirements(InputInterface $input, OutputInterface $output, array $requires = [], ?PlatformRepository $platformRepo = null, string $preferredStability = 'stable', bool $useBestVersionConstraint = true, bool $fixed = false): array
    {
        if (count($requires) > 0) {
            $requires = $this->normalizeRequirements($requires);
            $result = [];
            $io = $this->getIO();

            foreach ($requires as $requirement) {
                if (isset($requirement['version']) && Preg::isMatch('{^\d+(\.\d+)?$}', $requirement['version'])) {
                    $io->writeError('<warning>The "'.$requirement['version'].'" constraint for "'.$requirement['name'].'" appears too strict and will likely not match what you want. See https://getcomposer.org/constraints</warning>');
                }

                if (!isset($requirement['version'])) {
                    // determine the best version automatically
                    [$name, $version] = $this->findBestVersionAndNameForPackage($this->getIO(), $input, $requirement['name'], $platformRepo, $preferredStability, $fixed);

                    // replace package name from packagist.org
                    $requirement['name'] = $name;

                    if ($useBestVersionConstraint) {
                        $requirement['version'] = $version;
                        $io->writeError(sprintf(
                            'Using version <info>%s</info> for <info>%s</info>',
                            $requirement['version'],
                            $requirement['name']
                        ));
                    } else {
                        $requirement['version'] = 'guess';
                    }
                }

                $result[] = $requirement['name'] . ' ' . $requirement['version'];
            }

            return $result;
        }

        $versionParser = new VersionParser();

        // Collect existing packages
        $composer = $this->tryComposer();
        $installedRepo = null;
        if (null !== $composer) {
            $installedRepo = $composer->getRepositoryManager()->getLocalRepository();
        }
        $existingPackages = [];
        if (null !== $installedRepo) {
            foreach ($installedRepo->getPackages() as $package) {
                $existingPackages[] = $package->getName();
            }
        }
        unset($composer, $installedRepo);

        $io = $this->getIO();
        while (null !== $package = $io->ask('Search for a package: ')) {
            $matches = $this->getRepos()->search($package);

            if (count($matches) > 0) {
                // Remove existing packages from search results.
                foreach ($matches as $position => $foundPackage) {
                    if (in_array($foundPackage['name'], $existingPackages, true)) {
                        unset($matches[$position]);
                    }
                }
                $matches = array_values($matches);

                $exactMatch = false;
                foreach ($matches as $match) {
                    if ($match['name'] === $package) {
                        $exactMatch = true;
                        break;
                    }
                }

                // no match, prompt which to pick
                if (!$exactMatch) {
                    $providers = $this->getRepos()->getProviders($package);
                    if (count($providers) > 0) {
                        array_unshift($matches, ['name' => $package, 'description' => '']);
                    }

                    $choices = [];
                    foreach ($matches as $position => $foundPackage) {
                        $abandoned = '';
                        if (isset($foundPackage['abandoned'])) {
                            if (is_string($foundPackage['abandoned'])) {
                                $replacement = sprintf('Use %s instead', $foundPackage['abandoned']);
                            } else {
                                $replacement = 'No replacement was suggested';
                            }
                            $abandoned = sprintf('<warning>Abandoned. %s.</warning>', $replacement);
                        }

                        $choices[] = sprintf(' <info>%5s</info> %s %s', "[$position]", $foundPackage['name'], $abandoned);
                    }

                    $io->writeError([
                        '',
                        sprintf('Found <info>%s</info> packages matching <info>%s</info>', count($matches), $package),
                        '',
                    ]);

                    $io->writeError($choices);
                    $io->writeError('');

                    $validator = static function (string $selection) use ($matches, $versionParser) {
                        if ('' === $selection) {
                            return false;
                        }

                        if (is_numeric($selection) && isset($matches[(int) $selection])) {
                            $package = $matches[(int) $selection];

                            return $package['name'];
                        }

                        if (Preg::isMatch('{^\s*(?P<name>[\S/]+)(?:\s+(?P<version>\S+))?\s*$}', $selection, $packageMatches)) {
                            if (isset($packageMatches['version'])) {
                                // parsing `acme/example ~2.3`

                                // validate version constraint
                                $versionParser->parseConstraints($packageMatches['version']);

                                return $packageMatches['name'].' '.$packageMatches['version'];
                            }

                            // parsing `acme/example`
                            return $packageMatches['name'];
                        }

                        throw new \Exception('Not a valid selection');
                    };

                    $package = $io->askAndValidate(
                        'Enter package # to add, or the complete package name if it is not listed: ',
                        $validator,
                        3,
                        ''
                    );
                }

                // no constraint yet, determine the best version automatically
                if (false !== $package && false === strpos($package, ' ')) {
                    $validator = static function (string $input) {
                        $input = trim($input);

                        return strlen($input) > 0 ? $input : false;
                    };

                    $constraint = $io->askAndValidate(
                        'Enter the version constraint to require (or leave blank to use the latest version): ',
                        $validator,
                        3,
                        ''
                    );

                    if (false === $constraint) {
                        [, $constraint] = $this->findBestVersionAndNameForPackage($this->getIO(), $input, $package, $platformRepo, $preferredStability);

                        $io->writeError(sprintf(
                            'Using version <info>%s</info> for <info>%s</info>',
                            $constraint,
                            $package
                        ));
                    }

                    $package .= ' '.$constraint;
                }

                if (false !== $package) {
                    $requires[] = $package;
                    $existingPackages[] = explode(' ', $package)[0];
                }
            }
        }

        return $requires;
    }

    /**
     * Given a package name, this determines the best version to use in the require key.
     *
     * This returns a version with the ~ operator prefixed when possible.
     *
     * @throws \InvalidArgumentException
     * @return array{string, string}     name version
     */
    private function findBestVersionAndNameForPackage(IOInterface $io, InputInterface $input, string $name, ?PlatformRepository $platformRepo = null, string $preferredStability = 'stable', bool $fixed = false): array
    {
        // handle ignore-platform-reqs flag if present
        if ($input->hasOption('ignore-platform-reqs') && $input->hasOption('ignore-platform-req')) {
            $platformRequirementFilter = $this->getPlatformRequirementFilter($input);
        } else {
            $platformRequirementFilter = PlatformRequirementFilterFactory::ignoreNothing();
        }

        // find the latest version allowed in this repo set
        $repoSet = $this->getRepositorySet($input);
        $versionSelector = new VersionSelector($repoSet, $platformRepo);
        $effectiveMinimumStability = $this->getMinimumStability($input);

        $package = $versionSelector->findBestCandidate($name, null, $preferredStability, $platformRequirementFilter, 0, $this->getIO());

        if (false === $package) {
            // platform packages can not be found in the pool in versions other than the local platform's has
            // so if platform reqs are ignored we just take the user's word for it
            if ($platformRequirementFilter->isIgnored($name)) {
                return [$name, '*'];
            }

            // Check if it is a virtual package provided by others
            $providers = $repoSet->getProviders($name);
            if (count($providers) > 0) {
                $constraint = '*';
                if ($input->isInteractive()) {
                    $constraint = $this->getIO()->askAndValidate('Package "<info>'.$name.'</info>" does not exist but is provided by '.count($providers).' packages. Which version constraint would you like to use? [<info>*</info>] ', static function ($value) {
                        $parser = new VersionParser();
                        $parser->parseConstraints($value);

                        return $value;
                    }, 3, '*');
                }

                return [$name, $constraint];
            }

            // Check whether the package requirements were the problem
            if (!($platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter) && false !== ($candidate = $versionSelector->findBestCandidate($name, null, $preferredStability, PlatformRequirementFilterFactory::ignoreAll()))) {
                throw new \InvalidArgumentException(sprintf(
                    'Package %s has requirements incompatible with your PHP version, PHP extensions and Composer version' . $this->getPlatformExceptionDetails($candidate, $platformRepo),
                    $name
                ));
            }
            // Check whether the minimum stability was the problem but the package exists
            if (false !== ($package = $versionSelector->findBestCandidate($name, null, $preferredStability, $platformRequirementFilter, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES))) {
                // we must first verify if a valid package would be found in a lower priority repository
                if (false !== ($allReposPackage = $versionSelector->findBestCandidate($name, null, $preferredStability, $platformRequirementFilter, RepositorySet::ALLOW_SHADOWED_REPOSITORIES))) {
                    throw new \InvalidArgumentException(
                        'Package '.$name.' exists in '.$allReposPackage->getRepository()->getRepoName().' and '.$package->getRepository()->getRepoName().' which has a higher repository priority. The packages from the higher priority repository do not match your minimum-stability and are therefore not installable. That repository is canonical so the lower priority repo\'s packages are not installable. See https://getcomposer.org/repoprio for details and assistance.'
                    );
                }

                throw new \InvalidArgumentException(sprintf(
                    'Could not find a version of package %s matching your minimum-stability (%s). Require it with an explicit version constraint allowing its desired stability.',
                    $name,
                    $effectiveMinimumStability
                ));
            }
            // Check whether the PHP version was the problem for all versions
            if (!$platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter && false !== ($candidate = $versionSelector->findBestCandidate($name, null, $preferredStability, PlatformRequirementFilterFactory::ignoreAll(), RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES))) {
                $additional = '';
                if (false === $versionSelector->findBestCandidate($name, null, $preferredStability, PlatformRequirementFilterFactory::ignoreAll())) {
                    $additional = PHP_EOL.PHP_EOL.'Additionally, the package was only found with a stability of "'.$candidate->getStability().'" while your minimum stability is "'.$effectiveMinimumStability.'".';
                }

                throw new \InvalidArgumentException(sprintf(
                    'Could not find package %s in any version matching your PHP version, PHP extensions and Composer version' . $this->getPlatformExceptionDetails($candidate, $platformRepo) . '%s',
                    $name,
                    $additional
                ));
            }

            // Check for similar names/typos
            $similar = $this->findSimilar($name);
            if (count($similar) > 0) {
                if (in_array($name, $similar, true)) {
                    throw new \InvalidArgumentException(sprintf(
                        "Could not find package %s. It was however found via repository search, which indicates a consistency issue with the repository.",
                        $name
                    ));
                }

                if ($input->isInteractive()) {
                    $result = $io->select("<error>Could not find package $name.</error>\nPick one of these or leave empty to abort:", $similar, false, 1);
                    if ($result !== false) {
                        return $this->findBestVersionAndNameForPackage($io, $input, $similar[$result], $platformRepo, $preferredStability, $fixed);
                    }
                }

                throw new \InvalidArgumentException(sprintf(
                    "Could not find package %s.\n\nDid you mean " . (count($similar) > 1 ? 'one of these' : 'this') . "?\n    %s",
                    $name,
                    implode("\n    ", $similar)
                ));
            }

            throw new \InvalidArgumentException(sprintf(
                'Could not find a matching version of package %s. Check the package spelling, your version constraint and that the package is available in a stability which matches your minimum-stability (%s).',
                $name,
                $effectiveMinimumStability
            ));
        }

        return [
            $package->getPrettyName(),
            $fixed ? $package->getPrettyVersion() : $versionSelector->findRecommendedRequireVersion($package),
        ];
    }

    /**
     * @return array<string>
     */
    private function findSimilar(string $package): array
    {
        try {
            if (null === $this->repos) {
                throw new \LogicException('findSimilar was called before $this->repos was initialized');
            }
            $results = $this->repos->search($package);
        } catch (\Throwable $e) {
            if ($e instanceof \LogicException) {
                throw $e;
            }

            // ignore search errors
            return [];
        }
        $similarPackages = [];

        $installedRepo = $this->requireComposer()->getRepositoryManager()->getLocalRepository();

        foreach ($results as $result) {
            if (null !== $installedRepo->findPackage($result['name'], '*')) {
                // Ignore installed package
                continue;
            }
            $similarPackages[$result['name']] = levenshtein($package, $result['name']);
        }
        asort($similarPackages);

        return array_keys(array_slice($similarPackages, 0, 5));
    }

    private function getPlatformExceptionDetails(PackageInterface $candidate, ?PlatformRepository $platformRepo = null): string
    {
        $details = [];
        if (null === $platformRepo) {
            return '';
        }

        foreach ($candidate->getRequires() as $link) {
            if (!PlatformRepository::isPlatformPackage($link->getTarget())) {
                continue;
            }
            $platformPkg = $platformRepo->findPackage($link->getTarget(), '*');
            if (null === $platformPkg) {
                if ($platformRepo->isPlatformPackageDisabled($link->getTarget())) {
                    $details[] = $candidate->getPrettyName().' '.$candidate->getPrettyVersion().' requires '.$link->getTarget().' '.$link->getPrettyConstraint().' but it is disabled by your platform config. Enable it again with "composer config platform.'.$link->getTarget().' --unset".';
                } else {
                    $details[] = $candidate->getPrettyName().' '.$candidate->getPrettyVersion().' requires '.$link->getTarget().' '.$link->getPrettyConstraint().' but it is not present.';
                }
                continue;
            }
            if (!$link->getConstraint()->matches(new Constraint('==', $platformPkg->getVersion()))) {
                $platformPkgVersion = $platformPkg->getPrettyVersion();
                $platformExtra = $platformPkg->getExtra();
                if (isset($platformExtra['config.platform']) && $platformPkg instanceof CompletePackageInterface) {
                    $platformPkgVersion .= ' ('.$platformPkg->getDescription().')';
                }
                $details[] = $candidate->getPrettyName().' '.$candidate->getPrettyVersion().' requires '.$link->getTarget().' '.$link->getPrettyConstraint().' which does not match your installed version '.$platformPkgVersion.'.';
            }
        }

        if (count($details) === 0) {
            return '';
        }

        return ':'.PHP_EOL.'  - ' . implode(PHP_EOL.'  - ', $details);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Console;

use Composer\IO\IOInterface;
use Composer\Util\Platform;

final class GithubActionError
{
    /**
     * @var IOInterface
     */
    protected $io;

    public function __construct(IOInterface  $io)
    {
        $this->io = $io;
    }

    public function emit(string $message, ?string $file = null, ?int $line = null): void
    {
        if (Platform::getEnv('GITHUB_ACTIONS') && !Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING')) {
            $message = $this->escapeData($message);

            if ($file && $line) {
                $file = $this->escapeProperty($file);
                $this->io->write("::error file=". $file .",line=". $line ."::". $message);
            } elseif ($file) {
                $file = $this->escapeProperty($file);
                $this->io->write("::error file=". $file ."::". $message);
            } else {
                $this->io->write("::error ::". $message);
            }
        }
    }

    private function escapeData(string $data): string
    {
        // see https://github.com/actions/toolkit/blob/4f7fb6513a355689f69f0849edeb369a4dc81729/packages/core/src/command.ts#L80-L85
        $data = str_replace("%", '%25', $data);
        $data = str_replace("\r", '%0D', $data);
        $data = str_replace("\n", '%0A', $data);

        return $data;
    }

    private function escapeProperty(string $property): string
    {
        // see https://github.com/actions/toolkit/blob/4f7fb6513a355689f69f0849edeb369a4dc81729/packages/core/src/command.ts#L87-L94
        $property = str_replace("%", '%25', $property);
        $property = str_replace("\r", '%0D', $property);
        $property = str_replace("\n", '%0A', $property);
        $property = str_replace(":", '%3A', $property);
        $property = str_replace(",", '%2C', $property);

        return $property;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Console;

use Closure;
use Composer\Pcre\Preg;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class HtmlOutputFormatter extends OutputFormatter
{
    /** @var array<int, string> */
    private static $availableForegroundColors = [
        30 => 'black',
        31 => 'red',
        32 => 'green',
        33 => 'yellow',
        34 => 'blue',
        35 => 'magenta',
        36 => 'cyan',
        37 => 'white',
    ];
    /** @var array<int, string> */
    private static $availableBackgroundColors = [
        40 => 'black',
        41 => 'red',
        42 => 'green',
        43 => 'yellow',
        44 => 'blue',
        45 => 'magenta',
        46 => 'cyan',
        47 => 'white',
    ];
    /** @var array<int, string> */
    private static $availableOptions = [
        1 => 'bold',
        4 => 'underscore',
        //5 => 'blink',
        //7 => 'reverse',
        //8 => 'conceal'
    ];

    /**
     * @param array<string, OutputFormatterStyle> $styles Array of "name => FormatterStyle" instances
     */
    public function __construct(array $styles = [])
    {
        parent::__construct(true, $styles);
    }

    public function format(?string $message): ?string
    {
        $formatted = parent::format($message);

        if ($formatted === null) {
            return null;
        }

        $clearEscapeCodes = '(?:39|49|0|22|24|25|27|28)';

        return Preg::replaceCallback("{\033\[([0-9;]+)m(.*?)\033\[(?:".$clearEscapeCodes.";)*?".$clearEscapeCodes."m}s", Closure::fromCallable([$this, 'formatHtml']), $formatted);
    }

    /**
     * @param array<string|null> $matches
     */
    private function formatHtml(array $matches): string
    {
        assert(is_string($matches[1]));
        $out = '<span style="';
        foreach (explode(';', $matches[1]) as $code) {
            if (isset(self::$availableForegroundColors[(int) $code])) {
                $out .= 'color:'.self::$availableForegroundColors[(int) $code].';';
            } elseif (isset(self::$availableBackgroundColors[(int) $code])) {
                $out .= 'background-color:'.self::$availableBackgroundColors[(int) $code].';';
            } elseif (isset(self::$availableOptions[(int) $code])) {
                switch (self::$availableOptions[(int) $code]) {
                    case 'bold':
                        $out .= 'font-weight:bold;';
                        break;

                    case 'underscore':
                        $out .= 'text-decoration:underline;';
                        break;
                }
            }
        }

        return $out.'">'.$matches[2].'</span>';
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Console;

use Composer\Installer;
use Composer\IO\NullIO;
use Composer\Util\Filesystem;
use Composer\Util\Platform;
use Composer\Util\Silencer;
use LogicException;
use RuntimeException;
use Symfony\Component\Console\Application as BaseApplication;
use Symfony\Component\Console\Command\Command as SymfonyCommand;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputDefinition;
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 Seld\JsonLint\ParsingException;
use Composer\Command;
use Composer\Composer;
use Composer\Factory;
use Composer\Downloader\TransportException;
use Composer\IO\IOInterface;
use Composer\IO\ConsoleIO;
use Composer\Json\JsonValidationException;
use Composer\Util\ErrorHandler;
use Composer\Util\HttpDownloader;
use Composer\EventDispatcher\ScriptExecutionException;
use Composer\Exception\NoSslException;
use Composer\XdebugHandler\XdebugHandler;
use Symfony\Component\Console\SingleCommandApplication;
use Symfony\Component\Process\Exception\ProcessTimedOutException;

/**
 * The console application that handles the commands
 *
 * @author Ryan Weaver <ryan@knplabs.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 */
class Application extends BaseApplication
{
    /**
     * @var ?Composer
     */
    protected $composer;

    /**
     * @var IOInterface
     */
    protected $io;

    /** @var string */
    private static $logo = '   ______
  / ____/___  ____ ___  ____  ____  ________  _____
 / /   / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__  )  __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
                    /_/
';

    /** @var bool */
    private $hasPluginCommands = false;
    /** @var bool */
    private $disablePluginsByDefault = false;
    /** @var bool */
    private $disableScriptsByDefault = false;

    /**
     * @var string|false Store the initial working directory at startup time
     */
    private $initialWorkingDirectory;

    public function __construct(string $name = 'Composer', string $version = '')
    {
        if (method_exists($this, 'setCatchErrors')) {
            $this->setCatchErrors(true);
        }

        static $shutdownRegistered = false;
        if ($version === '') {
            $version = Composer::getVersion();
        }
        if (function_exists('ini_set') && extension_loaded('xdebug')) {
            ini_set('xdebug.show_exception_trace', '0');
            ini_set('xdebug.scream', '0');
        }

        if (function_exists('date_default_timezone_set') && function_exists('date_default_timezone_get')) {
            date_default_timezone_set(Silencer::call('date_default_timezone_get'));
        }

        $this->io = new NullIO();

        if (!$shutdownRegistered) {
            $shutdownRegistered = true;

            register_shutdown_function(static function (): void {
                $lastError = error_get_last();

                if ($lastError && $lastError['message'] &&
                   (strpos($lastError['message'], 'Allowed memory') !== false /*Zend PHP out of memory error*/ ||
                    strpos($lastError['message'], 'exceeded memory') !== false /*HHVM out of memory errors*/)) {
                    echo "\n". 'Check https://getcomposer.org/doc/articles/troubleshooting.md#memory-limit-errors for more info on how to handle out of memory errors.';
                }
            });
        }

        $this->initialWorkingDirectory = getcwd();

        parent::__construct($name, $version);
    }

    public function __destruct()
    {
    }

    public function run(?InputInterface $input = null, ?OutputInterface $output = null): int
    {
        if (null === $output) {
            $output = Factory::createOutput();
        }

        return parent::run($input, $output);
    }

    public function doRun(InputInterface $input, OutputInterface $output): int
    {
        $this->disablePluginsByDefault = $input->hasParameterOption('--no-plugins');
        $this->disableScriptsByDefault = $input->hasParameterOption('--no-scripts');

        static $stdin = null;
        if (null === $stdin) {
            $stdin = defined('STDIN') ? STDIN : fopen('php://stdin', 'r');
        }
        if (Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING') !== '1' && (Platform::getEnv('COMPOSER_NO_INTERACTION') || $stdin === false || !Platform::isTty($stdin))) {
            $input->setInteractive(false);
        }

        $io = $this->io = new ConsoleIO($input, $output, new HelperSet([
            new QuestionHelper(),
        ]));

        // Register error handler again to pass it the IO instance
        ErrorHandler::register($io);

        if ($input->hasParameterOption('--no-cache')) {
            $io->writeError('Disabling cache usage', true, IOInterface::DEBUG);
            Platform::putEnv('COMPOSER_CACHE_DIR', Platform::isWindows() ? 'nul' : '/dev/null');
        }

        // switch working dir
        $newWorkDir = $this->getNewWorkingDir($input);
        if (null !== $newWorkDir) {
            $oldWorkingDir = Platform::getCwd(true);
            chdir($newWorkDir);
            $this->initialWorkingDirectory = getcwd();
            $cwd = Platform::getCwd(true);
            $io->writeError('Changed CWD to ' . ($cwd !== '' ? $cwd : $newWorkDir), true, IOInterface::DEBUG);
        }

        // determine command name to be executed without including plugin commands
        $commandName = '';
        if ($rawCommandName = $this->getCommandNameBeforeBinding($input)) {
            try {
                $commandName = $this->find($rawCommandName)->getName();
            } catch (CommandNotFoundException $e) {
                // we'll check command validity again later after plugins are loaded
                $commandName = false;
            } catch (\InvalidArgumentException $e) {
            }
        }

        // prompt user for dir change if no composer.json is present in current dir
        if (
            null === $newWorkDir
            // do not prompt for commands that can function without composer.json
            && !in_array($commandName, ['', 'list', 'init', 'about', 'help', 'diagnose', 'self-update', 'global', 'create-project', 'outdated'], true)
            && !file_exists(Factory::getComposerFile())
            // if use-parent-dir is disabled we should not prompt
            && ($useParentDirIfNoJsonAvailable = $this->getUseParentDirConfigValue()) !== false
            // config --file ... should not prompt
            && ($commandName !== 'config' || ($input->hasParameterOption('--file', true) === false && $input->hasParameterOption('-f', true) === false))
            // calling a command's help should not prompt
            && $input->hasParameterOption('--help', true) === false
            && $input->hasParameterOption('-h', true) === false
        ) {
            $dir = dirname(Platform::getCwd(true));
            $home = realpath(Platform::getEnv('HOME') ?: Platform::getEnv('USERPROFILE') ?: '/');

            // abort when we reach the home dir or top of the filesystem
            while (dirname($dir) !== $dir && $dir !== $home) {
                if (file_exists($dir.'/'.Factory::getComposerFile())) {
                    if ($useParentDirIfNoJsonAvailable !== true && !$io->isInteractive()) {
                        $io->writeError('<info>No composer.json in current directory, to use the one at '.$dir.' run interactively or set config.use-parent-dir to true</info>');
                        break;
                    }
                    if ($useParentDirIfNoJsonAvailable === true || $io->askConfirmation('<info>No composer.json in current directory, do you want to use the one at '.$dir.'?</info> [<comment>y,n</comment>]? ')) {
                        if ($useParentDirIfNoJsonAvailable === true) {
                            $io->writeError('<info>No composer.json in current directory, changing working directory to '.$dir.'</info>');
                        } else {
                            $io->writeError('<info>Always want to use the parent dir? Use "composer config --global use-parent-dir true" to change the default.</info>');
                        }
                        $oldWorkingDir = Platform::getCwd(true);
                        chdir($dir);
                    }
                    break;
                }
                $dir = dirname($dir);
            }
            unset($dir, $home);
        }

        $needsSudoCheck = !Platform::isWindows()
            && function_exists('exec')
            && !Platform::getEnv('COMPOSER_ALLOW_SUPERUSER')
            && !Platform::isDocker();
        $isNonAllowedRoot = false;

        // Clobber sudo credentials if COMPOSER_ALLOW_SUPERUSER is not set before loading plugins
        if ($needsSudoCheck) {
            $isNonAllowedRoot = $this->isRunningAsRoot();

            if ($isNonAllowedRoot) {
                if ($uid = (int) Platform::getEnv('SUDO_UID')) {
                    // Silently clobber any sudo credentials on the invoking user to avoid privilege escalations later on
                    // ref. https://github.com/composer/composer/issues/5119
                    Silencer::call('exec', "sudo -u \\#{$uid} sudo -K > /dev/null 2>&1");
                }
            }

            // Silently clobber any remaining sudo leases on the current user as well to avoid privilege escalations
            Silencer::call('exec', 'sudo -K > /dev/null 2>&1');
        }

        // avoid loading plugins/initializing the Composer instance earlier than necessary if no plugin command is needed
        // if showing the version, we never need plugin commands
        $mayNeedPluginCommand = false === $input->hasParameterOption(['--version', '-V'])
            && (
                // not a composer command, so try loading plugin ones
                false === $commandName
                // list command requires plugin commands to show them
                || in_array($commandName, ['', 'list', 'help'], true)
                // autocompletion requires plugin commands but if we are running as root without COMPOSER_ALLOW_SUPERUSER
                // we'd rather not autocomplete plugins than abort autocompletion entirely, so we avoid loading plugins in this case
                || ($commandName === '_complete' && !$isNonAllowedRoot)
            );

        $mayNeedScriptCommand = $mayNeedPluginCommand || $commandName === 'run-script' || $rawCommandName !== $commandName;

        if ($mayNeedPluginCommand && !$this->disablePluginsByDefault && !$this->hasPluginCommands) {
            // at this point plugins are needed, so if we are running as root and it is not allowed we need to prompt
            // if interactive, and abort otherwise
            if ($isNonAllowedRoot) {
                $io->writeError('<warning>Do not run Composer as root/super user! See https://getcomposer.org/root for details</warning>');

                if ($io->isInteractive() && $io->askConfirmation('<info>Continue as root/super user</info> [<comment>yes</comment>]? ')) {
                    // avoid a second prompt later
                    $isNonAllowedRoot = false;
                } else {
                    $io->writeError('<warning>Aborting as no plugin should be loaded if running as super user is not explicitly allowed</warning>');

                    return 1;
                }
            }

            try {
                foreach ($this->getPluginCommands() as $command) {
                    if ($this->has($command->getName())) {
                        $io->writeError('<warning>Plugin command '.$command->getName().' ('.get_class($command).') would override a Composer command and has been skipped</warning>');
                    } else {
                        // Compatibility layer for symfony/console <7.4
                        // @phpstan-ignore method.notFound, function.alreadyNarrowedType
                        method_exists($this, 'addCommand') ? $this->addCommand($command) : $this->add($command);
                    }
                }
            } catch (NoSslException $e) {
                // suppress these as they are not relevant at this point
            } catch (ParsingException $e) {
                $details = $e->getDetails();

                $file = realpath(Factory::getComposerFile());

                $line = null;
                if ($details && isset($details['line'])) {
                    $line = $details['line'];
                }

                $ghe = new GithubActionError($this->io);
                $ghe->emit($e->getMessage(), $file, $line);

                throw $e;
            }

            $this->hasPluginCommands = true;
        }

        if (!$this->disablePluginsByDefault && $isNonAllowedRoot && !$io->isInteractive()) {
            $io->writeError('<error>Composer plugins have been disabled for safety in this non-interactive session.</error>');
            $io->writeError('<error>Set COMPOSER_ALLOW_SUPERUSER=1 if you want to allow plugins to run as root/super user.</error>');
            $this->disablePluginsByDefault = true;
        }

        // determine command name to be executed incl plugin commands, and check if it's a proxy command
        $isProxyCommand = false;
        if ($name = $this->getCommandNameBeforeBinding($input)) {
            try {
                $command = $this->find($name);
                $commandName = $command->getName();
                $isProxyCommand = ($command instanceof Command\BaseCommand && $command->isProxyCommand());
            } catch (\InvalidArgumentException $e) {
            }
        }

        if (!$isProxyCommand) {
            $io->writeError(sprintf(
                'Running %s (%s) with %s on %s',
                Composer::getVersion(),
                Composer::RELEASE_DATE,
                defined('HHVM_VERSION') ? 'HHVM '.HHVM_VERSION : 'PHP '.PHP_VERSION,
                function_exists('php_uname') ? php_uname('s') . ' / ' . php_uname('r') : 'Unknown OS'
            ), true, IOInterface::DEBUG);

            if (\PHP_VERSION_ID < 70205) {
                $io->writeError('<warning>Composer supports PHP 7.2.5 and above, you will most likely encounter problems with your PHP '.PHP_VERSION.'. Upgrading is strongly recommended but you can use Composer 2.2.x LTS as a fallback.</warning>');
            }

            if (XdebugHandler::isXdebugActive() && !Platform::getEnv('COMPOSER_DISABLE_XDEBUG_WARN')) {
                $io->writeError('<warning>Composer is operating slower than normal because you have Xdebug enabled. See https://getcomposer.org/xdebug</warning>');
            }

            if (defined('COMPOSER_DEV_WARNING_TIME') && $commandName !== 'self-update' && $commandName !== 'selfupdate' && time() > COMPOSER_DEV_WARNING_TIME) {
                $io->writeError(sprintf('<warning>Warning: This development build of Composer is over 60 days old. It is recommended to update it by running "%s self-update" to get the latest version.</warning>', $_SERVER['PHP_SELF']));
            }

            if ($isNonAllowedRoot) {
                if ($commandName !== 'self-update' && $commandName !== 'selfupdate' && $commandName !== '_complete') {
                    $io->writeError('<warning>Do not run Composer as root/super user! See https://getcomposer.org/root for details</warning>');

                    if ($io->isInteractive()) {
                        if (!$io->askConfirmation('<info>Continue as root/super user</info> [<comment>yes</comment>]? ')) {
                            return 1;
                        }
                    }
                }
            }

            // Check system temp folder for usability as it can cause weird runtime issues otherwise
            Silencer::call(static function () use ($io): void {
                $pid = function_exists('getmypid') ? getmypid() . '-' : '';
                $tempfile = sys_get_temp_dir() . '/temp-' . $pid . bin2hex(random_bytes(5));
                if (!(file_put_contents($tempfile, __FILE__) && (file_get_contents($tempfile) === __FILE__) && unlink($tempfile) && !file_exists($tempfile))) {
                    $io->writeError(sprintf('<error>PHP temp directory (%s) does not exist or is not writable to Composer. Set sys_temp_dir in your php.ini</error>', sys_get_temp_dir()));
                }
            });

            // add non-standard scripts as own commands
            $file = Factory::getComposerFile();
            if ($mayNeedScriptCommand && is_file($file) && Filesystem::isReadable($file) && is_array($composerJson = json_decode(file_get_contents($file), true))) {
                if (isset($composerJson['scripts']) && is_array($composerJson['scripts'])) {
                    foreach ($composerJson['scripts'] as $script => $dummy) {
                        if (!defined('Composer\Script\ScriptEvents::'.str_replace('-', '_', strtoupper($script)))) {
                            if ($this->has($script)) {
                                $io->writeError('<warning>A script named '.$script.' would override a Composer command and has been skipped</warning>');
                            } else {
                                $description = 'Runs the '.$script.' script as defined in composer.json';

                                if (isset($composerJson['scripts-descriptions'][$script])) {
                                    $description = $composerJson['scripts-descriptions'][$script];
                                }

                                $aliases = $composerJson['scripts-aliases'][$script] ?? [];

                                $composer = $this->getComposer(false);
                                if ($composer !== null) {
                                    $rootPackage = $composer->getPackage();
                                    $generator = $composer->getAutoloadGenerator();

                                    $packageMap = $generator->buildPackageMap($composer->getInstallationManager(), $rootPackage, []);
                                    $map = $generator->parseAutoloads($packageMap, $rootPackage);

                                    $loader = $generator->createLoader($map, $composer->getConfig()->get('vendor-dir'));
                                    $loader->register(false);
                                }

                                // if the command is not an array of commands, and points to a valid Command subclass, import its details directly
                                if (is_string($dummy) && class_exists($dummy) && is_subclass_of($dummy, SymfonyCommand::class)) {
                                    if (is_subclass_of($dummy, SingleCommandApplication::class)) {
                                        $io->writeError('<warning>The script named '.$script.' extends SingleCommandApplication which is not compatible with Composer 2.9+, make sure you extend Symfony\Component\Console\Command instead.</warning>');
                                    }

                                    $cmd = new $dummy($script);

                                    // makes sure the command is find()'able by the name defined in composer.json, and the name isn't overridden in its configure()
                                    if ($cmd->getName() !== '' && $cmd->getName() !== null && $cmd->getName() !== $script) {
                                        $io->writeError('<warning>The script named '.$script.' in composer.json has a mismatched name in its class definition. For consistency, either use the same name, or do not define one inside the class.</warning>');
                                        $cmd->setName($script); // override it with the defined script name
                                    }

                                    if ($cmd->getDescription() === '' && is_string($description)) {
                                        $cmd->setDescription($description);
                                    }
                                } else {
                                    // fallback to usual aliasing behavior
                                    $cmd = new Command\ScriptAliasCommand($script, $description, $aliases);
                                }

                                // Compatibility layer for symfony/console <7.4
                                // @phpstan-ignore method.notFound, function.alreadyNarrowedType
                                method_exists($this, 'addCommand') ? $this->addCommand($cmd) : $this->add($cmd);
                            }
                        }
                    }
                }
            }
        }

        try {
            if ($input->hasParameterOption('--profile')) {
                $startTime = microtime(true);
                $this->io->enableDebugging($startTime);
            }

            $result = parent::doRun($input, $output);

            if (true === $input->hasParameterOption(['--version', '-V'], true)) {
                $io->writeError(sprintf('<info>PHP</info> version <comment>%s</comment> (%s)', \PHP_VERSION, \PHP_BINARY));
                $io->writeError('Run the "diagnose" command to get more detailed diagnostics output.');
            }

            // chdir back to $oldWorkingDir if set
            if (isset($oldWorkingDir) && '' !== $oldWorkingDir) {
                Silencer::call('chdir', $oldWorkingDir);
            }

            if (isset($startTime)) {
                $io->writeError('<info>Memory usage: '.round(memory_get_usage() / 1024 / 1024, 2).'MiB (peak: '.round(memory_get_peak_usage() / 1024 / 1024, 2).'MiB), time: '.round(microtime(true) - $startTime, 2).'s</info>');
            }

            return $result;
        } catch (ScriptExecutionException $e) {
            if ($this->getDisablePluginsByDefault() && $this->isRunningAsRoot() && !$this->io->isInteractive()) {
                $io->writeError('<error>Plugins have been disabled automatically as you are running as root, this may be the cause of the script failure.</error>', true, IOInterface::QUIET);
                $io->writeError('<error>See also https://getcomposer.org/root</error>', true, IOInterface::QUIET);
            }

            return $e->getCode();
        } catch (\Throwable $e) {
            $ghe = new GithubActionError($this->io);
            $ghe->emit($e->getMessage());

            $this->hintCommonErrors($e, $output);

            // symfony/console <6.4 does not handle \Error subtypes so we have to renderThrowable ourselves
            // instead of rethrowing those for consumption by the parent class
            // can be removed when Composer supports PHP 8.1+
            if (!method_exists($this, 'setCatchErrors') && !$e instanceof \Exception) {
                if ($output instanceof ConsoleOutputInterface) {
                    $this->renderThrowable($e, $output->getErrorOutput());
                } else {
                    $this->renderThrowable($e, $output);
                }

                return max(1, $e->getCode());
            }

            // override TransportException's code for the purpose of parent::run() using it as process exit code
            // as http error codes are all beyond the 255 range of permitted exit codes
            if ($e instanceof TransportException) {
                $reflProp = new \ReflectionProperty($e, 'code');
                (\PHP_VERSION_ID < 80100) and $reflProp->setAccessible(true);
                $reflProp->setValue($e, Installer::ERROR_TRANSPORT_EXCEPTION);
            }

            throw $e;
        } finally {
            restore_error_handler();
        }
    }

    /**
     * @throws RuntimeException
     */
    private function getNewWorkingDir(InputInterface $input): ?string
    {
        /** @var string|null $workingDir */
        $workingDir = $input->getParameterOption(['--working-dir', '-d'], null, true);
        if (null !== $workingDir && !is_dir($workingDir)) {
            throw new RuntimeException('Invalid working directory specified, '.$workingDir.' does not exist.');
        }

        return $workingDir;
    }

    private function hintCommonErrors(\Throwable $exception, OutputInterface $output): void
    {
        $io = $this->getIO();

        if ((get_class($exception) === LogicException::class || $exception instanceof \Error) && $output->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) {
            $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
        }

        Silencer::suppress();
        try {
            $composer = $this->getComposer(false, true);
            if (null !== $composer && function_exists('disk_free_space')) {
                $config = $composer->getConfig();

                $minSpaceFree = 100 * 1024 * 1024;
                if ((($df = disk_free_space($dir = $config->get('home'))) !== false && $df < $minSpaceFree)
                    || (($df = disk_free_space($dir = $config->get('vendor-dir'))) !== false && $df < $minSpaceFree)
                    || (($df = disk_free_space($dir = sys_get_temp_dir())) !== false && $df < $minSpaceFree)
                ) {
                    $io->writeError('<error>The disk hosting '.$dir.' has less than 100MiB of free space, this may be the cause of the following exception</error>', true, IOInterface::QUIET);
                }
            }
        } catch (\Exception $e) {
        }
        Silencer::restore();

        if ($exception instanceof TransportException && str_contains($exception->getMessage(), 'Unable to use a proxy')) {
            $io->writeError('<error>The following exception indicates your proxy is misconfigured</error>', true, IOInterface::QUIET);
            $io->writeError('<error>Check https://getcomposer.org/doc/faqs/how-to-use-composer-behind-a-proxy.md for details</error>', true, IOInterface::QUIET);
        }

        if (Platform::isWindows() && $exception instanceof TransportException && str_contains($exception->getMessage(), 'unable to get local issuer certificate')) {
            $avastDetect = glob('C:\Program Files\Avast*');
            if (is_array($avastDetect) && count($avastDetect) !== 0) {
                $io->writeError('<error>The following exception indicates a possible issue with the Avast Firewall</error>', true, IOInterface::QUIET);
                $io->writeError('<error>Check https://getcomposer.org/local-issuer for details</error>', true, IOInterface::QUIET);
            } else {
                $io->writeError('<error>The following exception indicates a possible issue with a Firewall/Antivirus</error>', true, IOInterface::QUIET);
                $io->writeError('<error>Check https://getcomposer.org/local-issuer for details</error>', true, IOInterface::QUIET);
            }
        }

        if (Platform::isWindows() && false !== strpos($exception->getMessage(), 'The system cannot find the path specified')) {
            $io->writeError('<error>The following exception may be caused by a stale entry in your cmd.exe AutoRun</error>', true, IOInterface::QUIET);
            $io->writeError('<error>Check https://getcomposer.org/doc/articles/troubleshooting.md#-the-system-cannot-find-the-path-specified-windows- for details</error>', true, IOInterface::QUIET);
        }

        if (false !== strpos($exception->getMessage(), 'fork failed - Cannot allocate memory')) {
            $io->writeError('<error>The following exception is caused by a lack of memory or swap, or not having swap configured</error>', true, IOInterface::QUIET);
            $io->writeError('<error>Check https://getcomposer.org/doc/articles/troubleshooting.md#proc-open-fork-failed-errors for details</error>', true, IOInterface::QUIET);
        }

        if ($exception instanceof ProcessTimedOutException) {
            $io->writeError('<error>The following exception is caused by a process timeout</error>', true, IOInterface::QUIET);
            $io->writeError('<error>Check https://getcomposer.org/doc/06-config.md#process-timeout for details</error>', true, IOInterface::QUIET);
        }

        if ($this->getDisablePluginsByDefault() && $this->isRunningAsRoot() && !$this->io->isInteractive()) {
            $io->writeError('<error>Plugins have been disabled automatically as you are running as root, this may be the cause of the following exception. See also https://getcomposer.org/root</error>', true, IOInterface::QUIET);
        } elseif ($exception instanceof CommandNotFoundException && $this->getDisablePluginsByDefault()) {
            $io->writeError('<error>Plugins have been disabled, which may be why some commands are missing, unless you made a typo</error>', true, IOInterface::QUIET);
        }

        $hints = HttpDownloader::getExceptionHints($exception);
        if (null !== $hints && count($hints) > 0) {
            foreach ($hints as $hint) {
                $io->writeError($hint, true, IOInterface::QUIET);
            }
        }
    }

    /**
     * @throws JsonValidationException
     * @throws \InvalidArgumentException
     * @return ?Composer If $required is true then the return value is guaranteed
     */
    public function getComposer(bool $required = true, ?bool $disablePlugins = null, ?bool $disableScripts = null): ?Composer
    {
        if (null === $disablePlugins) {
            $disablePlugins = $this->disablePluginsByDefault;
        }
        if (null === $disableScripts) {
            $disableScripts = $this->disableScriptsByDefault;
        }

        if (null === $this->composer) {
            try {
                $this->composer = Factory::create(Platform::isInputCompletionProcess() ? new NullIO() : $this->io, null, $disablePlugins, $disableScripts);
            } catch (\InvalidArgumentException $e) {
                if ($required) {
                    $this->io->writeError($e->getMessage());
                    if ($this->areExceptionsCaught()) {
                        exit(1);
                    }
                    throw $e;
                }
            } catch (JsonValidationException $e) {
                if ($required) {
                    throw $e;
                }
            } catch (RuntimeException $e) {
                if ($required) {
                    throw $e;
                }
            }
        }

        return $this->composer;
    }

    /**
     * Removes the cached composer instance
     */
    public function resetComposer(): void
    {
        $this->composer = null;
        if (method_exists($this->getIO(), 'resetAuthentications')) {
            $this->getIO()->resetAuthentications();
        }
    }

    public function getIO(): IOInterface
    {
        return $this->io;
    }

    public function getHelp(): string
    {
        return self::$logo . parent::getHelp();
    }

    /**
     * Initializes all the composer commands.
     * @return SymfonyCommand[]
     */
    protected function getDefaultCommands(): array
    {
        return array_merge(parent::getDefaultCommands(), [
            new Command\AboutCommand(),
            new Command\ConfigCommand(),
            new Command\DependsCommand(),
            new Command\ProhibitsCommand(),
            new Command\InitCommand(),
            new Command\InstallCommand(),
            new Command\CreateProjectCommand(),
            new Command\UpdateCommand(),
            new Command\SearchCommand(),
            new Command\ValidateCommand(),
            new Command\AuditCommand(),
            new Command\ShowCommand(),
            new Command\SuggestsCommand(),
            new Command\RequireCommand(),
            new Command\DumpAutoloadCommand(),
            new Command\StatusCommand(),
            new Command\ArchiveCommand(),
            new Command\DiagnoseCommand(),
            new Command\RunScriptCommand(),
            new Command\LicensesCommand(),
            new Command\GlobalCommand(),
            new Command\ClearCacheCommand(),
            new Command\RemoveCommand(),
            new Command\HomeCommand(),
            new Command\ExecCommand(),
            new Command\OutdatedCommand(),
            new Command\CheckPlatformReqsCommand(),
            new Command\FundCommand(),
            new Command\ReinstallCommand(),
            new Command\BumpCommand(),
            new Command\RepositoryCommand(),
            new Command\SelfUpdateCommand(),
        ]);
    }

    /**
     * This ensures we can find the correct command name even if a global input option is present before it
     *
     * e.g. "composer -d foo bar" should detect bar as the command name, and not foo
     */
    private function getCommandNameBeforeBinding(InputInterface $input): ?string
    {
        $input = clone $input;
        try {
            // Makes ArgvInput::getFirstArgument() able to distinguish an option from an argument.
            $input->bind($this->getDefinition());
        } catch (ExceptionInterface $e) {
            // Errors must be ignored, full binding/validation happens later when the command is known.
        }

        return $input->getFirstArgument();
    }

    public function getLongVersion(): string
    {
        $branchAliasString = '';
        if (Composer::BRANCH_ALIAS_VERSION && Composer::BRANCH_ALIAS_VERSION !== '@package_branch_alias_version'.'@') {
            $branchAliasString = sprintf(' (%s)', Composer::BRANCH_ALIAS_VERSION);
        }

        return sprintf(
            '<info>%s</info> version <comment>%s%s</comment> %s',
            $this->getName(),
            $this->getVersion(),
            $branchAliasString,
            Composer::RELEASE_DATE
        );
    }

    protected function getDefaultInputDefinition(): InputDefinition
    {
        $definition = parent::getDefaultInputDefinition();
        $definition->addOption(new InputOption('--profile', null, InputOption::VALUE_NONE, 'Display timing and memory usage information'));
        $definition->addOption(new InputOption('--no-plugins', null, InputOption::VALUE_NONE, 'Whether to disable plugins.'));
        $definition->addOption(new InputOption('--no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'));
        $definition->addOption(new InputOption('--working-dir', '-d', InputOption::VALUE_REQUIRED, 'If specified, use the given directory as working directory.'));
        $definition->addOption(new InputOption('--no-cache', null, InputOption::VALUE_NONE, 'Prevent use of the cache'));

        return $definition;
    }

    /**
     * @return Command\BaseCommand[]
     */
    private function getPluginCommands(): array
    {
        $commands = [];

        $composer = $this->getComposer(false, false);
        if (null === $composer) {
            $composer = Factory::createGlobal($this->io, $this->disablePluginsByDefault, $this->disableScriptsByDefault);
        }

        if (null !== $composer) {
            $pm = $composer->getPluginManager();
            foreach ($pm->getPluginCapabilities('Composer\Plugin\Capability\CommandProvider', ['composer' => $composer, 'io' => $this->io]) as $capability) {
                $newCommands = $capability->getCommands();
                if (!is_array($newCommands)) {
                    throw new \UnexpectedValueException('Plugin capability '.get_class($capability).' failed to return an array from getCommands');
                }
                foreach ($newCommands as $command) {
                    if (!$command instanceof Command\BaseCommand) {
                        throw new \UnexpectedValueException('Plugin capability '.get_class($capability).' returned an invalid value, we expected an array of Composer\Command\BaseCommand objects');
                    }
                }
                $commands = array_merge($commands, $newCommands);
            }
        }

        return $commands;
    }

    /**
     * Get the working directory at startup time
     *
     * @return string|false
     */
    public function getInitialWorkingDirectory()
    {
        return $this->initialWorkingDirectory;
    }

    public function getDisablePluginsByDefault(): bool
    {
        return $this->disablePluginsByDefault;
    }

    public function getDisableScriptsByDefault(): bool
    {
        return $this->disableScriptsByDefault;
    }

    /**
     * @return 'prompt'|bool
     */
    private function getUseParentDirConfigValue()
    {
        $config = Factory::createConfig($this->io);

        return $config->get('use-parent-dir');
    }

    private function isRunningAsRoot(): bool
    {
        return function_exists('posix_getuid') && posix_getuid() === 0;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Console\Input;

use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Completion\Suggestion;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Input\InputOption as BaseInputOption;

/**
 * Backport suggested values definition from symfony/console 6.1+
 *
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 *
 * @internal
 *
 * TODO symfony/console:6.1 drop when PHP 8.1 / symfony 6.1+ can be required
 */
class InputOption extends BaseInputOption
{
    /**
     * @var list<string>|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion>
     */
    private $suggestedValues;

    /**
     * @param string|string[]|null                $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
     * @param int|null                            $mode     The option mode: One of the VALUE_* constants
     * @param string|bool|int|float|string[]|null $default  The default value (must be null for self::VALUE_NONE)
     * @param list<string>|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion> $suggestedValues The values used for input completionnull for self::VALUE_NONE)
     *
     * @throws InvalidArgumentException If option mode is invalid or incompatible
     */
    public function __construct(string $name, $shortcut = null, ?int $mode = null, string $description = '', $default = null, $suggestedValues = [])
    {
        parent::__construct($name, $shortcut, $mode, $description, $default);

        $this->suggestedValues = $suggestedValues;

        if ([] !== $suggestedValues && !$this->acceptValue()) {
            throw new LogicException('Cannot set suggested values if the option does not accept a value.');
        }
    }

    /**
     * Adds suggestions to $suggestions for the current completion input.
     *
     * @see Command::complete()
     */
    public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
    {
        $values = $this->suggestedValues;
        if ($values instanceof \Closure && !\is_array($values = $values($input, $suggestions))) { // @phpstan-ignore function.impossibleType
            throw new LogicException(sprintf('Closure for argument "%s" must return an array. Got "%s".', $this->getName(), get_debug_type($values)));
        }
        if ([] !== $values) {
            $suggestions->suggestValues($values);
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Console\Input;

use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Completion\Suggestion;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Input\InputArgument as BaseInputArgument;

/**
 * Backport suggested values definition from symfony/console 6.1+
 *
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 *
 * @internal
 *
 * TODO symfony/console:6.1 drop when PHP 8.1 / symfony 6.1+ can be required
 */
class InputArgument extends BaseInputArgument
{
    /**
     * @var list<string>|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion>
     */
    private $suggestedValues;

    /**
     * @param string                              $name        The argument name
     * @param int|null                            $mode        The argument mode: self::REQUIRED or self::OPTIONAL
     * @param string                              $description A description text
     * @param string|bool|int|float|string[]|null $default     The default value (for self::OPTIONAL mode only)
     * @param list<string>|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion> $suggestedValues The values used for input completion
     *
     * @throws InvalidArgumentException When argument mode is not valid
     */
    public function __construct(string $name, ?int $mode = null, string $description = '', $default = null, $suggestedValues = [])
    {
        parent::__construct($name, $mode, $description, $default);

        $this->suggestedValues = $suggestedValues;
    }

    /**
     * Adds suggestions to $suggestions for the current completion input.
     *
     * @see Command::complete()
     */
    public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
    {
        $values = $this->suggestedValues;
        if ($values instanceof \Closure && !\is_array($values = $values($input, $suggestions))) { // @phpstan-ignore function.impossibleType
            throw new LogicException(sprintf('Closure for option "%s" must return an array. Got "%s".', $this->getName(), get_debug_type($values)));
        }
        if ([] !== $values) {
            $suggestions->suggestValues($values);
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Question;

use Composer\Pcre\Preg;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Question\Question;

/**
 * Represents a yes/no question
 * Enforces strict responses rather than non-standard answers counting as default
 * Based on Symfony\Component\Console\Question\ConfirmationQuestion
 *
 * @author Theo Tonge <theo@theotonge.co.uk>
 */
class StrictConfirmationQuestion extends Question
{
    /** @var non-empty-string */
    private $trueAnswerRegex;
    /** @var non-empty-string */
    private $falseAnswerRegex;

    /**
     * Constructor.s
     *
     * @param string $question         The question to ask to the user
     * @param bool   $default          The default answer to return, true or false
     * @param non-empty-string $trueAnswerRegex  A regex to match the "yes" answer
     * @param non-empty-string $falseAnswerRegex A regex to match the "no" answer
     */
    public function __construct(string $question, bool $default = true, string $trueAnswerRegex = '/^y(?:es)?$/i', string $falseAnswerRegex = '/^no?$/i')
    {
        parent::__construct($question, $default);

        $this->trueAnswerRegex = $trueAnswerRegex;
        $this->falseAnswerRegex = $falseAnswerRegex;
        $this->setNormalizer($this->getDefaultNormalizer());
        $this->setValidator($this->getDefaultValidator());
    }

    /**
     * Returns the default answer normalizer.
     */
    private function getDefaultNormalizer(): callable
    {
        $default = $this->getDefault();
        $trueRegex = $this->trueAnswerRegex;
        $falseRegex = $this->falseAnswerRegex;

        return static function ($answer) use ($default, $trueRegex, $falseRegex) {
            if (is_bool($answer)) {
                return $answer;
            }
            if (empty($answer) && !empty($default)) {
                return $default;
            }

            if (Preg::isMatch($trueRegex, $answer)) {
                return true;
            }

            if (Preg::isMatch($falseRegex, $answer)) {
                return false;
            }

            return null;
        };
    }

    /**
     * Returns the default answer validator.
     */
    private function getDefaultValidator(): callable
    {
        return static function ($answer): bool {
            if (!is_bool($answer)) {
                throw new InvalidArgumentException('Please answer yes, y, no, or n.');
            }

            return $answer;
        };
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer;

use Composer\Json\JsonFile;
use Composer\CaBundle\CaBundle;
use Composer\Pcre\Preg;
use Composer\Util\Git;
use Composer\Util\ProcessExecutor;
use Symfony\Component\Finder\Finder;
use Seld\PharUtils\Timestamps;
use Seld\PharUtils\Linter;

/**
 * The Compiler class compiles composer into a phar
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Compiler
{
    /** @var string */
    private $version;
    /** @var string */
    private $branchAliasVersion = '';
    /** @var \DateTime */
    private $versionDate;

    /**
     * Compiles composer into a single phar file
     *
     * @param string $pharFile The full path to the file to create
     *
     * @throws \RuntimeException
     */
    public function compile(string $pharFile = 'composer.phar'): void
    {
        if (file_exists($pharFile)) {
            unlink($pharFile);
        }

        $process = new ProcessExecutor();

        $command = Git::buildRevListCommand($process, ['-n1', '--format=%H', 'HEAD']);
        if (0 !== $process->execute($command, $output, dirname(__DIR__, 2))) {
            throw new \RuntimeException('Can\'t run git rev-list. You must ensure to run compile from composer git repository clone and that git binary is available.');
        }
        $this->version = trim(Git::parseRevListOutput($output, $process));

        $command = Git::buildRevListCommand($process, ['-n1', '--format=%ci', 'HEAD']);
        if (0 !== $process->execute($command, $output, dirname(__DIR__, 2))) {
            throw new \RuntimeException('Can\'t run git rev-list. You must ensure to run compile from composer git repository clone and that git binary is available.');
        }

        $this->versionDate = new \DateTime(trim(Git::parseRevListOutput($output, $process)));
        $this->versionDate->setTimezone(new \DateTimeZone('UTC'));

        if (0 === $process->execute(['git', 'describe', '--tags', '--exact-match', 'HEAD'], $output, dirname(__DIR__, 2))) {
            $this->version = trim($output);
        } else {
            // get branch-alias defined in composer.json for dev-main (if any)
            $localConfig = __DIR__.'/../../composer.json';
            $file = new JsonFile($localConfig);
            $localConfig = $file->read();
            if (isset($localConfig['extra']['branch-alias']['dev-main'])) {
                $this->branchAliasVersion = $localConfig['extra']['branch-alias']['dev-main'];
            }
        }

        if ('' === $this->version) {
            throw new \UnexpectedValueException('Version detection failed');
        }

        $phar = new \Phar($pharFile, 0, 'composer.phar');
        $phar->setSignatureAlgorithm(\Phar::SHA512);

        $phar->startBuffering();

        $finderSort = static function ($a, $b): int {
            return strcmp(strtr($a->getRealPath(), '\\', '/'), strtr($b->getRealPath(), '\\', '/'));
        };

        // Add Composer sources
        $finder = new Finder();
        $finder->files()
            ->ignoreVCS(true)
            ->name('*.php')
            ->notName('Compiler.php')
            ->notName('ClassLoader.php')
            ->notName('InstalledVersions.php')
            ->in(__DIR__.'/..')
            ->sort($finderSort)
        ;
        foreach ($finder as $file) {
            $this->addFile($phar, $file);
        }
        // Add runtime utilities separately to make sure they retains the docblocks as these will get copied into projects
        $this->addFile($phar, new \SplFileInfo(__DIR__ . '/Autoload/ClassLoader.php'), false);
        $this->addFile($phar, new \SplFileInfo(__DIR__ . '/InstalledVersions.php'), false);

        // Add Composer resources
        $finder = new Finder();
        $finder->files()
            ->in(__DIR__.'/../../res')
            ->sort($finderSort)
        ;
        foreach ($finder as $file) {
            $this->addFile($phar, $file, false);
        }

        // Add vendor files
        $finder = new Finder();
        $finder->files()
            ->ignoreVCS(true)
            ->notPath('/\/(composer\.(?:json|lock)|[A-Z]+\.md(?:own)?|\.gitignore|appveyor.yml|phpunit\.xml\.dist|phpstan\.neon\.dist|phpstan-config\.neon|phpstan-baseline\.neon|UPGRADE.*\.(?:md|txt))$/')
            ->notPath('/bin\/(jsonlint|validate-json|simple-phpunit|phpstan|phpstan\.phar)(\.bat)?$/')
            ->notPath('justinrainbow/json-schema/demo/')
            ->notPath('justinrainbow/json-schema/dist/')
            ->notPath('justinrainbow/json-schema/bin/')
            ->notPath('composer/pcre/extension.neon')
            ->notPath('composer/LICENSE')
            ->exclude('Tests')
            ->exclude('tests')
            ->exclude('docs')
            ->in(__DIR__.'/../../vendor/')
            ->sort($finderSort)
        ;

        $extraFiles = [];
        foreach ([
            __DIR__ . '/../../vendor/composer/installed.json',
            __DIR__ . '/../../vendor/composer/spdx-licenses/res/spdx-exceptions.json',
            __DIR__ . '/../../vendor/composer/spdx-licenses/res/spdx-licenses.json',
            CaBundle::getBundledCaBundlePath(),
            __DIR__ . '/../../vendor/symfony/console/Resources/bin/hiddeninput.exe',
            __DIR__ . '/../../vendor/symfony/console/Resources/completion.bash',
        ] as $file) {
            $extraFiles[$file] = realpath($file);
            if (!file_exists($file)) {
                throw new \RuntimeException('Extra file listed is missing from the filesystem: '.$file);
            }
        }
        $unexpectedFiles = [];

        foreach ($finder as $file) {
            if (false !== ($index = array_search($file->getRealPath(), $extraFiles, true))) {
                unset($extraFiles[$index]);
            } elseif (!Preg::isMatch('{(^LICENSE(?:\.txt)?$|\.php$)}', $file->getFilename())) {
                $unexpectedFiles[] = (string) $file;
            }

            if (Preg::isMatch('{\.php[\d.]*$}', $file->getFilename())) {
                $this->addFile($phar, $file);
            } else {
                $this->addFile($phar, $file, false);
            }
        }

        if (count($extraFiles) > 0) {
            throw new \RuntimeException('These files were expected but not added to the phar, they might be excluded or gone from the source package:'.PHP_EOL.var_export($extraFiles, true));
        }
        if (count($unexpectedFiles) > 0) {
            throw new \RuntimeException('These files were unexpectedly added to the phar, make sure they are excluded or listed in $extraFiles:'.PHP_EOL.var_export($unexpectedFiles, true));
        }

        // Add bin/composer
        $this->addComposerBin($phar);

        // Stubs
        $phar->setStub($this->getStub());

        $phar->stopBuffering();

        // disabled for interoperability with systems without gzip ext
        // $phar->compressFiles(\Phar::GZ);

        $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../LICENSE'), false);

        unset($phar);

        // re-sign the phar with reproducible timestamp / signature
        $util = new Timestamps($pharFile);
        $util->updateTimestamps($this->versionDate);
        $util->save($pharFile, \Phar::SHA512);

        Linter::lint($pharFile, [
            'vendor/symfony/console/Attribute/AsCommand.php',
            'vendor/symfony/polyfill-intl-grapheme/bootstrap80.php',
            'vendor/symfony/polyfill-intl-normalizer/bootstrap80.php',
            'vendor/symfony/polyfill-mbstring/bootstrap80.php',
            'vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php',
            'vendor/symfony/service-contracts/Attribute/SubscribedService.php',
            'vendor/symfony/polyfill-php84/Resources/stubs/Deprecated.php',
            'vendor/symfony/polyfill-php84/Resources/Deprecated.php',
            'vendor/symfony/polyfill-php84/Resources/RoundingMode.php',
            'vendor/symfony/polyfill-php84/bootstrap82.php',
        ]);
    }

    private function getRelativeFilePath(\SplFileInfo $file): string
    {
        $realPath = $file->getRealPath();
        $pathPrefix = dirname(__DIR__, 2).DIRECTORY_SEPARATOR;

        $pos = strpos($realPath, $pathPrefix);
        $relativePath = ($pos !== false) ? substr_replace($realPath, '', $pos, strlen($pathPrefix)) : $realPath;

        return strtr($relativePath, '\\', '/');
    }

    private function addFile(\Phar $phar, \SplFileInfo $file, bool $strip = true): void
    {
        $path = $this->getRelativeFilePath($file);
        $content = file_get_contents((string) $file);
        if ($strip) {
            $content = $this->stripWhitespace($content);
        } elseif ('LICENSE' === $file->getFilename()) {
            $content = "\n".$content."\n";
        }

        if ($path === 'src/Composer/Composer.php') {
            $content = strtr(
                $content,
                [
                    '@package_version@' => $this->version,
                    '@package_branch_alias_version@' => $this->branchAliasVersion,
                    '@release_date@' => $this->versionDate->format('Y-m-d H:i:s'),
                ]
            );
            $content = Preg::replace('{SOURCE_VERSION = \'[^\']+\';}', 'SOURCE_VERSION = \'\';', $content);
        }

        $phar->addFromString($path, $content);
    }

    private function addComposerBin(\Phar $phar): void
    {
        $content = file_get_contents(__DIR__.'/../../bin/composer');
        $content = Preg::replace('{^#!/usr/bin/env php\s*}', '', $content);
        $phar->addFromString('bin/composer', $content);
    }

    /**
     * Removes whitespace from a PHP source string while preserving line numbers.
     *
     * @param  string $source A PHP string
     * @return string The PHP string with the whitespace removed
     */
    private function stripWhitespace(string $source): string
    {
        if (!function_exists('token_get_all')) {
            return $source;
        }

        $output = '';
        foreach (token_get_all($source) as $token) {
            if (is_string($token)) {
                $output .= $token;
            } elseif (in_array($token[0], [T_COMMENT, T_DOC_COMMENT])) {
                $output .= str_repeat("\n", substr_count($token[1], "\n"));
            } elseif (T_WHITESPACE === $token[0]) {
                // reduce wide spaces
                $whitespace = Preg::replace('{[ \t]+}', ' ', $token[1]);
                // normalize newlines to \n
                $whitespace = Preg::replace('{(?:\r\n|\r|\n)}', "\n", $whitespace);
                // trim leading spaces
                $whitespace = Preg::replace('{\n +}', "\n", $whitespace);
                $output .= $whitespace;
            } else {
                $output .= $token[1];
            }
        }

        return $output;
    }

    private function getStub(): string
    {
        $stub = <<<'EOF'
#!/usr/bin/env php
<?php
/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view
 * the license that is located at the bottom of this file.
 */

// Avoid APC causing random fatal errors per https://github.com/composer/composer/issues/264
if (extension_loaded('apc') && filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN) && filter_var(ini_get('apc.cache_by_default'), FILTER_VALIDATE_BOOLEAN)) {
    if (version_compare(phpversion('apc'), '3.0.12', '>=')) {
        ini_set('apc.cache_by_default', 0);
    } else {
        fwrite(STDERR, 'Warning: APC <= 3.0.12 may cause fatal errors when running composer commands.'.PHP_EOL);
        fwrite(STDERR, 'Update APC, or set apc.enable_cli or apc.cache_by_default to 0 in your php.ini.'.PHP_EOL);
    }
}

if (!class_exists('Phar')) {
    echo 'PHP\'s phar extension is missing. Composer requires it to run. Enable the extension or recompile php without --disable-phar then try again.' . PHP_EOL;
    exit(1);
}

Phar::mapPhar('composer.phar');

EOF;

        // add warning once the phar is older than 60 days
        if (Preg::isMatch('{^[a-f0-9]+$}', $this->version)) {
            $warningTime = ((int) $this->versionDate->format('U')) + 60 * 86400;
            $stub .= "define('COMPOSER_DEV_WARNING_TIME', $warningTime);\n";
        }

        return $stub . <<<'EOF'
require 'phar://composer.phar/bin/composer';

__HALT_COMPILER();
EOF;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\IO;

use Composer\Config;
use Psr\Log\LoggerInterface;

/**
 * The Input/Output helper interface.
 *
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 */
interface IOInterface extends LoggerInterface
{
    public const QUIET = 1;
    public const NORMAL = 2;
    public const VERBOSE = 4;
    public const VERY_VERBOSE = 8;
    public const DEBUG = 16;

    /**
     * Is this input means interactive?
     *
     * @return bool
     */
    public function isInteractive();

    /**
     * Is this output verbose?
     *
     * @return bool
     */
    public function isVerbose();

    /**
     * Is the output very verbose?
     *
     * @return bool
     */
    public function isVeryVerbose();

    /**
     * Is the output in debug verbosity?
     *
     * @return bool
     */
    public function isDebug();

    /**
     * Is this output decorated?
     *
     * @return bool
     */
    public function isDecorated();

    /**
     * Writes a message to the output.
     *
     * @param string|string[] $messages  The message as an array of lines or a single string
     * @param bool            $newline   Whether to add a newline or not
     * @param int             $verbosity Verbosity level from the VERBOSITY_* constants
     *
     * @return void
     */
    public function write($messages, bool $newline = true, int $verbosity = self::NORMAL);

    /**
     * Writes a message to the error output.
     *
     * @param string|string[] $messages  The message as an array of lines or a single string
     * @param bool            $newline   Whether to add a newline or not
     * @param int             $verbosity Verbosity level from the VERBOSITY_* constants
     *
     * @return void
     */
    public function writeError($messages, bool $newline = true, int $verbosity = self::NORMAL);

    /**
     * Writes a message to the output, without formatting it.
     *
     * @param string|string[] $messages  The message as an array of lines or a single string
     * @param bool            $newline   Whether to add a newline or not
     * @param int             $verbosity Verbosity level from the VERBOSITY_* constants
     *
     * @return void
     */
    public function writeRaw($messages, bool $newline = true, int $verbosity = self::NORMAL);

    /**
     * Writes a message to the error output, without formatting it.
     *
     * @param string|string[] $messages  The message as an array of lines or a single string
     * @param bool            $newline   Whether to add a newline or not
     * @param int             $verbosity Verbosity level from the VERBOSITY_* constants
     *
     * @return void
     */
    public function writeErrorRaw($messages, bool $newline = true, int $verbosity = self::NORMAL);

    /**
     * Overwrites a previous message to the output.
     *
     * @param string|string[] $messages  The message as an array of lines or a single string
     * @param bool            $newline   Whether to add a newline or not
     * @param int             $size      The size of line
     * @param int             $verbosity Verbosity level from the VERBOSITY_* constants
     *
     * @return void
     */
    public function overwrite($messages, bool $newline = true, ?int $size = null, int $verbosity = self::NORMAL);

    /**
     * Overwrites a previous message to the error output.
     *
     * @param string|string[] $messages  The message as an array of lines or a single string
     * @param bool            $newline   Whether to add a newline or not
     * @param int             $size      The size of line
     * @param int             $verbosity Verbosity level from the VERBOSITY_* constants
     *
     * @return void
     */
    public function overwriteError($messages, bool $newline = true, ?int $size = null, int $verbosity = self::NORMAL);

    /**
     * Asks a question to the user.
     *
     * @param string $question The question to ask
     * @param string|bool|int|float|null $default  The default answer if none is given by the user
     *
     * @throws \RuntimeException If there is no data to read in the input stream
     * @return mixed       The user answer
     */
    public function ask(string $question, $default = null);

    /**
     * Asks a confirmation to the user.
     *
     * The question will be asked until the user answers by nothing, yes, or no.
     *
     * @param string $question The question to ask
     * @param bool   $default  The default answer if the user enters nothing
     *
     * @return bool true if the user has confirmed, false otherwise
     */
    public function askConfirmation(string $question, bool $default = true);

    /**
     * Asks for a value and validates the response.
     *
     * The validator receives the data to validate. It must return the
     * validated data when the data is valid and throw an exception
     * otherwise.
     *
     * @param string   $question  The question to ask
     * @param callable $validator A PHP callback
     * @param null|int $attempts  Max number of times to ask before giving up (default of null means infinite)
     * @param mixed    $default   The default answer if none is given by the user
     *
     * @throws \Exception When any of the validators return an error
     * @return mixed
     */
    public function askAndValidate(string $question, callable $validator, ?int $attempts = null, $default = null);

    /**
     * Asks a question to the user and hide the answer.
     *
     * @param string $question The question to ask
     *
     * @return string|null The answer
     */
    public function askAndHideAnswer(string $question);

    /**
     * Asks the user to select a value.
     *
     * @param string      $question     The question to ask
     * @param string[]    $choices      List of choices to pick from
     * @param bool|string $default      The default answer if the user enters nothing
     * @param bool|int    $attempts     Max number of times to ask before giving up (false by default, which means infinite)
     * @param string      $errorMessage Message which will be shown if invalid value from choice list would be picked
     * @param bool        $multiselect  Select more than one value separated by comma
     *
     * @throws \InvalidArgumentException
     *
     * @return int|string|list<string>|bool     The selected value or values (the key of the choices array)
     * @phpstan-return ($multiselect is true ? list<string> : string|int|bool)
     */
    public function select(string $question, array $choices, $default, $attempts = false, string $errorMessage = 'Value "%s" is invalid', bool $multiselect = false);

    /**
     * Get all authentication information entered.
     *
     * @return array<string, array{username: string|null, password: string|null}> The map of authentication data
     */
    public function getAuthentications();

    /**
     * Verify if the repository has a authentication information.
     *
     * @param string $repositoryName The unique name of repository
     *
     * @return bool
     */
    public function hasAuthentication(string $repositoryName);

    /**
     * Get the username and password of repository.
     *
     * @param string $repositoryName The unique name of repository
     *
     * @return array{username: string|null, password: string|null}
     */
    public function getAuthentication(string $repositoryName);

    /**
     * Set the authentication information for the repository.
     *
     * @param string      $repositoryName The unique name of repository
     * @param string      $username       The username
     * @param null|string $password       The password
     *
     * @return void
     */
    public function setAuthentication(string $repositoryName, string $username, ?string $password = null);

    /**
     * Loads authentications from a config instance
     *
     * @return void
     */
    public function loadConfiguration(Config $config);
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\IO;

use Composer\Pcre\Preg;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Output\StreamOutput;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Input\StreamableInputInterface;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Helper\HelperSet;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class BufferIO extends ConsoleIO
{
    public function __construct(string $input = '', int $verbosity = StreamOutput::VERBOSITY_NORMAL, ?OutputFormatterInterface $formatter = null)
    {
        $input = new StringInput($input);
        $input->setInteractive(false);

        $stream = fopen('php://memory', 'rw');
        if ($stream === false) {
            throw new \RuntimeException('Unable to open memory output stream');
        }
        $output = new StreamOutput($stream, $verbosity, $formatter !== null ? $formatter->isDecorated() : false, $formatter);

        parent::__construct($input, $output, new HelperSet([
            new QuestionHelper(),
        ]));
    }

    /**
     * @return string output
     */
    public function getOutput(): string
    {
        assert($this->output instanceof StreamOutput);
        fseek($this->output->getStream(), 0);

        $output = (string) stream_get_contents($this->output->getStream());

        $output = Preg::replaceCallback("{(?<=^|\n|\x08)(.+?)(\x08+)}", static function ($matches): string {
            $pre = strip_tags($matches[1]);

            if (strlen($pre) === strlen($matches[2])) {
                return '';
            }

            // TODO reverse parse the string, skipping span tags and \033\[([0-9;]+)m(.*?)\033\[0m style blobs
            return rtrim($matches[1])."\n";
        }, $output);

        return $output;
    }

    /**
     * @param string[] $inputs
     *
     * @see createStream
     */
    public function setUserInputs(array $inputs): void
    {
        if (!$this->input instanceof StreamableInputInterface) {
            throw new \RuntimeException('Setting the user inputs requires at least the version 3.2 of the symfony/console component.');
        }

        $this->input->setStream($this->createStream($inputs));
        $this->input->setInteractive(true);
    }

    /**
     * @param string[] $inputs
     *
     * @return resource stream
     */
    private function createStream(array $inputs)
    {
        $stream = fopen('php://memory', 'r+');
        if ($stream === false) {
            throw new \RuntimeException('Unable to open memory output stream');
        }

        foreach ($inputs as $input) {
            fwrite($stream, $input.PHP_EOL);
        }

        rewind($stream);

        return $stream;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\IO;

/**
 * IOInterface that is not interactive and never writes the output
 *
 * @author Christophe Coevoet <stof@notk.org>
 */
class NullIO extends BaseIO
{
    /**
     * @inheritDoc
     */
    public function isInteractive(): bool
    {
        return false;
    }

    /**
     * @inheritDoc
     */
    public function isVerbose(): bool
    {
        return false;
    }

    /**
     * @inheritDoc
     */
    public function isVeryVerbose(): bool
    {
        return false;
    }

    /**
     * @inheritDoc
     */
    public function isDebug(): bool
    {
        return false;
    }

    /**
     * @inheritDoc
     */
    public function isDecorated(): bool
    {
        return false;
    }

    /**
     * @inheritDoc
     */
    public function write($messages, bool $newline = true, int $verbosity = self::NORMAL): void
    {
    }

    /**
     * @inheritDoc
     */
    public function writeError($messages, bool $newline = true, int $verbosity = self::NORMAL): void
    {
    }

    /**
     * @inheritDoc
     */
    public function overwrite($messages, bool $newline = true, ?int $size = null, int $verbosity = self::NORMAL): void
    {
    }

    /**
     * @inheritDoc
     */
    public function overwriteError($messages, bool $newline = true, ?int $size = null, int $verbosity = self::NORMAL): void
    {
    }

    /**
     * @inheritDoc
     */
    public function ask($question, $default = null)
    {
        return $default;
    }

    /**
     * @inheritDoc
     */
    public function askConfirmation($question, $default = true): bool
    {
        return $default;
    }

    /**
     * @inheritDoc
     */
    public function askAndValidate($question, $validator, $attempts = null, $default = null)
    {
        return $default;
    }

    /**
     * @inheritDoc
     */
    public function askAndHideAnswer($question): ?string
    {
        return null;
    }

    /**
     * @inheritDoc
     */
    public function select($question, $choices, $default, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false)
    {
        return $default;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\IO;

use Composer\Pcre\Preg;
use Composer\Question\StrictConfirmationQuestion;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\Question;

/**
 * The Input/Output helper.
 *
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ConsoleIO extends BaseIO
{
    /** @var InputInterface */
    protected $input;
    /** @var OutputInterface */
    protected $output;
    /** @var HelperSet */
    protected $helperSet;
    /** @var string */
    protected $lastMessage = '';
    /** @var string */
    protected $lastMessageErr = '';

    /** @var float */
    private $startTime;
    /** @var array<IOInterface::*, OutputInterface::VERBOSITY_*> */
    private $verbosityMap;

    /**
     * Constructor.
     *
     * @param InputInterface  $input     The input instance
     * @param OutputInterface $output    The output instance
     * @param HelperSet       $helperSet The helperSet instance
     */
    public function __construct(InputInterface $input, OutputInterface $output, HelperSet $helperSet)
    {
        $this->input = $input;
        $this->output = $output;
        $this->helperSet = $helperSet;
        $this->verbosityMap = [
            self::QUIET => OutputInterface::VERBOSITY_QUIET,
            self::NORMAL => OutputInterface::VERBOSITY_NORMAL,
            self::VERBOSE => OutputInterface::VERBOSITY_VERBOSE,
            self::VERY_VERBOSE => OutputInterface::VERBOSITY_VERY_VERBOSE,
            self::DEBUG => OutputInterface::VERBOSITY_DEBUG,
        ];
    }

    /**
     * @return void
     */
    public function enableDebugging(float $startTime)
    {
        $this->startTime = $startTime;
    }

    /**
     * @inheritDoc
     */
    public function isInteractive()
    {
        return $this->input->isInteractive();
    }

    /**
     * @inheritDoc
     */
    public function isDecorated()
    {
        return $this->output->isDecorated();
    }

    /**
     * @inheritDoc
     */
    public function isVerbose()
    {
        return $this->output->isVerbose();
    }

    /**
     * @inheritDoc
     */
    public function isVeryVerbose()
    {
        return $this->output->isVeryVerbose();
    }

    /**
     * @inheritDoc
     */
    public function isDebug()
    {
        return $this->output->isDebug();
    }

    /**
     * @inheritDoc
     */
    public function write($messages, bool $newline = true, int $verbosity = self::NORMAL)
    {
        $messages = self::sanitize($messages);

        $this->doWrite($messages, $newline, false, $verbosity);
    }

    /**
     * @inheritDoc
     */
    public function writeError($messages, bool $newline = true, int $verbosity = self::NORMAL)
    {
        $messages = self::sanitize($messages);

        $this->doWrite($messages, $newline, true, $verbosity);
    }

    /**
     * @inheritDoc
     */
    public function writeRaw($messages, bool $newline = true, int $verbosity = self::NORMAL)
    {
        $this->doWrite($messages, $newline, false, $verbosity, true);
    }

    /**
     * @inheritDoc
     */
    public function writeErrorRaw($messages, bool $newline = true, int $verbosity = self::NORMAL)
    {
        $this->doWrite($messages, $newline, true, $verbosity, true);
    }

    /**
     * @param string[]|string $messages
     */
    private function doWrite($messages, bool $newline, bool $stderr, int $verbosity, bool $raw = false): void
    {
        $sfVerbosity = $this->verbosityMap[$verbosity];
        if ($sfVerbosity > $this->output->getVerbosity()) {
            return;
        }

        if ($raw) {
            $sfVerbosity |= OutputInterface::OUTPUT_RAW;
        }

        if (null !== $this->startTime) {
            $memoryUsage = memory_get_usage() / 1024 / 1024;
            $timeSpent = microtime(true) - $this->startTime;
            $messages = array_map(static function ($message) use ($memoryUsage, $timeSpent): string {
                return sprintf('[%.1fMiB/%.2fs] %s', $memoryUsage, $timeSpent, $message);
            }, (array) $messages);
        }

        if (true === $stderr && $this->output instanceof ConsoleOutputInterface) {
            $this->output->getErrorOutput()->write($messages, $newline, $sfVerbosity);
            $this->lastMessageErr = implode($newline ? "\n" : '', (array) $messages);

            return;
        }

        $this->output->write($messages, $newline, $sfVerbosity);
        $this->lastMessage = implode($newline ? "\n" : '', (array) $messages);
    }

    /**
     * @inheritDoc
     */
    public function overwrite($messages, bool $newline = true, ?int $size = null, int $verbosity = self::NORMAL)
    {
        $this->doOverwrite($messages, $newline, $size, false, $verbosity);
    }

    /**
     * @inheritDoc
     */
    public function overwriteError($messages, bool $newline = true, ?int $size = null, int $verbosity = self::NORMAL)
    {
        $this->doOverwrite($messages, $newline, $size, true, $verbosity);
    }

    /**
     * @param string[]|string $messages
     */
    private function doOverwrite($messages, bool $newline, ?int $size, bool $stderr, int $verbosity): void
    {
        // messages can be an array, let's convert it to string anyway
        $messages = implode($newline ? "\n" : '', (array) $messages);

        // since overwrite is supposed to overwrite last message...
        if (!isset($size)) {
            // removing possible formatting of lastMessage with strip_tags
            $size = strlen(strip_tags($stderr ? $this->lastMessageErr : $this->lastMessage));
        }
        // ...let's fill its length with backspaces
        $this->doWrite(str_repeat("\x08", $size), false, $stderr, $verbosity);

        // write the new message
        $this->doWrite($messages, false, $stderr, $verbosity);

        // In cmd.exe on Win8.1 (possibly 10?), the line can not be cleared, so we need to
        // track the length of previous output and fill it with spaces to make sure the line is cleared.
        // See https://github.com/composer/composer/pull/5836 for more details
        $fill = $size - strlen(strip_tags($messages));
        if ($fill > 0) {
            // whitespace whatever has left
            $this->doWrite(str_repeat(' ', $fill), false, $stderr, $verbosity);
            // move the cursor back
            $this->doWrite(str_repeat("\x08", $fill), false, $stderr, $verbosity);
        }

        if ($newline) {
            $this->doWrite('', true, $stderr, $verbosity);
        }

        if ($stderr) {
            $this->lastMessageErr = $messages;
        } else {
            $this->lastMessage = $messages;
        }
    }

    /**
     * @return ProgressBar
     */
    public function getProgressBar(int $max = 0)
    {
        return new ProgressBar($this->getErrorOutput(), $max);
    }

    /**
     * @inheritDoc
     */
    public function ask($question, $default = null)
    {
        /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */
        $helper = $this->helperSet->get('question');
        $question = new Question(self::sanitize($question), is_string($default) ? self::sanitize($default) : $default);

        return $helper->ask($this->input, $this->getErrorOutput(), $question);
    }

    /**
     * @inheritDoc
     */
    public function askConfirmation($question, $default = true)
    {
        /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */
        $helper = $this->helperSet->get('question');
        $question = new StrictConfirmationQuestion(self::sanitize($question), is_string($default) ? self::sanitize($default) : $default);

        return $helper->ask($this->input, $this->getErrorOutput(), $question);
    }

    /**
     * @inheritDoc
     */
    public function askAndValidate($question, $validator, $attempts = null, $default = null)
    {
        /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */
        $helper = $this->helperSet->get('question');
        $question = new Question(self::sanitize($question), is_string($default) ? self::sanitize($default) : $default);
        $question->setValidator($validator);
        $question->setMaxAttempts($attempts);

        return $helper->ask($this->input, $this->getErrorOutput(), $question);
    }

    /**
     * @inheritDoc
     */
    public function askAndHideAnswer($question)
    {
        /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */
        $helper = $this->helperSet->get('question');
        $question = new Question(self::sanitize($question));
        $question->setHidden(true);

        return $helper->ask($this->input, $this->getErrorOutput(), $question);
    }

    /**
     * @inheritDoc
     */
    public function select($question, $choices, $default, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false)
    {
        /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */
        $helper = $this->helperSet->get('question');
        $question = new ChoiceQuestion(self::sanitize($question), self::sanitize($choices), is_string($default) ? self::sanitize($default) : $default);
        $question->setMaxAttempts($attempts ?: null); // IOInterface requires false, and Question requires null or int
        $question->setErrorMessage($errorMessage);
        $question->setMultiselect($multiselect);

        $result = $helper->ask($this->input, $this->getErrorOutput(), $question);

        $isAssoc = (bool) \count(array_filter(array_keys($choices), 'is_string'));
        if ($isAssoc) {
            return $result;
        }

        if (!is_array($result)) {
            return (string) array_search($result, $choices, true);
        }

        $results = [];
        foreach ($choices as $index => $choice) {
            if (in_array($choice, $result, true)) {
                $results[] = (string) $index;
            }
        }

        return $results;
    }

    public function getTable(): Table
    {
        return new Table($this->output);
    }

    private function getErrorOutput(): OutputInterface
    {
        if ($this->output instanceof ConsoleOutputInterface) {
            return $this->output->getErrorOutput();
        }

        return $this->output;
    }

    /**
     * Sanitize string to remove control characters
     *
     * If $allowNewlines is true, \x0A (\n) and \x0D\x0A (\r\n) are let through. Single \r are still sanitized away to prevent overwriting whole lines.
     *
     * All other control chars (except NULL bytes) as well as ANSI escape sequences are removed.
     *
     * Invalid unicode sequences are turned into question marks.
     *
     * @param string|iterable<string> $messages
     * @return string|array<string>
     * @phpstan-return ($messages is string ? string : array<string>)
     */
    public static function sanitize($messages, bool $allowNewlines = true)
    {
        // Match ANSI escape sequences:
        // - CSI (Control Sequence Introducer): ESC [ params intermediate final
        // - OSC (Operating System Command): ESC ] ... ESC \ or BEL
        // - Other ESC sequences: ESC followed by any character
        $escapePattern = '\x1B\[[\x30-\x3F]*[\x20-\x2F]*[\x40-\x7E]|\x1B\].*?(?:\x1B\\\\|\x07)|\x1B.';
        $pattern = $allowNewlines ? "{{$escapePattern}|[\x01-\x09\x0B\x0C\x0E-\x1A]|\r(?!\n)}u" : "{{$escapePattern}|[\x01-\x1A]}u";
        if (is_string($messages)) {
            $messages = self::ensureValidUtf8($messages);
            return Preg::replace($pattern, '', $messages);
        }

        $sanitized = [];
        foreach ($messages as $key => $message) {
            $message = self::ensureValidUtf8($message);
            $sanitized[$key] = Preg::replace($pattern, '', $message);
        }

        return $sanitized;
    }

    /**
     * Ensures a string is valid UTF-8, replacing invalid byte sequences with '?'
     */
    private static function ensureValidUtf8(string $string): string
    {
        // Quick check: if string is already valid UTF-8, return as-is
        if (function_exists('mb_check_encoding') && mb_check_encoding($string, 'UTF-8')) {
            return $string;
        }

        // Use mb_convert_encoding to replace invalid sequences with '?'
        // This makes it visible when data quality issues occur
        if (function_exists('mb_convert_encoding')) {
            return (string) mb_convert_encoding($string, 'UTF-8', 'UTF-8');
        }

        // Fallback to iconv if mbstring unavailable
        if (function_exists('iconv')) {
            $cleaned = @iconv('UTF-8', 'UTF-8//TRANSLIT', $string);
            if ($cleaned !== false) {
                return $cleaned;
            }
        }

        // Last resort: return as-is (should never happen - Composer requires mbstring OR iconv)
        return $string;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\IO;

use Composer\Config;
use Composer\Pcre\Preg;
use Composer\Util\ProcessExecutor;
use Composer\Util\Silencer;
use Psr\Log\LogLevel;

abstract class BaseIO implements IOInterface
{
    /** @var array<string, array{username: string|null, password: string|null}> */
    protected $authentications = [];

    /**
     * @inheritDoc
     */
    public function getAuthentications()
    {
        return $this->authentications;
    }

    /**
     * @return void
     */
    public function resetAuthentications()
    {
        $this->authentications = [];
    }

    /**
     * @inheritDoc
     */
    public function hasAuthentication($repositoryName)
    {
        return isset($this->authentications[$repositoryName]);
    }

    /**
     * @inheritDoc
     */
    public function getAuthentication($repositoryName)
    {
        if (isset($this->authentications[$repositoryName])) {
            return $this->authentications[$repositoryName];
        }

        return ['username' => null, 'password' => null];
    }

    /**
     * @inheritDoc
     */
    public function setAuthentication($repositoryName, $username, $password = null)
    {
        $this->authentications[$repositoryName] = ['username' => $username, 'password' => $password];
    }

    /**
     * @inheritDoc
     */
    public function writeRaw($messages, bool $newline = true, int $verbosity = self::NORMAL)
    {
        $this->write($messages, $newline, $verbosity);
    }

    /**
     * @inheritDoc
     */
    public function writeErrorRaw($messages, bool $newline = true, int $verbosity = self::NORMAL)
    {
        $this->writeError($messages, $newline, $verbosity);
    }

    /**
     * Check for overwrite and set the authentication information for the repository.
     *
     * @param string $repositoryName The unique name of repository
     * @param string $username       The username
     * @param string $password       The password
     *
     * @return void
     */
    protected function checkAndSetAuthentication(string $repositoryName, string $username, ?string $password = null)
    {
        if ($this->hasAuthentication($repositoryName)) {
            $auth = $this->getAuthentication($repositoryName);
            if ($auth['username'] === $username && $auth['password'] === $password) {
                return;
            }

            $this->writeError(
                sprintf(
                    "<warning>Warning: You should avoid overwriting already defined auth settings for %s.</warning>",
                    $repositoryName
                )
            );
        }
        $this->setAuthentication($repositoryName, $username, $password);
    }

    /**
     * @inheritDoc
     */
    public function loadConfiguration(Config $config)
    {
        $bitbucketOauth = $config->get('bitbucket-oauth');
        $githubOauth = $config->get('github-oauth');
        $gitlabOauth = $config->get('gitlab-oauth');
        $gitlabToken = $config->get('gitlab-token');
        $forgejoToken = $config->get('forgejo-token');
        $httpBasic = $config->get('http-basic');
        $bearerToken = $config->get('bearer');
        $customHeaders = $config->get('custom-headers');
        $clientCertificate = $config->get('client-certificate');

        // reload oauth tokens from config if available

        foreach ($bitbucketOauth as $domain => $cred) {
            $this->checkAndSetAuthentication($domain, $cred['consumer-key'], $cred['consumer-secret']);
        }

        foreach ($githubOauth as $domain => $token) {
            if ($domain !== 'github.com' && !in_array($domain, $config->get('github-domains'), true)) {
                $this->debug($domain.' is not in the configured github-domains, adding it implicitly as authentication is configured for this domain');
                $config->merge(['config' => ['github-domains' => array_merge($config->get('github-domains'), [$domain])]], 'implicit-due-to-auth');
            }

            // allowed chars for GH tokens are from https://github.blog/changelog/2021-03-04-authentication-token-format-updates/
            // plus dots which were at some point used for GH app integration tokens
            if (!Preg::isMatch('{^[.A-Za-z0-9_-]+$}', $token)) {
                throw new \UnexpectedValueException('Your github oauth token for '.$domain.' contains invalid characters.');
            }

            $this->checkAndSetAuthentication($domain, $token, 'x-oauth-basic');
        }

        foreach ($gitlabOauth as $domain => $token) {
            if ($domain !== 'gitlab.com' && !in_array($domain, $config->get('gitlab-domains'), true)) {
                $this->debug($domain.' is not in the configured gitlab-domains, adding it implicitly as authentication is configured for this domain');
                $config->merge(['config' => ['gitlab-domains' => array_merge($config->get('gitlab-domains'), [$domain])]], 'implicit-due-to-auth');
            }

            $token = is_array($token) ? $token["token"] : $token;
            $this->checkAndSetAuthentication($domain, $token, 'oauth2');
        }

        foreach ($gitlabToken as $domain => $token) {
            if ($domain !== 'gitlab.com' && !in_array($domain, $config->get('gitlab-domains'), true)) {
                $this->debug($domain.' is not in the configured gitlab-domains, adding it implicitly as authentication is configured for this domain');
                $config->merge(['config' => ['gitlab-domains' => array_merge($config->get('gitlab-domains'), [$domain])]], 'implicit-due-to-auth');
            }

            $username = is_array($token) ? $token["username"] : $token;
            $password = is_array($token) ? $token["token"] : 'private-token';
            $this->checkAndSetAuthentication($domain, $username, $password);
        }

        foreach ($forgejoToken as $domain => $cred) {
            if (!in_array($domain, $config->get('forgejo-domains'), true)) {
                $this->debug($domain.' is not in the configured forgejo-domains, adding it implicitly as authentication is configured for this domain');
                $config->merge(['config' => ['forgejo-domains' => array_merge($config->get('forgejo-domains'), [$domain])]], 'implicit-due-to-auth');
            }

            $this->checkAndSetAuthentication($domain, $cred['username'], $cred['token']);
        }

        // reload http basic credentials from config if available
        foreach ($httpBasic as $domain => $cred) {
            $this->checkAndSetAuthentication($domain, $cred['username'], $cred['password']);
        }

        foreach ($bearerToken as $domain => $token) {
            $this->checkAndSetAuthentication($domain, $token, 'bearer');
        }

        // load custom HTTP headers from config
        foreach ($customHeaders as $domain => $headers) {
            if ($headers !== null) {
                $this->checkAndSetAuthentication($domain, (string) json_encode($headers), 'custom-headers');
            }
        }

        // reload ssl client certificate credentials from config if available
        foreach ($clientCertificate as $domain => $cred) {
            $sslOptions = array_filter(
                [
                    'local_cert' => $cred['local_cert'] ?? null,
                    'local_pk' => $cred['local_pk'] ?? null,
                    'passphrase' => $cred['passphrase'] ?? null,
                ],
                static function (?string $value): bool { return $value !== null; }
            );
            if (!isset($sslOptions['local_cert'])) {
                $this->writeError(
                    sprintf(
                        '<warning>Warning: Client certificate configuration is missing key `local_cert` for %s.</warning>',
                        $domain
                    )
                );
                continue;
            }
            $this->checkAndSetAuthentication($domain, 'client-certificate', (string) json_encode($sslOptions));
        }

        // setup process timeout
        ProcessExecutor::setTimeout($config->get('process-timeout'));
    }

    /**
     * @param string|\Stringable $message
     */
    public function emergency($message, array $context = []): void
    {
        $this->log(LogLevel::EMERGENCY, $message, $context);
    }

    /**
     * @param string|\Stringable $message
     */
    public function alert($message, array $context = []): void
    {
        $this->log(LogLevel::ALERT, $message, $context);
    }

    /**
     * @param string|\Stringable $message
     */
    public function critical($message, array $context = []): void
    {
        $this->log(LogLevel::CRITICAL, $message, $context);
    }

    /**
     * @param string|\Stringable $message
     */
    public function error($message, array $context = []): void
    {
        $this->log(LogLevel::ERROR, $message, $context);
    }

    /**
     * @param string|\Stringable $message
     */
    public function warning($message, array $context = []): void
    {
        $this->log(LogLevel::WARNING, $message, $context);
    }

    /**
     * @param string|\Stringable $message
     */
    public function notice($message, array $context = []): void
    {
        $this->log(LogLevel::NOTICE, $message, $context);
    }

    /**
     * @param string|\Stringable $message
     */
    public function info($message, array $context = []): void
    {
        $this->log(LogLevel::INFO, $message, $context);
    }

    /**
     * @param string|\Stringable $message
     */
    public function debug($message, array $context = []): void
    {
        $this->log(LogLevel::DEBUG, $message, $context);
    }

    /**
     * @param mixed|LogLevel::* $level
     * @param string|\Stringable $message
     */
    public function log($level, $message, array $context = []): void
    {
        $message = (string) $message;

        if ($context !== []) {
            $json = Silencer::call('json_encode', $context, JSON_INVALID_UTF8_IGNORE | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
            if ($json !== false) {
                $message .= ' ' . $json;
            }
        }

        if (in_array($level, [LogLevel::EMERGENCY, LogLevel::ALERT, LogLevel::CRITICAL, LogLevel::ERROR])) {
            $this->writeError('<error>'.$message.'</error>');
        } elseif ($level === LogLevel::WARNING) {
            $this->writeError('<warning>'.$message.'</warning>');
        } elseif ($level === LogLevel::NOTICE) {
            $this->writeError('<info>'.$message.'</info>', true, self::VERBOSE);
        } elseif ($level === LogLevel::INFO) {
            $this->writeError('<info>'.$message.'</info>', true, self::VERY_VERBOSE);
        } else {
            $this->writeError($message, true, self::DEBUG);
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Script;

/**
 * The Script Events.
 *
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ScriptEvents
{
    /**
     * The PRE_INSTALL_CMD event occurs before the install command is executed.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    public const PRE_INSTALL_CMD = 'pre-install-cmd';

    /**
     * The POST_INSTALL_CMD event occurs after the install command is executed.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    public const POST_INSTALL_CMD = 'post-install-cmd';

    /**
     * The PRE_UPDATE_CMD event occurs before the update command is executed.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    public const PRE_UPDATE_CMD = 'pre-update-cmd';

    /**
     * The POST_UPDATE_CMD event occurs after the update command is executed.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    public const POST_UPDATE_CMD = 'post-update-cmd';

    /**
     * The PRE_STATUS_CMD event occurs before the status command is executed.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    public const PRE_STATUS_CMD = 'pre-status-cmd';

    /**
     * The POST_STATUS_CMD event occurs after the status command is executed.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    public const POST_STATUS_CMD = 'post-status-cmd';

    /**
     * The PRE_AUTOLOAD_DUMP event occurs before the autoload file is generated.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    public const PRE_AUTOLOAD_DUMP = 'pre-autoload-dump';

    /**
     * The POST_AUTOLOAD_DUMP event occurs after the autoload file has been generated.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    public const POST_AUTOLOAD_DUMP = 'post-autoload-dump';

    /**
     * The POST_ROOT_PACKAGE_INSTALL event occurs after the root package has been installed.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    public const POST_ROOT_PACKAGE_INSTALL = 'post-root-package-install';

    /**
     * The POST_CREATE_PROJECT event occurs after the create-project command has been executed.
     * Note: Event occurs after POST_INSTALL_CMD
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    public const POST_CREATE_PROJECT_CMD = 'post-create-project-cmd';

    /**
     * The PRE_ARCHIVE_CMD event occurs before the update command is executed.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    public const PRE_ARCHIVE_CMD = 'pre-archive-cmd';

    /**
     * The POST_ARCHIVE_CMD event occurs after the status command is executed.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    public const POST_ARCHIVE_CMD = 'post-archive-cmd';
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Script;

use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\EventDispatcher\Event as BaseEvent;

/**
 * The script event class
 *
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 * @author Nils Adermann <naderman@naderman.de>
 */
class Event extends BaseEvent
{
    /**
     * @var Composer The composer instance
     */
    private $composer;

    /**
     * @var IOInterface The IO instance
     */
    private $io;

    /**
     * @var bool Dev mode flag
     */
    private $devMode;

    /**
     * @var BaseEvent|null
     */
    private $originatingEvent;

    /**
     * Constructor.
     *
     * @param string $name The event name
     * @param Composer $composer The composer object
     * @param IOInterface $io The IOInterface object
     * @param bool $devMode Whether or not we are in dev mode
     * @param array<string|int|float|bool|null> $args Arguments passed by the user
     * @param mixed[] $flags Optional flags to pass data not as argument
     */
    public function __construct(string $name, Composer $composer, IOInterface $io, bool $devMode = false, array $args = [], array $flags = [])
    {
        parent::__construct($name, $args, $flags);
        $this->composer = $composer;
        $this->io = $io;
        $this->devMode = $devMode;
    }

    /**
     * Returns the composer instance.
     */
    public function getComposer(): Composer
    {
        return $this->composer;
    }

    /**
     * Returns the IO instance.
     */
    public function getIO(): IOInterface
    {
        return $this->io;
    }

    /**
     * Return the dev mode flag
     */
    public function isDevMode(): bool
    {
        return $this->devMode;
    }

    /**
     * Set the originating event.
     */
    public function getOriginatingEvent(): ?BaseEvent
    {
        return $this->originatingEvent;
    }

    /**
     * Set the originating event.
     *
     * @return $this
     */
    public function setOriginatingEvent(BaseEvent $event): self
    {
        $this->originatingEvent = $this->calculateOriginatingEvent($event);

        return $this;
    }

    /**
     * Returns the upper-most event in chain.
     */
    private function calculateOriginatingEvent(BaseEvent $event): BaseEvent
    {
        if ($event instanceof Event && $event->getOriginatingEvent()) {
            return $this->calculateOriginatingEvent($event->getOriginatingEvent());
        }

        return $event;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer;

use Composer\Package\RootPackageInterface;
use Composer\Util\Loop;
use Composer\Repository\RepositoryManager;
use Composer\Installer\InstallationManager;
use Composer\EventDispatcher\EventDispatcher;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PartialComposer
{
    /**
     * @var bool
     */
    private $global = false;

    /**
     * @var RootPackageInterface
     */
    private $package;

    /**
     * @var Loop
     */
    private $loop;

    /**
     * @var RepositoryManager
     */
    private $repositoryManager;

    /**
     * @var InstallationManager
     */
    private $installationManager;

    /**
     * @var Config
     */
    private $config;

    /**
     * @var EventDispatcher
     */
    private $eventDispatcher;

    public function setPackage(RootPackageInterface $package): void
    {
        $this->package = $package;
    }

    public function getPackage(): RootPackageInterface
    {
        return $this->package;
    }

    public function setConfig(Config $config): void
    {
        $this->config = $config;
    }

    public function getConfig(): Config
    {
        return $this->config;
    }

    public function setLoop(Loop $loop): void
    {
        $this->loop = $loop;
    }

    public function getLoop(): Loop
    {
        return $this->loop;
    }

    public function setRepositoryManager(RepositoryManager $manager): void
    {
        $this->repositoryManager = $manager;
    }

    public function getRepositoryManager(): RepositoryManager
    {
        return $this->repositoryManager;
    }

    public function setInstallationManager(InstallationManager $manager): void
    {
        $this->installationManager = $manager;
    }

    public function getInstallationManager(): InstallationManager
    {
        return $this->installationManager;
    }

    public function setEventDispatcher(EventDispatcher $eventDispatcher): void
    {
        $this->eventDispatcher = $eventDispatcher;
    }

    public function getEventDispatcher(): EventDispatcher
    {
        return $this->eventDispatcher;
    }

    public function isGlobal(): bool
    {
        return $this->global;
    }

    public function setGlobal(): void
    {
        $this->global = true;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Exception;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class IrrecoverableDownloadException extends \RuntimeException
{
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Exception;

/**
 * Specific exception for Composer\Util\HttpDownloader creation.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class NoSslException extends \RuntimeException
{
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer;

use Composer\Advisory\AuditConfig;
use Composer\Autoload\AutoloadGenerator;
use Composer\Console\GithubActionError;
use Composer\DependencyResolver\DefaultPolicy;
use Composer\DependencyResolver\LocalRepoTransaction;
use Composer\DependencyResolver\LockTransaction;
use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\UninstallOperation;
use Composer\DependencyResolver\PoolOptimizer;
use Composer\DependencyResolver\Pool;
use Composer\DependencyResolver\Request;
use Composer\DependencyResolver\SecurityAdvisoryPoolFilter;
use Composer\DependencyResolver\Solver;
use Composer\DependencyResolver\SolverProblemsException;
use Composer\DependencyResolver\PolicyInterface;
use Composer\Downloader\DownloadManager;
use Composer\Downloader\TransportException;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Filter\PlatformRequirementFilter\IgnoreListPlatformRequirementFilter;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
use Composer\Installer\InstallationManager;
use Composer\Installer\InstallerEvents;
use Composer\Installer\SuggestedPackagesReporter;
use Composer\IO\IOInterface;
use Composer\Package\AliasPackage;
use Composer\Package\RootAliasPackage;
use Composer\Package\BasePackage;
use Composer\Package\CompletePackage;
use Composer\Package\CompletePackageInterface;
use Composer\Package\Link;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Dumper\ArrayDumper;
use Composer\Package\Version\VersionParser;
use Composer\Package\Package;
use Composer\Repository\ArrayRepository;
use Composer\Repository\RepositorySet;
use Composer\Repository\CompositeRepository;
use Composer\Semver\Constraint\Constraint;
use Composer\Package\Locker;
use Composer\Package\RootPackageInterface;
use Composer\Repository\InstalledArrayRepository;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Repository\InstalledRepository;
use Composer\Repository\RootPackageRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\RepositoryManager;
use Composer\Repository\LockArrayRepository;
use Composer\Script\ScriptEvents;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Advisory\Auditor;
use Composer\Util\Platform;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Beau Simensen <beau@dflydev.com>
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Nils Adermann <naderman@naderman.de>
 */
class Installer
{
    public const ERROR_NONE = 0; // no error/success state
    public const ERROR_GENERIC_FAILURE = 1;
    public const ERROR_NO_LOCK_FILE_FOR_PARTIAL_UPDATE = 3;
    public const ERROR_LOCK_FILE_INVALID = 4;
    // used/declared in SolverProblemsException, carried over here for completeness
    public const ERROR_DEPENDENCY_RESOLUTION_FAILED = 2;
    public const ERROR_AUDIT_FAILED = 5;
    // technically exceptions are thrown with various status codes >400, but the process exit code is normalized to 100
    public const ERROR_TRANSPORT_EXCEPTION = 100;

    /**
     * @var IOInterface
     */
    protected $io;

    /**
     * @var Config
     */
    protected $config;

    /**
     * @var RootPackageInterface&BasePackage
     */
    protected $package;

    // TODO can we get rid of the below and just use the package itself?
    /**
     * @var RootPackageInterface&BasePackage
     */
    protected $fixedRootPackage;

    /**
     * @var DownloadManager
     */
    protected $downloadManager;

    /**
     * @var RepositoryManager
     */
    protected $repositoryManager;

    /**
     * @var Locker
     */
    protected $locker;

    /**
     * @var InstallationManager
     */
    protected $installationManager;

    /**
     * @var EventDispatcher
     */
    protected $eventDispatcher;

    /**
     * @var AutoloadGenerator
     */
    protected $autoloadGenerator;

    /** @var bool */
    protected $preferSource = false;
    /** @var bool */
    protected $preferDist = false;
    /** @var bool */
    protected $optimizeAutoloader = false;
    /** @var bool */
    protected $classMapAuthoritative = false;
    /** @var bool */
    protected $apcuAutoloader = false;
    /** @var string|null */
    protected $apcuAutoloaderPrefix = null;
    /** @var bool */
    protected $devMode = false;
    /** @var bool */
    protected $dryRun = false;
    /** @var bool */
    protected $downloadOnly = false;
    /** @var bool */
    protected $verbose = false;
    /** @var bool */
    protected $update = false;
    /** @var bool */
    protected $install = true;
    /** @var bool */
    protected $dumpAutoloader = true;
    /** @var bool */
    protected $runScripts = true;
    /** @var bool */
    protected $preferStable = false;
    /** @var bool */
    protected $preferLowest = false;
    /** @var bool */
    protected $minimalUpdate = false;
    /** @var bool */
    protected $writeLock;
    /** @var bool */
    protected $executeOperations = true;
    /** @var bool */
    protected $audit = true;
    /** @var bool */
    protected $errorOnAudit = false;
    /** @var Auditor::FORMAT_* */
    protected $auditFormat = Auditor::FORMAT_SUMMARY;
    /** @var AuditConfig|null */
    private $auditConfig = null;
    /** @var list<string> */
    private $ignoredTypes = ['php-ext', 'php-ext-zend'];
    /** @var list<string>|null */
    private $allowedTypes = null;

    /** @var bool */
    protected $updateMirrors = false;
    /**
     * Array of package names/globs flagged for update
     *
     * @var non-empty-list<string>|null
     */
    protected $updateAllowList = null;
    /** @var Request::UPDATE_* */
    protected $updateAllowTransitiveDependencies = Request::UPDATE_ONLY_LISTED;

    /**
     * @var SuggestedPackagesReporter
     */
    protected $suggestedPackagesReporter;

    /**
     * @var PlatformRequirementFilterInterface
     */
    protected $platformRequirementFilter;

    /**
     * @var ?RepositoryInterface
     */
    protected $additionalFixedRepository;

    /** @var array<string, ConstraintInterface> */
    protected $temporaryConstraints = [];

    /**
     * Constructor
     *
     * @param RootPackageInterface&BasePackage $package
     */
    public function __construct(IOInterface $io, Config $config, RootPackageInterface $package, DownloadManager $downloadManager, RepositoryManager $repositoryManager, Locker $locker, InstallationManager $installationManager, EventDispatcher $eventDispatcher, AutoloadGenerator $autoloadGenerator)
    {
        $this->io = $io;
        $this->config = $config;
        $this->package = $package;
        $this->downloadManager = $downloadManager;
        $this->repositoryManager = $repositoryManager;
        $this->locker = $locker;
        $this->installationManager = $installationManager;
        $this->eventDispatcher = $eventDispatcher;
        $this->autoloadGenerator = $autoloadGenerator;
        $this->suggestedPackagesReporter = new SuggestedPackagesReporter($this->io);
        $this->platformRequirementFilter = PlatformRequirementFilterFactory::ignoreNothing();

        $this->writeLock = $config->get('lock');
    }

    /**
     * Run installation (or update)
     *
     * @throws \Exception
     * @return int        0 on success or a positive error code on failure
     * @phpstan-return self::ERROR_*
     */
    public function run(): int
    {
        // Disable GC to save CPU cycles, as the dependency solver can create hundreds of thousands
        // of PHP objects, the GC can spend quite some time walking the tree of references looking
        // for stuff to collect while there is nothing to collect. This slows things down dramatically
        // and turning it off results in much better performance. Do not try this at home however.
        gc_collect_cycles();
        gc_disable();

        if ($this->updateAllowList !== null && $this->updateMirrors) {
            throw new \RuntimeException("The installer options updateMirrors and updateAllowList are mutually exclusive.");
        }

        $isFreshInstall = $this->repositoryManager->getLocalRepository()->isFresh();

        // Force update if there is no lock file present
        if (!$this->update && !$this->locker->isLocked()) {
            $this->io->writeError('<warning>No composer.lock file present. Updating dependencies to latest instead of installing from lock file. See https://getcomposer.org/install for more information.</warning>');
            $this->update = true;
        }

        if ($this->dryRun) {
            $this->verbose = true;
            $this->runScripts = false;
            $this->executeOperations = false;
            $this->writeLock = false;
            $this->dumpAutoloader = false;
            $this->mockLocalRepositories($this->repositoryManager);
        }

        if ($this->downloadOnly) {
            $this->dumpAutoloader = false;
        }

        if ($this->update && !$this->install) {
            $this->dumpAutoloader = false;
        }

        if ($this->runScripts) {
            Platform::putEnv('COMPOSER_DEV_MODE', $this->devMode ? '1' : '0');

            // dispatch pre event
            // should we treat this more strictly as running an update and then running an install, triggering events multiple times?
            $eventName = $this->update ? ScriptEvents::PRE_UPDATE_CMD : ScriptEvents::PRE_INSTALL_CMD;
            $this->eventDispatcher->dispatchScript($eventName, $this->devMode);
        }

        $this->downloadManager->setPreferSource($this->preferSource);
        $this->downloadManager->setPreferDist($this->preferDist);

        $localRepo = $this->repositoryManager->getLocalRepository();

        try {
            if ($this->update) {
                $res = $this->doUpdate($localRepo, $this->install);
            } else {
                $res = $this->doInstall($localRepo);
            }
            if ($res !== 0) {
                return $res;
            }
        } catch (\Exception $e) {
            if ($this->executeOperations && $this->install && $this->config->get('notify-on-install')) {
                $this->installationManager->notifyInstalls($this->io);
            }

            throw $e;
        }
        if ($this->executeOperations && $this->install && $this->config->get('notify-on-install')) {
            $this->installationManager->notifyInstalls($this->io);
        }

        if ($this->update) {
            $installedRepo = new InstalledRepository([
                $this->locker->getLockedRepository($this->devMode),
                $this->createPlatformRepo(false),
                new RootPackageRepository(clone $this->package),
            ]);
            if ($isFreshInstall) {
                $this->suggestedPackagesReporter->addSuggestionsFromPackage($this->package);
            }
            $this->suggestedPackagesReporter->outputMinimalistic($installedRepo);
        }

        // Find abandoned packages and warn user
        $lockedRepository = $this->locker->getLockedRepository(true);
        foreach ($lockedRepository->getPackages() as $package) {
            if (!$package instanceof CompletePackage || !$package->isAbandoned()) {
                continue;
            }

            $replacement = is_string($package->getReplacementPackage())
                ? 'Use ' . $package->getReplacementPackage() . ' instead'
                : 'No replacement was suggested';

            $this->io->writeError(
                sprintf(
                    "<warning>Package %s is abandoned, you should avoid using it. %s.</warning>",
                    $package->getPrettyName(),
                    $replacement
                )
            );
        }

        if ($this->dumpAutoloader) {
            // write autoloader
            if ($this->optimizeAutoloader) {
                $this->io->writeError('<info>Generating optimized autoload files</info>');
            } else {
                $this->io->writeError('<info>Generating autoload files</info>');
            }

            $this->autoloadGenerator->setClassMapAuthoritative($this->classMapAuthoritative);
            $this->autoloadGenerator->setApcu($this->apcuAutoloader, $this->apcuAutoloaderPrefix);
            $this->autoloadGenerator->setRunScripts($this->runScripts);
            $this->autoloadGenerator->setPlatformRequirementFilter($this->platformRequirementFilter);
            $this
                ->autoloadGenerator
                ->dump(
                    $this->config,
                    $localRepo,
                    $this->package,
                    $this->installationManager,
                    'composer',
                    $this->optimizeAutoloader,
                    null,
                    $this->locker
                );
        }

        if ($this->install && $this->executeOperations) {
            // force binaries re-generation in case they are missing
            foreach ($localRepo->getPackages() as $package) {
                $this->installationManager->ensureBinariesPresence($package);
            }
        }

        $fundEnv = Platform::getEnv('COMPOSER_FUND');
        $showFunding = true;
        if (is_numeric($fundEnv)) {
            $showFunding = intval($fundEnv) !== 0;
        }

        if ($showFunding) {
            $fundingCount = 0;
            foreach ($localRepo->getPackages() as $package) {
                if ($package instanceof CompletePackageInterface && !$package instanceof AliasPackage && $package->getFunding()) {
                    $fundingCount++;
                }
            }
            if ($fundingCount > 0) {
                $this->io->writeError([
                    sprintf(
                        "<info>%d package%s you are using %s looking for funding.</info>",
                        $fundingCount,
                        1 === $fundingCount ? '' : 's',
                        1 === $fundingCount ? 'is' : 'are'
                    ),
                    '<info>Use the `composer fund` command to find out more!</info>',
                ]);
            }
        }

        if ($this->runScripts) {
            // dispatch post event
            $eventName = $this->update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD;
            $this->eventDispatcher->dispatchScript($eventName, $this->devMode);
        }

        // re-enable GC except on HHVM which triggers a warning here
        if (!defined('HHVM_VERSION')) {
            gc_enable();
        }

        $auditConfig = $this->getAuditConfig();

        if ($auditConfig->audit) {
            if ($this->update && !$this->install) {
                $packages = $lockedRepository->getCanonicalPackages();
                $target = 'locked';
            } else {
                $packages = $localRepo->getCanonicalPackages();
                $target = 'installed';
            }
            if (count($packages) > 0) {
                try {
                    $auditor = new Auditor();
                    $repoSet = new RepositorySet();
                    foreach ($this->repositoryManager->getRepositories() as $repo) {
                        $repoSet->addRepository($repo);
                    }

                    return $auditor->audit($this->io, $repoSet, $packages, $auditConfig->auditFormat, true, $auditConfig->ignoreListForAudit, $auditConfig->auditAbandoned, $auditConfig->ignoreSeverityForAudit, $auditConfig->ignoreUnreachable, $auditConfig->ignoreAbandonedForAudit) > 0 && $this->errorOnAudit ? self::ERROR_AUDIT_FAILED : 0;
                } catch (TransportException $e) {
                    $this->io->error('Failed to audit '.$target.' packages.');
                    if ($this->io->isVerbose()) {
                        $this->io->error('['.get_class($e).'] '.$e->getMessage());
                    }
                }
            } else {
                $this->io->writeError('No '.$target.' packages - skipping audit.');
            }
        }

        return 0;
    }

    /**
     * @phpstan-return self::ERROR_*
     */
    protected function doUpdate(InstalledRepositoryInterface $localRepo, bool $doInstall): int
    {
        $platformRepo = $this->createPlatformRepo(true);
        $aliases = $this->getRootAliases(true);

        $lockedRepository = null;

        try {
            if ($this->locker->isLocked()) {
                $lockedRepository = $this->locker->getLockedRepository(true);
            }
        } catch (\Seld\JsonLint\ParsingException $e) {
            if ($this->updateAllowList !== null || $this->updateMirrors) {
                // in case we are doing a partial update or updating mirrors, the lock file is needed so we error
                throw $e;
            }
            // otherwise, ignoring parse errors as the lock file will be regenerated from scratch when
            // doing a full update
        }

        if (($this->updateAllowList !== null || $this->updateMirrors) && !$lockedRepository) {
            $this->io->writeError('<error>Cannot update ' . ($this->updateMirrors ? 'lock file information' : 'only a partial set of packages') . ' without a lock file present. Run `composer update` to generate a lock file.</error>', true, IOInterface::QUIET);

            return self::ERROR_NO_LOCK_FILE_FOR_PARTIAL_UPDATE;
        }

        $this->io->writeError('<info>Loading composer repositories with package information</info>');

        // creating repository set
        $policy = $this->createPolicy(true, $lockedRepository);
        $repositorySet = $this->createRepositorySet(true, $platformRepo, $aliases);
        $repositories = $this->repositoryManager->getRepositories();
        foreach ($repositories as $repository) {
            $repositorySet->addRepository($repository);
        }
        if ($lockedRepository) {
            $repositorySet->addRepository($lockedRepository);
        }

        $request = $this->createRequest($this->fixedRootPackage, $platformRepo, $lockedRepository);
        $this->requirePackagesForUpdate($request, $lockedRepository, true);

        // pass the allow list into the request, so the pool builder can apply it
        if ($this->updateAllowList !== null) {
            $request->setUpdateAllowList($this->updateAllowList, $this->updateAllowTransitiveDependencies);
        }

        $pool = $repositorySet->createPool($request, $this->io, $this->eventDispatcher, $this->createPoolOptimizer($policy), $this->ignoredTypes, $this->allowedTypes, $this->createSecurityAuditPoolFilter());

        $this->io->writeError('<info>Updating dependencies</info>');

        // solve dependencies
        $solver = new Solver($policy, $pool, $this->io);
        try {
            $lockTransaction = $solver->solve($request, $this->platformRequirementFilter);
            $ruleSetSize = $solver->getRuleSetSize();
            $solver = null;
        } catch (SolverProblemsException $e) {
            $err = 'Your requirements could not be resolved to an installable set of packages.';
            $prettyProblem = $e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose());

            $this->io->writeError('<error>'. $err .'</error>', true, IOInterface::QUIET);
            $this->io->writeError($prettyProblem);
            if (!$this->devMode) {
                $this->io->writeError('<warning>Running update with --no-dev does not mean require-dev is ignored, it just means the packages will not be installed. If dev requirements are blocking the update you have to resolve those problems.</warning>', true, IOInterface::QUIET);
            }

            $ghe = new GithubActionError($this->io);
            $ghe->emit($err."\n".$prettyProblem);

            return max(self::ERROR_GENERIC_FAILURE, $e->getCode());
        }

        $this->io->writeError("Analyzed ".count($pool)." packages to resolve dependencies", true, IOInterface::VERBOSE);
        $this->io->writeError("Analyzed ".$ruleSetSize." rules to resolve dependencies", true, IOInterface::VERBOSE);

        $pool = null;

        if (!$lockTransaction->getOperations()) {
            $this->io->writeError('Nothing to modify in lock file');

            if ($this->minimalUpdate && $this->updateAllowList === null && $this->locker->isFresh()) {
                $this->io->writeError('<warning>The --minimal-changes option should be used with package arguments or after modifying composer.json requirements, otherwise it will likely not yield any dependency changes.</warning>');
            }
        }

        $exitCode = $this->extractDevPackages($lockTransaction, $platformRepo, $aliases, $policy, $lockedRepository);
        if ($exitCode !== 0) {
            return $exitCode;
        }

        Semver\CompilingMatcher::clear();

        // write lock
        $platformReqs = $this->extractPlatformRequirements($this->package->getRequires());
        $platformDevReqs = $this->extractPlatformRequirements($this->package->getDevRequires());

        $installsUpdates = $uninstalls = [];
        if ($lockTransaction->getOperations()) {
            $installNames = $updateNames = $uninstallNames = [];
            foreach ($lockTransaction->getOperations() as $operation) {
                if ($operation instanceof InstallOperation) {
                    $installsUpdates[] = $operation;
                    $installNames[] = $operation->getPackage()->getPrettyName().':'.$operation->getPackage()->getFullPrettyVersion();
                } elseif ($operation instanceof UpdateOperation) {
                    // when mirrors/metadata from a package gets updated we do not want to list it as an
                    // update in the output as it is only an internal lock file metadata update
                    if ($this->updateMirrors
                        && $operation->getInitialPackage()->getName() === $operation->getTargetPackage()->getName()
                        && $operation->getInitialPackage()->getVersion() === $operation->getTargetPackage()->getVersion()
                    ) {
                        continue;
                    }

                    $installsUpdates[] = $operation;
                    $updateNames[] = $operation->getTargetPackage()->getPrettyName().':'.$operation->getTargetPackage()->getFullPrettyVersion();
                } elseif ($operation instanceof UninstallOperation) {
                    $uninstalls[] = $operation;
                    $uninstallNames[] = $operation->getPackage()->getPrettyName();
                }
            }

            if ($this->config->get('lock')) {
                $this->io->writeError(sprintf(
                    "<info>Lock file operations: %d install%s, %d update%s, %d removal%s</info>",
                    count($installNames),
                    1 === count($installNames) ? '' : 's',
                    count($updateNames),
                    1 === count($updateNames) ? '' : 's',
                    count($uninstalls),
                    1 === count($uninstalls) ? '' : 's'
                ));
                if ($installNames) {
                    $this->io->writeError("Installs: ".implode(', ', $installNames), true, IOInterface::VERBOSE);
                }
                if ($updateNames) {
                    $this->io->writeError("Updates: ".implode(', ', $updateNames), true, IOInterface::VERBOSE);
                }
                if ($uninstalls) {
                    $this->io->writeError("Removals: ".implode(', ', $uninstallNames), true, IOInterface::VERBOSE);
                }
            }
        }

        $sortByName = static function ($a, $b): int {
            if ($a instanceof UpdateOperation) {
                $a = $a->getTargetPackage()->getName();
            } else {
                $a = $a->getPackage()->getName();
            }
            if ($b instanceof UpdateOperation) {
                $b = $b->getTargetPackage()->getName();
            } else {
                $b = $b->getPackage()->getName();
            }

            return strcmp($a, $b);
        };
        usort($uninstalls, $sortByName);
        usort($installsUpdates, $sortByName);

        foreach (array_merge($uninstalls, $installsUpdates) as $operation) {
            // collect suggestions
            if ($operation instanceof InstallOperation) {
                $this->suggestedPackagesReporter->addSuggestionsFromPackage($operation->getPackage());
            }

            // output op if lock file is enabled, but alias op only in debug verbosity
            if ($this->config->get('lock') && (false === strpos($operation->getOperationType(), 'Alias') || $this->io->isDebug())) {
                $sourceRepo = '';
                if ($this->io->isVeryVerbose() && false === strpos($operation->getOperationType(), 'Alias')) {
                    $operationPkg = ($operation instanceof UpdateOperation ? $operation->getTargetPackage() : $operation->getPackage());
                    if ($operationPkg->getRepository() !== null) {
                        $sourceRepo = ' from ' . $operationPkg->getRepository()->getRepoName();
                    }
                }
                $this->io->writeError('  - ' . $operation->show(true) . $sourceRepo);
            }
        }

        $updatedLock = $this->locker->setLockData(
            $lockTransaction->getNewLockPackages(false, $this->updateMirrors),
            $lockTransaction->getNewLockPackages(true, $this->updateMirrors),
            $platformReqs,
            $platformDevReqs,
            $lockTransaction->getAliases($aliases),
            $this->package->getMinimumStability(),
            $this->package->getStabilityFlags(),
            $this->preferStable || $this->package->getPreferStable(),
            $this->preferLowest,
            $this->config->get('platform') ?: [],
            $this->writeLock && $this->executeOperations
        );
        if ($updatedLock && $this->writeLock && $this->executeOperations) {
            $this->io->writeError('<info>Writing lock file</info>');
        }

        if ($doInstall) {
            // TODO ensure lock is used from locker as-is, since it may not have been written to disk in case of executeOperations == false
            return $this->doInstall($localRepo, true);
        }

        return 0;
    }

    /**
     * Run the solver a second time on top of the existing update result with only the current result set in the pool
     * and see what packages would get removed if we only had the non-dev packages in the solver request
     *
     * @param array<int, array<string, string>> $aliases
     *
     * @phpstan-param list<array{package: string, version: string, alias: string, alias_normalized: string}> $aliases
     * @phpstan-return self::ERROR_*
     */
    protected function extractDevPackages(LockTransaction $lockTransaction, PlatformRepository $platformRepo, array $aliases, PolicyInterface $policy, ?LockArrayRepository $lockedRepository = null): int
    {
        if (!$this->package->getDevRequires()) {
            return 0;
        }

        $resultRepo = new ArrayRepository([]);
        $loader = new ArrayLoader(null, true);
        $dumper = new ArrayDumper();
        foreach ($lockTransaction->getNewLockPackages(false) as $pkg) {
            $resultRepo->addPackage($loader->load($dumper->dump($pkg)));
        }

        $repositorySet = $this->createRepositorySet(true, $platformRepo, $aliases);
        $repositorySet->addRepository($resultRepo);

        $request = $this->createRequest($this->fixedRootPackage, $platformRepo);
        $this->requirePackagesForUpdate($request, $lockedRepository, false);

        $pool = $repositorySet->createPoolWithAllPackages();

        $solver = new Solver($policy, $pool, $this->io);
        try {
            $nonDevLockTransaction = $solver->solve($request, $this->platformRequirementFilter);
            $solver = null;
        } catch (SolverProblemsException $e) {
            $err = 'Unable to find a compatible set of packages based on your non-dev requirements alone.';
            $prettyProblem = $e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose(), true);

            $this->io->writeError('<error>'. $err .'</error>', true, IOInterface::QUIET);
            $this->io->writeError('Your requirements can be resolved successfully when require-dev packages are present.');
            $this->io->writeError('You may need to move packages from require-dev or some of their dependencies to require.');
            $this->io->writeError($prettyProblem);

            $ghe = new GithubActionError($this->io);
            $ghe->emit($err."\n".$prettyProblem);

            return $e->getCode();
        }

        $lockTransaction->setNonDevPackages($nonDevLockTransaction);

        return 0;
    }

    /**
     * @param  bool                         $alreadySolved Whether the function is called as part of an update command or independently
     * @return int                          exit code
     * @phpstan-return self::ERROR_*
     */
    protected function doInstall(InstalledRepositoryInterface $localRepo, bool $alreadySolved = false): int
    {
        if ($this->config->get('lock')) {
            $this->io->writeError('<info>Installing dependencies from lock file'.($this->devMode ? ' (including require-dev)' : '').'</info>');
        }

        $lockedRepository = $this->locker->getLockedRepository($this->devMode);

        // verify that the lock file works with the current platform repository
        // we can skip this part if we're doing this as the second step after an update
        if (!$alreadySolved) {
            $this->io->writeError('<info>Verifying lock file contents can be installed on current platform.</info>');

            $platformRepo = $this->createPlatformRepo(false);
            // creating repository set
            $policy = $this->createPolicy(false);
            // use aliases from lock file only, so empty root aliases here
            $repositorySet = $this->createRepositorySet(false, $platformRepo, [], $lockedRepository);
            $repositorySet->addRepository($lockedRepository);

            // creating requirements request
            $request = $this->createRequest($this->fixedRootPackage, $platformRepo, $lockedRepository);

            if (!$this->locker->isFresh()) {
                $this->io->writeError('<warning>Warning: The lock file is not up to date with the latest changes in composer.json. You may be getting outdated dependencies. It is recommended that you run `composer update` or `composer update <package name>`.</warning>', true, IOInterface::QUIET);
            }

            $missingRequirementInfo = $this->locker->getMissingRequirementInfo($this->package, $this->devMode);
            if ($missingRequirementInfo !== []) {
                $this->io->writeError($missingRequirementInfo);

                if (!$this->config->get('allow-missing-requirements')) {
                    return self::ERROR_LOCK_FILE_INVALID;
                }
            }

            foreach ($lockedRepository->getPackages() as $package) {
                $request->fixLockedPackage($package);
            }

            $rootRequires = $this->package->getRequires();
            if ($this->devMode) {
                $rootRequires = array_merge($rootRequires, $this->package->getDevRequires());
            }
            foreach ($rootRequires as $link) {
                if (PlatformRepository::isPlatformPackage($link->getTarget())) {
                    $request->requireName($link->getTarget(), $link->getConstraint());
                }
            }

            foreach ($this->locker->getPlatformRequirements($this->devMode) as $link) {
                if (!isset($rootRequires[$link->getTarget()])) {
                    $request->requireName($link->getTarget(), $link->getConstraint());
                }
            }
            unset($rootRequires, $link);

            $pool = $repositorySet->createPool($request, $this->io, $this->eventDispatcher, null, $this->ignoredTypes, $this->allowedTypes, null);

            // solve dependencies
            $solver = new Solver($policy, $pool, $this->io);
            try {
                $lockTransaction = $solver->solve($request, $this->platformRequirementFilter);
                $solver = null;

                // installing the locked packages on this platform resulted in lock modifying operations, there wasn't a conflict, but the lock file as-is seems to not work on this system
                if (0 !== count($lockTransaction->getOperations())) {
                    $this->io->writeError('<error>Your lock file cannot be installed on this system without changes. Please run composer update.</error>', true, IOInterface::QUIET);

                    return self::ERROR_LOCK_FILE_INVALID;
                }
            } catch (SolverProblemsException $e) {
                $err = 'Your lock file does not contain a compatible set of packages. Please run composer update.';
                $prettyProblem = $e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose());

                $this->io->writeError('<error>'. $err .'</error>', true, IOInterface::QUIET);
                $this->io->writeError($prettyProblem);

                $ghe = new GithubActionError($this->io);
                $ghe->emit($err."\n".$prettyProblem);

                return max(self::ERROR_GENERIC_FAILURE, $e->getCode());
            }
        }

        // TODO in how far do we need to do anything here to ensure dev packages being updated to latest in lock without version change are treated correctly?
        $localRepoTransaction = new LocalRepoTransaction($lockedRepository, $localRepo);
        $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_OPERATIONS_EXEC, $this->devMode, $this->executeOperations, $localRepoTransaction);

        $installs = $updates = $uninstalls = [];
        foreach ($localRepoTransaction->getOperations() as $operation) {
            if ($operation instanceof InstallOperation) {
                $installs[] = $operation->getPackage()->getPrettyName().':'.$operation->getPackage()->getFullPrettyVersion();
            } elseif ($operation instanceof UpdateOperation) {
                $updates[] = $operation->getTargetPackage()->getPrettyName().':'.$operation->getTargetPackage()->getFullPrettyVersion();
            } elseif ($operation instanceof UninstallOperation) {
                $uninstalls[] = $operation->getPackage()->getPrettyName();
            }
        }

        if ($installs === [] && $updates === [] && $uninstalls === []) {
            $this->io->writeError('Nothing to install, update or remove');
        } else {
            $this->io->writeError(sprintf(
                "<info>Package operations: %d install%s, %d update%s, %d removal%s</info>",
                count($installs),
                1 === count($installs) ? '' : 's',
                count($updates),
                1 === count($updates) ? '' : 's',
                count($uninstalls),
                1 === count($uninstalls) ? '' : 's'
            ));
            if ($installs) {
                $this->io->writeError("Installs: ".implode(', ', $installs), true, IOInterface::VERBOSE);
            }
            if ($updates) {
                $this->io->writeError("Updates: ".implode(', ', $updates), true, IOInterface::VERBOSE);
            }
            if ($uninstalls) {
                $this->io->writeError("Removals: ".implode(', ', $uninstalls), true, IOInterface::VERBOSE);
            }
        }

        if ($this->executeOperations) {
            $localRepo->setDevPackageNames($this->locker->getDevPackageNames());
            $this->installationManager->execute($localRepo, $localRepoTransaction->getOperations(), $this->devMode, $this->runScripts, $this->downloadOnly);

            // see https://github.com/composer/composer/issues/2764
            if (count($localRepoTransaction->getOperations()) > 0) {
                $vendorDir = $this->config->get('vendor-dir');
                if (is_dir($vendorDir)) {
                    // suppress errors as this fails sometimes on OSX for no apparent reason
                    // see https://github.com/composer/composer/issues/4070#issuecomment-129792748
                    @touch($vendorDir);
                }
            }
        } else {
            foreach ($localRepoTransaction->getOperations() as $operation) {
                // output op, but alias op only in debug verbosity
                if (false === strpos($operation->getOperationType(), 'Alias') || $this->io->isDebug()) {
                    $this->io->writeError('  - ' . $operation->show(false));
                }
            }
        }

        return 0;
    }

    protected function createPlatformRepo(bool $forUpdate): PlatformRepository
    {
        if ($forUpdate) {
            $platformOverrides = $this->config->get('platform') ?: [];
        } else {
            $platformOverrides = $this->locker->getPlatformOverrides();
        }

        return new PlatformRepository([], $platformOverrides);
    }

    /**
     * @param  array<int, array<string, string>> $rootAliases
     *
     * @phpstan-param list<array{package: string, version: string, alias: string, alias_normalized: string}> $rootAliases
     */
    private function createRepositorySet(bool $forUpdate, PlatformRepository $platformRepo, array $rootAliases = [], ?RepositoryInterface $lockedRepository = null): RepositorySet
    {
        if ($forUpdate) {
            $minimumStability = $this->package->getMinimumStability();
            $stabilityFlags = $this->package->getStabilityFlags();

            $requires = array_merge($this->package->getRequires(), $this->package->getDevRequires());
        } else {
            $minimumStability = $this->locker->getMinimumStability();
            $stabilityFlags = $this->locker->getStabilityFlags();

            $requires = [];
            foreach ($lockedRepository->getPackages() as $package) {
                $constraint = new Constraint('=', $package->getVersion());
                $constraint->setPrettyString($package->getPrettyVersion());
                $requires[$package->getName()] = $constraint;
            }
        }

        $rootRequires = [];
        foreach ($requires as $req => $constraint) {
            if ($constraint instanceof Link) {
                $constraint = $constraint->getConstraint();
            }
            // skip platform requirements from the root package to avoid filtering out existing platform packages
            if ($this->platformRequirementFilter->isIgnored($req)) {
                continue;
            } elseif ($this->platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) {
                $constraint = $this->platformRequirementFilter->filterConstraint($req, $constraint);
            }
            $rootRequires[$req] = $constraint;
        }

        $this->fixedRootPackage = clone $this->package;
        $this->fixedRootPackage->setRequires([]);
        $this->fixedRootPackage->setDevRequires([]);

        $stabilityFlags[$this->package->getName()] = BasePackage::STABILITIES[VersionParser::parseStability($this->package->getVersion())];

        $repositorySet = new RepositorySet($minimumStability, $stabilityFlags, $rootAliases, $this->package->getReferences(), $rootRequires, $this->temporaryConstraints);
        $repositorySet->addRepository(new RootPackageRepository($this->fixedRootPackage));
        $repositorySet->addRepository($platformRepo);
        if ($this->additionalFixedRepository) {
            // allow using installed repos if needed to avoid warnings about installed repositories being used in the RepositorySet
            // see https://github.com/composer/composer/pull/9574
            $additionalFixedRepositories = $this->additionalFixedRepository;
            if ($additionalFixedRepositories instanceof CompositeRepository) {
                $additionalFixedRepositories = $additionalFixedRepositories->getRepositories();
            } else {
                $additionalFixedRepositories = [$additionalFixedRepositories];
            }
            foreach ($additionalFixedRepositories as $additionalFixedRepository) {
                if ($additionalFixedRepository instanceof InstalledRepository || $additionalFixedRepository instanceof InstalledRepositoryInterface) {
                    $repositorySet->allowInstalledRepositories();
                    break;
                }
            }

            $repositorySet->addRepository($this->additionalFixedRepository);
        }

        return $repositorySet;
    }

    private function createPolicy(bool $forUpdate, ?LockArrayRepository $lockedRepo = null): DefaultPolicy
    {
        $preferStable = null;
        $preferLowest = null;
        if (!$forUpdate) {
            $preferStable = $this->locker->getPreferStable();
            $preferLowest = $this->locker->getPreferLowest();
        }
        // old lock file without prefer stable/lowest will return null
        // so in this case we use the composer.json info
        if (null === $preferStable) {
            $preferStable = $this->preferStable || $this->package->getPreferStable();
        }
        if (null === $preferLowest) {
            $preferLowest = $this->preferLowest;
        }

        $preferredVersions = null;
        if ($forUpdate && $this->minimalUpdate && $lockedRepo !== null) {
            $preferredVersions = [];
            foreach ($lockedRepo->getPackages() as $pkg) {
                if ($pkg instanceof AliasPackage || ($this->updateAllowList !== null && in_array($pkg->getName(), $this->updateAllowList, true))) {
                    continue;
                }
                $preferredVersions[$pkg->getName()] = $pkg->getVersion();
            }
        }

        return new DefaultPolicy($preferStable, $preferLowest, $preferredVersions);
    }

    /**
     * @param RootPackageInterface&BasePackage $rootPackage
     */
    private function createRequest(RootPackageInterface $rootPackage, PlatformRepository $platformRepo, ?LockArrayRepository $lockedRepository = null): Request
    {
        $request = new Request($lockedRepository);

        $request->fixPackage($rootPackage);
        if ($rootPackage instanceof RootAliasPackage) {
            $request->fixPackage($rootPackage->getAliasOf());
        }

        $fixedPackages = $platformRepo->getPackages();
        if ($this->additionalFixedRepository) {
            $fixedPackages = array_merge($fixedPackages, $this->additionalFixedRepository->getPackages());
        }

        // fix the version of all platform packages + additionally installed packages
        // to prevent the solver trying to remove or update those
        // TODO why not replaces?
        $provided = $rootPackage->getProvides();
        foreach ($fixedPackages as $package) {
            // skip platform packages that are provided by the root package
            if ($package->getRepository() !== $platformRepo
                || !isset($provided[$package->getName()])
                || !$provided[$package->getName()]->getConstraint()->matches(new Constraint('=', $package->getVersion()))
            ) {
                $request->fixPackage($package);
            }
        }

        return $request;
    }

    private function requirePackagesForUpdate(Request $request, ?LockArrayRepository $lockedRepository = null, bool $includeDevRequires = true): void
    {
        // if we're updating mirrors we want to keep exactly the same versions installed which are in the lock file, but we want current remote metadata
        if ($this->updateMirrors) {
            $excludedPackages = [];
            if (!$includeDevRequires) {
                $excludedPackages = array_flip($this->locker->getDevPackageNames());
            }

            foreach ($lockedRepository->getPackages() as $lockedPackage) {
                // exclude alias packages here as for root aliases, both alias and aliased are
                // present in the lock repo and we only want to require the aliased version
                if (!$lockedPackage instanceof AliasPackage && !isset($excludedPackages[$lockedPackage->getName()])) {
                    $request->requireName($lockedPackage->getName(), new Constraint('==', $lockedPackage->getVersion()));
                }
            }
        } else {
            $links = $this->package->getRequires();
            if ($includeDevRequires) {
                $links = array_merge($links, $this->package->getDevRequires());
            }
            foreach ($links as $link) {
                $request->requireName($link->getTarget(), $link->getConstraint());
            }
        }
    }

    /**
     * @return array<int, array<string, string>>
     *
     * @phpstan-return list<array{package: string, version: string, alias: string, alias_normalized: string}>
     */
    private function getRootAliases(bool $forUpdate): array
    {
        if ($forUpdate) {
            $aliases = $this->package->getAliases();
        } else {
            $aliases = $this->locker->getAliases();
        }

        return $aliases;
    }

    /**
     * @param Link[] $links
     *
     * @return array<string, string>
     */
    private function extractPlatformRequirements(array $links): array
    {
        $platformReqs = [];
        foreach ($links as $link) {
            if (PlatformRepository::isPlatformPackage($link->getTarget())) {
                $platformReqs[$link->getTarget()] = $link->getPrettyConstraint();
            }
        }

        return $platformReqs;
    }

    /**
     * Replace local repositories with InstalledArrayRepository instances
     *
     * This is to prevent any accidental modification of the existing repos on disk
     */
    private function mockLocalRepositories(RepositoryManager $rm): void
    {
        $packages = [];
        foreach ($rm->getLocalRepository()->getPackages() as $package) {
            $packages[(string) $package] = clone $package;
        }
        foreach ($packages as $key => $package) {
            if ($package instanceof AliasPackage) {
                $alias = (string) $package->getAliasOf();
                $className = get_class($package);
                $packages[$key] = new $className($packages[$alias], $package->getVersion(), $package->getPrettyVersion());
            }
        }
        $rm->setLocalRepository(
            new InstalledArrayRepository($packages)
        );
    }

    private function createPoolOptimizer(PolicyInterface $policy): ?PoolOptimizer
    {
        // Not the best architectural decision here, would need to be able
        // to configure from the outside of Installer but this is only
        // a debugging tool and should never be required in any other use case
        if ('0' === Platform::getEnv('COMPOSER_POOL_OPTIMIZER')) {
            $this->io->write('Pool Optimizer was disabled for debugging purposes.', true, IOInterface::DEBUG);

            return null;
        }

        return new PoolOptimizer($policy);
    }

    private function getAuditConfig(): AuditConfig
    {
        if (null === $this->auditConfig) {
            $this->auditConfig = AuditConfig::fromConfig($this->config, $this->audit, $this->auditFormat);
        }

        return $this->auditConfig;
    }

    private function createSecurityAuditPoolFilter(): ?SecurityAdvisoryPoolFilter
    {
        $auditConfig = $this->getAuditConfig();

        if ($auditConfig->blockInsecure && !$this->updateMirrors) {
            return new SecurityAdvisoryPoolFilter(new Auditor(), $auditConfig);
        }

        return null;
    }

    /**
     * Create Installer
     */
    public static function create(IOInterface $io, Composer $composer): self
    {
        return new static(
            $io,
            $composer->getConfig(),
            $composer->getPackage(),
            $composer->getDownloadManager(),
            $composer->getRepositoryManager(),
            $composer->getLocker(),
            $composer->getInstallationManager(),
            $composer->getEventDispatcher(),
            $composer->getAutoloadGenerator()
        );
    }

    /**
     * Packages of those types are ignored, by default php-ext and php-ext-zend are ignored
     *
     * @param list<string> $types
     * @return $this
     */
    public function setIgnoredTypes(array $types): self
    {
        $this->ignoredTypes = $types;

        return $this;
    }

    /**
     * Only packages of those types are allowed if set to non-null
     *
     * @param list<string>|null $types
     * @return $this
     */
    public function setAllowedTypes(?array $types): self
    {
        $this->allowedTypes = $types;

        return $this;
    }

    /**
     * @return $this
     */
    public function setAdditionalFixedRepository(RepositoryInterface $additionalFixedRepository): self
    {
        $this->additionalFixedRepository = $additionalFixedRepository;

        return $this;
    }

    /**
     * @param array<string, ConstraintInterface> $constraints
     */
    public function setTemporaryConstraints(array $constraints): self
    {
        $this->temporaryConstraints = $constraints;

        return $this;
    }

    /**
     * Whether to run in drymode or not
     */
    public function setDryRun(bool $dryRun = true): self
    {
        $this->dryRun = $dryRun;

        return $this;
    }

    /**
     * Checks, if this is a dry run (simulation mode).
     */
    public function isDryRun(): bool
    {
        return $this->dryRun;
    }

    /**
     * Whether to download only or not.
     */
    public function setDownloadOnly(bool $downloadOnly = true): self
    {
        $this->downloadOnly = $downloadOnly;

        return $this;
    }

    /**
     * prefer source installation
     */
    public function setPreferSource(bool $preferSource = true): self
    {
        $this->preferSource = $preferSource;

        return $this;
    }

    /**
     * prefer dist installation
     */
    public function setPreferDist(bool $preferDist = true): self
    {
        $this->preferDist = $preferDist;

        return $this;
    }

    /**
     * Whether or not generated autoloader are optimized
     */
    public function setOptimizeAutoloader(bool $optimizeAutoloader): self
    {
        $this->optimizeAutoloader = $optimizeAutoloader;
        if (!$this->optimizeAutoloader) {
            // Force classMapAuthoritative off when not optimizing the
            // autoloader
            $this->setClassMapAuthoritative(false);
        }

        return $this;
    }

    /**
     * Whether or not generated autoloader considers the class map
     * authoritative.
     */
    public function setClassMapAuthoritative(bool $classMapAuthoritative): self
    {
        $this->classMapAuthoritative = $classMapAuthoritative;
        if ($this->classMapAuthoritative) {
            // Force optimizeAutoloader when classmap is authoritative
            $this->setOptimizeAutoloader(true);
        }

        return $this;
    }

    /**
     * Whether or not generated autoloader considers APCu caching.
     */
    public function setApcuAutoloader(bool $apcuAutoloader, ?string $apcuAutoloaderPrefix = null): self
    {
        $this->apcuAutoloader = $apcuAutoloader;
        $this->apcuAutoloaderPrefix = $apcuAutoloaderPrefix;

        return $this;
    }

    /**
     * update packages
     */
    public function setUpdate(bool $update): self
    {
        $this->update = $update;

        return $this;
    }

    /**
     * Allows disabling the install step after an update
     */
    public function setInstall(bool $install): self
    {
        $this->install = $install;

        return $this;
    }

    /**
     * enables dev packages
     */
    public function setDevMode(bool $devMode = true): self
    {
        $this->devMode = $devMode;

        return $this;
    }

    /**
     * set whether to run autoloader or not
     *
     * This is disabled implicitly when enabling dryRun
     */
    public function setDumpAutoloader(bool $dumpAutoloader = true): self
    {
        $this->dumpAutoloader = $dumpAutoloader;

        return $this;
    }

    /**
     * set whether to run scripts or not
     *
     * This is disabled implicitly when enabling dryRun
     *
     * @deprecated Use setRunScripts(false) on the EventDispatcher instance being injected instead
     */
    public function setRunScripts(bool $runScripts = true): self
    {
        $this->runScripts = $runScripts;

        return $this;
    }

    /**
     * set the config instance
     */
    public function setConfig(Config $config): self
    {
        $this->config = $config;

        return $this;
    }

    /**
     * run in verbose mode
     */
    public function setVerbose(bool $verbose = true): self
    {
        $this->verbose = $verbose;

        return $this;
    }

    /**
     * Checks, if running in verbose mode.
     */
    public function isVerbose(): bool
    {
        return $this->verbose;
    }

    /**
     * set ignore Platform Package requirements
     *
     * If this is set to true, all platform requirements are ignored
     * If this is set to false, no platform requirements are ignored
     * If this is set to string[], those packages will be ignored
     *
     * @param  bool|string[] $ignorePlatformReqs
     *
     *
     * @deprecated use setPlatformRequirementFilter instead
     */
    public function setIgnorePlatformRequirements($ignorePlatformReqs): self
    {
        trigger_error('Installer::setIgnorePlatformRequirements is deprecated since Composer 2.2, use setPlatformRequirementFilter instead.', E_USER_DEPRECATED);

        return $this->setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs));
    }

    public function setPlatformRequirementFilter(PlatformRequirementFilterInterface $platformRequirementFilter): self
    {
        $this->platformRequirementFilter = $platformRequirementFilter;

        return $this;
    }

    /**
     * Update the lock file to the exact same versions and references but use current remote metadata like URLs and mirror info
     */
    public function setUpdateMirrors(bool $updateMirrors): self
    {
        $this->updateMirrors = $updateMirrors;

        return $this;
    }

    /**
     * restrict the update operation to a few packages, all other packages
     * that are already installed will be kept at their current version
     *
     * @param string[] $packages
     */
    public function setUpdateAllowList(array $packages): self
    {
        if (count($packages) === 0) {
            $this->updateAllowList = null;
        } else {
            $this->updateAllowList = array_values(array_unique(array_map('strtolower', $packages)));
        }

        return $this;
    }

    /**
     * Should dependencies of packages marked for update be updated?
     *
     * Depending on the chosen constant this will either only update the directly named packages, all transitive
     * dependencies which are not root requirement or all transitive dependencies including root requirements
     *
     * @param  int       $updateAllowTransitiveDependencies One of the UPDATE_ constants on the Request class
     */
    public function setUpdateAllowTransitiveDependencies(int $updateAllowTransitiveDependencies): self
    {
        if (!in_array($updateAllowTransitiveDependencies, [Request::UPDATE_ONLY_LISTED, Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE, Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS], true)) {
            throw new \RuntimeException("Invalid value for updateAllowTransitiveDependencies supplied");
        }

        $this->updateAllowTransitiveDependencies = $updateAllowTransitiveDependencies;

        return $this;
    }

    /**
     * Should packages be preferred in a stable version when updating?
     */
    public function setPreferStable(bool $preferStable = true): self
    {
        $this->preferStable = $preferStable;

        return $this;
    }

    /**
     * Should packages be preferred in a lowest version when updating?
     */
    public function setPreferLowest(bool $preferLowest = true): self
    {
        $this->preferLowest = $preferLowest;

        return $this;
    }

    /**
     * Only relevant for partial updates (with setUpdateAllowList), if this is enabled currently locked versions will be preferred for packages which are not in the allowlist
     *
     * This reduces the update to
     */
    public function setMinimalUpdate(bool $minimalUpdate = true): self
    {
        $this->minimalUpdate = $minimalUpdate;

        return $this;
    }

    /**
     * Should the lock file be updated when updating?
     *
     * This is disabled implicitly when enabling dryRun
     */
    public function setWriteLock(bool $writeLock = true): self
    {
        $this->writeLock = $writeLock;

        return $this;
    }

    /**
     * Should the operations (package install, update and removal) be executed on disk?
     *
     * This is disabled implicitly when enabling dryRun
     */
    public function setExecuteOperations(bool $executeOperations = true): self
    {
        $this->executeOperations = $executeOperations;

        return $this;
    }

    /**
     * Should an audit be run after installation is complete?
     *
     * @deprecated Use setAuditConfig instead of calling this
     */
    public function setAudit(bool $audit): self
    {
        $this->audit = $audit;
        $this->auditConfig = null; // Invalidate cached config

        return $this;
    }

    /**
     * Should exit with status code 5 on audit error
     */
    public function setErrorOnAudit(bool $errorOnAudit): self
    {
        $this->errorOnAudit = $errorOnAudit;

        return $this;
    }

    /**
     * What format should be used for audit output?
     *
     * @param Auditor::FORMAT_* $auditFormat
     *
     * @deprecated Use setAuditConfig instead of calling this
     */
    public function setAuditFormat(string $auditFormat): self
    {
        $this->auditFormat = $auditFormat;
        $this->auditConfig = null; // Invalidate cached config

        return $this;
    }

    /**
     * Sets a custom AuditConfig to override the default configuration from Config
     */
    public function setAuditConfig(AuditConfig $auditConfig): self
    {
        $this->auditConfig = $auditConfig;

        return $this;
    }

    /**
     * Disables plugins.
     *
     * Call this if you want to ensure that third-party code never gets
     * executed. The default is to automatically install, and execute
     * custom third-party installers.
     */
    public function disablePlugins(): self
    {
        $this->installationManager->disablePlugins();

        return $this;
    }

    public function setSuggestedPackagesReporter(SuggestedPackagesReporter $suggestedPackagesReporter): self
    {
        $this->suggestedPackagesReporter = $suggestedPackagesReporter;

        return $this;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\PHPStan;

use Composer\Config;
use Composer\Json\JsonFile;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\ArrayType;
use PHPStan\Type\BooleanType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\IntegerRangeType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\MixedType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeUtils;
use PHPStan\Type\UnionType;

final class ConfigReturnTypeExtension implements DynamicMethodReturnTypeExtension
{
    /** @var array<string, Type> */
    private $properties = [];

    public function __construct()
    {
        $schema = JsonFile::parseJson((string) file_get_contents(JsonFile::COMPOSER_SCHEMA_PATH));
        /**
         * @var string $prop
         */
        foreach ($schema['properties']['config']['properties'] as $prop => $conf) {
            $type = $this->parseType($conf, $prop);

            $this->properties[$prop] = $type;
        }
    }

    public function getClass(): string
    {
        return Config::class;
    }

    public function isMethodSupported(MethodReflection $methodReflection): bool
    {
        return strtolower($methodReflection->getName()) === 'get';
    }

    public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type
    {
        $args = $methodCall->getArgs();

        if (count($args) < 1) {
            return null;
        }

        $keyType = $scope->getType($args[0]->value);
        if (method_exists($keyType, 'getConstantStrings')) { // @phpstan-ignore function.alreadyNarrowedType (- depending on PHPStan version, this method will always exist, or not.)
            $strings = $keyType->getConstantStrings();
        } else {
            // for compat with old phpstan versions, we use a deprecated phpstan method.
            $strings = TypeUtils::getConstantStrings($keyType); // @phpstan-ignore staticMethod.deprecated (ignore deprecation)
        }
        if ($strings !== []) {
            $types = [];
            foreach ($strings as $string) {
                if (!isset($this->properties[$string->getValue()])) {
                    return null;
                }
                $types[] = $this->properties[$string->getValue()];
            }

            return TypeCombinator::union(...$types);
        }

        return null;
    }

    /**
     * @param array<mixed> $def
     */
    private function parseType(array $def, string $path): Type
    {
        if (isset($def['type'])) {
            $types = [];
            foreach ((array) $def['type'] as $type) {
                switch ($type) {
                    case 'integer':
                        if (in_array($path, ['process-timeout', 'cache-ttl', 'cache-files-ttl', 'cache-files-maxsize'], true)) {
                            $types[] = IntegerRangeType::createAllGreaterThanOrEqualTo(0);
                        } else {
                            $types[] = new IntegerType();
                        }
                        break;

                    case 'string':
                        if ($path === 'cache-files-maxsize') {
                            // passthru, skip as it is always converted to int
                        } elseif ($path === 'discard-changes') {
                            $types[] = new ConstantStringType('stash');
                        } elseif ($path === 'use-parent-dir') {
                            $types[] = new ConstantStringType('prompt');
                        } elseif ($path === 'store-auths') {
                            $types[] = new ConstantStringType('prompt');
                        } elseif ($path === 'platform-check') {
                            $types[] = new ConstantStringType('php-only');
                        } elseif ($path === 'github-protocols') {
                            $types[] = new UnionType([new ConstantStringType('git'), new ConstantStringType('https'), new ConstantStringType('ssh'), new ConstantStringType('http')]);
                        } elseif (str_starts_with($path, 'preferred-install')) {
                            $types[] = new UnionType([new ConstantStringType('source'), new ConstantStringType('dist'), new ConstantStringType('auto')]);
                        } else {
                            $types[] = new StringType();
                        }
                        break;

                    case 'boolean':
                        if ($path === 'platform.additionalProperties') {
                            $types[] = new ConstantBooleanType(false);
                        } else {
                            $types[] = new BooleanType();
                        }
                        break;

                    case 'object':
                        $addlPropType = null;
                        if (isset($def['additionalProperties'])) {
                            $addlPropType = $this->parseType($def['additionalProperties'], $path.'.additionalProperties');
                        }

                        if (isset($def['properties'])) {
                            $keyNames = [];
                            $valTypes = [];
                            $optionalKeys = [];
                            $propIndex = 0;
                            foreach ($def['properties'] as $propName => $propdef) {
                                $keyNames[] = new ConstantStringType($propName);
                                $valType = $this->parseType($propdef, $path.'.'.$propName);
                                if (!isset($def['required']) || !in_array($propName, $def['required'], true)) {
                                    $valType = TypeCombinator::addNull($valType);
                                    $optionalKeys[] = $propIndex;
                                }
                                $valTypes[] = $valType;
                                $propIndex++;
                            }

                            if ($addlPropType !== null) {
                                $types[] = new ArrayType(TypeCombinator::union(new StringType(), ...$keyNames), TypeCombinator::union($addlPropType, ...$valTypes));
                            } else {
                                $types[] = new ConstantArrayType($keyNames, $valTypes, [0], $optionalKeys);
                            }
                        } else {
                            $types[] = new ArrayType(new StringType(), $addlPropType ?? new MixedType());
                        }
                        break;

                    case 'array':
                        if (isset($def['items'])) {
                            $valType = $this->parseType($def['items'], $path.'.items');
                        } else {
                            $valType = new MixedType();
                        }

                        $types[] = new ArrayType(new IntegerType(), $valType);
                        break;

                    default:
                        $types[] = new MixedType();
                }
            }

            $type = TypeCombinator::union(...$types);
        } elseif (isset($def['enum'])) {
            $type = TypeCombinator::union(...array_map(static function (string $value): ConstantStringType {
                return new ConstantStringType($value);
            }, $def['enum']));
        } else {
            $type = new MixedType();
        }

        // allow-plugins defaults to null until July 1st 2022 for some BC hackery, but after that it is not nullable anymore
        if ($path === 'allow-plugins' && time() < strtotime('2022-07-01')) {
            $type = TypeCombinator::addNull($type);
        }

        // default null props
        if (in_array($path, ['autoloader-suffix', 'gitlab-protocol'], true)) {
            $type = TypeCombinator::addNull($type);
        }

        return $type;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\PHPStan;

use Composer\DependencyResolver\Rule;
use Composer\Package\BasePackage;
use Composer\Package\Link;
use Composer\Semver\Constraint\ConstraintInterface;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\IntegerType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\ObjectType;
use PHPStan\Type\TypeCombinator;
use PhpParser\Node\Identifier;

final class RuleReasonDataReturnTypeExtension implements DynamicMethodReturnTypeExtension
{
    public function getClass(): string
    {
        return Rule::class;
    }

    public function isMethodSupported(MethodReflection $methodReflection): bool
    {
        return strtolower($methodReflection->getName()) === 'getreasondata';
    }

    public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
    {
        $reasonType = $scope->getType(new MethodCall($methodCall->var, new Identifier('getReason')));

        $types = [
            Rule::RULE_ROOT_REQUIRE => new ConstantArrayType([new ConstantStringType('packageName'), new ConstantStringType('constraint')], [new StringType, new ObjectType(ConstraintInterface::class)]),
            Rule::RULE_FIXED => new ConstantArrayType([new ConstantStringType('package')], [new ObjectType(BasePackage::class)]),
            Rule::RULE_PACKAGE_CONFLICT => new ObjectType(Link::class),
            Rule::RULE_PACKAGE_REQUIRES => new ObjectType(Link::class),
            Rule::RULE_PACKAGE_SAME_NAME => TypeCombinator::intersect(new StringType, new AccessoryNonEmptyStringType()),
            Rule::RULE_LEARNED => new IntegerType(),
            Rule::RULE_PACKAGE_ALIAS => new ObjectType(BasePackage::class),
            Rule::RULE_PACKAGE_INVERSE_ALIAS => new ObjectType(BasePackage::class),
        ];

        foreach ($types as $const => $type) {
            if ((new ConstantIntegerType($const))->isSuperTypeOf($reasonType)->yes()) {
                return $type;
            }
        }

        return TypeCombinator::union(...$types);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use Composer\Util\Platform;
use React\Promise\PromiseInterface;
use Composer\Package\PackageInterface;
use Composer\Pcre\Preg;
use RuntimeException;

/**
 * @author BohwaZ <http://bohwaz.net/>
 */
class FossilDownloader extends VcsDownloader
{
    /**
     * @inheritDoc
     */
    protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null): PromiseInterface
    {
        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    protected function doInstall(PackageInterface $package, string $path, string $url): PromiseInterface
    {
        // Ensure we are allowed to use this URL by config
        $this->config->prohibitUrlByConfig($url, $this->io);

        $repoFile = $path . '.fossil';
        $realPath = Platform::realpath($path);

        $this->io->writeError("Cloning ".$package->getSourceReference());
        $this->execute(['fossil', 'clone', '--', $url, $repoFile]);
        $this->execute(['fossil', 'open', '--nested', '--', $repoFile], $realPath);
        $this->execute(['fossil', 'update', '--', (string) $package->getSourceReference()], $realPath);

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface
    {
        // Ensure we are allowed to use this URL by config
        $this->config->prohibitUrlByConfig($url, $this->io);

        $this->io->writeError(" Updating to ".$target->getSourceReference());

        if (!$this->hasMetadataRepository($path)) {
            throw new RuntimeException('The .fslckout file is missing from '.$path.', see https://getcomposer.org/commit-deps for more information');
        }

        $realPath = Platform::realpath($path);
        $this->execute(['fossil', 'pull'], $realPath);
        $this->execute(['fossil', 'up', '--', (string) $target->getSourceReference()], $realPath);

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function getLocalChanges(PackageInterface $package, string $path): ?string
    {
        if (!$this->hasMetadataRepository($path)) {
            return null;
        }

        $this->process->execute(['fossil', 'changes'], $output, Platform::realpath($path));

        $output = trim($output);

        return strlen($output) > 0 ? $output : null;
    }

    /**
     * @inheritDoc
     */
    protected function getCommitLogs(string $fromReference, string $toReference, string $path): string
    {
        $this->execute(['fossil', 'timeline', '-t', 'ci', '-W', '0', '-n', '0', 'before', $toReference], Platform::realpath($path), $output);

        $log = '';
        $match = '/\d\d:\d\d:\d\d\s+\[' . $toReference . '\]/';

        foreach ($this->process->splitLines($output) as $line) {
            if (Preg::isMatch($match, $line)) {
                break;
            }
            $log .= $line;
        }

        return $log;
    }

    /**
     * @param non-empty-list<string> $command
     * @throws RuntimeException
     */
    private function execute(array $command, ?string $cwd = null, ?string &$output = null): void
    {
        if (0 !== $this->process->execute($command, $output, $cwd)) {
            throw new RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput());
        }
    }

    /**
     * @inheritDoc
     */
    protected function hasMetadataRepository(string $path): bool
    {
        return is_file($path . '/.fslckout') || is_file($path . '/_FOSSIL_');
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use Composer\Package\PackageInterface;
use Composer\Util\Platform;
use Symfony\Component\Finder\Finder;
use React\Promise\PromiseInterface;
use Composer\DependencyResolver\Operation\InstallOperation;

/**
 * Base downloader for archives
 *
 * @author Kirill chEbba Chebunin <iam@chebba.org>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 */
abstract class ArchiveDownloader extends FileDownloader
{
    /**
     * @var array<string, true>
     */
    protected $cleanupExecuted = [];

    public function prepare(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface
    {
        unset($this->cleanupExecuted[$package->getName()]);

        return parent::prepare($type, $package, $path, $prevPackage);
    }

    public function cleanup(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface
    {
        $this->cleanupExecuted[$package->getName()] = true;

        return parent::cleanup($type, $package, $path, $prevPackage);
    }

    /**
     * @inheritDoc
     *
     * @throws \RuntimeException
     * @throws \UnexpectedValueException
     */
    public function install(PackageInterface $package, string $path, bool $output = true): PromiseInterface
    {
        if ($output) {
            $this->io->writeError("  - " . InstallOperation::format($package) . $this->getInstallOperationAppendix($package, $path));
        }

        $vendorDir = $this->config->get('vendor-dir');

        // clean up the target directory, unless it contains the vendor dir, as the vendor dir contains
        // the archive to be extracted. This is the case when installing with create-project in the current directory
        // but in that case we ensure the directory is empty already in ProjectInstaller so no need to empty it here.
        if (false === strpos($this->filesystem->normalizePath($vendorDir), $this->filesystem->normalizePath($path.DIRECTORY_SEPARATOR))) {
            $this->filesystem->emptyDirectory($path);
        }

        do {
            $temporaryDir = $vendorDir.'/composer/'.bin2hex(random_bytes(4));
        } while (is_dir($temporaryDir));

        $this->addCleanupPath($package, $temporaryDir);
        // avoid cleaning up $path if installing in "." for eg create-project as we can not
        // delete the directory we are currently in on windows
        if (!is_dir($path) || realpath($path) !== Platform::getCwd()) {
            $this->addCleanupPath($package, $path);
        }

        $this->filesystem->ensureDirectoryExists($temporaryDir);
        $fileName = $this->getFileName($package, $path);

        $filesystem = $this->filesystem;

        $cleanup = function () use ($path, $filesystem, $temporaryDir, $package) {
            // remove cache if the file was corrupted
            $this->clearLastCacheWrite($package);

            // clean up
            $filesystem->removeDirectory($temporaryDir);
            if (is_dir($path) && realpath($path) !== Platform::getCwd()) {
                $filesystem->removeDirectory($path);
            }
            $this->removeCleanupPath($package, $temporaryDir);
            $realpath = realpath($path);
            if ($realpath !== false) {
                $this->removeCleanupPath($package, $realpath);
            }
        };

        try {
            $promise = $this->extract($package, $fileName, $temporaryDir);
        } catch (\Exception $e) {
            $cleanup();
            throw $e;
        }

        return $promise->then(function () use ($package, $filesystem, $fileName, $temporaryDir, $path): PromiseInterface {
            if (file_exists($fileName)) {
                $filesystem->unlink($fileName);
            }

            /**
             * Returns the folder content, excluding .DS_Store
             *
             * @param  string         $dir Directory
             * @return \SplFileInfo[]
             */
            $getFolderContent = static function ($dir): array {
                $finder = Finder::create()
                    ->ignoreVCS(false)
                    ->ignoreDotFiles(false)
                    ->notName('.DS_Store')
                    ->depth(0)
                    ->in($dir);

                return iterator_to_array($finder);
            };
            $renameRecursively = null;
            /**
             * Renames (and recursively merges if needed) a folder into another one
             *
             * For custom installers, where packages may share paths, and given Composer 2's parallelism, we need to make sure
             * that the source directory gets merged into the target one if the target exists. Otherwise rename() by default would
             * put the source into the target e.g. src/ => target/src/ (assuming target exists) instead of src/ => target/
             *
             * @param  string $from Directory
             * @param  string $to   Directory
             * @return void
             */
            $renameRecursively = static function ($from, $to) use ($filesystem, $getFolderContent, $package, &$renameRecursively) {
                $contentDir = $getFolderContent($from);

                // move files back out of the temp dir
                foreach ($contentDir as $file) {
                    $file = (string) $file;
                    if (is_dir($to . '/' . basename($file))) {
                        if (!is_dir($file)) {
                            throw new \RuntimeException('Installing '.$package.' would lead to overwriting the '.$to.'/'.basename($file).' directory with a file from the package, invalid operation.');
                        }
                        $renameRecursively($file, $to . '/' . basename($file));
                    } else {
                        $filesystem->rename($file, $to . '/' . basename($file));
                    }
                }
            };

            $renameAsOne = false;
            if (!file_exists($path)) {
                $renameAsOne = true;
            } elseif ($filesystem->isDirEmpty($path)) {
                try {
                    if ($filesystem->removeDirectoryPhp($path)) {
                        $renameAsOne = true;
                    }
                } catch (\RuntimeException $e) {
                    // ignore error, and simply do not renameAsOne
                }
            }

            $contentDir = $getFolderContent($temporaryDir);
            $singleDirAtTopLevel = 1 === count($contentDir) && is_dir((string) reset($contentDir));

            if ($renameAsOne) {
                // if the target $path is clear, we can rename the whole package in one go instead of looping over the contents
                if ($singleDirAtTopLevel) {
                    $extractedDir = (string) reset($contentDir);
                } else {
                    $extractedDir = $temporaryDir;
                }
                $filesystem->rename($extractedDir, $path);
            } else {
                // only one dir in the archive, extract its contents out of it
                $from = $temporaryDir;
                if ($singleDirAtTopLevel) {
                    $from = (string) reset($contentDir);
                }

                $renameRecursively($from, $path);
            }

            $promise = $filesystem->removeDirectoryAsync($temporaryDir);

            return $promise->then(function () use ($package, $path, $temporaryDir) {
                $this->removeCleanupPath($package, $temporaryDir);
                $this->removeCleanupPath($package, $path);
            });
        }, static function ($e) use ($cleanup) {
            $cleanup();

            throw $e;
        });
    }

    /**
     * @inheritDoc
     */
    protected function getInstallOperationAppendix(PackageInterface $package, string $path): string
    {
        return ': Extracting archive';
    }

    /**
     * Extract file to directory
     *
     * @param string $file Extracted file
     * @param string $path Directory
     * @phpstan-return PromiseInterface<void|null>
     *
     * @throws \UnexpectedValueException If can not extract downloaded file to path
     */
    abstract protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface;
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use React\Promise\PromiseInterface;
use Composer\Package\PackageInterface;
use Composer\Util\Hg as HgUtils;

/**
 * @author Per Bernhardt <plb@webfactory.de>
 */
class HgDownloader extends VcsDownloader
{
    /**
     * @inheritDoc
     */
    protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null): PromiseInterface
    {
        if (null === HgUtils::getVersion($this->process)) {
            throw new \RuntimeException('hg was not found in your PATH, skipping source download');
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    protected function doInstall(PackageInterface $package, string $path, string $url): PromiseInterface
    {
        $hgUtils = new HgUtils($this->io, $this->config, $this->process);

        $cloneCommand = static function (string $url) use ($path): array {
            return ['hg', 'clone', '--', $url, $path];
        };

        $hgUtils->runCommand($cloneCommand, $url, $path);

        $command = ['hg', 'up', '--', (string) $package->getSourceReference()];
        if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) {
            throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput());
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface
    {
        $hgUtils = new HgUtils($this->io, $this->config, $this->process);

        $ref = $target->getSourceReference();
        $this->io->writeError(" Updating to ".$target->getSourceReference());

        if (!$this->hasMetadataRepository($path)) {
            throw new \RuntimeException('The .hg directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information');
        }

        $command = static function ($url): array {
            return ['hg', 'pull', '--', $url];
        };
        $hgUtils->runCommand($command, $url, $path);

        $command = static function () use ($ref): array {
            return ['hg', 'up', '--', $ref];
        };
        $hgUtils->runCommand($command, $url, $path);

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function getLocalChanges(PackageInterface $package, string $path): ?string
    {
        if (!is_dir($path.'/.hg')) {
            return null;
        }

        $this->process->execute(['hg', 'st'], $output, realpath($path));

        $output = trim($output);

        return strlen($output) > 0 ? $output : null;
    }

    /**
     * @inheritDoc
     */
    protected function getCommitLogs(string $fromReference, string $toReference, string $path): string
    {
        $command = ['hg', 'log', '-r', $fromReference.':'.$toReference, '--style', 'compact'];

        if (0 !== $this->process->execute($command, $output, realpath($path))) {
            throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput());
        }

        return $output;
    }

    /**
     * @inheritDoc
     */
    protected function hasMetadataRepository(string $path): bool
    {
        return is_dir($path . '/.hg');
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use Composer\Package\PackageInterface;
use React\Promise\PromiseInterface;

/**
 * Downloader for tar files: tar, tar.gz or tar.bz2
 *
 * @author Kirill chEbba Chebunin <iam@chebba.org>
 */
class TarDownloader extends ArchiveDownloader
{
    /**
     * @inheritDoc
     */
    protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface
    {
        // Can throw an UnexpectedValueException
        $archive = new \PharData($file);
        $archive->extractTo($path, null, true);

        return \React\Promise\resolve(null);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use React\Promise\PromiseInterface;
use Composer\Package\PackageInterface;
use Composer\Util\Platform;

/**
 * GZip archive downloader.
 *
 * @author Pavel Puchkin <i@neoascetic.me>
 */
class GzipDownloader extends ArchiveDownloader
{
    protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface
    {
        $filename = pathinfo(parse_url(strtr((string) $package->getDistUrl(), '\\', '/'), PHP_URL_PATH), PATHINFO_FILENAME);
        $targetFilepath = $path . DIRECTORY_SEPARATOR . $filename;

        // Try to use gunzip on *nix
        if (!Platform::isWindows()) {
            $command = ['sh', '-c', 'gzip -cd -- "$0" > "$1"', $file, $targetFilepath];

            if (0 === $this->process->execute($command, $ignoredOutput)) {
                return \React\Promise\resolve(null);
            }

            if (extension_loaded('zlib')) {
                // Fallback to using the PHP extension.
                $this->extractUsingExt($file, $targetFilepath);

                return \React\Promise\resolve(null);
            }

            $processError = 'Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput();
            throw new \RuntimeException($processError);
        }

        // Windows version of PHP has built-in support of gzip functions
        $this->extractUsingExt($file, $targetFilepath);

        return \React\Promise\resolve(null);
    }

    private function extractUsingExt(string $file, string $targetFilepath): void
    {
        $archiveFile = gzopen($file, 'rb');
        $targetFile = fopen($targetFilepath, 'wb');
        while ($string = gzread($archiveFile, 4096)) {
            fwrite($targetFile, $string, Platform::strlen($string));
        }
        gzclose($archiveFile);
        fclose($targetFile);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use Composer\Config;
use Composer\Package\Dumper\ArrayDumper;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionGuesser;
use Composer\Package\Version\VersionParser;
use Composer\Util\ProcessExecutor;
use Composer\IO\IOInterface;
use Composer\Util\Filesystem;
use React\Promise\PromiseInterface;
use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\UninstallOperation;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterface, VcsCapableDownloaderInterface
{
    /** @var IOInterface */
    protected $io;
    /** @var Config */
    protected $config;
    /** @var ProcessExecutor */
    protected $process;
    /** @var Filesystem */
    protected $filesystem;
    /** @var array<string, true> */
    protected $hasCleanedChanges = [];

    public function __construct(IOInterface $io, Config $config, ?ProcessExecutor $process = null, ?Filesystem $fs = null)
    {
        $this->io = $io;
        $this->config = $config;
        $this->process = $process ?? new ProcessExecutor($io);
        $this->filesystem = $fs ?? new Filesystem($this->process);
    }

    /**
     * @inheritDoc
     */
    public function getInstallationSource(): string
    {
        return 'source';
    }

    /**
     * @inheritDoc
     */
    public function download(PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface
    {
        if (!$package->getSourceReference()) {
            throw new \InvalidArgumentException('Package '.$package->getPrettyName().' is missing reference information');
        }

        $urls = $this->prepareUrls($package->getSourceUrls());

        while ($url = array_shift($urls)) {
            try {
                return $this->doDownload($package, $path, $url, $prevPackage);
            } catch (\Exception $e) {
                // rethrow phpunit exceptions to avoid hard to debug bug failures
                if ($e instanceof \PHPUnit\Framework\Exception) {
                    throw $e;
                }
                if ($this->io->isDebug()) {
                    $this->io->writeError('Failed: ['.get_class($e).'] '.$e->getMessage());
                } elseif (count($urls)) {
                    $this->io->writeError('    Failed, trying the next URL');
                }
                if (!count($urls)) {
                    throw $e;
                }
            }
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function prepare(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface
    {
        if ($type === 'update') {
            $this->cleanChanges($prevPackage, $path, true);
            $this->hasCleanedChanges[$prevPackage->getUniqueName()] = true;
        } elseif ($type === 'install') {
            $this->filesystem->emptyDirectory($path);
        } elseif ($type === 'uninstall') {
            $this->cleanChanges($package, $path, false);
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function cleanup(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface
    {
        if ($type === 'update' && isset($this->hasCleanedChanges[$prevPackage->getUniqueName()])) {
            $this->reapplyChanges($path);
            unset($this->hasCleanedChanges[$prevPackage->getUniqueName()]);
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function install(PackageInterface $package, string $path): PromiseInterface
    {
        if (!$package->getSourceReference()) {
            throw new \InvalidArgumentException('Package '.$package->getPrettyName().' is missing reference information');
        }

        $this->io->writeError("  - " . InstallOperation::format($package).': ', false);

        $urls = $this->prepareUrls($package->getSourceUrls());
        while ($url = array_shift($urls)) {
            try {
                $this->doInstall($package, $path, $url);
                break;
            } catch (\Exception $e) {
                // rethrow phpunit exceptions to avoid hard to debug bug failures
                if ($e instanceof \PHPUnit\Framework\Exception) {
                    throw $e;
                }
                if ($this->io->isDebug()) {
                    $this->io->writeError('Failed: ['.get_class($e).'] '.$e->getMessage());
                } elseif (count($urls)) {
                    $this->io->writeError('    Failed, trying the next URL');
                }
                if (!count($urls)) {
                    throw $e;
                }
            }
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function update(PackageInterface $initial, PackageInterface $target, string $path): PromiseInterface
    {
        if (!$target->getSourceReference()) {
            throw new \InvalidArgumentException('Package '.$target->getPrettyName().' is missing reference information');
        }

        $this->io->writeError("  - " . UpdateOperation::format($initial, $target).': ', false);

        $urls = $this->prepareUrls($target->getSourceUrls());

        $exception = null;
        while ($url = array_shift($urls)) {
            try {
                $this->doUpdate($initial, $target, $path, $url);

                $exception = null;
                break;
            } catch (\Exception $exception) {
                // rethrow phpunit exceptions to avoid hard to debug bug failures
                if ($exception instanceof \PHPUnit\Framework\Exception) {
                    throw $exception;
                }
                if ($this->io->isDebug()) {
                    $this->io->writeError('Failed: ['.get_class($exception).'] '.$exception->getMessage());
                } elseif (count($urls)) {
                    $this->io->writeError('    Failed, trying the next URL');
                }
            }
        }

        // print the commit logs if in verbose mode and VCS metadata is present
        // because in case of missing metadata code would trigger another exception
        if (!$exception && $this->io->isVerbose() && $this->hasMetadataRepository($path)) {
            $message = 'Pulling in changes:';
            $logs = $this->getCommitLogs($initial->getSourceReference(), $target->getSourceReference(), $path);

            if ('' === trim($logs)) {
                $message = 'Rolling back changes:';
                $logs = $this->getCommitLogs($target->getSourceReference(), $initial->getSourceReference(), $path);
            }

            if ('' !== trim($logs)) {
                $logs = implode("\n", array_map(static function ($line): string {
                    return '      ' . $line;
                }, explode("\n", $logs)));

                // escape angle brackets for proper output in the console
                $logs = str_replace('<', '\<', $logs);

                $this->io->writeError('    '.$message);
                $this->io->writeError($logs);
            }
        }

        if (!$urls && $exception) {
            throw $exception;
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function remove(PackageInterface $package, string $path): PromiseInterface
    {
        $this->io->writeError("  - " . UninstallOperation::format($package));

        $promise = $this->filesystem->removeDirectoryAsync($path);

        return $promise->then(static function (bool $result) use ($path) {
            if (!$result) {
                throw new \RuntimeException('Could not completely delete '.$path.', aborting.');
            }
        });
    }

    /**
     * @inheritDoc
     */
    public function getVcsReference(PackageInterface $package, string $path): ?string
    {
        $parser = new VersionParser;
        $guesser = new VersionGuesser($this->config, $this->process, $parser, $this->io);
        $dumper = new ArrayDumper;

        $packageConfig = $dumper->dump($package);
        if ($packageVersion = $guesser->guessVersion($packageConfig, $path)) {
            return $packageVersion['commit'];
        }

        return null;
    }

    /**
     * Prompt the user to check if changes should be stashed/removed or the operation aborted
     *
     * @param  bool              $update  if true (update) the changes can be stashed and reapplied after an update,
     *                                    if false (remove) the changes should be assumed to be lost if the operation is not aborted
     *
     * @throws \RuntimeException in case the operation must be aborted
     * @phpstan-return PromiseInterface<void|null>
     */
    protected function cleanChanges(PackageInterface $package, string $path, bool $update): PromiseInterface
    {
        // the default implementation just fails if there are any changes, override in child classes to provide stash-ability
        if (null !== $this->getLocalChanges($package, $path)) {
            throw new \RuntimeException('Source directory ' . $path . ' has uncommitted changes.');
        }

        return \React\Promise\resolve(null);
    }

    /**
     * Reapply previously stashes changes if applicable, only called after an update (regardless if successful or not)
     *
     * @throws \RuntimeException in case the operation must be aborted or the patch does not apply cleanly
     */
    protected function reapplyChanges(string $path): void
    {
    }

    /**
     * Downloads data needed to run an install/update later
     *
     * @param PackageInterface      $package     package instance
     * @param string                $path        download path
     * @param string                $url         package url
     * @param PackageInterface|null $prevPackage previous package (in case of an update)
     * @phpstan-return PromiseInterface<void|null>
     */
    abstract protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null): PromiseInterface;

    /**
     * Downloads specific package into specific folder.
     *
     * @param PackageInterface $package package instance
     * @param string           $path    download path
     * @param string           $url     package url
     * @phpstan-return PromiseInterface<void|null>
     */
    abstract protected function doInstall(PackageInterface $package, string $path, string $url): PromiseInterface;

    /**
     * Updates specific package in specific folder from initial to target version.
     *
     * @param PackageInterface $initial initial package
     * @param PackageInterface $target  updated package
     * @param string           $path    download path
     * @param string           $url     package url
     * @phpstan-return PromiseInterface<void|null>
     */
    abstract protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface;

    /**
     * Fetches the commit logs between two commits
     *
     * @param  string $fromReference the source reference
     * @param  string $toReference   the target reference
     * @param  string $path          the package path
     */
    abstract protected function getCommitLogs(string $fromReference, string $toReference, string $path): string;

    /**
     * Checks if VCS metadata repository has been initialized
     * repository example: .git|.svn|.hg
     */
    abstract protected function hasMetadataRepository(string $path): bool;

    /**
     * @param string[] $urls
     *
     * @return string[]
     */
    private function prepareUrls(array $urls): array
    {
        foreach ($urls as $index => $url) {
            if (Filesystem::isLocalPath($url)) {
                // realpath() below will not understand
                // url that starts with "file://"
                $fileProtocol = 'file://';
                $isFileProtocol = false;
                if (0 === strpos($url, $fileProtocol)) {
                    $url = substr($url, strlen($fileProtocol));
                    $isFileProtocol = true;
                }

                // realpath() below will not understand %20 spaces etc.
                if (false !== strpos($url, '%')) {
                    $url = rawurldecode($url);
                }

                $urls[$index] = realpath($url);

                if ($isFileProtocol) {
                    $urls[$index] = $fileProtocol . $urls[$index];
                }
            }
        }

        return $urls;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use Composer\Package\PackageInterface;
use React\Promise\PromiseInterface;

/**
 * Downloader interface.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface DownloaderInterface
{
    /**
     * Returns installation source (either source or dist).
     *
     * @return string "source" or "dist"
     */
    public function getInstallationSource(): string;

    /**
     * This should do any network-related tasks to prepare for an upcoming install/update
     *
     * @param  string $path download path
     * @phpstan-return PromiseInterface<void|null|string>
     */
    public function download(PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface;

    /**
     * Do anything that needs to be done between all downloads have been completed and the actual operation is executed
     *
     * All packages get first downloaded, then all together prepared, then all together installed/updated/uninstalled. Therefore
     * for error recovery it is important to avoid failing during install/update/uninstall as much as possible, and risky things or
     * user prompts should happen in the prepare step rather. In case of failure, cleanup() will be called so that changes can
     * be undone as much as possible.
     *
     * @param  string                $type        one of install/update/uninstall
     * @param  PackageInterface      $package     package instance
     * @param  string                $path        download path
     * @param  PackageInterface      $prevPackage previous package instance in case of an update
     * @phpstan-return PromiseInterface<void|null>
     */
    public function prepare(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface;

    /**
     * Installs specific package into specific folder.
     *
     * @param  PackageInterface      $package package instance
     * @param  string                $path    download path
     * @phpstan-return PromiseInterface<void|null>
     */
    public function install(PackageInterface $package, string $path): PromiseInterface;

    /**
     * Updates specific package in specific folder from initial to target version.
     *
     * @param  PackageInterface      $initial initial package
     * @param  PackageInterface      $target  updated package
     * @param  string                $path    download path
     * @phpstan-return PromiseInterface<void|null>
     */
    public function update(PackageInterface $initial, PackageInterface $target, string $path): PromiseInterface;

    /**
     * Removes specific package from specific folder.
     *
     * @param  PackageInterface      $package package instance
     * @param  string                $path    download path
     * @phpstan-return PromiseInterface<void|null>
     */
    public function remove(PackageInterface $package, string $path): PromiseInterface;

    /**
     * Do anything to cleanup changes applied in the prepare or install/update/uninstall steps
     *
     * Note that cleanup will be called for all packages, either after install/update/uninstall is complete,
     * or if any package failed any operation. This is to give all installers a change to cleanup things
     * they did previously, so you need to keep track of changes applied in the installer/downloader themselves.
     *
     * @param  string                $type        one of install/update/uninstall
     * @param  PackageInterface      $package     package instance
     * @param  string                $path        download path
     * @param  PackageInterface      $prevPackage previous package instance in case of an update
     * @phpstan-return PromiseInterface<void|null>
     */
    public function cleanup(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface;
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

/**
 * Exception thrown when issues exist on local filesystem
 *
 * @author Javier Spagnoletti <jspagnoletti@javierspagnoletti.com.ar>
 */
class FilesystemException extends \Exception
{
    public function __construct(string $message = '', int $code = 0, ?\Exception $previous = null)
    {
        parent::__construct("Filesystem exception: \n".$message, $code, $previous);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use Composer\Package\PackageInterface;

/**
 * DVCS Downloader interface.
 *
 * @author James Titcumb <james@asgrim.com>
 */
interface DvcsDownloaderInterface
{
    /**
     * Checks for unpushed changes to a current branch
     *
     * @param  PackageInterface $package package instance
     * @param  string           $path    package directory
     * @return string|null      changes or null
     */
    public function getUnpushedChanges(PackageInterface $package, string $path): ?string;
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class TransportException extends \RuntimeException
{
    /** @var ?array<string> */
    protected $headers;
    /** @var ?string */
    protected $response;
    /** @var ?int */
    protected $statusCode;
    /** @var array<mixed> */
    protected $responseInfo = [];

    public function __construct(string $message = "", int $code = 400, ?\Throwable $previous = null)
    {
        parent::__construct($message, $code, $previous);
    }

    /**
     * @param array<string> $headers
     */
    public function setHeaders(array $headers): void
    {
        $this->headers = $headers;
    }

    /**
     * @return ?array<string>
     */
    public function getHeaders(): ?array
    {
        return $this->headers;
    }

    public function setResponse(?string $response): void
    {
        $this->response = $response;
    }

    public function getResponse(): ?string
    {
        return $this->response;
    }

    /**
     * @param ?int $statusCode
     */
    public function setStatusCode($statusCode): void
    {
        $this->statusCode = $statusCode;
    }

    public function getStatusCode(): ?int
    {
        return $this->statusCode;
    }

    /**
     * @return array<mixed>
     */
    public function getResponseInfo(): array
    {
        return $this->responseInfo;
    }

    /**
     * @param array<mixed> $responseInfo
     */
    public function setResponseInfo(array $responseInfo): void
    {
        $this->responseInfo = $responseInfo;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use React\Promise\PromiseInterface;
use Composer\Util\IniHelper;
use Composer\Util\Platform;
use Composer\Package\PackageInterface;
use RarArchive;

/**
 * RAR archive downloader.
 *
 * Based on previous work by Jordi Boggiano ({@see ZipDownloader}).
 *
 * @author Derrick Nelson <drrcknlsn@gmail.com>
 */
class RarDownloader extends ArchiveDownloader
{
    protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface
    {
        $processError = null;

        // Try to use unrar on *nix
        if (!Platform::isWindows()) {
            $command = ['sh', '-c', 'unrar x -- "$0" "$1" >/dev/null && chmod -R u+w "$1"', $file, $path];

            if (0 === $this->process->execute($command, $ignoredOutput)) {
                return \React\Promise\resolve(null);
            }

            $processError = 'Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput();
        }

        if (!class_exists('RarArchive')) {
            // php.ini path is added to the error message to help users find the correct file
            $iniMessage = IniHelper::getMessage();

            $error = "Could not decompress the archive, enable the PHP rar extension or install unrar.\n"
                . $iniMessage . "\n" . $processError;

            if (!Platform::isWindows()) {
                $error = "Could not decompress the archive, enable the PHP rar extension.\n" . $iniMessage;
            }

            throw new \RuntimeException($error);
        }

        $rarArchive = RarArchive::open($file);

        if (false === $rarArchive) {
            throw new \UnexpectedValueException('Could not open RAR archive: ' . $file);
        }

        $entries = $rarArchive->getEntries();

        if (false === $entries) {
            throw new \RuntimeException('Could not retrieve RAR archive entries');
        }

        foreach ($entries as $entry) {
            if (false === $entry->extract($path)) {
                throw new \RuntimeException('Could not extract entry');
            }
        }

        $rarArchive->close();

        return \React\Promise\resolve(null);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use React\Promise\PromiseInterface;
use Composer\Package\Archiver\ArchivableFilesFinder;
use Composer\Package\Dumper\ArrayDumper;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionGuesser;
use Composer\Package\Version\VersionParser;
use Composer\Util\Platform;
use Composer\Util\Filesystem;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\UninstallOperation;

/**
 * Download a package from a local path.
 *
 * @author Samuel Roze <samuel.roze@gmail.com>
 * @author Johann Reinke <johann.reinke@gmail.com>
 */
class PathDownloader extends FileDownloader implements VcsCapableDownloaderInterface
{
    private const STRATEGY_SYMLINK = 10;
    private const STRATEGY_MIRROR = 20;

    /**
     * @inheritDoc
     */
    public function download(PackageInterface $package, string $path, ?PackageInterface $prevPackage = null, bool $output = true): PromiseInterface
    {
        $path = Filesystem::trimTrailingSlash($path);
        $url = $package->getDistUrl();
        if (null === $url) {
            throw new \RuntimeException('The package '.$package->getPrettyName().' has no dist url configured, cannot download.');
        }
        $realUrl = realpath($url);
        if (false === $realUrl || !file_exists($realUrl) || !is_dir($realUrl)) {
            throw new \RuntimeException(sprintf(
                'Source path "%s" is not found for package %s',
                $url,
                $package->getName()
            ));
        }

        if (realpath($path) === $realUrl) {
            return \React\Promise\resolve(null);
        }

        if (strpos(realpath($path) . DIRECTORY_SEPARATOR, $realUrl . DIRECTORY_SEPARATOR) === 0) {
            // IMPORTANT NOTICE: If you wish to change this, don't. You are wasting your time and ours.
            //
            // Please see https://github.com/composer/composer/pull/5974 and https://github.com/composer/composer/pull/6174
            // for previous attempts that were shut down because they did not work well enough or introduced too many risks.
            throw new \RuntimeException(sprintf(
                'Package %s cannot install to "%s" inside its source at "%s"',
                $package->getName(),
                realpath($path),
                $realUrl
            ));
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function install(PackageInterface $package, string $path, bool $output = true): PromiseInterface
    {
        $path = Filesystem::trimTrailingSlash($path);
        $url = $package->getDistUrl();
        if (null === $url) {
            throw new \RuntimeException('The package '.$package->getPrettyName().' has no dist url configured, cannot install.');
        }
        $realUrl = realpath($url);
        if (false === $realUrl) {
            throw new \RuntimeException('Failed to realpath '.$url);
        }

        if (realpath($path) === $realUrl) {
            if ($output) {
                $this->io->writeError("  - " . InstallOperation::format($package) . $this->getInstallOperationAppendix($package, $path));
            }

            return \React\Promise\resolve(null);
        }

        // Get the transport options with default values
        $transportOptions = $package->getTransportOptions() + ['relative' => true];

        [$currentStrategy, $allowedStrategies] = $this->computeAllowedStrategies($transportOptions);

        $symfonyFilesystem = new SymfonyFilesystem();
        $this->filesystem->removeDirectory($path);

        if ($output) {
            $this->io->writeError("  - " . InstallOperation::format($package).': ', false);
        }

        $isFallback = false;
        if (self::STRATEGY_SYMLINK === $currentStrategy) {
            try {
                if (Platform::isWindows()) {
                    // Implement symlinks as NTFS junctions on Windows
                    if ($output) {
                        $this->io->writeError(sprintf('Junctioning from %s', $url), false);
                    }
                    $this->filesystem->junction($realUrl, $path);
                } else {
                    $path = rtrim($path, "/");
                    if ($output) {
                        $this->io->writeError(sprintf('Symlinking from %s', $url), false);
                    }
                    if ($transportOptions['relative'] === true) {
                        $absolutePath = $path;
                        if (!$this->filesystem->isAbsolutePath($absolutePath)) {
                            $absolutePath = Platform::getCwd() . DIRECTORY_SEPARATOR . $path;
                        }
                        $shortestPath = $this->filesystem->findShortestPath($absolutePath, $realUrl, false, true);
                        $symfonyFilesystem->symlink($shortestPath.'/', $path);
                    } else {
                        $symfonyFilesystem->symlink($realUrl.'/', $path);
                    }
                }
            } catch (IOException $e) {
                if (in_array(self::STRATEGY_MIRROR, $allowedStrategies, true)) {
                    if ($output) {
                        $this->io->writeError('');
                        $this->io->writeError('    <error>Symlink failed, fallback to use mirroring!</error>');
                    }
                    $currentStrategy = self::STRATEGY_MIRROR;
                    $isFallback = true;
                } else {
                    throw new \RuntimeException(sprintf('Symlink from "%s" to "%s" failed!', $realUrl, $path));
                }
            }
        }

        // Fallback if symlink failed or if symlink is not allowed for the package
        if (self::STRATEGY_MIRROR === $currentStrategy) {
            $realUrl = $this->filesystem->normalizePath($realUrl);

            if ($output) {
                $this->io->writeError(sprintf('%sMirroring from %s', $isFallback ? '    ' : '', $url), false);
            }
            $iterator = new ArchivableFilesFinder($realUrl, []);
            $symfonyFilesystem->mirror($realUrl, $path, $iterator);
        }

        if ($output) {
            $this->io->writeError('');
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function remove(PackageInterface $package, string $path, bool $output = true): PromiseInterface
    {
        $path = Filesystem::trimTrailingSlash($path);
        /**
         * realpath() may resolve Windows junctions to the source path, so we'll check for a junction first
         * to prevent a false positive when checking if the dist and install paths are the same.
         * See https://bugs.php.net/bug.php?id=77639
         *
         * For junctions don't blindly rely on Filesystem::removeDirectory as it may be overzealous. If a process
         * inadvertently locks the file the removal will fail, but it would fall back to recursive delete which
         * is disastrous within a junction. So in that case we have no other real choice but to fail hard.
         */
        if (Platform::isWindows() && $this->filesystem->isJunction($path)) {
            if ($output) {
                $this->io->writeError("  - " . UninstallOperation::format($package).", source is still present in $path");
            }
            if (!$this->filesystem->removeJunction($path)) {
                $this->io->writeError("    <warning>Could not remove junction at " . $path . " - is another process locking it?</warning>");
                throw new \RuntimeException('Could not reliably remove junction for package ' . $package->getName());
            }

            return \React\Promise\resolve(null);
        }

        $url = $package->getDistUrl();
        if (null === $url) {
            throw new \RuntimeException('The package '.$package->getPrettyName().' has no dist url configured, cannot remove.');
        }

        // ensure that the source path (dist url) is not the same as the install path, which
        // can happen when using custom installers, see https://github.com/composer/composer/pull/9116
        // not using realpath here as we do not want to resolve the symlink to the original dist url
        // it points to
        $fs = new Filesystem;
        $absPath = $fs->isAbsolutePath($path) ? $path : Platform::getCwd() . '/' . $path;
        $absDistUrl = $fs->isAbsolutePath($url) ? $url : Platform::getCwd() . '/' . $url;
        if ($fs->normalizePath($absPath) === $fs->normalizePath($absDistUrl)) {
            if ($output) {
                $this->io->writeError("  - " . UninstallOperation::format($package).", source is still present in $path");
            }

            return \React\Promise\resolve(null);
        }

        return parent::remove($package, $path, $output);
    }

    /**
     * @inheritDoc
     */
    public function getVcsReference(PackageInterface $package, string $path): ?string
    {
        $path = Filesystem::trimTrailingSlash($path);
        $parser = new VersionParser;
        $guesser = new VersionGuesser($this->config, $this->process, $parser, $this->io);
        $dumper = new ArrayDumper;

        $packageConfig = $dumper->dump($package);
        $packageVersion = $guesser->guessVersion($packageConfig, $path);
        if ($packageVersion !== null) {
            return $packageVersion['commit'];
        }

        return null;
    }

    /**
     * @inheritDoc
     */
    protected function getInstallOperationAppendix(PackageInterface $package, string $path): string
    {
        $url = $package->getDistUrl();
        if (null === $url) {
            throw new \RuntimeException('The package '.$package->getPrettyName().' has no dist url configured, cannot install.');
        }
        $realUrl = realpath($url);
        if (false === $realUrl) {
            throw new \RuntimeException('Failed to realpath '.$url);
        }

        if (realpath($path) === $realUrl) {
            return ': Source already present';
        }

        [$currentStrategy] = $this->computeAllowedStrategies($package->getTransportOptions());

        if ($currentStrategy === self::STRATEGY_SYMLINK) {
            if (Platform::isWindows()) {
                return ': Junctioning from '.$package->getDistUrl();
            }

            return ': Symlinking from '.$package->getDistUrl();
        }

        return ': Mirroring from '.$package->getDistUrl();
    }

    /**
     * @param mixed[] $transportOptions
     *
     * @phpstan-return array{self::STRATEGY_*, non-empty-list<self::STRATEGY_*>}
     */
    private function computeAllowedStrategies(array $transportOptions): array
    {
        // When symlink transport option is null, both symlink and mirror are allowed
        $currentStrategy = self::STRATEGY_SYMLINK;
        $allowedStrategies = [self::STRATEGY_SYMLINK, self::STRATEGY_MIRROR];

        $mirrorPathRepos = Platform::getEnv('COMPOSER_MIRROR_PATH_REPOS');
        if ((bool) $mirrorPathRepos) {
            $currentStrategy = self::STRATEGY_MIRROR;
        }

        $symlinkOption = $transportOptions['symlink'] ?? null;

        if (true === $symlinkOption) {
            $currentStrategy = self::STRATEGY_SYMLINK;
            $allowedStrategies = [self::STRATEGY_SYMLINK];
        } elseif (false === $symlinkOption) {
            $currentStrategy = self::STRATEGY_MIRROR;
            $allowedStrategies = [self::STRATEGY_MIRROR];
        }

        // Check we can use junctions safely if we are on Windows
        if (Platform::isWindows() && self::STRATEGY_SYMLINK === $currentStrategy && !$this->safeJunctions()) {
            if (!in_array(self::STRATEGY_MIRROR, $allowedStrategies, true)) {
                throw new \RuntimeException('You are on an old Windows / old PHP combo which does not allow Composer to use junctions/symlinks and this path repository has symlink:true in its options so copying is not allowed');
            }
            $currentStrategy = self::STRATEGY_MIRROR;
            $allowedStrategies = [self::STRATEGY_MIRROR];
        }

        // Check we can use symlink() otherwise
        if (!Platform::isWindows() && self::STRATEGY_SYMLINK === $currentStrategy && !function_exists('symlink')) {
            if (!in_array(self::STRATEGY_MIRROR, $allowedStrategies, true)) {
                throw new \RuntimeException('Your PHP has the symlink() function disabled which does not allow Composer to use symlinks and this path repository has symlink:true in its options so copying is not allowed');
            }
            $currentStrategy = self::STRATEGY_MIRROR;
            $allowedStrategies = [self::STRATEGY_MIRROR];
        }

        return [$currentStrategy, $allowedStrategies];
    }

    /**
     * Returns true if junctions can be created and safely used on Windows
     *
     * A PHP bug makes junction detection fragile, leading to possible data loss
     * when removing a package. See https://bugs.php.net/bug.php?id=77552
     *
     * For safety we require a minimum version of Windows 7, so we can call the
     * system rmdir which will preserve target content if given a junction.
     *
     * The PHP bug was fixed in 7.2.16 and 7.3.3 (requires at least Windows 7).
     */
    private function safeJunctions(): bool
    {
        // We need to call mklink, and rmdir on Windows 7 (version 6.1)
        return function_exists('proc_open') &&
            (PHP_WINDOWS_VERSION_MAJOR > 6 ||
            (PHP_WINDOWS_VERSION_MAJOR === 6 && PHP_WINDOWS_VERSION_MINOR >= 1));
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use Composer\Package\PackageInterface;
use Composer\Pcre\Preg;
use Composer\Util\IniHelper;
use Composer\Util\Platform;
use Symfony\Component\Process\ExecutableFinder;
use Symfony\Component\Process\Process;
use React\Promise\PromiseInterface;
use ZipArchive;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ZipDownloader extends ArchiveDownloader
{
    /** @var array<int, non-empty-list<string>> */
    private static $unzipCommands;
    /** @var bool */
    private static $hasZipArchive;
    /** @var bool */
    private static $isWindows;

    /** @var ZipArchive|null */
    private $zipArchiveObject; // @phpstan-ignore property.onlyRead (helper property that is set via reflection for testing purposes)

    /**
     * @inheritDoc
     */
    public function download(PackageInterface $package, string $path, ?PackageInterface $prevPackage = null, bool $output = true): PromiseInterface
    {
        if (null === self::$unzipCommands) {
            self::$unzipCommands = [];
            $finder = new ExecutableFinder;
            if (Platform::isWindows() && null !== ($cmd = $finder->find('7z', null, ['C:\Program Files\7-Zip']))) {
                self::$unzipCommands[] = ['7z', $cmd, 'x', '-bb0', '-y', '%file%', '-o%path%'];
            }
            if (null !== ($cmd = $finder->find('unzip'))) {
                self::$unzipCommands[] = ['unzip', $cmd, '-qq', '%file%', '-d', '%path%'];
            }
            if (!Platform::isWindows() && null !== ($cmd = $finder->find('7z'))) { // 7z linux/macOS support is only used if unzip is not present
                self::$unzipCommands[] = ['7z', $cmd, 'x', '-bb0', '-y', '%file%', '-o%path%'];
            } elseif (!Platform::isWindows() && null !== ($cmd = $finder->find('7zz'))) { // 7zz linux/macOS support is only used if unzip is not present
                self::$unzipCommands[] = ['7zz', $cmd, 'x', '-bb0', '-y', '%file%', '-o%path%'];
            } elseif (!Platform::isWindows() && null !== ($cmd = $finder->find('7za'))) { // 7za linux/macOS support is only used if unzip is not present
                self::$unzipCommands[] = ['7za', $cmd, 'x', '-bb0', '-y', '%file%', '-o%path%'];
            }
        }

        $procOpenMissing = false;
        if (!function_exists('proc_open')) {
            self::$unzipCommands = [];
            $procOpenMissing = true;
        }

        if (null === self::$hasZipArchive) {
            self::$hasZipArchive = class_exists('ZipArchive');
        }

        if (!self::$hasZipArchive && !self::$unzipCommands) {
            // php.ini path is added to the error message to help users find the correct file
            $iniMessage = IniHelper::getMessage();
            if ($procOpenMissing) {
                $error = "The zip extension is missing and unzip/7z commands cannot be called as proc_open is disabled, skipping.\n" . $iniMessage;
            } else {
                $error = "The zip extension and unzip/7z commands are both missing, skipping.\n" . $iniMessage;
            }

            throw new \RuntimeException($error);
        }

        if (null === self::$isWindows) {
            self::$isWindows = Platform::isWindows();

            if (!self::$isWindows && !self::$unzipCommands) {
                if ($procOpenMissing) {
                    $this->io->writeError("<warning>proc_open is disabled so 'unzip' and '7z' commands cannot be used, zip files are being unpacked using the PHP zip extension.</warning>");
                    $this->io->writeError("<warning>This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost.</warning>");
                    $this->io->writeError("<warning>Enabling proc_open and installing 'unzip' or '7z' (21.01+) may remediate them.</warning>");
                } else {
                    $this->io->writeError("<warning>As there is no 'unzip' nor '7z' command installed zip files are being unpacked using the PHP zip extension.</warning>");
                    $this->io->writeError("<warning>This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost.</warning>");
                    $this->io->writeError("<warning>Installing 'unzip' or '7z' (21.01+) may remediate them.</warning>");
                }
            }
        }

        return parent::download($package, $path, $prevPackage, $output);
    }

    /**
     * extract $file to $path with "unzip" command
     *
     * @param  string           $file File to extract
     * @param  string           $path Path where to extract file
     * @phpstan-return PromiseInterface<void|null>
     */
    private function extractWithSystemUnzip(PackageInterface $package, string $file, string $path): PromiseInterface
    {
        static $warned7ZipLinux = false;

        // Force Exception throwing if the other alternative extraction method is not available
        $isLastChance = !self::$hasZipArchive;

        if (0 === \count(self::$unzipCommands)) {
            // This was call as the favorite extract way, but is not available
            // We switch to the alternative
            return $this->extractWithZipArchive($package, $file, $path);
        }

        $commandSpec = reset(self::$unzipCommands);
        $executable = $commandSpec[0];
        $command = array_slice($commandSpec, 1);
        $map = [
            // normalize separators to backslashes to avoid problems with 7-zip on windows
            // see https://github.com/composer/composer/issues/10058
            '%file%' => strtr($file, '/', DIRECTORY_SEPARATOR),
            '%path%' => strtr($path, '/', DIRECTORY_SEPARATOR),
        ];
        $command = array_map(static function ($value) use ($map) {
            return strtr($value, $map);
        }, $command);

        if (!$warned7ZipLinux && !Platform::isWindows() && in_array($executable, ['7z', '7zz', '7za'], true)) {
            $warned7ZipLinux = true;
            if (0 === $this->process->execute([$commandSpec[1]], $output)) {
                if (Preg::isMatchStrictGroups('{^\s*7-Zip(?: \[64\])? ([0-9.]+)}', $output, $match) && version_compare($match[1], '21.01', '<')) {
                    $this->io->writeError('    <warning>Unzipping using '.$executable.' '.$match[1].' may result in incorrect file permissions. Install '.$executable.' 21.01+ or unzip to ensure you get correct permissions.</warning>');
                }
            }
        }

        $io = $this->io;
        $tryFallback = function (\Throwable $processError) use ($isLastChance, $io, $file, $path, $package, $executable): PromiseInterface {
            if ($isLastChance) {
                throw $processError;
            }

            if (str_contains($processError->getMessage(), 'zip bomb')) {
                throw $processError;
            }

            if (!is_file($file)) {
                $io->writeError('    <warning>'.$processError->getMessage().'</warning>');
                $io->writeError('    <warning>This most likely is due to a custom installer plugin not handling the returned Promise from the downloader</warning>');
                $io->writeError('    <warning>See https://github.com/composer/installers/commit/5006d0c28730ade233a8f42ec31ac68fb1c5c9bb for an example fix</warning>');
            } else {
                $io->writeError('    <warning>'.$processError->getMessage().'</warning>');
                $io->writeError('    The archive may contain identical file names with different capitalization (which fails on case insensitive filesystems)');
                $io->writeError('    Unzip with '.$executable.' command failed, falling back to ZipArchive class');

                // additional debug data to try to figure out GH actions issues https://github.com/composer/composer/issues/11148
                if (Platform::getEnv('GITHUB_ACTIONS') !== false && Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING') === false) {
                    $io->writeError('    <warning>Additional debug info, please report to https://github.com/composer/composer/issues/11148 if you see this:</warning>');
                    $io->writeError('File size: '.@filesize($file));
                    $io->writeError('File SHA1: '.hash_file('sha1', $file));
                    $io->writeError('First 100 bytes (hex): '.bin2hex(substr((string) file_get_contents($file), 0, 100)));
                    $io->writeError('Last 100 bytes (hex): '.bin2hex(substr((string) file_get_contents($file), -100)));
                    if (strlen((string) $package->getDistUrl()) > 0) {
                        $io->writeError('Origin URL: '.$this->processUrl($package, (string) $package->getDistUrl()));
                        $io->writeError('Response Headers: '.json_encode(FileDownloader::$responseHeaders[$package->getName()] ?? []));
                    }
                }
            }

            return $this->extractWithZipArchive($package, $file, $path);
        };

        try {
            $promise = $this->process->executeAsync($command);

            return $promise->then(function (Process $process) use ($tryFallback, $command, $package, $file) {
                if (!$process->isSuccessful()) {
                    if (isset($this->cleanupExecuted[$package->getName()])) {
                        throw new \RuntimeException('Failed to extract '.$package->getName().' as the installation was aborted by another package operation.');
                    }

                    $output = $process->getErrorOutput();
                    $output = str_replace(', '.$file.'.zip or '.$file.'.ZIP', '', $output);

                    return $tryFallback(new \RuntimeException('Failed to extract '.$package->getName().': ('.$process->getExitCode().') '.implode(' ', $command)."\n\n".$output));
                }
            });
        } catch (\Throwable $e) {
            return $tryFallback($e);
        }
    }

    /**
     * extract $file to $path with ZipArchive
     *
     * @param  string           $file File to extract
     * @param  string           $path Path where to extract file
     * @phpstan-return PromiseInterface<void|null>
     */
    private function extractWithZipArchive(PackageInterface $package, string $file, string $path): PromiseInterface
    {
        $processError = null;
        $zipArchive = $this->zipArchiveObject ?: new ZipArchive();

        try {
            if (!file_exists($file) || ($filesize = filesize($file)) === false || $filesize === 0) {
                $retval = -1;
            } else {
                $retval = $zipArchive->open($file);
            }

            if (true === $retval) {
                $totalSize = 0;
                $archiveSize = filesize($file);
                $totalFiles = $zipArchive->count();
                if ($totalFiles > 0) {
                    $inspectAll = false;
                    $filesToInspect = min($totalFiles, 5);
                    for ($i = 0; $i < $filesToInspect; $i++) {
                        $stat = $zipArchive->statIndex($inspectAll ? $i : random_int(0, $totalFiles - 1));
                        if ($stat === false) {
                            continue;
                        }
                        $totalSize += $stat['size'];
                        if (!$inspectAll && $stat['size'] > $stat['comp_size'] * 200) {
                            $totalSize = 0;
                            $inspectAll = true;
                            $i = -1;
                            $filesToInspect = $totalFiles;
                        }
                    }
                    if ($archiveSize !== false && $totalSize > $archiveSize * 100 && $totalSize > 50 * 1024 * 1024) {
                        throw new \RuntimeException('Invalid zip file for "'.$package->getName().'" with compression ratio >99% (possible zip bomb)');
                    }
                }

                $extractResult = $zipArchive->extractTo($path);

                if (true === $extractResult) {
                    $zipArchive->close();

                    return \React\Promise\resolve(null);
                }

                $processError = new \RuntimeException(rtrim("There was an error extracting the ZIP file for \"{$package->getName()}\", it is either corrupted or using an invalid format.\n"));
            } else {
                $processError = new \UnexpectedValueException(rtrim($this->getErrorMessage($retval, $file)."\n"), $retval);
            }
        } catch (\ErrorException $e) {
            $processError = new \RuntimeException('The archive for "'.$package->getName().'" may contain identical file names with different capitalization (which fails on case insensitive filesystems): '.$e->getMessage(), 0, $e);
        } catch (\Throwable $e) {
            $processError = $e;
        }

        throw $processError;
    }

    /**
     * extract $file to $path
     *
     * @param  string                $file File to extract
     * @param  string                $path Path where to extract file
     */
    protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface
    {
        return $this->extractWithSystemUnzip($package, $file, $path);
    }

    /**
     * Give a meaningful error message to the user.
     */
    protected function getErrorMessage(int $retval, string $file): string
    {
        switch ($retval) {
            case ZipArchive::ER_EXISTS:
                return sprintf("File '%s' already exists.", $file);
            case ZipArchive::ER_INCONS:
                return sprintf("Zip archive '%s' is inconsistent.", $file);
            case ZipArchive::ER_INVAL:
                return sprintf("Invalid argument (%s)", $file);
            case ZipArchive::ER_MEMORY:
                return sprintf("Malloc failure (%s)", $file);
            case ZipArchive::ER_NOENT:
                return sprintf("No such zip file: '%s'", $file);
            case ZipArchive::ER_NOZIP:
                return sprintf("'%s' is not a zip archive.", $file);
            case ZipArchive::ER_OPEN:
                return sprintf("Can't open zip file: %s", $file);
            case ZipArchive::ER_READ:
                return sprintf("Zip read error (%s)", $file);
            case ZipArchive::ER_SEEK:
                return sprintf("Zip seek error (%s)", $file);
            case -1:
                return sprintf("'%s' is a corrupted zip archive (0 bytes), try again.", $file);
            default:
                return sprintf("'%s' is not a valid zip archive, got error code: %s", $file, $retval);
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use Composer\Package\PackageInterface;
use Composer\IO\IOInterface;
use Composer\Pcre\Preg;
use Composer\Util\Filesystem;
use Composer\Exception\IrrecoverableDownloadException;
use React\Promise\PromiseInterface;

/**
 * Downloaders manager.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class DownloadManager
{
    /** @var IOInterface */
    private $io;
    /** @var bool */
    private $preferDist = false;
    /** @var bool */
    private $preferSource;
    /** @var array<string, string> */
    private $packagePreferences = [];
    /** @var Filesystem */
    private $filesystem;
    /** @var array<string, DownloaderInterface> */
    private $downloaders = [];

    /**
     * Initializes download manager.
     *
     * @param IOInterface     $io           The Input Output Interface
     * @param bool            $preferSource prefer downloading from source
     * @param Filesystem|null $filesystem   custom Filesystem object
     */
    public function __construct(IOInterface $io, bool $preferSource = false, ?Filesystem $filesystem = null)
    {
        $this->io = $io;
        $this->preferSource = $preferSource;
        $this->filesystem = $filesystem ?: new Filesystem();
    }

    /**
     * Makes downloader prefer source installation over the dist.
     *
     * @param  bool            $preferSource prefer downloading from source
     */
    public function setPreferSource(bool $preferSource): self
    {
        $this->preferSource = $preferSource;

        return $this;
    }

    /**
     * Makes downloader prefer dist installation over the source.
     *
     * @param  bool            $preferDist prefer downloading from dist
     */
    public function setPreferDist(bool $preferDist): self
    {
        $this->preferDist = $preferDist;

        return $this;
    }

    /**
     * Sets fine tuned preference settings for package level source/dist selection.
     *
     * @param array<string, string> $preferences array of preferences by package patterns
     */
    public function setPreferences(array $preferences): self
    {
        $this->packagePreferences = $preferences;

        return $this;
    }

    /**
     * Sets installer downloader for a specific installation type.
     *
     * @param  string              $type       installation type
     * @param  DownloaderInterface $downloader downloader instance
     */
    public function setDownloader(string $type, DownloaderInterface $downloader): self
    {
        $type = strtolower($type);
        $this->downloaders[$type] = $downloader;

        return $this;
    }

    /**
     * Returns downloader for a specific installation type.
     *
     * @param  string                    $type installation type
     * @throws \InvalidArgumentException if downloader for provided type is not registered
     */
    public function getDownloader(string $type): DownloaderInterface
    {
        $type = strtolower($type);
        if (!isset($this->downloaders[$type])) {
            throw new \InvalidArgumentException(sprintf('Unknown downloader type: %s. Available types: %s.', $type, implode(', ', array_keys($this->downloaders))));
        }

        return $this->downloaders[$type];
    }

    /**
     * Returns downloader for already installed package.
     *
     * @param  PackageInterface          $package package instance
     * @throws \InvalidArgumentException if package has no installation source specified
     * @throws \LogicException           if specific downloader used to load package with
     *                                           wrong type
     */
    public function getDownloaderForPackage(PackageInterface $package): ?DownloaderInterface
    {
        $installationSource = $package->getInstallationSource();

        if ('metapackage' === $package->getType()) {
            return null;
        }

        if ('dist' === $installationSource) {
            $downloader = $this->getDownloader($package->getDistType());
        } elseif ('source' === $installationSource) {
            $downloader = $this->getDownloader($package->getSourceType());
        } else {
            throw new \InvalidArgumentException(
                'Package '.$package.' does not have an installation source set'
            );
        }

        if ($installationSource !== $downloader->getInstallationSource()) {
            throw new \LogicException(sprintf(
                'Downloader "%s" is a %s type downloader and can not be used to download %s for package %s',
                get_class($downloader),
                $downloader->getInstallationSource(),
                $installationSource,
                $package
            ));
        }

        return $downloader;
    }

    public function getDownloaderType(DownloaderInterface $downloader): string
    {
        return array_search($downloader, $this->downloaders);
    }

    /**
     * Downloads package into target dir.
     *
     * @param PackageInterface      $package     package instance
     * @param string                $targetDir   target dir
     * @param PackageInterface|null $prevPackage previous package instance in case of updates
     * @phpstan-return PromiseInterface<void|null>
     *
     * @throws \InvalidArgumentException if package have no urls to download from
     * @throws \RuntimeException
     */
    public function download(PackageInterface $package, string $targetDir, ?PackageInterface $prevPackage = null): PromiseInterface
    {
        $targetDir = $this->normalizeTargetDir($targetDir);
        $this->filesystem->ensureDirectoryExists(dirname($targetDir));

        $sources = $this->getAvailableSources($package, $prevPackage);

        $io = $this->io;

        $download = function ($retry = false) use (&$sources, $io, $package, $targetDir, &$download, $prevPackage) {
            $source = array_shift($sources);
            if ($retry) {
                $io->writeError('    <warning>Now trying to download from ' . $source . '</warning>');
            }
            $package->setInstallationSource($source);

            $downloader = $this->getDownloaderForPackage($package);
            if (!$downloader) {
                return \React\Promise\resolve(null);
            }

            $handleError = static function ($e) use ($sources, $source, $package, $io, $download) {
                if ($e instanceof \RuntimeException && !$e instanceof IrrecoverableDownloadException) {
                    if (!$sources) {
                        throw $e;
                    }

                    $io->writeError(
                        '    <warning>Failed to download '.
                        $package->getPrettyName().
                        ' from ' . $source . ': '.
                        $e->getMessage().'</warning>'
                    );

                    return $download(true);
                }

                throw $e;
            };

            try {
                $result = $downloader->download($package, $targetDir, $prevPackage);
            } catch (\Exception $e) {
                return $handleError($e);
            }

            $res = $result->then(static function ($res) {
                return $res;
            }, $handleError);

            return $res;
        };

        return $download();
    }

    /**
     * Prepares an operation execution
     *
     * @param string                $type        one of install/update/uninstall
     * @param PackageInterface      $package     package instance
     * @param string                $targetDir   target dir
     * @param PackageInterface|null $prevPackage previous package instance in case of updates
     * @phpstan-return PromiseInterface<void|null>
     */
    public function prepare(string $type, PackageInterface $package, string $targetDir, ?PackageInterface $prevPackage = null): PromiseInterface
    {
        $targetDir = $this->normalizeTargetDir($targetDir);
        $downloader = $this->getDownloaderForPackage($package);
        if ($downloader) {
            return $downloader->prepare($type, $package, $targetDir, $prevPackage);
        }

        return \React\Promise\resolve(null);
    }

    /**
     * Installs package into target dir.
     *
     * @param PackageInterface $package   package instance
     * @param string           $targetDir target dir
     * @phpstan-return PromiseInterface<void|null>
     *
     * @throws \InvalidArgumentException if package have no urls to download from
     * @throws \RuntimeException
     */
    public function install(PackageInterface $package, string $targetDir): PromiseInterface
    {
        $targetDir = $this->normalizeTargetDir($targetDir);
        $downloader = $this->getDownloaderForPackage($package);
        if ($downloader) {
            return $downloader->install($package, $targetDir);
        }

        return \React\Promise\resolve(null);
    }

    /**
     * Updates package from initial to target version.
     *
     * @param PackageInterface $initial   initial package version
     * @param PackageInterface $target    target package version
     * @param string           $targetDir target dir
     * @phpstan-return PromiseInterface<void|null>
     *
     * @throws \InvalidArgumentException if initial package is not installed
     */
    public function update(PackageInterface $initial, PackageInterface $target, string $targetDir): PromiseInterface
    {
        $targetDir = $this->normalizeTargetDir($targetDir);
        $downloader = $this->getDownloaderForPackage($target);
        $initialDownloader = $this->getDownloaderForPackage($initial);

        // no downloaders present means update from metapackage to metapackage, nothing to do
        if (!$initialDownloader && !$downloader) {
            return \React\Promise\resolve(null);
        }

        // if we have a downloader present before, but not after, the package became a metapackage and its files should be removed
        if (!$downloader) {
            return $initialDownloader->remove($initial, $targetDir);
        }

        $initialType = $this->getDownloaderType($initialDownloader);
        $targetType = $this->getDownloaderType($downloader);
        if ($initialType === $targetType) {
            try {
                return $downloader->update($initial, $target, $targetDir);
            } catch (\RuntimeException $e) {
                if (!$this->io->isInteractive()) {
                    throw $e;
                }
                $this->io->writeError('<error>    Update failed ('.$e->getMessage().')</error>');
                if (!$this->io->askConfirmation('    Would you like to try reinstalling the package instead [<comment>yes</comment>]? ')) {
                    throw $e;
                }
            }
        }

        // if downloader type changed, or update failed and user asks for reinstall,
        // we wipe the dir and do a new install instead of updating it
        $promise = $initialDownloader->remove($initial, $targetDir);

        return $promise->then(function ($res) use ($target, $targetDir): PromiseInterface {
            return $this->install($target, $targetDir);
        });
    }

    /**
     * Removes package from target dir.
     *
     * @param PackageInterface $package   package instance
     * @param string           $targetDir target dir
     * @phpstan-return PromiseInterface<void|null>
     */
    public function remove(PackageInterface $package, string $targetDir): PromiseInterface
    {
        $targetDir = $this->normalizeTargetDir($targetDir);
        $downloader = $this->getDownloaderForPackage($package);
        if ($downloader) {
            return $downloader->remove($package, $targetDir);
        }

        return \React\Promise\resolve(null);
    }

    /**
     * Cleans up a failed operation
     *
     * @param string                $type        one of install/update/uninstall
     * @param PackageInterface      $package     package instance
     * @param string                $targetDir   target dir
     * @param PackageInterface|null $prevPackage previous package instance in case of updates
     * @phpstan-return PromiseInterface<void|null>
     */
    public function cleanup(string $type, PackageInterface $package, string $targetDir, ?PackageInterface $prevPackage = null): PromiseInterface
    {
        $targetDir = $this->normalizeTargetDir($targetDir);
        $downloader = $this->getDownloaderForPackage($package);
        if ($downloader) {
            return $downloader->cleanup($type, $package, $targetDir, $prevPackage);
        }

        return \React\Promise\resolve(null);
    }

    /**
     * Determines the install preference of a package
     *
     * @param PackageInterface $package package instance
     */
    protected function resolvePackageInstallPreference(PackageInterface $package): string
    {
        foreach ($this->packagePreferences as $pattern => $preference) {
            $pattern = '{^'.str_replace('\\*', '.*', preg_quote($pattern)).'$}i';
            if (Preg::isMatch($pattern, $package->getName())) {
                if ('dist' === $preference || (!$package->isDev() && 'auto' === $preference)) {
                    return 'dist';
                }

                return 'source';
            }
        }

        return $package->isDev() ? 'source' : 'dist';
    }

    /**
     * @return string[]
     * @phpstan-return array<'dist'|'source'>&non-empty-array
     */
    private function getAvailableSources(PackageInterface $package, ?PackageInterface $prevPackage = null): array
    {
        $sourceType = $package->getSourceType();
        $distType = $package->getDistType();

        // add source before dist by default
        $sources = [];
        if ($sourceType) {
            $sources[] = 'source';
        }
        if ($distType) {
            $sources[] = 'dist';
        }

        if (empty($sources)) {
            throw new \InvalidArgumentException('Package '.$package.' must have a source or dist specified');
        }

        if (
            $prevPackage
            // if we are updating, we want to keep the same source as the previously installed package (if available in the new one)
            && in_array($prevPackage->getInstallationSource(), $sources, true)
            // unless the previous package was stable dist (by default) and the new package is dev, then we allow the new default to take over
            && !(!$prevPackage->isDev() && $prevPackage->getInstallationSource() === 'dist' && $package->isDev())
        ) {
            $prevSource = $prevPackage->getInstallationSource();
            usort($sources, static function ($a, $b) use ($prevSource): int {
                return $a === $prevSource ? -1 : 1;
            });

            return $sources;
        }

        // reverse sources in case dist is the preferred source for this package
        if (!$this->preferSource && ($this->preferDist || 'dist' === $this->resolvePackageInstallPreference($package))) {
            $sources = array_reverse($sources);
        }

        return $sources;
    }

    /**
     * Downloaders expect a /path/to/dir without trailing slash
     *
     * If any Installer provides a path with a trailing slash, this can cause bugs so make sure we remove them
     */
    private function normalizeTargetDir(string $dir): string
    {
        if ($dir === '\\' || $dir === '/') {
            return $dir;
        }

        return rtrim($dir, '\\/');
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use Composer\Package\PackageInterface;

/**
 * VCS Capable Downloader interface.
 *
 * @author Steve Buzonas <steve@fancyguy.com>
 */
interface VcsCapableDownloaderInterface
{
    /**
     * Gets the VCS Reference for the package at path
     *
     * @param  PackageInterface $package package instance
     * @param  string           $path    package directory
     * @return string|null      reference or null
     */
    public function getVcsReference(PackageInterface $package, string $path): ?string;
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use React\Promise\PromiseInterface;
use Composer\Package\PackageInterface;
use Composer\Repository\VcsRepository;
use Composer\Util\Perforce;

/**
 * @author Matt Whittom <Matt.Whittom@veteransunited.com>
 */
class PerforceDownloader extends VcsDownloader
{
    /** @var Perforce|null */
    protected $perforce;

    /**
     * @inheritDoc
     */
    protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null): PromiseInterface
    {
        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function doInstall(PackageInterface $package, string $path, string $url): PromiseInterface
    {
        $ref = $package->getSourceReference();
        $label = $this->getLabelFromSourceReference((string) $ref);

        $this->io->writeError('Cloning ' . $ref);
        $this->initPerforce($package, $path, $url);
        $this->perforce->setStream($ref);
        $this->perforce->p4Login();
        $this->perforce->writeP4ClientSpec();
        $this->perforce->connectClient();
        $this->perforce->syncCodeBase($label);
        $this->perforce->cleanupClientSpec();

        return \React\Promise\resolve(null);
    }

    private function getLabelFromSourceReference(string $ref): ?string
    {
        $pos = strpos($ref, '@');
        if (false !== $pos) {
            return substr($ref, $pos + 1);
        }

        return null;
    }

    public function initPerforce(PackageInterface $package, string $path, string $url): void
    {
        if (!empty($this->perforce)) {
            $this->perforce->initializePath($path);

            return;
        }

        $repository = $package->getRepository();
        $repoConfig = null;
        if ($repository instanceof VcsRepository) {
            $repoConfig = $this->getRepoConfig($repository);
        }
        $this->perforce = Perforce::create($repoConfig, $url, $path, $this->process, $this->io);
    }

    /**
     * @return array<string, mixed>
     */
    private function getRepoConfig(VcsRepository $repository): array
    {
        return $repository->getRepoConfig();
    }

    /**
     * @inheritDoc
     */
    protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface
    {
        return $this->doInstall($target, $path, $url);
    }

    /**
     * @inheritDoc
     */
    public function getLocalChanges(PackageInterface $package, string $path): ?string
    {
        $this->io->writeError('Perforce driver does not check for local changes before overriding');

        return null;
    }

    /**
     * @inheritDoc
     */
    protected function getCommitLogs(string $fromReference, string $toReference, string $path): string
    {
        return $this->perforce->getCommitLogs($fromReference, $toReference);
    }

    public function setPerforce(Perforce $perforce): void
    {
        $this->perforce = $perforce;
    }

    /**
     * @inheritDoc
     */
    protected function hasMetadataRepository(string $path): bool
    {
        return true;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

class MaxFileSizeExceededException extends TransportException
{
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use React\Promise\PromiseInterface;
use Composer\Package\PackageInterface;

/**
 * Downloader for phar files
 *
 * @author Kirill chEbba Chebunin <iam@chebba.org>
 */
class PharDownloader extends ArchiveDownloader
{
    /**
     * @inheritDoc
     */
    protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface
    {
        // Can throw an UnexpectedValueException
        $archive = new \Phar($file);
        $archive->extractTo($path, null, true);
        /* TODO: handle openssl signed phars
         * https://github.com/composer/composer/pull/33#issuecomment-2250768
         * https://github.com/koto/phar-util
         * http://blog.kotowicz.net/2010/08/hardening-php-how-to-securely-include.html
         */

        return \React\Promise\resolve(null);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use Composer\Package\PackageInterface;

/**
 * ChangeReport interface.
 *
 * @author Sascha Egerer <sascha.egerer@dkd.de>
 */
interface ChangeReportInterface
{
    /**
     * Checks for changes to the local copy
     *
     * @param  PackageInterface $package package instance
     * @param  string           $path    package directory
     * @return string|null      changes or null
     */
    public function getLocalChanges(PackageInterface $package, string $path): ?string;
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use Composer\Package\PackageInterface;
use Composer\Pcre\Preg;
use Composer\Util\Svn as SvnUtil;
use Composer\Repository\VcsRepository;
use React\Promise\PromiseInterface;

/**
 * @author Ben Bieker <mail@ben-bieker.de>
 * @author Till Klampaeckel <till@php.net>
 */
class SvnDownloader extends VcsDownloader
{
    /** @var bool */
    protected $cacheCredentials = true;

    /**
     * @inheritDoc
     */
    protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null): PromiseInterface
    {
        SvnUtil::cleanEnv();
        $util = new SvnUtil($url, $this->io, $this->config, $this->process);
        if (null === $util->binaryVersion()) {
            throw new \RuntimeException('svn was not found in your PATH, skipping source download');
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    protected function doInstall(PackageInterface $package, string $path, string $url): PromiseInterface
    {
        SvnUtil::cleanEnv();
        $ref = $package->getSourceReference();

        $repo = $package->getRepository();
        if ($repo instanceof VcsRepository) {
            $repoConfig = $repo->getRepoConfig();
            if (array_key_exists('svn-cache-credentials', $repoConfig)) {
                $this->cacheCredentials = (bool) $repoConfig['svn-cache-credentials'];
            }
        }

        $this->io->writeError(" Checking out ".$package->getSourceReference());
        $this->execute($package, $url, ['svn', 'co'], sprintf("%s/%s", $url, $ref), null, $path);

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface
    {
        SvnUtil::cleanEnv();
        $ref = $target->getSourceReference();

        if (!$this->hasMetadataRepository($path)) {
            throw new \RuntimeException('The .svn directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information');
        }

        $util = new SvnUtil($url, $this->io, $this->config, $this->process);
        $flags = [];
        if (version_compare($util->binaryVersion(), '1.7.0', '>=')) {
            $flags[] = '--ignore-ancestry';
        }

        $this->io->writeError(" Checking out " . $ref);
        $this->execute($target, $url, array_merge(['svn', 'switch'], $flags), sprintf("%s/%s", $url, $ref), $path);

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function getLocalChanges(PackageInterface $package, string $path): ?string
    {
        if (!$this->hasMetadataRepository($path)) {
            return null;
        }

        $this->process->execute(['svn', 'status', '--ignore-externals'], $output, $path);

        return Preg::isMatch('{^ *[^X ] +}m', $output) ? $output : null;
    }

    /**
     * Execute an SVN command and try to fix up the process with credentials
     * if necessary.
     *
     * @param  string            $baseUrl Base URL of the repository
     * @param  non-empty-list<string> $command SVN command to run
     * @param  string            $url     SVN url
     * @param  string            $cwd     Working directory
     * @param  string            $path    Target for a checkout
     * @throws \RuntimeException
     */
    protected function execute(PackageInterface $package, string $baseUrl, array $command, string $url, ?string $cwd = null, ?string $path = null): string
    {
        $util = new SvnUtil($baseUrl, $this->io, $this->config, $this->process);
        $util->setCacheCredentials($this->cacheCredentials);
        try {
            return $util->execute($command, $url, $cwd, $path, $this->io->isVerbose());
        } catch (\RuntimeException $e) {
            throw new \RuntimeException(
                $package->getPrettyName().' could not be downloaded, '.$e->getMessage()
            );
        }
    }

    /**
     * @inheritDoc
     */
    protected function cleanChanges(PackageInterface $package, string $path, bool $update): PromiseInterface
    {
        if (null === ($changes = $this->getLocalChanges($package, $path))) {
            return \React\Promise\resolve(null);
        }

        if (!$this->io->isInteractive()) {
            if (true === $this->config->get('discard-changes')) {
                return $this->discardChanges($path);
            }

            return parent::cleanChanges($package, $path, $update);
        }

        $changes = array_map(static function ($elem): string {
            return '    '.$elem;
        }, Preg::split('{\s*\r?\n\s*}', $changes));
        $countChanges = count($changes);
        $this->io->writeError(sprintf('    <error>'.$package->getPrettyName().' has modified file%s:</error>', $countChanges === 1 ? '' : 's'));
        $this->io->writeError(array_slice($changes, 0, 10));
        if ($countChanges > 10) {
            $remainingChanges = $countChanges - 10;
            $this->io->writeError(
                sprintf(
                    '    <info>'.$remainingChanges.' more file%s modified, choose "v" to view the full list</info>',
                    $remainingChanges === 1 ? '' : 's'
                )
            );
        }

        while (true) {
            switch ($this->io->ask('    <info>Discard changes [y,n,v,?]?</info> ', '?')) {
                case 'y':
                    $this->discardChanges($path);
                    break 2;

                case 'n':
                    throw new \RuntimeException('Update aborted');

                case 'v':
                    $this->io->writeError($changes);
                    break;

                case '?':
                default:
                    $this->io->writeError([
                        '    y - discard changes and apply the '.($update ? 'update' : 'uninstall'),
                        '    n - abort the '.($update ? 'update' : 'uninstall').' and let you manually clean things up',
                        '    v - view modified files',
                        '    ? - print help',
                    ]);
                    break;
            }
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    protected function getCommitLogs(string $fromReference, string $toReference, string $path): string
    {
        if (Preg::isMatch('{@(\d+)$}', $fromReference) && Preg::isMatch('{@(\d+)$}', $toReference)) {
            // retrieve the svn base url from the checkout folder
            $command = ['svn', 'info', '--non-interactive', '--xml', '--', $path];
            if (0 !== $this->process->execute($command, $output, $path)) {
                throw new \RuntimeException(
                    'Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()
                );
            }

            $urlPattern = '#<url>(.*)</url>#';
            if (Preg::isMatchStrictGroups($urlPattern, $output, $matches)) {
                $baseUrl = $matches[1];
            } else {
                throw new \RuntimeException(
                    'Unable to determine svn url for path '. $path
                );
            }

            // strip paths from references and only keep the actual revision
            $fromRevision = Preg::replace('{.*@(\d+)$}', '$1', $fromReference);
            $toRevision = Preg::replace('{.*@(\d+)$}', '$1', $toReference);

            $command = ['svn', 'log', '-r', $fromRevision.':'.$toRevision, '--incremental'];

            $util = new SvnUtil($baseUrl, $this->io, $this->config, $this->process);
            $util->setCacheCredentials($this->cacheCredentials);
            try {
                return $util->executeLocal($command, $path, null, $this->io->isVerbose());
            } catch (\RuntimeException $e) {
                throw new \RuntimeException(
                    'Failed to execute ' . implode(' ', $command) . "\n\n".$e->getMessage()
                );
            }
        }

        return "Could not retrieve changes between $fromReference and $toReference due to missing revision information";
    }

    /**
     * @phpstan-return PromiseInterface<void|null>
     */
    protected function discardChanges(string $path): PromiseInterface
    {
        if (0 !== $this->process->execute(['svn', 'revert', '-R', '.'], $output, $path)) {
            throw new \RuntimeException("Could not reset changes\n\n:".$this->process->getErrorOutput());
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    protected function hasMetadataRepository(string $path): bool
    {
        return is_dir($path.'/.svn');
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use Composer\Config;
use Composer\Cache;
use Composer\IO\IOInterface;
use Composer\IO\NullIO;
use Composer\Exception\IrrecoverableDownloadException;
use Composer\Package\Comparer\Comparer;
use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\UninstallOperation;
use Composer\Package\PackageInterface;
use Composer\Plugin\PluginEvents;
use Composer\Plugin\PostFileDownloadEvent;
use Composer\Plugin\PreFileDownloadEvent;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Util\Filesystem;
use Composer\Util\Http\Response;
use Composer\Util\Platform;
use Composer\Util\Silencer;
use Composer\Util\HttpDownloader;
use Composer\Util\Url as UrlUtil;
use Composer\Util\ProcessExecutor;
use React\Promise\PromiseInterface;

/**
 * Base downloader for files
 *
 * @author Kirill chEbba Chebunin <iam@chebba.org>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 * @author Nils Adermann <naderman@naderman.de>
 */
class FileDownloader implements DownloaderInterface, ChangeReportInterface
{
    /** @var IOInterface */
    protected $io;
    /** @var Config */
    protected $config;
    /** @var HttpDownloader */
    protected $httpDownloader;
    /** @var Filesystem */
    protected $filesystem;
    /** @var ?Cache */
    protected $cache;
    /** @var ?EventDispatcher */
    protected $eventDispatcher;
    /** @var ProcessExecutor */
    protected $process;
    /**
     * @var array<string, int|string>
     * @private
     * @internal
     */
    public static $downloadMetadata = [];
    /**
     * Collects response headers when running on GH Actions
     *
     * @see https://github.com/composer/composer/issues/11148
     * @var array<string, array<string>>
     * @private
     * @internal
     */
    public static $responseHeaders = [];

    /**
     * @var array<string, string> Map of package name to cache key
     */
    private $lastCacheWrites = [];
    /** @var array<string, string[]> Map of package name to list of paths */
    private $additionalCleanupPaths = [];

    /**
     * Constructor.
     *
     * @param IOInterface     $io              The IO instance
     * @param Config          $config          The config
     * @param HttpDownloader  $httpDownloader  The remote filesystem
     * @param EventDispatcher $eventDispatcher The event dispatcher
     * @param Cache           $cache           Cache instance
     * @param Filesystem      $filesystem      The filesystem
     */
    public function __construct(IOInterface $io, Config $config, HttpDownloader $httpDownloader, ?EventDispatcher $eventDispatcher = null, ?Cache $cache = null, ?Filesystem $filesystem = null, ?ProcessExecutor $process = null)
    {
        $this->io = $io;
        $this->config = $config;
        $this->eventDispatcher = $eventDispatcher;
        $this->httpDownloader = $httpDownloader;
        $this->cache = $cache;
        $this->process = $process ?? new ProcessExecutor($io);
        $this->filesystem = $filesystem ?? new Filesystem($this->process);

        if ($this->cache !== null && $this->cache->gcIsNecessary()) {
            $this->io->writeError('Running cache garbage collection', true, IOInterface::VERY_VERBOSE);
            $this->cache->gc($config->get('cache-files-ttl'), $config->get('cache-files-maxsize'));
        }
    }

    /**
     * @inheritDoc
     */
    public function getInstallationSource(): string
    {
        return 'dist';
    }

    /**
     * @inheritDoc
     */
    public function download(PackageInterface $package, string $path, ?PackageInterface $prevPackage = null, bool $output = true): PromiseInterface
    {
        if (null === $package->getDistUrl()) {
            throw new \InvalidArgumentException('The given package is missing url information');
        }

        $cacheKeyGenerator = static function (PackageInterface $package, $key): string {
            $cacheKey = hash('sha1', $key);

            return $package->getName().'/'.$cacheKey.'.'.$package->getDistType();
        };

        $retries = 3;
        $distUrls = $package->getDistUrls();
        /** @var array<array{base: non-empty-string, processed: non-empty-string, cacheKey: string}> $urls */
        $urls = [];
        foreach ($distUrls as $index => $url) {
            $processedUrl = $this->processUrl($package, $url);
            $urls[$index] = [
                'base' => $url,
                'processed' => $processedUrl,
                // we use the complete download url here to avoid conflicting entries
                // from different packages, which would potentially allow a given package
                // in a third party repo to pre-populate the cache for the same package in
                // packagist for example.
                'cacheKey' => $cacheKeyGenerator($package, $processedUrl),
            ];
        }
        assert(count($urls) > 0);

        $fileName = $this->getFileName($package, $path);
        $this->filesystem->ensureDirectoryExists($path);
        $this->filesystem->ensureDirectoryExists(dirname($fileName));

        $accept = null;
        $reject = null;
        $download = function () use ($output, $cacheKeyGenerator, $package, $fileName, &$urls, &$accept, &$reject) {
            $url = reset($urls);
            $index = key($urls);

            if ($this->eventDispatcher !== null) {
                $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $url['processed'], 'package', $package);
                $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent);
                if ($preFileDownloadEvent->getCustomCacheKey() !== null) {
                    $url['cacheKey'] = $cacheKeyGenerator($package, $preFileDownloadEvent->getCustomCacheKey());
                } elseif ($preFileDownloadEvent->getProcessedUrl() !== $url['processed']) {
                    $url['cacheKey'] = $cacheKeyGenerator($package, $preFileDownloadEvent->getProcessedUrl());
                }
                $url['processed'] = $preFileDownloadEvent->getProcessedUrl();
            }

            $urls[$index] = $url;

            $checksum = $package->getDistSha1Checksum();
            $cacheKey = $url['cacheKey'];

            // use from cache if it is present and has a valid checksum or we have no checksum to check against
            if ($this->cache !== null && ($checksum === null || $checksum === '' || $checksum === $this->cache->sha1($cacheKey)) && $this->cache->copyTo($cacheKey, $fileName)) {
                if ($output) {
                    $this->io->writeError("  - Loading <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>) from cache", true, IOInterface::VERY_VERBOSE);
                }
                // mark the file as having been written in cache even though it is only read from cache, so that if
                // the cache is corrupt the archive will be deleted and the next attempt will re-download it
                // see https://github.com/composer/composer/issues/10028
                if (!$this->cache->isReadOnly()) {
                    $this->lastCacheWrites[$package->getName()] = $cacheKey;
                }
                $result = \React\Promise\resolve($fileName);
            } else {
                if ($output) {
                    $this->io->writeError("  - Downloading <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");
                }

                $result = $this->httpDownloader->addCopy($url['processed'], $fileName, $package->getTransportOptions())
                    ->then($accept, $reject);
            }

            return $result->then(function ($result) use ($fileName, $checksum, $url, $package): string {
                // in case of retry, the first call's Promise chain finally calls this twice at the end,
                // once with $result being the returned $fileName from $accept, and then once for every
                // failed request with a null result, which can be skipped.
                if (null === $result) {
                    return $fileName;
                }

                if (!file_exists($fileName)) {
                    throw new \UnexpectedValueException($url['base'].' could not be saved to '.$fileName.', make sure the'
                        .' directory is writable and you have internet connectivity');
                }

                if ($checksum !== null && $checksum !== '' && hash_file('sha1', $fileName) !== $checksum) {
                    throw new \UnexpectedValueException('The checksum verification of the file failed (downloaded from '.$url['base'].')');
                }

                if ($this->eventDispatcher !== null) {
                    $postFileDownloadEvent = new PostFileDownloadEvent(PluginEvents::POST_FILE_DOWNLOAD, $fileName, $checksum, $url['processed'], 'package', $package);
                    $this->eventDispatcher->dispatch($postFileDownloadEvent->getName(), $postFileDownloadEvent);
                }

                return $fileName;
            });
        };

        $accept = function (Response $response) use ($package, $fileName, &$urls): string {
            $url = reset($urls);
            $cacheKey = $url['cacheKey'];
            $fileSize = @filesize($fileName);
            if (false === $fileSize) {
                $fileSize = $response->getHeader('Content-Length') ?? '?';
            }
            FileDownloader::$downloadMetadata[$package->getName()] = $fileSize;

            if (Platform::getEnv('GITHUB_ACTIONS') !== false && Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING') === false) {
                FileDownloader::$responseHeaders[$package->getName()] = $response->getHeaders();
            }

            if ($this->cache !== null && !$this->cache->isReadOnly()) {
                $this->lastCacheWrites[$package->getName()] = $cacheKey;
                $this->cache->copyFrom($cacheKey, $fileName);
            }

            $response->collect();

            return $fileName;
        };

        $reject = function ($e) use (&$urls, $download, $fileName, $package, &$retries) {
            // clean up
            if (file_exists($fileName)) {
                $this->filesystem->unlink($fileName);
            }
            $this->clearLastCacheWrite($package);

            if ($e instanceof IrrecoverableDownloadException) {
                throw $e;
            }

            if ($e instanceof MaxFileSizeExceededException) {
                throw $e;
            }

            if ($e instanceof TransportException) {
                // if we got an http response with a proper code, then requesting again will probably not help, abort
                if (0 !== $e->getCode() && !in_array($e->getCode(), [500, 502, 503, 504], true)) {
                    $retries = 0;
                }
            }

            // special error code returned when network is being artificially disabled
            if ($e instanceof TransportException && $e->getStatusCode() === 499) {
                $retries = 0;
                $urls = [];
            }

            if ($retries > 0) {
                usleep(500000);
                $retries--;

                return $download();
            }

            array_shift($urls);
            if (\count($urls) > 0) {
                if ($this->io->isDebug()) {
                    $this->io->writeError('    Failed downloading '.$package->getName().': ['.get_class($e).'] '.$e->getCode().': '.$e->getMessage());
                    $this->io->writeError('    Trying the next URL for '.$package->getName());
                } else {
                    $this->io->writeError('    Failed downloading '.$package->getName().', trying the next URL ('.$e->getCode().': '.$e->getMessage().')');
                }

                $retries = 3;
                usleep(100000);

                return $download();
            }

            throw $e;
        };

        return $download();
    }

    /**
     * @inheritDoc
     */
    public function prepare(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface
    {
        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function cleanup(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface
    {
        $fileName = $this->getFileName($package, $path);
        if (file_exists($fileName)) {
            $this->filesystem->unlink($fileName);
        }

        $dirsToCleanUp = [
            $path,
            $this->config->get('vendor-dir').'/'.explode('/', $package->getPrettyName())[0],
            $this->config->get('vendor-dir').'/composer/',
            $this->config->get('vendor-dir'),
        ];

        if (isset($this->additionalCleanupPaths[$package->getName()])) {
            foreach ($this->additionalCleanupPaths[$package->getName()] as $pathToClean) {
                $this->filesystem->remove($pathToClean);
            }
        }

        foreach ($dirsToCleanUp as $dir) {
            if (is_dir($dir) && $this->filesystem->isDirEmpty($dir) && realpath($dir) !== Platform::getCwd()) {
                $this->filesystem->removeDirectoryPhp($dir);
            }
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function install(PackageInterface $package, string $path, bool $output = true): PromiseInterface
    {
        if ($output) {
            $this->io->writeError("  - " . InstallOperation::format($package));
        }

        $vendorDir = $this->config->get('vendor-dir');

        // clean up the target directory, unless it contains the vendor dir, as the vendor dir contains
        // the file to be installed. This is the case when installing with create-project in the current directory
        // but in that case we ensure the directory is empty already in ProjectInstaller so no need to empty it here.
        if (false === strpos($this->filesystem->normalizePath($vendorDir), $this->filesystem->normalizePath($path.DIRECTORY_SEPARATOR))) {
            $this->filesystem->emptyDirectory($path);
        }
        $this->filesystem->ensureDirectoryExists($path);
        $this->filesystem->rename($this->getFileName($package, $path), $path . '/' . $this->getDistPath($package, PATHINFO_BASENAME));

        // Single files can not have a mode set like files in archives
        // so we make sure if the file is a binary that it is executable
        foreach ($package->getBinaries() as $bin) {
            if (file_exists($path . '/' . $bin) && !is_executable($path . '/' . $bin)) {
                Silencer::call('chmod', $path . '/' . $bin, 0777 & ~umask());
            }
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @param PATHINFO_EXTENSION|PATHINFO_BASENAME $component
     */
    protected function getDistPath(PackageInterface $package, int $component): string
    {
        return pathinfo((string) parse_url(strtr((string) $package->getDistUrl(), '\\', '/'), PHP_URL_PATH), $component);
    }

    protected function clearLastCacheWrite(PackageInterface $package): void
    {
        if ($this->cache !== null && isset($this->lastCacheWrites[$package->getName()])) {
            $this->cache->remove($this->lastCacheWrites[$package->getName()]);
            unset($this->lastCacheWrites[$package->getName()]);
        }
    }

    protected function addCleanupPath(PackageInterface $package, string $path): void
    {
        $this->additionalCleanupPaths[$package->getName()][] = $path;
    }

    protected function removeCleanupPath(PackageInterface $package, string $path): void
    {
        if (isset($this->additionalCleanupPaths[$package->getName()])) {
            $idx = array_search($path, $this->additionalCleanupPaths[$package->getName()], true);
            if (false !== $idx) {
                unset($this->additionalCleanupPaths[$package->getName()][$idx]);
            }
        }
    }

    /**
     * @inheritDoc
     */
    public function update(PackageInterface $initial, PackageInterface $target, string $path): PromiseInterface
    {
        $this->io->writeError("  - " . UpdateOperation::format($initial, $target) . $this->getInstallOperationAppendix($target, $path));

        $promise = $this->remove($initial, $path, false);

        return $promise->then(function () use ($target, $path): PromiseInterface {
            return $this->install($target, $path, false);
        });
    }

    /**
     * @inheritDoc
     */
    public function remove(PackageInterface $package, string $path, bool $output = true): PromiseInterface
    {
        if ($output) {
            $this->io->writeError("  - " . UninstallOperation::format($package));
        }
        $promise = $this->filesystem->removeDirectoryAsync($path);

        return $promise->then(static function ($result) use ($path): void {
            if (!$result) {
                throw new \RuntimeException('Could not completely delete '.$path.', aborting.');
            }
        });
    }

    /**
     * Gets file name for specific package
     *
     * @param  PackageInterface $package package instance
     * @param  string           $path    download path
     * @return string           file name
     */
    protected function getFileName(PackageInterface $package, string $path): string
    {
        $extension = $this->getDistPath($package, PATHINFO_EXTENSION);
        if ($extension === '') {
            $extension = $package->getDistType();
        }

        return rtrim($this->config->get('vendor-dir') . '/composer/tmp-' . hash('md5', $package . spl_object_hash($package)) . '.' . $extension, '.');
    }

    /**
     * Gets appendix message to add to the "- Upgrading x" string being output on update
     *
     * @param  PackageInterface $package package instance
     * @param  string           $path    download path
     */
    protected function getInstallOperationAppendix(PackageInterface $package, string $path): string
    {
        return '';
    }

    /**
     * Process the download url
     *
     * @param  PackageInterface  $package package instance
     * @param  non-empty-string  $url     download url
     * @throws \RuntimeException If any problem with the url
     * @return non-empty-string  url
     */
    protected function processUrl(PackageInterface $package, string $url): string
    {
        if (!extension_loaded('openssl') && 0 === strpos($url, 'https:')) {
            throw new \RuntimeException('You must enable the openssl extension to download files via https');
        }

        if ($package->getDistReference() !== null) {
            $url = UrlUtil::updateDistReference($this->config, $url, $package->getDistReference());
        }

        return $url;
    }

    /**
     * @inheritDoc
     * @throws \RuntimeException
     */
    public function getLocalChanges(PackageInterface $package, string $path): ?string
    {
        $prevIO = $this->io;

        $this->io = new NullIO;
        $this->io->loadConfiguration($this->config);
        $e = null;
        $output = '';

        $targetDir = Filesystem::trimTrailingSlash($path);
        try {
            if (is_dir($targetDir.'_compare')) {
                $this->filesystem->removeDirectory($targetDir.'_compare');
            }

            $promise = $this->download($package, $targetDir.'_compare', null, false);
            $promise->then(null, static function ($ex) use (&$e) {
                $e = $ex;
            });
            $this->httpDownloader->wait();
            if ($e !== null) {
                throw $e;
            }
            $promise = $this->install($package, $targetDir.'_compare', false);
            $promise->then(null, static function ($ex) use (&$e) {
                $e = $ex;
            });
            $this->process->wait();
            if ($e !== null) {
                throw $e;
            }

            $comparer = new Comparer();
            $comparer->setSource($targetDir.'_compare');
            $comparer->setUpdate($targetDir);
            $comparer->doCompare();
            $output = $comparer->getChangedAsString(true);
            $this->filesystem->removeDirectory($targetDir.'_compare');
        } catch (\Exception $e) {
        }

        $this->io = $prevIO;

        if ($e !== null) {
            if ($this->io->isDebug()) {
                throw $e;
            }

            return 'Failed to detect changes: ['.get_class($e).'] '.$e->getMessage();
        }

        $output = trim($output);

        return strlen($output) > 0 ? $output : null;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Pcre\Preg;
use Composer\Util\Filesystem;
use Composer\Util\Git as GitUtil;
use Composer\Util\Url;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Cache;
use React\Promise\PromiseInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
{
    /**
     * @var bool[]
     * @phpstan-var array<string, bool>
     */
    private $hasStashedChanges = [];
    /**
     * @var bool[]
     * @phpstan-var array<string, bool>
     */
    private $hasDiscardedChanges = [];
    /**
     * @var GitUtil
     */
    private $gitUtil;
    /**
     * @var array
     * @phpstan-var array<int, array<string, bool>>
     */
    private $cachedPackages = [];

    public function __construct(IOInterface $io, Config $config, ?ProcessExecutor $process = null, ?Filesystem $fs = null)
    {
        parent::__construct($io, $config, $process, $fs);
        $this->gitUtil = new GitUtil($this->io, $this->config, $this->process, $this->filesystem);
    }

    /**
     * @inheritDoc
     */
    protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null): PromiseInterface
    {
        // Do not create an extra local cache when repository is already local
        if (Filesystem::isLocalPath($url)) {
            return \React\Promise\resolve(null);
        }

        GitUtil::cleanEnv($this->process);

        $cachePath = $this->config->get('cache-vcs-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($url)).'/';
        $gitVersion = GitUtil::getVersion($this->process);

        // --dissociate option is only available since git 2.3.0-rc0
        if ($gitVersion && version_compare($gitVersion, '2.3.0-rc0', '>=') && Cache::isUsable($cachePath)) {
            $this->io->writeError("  - Syncing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>) into cache");
            $this->io->writeError(sprintf('    Cloning to cache at %s', $cachePath), true, IOInterface::DEBUG);
            $ref = $package->getSourceReference();
            if ($this->gitUtil->fetchRefOrSyncMirror($url, $cachePath, $ref, $package->getPrettyVersion()) && is_dir($cachePath)) {
                $this->cachedPackages[$package->getId()][$ref] = true;
            }
        } elseif (null === $gitVersion) {
            throw new \RuntimeException('git was not found in your PATH, skipping source download');
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    protected function doInstall(PackageInterface $package, string $path, string $url): PromiseInterface
    {
        GitUtil::cleanEnv($this->process);
        $path = $this->normalizePath($path);
        $cachePath = $this->config->get('cache-vcs-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($url)).'/';
        $ref = $package->getSourceReference();

        if (!empty($this->cachedPackages[$package->getId()][$ref])) {
            $msg = "Cloning ".$this->getShortHash($ref).' from cache';

            $cloneFlags = ['--dissociate', '--reference', $cachePath];
            $transportOptions = $package->getTransportOptions();
            if (isset($transportOptions['git']['single_use_clone']) && $transportOptions['git']['single_use_clone']) {
                $cloneFlags = [];
            }

            $commands = [
                array_merge(['git', 'clone', '--no-checkout', $cachePath, $path], $cloneFlags),
                ['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%'],
                ['git', 'remote', 'add', 'composer', '--', '%sanitizedUrl%'],
            ];
        } else {
            $msg = "Cloning ".$this->getShortHash($ref);
            $commands = [
                array_merge(['git', 'clone', '--no-checkout', '--', '%url%', $path]),
                ['git', 'remote', 'add', 'composer', '--', '%url%'],
                ['git', 'fetch', 'composer'],
                ['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%'],
                ['git', 'remote', 'set-url', 'composer', '--', '%sanitizedUrl%'],
            ];
            if (Platform::getEnv('COMPOSER_DISABLE_NETWORK')) {
                throw new \RuntimeException('The required git reference for '.$package->getName().' is not in cache and network is disabled, aborting');
            }
        }

        $this->io->writeError($msg);

        $this->gitUtil->runCommands($commands, $url, $path, true);

        $sourceUrl = $package->getSourceUrl();
        if ($url !== $sourceUrl && $sourceUrl !== null) {
            $this->updateOriginUrl($path, $sourceUrl);
        } else {
            $this->setPushUrl($path, $url);
        }

        if ($newRef = $this->updateToCommit($package, $path, (string) $ref, $package->getPrettyVersion())) {
            if ($package->getDistReference() === $package->getSourceReference()) {
                $package->setDistReference($newRef);
            }
            $package->setSourceReference($newRef);
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface
    {
        GitUtil::cleanEnv($this->process);
        $path = $this->normalizePath($path);
        if (!$this->hasMetadataRepository($path)) {
            throw new \RuntimeException('The .git directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information');
        }

        $cachePath = $this->config->get('cache-vcs-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($url)).'/';
        $ref = $target->getSourceReference();

        if (!empty($this->cachedPackages[$target->getId()][$ref])) {
            $msg = "Checking out ".$this->getShortHash($ref).' from cache';
            $remoteUrl = $cachePath;
        } else {
            $msg = "Checking out ".$this->getShortHash($ref);
            $remoteUrl = '%url%';
            if (Platform::getEnv('COMPOSER_DISABLE_NETWORK')) {
                throw new \RuntimeException('The required git reference for '.$target->getName().' is not in cache and network is disabled, aborting');
            }
        }

        $this->io->writeError($msg);

        if (0 !== $this->process->execute(['git', 'rev-parse', '--quiet', '--verify', $ref.'^{commit}'], $output, $path)) {
            $commands = [
                ['git', 'remote', 'set-url', 'composer', '--', $remoteUrl],
                ['git', 'fetch', 'composer'],
                ['git', 'fetch', '--tags', 'composer'],
            ];

            $this->gitUtil->runCommands($commands, $url, $path);
        }

        $command = ['git', 'remote', 'set-url', 'composer', '--', '%sanitizedUrl%'];
        $this->gitUtil->runCommands([$command], $url, $path);

        if ($newRef = $this->updateToCommit($target, $path, (string) $ref, $target->getPrettyVersion())) {
            if ($target->getDistReference() === $target->getSourceReference()) {
                $target->setDistReference($newRef);
            }
            $target->setSourceReference($newRef);
        }

        $updateOriginUrl = false;
        if (
            0 === $this->process->execute(['git', 'remote', '-v'], $output, $path)
            && Preg::isMatch('{^origin\s+(?P<url>\S+)}m', $output, $originMatch)
            && Preg::isMatch('{^composer\s+(?P<url>\S+)}m', $output, $composerMatch)
        ) {
            if ($originMatch['url'] === $composerMatch['url'] && $composerMatch['url'] !== $target->getSourceUrl()) {
                $updateOriginUrl = true;
            }
        }
        if ($updateOriginUrl && $target->getSourceUrl() !== null) {
            $this->updateOriginUrl($path, $target->getSourceUrl());
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function getLocalChanges(PackageInterface $package, string $path): ?string
    {
        GitUtil::cleanEnv($this->process);
        if (!$this->hasMetadataRepository($path)) {
            return null;
        }

        $command = ['git', 'status', '--porcelain', '--untracked-files=no'];
        if (0 !== $this->process->execute($command, $output, $path)) {
            throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput());
        }

        $output = trim($output);

        return strlen($output) > 0 ? $output : null;
    }

    public function getUnpushedChanges(PackageInterface $package, string $path): ?string
    {
        GitUtil::cleanEnv($this->process);
        $path = $this->normalizePath($path);
        if (!$this->hasMetadataRepository($path)) {
            return null;
        }

        $command = ['git', 'show-ref', '--head', '-d'];
        if (0 !== $this->process->execute($command, $output, $path)) {
            throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput());
        }

        $refs = trim($output);
        if (!Preg::isMatchStrictGroups('{^([a-f0-9]+) HEAD$}mi', $refs, $match)) {
            // could not match the HEAD for some reason
            return null;
        }

        $headRef = $match[1];
        if (!Preg::isMatchAllStrictGroups('{^'.preg_quote($headRef).' refs/heads/(.+)$}mi', $refs, $matches)) {
            // not on a branch, we are either on a not-modified tag or some sort of detached head, so skip this
            return null;
        }

        $candidateBranches = $matches[1];
        // use the first match as branch name for now
        $branch = $candidateBranches[0];
        $unpushedChanges = null;
        $branchNotFoundError = false;

        // do two passes, as if we find anything we want to fetch and then re-try
        for ($i = 0; $i <= 1; $i++) {
            $remoteBranches = [];

            // try to find matching branch names in remote repos
            foreach ($candidateBranches as $candidate) {
                if (Preg::isMatchAllStrictGroups('{^[a-f0-9]+ refs/remotes/((?:[^/]+)/'.preg_quote($candidate).')$}mi', $refs, $matches)) {
                    foreach ($matches[1] as $match) {
                        $branch = $candidate;
                        $remoteBranches[] = $match;
                    }
                    break;
                }
            }

            // if it doesn't exist, then we assume it is an unpushed branch
            // this is bad as we have no reference point to do a diff so we just bail listing
            // the branch as being unpushed
            if (count($remoteBranches) === 0) {
                $unpushedChanges = 'Branch ' . $branch . ' could not be found on any remote and appears to be unpushed';
                $branchNotFoundError = true;
            } else {
                // if first iteration found no remote branch but it has now found some, reset $unpushedChanges
                // so we get the real diff output no matter its length
                if ($branchNotFoundError) {
                    $unpushedChanges = null;
                }
                foreach ($remoteBranches as $remoteBranch) {
                    $command = ['git', 'diff', '--name-status', $remoteBranch.'...'.$branch, '--'];
                    if (0 !== $this->process->execute($command, $output, $path)) {
                        throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput());
                    }

                    $output = trim($output);
                    // keep the shortest diff from all remote branches we compare against
                    if ($unpushedChanges === null || strlen($output) < strlen($unpushedChanges)) {
                        $unpushedChanges = $output;
                    }
                }
            }

            // first pass and we found unpushed changes, fetch from all remotes to make sure we have up to date
            // remotes and then try again as outdated remotes can sometimes cause false-positives
            if ($unpushedChanges && $i === 0) {
                $this->process->execute(['git', 'fetch', '--all'], $output, $path);

                // update list of refs after fetching
                $command = ['git', 'show-ref', '--head', '-d'];
                if (0 !== $this->process->execute($command, $output, $path)) {
                    throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput());
                }
                $refs = trim($output);
            }

            // abort after first pass if we didn't find anything
            if (!$unpushedChanges) {
                break;
            }
        }

        return $unpushedChanges;
    }

    /**
     * @inheritDoc
     */
    protected function cleanChanges(PackageInterface $package, string $path, bool $update): PromiseInterface
    {
        GitUtil::cleanEnv($this->process);
        $path = $this->normalizePath($path);

        $unpushed = $this->getUnpushedChanges($package, $path);
        if ($unpushed && ($this->io->isInteractive() || $this->config->get('discard-changes') !== true)) {
            throw new \RuntimeException('Source directory ' . $path . ' has unpushed changes on the current branch: '."\n".$unpushed);
        }

        if (null === ($changes = $this->getLocalChanges($package, $path))) {
            return \React\Promise\resolve(null);
        }

        if (!$this->io->isInteractive()) {
            $discardChanges = $this->config->get('discard-changes');
            if (true === $discardChanges) {
                return $this->discardChanges($path);
            }
            if ('stash' === $discardChanges) {
                if (!$update) {
                    return parent::cleanChanges($package, $path, $update);
                }

                return $this->stashChanges($path);
            }

            return parent::cleanChanges($package, $path, $update);
        }

        $changes = array_map(static function ($elem): string {
            return '    '.$elem;
        }, Preg::split('{\s*\r?\n\s*}', $changes));
        $this->io->writeError('    <error>'.$package->getPrettyName().' has modified files:</error>');
        $this->io->writeError(array_slice($changes, 0, 10));
        if (count($changes) > 10) {
            $this->io->writeError('    <info>' . (count($changes) - 10) . ' more files modified, choose "v" to view the full list</info>');
        }

        while (true) {
            switch ($this->io->ask('    <info>Discard changes [y,n,v,d,'.($update ? 's,' : '').'?]?</info> ', '?')) {
                case 'y':
                    $this->discardChanges($path);
                    break 2;

                case 's':
                    if (!$update) {
                        goto help;
                    }

                    $this->stashChanges($path);
                    break 2;

                case 'n':
                    throw new \RuntimeException('Update aborted');

                case 'v':
                    $this->io->writeError($changes);
                    break;

                case 'd':
                    $this->viewDiff($path);
                    break;

                case '?':
                default:
                    help :
                    $this->io->writeError([
                        '    y - discard changes and apply the '.($update ? 'update' : 'uninstall'),
                        '    n - abort the '.($update ? 'update' : 'uninstall').' and let you manually clean things up',
                        '    v - view modified files',
                        '    d - view local modifications (diff)',
                    ]);
                    if ($update) {
                        $this->io->writeError('    s - stash changes and try to reapply them after the update');
                    }
                    $this->io->writeError('    ? - print help');
                    break;
            }
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    protected function reapplyChanges(string $path): void
    {
        $path = $this->normalizePath($path);
        if (!empty($this->hasStashedChanges[$path])) {
            unset($this->hasStashedChanges[$path]);
            $this->io->writeError('    <info>Re-applying stashed changes</info>');
            if (0 !== $this->process->execute(['git', 'stash', 'pop'], $output, $path)) {
                throw new \RuntimeException("Failed to apply stashed changes:\n\n".$this->process->getErrorOutput());
            }
        }

        unset($this->hasDiscardedChanges[$path]);
    }

    /**
     * Updates the given path to the given commit ref
     *
     * @throws \RuntimeException
     * @return null|string       if a string is returned, it is the commit reference that was checked out if the original could not be found
     */
    protected function updateToCommit(PackageInterface $package, string $path, string $reference, string $prettyVersion): ?string
    {
        $force = !empty($this->hasDiscardedChanges[$path]) || !empty($this->hasStashedChanges[$path]) ? ['-f'] : [];

        // This uses the "--" sequence to separate branch from file parameters.
        //
        // Otherwise git tries the branch name as well as file name.
        // If the non-existent branch is actually the name of a file, the file
        // is checked out.

        $branch = Preg::replace('{(?:^dev-|(?:\.x)?-dev$)}i', '', $prettyVersion);

        /**
         * @var \Closure(non-empty-list<string>): bool $execute
         * @phpstan-ignore varTag.nativeType
         */
        $execute = function (array $command) use (&$output, $path) {
            /** @var non-empty-list<string> $command */
            $output = '';

            return 0 === $this->process->execute($command, $output, $path);
        };

        $branches = null;
        if ($execute(['git', 'branch', '-r'])) {
            $branches = $output;
        }

        // check whether non-commitish are branches or tags, and fetch branches with the remote name
        $gitRef = $reference;
        if (!Preg::isMatch('{^[a-f0-9]{40}$}', $reference)
            && null !== $branches
            && Preg::isMatch('{^\s+composer/'.preg_quote($reference).'$}m', $branches)
        ) {
            $command1 = array_merge(['git', 'checkout'], $force, ['-B', $branch, 'composer/'.$reference, '--']);
            $command2 = ['git', 'reset', '--hard', 'composer/'.$reference, '--'];

            if ($execute($command1) && $execute($command2)) {
                return null;
            }
        }

        // try to checkout branch by name and then reset it so it's on the proper branch name
        if (Preg::isMatch('{^[a-f0-9]{40}$}', $reference)) {
            // add 'v' in front of the branch if it was stripped when generating the pretty name
            if (null !== $branches && !Preg::isMatch('{^\s+composer/'.preg_quote($branch).'$}m', $branches) && Preg::isMatch('{^\s+composer/v'.preg_quote($branch).'$}m', $branches)) {
                $branch = 'v' . $branch;
            }

            $command = ['git', 'checkout', $branch, '--'];
            $fallbackCommand = array_merge(['git', 'checkout'], $force, ['-B', $branch, 'composer/'.$branch, '--']);
            $resetCommand = ['git', 'reset', '--hard', $reference, '--'];

            if (($execute($command) || $execute($fallbackCommand)) && $execute($resetCommand)) {
                return null;
            }
        }

        $command1 = array_merge(['git', 'checkout'], $force, [$gitRef, '--']);
        $command2 = ['git', 'reset', '--hard', $gitRef, '--'];
        if ($execute($command1) && $execute($command2)) {
            return null;
        }

        $exceptionExtra = '';

        // reference was not found (prints "fatal: reference is not a tree: $ref")
        if (false !== strpos($this->process->getErrorOutput(), $reference)) {
            $this->io->writeError('    <warning>'.$reference.' is gone (history was rewritten?)</warning>');
            $exceptionExtra = "\nIt looks like the commit hash is not available in the repository, maybe ".($package->isDev() ? 'the commit was removed from the branch' : 'the tag was recreated').'? Run "composer update '.$package->getPrettyName().'" to resolve this.';
        }

        $command = implode(' ', $command1). ' && '.implode(' ', $command2);

        throw new \RuntimeException(Url::sanitize('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput() . $exceptionExtra));
    }

    protected function updateOriginUrl(string $path, string $url): void
    {
        $this->process->execute(['git', 'remote', 'set-url', 'origin', '--', $url], $output, $path);
        $this->setPushUrl($path, $url);
    }

    protected function setPushUrl(string $path, string $url): void
    {
        // set push url for github projects
        if (Preg::isMatch('{^(?:https?|git)://'.GitUtil::getGitHubDomainsRegex($this->config).'/([^/]+)/([^/]+?)(?:\.git)?$}', $url, $match)) {
            $protocols = $this->config->get('github-protocols');
            $pushUrl = 'git@'.$match[1].':'.$match[2].'/'.$match[3].'.git';
            if (!in_array('ssh', $protocols, true)) {
                $pushUrl = 'https://' . $match[1] . '/'.$match[2].'/'.$match[3].'.git';
            }
            $cmd = ['git', 'remote', 'set-url', '--push', 'origin', '--', $pushUrl];
            $this->process->execute($cmd, $ignoredOutput, $path);
        }
    }

    /**
     * @inheritDoc
     */
    protected function getCommitLogs(string $fromReference, string $toReference, string $path): string
    {
        $path = $this->normalizePath($path);
        $command = GitUtil::buildRevListCommand($this->process, array_merge(['--format=%h - %an: %s', $fromReference.'..'.$toReference], GitUtil::getNoShowSignatureFlags($this->process)));

        if (0 !== $this->process->execute($command, $output, $path)) {
            throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput());
        }

        return GitUtil::parseRevListOutput($output, $this->process);
    }

    /**
     * @phpstan-return PromiseInterface<void|null>
     * @throws \RuntimeException
     */
    protected function discardChanges(string $path): PromiseInterface
    {
        $path = $this->normalizePath($path);
        if (0 !== $this->process->execute(['git', 'clean', '-df'], $output, $path)) {
            throw new \RuntimeException("Could not reset changes\n\n:".$output);
        }
        if (0 !== $this->process->execute(['git', 'reset', '--hard'], $output, $path)) {
            throw new \RuntimeException("Could not reset changes\n\n:".$output);
        }

        $this->hasDiscardedChanges[$path] = true;

        return \React\Promise\resolve(null);
    }

    /**
     * @phpstan-return PromiseInterface<void|null>
     * @throws \RuntimeException
     */
    protected function stashChanges(string $path): PromiseInterface
    {
        $path = $this->normalizePath($path);
        if (0 !== $this->process->execute(['git', 'stash', '--include-untracked'], $output, $path)) {
            throw new \RuntimeException("Could not stash changes\n\n:".$output);
        }

        $this->hasStashedChanges[$path] = true;

        return \React\Promise\resolve(null);
    }

    /**
     * @throws \RuntimeException
     */
    protected function viewDiff(string $path): void
    {
        $path = $this->normalizePath($path);
        if (0 !== $this->process->execute(['git', 'diff', 'HEAD'], $output, $path)) {
            throw new \RuntimeException("Could not view diff\n\n:".$output);
        }

        $this->io->writeError($output);
    }

    protected function normalizePath(string $path): string
    {
        if (Platform::isWindows() && strlen($path) > 0) {
            $basePath = $path;
            $removed = [];

            while (!is_dir($basePath) && $basePath !== '\\') {
                array_unshift($removed, basename($basePath));
                $basePath = dirname($basePath);
            }

            if ($basePath === '\\') {
                return $path;
            }

            $path = rtrim(realpath($basePath) . '/' . implode('/', $removed), '/');
        }

        return $path;
    }

    /**
     * @inheritDoc
     */
    protected function hasMetadataRepository(string $path): bool
    {
        $path = $this->normalizePath($path);

        return is_dir($path.'/.git');
    }

    protected function getShortHash(string $reference): string
    {
        if (!$this->io->isVerbose() && Preg::isMatch('{^[0-9a-f]{40}$}', $reference)) {
            return substr($reference, 0, 10);
        }

        return $reference;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use React\Promise\PromiseInterface;
use Composer\Package\PackageInterface;

/**
 * Xz archive downloader.
 *
 * @author Pavel Puchkin <i@neoascetic.me>
 * @author Pierre Rudloff <contact@rudloff.pro>
 */
class XzDownloader extends ArchiveDownloader
{
    protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface
    {
        $command = ['tar', '-xJf', $file, '-C', $path];

        if (0 === $this->process->execute($command, $ignoredOutput)) {
            return \React\Promise\resolve(null);
        }

        $processError = 'Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput();

        throw new \RuntimeException($processError);
    }
}
<?php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Autoload;

/**
 * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
 *
 *     $loader = new \Composer\Autoload\ClassLoader();
 *
 *     // register classes with namespaces
 *     $loader->add('Symfony\Component', __DIR__.'/component');
 *     $loader->add('Symfony',           __DIR__.'/framework');
 *
 *     // activate the autoloader
 *     $loader->register();
 *
 *     // to enable searching the include path (eg. for PEAR packages)
 *     $loader->setUseIncludePath(true);
 *
 * In this example, if you try to use a class in the Symfony\Component
 * namespace or one of its children (Symfony\Component\Console for instance),
 * the autoloader will first look for the class under the component/
 * directory, and it will then fallback to the framework/ directory if not
 * found before giving up.
 *
 * This class is loosely based on the Symfony UniversalClassLoader.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @see    https://www.php-fig.org/psr/psr-0/
 * @see    https://www.php-fig.org/psr/psr-4/
 */
class ClassLoader
{
    /** @var \Closure(string):void */
    private static $includeFile;

    /** @var string|null */
    private $vendorDir;

    // PSR-4
    /**
     * @var array<string, array<string, int>>
     */
    private $prefixLengthsPsr4 = array();
    /**
     * @var array<string, list<string>>
     */
    private $prefixDirsPsr4 = array();
    /**
     * @var list<string>
     */
    private $fallbackDirsPsr4 = array();

    // PSR-0
    /**
     * List of PSR-0 prefixes
     *
     * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
     *
     * @var array<string, array<string, list<string>>>
     */
    private $prefixesPsr0 = array();
    /**
     * @var list<string>
     */
    private $fallbackDirsPsr0 = array();

    /** @var bool */
    private $useIncludePath = false;

    /**
     * @var array<string, string>
     */
    private $classMap = array();

    /** @var bool */
    private $classMapAuthoritative = false;

    /**
     * @var array<string, bool>
     */
    private $missingClasses = array();

    /** @var string|null */
    private $apcuPrefix;

    /**
     * @var array<string, self>
     */
    private static $registeredLoaders = array();

    /**
     * @param string|null $vendorDir
     */
    public function __construct($vendorDir = null)
    {
        $this->vendorDir = $vendorDir;
        self::initializeIncludeClosure();
    }

    /**
     * @return array<string, list<string>>
     */
    public function getPrefixes()
    {
        if (!empty($this->prefixesPsr0)) {
            return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
        }

        return array();
    }

    /**
     * @return array<string, list<string>>
     */
    public function getPrefixesPsr4()
    {
        return $this->prefixDirsPsr4;
    }

    /**
     * @return list<string>
     */
    public function getFallbackDirs()
    {
        return $this->fallbackDirsPsr0;
    }

    /**
     * @return list<string>
     */
    public function getFallbackDirsPsr4()
    {
        return $this->fallbackDirsPsr4;
    }

    /**
     * @return array<string, string> Array of classname => path
     */
    public function getClassMap()
    {
        return $this->classMap;
    }

    /**
     * @param array<string, string> $classMap Class to filename map
     *
     * @return void
     */
    public function addClassMap(array $classMap)
    {
        if ($this->classMap) {
            $this->classMap = array_merge($this->classMap, $classMap);
        } else {
            $this->classMap = $classMap;
        }
    }

    /**
     * Registers a set of PSR-0 directories for a given prefix, either
     * appending or prepending to the ones previously set for this prefix.
     *
     * @param string              $prefix  The prefix
     * @param list<string>|string $paths   The PSR-0 root directories
     * @param bool                $prepend Whether to prepend the directories
     *
     * @return void
     */
    public function add($prefix, $paths, $prepend = false)
    {
        $paths = (array) $paths;
        if (!$prefix) {
            if ($prepend) {
                $this->fallbackDirsPsr0 = array_merge(
                    $paths,
                    $this->fallbackDirsPsr0
                );
            } else {
                $this->fallbackDirsPsr0 = array_merge(
                    $this->fallbackDirsPsr0,
                    $paths
                );
            }

            return;
        }

        $first = $prefix[0];
        if (!isset($this->prefixesPsr0[$first][$prefix])) {
            $this->prefixesPsr0[$first][$prefix] = $paths;

            return;
        }
        if ($prepend) {
            $this->prefixesPsr0[$first][$prefix] = array_merge(
                $paths,
                $this->prefixesPsr0[$first][$prefix]
            );
        } else {
            $this->prefixesPsr0[$first][$prefix] = array_merge(
                $this->prefixesPsr0[$first][$prefix],
                $paths
            );
        }
    }

    /**
     * Registers a set of PSR-4 directories for a given namespace, either
     * appending or prepending to the ones previously set for this namespace.
     *
     * @param string              $prefix  The prefix/namespace, with trailing '\\'
     * @param list<string>|string $paths   The PSR-4 base directories
     * @param bool                $prepend Whether to prepend the directories
     *
     * @throws \InvalidArgumentException
     *
     * @return void
     */
    public function addPsr4($prefix, $paths, $prepend = false)
    {
        $paths = (array) $paths;
        if (!$prefix) {
            // Register directories for the root namespace.
            if ($prepend) {
                $this->fallbackDirsPsr4 = array_merge(
                    $paths,
                    $this->fallbackDirsPsr4
                );
            } else {
                $this->fallbackDirsPsr4 = array_merge(
                    $this->fallbackDirsPsr4,
                    $paths
                );
            }
        } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
            // Register directories for a new namespace.
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            $this->prefixDirsPsr4[$prefix] = $paths;
        } elseif ($prepend) {
            // Prepend directories for an already registered namespace.
            $this->prefixDirsPsr4[$prefix] = array_merge(
                $paths,
                $this->prefixDirsPsr4[$prefix]
            );
        } else {
            // Append directories for an already registered namespace.
            $this->prefixDirsPsr4[$prefix] = array_merge(
                $this->prefixDirsPsr4[$prefix],
                $paths
            );
        }
    }

    /**
     * Registers a set of PSR-0 directories for a given prefix,
     * replacing any others previously set for this prefix.
     *
     * @param string              $prefix The prefix
     * @param list<string>|string $paths  The PSR-0 base directories
     *
     * @return void
     */
    public function set($prefix, $paths)
    {
        if (!$prefix) {
            $this->fallbackDirsPsr0 = (array) $paths;
        } else {
            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
        }
    }

    /**
     * Registers a set of PSR-4 directories for a given namespace,
     * replacing any others previously set for this namespace.
     *
     * @param string              $prefix The prefix/namespace, with trailing '\\'
     * @param list<string>|string $paths  The PSR-4 base directories
     *
     * @throws \InvalidArgumentException
     *
     * @return void
     */
    public function setPsr4($prefix, $paths)
    {
        if (!$prefix) {
            $this->fallbackDirsPsr4 = (array) $paths;
        } else {
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            $this->prefixDirsPsr4[$prefix] = (array) $paths;
        }
    }

    /**
     * Turns on searching the include path for class files.
     *
     * @param bool $useIncludePath
     *
     * @return void
     */
    public function setUseIncludePath($useIncludePath)
    {
        $this->useIncludePath = $useIncludePath;
    }

    /**
     * Can be used to check if the autoloader uses the include path to check
     * for classes.
     *
     * @return bool
     */
    public function getUseIncludePath()
    {
        return $this->useIncludePath;
    }

    /**
     * Turns off searching the prefix and fallback directories for classes
     * that have not been registered with the class map.
     *
     * @param bool $classMapAuthoritative
     *
     * @return void
     */
    public function setClassMapAuthoritative($classMapAuthoritative)
    {
        $this->classMapAuthoritative = $classMapAuthoritative;
    }

    /**
     * Should class lookup fail if not found in the current class map?
     *
     * @return bool
     */
    public function isClassMapAuthoritative()
    {
        return $this->classMapAuthoritative;
    }

    /**
     * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
     *
     * @param string|null $apcuPrefix
     *
     * @return void
     */
    public function setApcuPrefix($apcuPrefix)
    {
        $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
    }

    /**
     * The APCu prefix in use, or null if APCu caching is not enabled.
     *
     * @return string|null
     */
    public function getApcuPrefix()
    {
        return $this->apcuPrefix;
    }

    /**
     * Registers this instance as an autoloader.
     *
     * @param bool $prepend Whether to prepend the autoloader or not
     *
     * @return void
     */
    public function register($prepend = false)
    {
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);

        if (null === $this->vendorDir) {
            return;
        }

        if ($prepend) {
            self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
        } else {
            unset(self::$registeredLoaders[$this->vendorDir]);
            self::$registeredLoaders[$this->vendorDir] = $this;
        }
    }

    /**
     * Unregisters this instance as an autoloader.
     *
     * @return void
     */
    public function unregister()
    {
        spl_autoload_unregister(array($this, 'loadClass'));

        if (null !== $this->vendorDir) {
            unset(self::$registeredLoaders[$this->vendorDir]);
        }
    }

    /**
     * Loads the given class or interface.
     *
     * @param  string    $class The name of the class
     * @return true|null True if loaded, null otherwise
     */
    public function loadClass($class)
    {
        if ($file = $this->findFile($class)) {
            $includeFile = self::$includeFile;
            $includeFile($file);

            return true;
        }

        return null;
    }

    /**
     * Finds the path to the file where the class is defined.
     *
     * @param string $class The name of the class
     *
     * @return string|false The path if found, false otherwise
     */
    public function findFile($class)
    {
        // class map lookup
        if (isset($this->classMap[$class])) {
            return $this->classMap[$class];
        }
        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
            return false;
        }
        if (null !== $this->apcuPrefix) {
            $file = apcu_fetch($this->apcuPrefix.$class, $hit);
            if ($hit) {
                return $file;
            }
        }

        $file = $this->findFileWithExtension($class, '.php');

        // Search for Hack files if we are running on HHVM
        if (false === $file && defined('HHVM_VERSION')) {
            $file = $this->findFileWithExtension($class, '.hh');
        }

        if (null !== $this->apcuPrefix) {
            apcu_add($this->apcuPrefix.$class, $file);
        }

        if (false === $file) {
            // Remember that this class does not exist.
            $this->missingClasses[$class] = true;
        }

        return $file;
    }

    /**
     * Returns the currently registered loaders keyed by their corresponding vendor directories.
     *
     * @return array<string, self>
     */
    public static function getRegisteredLoaders()
    {
        return self::$registeredLoaders;
    }

    /**
     * @param  string       $class
     * @param  string       $ext
     * @return string|false
     */
    private function findFileWithExtension($class, $ext)
    {
        // PSR-4 lookup
        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

        $first = $class[0];
        if (isset($this->prefixLengthsPsr4[$first])) {
            $subPath = $class;
            while (false !== $lastPos = strrpos($subPath, '\\')) {
                $subPath = substr($subPath, 0, $lastPos);
                $search = $subPath . '\\';
                if (isset($this->prefixDirsPsr4[$search])) {
                    $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
                    foreach ($this->prefixDirsPsr4[$search] as $dir) {
                        if (file_exists($file = $dir . $pathEnd)) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-4 fallback dirs
        foreach ($this->fallbackDirsPsr4 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
                return $file;
            }
        }

        // PSR-0 lookup
        if (false !== $pos = strrpos($class, '\\')) {
            // namespaced class name
            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
        } else {
            // PEAR-like class name
            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
        }

        if (isset($this->prefixesPsr0[$first])) {
            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
                if (0 === strpos($class, $prefix)) {
                    foreach ($dirs as $dir) {
                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-0 fallback dirs
        foreach ($this->fallbackDirsPsr0 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                return $file;
            }
        }

        // PSR-0 include paths.
        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
            return $file;
        }

        return false;
    }

    /**
     * @return void
     */
    private static function initializeIncludeClosure()
    {
        if (self::$includeFile !== null) {
            return;
        }

        /**
         * Scope isolated include.
         *
         * Prevents access to $this/self from included files.
         *
         * @param  string $file
         * @return void
         */
        self::$includeFile = \Closure::bind(static function($file) {
            include $file;
        }, null, null);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/*
 * This file is copied from the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 */

namespace Composer\Autoload;

use Composer\ClassMapGenerator\FileList;
use Composer\IO\IOInterface;

/**
 * ClassMapGenerator
 *
 * @author Gyula Sallai <salla016@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 *
 * @deprecated Since Composer 2.4.0 use the composer/class-map-generator package instead
 */
class ClassMapGenerator
{
    /**
     * Generate a class map file
     *
     * @param \Traversable<string>|array<string> $dirs Directories or a single path to search in
     * @param string                             $file The name of the class map file
     */
    public static function dump(iterable $dirs, string $file): void
    {
        $maps = [];

        foreach ($dirs as $dir) {
            $maps = array_merge($maps, static::createMap($dir));
        }

        file_put_contents($file, sprintf('<?php return %s;', var_export($maps, true)));
    }

    /**
     * Iterate over all files in the given directory searching for classes
     *
     * @param \Traversable<\SplFileInfo>|string|array<\SplFileInfo> $path The path to search in or an iterator
     * @param non-empty-string|null                                 $excluded     Regex that matches file paths to be excluded from the classmap
     * @param ?IOInterface                                          $io           IO object
     * @param null|string                                           $namespace    Optional namespace prefix to filter by
     * @param null|'psr-0'|'psr-4'|'classmap'                       $autoloadType psr-0|psr-4 Optional autoload standard to use mapping rules
     * @param array<non-empty-string, true>                         $scannedFiles
     * @return array<class-string, non-empty-string> A class map array
     * @throws \RuntimeException When the path is neither an existing file nor directory
     */
    public static function createMap($path, ?string $excluded = null, ?IOInterface $io = null, ?string $namespace = null, ?string $autoloadType = null, array &$scannedFiles = []): array
    {
        $generator = new \Composer\ClassMapGenerator\ClassMapGenerator(['php', 'inc', 'hh']);
        $fileList = new FileList();
        $fileList->files = $scannedFiles;
        $generator->avoidDuplicateScans($fileList);

        $generator->scanPaths($path, $excluded, $autoloadType ?? 'classmap', $namespace);

        $classMap = $generator->getClassMap();

        $scannedFiles = $fileList->files;

        if ($io !== null) {
            foreach ($classMap->getPsrViolations() as $msg) {
                $io->writeError("<warning>$msg</warning>");
            }

            foreach ($classMap->getAmbiguousClasses() as $class => $paths) {
                if (count($paths) > 1) {
                    $io->writeError(
                        '<warning>Warning: Ambiguous class resolution, "'.$class.'"'.
                        ' was found '. (count($paths) + 1) .'x: in "'.$classMap->getClassPath($class).'" and "'. implode('", "', $paths) .'", the first will be used.</warning>'
                    );
                } else {
                    $io->writeError(
                        '<warning>Warning: Ambiguous class resolution, "'.$class.'"'.
                        ' was found in both "'.$classMap->getClassPath($class).'" and "'. implode('", "', $paths) .'", the first will be used.</warning>'
                    );
                }
            }
        }

        return $classMap->getMap();
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Autoload;

use Composer\ClassMapGenerator\ClassMap;
use Composer\ClassMapGenerator\ClassMapGenerator;
use Composer\Config;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
use Composer\Installer\InstallationManager;
use Composer\IO\IOInterface;
use Composer\IO\NullIO;
use Composer\Package\AliasPackage;
use Composer\Package\PackageInterface;
use Composer\Package\RootPackageInterface;
use Composer\Pcre\Preg;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Semver\Constraint\Bound;
use Composer\Util\Filesystem;
use Composer\Util\Platform;
use Composer\Script\ScriptEvents;
use Composer\Util\PackageSorter;
use Composer\Json\JsonFile;
use Composer\Package\Locker;
use Symfony\Component\Console\Formatter\OutputFormatter;

/**
 * @author Igor Wiedler <igor@wiedler.ch>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class AutoloadGenerator
{
    /**
     * @var EventDispatcher
     */
    private $eventDispatcher;

    /**
     * @var IOInterface
     */
    private $io;

    /**
     * @var ?bool
     */
    private $devMode = null;

    /**
     * @var bool
     */
    private $classMapAuthoritative = false;

    /**
     * @var bool
     */
    private $apcu = false;

    /**
     * @var string|null
     */
    private $apcuPrefix;

    /**
     * @var bool
     */
    private $dryRun = false;

    /**
     * @var bool
     */
    private $runScripts = false;

    /**
     * @var PlatformRequirementFilterInterface
     */
    private $platformRequirementFilter;

    public function __construct(EventDispatcher $eventDispatcher, ?IOInterface $io = null)
    {
        $this->eventDispatcher = $eventDispatcher;
        $this->io = $io ?? new NullIO();

        $this->platformRequirementFilter = PlatformRequirementFilterFactory::ignoreNothing();
    }

    /**
     * @return void
     */
    public function setDevMode(bool $devMode = true)
    {
        $this->devMode = $devMode;
    }

    /**
     * Whether generated autoloader considers the class map authoritative.
     *
     * @return void
     */
    public function setClassMapAuthoritative(bool $classMapAuthoritative)
    {
        $this->classMapAuthoritative = $classMapAuthoritative;
    }

    /**
     * Whether generated autoloader considers APCu caching.
     *
     * @return void
     */
    public function setApcu(bool $apcu, ?string $apcuPrefix = null)
    {
        $this->apcu = $apcu;
        $this->apcuPrefix = $apcuPrefix;
    }

    /**
     * Whether to run scripts or not
     *
     * @return void
     */
    public function setRunScripts(bool $runScripts = true)
    {
        $this->runScripts = $runScripts;
    }

    /**
     * Whether to run in drymode or not
     */
    public function setDryRun(bool $dryRun = true): void
    {
        $this->dryRun = $dryRun;
    }

    /**
     * Whether platform requirements should be ignored.
     *
     * If this is set to true, the platform check file will not be generated
     * If this is set to false, the platform check file will be generated with all requirements
     * If this is set to string[], those packages will be ignored from the platform check file
     *
     * @param bool|string[] $ignorePlatformReqs
     * @return void
     *
     * @deprecated use setPlatformRequirementFilter instead
     */
    public function setIgnorePlatformRequirements($ignorePlatformReqs)
    {
        trigger_error('AutoloadGenerator::setIgnorePlatformRequirements is deprecated since Composer 2.2, use setPlatformRequirementFilter instead.', E_USER_DEPRECATED);

        $this->setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs));
    }

    /**
     * @return void
     */
    public function setPlatformRequirementFilter(PlatformRequirementFilterInterface $platformRequirementFilter)
    {
        $this->platformRequirementFilter = $platformRequirementFilter;
    }

    /**
     * @return ClassMap
     * @throws \Seld\JsonLint\ParsingException
     * @throws \RuntimeException
     */
    public function dump(Config $config, InstalledRepositoryInterface $localRepo, RootPackageInterface $rootPackage, InstallationManager $installationManager, string $targetDir, bool $scanPsrPackages = false, ?string $suffix = null, ?Locker $locker = null, bool $strictAmbiguous = false)
    {
        if ($this->classMapAuthoritative) {
            // Force scanPsrPackages when classmap is authoritative
            $scanPsrPackages = true;
        }

        // auto-set devMode based on whether dev dependencies are installed or not
        if (null === $this->devMode) {
            // we assume no-dev mode if no vendor dir is present or it is too old to contain dev information
            $this->devMode = false;

            $installedJson = new JsonFile($config->get('vendor-dir').'/composer/installed.json');
            if ($installedJson->exists()) {
                $installedJson = $installedJson->read();
                if (isset($installedJson['dev'])) {
                    $this->devMode = $installedJson['dev'];
                }
            }
        }

        if ($this->runScripts) {
            // set COMPOSER_DEV_MODE in case not set yet so it is available in the dump-autoload event listeners
            if (!isset($_SERVER['COMPOSER_DEV_MODE'])) {
                Platform::putEnv('COMPOSER_DEV_MODE', $this->devMode ? '1' : '0');
            }

            $this->eventDispatcher->dispatchScript(ScriptEvents::PRE_AUTOLOAD_DUMP, $this->devMode, [], [
                'optimize' => $scanPsrPackages,
            ]);
        }

        $classMapGenerator = new ClassMapGenerator(['php', 'inc', 'hh']);
        $classMapGenerator->avoidDuplicateScans();

        $filesystem = new Filesystem();
        $filesystem->ensureDirectoryExists($config->get('vendor-dir'));
        // Do not remove double realpath() calls.
        // Fixes failing Windows realpath() implementation.
        // See https://bugs.php.net/bug.php?id=72738
        $basePath = $filesystem->normalizePath(realpath(realpath(Platform::getCwd())));
        $vendorPath = $filesystem->normalizePath(realpath(realpath($config->get('vendor-dir'))));
        $useGlobalIncludePath = $config->get('use-include-path');
        $prependAutoloader = $config->get('prepend-autoloader') === false ? 'false' : 'true';
        $targetDir = $vendorPath.'/'.$targetDir;
        $filesystem->ensureDirectoryExists($targetDir);

        $vendorPathCode = $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true);
        $vendorPathToTargetDirCode = $filesystem->findShortestPathCode($vendorPath, realpath($targetDir), true);

        $appBaseDirCode = $filesystem->findShortestPathCode($vendorPath, $basePath, true);
        $appBaseDirCode = str_replace('__DIR__', '$vendorDir', $appBaseDirCode);

        $namespacesFile = <<<EOF
<?php

// autoload_namespaces.php @generated by Composer

\$vendorDir = $vendorPathCode;
\$baseDir = $appBaseDirCode;

return array(

EOF;

        $psr4File = <<<EOF
<?php

// autoload_psr4.php @generated by Composer

\$vendorDir = $vendorPathCode;
\$baseDir = $appBaseDirCode;

return array(

EOF;

        // Collect information from all packages.
        $devPackageNames = $localRepo->getDevPackageNames();
        $packageMap = $this->buildPackageMap($installationManager, $rootPackage, $localRepo->getCanonicalPackages());
        if ($this->devMode) {
            // if dev mode is enabled, then we do not filter any dev packages out so disable this entirely
            $filteredDevPackages = false;
        } else {
            // if the list of dev package names is available we use that straight, otherwise pass true which means use legacy algo to figure them out
            $filteredDevPackages = $devPackageNames ?: true;
        }
        $autoloads = $this->parseAutoloads($packageMap, $rootPackage, $filteredDevPackages);

        // Process the 'psr-0' base directories.
        foreach ($autoloads['psr-0'] as $namespace => $paths) {
            $exportedPaths = [];
            foreach ($paths as $path) {
                $exportedPaths[] = $this->getPathCode($filesystem, $basePath, $vendorPath, $path);
            }
            $exportedPrefix = var_export($namespace, true);
            $namespacesFile .= "    $exportedPrefix => ";
            $namespacesFile .= "array(".implode(', ', $exportedPaths)."),\n";
        }
        $namespacesFile .= ");\n";

        // Process the 'psr-4' base directories.
        foreach ($autoloads['psr-4'] as $namespace => $paths) {
            $exportedPaths = [];
            foreach ($paths as $path) {
                $exportedPaths[] = $this->getPathCode($filesystem, $basePath, $vendorPath, $path);
            }
            $exportedPrefix = var_export($namespace, true);
            $psr4File .= "    $exportedPrefix => ";
            $psr4File .= "array(".implode(', ', $exportedPaths)."),\n";
        }
        $psr4File .= ");\n";

        // add custom psr-0 autoloading if the root package has a target dir
        $targetDirLoader = null;
        $mainAutoload = $rootPackage->getAutoload();
        if ($rootPackage->getTargetDir() && !empty($mainAutoload['psr-0'])) {
            $levels = substr_count($filesystem->normalizePath($rootPackage->getTargetDir()), '/') + 1;
            $prefixes = implode(', ', array_map(static function ($prefix): string {
                return var_export($prefix, true);
            }, array_keys($mainAutoload['psr-0'])));
            $baseDirFromTargetDirCode = $filesystem->findShortestPathCode($targetDir, $basePath, true);

            $targetDirLoader = <<<EOF

    public static function autoload(\$class)
    {
        \$dir = $baseDirFromTargetDirCode . '/';
        \$prefixes = array($prefixes);
        foreach (\$prefixes as \$prefix) {
            if (0 !== strpos(\$class, \$prefix)) {
                continue;
            }
            \$path = \$dir . implode('/', array_slice(explode('\\\\', \$class), $levels)).'.php';
            if (!\$path = stream_resolve_include_path(\$path)) {
                return false;
            }
            require \$path;

            return true;
        }
    }

EOF;
        }

        $excluded = [];
        if (!empty($autoloads['exclude-from-classmap'])) {
            $excluded = $autoloads['exclude-from-classmap'];
        }

        foreach ($autoloads['classmap'] as $dir) {
            $classMapGenerator->scanPaths($dir, $this->buildExclusionRegex($dir, $excluded));
        }

        if ($scanPsrPackages) {
            $namespacesToScan = [];

            // Scan the PSR-0/4 directories for class files, and add them to the class map
            foreach (['psr-4', 'psr-0'] as $psrType) {
                foreach ($autoloads[$psrType] as $namespace => $paths) {
                    $namespacesToScan[$namespace][] = ['paths' => $paths, 'type' => $psrType];
                }
            }

            krsort($namespacesToScan);

            foreach ($namespacesToScan as $namespace => $groups) {
                foreach ($groups as $group) {
                    foreach ($group['paths'] as $dir) {
                        $dir = $filesystem->normalizePath($filesystem->isAbsolutePath($dir) ? $dir : $basePath.'/'.$dir);
                        if (!is_dir($dir)) {
                            continue;
                        }

                        // if the vendor dir is contained within a psr-0/psr-4 dir being scanned we exclude it
                        if (str_contains($vendorPath, $dir.'/')) {
                            $exclusionRegex = $this->buildExclusionRegex($dir, array_merge($excluded, [$vendorPath.'/']));
                        } else {
                            $exclusionRegex = $this->buildExclusionRegex($dir, $excluded);
                        }

                        $classMapGenerator->scanPaths($dir, $exclusionRegex, $group['type'], $namespace);
                    }
                }
            }
        }

        $classMap = $classMapGenerator->getClassMap();
        if ($strictAmbiguous) {
            $ambiguousClasses = $classMap->getAmbiguousClasses(false);
        } else {
            $ambiguousClasses = $classMap->getAmbiguousClasses();
        }
        foreach ($ambiguousClasses as $className => $ambiguousPaths) {
            if (count($ambiguousPaths) > 1) {
                $this->io->writeError(
                    '<warning>Warning: Ambiguous class resolution, "'.$className.'"'.
                    ' was found '. (count($ambiguousPaths) + 1) .'x: in "'.$classMap->getClassPath($className).'" and "'. implode('", "', $ambiguousPaths) .'", the first will be used.</warning>'
                );
            } else {
                $this->io->writeError(
                    '<warning>Warning: Ambiguous class resolution, "'.$className.'"'.
                    ' was found in both "'.$classMap->getClassPath($className).'" and "'. implode('", "', $ambiguousPaths) .'", the first will be used.</warning>'
                );
            }
        }
        if (\count($ambiguousClasses) > 0) {
            $this->io->writeError('<info>To resolve ambiguity in classes not under your control you can ignore them by path using <href='.OutputFormatter::escape('https://getcomposer.org/doc/04-schema.md#exclude-files-from-classmaps').'>exclude-from-classmap</>');
        }

        // output PSR violations which are not coming from the vendor dir
        $classMap->clearPsrViolationsByPath($vendorPath);
        foreach ($classMap->getPsrViolations() as $msg) {
            $this->io->writeError("<warning>$msg</warning>");
        }

        $classMap->addClass('Composer\InstalledVersions', $vendorPath . '/composer/InstalledVersions.php');
        $classMap->sort();

        $classmapFile = <<<EOF
<?php

// autoload_classmap.php @generated by Composer

\$vendorDir = $vendorPathCode;
\$baseDir = $appBaseDirCode;

return array(

EOF;
        foreach ($classMap->getMap() as $className => $path) {
            $pathCode = $this->getPathCode($filesystem, $basePath, $vendorPath, $path).",\n";
            $classmapFile .= '    '.var_export($className, true).' => '.$pathCode;
        }
        $classmapFile .= ");\n";

        if ('' === $suffix) {
            $suffix = null;
        }
        if (null === $suffix) {
            $suffix = $config->get('autoloader-suffix');

            // carry over existing autoload.php's suffix if possible and none is configured
            if (null === $suffix && Filesystem::isReadable($vendorPath.'/autoload.php')) {
                $content = (string) file_get_contents($vendorPath.'/autoload.php');
                if (Preg::isMatch('{ComposerAutoloaderInit([^:\s]+)::}', $content, $match)) {
                    $suffix = $match[1];
                }
            }

            if (null === $suffix) {
                $suffix = $locker !== null && $locker->isLocked() ? $locker->getLockData()['content-hash'] : bin2hex(random_bytes(16));
            }
        }

        if ($this->dryRun) {
            return $classMap;
        }

        $filesystem->filePutContentsIfModified($targetDir.'/autoload_namespaces.php', $namespacesFile);
        $filesystem->filePutContentsIfModified($targetDir.'/autoload_psr4.php', $psr4File);
        $filesystem->filePutContentsIfModified($targetDir.'/autoload_classmap.php', $classmapFile);
        $includePathFilePath = $targetDir.'/include_paths.php';
        if ($includePathFileContents = $this->getIncludePathsFile($packageMap, $filesystem, $basePath, $vendorPath, $vendorPathCode, $appBaseDirCode)) {
            $filesystem->filePutContentsIfModified($includePathFilePath, $includePathFileContents);
        } elseif (file_exists($includePathFilePath)) {
            unlink($includePathFilePath);
        }
        $includeFilesFilePath = $targetDir.'/autoload_files.php';
        if ($includeFilesFileContents = $this->getIncludeFilesFile($autoloads['files'], $filesystem, $basePath, $vendorPath, $vendorPathCode, $appBaseDirCode)) {
            $filesystem->filePutContentsIfModified($includeFilesFilePath, $includeFilesFileContents);
        } elseif (file_exists($includeFilesFilePath)) {
            unlink($includeFilesFilePath);
        }
        $filesystem->filePutContentsIfModified($targetDir.'/autoload_static.php', $this->getStaticFile($suffix, $targetDir, $vendorPath, $basePath));
        $checkPlatform = $config->get('platform-check') !== false && !($this->platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter);
        $platformCheckContent = null;
        if ($checkPlatform) {
            $platformCheckContent = $this->getPlatformCheck($packageMap, $config->get('platform-check'), $devPackageNames);
            if (null === $platformCheckContent) {
                $checkPlatform = false;
            }
        }
        if ($checkPlatform) {
            $filesystem->filePutContentsIfModified($targetDir.'/platform_check.php', $platformCheckContent);
        } elseif (file_exists($targetDir.'/platform_check.php')) {
            unlink($targetDir.'/platform_check.php');
        }
        $filesystem->filePutContentsIfModified($vendorPath.'/autoload.php', $this->getAutoloadFile($vendorPathToTargetDirCode, $suffix));
        $filesystem->filePutContentsIfModified($targetDir.'/autoload_real.php', $this->getAutoloadRealFile(true, (bool) $includePathFileContents, $targetDirLoader, (bool) $includeFilesFileContents, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader, $checkPlatform));

        $filesystem->safeCopy(__DIR__.'/ClassLoader.php', $targetDir.'/ClassLoader.php');
        $filesystem->safeCopy(__DIR__.'/../../../LICENSE', $targetDir.'/LICENSE');

        if ($this->runScripts) {
            $this->eventDispatcher->dispatchScript(ScriptEvents::POST_AUTOLOAD_DUMP, $this->devMode, [], [
                'optimize' => $scanPsrPackages,
            ]);
        }

        return $classMap;
    }

    /**
     * @param array<string> $excluded
     * @return non-empty-string|null
     */
    private function buildExclusionRegex(string $dir, array $excluded): ?string
    {
        if ([] === $excluded) {
            return null;
        }

        // filter excluded patterns here to only use those matching $dir
        // exclude-from-classmap patterns are all realpath'd so we can only filter them if $dir exists so that realpath($dir) will work
        // if $dir does not exist, it should anyway not find anything there so no trouble
        if (file_exists($dir)) {
            // transform $dir in the same way that exclude-from-classmap patterns are transformed so we can match them against each other
            $dirMatch = preg_quote(strtr(realpath($dir), '\\', '/'));
            // also match against the non-realpath version for symlinks
            $fs = new Filesystem();
            $absDir = $fs->isAbsolutePath($dir) ? $dir : realpath(Platform::getCwd()).'/'.$dir;
            $dirMatchNormalized = preg_quote(strtr($fs->normalizePath($absDir), '\\', '/'));
            $isSymlink = $dirMatch !== $dirMatchNormalized;

            foreach ($excluded as $index => $pattern) {
                // extract the constant string prefix of the pattern here, until we reach a non-escaped regex special character
                $pattern = Preg::replace('{^(([^.+*?\[^\]$(){}=!<>|:\\\\#-]+|\\\\[.+*?\[^\]$(){}=!<>|:#-])*).*}', '$1', $pattern);
                // if the pattern is not a subset or superset of $dir, it is unrelated and we skip it
                if (
                    (!str_starts_with($pattern, $dirMatch) && !str_starts_with($dirMatch, $pattern))
                    && (
                        // only check dirMatchNormalized if it is actually different from dirMatch otherwise there is no point
                        !$isSymlink
                        || (!str_starts_with($pattern, $dirMatchNormalized) && !str_starts_with($dirMatchNormalized, $pattern))
                    )
                ) {
                    unset($excluded[$index]);
                }
            }
        }

        return \count($excluded) > 0 ? '{(' . implode('|', $excluded) . ')}' : null;
    }

    /**
     * @param PackageInterface[] $packages
     * @return non-empty-array<int, array{0: PackageInterface, 1: string|null}>
     */
    public function buildPackageMap(InstallationManager $installationManager, PackageInterface $rootPackage, array $packages)
    {
        // build package => install path map
        $packageMap = [[$rootPackage, '']];

        foreach ($packages as $package) {
            if ($package instanceof AliasPackage) {
                continue;
            }
            $this->validatePackage($package);
            $packageMap[] = [
                $package,
                $installationManager->getInstallPath($package),
            ];
        }

        return $packageMap;
    }

    /**
     * @return void
     * @throws \InvalidArgumentException Throws an exception, if the package has illegal settings.
     */
    protected function validatePackage(PackageInterface $package)
    {
        $autoload = $package->getAutoload();
        if (!empty($autoload['psr-4']) && null !== $package->getTargetDir()) {
            $name = $package->getName();
            $package->getTargetDir();
            throw new \InvalidArgumentException("PSR-4 autoloading is incompatible with the target-dir property, remove the target-dir in package '$name'.");
        }
        if (!empty($autoload['psr-4'])) {
            foreach ($autoload['psr-4'] as $namespace => $dirs) {
                if ($namespace !== '' && '\\' !== substr($namespace, -1)) {
                    throw new \InvalidArgumentException("psr-4 namespaces must end with a namespace separator, '$namespace' does not, use '$namespace\\'.");
                }
            }
        }
    }

    /**
     * Compiles an ordered list of namespace => path mappings
     *
     * @param non-empty-array<int, array{0: PackageInterface, 1: string|null}> $packageMap array of array(package, installDir-relative-to-composer.json or null for metapackages)
     * @param RootPackageInterface $rootPackage root package instance
     * @param bool|string[] $filteredDevPackages If an array, the list of packages that must be removed. If bool, whether to filter out require-dev packages
     * @return array
     * @phpstan-return array{
     *     'psr-0': array<string, array<string>>,
     *     'psr-4': array<string, array<string>>,
     *     'classmap': array<int, string>,
     *     'files': array<string, string>,
     *     'exclude-from-classmap': array<int, string>,
     * }
     */
    public function parseAutoloads(array $packageMap, PackageInterface $rootPackage, $filteredDevPackages = false)
    {
        $rootPackageMap = array_shift($packageMap);
        if (is_array($filteredDevPackages)) {
            $packageMap = array_filter($packageMap, static function ($item) use ($filteredDevPackages): bool {
                return !in_array($item[0]->getName(), $filteredDevPackages, true);
            });
        } elseif ($filteredDevPackages) {
            $packageMap = $this->filterPackageMap($packageMap, $rootPackage);
        }
        $sortedPackageMap = $this->sortPackageMap($packageMap);
        $sortedPackageMap[] = $rootPackageMap;
        $reverseSortedMap = array_reverse($sortedPackageMap);

        // reverse-sorted means root first, then dependents, then their dependents, etc.
        // which makes sense to allow root to override classmap or psr-0/4 entries with higher precedence rules
        $psr0 = $this->parseAutoloadsType($reverseSortedMap, 'psr-0', $rootPackage);
        $psr4 = $this->parseAutoloadsType($reverseSortedMap, 'psr-4', $rootPackage);
        $classmap = $this->parseAutoloadsType($reverseSortedMap, 'classmap', $rootPackage);

        // sorted (i.e. dependents first) for files to ensure that dependencies are loaded/available once a file is included
        $files = $this->parseAutoloadsType($sortedPackageMap, 'files', $rootPackage);
        // using sorted here but it does not really matter as all are excluded equally
        $exclude = $this->parseAutoloadsType($sortedPackageMap, 'exclude-from-classmap', $rootPackage);

        krsort($psr0);
        krsort($psr4);

        return [
            'psr-0' => $psr0,
            'psr-4' => $psr4,
            'classmap' => $classmap,
            'files' => $files,
            'exclude-from-classmap' => $exclude,
        ];
    }

    /**
     * Registers an autoloader based on an autoload-map returned by parseAutoloads
     *
     * @param array<string, mixed[]> $autoloads see parseAutoloads return value
     * @return ClassLoader
     */
    public function createLoader(array $autoloads, ?string $vendorDir = null)
    {
        $loader = new ClassLoader($vendorDir);

        if (isset($autoloads['psr-0'])) {
            foreach ($autoloads['psr-0'] as $namespace => $path) {
                $loader->add($namespace, $path);
            }
        }

        if (isset($autoloads['psr-4'])) {
            foreach ($autoloads['psr-4'] as $namespace => $path) {
                $loader->addPsr4($namespace, $path);
            }
        }

        if (isset($autoloads['classmap'])) {
            $excluded = [];
            if (!empty($autoloads['exclude-from-classmap'])) {
                $excluded = $autoloads['exclude-from-classmap'];
            }

            $classMapGenerator = new ClassMapGenerator(['php', 'inc', 'hh']);
            $classMapGenerator->avoidDuplicateScans();

            foreach ($autoloads['classmap'] as $dir) {
                try {
                    $classMapGenerator->scanPaths($dir, $this->buildExclusionRegex($dir, $excluded));
                } catch (\RuntimeException $e) {
                    $this->io->writeError('<warning>'.$e->getMessage().'</warning>');
                }
            }

            $loader->addClassMap($classMapGenerator->getClassMap()->getMap());
        }

        return $loader;
    }

    /**
     * @param array<int, array{0: PackageInterface, 1: string|null}> $packageMap
     * @return ?string
     */
    protected function getIncludePathsFile(array $packageMap, Filesystem $filesystem, string $basePath, string $vendorPath, string $vendorPathCode, string $appBaseDirCode)
    {
        $includePaths = [];

        foreach ($packageMap as $item) {
            [$package, $installPath] = $item;

            // packages that are not installed cannot autoload anything
            if (null === $installPath) {
                continue;
            }

            if (null !== $package->getTargetDir() && strlen($package->getTargetDir()) > 0) {
                $installPath = substr($installPath, 0, -strlen('/'.$package->getTargetDir()));
            }

            foreach ($package->getIncludePaths() as $includePath) {
                $includePath = trim($includePath, '/');
                $includePaths[] = $installPath === '' ? $includePath : $installPath.'/'.$includePath;
            }
        }

        if (\count($includePaths) === 0) {
            return null;
        }

        $includePathsCode = '';
        foreach ($includePaths as $path) {
            $includePathsCode .= "    " . $this->getPathCode($filesystem, $basePath, $vendorPath, $path) . ",\n";
        }

        return <<<EOF
<?php

// include_paths.php @generated by Composer

\$vendorDir = $vendorPathCode;
\$baseDir = $appBaseDirCode;

return array(
$includePathsCode);

EOF;
    }

    /**
     * @param array<string, string> $files
     * @return ?string
     */
    protected function getIncludeFilesFile(array $files, Filesystem $filesystem, string $basePath, string $vendorPath, string $vendorPathCode, string $appBaseDirCode)
    {
        // Get the path to each file, and make sure these paths are unique.
        $files = array_map(
            function (string $functionFile) use ($filesystem, $basePath, $vendorPath): string {
                return $this->getPathCode($filesystem, $basePath, $vendorPath, $functionFile);
            },
            $files
        );
        $uniqueFiles = array_unique($files);
        if (count($uniqueFiles) < count($files)) {
            $this->io->writeError('<warning>The following "files" autoload rules are included multiple times, this may cause issues and should be resolved:</warning>');
            foreach (array_unique(array_diff_assoc($files, $uniqueFiles)) as $duplicateFile) {
                $this->io->writeError('<warning> - '.$duplicateFile.'</warning>');
            }
        }
        unset($uniqueFiles);

        $filesCode = '';

        foreach ($files as $fileIdentifier => $functionFile) {
            $filesCode .= '    ' . var_export($fileIdentifier, true) . ' => ' . $functionFile . ",\n";
        }

        if (!$filesCode) {
            return null;
        }

        return <<<EOF
<?php

// autoload_files.php @generated by Composer

\$vendorDir = $vendorPathCode;
\$baseDir = $appBaseDirCode;

return array(
$filesCode);

EOF;
    }

    /**
     * @return string
     */
    protected function getPathCode(Filesystem $filesystem, string $basePath, string $vendorPath, string $path)
    {
        if (!$filesystem->isAbsolutePath($path)) {
            $path = $basePath . '/' . $path;
        }
        $path = $filesystem->normalizePath($path);

        $baseDir = '';
        if (strpos($path.'/', $vendorPath.'/') === 0) {
            $path = (string) substr($path, strlen($vendorPath));
            $baseDir = '$vendorDir . ';
        } else {
            $path = $filesystem->normalizePath($filesystem->findShortestPath($basePath, $path, true));
            if (!$filesystem->isAbsolutePath($path)) {
                $baseDir = '$baseDir . ';
                $path = '/' . $path;
            }
        }

        if (Preg::isMatch('{\.phar([\\\\/]|$)}', $path)) {
            $baseDir = "'phar://' . " . $baseDir;
        }

        return $baseDir . var_export($path, true);
    }

    /**
     * @param array<int, array{0: PackageInterface, 1: string|null}> $packageMap
     * @param bool|'php-only' $checkPlatform
     * @param string[] $devPackageNames
     * @return ?string
     */
    protected function getPlatformCheck(array $packageMap, $checkPlatform, array $devPackageNames)
    {
        $lowestPhpVersion = Bound::zero();
        $requiredPhp64bit = false;
        $requiredExtensions = [];
        $extensionProviders = [];

        foreach ($packageMap as $item) {
            $package = $item[0];
            foreach (array_merge($package->getReplaces(), $package->getProvides()) as $link) {
                if (Preg::isMatch('{^ext-(.+)$}iD', $link->getTarget(), $match)) {
                    $extensionProviders[$match[1]][] = $link->getConstraint();
                }
            }
        }

        foreach ($packageMap as $item) {
            $package = $item[0];
            // skip dev dependencies platform requirements as platform-check really should only be a production safeguard
            if (in_array($package->getName(), $devPackageNames, true)) {
                continue;
            }

            foreach ($package->getRequires() as $link) {
                if ($this->platformRequirementFilter->isIgnored($link->getTarget())) {
                    continue;
                }

                if (in_array($link->getTarget(), ['php', 'php-64bit'], true)) {
                    $constraint = $link->getConstraint();
                    if ($constraint->getLowerBound()->compareTo($lowestPhpVersion, '>')) {
                        $lowestPhpVersion = $constraint->getLowerBound();
                    }
                }

                if ('php-64bit' === $link->getTarget()) {
                    $requiredPhp64bit = true;
                }

                if ($checkPlatform === true && Preg::isMatch('{^ext-(.+)$}iD', $link->getTarget(), $match)) {
                    // skip extension checks if they have a valid provider/replacer
                    if (isset($extensionProviders[$match[1]])) {
                        foreach ($extensionProviders[$match[1]] as $provided) {
                            if ($provided->matches($link->getConstraint())) {
                                continue 2;
                            }
                        }
                    }

                    if ($match[1] === 'zend-opcache') {
                        $match[1] = 'zend opcache';
                    }

                    $extension = var_export($match[1], true);
                    if ($match[1] === 'pcntl' || $match[1] === 'readline') {
                        $requiredExtensions[$extension] = "PHP_SAPI !== 'cli' || extension_loaded($extension) || \$missingExtensions[] = $extension;\n";
                    } else {
                        $requiredExtensions[$extension] = "extension_loaded($extension) || \$missingExtensions[] = $extension;\n";
                    }
                }
            }
        }

        ksort($requiredExtensions);

        $formatToPhpVersionId = static function (Bound $bound): int {
            if ($bound->isZero()) {
                return 0;
            }

            if ($bound->isPositiveInfinity()) {
                return 99999;
            }

            $version = str_replace('-', '.', $bound->getVersion());
            $chunks = array_map('intval', explode('.', $version));

            return $chunks[0] * 10000 + $chunks[1] * 100 + $chunks[2];
        };

        $formatToHumanReadable = static function (Bound $bound) {
            if ($bound->isZero()) {
                return 0;
            }

            if ($bound->isPositiveInfinity()) {
                return 99999;
            }

            $version = str_replace('-', '.', $bound->getVersion());
            $chunks = explode('.', $version);
            $chunks = array_slice($chunks, 0, 3);

            return implode('.', $chunks);
        };

        $requiredPhp = '';
        $requiredPhpError = '';
        if (!$lowestPhpVersion->isZero()) {
            $operator = $lowestPhpVersion->isInclusive() ? '>=' : '>';
            $requiredPhp = 'PHP_VERSION_ID '.$operator.' '.$formatToPhpVersionId($lowestPhpVersion);
            $requiredPhpError = '"'.$operator.' '.$formatToHumanReadable($lowestPhpVersion).'"';
        }

        if ($requiredPhp) {
            $requiredPhp = <<<PHP_CHECK

if (!($requiredPhp)) {
    \$issues[] = 'Your Composer dependencies require a PHP version $requiredPhpError. You are running ' . PHP_VERSION . '.';
}

PHP_CHECK;
        }

        if ($requiredPhp64bit) {
            $requiredPhp .= <<<PHP_CHECK

if (PHP_INT_SIZE !== 8) {
    \$issues[] = 'Your Composer dependencies require a 64-bit build of PHP.';
}

PHP_CHECK;
        }

        $requiredExtensions = implode('', $requiredExtensions);
        if ('' !== $requiredExtensions) {
            $requiredExtensions = <<<EXT_CHECKS

\$missingExtensions = array();
$requiredExtensions
if (\$missingExtensions) {
    \$issues[] = 'Your Composer dependencies require the following PHP extensions to be installed: ' . implode(', ', \$missingExtensions) . '.';
}

EXT_CHECKS;
        }

        if (!$requiredPhp && !$requiredExtensions) {
            return null;
        }

        return <<<PLATFORM_CHECK
<?php

// platform_check.php @generated by Composer

\$issues = array();
{$requiredPhp}{$requiredExtensions}
if (\$issues) {
    if (!headers_sent()) {
        header('HTTP/1.1 500 Internal Server Error');
    }
    if (!ini_get('display_errors')) {
        if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
            fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, \$issues) . PHP_EOL.PHP_EOL);
        } elseif (!headers_sent()) {
            echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, \$issues)) . PHP_EOL.PHP_EOL;
        }
    }
    throw new \RuntimeException(
        'Composer detected issues in your platform: ' . implode(' ', \$issues)
    );
}

PLATFORM_CHECK;
    }

    /**
     * @return string
     */
    protected function getAutoloadFile(string $vendorPathToTargetDirCode, string $suffix)
    {
        $lastChar = $vendorPathToTargetDirCode[strlen($vendorPathToTargetDirCode) - 1];
        if ("'" === $lastChar || '"' === $lastChar) {
            $vendorPathToTargetDirCode = substr($vendorPathToTargetDirCode, 0, -1).'/autoload_real.php'.$lastChar;
        } else {
            $vendorPathToTargetDirCode .= " . '/autoload_real.php'";
        }

        return <<<AUTOLOAD
<?php

// autoload.php @generated by Composer

if (PHP_VERSION_ID < 50600) {
    if (!headers_sent()) {
        header('HTTP/1.1 500 Internal Server Error');
    }
    \$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
    if (!ini_get('display_errors')) {
        if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
            fwrite(STDERR, \$err);
        } elseif (!headers_sent()) {
            echo \$err;
        }
    }
    throw new RuntimeException(\$err);
}

require_once $vendorPathToTargetDirCode;

return ComposerAutoloaderInit$suffix::getLoader();

AUTOLOAD;
    }

    /**
     * @param string $vendorPathCode unused in this method
     * @param string $appBaseDirCode unused in this method
     * @param string $prependAutoloader 'true'|'false'
     * @return string
     */
    protected function getAutoloadRealFile(bool $useClassMap, bool $useIncludePath, ?string $targetDirLoader, bool $useIncludeFiles, string $vendorPathCode, string $appBaseDirCode, string $suffix, bool $useGlobalIncludePath, string $prependAutoloader, bool $checkPlatform)
    {
        $file = <<<HEADER
<?php

// autoload_real.php @generated by Composer

class ComposerAutoloaderInit$suffix
{
    private static \$loader;

    public static function loadClassLoader(\$class)
    {
        if ('Composer\\Autoload\\ClassLoader' === \$class) {
            require __DIR__ . '/ClassLoader.php';
        }
    }

    /**
     * @return \Composer\Autoload\ClassLoader
     */
    public static function getLoader()
    {
        if (null !== self::\$loader) {
            return self::\$loader;
        }


HEADER;

        if ($checkPlatform) {
            $file .= <<<'PLATFORM_CHECK'
        require __DIR__ . '/platform_check.php';


PLATFORM_CHECK;
        }

        $file .= <<<CLASSLOADER_INIT
        spl_autoload_register(array('ComposerAutoloaderInit$suffix', 'loadClassLoader'), true, $prependAutoloader);
        self::\$loader = \$loader = new \\Composer\\Autoload\\ClassLoader(\\dirname(__DIR__));
        spl_autoload_unregister(array('ComposerAutoloaderInit$suffix', 'loadClassLoader'));


CLASSLOADER_INIT;

        if ($useIncludePath) {
            $file .= <<<'INCLUDE_PATH'
        $includePaths = require __DIR__ . '/include_paths.php';
        $includePaths[] = get_include_path();
        set_include_path(implode(PATH_SEPARATOR, $includePaths));


INCLUDE_PATH;
        }

        // keeping PHP 5.6+ compatibility for the autoloader here by using call_user_func vs getInitializer()()
        $file .= <<<STATIC_INIT
        require __DIR__ . '/autoload_static.php';
        call_user_func(\Composer\Autoload\ComposerStaticInit$suffix::getInitializer(\$loader));


STATIC_INIT;

        if ($this->classMapAuthoritative) {
            $file .= <<<'CLASSMAPAUTHORITATIVE'
        $loader->setClassMapAuthoritative(true);

CLASSMAPAUTHORITATIVE;
        }

        if ($this->apcu) {
            $apcuPrefix = var_export(($this->apcuPrefix !== null ? $this->apcuPrefix : bin2hex(random_bytes(10))), true);
            $file .= <<<APCU
        \$loader->setApcuPrefix($apcuPrefix);

APCU;
        }

        if ($useGlobalIncludePath) {
            $file .= <<<'INCLUDEPATH'
        $loader->setUseIncludePath(true);

INCLUDEPATH;
        }

        if ($targetDirLoader) {
            $file .= <<<REGISTER_TARGET_DIR_AUTOLOAD
        spl_autoload_register(array('ComposerAutoloaderInit$suffix', 'autoload'), true, true);


REGISTER_TARGET_DIR_AUTOLOAD;
        }

        $file .= <<<REGISTER_LOADER
        \$loader->register($prependAutoloader);


REGISTER_LOADER;

        if ($useIncludeFiles) {
            $file .= <<<INCLUDE_FILES
        \$filesToLoad = \Composer\Autoload\ComposerStaticInit$suffix::\$files;
        \$requireFile = \Closure::bind(static function (\$fileIdentifier, \$file) {
            if (empty(\$GLOBALS['__composer_autoload_files'][\$fileIdentifier])) {
                \$GLOBALS['__composer_autoload_files'][\$fileIdentifier] = true;

                require \$file;
            }
        }, null, null);
        foreach (\$filesToLoad as \$fileIdentifier => \$file) {
            \$requireFile(\$fileIdentifier, \$file);
        }


INCLUDE_FILES;
        }

        $file .= <<<METHOD_FOOTER
        return \$loader;
    }

METHOD_FOOTER;

        $file .= $targetDirLoader;

        return $file . <<<FOOTER
}

FOOTER;
    }

    /**
     * @param string $vendorPath input for findShortestPathCode
     * @param string $basePath input for findShortestPathCode
     * @return string
     */
    protected function getStaticFile(string $suffix, string $targetDir, string $vendorPath, string $basePath)
    {
        $file = <<<HEADER
<?php

// autoload_static.php @generated by Composer

namespace Composer\Autoload;

class ComposerStaticInit$suffix
{

HEADER;

        $loader = new ClassLoader();

        $map = require $targetDir . '/autoload_namespaces.php';
        foreach ($map as $namespace => $path) {
            $loader->set($namespace, $path);
        }

        $map = require $targetDir . '/autoload_psr4.php';
        foreach ($map as $namespace => $path) {
            $loader->setPsr4($namespace, $path);
        }

        /**
         * @var string $vendorDir
         * @var string $baseDir
         */
        $classMap = require $targetDir . '/autoload_classmap.php';
        if ($classMap) {
            $loader->addClassMap($classMap);
        }

        $filesystem = new Filesystem();

        $vendorPathCode = ' => ' . $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true, true) . " . '/";
        $vendorPharPathCode = ' => \'phar://\' . ' . $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true, true) . " . '/";
        $appBaseDirCode = ' => ' . $filesystem->findShortestPathCode(realpath($targetDir), $basePath, true, true) . " . '/";
        $appBaseDirPharCode = ' => \'phar://\' . ' . $filesystem->findShortestPathCode(realpath($targetDir), $basePath, true, true) . " . '/";

        $absoluteVendorPathCode = ' => ' . substr(var_export(rtrim($vendorDir, '\\/') . '/', true), 0, -1);
        $absoluteVendorPharPathCode = ' => ' . substr(var_export(rtrim('phar://' . $vendorDir, '\\/') . '/', true), 0, -1);
        $absoluteAppBaseDirCode = ' => ' . substr(var_export(rtrim($baseDir, '\\/') . '/', true), 0, -1);
        $absoluteAppBaseDirPharCode = ' => ' . substr(var_export(rtrim('phar://' . $baseDir, '\\/') . '/', true), 0, -1);

        $initializer = '';
        $prefix = "\0Composer\Autoload\ClassLoader\0";
        $prefixLen = strlen($prefix);
        if (file_exists($targetDir . '/autoload_files.php')) {
            $maps = ['files' => require $targetDir . '/autoload_files.php'];
        } else {
            $maps = [];
        }

        foreach ((array) $loader as $prop => $value) {
            if (!is_array($value) || \count($value) === 0 || !str_starts_with($prop, $prefix)) {
                continue;
            }
            $maps[substr($prop, $prefixLen)] = $value;
        }

        foreach ($maps as $prop => $value) {
            $value = strtr(
                var_export($value, true),
                [
                    $absoluteVendorPathCode => $vendorPathCode,
                    $absoluteVendorPharPathCode => $vendorPharPathCode,
                    $absoluteAppBaseDirCode => $appBaseDirCode,
                    $absoluteAppBaseDirPharCode => $appBaseDirPharCode,
                ]
            );
            $value = ltrim(Preg::replace('/^ */m', '    $0$0', $value));
            $value = Preg::replace('/ +$/m', '', $value);

            $file .= sprintf("    public static $%s = %s;\n\n", $prop, $value);
            if ('files' !== $prop) {
                $initializer .= "            \$loader->$prop = ComposerStaticInit$suffix::\$$prop;\n";
            }
        }

        return $file . <<<INITIALIZER
    public static function getInitializer(ClassLoader \$loader)
    {
        return \Closure::bind(function () use (\$loader) {
$initializer
        }, null, ClassLoader::class);
    }
}

INITIALIZER;
    }

    /**
     * @param array<int, array{0: PackageInterface, 1: string|null}> $packageMap
     * @param string $type one of: 'psr-0'|'psr-4'|'classmap'|'files'
     * @return array<int, string>|array<string, array<string>>|array<string, string>
     */
    protected function parseAutoloadsType(array $packageMap, string $type, RootPackageInterface $rootPackage)
    {
        $autoloads = [];

        foreach ($packageMap as $item) {
            [$package, $installPath] = $item;

            // packages that are not installed cannot autoload anything
            if (null === $installPath) {
                continue;
            }

            $autoload = $package->getAutoload();
            if ($this->devMode && $package === $rootPackage) {
                $autoload = array_merge_recursive($autoload, $package->getDevAutoload());
            }

            // skip misconfigured packages
            if (!isset($autoload[$type]) || !is_array($autoload[$type])) {
                continue;
            }
            if (null !== $package->getTargetDir() && $package !== $rootPackage) {
                $installPath = substr($installPath, 0, -strlen('/'.$package->getTargetDir()));
            }

            foreach ($autoload[$type] as $namespace => $paths) {
                if (in_array($type, ['psr-4', 'psr-0'], true)) {
                    // normalize namespaces to ensure "\" becomes "" and others do not have leading separators as they are not needed
                    $namespace = ltrim($namespace, '\\');
                }
                foreach ((array) $paths as $path) {
                    if (($type === 'files' || $type === 'classmap' || $type === 'exclude-from-classmap') && $package->getTargetDir() && !Filesystem::isReadable($installPath.'/'.$path)) {
                        // remove target-dir from file paths of the root package
                        if ($package === $rootPackage) {
                            $targetDir = str_replace('\\<dirsep\\>', '[\\\\/]', preg_quote(str_replace(['/', '\\'], '<dirsep>', $package->getTargetDir())));
                            $path = ltrim(Preg::replace('{^'.$targetDir.'}', '', ltrim($path, '\\/')), '\\/');
                        } else {
                            // add target-dir from file paths that don't have it
                            $path = $package->getTargetDir() . '/' . $path;
                        }
                    }

                    if ($type === 'exclude-from-classmap') {
                        // first escape user input
                        $path = Preg::replace('{/+}', '/', preg_quote(trim(strtr($path, '\\', '/'), '/')));

                        // add support for wildcards * and **
                        $path = strtr($path, ['\\*\\*' => '.+?', '\\*' => '[^/]+?']);

                        // add support for up-level relative paths
                        $updir = null;
                        $path = Preg::replaceCallback(
                            '{^((?:(?:\\\\\\.){1,2}+/)+)}',
                            static function ($matches) use (&$updir): string {
                                // undo preg_quote for the matched string
                                $updir = str_replace('\\.', '.', $matches[1]);

                                return '';
                            },
                            $path
                        );
                        if (empty($installPath)) {
                            $installPath = strtr(Platform::getCwd(), '\\', '/');
                        }

                        $resolvedPath = realpath($installPath . '/' . $updir);
                        if (false === $resolvedPath) {
                            continue;
                        }
                        $autoloads[] = preg_quote(strtr($resolvedPath, '\\', '/')) . '/' . $path . '($|/)';
                        continue;
                    }

                    $relativePath = empty($installPath) ? (empty($path) ? '.' : $path) : $installPath.'/'.$path;

                    if ($type === 'files') {
                        $autoloads[$this->getFileIdentifier($package, $path)] = $relativePath;
                        continue;
                    }
                    if ($type === 'classmap') {
                        $autoloads[] = $relativePath;
                        continue;
                    }

                    $autoloads[$namespace][] = $relativePath;
                }
            }
        }

        return $autoloads;
    }

    /**
     * @return string
     */
    protected function getFileIdentifier(PackageInterface $package, string $path)
    {
        // TODO composer v3 change this to sha1 or xxh3? Possibly not worth the potential breakage though
        return hash('md5', $package->getName() . ':' . $path);
    }

    /**
     * Filters out dev-dependencies
     *
     * @param array<int, array{0: PackageInterface, 1: string|null}> $packageMap
     * @return array<int, array{0: PackageInterface, 1: string|null}>
     */
    protected function filterPackageMap(array $packageMap, RootPackageInterface $rootPackage)
    {
        $packages = [];
        $include = [];
        $replacedBy = [];

        foreach ($packageMap as $item) {
            $package = $item[0];
            $name = $package->getName();
            $packages[$name] = $package;
            foreach ($package->getReplaces() as $replace) {
                $replacedBy[$replace->getTarget()] = $name;
            }
        }

        $add = static function (PackageInterface $package) use (&$add, $packages, &$include, $replacedBy): void {
            foreach ($package->getRequires() as $link) {
                $target = $link->getTarget();
                if (isset($replacedBy[$target])) {
                    $target = $replacedBy[$target];
                }
                if (!isset($include[$target])) {
                    $include[$target] = true;
                    if (isset($packages[$target])) {
                        $add($packages[$target]);
                    }
                }
            }
        };
        $add($rootPackage);

        return array_filter(
            $packageMap,
            static function ($item) use ($include): bool {
                $package = $item[0];
                foreach ($package->getNames() as $name) {
                    if (isset($include[$name])) {
                        return true;
                    }
                }

                return false;
            }
        );
    }

    /**
     * Sorts packages by dependency weight
     *
     * Packages of equal weight are sorted alphabetically
     *
     * @param array<int, array{0: PackageInterface, 1: string|null}> $packageMap
     * @return array<int, array{0: PackageInterface, 1: string|null}>
     */
    protected function sortPackageMap(array $packageMap)
    {
        $packages = [];
        $paths = [];

        foreach ($packageMap as $item) {
            [$package, $path] = $item;
            $name = $package->getName();
            $packages[$name] = $package;
            $paths[$name] = $path;
        }

        $sortedPackages = PackageSorter::sortPackages($packages);

        $sortedPackageMap = [];

        foreach ($sortedPackages as $package) {
            $name = $package->getName();
            $sortedPackageMap[] = [$packages[$name], $paths[$name]];
        }

        return $sortedPackageMap;
    }
}

function composerRequire(string $fileIdentifier, string $file): void
{
    if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
        $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;

        require $file;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\EventDispatcher;

/**
 * An EventSubscriber knows which events it is interested in.
 *
 * If an EventSubscriber is added to an EventDispatcher, the manager invokes
 * {@link getSubscribedEvents} and registers the subscriber as a listener for all
 * returned events.
 *
 * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
 * @author Jonathan Wage <jonwage@gmail.com>
 * @author Roman Borschel <roman@code-factory.org>
 * @author Bernhard Schussek <bschussek@gmail.com>
 */
interface EventSubscriberInterface
{
    /**
     * Returns an array of event names this subscriber wants to listen to.
     *
     * The array keys are event names and the value can be:
     *
     * * The method name to call (priority defaults to 0)
     * * An array composed of the method name to call and the priority
     * * An array of arrays composed of the method names to call and respective
     *   priorities, or 0 if unset
     *
     * For instance:
     *
     * * array('eventName' => 'methodName')
     * * array('eventName' => array('methodName', $priority))
     * * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
     *
     * @return array<string, string|array{0: string, 1?: int}|array<array{0: string, 1?: int}>> The event names to listen to
     */
    public static function getSubscribedEvents();
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\EventDispatcher;

/**
 * The base event class
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class Event
{
    /**
     * @var string This event's name
     */
    protected $name;

    /**
     * @var string[] Arguments passed by the user, these will be forwarded to CLI script handlers
     */
    protected $args;

    /**
     * @var mixed[] Flags usable in PHP script handlers
     */
    protected $flags;

    /**
     * @var bool Whether the event should not be passed to more listeners
     */
    private $propagationStopped = false;

    /**
     * Constructor.
     *
     * @param string   $name  The event name
     * @param string[] $args  Arguments passed by the user
     * @param mixed[]  $flags Optional flags to pass data not as argument
     */
    public function __construct(string $name, array $args = [], array $flags = [])
    {
        $this->name = $name;
        $this->args = $args;
        $this->flags = $flags;
    }

    /**
     * Returns the event's name.
     *
     * @return string The event name
     */
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * Returns the event's arguments.
     *
     * @return string[] The event arguments
     */
    public function getArguments(): array
    {
        return $this->args;
    }

    /**
     * Returns the event's flags.
     *
     * @return mixed[] The event flags
     */
    public function getFlags(): array
    {
        return $this->flags;
    }

    /**
     * Checks if stopPropagation has been called
     *
     * @return bool Whether propagation has been stopped
     */
    public function isPropagationStopped(): bool
    {
        return $this->propagationStopped;
    }

    /**
     * Prevents the event from being passed to further listeners
     */
    public function stopPropagation(): void
    {
        $this->propagationStopped = true;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\EventDispatcher;

use Composer\DependencyResolver\Transaction;
use Composer\Installer\InstallerEvent;
use Composer\IO\ConsoleIO;
use Composer\IO\IOInterface;
use Composer\Composer;
use Composer\Package\PackageInterface;
use Composer\PartialComposer;
use Composer\Pcre\Preg;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PreCommandRunEvent;
use Composer\Util\Platform;
use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\Repository\RepositoryInterface;
use Composer\Script;
use Composer\Installer\PackageEvent;
use Composer\Installer\BinaryInstaller;
use Composer\Util\ProcessExecutor;
use Composer\Script\Event as ScriptEvent;
use Composer\Autoload\ClassLoader;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Process\PhpExecutableFinder;
use Symfony\Component\Process\ExecutableFinder;

/**
 * The Event Dispatcher.
 *
 * Example in command:
 *     $dispatcher = new EventDispatcher($this->requireComposer(), $this->getApplication()->getIO());
 *     // ...
 *     $dispatcher->dispatch(ScriptEvents::POST_INSTALL_CMD);
 *     // ...
 *
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Nils Adermann <naderman@naderman.de>
 */
class EventDispatcher
{
    /** @var PartialComposer */
    protected $composer;
    /** @var IOInterface */
    protected $io;
    /** @var ?ClassLoader */
    protected $loader;
    /** @var ProcessExecutor */
    protected $process;
    /** @var array<string, array<int, array<callable|string>>> */
    protected $listeners = [];
    /** @var bool */
    protected $runScripts = true;
    /** @var list<string> */
    private $eventStack;
    /** @var list<string> */
    private $skipScripts;
    /** @var string|null */
    private $previousHash = null;
    /** @var array<string, true> */
    private $previousListeners = [];

    /**
     * Constructor.
     *
     * @param PartialComposer $composer The composer instance
     * @param IOInterface     $io       The IOInterface instance
     */
    public function __construct(PartialComposer $composer, IOInterface $io, ?ProcessExecutor $process = null)
    {
        $this->composer = $composer;
        $this->io = $io;
        $this->process = $process ?? new ProcessExecutor($io);
        $this->eventStack = [];
        $this->skipScripts = array_values(array_filter(
            array_map('trim', explode(',', (string) Platform::getEnv('COMPOSER_SKIP_SCRIPTS'))),
            static function ($val) {
                return $val !== '';
            }
        ));
    }

    /**
     * Set whether script handlers are active or not
     *
     * @return $this
     */
    public function setRunScripts(bool $runScripts = true): self
    {
        $this->runScripts = $runScripts;

        return $this;
    }

    /**
     * Dispatch an event
     *
     * @param  string|null $eventName The event name, required if no $event is provided
     * @param  Event       $event An event instance, required if no $eventName is provided
     * @return int         return code of the executed script if any, for php scripts a false return
     *                          value is changed to 1, anything else to 0
     */
    public function dispatch(?string $eventName, ?Event $event = null): int
    {
        if (null === $event) {
            if (null === $eventName) {
                throw new \InvalidArgumentException('If no $event is passed in to '.__METHOD__.' you have to pass in an $eventName, got null.');
            }
            $event = new Event($eventName);
        }

        return $this->doDispatch($event);
    }

    /**
     * Dispatch a script event.
     *
     * @param  string               $eventName      The constant in ScriptEvents
     * @param  array<int, mixed>    $additionalArgs Arguments passed by the user
     * @param  array<string, mixed> $flags          Optional flags to pass data not as argument
     * @return int                                  return code of the executed script if any, for php scripts a false return
     *                                              value is changed to 1, anything else to 0
     */
    public function dispatchScript(string $eventName, bool $devMode = false, array $additionalArgs = [], array $flags = []): int
    {
        assert($this->composer instanceof Composer, new \LogicException('This should only be reached with a fully loaded Composer'));

        return $this->doDispatch(new ScriptEvent($eventName, $this->composer, $this->io, $devMode, $additionalArgs, $flags));
    }

    /**
     * Dispatch a package event.
     *
     * @param string               $eventName  The constant in PackageEvents
     * @param bool                 $devMode    Whether or not we are in dev mode
     * @param RepositoryInterface  $localRepo  The installed repository
     * @param OperationInterface[] $operations The list of operations
     * @param OperationInterface   $operation  The package being installed/updated/removed
     *
     * @return int return code of the executed script if any, for php scripts a false return
     *             value is changed to 1, anything else to 0
     */
    public function dispatchPackageEvent(string $eventName, bool $devMode, RepositoryInterface $localRepo, array $operations, OperationInterface $operation): int
    {
        assert($this->composer instanceof Composer, new \LogicException('This should only be reached with a fully loaded Composer'));

        return $this->doDispatch(new PackageEvent($eventName, $this->composer, $this->io, $devMode, $localRepo, $operations, $operation));
    }

    /**
     * Dispatch a installer event.
     *
     * @param string      $eventName         The constant in InstallerEvents
     * @param bool        $devMode           Whether or not we are in dev mode
     * @param bool        $executeOperations True if operations will be executed, false in --dry-run
     * @param Transaction $transaction       The transaction contains the list of operations
     *
     * @return int return code of the executed script if any, for php scripts a false return
     *             value is changed to 1, anything else to 0
     */
    public function dispatchInstallerEvent(string $eventName, bool $devMode, bool $executeOperations, Transaction $transaction): int
    {
        assert($this->composer instanceof Composer, new \LogicException('This should only be reached with a fully loaded Composer'));

        return $this->doDispatch(new InstallerEvent($eventName, $this->composer, $this->io, $devMode, $executeOperations, $transaction));
    }

    /**
     * Triggers the listeners of an event.
     *
     * @param  Event                        $event The event object to pass to the event handlers/listeners.
     * @throws \RuntimeException|\Exception
     * @return int                          return code of the executed script if any, for php scripts a false return
     *                                            value is changed to 1, anything else to 0
     */
    protected function doDispatch(Event $event)
    {
        if (Platform::getEnv('COMPOSER_DEBUG_EVENTS')) {
            $details = null;
            if ($event instanceof PackageEvent) {
                $details = (string) $event->getOperation();
            } elseif ($event instanceof CommandEvent) {
                $details = $event->getCommandName();
            } elseif ($event instanceof PreCommandRunEvent) {
                $details = $event->getCommand();
            }
            $this->io->writeError('Dispatching <info>'.$event->getName().'</info>'.($details ? ' ('.$details.')' : '').' event');
        }

        $listeners = $this->getListeners($event);

        $this->pushEvent($event);

        $autoloadersBefore = spl_autoload_functions();

        try {
            $returnMax = 0;
            foreach ($listeners as $callable) {
                $return = 0;
                $this->ensureBinDirIsInPath();

                $additionalArgs = $event->getArguments();
                if (is_string($callable) && str_contains($callable, '@no_additional_args')) {
                    $callable = Preg::replace('{ ?@no_additional_args}', '', $callable);
                    $additionalArgs = [];
                }
                $formattedEventNameWithArgs = $event->getName() . ($additionalArgs !== [] ? ' (' . implode(', ', $additionalArgs) . ')' : '');
                if (!is_string($callable)) {
                    $this->makeAutoloader($event, $callable);
                    if (!is_callable($callable)) {
                        $className = is_object($callable[0]) ? get_class($callable[0]) : $callable[0];

                        throw new \RuntimeException('Subscriber '.$className.'::'.$callable[1].' for event '.$event->getName().' is not callable, make sure the function is defined and public');
                    }
                    if (is_array($callable) && (is_string($callable[0]) || is_object($callable[0])) && is_string($callable[1])) {
                        $this->io->writeError(sprintf('> %s: %s', $formattedEventNameWithArgs, (is_object($callable[0]) ? get_class($callable[0]) : $callable[0]).'->'.$callable[1]), true, IOInterface::VERBOSE);
                    }
                    $return = false === $callable($event) ? 1 : 0;
                } elseif ($this->isComposerScript($callable)) {
                    $this->io->writeError(sprintf('> %s: %s', $formattedEventNameWithArgs, $callable), true, IOInterface::VERBOSE);

                    $script = explode(' ', substr($callable, 1));
                    $scriptName = $script[0];
                    unset($script[0]);

                    $index = array_search('@additional_args', $script, true);
                    if ($index !== false) {
                        $args = array_splice($script, $index, 0, $additionalArgs);
                    } else {
                        $args = array_merge($script, $additionalArgs);
                    }
                    $flags = $event->getFlags();
                    if (isset($flags['script-alias-input'])) {
                        $argsString = implode(' ', array_map(static function ($arg) { return ProcessExecutor::escape($arg); }, $script));
                        $flags['script-alias-input'] = $argsString . ' ' . $flags['script-alias-input'];
                        unset($argsString);
                    }
                    if (strpos($callable, '@composer ') === 0) {
                        $exec = $this->getPhpExecCommand() . ' ' . ProcessExecutor::escape(Platform::getEnv('COMPOSER_BINARY')) . ' ' . implode(' ', $args);
                        if (0 !== ($exitCode = $this->executeTty($exec))) {
                            $this->io->writeError(sprintf('<error>Script %s handling the %s event returned with error code '.$exitCode.'</error>', $callable, $event->getName()), true, IOInterface::QUIET);

                            throw new ScriptExecutionException('Error Output: '.$this->process->getErrorOutput(), $exitCode);
                        }
                    } else {
                        if (!$this->getListeners(new Event($scriptName))) {
                            $this->io->writeError(sprintf('<warning>You made a reference to a non-existent script %s</warning>', $callable), true, IOInterface::QUIET);
                        }

                        try {
                            /** @var InstallerEvent $event */
                            $scriptEvent = new ScriptEvent($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags);
                            $scriptEvent->setOriginatingEvent($event);
                            $return = $this->dispatch($scriptName, $scriptEvent);
                        } catch (ScriptExecutionException $e) {
                            $this->io->writeError(sprintf('<error>Script %s was called via %s</error>', $callable, $event->getName()), true, IOInterface::QUIET);
                            throw $e;
                        }
                    }
                } elseif ($this->isPhpScript($callable)) {
                    $className = substr($callable, 0, strpos($callable, '::'));
                    $methodName = substr($callable, strpos($callable, '::') + 2);

                    $this->makeAutoloader($event, $callable);
                    if (!class_exists($className)) {
                        $this->io->writeError('<warning>Class '.$className.' is not autoloadable, can not call '.$event->getName().' script</warning>', true, IOInterface::QUIET);
                        continue;
                    }
                    if (!is_callable($callable)) {
                        $this->io->writeError('<warning>Method '.$callable.' is not callable, can not call '.$event->getName().' script</warning>', true, IOInterface::QUIET);
                        continue;
                    }

                    try {
                        $return = false === $this->executeEventPhpScript($className, $methodName, $event) ? 1 : 0;
                    } catch (\Exception $e) {
                        $message = "Script %s handling the %s event terminated with an exception";
                        $this->io->writeError('<error>'.sprintf($message, $callable, $event->getName()).'</error>', true, IOInterface::QUIET);
                        throw $e;
                    }
                } elseif ($this->isCommandClass($callable)) {
                    $className = $callable;

                    $this->makeAutoloader($event, [$callable, 'run']);
                    if (!class_exists($className)) {
                        $this->io->writeError('<warning>Class '.$className.' is not autoloadable, can not call '.$event->getName().' script</warning>', true, IOInterface::QUIET);
                        continue;
                    }
                    if (!is_a($className, Command::class, true)) {
                        $this->io->writeError('<warning>Class '.$className.' does not extend '.Command::class.', can not call '.$event->getName().' script</warning>', true, IOInterface::QUIET);
                        continue;
                    }
                    if (defined('Composer\Script\ScriptEvents::'.str_replace('-', '_', strtoupper($event->getName())))) {
                        $this->io->writeError('<warning>You cannot bind '.$event->getName().' to a Command class, use a non-reserved name</warning>', true, IOInterface::QUIET);
                        continue;
                    }

                    $app = new Application();
                    $app->setCatchExceptions(false);
                    if (method_exists($app, 'setCatchErrors')) {
                        $app->setCatchErrors(false);
                    }
                    $app->setAutoExit(false);
                    $cmd = new $className($event->getName());
                    // @phpstan-ignore method.notFound, function.alreadyNarrowedType
                    if (method_exists($app, 'addCommand')) {
                        $app->addCommand($cmd);
                    } else {
                        // Compatibility layer for symfony/console <7.4
                        // @phpstan-ignore method.notFound
                        $app->add($cmd);
                    }
                    $app->setDefaultCommand((string) $cmd->getName(), true);
                    try {
                        $args = implode(' ', array_map(static function ($arg) { return ProcessExecutor::escape($arg); }, $additionalArgs));
                        // reusing the output from $this->io is mostly needed for tests, but generally speaking
                        // it does not hurt to keep the same stream as the current Application
                        if ($this->io instanceof ConsoleIO) {
                            $reflProp = new \ReflectionProperty($this->io, 'output');
                            if (\PHP_VERSION_ID < 80100) {
                                $reflProp->setAccessible(true);
                            }
                            $output = $reflProp->getValue($this->io);
                        } else {
                            $output = new ConsoleOutput();
                        }
                        $return = $app->run(new StringInput($event->getFlags()['script-alias-input'] ?? $args), $output);
                    } catch (\Exception $e) {
                        $message = "Script %s handling the %s event terminated with an exception";
                        $this->io->writeError('<error>'.sprintf($message, $callable, $event->getName()).'</error>', true, IOInterface::QUIET);
                        throw $e;
                    }
                } else {
                    $args = implode(' ', array_map(['Composer\Util\ProcessExecutor', 'escape'], $additionalArgs));

                    // @putenv does not receive arguments
                    if (strpos($callable, '@putenv ') === 0) {
                        $exec = $callable;
                    } else {
                        if (str_contains($callable, '@additional_args')) {
                            $exec = str_replace('@additional_args', $args, $callable);
                        } else {
                            $exec = $callable . ($args === '' ? '' : ' '.$args);
                        }
                    }

                    if ($this->io->isVerbose()) {
                        $this->io->writeError(sprintf('> %s: %s', $event->getName(), $exec));
                    } elseif ($this->eventNeedsToOutput($event)) {
                        $this->io->writeError(sprintf('> %s', $exec));
                    }

                    $possibleLocalBinaries = $this->composer->getPackage()->getBinaries();
                    if (count($possibleLocalBinaries) > 0) {
                        foreach ($possibleLocalBinaries as $localExec) {
                            if (Preg::isMatch('{\b'.preg_quote($callable).'$}', $localExec)) {
                                $caller = BinaryInstaller::determineBinaryCaller($localExec);
                                $exec = Preg::replace('{^'.preg_quote($callable).'}', $caller . ' ' . $localExec, $exec);
                                break;
                            }
                        }
                    }

                    if (strpos($exec, '@putenv ') === 0) {
                        if (false === strpos($exec, '=')) {
                            Platform::clearEnv(substr($exec, 8));
                        } else {
                            [$var, $value] = explode('=', substr($exec, 8), 2);
                            Platform::putEnv($var, $value);
                        }

                        continue;
                    }
                    if (strpos($exec, '@php ') === 0) {
                        $pathAndArgs = substr($exec, 5);
                        if (Platform::isWindows()) {
                            $pathAndArgs = Preg::replaceCallback('{^\S+}', static function ($path) {
                                return str_replace('/', '\\', $path[0]);
                            }, $pathAndArgs);
                        }
                        // match somename (not in quote, and not a qualified path) and if it is not a valid path from CWD then try to find it
                        // in $PATH. This allows support for `@php foo` where foo is a binary name found in PATH but not an actual relative path
                        $matched = Preg::isMatchStrictGroups('{^[^\'"\s/\\\\]+}', $pathAndArgs, $match);
                        if ($matched && !file_exists($match[0])) {
                            $finder = new ExecutableFinder;
                            if ($pathToExec = $finder->find($match[0])) {
                                if (Platform::isWindows()) {
                                    $execWithoutExt = Preg::replace('{\.(exe|bat|cmd|com)$}i', '', $pathToExec);
                                    // prefer non-extension file if it exists when executing with PHP
                                    if (file_exists($execWithoutExt)) {
                                        $pathToExec = $execWithoutExt;
                                    }
                                    unset($execWithoutExt);
                                }
                                $pathAndArgs = $pathToExec . substr($pathAndArgs, strlen($match[0]));
                            }
                        }
                        $exec = $this->getPhpExecCommand() . ' ' . $pathAndArgs;
                    } else {
                        $finder = new PhpExecutableFinder();
                        $phpPath = $finder->find(false);
                        if ($phpPath) {
                            Platform::putEnv('PHP_BINARY', $phpPath);
                        }

                        if (Platform::isWindows()) {
                            $exec = Preg::replaceCallback('{^\S+}', static function ($path) {
                                return str_replace('/', '\\', $path[0]);
                            }, $exec);
                        }
                    }

                    // if composer is being executed, make sure it runs the expected composer from current path
                    // resolution, even if bin-dir contains composer too because the project requires composer/composer
                    // see https://github.com/composer/composer/issues/8748
                    if (strpos($exec, 'composer ') === 0) {
                        $exec = $this->getPhpExecCommand() . ' ' . ProcessExecutor::escape(Platform::getEnv('COMPOSER_BINARY')) . substr($exec, 8);
                    }

                    if (0 !== ($exitCode = $this->executeTty($exec))) {
                        $this->io->writeError(sprintf('<error>Script %s handling the %s event returned with error code '.$exitCode.'</error>', $callable, $event->getName()), true, IOInterface::QUIET);

                        throw new ScriptExecutionException('Error Output: '.$this->process->getErrorOutput(), $exitCode);
                    }
                }

                $returnMax = max($returnMax, $return);

                if ($event->isPropagationStopped()) {
                    break;
                }
            }
        } finally {
            $this->popEvent();

            $knownIdentifiers = [];
            foreach ($autoloadersBefore as $key => $cb) {
                $knownIdentifiers[$this->getCallbackIdentifier($cb)] = ['key' => $key, 'callback' => $cb];
            }
            foreach (spl_autoload_functions() as $cb) {
                // once we get to the first known autoloader, we can leave any appended autoloader without problems
                if (isset($knownIdentifiers[$this->getCallbackIdentifier($cb)]) && $knownIdentifiers[$this->getCallbackIdentifier($cb)]['key'] === 0) {
                    break;
                }

                // other newly appeared prepended autoloaders should be appended instead to ensure Composer loads its classes first
                if ($cb instanceof ClassLoader) {
                    $cb->unregister();
                    $cb->register(false);
                } else {
                    spl_autoload_unregister($cb);
                    spl_autoload_register($cb);
                }
            }
        }

        return $returnMax;
    }

    protected function executeTty(string $exec): int
    {
        if ($this->io->isInteractive()) {
            return $this->process->executeTty($exec);
        }

        return $this->process->execute($exec);
    }

    protected function getPhpExecCommand(): string
    {
        $finder = new PhpExecutableFinder();
        $phpPath = $finder->find(false);
        if (!$phpPath) {
            throw new \RuntimeException('Failed to locate PHP binary to execute '.$phpPath);
        }
        $phpArgs = $finder->findArguments();
        $phpArgs = \count($phpArgs) > 0 ? ' ' . implode(' ', $phpArgs) : '';
        $allowUrlFOpenFlag = ' -d allow_url_fopen=' . ProcessExecutor::escape(ini_get('allow_url_fopen'));
        $disableFunctionsFlag = ' -d disable_functions=' . ProcessExecutor::escape(ini_get('disable_functions'));
        $memoryLimitFlag = ' -d memory_limit=' . ProcessExecutor::escape(ini_get('memory_limit'));

        return ProcessExecutor::escape($phpPath) . $phpArgs . $allowUrlFOpenFlag . $disableFunctionsFlag . $memoryLimitFlag;
    }

    /**
     * @param Event  $event      Event invoking the PHP callable
     *
     * @return mixed
     */
    protected function executeEventPhpScript(string $className, string $methodName, Event $event)
    {
        if ($this->io->isVerbose()) {
            $this->io->writeError(sprintf('> %s: %s::%s', $event->getName(), $className, $methodName));
        } elseif ($this->eventNeedsToOutput($event)) {
            $this->io->writeError(sprintf('> %s::%s', $className, $methodName));
        }

        return $className::$methodName($event);
    }

    private function eventNeedsToOutput(Event $event): bool
    {
        // do not output the command being run when using `composer exec` as it is fairly obvious the user is running it
        if ($event->getName() === '__exec_command') {
            return false;
        }

        // do not output the command being run when using `composer <script-name>` as it is also fairly obvious the user is running it
        if (($event->getFlags()['script-alias-input'] ?? null) !== null) {
            return false;
        }

        return true;
    }

    /**
     * Add a listener for a particular event
     *
     * @param string          $eventName The event name - typically a constant
     * @param callable|string $listener  A callable expecting an event argument, or a command string to be executed (same as a composer.json "scripts" entry)
     * @param int             $priority  A higher value represents a higher priority
     */
    public function addListener(string $eventName, $listener, int $priority = 0): void
    {
        $this->listeners[$eventName][$priority][] = $listener;
    }

    /**
     * @param callable|object $listener A callable or an object instance for which all listeners should be removed
     */
    public function removeListener($listener): void
    {
        foreach ($this->listeners as $eventName => $priorities) {
            foreach ($priorities as $priority => $listeners) {
                foreach ($listeners as $index => $candidate) {
                    if ($listener === $candidate || (is_array($candidate) && is_object($listener) && $candidate[0] === $listener)) {
                        unset($this->listeners[$eventName][$priority][$index]);
                    }
                }
            }
        }
    }

    /**
     * Adds object methods as listeners for the events in getSubscribedEvents
     *
     * @see EventSubscriberInterface
     */
    public function addSubscriber(EventSubscriberInterface $subscriber): void
    {
        foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
            if (is_string($params)) {
                $this->addListener($eventName, [$subscriber, $params]);
            } elseif (is_string($params[0])) {
                $this->addListener($eventName, [$subscriber, $params[0]], $params[1] ?? 0);
            } else {
                foreach ($params as $listener) {
                    $this->addListener($eventName, [$subscriber, $listener[0]], $listener[1] ?? 0);
                }
            }
        }
    }

    /**
     * Retrieves all listeners for a given event
     *
     * @return array<callable|string> All listeners: callables and scripts
     */
    protected function getListeners(Event $event): array
    {
        $scriptListeners = $this->runScripts ? $this->getScriptListeners($event) : [];

        if (!isset($this->listeners[$event->getName()][0])) {
            $this->listeners[$event->getName()][0] = [];
        }
        krsort($this->listeners[$event->getName()]);

        $listeners = $this->listeners;
        $listeners[$event->getName()][0] = array_merge($listeners[$event->getName()][0], $scriptListeners);

        return array_merge(...$listeners[$event->getName()]);
    }

    /**
     * Checks if an event has listeners registered
     */
    public function hasEventListeners(Event $event): bool
    {
        $listeners = $this->getListeners($event);

        return count($listeners) > 0;
    }

    /**
     * Finds all listeners defined as scripts in the package
     *
     * @param  Event $event Event object
     * @return string[] Listeners
     */
    protected function getScriptListeners(Event $event): array
    {
        $package = $this->composer->getPackage();
        $scripts = $package->getScripts();

        if (empty($scripts[$event->getName()])) {
            return [];
        }

        if (in_array($event->getName(), $this->skipScripts, true)) {
            $this->io->writeError('Skipped script listeners for <info>'.$event->getName().'</info> because of COMPOSER_SKIP_SCRIPTS', true, IOInterface::VERBOSE);

            return [];
        }

        return $scripts[$event->getName()];
    }

    /**
     * Checks if string given references a class path and method
     */
    protected function isPhpScript(string $callable): bool
    {
        return false === strpos($callable, ' ') && false !== strpos($callable, '::');
    }

    /**
     * Checks if string given references a command class
     */
    protected function isCommandClass(string $callable): bool
    {
        return str_contains($callable, '\\') && !str_contains($callable, ' ') && str_ends_with($callable, 'Command');
    }

    /**
     * Checks if string given references a composer run-script
     */
    protected function isComposerScript(string $callable): bool
    {
        return str_starts_with($callable, '@') && !str_starts_with($callable, '@php ') && !str_starts_with($callable, '@putenv ');
    }

    /**
     * Push an event to the stack of active event
     *
     * @throws \RuntimeException
     */
    protected function pushEvent(Event $event): int
    {
        $eventName = $event->getName();
        if (in_array($eventName, $this->eventStack)) {
            throw new \RuntimeException(sprintf("Circular call to script handler '%s' detected", $eventName));
        }

        return array_push($this->eventStack, $eventName);
    }

    /**
     * Pops the active event from the stack
     */
    protected function popEvent(): ?string
    {
        return array_pop($this->eventStack);
    }

    private function ensureBinDirIsInPath(): void
    {
        $pathEnv = 'PATH';

        // checking if only Path and not PATH is set then we probably need to update the Path env
        // on Windows getenv is case-insensitive so we cannot check it via Platform::getEnv and
        // we need to check in $_SERVER directly
        if (!isset($_SERVER[$pathEnv]) && isset($_SERVER['Path'])) {
            $pathEnv = 'Path';
        }

        // add the bin dir to the PATH to make local binaries of deps usable in scripts
        $binDir = $this->composer->getConfig()->get('bin-dir');
        if (is_dir($binDir)) {
            $binDir = realpath($binDir);
            $pathValue = (string) Platform::getEnv($pathEnv);
            if (!Preg::isMatch('{(^|'.PATH_SEPARATOR.')'.preg_quote($binDir).'($|'.PATH_SEPARATOR.')}', $pathValue)) {
                Platform::putEnv($pathEnv, $binDir.PATH_SEPARATOR.$pathValue);
            }
        }
    }

    /**
     * @param callable $cb DO NOT MOVE TO TYPE HINT as private autoload callbacks are not technically callable
     */
    private function getCallbackIdentifier($cb): string
    {
        if (is_string($cb)) {
            return 'fn:'.$cb;
        }
        if (is_object($cb)) {
            return 'obj:'.spl_object_hash($cb);
        }
        if (is_array($cb)) {
            return 'array:'.(is_string($cb[0]) ? $cb[0] : get_class($cb[0]) .'#'.spl_object_hash($cb[0])).'::'.$cb[1];
        }

        // not great but also do not want to break everything here
        return 'unsupported';
    }

    /**
     * @param mixed $callable Technically a callable shape but as the class may not be autoloadable yet PHP might not see it this way, so we cannot type hint as such
     */
    private function makeAutoloader(Event $event, $callable): void
    {
        if (!$this->composer instanceof Composer) {
            return;
        }

        if (is_array($callable)) {
            if (is_string($callable[0])) {
                $callableKey = $callable[0] . '::' . $callable[1];
            } else {
                $callableKey = get_class($callable[0]).'::'.$callable[1];
            }
        } elseif (is_string($callable)) {
            $callableKey = $callable;
        } elseif ($callable instanceof \Closure) {
            $callableKey = 'closure';
        } else {
            $callableKey = 'unknown';
        }
        if (isset($this->previousListeners[$callableKey])) {
            return;
        }
        $this->previousListeners[$callableKey] = true;

        $package = $this->composer->getPackage();
        $packages = $this->composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages();
        $generator = $this->composer->getAutoloadGenerator();
        $hash = implode(',', array_map(static function (PackageInterface $p) {
            return $p->getName().'/'.$p->getVersion();
        }, $packages));
        if ($event instanceof ScriptEvent || $event instanceof PackageEvent || $event instanceof InstallerEvent) {
            $generator->setDevMode($event->isDevMode());
            $hash .= $event->isDevMode() ? '/dev' : '';
        }
        $hash = hash('sha256', $hash);

        if ($this->previousHash === $hash) {
            return;
        }

        $this->previousHash = $hash;

        $packageMap = $generator->buildPackageMap($this->composer->getInstallationManager(), $package, $packages);
        $map = $generator->parseAutoloads($packageMap, $package);

        if ($this->loader !== null) {
            $this->loader->unregister();
        }

        $this->loader = $generator->createLoader($map, $this->composer->getConfig()->get('vendor-dir'));
        $this->loader->register(false);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\EventDispatcher;

/**
 * Thrown when a script running an external process exits with a non-0 status code
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ScriptExecutionException extends \RuntimeException
{
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Advisory;

use Composer\Pcre\Preg;
use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\VersionParser;
use JsonSerializable;

class PartialSecurityAdvisory implements JsonSerializable
{
    /**
     * @var string
     * @readonly
     */
    public $advisoryId;

    /**
     * @var string
     * @readonly
     */
    public $packageName;

    /**
     * @var ConstraintInterface
     * @readonly
     */
    public $affectedVersions;

    /**
     * @param array<mixed> $data
     * @return SecurityAdvisory|PartialSecurityAdvisory
     */
    public static function create(string $packageName, array $data, VersionParser $parser): self
    {
        try {
            $constraint = $parser->parseConstraints($data['affectedVersions']);
        } catch (\UnexpectedValueException $e) {
            // try to keep only the essential part of the constraint to turn invalid ones like <=3.20-test2 into <=3.20 which is better than nothing
            try {
                $affectedVersion = Preg::replace('{(^[>=<^~]*[\d.]+).*}', '$1', $data['affectedVersions']);
                $constraint = $parser->parseConstraints($affectedVersion);
            } catch (\UnexpectedValueException $e) {
                $constraint = new Constraint('==', '0.0.0-invalid-version');
            }
        }

        if (isset($data['title'], $data['sources'], $data['reportedAt'])) {
            return new SecurityAdvisory($packageName, $data['advisoryId'], $constraint, $data['title'], $data['sources'], new \DateTimeImmutable($data['reportedAt'], new \DateTimeZone('UTC')), $data['cve'] ?? null, $data['link'] ?? null, $data['severity'] ?? null);
        }

        return new self($packageName, $data['advisoryId'], $constraint);
    }

    public function __construct(string $packageName, string $advisoryId, ConstraintInterface $affectedVersions)
    {
        $this->advisoryId = $advisoryId;
        $this->packageName = $packageName;
        $this->affectedVersions = $affectedVersions;
    }

    /**
     * @return mixed
     */
    #[\ReturnTypeWillChange]
    public function jsonSerialize()
    {
        $data = (array) $this;
        $data['affectedVersions'] = $data['affectedVersions']->getPrettyString();

        return $data;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Advisory;

use Composer\IO\ConsoleIO;
use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
use Composer\Package\BasePackage;
use Composer\Package\CompletePackageInterface;
use Composer\Package\PackageInterface;
use Composer\Pcre\Preg;
use Composer\Repository\RepositorySet;
use Composer\Util\PackageInfo;
use InvalidArgumentException;
use Symfony\Component\Console\Formatter\OutputFormatter;

/**
 * @internal
 */
class Auditor
{
    public const FORMAT_TABLE = 'table';

    public const FORMAT_PLAIN = 'plain';

    public const FORMAT_JSON = 'json';

    public const FORMAT_SUMMARY = 'summary';

    public const FORMATS = [
        self::FORMAT_TABLE,
        self::FORMAT_PLAIN,
        self::FORMAT_JSON,
        self::FORMAT_SUMMARY,
    ];

    public const ABANDONED_IGNORE = 'ignore';
    public const ABANDONED_REPORT = 'report';
    public const ABANDONED_FAIL = 'fail';

    /** @internal */
    public const ABANDONEDS = [
        self::ABANDONED_IGNORE,
        self::ABANDONED_REPORT,
        self::ABANDONED_FAIL,
    ];

    /** Values to determine the audit result. */
    public const STATUS_OK = 0;
    public const STATUS_VULNERABLE = 1;
    public const STATUS_ABANDONED = 2;

    /**
     * @param PackageInterface[] $packages
     * @param self::FORMAT_* $format The format that will be used to output audit results.
     * @param bool $warningOnly If true, outputs a warning. If false, outputs an error.
     * @param array<string, string|null> $ignoreList List of advisory IDs, remote IDs, CVE IDs or package names that reported but not listed as vulnerabilities.
     * @param self::ABANDONED_* $abandoned
     * @param array<string, string|null> $ignoredSeverities List of ignored severity levels
     * @param array<string, string|null> $ignoreAbandoned List of abandoned package name that reported but not listed as vulnerabilities.
     *
     * @return int-mask<self::STATUS_*> A bitmask of STATUS_* constants or 0 on success
     * @throws InvalidArgumentException If no packages are passed in
     */
    public function audit(IOInterface $io, RepositorySet $repoSet, array $packages, string $format, bool $warningOnly = true, array $ignoreList = [], string $abandoned = self::ABANDONED_FAIL, array $ignoredSeverities = [], bool $ignoreUnreachable = false, array $ignoreAbandoned = []): int
    {
        $result = $repoSet->getMatchingSecurityAdvisories($packages, $format === self::FORMAT_SUMMARY, $ignoreUnreachable);
        $allAdvisories = $result['advisories'];
        $unreachableRepos = $result['unreachableRepos'];

        // we need the CVE & remote IDs set to filter ignores correctly so if we have any matches using the optimized codepath above
        // and ignores are set then we need to query again the full data to make sure it can be filtered
        if ($format === self::FORMAT_SUMMARY && $this->needsCompleteAdvisoryLoad($allAdvisories, $ignoreList)) {
            $result = $repoSet->getMatchingSecurityAdvisories($packages, false, $ignoreUnreachable);
            $allAdvisories = $result['advisories'];
            $unreachableRepos = array_merge($unreachableRepos, $result['unreachableRepos']);
        }
        ['advisories' => $advisories, 'ignoredAdvisories' => $ignoredAdvisories] = $this->processAdvisories($allAdvisories, $ignoreList, $ignoredSeverities);

        $abandonedCount = 0;
        $affectedPackagesCount = count($advisories);
        if ($abandoned === self::ABANDONED_IGNORE) {
            $abandonedPackages = [];
        } else {
            $abandonedPackages = $this->filterAbandonedPackages($packages, $ignoreAbandoned);
            if ($abandoned === self::ABANDONED_FAIL) {
                $abandonedCount = count($abandonedPackages);
            }
        }

        $auditBitmask = $this->calculateBitmask(0 < $affectedPackagesCount, 0 < $abandonedCount);

        if (self::FORMAT_JSON === $format) {
            $json = ['advisories' => $advisories];
            if ($ignoredAdvisories !== []) {
                $json['ignored-advisories'] = $ignoredAdvisories;
            }
            if ($unreachableRepos !== []) {
                $json['unreachable-repositories'] = $unreachableRepos;
            }
            $json['abandoned'] = array_reduce($abandonedPackages, static function (array $carry, CompletePackageInterface $package): array {
                $carry[$package->getPrettyName()] = $package->getReplacementPackage();

                return $carry;
            }, []);

            $io->write(JsonFile::encode($json));

            return $auditBitmask;
        }

        $errorOrWarn = $warningOnly ? 'warning' : 'error';
        if ($affectedPackagesCount > 0 || count($ignoredAdvisories) > 0) {
            $passes = [
                [$ignoredAdvisories, "<info>Found %d ignored security vulnerability advisor%s affecting %d package%s%s</info>"],
                [$advisories, "<$errorOrWarn>Found %d security vulnerability advisor%s affecting %d package%s%s</$errorOrWarn>"],
            ];
            foreach ($passes as [$advisoriesToOutput, $message]) {
                [$pkgCount, $totalAdvisoryCount] = $this->countAdvisories($advisoriesToOutput);
                if ($pkgCount > 0) {
                    $plurality = $totalAdvisoryCount === 1 ? 'y' : 'ies';
                    $pkgPlurality = $pkgCount === 1 ? '' : 's';
                    $punctuation = $format === 'summary' ? '.' : ':';
                    $io->writeError(sprintf($message, $totalAdvisoryCount, $plurality, $pkgCount, $pkgPlurality, $punctuation));
                    $this->outputAdvisories($io, $advisoriesToOutput, $format);
                }
            }

            if ($format === self::FORMAT_SUMMARY) {
                $io->writeError('Run "composer audit" for a full list of advisories.');
            }
        } else {
            $io->writeError('<info>No security vulnerability advisories found.</info>');
        }

        if (count($unreachableRepos) > 0) {
            $io->writeError('<warning>The following repositories were unreachable:</warning>');
            foreach ($unreachableRepos as $repo) {
                $io->writeError('  - ' . $repo);
            }
        }

        if (count($abandonedPackages) > 0 && $format !== self::FORMAT_SUMMARY) {
            $this->outputAbandonedPackages($io, $abandonedPackages, $format);
        }

        return $auditBitmask;
    }

    /**
     * @param array<string, array<SecurityAdvisory|PartialSecurityAdvisory>> $advisories
     * @param array<string, string|null> $ignoreList
     * @return bool
     */
    public function needsCompleteAdvisoryLoad(array $advisories, array $ignoreList): bool
    {
        if (\count($advisories) === 0) {
            return false;
        }

        // no partial advisories present
        if (array_all($advisories, static function (array $pkgAdvisories) {
            return array_all($pkgAdvisories, static function ($advisory) { return $advisory instanceof SecurityAdvisory; });
        })) {
            return false;
        }

        $ignoredIds = array_keys($ignoreList);

        return array_any($ignoredIds, static function (string $id) {
            return !str_starts_with($id, 'PKSA-');
        });
    }

    /**
     * @param array<PackageInterface> $packages
     * @param array<string, string|null> $ignoreAbandoned
     * @return array<CompletePackageInterface>
     */
    public function filterAbandonedPackages(array $packages, array $ignoreAbandoned): array
    {
        $filter = null;
        if (\count($ignoreAbandoned) !== 0) {
            $filter = BasePackage::packageNamesToRegexp(array_keys($ignoreAbandoned));
        }

        return array_filter($packages, static function (PackageInterface $pkg) use ($filter): bool {
            return $pkg instanceof CompletePackageInterface && $pkg->isAbandoned() && ($filter === null || !Preg::isMatch($filter, $pkg->getName()));
        });
    }

    /**
     * @phpstan-param array<string, array<PartialSecurityAdvisory|SecurityAdvisory>> $allAdvisories
     * @param array<string, string|null> $ignoreList List of advisory IDs, remote IDs, CVE IDs or package names that reported but not listed as vulnerabilities.
     * @param array<string, string|null> $ignoredSeverities List of ignored severity levels
     * @phpstan-return array{advisories: array<string, array<PartialSecurityAdvisory|SecurityAdvisory>>, ignoredAdvisories: array<string, array<PartialSecurityAdvisory|SecurityAdvisory>>}
     */
    public function processAdvisories(array $allAdvisories, array $ignoreList, array $ignoredSeverities): array
    {
        if ($ignoreList === [] && $ignoredSeverities === []) {
            return ['advisories' => $allAdvisories, 'ignoredAdvisories' => []];
        }

        $advisories = [];
        $ignored = [];
        $ignoreReason = null;

        foreach ($allAdvisories as $package => $pkgAdvisories) {
            foreach ($pkgAdvisories as $advisory) {
                $isActive = true;

                if (array_key_exists($package, $ignoreList)) {
                    $isActive = false;
                    $ignoreReason = $ignoreList[$package] ?? null;
                }

                if (array_key_exists($advisory->advisoryId, $ignoreList)) {
                    $isActive = false;
                    $ignoreReason = $ignoreList[$advisory->advisoryId] ?? null;
                }

                if ($advisory instanceof SecurityAdvisory) {
                    if (is_string($advisory->severity) && array_key_exists($advisory->severity, $ignoredSeverities)) {
                        $isActive = false;
                        $ignoreReason = $ignoredSeverities[$advisory->severity] ?? $advisory->severity.' severity is ignored';
                    }

                    if (is_string($advisory->cve) && array_key_exists($advisory->cve, $ignoreList)) {
                        $isActive = false;
                        $ignoreReason = $ignoreList[$advisory->cve] ?? null;
                    }

                    foreach ($advisory->sources as $source) {
                        if (array_key_exists($source['remoteId'], $ignoreList)) {
                            $isActive = false;
                            $ignoreReason = $ignoreList[$source['remoteId']] ?? null;
                            break;
                        }
                    }
                }

                if ($isActive) {
                    $advisories[$package][] = $advisory;
                    continue;
                }

                // Partial security advisories only used in summary mode
                // and in that case we do not need to cast the object.
                if ($advisory instanceof SecurityAdvisory) {
                    $advisory = $advisory->toIgnoredAdvisory($ignoreReason);
                }

                $ignored[$package][] = $advisory;
            }
        }

        return ['advisories' => $advisories, 'ignoredAdvisories' => $ignored];
    }

    /**
     * @param array<string, array<PartialSecurityAdvisory>> $advisories
     * @return array{int, int} Count of affected packages and total count of advisories
     */
    private function countAdvisories(array $advisories): array
    {
        $count = 0;
        foreach ($advisories as $packageAdvisories) {
            $count += count($packageAdvisories);
        }

        return [count($advisories), $count];
    }

    /**
     * @param array<string, array<SecurityAdvisory>> $advisories
     * @param self::FORMAT_* $format The format that will be used to output audit results.
     */
    private function outputAdvisories(IOInterface $io, array $advisories, string $format): void
    {
        switch ($format) {
            case self::FORMAT_TABLE:
                if (!($io instanceof ConsoleIO)) {
                    throw new InvalidArgumentException('Cannot use table format with ' . get_class($io));
                }
                $this->outputAdvisoriesTable($io, $advisories);

                return;
            case self::FORMAT_PLAIN:
                $this->outputAdvisoriesPlain($io, $advisories);

                return;
            case self::FORMAT_SUMMARY:

                return;
            default:
                throw new InvalidArgumentException('Invalid format "'.$format.'".');
        }
    }

    /**
     * @param array<string, array<SecurityAdvisory>> $advisories
     */
    private function outputAdvisoriesTable(ConsoleIO $io, array $advisories): void
    {
        foreach ($advisories as $packageAdvisories) {
            foreach ($packageAdvisories as $advisory) {
                $headers = [
                    'Package',
                    'Severity',
                    'Advisory ID',
                    'CVE',
                    'Title',
                    'URL',
                    'Affected versions',
                    'Reported at',
                ];
                $row = [
                    $advisory->packageName,
                    $this->getSeverity($advisory),
                    $this->getAdvisoryId($advisory),
                    $this->getCVE($advisory),
                    $advisory->title,
                    $this->getURL($advisory),
                    $advisory->affectedVersions->getPrettyString(),
                    $advisory->reportedAt->format(DATE_ATOM),
                ];
                if ($advisory instanceof IgnoredSecurityAdvisory) {
                    $headers[] = 'Ignore reason';
                    $row[] = $advisory->ignoreReason ?? 'None specified';
                }
                $io->getTable()
                    ->setHorizontal()
                    ->setHeaders($headers)
                    ->addRow(ConsoleIO::sanitize($row))
                    ->setColumnWidth(1, 80)
                    ->setColumnMaxWidth(1, 80)
                    ->render();
            }
        }
    }

    /**
     * @param array<string, array<SecurityAdvisory>> $advisories
     */
    private function outputAdvisoriesPlain(IOInterface $io, array $advisories): void
    {
        $error = [];
        $firstAdvisory = true;
        foreach ($advisories as $packageAdvisories) {
            foreach ($packageAdvisories as $advisory) {
                if (!$firstAdvisory) {
                    $error[] = '--------';
                }
                $error[] = "Package: ".$advisory->packageName;
                $error[] = "Severity: ".$this->getSeverity($advisory);
                $error[] = "Advisory ID: ".$this->getAdvisoryId($advisory);
                $error[] = "CVE: ".$this->getCVE($advisory);
                $error[] = "Title: ".OutputFormatter::escape($advisory->title);
                $error[] = "URL: ".$this->getURL($advisory);
                $error[] = "Affected versions: ".OutputFormatter::escape($advisory->affectedVersions->getPrettyString());
                $error[] = "Reported at: ".$advisory->reportedAt->format(DATE_ATOM);
                if ($advisory instanceof IgnoredSecurityAdvisory) {
                    $error[] = "Ignore reason: ".($advisory->ignoreReason ?? 'None specified');
                }
                $firstAdvisory = false;
            }
        }
        $io->writeError($error);
    }

    /**
     * @param array<CompletePackageInterface> $packages
     * @param self::FORMAT_PLAIN|self::FORMAT_TABLE $format
     */
    private function outputAbandonedPackages(IOInterface $io, array $packages, string $format): void
    {
        $io->writeError(sprintf('<error>Found %d abandoned package%s:</error>', count($packages), count($packages) > 1 ? 's' : ''));

        if ($format === self::FORMAT_PLAIN) {
            foreach ($packages as $pkg) {
                $replacement = $pkg->getReplacementPackage() !== null
                    ? 'Use '.$pkg->getReplacementPackage().' instead'
                    : 'No replacement was suggested';
                $io->writeError(sprintf(
                    '%s is abandoned. %s.',
                    $this->getPackageNameWithLink($pkg),
                    $replacement
                ));
            }

            return;
        }

        if (!($io instanceof ConsoleIO)) {
            throw new InvalidArgumentException('Cannot use table format with ' . get_class($io));
        }

        $table = $io->getTable()
            ->setHeaders(['Abandoned Package', 'Suggested Replacement'])
            ->setColumnWidth(1, 80)
            ->setColumnMaxWidth(1, 80);

        foreach ($packages as $pkg) {
            $replacement = $pkg->getReplacementPackage() !== null ? $pkg->getReplacementPackage() : 'none';
            $table->addRow(ConsoleIO::sanitize([$this->getPackageNameWithLink($pkg), $replacement]));
        }

        $table->render();
    }

    private function getPackageNameWithLink(PackageInterface $package): string
    {
        $packageUrl = PackageInfo::getViewSourceOrHomepageUrl($package);

        return $packageUrl !== null ? '<href=' . OutputFormatter::escape($packageUrl) . '>' . $package->getPrettyName() . '</>' : $package->getPrettyName();
    }

    private function getSeverity(SecurityAdvisory $advisory): string
    {
        if ($advisory->severity === null) {
            return '';
        }

        return $advisory->severity;
    }

    private function getAdvisoryId(SecurityAdvisory $advisory): string
    {
        if (str_starts_with($advisory->advisoryId, 'PKSA-')) {
            return '<href=https://packagist.org/security-advisories/'.$advisory->advisoryId.'>'.$advisory->advisoryId.'</>';
        }

        return $advisory->advisoryId;
    }

    private function getCVE(SecurityAdvisory $advisory): string
    {
        if ($advisory->cve === null) {
            return 'NO CVE';
        }

        return '<href=https://cve.mitre.org/cgi-bin/cvename.cgi?name='.$advisory->cve.'>'.$advisory->cve.'</>';
    }

    private function getURL(SecurityAdvisory $advisory): string
    {
        if ($advisory->link === null) {
            return '';
        }

        return '<href='.OutputFormatter::escape($advisory->link).'>'.OutputFormatter::escape($advisory->link).'</>';
    }

    /**
     * @return int-mask<self::STATUS_*>
     */
    private function calculateBitmask(bool $hasVulnerablePackages, bool $hasAbandonedPackages): int
    {
        $bitmask = self::STATUS_OK;

        if ($hasVulnerablePackages) {
            $bitmask |= self::STATUS_VULNERABLE;
        }

        if ($hasAbandonedPackages) {
            $bitmask |= self::STATUS_ABANDONED;
        }

        return $bitmask;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Advisory;

use Composer\Semver\Constraint\ConstraintInterface;
use DateTimeImmutable;

class IgnoredSecurityAdvisory extends SecurityAdvisory
{
    /**
     * @var string|null
     * @readonly
     */
    public $ignoreReason;

    /**
     * @param non-empty-array<array{name: string, remoteId: string}> $sources
     */
    public function __construct(string $packageName, string $advisoryId, ConstraintInterface $affectedVersions, string $title, array $sources, DateTimeImmutable $reportedAt, ?string $cve = null, ?string $link = null, ?string $ignoreReason = null, ?string $severity = null)
    {
        parent::__construct($packageName, $advisoryId, $affectedVersions, $title, $sources, $reportedAt, $cve, $link, $severity);

        $this->ignoreReason = $ignoreReason;
    }

    /**
     * @return mixed
     */
    #[\ReturnTypeWillChange]
    public function jsonSerialize()
    {
        $data = parent::jsonSerialize();
        if ($this->ignoreReason === null) {
            unset($data['ignoreReason']);
        }

        return $data;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Advisory;

use Composer\Config;

/**
 * @readonly
 * @internal
 */
class AuditConfig
{
    /**
     * @var bool Whether to run audit
     */
    public $audit;

    /**
     * @var Auditor::FORMAT_*
     */
    public $auditFormat;

    /**
     * @var Auditor::ABANDONED_*
     */
    public $auditAbandoned;

    /**
     * @var bool Should insecure versions be blocked during a composer update/required command
     */
    public $blockInsecure;

    /**
     * @var bool Should abandoned packages be blocked during a composer update/required command
     */
    public $blockAbandoned;

    /**
     * @var bool Should repositories that are unreachable or return a non-200 status code be ignored.
     */
    public $ignoreUnreachable;

    /**
     * @var array<string, string|null> List of advisory IDs to ignore during auditing => reason for ignoring
     */
    public $ignoreListForAudit;

    /**
     * @var array<string, string|null> List of advisory IDs to ignore during blocking
     */
    public $ignoreListForBlocking;

    /**
     * @var array<string, string|null> List of severities to ignore during auditing
     */
    public $ignoreSeverityForAudit;

    /**
     * @var array<string, string|null> List of severities to ignore during blocking
     */
    public $ignoreSeverityForBlocking;

    /**
     * @var array<string, string|null> List of abandoned packages to ignore during auditing
     */
    public $ignoreAbandonedForAudit;

    /**
     * @var array<string, string|null> List of abandoned packages to ignore during blocking
     */
    public $ignoreAbandonedForBlocking;

    /**
     * @param Auditor::FORMAT_* $auditFormat
     * @param Auditor::ABANDONED_* $auditAbandoned
     * @param array<string, string|null> $ignoreListForAudit
     * @param array<string, string|null> $ignoreListForBlocking
     * @param array<string, string|null> $ignoreSeverityForAudit
     * @param array<string, string|null> $ignoreSeverityForBlocking
     * @param array<string, string|null> $ignoreAbandonedForAudit
     * @param array<string, string|null> $ignoreAbandonedForBlocking
     */
    public function __construct(bool $audit, string $auditFormat, string $auditAbandoned, bool $blockInsecure, bool $blockAbandoned, bool $ignoreUnreachable, array $ignoreListForAudit, array $ignoreListForBlocking, array $ignoreSeverityForAudit, array $ignoreSeverityForBlocking, array $ignoreAbandonedForAudit, array $ignoreAbandonedForBlocking)
    {
        $this->audit = $audit;
        $this->auditFormat = $auditFormat;
        $this->auditAbandoned = $auditAbandoned;
        $this->blockInsecure = $blockInsecure;
        $this->blockAbandoned = $blockAbandoned;
        $this->ignoreUnreachable = $ignoreUnreachable;
        $this->ignoreListForAudit = $ignoreListForAudit;
        $this->ignoreListForBlocking = $ignoreListForBlocking;
        $this->ignoreSeverityForAudit = $ignoreSeverityForAudit;
        $this->ignoreSeverityForBlocking = $ignoreSeverityForBlocking;
        $this->ignoreAbandonedForAudit = $ignoreAbandonedForAudit;
        $this->ignoreAbandonedForBlocking = $ignoreAbandonedForBlocking;
    }

    /**
     * Parse ignore configuration supporting both simple and detailed formats with apply scopes
     *
     * Simple format: ['CVE-123', 'CVE-456'] or ['CVE-123' => 'reason']
     * Detailed format: ['CVE-123' => ['apply' => 'audit|block|all', 'reason' => '...']]
     *
     * @param array<mixed> $config
     * @return array{audit: array<string, string|null>, block: array<string, string|null>}
     */
    private static function parseIgnoreWithApply(array $config): array
    {
        $forAudit = [];
        $forBlock = [];

        foreach ($config as $key => $value) {
            // Simple format: ['CVE-123']
            if (is_int($key) && is_string($value)) {
                $id = $value;
                $apply = 'all';
                $reason = null;
            }
            // Simple format with reason: ['CVE-123' => 'reason']
            elseif (is_string($value)) {
                $id = $key;
                $apply = 'all';
                $reason = $value;
            }
            // Detailed format: ['CVE-123' => ['apply' => '...', 'reason' => '...']]
            elseif (is_array($value)) {
                $id = $key;
                $apply = $value['apply'] ?? 'all';
                $reason = $value['reason'] ?? null;

                // Validate apply value
                if (!in_array($apply, ['audit', 'block', 'all'], true)) {
                    throw new \InvalidArgumentException(
                        "Invalid 'apply' value for '{$id}': {$apply}. Expected 'audit', 'block', or 'all'."
                    );
                }
            }
            // Simple format with null: ['CVE-123' => null]
            elseif ($value === null) {
                $id = $key;
                $apply = 'all';
                $reason = null;
            }
            else {
                continue;
            }

            // Store in appropriate lists based on apply scope
            if ($apply === 'audit' || $apply === 'all') {
                $forAudit[$id] = $reason;
            }
            if ($apply === 'block' || $apply === 'all') {
                $forBlock[$id] = $reason;
            }
        }

        return [
            'audit' => $forAudit,
            'block' => $forBlock,
        ];
    }

    /**
     * @param Auditor::FORMAT_* $auditFormat
     */
    public static function fromConfig(Config $config, bool $audit = true, string $auditFormat = Auditor::FORMAT_SUMMARY): self
    {
        $auditConfig = $config->get('audit');

        // Parse ignore lists with apply scopes
        $ignoreListParsed = self::parseIgnoreWithApply($auditConfig['ignore'] ?? []);
        $ignoreAbandonedParsed = self::parseIgnoreWithApply($auditConfig['ignore-abandoned'] ?? []);
        $ignoreSeverityParsed = self::parseIgnoreWithApply($auditConfig['ignore-severity'] ?? []);

        return new self(
            $audit,
            $auditFormat,
            $auditConfig['abandoned'] ?? Auditor::ABANDONED_FAIL,
            (bool) ($auditConfig['block-insecure'] ?? true),
            (bool) ($auditConfig['block-abandoned'] ?? false),
            (bool) ($auditConfig['ignore-unreachable'] ?? false),
            $ignoreListParsed['audit'],
            $ignoreListParsed['block'],
            $ignoreSeverityParsed['audit'],
            $ignoreSeverityParsed['block'],
            $ignoreAbandonedParsed['audit'],
            $ignoreAbandonedParsed['block']
        );
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Advisory;

use Composer\Semver\Constraint\ConstraintInterface;
use DateTimeImmutable;

class SecurityAdvisory extends PartialSecurityAdvisory
{
    /**
     * @var string
     * @readonly
     */
    public $title;

    /**
     * @var string|null
     * @readonly
     */
    public $cve;

    /**
     * @var string|null
     * @readonly
     */
    public $link;

    /**
     * @var DateTimeImmutable
     * @readonly
     */
    public $reportedAt;

    /**
     * @var non-empty-array<array{name: string, remoteId: string}>
     * @readonly
     */
    public $sources;

    /**
     * @var string|null
     * @readonly
     */
    public $severity;

    /**
     * @param non-empty-array<array{name: string, remoteId: string}> $sources
     */
    public function __construct(string $packageName, string $advisoryId, ConstraintInterface $affectedVersions, string $title, array $sources, DateTimeImmutable $reportedAt, ?string $cve = null, ?string $link = null, ?string $severity = null)
    {
        parent::__construct($packageName, $advisoryId, $affectedVersions);

        $this->title = $title;
        $this->sources = $sources;
        $this->reportedAt = $reportedAt;
        $this->cve = $cve;
        $this->link = $link;
        $this->severity = $severity;
    }

    /**
     * @internal
     */
    public function toIgnoredAdvisory(?string $ignoreReason): IgnoredSecurityAdvisory
    {
        return new IgnoredSecurityAdvisory(
            $this->packageName,
            $this->advisoryId,
            $this->affectedVersions,
            $this->title,
            $this->sources,
            $this->reportedAt,
            $this->cve,
            $this->link,
            $ignoreReason,
            $this->severity
        );
    }

    /**
     * @return mixed
     */
    #[\ReturnTypeWillChange]
    public function jsonSerialize()
    {
        $data = parent::jsonSerialize();
        $data['reportedAt'] = $data['reportedAt']->format(DATE_RFC3339);

        return $data;
    }
}
<?php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer;

use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;

/**
 * This class is copied in every Composer installed project and available to all
 *
 * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
 *
 * To require its presence, you can require `composer-runtime-api ^2.0`
 *
 * @final
 */
class InstalledVersions
{
    /**
     * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
     * @internal
     */
    private static $selfDir = null;

    /**
     * @var mixed[]|null
     * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
     */
    private static $installed;

    /**
     * @var bool
     */
    private static $installedIsLocalDir;

    /**
     * @var bool|null
     */
    private static $canGetVendors;

    /**
     * @var array[]
     * @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
     */
    private static $installedByVendor = array();

    /**
     * Returns a list of all package names which are present, either by being installed, replaced or provided
     *
     * @return string[]
     * @psalm-return list<string>
     */
    public static function getInstalledPackages()
    {
        $packages = array();
        foreach (self::getInstalled() as $installed) {
            $packages[] = array_keys($installed['versions']);
        }

        if (1 === \count($packages)) {
            return $packages[0];
        }

        return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
    }

    /**
     * Returns a list of all package names with a specific type e.g. 'library'
     *
     * @param  string   $type
     * @return string[]
     * @psalm-return list<string>
     */
    public static function getInstalledPackagesByType($type)
    {
        $packagesByType = array();

        foreach (self::getInstalled() as $installed) {
            foreach ($installed['versions'] as $name => $package) {
                if (isset($package['type']) && $package['type'] === $type) {
                    $packagesByType[] = $name;
                }
            }
        }

        return $packagesByType;
    }

    /**
     * Checks whether the given package is installed
     *
     * This also returns true if the package name is provided or replaced by another package
     *
     * @param  string $packageName
     * @param  bool   $includeDevRequirements
     * @return bool
     */
    public static function isInstalled($packageName, $includeDevRequirements = true)
    {
        foreach (self::getInstalled() as $installed) {
            if (isset($installed['versions'][$packageName])) {
                return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
            }
        }

        return false;
    }

    /**
     * Checks whether the given package satisfies a version constraint
     *
     * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
     *
     *   Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
     *
     * @param  VersionParser $parser      Install composer/semver to have access to this class and functionality
     * @param  string        $packageName
     * @param  string|null   $constraint  A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
     * @return bool
     */
    public static function satisfies(VersionParser $parser, $packageName, $constraint)
    {
        $constraint = $parser->parseConstraints((string) $constraint);
        $provided = $parser->parseConstraints(self::getVersionRanges($packageName));

        return $provided->matches($constraint);
    }

    /**
     * Returns a version constraint representing all the range(s) which are installed for a given package
     *
     * It is easier to use this via isInstalled() with the $constraint argument if you need to check
     * whether a given version of a package is installed, and not just whether it exists
     *
     * @param  string $packageName
     * @return string Version constraint usable with composer/semver
     */
    public static function getVersionRanges($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            $ranges = array();
            if (isset($installed['versions'][$packageName]['pretty_version'])) {
                $ranges[] = $installed['versions'][$packageName]['pretty_version'];
            }
            if (array_key_exists('aliases', $installed['versions'][$packageName])) {
                $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
            }
            if (array_key_exists('replaced', $installed['versions'][$packageName])) {
                $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
            }
            if (array_key_exists('provided', $installed['versions'][$packageName])) {
                $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
            }

            return implode(' || ', $ranges);
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

    /**
     * @param  string      $packageName
     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
     */
    public static function getVersion($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            if (!isset($installed['versions'][$packageName]['version'])) {
                return null;
            }

            return $installed['versions'][$packageName]['version'];
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

    /**
     * @param  string      $packageName
     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
     */
    public static function getPrettyVersion($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            if (!isset($installed['versions'][$packageName]['pretty_version'])) {
                return null;
            }

            return $installed['versions'][$packageName]['pretty_version'];
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

    /**
     * @param  string      $packageName
     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
     */
    public static function getReference($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            if (!isset($installed['versions'][$packageName]['reference'])) {
                return null;
            }

            return $installed['versions'][$packageName]['reference'];
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

    /**
     * @param  string      $packageName
     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
     */
    public static function getInstallPath($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

    /**
     * @return array
     * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
     */
    public static function getRootPackage()
    {
        $installed = self::getInstalled();

        return $installed[0]['root'];
    }

    /**
     * Returns the raw installed.php data for custom implementations
     *
     * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
     * @return array[]
     * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
     */
    public static function getRawData()
    {
        @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);

        if (null === self::$installed) {
            // only require the installed.php file if this file is loaded from its dumped location,
            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
            if (substr(__DIR__, -8, 1) !== 'C') {
                self::$installed = include __DIR__ . '/installed.php';
            } else {
                self::$installed = array();
            }
        }

        return self::$installed;
    }

    /**
     * Returns the raw data of all installed.php which are currently loaded for custom implementations
     *
     * @return array[]
     * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
     */
    public static function getAllRawData()
    {
        return self::getInstalled();
    }

    /**
     * Lets you reload the static array from another file
     *
     * This is only useful for complex integrations in which a project needs to use
     * this class but then also needs to execute another project's autoloader in process,
     * and wants to ensure both projects have access to their version of installed.php.
     *
     * A typical case would be PHPUnit, where it would need to make sure it reads all
     * the data it needs from this class, then call reload() with
     * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
     * the project in which it runs can then also use this class safely, without
     * interference between PHPUnit's dependencies and the project's dependencies.
     *
     * @param  array[] $data A vendor/composer/installed.php data set
     * @return void
     *
     * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
     */
    public static function reload($data)
    {
        self::$installed = $data;
        self::$installedByVendor = array();

        // when using reload, we disable the duplicate protection to ensure that self::$installed data is
        // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
        // so we have to assume it does not, and that may result in duplicate data being returned when listing
        // all installed packages for example
        self::$installedIsLocalDir = false;
    }

    /**
     * @return string
     */
    private static function getSelfDir()
    {
        if (self::$selfDir === null) {
            self::$selfDir = strtr(__DIR__, '\\', '/');
        }

        return self::$selfDir;
    }

    /**
     * @return array[]
     * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
     */
    private static function getInstalled()
    {
        if (null === self::$canGetVendors) {
            self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
        }

        $installed = array();
        $copiedLocalDir = false;

        if (self::$canGetVendors) {
            $selfDir = self::getSelfDir();
            foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
                $vendorDir = strtr($vendorDir, '\\', '/');
                if (isset(self::$installedByVendor[$vendorDir])) {
                    $installed[] = self::$installedByVendor[$vendorDir];
                } elseif (is_file($vendorDir.'/composer/installed.php')) {
                    /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
                    $required = require $vendorDir.'/composer/installed.php';
                    self::$installedByVendor[$vendorDir] = $required;
                    $installed[] = $required;
                    if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
                        self::$installed = $required;
                        self::$installedIsLocalDir = true;
                    }
                }
                if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
                    $copiedLocalDir = true;
                }
            }
        }

        if (null === self::$installed) {
            // only require the installed.php file if this file is loaded from its dumped location,
            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
            if (substr(__DIR__, -8, 1) !== 'C') {
                /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
                $required = require __DIR__ . '/installed.php';
                self::$installed = $required;
            } else {
                self::$installed = array();
            }
        }

        if (self::$installed !== array() && !$copiedLocalDir) {
            $installed[] = self::$installed;
        }

        return $installed;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package;

use Composer\Repository\RepositoryInterface;

/**
 * Defines the essential information a package has that is used during solving/installation
 *
 * PackageInterface & derivatives are considered internal, you may use them in type hints but extending/implementing them is not recommended and not supported. Things may change without notice.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 *
 * @phpstan-type AutoloadRules    array{psr-0?: array<string, string|string[]>, psr-4?: array<string, string|string[]>, classmap?: list<string>, files?: list<string>, exclude-from-classmap?: list<string>}
 * @phpstan-type DevAutoloadRules array{psr-0?: array<string, string|string[]>, psr-4?: array<string, string|string[]>, classmap?: list<string>, files?: list<string>}
 * @phpstan-type PhpExtConfig     array{extension-name?: string, priority?: int, support-zts?: bool, support-nts?: bool, build-path?: string|null, download-url-method?: string|list<string>, os-families?: non-empty-list<non-empty-string>, os-families-exclude?: non-empty-list<non-empty-string>, configure-options?: list<array{name: string, description?: string}>}
 */
interface PackageInterface
{
    public const DISPLAY_SOURCE_REF_IF_DEV = 0;
    public const DISPLAY_SOURCE_REF = 1;
    public const DISPLAY_DIST_REF = 2;

    /**
     * Returns the package's name without version info, thus not a unique identifier
     *
     * @return string package name
     */
    public function getName(): string;

    /**
     * Returns the package's pretty (i.e. with proper case) name
     *
     * @return string package name
     */
    public function getPrettyName(): string;

    /**
     * Returns a set of names that could refer to this package
     *
     * No version or release type information should be included in any of the
     * names. Provided or replaced package names need to be returned as well.
     *
     * @param bool $provides Whether provided names should be included
     *
     * @return string[] An array of strings referring to this package
     */
    public function getNames(bool $provides = true): array;

    /**
     * Allows the solver to set an id for this package to refer to it.
     */
    public function setId(int $id): void;

    /**
     * Retrieves the package's id set through setId
     *
     * @return int The previously set package id
     */
    public function getId(): int;

    /**
     * Returns whether the package is a development virtual package or a concrete one
     */
    public function isDev(): bool;

    /**
     * Returns the package type, e.g. library
     *
     * @return string The package type
     */
    public function getType(): string;

    /**
     * Returns the package targetDir property
     *
     * @return ?string The package targetDir
     */
    public function getTargetDir(): ?string;

    /**
     * Returns the package extra data
     *
     * @return mixed[] The package extra data
     */
    public function getExtra(): array;

    /**
     * Sets source from which this package was installed (source/dist).
     *
     * @param ?string $type source/dist
     * @phpstan-param 'source'|'dist'|null $type
     */
    public function setInstallationSource(?string $type): void;

    /**
     * Returns source from which this package was installed (source/dist).
     *
     * @return ?string source/dist
     * @phpstan-return 'source'|'dist'|null
     */
    public function getInstallationSource(): ?string;

    /**
     * Returns the repository type of this package, e.g. git, svn
     *
     * @return ?string The repository type
     */
    public function getSourceType(): ?string;

    /**
     * Returns the repository url of this package, e.g. git://github.com/naderman/composer.git
     *
     * @return ?string The repository url
     */
    public function getSourceUrl(): ?string;

    /**
     * Returns the repository urls of this package including mirrors, e.g. git://github.com/naderman/composer.git
     *
     * @return list<string>
     */
    public function getSourceUrls(): array;

    /**
     * Returns the repository reference of this package, e.g. master, 1.0.0 or a commit hash for git
     *
     * @return ?string The repository reference
     */
    public function getSourceReference(): ?string;

    /**
     * Returns the source mirrors of this package
     *
     * @return ?list<array{url: non-empty-string, preferred: bool}>
     */
    public function getSourceMirrors(): ?array;

    /**
     * @param  null|list<array{url: non-empty-string, preferred: bool}> $mirrors
     */
    public function setSourceMirrors(?array $mirrors): void;

    /**
     * Returns the type of the distribution archive of this version, e.g. zip, tarball
     *
     * @return ?string The repository type
     */
    public function getDistType(): ?string;

    /**
     * Returns the url of the distribution archive of this version
     *
     * @return ?non-empty-string
     */
    public function getDistUrl(): ?string;

    /**
     * Returns the urls of the distribution archive of this version, including mirrors
     *
     * @return non-empty-string[]
     */
    public function getDistUrls(): array;

    /**
     * Returns the reference of the distribution archive of this version, e.g. master, 1.0.0 or a commit hash for git
     */
    public function getDistReference(): ?string;

    /**
     * Returns the sha1 checksum for the distribution archive of this version
     *
     * Can be an empty string which should be treated as null
     */
    public function getDistSha1Checksum(): ?string;

    /**
     * Returns the dist mirrors of this package
     *
     * @return ?list<array{url: non-empty-string, preferred: bool}>
     */
    public function getDistMirrors(): ?array;

    /**
     * @param  null|list<array{url: non-empty-string, preferred: bool}> $mirrors
     */
    public function setDistMirrors(?array $mirrors): void;

    /**
     * Returns the version of this package
     *
     * @return string version
     */
    public function getVersion(): string;

    /**
     * Returns the pretty (i.e. non-normalized) version string of this package
     *
     * @return string version
     */
    public function getPrettyVersion(): string;

    /**
     * Returns the pretty version string plus a git or hg commit hash of this package
     *
     * @see getPrettyVersion
     *
     * @param  bool   $truncate    If the source reference is a sha1 hash, truncate it
     * @param  int    $displayMode One of the DISPLAY_ constants on this interface determining display of references
     * @return string version
     *
     * @phpstan-param self::DISPLAY_SOURCE_REF_IF_DEV|self::DISPLAY_SOURCE_REF|self::DISPLAY_DIST_REF $displayMode
     */
    public function getFullPrettyVersion(bool $truncate = true, int $displayMode = self::DISPLAY_SOURCE_REF_IF_DEV): string;

    /**
     * Returns the release date of the package
     */
    public function getReleaseDate(): ?\DateTimeInterface;

    /**
     * Returns the stability of this package: one of (dev, alpha, beta, RC, stable)
     *
     * @phpstan-return 'stable'|'RC'|'beta'|'alpha'|'dev'
     */
    public function getStability(): string;

    /**
     * Returns a set of links to packages which need to be installed before
     * this package can be installed
     *
     * @return array<string, Link> A map of package links defining required packages, indexed by the require package's name
     */
    public function getRequires(): array;

    /**
     * Returns a set of links to packages which must not be installed at the
     * same time as this package
     *
     * @return Link[] An array of package links defining conflicting packages
     */
    public function getConflicts(): array;

    /**
     * Returns a set of links to virtual packages that are provided through
     * this package
     *
     * @return Link[] An array of package links defining provided packages
     */
    public function getProvides(): array;

    /**
     * Returns a set of links to packages which can alternatively be
     * satisfied by installing this package
     *
     * @return Link[] An array of package links defining replaced packages
     */
    public function getReplaces(): array;

    /**
     * Returns a set of links to packages which are required to develop
     * this package. These are installed if in dev mode.
     *
     * @return array<string, Link> A map of package links defining packages required for development, indexed by the require package's name
     */
    public function getDevRequires(): array;

    /**
     * Returns a set of package names and reasons why they are useful in
     * combination with this package.
     *
     * @return array An array of package suggestions with descriptions
     * @phpstan-return array<string, string>
     */
    public function getSuggests(): array;

    /**
     * Returns an associative array of autoloading rules
     *
     * {"<type>": {"<namespace": "<directory>"}}
     *
     * Type is either "psr-4", "psr-0", "classmap" or "files". Namespaces are mapped to
     * directories for autoloading using the type specified.
     *
     * @return array Mapping of autoloading rules
     * @phpstan-return AutoloadRules
     */
    public function getAutoload(): array;

    /**
     * Returns an associative array of dev autoloading rules
     *
     * {"<type>": {"<namespace": "<directory>"}}
     *
     * Type is either "psr-4", "psr-0", "classmap" or "files". Namespaces are mapped to
     * directories for autoloading using the type specified.
     *
     * @return array Mapping of dev autoloading rules
     * @phpstan-return DevAutoloadRules
     */
    public function getDevAutoload(): array;

    /**
     * Returns a list of directories which should get added to PHP's
     * include path.
     *
     * @return string[]
     */
    public function getIncludePaths(): array;

    /**
     * Returns the settings for php extension packages
     *
     *
     * @phpstan-return PhpExtConfig|null
     */
    public function getPhpExt(): ?array;

    /**
     * Stores a reference to the repository that owns the package
     */
    public function setRepository(RepositoryInterface $repository): void;

    /**
     * Returns a reference to the repository that owns the package
     */
    public function getRepository(): ?RepositoryInterface;

    /**
     * Returns the package binaries
     *
     * @return string[]
     */
    public function getBinaries(): array;

    /**
     * Returns package unique name, constructed from name and version.
     */
    public function getUniqueName(): string;

    /**
     * Returns the package notification url
     */
    public function getNotificationUrl(): ?string;

    /**
     * Converts the package into a readable and unique string
     */
    public function __toString(): string;

    /**
     * Converts the package into a pretty readable string
     */
    public function getPrettyString(): string;

    public function isDefaultBranch(): bool;

    /**
     * Returns a list of options to download package dist files
     *
     * @return mixed[]
     */
    public function getTransportOptions(): array;

    /**
     * Configures the list of options to download package dist files
     *
     * @param mixed[] $options
     */
    public function setTransportOptions(array $options): void;

    public function setSourceReference(?string $reference): void;

    public function setDistUrl(?string $url): void;

    public function setDistType(?string $type): void;

    public function setDistReference(?string $reference): void;

    /**
     * Set dist and source references and update dist URL for ones that contain a reference
     */
    public function setSourceDistReferences(string $reference): void;
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package;

/**
 * The root package represents the project's composer.json and contains additional metadata
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class RootPackage extends CompletePackage implements RootPackageInterface
{
    public const DEFAULT_PRETTY_VERSION = '1.0.0+no-version-set';

    /** @var key-of<BasePackage::STABILITIES> */
    protected $minimumStability = 'stable';
    /** @var bool */
    protected $preferStable = false;
    /** @var array<string, BasePackage::STABILITY_*> Map of package name to stability constant */
    protected $stabilityFlags = [];
    /** @var mixed[] */
    protected $config = [];
    /** @var array<string, string> Map of package name to reference/commit hash */
    protected $references = [];
    /** @var list<array{package: string, version: string, alias: string, alias_normalized: string}> */
    protected $aliases = [];

    /**
     * @inheritDoc
     */
    public function setMinimumStability(string $minimumStability): void
    {
        $this->minimumStability = $minimumStability;
    }

    /**
     * @inheritDoc
     */
    public function getMinimumStability(): string
    {
        return $this->minimumStability;
    }

    /**
     * @inheritDoc
     */
    public function setStabilityFlags(array $stabilityFlags): void
    {
        $this->stabilityFlags = $stabilityFlags;
    }

    /**
     * @inheritDoc
     */
    public function getStabilityFlags(): array
    {
        return $this->stabilityFlags;
    }

    /**
     * @inheritDoc
     */
    public function setPreferStable(bool $preferStable): void
    {
        $this->preferStable = $preferStable;
    }

    /**
     * @inheritDoc
     */
    public function getPreferStable(): bool
    {
        return $this->preferStable;
    }

    /**
     * @inheritDoc
     */
    public function setConfig(array $config): void
    {
        $this->config = $config;
    }

    /**
     * @inheritDoc
     */
    public function getConfig(): array
    {
        return $this->config;
    }

    /**
     * @inheritDoc
     */
    public function setReferences(array $references): void
    {
        $this->references = $references;
    }

    /**
     * @inheritDoc
     */
    public function getReferences(): array
    {
        return $this->references;
    }

    /**
     * @inheritDoc
     */
    public function setAliases(array $aliases): void
    {
        $this->aliases = $aliases;
    }

    /**
     * @inheritDoc
     */
    public function getAliases(): array
    {
        return $this->aliases;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package;

/**
 * Defines package metadata that is not necessarily needed for solving and installing packages
 *
 * PackageInterface & derivatives are considered internal, you may use them in type hints but extending/implementing them is not recommended and not supported. Things may change without notice.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
interface CompletePackageInterface extends PackageInterface
{
    /**
     * Returns the scripts of this package
     *
     * @return array<string, string[]> Map of script name to array of handlers
     */
    public function getScripts(): array;

    /**
     * @param  array<string, string[]> $scripts
     */
    public function setScripts(array $scripts): void;

    /**
     * Returns an array of repositories
     *
     * @return mixed[] Repositories
     */
    public function getRepositories(): array;

    /**
     * Set the repositories
     *
     * @param  mixed[] $repositories
     */
    public function setRepositories(array $repositories): void;

    /**
     * Returns the package license, e.g. MIT, BSD, GPL
     *
     * @return string[] The package licenses
     */
    public function getLicense(): array;

    /**
     * Set the license
     *
     * @param  string[] $license
     */
    public function setLicense(array $license): void;

    /**
     * Returns an array of keywords relating to the package
     *
     * @return string[]
     */
    public function getKeywords(): array;

    /**
     * Set the keywords
     *
     * @param  string[] $keywords
     */
    public function setKeywords(array $keywords): void;

    /**
     * Returns the package description
     */
    public function getDescription(): ?string;

    /**
     * Set the description
     */
    public function setDescription(string $description): void;

    /**
     * Returns the package homepage
     */
    public function getHomepage(): ?string;

    /**
     * Set the homepage
     */
    public function setHomepage(string $homepage): void;

    /**
     * Returns an array of authors of the package
     *
     * Each item can contain name/homepage/email keys
     *
     * @return array<array{name?: string, homepage?: string, email?: string, role?: string}>
     */
    public function getAuthors(): array;

    /**
     * Set the authors
     *
     * @param  array<array{name?: string, homepage?: string, email?: string, role?: string}> $authors
     */
    public function setAuthors(array $authors): void;

    /**
     * Returns the support information
     *
     * @return array{issues?: string, forum?: string, wiki?: string, source?: string, email?: string, irc?: string, docs?: string, rss?: string, chat?: string, security?: string}
     */
    public function getSupport(): array;

    /**
     * Set the support information
     *
     * @param  array{issues?: string, forum?: string, wiki?: string, source?: string, email?: string, irc?: string, docs?: string, rss?: string, chat?: string, security?: string} $support
     */
    public function setSupport(array $support): void;

    /**
     * Returns an array of funding options for the package
     *
     * Each item will contain type and url keys
     *
     * @return array<array{type?: string, url?: string}>
     */
    public function getFunding(): array;

    /**
     * Set the funding
     *
     * @param  array<array{type?: string, url?: string}> $funding
     */
    public function setFunding(array $funding): void;

    /**
     * Returns if the package is abandoned or not
     */
    public function isAbandoned(): bool;

    /**
     * If the package is abandoned and has a suggested replacement, this method returns it
     */
    public function getReplacementPackage(): ?string;

    /**
     * @param  bool|string $abandoned
     */
    public function setAbandoned($abandoned): void;

    /**
     * Returns default base filename for archive
     */
    public function getArchiveName(): ?string;

    /**
     * Sets default base filename for archive
     */
    public function setArchiveName(string $name): void;

    /**
     * Returns a list of patterns to exclude from package archives
     *
     * @return string[]
     */
    public function getArchiveExcludes(): array;

    /**
     * Sets a list of patterns to be excluded from archives
     *
     * @param  string[] $excludes
     */
    public function setArchiveExcludes(array $excludes): void;
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Archiver;

use Composer\Pcre\Preg;
use Symfony\Component\Finder;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
abstract class BaseExcludeFilter
{
    /**
     * @var string
     */
    protected $sourcePath;

    /**
     * @var array<array{0: non-empty-string, 1: bool, 2: bool}> array of [$pattern, $negate, $stripLeadingSlash] arrays
     */
    protected $excludePatterns;

    /**
     * @param string $sourcePath Directory containing sources to be filtered
     */
    public function __construct(string $sourcePath)
    {
        $this->sourcePath = $sourcePath;
        $this->excludePatterns = [];
    }

    /**
     * Checks the given path against all exclude patterns in this filter
     *
     * Negated patterns overwrite exclude decisions of previous filters.
     *
     * @param string $relativePath The file's path relative to the sourcePath
     * @param bool $exclude Whether a previous filter wants to exclude this file
     *
     * @return bool Whether the file should be excluded
     */
    public function filter(string $relativePath, bool $exclude): bool
    {
        foreach ($this->excludePatterns as $patternData) {
            [$pattern, $negate, $stripLeadingSlash] = $patternData;

            if ($stripLeadingSlash) {
                $path = substr($relativePath, 1);
            } else {
                $path = $relativePath;
            }

            try {
                if (Preg::isMatch($pattern, $path)) {
                    $exclude = !$negate;
                }
            } catch (\RuntimeException $e) {
                // suppressed
            }
        }

        return $exclude;
    }

    /**
     * Processes a file containing exclude rules of different formats per line
     *
     * @param string[] $lines A set of lines to be parsed
     * @param callable $lineParser The parser to be used on each line
     *
     * @return array<array{0: non-empty-string, 1: bool, 2: bool}> Exclude patterns to be used in filter()
     */
    protected function parseLines(array $lines, callable $lineParser): array
    {
        return array_filter(
            array_map(
                static function ($line) use ($lineParser) {
                    $line = trim($line);

                    if (!$line || 0 === strpos($line, '#')) {
                        return null;
                    }

                    return $lineParser($line);
                },
                $lines
            ),
            static function ($pattern): bool {
                return $pattern !== null;
            }
        );
    }

    /**
     * Generates a set of exclude patterns for filter() from gitignore rules
     *
     * @param string[] $rules A list of exclude rules in gitignore syntax
     *
     * @return array<int, array{0: non-empty-string, 1: bool, 2: bool}> Exclude patterns
     */
    protected function generatePatterns(array $rules): array
    {
        $patterns = [];
        foreach ($rules as $rule) {
            $patterns[] = $this->generatePattern($rule);
        }

        return $patterns;
    }

    /**
     * Generates an exclude pattern for filter() from a gitignore rule
     *
     * @param string $rule An exclude rule in gitignore syntax
     *
     * @return array{0: non-empty-string, 1: bool, 2: bool} An exclude pattern
     */
    protected function generatePattern(string $rule): array
    {
        $negate = false;
        $pattern = '';

        if ($rule !== '' && $rule[0] === '!') {
            $negate = true;
            $rule = ltrim($rule, '!');
        }

        $firstSlashPosition = strpos($rule, '/');
        if (0 === $firstSlashPosition) {
            $pattern = '^/';
        } elseif (false === $firstSlashPosition || strlen($rule) - 1 === $firstSlashPosition) {
            $pattern = '/';
        }

        $rule = trim($rule, '/');

        // remove delimiters as well as caret (^) and dollar sign ($) from the regex
        $rule = substr(Finder\Glob::toRegex($rule), 2, -2);

        return ['{'.$pattern.$rule.'(?=$|/)}', $negate, false];
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Archiver;

/**
 * @author Till Klampaeckel <till@php.net>
 * @author Matthieu Moquet <matthieu@moquet.net>
 * @author Nils Adermann <naderman@naderman.de>
 */
interface ArchiverInterface
{
    /**
     * Create an archive from the sources.
     *
     * @param string   $sources       The sources directory
     * @param string   $target        The target file
     * @param string   $format        The format used for archive
     * @param string[] $excludes      A list of patterns for files to exclude
     * @param bool     $ignoreFilters Whether to ignore filters when looking for files
     *
     * @return string The path to the written archive file
     */
    public function archive(string $sources, string $target, string $format, array $excludes = [], bool $ignoreFilters = false): string;

    /**
     * Format supported by the archiver.
     *
     * @param string $format     The archive format
     * @param ?string $sourceType The source type (git, svn, hg, etc.)
     *
     * @return bool true if the format is supported by the archiver
     */
    public function supports(string $format, ?string $sourceType): bool;
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Archiver;

use Composer\Downloader\DownloadManager;
use Composer\Package\RootPackageInterface;
use Composer\Pcre\Preg;
use Composer\Util\Filesystem;
use Composer\Util\Loop;
use Composer\Util\SyncHelper;
use Composer\Json\JsonFile;
use Composer\Package\CompletePackageInterface;

/**
 * @author Matthieu Moquet <matthieu@moquet.net>
 * @author Till Klampaeckel <till@php.net>
 */
class ArchiveManager
{
    /** @var DownloadManager */
    protected $downloadManager;
    /** @var Loop */
    protected $loop;

    /**
     * @var ArchiverInterface[]
     */
    protected $archivers = [];

    /**
     * @var bool
     */
    protected $overwriteFiles = true;

    /**
     * @param DownloadManager $downloadManager A manager used to download package sources
     */
    public function __construct(DownloadManager $downloadManager, Loop $loop)
    {
        $this->downloadManager = $downloadManager;
        $this->loop = $loop;
    }

    public function addArchiver(ArchiverInterface $archiver): void
    {
        $this->archivers[] = $archiver;
    }

    /**
     * Set whether existing archives should be overwritten
     *
     * @param bool $overwriteFiles New setting
     *
     * @return $this
     */
    public function setOverwriteFiles(bool $overwriteFiles): self
    {
        $this->overwriteFiles = $overwriteFiles;

        return $this;
    }

    /**
     * @return array<string, string>
     * @internal
     */
    public function getPackageFilenameParts(CompletePackageInterface $package): array
    {
        $baseName = $package->getArchiveName();
        if (null === $baseName) {
            $baseName = Preg::replace('#[^a-z0-9-_]#i', '-', $package->getName());
        }

        $parts = [
            'base' => $baseName,
        ];

        $distReference = $package->getDistReference();
        if (null !== $distReference && Preg::isMatch('{^[a-f0-9]{40}$}', $distReference)) {
            $parts['dist_reference'] = $distReference;
            $parts['dist_type'] = $package->getDistType();
        } else {
            $parts['version'] = $package->getPrettyVersion();
            $parts['dist_reference'] = $distReference;
        }

        $sourceReference = $package->getSourceReference();
        if (null !== $sourceReference) {
            $parts['source_reference'] = substr(hash('sha1', $sourceReference), 0, 6);
        }

        $parts = array_filter($parts, static function (?string $part) {
            return $part !== null;
        });
        foreach ($parts as $key => $part) {
            $parts[$key] = str_replace('/', '-', $part);
        }

        return $parts;
    }

    /**
     * @param array<string, string> $parts
     *
     * @internal
     */
    public function getPackageFilenameFromParts(array $parts): string
    {
        return implode('-', $parts);
    }

    /**
     * Generate a distinct filename for a particular version of a package.
     *
     * @param CompletePackageInterface $package The package to get a name for
     *
     * @return string A filename without an extension
     */
    public function getPackageFilename(CompletePackageInterface $package): string
    {
        return $this->getPackageFilenameFromParts($this->getPackageFilenameParts($package));
    }

    /**
     * Create an archive of the specified package.
     *
     * @param  CompletePackageInterface  $package       The package to archive
     * @param  string                    $format        The format of the archive (zip, tar, ...)
     * @param  string                    $targetDir     The directory where to build the archive
     * @param  string|null               $fileName      The relative file name to use for the archive, or null to generate
     *                                                  the package name. Note that the format will be appended to this name
     * @param  bool                      $ignoreFilters Ignore filters when looking for files in the package
     * @throws \InvalidArgumentException
     * @throws \RuntimeException
     * @return string                    The path of the created archive
     */
    public function archive(CompletePackageInterface $package, string $format, string $targetDir, ?string $fileName = null, bool $ignoreFilters = false): string
    {
        if (empty($format)) {
            throw new \InvalidArgumentException('Format must be specified');
        }

        // Search for the most appropriate archiver
        $usableArchiver = null;
        foreach ($this->archivers as $archiver) {
            if ($archiver->supports($format, $package->getSourceType())) {
                $usableArchiver = $archiver;
                break;
            }
        }

        // Checks the format/source type are supported before downloading the package
        if (null === $usableArchiver) {
            throw new \RuntimeException(sprintf('No archiver found to support %s format', $format));
        }

        $filesystem = new Filesystem();

        if ($package instanceof RootPackageInterface) {
            $sourcePath = realpath('.');
        } else {
            // Directory used to download the sources
            $sourcePath = sys_get_temp_dir().'/composer_archive'.bin2hex(random_bytes(5));
            $filesystem->ensureDirectoryExists($sourcePath);

            try {
                // Download sources
                $promise = $this->downloadManager->download($package, $sourcePath);
                SyncHelper::await($this->loop, $promise);
                $promise = $this->downloadManager->install($package, $sourcePath);
                SyncHelper::await($this->loop, $promise);
            } catch (\Exception $e) {
                $filesystem->removeDirectory($sourcePath);
                throw  $e;
            }

            // Check exclude from downloaded composer.json
            if (file_exists($composerJsonPath = $sourcePath.'/composer.json')) {
                $jsonFile = new JsonFile($composerJsonPath);
                $jsonData = $jsonFile->read();
                if (!empty($jsonData['archive']['name'])) {
                    $package->setArchiveName($jsonData['archive']['name']);
                }
                if (!empty($jsonData['archive']['exclude'])) {
                    $package->setArchiveExcludes($jsonData['archive']['exclude']);
                }
            }
        }

        $supportedFormats = $this->getSupportedFormats();
        $packageNameParts = null === $fileName ?
            $this->getPackageFilenameParts($package)
            : ['base' => $fileName];

        $packageName = $this->getPackageFilenameFromParts($packageNameParts);
        $excludePatterns = $this->buildExcludePatterns($packageNameParts, $supportedFormats);

        // Archive filename
        $filesystem->ensureDirectoryExists($targetDir);
        $target = realpath($targetDir).'/'.$packageName.'.'.$format;
        $filesystem->ensureDirectoryExists(dirname($target));

        if (!$this->overwriteFiles && file_exists($target)) {
            return $target;
        }

        // Create the archive
        $tempTarget = sys_get_temp_dir().'/composer_archive'.bin2hex(random_bytes(5)).'.'.$format;
        $filesystem->ensureDirectoryExists(dirname($tempTarget));

        $archivePath = $usableArchiver->archive(
            $sourcePath,
            $tempTarget,
            $format,
            array_merge($excludePatterns, $package->getArchiveExcludes()),
            $ignoreFilters
        );
        $filesystem->rename($archivePath, $target);

        // cleanup temporary download
        if (!$package instanceof RootPackageInterface) {
            $filesystem->removeDirectory($sourcePath);
        }
        $filesystem->remove($tempTarget);

        return $target;
    }

    /**
     * @param string[] $parts
     * @param string[] $formats
     *
     * @return string[]
     */
    private function buildExcludePatterns(array $parts, array $formats): array
    {
        $base = $parts['base'];
        if (count($parts) > 1) {
            $base .= '-*';
        }

        $patterns = [];
        foreach ($formats as $format) {
            $patterns[] = "$base.$format";
        }

        return $patterns;
    }

    /**
     * @return string[]
     */
    private function getSupportedFormats(): array
    {
        // The problem is that the \Composer\Package\Archiver\ArchiverInterface
        // doesn't provide method to get the supported formats.
        // Supported formats are also hard-coded into the description of the
        // --format option.
        // See \Composer\Command\ArchiveCommand::configure().
        $formats = [];
        foreach ($this->archivers as $archiver) {
            $items = [];
            switch (get_class($archiver)) {
                case ZipArchiver::class:
                    $items = ['zip'];
                    break;

                case PharArchiver::class:
                    $items = ['zip', 'tar', 'tar.gz', 'tar.bz2'];
                    break;
            }

            $formats = array_merge($formats, $items);
        }

        return array_unique($formats);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Archiver;

use PharData;

/**
 * @author Till Klampaeckel <till@php.net>
 * @author Nils Adermann <naderman@naderman.de>
 * @author Matthieu Moquet <matthieu@moquet.net>
 */
class PharArchiver implements ArchiverInterface
{
    /** @var array<string, int> */
    protected static $formats = [
        'zip' => \Phar::ZIP,
        'tar' => \Phar::TAR,
        'tar.gz' => \Phar::TAR,
        'tar.bz2' => \Phar::TAR,
    ];

    /** @var array<string, int> */
    protected static $compressFormats = [
        'tar.gz' => \Phar::GZ,
        'tar.bz2' => \Phar::BZ2,
    ];

    /**
     * @inheritDoc
     */
    public function archive(string $sources, string $target, string $format, array $excludes = [], bool $ignoreFilters = false): string
    {
        $sources = realpath($sources);

        // Phar would otherwise load the file which we don't want
        if (file_exists($target)) {
            unlink($target);
        }

        try {
            $filename = substr($target, 0, strrpos($target, $format) - 1);

            // Check if compress format
            if (isset(static::$compressFormats[$format])) {
                // Current compress format supported base on tar
                $target = $filename . '.tar';
            }

            $phar = new PharData(
                $target,
                \FilesystemIterator::KEY_AS_PATHNAME | \FilesystemIterator::CURRENT_AS_FILEINFO,
                '',
                static::$formats[$format]
            );
            $files = new ArchivableFilesFinder($sources, $excludes, $ignoreFilters);
            $filesOnly = new ArchivableFilesFilter($files);
            $phar->buildFromIterator($filesOnly, $sources);
            $filesOnly->addEmptyDir($phar, $sources);

            if (!file_exists($target)) {
                $target = $filename . '.' . $format;
                unset($phar);

                if ($format === 'tar') {
                    // create an empty tar file (=10240 null bytes) if the tar file is empty and PharData thus did not write it to disk
                    file_put_contents($target, str_repeat("\0", 10240));
                } elseif ($format === 'zip') {
                    // create minimal valid ZIP file (Empty Central Directory + End of Central Directory record)
                    $eocd = pack(
                        'VvvvvVVv',
                        0x06054b50,  // End of central directory signature
                        0,           // Number of this disk
                        0,           // Disk where central directory starts
                        0,           // Number of central directory records on this disk
                        0,           // Total number of central directory records
                        0,           // Size of central directory (bytes)
                        0,           // Offset of start of central directory
                        0            // Comment length
                    );

                    file_put_contents($target, $eocd);
                } elseif ($format === 'tar.gz' || $format === 'tar.bz2') {
                    if (!PharData::canCompress(static::$compressFormats[$format])) {
                        throw new \RuntimeException(sprintf('Can not compress to %s format', $format));
                    }
                    if ($format === 'tar.gz' && function_exists('gzcompress')) {
                        file_put_contents($target, gzcompress(str_repeat("\0", 10240)));
                    } elseif ($format === 'tar.bz2' && function_exists('bzcompress')) {
                        file_put_contents($target, bzcompress(str_repeat("\0", 10240)));
                    }
                }

                return $target;
            }

            if (isset(static::$compressFormats[$format])) {
                // Check can be compressed?
                if (!PharData::canCompress(static::$compressFormats[$format])) {
                    throw new \RuntimeException(sprintf('Can not compress to %s format', $format));
                }

                // Delete old tar
                unlink($target);

                // Compress the new tar
                $phar->compress(static::$compressFormats[$format]);

                // Make the correct filename
                $target = $filename . '.' . $format;
            }

            return $target;
        } catch (\UnexpectedValueException $e) {
            $message = sprintf(
                "Could not create archive '%s' from '%s': %s",
                $target,
                $sources,
                $e->getMessage()
            );

            throw new \RuntimeException($message, $e->getCode(), $e);
        }
    }

    /**
     * @inheritDoc
     */
    public function supports(string $format, ?string $sourceType): bool
    {
        return isset(static::$formats[$format]);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Archiver;

use Composer\Pcre\Preg;
use Composer\Util\Filesystem;
use FilesystemIterator;
use FilterIterator;
use Iterator;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;

/**
 * A Symfony Finder wrapper which locates files that should go into archives
 *
 * Handles .gitignore, .gitattributes and .hgignore files as well as composer's
 * own exclude rules from composer.json
 *
 * @author Nils Adermann <naderman@naderman.de>
 * @phpstan-extends FilterIterator<string, SplFileInfo, Iterator<string, SplFileInfo>>
 */
class ArchivableFilesFinder extends FilterIterator
{
    /**
     * @var Finder
     */
    protected $finder;

    /**
     * Initializes the internal Symfony Finder with appropriate filters
     *
     * @param string $sources Path to source files to be archived
     * @param string[] $excludes Composer's own exclude rules from composer.json
     * @param bool $ignoreFilters Ignore filters when looking for files
     */
    public function __construct(string $sources, array $excludes, bool $ignoreFilters = false)
    {
        $fs = new Filesystem();

        $sourcesRealPath = realpath($sources);
        if ($sourcesRealPath === false) {
            throw new \RuntimeException('Could not realpath() the source directory "'.$sources.'"');
        }
        $sources = $fs->normalizePath($sourcesRealPath);

        if ($ignoreFilters) {
            $filters = [];
        } else {
            $filters = [
                new GitExcludeFilter($sources),
                new ComposerExcludeFilter($sources, $excludes),
            ];
        }

        $this->finder = new Finder();

        $filter = static function (\SplFileInfo $file) use ($sources, $filters, $fs): bool {
            $realpath = $file->getRealPath();
            if ($realpath === false) {
                return false;
            }
            if ($file->isLink() && strpos($realpath, $sources) !== 0) {
                return false;
            }

            $relativePath = Preg::replace(
                '#^'.preg_quote($sources, '#').'#',
                '',
                $fs->normalizePath($realpath)
            );

            $exclude = false;
            foreach ($filters as $filter) {
                $exclude = $filter->filter($relativePath, $exclude);
            }

            return !$exclude;
        };

        $this->finder
            ->in($sources)
            ->filter($filter)
            ->ignoreVCS(true)
            ->ignoreDotFiles(false)
            ->sortByName();

        parent::__construct($this->finder->getIterator());
    }

    public function accept(): bool
    {
        /** @var SplFileInfo $current */
        $current = $this->getInnerIterator()->current();

        if (!$current->isDir()) {
            return true;
        }

        $iterator = new FilesystemIterator((string) $current, FilesystemIterator::SKIP_DOTS);

        return !$iterator->valid();
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Archiver;

use Composer\Pcre\Preg;

/**
 * An exclude filter that processes gitattributes
 *
 * It respects export-ignore git attributes
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class GitExcludeFilter extends BaseExcludeFilter
{
    /**
     * Parses .gitattributes if it exists
     */
    public function __construct(string $sourcePath)
    {
        parent::__construct($sourcePath);

        if (file_exists($sourcePath.'/.gitattributes')) {
            $this->excludePatterns = array_merge(
                $this->excludePatterns,
                $this->parseLines(
                    file($sourcePath.'/.gitattributes'),
                    [$this, 'parseGitAttributesLine']
                )
            );
        }
    }

    /**
     * Callback parser which finds export-ignore rules in git attribute lines
     *
     * @param string $line A line from .gitattributes
     *
     * @return array{0: string, 1: bool, 2: bool}|null An exclude pattern for filter()
     */
    public function parseGitAttributesLine(string $line): ?array
    {
        $parts = Preg::split('#\s+#', $line);

        if (count($parts) === 2 && $parts[1] === 'export-ignore') {
            return $this->generatePattern($parts[0]);
        }

        if (count($parts) === 2 && $parts[1] === '-export-ignore') {
            return $this->generatePattern('!'.$parts[0]);
        }

        return null;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Archiver;

/**
 * An exclude filter which processes composer's own exclude rules
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class ComposerExcludeFilter extends BaseExcludeFilter
{
    /**
     * @param string $sourcePath Directory containing sources to be filtered
     * @param string[] $excludeRules An array of exclude rules from composer.json
     */
    public function __construct(string $sourcePath, array $excludeRules)
    {
        parent::__construct($sourcePath);
        $this->excludePatterns = $this->generatePatterns($excludeRules);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Archiver;

use Composer\Util\Filesystem;
use Composer\Util\Platform;
use ZipArchive;

/**
 * @author Jan Prieser <jan@prieser.net>
 */
class ZipArchiver implements ArchiverInterface
{
    /** @var array<string, bool> */
    protected static $formats = [
        'zip' => true,
    ];

    /**
     * @inheritDoc
     */
    public function archive(string $sources, string $target, string $format, array $excludes = [], bool $ignoreFilters = false): string
    {
        $fs = new Filesystem();
        $sourcesRealpath = realpath($sources);
        if (false !== $sourcesRealpath) {
            $sources = $sourcesRealpath;
        }
        unset($sourcesRealpath);
        $sources = $fs->normalizePath($sources);

        $zip = new ZipArchive();
        $res = $zip->open($target, ZipArchive::CREATE);
        if ($res === true) {
            $files = new ArchivableFilesFinder($sources, $excludes, $ignoreFilters);
            foreach ($files as $file) {
                /** @var \Symfony\Component\Finder\SplFileInfo $file */
                $filepath = $file->getPathname();
                $relativePath = $file->getRelativePathname();

                if (Platform::isWindows()) {
                    $relativePath = strtr($relativePath, '\\', '/');
                }

                if ($file->isDir()) {
                    $zip->addEmptyDir($relativePath);
                } else {
                    $zip->addFile($filepath, $relativePath);
                }

                /**
                 * setExternalAttributesName() is only available with libzip 0.11.2 or above
                 */
                if (method_exists($zip, 'setExternalAttributesName')) {
                    $perms = fileperms($filepath);

                    /**
                     * Ensure to preserve the permission umasks for the filepath in the archive.
                     */
                    $zip->setExternalAttributesName($relativePath, ZipArchive::OPSYS_UNIX, $perms << 16);
                }
            }
            if ($zip->close()) {
                if (!file_exists($target)) {
                    // create minimal valid ZIP file (Empty Central Directory + End of Central Directory record)
                    $eocd = pack(
                        'VvvvvVVv',
                        0x06054b50,  // End of central directory signature
                        0,           // Number of this disk
                        0,           // Disk where central directory starts
                        0,           // Number of central directory records on this disk
                        0,           // Total number of central directory records
                        0,           // Size of central directory (bytes)
                        0,           // Offset of start of central directory
                        0            // Comment length
                    );
                    file_put_contents($target, $eocd);
                }

                return $target;
            }
        }
        $message = sprintf(
            "Could not create archive '%s' from '%s': %s",
            $target,
            $sources,
            $zip->getStatusString()
        );
        throw new \RuntimeException($message);
    }

    /**
     * @inheritDoc
     */
    public function supports(string $format, ?string $sourceType): bool
    {
        return isset(static::$formats[$format]) && $this->compressionAvailable();
    }

    private function compressionAvailable(): bool
    {
        return class_exists('ZipArchive');
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Archiver;

use FilterIterator;
use Iterator;
use PharData;
use SplFileInfo;

/**
 * @phpstan-extends FilterIterator<string, SplFileInfo, Iterator<string, SplFileInfo>>
 */
class ArchivableFilesFilter extends FilterIterator
{
    /** @var string[] */
    private $dirs = [];

    /**
     * @return bool true if the current element is acceptable, otherwise false.
     */
    public function accept(): bool
    {
        $file = $this->getInnerIterator()->current();
        if ($file->isDir()) {
            $this->dirs[] = (string) $file;

            return false;
        }

        return true;
    }

    public function addEmptyDir(PharData $phar, string $sources): void
    {
        foreach ($this->dirs as $filepath) {
            $localname = str_replace($sources . "/", '', $filepath);
            $phar->addEmptyDir($localname);
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class RootAliasPackage extends CompleteAliasPackage implements RootPackageInterface
{
    /** @var RootPackage */
    protected $aliasOf;

    /**
     * All descendants' constructors should call this parent constructor
     *
     * @param RootPackage $aliasOf       The package this package is an alias of
     * @param string      $version       The version the alias must report
     * @param string      $prettyVersion The alias's non-normalized version
     */
    public function __construct(RootPackage $aliasOf, string $version, string $prettyVersion)
    {
        parent::__construct($aliasOf, $version, $prettyVersion);
    }

    /**
     * @return RootPackage
     */
    public function getAliasOf()
    {
        return $this->aliasOf;
    }

    /**
     * @inheritDoc
     */
    public function getAliases(): array
    {
        return $this->aliasOf->getAliases();
    }

    /**
     * @inheritDoc
     */
    public function getMinimumStability(): string
    {
        return $this->aliasOf->getMinimumStability();
    }

    /**
     * @inheritDoc
     */
    public function getStabilityFlags(): array
    {
        return $this->aliasOf->getStabilityFlags();
    }

    /**
     * @inheritDoc
     */
    public function getReferences(): array
    {
        return $this->aliasOf->getReferences();
    }

    /**
     * @inheritDoc
     */
    public function getPreferStable(): bool
    {
        return $this->aliasOf->getPreferStable();
    }

    /**
     * @inheritDoc
     */
    public function getConfig(): array
    {
        return $this->aliasOf->getConfig();
    }

    /**
     * @inheritDoc
     */
    public function setRequires(array $requires): void
    {
        $this->requires = $this->replaceSelfVersionDependencies($requires, Link::TYPE_REQUIRE);

        $this->aliasOf->setRequires($requires);
    }

    /**
     * @inheritDoc
     */
    public function setDevRequires(array $devRequires): void
    {
        $this->devRequires = $this->replaceSelfVersionDependencies($devRequires, Link::TYPE_DEV_REQUIRE);

        $this->aliasOf->setDevRequires($devRequires);
    }

    /**
     * @inheritDoc
     */
    public function setConflicts(array $conflicts): void
    {
        $this->conflicts = $this->replaceSelfVersionDependencies($conflicts, Link::TYPE_CONFLICT);
        $this->aliasOf->setConflicts($conflicts);
    }

    /**
     * @inheritDoc
     */
    public function setProvides(array $provides): void
    {
        $this->provides = $this->replaceSelfVersionDependencies($provides, Link::TYPE_PROVIDE);
        $this->aliasOf->setProvides($provides);
    }

    /**
     * @inheritDoc
     */
    public function setReplaces(array $replaces): void
    {
        $this->replaces = $this->replaceSelfVersionDependencies($replaces, Link::TYPE_REPLACE);
        $this->aliasOf->setReplaces($replaces);
    }

    /**
     * @inheritDoc
     */
    public function setAutoload(array $autoload): void
    {
        $this->aliasOf->setAutoload($autoload);
    }

    /**
     * @inheritDoc
     */
    public function setDevAutoload(array $devAutoload): void
    {
        $this->aliasOf->setDevAutoload($devAutoload);
    }

    /**
     * @inheritDoc
     */
    public function setStabilityFlags(array $stabilityFlags): void
    {
        $this->aliasOf->setStabilityFlags($stabilityFlags);
    }

    /**
     * @inheritDoc
     */
    public function setMinimumStability(string $minimumStability): void
    {
        $this->aliasOf->setMinimumStability($minimumStability);
    }

    /**
     * @inheritDoc
     */
    public function setPreferStable(bool $preferStable): void
    {
        $this->aliasOf->setPreferStable($preferStable);
    }

    /**
     * @inheritDoc
     */
    public function setConfig(array $config): void
    {
        $this->aliasOf->setConfig($config);
    }

    /**
     * @inheritDoc
     */
    public function setReferences(array $references): void
    {
        $this->aliasOf->setReferences($references);
    }

    /**
     * @inheritDoc
     */
    public function setAliases(array $aliases): void
    {
        $this->aliasOf->setAliases($aliases);
    }

    /**
     * @inheritDoc
     */
    public function setSuggests(array $suggests): void
    {
        $this->aliasOf->setSuggests($suggests);
    }

    /**
     * @inheritDoc
     */
    public function setExtra(array $extra): void
    {
        $this->aliasOf->setExtra($extra);
    }

    public function __clone()
    {
        parent::__clone();
        $this->aliasOf = clone $this->aliasOf;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Dumper;

use Composer\Package\BasePackage;
use Composer\Package\PackageInterface;
use Composer\Package\CompletePackageInterface;
use Composer\Package\RootPackageInterface;

/**
 * @author Konstantin Kudryashiv <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ArrayDumper
{
    /**
     * @return array<string, mixed>
     */
    public function dump(PackageInterface $package): array
    {
        $keys = [
            'binaries' => 'bin',
            'type',
            'extra',
            'installationSource' => 'installation-source',
            'autoload',
            'devAutoload' => 'autoload-dev',
            'notificationUrl' => 'notification-url',
            'includePaths' => 'include-path',
            'phpExt' => 'php-ext',
        ];

        $data = [];
        $data['name'] = $package->getPrettyName();
        $data['version'] = $package->getPrettyVersion();
        $data['version_normalized'] = $package->getVersion();

        if ($package->getTargetDir() !== null) {
            $data['target-dir'] = $package->getTargetDir();
        }

        if ($package->getSourceType() !== null) {
            $data['source']['type'] = $package->getSourceType();
            $data['source']['url'] = $package->getSourceUrl();
            if (null !== ($value = $package->getSourceReference())) {
                $data['source']['reference'] = $value;
            }
            if ($mirrors = $package->getSourceMirrors()) {
                $data['source']['mirrors'] = $mirrors;
            }
        }

        if ($package->getDistType() !== null) {
            $data['dist']['type'] = $package->getDistType();
            $data['dist']['url'] = $package->getDistUrl();
            if (null !== ($value = $package->getDistReference())) {
                $data['dist']['reference'] = $value;
            }
            if (null !== ($value = $package->getDistSha1Checksum())) {
                $data['dist']['shasum'] = $value;
            }
            if ($mirrors = $package->getDistMirrors()) {
                $data['dist']['mirrors'] = $mirrors;
            }
        }

        foreach (BasePackage::$supportedLinkTypes as $type => $opts) {
            $links = $package->{'get'.ucfirst($opts['method'])}();
            if (\count($links) === 0) {
                continue;
            }
            foreach ($links as $link) {
                $data[$type][$link->getTarget()] = $link->getPrettyConstraint();
            }
            ksort($data[$type]);
        }

        $packages = $package->getSuggests();
        if (\count($packages) > 0) {
            ksort($packages);
            $data['suggest'] = $packages;
        }

        if ($package->getReleaseDate() instanceof \DateTimeInterface) {
            $data['time'] = $package->getReleaseDate()->format(DATE_RFC3339);
        }

        if ($package->isDefaultBranch()) {
            $data['default-branch'] = true;
        }

        $data = $this->dumpValues($package, $keys, $data);

        if ($package instanceof CompletePackageInterface) {
            if ($package->getArchiveName()) {
                $data['archive']['name'] = $package->getArchiveName();
            }
            if ($package->getArchiveExcludes()) {
                $data['archive']['exclude'] = $package->getArchiveExcludes();
            }

            $keys = [
                'scripts',
                'license',
                'authors',
                'description',
                'homepage',
                'keywords',
                'repositories',
                'support',
                'funding',
            ];

            $data = $this->dumpValues($package, $keys, $data);

            if (isset($data['keywords']) && \is_array($data['keywords'])) {
                sort($data['keywords']);
            }

            if ($package->isAbandoned()) {
                $data['abandoned'] = $package->getReplacementPackage() ?: true;
            }
        }

        if ($package instanceof RootPackageInterface) {
            $minimumStability = $package->getMinimumStability();
            if ($minimumStability !== '') {
                $data['minimum-stability'] = $minimumStability;
            }
        }

        if (\count($package->getTransportOptions()) > 0) {
            $data['transport-options'] = $package->getTransportOptions();
        }

        return $data;
    }

    /**
     * @param array<int|string, string> $keys
     * @param array<string, mixed>      $data
     *
     * @return array<string, mixed>
     */
    private function dumpValues(PackageInterface $package, array $keys, array $data): array
    {
        foreach ($keys as $method => $key) {
            if (is_numeric($method)) {
                $method = $key;
            }

            $getter = 'get'.ucfirst($method);
            $value = $package->{$getter}();

            if (null !== $value && !(\is_array($value) && 0 === \count($value))) {
                $data[$key] = $value;
            }
        }

        return $data;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Comparer;

use Composer\Util\Platform;

/**
 * class Comparer
 *
 * @author Hector Prats <hectorpratsortega@gmail.com>
 */
class Comparer
{
    /** @var string Source directory */
    private $source;
    /** @var string Target directory */
    private $update;
    /** @var array{changed?: string[], removed?: string[], added?: string[]} */
    private $changed;

    public function setSource(string $source): void
    {
        $this->source = $source;
    }

    public function setUpdate(string $update): void
    {
        $this->update = $update;
    }

    /**
     * @return array{changed?: string[], removed?: string[], added?: string[]}|false false if no change
     */
    public function getChanged(bool $explicated = false)
    {
        $changed = $this->changed;
        if (!count($changed)) {
            return false;
        }
        if ($explicated) {
            foreach ($changed as $sectionKey => $itemSection) {
                foreach ($itemSection as $itemKey => $item) {
                    $changed[$sectionKey][$itemKey] = $item.' ('.$sectionKey.')';
                }
            }
        }

        return $changed;
    }

    /**
     * @return string empty string if no changes
     */
    public function getChangedAsString(bool $toString = false, bool $explicated = false): string
    {
        $changed = $this->getChanged($explicated);
        if (false === $changed) {
            return '';
        }

        $strings = [];
        foreach ($changed as $sectionKey => $itemSection) {
            foreach ($itemSection as $itemKey => $item) {
                $strings[] = $item."\r\n";
            }
        }

        return trim(implode("\r\n", $strings));
    }

    public function doCompare(): void
    {
        $source = [];
        $destination = [];
        $this->changed = [];
        $currentDirectory = Platform::getCwd();
        chdir($this->source);
        $source = $this->doTree('.', $source);
        if (!is_array($source)) {
            return;
        }
        chdir($currentDirectory);
        chdir($this->update);
        $destination = $this->doTree('.', $destination);
        if (!is_array($destination)) {
            exit;
        }
        chdir($currentDirectory);
        foreach ($source as $dir => $value) {
            foreach ($value as $file => $hash) {
                if (isset($destination[$dir][$file])) {
                    if ($hash !== $destination[$dir][$file]) {
                        $this->changed['changed'][] = $dir.'/'.$file;
                    }
                } else {
                    $this->changed['removed'][] = $dir.'/'.$file;
                }
            }
        }
        foreach ($destination as $dir => $value) {
            foreach ($value as $file => $hash) {
                if (!isset($source[$dir][$file])) {
                    $this->changed['added'][] = $dir.'/'.$file;
                }
            }
        }
    }

    /**
     * @param mixed[] $array
     *
     * @return array<string, array<string, string|false>>|false
     */
    private function doTree(string $dir, array &$array)
    {
        if ($dh = opendir($dir)) {
            while ($file = readdir($dh)) {
                if ($file !== '.' && $file !== '..') {
                    if (is_link($dir.'/'.$file)) {
                        $array[$dir][$file] = readlink($dir.'/'.$file);
                    } elseif (is_dir($dir.'/'.$file)) {
                        if (!count($array)) {
                            $array[0] = 'Temp';
                        }
                        if (!$this->doTree($dir.'/'.$file, $array)) {
                            return false;
                        }
                    } elseif (is_file($dir.'/'.$file) && filesize($dir.'/'.$file)) {
                        $array[$dir][$file] = hash_file(\PHP_VERSION_ID > 80100 ? 'xxh3' : 'sha1', $dir.'/'.$file);
                    }
                }
            }
            if (count($array) > 1 && isset($array['0'])) {
                unset($array['0']);
            }

            return $array;
        }

        return false;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package;

/**
 * Defines additional fields that are only needed for the root package
 *
 * PackageInterface & derivatives are considered internal, you may use them in type hints but extending/implementing them is not recommended and not supported. Things may change without notice.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 *
 * @phpstan-import-type AutoloadRules from PackageInterface
 * @phpstan-import-type DevAutoloadRules from PackageInterface
 */
interface RootPackageInterface extends CompletePackageInterface
{
    /**
     * Returns a set of package names and their aliases
     *
     * @return list<array{package: string, version: string, alias: string, alias_normalized: string}>
     */
    public function getAliases(): array;

    /**
     * Returns the minimum stability of the package
     *
     * @return key-of<BasePackage::STABILITIES>
     */
    public function getMinimumStability(): string;

    /**
     * Returns the stability flags to apply to dependencies
     *
     * array('foo/bar' => 'dev')
     *
     * @return array<string, BasePackage::STABILITY_*>
     */
    public function getStabilityFlags(): array;

    /**
     * Returns a set of package names and source references that must be enforced on them
     *
     * array('foo/bar' => 'abcd1234')
     *
     * @return array<string, string>
     */
    public function getReferences(): array;

    /**
     * Returns true if the root package prefers picking stable packages over unstable ones
     */
    public function getPreferStable(): bool;

    /**
     * Returns the root package's configuration
     *
     * @return mixed[]
     */
    public function getConfig(): array;

    /**
     * Set the required packages
     *
     * @param Link[] $requires A set of package links
     */
    public function setRequires(array $requires): void;

    /**
     * Set the recommended packages
     *
     * @param Link[] $devRequires A set of package links
     */
    public function setDevRequires(array $devRequires): void;

    /**
     * Set the conflicting packages
     *
     * @param Link[] $conflicts A set of package links
     */
    public function setConflicts(array $conflicts): void;

    /**
     * Set the provided virtual packages
     *
     * @param Link[] $provides A set of package links
     */
    public function setProvides(array $provides): void;

    /**
     * Set the packages this one replaces
     *
     * @param Link[] $replaces A set of package links
     */
    public function setReplaces(array $replaces): void;

    /**
     * Set the autoload mapping
     *
     * @param array $autoload Mapping of autoloading rules
     * @phpstan-param AutoloadRules $autoload
     */
    public function setAutoload(array $autoload): void;

    /**
     * Set the dev autoload mapping
     *
     * @param array $devAutoload Mapping of dev autoloading rules
     * @phpstan-param DevAutoloadRules $devAutoload
     */
    public function setDevAutoload(array $devAutoload): void;

    /**
     * Set the stabilityFlags
     *
     * @phpstan-param array<string, BasePackage::STABILITY_*> $stabilityFlags
     */
    public function setStabilityFlags(array $stabilityFlags): void;

    /**
     * Set the minimumStability
     *
     * @phpstan-param key-of<BasePackage::STABILITIES> $minimumStability
     */
    public function setMinimumStability(string $minimumStability): void;

    /**
     * Set the preferStable
     */
    public function setPreferStable(bool $preferStable): void;

    /**
     * Set the config
     *
     * @param mixed[] $config
     */
    public function setConfig(array $config): void;

    /**
     * Set the references
     *
     * @param array<string, string> $references
     */
    public function setReferences(array $references): void;

    /**
     * Set the aliases
     *
     * @param list<array{package: string, version: string, alias: string, alias_normalized: string}> $aliases
     */
    public function setAliases(array $aliases): void;

    /**
     * Set the suggested packages
     *
     * @param array<string, string> $suggests A set of package names/comments
     */
    public function setSuggests(array $suggests): void;

    /**
     * @param mixed[] $extra
     */
    public function setExtra(array $extra): void;
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Loader;

use Composer\Package\BasePackage;
use Composer\Pcre\Preg;
use Composer\Semver\Constraint\Constraint;
use Composer\Package\Version\VersionParser;
use Composer\Repository\PlatformRepository;
use Composer\Semver\Constraint\MatchNoneConstraint;
use Composer\Semver\Intervals;
use Composer\Spdx\SpdxLicenses;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ValidatingArrayLoader implements LoaderInterface
{
    public const CHECK_ALL = 3;
    public const CHECK_UNBOUND_CONSTRAINTS = 1;
    public const CHECK_STRICT_CONSTRAINTS = 2;

    /** @var LoaderInterface */
    private $loader;
    /** @var VersionParser */
    private $versionParser;
    /** @var list<string> */
    private $errors;
    /** @var list<string> */
    private $warnings;
    /** @var mixed[] */
    private $config;
    /** @var int One or more of self::CHECK_* constants */
    private $flags;

    /**
     * @param true $strictName
     */
    public function __construct(LoaderInterface $loader, bool $strictName = true, ?VersionParser $parser = null, int $flags = 0)
    {
        $this->loader = $loader;
        $this->versionParser = $parser ?? new VersionParser();
        $this->flags = $flags;

        if ($strictName !== true) { // @phpstan-ignore-line
            trigger_error('$strictName must be set to true in ValidatingArrayLoader\'s constructor as of 2.2, and it will be removed in 3.0', E_USER_DEPRECATED);
        }
    }

    /**
     * @inheritDoc
     */
    public function load(array $config, string $class = 'Composer\Package\CompletePackage'): BasePackage
    {
        $this->errors = [];
        $this->warnings = [];
        $this->config = $config;

        $this->validateString('name', true);
        if (isset($config['name']) && null !== ($err = self::hasPackageNamingError($config['name']))) {
            $this->errors[] = 'name : '.$err;
        }

        if (isset($this->config['version'])) {
            if (!is_scalar($this->config['version'])) {
                $this->validateString('version');
            } else {
                if (!is_string($this->config['version'])) {
                    $this->config['version'] = (string) $this->config['version'];
                }
                try {
                    $this->versionParser->normalize($this->config['version']);
                } catch (\Exception $e) {
                    $this->errors[] = 'version : invalid value ('.$this->config['version'].'): '.$e->getMessage();
                    unset($this->config['version']);
                }
            }
        }

        if (isset($this->config['config']['platform'])) {
            foreach ((array) $this->config['config']['platform'] as $key => $platform) {
                if (false === $platform) {
                    continue;
                }
                if (!is_string($platform)) {
                    $this->errors[] = 'config.platform.' . $key . ' : invalid value ('.get_debug_type($platform).' '.var_export($platform, true).'): expected string or false';
                    continue;
                }
                try {
                    $this->versionParser->normalize($platform);
                } catch (\Exception $e) {
                    $this->errors[] = 'config.platform.' . $key . ' : invalid value ('.$platform.'): '.$e->getMessage();
                }
            }
        }

        $this->validateRegex('type', '[A-Za-z0-9-]+');
        $this->validateString('target-dir');
        $this->validateArray('extra');

        if (isset($this->config['bin'])) {
            if (is_string($this->config['bin'])) {
                $this->validateString('bin');
            } else {
                $this->validateFlatArray('bin');
            }
        }

        $this->validateArray('scripts'); // TODO validate event names & listener syntax
        $this->validateString('description');
        $this->validateUrl('homepage');
        $this->validateFlatArray('keywords', '[\p{N}\p{L} ._-]+');

        $releaseDate = null;
        $this->validateString('time');
        if (isset($this->config['time'])) {
            try {
                $releaseDate = new \DateTime($this->config['time'], new \DateTimeZone('UTC'));
            } catch (\Exception $e) {
                $this->errors[] = 'time : invalid value ('.$this->config['time'].'): '.$e->getMessage();
                unset($this->config['time']);
            }
        }

        if (isset($this->config['license'])) {
            // validate main data types
            if (is_array($this->config['license']) || is_string($this->config['license'])) {
                $licenses = (array) $this->config['license'];

                foreach ($licenses as $index => $license) {
                    if (!is_string($license)) {
                        $this->warnings[] = sprintf(
                            'License %s should be a string.',
                            json_encode($license)
                        );
                        unset($licenses[$index]);
                    }
                }

                // check for license validity on newly updated branches/tags
                if (null === $releaseDate || $releaseDate->getTimestamp() >= strtotime('-8days')) {
                    $licenseValidator = new SpdxLicenses();
                    foreach ($licenses as $license) {
                        // replace proprietary by MIT for validation purposes since it's not a valid SPDX identifier, but is accepted by composer
                        if ('proprietary' === $license) {
                            continue;
                        }
                        $licenseToValidate = str_replace('proprietary', 'MIT', $license);
                        if (!$licenseValidator->validate($licenseToValidate)) {
                            if ($licenseValidator->validate(trim($licenseToValidate))) {
                                $this->warnings[] = sprintf(
                                    'License %s must not contain extra spaces, make sure to trim it.',
                                    json_encode($license)
                                );
                            } else {
                                $this->warnings[] = sprintf(
                                    'License %s is not a valid SPDX license identifier, see https://spdx.org/licenses/ if you use an open license.' . PHP_EOL .
                                    'If the software is closed-source, you may use "proprietary" as license.',
                                    json_encode($license)
                                );
                            }
                        }
                    }
                }

                $this->config['license'] = array_values($licenses);
            } else {
                $this->warnings[] = sprintf(
                    'License must be a string or array of strings, got %s.',
                    json_encode($this->config['license'])
                );
                unset($this->config['license']);
            }
        }

        if ($this->validateArray('authors')) {
            foreach ($this->config['authors'] as $key => $author) {
                if (!is_array($author)) {
                    $this->errors[] = 'authors.'.$key.' : should be an array, '.get_debug_type($author).' given';
                    unset($this->config['authors'][$key]);
                    continue;
                }
                foreach (['homepage', 'email', 'name', 'role'] as $authorData) {
                    if (isset($author[$authorData]) && !is_string($author[$authorData])) {
                        $this->errors[] = 'authors.'.$key.'.'.$authorData.' : invalid value, must be a string';
                        unset($this->config['authors'][$key][$authorData]);
                    }
                }
                if (isset($author['homepage']) && !$this->filterUrl($author['homepage'])) {
                    $this->warnings[] = 'authors.'.$key.'.homepage : invalid value ('.$author['homepage'].'), must be an http/https URL';
                    unset($this->config['authors'][$key]['homepage']);
                }
                if (isset($author['email']) && false === filter_var($author['email'], FILTER_VALIDATE_EMAIL)) {
                    $this->warnings[] = 'authors.'.$key.'.email : invalid value ('.$author['email'].'), must be a valid email address';
                    unset($this->config['authors'][$key]['email']);
                }
                if (\count($this->config['authors'][$key]) === 0) {
                    unset($this->config['authors'][$key]);
                }
            }
            if (\count($this->config['authors']) === 0) {
                unset($this->config['authors']);
            }
        }

        if ($this->validateArray('support') && !empty($this->config['support'])) {
            foreach (['issues', 'forum', 'wiki', 'source', 'email', 'irc', 'docs', 'rss', 'chat', 'security'] as $key) {
                if (isset($this->config['support'][$key]) && !is_string($this->config['support'][$key])) {
                    $this->errors[] = 'support.'.$key.' : invalid value, must be a string';
                    unset($this->config['support'][$key]);
                }
            }

            if (isset($this->config['support']['email']) && !filter_var($this->config['support']['email'], FILTER_VALIDATE_EMAIL)) {
                $this->warnings[] = 'support.email : invalid value ('.$this->config['support']['email'].'), must be a valid email address';
                unset($this->config['support']['email']);
            }

            if (isset($this->config['support']['irc']) && !$this->filterUrl($this->config['support']['irc'], ['irc', 'ircs'])) {
                $this->warnings[] = 'support.irc : invalid value ('.$this->config['support']['irc'].'), must be a irc://<server>/<channel> or ircs:// URL';
                unset($this->config['support']['irc']);
            }

            foreach (['issues', 'forum', 'wiki', 'source', 'docs', 'chat', 'security'] as $key) {
                if (isset($this->config['support'][$key]) && !$this->filterUrl($this->config['support'][$key])) {
                    $this->warnings[] = 'support.'.$key.' : invalid value ('.$this->config['support'][$key].'), must be an http/https URL';
                    unset($this->config['support'][$key]);
                }
            }
            if (empty($this->config['support'])) {
                unset($this->config['support']);
            }
        }

        if ($this->validateArray('funding') && !empty($this->config['funding'])) {
            foreach ($this->config['funding'] as $key => $fundingOption) {
                if (!is_array($fundingOption)) {
                    $this->errors[] = 'funding.'.$key.' : should be an array, '.get_debug_type($fundingOption).' given';
                    unset($this->config['funding'][$key]);
                    continue;
                }
                foreach (['type', 'url'] as $fundingData) {
                    if (isset($fundingOption[$fundingData]) && !is_string($fundingOption[$fundingData])) {
                        $this->errors[] = 'funding.'.$key.'.'.$fundingData.' : invalid value, must be a string';
                        unset($this->config['funding'][$key][$fundingData]);
                    }
                }
                if (isset($fundingOption['url']) && !$this->filterUrl($fundingOption['url'])) {
                    $this->warnings[] = 'funding.'.$key.'.url : invalid value ('.$fundingOption['url'].'), must be an http/https URL';
                    unset($this->config['funding'][$key]['url']);
                }
                if (empty($this->config['funding'][$key])) {
                    unset($this->config['funding'][$key]);
                }
            }
            if (empty($this->config['funding'])) {
                unset($this->config['funding']);
            }
        }

        if (isset($this->config['php-ext']) && $this->validateArray('php-ext')) {
            if (!in_array($this->config['type'] ?? '', ['php-ext', 'php-ext-zend'], true)) {
                $this->errors[] = 'php-ext can only be set by packages of type "php-ext" or "php-ext-zend" which must be C extensions';
                unset($this->config['php-ext']);
            }

            $phpExt = &$this->config['php-ext'];

            if (isset($phpExt['extension-name']) && !is_string($phpExt['extension-name'])) {
                $this->errors[] = 'php-ext.extension-name : should be a string, '.get_debug_type($phpExt['extension-name']).' given';
                unset($phpExt['extension-name']);
            }

            if (isset($phpExt['priority']) && !is_int($phpExt['priority'])) {
                $this->errors[] = 'php-ext.priority : should be an integer, '.get_debug_type($phpExt['priority']).' given';
                unset($phpExt['priority']);
            }

            if (isset($phpExt['support-zts']) && !is_bool($phpExt['support-zts'])) {
                $this->errors[] = 'php-ext.support-zts : should be a boolean, '.get_debug_type($phpExt['support-zts']).' given';
                unset($phpExt['support-zts']);
            }

            if (isset($phpExt['support-nts']) && !is_bool($phpExt['support-nts'])) {
                $this->errors[] = 'php-ext.support-nts : should be a boolean, '.get_debug_type($phpExt['support-nts']).' given';
                unset($phpExt['support-nts']);
            }

            if (isset($phpExt['build-path']) && !is_string($phpExt['build-path']) && !is_null($phpExt['build-path'])) {
                $this->errors[] = 'php-ext.build-path : should be a string or null, '.get_debug_type($phpExt['build-path']).' given';
                unset($phpExt['build-path']);
            }

            if (isset($phpExt['download-url-method'])) {
                if (!is_array($phpExt['download-url-method']) && !is_string($phpExt['download-url-method'])) {
                    $this->errors[] = 'php-ext.download-url-method : should be an array or a string, '.get_debug_type($phpExt['download-url-method']).' given';
                    unset($phpExt['download-url-method']);
                } else {
                    $validDownloadUrlMethods = ['composer-default', 'pre-packaged-source', 'pre-packaged-binary'];
                    $definedDownloadUrlMethods = is_array($phpExt['download-url-method']) ? $phpExt['download-url-method'] : [$phpExt['download-url-method']];

                    if ([] === $definedDownloadUrlMethods) {
                        $this->errors[] = 'php-ext.download-url-method : must contain at least one element';
                        unset($phpExt['download-url-method']);
                    } else {
                        foreach ($definedDownloadUrlMethods as $key => $downloadUrlMethod) {
                            if (!is_string($downloadUrlMethod)) {
                                $this->errors[] = 'php-ext.download-url-method.'.$key.' : should be a string, '.get_debug_type($downloadUrlMethod).' given';
                                unset($phpExt['download-url-method']);
                            } elseif (!in_array($downloadUrlMethod, $validDownloadUrlMethods, true)) {
                                $this->errors[] = 'php-ext.download-url-method.'.$key.' : invalid value ('.$downloadUrlMethod.'), must be one of ' . implode(', ', $validDownloadUrlMethods);
                                unset($phpExt['download-url-method']);
                            }
                        }
                    }
                }
            }

            if (isset($phpExt['os-families']) && isset($phpExt['os-families-exclude'])) {
                $this->errors[] = 'php-ext : os-families and os-families-exclude cannot both be specified';
                unset($phpExt['os-families'], $phpExt['os-families-exclude']);
            } else {
                $validOsFamilies = ['windows', 'bsd', 'darwin', 'solaris', 'linux', 'unknown'];

                foreach (['os-families', 'os-families-exclude'] as $fieldName) {
                    if (isset($phpExt[$fieldName])) {
                        if (!is_array($phpExt[$fieldName])) {
                            $this->errors[] = 'php-ext.'.$fieldName.' : should be an array, '.get_debug_type($phpExt[$fieldName]).' given';
                            unset($phpExt[$fieldName]);
                        } elseif ([] === $phpExt[$fieldName]) {
                            $this->errors[] = 'php-ext.'.$fieldName.' : must contain at least one element';
                            unset($phpExt[$fieldName]);
                        } else {
                            foreach ($phpExt[$fieldName] as $key => $osFamily) {
                                if (!is_string($osFamily)) {
                                    $this->errors[] = 'php-ext.'.$fieldName.'.'.$key.' : should be a string, '.get_debug_type($osFamily).' given';
                                    unset($phpExt[$fieldName][$key]);
                                } elseif (!in_array($osFamily, $validOsFamilies, true)) {
                                    $this->errors[] = 'php-ext.'.$fieldName.'.'.$key.' : invalid value ('.$osFamily.'), must be one of '.implode(', ', $validOsFamilies);
                                    unset($phpExt[$fieldName][$key]);
                                }
                            }
                            if ([] === $phpExt[$fieldName]) {
                                unset($phpExt[$fieldName]);
                            }
                        }
                    }
                }
            }

            if (isset($phpExt['configure-options'])) {
                if (!is_array($phpExt['configure-options'])) {
                    $this->errors[] = 'php-ext.configure-options : should be an array, '.get_debug_type($phpExt['configure-options']).' given';
                    unset($phpExt['configure-options']);
                } else {
                    foreach ($phpExt['configure-options'] as $key => $option) {
                        if (!is_array($option)) {
                            $this->errors[] = 'php-ext.configure-options.'.$key.' : should be an array, '.get_debug_type($option).' given';
                            unset($phpExt['configure-options'][$key]);
                            continue;
                        }

                        if (!isset($option['name'])) {
                            $this->errors[] = 'php-ext.configure-options.'.$key.'.name : must be present';
                            unset($phpExt['configure-options'][$key]);
                            continue;
                        }

                        if (!is_string($option['name'])) {
                            $this->errors[] = 'php-ext.configure-options.'.$key.'.name : should be a string, '.get_debug_type($option['name']).' given';
                            unset($phpExt['configure-options'][$key]);
                            continue;
                        }

                        if (isset($option['needs-value']) && !is_bool($option['needs-value'])) {
                            $this->errors[] = 'php-ext.configure-options.'.$key.'.needs-value : should be a boolean, '.get_debug_type($option['needs-value']).' given';
                            unset($phpExt['configure-options'][$key]['needs-value']);
                        }

                        if (isset($option['description']) && !is_string($option['description'])) {
                            $this->errors[] = 'php-ext.configure-options.'.$key.'.description : should be a string, '.get_debug_type($option['description']).' given';
                            unset($phpExt['configure-options'][$key]['description']);
                        }
                    }

                    if ([] === $phpExt['configure-options']) {
                        unset($phpExt['configure-options']);
                    }
                }
            }

            // If php-ext is now empty, unset it
            if ([] === $phpExt) {
                unset($this->config['php-ext']);
            }

            unset($phpExt);
        }

        $unboundConstraint = new Constraint('=', '10000000-dev');

        foreach (array_keys(BasePackage::$supportedLinkTypes) as $linkType) {
            if ($this->validateArray($linkType) && isset($this->config[$linkType])) {
                foreach ($this->config[$linkType] as $package => $constraint) {
                    $package = (string) $package;
                    if (isset($this->config['name']) && 0 === strcasecmp($package, $this->config['name'])) {
                        $this->errors[] = $linkType.'.'.$package.' : a package cannot set a '.$linkType.' on itself';
                        unset($this->config[$linkType][$package]);
                        continue;
                    }
                    if ($err = self::hasPackageNamingError($package, true)) {
                        $this->warnings[] = $linkType.'.'.$err;
                    } elseif (!Preg::isMatch('{^[A-Za-z0-9_./-]+$}', $package)) {
                        $this->errors[] = $linkType.'.'.$package.' : invalid key, package names must be strings containing only [A-Za-z0-9_./-]';
                    }
                    if (!is_string($constraint)) {
                        $this->errors[] = $linkType.'.'.$package.' : invalid value, must be a string containing a version constraint';
                        unset($this->config[$linkType][$package]);
                    } elseif ('self.version' !== $constraint) {
                        try {
                            $linkConstraint = $this->versionParser->parseConstraints($constraint);
                        } catch (\Exception $e) {
                            $this->errors[] = $linkType.'.'.$package.' : invalid version constraint ('.$e->getMessage().')';
                            unset($this->config[$linkType][$package]);
                            continue;
                        }

                        // check requires for unbound constraints on non-platform packages
                        if (
                            ($this->flags & self::CHECK_UNBOUND_CONSTRAINTS)
                            && 'require' === $linkType
                            && $linkConstraint->matches($unboundConstraint)
                            && !PlatformRepository::isPlatformPackage($package)
                        ) {
                            $this->warnings[] = $linkType.'.'.$package.' : unbound version constraints ('.$constraint.') should be avoided';
                        } elseif (
                            // check requires for exact constraints
                            ($this->flags & self::CHECK_STRICT_CONSTRAINTS)
                            && 'require' === $linkType
                            && $linkConstraint instanceof Constraint && in_array($linkConstraint->getOperator(), ['==', '='], true)
                            && (new Constraint('>=', '1.0.0.0-dev'))->matches($linkConstraint)
                        ) {
                            $this->warnings[] = $linkType.'.'.$package.' : exact version constraints ('.$constraint.') should be avoided if the package follows semantic versioning';
                        }

                        $compacted = Intervals::compactConstraint($linkConstraint);
                        if ($compacted instanceof MatchNoneConstraint) {
                            $this->warnings[] = $linkType.'.'.$package.' : this version constraint cannot possibly match anything ('.$constraint.')';
                        }
                    }

                    if ($linkType === 'conflict' && isset($this->config['replace']) && $keys = array_intersect_key($this->config['replace'], $this->config['conflict'])) {
                        $this->errors[] = $linkType.'.'.$package.' : you cannot conflict with a package that is also replaced, as replace already creates an implicit conflict rule';
                        unset($this->config[$linkType][$package]);
                    }
                }
            }
        }

        if ($this->validateArray('suggest') && isset($this->config['suggest'])) {
            foreach ($this->config['suggest'] as $package => $description) {
                if (!is_string($description)) {
                    $this->errors[] = 'suggest.'.$package.' : invalid value, must be a string describing why the package is suggested';
                    unset($this->config['suggest'][$package]);
                }
            }
        }

        if ($this->validateString('minimum-stability') && isset($this->config['minimum-stability'])) {
            if (!isset(BasePackage::STABILITIES[strtolower($this->config['minimum-stability'])]) && $this->config['minimum-stability'] !== 'RC') {
                $this->errors[] = 'minimum-stability : invalid value ('.$this->config['minimum-stability'].'), must be one of '.implode(', ', array_keys(BasePackage::STABILITIES));
                unset($this->config['minimum-stability']);
            }
        }

        if ($this->validateArray('autoload') && isset($this->config['autoload'])) {
            $types = ['psr-0', 'psr-4', 'classmap', 'files', 'exclude-from-classmap'];
            foreach ($this->config['autoload'] as $type => $typeConfig) {
                if (!in_array($type, $types)) {
                    $this->errors[] = 'autoload : invalid value ('.$type.'), must be one of '.implode(', ', $types);
                    unset($this->config['autoload'][$type]);
                }
                if ($type === 'psr-4') {
                    foreach ($typeConfig as $namespace => $dirs) {
                        if ($namespace !== '' && '\\' !== substr((string) $namespace, -1)) {
                            $this->errors[] = 'autoload.psr-4 : invalid value ('.$namespace.'), namespaces must end with a namespace separator, should be '.$namespace.'\\\\';
                        }
                    }
                }
            }
        }

        if (isset($this->config['autoload']['psr-4']) && isset($this->config['target-dir'])) {
            $this->errors[] = 'target-dir : this can not be used together with the autoload.psr-4 setting, remove target-dir to upgrade to psr-4';
            // Unset the psr-4 setting, since unsetting target-dir might
            // interfere with other settings.
            unset($this->config['autoload']['psr-4']);
        }

        foreach (['source', 'dist'] as $srcType) {
            if ($this->validateArray($srcType) && !empty($this->config[$srcType])) {
                if (!isset($this->config[$srcType]['type'])) {
                    $this->errors[] = $srcType . '.type : must be present';
                }
                if (!isset($this->config[$srcType]['url'])) {
                    $this->errors[] = $srcType . '.url : must be present';
                }
                if ($srcType === 'source' && !isset($this->config[$srcType]['reference'])) {
                    $this->errors[] = $srcType . '.reference : must be present';
                }
                if (isset($this->config[$srcType]['type']) && !is_string($this->config[$srcType]['type'])) {
                    $this->errors[] = $srcType . '.type : should be a string, '.get_debug_type($this->config[$srcType]['type']).' given';
                }
                if (isset($this->config[$srcType]['url']) && !is_string($this->config[$srcType]['url'])) {
                    $this->errors[] = $srcType . '.url : should be a string, '.get_debug_type($this->config[$srcType]['url']).' given';
                }
                if (isset($this->config[$srcType]['reference']) && !is_string($this->config[$srcType]['reference']) && !is_int($this->config[$srcType]['reference'])) {
                    $this->errors[] = $srcType . '.reference : should be a string or int, '.get_debug_type($this->config[$srcType]['reference']).' given';
                }
                if (isset($this->config[$srcType]['reference']) && Preg::isMatch('{^\s*-}', (string) $this->config[$srcType]['reference'])) {
                    $this->errors[] = $srcType . '.reference : must not start with a "-", "'.$this->config[$srcType]['reference'].'" given';
                }
                if (isset($this->config[$srcType]['url']) && Preg::isMatch('{^\s*-}', (string) $this->config[$srcType]['url'])) {
                    $this->errors[] = $srcType . '.url : must not start with a "-", "'.$this->config[$srcType]['url'].'" given';
                }
            }
        }

        // TODO validate repositories
        // TODO validate package repositories' packages using this recursively

        $this->validateFlatArray('include-path');
        $this->validateArray('transport-options');

        // branch alias validation
        if (isset($this->config['extra']['branch-alias'])) {
            if (!is_array($this->config['extra']['branch-alias'])) {
                $this->errors[] = 'extra.branch-alias : must be an array of versions => aliases';
            } else {
                foreach ($this->config['extra']['branch-alias'] as $sourceBranch => $targetBranch) {
                    if (!is_string($targetBranch)) {
                        $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.json_encode($targetBranch).') must be a string, "'.get_debug_type($targetBranch).'" received.';
                        unset($this->config['extra']['branch-alias'][$sourceBranch]);

                        continue;
                    }

                    // ensure it is an alias to a -dev package
                    if ('-dev' !== substr($targetBranch, -4)) {
                        $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') must end in -dev';
                        unset($this->config['extra']['branch-alias'][$sourceBranch]);

                        continue;
                    }

                    // normalize without -dev and ensure it's a numeric branch that is parseable
                    $validatedTargetBranch = $this->versionParser->normalizeBranch(substr($targetBranch, 0, -4));
                    if ('-dev' !== substr($validatedTargetBranch, -4)) {
                        $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') must be a parseable number like 2.0-dev';
                        unset($this->config['extra']['branch-alias'][$sourceBranch]);

                        continue;
                    }

                    // If using numeric aliases ensure the alias is a valid subversion
                    if (($sourcePrefix = $this->versionParser->parseNumericAliasPrefix($sourceBranch))
                        && ($targetPrefix = $this->versionParser->parseNumericAliasPrefix($targetBranch))
                        && (stripos($targetPrefix, $sourcePrefix) !== 0)
                    ) {
                        $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') is not a valid numeric alias for this version';
                        unset($this->config['extra']['branch-alias'][$sourceBranch]);
                    }
                }
            }
        }

        if ($this->errors) {
            throw new InvalidPackageException($this->errors, $this->warnings, $config);
        }

        $package = $this->loader->load($this->config, $class);
        $this->config = [];

        return $package;
    }

    /**
     * @return list<string>
     */
    public function getWarnings(): array
    {
        return $this->warnings;
    }

    /**
     * @return list<string>
     */
    public function getErrors(): array
    {
        return $this->errors;
    }

    public static function hasPackageNamingError(string $name, bool $isLink = false): ?string
    {
        if (PlatformRepository::isPlatformPackage($name)) {
            return null;
        }

        if (!Preg::isMatch('{^[a-z0-9](?:[_.-]?[a-z0-9]++)*+/[a-z0-9](?:(?:[_.]|-{1,2})?[a-z0-9]++)*+$}iD', $name)) {
            return $name.' is invalid, it should have a vendor name, a forward slash, and a package name. The vendor and package name can be words separated by -, . or _. The complete name should match "^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9](([_.]?|-{0,2})[a-z0-9]+)*$".';
        }

        $reservedNames = ['nul', 'con', 'prn', 'aux', 'com1', 'com2', 'com3', 'com4', 'com5', 'com6', 'com7', 'com8', 'com9', 'lpt1', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 'lpt6', 'lpt7', 'lpt8', 'lpt9'];
        $bits = explode('/', strtolower($name));
        if (in_array($bits[0], $reservedNames, true) || in_array($bits[1], $reservedNames, true)) {
            return $name.' is reserved, package and vendor names can not match any of: '.implode(', ', $reservedNames).'.';
        }

        if (Preg::isMatch('{\.json$}', $name)) {
            return $name.' is invalid, package names can not end in .json, consider renaming it or perhaps using a -json suffix instead.';
        }

        if (Preg::isMatch('{[A-Z]}', $name)) {
            if ($isLink) {
                return $name.' is invalid, it should not contain uppercase characters. Please use '.strtolower($name).' instead.';
            }

            $suggestName = Preg::replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '\\1\\3-\\2\\4', $name);
            $suggestName = strtolower($suggestName);

            return $name.' is invalid, it should not contain uppercase characters. We suggest using '.$suggestName.' instead.';
        }

        return null;
    }

    /**
     * @phpstan-param non-empty-string $property
     * @phpstan-param non-empty-string $regex
     */
    private function validateRegex(string $property, string $regex, bool $mandatory = false): bool
    {
        if (!$this->validateString($property, $mandatory)) {
            return false;
        }

        if (!Preg::isMatch('{^'.$regex.'$}u', $this->config[$property])) {
            $message = $property.' : invalid value ('.$this->config[$property].'), must match '.$regex;
            if ($mandatory) {
                $this->errors[] = $message;
            } else {
                $this->warnings[] = $message;
            }
            unset($this->config[$property]);

            return false;
        }

        return true;
    }

    /**
     * @phpstan-param non-empty-string $property
     */
    private function validateString(string $property, bool $mandatory = false): bool
    {
        if (isset($this->config[$property]) && !is_string($this->config[$property])) {
            $this->errors[] = $property.' : should be a string, '.get_debug_type($this->config[$property]).' given';
            unset($this->config[$property]);

            return false;
        }

        if (!isset($this->config[$property]) || trim($this->config[$property]) === '') {
            if ($mandatory) {
                $this->errors[] = $property.' : must be present';
            }
            unset($this->config[$property]);

            return false;
        }

        return true;
    }

    /**
     * @phpstan-param non-empty-string $property
     */
    private function validateArray(string $property, bool $mandatory = false): bool
    {
        if (isset($this->config[$property]) && !is_array($this->config[$property])) {
            $this->errors[] = $property.' : should be an array, '.get_debug_type($this->config[$property]).' given';
            unset($this->config[$property]);

            return false;
        }

        if (!isset($this->config[$property]) || !count($this->config[$property])) {
            if ($mandatory) {
                $this->errors[] = $property.' : must be present and contain at least one element';
            }
            unset($this->config[$property]);

            return false;
        }

        return true;
    }

    /**
     * @phpstan-param non-empty-string      $property
     * @phpstan-param non-empty-string|null $regex
     */
    private function validateFlatArray(string $property, ?string $regex = null, bool $mandatory = false): bool
    {
        if (!$this->validateArray($property, $mandatory)) {
            return false;
        }

        $pass = true;
        foreach ($this->config[$property] as $key => $value) {
            if (!is_string($value) && !is_numeric($value)) {
                $this->errors[] = $property.'.'.$key.' : must be a string or int, '.get_debug_type($value).' given';
                unset($this->config[$property][$key]);
                $pass = false;

                continue;
            }

            if ($regex && !Preg::isMatch('{^'.$regex.'$}u', (string) $value)) {
                $this->warnings[] = $property.'.'.$key.' : invalid value ('.$value.'), must match '.$regex;
                unset($this->config[$property][$key]);
                $pass = false;
            }
        }

        return $pass;
    }

    /**
     * @phpstan-param non-empty-string $property
     */
    private function validateUrl(string $property, bool $mandatory = false): bool
    {
        if (!$this->validateString($property, $mandatory)) {
            return false;
        }

        if (!$this->filterUrl($this->config[$property])) {
            $this->warnings[] = $property.' : invalid value ('.$this->config[$property].'), must be an http/https URL';
            unset($this->config[$property]);

            return false;
        }

        return true;
    }

    /**
     * @param mixed    $value
     * @param string[] $schemes
     */
    private function filterUrl($value, array $schemes = ['http', 'https']): bool
    {
        if ($value === '') {
            return true;
        }

        $bits = parse_url($value);
        if (empty($bits['scheme']) || empty($bits['host'])) {
            return false;
        }

        if (!in_array($bits['scheme'], $schemes, true)) {
            return false;
        }

        return true;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Loader;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class InvalidPackageException extends \Exception
{
    /** @var list<string> */
    private $errors;
    /** @var list<string> */
    private $warnings;
    /** @var mixed[] package config */
    private $data;

    /**
     * @param list<string> $errors
     * @param list<string> $warnings
     * @param mixed[]  $data
     */
    public function __construct(array $errors, array $warnings, array $data)
    {
        $this->errors = $errors;
        $this->warnings = $warnings;
        $this->data = $data;
        parent::__construct("Invalid package information: \n".implode("\n", array_merge($errors, $warnings)));
    }

    /**
     * @return mixed[]
     */
    public function getData(): array
    {
        return $this->data;
    }

    /**
     * @return list<string>
     */
    public function getErrors(): array
    {
        return $this->errors;
    }

    /**
     * @return list<string>
     */
    public function getWarnings(): array
    {
        return $this->warnings;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Loader;

use Composer\Package\CompletePackage;
use Composer\Package\CompleteAliasPackage;
use Composer\Package\RootAliasPackage;
use Composer\Package\RootPackage;
use Composer\Package\BasePackage;

/**
 * Defines a loader that takes an array to create package instances
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface LoaderInterface
{
    /**
     * Converts a package from an array to a real instance
     *
     * @param  mixed[] $config package data
     * @param  string  $class  FQCN to be instantiated
     *
     * @return CompletePackage|CompleteAliasPackage|RootPackage|RootAliasPackage
     *
     * @phpstan-param class-string<CompletePackage|RootPackage> $class
     */
    public function load(array $config, string $class = 'Composer\Package\CompletePackage'): BasePackage;
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Loader;

use Composer\Json\JsonFile;
use Composer\Package\BasePackage;
use Composer\Package\CompletePackage;
use Composer\Package\CompleteAliasPackage;
use Composer\Package\RootPackage;
use Composer\Package\RootAliasPackage;

/**
 * @author Konstantin Kudryashiv <ever.zet@gmail.com>
 */
class JsonLoader
{
    /** @var LoaderInterface */
    private $loader;

    public function __construct(LoaderInterface $loader)
    {
        $this->loader = $loader;
    }

    /**
     * @param  string|JsonFile                      $json A filename, json string or JsonFile instance to load the package from
     * @return CompletePackage|CompleteAliasPackage|RootPackage|RootAliasPackage
     */
    public function load($json): BasePackage
    {
        if ($json instanceof JsonFile) {
            $config = $json->read();
        } elseif (file_exists($json)) {
            $config = JsonFile::parseJson(file_get_contents($json), $json);
        } elseif (is_string($json)) {
            $config = JsonFile::parseJson($json);
        } else {
            throw new \InvalidArgumentException(sprintf(
                "JsonLoader: Unknown \$json parameter %s. Please report at https://github.com/composer/composer/issues/new.",
                get_debug_type($json)
            ));
        }

        return $this->loader->load($config);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Loader;

use Composer\Package\BasePackage;
use Composer\Package\CompleteAliasPackage;
use Composer\Package\CompletePackage;
use Composer\Package\RootPackage;
use Composer\Package\PackageInterface;
use Composer\Package\CompletePackageInterface;
use Composer\Package\Link;
use Composer\Package\RootAliasPackage;
use Composer\Package\Version\VersionParser;
use Composer\Pcre\Preg;

/**
 * @author Konstantin Kudryashiv <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ArrayLoader implements LoaderInterface
{
    /** @var VersionParser */
    protected $versionParser;
    /** @var bool */
    protected $loadOptions;

    public function __construct(?VersionParser $parser = null, bool $loadOptions = false)
    {
        if (!$parser) {
            $parser = new VersionParser;
        }
        $this->versionParser = $parser;
        $this->loadOptions = $loadOptions;
    }

    /**
     * @inheritDoc
     */
    public function load(array $config, string $class = 'Composer\Package\CompletePackage'): BasePackage
    {
        if ($class !== 'Composer\Package\CompletePackage' && $class !== 'Composer\Package\RootPackage') {
            trigger_error('The $class arg is deprecated, please reach out to Composer maintainers ASAP if you still need this.', E_USER_DEPRECATED);
        }

        $package = $this->createObject($config, $class);

        foreach (BasePackage::$supportedLinkTypes as $type => $opts) {
            if (!isset($config[$type]) || !is_array($config[$type])) {
                continue;
            }
            $method = 'set'.ucfirst($opts['method']);
            $package->{$method}(
                $this->parseLinks(
                    $package->getName(),
                    $package->getPrettyVersion(),
                    $opts['method'],
                    $config[$type]
                )
            );
        }

        $package = $this->configureObject($package, $config);

        return $package;
    }

    /**
     * @param array<array<mixed>> $versions
     *
     * @return list<CompletePackage|CompleteAliasPackage>
     */
    public function loadPackages(array $versions): array
    {
        $packages = [];
        $linkCache = [];

        foreach ($versions as $version) {
            $package = $this->createObject($version, 'Composer\Package\CompletePackage');

            $this->configureCachedLinks($linkCache, $package, $version);
            $package = $this->configureObject($package, $version);

            $packages[] = $package;
        }

        return $packages;
    }

    /**
     * @template PackageClass of CompletePackage
     *
     * @param mixed[] $config package data
     * @param string  $class  FQCN to be instantiated
     *
     * @return CompletePackage|RootPackage
     *
     * @phpstan-param class-string<PackageClass> $class
     */
    private function createObject(array $config, string $class): CompletePackage
    {
        if (!isset($config['name'])) {
            throw new \UnexpectedValueException('Unknown package has no name defined ('.json_encode($config).').');
        }
        if (!isset($config['version']) || !is_scalar($config['version'])) {
            throw new \UnexpectedValueException('Package '.$config['name'].' has no version defined.');
        }
        if (!is_string($config['version'])) {
            $config['version'] = (string) $config['version'];
        }

        // handle already normalized versions
        if (isset($config['version_normalized']) && is_string($config['version_normalized'])) {
            $version = $config['version_normalized'];

            // handling of existing repos which need to remain composer v1 compatible, in case the version_normalized contained VersionParser::DEFAULT_BRANCH_ALIAS, we renormalize it
            if ($version === VersionParser::DEFAULT_BRANCH_ALIAS) {
                $version = $this->versionParser->normalize($config['version']);
            }
        } else {
            try {
                $version = $this->versionParser->normalize($config['version']);
            } catch (\UnexpectedValueException $e) {
                throw new \UnexpectedValueException(
                    'Failed to normalize version for package "'.$config['name'].'": '.$e->getMessage(),
                    $e->getCode(),
                    $e
                );
            }
        }

        return new $class($config['name'], $version, $config['version']);
    }

    /**
     * @param CompletePackage $package
     * @param mixed[]         $config package data
     *
     * @return RootPackage|RootAliasPackage|CompletePackage|CompleteAliasPackage
     */
    private function configureObject(PackageInterface $package, array $config): BasePackage
    {
        if (!$package instanceof CompletePackage) {
            throw new \LogicException('ArrayLoader expects instances of the Composer\Package\CompletePackage class to function correctly');
        }

        $package->setType(isset($config['type']) ? strtolower($config['type']) : 'library');

        if (isset($config['target-dir'])) {
            $package->setTargetDir($config['target-dir']);
        }

        if (isset($config['extra']) && \is_array($config['extra'])) {
            $package->setExtra($config['extra']);
        }

        if (isset($config['bin'])) {
            if (!\is_array($config['bin'])) {
                $config['bin'] = [$config['bin']];
            }
            foreach ($config['bin'] as $key => $bin) {
                $config['bin'][$key] = ltrim($bin, '/');
            }
            $package->setBinaries($config['bin']);
        }

        if (isset($config['installation-source'])) {
            $package->setInstallationSource($config['installation-source']);
        }

        if (isset($config['default-branch']) && $config['default-branch'] === true) {
            $package->setIsDefaultBranch(true);
        }

        if (isset($config['source'])) {
            if (!isset($config['source']['type'], $config['source']['url'], $config['source']['reference'])) {
                throw new \UnexpectedValueException(sprintf(
                    "Package %s's source key should be specified as {\"type\": ..., \"url\": ..., \"reference\": ...},\n%s given.",
                    $config['name'],
                    json_encode($config['source'])
                ));
            }
            $package->setSourceType($config['source']['type']);
            $package->setSourceUrl($config['source']['url']);
            $package->setSourceReference(isset($config['source']['reference']) ? (string) $config['source']['reference'] : null);
            if (isset($config['source']['mirrors'])) {
                $package->setSourceMirrors($config['source']['mirrors']);
            }
        }

        if (isset($config['dist'])) {
            if (!isset($config['dist']['type'], $config['dist']['url'])) {
                throw new \UnexpectedValueException(sprintf(
                    "Package %s's dist key should be specified as ".
                    "{\"type\": ..., \"url\": ..., \"reference\": ..., \"shasum\": ...},\n%s given.",
                    $config['name'],
                    json_encode($config['dist'])
                ));
            }
            $package->setDistType($config['dist']['type']);
            $package->setDistUrl($config['dist']['url']);
            $package->setDistReference(isset($config['dist']['reference']) ? (string) $config['dist']['reference'] : null);
            $package->setDistSha1Checksum($config['dist']['shasum'] ?? null);
            if (isset($config['dist']['mirrors'])) {
                $package->setDistMirrors($config['dist']['mirrors']);
            }
        }

        if (isset($config['suggest']) && \is_array($config['suggest'])) {
            foreach ($config['suggest'] as $target => $reason) {
                if ('self.version' === trim($reason)) {
                    $config['suggest'][$target] = $package->getPrettyVersion();
                }
            }
            $package->setSuggests($config['suggest']);
        }

        if (isset($config['autoload'])) {
            $package->setAutoload($config['autoload']);
        }

        if (isset($config['autoload-dev'])) {
            $package->setDevAutoload($config['autoload-dev']);
        }

        if (isset($config['include-path'])) {
            $package->setIncludePaths($config['include-path']);
        }

        if (isset($config['php-ext'])) {
            $package->setPhpExt($config['php-ext']);
        }

        if (!empty($config['time'])) {
            $time = Preg::isMatch('/^\d++$/D', $config['time']) ? '@'.$config['time'] : $config['time'];

            try {
                $date = new \DateTime($time, new \DateTimeZone('UTC'));
                $package->setReleaseDate($date);
            } catch (\Exception $e) {
            }
        }

        if (!empty($config['notification-url'])) {
            $package->setNotificationUrl($config['notification-url']);
        }

        if ($package instanceof CompletePackageInterface) {
            if (!empty($config['archive']['name'])) {
                $package->setArchiveName($config['archive']['name']);
            }
            if (!empty($config['archive']['exclude'])) {
                $package->setArchiveExcludes($config['archive']['exclude']);
            }

            if (isset($config['scripts']) && \is_array($config['scripts'])) {
                foreach ($config['scripts'] as $event => $listeners) {
                    $config['scripts'][$event] = (array) $listeners;
                }
                foreach (['composer', 'php', 'putenv'] as $reserved) {
                    if (isset($config['scripts'][$reserved])) {
                        trigger_error('The `'.$reserved.'` script name is reserved for internal use, please avoid defining it', E_USER_DEPRECATED);
                    }
                }
                $package->setScripts($config['scripts']);
            }

            if (!empty($config['description']) && \is_string($config['description'])) {
                $package->setDescription($config['description']);
            }

            if (!empty($config['homepage']) && \is_string($config['homepage'])) {
                $package->setHomepage($config['homepage']);
            }

            if (!empty($config['keywords']) && \is_array($config['keywords'])) {
                $package->setKeywords(array_map('strval', $config['keywords']));
            }

            if (!empty($config['license'])) {
                $package->setLicense(\is_array($config['license']) ? $config['license'] : [$config['license']]);
            }

            if (!empty($config['authors']) && \is_array($config['authors'])) {
                $package->setAuthors($config['authors']);
            }

            if (isset($config['support']) && \is_array($config['support'])) {
                $package->setSupport($config['support']);
            }

            if (!empty($config['funding']) && \is_array($config['funding'])) {
                $package->setFunding($config['funding']);
            }

            if (isset($config['abandoned'])) {
                $package->setAbandoned($config['abandoned']);
            }
        }

        if ($this->loadOptions && isset($config['transport-options'])) {
            $package->setTransportOptions($config['transport-options']);
        }

        if ($aliasNormalized = $this->getBranchAlias($config)) {
            $prettyAlias = Preg::replace('{(\.9{7})+}', '.x', $aliasNormalized);

            if ($package instanceof RootPackage) {
                return new RootAliasPackage($package, $aliasNormalized, $prettyAlias);
            }

            return new CompleteAliasPackage($package, $aliasNormalized, $prettyAlias);
        }

        return $package;
    }

    /**
     * @param array<string, array<string, array<int|string, array<int|string, array{string, Link}>>>> $linkCache
     * @param mixed[]                                                                             $config
     */
    private function configureCachedLinks(array &$linkCache, PackageInterface $package, array $config): void
    {
        $name = $package->getName();
        $prettyVersion = $package->getPrettyVersion();

        foreach (BasePackage::$supportedLinkTypes as $type => $opts) {
            if (isset($config[$type])) {
                $method = 'set'.ucfirst($opts['method']);

                $links = [];
                foreach ($config[$type] as $prettyTarget => $constraint) {
                    $target = strtolower($prettyTarget);

                    // recursive links are not supported
                    if ($target === $name) {
                        continue;
                    }

                    if ($constraint === 'self.version') {
                        $links[$target] = $this->createLink($name, $prettyVersion, $opts['method'], $target, $constraint);
                    } else {
                        if (!isset($linkCache[$name][$type][$target][$constraint])) {
                            $linkCache[$name][$type][$target][$constraint] = [$target, $this->createLink($name, $prettyVersion, $opts['method'], $target, $constraint)];
                        }

                        [$target, $link] = $linkCache[$name][$type][$target][$constraint];
                        $links[$target] = $link;
                    }
                }

                $package->{$method}($links);
            }
        }
    }

    /**
     * @param  string                    $source        source package name
     * @param  string                    $sourceVersion source package version (pretty version ideally)
     * @param  string                    $description   link description (e.g. requires, replaces, ..)
     * @param  array<string|int, string> $links         array of package name => constraint mappings
     *
     * @return Link[]
     *
     * @phpstan-param Link::TYPE_* $description
     */
    public function parseLinks(string $source, string $sourceVersion, string $description, array $links): array
    {
        $res = [];
        foreach ($links as $target => $constraint) {
            if (!is_string($constraint)) {
                continue;
            }
            $target = strtolower((string) $target);
            $res[$target] = $this->createLink($source, $sourceVersion, $description, $target, $constraint);
        }

        return $res;
    }

    /**
     * @param  string       $source           source package name
     * @param  string       $sourceVersion    source package version (pretty version ideally)
     * @param  Link::TYPE_* $description      link description (e.g. requires, replaces, ..)
     * @param  string       $target           target package name
     * @param  string       $prettyConstraint constraint string
     */
    private function createLink(string $source, string $sourceVersion, string $description, string $target, string $prettyConstraint): Link
    {
        if (!\is_string($prettyConstraint)) {
            throw new \UnexpectedValueException('Link constraint in '.$source.' '.$description.' > '.$target.' should be a string, got '.\get_debug_type($prettyConstraint) . ' (' . var_export($prettyConstraint, true) . ')');
        }

        if ('self.version' === $prettyConstraint) {
            $constraint = $sourceVersion;
        } else {
            $constraint = $prettyConstraint;
        }

        try {
            $parsedConstraint = $this->versionParser->parseConstraints($constraint);
        } catch (\UnexpectedValueException $e) {
            throw new \UnexpectedValueException('Link constraint in '.$source.' '.$description.' > '.$target.' should be a valid version constraint, got "'.$constraint.'"',
                $e->getCode(),
                $e
            );
        }

        return new Link($source, $target, $parsedConstraint, $description, $prettyConstraint);
    }

    /**
     * Retrieves a branch alias (dev-master => 1.0.x-dev for example) if it exists
     *
     * @param mixed[] $config the entire package config
     *
     * @return string|null normalized version of the branch alias or null if there is none
     */
    public function getBranchAlias(array $config): ?string
    {
        if (!isset($config['version']) || !is_scalar($config['version'])) {
            throw new \UnexpectedValueException('no/invalid version defined');
        }
        if (!is_string($config['version'])) {
            $config['version'] = (string) $config['version'];
        }

        if (strpos($config['version'], 'dev-') !== 0 && '-dev' !== substr($config['version'], -4)) {
            return null;
        }

        if (isset($config['extra']['branch-alias']) && \is_array($config['extra']['branch-alias'])) {
            foreach ($config['extra']['branch-alias'] as $sourceBranch => $targetBranch) {
                $sourceBranch = (string) $sourceBranch;

                // ensure it is an alias to a -dev package
                if ('-dev' !== substr($targetBranch, -4)) {
                    continue;
                }

                // normalize without -dev and ensure it's a numeric branch that is parseable
                if ($targetBranch === VersionParser::DEFAULT_BRANCH_ALIAS) {
                    $validatedTargetBranch = VersionParser::DEFAULT_BRANCH_ALIAS;
                } else {
                    $validatedTargetBranch = $this->versionParser->normalizeBranch(substr($targetBranch, 0, -4));
                }
                if ('-dev' !== substr($validatedTargetBranch, -4)) {
                    continue;
                }

                // ensure that it is the current branch aliasing itself
                if (strtolower($config['version']) !== strtolower($sourceBranch)) {
                    continue;
                }

                // If using numeric aliases ensure the alias is a valid subversion
                if (($sourcePrefix = $this->versionParser->parseNumericAliasPrefix($sourceBranch))
                    && ($targetPrefix = $this->versionParser->parseNumericAliasPrefix($targetBranch))
                    && (stripos($targetPrefix, $sourcePrefix) !== 0)
                ) {
                    continue;
                }

                return $validatedTargetBranch;
            }
        }

        if (
            isset($config['default-branch'])
            && $config['default-branch'] === true
            && false === $this->versionParser->parseNumericAliasPrefix(Preg::replace('{^v}', '', $config['version']))
        ) {
            return VersionParser::DEFAULT_BRANCH_ALIAS;
        }

        return null;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Loader;

use Composer\Package\BasePackage;
use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Package\RootAliasPackage;
use Composer\Pcre\Preg;
use Composer\Repository\RepositoryFactory;
use Composer\Package\Version\VersionGuesser;
use Composer\Package\Version\VersionParser;
use Composer\Package\RootPackage;
use Composer\Repository\RepositoryManager;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;

/**
 * ArrayLoader built for the sole purpose of loading the root package
 *
 * Sets additional defaults and loads repositories
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class RootPackageLoader extends ArrayLoader
{
    /**
     * @var RepositoryManager
     */
    private $manager;

    /**
     * @var Config
     */
    private $config;

    /**
     * @var VersionGuesser
     */
    private $versionGuesser;

    /**
     * @var IOInterface|null
     */
    private $io;

    public function __construct(RepositoryManager $manager, Config $config, ?VersionParser $parser = null, ?VersionGuesser $versionGuesser = null, ?IOInterface $io = null)
    {
        parent::__construct($parser);

        $this->manager = $manager;
        $this->config = $config;
        if (null === $versionGuesser) {
            $processExecutor = new ProcessExecutor($io);
            $processExecutor->enableAsync();
            $versionGuesser = new VersionGuesser($config, $processExecutor, $this->versionParser);
        }
        $this->versionGuesser = $versionGuesser;
        $this->io = $io;
    }

    /**
     * @inheritDoc
     *
     * @return RootPackage|RootAliasPackage
     *
     * @phpstan-param class-string<RootPackage> $class
     */
    public function load(array $config, string $class = 'Composer\Package\RootPackage', ?string $cwd = null): BasePackage
    {
        if ($class !== 'Composer\Package\RootPackage') {
            trigger_error('The $class arg is deprecated, please reach out to Composer maintainers ASAP if you still need this.', E_USER_DEPRECATED);
        }

        if (!isset($config['name'])) {
            $config['name'] = '__root__';
        } elseif ($err = ValidatingArrayLoader::hasPackageNamingError($config['name'])) {
            throw new \RuntimeException('Your package name '.$err);
        }
        $autoVersioned = false;
        if (!isset($config['version'])) {
            $commit = null;

            // override with env var if available
            if (Platform::getEnv('COMPOSER_ROOT_VERSION')) {
                $config['version'] = $this->versionGuesser->getRootVersionFromEnv();
            } else {
                $versionData = $this->versionGuesser->guessVersion($config, $cwd ?? Platform::getCwd(true));
                if ($versionData) {
                    $config['version'] = $versionData['pretty_version'];
                    $config['version_normalized'] = $versionData['version'];
                    $commit = $versionData['commit'];
                }
            }

            if (!isset($config['version'])) {
                if ($this->io !== null && $config['name'] !== '__root__' && 'project' !== ($config['type'] ?? '')) {
                    $this->io->warning(
                        sprintf(
                            "Composer could not detect the root package (%s) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version",
                            $config['name']
                        )
                    );
                }
                $config['version'] = '1.0.0';
                $autoVersioned = true;
            }

            if ($commit) {
                $config['source'] = [
                    'type' => '',
                    'url' => '',
                    'reference' => $commit,
                ];
                $config['dist'] = [
                    'type' => '',
                    'url' => '',
                    'reference' => $commit,
                ];
            }
        }

        /** @var RootPackage|RootAliasPackage $package */
        $package = parent::load($config, $class);
        if ($package instanceof RootAliasPackage) {
            $realPackage = $package->getAliasOf();
        } else {
            $realPackage = $package;
        }

        if (!$realPackage instanceof RootPackage) {
            throw new \LogicException('Expecting a Composer\Package\RootPackage at this point');
        }

        if ($autoVersioned) {
            $realPackage->replaceVersion($realPackage->getVersion(), RootPackage::DEFAULT_PRETTY_VERSION);
        }

        if (isset($config['minimum-stability'])) {
            $realPackage->setMinimumStability(VersionParser::normalizeStability($config['minimum-stability']));
        }

        $aliases = [];
        $stabilityFlags = [];
        $references = [];
        foreach (['require', 'require-dev'] as $linkType) {
            if (isset($config[$linkType])) {
                $linkInfo = BasePackage::$supportedLinkTypes[$linkType];
                $method = 'get'.ucfirst($linkInfo['method']);
                $links = [];
                foreach ($realPackage->{$method}() as $link) {
                    $links[$link->getTarget()] = $link->getConstraint()->getPrettyString();
                }
                $aliases = $this->extractAliases($links, $aliases);
                $stabilityFlags = self::extractStabilityFlags($links, $realPackage->getMinimumStability(), $stabilityFlags);
                $references = self::extractReferences($links, $references);

                if (isset($links[$config['name']])) {
                    throw new \RuntimeException(sprintf('Root package \'%s\' cannot require itself in its composer.json' . PHP_EOL .
                                'Did you accidentally name your root package after an external package?', $config['name']));
                }
            }
        }

        foreach (array_keys(BasePackage::$supportedLinkTypes) as $linkType) {
            if (isset($config[$linkType])) {
                foreach ($config[$linkType] as $linkName => $constraint) {
                    if ($err = ValidatingArrayLoader::hasPackageNamingError($linkName, true)) {
                        throw new \RuntimeException($linkType.'.'.$err);
                    }
                }
            }
        }

        $realPackage->setAliases($aliases);
        $realPackage->setStabilityFlags($stabilityFlags);
        $realPackage->setReferences($references);

        if (isset($config['prefer-stable'])) {
            $realPackage->setPreferStable((bool) $config['prefer-stable']);
        }

        if (isset($config['config'])) {
            $realPackage->setConfig($config['config']);
        }

        $repos = RepositoryFactory::defaultRepos(null, $this->config, $this->manager);
        foreach ($repos as $repo) {
            $this->manager->addRepository($repo);
        }
        $realPackage->setRepositories($this->config->getRepositories());

        return $package;
    }

    /**
     * @param array<string, string>                                                                  $requires
     * @param list<array{package: string, version: string, alias: string, alias_normalized: string}> $aliases
     *
     * @return list<array{package: string, version: string, alias: string, alias_normalized: string}>
     */
    private function extractAliases(array $requires, array $aliases): array
    {
        foreach ($requires as $reqName => $reqVersion) {
            if (Preg::isMatchStrictGroups('{(?:^|\| *|, *)([^,\s#|]+)(?:#[^ ]+)? +as +([^,\s|]+)(?:$| *\|| *,)}', $reqVersion, $match)) {
                $aliases[] = [
                    'package' => strtolower($reqName),
                    'version' => $this->versionParser->normalize($match[1], $reqVersion),
                    'alias' => $match[2],
                    'alias_normalized' => $this->versionParser->normalize($match[2], $reqVersion),
                ];
            } elseif (strpos($reqVersion, ' as ') !== false) {
                throw new \UnexpectedValueException('Invalid alias definition in "'.$reqName.'": "'.$reqVersion.'". Aliases should be in the form "exact-version as other-exact-version".');
            }
        }

        return $aliases;
    }

    /**
     * @internal
     *
     * @param array<string, string> $requires
     * @param array<string, int>    $stabilityFlags
     * @param key-of<BasePackage::STABILITIES> $minimumStability
     *
     * @return array<string, int>
     *
     * @phpstan-param array<string, BasePackage::STABILITY_*> $stabilityFlags
     * @phpstan-return array<string, BasePackage::STABILITY_*>
     */
    public static function extractStabilityFlags(array $requires, string $minimumStability, array $stabilityFlags): array
    {
        $stabilities = BasePackage::STABILITIES;
        $minimumStability = $stabilities[$minimumStability];
        foreach ($requires as $reqName => $reqVersion) {
            $constraints = [];

            // extract all sub-constraints in case it is an OR/AND multi-constraint
            $orSplit = Preg::split('{\s*\|\|?\s*}', trim($reqVersion));
            foreach ($orSplit as $orConstraint) {
                $andSplit = Preg::split('{(?<!^|as|[=>< ,]) *(?<!-)[, ](?!-) *(?!,|as|$)}', $orConstraint);
                foreach ($andSplit as $andConstraint) {
                    $constraints[] = $andConstraint;
                }
            }

            // parse explicit stability flags to the most unstable
            $matched = false;
            foreach ($constraints as $constraint) {
                if (Preg::isMatchStrictGroups('{^[^@]*?@('.implode('|', array_keys($stabilities)).')$}i', $constraint, $match)) {
                    $name = strtolower($reqName);
                    $stability = $stabilities[VersionParser::normalizeStability($match[1])];

                    if (isset($stabilityFlags[$name]) && $stabilityFlags[$name] > $stability) {
                        continue;
                    }
                    $stabilityFlags[$name] = $stability;
                    $matched = true;
                }
            }

            if ($matched) {
                continue;
            }

            foreach ($constraints as $constraint) {
                // infer flags for requirements that have an explicit -dev or -beta version specified but only
                // for those that are more unstable than the minimumStability or existing flags
                $reqVersion = Preg::replace('{^([^,\s@]+) as .+$}', '$1', $constraint);
                if (Preg::isMatch('{^[^,\s@]+$}', $reqVersion) && 'stable' !== ($stabilityName = VersionParser::parseStability($reqVersion))) {
                    $name = strtolower($reqName);
                    $stability = $stabilities[$stabilityName];
                    if ((isset($stabilityFlags[$name]) && $stabilityFlags[$name] > $stability) || ($minimumStability > $stability)) {
                        continue;
                    }
                    $stabilityFlags[$name] = $stability;
                }
            }
        }

        return $stabilityFlags;
    }

    /**
     * @internal
     *
     * @param array<string, string> $requires
     * @param array<string, string> $references
     *
     * @return array<string, string>
     */
    public static function extractReferences(array $requires, array $references): array
    {
        foreach ($requires as $reqName => $reqVersion) {
            $reqVersion = Preg::replace('{^([^,\s@]+) as .+$}', '$1', $reqVersion);
            if (Preg::isMatchStrictGroups('{^[^,\s@]+?#([a-f0-9]+)$}', $reqVersion, $match) && 'dev' === VersionParser::parseStability($reqVersion)) {
                $name = strtolower($reqName);
                $references[$name] = $match[1];
            }
        }

        return $references;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package;

use Composer\Semver\Constraint\Constraint;
use Composer\Package\Version\VersionParser;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class AliasPackage extends BasePackage
{
    /** @var string */
    protected $version;
    /** @var string */
    protected $prettyVersion;
    /** @var bool */
    protected $dev;
    /** @var bool */
    protected $rootPackageAlias = false;
    /**
     * @var string
     * @phpstan-var 'stable'|'RC'|'beta'|'alpha'|'dev'
     */
    protected $stability;
    /** @var bool */
    protected $hasSelfVersionRequires = false;

    /** @var BasePackage */
    protected $aliasOf;
    /** @var Link[] */
    protected $requires;
    /** @var Link[] */
    protected $devRequires;
    /** @var Link[] */
    protected $conflicts;
    /** @var Link[] */
    protected $provides;
    /** @var Link[] */
    protected $replaces;

    /**
     * All descendants' constructors should call this parent constructor
     *
     * @param BasePackage $aliasOf       The package this package is an alias of
     * @param string      $version       The version the alias must report
     * @param string      $prettyVersion The alias's non-normalized version
     */
    public function __construct(BasePackage $aliasOf, string $version, string $prettyVersion)
    {
        parent::__construct($aliasOf->getName());

        $this->version = $version;
        $this->prettyVersion = $prettyVersion;
        $this->aliasOf = $aliasOf;
        $this->stability = VersionParser::parseStability($version);
        $this->dev = $this->stability === 'dev';

        foreach (Link::$TYPES as $type) {
            $links = $aliasOf->{'get' . ucfirst($type)}();
            $this->{$type} = $this->replaceSelfVersionDependencies($links, $type);
        }
    }

    /**
     * @return BasePackage
     */
    public function getAliasOf()
    {
        return $this->aliasOf;
    }

    /**
     * @inheritDoc
     */
    public function getVersion(): string
    {
        return $this->version;
    }

    /**
     * @inheritDoc
     */
    public function getStability(): string
    {
        return $this->stability;
    }

    /**
     * @inheritDoc
     */
    public function getPrettyVersion(): string
    {
        return $this->prettyVersion;
    }

    /**
     * @inheritDoc
     */
    public function isDev(): bool
    {
        return $this->dev;
    }

    /**
     * @inheritDoc
     */
    public function getRequires(): array
    {
        return $this->requires;
    }

    /**
     * @inheritDoc
     * @return array<string|int, Link>
     */
    public function getConflicts(): array
    {
        return $this->conflicts;
    }

    /**
     * @inheritDoc
     * @return array<string|int, Link>
     */
    public function getProvides(): array
    {
        return $this->provides;
    }

    /**
     * @inheritDoc
     * @return array<string|int, Link>
     */
    public function getReplaces(): array
    {
        return $this->replaces;
    }

    /**
     * @inheritDoc
     */
    public function getDevRequires(): array
    {
        return $this->devRequires;
    }

    /**
     * Stores whether this is an alias created by an aliasing in the requirements of the root package or not
     *
     * Use by the policy for sorting manually aliased packages first, see #576
     */
    public function setRootPackageAlias(bool $value): void
    {
        $this->rootPackageAlias = $value;
    }

    /**
     * @see setRootPackageAlias
     */
    public function isRootPackageAlias(): bool
    {
        return $this->rootPackageAlias;
    }

    /**
     * @param Link[]       $links
     * @param Link::TYPE_* $linkType
     *
     * @return Link[]
     */
    protected function replaceSelfVersionDependencies(array $links, $linkType): array
    {
        // for self.version requirements, we use the original package's branch name instead, to avoid leaking the magic dev-master-alias to users
        $prettyVersion = $this->prettyVersion;
        if ($prettyVersion === VersionParser::DEFAULT_BRANCH_ALIAS) {
            $prettyVersion = $this->aliasOf->getPrettyVersion();
        }

        if (\in_array($linkType, [Link::TYPE_CONFLICT, Link::TYPE_PROVIDE, Link::TYPE_REPLACE], true)) {
            $newLinks = [];
            foreach ($links as $link) {
                // link is self.version, but must be replacing also the replaced version
                if ('self.version' === $link->getPrettyConstraint()) {
                    $newLinks[] = new Link($link->getSource(), $link->getTarget(), $constraint = new Constraint('=', $this->version), $linkType, $prettyVersion);
                    $constraint->setPrettyString($prettyVersion);
                }
            }
            $links = array_merge($links, $newLinks);
        } else {
            foreach ($links as $index => $link) {
                if ('self.version' === $link->getPrettyConstraint()) {
                    if ($linkType === Link::TYPE_REQUIRE) {
                        $this->hasSelfVersionRequires = true;
                    }
                    $links[$index] = new Link($link->getSource(), $link->getTarget(), $constraint = new Constraint('=', $this->version), $linkType, $prettyVersion);
                    $constraint->setPrettyString($prettyVersion);
                }
            }
        }

        return $links;
    }

    public function hasSelfVersionRequires(): bool
    {
        return $this->hasSelfVersionRequires;
    }

    public function __toString(): string
    {
        return parent::__toString().' ('.($this->rootPackageAlias ? 'root ' : ''). 'alias of '.$this->aliasOf->getVersion().')';
    }

    /***************************************
     * Wrappers around the aliased package *
     ***************************************/

    public function getType(): string
    {
        return $this->aliasOf->getType();
    }

    public function getTargetDir(): ?string
    {
        return $this->aliasOf->getTargetDir();
    }

    public function getExtra(): array
    {
        return $this->aliasOf->getExtra();
    }

    public function setInstallationSource(?string $type): void
    {
        $this->aliasOf->setInstallationSource($type);
    }

    public function getInstallationSource(): ?string
    {
        return $this->aliasOf->getInstallationSource();
    }

    public function getSourceType(): ?string
    {
        return $this->aliasOf->getSourceType();
    }

    public function getSourceUrl(): ?string
    {
        return $this->aliasOf->getSourceUrl();
    }

    public function getSourceUrls(): array
    {
        return $this->aliasOf->getSourceUrls();
    }

    public function getSourceReference(): ?string
    {
        return $this->aliasOf->getSourceReference();
    }

    public function setSourceReference(?string $reference): void
    {
        $this->aliasOf->setSourceReference($reference);
    }

    public function setSourceMirrors(?array $mirrors): void
    {
        $this->aliasOf->setSourceMirrors($mirrors);
    }

    public function getSourceMirrors(): ?array
    {
        return $this->aliasOf->getSourceMirrors();
    }

    public function getDistType(): ?string
    {
        return $this->aliasOf->getDistType();
    }

    public function getDistUrl(): ?string
    {
        return $this->aliasOf->getDistUrl();
    }

    public function getDistUrls(): array
    {
        return $this->aliasOf->getDistUrls();
    }

    public function getDistReference(): ?string
    {
        return $this->aliasOf->getDistReference();
    }

    public function setDistReference(?string $reference): void
    {
        $this->aliasOf->setDistReference($reference);
    }

    public function getDistSha1Checksum(): ?string
    {
        return $this->aliasOf->getDistSha1Checksum();
    }

    public function setTransportOptions(array $options): void
    {
        $this->aliasOf->setTransportOptions($options);
    }

    public function getTransportOptions(): array
    {
        return $this->aliasOf->getTransportOptions();
    }

    public function setDistMirrors(?array $mirrors): void
    {
        $this->aliasOf->setDistMirrors($mirrors);
    }

    public function getDistMirrors(): ?array
    {
        return $this->aliasOf->getDistMirrors();
    }

    public function getAutoload(): array
    {
        return $this->aliasOf->getAutoload();
    }

    public function getDevAutoload(): array
    {
        return $this->aliasOf->getDevAutoload();
    }

    public function getIncludePaths(): array
    {
        return $this->aliasOf->getIncludePaths();
    }

    public function getPhpExt(): ?array
    {
        return $this->aliasOf->getPhpExt();
    }

    public function getReleaseDate(): ?\DateTimeInterface
    {
        return $this->aliasOf->getReleaseDate();
    }

    public function getBinaries(): array
    {
        return $this->aliasOf->getBinaries();
    }

    public function getSuggests(): array
    {
        return $this->aliasOf->getSuggests();
    }

    public function getNotificationUrl(): ?string
    {
        return $this->aliasOf->getNotificationUrl();
    }

    public function isDefaultBranch(): bool
    {
        return $this->aliasOf->isDefaultBranch();
    }

    public function setDistUrl(?string $url): void
    {
        $this->aliasOf->setDistUrl($url);
    }

    public function setDistType(?string $type): void
    {
        $this->aliasOf->setDistType($type);
    }

    public function setSourceDistReferences(string $reference): void
    {
        $this->aliasOf->setSourceDistReferences($reference);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class CompleteAliasPackage extends AliasPackage implements CompletePackageInterface
{
    /** @var CompletePackage */
    protected $aliasOf;

    /**
     * All descendants' constructors should call this parent constructor
     *
     * @param CompletePackage $aliasOf       The package this package is an alias of
     * @param string          $version       The version the alias must report
     * @param string          $prettyVersion The alias's non-normalized version
     */
    public function __construct(CompletePackage $aliasOf, string $version, string $prettyVersion)
    {
        parent::__construct($aliasOf, $version, $prettyVersion);
    }

    /**
     * @return CompletePackage
     */
    public function getAliasOf()
    {
        return $this->aliasOf;
    }

    public function getScripts(): array
    {
        return $this->aliasOf->getScripts();
    }

    public function setScripts(array $scripts): void
    {
        $this->aliasOf->setScripts($scripts);
    }

    public function getRepositories(): array
    {
        return $this->aliasOf->getRepositories();
    }

    public function setRepositories(array $repositories): void
    {
        $this->aliasOf->setRepositories($repositories);
    }

    public function getLicense(): array
    {
        return $this->aliasOf->getLicense();
    }

    public function setLicense(array $license): void
    {
        $this->aliasOf->setLicense($license);
    }

    public function getKeywords(): array
    {
        return $this->aliasOf->getKeywords();
    }

    public function setKeywords(array $keywords): void
    {
        $this->aliasOf->setKeywords($keywords);
    }

    public function getDescription(): ?string
    {
        return $this->aliasOf->getDescription();
    }

    public function setDescription(?string $description): void
    {
        $this->aliasOf->setDescription($description);
    }

    public function getHomepage(): ?string
    {
        return $this->aliasOf->getHomepage();
    }

    public function setHomepage(?string $homepage): void
    {
        $this->aliasOf->setHomepage($homepage);
    }

    public function getAuthors(): array
    {
        return $this->aliasOf->getAuthors();
    }

    public function setAuthors(array $authors): void
    {
        $this->aliasOf->setAuthors($authors);
    }

    public function getSupport(): array
    {
        return $this->aliasOf->getSupport();
    }

    public function setSupport(array $support): void
    {
        $this->aliasOf->setSupport($support);
    }

    public function getFunding(): array
    {
        return $this->aliasOf->getFunding();
    }

    public function setFunding(array $funding): void
    {
        $this->aliasOf->setFunding($funding);
    }

    public function isAbandoned(): bool
    {
        return $this->aliasOf->isAbandoned();
    }

    public function getReplacementPackage(): ?string
    {
        return $this->aliasOf->getReplacementPackage();
    }

    public function setAbandoned($abandoned): void
    {
        $this->aliasOf->setAbandoned($abandoned);
    }

    public function getArchiveName(): ?string
    {
        return $this->aliasOf->getArchiveName();
    }

    public function setArchiveName(?string $name): void
    {
        $this->aliasOf->setArchiveName($name);
    }

    public function getArchiveExcludes(): array
    {
        return $this->aliasOf->getArchiveExcludes();
    }

    public function setArchiveExcludes(array $excludes): void
    {
        $this->aliasOf->setArchiveExcludes($excludes);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package;

/**
 * Package containing additional metadata that is not used by the solver
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class CompletePackage extends Package implements CompletePackageInterface
{
    /** @var mixed[] */
    protected $repositories = [];
    /** @var string[] */
    protected $license = [];
    /** @var string[] */
    protected $keywords = [];
    /** @var array<array{name?: string, homepage?: string, email?: string, role?: string}> */
    protected $authors = [];
    /** @var ?string */
    protected $description = null;
    /** @var ?string */
    protected $homepage = null;
    /** @var array<string, string[]> Map of script name to array of handlers */
    protected $scripts = [];
    /** @var array{issues?: string, forum?: string, wiki?: string, source?: string, email?: string, irc?: string, docs?: string, rss?: string, chat?: string, security?: string} */
    protected $support = [];
    /** @var array<array{url?: string, type?: string}> */
    protected $funding = [];
    /** @var bool|string */
    protected $abandoned = false;
    /** @var ?string */
    protected $archiveName = null;
    /** @var string[] */
    protected $archiveExcludes = [];

    /**
     * @inheritDoc
     */
    public function setScripts(array $scripts): void
    {
        $this->scripts = $scripts;
    }

    /**
     * @inheritDoc
     */
    public function getScripts(): array
    {
        return $this->scripts;
    }

    /**
     * @inheritDoc
     */
    public function setRepositories(array $repositories): void
    {
        $this->repositories = $repositories;
    }

    /**
     * @inheritDoc
     */
    public function getRepositories(): array
    {
        return $this->repositories;
    }

    /**
     * @inheritDoc
     */
    public function setLicense(array $license): void
    {
        $this->license = $license;
    }

    /**
     * @inheritDoc
     */
    public function getLicense(): array
    {
        return $this->license;
    }

    /**
     * @inheritDoc
     */
    public function setKeywords(array $keywords): void
    {
        $this->keywords = $keywords;
    }

    /**
     * @inheritDoc
     */
    public function getKeywords(): array
    {
        return $this->keywords;
    }

    /**
     * @inheritDoc
     */
    public function setAuthors(array $authors): void
    {
        $this->authors = $authors;
    }

    /**
     * @inheritDoc
     */
    public function getAuthors(): array
    {
        return $this->authors;
    }

    /**
     * @inheritDoc
     */
    public function setDescription(?string $description): void
    {
        $this->description = $description;
    }

    /**
     * @inheritDoc
     */
    public function getDescription(): ?string
    {
        return $this->description;
    }

    /**
     * @inheritDoc
     */
    public function setHomepage(?string $homepage): void
    {
        $this->homepage = $homepage;
    }

    /**
     * @inheritDoc
     */
    public function getHomepage(): ?string
    {
        return $this->homepage;
    }

    /**
     * @inheritDoc
     */
    public function setSupport(array $support): void
    {
        $this->support = $support;
    }

    /**
     * @inheritDoc
     */
    public function getSupport(): array
    {
        return $this->support;
    }

    /**
     * @inheritDoc
     */
    public function setFunding(array $funding): void
    {
        $this->funding = $funding;
    }

    /**
     * @inheritDoc
     */
    public function getFunding(): array
    {
        return $this->funding;
    }

    /**
     * @inheritDoc
     */
    public function isAbandoned(): bool
    {
        return (bool) $this->abandoned;
    }

    /**
     * @inheritDoc
     */
    public function setAbandoned($abandoned): void
    {
        $this->abandoned = $abandoned;
    }

    /**
     * @inheritDoc
     */
    public function getReplacementPackage(): ?string
    {
        return \is_string($this->abandoned) ? $this->abandoned : null;
    }

    /**
     * @inheritDoc
     */
    public function setArchiveName(?string $name): void
    {
        $this->archiveName = $name;
    }

    /**
     * @inheritDoc
     */
    public function getArchiveName(): ?string
    {
        return $this->archiveName;
    }

    /**
     * @inheritDoc
     */
    public function setArchiveExcludes(array $excludes): void
    {
        $this->archiveExcludes = $excludes;
    }

    /**
     * @inheritDoc
     */
    public function getArchiveExcludes(): array
    {
        return $this->archiveExcludes;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package;

use Composer\Json\JsonFile;
use Composer\Installer\InstallationManager;
use Composer\Pcre\Preg;
use Composer\Repository\InstalledRepository;
use Composer\Repository\LockArrayRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RootPackageRepository;
use Composer\Util\ProcessExecutor;
use Composer\Package\Dumper\ArrayDumper;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Version\VersionParser;
use Composer\Plugin\PluginInterface;
use Composer\Util\Git as GitUtil;
use Composer\IO\IOInterface;
use Seld\JsonLint\ParsingException;

/**
 * Reads/writes project lockfile (composer.lock).
 *
 * @author Konstantin Kudryashiv <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Locker
{
    /** @var JsonFile */
    private $lockFile;
    /** @var InstallationManager */
    private $installationManager;
    /** @var string */
    private $hash;
    /** @var string */
    private $contentHash;
    /** @var ArrayLoader */
    private $loader;
    /** @var ArrayDumper */
    private $dumper;
    /** @var ProcessExecutor */
    private $process;
    /** @var mixed[]|null */
    private $lockDataCache = null;
    /** @var bool */
    private $virtualFileWritten = false;

    /**
     * Initializes packages locker.
     *
     * @param JsonFile            $lockFile             lockfile loader
     * @param InstallationManager $installationManager  installation manager instance
     * @param string              $composerFileContents The contents of the composer file
     */
    public function __construct(IOInterface $io, JsonFile $lockFile, InstallationManager $installationManager, string $composerFileContents, ?ProcessExecutor $process = null)
    {
        $this->lockFile = $lockFile;
        $this->installationManager = $installationManager;
        $this->hash = hash('md5', $composerFileContents);
        $this->contentHash = self::getContentHash($composerFileContents);
        $this->loader = new ArrayLoader(null, true);
        $this->dumper = new ArrayDumper();
        $this->process = $process ?? new ProcessExecutor($io);
    }

    /**
     * @internal
     */
    public function getJsonFile(): JsonFile
    {
        return $this->lockFile;
    }

    /**
     * Returns the md5 hash of the sorted content of the composer file.
     *
     * @param string $composerFileContents The contents of the composer file.
     */
    public static function getContentHash(string $composerFileContents): string
    {
        $content = JsonFile::parseJson($composerFileContents, 'composer.json');

        $relevantKeys = [
            'name',
            'version',
            'require',
            'require-dev',
            'conflict',
            'replace',
            'provide',
            'minimum-stability',
            'prefer-stable',
            'repositories',
            'extra',
        ];

        $relevantContent = [];

        foreach (array_intersect($relevantKeys, array_keys($content)) as $key) {
            $relevantContent[$key] = $content[$key];
        }
        if (isset($content['config']['platform'])) {
            $relevantContent['config']['platform'] = $content['config']['platform'];
        }

        ksort($relevantContent);

        return hash('md5', JsonFile::encode($relevantContent, 0));
    }

    /**
     * Checks whether locker has been locked (lockfile found).
     */
    public function isLocked(): bool
    {
        if (!$this->virtualFileWritten && !$this->lockFile->exists()) {
            return false;
        }

        $data = $this->getLockData();

        return isset($data['packages']);
    }

    /**
     * Checks whether the lock file is still up to date with the current hash
     */
    public function isFresh(): bool
    {
        $lock = $this->lockFile->read();

        if (!empty($lock['content-hash'])) {
            // There is a content hash key, use that instead of the file hash
            return $this->contentHash === $lock['content-hash'];
        }

        // BC support for old lock files without content-hash
        if (!empty($lock['hash'])) {
            return $this->hash === $lock['hash'];
        }

        // should not be reached unless the lock file is corrupted, so assume it's out of date
        return false;
    }

    /**
     * Searches and returns an array of locked packages, retrieved from registered repositories.
     *
     * @param  bool                                     $withDevReqs true to retrieve the locked dev packages
     * @throws \RuntimeException
     */
    public function getLockedRepository(bool $withDevReqs = false): LockArrayRepository
    {
        $lockData = $this->getLockData();
        $packages = new LockArrayRepository();

        $lockedPackages = $lockData['packages'];
        if ($withDevReqs) {
            if (isset($lockData['packages-dev'])) {
                $lockedPackages = array_merge($lockedPackages, $lockData['packages-dev']);
            } else {
                throw new \RuntimeException('The lock file does not contain require-dev information, run install with the --no-dev option or delete it and run composer update to generate a new lock file.');
            }
        }

        if (empty($lockedPackages)) {
            return $packages;
        }

        if (isset($lockedPackages[0]['name'])) {
            $packageByName = [];
            foreach ($lockedPackages as $info) {
                $package = $this->loader->load($info);
                $packages->addPackage($package);
                $packageByName[$package->getName()] = $package;

                if ($package instanceof AliasPackage) {
                    $packageByName[$package->getAliasOf()->getName()] = $package->getAliasOf();
                }
            }

            if (isset($lockData['aliases'])) {
                foreach ($lockData['aliases'] as $alias) {
                    if (isset($packageByName[$alias['package']])) {
                        $aliasPkg = new CompleteAliasPackage($packageByName[$alias['package']], $alias['alias_normalized'], $alias['alias']);
                        $aliasPkg->setRootPackageAlias(true);
                        $packages->addPackage($aliasPkg);
                    }
                }
            }

            return $packages;
        }

        throw new \RuntimeException('Your composer.lock is invalid. Run "composer update" to generate a new one.');
    }

    /**
     * @return string[] Names of dependencies installed through require-dev
     */
    public function getDevPackageNames(): array
    {
        $names = [];
        $lockData = $this->getLockData();
        if (isset($lockData['packages-dev'])) {
            foreach ($lockData['packages-dev'] as $package) {
                $names[] = strtolower($package['name']);
            }
        }

        return $names;
    }

    /**
     * Returns the platform requirements stored in the lock file
     *
     * @param  bool                     $withDevReqs if true, the platform requirements from the require-dev block are also returned
     * @return Link[]
     */
    public function getPlatformRequirements(bool $withDevReqs = false): array
    {
        $lockData = $this->getLockData();
        $requirements = [];

        if (!empty($lockData['platform'])) {
            $requirements = $this->loader->parseLinks(
                '__root__',
                '1.0.0',
                Link::TYPE_REQUIRE,
                $lockData['platform'] ?? []
            );
        }

        if ($withDevReqs && !empty($lockData['platform-dev'])) {
            $devRequirements = $this->loader->parseLinks(
                '__root__',
                '1.0.0',
                Link::TYPE_REQUIRE,
                $lockData['platform-dev'] ?? []
            );

            $requirements = array_merge($requirements, $devRequirements);
        }

        return $requirements;
    }

    /**
     * @return key-of<BasePackage::STABILITIES>
     */
    public function getMinimumStability(): string
    {
        $lockData = $this->getLockData();

        return $lockData['minimum-stability'] ?? 'stable';
    }

    /**
     * @return array<string, string>
     */
    public function getStabilityFlags(): array
    {
        $lockData = $this->getLockData();

        return $lockData['stability-flags'] ?? [];
    }

    public function getPreferStable(): ?bool
    {
        $lockData = $this->getLockData();

        // return null if not set to allow caller logic to choose the
        // right behavior since old lock files have no prefer-stable
        return $lockData['prefer-stable'] ?? null;
    }

    public function getPreferLowest(): ?bool
    {
        $lockData = $this->getLockData();

        // return null if not set to allow caller logic to choose the
        // right behavior since old lock files have no prefer-lowest
        return $lockData['prefer-lowest'] ?? null;
    }

    /**
     * @return array<string, string>
     */
    public function getPlatformOverrides(): array
    {
        $lockData = $this->getLockData();

        return $lockData['platform-overrides'] ?? [];
    }

    /**
     * @return string[][]
     *
     * @phpstan-return list<array{package: string, version: string, alias: string, alias_normalized: string}>
     */
    public function getAliases(): array
    {
        $lockData = $this->getLockData();

        return $lockData['aliases'] ?? [];
    }

    /**
     * @return string
     */
    public function getPluginApi()
    {
        $lockData = $this->getLockData();

        return $lockData['plugin-api-version'] ?? '1.1.0';
    }

    /**
     * @return array<string, mixed>
     */
    public function getLockData(): array
    {
        if (null !== $this->lockDataCache) {
            return $this->lockDataCache;
        }

        if (!$this->lockFile->exists()) {
            throw new \LogicException('No lockfile found. Unable to read locked packages');
        }

        return $this->lockDataCache = $this->lockFile->read();
    }

    /**
     * Locks provided data into lockfile.
     *
     * @param PackageInterface[]          $packages          array of packages
     * @param PackageInterface[]|null     $devPackages       array of dev packages or null if installed without --dev
     * @param array<string, string>       $platformReqs      array of package name => constraint for required platform packages
     * @param array<string, string>       $platformDevReqs   array of package name => constraint for dev-required platform packages
     * @param string[][]                  $aliases           array of aliases
     * @param array<string, int>          $stabilityFlags
     * @param array<string, string|false> $platformOverrides
     * @param bool                        $write             Whether to actually write data to disk, useful in tests and for --dry-run
     *
     * @phpstan-param list<array{package: string, version: string, alias: string, alias_normalized: string}> $aliases
     */
    public function setLockData(array $packages, ?array $devPackages, array $platformReqs, array $platformDevReqs, array $aliases, string $minimumStability, array $stabilityFlags, bool $preferStable, bool $preferLowest, array $platformOverrides, bool $write = true): bool
    {
        // keep old default branch names normalized to DEFAULT_BRANCH_ALIAS for BC as that is how Composer 1 outputs the lock file
        // when loading the lock file the version is anyway ignored in Composer 2, so it has no adverse effect
        $aliases = array_map(static function ($alias): array {
            if (in_array($alias['version'], ['dev-master', 'dev-trunk', 'dev-default'], true)) {
                $alias['version'] = VersionParser::DEFAULT_BRANCH_ALIAS;
            }

            return $alias;
        }, $aliases);

        $lock = [
            '_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 @gener'.'ated automatically', ],
            'content-hash' => $this->contentHash,
            'packages' => $this->lockPackages($packages),
            'packages-dev' => null,
            'aliases' => $aliases,
            'minimum-stability' => $minimumStability,
            'stability-flags' => $stabilityFlags,
            'prefer-stable' => $preferStable,
            'prefer-lowest' => $preferLowest,
        ];

        if (null !== $devPackages) {
            $lock['packages-dev'] = $this->lockPackages($devPackages);
        }

        $lock['platform'] = $platformReqs;
        $lock['platform-dev'] = $platformDevReqs;
        if (\count($platformOverrides) > 0) {
            $lock['platform-overrides'] = $platformOverrides;
        }
        $lock['plugin-api-version'] = PluginInterface::PLUGIN_API_VERSION;

        $lock = $this->fixupJsonDataType($lock);

        try {
            $isLocked = $this->isLocked();
        } catch (ParsingException $e) {
            $isLocked = false;
        }
        if (!$isLocked || $lock !== $this->getLockData()) {
            if ($write) {
                $this->lockFile->write($lock);
                $this->lockDataCache = null;
                $this->virtualFileWritten = false;
            } else {
                $this->virtualFileWritten = true;
                $this->lockDataCache = JsonFile::parseJson(JsonFile::encode($lock));
            }

            return true;
        }

        return false;
    }

    /**
     * Updates the lock file's hash in-place from a given composer.json's JsonFile
     *
     * This does not reload or require any packages, and retains the filemtime of the lock file.
     *
     * Use this only to update the lock file hash after updating a composer.json in ways that are guaranteed NOT to impact the dependency resolution.
     *
     * This is a risky method, use carefully.
     *
     * @param (callable(array<string, mixed>): array<string, mixed>)|null $dataProcessor Receives the lock data and can process it before it gets written to disk
     */
    public function updateHash(JsonFile $composerJson, ?callable $dataProcessor = null): void
    {
        $contents = file_get_contents($composerJson->getPath());
        if (false === $contents) {
            throw new \RuntimeException('Unable to read '.$composerJson->getPath().' contents to update the lock file hash.');
        }

        $lockMtime = filemtime($this->lockFile->getPath());
        $lockData = $this->lockFile->read();
        $lockData['content-hash'] = Locker::getContentHash($contents);
        if ($dataProcessor !== null) {
            $lockData = $dataProcessor($lockData);
        }

        $this->lockFile->write($this->fixupJsonDataType($lockData));
        $this->lockDataCache = null;
        $this->virtualFileWritten = false;
        if (is_int($lockMtime)) {
            @touch($this->lockFile->getPath(), $lockMtime);
        }
    }

    /**
     * Ensures correct data types and ordering for the JSON lock format
     *
     * @param array<mixed> $lockData
     * @return array<mixed>
     */
    private function fixupJsonDataType(array $lockData): array
    {
        foreach (['stability-flags', 'platform', 'platform-dev'] as $key) {
            if (isset($lockData[$key]) && is_array($lockData[$key]) && \count($lockData[$key]) === 0) {
                $lockData[$key] = new \stdClass();
            }
        }

        if (is_array($lockData['stability-flags'])) {
            ksort($lockData['stability-flags']);
        }

        return $lockData;
    }

    /**
     * @param PackageInterface[] $packages
     *
     * @return mixed[][]
     *
     * @phpstan-return list<array<string, mixed>>
     */
    private function lockPackages(array $packages): array
    {
        $locked = [];

        foreach ($packages as $package) {
            if ($package instanceof AliasPackage) {
                continue;
            }

            $name = $package->getPrettyName();
            $version = $package->getPrettyVersion();

            if (!$name || !$version) {
                throw new \LogicException(sprintf(
                    'Package "%s" has no version or name and can not be locked',
                    $package
                ));
            }

            $spec = $this->dumper->dump($package);
            unset($spec['version_normalized']);

            // always move time to the end of the package definition
            $time = $spec['time'] ?? null;
            unset($spec['time']);
            if ($package->isDev() && $package->getInstallationSource() === 'source') {
                // use the exact commit time of the current reference if it's a dev package
                $time = $this->getPackageTime($package) ?: $time;
            }
            if (null !== $time) {
                $spec['time'] = $time;
            }

            unset($spec['installation-source']);

            $locked[] = $spec;
        }

        usort($locked, static function ($a, $b) {
            $comparison = strcmp($a['name'], $b['name']);

            if (0 !== $comparison) {
                return $comparison;
            }

            // If it is the same package, compare the versions to make the order deterministic
            return strcmp($a['version'], $b['version']);
        });

        return $locked;
    }

    /**
     * Returns the packages's datetime for its source reference.
     *
     * @param  PackageInterface $package The package to scan.
     * @return string|null      The formatted datetime or null if none was found.
     */
    private function getPackageTime(PackageInterface $package): ?string
    {
        if (!function_exists('proc_open')) {
            return null;
        }

        $path = $this->installationManager->getInstallPath($package);
        if ($path === null) {
            return null;
        }
        $path = realpath($path);
        $sourceType = $package->getSourceType();
        $datetime = null;

        if ($path && in_array($sourceType, ['git', 'hg'])) {
            $sourceRef = $package->getSourceReference() ?: $package->getDistReference();
            switch ($sourceType) {
                case 'git':
                    GitUtil::cleanEnv($this->process);

                    $command = GitUtil::buildRevListCommand($this->process, array_merge(['-n1', '--format=%ct', (string) $sourceRef], GitUtil::getNoShowSignatureFlags($this->process)));
                    if (0 === $this->process->execute($command, $output, $path)) {
                        $output = trim(GitUtil::parseRevListOutput($output, $this->process));
                        if (Preg::isMatch('{^\s*\d+\s*$}', $output)) {
                            $datetime = new \DateTime('@'.trim($output), new \DateTimeZone('UTC'));
                        }
                    }
                    break;

                case 'hg':
                    if (0 === $this->process->execute(['hg', 'log', '--template', '{date|hgdate}', '-r', (string) $sourceRef], $output, $path) && Preg::isMatch('{^\s*(\d+)\s*}', $output, $match)) {
                        $datetime = new \DateTime('@'.$match[1], new \DateTimeZone('UTC'));
                    }
                    break;
            }
        }

        return $datetime ? $datetime->format(DATE_RFC3339) : null;
    }

    /**
     * @return array<string>
     */
    public function getMissingRequirementInfo(RootPackageInterface $package, bool $includeDev): array
    {
        $missingRequirementInfo = [];
        $missingRequirements = false;
        $sets = [['repo' => $this->getLockedRepository(false), 'method' => 'getRequires', 'description' => 'Required']];
        if ($includeDev === true) {
            $sets[] = ['repo' => $this->getLockedRepository(true), 'method' => 'getDevRequires', 'description' => 'Required (in require-dev)'];
        }
        $rootRepo = new RootPackageRepository(clone $package);

        foreach ($sets as $set) {
            $installedRepo = new InstalledRepository([$set['repo'], $rootRepo]);

            foreach (call_user_func([$package, $set['method']]) as $link) {
                if (PlatformRepository::isPlatformPackage($link->getTarget())) {
                    continue;
                }
                if ($link->getPrettyConstraint() === 'self.version') {
                    continue;
                }
                if ($installedRepo->findPackagesWithReplacersAndProviders($link->getTarget(), $link->getConstraint()) === []) {
                    $results = $installedRepo->findPackagesWithReplacersAndProviders($link->getTarget());

                    if ($results !== []) {
                        $provider = reset($results);
                        $description = $provider->getPrettyVersion();
                        if ($provider->getName() !== $link->getTarget()) {
                            foreach (['getReplaces' => 'replaced as %s by %s', 'getProvides' => 'provided as %s by %s'] as $method => $text) {
                                foreach (call_user_func([$provider, $method]) as $providerLink) {
                                    if ($providerLink->getTarget() === $link->getTarget()) {
                                        $description = sprintf($text, $providerLink->getPrettyConstraint(), $provider->getPrettyName().' '.$provider->getPrettyVersion());
                                        break 2;
                                    }
                                }
                            }
                        }
                        $missingRequirementInfo[] = '- ' . $set['description'].' package "' . $link->getTarget() . '" is in the lock file as "'.$description.'" but that does not satisfy your constraint "'.$link->getPrettyConstraint().'".';
                    } else {
                        $missingRequirementInfo[] = '- ' . $set['description'].' package "' . $link->getTarget() . '" is not present in the lock file.';
                    }
                    $missingRequirements = true;
                }
            }
        }

        if ($missingRequirements) {
            $missingRequirementInfo[] = 'This usually happens when composer files are incorrectly merged or the composer.json file is manually edited.';
            $missingRequirementInfo[] = 'Read more about correctly resolving merge conflicts https://getcomposer.org/doc/articles/resolving-merge-conflicts.md';
            $missingRequirementInfo[] = 'and prefer using the "require" command over editing the composer.json file directly https://getcomposer.org/doc/03-cli.md#require-r';
        }

        return $missingRequirementInfo;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Version;

use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Pcre\Preg;
use Composer\Repository\Vcs\HgDriver;
use Composer\IO\NullIO;
use Composer\Semver\VersionParser as SemverVersionParser;
use Composer\Util\Git as GitUtil;
use Composer\Util\HttpDownloader;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Util\Svn as SvnUtil;
use Symfony\Component\Process\Process;

/**
 * Try to guess the current version number based on different VCS configuration.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Samuel Roze <samuel.roze@gmail.com>
 *
 * @phpstan-type Version array{version: string, commit: string|null, pretty_version: string|null}|array{version: string, commit: string|null, pretty_version: string|null, feature_version: string|null, feature_pretty_version: string|null}
 */
class VersionGuesser
{
    /**
     * @var Config
     */
    private $config;

    /**
     * @var ProcessExecutor
     */
    private $process;

    /**
     * @var SemverVersionParser
     */
    private $versionParser;

    /**
     * @var IOInterface|null
     */
    private $io;

    public function __construct(Config $config, ProcessExecutor $process, SemverVersionParser $versionParser, ?IOInterface $io = null)
    {
        $this->config = $config;
        $this->process = $process;
        $this->versionParser = $versionParser;
        $this->io = $io;
    }

    /**
     * @param array<string, mixed> $packageConfig
     * @param string               $path Path to guess into
     *
     * @phpstan-return Version|null
     */
    public function guessVersion(array $packageConfig, string $path): ?array
    {
        if (!function_exists('proc_open')) {
            return null;
        }

        // bypass version guessing in bash completions as it takes time to create
        // new processes and the root version is usually not that important
        if (Platform::isInputCompletionProcess()) {
            return null;
        }

        $versionData = $this->guessGitVersion($packageConfig, $path);
        if (null !== $versionData['version']) {
            return $this->postprocess($versionData);
        }

        $versionData = $this->guessHgVersion($packageConfig, $path);
        if (null !== $versionData && null !== $versionData['version']) {
            return $this->postprocess($versionData);
        }

        $versionData = $this->guessFossilVersion($path);
        if (null !== $versionData['version']) {
            return $this->postprocess($versionData);
        }

        $versionData = $this->guessSvnVersion($packageConfig, $path);
        if (null !== $versionData && null !== $versionData['version']) {
            return $this->postprocess($versionData);
        }

        return null;
    }

    /**
     * @phpstan-param Version $versionData
     *
     * @phpstan-return Version
     */
    private function postprocess(array $versionData): array
    {
        if (!empty($versionData['feature_version']) && $versionData['feature_version'] === $versionData['version'] && $versionData['feature_pretty_version'] === $versionData['pretty_version']) {
            unset($versionData['feature_version'], $versionData['feature_pretty_version']);
        }

        if ('-dev' === substr($versionData['version'], -4) && Preg::isMatch('{\.9{7}}', $versionData['version'])) {
            $versionData['pretty_version'] = Preg::replace('{(\.9{7})+}', '.x', $versionData['version']);
        }

        if (!empty($versionData['feature_version']) && '-dev' === substr($versionData['feature_version'], -4) && Preg::isMatch('{\.9{7}}', $versionData['feature_version'])) {
            $versionData['feature_pretty_version'] = Preg::replace('{(\.9{7})+}', '.x', $versionData['feature_version']);
        }

        return $versionData;
    }

    /**
     * @param array<string, mixed> $packageConfig
     *
     * @return array{version: string|null, commit: string|null, pretty_version: string|null, feature_version?: string|null, feature_pretty_version?: string|null}
     */
    private function guessGitVersion(array $packageConfig, string $path): array
    {
        GitUtil::cleanEnv($this->process);
        $commit = null;
        $version = null;
        $prettyVersion = null;
        $featureVersion = null;
        $featurePrettyVersion = null;
        $isDetached = false;

        // try to fetch current version from git branch
        if (0 === $this->process->execute(['git', 'branch', '-a', '--no-color', '--no-abbrev', '-v'], $output, $path)) {
            $branches = [];
            $isFeatureBranch = false;

            // find current branch and collect all branch names
            foreach ($this->process->splitLines($output) as $branch) {
                if ($branch && Preg::isMatchStrictGroups('{^(?:\* ) *(\(no branch\)|\(detached from \S+\)|\(HEAD detached at \S+\)|\S+) *([a-f0-9]+) .*$}', $branch, $match)) {
                    if (
                        $match[1] === '(no branch)'
                        || strpos($match[1], '(detached ') === 0
                        || strpos($match[1], '(HEAD detached at') === 0
                    ) {
                        $version = 'dev-' . $match[2];
                        $prettyVersion = $version;
                        $isFeatureBranch = true;
                        $isDetached = true;
                    } else {
                        $version = $this->versionParser->normalizeBranch($match[1]);
                        $prettyVersion = 'dev-' . $match[1];
                        $isFeatureBranch = $this->isFeatureBranch($packageConfig, $match[1]);
                    }

                    $commit = $match[2];
                }

                if ($branch && !Preg::isMatchStrictGroups('{^ *.+/HEAD }', $branch)) {
                    if (Preg::isMatchStrictGroups('{^(?:\* )? *((?:remotes/(?:origin|upstream)/)?[^\s/]+) *([a-f0-9]+) .*$}', $branch, $match)) {
                        $branches[] = $match[1];
                    }
                }
            }

            if ($isFeatureBranch) {
                $featureVersion = $version;
                $featurePrettyVersion = $prettyVersion;

                // try to find the best (nearest) version branch to assume this feature's version
                $result = $this->guessFeatureVersion($packageConfig, $version, $branches, ['git', 'rev-list', '%candidate%..%branch%'], $path);
                $version = $result['version'];
                $prettyVersion = $result['pretty_version'];
            }
        }
        GitUtil::checkForRepoOwnershipError($this->process->getErrorOutput(), $path, $this->io);

        if (!$version || $isDetached) {
            $result = $this->versionFromGitTags($path);
            if ($result) {
                $version = $result['version'];
                $prettyVersion = $result['pretty_version'];
                $featureVersion = null;
                $featurePrettyVersion = null;
            }
        }

        if (null === $commit) {
            $command = GitUtil::buildRevListCommand($this->process, array_merge(['--format=%H', '-n1', 'HEAD'], GitUtil::getNoShowSignatureFlags($this->process)));
            if (0 === $this->process->execute($command, $output, $path)) {
                $commit = trim(GitUtil::parseRevListOutput($output, $this->process)) ?: null;
            }
        }

        if ($featureVersion) {
            return ['version' => $version, 'commit' => $commit, 'pretty_version' => $prettyVersion, 'feature_version' => $featureVersion, 'feature_pretty_version' => $featurePrettyVersion];
        }

        return ['version' => $version, 'commit' => $commit, 'pretty_version' => $prettyVersion];
    }

    /**
     * @return array{version: string, pretty_version: string}|null
     */
    private function versionFromGitTags(string $path): ?array
    {
        // try to fetch current version from git tags
        if (0 === $this->process->execute(['git', 'describe', '--exact-match', '--tags'], $output, $path)) {
            try {
                $version = $this->versionParser->normalize(trim($output));

                return ['version' => $version, 'pretty_version' => trim($output)];
            } catch (\Exception $e) {
            }
        }

        return null;
    }

    /**
     * @param array<string, mixed> $packageConfig
     *
     * @return array{version: string|null, commit: ''|null, pretty_version: string|null, feature_version?: string|null, feature_pretty_version?: string|null}|null
     */
    private function guessHgVersion(array $packageConfig, string $path): ?array
    {
        // try to fetch current version from hg branch
        if (0 === $this->process->execute(['hg', 'branch'], $output, $path)) {
            $branch = trim($output);
            $version = $this->versionParser->normalizeBranch($branch);
            $isFeatureBranch = 0 === strpos($version, 'dev-');

            if (VersionParser::DEFAULT_BRANCH_ALIAS === $version) {
                return ['version' => $version, 'commit' => null, 'pretty_version' => 'dev-'.$branch];
            }

            if (!$isFeatureBranch) {
                return ['version' => $version, 'commit' => null, 'pretty_version' => $version];
            }

            // re-use the HgDriver to fetch branches (this properly includes bookmarks)
            $io = new NullIO();
            $driver = new HgDriver(['url' => $path], $io, $this->config, new HttpDownloader($io, $this->config), $this->process);
            $branches = array_map('strval', array_keys($driver->getBranches()));

            // try to find the best (nearest) version branch to assume this feature's version
            $result = $this->guessFeatureVersion($packageConfig, $version, $branches, ['hg', 'log', '-r', 'not ancestors(\'%candidate%\') and ancestors(\'%branch%\')', '--template', '"{node}\\n"'], $path);
            $result['commit'] = '';
            $result['feature_version'] = $version;
            $result['feature_pretty_version'] = $version;

            return $result;
        }

        return null;
    }

    /**
     * @param array<string, mixed>     $packageConfig
     * @param list<string>             $branches
     * @param list<string>             $scmCmdline
     *
     * @return array{version: string|null, pretty_version: string|null}
     */
    private function guessFeatureVersion(array $packageConfig, ?string $version, array $branches, array $scmCmdline, string $path): array
    {
        $prettyVersion = $version;

        // ignore feature branches if they have no branch-alias or self.version is used
        // and find the branch they came from to use as a version instead
        if (!isset($packageConfig['extra']['branch-alias'][$version])
            || strpos(json_encode($packageConfig), '"self.version"')
        ) {
            $branch = Preg::replace('{^dev-}', '', $version);
            $length = PHP_INT_MAX;

            // return directly, if branch is configured to be non-feature branch
            if (!$this->isFeatureBranch($packageConfig, $branch)) {
                return ['version' => $version, 'pretty_version' => $prettyVersion];
            }

            // sort local branches first then remote ones
            // and sort numeric branches below named ones, to make sure if the branch has the same distance from main and 1.10 and 1.9 for example, 1.9 is picked
            // and sort using natural sort so that 1.10 will appear before 1.9
            usort($branches, static function ($a, $b): int {
                $aRemote = 0 === strpos($a, 'remotes/');
                $bRemote = 0 === strpos($b, 'remotes/');

                if ($aRemote !== $bRemote) {
                    return $aRemote ? 1 : -1;
                }

                return strnatcasecmp($b, $a);
            });

            $promises = [];
            $this->process->setMaxJobs(30);
            try {
                $lastIndex = -1;
                foreach ($branches as $index => $candidate) {
                    $candidateVersion = Preg::replace('{^remotes/\S+/}', '', $candidate);

                    // do not compare against itself or other feature branches
                    if ($candidate === $branch || $this->isFeatureBranch($packageConfig, $candidateVersion)) {
                        continue;
                    }

                    $cmdLine = array_map(static function (string $component) use ($candidate, $branch) {
                        return str_replace(['%candidate%', '%branch%'], [$candidate, $branch], $component);
                    }, $scmCmdline);
                    $promises[] = $this->process->executeAsync($cmdLine, $path)->then(function (Process $process) use (&$lastIndex, $index, &$length, &$version, &$prettyVersion, $candidateVersion, &$promises): void {
                        if (!$process->isSuccessful()) {
                            return;
                        }

                        $output = $process->getOutput();
                        // overwrite existing if we have a shorter diff, or we have an equal diff and an index that comes later in the array (i.e. older version)
                        // as newer versions typically have more commits, if the feature branch is based on a newer branch it should have a longer diff to the old version
                        // but if it doesn't and they have equal diffs, then it probably is based on the old version
                        if (strlen($output) < $length || (strlen($output) === $length && $lastIndex < $index)) {
                            $lastIndex = $index;
                            $length = strlen($output);
                            $version = $this->versionParser->normalizeBranch($candidateVersion);
                            $prettyVersion = 'dev-' . $candidateVersion;
                            if ($length === 0) {
                                foreach ($promises as $promise) {
                                    // to support react/promise 2.x we wrap the promise in a resolve() call for safety
                                    \React\Promise\resolve($promise)->cancel();
                                }
                            }
                        }
                    });
                }

                $this->process->wait();
            } finally {
                $this->process->resetMaxJobs();
            }
        }

        return ['version' => $version, 'pretty_version' => $prettyVersion];
    }

    /**
     * @param array<string, mixed> $packageConfig
     */
    private function isFeatureBranch(array $packageConfig, ?string $branchName): bool
    {
        $nonFeatureBranches = '';
        if (!empty($packageConfig['non-feature-branches'])) {
            $nonFeatureBranches = implode('|', $packageConfig['non-feature-branches']);
        }

        return !Preg::isMatch('{^(' . $nonFeatureBranches . '|master|main|latest|next|current|support|tip|trunk|default|develop|\d+\..+)$}', $branchName, $match);
    }

    /**
     * @return array{version: string|null, commit: '', pretty_version: string|null}
     */
    private function guessFossilVersion(string $path): array
    {
        $version = null;
        $prettyVersion = null;

        // try to fetch current version from fossil
        if (0 === $this->process->execute(['fossil', 'branch', 'list'], $output, $path)) {
            $branch = trim($output);
            $version = $this->versionParser->normalizeBranch($branch);
            $prettyVersion = 'dev-' . $branch;
        }

        // try to fetch current version from fossil tags
        if (0 === $this->process->execute(['fossil', 'tag', 'list'], $output, $path)) {
            try {
                $version = $this->versionParser->normalize(trim($output));
                $prettyVersion = trim($output);
            } catch (\Exception $e) {
            }
        }

        return ['version' => $version, 'commit' => '', 'pretty_version' => $prettyVersion];
    }

    /**
     * @param array<string, mixed> $packageConfig
     *
     * @return array{version: string, commit: '', pretty_version: string}|null
     */
    private function guessSvnVersion(array $packageConfig, string $path): ?array
    {
        SvnUtil::cleanEnv();

        // try to fetch current version from svn
        if (0 === $this->process->execute(['svn', 'info', '--xml'], $output, $path)) {
            $trunkPath = isset($packageConfig['trunk-path']) ? preg_quote($packageConfig['trunk-path'], '#') : 'trunk';
            $branchesPath = isset($packageConfig['branches-path']) ? preg_quote($packageConfig['branches-path'], '#') : 'branches';
            $tagsPath = isset($packageConfig['tags-path']) ? preg_quote($packageConfig['tags-path'], '#') : 'tags';

            $urlPattern = '#<url>.*/(' . $trunkPath . '|(' . $branchesPath . '|' . $tagsPath . ')/(.*))</url>#';

            if (Preg::isMatch($urlPattern, $output, $matches)) {
                if (isset($matches[2], $matches[3]) && ($branchesPath === $matches[2] || $tagsPath === $matches[2])) {
                    // we are in a branches path
                    $version = $this->versionParser->normalizeBranch($matches[3]);
                    $prettyVersion = 'dev-' . $matches[3];

                    return ['version' => $version, 'commit' => '', 'pretty_version' => $prettyVersion];
                }

                assert(is_string($matches[1]));
                $prettyVersion = trim($matches[1]);
                if ($prettyVersion === 'trunk') {
                    $version = 'dev-trunk';
                } else {
                    $version = $this->versionParser->normalize($prettyVersion);
                }

                return ['version' => $version, 'commit' => '', 'pretty_version' => $prettyVersion];
            }
        }

        return null;
    }

    public function getRootVersionFromEnv(): string
    {
        $version = Platform::getEnv('COMPOSER_ROOT_VERSION');
        if (!is_string($version) || $version === '') {
            throw new \RuntimeException('COMPOSER_ROOT_VERSION not set or empty');
        }
        if (Preg::isMatch('{^(\d+(?:\.\d+)*)-dev$}i', $version, $match)) {
            $version = $match[1].'.x-dev';
        }

        return $version;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Version;

use Composer\Package\BasePackage;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class StabilityFilter
{
    /**
     * Checks if any of the provided package names in the given stability match the configured acceptable stability and flags
     *
     * @param int[] $acceptableStabilities array of stability => BasePackage::STABILITY_* value
     * @phpstan-param array<key-of<BasePackage::STABILITIES>, BasePackage::STABILITY_*> $acceptableStabilities
     * @param int[] $stabilityFlags an array of package name => BasePackage::STABILITY_* value
     * @phpstan-param array<string, BasePackage::STABILITY_*> $stabilityFlags
     * @param  string[] $names     The package name(s) to check for stability flags
     * @param  key-of<BasePackage::STABILITIES> $stability one of 'stable', 'RC', 'beta', 'alpha' or 'dev'
     * @return bool     true if any package name is acceptable
     */
    public static function isPackageAcceptable(array $acceptableStabilities, array $stabilityFlags, array $names, string $stability): bool
    {
        foreach ($names as $name) {
            // allow if package matches the package-specific stability flag
            if (isset($stabilityFlags[$name])) {
                if (BasePackage::STABILITIES[$stability] <= $stabilityFlags[$name]) {
                    return true;
                }
            } elseif (isset($acceptableStabilities[$stability])) {
                // allow if package matches the global stability requirement and has no exception
                return true;
            }
        }

        return false;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Version;

use Composer\Package\PackageInterface;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Dumper\ArrayDumper;
use Composer\Pcre\Preg;
use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Intervals;
use Composer\Util\Platform;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @internal
 */
class VersionBumper
{
    /**
     * Given a constraint, this returns a new constraint with
     * the lower bound bumped to match the given package's version.
     *
     * For example:
     *  * ^1.0 + 1.2.1            -> ^1.2.1
     *  * ^1.2 + 1.2.0            -> ^1.2
     *  * ^1.2.0 + 1.3.0          -> ^1.3.0
     *  * ^1.2 || ^2.3 + 1.3.0    -> ^1.3 || ^2.3
     *  * ^1.2 || ^2.3 + 2.4.0    -> ^1.2 || ^2.4
     *  * ^3@dev + 3.2.99999-dev  -> ^3.2@dev
     *  * ~2 + 2.0-beta.1         -> ~2
     *  * ~2.0.0 + 2.0.3          -> ~2.0.3
     *  * ~2.0 + 2.0.3            -> ^2.0.3
     *  * dev-master + dev-master -> dev-master
     *  * * + 1.2.3               -> >=1.2.3
     */
    public function bumpRequirement(ConstraintInterface $constraint, PackageInterface $package): string
    {
        $parser = new VersionParser();
        $prettyConstraint = $constraint->getPrettyString();
        if (str_starts_with($constraint->getPrettyString(), 'dev-')) {
            return $prettyConstraint;
        }

        $version = $package->getVersion();
        if (str_starts_with($package->getVersion(), 'dev-')) {
            $loader = new ArrayLoader($parser);
            $dumper = new ArrayDumper();
            $extra = $loader->getBranchAlias($dumper->dump($package));

            // dev packages without branch alias cannot be processed
            if (null === $extra || $extra === VersionParser::DEFAULT_BRANCH_ALIAS) {
                return $prettyConstraint;
            }

            $version = $extra;
        }

        $intervals = Intervals::get($constraint);

        // complex constraints with branch names are not bumped
        if (\count($intervals['branches']['names']) > 0) {
            return $prettyConstraint;
        }

        $major = Preg::replace('{^([1-9][0-9]*|0\.\d+).*}', '$1', $version);
        $versionWithoutSuffix = Preg::replace('{(?:\.(?:0|9999999))+(-dev)?$}', '', $version);
        $newPrettyConstraint = '^'.$versionWithoutSuffix;

        // not a simple stable version, abort
        if (!Preg::isMatch('{^\^\d+(\.\d+)*$}', $newPrettyConstraint)) {
            return $prettyConstraint;
        }

        $pattern = '{
            (?<=,|\ |\||^) # leading separator
            (?P<constraint>
                \^v?'.$major.'(?:\.\d+)* # e.g. ^2.anything
                | ~v?'.$major.'(?:\.\d+){1,3} # e.g. ~2.2 or ~2.2.2 or ~2.2.2.2
                | v?'.$major.'(?:\.[*x])+ # e.g. 2.* or 2.*.* or 2.x.x.x etc
                | >=v?\d(?:\.\d+)* # e.g. >=2 or >=1.2 etc
                | \* # full wildcard
            )
            (?=,|$|\ |\||@) # trailing separator
        }x';
        if (Preg::isMatchAllWithOffsets($pattern, $prettyConstraint, $matches)) {
            $modified = $prettyConstraint;
            foreach (array_reverse($matches['constraint']) as $match) {
                assert(is_string($match[0]));
                $suffix = '';
                if (substr_count($match[0], '.') === 2 && substr_count($versionWithoutSuffix, '.') === 1) {
                    $suffix = '.0';
                }
                if (str_starts_with($match[0], '~') && substr_count($match[0], '.') !== 1) {
                    // take as many version bits from the current version as we have in the constraint to bump it without making it more specific
                    $versionBits = explode('.', $versionWithoutSuffix);
                    $versionBits = array_pad($versionBits, substr_count($match[0], '.') + 1, '0');
                    $replacement = '~'.implode('.', array_slice($versionBits, 0, substr_count($match[0], '.') + 1));
                } elseif ($match[0] === '*' || str_starts_with($match[0], '>=')) {
                    $replacement = '>='.$versionWithoutSuffix.$suffix;
                } else {
                    $replacement = $newPrettyConstraint.$suffix;
                }
                $modified = substr_replace($modified, $replacement, $match[1], Platform::strlen($match[0]));
            }

            // if it is strictly equal to the previous one then no need to change anything
            $newConstraint = $parser->parseConstraints($modified);
            if (Intervals::isSubsetOf($newConstraint, $constraint) && Intervals::isSubsetOf($constraint, $newConstraint)) {
                return $prettyConstraint;
            }

            return $modified;
        }

        return $prettyConstraint;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Version;

use Composer\Pcre\Preg;
use Composer\Repository\PlatformRepository;
use Composer\Semver\VersionParser as SemverVersionParser;
use Composer\Semver\Semver;
use Composer\Semver\Constraint\ConstraintInterface;

class VersionParser extends SemverVersionParser
{
    public const DEFAULT_BRANCH_ALIAS = '9999999-dev';

    /** @var array<string, ConstraintInterface> Constraint parsing cache */
    private static $constraints = [];

    /**
     * @inheritDoc
     */
    public function parseConstraints($constraints): ConstraintInterface
    {
        if (!isset(self::$constraints[$constraints])) {
            self::$constraints[$constraints] = parent::parseConstraints($constraints);
        }

        return self::$constraints[$constraints];
    }

    /**
     * Parses an array of strings representing package/version pairs.
     *
     * The parsing results in an array of arrays, each of which
     * contain a 'name' key with value and optionally a 'version' key with value.
     *
     * @param string[] $pairs a set of package/version pairs separated by ":", "=" or " "
     *
     * @return list<array{name: string, version?: string}>
     */
    public function parseNameVersionPairs(array $pairs): array
    {
        $pairs = array_values($pairs);
        $result = [];

        for ($i = 0, $count = count($pairs); $i < $count; $i++) {
            $pair = Preg::replace('{^([^=: ]+)[=: ](.*)$}', '$1 $2', trim($pairs[$i]));
            if (false === strpos($pair, ' ') && isset($pairs[$i + 1]) && false === strpos($pairs[$i + 1], '/') && !Preg::isMatch('{(?<=[a-z0-9_/-])\*|\*(?=[a-z0-9_/-])}i', $pairs[$i + 1]) && !PlatformRepository::isPlatformPackage($pairs[$i + 1])) {
                $pair .= ' '.$pairs[$i + 1];
                $i++;
            }

            if (strpos($pair, ' ')) {
                [$name, $version] = explode(' ', $pair, 2);
                $result[] = ['name' => $name, 'version' => $version];
            } else {
                $result[] = ['name' => $pair];
            }
        }

        return $result;
    }

    public static function isUpgrade(string $normalizedFrom, string $normalizedTo): bool
    {
        if ($normalizedFrom === $normalizedTo) {
            return true;
        }

        if (in_array($normalizedFrom, ['dev-master', 'dev-trunk', 'dev-default'], true)) {
            $normalizedFrom = VersionParser::DEFAULT_BRANCH_ALIAS;
        }
        if (in_array($normalizedTo, ['dev-master', 'dev-trunk', 'dev-default'], true)) {
            $normalizedTo = VersionParser::DEFAULT_BRANCH_ALIAS;
        }

        if (strpos($normalizedFrom, 'dev-') === 0 || strpos($normalizedTo, 'dev-') === 0) {
            return true;
        }

        $sorted = Semver::sort([$normalizedTo, $normalizedFrom]);

        return $sorted[0] === $normalizedFrom;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Version;

use Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter;
use Composer\Filter\PlatformRequirementFilter\IgnoreListPlatformRequirementFilter;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
use Composer\IO\IOInterface;
use Composer\Package\BasePackage;
use Composer\Package\AliasPackage;
use Composer\Package\PackageInterface;
use Composer\Composer;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Dumper\ArrayDumper;
use Composer\Pcre\Preg;
use Composer\Repository\RepositorySet;
use Composer\Repository\PlatformRepository;
use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\ConstraintInterface;

/**
 * Selects the best possible version for a package
 *
 * @author Ryan Weaver <ryan@knpuniversity.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class VersionSelector
{
    /** @var RepositorySet */
    private $repositorySet;

    /** @var array<string, ConstraintInterface[]> */
    private $platformConstraints = [];

    /** @var VersionParser */
    private $parser;

    /**
     * @param PlatformRepository $platformRepo If passed in, the versions found will be filtered against their requirements to eliminate any not matching the current platform packages
     */
    public function __construct(RepositorySet $repositorySet, ?PlatformRepository $platformRepo = null)
    {
        $this->repositorySet = $repositorySet;
        if ($platformRepo) {
            foreach ($platformRepo->getPackages() as $package) {
                $this->platformConstraints[$package->getName()][] = new Constraint('==', $package->getVersion());
            }
        }
    }

    /**
     * Given a package name and optional version, returns the latest PackageInterface
     * that matches.
     *
     * @param PlatformRequirementFilterInterface|bool|string[] $platformRequirementFilter
     * @param IOInterface|null                                 $io                        If passed, warnings will be output there in case versions cannot be selected due to platform requirements
     * @param callable(PackageInterface):bool|bool             $showWarnings
     * @return PackageInterface|false
     */
    public function findBestCandidate(string $packageName, ?string $targetPackageVersion = null, string $preferredStability = 'stable', $platformRequirementFilter = null, int $repoSetFlags = 0, ?IOInterface $io = null, $showWarnings = true)
    {
        if (!isset(BasePackage::STABILITIES[$preferredStability])) {
            // If you get this, maybe you are still relying on the Composer 1.x signature where the 3rd arg was the php version
            throw new \UnexpectedValueException('Expected a valid stability name as 3rd argument, got '.$preferredStability);
        }

        if (null === $platformRequirementFilter) {
            $platformRequirementFilter = PlatformRequirementFilterFactory::ignoreNothing();
        } elseif (!($platformRequirementFilter instanceof PlatformRequirementFilterInterface)) {
            trigger_error('VersionSelector::findBestCandidate with ignored platform reqs as bool|array is deprecated since Composer 2.2, use an instance of PlatformRequirementFilterInterface instead.', E_USER_DEPRECATED);
            $platformRequirementFilter = PlatformRequirementFilterFactory::fromBoolOrList($platformRequirementFilter);
        }

        $constraint = $targetPackageVersion ? $this->getParser()->parseConstraints($targetPackageVersion) : null;
        $candidates = $this->repositorySet->findPackages(strtolower($packageName), $constraint, $repoSetFlags);

        $minPriority = BasePackage::STABILITIES[$preferredStability];
        usort($candidates, static function (PackageInterface $a, PackageInterface $b) use ($minPriority) {
            $aPriority = $a->getStabilityPriority();
            $bPriority = $b->getStabilityPriority();

            // A is less stable than our preferred stability,
            // and B is more stable than A, select B
            if ($minPriority < $aPriority && $bPriority < $aPriority) {
                return 1;
            }

            // A is less stable than our preferred stability,
            // and B is less stable than A, select A
            if ($minPriority < $aPriority && $aPriority < $bPriority) {
                return -1;
            }

            // A is more stable than our preferred stability,
            // and B is less stable than preferred stability, select A
            if ($minPriority >= $aPriority && $minPriority < $bPriority) {
                return -1;
            }

            // select highest version of the two
            return version_compare($b->getVersion(), $a->getVersion());
        });

        if (count($this->platformConstraints) > 0 && !($platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter)) {
            /** @var array<string, true> $alreadyWarnedNames */
            $alreadyWarnedNames = [];
            /** @var array<string, true> $alreadySeenNames */
            $alreadySeenNames = [];

            foreach ($candidates as $pkg) {
                $reqs = $pkg->getRequires();
                $skip = false;
                foreach ($reqs as $name => $link) {
                    if (!PlatformRepository::isPlatformPackage($name) || $platformRequirementFilter->isIgnored($name)) {
                        continue;
                    }
                    if (isset($this->platformConstraints[$name])) {
                        foreach ($this->platformConstraints[$name] as $providedConstraint) {
                            if ($link->getConstraint()->matches($providedConstraint)) {
                                // constraint satisfied, go to next require
                                continue 2;
                            }
                            if ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter && $platformRequirementFilter->isUpperBoundIgnored($name)) {
                                $filteredConstraint = $platformRequirementFilter->filterConstraint($name, $link->getConstraint());
                                if ($filteredConstraint->matches($providedConstraint)) {
                                    // constraint satisfied with the upper bound ignored, go to next require
                                    continue 2;
                                }
                            }
                        }

                        // constraint not satisfied
                        $reason = 'is not satisfied by your platform';
                    } else {
                        // Package requires a platform package that is unknown on current platform.
                        // It means that current platform cannot validate this constraint and so package is not installable.
                        $reason = 'is missing from your platform';
                    }

                    $isLatestVersion = !isset($alreadySeenNames[$pkg->getName()]);
                    $alreadySeenNames[$pkg->getName()] = true;
                    if ($io !== null && ($showWarnings === true || (is_callable($showWarnings) && $showWarnings($pkg)))) {
                        $isFirstWarning = !isset($alreadyWarnedNames[$pkg->getName().'/'.$link->getTarget()]);
                        $alreadyWarnedNames[$pkg->getName().'/'.$link->getTarget()] = true;
                        $latest = $isLatestVersion ? "'s latest version" : '';
                        $io->writeError(
                            '<warning>Cannot use '.$pkg->getPrettyName().$latest.' '.$pkg->getPrettyVersion().' as it '.$link->getDescription().' '.$link->getTarget().' '.$link->getPrettyConstraint().' which '.$reason.'.</>',
                            true,
                            $isFirstWarning ? IOInterface::NORMAL : IOInterface::VERBOSE
                        );
                    }

                    // skip candidate
                    $skip = true;
                }

                if ($skip) {
                    continue;
                }

                $package = $pkg;
                break;
            }
        } else {
            $package = count($candidates) > 0 ? $candidates[0] : null;
        }

        if (!isset($package)) {
            return false;
        }

        // if we end up with 9999999-dev as selected package, make sure we use the original version instead of the alias
        if ($package instanceof AliasPackage && $package->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) {
            $package = $package->getAliasOf();
        }

        return $package;
    }

    /**
     * Given a concrete version, this returns a ^ constraint (when possible)
     * that should be used, for example, in composer.json.
     *
     * For example:
     *  * 1.2.1         -> ^1.2
     *  * 1.2.1.2       -> ^1.2
     *  * 1.2           -> ^1.2
     *  * v3.2.1        -> ^3.2
     *  * 2.0-beta.1    -> ^2.0@beta
     *  * dev-master    -> ^2.1@dev      (dev version with alias)
     *  * dev-master    -> dev-master    (dev versions are untouched)
     */
    public function findRecommendedRequireVersion(PackageInterface $package): string
    {
        // Extensions which are versioned in sync with PHP should rather be required as "*" to simplify
        // the requires and have only one required version to change when bumping the php requirement
        if (0 === strpos($package->getName(), 'ext-')) {
            $phpVersion = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION;
            $extVersion = implode('.', array_slice(explode('.', $package->getVersion()), 0, 3));
            if ($phpVersion === $extVersion) {
                return '*';
            }
        }

        $version = $package->getVersion();
        if (!$package->isDev()) {
            return $this->transformVersion($version, $package->getPrettyVersion(), $package->getStability());
        }

        $loader = new ArrayLoader($this->getParser());
        $dumper = new ArrayDumper();
        $extra = $loader->getBranchAlias($dumper->dump($package));
        if ($extra && $extra !== VersionParser::DEFAULT_BRANCH_ALIAS) {
            $extra = Preg::replace('{^(\d+\.\d+\.\d+)(\.9999999)-dev$}', '$1.0', $extra, -1, $count);
            if ($count > 0) {
                $extra = str_replace('.9999999', '.0', $extra);

                return $this->transformVersion($extra, $extra, 'dev');
            }
        }

        return $package->getPrettyVersion();
    }

    private function transformVersion(string $version, string $prettyVersion, string $stability): string
    {
        // attempt to transform 2.1.1 to 2.1
        // this allows you to upgrade through minor versions
        $semanticVersionParts = explode('.', $version);

        // check to see if we have a semver-looking version
        if (count($semanticVersionParts) === 4 && Preg::isMatch('{^\d+\D?}', $semanticVersionParts[3])) {
            // remove the last parts (i.e. the patch version number and any extra)
            if ($semanticVersionParts[0] === '0') {
                unset($semanticVersionParts[3]);
            } else {
                unset($semanticVersionParts[2], $semanticVersionParts[3]);
            }
            $version = implode('.', $semanticVersionParts);
        } else {
            return $prettyVersion;
        }

        // append stability flag if not default
        if ($stability !== 'stable') {
            $version .= '@'.$stability;
        }

        // 2.1 -> ^2.1
        return '^' . $version;
    }

    private function getParser(): VersionParser
    {
        if ($this->parser === null) {
            $this->parser = new VersionParser();
        }

        return $this->parser;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package;

use Composer\Package\Version\VersionParser;
use Composer\Pcre\Preg;
use Composer\Util\ComposerMirror;

/**
 * Core package definitions that are needed to resolve dependencies and install packages
 *
 * @author Nils Adermann <naderman@naderman.de>
 *
 * @phpstan-import-type AutoloadRules from PackageInterface
 * @phpstan-import-type DevAutoloadRules from PackageInterface
 * @phpstan-import-type PhpExtConfig from PackageInterface
 */
class Package extends BasePackage
{
    /** @var string */
    protected $type;
    /** @var ?string */
    protected $targetDir;
    /** @var 'source'|'dist'|null */
    protected $installationSource;
    /** @var ?string */
    protected $sourceType;
    /** @var ?string */
    protected $sourceUrl;
    /** @var ?string */
    protected $sourceReference;
    /** @var ?list<array{url: non-empty-string, preferred: bool}> */
    protected $sourceMirrors;
    /** @var ?non-empty-string */
    protected $distType;
    /** @var ?non-empty-string */
    protected $distUrl;
    /** @var ?string */
    protected $distReference;
    /** @var ?string */
    protected $distSha1Checksum;
    /** @var ?list<array{url: non-empty-string, preferred: bool}> */
    protected $distMirrors;
    /** @var string */
    protected $version;
    /** @var string */
    protected $prettyVersion;
    /** @var ?\DateTimeInterface */
    protected $releaseDate;
    /** @var mixed[] */
    protected $extra = [];
    /** @var string[] */
    protected $binaries = [];
    /** @var bool */
    protected $dev;
    /**
     * @var string
     * @phpstan-var 'stable'|'RC'|'beta'|'alpha'|'dev'
     */
    protected $stability;
    /** @var ?string */
    protected $notificationUrl;

    /** @var array<string, Link> */
    protected $requires = [];
    /** @var array<string, Link> */
    protected $conflicts = [];
    /** @var array<string, Link> */
    protected $provides = [];
    /** @var array<string, Link> */
    protected $replaces = [];
    /** @var array<string, Link> */
    protected $devRequires = [];
    /** @var array<string, string> */
    protected $suggests = [];
    /**
     * @var array
     * @phpstan-var AutoloadRules
     */
    protected $autoload = [];
    /**
     * @var array
     * @phpstan-var DevAutoloadRules
     */
    protected $devAutoload = [];
    /** @var string[] */
    protected $includePaths = [];
    /** @var bool */
    protected $isDefaultBranch = false;
    /** @var mixed[] */
    protected $transportOptions = [];
    /**
     * @var array|null
     * @phpstan-var PhpExtConfig|null
     */
    protected $phpExt = null;

    /**
     * Creates a new in memory package.
     *
     * @param string $name          The package's name
     * @param string $version       The package's version
     * @param string $prettyVersion The package's non-normalized version
     */
    public function __construct(string $name, string $version, string $prettyVersion)
    {
        parent::__construct($name);

        $this->version = $version;
        $this->prettyVersion = $prettyVersion;

        $this->stability = VersionParser::parseStability($version);
        $this->dev = $this->stability === 'dev';
    }

    /**
     * @inheritDoc
     */
    public function isDev(): bool
    {
        return $this->dev;
    }

    public function setType(string $type): void
    {
        $this->type = $type;
    }

    /**
     * @inheritDoc
     */
    public function getType(): string
    {
        return $this->type ?: 'library';
    }

    /**
     * @inheritDoc
     */
    public function getStability(): string
    {
        return $this->stability;
    }

    public function setTargetDir(?string $targetDir): void
    {
        $this->targetDir = $targetDir;
    }

    /**
     * @inheritDoc
     */
    public function getTargetDir(): ?string
    {
        if (null === $this->targetDir) {
            return null;
        }

        return ltrim(Preg::replace('{ (?:^|[\\\\/]+) \.\.? (?:[\\\\/]+|$) (?:\.\.? (?:[\\\\/]+|$) )*}x', '/', $this->targetDir), '/');
    }

    /**
     * @param mixed[] $extra
     */
    public function setExtra(array $extra): void
    {
        $this->extra = $extra;
    }

    /**
     * @inheritDoc
     */
    public function getExtra(): array
    {
        return $this->extra;
    }

    /**
     * @param string[] $binaries
     */
    public function setBinaries(array $binaries): void
    {
        $this->binaries = $binaries;
    }

    /**
     * @inheritDoc
     */
    public function getBinaries(): array
    {
        return $this->binaries;
    }

    /**
     * @inheritDoc
     */
    public function setInstallationSource(?string $type): void
    {
        $this->installationSource = $type;
    }

    /**
     * @inheritDoc
     */
    public function getInstallationSource(): ?string
    {
        return $this->installationSource;
    }

    public function setSourceType(?string $type): void
    {
        $this->sourceType = $type;
    }

    /**
     * @inheritDoc
     */
    public function getSourceType(): ?string
    {
        return $this->sourceType;
    }

    public function setSourceUrl(?string $url): void
    {
        $this->sourceUrl = $url;
    }

    /**
     * @inheritDoc
     */
    public function getSourceUrl(): ?string
    {
        return $this->sourceUrl;
    }

    public function setSourceReference(?string $reference): void
    {
        $this->sourceReference = $reference;
    }

    /**
     * @inheritDoc
     */
    public function getSourceReference(): ?string
    {
        return $this->sourceReference;
    }

    public function setSourceMirrors(?array $mirrors): void
    {
        $this->sourceMirrors = $mirrors;
    }

    /**
     * @inheritDoc
     */
    public function getSourceMirrors(): ?array
    {
        return $this->sourceMirrors;
    }

    /**
     * @inheritDoc
     */
    public function getSourceUrls(): array
    {
        return $this->getUrls($this->sourceUrl, $this->sourceMirrors, $this->sourceReference, $this->sourceType, 'source');
    }

    public function setDistType(?string $type): void
    {
        $this->distType = $type === '' ? null : $type;
    }

    /**
     * @inheritDoc
     */
    public function getDistType(): ?string
    {
        return $this->distType;
    }

    public function setDistUrl(?string $url): void
    {
        $this->distUrl = $url === '' ? null : $url;
    }

    /**
     * @inheritDoc
     */
    public function getDistUrl(): ?string
    {
        return $this->distUrl;
    }

    public function setDistReference(?string $reference): void
    {
        $this->distReference = $reference;
    }

    /**
     * @inheritDoc
     */
    public function getDistReference(): ?string
    {
        return $this->distReference;
    }

    public function setDistSha1Checksum(?string $sha1checksum): void
    {
        $this->distSha1Checksum = $sha1checksum;
    }

    /**
     * @inheritDoc
     */
    public function getDistSha1Checksum(): ?string
    {
        return $this->distSha1Checksum;
    }

    public function setDistMirrors(?array $mirrors): void
    {
        $this->distMirrors = $mirrors;
    }

    /**
     * @inheritDoc
     */
    public function getDistMirrors(): ?array
    {
        return $this->distMirrors;
    }

    /**
     * @inheritDoc
     */
    public function getDistUrls(): array
    {
        return $this->getUrls($this->distUrl, $this->distMirrors, $this->distReference, $this->distType, 'dist');
    }

    /**
     * @inheritDoc
     */
    public function getTransportOptions(): array
    {
        return $this->transportOptions;
    }

    /**
     * @inheritDoc
     */
    public function setTransportOptions(array $options): void
    {
        $this->transportOptions = $options;
    }

    /**
     * @inheritDoc
     */
    public function getVersion(): string
    {
        return $this->version;
    }

    /**
     * @inheritDoc
     */
    public function getPrettyVersion(): string
    {
        return $this->prettyVersion;
    }

    public function setReleaseDate(?\DateTimeInterface $releaseDate): void
    {
        $this->releaseDate = $releaseDate;
    }

    /**
     * @inheritDoc
     */
    public function getReleaseDate(): ?\DateTimeInterface
    {
        return $this->releaseDate;
    }

    /**
     * Set the required packages
     *
     * @param array<string, Link> $requires A set of package links
     */
    public function setRequires(array $requires): void
    {
        if (isset($requires[0])) { // @phpstan-ignore-line
            $requires = $this->convertLinksToMap($requires, 'setRequires');
        }

        $this->requires = $requires;
    }

    /**
     * @inheritDoc
     */
    public function getRequires(): array
    {
        return $this->requires;
    }

    /**
     * Set the conflicting packages
     *
     * @param array<string, Link> $conflicts A set of package links
     */
    public function setConflicts(array $conflicts): void
    {
        if (isset($conflicts[0])) { // @phpstan-ignore-line
            $conflicts = $this->convertLinksToMap($conflicts, 'setConflicts');
        }

        $this->conflicts = $conflicts;
    }

    /**
     * @inheritDoc
     * @return array<string, Link>
     */
    public function getConflicts(): array
    {
        return $this->conflicts;
    }

    /**
     * Set the provided virtual packages
     *
     * @param array<string, Link> $provides A set of package links
     */
    public function setProvides(array $provides): void
    {
        if (isset($provides[0])) { // @phpstan-ignore-line
            $provides = $this->convertLinksToMap($provides, 'setProvides');
        }

        $this->provides = $provides;
    }

    /**
     * @inheritDoc
     * @return array<string, Link>
     */
    public function getProvides(): array
    {
        return $this->provides;
    }

    /**
     * Set the packages this one replaces
     *
     * @param array<string, Link> $replaces A set of package links
     */
    public function setReplaces(array $replaces): void
    {
        if (isset($replaces[0])) { // @phpstan-ignore-line
            $replaces = $this->convertLinksToMap($replaces, 'setReplaces');
        }

        $this->replaces = $replaces;
    }

    /**
     * @inheritDoc
     * @return array<string, Link>
     */
    public function getReplaces(): array
    {
        return $this->replaces;
    }

    /**
     * Set the recommended packages
     *
     * @param array<string, Link> $devRequires A set of package links
     */
    public function setDevRequires(array $devRequires): void
    {
        if (isset($devRequires[0])) { // @phpstan-ignore-line
            $devRequires = $this->convertLinksToMap($devRequires, 'setDevRequires');
        }

        $this->devRequires = $devRequires;
    }

    /**
     * @inheritDoc
     */
    public function getDevRequires(): array
    {
        return $this->devRequires;
    }

    /**
     * Set the suggested packages
     *
     * @param array<string, string> $suggests A set of package names/comments
     */
    public function setSuggests(array $suggests): void
    {
        $this->suggests = $suggests;
    }

    /**
     * @inheritDoc
     */
    public function getSuggests(): array
    {
        return $this->suggests;
    }

    /**
     * Set the autoload mapping
     *
     * @param array $autoload Mapping of autoloading rules
     *
     * @phpstan-param AutoloadRules $autoload
     */
    public function setAutoload(array $autoload): void
    {
        $this->autoload = $autoload;
    }

    /**
     * @inheritDoc
     */
    public function getAutoload(): array
    {
        return $this->autoload;
    }

    /**
     * Set the dev autoload mapping
     *
     * @param array $devAutoload Mapping of dev autoloading rules
     *
     * @phpstan-param DevAutoloadRules $devAutoload
     */
    public function setDevAutoload(array $devAutoload): void
    {
        $this->devAutoload = $devAutoload;
    }

    /**
     * @inheritDoc
     */
    public function getDevAutoload(): array
    {
        return $this->devAutoload;
    }

    /**
     * Sets the list of paths added to PHP's include path.
     *
     * @param string[] $includePaths List of directories.
     */
    public function setIncludePaths(array $includePaths): void
    {
        $this->includePaths = $includePaths;
    }

    /**
     * @inheritDoc
     */
    public function getIncludePaths(): array
    {
        return $this->includePaths;
    }

    /**
     * Sets the settings for php extension packages
     *
     *
     * @phpstan-param PhpExtConfig|null $phpExt
     */
    public function setPhpExt(?array $phpExt): void
    {
        $this->phpExt = $phpExt;
    }

    /**
     * @inheritDoc
     */
    public function getPhpExt(): ?array
    {
        return $this->phpExt;
    }

    /**
     * Sets the notification URL
     */
    public function setNotificationUrl(string $notificationUrl): void
    {
        $this->notificationUrl = $notificationUrl;
    }

    /**
     * @inheritDoc
     */
    public function getNotificationUrl(): ?string
    {
        return $this->notificationUrl;
    }

    public function setIsDefaultBranch(bool $defaultBranch): void
    {
        $this->isDefaultBranch = $defaultBranch;
    }

    /**
     * @inheritDoc
     */
    public function isDefaultBranch(): bool
    {
        return $this->isDefaultBranch;
    }

    /**
     * @inheritDoc
     */
    public function setSourceDistReferences(string $reference): void
    {
        $this->setSourceReference($reference);

        // only bitbucket, github and gitlab have auto generated dist URLs that easily allow replacing the reference in the dist URL
        // TODO generalize this a bit for self-managed/on-prem versions? Some kind of replace token in dist urls which allow this?
        if (
            $this->getDistUrl() !== null
            && Preg::isMatch('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com|(?:www\.)?gitlab\.com)/}i', $this->getDistUrl())
        ) {
            $this->setDistReference($reference);
            $this->setDistUrl(Preg::replace('{(?<=/|sha=)[a-f0-9]{40}(?=/|$)}i', $reference, $this->getDistUrl()));
        } elseif ($this->getDistReference()) { // update the dist reference if there was one, but if none was provided ignore it
            $this->setDistReference($reference);
        }
    }

    /**
     * Replaces current version and pretty version with passed values.
     * It also sets stability.
     *
     * @param string $version       The package's normalized version
     * @param string $prettyVersion The package's non-normalized version
     */
    public function replaceVersion(string $version, string $prettyVersion): void
    {
        $this->version = $version;
        $this->prettyVersion = $prettyVersion;

        $this->stability = VersionParser::parseStability($version);
        $this->dev = $this->stability === 'dev';
    }

    /**
     * @param mixed[]|null $mirrors
     *
     * @return list<non-empty-string>
     *
     * @phpstan-param list<array{url: non-empty-string, preferred: bool}>|null $mirrors
     */
    protected function getUrls(?string $url, ?array $mirrors, ?string $ref, ?string $type, string $urlType): array
    {
        if (!$url) {
            return [];
        }

        if ($urlType === 'dist' && false !== strpos($url, '%')) {
            $url = ComposerMirror::processUrl($url, $this->name, $this->version, $ref, $type, $this->prettyVersion);
        }

        $urls = [$url];
        if ($mirrors) {
            foreach ($mirrors as $mirror) {
                if ($urlType === 'dist') {
                    $mirrorUrl = ComposerMirror::processUrl($mirror['url'], $this->name, $this->version, $ref, $type, $this->prettyVersion);
                } elseif ($urlType === 'source' && $type === 'git') {
                    $mirrorUrl = ComposerMirror::processGitUrl($mirror['url'], $this->name, $url, $type);
                } elseif ($urlType === 'source' && $type === 'hg') {
                    $mirrorUrl = ComposerMirror::processHgUrl($mirror['url'], $this->name, $url, $type);
                } else {
                    continue;
                }
                if (!\in_array($mirrorUrl, $urls)) {
                    $func = $mirror['preferred'] ? 'array_unshift' : 'array_push';
                    $func($urls, $mirrorUrl);
                }
            }
        }

        return $urls;
    }

    /**
     * @param  array<int, Link> $links
     * @return array<string, Link>
     */
    private function convertLinksToMap(array $links, string $source): array
    {
        trigger_error('Package::'.$source.' must be called with a map of lowercased package name => Link object, got a indexed array, this is deprecated and you should fix your usage.');
        $newLinks = [];
        foreach ($links as $link) {
            $newLinks[$link->getTarget()] = $link;
        }

        return $newLinks;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package;

use Composer\Semver\Constraint\ConstraintInterface;

/**
 * Represents a link between two packages, represented by their names
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class Link
{
    public const TYPE_REQUIRE = 'requires';
    public const TYPE_DEV_REQUIRE = 'devRequires';
    public const TYPE_PROVIDE = 'provides';
    public const TYPE_CONFLICT = 'conflicts';
    public const TYPE_REPLACE = 'replaces';

    /**
     * Special type
     * @internal
     */
    public const TYPE_DOES_NOT_REQUIRE = 'does not require';

    private const TYPE_UNKNOWN = 'relates to';

    /**
     * Will be converted into a constant once the min PHP version allows this
     *
     * @internal
     * @var string[]
     * @phpstan-var array<self::TYPE_REQUIRE|self::TYPE_DEV_REQUIRE|self::TYPE_PROVIDE|self::TYPE_CONFLICT|self::TYPE_REPLACE>
     */
    public static $TYPES = [
        self::TYPE_REQUIRE,
        self::TYPE_DEV_REQUIRE,
        self::TYPE_PROVIDE,
        self::TYPE_CONFLICT,
        self::TYPE_REPLACE,
    ];

    /**
     * @var string
     */
    protected $source;

    /**
     * @var string
     */
    protected $target;

    /**
     * @var ConstraintInterface
     */
    protected $constraint;

    /**
     * @var string
     * @phpstan-var string $description
     */
    protected $description;

    /**
     * @var ?string
     */
    protected $prettyConstraint;

    /**
     * Creates a new package link.
     *
     * @param ConstraintInterface $constraint       Constraint applying to the target of this link
     * @param self::TYPE_*        $description      Used to create a descriptive string representation
     */
    public function __construct(
        string $source,
        string $target,
        ConstraintInterface $constraint,
        $description = self::TYPE_UNKNOWN,
        ?string $prettyConstraint = null
    ) {
        $this->source = strtolower($source);
        $this->target = strtolower($target);
        $this->constraint = $constraint;
        $this->description = self::TYPE_DEV_REQUIRE === $description ? 'requires (for development)' : $description;
        $this->prettyConstraint = $prettyConstraint;
    }

    public function getDescription(): string
    {
        return $this->description;
    }

    public function getSource(): string
    {
        return $this->source;
    }

    public function getTarget(): string
    {
        return $this->target;
    }

    public function getConstraint(): ConstraintInterface
    {
        return $this->constraint;
    }

    /**
     * @throws \UnexpectedValueException If no pretty constraint was provided
     */
    public function getPrettyConstraint(): string
    {
        if (null === $this->prettyConstraint) {
            throw new \UnexpectedValueException(sprintf('Link %s has been misconfigured and had no prettyConstraint given.', $this));
        }

        return $this->prettyConstraint;
    }

    public function __toString(): string
    {
        return $this->source.' '.$this->description.' '.$this->target.' ('.$this->constraint.')';
    }

    public function getPrettyString(PackageInterface $sourcePackage): string
    {
        return $sourcePackage->getPrettyString().' '.$this->description.' '.$this->target.' '.$this->constraint->getPrettyString();
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package;

use Composer\Repository\RepositoryInterface;
use Composer\Repository\PlatformRepository;

/**
 * Base class for packages providing name storage and default match implementation
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
abstract class BasePackage implements PackageInterface
{
    /**
     * @phpstan-var array<non-empty-string, array{description: string, method: Link::TYPE_*}>
     * @internal
     */
    public static $supportedLinkTypes = [
        'require' => ['description' => 'requires', 'method' => Link::TYPE_REQUIRE],
        'conflict' => ['description' => 'conflicts', 'method' => Link::TYPE_CONFLICT],
        'provide' => ['description' => 'provides', 'method' => Link::TYPE_PROVIDE],
        'replace' => ['description' => 'replaces', 'method' => Link::TYPE_REPLACE],
        'require-dev' => ['description' => 'requires (for development)', 'method' => Link::TYPE_DEV_REQUIRE],
    ];

    public const STABILITY_STABLE = 0;
    public const STABILITY_RC = 5;
    public const STABILITY_BETA = 10;
    public const STABILITY_ALPHA = 15;
    public const STABILITY_DEV = 20;

    public const STABILITIES = [
        'stable' => self::STABILITY_STABLE,
        'RC' => self::STABILITY_RC,
        'beta' => self::STABILITY_BETA,
        'alpha' => self::STABILITY_ALPHA,
        'dev' => self::STABILITY_DEV,
    ];

    /**
     * @deprecated
     * @readonly
     * @var array<key-of<BasePackage::STABILITIES>, self::STABILITY_*>
     * @phpstan-ignore property.readOnlyByPhpDocDefaultValue
     */
    public static $stabilities = self::STABILITIES;

    /**
     * READ-ONLY: The package id, public for fast access in dependency solver
     * @var int
     * @internal
     */
    public $id;
    /** @var string */
    protected $name;
    /** @var string */
    protected $prettyName;
    /** @var ?RepositoryInterface */
    protected $repository = null;

    /**
     * All descendants' constructors should call this parent constructor
     *
     * @param string $name The package's name
     */
    public function __construct(string $name)
    {
        $this->prettyName = $name;
        $this->name = strtolower($name);
        $this->id = -1;
    }

    /**
     * @inheritDoc
     */
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * @inheritDoc
     */
    public function getPrettyName(): string
    {
        return $this->prettyName;
    }

    /**
     * @inheritDoc
     */
    public function getNames($provides = true): array
    {
        $names = [
            $this->getName() => true,
        ];

        if ($provides) {
            foreach ($this->getProvides() as $link) {
                $names[$link->getTarget()] = true;
            }
        }

        foreach ($this->getReplaces() as $link) {
            $names[$link->getTarget()] = true;
        }

        return array_keys($names);
    }

    /**
     * @inheritDoc
     */
    public function setId(int $id): void
    {
        $this->id = $id;
    }

    /**
     * @inheritDoc
     */
    public function getId(): int
    {
        return $this->id;
    }

    /**
     * @inheritDoc
     */
    public function setRepository(RepositoryInterface $repository): void
    {
        if ($this->repository && $repository !== $this->repository) {
            throw new \LogicException(sprintf(
                'Package "%s" cannot be added to repository "%s" as it is already in repository "%s".',
                $this->getPrettyName(),
                $repository->getRepoName(),
                $this->repository->getRepoName()
            ));
        }
        $this->repository = $repository;
    }

    /**
     * @inheritDoc
     */
    public function getRepository(): ?RepositoryInterface
    {
        return $this->repository;
    }

    /**
     * checks if this package is a platform package
     */
    public function isPlatform(): bool
    {
        return $this->getRepository() instanceof PlatformRepository;
    }

    /**
     * Returns package unique name, constructed from name, version and release type.
     */
    public function getUniqueName(): string
    {
        return $this->getName().'-'.$this->getVersion();
    }

    public function equals(PackageInterface $package): bool
    {
        $self = $this;
        if ($this instanceof AliasPackage) {
            $self = $this->getAliasOf();
        }
        if ($package instanceof AliasPackage) {
            $package = $package->getAliasOf();
        }

        return $package === $self;
    }

    /**
     * Converts the package into a readable and unique string
     */
    public function __toString(): string
    {
        return $this->getUniqueName();
    }

    public function getPrettyString(): string
    {
        return $this->getPrettyName().' '.$this->getPrettyVersion();
    }

    /**
     * @inheritDoc
     */
    public function getFullPrettyVersion(bool $truncate = true, int $displayMode = PackageInterface::DISPLAY_SOURCE_REF_IF_DEV): string
    {
        if (
            $displayMode === PackageInterface::DISPLAY_SOURCE_REF_IF_DEV
            && (
                !$this->isDev()
                || (
                    !\in_array($this->getSourceType(), ['hg', 'git'])
                    && ((string) $this->getSourceType() !== '' || (string) $this->getDistReference() === '')
                )
            )
        ) {
            return $this->getPrettyVersion();
        }

        switch ($displayMode) {
            case PackageInterface::DISPLAY_SOURCE_REF_IF_DEV:
                $reference = (string) $this->getSourceReference() !== '' ? $this->getSourceReference() : $this->getDistReference();
                break;
            case PackageInterface::DISPLAY_SOURCE_REF:
                $reference = $this->getSourceReference();
                break;
            case PackageInterface::DISPLAY_DIST_REF:
                $reference = $this->getDistReference();
                break;
            default:
                throw new \UnexpectedValueException('Display mode '.$displayMode.' is not supported');
        }

        if (null === $reference) {
            return $this->getPrettyVersion();
        }

        // if source reference is a sha1 hash -- truncate
        if ($truncate && \strlen($reference) === 40 && $this->getSourceType() !== 'svn') {
            return $this->getPrettyVersion() . ' ' . substr($reference, 0, 7);
        }

        return $this->getPrettyVersion() . ' ' . $reference;
    }

    /**
     * @phpstan-return self::STABILITY_*
     */
    public function getStabilityPriority(): int
    {
        return self::STABILITIES[$this->getStability()];
    }

    public function __clone()
    {
        $this->repository = null;
        $this->id = -1;
    }

    /**
     * Build a regexp from a package name, expanding * globs as required
     *
     * @param  non-empty-string $wrap         Wrap the cleaned string by the given string
     * @return non-empty-string
     */
    public static function packageNameToRegexp(string $allowPattern, string $wrap = '{^%s$}i'): string
    {
        $cleanedAllowPattern = str_replace('\\*', '.*', preg_quote($allowPattern));

        return sprintf($wrap, $cleanedAllowPattern);
    }

    /**
     * Build a regexp from package names, expanding * globs as required
     *
     * @param string[] $packageNames
     * @param non-empty-string $wrap
     * @return non-empty-string
     */
    public static function packageNamesToRegexp(array $packageNames, string $wrap = '{^(?:%s)$}iD'): string
    {
        $packageNames = array_map(
            static function ($packageName): string {
                return BasePackage::packageNameToRegexp($packageName, '%s');
            },
            $packageNames
        );

        return sprintf($wrap, implode('|', $packageNames));
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Pcre\Preg;
use Composer\Util\Filesystem;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Util\Silencer;

/**
 * Utility to handle installation of package "bin"/binaries
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Helmut Hummel <info@helhum.io>
 */
class BinaryInstaller
{
    /** @var string */
    protected $binDir;
    /** @var string */
    protected $binCompat;
    /** @var IOInterface */
    protected $io;
    /** @var Filesystem */
    protected $filesystem;
    /** @var string|null */
    private $vendorDir;

    public function __construct(IOInterface $io, string $binDir, string $binCompat, ?Filesystem $filesystem = null, ?string $vendorDir = null)
    {
        $this->binDir = $binDir;
        $this->binCompat = $binCompat;
        $this->io = $io;
        $this->filesystem = $filesystem ?: new Filesystem();
        $this->vendorDir = $vendorDir;
    }

    public function installBinaries(PackageInterface $package, string $installPath, bool $warnOnOverwrite = true): void
    {
        $binaries = $this->getBinaries($package);
        if (!$binaries) {
            return;
        }

        Platform::workaroundFilesystemIssues();

        foreach ($binaries as $bin) {
            $binPath = $installPath.'/'.$bin;
            if (!file_exists($binPath)) {
                $this->io->writeError('    <warning>Skipped installation of bin '.$bin.' for package '.$package->getName().': file not found in package</warning>');
                continue;
            }
            if (is_dir($binPath)) {
                $this->io->writeError('    <warning>Skipped installation of bin '.$bin.' for package '.$package->getName().': found a directory at that path</warning>');
                continue;
            }
            if (!$this->filesystem->isAbsolutePath($binPath)) {
                // in case a custom installer returned a relative path for the
                // $package, we can now safely turn it into a absolute path (as we
                // already checked the binary's existence). The following helpers
                // will require absolute paths to work properly.
                $binPath = realpath($binPath);
            }
            $this->initializeBinDir();
            $link = $this->binDir.'/'.basename($bin);
            if (file_exists($link)) {
                if (!is_link($link)) {
                    if ($warnOnOverwrite) {
                        $this->io->writeError('    Skipped installation of bin '.$bin.' for package '.$package->getName().': name conflicts with an existing file');
                    }
                    continue;
                }
                if (realpath($link) === realpath($binPath)) {
                    // It is a linked binary from a previous installation, which can be replaced with a proxy file
                    $this->filesystem->unlink($link);
                }
            }

            $binCompat = $this->binCompat;
            if ($binCompat === "auto" && (Platform::isWindows() || Platform::isWindowsSubsystemForLinux())) {
                $binCompat = 'full';
            }

            if ($binCompat === "full") {
                $this->installFullBinaries($binPath, $link, $bin, $package);
            } else {
                $this->installUnixyProxyBinaries($binPath, $link);
            }
            Silencer::call('chmod', $binPath, 0777 & ~umask());
        }
    }

    public function removeBinaries(PackageInterface $package): void
    {
        $this->initializeBinDir();

        $binaries = $this->getBinaries($package);
        if (!$binaries) {
            return;
        }
        foreach ($binaries as $bin) {
            $link = $this->binDir.'/'.basename($bin);
            if (is_link($link) || file_exists($link)) { // still checking for symlinks here for legacy support
                $this->filesystem->unlink($link);
            }
            if (is_file($link.'.bat')) {
                $this->filesystem->unlink($link.'.bat');
            }
        }

        // attempt removing the bin dir in case it is left empty
        if (is_dir($this->binDir) && $this->filesystem->isDirEmpty($this->binDir)) {
            Silencer::call('rmdir', $this->binDir);
        }
    }

    public static function determineBinaryCaller(string $bin): string
    {
        if ('.bat' === substr($bin, -4) || '.exe' === substr($bin, -4)) {
            return 'call';
        }

        $handle = fopen($bin, 'r');
        $line = fgets($handle);
        fclose($handle);
        if (Preg::isMatchStrictGroups('{^#!/(?:usr/bin/env )?(?:[^/]+/)*(.+)$}m', (string) $line, $match)) {
            return trim($match[1]);
        }

        return 'php';
    }

    /**
     * @return string[]
     */
    protected function getBinaries(PackageInterface $package): array
    {
        return $package->getBinaries();
    }

    protected function installFullBinaries(string $binPath, string $link, string $bin, PackageInterface $package): void
    {
        // add unixy support for cygwin and similar environments
        if ('.bat' !== substr($binPath, -4)) {
            $this->installUnixyProxyBinaries($binPath, $link);
            $link .= '.bat';
            if (file_exists($link)) {
                $this->io->writeError('    Skipped installation of bin '.$bin.'.bat proxy for package '.$package->getName().': a .bat proxy was already installed');
            }
        }
        if (!file_exists($link)) {
            file_put_contents($link, $this->generateWindowsProxyCode($binPath, $link));
            Silencer::call('chmod', $link, 0777 & ~umask());
        }
    }

    protected function installUnixyProxyBinaries(string $binPath, string $link): void
    {
        file_put_contents($link, $this->generateUnixyProxyCode($binPath, $link));
        Silencer::call('chmod', $link, 0777 & ~umask());
    }

    protected function initializeBinDir(): void
    {
        $this->filesystem->ensureDirectoryExists($this->binDir);
        $this->binDir = realpath($this->binDir);
    }

    protected function generateWindowsProxyCode(string $bin, string $link): string
    {
        $binPath = $this->filesystem->findShortestPath($link, $bin);
        $caller = self::determineBinaryCaller($bin);

        // if the target is a php file, we run the unixy proxy file
        // to ensure that _composer_autoload_path gets defined, instead
        // of running the binary directly
        if ($caller === 'php') {
            return "@ECHO OFF\r\n".
                "setlocal DISABLEDELAYEDEXPANSION\r\n".
                "SET BIN_TARGET=%~dp0/".trim(ProcessExecutor::escape(basename($link, '.bat')), '"\'')."\r\n".
                "SET COMPOSER_RUNTIME_BIN_DIR=%~dp0\r\n".
                "{$caller} \"%BIN_TARGET%\" %*\r\n";
        }

        return "@ECHO OFF\r\n".
            "setlocal DISABLEDELAYEDEXPANSION\r\n".
            "SET BIN_TARGET=%~dp0/".trim(ProcessExecutor::escape($binPath), '"\'')."\r\n".
            "SET COMPOSER_RUNTIME_BIN_DIR=%~dp0\r\n".
            "{$caller} \"%BIN_TARGET%\" %*\r\n";
    }

    protected function generateUnixyProxyCode(string $bin, string $link): string
    {
        $binPath = $this->filesystem->findShortestPath($link, $bin);

        $binDir = ProcessExecutor::escape(dirname($binPath));
        $binFile = basename($binPath);

        $binContents = (string) file_get_contents($bin, false, null, 0, 500);
        // For php files, we generate a PHP proxy instead of a shell one,
        // which allows calling the proxy with a custom php process
        if (Preg::isMatch('{^(#!.*\r?\n)?[\r\n\t ]*<\?php}', $binContents, $match)) {
            // carry over the existing shebang if present, otherwise add our own
            $proxyCode = $match[1] === null ? '#!/usr/bin/env php' : trim($match[1]);
            $binPathExported = $this->filesystem->findShortestPathCode($link, $bin, false, true);
            $streamProxyCode = $streamHint = '';
            $globalsCode = '$GLOBALS[\'_composer_bin_dir\'] = __DIR__;'."\n";
            $phpunitHack1 = $phpunitHack2 = '';
            // Don't expose autoload path when vendor dir was not set in custom installers
            if ($this->vendorDir !== null) {
                // ensure comparisons work accurately if the CWD is a symlink, as $link is realpath'd already
                $vendorDirReal = realpath($this->vendorDir);
                if ($vendorDirReal === false) {
                    $vendorDirReal = $this->vendorDir;
                }
                $globalsCode .= '$GLOBALS[\'_composer_autoload_path\'] = ' . $this->filesystem->findShortestPathCode($link, $vendorDirReal . '/autoload.php', false, true).";\n";
            }
            // Add workaround for PHPUnit process isolation
            if ($this->filesystem->normalizePath($bin) === $this->filesystem->normalizePath($this->vendorDir.'/phpunit/phpunit/phpunit')) {
                // workaround issue on PHPUnit 6.5+ running on PHP 8+
                $globalsCode .= '$GLOBALS[\'__PHPUNIT_ISOLATION_EXCLUDE_LIST\'] = $GLOBALS[\'__PHPUNIT_ISOLATION_BLACKLIST\'] = array(realpath('.$binPathExported.'));'."\n";
                // workaround issue on all PHPUnit versions running on PHP <8
                $phpunitHack1 = "'phpvfscomposer://'.";
                $phpunitHack2 = '
                $data = str_replace(\'__DIR__\', var_export(dirname($this->realpath), true), $data);
                $data = str_replace(\'__FILE__\', var_export($this->realpath, true), $data);';
            }
            if (trim($match[0]) !== '<?php') {
                $streamHint = ' using a stream wrapper to prevent the shebang from being output on PHP<8'."\n *";
                $streamProxyCode = <<<STREAMPROXY
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 = $phpunitHack1\$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);
                }$phpunitHack2

                \$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'))
    ) {
        return include("phpvfscomposer://" . $binPathExported);
    }
}

STREAMPROXY;
            }

            return $proxyCode . "\n" . <<<PROXY
<?php

/**
 * Proxy PHP file generated by Composer
 *
 * This file includes the referenced bin path ($binPath)
 *$streamHint
 * @generated
 */

namespace Composer;

$globalsCode
$streamProxyCode
return include $binPathExported;

PROXY;
        }

        return <<<PROXY
#!/usr/bin/env sh

# Support bash to support `source` with fallback on $0 if this does not run with bash
# https://stackoverflow.com/a/35006505/6512
selfArg="\$BASH_SOURCE"
if [ -z "\$selfArg" ]; then
    selfArg="\$0"
fi

self=\$(realpath "\$selfArg" 2> /dev/null)
if [ -z "\$self" ]; then
    self="\$selfArg"
fi

dir=\$(cd "\${self%[/\\\\]*}" > /dev/null; cd $binDir && pwd)

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

export COMPOSER_RUNTIME_BIN_DIR="\$(cd "\${self%[/\\\\]*}" > /dev/null; pwd)"

# If bash is sourcing this file, we have to source the target as well
bashSource="\$BASH_SOURCE"
if [ -n "\$bashSource" ]; then
    if [ "\$bashSource" != "\$0" ]; then
        source "\${dir}/$binFile" "\$@"
        return
    fi
fi

exec "\${dir}/$binFile" "\$@"

PROXY;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\Repository\RepositoryInterface;
use Composer\EventDispatcher\Event;

/**
 * The Package Event.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PackageEvent extends Event
{
    /**
     * @var Composer
     */
    private $composer;

    /**
     * @var IOInterface
     */
    private $io;

    /**
     * @var bool
     */
    private $devMode;

    /**
     * @var RepositoryInterface
     */
    private $localRepo;

    /**
     * @var OperationInterface[]
     */
    private $operations;

    /**
     * @var OperationInterface The operation instance which is being executed
     */
    private $operation;

    /**
     * Constructor.
     *
     * @param OperationInterface[] $operations
     */
    public function __construct(string $eventName, Composer $composer, IOInterface $io, bool $devMode, RepositoryInterface $localRepo, array $operations, OperationInterface $operation)
    {
        parent::__construct($eventName);

        $this->composer = $composer;
        $this->io = $io;
        $this->devMode = $devMode;
        $this->localRepo = $localRepo;
        $this->operations = $operations;
        $this->operation = $operation;
    }

    public function getComposer(): Composer
    {
        return $this->composer;
    }

    public function getIO(): IOInterface
    {
        return $this->io;
    }

    public function isDevMode(): bool
    {
        return $this->devMode;
    }

    public function getLocalRepo(): RepositoryInterface
    {
        return $this->localRepo;
    }

    /**
     * @return OperationInterface[]
     */
    public function getOperations(): array
    {
        return $this->operations;
    }

    /**
     * Returns the package instance.
     */
    public function getOperation(): OperationInterface
    {
        return $this->operation;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

class InstallerEvents
{
    /**
     * The PRE_OPERATIONS_EXEC event occurs before the lock file gets
     * installed and operations are executed.
     *
     * The event listener method receives an Composer\Installer\InstallerEvent instance.
     *
     * @var string
     */
    public const PRE_OPERATIONS_EXEC = 'pre-operations-exec';
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

use Composer\Composer;
use Composer\DependencyResolver\Transaction;
use Composer\EventDispatcher\Event;
use Composer\IO\IOInterface;

class InstallerEvent extends Event
{
    /**
     * @var Composer
     */
    private $composer;

    /**
     * @var IOInterface
     */
    private $io;

    /**
     * @var bool
     */
    private $devMode;

    /**
     * @var bool
     */
    private $executeOperations;

    /**
     * @var Transaction
     */
    private $transaction;

    /**
     * Constructor.
     */
    public function __construct(string $eventName, Composer $composer, IOInterface $io, bool $devMode, bool $executeOperations, Transaction $transaction)
    {
        parent::__construct($eventName);

        $this->composer = $composer;
        $this->io = $io;
        $this->devMode = $devMode;
        $this->executeOperations = $executeOperations;
        $this->transaction = $transaction;
    }

    public function getComposer(): Composer
    {
        return $this->composer;
    }

    public function getIO(): IOInterface
    {
        return $this->io;
    }

    public function isDevMode(): bool
    {
        return $this->devMode;
    }

    public function isExecutingOperations(): bool
    {
        return $this->executeOperations;
    }

    public function getTransaction(): ?Transaction
    {
        return $this->transaction;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

/**
 * Package Events.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PackageEvents
{
    /**
     * The PRE_PACKAGE_INSTALL event occurs before a package is installed.
     *
     * The event listener method receives a Composer\Installer\PackageEvent instance.
     *
     * @var string
     */
    public const PRE_PACKAGE_INSTALL = 'pre-package-install';

    /**
     * The POST_PACKAGE_INSTALL event occurs after a package is installed.
     *
     * The event listener method receives a Composer\Installer\PackageEvent instance.
     *
     * @var string
     */
    public const POST_PACKAGE_INSTALL = 'post-package-install';

    /**
     * The PRE_PACKAGE_UPDATE event occurs before a package is updated.
     *
     * The event listener method receives a Composer\Installer\PackageEvent instance.
     *
     * @var string
     */
    public const PRE_PACKAGE_UPDATE = 'pre-package-update';

    /**
     * The POST_PACKAGE_UPDATE event occurs after a package is updated.
     *
     * The event listener method receives a Composer\Installer\PackageEvent instance.
     *
     * @var string
     */
    public const POST_PACKAGE_UPDATE = 'post-package-update';

    /**
     * The PRE_PACKAGE_UNINSTALL event occurs before a package has been uninstalled.
     *
     * The event listener method receives a Composer\Installer\PackageEvent instance.
     *
     * @var string
     */
    public const PRE_PACKAGE_UNINSTALL = 'pre-package-uninstall';

    /**
     * The POST_PACKAGE_UNINSTALL event occurs after a package has been uninstalled.
     *
     * The event listener method receives a Composer\Installer\PackageEvent instance.
     *
     * @var string
     */
    public const POST_PACKAGE_UNINSTALL = 'post-package-uninstall';
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

use React\Promise\PromiseInterface;
use Composer\Package\PackageInterface;
use Composer\Downloader\DownloadManager;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Util\Filesystem;

/**
 * Project Installer is used to install a single package into a directory as
 * root project.
 *
 * @author Benjamin Eberlei <kontakt@beberlei.de>
 */
class ProjectInstaller implements InstallerInterface
{
    /** @var string */
    private $installPath;
    /** @var DownloadManager */
    private $downloadManager;
    /** @var Filesystem */
    private $filesystem;

    public function __construct(string $installPath, DownloadManager $dm, Filesystem $fs)
    {
        $this->installPath = rtrim(strtr($installPath, '\\', '/'), '/').'/';
        $this->downloadManager = $dm;
        $this->filesystem = $fs;
    }

    /**
     * Decides if the installer supports the given type
     */
    public function supports(string $packageType): bool
    {
        return true;
    }

    /**
     * @inheritDoc
     */
    public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package): bool
    {
        return false;
    }

    /**
     * @inheritDoc
     */
    public function download(PackageInterface $package, ?PackageInterface $prevPackage = null): ?PromiseInterface
    {
        $installPath = $this->installPath;
        if (file_exists($installPath) && !$this->filesystem->isDirEmpty($installPath)) {
            throw new \InvalidArgumentException("Project directory $installPath is not empty.");
        }
        if (!is_dir($installPath)) {
            mkdir($installPath, 0777, true);
        }

        return $this->downloadManager->download($package, $installPath, $prevPackage);
    }

    /**
     * @inheritDoc
     */
    public function prepare($type, PackageInterface $package, ?PackageInterface $prevPackage = null): ?PromiseInterface
    {
        return $this->downloadManager->prepare($type, $package, $this->installPath, $prevPackage);
    }

    /**
     * @inheritDoc
     */
    public function cleanup($type, PackageInterface $package, ?PackageInterface $prevPackage = null): ?PromiseInterface
    {
        return $this->downloadManager->cleanup($type, $package, $this->installPath, $prevPackage);
    }

    /**
     * @inheritDoc
     */
    public function install(InstalledRepositoryInterface $repo, PackageInterface $package): ?PromiseInterface
    {
        return $this->downloadManager->install($package, $this->installPath);
    }

    /**
     * @inheritDoc
     */
    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target): ?PromiseInterface
    {
        throw new \InvalidArgumentException("not supported");
    }

    /**
     * @inheritDoc
     */
    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package): ?PromiseInterface
    {
        throw new \InvalidArgumentException("not supported");
    }

    /**
     * Returns the installation path of a package
     *
     * @return string configured install path
     */
    public function getInstallPath(PackageInterface $package): string
    {
        return $this->installPath;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

use Composer\Repository\InstalledRepositoryInterface;
use Composer\Package\PackageInterface;

/**
 * Does not install anything but marks packages installed in the repo
 *
 * Useful for dry runs
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class NoopInstaller implements InstallerInterface
{
    /**
     * @inheritDoc
     */
    public function supports(string $packageType)
    {
        return true;
    }

    /**
     * @inheritDoc
     */
    public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        return $repo->hasPackage($package);
    }

    /**
     * @inheritDoc
     */
    public function download(PackageInterface $package, ?PackageInterface $prevPackage = null)
    {
        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function prepare($type, PackageInterface $package, ?PackageInterface $prevPackage = null)
    {
        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function cleanup($type, PackageInterface $package, ?PackageInterface $prevPackage = null)
    {
        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        if (!$repo->hasPackage($package)) {
            $repo->addPackage(clone $package);
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
    {
        if (!$repo->hasPackage($initial)) {
            throw new \InvalidArgumentException('Package is not installed: '.$initial);
        }

        $repo->removePackage($initial);
        if (!$repo->hasPackage($target)) {
            $repo->addPackage(clone $target);
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        if (!$repo->hasPackage($package)) {
            throw new \InvalidArgumentException('Package is not installed: '.$package);
        }
        $repo->removePackage($package);

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function getInstallPath(PackageInterface $package)
    {
        $targetDir = $package->getTargetDir();

        return $package->getPrettyName() . ($targetDir ? '/'.$targetDir : '');
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

use Composer\Package\PackageInterface;
use Composer\Repository\InstalledRepositoryInterface;
use InvalidArgumentException;
use React\Promise\PromiseInterface;

/**
 * Interface for the package installation manager.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface InstallerInterface
{
    /**
     * Decides if the installer supports the given type
     *
     * @return bool
     */
    public function supports(string $packageType);

    /**
     * Checks that provided package is installed.
     *
     * @param InstalledRepositoryInterface $repo    repository in which to check
     * @param PackageInterface             $package package instance
     *
     * @return bool
     */
    public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package);

    /**
     * Downloads the files needed to later install the given package.
     *
     * @param  PackageInterface      $package     package instance
     * @param  PackageInterface      $prevPackage previous package instance in case of an update
     * @return PromiseInterface|null
     * @phpstan-return PromiseInterface<void|null>|null
     */
    public function download(PackageInterface $package, ?PackageInterface $prevPackage = null);

    /**
     * Do anything that needs to be done between all downloads have been completed and the actual operation is executed
     *
     * All packages get first downloaded, then all together prepared, then all together installed/updated/uninstalled. Therefore
     * for error recovery it is important to avoid failing during install/update/uninstall as much as possible, and risky things or
     * user prompts should happen in the prepare step rather. In case of failure, cleanup() will be called so that changes can
     * be undone as much as possible.
     *
     * @param  string                $type        one of install/update/uninstall
     * @param  PackageInterface      $package     package instance
     * @param  PackageInterface      $prevPackage previous package instance in case of an update
     * @return PromiseInterface|null
     * @phpstan-return PromiseInterface<void|null>|null
     */
    public function prepare(string $type, PackageInterface $package, ?PackageInterface $prevPackage = null);

    /**
     * Installs specific package.
     *
     * @param  InstalledRepositoryInterface $repo    repository in which to check
     * @param  PackageInterface             $package package instance
     * @return PromiseInterface|null
     * @phpstan-return PromiseInterface<void|null>|null
     */
    public function install(InstalledRepositoryInterface $repo, PackageInterface $package);

    /**
     * Updates specific package.
     *
     * @param  InstalledRepositoryInterface $repo    repository in which to check
     * @param  PackageInterface             $initial already installed package version
     * @param  PackageInterface             $target  updated version
     * @throws InvalidArgumentException     if $initial package is not installed
     * @return PromiseInterface|null
     * @phpstan-return PromiseInterface<void|null>|null
     */
    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target);

    /**
     * Uninstalls specific package.
     *
     * @param  InstalledRepositoryInterface $repo    repository in which to check
     * @param  PackageInterface             $package package instance
     * @return PromiseInterface|null
     * @phpstan-return PromiseInterface<void|null>|null
     */
    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package);

    /**
     * Do anything to cleanup changes applied in the prepare or install/update/uninstall steps
     *
     * Note that cleanup will be called for all packages regardless if they failed an operation or not, to give
     * all installers a change to cleanup things they did previously, so you need to keep track of changes
     * applied in the installer/downloader themselves.
     *
     * @param  string                $type        one of install/update/uninstall
     * @param  PackageInterface      $package     package instance
     * @param  PackageInterface      $prevPackage previous package instance in case of an update
     * @return PromiseInterface|null
     * @phpstan-return PromiseInterface<void|null>|null
     */
    public function cleanup(string $type, PackageInterface $package, ?PackageInterface $prevPackage = null);

    /**
     * Returns the absolute installation path of a package.
     *
     * @return string|null absolute path to install to, which MUST not end with a slash, or null if the package does not have anything installed on disk
     */
    public function getInstallPath(PackageInterface $package);
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

use Composer\IO\IOInterface;
use Composer\IO\ConsoleIO;
use Composer\Package\PackageInterface;
use Composer\Package\AliasPackage;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\DependencyResolver\Operation\UninstallOperation;
use Composer\DependencyResolver\Operation\MarkAliasInstalledOperation;
use Composer\DependencyResolver\Operation\MarkAliasUninstalledOperation;
use Composer\Downloader\FileDownloader;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Util\Loop;
use Composer\Util\Platform;
use React\Promise\PromiseInterface;
use Seld\Signal\SignalHandler;

/**
 * Package operation manager.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Nils Adermann <naderman@naderman.de>
 */
class InstallationManager
{
    /** @var list<InstallerInterface> */
    private $installers = [];
    /** @var array<string, InstallerInterface> */
    private $cache = [];
    /** @var array<string, array<PackageInterface>> */
    private $notifiablePackages = [];
    /** @var Loop */
    private $loop;
    /** @var IOInterface */
    private $io;
    /** @var ?EventDispatcher */
    private $eventDispatcher;
    /** @var bool */
    private $outputProgress;

    public function __construct(Loop $loop, IOInterface $io, ?EventDispatcher $eventDispatcher = null)
    {
        $this->loop = $loop;
        $this->io = $io;
        $this->eventDispatcher = $eventDispatcher;
    }

    public function reset(): void
    {
        $this->notifiablePackages = [];
        FileDownloader::$downloadMetadata = [];
    }

    /**
     * Adds installer
     *
     * @param InstallerInterface $installer installer instance
     */
    public function addInstaller(InstallerInterface $installer): void
    {
        array_unshift($this->installers, $installer);
        $this->cache = [];
    }

    /**
     * Removes installer
     *
     * @param InstallerInterface $installer installer instance
     */
    public function removeInstaller(InstallerInterface $installer): void
    {
        if (false !== ($key = array_search($installer, $this->installers, true))) {
            array_splice($this->installers, $key, 1);
            $this->cache = [];
        }
    }

    /**
     * Disables plugins.
     *
     * We prevent any plugins from being instantiated by
     * disabling the PluginManager. This ensures that no third-party
     * code is ever executed.
     */
    public function disablePlugins(): void
    {
        foreach ($this->installers as $i => $installer) {
            if (!$installer instanceof PluginInstaller) {
                continue;
            }

            $installer->disablePlugins();
        }
    }

    /**
     * Returns installer for a specific package type.
     *
     * @param string $type package type
     *
     * @throws \InvalidArgumentException if installer for provided type is not registered
     */
    public function getInstaller(string $type): InstallerInterface
    {
        $type = strtolower($type);

        if (isset($this->cache[$type])) {
            return $this->cache[$type];
        }

        foreach ($this->installers as $installer) {
            if ($installer->supports($type)) {
                return $this->cache[$type] = $installer;
            }
        }

        throw new \InvalidArgumentException('Unknown installer type: '.$type);
    }

    /**
     * Checks whether provided package is installed in one of the registered installers.
     *
     * @param InstalledRepositoryInterface $repo    repository in which to check
     * @param PackageInterface             $package package instance
     */
    public function isPackageInstalled(InstalledRepositoryInterface $repo, PackageInterface $package): bool
    {
        if ($package instanceof AliasPackage) {
            return $repo->hasPackage($package) && $this->isPackageInstalled($repo, $package->getAliasOf());
        }

        return $this->getInstaller($package->getType())->isInstalled($repo, $package);
    }

    /**
     * Install binary for the given package.
     * If the installer associated to this package doesn't handle that function, it'll do nothing.
     *
     * @param PackageInterface $package Package instance
     */
    public function ensureBinariesPresence(PackageInterface $package): void
    {
        try {
            $installer = $this->getInstaller($package->getType());
        } catch (\InvalidArgumentException $e) {
            // no installer found for the current package type (@see `getInstaller()`)
            return;
        }

        // if the given installer support installing binaries
        if ($installer instanceof BinaryPresenceInterface) {
            $installer->ensureBinariesPresence($package);
        }
    }

    /**
     * Executes solver operation.
     *
     * @param InstalledRepositoryInterface $repo         repository in which to add/remove/update packages
     * @param OperationInterface[]         $operations   operations to execute
     * @param bool                         $devMode      whether the install is being run in dev mode
     * @param bool                         $runScripts   whether to dispatch script events
     * @param bool                         $downloadOnly whether to only download packages
     */
    public function execute(InstalledRepositoryInterface $repo, array $operations, bool $devMode = true, bool $runScripts = true, bool $downloadOnly = false): void
    {
        /** @var array<callable(): ?PromiseInterface<void|null>> $cleanupPromises */
        $cleanupPromises = [];

        $signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) use (&$cleanupPromises) {
            $this->io->writeError('Received '.$signal.', aborting', true, IOInterface::DEBUG);
            $this->runCleanup($cleanupPromises);
            $handler->exitWithLastSignal();
        });

        try {
            // execute operations in batches to make sure download-modifying-plugins are installed
            // before the other packages get downloaded
            $batches = [];
            $batch = [];
            foreach ($operations as $index => $operation) {
                if ($operation instanceof UpdateOperation || $operation instanceof InstallOperation) {
                    $package = $operation instanceof UpdateOperation ? $operation->getTargetPackage() : $operation->getPackage();
                    if ($package->getType() === 'composer-plugin') {
                        $extra = $package->getExtra();
                        if (isset($extra['plugin-modifies-downloads']) && $extra['plugin-modifies-downloads'] === true) {
                            if (count($batch) > 0) {
                                $batches[] = $batch;
                            }
                            $batches[] = [$index => $operation];
                            $batch = [];

                            continue;
                        }
                    }
                }
                $batch[$index] = $operation;
            }

            if (count($batch) > 0) {
                $batches[] = $batch;
            }

            foreach ($batches as $batchToExecute) {
                $this->downloadAndExecuteBatch($repo, $batchToExecute, $cleanupPromises, $devMode, $runScripts, $downloadOnly, $operations);
            }
        } catch (\Exception $e) {
            $this->runCleanup($cleanupPromises);

            throw $e;
        } finally {
            $signalHandler->unregister();
        }

        if ($downloadOnly) {
            return;
        }

        // do a last write so that we write the repository even if nothing changed
        // as that can trigger an update of some files like InstalledVersions.php if
        // running a new composer version
        $repo->write($devMode, $this);
    }

    /**
     * @param OperationInterface[] $operations    List of operations to execute in this batch
     * @param OperationInterface[] $allOperations Complete list of operations to be executed in the install job, used for event listeners
     * @phpstan-param array<callable(): ?PromiseInterface<void|null>> $cleanupPromises
     */
    private function downloadAndExecuteBatch(InstalledRepositoryInterface $repo, array $operations, array &$cleanupPromises, bool $devMode, bool $runScripts, bool $downloadOnly, array $allOperations): void
    {
        $promises = [];

        foreach ($operations as $index => $operation) {
            $opType = $operation->getOperationType();

            // ignoring alias ops as they don't need to execute anything at this stage
            if (!in_array($opType, ['update', 'install', 'uninstall'], true)) {
                continue;
            }

            if ($opType === 'update') {
                /** @var UpdateOperation $operation */
                $package = $operation->getTargetPackage();
                $initialPackage = $operation->getInitialPackage();
            } else {
                /** @var InstallOperation|MarkAliasInstalledOperation|MarkAliasUninstalledOperation|UninstallOperation $operation */
                $package = $operation->getPackage();
                $initialPackage = null;
            }
            $installer = $this->getInstaller($package->getType());

            $cleanupPromises[$index] = static function () use ($opType, $installer, $package, $initialPackage): ?PromiseInterface {
                // avoid calling cleanup if the download was not even initialized for a package
                // as without installation source configured nothing will work
                if (null === $package->getInstallationSource()) {
                    return \React\Promise\resolve(null);
                }

                return $installer->cleanup($opType, $package, $initialPackage);
            };

            if ($opType !== 'uninstall') {
                $promise = $installer->download($package, $initialPackage);
                if (null !== $promise) {
                    $promises[] = $promise;
                }
            }
        }

        // execute all downloads first
        if (count($promises) > 0) {
            $this->waitOnPromises($promises);
        }

        if ($downloadOnly) {
            $this->runCleanup($cleanupPromises);

            return;
        }

        // execute operations in batches to make sure every plugin is installed in the
        // right order and activated before the packages depending on it are installed
        $batches = [];
        $batch = [];
        foreach ($operations as $index => $operation) {
            if ($operation instanceof InstallOperation || $operation instanceof UpdateOperation) {
                $package = $operation instanceof UpdateOperation ? $operation->getTargetPackage() : $operation->getPackage();
                if ($package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer') {
                    if (count($batch) > 0) {
                        $batches[] = $batch;
                    }
                    $batches[] = [$index => $operation];
                    $batch = [];

                    continue;
                }
            }
            $batch[$index] = $operation;
        }

        if (count($batch) > 0) {
            $batches[] = $batch;
        }

        foreach ($batches as $batchToExecute) {
            $this->executeBatch($repo, $batchToExecute, $cleanupPromises, $devMode, $runScripts, $allOperations);
        }
    }

    /**
     * @param OperationInterface[] $operations    List of operations to execute in this batch
     * @param OperationInterface[] $allOperations Complete list of operations to be executed in the install job, used for event listeners
     * @phpstan-param array<callable(): ?PromiseInterface<void|null>> $cleanupPromises
     */
    private function executeBatch(InstalledRepositoryInterface $repo, array $operations, array $cleanupPromises, bool $devMode, bool $runScripts, array $allOperations): void
    {
        $promises = [];
        $postExecCallbacks = [];

        foreach ($operations as $index => $operation) {
            $opType = $operation->getOperationType();

            // ignoring alias ops as they don't need to execute anything
            if (!in_array($opType, ['update', 'install', 'uninstall'], true)) {
                // output alias ops in debug verbosity as they have no output otherwise
                if ($this->io->isDebug()) {
                    $this->io->writeError('  - ' . $operation->show(false));
                }
                $this->{$opType}($repo, $operation);

                continue;
            }

            if ($opType === 'update') {
                /** @var UpdateOperation $operation */
                $package = $operation->getTargetPackage();
                $initialPackage = $operation->getInitialPackage();
            } else {
                /** @var InstallOperation|MarkAliasInstalledOperation|MarkAliasUninstalledOperation|UninstallOperation $operation */
                $package = $operation->getPackage();
                $initialPackage = null;
            }

            $installer = $this->getInstaller($package->getType());

            $eventName = [
                'install' => PackageEvents::PRE_PACKAGE_INSTALL,
                'update' => PackageEvents::PRE_PACKAGE_UPDATE,
                'uninstall' => PackageEvents::PRE_PACKAGE_UNINSTALL,
            ][$opType];

            if ($runScripts && $this->eventDispatcher !== null) {
                $this->eventDispatcher->dispatchPackageEvent($eventName, $devMode, $repo, $allOperations, $operation);
            }

            $dispatcher = $this->eventDispatcher;
            $io = $this->io;

            $promise = $installer->prepare($opType, $package, $initialPackage);
            if (!$promise instanceof PromiseInterface) {
                $promise = \React\Promise\resolve(null);
            }

            $promise = $promise->then(function () use ($opType, $repo, $operation) {
                return $this->{$opType}($repo, $operation);
            })->then($cleanupPromises[$index])
            ->then(function () use ($devMode, $repo): void {
                $repo->write($devMode, $this);
            }, static function ($e) use ($opType, $package, $io): void {
                $io->writeError('    <error>' . ucfirst($opType) .' of '.$package->getPrettyName().' failed</error>');

                throw $e;
            });

            $eventName = [
                'install' => PackageEvents::POST_PACKAGE_INSTALL,
                'update' => PackageEvents::POST_PACKAGE_UPDATE,
                'uninstall' => PackageEvents::POST_PACKAGE_UNINSTALL,
            ][$opType];

            if ($runScripts && $dispatcher !== null) {
                $postExecCallbacks[] = static function () use ($dispatcher, $eventName, $devMode, $repo, $allOperations, $operation): void {
                    $dispatcher->dispatchPackageEvent($eventName, $devMode, $repo, $allOperations, $operation);
                };
            }

            $promises[] = $promise;
        }

        // execute all prepare => installs/updates/removes => cleanup steps
        if (count($promises) > 0) {
            $this->waitOnPromises($promises);
        }

        Platform::workaroundFilesystemIssues();

        foreach ($postExecCallbacks as $cb) {
            $cb();
        }
    }

    /**
     * @param array<PromiseInterface<void|null>> $promises
     */
    private function waitOnPromises(array $promises): void
    {
        $progress = null;
        if (
            $this->outputProgress
            && $this->io instanceof ConsoleIO
            && !((bool) Platform::getEnv('CI'))
            && !$this->io->isDebug()
            && count($promises) > 1
        ) {
            $progress = $this->io->getProgressBar();
        }
        $this->loop->wait($promises, $progress);
        if ($progress !== null) {
            $progress->clear();
            // ProgressBar in non-decorated output does not output a final line-break and clear() does nothing
            if (!$this->io->isDecorated()) {
                $this->io->writeError('');
            }
        }
    }

    /**
     * Executes download operation.
     *
     * @phpstan-return PromiseInterface<void|null>|null
     */
    public function download(PackageInterface $package): ?PromiseInterface
    {
        $installer = $this->getInstaller($package->getType());
        $promise = $installer->cleanup("install", $package);

        return $promise;
    }

    /**
     * Executes install operation.
     *
     * @param InstalledRepositoryInterface $repo      repository in which to check
     * @param InstallOperation             $operation operation instance
     * @phpstan-return PromiseInterface<void|null>|null
     */
    public function install(InstalledRepositoryInterface $repo, InstallOperation $operation): ?PromiseInterface
    {
        $package = $operation->getPackage();
        $installer = $this->getInstaller($package->getType());
        $promise = $installer->install($repo, $package);
        $this->markForNotification($package);

        return $promise;
    }

    /**
     * Executes update operation.
     *
     * @param InstalledRepositoryInterface $repo      repository in which to check
     * @param UpdateOperation              $operation operation instance
     * @phpstan-return PromiseInterface<void|null>|null
     */
    public function update(InstalledRepositoryInterface $repo, UpdateOperation $operation): ?PromiseInterface
    {
        $initial = $operation->getInitialPackage();
        $target = $operation->getTargetPackage();

        $initialType = $initial->getType();
        $targetType = $target->getType();

        if ($initialType === $targetType) {
            $installer = $this->getInstaller($initialType);
            $promise = $installer->update($repo, $initial, $target);
            $this->markForNotification($target);
        } else {
            $promise = $this->getInstaller($initialType)->uninstall($repo, $initial);
            if (!$promise instanceof PromiseInterface) {
                $promise = \React\Promise\resolve(null);
            }

            $installer = $this->getInstaller($targetType);
            $promise = $promise->then(static function () use ($installer, $repo, $target): PromiseInterface {
                $promise = $installer->install($repo, $target);
                if ($promise instanceof PromiseInterface) {
                    return $promise;
                }

                return \React\Promise\resolve(null);
            });
        }

        return $promise;
    }

    /**
     * Uninstalls package.
     *
     * @param InstalledRepositoryInterface $repo      repository in which to check
     * @param UninstallOperation           $operation operation instance
     * @phpstan-return PromiseInterface<void|null>|null
     */
    public function uninstall(InstalledRepositoryInterface $repo, UninstallOperation $operation): ?PromiseInterface
    {
        $package = $operation->getPackage();
        $installer = $this->getInstaller($package->getType());

        return $installer->uninstall($repo, $package);
    }

    /**
     * Executes markAliasInstalled operation.
     *
     * @param InstalledRepositoryInterface $repo      repository in which to check
     * @param MarkAliasInstalledOperation  $operation operation instance
     */
    public function markAliasInstalled(InstalledRepositoryInterface $repo, MarkAliasInstalledOperation $operation): void
    {
        $package = $operation->getPackage();

        if (!$repo->hasPackage($package)) {
            $repo->addPackage(clone $package);
        }
    }

    /**
     * Executes markAlias operation.
     *
     * @param InstalledRepositoryInterface  $repo      repository in which to check
     * @param MarkAliasUninstalledOperation $operation operation instance
     */
    public function markAliasUninstalled(InstalledRepositoryInterface $repo, MarkAliasUninstalledOperation $operation): void
    {
        $package = $operation->getPackage();

        $repo->removePackage($package);
    }

    /**
     * Returns the installation path of a package
     *
     * @return string|null absolute path to install to, which does not end with a slash, or null if the package does not have anything installed on disk
     */
    public function getInstallPath(PackageInterface $package): ?string
    {
        $installer = $this->getInstaller($package->getType());

        return $installer->getInstallPath($package);
    }

    public function setOutputProgress(bool $outputProgress): void
    {
        $this->outputProgress = $outputProgress;
    }

    public function notifyInstalls(IOInterface $io): void
    {
        $promises = [];

        try {
            foreach ($this->notifiablePackages as $repoUrl => $packages) {
                // non-batch API, deprecated
                if (str_contains($repoUrl, '%package%')) {
                    foreach ($packages as $package) {
                        $url = str_replace('%package%', $package->getPrettyName(), $repoUrl);

                        $params = [
                            'version' => $package->getPrettyVersion(),
                            'version_normalized' => $package->getVersion(),
                        ];
                        $opts = [
                            'retry-auth-failure' => false,
                            'http' => [
                                'method' => 'POST',
                                'header' => ['Content-type: application/x-www-form-urlencoded'],
                                'content' => http_build_query($params, '', '&'),
                                'timeout' => 3,
                            ],
                        ];

                        $promises[] = $this->loop->getHttpDownloader()->add($url, $opts);
                    }

                    continue;
                }

                $postData = ['downloads' => []];
                foreach ($packages as $package) {
                    $packageNotification = [
                        'name' => $package->getPrettyName(),
                        'version' => $package->getVersion(),
                    ];
                    if (strpos($repoUrl, 'packagist.org/') !== false) {
                        if (isset(FileDownloader::$downloadMetadata[$package->getName()])) {
                            $packageNotification['downloaded'] = FileDownloader::$downloadMetadata[$package->getName()];
                        } else {
                            $packageNotification['downloaded'] = false;
                        }
                    }
                    $postData['downloads'][] = $packageNotification;
                }

                $opts = [
                    'retry-auth-failure' => false,
                    'http' => [
                        'method' => 'POST',
                        'header' => ['Content-Type: application/json'],
                        'content' => json_encode($postData),
                        'timeout' => 6,
                    ],
                ];

                $promises[] = $this->loop->getHttpDownloader()->add($repoUrl, $opts);
            }

            $this->loop->wait($promises);
        } catch (\Exception $e) {
        }

        $this->reset();
    }

    private function markForNotification(PackageInterface $package): void
    {
        if ($package->getNotificationUrl() !== null) {
            $this->notifiablePackages[$package->getNotificationUrl()][$package->getName()] = $package;
        }
    }

    /**
     * @phpstan-param array<callable(): ?PromiseInterface<void|null>> $cleanupPromises
     */
    private function runCleanup(array $cleanupPromises): void
    {
        $promises = [];

        $this->loop->abortJobs();

        foreach ($cleanupPromises as $cleanup) {
            $promises[] = new \React\Promise\Promise(static function ($resolve) use ($cleanup): void {
                $promise = $cleanup();
                if (!$promise instanceof PromiseInterface) {
                    $resolve(null);
                } else {
                    $promise->then(static function () use ($resolve): void {
                        $resolve(null);
                    });
                }
            });
        }

        if (count($promises) > 0) {
            $this->loop->wait($promises);
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

use Composer\Repository\InstalledRepositoryInterface;
use Composer\Package\PackageInterface;
use Composer\IO\IOInterface;
use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\UninstallOperation;

/**
 * Metapackage installation manager.
 *
 * @author Martin Hasoň <martin.hason@gmail.com>
 */
class MetapackageInstaller implements InstallerInterface
{
    /** @var IOInterface */
    private $io;

    public function __construct(IOInterface $io)
    {
        $this->io = $io;
    }

    /**
     * @inheritDoc
     */
    public function supports(string $packageType)
    {
        return $packageType === 'metapackage';
    }

    /**
     * @inheritDoc
     */
    public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        return $repo->hasPackage($package);
    }

    /**
     * @inheritDoc
     */
    public function download(PackageInterface $package, ?PackageInterface $prevPackage = null)
    {
        // noop
        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function prepare($type, PackageInterface $package, ?PackageInterface $prevPackage = null)
    {
        // noop
        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function cleanup($type, PackageInterface $package, ?PackageInterface $prevPackage = null)
    {
        // noop
        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        $this->io->writeError("  - " . InstallOperation::format($package));

        $repo->addPackage(clone $package);

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
    {
        if (!$repo->hasPackage($initial)) {
            throw new \InvalidArgumentException('Package is not installed: '.$initial);
        }

        $this->io->writeError("  - " . UpdateOperation::format($initial, $target));

        $repo->removePackage($initial);
        $repo->addPackage(clone $target);

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        if (!$repo->hasPackage($package)) {
            throw new \InvalidArgumentException('Package is not installed: '.$package);
        }

        $this->io->writeError("  - " . UninstallOperation::format($package));

        $repo->removePackage($package);

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     *
     * @return null
     */
    public function getInstallPath(PackageInterface $package)
    {
        return null;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\PartialComposer;
use Composer\Pcre\Preg;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Package\PackageInterface;
use Composer\Util\Filesystem;
use Composer\Util\Silencer;
use Composer\Util\Platform;
use React\Promise\PromiseInterface;
use Composer\Downloader\DownloadManager;

/**
 * Package installation manager.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface
{
    /** @var PartialComposer */
    protected $composer;
    /** @var string */
    protected $vendorDir;
    /** @var DownloadManager|null */
    protected $downloadManager;
    /** @var IOInterface */
    protected $io;
    /** @var string */
    protected $type;
    /** @var Filesystem */
    protected $filesystem;
    /** @var BinaryInstaller */
    protected $binaryInstaller;

    /**
     * Initializes library installer.
     */
    public function __construct(IOInterface $io, PartialComposer $composer, ?string $type = 'library', ?Filesystem $filesystem = null, ?BinaryInstaller $binaryInstaller = null)
    {
        $this->composer = $composer;
        $this->downloadManager = $composer instanceof Composer ? $composer->getDownloadManager() : null;
        $this->io = $io;
        $this->type = $type;

        $this->filesystem = $filesystem ?: new Filesystem();
        $this->vendorDir = rtrim($composer->getConfig()->get('vendor-dir'), '/');
        $this->binaryInstaller = $binaryInstaller ?: new BinaryInstaller($this->io, rtrim($composer->getConfig()->get('bin-dir'), '/'), $composer->getConfig()->get('bin-compat'), $this->filesystem, $this->vendorDir);
    }

    /**
     * @inheritDoc
     */
    public function supports(string $packageType)
    {
        return $packageType === $this->type || null === $this->type;
    }

    /**
     * @inheritDoc
     */
    public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        if (!$repo->hasPackage($package)) {
            return false;
        }

        $installPath = $this->getInstallPath($package);

        if (Filesystem::isReadable($installPath)) {
            return true;
        }

        if (Platform::isWindows() && $this->filesystem->isJunction($installPath)) {
            return true;
        }

        if (is_link($installPath)) {
            if (realpath($installPath) === false) {
                return false;
            }

            return true;
        }

        return false;
    }

    /**
     * @inheritDoc
     */
    public function download(PackageInterface $package, ?PackageInterface $prevPackage = null)
    {
        $this->initializeVendorDir();
        $downloadPath = $this->getInstallPath($package);

        return $this->getDownloadManager()->download($package, $downloadPath, $prevPackage);
    }

    /**
     * @inheritDoc
     */
    public function prepare($type, PackageInterface $package, ?PackageInterface $prevPackage = null)
    {
        $this->initializeVendorDir();
        $downloadPath = $this->getInstallPath($package);

        return $this->getDownloadManager()->prepare($type, $package, $downloadPath, $prevPackage);
    }

    /**
     * @inheritDoc
     */
    public function cleanup($type, PackageInterface $package, ?PackageInterface $prevPackage = null)
    {
        $this->initializeVendorDir();
        $downloadPath = $this->getInstallPath($package);

        return $this->getDownloadManager()->cleanup($type, $package, $downloadPath, $prevPackage);
    }

    /**
     * @inheritDoc
     */
    public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        $this->initializeVendorDir();
        $downloadPath = $this->getInstallPath($package);

        // remove the binaries if it appears the package files are missing
        if (!Filesystem::isReadable($downloadPath) && $repo->hasPackage($package)) {
            $this->binaryInstaller->removeBinaries($package);
        }

        $promise = $this->installCode($package);
        if (!$promise instanceof PromiseInterface) {
            $promise = \React\Promise\resolve(null);
        }

        $binaryInstaller = $this->binaryInstaller;
        $installPath = $this->getInstallPath($package);

        return $promise->then(static function () use ($binaryInstaller, $installPath, $package, $repo): void {
            $binaryInstaller->installBinaries($package, $installPath);
            if (!$repo->hasPackage($package)) {
                $repo->addPackage(clone $package);
            }
        });
    }

    /**
     * @inheritDoc
     */
    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
    {
        if (!$repo->hasPackage($initial)) {
            throw new \InvalidArgumentException('Package is not installed: '.$initial);
        }

        $this->initializeVendorDir();

        $this->binaryInstaller->removeBinaries($initial);
        $promise = $this->updateCode($initial, $target);
        if (!$promise instanceof PromiseInterface) {
            $promise = \React\Promise\resolve(null);
        }

        $binaryInstaller = $this->binaryInstaller;
        $installPath = $this->getInstallPath($target);

        return $promise->then(static function () use ($binaryInstaller, $installPath, $target, $initial, $repo): void {
            $binaryInstaller->installBinaries($target, $installPath);
            $repo->removePackage($initial);
            if (!$repo->hasPackage($target)) {
                $repo->addPackage(clone $target);
            }
        });
    }

    /**
     * @inheritDoc
     */
    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        if (!$repo->hasPackage($package)) {
            throw new \InvalidArgumentException('Package is not installed: '.$package);
        }

        $promise = $this->removeCode($package);
        if (!$promise instanceof PromiseInterface) {
            $promise = \React\Promise\resolve(null);
        }

        $binaryInstaller = $this->binaryInstaller;
        $downloadPath = $this->getPackageBasePath($package);
        $filesystem = $this->filesystem;

        return $promise->then(static function () use ($binaryInstaller, $filesystem, $downloadPath, $package, $repo): void {
            $binaryInstaller->removeBinaries($package);
            $repo->removePackage($package);

            if (strpos($package->getName(), '/')) {
                $packageVendorDir = dirname($downloadPath);
                if (is_dir($packageVendorDir) && $filesystem->isDirEmpty($packageVendorDir)) {
                    Silencer::call('rmdir', $packageVendorDir);
                }
            }
        });
    }

    /**
     * @inheritDoc
     *
     * @return string
     */
    public function getInstallPath(PackageInterface $package)
    {
        $this->initializeVendorDir();

        $basePath = ($this->vendorDir ? $this->vendorDir.'/' : '') . $package->getPrettyName();
        $targetDir = $package->getTargetDir();

        return $basePath . ($targetDir ? '/'.$targetDir : '');
    }

    /**
     * Make sure binaries are installed for a given package.
     *
     * @param PackageInterface $package Package instance
     */
    public function ensureBinariesPresence(PackageInterface $package)
    {
        $this->binaryInstaller->installBinaries($package, $this->getInstallPath($package), false);
    }

    /**
     * Returns the base path of the package without target-dir path
     *
     * It is used for BC as getInstallPath tends to be overridden by
     * installer plugins but not getPackageBasePath
     *
     * @return string
     */
    protected function getPackageBasePath(PackageInterface $package)
    {
        $installPath = $this->getInstallPath($package);
        $targetDir = $package->getTargetDir();

        if ($targetDir) {
            return Preg::replace('{/*'.str_replace('/', '/+', preg_quote($targetDir)).'/?$}', '', $installPath);
        }

        return $installPath;
    }

    /**
     * @return PromiseInterface|null
     * @phpstan-return PromiseInterface<void|null>|null
     */
    protected function installCode(PackageInterface $package)
    {
        $downloadPath = $this->getInstallPath($package);

        return $this->getDownloadManager()->install($package, $downloadPath);
    }

    /**
     * @return PromiseInterface|null
     * @phpstan-return PromiseInterface<void|null>|null
     */
    protected function updateCode(PackageInterface $initial, PackageInterface $target)
    {
        $initialDownloadPath = $this->getInstallPath($initial);
        $targetDownloadPath = $this->getInstallPath($target);
        if ($targetDownloadPath !== $initialDownloadPath) {
            // if the target and initial dirs intersect, we force a remove + install
            // to avoid the rename wiping the target dir as part of the initial dir cleanup
            if (strpos($initialDownloadPath, $targetDownloadPath) === 0
                || strpos($targetDownloadPath, $initialDownloadPath) === 0
            ) {
                $promise = $this->removeCode($initial);
                if (!$promise instanceof PromiseInterface) {
                    $promise = \React\Promise\resolve(null);
                }

                return $promise->then(function () use ($target): PromiseInterface {
                    $promise = $this->installCode($target);
                    if ($promise instanceof PromiseInterface) {
                        return $promise;
                    }

                    return \React\Promise\resolve(null);
                });
            }

            $this->filesystem->rename($initialDownloadPath, $targetDownloadPath);
        }

        return $this->getDownloadManager()->update($initial, $target, $targetDownloadPath);
    }

    /**
     * @return PromiseInterface|null
     * @phpstan-return PromiseInterface<void|null>|null
     */
    protected function removeCode(PackageInterface $package)
    {
        $downloadPath = $this->getPackageBasePath($package);

        return $this->getDownloadManager()->remove($package, $downloadPath);
    }

    /**
     * @return void
     */
    protected function initializeVendorDir()
    {
        $this->filesystem->ensureDirectoryExists($this->vendorDir);
        $this->vendorDir = realpath($this->vendorDir);
    }

    protected function getDownloadManager(): DownloadManager
    {
        assert($this->downloadManager instanceof DownloadManager, new \LogicException(self::class.' should be initialized with a fully loaded Composer instance to be able to install/... packages'));

        return $this->downloadManager;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

use Composer\Package\PackageInterface;

/**
 * Interface for the package installation manager that handle binary installation.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface BinaryPresenceInterface
{
    /**
     * Make sure binaries are installed for a given package.
     *
     * @param PackageInterface $package package instance
     *
     * @return void
     */
    public function ensureBinariesPresence(PackageInterface $package);
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\PartialComposer;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Package\PackageInterface;
use Composer\Plugin\PluginManager;
use Composer\Util\Filesystem;
use Composer\Util\Platform;
use React\Promise\PromiseInterface;

/**
 * Installer for plugin packages
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Nils Adermann <naderman@naderman.de>
 */
class PluginInstaller extends LibraryInstaller
{
    public function __construct(IOInterface $io, PartialComposer $composer, ?Filesystem $fs = null, ?BinaryInstaller $binaryInstaller = null)
    {
        parent::__construct($io, $composer, 'composer-plugin', $fs, $binaryInstaller);
    }

    /**
     * @inheritDoc
     */
    public function supports(string $packageType)
    {
        return $packageType === 'composer-plugin' || $packageType === 'composer-installer';
    }

    public function disablePlugins(): void
    {
        $this->getPluginManager()->disablePlugins();
    }

    /**
     * @inheritDoc
     */
    public function prepare($type, PackageInterface $package, ?PackageInterface $prevPackage = null)
    {
        // fail install process early if it is going to fail due to a plugin not being allowed
        if (($type === 'install' || $type === 'update') && !$this->getPluginManager()->arePluginsDisabled('local')) {
            $this->getPluginManager()->isPluginAllowed($package->getName(), false, true === ($package->getExtra()['plugin-optional'] ?? false));
        }

        return parent::prepare($type, $package, $prevPackage);
    }

    /**
     * @inheritDoc
     */
    public function download(PackageInterface $package, ?PackageInterface $prevPackage = null)
    {
        $extra = $package->getExtra();
        if (empty($extra['class'])) {
            throw new \UnexpectedValueException('Error while installing '.$package->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.');
        }

        return parent::download($package, $prevPackage);
    }

    /**
     * @inheritDoc
     */
    public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        $promise = parent::install($repo, $package);
        if (!$promise instanceof PromiseInterface) {
            $promise = \React\Promise\resolve(null);
        }

        return $promise->then(function () use ($package, $repo): void {
            try {
                Platform::workaroundFilesystemIssues();
                $this->getPluginManager()->registerPackage($package, true);
            } catch (\Exception $e) {
                $this->rollbackInstall($e, $repo, $package);
            }
        });
    }

    /**
     * @inheritDoc
     */
    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
    {
        $promise = parent::update($repo, $initial, $target);
        if (!$promise instanceof PromiseInterface) {
            $promise = \React\Promise\resolve(null);
        }

        return $promise->then(function () use ($initial, $target, $repo): void {
            try {
                Platform::workaroundFilesystemIssues();
                $this->getPluginManager()->deactivatePackage($initial);
                $this->getPluginManager()->registerPackage($target, true);
            } catch (\Exception $e) {
                $this->rollbackInstall($e, $repo, $target);
            }
        });
    }

    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        $this->getPluginManager()->uninstallPackage($package);

        return parent::uninstall($repo, $package);
    }

    private function rollbackInstall(\Exception $e, InstalledRepositoryInterface $repo, PackageInterface $package): void
    {
        $this->io->writeError('Plugin initialization failed ('.$e->getMessage().'), uninstalling plugin');
        parent::uninstall($repo, $package);
        throw $e;
    }

    protected function getPluginManager(): PluginManager
    {
        assert($this->composer instanceof Composer, new \LogicException(self::class.' should be initialized with a fully loaded Composer instance.'));
        $pluginManager = $this->composer->getPluginManager();

        return $pluginManager;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Pcre\Preg;
use Composer\Repository\InstalledRepository;
use Symfony\Component\Console\Formatter\OutputFormatter;

/**
 * Add suggested packages from different places to output them in the end.
 *
 * @author Haralan Dobrev <hkdobrev@gmail.com>
 */
class SuggestedPackagesReporter
{
    public const MODE_LIST = 1;
    public const MODE_BY_PACKAGE = 2;
    public const MODE_BY_SUGGESTION = 4;

    /**
     * @var array<array{source: string, target: string, reason: string}>
     */
    protected $suggestedPackages = [];

    /**
     * @var IOInterface
     */
    private $io;

    public function __construct(IOInterface $io)
    {
        $this->io = $io;
    }

    /**
     * @return array<array{source: string, target: string, reason: string}> Suggested packages with source, target and reason keys.
     */
    public function getPackages(): array
    {
        return $this->suggestedPackages;
    }

    /**
     * Add suggested packages to be listed after install
     *
     * Could be used to add suggested packages both from the installer
     * or from CreateProjectCommand.
     *
     * @param  string                    $source Source package which made the suggestion
     * @param  string                    $target Target package to be suggested
     * @param  string                    $reason Reason the target package to be suggested
     */
    public function addPackage(string $source, string $target, string $reason): SuggestedPackagesReporter
    {
        $this->suggestedPackages[] = [
            'source' => $source,
            'target' => $target,
            'reason' => $reason,
        ];

        return $this;
    }

    /**
     * Add all suggestions from a package.
     */
    public function addSuggestionsFromPackage(PackageInterface $package): SuggestedPackagesReporter
    {
        $source = $package->getPrettyName();
        foreach ($package->getSuggests() as $target => $reason) {
            $this->addPackage(
                $source,
                $target,
                $reason
            );
        }

        return $this;
    }

    /**
     * Output suggested packages.
     *
     * Do not list the ones already installed if installed repository provided.
     *
     * @param  int                      $mode             One of the MODE_* constants from this class
     * @param  InstalledRepository|null $installedRepo    If passed in, suggested packages which are installed already will be skipped
     * @param  PackageInterface|null    $onlyDependentsOf If passed in, only the suggestions from direct dependents of that package, or from the package itself, will be shown
     */
    public function output(int $mode, ?InstalledRepository $installedRepo = null, ?PackageInterface $onlyDependentsOf = null): void
    {
        $suggestedPackages = $this->getFilteredSuggestions($installedRepo, $onlyDependentsOf);

        $suggesters = [];
        $suggested = [];
        foreach ($suggestedPackages as $suggestion) {
            $suggesters[$suggestion['source']][$suggestion['target']] = $suggestion['reason'];
            $suggested[$suggestion['target']][$suggestion['source']] = $suggestion['reason'];
        }
        ksort($suggesters);
        ksort($suggested);

        // Simple mode
        if ($mode & self::MODE_LIST) {
            foreach (array_keys($suggested) as $name) {
                $this->io->write(sprintf('<info>%s</info>', $name));
            }

            return;
        }

        // Grouped by package
        if ($mode & self::MODE_BY_PACKAGE) {
            foreach ($suggesters as $suggester => $suggestions) {
                $this->io->write(sprintf('<comment>%s</comment> suggests:', $suggester));

                foreach ($suggestions as $suggestion => $reason) {
                    $this->io->write(sprintf(' - <info>%s</info>' . ($reason ? ': %s' : ''), $suggestion, $this->escapeOutput($reason)));
                }
                $this->io->write('');
            }
        }

        // Grouped by suggestion
        if ($mode & self::MODE_BY_SUGGESTION) {
            // Improve readability in full mode
            if ($mode & self::MODE_BY_PACKAGE) {
                $this->io->write(str_repeat('-', 78));
            }
            foreach ($suggested as $suggestion => $suggesters) {
                $this->io->write(sprintf('<comment>%s</comment> is suggested by:', $suggestion));

                foreach ($suggesters as $suggester => $reason) {
                    $this->io->write(sprintf(' - <info>%s</info>' . ($reason ? ': %s' : ''), $suggester, $this->escapeOutput($reason)));
                }
                $this->io->write('');
            }
        }

        if ($onlyDependentsOf) {
            $allSuggestedPackages = $this->getFilteredSuggestions($installedRepo);
            $diff = count($allSuggestedPackages) - count($suggestedPackages);
            if ($diff) {
                $this->io->write('<info>'.$diff.' additional suggestions</info> by transitive dependencies can be shown with <info>--all</info>');
            }
        }
    }

    /**
     * Output number of new suggested packages and a hint to use suggest command.
     *
     * @param  InstalledRepository|null $installedRepo    If passed in, suggested packages which are installed already will be skipped
     * @param  PackageInterface|null    $onlyDependentsOf If passed in, only the suggestions from direct dependents of that package, or from the package itself, will be shown
     */
    public function outputMinimalistic(?InstalledRepository $installedRepo = null, ?PackageInterface $onlyDependentsOf = null): void
    {
        $suggestedPackages = $this->getFilteredSuggestions($installedRepo, $onlyDependentsOf);
        if ($suggestedPackages) {
            $this->io->writeError('<info>'.count($suggestedPackages).' package suggestions were added by new dependencies, use `composer suggest` to see details.</info>');
        }
    }

    /**
     * @param  InstalledRepository|null $installedRepo    If passed in, suggested packages which are installed already will be skipped
     * @param  PackageInterface|null    $onlyDependentsOf If passed in, only the suggestions from direct dependents of that package, or from the package itself, will be shown
     * @return mixed[]
     */
    private function getFilteredSuggestions(?InstalledRepository $installedRepo = null, ?PackageInterface $onlyDependentsOf = null): array
    {
        $suggestedPackages = $this->getPackages();
        $installedNames = [];
        if (null !== $installedRepo && !empty($suggestedPackages)) {
            foreach ($installedRepo->getPackages() as $package) {
                $installedNames = array_merge(
                    $installedNames,
                    $package->getNames()
                );
            }
        }

        $sourceFilter = [];
        if ($onlyDependentsOf) {
            $sourceFilter = array_map(static function ($link): string {
                return $link->getTarget();
            }, array_merge($onlyDependentsOf->getRequires(), $onlyDependentsOf->getDevRequires()));
            $sourceFilter[] = $onlyDependentsOf->getName();
        }

        $suggestions = [];
        foreach ($suggestedPackages as $suggestion) {
            if (in_array($suggestion['target'], $installedNames) || (\count($sourceFilter) > 0 && !in_array($suggestion['source'], $sourceFilter))) {
                continue;
            }

            $suggestions[] = $suggestion;
        }

        return $suggestions;
    }

    private function escapeOutput(string $string): string
    {
        return OutputFormatter::escape(
            $this->removeControlCharacters($string)
        );
    }

    private function removeControlCharacters(string $string): string
    {
        return Preg::replace(
            '/[[:cntrl:]]/',
            '',
            str_replace("\n", ' ', $string)
        );
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\Config;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Pcre\Preg;
use Composer\Util\HttpDownloader;
use Composer\Util\ProcessExecutor;
use Composer\Json\JsonFile;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class RepositoryFactory
{
    /**
     * @return array|mixed
     */
    public static function configFromString(IOInterface $io, Config $config, string $repository, bool $allowFilesystem = false)
    {
        if (0 === strpos($repository, 'http')) {
            $repoConfig = ['type' => 'composer', 'url' => $repository];
        } elseif ("json" === pathinfo($repository, PATHINFO_EXTENSION)) {
            $json = new JsonFile($repository, Factory::createHttpDownloader($io, $config));
            $data = $json->read();
            if (!empty($data['packages']) || !empty($data['includes']) || !empty($data['provider-includes'])) {
                $repoConfig = ['type' => 'composer', 'url' => 'file://' . strtr(realpath($repository), '\\', '/')];
            } elseif ($allowFilesystem) {
                $repoConfig = ['type' => 'filesystem', 'json' => $json];
            } else {
                throw new \InvalidArgumentException("Invalid repository URL ($repository) given. This file does not contain a valid composer repository.");
            }
        } elseif (strpos($repository, '{') === 0) {
            // assume it is a json object that makes a repo config
            $repoConfig = JsonFile::parseJson($repository);
        } else {
            throw new \InvalidArgumentException("Invalid repository url ($repository) given. Has to be a .json file, an http url or a JSON object.");
        }

        return $repoConfig;
    }

    public static function fromString(IOInterface $io, Config $config, string $repository, bool $allowFilesystem = false, ?RepositoryManager $rm = null): RepositoryInterface
    {
        $repoConfig = static::configFromString($io, $config, $repository, $allowFilesystem);

        return static::createRepo($io, $config, $repoConfig, $rm);
    }

    /**
     * @param  array<string, mixed> $repoConfig
     */
    public static function createRepo(IOInterface $io, Config $config, array $repoConfig, ?RepositoryManager $rm = null): RepositoryInterface
    {
        if (!$rm) {
            @trigger_error('Not passing a repository manager when calling createRepo is deprecated since Composer 2.3.6', E_USER_DEPRECATED);
            $rm = static::manager($io, $config);
        }
        $repos = self::createRepos($rm, [$repoConfig]);

        return reset($repos);
    }

    /**
     * @return RepositoryInterface[]
     */
    public static function defaultRepos(?IOInterface $io = null, ?Config $config = null, ?RepositoryManager $rm = null): array
    {
        if (null === $rm) {
            @trigger_error('Not passing a repository manager when calling defaultRepos is deprecated since Composer 2.3.6, use defaultReposWithDefaultManager() instead if you cannot get a manager.', E_USER_DEPRECATED);
        }

        if (null === $config) {
            $config = Factory::createConfig($io);
        }
        if (null !== $io) {
            $io->loadConfiguration($config);
        }
        if (null === $rm) {
            if (null === $io) {
                throw new \InvalidArgumentException('This function requires either an IOInterface or a RepositoryManager');
            }
            $rm = static::manager($io, $config, Factory::createHttpDownloader($io, $config));
        }

        return self::createRepos($rm, $config->getRepositories());
    }

    public static function manager(IOInterface $io, Config $config, ?HttpDownloader $httpDownloader = null, ?EventDispatcher $eventDispatcher = null, ?ProcessExecutor $process = null): RepositoryManager
    {
        if ($httpDownloader === null) {
            $httpDownloader = Factory::createHttpDownloader($io, $config);
        }
        if ($process === null) {
            $process = new ProcessExecutor($io);
            $process->enableAsync();
        }

        $rm = new RepositoryManager($io, $config, $httpDownloader, $eventDispatcher, $process);
        $rm->setRepositoryClass('composer', 'Composer\Repository\ComposerRepository');
        $rm->setRepositoryClass('vcs', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('package', 'Composer\Repository\PackageRepository');
        $rm->setRepositoryClass('pear', 'Composer\Repository\PearRepository');
        $rm->setRepositoryClass('git', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('bitbucket', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('git-bitbucket', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('github', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('gitlab', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('svn', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('fossil', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('perforce', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('hg', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('artifact', 'Composer\Repository\ArtifactRepository');
        $rm->setRepositoryClass('path', 'Composer\Repository\PathRepository');

        return $rm;
    }

    /**
     * @return RepositoryInterface[]
     */
    public static function defaultReposWithDefaultManager(IOInterface $io): array
    {
        $manager = RepositoryFactory::manager($io, $config = Factory::createConfig($io));
        $io->loadConfiguration($config);

        return RepositoryFactory::defaultRepos($io, $config, $manager);
    }

    /**
     * @param array<int|string, mixed> $repoConfigs
     *
     * @return RepositoryInterface[]
     */
    private static function createRepos(RepositoryManager $rm, array $repoConfigs): array
    {
        $repos = [];

        foreach ($repoConfigs as $index => $repo) {
            if (is_string($repo)) {
                throw new \UnexpectedValueException('"repositories" should be an array of repository definitions, only a single repository was given');
            }
            if (!is_array($repo)) {
                throw new \UnexpectedValueException('Repository "'.$index.'" ('.json_encode($repo).') should be an array, '.get_debug_type($repo).' given');
            }
            if (!isset($repo['type'])) {
                throw new \UnexpectedValueException('Repository "'.$index.'" ('.json_encode($repo).') must have a type defined');
            }

            $name = self::generateRepositoryName($index, $repo, $repos);
            if ($repo['type'] === 'filesystem') {
                $repos[$name] = new FilesystemRepository($repo['json']);
            } else {
                $repos[$name] = $rm->createRepository($repo['type'], $repo, (string) $index);
            }
        }

        return $repos;
    }

    /**
     * @param int|string $index
     * @param array{url?: string} $repo
     * @param array<int|string, mixed> $existingRepos
     */
    public static function generateRepositoryName($index, array $repo, array $existingRepos): string
    {
        $name = is_int($index) && isset($repo['url']) ? Preg::replace('{^https?://}i', '', $repo['url']) : (string) $index;
        while (isset($existingRepos[$name])) {
            $name .= '2';
        }

        return $name;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

/**
 * Installed filesystem repository.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class InstalledFilesystemRepository extends FilesystemRepository implements InstalledRepositoryInterface
{
    public function getRepoName()
    {
        return 'installed '.parent::getRepoName();
    }

    /**
     * @inheritDoc
     */
    public function isFresh()
    {
        return !$this->file->exists();
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Advisory\PartialSecurityAdvisory;
use Composer\Advisory\SecurityAdvisory;
use Composer\Package\BasePackage;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\PackageInterface;
use Composer\Package\AliasPackage;
use Composer\Package\CompletePackage;
use Composer\Package\CompleteAliasPackage;
use Composer\Package\Version\VersionParser;
use Composer\Package\Version\StabilityFilter;
use Composer\Json\JsonFile;
use Composer\Cache;
use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Pcre\Preg;
use Composer\Plugin\PostFileDownloadEvent;
use Composer\Semver\CompilingMatcher;
use Composer\Util\HttpDownloader;
use Composer\Util\Loop;
use Composer\Plugin\PluginEvents;
use Composer\Plugin\PreFileDownloadEvent;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Downloader\TransportException;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\MatchAllConstraint;
use Composer\Util\Http\Response;
use Composer\MetadataMinifier\MetadataMinifier;
use Composer\Util\Url;
use React\Promise\PromiseInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ComposerRepository extends ArrayRepository implements ConfigurableRepositoryInterface, AdvisoryProviderInterface
{
    /**
     * @var mixed[]
     * @phpstan-var array{url: string, options?: mixed[], type?: 'composer', allow_ssl_downgrade?: bool}
     */
    private $repoConfig;
    /** @var mixed[] */
    private $options;
    /** @var non-empty-string */
    private $url;
    /** @var non-empty-string */
    private $baseUrl;
    /** @var IOInterface */
    private $io;
    /** @var HttpDownloader */
    private $httpDownloader;
    /** @var Loop */
    private $loop;
    /** @var Cache */
    protected $cache;
    /** @var ?non-empty-string */
    protected $notifyUrl = null;
    /** @var ?non-empty-string */
    protected $searchUrl = null;
    /** @var ?non-empty-string a URL containing %package% which can be queried to get providers of a given name */
    protected $providersApiUrl = null;
    /** @var bool */
    protected $hasProviders = false;
    /** @var ?non-empty-string */
    protected $providersUrl = null;
    /** @var ?non-empty-string */
    protected $listUrl = null;
    /** @var bool Indicates whether a comprehensive list of packages this repository might provide is expressed in the repository root. **/
    protected $hasAvailablePackageList = false;
    /** @var ?array<string> */
    protected $availablePackages = null;
    /** @var ?array<non-empty-string> */
    protected $availablePackagePatterns = null;
    /** @var ?non-empty-string */
    protected $lazyProvidersUrl = null;
    /** @var ?array<string, array{sha256: string}> */
    protected $providerListing;
    /** @var ArrayLoader */
    protected $loader;
    /** @var bool */
    private $allowSslDowngrade = false;
    /** @var ?EventDispatcher */
    private $eventDispatcher;
    /** @var ?array<string, list<array{url: non-empty-string, preferred: bool}>> */
    private $sourceMirrors;
    /** @var ?list<array{url: non-empty-string, preferred: bool}> */
    private $distMirrors;
    /** @var bool */
    private $degradedMode = false;
    /** @var mixed[]|true */
    private $rootData;
    /** @var bool */
    private $hasPartialPackages = false;
    /** @var ?array<string, mixed[]> */
    private $partialPackagesByName = null;
    /** @var bool */
    private $displayedWarningAboutNonMatchingPackageIndex = false;
    /** @var array{metadata: bool, api-url: string|null}|null */
    private $securityAdvisoryConfig = null;

    /**
     * @var array list of package names which are fresh and can be loaded from the cache directly in case loadPackage is called several times
     *            useful for v2 metadata repositories with lazy providers
     * @phpstan-var array<string, true>
     */
    private $freshMetadataUrls = [];

    /**
     * @var array list of package names which returned a 404 and should not be re-fetched in case loadPackage is called several times
     *            useful for v2 metadata repositories with lazy providers
     * @phpstan-var array<string, true>
     */
    private $packagesNotFoundCache = [];

    /**
     * @var VersionParser
     */
    private $versionParser;

    /**
     * @param array<string, mixed> $repoConfig
     * @phpstan-param array{url: non-empty-string, options?: mixed[], type?: 'composer', allow_ssl_downgrade?: bool} $repoConfig
     */
    public function __construct(array $repoConfig, IOInterface $io, Config $config, HttpDownloader $httpDownloader, ?EventDispatcher $eventDispatcher = null)
    {
        parent::__construct();
        if (!Preg::isMatch('{^[\w.]+\??://}', $repoConfig['url'])) {
            if (($localFilePath = realpath($repoConfig['url'])) !== false) {
                // it is a local path, add file scheme
                $repoConfig['url'] = 'file://'.$localFilePath;
            } else {
                // otherwise, assume http as the default protocol
                $repoConfig['url'] = 'http://'.$repoConfig['url'];
            }
        }
        $repoConfig['url'] = rtrim($repoConfig['url'], '/');
        if ($repoConfig['url'] === '') {
            throw new \InvalidArgumentException('The repository url must not be an empty string');
        }

        if (str_starts_with($repoConfig['url'], 'https?')) {
            $repoConfig['url'] = (extension_loaded('openssl') ? 'https' : 'http') . substr($repoConfig['url'], 6);
        }

        $urlBits = parse_url(strtr($repoConfig['url'], '\\', '/'));
        if ($urlBits === false || empty($urlBits['scheme'])) {
            throw new \UnexpectedValueException('Invalid url given for Composer repository: '.$repoConfig['url']);
        }

        if (!isset($repoConfig['options'])) {
            $repoConfig['options'] = [];
        }
        if (isset($repoConfig['allow_ssl_downgrade']) && true === $repoConfig['allow_ssl_downgrade']) {
            $this->allowSslDowngrade = true;
        }

        $this->options = $repoConfig['options'];
        $this->url = $repoConfig['url'];

        // force url for packagist.org to repo.packagist.org
        if (Preg::isMatch('{^(?P<proto>https?)://packagist\.org/?$}i', $this->url, $match)) {
            $this->url = $match['proto'].'://repo.packagist.org';
        }

        $baseUrl = rtrim(Preg::replace('{(?:/[^/\\\\]+\.json)?(?:[?#].*)?$}', '', $this->url), '/');
        assert($baseUrl !== '');
        $this->baseUrl = $baseUrl;
        $this->io = $io;
        $this->cache = new Cache($io, $config->get('cache-repo-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($this->url)), 'a-z0-9.$~_');
        $this->cache->setReadOnly($config->get('cache-read-only'));
        $this->versionParser = new VersionParser();
        $this->loader = new ArrayLoader($this->versionParser);
        $this->httpDownloader = $httpDownloader;
        $this->eventDispatcher = $eventDispatcher;
        $this->repoConfig = $repoConfig;
        $this->loop = new Loop($this->httpDownloader);
    }

    public function getRepoName()
    {
        return 'composer repo ('.Url::sanitize($this->url).')';
    }

    public function getRepoConfig()
    {
        return $this->repoConfig;
    }

    /**
     * @inheritDoc
     */
    public function findPackage(string $name, $constraint)
    {
        // this call initializes loadRootServerFile which is needed for the rest below to work
        $hasProviders = $this->hasProviders();

        $name = strtolower($name);
        if (!$constraint instanceof ConstraintInterface) {
            $constraint = $this->versionParser->parseConstraints($constraint);
        }

        if ($this->lazyProvidersUrl) {
            if ($this->hasPartialPackages() && isset($this->partialPackagesByName[$name])) {
                return $this->filterPackages($this->whatProvides($name), $constraint, true);
            }

            if ($this->hasAvailablePackageList && !$this->lazyProvidersRepoContains($name)) {
                return null;
            }

            $packages = $this->loadAsyncPackages([$name => $constraint]);

            if (count($packages['packages']) > 0) {
                return reset($packages['packages']);
            }

            return null;
        }

        if ($hasProviders) {
            foreach ($this->getProviderNames() as $providerName) {
                if ($name === $providerName) {
                    return $this->filterPackages($this->whatProvides($providerName), $constraint, true);
                }
            }

            return null;
        }

        return parent::findPackage($name, $constraint);
    }

    /**
     * @inheritDoc
     */
    public function findPackages(string $name, $constraint = null)
    {
        // this call initializes loadRootServerFile which is needed for the rest below to work
        $hasProviders = $this->hasProviders();

        $name = strtolower($name);
        if (null !== $constraint && !$constraint instanceof ConstraintInterface) {
            $constraint = $this->versionParser->parseConstraints($constraint);
        }

        if ($this->lazyProvidersUrl) {
            if ($this->hasPartialPackages() && isset($this->partialPackagesByName[$name])) {
                return $this->filterPackages($this->whatProvides($name), $constraint);
            }

            if ($this->hasAvailablePackageList && !$this->lazyProvidersRepoContains($name)) {
                return [];
            }

            $result = $this->loadAsyncPackages([$name => $constraint]);

            return $result['packages'];
        }

        if ($hasProviders) {
            foreach ($this->getProviderNames() as $providerName) {
                if ($name === $providerName) {
                    return $this->filterPackages($this->whatProvides($providerName), $constraint);
                }
            }

            return [];
        }

        return parent::findPackages($name, $constraint);
    }

    /**
     * @param array<BasePackage> $packages
     *
     * @return BasePackage|array<BasePackage>|null
     */
    private function filterPackages(array $packages, ?ConstraintInterface $constraint = null, bool $returnFirstMatch = false)
    {
        if (null === $constraint) {
            if ($returnFirstMatch) {
                return reset($packages);
            }

            return $packages;
        }

        $filteredPackages = [];

        foreach ($packages as $package) {
            $pkgConstraint = new Constraint('==', $package->getVersion());

            if ($constraint->matches($pkgConstraint)) {
                if ($returnFirstMatch) {
                    return $package;
                }

                $filteredPackages[] = $package;
            }
        }

        if ($returnFirstMatch) {
            return null;
        }

        return $filteredPackages;
    }

    public function getPackages()
    {
        $hasProviders = $this->hasProviders();

        if ($this->lazyProvidersUrl) {
            if (is_array($this->availablePackages) && !$this->availablePackagePatterns) {
                $packageMap = [];
                foreach ($this->availablePackages as $name) {
                    $packageMap[$name] = new MatchAllConstraint();
                }

                $result = $this->loadAsyncPackages($packageMap);

                return array_values($result['packages']);
            }

            if ($this->hasPartialPackages()) {
                if (!is_array($this->partialPackagesByName)) {
                    throw new \LogicException('hasPartialPackages failed to initialize $this->partialPackagesByName');
                }

                return $this->createPackages($this->partialPackagesByName, 'packages.json inline packages');
            }

            throw new \LogicException('Composer repositories that have lazy providers and no available-packages list can not load the complete list of packages, use getPackageNames instead.');
        }

        if ($hasProviders) {
            throw new \LogicException('Composer repositories that have providers can not load the complete list of packages, use getPackageNames instead.');
        }

        return parent::getPackages();
    }

    /**
     * @param string|null $packageFilter Package pattern filter which can include "*" as a wildcard
     *
     * @return string[]
     */
    public function getPackageNames(?string $packageFilter = null)
    {
        $hasProviders = $this->hasProviders();

        $filterResults =
            /**
             * @param list<string> $results
             * @return list<string>
             */
            static function (array $results): array {
                return $results;
            }
        ;
        if (null !== $packageFilter && '' !== $packageFilter) {
            $packageFilterRegex = BasePackage::packageNameToRegexp($packageFilter);
            $filterResults =
                /**
                 * @param list<string> $results
                 * @return list<string>
                 */
                static function (array $results) use ($packageFilterRegex): array {
                    /** @var list<string> $results */
                    return Preg::grep($packageFilterRegex, $results);
                }
            ;
        }

        if ($this->lazyProvidersUrl) {
            if (is_array($this->availablePackages)) {
                return $filterResults(array_keys($this->availablePackages));
            }

            if ($this->listUrl) {
                // no need to call $filterResults here as the $packageFilter is applied in the function itself
                return $this->loadPackageList($packageFilter);
            }

            if ($this->hasPartialPackages() && $this->partialPackagesByName !== null) {
                return $filterResults(array_keys($this->partialPackagesByName));
            }

            return [];
        }

        if ($hasProviders) {
            return $filterResults($this->getProviderNames());
        }

        $names = [];
        foreach ($this->getPackages() as $package) {
            $names[] = $package->getPrettyName();
        }

        return $filterResults($names);
    }

    /**
     * @return list<string>
     */
    private function getVendorNames(): array
    {
        $cacheKey = 'vendor-list.txt';
        $cacheAge = $this->cache->getAge($cacheKey);
        if (false !== $cacheAge && $cacheAge < 600 && ($cachedData = $this->cache->read($cacheKey)) !== false) {
            $cachedData = explode("\n", $cachedData);

            return $cachedData;
        }

        $names = $this->getPackageNames();

        $uniques = [];
        foreach ($names as $name) {
            $uniques[explode('/', $name, 2)[0]] = true;
        }

        $vendors = array_keys($uniques);

        if (!$this->cache->isReadOnly()) {
            $this->cache->write($cacheKey, implode("\n", $vendors));
        }

        return $vendors;
    }

    /**
     * @return list<string>
     */
    private function loadPackageList(?string $packageFilter = null): array
    {
        if (null === $this->listUrl) {
            throw new \LogicException('Make sure to call loadRootServerFile before loadPackageList');
        }

        $url = $this->listUrl;
        if (is_string($packageFilter) && $packageFilter !== '') {
            $url .= '?filter='.urlencode($packageFilter);
            $result = $this->httpDownloader->get($url, $this->options)->decodeJson();

            return $result['packageNames'];
        }

        $cacheKey = 'package-list.txt';
        $cacheAge = $this->cache->getAge($cacheKey);
        if (false !== $cacheAge && $cacheAge < 600 && ($cachedData = $this->cache->read($cacheKey)) !== false) {
            $cachedData = explode("\n", $cachedData);

            return $cachedData;
        }

        $result = $this->httpDownloader->get($url, $this->options)->decodeJson();
        if (!$this->cache->isReadOnly()) {
            $this->cache->write($cacheKey, implode("\n", $result['packageNames']));
        }

        return $result['packageNames'];
    }

    public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = [])
    {
        // this call initializes loadRootServerFile which is needed for the rest below to work
        $hasProviders = $this->hasProviders();

        if (!$hasProviders && !$this->hasPartialPackages() && null === $this->lazyProvidersUrl) {
            return parent::loadPackages($packageNameMap, $acceptableStabilities, $stabilityFlags, $alreadyLoaded);
        }

        $packages = [];
        $namesFound = [];

        if ($hasProviders || $this->hasPartialPackages()) {
            foreach ($packageNameMap as $name => $constraint) {
                $matches = [];

                // if a repo has no providers but only partial packages and the partial packages are missing
                // then we don't want to call whatProvides as it would try to load from the providers and fail
                if (!$hasProviders && !isset($this->partialPackagesByName[$name])) {
                    continue;
                }

                $candidates = $this->whatProvides($name, $acceptableStabilities, $stabilityFlags, $alreadyLoaded);
                foreach ($candidates as $candidate) {
                    if ($candidate->getName() !== $name) {
                        throw new \LogicException('whatProvides should never return a package with a different name than the requested one');
                    }
                    $namesFound[$name] = true;

                    if (!$constraint || $constraint->matches(new Constraint('==', $candidate->getVersion()))) {
                        $matches[spl_object_hash($candidate)] = $candidate;
                        if ($candidate instanceof AliasPackage && !isset($matches[spl_object_hash($candidate->getAliasOf())])) {
                            $matches[spl_object_hash($candidate->getAliasOf())] = $candidate->getAliasOf();
                        }
                    }
                }

                // add aliases of matched packages even if they did not match the constraint
                foreach ($candidates as $candidate) {
                    if ($candidate instanceof AliasPackage) {
                        if (isset($matches[spl_object_hash($candidate->getAliasOf())])) {
                            $matches[spl_object_hash($candidate)] = $candidate;
                        }
                    }
                }
                $packages = array_merge($packages, $matches);

                unset($packageNameMap[$name]);
            }
        }

        if ($this->lazyProvidersUrl && count($packageNameMap)) {
            if ($this->hasAvailablePackageList) {
                foreach ($packageNameMap as $name => $constraint) {
                    if (!$this->lazyProvidersRepoContains(strtolower($name))) {
                        unset($packageNameMap[$name]);
                    }
                }
            }

            $result = $this->loadAsyncPackages($packageNameMap, $acceptableStabilities, $stabilityFlags, $alreadyLoaded);
            $packages = array_merge($packages, $result['packages']);
            $namesFound = array_merge($namesFound, $result['namesFound']);
        }

        return ['namesFound' => array_keys($namesFound), 'packages' => $packages];
    }

    /**
     * @inheritDoc
     */
    public function search(string $query, int $mode = 0, ?string $type = null)
    {
        $this->loadRootServerFile(600);

        if ($this->searchUrl && $mode === self::SEARCH_FULLTEXT) {
            $url = str_replace(['%query%', '%type%'], [urlencode($query), $type], $this->searchUrl);

            $search = $this->httpDownloader->get($url, $this->options)->decodeJson();

            if (empty($search['results'])) {
                return [];
            }

            $results = [];
            foreach ($search['results'] as $result) {
                // do not show virtual packages in results as they are not directly useful from a composer perspective
                if (!empty($result['virtual'])) {
                    continue;
                }

                $results[] = $result;
            }

            return $results;
        }

        if ($mode === self::SEARCH_VENDOR) {
            $results = [];
            $regex = '{(?:'.implode('|', Preg::split('{\s+}', $query)).')}i';

            $vendorNames = $this->getVendorNames();
            foreach (Preg::grep($regex, $vendorNames) as $name) {
                $results[] = ['name' => $name, 'description' => ''];
            }

            return $results;
        }

        if ($this->hasProviders() || $this->lazyProvidersUrl) {
            // optimize search for "^foo/bar" where at least "^foo/" is present by loading this directly from the listUrl if present
            if (Preg::isMatchStrictGroups('{^\^(?P<query>(?P<vendor>[a-z0-9_.-]+)/[a-z0-9_.-]*)\*?$}i', $query, $match) && $this->listUrl !== null) {
                $url = $this->listUrl . '?vendor='.urlencode($match['vendor']).'&filter='.urlencode($match['query'].'*');
                $result = $this->httpDownloader->get($url, $this->options)->decodeJson();

                $results = [];
                foreach ($result['packageNames'] as $name) {
                    $results[] = ['name' => $name, 'description' => ''];
                }

                return $results;
            }

            $results = [];
            $regex = '{(?:'.implode('|', Preg::split('{\s+}', $query)).')}i';

            $packageNames = $this->getPackageNames();
            foreach (Preg::grep($regex, $packageNames) as $name) {
                $results[] = ['name' => $name, 'description' => ''];
            }

            return $results;
        }

        return parent::search($query, $mode);
    }

    public function hasSecurityAdvisories(): bool
    {
        $this->loadRootServerFile(600);

        return $this->securityAdvisoryConfig !== null && ($this->securityAdvisoryConfig['metadata'] || $this->securityAdvisoryConfig['api-url'] !== null);
    }

    /**
     * @inheritDoc
     */
    public function getSecurityAdvisories(array $packageConstraintMap, bool $allowPartialAdvisories = false): array
    {
        $this->loadRootServerFile(600);
        if (null === $this->securityAdvisoryConfig) {
            return ['namesFound' => [], 'advisories' => []];
        }

        $advisories = [];
        $namesFound = [];

        $apiUrl = $this->securityAdvisoryConfig['api-url'];

        // respect available-package-patterns / available-packages directives from the repo
        if ($this->hasAvailablePackageList) {
            foreach ($packageConstraintMap as $name => $constraint) {
                if (!$this->lazyProvidersRepoContains(strtolower($name))) {
                    unset($packageConstraintMap[$name]);
                }
            }
        }

        $parser = new VersionParser();
        /**
         * @param array<mixed> $data
         * @param string $name
         * @return ($allowPartialAdvisories is false ? SecurityAdvisory|null : PartialSecurityAdvisory|SecurityAdvisory|null)
         */
        $create = function (array $data, string $name) use ($parser, $allowPartialAdvisories, &$packageConstraintMap): ?PartialSecurityAdvisory {
            $advisory = PartialSecurityAdvisory::create($name, $data, $parser);
            if (!$allowPartialAdvisories && !$advisory instanceof SecurityAdvisory) {
                throw new \RuntimeException('Advisory for '.$name.' could not be loaded as a full advisory from '.$this->getRepoName() . PHP_EOL . var_export($data, true));
            }
            if (!$advisory->affectedVersions->matches($packageConstraintMap[$name])) {
                return null;
            }

            return $advisory;
        };

        if ($this->securityAdvisoryConfig['metadata'] && ($allowPartialAdvisories || $apiUrl === null)) {
            $promises = [];
            foreach ($packageConstraintMap as $name => $constraint) {
                $name = strtolower($name);

                // skip platform packages, root package and composer-plugin-api
                if (PlatformRepository::isPlatformPackage($name) || '__root__' === $name) {
                    continue;
                }

                $promises[] = $this->startCachedAsyncDownload($name, $name)
                    ->then(static function (array $spec) use (&$advisories, &$namesFound, &$packageConstraintMap, $name, $create): void {
                        [$response] = $spec;

                        if (!isset($response['security-advisories']) || !is_array($response['security-advisories'])) {
                            return;
                        }

                        $namesFound[$name] = true;
                        if (count($response['security-advisories']) > 0) {
                            $advisories[$name] = array_values(array_filter(array_map(
                                static function ($data) use ($name, $create) {
                                    return $create($data, $name);
                                },
                                $response['security-advisories']
                            )));
                        }
                        unset($packageConstraintMap[$name]);
                    });
            }

            $this->loop->wait($promises);
        }

        if ($apiUrl !== null && count($packageConstraintMap) > 0) {
            $options = $this->options;
            $options['http']['method'] = 'POST';
            if (isset($options['http']['header'])) {
                $options['http']['header'] = (array) $options['http']['header'];
            }
            $options['http']['header'][] = 'Content-type: application/x-www-form-urlencoded';
            $options['http']['timeout'] = 10;
            $options['http']['content'] = http_build_query(['packages' => array_keys($packageConstraintMap)]);

            $response = $this->httpDownloader->get($apiUrl, $options);
            $warned = false;
            /** @var string $name */
            foreach ($response->decodeJson()['advisories'] as $name => $list) {
                if (!isset($packageConstraintMap[$name])) {
                    if (!$warned) {
                        $this->io->writeError('<warning>'.$this->getRepoName().' returned names which were not requested in response to the security-advisories API. '.$name.' was not requested but is present in the response. Requested names were: '.implode(', ', array_keys($packageConstraintMap)).'</warning>');
                        $warned = true;
                    }
                    continue;
                }
                if (count($list) > 0) {
                    $advisories[$name] = array_values(array_filter(array_map(
                        static function ($data) use ($name, $create) {
                            return $create($data, $name);
                        },
                        $list
                    )));
                }
                $namesFound[$name] = true;
            }
        }

        return ['namesFound' => array_keys($namesFound), 'advisories' => array_filter($advisories, static function ($adv): bool { return \count($adv) > 0; })];
    }

    public function getProviders(string $packageName)
    {
        $this->loadRootServerFile();
        $result = [];

        if ($this->providersApiUrl) {
            try {
                $apiResult = $this->httpDownloader->get(str_replace('%package%', $packageName, $this->providersApiUrl), $this->options)->decodeJson();
            } catch (TransportException $e) {
                if ($e->getStatusCode() === 404) {
                    return $result;
                }
                throw $e;
            }

            foreach ($apiResult['providers'] as $provider) {
                $result[$provider['name']] = $provider;
            }

            return $result;
        }

        if ($this->hasPartialPackages()) {
            if (!is_array($this->partialPackagesByName)) {
                throw new \LogicException('hasPartialPackages failed to initialize $this->partialPackagesByName');
            }
            foreach ($this->partialPackagesByName as $versions) {
                foreach ($versions as $candidate) {
                    if (isset($result[$candidate['name']]) || !isset($candidate['provide'][$packageName])) {
                        continue;
                    }
                    $result[$candidate['name']] = [
                        'name' => $candidate['name'],
                        'description' => $candidate['description'] ?? '',
                        'type' => $candidate['type'] ?? '',
                    ];
                }
            }
        }

        if ($this->packages) {
            $result = array_merge($result, parent::getProviders($packageName));
        }

        return $result;
    }

    /**
     * @return string[]
     */
    private function getProviderNames(): array
    {
        $this->loadRootServerFile();

        if (null === $this->providerListing) {
            $data = $this->loadRootServerFile();
            if (is_array($data)) {
                $this->loadProviderListings($data);
            }
        }

        if ($this->lazyProvidersUrl) {
            // Can not determine list of provided packages for lazy repositories
            return [];
        }

        if (null !== $this->providersUrl && null !== $this->providerListing) {
            return array_keys($this->providerListing);
        }

        return [];
    }

    protected function configurePackageTransportOptions(PackageInterface $package): void
    {
        foreach ($package->getDistUrls() as $url) {
            if (strpos($url, $this->baseUrl) === 0) {
                $package->setTransportOptions($this->options);

                return;
            }
        }
    }

    private function hasProviders(): bool
    {
        $this->loadRootServerFile();

        return $this->hasProviders;
    }

    /**
     * @param  string      $name package name
     * @param array<string, int>|null $acceptableStabilities
     * @phpstan-param array<key-of<BasePackage::STABILITIES>, BasePackage::STABILITY_*>|null $acceptableStabilities
     * @param array<string, int>|null $stabilityFlags an array of package name => BasePackage::STABILITY_* value
     * @phpstan-param array<string, BasePackage::STABILITY_*>|null $stabilityFlags
     * @param array<string, array<string, PackageInterface>> $alreadyLoaded
     *
     * @return array<string, BasePackage>
     */
    private function whatProvides(string $name, ?array $acceptableStabilities = null, ?array $stabilityFlags = null, array $alreadyLoaded = []): array
    {
        $packagesSource = null;
        if (!$this->hasPartialPackages() || !isset($this->partialPackagesByName[$name])) {
            // skip platform packages, root package and composer-plugin-api
            if (PlatformRepository::isPlatformPackage($name) || '__root__' === $name) {
                return [];
            }

            if (null === $this->providerListing) {
                $data = $this->loadRootServerFile();
                if (is_array($data)) {
                    $this->loadProviderListings($data);
                }
            }

            $useLastModifiedCheck = false;
            if ($this->lazyProvidersUrl && !isset($this->providerListing[$name])) {
                $hash = null;
                $url = str_replace('%package%', $name, $this->lazyProvidersUrl);
                $cacheKey = 'provider-'.strtr($name, '/', '$').'.json';
                $useLastModifiedCheck = true;
            } elseif ($this->providersUrl) {
                // package does not exist in this repo
                if (!isset($this->providerListing[$name])) {
                    return [];
                }

                $hash = $this->providerListing[$name]['sha256'];
                $url = str_replace(['%package%', '%hash%'], [$name, $hash], $this->providersUrl);
                $cacheKey = 'provider-'.strtr($name, '/', '$').'.json';
            } else {
                return [];
            }

            $packages = null;
            if (!$useLastModifiedCheck && $hash && $this->cache->sha256($cacheKey) === $hash) {
                $packages = json_decode($this->cache->read($cacheKey), true);
                $packagesSource = 'cached file ('.$cacheKey.' originating from '.Url::sanitize($url).')';
            } elseif ($useLastModifiedCheck) {
                if ($contents = $this->cache->read($cacheKey)) {
                    $contents = json_decode($contents, true);
                    // we already loaded some packages from this file, so assume it is fresh and avoid fetching it again
                    if (isset($alreadyLoaded[$name])) {
                        $packages = $contents;
                        $packagesSource = 'cached file ('.$cacheKey.' originating from '.Url::sanitize($url).')';
                    } elseif (isset($contents['last-modified'])) {
                        $response = $this->fetchFileIfLastModified($url, $cacheKey, $contents['last-modified']);
                        $packages = true === $response ? $contents : $response;
                        $packagesSource = true === $response ? 'cached file ('.$cacheKey.' originating from '.Url::sanitize($url).')' : 'downloaded file ('.Url::sanitize($url).')';
                    }
                }
            }

            if (!$packages) {
                try {
                    $packages = $this->fetchFile($url, $cacheKey, $hash, $useLastModifiedCheck);
                    $packagesSource = 'downloaded file ('.Url::sanitize($url).')';
                } catch (TransportException $e) {
                    // 404s are acceptable for lazy provider repos
                    if ($this->lazyProvidersUrl && in_array($e->getStatusCode(), [404, 499], true)) {
                        $packages = ['packages' => []];
                        $packagesSource = 'not-found file ('.Url::sanitize($url).')';
                        if ($e->getStatusCode() === 499) {
                            $this->io->error('<warning>' . $e->getMessage() . '</warning>');
                        }
                    } else {
                        throw $e;
                    }
                }
            }

            $loadingPartialPackage = false;
        } else {
            $packages = ['packages' => ['versions' => $this->partialPackagesByName[$name]]];
            $packagesSource = 'root file ('.Url::sanitize($this->getPackagesJsonUrl()).')';
            $loadingPartialPackage = true;
        }

        $result = [];
        $versionsToLoad = [];
        foreach ($packages['packages'] as $versions) {
            foreach ($versions as $version) {
                $normalizedName = strtolower($version['name']);

                // only load the actual named package, not other packages that might find themselves in the same file
                if ($normalizedName !== $name) {
                    continue;
                }

                if (!$loadingPartialPackage && $this->hasPartialPackages() && isset($this->partialPackagesByName[$normalizedName])) {
                    continue;
                }

                if (!isset($versionsToLoad[$version['uid']])) {
                    if (!isset($version['version_normalized'])) {
                        $version['version_normalized'] = $this->versionParser->normalize($version['version']);
                    } elseif ($version['version_normalized'] === VersionParser::DEFAULT_BRANCH_ALIAS) {
                        // handling of existing repos which need to remain composer v1 compatible, in case the version_normalized contained VersionParser::DEFAULT_BRANCH_ALIAS, we renormalize it
                        $version['version_normalized'] = $this->versionParser->normalize($version['version']);
                    }

                    // avoid loading packages which have already been loaded
                    if (isset($alreadyLoaded[$name][$version['version_normalized']])) {
                        continue;
                    }

                    if ($this->isVersionAcceptable(null, $normalizedName, $version, $acceptableStabilities, $stabilityFlags)) {
                        $versionsToLoad[$version['uid']] = $version;
                    }
                }
            }
        }

        // load acceptable packages in the providers
        $loadedPackages = $this->createPackages($versionsToLoad, $packagesSource);
        $uids = array_keys($versionsToLoad);

        foreach ($loadedPackages as $index => $package) {
            $package->setRepository($this);
            $uid = $uids[$index];

            if ($package instanceof AliasPackage) {
                $aliased = $package->getAliasOf();
                $aliased->setRepository($this);

                $result[$uid] = $aliased;
                $result[$uid.'-alias'] = $package;
            } else {
                $result[$uid] = $package;
            }
        }

        return $result;
    }

    /**
     * @inheritDoc
     */
    protected function initialize()
    {
        parent::initialize();

        $repoData = $this->loadDataFromServer();

        foreach ($this->createPackages($repoData, 'root file ('.Url::sanitize($this->getPackagesJsonUrl()).')') as $package) {
            $this->addPackage($package);
        }
    }

    /**
     * Adds a new package to the repository
     */
    public function addPackage(PackageInterface $package)
    {
        parent::addPackage($package);
        $this->configurePackageTransportOptions($package);
    }

    /**
     * @param array<string, ConstraintInterface|null> $packageNames array of package name => ConstraintInterface|null - if a constraint is provided, only
     *                                                packages matching it will be loaded
     * @param array<string, int>|null $acceptableStabilities
     * @phpstan-param array<key-of<BasePackage::STABILITIES>, BasePackage::STABILITY_*>|null $acceptableStabilities
     * @param array<string, int>|null $stabilityFlags an array of package name => BasePackage::STABILITY_* value
     * @phpstan-param array<string, BasePackage::STABILITY_*>|null $stabilityFlags
     * @param array<string, array<string, PackageInterface>> $alreadyLoaded
     *
     * @return array{namesFound: array<string, true>, packages: array<string, BasePackage>}
     */
    private function loadAsyncPackages(array $packageNames, ?array $acceptableStabilities = null, ?array $stabilityFlags = null, array $alreadyLoaded = []): array
    {
        $this->loadRootServerFile();

        $packages = [];
        $namesFound = [];
        $promises = [];

        if (null === $this->lazyProvidersUrl) {
            throw new \LogicException('loadAsyncPackages only supports v2 protocol composer repos with a metadata-url');
        }

        // load ~dev versions of the packages as well if needed
        foreach ($packageNames as $name => $constraint) {
            if ($acceptableStabilities === null || $stabilityFlags === null || StabilityFilter::isPackageAcceptable($acceptableStabilities, $stabilityFlags, [$name], 'dev')) {
                $packageNames[$name.'~dev'] = $constraint;
            }
            // if only dev stability is requested, we skip loading the non dev file
            if (isset($acceptableStabilities['dev']) && count($acceptableStabilities) === 1 && count($stabilityFlags) === 0) {
                unset($packageNames[$name]);
            }
        }

        foreach ($packageNames as $name => $constraint) {
            $name = strtolower($name);

            $realName = Preg::replace('{~dev$}', '', $name);
            // skip platform packages, root package and composer-plugin-api
            if (PlatformRepository::isPlatformPackage($realName) || '__root__' === $realName) {
                continue;
            }

            $promises[] = $this->startCachedAsyncDownload($name, $realName)
                ->then(function (array $spec) use (&$packages, &$namesFound, $realName, $constraint, $acceptableStabilities, $stabilityFlags, $alreadyLoaded): void {
                    [$response, $packagesSource] = $spec;
                    if (null === $response || !isset($response['packages'][$realName])) {
                        return;
                    }

                    $versions = $response['packages'][$realName];

                    if (isset($response['minified']) && $response['minified'] === 'composer/2.0') {
                        $versions = MetadataMinifier::expand($versions);
                    }

                    $namesFound[$realName] = true;
                    $versionsToLoad = [];
                    foreach ($versions as $version) {
                        if (!isset($version['version_normalized'])) {
                            $version['version_normalized'] = $this->versionParser->normalize($version['version']);
                        } elseif ($version['version_normalized'] === VersionParser::DEFAULT_BRANCH_ALIAS) {
                            // handling of existing repos which need to remain composer v1 compatible, in case the version_normalized contained VersionParser::DEFAULT_BRANCH_ALIAS, we renormalize it
                            $version['version_normalized'] = $this->versionParser->normalize($version['version']);
                        }

                        // avoid loading packages which have already been loaded
                        if (isset($alreadyLoaded[$realName][$version['version_normalized']])) {
                            continue;
                        }

                        if ($this->isVersionAcceptable($constraint, $realName, $version, $acceptableStabilities, $stabilityFlags)) {
                            $versionsToLoad[] = $version;
                        }
                    }

                    $loadedPackages = $this->createPackages($versionsToLoad, $packagesSource);
                    foreach ($loadedPackages as $package) {
                        $package->setRepository($this);
                        $packages[spl_object_hash($package)] = $package;

                        if ($package instanceof AliasPackage && !isset($packages[spl_object_hash($package->getAliasOf())])) {
                            $package->getAliasOf()->setRepository($this);
                            $packages[spl_object_hash($package->getAliasOf())] = $package->getAliasOf();
                        }
                    }
                });
        }

        $this->loop->wait($promises);

        return ['namesFound' => $namesFound, 'packages' => $packages];
    }

    /**
     * @phpstan-return PromiseInterface<array{mixed, string}>
     */
    private function startCachedAsyncDownload(string $fileName, ?string $packageName = null): PromiseInterface
    {
        if (null === $this->lazyProvidersUrl) {
            throw new \LogicException('startCachedAsyncDownload only supports v2 protocol composer repos with a metadata-url');
        }

        $name = strtolower($fileName);
        $packageName = $packageName ?? $name;

        $url = str_replace('%package%', $name, $this->lazyProvidersUrl);
        $cacheKey = 'provider-'.strtr($name, '/', '~').'.json';

        $lastModified = null;
        if ($contents = $this->cache->read($cacheKey)) {
            $contents = json_decode($contents, true);
            $lastModified = $contents['last-modified'] ?? null;
        }

        return $this->asyncFetchFile($url, $cacheKey, $lastModified)
            ->then(static function ($response) use ($url, $cacheKey, $contents, $packageName): array {
                $packagesSource = 'downloaded file ('.Url::sanitize($url).')';

                if (true === $response) {
                    $packagesSource = 'cached file ('.$cacheKey.' originating from '.Url::sanitize($url).')';
                    $response = $contents;
                }

                if (!isset($response['packages'][$packageName]) && !isset($response['security-advisories'])) {
                    return [null, $packagesSource];
                }

                return [$response, $packagesSource];
            });
    }

    /**
     * @param string $name package name (must be lowercased already)
     * @param array<string, mixed> $versionData
     * @param array<string, int>|null $acceptableStabilities
     * @phpstan-param array<key-of<BasePackage::STABILITIES>, BasePackage::STABILITY_*>|null $acceptableStabilities
     * @param array<string, int>|null $stabilityFlags an array of package name => BasePackage::STABILITY_* value
     * @phpstan-param array<string, BasePackage::STABILITY_*>|null $stabilityFlags
     */
    private function isVersionAcceptable(?ConstraintInterface $constraint, string $name, array $versionData, ?array $acceptableStabilities = null, ?array $stabilityFlags = null): bool
    {
        $versions = [$versionData['version_normalized']];

        if ($alias = $this->loader->getBranchAlias($versionData)) {
            $versions[] = $alias;
        }

        foreach ($versions as $version) {
            if (null !== $acceptableStabilities && null !== $stabilityFlags && !StabilityFilter::isPackageAcceptable($acceptableStabilities, $stabilityFlags, [$name], VersionParser::parseStability($version))) {
                continue;
            }

            if ($constraint && !CompilingMatcher::match($constraint, Constraint::OP_EQ, $version)) {
                continue;
            }

            return true;
        }

        return false;
    }

    private function getPackagesJsonUrl(): string
    {
        $jsonUrlParts = parse_url(strtr($this->url, '\\', '/'));

        if (isset($jsonUrlParts['path']) && false !== strpos($jsonUrlParts['path'], '.json')) {
            return $this->url;
        }

        return $this->url . '/packages.json';
    }

    /**
     * @return array<'providers'|'provider-includes'|'packages'|'providers-url'|'notify-batch'|'search'|'mirrors'|'providers-lazy-url'|'metadata-url'|'available-packages'|'available-package-patterns', mixed>|true
     */
    protected function loadRootServerFile(?int $rootMaxAge = null)
    {
        if (null !== $this->rootData) {
            return $this->rootData;
        }

        if (!extension_loaded('openssl') && strpos($this->url, 'https') === 0) {
            throw new \RuntimeException('You must enable the openssl extension in your php.ini to load information from '.$this->url);
        }

        if ($cachedData = $this->cache->read('packages.json')) {
            $cachedData = json_decode($cachedData, true);
            if ($rootMaxAge !== null && ($age = $this->cache->getAge('packages.json')) !== false && $age <= $rootMaxAge) {
                $data = $cachedData;
            } elseif (isset($cachedData['last-modified'])) {
                $response = $this->fetchFileIfLastModified($this->getPackagesJsonUrl(), 'packages.json', $cachedData['last-modified']);
                $data = true === $response ? $cachedData : $response;
            }
        }

        if (!isset($data)) {
            $data = $this->fetchFile($this->getPackagesJsonUrl(), 'packages.json', null, true);
        }

        if (!empty($data['notify-batch'])) {
            $this->notifyUrl = $this->canonicalizeUrl($data['notify-batch']);
        } elseif (!empty($data['notify'])) {
            $this->notifyUrl = $this->canonicalizeUrl($data['notify']);
        }

        if (!empty($data['search'])) {
            $this->searchUrl = $this->canonicalizeUrl($data['search']);
        }

        if (!empty($data['mirrors'])) {
            foreach ($data['mirrors'] as $mirror) {
                if (!empty($mirror['git-url'])) {
                    $this->sourceMirrors['git'][] = ['url' => $mirror['git-url'], 'preferred' => !empty($mirror['preferred'])];
                }
                if (!empty($mirror['hg-url'])) {
                    $this->sourceMirrors['hg'][] = ['url' => $mirror['hg-url'], 'preferred' => !empty($mirror['preferred'])];
                }
                if (!empty($mirror['dist-url'])) {
                    $this->distMirrors[] = [
                        'url' => $this->canonicalizeUrl($mirror['dist-url']),
                        'preferred' => !empty($mirror['preferred']),
                    ];
                }
            }
        }

        if (!empty($data['providers-lazy-url'])) {
            $this->lazyProvidersUrl = $this->canonicalizeUrl($data['providers-lazy-url']);
            $this->hasProviders = true;

            $this->hasPartialPackages = !empty($data['packages']) && is_array($data['packages']);
        }

        // metadata-url indicates V2 repo protocol so it takes over from all the V1 types
        // V2 only has lazyProviders and possibly partial packages, but no ability to process anything else,
        // V2 also supports async loading
        if (!empty($data['metadata-url'])) {
            $this->lazyProvidersUrl = $this->canonicalizeUrl($data['metadata-url']);
            $this->providersUrl = null;
            $this->hasProviders = false;
            $this->hasPartialPackages = !empty($data['packages']) && is_array($data['packages']);
            $this->allowSslDowngrade = false;

            // provides a list of package names that are available in this repo
            // this disables lazy-provider behavior in the sense that if a list is available we assume it is finite and won't search for other packages in that repo
            // while if no list is there lazyProvidersUrl is used when looking for any package name to see if the repo knows it
            if (!empty($data['available-packages'])) {
                $availPackages = array_map('strtolower', $data['available-packages']);
                $this->availablePackages = array_combine($availPackages, $availPackages);
                $this->hasAvailablePackageList = true;
            }

            // Provides a list of package name patterns (using * wildcards to match any substring, e.g. "vendor/*") that are available in this repo
            // Disables lazy-provider behavior as with available-packages, but may allow much more compact expression of packages covered by this repository.
            // Over-specifying covered packages is safe, but may result in increased traffic to your repository.
            if (!empty($data['available-package-patterns'])) {
                $this->availablePackagePatterns = array_map(static function ($pattern): string {
                    return BasePackage::packageNameToRegexp($pattern);
                }, $data['available-package-patterns']);
                $this->hasAvailablePackageList = true;
            }

            // Remove legacy keys as most repos need to be compatible with Composer v1
            // as well but we are not interested in the old format anymore at this point
            unset($data['providers-url'], $data['providers'], $data['providers-includes']);

            if (isset($data['security-advisories']) && is_array($data['security-advisories'])) {
                $this->securityAdvisoryConfig = [
                    'metadata' => $data['security-advisories']['metadata'] ?? false,
                    'api-url' => isset($data['security-advisories']['api-url']) && is_string($data['security-advisories']['api-url']) ? $this->canonicalizeUrl($data['security-advisories']['api-url']) : null,
                ];
                if ($this->securityAdvisoryConfig['api-url'] === null && !$this->hasAvailablePackageList) {
                    throw new \UnexpectedValueException('Invalid security advisory configuration on '.$this->getRepoName().': If the repository does not provide a security-advisories.api-url then available-packages or available-package-patterns are required to be provided for performance reason.');
                }
            }
        }

        if ($this->allowSslDowngrade) {
            $this->url = str_replace('https://', 'http://', $this->url);
            $this->baseUrl = str_replace('https://', 'http://', $this->baseUrl);
        }

        if (!empty($data['providers-url'])) {
            $this->providersUrl = $this->canonicalizeUrl($data['providers-url']);
            $this->hasProviders = true;
        }

        if (!empty($data['list'])) {
            $this->listUrl = $this->canonicalizeUrl($data['list']);
        }

        if (!empty($data['providers']) || !empty($data['providers-includes'])) {
            $this->hasProviders = true;
        }

        if (!empty($data['providers-api'])) {
            $this->providersApiUrl = $this->canonicalizeUrl($data['providers-api']);
        }

        return $this->rootData = $data;
    }

    /**
     * @return non-empty-string
     */
    private function canonicalizeUrl(string $url): string
    {
        if (strlen($url) === 0) {
            throw new \InvalidArgumentException('Expected a string with a value and not an empty string');
        }

        if (str_starts_with($url, '/')) {
            if (Preg::isMatch('{^[^:]++://[^/]*+}', $this->url, $matches)) {
                return $matches[0] . $url;
            }

            return $this->url;
        }

        return $url;
    }

    /**
     * @return mixed[]
     */
    private function loadDataFromServer(): array
    {
        $data = $this->loadRootServerFile();
        if (true === $data) {
            throw new \LogicException('loadRootServerFile should not return true during initialization');
        }

        return $this->loadIncludes($data);
    }

    private function hasPartialPackages(): bool
    {
        if ($this->hasPartialPackages && null === $this->partialPackagesByName) {
            $this->initializePartialPackages();
        }

        return $this->hasPartialPackages;
    }

    /**
     * @param array{providers?: mixed[], provider-includes?: mixed[]} $data
     */
    private function loadProviderListings($data): void
    {
        if (isset($data['providers'])) {
            if (!is_array($this->providerListing)) {
                $this->providerListing = [];
            }
            $this->providerListing = array_merge($this->providerListing, $data['providers']);
        }

        if ($this->providersUrl && isset($data['provider-includes'])) {
            $includes = $data['provider-includes'];
            foreach ($includes as $include => $metadata) {
                $url = $this->baseUrl . '/' . str_replace('%hash%', $metadata['sha256'], $include);
                $cacheKey = str_replace(['%hash%','$'], '', $include);
                if ($this->cache->sha256($cacheKey) === $metadata['sha256']) {
                    $includedData = json_decode($this->cache->read($cacheKey), true);
                } else {
                    $includedData = $this->fetchFile($url, $cacheKey, $metadata['sha256']);
                }

                $this->loadProviderListings($includedData);
            }
        }
    }

    /**
     * @param mixed[] $data
     *
     * @return mixed[]
     */
    private function loadIncludes(array $data): array
    {
        $packages = [];

        // legacy repo handling
        if (!isset($data['packages']) && !isset($data['includes'])) {
            foreach ($data as $pkg) {
                if (isset($pkg['versions']) && is_array($pkg['versions'])) {
                    foreach ($pkg['versions'] as $metadata) {
                        $packages[] = $metadata;
                    }
                }
            }

            return $packages;
        }

        if (isset($data['packages'])) {
            foreach ($data['packages'] as $package => $versions) {
                $packageName = strtolower((string) $package);
                foreach ($versions as $version => $metadata) {
                    $packages[] = $metadata;
                    if (!$this->displayedWarningAboutNonMatchingPackageIndex && $packageName !== strtolower((string) ($metadata['name'] ?? ''))) {
                        $this->displayedWarningAboutNonMatchingPackageIndex = true;
                        $this->io->writeError(sprintf("<warning>Warning: the packages key '%s' doesn't match the name defined in the package metadata '%s' in repository %s</warning>", $package, $metadata['name'] ?? '', $this->baseUrl));
                    }
                }
            }
        }

        if (isset($data['includes'])) {
            foreach ($data['includes'] as $include => $metadata) {
                if (isset($metadata['sha1']) && $this->cache->sha1((string) $include) === $metadata['sha1']) {
                    $includedData = json_decode($this->cache->read((string) $include), true);
                } else {
                    $includedData = $this->fetchFile($include);
                }
                $packages = array_merge($packages, $this->loadIncludes($includedData));
            }
        }

        return $packages;
    }

    /**
     * @param mixed[] $packages
     *
     * @return list<CompletePackage|CompleteAliasPackage>
     */
    private function createPackages(array $packages, ?string $source = null): array
    {
        if (!$packages) {
            return [];
        }

        try {
            foreach ($packages as &$data) {
                if (!isset($data['notification-url'])) {
                    $data['notification-url'] = $this->notifyUrl;
                }
            }

            $packageInstances = $this->loader->loadPackages($packages);

            foreach ($packageInstances as $package) {
                if (isset($this->sourceMirrors[$package->getSourceType()])) {
                    $package->setSourceMirrors($this->sourceMirrors[$package->getSourceType()]);
                }
                $package->setDistMirrors($this->distMirrors);
                $this->configurePackageTransportOptions($package);
            }

            return $packageInstances;
        } catch (\Exception $e) {
            throw new \RuntimeException('Could not load packages in '.$this->getRepoName().($source ? ' from '.$source : '').': ['.get_class($e).'] '.$e->getMessage(), 0, $e);
        }
    }

    /**
     * @return array<mixed>
     */
    protected function fetchFile(string $filename, ?string $cacheKey = null, ?string $sha256 = null, bool $storeLastModifiedTime = false)
    {
        if ('' === $filename) {
            throw new \InvalidArgumentException('$filename should not be an empty string');
        }

        if (null === $cacheKey) {
            $cacheKey = $filename;
            $filename = $this->baseUrl.'/'.$filename;
        }

        // url-encode $ signs in URLs as bad proxies choke on them
        if (($pos = strpos($filename, '$')) && Preg::isMatch('{^https?://}i', $filename)) {
            $filename = substr($filename, 0, $pos) . '%24' . substr($filename, $pos + 1);
        }

        $retries = 3;
        while ($retries--) {
            try {
                $options = $this->options;
                if ($this->eventDispatcher) {
                    $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $filename, 'metadata', ['repository' => $this]);
                    $preFileDownloadEvent->setTransportOptions($this->options);
                    $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent);
                    $filename = $preFileDownloadEvent->getProcessedUrl();
                    $options = $preFileDownloadEvent->getTransportOptions();
                }

                $response = $this->httpDownloader->get($filename, $options);
                $json = (string) $response->getBody();
                if ($sha256 && $sha256 !== hash('sha256', $json)) {
                    // undo downgrade before trying again if http seems to be hijacked or modifying content somehow
                    if ($this->allowSslDowngrade) {
                        $this->url = str_replace('http://', 'https://', $this->url);
                        $this->baseUrl = str_replace('http://', 'https://', $this->baseUrl);
                        $filename = str_replace('http://', 'https://', $filename);
                    }

                    if ($retries > 0) {
                        usleep(100000);

                        continue;
                    }

                    // TODO use scarier wording once we know for sure it doesn't do false positives anymore
                    throw new RepositorySecurityException('The contents of '.$filename.' do not match its signature. This could indicate a man-in-the-middle attack or e.g. antivirus software corrupting files. Try running composer again and report this if you think it is a mistake.');
                }

                if ($this->eventDispatcher) {
                    $postFileDownloadEvent = new PostFileDownloadEvent(PluginEvents::POST_FILE_DOWNLOAD, null, $sha256, $filename, 'metadata', ['response' => $response, 'repository' => $this]);
                    $this->eventDispatcher->dispatch($postFileDownloadEvent->getName(), $postFileDownloadEvent);
                }

                $data = $response->decodeJson();
                HttpDownloader::outputWarnings($this->io, $this->url, $data);

                if ($cacheKey && !$this->cache->isReadOnly()) {
                    if ($storeLastModifiedTime) {
                        $lastModifiedDate = $response->getHeader('last-modified');
                        if ($lastModifiedDate) {
                            $data['last-modified'] = $lastModifiedDate;
                            $json = JsonFile::encode($data, 0);
                        }
                    }
                    $this->cache->write($cacheKey, $json);
                }

                $response->collect();

                break;
            } catch (\Exception $e) {
                if ($e instanceof \LogicException) {
                    throw $e;
                }

                if ($e instanceof TransportException && $e->getStatusCode() === 404) {
                    throw $e;
                }

                if ($e instanceof RepositorySecurityException) {
                    throw $e;
                }

                if ($cacheKey && ($contents = $this->cache->read($cacheKey))) {
                    if (!$this->degradedMode) {
                        $this->io->writeError('<warning>'.$this->url.' could not be fully loaded ('.$e->getMessage().'), package information was loaded from the local cache and may be out of date</warning>');
                    }
                    $this->degradedMode = true;
                    $data = JsonFile::parseJson($contents, $this->cache->getRoot().$cacheKey);

                    break;
                }

                throw $e;
            }
        }

        if (!isset($data)) {
            throw new \LogicException("ComposerRepository: Undefined \$data. Please report at https://github.com/composer/composer/issues/new.");
        }

        return $data;
    }

    /**
     * @return array<mixed>|true
     */
    private function fetchFileIfLastModified(string $filename, string $cacheKey, string $lastModifiedTime)
    {
        if ('' === $filename) {
            throw new \InvalidArgumentException('$filename should not be an empty string');
        }

        try {
            $options = $this->options;
            if ($this->eventDispatcher) {
                $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $filename, 'metadata', ['repository' => $this]);
                $preFileDownloadEvent->setTransportOptions($this->options);
                $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent);
                $filename = $preFileDownloadEvent->getProcessedUrl();
                $options = $preFileDownloadEvent->getTransportOptions();
            }

            if (isset($options['http']['header'])) {
                $options['http']['header'] = (array) $options['http']['header'];
            }
            $options['http']['header'][] = 'If-Modified-Since: '.$lastModifiedTime;
            $response = $this->httpDownloader->get($filename, $options);
            $json = (string) $response->getBody();
            if ($json === '' && $response->getStatusCode() === 304) {
                return true;
            }

            if ($this->eventDispatcher) {
                $postFileDownloadEvent = new PostFileDownloadEvent(PluginEvents::POST_FILE_DOWNLOAD, null, null, $filename, 'metadata', ['response' => $response, 'repository' => $this]);
                $this->eventDispatcher->dispatch($postFileDownloadEvent->getName(), $postFileDownloadEvent);
            }

            $data = $response->decodeJson();
            HttpDownloader::outputWarnings($this->io, $this->url, $data);

            $lastModifiedDate = $response->getHeader('last-modified');
            $response->collect();
            if ($lastModifiedDate) {
                $data['last-modified'] = $lastModifiedDate;
                $json = JsonFile::encode($data, 0);
            }
            if (!$this->cache->isReadOnly()) {
                $this->cache->write($cacheKey, $json);
            }

            return $data;
        } catch (\Exception $e) {
            if ($e instanceof \LogicException) {
                throw $e;
            }

            if ($e instanceof TransportException && $e->getStatusCode() === 404) {
                throw $e;
            }

            if (!$this->degradedMode) {
                $this->io->writeError('<warning>'.$this->url.' could not be fully loaded ('.$e->getMessage().'), package information was loaded from the local cache and may be out of date</warning>');
            }
            $this->degradedMode = true;

            return true;
        }
    }

    /**
     * @phpstan-return PromiseInterface<array<mixed>|true> true if the response was a 304 and the cache is fresh, otherwise it returns the decoded json
     */
    private function asyncFetchFile(string $filename, string $cacheKey, ?string $lastModifiedTime = null): PromiseInterface
    {
        if ('' === $filename) {
            throw new \InvalidArgumentException('$filename should not be an empty string');
        }

        if (isset($this->packagesNotFoundCache[$filename])) {
            return \React\Promise\resolve(['packages' => []]);
        }

        if (isset($this->freshMetadataUrls[$filename]) && $lastModifiedTime) {
            // make it look like we got a 304 response
            /** @var PromiseInterface<true> $promise */
            $promise = \React\Promise\resolve(true);

            return $promise;
        }

        $httpDownloader = $this->httpDownloader;
        $options = $this->options;
        if ($this->eventDispatcher) {
            $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $filename, 'metadata', ['repository' => $this]);
            $preFileDownloadEvent->setTransportOptions($this->options);
            $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent);
            $filename = $preFileDownloadEvent->getProcessedUrl();
            $options = $preFileDownloadEvent->getTransportOptions();
        }

        if ($lastModifiedTime) {
            if (isset($options['http']['header'])) {
                $options['http']['header'] = (array) $options['http']['header'];
            }
            $options['http']['header'][] = 'If-Modified-Since: '.$lastModifiedTime;
        }

        $io = $this->io;
        $url = $this->url;
        $cache = $this->cache;
        $degradedMode = &$this->degradedMode;
        $eventDispatcher = $this->eventDispatcher;

        /**
         * @return array<mixed>|true true if the response was a 304 and the cache is fresh
         */
        $accept = function ($response) use ($io, $url, $filename, $cache, $cacheKey, $eventDispatcher) {
            // package not found is acceptable for a v2 protocol repository
            if ($response->getStatusCode() === 404) {
                $this->packagesNotFoundCache[$filename] = true;

                return ['packages' => []];
            }

            $json = (string) $response->getBody();
            if ($json === '' && $response->getStatusCode() === 304) {
                $this->freshMetadataUrls[$filename] = true;

                return true;
            }

            if ($eventDispatcher) {
                $postFileDownloadEvent = new PostFileDownloadEvent(PluginEvents::POST_FILE_DOWNLOAD, null, null, $filename, 'metadata', ['response' => $response, 'repository' => $this]);
                $eventDispatcher->dispatch($postFileDownloadEvent->getName(), $postFileDownloadEvent);
            }

            $data = $response->decodeJson();
            HttpDownloader::outputWarnings($io, $url, $data);

            $lastModifiedDate = $response->getHeader('last-modified');
            $response->collect();
            if ($lastModifiedDate) {
                $data['last-modified'] = $lastModifiedDate;
                $json = JsonFile::encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
            }
            if (!$cache->isReadOnly()) {
                $cache->write($cacheKey, $json);
            }
            $this->freshMetadataUrls[$filename] = true;

            return $data;
        };

        $reject = function ($e) use ($filename, $accept, $io, $url, &$degradedMode, $lastModifiedTime) {
            if ($e instanceof TransportException && $e->getStatusCode() === 404) {
                $this->packagesNotFoundCache[$filename] = true;

                return false;
            }

            if (!$degradedMode) {
                $io->writeError('<warning>'.$url.' could not be fully loaded ('.$e->getMessage().'), package information was loaded from the local cache and may be out of date</warning>');
            }
            $degradedMode = true;

            // if the file is in the cache, we fake a 304 Not Modified to allow the process to continue
            if ($lastModifiedTime) {
                return $accept(new Response(['url' => $url], 304, [], ''));
            }

            // special error code returned when network is being artificially disabled
            if ($e instanceof TransportException && $e->getStatusCode() === 499) {
                return $accept(new Response(['url' => $url], 404, [], ''));
            }

            throw $e;
        };

        return $httpDownloader->add($filename, $options)->then($accept, $reject);
    }

    /**
     * This initializes the packages key of a partial packages.json that contain some packages inlined + a providers-lazy-url
     *
     * This should only be called once
     */
    private function initializePartialPackages(): void
    {
        $rootData = $this->loadRootServerFile();
        if ($rootData === true) {
            return;
        }

        $this->partialPackagesByName = [];
        foreach ($rootData['packages'] as $package => $versions) {
            foreach ($versions as $version) {
                $versionPackageName = strtolower((string) ($version['name'] ?? ''));
                $this->partialPackagesByName[$versionPackageName][] = $version;
                if (!$this->displayedWarningAboutNonMatchingPackageIndex && $versionPackageName !== strtolower($package)) {
                    $this->io->writeError(sprintf("<warning>Warning: the packages key '%s' doesn't match the name defined in the package metadata '%s' in repository %s</warning>", $package, $version['name'] ?? '', $this->baseUrl));
                    $this->displayedWarningAboutNonMatchingPackageIndex = true;
                }
            }
        }

        // wipe rootData as it is fully consumed at this point and this saves some memory
        $this->rootData = true;
    }

    /**
     * Checks if the package name is present in this lazy providers repo
     *
     * @return bool   true if the package name is present in availablePackages or matched by availablePackagePatterns
     */
    protected function lazyProvidersRepoContains(string $name)
    {
        if (!$this->hasAvailablePackageList) {
            throw new \LogicException('lazyProvidersRepoContains should not be called unless hasAvailablePackageList is true');
        }

        if (is_array($this->availablePackages) && isset($this->availablePackages[$name])) {
            return true;
        }

        if (is_array($this->availablePackagePatterns)) {
            foreach ($this->availablePackagePatterns as $providerRegex) {
                if (Preg::isMatch($providerRegex, $name)) {
                    return true;
                }
            }
        }

        return false;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Package\PackageInterface;
use Composer\Package\BasePackage;
use Composer\Semver\Constraint\ConstraintInterface;

/**
 * Repository interface.
 *
 * @author Nils Adermann <naderman@naderman.de>
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface RepositoryInterface extends \Countable
{
    public const SEARCH_FULLTEXT = 0;
    public const SEARCH_NAME = 1;
    public const SEARCH_VENDOR = 2;

    /**
     * Checks if specified package registered (installed).
     *
     * @param PackageInterface $package package instance
     *
     * @return bool
     */
    public function hasPackage(PackageInterface $package);

    /**
     * Searches for the first match of a package by name and version.
     *
     * @param string                     $name       package name
     * @param string|ConstraintInterface $constraint package version or version constraint to match against
     *
     * @return BasePackage|null
     */
    public function findPackage(string $name, $constraint);

    /**
     * Searches for all packages matching a name and optionally a version.
     *
     * @param string                     $name       package name
     * @param string|ConstraintInterface $constraint package version or version constraint to match against
     *
     * @return BasePackage[]
     */
    public function findPackages(string $name, $constraint = null);

    /**
     * Returns list of registered packages.
     *
     * @return BasePackage[]
     */
    public function getPackages();

    /**
     * Returns list of registered packages with the supplied name
     *
     * - The packages returned are the packages found which match the constraints, acceptable stability and stability flags provided
     * - The namesFound returned are names which should be considered as canonically found in this repository, that should not be looked up in any further lower priority repositories
     *
     * @param ConstraintInterface[]                          $packageNameMap        package names pointing to constraints
     * @param array<string, int>        $acceptableStabilities array of stability => BasePackage::STABILITY_* value
     * @param array<string, BasePackage::STABILITY_*>        $stabilityFlags        an array of package name => BasePackage::STABILITY_* value
     * @param array<string, array<string, PackageInterface>> $alreadyLoaded         an array of package name => package version => package
     *
     * @return array
     *
     * @phpstan-param  array<key-of<BasePackage::STABILITIES>, BasePackage::STABILITY_*> $acceptableStabilities
     * @phpstan-param  array<string, ConstraintInterface|null> $packageNameMap
     * @phpstan-return array{namesFound: array<string>, packages: array<BasePackage>}
     */
    public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = []);

    /**
     * Searches the repository for packages containing the query
     *
     * @param string  $query search query, for SEARCH_NAME and SEARCH_VENDOR regular expressions metacharacters are supported by implementations, and user input should be escaped through preg_quote by callers
     * @param int     $mode  a set of SEARCH_* constants to search on, implementations should do a best effort only, default is SEARCH_FULLTEXT
     * @param ?string $type  The type of package to search for. Defaults to all types of packages
     *
     * @return array[] an array of array('name' => '...', 'description' => '...'|null, 'abandoned' => 'string'|true|unset) For SEARCH_VENDOR the name will be in "vendor" form
     * @phpstan-return list<array{name: string, description: ?string, abandoned?: string|true, url?: string}>
     */
    public function search(string $query, int $mode = 0, ?string $type = null);

    /**
     * Returns a list of packages providing a given package name
     *
     * Packages which have the same name as $packageName should not be returned, only those that have a "provide" on it.
     *
     * @param string $packageName package name which must be provided
     *
     * @return array[] an array with the provider name as key and value of array('name' => '...', 'description' => '...', 'type' => '...')
     * @phpstan-return array<string, array{name: string, description: string|null, type: string}>
     */
    public function getProviders(string $packageName);

    /**
     * Returns a name representing this repository to the user
     *
     * This is best effort and definitely can not always be very precise
     *
     * @return string
     */
    public function getRepoName();
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

/**
 * Exception thrown when a package repository is utterly broken
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class InvalidRepositoryException extends \Exception
{
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Package\BasePackage;
use Composer\Package\PackageInterface;

/**
 * Composite repository.
 *
 * @author Beau Simensen <beau@dflydev.com>
 */
class CompositeRepository implements RepositoryInterface
{
    /**
     * List of repositories
     * @var RepositoryInterface[]
     */
    private $repositories;

    /**
     * Constructor
     * @param RepositoryInterface[] $repositories
     */
    public function __construct(array $repositories)
    {
        $this->repositories = [];
        foreach ($repositories as $repo) {
            $this->addRepository($repo);
        }
    }

    public function getRepoName(): string
    {
        return 'composite repo ('.implode(', ', array_map(static function ($repo): string {
            return $repo->getRepoName();
        }, $this->repositories)).')';
    }

    /**
     * Returns all the wrapped repositories
     *
     * @return RepositoryInterface[]
     */
    public function getRepositories(): array
    {
        return $this->repositories;
    }

    /**
     * @inheritDoc
     */
    public function hasPackage(PackageInterface $package): bool
    {
        foreach ($this->repositories as $repository) {
            /* @var $repository RepositoryInterface */
            if ($repository->hasPackage($package)) {
                return true;
            }
        }

        return false;
    }

    /**
     * @inheritDoc
     */
    public function findPackage($name, $constraint): ?BasePackage
    {
        foreach ($this->repositories as $repository) {
            /* @var $repository RepositoryInterface */
            $package = $repository->findPackage($name, $constraint);
            if (null !== $package) {
                return $package;
            }
        }

        return null;
    }

    /**
     * @inheritDoc
     */
    public function findPackages($name, $constraint = null): array
    {
        $packages = [];
        foreach ($this->repositories as $repository) {
            /* @var $repository RepositoryInterface */
            $packages[] = $repository->findPackages($name, $constraint);
        }

        return $packages ? array_merge(...$packages) : [];
    }

    /**
     * @inheritDoc
     */
    public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = []): array
    {
        $packages = [];
        $namesFound = [];
        foreach ($this->repositories as $repository) {
            /* @var $repository RepositoryInterface */
            $result = $repository->loadPackages($packageNameMap, $acceptableStabilities, $stabilityFlags, $alreadyLoaded);
            $packages[] = $result['packages'];
            $namesFound[] = $result['namesFound'];
        }

        return [
            'packages' => $packages ? array_merge(...$packages) : [],
            'namesFound' => $namesFound ? array_unique(array_merge(...$namesFound)) : [],
        ];
    }

    /**
     * @inheritDoc
     */
    public function search(string $query, int $mode = 0, ?string $type = null): array
    {
        $matches = [];
        foreach ($this->repositories as $repository) {
            /* @var $repository RepositoryInterface */
            $matches[] = $repository->search($query, $mode, $type);
        }

        return \count($matches) > 0 ? array_merge(...$matches) : [];
    }

    /**
     * @inheritDoc
     */
    public function getPackages(): array
    {
        $packages = [];
        foreach ($this->repositories as $repository) {
            /* @var $repository RepositoryInterface */
            $packages[] = $repository->getPackages();
        }

        return $packages ? array_merge(...$packages) : [];
    }

    /**
     * @inheritDoc
     */
    public function getProviders($packageName): array
    {
        $results = [];
        foreach ($this->repositories as $repository) {
            /* @var $repository RepositoryInterface */
            $results[] = $repository->getProviders($packageName);
        }

        return $results ? array_merge(...$results) : [];
    }

    public function removePackage(PackageInterface $package): void
    {
        foreach ($this->repositories as $repository) {
            if ($repository instanceof WritableRepositoryInterface) {
                $repository->removePackage($package);
            }
        }
    }

    /**
     * @inheritDoc
     */
    public function count(): int
    {
        $total = 0;
        foreach ($this->repositories as $repository) {
            /* @var $repository RepositoryInterface */
            $total += $repository->count();
        }

        return $total;
    }

    /**
     * Add a repository.
     */
    public function addRepository(RepositoryInterface $repository): void
    {
        if ($repository instanceof self) {
            foreach ($repository->getRepositories() as $repo) {
                $this->addRepository($repo);
            }
        } else {
            $this->repositories[] = $repository;
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Package\BasePackage;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\MatchAllConstraint;
use Composer\Package\RootPackageInterface;
use Composer\Package\Link;

/**
 * Installed repository is a composite of all installed repo types.
 *
 * The main use case is tagging a repo as an "installed" repository, and offering a way to get providers/replacers easily.
 *
 * Installed repos are LockArrayRepository, InstalledRepositoryInterface, RootPackageRepository and PlatformRepository
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class InstalledRepository extends CompositeRepository
{
    /**
     * @param ConstraintInterface|string|null $constraint
     *
     * @return BasePackage[]
     */
    public function findPackagesWithReplacersAndProviders(string $name, $constraint = null): array
    {
        $name = strtolower($name);

        if (null !== $constraint && !$constraint instanceof ConstraintInterface) {
            $versionParser = new VersionParser();
            $constraint = $versionParser->parseConstraints($constraint);
        }

        $matches = [];
        foreach ($this->getRepositories() as $repo) {
            foreach ($repo->getPackages() as $candidate) {
                if ($name === $candidate->getName()) {
                    if (null === $constraint || $constraint->matches(new Constraint('==', $candidate->getVersion()))) {
                        $matches[] = $candidate;
                    }
                    continue;
                }

                foreach (array_merge($candidate->getProvides(), $candidate->getReplaces()) as $link) {
                    if (
                        $name === $link->getTarget()
                        && ($constraint === null || $constraint->matches($link->getConstraint()))
                    ) {
                        $matches[] = $candidate;
                        continue 2;
                    }
                }
            }
        }

        return $matches;
    }

    /**
     * Returns a list of links causing the requested needle packages to be installed, as an associative array with the
     * dependent's name as key, and an array containing in order the PackageInterface and Link describing the relationship
     * as values. If recursive lookup was requested a third value is returned containing an identically formed array up
     * to the root package. That third value will be false in case a circular recursion was detected.
     *
     * @param  string|string[]          $needle        The package name(s) to inspect.
     * @param  ConstraintInterface|null $constraint    Optional constraint to filter by.
     * @param  bool                     $invert        Whether to invert matches to discover reasons for the package *NOT* to be installed.
     * @param  bool                     $recurse       Whether to recursively expand the requirement tree up to the root package.
     * @param  string[]                 $packagesFound Used internally when recurring
     *
     * @return array[] An associative array of arrays as described above.
     * @phpstan-return array<array{0: PackageInterface, 1: Link, 2: array<mixed>|false}>
     */
    public function getDependents($needle, ?ConstraintInterface $constraint = null, bool $invert = false, bool $recurse = true, ?array $packagesFound = null): array
    {
        $needles = array_map('strtolower', (array) $needle);
        $results = [];

        // initialize the array with the needles before any recursion occurs
        if (null === $packagesFound) {
            $packagesFound = $needles;
        }

        // locate root package for use below
        $rootPackage = null;
        foreach ($this->getPackages() as $package) {
            if ($package instanceof RootPackageInterface) {
                $rootPackage = $package;
                break;
            }
        }

        // Loop over all currently installed packages.
        foreach ($this->getPackages() as $package) {
            $links = $package->getRequires();

            // each loop needs its own "tree" as we want to show the complete dependent set of every needle
            // without warning all the time about finding circular deps
            $packagesInTree = $packagesFound;

            // Replacements are considered valid reasons for a package to be installed during forward resolution
            if (!$invert) {
                $links += $package->getReplaces();

                // On forward search, check if any replaced package was required and add the replaced
                // packages to the list of needles. Contrary to the cross-reference link check below,
                // replaced packages are the target of links.
                foreach ($package->getReplaces() as $link) {
                    foreach ($needles as $needle) {
                        if ($link->getSource() === $needle) {
                            if ($constraint === null || ($link->getConstraint()->matches($constraint) === true)) {
                                // already displayed this node's dependencies, cutting short
                                if (in_array($link->getTarget(), $packagesInTree)) {
                                    $results[] = [$package, $link, false];
                                    continue;
                                }
                                $packagesInTree[] = $link->getTarget();
                                $dependents = $recurse ? $this->getDependents($link->getTarget(), null, false, true, $packagesInTree) : [];
                                $results[] = [$package, $link, $dependents];
                                $needles[] = $link->getTarget();
                            }
                        }
                    }
                }
                unset($needle);
            }

            // Require-dev is only relevant for the root package
            if ($package instanceof RootPackageInterface) {
                $links += $package->getDevRequires();
            }

            // Cross-reference all discovered links to the needles
            foreach ($links as $link) {
                foreach ($needles as $needle) {
                    if ($link->getTarget() === $needle) {
                        if ($constraint === null || ($link->getConstraint()->matches($constraint) === !$invert)) {
                            // already displayed this node's dependencies, cutting short
                            if (in_array($link->getSource(), $packagesInTree)) {
                                $results[] = [$package, $link, false];
                                continue;
                            }
                            $packagesInTree[] = $link->getSource();
                            $dependents = $recurse ? $this->getDependents($link->getSource(), null, false, true, $packagesInTree) : [];
                            $results[] = [$package, $link, $dependents];
                        }
                    }
                }
            }

            // When inverting, we need to check for conflicts of the needles against installed packages
            if ($invert && in_array($package->getName(), $needles, true)) {
                foreach ($package->getConflicts() as $link) {
                    foreach ($this->findPackages($link->getTarget()) as $pkg) {
                        $version = new Constraint('=', $pkg->getVersion());
                        if ($link->getConstraint()->matches($version) === $invert) {
                            $results[] = [$package, $link, false];
                        }
                    }
                }
            }

            // List conflicts against X as they may explain why the current version was selected, or explain why it is rejected if the conflict matched when inverting
            foreach ($package->getConflicts() as $link) {
                if (in_array($link->getTarget(), $needles, true)) {
                    foreach ($this->findPackages($link->getTarget()) as $pkg) {
                        $version = new Constraint('=', $pkg->getVersion());
                        if ($link->getConstraint()->matches($version) === $invert) {
                            $results[] = [$package, $link, false];
                        }
                    }
                }
            }

            // When inverting, we need to check for conflicts of the needles' requirements against installed packages
            if ($invert && $constraint && in_array($package->getName(), $needles, true) && $constraint->matches(new Constraint('=', $package->getVersion()))) {
                foreach ($package->getRequires() as $link) {
                    if (PlatformRepository::isPlatformPackage($link->getTarget())) {
                        if ($this->findPackage($link->getTarget(), $link->getConstraint())) {
                            continue;
                        }

                        $platformPkg = $this->findPackage($link->getTarget(), '*');
                        $description = $platformPkg ? 'but '.$platformPkg->getPrettyVersion().' is installed' : 'but it is missing';
                        $results[] = [$package, new Link($package->getName(), $link->getTarget(), new MatchAllConstraint, Link::TYPE_REQUIRE, $link->getPrettyConstraint().' '.$description), false];

                        continue;
                    }

                    foreach ($this->getPackages() as $pkg) {
                        if (!in_array($link->getTarget(), $pkg->getNames())) {
                            continue;
                        }

                        $version = new Constraint('=', $pkg->getVersion());

                        if ($link->getTarget() !== $pkg->getName()) {
                            foreach (array_merge($pkg->getReplaces(), $pkg->getProvides()) as $prov) {
                                if ($link->getTarget() === $prov->getTarget()) {
                                    $version = $prov->getConstraint();
                                    break;
                                }
                            }
                        }

                        if (!$link->getConstraint()->matches($version)) {
                            // if we have a root package (we should but can not guarantee..) we show
                            // the root requires as well to perhaps allow to find an issue there
                            if ($rootPackage) {
                                foreach (array_merge($rootPackage->getRequires(), $rootPackage->getDevRequires()) as $rootReq) {
                                    if (in_array($rootReq->getTarget(), $pkg->getNames()) && !$rootReq->getConstraint()->matches($link->getConstraint())) {
                                        $results[] = [$package, $link, false];
                                        $results[] = [$rootPackage, $rootReq, false];
                                        continue 3;
                                    }
                                }

                                $results[] = [$package, $link, false];
                                $results[] = [$rootPackage, new Link($rootPackage->getName(), $link->getTarget(), new MatchAllConstraint, Link::TYPE_DOES_NOT_REQUIRE, 'but ' . $pkg->getPrettyVersion() . ' is installed'), false];
                            } else {
                                // no root so let's just print whatever we found
                                $results[] = [$package, $link, false];
                            }
                        }

                        continue 2;
                    }
                }
            }
        }

        ksort($results);

        return $results;
    }

    public function getRepoName(): string
    {
        return 'installed repo ('.implode(', ', array_map(static function ($repo): string {
            return $repo->getRepoName();
        }, $this->getRepositories())).')';
    }

    /**
     * @inheritDoc
     */
    public function addRepository(RepositoryInterface $repository): void
    {
        if (
            $repository instanceof LockArrayRepository
            || $repository instanceof InstalledRepositoryInterface
            || $repository instanceof RootPackageRepository
            || $repository instanceof PlatformRepository
        ) {
            parent::addRepository($repository);

            return;
        }

        throw new \LogicException('An InstalledRepository can not contain a repository of type '.get_class($repository).' ('.$repository->getRepoName().')');
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Package\PackageInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 *
 * @see RepositorySet for ways to work with sets of repos
 */
class RepositoryUtils
{
    /**
     * Find all of $packages which are required by $requirer, either directly or transitively
     *
     * Require-dev is ignored by default, you can enable the require-dev of the initial $requirer
     * packages by passing $includeRequireDev=true, but require-dev of transitive dependencies
     * are always ignored.
     *
     * @template T of PackageInterface
     * @param  array<T> $packages
     * @param  list<T> $bucket Do not pass this in, only used to avoid recursion with circular deps
     * @return list<T>
     */
    public static function filterRequiredPackages(array $packages, PackageInterface $requirer, bool $includeRequireDev = false, array $bucket = []): array
    {
        $requires = $requirer->getRequires();
        if ($includeRequireDev) {
            $requires = array_merge($requires, $requirer->getDevRequires());
        }

        foreach ($packages as $candidate) {
            foreach ($candidate->getNames() as $name) {
                if (isset($requires[$name])) {
                    if (!in_array($candidate, $bucket, true)) {
                        $bucket[] = $candidate;
                        $bucket = self::filterRequiredPackages($packages, $candidate, false, $bucket);
                    }
                    break;
                }
            }
        }

        return $bucket;
    }

    /**
     * Unwraps CompositeRepository, InstalledRepository and optionally FilterRepository to get a flat array of pure repository instances
     *
     * @return RepositoryInterface[]
     */
    public static function flattenRepositories(RepositoryInterface $repo, bool $unwrapFilterRepos = true): array
    {
        // unwrap filter repos
        if ($unwrapFilterRepos && $repo instanceof FilterRepository) {
            $repo = $repo->getRepository();
        }

        if (!$repo instanceof CompositeRepository) {
            return [$repo];
        }

        $repos = [];
        foreach ($repo->getRepositories() as $r) {
            foreach (self::flattenRepositories($r, $unwrapFilterRepos) as $r2) {
                $repos[] = $r2;
            }
        }

        return $repos;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

/**
 * Installed array repository.
 *
 * This is used as an in-memory InstalledRepository mostly for testing purposes
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class InstalledArrayRepository extends WritableArrayRepository implements InstalledRepositoryInterface
{
    public function getRepoName(): string
    {
        return 'installed '.parent::getRepoName();
    }

    /**
     * @inheritDoc
     */
    public function isFresh(): bool
    {
        // this is not a completely correct implementation but there is no way to
        // distinguish an empty repo and a newly created one given this is all in-memory
        return $this->count() === 0;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Json\JsonFile;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\PackageInterface;
use Composer\Package\RootAliasPackage;
use Composer\Package\RootPackageInterface;
use Composer\Package\AliasPackage;
use Composer\Package\Dumper\ArrayDumper;
use Composer\Installer\InstallationManager;
use Composer\Pcre\Preg;
use Composer\Util\Filesystem;
use Composer\Util\Platform;

/**
 * Filesystem repository.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class FilesystemRepository extends WritableArrayRepository
{
    /** @var JsonFile */
    protected $file;
    /** @var bool */
    private $dumpVersions;
    /** @var ?RootPackageInterface */
    private $rootPackage;
    /** @var Filesystem */
    private $filesystem;
    /** @var bool|null */
    private $devMode = null;

    /**
     * Initializes filesystem repository.
     *
     * @param JsonFile              $repositoryFile repository json file
     * @param ?RootPackageInterface $rootPackage    Must be provided if $dumpVersions is true
     */
    public function __construct(JsonFile $repositoryFile, bool $dumpVersions = false, ?RootPackageInterface $rootPackage = null, ?Filesystem $filesystem = null)
    {
        parent::__construct();
        $this->file = $repositoryFile;
        $this->dumpVersions = $dumpVersions;
        $this->rootPackage = $rootPackage;
        $this->filesystem = $filesystem ?: new Filesystem;
        if ($dumpVersions && !$rootPackage) {
            throw new \InvalidArgumentException('Expected a root package instance if $dumpVersions is true');
        }
    }

    /**
     * @return bool|null true if dev requirements were installed, false if --no-dev was used, null if yet unknown
     */
    public function getDevMode()
    {
        return $this->devMode;
    }

    /**
     * Initializes repository (reads file, or remote address).
     */
    protected function initialize()
    {
        parent::initialize();

        if (!$this->file->exists()) {
            return;
        }

        try {
            $data = $this->file->read();
            if (isset($data['packages'])) {
                $packages = $data['packages'];
            } else {
                $packages = $data;
            }

            if (isset($data['dev-package-names'])) {
                $this->setDevPackageNames($data['dev-package-names']);
            }
            if (isset($data['dev'])) {
                $this->devMode = $data['dev'];
            }

            if (!is_array($packages)) {
                throw new \UnexpectedValueException('Could not parse package list from the repository');
            }
        } catch (\Exception $e) {
            throw new InvalidRepositoryException('Invalid repository data in '.$this->file->getPath().', packages could not be loaded: ['.get_class($e).'] '.$e->getMessage());
        }

        $loader = new ArrayLoader(null, true);
        foreach ($packages as $packageData) {
            $package = $loader->load($packageData);
            $this->addPackage($package);
        }
    }

    public function reload()
    {
        $this->packages = null;
        $this->initialize();
    }

    /**
     * Writes writable repository.
     */
    public function write(bool $devMode, InstallationManager $installationManager)
    {
        $data = ['packages' => [], 'dev' => $devMode, 'dev-package-names' => []];
        $dumper = new ArrayDumper();

        // make sure the directory is created so we can realpath it
        // as realpath() does some additional normalizations with network paths that normalizePath does not
        // and we need to find shortest path correctly
        $repoDir = dirname($this->file->getPath());
        $this->filesystem->ensureDirectoryExists($repoDir);

        $repoDir = $this->filesystem->normalizePath(realpath($repoDir));
        $installPaths = [];

        foreach ($this->getCanonicalPackages() as $package) {
            $pkgArray = $dumper->dump($package);
            $path = $installationManager->getInstallPath($package);
            $installPath = null;
            if ('' !== $path && null !== $path) {
                $normalizedPath = $this->filesystem->normalizePath($this->filesystem->isAbsolutePath($path) ? $path : Platform::getCwd() . '/' . $path);
                $installPath = $this->filesystem->findShortestPath($repoDir, $normalizedPath, true);
            }
            $installPaths[$package->getName()] = $installPath;

            $pkgArray['install-path'] = $installPath;
            $data['packages'][] = $pkgArray;

            // only write to the files the names which are really installed, as we receive the full list
            // of dev package names before they get installed during composer install
            if (in_array($package->getName(), $this->devPackageNames, true)) {
                $data['dev-package-names'][] = $package->getName();
            }
        }

        sort($data['dev-package-names']);
        usort($data['packages'], static function ($a, $b): int {
            return strcmp($a['name'], $b['name']);
        });

        $this->file->write($data);

        if ($this->dumpVersions) {
            $versions = $this->generateInstalledVersions($installationManager, $installPaths, $devMode, $repoDir);

            $this->filesystem->filePutContentsIfModified($repoDir.'/installed.php', '<?php return ' . $this->dumpToPhpCode($versions) . ';'."\n");
            $installedVersionsClass = file_get_contents(__DIR__.'/../InstalledVersions.php');

            // this normally should not happen but during upgrades of Composer when it is installed in the project it is a possibility
            if ($installedVersionsClass !== false) {
                $this->filesystem->filePutContentsIfModified($repoDir.'/InstalledVersions.php', $installedVersionsClass);

                // make sure the in memory state is up to date with on disk
                \Composer\InstalledVersions::reload($versions);

                // make sure the selfDir matches the expected data at runtime if the class was loaded from the vendor dir, as it may have been
                // loaded from the Composer sources, causing packages to appear twice in that case if the installed.php is loaded in addition to the
                // in memory loaded data from above
                try {
                    $reflProp = new \ReflectionProperty(\Composer\InstalledVersions::class, 'selfDir');
                    (\PHP_VERSION_ID < 80100) and $reflProp->setAccessible(true);
                    $reflProp->setValue(null, strtr($repoDir, '\\', '/'));

                    $reflProp = new \ReflectionProperty(\Composer\InstalledVersions::class, 'installedIsLocalDir');
                    (\PHP_VERSION_ID < 80100) and $reflProp->setAccessible(true);
                    $reflProp->setValue(null, true);
                } catch (\ReflectionException $e) {
                    if (!Preg::isMatch('{Property .*? does not exist}i', $e->getMessage())) {
                        throw $e;
                    }
                    // noop, if outdated class is loaded we do not want to cause trouble
                }
            }
        }
    }

    /**
     * As we load the file from vendor dir during bootstrap, we need to make sure it contains only expected code before executing it
     *
     * @internal
     */
    public static function safelyLoadInstalledVersions(string $path): bool
    {
        $installedVersionsData = @file_get_contents($path);
        $pattern = <<<'REGEX'
{(?(DEFINE)
   (?<number>  -? \s*+ \d++ (?:\.\d++)? )
   (?<boolean> true | false | null )
   (?<strings> (?&string) (?: \s*+ \. \s*+ (?&string))*+ )
   (?<string>  (?: " (?:[^"\\$]*+ | \\ ["\\0] )* " | ' (?:[^'\\]*+ | \\ ['\\] )* ' ) )
   (?<array>   array\( \s*+ (?: (?:(?&number)|(?&strings)) \s*+ => \s*+ (?: (?:__DIR__ \s*+ \. \s*+)? (?&strings) | (?&value) ) \s*+, \s*+ )*+  \s*+ \) )
   (?<value>   (?: (?&number) | (?&boolean) | (?&strings) | (?&array) ) )
)
^<\?php\s++return\s++(?&array)\s*+;$}ix
REGEX;
        if (is_string($installedVersionsData) && Preg::isMatch($pattern, trim($installedVersionsData))) {
            \Composer\InstalledVersions::reload(eval('?>'.Preg::replace('{=>\s*+__DIR__\s*+\.\s*+([\'"])}', '=> '.var_export(dirname($path), true).' . $1', $installedVersionsData)));

            return true;
        }

        return false;
    }

    /**
     * @param array<mixed> $array
     */
    private function dumpToPhpCode(array $array = [], int $level = 0): string
    {
        $lines = "array(\n";
        $level++;

        foreach ($array as $key => $value) {
            $lines .= str_repeat('    ', $level);
            $lines .= is_int($key) ? $key . ' => ' : var_export($key, true) . ' => ';

            if (is_array($value)) {
                if (!empty($value)) {
                    $lines .= $this->dumpToPhpCode($value, $level);
                } else {
                    $lines .= "array(),\n";
                }
            } elseif ($key === 'install_path' && is_string($value)) {
                if ($this->filesystem->isAbsolutePath($value)) {
                    $lines .= var_export($value, true) . ",\n";
                } else {
                    $lines .= "__DIR__ . " . var_export('/' . $value, true) . ",\n";
                }
            } elseif (is_string($value)) {
                $lines .= var_export($value, true) . ",\n";
            } elseif (is_bool($value)) {
                $lines .= ($value ? 'true' : 'false') . ",\n";
            } elseif (is_null($value)) {
                $lines .= "null,\n";
            } else {
                throw new \UnexpectedValueException('Unexpected type '.get_debug_type($value));
            }
        }

        $lines .= str_repeat('    ', $level - 1) . ')' . ($level - 1 === 0 ? '' : ",\n");

        return $lines;
    }

    /**
     * @param array<string, string> $installPaths
     *
     * @return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
     */
    private function generateInstalledVersions(InstallationManager $installationManager, array $installPaths, bool $devMode, string $repoDir): array
    {
        $devPackages = array_flip($this->devPackageNames);
        $packages = $this->getPackages();
        if (null === $this->rootPackage) {
            throw new \LogicException('It should not be possible to dump packages if no root package is given');
        }
        $packages[] = $rootPackage = $this->rootPackage;

        while ($rootPackage instanceof RootAliasPackage) {
            $rootPackage = $rootPackage->getAliasOf();
            $packages[] = $rootPackage;
        }
        $versions = [
            'root' => $this->dumpRootPackage($rootPackage, $installPaths, $devMode, $repoDir, $devPackages),
            'versions' => [],
        ];

        // add real installed packages
        foreach ($packages as $package) {
            if ($package instanceof AliasPackage) {
                continue;
            }

            $versions['versions'][$package->getName()] = $this->dumpInstalledPackage($package, $installPaths, $repoDir, $devPackages);
        }

        // add provided/replaced packages
        foreach ($packages as $package) {
            $isDevPackage = isset($devPackages[$package->getName()]);
            foreach ($package->getReplaces() as $replace) {
                // exclude platform replaces as when they are really there we can not check for their presence
                if (PlatformRepository::isPlatformPackage($replace->getTarget())) {
                    continue;
                }
                if (!isset($versions['versions'][$replace->getTarget()]['dev_requirement'])) {
                    $versions['versions'][$replace->getTarget()]['dev_requirement'] = $isDevPackage;
                } elseif (!$isDevPackage) {
                    $versions['versions'][$replace->getTarget()]['dev_requirement'] = false;
                }
                $replaced = $replace->getPrettyConstraint();
                if ($replaced === 'self.version') {
                    $replaced = $package->getPrettyVersion();
                }
                if (!isset($versions['versions'][$replace->getTarget()]['replaced']) || !in_array($replaced, $versions['versions'][$replace->getTarget()]['replaced'], true)) {
                    $versions['versions'][$replace->getTarget()]['replaced'][] = $replaced;
                }
            }
            foreach ($package->getProvides() as $provide) {
                // exclude platform provides as when they are really there we can not check for their presence
                if (PlatformRepository::isPlatformPackage($provide->getTarget())) {
                    continue;
                }
                if (!isset($versions['versions'][$provide->getTarget()]['dev_requirement'])) {
                    $versions['versions'][$provide->getTarget()]['dev_requirement'] = $isDevPackage;
                } elseif (!$isDevPackage) {
                    $versions['versions'][$provide->getTarget()]['dev_requirement'] = false;
                }
                $provided = $provide->getPrettyConstraint();
                if ($provided === 'self.version') {
                    $provided = $package->getPrettyVersion();
                }
                if (!isset($versions['versions'][$provide->getTarget()]['provided']) || !in_array($provided, $versions['versions'][$provide->getTarget()]['provided'], true)) {
                    $versions['versions'][$provide->getTarget()]['provided'][] = $provided;
                }
            }
        }

        // add aliases
        foreach ($packages as $package) {
            if (!$package instanceof AliasPackage) {
                continue;
            }
            $versions['versions'][$package->getName()]['aliases'][] = $package->getPrettyVersion();
            if ($package instanceof RootPackageInterface) {
                $versions['root']['aliases'][] = $package->getPrettyVersion();
            }
        }

        ksort($versions['versions']);
        ksort($versions);

        foreach ($versions['versions'] as $name => $version) {
            foreach (['aliases', 'replaced', 'provided'] as $key) {
                if (isset($versions['versions'][$name][$key])) {
                    sort($versions['versions'][$name][$key], SORT_NATURAL);
                }
            }
        }

        return $versions;
    }

    /**
     * @param array<string, string> $installPaths
     * @param array<string, int> $devPackages
     * @return array{pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev_requirement: bool}
     */
    private function dumpInstalledPackage(PackageInterface $package, array $installPaths, string $repoDir, array $devPackages): array
    {
        $reference = null;
        if ($package->getInstallationSource()) {
            $reference = $package->getInstallationSource() === 'source' ? $package->getSourceReference() : $package->getDistReference();
        }
        if (null === $reference) {
            $reference = ($package->getSourceReference() ?: $package->getDistReference()) ?: null;
        }

        if ($package instanceof RootPackageInterface) {
            $to = $this->filesystem->normalizePath(realpath(Platform::getCwd()));
            $installPath = $this->filesystem->findShortestPath($repoDir, $to, true);
        } else {
            $installPath = $installPaths[$package->getName()];
        }

        $data = [
            'pretty_version' => $package->getPrettyVersion(),
            'version' => $package->getVersion(),
            'reference' => $reference,
            'type' => $package->getType(),
            'install_path' => $installPath,
            'aliases' => [],
            'dev_requirement' => isset($devPackages[$package->getName()]),
        ];

        return $data;
    }

    /**
     * @param array<string, string> $installPaths
     * @param array<string, int> $devPackages
     * @return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
     */
    private function dumpRootPackage(RootPackageInterface $package, array $installPaths, bool $devMode, string $repoDir, array $devPackages)
    {
        $data = $this->dumpInstalledPackage($package, $installPaths, $repoDir, $devPackages);

        return [
            'name' => $package->getName(),
            'pretty_version' => $data['pretty_version'],
            'version' => $data['version'],
            'reference' => $data['reference'],
            'type' => $data['type'],
            'install_path' => $data['install_path'],
            'aliases' => $data['aliases'],
            'dev' => $devMode,
        ];
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

/**
 * Lock array repository.
 *
 * Regular array repository, only uses a different type to identify the lock file as the source of info
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class LockArrayRepository extends ArrayRepository
{
    use CanonicalPackagesTrait;

    public function getRepoName(): string
    {
        return 'lock repo';
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Package\AliasPackage;
use Composer\Package\BasePackage;
use Composer\Package\CompleteAliasPackage;
use Composer\Package\CompletePackage;
use Composer\Package\PackageInterface;
use Composer\Package\CompletePackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\Package\Version\StabilityFilter;
use Composer\Pcre\Preg;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\Constraint;

/**
 * A repository implementation that simply stores packages in an array
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class ArrayRepository implements RepositoryInterface
{
    /** @var ?array<BasePackage> */
    protected $packages = null;

    /**
     * @var ?array<BasePackage> indexed by package unique name and used to cache hasPackage calls
     */
    protected $packageMap = null;

    /**
     * @param array<PackageInterface> $packages
     */
    public function __construct(array $packages = [])
    {
        foreach ($packages as $package) {
            $this->addPackage($package);
        }
    }

    public function getRepoName()
    {
        return 'array repo (defining '.$this->count().' package'.($this->count() > 1 ? 's' : '').')';
    }

    /**
     * @inheritDoc
     */
    public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = [])
    {
        $packages = $this->getPackages();

        $result = [];
        $namesFound = [];
        foreach ($packages as $package) {
            if (array_key_exists($package->getName(), $packageNameMap)) {
                if (
                    (!$packageNameMap[$package->getName()] || $packageNameMap[$package->getName()]->matches(new Constraint('==', $package->getVersion())))
                    && StabilityFilter::isPackageAcceptable($acceptableStabilities, $stabilityFlags, $package->getNames(), $package->getStability())
                    && !isset($alreadyLoaded[$package->getName()][$package->getVersion()])
                ) {
                    // add selected packages which match stability requirements
                    $result[spl_object_hash($package)] = $package;
                    // add the aliased package for packages where the alias matches
                    if ($package instanceof AliasPackage && !isset($result[spl_object_hash($package->getAliasOf())])) {
                        $result[spl_object_hash($package->getAliasOf())] = $package->getAliasOf();
                    }
                }

                $namesFound[$package->getName()] = true;
            }
        }

        // add aliases of packages that were selected, even if the aliases did not match
        foreach ($packages as $package) {
            if ($package instanceof AliasPackage) {
                if (isset($result[spl_object_hash($package->getAliasOf())])) {
                    $result[spl_object_hash($package)] = $package;
                }
            }
        }

        return ['namesFound' => array_keys($namesFound), 'packages' => $result];
    }

    /**
     * @inheritDoc
     */
    public function findPackage(string $name, $constraint)
    {
        $name = strtolower($name);

        if (!$constraint instanceof ConstraintInterface) {
            $versionParser = new VersionParser();
            $constraint = $versionParser->parseConstraints($constraint);
        }

        foreach ($this->getPackages() as $package) {
            if ($name === $package->getName()) {
                $pkgConstraint = new Constraint('==', $package->getVersion());
                if ($constraint->matches($pkgConstraint)) {
                    return $package;
                }
            }
        }

        return null;
    }

    /**
     * @inheritDoc
     */
    public function findPackages(string $name, $constraint = null)
    {
        // normalize name
        $name = strtolower($name);
        $packages = [];

        if (null !== $constraint && !$constraint instanceof ConstraintInterface) {
            $versionParser = new VersionParser();
            $constraint = $versionParser->parseConstraints($constraint);
        }

        foreach ($this->getPackages() as $package) {
            if ($name === $package->getName()) {
                if (null === $constraint || $constraint->matches(new Constraint('==', $package->getVersion()))) {
                    $packages[] = $package;
                }
            }
        }

        return $packages;
    }

    /**
     * @inheritDoc
     */
    public function search(string $query, int $mode = 0, ?string $type = null)
    {
        if ($mode === self::SEARCH_FULLTEXT) {
            $regex = '{(?:'.implode('|', Preg::split('{\s+}', preg_quote($query))).')}i';
        } else {
            // vendor/name searches expect the caller to have preg_quoted the query
            $regex = '{(?:'.implode('|', Preg::split('{\s+}', $query)).')}i';
        }

        $matches = [];
        foreach ($this->getPackages() as $package) {
            $name = $package->getName();
            if ($mode === self::SEARCH_VENDOR) {
                [$name] = explode('/', $name);
            }
            if (isset($matches[$name])) {
                continue;
            }
            if (null !== $type && $package->getType() !== $type) {
                continue;
            }

            if (Preg::isMatch($regex, $name)
                || ($mode === self::SEARCH_FULLTEXT && $package instanceof CompletePackageInterface && Preg::isMatch($regex, implode(' ', (array) $package->getKeywords()) . ' ' . $package->getDescription()))
            ) {
                if ($mode === self::SEARCH_VENDOR) {
                    $matches[$name] = [
                        'name' => $name,
                        'description' => null,
                    ];
                } else {
                    $matches[$name] = [
                        'name' => $package->getPrettyName(),
                        'description' => $package instanceof CompletePackageInterface ? $package->getDescription() : null,
                    ];

                    if ($package instanceof CompletePackageInterface && $package->isAbandoned()) {
                        $matches[$name]['abandoned'] = $package->getReplacementPackage() ?: true;
                    }
                }
            }
        }

        return array_values($matches);
    }

    /**
     * @inheritDoc
     */
    public function hasPackage(PackageInterface $package)
    {
        if ($this->packageMap === null) {
            $this->packageMap = [];
            foreach ($this->getPackages() as $repoPackage) {
                $this->packageMap[$repoPackage->getUniqueName()] = $repoPackage;
            }
        }

        return isset($this->packageMap[$package->getUniqueName()]);
    }

    /**
     * Adds a new package to the repository
     *
     * @return void
     */
    public function addPackage(PackageInterface $package)
    {
        if (!$package instanceof BasePackage) {
            throw new \InvalidArgumentException('Only subclasses of BasePackage are supported');
        }
        if (null === $this->packages) {
            $this->initialize();
        }
        $package->setRepository($this);
        $this->packages[] = $package;

        if ($package instanceof AliasPackage) {
            $aliasedPackage = $package->getAliasOf();
            if (null === $aliasedPackage->getRepository()) {
                $this->addPackage($aliasedPackage);
            }
        }

        // invalidate package map cache
        $this->packageMap = null;
    }

    /**
     * @inheritDoc
     */
    public function getProviders(string $packageName)
    {
        $result = [];

        foreach ($this->getPackages() as $candidate) {
            if (isset($result[$candidate->getName()])) {
                continue;
            }
            foreach ($candidate->getProvides() as $link) {
                if ($packageName === $link->getTarget()) {
                    $result[$candidate->getName()] = [
                        'name' => $candidate->getName(),
                        'description' => $candidate instanceof CompletePackageInterface ? $candidate->getDescription() : null,
                        'type' => $candidate->getType(),
                    ];
                    continue 2;
                }
            }
        }

        return $result;
    }

    /**
     * @return AliasPackage|CompleteAliasPackage
     */
    protected function createAliasPackage(BasePackage $package, string $alias, string $prettyAlias)
    {
        while ($package instanceof AliasPackage) {
            $package = $package->getAliasOf();
        }

        if ($package instanceof CompletePackage) {
            return new CompleteAliasPackage($package, $alias, $prettyAlias);
        }

        return new AliasPackage($package, $alias, $prettyAlias);
    }

    /**
     * Removes package from repository.
     *
     * @param PackageInterface $package package instance
     *
     * @return void
     */
    public function removePackage(PackageInterface $package)
    {
        $packageId = $package->getUniqueName();

        foreach ($this->getPackages() as $key => $repoPackage) {
            if ($packageId === $repoPackage->getUniqueName()) {
                array_splice($this->packages, $key, 1);

                // invalidate package map cache
                $this->packageMap = null;

                return;
            }
        }
    }

    /**
     * @inheritDoc
     */
    public function getPackages()
    {
        if (null === $this->packages) {
            $this->initialize();
        }

        if (null === $this->packages) {
            throw new \LogicException('initialize failed to initialize the packages array');
        }

        return $this->packages;
    }

    /**
     * Returns the number of packages in this repository
     *
     * @return 0|positive-int Number of packages
     */
    public function count(): int
    {
        if (null === $this->packages) {
            $this->initialize();
        }

        return count($this->packages);
    }

    /**
     * Initializes the packages array. Mostly meant as an extension point.
     *
     * @return void
     */
    protected function initialize()
    {
        $this->packages = [];
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Package\AliasPackage;
use Composer\Package\PackageInterface;

/**
 * Provides getCanonicalPackages() to various repository implementations
 *
 * @internal
 */
trait CanonicalPackagesTrait
{
    /**
     * Get unique packages (at most one package of each name), with aliases resolved and removed.
     *
     * @return PackageInterface[]
     */
    public function getCanonicalPackages()
    {
        $packages = $this->getPackages();

        // get at most one package of each name, preferring non-aliased ones
        $packagesByName = [];
        foreach ($packages as $package) {
            if (!isset($packagesByName[$package->getName()]) || $packagesByName[$package->getName()] instanceof AliasPackage) {
                $packagesByName[$package->getName()] = $package;
            }
        }

        $canonicalPackages = [];

        // unfold aliased packages
        foreach ($packagesByName as $package) {
            while ($package instanceof AliasPackage) {
                $package = $package->getAliasOf();
            }

            $canonicalPackages[] = $package;
        }

        return $canonicalPackages;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

/**
 * Installable repository interface.
 *
 * Just used to tag installed repositories so the base classes can act differently on Alias packages
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface InstalledRepositoryInterface extends WritableRepositoryInterface
{
    /**
     * @return bool|null true if dev requirements were installed, false if --no-dev was used, null if yet unknown
     */
    public function getDevMode();

    /**
     * @return bool true if packages were never installed in this repository
     */
    public function isFresh();
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Package\RootPackageInterface;

/**
 * Root package repository.
 *
 * This is used for serving the RootPackage inside an in-memory InstalledRepository
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class RootPackageRepository extends ArrayRepository
{
    public function __construct(RootPackageInterface $package)
    {
        parent::__construct([$package]);
    }

    public function getRepoName(): string
    {
        return 'root package repo';
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Composer;
use Composer\Package\CompletePackage;
use Composer\Package\CompletePackageInterface;
use Composer\Package\Link;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\Pcre\Preg;
use Composer\Platform\HhvmDetector;
use Composer\Platform\Runtime;
use Composer\Platform\Version;
use Composer\Plugin\PluginInterface;
use Composer\Semver\Constraint\Constraint;
use Composer\Util\Silencer;
use Composer\XdebugHandler\XdebugHandler;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PlatformRepository extends ArrayRepository
{
    /**
     * @deprecated use PlatformRepository::isPlatformPackage(string $name) instead
     * @private
     */
    public const PLATFORM_PACKAGE_REGEX = '{^(?:php(?:-64bit|-ipv6|-zts|-debug)?|hhvm|(?:ext|lib)-[a-z0-9](?:[_.-]?[a-z0-9]+)*|composer(?:-(?:plugin|runtime)-api)?)$}iD';

    /**
     * @var ?string
     */
    private static $lastSeenPlatformPhp = null;

    /**
     * @var VersionParser
     */
    private $versionParser;

    /**
     * Defines overrides so that the platform can be mocked
     *
     * Keyed by package name (lowercased)
     *
     * @var array<string, array{name: string, version: string|false}>
     */
    private $overrides = [];

    /**
     * Stores which packages have been disabled and their actual version
     *
     * @var array<string, CompletePackageInterface>
     */
    private $disabledPackages = [];

    /** @var Runtime */
    private $runtime;
    /** @var HhvmDetector */
    private $hhvmDetector;

    /**
     * @param array<string, string|false> $overrides
     */
    public function __construct(array $packages = [], array $overrides = [], ?Runtime $runtime = null, ?HhvmDetector $hhvmDetector = null)
    {
        $this->runtime = $runtime ?: new Runtime();
        $this->hhvmDetector = $hhvmDetector ?: new HhvmDetector();
        foreach ($overrides as $name => $version) {
            if (!is_string($version) && false !== $version) { // @phpstan-ignore-line
                throw new \UnexpectedValueException('config.platform.'.$name.' should be a string or false, but got '.get_debug_type($version).' '.var_export($version, true));
            }
            if ($name === 'php' && $version === false) {
                throw new \UnexpectedValueException('config.platform.'.$name.' cannot be set to false as you cannot disable php entirely.');
            }
            $this->overrides[strtolower($name)] = ['name' => $name, 'version' => $version];
        }
        parent::__construct($packages);
    }

    public function getRepoName(): string
    {
        return 'platform repo';
    }

    public function isPlatformPackageDisabled(string $name): bool
    {
        return isset($this->disabledPackages[$name]);
    }

    /**
     * @return array<string, CompletePackageInterface>
     */
    public function getDisabledPackages(): array
    {
        return $this->disabledPackages;
    }

    protected function initialize(): void
    {
        parent::initialize();

        $libraries = [];

        $this->versionParser = new VersionParser();

        // Add each of the override versions as options.
        // Later we might even replace the extensions instead.
        foreach ($this->overrides as $override) {
            // Check that it's a platform package.
            if (!self::isPlatformPackage($override['name'])) {
                throw new \InvalidArgumentException('Invalid platform package name in config.platform: '.$override['name']);
            }

            if ($override['version'] !== false) {
                $this->addOverriddenPackage($override);
            }
        }

        $prettyVersion = Composer::getVersion();
        $version = $this->versionParser->normalize($prettyVersion);
        $composer = new CompletePackage('composer', $version, $prettyVersion);
        $composer->setDescription('Composer package');
        $this->addPackage($composer);

        $prettyVersion = PluginInterface::PLUGIN_API_VERSION;
        $version = $this->versionParser->normalize($prettyVersion);
        $composerPluginApi = new CompletePackage('composer-plugin-api', $version, $prettyVersion);
        $composerPluginApi->setDescription('The Composer Plugin API');
        $this->addPackage($composerPluginApi);

        $prettyVersion = Composer::RUNTIME_API_VERSION;
        $version = $this->versionParser->normalize($prettyVersion);
        $composerRuntimeApi = new CompletePackage('composer-runtime-api', $version, $prettyVersion);
        $composerRuntimeApi->setDescription('The Composer Runtime API');
        $this->addPackage($composerRuntimeApi);

        try {
            $prettyVersion = $this->runtime->getConstant('PHP_VERSION');
            $version = $this->versionParser->normalize($prettyVersion);
        } catch (\UnexpectedValueException $e) {
            $prettyVersion = Preg::replace('#^([^~+-]+).*$#', '$1', $this->runtime->getConstant('PHP_VERSION'));
            $version = $this->versionParser->normalize($prettyVersion);
        }

        $php = new CompletePackage('php', $version, $prettyVersion);
        $php->setDescription('The PHP interpreter');
        $this->addPackage($php);

        if ($this->runtime->getConstant('PHP_DEBUG')) {
            $phpdebug = new CompletePackage('php-debug', $version, $prettyVersion);
            $phpdebug->setDescription('The PHP interpreter, with debugging symbols');
            $this->addPackage($phpdebug);
        }

        if ($this->runtime->hasConstant('PHP_ZTS') && $this->runtime->getConstant('PHP_ZTS')) {
            $phpzts = new CompletePackage('php-zts', $version, $prettyVersion);
            $phpzts->setDescription('The PHP interpreter, with Zend Thread Safety');
            $this->addPackage($phpzts);
        }

        if ($this->runtime->getConstant('PHP_INT_SIZE') === 8) {
            $php64 = new CompletePackage('php-64bit', $version, $prettyVersion);
            $php64->setDescription('The PHP interpreter, 64bit');
            $this->addPackage($php64);
        }

        // The AF_INET6 constant is only defined if ext-sockets is available but
        // IPv6 support might still be available.
        if ($this->runtime->hasConstant('AF_INET6') || Silencer::call([$this->runtime, 'invoke'], 'inet_pton', ['::']) !== false) {
            $phpIpv6 = new CompletePackage('php-ipv6', $version, $prettyVersion);
            $phpIpv6->setDescription('The PHP interpreter, with IPv6 support');
            $this->addPackage($phpIpv6);
        }

        $loadedExtensions = $this->runtime->getExtensions();

        // Extensions scanning
        foreach ($loadedExtensions as $name) {
            if (in_array($name, ['standard', 'Core'])) {
                continue;
            }

            $this->addExtension($name, $this->runtime->getExtensionVersion($name));
        }

        // Check for Xdebug in a restarted process
        if (!in_array('xdebug', $loadedExtensions, true) && ($prettyVersion = XdebugHandler::getSkippedVersion())) {
            $this->addExtension('xdebug', $prettyVersion);
        }

        // Another quick loop, just for possible libraries
        // Doing it this way to know that functions or constants exist before
        // relying on them.
        foreach ($loadedExtensions as $name) {
            switch ($name) {
                case 'amqp':
                    $info = $this->runtime->getExtensionInfo($name);

                    // librabbitmq version => 0.9.0
                    if (Preg::isMatch('/^librabbitmq version => (?<version>.+)$/im', $info, $librabbitmqMatches)) {
                        $this->addLibrary($libraries, $name.'-librabbitmq', $librabbitmqMatches['version'], 'AMQP librabbitmq version');
                    }

                    // AMQP protocol version => 0-9-1
                    if (Preg::isMatchStrictGroups('/^AMQP protocol version => (?<version>.+)$/im', $info, $protocolMatches)) {
                        $this->addLibrary($libraries, $name.'-protocol', str_replace('-', '.', $protocolMatches['version']), 'AMQP protocol version');
                    }
                    break;

                case 'bz2':
                    $info = $this->runtime->getExtensionInfo($name);

                    // BZip2 Version => 1.0.6, 6-Sept-2010
                    if (Preg::isMatch('/^BZip2 Version => (?<version>.*),/im', $info, $matches)) {
                        $this->addLibrary($libraries, $name, $matches['version']);
                    }
                    break;

                case 'curl':
                    $curlVersion = $this->runtime->invoke('curl_version');
                    $this->addLibrary($libraries, $name, $curlVersion['version']);

                    $info = $this->runtime->getExtensionInfo($name);

                    // SSL Version => OpenSSL/1.0.1t
                    if (Preg::isMatchStrictGroups('{^SSL Version => (?<library>[^/]+)/(?<version>.+)$}im', $info, $sslMatches)) {
                        $library = strtolower($sslMatches['library']);
                        if ($library === 'openssl') {
                            $parsedVersion = Version::parseOpenssl($sslMatches['version'], $isFips);
                            $this->addLibrary($libraries, $name.'-openssl'.($isFips ? '-fips' : ''), $parsedVersion, 'curl OpenSSL version ('.$parsedVersion.')', [], $isFips ? ['curl-openssl'] : []);
                        } else {
                            if (str_starts_with($library, '(securetransport)') && Preg::isMatch('{^\(securetransport\) ([a-z0-9]+)}', $library, $securetransportMatches)) {
                                $shortlib = 'securetransport';
                                $sslLib = 'curl-'.$securetransportMatches[1];
                            } else {
                                $shortlib = $library;
                                $sslLib = 'curl-openssl';
                            }
                            $this->addLibrary($libraries, $name.'-'.$shortlib, $sslMatches['version'], 'curl '.$library.' version ('.$sslMatches['version'].')', [$sslLib]);
                        }
                    }

                    // libSSH Version => libssh2/1.4.3
                    if (Preg::isMatchStrictGroups('{^libSSH Version => (?<library>[^/]+)/(?<version>.+?)(?:/.*)?$}im', $info, $sshMatches)) {
                        $this->addLibrary($libraries, $name.'-'.strtolower($sshMatches['library']), $sshMatches['version'], 'curl '.$sshMatches['library'].' version');
                    }

                    // ZLib Version => 1.2.8
                    if (Preg::isMatchStrictGroups('{^ZLib Version => (?<version>.+)$}im', $info, $zlibMatches)) {
                        $this->addLibrary($libraries, $name.'-zlib', $zlibMatches['version'], 'curl zlib version');
                    }
                    break;

                case 'date':
                    $info = $this->runtime->getExtensionInfo($name);

                    // timelib version => 2018.03
                    if (Preg::isMatchStrictGroups('/^timelib version => (?<version>.+)$/im', $info, $timelibMatches)) {
                        $this->addLibrary($libraries, $name.'-timelib', $timelibMatches['version'], 'date timelib version');
                    }

                    // Timezone Database => internal
                    if (Preg::isMatchStrictGroups('/^Timezone Database => (?<source>internal|external)$/im', $info, $zoneinfoSourceMatches)) {
                        $external = $zoneinfoSourceMatches['source'] === 'external';
                        if (Preg::isMatchStrictGroups('/^"Olson" Timezone Database Version => (?<version>.+?)(?:\.system)?$/im', $info, $zoneinfoMatches)) {
                            // If the timezonedb is provided by ext/timezonedb, register that version as a replacement
                            if ($external && in_array('timezonedb', $loadedExtensions, true)) {
                                $this->addLibrary($libraries, 'timezonedb-zoneinfo', $zoneinfoMatches['version'], 'zoneinfo ("Olson") database for date (replaced by timezonedb)', [$name.'-zoneinfo']);
                            } else {
                                $this->addLibrary($libraries, $name.'-zoneinfo', $zoneinfoMatches['version'], 'zoneinfo ("Olson") database for date');
                            }
                        }
                    }
                    break;

                case 'fileinfo':
                    $info = $this->runtime->getExtensionInfo($name);

                    // libmagic => 537
                    if (Preg::isMatch('/^libmagic => (?<version>.+)$/im', $info, $magicMatches)) {
                        $this->addLibrary($libraries, $name.'-libmagic', $magicMatches['version'], 'fileinfo libmagic version');
                    }
                    break;

                case 'gd':
                    $this->addLibrary($libraries, $name, $this->runtime->getConstant('GD_VERSION'));

                    $info = $this->runtime->getExtensionInfo($name);

                    if (Preg::isMatchStrictGroups('/^libJPEG Version => (?<version>.+?)(?: compatible)?$/im', $info, $libjpegMatches)) {
                        $this->addLibrary($libraries, $name.'-libjpeg', Version::parseLibjpeg($libjpegMatches['version']), 'libjpeg version for gd');
                    }

                    if (Preg::isMatchStrictGroups('/^libPNG Version => (?<version>.+)$/im', $info, $libpngMatches)) {
                        $this->addLibrary($libraries, $name.'-libpng', $libpngMatches['version'], 'libpng version for gd');
                    }

                    if (Preg::isMatchStrictGroups('/^FreeType Version => (?<version>.+)$/im', $info, $freetypeMatches)) {
                        $this->addLibrary($libraries, $name.'-freetype', $freetypeMatches['version'], 'freetype version for gd');
                    }

                    if (Preg::isMatchStrictGroups('/^libXpm Version => (?<versionId>\d+)$/im', $info, $libxpmMatches)) {
                        $this->addLibrary($libraries, $name.'-libxpm', Version::convertLibxpmVersionId((int) $libxpmMatches['versionId']), 'libxpm version for gd');
                    }

                    break;

                case 'gmp':
                    $this->addLibrary($libraries, $name, $this->runtime->getConstant('GMP_VERSION'));
                    break;

                case 'iconv':
                    $this->addLibrary($libraries, $name, $this->runtime->getConstant('ICONV_VERSION'));
                    break;

                case 'intl':
                    $info = $this->runtime->getExtensionInfo($name);

                    $description = 'The ICU unicode and globalization support library';
                    // Truthy check is for testing only so we can make the condition fail
                    if ($this->runtime->hasConstant('INTL_ICU_VERSION')) {
                        $this->addLibrary($libraries, 'icu', $this->runtime->getConstant('INTL_ICU_VERSION'), $description);
                    } elseif (Preg::isMatch('/^ICU version => (?<version>.+)$/im', $info, $matches)) {
                        $this->addLibrary($libraries, 'icu', $matches['version'], $description);
                    }

                    // ICU TZData version => 2019c
                    if (Preg::isMatchStrictGroups('/^ICU TZData version => (?<version>.*)$/im', $info, $zoneinfoMatches) && null !== ($version = Version::parseZoneinfoVersion($zoneinfoMatches['version']))) {
                        $this->addLibrary($libraries, 'icu-zoneinfo', $version, 'zoneinfo ("Olson") database for icu');
                    }

                    // Add a separate version for the CLDR library version
                    if ($this->runtime->hasClass('ResourceBundle')) {
                        $resourceBundle = $this->runtime->invoke(['ResourceBundle', 'create'], ['root', 'ICUDATA', false]);
                        if ($resourceBundle !== null) {
                            $this->addLibrary($libraries, 'icu-cldr', $resourceBundle->get('Version'), 'ICU CLDR project version');
                        }
                    }

                    if ($this->runtime->hasClass('IntlChar')) {
                        $this->addLibrary($libraries, 'icu-unicode', implode('.', array_slice($this->runtime->invoke(['IntlChar', 'getUnicodeVersion']), 0, 3)), 'ICU unicode version');
                    }
                    break;

                case 'imagick':
                    // @phpstan-ignore staticMethod.dynamicCall (called like this for mockability)
                    $imageMagickVersion = $this->runtime->construct('Imagick')->getVersion();
                    // 6.x: ImageMagick 6.2.9 08/24/06 Q16 http://www.imagemagick.org
                    // 7.x: ImageMagick 7.0.8-34 Q16 x86_64 2019-03-23 https://imagemagick.org
                    if (Preg::isMatch('/^ImageMagick (?<version>[\d.]+)(?:-(?<patch>\d+))?/', $imageMagickVersion['versionString'], $matches)) {
                        $version = $matches['version'];
                        if (isset($matches['patch'])) {
                            $version .= '.'.$matches['patch'];
                        }

                        $this->addLibrary($libraries, $name.'-imagemagick', $version, null, ['imagick']);
                    }
                    break;

                case 'ldap':
                    $info = $this->runtime->getExtensionInfo($name);

                    if (Preg::isMatchStrictGroups('/^Vendor Version => (?<versionId>\d+)$/im', $info, $matches) && Preg::isMatchStrictGroups('/^Vendor Name => (?<vendor>.+)$/im', $info, $vendorMatches)) {
                        $this->addLibrary($libraries, $name.'-'.strtolower($vendorMatches['vendor']), Version::convertOpenldapVersionId((int) $matches['versionId']), $vendorMatches['vendor'].' version of ldap');
                    }
                    break;

                case 'libxml':
                    // ext/dom, ext/simplexml, ext/xmlreader and ext/xmlwriter use the same libxml as the ext/libxml
                    $libxmlProvides = array_map(static function ($extension): string {
                        return $extension . '-libxml';
                    }, array_intersect($loadedExtensions, ['dom', 'simplexml', 'xml', 'xmlreader', 'xmlwriter']));
                    $this->addLibrary($libraries, $name, $this->runtime->getConstant('LIBXML_DOTTED_VERSION'), 'libxml library version', [], $libxmlProvides);

                    break;

                case 'mbstring':
                    $info = $this->runtime->getExtensionInfo($name);

                    // libmbfl version => 1.3.2
                    if (Preg::isMatch('/^libmbfl version => (?<version>.+)$/im', $info, $libmbflMatches)) {
                        $this->addLibrary($libraries, $name.'-libmbfl', $libmbflMatches['version'], 'mbstring libmbfl version');
                    }

                    if ($this->runtime->hasConstant('MB_ONIGURUMA_VERSION')) {
                        $this->addLibrary($libraries, $name.'-oniguruma', $this->runtime->getConstant('MB_ONIGURUMA_VERSION'), 'mbstring oniguruma version');

                    // Multibyte regex (oniguruma) version => 5.9.5
                    // oniguruma version => 6.9.0
                    } elseif (Preg::isMatch('/^(?:oniguruma|Multibyte regex \(oniguruma\)) version => (?<version>.+)$/im', $info, $onigurumaMatches)) {
                        $this->addLibrary($libraries, $name.'-oniguruma', $onigurumaMatches['version'], 'mbstring oniguruma version');
                    }

                    break;

                case 'memcached':
                    $info = $this->runtime->getExtensionInfo($name);

                    // libmemcached version => 1.0.18
                    if (Preg::isMatch('/^libmemcached version => (?<version>.+)$/im', $info, $matches)) {
                        $this->addLibrary($libraries, $name.'-libmemcached', $matches['version'], 'libmemcached version');
                    }
                    break;

                case 'openssl':
                    // OpenSSL 1.1.1g  21 Apr 2020
                    if (Preg::isMatchStrictGroups('{^(?:OpenSSL|LibreSSL)?\s*(?<version>\S+)}i', $this->runtime->getConstant('OPENSSL_VERSION_TEXT'), $matches)) {
                        $parsedVersion = Version::parseOpenssl($matches['version'], $isFips);
                        $this->addLibrary($libraries, $name.($isFips ? '-fips' : ''), $parsedVersion, $this->runtime->getConstant('OPENSSL_VERSION_TEXT'), [], $isFips ? [$name] : []);
                    }
                    break;

                case 'pcre':
                    $this->addLibrary($libraries, $name, Preg::replace('{^(\S+).*}', '$1', $this->runtime->getConstant('PCRE_VERSION')));

                    $info = $this->runtime->getExtensionInfo($name);

                    // PCRE Unicode Version => 12.1.0
                    if (Preg::isMatchStrictGroups('/^PCRE Unicode Version => (?<version>.+)$/im', $info, $pcreUnicodeMatches)) {
                        $this->addLibrary($libraries, $name.'-unicode', $pcreUnicodeMatches['version'], 'PCRE Unicode version support');
                    }

                    break;

                case 'mysqlnd':
                case 'pdo_mysql':
                    $info = $this->runtime->getExtensionInfo($name);

                    if (Preg::isMatchStrictGroups('/^(?:Client API version|Version) => mysqlnd (?<version>.+?) /mi', $info, $matches)) {
                        $this->addLibrary($libraries, $name.'-mysqlnd', $matches['version'], 'mysqlnd library version for '.$name);
                    }
                    break;

                case 'mongodb':
                    $info = $this->runtime->getExtensionInfo($name);

                    if (Preg::isMatchStrictGroups('/^libmongoc bundled version => (?<version>.+)$/im', $info, $libmongocMatches)) {
                        $this->addLibrary($libraries, $name.'-libmongoc', $libmongocMatches['version'], 'libmongoc version of mongodb');
                    }

                    if (Preg::isMatchStrictGroups('/^libbson bundled version => (?<version>.+)$/im', $info, $libbsonMatches)) {
                        $this->addLibrary($libraries, $name.'-libbson', $libbsonMatches['version'], 'libbson version of mongodb');
                    }
                    break;

                case 'pgsql':
                    if ($this->runtime->hasConstant('PGSQL_LIBPQ_VERSION')) {
                        $this->addLibrary($libraries, 'pgsql-libpq', $this->runtime->getConstant('PGSQL_LIBPQ_VERSION'), 'libpq for pgsql');
                        break;
                    }
                // intentional fall-through to next case...

                case 'pdo_pgsql':
                    $info = $this->runtime->getExtensionInfo($name);

                    if (Preg::isMatch('/^PostgreSQL\(libpq\) Version => (?<version>.*)$/im', $info, $matches)) {
                        $this->addLibrary($libraries, $name.'-libpq', $matches['version'], 'libpq for '.$name);
                    }
                    break;

                case 'pq':
                    $info = $this->runtime->getExtensionInfo($name);

                    // Used Library => Compiled => Linked
                    // libpq => 14.3 (Ubuntu 14.3-1.pgdg22.04+1) => 15.0.2
                    if (Preg::isMatch('/^libpq => (?<compiled>.+) => (?<linked>.+)$/im', $info, $matches)) {
                        $this->addLibrary($libraries, $name.'-libpq', $matches['linked'], 'libpq for '.$name);
                    }
                    break;

                case 'rdkafka':
                    if ($this->runtime->hasConstant('RD_KAFKA_VERSION')) {
                        /**
                         * Interpreted as hex \c MM.mm.rr.xx:
                         *  - MM = Major
                         *  - mm = minor
                         *  - rr = revision
                         *  - xx = pre-release id (0xff is the final release)
                         *
                         * pre-release ID in practice is always 0xff even for RCs etc, so we ignore it
                         */
                        $libRdKafkaVersionInt = $this->runtime->getConstant('RD_KAFKA_VERSION');
                        $this->addLibrary($libraries, $name.'-librdkafka', sprintf('%d.%d.%d', ($libRdKafkaVersionInt & 0x7F000000) >> 24, ($libRdKafkaVersionInt & 0x00FF0000) >> 16, ($libRdKafkaVersionInt & 0x0000FF00) >> 8), 'librdkafka for '.$name);
                    }
                    break;

                case 'libsodium':
                case 'sodium':
                    if ($this->runtime->hasConstant('SODIUM_LIBRARY_VERSION')) {
                        $this->addLibrary($libraries, 'libsodium', $this->runtime->getConstant('SODIUM_LIBRARY_VERSION'));
                        $this->addLibrary($libraries, 'libsodium', $this->runtime->getConstant('SODIUM_LIBRARY_VERSION'));
                    }
                    break;

                case 'sqlite3':
                case 'pdo_sqlite':
                    $info = $this->runtime->getExtensionInfo($name);

                    if (Preg::isMatch('/^SQLite Library => (?<version>.+)$/im', $info, $matches)) {
                        $this->addLibrary($libraries, $name.'-sqlite', $matches['version']);
                    }
                    break;

                case 'ssh2':
                    $info = $this->runtime->getExtensionInfo($name);

                    if (Preg::isMatch('/^libssh2 version => (?<version>.+)$/im', $info, $matches)) {
                        $this->addLibrary($libraries, $name.'-libssh2', $matches['version']);
                    }
                    break;

                case 'xsl':
                    $this->addLibrary($libraries, 'libxslt', $this->runtime->getConstant('LIBXSLT_DOTTED_VERSION'), null, ['xsl']);

                    $info = $this->runtime->getExtensionInfo('xsl');
                    if (Preg::isMatch('/^libxslt compiled against libxml Version => (?<version>.+)$/im', $info, $matches)) {
                        $this->addLibrary($libraries, 'libxslt-libxml', $matches['version'], 'libxml version libxslt is compiled against');
                    }
                    break;

                case 'yaml':
                    $info = $this->runtime->getExtensionInfo('yaml');

                    if (Preg::isMatch('/^LibYAML Version => (?<version>.+)$/im', $info, $matches)) {
                        $this->addLibrary($libraries, $name.'-libyaml', $matches['version'], 'libyaml version of yaml');
                    }
                    break;

                case 'zip':
                    if ($this->runtime->hasConstant('LIBZIP_VERSION', 'ZipArchive')) {
                        $this->addLibrary($libraries, $name.'-libzip', $this->runtime->getConstant('LIBZIP_VERSION', 'ZipArchive'), null, ['zip']);
                    }
                    break;

                case 'zlib':
                    if ($this->runtime->hasConstant('ZLIB_VERSION')) {
                        $this->addLibrary($libraries, $name, $this->runtime->getConstant('ZLIB_VERSION'));

                    // Linked Version => 1.2.8
                    } elseif (Preg::isMatch('/^Linked Version => (?<version>.+)$/im', $this->runtime->getExtensionInfo($name), $matches)) {
                        $this->addLibrary($libraries, $name, $matches['version']);
                    }
                    break;

                default:
                    break;
            }
        }

        $hhvmVersion = $this->hhvmDetector->getVersion();
        if ($hhvmVersion) {
            try {
                $prettyVersion = $hhvmVersion;
                $version = $this->versionParser->normalize($prettyVersion);
            } catch (\UnexpectedValueException $e) {
                $prettyVersion = Preg::replace('#^([^~+-]+).*$#', '$1', $hhvmVersion);
                $version = $this->versionParser->normalize($prettyVersion);
            }

            $hhvm = new CompletePackage('hhvm', $version, $prettyVersion);
            $hhvm->setDescription('The HHVM Runtime (64bit)');
            $this->addPackage($hhvm);
        }
    }

    /**
     * @inheritDoc
     */
    public function addPackage(PackageInterface $package): void
    {
        if (!$package instanceof CompletePackage) {
            throw new \UnexpectedValueException('Expected CompletePackage but got '.get_class($package));
        }

        // Skip if overridden
        if (isset($this->overrides[$package->getName()])) {
            if ($this->overrides[$package->getName()]['version'] === false) {
                $this->addDisabledPackage($package);

                return;
            }

            $overrider = $this->findPackage($package->getName(), '*');
            if ($package->getVersion() === $overrider->getVersion()) {
                $actualText = 'same as actual';
            } else {
                $actualText = 'actual: '.$package->getPrettyVersion();
            }
            if ($overrider instanceof CompletePackageInterface) {
                $overrider->setDescription($overrider->getDescription().', '.$actualText);
            }

            return;
        }

        // Skip if PHP is overridden and we are adding a php-* package
        if (isset($this->overrides['php']) && 0 === strpos($package->getName(), 'php-')) {
            $overrider = $this->addOverriddenPackage($this->overrides['php'], $package->getPrettyName());
            if ($package->getVersion() === $overrider->getVersion()) {
                $actualText = 'same as actual';
            } else {
                $actualText = 'actual: '.$package->getPrettyVersion();
            }
            $overrider->setDescription($overrider->getDescription().', '.$actualText);

            return;
        }

        parent::addPackage($package);
    }

    /**
     * @param array{version: string, name: string} $override
     */
    private function addOverriddenPackage(array $override, ?string $name = null): CompletePackage
    {
        $version = $this->versionParser->normalize($override['version']);
        $package = new CompletePackage($name ?: $override['name'], $version, $override['version']);
        $package->setDescription('Package overridden via config.platform');
        $package->setExtra(['config.platform' => true]);
        parent::addPackage($package);

        if ($package->getName() === 'php') {
            self::$lastSeenPlatformPhp = implode('.', array_slice(explode('.', $package->getVersion()), 0, 3));
        }

        return $package;
    }

    private function addDisabledPackage(CompletePackage $package): void
    {
        $package->setDescription($package->getDescription().'. <warning>Package disabled via config.platform</warning>');
        $package->setExtra(['config.platform' => true]);

        $this->disabledPackages[$package->getName()] = $package;
    }

    /**
     * Parses the version and adds a new package to the repository
     */
    private function addExtension(string $name, string $prettyVersion): void
    {
        $extraDescription = null;

        try {
            $version = $this->versionParser->normalize($prettyVersion);
        } catch (\UnexpectedValueException $e) {
            $extraDescription = ' (actual version: '.$prettyVersion.')';
            if (Preg::isMatchStrictGroups('{^(\d+\.\d+\.\d+(?:\.\d+)?)}', $prettyVersion, $match)) {
                $prettyVersion = $match[1];
            } else {
                $prettyVersion = '0';
            }
            $version = $this->versionParser->normalize($prettyVersion);
        }

        $packageName = $this->buildPackageName($name);
        $ext = new CompletePackage($packageName, $version, $prettyVersion);
        $ext->setDescription('The '.$name.' PHP extension'.$extraDescription);
        $ext->setType('php-ext');

        if ($name === 'uuid') {
            $ext->setReplaces([
                'lib-uuid' => new Link('ext-uuid', 'lib-uuid', new Constraint('=', $version), Link::TYPE_REPLACE, $ext->getPrettyVersion()),
            ]);
        }

        $this->addPackage($ext);
    }

    private function buildPackageName(string $name): string
    {
        return 'ext-' . str_replace(' ', '-', strtolower($name));
    }

    /**
     * @param array<string, bool> $libraries
     * @param array<string> $replaces
     * @param array<string> $provides
     */
    private function addLibrary(array &$libraries, string $name, ?string $prettyVersion, ?string $description = null, array $replaces = [], array $provides = []): void
    {
        if (null === $prettyVersion) {
            return;
        }
        try {
            $version = $this->versionParser->normalize($prettyVersion);
        } catch (\UnexpectedValueException $e) {
            return;
        }

        // avoid adding the same lib twice even if two conflicting extensions provide the same lib
        // see https://github.com/composer/composer/issues/12082
        if (isset($libraries['lib-'.$name])) {
            return;
        }
        $libraries['lib-'.$name] = true;

        if ($description === null) {
            $description = 'The '.$name.' library';
        }

        $lib = new CompletePackage('lib-'.$name, $version, $prettyVersion);
        $lib->setDescription($description);

        $replaceLinks = [];
        foreach ($replaces as $replace) {
            $replace = strtolower($replace);
            $replaceLinks[$replace] = new Link('lib-'.$name, 'lib-'.$replace, new Constraint('=', $version), Link::TYPE_REPLACE, $lib->getPrettyVersion());
        }
        $provideLinks = [];
        foreach ($provides as $provide) {
            $provide = strtolower($provide);
            $provideLinks[$provide] = new Link('lib-'.$name, 'lib-'.$provide, new Constraint('=', $version), Link::TYPE_PROVIDE, $lib->getPrettyVersion());
        }
        $lib->setReplaces($replaceLinks);
        $lib->setProvides($provideLinks);

        $this->addPackage($lib);
    }

    /**
     * Check if a package name is a platform package.
     */
    public static function isPlatformPackage(string $name): bool
    {
        static $cache = [];

        if (isset($cache[$name])) {
            return $cache[$name];
        }

        return $cache[$name] = Preg::isMatch(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name);
    }

    /**
     * Returns the last seen config.platform.php version if defined
     *
     * This is a best effort attempt for internal purposes, retrieve the real
     * packages from a PlatformRepository instance if you need a version guaranteed to
     * be correct.
     *
     * @internal
     */
    public static function getPlatformPhpVersion(): ?string
    {
        return self::$lastSeenPlatformPhp;
    }

    public function search(string $query, int $mode = 0, ?string $type = null): array
    {
        // suppress vendor search as there are no vendors to match in platform packages
        if ($mode === self::SEARCH_VENDOR) {
            return [];
        }

        return parent::search($query, $mode, $type);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

interface VersionCacheInterface
{
    /**
     * @return mixed[]|null|false Package version data if found, false to indicate the identifier is known but has no package, null for an unknown identifier
     */
    public function getVersionPackage(string $version, string $identifier);
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Installer\InstallationManager;

/**
 * Writable array repository.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class WritableArrayRepository extends ArrayRepository implements WritableRepositoryInterface
{
    use CanonicalPackagesTrait;

    /**
     * @var string[]
     */
    protected $devPackageNames = [];

    /** @var bool|null */
    private $devMode = null;

    /**
     * @return bool|null true if dev requirements were installed, false if --no-dev was used, null if yet unknown
     */
    public function getDevMode()
    {
        return $this->devMode;
    }

    /**
     * @inheritDoc
     */
    public function setDevPackageNames(array $devPackageNames)
    {
        $this->devPackageNames = $devPackageNames;
    }

    /**
     * @inheritDoc
     */
    public function getDevPackageNames()
    {
        return $this->devPackageNames;
    }

    /**
     * @inheritDoc
     */
    public function write(bool $devMode, InstallationManager $installationManager)
    {
        $this->devMode = $devMode;
    }

    /**
     * @inheritDoc
     */
    public function reload()
    {
        $this->devMode = null;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\IO\IOInterface;
use Composer\Config;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Package\PackageInterface;
use Composer\Util\HttpDownloader;
use Composer\Util\ProcessExecutor;

/**
 * Repositories manager.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 */
class RepositoryManager
{
    /** @var InstalledRepositoryInterface */
    private $localRepository;
    /** @var list<RepositoryInterface> */
    private $repositories = [];
    /** @var array<string, class-string<RepositoryInterface>> */
    private $repositoryClasses = [];
    /** @var IOInterface */
    private $io;
    /** @var Config */
    private $config;
    /** @var HttpDownloader */
    private $httpDownloader;
    /** @var ?EventDispatcher */
    private $eventDispatcher;
    /** @var ProcessExecutor */
    private $process;

    public function __construct(IOInterface $io, Config $config, HttpDownloader $httpDownloader, ?EventDispatcher $eventDispatcher = null, ?ProcessExecutor $process = null)
    {
        $this->io = $io;
        $this->config = $config;
        $this->httpDownloader = $httpDownloader;
        $this->eventDispatcher = $eventDispatcher;
        $this->process = $process ?? new ProcessExecutor($io);
    }

    /**
     * Searches for a package by its name and version in managed repositories.
     *
     * @param string                                                 $name       package name
     * @param string|\Composer\Semver\Constraint\ConstraintInterface $constraint package version or version constraint to match against
     */
    public function findPackage(string $name, $constraint): ?PackageInterface
    {
        foreach ($this->repositories as $repository) {
            /** @var RepositoryInterface $repository */
            if ($package = $repository->findPackage($name, $constraint)) {
                return $package;
            }
        }

        return null;
    }

    /**
     * Searches for all packages matching a name and optionally a version in managed repositories.
     *
     * @param string                                                 $name       package name
     * @param string|\Composer\Semver\Constraint\ConstraintInterface $constraint package version or version constraint to match against
     *
     * @return PackageInterface[]
     */
    public function findPackages(string $name, $constraint): array
    {
        $packages = [];

        foreach ($this->getRepositories() as $repository) {
            $packages = array_merge($packages, $repository->findPackages($name, $constraint));
        }

        return $packages;
    }

    /**
     * Adds repository
     *
     * @param RepositoryInterface $repository repository instance
     */
    public function addRepository(RepositoryInterface $repository): void
    {
        $this->repositories[] = $repository;
    }

    /**
     * Adds a repository to the beginning of the chain
     *
     * This is useful when injecting additional repositories that should trump Packagist, e.g. from a plugin.
     *
     * @param RepositoryInterface $repository repository instance
     */
    public function prependRepository(RepositoryInterface $repository): void
    {
        array_unshift($this->repositories, $repository);
    }

    /**
     * Returns a new repository for a specific installation type.
     *
     * @param  string                    $type   repository type
     * @param  array<string, mixed>      $config repository configuration
     * @param  string                    $name   repository name
     * @throws \InvalidArgumentException if repository for provided type is not registered
     */
    public function createRepository(string $type, array $config, ?string $name = null): RepositoryInterface
    {
        if (!isset($this->repositoryClasses[$type])) {
            throw new \InvalidArgumentException('Repository type is not registered: '.$type);
        }

        if (isset($config['packagist']) && false === $config['packagist']) {
            $this->io->writeError('<warning>Repository "'.$name.'" ('.json_encode($config).') has a packagist key which should be in its own repository definition</warning>');
        }

        $class = $this->repositoryClasses[$type];

        if (isset($config['only']) || isset($config['exclude']) || isset($config['canonical'])) {
            $filterConfig = $config;
            unset($config['only'], $config['exclude'], $config['canonical']);
        }

        $repository = new $class($config, $this->io, $this->config, $this->httpDownloader, $this->eventDispatcher, $this->process);

        if (isset($filterConfig)) {
            $repository = new FilterRepository($repository, $filterConfig);
        }

        return $repository;
    }

    /**
     * Stores repository class for a specific installation type.
     *
     * @param string $type  installation type
     * @param class-string<RepositoryInterface> $class class name of the repo implementation
     */
    public function setRepositoryClass(string $type, $class): void
    {
        $this->repositoryClasses[$type] = $class;
    }

    /**
     * Returns all repositories, except local one.
     *
     * @return RepositoryInterface[]
     */
    public function getRepositories(): array
    {
        return $this->repositories;
    }

    /**
     * Sets local repository for the project.
     *
     * @param InstalledRepositoryInterface $repository repository instance
     */
    public function setLocalRepository(InstalledRepositoryInterface $repository): void
    {
        $this->localRepository = $repository;
    }

    /**
     * Returns local repository for the project.
     */
    public function getLocalRepository(): InstalledRepositoryInterface
    {
        return $this->localRepository;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Package\PackageInterface;
use Composer\Installer\InstallationManager;

/**
 * Writable repository interface.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
interface WritableRepositoryInterface extends RepositoryInterface
{
    /**
     * Writes repository (f.e. to the disc).
     *
     * @param bool $devMode Whether dev requirements were included or not in this installation
     * @return void
     */
    public function write(bool $devMode, InstallationManager $installationManager);

    /**
     * Adds package to the repository.
     *
     * @param PackageInterface $package package instance
     * @return void
     */
    public function addPackage(PackageInterface $package);

    /**
     * Removes package from the repository.
     *
     * @param PackageInterface $package package instance
     * @return void
     */
    public function removePackage(PackageInterface $package);

    /**
     * Get unique packages (at most one package of each name), with aliases resolved and removed.
     *
     * @return PackageInterface[]
     */
    public function getCanonicalPackages();

    /**
     * Forces a reload of all packages.
     *
     * @return void
     */
    public function reload();

    /**
     * @param string[] $devPackageNames
     * @return void
     */
    public function setDevPackageNames(array $devPackageNames);

    /**
     * @return string[] Names of dependencies installed through require-dev
     */
    public function getDevPackageNames();
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Config;
use Composer\EventDispatcher\EventDispatcher;
use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Version\VersionGuesser;
use Composer\Package\Version\VersionParser;
use Composer\Pcre\Preg;
use Composer\Util\HttpDownloader;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Util\Filesystem;
use Composer\Util\Url;
use Composer\Util\Git as GitUtil;

/**
 * This repository allows installing local packages that are not necessarily under their own VCS.
 *
 * The local packages will be symlinked when possible, else they will be copied.
 *
 * @code
 * "require": {
 *     "<vendor>/<local-package>": "*"
 * },
 * "repositories": [
 *     {
 *         "type": "path",
 *         "url": "../../relative/path/to/package/"
 *     },
 *     {
 *         "type": "path",
 *         "url": "/absolute/path/to/package/"
 *     },
 *     {
 *         "type": "path",
 *         "url": "/absolute/path/to/several/packages/*"
 *     },
 *     {
 *         "type": "path",
 *         "url": "../../relative/path/to/package/",
 *         "options": {
 *             "symlink": false
 *         }
 *     },
 *     {
 *         "type": "path",
 *         "url": "../../relative/path/to/package/",
 *         "options": {
 *             "reference": "none"
 *         }
 *     },
 * ]
 * @endcode
 *
 * @author Samuel Roze <samuel.roze@gmail.com>
 * @author Johann Reinke <johann.reinke@gmail.com>
 */
class PathRepository extends ArrayRepository implements ConfigurableRepositoryInterface
{
    /**
     * @var ArrayLoader
     */
    private $loader;

    /**
     * @var VersionGuesser
     */
    private $versionGuesser;

    /**
     * @var string
     */
    private $url;

    /**
     * @var mixed[]
     * @phpstan-var array{url: string, options?: array{symlink?: bool, reference?: string, relative?: bool, versions?: array<string, string>}}
     */
    private $repoConfig;

    /**
     * @var ProcessExecutor
     */
    private $process;

    /**
     * @var array{symlink?: bool, reference?: string, relative?: bool, versions?: array<string, string>}
     */
    private $options;

    /**
     * Initializes path repository.
     *
     * @param array{url?: string, options?: array{symlink?: bool, reference?: string, relative?: bool, versions?: array<string, string>}} $repoConfig
     */
    public function __construct(array $repoConfig, IOInterface $io, Config $config, ?HttpDownloader $httpDownloader = null, ?EventDispatcher $dispatcher = null, ?ProcessExecutor $process = null)
    {
        if (!isset($repoConfig['url'])) {
            throw new \RuntimeException('You must specify the `url` configuration for the path repository');
        }

        $this->loader = new ArrayLoader(null, true);
        $this->url = Platform::expandPath($repoConfig['url']);
        $this->process = $process ?? new ProcessExecutor($io);
        $this->versionGuesser = new VersionGuesser($config, $this->process, new VersionParser(), $io);
        $this->repoConfig = $repoConfig;
        $this->options = $repoConfig['options'] ?? [];
        if (!isset($this->options['relative'])) {
            $filesystem = new Filesystem();
            $this->options['relative'] = !$filesystem->isAbsolutePath($this->url);
        }

        parent::__construct();
    }

    public function getRepoName(): string
    {
        return 'path repo ('.Url::sanitize($this->repoConfig['url']).')';
    }

    public function getRepoConfig(): array
    {
        return $this->repoConfig;
    }

    /**
     * Initializes path repository.
     *
     * This method will basically read the folder and add the found package.
     */
    protected function initialize(): void
    {
        parent::initialize();

        $urlMatches = $this->getUrlMatches();

        if (empty($urlMatches)) {
            if (Preg::isMatch('{[*{}]}', $this->url)) {
                $url = $this->url;
                while (Preg::isMatch('{[*{}]}', $url)) {
                    $url = dirname($url);
                }
                // the parent directory before any wildcard exists, so we assume it is correctly configured but simply empty
                if (is_dir($url)) {
                    return;
                }
            }

            throw new \RuntimeException('The `url` supplied for the path (' . $this->url . ') repository does not exist');
        }

        foreach ($urlMatches as $url) {
            $path = realpath($url) . DIRECTORY_SEPARATOR;
            $composerFilePath = $path.'composer.json';

            if (!file_exists($composerFilePath)) {
                continue;
            }

            $json = file_get_contents($composerFilePath);
            $package = JsonFile::parseJson($json, $composerFilePath);
            $package['dist'] = [
                'type' => 'path',
                'url' => $url,
            ];
            $reference = $this->options['reference'] ?? 'auto';
            if ('none' === $reference) {
                $package['dist']['reference'] = null;
            } elseif ('config' === $reference || 'auto' === $reference) {
                $package['dist']['reference'] = hash('sha1', $json . serialize($this->options));
            }

            // copy symlink/relative options to transport options
            $package['transport-options'] = array_intersect_key($this->options, ['symlink' => true, 'relative' => true]);
            // use the version provided as option if available
            if (isset($package['name'], $this->options['versions'][$package['name']])) {
                $package['version'] = $this->options['versions'][$package['name']];
            }

            // carry over the root package version if this path repo is in the same git repository as root package
            if (!isset($package['version']) && ($rootVersion = Platform::getEnv('COMPOSER_ROOT_VERSION'))) {
                if (
                    0 === $this->process->execute(['git', 'rev-parse', 'HEAD'], $ref1, $path)
                    && 0 === $this->process->execute(['git', 'rev-parse', 'HEAD'], $ref2)
                    && $ref1 === $ref2
                ) {
                    $package['version'] = $this->versionGuesser->getRootVersionFromEnv();
                }
            }

            $output = '';
            $command = GitUtil::buildRevListCommand($this->process, array_merge(['-n1', '--format=%H', 'HEAD'], GitUtil::getNoShowSignatureFlags($this->process)));
            if ('auto' === $reference && is_dir($path . DIRECTORY_SEPARATOR . '.git') && 0 === $this->process->execute($command, $output, $path)) {
                $package['dist']['reference'] = trim(GitUtil::parseRevListOutput($output, $this->process));
            }

            if (!isset($package['version'])) {
                $versionData = $this->versionGuesser->guessVersion($package, $path);
                if (is_array($versionData) && $versionData['pretty_version']) {
                    // if there is a feature branch detected, we add a second packages with the feature branch version
                    if (!empty($versionData['feature_pretty_version'])) {
                        $package['version'] = $versionData['feature_pretty_version'];
                        $this->addPackage($this->loader->load($package));
                    }

                    $package['version'] = $versionData['pretty_version'];
                } else {
                    $package['version'] = 'dev-main';
                }
            }

            try {
                $this->addPackage($this->loader->load($package));
            } catch (\Exception $e) {
                throw new \RuntimeException('Failed loading the package in '.$composerFilePath, 0, $e);
            }
        }
    }

    /**
     * Get a list of all (possibly relative) path names matching given url (supports globbing).
     *
     * @return string[]
     */
    private function getUrlMatches(): array
    {
        $flags = GLOB_MARK | GLOB_ONLYDIR;

        if (defined('GLOB_BRACE')) {
            $flags |= GLOB_BRACE;
        } elseif (strpos($this->url, '{') !== false || strpos($this->url, '}') !== false) {
            throw new \RuntimeException('The operating system does not support GLOB_BRACE which is required for the url '. $this->url);
        }

        // Ensure environment-specific path separators are normalized to URL separators
        return array_map(static function ($val): string {
            return rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $val), '/');
        }, glob($this->url, $flags));
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository\Vcs;

use Composer\Pcre\Preg;
use Composer\Util\ProcessExecutor;
use Composer\Util\Filesystem;
use Composer\Util\Url;
use Composer\Util\Git as GitUtil;
use Composer\IO\IOInterface;
use Composer\Cache;
use Composer\Config;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class GitDriver extends VcsDriver
{
    /** @var array<int|string, string> Map of tag name (can be turned to an int by php if it is a numeric name) to identifier */
    protected $tags;
    /** @var array<int|string, string> Map of branch name (can be turned to an int by php if it is a numeric name) to identifier */
    protected $branches;
    /** @var string */
    protected $rootIdentifier;
    /** @var string */
    protected $repoDir;

    /**
     * @inheritDoc
     */
    public function initialize(): void
    {
        if (Filesystem::isLocalPath($this->url)) {
            $this->url = Preg::replace('{[\\/]\.git/?$}', '', $this->url);
            if (!is_dir($this->url)) {
                throw new \RuntimeException('Failed to read package information from '.$this->url.' as the path does not exist');
            }
            $this->repoDir = $this->url;
            $cacheUrl = realpath($this->url);
        } else {
            if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) {
                throw new \RuntimeException('GitDriver requires a usable cache directory, and it looks like you set it to be disabled');
            }

            $this->repoDir = $this->config->get('cache-vcs-dir') . '/' . Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($this->url)) . '/';

            GitUtil::cleanEnv($this->process);

            $fs = new Filesystem();
            $fs->ensureDirectoryExists(dirname($this->repoDir));

            if (!is_writable(dirname($this->repoDir))) {
                throw new \RuntimeException('Can not clone '.$this->url.' to access package information. The "'.dirname($this->repoDir).'" directory is not writable by the current user.');
            }

            if (Preg::isMatch('{^ssh://[^@]+@[^:]+:[^0-9]+}', $this->url)) {
                throw new \InvalidArgumentException('The source URL '.$this->url.' is invalid, ssh URLs should have a port number after ":".'."\n".'Use ssh://git@example.com:22/path or just git@example.com:path if you do not want to provide a password or custom port.');
            }

            $gitUtil = new GitUtil($this->io, $this->config, $this->process, $fs);
            if (!$gitUtil->syncMirror($this->url, $this->repoDir)) {
                if (!is_dir($this->repoDir)) {
                    throw new \RuntimeException('Failed to clone '.$this->url.' to read package information from it');
                }
                $this->io->writeError('<error>Failed to update '.$this->url.', package information from this repository may be outdated</error>');
            }

            $cacheUrl = $this->url;
        }

        $this->getTags();
        $this->getBranches();

        $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($cacheUrl)));
        $this->cache->setReadOnly($this->config->get('cache-read-only'));
    }

    /**
     * @inheritDoc
     */
    public function getRootIdentifier(): string
    {
        if (null === $this->rootIdentifier) {
            $this->rootIdentifier = 'master';

            $gitUtil = new GitUtil($this->io, $this->config, $this->process, new Filesystem());
            if (!Filesystem::isLocalPath($this->url)) {
                $defaultBranch = $gitUtil->getMirrorDefaultBranch($this->url, $this->repoDir, false);
                if ($defaultBranch !== null) {
                    return $this->rootIdentifier = $defaultBranch;
                }
            }

            // select currently checked out branch as default branch
            $this->process->execute(['git', 'branch', '--no-color'], $output, $this->repoDir);
            $branches = $this->process->splitLines($output);
            if (!in_array('* master', $branches)) {
                foreach ($branches as $branch) {
                    if ($branch && Preg::isMatchStrictGroups('{^\* +(\S+)}', $branch, $match)) {
                        $this->rootIdentifier = $match[1];
                        break;
                    }
                }
            }
        }

        return $this->rootIdentifier;
    }

    /**
     * @inheritDoc
     */
    public function getUrl(): string
    {
        return $this->url;
    }

    /**
     * @inheritDoc
     */
    public function getSource(string $identifier): array
    {
        return ['type' => 'git', 'url' => $this->getUrl(), 'reference' => $identifier];
    }

    /**
     * @inheritDoc
     */
    public function getDist(string $identifier): ?array
    {
        return null;
    }

    /**
     * @inheritDoc
     */
    public function getFileContent(string $file, string $identifier): ?string
    {
        if (isset($identifier[0]) && $identifier[0] === '-') {
            throw new \RuntimeException('Invalid git identifier detected. Identifier must not start with a -, given: ' . $identifier);
        }

        $this->process->execute(['git', 'show', $identifier.':'.$file], $content, $this->repoDir);

        if (trim($content) === '') {
            return null;
        }

        return $content;
    }

    /**
     * @inheritDoc
     */
    public function getChangeDate(string $identifier): ?\DateTimeImmutable
    {
        if (isset($identifier[0]) && $identifier[0] === '-') {
            throw new \RuntimeException('Invalid git identifier detected. Identifier must not start with a -, given: ' . $identifier);
        }

        $command = GitUtil::buildRevListCommand($this->process, ['-n1', '--format=%at', $identifier]);
        $this->process->execute($command, $output, $this->repoDir);

        return new \DateTimeImmutable('@'.trim(GitUtil::parseRevListOutput($output, $this->process)), new \DateTimeZone('UTC'));
    }

    /**
     * @inheritDoc
     */
    public function getTags(): array
    {
        if (null === $this->tags) {
            $this->tags = [];

            $this->process->execute(['git', 'show-ref', '--tags', '--dereference'], $output, $this->repoDir);
            foreach ($this->process->splitLines($output) as $tag) {
                if ($tag !== '' && Preg::isMatch('{^([a-f0-9]{40}) refs/tags/(\S+?)(\^\{\})?$}', $tag, $match)) {
                    $this->tags[$match[2]] = $match[1];
                }
            }
        }

        return $this->tags;
    }

    /**
     * @inheritDoc
     */
    public function getBranches(): array
    {
        if (null === $this->branches) {
            $branches = [];

            $this->process->execute(['git', 'branch', '--no-color', '--no-abbrev', '-v'], $output, $this->repoDir);
            foreach ($this->process->splitLines($output) as $branch) {
                if ($branch !== '' && !Preg::isMatch('{^ *[^/]+/HEAD }', $branch)) {
                    if (Preg::isMatchStrictGroups('{^(?:\* )? *(\S+) *([a-f0-9]+)(?: .*)?$}', $branch, $match) && $match[1][0] !== '-') {
                        $branches[$match[1]] = $match[2];
                    }
                }
            }

            $this->branches = $branches;
        }

        return $this->branches;
    }

    /**
     * @inheritDoc
     */
    public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool
    {
        if (Preg::isMatch('#(^git://|\.git/?$|git(?:olite)?@|//git\.|//github.com/)#i', $url)) {
            return true;
        }

        // local filesystem
        if (Filesystem::isLocalPath($url)) {
            $url = Filesystem::getPlatformPath($url);
            if (!is_dir($url)) {
                return false;
            }

            $process = new ProcessExecutor($io);
            // check whether there is a git repo in that path
            if ($process->execute(['git', 'tag'], $output, $url) === 0) {
                return true;
            }
            GitUtil::checkForRepoOwnershipError($process->getErrorOutput(), $url);
        }

        if (!$deep) {
            return false;
        }

        $process = new ProcessExecutor($io);
        $gitUtil = new GitUtil($io, $config, $process, new Filesystem());
        GitUtil::cleanEnv($process);

        try {
            $gitUtil->runCommands([['git', 'ls-remote', '--heads', '--', '%url%']], $url, sys_get_temp_dir());
        } catch (\RuntimeException $e) {
            return false;
        }

        return true;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository\Vcs;

use Composer\Config;
use Composer\Downloader\TransportException;
use Composer\Json\JsonFile;
use Composer\Cache;
use Composer\IO\IOInterface;
use Composer\Pcre\Preg;
use Composer\Util\GitHub;
use Composer\Util\Http\Response;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class GitHubDriver extends VcsDriver
{
    /** @var string */
    protected $owner;
    /** @var string */
    protected $repository;
    /** @var array<int|string, string> Map of tag name to identifier */
    protected $tags;
    /** @var array<int|string, string> Map of branch name to identifier */
    protected $branches;
    /** @var string */
    protected $rootIdentifier;
    /** @var mixed[] */
    protected $repoData;
    /** @var bool */
    protected $hasIssues = false;
    /** @var bool */
    protected $isPrivate = false;
    /** @var bool */
    private $isArchived = false;
    /** @var array<int, array{type: string, url: string}>|false|null */
    private $fundingInfo;
    /** @var bool */
    private $allowGitFallback = true;

    /**
     * Git Driver
     *
     * @var ?GitDriver
     */
    protected $gitDriver = null;

    /**
     * @inheritDoc
     */
    public function initialize(): void
    {
        if (!Preg::isMatch('#^(?:(?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/([^/]+?)(?:\.git|/)?$#', $this->url, $match)) {
            throw new \InvalidArgumentException(sprintf('The GitHub repository URL %s is invalid.', $this->url));
        }

        $this->owner = $match[3];
        $this->repository = $match[4];
        $this->originUrl = strtolower($match[1] ?? (string) $match[2]);
        if ($this->originUrl === 'www.github.com') {
            $this->originUrl = 'github.com';
        }
        $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->owner.'/'.$this->repository);
        $this->cache->setReadOnly($this->config->get('cache-read-only'));

        if (isset($this->repoConfig['allow-git-fallback']) && $this->repoConfig['allow-git-fallback'] === false) {
            $this->allowGitFallback = false;
        }

        if ($this->config->get('use-github-api') === false || (isset($this->repoConfig['no-api']) && $this->repoConfig['no-api'])) {
            $this->setupGitDriver($this->url);

            return;
        }

        $this->fetchRootIdentifier();
    }

    public function getRepositoryUrl(): string
    {
        return 'https://'.$this->originUrl.'/'.$this->owner.'/'.$this->repository;
    }

    /**
     * @inheritDoc
     */
    public function getRootIdentifier(): string
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getRootIdentifier();
        }

        return $this->rootIdentifier;
    }

    /**
     * @inheritDoc
     */
    public function getUrl(): string
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getUrl();
        }

        return 'https://' . $this->originUrl . '/'.$this->owner.'/'.$this->repository.'.git';
    }

    protected function getApiUrl(): string
    {
        if ('github.com' === $this->originUrl) {
            $apiUrl = 'api.github.com';
        } else {
            $apiUrl = $this->originUrl . '/api/v3';
        }

        return 'https://' . $apiUrl;
    }

    /**
     * @inheritDoc
     */
    public function getSource(string $identifier): array
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getSource($identifier);
        }
        if ($this->isPrivate) {
            // Private GitHub repositories should be accessed using the
            // SSH version of the URL.
            $url = $this->generateSshUrl();
        } else {
            $url = $this->getUrl();
        }

        return ['type' => 'git', 'url' => $url, 'reference' => $identifier];
    }

    /**
     * @inheritDoc
     */
    public function getDist(string $identifier): ?array
    {
        $url = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/zipball/'.$identifier;

        return ['type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => ''];
    }

    /**
     * @inheritDoc
     */
    public function getComposerInformation(string $identifier): ?array
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getComposerInformation($identifier);
        }

        if (!isset($this->infoCache[$identifier])) {
            if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) {
                $composer = JsonFile::parseJson($res);
            } else {
                $composer = $this->getBaseComposerInformation($identifier);

                if ($this->shouldCache($identifier)) {
                    $this->cache->write($identifier, JsonFile::encode($composer, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES));
                }
            }

            if ($composer !== null) {
                // specials for github
                if (isset($composer['support']) && !is_array($composer['support'])) {
                    $composer['support'] = [];
                }
                if (!isset($composer['support']['source'])) {
                    $label = array_search($identifier, $this->getTags()) ?: array_search($identifier, $this->getBranches()) ?: $identifier;
                    $composer['support']['source'] = sprintf('https://%s/%s/%s/tree/%s', $this->originUrl, $this->owner, $this->repository, $label);
                }
                if (!isset($composer['support']['issues']) && $this->hasIssues) {
                    $composer['support']['issues'] = sprintf('https://%s/%s/%s/issues', $this->originUrl, $this->owner, $this->repository);
                }
                if (!isset($composer['abandoned']) && $this->isArchived) {
                    $composer['abandoned'] = true;
                }
                if (!isset($composer['funding']) && $funding = $this->getFundingInfo()) {
                    $composer['funding'] = $funding;
                }
            }

            $this->infoCache[$identifier] = $composer;
        }

        return $this->infoCache[$identifier];
    }

    /**
     * @return array<int, array{type: string, url: string}>|false
     */
    private function getFundingInfo()
    {
        if (null !== $this->fundingInfo) {
            return $this->fundingInfo;
        }

        if ($this->originUrl !== 'github.com') {
            return $this->fundingInfo = false;
        }

        foreach ([$this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/contents/.github/FUNDING.yml', $this->getApiUrl() . '/repos/'.$this->owner.'/.github/contents/FUNDING.yml'] as $file) {
            try {
                $response = $this->httpDownloader->get($file, [
                    'retry-auth-failure' => false,
                ])->decodeJson();
            } catch (TransportException $e) {
                continue;
            }
            if (empty($response['content']) || $response['encoding'] !== 'base64' || !($funding = base64_decode($response['content']))) {
                continue;
            }
            break;
        }
        if (empty($funding)) {
            return $this->fundingInfo = false;
        }

        $result = [];
        $key = null;
        foreach (Preg::split('{\r?\n}', $funding) as $line) {
            $line = trim($line);
            if (Preg::isMatchStrictGroups('{^(\w+)\s*:\s*(.+)$}', $line, $match)) {
                if ($match[2] === '[') {
                    $key = $match[1];
                    continue;
                }
                if (Preg::isMatchStrictGroups('{^\[(.*?)\](?:\s*#.*)?$}', $match[2], $match2)) {
                    foreach (array_map('trim', Preg::split('{[\'"]?\s*,\s*[\'"]?}', $match2[1])) as $item) {
                        $result[] = ['type' => $match[1], 'url' => trim($item, '"\' ')];
                    }
                } elseif (Preg::isMatchStrictGroups('{^([^#].*?)(?:\s+#.*)?$}', $match[2], $match2)) {
                    $result[] = ['type' => $match[1], 'url' => trim($match2[1], '"\' ')];
                }
                $key = null;
            } elseif (Preg::isMatchStrictGroups('{^(\w+)\s*:\s*#\s*$}', $line, $match)) {
                $key = $match[1];
            } elseif ($key !== null && (
                Preg::isMatchStrictGroups('{^-\s*(.+)(?:\s+#.*)?$}', $line, $match)
                || Preg::isMatchStrictGroups('{^(.+),(?:\s*#.*)?$}', $line, $match)
            )) {
                $result[] = ['type' => $key, 'url' => trim($match[1], '"\' ')];
            } elseif ($key !== null && $line === ']') {
                $key = null;
            }
        }

        foreach ($result as $key => $item) {
            switch ($item['type']) {
                case 'community_bridge':
                    $result[$key]['url'] = 'https://funding.communitybridge.org/projects/' . basename($item['url']);
                    break;
                case 'github':
                    $result[$key]['url'] = 'https://github.com/' . basename($item['url']);
                    break;
                case 'issuehunt':
                    $result[$key]['url'] = 'https://issuehunt.io/r/' . $item['url'];
                    break;
                case 'ko_fi':
                    $result[$key]['url'] = 'https://ko-fi.com/' . basename($item['url']);
                    break;
                case 'liberapay':
                    $result[$key]['url'] = 'https://liberapay.com/' . basename($item['url']);
                    break;
                case 'open_collective':
                    $result[$key]['url'] = 'https://opencollective.com/' . basename($item['url']);
                    break;
                case 'patreon':
                    $result[$key]['url'] = 'https://www.patreon.com/' . basename($item['url']);
                    break;
                case 'tidelift':
                    $result[$key]['url'] = 'https://tidelift.com/funding/github/' . $item['url'];
                    break;
                case 'polar':
                    $result[$key]['url'] = 'https://polar.sh/' . basename($item['url']);
                    break;
                case 'buy_me_a_coffee':
                    $result[$key]['url'] = 'https://www.buymeacoffee.com/' . basename($item['url']);
                    break;
                case 'thanks_dev':
                    $result[$key]['url'] = 'https://thanks.dev/' . $item['url'];
                    break;
                case 'otechie':
                    $result[$key]['url'] = 'https://otechie.com/' . basename($item['url']);
                    break;
                case 'custom':
                    $bits = parse_url($item['url']);
                    if ($bits === false) {
                        unset($result[$key]);
                        break;
                    }

                    if (!array_key_exists('scheme', $bits) && !array_key_exists('host', $bits)) {
                        if (Preg::isMatch('{^[a-z0-9-]++\.[a-z]{2,3}$}', $item['url'])) {
                            $result[$key]['url'] = 'https://'.$item['url'];
                            break;
                        }

                        $this->io->writeError('<warning>Funding URL '.$item['url'].' not in a supported format.</warning>');
                        unset($result[$key]);
                        break;
                    }
                    break;
            }
        }

        return $this->fundingInfo = $result;
    }

    /**
     * @inheritDoc
     */
    public function getFileContent(string $file, string $identifier): ?string
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getFileContent($file, $identifier);
        }

        $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/contents/' . $file . '?ref='.urlencode($identifier);
        $resource = $this->getContents($resource)->decodeJson();

        // The GitHub contents API only returns files up to 1MB as base64 encoded files
        // larger files either need be fetched with a raw accept header or by using the git blob endpoint
        if ((!isset($resource['content']) || $resource['content'] === '') && $resource['encoding'] === 'none' && isset($resource['git_url'])) {
            $resource = $this->getContents($resource['git_url'])->decodeJson();
        }

        if (!isset($resource['content']) || $resource['encoding'] !== 'base64' || false === ($content = base64_decode($resource['content']))) {
            throw new \RuntimeException('Could not retrieve ' . $file . ' for '.$identifier);
        }

        return $content;
    }

    /**
     * @inheritDoc
     */
    public function getChangeDate(string $identifier): ?\DateTimeImmutable
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getChangeDate($identifier);
        }

        $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/commits/'.urlencode($identifier);
        $commit = $this->getContents($resource)->decodeJson();

        return new \DateTimeImmutable($commit['commit']['committer']['date']);
    }

    /**
     * @inheritDoc
     */
    public function getTags(): array
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getTags();
        }
        if (null === $this->tags) {
            $tags = [];
            $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/tags?per_page=100';

            do {
                $response = $this->getContents($resource);
                $tagsData = $response->decodeJson();
                foreach ($tagsData as $tag) {
                    $tags[$tag['name']] = $tag['commit']['sha'];
                }

                $resource = $this->getNextPage($response);
            } while ($resource);

            $this->tags = $tags;
        }

        return $this->tags;
    }

    /**
     * @inheritDoc
     */
    public function getBranches(): array
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getBranches();
        }
        if (null === $this->branches) {
            $branches = [];
            $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/git/refs/heads?per_page=100';

            do {
                $response = $this->getContents($resource);
                $branchData = $response->decodeJson();
                foreach ($branchData as $branch) {
                    $name = substr($branch['ref'], 11);
                    if ($name !== 'gh-pages') {
                        $branches[$name] = $branch['object']['sha'];
                    }
                }

                $resource = $this->getNextPage($response);
            } while ($resource);

            $this->branches = $branches;
        }

        return $this->branches;
    }

    /**
     * @inheritDoc
     */
    public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool
    {
        if (!Preg::isMatch('#^((?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/([^/]+?)(?:\.git|/)?$#', $url, $matches)) {
            return false;
        }

        $originUrl = $matches[2] ?? (string) $matches[3];
        if (!in_array(strtolower(Preg::replace('{^www\.}i', '', $originUrl)), $config->get('github-domains'))) {
            return false;
        }

        if (!extension_loaded('openssl')) {
            $io->writeError('Skipping GitHub driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE);

            return false;
        }

        return true;
    }

    /**
     * Gives back the loaded <github-api>/repos/<owner>/<repo> result
     *
     * @return mixed[]|null
     */
    public function getRepoData(): ?array
    {
        $this->fetchRootIdentifier();

        return $this->repoData;
    }

    /**
     * Generate an SSH URL
     */
    protected function generateSshUrl(): string
    {
        if (false !== strpos($this->originUrl, ':')) {
            return 'ssh://git@' . $this->originUrl . '/'.$this->owner.'/'.$this->repository.'.git';
        }

        return 'git@' . $this->originUrl . ':'.$this->owner.'/'.$this->repository.'.git';
    }

    /**
     * @inheritDoc
     */
    protected function getContents(string $url, bool $fetchingRepoData = false): Response
    {
        try {
            return parent::getContents($url);
        } catch (TransportException $e) {
            $gitHubUtil = new GitHub($this->io, $this->config, $this->process, $this->httpDownloader);

            switch ($e->getCode()) {
                case 401:
                case 404:
                    // try to authorize only if we are fetching the main /repos/foo/bar data, otherwise it must be a real 404
                    if (!$fetchingRepoData) {
                        throw $e;
                    }

                    if ($gitHubUtil->authorizeOAuth($this->originUrl)) {
                        return parent::getContents($url);
                    }

                    if (!$this->io->isInteractive()) {
                        $this->attemptCloneFallback($e);

                        return new Response(['url' => 'dummy'], 200, [], 'null');
                    }

                    $scopesIssued = [];
                    $scopesNeeded = [];
                    if ($headers = $e->getHeaders()) {
                        if ($scopes = Response::findHeaderValue($headers, 'X-OAuth-Scopes')) {
                            $scopesIssued = explode(' ', $scopes);
                        }
                        if ($scopes = Response::findHeaderValue($headers, 'X-Accepted-OAuth-Scopes')) {
                            $scopesNeeded = explode(' ', $scopes);
                        }
                    }
                    $scopesFailed = array_diff($scopesNeeded, $scopesIssued);
                    // non-authenticated requests get no scopesNeeded, so ask for credentials
                    // authenticated requests which failed some scopes should ask for new credentials too
                    if (!$headers || !count($scopesNeeded) || count($scopesFailed)) {
                        $gitHubUtil->authorizeOAuthInteractively($this->originUrl, 'Your GitHub credentials are required to fetch private repository metadata (<info>'.$this->url.'</info>)');
                    }

                    return parent::getContents($url);

                case 403:
                    if (!$this->io->hasAuthentication($this->originUrl) && $gitHubUtil->authorizeOAuth($this->originUrl)) {
                        return parent::getContents($url);
                    }

                    if (!$this->io->isInteractive() && $fetchingRepoData) {
                        $this->attemptCloneFallback($e);

                        return new Response(['url' => 'dummy'], 200, [], 'null');
                    }

                    $rateLimited = $gitHubUtil->isRateLimited((array) $e->getHeaders());

                    if (!$this->io->hasAuthentication($this->originUrl)) {
                        if (!$this->io->isInteractive()) {
                            $this->io->writeError('<error>GitHub API limit exhausted. Failed to get metadata for the '.$this->url.' repository, try running in interactive mode so that you can enter your GitHub credentials to increase the API limit</error>');
                            throw $e;
                        }

                        $gitHubUtil->authorizeOAuthInteractively($this->originUrl, 'API limit exhausted. Enter your GitHub credentials to get a larger API limit (<info>'.$this->url.'</info>)');

                        return parent::getContents($url);
                    }

                    if ($rateLimited) {
                        $rateLimit = $gitHubUtil->getRateLimit($e->getHeaders());
                        $this->io->writeError(sprintf(
                            '<error>GitHub API limit (%d calls/hr) is exhausted. You are already authorized so you have to wait until %s before doing more requests</error>',
                            $rateLimit['limit'],
                            $rateLimit['reset']
                        ));
                    }

                    throw $e;

                default:
                    throw $e;
            }
        }
    }

    /**
     * Fetch root identifier from GitHub
     *
     * @throws TransportException
     */
    protected function fetchRootIdentifier(): void
    {
        if ($this->repoData) {
            return;
        }

        $repoDataUrl = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository;

        try {
            $this->repoData = $this->getContents($repoDataUrl, true)->decodeJson();
        } catch (TransportException $e) {
            if ($e->getCode() === 499) {
                $this->attemptCloneFallback($e);
            } else {
                throw $e;
            }
        }
        if (null === $this->repoData && null !== $this->gitDriver) {
            return;
        }

        $this->owner = $this->repoData['owner']['login'];
        $this->repository = $this->repoData['name'];

        $this->isPrivate = !empty($this->repoData['private']);
        if (isset($this->repoData['default_branch'])) {
            $this->rootIdentifier = $this->repoData['default_branch'];
        } elseif (isset($this->repoData['master_branch'])) {
            $this->rootIdentifier = $this->repoData['master_branch'];
        } else {
            $this->rootIdentifier = 'master';
        }
        $this->hasIssues = !empty($this->repoData['has_issues']);
        $this->isArchived = !empty($this->repoData['archived']);
    }

    /**
     * @phpstan-impure
     *
     * @return true
     * @throws \RuntimeException
     */
    protected function attemptCloneFallback(?\Throwable $e = null): bool
    {
        if (!$this->allowGitFallback) {
            throw new \RuntimeException('Fallback to git driver disabled', 0, $e);
        }

        $this->isPrivate = true;

        try {
            // If this repository may be private (hard to say for sure,
            // GitHub returns 404 for private repositories) and we
            // cannot ask for authentication credentials (because we
            // are not interactive) then we fallback to GitDriver.
            $this->setupGitDriver($this->generateSshUrl());

            return true;
        } catch (\RuntimeException $e) {
            $this->gitDriver = null;

            $this->io->writeError('<error>Failed to clone the '.$this->generateSshUrl().' repository, try running in interactive mode so that you can enter your GitHub credentials</error>');
            throw $e;
        }
    }

    protected function setupGitDriver(string $url): void
    {
        if (!$this->allowGitFallback) {
            throw new \RuntimeException('Fallback to git driver disabled');
        }
        $this->gitDriver = new GitDriver(
            ['url' => $url],
            $this->io,
            $this->config,
            $this->httpDownloader,
            $this->process
        );
        $this->gitDriver->initialize();
    }

    protected function getNextPage(Response $response): ?string
    {
        $header = $response->getHeader('link');
        if (!$header) {
            return null;
        }

        $links = explode(',', $header);
        foreach ($links as $link) {
            if (Preg::isMatch('{<(.+?)>; *rel="next"}', $link, $match)) {
                return $match[1];
            }
        }

        return null;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository\Vcs;

use Composer\Config;
use Composer\Cache;
use Composer\Pcre\Preg;
use Composer\Util\Hg as HgUtils;
use Composer\Util\ProcessExecutor;
use Composer\Util\Filesystem;
use Composer\IO\IOInterface;
use Composer\Util\Url;

/**
 * @author Per Bernhardt <plb@webfactory.de>
 */
class HgDriver extends VcsDriver
{
    /** @var array<int|string, string> Map of tag name to identifier */
    protected $tags;
    /** @var array<int|string, string> Map of branch name to identifier */
    protected $branches;
    /** @var string */
    protected $rootIdentifier;
    /** @var string */
    protected $repoDir;

    /**
     * @inheritDoc
     */
    public function initialize(): void
    {
        if (Filesystem::isLocalPath($this->url)) {
            $this->repoDir = $this->url;
        } else {
            if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) {
                throw new \RuntimeException('HgDriver requires a usable cache directory, and it looks like you set it to be disabled');
            }

            $cacheDir = $this->config->get('cache-vcs-dir');
            $this->repoDir = $cacheDir . '/' . Preg::replace('{[^a-z0-9]}i', '-', Url::sanitize($this->url)) . '/';

            $fs = new Filesystem();
            $fs->ensureDirectoryExists($cacheDir);

            if (!is_writable(dirname($this->repoDir))) {
                throw new \RuntimeException('Can not clone '.$this->url.' to access package information. The "'.$cacheDir.'" directory is not writable by the current user.');
            }

            // Ensure we are allowed to use this URL by config
            $this->config->prohibitUrlByConfig($this->url, $this->io);

            $hgUtils = new HgUtils($this->io, $this->config, $this->process);

            // update the repo if it is a valid hg repository
            if (is_dir($this->repoDir) && 0 === $this->process->execute(['hg', 'summary'], $output, $this->repoDir)) {
                if (0 !== $this->process->execute(['hg', 'pull'], $output, $this->repoDir)) {
                    $this->io->writeError('<error>Failed to update '.$this->url.', package information from this repository may be outdated ('.$this->process->getErrorOutput().')</error>');
                }
            } else {
                // clean up directory and do a fresh clone into it
                $fs->removeDirectory($this->repoDir);

                $repoDir = $this->repoDir;
                $command = static function ($url) use ($repoDir): array {
                    return ['hg', 'clone', '--noupdate', '--', $url, $repoDir];
                };

                $hgUtils->runCommand($command, $this->url, null);
            }
        }

        $this->getTags();
        $this->getBranches();
    }

    /**
     * @inheritDoc
     */
    public function getRootIdentifier(): string
    {
        if (null === $this->rootIdentifier) {
            $this->process->execute(['hg', 'tip', '--template', '{node}'], $output, $this->repoDir);
            $output = $this->process->splitLines($output);
            $this->rootIdentifier = $output[0];
        }

        return $this->rootIdentifier;
    }

    /**
     * @inheritDoc
     */
    public function getUrl(): string
    {
        return $this->url;
    }

    /**
     * @inheritDoc
     */
    public function getSource(string $identifier): array
    {
        return ['type' => 'hg', 'url' => $this->getUrl(), 'reference' => $identifier];
    }

    /**
     * @inheritDoc
     */
    public function getDist(string $identifier): ?array
    {
        return null;
    }

    /**
     * @inheritDoc
     */
    public function getFileContent(string $file, string $identifier): ?string
    {
        if (isset($identifier[0]) && $identifier[0] === '-') {
            throw new \RuntimeException('Invalid hg identifier detected. Identifier must not start with a -, given: ' . $identifier);
        }

        $resource = ['hg', 'cat', '-r', $identifier, '--', $file];
        $this->process->execute($resource, $content, $this->repoDir);

        if (!trim($content)) {
            return null;
        }

        return $content;
    }

    /**
     * @inheritDoc
     */
    public function getChangeDate(string $identifier): ?\DateTimeImmutable
    {
        if (isset($identifier[0]) && $identifier[0] === '-') {
            throw new \RuntimeException('Invalid hg identifier detected. Identifier must not start with a -, given: ' . $identifier);
        }

        $this->process->execute(
            ['hg', 'log', '--template', '{date|rfc3339date}', '-r', $identifier],
            $output,
            $this->repoDir
        );

        return new \DateTimeImmutable(trim($output), new \DateTimeZone('UTC'));
    }

    /**
     * @inheritDoc
     */
    public function getTags(): array
    {
        if (null === $this->tags) {
            $tags = [];

            $this->process->execute(['hg', 'tags'], $output, $this->repoDir);
            foreach ($this->process->splitLines($output) as $tag) {
                if ($tag && Preg::isMatchStrictGroups('(^([^\s]+)\s+\d+:(.*)$)', $tag, $match)) {
                    $tags[$match[1]] = $match[2];
                }
            }
            unset($tags['tip']);

            $this->tags = $tags;
        }

        return $this->tags;
    }

    /**
     * @inheritDoc
     */
    public function getBranches(): array
    {
        if (null === $this->branches) {
            $branches = [];
            $bookmarks = [];

            $this->process->execute(['hg', 'branches'], $output, $this->repoDir);
            foreach ($this->process->splitLines($output) as $branch) {
                if ($branch && Preg::isMatchStrictGroups('(^([^\s]+)\s+\d+:([a-f0-9]+))', $branch, $match) && $match[1][0] !== '-') {
                    $branches[$match[1]] = $match[2];
                }
            }

            $this->process->execute(['hg', 'bookmarks'], $output, $this->repoDir);
            foreach ($this->process->splitLines($output) as $branch) {
                if ($branch && Preg::isMatchStrictGroups('(^(?:[\s*]*)([^\s]+)\s+\d+:(.*)$)', $branch, $match) && $match[1][0] !== '-') {
                    $bookmarks[$match[1]] = $match[2];
                }
            }

            // Branches will have preference over bookmarks
            $this->branches = array_merge($bookmarks, $branches);
        }

        return $this->branches;
    }

    /**
     * @inheritDoc
     */
    public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool
    {
        if (Preg::isMatch('#(^(?:https?|ssh)://(?:[^@]+@)?bitbucket.org|https://(?:.*?)\.kilnhg.com)#i', $url)) {
            return true;
        }

        // local filesystem
        if (Filesystem::isLocalPath($url)) {
            $url = Filesystem::getPlatformPath($url);
            if (!is_dir($url)) {
                return false;
            }

            $process = new ProcessExecutor($io);
            // check whether there is a hg repo in that path
            if ($process->execute(['hg', 'summary'], $output, $url) === 0) {
                return true;
            }
        }

        if (!$deep) {
            return false;
        }

        $process = new ProcessExecutor($io);
        $exit = $process->execute(['hg', 'identify', '--', $url], $ignored);

        return $exit === 0;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository\Vcs;

use Composer\Config;
use Composer\IO\IOInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @internal
 */
interface VcsDriverInterface
{
    /**
     * Initializes the driver (git clone, svn checkout, fetch info etc)
     */
    public function initialize(): void;

    /**
     * Return the composer.json file information
     *
     * @param  string  $identifier Any identifier to a specific branch/tag/commit
     * @return mixed[]|null Array containing all infos from the composer.json file, or null to denote that no file was present
     */
    public function getComposerInformation(string $identifier): ?array;

    /**
     * Return the content of $file or null if the file does not exist.
     */
    public function getFileContent(string $file, string $identifier): ?string;

    /**
     * Get the changedate for $identifier.
     */
    public function getChangeDate(string $identifier): ?\DateTimeImmutable;

    /**
     * Return the root identifier (trunk, master, default/tip ..)
     *
     * @return string Identifier
     */
    public function getRootIdentifier(): string;

    /**
     * Return list of branches in the repository
     *
     * @return array<int|string, string> Branch names as keys, identifiers as values
     */
    public function getBranches(): array;

    /**
     * Return list of tags in the repository
     *
     * @return array<int|string, string> Tag names as keys, identifiers as values
     */
    public function getTags(): array;

    /**
     * @param string $identifier Any identifier to a specific branch/tag/commit
     *
     * @return array{type: string, url: string, reference: string, shasum: string}|null
     */
    public function getDist(string $identifier): ?array;

    /**
     * @param string $identifier Any identifier to a specific branch/tag/commit
     *
     * @return array{type: string, url: string, reference: string}
     */
    public function getSource(string $identifier): array;

    /**
     * Return the URL of the repository
     */
    public function getUrl(): string;

    /**
     * Return true if the repository has a composer file for a given identifier,
     * false otherwise.
     *
     * @param  string $identifier Any identifier to a specific branch/tag/commit
     * @return bool   Whether the repository has a composer file for a given identifier.
     */
    public function hasComposerFile(string $identifier): bool;

    /**
     * Performs any cleanup necessary as the driver is not longer needed
     */
    public function cleanup(): void;

    /**
     * Checks if this driver can handle a given url
     *
     * @param  IOInterface $io     IO instance
     * @param  Config      $config current $config
     * @param  string      $url    URL to validate/check
     * @param  bool        $deep   unless true, only shallow checks (url matching typically) should be done
     */
    public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool;
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository\Vcs;

use Composer\Config;
use Composer\Cache;
use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
use Composer\Downloader\TransportException;
use Composer\Pcre\Preg;
use Composer\Util\HttpDownloader;
use Composer\Util\GitLab;
use Composer\Util\Http\Response;

/**
 * Driver for GitLab API, use the Git driver for local checkouts.
 *
 * @author Henrik Bjørnskov <henrik@bjrnskov.dk>
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class GitLabDriver extends VcsDriver
{
    /**
     * @var string
     * @phpstan-var 'https'|'http'
     */
    private $scheme;
    /** @var string */
    private $namespace;
    /** @var string */
    private $repository;

    /**
     * @var mixed[] Project data returned by GitLab API
     */
    private $project = null;

    /**
     * @var array<string|int, mixed[]> Keeps commits returned by GitLab API as commit id => info
     */
    private $commits = [];

    /** @var array<int|string, string> Map of tag name to identifier */
    private $tags;

    /** @var array<int|string, string> Map of branch name to identifier */
    private $branches;

    /**
     * Git Driver
     *
     * @var ?GitDriver
     */
    protected $gitDriver = null;

    /**
     * Protocol to force use of for repository URLs.
     *
     * @var string One of ssh, http
     */
    protected $protocol;

    /**
     * Defaults to true unless we can make sure it is public
     *
     * @var bool defines whether the repo is private or not
     */
    private $isPrivate = true;

    /**
     * @var bool true if the origin has a port number or a path component in it
     */
    private $hasNonstandardOrigin = false;

    public const URL_REGEX = '#^(?:(?P<scheme>https?)://(?P<domain>.+?)(?::(?P<port>[0-9]+))?/|git@(?P<domain2>[^:]+):)(?P<parts>.+)/(?P<repo>[^/]+?)(?:\.git|/)?$#';

    /**
     * Extracts information from the repository url.
     *
     * SSH urls use https by default. Set "secure-http": false on the repository config to use http instead.
     *
     * @inheritDoc
     */
    public function initialize(): void
    {
        if (!Preg::isMatch(self::URL_REGEX, $this->url, $match)) {
            throw new \InvalidArgumentException(sprintf('The GitLab repository URL %s is invalid. It must be the HTTP URL of a GitLab project.', $this->url));
        }

        $guessedDomain = $match['domain'] ?? (string) $match['domain2'];
        $configuredDomains = $this->config->get('gitlab-domains');
        $urlParts = explode('/', $match['parts']);

        $this->scheme = in_array($match['scheme'], ['https', 'http'], true)
            ? $match['scheme']
            : (isset($this->repoConfig['secure-http']) && $this->repoConfig['secure-http'] === false ? 'http' : 'https')
        ;
        $origin = self::determineOrigin($configuredDomains, $guessedDomain, $urlParts, $match['port']);
        if (false === $origin) {
            throw new \LogicException('It should not be possible to create a gitlab driver with an unparsable origin URL ('.$this->url.')');
        }
        $this->originUrl = $origin;

        if (is_string($protocol = $this->config->get('gitlab-protocol'))) {
            // https treated as a synonym for http.
            if (!in_array($protocol, ['git', 'http', 'https'], true)) {
                throw new \RuntimeException('gitlab-protocol must be one of git, http.');
            }
            $this->protocol = $protocol === 'git' ? 'ssh' : 'http';
        }

        if (false !== strpos($this->originUrl, ':') || false !== strpos($this->originUrl, '/')) {
            $this->hasNonstandardOrigin = true;
        }

        $this->namespace = implode('/', $urlParts);
        $this->repository = Preg::replace('#(\.git)$#', '', $match['repo']);

        $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->namespace.'/'.$this->repository);
        $this->cache->setReadOnly($this->config->get('cache-read-only'));

        $this->fetchProject();
    }

    /**
     * Updates the HttpDownloader instance.
     * Mainly useful for tests.
     *
     * @internal
     */
    public function setHttpDownloader(HttpDownloader $httpDownloader): void
    {
        $this->httpDownloader = $httpDownloader;
    }

    /**
     * @inheritDoc
     */
    public function getComposerInformation(string $identifier): ?array
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getComposerInformation($identifier);
        }

        if (!isset($this->infoCache[$identifier])) {
            if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) {
                $composer = JsonFile::parseJson($res);
            } else {
                $composer = $this->getBaseComposerInformation($identifier);

                if ($this->shouldCache($identifier)) {
                    $this->cache->write($identifier, JsonFile::encode($composer, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES));
                }
            }

            if (null !== $composer) {
                // specials for gitlab (this data is only available if authentication is provided)
                if (isset($composer['support']) && !is_array($composer['support'])) {
                    $composer['support'] = [];
                }
                if (!isset($composer['support']['source']) && isset($this->project['web_url'])) {
                    $label = array_search($identifier, $this->getTags(), true) ?: array_search($identifier, $this->getBranches(), true) ?: $identifier;
                    $composer['support']['source'] = sprintf('%s/-/tree/%s', $this->project['web_url'], $label);
                }
                if (!isset($composer['support']['issues']) && !empty($this->project['issues_enabled']) && isset($this->project['web_url'])) {
                    $composer['support']['issues'] = sprintf('%s/-/issues', $this->project['web_url']);
                }
                if (!isset($composer['abandoned']) && !empty($this->project['archived'])) {
                    $composer['abandoned'] = true;
                }
            }

            $this->infoCache[$identifier] = $composer;
        }

        return $this->infoCache[$identifier];
    }

    /**
     * @inheritDoc
     */
    public function getFileContent(string $file, string $identifier): ?string
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getFileContent($file, $identifier);
        }

        // Convert the root identifier to a cacheable commit id
        if (!Preg::isMatch('{[a-f0-9]{40}}i', $identifier)) {
            $branches = $this->getBranches();
            if (isset($branches[$identifier])) {
                $identifier = $branches[$identifier];
            }
        }

        $resource = $this->getApiUrl().'/repository/files/'.$this->urlEncodeAll($file).'/raw?ref='.$identifier;

        try {
            $content = $this->getContents($resource)->getBody();
        } catch (TransportException $e) {
            if ($e->getCode() !== 404) {
                throw $e;
            }

            return null;
        }

        return $content;
    }

    /**
     * @inheritDoc
     */
    public function getChangeDate(string $identifier): ?\DateTimeImmutable
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getChangeDate($identifier);
        }

        if (isset($this->commits[$identifier])) {
            return new \DateTimeImmutable($this->commits[$identifier]['committed_date']);
        }

        return null;
    }

    public function getRepositoryUrl(): string
    {
        if ($this->protocol) {
            return $this->project["{$this->protocol}_url_to_repo"];
        }

        return $this->isPrivate ? $this->project['ssh_url_to_repo'] : $this->project['http_url_to_repo'];
    }

    /**
     * @inheritDoc
     */
    public function getUrl(): string
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getUrl();
        }

        return $this->project['web_url'];
    }

    /**
     * @inheritDoc
     */
    public function getDist(string $identifier): ?array
    {
        $url = $this->getApiUrl().'/repository/archive.zip?sha='.$identifier;

        return ['type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => ''];
    }

    /**
     * @inheritDoc
     */
    public function getSource(string $identifier): array
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getSource($identifier);
        }

        return ['type' => 'git', 'url' => $this->getRepositoryUrl(), 'reference' => $identifier];
    }

    /**
     * @inheritDoc
     */
    public function getRootIdentifier(): string
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getRootIdentifier();
        }

        return $this->project['default_branch'];
    }

    /**
     * @inheritDoc
     */
    public function getBranches(): array
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getBranches();
        }

        if (null === $this->branches) {
            $this->branches = $this->getReferences('branches');
        }

        return $this->branches;
    }

    /**
     * @inheritDoc
     */
    public function getTags(): array
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getTags();
        }

        if (null === $this->tags) {
            $this->tags = $this->getReferences('tags');
        }

        return $this->tags;
    }

    /**
     * @return string Base URL for GitLab API v3
     */
    public function getApiUrl(): string
    {
        return $this->scheme.'://'.$this->originUrl.'/api/v4/projects/'.$this->urlEncodeAll($this->namespace).'%2F'.$this->urlEncodeAll($this->repository);
    }

    /**
     * Urlencode all non alphanumeric characters. rawurlencode() can not be used as it does not encode `.`
     */
    private function urlEncodeAll(string $string): string
    {
        $encoded = '';
        for ($i = 0; isset($string[$i]); $i++) {
            $character = $string[$i];
            if (!ctype_alnum($character) && !in_array($character, ['-', '_'], true)) {
                $character = '%' . sprintf('%02X', ord($character));
            }
            $encoded .= $character;
        }

        return $encoded;
    }

    /**
     * @return string[] where keys are named references like tags or branches and the value a sha
     */
    protected function getReferences(string $type): array
    {
        $perPage = 100;
        $resource = $this->getApiUrl().'/repository/'.$type.'?per_page='.$perPage;

        $references = [];
        do {
            $response = $this->getContents($resource);
            $data = $response->decodeJson();

            foreach ($data as $datum) {
                $references[$datum['name']] = $datum['commit']['id'];

                // Keep the last commit date of a reference to avoid
                // unnecessary API call when retrieving the composer file.
                $this->commits[$datum['commit']['id']] = $datum['commit'];
            }

            if (count($data) >= $perPage) {
                $resource = $this->getNextPage($response);
            } else {
                $resource = false;
            }
        } while ($resource);

        return $references;
    }

    protected function fetchProject(): void
    {
        if (!is_null($this->project)) {
            return;
        }

        // we need to fetch the default branch from the api
        $resource = $this->getApiUrl();
        $this->project = $this->getContents($resource, true)->decodeJson();
        if (isset($this->project['visibility'])) {
            $this->isPrivate = $this->project['visibility'] !== 'public';
        } else {
            // client is not authenticated, therefore repository has to be public
            $this->isPrivate = false;
        }
    }

    /**
     * @phpstan-impure
     *
     * @return true
     * @throws \RuntimeException
     */
    protected function attemptCloneFallback(): bool
    {
        if ($this->isPrivate === false) {
            $url = $this->generatePublicUrl();
        } else {
            $url = $this->generateSshUrl();
        }

        try {
            // If this repository may be private and we
            // cannot ask for authentication credentials (because we
            // are not interactive) then we fallback to GitDriver.
            $this->setupGitDriver($url);

            return true;
        } catch (\RuntimeException $e) {
            $this->gitDriver = null;

            $this->io->writeError('<error>Failed to clone the '.$url.' repository, try running in interactive mode so that you can enter your credentials</error>');
            throw $e;
        }
    }

    /**
     * Generate an SSH URL
     */
    protected function generateSshUrl(): string
    {
        if ($this->hasNonstandardOrigin) {
            return 'ssh://git@'.$this->originUrl.'/'.$this->namespace.'/'.$this->repository.'.git';
        }

        return 'git@' . $this->originUrl . ':'.$this->namespace.'/'.$this->repository.'.git';
    }

    protected function generatePublicUrl(): string
    {
        return $this->scheme . '://' . $this->originUrl . '/'.$this->namespace.'/'.$this->repository.'.git';
    }

    protected function setupGitDriver(string $url): void
    {
        $this->gitDriver = new GitDriver(
            ['url' => $url],
            $this->io,
            $this->config,
            $this->httpDownloader,
            $this->process
        );
        $this->gitDriver->initialize();
    }

    /**
     * @inheritDoc
     */
    protected function getContents(string $url, bool $fetchingRepoData = false): Response
    {
        try {
            $response = parent::getContents($url);

            if ($fetchingRepoData) {
                $json = $response->decodeJson();

                // Accessing the API with a token with Guest (10) or Planner (15) access will return
                // more data than unauthenticated access but no default_branch data
                // accessing files via the API will then also fail
                if (!isset($json['default_branch']) && isset($json['permissions'])) {
                    $this->isPrivate = $json['visibility'] !== 'public';

                    $moreThanGuestAccess = false;
                    // Check both access levels (e.g. project, group)
                    // - value will be null if no access is set
                    // - value will be array with key access_level if set
                    foreach ($json['permissions'] as $permission) {
                        if ($permission && $permission['access_level'] >= 20) {
                            $moreThanGuestAccess = true;
                        }
                    }

                    if (!$moreThanGuestAccess) {
                        $this->io->writeError('<warning>GitLab token with Guest or Planner only access detected</warning>');

                        $this->attemptCloneFallback();

                        return new Response(['url' => 'dummy'], 200, [], 'null');
                    }
                }

                // force auth as the unauthenticated version of the API is broken
                if (!isset($json['default_branch'])) {
                    // GitLab allows you to disable the repository inside a project to use a project only for issues and wiki
                    if (isset($json['repository_access_level']) && $json['repository_access_level'] === 'disabled') {
                        throw new TransportException('The GitLab repository is disabled in the project', 400);
                    }

                    if (!empty($json['id'])) {
                        $this->isPrivate = false;
                    }

                    throw new TransportException('GitLab API seems to not be authenticated as it did not return a default_branch', 401);
                }
            }

            return $response;
        } catch (TransportException $e) {
            $gitLabUtil = new GitLab($this->io, $this->config, $this->process, $this->httpDownloader);

            switch ($e->getCode()) {
                case 401:
                case 404:
                    // try to authorize only if we are fetching the main /repos/foo/bar data, otherwise it must be a real 404
                    if (!$fetchingRepoData) {
                        throw $e;
                    }

                    if ($gitLabUtil->authorizeOAuth($this->originUrl)) {
                        return parent::getContents($url);
                    }

                    if ($gitLabUtil->isOAuthExpired($this->originUrl) && $gitLabUtil->authorizeOAuthRefresh($this->scheme, $this->originUrl)) {
                        return parent::getContents($url);
                    }

                    if (!$this->io->isInteractive()) {
                        $this->attemptCloneFallback();

                        return new Response(['url' => 'dummy'], 200, [], 'null');
                    }
                    $this->io->writeError('<warning>Failed to download ' . $this->namespace . '/' . $this->repository . ':' . $e->getMessage() . '</warning>');
                    $gitLabUtil->authorizeOAuthInteractively($this->scheme, $this->originUrl, 'Your credentials are required to fetch private repository metadata (<info>'.$this->url.'</info>)');

                    return parent::getContents($url);

                case 403:
                    if (!$this->io->hasAuthentication($this->originUrl) && $gitLabUtil->authorizeOAuth($this->originUrl)) {
                        return parent::getContents($url);
                    }

                    if (!$this->io->isInteractive() && $fetchingRepoData) {
                        $this->attemptCloneFallback();

                        return new Response(['url' => 'dummy'], 200, [], 'null');
                    }

                    throw $e;

                default:
                    throw $e;
            }
        }
    }

    /**
     * Uses the config `gitlab-domains` to see if the driver supports the url for the
     * repository given.
     *
     * @inheritDoc
     */
    public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool
    {
        if (!Preg::isMatch(self::URL_REGEX, $url, $match)) {
            return false;
        }

        $scheme = $match['scheme'];
        $guessedDomain = $match['domain'] ?? (string) $match['domain2'];
        $urlParts = explode('/', $match['parts']);

        if (false === self::determineOrigin($config->get('gitlab-domains'), $guessedDomain, $urlParts, $match['port'])) {
            return false;
        }

        if ('https' === $scheme && !extension_loaded('openssl')) {
            $io->writeError('Skipping GitLab driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE);

            return false;
        }

        return true;
    }

    /**
     * Gives back the loaded <gitlab-api>/projects/<owner>/<repo> result
     *
     * @return mixed[]|null
     */
    public function getRepoData(): ?array
    {
        $this->fetchProject();

        return $this->project;
    }

    protected function getNextPage(Response $response): ?string
    {
        $header = $response->getHeader('link');

        $links = explode(',', $header);
        foreach ($links as $link) {
            if (Preg::isMatchStrictGroups('{<(.+?)>; *rel="next"}', $link, $match)) {
                return $match[1];
            }
        }

        return null;
    }

    /**
     * @param  array<string> $configuredDomains
     * @param  array<string> $urlParts
     *
     * @return string|false
     */
    private static function determineOrigin(array $configuredDomains, string $guessedDomain, array &$urlParts, ?string $portNumber)
    {
        $guessedDomain = strtolower($guessedDomain);

        if (in_array($guessedDomain, $configuredDomains) || (null !== $portNumber && in_array($guessedDomain.':'.$portNumber, $configuredDomains))) {
            if (null !== $portNumber) {
                return $guessedDomain.':'.$portNumber;
            }

            return $guessedDomain;
        }

        if (null !== $portNumber) {
            $guessedDomain .= ':'.$portNumber;
        }

        while (null !== ($part = array_shift($urlParts))) {
            $guessedDomain .= '/' . $part;

            if (in_array($guessedDomain, $configuredDomains) || (null !== $portNumber && in_array(Preg::replace('{:\d+}', '', $guessedDomain), $configuredDomains))) {
                return $guessedDomain;
            }
        }

        return false;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository\Vcs;

use Composer\Cache;
use Composer\Config;
use Composer\Downloader\TransportException;
use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
use Composer\Pcre\Preg;
use Composer\Util\Forgejo;
use Composer\Util\ForgejoRepositoryData;
use Composer\Util\ForgejoUrl;
use Composer\Util\Http\Response;

class ForgejoDriver extends VcsDriver
{
    /** @var ForgejoUrl */
    private $forgejoUrl;
    /** @var ForgejoRepositoryData */
    private $repositoryData;

    /** @var ?GitDriver */
    protected $gitDriver = null;
    /** @var array<int|string, string> Map of tag name to identifier */
    private $tags;
    /** @var array<int|string, string> Map of branch name to identifier */
    private $branches;

    public function initialize(): void
    {
        $this->forgejoUrl = ForgejoUrl::create($this->url);
        $this->originUrl = $this->forgejoUrl->originUrl;

        $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->forgejoUrl->owner.'/'.$this->forgejoUrl->repository);
        $this->cache->setReadOnly($this->config->get('cache-read-only'));

        $this->fetchRepositoryData();
    }

    public function getFileContent(string $file, string $identifier): ?string
    {
        if ($this->gitDriver !== null) {
            return $this->gitDriver->getFileContent($file, $identifier);
        }

        $resource = $this->forgejoUrl->apiUrl.'/contents/' . $file . '?ref='.urlencode($identifier);
        $resource = $this->getContents($resource)->decodeJson();

        // The Forgejo contents API only returns files up to 1MB as base64 encoded files
        // larger files either need be fetched with a raw accept header or by using the git blob endpoint
        if ((!isset($resource['content']) || $resource['content'] === '') && $resource['encoding'] === 'none' && isset($resource['git_url'])) {
            $resource = $this->getContents($resource['git_url'])->decodeJson();
        }

        if (!isset($resource['content']) || $resource['encoding'] !== 'base64' || false === ($content = base64_decode($resource['content'], true))) {
            throw new \RuntimeException('Could not retrieve ' . $file . ' for '.$identifier);
        }

        return $content;
    }

    public function getChangeDate(string $identifier): ?\DateTimeImmutable
    {
        if ($this->gitDriver !== null) {
            return $this->gitDriver->getChangeDate($identifier);
        }

        $resource = $this->forgejoUrl->apiUrl.'/git/commits/'.urlencode($identifier).'?verification=false&files=false';
        $commit = $this->getContents($resource)->decodeJson();

        return new \DateTimeImmutable($commit['commit']['committer']['date']);
    }

    public function getRootIdentifier(): string
    {
        if ($this->gitDriver !== null) {
            return $this->gitDriver->getRootIdentifier();
        }

        return $this->repositoryData->defaultBranch;
    }

    public function getBranches(): array
    {
        if ($this->gitDriver !== null) {
            return $this->gitDriver->getBranches();
        }

        if (null === $this->branches) {
            $branches = [];
            $resource = $this->forgejoUrl->apiUrl.'/branches?per_page=100';

            do {
                $response = $this->getContents($resource);
                $branchData = $response->decodeJson();
                foreach ($branchData as $branch) {
                    $branches[$branch['name']] = $branch['commit']['id'];
                }

                $resource = $this->getNextPage($response);
            } while ($resource);

            $this->branches = $branches;
        }

        return $this->branches;
    }

    public function getTags(): array
    {
        if ($this->gitDriver !== null) {
            return $this->gitDriver->getTags();
        }
        if (null === $this->tags) {
            $tags = [];
            $resource = $this->forgejoUrl->apiUrl.'/tags?per_page=100';

            do {
                $response = $this->getContents($resource);
                $tagsData = $response->decodeJson();
                foreach ($tagsData as $tag) {
                    $tags[$tag['name']] = $tag['commit']['sha'];
                }

                $resource = $this->getNextPage($response);
            } while ($resource);

            $this->tags = $tags;
        }

        return $this->tags;
    }

    public function getDist(string $identifier): ?array
    {
        $url = $this->forgejoUrl->apiUrl.'/archive/'.$identifier.'.zip';

        return ['type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => ''];
    }

    public function getComposerInformation(string $identifier): ?array
    {
        if ($this->gitDriver !== null) {
            return $this->gitDriver->getComposerInformation($identifier);
        }

        if (!isset($this->infoCache[$identifier])) {
            if ($this->shouldCache($identifier) && false !== ($res = $this->cache->read($identifier))) {
                $composer = JsonFile::parseJson($res);
            } else {
                $composer = $this->getBaseComposerInformation($identifier);

                if ($this->shouldCache($identifier)) {
                    $this->cache->write($identifier, JsonFile::encode($composer, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES));
                }
            }

            if ($composer !== null) {
                // specials for forgejo
                if (isset($composer['support']) && !is_array($composer['support'])) {
                    $composer['support'] = [];
                }
                if (!isset($composer['support']['source'])) {
                    if (false !== ($label = array_search($identifier, $this->getTags(), true))) {
                        $composer['support']['source'] = $this->repositoryData->htmlUrl.'/tag/' . $label;
                    } elseif (false !== ($label = array_search($identifier, $this->getBranches(), true))) {
                        $composer['support']['source'] = $this->repositoryData->htmlUrl.'/branch/'.$label;
                    } else {
                        $composer['support']['source'] = $this->repositoryData->htmlUrl.'/commit/'.$identifier;
                    }
                }
                if (!isset($composer['support']['issues']) && $this->repositoryData->hasIssues) {
                    $composer['support']['issues'] = $this->repositoryData->htmlUrl.'/issues';
                }
                if (!isset($composer['abandoned']) && $this->repositoryData->isArchived) {
                    $composer['abandoned'] = true;
                }
            }

            $this->infoCache[$identifier] = $composer;
        }

        return $this->infoCache[$identifier];
    }

    public function getSource(string $identifier): array
    {
        if ($this->gitDriver !== null) {
            return $this->gitDriver->getSource($identifier);
        }

        return ['type' => 'git', 'url' => $this->getUrl(), 'reference' => $identifier];
    }

    public function getUrl(): string
    {
        if ($this->gitDriver !== null) {
            return $this->gitDriver->getUrl();
        }

        return $this->repositoryData->isPrivate ? $this->repositoryData->sshUrl : $this->repositoryData->httpCloneUrl;
    }

    public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool
    {
        $forgejoUrl = ForgejoUrl::tryFrom($url);
        if ($forgejoUrl === null) {
            return false;
        }

        if (!in_array(strtolower($forgejoUrl->originUrl), $config->get('forgejo-domains'), true)) {
            return false;
        }

        if (!extension_loaded('openssl')) {
            $io->writeError('Skipping Forgejo driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE);

            return false;
        }

        return true;
    }

    protected function setupGitDriver(string $url): void
    {
        $this->gitDriver = new GitDriver(
            ['url' => $url],
            $this->io,
            $this->config,
            $this->httpDownloader,
            $this->process
        );
        $this->gitDriver->initialize();
    }

    private function fetchRepositoryData(): void
    {
        if ($this->repositoryData !== null) {
            return;
        }

        $data = $this->getContents($this->forgejoUrl->apiUrl, true)->decodeJson();

        if (null === $data && null !== $this->gitDriver) {
            return;
        }

        $this->repositoryData = ForgejoRepositoryData::fromRemoteData($data);
    }

    protected function getNextPage(Response $response): ?string
    {
        $header = $response->getHeader('link');
        if ($header === null) {
            return null;
        }

        $links = explode(',', $header);
        foreach ($links as $link) {
            if (Preg::isMatch('{<(.+?)>; *rel="next"}', $link, $match)) {
                return $match[1];
            }
        }

        return null;
    }

    protected function getContents(string $url, bool $fetchingRepoData = false): Response
    {
        $forgejo = new Forgejo($this->io, $this->config, $this->httpDownloader);

        try {
            return parent::getContents($url);
        } catch (TransportException $e) {
            switch ($e->getCode()) {
                case 401:
                case 403:
                case 404:
                case 429:
                    if (!$fetchingRepoData) {
                        throw $e;
                    }

                    if (!$this->io->isInteractive()) {
                        $this->attemptCloneFallback();

                        return new Response(['url' => 'dummy'], 200, [], 'null');
                    }

                    if (
                        !$this->io->hasAuthentication($this->originUrl) &&
                        $forgejo->authorizeOAuthInteractively($this->forgejoUrl->originUrl, $e->getCode() === 429 ? 'API limit exhausted. Enter your Forgejo credentials to get a larger API limit (<info>'.$this->url.'</info>)' : null)
                    ) {
                        return parent::getContents($url);
                    }

                    throw $e;
                default:
                    throw $e;
            }
        }
    }

    /**
     * @phpstan-impure
     *
     * @return true
     * @throws \RuntimeException
     */
    protected function attemptCloneFallback(): bool
    {
        try {
            // If this repository may be private (hard to say for sure,
            // Forgejo returns 404 for private repositories) and we
            // cannot ask for authentication credentials (because we
            // are not interactive) then we fallback to GitDriver.
            $this->setupGitDriver($this->forgejoUrl->generateSshUrl());

            return true;
        } catch (\RuntimeException $e) {
            $this->gitDriver = null;

            $this->io->writeError('<error>Failed to clone the '.$this->forgejoUrl->generateSshUrl().' repository, try running in interactive mode so that you can enter your Forgejo credentials</error>');
            throw $e;
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository\Vcs;

use Composer\Cache;
use Composer\Downloader\TransportException;
use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
use Composer\Pcre\Preg;
use Composer\Util\ProcessExecutor;
use Composer\Util\HttpDownloader;
use Composer\Util\Filesystem;
use Composer\Util\Http\Response;

/**
 * A driver implementation for driver with authentication interaction.
 *
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 */
abstract class VcsDriver implements VcsDriverInterface
{
    /** @var string */
    protected $url;
    /** @var string */
    protected $originUrl;
    /** @var array<string, mixed> */
    protected $repoConfig;
    /** @var IOInterface */
    protected $io;
    /** @var Config */
    protected $config;
    /** @var ProcessExecutor */
    protected $process;
    /** @var HttpDownloader */
    protected $httpDownloader;
    /** @var array<int|string, mixed> */
    protected $infoCache = [];
    /** @var ?Cache */
    protected $cache;

    /**
     * Constructor.
     *
     * @param array{url: string}&array<string, mixed>           $repoConfig     The repository configuration
     * @param IOInterface     $io             The IO instance
     * @param Config          $config         The composer configuration
     * @param HttpDownloader  $httpDownloader Remote Filesystem, injectable for mocking
     * @param ProcessExecutor $process        Process instance, injectable for mocking
     */
    final public function __construct(array $repoConfig, IOInterface $io, Config $config, HttpDownloader $httpDownloader, ProcessExecutor $process)
    {
        if (Filesystem::isLocalPath($repoConfig['url'])) {
            $repoConfig['url'] = Filesystem::getPlatformPath($repoConfig['url']);
        }

        $this->url = $repoConfig['url'];
        $this->originUrl = $repoConfig['url'];
        $this->repoConfig = $repoConfig;
        $this->io = $io;
        $this->config = $config;
        $this->httpDownloader = $httpDownloader;
        $this->process = $process;
    }

    /**
     * Returns whether or not the given $identifier should be cached or not.
     * @phpstan-assert-if-true !null $this->cache
     */
    protected function shouldCache(string $identifier): bool
    {
        return $this->cache && Preg::isMatch('{^[a-f0-9]{40}$}iD', $identifier);
    }

    /**
     * @inheritDoc
     */
    public function getComposerInformation(string $identifier): ?array
    {
        if (!isset($this->infoCache[$identifier])) {
            if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) {
                return $this->infoCache[$identifier] = JsonFile::parseJson($res);
            }

            $composer = $this->getBaseComposerInformation($identifier);

            if ($this->shouldCache($identifier)) {
                $this->cache->write($identifier, JsonFile::encode($composer, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES));
            }

            $this->infoCache[$identifier] = $composer;
        }

        return $this->infoCache[$identifier];
    }

    /**
     * @return array<mixed>|null
     */
    protected function getBaseComposerInformation(string $identifier): ?array
    {
        $composerFileContent = $this->getFileContent('composer.json', $identifier);

        if (!$composerFileContent) {
            return null;
        }

        $composer = JsonFile::parseJson($composerFileContent, $identifier . ':composer.json');

        if ([] === $composer || !is_array($composer)) {
            return null;
        }

        if (empty($composer['time']) && null !== ($changeDate = $this->getChangeDate($identifier))) {
            $composer['time'] = $changeDate->format(DATE_RFC3339);
        }

        return $composer;
    }

    /**
     * @inheritDoc
     */
    public function hasComposerFile(string $identifier): bool
    {
        try {
            return null !== $this->getComposerInformation($identifier);
        } catch (TransportException $e) {
        }

        return false;
    }

    /**
     * Get the https or http protocol depending on SSL support.
     *
     * Call this only if you know that the server supports both.
     *
     * @return string The correct type of protocol
     */
    protected function getScheme(): string
    {
        if (extension_loaded('openssl')) {
            return 'https';
        }

        return 'http';
    }

    /**
     * Get the remote content.
     *
     * @param string $url The URL of content
     *
     * @throws TransportException
     */
    protected function getContents(string $url): Response
    {
        $options = $this->repoConfig['options'] ?? [];

        return $this->httpDownloader->get($url, $options);
    }

    /**
     * @inheritDoc
     */
    public function cleanup(): void
    {
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository\Vcs;

use Composer\Cache;
use Composer\Config;
use Composer\Json\JsonFile;
use Composer\Pcre\Preg;
use Composer\Util\ProcessExecutor;
use Composer\Util\Filesystem;
use Composer\Util\Url;
use Composer\Util\Svn as SvnUtil;
use Composer\IO\IOInterface;
use Composer\Downloader\TransportException;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Till Klampaeckel <till@php.net>
 */
class SvnDriver extends VcsDriver
{
    /** @var string */
    protected $baseUrl;
    /** @var array<int|string, string> Map of tag name to identifier */
    protected $tags;
    /** @var array<int|string, string> Map of branch name to identifier */
    protected $branches;
    /** @var ?string */
    protected $rootIdentifier;

    /** @var string|false */
    protected $trunkPath = 'trunk';
    /** @var string */
    protected $branchesPath = 'branches';
    /** @var string */
    protected $tagsPath = 'tags';
    /** @var string */
    protected $packagePath = '';
    /** @var bool */
    protected $cacheCredentials = true;

    /**
     * @var SvnUtil
     */
    private $util;

    /**
     * @inheritDoc
     */
    public function initialize(): void
    {
        $this->url = $this->baseUrl = rtrim(self::normalizeUrl($this->url), '/');

        SvnUtil::cleanEnv();

        if (isset($this->repoConfig['trunk-path'])) {
            $this->trunkPath = $this->repoConfig['trunk-path'];
        }
        if (isset($this->repoConfig['branches-path'])) {
            $this->branchesPath = $this->repoConfig['branches-path'];
        }
        if (isset($this->repoConfig['tags-path'])) {
            $this->tagsPath = $this->repoConfig['tags-path'];
        }
        if (array_key_exists('svn-cache-credentials', $this->repoConfig)) {
            $this->cacheCredentials = (bool) $this->repoConfig['svn-cache-credentials'];
        }
        if (isset($this->repoConfig['package-path'])) {
            $this->packagePath = '/' . trim($this->repoConfig['package-path'], '/');
        }

        if (false !== ($pos = strrpos($this->url, '/' . $this->trunkPath))) {
            $this->baseUrl = substr($this->url, 0, $pos);
        }

        $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($this->baseUrl)));
        $this->cache->setReadOnly($this->config->get('cache-read-only'));

        $this->getBranches();
        $this->getTags();
    }

    /**
     * @inheritDoc
     */
    public function getRootIdentifier(): string
    {
        return $this->rootIdentifier ?: $this->trunkPath;
    }

    /**
     * @inheritDoc
     */
    public function getUrl(): string
    {
        return $this->url;
    }

    /**
     * @inheritDoc
     */
    public function getSource(string $identifier): array
    {
        return ['type' => 'svn', 'url' => $this->baseUrl, 'reference' => $identifier];
    }

    /**
     * @inheritDoc
     */
    public function getDist(string $identifier): ?array
    {
        return null;
    }

    /**
     * @inheritDoc
     */
    protected function shouldCache(string $identifier): bool
    {
        return $this->cache && Preg::isMatch('{@\d+$}', $identifier);
    }

    /**
     * @inheritDoc
     */
    public function getComposerInformation(string $identifier): ?array
    {
        if (!isset($this->infoCache[$identifier])) {
            if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier.'.json')) {
                // old cache files had '' stored instead of null due to af3783b5f40bae32a23e353eaf0a00c9b8ce82e2, so we make sure here that we always return null or array
                // and fix outdated invalid cache files
                if ($res === '""') {
                    $res = 'null';
                    $this->cache->write($identifier.'.json', $res);
                }

                return $this->infoCache[$identifier] = JsonFile::parseJson($res);
            }

            try {
                $composer = $this->getBaseComposerInformation($identifier);
            } catch (TransportException $e) {
                $message = $e->getMessage();
                if (stripos($message, 'path not found') === false && stripos($message, 'svn: warning: W160013') === false) {
                    throw $e;
                }
                // remember a not-existent composer.json
                $composer = null;
            }

            if ($this->shouldCache($identifier)) {
                $this->cache->write($identifier.'.json', JsonFile::encode($composer, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES));
            }

            $this->infoCache[$identifier] = $composer;
        }

        // old cache files had '' stored instead of null due to af3783b5f40bae32a23e353eaf0a00c9b8ce82e2, so we make sure here that we always return null or array
        if (!is_array($this->infoCache[$identifier])) {
            return null;
        }

        return $this->infoCache[$identifier];
    }

    public function getFileContent(string $file, string $identifier): ?string
    {
        $identifier = '/' . trim($identifier, '/') . '/';

        if (Preg::isMatch('{^(.+?)(@\d+)?/$}', $identifier, $match) && $match[2] !== null) {
            $path = $match[1];
            $rev = $match[2];
        } else {
            $path = $identifier;
            $rev = '';
        }

        try {
            $resource = $path.$file;
            $output = $this->execute(['svn', 'cat'], $this->baseUrl . $resource . $rev);
            if ('' === trim($output)) {
                return null;
            }
        } catch (\RuntimeException $e) {
            throw new TransportException($e->getMessage());
        }

        return $output;
    }

    /**
     * @inheritDoc
     */
    public function getChangeDate(string $identifier): ?\DateTimeImmutable
    {
        $identifier = '/' . trim($identifier, '/') . '/';

        if (Preg::isMatch('{^(.+?)(@\d+)?/$}', $identifier, $match) && null !== $match[2]) {
            $path = $match[1];
            $rev = $match[2];
        } else {
            $path = $identifier;
            $rev = '';
        }

        $output = $this->execute(['svn', 'info'], $this->baseUrl . $path . $rev);
        foreach ($this->process->splitLines($output) as $line) {
            if ($line !== '' && Preg::isMatchStrictGroups('{^Last Changed Date: ([^(]+)}', $line, $match)) {
                return new \DateTimeImmutable($match[1], new \DateTimeZone('UTC'));
            }
        }

        return null;
    }

    /**
     * @inheritDoc
     */
    public function getTags(): array
    {
        if (null === $this->tags) {
            $tags = [];

            if ($this->tagsPath !== false) {
                $output = $this->execute(['svn', 'ls', '--verbose'], $this->baseUrl . '/' . $this->tagsPath);
                if ($output !== '') {
                    $lastRev = 0;
                    foreach ($this->process->splitLines($output) as $line) {
                        $line = trim($line);
                        if ($line !== '' && Preg::isMatch('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) {
                            if ($match[2] === './') {
                                $lastRev = (int) $match[1];
                            } else {
                                $tags[rtrim($match[2], '/')] = $this->buildIdentifier(
                                    '/' . $this->tagsPath . '/' . $match[2],
                                    max($lastRev, (int) $match[1])
                                );
                            }
                        }
                    }
                }
            }

            $this->tags = $tags;
        }

        return $this->tags;
    }

    /**
     * @inheritDoc
     */
    public function getBranches(): array
    {
        if (null === $this->branches) {
            $branches = [];

            if (false === $this->trunkPath) {
                $trunkParent = $this->baseUrl . '/';
            } else {
                $trunkParent = $this->baseUrl . '/' . $this->trunkPath;
            }

            $output = $this->execute(['svn', 'ls', '--verbose'], $trunkParent);
            if ($output !== '') {
                foreach ($this->process->splitLines($output) as $line) {
                    $line = trim($line);
                    if ($line !== '' && Preg::isMatch('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) {
                        if ($match[2] === './') {
                            $branches['trunk'] = $this->buildIdentifier(
                                '/' . $this->trunkPath,
                                (int) $match[1]
                            );
                            $this->rootIdentifier = $branches['trunk'];
                            break;
                        }
                    }
                }
            }
            unset($output);

            if ($this->branchesPath !== false) {
                $output = $this->execute(['svn', 'ls', '--verbose'], $this->baseUrl . '/' . $this->branchesPath);
                if ($output !== '') {
                    $lastRev = 0;
                    foreach ($this->process->splitLines(trim($output)) as $line) {
                        $line = trim($line);
                        if ($line !== '' && Preg::isMatch('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) {
                            if ($match[2] === './') {
                                $lastRev = (int) $match[1];
                            } else {
                                $branches[rtrim($match[2], '/')] = $this->buildIdentifier(
                                    '/' . $this->branchesPath . '/' . $match[2],
                                    max($lastRev, (int) $match[1])
                                );
                            }
                        }
                    }
                }
            }

            $this->branches = $branches;
        }

        return $this->branches;
    }

    /**
     * @inheritDoc
     */
    public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool
    {
        $url = self::normalizeUrl($url);
        if (Preg::isMatch('#(^svn://|^svn\+ssh://|svn\.)#i', $url)) {
            return true;
        }

        // proceed with deep check for local urls since they are fast to process
        if (!$deep && !Filesystem::isLocalPath($url)) {
            return false;
        }

        $process = new ProcessExecutor($io);
        $exit = $process->execute(['svn', 'info', '--non-interactive', '--', $url], $ignoredOutput);

        if ($exit === 0) {
            // This is definitely a Subversion repository.
            return true;
        }

        // Subversion client 1.7 and older
        if (false !== stripos($process->getErrorOutput(), 'authorization failed:')) {
            // This is likely a remote Subversion repository that requires
            // authentication. We will handle actual authentication later.
            return true;
        }

        // Subversion client 1.8 and newer
        if (false !== stripos($process->getErrorOutput(), 'Authentication failed')) {
            // This is likely a remote Subversion or newer repository that requires
            // authentication. We will handle actual authentication later.
            return true;
        }

        return false;
    }

    /**
     * An absolute path (leading '/') is converted to a file:// url.
     */
    protected static function normalizeUrl(string $url): string
    {
        $fs = new Filesystem();
        if ($fs->isAbsolutePath($url)) {
            return 'file://' . strtr($url, '\\', '/');
        }

        return $url;
    }

    /**
     * Execute an SVN command and try to fix up the process with credentials
     * if necessary.
     *
     * @param  non-empty-list<string> $command The svn command to run.
     * @param  string            $url     The SVN URL.
     * @throws \RuntimeException
     */
    protected function execute(array $command, string $url): string
    {
        if (null === $this->util) {
            $this->util = new SvnUtil($this->baseUrl, $this->io, $this->config, $this->process);
            $this->util->setCacheCredentials($this->cacheCredentials);
        }

        try {
            return $this->util->execute($command, $url);
        } catch (\RuntimeException $e) {
            if (null === $this->util->binaryVersion()) {
                throw new \RuntimeException('Failed to load '.$this->url.', svn was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput());
            }

            throw new \RuntimeException(
                'Repository '.$this->url.' could not be processed, '.$e->getMessage()
            );
        }
    }

    /**
     * Build the identifier respecting "package-path" config option
     *
     * @param string $baseDir  The path to trunk/branch/tag
     * @param int $revision The revision mark to add to identifier
     */
    protected function buildIdentifier(string $baseDir, int $revision): string
    {
        return rtrim($baseDir, '/') . $this->packagePath . '/@' . $revision;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository\Vcs;

use Composer\Config;
use Composer\Cache;
use Composer\IO\IOInterface;
use Composer\Pcre\Preg;
use Composer\Util\ProcessExecutor;
use Composer\Util\Perforce;
use Composer\Util\Http\Response;

/**
 * @author Matt Whittom <Matt.Whittom@veteransunited.com>
 */
class PerforceDriver extends VcsDriver
{
    /** @var string */
    protected $depot;
    /** @var string */
    protected $branch;
    /** @var ?Perforce */
    protected $perforce = null;

    /**
     * @inheritDoc
     */
    public function initialize(): void
    {
        $this->depot = $this->repoConfig['depot'];
        $this->branch = '';
        if (!empty($this->repoConfig['branch'])) {
            $this->branch = $this->repoConfig['branch'];
        }

        $this->initPerforce($this->repoConfig);
        $this->perforce->p4Login();
        $this->perforce->checkStream();

        $this->perforce->writeP4ClientSpec();
        $this->perforce->connectClient();
    }

    /**
     * @param array<string, mixed> $repoConfig
     */
    private function initPerforce(array $repoConfig): void
    {
        if (!empty($this->perforce)) {
            return;
        }

        if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) {
            throw new \RuntimeException('PerforceDriver requires a usable cache directory, and it looks like you set it to be disabled');
        }

        $repoDir = $this->config->get('cache-vcs-dir') . '/' . $this->depot;
        $this->perforce = Perforce::create($repoConfig, $this->getUrl(), $repoDir, $this->process, $this->io);
    }

    /**
     * @inheritDoc
     */
    public function getFileContent(string $file, string $identifier): ?string
    {
        return $this->perforce->getFileContent($file, $identifier);
    }

    /**
     * @inheritDoc
     */
    public function getChangeDate(string $identifier): ?\DateTimeImmutable
    {
        return null;
    }

    /**
     * @inheritDoc
     */
    public function getRootIdentifier(): string
    {
        return $this->branch;
    }

    /**
     * @inheritDoc
     */
    public function getBranches(): array
    {
        return $this->perforce->getBranches();
    }

    /**
     * @inheritDoc
     */
    public function getTags(): array
    {
        return $this->perforce->getTags();
    }

    /**
     * @inheritDoc
     */
    public function getDist(string $identifier): ?array
    {
        return null;
    }

    /**
     * @inheritDoc
     */
    public function getSource(string $identifier): array
    {
        return [
            'type' => 'perforce',
            'url' => $this->repoConfig['url'],
            'reference' => $identifier,
            'p4user' => $this->perforce->getUser(),
        ];
    }

    /**
     * @inheritDoc
     */
    public function getUrl(): string
    {
        return $this->url;
    }

    /**
     * @inheritDoc
     */
    public function hasComposerFile(string $identifier): bool
    {
        $composerInfo = $this->perforce->getComposerInformation('//' . $this->depot . '/' . $identifier);

        return !empty($composerInfo);
    }

    /**
     * @inheritDoc
     */
    public function getContents(string $url): Response
    {
        throw new \BadMethodCallException('Not implemented/used in PerforceDriver');
    }

    /**
     * @inheritDoc
     */
    public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool
    {
        if ($deep || Preg::isMatch('#\b(perforce|p4)\b#i', $url)) {
            return Perforce::checkServerExists($url, new ProcessExecutor($io));
        }

        return false;
    }

    /**
     * @inheritDoc
     */
    public function cleanup(): void
    {
        $this->perforce->cleanupClientSpec();
        $this->perforce = null;
    }

    public function getDepot(): string
    {
        return $this->depot;
    }

    public function getBranch(): string
    {
        return $this->branch;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository\Vcs;

use Composer\Cache;
use Composer\Config;
use Composer\Pcre\Preg;
use Composer\Util\ProcessExecutor;
use Composer\Util\Filesystem;
use Composer\IO\IOInterface;

/**
 * @author BohwaZ <http://bohwaz.net/>
 */
class FossilDriver extends VcsDriver
{
    /** @var array<int|string, string> Map of tag name to identifier */
    protected $tags;
    /** @var array<int|string, string> Map of branch name to identifier */
    protected $branches;
    /** @var ?string */
    protected $rootIdentifier = null;
    /** @var ?string */
    protected $repoFile = null;
    /** @var string */
    protected $checkoutDir;

    /**
     * @inheritDoc
     */
    public function initialize(): void
    {
        // Make sure fossil is installed and reachable.
        $this->checkFossil();

        // Ensure we are allowed to use this URL by config.
        $this->config->prohibitUrlByConfig($this->url, $this->io);

        // Only if url points to a locally accessible directory, assume it's the checkout directory.
        // Otherwise, it should be something fossil can clone from.
        if (Filesystem::isLocalPath($this->url) && is_dir($this->url)) {
            $this->checkoutDir = $this->url;
        } else {
            if (!Cache::isUsable($this->config->get('cache-repo-dir')) || !Cache::isUsable($this->config->get('cache-vcs-dir'))) {
                throw new \RuntimeException('FossilDriver requires a usable cache directory, and it looks like you set it to be disabled');
            }

            $localName = Preg::replace('{[^a-z0-9]}i', '-', $this->url);
            $this->repoFile = $this->config->get('cache-repo-dir') . '/' . $localName . '.fossil';
            $this->checkoutDir = $this->config->get('cache-vcs-dir') . '/' . $localName . '/';

            $this->updateLocalRepo();
        }

        $this->getTags();
        $this->getBranches();
    }

    /**
     * Check that fossil can be invoked via command line.
     */
    protected function checkFossil(): void
    {
        if (0 !== $this->process->execute(['fossil', 'version'], $ignoredOutput)) {
            throw new \RuntimeException("fossil was not found, check that it is installed and in your PATH env.\n\n" . $this->process->getErrorOutput());
        }
    }

    /**
     * Clone or update existing local fossil repository.
     */
    protected function updateLocalRepo(): void
    {
        assert($this->repoFile !== null);

        $fs = new Filesystem();
        $fs->ensureDirectoryExists($this->checkoutDir);

        if (!is_writable(dirname($this->checkoutDir))) {
            throw new \RuntimeException('Can not clone '.$this->url.' to access package information. The "'.$this->checkoutDir.'" directory is not writable by the current user.');
        }

        // update the repo if it is a valid fossil repository
        if (is_file($this->repoFile) && is_dir($this->checkoutDir) && 0 === $this->process->execute(['fossil', 'info'], $output, $this->checkoutDir)) {
            if (0 !== $this->process->execute(['fossil', 'pull'], $output, $this->checkoutDir)) {
                $this->io->writeError('<error>Failed to update '.$this->url.', package information from this repository may be outdated ('.$this->process->getErrorOutput().')</error>');
            }
        } else {
            // clean up directory and do a fresh clone into it
            $fs->removeDirectory($this->checkoutDir);
            $fs->remove($this->repoFile);

            $fs->ensureDirectoryExists($this->checkoutDir);

            if (0 !== $this->process->execute(['fossil', 'clone', '--', $this->url, $this->repoFile], $output)) {
                $output = $this->process->getErrorOutput();

                throw new \RuntimeException('Failed to clone '.$this->url.' to repository ' . $this->repoFile . "\n\n" .$output);
            }

            if (0 !== $this->process->execute(['fossil', 'open', '--nested', '--', $this->repoFile], $output, $this->checkoutDir)) {
                $output = $this->process->getErrorOutput();

                throw new \RuntimeException('Failed to open repository '.$this->repoFile.' in ' . $this->checkoutDir . "\n\n" .$output);
            }
        }
    }

    /**
     * @inheritDoc
     */
    public function getRootIdentifier(): string
    {
        if (null === $this->rootIdentifier) {
            $this->rootIdentifier = 'trunk';
        }

        return $this->rootIdentifier;
    }

    /**
     * @inheritDoc
     */
    public function getUrl(): string
    {
        return $this->url;
    }

    /**
     * @inheritDoc
     */
    public function getSource(string $identifier): array
    {
        return ['type' => 'fossil', 'url' => $this->getUrl(), 'reference' => $identifier];
    }

    /**
     * @inheritDoc
     */
    public function getDist(string $identifier): ?array
    {
        return null;
    }

    /**
     * @inheritDoc
     */
    public function getFileContent(string $file, string $identifier): ?string
    {
        if (isset($identifier[0]) && $identifier[0] === '-') {
            throw new \RuntimeException('Invalid fossil identifier detected. Identifier must not start with a -, given: ' . $identifier);
        }

        $this->process->execute(['fossil', 'cat', '-r', $identifier, '--', $file], $content, $this->checkoutDir);

        if ('' === trim($content)) {
            return null;
        }

        return $content;
    }

    /**
     * @inheritDoc
     */
    public function getChangeDate(string $identifier): ?\DateTimeImmutable
    {
        $this->process->execute(['fossil', 'finfo', '-b', '-n', '1', 'composer.json'], $output, $this->checkoutDir);
        [, $date] = explode(' ', trim($output), 3);

        return new \DateTimeImmutable($date, new \DateTimeZone('UTC'));
    }

    /**
     * @inheritDoc
     */
    public function getTags(): array
    {
        if (null === $this->tags) {
            $tags = [];

            $this->process->execute(['fossil', 'tag', 'list'], $output, $this->checkoutDir);
            foreach ($this->process->splitLines($output) as $tag) {
                $tags[$tag] = $tag;
            }

            $this->tags = $tags;
        }

        return $this->tags;
    }

    /**
     * @inheritDoc
     */
    public function getBranches(): array
    {
        if (null === $this->branches) {
            $branches = [];

            $this->process->execute(['fossil', 'branch', 'list'], $output, $this->checkoutDir);
            foreach ($this->process->splitLines($output) as $branch) {
                $branch = trim(Preg::replace('/^\*/', '', trim($branch)));
                $branches[$branch] = $branch;
            }

            $this->branches = $branches;
        }

        return $this->branches;
    }

    /**
     * @inheritDoc
     */
    public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool
    {
        if (Preg::isMatch('#(^(?:https?|ssh)://(?:[^@]@)?(?:chiselapp\.com|fossil\.))#i', $url)) {
            return true;
        }

        if (Preg::isMatch('!/fossil/|\.fossil!', $url)) {
            return true;
        }

        // local filesystem
        if (Filesystem::isLocalPath($url)) {
            $url = Filesystem::getPlatformPath($url);
            if (!is_dir($url)) {
                return false;
            }

            $process = new ProcessExecutor($io);
            // check whether there is a fossil repo in that path
            if ($process->execute(['fossil', 'info'], $output, $url) === 0) {
                return true;
            }
        }

        return false;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository\Vcs;

use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Cache;
use Composer\Downloader\TransportException;
use Composer\Json\JsonFile;
use Composer\Pcre\Preg;
use Composer\Util\Bitbucket;
use Composer\Util\Http\Response;

/**
 * @author Per Bernhardt <plb@webfactory.de>
 */
class GitBitbucketDriver extends VcsDriver
{
    /** @var string */
    protected $owner;
    /** @var string */
    protected $repository;
    /** @var bool */
    private $hasIssues = false;
    /** @var ?string */
    private $rootIdentifier;
    /** @var array<int|string, string> Map of tag name to identifier */
    private $tags;
    /** @var array<int|string, string> Map of branch name to identifier */
    private $branches;
    /** @var string */
    private $branchesUrl = '';
    /** @var string */
    private $tagsUrl = '';
    /** @var string */
    private $homeUrl = '';
    /** @var string */
    private $website = '';
    /** @var string */
    private $cloneHttpsUrl = '';
    /** @var array<string, mixed> */
    private $repoData;

    /**
     * @var ?VcsDriver
     */
    protected $fallbackDriver = null;
    /** @var string|null if set either git or hg */
    private $vcsType;

    /**
     * @inheritDoc
     */
    public function initialize(): void
    {
        if (!Preg::isMatchStrictGroups('#^https?://bitbucket\.org/([^/]+)/([^/]+?)(?:\.git|/?)?$#i', $this->url, $match)) {
            throw new \InvalidArgumentException(sprintf('The Bitbucket repository URL %s is invalid. It must be the HTTPS URL of a Bitbucket repository.', $this->url));
        }

        $this->owner = $match[1];
        $this->repository = $match[2];
        $this->originUrl = 'bitbucket.org';
        $this->cache = new Cache(
            $this->io,
            implode('/', [
                $this->config->get('cache-repo-dir'),
                $this->originUrl,
                $this->owner,
                $this->repository,
            ])
        );
        $this->cache->setReadOnly($this->config->get('cache-read-only'));
    }

    /**
     * @inheritDoc
     */
    public function getUrl(): string
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getUrl();
        }

        return $this->cloneHttpsUrl;
    }

    /**
     * Attempts to fetch the repository data via the BitBucket API and
     * sets some parameters which are used in other methods
     *
     * @phpstan-impure
     */
    protected function getRepoData(): bool
    {
        $resource = sprintf(
            'https://api.bitbucket.org/2.0/repositories/%s/%s?%s',
            $this->owner,
            $this->repository,
            http_build_query(
                ['fields' => '-project,-owner'],
                '',
                '&'
            )
        );

        $repoData = $this->fetchWithOAuthCredentials($resource, true)->decodeJson();
        if ($this->fallbackDriver) {
            return false;
        }
        $this->parseCloneUrls($repoData['links']['clone']);

        $this->hasIssues = !empty($repoData['has_issues']);
        $this->branchesUrl = $repoData['links']['branches']['href'];
        $this->tagsUrl = $repoData['links']['tags']['href'];
        $this->homeUrl = $repoData['links']['html']['href'];
        $this->website = $repoData['website'];
        $this->vcsType = $repoData['scm'];

        $this->repoData = $repoData;

        return true;
    }

    /**
     * @inheritDoc
     */
    public function getComposerInformation(string $identifier): ?array
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getComposerInformation($identifier);
        }

        if (!isset($this->infoCache[$identifier])) {
            if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) {
                $composer = JsonFile::parseJson($res);
            } else {
                $composer = $this->getBaseComposerInformation($identifier);

                if ($this->shouldCache($identifier)) {
                    $this->cache->write($identifier, JsonFile::encode($composer, \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES));
                }
            }

            if ($composer !== null) {
                // specials for bitbucket
                if (isset($composer['support']) && !is_array($composer['support'])) {
                    $composer['support'] = [];
                }
                if (!isset($composer['support']['source'])) {
                    $label = array_search(
                        $identifier,
                        $this->getTags()
                    ) ?: array_search(
                        $identifier,
                        $this->getBranches()
                    ) ?: $identifier;

                    if (array_key_exists($label, $tags = $this->getTags())) {
                        $hash = $tags[$label];
                    } elseif (array_key_exists($label, $branches = $this->getBranches())) {
                        $hash = $branches[$label];
                    }

                    if (!isset($hash)) {
                        $composer['support']['source'] = sprintf(
                            'https://%s/%s/%s/src',
                            $this->originUrl,
                            $this->owner,
                            $this->repository
                        );
                    } else {
                        $composer['support']['source'] = sprintf(
                            'https://%s/%s/%s/src/%s/?at=%s',
                            $this->originUrl,
                            $this->owner,
                            $this->repository,
                            $hash,
                            $label
                        );
                    }
                }
                if (!isset($composer['support']['issues']) && $this->hasIssues) {
                    $composer['support']['issues'] = sprintf(
                        'https://%s/%s/%s/issues',
                        $this->originUrl,
                        $this->owner,
                        $this->repository
                    );
                }
                if (!isset($composer['homepage'])) {
                    $composer['homepage'] = empty($this->website) ? $this->homeUrl : $this->website;
                }
            }

            $this->infoCache[$identifier] = $composer;
        }

        return $this->infoCache[$identifier];
    }

    /**
     * @inheritDoc
     */
    public function getFileContent(string $file, string $identifier): ?string
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getFileContent($file, $identifier);
        }

        if (strpos($identifier, '/') !== false) {
            $branches = $this->getBranches();
            if (isset($branches[$identifier])) {
                $identifier = $branches[$identifier];
            }
        }

        $resource = sprintf(
            'https://api.bitbucket.org/2.0/repositories/%s/%s/src/%s/%s',
            $this->owner,
            $this->repository,
            $identifier,
            $file
        );

        return $this->fetchWithOAuthCredentials($resource)->getBody();
    }

    /**
     * @inheritDoc
     */
    public function getChangeDate(string $identifier): ?\DateTimeImmutable
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getChangeDate($identifier);
        }

        if (strpos($identifier, '/') !== false) {
            $branches = $this->getBranches();
            if (isset($branches[$identifier])) {
                $identifier = $branches[$identifier];
            }
        }

        $resource = sprintf(
            'https://api.bitbucket.org/2.0/repositories/%s/%s/commit/%s?fields=date',
            $this->owner,
            $this->repository,
            $identifier
        );
        $commit = $this->fetchWithOAuthCredentials($resource)->decodeJson();

        return new \DateTimeImmutable($commit['date']);
    }

    /**
     * @inheritDoc
     */
    public function getSource(string $identifier): array
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getSource($identifier);
        }

        return ['type' => $this->vcsType, 'url' => $this->getUrl(), 'reference' => $identifier];
    }

    /**
     * @inheritDoc
     */
    public function getDist(string $identifier): ?array
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getDist($identifier);
        }

        $url = sprintf(
            'https://bitbucket.org/%s/%s/get/%s.zip',
            $this->owner,
            $this->repository,
            $identifier
        );

        return ['type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => ''];
    }

    /**
     * @inheritDoc
     */
    public function getTags(): array
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getTags();
        }

        if (null === $this->tags) {
            $tags = [];
            $resource = sprintf(
                '%s?%s',
                $this->tagsUrl,
                http_build_query(
                    [
                        'pagelen' => 100,
                        'fields' => 'values.name,values.target.hash,next',
                        'sort' => '-target.date',
                    ],
                    '',
                    '&'
                )
            );
            $hasNext = true;
            while ($hasNext) {
                $tagsData = $this->fetchWithOAuthCredentials($resource)->decodeJson();
                foreach ($tagsData['values'] as $data) {
                    $tags[$data['name']] = $data['target']['hash'];
                }
                if (empty($tagsData['next'])) {
                    $hasNext = false;
                } else {
                    $resource = $tagsData['next'];
                }
            }

            $this->tags = $tags;
        }

        return $this->tags;
    }

    /**
     * @inheritDoc
     */
    public function getBranches(): array
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getBranches();
        }

        if (null === $this->branches) {
            $branches = [];
            $resource = sprintf(
                '%s?%s',
                $this->branchesUrl,
                http_build_query(
                    [
                        'pagelen' => 100,
                        'fields' => 'values.name,values.target.hash,values.heads,next',
                        'sort' => '-target.date',
                    ],
                    '',
                    '&'
                )
            );
            $hasNext = true;
            while ($hasNext) {
                $branchData = $this->fetchWithOAuthCredentials($resource)->decodeJson();
                foreach ($branchData['values'] as $data) {
                    $branches[$data['name']] = $data['target']['hash'];
                }
                if (empty($branchData['next'])) {
                    $hasNext = false;
                } else {
                    $resource = $branchData['next'];
                }
            }

            $this->branches = $branches;
        }

        return $this->branches;
    }

    /**
     * Get the remote content.
     *
     * @param string $url              The URL of content
     *
     * @return Response The result
     *
     * @phpstan-impure
     */
    protected function fetchWithOAuthCredentials(string $url, bool $fetchingRepoData = false): Response
    {
        try {
            return parent::getContents($url);
        } catch (TransportException $e) {
            $bitbucketUtil = new Bitbucket($this->io, $this->config, $this->process, $this->httpDownloader);

            if (in_array($e->getCode(), [403, 404], true) || (401 === $e->getCode() && strpos($e->getMessage(), 'Could not authenticate against') === 0)) {
                if (!$this->io->hasAuthentication($this->originUrl)
                    && $bitbucketUtil->authorizeOAuth($this->originUrl)
                ) {
                    return parent::getContents($url);
                }

                if (!$this->io->isInteractive() && $fetchingRepoData) {
                    $this->attemptCloneFallback();

                    return new Response(['url' => 'dummy'], 200, [], 'null');
                }
            }

            throw $e;
        }
    }

    /**
     * Generate an SSH URL
     */
    protected function generateSshUrl(): string
    {
        return 'git@' . $this->originUrl . ':' . $this->owner.'/'.$this->repository.'.git';
    }

    /**
     * @phpstan-impure
     *
     * @return true
     * @throws \RuntimeException
     */
    protected function attemptCloneFallback(): bool
    {
        try {
            $this->setupFallbackDriver($this->generateSshUrl());

            return true;
        } catch (\RuntimeException $e) {
            $this->fallbackDriver = null;

            $this->io->writeError(
                '<error>Failed to clone the ' . $this->generateSshUrl() . ' repository, try running in interactive mode'
                    . ' so that you can enter your Bitbucket OAuth consumer credentials</error>'
            );
            throw $e;
        }
    }

    protected function setupFallbackDriver(string $url): void
    {
        $this->fallbackDriver = new GitDriver(
            ['url' => $url],
            $this->io,
            $this->config,
            $this->httpDownloader,
            $this->process
        );
        $this->fallbackDriver->initialize();
    }

    /**
     * @param  array<array{name: string, href: string}> $cloneLinks
     */
    protected function parseCloneUrls(array $cloneLinks): void
    {
        foreach ($cloneLinks as $cloneLink) {
            if ($cloneLink['name'] === 'https') {
                // Format: https://(user@)bitbucket.org/{user}/{repo}
                // Strip username from URL (only present in clone URL's for private repositories)
                $this->cloneHttpsUrl = Preg::replace('/https:\/\/([^@]+@)?/', 'https://', $cloneLink['href']);
            }
        }
    }

    /**
     * @inheritDoc
     */
    public function getRootIdentifier(): string
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getRootIdentifier();
        }

        if (null === $this->rootIdentifier) {
            if (!$this->getRepoData()) {
                if (!$this->fallbackDriver) {
                    throw new \LogicException('A fallback driver should be setup if getRepoData returns false');
                }

                return $this->fallbackDriver->getRootIdentifier();
            }

            if ($this->vcsType !== 'git') {
                throw new \RuntimeException(
                    $this->url.' does not appear to be a git repository, use '.
                    $this->cloneHttpsUrl.' but remember that Bitbucket no longer supports the mercurial repositories. '.
                    'https://bitbucket.org/blog/sunsetting-mercurial-support-in-bitbucket'
                );
            }

            $this->rootIdentifier = $this->repoData['mainbranch']['name'] ?? 'master';
        }

        return $this->rootIdentifier;
    }

    /**
     * @inheritDoc
     */
    public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool
    {
        if (!Preg::isMatch('#^https?://bitbucket\.org/([^/]+)/([^/]+?)(\.git|/?)?$#i', $url)) {
            return false;
        }

        if (!extension_loaded('openssl')) {
            $io->writeError('Skipping Bitbucket git driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE);

            return false;
        }

        return true;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

/**
 * Configurable repository interface.
 *
 * @author Lukas Homza <lukashomz@gmail.com>
 */
interface ConfigurableRepositoryInterface
{
    /**
     * @return mixed[]
     */
    public function getRepoConfig();
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
use Composer\Package\BasePackage;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Loader\LoaderInterface;
use Composer\Util\Platform;
use Composer\Util\Tar;
use Composer\Util\Zip;

/**
 * @author Serge Smertin <serg.smertin@gmail.com>
 */
class ArtifactRepository extends ArrayRepository implements ConfigurableRepositoryInterface
{
    /** @var LoaderInterface */
    protected $loader;

    /** @var string */
    protected $lookup;
    /** @var array{url: string} */
    protected $repoConfig;
    /** @var IOInterface */
    private $io;

    /**
     * @param array{url: string} $repoConfig
     */
    public function __construct(array $repoConfig, IOInterface $io)
    {
        parent::__construct();
        if (!extension_loaded('zip')) {
            throw new \RuntimeException('The artifact repository requires PHP\'s zip extension');
        }

        $this->loader = new ArrayLoader();
        $this->lookup = Platform::expandPath($repoConfig['url']);
        $this->io = $io;
        $this->repoConfig = $repoConfig;
    }

    public function getRepoName()
    {
        return 'artifact repo ('.$this->lookup.')';
    }

    public function getRepoConfig()
    {
        return $this->repoConfig;
    }

    protected function initialize()
    {
        parent::initialize();

        $this->scanDirectory($this->lookup);
    }

    private function scanDirectory(string $path): void
    {
        $io = $this->io;

        $directory = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS);
        $iterator = new \RecursiveIteratorIterator($directory);
        $regex = new \RegexIterator($iterator, '/^.+\.(zip|tar|gz|tgz)$/i');
        foreach ($regex as $file) {
            /* @var $file \SplFileInfo */
            if (!$file->isFile()) {
                continue;
            }

            $package = $this->getComposerInformation($file);
            if (!$package) {
                $io->writeError("File <comment>{$file->getBasename()}</comment> doesn't seem to hold a package", true, IOInterface::VERBOSE);
                continue;
            }

            $template = 'Found package <info>%s</info> (<comment>%s</comment>) in file <info>%s</info>';
            $io->writeError(sprintf($template, $package->getName(), $package->getPrettyVersion(), $file->getBasename()), true, IOInterface::VERBOSE);

            $this->addPackage($package);
        }
    }

    private function getComposerInformation(\SplFileInfo $file): ?BasePackage
    {
        $json = null;
        $fileType = null;
        $fileExtension = pathinfo($file->getPathname(), PATHINFO_EXTENSION);
        if (in_array($fileExtension, ['gz', 'tar', 'tgz'], true)) {
            $fileType = 'tar';
        } elseif ($fileExtension === 'zip') {
            $fileType = 'zip';
        } else {
            throw new \RuntimeException('Files with "'.$fileExtension.'" extensions aren\'t supported. Only ZIP and TAR/TAR.GZ/TGZ archives are supported.');
        }

        try {
            if ($fileType === 'tar') {
                $json = Tar::getComposerJson($file->getPathname());
            } else {
                $json = Zip::getComposerJson($file->getPathname());
            }
        } catch (\Exception $exception) {
            $this->io->write('Failed loading package '.$file->getPathname().': '.$exception->getMessage(), false, IOInterface::VERBOSE);
        }

        if (null === $json) {
            return null;
        }

        $package = JsonFile::parseJson($json, $file->getPathname().'#composer.json');
        $package['dist'] = [
            'type' => $fileType,
            'url' => strtr($file->getPathname(), '\\', '/'),
            'shasum' => hash_file('sha1', $file->getRealPath()),
        ];

        try {
            $package = $this->loader->load($package);
        } catch (\UnexpectedValueException $e) {
            throw new \UnexpectedValueException('Failed loading package in '.$file.': '.$e->getMessage(), 0, $e);
        }

        return $package;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

/**
 * Thrown when a security problem, like a broken or missing signature
 *
 * @author Eric Daspet <edaspet@survol.fr>
 */
class RepositorySecurityException extends \Exception
{
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\DependencyResolver\PoolOptimizer;
use Composer\DependencyResolver\Pool;
use Composer\DependencyResolver\PoolBuilder;
use Composer\DependencyResolver\Request;
use Composer\DependencyResolver\SecurityAdvisoryPoolFilter;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Advisory\SecurityAdvisory;
use Composer\Advisory\PartialSecurityAdvisory;
use Composer\IO\IOInterface;
use Composer\IO\NullIO;
use Composer\Package\BasePackage;
use Composer\Package\AliasPackage;
use Composer\Package\CompleteAliasPackage;
use Composer\Package\CompletePackage;
use Composer\Package\PackageInterface;
use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Package\Version\StabilityFilter;
use Composer\Semver\Constraint\MatchAllConstraint;
use Composer\Semver\Constraint\MultiConstraint;

/**
 * @author Nils Adermann <naderman@naderman.de>
 *
 * @see RepositoryUtils for ways to work with single repos
 */
class RepositorySet
{
    /**
     * Packages are returned even though their stability does not match the required stability
     */
    public const ALLOW_UNACCEPTABLE_STABILITIES = 1;
    /**
     * Packages will be looked up in all repositories, even after they have been found in a higher prio one
     */
    public const ALLOW_SHADOWED_REPOSITORIES = 2;

    /**
     * @var array[]
     * @phpstan-var array<string, array<string, array{alias: string, alias_normalized: string}>>
     */
    private $rootAliases;

    /**
     * @var string[]
     * @phpstan-var array<string, string>
     */
    private $rootReferences;

    /** @var RepositoryInterface[] */
    private $repositories = [];

    /**
     * @var int[] array of stability => BasePackage::STABILITY_* value
     * @phpstan-var array<key-of<BasePackage::STABILITIES>, BasePackage::STABILITY_*>
     */
    private $acceptableStabilities;

    /**
     * @var int[] array of package name => BasePackage::STABILITY_* value
     * @phpstan-var array<string, BasePackage::STABILITY_*>
     */
    private $stabilityFlags;

    /**
     * @var ConstraintInterface[]
     * @phpstan-var array<string, ConstraintInterface>
     */
    private $rootRequires;

    /**
     * @var array<string, ConstraintInterface>
     */
    private $temporaryConstraints;

    /** @var bool */
    private $locked = false;
    /** @var bool */
    private $allowInstalledRepositories = false;

    /**
     * In most cases if you are looking to use this class as a way to find packages from repositories
     * passing minimumStability is all you need to worry about. The rest is for advanced pool creation including
     * aliases, pinned references and other special cases.
     *
     * @param key-of<BasePackage::STABILITIES> $minimumStability
     * @param int[]  $stabilityFlags   an array of package name => BasePackage::STABILITY_* value
     * @phpstan-param array<string, BasePackage::STABILITY_*> $stabilityFlags
     * @param array[] $rootAliases
     * @phpstan-param list<array{package: string, version: string, alias: string, alias_normalized: string}> $rootAliases
     * @param string[] $rootReferences an array of package name => source reference
     * @phpstan-param array<string, string> $rootReferences
     * @param ConstraintInterface[] $rootRequires an array of package name => constraint from the root package
     * @phpstan-param array<string, ConstraintInterface> $rootRequires
     * @param array<string, ConstraintInterface> $temporaryConstraints Runtime temporary constraints that will be used to filter packages
     */
    public function __construct(string $minimumStability = 'stable', array $stabilityFlags = [], array $rootAliases = [], array $rootReferences = [], array $rootRequires = [], array $temporaryConstraints = [])
    {
        $this->rootAliases = self::getRootAliasesPerPackage($rootAliases);
        $this->rootReferences = $rootReferences;

        $this->acceptableStabilities = [];
        foreach (BasePackage::STABILITIES as $stability => $value) {
            if ($value <= BasePackage::STABILITIES[$minimumStability]) {
                $this->acceptableStabilities[$stability] = $value;
            }
        }
        $this->stabilityFlags = $stabilityFlags;
        $this->rootRequires = $rootRequires;
        foreach ($rootRequires as $name => $constraint) {
            if (PlatformRepository::isPlatformPackage($name)) {
                unset($this->rootRequires[$name]);
            }
        }

        $this->temporaryConstraints = $temporaryConstraints;
    }

    public function allowInstalledRepositories(bool $allow = true): void
    {
        $this->allowInstalledRepositories = $allow;
    }

    /**
     * @return ConstraintInterface[] an array of package name => constraint from the root package, platform requirements excluded
     * @phpstan-return array<string, ConstraintInterface>
     */
    public function getRootRequires(): array
    {
        return $this->rootRequires;
    }

    /**
     * @return array<string, ConstraintInterface> Runtime temporary constraints that will be used to filter packages
     */
    public function getTemporaryConstraints(): array
    {
        return $this->temporaryConstraints;
    }

    /**
     * Adds a repository to this repository set
     *
     * The first repos added have a higher priority. As soon as a package is found in any
     * repository the search for that package ends, and following repos will not be consulted.
     *
     * @param RepositoryInterface $repo A package repository
     */
    public function addRepository(RepositoryInterface $repo): void
    {
        if ($this->locked) {
            throw new \RuntimeException("Pool has already been created from this repository set, it cannot be modified anymore.");
        }

        if ($repo instanceof CompositeRepository) {
            $repos = $repo->getRepositories();
        } else {
            $repos = [$repo];
        }

        foreach ($repos as $repo) {
            $this->repositories[] = $repo;
        }
    }

    /**
     * Find packages providing or matching a name and optionally meeting a constraint in all repositories
     *
     * Returned in the order of repositories, matching priority
     *
     * @param  int                      $flags      any of the ALLOW_* constants from this class to tweak what is returned
     * @return BasePackage[]
     */
    public function findPackages(string $name, ?ConstraintInterface $constraint = null, int $flags = 0): array
    {
        $ignoreStability = ($flags & self::ALLOW_UNACCEPTABLE_STABILITIES) !== 0;
        $loadFromAllRepos = ($flags & self::ALLOW_SHADOWED_REPOSITORIES) !== 0;

        $packages = [];
        if ($loadFromAllRepos) {
            foreach ($this->repositories as $repository) {
                $packages[] = $repository->findPackages($name, $constraint) ?: [];
            }
        } else {
            foreach ($this->repositories as $repository) {
                $result = $repository->loadPackages([$name => $constraint], $ignoreStability ? BasePackage::STABILITIES : $this->acceptableStabilities, $ignoreStability ? [] : $this->stabilityFlags);

                $packages[] = $result['packages'];
                foreach ($result['namesFound'] as $nameFound) {
                    // avoid loading the same package again from other repositories once it has been found
                    if ($name === $nameFound) {
                        break 2;
                    }
                }
            }
        }

        $candidates = $packages ? array_merge(...$packages) : [];

        // when using loadPackages above (!$loadFromAllRepos) the repos already filter for stability so no need to do it again
        if ($ignoreStability || !$loadFromAllRepos) {
            return $candidates;
        }

        $result = [];
        foreach ($candidates as $candidate) {
            if ($this->isPackageAcceptable($candidate->getNames(), $candidate->getStability())) {
                $result[] = $candidate;
            }
        }

        return $result;
    }

    /**
     * @param string[] $packageNames
     * @return ($allowPartialAdvisories is true ? array{advisories: array<string, array<PartialSecurityAdvisory|SecurityAdvisory>>, unreachableRepos: array<string>} : array{advisories: array<string, array<SecurityAdvisory>>, unreachableRepos: array<string>})
     */
    public function getSecurityAdvisories(array $packageNames, bool $allowPartialAdvisories, bool $ignoreUnreachable = false): array
    {
        $map = [];
        foreach ($packageNames as $name) {
            $map[$name] = new MatchAllConstraint();
        }

        $unreachableRepos = [];
        $advisories = $this->getSecurityAdvisoriesForConstraints($map, $allowPartialAdvisories, $ignoreUnreachable, $unreachableRepos);

        return ['advisories' => $advisories, 'unreachableRepos' => $unreachableRepos];
    }

    /**
     * @param PackageInterface[] $packages
     * @return ($allowPartialAdvisories is true ? array{advisories: array<string, array<PartialSecurityAdvisory|SecurityAdvisory>>, unreachableRepos: array<string>} : array{advisories: array<string, array<SecurityAdvisory>>, unreachableRepos: array<string>})
     */
    public function getMatchingSecurityAdvisories(array $packages, bool $allowPartialAdvisories = false, bool $ignoreUnreachable = false): array
    {
        $map = [];
        foreach ($packages as $package) {
            // ignore root alias versions as they are not actual package versions and should not matter when it comes to vulnerabilities
            if ($package instanceof AliasPackage && $package->isRootPackageAlias()) {
                continue;
            }
            if (isset($map[$package->getName()])) {
                $map[$package->getName()] = new MultiConstraint([new Constraint('=', $package->getVersion()), $map[$package->getName()]], false);
            } else {
                $map[$package->getName()] = new Constraint('=', $package->getVersion());
            }
        }

        $unreachableRepos = [];
        $advisories = $this->getSecurityAdvisoriesForConstraints($map, $allowPartialAdvisories, $ignoreUnreachable, $unreachableRepos);

        return ['advisories' => $advisories, 'unreachableRepos' => $unreachableRepos];
    }

    /**
     * @param array<string, ConstraintInterface> $packageConstraintMap
     * @param array<string> &$unreachableRepos Array to store messages about unreachable repositories
     * @return ($allowPartialAdvisories is true ? array<string, array<PartialSecurityAdvisory|SecurityAdvisory>> : array<string, array<SecurityAdvisory>>)
     */
    private function getSecurityAdvisoriesForConstraints(array $packageConstraintMap, bool $allowPartialAdvisories, bool $ignoreUnreachable = false, array &$unreachableRepos = []): array
    {
        $repoAdvisories = [];
        foreach ($this->repositories as $repository) {
            try {
                if (!$repository instanceof AdvisoryProviderInterface || !$repository->hasSecurityAdvisories()) {
                    continue;
                }

                $repoAdvisories[] = $repository->getSecurityAdvisories($packageConstraintMap, $allowPartialAdvisories)['advisories'];
            } catch (\Composer\Downloader\TransportException $e) {
                if (!$ignoreUnreachable) {
                    throw $e;
                }
                $unreachableRepos[] = $e->getMessage();
            }
        }

        $advisories = count($repoAdvisories) > 0 ? array_merge_recursive([], ...$repoAdvisories) : [];
        ksort($advisories);

        return $advisories;
    }

    /**
     * @return array[] an array with the provider name as key and value of array('name' => '...', 'description' => '...', 'type' => '...')
     * @phpstan-return array<string, array{name: string, description: string|null, type: string}>
     */
    public function getProviders(string $packageName): array
    {
        $providers = [];
        foreach ($this->repositories as $repository) {
            if ($repoProviders = $repository->getProviders($packageName)) {
                $providers = array_merge($providers, $repoProviders);
            }
        }

        return $providers;
    }

    /**
     * Check for each given package name whether it would be accepted by this RepositorySet in the given $stability
     *
     * @param string[] $names
     * @param key-of<BasePackage::STABILITIES> $stability one of 'stable', 'RC', 'beta', 'alpha' or 'dev'
     */
    public function isPackageAcceptable(array $names, string $stability): bool
    {
        return StabilityFilter::isPackageAcceptable($this->acceptableStabilities, $this->stabilityFlags, $names, $stability);
    }

    /**
     * Create a pool for dependency resolution from the packages in this repository set.
     *
     * @param list<string>      $ignoredTypes Packages of those types are ignored
     * @param list<string>|null $allowedTypes Only packages of those types are allowed if set to non-null
     */
    public function createPool(Request $request, IOInterface $io, ?EventDispatcher $eventDispatcher = null, ?PoolOptimizer $poolOptimizer = null, array $ignoredTypes = [], ?array $allowedTypes = null, ?SecurityAdvisoryPoolFilter $securityAdvisoryPoolFilter = null): Pool
    {
        $poolBuilder = new PoolBuilder($this->acceptableStabilities, $this->stabilityFlags, $this->rootAliases, $this->rootReferences, $io, $eventDispatcher, $poolOptimizer, $this->temporaryConstraints, $securityAdvisoryPoolFilter);
        $poolBuilder->setIgnoredTypes($ignoredTypes);
        $poolBuilder->setAllowedTypes($allowedTypes);

        foreach ($this->repositories as $repo) {
            if (($repo instanceof InstalledRepositoryInterface || $repo instanceof InstalledRepository) && !$this->allowInstalledRepositories) {
                throw new \LogicException('The pool can not accept packages from an installed repository');
            }
        }

        $this->locked = true;

        return $poolBuilder->buildPool($this->repositories, $request);
    }

    /**
     * Create a pool for dependency resolution from the packages in this repository set.
     */
    public function createPoolWithAllPackages(): Pool
    {
        foreach ($this->repositories as $repo) {
            if (($repo instanceof InstalledRepositoryInterface || $repo instanceof InstalledRepository) && !$this->allowInstalledRepositories) {
                throw new \LogicException('The pool can not accept packages from an installed repository');
            }
        }

        $this->locked = true;

        $packages = [];
        foreach ($this->repositories as $repository) {
            foreach ($repository->getPackages() as $package) {
                $packages[] = $package;

                if (isset($this->rootAliases[$package->getName()][$package->getVersion()])) {
                    $alias = $this->rootAliases[$package->getName()][$package->getVersion()];
                    while ($package instanceof AliasPackage) {
                        $package = $package->getAliasOf();
                    }
                    if ($package instanceof CompletePackage) {
                        $aliasPackage = new CompleteAliasPackage($package, $alias['alias_normalized'], $alias['alias']);
                    } else {
                        $aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias']);
                    }
                    $aliasPackage->setRootPackageAlias(true);
                    $packages[] = $aliasPackage;
                }
            }
        }

        return new Pool($packages);
    }

    public function createPoolForPackage(string $packageName, ?LockArrayRepository $lockedRepo = null): Pool
    {
        // TODO unify this with above in some simpler version without "request"?
        return $this->createPoolForPackages([$packageName], $lockedRepo);
    }

    /**
     * @param string[] $packageNames
     */
    public function createPoolForPackages(array $packageNames, ?LockArrayRepository $lockedRepo = null): Pool
    {
        $request = new Request($lockedRepo);

        $allowedPackages = [];
        foreach ($packageNames as $packageName) {
            if (PlatformRepository::isPlatformPackage($packageName)) {
                throw new \LogicException('createPoolForPackage(s) can not be used for platform packages, as they are never loaded by the PoolBuilder which expects them to be fixed. Use createPoolWithAllPackages or pass in a proper request with the platform packages you need fixed in it.');
            }

            $request->requireName($packageName);
            $allowedPackages[] = strtolower($packageName);
        }

        if (count($allowedPackages) > 0) {
            $request->restrictPackages($allowedPackages);
        }

        return $this->createPool($request, new NullIO());
    }

    /**
     * @param array[] $aliases
     * @phpstan-param list<array{package: string, version: string, alias: string, alias_normalized: string}> $aliases
     *
     * @return array<string, array<string, array{alias: string, alias_normalized: string}>>
     */
    private static function getRootAliasesPerPackage(array $aliases): array
    {
        $normalizedAliases = [];

        foreach ($aliases as $alias) {
            $normalizedAliases[$alias['package']][$alias['version']] = [
                'alias' => $alias['alias'],
                'alias_normalized' => $alias['alias_normalized'],
            ];
        }

        return $normalizedAliases;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Package\PackageInterface;
use Composer\Package\BasePackage;
use Composer\Pcre\Preg;

/**
 * Filters which packages are seen as canonical on this repo by loadPackages
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class FilterRepository implements RepositoryInterface, AdvisoryProviderInterface
{
    /** @var ?string */
    private $only = null;
    /** @var ?non-empty-string */
    private $exclude = null;
    /** @var bool */
    private $canonical = true;
    /** @var RepositoryInterface */
    private $repo;

    /**
     * @param array{only?: array<string>, exclude?: array<string>, canonical?: bool} $options
     */
    public function __construct(RepositoryInterface $repo, array $options)
    {
        if (isset($options['only'])) {
            if (!is_array($options['only'])) {
                throw new \InvalidArgumentException('"only" key for repository '.$repo->getRepoName().' should be an array');
            }
            $this->only = BasePackage::packageNamesToRegexp($options['only']);
        }
        if (isset($options['exclude'])) {
            if (!is_array($options['exclude'])) {
                throw new \InvalidArgumentException('"exclude" key for repository '.$repo->getRepoName().' should be an array');
            }
            $this->exclude = BasePackage::packageNamesToRegexp($options['exclude']);
        }
        if ($this->exclude && $this->only) {
            throw new \InvalidArgumentException('Only one of "only" and "exclude" can be specified for repository '.$repo->getRepoName());
        }
        if (isset($options['canonical'])) {
            if (!is_bool($options['canonical'])) {
                throw new \InvalidArgumentException('"canonical" key for repository '.$repo->getRepoName().' should be a boolean');
            }
            $this->canonical = $options['canonical'];
        }

        $this->repo = $repo;
    }

    public function getRepoName(): string
    {
        return $this->repo->getRepoName();
    }

    /**
     * Returns the wrapped repositories
     */
    public function getRepository(): RepositoryInterface
    {
        return $this->repo;
    }

    /**
     * @inheritDoc
     */
    public function hasPackage(PackageInterface $package): bool
    {
        return $this->repo->hasPackage($package);
    }

    /**
     * @inheritDoc
     */
    public function findPackage($name, $constraint): ?BasePackage
    {
        if (!$this->isAllowed($name)) {
            return null;
        }

        return $this->repo->findPackage($name, $constraint);
    }

    /**
     * @inheritDoc
     */
    public function findPackages($name, $constraint = null): array
    {
        if (!$this->isAllowed($name)) {
            return [];
        }

        return $this->repo->findPackages($name, $constraint);
    }

    /**
     * @inheritDoc
     */
    public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = []): array
    {
        foreach ($packageNameMap as $name => $constraint) {
            if (!$this->isAllowed($name)) {
                unset($packageNameMap[$name]);
            }
        }

        if (!$packageNameMap) {
            return ['namesFound' => [], 'packages' => []];
        }

        $result = $this->repo->loadPackages($packageNameMap, $acceptableStabilities, $stabilityFlags, $alreadyLoaded);
        if (!$this->canonical) {
            $result['namesFound'] = [];
        }

        return $result;
    }

    /**
     * @inheritDoc
     */
    public function search(string $query, int $mode = 0, ?string $type = null): array
    {
        $result = [];

        foreach ($this->repo->search($query, $mode, $type) as $package) {
            if ($this->isAllowed($package['name'])) {
                $result[] = $package;
            }
        }

        return $result;
    }

    /**
     * @inheritDoc
     */
    public function getPackages(): array
    {
        $result = [];
        foreach ($this->repo->getPackages() as $package) {
            if ($this->isAllowed($package->getName())) {
                $result[] = $package;
            }
        }

        return $result;
    }

    /**
     * @inheritDoc
     */
    public function getProviders($packageName): array
    {
        $result = [];
        foreach ($this->repo->getProviders($packageName) as $name => $provider) {
            if ($this->isAllowed($provider['name'])) {
                $result[$name] = $provider;
            }
        }

        return $result;
    }

    /**
     * @inheritDoc
     */
    public function count(): int
    {
        if ($this->repo->count() > 0) {
            return count($this->getPackages());
        }

        return 0;
    }

    public function hasSecurityAdvisories(): bool
    {
        if (!$this->repo instanceof AdvisoryProviderInterface) {
            return false;
        }

        return $this->repo->hasSecurityAdvisories();
    }

    /**
     * @inheritDoc
     */
    public function getSecurityAdvisories(array $packageConstraintMap, bool $allowPartialAdvisories = false): array
    {
        if (!$this->repo instanceof AdvisoryProviderInterface) {
            return ['namesFound' => [], 'advisories' => []];
        }

        foreach ($packageConstraintMap as $name => $constraint) {
            if (!$this->isAllowed($name)) {
                unset($packageConstraintMap[$name]);
            }
        }

        return $this->repo->getSecurityAdvisories($packageConstraintMap, $allowPartialAdvisories);
    }

    private function isAllowed(string $name): bool
    {
        if (!$this->only && !$this->exclude) {
            return true;
        }

        if ($this->only) {
            return Preg::isMatch($this->only, $name);
        }

        if ($this->exclude === null) {
            return true;
        }

        return !Preg::isMatch($this->exclude, $name);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Advisory\PartialSecurityAdvisory;
use Composer\Advisory\SecurityAdvisory;

/**
 * Repositories that allow fetching security advisory data
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @internal
 */
interface AdvisoryProviderInterface
{
    public function hasSecurityAdvisories(): bool;

    /**
     * @param array<string, ConstraintInterface> $packageConstraintMap Map of package name to constraint (can be MatchAllConstraint to fetch all advisories)
     * @return ($allowPartialAdvisories is true ? array{namesFound: string[], advisories: array<string, list<PartialSecurityAdvisory|SecurityAdvisory>>} : array{namesFound: string[], advisories: array<string, list<SecurityAdvisory>>})
     */
    public function getSecurityAdvisories(array $packageConstraintMap, bool $allowPartialAdvisories = false): array;
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Downloader\TransportException;
use Composer\Pcre\Preg;
use Composer\Repository\Vcs\VcsDriverInterface;
use Composer\Package\Version\VersionParser;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Loader\ValidatingArrayLoader;
use Composer\Package\Loader\InvalidPackageException;
use Composer\Package\Loader\LoaderInterface;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Util\HttpDownloader;
use Composer\Util\Url;
use Composer\Semver\Constraint\Constraint;
use Composer\IO\IOInterface;
use Composer\Config;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInterface
{
    /** @var string */
    protected $url;
    /** @var ?string */
    protected $packageName;
    /** @var bool */
    protected $isVerbose;
    /** @var bool */
    protected $isVeryVerbose;
    /** @var IOInterface */
    protected $io;
    /** @var Config */
    protected $config;
    /** @var VersionParser */
    protected $versionParser;
    /** @var string */
    protected $type;
    /** @var ?LoaderInterface */
    protected $loader;
    /** @var array<string, mixed> */
    protected $repoConfig;
    /** @var HttpDownloader */
    protected $httpDownloader;
    /** @var ProcessExecutor */
    protected $processExecutor;
    /** @var bool */
    protected $branchErrorOccurred = false;
    /** @var array<string, class-string<VcsDriverInterface>> */
    private $drivers;
    /** @var ?VcsDriverInterface */
    private $driver;
    /** @var ?VersionCacheInterface */
    private $versionCache;
    /** @var list<string> */
    private $emptyReferences = [];
    /** @var array<'tags'|'branches', array<string, TransportException>> */
    private $versionTransportExceptions = [];

    /**
     * @param array{url: string, type?: string}&array<string, mixed> $repoConfig
     * @param array<string, class-string<VcsDriverInterface>>|null $drivers
     */
    public function __construct(array $repoConfig, IOInterface $io, Config $config, HttpDownloader $httpDownloader, ?EventDispatcher $dispatcher = null, ?ProcessExecutor $process = null, ?array $drivers = null, ?VersionCacheInterface $versionCache = null)
    {
        parent::__construct();
        $this->drivers = $drivers ?: [
            'github' => 'Composer\Repository\Vcs\GitHubDriver',
            'gitlab' => 'Composer\Repository\Vcs\GitLabDriver',
            'bitbucket' => 'Composer\Repository\Vcs\GitBitbucketDriver',
            'git-bitbucket' => 'Composer\Repository\Vcs\GitBitbucketDriver',
            'forgejo' => 'Composer\Repository\Vcs\ForgejoDriver',
            'git' => 'Composer\Repository\Vcs\GitDriver',
            'hg' => 'Composer\Repository\Vcs\HgDriver',
            'perforce' => 'Composer\Repository\Vcs\PerforceDriver',
            'fossil' => 'Composer\Repository\Vcs\FossilDriver',
            // svn must be last because identifying a subversion server for sure is practically impossible
            'svn' => 'Composer\Repository\Vcs\SvnDriver',
        ];

        $this->url = $repoConfig['url'] = Platform::expandPath($repoConfig['url']);
        $this->io = $io;
        $this->type = $repoConfig['type'] ?? 'vcs';
        $this->isVerbose = $io->isVerbose();
        $this->isVeryVerbose = $io->isVeryVerbose();
        $this->config = $config;
        $this->repoConfig = $repoConfig;
        $this->versionCache = $versionCache;
        $this->httpDownloader = $httpDownloader;
        $this->processExecutor = $process ?? new ProcessExecutor($io);
    }

    public function getRepoName()
    {
        $driverClass = get_class($this->getDriver());
        $driverType = array_search($driverClass, $this->drivers);
        if (!$driverType) {
            $driverType = $driverClass;
        }

        return 'vcs repo ('.$driverType.' '.Url::sanitize($this->url).')';
    }

    public function getRepoConfig()
    {
        return $this->repoConfig;
    }

    public function setLoader(LoaderInterface $loader): void
    {
        $this->loader = $loader;
    }

    public function getDriver(): ?VcsDriverInterface
    {
        if ($this->driver) {
            return $this->driver;
        }

        if (isset($this->drivers[$this->type])) {
            $class = $this->drivers[$this->type];
            $this->driver = new $class($this->repoConfig, $this->io, $this->config, $this->httpDownloader, $this->processExecutor);
            $this->driver->initialize();

            return $this->driver;
        }

        foreach ($this->drivers as $driver) {
            if ($driver::supports($this->io, $this->config, $this->url)) {
                $this->driver = new $driver($this->repoConfig, $this->io, $this->config, $this->httpDownloader, $this->processExecutor);
                $this->driver->initialize();

                return $this->driver;
            }
        }

        foreach ($this->drivers as $driver) {
            if ($driver::supports($this->io, $this->config, $this->url, true)) {
                $this->driver = new $driver($this->repoConfig, $this->io, $this->config, $this->httpDownloader, $this->processExecutor);
                $this->driver->initialize();

                return $this->driver;
            }
        }

        return null;
    }

    public function hadInvalidBranches(): bool
    {
        return $this->branchErrorOccurred;
    }

    /**
     * @return list<string>
     */
    public function getEmptyReferences(): array
    {
        return $this->emptyReferences;
    }

    /**
     * @return array<'tags'|'branches', array<string, TransportException>>
     */
    public function getVersionTransportExceptions(): array
    {
        return $this->versionTransportExceptions;
    }

    protected function initialize()
    {
        parent::initialize();

        $isVerbose = $this->isVerbose;
        $isVeryVerbose = $this->isVeryVerbose;

        $driver = $this->getDriver();
        if (!$driver) {
            throw new \InvalidArgumentException('No driver found to handle VCS repository '.$this->url);
        }

        $this->versionParser = new VersionParser;
        if (!$this->loader) {
            $this->loader = new ArrayLoader($this->versionParser);
        }

        $hasRootIdentifierComposerJson = false;
        try {
            $hasRootIdentifierComposerJson = $driver->hasComposerFile($driver->getRootIdentifier());
            if ($hasRootIdentifierComposerJson) {
                $data = $driver->getComposerInformation($driver->getRootIdentifier());
                $this->packageName = !empty($data['name']) ? $data['name'] : null;
            }
        } catch (\Exception $e) {
            if ($e instanceof TransportException && $this->shouldRethrowTransportException($e)) {
                throw $e;
            }

            if ($isVeryVerbose) {
                $this->io->writeError('<error>Skipped parsing '.$driver->getRootIdentifier().', '.$e->getMessage().'</error>');
            }
        }

        foreach ($driver->getTags() as $tag => $identifier) {
            $tag = (string) $tag;
            $msg = 'Reading composer.json of <info>' . ($this->packageName ?: $this->url) . '</info> (<comment>' . $tag . '</comment>)';

            // strip the release- prefix from tags if present
            $tag = str_replace('release-', '', $tag);

            $cachedPackage = $this->getCachedPackageVersion($tag, $identifier, $isVerbose, $isVeryVerbose);
            if ($cachedPackage) {
                $this->addPackage($cachedPackage);

                continue;
            }
            if ($cachedPackage === false) {
                $this->emptyReferences[] = $identifier;

                continue;
            }

            if (!$parsedTag = $this->validateTag($tag)) {
                if ($isVeryVerbose) {
                    $this->io->writeError('<warning>Skipped tag '.$tag.', invalid tag name</warning>');
                }
                continue;
            }

            if ($isVeryVerbose) {
                $this->io->writeError($msg);
            } elseif ($isVerbose) {
                $this->io->overwriteError($msg, false);
            }

            try {
                $data = $driver->getComposerInformation($identifier);
                if (null === $data) {
                    if ($isVeryVerbose) {
                        $this->io->writeError('<warning>Skipped tag '.$tag.', no composer file</warning>');
                    }
                    $this->emptyReferences[] = $identifier;
                    continue;
                }

                // manually versioned package
                if (isset($data['version'])) {
                    $data['version_normalized'] = $this->versionParser->normalize($data['version']);
                } else {
                    // auto-versioned package, read value from tag
                    $data['version'] = $tag;
                    $data['version_normalized'] = $parsedTag;
                }

                // make sure tag packages have no -dev flag
                $data['version'] = Preg::replace('{[.-]?dev$}i', '', $data['version']);
                $data['version_normalized'] = Preg::replace('{(^dev-|[.-]?dev$)}i', '', $data['version_normalized']);

                // make sure tag do not contain the default-branch marker
                unset($data['default-branch']);

                // broken package, version doesn't match tag
                if ($data['version_normalized'] !== $parsedTag) {
                    if ($isVeryVerbose) {
                        if (Preg::isMatch('{(^dev-|[.-]?dev$)}i', $parsedTag)) {
                            $this->io->writeError('<warning>Skipped tag '.$tag.', invalid tag name, tags can not use dev prefixes or suffixes</warning>');
                        } else {
                            $this->io->writeError('<warning>Skipped tag '.$tag.', tag ('.$parsedTag.') does not match version ('.$data['version_normalized'].') in composer.json</warning>');
                        }
                    }
                    continue;
                }

                $tagPackageName = $this->packageName ?: ($data['name'] ?? '');
                if ($existingPackage = $this->findPackage($tagPackageName, $data['version_normalized'])) {
                    if ($isVeryVerbose) {
                        $this->io->writeError('<warning>Skipped tag '.$tag.', it conflicts with an another tag ('.$existingPackage->getPrettyVersion().') as both resolve to '.$data['version_normalized'].' internally</warning>');
                    }
                    continue;
                }

                if ($isVeryVerbose) {
                    $this->io->writeError('Importing tag '.$tag.' ('.$data['version_normalized'].')');
                }

                $this->addPackage($this->loader->load($this->preProcess($driver, $data, $identifier)));
            } catch (\Exception $e) {
                if ($e instanceof TransportException) {
                    $this->versionTransportExceptions['tags'][$tag] = $e;
                    if ($e->getCode() === 404) {
                        $this->emptyReferences[] = $identifier;
                    }
                    if ($this->shouldRethrowTransportException($e)) {
                        throw $e;
                    }
                }
                if ($isVeryVerbose) {
                    $this->io->writeError('<warning>Skipped tag '.$tag.', '.($e instanceof TransportException ? 'no composer file was found (' . $e->getCode() . ' HTTP status code)' : $e->getMessage()).'</warning>');
                }
                continue;
            }
        }

        if (!$isVeryVerbose) {
            $this->io->overwriteError('', false);
        }

        $branches = $driver->getBranches();
        // make sure the root identifier branch gets loaded first
        if ($hasRootIdentifierComposerJson && isset($branches[$driver->getRootIdentifier()])) {
            $branches = [$driver->getRootIdentifier() => $branches[$driver->getRootIdentifier()]] + $branches;
        }

        foreach ($branches as $branch => $identifier) {
            $branch = (string) $branch;
            $msg = 'Reading composer.json of <info>' . ($this->packageName ?: $this->url) . '</info> (<comment>' . $branch . '</comment>)';
            if ($isVeryVerbose) {
                $this->io->writeError($msg);
            } elseif ($isVerbose) {
                $this->io->overwriteError($msg, false);
            }

            if (!$parsedBranch = $this->validateBranch($branch)) {
                if ($isVeryVerbose) {
                    $this->io->writeError('<warning>Skipped branch '.$branch.', invalid name</warning>');
                }
                continue;
            }

            // make sure branch packages have a dev flag
            if (strpos($parsedBranch, 'dev-') === 0 || VersionParser::DEFAULT_BRANCH_ALIAS === $parsedBranch) {
                $version = 'dev-' . str_replace('#', '+', $branch);
                $parsedBranch = str_replace('#', '+', $parsedBranch);
            } else {
                $prefix = strpos($branch, 'v') === 0 ? 'v' : '';
                $version = $prefix . Preg::replace('{(\.9{7})+}', '.x', $parsedBranch);
            }

            $cachedPackage = $this->getCachedPackageVersion($version, $identifier, $isVerbose, $isVeryVerbose, $driver->getRootIdentifier() === $branch);
            if ($cachedPackage) {
                $this->addPackage($cachedPackage);

                continue;
            }
            if ($cachedPackage === false) {
                $this->emptyReferences[] = $identifier;

                continue;
            }

            try {
                $data = $driver->getComposerInformation($identifier);
                if (null === $data) {
                    if ($isVeryVerbose) {
                        $this->io->writeError('<warning>Skipped branch '.$branch.', no composer file</warning>');
                    }
                    $this->emptyReferences[] = $identifier;
                    continue;
                }

                // branches are always auto-versioned, read value from branch name
                $data['version'] = $version;
                $data['version_normalized'] = $parsedBranch;

                unset($data['default-branch']);
                if ($driver->getRootIdentifier() === $branch) {
                    $data['default-branch'] = true;
                }

                if ($isVeryVerbose) {
                    $this->io->writeError('Importing branch '.$branch.' ('.$data['version'].')');
                }

                $packageData = $this->preProcess($driver, $data, $identifier);
                $package = $this->loader->load($packageData);
                if ($this->loader instanceof ValidatingArrayLoader && \count($this->loader->getWarnings()) > 0) {
                    throw new InvalidPackageException($this->loader->getErrors(), $this->loader->getWarnings(), $packageData);
                }
                $this->addPackage($package);
            } catch (TransportException $e) {
                $this->versionTransportExceptions['branches'][$branch] = $e;
                if ($e->getCode() === 404) {
                    $this->emptyReferences[] = $identifier;
                }
                if ($this->shouldRethrowTransportException($e)) {
                    throw $e;
                }
                if ($isVeryVerbose) {
                    $this->io->writeError('<warning>Skipped branch '.$branch.', no composer file was found (' . $e->getCode() . ' HTTP status code)</warning>');
                }
                continue;
            } catch (\Exception $e) {
                if (!$isVeryVerbose) {
                    $this->io->writeError('');
                }
                $this->branchErrorOccurred = true;
                $this->io->writeError('<error>Skipped branch '.$branch.', '.$e->getMessage().'</error>');
                $this->io->writeError('');
                continue;
            }
        }
        $driver->cleanup();

        if (!$isVeryVerbose) {
            $this->io->overwriteError('', false);
        }

        if (!$this->getPackages()) {
            throw new InvalidRepositoryException('No valid composer.json was found in any branch or tag of '.$this->url.', could not load a package from it.');
        }
    }

    /**
     * @param array{name?: string, dist?: array{type: string, url: string, reference: string, shasum: string}, source?: array{type: string, url: string, reference: string}} $data
     *
     * @return array{name: string|null, dist: array{type: string, url: string, reference: string, shasum: string}|null, source: array{type: string, url: string, reference: string}}
     */
    protected function preProcess(VcsDriverInterface $driver, array $data, string $identifier): array
    {
        // keep the name of the main identifier for all packages
        // this ensures that a package can be renamed in one place and that all old tags
        // will still be installable using that new name without requiring re-tagging
        $dataPackageName = $data['name'] ?? null;
        $data['name'] = $this->packageName ?: $dataPackageName;

        if (!isset($data['dist'])) {
            $data['dist'] = $driver->getDist($identifier);
        }
        if (!isset($data['source'])) {
            $data['source'] = $driver->getSource($identifier);
        }

        // if custom dist info is provided but does not provide a reference, copy the source reference to it
        if (is_array($data['dist']) && !isset($data['dist']['reference']) && isset($data['source']['reference'])) {
            $data['dist']['reference'] = $data['source']['reference'];
        }

        return $data;
    }

    /**
     * @return string|false
     */
    private function validateBranch(string $branch)
    {
        try {
            $normalizedBranch = $this->versionParser->normalizeBranch($branch);

            // validate that the branch name has no weird characters conflicting with constraints
            $this->versionParser->parseConstraints($normalizedBranch);

            return $normalizedBranch;
        } catch (\Exception $e) {
        }

        return false;
    }

    /**
     * @return string|false
     */
    private function validateTag(string $version)
    {
        try {
            return $this->versionParser->normalize($version);
        } catch (\Exception $e) {
        }

        return false;
    }

    /**
     * @return \Composer\Package\CompletePackage|\Composer\Package\CompleteAliasPackage|null|false null if no cache present, false if the absence of a version was cached
     */
    private function getCachedPackageVersion(string $version, string $identifier, bool $isVerbose, bool $isVeryVerbose, bool $isDefaultBranch = false)
    {
        if (!$this->versionCache) {
            return null;
        }

        $cachedPackage = $this->versionCache->getVersionPackage($version, $identifier);
        if ($cachedPackage === false) {
            if ($isVeryVerbose) {
                $this->io->writeError('<warning>Skipped '.$version.', no composer file (cached from ref '.$identifier.')</warning>');
            }

            return false;
        }

        if ($cachedPackage) {
            $msg = 'Found cached composer.json of <info>' . ($this->packageName ?: $this->url) . '</info> (<comment>' . $version . '</comment>)';
            if ($isVeryVerbose) {
                $this->io->writeError($msg);
            } elseif ($isVerbose) {
                $this->io->overwriteError($msg, false);
            }

            unset($cachedPackage['default-branch']);
            if ($isDefaultBranch) {
                $cachedPackage['default-branch'] = true;
            }

            if ($existingPackage = $this->findPackage($cachedPackage['name'], new Constraint('=', $cachedPackage['version_normalized']))) {
                if ($isVeryVerbose) {
                    $this->io->writeError('<warning>Skipped cached version '.$version.', it conflicts with an another tag ('.$existingPackage->getPrettyVersion().') as both resolve to '.$cachedPackage['version_normalized'].' internally</warning>');
                }
                $cachedPackage = null;
            }
        }

        if ($cachedPackage) {
            return $this->loader->load($cachedPackage);
        }

        return null;
    }

    private function shouldRethrowTransportException(TransportException $e): bool
    {
        return in_array($e->getCode(), [401, 403, 429], true) || $e->getCode() >= 500;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

/**
 * Builds list of package from PEAR channel.
 *
 * Packages read from channel are named as 'pear-{channelName}/{packageName}'
 * and has aliased as 'pear-{channelAlias}/{packageName}'
 *
 * @author Benjamin Eberlei <kontakt@beberlei.de>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @deprecated
 * @private
 */
class PearRepository extends ArrayRepository
{
    public function __construct()
    {
        throw new \InvalidArgumentException('The PEAR repository has been removed from Composer 2.x');
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Advisory\PartialSecurityAdvisory;
use Composer\Advisory\SecurityAdvisory;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Loader\ValidatingArrayLoader;
use Composer\Package\Version\VersionParser;
use Composer\Pcre\Preg;

/**
 * Package repository.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PackageRepository extends ArrayRepository implements AdvisoryProviderInterface
{
    /** @var mixed[] */
    private $config;

    /** @var mixed[] */
    private $securityAdvisories;

    /**
     * Initializes filesystem repository.
     *
     * @param array{package: mixed[]} $config package definition
     */
    public function __construct(array $config)
    {
        parent::__construct();
        $this->config = $config['package'];

        // make sure we have an array of package definitions
        if (!is_numeric(key($this->config))) {
            $this->config = [$this->config];
        }

        $this->securityAdvisories = $config['security-advisories'] ?? [];
    }

    /**
     * Initializes repository (reads file, or remote address).
     */
    protected function initialize(): void
    {
        parent::initialize();

        $loader = new ValidatingArrayLoader(new ArrayLoader(null, true), true);
        foreach ($this->config as $package) {
            try {
                $package = $loader->load($package);
            } catch (\Exception $e) {
                throw new InvalidRepositoryException('A repository of type "package" contains an invalid package definition: '.$e->getMessage()."\n\nInvalid package definition:\n".json_encode($package));
            }

            $this->addPackage($package);
        }
    }

    public function getRepoName(): string
    {
        return Preg::replace('{^array }', 'package ', parent::getRepoName());
    }

    public function hasSecurityAdvisories(): bool
    {
        return count($this->securityAdvisories) > 0;
    }

    /**
     * @todo not sure if this is a good idea, just helped setting up the test fixtures
     */
    public function getSecurityAdvisories(array $packageConstraintMap, bool $allowPartialAdvisories = false): array
    {
        $parser = new VersionParser();

        $advisories = [];
        foreach ($this->securityAdvisories as $packageName => $packageAdvisories) {
            if (isset($packageConstraintMap[$packageName])) {
                $advisories[$packageName] = array_values(array_filter(array_map(function (array $data) use ($packageName, $allowPartialAdvisories, $packageConstraintMap, $parser) {
                    $advisory = PartialSecurityAdvisory::create($packageName, $data, $parser);
                    if (!$allowPartialAdvisories && !$advisory instanceof SecurityAdvisory) {
                        throw new \RuntimeException('Advisory for '.$packageName.' could not be loaded as a full advisory from '.$this->getRepoName() . PHP_EOL . var_export($data, true));
                    }

                    if (!$advisory->affectedVersions->matches($packageConstraintMap[$packageName])) {
                        return null;
                    }

                    return $advisory;
                }, $packageAdvisories)));
            }
        }

        return ['advisories' => array_filter($advisories, static function ($adv): bool { return \count($adv) > 0; }), 'namesFound' => array_keys($advisories)];
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\SelfUpdate;

use Composer\Pcre\Preg;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Keys
{
    public static function fingerprint(string $path): string
    {
        $hash = strtoupper(hash('sha256', Preg::replace('{\s}', '', file_get_contents($path))));

        return implode(' ', [
            substr($hash, 0, 8),
            substr($hash, 8, 8),
            substr($hash, 16, 8),
            substr($hash, 24, 8),
            '', // Extra space
            substr($hash, 32, 8),
            substr($hash, 40, 8),
            substr($hash, 48, 8),
            substr($hash, 56, 8),
        ]);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\SelfUpdate;

use Composer\IO\IOInterface;
use Composer\Pcre\Preg;
use Composer\Util\HttpDownloader;
use Composer\Config;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Versions
{
    /**
     * @var string[]
     * @deprecated use Versions::CHANNELS
     */
    public static $channels = self::CHANNELS;

    public const CHANNELS = ['stable', 'preview', 'snapshot', '1', '2', '2.2'];

    /** @var HttpDownloader */
    private $httpDownloader;
    /** @var Config */
    private $config;
    /** @var string */
    private $channel;
    /** @var array<string, array<int, array{path: string, version: string, min-php: int, eol?: true}>>|null */
    private $versionsData = null;

    public function __construct(Config $config, HttpDownloader $httpDownloader)
    {
        $this->httpDownloader = $httpDownloader;
        $this->config = $config;
    }

    public function getChannel(): string
    {
        if ($this->channel) {
            return $this->channel;
        }

        $channelFile = $this->config->get('home').'/update-channel';
        if (file_exists($channelFile)) {
            $channel = trim(file_get_contents($channelFile));
            if (in_array($channel, ['stable', 'preview', 'snapshot', '2.2'], true)) {
                return $this->channel = $channel;
            }
        }

        return $this->channel = 'stable';
    }

    public function setChannel(string $channel, ?IOInterface $io = null): void
    {
        if (!in_array($channel, self::CHANNELS, true)) {
            throw new \InvalidArgumentException('Invalid channel '.$channel.', must be one of: ' . implode(', ', self::CHANNELS));
        }

        $channelFile = $this->config->get('home').'/update-channel';
        $this->channel = $channel;

        // rewrite '2' and '1' channels to stable for future self-updates, but LTS ones like '2.2' remain pinned
        $storedChannel = Preg::isMatch('{^\d+$}D', $channel) ? 'stable' : $channel;
        $previouslyStored = file_exists($channelFile) ? trim((string) file_get_contents($channelFile)) : null;
        file_put_contents($channelFile, $storedChannel.PHP_EOL);

        if ($io !== null && $previouslyStored !== $storedChannel) {
            $io->writeError('Storing "<info>'.$storedChannel.'</info>" as default update channel for the next self-update run.');
        }
    }

    /**
     * @return array{path: string, version: string, min-php: int, eol?: true}
     */
    public function getLatest(?string $channel = null): array
    {
        $versions = $this->getVersionsData();

        foreach ($versions[$channel ?: $this->getChannel()] as $version) {
            if ($version['min-php'] <= \PHP_VERSION_ID) {
                return $version;
            }
        }

        throw new \UnexpectedValueException('There is no version of Composer available for your PHP version ('.PHP_VERSION.')');
    }

    /**
     * @return array<string, array<int, array{path: string, version: string, min-php: int, eol?: true}>>
     */
    private function getVersionsData(): array
    {
        if (null === $this->versionsData) {
            if ($this->config->get('disable-tls') === true) {
                $protocol = 'http';
            } else {
                $protocol = 'https';
            }

            $this->versionsData = $this->httpDownloader->get($protocol . '://getcomposer.org/versions')->decodeJson();
        }

        return $this->versionsData;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Platform;

use Composer\Pcre\Preg;

class Runtime
{
    /**
     * @param class-string $class
     */
    public function hasConstant(string $constant, ?string $class = null): bool
    {
        return defined(ltrim($class.'::'.$constant, ':'));
    }

    /**
     * @param class-string $class
     *
     * @return mixed
     */
    public function getConstant(string $constant, ?string $class = null)
    {
        return constant(ltrim($class.'::'.$constant, ':'));
    }

    public function hasFunction(string $fn): bool
    {
        return function_exists($fn);
    }

    /**
     * @param mixed[] $arguments
     *
     * @return mixed
     */
    public function invoke(callable $callable, array $arguments = [])
    {
        return $callable(...$arguments);
    }

    /**
     * @param class-string $class
     */
    public function hasClass(string $class): bool
    {
        return class_exists($class, false);
    }

    /**
     * @template T of object
     * @param mixed[] $arguments
     *
     * @phpstan-param class-string<T> $class
     * @phpstan-return T
     *
     * @throws \ReflectionException
     */
    public function construct(string $class, array $arguments = []): object
    {
        if (empty($arguments)) {
            return new $class;
        }

        $refl = new \ReflectionClass($class);

        return $refl->newInstanceArgs($arguments);
    }

    /** @return string[] */
    public function getExtensions(): array
    {
        return get_loaded_extensions();
    }

    public function getExtensionVersion(string $extension): string
    {
        $version = phpversion($extension);
        if ($version === false) {
            $version = '0';
        }

        return $version;
    }

    /**
     * @throws \ReflectionException
     */
    public function getExtensionInfo(string $extension): string
    {
        $reflector = new \ReflectionExtension($extension);

        ob_start();
        $reflector->info();

        $info = (string) ob_get_clean();

        if ('cli' === PHP_SAPI) {
            return $info;
        }

        return self::parseHtmlExtensionInfo($info);
    }

    /**
     * @internal Only public for unit tests
     */
    public static function parseHtmlExtensionInfo(string $html): string
    {
        $result = [];

        if ((bool) Preg::match('~<h2>\s*<a[^>]*>([^<]+)</a>\s*</h2>~i', $html, $matches)) {
            $result[] = trim(html_entity_decode($matches[1]));
            $result[] = '';
        }

        if ((bool) Preg::matchAll('~<tr>\s*<td class="e">\s*(.*?)\s*</td>\s*<td class="v">\s*(.*?)\s*</td>\s*</tr>~is', $html, $matches)) {
            $count = min(\count($matches[1]), \count($matches[2]));

            for ($i = 0; $i < $count; $i++) {
                $key   = trim(html_entity_decode(strip_tags($matches[1][$i])));
                $value = trim(html_entity_decode(strip_tags($matches[2][$i])));
                $result[] = $key . ' => ' . $value;
            }
        }

        return implode("\n", $result);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Platform;

use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Symfony\Component\Process\ExecutableFinder;

class HhvmDetector
{
    /** @var string|false|null */
    private static $hhvmVersion = null;
    /** @var ?ExecutableFinder */
    private $executableFinder;
    /** @var ?ProcessExecutor */
    private $processExecutor;

    public function __construct(?ExecutableFinder $executableFinder = null, ?ProcessExecutor $processExecutor = null)
    {
        $this->executableFinder = $executableFinder;
        $this->processExecutor = $processExecutor;
    }

    public function reset(): void
    {
        self::$hhvmVersion = null;
    }

    public function getVersion(): ?string
    {
        if (null !== self::$hhvmVersion) {
            return self::$hhvmVersion ?: null;
        }

        self::$hhvmVersion = defined('HHVM_VERSION') ? HHVM_VERSION : null;
        if (self::$hhvmVersion === null && !Platform::isWindows()) {
            self::$hhvmVersion = false;
            $this->executableFinder = $this->executableFinder ?: new ExecutableFinder();
            $hhvmPath = $this->executableFinder->find('hhvm');
            if ($hhvmPath !== null) {
                $this->processExecutor = $this->processExecutor ?? new ProcessExecutor();
                $exitCode = $this->processExecutor->execute([$hhvmPath, '--php', '-d', 'hhvm.jit=0', '-r', 'echo HHVM_VERSION;'], self::$hhvmVersion);
                if ($exitCode !== 0) {
                    self::$hhvmVersion = false;
                }
            }
        }

        return self::$hhvmVersion ?: null;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Platform;

use Composer\Pcre\Preg;

/**
 * @author Lars Strojny <lars@strojny.net>
 */
class Version
{
    /**
     * @param bool $isFips Set by the method
     *
     * @param-out bool $isFips
     */
    public static function parseOpenssl(string $opensslVersion, ?bool &$isFips): ?string
    {
        $isFips = false;

        if (!Preg::isMatchStrictGroups('/^(?<version>[0-9.]+)(?<patch>[a-z]{0,2})(?<suffix>(?:-?(?:dev|pre|alpha|beta|rc|fips)[\d]*)*)(?:-\w+)?(?: \(.+?\))?$/', $opensslVersion, $matches)) {
            return null;
        }

        // OpenSSL 1 used 1.2.3a style versioning, 3+ uses semver
        $patch = '';
        if (version_compare($matches['version'], '3.0.0', '<')) {
            $patch = '.'.self::convertAlphaVersionToIntVersion($matches['patch']);
        }

        $isFips = strpos($matches['suffix'], 'fips') !== false;
        $suffix = strtr('-'.ltrim($matches['suffix'], '-'), ['-fips' => '', '-pre' => '-alpha']);

        return rtrim($matches['version'].$patch.$suffix, '-');
    }

    public static function parseLibjpeg(string $libjpegVersion): ?string
    {
        if (!Preg::isMatchStrictGroups('/^(?<major>\d+)(?<minor>[a-z]*)$/', $libjpegVersion, $matches)) {
            return null;
        }

        return $matches['major'].'.'.self::convertAlphaVersionToIntVersion($matches['minor']);
    }

    public static function parseZoneinfoVersion(string $zoneinfoVersion): ?string
    {
        if (!Preg::isMatchStrictGroups('/^(?<year>\d{4})(?<revision>[a-z]*)$/', $zoneinfoVersion, $matches)) {
            return null;
        }

        return $matches['year'].'.'.self::convertAlphaVersionToIntVersion($matches['revision']);
    }

    /**
     * "" => 0, "a" => 1, "zg" => 33
     */
    private static function convertAlphaVersionToIntVersion(string $alpha): int
    {
        return strlen($alpha) * (-ord('a') + 1) + array_sum(array_map('ord', str_split($alpha)));
    }

    public static function convertLibxpmVersionId(int $versionId): string
    {
        return self::convertVersionId($versionId, 100);
    }

    public static function convertOpenldapVersionId(int $versionId): string
    {
        return self::convertVersionId($versionId, 100);
    }

    private static function convertVersionId(int $versionId, int $base): string
    {
        return sprintf(
            '%d.%d.%d',
            $versionId / ($base * $base),
            (int) ($versionId / $base) % $base,
            $versionId % $base
        );
    }
}
# Composer-specific PHPStan extensions
#
# These can be reused by third party packages by including 'vendor/composer/composer/phpstan/rules.neon'
# in your phpstan config

services:
    -
        class: Composer\PHPStan\ConfigReturnTypeExtension
        tags:
            - phpstan.broker.dynamicMethodReturnTypeExtension
    -
        class: Composer\PHPStan\RuleReasonDataReturnTypeExtension
        tags:
            - phpstan.broker.dynamicMethodReturnTypeExtension
#!/usr/bin/env php
<?php

if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') {
    if (0 === strpos(__FILE__, 'phar:') && ini_get('register_argc_argv')) {
        echo 'Composer cannot be run safely on non-CLI SAPIs with register_argc_argv=On. Aborting.'.PHP_EOL;
        exit(1);
    }

    echo 'Warning: Composer should be invoked via the CLI version of PHP, not the '.PHP_SAPI.' SAPI'.PHP_EOL;
}

if (PHP_VERSION_ID < 70205) {
    echo 'Composer 2.3.0 dropped support for PHP <7.2.5 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
    exit(1);
}

if (ini_get('opcache.enable_cli') && ini_get('opcache.preload')) {
    register_shutdown_function(static function () {
        $e = error_get_last();
        if ($e !== null
            && (
                strpos($e['message'], 'Cannot redeclare class') !== false
                || (strpos($e['message'], 'Declaration of') !== false && strpos($e['message'], 'must be compatible with') !== false)
            )
        ) {
            echo 'Warning: The error above might be caused by the fact opcache.enable_cli is enabled and opcache.preload is set in php.ini. We recommend disabling opcache.enable_cli if you really need preloading on this machine.'.PHP_EOL;
        }
    });
}

setlocale(LC_ALL, 'C');
require __DIR__.'/../src/bootstrap.php';

use Composer\Console\Application;
use Composer\XdebugHandler\XdebugHandler;
use Composer\Util\Platform;
use Composer\Util\ErrorHandler;

error_reporting(-1);

// Restart without Xdebug
$xdebug = new XdebugHandler('Composer');
$xdebug->check();
unset($xdebug);

if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '4.0', '>=')) {
    echo 'HHVM 4.0 has dropped support for Composer, please use PHP instead. Aborting.'.PHP_EOL;
    exit(1);
}
if (!extension_loaded('iconv') && !extension_loaded('mbstring')) {
    echo 'The iconv OR mbstring extension is required and both are missing.'
        .PHP_EOL.'Install either of them or recompile php without --disable-iconv.'
        .PHP_EOL.'Aborting.'.PHP_EOL;
    exit(1);
}

if (function_exists('ini_set')) {
    // check if error logging is on, but to an empty destination - for the CLI SAPI, that means stderr
    $logsToSapiDefault = ('' === ini_get('error_log') && (bool) ini_get('log_errors'));
    // on the CLI SAPI, ensure errors are displayed on stderr, either via display_errors or via error_log
    if (PHP_SAPI === 'cli') {
        @ini_set('display_errors', $logsToSapiDefault ? '0' : 'stderr');
    }

    // Set user defined memory limit
    if ($memoryLimit = getenv('COMPOSER_MEMORY_LIMIT')) {
        @ini_set('memory_limit', $memoryLimit);
    } else {
        $memoryInBytes = function ($value) {
            $unit = strtolower(substr($value, -1, 1));
            $value = (int) $value;
            switch($unit) {
                case 'g':
                    $value *= 1024;
                    // no break (cumulative multiplier)
                case 'm':
                    $value *= 1024;
                    // no break (cumulative multiplier)
                case 'k':
                    $value *= 1024;
            }

            return $value;
        };

        $memoryLimit = trim(ini_get('memory_limit'));
        // Increase memory_limit if it is lower than 1.5GB
        if ($memoryLimit != -1 && $memoryInBytes($memoryLimit) < 1024 * 1024 * 1536) {
            @ini_set('memory_limit', '1536M');
        }
        unset($memoryInBytes);
    }
    unset($memoryLimit);
}

// Workaround PHP bug on Windows where env vars containing Unicode chars are mangled in $_SERVER
// see https://github.com/php/php-src/issues/7896
if (PHP_VERSION_ID >= 70113 && (PHP_VERSION_ID < 80016 || (PHP_VERSION_ID >= 80100 && PHP_VERSION_ID < 80103)) && Platform::isWindows()) {
    foreach ($_SERVER as $serverVar => $serverVal) {
        if (($serverVal = getenv($serverVar)) !== false) {
            $_SERVER[$serverVar] = $serverVal;
        }
    }
}

Platform::putEnv('COMPOSER_BINARY', realpath($_SERVER['argv'][0]));

ErrorHandler::register();

// run the command application
$application = new Application();
$application->run();
#!/usr/bin/env php
<?php

$cwd = getcwd();
assert(is_string($cwd));
chdir(__DIR__.'/../');
$ts = rtrim(exec('git rev-list --no-commit-header -n1 --format=%ct HEAD'));
if (!is_numeric($ts)) {
    echo 'Could not detect date using "git rev-list --no-commit-header -n1 --format=%ct HEAD"'.PHP_EOL;
    exit(1);
}
// Install with the current version to force it having the right ClassLoader version
// Install without dev packages to clean up the included classmap from phpunit classes
exec('php bin/composer config autoloader-suffix ComposerPhar' . $ts, $output, $result);
if (0 !== $result) {
    echo 'Could not set the autoloader suffix, make sure exec is allowed and php can be found in your PATH';
    exit(1);
}
exec('php bin/composer install -q --no-dev', $output, $result);
if (0 !== $result) {
    echo 'Could not remove dev deps, make sure exec is allowed and php can be found in your PATH';
    exit(1);
}
exec('php bin/composer config autoloader-suffix --unset', $output, $result);
if (0 !== $result) {
    echo 'Could not remove the autoloader suffix, make sure exec is allowed and php can be found in your PATH';
    exit(1);
}
chdir($cwd);

require __DIR__.'/../src/bootstrap.php';

use Composer\Compiler;

error_reporting(-1);
ini_set('display_errors', '1');

try {
    $compiler = new Compiler();
    $compiler->compile();
} catch (\Exception $e) {
    echo 'Failed to compile phar: ['.get_class($e).'] '.$e->getMessage().' at '.$e->getFile().':'.$e->getLine().PHP_EOL;
    exit(1);
}
Copyright (C) 2021 Composer

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.
<?php

/*
 * This file is part of composer/metadata-minifier.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\MetadataMinifier;

class MetadataMinifier
{
    /**
     * Expands an array of minified versions back to their original format
     *
     * @param array[] $versions A list of minified version arrays
     * @return array[] A list of version arrays
     */
    public static function expand(array $versions)
    {
        $expanded = array();
        $expandedVersion = null;
        foreach ($versions as $versionData) {
            if (!$expandedVersion) {
                $expandedVersion = $versionData;
                $expanded[] = $expandedVersion;
                continue;
            }

            // add any changes from the previous version to the expanded one
            foreach ($versionData as $key => $val) {
                if ($val === '__unset') {
                    unset($expandedVersion[$key]);
                } else {
                    $expandedVersion[$key] = $val;
                }
            }

            $expanded[] = $expandedVersion;
        }

        return $expanded;
    }

    /**
     * Minifies an array of versions into a set of version diffs
     *
     * @param array[] $versions A list of version arrays
     * @return array[] A list of versions minified with each array only containing the differences to the previous one
     */
    public static function minify(array $versions)
    {
        $minifiedVersions = array();

        $lastKnownVersionData = null;
        foreach ($versions as $version) {
            if (!$lastKnownVersionData) {
                $lastKnownVersionData = $version;
                $minifiedVersions[] = $version;
                continue;
            }

            $minifiedVersion = array();

            // add any changes from the previous version
            foreach ($version as $key => $val) {
                if (!isset($lastKnownVersionData[$key]) || $lastKnownVersionData[$key] !== $val) {
                    $minifiedVersion[$key] = $val;
                    $lastKnownVersionData[$key] = $val;
                }
            }

            // store any deletions from the previous version for keys missing in current one
            foreach ($lastKnownVersionData as $key => $val) {
                if (!isset($version[$key])) {
                    $minifiedVersion[$key] = "__unset";
                    unset($lastKnownVersionData[$key]);
                }
            }

            $minifiedVersions[] = $minifiedVersion;
        }

        return $minifiedVersions;
    }
}
<?php return array(
    'root' => array(
        'name' => 'php/pie',
        'pretty_version' => '1.5.x-dev',
        'version' => '1.5.9999999.9999999-dev',
        'reference' => 'f65dec06f127fcff462048492f36ce258489af81',
        'type' => 'project',
        'install_path' => __DIR__ . '/../../',
        'aliases' => array(),
        'dev' => true,
    ),
    'versions' => array(
        'behat/behat' => array(
            'pretty_version' => 'v3.29.0',
            'version' => '3.29.0.0',
            'reference' => '51bdf81639a14645c5d2c06926f4aa37d204921b',
            'type' => 'library',
            'install_path' => __DIR__ . '/../behat/behat',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'behat/gherkin' => array(
            'pretty_version' => 'v4.16.1',
            'version' => '4.16.1.0',
            'reference' => 'e26037937dfd48528746764dd870bc5d0836665f',
            'type' => 'library',
            'install_path' => __DIR__ . '/../behat/gherkin',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'bnf/phpstan-psr-container' => array(
            'pretty_version' => '1.1.0',
            'version' => '1.1.0.0',
            'reference' => '2e2fd1973576bdc755ea814b2142fcdbc673fd1c',
            'type' => 'phpstan-extension',
            'install_path' => __DIR__ . '/../bnf/phpstan-psr-container',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'composer/ca-bundle' => array(
            'pretty_version' => '1.5.11',
            'version' => '1.5.11.0',
            'reference' => '68ff39175e8e94a4bb1d259407ce51a6a60f09e6',
            'type' => 'library',
            'install_path' => __DIR__ . '/./ca-bundle',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'composer/class-map-generator' => array(
            'pretty_version' => '1.7.3',
            'version' => '1.7.3.0',
            'reference' => '86d8208fc3c649a3a999daf1a63c25201be2990f',
            'type' => 'library',
            'install_path' => __DIR__ . '/./class-map-generator',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'composer/composer' => array(
            'pretty_version' => '2.9.8',
            'version' => '2.9.8.0',
            'reference' => '39ee8baff8e97a1b657bbfcd6a236ff93a5efbb2',
            'type' => 'library',
            'install_path' => __DIR__ . '/./composer',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'composer/metadata-minifier' => array(
            'pretty_version' => '1.0.0',
            'version' => '1.0.0.0',
            'reference' => 'c549d23829536f0d0e984aaabbf02af91f443207',
            'type' => 'library',
            'install_path' => __DIR__ . '/./metadata-minifier',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'composer/pcre' => array(
            'pretty_version' => '3.3.2',
            'version' => '3.3.2.0',
            'reference' => 'b2bed4734f0cc156ee1fe9c0da2550420d99a21e',
            'type' => 'library',
            'install_path' => __DIR__ . '/./pcre',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'composer/semver' => array(
            'pretty_version' => '3.4.4',
            'version' => '3.4.4.0',
            'reference' => '198166618906cb2de69b95d7d47e5fa8aa1b2b95',
            'type' => 'library',
            'install_path' => __DIR__ . '/./semver',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'composer/spdx-licenses' => array(
            'pretty_version' => '1.6.0',
            'version' => '1.6.0.0',
            'reference' => '5ecd0cb4177696f9fd48f1605dda81db3dee7889',
            'type' => 'library',
            'install_path' => __DIR__ . '/./spdx-licenses',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'composer/xdebug-handler' => array(
            'pretty_version' => '3.0.5',
            'version' => '3.0.5.0',
            'reference' => '6c1925561632e83d60a44492e0b344cf48ab85ef',
            'type' => 'library',
            'install_path' => __DIR__ . '/./xdebug-handler',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'dealerdirect/phpcodesniffer-composer-installer' => array(
            'pretty_version' => 'v1.2.1',
            'version' => '1.2.1.0',
            'reference' => '963f0c67bffde0eac41b56be71ac0e8ba132f0bd',
            'type' => 'composer-plugin',
            'install_path' => __DIR__ . '/../dealerdirect/phpcodesniffer-composer-installer',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'doctrine/coding-standard' => array(
            'pretty_version' => '14.0.0',
            'version' => '14.0.0.0',
            'reference' => '897a7dc209e49ee6cf04e689c41112df17967130',
            'type' => 'phpcodesniffer-standard',
            'install_path' => __DIR__ . '/../doctrine/coding-standard',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'fidry/cpu-core-counter' => array(
            'pretty_version' => '1.3.0',
            'version' => '1.3.0.0',
            'reference' => 'db9508f7b1474469d9d3c53b86f817e344732678',
            'type' => 'library',
            'install_path' => __DIR__ . '/../fidry/cpu-core-counter',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'illuminate/container' => array(
            'pretty_version' => 'v10.49.0',
            'version' => '10.49.0.0',
            'reference' => 'b4956de5de18524c21ef36221a8ffd7fa3b534db',
            'type' => 'library',
            'install_path' => __DIR__ . '/../illuminate/container',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'illuminate/contracts' => array(
            'pretty_version' => 'v10.49.0',
            'version' => '10.49.0.0',
            'reference' => '2393ef579e020d88e24283913c815c3e2c143323',
            'type' => 'library',
            'install_path' => __DIR__ . '/../illuminate/contracts',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'justinrainbow/json-schema' => array(
            'pretty_version' => '6.8.2',
            'version' => '6.8.2.0',
            'reference' => '2c89ebb95ca9cedc9347f780333f7b25792dcb76',
            'type' => 'library',
            'install_path' => __DIR__ . '/../justinrainbow/json-schema',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'marc-mabe/php-enum' => array(
            'pretty_version' => 'v4.7.2',
            'version' => '4.7.2.0',
            'reference' => 'bb426fcdd65c60fb3638ef741e8782508fda7eef',
            'type' => 'library',
            'install_path' => __DIR__ . '/../marc-mabe/php-enum',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'myclabs/deep-copy' => array(
            'pretty_version' => '1.13.4',
            'version' => '1.13.4.0',
            'reference' => '07d290f0c47959fd5eed98c95ee5602db07e0b6a',
            'type' => 'library',
            'install_path' => __DIR__ . '/../myclabs/deep-copy',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'nikic/php-parser' => array(
            'pretty_version' => 'v5.7.0',
            'version' => '5.7.0.0',
            'reference' => 'dca41cd15c2ac9d055ad70dbfd011130757d1f82',
            'type' => 'library',
            'install_path' => __DIR__ . '/../nikic/php-parser',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'phar-io/manifest' => array(
            'pretty_version' => '2.0.4',
            'version' => '2.0.4.0',
            'reference' => '54750ef60c58e43759730615a392c31c80e23176',
            'type' => 'library',
            'install_path' => __DIR__ . '/../phar-io/manifest',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'phar-io/version' => array(
            'pretty_version' => '3.2.1',
            'version' => '3.2.1.0',
            'reference' => '4f7fd7836c6f332bb2933569e566a0d6c4cbed74',
            'type' => 'library',
            'install_path' => __DIR__ . '/../phar-io/version',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'php/pie' => array(
            'pretty_version' => '1.5.x-dev',
            'version' => '1.5.9999999.9999999-dev',
            'reference' => 'f65dec06f127fcff462048492f36ce258489af81',
            'type' => 'project',
            'install_path' => __DIR__ . '/../../',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'phpstan/phpdoc-parser' => array(
            'pretty_version' => '2.3.2',
            'version' => '2.3.2.0',
            'reference' => 'a004701b11273a26cd7955a61d67a7f1e525a45a',
            'type' => 'library',
            'install_path' => __DIR__ . '/../phpstan/phpdoc-parser',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'phpstan/phpstan' => array(
            'pretty_version' => '2.1.54',
            'version' => '2.1.54.0',
            'reference' => '8be50c3992107dc837b17da4d140fbbdf9a5c5bd',
            'type' => 'library',
            'install_path' => __DIR__ . '/../phpstan/phpstan',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'phpstan/phpstan-phpunit' => array(
            'pretty_version' => '2.0.16',
            'version' => '2.0.16.0',
            'reference' => '6ab598e1bc106e6827fd346ae4a12b4a5d634c32',
            'type' => 'phpstan-extension',
            'install_path' => __DIR__ . '/../phpstan/phpstan-phpunit',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'phpstan/phpstan-webmozart-assert' => array(
            'pretty_version' => '2.0.0',
            'version' => '2.0.0.0',
            'reference' => '0c641817d2a8f05c7157f92d91986e74d3c8ab0c',
            'type' => 'phpstan-extension',
            'install_path' => __DIR__ . '/../phpstan/phpstan-webmozart-assert',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'phpunit/php-code-coverage' => array(
            'pretty_version' => '10.1.16',
            'version' => '10.1.16.0',
            'reference' => '7e308268858ed6baedc8704a304727d20bc07c77',
            'type' => 'library',
            'install_path' => __DIR__ . '/../phpunit/php-code-coverage',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'phpunit/php-file-iterator' => array(
            'pretty_version' => '4.1.0',
            'version' => '4.1.0.0',
            'reference' => 'a95037b6d9e608ba092da1b23931e537cadc3c3c',
            'type' => 'library',
            'install_path' => __DIR__ . '/../phpunit/php-file-iterator',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'phpunit/php-invoker' => array(
            'pretty_version' => '4.0.0',
            'version' => '4.0.0.0',
            'reference' => 'f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7',
            'type' => 'library',
            'install_path' => __DIR__ . '/../phpunit/php-invoker',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'phpunit/php-text-template' => array(
            'pretty_version' => '3.0.1',
            'version' => '3.0.1.0',
            'reference' => '0c7b06ff49e3d5072f057eb1fa59258bf287a748',
            'type' => 'library',
            'install_path' => __DIR__ . '/../phpunit/php-text-template',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'phpunit/php-timer' => array(
            'pretty_version' => '6.0.0',
            'version' => '6.0.0.0',
            'reference' => 'e2a2d67966e740530f4a3343fe2e030ffdc1161d',
            'type' => 'library',
            'install_path' => __DIR__ . '/../phpunit/php-timer',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'phpunit/phpunit' => array(
            'pretty_version' => '10.5.63',
            'version' => '10.5.63.0',
            'reference' => '33198268dad71e926626b618f3ec3966661e4d90',
            'type' => 'library',
            'install_path' => __DIR__ . '/../phpunit/phpunit',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'psr/container' => array(
            'pretty_version' => '2.0.2',
            'version' => '2.0.2.0',
            'reference' => 'c71ecc56dfe541dbd90c5360474fbc405f8d5963',
            'type' => 'library',
            'install_path' => __DIR__ . '/../psr/container',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'psr/container-implementation' => array(
            'dev_requirement' => false,
            'provided' => array(
                0 => '1.1|2.0',
            ),
        ),
        'psr/event-dispatcher' => array(
            'pretty_version' => '1.0.0',
            'version' => '1.0.0.0',
            'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0',
            'type' => 'library',
            'install_path' => __DIR__ . '/../psr/event-dispatcher',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'psr/event-dispatcher-implementation' => array(
            'dev_requirement' => false,
            'provided' => array(
                0 => '1.0',
            ),
        ),
        'psr/log' => array(
            'pretty_version' => '3.0.2',
            'version' => '3.0.2.0',
            'reference' => 'f16e1d5863e37f8d8c2a01719f5b34baa2b714d3',
            'type' => 'library',
            'install_path' => __DIR__ . '/../psr/log',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'psr/log-implementation' => array(
            'dev_requirement' => false,
            'provided' => array(
                0 => '1.0|2.0|3.0',
            ),
        ),
        'psr/simple-cache' => array(
            'pretty_version' => '3.0.0',
            'version' => '3.0.0.0',
            'reference' => '764e0b3939f5ca87cb904f570ef9be2d78a07865',
            'type' => 'library',
            'install_path' => __DIR__ . '/../psr/simple-cache',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'react/promise' => array(
            'pretty_version' => 'v3.3.0',
            'version' => '3.3.0.0',
            'reference' => '23444f53a813a3296c1368bb104793ce8d88f04a',
            'type' => 'library',
            'install_path' => __DIR__ . '/../react/promise',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'sebastian/cli-parser' => array(
            'pretty_version' => '2.0.1',
            'version' => '2.0.1.0',
            'reference' => 'c34583b87e7b7a8055bf6c450c2c77ce32a24084',
            'type' => 'library',
            'install_path' => __DIR__ . '/../sebastian/cli-parser',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'sebastian/code-unit' => array(
            'pretty_version' => '2.0.0',
            'version' => '2.0.0.0',
            'reference' => 'a81fee9eef0b7a76af11d121767abc44c104e503',
            'type' => 'library',
            'install_path' => __DIR__ . '/../sebastian/code-unit',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'sebastian/code-unit-reverse-lookup' => array(
            'pretty_version' => '3.0.0',
            'version' => '3.0.0.0',
            'reference' => '5e3a687f7d8ae33fb362c5c0743794bbb2420a1d',
            'type' => 'library',
            'install_path' => __DIR__ . '/../sebastian/code-unit-reverse-lookup',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'sebastian/comparator' => array(
            'pretty_version' => '5.0.5',
            'version' => '5.0.5.0',
            'reference' => '55dfef806eb7dfeb6e7a6935601fef866f8ca48d',
            'type' => 'library',
            'install_path' => __DIR__ . '/../sebastian/comparator',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'sebastian/complexity' => array(
            'pretty_version' => '3.2.0',
            'version' => '3.2.0.0',
            'reference' => '68ff824baeae169ec9f2137158ee529584553799',
            'type' => 'library',
            'install_path' => __DIR__ . '/../sebastian/complexity',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'sebastian/diff' => array(
            'pretty_version' => '5.1.1',
            'version' => '5.1.1.0',
            'reference' => 'c41e007b4b62af48218231d6c2275e4c9b975b2e',
            'type' => 'library',
            'install_path' => __DIR__ . '/../sebastian/diff',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'sebastian/environment' => array(
            'pretty_version' => '6.1.0',
            'version' => '6.1.0.0',
            'reference' => '8074dbcd93529b357029f5cc5058fd3e43666984',
            'type' => 'library',
            'install_path' => __DIR__ . '/../sebastian/environment',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'sebastian/exporter' => array(
            'pretty_version' => '5.1.4',
            'version' => '5.1.4.0',
            'reference' => '0735b90f4da94969541dac1da743446e276defa6',
            'type' => 'library',
            'install_path' => __DIR__ . '/../sebastian/exporter',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'sebastian/global-state' => array(
            'pretty_version' => '6.0.2',
            'version' => '6.0.2.0',
            'reference' => '987bafff24ecc4c9ac418cab1145b96dd6e9cbd9',
            'type' => 'library',
            'install_path' => __DIR__ . '/../sebastian/global-state',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'sebastian/lines-of-code' => array(
            'pretty_version' => '2.0.2',
            'version' => '2.0.2.0',
            'reference' => '856e7f6a75a84e339195d48c556f23be2ebf75d0',
            'type' => 'library',
            'install_path' => __DIR__ . '/../sebastian/lines-of-code',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'sebastian/object-enumerator' => array(
            'pretty_version' => '5.0.0',
            'version' => '5.0.0.0',
            'reference' => '202d0e344a580d7f7d04b3fafce6933e59dae906',
            'type' => 'library',
            'install_path' => __DIR__ . '/../sebastian/object-enumerator',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'sebastian/object-reflector' => array(
            'pretty_version' => '3.0.0',
            'version' => '3.0.0.0',
            'reference' => '24ed13d98130f0e7122df55d06c5c4942a577957',
            'type' => 'library',
            'install_path' => __DIR__ . '/../sebastian/object-reflector',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'sebastian/recursion-context' => array(
            'pretty_version' => '5.0.1',
            'version' => '5.0.1.0',
            'reference' => '47e34210757a2f37a97dcd207d032e1b01e64c7a',
            'type' => 'library',
            'install_path' => __DIR__ . '/../sebastian/recursion-context',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'sebastian/type' => array(
            'pretty_version' => '4.0.0',
            'version' => '4.0.0.0',
            'reference' => '462699a16464c3944eefc02ebdd77882bd3925bf',
            'type' => 'library',
            'install_path' => __DIR__ . '/../sebastian/type',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'sebastian/version' => array(
            'pretty_version' => '4.0.1',
            'version' => '4.0.1.0',
            'reference' => 'c51fa83a5d8f43f1402e3f32a005e6262244ef17',
            'type' => 'library',
            'install_path' => __DIR__ . '/../sebastian/version',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'seld/jsonlint' => array(
            'pretty_version' => '1.11.0',
            'version' => '1.11.0.0',
            'reference' => '1748aaf847fc731cfad7725aec413ee46f0cc3a2',
            'type' => 'library',
            'install_path' => __DIR__ . '/../seld/jsonlint',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'seld/phar-utils' => array(
            'pretty_version' => '1.2.1',
            'version' => '1.2.1.0',
            'reference' => 'ea2f4014f163c1be4c601b9b7bd6af81ba8d701c',
            'type' => 'library',
            'install_path' => __DIR__ . '/../seld/phar-utils',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'seld/signal-handler' => array(
            'pretty_version' => '2.0.2',
            'version' => '2.0.2.0',
            'reference' => '04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98',
            'type' => 'library',
            'install_path' => __DIR__ . '/../seld/signal-handler',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'slevomat/coding-standard' => array(
            'pretty_version' => '8.29.0',
            'version' => '8.29.0.0',
            'reference' => '81fce13c4ef4b53a03e5cfa6ce36afc191c1598e',
            'type' => 'phpcodesniffer-standard',
            'install_path' => __DIR__ . '/../slevomat/coding-standard',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'squizlabs/php_codesniffer' => array(
            'pretty_version' => '4.0.1',
            'version' => '4.0.1.0',
            'reference' => '0525c73950de35ded110cffafb9892946d7771b5',
            'type' => 'library',
            'install_path' => __DIR__ . '/../squizlabs/php_codesniffer',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'symfony/config' => array(
            'pretty_version' => 'v6.4.37',
            'version' => '6.4.37.0',
            'reference' => 'ee615e8352db9c5f0b7b149154a3f654dc72042b',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/config',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'symfony/console' => array(
            'pretty_version' => 'v6.4.37',
            'version' => '6.4.37.0',
            'reference' => '7bbcaf3fdb1e18fa42a7f0b84a10d091c10548f5',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/console',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/dependency-injection' => array(
            'pretty_version' => 'v6.4.38',
            'version' => '6.4.38.0',
            'reference' => 'f0990df92ee67721886a2a8b6e19a1bafbf3d7a4',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/dependency-injection',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'symfony/deprecation-contracts' => array(
            'pretty_version' => 'v3.7.0',
            'version' => '3.7.0.0',
            'reference' => '50f59d1f3ca46d41ac911f97a78626b6756af35b',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/deprecation-contracts',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/event-dispatcher' => array(
            'pretty_version' => 'v6.4.37',
            'version' => '6.4.37.0',
            'reference' => '2e3bf817ba9347341ab15926700fb6320367c0e1',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/event-dispatcher',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/event-dispatcher-contracts' => array(
            'pretty_version' => 'v3.7.0',
            'version' => '3.7.0.0',
            'reference' => 'ccba7060602b7fed0b03c85bf025257f76d9ef32',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/event-dispatcher-contracts',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/event-dispatcher-implementation' => array(
            'dev_requirement' => false,
            'provided' => array(
                0 => '2.0|3.0',
            ),
        ),
        'symfony/filesystem' => array(
            'pretty_version' => 'v6.4.37',
            'version' => '6.4.37.0',
            'reference' => '29f792d7dc30cc670fc4cdd50d7c6653d067ce7b',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/filesystem',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/finder' => array(
            'pretty_version' => 'v6.4.34',
            'version' => '6.4.34.0',
            'reference' => '9590e86be1d1c57bfbb16d0dd040345378c20896',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/finder',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/polyfill-ctype' => array(
            'pretty_version' => 'v1.37.0',
            'version' => '1.37.0.0',
            'reference' => '141046a8f9477948ff284fa65be2095baafb94f2',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/polyfill-ctype',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/polyfill-intl-grapheme' => array(
            'pretty_version' => 'v1.37.0',
            'version' => '1.37.0.0',
            'reference' => '4864388bfbd3001ce88e234fab652acd91fdc57e',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/polyfill-intl-grapheme',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/polyfill-intl-normalizer' => array(
            'pretty_version' => 'v1.37.0',
            'version' => '1.37.0.0',
            'reference' => '3833d7255cc303546435cb650316bff708a1c75c',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/polyfill-intl-normalizer',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/polyfill-mbstring' => array(
            'pretty_version' => 'v1.37.0',
            'version' => '1.37.0.0',
            'reference' => '6a21eb99c6973357967f6ce3708cd55a6bec6315',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/polyfill-mbstring',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/polyfill-php73' => array(
            'dev_requirement' => false,
            'replaced' => array(
                0 => '*',
            ),
        ),
        'symfony/polyfill-php80' => array(
            'dev_requirement' => false,
            'replaced' => array(
                0 => '*',
            ),
        ),
        'symfony/polyfill-php81' => array(
            'dev_requirement' => false,
            'replaced' => array(
                0 => '*',
            ),
        ),
        'symfony/polyfill-php84' => array(
            'pretty_version' => 'v1.37.0',
            'version' => '1.37.0.0',
            'reference' => '88486db2c389b290bf87ff1de7ebc1e13e42bb06',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/polyfill-php84',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/process' => array(
            'pretty_version' => 'v6.4.33',
            'version' => '6.4.33.0',
            'reference' => 'c46e854e79b52d07666e43924a20cb6dc546644e',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/process',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/service-contracts' => array(
            'pretty_version' => 'v3.7.0',
            'version' => '3.7.0.0',
            'reference' => 'd25d82433a80eba6aa0e6c24b61d7370d99e444a',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/service-contracts',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/service-implementation' => array(
            'dev_requirement' => true,
            'provided' => array(
                0 => '1.1|2.0|3.0',
            ),
        ),
        'symfony/string' => array(
            'pretty_version' => 'v6.4.34',
            'version' => '6.4.34.0',
            'reference' => '2adaf4106f2ef4c67271971bde6d3fe0a6936432',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/string',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/translation' => array(
            'pretty_version' => 'v6.4.38',
            'version' => '6.4.38.0',
            'reference' => 'afaa31b0c12d9a659eed1ea97f268a614cc1299c',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/translation',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'symfony/translation-contracts' => array(
            'pretty_version' => 'v3.7.0',
            'version' => '3.7.0.0',
            'reference' => '0ab302977a952b42fd51475c4ebac81f8da0a95d',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/translation-contracts',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'symfony/translation-implementation' => array(
            'dev_requirement' => true,
            'provided' => array(
                0 => '2.3|3.0',
            ),
        ),
        'symfony/var-exporter' => array(
            'pretty_version' => 'v6.4.37',
            'version' => '6.4.37.0',
            'reference' => '34f6957deffacabd1b1c579a312daa481e3e99ca',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/var-exporter',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'symfony/yaml' => array(
            'pretty_version' => 'v6.4.38',
            'version' => '6.4.38.0',
            'reference' => 'f8d2f4af29053842c01b4cae6bd4c2c3191fc63c',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/yaml',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'thecodingmachine/phpstan-safe-rule' => array(
            'pretty_version' => 'v1.4.3',
            'version' => '1.4.3.0',
            'reference' => '5c804889253ce9498ef185e108e9f94b6023208e',
            'type' => 'phpstan-extension',
            'install_path' => __DIR__ . '/../thecodingmachine/phpstan-safe-rule',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'thecodingmachine/safe' => array(
            'pretty_version' => 'v3.4.0',
            'version' => '3.4.0.0',
            'reference' => '705683a25bacf0d4860c7dea4d7947bfd09eea19',
            'type' => 'library',
            'install_path' => __DIR__ . '/../thecodingmachine/safe',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'thephpf/attestation' => array(
            'pretty_version' => '0.0.5',
            'version' => '0.0.5.0',
            'reference' => 'fa81efb3f6f8147287ebaebd81ff6688e0f2ec9d',
            'type' => 'library',
            'install_path' => __DIR__ . '/../thephpf/attestation',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'theseer/tokenizer' => array(
            'pretty_version' => '1.3.1',
            'version' => '1.3.1.0',
            'reference' => 'b7489ce515e168639d17feec34b8847c326b0b3c',
            'type' => 'library',
            'install_path' => __DIR__ . '/../theseer/tokenizer',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'webmozart/assert' => array(
            'pretty_version' => '1.12.1',
            'version' => '1.12.1.0',
            'reference' => '9be6926d8b485f55b9229203f962b51ed377ba68',
            'type' => 'library',
            'install_path' => __DIR__ . '/../webmozart/assert',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
    ),
);
<?php

// autoload_real.php @generated by Composer

class ComposerAutoloaderInit020913853b4b516248bbc0df3c21c772
{
    private static $loader;

    public static function loadClassLoader($class)
    {
        if ('Composer\Autoload\ClassLoader' === $class) {
            require __DIR__ . '/ClassLoader.php';
        }
    }

    /**
     * @return \Composer\Autoload\ClassLoader
     */
    public static function getLoader()
    {
        if (null !== self::$loader) {
            return self::$loader;
        }

        require __DIR__ . '/platform_check.php';

        spl_autoload_register(array('ComposerAutoloaderInit020913853b4b516248bbc0df3c21c772', 'loadClassLoader'), true, true);
        self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
        spl_autoload_unregister(array('ComposerAutoloaderInit020913853b4b516248bbc0df3c21c772', 'loadClassLoader'));

        require __DIR__ . '/autoload_static.php';
        call_user_func(\Composer\Autoload\ComposerStaticInit020913853b4b516248bbc0df3c21c772::getInitializer($loader));

        $loader->setClassMapAuthoritative(true);
        $loader->register(true);

        $filesToLoad = \Composer\Autoload\ComposerStaticInit020913853b4b516248bbc0df3c21c772::$files;
        $requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
            if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
                $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;

                require $file;
            }
        }, null, null);
        foreach ($filesToLoad as $fileIdentifier => $file) {
            $requireFile($fileIdentifier, $file);
        }

        return $loader;
    }
}
<?php

// autoload_files.php @generated by Composer

$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);

return array(
    '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
    '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
    '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
    '8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php',
    'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
    'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php',
    'ad155f8f1cf0d418fe49e248db8c661b' => $vendorDir . '/react/promise/src/functions_include.php',
    '9d2b9fc6db0f153a0a149fefb182415e' => $vendorDir . '/symfony/polyfill-php84/bootstrap.php',
    '72243e5536b63e298acb6476f01f1aff' => $vendorDir . '/thecodingmachine/safe/lib/special_cases.php',
    '3f648889e687f31c52f949ba8a9d0873' => $vendorDir . '/thecodingmachine/safe/generated/apache.php',
    'eeb4581d958421a4244aaa4167c6a575' => $vendorDir . '/thecodingmachine/safe/generated/apcu.php',
    '04cb0b3c1dac5b5ddb23c14e3d66dbe9' => $vendorDir . '/thecodingmachine/safe/generated/array.php',
    '450b332a74a9a21e043c5e953485a791' => $vendorDir . '/thecodingmachine/safe/generated/bzip2.php',
    '6e9b7954ecfd7cbb9ca239319d1acdb6' => $vendorDir . '/thecodingmachine/safe/generated/calendar.php',
    '2c6d7e8bd2de9a272a9d4d43b0a4304a' => $vendorDir . '/thecodingmachine/safe/generated/classobj.php',
    '0b8231c1ad0865447c988a4c16b4001f' => $vendorDir . '/thecodingmachine/safe/generated/com.php',
    '7643a71fe1c3256058c8fee234cb86e5' => $vendorDir . '/thecodingmachine/safe/generated/cubrid.php',
    '68e1365710575942efc1d55000032cee' => $vendorDir . '/thecodingmachine/safe/generated/curl.php',
    '02fd26bca803106c5b942a7197c3ad8b' => $vendorDir . '/thecodingmachine/safe/generated/datetime.php',
    'f4817dcbd956cd221b1c31f6fbd5749c' => $vendorDir . '/thecodingmachine/safe/generated/dir.php',
    '51c3f2d10ca61a70dbcea0e38d8e902d' => $vendorDir . '/thecodingmachine/safe/generated/eio.php',
    '1d34f34327ca3e81535963016e3be2c3' => $vendorDir . '/thecodingmachine/safe/generated/errorfunc.php',
    '4fd0ba2d3717b0424d474bebfdafa2b4' => $vendorDir . '/thecodingmachine/safe/generated/exec.php',
    '98f4dae054bc7fb19c13be14935cbdd3' => $vendorDir . '/thecodingmachine/safe/generated/fileinfo.php',
    '5530ae063ba88323eaf0a07904efdf85' => $vendorDir . '/thecodingmachine/safe/generated/filesystem.php',
    '633f4f134975d70e97bddad83348e91a' => $vendorDir . '/thecodingmachine/safe/generated/filter.php',
    'fbd163fc68c5faf73d5ed4002ffd836d' => $vendorDir . '/thecodingmachine/safe/generated/fpm.php',
    '21b511999d61411fab0692ff8795bbed' => $vendorDir . '/thecodingmachine/safe/generated/ftp.php',
    '85fbd73fc92365cd90526b0ea03cae3a' => $vendorDir . '/thecodingmachine/safe/generated/funchand.php',
    'a2e4c6dfdbf36f56f1945ddcbd54e289' => $vendorDir . '/thecodingmachine/safe/generated/gettext.php',
    '51df9c146e0b7dcbdf358d8abd24dbdc' => $vendorDir . '/thecodingmachine/safe/generated/gmp.php',
    '93bb7fe678d7dcfb1322f8e3475a48b0' => $vendorDir . '/thecodingmachine/safe/generated/gnupg.php',
    'c171ba99cf316379ff66468392bf4950' => $vendorDir . '/thecodingmachine/safe/generated/hash.php',
    '5ab4aad4c28e468209fbfcceb2e5e6a5' => $vendorDir . '/thecodingmachine/safe/generated/ibase.php',
    '4d57409c5e8e576b0c64c08d9d731cfb' => $vendorDir . '/thecodingmachine/safe/generated/ibmDb2.php',
    'eeb246d5403972a9d62106e4a4883496' => $vendorDir . '/thecodingmachine/safe/generated/iconv.php',
    'c28a05f498c01b810a714f7214b7a8da' => $vendorDir . '/thecodingmachine/safe/generated/image.php',
    '8063cd92acdf00fd978b5599eb7cc142' => $vendorDir . '/thecodingmachine/safe/generated/imap.php',
    '8bd26dbe768e9c9599edad7b198e5446' => $vendorDir . '/thecodingmachine/safe/generated/info.php',
    'd4362910bde43c0f956b52527effd7d4' => $vendorDir . '/thecodingmachine/safe/generated/inotify.php',
    '696ba49197d9b55f0428a12bb5a818e1' => $vendorDir . '/thecodingmachine/safe/generated/json.php',
    '9818aaa99c8647c63f8ef62b7a368160' => $vendorDir . '/thecodingmachine/safe/generated/ldap.php',
    'bcf523ff2a195eb08e0fbb668ed784d0' => $vendorDir . '/thecodingmachine/safe/generated/libxml.php',
    '68be68a9a8b95bb56cab6109ff03bc88' => $vendorDir . '/thecodingmachine/safe/generated/lzf.php',
    'bdca804bb0904ea9f53f328dfc0bb8a5' => $vendorDir . '/thecodingmachine/safe/generated/mailparse.php',
    'b0a3fcac3eaf55445796d6af26b89366' => $vendorDir . '/thecodingmachine/safe/generated/mbstring.php',
    '98de16b8db03eb0cb4d318b4402215a6' => $vendorDir . '/thecodingmachine/safe/generated/misc.php',
    '7cefd81607cd21b8b3a15656eb6465f5' => $vendorDir . '/thecodingmachine/safe/generated/mysql.php',
    'aaf438b080089c6d0686679cd34aa72e' => $vendorDir . '/thecodingmachine/safe/generated/mysqli.php',
    'cbac956836b72483dcff1ac39d5c0a0f' => $vendorDir . '/thecodingmachine/safe/generated/network.php',
    '6c8f89dfbdc117d7871f572269363f25' => $vendorDir . '/thecodingmachine/safe/generated/oci8.php',
    '169a669966a45c06bf55ed029122729b' => $vendorDir . '/thecodingmachine/safe/generated/opcache.php',
    'def61bf4fecd4d4bca7354919cd69302' => $vendorDir . '/thecodingmachine/safe/generated/openssl.php',
    '26bb010649a6d32d4120181458aa6ef2' => $vendorDir . '/thecodingmachine/safe/generated/outcontrol.php',
    '002ebcb842e2c0d5b7f67fe64cc93158' => $vendorDir . '/thecodingmachine/safe/generated/pcntl.php',
    '86df38612982dade72c7085ce7eca81f' => $vendorDir . '/thecodingmachine/safe/generated/pcre.php',
    '1fc22f445c69ea8706e82fce301c0831' => $vendorDir . '/thecodingmachine/safe/generated/pgsql.php',
    'c70b42561584f7144bff38cd63c4eef3' => $vendorDir . '/thecodingmachine/safe/generated/posix.php',
    '9923214639c32ca5173db03a177d3b63' => $vendorDir . '/thecodingmachine/safe/generated/ps.php',
    '7e9c3f8eae2b5bf42205c4f1295cb7a7' => $vendorDir . '/thecodingmachine/safe/generated/pspell.php',
    '91aa91f6245c349c2e2e88bd0025f199' => $vendorDir . '/thecodingmachine/safe/generated/readline.php',
    'ccf972e9e1fd8935f45f95e13b54ad59' => $vendorDir . '/thecodingmachine/safe/generated/rnp.php',
    'd43773cacb9e5e8e897aa255e32007d1' => $vendorDir . '/thecodingmachine/safe/generated/rpminfo.php',
    'f053a3849e9e8383762b34b91db0320b' => $vendorDir . '/thecodingmachine/safe/generated/rrd.php',
    '775b964f72f827a1bf87c65ab5b10800' => $vendorDir . '/thecodingmachine/safe/generated/sem.php',
    '816428bd69c29ab5e1ed622af5dca0cd' => $vendorDir . '/thecodingmachine/safe/generated/session.php',
    '5093e233bedbefaef0df262bfbab0a5c' => $vendorDir . '/thecodingmachine/safe/generated/shmop.php',
    'b080617b1d949683c2e37f8f01dc0e15' => $vendorDir . '/thecodingmachine/safe/generated/sockets.php',
    '2708aa182ddcfe6ce27c96acaaa40f69' => $vendorDir . '/thecodingmachine/safe/generated/sodium.php',
    'f1b96cb260a5baeea9a7285cda82a1ec' => $vendorDir . '/thecodingmachine/safe/generated/solr.php',
    '3fd8853757d0fe3557c179efb807afeb' => $vendorDir . '/thecodingmachine/safe/generated/spl.php',
    '9312ce96a51c846913fcda5f186d58dd' => $vendorDir . '/thecodingmachine/safe/generated/sqlsrv.php',
    'd3eb383ad0b8b962b29dc4afd29d6715' => $vendorDir . '/thecodingmachine/safe/generated/ssdeep.php',
    '42a09bc448f441a0b9f9367ea975c0bf' => $vendorDir . '/thecodingmachine/safe/generated/ssh2.php',
    'ef711077d356d1b33ca0b10b67b0be8f' => $vendorDir . '/thecodingmachine/safe/generated/stream.php',
    '764b09f6df081cbb2807b97c6ace3866' => $vendorDir . '/thecodingmachine/safe/generated/strings.php',
    'ef241678769fee4a44aaa288f3b78aa1' => $vendorDir . '/thecodingmachine/safe/generated/swoole.php',
    '0efc8f6778cba932b9e2a89e28de2452' => $vendorDir . '/thecodingmachine/safe/generated/uodbc.php',
    'd383d32907b98af53ee9208c62204fd0' => $vendorDir . '/thecodingmachine/safe/generated/uopz.php',
    '2fd2e4060f7fe772660f002ce38f0b71' => $vendorDir . '/thecodingmachine/safe/generated/url.php',
    '782249e03deebeaf57b9991ff5493aa0' => $vendorDir . '/thecodingmachine/safe/generated/var.php',
    '344440cd1cd7200fdb4f12af0d3c587f' => $vendorDir . '/thecodingmachine/safe/generated/xdiff.php',
    '3599f369219c658a5fb6c4fe66832f62' => $vendorDir . '/thecodingmachine/safe/generated/xml.php',
    '7fcd313da9fae337051b091b3492c21b' => $vendorDir . '/thecodingmachine/safe/generated/xmlrpc.php',
    'd668c74cfa92d893b582356733d9a80e' => $vendorDir . '/thecodingmachine/safe/generated/yaml.php',
    '4af1dca6db8c527c6eed27bff85ff0e5' => $vendorDir . '/thecodingmachine/safe/generated/yaz.php',
    'fe43ca06499ac37bc2dedd823af71eb5' => $vendorDir . '/thecodingmachine/safe/generated/zip.php',
    '356736db98a6834f0a886b8d509b0ecd' => $vendorDir . '/thecodingmachine/safe/generated/zlib.php',
);
<?php

// autoload_classmap.php @generated by Composer

$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);

return array(
    'Composer\\Advisory\\AuditConfig' => $vendorDir . '/composer/composer/src/Composer/Advisory/AuditConfig.php',
    'Composer\\Advisory\\Auditor' => $vendorDir . '/composer/composer/src/Composer/Advisory/Auditor.php',
    'Composer\\Advisory\\IgnoredSecurityAdvisory' => $vendorDir . '/composer/composer/src/Composer/Advisory/IgnoredSecurityAdvisory.php',
    'Composer\\Advisory\\PartialSecurityAdvisory' => $vendorDir . '/composer/composer/src/Composer/Advisory/PartialSecurityAdvisory.php',
    'Composer\\Advisory\\SecurityAdvisory' => $vendorDir . '/composer/composer/src/Composer/Advisory/SecurityAdvisory.php',
    'Composer\\Autoload\\AutoloadGenerator' => $vendorDir . '/composer/composer/src/Composer/Autoload/AutoloadGenerator.php',
    'Composer\\Autoload\\ClassLoader' => $vendorDir . '/composer/composer/src/Composer/Autoload/ClassLoader.php',
    'Composer\\Autoload\\ClassMapGenerator' => $vendorDir . '/composer/composer/src/Composer/Autoload/ClassMapGenerator.php',
    'Composer\\CaBundle\\CaBundle' => $vendorDir . '/composer/ca-bundle/src/CaBundle.php',
    'Composer\\Cache' => $vendorDir . '/composer/composer/src/Composer/Cache.php',
    'Composer\\ClassMapGenerator\\ClassMap' => $vendorDir . '/composer/class-map-generator/src/ClassMap.php',
    'Composer\\ClassMapGenerator\\ClassMapGenerator' => $vendorDir . '/composer/class-map-generator/src/ClassMapGenerator.php',
    'Composer\\ClassMapGenerator\\FileList' => $vendorDir . '/composer/class-map-generator/src/FileList.php',
    'Composer\\ClassMapGenerator\\PhpFileCleaner' => $vendorDir . '/composer/class-map-generator/src/PhpFileCleaner.php',
    'Composer\\ClassMapGenerator\\PhpFileParser' => $vendorDir . '/composer/class-map-generator/src/PhpFileParser.php',
    'Composer\\Command\\AboutCommand' => $vendorDir . '/composer/composer/src/Composer/Command/AboutCommand.php',
    'Composer\\Command\\ArchiveCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ArchiveCommand.php',
    'Composer\\Command\\AuditCommand' => $vendorDir . '/composer/composer/src/Composer/Command/AuditCommand.php',
    'Composer\\Command\\BaseCommand' => $vendorDir . '/composer/composer/src/Composer/Command/BaseCommand.php',
    'Composer\\Command\\BaseConfigCommand' => $vendorDir . '/composer/composer/src/Composer/Command/BaseConfigCommand.php',
    'Composer\\Command\\BaseDependencyCommand' => $vendorDir . '/composer/composer/src/Composer/Command/BaseDependencyCommand.php',
    'Composer\\Command\\BumpCommand' => $vendorDir . '/composer/composer/src/Composer/Command/BumpCommand.php',
    'Composer\\Command\\CheckPlatformReqsCommand' => $vendorDir . '/composer/composer/src/Composer/Command/CheckPlatformReqsCommand.php',
    'Composer\\Command\\ClearCacheCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ClearCacheCommand.php',
    'Composer\\Command\\CompletionTrait' => $vendorDir . '/composer/composer/src/Composer/Command/CompletionTrait.php',
    'Composer\\Command\\ConfigCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ConfigCommand.php',
    'Composer\\Command\\CreateProjectCommand' => $vendorDir . '/composer/composer/src/Composer/Command/CreateProjectCommand.php',
    'Composer\\Command\\DependsCommand' => $vendorDir . '/composer/composer/src/Composer/Command/DependsCommand.php',
    'Composer\\Command\\DiagnoseCommand' => $vendorDir . '/composer/composer/src/Composer/Command/DiagnoseCommand.php',
    'Composer\\Command\\DumpAutoloadCommand' => $vendorDir . '/composer/composer/src/Composer/Command/DumpAutoloadCommand.php',
    'Composer\\Command\\ExecCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ExecCommand.php',
    'Composer\\Command\\FundCommand' => $vendorDir . '/composer/composer/src/Composer/Command/FundCommand.php',
    'Composer\\Command\\GlobalCommand' => $vendorDir . '/composer/composer/src/Composer/Command/GlobalCommand.php',
    'Composer\\Command\\HomeCommand' => $vendorDir . '/composer/composer/src/Composer/Command/HomeCommand.php',
    'Composer\\Command\\InitCommand' => $vendorDir . '/composer/composer/src/Composer/Command/InitCommand.php',
    'Composer\\Command\\InstallCommand' => $vendorDir . '/composer/composer/src/Composer/Command/InstallCommand.php',
    'Composer\\Command\\LicensesCommand' => $vendorDir . '/composer/composer/src/Composer/Command/LicensesCommand.php',
    'Composer\\Command\\OutdatedCommand' => $vendorDir . '/composer/composer/src/Composer/Command/OutdatedCommand.php',
    'Composer\\Command\\PackageDiscoveryTrait' => $vendorDir . '/composer/composer/src/Composer/Command/PackageDiscoveryTrait.php',
    'Composer\\Command\\ProhibitsCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ProhibitsCommand.php',
    'Composer\\Command\\ReinstallCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ReinstallCommand.php',
    'Composer\\Command\\RemoveCommand' => $vendorDir . '/composer/composer/src/Composer/Command/RemoveCommand.php',
    'Composer\\Command\\RepositoryCommand' => $vendorDir . '/composer/composer/src/Composer/Command/RepositoryCommand.php',
    'Composer\\Command\\RequireCommand' => $vendorDir . '/composer/composer/src/Composer/Command/RequireCommand.php',
    'Composer\\Command\\RunScriptCommand' => $vendorDir . '/composer/composer/src/Composer/Command/RunScriptCommand.php',
    'Composer\\Command\\ScriptAliasCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ScriptAliasCommand.php',
    'Composer\\Command\\SearchCommand' => $vendorDir . '/composer/composer/src/Composer/Command/SearchCommand.php',
    'Composer\\Command\\SelfUpdateCommand' => $vendorDir . '/composer/composer/src/Composer/Command/SelfUpdateCommand.php',
    'Composer\\Command\\ShowCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ShowCommand.php',
    'Composer\\Command\\StatusCommand' => $vendorDir . '/composer/composer/src/Composer/Command/StatusCommand.php',
    'Composer\\Command\\SuggestsCommand' => $vendorDir . '/composer/composer/src/Composer/Command/SuggestsCommand.php',
    'Composer\\Command\\UpdateCommand' => $vendorDir . '/composer/composer/src/Composer/Command/UpdateCommand.php',
    'Composer\\Command\\ValidateCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ValidateCommand.php',
    'Composer\\Compiler' => $vendorDir . '/composer/composer/src/Composer/Compiler.php',
    'Composer\\Composer' => $vendorDir . '/composer/composer/src/Composer/Composer.php',
    'Composer\\Config' => $vendorDir . '/composer/composer/src/Composer/Config.php',
    'Composer\\Config\\ConfigSourceInterface' => $vendorDir . '/composer/composer/src/Composer/Config/ConfigSourceInterface.php',
    'Composer\\Config\\JsonConfigSource' => $vendorDir . '/composer/composer/src/Composer/Config/JsonConfigSource.php',
    'Composer\\Console\\Application' => $vendorDir . '/composer/composer/src/Composer/Console/Application.php',
    'Composer\\Console\\GithubActionError' => $vendorDir . '/composer/composer/src/Composer/Console/GithubActionError.php',
    'Composer\\Console\\HtmlOutputFormatter' => $vendorDir . '/composer/composer/src/Composer/Console/HtmlOutputFormatter.php',
    'Composer\\Console\\Input\\InputArgument' => $vendorDir . '/composer/composer/src/Composer/Console/Input/InputArgument.php',
    'Composer\\Console\\Input\\InputOption' => $vendorDir . '/composer/composer/src/Composer/Console/Input/InputOption.php',
    'Composer\\DependencyResolver\\Decisions' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Decisions.php',
    'Composer\\DependencyResolver\\DefaultPolicy' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/DefaultPolicy.php',
    'Composer\\DependencyResolver\\GenericRule' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/GenericRule.php',
    'Composer\\DependencyResolver\\LocalRepoTransaction' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/LocalRepoTransaction.php',
    'Composer\\DependencyResolver\\LockTransaction' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/LockTransaction.php',
    'Composer\\DependencyResolver\\MultiConflictRule' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/MultiConflictRule.php',
    'Composer\\DependencyResolver\\Operation\\InstallOperation' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/InstallOperation.php',
    'Composer\\DependencyResolver\\Operation\\MarkAliasInstalledOperation' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php',
    'Composer\\DependencyResolver\\Operation\\MarkAliasUninstalledOperation' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php',
    'Composer\\DependencyResolver\\Operation\\OperationInterface' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/OperationInterface.php',
    'Composer\\DependencyResolver\\Operation\\SolverOperation' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/SolverOperation.php',
    'Composer\\DependencyResolver\\Operation\\UninstallOperation' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/UninstallOperation.php',
    'Composer\\DependencyResolver\\Operation\\UpdateOperation' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/UpdateOperation.php',
    'Composer\\DependencyResolver\\PolicyInterface' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/PolicyInterface.php',
    'Composer\\DependencyResolver\\Pool' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Pool.php',
    'Composer\\DependencyResolver\\PoolBuilder' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/PoolBuilder.php',
    'Composer\\DependencyResolver\\PoolOptimizer' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/PoolOptimizer.php',
    'Composer\\DependencyResolver\\Problem' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Problem.php',
    'Composer\\DependencyResolver\\Request' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Request.php',
    'Composer\\DependencyResolver\\Rule' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Rule.php',
    'Composer\\DependencyResolver\\Rule2Literals' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Rule2Literals.php',
    'Composer\\DependencyResolver\\RuleSet' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/RuleSet.php',
    'Composer\\DependencyResolver\\RuleSetGenerator' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/RuleSetGenerator.php',
    'Composer\\DependencyResolver\\RuleSetIterator' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/RuleSetIterator.php',
    'Composer\\DependencyResolver\\RuleWatchChain' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/RuleWatchChain.php',
    'Composer\\DependencyResolver\\RuleWatchGraph' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/RuleWatchGraph.php',
    'Composer\\DependencyResolver\\RuleWatchNode' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/RuleWatchNode.php',
    'Composer\\DependencyResolver\\SecurityAdvisoryPoolFilter' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/SecurityAdvisoryPoolFilter.php',
    'Composer\\DependencyResolver\\Solver' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Solver.php',
    'Composer\\DependencyResolver\\SolverBugException' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/SolverBugException.php',
    'Composer\\DependencyResolver\\SolverProblemsException' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/SolverProblemsException.php',
    'Composer\\DependencyResolver\\Transaction' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Transaction.php',
    'Composer\\Downloader\\ArchiveDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/ArchiveDownloader.php',
    'Composer\\Downloader\\ChangeReportInterface' => $vendorDir . '/composer/composer/src/Composer/Downloader/ChangeReportInterface.php',
    'Composer\\Downloader\\DownloadManager' => $vendorDir . '/composer/composer/src/Composer/Downloader/DownloadManager.php',
    'Composer\\Downloader\\DownloaderInterface' => $vendorDir . '/composer/composer/src/Composer/Downloader/DownloaderInterface.php',
    'Composer\\Downloader\\DvcsDownloaderInterface' => $vendorDir . '/composer/composer/src/Composer/Downloader/DvcsDownloaderInterface.php',
    'Composer\\Downloader\\FileDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/FileDownloader.php',
    'Composer\\Downloader\\FilesystemException' => $vendorDir . '/composer/composer/src/Composer/Downloader/FilesystemException.php',
    'Composer\\Downloader\\FossilDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/FossilDownloader.php',
    'Composer\\Downloader\\GitDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/GitDownloader.php',
    'Composer\\Downloader\\GzipDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/GzipDownloader.php',
    'Composer\\Downloader\\HgDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/HgDownloader.php',
    'Composer\\Downloader\\MaxFileSizeExceededException' => $vendorDir . '/composer/composer/src/Composer/Downloader/MaxFileSizeExceededException.php',
    'Composer\\Downloader\\PathDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/PathDownloader.php',
    'Composer\\Downloader\\PerforceDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/PerforceDownloader.php',
    'Composer\\Downloader\\PharDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/PharDownloader.php',
    'Composer\\Downloader\\RarDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/RarDownloader.php',
    'Composer\\Downloader\\SvnDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/SvnDownloader.php',
    'Composer\\Downloader\\TarDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/TarDownloader.php',
    'Composer\\Downloader\\TransportException' => $vendorDir . '/composer/composer/src/Composer/Downloader/TransportException.php',
    'Composer\\Downloader\\VcsCapableDownloaderInterface' => $vendorDir . '/composer/composer/src/Composer/Downloader/VcsCapableDownloaderInterface.php',
    'Composer\\Downloader\\VcsDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/VcsDownloader.php',
    'Composer\\Downloader\\XzDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/XzDownloader.php',
    'Composer\\Downloader\\ZipDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/ZipDownloader.php',
    'Composer\\EventDispatcher\\Event' => $vendorDir . '/composer/composer/src/Composer/EventDispatcher/Event.php',
    'Composer\\EventDispatcher\\EventDispatcher' => $vendorDir . '/composer/composer/src/Composer/EventDispatcher/EventDispatcher.php',
    'Composer\\EventDispatcher\\EventSubscriberInterface' => $vendorDir . '/composer/composer/src/Composer/EventDispatcher/EventSubscriberInterface.php',
    'Composer\\EventDispatcher\\ScriptExecutionException' => $vendorDir . '/composer/composer/src/Composer/EventDispatcher/ScriptExecutionException.php',
    'Composer\\Exception\\IrrecoverableDownloadException' => $vendorDir . '/composer/composer/src/Composer/Exception/IrrecoverableDownloadException.php',
    'Composer\\Exception\\NoSslException' => $vendorDir . '/composer/composer/src/Composer/Exception/NoSslException.php',
    'Composer\\Factory' => $vendorDir . '/composer/composer/src/Composer/Factory.php',
    'Composer\\Filter\\PlatformRequirementFilter\\IgnoreAllPlatformRequirementFilter' => $vendorDir . '/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreAllPlatformRequirementFilter.php',
    'Composer\\Filter\\PlatformRequirementFilter\\IgnoreListPlatformRequirementFilter' => $vendorDir . '/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilter.php',
    'Composer\\Filter\\PlatformRequirementFilter\\IgnoreNothingPlatformRequirementFilter' => $vendorDir . '/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilter.php',
    'Composer\\Filter\\PlatformRequirementFilter\\PlatformRequirementFilterFactory' => $vendorDir . '/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterFactory.php',
    'Composer\\Filter\\PlatformRequirementFilter\\PlatformRequirementFilterInterface' => $vendorDir . '/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterInterface.php',
    'Composer\\IO\\BaseIO' => $vendorDir . '/composer/composer/src/Composer/IO/BaseIO.php',
    'Composer\\IO\\BufferIO' => $vendorDir . '/composer/composer/src/Composer/IO/BufferIO.php',
    'Composer\\IO\\ConsoleIO' => $vendorDir . '/composer/composer/src/Composer/IO/ConsoleIO.php',
    'Composer\\IO\\IOInterface' => $vendorDir . '/composer/composer/src/Composer/IO/IOInterface.php',
    'Composer\\IO\\NullIO' => $vendorDir . '/composer/composer/src/Composer/IO/NullIO.php',
    'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
    'Composer\\Installer' => $vendorDir . '/composer/composer/src/Composer/Installer.php',
    'Composer\\Installer\\BinaryInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/BinaryInstaller.php',
    'Composer\\Installer\\BinaryPresenceInterface' => $vendorDir . '/composer/composer/src/Composer/Installer/BinaryPresenceInterface.php',
    'Composer\\Installer\\InstallationManager' => $vendorDir . '/composer/composer/src/Composer/Installer/InstallationManager.php',
    'Composer\\Installer\\InstallerEvent' => $vendorDir . '/composer/composer/src/Composer/Installer/InstallerEvent.php',
    'Composer\\Installer\\InstallerEvents' => $vendorDir . '/composer/composer/src/Composer/Installer/InstallerEvents.php',
    'Composer\\Installer\\InstallerInterface' => $vendorDir . '/composer/composer/src/Composer/Installer/InstallerInterface.php',
    'Composer\\Installer\\LibraryInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/LibraryInstaller.php',
    'Composer\\Installer\\MetapackageInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/MetapackageInstaller.php',
    'Composer\\Installer\\NoopInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/NoopInstaller.php',
    'Composer\\Installer\\PackageEvent' => $vendorDir . '/composer/composer/src/Composer/Installer/PackageEvent.php',
    'Composer\\Installer\\PackageEvents' => $vendorDir . '/composer/composer/src/Composer/Installer/PackageEvents.php',
    'Composer\\Installer\\PluginInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/PluginInstaller.php',
    'Composer\\Installer\\ProjectInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/ProjectInstaller.php',
    'Composer\\Installer\\SuggestedPackagesReporter' => $vendorDir . '/composer/composer/src/Composer/Installer/SuggestedPackagesReporter.php',
    'Composer\\Json\\JsonFile' => $vendorDir . '/composer/composer/src/Composer/Json/JsonFile.php',
    'Composer\\Json\\JsonFormatter' => $vendorDir . '/composer/composer/src/Composer/Json/JsonFormatter.php',
    'Composer\\Json\\JsonManipulator' => $vendorDir . '/composer/composer/src/Composer/Json/JsonManipulator.php',
    'Composer\\Json\\JsonValidationException' => $vendorDir . '/composer/composer/src/Composer/Json/JsonValidationException.php',
    'Composer\\MetadataMinifier\\MetadataMinifier' => $vendorDir . '/composer/metadata-minifier/src/MetadataMinifier.php',
    'Composer\\PHPStan\\ConfigReturnTypeExtension' => $vendorDir . '/composer/composer/src/Composer/PHPStan/ConfigReturnTypeExtension.php',
    'Composer\\PHPStan\\RuleReasonDataReturnTypeExtension' => $vendorDir . '/composer/composer/src/Composer/PHPStan/RuleReasonDataReturnTypeExtension.php',
    'Composer\\Package\\AliasPackage' => $vendorDir . '/composer/composer/src/Composer/Package/AliasPackage.php',
    'Composer\\Package\\Archiver\\ArchivableFilesFilter' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFilter.php',
    'Composer\\Package\\Archiver\\ArchivableFilesFinder' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFinder.php',
    'Composer\\Package\\Archiver\\ArchiveManager' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/ArchiveManager.php',
    'Composer\\Package\\Archiver\\ArchiverInterface' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/ArchiverInterface.php',
    'Composer\\Package\\Archiver\\BaseExcludeFilter' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/BaseExcludeFilter.php',
    'Composer\\Package\\Archiver\\ComposerExcludeFilter' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/ComposerExcludeFilter.php',
    'Composer\\Package\\Archiver\\GitExcludeFilter' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/GitExcludeFilter.php',
    'Composer\\Package\\Archiver\\PharArchiver' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/PharArchiver.php',
    'Composer\\Package\\Archiver\\ZipArchiver' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/ZipArchiver.php',
    'Composer\\Package\\BasePackage' => $vendorDir . '/composer/composer/src/Composer/Package/BasePackage.php',
    'Composer\\Package\\Comparer\\Comparer' => $vendorDir . '/composer/composer/src/Composer/Package/Comparer/Comparer.php',
    'Composer\\Package\\CompleteAliasPackage' => $vendorDir . '/composer/composer/src/Composer/Package/CompleteAliasPackage.php',
    'Composer\\Package\\CompletePackage' => $vendorDir . '/composer/composer/src/Composer/Package/CompletePackage.php',
    'Composer\\Package\\CompletePackageInterface' => $vendorDir . '/composer/composer/src/Composer/Package/CompletePackageInterface.php',
    'Composer\\Package\\Dumper\\ArrayDumper' => $vendorDir . '/composer/composer/src/Composer/Package/Dumper/ArrayDumper.php',
    'Composer\\Package\\Link' => $vendorDir . '/composer/composer/src/Composer/Package/Link.php',
    'Composer\\Package\\Loader\\ArrayLoader' => $vendorDir . '/composer/composer/src/Composer/Package/Loader/ArrayLoader.php',
    'Composer\\Package\\Loader\\InvalidPackageException' => $vendorDir . '/composer/composer/src/Composer/Package/Loader/InvalidPackageException.php',
    'Composer\\Package\\Loader\\JsonLoader' => $vendorDir . '/composer/composer/src/Composer/Package/Loader/JsonLoader.php',
    'Composer\\Package\\Loader\\LoaderInterface' => $vendorDir . '/composer/composer/src/Composer/Package/Loader/LoaderInterface.php',
    'Composer\\Package\\Loader\\RootPackageLoader' => $vendorDir . '/composer/composer/src/Composer/Package/Loader/RootPackageLoader.php',
    'Composer\\Package\\Loader\\ValidatingArrayLoader' => $vendorDir . '/composer/composer/src/Composer/Package/Loader/ValidatingArrayLoader.php',
    'Composer\\Package\\Locker' => $vendorDir . '/composer/composer/src/Composer/Package/Locker.php',
    'Composer\\Package\\Package' => $vendorDir . '/composer/composer/src/Composer/Package/Package.php',
    'Composer\\Package\\PackageInterface' => $vendorDir . '/composer/composer/src/Composer/Package/PackageInterface.php',
    'Composer\\Package\\RootAliasPackage' => $vendorDir . '/composer/composer/src/Composer/Package/RootAliasPackage.php',
    'Composer\\Package\\RootPackage' => $vendorDir . '/composer/composer/src/Composer/Package/RootPackage.php',
    'Composer\\Package\\RootPackageInterface' => $vendorDir . '/composer/composer/src/Composer/Package/RootPackageInterface.php',
    'Composer\\Package\\Version\\StabilityFilter' => $vendorDir . '/composer/composer/src/Composer/Package/Version/StabilityFilter.php',
    'Composer\\Package\\Version\\VersionBumper' => $vendorDir . '/composer/composer/src/Composer/Package/Version/VersionBumper.php',
    'Composer\\Package\\Version\\VersionGuesser' => $vendorDir . '/composer/composer/src/Composer/Package/Version/VersionGuesser.php',
    'Composer\\Package\\Version\\VersionParser' => $vendorDir . '/composer/composer/src/Composer/Package/Version/VersionParser.php',
    'Composer\\Package\\Version\\VersionSelector' => $vendorDir . '/composer/composer/src/Composer/Package/Version/VersionSelector.php',
    'Composer\\PartialComposer' => $vendorDir . '/composer/composer/src/Composer/PartialComposer.php',
    'Composer\\Pcre\\MatchAllResult' => $vendorDir . '/composer/pcre/src/MatchAllResult.php',
    'Composer\\Pcre\\MatchAllStrictGroupsResult' => $vendorDir . '/composer/pcre/src/MatchAllStrictGroupsResult.php',
    'Composer\\Pcre\\MatchAllWithOffsetsResult' => $vendorDir . '/composer/pcre/src/MatchAllWithOffsetsResult.php',
    'Composer\\Pcre\\MatchResult' => $vendorDir . '/composer/pcre/src/MatchResult.php',
    'Composer\\Pcre\\MatchStrictGroupsResult' => $vendorDir . '/composer/pcre/src/MatchStrictGroupsResult.php',
    'Composer\\Pcre\\MatchWithOffsetsResult' => $vendorDir . '/composer/pcre/src/MatchWithOffsetsResult.php',
    'Composer\\Pcre\\PHPStan\\InvalidRegexPatternRule' => $vendorDir . '/composer/pcre/src/PHPStan/InvalidRegexPatternRule.php',
    'Composer\\Pcre\\PHPStan\\PregMatchFlags' => $vendorDir . '/composer/pcre/src/PHPStan/PregMatchFlags.php',
    'Composer\\Pcre\\PHPStan\\PregMatchParameterOutTypeExtension' => $vendorDir . '/composer/pcre/src/PHPStan/PregMatchParameterOutTypeExtension.php',
    'Composer\\Pcre\\PHPStan\\PregMatchTypeSpecifyingExtension' => $vendorDir . '/composer/pcre/src/PHPStan/PregMatchTypeSpecifyingExtension.php',
    'Composer\\Pcre\\PHPStan\\PregReplaceCallbackClosureTypeExtension' => $vendorDir . '/composer/pcre/src/PHPStan/PregReplaceCallbackClosureTypeExtension.php',
    'Composer\\Pcre\\PHPStan\\UnsafeStrictGroupsCallRule' => $vendorDir . '/composer/pcre/src/PHPStan/UnsafeStrictGroupsCallRule.php',
    'Composer\\Pcre\\PcreException' => $vendorDir . '/composer/pcre/src/PcreException.php',
    'Composer\\Pcre\\Preg' => $vendorDir . '/composer/pcre/src/Preg.php',
    'Composer\\Pcre\\Regex' => $vendorDir . '/composer/pcre/src/Regex.php',
    'Composer\\Pcre\\ReplaceResult' => $vendorDir . '/composer/pcre/src/ReplaceResult.php',
    'Composer\\Pcre\\UnexpectedNullMatchException' => $vendorDir . '/composer/pcre/src/UnexpectedNullMatchException.php',
    'Composer\\Platform\\HhvmDetector' => $vendorDir . '/composer/composer/src/Composer/Platform/HhvmDetector.php',
    'Composer\\Platform\\Runtime' => $vendorDir . '/composer/composer/src/Composer/Platform/Runtime.php',
    'Composer\\Platform\\Version' => $vendorDir . '/composer/composer/src/Composer/Platform/Version.php',
    'Composer\\Plugin\\Capability\\Capability' => $vendorDir . '/composer/composer/src/Composer/Plugin/Capability/Capability.php',
    'Composer\\Plugin\\Capability\\CommandProvider' => $vendorDir . '/composer/composer/src/Composer/Plugin/Capability/CommandProvider.php',
    'Composer\\Plugin\\Capable' => $vendorDir . '/composer/composer/src/Composer/Plugin/Capable.php',
    'Composer\\Plugin\\CommandEvent' => $vendorDir . '/composer/composer/src/Composer/Plugin/CommandEvent.php',
    'Composer\\Plugin\\PluginBlockedException' => $vendorDir . '/composer/composer/src/Composer/Plugin/PluginBlockedException.php',
    'Composer\\Plugin\\PluginEvents' => $vendorDir . '/composer/composer/src/Composer/Plugin/PluginEvents.php',
    'Composer\\Plugin\\PluginInterface' => $vendorDir . '/composer/composer/src/Composer/Plugin/PluginInterface.php',
    'Composer\\Plugin\\PluginManager' => $vendorDir . '/composer/composer/src/Composer/Plugin/PluginManager.php',
    'Composer\\Plugin\\PostFileDownloadEvent' => $vendorDir . '/composer/composer/src/Composer/Plugin/PostFileDownloadEvent.php',
    'Composer\\Plugin\\PreCommandRunEvent' => $vendorDir . '/composer/composer/src/Composer/Plugin/PreCommandRunEvent.php',
    'Composer\\Plugin\\PreFileDownloadEvent' => $vendorDir . '/composer/composer/src/Composer/Plugin/PreFileDownloadEvent.php',
    'Composer\\Plugin\\PrePoolCreateEvent' => $vendorDir . '/composer/composer/src/Composer/Plugin/PrePoolCreateEvent.php',
    'Composer\\Question\\StrictConfirmationQuestion' => $vendorDir . '/composer/composer/src/Composer/Question/StrictConfirmationQuestion.php',
    'Composer\\Repository\\AdvisoryProviderInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/AdvisoryProviderInterface.php',
    'Composer\\Repository\\ArrayRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/ArrayRepository.php',
    'Composer\\Repository\\ArtifactRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/ArtifactRepository.php',
    'Composer\\Repository\\CanonicalPackagesTrait' => $vendorDir . '/composer/composer/src/Composer/Repository/CanonicalPackagesTrait.php',
    'Composer\\Repository\\ComposerRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/ComposerRepository.php',
    'Composer\\Repository\\CompositeRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/CompositeRepository.php',
    'Composer\\Repository\\ConfigurableRepositoryInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/ConfigurableRepositoryInterface.php',
    'Composer\\Repository\\FilesystemRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/FilesystemRepository.php',
    'Composer\\Repository\\FilterRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/FilterRepository.php',
    'Composer\\Repository\\InstalledArrayRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/InstalledArrayRepository.php',
    'Composer\\Repository\\InstalledFilesystemRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/InstalledFilesystemRepository.php',
    'Composer\\Repository\\InstalledRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/InstalledRepository.php',
    'Composer\\Repository\\InstalledRepositoryInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/InstalledRepositoryInterface.php',
    'Composer\\Repository\\InvalidRepositoryException' => $vendorDir . '/composer/composer/src/Composer/Repository/InvalidRepositoryException.php',
    'Composer\\Repository\\LockArrayRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/LockArrayRepository.php',
    'Composer\\Repository\\PackageRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/PackageRepository.php',
    'Composer\\Repository\\PathRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/PathRepository.php',
    'Composer\\Repository\\PearRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/PearRepository.php',
    'Composer\\Repository\\PlatformRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/PlatformRepository.php',
    'Composer\\Repository\\RepositoryFactory' => $vendorDir . '/composer/composer/src/Composer/Repository/RepositoryFactory.php',
    'Composer\\Repository\\RepositoryInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/RepositoryInterface.php',
    'Composer\\Repository\\RepositoryManager' => $vendorDir . '/composer/composer/src/Composer/Repository/RepositoryManager.php',
    'Composer\\Repository\\RepositorySecurityException' => $vendorDir . '/composer/composer/src/Composer/Repository/RepositorySecurityException.php',
    'Composer\\Repository\\RepositorySet' => $vendorDir . '/composer/composer/src/Composer/Repository/RepositorySet.php',
    'Composer\\Repository\\RepositoryUtils' => $vendorDir . '/composer/composer/src/Composer/Repository/RepositoryUtils.php',
    'Composer\\Repository\\RootPackageRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/RootPackageRepository.php',
    'Composer\\Repository\\VcsRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/VcsRepository.php',
    'Composer\\Repository\\Vcs\\ForgejoDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/ForgejoDriver.php',
    'Composer\\Repository\\Vcs\\FossilDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/FossilDriver.php',
    'Composer\\Repository\\Vcs\\GitBitbucketDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/GitBitbucketDriver.php',
    'Composer\\Repository\\Vcs\\GitDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/GitDriver.php',
    'Composer\\Repository\\Vcs\\GitHubDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/GitHubDriver.php',
    'Composer\\Repository\\Vcs\\GitLabDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/GitLabDriver.php',
    'Composer\\Repository\\Vcs\\HgDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/HgDriver.php',
    'Composer\\Repository\\Vcs\\PerforceDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/PerforceDriver.php',
    'Composer\\Repository\\Vcs\\SvnDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/SvnDriver.php',
    'Composer\\Repository\\Vcs\\VcsDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/VcsDriver.php',
    'Composer\\Repository\\Vcs\\VcsDriverInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/VcsDriverInterface.php',
    'Composer\\Repository\\VersionCacheInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/VersionCacheInterface.php',
    'Composer\\Repository\\WritableArrayRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/WritableArrayRepository.php',
    'Composer\\Repository\\WritableRepositoryInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/WritableRepositoryInterface.php',
    'Composer\\Script\\Event' => $vendorDir . '/composer/composer/src/Composer/Script/Event.php',
    'Composer\\Script\\ScriptEvents' => $vendorDir . '/composer/composer/src/Composer/Script/ScriptEvents.php',
    'Composer\\SelfUpdate\\Keys' => $vendorDir . '/composer/composer/src/Composer/SelfUpdate/Keys.php',
    'Composer\\SelfUpdate\\Versions' => $vendorDir . '/composer/composer/src/Composer/SelfUpdate/Versions.php',
    'Composer\\Semver\\Comparator' => $vendorDir . '/composer/semver/src/Comparator.php',
    'Composer\\Semver\\CompilingMatcher' => $vendorDir . '/composer/semver/src/CompilingMatcher.php',
    'Composer\\Semver\\Constraint\\Bound' => $vendorDir . '/composer/semver/src/Constraint/Bound.php',
    'Composer\\Semver\\Constraint\\Constraint' => $vendorDir . '/composer/semver/src/Constraint/Constraint.php',
    'Composer\\Semver\\Constraint\\ConstraintInterface' => $vendorDir . '/composer/semver/src/Constraint/ConstraintInterface.php',
    'Composer\\Semver\\Constraint\\MatchAllConstraint' => $vendorDir . '/composer/semver/src/Constraint/MatchAllConstraint.php',
    'Composer\\Semver\\Constraint\\MatchNoneConstraint' => $vendorDir . '/composer/semver/src/Constraint/MatchNoneConstraint.php',
    'Composer\\Semver\\Constraint\\MultiConstraint' => $vendorDir . '/composer/semver/src/Constraint/MultiConstraint.php',
    'Composer\\Semver\\Interval' => $vendorDir . '/composer/semver/src/Interval.php',
    'Composer\\Semver\\Intervals' => $vendorDir . '/composer/semver/src/Intervals.php',
    'Composer\\Semver\\Semver' => $vendorDir . '/composer/semver/src/Semver.php',
    'Composer\\Semver\\VersionParser' => $vendorDir . '/composer/semver/src/VersionParser.php',
    'Composer\\Spdx\\SpdxLicenses' => $vendorDir . '/composer/spdx-licenses/src/SpdxLicenses.php',
    'Composer\\Util\\AuthHelper' => $vendorDir . '/composer/composer/src/Composer/Util/AuthHelper.php',
    'Composer\\Util\\Bitbucket' => $vendorDir . '/composer/composer/src/Composer/Util/Bitbucket.php',
    'Composer\\Util\\ComposerMirror' => $vendorDir . '/composer/composer/src/Composer/Util/ComposerMirror.php',
    'Composer\\Util\\ConfigValidator' => $vendorDir . '/composer/composer/src/Composer/Util/ConfigValidator.php',
    'Composer\\Util\\ErrorHandler' => $vendorDir . '/composer/composer/src/Composer/Util/ErrorHandler.php',
    'Composer\\Util\\Filesystem' => $vendorDir . '/composer/composer/src/Composer/Util/Filesystem.php',
    'Composer\\Util\\Forgejo' => $vendorDir . '/composer/composer/src/Composer/Util/Forgejo.php',
    'Composer\\Util\\ForgejoRepositoryData' => $vendorDir . '/composer/composer/src/Composer/Util/ForgejoRepositoryData.php',
    'Composer\\Util\\ForgejoUrl' => $vendorDir . '/composer/composer/src/Composer/Util/ForgejoUrl.php',
    'Composer\\Util\\Git' => $vendorDir . '/composer/composer/src/Composer/Util/Git.php',
    'Composer\\Util\\GitHub' => $vendorDir . '/composer/composer/src/Composer/Util/GitHub.php',
    'Composer\\Util\\GitLab' => $vendorDir . '/composer/composer/src/Composer/Util/GitLab.php',
    'Composer\\Util\\Hg' => $vendorDir . '/composer/composer/src/Composer/Util/Hg.php',
    'Composer\\Util\\HttpDownloader' => $vendorDir . '/composer/composer/src/Composer/Util/HttpDownloader.php',
    'Composer\\Util\\Http\\CurlDownloader' => $vendorDir . '/composer/composer/src/Composer/Util/Http/CurlDownloader.php',
    'Composer\\Util\\Http\\CurlResponse' => $vendorDir . '/composer/composer/src/Composer/Util/Http/CurlResponse.php',
    'Composer\\Util\\Http\\ProxyItem' => $vendorDir . '/composer/composer/src/Composer/Util/Http/ProxyItem.php',
    'Composer\\Util\\Http\\ProxyManager' => $vendorDir . '/composer/composer/src/Composer/Util/Http/ProxyManager.php',
    'Composer\\Util\\Http\\RequestProxy' => $vendorDir . '/composer/composer/src/Composer/Util/Http/RequestProxy.php',
    'Composer\\Util\\Http\\Response' => $vendorDir . '/composer/composer/src/Composer/Util/Http/Response.php',
    'Composer\\Util\\IniHelper' => $vendorDir . '/composer/composer/src/Composer/Util/IniHelper.php',
    'Composer\\Util\\Loop' => $vendorDir . '/composer/composer/src/Composer/Util/Loop.php',
    'Composer\\Util\\MetadataMinifier' => $vendorDir . '/composer/composer/src/Composer/Util/MetadataMinifier.php',
    'Composer\\Util\\NoProxyPattern' => $vendorDir . '/composer/composer/src/Composer/Util/NoProxyPattern.php',
    'Composer\\Util\\PackageInfo' => $vendorDir . '/composer/composer/src/Composer/Util/PackageInfo.php',
    'Composer\\Util\\PackageSorter' => $vendorDir . '/composer/composer/src/Composer/Util/PackageSorter.php',
    'Composer\\Util\\Perforce' => $vendorDir . '/composer/composer/src/Composer/Util/Perforce.php',
    'Composer\\Util\\Platform' => $vendorDir . '/composer/composer/src/Composer/Util/Platform.php',
    'Composer\\Util\\ProcessExecutor' => $vendorDir . '/composer/composer/src/Composer/Util/ProcessExecutor.php',
    'Composer\\Util\\RemoteFilesystem' => $vendorDir . '/composer/composer/src/Composer/Util/RemoteFilesystem.php',
    'Composer\\Util\\Silencer' => $vendorDir . '/composer/composer/src/Composer/Util/Silencer.php',
    'Composer\\Util\\StreamContextFactory' => $vendorDir . '/composer/composer/src/Composer/Util/StreamContextFactory.php',
    'Composer\\Util\\Svn' => $vendorDir . '/composer/composer/src/Composer/Util/Svn.php',
    'Composer\\Util\\SyncHelper' => $vendorDir . '/composer/composer/src/Composer/Util/SyncHelper.php',
    'Composer\\Util\\Tar' => $vendorDir . '/composer/composer/src/Composer/Util/Tar.php',
    'Composer\\Util\\TlsHelper' => $vendorDir . '/composer/composer/src/Composer/Util/TlsHelper.php',
    'Composer\\Util\\Url' => $vendorDir . '/composer/composer/src/Composer/Util/Url.php',
    'Composer\\Util\\Zip' => $vendorDir . '/composer/composer/src/Composer/Util/Zip.php',
    'Composer\\XdebugHandler\\PhpConfig' => $vendorDir . '/composer/xdebug-handler/src/PhpConfig.php',
    'Composer\\XdebugHandler\\Process' => $vendorDir . '/composer/xdebug-handler/src/Process.php',
    'Composer\\XdebugHandler\\Status' => $vendorDir . '/composer/xdebug-handler/src/Status.php',
    'Composer\\XdebugHandler\\XdebugHandler' => $vendorDir . '/composer/xdebug-handler/src/XdebugHandler.php',
    'Deprecated' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Deprecated.php',
    'Fidry\\CpuCoreCounter\\CpuCoreCounter' => $vendorDir . '/fidry/cpu-core-counter/src/CpuCoreCounter.php',
    'Fidry\\CpuCoreCounter\\Diagnoser' => $vendorDir . '/fidry/cpu-core-counter/src/Diagnoser.php',
    'Fidry\\CpuCoreCounter\\Executor\\ProcOpenExecutor' => $vendorDir . '/fidry/cpu-core-counter/src/Executor/ProcOpenExecutor.php',
    'Fidry\\CpuCoreCounter\\Executor\\ProcessExecutor' => $vendorDir . '/fidry/cpu-core-counter/src/Executor/ProcessExecutor.php',
    'Fidry\\CpuCoreCounter\\Finder\\CmiCmdletLogicalFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/CmiCmdletLogicalFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\CmiCmdletPhysicalFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/CmiCmdletPhysicalFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\CpuCoreFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/CpuCoreFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\CpuInfoFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/CpuInfoFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\DummyCpuCoreFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/DummyCpuCoreFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\EnvVariableFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/EnvVariableFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\FinderRegistry' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/FinderRegistry.php',
    'Fidry\\CpuCoreCounter\\Finder\\HwLogicalFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/HwLogicalFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\HwPhysicalFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/HwPhysicalFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\LscpuLogicalFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/LscpuLogicalFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\LscpuPhysicalFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/LscpuPhysicalFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\NProcFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/NProcFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\NProcessorFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/NProcessorFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\NullCpuCoreFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/NullCpuCoreFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\OnlyInPowerShellFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/OnlyInPowerShellFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\OnlyOnOSFamilyFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/OnlyOnOSFamilyFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\ProcOpenBasedFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/ProcOpenBasedFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\SkipOnOSFamilyFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/SkipOnOSFamilyFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\WindowsRegistryLogicalFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/WindowsRegistryLogicalFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\WmicLogicalFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/WmicLogicalFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\WmicPhysicalFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/WmicPhysicalFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\_NProcessorFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/_NProcessorFinder.php',
    'Fidry\\CpuCoreCounter\\NumberOfCpuCoreNotFound' => $vendorDir . '/fidry/cpu-core-counter/src/NumberOfCpuCoreNotFound.php',
    'Fidry\\CpuCoreCounter\\ParallelisationResult' => $vendorDir . '/fidry/cpu-core-counter/src/ParallelisationResult.php',
    'Illuminate\\Container\\BoundMethod' => $vendorDir . '/illuminate/container/BoundMethod.php',
    'Illuminate\\Container\\Container' => $vendorDir . '/illuminate/container/Container.php',
    'Illuminate\\Container\\ContextualBindingBuilder' => $vendorDir . '/illuminate/container/ContextualBindingBuilder.php',
    'Illuminate\\Container\\EntryNotFoundException' => $vendorDir . '/illuminate/container/EntryNotFoundException.php',
    'Illuminate\\Container\\RewindableGenerator' => $vendorDir . '/illuminate/container/RewindableGenerator.php',
    'Illuminate\\Container\\Util' => $vendorDir . '/illuminate/container/Util.php',
    'Illuminate\\Contracts\\Auth\\Access\\Authorizable' => $vendorDir . '/illuminate/contracts/Auth/Access/Authorizable.php',
    'Illuminate\\Contracts\\Auth\\Access\\Gate' => $vendorDir . '/illuminate/contracts/Auth/Access/Gate.php',
    'Illuminate\\Contracts\\Auth\\Authenticatable' => $vendorDir . '/illuminate/contracts/Auth/Authenticatable.php',
    'Illuminate\\Contracts\\Auth\\CanResetPassword' => $vendorDir . '/illuminate/contracts/Auth/CanResetPassword.php',
    'Illuminate\\Contracts\\Auth\\Factory' => $vendorDir . '/illuminate/contracts/Auth/Factory.php',
    'Illuminate\\Contracts\\Auth\\Guard' => $vendorDir . '/illuminate/contracts/Auth/Guard.php',
    'Illuminate\\Contracts\\Auth\\Middleware\\AuthenticatesRequests' => $vendorDir . '/illuminate/contracts/Auth/Middleware/AuthenticatesRequests.php',
    'Illuminate\\Contracts\\Auth\\MustVerifyEmail' => $vendorDir . '/illuminate/contracts/Auth/MustVerifyEmail.php',
    'Illuminate\\Contracts\\Auth\\PasswordBroker' => $vendorDir . '/illuminate/contracts/Auth/PasswordBroker.php',
    'Illuminate\\Contracts\\Auth\\PasswordBrokerFactory' => $vendorDir . '/illuminate/contracts/Auth/PasswordBrokerFactory.php',
    'Illuminate\\Contracts\\Auth\\StatefulGuard' => $vendorDir . '/illuminate/contracts/Auth/StatefulGuard.php',
    'Illuminate\\Contracts\\Auth\\SupportsBasicAuth' => $vendorDir . '/illuminate/contracts/Auth/SupportsBasicAuth.php',
    'Illuminate\\Contracts\\Auth\\UserProvider' => $vendorDir . '/illuminate/contracts/Auth/UserProvider.php',
    'Illuminate\\Contracts\\Broadcasting\\Broadcaster' => $vendorDir . '/illuminate/contracts/Broadcasting/Broadcaster.php',
    'Illuminate\\Contracts\\Broadcasting\\Factory' => $vendorDir . '/illuminate/contracts/Broadcasting/Factory.php',
    'Illuminate\\Contracts\\Broadcasting\\HasBroadcastChannel' => $vendorDir . '/illuminate/contracts/Broadcasting/HasBroadcastChannel.php',
    'Illuminate\\Contracts\\Broadcasting\\ShouldBeUnique' => $vendorDir . '/illuminate/contracts/Broadcasting/ShouldBeUnique.php',
    'Illuminate\\Contracts\\Broadcasting\\ShouldBroadcast' => $vendorDir . '/illuminate/contracts/Broadcasting/ShouldBroadcast.php',
    'Illuminate\\Contracts\\Broadcasting\\ShouldBroadcastNow' => $vendorDir . '/illuminate/contracts/Broadcasting/ShouldBroadcastNow.php',
    'Illuminate\\Contracts\\Bus\\Dispatcher' => $vendorDir . '/illuminate/contracts/Bus/Dispatcher.php',
    'Illuminate\\Contracts\\Bus\\QueueingDispatcher' => $vendorDir . '/illuminate/contracts/Bus/QueueingDispatcher.php',
    'Illuminate\\Contracts\\Cache\\Factory' => $vendorDir . '/illuminate/contracts/Cache/Factory.php',
    'Illuminate\\Contracts\\Cache\\Lock' => $vendorDir . '/illuminate/contracts/Cache/Lock.php',
    'Illuminate\\Contracts\\Cache\\LockProvider' => $vendorDir . '/illuminate/contracts/Cache/LockProvider.php',
    'Illuminate\\Contracts\\Cache\\LockTimeoutException' => $vendorDir . '/illuminate/contracts/Cache/LockTimeoutException.php',
    'Illuminate\\Contracts\\Cache\\Repository' => $vendorDir . '/illuminate/contracts/Cache/Repository.php',
    'Illuminate\\Contracts\\Cache\\Store' => $vendorDir . '/illuminate/contracts/Cache/Store.php',
    'Illuminate\\Contracts\\Config\\Repository' => $vendorDir . '/illuminate/contracts/Config/Repository.php',
    'Illuminate\\Contracts\\Console\\Application' => $vendorDir . '/illuminate/contracts/Console/Application.php',
    'Illuminate\\Contracts\\Console\\Isolatable' => $vendorDir . '/illuminate/contracts/Console/Isolatable.php',
    'Illuminate\\Contracts\\Console\\Kernel' => $vendorDir . '/illuminate/contracts/Console/Kernel.php',
    'Illuminate\\Contracts\\Console\\PromptsForMissingInput' => $vendorDir . '/illuminate/contracts/Console/PromptsForMissingInput.php',
    'Illuminate\\Contracts\\Container\\BindingResolutionException' => $vendorDir . '/illuminate/contracts/Container/BindingResolutionException.php',
    'Illuminate\\Contracts\\Container\\CircularDependencyException' => $vendorDir . '/illuminate/contracts/Container/CircularDependencyException.php',
    'Illuminate\\Contracts\\Container\\Container' => $vendorDir . '/illuminate/contracts/Container/Container.php',
    'Illuminate\\Contracts\\Container\\ContextualBindingBuilder' => $vendorDir . '/illuminate/contracts/Container/ContextualBindingBuilder.php',
    'Illuminate\\Contracts\\Cookie\\Factory' => $vendorDir . '/illuminate/contracts/Cookie/Factory.php',
    'Illuminate\\Contracts\\Cookie\\QueueingFactory' => $vendorDir . '/illuminate/contracts/Cookie/QueueingFactory.php',
    'Illuminate\\Contracts\\Database\\Eloquent\\Builder' => $vendorDir . '/illuminate/contracts/Database/Eloquent/Builder.php',
    'Illuminate\\Contracts\\Database\\Eloquent\\Castable' => $vendorDir . '/illuminate/contracts/Database/Eloquent/Castable.php',
    'Illuminate\\Contracts\\Database\\Eloquent\\CastsAttributes' => $vendorDir . '/illuminate/contracts/Database/Eloquent/CastsAttributes.php',
    'Illuminate\\Contracts\\Database\\Eloquent\\CastsInboundAttributes' => $vendorDir . '/illuminate/contracts/Database/Eloquent/CastsInboundAttributes.php',
    'Illuminate\\Contracts\\Database\\Eloquent\\DeviatesCastableAttributes' => $vendorDir . '/illuminate/contracts/Database/Eloquent/DeviatesCastableAttributes.php',
    'Illuminate\\Contracts\\Database\\Eloquent\\SerializesCastableAttributes' => $vendorDir . '/illuminate/contracts/Database/Eloquent/SerializesCastableAttributes.php',
    'Illuminate\\Contracts\\Database\\Eloquent\\SupportsPartialRelations' => $vendorDir . '/illuminate/contracts/Database/Eloquent/SupportsPartialRelations.php',
    'Illuminate\\Contracts\\Database\\Events\\MigrationEvent' => $vendorDir . '/illuminate/contracts/Database/Events/MigrationEvent.php',
    'Illuminate\\Contracts\\Database\\ModelIdentifier' => $vendorDir . '/illuminate/contracts/Database/ModelIdentifier.php',
    'Illuminate\\Contracts\\Database\\Query\\Builder' => $vendorDir . '/illuminate/contracts/Database/Query/Builder.php',
    'Illuminate\\Contracts\\Database\\Query\\ConditionExpression' => $vendorDir . '/illuminate/contracts/Database/Query/ConditionExpression.php',
    'Illuminate\\Contracts\\Database\\Query\\Expression' => $vendorDir . '/illuminate/contracts/Database/Query/Expression.php',
    'Illuminate\\Contracts\\Debug\\ExceptionHandler' => $vendorDir . '/illuminate/contracts/Debug/ExceptionHandler.php',
    'Illuminate\\Contracts\\Encryption\\DecryptException' => $vendorDir . '/illuminate/contracts/Encryption/DecryptException.php',
    'Illuminate\\Contracts\\Encryption\\EncryptException' => $vendorDir . '/illuminate/contracts/Encryption/EncryptException.php',
    'Illuminate\\Contracts\\Encryption\\Encrypter' => $vendorDir . '/illuminate/contracts/Encryption/Encrypter.php',
    'Illuminate\\Contracts\\Encryption\\StringEncrypter' => $vendorDir . '/illuminate/contracts/Encryption/StringEncrypter.php',
    'Illuminate\\Contracts\\Events\\Dispatcher' => $vendorDir . '/illuminate/contracts/Events/Dispatcher.php',
    'Illuminate\\Contracts\\Events\\ShouldDispatchAfterCommit' => $vendorDir . '/illuminate/contracts/Events/ShouldDispatchAfterCommit.php',
    'Illuminate\\Contracts\\Events\\ShouldHandleEventsAfterCommit' => $vendorDir . '/illuminate/contracts/Events/ShouldHandleEventsAfterCommit.php',
    'Illuminate\\Contracts\\Filesystem\\Cloud' => $vendorDir . '/illuminate/contracts/Filesystem/Cloud.php',
    'Illuminate\\Contracts\\Filesystem\\Factory' => $vendorDir . '/illuminate/contracts/Filesystem/Factory.php',
    'Illuminate\\Contracts\\Filesystem\\FileNotFoundException' => $vendorDir . '/illuminate/contracts/Filesystem/FileNotFoundException.php',
    'Illuminate\\Contracts\\Filesystem\\Filesystem' => $vendorDir . '/illuminate/contracts/Filesystem/Filesystem.php',
    'Illuminate\\Contracts\\Filesystem\\LockTimeoutException' => $vendorDir . '/illuminate/contracts/Filesystem/LockTimeoutException.php',
    'Illuminate\\Contracts\\Foundation\\Application' => $vendorDir . '/illuminate/contracts/Foundation/Application.php',
    'Illuminate\\Contracts\\Foundation\\CachesConfiguration' => $vendorDir . '/illuminate/contracts/Foundation/CachesConfiguration.php',
    'Illuminate\\Contracts\\Foundation\\CachesRoutes' => $vendorDir . '/illuminate/contracts/Foundation/CachesRoutes.php',
    'Illuminate\\Contracts\\Foundation\\ExceptionRenderer' => $vendorDir . '/illuminate/contracts/Foundation/ExceptionRenderer.php',
    'Illuminate\\Contracts\\Foundation\\MaintenanceMode' => $vendorDir . '/illuminate/contracts/Foundation/MaintenanceMode.php',
    'Illuminate\\Contracts\\Hashing\\Hasher' => $vendorDir . '/illuminate/contracts/Hashing/Hasher.php',
    'Illuminate\\Contracts\\Http\\Kernel' => $vendorDir . '/illuminate/contracts/Http/Kernel.php',
    'Illuminate\\Contracts\\Mail\\Attachable' => $vendorDir . '/illuminate/contracts/Mail/Attachable.php',
    'Illuminate\\Contracts\\Mail\\Factory' => $vendorDir . '/illuminate/contracts/Mail/Factory.php',
    'Illuminate\\Contracts\\Mail\\MailQueue' => $vendorDir . '/illuminate/contracts/Mail/MailQueue.php',
    'Illuminate\\Contracts\\Mail\\Mailable' => $vendorDir . '/illuminate/contracts/Mail/Mailable.php',
    'Illuminate\\Contracts\\Mail\\Mailer' => $vendorDir . '/illuminate/contracts/Mail/Mailer.php',
    'Illuminate\\Contracts\\Notifications\\Dispatcher' => $vendorDir . '/illuminate/contracts/Notifications/Dispatcher.php',
    'Illuminate\\Contracts\\Notifications\\Factory' => $vendorDir . '/illuminate/contracts/Notifications/Factory.php',
    'Illuminate\\Contracts\\Pagination\\CursorPaginator' => $vendorDir . '/illuminate/contracts/Pagination/CursorPaginator.php',
    'Illuminate\\Contracts\\Pagination\\LengthAwarePaginator' => $vendorDir . '/illuminate/contracts/Pagination/LengthAwarePaginator.php',
    'Illuminate\\Contracts\\Pagination\\Paginator' => $vendorDir . '/illuminate/contracts/Pagination/Paginator.php',
    'Illuminate\\Contracts\\Pipeline\\Hub' => $vendorDir . '/illuminate/contracts/Pipeline/Hub.php',
    'Illuminate\\Contracts\\Pipeline\\Pipeline' => $vendorDir . '/illuminate/contracts/Pipeline/Pipeline.php',
    'Illuminate\\Contracts\\Process\\InvokedProcess' => $vendorDir . '/illuminate/contracts/Process/InvokedProcess.php',
    'Illuminate\\Contracts\\Process\\ProcessResult' => $vendorDir . '/illuminate/contracts/Process/ProcessResult.php',
    'Illuminate\\Contracts\\Queue\\ClearableQueue' => $vendorDir . '/illuminate/contracts/Queue/ClearableQueue.php',
    'Illuminate\\Contracts\\Queue\\EntityNotFoundException' => $vendorDir . '/illuminate/contracts/Queue/EntityNotFoundException.php',
    'Illuminate\\Contracts\\Queue\\EntityResolver' => $vendorDir . '/illuminate/contracts/Queue/EntityResolver.php',
    'Illuminate\\Contracts\\Queue\\Factory' => $vendorDir . '/illuminate/contracts/Queue/Factory.php',
    'Illuminate\\Contracts\\Queue\\Job' => $vendorDir . '/illuminate/contracts/Queue/Job.php',
    'Illuminate\\Contracts\\Queue\\Monitor' => $vendorDir . '/illuminate/contracts/Queue/Monitor.php',
    'Illuminate\\Contracts\\Queue\\Queue' => $vendorDir . '/illuminate/contracts/Queue/Queue.php',
    'Illuminate\\Contracts\\Queue\\QueueableCollection' => $vendorDir . '/illuminate/contracts/Queue/QueueableCollection.php',
    'Illuminate\\Contracts\\Queue\\QueueableEntity' => $vendorDir . '/illuminate/contracts/Queue/QueueableEntity.php',
    'Illuminate\\Contracts\\Queue\\ShouldBeEncrypted' => $vendorDir . '/illuminate/contracts/Queue/ShouldBeEncrypted.php',
    'Illuminate\\Contracts\\Queue\\ShouldBeUnique' => $vendorDir . '/illuminate/contracts/Queue/ShouldBeUnique.php',
    'Illuminate\\Contracts\\Queue\\ShouldBeUniqueUntilProcessing' => $vendorDir . '/illuminate/contracts/Queue/ShouldBeUniqueUntilProcessing.php',
    'Illuminate\\Contracts\\Queue\\ShouldQueue' => $vendorDir . '/illuminate/contracts/Queue/ShouldQueue.php',
    'Illuminate\\Contracts\\Queue\\ShouldQueueAfterCommit' => $vendorDir . '/illuminate/contracts/Queue/ShouldQueueAfterCommit.php',
    'Illuminate\\Contracts\\Redis\\Connection' => $vendorDir . '/illuminate/contracts/Redis/Connection.php',
    'Illuminate\\Contracts\\Redis\\Connector' => $vendorDir . '/illuminate/contracts/Redis/Connector.php',
    'Illuminate\\Contracts\\Redis\\Factory' => $vendorDir . '/illuminate/contracts/Redis/Factory.php',
    'Illuminate\\Contracts\\Redis\\LimiterTimeoutException' => $vendorDir . '/illuminate/contracts/Redis/LimiterTimeoutException.php',
    'Illuminate\\Contracts\\Routing\\BindingRegistrar' => $vendorDir . '/illuminate/contracts/Routing/BindingRegistrar.php',
    'Illuminate\\Contracts\\Routing\\Registrar' => $vendorDir . '/illuminate/contracts/Routing/Registrar.php',
    'Illuminate\\Contracts\\Routing\\ResponseFactory' => $vendorDir . '/illuminate/contracts/Routing/ResponseFactory.php',
    'Illuminate\\Contracts\\Routing\\UrlGenerator' => $vendorDir . '/illuminate/contracts/Routing/UrlGenerator.php',
    'Illuminate\\Contracts\\Routing\\UrlRoutable' => $vendorDir . '/illuminate/contracts/Routing/UrlRoutable.php',
    'Illuminate\\Contracts\\Session\\Middleware\\AuthenticatesSessions' => $vendorDir . '/illuminate/contracts/Session/Middleware/AuthenticatesSessions.php',
    'Illuminate\\Contracts\\Session\\Session' => $vendorDir . '/illuminate/contracts/Session/Session.php',
    'Illuminate\\Contracts\\Support\\Arrayable' => $vendorDir . '/illuminate/contracts/Support/Arrayable.php',
    'Illuminate\\Contracts\\Support\\CanBeEscapedWhenCastToString' => $vendorDir . '/illuminate/contracts/Support/CanBeEscapedWhenCastToString.php',
    'Illuminate\\Contracts\\Support\\DeferrableProvider' => $vendorDir . '/illuminate/contracts/Support/DeferrableProvider.php',
    'Illuminate\\Contracts\\Support\\DeferringDisplayableValue' => $vendorDir . '/illuminate/contracts/Support/DeferringDisplayableValue.php',
    'Illuminate\\Contracts\\Support\\Htmlable' => $vendorDir . '/illuminate/contracts/Support/Htmlable.php',
    'Illuminate\\Contracts\\Support\\Jsonable' => $vendorDir . '/illuminate/contracts/Support/Jsonable.php',
    'Illuminate\\Contracts\\Support\\MessageBag' => $vendorDir . '/illuminate/contracts/Support/MessageBag.php',
    'Illuminate\\Contracts\\Support\\MessageProvider' => $vendorDir . '/illuminate/contracts/Support/MessageProvider.php',
    'Illuminate\\Contracts\\Support\\Renderable' => $vendorDir . '/illuminate/contracts/Support/Renderable.php',
    'Illuminate\\Contracts\\Support\\Responsable' => $vendorDir . '/illuminate/contracts/Support/Responsable.php',
    'Illuminate\\Contracts\\Support\\ValidatedData' => $vendorDir . '/illuminate/contracts/Support/ValidatedData.php',
    'Illuminate\\Contracts\\Translation\\HasLocalePreference' => $vendorDir . '/illuminate/contracts/Translation/HasLocalePreference.php',
    'Illuminate\\Contracts\\Translation\\Loader' => $vendorDir . '/illuminate/contracts/Translation/Loader.php',
    'Illuminate\\Contracts\\Translation\\Translator' => $vendorDir . '/illuminate/contracts/Translation/Translator.php',
    'Illuminate\\Contracts\\Validation\\DataAwareRule' => $vendorDir . '/illuminate/contracts/Validation/DataAwareRule.php',
    'Illuminate\\Contracts\\Validation\\Factory' => $vendorDir . '/illuminate/contracts/Validation/Factory.php',
    'Illuminate\\Contracts\\Validation\\ImplicitRule' => $vendorDir . '/illuminate/contracts/Validation/ImplicitRule.php',
    'Illuminate\\Contracts\\Validation\\InvokableRule' => $vendorDir . '/illuminate/contracts/Validation/InvokableRule.php',
    'Illuminate\\Contracts\\Validation\\Rule' => $vendorDir . '/illuminate/contracts/Validation/Rule.php',
    'Illuminate\\Contracts\\Validation\\UncompromisedVerifier' => $vendorDir . '/illuminate/contracts/Validation/UncompromisedVerifier.php',
    'Illuminate\\Contracts\\Validation\\ValidatesWhenResolved' => $vendorDir . '/illuminate/contracts/Validation/ValidatesWhenResolved.php',
    'Illuminate\\Contracts\\Validation\\ValidationRule' => $vendorDir . '/illuminate/contracts/Validation/ValidationRule.php',
    'Illuminate\\Contracts\\Validation\\Validator' => $vendorDir . '/illuminate/contracts/Validation/Validator.php',
    'Illuminate\\Contracts\\Validation\\ValidatorAwareRule' => $vendorDir . '/illuminate/contracts/Validation/ValidatorAwareRule.php',
    'Illuminate\\Contracts\\View\\Engine' => $vendorDir . '/illuminate/contracts/View/Engine.php',
    'Illuminate\\Contracts\\View\\Factory' => $vendorDir . '/illuminate/contracts/View/Factory.php',
    'Illuminate\\Contracts\\View\\View' => $vendorDir . '/illuminate/contracts/View/View.php',
    'Illuminate\\Contracts\\View\\ViewCompilationException' => $vendorDir . '/illuminate/contracts/View/ViewCompilationException.php',
    'JsonSchema\\ConstraintError' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/ConstraintError.php',
    'JsonSchema\\Constraints\\BaseConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/BaseConstraint.php',
    'JsonSchema\\Constraints\\CollectionConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/CollectionConstraint.php',
    'JsonSchema\\Constraints\\ConstConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstConstraint.php',
    'JsonSchema\\Constraints\\Constraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Constraint.php',
    'JsonSchema\\Constraints\\ConstraintInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstraintInterface.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\AdditionalItemsConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AdditionalItemsConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\AdditionalPropertiesConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AdditionalPropertiesConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\AllOfConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AllOfConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\AnyOfConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/AnyOfConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\ConstConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ConstConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\ContainsConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ContainsConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\DependenciesConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/DependenciesConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\Draft06Constraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/Draft06Constraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\EnumConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/EnumConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\ExclusiveMaximumConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ExclusiveMaximumConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\ExclusiveMinimumConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ExclusiveMinimumConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\Factory' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/Factory.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\FormatConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/FormatConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\ItemsConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/ItemsConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\MaxItemsConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MaxItemsConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\MaxLengthConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MaxLengthConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\MaxPropertiesConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MaxPropertiesConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\MaximumConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MaximumConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\MinItemsConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MinItemsConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\MinLengthConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MinLengthConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\MinPropertiesConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MinPropertiesConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\MinimumConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MinimumConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\MultipleOfConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/MultipleOfConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\NotConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/NotConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\OneOfConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/OneOfConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\PatternConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PatternConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\PatternPropertiesConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PatternPropertiesConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\PropertiesConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PropertiesConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\PropertiesNamesConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/PropertiesNamesConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\RefConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/RefConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\RequiredConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/RequiredConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\TypeConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/TypeConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft06\\UniqueItemsConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft06/UniqueItemsConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\AdditionalItemsConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AdditionalItemsConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\AdditionalPropertiesConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AdditionalPropertiesConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\AllOfConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AllOfConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\AnyOfConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/AnyOfConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\ConstConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ConstConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\ContainsConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ContainsConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\ContentConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ContentConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\DependenciesConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/DependenciesConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\Draft07Constraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/Draft07Constraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\EnumConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/EnumConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\ExclusiveMaximumConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ExclusiveMaximumConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\ExclusiveMinimumConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ExclusiveMinimumConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\Factory' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/Factory.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\FormatConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/FormatConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\IfThenElseConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/IfThenElseConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\ItemsConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/ItemsConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\MaxItemsConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxItemsConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\MaxLengthConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxLengthConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\MaxPropertiesConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaxPropertiesConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\MaximumConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MaximumConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\MinItemsConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinItemsConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\MinLengthConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinLengthConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\MinPropertiesConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinPropertiesConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\MinimumConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MinimumConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\MultipleOfConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/MultipleOfConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\NotConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/NotConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\OneOfConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/OneOfConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\PatternConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PatternConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\PatternPropertiesConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PatternPropertiesConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\PropertiesConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PropertiesConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\PropertiesNamesConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/PropertiesNamesConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\RefConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/RefConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\RequiredConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/RequiredConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\TypeConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/TypeConstraint.php',
    'JsonSchema\\Constraints\\Drafts\\Draft07\\UniqueItemsConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Drafts/Draft07/UniqueItemsConstraint.php',
    'JsonSchema\\Constraints\\EnumConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/EnumConstraint.php',
    'JsonSchema\\Constraints\\Factory' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.php',
    'JsonSchema\\Constraints\\FormatConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php',
    'JsonSchema\\Constraints\\NumberConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/NumberConstraint.php',
    'JsonSchema\\Constraints\\ObjectConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/ObjectConstraint.php',
    'JsonSchema\\Constraints\\SchemaConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/SchemaConstraint.php',
    'JsonSchema\\Constraints\\StringConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/StringConstraint.php',
    'JsonSchema\\Constraints\\TypeCheck\\LooseTypeCheck' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/LooseTypeCheck.php',
    'JsonSchema\\Constraints\\TypeCheck\\StrictTypeCheck' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/StrictTypeCheck.php',
    'JsonSchema\\Constraints\\TypeCheck\\TypeCheckInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/TypeCheckInterface.php',
    'JsonSchema\\Constraints\\TypeConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeConstraint.php',
    'JsonSchema\\Constraints\\UndefinedConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/UndefinedConstraint.php',
    'JsonSchema\\DraftIdentifiers' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/DraftIdentifiers.php',
    'JsonSchema\\Entity\\ErrorBag' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Entity/ErrorBag.php',
    'JsonSchema\\Entity\\ErrorBagProxy' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Entity/ErrorBagProxy.php',
    'JsonSchema\\Entity\\JsonPointer' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Entity/JsonPointer.php',
    'JsonSchema\\Enum' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Enum.php',
    'JsonSchema\\Exception\\ExceptionInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/ExceptionInterface.php',
    'JsonSchema\\Exception\\InvalidArgumentException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidArgumentException.php',
    'JsonSchema\\Exception\\InvalidConfigException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidConfigException.php',
    'JsonSchema\\Exception\\InvalidSchemaException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaException.php',
    'JsonSchema\\Exception\\InvalidSchemaMediaTypeException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaMediaTypeException.php',
    'JsonSchema\\Exception\\InvalidSourceUriException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSourceUriException.php',
    'JsonSchema\\Exception\\JsonDecodingException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/JsonDecodingException.php',
    'JsonSchema\\Exception\\ResourceNotFoundException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/ResourceNotFoundException.php',
    'JsonSchema\\Exception\\RuntimeException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/RuntimeException.php',
    'JsonSchema\\Exception\\UnresolvableJsonPointerException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/UnresolvableJsonPointerException.php',
    'JsonSchema\\Exception\\UriResolverException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/UriResolverException.php',
    'JsonSchema\\Exception\\ValidationException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/ValidationException.php',
    'JsonSchema\\Iterator\\ObjectIterator' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Iterator/ObjectIterator.php',
    'JsonSchema\\Rfc3339' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Rfc3339.php',
    'JsonSchema\\SchemaStorage' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/SchemaStorage.php',
    'JsonSchema\\SchemaStorageInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/SchemaStorageInterface.php',
    'JsonSchema\\Tool\\DeepComparer' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Tool/DeepComparer.php',
    'JsonSchema\\Tool\\DeepCopy' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Tool/DeepCopy.php',
    'JsonSchema\\Tool\\Validator\\RelativeReferenceValidator' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Tool/Validator/RelativeReferenceValidator.php',
    'JsonSchema\\Tool\\Validator\\UriValidator' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Tool/Validator/UriValidator.php',
    'JsonSchema\\UriResolverInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/UriResolverInterface.php',
    'JsonSchema\\UriRetrieverInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/UriRetrieverInterface.php',
    'JsonSchema\\Uri\\Retrievers\\AbstractRetriever' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/AbstractRetriever.php',
    'JsonSchema\\Uri\\Retrievers\\Curl' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/Curl.php',
    'JsonSchema\\Uri\\Retrievers\\FileGetContents' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/FileGetContents.php',
    'JsonSchema\\Uri\\Retrievers\\PredefinedArray' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/PredefinedArray.php',
    'JsonSchema\\Uri\\Retrievers\\UriRetrieverInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/UriRetrieverInterface.php',
    'JsonSchema\\Uri\\UriResolver' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/UriResolver.php',
    'JsonSchema\\Uri\\UriRetriever' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php',
    'JsonSchema\\Validator' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Validator.php',
    'MabeEnum\\Enum' => $vendorDir . '/marc-mabe/php-enum/src/Enum.php',
    'MabeEnum\\EnumMap' => $vendorDir . '/marc-mabe/php-enum/src/EnumMap.php',
    'MabeEnum\\EnumSerializableTrait' => $vendorDir . '/marc-mabe/php-enum/src/EnumSerializableTrait.php',
    'MabeEnum\\EnumSet' => $vendorDir . '/marc-mabe/php-enum/src/EnumSet.php',
    'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
    'Pdo\\Dblib' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Dblib.php',
    'Pdo\\Firebird' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Firebird.php',
    'Pdo\\Mysql' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Mysql.php',
    'Pdo\\Odbc' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Odbc.php',
    'Pdo\\Pgsql' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Pgsql.php',
    'Pdo\\Sqlite' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/Pdo/Sqlite.php',
    'Php\\Pie\\Building\\Build' => $baseDir . '/src/Building/Build.php',
    'Php\\Pie\\Building\\ExtensionBinaryNotFound' => $baseDir . '/src/Building/ExtensionBinaryNotFound.php',
    'Php\\Pie\\Building\\PlaceholderReplacer' => $baseDir . '/src/Building/PlaceholderReplacer.php',
    'Php\\Pie\\Building\\UnixBuild' => $baseDir . '/src/Building/UnixBuild.php',
    'Php\\Pie\\Building\\WindowsBuild' => $baseDir . '/src/Building/WindowsBuild.php',
    'Php\\Pie\\Command\\ArgvInput' => $baseDir . '/src/Command/ArgvInput.php',
    'Php\\Pie\\Command\\BuildCommand' => $baseDir . '/src/Command/BuildCommand.php',
    'Php\\Pie\\Command\\CommandHelper' => $baseDir . '/src/Command/CommandHelper.php',
    'Php\\Pie\\Command\\DownloadCommand' => $baseDir . '/src/Command/DownloadCommand.php',
    'Php\\Pie\\Command\\InfoCommand' => $baseDir . '/src/Command/InfoCommand.php',
    'Php\\Pie\\Command\\InstallCommand' => $baseDir . '/src/Command/InstallCommand.php',
    'Php\\Pie\\Command\\InstallExtensionsForProjectCommand' => $baseDir . '/src/Command/InstallExtensionsForProjectCommand.php',
    'Php\\Pie\\Command\\InvokeSubCommand' => $baseDir . '/src/Command/InvokeSubCommand.php',
    'Php\\Pie\\Command\\RepositoryAddCommand' => $baseDir . '/src/Command/RepositoryAddCommand.php',
    'Php\\Pie\\Command\\RepositoryListCommand' => $baseDir . '/src/Command/RepositoryListCommand.php',
    'Php\\Pie\\Command\\RepositoryRemoveCommand' => $baseDir . '/src/Command/RepositoryRemoveCommand.php',
    'Php\\Pie\\Command\\SelfUpdateCommand' => $baseDir . '/src/Command/SelfUpdateCommand.php',
    'Php\\Pie\\Command\\SelfVerifyCommand' => $baseDir . '/src/Command/SelfVerifyCommand.php',
    'Php\\Pie\\Command\\ShowCommand' => $baseDir . '/src/Command/ShowCommand.php',
    'Php\\Pie\\Command\\UninstallCommand' => $baseDir . '/src/Command/UninstallCommand.php',
    'Php\\Pie\\ComposerIntegration\\AddInstalledJsonMetadata' => $baseDir . '/src/ComposerIntegration/AddInstalledJsonMetadata.php',
    'Php\\Pie\\ComposerIntegration\\BundledPhpExtensionsRepository' => $baseDir . '/src/ComposerIntegration/BundledPhpExtensionsRepository.php',
    'Php\\Pie\\ComposerIntegration\\ComposerIntegrationHandler' => $baseDir . '/src/ComposerIntegration/ComposerIntegrationHandler.php',
    'Php\\Pie\\ComposerIntegration\\ComposerRunFailed' => $baseDir . '/src/ComposerIntegration/ComposerRunFailed.php',
    'Php\\Pie\\ComposerIntegration\\InstallAndBuildProcess' => $baseDir . '/src/ComposerIntegration/InstallAndBuildProcess.php',
    'Php\\Pie\\ComposerIntegration\\InstalledJsonMetadata' => $baseDir . '/src/ComposerIntegration/InstalledJsonMetadata.php',
    'Php\\Pie\\ComposerIntegration\\Listeners\\CouldNotDetermineDownloadUrlMethod' => $baseDir . '/src/ComposerIntegration/Listeners/CouldNotDetermineDownloadUrlMethod.php',
    'Php\\Pie\\ComposerIntegration\\Listeners\\OverrideDownloadUrlInstallListener' => $baseDir . '/src/ComposerIntegration/Listeners/OverrideDownloadUrlInstallListener.php',
    'Php\\Pie\\ComposerIntegration\\Listeners\\RemoveUnrelatedInstallOperations' => $baseDir . '/src/ComposerIntegration/Listeners/RemoveUnrelatedInstallOperations.php',
    'Php\\Pie\\ComposerIntegration\\MinimalHelperSet' => $baseDir . '/src/ComposerIntegration/MinimalHelperSet.php',
    'Php\\Pie\\ComposerIntegration\\PhpBinaryPathBasedPlatformRepository' => $baseDir . '/src/ComposerIntegration/PhpBinaryPathBasedPlatformRepository.php',
    'Php\\Pie\\ComposerIntegration\\PieComposerFactory' => $baseDir . '/src/ComposerIntegration/PieComposerFactory.php',
    'Php\\Pie\\ComposerIntegration\\PieComposerInstaller' => $baseDir . '/src/ComposerIntegration/PieComposerInstaller.php',
    'Php\\Pie\\ComposerIntegration\\PieComposerRequest' => $baseDir . '/src/ComposerIntegration/PieComposerRequest.php',
    'Php\\Pie\\ComposerIntegration\\PieJsonEditor' => $baseDir . '/src/ComposerIntegration/PieJsonEditor.php',
    'Php\\Pie\\ComposerIntegration\\PieOperation' => $baseDir . '/src/ComposerIntegration/PieOperation.php',
    'Php\\Pie\\ComposerIntegration\\PiePackageInstaller' => $baseDir . '/src/ComposerIntegration/PiePackageInstaller.php',
    'Php\\Pie\\ComposerIntegration\\QuieterConsoleIO' => $baseDir . '/src/ComposerIntegration/QuieterConsoleIO.php',
    'Php\\Pie\\ComposerIntegration\\UninstallProcess' => $baseDir . '/src/ComposerIntegration/UninstallProcess.php',
    'Php\\Pie\\ComposerIntegration\\VendorCleanup' => $baseDir . '/src/ComposerIntegration/VendorCleanup.php',
    'Php\\Pie\\ComposerIntegration\\VersionSelectorFactory' => $baseDir . '/src/ComposerIntegration/VersionSelectorFactory.php',
    'Php\\Pie\\ConfigureOption' => $baseDir . '/src/ConfigureOption.php',
    'Php\\Pie\\Container' => $baseDir . '/src/Container.php',
    'Php\\Pie\\DependencyResolver\\BundledPhpExtensionRefusal' => $baseDir . '/src/DependencyResolver/BundledPhpExtensionRefusal.php',
    'Php\\Pie\\DependencyResolver\\DependencyInstaller\\PrescanSystemDependencies' => $baseDir . '/src/DependencyResolver/DependencyInstaller/PrescanSystemDependencies.php',
    'Php\\Pie\\DependencyResolver\\DependencyInstaller\\SystemDependenciesDefinition' => $baseDir . '/src/DependencyResolver/DependencyInstaller/SystemDependenciesDefinition.php',
    'Php\\Pie\\DependencyResolver\\DependencyResolver' => $baseDir . '/src/DependencyResolver/DependencyResolver.php',
    'Php\\Pie\\DependencyResolver\\DependencyStatus' => $baseDir . '/src/DependencyResolver/DependencyStatus.php',
    'Php\\Pie\\DependencyResolver\\DetermineMinimumStability' => $baseDir . '/src/DependencyResolver/DetermineMinimumStability.php',
    'Php\\Pie\\DependencyResolver\\FetchDependencyStatuses' => $baseDir . '/src/DependencyResolver/FetchDependencyStatuses.php',
    'Php\\Pie\\DependencyResolver\\IncompatibleOperatingSystemFamily' => $baseDir . '/src/DependencyResolver/IncompatibleOperatingSystemFamily.php',
    'Php\\Pie\\DependencyResolver\\IncompatibleThreadSafetyMode' => $baseDir . '/src/DependencyResolver/IncompatibleThreadSafetyMode.php',
    'Php\\Pie\\DependencyResolver\\InvalidPackageName' => $baseDir . '/src/DependencyResolver/InvalidPackageName.php',
    'Php\\Pie\\DependencyResolver\\Package' => $baseDir . '/src/DependencyResolver/Package.php',
    'Php\\Pie\\DependencyResolver\\RequestedPackageAndVersion' => $baseDir . '/src/DependencyResolver/RequestedPackageAndVersion.php',
    'Php\\Pie\\DependencyResolver\\ResolveDependencyWithComposer' => $baseDir . '/src/DependencyResolver/ResolveDependencyWithComposer.php',
    'Php\\Pie\\DependencyResolver\\UnableToResolveRequirement' => $baseDir . '/src/DependencyResolver/UnableToResolveRequirement.php',
    'Php\\Pie\\Downloading\\DownloadUrlMethod' => $baseDir . '/src/Downloading/DownloadUrlMethod.php',
    'Php\\Pie\\Downloading\\DownloadedPackage' => $baseDir . '/src/Downloading/DownloadedPackage.php',
    'Php\\Pie\\Downloading\\Exception\\CouldNotFindReleaseAsset' => $baseDir . '/src/Downloading/Exception/CouldNotFindReleaseAsset.php',
    'Php\\Pie\\Downloading\\GithubPackageReleaseAssets' => $baseDir . '/src/Downloading/GithubPackageReleaseAssets.php',
    'Php\\Pie\\Downloading\\PackageReleaseAssets' => $baseDir . '/src/Downloading/PackageReleaseAssets.php',
    'Php\\Pie\\ExtensionName' => $baseDir . '/src/ExtensionName.php',
    'Php\\Pie\\ExtensionType' => $baseDir . '/src/ExtensionType.php',
    'Php\\Pie\\File\\BinaryFile' => $baseDir . '/src/File/BinaryFile.php',
    'Php\\Pie\\File\\BinaryFileFailedVerification' => $baseDir . '/src/File/BinaryFileFailedVerification.php',
    'Php\\Pie\\File\\FailedToCreateFile' => $baseDir . '/src/File/FailedToCreateFile.php',
    'Php\\Pie\\File\\FailedToUnlinkFile' => $baseDir . '/src/File/FailedToUnlinkFile.php',
    'Php\\Pie\\File\\FailedToWriteFile' => $baseDir . '/src/File/FailedToWriteFile.php',
    'Php\\Pie\\File\\FullPathToSelf' => $baseDir . '/src/File/FullPathToSelf.php',
    'Php\\Pie\\File\\Sudo' => $baseDir . '/src/File/Sudo.php',
    'Php\\Pie\\File\\SudoCreate' => $baseDir . '/src/File/SudoCreate.php',
    'Php\\Pie\\File\\SudoFilePut' => $baseDir . '/src/File/SudoFilePut.php',
    'Php\\Pie\\File\\SudoNotFoundOnSystem' => $baseDir . '/src/File/SudoNotFoundOnSystem.php',
    'Php\\Pie\\File\\SudoRequiresInteractiveTerminal' => $baseDir . '/src/File/SudoRequiresInteractiveTerminal.php',
    'Php\\Pie\\File\\SudoUnlink' => $baseDir . '/src/File/SudoUnlink.php',
    'Php\\Pie\\File\\WindowsDelete' => $baseDir . '/src/File/WindowsDelete.php',
    'Php\\Pie\\Installing\\FailedToRemoveExtension' => $baseDir . '/src/Installing/FailedToRemoveExtension.php',
    'Php\\Pie\\Installing\\Ini\\AddExtensionToTheIniFile' => $baseDir . '/src/Installing/Ini/AddExtensionToTheIniFile.php',
    'Php\\Pie\\Installing\\Ini\\CheckAndAddExtensionToIniIfNeeded' => $baseDir . '/src/Installing/Ini/CheckAndAddExtensionToIniIfNeeded.php',
    'Php\\Pie\\Installing\\Ini\\DockerPhpExtEnable' => $baseDir . '/src/Installing/Ini/DockerPhpExtEnable.php',
    'Php\\Pie\\Installing\\Ini\\IsExtensionAlreadyInTheIniFile' => $baseDir . '/src/Installing/Ini/IsExtensionAlreadyInTheIniFile.php',
    'Php\\Pie\\Installing\\Ini\\OndrejPhpenmod' => $baseDir . '/src/Installing/Ini/OndrejPhpenmod.php',
    'Php\\Pie\\Installing\\Ini\\PickBestSetupIniApproach' => $baseDir . '/src/Installing/Ini/PickBestSetupIniApproach.php',
    'Php\\Pie\\Installing\\Ini\\PreCheckExtensionAlreadyLoaded' => $baseDir . '/src/Installing/Ini/PreCheckExtensionAlreadyLoaded.php',
    'Php\\Pie\\Installing\\Ini\\RemoveIniEntry' => $baseDir . '/src/Installing/Ini/RemoveIniEntry.php',
    'Php\\Pie\\Installing\\Ini\\RemoveIniEntryWithFileGetContents' => $baseDir . '/src/Installing/Ini/RemoveIniEntryWithFileGetContents.php',
    'Php\\Pie\\Installing\\Ini\\SetupIniApproach' => $baseDir . '/src/Installing/Ini/SetupIniApproach.php',
    'Php\\Pie\\Installing\\Ini\\StandardAdditionalPhpIniDirectory' => $baseDir . '/src/Installing/Ini/StandardAdditionalPhpIniDirectory.php',
    'Php\\Pie\\Installing\\Ini\\StandardSinglePhpIni' => $baseDir . '/src/Installing/Ini/StandardSinglePhpIni.php',
    'Php\\Pie\\Installing\\Install' => $baseDir . '/src/Installing/Install.php',
    'Php\\Pie\\Installing\\InstallForPhpProject\\ComposerFactoryForProject' => $baseDir . '/src/Installing/InstallForPhpProject/ComposerFactoryForProject.php',
    'Php\\Pie\\Installing\\InstallForPhpProject\\DetermineExtensionsRequired' => $baseDir . '/src/Installing/InstallForPhpProject/DetermineExtensionsRequired.php',
    'Php\\Pie\\Installing\\InstallForPhpProject\\FindMatchingPackages' => $baseDir . '/src/Installing/InstallForPhpProject/FindMatchingPackages.php',
    'Php\\Pie\\Installing\\InstallForPhpProject\\InstallPiePackageFromPath' => $baseDir . '/src/Installing/InstallForPhpProject/InstallPiePackageFromPath.php',
    'Php\\Pie\\Installing\\InstallForPhpProject\\InstallSelectedPackage' => $baseDir . '/src/Installing/InstallForPhpProject/InstallSelectedPackage.php',
    'Php\\Pie\\Installing\\PackageMetadataMissing' => $baseDir . '/src/Installing/PackageMetadataMissing.php',
    'Php\\Pie\\Installing\\SetupIniFile' => $baseDir . '/src/Installing/SetupIniFile.php',
    'Php\\Pie\\Installing\\Uninstall' => $baseDir . '/src/Installing/Uninstall.php',
    'Php\\Pie\\Installing\\UninstallUsingUnlink' => $baseDir . '/src/Installing/UninstallUsingUnlink.php',
    'Php\\Pie\\Installing\\UnixInstall' => $baseDir . '/src/Installing/UnixInstall.php',
    'Php\\Pie\\Installing\\WindowsInstall' => $baseDir . '/src/Installing/WindowsInstall.php',
    'Php\\Pie\\Platform' => $baseDir . '/src/Platform.php',
    'Php\\Pie\\Platform\\Architecture' => $baseDir . '/src/Platform/Architecture.php',
    'Php\\Pie\\Platform\\DebugBuild' => $baseDir . '/src/Platform/DebugBuild.php',
    'Php\\Pie\\Platform\\InstalledPiePackages' => $baseDir . '/src/Platform/InstalledPiePackages.php',
    'Php\\Pie\\Platform\\LibcFlavour' => $baseDir . '/src/Platform/LibcFlavour.php',
    'Php\\Pie\\Platform\\OperatingSystem' => $baseDir . '/src/Platform/OperatingSystem.php',
    'Php\\Pie\\Platform\\OperatingSystemFamily' => $baseDir . '/src/Platform/OperatingSystemFamily.php',
    'Php\\Pie\\Platform\\PackageManager' => $baseDir . '/src/Platform/PackageManager.php',
    'Php\\Pie\\Platform\\PiePackageList' => $baseDir . '/src/Platform/PiePackageList.php',
    'Php\\Pie\\Platform\\PrePackagedBinaryAssetName' => $baseDir . '/src/Platform/PrePackagedBinaryAssetName.php',
    'Php\\Pie\\Platform\\PrePackagedSourceAssetName' => $baseDir . '/src/Platform/PrePackagedSourceAssetName.php',
    'Php\\Pie\\Platform\\TargetPhp\\Exception\\ExtensionIsNotLoaded' => $baseDir . '/src/Platform/TargetPhp/Exception/ExtensionIsNotLoaded.php',
    'Php\\Pie\\Platform\\TargetPhp\\Exception\\ExtensionPathProblem' => $baseDir . '/src/Platform/TargetPhp/Exception/ExtensionPathProblem.php',
    'Php\\Pie\\Platform\\TargetPhp\\Exception\\InvalidPhpBinaryPath' => $baseDir . '/src/Platform/TargetPhp/Exception/InvalidPhpBinaryPath.php',
    'Php\\Pie\\Platform\\TargetPhp\\PhpBinaryPath' => $baseDir . '/src/Platform/TargetPhp/PhpBinaryPath.php',
    'Php\\Pie\\Platform\\TargetPhp\\PhpizePath' => $baseDir . '/src/Platform/TargetPhp/PhpizePath.php',
    'Php\\Pie\\Platform\\TargetPlatform' => $baseDir . '/src/Platform/TargetPlatform.php',
    'Php\\Pie\\Platform\\ThreadSafetyMode' => $baseDir . '/src/Platform/ThreadSafetyMode.php',
    'Php\\Pie\\Platform\\WindowsCompiler' => $baseDir . '/src/Platform/WindowsCompiler.php',
    'Php\\Pie\\Platform\\WindowsExtensionAssetName' => $baseDir . '/src/Platform/WindowsExtensionAssetName.php',
    'Php\\Pie\\SelfManage\\BuildTools\\BinaryBuildToolFinder' => $baseDir . '/src/SelfManage/BuildTools/BinaryBuildToolFinder.php',
    'Php\\Pie\\SelfManage\\BuildTools\\CheckAllBuildTools' => $baseDir . '/src/SelfManage/BuildTools/CheckAllBuildTools.php',
    'Php\\Pie\\SelfManage\\BuildTools\\PhpizeBuildToolFinder' => $baseDir . '/src/SelfManage/BuildTools/PhpizeBuildToolFinder.php',
    'Php\\Pie\\SelfManage\\Update\\Channel' => $baseDir . '/src/SelfManage/Update/Channel.php',
    'Php\\Pie\\SelfManage\\Update\\FetchPieRelease' => $baseDir . '/src/SelfManage/Update/FetchPieRelease.php',
    'Php\\Pie\\SelfManage\\Update\\FetchPieReleaseFromGitHub' => $baseDir . '/src/SelfManage/Update/FetchPieReleaseFromGitHub.php',
    'Php\\Pie\\SelfManage\\Update\\PiePharMissingFromLatestRelease' => $baseDir . '/src/SelfManage/Update/PiePharMissingFromLatestRelease.php',
    'Php\\Pie\\SelfManage\\Update\\ReleaseIsNewer' => $baseDir . '/src/SelfManage/Update/ReleaseIsNewer.php',
    'Php\\Pie\\SelfManage\\Update\\ReleaseMetadata' => $baseDir . '/src/SelfManage/Update/ReleaseMetadata.php',
    'Php\\Pie\\SelfManage\\Verify\\Attestation' => $baseDir . '/src/SelfManage/Verify/Attestation.php',
    'Php\\Pie\\SelfManage\\Verify\\FailedToVerifyRelease' => $baseDir . '/src/SelfManage/Verify/FailedToVerifyRelease.php',
    'Php\\Pie\\SelfManage\\Verify\\FallbackVerificationUsingOpenSsl' => $baseDir . '/src/SelfManage/Verify/FallbackVerificationUsingOpenSsl.php',
    'Php\\Pie\\SelfManage\\Verify\\GithubCliAttestationVerification' => $baseDir . '/src/SelfManage/Verify/GithubCliAttestationVerification.php',
    'Php\\Pie\\SelfManage\\Verify\\GithubCliNotAvailable' => $baseDir . '/src/SelfManage/Verify/GithubCliNotAvailable.php',
    'Php\\Pie\\SelfManage\\Verify\\VerifyPiePhar' => $baseDir . '/src/SelfManage/Verify/VerifyPiePhar.php',
    'Php\\Pie\\SelfManage\\Verify\\VerifyPieReleaseUsingAttestation' => $baseDir . '/src/SelfManage/Verify/VerifyPieReleaseUsingAttestation.php',
    'Php\\Pie\\Settings' => $baseDir . '/src/Settings.php',
    'Php\\Pie\\Util\\Emoji' => $baseDir . '/src/Util/Emoji.php',
    'Php\\Pie\\Util\\FileNotFound' => $baseDir . '/src/Util/FileNotFound.php',
    'Php\\Pie\\Util\\OutputFormatterWithPrefix' => $baseDir . '/src/Util/OutputFormatterWithPrefix.php',
    'Php\\Pie\\Util\\PackageVerificationStatus' => $baseDir . '/src/Util/PackageVerificationStatus.php',
    'Php\\Pie\\Util\\PieVersion' => $baseDir . '/src/Util/PieVersion.php',
    'Php\\Pie\\Util\\Process' => $baseDir . '/src/Util/Process.php',
    'Php\\Pie\\Util\\ProcessFailedWithLimitedOutput' => $baseDir . '/src/Util/ProcessFailedWithLimitedOutput.php',
    '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\\Log\\AbstractLogger' => $vendorDir . '/psr/log/src/AbstractLogger.php',
    'Psr\\Log\\InvalidArgumentException' => $vendorDir . '/psr/log/src/InvalidArgumentException.php',
    'Psr\\Log\\LogLevel' => $vendorDir . '/psr/log/src/LogLevel.php',
    'Psr\\Log\\LoggerAwareInterface' => $vendorDir . '/psr/log/src/LoggerAwareInterface.php',
    'Psr\\Log\\LoggerAwareTrait' => $vendorDir . '/psr/log/src/LoggerAwareTrait.php',
    'Psr\\Log\\LoggerInterface' => $vendorDir . '/psr/log/src/LoggerInterface.php',
    'Psr\\Log\\LoggerTrait' => $vendorDir . '/psr/log/src/LoggerTrait.php',
    'Psr\\Log\\NullLogger' => $vendorDir . '/psr/log/src/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',
    'React\\Promise\\Deferred' => $vendorDir . '/react/promise/src/Deferred.php',
    'React\\Promise\\Exception\\CompositeException' => $vendorDir . '/react/promise/src/Exception/CompositeException.php',
    'React\\Promise\\Exception\\LengthException' => $vendorDir . '/react/promise/src/Exception/LengthException.php',
    'React\\Promise\\Internal\\CancellationQueue' => $vendorDir . '/react/promise/src/Internal/CancellationQueue.php',
    'React\\Promise\\Internal\\FulfilledPromise' => $vendorDir . '/react/promise/src/Internal/FulfilledPromise.php',
    'React\\Promise\\Internal\\RejectedPromise' => $vendorDir . '/react/promise/src/Internal/RejectedPromise.php',
    'React\\Promise\\Promise' => $vendorDir . '/react/promise/src/Promise.php',
    'React\\Promise\\PromiseInterface' => $vendorDir . '/react/promise/src/PromiseInterface.php',
    'ReflectionConstant' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/ReflectionConstant.php',
    'RoundingMode' => $vendorDir . '/symfony/polyfill-php84/Resources/stubs/RoundingMode.php',
    'Safe\\DateTime' => $vendorDir . '/thecodingmachine/safe/lib/DateTime.php',
    'Safe\\DateTimeImmutable' => $vendorDir . '/thecodingmachine/safe/lib/DateTimeImmutable.php',
    'Safe\\Exceptions\\ApacheException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/ApacheException.php',
    'Safe\\Exceptions\\ApcuException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/ApcuException.php',
    'Safe\\Exceptions\\ArrayException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/ArrayException.php',
    'Safe\\Exceptions\\Bzip2Exception' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/Bzip2Exception.php',
    'Safe\\Exceptions\\CalendarException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/CalendarException.php',
    'Safe\\Exceptions\\ClassobjException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/ClassobjException.php',
    'Safe\\Exceptions\\ComException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/ComException.php',
    'Safe\\Exceptions\\CubridException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/CubridException.php',
    'Safe\\Exceptions\\CurlException' => $vendorDir . '/thecodingmachine/safe/lib/Exceptions/CurlException.php',
    'Safe\\Exceptions\\DatetimeException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/DatetimeException.php',
    'Safe\\Exceptions\\DirException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/DirException.php',
    'Safe\\Exceptions\\EioException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/EioException.php',
    'Safe\\Exceptions\\ErrorfuncException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/ErrorfuncException.php',
    'Safe\\Exceptions\\ExecException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/ExecException.php',
    'Safe\\Exceptions\\FileinfoException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/FileinfoException.php',
    'Safe\\Exceptions\\FilesystemException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/FilesystemException.php',
    'Safe\\Exceptions\\FilterException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/FilterException.php',
    'Safe\\Exceptions\\FpmException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/FpmException.php',
    'Safe\\Exceptions\\FtpException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/FtpException.php',
    'Safe\\Exceptions\\FunchandException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/FunchandException.php',
    'Safe\\Exceptions\\GettextException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/GettextException.php',
    'Safe\\Exceptions\\GmpException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/GmpException.php',
    'Safe\\Exceptions\\GnupgException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/GnupgException.php',
    'Safe\\Exceptions\\HashException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/HashException.php',
    'Safe\\Exceptions\\IbaseException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/IbaseException.php',
    'Safe\\Exceptions\\IbmDb2Exception' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/IbmDb2Exception.php',
    'Safe\\Exceptions\\IconvException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/IconvException.php',
    'Safe\\Exceptions\\ImageException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/ImageException.php',
    'Safe\\Exceptions\\ImapException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/ImapException.php',
    'Safe\\Exceptions\\InfoException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/InfoException.php',
    'Safe\\Exceptions\\InotifyException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/InotifyException.php',
    'Safe\\Exceptions\\JsonException' => $vendorDir . '/thecodingmachine/safe/lib/Exceptions/JsonException.php',
    'Safe\\Exceptions\\LdapException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/LdapException.php',
    'Safe\\Exceptions\\LibxmlException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/LibxmlException.php',
    'Safe\\Exceptions\\LzfException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/LzfException.php',
    'Safe\\Exceptions\\MailparseException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/MailparseException.php',
    'Safe\\Exceptions\\MbstringException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/MbstringException.php',
    'Safe\\Exceptions\\MiscException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/MiscException.php',
    'Safe\\Exceptions\\MysqlException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/MysqlException.php',
    'Safe\\Exceptions\\MysqliException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/MysqliException.php',
    'Safe\\Exceptions\\NetworkException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/NetworkException.php',
    'Safe\\Exceptions\\Oci8Exception' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/Oci8Exception.php',
    'Safe\\Exceptions\\OpcacheException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/OpcacheException.php',
    'Safe\\Exceptions\\OpensslException' => $vendorDir . '/thecodingmachine/safe/lib/Exceptions/OpensslException.php',
    'Safe\\Exceptions\\OutcontrolException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/OutcontrolException.php',
    'Safe\\Exceptions\\PcntlException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/PcntlException.php',
    'Safe\\Exceptions\\PcreException' => $vendorDir . '/thecodingmachine/safe/lib/Exceptions/PcreException.php',
    'Safe\\Exceptions\\PgsqlException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/PgsqlException.php',
    'Safe\\Exceptions\\PosixException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/PosixException.php',
    'Safe\\Exceptions\\PsException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/PsException.php',
    'Safe\\Exceptions\\PspellException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/PspellException.php',
    'Safe\\Exceptions\\ReadlineException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/ReadlineException.php',
    'Safe\\Exceptions\\RnpException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/RnpException.php',
    'Safe\\Exceptions\\RpminfoException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/RpminfoException.php',
    'Safe\\Exceptions\\RrdException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/RrdException.php',
    'Safe\\Exceptions\\SafeExceptionInterface' => $vendorDir . '/thecodingmachine/safe/lib/Exceptions/SafeExceptionInterface.php',
    'Safe\\Exceptions\\SemException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/SemException.php',
    'Safe\\Exceptions\\SessionException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/SessionException.php',
    'Safe\\Exceptions\\ShmopException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/ShmopException.php',
    'Safe\\Exceptions\\SimplexmlException' => $vendorDir . '/thecodingmachine/safe/lib/Exceptions/SimplexmlException.php',
    'Safe\\Exceptions\\SocketsException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/SocketsException.php',
    'Safe\\Exceptions\\SodiumException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/SodiumException.php',
    'Safe\\Exceptions\\SolrException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/SolrException.php',
    'Safe\\Exceptions\\SplException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/SplException.php',
    'Safe\\Exceptions\\SqlsrvException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/SqlsrvException.php',
    'Safe\\Exceptions\\SsdeepException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/SsdeepException.php',
    'Safe\\Exceptions\\Ssh2Exception' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/Ssh2Exception.php',
    'Safe\\Exceptions\\StreamException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/StreamException.php',
    'Safe\\Exceptions\\StringsException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/StringsException.php',
    'Safe\\Exceptions\\SwooleException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/SwooleException.php',
    'Safe\\Exceptions\\UodbcException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/UodbcException.php',
    'Safe\\Exceptions\\UopzException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/UopzException.php',
    'Safe\\Exceptions\\UrlException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/UrlException.php',
    'Safe\\Exceptions\\VarException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/VarException.php',
    'Safe\\Exceptions\\XdiffException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/XdiffException.php',
    'Safe\\Exceptions\\XmlException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/XmlException.php',
    'Safe\\Exceptions\\XmlrpcException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/XmlrpcException.php',
    'Safe\\Exceptions\\YamlException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/YamlException.php',
    'Safe\\Exceptions\\YazException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/YazException.php',
    'Safe\\Exceptions\\ZipException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/ZipException.php',
    'Safe\\Exceptions\\ZlibException' => $vendorDir . '/thecodingmachine/safe/generated/Exceptions/ZlibException.php',
    'Seld\\JsonLint\\DuplicateKeyException' => $vendorDir . '/seld/jsonlint/src/Seld/JsonLint/DuplicateKeyException.php',
    'Seld\\JsonLint\\JsonParser' => $vendorDir . '/seld/jsonlint/src/Seld/JsonLint/JsonParser.php',
    'Seld\\JsonLint\\Lexer' => $vendorDir . '/seld/jsonlint/src/Seld/JsonLint/Lexer.php',
    'Seld\\JsonLint\\ParsingException' => $vendorDir . '/seld/jsonlint/src/Seld/JsonLint/ParsingException.php',
    'Seld\\JsonLint\\Undefined' => $vendorDir . '/seld/jsonlint/src/Seld/JsonLint/Undefined.php',
    'Seld\\PharUtils\\Linter' => $vendorDir . '/seld/phar-utils/src/Linter.php',
    'Seld\\PharUtils\\Timestamps' => $vendorDir . '/seld/phar-utils/src/Timestamps.php',
    'Seld\\Signal\\SignalHandler' => $vendorDir . '/seld/signal-handler/src/SignalHandler.php',
    'Stringable' => $vendorDir . '/marc-mabe/php-enum/stubs/Stringable.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\\Command\\TraceableCommand' => $vendorDir . '/symfony/console/Command/TraceableCommand.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\\Output\\FishCompletionOutput' => $vendorDir . '/symfony/console/Completion/Output/FishCompletionOutput.php',
    'Symfony\\Component\\Console\\Completion\\Output\\ZshCompletionOutput' => $vendorDir . '/symfony/console/Completion/Output/ZshCompletionOutput.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\\DataCollector\\CommandDataCollector' => $vendorDir . '/symfony/console/DataCollector/CommandDataCollector.php',
    'Symfony\\Component\\Console\\Debug\\CliRequest' => $vendorDir . '/symfony/console/Debug/CliRequest.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',
    'Symfony\\Component\\Console\\Descriptor\\DescriptorInterface' => $vendorDir . '/symfony/console/Descriptor/DescriptorInterface.php',
    'Symfony\\Component\\Console\\Descriptor\\JsonDescriptor' => $vendorDir . '/symfony/console/Descriptor/JsonDescriptor.php',
    'Symfony\\Component\\Console\\Descriptor\\MarkdownDescriptor' => $vendorDir . '/symfony/console/Descriptor/MarkdownDescriptor.php',
    'Symfony\\Component\\Console\\Descriptor\\ReStructuredTextDescriptor' => $vendorDir . '/symfony/console/Descriptor/ReStructuredTextDescriptor.php',
    'Symfony\\Component\\Console\\Descriptor\\TextDescriptor' => $vendorDir . '/symfony/console/Descriptor/TextDescriptor.php',
    'Symfony\\Component\\Console\\Descriptor\\XmlDescriptor' => $vendorDir . '/symfony/console/Descriptor/XmlDescriptor.php',
    'Symfony\\Component\\Console\\EventListener\\ErrorListener' => $vendorDir . '/symfony/console/EventListener/ErrorListener.php',
    '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\\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\\RunCommandFailedException' => $vendorDir . '/symfony/console/Exception/RunCommandFailedException.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',
    'Symfony\\Component\\Console\\Helper\\HelperSet' => $vendorDir . '/symfony/console/Helper/HelperSet.php',
    'Symfony\\Component\\Console\\Helper\\InputAwareHelper' => $vendorDir . '/symfony/console/Helper/InputAwareHelper.php',
    'Symfony\\Component\\Console\\Helper\\OutputWrapper' => $vendorDir . '/symfony/console/Helper/OutputWrapper.php',
    'Symfony\\Component\\Console\\Helper\\ProcessHelper' => $vendorDir . '/symfony/console/Helper/ProcessHelper.php',
    'Symfony\\Component\\Console\\Helper\\ProgressBar' => $vendorDir . '/symfony/console/Helper/ProgressBar.php',
    'Symfony\\Component\\Console\\Helper\\ProgressIndicator' => $vendorDir . '/symfony/console/Helper/ProgressIndicator.php',
    'Symfony\\Component\\Console\\Helper\\QuestionHelper' => $vendorDir . '/symfony/console/Helper/QuestionHelper.php',
    '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\\Helper\\TerminalInputHelper' => $vendorDir . '/symfony/console/Helper/TerminalInputHelper.php',
    'Symfony\\Component\\Console\\Input\\ArgvInput' => $vendorDir . '/symfony/console/Input/ArgvInput.php',
    'Symfony\\Component\\Console\\Input\\ArrayInput' => $vendorDir . '/symfony/console/Input/ArrayInput.php',
    'Symfony\\Component\\Console\\Input\\Input' => $vendorDir . '/symfony/console/Input/Input.php',
    'Symfony\\Component\\Console\\Input\\InputArgument' => $vendorDir . '/symfony/console/Input/InputArgument.php',
    'Symfony\\Component\\Console\\Input\\InputAwareInterface' => $vendorDir . '/symfony/console/Input/InputAwareInterface.php',
    'Symfony\\Component\\Console\\Input\\InputDefinition' => $vendorDir . '/symfony/console/Input/InputDefinition.php',
    'Symfony\\Component\\Console\\Input\\InputInterface' => $vendorDir . '/symfony/console/Input/InputInterface.php',
    'Symfony\\Component\\Console\\Input\\InputOption' => $vendorDir . '/symfony/console/Input/InputOption.php',
    'Symfony\\Component\\Console\\Input\\StreamableInputInterface' => $vendorDir . '/symfony/console/Input/StreamableInputInterface.php',
    'Symfony\\Component\\Console\\Input\\StringInput' => $vendorDir . '/symfony/console/Input/StringInput.php',
    'Symfony\\Component\\Console\\Logger\\ConsoleLogger' => $vendorDir . '/symfony/console/Logger/ConsoleLogger.php',
    'Symfony\\Component\\Console\\Messenger\\RunCommandContext' => $vendorDir . '/symfony/console/Messenger/RunCommandContext.php',
    'Symfony\\Component\\Console\\Messenger\\RunCommandMessage' => $vendorDir . '/symfony/console/Messenger/RunCommandMessage.php',
    'Symfony\\Component\\Console\\Messenger\\RunCommandMessageHandler' => $vendorDir . '/symfony/console/Messenger/RunCommandMessageHandler.php',
    'Symfony\\Component\\Console\\Output\\AnsiColorMode' => $vendorDir . '/symfony/console/Output/AnsiColorMode.php',
    '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\\SignalMap' => $vendorDir . '/symfony/console/SignalRegistry/SignalMap.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\\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\\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\\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\\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\\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\\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',
    'Symfony\\Component\\Finder\\Iterator\\DepthRangeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/DepthRangeFilterIterator.php',
    'Symfony\\Component\\Finder\\Iterator\\ExcludeDirectoryFilterIterator' => $vendorDir . '/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php',
    '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\\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\\Process\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/process/Exception/ExceptionInterface.php',
    'Symfony\\Component\\Process\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/process/Exception/InvalidArgumentException.php',
    'Symfony\\Component\\Process\\Exception\\LogicException' => $vendorDir . '/symfony/process/Exception/LogicException.php',
    'Symfony\\Component\\Process\\Exception\\ProcessFailedException' => $vendorDir . '/symfony/process/Exception/ProcessFailedException.php',
    'Symfony\\Component\\Process\\Exception\\ProcessSignaledException' => $vendorDir . '/symfony/process/Exception/ProcessSignaledException.php',
    'Symfony\\Component\\Process\\Exception\\ProcessTimedOutException' => $vendorDir . '/symfony/process/Exception/ProcessTimedOutException.php',
    'Symfony\\Component\\Process\\Exception\\RunProcessFailedException' => $vendorDir . '/symfony/process/Exception/RunProcessFailedException.php',
    'Symfony\\Component\\Process\\Exception\\RuntimeException' => $vendorDir . '/symfony/process/Exception/RuntimeException.php',
    'Symfony\\Component\\Process\\ExecutableFinder' => $vendorDir . '/symfony/process/ExecutableFinder.php',
    'Symfony\\Component\\Process\\InputStream' => $vendorDir . '/symfony/process/InputStream.php',
    'Symfony\\Component\\Process\\Messenger\\RunProcessContext' => $vendorDir . '/symfony/process/Messenger/RunProcessContext.php',
    'Symfony\\Component\\Process\\Messenger\\RunProcessMessage' => $vendorDir . '/symfony/process/Messenger/RunProcessMessage.php',
    'Symfony\\Component\\Process\\Messenger\\RunProcessMessageHandler' => $vendorDir . '/symfony/process/Messenger/RunProcessMessageHandler.php',
    'Symfony\\Component\\Process\\PhpExecutableFinder' => $vendorDir . '/symfony/process/PhpExecutableFinder.php',
    'Symfony\\Component\\Process\\PhpProcess' => $vendorDir . '/symfony/process/PhpProcess.php',
    'Symfony\\Component\\Process\\PhpSubprocess' => $vendorDir . '/symfony/process/PhpSubprocess.php',
    'Symfony\\Component\\Process\\Pipes\\AbstractPipes' => $vendorDir . '/symfony/process/Pipes/AbstractPipes.php',
    'Symfony\\Component\\Process\\Pipes\\PipesInterface' => $vendorDir . '/symfony/process/Pipes/PipesInterface.php',
    'Symfony\\Component\\Process\\Pipes\\UnixPipes' => $vendorDir . '/symfony/process/Pipes/UnixPipes.php',
    'Symfony\\Component\\Process\\Pipes\\WindowsPipes' => $vendorDir . '/symfony/process/Pipes/WindowsPipes.php',
    'Symfony\\Component\\Process\\Process' => $vendorDir . '/symfony/process/Process.php',
    'Symfony\\Component\\Process\\ProcessUtils' => $vendorDir . '/symfony/process/ProcessUtils.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\\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\\ContainerAwareInterface' => $vendorDir . '/symfony/service-contracts/ContainerAwareInterface.php',
    'Symfony\\Contracts\\Service\\ResetInterface' => $vendorDir . '/symfony/service-contracts/ResetInterface.php',
    'Symfony\\Contracts\\Service\\ServiceCollectionInterface' => $vendorDir . '/symfony/service-contracts/ServiceCollectionInterface.php',
    'Symfony\\Contracts\\Service\\ServiceLocatorTrait' => $vendorDir . '/symfony/service-contracts/ServiceLocatorTrait.php',
    'Symfony\\Contracts\\Service\\ServiceMethodsSubscriberTrait' => $vendorDir . '/symfony/service-contracts/ServiceMethodsSubscriberTrait.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\\Polyfill\\Ctype\\Ctype' => $vendorDir . '/symfony/polyfill-ctype/Ctype.php',
    'Symfony\\Polyfill\\Intl\\Grapheme\\Grapheme' => $vendorDir . '/symfony/polyfill-intl-grapheme/Grapheme.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\\Php84\\Php84' => $vendorDir . '/symfony/polyfill-php84/Php84.php',
    'ThePhpFoundation\\Attestation\\Attestation' => $vendorDir . '/thephpf/attestation/src/Attestation.php',
    'ThePhpFoundation\\Attestation\\AttestationException' => $vendorDir . '/thephpf/attestation/src/AttestationException.php',
    'ThePhpFoundation\\Attestation\\FilenameWithChecksum' => $vendorDir . '/thephpf/attestation/src/FilenameWithChecksum.php',
    'ThePhpFoundation\\Attestation\\FulcioSigstoreOidExtensions' => $vendorDir . '/thephpf/attestation/src/FulcioSigstoreOidExtensions.php',
    'ThePhpFoundation\\Attestation\\Verification\\Exception\\DigestMismatch' => $vendorDir . '/thephpf/attestation/src/Verification/Exception/DigestMismatch.php',
    'ThePhpFoundation\\Attestation\\Verification\\Exception\\FailedToVerifyArtifact' => $vendorDir . '/thephpf/attestation/src/Verification/Exception/FailedToVerifyArtifact.php',
    'ThePhpFoundation\\Attestation\\Verification\\Exception\\InvalidDerEncodedStringLength' => $vendorDir . '/thephpf/attestation/src/Verification/Exception/InvalidDerEncodedStringLength.php',
    'ThePhpFoundation\\Attestation\\Verification\\Exception\\InvalidSubjectDefinition' => $vendorDir . '/thephpf/attestation/src/Verification/Exception/InvalidSubjectDefinition.php',
    'ThePhpFoundation\\Attestation\\Verification\\Exception\\IssuerCertificateVerificationFailed' => $vendorDir . '/thephpf/attestation/src/Verification/Exception/IssuerCertificateVerificationFailed.php',
    'ThePhpFoundation\\Attestation\\Verification\\Exception\\MismatchingExtensionValues' => $vendorDir . '/thephpf/attestation/src/Verification/Exception/MismatchingExtensionValues.php',
    'ThePhpFoundation\\Attestation\\Verification\\Exception\\MissingAttestation' => $vendorDir . '/thephpf/attestation/src/Verification/Exception/MissingAttestation.php',
    'ThePhpFoundation\\Attestation\\Verification\\Exception\\NoIssuerCertificateInTrustedRoot' => $vendorDir . '/thephpf/attestation/src/Verification/Exception/NoIssuerCertificateInTrustedRoot.php',
    'ThePhpFoundation\\Attestation\\Verification\\Exception\\NoOpenSsl' => $vendorDir . '/thephpf/attestation/src/Verification/Exception/NoOpenSsl.php',
    'ThePhpFoundation\\Attestation\\Verification\\Exception\\SignatureVerificationFailed' => $vendorDir . '/thephpf/attestation/src/Verification/Exception/SignatureVerificationFailed.php',
    'ThePhpFoundation\\Attestation\\Verification\\VerifyAttestation' => $vendorDir . '/thephpf/attestation/src/Verification/VerifyAttestation.php',
    'ThePhpFoundation\\Attestation\\Verification\\VerifyAttestationWithOpenSsl' => $vendorDir . '/thephpf/attestation/src/Verification/VerifyAttestationWithOpenSsl.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',
);
##
## Bundle of CA Root Certificates
##
## Certificate data from Mozilla last updated on: Wed Feb 11 18:26:30 2026 GMT
##
## Find updated versions here: https://curl.se/docs/caextract.html
##
## This is a bundle of X.509 certificates of public Certificate Authorities
## (CA). These were automatically extracted from Mozilla's root certificates
## file (certdata.txt).  This file can be found in the mozilla source tree:
## https://raw.githubusercontent.com/mozilla-firefox/firefox/refs/heads/release/security/nss/lib/ckfw/builtins/certdata.txt
##
## It contains the certificates in PEM format and therefore
## can be directly used with curl / libcurl / php_curl, or with
## an Apache+mod_ssl webserver for SSL client authentication.
## Just configure this file as the SSLCACertificateFile.
##
## Conversion done with mk-ca-bundle.pl version 1.32.
## SHA256: 3b98d4e3ff57a326d9587c33633039c8c3a9cf0b55f7ca581d7598ff329eb1f3
##


Entrust Root Certification Authority
====================================
-----BEGIN CERTIFICATE-----
MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAUBgNV
BAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0Lm5ldC9DUFMgaXMgaW5jb3Jw
b3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMWKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsG
A1UEAxMkRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0
MloXDTI2MTEyNzIwNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMu
MTkwNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSByZWZlcmVu
Y2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNVBAMTJEVudHJ1c3QgUm9v
dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
ALaVtkNC+sZtKm9I35RMOVcF7sN5EUFoNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYsz
A9u3g3s+IIRe7bJWKKf44LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOww
Cj0Yzfv9KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGIrb68
j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi94DkZfs0Nw4pgHBN
rziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOBsDCBrTAOBgNVHQ8BAf8EBAMCAQYw
DwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAigA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1
MzQyWjAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DH
hmak8fdLQ/uEvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA
A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9tO1KzKtvn1ISM
Y/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6ZuaAGAT/3B+XxFNSRuzFVJ7yVTa
v52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTS
W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0
tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8
-----END CERTIFICATE-----

QuoVadis Root CA 2
==================
-----BEGIN CERTIFICATE-----
MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT
EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMjAeFw0wNjExMjQx
ODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4IC
DwAwggIKAoICAQCaGMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6
XJxgFyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55JWpzmM+Yk
lvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bBrrcCaoF6qUWD4gXmuVbB
lDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp+ARz8un+XJiM9XOva7R+zdRcAitMOeGy
lZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt
66/3FsvbzSUr5R/7mp/iUcw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1Jdxn
wQ5hYIizPtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og/zOh
D7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UHoycR7hYQe7xFSkyy
BNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuIyV77zGHcizN300QyNQliBJIWENie
J0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1Ud
DgQWBBQahGK8SEwzJQTU7tD2A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGU
a6FJpEcwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT
ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2fBluornFdLwUv
Z+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzng/iN/Ae42l9NLmeyhP3ZRPx3
UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2BlfF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodm
VjB3pjd4M1IQWK4/YY7yarHvGH5KWWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK
+JDSV6IZUaUtl0HaB0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrW
IozchLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPRTUIZ3Ph1
WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWDmbA4CD/pXvk1B+TJYm5X
f6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0ZohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II
4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8
VCLAAVBpQ570su9t+Oza8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u
-----END CERTIFICATE-----

QuoVadis Root CA 3
==================
-----BEGIN CERTIFICATE-----
MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT
EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMzAeFw0wNjExMjQx
OTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4IC
DwAwggIKAoICAQDMV0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNgg
DhoB4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUrH556VOij
KTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd8lyyBTNvijbO0BNO/79K
DDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9CabwvvWhDFlaJKjdhkf2mrk7AyxRllDdLkgbv
BNDInIjbC3uBr7E9KsRlOni27tyAsdLTmZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwp
p5ijJUMv7/FfJuGITfhebtfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8
nT8KKdjcT5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDtWAEX
MJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZc6tsgLjoC2SToJyM
Gf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A4iLItLRkT9a6fUg+qGkM17uGcclz
uD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYDVR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHT
BgkrBgEEAb5YAAMwgcUwgZMGCCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmlj
YXRlIGNvbnN0aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0
aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVudC4wLQYIKwYB
BQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2NwczALBgNVHQ8EBAMCAQYwHQYD
VR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4GA1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4
ywLQoUmkRzBFMQswCQYDVQQGEwJCTTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UE
AxMSUXVvVmFkaXMgUm9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZV
qyM07ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSemd1o417+s
hvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd+LJ2w/w4E6oM3kJpK27z
POuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2
Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadNt54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp
8kokUvd0/bpO5qgdAm6xDYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBC
bjPsMZ57k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6szHXu
g/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0jWy10QJLZYxkNc91p
vGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeTmJlglFwjz1onl14LBQaTNx47aTbr
qZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto=
-----END CERTIFICATE-----

DigiCert Assured ID Root CA
===========================
-----BEGIN CERTIFICATE-----
MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw
IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzEx
MTEwMDAwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL
ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0Ew
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7cJpSIqvTO
9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYPmDI2dsze3Tyoou9q+yHy
UmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW
/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpy
oeb6pNnVFzF1roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf
GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRF
66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkq
hkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2Bc
EkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38Fn
SbNd67IJKusm7Xi+fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i
8b5QZ7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe
+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g==
-----END CERTIFICATE-----

DigiCert Global Root CA
=======================
-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw
HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAw
MDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsBCSDMAZOn
TjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97nh6Vfe63SKMI2tavegw5
BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt43C/dxC//AH2hdmoRBBYMql1GNXRor5H
4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y
7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQAB
o2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm
8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEF
BQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmr
EbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIt
tep3Sp+dWOIrWcBAI+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886
UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
-----END CERTIFICATE-----

DigiCert High Assurance EV Root CA
==================================
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw
KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw
MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ
MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu
Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t
Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS
OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3
MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ
NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe
h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB
Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY
JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ
V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp
myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK
mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K
-----END CERTIFICATE-----

SwissSign Gold CA - G2
======================
-----BEGIN CERTIFICATE-----
MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkNIMRUw
EwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzIwHhcN
MDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBFMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dp
c3NTaWduIEFHMR8wHQYDVQQDExZTd2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0B
AQEFAAOCAg8AMIICCgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUq
t2/876LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+bbqBHH5C
jCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c6bM8K8vzARO/Ws/BtQpg
vd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqEemA8atufK+ze3gE/bk3lUIbLtK/tREDF
ylqM2tIrfKjuvqblCqoOpd8FUrdVxyJdMmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvR
AiTysybUa9oEVeXBCsdtMDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuend
jIj3o02yMszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69yFGkO
peUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPiaG59je883WX0XaxR
7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxMgI93e2CaHt+28kgeDrpOVG2Y4OGi
GqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw
AwEB/zAdBgNVHQ4EFgQUWyV7lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64
OfPAeGZe6Drn8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov
L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe645R88a7A3hfm
5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczOUYrHUDFu4Up+GC9pWbY9ZIEr
44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOf
Mke6UiI0HTJ6CVanfCU2qT1L2sCCbwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6m
Gu6uLftIdxf+u+yvGPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxp
mo/a77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCChdiDyyJk
vC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid392qgQmwLOM7XdVAyksLf
KzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEppLd6leNcG2mqeSz53OiATIgHQv2ieY2Br
NU0LbbqhPcCT4H8js1WtciVORvnSFu+wZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6Lqj
viOvrv1vA+ACOzB2+httQc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ
-----END CERTIFICATE-----

SecureTrust CA
==============
-----BEGIN CERTIFICATE-----
MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQG
EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xFzAVBgNVBAMTDlNlY3VyZVRy
dXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIzMTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAe
BgNVBAoTF1NlY3VyZVRydXN0IENvcnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQX
OZEzZum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO0gMdA+9t
DWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIaowW8xQmxSPmjL8xk037uH
GFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b
01k/unK8RCSc43Oz969XL0Imnal0ugBS8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmH
ursCAwEAAaOBnTCBmjATBgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/
BAUwAwEB/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCegJYYj
aHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ
KoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt36Z3q059c4EVlew3KW+JwULKUBRSu
SceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHf
mbx8IVQr5Fiiu1cprp6poxkmD5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZ
nMUFdAvnZyPSCPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR
3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE=
-----END CERTIFICATE-----

Secure Global CA
================
-----BEGIN CERTIFICATE-----
MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQG
EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBH
bG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkxMjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEg
MB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwg
Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jx
YDiJiQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa/FHtaMbQ
bqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJjnIFHovdRIWCQtBJwB1g
8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnIHmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYV
HDGA76oYa8J719rO+TMg1fW9ajMtgQT7sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi
0XPnj3pDAgMBAAGjgZ0wgZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud
EwEB/wQFMAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCswKaAn
oCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsGAQQBgjcVAQQDAgEA
MA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0LURYD7xh8yOOvaliTFGCRsoTciE6+
OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXOH0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cn
CDpOGR86p1hcF895P4vkp9MmI50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/5
3CYNv6ZHdAbYiNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc
f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW
-----END CERTIFICATE-----

COMODO Certification Authority
==============================
-----BEGIN CERTIFICATE-----
MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UE
BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG
A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNVBAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1
dGhvcml0eTAeFw0wNjEyMDEwMDAwMDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEb
MBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD
T01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0aG9yaXR5
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3UcEbVASY06m/weaKXTuH
+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI2GqGd0S7WWaXUF601CxwRM/aN5VCaTww
xHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV
4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA
1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5OnKVI
rLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAOBgNVHQ8BAf8E
BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9k
b2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOC
AQEAPpiem/Yb6dc5t3iuHXIYSdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CP
OGEIqB6BCsAvIC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/
RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4zJVSk/BwJVmc
IGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5ddBA6+C4OmF4O5MBKgxTMVBbkN
+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IBZQ==
-----END CERTIFICATE-----

COMODO ECC Certification Authority
==================================
-----BEGIN CERTIFICATE-----
MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTELMAkGA1UEBhMC
R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE
ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBB
dXRob3JpdHkwHhcNMDgwMzA2MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0Ix
GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRo
b3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSRFtSrYpn1PlILBs5BAH+X
4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0JcfRK9ChQtP6IHG4/bC8vCVlbpVsLM5ni
wz2J+Wos77LTBumjQjBAMB0GA1UdDgQWBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8E
BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VG
FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA
U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY=
-----END CERTIFICATE-----

Certigna
========
-----BEGIN CERTIFICATE-----
MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNVBAYTAkZSMRIw
EAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4XDTA3MDYyOTE1MTMwNVoXDTI3
MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwI
Q2VydGlnbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7q
XOEm7RFHYeGifBZ4QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyH
GxnygQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbwzBfsV1/p
ogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q130yGLMLLGq/jj8UEYkg
DncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKf
Irjxwo1p3Po6WAbfAgMBAAGjgbwwgbkwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQ
tCRZvgHyUtVF9lo53BEwZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJ
BgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzjAQ/J
SP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG9w0BAQUFAAOCAQEA
hQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8hbV6lUmPOEvjvKtpv6zf+EwLHyzs+
ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFncfca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1klu
PBS1xp81HlDQwY9qcEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY
1gkIl2PlwS6wt0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw
WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg==
-----END CERTIFICATE-----

ePKI Root Certification Authority
=================================
-----BEGIN CERTIFICATE-----
MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQG
EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xKjAoBgNVBAsMIWVQS0kg
Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMx
MjdaMF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEq
MCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0B
AQEFAAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAHSyZbCUNs
IZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAhijHyl3SJCRImHJ7K2RKi
lTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3XDZoTM1PRYfl61dd4s5oz9wCGzh1NlDiv
qOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX
12ruOzjjK9SXDrkb5wdJfzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0O
WQqraffAsgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uUWH1+
ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLSnT0IFaUQAS2zMnao
lQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pHdmX2Os+PYhcZewoozRrSgx4hxyy/
vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJipNiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXi
Zo1jDiVN1Rmy5nk3pyKdVDECAwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/Qkqi
MAwGA1UdEwQFMAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH
ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGBuvl2ICO1J2B0
1GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6YlPwZpVnPDimZI+ymBV3QGypzq
KOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkPJXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdV
xrsStZf0X4OFunHB2WyBEXYKCrC/gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEP
NXubrjlpC2JgQCA2j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+r
GNm65ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUBo2M3IUxE
xJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS/jQ6fbjpKdx2qcgw+BRx
gMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2zGp1iro2C6pSe3VkQw63d4k3jMdXH7Ojy
sP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTEW9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmOD
BCEIZ43ygknQW/2xzQ+DhNQ+IIX3Sj0rnP0qCglN6oH4EZw=
-----END CERTIFICATE-----

certSIGN ROOT CA
================
-----BEGIN CERTIFICATE-----
MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYTAlJPMREwDwYD
VQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTAeFw0wNjA3MDQxNzIwMDRa
Fw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UE
CxMQY2VydFNJR04gUk9PVCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7I
JUqOtdu0KBuqV5Do0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHH
rfAQUySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5dRdY4zTW2
ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQOA7+j0xbm0bqQfWwCHTD
0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwvJoIQ4uNllAoEwF73XVv4EOLQunpL+943
AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B
Af8EBAMCAcYwHQYDVR0OBBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IB
AQA+0hyJLjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecYMnQ8
SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ44gx+FkagQnIl6Z0
x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6IJd1hJyMctTEHBDa0GpC9oHRxUIlt
vBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNwi/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7Nz
TogVZ96edhBiIL5VaZVDADlN9u6wWk5JRFRYX0KD
-----END CERTIFICATE-----

NetLock Arany (Class Gold) Főtanúsítvány
========================================
-----BEGIN CERTIFICATE-----
MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTERMA8G
A1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610
dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBB
cmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgx
MjA2MTUwODIxWjCBpzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxO
ZXRMb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlmaWNhdGlv
biBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNzIEdvbGQpIEbFkXRhbsO6
c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu
0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrTlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw
/HpYzY6b7cNGbIRwXdrzAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAk
H3B5r9s5VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRGILdw
fzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2BJtr+UBdADTHLpl1
neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIB
BjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2MU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwW
qZw8UQCgwBEIBaeZ5m8BiFRhbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTta
YtOUZcTh5m2C+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC
bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2FuLjbvrW5Kfna
NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu
dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=
-----END CERTIFICATE-----

Microsec e-Szigno Root CA 2009
==============================
-----BEGIN CERTIFICATE-----
MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYDVQQGEwJIVTER
MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jv
c2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o
dTAeFw0wOTA2MTYxMTMwMThaFw0yOTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UE
BwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUt
U3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvPkd6mJviZpWNwrZuuyjNA
fW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tccbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG
0IMZfcChEhyVbUr02MelTTMuhTlAdX4UfIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKA
pxn1ntxVUwOXewdI/5n7N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm
1HxdrtbCxkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1+rUC
AwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTLD8bf
QkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAbBgNVHREE
FDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqGSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0o
lZMEyL/azXm4Q5DwpL7v8u8hmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfX
I/OMn74dseGkddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775
tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c2Pm2G2JwCz02
yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi
LXpUq3DDfSJlgnCW
-----END CERTIFICATE-----

GlobalSign Root CA - R3
=======================
-----BEGIN CERTIFICATE-----
MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4GA1UECxMXR2xv
YmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh
bFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT
aWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln
bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWt
iHL8RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsTgHeMCOFJ
0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmmKPZpO/bLyCiR5Z2KYVc3
rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zdQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjl
OCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2
xmmFghcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
FI/wS3+oLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZURUm7
lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMpjjM5RcOO5LlXbKr8
EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK6fBdRoyV3XpYKBovHd7NADdBj+1E
bddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQXmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18
YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7r
kpeDMdmztcpHWD9f
-----END CERTIFICATE-----

Izenpe.com
==========
-----BEGIN CERTIFICATE-----
MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4MQswCQYDVQQG
EwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wHhcNMDcxMjEz
MTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMu
QS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ
03rKDx6sp4boFmVqscIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAK
ClaOxdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6HLmYRY2xU
+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFXuaOKmMPsOzTFlUFpfnXC
PCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQDyCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxT
OTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbK
F7jJeodWLBoBHmy+E60QrLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK
0GqfvEyNBjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8Lhij+
0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIBQFqNeb+Lz0vPqhbB
leStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+HMh3/1uaD7euBUbl8agW7EekFwID
AQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2luZm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+
SVpFTlBFIFMuQS4gLSBDSUYgQTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBG
NjIgUzgxQzBBBgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx
MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O
BBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUAA4ICAQB4pgwWSp9MiDrAyw6l
Fn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWblaQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbga
kEyrkgPH7UIBzg/YsfqikuFgba56awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8q
hT/AQKM6WfxZSzwoJNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Cs
g1lwLDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCTVyvehQP5
aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGkLhObNA5me0mrZJfQRsN5
nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJbUjWumDqtujWTI6cfSN01RpiyEGjkpTHC
ClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZo
Q0iy2+tzJOeRf1SktoA+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1Z
WrOZyGlsQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw==
-----END CERTIFICATE-----

Go Daddy Root Certificate Authority - G2
========================================
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT
B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMu
MTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5
MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6
b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8G
A1UEAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKDE6bFIEMBO4Tx5oVJnyfq
9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD
+qK+ihVqf94Lw7YZFAXK6sOoBJQ7RnwyDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutd
fMh8+7ArU6SSYmlRJQVhGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMl
NAJWJwGRtDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEAAaNC
MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDqahQcQZyi27/a9
BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmXWWcDYfF+OwYxdS2hII5PZYe096ac
vNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r
5N9ss4UXnT3ZJE95kTXWXwTrgIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYV
N8Gb5DKj7Tjo2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO
LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI4uJEvlz36hz1
-----END CERTIFICATE-----

Starfield Root Certificate Authority - G2
=========================================
-----BEGIN CERTIFICATE-----
MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT
B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s
b2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVsZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0
eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAw
DgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQg
VGVjaG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZpY2F0ZSBB
dXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3twQP89o/8ArFv
W59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMgnLRJdzIpVv257IzdIvpy3Cdhl+72WoTs
bhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNk
N3mSwOxGXn/hbVNMYq/NHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7Nf
ZTD4p7dNdloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0HZbU
JtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
AQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0GCSqGSIb3DQEBCwUAA4IBAQARWfol
TwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjUsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx
4mcujJUDJi5DnUox9g61DLu34jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUw
F5okxBDgBPfg8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K
pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1mMpYjn0q7pBZ
c2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0
-----END CERTIFICATE-----

Starfield Services Root Certificate Authority - G2
==================================================
-----BEGIN CERTIFICATE-----
MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT
B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s
b2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRl
IEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNV
BAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxT
dGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2VydmljZXMg
Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20pOsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2
h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm28xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4Pa
hHQUw2eeBGg6345AWh1KTs9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLP
LJGmpufehRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk6mFB
rMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAwDwYDVR0TAQH/BAUw
AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMA0GCSqG
SIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMIbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPP
E95Dz+I0swSdHynVv/heyNXBve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTy
xQGjhdByPq1zqwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd
iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn0q23KXB56jza
YyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCNsSi6
-----END CERTIFICATE-----

AffirmTrust Commercial
======================
-----BEGIN CERTIFICATE-----
MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxFDAS
BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMB4XDTEw
MDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly
bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6Eqdb
DuKPHx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yrba0F8PrV
C8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPALMeIrJmqbTFeurCA+ukV6
BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1yHp52UKqK39c/s4mT6NmgTWvRLpUHhww
MmWd5jyTXlBOeuM61G7MGvv50jeuJCqrVwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNV
HQ4EFgQUnZPGU4teyq8/nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
AQYwDQYJKoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYGXUPG
hi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNjvbz4YYCanrHOQnDi
qX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivtZ8SOyUOyXGsViQK8YvxO8rUzqrJv
0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9gN53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0kh
sUlHRUe072o0EclNmsxZt9YCnlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8=
-----END CERTIFICATE-----

AffirmTrust Networking
======================
-----BEGIN CERTIFICATE-----
MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UEBhMCVVMxFDAS
BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMB4XDTEw
MDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly
bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SE
Hi3yYJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbuakCNrmreI
dIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRLQESxG9fhwoXA3hA/Pe24
/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gb
h+0t+nvujArjqWaJGctB+d1ENmHP4ndGyH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNV
HQ4EFgQUBx/S55zawm6iQLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
AQYwDQYJKoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfOtDIu
UFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzuQY0x2+c06lkh1QF6
12S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZLgo/bNjR9eUJtGxUAArgFU2HdW23
WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4uolu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9
/ZFvgrG+CJPbFEfxojfHRZ48x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s=
-----END CERTIFICATE-----

AffirmTrust Premium
===================
-----BEGIN CERTIFICATE-----
MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UEBhMCVVMxFDAS
BgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMB4XDTEwMDEy
OTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRy
dXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
MIICCgKCAgEAxBLfqV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtn
BKAQJG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ+jjeRFcV
5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrSs8PhaJyJ+HoAVt70VZVs
+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmd
GPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d770O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5R
p9EixAqnOEhss/n/fauGV+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NI
S+LI+H+SqHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S5u04
6uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4IaC1nEWTJ3s7xgaVY5
/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TXOwF0lkLgAOIua+rF7nKsu7/+6qqo
+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYEFJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB
/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByv
MiPIs0laUZx2KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg
Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B8OWycvpEgjNC
6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQMKSOyARiqcTtNd56l+0OOF6S
L5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK
+4w1IX2COPKpVJEZNZOUbWo6xbLQu4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmV
BtWVyuEklut89pMFu+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFg
IxpHYoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8GKa1qF60
g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaORtGdFNrHF+QFlozEJLUb
zxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6eKeC2uAloGRwYQw==
-----END CERTIFICATE-----

AffirmTrust Premium ECC
=======================
-----BEGIN CERTIFICATE-----
MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMCVVMxFDASBgNV
BAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQcmVtaXVtIEVDQzAeFw0xMDAx
MjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1U
cnVzdDEgMB4GA1UEAwwXQWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQA
IgNiAAQNMF4bFZ0D0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQ
N8O9ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0GA1UdDgQW
BBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAK
BggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/VsaobgxCd05DhT1wV/GzTjxi+zygk8N53X
57hG8f2h4nECMEJZh0PUUd+60wkyWs6Iflc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKM
eQ==
-----END CERTIFICATE-----

Certum Trusted Network CA
=========================
-----BEGIN CERTIFICATE-----
MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK
ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIy
MTIwNzM3WhcNMjkxMjMxMTIwNzM3WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBU
ZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5
MSIwIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rHUV+rpDKmYYe2bg+G0jAC
l/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LMTXPb865Px1bVWqeWifrzq2jUI4ZZJ88J
J7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVUBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4
fOQtf/WsX+sWn7Et0brMkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0
cvW0QM8xAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNVHRMB
Af8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNVHQ8BAf8EBAMCAQYw
DQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15ysHhE49wcrwn9I0j6vSrEuVUEtRCj
jSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfLI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1
mS1FhIrlQgnXdAIv94nYmem8J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5aj
Zt3hrvJBW8qYVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI
03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw=
-----END CERTIFICATE-----

TWCA Root Certification Authority
=================================
-----BEGIN CERTIFICATE-----
MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJ
VEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMzWhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQG
EwJUVzESMBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NB
IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFEAcK0HMMx
QhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HHK3XLfJ+utdGdIzdjp9xC
oi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeXRfwZVzsrb+RH9JlF/h3x+JejiB03HFyP
4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/zrX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1r
y+UPizgN7gr8/g+YnzAx3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIB
BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkqhkiG
9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeCMErJk/9q56YAf4lC
mtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdlsXebQ79NqZp4VKIV66IIArB6nCWlW
QtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62Dlhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVY
T0bf+215WfKEIlKuD8z7fDvnaspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocny
Yh0igzyXxfkZYiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw==
-----END CERTIFICATE-----

Security Communication RootCA2
==============================
-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc
U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMeU2VjdXJpdHkgQ29tbXVuaWNh
dGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoXDTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMC
SlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3Vy
aXR5IENvbW11bmljYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
ANAVOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGrzbl+dp++
+T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVMVAX3NuRFg3sUZdbcDE3R
3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQhNBqyjoGADdH5H5XTz+L62e4iKrFvlNV
spHEfbmwhRkGeC7bYRr6hfVKkaHnFtWOojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1K
EOtOghY6rCcMU/Gt1SSwawNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8
QIH4D5csOPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB
CwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpFcoJxDjrSzG+ntKEj
u/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXcokgfGT+Ok+vx+hfuzU7jBBJV1uXk
3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6q
tnRGEmyR7jTV7JqR50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29
mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03
-----END CERTIFICATE-----

Actalis Authentication Root CA
==============================
-----BEGIN CERTIFICATE-----
MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCSVQxDjAM
BgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UE
AwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDky
MjExMjIwMlowazELMAkGA1UEBhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlz
IFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290
IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNvUTufClrJ
wkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX4ay8IMKx4INRimlNAJZa
by/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9KK3giq0itFZljoZUj5NDKd45RnijMCO6
zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1f
YVEiVRvjRuPjPdA1YprbrxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2
oxgkg4YQ51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2Fbe8l
EfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxeKF+w6D9Fz8+vm2/7
hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4Fv6MGn8i1zeQf1xcGDXqVdFUNaBr8
EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbnfpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5
jF66CyCU3nuDuP/jVo23Eek7jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLY
iDrIn3hm7YnzezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt
ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQALe3KHwGCmSUyI
WOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70jsNjLiNmsGe+b7bAEzlgqqI0
JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDzWochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKx
K3JCaKygvU5a2hi/a5iB0P2avl4VSM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+
Xlff1ANATIGk0k9jpwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC
4yyXX04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+OkfcvHlXHo
2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7RK4X9p2jIugErsWx0Hbhz
lefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btUZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXem
OR/qnuOf0GZvBeyqdn6/axag67XH/JJULysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9
vwGYT7JZVEc+NHt4bVaTLnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg==
-----END CERTIFICATE-----

Buypass Class 2 Root CA
=======================
-----BEGIN CERTIFICATE-----
MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU
QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290IENBMB4X
DTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1
eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIw
DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1
g1Lr6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPVL4O2fuPn
9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC911K2GScuVr1QGbNgGE41b
/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHxMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqU
CqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeff
awrbD02TTqigzXsu8lkBarcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgI
zRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhn
Bkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/TizPJhk9H9Z2vX
Uq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHs
M+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD
VR0OBBYEFMmAd+BikoL1RpzzuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF
AAOCAgEAU18h9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s
A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EI
osHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48S
aq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYd
DnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWD
LfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0
oyLQI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK75t98biGC
wWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h3PFaTWwyI0PurKju7koS
CTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv4x3kqdbQCtCev9eBCfHJxyYN
rJgWVqA=
-----END CERTIFICATE-----

Buypass Class 3 Root CA
=======================
-----BEGIN CERTIFICATE-----
MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU
QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMyBSb290IENBMB4X
DTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFowTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1
eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIw
DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRH
sJ8YZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3EN3coTRiR
5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9tznDDgFHmV0ST9tD+leh
7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX0DJq1l1sDPGzbjniazEuOQAnFN44wOwZ
ZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH
2xc519woe2v1n/MuwU8XKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV
/afmiSTYzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvSO1UQ
RwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D34xFMFbG02SrZvPA
Xpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgPK9Dx2hzLabjKSWJtyNBjYt1gD1iq
j6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD
VR0OBBYEFEe4zf/lb+74suwvTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF
AAOCAgEAACAjQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV
cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXSIGrs/CIBKM+G
uIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2HJLw5QY33KbmkJs4j1xrG0aG
Q0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsaO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8
ZORK15FTAaggiG6cX0S5y2CBNOxv033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2
KSb12tjE8nVhz36udmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz
6MkEkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg413OEMXbug
UZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvDu79leNKGef9JOxqDDPDe
eOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq4/g7u9xN12TyUb7mqqta6THuBrxzvxNi
Cp/HuZc=
-----END CERTIFICATE-----

T-TeleSec GlobalRoot Class 3
============================
-----BEGIN CERTIFICATE-----
MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM
IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU
cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgx
MDAxMTAyOTU2WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz
dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD
ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN8ELg63iIVl6bmlQdTQyK
9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/RLyTPWGrTs0NvvAgJ1gORH8EGoel15YU
NpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZF
iP0Zf3WHHx+xGwpzJFu5ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W
0eDrXltMEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGjQjBA
MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1A/d2O2GCahKqGFPr
AyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOyWL6ukK2YJ5f+AbGwUgC4TeQbIXQb
fsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzT
ucpH9sry9uetuUg/vBa3wW306gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7h
P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml
e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw==
-----END CERTIFICATE-----

D-TRUST Root Class 3 CA 2 2009
==============================
-----BEGIN CERTIFICATE-----
MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQK
DAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTAe
Fw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NThaME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxE
LVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOAD
ER03UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42tSHKXzlA
BF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9RySPocq60vFYJfxLLHLGv
KZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsMlFqVlNpQmvH/pStmMaTJOKDfHR+4CS7z
p+hnUquVH+BGPtikw8paxTGA6Eian5Rp/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUC
AwEAAaOCARowggEWMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ
4PGEMA4GA1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVjdG9y
eS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUyMENBJTIwMiUyMDIw
MDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwQ6BBoD+G
PWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAw
OS5jcmwwDQYJKoZIhvcNAQELBQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm
2H6NMLVwMeniacfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0
o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4KzCUqNQT4YJEV
dT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8PIWmawomDeCTmGCufsYkl4ph
X5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3YJohw1+qRzT65ysCQblrGXnRl11z+o+I=
-----END CERTIFICATE-----

D-TRUST Root Class 3 CA 2 EV 2009
=================================
-----BEGIN CERTIFICATE-----
MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK
DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw
OTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUwNDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK
DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw
OTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfS
egpnljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM03TP1YtHh
zRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6ZqQTMFexgaDbtCHu39b+T
7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lRp75mpoo6Kr3HGrHhFPC+Oh25z1uxav60
sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure35
11H3a6UCAwEAAaOCASQwggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyv
cop9NteaHNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFwOi8v
ZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xhc3MlMjAzJTIwQ0El
MjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRp
b25saXN0MEagRKBChkBodHRwOi8vd3d3LmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xh
c3NfM19jYV8yX2V2XzIwMDkuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+
PPoeUSbrh/Yp3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05
nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNFCSuGdXzfX2lX
ANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7naxpeG0ILD5EJt/rDiZE4OJudA
NCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqXKVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVv
w9y4AyHqnxbxLFS1
-----END CERTIFICATE-----

CA Disig Root R2
================
-----BEGIN CERTIFICATE-----
MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNVBAYTAlNLMRMw
EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp
ZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQyMDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sx
EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp
c2lnIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbC
w3OeNcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNHPWSb6Wia
xswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3Ix2ymrdMxp7zo5eFm1tL7
A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbeQTg06ov80egEFGEtQX6sx3dOy1FU+16S
GBsEWmjGycT6txOgmLcRK7fWV8x8nhfRyyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqV
g8NTEQxzHQuyRpDRQjrOQG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa
5Beny912H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJQfYE
koopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUDi/ZnWejBBhG93c+A
Ak9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORsnLMOPReisjQS1n6yqEm70XooQL6i
Fh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNV
HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5u
Qu0wDQYJKoZIhvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM
tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqfGopTpti72TVV
sRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkblvdhuDvEK7Z4bLQjb/D907Je
dR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W8
1k/BfDxujRNt+3vrMNDcTa/F1balTFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjx
mHHEt38OFdAlab0inSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01
utI3gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18DrG5gPcFw0
sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3OszMOl6W8KjptlwlCFtaOg
UxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8xL4ysEr3vQCj8KWefshNPZiTEUxnpHikV
7+ZtsH8tZ/3zbBt1RqPlShfppNcL
-----END CERTIFICATE-----

ACCVRAIZ1
=========
-----BEGIN CERTIFICATE-----
MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UEAwwJQUNDVlJB
SVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQswCQYDVQQGEwJFUzAeFw0xMTA1
MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQBgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwH
UEtJQUNDVjENMAsGA1UECgwEQUNDVjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4IC
DwAwggIKAoICAQCbqau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gM
jmoYHtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWoG2ioPej0
RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpAlHPrzg5XPAOBOp0KoVdD
aaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhrIA8wKFSVf+DuzgpmndFALW4ir50awQUZ
0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDG
WuzndN9wrqODJerWx5eHk6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs7
8yM2x/474KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMOm3WR
5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpacXpkatcnYGMN285J
9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPluUsXQA+xtrn13k/c4LOsOxFwYIRK
Q26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYIKwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRw
Oi8vd3d3LmFjY3YuZXMvZmlsZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEu
Y3J0MB8GCCsGAQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2
VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeTVfZW6oHlNsyM
Hj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIGCCsGAQUFBwICMIIBFB6CARAA
QQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUAcgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBh
AO0AegAgAGQAZQAgAGwAYQAgAEEAQwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUA
YwBuAG8AbABvAGcA7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBj
AHQAcgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAAQwBQAFMA
IABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUAczAwBggrBgEFBQcCARYk
aHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2MuaHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0
dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRtaW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2
MV9kZXIuY3JsMA4GA1UdDwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZI
hvcNAQEFBQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdpD70E
R9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gUJyCpZET/LtZ1qmxN
YEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+mAM/EKXMRNt6GGT6d7hmKG9Ww7Y49
nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepDvV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJ
TS+xJlsndQAJxGJ3KQhfnlmstn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3
sCPdK6jT2iWH7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h
I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szAh1xA2syVP1Xg
Nce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xFd3+YJ5oyXSrjhO7FmGYvliAd
3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2HpPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3p
EfbRD0tVNEYqi4Y7
-----END CERTIFICATE-----

TWCA Global Root CA
===================
-----BEGIN CERTIFICATE-----
MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcxEjAQBgNVBAoT
CVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMTVFdDQSBHbG9iYWwgUm9vdCBD
QTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQK
EwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3Qg
Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2C
nJfF10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz0ALfUPZV
r2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfChMBwqoJimFb3u/Rk28OKR
Q4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbHzIh1HrtsBv+baz4X7GGqcXzGHaL3SekV
tTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1W
KKD+u4ZqyPpcC1jcxkt2yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99
sy2sbZCilaLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYPoA/p
yJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQABDzfuBSO6N+pjWxn
kjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcEqYSjMq+u7msXi7Kx/mzhkIyIqJdI
zshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMC
AQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6g
cFGn90xHNcgL1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn
LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WFH6vPNOw/KP4M
8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNoRI2T9GRwoD2dKAXDOXC4Ynsg
/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlg
lPx4mI88k1HtQJAH32RjJMtOcQWh15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryP
A9gK8kxkRr05YuWW6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3m
i4TWnsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5jwa19hAM8
EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWzaGHQRiapIVJpLesux+t3
zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmyKwbQBM0=
-----END CERTIFICATE-----

TeliaSonera Root CA v1
======================
-----BEGIN CERTIFICATE-----
MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAwNzEUMBIGA1UE
CgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJvb3QgQ0EgdjEwHhcNMDcxMDE4
MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYDVQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwW
VGVsaWFTb25lcmEgUm9vdCBDQSB2MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+
6yfwIaPzaSZVfp3FVRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA
3GV17CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+XZ75Ljo1k
B1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+/jXh7VB7qTCNGdMJjmhn
Xb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxH
oLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkmdtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3
F0fUTPHSiXk+TT2YqGHeOh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJ
oWjiUIMusDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4pgd7
gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fsslESl1MpWtTwEhDc
TwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQarMCpgKIv7NHfirZ1fpoeDVNAgMB
AAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qW
DNXr+nuqF+gTEjANBgkqhkiG9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNm
zqjMDfz1mgbldxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx
0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1TjTQpgcmLNkQfW
pb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBedY2gea+zDTYa4EzAvXUYNR0PV
G6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpc
c41teyWRyu5FrgZLAMzTsVlQ2jqIOylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOT
JsjrDNYmiLbAJM+7vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2
qReWt88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcnHL/EVlP6
Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVxSK236thZiNSQvxaz2ems
WWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY=
-----END CERTIFICATE-----

T-TeleSec GlobalRoot Class 2
============================
-----BEGIN CERTIFICATE-----
MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM
IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU
cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgx
MDAxMTA0MDE0WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz
dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD
ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUdAqSzm1nzHoqvNK38DcLZ
SBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiCFoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/F
vudocP05l03Sx5iRUKrERLMjfTlH6VJi1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx970
2cu+fjOlbpSD8DT6IavqjnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGV
WOHAD3bZwI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGjQjBA
MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/WSA2AHmgoCJrjNXy
YdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhyNsZt+U2e+iKo4YFWz827n+qrkRk4
r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPACuvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNf
vNoBYimipidx5joifsFvHZVwIEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR
3p1m0IvVVGb6g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN
9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlPBSeOE6Fuwg==
-----END CERTIFICATE-----

Atos TrustedRoot 2011
=====================
-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UEAwwVQXRvcyBU
cnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0xMTA3MDcxNDU4
MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsG
A1UECgwEQXRvczELMAkGA1UEBhMCREUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCV
hTuXbyo7LjvPpvMpNb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr
54rMVD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+SZFhyBH+
DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ4J7sVaE3IqKHBAUsR320
HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0Lcp2AMBYHlT8oDv3FdU9T1nSatCQujgKR
z3bFmx5VdJx4IbHwLfELn8LVlhgf8FQieowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7R
l+lwrrw7GWzbITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZ
bNshMBgGA1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB
CwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8jvZfza1zv7v1Apt+h
k6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kPDpFrdRbhIfzYJsdHt6bPWHJxfrrh
TZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pcmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a9
61qn8FYiqTxlVMYVqL2Gns2Dlmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G
3mB/ufNPRJLvKrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed
-----END CERTIFICATE-----

QuoVadis Root CA 1 G3
=====================
-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQELBQAwSDELMAkG
A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv
b3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJN
MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEg
RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakE
PBtVwedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWerNrwU8lm
PNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF34168Xfuw6cwI2H44g4hWf6
Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh4Pw5qlPafX7PGglTvF0FBM+hSo+LdoIN
ofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXpUhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/l
g6AnhF4EwfWQvTA9xO+oabw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV
7qJZjqlc3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/GKubX
9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSthfbZxbGL0eUQMk1f
iyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KOTk0k+17kBL5yG6YnLUlamXrXXAkg
t3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOtzCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZI
hvcNAQELBQADggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC
MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2cDMT/uFPpiN3
GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUNqXsCHKnQO18LwIE6PWThv6ct
Tr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP
+V04ikkwj+3x6xn0dxoxGE1nVGwvb2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh
3jRJjehZrJ3ydlo28hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fa
wx/kNSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNjZgKAvQU6
O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhpq1467HxpvMc7hU6eFbm0
FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFtnh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOV
hMJKzRwuJIczYOXD
-----END CERTIFICATE-----

QuoVadis Root CA 2 G3
=====================
-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQELBQAwSDELMAkG
A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv
b3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJN
MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIg
RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFh
ZiFfqq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMWn4rjyduY
NM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ymc5GQYaYDFCDy54ejiK2t
oIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+O7q414AB+6XrW7PFXmAqMaCvN+ggOp+o
MiwMzAkd056OXbxMmO7FGmh77FOm6RQ1o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+l
V0POKa2Mq1W/xPtbAd0jIaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZo
L1NesNKqIcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz8eQQ
sSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43ehvNURG3YBZwjgQQvD
6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l7ZizlWNof/k19N+IxWA1ksB8aRxh
lRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALGcC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZI
hvcNAQELBQADggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66
AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RCroijQ1h5fq7K
pVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0GaW/ZZGYjeVYg3UQt4XAoeo0L9
x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4nlv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgz
dWqTHBLmYF5vHX/JHyPLhGGfHoJE+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6X
U/IyAgkwo1jwDQHVcsaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+Nw
mNtddbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNgKCLjsZWD
zYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeMHVOyToV7BjjHLPj4sHKN
JeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4WSr2Rz0ZiC3oheGe7IUIarFsNMkd7Egr
O3jtZsSOeWmD3n+M
-----END CERTIFICATE-----

QuoVadis Root CA 3 G3
=====================
-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQELBQAwSDELMAkG
A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv
b3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJN
MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMg
RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286
IxSR/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNuFoM7pmRL
Mon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXRU7Ox7sWTaYI+FrUoRqHe
6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+cra1AdHkrAj80//ogaX3T7mH1urPnMNA3
I4ZyYUUpSFlob3emLoG+B01vr87ERRORFHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3U
VDmrJqMz6nWB2i3ND0/kA9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f7
5li59wzweyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634RylsSqi
Md5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBpVzgeAVuNVejH38DM
dyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0QA4XN8f+MFrXBsj6IbGB/kE+V9/Yt
rQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZI
hvcNAQELBQADggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px
KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnIFUBhynLWcKzS
t/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5WvvoxXqA/4Ti2Tk08HS6IT7SdEQ
TXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFgu/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9Du
DcpmvJRPpq3t/O5jrFc/ZSXPsoaP0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGib
Ih6BJpsQBJFxwAYf3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmD
hPbl8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+DhcI00iX
0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HNPlopNLk9hM6xZdRZkZFW
dSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ywaZWWDYWGWVjUTR939+J399roD1B0y2
PpxxVJkES/1Y+Zj0
-----END CERTIFICATE-----

DigiCert Assured ID Root G2
===========================
-----BEGIN CERTIFICATE-----
MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw
IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgw
MTE1MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL
ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIw
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSAn61UQbVH
35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4HteccbiJVMWWXvdMX0h5i89vq
bFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9HpEgjAALAcKxHad3A2m67OeYfcgnDmCXRw
VWmvo2ifv922ebPynXApVfSr/5Vh88lAbx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OP
YLfykqGxvYmJHzDNw6YuYjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+Rn
lTGNAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTO
w0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPIQW5pJ6d1Ee88hjZv
0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I0jJmwYrA8y8678Dj1JGG0VDjA9tz
d29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4GnilmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAW
hsI6yLETcDbYz+70CjTVW0z9B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0M
jomZmWzwPDCvON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo
IhNzbM8m9Yop5w==
-----END CERTIFICATE-----

DigiCert Assured ID Root G3
===========================
-----BEGIN CERTIFICATE-----
MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV
UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYD
VQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1
MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQ
BgcqhkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJfZn4f5dwb
RXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17QRSAPWXYQ1qAk8C3eNvJs
KTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgF
UaFNN6KDec6NHSrkhDAKBggqhkjOPQQDAwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5Fy
YZ5eEJJZVrmDxxDnOOlYJjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy
1vUhZscv6pZjamVFkpUBtA==
-----END CERTIFICATE-----

DigiCert Global Root G2
=======================
-----BEGIN CERTIFICATE-----
MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw
HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUx
MjAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI2/Ou8jqJ
kTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx1x7e/dfgy5SDN67sH0NO
3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQq2EGnI/yuum06ZIya7XzV+hdG82MHauV
BJVJ8zUtluNJbd134/tJS7SsVQepj5WztCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyM
UNGPHgm+F6HmIcr9g+UQvIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQAB
o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV5uNu
5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY1Yl9PMWLSn/pvtsr
F9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4NeF22d+mQrvHRAiGfzZ0JFrabA0U
WTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NGFdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBH
QRFXGU7Aj64GxJUTFy8bJZ918rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/
iyK5S9kJRaTepLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
MrY=
-----END CERTIFICATE-----

DigiCert Global Root G3
=======================
-----BEGIN CERTIFICATE-----
MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQswCQYDVQQGEwJV
UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYD
VQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAw
MDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5k
aWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0C
AQYFK4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FGfp4tn+6O
YwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPOZ9wj/wMco+I+o0IwQDAP
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNp
Yim8S8YwCgYIKoZIzj0EAwMDaAAwZQIxAK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y
3maTD/HMsQmP3Wyr+mt/oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34
VOKa5Vt8sycX
-----END CERTIFICATE-----

DigiCert Trusted Root G4
========================
-----BEGIN CERTIFICATE-----
MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEw
HwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1
MTIwMDAwWjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0G
CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3yithZwuEp
pz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9o
k3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7Fsa
vOvJz82sNEBfsXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGY
QJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6
MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCiEhtm
mnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7
f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFH
dL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8
oR7FwI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud
DwEB/wQEAwIBhjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD
ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2SV1EY+CtnJYY
ZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd+SeuMIW59mdNOj6PWTkiU0Tr
yF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWcfFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy
7zBZLq7gcfJW5GqXb5JQbZaNaHqasjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iah
ixTXTBmyUEFxPT9NcCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN
5r5N0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie4u1Ki7wb
/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mIr/OSmbaz5mEP0oUA51Aa
5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tK
G48BtieVU+i2iW1bvGjUI+iLUaJW+fCmgKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP
82Z+
-----END CERTIFICATE-----

COMODO RSA Certification Authority
==================================
-----BEGIN CERTIFICATE-----
MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCBhTELMAkGA1UE
BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG
A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkwHhcNMTAwMTE5MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMC
R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE
ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBB
dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR6FSS0gpWsawNJN3Fz0Rn
dJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8Xpz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZ
FGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+
5eNu/Nio5JIk2kNrYrhV/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pG
x8cgoLEfZd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z+pUX
2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7wqP/0uK3pN/u6uPQL
OvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZahSL0896+1DSJMwBGB7FY79tOi4lu3
sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVICu9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+C
GCe01a60y1Dma/RMhnEw6abfFobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5
WdYgGq/yapiqcrxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E
FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w
DQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvlwFTPoCWOAvn9sKIN9SCYPBMt
rFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+
nq6PK7o9mfjYcwlYRm6mnPTXJ9OV2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSg
tZx8jb8uk2IntznaFxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwW
sRqZCuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiKboHGhfKp
pC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmckejkk9u+UJueBPSZI9FoJA
zMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yLS0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHq
ZJx64SIDqZxubw5lT2yHh17zbqD5daWbQOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk52
7RH89elWsn2/x20Kk4yl0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7I
LaZRfyHBNVOFBkpdn627G190
-----END CERTIFICATE-----

USERTrust RSA Certification Authority
=====================================
-----BEGIN CERTIFICATE-----
MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCBiDELMAkGA1UE
BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK
ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UE
BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK
ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCAEmUXNg7D2wiz
0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2j
Y0K2dvKpOyuR+OJv0OwWIJAJPuLodMkYtJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFn
RghRy4YUVD+8M/5+bJz/Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O
+T23LLb2VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT79uq
/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6c0Plfg6lZrEpfDKE
Y1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmTYo61Zs8liM2EuLE/pDkP2QKe6xJM
lXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97lc6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8
yexDJtC/QV9AqURE9JnnV4eeUB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+
eLf8ZxXhyVeEHg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd
BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF
MAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPFUp/L+M+ZBn8b2kMVn54CVVeW
FPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KOVWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ
7l8wXEskEVX/JJpuXior7gtNn3/3ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQ
Eg9zKC7F4iRO/Fjs8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM
8WcRiQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYzeSf7dNXGi
FSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZXHlKYC6SQK5MNyosycdi
yA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9c
J2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRBVXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGw
sAvgnEzDHNb842m1R0aBL6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gx
Q+6IHdfGjjxDah2nGN59PRbxYvnKkKj9
-----END CERTIFICATE-----

USERTrust ECC Certification Authority
=====================================
-----BEGIN CERTIFICATE-----
MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDELMAkGA1UEBhMC
VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU
aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMC
VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU
aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqfloI+d61SRvU8Za2EurxtW2
0eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinngo4N+LZfQYcTxmdwlkWOrfzCjtHDix6Ez
nPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNV
HQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBB
HU6+4WMBzzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbWRNZu
9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=
-----END CERTIFICATE-----

GlobalSign ECC Root CA - R5
===========================
-----BEGIN CERTIFICATE-----
MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEkMCIGA1UECxMb
R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD
EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb
R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD
EwpHbG9iYWxTaWduMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6
SFkc8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8kehOvRnkmS
h5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd
BgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYIKoZIzj0EAwMDaAAwZQIxAOVpEslu28Yx
uglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7
yFz9SO8NdCKoCOJuxUnOxwy8p2Fp8fc74SrL+SvzZpA3
-----END CERTIFICATE-----

IdenTrust Commercial Root CA 1
==============================
-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBKMQswCQYDVQQG
EwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBS
b290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQwMTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzES
MBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENB
IDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ld
hNlT3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU+ehcCuz/
mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gpS0l4PJNgiCL8mdo2yMKi
1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1bVoE/c40yiTcdCMbXTMTEl3EASX2MN0C
XZ/g1Ue9tOsbobtJSdifWwLziuQkkORiT0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl
3ZBWzvurpWCdxJ35UrCLvYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzy
NeVJSQjKVsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZKdHzV
WYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHTc+XvvqDtMwt0viAg
xGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hvl7yTmvmcEpB4eoCHFddydJxVdHix
uuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5NiGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC
AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZI
hvcNAQELBQADggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH
6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwtLRvM7Kqas6pg
ghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93nAbowacYXVKV7cndJZ5t+qnt
ozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmV
YjzlVYA211QC//G5Xc7UI2/YRYRKW2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUX
feu+h1sXIFRRk0pTAwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/ro
kTLql1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG4iZZRHUe
2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZmUlO+KWA2yUPHGNiiskz
Z2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7R
cGzM7vRX+Bi6hG6H
-----END CERTIFICATE-----

IdenTrust Public Sector Root CA 1
=================================
-----BEGIN CERTIFICATE-----
MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQG
EwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3Rv
ciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcNMzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJV
UzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBS
b290IENBIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTy
P4o7ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGyRBb06tD6
Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlSbdsHyo+1W/CD80/HLaXI
rcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF/YTLNiCBWS2ab21ISGHKTN9T0a9SvESf
qy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoS
mJxZZoY+rfGwyj4GD3vwEUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFn
ol57plzy9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9VGxyh
LrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ2fjXctscvG29ZV/v
iDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsVWaFHVCkugyhfHMKiq3IXAAaOReyL
4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gDW/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8B
Af8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMw
DQYJKoZIhvcNAQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj
t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHVDRDtfULAj+7A
mgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9TaDKQGXSc3z1i9kKlT/YPyNt
GtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8GlwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFt
m6/n6J91eEyrRjuazr8FGF1NFTwWmhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMx
NRF4eKLg6TCMf4DfWN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4
Mhn5+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJtshquDDI
ajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhAGaQdp/lLQzfcaFpPz+vC
ZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ
3Wl9af0AVqW3rLatt8o+Ae+c
-----END CERTIFICATE-----

Entrust Root Certification Authority - G2
=========================================
-----BEGIN CERTIFICATE-----
MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMCVVMxFjAUBgNV
BAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVy
bXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ug
b25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIw
HhcNMDkwNzA3MTcyNTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoT
DUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMx
OTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25s
eTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP
/vaCeb9zYQYKpSfYs1/TRU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXz
HHfV1IWNcCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hWwcKU
s/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1U1+cPvQXLOZprE4y
TGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0jaWvYkxN4FisZDQSA/i2jZRjJKRx
AgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ6
0B7vfec7aVHUbI2fkBJmqzANBgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5Z
iXMRrEPR9RP/jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ
Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v1fN2D807iDgi
nWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4RnAuknZoh8/CbCzB428Hch0P+
vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmHVHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xO
e4pIb4tF9g==
-----END CERTIFICATE-----

Entrust Root Certification Authority - EC1
==========================================
-----BEGIN CERTIFICATE-----
MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkGA1UEBhMCVVMx
FjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVn
YWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXpl
ZCB1c2Ugb25seTEzMDEGA1UEAxMqRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5
IC0gRUMxMB4XDTEyMTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYw
FAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2Fs
LXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQg
dXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt
IEVDMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHy
AsWfoPZb1YsGGYZPUxBtByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef
9eNi1KlHBz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
FLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVCR98crlOZF7ZvHH3h
vxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nXhTcGtXsI/esni0qU+eH6p44mCOh8
kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G
-----END CERTIFICATE-----

CFCA EV ROOT
============
-----BEGIN CERTIFICATE-----
MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJDTjEwMC4GA1UE
CgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNB
IEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkxMjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEw
MC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQD
DAxDRkNBIEVWIFJPT1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnV
BU03sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpLTIpTUnrD
7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5/ZOkVIBMUtRSqy5J35DN
uF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp7hZZLDRJGqgG16iI0gNyejLi6mhNbiyW
ZXvKWfry4t3uMCz7zEasxGPrb382KzRzEpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7
xzbh72fROdOXW3NiGUgthxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9f
py25IGvPa931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqotaK8K
gWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNgTnYGmE69g60dWIol
hdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfVPKPtl8MeNPo4+QgO48BdK4PRVmrJ
tqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hvcWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAf
BgNVHSMEGDAWgBTj/i39KNALtbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB
/wQEAwIBBjAdBgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB
ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObTej/tUxPQ4i9q
ecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdLjOztUmCypAbqTuv0axn96/Ua
4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBSESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sG
E5uPhnEFtC+NiWYzKXZUmhH4J/qyP5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfX
BDrDMlI1Dlb4pd19xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjn
aH9dCi77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN5mydLIhy
PDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe/v5WOaHIz16eGWRGENoX
kbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+ZAAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3C
ekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su
-----END CERTIFICATE-----

OISTE WISeKey Global Root GB CA
===============================
-----BEGIN CERTIFICATE-----
MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQG
EwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl
ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAw
MzJaFw0zOTEyMDExNTEwMzFaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYD
VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEds
b2JhbCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3HEokKtaX
scriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGxWuR51jIjK+FTzJlFXHtP
rby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk
9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNku7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4o
Qnc/nSMbsrY9gBQHTC5P99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvg
GUpuuy9rM2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB
/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZI
hvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrghcViXfa43FK8+5/ea4n32cZiZBKpD
dHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0
VQreUGdNZtGn//3ZwLWoo4rOZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEui
HZeeevJuQHHfaPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic
Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM=
-----END CERTIFICATE-----

SZAFIR ROOT CA2
===============
-----BEGIN CERTIFICATE-----
MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQELBQAwUTELMAkG
A1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6ZW5pb3dhIFMuQS4xGDAWBgNV
BAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkwNzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJ
BgNVBAYTAlBMMSgwJgYDVQQKDB9LcmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYD
VQQDDA9TWkFGSVIgUk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5Q
qEvNQLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT3PSQ1hNK
DJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw3gAeqDRHu5rr/gsUvTaE
2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr63fE9biCloBK0TXC5ztdyO4mTp4CEHCdJ
ckm1/zuVnsHMyAHs6A6KCpbns6aH5db5BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwi
ieDhZNRnvDF5YTy7ykHNXGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P
AQH/BAQDAgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsFAAOC
AQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw8PRBEew/R40/cof5
O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOGnXkZ7/e7DDWQw4rtTw/1zBLZpD67
oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCPoky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul
4+vJhaAlIDf7js4MNIThPIGyd05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6
+/NNIxuZMzSgLvWpCz/UXeHPhJ/iGcJfitYgHuNztw==
-----END CERTIFICATE-----

Certum Trusted Network CA 2
===========================
-----BEGIN CERTIFICATE-----
MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCBgDELMAkGA1UE
BhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAlBgNVBAsTHkNlcnR1
bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29y
ayBDQSAyMCIYDzIwMTExMDA2MDgzOTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQ
TDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENl
cnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENB
IDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWADGSdhhuWZGc/IjoedQF9
7/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+o
CgCXhVqqndwpyeI1B+twTUrWwbNWuKFBOJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40b
Rr5HMNUuctHFY9rnY3lEfktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2p
uTRZCr+ESv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1mo130
GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02isx7QBlrd9pPPV3WZ
9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOWOZV7bIBaTxNyxtd9KXpEulKkKtVB
Rgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgezTv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pye
hizKV/Ma5ciSixqClnrDvFASadgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vM
BhBgu4M1t15n3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZI
hvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQF/xlhMcQSZDe28cmk4gmb3DW
Al45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTfCVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuA
L55MYIR4PSFk1vtBHxgP58l1cb29XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMo
clm2q8KMZiYcdywmdjWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tM
pkT/WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jbAoJnwTnb
w3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksqP/ujmv5zMnHCnsZy4Ypo
J/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Kob7a6bINDd82Kkhehnlt4Fj1F4jNy3eFm
ypnTycUm/Q1oBEauttmbjL4ZvrHG8hnjXALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLX
is7VmFxWlgPF7ncGNf/P5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7
zAYspsbiDrW5viSP
-----END CERTIFICATE-----

Hellenic Academic and Research Institutions RootCA 2015
=======================================================
-----BEGIN CERTIFICATE-----
MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcT
BkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0
aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl
YXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAx
MTIxWjCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMg
QWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNV
BAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIw
MTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDC+Kk/G4n8PDwEXT2QNrCROnk8Zlrv
bTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+eh
iGsxr/CL0BgzuNtFajT0AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+
6PAQZe104S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06CojXd
FPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV9Cz82XBST3i4vTwr
i5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrDgfgXy5I2XdGj2HUb4Ysn6npIQf1F
GQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2
fu/Z8VFRfS0myGlZYeCsargqNhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9mu
iNX6hME6wGkoLfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc
Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVdctA4GGqd83EkVAswDQYJKoZI
hvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0IXtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+
D1hYc2Ryx+hFjtyp8iY/xnmMsVMIM4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrM
d/K4kPFox/la/vot9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+y
d+2VZ5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/eaj8GsGsVn
82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnhX9izjFk0WaSrT2y7Hxjb
davYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQl033DlZdwJVqwjbDG2jJ9SrcR5q+ss7F
Jej6A7na+RZukYT1HCjI/CbM1xyQVqdfbzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVt
J94Cj8rDtSvK6evIIVM4pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGa
JI7ZjnHKe7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0vm9q
p/UsQu0yrbYhnr68
-----END CERTIFICATE-----

Hellenic Academic and Research Institutions ECC RootCA 2015
===========================================================
-----BEGIN CERTIFICATE-----
MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0
aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u
cyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj
aCBJbnN0aXR1dGlvbnMgRUNDIFJvb3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEw
MzcxMlowgaoxCzAJBgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmlj
IEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUQwQgYD
VQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIEVDQyBSb290
Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKgQehLgoRc4vgxEZmGZE4JJS+dQS8KrjVP
dJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJajq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoK
Vlp8aQuqgAkkbH7BRqNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O
BBYEFLQiC4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaeplSTA
GiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7SofTUwJCA3sS61kFyjn
dc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR
-----END CERTIFICATE-----

ISRG Root X1
============
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAwTzELMAkGA1UE
BhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQD
EwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQG
EwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMT
DElTUkcgUm9vdCBYMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54r
Vygch77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+0TM8ukj1
3Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6UA5/TR5d8mUgjU+g4rk8K
b4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sWT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCN
Aymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyHB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ
4Q7e2RCOFvu396j3x+UCB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf
1b0SHzUvKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWnOlFu
hjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTnjh8BCNAw1FtxNrQH
usEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbwqHyGO0aoSCqI3Haadr8faqU9GY/r
OPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CIrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4G
A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY
9umbbjANBgkqhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ3BebYhtF8GaV
0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KKNFtY2PwByVS5uCbMiogziUwt
hDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJw
TdwJx4nLCgdNbOhdjsnvzqvHu7UrTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nx
e5AW0wdeRlN8NwdCjNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZA
JzVcoyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq4RgqsahD
YVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPAmRGunUHBcnWEvgJBQl9n
JEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57demyPxgcYxn/eR44/KJ4EBs+lVDR3veyJ
m+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----

AC RAIZ FNMT-RCM
================
-----BEGIN CERTIFICATE-----
MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsxCzAJBgNVBAYT
AkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTAeFw0wODEw
MjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJD
TTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
ggIBALpxgHpMhm5/yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcf
qQgfBBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAzWHFctPVr
btQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxFtBDXaEAUwED653cXeuYL
j2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z374jNUUeAlz+taibmSXaXvMiwzn15Cou
08YfxGyqxRxqAQVKL9LFwag0Jl1mpdICIfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mw
WsXmo8RZZUc1g16p6DULmbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnT
tOmlcYF7wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peSMKGJ
47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2ZSysV4999AeU14EC
ll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMetUqIJ5G+GR4of6ygnXYMgrwTJbFaa
i0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE
FPd9xf3E6Jobd2Sn9R2gzL+HYJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1o
dHRwOi8vd3d3LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD
nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1RXxlDPiyN8+s
D8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYMLVN0V2Ue1bLdI4E7pWYjJ2cJ
j+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrT
Qfv6MooqtyuGC2mDOL7Nii4LcK2NJpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW
+YJF1DngoABd15jmfZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7
Ixjp6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp1txyM/1d
8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B9kiABdcPUXmsEKvU7ANm
5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wokRqEIr9baRRmW1FMdW4R58MD3R++Lj8UG
rp1MYp3/RgT408m2ECVAdf4WqslKYIYvuu8wd+RU4riEmViAqhOLUTpPSPaLtrM=
-----END CERTIFICATE-----

Amazon Root CA 1
================
-----BEGIN CERTIFICATE-----
MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsFADA5MQswCQYD
VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAxMB4XDTE1
MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv
bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBALJ4gHHKeNXjca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgH
FzZM9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qwIFAGbHrQ
gLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6VOujw5H5SNz/0egwLX0t
dHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L93FcXmn/6pUCyziKrlA4b9v7LWIbxcce
VOF34GfID5yHI9Y/QCB/IIDEgEw+OyQmjgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB
/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3
DQEBCwUAA4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDIU5PM
CCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUsN+gDS63pYaACbvXy
8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vvo/ufQJVtMVT8QtPHRh8jrdkPSHCa
2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2
xJNDd2ZhwLnoQdeXeGADbkpyrqXRfboQnoZsG4q5WTP468SQvvG5
-----END CERTIFICATE-----

Amazon Root CA 2
================
-----BEGIN CERTIFICATE-----
MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwFADA5MQswCQYD
VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAyMB4XDTE1
MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv
bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
ggIBAK2Wny2cSkxKgXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4
kHbZW0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg1dKmSYXp
N+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K8nu+NQWpEjTj82R0Yiw9
AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvd
fLC6HM783k81ds8P+HgfajZRRidhW+mez/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAEx
kv8LV/SasrlX6avvDXbR8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSS
btqDT6ZjmUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz7Mt0
Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6+XUyo05f7O0oYtlN
c/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI0u1ufm8/0i2BWSlmy5A5lREedCf+
3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSw
DPBMMPQFWAJI/TPlUq9LhONmUjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oA
A7CXDpO8Wqj2LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY
+gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kSk5Nrp+gvU5LE
YFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl7uxMMne0nxrpS10gxdr9HIcW
xkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygmbtmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQ
gj9sAq+uEjonljYE1x2igGOpm/HlurR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbW
aQbLU8uz/mtBzUF+fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoV
Yh63n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE76KlXIx3
KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H9jVlpNMKVv/1F2Rs76gi
JUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT4PsJYGw=
-----END CERTIFICATE-----

Amazon Root CA 3
================
-----BEGIN CERTIFICATE-----
MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5MQswCQYDVQQG
EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAzMB4XDTE1MDUy
NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ
MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZB
f8ANm+gBG1bG8lKlui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjr
Zt6jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSrttvXBp43
rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkrBqWTrBqYaGFy+uGh0Psc
eGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteMYyRIHN8wfdVoOw==
-----END CERTIFICATE-----

Amazon Root CA 4
================
-----BEGIN CERTIFICATE-----
MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5MQswCQYDVQQG
EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSA0MB4XDTE1MDUy
NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ
MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN
/sGKe0uoe0ZLY7Bi9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri
83BkM6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
HQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WBMAoGCCqGSM49BAMDA2gA
MGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlwCkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1
AE47xDqUEpHJWEadIRNyp4iciuRMStuW1KyLa2tJElMzrdfkviT8tQp21KW8EA==
-----END CERTIFICATE-----

TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1
=============================================
-----BEGIN CERTIFICATE-----
MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIxGDAWBgNVBAcT
D0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxpbXNlbCB2ZSBUZWtub2xvamlr
IEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0wKwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24g
TWVya2V6aSAtIEthbXUgU00xNjA0BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRp
ZmlrYXNpIC0gU3VydW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYD
VQQGEwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXllIEJpbGlt
c2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklUQUsxLTArBgNVBAsTJEth
bXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBTTTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11
IFNNIFNTTCBLb2sgU2VydGlmaWthc2kgLSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAr3UwM6q7a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y8
6Ij5iySrLqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INrN3wc
wv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2XYacQuFWQfw4tJzh0
3+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/iSIzL+aFCr2lqBs23tPcLG07xxO9
WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4fAJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQU
ZT/HiobGPN08VFw1+DrtUgxHV8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJ
KoZIhvcNAQELBQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh
AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPfIPP54+M638yc
lNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4lzwDGrpDxpa5RXI4s6ehlj2R
e37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0j
q5Rm+K37DwhuJi1/FwcJsoz7UMCflo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM=
-----END CERTIFICATE-----

GDCA TrustAUTH R5 ROOT
======================
-----BEGIN CERTIFICATE-----
MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMCQ04xMjAw
BgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8wHQYDVQQD
DBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVow
YjELMAkGA1UEBhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ
IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0B
AQEFAAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJjDp6L3TQs
AlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBjTnnEt1u9ol2x8kECK62p
OqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+uKU49tm7srsHwJ5uu4/Ts765/94Y9cnrr
pftZTqfrlYwiOXnhLQiPzLyRuEH3FMEjqcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ
9Cy5WmYqsBebnh52nUpmMUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQ
xXABZG12ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloPzgsM
R6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3GkL30SgLdTMEZeS1SZ
D2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeCjGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4
oR24qoAATILnsn8JuLwwoC8N9VKejveSswoAHQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx
9hoh49pwBiFYFIeFd3mqgnkCAwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlR
MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg
p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZmDRd9FBUb1Ov9
H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5COmSdI31R9KrO9b7eGZONn35
6ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ryL3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd
+PwyvzeG5LuOmCd+uh8W4XAR8gPfJWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQ
HtZa37dG/OaG+svgIHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBD
F8Io2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV09tL7ECQ
8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQXR4EzzffHqhmsYzmIGrv
/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrqT8p+ck0LcIymSLumoRT2+1hEmRSuqguT
aaApJUqlyyvdimYHFngVV3Eb7PVHhPOeMTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g==
-----END CERTIFICATE-----

SSL.com Root Certification Authority RSA
========================================
-----BEGIN CERTIFICATE-----
MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxDjAM
BgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24x
MTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYw
MjEyMTczOTM5WhcNNDEwMjEyMTczOTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx
EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NM
LmNvbSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcNAQEBBQAD
ggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2RxFdHaxh3a3by/ZPkPQ/C
Fp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aXqhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8
P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcCC52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/ge
oeOy3ZExqysdBP+lSgQ36YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkp
k8zruFvh/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrFYD3Z
fBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93EJNyAKoFBbZQ+yODJ
gUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVcUS4cK38acijnALXRdMbX5J+tB5O2
UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi8
1xtZPCvM8hnIk2snYxnP/Okm+Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4s
bE6x/c+cCbqiM+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV
HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4GA1UdDwEB/wQE
AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGVcpNxJK1ok1iOMq8bs3AD/CUr
dIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBcHadm47GUBwwyOabqG7B52B2ccETjit3E+ZUf
ijhDPwGFpUenPUayvOUiaPd7nNgsPgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAsl
u1OJD7OAUN5F7kR/q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjq
erQ0cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jra6x+3uxj
MxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90IH37hVZkLId6Tngr75qNJ
vTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/YK9f1JmzJBjSWFupwWRoyeXkLtoh/D1JI
Pb9s2KJELtFOt3JY04kTlf5Eq/jXixtunLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406y
wKBjYZC6VWg3dGq2ktufoYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NI
WuuA8ShYIc2wBlX7Jz9TkHCpBB5XJ7k=
-----END CERTIFICATE-----

SSL.com Root Certification Authority ECC
========================================
-----BEGIN CERTIFICATE-----
MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMCVVMxDjAMBgNV
BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xMTAv
BgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEy
MTgxNDAzWhcNNDEwMjEyMTgxNDAzWjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAO
BgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv
bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuBBAAiA2IA
BEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI7Z4INcgn64mMU1jrYor+
8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPgCemB+vNH06NjMGEwHQYDVR0OBBYEFILR
hXMw5zUE044CkvvlpNHEIejNMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTT
jgKS++Wk0cQh6M0wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCW
e+0F+S8Tkdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+gA0z
5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl
-----END CERTIFICATE-----

SSL.com EV Root Certification Authority RSA R2
==============================================
-----BEGIN CERTIFICATE-----
MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNVBAYTAlVTMQ4w
DAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9u
MTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy
MB4XDTE3MDUzMTE4MTQzN1oXDTQyMDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQI
DAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYD
VQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMIICIjAN
BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvqM0fNTPl9fb69LT3w23jh
hqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssufOePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7w
cXHswxzpY6IXFJ3vG2fThVUCAtZJycxa4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTO
Zw+oz12WGQvE43LrrdF9HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+
B6KjBSYRaZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcAb9Zh
CBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQGp8hLH94t2S42Oim
9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQVPWKchjgGAGYS5Fl2WlPAApiiECto
RHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMOpgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+Slm
JuwgUHfbSguPvuUCYHBBXtSuUDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48
+qvWBkofZ6aYMBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV
HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa49QaAJadz20Zp
qJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBWs47LCp1Jjr+kxJG7ZhcFUZh1
++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nx
Y/hoLVUE0fKNsKTPvDxeH3jnpaAgcLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2G
guDKBAdRUNf/ktUM79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDz
OFSz/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXtll9ldDz7
CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEmKf7GUmG6sXP/wwyc5Wxq
lD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKKQbNmC1r7fSOl8hqw/96bg5Qu0T/fkreR
rwU7ZcegbLHNYhLDkBvjJc40vG93drEQw/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1
hlMYegouCRw2n5H9gooiS9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX
9hwJ1C07mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w==
-----END CERTIFICATE-----

SSL.com EV Root Certification Authority ECC
===========================================
-----BEGIN CERTIFICATE-----
MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMCVVMxDjAMBgNV
BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xNDAy
BgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYw
MjEyMTgxNTIzWhcNNDEwMjEyMTgxNTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx
EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NM
LmNvbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB
BAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMAVIbc/R/fALhBYlzccBYy
3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1KthkuWnBaBu2+8KGwytAJKaNjMGEwHQYDVR0O
BBYEFFvKXuXe0oGqzagtZFG22XKbl+ZPMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe
5d7SgarNqC1kUbbZcpuX5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJ
N+vp1RPZytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZgh5Mm
m7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg==
-----END CERTIFICATE-----

GlobalSign Root CA - R6
=======================
-----BEGIN CERTIFICATE-----
MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEgMB4GA1UECxMX
R2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds
b2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQxMjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9i
YWxTaWduIFJvb3QgQ0EgLSBSNjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFs
U2lnbjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQss
grRIxutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1kZguSgMpE
3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxDaNc9PIrFsmbVkJq3MQbF
vuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJwLnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqM
PKq0pPbzlUoSB239jLKJz9CgYXfIWHSw1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+
azayOeSsJDa38O+2HBNXk7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05O
WgtH8wY2SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/hbguy
CLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4nWUx2OVvq+aWh2IMP
0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpYrZxCRXluDocZXFSxZba/jJvcE+kN
b7gu3GduyYsRtYQUigAZcIN5kZeR1BonvzceMgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQE
AwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNV
HSMEGDAWgBSubAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN
nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGtIxg93eFyRJa0
lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr6155wsTLxDKZmOMNOsIeDjHfrY
BzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLjvUYAGm0CuiVdjaExUd1URhxN25mW7xocBFym
Fe944Hn+Xds+qkxV/ZoVqW/hpvvfcDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr
3TsTjxKM4kEaSHpzoHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB1
0jZpnOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfspA9MRf/T
uTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+vJJUEeKgDu+6B5dpffItK
oZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+t
JDfLRVpOoERIyNiwmcUVhAn21klJwGW45hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA=
-----END CERTIFICATE-----

OISTE WISeKey Global Root GC CA
===============================
-----BEGIN CERTIFICATE-----
MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQswCQYDVQQGEwJD
SDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEo
MCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRa
Fw00MjA1MDkwOTU4MzNaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQL
ExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh
bCBSb290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4nieUqjFqdr
VCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4Wp2OQ0jnUsYd4XxiWD1Ab
NTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd
BgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7TrYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0E
AwMDaAAwZQIwJsdpW9zV57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtk
AjEA2zQgMgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9
-----END CERTIFICATE-----

UCA Global G2 Root
==================
-----BEGIN CERTIFICATE-----
MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9MQswCQYDVQQG
EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBHbG9iYWwgRzIgUm9vdDAeFw0x
NjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0xCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlU
cnVzdDEbMBkGA1UEAwwSVUNBIEdsb2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
MIICCgKCAgEAxeYrb3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmT
oni9kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzmVHqUwCoV
8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/RVogvGjqNO7uCEeBHANBS
h6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDcC/Vkw85DvG1xudLeJ1uK6NjGruFZfc8o
LTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIjtm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/
R+zvWr9LesGtOxdQXGLYD0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBe
KW4bHAyvj5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6DlNaBa
4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6iIis7nCs+dwp4wwc
OxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznPO6Q0ibd5Ei9Hxeepl2n8pndntd97
8XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O
BBYEFIHEjMz15DD/pQwIX4wVZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo
5sOASD0Ee/ojL3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5
1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl1qnN3e92mI0A
Ds0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oUb3n09tDh05S60FdRvScFDcH9
yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LVPtateJLbXDzz2K36uGt/xDYotgIVilQsnLAX
c47QN6MUPJiVAAwpBVueSUmxX8fjy88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHo
jhJi6IjMtX9Gl8CbEGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZk
bxqgDMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI+Vg7RE+x
ygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGyYiGqhkCyLmTTX8jjfhFn
RR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bXUB+K+wb1whnw0A==
-----END CERTIFICATE-----

UCA Extended Validation Root
============================
-----BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBHMQswCQYDVQQG
EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9u
IFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMxMDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8G
A1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrs
iWogD4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvSsPGP2KxF
Rv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aopO2z6+I9tTcg1367r3CTu
eUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dksHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR
59mzLC52LqGj3n5qiAno8geK+LLNEOfic0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH
0mK1lTnj8/FtDw5lhIpjVMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KR
el7sFsLzKuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/TuDv
B0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41Gsx2VYVdWf6/wFlth
WG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs1+lvK9JKBZP8nm9rZ/+I8U6laUpS
NwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQDfwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS
3H5aBZ8eNJr34RQwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQEL
BQADggIBADaNl8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR
ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQVBcZEhrxH9cM
aVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5c6sq1WnIeJEmMX3ixzDx/BR4
dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb
+7lsq+KePRXBOy5nAliRn+/4Qh8st2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOW
F3sGPjLtx7dCvHaj2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwi
GpWOvpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2CxR9GUeOc
GMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmxcmtpzyKEC2IPrNkZAJSi
djzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbMfjKaiJUINlK73nZfdklJrX+9ZSCyycEr
dhh2n1ax
-----END CERTIFICATE-----

Certigna Root CA
================
-----BEGIN CERTIFICATE-----
MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UE
BhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAwMiA0ODE0NjMwODEwMDAzNjEZ
MBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0xMzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjda
MFoxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYz
MDgxMDAwMzYxGTAXBgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4IC
DwAwggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sOty3tRQgX
stmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9MCiBtnyN6tMbaLOQdLNyz
KNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPuI9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8
JXrJhFwLrN1CTivngqIkicuQstDuI7pmTLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16
XdG+RCYyKfHx9WzMfgIhC59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq
4NYKpkDfePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3YzIoej
wpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWTCo/1VTp2lc5ZmIoJ
lXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1kJWumIWmbat10TWuXekG9qxf5kBdI
jzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp/
/TBt2dzhauH8XwIDAQABo4IBGjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw
HQYDVR0OBBYEFBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of
1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczovL3d3d3cuY2Vy
dGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilodHRwOi8vY3JsLmNlcnRpZ25h
LmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYraHR0cDovL2NybC5kaGlteW90aXMuY29tL2Nl
cnRpZ25hcm9vdGNhLmNybDANBgkqhkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOIt
OoldaDgvUSILSo3L6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxP
TGRGHVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH60BGM+RFq
7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncBlA2c5uk5jR+mUYyZDDl3
4bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdio2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd
8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS
6Cvu5zHbugRqh5jnxV/vfaci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaY
tlu3zM63Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayhjWZS
aX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw3kAP+HwV96LOPNde
E4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0=
-----END CERTIFICATE-----

emSign Root CA - G1
===================
-----BEGIN CERTIFICATE-----
MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYDVQQGEwJJTjET
MBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl
ZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBHMTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgx
ODMwMDBaMGcxCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVk
aHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQzf2N4aLTN
LnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO8oG0x5ZOrRkVUkr+PHB1
cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aqd7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHW
DV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhMtTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ
6DqS0hdW5TUaQBw+jSztOd9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrH
hQIDAQABo0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQDAgEG
MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31xPaOfG1vR2vjTnGs2
vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjMwiI/aTvFthUvozXGaCocV685743Q
NcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6dGNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q
+Mri/Tm3R7nrft8EI6/6nAYH6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeih
U80Bv2noWgbyRQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx
iN66zB+Afko=
-----END CERTIFICATE-----

emSign ECC Root CA - G3
=======================
-----BEGIN CERTIFICATE-----
MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQGEwJJTjETMBEG
A1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEg
MB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4
MTgzMDAwWjBrMQswCQYDVQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11
ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g
RzMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0WXTsuwYc
58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xySfvalY8L1X44uT6EYGQIr
MgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuBzhccLikenEhjQjAOBgNVHQ8BAf8EBAMC
AQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+D
CBeQyh+KTOgNG3qxrdWBCUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7
jHvrZQnD+JbNR6iC8hZVdyR+EhCVBCyj
-----END CERTIFICATE-----

emSign Root CA - C1
===================
-----BEGIN CERTIFICATE-----
MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkGA1UEBhMCVVMx
EzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNp
Z24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UE
BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQD
ExNlbVNpZ24gUm9vdCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+up
ufGZBczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZHdPIWoU/
Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH3DspVpNqs8FqOp099cGX
OFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvHGPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4V
I5b2P/AgNBbeCsbEBEV5f6f9vtKppa+cxSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleooms
lMuoaJuvimUnzYnu3Yy1aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+
XJGFehiqTbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQAD
ggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87/kOXSTKZEhVb3xEp
/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4kqNPEjE2NuLe/gDEo2APJ62gsIq1
NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrGYQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9
wC68AivTxEDkigcxHpvOJpkT+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQ
BmIMMMAVSKeoWXzhriKi4gp6D/piq1JM4fHfyr6DDUI=
-----END CERTIFICATE-----

emSign ECC Root CA - C3
=======================
-----BEGIN CERTIFICATE-----
MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQGEwJVUzETMBEG
A1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMxIDAeBgNVBAMTF2VtU2lnbiBF
Q0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UE
BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQD
ExdlbVNpZ24gRUNDIFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd
6bciMK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4OjavtisIGJAnB9
SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0OBBYEFPtaSNCAIEDyqOkA
B2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gA
MGUCMQC02C8Cif22TGK6Q04ThHK1rt0c3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwU
ZOR8loMRnLDRWmFLpg9J0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ==
-----END CERTIFICATE-----

Hongkong Post Root CA 3
=======================
-----BEGIN CERTIFICATE-----
MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQELBQAwbzELMAkG
A1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJSG9uZyBLb25nMRYwFAYDVQQK
Ew1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25na29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2
MDMwMjI5NDZaFw00MjA2MDMwMjI5NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtv
bmcxEjAQBgNVBAcTCUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMX
SG9uZ2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz
iNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFOdem1p+/l6TWZ5Mwc50tf
jTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mIVoBc+L0sPOFMV4i707mV78vH9toxdCim
5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOe
sL4jpNrcyCse2m5FHomY2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj
0mRiikKYvLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+TtbNe/
JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZbx39ri1UbSsUgYT2u
y1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+l2oBlKN8W4UdKjk60FSh0Tlxnf0h
+bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YKTE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsG
xVd7GYYKecsAyVKvQv83j+GjHno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwID
AQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e
i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEwDQYJKoZIhvcN
AQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG7BJ8dNVI0lkUmcDrudHr9Egw
W62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCkMpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWld
y8joRTnU+kLBEUx3XZL7av9YROXrgZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov
+BS5gLNdTaqX4fnkGMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDc
eqFS3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJmOzj/2ZQw
9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+l6mc1X5VTMbeRRAc6uk7
nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6cJfTzPV4e0hz5sy229zdcxsshTrD3mUcY
hcErulWuBurQB7Lcq9CClnXO0lD+mefPL5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB
60PZ2Pierc+xYw5F9KBaLJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fq
dBb9HxEGmpv0
-----END CERTIFICATE-----

Microsoft ECC Root Certificate Authority 2017
=============================================
-----BEGIN CERTIFICATE-----
MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV
UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQgRUND
IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4
MjMxNjA0WjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw
NAYDVQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQ
BgcqhkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZRogPZnZH6
thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYbhGBKia/teQ87zvH2RPUB
eMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTIy5lycFIM
+Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlf
Xu5gKcs68tvWMoQZP3zVL8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaR
eNtUjGUBiudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M=
-----END CERTIFICATE-----

Microsoft RSA Root Certificate Authority 2017
=============================================
-----BEGIN CERTIFICATE-----
MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBlMQswCQYDVQQG
EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQg
UlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIw
NzE4MjMwMDIzWjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u
MTYwNAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcw
ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZNt9GkMml
7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0ZdDMbRnMlfl7rEqUrQ7e
S0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw7
1VdyvD/IybLeS2v4I2wDwAW9lcfNcztmgGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+
dkC0zVJhUXAoP8XFWvLJjEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49F
yGcohJUcaDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaGYaRS
MLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6W6IYZVcSn2i51BVr
lMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4KUGsTuqwPN1q3ErWQgR5WrlcihtnJ
0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH+FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJ
ClTUFLkqqNfs+avNJVgyeY+QW5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYw
DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC
NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZCLgLNFgVZJ8og
6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OCgMNPOsduET/m4xaRhPtthH80
dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk
+ONVFT24bcMKpBLBaYVu32TxU5nhSnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex
/2kskZGT4d9Mozd2TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDy
AmH3pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGRxpl/j8nW
ZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiAppGWSZI1b7rCoucL5mxAyE
7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKT
c0QWbej09+CVgI+WXTik9KveCjCHk9hNAHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D
5KbvtwEwXlGjefVwaaZBRA+GsCyRxj3qrg+E
-----END CERTIFICATE-----

e-Szigno Root CA 2017
=====================
-----BEGIN CERTIFICATE-----
MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNVBAYTAkhVMREw
DwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUt
MjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJvb3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZa
Fw00MjA4MjIxMjA3MDZaMHExCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UE
CgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3pp
Z25vIFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtvxie+RJCx
s1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+HWyx7xf58etqjYzBhMA8G
A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSHERUI0arBeAyxr87GyZDv
vzAEwDAfBgNVHSMEGDAWgBSHERUI0arBeAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEA
tVfd14pVCzbhhkT61NlojbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxO
svxyqltZ+efcMQ==
-----END CERTIFICATE-----

certSIGN Root CA G2
===================
-----BEGIN CERTIFICATE-----
MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNVBAYTAlJPMRQw
EgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjAeFw0xNzAy
MDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJBgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lH
TiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP
ADCCAgoCggIBAMDFdRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05
N0IwvlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZuIt4Imfk
abBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhpn+Sc8CnTXPnGFiWeI8Mg
wT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKscpc/I1mbySKEwQdPzH/iV8oScLumZfNp
dWO9lfsbl83kqK/20U6o2YpxJM02PbyWxPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91Qqh
ngLjYl/rNUssuHLoPj1PrCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732
jcZZroiFDsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fxDTvf
95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgyLcsUDFDYg2WD7rlc
z8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6CeWRgKRM+o/1Pcmqr4tTluCRVLERL
iohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1Ud
DgQWBBSCIS1mxteg4BXrzkwJd8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOB
ywaK8SJJ6ejqkX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC
b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQlqiCA2ClV9+BB
/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0OJD7uNGzcgbJceaBxXntC6Z5
8hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+cNywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5
BiKDUyUM/FHE5r7iOZULJK2v0ZXkltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklW
atKcsWMy5WHgUyIOpwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tU
Sxfj03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZkPuXaTH4M
NMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE1LlSVHJ7liXMvGnjSG4N
0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MXQRBdJ3NghVdJIgc=
-----END CERTIFICATE-----

Trustwave Global Certification Authority
========================================
-----BEGIN CERTIFICATE-----
MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJV
UzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2
ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9u
IEF1dGhvcml0eTAeFw0xNzA4MjMxOTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJV
UzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2
ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9u
IEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALldUShLPDeS0YLOvR29
zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0XznswuvCAAJWX/NKSqIk4cXGIDtiLK0thAf
LdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4Bq
stTnoApTAbqOl5F2brz81Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9o
WN0EACyW80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotPJqX+
OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1lRtzuzWniTY+HKE40
Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfwhI0Vcnyh78zyiGG69Gm7DIwLdVcE
uE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm
+9jaJXLE9gCxInm943xZYkqcBW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqj
ifLJS3tBEW1ntwiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud
EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1UdDwEB/wQEAwIB
BjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W0OhUKDtkLSGm+J1WE2pIPU/H
PinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfeuyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0H
ZJDmHvUqoai7PF35owgLEQzxPy0QlG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla
4gt5kNdXElE1GYhBaCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5R
vbbEsLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPTMaCm/zjd
zyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qequ5AvzSxnI9O4fKSTx+O
856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxhVicGaeVyQYHTtgGJoC86cnn+OjC/QezH
Yj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu
3R3y4G5OBVixwJAWKqQ9EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP
29FpHOTKyeC2nOnOcXHebD8WpHk=
-----END CERTIFICATE-----

Trustwave Global ECC P256 Certification Authority
=================================================
-----BEGIN CERTIFICATE-----
MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYDVQQGEwJVUzER
MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI
b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZp
Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYD
VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy
dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1
NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABH77bOYj
43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoNFWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqm
P62jQzBBMA8GA1UdEwEB/wQFMAMBAf8wDwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt
0UrrdaVKEJmzsaGLSvcwCgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjz
RM4q3wghDDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7
-----END CERTIFICATE-----

Trustwave Global ECC P384 Certification Authority
=================================================
-----BEGIN CERTIFICATE-----
MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYDVQQGEwJVUzER
MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI
b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZp
Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYD
VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy
dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4
NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuBBAAiA2IABGvaDXU1CDFH
Ba5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJj9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr
/TklZvFe/oyujUF5nQlgziip04pt89ZF1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNV
HQ8BAf8EBQMDBwYAMB0GA1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNn
ADBkAjA3AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsCMGcl
CrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVuSw==
-----END CERTIFICATE-----

NAVER Global Root Certification Authority
=========================================
-----BEGIN CERTIFICATE-----
MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEMBQAwaTELMAkG
A1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRGT1JNIENvcnAuMTIwMAYDVQQD
DClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4
NDJaFw0zNzA4MTgyMzU5NTlaMGkxCzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVT
UyBQTEFURk9STSBDb3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVAiQqrDZBb
UGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH38dq6SZeWYp34+hInDEW
+j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lEHoSTGEq0n+USZGnQJoViAbbJAh2+g1G7
XNr4rRVqmfeSVPc0W+m/6imBEtRTkZazkVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2
aacp+yPOiNgSnABIqKYPszuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4
Yb8ObtoqvC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHfnZ3z
VHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaGYQ5fG8Ir4ozVu53B
A0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo0es+nPxdGoMuK8u180SdOqcXYZai
cdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3aCJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejy
YhbLgGvtPe31HzClrkvJE+2KAQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNV
HQ4EFgQU0p+I36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB
Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoNqo0hV4/GPnrK
21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatjcu3cvuzHV+YwIHHW1xDBE1UB
jCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm+LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bx
hYTeodoS76TiEJd6eN4MUZeoIUCLhr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTg
E34h5prCy8VCZLQelHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTH
D8z7p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8piKCk5XQ
A76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLRLBT/DShycpWbXgnbiUSY
qqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oG
I/hGoiLtk/bdmuYqh7GYVPEi92tF4+KOdh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmg
kpzNNIaRkPpkUZ3+/uul9XXeifdy
-----END CERTIFICATE-----

AC RAIZ FNMT-RCM SERVIDORES SEGUROS
===================================
-----BEGIN CERTIFICATE-----
MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQswCQYDVQQGEwJF
UzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgwFgYDVQRhDA9WQVRFUy1RMjgy
NjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1SQ00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4
MTIyMDA5MzczM1oXDTQzMTIyMDA5MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQt
UkNNMQ4wDAYDVQQLDAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNB
QyBSQUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuBBAAiA2IA
BPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LHsbI6GA60XYyzZl2hNPk2
LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oKUm8BA06Oi6NCMEAwDwYDVR0TAQH/BAUw
AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqG
SM49BAMDA2kAMGYCMQCuSuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoD
zBOQn5ICMQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJyv+c=
-----END CERTIFICATE-----

GlobalSign Root R46
===================
-----BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUAMEYxCzAJBgNV
BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJv
b3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAX
BgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08Es
CVeJOaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQGvGIFAha/
r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud316HCkD7rRlr+/fKYIje
2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo0q3v84RLHIf8E6M6cqJaESvWJ3En7YEt
bWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSEy132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvj
K8Cd+RTyG/FWaha/LIWFzXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD4
12lPFzYE+cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCNI/on
ccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzsx2sZy/N78CsHpdls
eVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqaByFrgY/bxFn63iLABJzjqls2k+g9
vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYD
VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEM
BQADggIBAHx47PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg
JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti2kM3S+LGteWy
gxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIkpnnpHs6i58FZFZ8d4kuaPp92
CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRFFRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZm
OUdkLG5NrmJ7v2B0GbhWrJKsFjLtrWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qq
JZ4d16GLuc1CLgSkZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwye
qiv5u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP4vkYxboz
nxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6N3ec592kD3ZDZopD8p/7
DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3vouXsXgxT7PntgMTzlSdriVZzH81Xwj3
QEUxeCp6
-----END CERTIFICATE-----

GlobalSign Root E46
===================
-----BEGIN CERTIFICATE-----
MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYxCzAJBgNVBAYT
AkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3Qg
RTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNV
BAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcq
hkjOPQIBBgUrgQQAIgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkB
jtjqR+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGddyXqBPCCj
QjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQxCpCPtsad0kRL
gLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZk
vLtoURMMA/cVi4RguYv/Uo7njLwcAjA8+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+
CAezNIm8BZ/3Hobui3A=
-----END CERTIFICATE-----

GLOBALTRUST 2020
================
-----BEGIN CERTIFICATE-----
MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkGA1UEBhMCQVQx
IzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVT
VCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYxMDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAh
BgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAy
MDIwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWi
D59bRatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9ZYybNpyrO
VPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3QWPKzv9pj2gOlTblzLmM
CcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPwyJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCm
fecqQjuCgGOlYx8ZzHyyZqjC0203b+J+BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKA
A1GqtH6qRNdDYfOiaxaJSaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9OR
JitHHmkHr96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj04KlG
DfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9MedKZssCz3AwyIDMvU
clOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIwq7ejMZdnrY8XD2zHc+0klGvIg5rQ
mjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUw
AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1Ud
IwQYMBaAFNwuH9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA
VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJCXtzoRlgHNQIw
4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd6IwPS3BD0IL/qMy/pJTAvoe9
iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf+I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS
8cE54+X1+NZK3TTN+2/BT+MAi1bikvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2
HcqtbepBEX4tdJP7wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxS
vTOBTI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6CMUO+1918
oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn4rnvyOL2NSl6dPrFf4IF
YqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+IaFvowdlxfv1k7/9nR4hYJS8+hge9+6jl
gqispdNpQ80xiEmEU5LAsTkbOYMBMMTyqfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg==
-----END CERTIFICATE-----

ANF Secure Server Root CA
=========================
-----BEGIN CERTIFICATE-----
MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNVBAUTCUc2MzI4
NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lv
bjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNVBAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3Qg
Q0EwHhcNMTkwOTA0MTAwMDM4WhcNMzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEw
MQswCQYDVQQGEwJFUzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQw
EgYDVQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9vdCBDQTCC
AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCjcqQZAZ2cC4Ffc0m6p6zz
BE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9qyGFOtibBTI3/TO80sh9l2Ll49a2pcbnv
T1gdpd50IJeh7WhM3pIXS7yr/2WanvtH2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcv
B2VSAKduyK9o7PQUlrZXH1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXse
zx76W0OLzc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyRp1RM
VwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQzW7i1o0TJrH93PB0j
7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/SiOL9V8BY9KHcyi1Swr1+KuCLH5z
JTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJnLNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe
8TZBAQIvfXOn3kLMTOmJDVb3n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVO
Hj1tyRRM4y5Bu8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj
o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAOBgNVHQ8BAf8E
BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEATh65isagmD9uw2nAalxJ
UqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzx
j6ptBZNscsdW699QIyjlRRA96Gejrw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDt
dD+4E5UGUcjohybKpFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM
5gf0vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjqOknkJjCb
5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ/zo1PqVUSlJZS2Db7v54
EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ92zg/LFis6ELhDtjTO0wugumDLmsx2d1H
hk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI+PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGy
g77FGr8H6lnco4g175x2MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3
r5+qPeoott7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw=
-----END CERTIFICATE-----

Certum EC-384 CA
================
-----BEGIN CERTIFICATE-----
MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQswCQYDVQQGEwJQ
TDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2Vy
dGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2
MDcyNDU0WhcNNDMwMzI2MDcyNDU0WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERh
dGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx
GTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATEKI6rGFtq
vm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7TmFy8as10CW4kjPMIRBSqn
iBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68KjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD
VR0OBBYEFI0GZnQkdjrzife81r1HfS+8EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNo
ADBlAjADVS2m5hjEfO/JUG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0
QoSZ/6vnnvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k=
-----END CERTIFICATE-----

Certum Trusted Root CA
======================
-----BEGIN CERTIFICATE-----
MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6MQswCQYDVQQG
EwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0g
Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0Ew
HhcNMTgwMzE2MTIxMDEzWhcNNDMwMzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMY
QXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBB
dXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZn0EGze2jusDbCSzBfN8p
fktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/qp1x4EaTByIVcJdPTsuclzxFUl6s1wB52
HO8AU5853BSlLCIls3Jy/I2z5T4IHhQqNwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2
fJmItdUDmj0VDT06qKhF8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGt
g/BKEiJ3HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGamqi4
NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi7VdNIuJGmj8PkTQk
fVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSFytKAQd8FqKPVhJBPC/PgP5sZ0jeJ
P/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0PqafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSY
njYJdmZm/Bo/6khUHL4wvYBQv3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHK
HRzQ+8S1h9E6Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1
vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQADggIBAEii1QAL
LtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4WxmB82M+w85bj/UvXgF2Ez8s
ALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvozMrnadyHncI013nR03e4qllY/p0m+jiGPp2K
h2RX5Rc64vmNueMzeMGQ2Ljdt4NR5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8
CYyqOhNf6DR5UMEQGfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA
4kZf5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq0Uc9Nneo
WWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7DP78v3DSk+yshzWePS/Tj
6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTMqJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmT
OPQD8rv7gmsHINFSH5pkAnuYZttcTVoP0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZck
bxJF0WddCajJFdr60qZfE2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb
-----END CERTIFICATE-----

TunTrust Root CA
================
-----BEGIN CERTIFICATE-----
MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQELBQAwYTELMAkG
A1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUgQ2VydGlmaWNhdGlvbiBFbGVj
dHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJvb3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQw
NDI2MDg1NzU2WjBhMQswCQYDVQQGEwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBD
ZXJ0aWZpY2F0aW9uIEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIw
DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZn56eY+hz
2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd2JQDoOw05TDENX37Jk0b
bjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgFVwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7
NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZGoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAd
gjH8KcwAWJeRTIAAHDOFli/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViW
VSHbhlnUr8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2eY8f
Tpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIbMlEsPvLfe/ZdeikZ
juXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISgjwBUFfyRbVinljvrS5YnzWuioYas
DXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwS
VXAkPcvCFDVDXSdOvsC9qnyW5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI
04Y+oXNZtPdEITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0
90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+zxiD2BkewhpMl
0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYuQEkHDVneixCwSQXi/5E/S7fd
Ao74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRY
YdZ2vyJ/0Adqp2RT8JeNnYA/u8EH22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJp
adbGNjHh/PqAulxPxOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65x
xBzndFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5Xc0yGYuP
jCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7bnV2UqL1g52KAdoGDDIzM
MEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQCvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9z
ZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZHu/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3r
AZ3r2OvEhJn7wAzMMujjd9qDRIueVSjAi1jTkD5OGwDxFa2DK5o=
-----END CERTIFICATE-----

HARICA TLS RSA Root CA 2021
===========================
-----BEGIN CERTIFICATE-----
MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQG
EwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u
cyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0EgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUz
OFoXDTQ1MDIxMzEwNTUzN1owbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRl
bWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNB
IFJvb3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569lmwVnlskN
JLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE4VGC/6zStGndLuwRo0Xu
a2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uva9of08WRiFukiZLRgeaMOVig1mlDqa2Y
Ulhu2wr7a89o+uOkXjpFc5gH6l8Cct4MpbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K
5FrZx40d/JiZ+yykgmvwKh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEv
dmn8kN3bLW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcYAuUR
0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqBAGMUuTNe3QvboEUH
GjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYqE613TBoYm5EPWNgGVMWX+Ko/IIqm
haZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHrW2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQ
CPxrvrNQKlr9qEgYRtaQQJKQCoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8G
A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE
AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAUX15QvWiWkKQU
EapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3f5Z2EMVGpdAgS1D0NTsY9FVq
QRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxajaH6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxD
QpSbIPDRzbLrLFPCU3hKTwSUQZqPJzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcR
j88YxeMn/ibvBZ3PzzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5
vZStjBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0/L5H9MG0
qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pTBGIBnfHAT+7hOtSLIBD6
Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79aPib8qXPMThcFarmlwDB31qlpzmq6YR/
PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YWxw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnn
kf3/W9b3raYvAwtt41dU63ZTGI0RmLo=
-----END CERTIFICATE-----

HARICA TLS ECC Root CA 2021
===========================
-----BEGIN CERTIFICATE-----
MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQswCQYDVQQGEwJH
UjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBD
QTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoX
DTQ1MDIxMzExMDEwOVowbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWlj
IGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJv
b3QgQ0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7KKrxcm1l
AEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9YSTHMmE5gEYd103KUkE+b
ECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW
0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAi
rcJRQO9gcS3ujwLEXQNwSaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/Qw
CZ61IygNnxS2PFOiTAZpffpskcYqSUXm7LcT4Tps
-----END CERTIFICATE-----

Autoridad de Certificacion Firmaprofesional CIF A62634068
=========================================================
-----BEGIN CERTIFICATE-----
MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCRVMxQjBA
BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2
MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIw
QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB
NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD
Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P
B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY
7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH
ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI
plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX
MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX
LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK
bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU
vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1Ud
DgQWBBRlzeurNR4APn7VdMActHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4w
gZswgZgGBFUdIAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j
b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABCAG8AbgBhAG4A
bwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAwADEANzAOBgNVHQ8BAf8EBAMC
AQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9miWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL
4QjbEwj4KKE1soCzC1HA01aajTNFSa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDb
LIpgD7dvlAceHabJhfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1il
I45PVf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZEEAEeiGaP
cjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV1aUsIC+nmCjuRfzxuIgA
LI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2tCsvMo2ebKHTEm9caPARYpoKdrcd7b/+A
lun4jWq9GJAd/0kakFI3ky88Al2CdgtR5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH
9IBk9W6VULgRfhVwOEqwf9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpf
NIbnYrX9ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNKGbqE
ZycPvEJdvSRUDewdcAZfpLz6IHxV
-----END CERTIFICATE-----

vTrus ECC Root CA
=================
-----BEGIN CERTIFICATE-----
MIICDzCCAZWgAwIBAgIUbmq8WapTvpg5Z6LSa6Q75m0c1towCgYIKoZIzj0EAwMwRzELMAkGA1UE
BhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBS
b290IENBMB4XDTE4MDczMTA3MjY0NFoXDTQzMDczMTA3MjY0NFowRzELMAkGA1UEBhMCQ04xHDAa
BgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBSb290IENBMHYw
EAYHKoZIzj0CAQYFK4EEACIDYgAEZVBKrox5lkqqHAjDo6LN/llWQXf9JpRCux3NCNtzslt188+c
ToL0v/hhJoVs1oVbcnDS/dtitN9Ti72xRFhiQgnH+n9bEOf+QP3A2MMrMudwpremIFUde4BdS49n
TPEQo0IwQDAdBgNVHQ4EFgQUmDnNvtiyjPeyq+GtJK97fKHbH88wDwYDVR0TAQH/BAUwAwEB/zAO
BgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaAAwZQIwV53dVvHH4+m4SVBrm2nDb+zDfSXkV5UT
QJtS0zvzQBm8JsctBp61ezaf9SXUY2sAAjEA6dPGnlaaKsyh2j/IZivTWJwghfqrkYpwcBE4YGQL
YgmRWAD5Tfs0aNoJrSEGGJTO
-----END CERTIFICATE-----

vTrus Root CA
=============
-----BEGIN CERTIFICATE-----
MIIFVjCCAz6gAwIBAgIUQ+NxE9izWRRdt86M/TX9b7wFjUUwDQYJKoZIhvcNAQELBQAwQzELMAkG
A1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xFjAUBgNVBAMTDXZUcnVzIFJv
b3QgQ0EwHhcNMTgwNzMxMDcyNDA1WhcNNDMwNzMxMDcyNDA1WjBDMQswCQYDVQQGEwJDTjEcMBoG
A1UEChMTaVRydXNDaGluYSBDby4sTHRkLjEWMBQGA1UEAxMNdlRydXMgUm9vdCBDQTCCAiIwDQYJ
KoZIhvcNAQEBBQADggIPADCCAgoCggIBAL1VfGHTuB0EYgWgrmy3cLRB6ksDXhA/kFocizuwZots
SKYcIrrVQJLuM7IjWcmOvFjai57QGfIvWcaMY1q6n6MLsLOaXLoRuBLpDLvPbmyAhykUAyyNJJrI
ZIO1aqwTLDPxn9wsYTwaP3BVm60AUn/PBLn+NvqcwBauYv6WTEN+VRS+GrPSbcKvdmaVayqwlHeF
XgQPYh1jdfdr58tbmnDsPmcF8P4HCIDPKNsFxhQnL4Z98Cfe/+Z+M0jnCx5Y0ScrUw5XSmXX+6KA
YPxMvDVTAWqXcoKv8R1w6Jz1717CbMdHflqUhSZNO7rrTOiwCcJlwp2dCZtOtZcFrPUGoPc2BX70
kLJrxLT5ZOrpGgrIDajtJ8nU57O5q4IikCc9Kuh8kO+8T/3iCiSn3mUkpF3qwHYw03dQ+A0Em5Q2
AXPKBlim0zvc+gRGE1WKyURHuFE5Gi7oNOJ5y1lKCn+8pu8fA2dqWSslYpPZUxlmPCdiKYZNpGvu
/9ROutW04o5IWgAZCfEF2c6Rsffr6TlP9m8EQ5pV9T4FFL2/s1m02I4zhKOQUqqzApVg+QxMaPnu
1RcN+HFXtSXkKe5lXa/R7jwXC1pDxaWG6iSe4gUH3DRCEpHWOXSuTEGC2/KmSNGzm/MzqvOmwMVO
9fSddmPmAsYiS8GVP1BkLFTltvA8Kc9XAgMBAAGjQjBAMB0GA1UdDgQWBBRUYnBj8XWEQ1iO0RYg
scasGrz2iTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOC
AgEAKbqSSaet8PFww+SX8J+pJdVrnjT+5hpk9jprUrIQeBqfTNqK2uwcN1LgQkv7bHbKJAs5EhWd
nxEt/Hlk3ODg9d3gV8mlsnZwUKT+twpw1aA08XXXTUm6EdGz2OyC/+sOxL9kLX1jbhd47F18iMjr
jld22VkE+rxSH0Ws8HqA7Oxvdq6R2xCOBNyS36D25q5J08FsEhvMKar5CKXiNxTKsbhm7xqC5PD4
8acWabfbqWE8n/Uxy+QARsIvdLGx14HuqCaVvIivTDUHKgLKeBRtRytAVunLKmChZwOgzoy8sHJn
xDHO2zTlJQNgJXtxmOTAGytfdELSS8VZCAeHvsXDf+eW2eHcKJfWjwXj9ZtOyh1QRwVTsMo554Wg
icEFOwE30z9J4nfrI8iIZjs9OXYhRvHsXyO466JmdXTBQPfYaJqT4i2pLr0cox7IdMakLXogqzu4
sEb9b91fUlV1YvCXoHzXOP0l382gmxDPi7g4Xl7FtKYCNqEeXxzP4padKar9mK5S4fNBUvupLnKW
nyfjqnN9+BojZns7q2WwMgFLFT49ok8MKzWixtlnEjUwzXYuFrOZnk1PTi07NEPhmg4NpGaXutIc
SkwsKouLgU9xGqndXHt7CMUADTdA43x7VF8vhV929vensBxXVsFy6K2ir40zSbofitzmdHxghm+H
l3s=
-----END CERTIFICATE-----

ISRG Root X2
============
-----BEGIN CERTIFICATE-----
MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQswCQYDVQQGEwJV
UzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElT
UkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVT
MSkwJwYDVQQKEyBJbnRlcm5ldCBTZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNS
RyBSb290IFgyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0H
ttwW+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9ItgKbppb
d9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
HQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZIzj0EAwMDaAAwZQIwe3lORlCEwkSHRhtF
cP9Ymd70/aTSVaYgLXTWNLxBo1BfASdWtL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5
U6VR5CmD1/iQMVtCnwr1/q4AaOeMSQ+2b1tbFfLn
-----END CERTIFICATE-----

HiPKI Root CA - G1
==================
-----BEGIN CERTIFICATE-----
MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBPMQswCQYDVQQG
EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xGzAZBgNVBAMMEkhpUEtJ
IFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRaFw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYT
AlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kg
Um9vdCBDQSAtIEcxMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0
o9QwqNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twvVcg3Px+k
wJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6lZgRZq2XNdZ1AYDgr/SE
YYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnzQs7ZngyzsHeXZJzA9KMuH5UHsBffMNsA
GJZMoYFL3QRtU6M9/Aes1MU3guvklQgZKILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfd
hSi8MEyr48KxRURHH+CKFgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj
1jOXTyFjHluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDry+K4
9a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ/W3c1pzAtH2lsN0/
Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgMa/aOEmem8rJY5AIJEzypuxC00jBF
8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYD
VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQD
AgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi
7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqcSE5XCV0vrPSl
tJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6FzaZsT0pPBWGTMpWmWSBUdGSquE
wx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9TcXzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07Q
JNBAsNB1CI69aO4I1258EHBGG3zgiLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv
5wiZqAxeJoBF1PhoL5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+Gpz
jLrFNe85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wrkkVbbiVg
hUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+vhV4nYWBSipX3tUZQ9rb
yltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQUYDksswBVLuT1sw5XxJFBAJw/6KXf6vb/
yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ==
-----END CERTIFICATE-----

GlobalSign ECC Root CA - R4
===========================
-----BEGIN CERTIFICATE-----
MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYDVQQLExtHbG9i
YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds
b2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgwMTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9i
YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds
b2JhbFNpZ24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkW
ymOxuYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNVHQ8BAf8E
BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/+wpu+74zyTyjhNUwCgYI
KoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147bmF0774BxL4YSFlhgjICICadVGNA3jdg
UM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm
-----END CERTIFICATE-----

GTS Root R1
===========
-----BEGIN CERTIFICATE-----
MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJV
UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg
UjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE
ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0G
CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM
f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7wCl7raKb0
xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjwTcLCeoiKu7rPWRnWr4+w
B7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0PfyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXW
nOunVmSPlk9orj2XwoSPwLxAwAtcvfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk
9+aCEI3oncKKiPo4Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zq
kUspzBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92wO1A
K/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70paDPvOmbsB4om3xPX
V2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDW
cfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T
AQH/BAUwAwEB/zAdBgNVHQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQAD
ggIBAJ+qQibbC5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe
QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuyh6f88/qBVRRi
ClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM47HLwEXWdyzRSjeZ2axfG34ar
J45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8JZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYci
NuaCp+0KueIHoI17eko8cdLiA6EfMgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5me
LMFrUKTX5hgUvYU/Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJF
fbdT6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ0E6yove+
7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm2tIMPNuzjsmhDYAPexZ3
FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bbbP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3
gm3c
-----END CERTIFICATE-----

GTS Root R2
===========
-----BEGIN CERTIFICATE-----
MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJV
UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg
UjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE
ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0G
CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3Lv
CvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY6Dlo7JUl
e3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAuMC6C/Pq8tBcKSOWIm8Wb
a96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7kRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS
+LFjKBC4swm4VndAoiaYecb+3yXuPuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7M
kogwTZq9TwtImoS1mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJG
r61K8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RWIr9q
S34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKaG73VululycslaVNV
J1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCqgc7dGtxRcw1PcOnlthYhGXmy5okL
dWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T
AQH/BAUwAwEB/zAdBgNVHQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQAD
ggIBAB/Kzt3HvqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8
0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyCB19m3H0Q/gxh
swWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2uNmSRXbBoGOqKYcl3qJfEycel
/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMgyALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVn
jWQye+mew4K6Ki3pHrTgSAai/GevHyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y5
9PYjJbigapordwj6xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M
7YNRTOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924SgJPFI/2R8
0L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV7LXTWtiBmelDGDfrs7vR
WGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjW
HYbL
-----END CERTIFICATE-----

GTS Root R3
===========
-----BEGIN CERTIFICATE-----
MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi
MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMw
HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ
R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjO
PQIBBgUrgQQAIgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout
736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL24CejQjBA
MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTB8Sa6oC2uhYHP0/Eq
Er24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azT
L818+FsuVbu/3ZL3pAzcMeGiAjEA/JdmZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV
11RZt+cRLInUue4X
-----END CERTIFICATE-----

GTS Root R4
===========
-----BEGIN CERTIFICATE-----
MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi
MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQw
HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ
R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjO
PQIBBgUrgQQAIgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu
hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvRHYqjQjBA
MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSATNbrdP9JNqPV2Py1
PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/C
r8deVl5c1RxYIigL9zC2L7F8AjEA8GE8p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh
4rsUecrNIdSUtUlD
-----END CERTIFICATE-----

Telia Root CA v2
================
-----BEGIN CERTIFICATE-----
MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQxCzAJBgNVBAYT
AkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2
MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQK
DBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZI
hvcNAQEBBQADggIPADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ7
6zBqAMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9vVYiQJ3q
9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9lRdU2HhE8Qx3FZLgmEKn
pNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTODn3WhUidhOPFZPY5Q4L15POdslv5e2QJl
tI5c0BE0312/UqeBAMN/mUWZFdUXyApT7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW
5olWK8jjfN7j/4nlNW4o6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNr
RBH0pUPCTEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6WT0E
BXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63RDolUK5X6wK0dmBR4
M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZIpEYslOqodmJHixBTB0hXbOKSTbau
BcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGjYzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7W
xy+G2CQ5MB0GA1UdDgQWBBRyrOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYD
VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ
8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi0f6X+J8wfBj5
tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMMA8iZGok1GTzTyVR8qPAs5m4H
eW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBSSRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+C
y748fdHif64W1lZYudogsYMVoe+KTTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygC
QMez2P2ccGrGKMOF6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15
h2Er3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMtTy3EHD70
sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pTVmBds9hCG1xLEooc6+t9
xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAWysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQ
raVplI/owd8k+BsHMYeB2F326CjYSlKArBPuUBQemMc=
-----END CERTIFICATE-----

D-TRUST BR Root CA 1 2020
=========================
-----BEGIN CERTIFICATE-----
MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE
RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEJSIFJvb3QgQ0EgMSAy
MDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNV
BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAG
ByqGSM49AgEGBSuBBAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7
dPYSzuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0QVK5buXu
QqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/VbNafAkl1bK6CKBrqx9t
MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu
bmV0L2NybC9kLXRydXN0X2JyX3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj
dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP
PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD
AwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFWwKrY7RjEsK70Pvom
AjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHVdWNbFJWcHwHP2NVypw87
-----END CERTIFICATE-----

D-TRUST EV Root CA 1 2020
=========================
-----BEGIN CERTIFICATE-----
MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE
RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEVWIFJvb3QgQ0EgMSAy
MDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNV
BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAG
ByqGSM49AgEGBSuBBAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8
ZRCC/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rDwpdhQntJ
raOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3OqQo5FD4pPfsazK2/umL
MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu
bmV0L2NybC9kLXRydXN0X2V2X3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj
dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP
PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD
AwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CAy/m0sRtW9XLS/BnR
AjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJbgfM0agPnIjhQW+0ZT0MW
-----END CERTIFICATE-----

DigiCert TLS ECC P384 Root G5
=============================
-----BEGIN CERTIFICATE-----
MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQswCQYDVQQGEwJV
UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURpZ2lDZXJ0IFRMUyBFQ0MgUDM4
NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMx
FzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQg
Um9vdCBHNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1Tzvd
lHJS7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp0zVozptj
n4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICISB4CIfBFqMA4GA1UdDwEB
/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQCJao1H5+z8blUD2Wds
Jk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQLgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIx
AJSdYsiJvRmEFOml+wG4DXZDjC5Ty3zfDBeWUA==
-----END CERTIFICATE-----

DigiCert TLS RSA4096 Root G5
============================
-----BEGIN CERTIFICATE-----
MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBNMQswCQYDVQQG
EwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0
MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcNNDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJV
UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2
IFJvb3QgRzUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS8
7IE+ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG02C+JFvuU
AT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgpwgscONyfMXdcvyej/Ces
tyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZMpG2T6T867jp8nVid9E6P/DsjyG244gXa
zOvswzH016cpVIDPRFtMbzCe88zdH5RDnU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnV
DdXifBBiqmvwPXbzP6PosMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9q
TXeXAaDxZre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cdLvvy
z6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvXKyY//SovcfXWJL5/
MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNeXoVPzthwiHvOAbWWl9fNff2C+MIk
wcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPLtgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4E
FgQUUTMc7TZArxfTJc1paPKvTiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8w
DQYJKoZIhvcNAQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw
GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7HPNtQOa27PShN
lnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLFO4uJ+DQtpBflF+aZfTCIITfN
MBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQREtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/
u4cnYiWB39yhL/btp/96j1EuMPikAdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9G
OUrYU9DzLjtxpdRv/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh
47a+p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilwMUc/dNAU
FvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WFqUITVuwhd4GTWgzqltlJ
yqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCKovfepEWFJqgejF0pW8hL2JpqA15w8oVP
bEtoL8pU9ozaMv7Da4M/OMZ+
-----END CERTIFICATE-----

Certainly Root R1
=================
-----BEGIN CERTIFICATE-----
MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAwPTELMAkGA1UE
BhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2VydGFpbmx5IFJvb3QgUjEwHhcN
MjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2Vy
dGFpbmx5MRowGAYDVQQDExFDZXJ0YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIP
ADCCAgoCggIBANA21B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O
5MQTvqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbedaFySpvXl
8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b01C7jcvk2xusVtyWMOvwl
DbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGI
XsXwClTNSaa/ApzSRKft43jvRl5tcdF5cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkN
KPl6I7ENPT2a/Z2B7yyQwHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQ
AjeZjOVJ6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA2Cnb
rlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyHWyf5QBGenDPBt+U1
VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMReiFPCyEQtkA6qyI6BJyLm4SGcprS
p6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
DgQWBBTgqj8ljZ9EXME66C6ud0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAsz
HQNTVfSVcOQrPbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d
8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi1wrykXprOQ4v
MMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrdrRT90+7iIgXr0PK3aBLXWopB
GsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9ditaY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+
gjwN/KUD+nsa2UUeYNrEjvn8K8l7lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgH
JBu6haEaBQmAupVjyTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7
fpYnKx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLyyCwzk5Iw
x06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5nwXARPbv0+Em34yaXOp/S
X3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6OV+KmalBWQewLK8=
-----END CERTIFICATE-----

Certainly Root E1
=================
-----BEGIN CERTIFICATE-----
MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQswCQYDVQQGEwJV
UzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlubHkgUm9vdCBFMTAeFw0yMTA0
MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlu
bHkxGjAYBgNVBAMTEUNlcnRhaW5seSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4
fxzf7flHh4axpMCK+IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9
YBk2QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8EBAMCAQYw
DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4hevIIgcwCgYIKoZIzj0E
AwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozmut6Dacpps6kFtZaSF4fC0urQe87YQVt8
rgIwRt7qy12a7DLCZRawTDBcMPPaTnOGBtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR
-----END CERTIFICATE-----

Security Communication ECC RootCA1
==================================
-----BEGIN CERTIFICATE-----
MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYTAkpQMSUwIwYD
VQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYDVQQDEyJTZWN1cml0eSBDb21t
dW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYxNjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTEL
MAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNV
BAMTIlNlY3VyaXR5IENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQA
IgNiAASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+CnnfdldB9sELLo
5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpKULGjQjBAMB0GA1UdDgQW
BBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAK
BggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3L
snNdo4gIxwwCMQDAqy0Obe0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70e
N9k=
-----END CERTIFICATE-----

BJCA Global Root CA1
====================
-----BEGIN CERTIFICATE-----
MIIFdDCCA1ygAwIBAgIQVW9l47TZkGobCdFsPsBsIDANBgkqhkiG9w0BAQsFADBUMQswCQYDVQQG
EwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJVFkxHTAbBgNVBAMMFEJK
Q0EgR2xvYmFsIFJvb3QgQ0ExMB4XDTE5MTIxOTAzMTYxN1oXDTQ0MTIxMjAzMTYxN1owVDELMAkG
A1UEBhMCQ04xJjAkBgNVBAoMHUJFSUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQD
DBRCSkNBIEdsb2JhbCBSb290IENBMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPFm
CL3ZxRVhy4QEQaVpN3cdwbB7+sN3SJATcmTRuHyQNZ0YeYjjlwE8R4HyDqKYDZ4/N+AZspDyRhyS
sTphzvq3Rp4Dhtczbu33RYx2N95ulpH3134rhxfVizXuhJFyV9xgw8O558dnJCNPYwpj9mZ9S1Wn
P3hkSWkSl+BMDdMJoDIwOvqfwPKcxRIqLhy1BDPapDgRat7GGPZHOiJBhyL8xIkoVNiMpTAK+BcW
yqw3/XmnkRd4OJmtWO2y3syJfQOcs4ll5+M7sSKGjwZteAf9kRJ/sGsciQ35uMt0WwfCyPQ10WRj
eulumijWML3mG90Vr4TqnMfK9Q7q8l0ph49pczm+LiRvRSGsxdRpJQaDrXpIhRMsDQa4bHlW/KNn
MoH1V6XKV0Jp6VwkYe/iMBhORJhVb3rCk9gZtt58R4oRTklH2yiUAguUSiz5EtBP6DF+bHq/pj+b
OT0CFqMYs2esWz8sgytnOYFcuX6U1WTdno9uruh8W7TXakdI136z1C2OVnZOz2nxbkRs1CTqjSSh
GL+9V/6pmTW12xB3uD1IutbB5/EjPtffhZ0nPNRAvQoMvfXnjSXWgXSHRtQpdaJCbPdzied9v3pK
H9MiyRVVz99vfFXQpIsHETdfg6YmV6YBW37+WGgHqel62bno/1Afq8K0wM7o6v0PvY1NuLxxAgMB
AAGjQjBAMB0GA1UdDgQWBBTF7+3M2I0hxkjk49cULqcWk+WYATAPBgNVHRMBAf8EBTADAQH/MA4G
A1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAUoKsITQfI/Ki2Pm4rzc2IInRNwPWaZ+4
YRC6ojGYWUfo0Q0lHhVBDOAqVdVXUsv45Mdpox1NcQJeXyFFYEhcCY5JEMEE3KliawLwQ8hOnThJ
dMkycFRtwUf8jrQ2ntScvd0g1lPJGKm1Vrl2i5VnZu69mP6u775u+2D2/VnGKhs/I0qUJDAnyIm8
60Qkmss9vk/Ves6OF8tiwdneHg56/0OGNFK8YT88X7vZdrRTvJez/opMEi4r89fO4aL/3Xtw+zuh
TaRjAv04l5U/BXCga99igUOLtFkNSoxUnMW7gZ/NfaXvCyUeOiDbHPwfmGcCCtRzRBPbUYQaVQNW
4AB+dAb/OMRyHdOoP2gxXdMJxy6MW2Pg6Nwe0uxhHvLe5e/2mXZgLR6UcnHGCyoyx5JO1UbXHfmp
GQrI+pXObSOYqgs4rZpWDW+N8TEAiMEXnM0ZNjX+VVOg4DwzX5Ze4jLp3zO7Bkqp2IRzznfSxqxx
4VyjHQy7Ct9f4qNx2No3WqB4K/TUfet27fJhcKVlmtOJNBir+3I+17Q9eVzYH6Eze9mCUAyTF6ps
3MKCuwJXNq+YJyo5UOGwifUll35HaBC07HPKs5fRJNz2YqAo07WjuGS3iGJCz51TzZm+ZGiPTx4S
SPfSKcOYKMryMguTjClPPGAyzQWWYezyr/6zcCwupvI=
-----END CERTIFICATE-----

BJCA Global Root CA2
====================
-----BEGIN CERTIFICATE-----
MIICJTCCAaugAwIBAgIQLBcIfWQqwP6FGFkGz7RK6zAKBggqhkjOPQQDAzBUMQswCQYDVQQGEwJD
TjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJVFkxHTAbBgNVBAMMFEJKQ0Eg
R2xvYmFsIFJvb3QgQ0EyMB4XDTE5MTIxOTAzMTgyMVoXDTQ0MTIxMjAzMTgyMVowVDELMAkGA1UE
BhMCQ04xJjAkBgNVBAoMHUJFSUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRC
SkNBIEdsb2JhbCBSb290IENBMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABJ3LgJGNU2e1uVCxA/jl
SR9BIgmwUVJY1is0j8USRhTFiy8shP8sbqjV8QnjAyEUxEM9fMEsxEtqSs3ph+B99iK++kpRuDCK
/eHeGBIK9ke35xe/J4rUQUyWPGCWwf0VHKNCMEAwHQYDVR0OBBYEFNJKsVF/BvDRgh9Obl+rg/xI
1LCRMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMBq8
W9f+qdJUDkpd0m2xQNz0Q9XSSpkZElaA94M04TVOSG0ED1cxMDAtsaqdAzjbBgIxAMvMh1PLet8g
UXOQwKhbYdDFUDn9hf7B43j4ptZLvZuHjw/l1lOWqzzIQNph91Oj9w==
-----END CERTIFICATE-----

Sectigo Public Server Authentication Root E46
=============================================
-----BEGIN CERTIFICATE-----
MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQswCQYDVQQGEwJH
QjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBTZXJ2
ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5
WjBfMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0
aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUr
gQQAIgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccCWvkEN/U0
NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+6xnOQ6OjQjBAMB0GA1Ud
DgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB
/zAKBggqhkjOPQQDAwNnADBkAjAn7qRaqCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RH
lAFWovgzJQxC36oCMB3q4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21U
SAGKcw==
-----END CERTIFICATE-----

Sectigo Public Server Authentication Root R46
=============================================
-----BEGIN CERTIFICATE-----
MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBfMQswCQYDVQQG
EwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT
ZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwHhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1
OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T
ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3
DQEBAQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDaef0rty2k
1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnzSDBh+oF8HqcIStw+Kxwf
GExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xfiOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMP
FF1bFOdLvt30yNoDN9HWOaEhUTCDsG3XME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vu
ZDCQOc2TZYEhMbUjUDM3IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5Qaz
Yw6A3OASVYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgESJ/A
wSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu+Zd4KKTIRJLpfSYF
plhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt8uaZFURww3y8nDnAtOFr94MlI1fZ
EoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+LHaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW
6aWWrL3DkJiy4Pmi1KZHQ3xtzwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWI
IUkwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c
mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQYKlJfp/imTYp
E0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52gDY9hAaLMyZlbcp+nv4fjFg4
exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZAFv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M
0ejf5lG5Nkc/kLnHvALcWxxPDkjBJYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI
84HxZmduTILA7rpXDhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9m
pFuiTdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5dHn5Hrwd
Vw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65LvKRRFHQV80MNNVIIb/b
E/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmm
J1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAYQqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL
-----END CERTIFICATE-----

SSL.com TLS RSA Root CA 2022
============================
-----BEGIN CERTIFICATE-----
MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQG
EwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxTU0wuY29tIFRMUyBSU0Eg
Um9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloXDTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMC
VVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJv
b3QgQ0EgMjAyMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u
9nTPL3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OYt6/wNr/y
7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0insS657Lb85/bRi3pZ7Qcac
oOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3PnxEX4MN8/HdIGkWCVDi1FW24IBydm5M
R7d1VVm0U3TZlMZBrViKMWYPHqIbKUBOL9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDG
D6C1vBdOSHtRwvzpXGk3R2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEW
TO6Af77wdr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS+YCk
8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYSd66UNHsef8JmAOSq
g+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoGAtUjHBPW6dvbxrB6y3snm/vg1UYk
7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2fgTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1Ud
EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsu
N+7jhHonLs0ZNbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt
hEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsMQtfhWsSWTVTN
j8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvfR4iyrT7gJ4eLSYwfqUdYe5by
iB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJDPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjU
o3KUQyxi4U5cMj29TH0ZR6LDSeeWP4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqo
ENjwuSfr98t67wVylrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7Egkaib
MOlqbLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2wAgDHbICi
vRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3qr5nsLFR+jM4uElZI7xc7
P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sjiMho6/4UIyYOf8kpIEFR3N+2ivEC+5BB0
9+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA=
-----END CERTIFICATE-----

SSL.com TLS ECC Root CA 2022
============================
-----BEGIN CERTIFICATE-----
MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQswCQYDVQQGEwJV
UzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxTU0wuY29tIFRMUyBFQ0MgUm9v
dCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2MDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMx
GDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3Qg
Q0EgMjAyMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWy
JGYmacCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFNSeR7T5v1
5wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSJjy+j6CugFFR7
81a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NWuCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGG
MAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w
7deedWo1dlJF4AIxAMeNb0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5
Zn6g6g==
-----END CERTIFICATE-----

Atos TrustedRoot Root CA ECC TLS 2021
=====================================
-----BEGIN CERTIFICATE-----
MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4wLAYDVQQDDCVB
dG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0wCwYDVQQKDARBdG9zMQswCQYD
VQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0MTcwOTI2MjJaMEwxLjAsBgNVBAMMJUF0b3Mg
VHJ1c3RlZFJvb3QgUm9vdCBDQSBFQ0MgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYT
AkRFMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEloZYKDcKZ9Cg3iQZGeHkBQcfl+3oZIK59sRxUM6K
DP/XtXa7oWyTbIOiaG6l2b4siJVBzV3dscqDY4PMwL502eCdpO5KTlbgmClBk1IQ1SQ4AjJn8ZQS
b+/Xxd4u/RmAo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR2KCXWfeBmmnoJsmo7jjPX
NtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIwW5kp85wxtolrbNa9d+F851F+
uDrNozZffPc8dz7kUK2o59JZDCaOMDtuCCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGY
a3cpetskz2VAv9LcjBHo9H1/IISpQuQo
-----END CERTIFICATE-----

Atos TrustedRoot Root CA RSA TLS 2021
=====================================
-----BEGIN CERTIFICATE-----
MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBMMS4wLAYDVQQD
DCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIxMQ0wCwYDVQQKDARBdG9zMQsw
CQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00MTA0MTcwOTIxMDlaMEwxLjAsBgNVBAMMJUF0
b3MgVHJ1c3RlZFJvb3QgUm9vdCBDQSBSU0EgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNV
BAYTAkRFMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtoAOxHm9BYx9sKOdTSJNy/BB
l01Z4NH+VoyX8te9j2y3I49f1cTYQcvyAh5x5en2XssIKl4w8i1mx4QbZFc4nXUtVsYvYe+W/CBG
vevUez8/fEc4BKkbqlLfEzfTFRVOvV98r61jx3ncCHvVoOX3W3WsgFWZkmGbzSoXfduP9LVq6hdK
ZChmFSlsAvFr1bqjM9xaZ6cF4r9lthawEO3NUDPJcFDsGY6wx/J0W2tExn2WuZgIWWbeKQGb9Cpt
0xU6kGpn8bRrZtkh68rZYnxGEFzedUlnnkL5/nWpo63/dgpnQOPF943HhZpZnmKaau1Fh5hnstVK
PNe0OwANwI8f4UDErmwh3El+fsqyjW22v5MvoVw+j8rtgI5Y4dtXz4U2OLJxpAmMkokIiEjxQGMY
sluMWuPD0xeqqxmjLBvk1cbiZnrXghmmOxYsL3GHX0WelXOTwkKBIROW1527k2gV+p2kHYzygeBY
Br3JtuP2iV2J+axEoctr+hbxx1A9JNr3w+SH1VbxT5Aw+kUJWdo0zuATHAR8ANSbhqRAvNncTFd+
rrcztl524WWLZt+NyteYr842mIycg5kDcPOvdO3GDjbnvezBc6eUWsuSZIKmAMFwoW4sKeFYV+xa
fJlrJaSQOoD0IJ2azsct+bJLKZWD6TWNp0lIpw9MGZHQ9b8Q4HECAwEAAaNCMEAwDwYDVR0TAQH/
BAUwAwEB/zAdBgNVHQ4EFgQUdEmZ0f+0emhFdcN+tNzMzjkz2ggwDgYDVR0PAQH/BAQDAgGGMA0G
CSqGSIb3DQEBDAUAA4ICAQAjQ1MkYlxt/T7Cz1UAbMVWiLkO3TriJQ2VSpfKgInuKs1l+NsW4AmS
4BjHeJi78+xCUvuppILXTdiK/ORO/auQxDh1MoSf/7OwKwIzNsAQkG8dnK/haZPso0UvFJ/1TCpl
Q3IM98P4lYsU84UgYt1UU90s3BiVaU+DR3BAM1h3Egyi61IxHkzJqM7F78PRreBrAwA0JrRUITWX
AdxfG/F851X6LWh3e9NpzNMOa7pNdkTWwhWaJuywxfW70Xp0wmzNxbVe9kzmWy2B27O3Opee7c9G
slA9hGCZcbUztVdF5kJHdWoOsAgMrr3e97sPWD2PAzHoPYJQyi9eDF20l74gNAf0xBLh7tew2Vkt
afcxBPTy+av5EzH4AXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9q
TFsR0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuYo7Ey7Nmj
1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5dDTedk+SKlOxJTnbPP/l
PqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcEoji2jbDwN/zIIX8/syQbPYtuzE2wFg2W
HYMfRsCbvUOZ58SWLs5fyQ==
-----END CERTIFICATE-----

TrustAsia Global Root CA G3
===========================
-----BEGIN CERTIFICATE-----
MIIFpTCCA42gAwIBAgIUZPYOZXdhaqs7tOqFhLuxibhxkw8wDQYJKoZIhvcNAQEMBQAwWjELMAkG
A1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xJDAiBgNVBAMM
G1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHMzAeFw0yMTA1MjAwMjEwMTlaFw00NjA1MTkwMjEw
MTlaMFoxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMu
MSQwIgYDVQQDDBtUcnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzMwggIiMA0GCSqGSIb3DQEBAQUA
A4ICDwAwggIKAoICAQDAMYJhkuSUGwoqZdC+BqmHO1ES6nBBruL7dOoKjbmzTNyPtxNST1QY4Sxz
lZHFZjtqz6xjbYdT8PfxObegQ2OwxANdV6nnRM7EoYNl9lA+sX4WuDqKAtCWHwDNBSHvBm3dIZwZ
Q0WhxeiAysKtQGIXBsaqvPPW5vxQfmZCHzyLpnl5hkA1nyDvP+uLRx+PjsXUjrYsyUQE49RDdT/V
P68czH5GX6zfZBCK70bwkPAPLfSIC7Epqq+FqklYqL9joDiR5rPmd2jE+SoZhLsO4fWvieylL1Ag
dB4SQXMeJNnKziyhWTXAyB1GJ2Faj/lN03J5Zh6fFZAhLf3ti1ZwA0pJPn9pMRJpxx5cynoTi+jm
9WAPzJMshH/x/Gr8m0ed262IPfN2dTPXS6TIi/n1Q1hPy8gDVI+lhXgEGvNz8teHHUGf59gXzhqc
D0r83ERoVGjiQTz+LISGNzzNPy+i2+f3VANfWdP3kXjHi3dqFuVJhZBFcnAvkV34PmVACxmZySYg
WmjBNb9Pp1Hx2BErW+Canig7CjoKH8GB5S7wprlppYiU5msTf9FkPz2ccEblooV7WIQn3MSAPmea
mseaMQ4w7OYXQJXZRe0Blqq/DPNL0WP3E1jAuPP6Z92bfW1K/zJMtSU7/xxnD4UiWQWRkUF3gdCF
TIcQcf+eQxuulXUtgQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEDk5PIj
7zjKsK5Xf/IhMBY027ySMB0GA1UdDgQWBBRA5OTyI+84yrCuV3/yITAWNNu8kjAOBgNVHQ8BAf8E
BAMCAQYwDQYJKoZIhvcNAQEMBQADggIBACY7UeFNOPMyGLS0XuFlXsSUT9SnYaP4wM8zAQLpw6o1
D/GUE3d3NZ4tVlFEbuHGLige/9rsR82XRBf34EzC4Xx8MnpmyFq2XFNFV1pF1AWZLy4jVe5jaN/T
G3inEpQGAHUNcoTpLrxaatXeL1nHo+zSh2bbt1S1JKv0Q3jbSwTEb93mPmY+KfJLaHEih6D4sTNj
duMNhXJEIlU/HHzp/LgV6FL6qj6jITk1dImmasI5+njPtqzn59ZW/yOSLlALqbUHM/Q4X6RJpstl
cHboCoWASzY9M/eVVHUl2qzEc4Jl6VL1XP04lQJqaTDFHApXB64ipCz5xUG3uOyfT0gA+QEEVcys
+TIxxHWVBqB/0Y0n3bOppHKH/lmLmnp0Ft0WpWIp6zqW3IunaFnT63eROfjXy9mPX1onAX1daBli
2MjN9LdyR75bl87yraKZk62Uy5P2EgmVtqvXO9A/EcswFi55gORngS1d7XB4tmBZrOFdRWOPyN9y
aFvqHbgB8X7754qz41SgOAngPN5C8sLtLpvzHzW2NtjjgKGLzZlkD8Kqq7HK9W+eQ42EVJmzbsAS
ZthwEPEGNTNDqJwuuhQxzhB/HIbjj9LV+Hfsm6vxL2PZQl/gZ4FkkfGXL/xuJvYz+NO1+MRiqzFR
JQJ6+N1rZdVtTTDIZbpoFGWsJwt0ivKH
-----END CERTIFICATE-----

TrustAsia Global Root CA G4
===========================
-----BEGIN CERTIFICATE-----
MIICVTCCAdygAwIBAgIUTyNkuI6XY57GU4HBdk7LKnQV1tcwCgYIKoZIzj0EAwMwWjELMAkGA1UE
BhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xJDAiBgNVBAMMG1Ry
dXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHNDAeFw0yMTA1MjAwMjEwMjJaFw00NjA1MTkwMjEwMjJa
MFoxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQw
IgYDVQQDDBtUcnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi
AATxs8045CVD5d4ZCbuBeaIVXxVjAd7Cq92zphtnS4CDr5nLrBfbK5bKfFJV4hrhPVbwLxYI+hW8
m7tH5j/uqOFMjPXTNvk4XatwmkcN4oFBButJ+bAp3TPsUKV/eSm4IJijYzBhMA8GA1UdEwEB/wQF
MAMBAf8wHwYDVR0jBBgwFoAUpbtKl86zK3+kMd6Xg1mDpm9xy94wHQYDVR0OBBYEFKW7SpfOsyt/
pDHel4NZg6ZvccveMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjBe8usGzEkxn0AA
bbd+NvBNEU/zy4k6LHiRUKNbwMp1JvK/kF0LgoxgKJ/GcJpo5PECMFxYDlZ2z1jD1xCMuo6u47xk
dUfFVZDj/bpV6wfEU6s3qe4hsiFbYI89MvHVI5TWWA==
-----END CERTIFICATE-----

Telekom Security TLS ECC Root 2020
==================================
-----BEGIN CERTIFICATE-----
MIICQjCCAcmgAwIBAgIQNjqWjMlcsljN0AFdxeVXADAKBggqhkjOPQQDAzBjMQswCQYDVQQGEwJE
RTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBHbWJIMSswKQYDVQQDDCJUZWxl
a29tIFNlY3VyaXR5IFRMUyBFQ0MgUm9vdCAyMDIwMB4XDTIwMDgyNTA3NDgyMFoXDTQ1MDgyNTIz
NTk1OVowYzELMAkGA1UEBhMCREUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkg
R21iSDErMCkGA1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgRUNDIFJvb3QgMjAyMDB2MBAGByqG
SM49AgEGBSuBBAAiA2IABM6//leov9Wq9xCazbzREaK9Z0LMkOsVGJDZos0MKiXrPk/OtdKPD/M1
2kOLAoC+b1EkHQ9rK8qfwm9QMuU3ILYg/4gND21Ju9sGpIeQkpT0CdDPf8iAC8GXs7s1J8nCG6NC
MEAwHQYDVR0OBBYEFONyzG6VmUex5rNhTNHLq+O6zd6fMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P
AQH/BAQDAgEGMAoGCCqGSM49BAMDA2cAMGQCMHVSi7ekEE+uShCLsoRbQuHmKjYC2qBuGT8lv9pZ
Mo7k+5Dck2TOrbRBR2Diz6fLHgIwN0GMZt9Ba9aDAEH9L1r3ULRn0SyocddDypwnJJGDSA3PzfdU
ga/sf+Rn27iQ7t0l
-----END CERTIFICATE-----

Telekom Security TLS RSA Root 2023
==================================
-----BEGIN CERTIFICATE-----
MIIFszCCA5ugAwIBAgIQIZxULej27HF3+k7ow3BXlzANBgkqhkiG9w0BAQwFADBjMQswCQYDVQQG
EwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBHbWJIMSswKQYDVQQDDCJU
ZWxla29tIFNlY3VyaXR5IFRMUyBSU0EgUm9vdCAyMDIzMB4XDTIzMDMyODEyMTY0NVoXDTQ4MDMy
NzIzNTk1OVowYzELMAkGA1UEBhMCREUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJp
dHkgR21iSDErMCkGA1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgUlNBIFJvb3QgMjAyMzCCAiIw
DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAO01oYGA88tKaVvC+1GDrib94W7zgRJ9cUD/h3VC
KSHtgVIs3xLBGYSJwb3FKNXVS2xE1kzbB5ZKVXrKNoIENqil/Cf2SfHVcp6R+SPWcHu79ZvB7JPP
GeplfohwoHP89v+1VmLhc2o0mD6CuKyVU/QBoCcHcqMAU6DksquDOFczJZSfvkgdmOGjup5czQRx
UX11eKvzWarE4GC+j4NSuHUaQTXtvPM6Y+mpFEXX5lLRbtLevOP1Czvm4MS9Q2QTps70mDdsipWo
l8hHD/BeEIvnHRz+sTugBTNoBUGCwQMrAcjnj02r6LX2zWtEtefdi+zqJbQAIldNsLGyMcEWzv/9
FIS3R/qy8XDe24tsNlikfLMR0cN3f1+2JeANxdKz+bi4d9s3cXFH42AYTyS2dTd4uaNir73Jco4v
zLuu2+QVUhkHM/tqty1LkCiCc/4YizWN26cEar7qwU02OxY2kTLvtkCJkUPg8qKrBC7m8kwOFjQg
rIfBLX7JZkcXFBGk8/ehJImr2BrIoVyxo/eMbcgByU/J7MT8rFEz0ciD0cmfHdRHNCk+y7AO+oML
KFjlKdw/fKifybYKu6boRhYPluV75Gp6SG12mAWl3G0eQh5C2hrgUve1g8Aae3g1LDj1H/1Joy7S
WWO/gLCMk3PLNaaZlSJhZQNg+y+TS/qanIA7AgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAdBgNV
HQ4EFgQUtqeXgj10hZv3PJ+TmpV5dVKMbUcwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS2
p5eCPXSFm/c8n5OalXl1UoxtRzANBgkqhkiG9w0BAQwFAAOCAgEAqMxhpr51nhVQpGv7qHBFfLp+
sVr8WyP6Cnf4mHGCDG3gXkaqk/QeoMPhk9tLrbKmXauw1GLLXrtm9S3ul0A8Yute1hTWjOKWi0Fp
kzXmuZlrYrShF2Y0pmtjxrlO8iLpWA1WQdH6DErwM807u20hOq6OcrXDSvvpfeWxm4bu4uB9tPcy
/SKE8YXJN3nptT+/XOR0so8RYgDdGGah2XsjX/GO1WfoVNpbOms2b/mBsTNHM3dA+VKq3dSDz4V4
mZqTuXNnQkYRIer+CqkbGmVps4+uFrb2S1ayLfmlyOw7YqPta9BO1UAJpB+Y1zqlklkg5LB9zVtz
aL1txKITDmcZuI1CfmwMmm6gJC3VRRvcxAIU/oVbZZfKTpBQCHpCNfnqwmbU+AGuHrS+w6jv/naa
oqYfRvaE7fzbzsQCzndILIyy7MMAo+wsVRjBfhnu4S/yrYObnqsZ38aKL4x35bcF7DvB7L6Gs4a8
wPfc5+pbrrLMtTWGS9DiP7bY+A4A7l3j941Y/8+LN+ljX273CXE2whJdV/LItM3z7gLfEdxquVeE
HVlNjM7IDiPCtyaaEBRx/pOyiriA8A4QntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0
o82bNSQ3+pCTE4FCxpgmdTdmQRCsu/WU48IxK63nI1bMNSWSs1A=
-----END CERTIFICATE-----

FIRMAPROFESIONAL CA ROOT-A WEB
==============================
-----BEGIN CERTIFICATE-----
MIICejCCAgCgAwIBAgIQMZch7a+JQn81QYehZ1ZMbTAKBggqhkjOPQQDAzBuMQswCQYDVQQGEwJF
UzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UEYQwPVkFURVMtQTYyNjM0MDY4
MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENBIFJPT1QtQSBXRUIwHhcNMjIwNDA2MDkwMTM2
WhcNNDcwMzMxMDkwMTM2WjBuMQswCQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25h
bCBTQTEYMBYGA1UEYQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFM
IENBIFJPT1QtQSBXRUIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARHU+osEaR3xyrq89Zfe9MEkVz6
iMYiuYMQYneEMy3pA4jU4DP37XcsSmDq5G+tbbT4TIqk5B/K6k84Si6CcyvHZpsKjECcfIr28jlg
st7L7Ljkb+qbXbdTkBgyVcUgt5SjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUk+FD
Y1w8ndYn81LsF7Kpryz3dvgwHQYDVR0OBBYEFJPhQ2NcPJ3WJ/NS7Beyqa8s93b4MA4GA1UdDwEB
/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjAdfKR7w4l1M+E7qUW/Runpod3JIha3RxEL2Jq68cgL
cFBTApFwhVmpHqTm6iMxoAACMQD94vizrxa5HnPEluPBMBnYfubDl94cT7iJLzPrSA8Z94dGXSaQ
pYXFuXqUPoeovQA=
-----END CERTIFICATE-----

TWCA CYBER Root CA
==================
-----BEGIN CERTIFICATE-----
MIIFjTCCA3WgAwIBAgIQQAE0jMIAAAAAAAAAATzyxjANBgkqhkiG9w0BAQwFADBQMQswCQYDVQQG
EwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NB
IENZQkVSIFJvb3QgQ0EwHhcNMjIxMTIyMDY1NDI5WhcNNDcxMTIyMTU1OTU5WjBQMQswCQYDVQQG
EwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NB
IENZQkVSIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDG+Moe2Qkgfh1s
Ts6P40czRJzHyWmqOlt47nDSkvgEs1JSHWdyKKHfi12VCv7qze33Kc7wb3+szT3vsxxFavcokPFh
V8UMxKNQXd7UtcsZyoC5dc4pztKFIuwCY8xEMCDa6pFbVuYdHNWdZsc/34bKS1PE2Y2yHer43CdT
o0fhYcx9tbD47nORxc5zb87uEB8aBs/pJ2DFTxnk684iJkXXYJndzk834H/nY62wuFm40AZoNWDT
Nq5xQwTxaWV4fPMf88oon1oglWa0zbfuj3ikRRjpJi+NmykosaS3Om251Bw4ckVYsV7r8Cibt4LK
/c/WMw+f+5eesRycnupfXtuq3VTpMCEobY5583WSjCb+3MX2w7DfRFlDo7YDKPYIMKoNM+HvnKkH
IuNZW0CP2oi3aQiotyMuRAlZN1vH4xfyIutuOVLF3lSnmMlLIJXcRolftBL5hSmO68gnFSDAS9TM
fAxsNAwmmyYxpjyn9tnQS6Jk/zuZQXLB4HCX8SS7K8R0IrGsayIyJNN4KsDAoS/xUgXJP+92ZuJF
2A09rZXIx4kmyA+upwMu+8Ff+iDhcK2wZSA3M2Cw1a/XDBzCkHDXShi8fgGwsOsVHkQGzaRP6AzR
wyAQ4VRlnrZR0Bp2a0JaWHY06rc3Ga4udfmW5cFZ95RXKSWNOkyrTZpB0F8mAwIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSdhWEUfMFib5do5E83
QOGt4A1WNzAdBgNVHQ4EFgQUnYVhFHzBYm+XaORPN0DhreANVjcwDQYJKoZIhvcNAQEMBQADggIB
AGSPesRiDrWIzLjHhg6hShbNcAu3p4ULs3a2D6f/CIsLJc+o1IN1KriWiLb73y0ttGlTITVX1olN
c79pj3CjYcya2x6a4CD4bLubIp1dhDGaLIrdaqHXKGnK/nZVekZn68xDiBaiA9a5F/gZbG0jAn/x
X9AKKSM70aoK7akXJlQKTcKlTfjF/biBzysseKNnTKkHmvPfXvt89YnNdJdhEGoHK4Fa0o635yDR
IG4kqIQnoVesqlVYL9zZyvpoBJ7tRCT5dEA7IzOrg1oYJkK2bVS1FmAwbLGg+LhBoF1JSdJlBTrq
/p1hvIbZv97Tujqxf36SNI7JAG7cmL3c7IAFrQI932XtCwP39xaEBDG6k5TY8hL4iuO/Qq+n1M0R
FxbIQh0UqEL20kCGoE8jypZFVmAGzbdVAaYBlGX+bgUJurSkquLvWL69J1bY73NxW0Qz8ppy6rBe
Pm6pUlvscG21h483XjyMnM7k8M4MZ0HMzvaAq07MTFb1wWFZk7Q+ptq4NxKfKjLji7gh7MMrZQzv
It6IKTtM1/r+t+FHvpw+PoP7UV31aPcuIYXcv/Fa4nzXxeSDwWrruoBa3lwtcHb4yOWHh8qgnaHl
IhInD0Q9HWzq1MKLL295q39QpsQZp6F6t5b5wR9iWqJDB0BeJsas7a5wFsWqynKKTbDPAYsDP27X
-----END CERTIFICATE-----

SecureSign Root CA12
====================
-----BEGIN CERTIFICATE-----
MIIDcjCCAlqgAwIBAgIUZvnHwa/swlG07VOX5uaCwysckBYwDQYJKoZIhvcNAQELBQAwUTELMAkG
A1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMR0wGwYDVQQDExRT
ZWN1cmVTaWduIFJvb3QgQ0ExMjAeFw0yMDA0MDgwNTM2NDZaFw00MDA0MDgwNTM2NDZaMFExCzAJ
BgNVBAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMU
U2VjdXJlU2lnbiBSb290IENBMTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6OcE3
emhFKxS06+QT61d1I02PJC0W6K6OyX2kVzsqdiUzg2zqMoqUm048luT9Ub+ZyZN+v/mtp7JIKwcc
J/VMvHASd6SFVLX9kHrko+RRWAPNEHl57muTH2SOa2SroxPjcf59q5zdJ1M3s6oYwlkm7Fsf0uZl
fO+TvdhYXAvA42VvPMfKWeP+bl+sg779XSVOKik71gurFzJ4pOE+lEa+Ym6b3kaosRbnhW70CEBF
EaCeVESE99g2zvVQR9wsMJvuwPWW0v4JhscGWa5Pro4RmHvzC1KqYiaqId+OJTN5lxZJjfU+1Uef
NzFJM3IFTQy2VYzxV4+Kh9GtxRESOaCtAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P
AQH/BAQDAgEGMB0GA1UdDgQWBBRXNPN0zwRL1SXm8UC2LEzZLemgrTANBgkqhkiG9w0BAQsFAAOC
AQEAPrvbFxbS8hQBICw4g0utvsqFepq2m2um4fylOqyttCg6r9cBg0krY6LdmmQOmFxv3Y67ilQi
LUoT865AQ9tPkbeGGuwAtEGBpE/6aouIs3YIcipJQMPTw4WJmBClnW8Zt7vPemVV2zfrPIpyMpce
mik+rY3moxtt9XUa5rBouVui7mlHJzWhhpmA8zNL4WukJsPvdFlseqJkth5Ew1DgDzk9qTPxpfPS
vWKErI4cqc1avTc7bgoitPQV55FYxTpE05Uo2cBl6XLK0A+9H7MV2anjpEcJnuDLN/v9vZfVvhga
aaI5gdka9at/yOPiZwud9AzqVN/Ssq+xIvEg37xEHA==
-----END CERTIFICATE-----

SecureSign Root CA14
====================
-----BEGIN CERTIFICATE-----
MIIFcjCCA1qgAwIBAgIUZNtaDCBO6Ncpd8hQJ6JaJ90t8sswDQYJKoZIhvcNAQEMBQAwUTELMAkG
A1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMR0wGwYDVQQDExRT
ZWN1cmVTaWduIFJvb3QgQ0ExNDAeFw0yMDA0MDgwNzA2MTlaFw00NTA0MDgwNzA2MTlaMFExCzAJ
BgNVBAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMU
U2VjdXJlU2lnbiBSb290IENBMTQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDF0nqh
1oq/FjHQmNE6lPxauG4iwWL3pwon71D2LrGeaBLwbCRjOfHw3xDG3rdSINVSW0KZnvOgvlIfX8xn
bacuUKLBl422+JX1sLrcneC+y9/3OPJH9aaakpUqYllQC6KxNedlsmGy6pJxaeQp8E+BgQQ8sqVb
1MWoWWd7VRxJq3qdwudzTe/NCcLEVxLbAQ4jeQkHO6Lo/IrPj8BGJJw4J+CDnRugv3gVEOuGTgpa
/d/aLIJ+7sr2KeH6caH3iGicnPCNvg9JkdjqOvn90Ghx2+m1K06Ckm9mH+Dw3EzsytHqunQG+bOE
kJTRX45zGRBdAuVwpcAQ0BB8b8VYSbSwbprafZX1zNoCr7gsfXmPvkPx+SgojQlD+Ajda8iLLCSx
jVIHvXiby8posqTdDEx5YMaZ0ZPxMBoH064iwurO8YQJzOAUbn8/ftKChazcqRZOhaBgy/ac18iz
ju3Gm5h1DVXoX+WViwKkrkMpKBGk5hIwAUt1ax5mnXkvpXYvHUC0bcl9eQjs0Wq2XSqypWa9a4X0
dFbD9ed1Uigspf9mR6XU/v6eVL9lfgHWMI+lNpyiUBzuOIABSMbHdPTGrMNASRZhdCyvjG817XsY
AFs2PJxQDcqSMxDxJklt33UkN4Ii1+iW/RVLApY+B3KVfqs9TC7XyvDf4Fg/LS8EmjijAQIDAQAB
o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUBpOjCl4oaTeq
YR3r6/wtbyPk86AwDQYJKoZIhvcNAQEMBQADggIBAJaAcgkGfpzMkwQWu6A6jZJOtxEaCnFxEM0E
rX+lRVAQZk5KQaID2RFPeje5S+LGjzJmdSX7684/AykmjbgWHfYfM25I5uj4V7Ibed87hwriZLoA
ymzvftAj63iP/2SbNDefNWWipAA9EiOWWF3KY4fGoweITedpdopTzfFP7ELyk+OZpDc8h7hi2/Ds
Hzc/N19DzFGdtfCXwreFamgLRB7lUe6TzktuhsHSDCRZNhqfLJGP4xjblJUK7ZGqDpncllPjYYPG
FrojutzdfhrGe0K22VoF3Jpf1d+42kd92jjbrDnVHmtsKheMYc2xbXIBw8MgAGJoFjHVdqqGuw6q
nsb58Nn4DSEC5MUoFlkRudlpcyqSeLiSV5sI8jrlL5WwWLdrIBRtFO8KvH7YVdiI2i/6GaX7i+B/
OfVyK4XELKzvGUWSTLNhB9xNH27SgRNcmvMSZ4PPmz+Ln52kuaiWA3rF7iDeM9ovnhp6dB7h7sxa
OgTdsxoEqBRjrLdHEoOabPXm6RUVkRqEGQ6UROcSjiVbgGcZ3GOTEAtlLor6CZpO2oYofaphNdgO
pygau1LgePhsumywbrmHXumZNTfxPWQrqaA0k89jL9WB365jJ6UeTo3cKXhZ+PmhIIynJkBugnLN
eLLIjzwec+fBH7/PzqUqm9tEZDKgu39cJRNItX+S
-----END CERTIFICATE-----

SecureSign Root CA15
====================
-----BEGIN CERTIFICATE-----
MIICIzCCAamgAwIBAgIUFhXHw9hJp75pDIqI7fBw+d23PocwCgYIKoZIzj0EAwMwUTELMAkGA1UE
BhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMR0wGwYDVQQDExRTZWN1
cmVTaWduIFJvb3QgQ0ExNTAeFw0yMDA0MDgwODMyNTZaFw00NTA0MDgwODMyNTZaMFExCzAJBgNV
BAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2Vj
dXJlU2lnbiBSb290IENBMTUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQLUHSNZDKZmbPSYAi4Io5G
dCx4wCtELW1fHcmuS1Iggz24FG1Th2CeX2yF2wYUleDHKP+dX+Sq8bOLbe1PL0vJSpSRZHX+AezB
2Ot6lHhWGENfa4HL9rzatAy2KZMIaY+jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD
AgEGMB0GA1UdDgQWBBTrQciu/NWeUUj1vYv0hyCTQSvT9DAKBggqhkjOPQQDAwNoADBlAjEA2S6J
fl5OpBEHvVnCB96rMjhTKkZEBhd6zlHp4P9mLQlO4E/0BdGF9jVg3PVys0Z9AjBEmEYagoUeYWmJ
SwdLZrWeqrqgHkHZAXQ6bkU6iYAZezKYVWOr62Nuk22rGwlgMU4=
-----END CERTIFICATE-----

D-TRUST BR Root CA 2 2023
=========================
-----BEGIN CERTIFICATE-----
MIIFqTCCA5GgAwIBAgIQczswBEhb2U14LnNLyaHcZjANBgkqhkiG9w0BAQ0FADBIMQswCQYDVQQG
EwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEJSIFJvb3QgQ0Eg
MiAyMDIzMB4XDTIzMDUwOTA4NTYzMVoXDTM4MDUwOTA4NTYzMFowSDELMAkGA1UEBhMCREUxFTAT
BgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDIgMjAyMzCC
AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK7/CVmRgApKaOYkP7in5Mg6CjoWzckjYaCT
cfKri3OPoGdlYNJUa2NRb0kz4HIHE304zQaSBylSa053bATTlfrdTIzZXcFhfUvnKLNEgXtRr90z
sWh81k5M/itoucpmacTsXld/9w3HnDY25QdgrMBM6ghs7wZ8T1soegj8k12b9py0i4a6Ibn08OhZ
WiihNIQaJZG2tY/vsvmA+vk9PBFy2OMvhnbFeSzBqZCTRphny4NqoFAjpzv2gTng7fC5v2Xx2Mt6
++9zA84A9H3X4F07ZrjcjrqDy4d2A/wl2ecjbwb9Z/Pg/4S8R7+1FhhGaRTMBffb00msa8yr5LUL
QyReS2tNZ9/WtT5PeB+UcSTq3nD88ZP+npNa5JRal1QMNXtfbO4AHyTsA7oC9Xb0n9Sa7YUsOCIv
x9gvdhFP/Wxc6PWOJ4d/GUohR5AdeY0cW/jPSoXk7bNbjb7EZChdQcRurDhaTyN0dKkSw/bSuREV
MweR2Ds3OmMwBtHFIjYoYiMQ4EbMl6zWK11kJNXuHA7e+whadSr2Y23OC0K+0bpwHJwh5Q8xaRfX
/Aq03u2AnMuStIv13lmiWAmlY0cL4UEyNEHZmrHZqLAbWt4NDfTisl01gLmB1IRpkQLLddCNxbU9
CZEJjxShFHR5PtbJFR2kWVki3PaKRT08EtY+XTIvAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB
/zAdBgNVHQ4EFgQUZ5Dw1t61GNVGKX5cq/ieCLxklRAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRC
MEAwPqA8oDqGOGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfYnJfcm9vdF9jYV8y
XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQA097N3U9swFrktpSHxQCF16+tIFoE9c+CeJyrr
d6kTpGoKWloUMz1oH4Guaf2Mn2VsNELZLdB/eBaxOqwjMa1ef67nriv6uvw8l5VAk1/DLQOj7aRv
U9f6QA4w9QAgLABMjDu0ox+2v5Eyq6+SmNMW5tTRVFxDWy6u71cqqLRvpO8NVhTaIasgdp4D/Ca4
nj8+AybmTNudX0KEPUUDAxxZiMrcLmEkWqTqJwtzEr5SswrPMhfiHocaFpVIbVrg0M8JkiZmkdij
YQ6qgYF/6FKC0ULn4B0Y+qSFNueG4A3rvNTJ1jxD8V1Jbn6Bm2m1iWKPiFLY1/4nwSPFyysCu7Ff
/vtDhQNGvl3GyiEm/9cCnnRK3PgTFbGBVzbLZVzRHTF36SXDw7IyN9XxmAnkbWOACKsGkoHU6XCP
pz+y7YaMgmo1yEJagtFSGkUPFaUA8JR7ZSdXOUPPfH/mvTWze/EZTN46ls/pdu4D58JDUjxqgejB
WoC9EV2Ta/vH5mQ/u2kc6d0li690yVRAysuTEwrt+2aSEcr1wPrYg1UDfNPFIkZ1cGt5SAYqgpq/
5usWDiJFAbzdNpQ0qTUmiteXue4Icr80knCDgKs4qllo3UCkGJCy89UDyibK79XH4I9TjvAA46jt
n/mtd+ArY0+ew+43u3gJhJ65bvspmZDogNOfJA==
-----END CERTIFICATE-----

TrustAsia TLS ECC Root CA
=========================
-----BEGIN CERTIFICATE-----
MIICMTCCAbegAwIBAgIUNnThTXxlE8msg1UloD5Sfi9QaMcwCgYIKoZIzj0EAwMwWDELMAkGA1UE
BhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xIjAgBgNVBAMTGVRy
dXN0QXNpYSBUTFMgRUNDIFJvb3QgQ0EwHhcNMjQwNTE1MDU0MTU2WhcNNDQwNTE1MDU0MTU1WjBY
MQswCQYDVQQGEwJDTjElMCMGA1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEiMCAG
A1UEAxMZVHJ1c3RBc2lhIFRMUyBFQ0MgUm9vdCBDQTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLh/
pVs/AT598IhtrimY4ZtcU5nb9wj/1WrgjstEpvDBjL1P1M7UiFPoXlfXTr4sP/MSpwDpguMqWzJ8
S5sUKZ74LYO1644xST0mYekdcouJtgq7nDM1D9rs3qlKH8kzsaNCMEAwDwYDVR0TAQH/BAUwAwEB
/zAdBgNVHQ4EFgQULIVTu7FDzTLqnqOH/qKYqKaT6RAwDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49
BAMDA2gAMGUCMFRH18MtYYZI9HlaVQ01L18N9mdsd0AaRuf4aFtOJx24mH1/k78ITcTaRTChD15K
eAIxAKORh/IRM4PDwYqROkwrULG9IpRdNYlzg8WbGf60oenUoWa2AaU2+dhoYSi3dOGiMQ==
-----END CERTIFICATE-----

TrustAsia TLS RSA Root CA
=========================
-----BEGIN CERTIFICATE-----
MIIFgDCCA2igAwIBAgIUHBjYz+VTPyI1RlNUJDxsR9FcSpwwDQYJKoZIhvcNAQEMBQAwWDELMAkG
A1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xIjAgBgNVBAMT
GVRydXN0QXNpYSBUTFMgUlNBIFJvb3QgQ0EwHhcNMjQwNTE1MDU0MTU3WhcNNDQwNTE1MDU0MTU2
WjBYMQswCQYDVQQGEwJDTjElMCMGA1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEi
MCAGA1UEAxMZVHJ1c3RBc2lhIFRMUyBSU0EgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIP
ADCCAgoCggIBAMMWuBtqpERz5dZO9LnPWwvB0ZqB9WOwj0PBuwhaGnrhB3YmH49pVr7+NmDQDIPN
lOrnxS1cLwUWAp4KqC/lYCZUlviYQB2srp10Zy9U+5RjmOMmSoPGlbYJQ1DNDX3eRA5gEk9bNb2/
mThtfWza4mhzH/kxpRkQcwUqwzIZheo0qt1CHjCNP561HmHVb70AcnKtEj+qpklz8oYVlQwQX1Fk
zv93uMltrOXVmPGZLmzjyUT5tUMnCE32ft5EebuyjBza00tsLtbDeLdM1aTk2tyKjg7/D8OmYCYo
zza/+lcK7Fs/6TAWe8TbxNRkoDD75f0dcZLdKY9BWN4ArTr9PXwaqLEX8E40eFgl1oUh63kd0Nyr
z2I8sMeXi9bQn9P+PN7F4/w6g3CEIR0JwqH8uyghZVNgepBtljhb//HXeltt08lwSUq6HTrQUNoy
IBnkiz/r1RYmNzz7dZ6wB3C4FGB33PYPXFIKvF1tjVEK2sUYyJtt3LCDs3+jTnhMmCWr8n4uIF6C
FabW2I+s5c0yhsj55NqJ4js+k8UTav/H9xj8Z7XvGCxUq0DTbE3txci3OE9kxJRMT6DNrqXGJyV1
J23G2pyOsAWZ1SgRxSHUuPzHlqtKZFlhaxP8S8ySpg+kUb8OWJDZgoM5pl+z+m6Ss80zDoWo8SnT
q1mt1tve1CuBAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLgHkXlcBvRG/XtZ
ylomkadFK/hTMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQwFAAOCAgEAIZtqBSBdGBanEqT3
Rz/NyjuujsCCztxIJXgXbODgcMTWltnZ9r96nBO7U5WS/8+S4PPFJzVXqDuiGev4iqME3mmL5Dw8
veWv0BIb5Ylrc5tvJQJLkIKvQMKtuppgJFqBTQUYo+IzeXoLH5Pt7DlK9RME7I10nYEKqG/odv6L
TytpEoYKNDbdgptvT+Bz3Ul/KD7JO6NXBNiT2Twp2xIQaOHEibgGIOcberyxk2GaGUARtWqFVwHx
tlotJnMnlvm5P1vQiJ3koP26TpUJg3933FEFlJ0gcXax7PqJtZwuhfG5WyRasQmr2soaB82G39tp
27RIGAAtvKLEiUUjpQ7hRGU+isFqMB3iYPg6qocJQrmBktwliJiJ8Xw18WLK7nn4GS/+X/jbh87q
qA8MpugLoDzga5SYnH+tBuYc6kIQX+ImFTw3OffXvO645e8D7r0i+yiGNFjEWn9hongPXvPKnbwb
PKfILfanIhHKA9jnZwqKDss1jjQ52MjqjZ9k4DewbNfFj8GQYSbbJIweSsCI3zWQzj8C9GRh3sfI
B5XeMhg6j6JCQCTl1jNdfK7vsU1P1FeQNWrcrgSXSYk0ly4wBOeY99sLAZDBHwo/+ML+TvrbmnNz
FrwFuHnYWa8G5z9nODmxfKuU4CkUpijy323imttUQ/hHWKNddBWcwauwxzQ=
-----END CERTIFICATE-----

D-TRUST EV Root CA 2 2023
=========================
-----BEGIN CERTIFICATE-----
MIIFqTCCA5GgAwIBAgIQaSYJfoBLTKCnjHhiU19abzANBgkqhkiG9w0BAQ0FADBIMQswCQYDVQQG
EwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEVWIFJvb3QgQ0Eg
MiAyMDIzMB4XDTIzMDUwOTA5MTAzM1oXDTM4MDUwOTA5MTAzMlowSDELMAkGA1UEBhMCREUxFTAT
BgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDIgMjAyMzCC
AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANiOo4mAC7JXUtypU0w3uX9jFxPvp1sjW2l1
sJkKF8GLxNuo4MwxusLyzV3pt/gdr2rElYfXR8mV2IIEUD2BCP/kPbOx1sWy/YgJ25yE7CUXFId/
MHibaljJtnMoPDT3mfd/06b4HEV8rSyMlD/YZxBTfiLNTiVR8CUkNRFeEMbsh2aJgWi6zCudR3Mf
vc2RpHJqnKIbGKBv7FD0fUDCqDDPvXPIEysQEx6Lmqg6lHPTGGkKSv/BAQP/eX+1SH977ugpbzZM
lWGG2Pmic4ruri+W7mjNPU0oQvlFKzIbRlUWaqZLKfm7lVa/Rh3sHZMdwGWyH6FDrlaeoLGPaxK3
YG14C8qKXO0elg6DpkiVjTujIcSuWMYAsoS0I6SWhjW42J7YrDRJmGOVxcttSEfi8i4YHtAxq910
7PncjLgcjmgjutDzUNzPZY9zOjLHfP7KgiJPvo5iR2blzYfi6NUPGJ/lBHJLRjwQ8kTCZFZxTnXo
nMkmdMV9WdEKWw9t/p51HBjGGjp82A0EzM23RWV6sY+4roRIPrN6TagD4uJ+ARZZaBhDM7DS3LAa
QzXupdqpRlyuhoFBAUp0JuyfBr/CBTdkdXgpaP3F9ev+R/nkhbDhezGdpn9yo7nELC7MmVcOIQxF
AZRl62UJxmMiCzNJkkg8/M3OsD6Onov4/knFNXJHAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB
/zAdBgNVHQ4EFgQUqvyREBuHkV8Wub9PS5FeAByxMoAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRC
MEAwPqA8oDqGOGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfZXZfcm9vdF9jYV8y
XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQCTy6UfmRHsmg1fLBWTxj++EI14QvBukEdHjqOS
Mo1wj/Zbjb6JzkcBahsgIIlbyIIQbODnmaprxiqgYzWRaoUlrRc4pZt+UPJ26oUFKidBK7GB0aL2
QHWpDsvxVUjY7NHss+jOFKE17MJeNRqrphYBBo7q3C+jisosketSjl8MmxfPy3MHGcRqwnNU73xD
UmPBEcrCRbH0O1P1aa4846XerOhUt7KR/aypH/KH5BfGSah82ApB9PI+53c0BFLd6IHyTS9URZ0V
4U/M5d40VxDJI3IXcI1QcB9WbMy5/zpaT2N6w25lBx2Eof+pDGOJbbJAiDnXH3dotfyc1dZnaVuo
dNv8ifYbMvekJKZ2t0dT741Jj6m2g1qllpBFYfXeA08mD6iL8AOWsKwV0HFaanuU5nCT2vFp4LJi
TZ6P/4mdm13NRemUAiKN4DV/6PEEeXFsVIP4M7kFMhtYVRFP0OUnR3Hs7dpn1mKmS00PaaLJvOwi
S5THaJQXfuKOKD62xur1NGyfN4gHONuGcfrNlUhDbqNPgofXNJhuS5N5YHVpD/Aa1VP6IQzCP+k/
HxiMkl14p3ZnGbuy6n/pcAlWVqOwDAstNl7F6cTVg8uGF5csbBNvh1qvSaYd2804BC5f4ko1Di1L
+KIkBI3Y4WNeApI02phhXBxvWHZks/wCuPWdCg==
-----END CERTIFICATE-----

SwissSign RSA TLS Root CA 2022 - 1
==================================
-----BEGIN CERTIFICATE-----
MIIFkzCCA3ugAwIBAgIUQ/oMX04bgBhE79G0TzUfRPSA7cswDQYJKoZIhvcNAQELBQAwUTELMAkG
A1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzErMCkGA1UEAxMiU3dpc3NTaWduIFJTQSBU
TFMgUm9vdCBDQSAyMDIyIC0gMTAeFw0yMjA2MDgxMTA4MjJaFw00NzA2MDgxMTA4MjJaMFExCzAJ
BgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxKzApBgNVBAMTIlN3aXNzU2lnbiBSU0Eg
VExTIFJvb3QgQ0EgMjAyMiAtIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDLKmji
C8NXvDVjvHClO/OMPE5Xlm7DTjak9gLKHqquuN6orx122ro10JFwB9+zBvKK8i5VUXu7LCTLf5Im
gKO0lPaCoaTo+nUdWfMHamFk4saMla+ju45vVs9xzF6BYQ1t8qsCLqSX5XH8irCRIFucdFJtrhUn
WXjyCcplDn/L9Ovn3KlMd/YrFgSVrpxxpT8q2kFC5zyEEPThPYxr4iuRR1VPuFa+Rd4iUU1OKNlf
GUEGjw5NBuBwQCMBauTLE5tzrE0USJIt/m2n+IdreXXhvhCxqohAWVTXz8TQm0SzOGlkjIHRI36q
OTw7D59Ke4LKa2/KIj4x0LDQKhySio/YGZxH5D4MucLNvkEM+KRHBdvBFzA4OmnczcNpI/2aDwLO
EGrOyvi5KaM2iYauC8BPY7kGWUleDsFpswrzd34unYyzJ5jSmY0lpx+Gs6ZUcDj8fV3oT4MM0ZPl
EuRU2j7yrTrePjxF8CgPBrnh25d7mUWe3f6VWQQvdT/TromZhqwUtKiE+shdOxtYk8EXlFXIC+OC
eYSf8wCENO7cMdWP8vpPlkwGqnj73mSiI80fPsWMvDdUDrtaclXvyFu1cvh43zcgTFeRc5JzrBh3
Q4IgaezprClG5QtO+DdziZaKHG29777YtvTKwP1H8K4LWCDFyB02rpeNUIMmJCn3nTsPBQIDAQAB
o2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBRvjmKLk0Ow
4UD2p8P98Q+4DxU4pTAdBgNVHQ4EFgQUb45ii5NDsOFA9qfD/fEPuA8VOKUwDQYJKoZIhvcNAQEL
BQADggIBAKwsKUF9+lz1GpUYvyypiqkkVHX1uECry6gkUSsYP2OprphWKwVDIqO310aewCoSPY6W
lkDfDDOLazeROpW7OSltwAJsipQLBwJNGD77+3v1dj2b9l4wBlgzHqp41eZUBDqyggmNzhYzWUUo
8aWjlw5DI/0LIICQ/+Mmz7hkkeUFjxOgdg3XNwwQiJb0Pr6VvfHDffCjw3lHC1ySFWPtUnWK50Zp
y1FVCypM9fJkT6lc/2cyjlUtMoIcgC9qkfjLvH4YoiaoLqNTKIftV+Vlek4ASltOU8liNr3Cjlvr
zG4ngRhZi0Rjn9UMZfQpZX+RLOV/fuiJz48gy20HQhFRJjKKLjpHE7iNvUcNCfAWpO2Whi4Z2L6M
OuhFLhG6rlrnub+xzI/goP+4s9GFe3lmozm1O2bYQL7Pt2eLSMkZJVX8vY3PXtpOpvJpzv1/THfQ
wUY1mFwjmwJFQ5Ra3bxHrSL+ul4vkSkphnsh3m5kt8sNjzdbowhq6/TdAo9QAwKxuDdollDruF/U
KIqlIgyKhPBZLtU30WHlQnNYKoH3dtvi4k0NX/a3vgW0rk4N3hY9A4GzJl5LuEsAz/+MF7psYC0n
hzck5npgL7XTgwSqT0N1osGDsieYK7EOgLrAhV5Cud+xYJHT6xh+cHiudoO+cVrQkOPKwRYlZ0rw
tnu64ZzZ
-----END CERTIFICATE-----

OISTE Server Root ECC G1
========================
-----BEGIN CERTIFICATE-----
MIICNTCCAbqgAwIBAgIQI/nD1jWvjyhLH/BU6n6XnTAKBggqhkjOPQQDAzBLMQswCQYDVQQGEwJD
SDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UEAwwYT0lTVEUgU2VydmVyIFJvb3Qg
RUNDIEcxMB4XDTIzMDUzMTE0NDIyOFoXDTQ4MDUyNDE0NDIyN1owSzELMAkGA1UEBhMCQ0gxGTAX
BgNVBAoMEE9JU1RFIEZvdW5kYXRpb24xITAfBgNVBAMMGE9JU1RFIFNlcnZlciBSb290IEVDQyBH
MTB2MBAGByqGSM49AgEGBSuBBAAiA2IABBcv+hK8rBjzCvRE1nZCnrPoH7d5qVi2+GXROiFPqOuj
vqQycvO2Ackr/XeFblPdreqqLiWStukhEaivtUwL85Zgmjvn6hp4LrQ95SjeHIC6XG4N2xml4z+c
KrhAS93mT6NjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBQ3TYhlz/w9itWj8UnATgwQ
b0K0nDAdBgNVHQ4EFgQUN02IZc/8PYrVo/FJwE4MEG9CtJwwDgYDVR0PAQH/BAQDAgGGMAoGCCqG
SM49BAMDA2kAMGYCMQCpKjAd0MKfkFFRQD6VVCHNFmb3U2wIFjnQEnx/Yxvf4zgAOdktUyBFCxxg
ZzFDJe0CMQCSia7pXGKDYmH5LVerVrkR3SW+ak5KGoJr3M/TvEqzPNcum9v4KGm8ay3sMaE641c=
-----END CERTIFICATE-----

OISTE Server Root RSA G1
========================
-----BEGIN CERTIFICATE-----
MIIFgzCCA2ugAwIBAgIQVaXZZ5Qoxu0M+ifdWwFNGDANBgkqhkiG9w0BAQwFADBLMQswCQYDVQQG
EwJDSDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UEAwwYT0lTVEUgU2VydmVyIFJv
b3QgUlNBIEcxMB4XDTIzMDUzMTE0MzcxNloXDTQ4MDUyNDE0MzcxNVowSzELMAkGA1UEBhMCQ0gx
GTAXBgNVBAoMEE9JU1RFIEZvdW5kYXRpb24xITAfBgNVBAMMGE9JU1RFIFNlcnZlciBSb290IFJT
QSBHMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKqu9KuCz/vlNwvn1ZatkOhLKdxV
YOPMvLO8LZK55KN68YG0nnJyQ98/qwsmtO57Gmn7KNByXEptaZnwYx4M0rH/1ow00O7brEi56rAU
jtgHqSSY3ekJvqgiG1k50SeH3BzN+Puz6+mTeO0Pzjd8JnduodgsIUzkik/HEzxux9UTl7Ko2yRp
g1bTacuCErudG/L4NPKYKyqOBGf244ehHa1uzjZ0Dl4zO8vbUZeUapU8zhhabkvG/AePLhq5Svdk
NCncpo1Q4Y2LS+VIG24ugBA/5J8bZT8RtOpXaZ+0AOuFJJkk9SGdl6r7NH8CaxWQrbueWhl/pIzY
+m0o/DjH40ytas7ZTpOSjswMZ78LS5bOZmdTaMsXEY5Z96ycG7mOaES3GK/m5Q9l3JUJsJMStR8+
lKXHiHUhsd4JJCpM4rzsTGdHwimIuQq6+cF0zowYJmXa92/GjHtoXAvuY8BeS/FOzJ8vD+HomnqT
8eDI278n5mUpezbgMxVz8p1rhAhoKzYHKyfMeNhqhw5HdPSqoBNdZH702xSu+zrkL8Fl47l6QGzw
Brd7KJvX4V84c5Ss2XCTLdyEr0YconosP4EmQufU2MVshGYRi3drVByjtdgQ8K4p92cIiBdcuJd5
z+orKu5YM+Vt6SmqZQENghPsJQtdLEByFSnTkCz3GkPVavBpAgMBAAGjYzBhMA8GA1UdEwEB/wQF
MAMBAf8wHwYDVR0jBBgwFoAU8snBDw1jALvsRQ5KH7WxszbNDo0wHQYDVR0OBBYEFPLJwQ8NYwC7
7EUOSh+1sbM2zQ6NMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQwFAAOCAgEANGd5sjrG5T33
I3K5Ce+SrScfoE4KsvXaFwyihdJ+klH9FWXXXGtkFu6KRcoMQzZENdl//nk6HOjG5D1rd9QhEOP2
8yBOqb6J8xycqd+8MDoX0TJD0KqKchxRKEzdNsjkLWd9kYccnbz8qyiWXmFcuCIzGEgWUOrKL+ml
Sdx/PKQZvDatkuK59EvV6wit53j+F8Bdh3foZ3dPAGav9LEDOr4SfEE15fSmG0eLy3n31r8Xbk5l
8PjaV8GUgeV6Vg27Rn9vkf195hfkgSe7BYhW3SCl95gtkRlpMV+bMPKZrXJAlszYd2abtNUOshD+
FKrDgHGdPY3ofRRsYWSGRqbXVMW215AWRqWFyp464+YTFrYVI8ypKVL9AMb2kI5Wj4kI3Zaq5tNq
qYY19tVFeEJKRvwDyF7YZvZFZSS0vod7VSCd9521Kvy5YhnLbDuv0204bKt7ph6N/Ome/msVuduC
msuY33OhkKCgxeDoAaijFJzIwZqsFVAzje18KotzlUBDJvyBpCpfOZC3J8tRd/iWkx7P8nd9H0aT
olkelUTFLXVksNb54Dxp6gS1HAviRkRNQzuXSXERvSS2wq1yVAb+axj5d9spLFKebXd7Yv0PTY6Y
MjAwcRLWJTXjn/hvnLXrahut6hDTlhZyBiElxky8j3C7DOReIoMt0r7+hVu05L0=
-----END CERTIFICATE-----

e-Szigno TLS Root CA 2023
=========================
-----BEGIN CERTIFICATE-----
MIICzzCCAjGgAwIBAgINAOhvGHvWOWuYSkmYCjAKBggqhkjOPQQDBDB1MQswCQYDVQQGEwJIVTER
MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xFzAVBgNVBGEMDlZBVEhV
LTIzNTg0NDk3MSIwIAYDVQQDDBllLVN6aWdubyBUTFMgUm9vdCBDQSAyMDIzMB4XDTIzMDcxNzE0
MDAwMFoXDTM4MDcxNzE0MDAwMFowdTELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRYw
FAYDVQQKDA1NaWNyb3NlYyBMdGQuMRcwFQYDVQRhDA5WQVRIVS0yMzU4NDQ5NzEiMCAGA1UEAwwZ
ZS1Temlnbm8gVExTIFJvb3QgQ0EgMjAyMzCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAEAGgP36J8
PKp0iGEKjcJMpQEiFNT3YHdCnAo4YKGMZz6zY+n6kbCLS+Y53wLCMAFSAL/fjO1ZrTJlqwlZULUZ
wmgcAOAFX9pQJhzDrAQixTpN7+lXWDajwRlTEArRzT/vSzUaQ49CE0y5LBqcvjC2xN7cS53kpDzL
Ltmt3999Cd8ukv+ho2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4E
FgQUWYQCYlpGePVd3I8KECgj3NXW+0UwHwYDVR0jBBgwFoAUWYQCYlpGePVd3I8KECgj3NXW+0Uw
CgYIKoZIzj0EAwQDgYsAMIGHAkIBLdqu9S54tma4n7Zwf2Z0z+yOfP7AAXmazlIC58PRDHpty7Ve
7hekm9sEdu4pKeiv+62sUvTXK9Z3hBC9xdIoaDQCQTV2WnXzkoYI9bIeCvZlC9p2x1L/Cx6AcCIw
wzPbGO2E14vs7dOoY4G1VnxHx1YwlGhza9IuqbnZLBwpvQy6uWWL
-----END CERTIFICATE-----
Copyright (C) 2016 Composer

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.
<?php

/*
 * This file is part of composer/ca-bundle.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\CaBundle;

use Psr\Log\LoggerInterface;
use Symfony\Component\Process\PhpProcess;

/**
 * @author Chris Smith <chris@cs278.org>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class CaBundle
{
    /** @var string|null */
    private static $caPath;
    /** @var array<string, bool> */
    private static $caFileValidity = array();

    /**
     * Returns the system CA bundle path, or a path to the bundled one
     *
     * This method was adapted from Sslurp.
     * https://github.com/EvanDotPro/Sslurp
     *
     * (c) Evan Coury <me@evancoury.com>
     *
     * For the full copyright and license information, please see below:
     *
     * Copyright (c) 2013, Evan Coury
     * 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.
     *
     * @param  LoggerInterface $logger optional logger for information about which CA files were loaded
     * @return string          path to a CA bundle file or directory
     */
    public static function getSystemCaRootBundlePath(?LoggerInterface $logger = null)
    {
        if (self::$caPath !== null) {
            return self::$caPath;
        }
        $caBundlePaths = array();

        // If SSL_CERT_FILE env variable points to a valid certificate/bundle, use that.
        // This mimics how OpenSSL uses the SSL_CERT_FILE env variable.
        $caBundlePaths[] = self::getEnvVariable('SSL_CERT_FILE');

        // If SSL_CERT_DIR env variable points to a valid certificate/bundle, use that.
        // This mimics how OpenSSL uses the SSL_CERT_FILE env variable.
        $caBundlePaths[] = self::getEnvVariable('SSL_CERT_DIR');

        $caBundlePaths[] = ini_get('openssl.cafile');
        $caBundlePaths[] = ini_get('openssl.capath');

        $otherLocations = array(
            '/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem', // Fedora, RHEL, CentOS (ca-certificates package) - NEW
            '/etc/pki/tls/certs/ca-bundle.crt', // Fedora, RHEL, CentOS (ca-certificates package) - Deprecated
            '/etc/ssl/certs/ca-certificates.crt', // Debian, Ubuntu, Gentoo, Arch Linux (ca-certificates package)
            '/etc/ssl/ca-bundle.pem', // SUSE, openSUSE (ca-certificates package)
            '/usr/ssl/certs/ca-bundle.crt', // Cygwin
            '/opt/local/share/curl/curl-ca-bundle.crt', // OS X macports, curl-ca-bundle package
            '/usr/local/share/curl/curl-ca-bundle.crt', // Default cURL CA bunde path (without --with-ca-bundle option)
            '/usr/share/ssl/certs/ca-bundle.crt', // Really old RedHat?
            '/etc/ssl/cert.pem', // OpenBSD
            '/usr/local/etc/openssl/cert.pem', // OS X homebrew, openssl package
            '/usr/local/etc/openssl@1.1/cert.pem', // OS X homebrew, openssl@1.1 package
            '/opt/homebrew/etc/openssl@3/cert.pem', // macOS silicon homebrew, openssl@3 package
            '/opt/homebrew/etc/openssl@1.1/cert.pem', // macOS silicon homebrew, openssl@1.1 package
            '/etc/pki/tls/certs',
            '/etc/ssl/certs', // FreeBSD
        );

        $caBundlePaths = array_merge($caBundlePaths, $otherLocations);

        foreach ($caBundlePaths as $caBundle) {
            if ($caBundle && self::caFileUsable($caBundle, $logger)) {
                return self::$caPath = $caBundle;
            }

            if ($caBundle && self::caDirUsable($caBundle, $logger)) {
                return self::$caPath = $caBundle;
            }
        }

        return self::$caPath = static::getBundledCaBundlePath(); // Bundled CA file, last resort
    }

    /**
     * Returns the path to the bundled CA file
     *
     * In case you don't want to trust the user or the system, you can use this directly
     *
     * @return string path to a CA bundle file
     */
    public static function getBundledCaBundlePath()
    {
        $caBundleFile = __DIR__.'/../res/cacert.pem';

        // cURL does not understand 'phar://' paths
        // see https://github.com/composer/ca-bundle/issues/10
        if (0 === strpos($caBundleFile, 'phar://')) {
            $tempCaBundleFile = tempnam(sys_get_temp_dir(), 'openssl-ca-bundle-');
            if (false === $tempCaBundleFile) {
                throw new \RuntimeException('Could not create a temporary file to store the bundled CA file');
            }

            file_put_contents(
                $tempCaBundleFile,
                file_get_contents($caBundleFile)
            );

            register_shutdown_function(function() use ($tempCaBundleFile) {
                @unlink($tempCaBundleFile);
            });

            $caBundleFile = $tempCaBundleFile;
        }

        return $caBundleFile;
    }

    /**
     * Validates a CA file using opensl_x509_parse only if it is safe to use
     *
     * @param string          $filename
     * @param LoggerInterface $logger   optional logger for information about which CA files were loaded
     *
     * @return bool
     */
    public static function validateCaFile($filename, ?LoggerInterface $logger = null)
    {
        static $warned = false;

        if (isset(self::$caFileValidity[$filename])) {
            return self::$caFileValidity[$filename];
        }

        $contents = file_get_contents($filename);

        if (is_string($contents) && strlen($contents) > 0) {
            $contents = preg_replace("/^(\\-+(?:BEGIN|END))\\s+TRUSTED\\s+(CERTIFICATE\\-+)\$/m", '$1 $2', $contents);
            if (null === $contents) {
                // regex extraction failed
                $isValid = false;
            } else {
                $isValid = (bool) openssl_x509_parse($contents);
            }
        } else {
            $isValid = false;
        }

        if ($logger) {
            $logger->debug('Checked CA file '.realpath($filename).': '.($isValid ? 'valid' : 'invalid'));
        }

        return self::$caFileValidity[$filename] = $isValid;
    }

    /**
     * Test if it is safe to use the PHP function openssl_x509_parse().
     *
     * This checks if OpenSSL extensions is vulnerable to remote code execution
     * via the exploit documented as CVE-2013-6420.
     *
     * @return bool
     */
    public static function isOpensslParseSafe()
    {
        return true;
    }

    /**
     * Resets the static caches
     * @return void
     */
    public static function reset()
    {
        self::$caFileValidity = array();
        self::$caPath = null;
    }

    /**
     * @param  string $name
     * @return string|false
     */
    private static function getEnvVariable($name)
    {
        if (isset($_SERVER[$name])) {
            return (string) $_SERVER[$name];
        }

        if (PHP_SAPI === 'cli' && ($value = getenv($name)) !== false && $value !== null) {
            return (string) $value;
        }

        return false;
    }

    /**
     * @param  string|false $certFile
     * @param  LoggerInterface|null $logger
     * @return bool
     */
    private static function caFileUsable($certFile, ?LoggerInterface $logger = null)
    {
        return $certFile
            && self::isFile($certFile, $logger)
            && self::isReadable($certFile, $logger)
            && self::validateCaFile($certFile, $logger);
    }

    /**
     * @param  string|false $certDir
     * @param  LoggerInterface|null $logger
     * @return bool
     */
    private static function caDirUsable($certDir, ?LoggerInterface $logger = null)
    {
        return $certDir
            && self::isDir($certDir, $logger)
            && self::isReadable($certDir, $logger)
            && self::glob($certDir . '/*', $logger);
    }

    /**
     * @param  string $certFile
     * @param  LoggerInterface|null $logger
     * @return bool
     */
    private static function isFile($certFile, ?LoggerInterface $logger = null)
    {
        $isFile = @is_file($certFile);
        if (!$isFile && $logger) {
            $logger->debug(sprintf('Checked CA file %s does not exist or it is not a file.', $certFile));
        }

        return $isFile;
    }

    /**
     * @param  string $certDir
     * @param  LoggerInterface|null $logger
     * @return bool
     */
    private static function isDir($certDir, ?LoggerInterface $logger = null)
    {
        $isDir = @is_dir($certDir);
        if (!$isDir && $logger) {
            $logger->debug(sprintf('Checked directory %s does not exist or it is not a directory.', $certDir));
        }

        return $isDir;
    }

    /**
     * @param  string $certFileOrDir
     * @param  LoggerInterface|null $logger
     * @return bool
     */
    private static function isReadable($certFileOrDir, ?LoggerInterface $logger = null)
    {
        $isReadable = @is_readable($certFileOrDir);
        if (!$isReadable && $logger) {
            $logger->debug(sprintf('Checked file or directory %s is not readable.', $certFileOrDir));
        }

        return $isReadable;
    }

    /**
     * @param  string $pattern
     * @param  LoggerInterface|null $logger
     * @return bool
     */
    private static function glob($pattern, ?LoggerInterface $logger = null)
    {
        $certs = glob($pattern);
        if ($certs === false) {
            if ($logger) {
                $logger->debug(sprintf("An error occurred while trying to find certificates for pattern: %s", $pattern));
            }
            return false;
        }

        if (count($certs) === 0) {
            if ($logger) {
                $logger->debug(sprintf("No CA files found for pattern: %s", $pattern));
            }
            return false;
        }

        return true;
    }
}
{
    "0BSD": [
        "BSD Zero Clause License",
        true,
        false
    ],
    "3D-Slicer-1.0": [
        "3D Slicer License v1.0",
        false,
        false
    ],
    "AAL": [
        "Attribution Assurance License",
        true,
        false
    ],
    "Abstyles": [
        "Abstyles License",
        false,
        false
    ],
    "AdaCore-doc": [
        "AdaCore Doc License",
        false,
        false
    ],
    "Adobe-2006": [
        "Adobe Systems Incorporated Source Code License Agreement",
        false,
        false
    ],
    "Adobe-Display-PostScript": [
        "Adobe Display PostScript License",
        false,
        false
    ],
    "Adobe-Glyph": [
        "Adobe Glyph List License",
        false,
        false
    ],
    "Adobe-Utopia": [
        "Adobe Utopia Font License",
        false,
        false
    ],
    "ADSL": [
        "Amazon Digital Services License",
        false,
        false
    ],
    "Advanced-Cryptics-Dictionary": [
        "Advanced Cryptics Dictionary License",
        false,
        false
    ],
    "AFL-1.1": [
        "Academic Free License v1.1",
        true,
        false
    ],
    "AFL-1.2": [
        "Academic Free License v1.2",
        true,
        false
    ],
    "AFL-2.0": [
        "Academic Free License v2.0",
        true,
        false
    ],
    "AFL-2.1": [
        "Academic Free License v2.1",
        true,
        false
    ],
    "AFL-3.0": [
        "Academic Free License v3.0",
        true,
        false
    ],
    "Afmparse": [
        "Afmparse License",
        false,
        false
    ],
    "AGPL-1.0": [
        "Affero General Public License v1.0",
        false,
        true
    ],
    "AGPL-1.0-only": [
        "Affero General Public License v1.0 only",
        false,
        false
    ],
    "AGPL-1.0-or-later": [
        "Affero General Public License v1.0 or later",
        false,
        false
    ],
    "AGPL-3.0": [
        "GNU Affero General Public License v3.0",
        true,
        true
    ],
    "AGPL-3.0-only": [
        "GNU Affero General Public License v3.0 only",
        true,
        false
    ],
    "AGPL-3.0-or-later": [
        "GNU Affero General Public License v3.0 or later",
        true,
        false
    ],
    "Aladdin": [
        "Aladdin Free Public License",
        false,
        false
    ],
    "ALGLIB-Documentation": [
        "ALGLIB Documentation License",
        true,
        false
    ],
    "AMD-newlib": [
        "AMD newlib License",
        false,
        false
    ],
    "AMDPLPA": [
        "AMD's plpa_map.c License",
        false,
        false
    ],
    "AML": [
        "Apple MIT License",
        false,
        false
    ],
    "AML-glslang": [
        "AML glslang variant License",
        false,
        false
    ],
    "AMPAS": [
        "Academy of Motion Picture Arts and Sciences BSD",
        false,
        false
    ],
    "ANTLR-PD": [
        "ANTLR Software Rights Notice",
        false,
        false
    ],
    "ANTLR-PD-fallback": [
        "ANTLR Software Rights Notice with license fallback",
        false,
        false
    ],
    "any-OSI": [
        "Any OSI License",
        false,
        false
    ],
    "any-OSI-perl-modules": [
        "Any OSI License - Perl Modules",
        false,
        false
    ],
    "Apache-1.0": [
        "Apache License 1.0",
        false,
        false
    ],
    "Apache-1.1": [
        "Apache License 1.1",
        true,
        false
    ],
    "Apache-2.0": [
        "Apache License 2.0",
        true,
        false
    ],
    "APAFML": [
        "Adobe Postscript AFM License",
        false,
        false
    ],
    "APL-1.0": [
        "Adaptive Public License 1.0",
        true,
        false
    ],
    "App-s2p": [
        "App::s2p License",
        false,
        false
    ],
    "APSL-1.0": [
        "Apple Public Source License 1.0",
        true,
        false
    ],
    "APSL-1.1": [
        "Apple Public Source License 1.1",
        true,
        false
    ],
    "APSL-1.2": [
        "Apple Public Source License 1.2",
        true,
        false
    ],
    "APSL-2.0": [
        "Apple Public Source License 2.0",
        true,
        false
    ],
    "Arphic-1999": [
        "Arphic Public License",
        false,
        false
    ],
    "Artistic-1.0": [
        "Artistic License 1.0",
        true,
        false
    ],
    "Artistic-1.0-cl8": [
        "Artistic License 1.0 w/clause 8",
        true,
        false
    ],
    "Artistic-1.0-Perl": [
        "Artistic License 1.0 (Perl)",
        true,
        false
    ],
    "Artistic-2.0": [
        "Artistic License 2.0",
        true,
        false
    ],
    "Artistic-dist": [
        "Artistic License 1.0 (dist)",
        false,
        false
    ],
    "Aspell-RU": [
        "Aspell Russian License",
        false,
        false
    ],
    "ASWF-Digital-Assets-1.0": [
        "ASWF Digital Assets License version 1.0",
        false,
        false
    ],
    "ASWF-Digital-Assets-1.1": [
        "ASWF Digital Assets License 1.1",
        false,
        false
    ],
    "Baekmuk": [
        "Baekmuk License",
        false,
        false
    ],
    "Bahyph": [
        "Bahyph License",
        false,
        false
    ],
    "Barr": [
        "Barr License",
        false,
        false
    ],
    "bcrypt-Solar-Designer": [
        "bcrypt Solar Designer License",
        false,
        false
    ],
    "Beerware": [
        "Beerware License",
        false,
        false
    ],
    "Bitstream-Charter": [
        "Bitstream Charter Font License",
        false,
        false
    ],
    "Bitstream-Vera": [
        "Bitstream Vera Font License",
        false,
        false
    ],
    "BitTorrent-1.0": [
        "BitTorrent Open Source License v1.0",
        false,
        false
    ],
    "BitTorrent-1.1": [
        "BitTorrent Open Source License v1.1",
        false,
        false
    ],
    "blessing": [
        "SQLite Blessing",
        false,
        false
    ],
    "BlueOak-1.0.0": [
        "Blue Oak Model License 1.0.0",
        true,
        false
    ],
    "Boehm-GC": [
        "Boehm-Demers-Weiser GC License",
        false,
        false
    ],
    "Boehm-GC-without-fee": [
        "Boehm-Demers-Weiser GC License (without fee)",
        false,
        false
    ],
    "BOLA-1.1": [
        "Buena Onda License Agreement v1.1",
        false,
        false
    ],
    "Borceux": [
        "Borceux license",
        false,
        false
    ],
    "Brian-Gladman-2-Clause": [
        "Brian Gladman 2-Clause License",
        false,
        false
    ],
    "Brian-Gladman-3-Clause": [
        "Brian Gladman 3-Clause License",
        false,
        false
    ],
    "BSD-1-Clause": [
        "BSD 1-Clause License",
        true,
        false
    ],
    "BSD-2-Clause": [
        "BSD 2-Clause \"Simplified\" License",
        true,
        false
    ],
    "BSD-2-Clause-Darwin": [
        "BSD 2-Clause - Ian Darwin variant",
        false,
        false
    ],
    "BSD-2-Clause-first-lines": [
        "BSD 2-Clause - first lines requirement",
        false,
        false
    ],
    "BSD-2-Clause-FreeBSD": [
        "BSD 2-Clause FreeBSD License",
        false,
        true
    ],
    "BSD-2-Clause-NetBSD": [
        "BSD 2-Clause NetBSD License",
        false,
        true
    ],
    "BSD-2-Clause-Patent": [
        "BSD-2-Clause Plus Patent License",
        true,
        false
    ],
    "BSD-2-Clause-pkgconf-disclaimer": [
        "BSD 2-Clause pkgconf disclaimer variant",
        false,
        false
    ],
    "BSD-2-Clause-Views": [
        "BSD 2-Clause with views sentence",
        false,
        false
    ],
    "BSD-3-Clause": [
        "BSD 3-Clause \"New\" or \"Revised\" License",
        true,
        false
    ],
    "BSD-3-Clause-acpica": [
        "BSD 3-Clause acpica variant",
        false,
        false
    ],
    "BSD-3-Clause-Attribution": [
        "BSD with attribution",
        false,
        false
    ],
    "BSD-3-Clause-Clear": [
        "BSD 3-Clause Clear License",
        false,
        false
    ],
    "BSD-3-Clause-flex": [
        "BSD 3-Clause Flex variant",
        false,
        false
    ],
    "BSD-3-Clause-HP": [
        "Hewlett-Packard BSD variant license",
        false,
        false
    ],
    "BSD-3-Clause-LBNL": [
        "Lawrence Berkeley National Labs BSD variant license",
        true,
        false
    ],
    "BSD-3-Clause-Modification": [
        "BSD 3-Clause Modification",
        false,
        false
    ],
    "BSD-3-Clause-No-Military-License": [
        "BSD 3-Clause No Military License",
        false,
        false
    ],
    "BSD-3-Clause-No-Nuclear-License": [
        "BSD 3-Clause No Nuclear License",
        false,
        false
    ],
    "BSD-3-Clause-No-Nuclear-License-2014": [
        "BSD 3-Clause No Nuclear License 2014",
        false,
        false
    ],
    "BSD-3-Clause-No-Nuclear-Warranty": [
        "BSD 3-Clause No Nuclear Warranty",
        false,
        false
    ],
    "BSD-3-Clause-Open-MPI": [
        "BSD 3-Clause Open MPI variant",
        true,
        false
    ],
    "BSD-3-Clause-Sun": [
        "BSD 3-Clause Sun Microsystems",
        false,
        false
    ],
    "BSD-3-Clause-Tso": [
        "BSD 3-Clause Tso variant",
        false,
        false
    ],
    "BSD-4-Clause": [
        "BSD 4-Clause \"Original\" or \"Old\" License",
        false,
        false
    ],
    "BSD-4-Clause-Shortened": [
        "BSD 4 Clause Shortened",
        false,
        false
    ],
    "BSD-4-Clause-UC": [
        "BSD-4-Clause (University of California-Specific)",
        false,
        false
    ],
    "BSD-4.3RENO": [
        "BSD 4.3 RENO License",
        false,
        false
    ],
    "BSD-4.3TAHOE": [
        "BSD 4.3 TAHOE License",
        false,
        false
    ],
    "BSD-Advertising-Acknowledgement": [
        "BSD Advertising Acknowledgement License",
        false,
        false
    ],
    "BSD-Attribution-HPND-disclaimer": [
        "BSD with Attribution and HPND disclaimer",
        false,
        false
    ],
    "BSD-Inferno-Nettverk": [
        "BSD-Inferno-Nettverk",
        false,
        false
    ],
    "BSD-Mark-Modifications": [
        "BSD Mark Modifications License",
        false,
        false
    ],
    "BSD-Protection": [
        "BSD Protection License",
        false,
        false
    ],
    "BSD-Source-beginning-file": [
        "BSD Source Code Attribution - beginning of file variant",
        false,
        false
    ],
    "BSD-Source-Code": [
        "BSD Source Code Attribution",
        false,
        false
    ],
    "BSD-Systemics": [
        "Systemics BSD variant license",
        false,
        false
    ],
    "BSD-Systemics-W3Works": [
        "Systemics W3Works BSD variant license",
        false,
        false
    ],
    "BSL-1.0": [
        "Boost Software License 1.0",
        true,
        false
    ],
    "Buddy": [
        "Buddy License",
        false,
        false
    ],
    "BUSL-1.1": [
        "Business Source License 1.1",
        false,
        false
    ],
    "bzip2-1.0.5": [
        "bzip2 and libbzip2 License v1.0.5",
        false,
        true
    ],
    "bzip2-1.0.6": [
        "bzip2 and libbzip2 License v1.0.6",
        false,
        false
    ],
    "C-UDA-1.0": [
        "Computational Use of Data Agreement v1.0",
        false,
        false
    ],
    "CAL-1.0": [
        "Cryptographic Autonomy License 1.0",
        true,
        false
    ],
    "CAL-1.0-Combined-Work-Exception": [
        "Cryptographic Autonomy License 1.0 (Combined Work Exception)",
        true,
        false
    ],
    "Caldera": [
        "Caldera License",
        false,
        false
    ],
    "Caldera-no-preamble": [
        "Caldera License (without preamble)",
        false,
        false
    ],
    "CAPEC-tou": [
        "Common Attack    Pattern Enumeration and Classification License",
        false,
        false
    ],
    "Catharon": [
        "Catharon License",
        false,
        false
    ],
    "CATOSL-1.1": [
        "Computer Associates Trusted Open Source License 1.1",
        true,
        false
    ],
    "CC-BY-1.0": [
        "Creative Commons Attribution 1.0 Generic",
        false,
        false
    ],
    "CC-BY-2.0": [
        "Creative Commons Attribution 2.0 Generic",
        false,
        false
    ],
    "CC-BY-2.5": [
        "Creative Commons Attribution 2.5 Generic",
        false,
        false
    ],
    "CC-BY-2.5-AU": [
        "Creative Commons Attribution 2.5 Australia",
        false,
        false
    ],
    "CC-BY-3.0": [
        "Creative Commons Attribution 3.0 Unported",
        false,
        false
    ],
    "CC-BY-3.0-AT": [
        "Creative Commons Attribution 3.0 Austria",
        false,
        false
    ],
    "CC-BY-3.0-AU": [
        "Creative Commons Attribution 3.0 Australia",
        false,
        false
    ],
    "CC-BY-3.0-DE": [
        "Creative Commons Attribution 3.0 Germany",
        false,
        false
    ],
    "CC-BY-3.0-IGO": [
        "Creative Commons Attribution 3.0 IGO",
        false,
        false
    ],
    "CC-BY-3.0-NL": [
        "Creative Commons Attribution 3.0 Netherlands",
        false,
        false
    ],
    "CC-BY-3.0-US": [
        "Creative Commons Attribution 3.0 United States",
        false,
        false
    ],
    "CC-BY-4.0": [
        "Creative Commons Attribution 4.0 International",
        false,
        false
    ],
    "CC-BY-NC-1.0": [
        "Creative Commons Attribution Non Commercial 1.0 Generic",
        false,
        false
    ],
    "CC-BY-NC-2.0": [
        "Creative Commons Attribution Non Commercial 2.0 Generic",
        false,
        false
    ],
    "CC-BY-NC-2.5": [
        "Creative Commons Attribution Non Commercial 2.5 Generic",
        false,
        false
    ],
    "CC-BY-NC-3.0": [
        "Creative Commons Attribution Non Commercial 3.0 Unported",
        false,
        false
    ],
    "CC-BY-NC-3.0-DE": [
        "Creative Commons Attribution Non Commercial 3.0 Germany",
        false,
        false
    ],
    "CC-BY-NC-4.0": [
        "Creative Commons Attribution Non Commercial 4.0 International",
        false,
        false
    ],
    "CC-BY-NC-ND-1.0": [
        "Creative Commons Attribution Non Commercial No Derivatives 1.0 Generic",
        false,
        false
    ],
    "CC-BY-NC-ND-2.0": [
        "Creative Commons Attribution Non Commercial No Derivatives 2.0 Generic",
        false,
        false
    ],
    "CC-BY-NC-ND-2.5": [
        "Creative Commons Attribution Non Commercial No Derivatives 2.5 Generic",
        false,
        false
    ],
    "CC-BY-NC-ND-3.0": [
        "Creative Commons Attribution Non Commercial No Derivatives 3.0 Unported",
        false,
        false
    ],
    "CC-BY-NC-ND-3.0-DE": [
        "Creative Commons Attribution Non Commercial No Derivatives 3.0 Germany",
        false,
        false
    ],
    "CC-BY-NC-ND-3.0-IGO": [
        "Creative Commons Attribution Non Commercial No Derivatives 3.0 IGO",
        false,
        false
    ],
    "CC-BY-NC-ND-4.0": [
        "Creative Commons Attribution Non Commercial No Derivatives 4.0 International",
        false,
        false
    ],
    "CC-BY-NC-SA-1.0": [
        "Creative Commons Attribution Non Commercial Share Alike 1.0 Generic",
        false,
        false
    ],
    "CC-BY-NC-SA-2.0": [
        "Creative Commons Attribution Non Commercial Share Alike 2.0 Generic",
        false,
        false
    ],
    "CC-BY-NC-SA-2.0-DE": [
        "Creative Commons Attribution Non Commercial Share Alike 2.0 Germany",
        false,
        false
    ],
    "CC-BY-NC-SA-2.0-FR": [
        "Creative Commons Attribution-NonCommercial-ShareAlike 2.0 France",
        false,
        false
    ],
    "CC-BY-NC-SA-2.0-UK": [
        "Creative Commons Attribution Non Commercial Share Alike 2.0 England and Wales",
        false,
        false
    ],
    "CC-BY-NC-SA-2.5": [
        "Creative Commons Attribution Non Commercial Share Alike 2.5 Generic",
        false,
        false
    ],
    "CC-BY-NC-SA-3.0": [
        "Creative Commons Attribution Non Commercial Share Alike 3.0 Unported",
        false,
        false
    ],
    "CC-BY-NC-SA-3.0-DE": [
        "Creative Commons Attribution Non Commercial Share Alike 3.0 Germany",
        false,
        false
    ],
    "CC-BY-NC-SA-3.0-IGO": [
        "Creative Commons Attribution Non Commercial Share Alike 3.0 IGO",
        false,
        false
    ],
    "CC-BY-NC-SA-4.0": [
        "Creative Commons Attribution Non Commercial Share Alike 4.0 International",
        false,
        false
    ],
    "CC-BY-ND-1.0": [
        "Creative Commons Attribution No Derivatives 1.0 Generic",
        false,
        false
    ],
    "CC-BY-ND-2.0": [
        "Creative Commons Attribution No Derivatives 2.0 Generic",
        false,
        false
    ],
    "CC-BY-ND-2.5": [
        "Creative Commons Attribution No Derivatives 2.5 Generic",
        false,
        false
    ],
    "CC-BY-ND-3.0": [
        "Creative Commons Attribution No Derivatives 3.0 Unported",
        false,
        false
    ],
    "CC-BY-ND-3.0-DE": [
        "Creative Commons Attribution No Derivatives 3.0 Germany",
        false,
        false
    ],
    "CC-BY-ND-4.0": [
        "Creative Commons Attribution No Derivatives 4.0 International",
        false,
        false
    ],
    "CC-BY-SA-1.0": [
        "Creative Commons Attribution Share Alike 1.0 Generic",
        false,
        false
    ],
    "CC-BY-SA-2.0": [
        "Creative Commons Attribution Share Alike 2.0 Generic",
        false,
        false
    ],
    "CC-BY-SA-2.0-UK": [
        "Creative Commons Attribution Share Alike 2.0 England and Wales",
        false,
        false
    ],
    "CC-BY-SA-2.1-JP": [
        "Creative Commons Attribution Share Alike 2.1 Japan",
        false,
        false
    ],
    "CC-BY-SA-2.5": [
        "Creative Commons Attribution Share Alike 2.5 Generic",
        false,
        false
    ],
    "CC-BY-SA-3.0": [
        "Creative Commons Attribution Share Alike 3.0 Unported",
        false,
        false
    ],
    "CC-BY-SA-3.0-AT": [
        "Creative Commons Attribution Share Alike 3.0 Austria",
        false,
        false
    ],
    "CC-BY-SA-3.0-DE": [
        "Creative Commons Attribution Share Alike 3.0 Germany",
        false,
        false
    ],
    "CC-BY-SA-3.0-IGO": [
        "Creative Commons Attribution-ShareAlike 3.0 IGO",
        false,
        false
    ],
    "CC-BY-SA-4.0": [
        "Creative Commons Attribution Share Alike 4.0 International",
        false,
        false
    ],
    "CC-PDDC": [
        "Creative Commons Public Domain Dedication and Certification",
        false,
        false
    ],
    "CC-PDM-1.0": [
        "Creative    Commons Public Domain Mark 1.0 Universal",
        false,
        false
    ],
    "CC-SA-1.0": [
        "Creative Commons Share Alike 1.0 Generic",
        false,
        false
    ],
    "CC0-1.0": [
        "Creative Commons Zero v1.0 Universal",
        false,
        false
    ],
    "CDDL-1.0": [
        "Common Development and Distribution License 1.0",
        true,
        false
    ],
    "CDDL-1.1": [
        "Common Development and Distribution License 1.1",
        false,
        false
    ],
    "CDL-1.0": [
        "Common Documentation License 1.0",
        false,
        false
    ],
    "CDLA-Permissive-1.0": [
        "Community Data License Agreement Permissive 1.0",
        false,
        false
    ],
    "CDLA-Permissive-2.0": [
        "Community Data License Agreement Permissive 2.0",
        false,
        false
    ],
    "CDLA-Sharing-1.0": [
        "Community Data License Agreement Sharing 1.0",
        false,
        false
    ],
    "CECILL-1.0": [
        "CeCILL Free Software License Agreement v1.0",
        false,
        false
    ],
    "CECILL-1.1": [
        "CeCILL Free Software License Agreement v1.1",
        false,
        false
    ],
    "CECILL-2.0": [
        "CeCILL Free Software License Agreement v2.0",
        false,
        false
    ],
    "CECILL-2.1": [
        "CeCILL Free Software License Agreement v2.1",
        true,
        false
    ],
    "CECILL-B": [
        "CeCILL-B Free Software License Agreement",
        false,
        false
    ],
    "CECILL-C": [
        "CeCILL-C Free Software License Agreement",
        false,
        false
    ],
    "CERN-OHL-1.1": [
        "CERN Open Hardware Licence v1.1",
        false,
        false
    ],
    "CERN-OHL-1.2": [
        "CERN Open Hardware Licence v1.2",
        false,
        false
    ],
    "CERN-OHL-P-2.0": [
        "CERN Open Hardware Licence Version 2 - Permissive",
        true,
        false
    ],
    "CERN-OHL-S-2.0": [
        "CERN Open Hardware Licence Version 2 - Strongly Reciprocal",
        true,
        false
    ],
    "CERN-OHL-W-2.0": [
        "CERN Open Hardware Licence Version 2 - Weakly Reciprocal",
        true,
        false
    ],
    "CFITSIO": [
        "CFITSIO License",
        false,
        false
    ],
    "check-cvs": [
        "check-cvs License",
        false,
        false
    ],
    "checkmk": [
        "Checkmk License",
        false,
        false
    ],
    "ClArtistic": [
        "Clarified Artistic License",
        false,
        false
    ],
    "Clips": [
        "Clips License",
        false,
        false
    ],
    "CMU-Mach": [
        "CMU Mach License",
        false,
        false
    ],
    "CMU-Mach-nodoc": [
        "CMU    Mach - no notices-in-documentation variant",
        false,
        false
    ],
    "CNRI-Jython": [
        "CNRI Jython License",
        false,
        false
    ],
    "CNRI-Python": [
        "CNRI Python License",
        true,
        false
    ],
    "CNRI-Python-GPL-Compatible": [
        "CNRI Python Open Source GPL Compatible License Agreement",
        false,
        false
    ],
    "COIL-1.0": [
        "Copyfree Open Innovation License",
        false,
        false
    ],
    "Community-Spec-1.0": [
        "Community Specification License 1.0",
        false,
        false
    ],
    "Condor-1.1": [
        "Condor Public License v1.1",
        false,
        false
    ],
    "copyleft-next-0.3.0": [
        "copyleft-next 0.3.0",
        false,
        false
    ],
    "copyleft-next-0.3.1": [
        "copyleft-next 0.3.1",
        false,
        false
    ],
    "Cornell-Lossless-JPEG": [
        "Cornell Lossless JPEG License",
        false,
        false
    ],
    "CPAL-1.0": [
        "Common Public Attribution License 1.0",
        true,
        false
    ],
    "CPL-1.0": [
        "Common Public License 1.0",
        true,
        false
    ],
    "CPOL-1.02": [
        "Code Project Open License 1.02",
        false,
        false
    ],
    "Cronyx": [
        "Cronyx License",
        false,
        false
    ],
    "Crossword": [
        "Crossword License",
        false,
        false
    ],
    "CryptoSwift": [
        "CryptoSwift License",
        false,
        false
    ],
    "CrystalStacker": [
        "CrystalStacker License",
        false,
        false
    ],
    "CUA-OPL-1.0": [
        "CUA Office Public License v1.0",
        true,
        false
    ],
    "Cube": [
        "Cube License",
        false,
        false
    ],
    "curl": [
        "curl License",
        false,
        false
    ],
    "cve-tou": [
        "Common Vulnerability Enumeration ToU License",
        false,
        false
    ],
    "D-FSL-1.0": [
        "Deutsche Freie Software Lizenz",
        false,
        false
    ],
    "DEC-3-Clause": [
        "DEC 3-Clause License",
        false,
        false
    ],
    "diffmark": [
        "diffmark license",
        false,
        false
    ],
    "DL-DE-BY-2.0": [
        "Data licence Germany \u2013 attribution \u2013 version 2.0",
        false,
        false
    ],
    "DL-DE-ZERO-2.0": [
        "Data licence Germany \u2013 zero \u2013 version 2.0",
        false,
        false
    ],
    "DOC": [
        "DOC License",
        false,
        false
    ],
    "DocBook-DTD": [
        "DocBook DTD License",
        false,
        false
    ],
    "DocBook-Schema": [
        "DocBook Schema License",
        false,
        false
    ],
    "DocBook-Stylesheet": [
        "DocBook Stylesheet License",
        false,
        false
    ],
    "DocBook-XML": [
        "DocBook XML License",
        false,
        false
    ],
    "Dotseqn": [
        "Dotseqn License",
        false,
        false
    ],
    "DRL-1.0": [
        "Detection Rule License 1.0",
        false,
        false
    ],
    "DRL-1.1": [
        "Detection Rule License 1.1",
        false,
        false
    ],
    "DSDP": [
        "DSDP License",
        false,
        false
    ],
    "dtoa": [
        "David M. Gay dtoa License",
        false,
        false
    ],
    "dvipdfm": [
        "dvipdfm License",
        false,
        false
    ],
    "ECL-1.0": [
        "Educational Community License v1.0",
        true,
        false
    ],
    "ECL-2.0": [
        "Educational Community License v2.0",
        true,
        false
    ],
    "eCos-2.0": [
        "eCos license version 2.0",
        false,
        true
    ],
    "EFL-1.0": [
        "Eiffel Forum License v1.0",
        true,
        false
    ],
    "EFL-2.0": [
        "Eiffel Forum License v2.0",
        true,
        false
    ],
    "eGenix": [
        "eGenix.com Public License 1.1.0",
        false,
        false
    ],
    "Elastic-2.0": [
        "Elastic License 2.0",
        false,
        false
    ],
    "Entessa": [
        "Entessa Public License v1.0",
        true,
        false
    ],
    "EPICS": [
        "EPICS Open License",
        false,
        false
    ],
    "EPL-1.0": [
        "Eclipse Public License 1.0",
        true,
        false
    ],
    "EPL-2.0": [
        "Eclipse Public License 2.0",
        true,
        false
    ],
    "ErlPL-1.1": [
        "Erlang Public License v1.1",
        false,
        false
    ],
    "ESA-PL-permissive-2.4": [
        "European Space Agency Public License \u2013 v2.4 \u2013 Permissive (Type 3)",
        false,
        false
    ],
    "ESA-PL-strong-copyleft-2.4": [
        "European Space Agency Public License (ESA-PL) - V2.4 - Strong Copyleft (Type 1)",
        false,
        false
    ],
    "ESA-PL-weak-copyleft-2.4": [
        "European Space Agency Public License \u2013 v2.4 \u2013 Weak Copyleft (Type 2)",
        false,
        false
    ],
    "etalab-2.0": [
        "Etalab Open License 2.0",
        false,
        false
    ],
    "EUDatagrid": [
        "EU DataGrid Software License",
        true,
        false
    ],
    "EUPL-1.0": [
        "European Union Public License 1.0",
        false,
        false
    ],
    "EUPL-1.1": [
        "European Union Public License 1.1",
        true,
        false
    ],
    "EUPL-1.2": [
        "European Union Public License 1.2",
        true,
        false
    ],
    "Eurosym": [
        "Eurosym License",
        false,
        false
    ],
    "Fair": [
        "Fair License",
        true,
        false
    ],
    "FBM": [
        "Fuzzy Bitmap License",
        false,
        false
    ],
    "FDK-AAC": [
        "Fraunhofer FDK AAC Codec Library",
        false,
        false
    ],
    "Ferguson-Twofish": [
        "Ferguson Twofish License",
        false,
        false
    ],
    "Frameworx-1.0": [
        "Frameworx Open License 1.0",
        true,
        false
    ],
    "FreeBSD-DOC": [
        "FreeBSD Documentation License",
        false,
        false
    ],
    "FreeImage": [
        "FreeImage Public License v1.0",
        false,
        false
    ],
    "FSFAP": [
        "FSF All Permissive License",
        false,
        false
    ],
    "FSFAP-no-warranty-disclaimer": [
        "FSF All Permissive License (without Warranty)",
        false,
        false
    ],
    "FSFUL": [
        "FSF Unlimited License",
        false,
        false
    ],
    "FSFULLR": [
        "FSF Unlimited License (with License Retention)",
        false,
        false
    ],
    "FSFULLRSD": [
        "FSF Unlimited License (with License Retention and Short Disclaimer)",
        false,
        false
    ],
    "FSFULLRWD": [
        "FSF Unlimited License (With License Retention and Warranty Disclaimer)",
        false,
        false
    ],
    "FSL-1.1-ALv2": [
        "Functional Source License, Version 1.1, ALv2 Future License",
        false,
        false
    ],
    "FSL-1.1-MIT": [
        "Functional Source License, Version 1.1, MIT Future License",
        false,
        false
    ],
    "FTL": [
        "Freetype Project License",
        false,
        false
    ],
    "Furuseth": [
        "Furuseth License",
        false,
        false
    ],
    "fwlw": [
        "fwlw License",
        false,
        false
    ],
    "Game-Programming-Gems": [
        "Game Programming Gems License",
        false,
        false
    ],
    "GCR-docs": [
        "Gnome GCR Documentation License",
        false,
        false
    ],
    "GD": [
        "GD License",
        false,
        false
    ],
    "generic-xts": [
        "Generic XTS License",
        false,
        false
    ],
    "GFDL-1.1": [
        "GNU Free Documentation License v1.1",
        false,
        true
    ],
    "GFDL-1.1-invariants-only": [
        "GNU Free Documentation License v1.1 only - invariants",
        false,
        false
    ],
    "GFDL-1.1-invariants-or-later": [
        "GNU Free Documentation License v1.1 or later - invariants",
        false,
        false
    ],
    "GFDL-1.1-no-invariants-only": [
        "GNU Free Documentation License v1.1 only - no invariants",
        false,
        false
    ],
    "GFDL-1.1-no-invariants-or-later": [
        "GNU Free Documentation License v1.1 or later - no invariants",
        false,
        false
    ],
    "GFDL-1.1-only": [
        "GNU Free Documentation License v1.1 only",
        false,
        false
    ],
    "GFDL-1.1-or-later": [
        "GNU Free Documentation License v1.1 or later",
        false,
        false
    ],
    "GFDL-1.2": [
        "GNU Free Documentation License v1.2",
        false,
        true
    ],
    "GFDL-1.2-invariants-only": [
        "GNU Free Documentation License v1.2 only - invariants",
        false,
        false
    ],
    "GFDL-1.2-invariants-or-later": [
        "GNU Free Documentation License v1.2 or later - invariants",
        false,
        false
    ],
    "GFDL-1.2-no-invariants-only": [
        "GNU Free Documentation License v1.2 only - no invariants",
        false,
        false
    ],
    "GFDL-1.2-no-invariants-or-later": [
        "GNU Free Documentation License v1.2 or later - no invariants",
        false,
        false
    ],
    "GFDL-1.2-only": [
        "GNU Free Documentation License v1.2 only",
        false,
        false
    ],
    "GFDL-1.2-or-later": [
        "GNU Free Documentation License v1.2 or later",
        false,
        false
    ],
    "GFDL-1.3": [
        "GNU Free Documentation License v1.3",
        false,
        true
    ],
    "GFDL-1.3-invariants-only": [
        "GNU Free Documentation License v1.3 only - invariants",
        false,
        false
    ],
    "GFDL-1.3-invariants-or-later": [
        "GNU Free Documentation License v1.3 or later - invariants",
        false,
        false
    ],
    "GFDL-1.3-no-invariants-only": [
        "GNU Free Documentation License v1.3 only - no invariants",
        false,
        false
    ],
    "GFDL-1.3-no-invariants-or-later": [
        "GNU Free Documentation License v1.3 or later - no invariants",
        false,
        false
    ],
    "GFDL-1.3-only": [
        "GNU Free Documentation License v1.3 only",
        false,
        false
    ],
    "GFDL-1.3-or-later": [
        "GNU Free Documentation License v1.3 or later",
        false,
        false
    ],
    "Giftware": [
        "Giftware License",
        false,
        false
    ],
    "GL2PS": [
        "GL2PS License",
        false,
        false
    ],
    "Glide": [
        "3dfx Glide License",
        false,
        false
    ],
    "Glulxe": [
        "Glulxe License",
        false,
        false
    ],
    "GLWTPL": [
        "Good Luck With That Public License",
        false,
        false
    ],
    "gnuplot": [
        "gnuplot License",
        false,
        false
    ],
    "GPL-1.0": [
        "GNU General Public License v1.0 only",
        false,
        true
    ],
    "GPL-1.0+": [
        "GNU General Public License v1.0 or later",
        false,
        true
    ],
    "GPL-1.0-only": [
        "GNU General Public License v1.0 only",
        false,
        false
    ],
    "GPL-1.0-or-later": [
        "GNU General Public License v1.0 or later",
        false,
        false
    ],
    "GPL-2.0": [
        "GNU General Public License v2.0 only",
        true,
        true
    ],
    "GPL-2.0+": [
        "GNU General Public License v2.0 or later",
        true,
        true
    ],
    "GPL-2.0-only": [
        "GNU General Public License v2.0 only",
        true,
        false
    ],
    "GPL-2.0-or-later": [
        "GNU General Public License v2.0 or later",
        true,
        false
    ],
    "GPL-2.0-with-autoconf-exception": [
        "GNU General Public License v2.0 w/Autoconf exception",
        false,
        true
    ],
    "GPL-2.0-with-bison-exception": [
        "GNU General Public License v2.0 w/Bison exception",
        false,
        true
    ],
    "GPL-2.0-with-classpath-exception": [
        "GNU General Public License v2.0 w/Classpath exception",
        false,
        true
    ],
    "GPL-2.0-with-font-exception": [
        "GNU General Public License v2.0 w/Font exception",
        false,
        true
    ],
    "GPL-2.0-with-GCC-exception": [
        "GNU General Public License v2.0 w/GCC Runtime Library exception",
        false,
        true
    ],
    "GPL-3.0": [
        "GNU General Public License v3.0 only",
        true,
        true
    ],
    "GPL-3.0+": [
        "GNU General Public License v3.0 or later",
        true,
        true
    ],
    "GPL-3.0-only": [
        "GNU General Public License v3.0 only",
        true,
        false
    ],
    "GPL-3.0-or-later": [
        "GNU General Public License v3.0 or later",
        true,
        false
    ],
    "GPL-3.0-with-autoconf-exception": [
        "GNU General Public License v3.0 w/Autoconf exception",
        false,
        true
    ],
    "GPL-3.0-with-GCC-exception": [
        "GNU General Public License v3.0 w/GCC Runtime Library exception",
        true,
        true
    ],
    "Graphics-Gems": [
        "Graphics Gems License",
        false,
        false
    ],
    "gSOAP-1.3b": [
        "gSOAP Public License v1.3b",
        false,
        false
    ],
    "gtkbook": [
        "gtkbook License",
        false,
        false
    ],
    "Gutmann": [
        "Gutmann License",
        false,
        false
    ],
    "HaskellReport": [
        "Haskell Language Report License",
        false,
        false
    ],
    "HDF5": [
        "HDF5 License",
        false,
        false
    ],
    "hdparm": [
        "hdparm License",
        false,
        false
    ],
    "HIDAPI": [
        "HIDAPI License",
        false,
        false
    ],
    "Hippocratic-2.1": [
        "Hippocratic License 2.1",
        false,
        false
    ],
    "HP-1986": [
        "Hewlett-Packard 1986 License",
        false,
        false
    ],
    "HP-1989": [
        "Hewlett-Packard 1989 License",
        false,
        false
    ],
    "HPND": [
        "Historical Permission Notice and Disclaimer",
        true,
        false
    ],
    "HPND-DEC": [
        "Historical Permission Notice and Disclaimer - DEC variant",
        false,
        false
    ],
    "HPND-doc": [
        "Historical Permission Notice and Disclaimer - documentation variant",
        false,
        false
    ],
    "HPND-doc-sell": [
        "Historical Permission Notice and Disclaimer - documentation sell variant",
        false,
        false
    ],
    "HPND-export-US": [
        "HPND with US Government export control warning",
        false,
        false
    ],
    "HPND-export-US-acknowledgement": [
        "HPND with US Government export control warning and acknowledgment",
        false,
        false
    ],
    "HPND-export-US-modify": [
        "HPND with US Government export control warning and modification rqmt",
        false,
        false
    ],
    "HPND-export2-US": [
        "HPND with US Government export control and 2 disclaimers",
        false,
        false
    ],
    "HPND-Fenneberg-Livingston": [
        "Historical Permission Notice and Disclaimer - Fenneberg-Livingston variant",
        false,
        false
    ],
    "HPND-INRIA-IMAG": [
        "Historical Permission Notice and Disclaimer    - INRIA-IMAG variant",
        false,
        false
    ],
    "HPND-Intel": [
        "Historical Permission Notice and Disclaimer - Intel variant",
        false,
        false
    ],
    "HPND-Kevlin-Henney": [
        "Historical Permission Notice and Disclaimer - Kevlin Henney variant",
        false,
        false
    ],
    "HPND-Markus-Kuhn": [
        "Historical Permission Notice and Disclaimer - Markus Kuhn variant",
        false,
        false
    ],
    "HPND-merchantability-variant": [
        "Historical Permission Notice and Disclaimer - merchantability variant",
        false,
        false
    ],
    "HPND-MIT-disclaimer": [
        "Historical Permission Notice and Disclaimer with MIT disclaimer",
        false,
        false
    ],
    "HPND-Netrek": [
        "Historical Permission Notice and Disclaimer - Netrek variant",
        false,
        false
    ],
    "HPND-Pbmplus": [
        "Historical Permission Notice and Disclaimer - Pbmplus variant",
        false,
        false
    ],
    "HPND-sell-MIT-disclaimer-xserver": [
        "Historical Permission Notice and Disclaimer - sell xserver variant with MIT disclaimer",
        false,
        false
    ],
    "HPND-sell-regexpr": [
        "Historical Permission Notice and Disclaimer - sell regexpr variant",
        false,
        false
    ],
    "HPND-sell-variant": [
        "Historical Permission Notice and Disclaimer - sell variant",
        false,
        false
    ],
    "HPND-sell-variant-critical-systems": [
        "HPND - sell variant with safety critical systems clause",
        false,
        false
    ],
    "HPND-sell-variant-MIT-disclaimer": [
        "HPND sell variant with MIT disclaimer",
        false,
        false
    ],
    "HPND-sell-variant-MIT-disclaimer-rev": [
        "HPND sell variant with MIT disclaimer - reverse",
        false,
        false
    ],
    "HPND-SMC": [
        "Historical Permission Notice and Disclaimer - SMC variant",
        false,
        false
    ],
    "HPND-UC": [
        "Historical Permission Notice and Disclaimer - University of California variant",
        false,
        false
    ],
    "HPND-UC-export-US": [
        "Historical Permission Notice and Disclaimer - University of California, US export warning",
        false,
        false
    ],
    "HTMLTIDY": [
        "HTML Tidy License",
        false,
        false
    ],
    "hyphen-bulgarian": [
        "hyphen-bulgarian License",
        false,
        false
    ],
    "IBM-pibs": [
        "IBM PowerPC Initialization and Boot Software",
        false,
        false
    ],
    "ICU": [
        "ICU License",
        true,
        false
    ],
    "IEC-Code-Components-EULA": [
        "IEC    Code Components End-user licence agreement",
        false,
        false
    ],
    "IJG": [
        "Independent JPEG Group License",
        false,
        false
    ],
    "IJG-short": [
        "Independent JPEG Group License - short",
        false,
        false
    ],
    "ImageMagick": [
        "ImageMagick License",
        false,
        false
    ],
    "iMatix": [
        "iMatix Standard Function Library Agreement",
        false,
        false
    ],
    "Imlib2": [
        "Imlib2 License",
        false,
        false
    ],
    "Info-ZIP": [
        "Info-ZIP License",
        false,
        false
    ],
    "Inner-Net-2.0": [
        "Inner Net License v2.0",
        false,
        false
    ],
    "InnoSetup": [
        "Inno Setup License",
        false,
        false
    ],
    "Intel": [
        "Intel Open Source License",
        true,
        false
    ],
    "Intel-ACPI": [
        "Intel ACPI Software License Agreement",
        false,
        false
    ],
    "Interbase-1.0": [
        "Interbase Public License v1.0",
        false,
        false
    ],
    "IPA": [
        "IPA Font License",
        true,
        false
    ],
    "IPL-1.0": [
        "IBM Public License v1.0",
        true,
        false
    ],
    "ISC": [
        "ISC License",
        true,
        false
    ],
    "ISC-Veillard": [
        "ISC Veillard variant",
        false,
        false
    ],
    "ISO-permission": [
        "ISO permission notice",
        false,
        false
    ],
    "Jam": [
        "Jam License",
        true,
        false
    ],
    "JasPer-2.0": [
        "JasPer License",
        false,
        false
    ],
    "jove": [
        "Jove License",
        false,
        false
    ],
    "JPL-image": [
        "JPL Image Use Policy",
        false,
        false
    ],
    "JPNIC": [
        "Japan Network Information Center License",
        false,
        false
    ],
    "JSON": [
        "JSON License",
        false,
        false
    ],
    "Kastrup": [
        "Kastrup License",
        false,
        false
    ],
    "Kazlib": [
        "Kazlib License",
        false,
        false
    ],
    "Knuth-CTAN": [
        "Knuth CTAN License",
        false,
        false
    ],
    "LAL-1.2": [
        "Licence Art Libre 1.2",
        false,
        false
    ],
    "LAL-1.3": [
        "Licence Art Libre 1.3",
        false,
        false
    ],
    "Latex2e": [
        "Latex2e License",
        false,
        false
    ],
    "Latex2e-translated-notice": [
        "Latex2e with translated notice permission",
        false,
        false
    ],
    "Leptonica": [
        "Leptonica License",
        false,
        false
    ],
    "LGPL-2.0": [
        "GNU Library General Public License v2 only",
        true,
        true
    ],
    "LGPL-2.0+": [
        "GNU Library General Public License v2 or later",
        true,
        true
    ],
    "LGPL-2.0-only": [
        "GNU Library General Public License v2 only",
        true,
        false
    ],
    "LGPL-2.0-or-later": [
        "GNU Library General Public License v2 or later",
        true,
        false
    ],
    "LGPL-2.1": [
        "GNU Lesser General Public License v2.1 only",
        true,
        true
    ],
    "LGPL-2.1+": [
        "GNU Lesser General Public License v2.1 or later",
        true,
        true
    ],
    "LGPL-2.1-only": [
        "GNU Lesser General Public License v2.1 only",
        true,
        false
    ],
    "LGPL-2.1-or-later": [
        "GNU Lesser General Public License v2.1 or later",
        true,
        false
    ],
    "LGPL-3.0": [
        "GNU Lesser General Public License v3.0 only",
        true,
        true
    ],
    "LGPL-3.0+": [
        "GNU Lesser General Public License v3.0 or later",
        true,
        true
    ],
    "LGPL-3.0-only": [
        "GNU Lesser General Public License v3.0 only",
        true,
        false
    ],
    "LGPL-3.0-or-later": [
        "GNU Lesser General Public License v3.0 or later",
        true,
        false
    ],
    "LGPLLR": [
        "Lesser General Public License For Linguistic Resources",
        false,
        false
    ],
    "Libpng": [
        "libpng License",
        false,
        false
    ],
    "libpng-1.6.35": [
        "PNG Reference Library License v1 (for libpng 0.5 through 1.6.35)",
        false,
        false
    ],
    "libpng-2.0": [
        "PNG Reference Library version 2",
        false,
        false
    ],
    "libselinux-1.0": [
        "libselinux public domain notice",
        false,
        false
    ],
    "libtiff": [
        "libtiff License",
        false,
        false
    ],
    "libutil-David-Nugent": [
        "libutil David Nugent License",
        false,
        false
    ],
    "LiLiQ-P-1.1": [
        "Licence Libre du Qu\u00e9bec \u2013 Permissive version 1.1",
        true,
        false
    ],
    "LiLiQ-R-1.1": [
        "Licence Libre du Qu\u00e9bec \u2013 R\u00e9ciprocit\u00e9 version 1.1",
        true,
        false
    ],
    "LiLiQ-Rplus-1.1": [
        "Licence Libre du Qu\u00e9bec \u2013 R\u00e9ciprocit\u00e9 forte version 1.1",
        true,
        false
    ],
    "Linux-man-pages-1-para": [
        "Linux man-pages - 1 paragraph",
        false,
        false
    ],
    "Linux-man-pages-copyleft": [
        "Linux man-pages Copyleft",
        false,
        false
    ],
    "Linux-man-pages-copyleft-2-para": [
        "Linux man-pages Copyleft - 2 paragraphs",
        false,
        false
    ],
    "Linux-man-pages-copyleft-var": [
        "Linux man-pages Copyleft Variant",
        false,
        false
    ],
    "Linux-OpenIB": [
        "Linux Kernel Variant of OpenIB.org license",
        false,
        false
    ],
    "LOOP": [
        "Common Lisp LOOP License",
        false,
        false
    ],
    "LPD-document": [
        "LPD Documentation License",
        false,
        false
    ],
    "LPL-1.0": [
        "Lucent Public License Version 1.0",
        true,
        false
    ],
    "LPL-1.02": [
        "Lucent Public License v1.02",
        true,
        false
    ],
    "LPPL-1.0": [
        "LaTeX Project Public License v1.0",
        false,
        false
    ],
    "LPPL-1.1": [
        "LaTeX Project Public License v1.1",
        false,
        false
    ],
    "LPPL-1.2": [
        "LaTeX Project Public License v1.2",
        false,
        false
    ],
    "LPPL-1.3a": [
        "LaTeX Project Public License v1.3a",
        false,
        false
    ],
    "LPPL-1.3c": [
        "LaTeX Project Public License v1.3c",
        true,
        false
    ],
    "lsof": [
        "lsof License",
        false,
        false
    ],
    "Lucida-Bitmap-Fonts": [
        "Lucida Bitmap Fonts License",
        false,
        false
    ],
    "LZMA-SDK-9.11-to-9.20": [
        "LZMA SDK License (versions 9.11 to 9.20)",
        false,
        false
    ],
    "LZMA-SDK-9.22": [
        "LZMA SDK License (versions 9.22 and beyond)",
        false,
        false
    ],
    "Mackerras-3-Clause": [
        "Mackerras 3-Clause License",
        false,
        false
    ],
    "Mackerras-3-Clause-acknowledgment": [
        "Mackerras 3-Clause - acknowledgment variant",
        false,
        false
    ],
    "magaz": [
        "magaz License",
        false,
        false
    ],
    "mailprio": [
        "mailprio License",
        false,
        false
    ],
    "MakeIndex": [
        "MakeIndex License",
        false,
        false
    ],
    "man2html": [
        "man2html License",
        false,
        false
    ],
    "Martin-Birgmeier": [
        "Martin Birgmeier License",
        false,
        false
    ],
    "McPhee-slideshow": [
        "McPhee Slideshow License",
        false,
        false
    ],
    "metamail": [
        "metamail License",
        false,
        false
    ],
    "Minpack": [
        "Minpack License",
        false,
        false
    ],
    "MIPS": [
        "MIPS License",
        false,
        false
    ],
    "MirOS": [
        "The MirOS Licence",
        true,
        false
    ],
    "MIT": [
        "MIT License",
        true,
        false
    ],
    "MIT-0": [
        "MIT No Attribution",
        true,
        false
    ],
    "MIT-advertising": [
        "Enlightenment License (e16)",
        false,
        false
    ],
    "MIT-Click": [
        "MIT Click License",
        false,
        false
    ],
    "MIT-CMU": [
        "CMU License",
        false,
        false
    ],
    "MIT-enna": [
        "enna License",
        false,
        false
    ],
    "MIT-feh": [
        "feh License",
        false,
        false
    ],
    "MIT-Festival": [
        "MIT Festival Variant",
        false,
        false
    ],
    "MIT-Khronos-old": [
        "MIT Khronos - old variant",
        false,
        false
    ],
    "MIT-Modern-Variant": [
        "MIT License Modern Variant",
        true,
        false
    ],
    "MIT-open-group": [
        "MIT Open Group variant",
        false,
        false
    ],
    "MIT-STK": [
        "MIT-STK License",
        false,
        false
    ],
    "MIT-testregex": [
        "MIT testregex Variant",
        false,
        false
    ],
    "MIT-Wu": [
        "MIT Tom Wu Variant",
        false,
        false
    ],
    "MITNFA": [
        "MIT +no-false-attribs license",
        false,
        false
    ],
    "MMIXware": [
        "MMIXware License",
        false,
        false
    ],
    "MMPL-1.0.1": [
        "Minecraft Mod Public License v1.0.1",
        false,
        false
    ],
    "Motosoto": [
        "Motosoto License",
        true,
        false
    ],
    "MPEG-SSG": [
        "MPEG Software Simulation",
        false,
        false
    ],
    "mpi-permissive": [
        "mpi Permissive License",
        false,
        false
    ],
    "mpich2": [
        "mpich2 License",
        false,
        false
    ],
    "MPL-1.0": [
        "Mozilla Public License 1.0",
        true,
        false
    ],
    "MPL-1.1": [
        "Mozilla Public License 1.1",
        true,
        false
    ],
    "MPL-2.0": [
        "Mozilla Public License 2.0",
        true,
        false
    ],
    "MPL-2.0-no-copyleft-exception": [
        "Mozilla Public License 2.0 (no copyleft exception)",
        true,
        false
    ],
    "mplus": [
        "mplus Font License",
        false,
        false
    ],
    "MS-LPL": [
        "Microsoft Limited Public License",
        false,
        false
    ],
    "MS-PL": [
        "Microsoft Public License",
        true,
        false
    ],
    "MS-RL": [
        "Microsoft Reciprocal License",
        true,
        false
    ],
    "MTLL": [
        "Matrix Template Library License",
        false,
        false
    ],
    "MulanPSL-1.0": [
        "Mulan Permissive Software License, Version 1",
        false,
        false
    ],
    "MulanPSL-2.0": [
        "Mulan Permissive Software License, Version 2",
        true,
        false
    ],
    "Multics": [
        "Multics License",
        true,
        false
    ],
    "Mup": [
        "Mup License",
        false,
        false
    ],
    "NAIST-2003": [
        "Nara Institute of Science and Technology License (2003)",
        false,
        false
    ],
    "NASA-1.3": [
        "NASA Open Source Agreement 1.3",
        true,
        false
    ],
    "Naumen": [
        "Naumen Public License",
        true,
        false
    ],
    "NBPL-1.0": [
        "Net Boolean Public License v1",
        false,
        false
    ],
    "NCBI-PD": [
        "NCBI Public Domain Notice",
        false,
        false
    ],
    "NCGL-UK-2.0": [
        "Non-Commercial Government Licence",
        false,
        false
    ],
    "NCL": [
        "NCL Source Code License",
        false,
        false
    ],
    "NCSA": [
        "University of Illinois/NCSA Open Source License",
        true,
        false
    ],
    "Net-SNMP": [
        "Net-SNMP License",
        false,
        true
    ],
    "NetCDF": [
        "NetCDF license",
        false,
        false
    ],
    "Newsletr": [
        "Newsletr License",
        false,
        false
    ],
    "NGPL": [
        "Nethack General Public License",
        true,
        false
    ],
    "ngrep": [
        "ngrep License",
        false,
        false
    ],
    "NICTA-1.0": [
        "NICTA Public Software License, Version 1.0",
        false,
        false
    ],
    "NIST-PD": [
        "NIST Public Domain Notice",
        false,
        false
    ],
    "NIST-PD-fallback": [
        "NIST Public Domain Notice with license fallback",
        false,
        false
    ],
    "NIST-PD-TNT": [
        "NIST    Public Domain Notice TNT variant",
        false,
        false
    ],
    "NIST-Software": [
        "NIST Software License",
        false,
        false
    ],
    "NLOD-1.0": [
        "Norwegian Licence for Open Government Data (NLOD) 1.0",
        false,
        false
    ],
    "NLOD-2.0": [
        "Norwegian Licence for Open Government Data (NLOD) 2.0",
        false,
        false
    ],
    "NLPL": [
        "No Limit Public License",
        false,
        false
    ],
    "Nokia": [
        "Nokia Open Source License",
        true,
        false
    ],
    "NOSL": [
        "Netizen Open Source License",
        false,
        false
    ],
    "Noweb": [
        "Noweb License",
        false,
        false
    ],
    "NPL-1.0": [
        "Netscape Public License v1.0",
        false,
        false
    ],
    "NPL-1.1": [
        "Netscape Public License v1.1",
        false,
        false
    ],
    "NPOSL-3.0": [
        "Non-Profit Open Software License 3.0",
        true,
        false
    ],
    "NRL": [
        "NRL License",
        false,
        false
    ],
    "NTIA-PD": [
        "NTIA Public Domain Notice",
        false,
        false
    ],
    "NTP": [
        "NTP License",
        true,
        false
    ],
    "NTP-0": [
        "NTP No Attribution",
        false,
        false
    ],
    "Nunit": [
        "Nunit License",
        false,
        true
    ],
    "O-UDA-1.0": [
        "Open Use of Data Agreement v1.0",
        false,
        false
    ],
    "OAR": [
        "OAR License",
        false,
        false
    ],
    "OCCT-PL": [
        "Open CASCADE Technology Public License",
        false,
        false
    ],
    "OCLC-2.0": [
        "OCLC Research Public License 2.0",
        true,
        false
    ],
    "ODbL-1.0": [
        "Open Data Commons Open Database License v1.0",
        false,
        false
    ],
    "ODC-By-1.0": [
        "Open Data Commons Attribution License v1.0",
        false,
        false
    ],
    "OFFIS": [
        "OFFIS License",
        false,
        false
    ],
    "OFL-1.0": [
        "SIL Open Font License 1.0",
        false,
        false
    ],
    "OFL-1.0-no-RFN": [
        "SIL Open Font License 1.0 with no Reserved Font Name",
        false,
        false
    ],
    "OFL-1.0-RFN": [
        "SIL Open Font License 1.0 with Reserved Font Name",
        false,
        false
    ],
    "OFL-1.1": [
        "SIL Open Font License 1.1",
        true,
        false
    ],
    "OFL-1.1-no-RFN": [
        "SIL Open Font License 1.1 with no Reserved Font Name",
        true,
        false
    ],
    "OFL-1.1-RFN": [
        "SIL Open Font License 1.1 with Reserved Font Name",
        true,
        false
    ],
    "OGC-1.0": [
        "OGC Software License, Version 1.0",
        false,
        false
    ],
    "OGDL-Taiwan-1.0": [
        "Taiwan Open Government Data License, version 1.0",
        false,
        false
    ],
    "OGL-Canada-2.0": [
        "Open Government Licence - Canada",
        false,
        false
    ],
    "OGL-UK-1.0": [
        "Open Government Licence v1.0",
        false,
        false
    ],
    "OGL-UK-2.0": [
        "Open Government Licence v2.0",
        false,
        false
    ],
    "OGL-UK-3.0": [
        "Open Government Licence v3.0",
        false,
        false
    ],
    "OGTSL": [
        "Open Group Test Suite License",
        true,
        false
    ],
    "OLDAP-1.1": [
        "Open LDAP Public License v1.1",
        false,
        false
    ],
    "OLDAP-1.2": [
        "Open LDAP Public License v1.2",
        false,
        false
    ],
    "OLDAP-1.3": [
        "Open LDAP Public License v1.3",
        false,
        false
    ],
    "OLDAP-1.4": [
        "Open LDAP Public License v1.4",
        false,
        false
    ],
    "OLDAP-2.0": [
        "Open LDAP Public License v2.0 (or possibly 2.0A and 2.0B)",
        false,
        false
    ],
    "OLDAP-2.0.1": [
        "Open LDAP Public License v2.0.1",
        false,
        false
    ],
    "OLDAP-2.1": [
        "Open LDAP Public License v2.1",
        false,
        false
    ],
    "OLDAP-2.2": [
        "Open LDAP Public License v2.2",
        false,
        false
    ],
    "OLDAP-2.2.1": [
        "Open LDAP Public License v2.2.1",
        false,
        false
    ],
    "OLDAP-2.2.2": [
        "Open LDAP Public License 2.2.2",
        false,
        false
    ],
    "OLDAP-2.3": [
        "Open LDAP Public License v2.3",
        false,
        false
    ],
    "OLDAP-2.4": [
        "Open LDAP Public License v2.4",
        false,
        false
    ],
    "OLDAP-2.5": [
        "Open LDAP Public License v2.5",
        false,
        false
    ],
    "OLDAP-2.6": [
        "Open LDAP Public License v2.6",
        false,
        false
    ],
    "OLDAP-2.7": [
        "Open LDAP Public License v2.7",
        false,
        false
    ],
    "OLDAP-2.8": [
        "Open LDAP Public License v2.8",
        true,
        false
    ],
    "OLFL-1.3": [
        "Open Logistics Foundation License Version 1.3",
        true,
        false
    ],
    "OML": [
        "Open Market License",
        false,
        false
    ],
    "OpenMDW-1.0": [
        "OpenMDW License Agreement v1.0",
        false,
        false
    ],
    "OpenPBS-2.3": [
        "OpenPBS v2.3 Software License",
        false,
        false
    ],
    "OpenSSL": [
        "OpenSSL License",
        false,
        false
    ],
    "OpenSSL-standalone": [
        "OpenSSL License - standalone",
        false,
        false
    ],
    "OpenVision": [
        "OpenVision License",
        false,
        false
    ],
    "OPL-1.0": [
        "Open Public License v1.0",
        false,
        false
    ],
    "OPL-UK-3.0": [
        "United    Kingdom Open Parliament Licence v3.0",
        false,
        false
    ],
    "OPUBL-1.0": [
        "Open Publication License v1.0",
        false,
        false
    ],
    "OSC-1.0": [
        "OSC License 1.0",
        true,
        false
    ],
    "OSET-PL-2.1": [
        "OSET Public License version 2.1",
        true,
        false
    ],
    "OSL-1.0": [
        "Open Software License 1.0",
        true,
        false
    ],
    "OSL-1.1": [
        "Open Software License 1.1",
        false,
        false
    ],
    "OSL-2.0": [
        "Open Software License 2.0",
        true,
        false
    ],
    "OSL-2.1": [
        "Open Software License 2.1",
        true,
        false
    ],
    "OSL-3.0": [
        "Open Software License 3.0",
        true,
        false
    ],
    "OSSP": [
        "OSSP License",
        false,
        false
    ],
    "PADL": [
        "PADL License",
        false,
        false
    ],
    "ParaType-Free-Font-1.3": [
        "ParaType Free Font Licensing Agreement v1.3",
        false,
        false
    ],
    "Parity-6.0.0": [
        "The Parity Public License 6.0.0",
        false,
        false
    ],
    "Parity-7.0.0": [
        "The Parity Public License 7.0.0",
        false,
        false
    ],
    "PDDL-1.0": [
        "Open Data Commons Public Domain Dedication & License 1.0",
        false,
        false
    ],
    "PHP-3.0": [
        "PHP License v3.0",
        true,
        false
    ],
    "PHP-3.01": [
        "PHP License v3.01",
        true,
        false
    ],
    "Pixar": [
        "Pixar License",
        false,
        false
    ],
    "pkgconf": [
        "pkgconf License",
        false,
        false
    ],
    "Plexus": [
        "Plexus Classworlds License",
        false,
        false
    ],
    "pnmstitch": [
        "pnmstitch License",
        false,
        false
    ],
    "PolyForm-Noncommercial-1.0.0": [
        "PolyForm Noncommercial License 1.0.0",
        false,
        false
    ],
    "PolyForm-Small-Business-1.0.0": [
        "PolyForm Small Business License 1.0.0",
        false,
        false
    ],
    "PostgreSQL": [
        "PostgreSQL License",
        true,
        false
    ],
    "PPL": [
        "Peer Production License",
        false,
        false
    ],
    "PSF-2.0": [
        "Python Software Foundation License 2.0",
        false,
        false
    ],
    "psfrag": [
        "psfrag License",
        false,
        false
    ],
    "psutils": [
        "psutils License",
        false,
        false
    ],
    "Python-2.0": [
        "Python License 2.0",
        true,
        false
    ],
    "Python-2.0.1": [
        "Python License 2.0.1",
        false,
        false
    ],
    "python-ldap": [
        "Python ldap License",
        false,
        false
    ],
    "Qhull": [
        "Qhull License",
        false,
        false
    ],
    "QPL-1.0": [
        "Q Public License 1.0",
        true,
        false
    ],
    "QPL-1.0-INRIA-2004": [
        "Q Public License 1.0 - INRIA 2004 variant",
        false,
        false
    ],
    "radvd": [
        "radvd License",
        false,
        false
    ],
    "Rdisc": [
        "Rdisc License",
        false,
        false
    ],
    "RHeCos-1.1": [
        "Red Hat eCos Public License v1.1",
        false,
        false
    ],
    "RPL-1.1": [
        "Reciprocal Public License 1.1",
        true,
        false
    ],
    "RPL-1.5": [
        "Reciprocal Public License 1.5",
        true,
        false
    ],
    "RPSL-1.0": [
        "RealNetworks Public Source License v1.0",
        true,
        false
    ],
    "RSA-MD": [
        "RSA Message-Digest License",
        false,
        false
    ],
    "RSCPL": [
        "Ricoh Source Code Public License",
        true,
        false
    ],
    "Ruby": [
        "Ruby License",
        false,
        false
    ],
    "Ruby-pty": [
        "Ruby pty extension license",
        false,
        false
    ],
    "SAX-PD": [
        "Sax Public Domain Notice",
        false,
        false
    ],
    "SAX-PD-2.0": [
        "Sax Public Domain Notice 2.0",
        false,
        false
    ],
    "Saxpath": [
        "Saxpath License",
        false,
        false
    ],
    "SCEA": [
        "SCEA Shared Source License",
        false,
        false
    ],
    "SchemeReport": [
        "Scheme Language Report License",
        false,
        false
    ],
    "Sendmail": [
        "Sendmail License",
        false,
        false
    ],
    "Sendmail-8.23": [
        "Sendmail License 8.23",
        false,
        false
    ],
    "Sendmail-Open-Source-1.1": [
        "Sendmail Open Source License v1.1",
        false,
        false
    ],
    "SGI-B-1.0": [
        "SGI Free Software License B v1.0",
        false,
        false
    ],
    "SGI-B-1.1": [
        "SGI Free Software License B v1.1",
        false,
        false
    ],
    "SGI-B-2.0": [
        "SGI Free Software License B v2.0",
        false,
        false
    ],
    "SGI-OpenGL": [
        "SGI OpenGL License",
        false,
        false
    ],
    "SGMLUG-PM": [
        "SGMLUG Parser Materials License",
        false,
        false
    ],
    "SGP4": [
        "SGP4 Permission Notice",
        false,
        false
    ],
    "SHL-0.5": [
        "Solderpad Hardware License v0.5",
        false,
        false
    ],
    "SHL-0.51": [
        "Solderpad Hardware License, Version 0.51",
        false,
        false
    ],
    "SimPL-2.0": [
        "Simple Public License 2.0",
        true,
        false
    ],
    "SISSL": [
        "Sun Industry Standards Source License v1.1",
        true,
        false
    ],
    "SISSL-1.2": [
        "Sun Industry Standards Source License v1.2",
        false,
        false
    ],
    "SL": [
        "SL License",
        false,
        false
    ],
    "Sleepycat": [
        "Sleepycat License",
        true,
        false
    ],
    "SMAIL-GPL": [
        "SMAIL General Public License",
        false,
        false
    ],
    "SMLNJ": [
        "Standard ML of New Jersey License",
        false,
        false
    ],
    "SMPPL": [
        "Secure Messaging Protocol Public License",
        false,
        false
    ],
    "SNIA": [
        "SNIA Public License 1.1",
        false,
        false
    ],
    "snprintf": [
        "snprintf License",
        false,
        false
    ],
    "SOFA": [
        "SOFA Software License",
        false,
        false
    ],
    "softSurfer": [
        "softSurfer License",
        false,
        false
    ],
    "Soundex": [
        "Soundex License",
        false,
        false
    ],
    "Spencer-86": [
        "Spencer License 86",
        false,
        false
    ],
    "Spencer-94": [
        "Spencer License 94",
        false,
        false
    ],
    "Spencer-99": [
        "Spencer License 99",
        false,
        false
    ],
    "SPL-1.0": [
        "Sun Public License v1.0",
        true,
        false
    ],
    "ssh-keyscan": [
        "ssh-keyscan License",
        false,
        false
    ],
    "SSH-OpenSSH": [
        "SSH OpenSSH license",
        false,
        false
    ],
    "SSH-short": [
        "SSH short notice",
        false,
        false
    ],
    "SSLeay-standalone": [
        "SSLeay License - standalone",
        false,
        false
    ],
    "SSPL-1.0": [
        "Server Side Public License, v 1",
        false,
        false
    ],
    "StandardML-NJ": [
        "Standard ML of New Jersey License",
        false,
        true
    ],
    "SugarCRM-1.1.3": [
        "SugarCRM Public License v1.1.3",
        false,
        false
    ],
    "SUL-1.0": [
        "Sustainable Use License v1.0",
        false,
        false
    ],
    "Sun-PPP": [
        "Sun PPP License",
        false,
        false
    ],
    "Sun-PPP-2000": [
        "Sun PPP License (2000)",
        false,
        false
    ],
    "SunPro": [
        "SunPro License",
        false,
        false
    ],
    "SWL": [
        "Scheme Widget Library (SWL) Software License Agreement",
        false,
        false
    ],
    "swrule": [
        "swrule License",
        false,
        false
    ],
    "Symlinks": [
        "Symlinks License",
        false,
        false
    ],
    "TAPR-OHL-1.0": [
        "TAPR Open Hardware License v1.0",
        false,
        false
    ],
    "TCL": [
        "TCL/TK License",
        false,
        false
    ],
    "TCP-wrappers": [
        "TCP Wrappers License",
        false,
        false
    ],
    "TekHVC": [
        "TekHVC License",
        false,
        false
    ],
    "TermReadKey": [
        "TermReadKey License",
        false,
        false
    ],
    "TGPPL-1.0": [
        "Transitive Grace Period Public Licence 1.0",
        false,
        false
    ],
    "ThirdEye": [
        "ThirdEye License",
        false,
        false
    ],
    "threeparttable": [
        "threeparttable License",
        false,
        false
    ],
    "TMate": [
        "TMate Open Source License",
        false,
        false
    ],
    "TORQUE-1.1": [
        "TORQUE v2.5+ Software License v1.1",
        false,
        false
    ],
    "TOSL": [
        "Trusster Open Source License",
        false,
        false
    ],
    "TPDL": [
        "Time::ParseDate License",
        false,
        false
    ],
    "TPL-1.0": [
        "THOR Public License 1.0",
        false,
        false
    ],
    "TrustedQSL": [
        "TrustedQSL License",
        false,
        false
    ],
    "TTWL": [
        "Text-Tabs+Wrap License",
        false,
        false
    ],
    "TTYP0": [
        "TTYP0 License",
        false,
        false
    ],
    "TU-Berlin-1.0": [
        "Technische Universitaet Berlin License 1.0",
        false,
        false
    ],
    "TU-Berlin-2.0": [
        "Technische Universitaet Berlin License 2.0",
        false,
        false
    ],
    "Ubuntu-font-1.0": [
        "Ubuntu Font Licence v1.0",
        false,
        false
    ],
    "UCAR": [
        "UCAR License",
        false,
        false
    ],
    "UCL-1.0": [
        "Upstream Compatibility License v1.0",
        true,
        false
    ],
    "ulem": [
        "ulem License",
        false,
        false
    ],
    "UMich-Merit": [
        "Michigan/Merit Networks License",
        false,
        false
    ],
    "Unicode-3.0": [
        "Unicode License v3",
        true,
        false
    ],
    "Unicode-DFS-2015": [
        "Unicode License Agreement - Data Files and Software (2015)",
        false,
        false
    ],
    "Unicode-DFS-2016": [
        "Unicode License Agreement - Data Files and Software (2016)",
        true,
        false
    ],
    "Unicode-TOU": [
        "Unicode Terms of Use",
        false,
        false
    ],
    "UnixCrypt": [
        "UnixCrypt License",
        false,
        false
    ],
    "Unlicense": [
        "The Unlicense",
        true,
        false
    ],
    "Unlicense-libtelnet": [
        "Unlicense - libtelnet variant",
        false,
        false
    ],
    "Unlicense-libwhirlpool": [
        "Unlicense - libwhirlpool variant",
        false,
        false
    ],
    "UnRAR": [
        "UnRAR License",
        false,
        false
    ],
    "UPL-1.0": [
        "Universal Permissive License v1.0",
        true,
        false
    ],
    "URT-RLE": [
        "Utah Raster Toolkit Run Length Encoded License",
        false,
        false
    ],
    "Vim": [
        "Vim License",
        false,
        false
    ],
    "Vixie-Cron": [
        "Vixie Cron License",
        false,
        false
    ],
    "VOSTROM": [
        "VOSTROM Public License for Open Source",
        false,
        false
    ],
    "VSL-1.0": [
        "Vovida Software License v1.0",
        true,
        false
    ],
    "W3C": [
        "W3C Software Notice and License (2002-12-31)",
        true,
        false
    ],
    "W3C-19980720": [
        "W3C Software Notice and License (1998-07-20)",
        false,
        false
    ],
    "W3C-20150513": [
        "W3C Software Notice and Document License (2015-05-13)",
        true,
        false
    ],
    "w3m": [
        "w3m License",
        false,
        false
    ],
    "Watcom-1.0": [
        "Sybase Open Watcom Public License 1.0",
        true,
        false
    ],
    "Widget-Workshop": [
        "Widget Workshop License",
        false,
        false
    ],
    "WordNet": [
        "WordNet License",
        true,
        false
    ],
    "Wsuipa": [
        "Wsuipa License",
        false,
        false
    ],
    "WTFNMFPL": [
        "Do What The F*ck You Want To But It's Not My Fault Public License",
        false,
        false
    ],
    "WTFPL": [
        "Do What The F*ck You Want To Public License",
        false,
        false
    ],
    "wwl": [
        "WWL License",
        false,
        false
    ],
    "wxWindows": [
        "wxWindows Library License",
        true,
        true
    ],
    "X11": [
        "X11 License",
        false,
        false
    ],
    "X11-distribute-modifications-variant": [
        "X11 License Distribution Modification Variant",
        false,
        false
    ],
    "X11-no-permit-persons": [
        "X11 no permit persons clause",
        false,
        false
    ],
    "X11-swapped": [
        "X11 swapped final paragraphs",
        false,
        false
    ],
    "Xdebug-1.03": [
        "Xdebug License v 1.03",
        false,
        false
    ],
    "Xerox": [
        "Xerox License",
        false,
        false
    ],
    "Xfig": [
        "Xfig License",
        false,
        false
    ],
    "XFree86-1.1": [
        "XFree86 License 1.1",
        false,
        false
    ],
    "xinetd": [
        "xinetd License",
        false,
        false
    ],
    "xkeyboard-config-Zinoviev": [
        "xkeyboard-config Zinoviev License",
        false,
        false
    ],
    "xlock": [
        "xlock License",
        false,
        false
    ],
    "Xnet": [
        "X.Net License",
        true,
        false
    ],
    "xpp": [
        "XPP License",
        false,
        false
    ],
    "XSkat": [
        "XSkat License",
        false,
        false
    ],
    "xzoom": [
        "xzoom License",
        false,
        false
    ],
    "YPL-1.0": [
        "Yahoo! Public License v1.0",
        false,
        false
    ],
    "YPL-1.1": [
        "Yahoo! Public License v1.1",
        false,
        false
    ],
    "Zed": [
        "Zed License",
        false,
        false
    ],
    "Zeeff": [
        "Zeeff License",
        false,
        false
    ],
    "Zend-2.0": [
        "Zend License v2.0",
        false,
        false
    ],
    "Zimbra-1.3": [
        "Zimbra Public License v1.3",
        false,
        false
    ],
    "Zimbra-1.4": [
        "Zimbra Public License v1.4",
        false,
        false
    ],
    "Zlib": [
        "zlib License",
        true,
        false
    ],
    "zlib-acknowledgement": [
        "zlib/libpng License with Acknowledgement",
        false,
        false
    ],
    "ZPL-1.1": [
        "Zope Public License 1.1",
        false,
        false
    ],
    "ZPL-2.0": [
        "Zope Public License 2.0",
        true,
        false
    ],
    "ZPL-2.1": [
        "Zope Public License 2.1",
        true,
        false
    ]
}{
    "389-exception": [
        "389 Directory Server Exception"
    ],
    "Asterisk-exception": [
        "Asterisk exception"
    ],
    "Asterisk-linking-protocols-exception": [
        "Asterisk linking protocols exception"
    ],
    "Autoconf-exception-2.0": [
        "Autoconf exception 2.0"
    ],
    "Autoconf-exception-3.0": [
        "Autoconf exception 3.0"
    ],
    "Autoconf-exception-generic": [
        "Autoconf generic exception"
    ],
    "Autoconf-exception-generic-3.0": [
        "Autoconf generic exception for GPL-3.0"
    ],
    "Autoconf-exception-macro": [
        "Autoconf macro exception"
    ],
    "Bison-exception-1.24": [
        "Bison exception 1.24"
    ],
    "Bison-exception-2.2": [
        "Bison exception 2.2"
    ],
    "Bootloader-exception": [
        "Bootloader Distribution Exception"
    ],
    "CGAL-linking-exception": [
        "CGAL Linking Exception"
    ],
    "Classpath-exception-2.0": [
        "Classpath exception 2.0"
    ],
    "Classpath-exception-2.0-short": [
        "Classpath exception 2.0 - short"
    ],
    "CLISP-exception-2.0": [
        "CLISP exception 2.0"
    ],
    "cryptsetup-OpenSSL-exception": [
        "cryptsetup OpenSSL exception"
    ],
    "Digia-Qt-LGPL-exception-1.1": [
        "Digia Qt LGPL Exception version 1.1"
    ],
    "DigiRule-FOSS-exception": [
        "DigiRule FOSS License Exception"
    ],
    "eCos-exception-2.0": [
        "eCos exception 2.0"
    ],
    "erlang-otp-linking-exception": [
        "Erlang/OTP Linking Exception"
    ],
    "Fawkes-Runtime-exception": [
        "Fawkes Runtime Exception"
    ],
    "FLTK-exception": [
        "FLTK exception"
    ],
    "fmt-exception": [
        "fmt exception"
    ],
    "Font-exception-2.0": [
        "Font exception 2.0"
    ],
    "freertos-exception-2.0": [
        "FreeRTOS Exception 2.0"
    ],
    "GCC-exception-2.0": [
        "GCC Runtime Library exception 2.0"
    ],
    "GCC-exception-2.0-note": [
        "GCC    Runtime Library exception 2.0 - note variant"
    ],
    "GCC-exception-3.1": [
        "GCC Runtime Library exception 3.1"
    ],
    "Gmsh-exception": [
        "Gmsh exception"
    ],
    "GNAT-exception": [
        "GNAT exception"
    ],
    "GNOME-examples-exception": [
        "GNOME examples exception"
    ],
    "GNU-compiler-exception": [
        "GNU Compiler Exception"
    ],
    "gnu-javamail-exception": [
        "GNU JavaMail exception"
    ],
    "GPL-3.0-389-ds-base-exception": [
        "GPL-3.0 389 DS Base Exception"
    ],
    "GPL-3.0-interface-exception": [
        "GPL-3.0 Interface Exception"
    ],
    "GPL-3.0-linking-exception": [
        "GPL-3.0 Linking Exception"
    ],
    "GPL-3.0-linking-source-exception": [
        "GPL-3.0 Linking Exception (with Corresponding Source)"
    ],
    "GPL-CC-1.0": [
        "GPL Cooperation Commitment 1.0"
    ],
    "GStreamer-exception-2005": [
        "GStreamer Exception (2005)"
    ],
    "GStreamer-exception-2008": [
        "GStreamer Exception (2008)"
    ],
    "harbour-exception": [
        "harbour exception"
    ],
    "i2p-gpl-java-exception": [
        "i2p GPL+Java Exception"
    ],
    "Independent-modules-exception": [
        "Independent Module Linking exception"
    ],
    "KiCad-libraries-exception": [
        "KiCad Libraries Exception"
    ],
    "kvirc-openssl-exception": [
        "kvirc OpenSSL Exception"
    ],
    "LGPL-3.0-linking-exception": [
        "LGPL-3.0 Linking Exception"
    ],
    "libpri-OpenH323-exception": [
        "libpri OpenH323 exception"
    ],
    "Libtool-exception": [
        "Libtool Exception"
    ],
    "Linux-syscall-note": [
        "Linux Syscall Note"
    ],
    "LLGPL": [
        "LLGPL Preamble"
    ],
    "LLVM-exception": [
        "LLVM Exception"
    ],
    "LZMA-exception": [
        "LZMA exception"
    ],
    "mif-exception": [
        "Macros and Inline Functions Exception"
    ],
    "mxml-exception": [
        "mxml Exception"
    ],
    "Nokia-Qt-exception-1.1": [
        "Nokia Qt LGPL exception 1.1"
    ],
    "OCaml-LGPL-linking-exception": [
        "OCaml LGPL Linking Exception"
    ],
    "OCCT-exception-1.0": [
        "Open CASCADE Exception 1.0"
    ],
    "OpenJDK-assembly-exception-1.0": [
        "OpenJDK Assembly exception 1.0"
    ],
    "openvpn-openssl-exception": [
        "OpenVPN OpenSSL Exception"
    ],
    "PCRE2-exception": [
        "PCRE2 exception"
    ],
    "polyparse-exception": [
        "Polyparse Exception"
    ],
    "PS-or-PDF-font-exception-20170817": [
        "PS/PDF font exception (2017-08-17)"
    ],
    "QPL-1.0-INRIA-2004-exception": [
        "INRIA QPL 1.0 2004 variant exception"
    ],
    "Qt-GPL-exception-1.0": [
        "Qt GPL exception 1.0"
    ],
    "Qt-LGPL-exception-1.1": [
        "Qt LGPL exception 1.1"
    ],
    "Qwt-exception-1.0": [
        "Qwt exception 1.0"
    ],
    "romic-exception": [
        "Romic Exception"
    ],
    "RRDtool-FLOSS-exception-2.0": [
        "RRDtool FLOSS exception 2.0"
    ],
    "rsync-linking-exception": [
        "rsync Linking Exception"
    ],
    "SANE-exception": [
        "SANE Exception"
    ],
    "SHL-2.0": [
        "Solderpad Hardware License v2.0"
    ],
    "SHL-2.1": [
        "Solderpad Hardware License v2.1"
    ],
    "Simple-Library-Usage-exception": [
        "Simple Library Usage Exception"
    ],
    "sqlitestudio-OpenSSL-exception": [
        "sqlitestudio OpenSSL exception"
    ],
    "stunnel-exception": [
        "stunnel Exception"
    ],
    "SWI-exception": [
        "SWI exception"
    ],
    "Swift-exception": [
        "Swift Exception"
    ],
    "Texinfo-exception": [
        "Texinfo exception"
    ],
    "u-boot-exception-2.0": [
        "U-Boot exception 2.0"
    ],
    "UBDL-exception": [
        "Unmodified Binary Distribution exception"
    ],
    "Universal-FOSS-exception-1.0": [
        "Universal FOSS Exception, Version 1.0"
    ],
    "vsftpd-openssl-exception": [
        "vsftpd OpenSSL exception"
    ],
    "WxWindows-exception-3.1": [
        "WxWindows Library Exception 3.1"
    ],
    "x11vnc-openssl-exception": [
        "x11vnc OpenSSL Exception"
    ]
}Copyright (C) 2015 Composer

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.
<?php declare(strict_types=1);

/*
 * This file is part of composer/spdx-licenses.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Spdx;

class SpdxLicenses
{
    /** @var string */
    public const LICENSES_FILE = 'spdx-licenses.json';

    /** @var string */
    public const EXCEPTIONS_FILE = 'spdx-exceptions.json';

    /**
     * Contains all the licenses.
     *
     * The array is indexed by license identifiers, which contain
     * a numerically indexed array with license details.
     *
     *  [ lowercased license identifier =>
     *      [ 0 => identifier (string), 1 => full name (string), 2 => osi certified (bool), 3 => deprecated (bool) ]
     *    , ...
     *  ]
     *
     * @var array<string, array{0: string, 1: string, 2: bool, 3: bool}>
     */
    private $licenses;

    /**
     * @var string
     */
    private $licensesExpression;

    /**
     * Contains all the license exceptions.
     *
     * The array is indexed by license exception identifiers, which contain
     * a numerically indexed array with license exception details.
     *
     *  [ lowercased exception identifier =>
     *      [ 0 => exception identifier (string), 1 => full name (string) ]
     *    , ...
     *  ]
     *
     * @var array<string, array{0: string, 1: string}>
     */
    private $exceptions;

    /**
     * @var string
     */
    private $exceptionsExpression;

    public function __construct()
    {
        $this->loadLicenses();
        $this->loadExceptions();
    }

    /**
     * Returns license metadata by license identifier.
     *
     * This function adds a link to the full license text to the license metadata.
     * The array returned is in the form of:
     *
     *  [ 0 => full name (string), 1 => osi certified, 2 => link to license text (string), 3 => deprecation status (bool) ]
     *
     * @param string $identifier
     *
     * @return array{0: string, 1: bool, 2: string, 3: bool}|null
     */
    public function getLicenseByIdentifier($identifier)
    {
        $key = strtolower($identifier);

        if (!isset($this->licenses[$key])) {
            return null;
        }

        [$identifier, $name, $isOsiApproved, $isDeprecatedLicenseId] = $this->licenses[$key];

        return [
            $name,
            $isOsiApproved,
            'https://spdx.org/licenses/' . $identifier . '.html#licenseText',
            $isDeprecatedLicenseId,
        ];
    }

    /**
     * Returns all licenses information, keyed by the lowercased license identifier.
     *
     * @return array{0: string, 1: string, 2: bool, 3: bool}[] Each item is [ 0 => identifier (string), 1 => full name (string), 2 => osi certified (bool), 3 => deprecated (bool) ]
     */
    public function getLicenses()
    {
        return $this->licenses;
    }

    /**
     * Returns license exception metadata by license exception identifier.
     *
     * This function adds a link to the full license exception text to the license exception metadata.
     * The array returned is in the form of:
     *
     *  [ 0 => full name (string), 1 => link to license text (string) ]
     *
     * @param string $identifier
     *
     * @return array{0: string, 1: string}|null
     */
    public function getExceptionByIdentifier($identifier)
    {
        $key = strtolower($identifier);

        if (!isset($this->exceptions[$key])) {
            return null;
        }

        [$identifier, $name] = $this->exceptions[$key];

        return [
            $name,
            'https://spdx.org/licenses/' . $identifier . '.html#licenseExceptionText',
        ];
    }

    /**
     * Returns the short identifier of a license (or license exception) by full name.
     *
     * @param string $name
     *
     * @return string|null
     */
    public function getIdentifierByName($name)
    {
        foreach ($this->licenses as $licenseData) {
            if ($licenseData[1] === $name) {
                return $licenseData[0];
            }
        }

        foreach ($this->exceptions as $licenseData) {
            if ($licenseData[1] === $name) {
                return $licenseData[0];
            }
        }

        return null;
    }

    /**
     * Returns the OSI Approved status for a license by identifier.
     *
     * @param string $identifier
     *
     * @return bool
     */
    public function isOsiApprovedByIdentifier($identifier)
    {
        return $this->licenses[strtolower($identifier)][2];
    }

    /**
     * Returns the deprecation status for a license by identifier.
     *
     * @param string $identifier
     *
     * @return bool
     */
    public function isDeprecatedByIdentifier($identifier)
    {
        return $this->licenses[strtolower($identifier)][3];
    }

    /**
     * @param string[]|string $license
     *
     * @throws \InvalidArgumentException
     *
     * @return bool
     */
    public function validate($license)
    {
        if (is_array($license)) {
            $count = count($license);
            if ($count !== count(array_filter($license, 'is_string'))) {
                throw new \InvalidArgumentException('Array of strings expected.');
            }
            $license = $count > 1 ? '(' . implode(' OR ', $license) . ')' : (string) reset($license);
        }

        if (!is_string($license)) {
            throw new \InvalidArgumentException(sprintf(
                'Array or String expected, %s given.',
                gettype($license)
            ));
        }

        return $this->isValidLicenseString($license);
    }

    /**
     * @return string
     */
    public static function getResourcesDir()
    {
        return dirname(__DIR__) . '/res';
    }

    /**
     * @return void
     */
    private function loadLicenses()
    {
        if (null !== $this->licenses) {
            return;
        }

        $json = file_get_contents(self::getResourcesDir() . '/' . self::LICENSES_FILE);
        if (false === $json) {
            throw new \RuntimeException('Missing license file in ' . self::getResourcesDir() . '/' . self::LICENSES_FILE);
        }
        $this->licenses = [];

        foreach (json_decode($json, true) as $identifier => $license) {
            $this->licenses[strtolower($identifier)] = [$identifier, $license[0], $license[1], $license[2]];
        }
    }

    /**
     * @return void
     */
    private function loadExceptions()
    {
        if (null !== $this->exceptions) {
            return;
        }

        $json = file_get_contents(self::getResourcesDir() . '/' . self::EXCEPTIONS_FILE);
        if (false === $json) {
            throw new \RuntimeException('Missing exceptions file in ' . self::getResourcesDir() . '/' . self::EXCEPTIONS_FILE);
        }
        $this->exceptions = [];

        foreach (json_decode($json, true) as $identifier => $exception) {
            $this->exceptions[strtolower($identifier)] = [$identifier, $exception[0]];
        }
    }

    /**
     * @return string
     */
    private function getLicensesExpression()
    {
        if (null === $this->licensesExpression) {
            $licenses = array_map('preg_quote', array_keys($this->licenses));
            rsort($licenses);
            $licenses = implode('|', $licenses);
            $this->licensesExpression = $licenses;
        }

        return $this->licensesExpression;
    }

    /**
     * @return string
     */
    private function getExceptionsExpression()
    {
        if (null === $this->exceptionsExpression) {
            $exceptions = array_map('preg_quote', array_keys($this->exceptions));
            rsort($exceptions);
            $exceptions = implode('|', $exceptions);
            $this->exceptionsExpression = $exceptions;
        }

        return $this->exceptionsExpression;
    }

    /**
     * @param string $license
     *
     * @throws \RuntimeException
     *
     * @return bool
     */
    private function isValidLicenseString($license)
    {
        if (isset($this->licenses[strtolower($license)])) {
            return true;
        }

        $licenses = $this->getLicensesExpression();
        $exceptions = $this->getExceptionsExpression();

        $regex = <<<REGEX
{
(?(DEFINE)
    # idstring: 1*( ALPHA / DIGIT / - / . )
    (?<idstring>[\pL\pN.-]{1,})

    # license-id: taken from list
    (?<licenseid>{$licenses})

    # license-exception-id: taken from list
    (?<licenseexceptionid>{$exceptions})

    # license-ref: [DocumentRef-1*(idstring):]LicenseRef-1*(idstring)
    (?<licenseref>(?:DocumentRef-(?&idstring):)?LicenseRef-(?&idstring))

    # simple-expresssion: license-id / license-id+ / license-ref
    (?<simple_expression>(?&licenseid)\+? | (?&licenseid) | (?&licenseref))

    # compound-expression: 1*(
    #   simple-expression /
    #   simple-expression WITH license-exception-id /
    #   compound-expression AND compound-expression /
    #   compound-expression OR compound-expression
    # ) / ( compound-expression ) )
    (?<compound_head>
        (?&simple_expression) ( \s+ WITH \s+ (?&licenseexceptionid))?
            | \( \s* (?&compound_expression) \s* \)
    )
    (?<compound_expression>
        (?&compound_head) (?: \s+ (?:AND|OR) \s+ (?&compound_expression))?
    )

    # license-expression: 1*1(simple-expression / compound-expression)
    (?<license_expression>(?&compound_expression) | (?&simple_expression))
) # end of define

^(NONE | NOASSERTION | (?&license_expression))$
}xi
REGEX;

        $match = preg_match($regex, $license);

        if (0 === $match) {
            return false;
        }

        if (false === $match) {
            throw new \RuntimeException('Regex failed to compile/run.');
        }

        return true;
    }
}
<?php

// platform_check.php @generated by Composer

$issues = array();

if (!(PHP_VERSION_ID >= 80100)) {
    $issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.';
}

if ($issues) {
    if (!headers_sent()) {
        header('HTTP/1.1 500 Internal Server Error');
    }
    if (!ini_get('display_errors')) {
        if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
            fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
        } elseif (!headers_sent()) {
            echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
        }
    }
    throw new \RuntimeException(
        'Composer detected issues in your platform: ' . implode(' ', $issues)
    );
}
Copyright (C) 2015 Composer

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.
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver;

use Composer\Semver\Constraint\Constraint;

class Comparator
{
    /**
     * Evaluates the expression: $version1 > $version2.
     *
     * @param string $version1
     * @param string $version2
     *
     * @return bool
     */
    public static function greaterThan($version1, $version2)
    {
        return self::compare($version1, '>', $version2);
    }

    /**
     * Evaluates the expression: $version1 >= $version2.
     *
     * @param string $version1
     * @param string $version2
     *
     * @return bool
     */
    public static function greaterThanOrEqualTo($version1, $version2)
    {
        return self::compare($version1, '>=', $version2);
    }

    /**
     * Evaluates the expression: $version1 < $version2.
     *
     * @param string $version1
     * @param string $version2
     *
     * @return bool
     */
    public static function lessThan($version1, $version2)
    {
        return self::compare($version1, '<', $version2);
    }

    /**
     * Evaluates the expression: $version1 <= $version2.
     *
     * @param string $version1
     * @param string $version2
     *
     * @return bool
     */
    public static function lessThanOrEqualTo($version1, $version2)
    {
        return self::compare($version1, '<=', $version2);
    }

    /**
     * Evaluates the expression: $version1 == $version2.
     *
     * @param string $version1
     * @param string $version2
     *
     * @return bool
     */
    public static function equalTo($version1, $version2)
    {
        return self::compare($version1, '==', $version2);
    }

    /**
     * Evaluates the expression: $version1 != $version2.
     *
     * @param string $version1
     * @param string $version2
     *
     * @return bool
     */
    public static function notEqualTo($version1, $version2)
    {
        return self::compare($version1, '!=', $version2);
    }

    /**
     * Evaluates the expression: $version1 $operator $version2.
     *
     * @param string $version1
     * @param string $operator
     * @param string $version2
     *
     * @return bool
     *
     * @phpstan-param Constraint::STR_OP_*  $operator
     */
    public static function compare($version1, $operator, $version2)
    {
        $constraint = new Constraint($operator, $version2);

        return $constraint->matchSpecific(new Constraint('==', $version1), true);
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver;

use Composer\Semver\Constraint\Constraint;

class Interval
{
    /** @var Constraint */
    private $start;
    /** @var Constraint */
    private $end;

    public function __construct(Constraint $start, Constraint $end)
    {
        $this->start = $start;
        $this->end = $end;
    }

    /**
     * @return Constraint
     */
    public function getStart()
    {
        return $this->start;
    }

    /**
     * @return Constraint
     */
    public function getEnd()
    {
        return $this->end;
    }

    /**
     * @return Constraint
     */
    public static function fromZero()
    {
        static $zero;

        if (null === $zero) {
            $zero = new Constraint('>=', '0.0.0.0-dev');
        }

        return $zero;
    }

    /**
     * @return Constraint
     */
    public static function untilPositiveInfinity()
    {
        static $positiveInfinity;

        if (null === $positiveInfinity) {
            $positiveInfinity = new Constraint('<', PHP_INT_MAX.'.0.0.0');
        }

        return $positiveInfinity;
    }

    /**
     * @return self
     */
    public static function any()
    {
        return new self(self::fromZero(), self::untilPositiveInfinity());
    }

    /**
     * @return array{'names': string[], 'exclude': bool}
     */
    public static function anyDev()
    {
        // any == exclude nothing
        return array('names' => array(), 'exclude' => true);
    }

    /**
     * @return array{'names': string[], 'exclude': bool}
     */
    public static function noDev()
    {
        // nothing == no names included
        return array('names' => array(), 'exclude' => false);
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver;

use Composer\Semver\Constraint\Constraint;

class Semver
{
    const SORT_ASC = 1;
    const SORT_DESC = -1;

    /** @var VersionParser */
    private static $versionParser;

    /**
     * Determine if given version satisfies given constraints.
     *
     * @param string $version
     * @param string $constraints
     *
     * @return bool
     */
    public static function satisfies($version, $constraints)
    {
        if (null === self::$versionParser) {
            self::$versionParser = new VersionParser();
        }

        $versionParser = self::$versionParser;
        $provider = new Constraint('==', $versionParser->normalize($version));
        $parsedConstraints = $versionParser->parseConstraints($constraints);

        return $parsedConstraints->matches($provider);
    }

    /**
     * Return all versions that satisfy given constraints.
     *
     * @param string[] $versions
     * @param string   $constraints
     *
     * @return list<string>
     */
    public static function satisfiedBy(array $versions, $constraints)
    {
        $versions = array_filter($versions, function ($version) use ($constraints) {
            return Semver::satisfies($version, $constraints);
        });

        return array_values($versions);
    }

    /**
     * Sort given array of versions.
     *
     * @param string[] $versions
     *
     * @return list<string>
     */
    public static function sort(array $versions)
    {
        return self::usort($versions, self::SORT_ASC);
    }

    /**
     * Sort given array of versions in reverse.
     *
     * @param string[] $versions
     *
     * @return list<string>
     */
    public static function rsort(array $versions)
    {
        return self::usort($versions, self::SORT_DESC);
    }

    /**
     * @param string[] $versions
     * @param int      $direction
     *
     * @return list<string>
     */
    private static function usort(array $versions, $direction)
    {
        if (null === self::$versionParser) {
            self::$versionParser = new VersionParser();
        }

        $versionParser = self::$versionParser;
        $normalized = array();

        // Normalize outside of usort() scope for minor performance increase.
        // Creates an array of arrays: [[normalized, key], ...]
        foreach ($versions as $key => $version) {
            $normalizedVersion = $versionParser->normalize($version);
            $normalizedVersion = $versionParser->normalizeDefaultBranch($normalizedVersion);
            $normalized[] = array($normalizedVersion, $key);
        }

        usort($normalized, function (array $left, array $right) use ($direction) {
            if ($left[0] === $right[0]) {
                return 0;
            }

            if (Comparator::lessThan($left[0], $right[0])) {
                return -$direction;
            }

            return $direction;
        });

        // Recreate input array, using the original indexes which are now in sorted order.
        $sorted = array();
        foreach ($normalized as $item) {
            $sorted[] = $versions[$item[1]];
        }

        return $sorted;
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver\Constraint;

/**
 * Defines a constraint.
 */
class Constraint implements ConstraintInterface
{
    /* operator integer values */
    const OP_EQ = 0;
    const OP_LT = 1;
    const OP_LE = 2;
    const OP_GT = 3;
    const OP_GE = 4;
    const OP_NE = 5;

    /* operator string values */
    const STR_OP_EQ = '==';
    const STR_OP_EQ_ALT = '=';
    const STR_OP_LT = '<';
    const STR_OP_LE = '<=';
    const STR_OP_GT = '>';
    const STR_OP_GE = '>=';
    const STR_OP_NE = '!=';
    const STR_OP_NE_ALT = '<>';

    /**
     * Operator to integer translation table.
     *
     * @var array
     * @phpstan-var array<self::STR_OP_*, self::OP_*>
     */
    private static $transOpStr = array(
        '=' => self::OP_EQ,
        '==' => self::OP_EQ,
        '<' => self::OP_LT,
        '<=' => self::OP_LE,
        '>' => self::OP_GT,
        '>=' => self::OP_GE,
        '<>' => self::OP_NE,
        '!=' => self::OP_NE,
    );

    /**
     * Integer to operator translation table.
     *
     * @var array
     * @phpstan-var array<self::OP_*, self::STR_OP_*>
     */
    private static $transOpInt = array(
        self::OP_EQ => '==',
        self::OP_LT => '<',
        self::OP_LE => '<=',
        self::OP_GT => '>',
        self::OP_GE => '>=',
        self::OP_NE => '!=',
    );

    /**
     * @var int
     * @phpstan-var self::OP_*
     */
    protected $operator;

    /** @var string */
    protected $version;

    /** @var string|null */
    protected $prettyString;

    /** @var Bound */
    protected $lowerBound;

    /** @var Bound */
    protected $upperBound;

    /**
     * Sets operator and version to compare with.
     *
     * @param string $operator
     * @param string $version
     *
     * @throws \InvalidArgumentException if invalid operator is given.
     *
     * @phpstan-param self::STR_OP_* $operator
     */
    public function __construct($operator, $version)
    {
        if (!isset(self::$transOpStr[$operator])) {
            throw new \InvalidArgumentException(sprintf(
                'Invalid operator "%s" given, expected one of: %s',
                $operator,
                implode(', ', self::getSupportedOperators())
            ));
        }

        $this->operator = self::$transOpStr[$operator];
        $this->version = $version;
    }

    /**
     * @return string
     */
    public function getVersion()
    {
        return $this->version;
    }

    /**
     * @return string
     *
     * @phpstan-return self::STR_OP_*
     */
    public function getOperator()
    {
        return self::$transOpInt[$this->operator];
    }

    /**
     * @param ConstraintInterface $provider
     *
     * @return bool
     */
    public function matches(ConstraintInterface $provider)
    {
        if ($provider instanceof self) {
            return $this->matchSpecific($provider);
        }

        // turn matching around to find a match
        return $provider->matches($this);
    }

    /**
     * {@inheritDoc}
     */
    public function setPrettyString($prettyString)
    {
        $this->prettyString = $prettyString;
    }

    /**
     * {@inheritDoc}
     */
    public function getPrettyString()
    {
        if ($this->prettyString) {
            return $this->prettyString;
        }

        return $this->__toString();
    }

    /**
     * Get all supported comparison operators.
     *
     * @return array
     *
     * @phpstan-return list<self::STR_OP_*>
     */
    public static function getSupportedOperators()
    {
        return array_keys(self::$transOpStr);
    }

    /**
     * @param  string $operator
     * @return int
     *
     * @phpstan-param  self::STR_OP_* $operator
     * @phpstan-return self::OP_*
     */
    public static function getOperatorConstant($operator)
    {
        return self::$transOpStr[$operator];
    }

    /**
     * @param string $a
     * @param string $b
     * @param string $operator
     * @param bool   $compareBranches
     *
     * @throws \InvalidArgumentException if invalid operator is given.
     *
     * @return bool
     *
     * @phpstan-param self::STR_OP_* $operator
     */
    public function versionCompare($a, $b, $operator, $compareBranches = false)
    {
        if (!isset(self::$transOpStr[$operator])) {
            throw new \InvalidArgumentException(sprintf(
                'Invalid operator "%s" given, expected one of: %s',
                $operator,
                implode(', ', self::getSupportedOperators())
            ));
        }

        $aIsBranch = strpos($a, 'dev-') === 0;
        $bIsBranch = strpos($b, 'dev-') === 0;

        if ($operator === '!=' && ($aIsBranch || $bIsBranch)) {
            return $a !== $b;
        }

        if ($aIsBranch && $bIsBranch) {
            return $operator === '==' && $a === $b;
        }

        // when branches are not comparable, we make sure dev branches never match anything
        if (!$compareBranches && ($aIsBranch || $bIsBranch)) {
            return false;
        }

        return \version_compare($a, $b, $operator);
    }

    /**
     * {@inheritDoc}
     */
    public function compile($otherOperator)
    {
        if (strpos($this->version, 'dev-') === 0) {
            if (self::OP_EQ === $this->operator) {
                if (self::OP_EQ === $otherOperator) {
                    return sprintf('$b && $v === %s', \var_export($this->version, true));
                }
                if (self::OP_NE === $otherOperator) {
                    return sprintf('!$b || $v !== %s', \var_export($this->version, true));
                }
                return 'false';
            }

            if (self::OP_NE === $this->operator) {
                if (self::OP_EQ === $otherOperator) {
                    return sprintf('!$b || $v !== %s', \var_export($this->version, true));
                }
                if (self::OP_NE === $otherOperator) {
                    return 'true';
                }
                return '!$b';
            }

            return 'false';
        }

        if (self::OP_EQ === $this->operator) {
            if (self::OP_EQ === $otherOperator) {
                return sprintf('\version_compare($v, %s, \'==\')', \var_export($this->version, true));
            }
            if (self::OP_NE === $otherOperator) {
                return sprintf('$b || \version_compare($v, %s, \'!=\')', \var_export($this->version, true));
            }

            return sprintf('!$b && \version_compare(%s, $v, \'%s\')', \var_export($this->version, true), self::$transOpInt[$otherOperator]);
        }

        if (self::OP_NE === $this->operator) {
            if (self::OP_EQ === $otherOperator) {
                return sprintf('$b || (!$b && \version_compare($v, %s, \'!=\'))', \var_export($this->version, true));
            }

            if (self::OP_NE === $otherOperator) {
                return 'true';
            }
            return '!$b';
        }

        if (self::OP_LT === $this->operator || self::OP_LE === $this->operator) {
            if (self::OP_LT === $otherOperator || self::OP_LE === $otherOperator) {
                return '!$b';
            }
        } else { // $this->operator must be self::OP_GT || self::OP_GE here
            if (self::OP_GT === $otherOperator || self::OP_GE === $otherOperator) {
                return '!$b';
            }
        }

        if (self::OP_NE === $otherOperator) {
            return 'true';
        }

        $codeComparison = sprintf('\version_compare($v, %s, \'%s\')', \var_export($this->version, true), self::$transOpInt[$this->operator]);
        if ($this->operator === self::OP_LE) {
            if ($otherOperator === self::OP_GT) {
                return sprintf('!$b && \version_compare($v, %s, \'!=\') && ', \var_export($this->version, true)) . $codeComparison;
            }
        } elseif ($this->operator === self::OP_GE) {
            if ($otherOperator === self::OP_LT) {
                return sprintf('!$b && \version_compare($v, %s, \'!=\') && ', \var_export($this->version, true)) . $codeComparison;
            }
        }

        return sprintf('!$b && %s', $codeComparison);
    }

    /**
     * @param Constraint $provider
     * @param bool       $compareBranches
     *
     * @return bool
     */
    public function matchSpecific(Constraint $provider, $compareBranches = false)
    {
        $noEqualOp = str_replace('=', '', self::$transOpInt[$this->operator]);
        $providerNoEqualOp = str_replace('=', '', self::$transOpInt[$provider->operator]);

        $isEqualOp = self::OP_EQ === $this->operator;
        $isNonEqualOp = self::OP_NE === $this->operator;
        $isProviderEqualOp = self::OP_EQ === $provider->operator;
        $isProviderNonEqualOp = self::OP_NE === $provider->operator;

        // '!=' operator is match when other operator is not '==' operator or version is not match
        // these kinds of comparisons always have a solution
        if ($isNonEqualOp || $isProviderNonEqualOp) {
            if ($isNonEqualOp && !$isProviderNonEqualOp && !$isProviderEqualOp && strpos($provider->version, 'dev-') === 0) {
                return false;
            }

            if ($isProviderNonEqualOp && !$isNonEqualOp && !$isEqualOp && strpos($this->version, 'dev-') === 0) {
                return false;
            }

            if (!$isEqualOp && !$isProviderEqualOp) {
                return true;
            }
            return $this->versionCompare($provider->version, $this->version, '!=', $compareBranches);
        }

        // an example for the condition is <= 2.0 & < 1.0
        // these kinds of comparisons always have a solution
        if ($this->operator !== self::OP_EQ && $noEqualOp === $providerNoEqualOp) {
            return !(strpos($this->version, 'dev-') === 0 || strpos($provider->version, 'dev-') === 0);
        }

        $version1 = $isEqualOp ? $this->version : $provider->version;
        $version2 = $isEqualOp ? $provider->version : $this->version;
        $operator = $isEqualOp ? $provider->operator : $this->operator;

        if ($this->versionCompare($version1, $version2, self::$transOpInt[$operator], $compareBranches)) {
            // special case, e.g. require >= 1.0 and provide < 1.0
            // 1.0 >= 1.0 but 1.0 is outside of the provided interval

            return !(self::$transOpInt[$provider->operator] === $providerNoEqualOp
                && self::$transOpInt[$this->operator] !== $noEqualOp
                && \version_compare($provider->version, $this->version, '=='));
        }

        return false;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return self::$transOpInt[$this->operator] . ' ' . $this->version;
    }

    /**
     * {@inheritDoc}
     */
    public function getLowerBound()
    {
        $this->extractBounds();

        return $this->lowerBound;
    }

    /**
     * {@inheritDoc}
     */
    public function getUpperBound()
    {
        $this->extractBounds();

        return $this->upperBound;
    }

    /**
     * @return void
     */
    private function extractBounds()
    {
        if (null !== $this->lowerBound) {
            return;
        }

        // Branches
        if (strpos($this->version, 'dev-') === 0) {
            $this->lowerBound = Bound::zero();
            $this->upperBound = Bound::positiveInfinity();

            return;
        }

        switch ($this->operator) {
            case self::OP_EQ:
                $this->lowerBound = new Bound($this->version, true);
                $this->upperBound = new Bound($this->version, true);
                break;
            case self::OP_LT:
                $this->lowerBound = Bound::zero();
                $this->upperBound = new Bound($this->version, false);
                break;
            case self::OP_LE:
                $this->lowerBound = Bound::zero();
                $this->upperBound = new Bound($this->version, true);
                break;
            case self::OP_GT:
                $this->lowerBound = new Bound($this->version, false);
                $this->upperBound = Bound::positiveInfinity();
                break;
            case self::OP_GE:
                $this->lowerBound = new Bound($this->version, true);
                $this->upperBound = Bound::positiveInfinity();
                break;
            case self::OP_NE:
                $this->lowerBound = Bound::zero();
                $this->upperBound = Bound::positiveInfinity();
                break;
        }
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver\Constraint;

class Bound
{
    /**
     * @var string
     */
    private $version;

    /**
     * @var bool
     */
    private $isInclusive;

    /**
     * @param string $version
     * @param bool   $isInclusive
     */
    public function __construct($version, $isInclusive)
    {
        $this->version = $version;
        $this->isInclusive = $isInclusive;
    }

    /**
     * @return string
     */
    public function getVersion()
    {
        return $this->version;
    }

    /**
     * @return bool
     */
    public function isInclusive()
    {
        return $this->isInclusive;
    }

    /**
     * @return bool
     */
    public function isZero()
    {
        return $this->getVersion() === '0.0.0.0-dev' && $this->isInclusive();
    }

    /**
     * @return bool
     */
    public function isPositiveInfinity()
    {
        return $this->getVersion() === PHP_INT_MAX.'.0.0.0' && !$this->isInclusive();
    }

    /**
     * Compares a bound to another with a given operator.
     *
     * @param Bound  $other
     * @param string $operator
     *
     * @return bool
     */
    public function compareTo(Bound $other, $operator)
    {
        if (!\in_array($operator, array('<', '>'), true)) {
            throw new \InvalidArgumentException('Does not support any other operator other than > or <.');
        }

        // If they are the same it doesn't matter
        if ($this == $other) {
            return false;
        }

        $compareResult = version_compare($this->getVersion(), $other->getVersion());

        // Not the same version means we don't need to check if the bounds are inclusive or not
        if (0 !== $compareResult) {
            return (('>' === $operator) ? 1 : -1) === $compareResult;
        }

        // Question we're answering here is "am I higher than $other?"
        return '>' === $operator ? $other->isInclusive() : !$other->isInclusive();
    }

    public function __toString()
    {
        return sprintf(
            '%s [%s]',
            $this->getVersion(),
            $this->isInclusive() ? 'inclusive' : 'exclusive'
        );
    }

    /**
     * @return self
     */
    public static function zero()
    {
        return new Bound('0.0.0.0-dev', true);
    }

    /**
     * @return self
     */
    public static function positiveInfinity()
    {
        return new Bound(PHP_INT_MAX.'.0.0.0', false);
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver\Constraint;

/**
 * Defines the absence of a constraint.
 *
 * This constraint matches everything.
 */
class MatchAllConstraint implements ConstraintInterface
{
    /** @var string|null */
    protected $prettyString;

    /**
     * @param ConstraintInterface $provider
     *
     * @return bool
     */
    public function matches(ConstraintInterface $provider)
    {
        return true;
    }

    /**
     * {@inheritDoc}
     */
    public function compile($otherOperator)
    {
        return 'true';
    }

    /**
     * {@inheritDoc}
     */
    public function setPrettyString($prettyString)
    {
        $this->prettyString = $prettyString;
    }

    /**
     * {@inheritDoc}
     */
    public function getPrettyString()
    {
        if ($this->prettyString) {
            return $this->prettyString;
        }

        return (string) $this;
    }

    /**
     * {@inheritDoc}
     */
    public function __toString()
    {
        return '*';
    }

    /**
     * {@inheritDoc}
     */
    public function getUpperBound()
    {
        return Bound::positiveInfinity();
    }

    /**
     * {@inheritDoc}
     */
    public function getLowerBound()
    {
        return Bound::zero();
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver\Constraint;

/**
 * Defines a conjunctive or disjunctive set of constraints.
 */
class MultiConstraint implements ConstraintInterface
{
    /**
     * @var ConstraintInterface[]
     * @phpstan-var non-empty-array<ConstraintInterface>
     */
    protected $constraints;

    /** @var string|null */
    protected $prettyString;

    /** @var string|null */
    protected $string;

    /** @var bool */
    protected $conjunctive;

    /** @var Bound|null */
    protected $lowerBound;

    /** @var Bound|null */
    protected $upperBound;

    /**
     * @param ConstraintInterface[] $constraints A set of constraints
     * @param bool                  $conjunctive Whether the constraints should be treated as conjunctive or disjunctive
     *
     * @throws \InvalidArgumentException If less than 2 constraints are passed
     */
    public function __construct(array $constraints, $conjunctive = true)
    {
        if (\count($constraints) < 2) {
            throw new \InvalidArgumentException(
                'Must provide at least two constraints for a MultiConstraint. Use '.
                'the regular Constraint class for one constraint only or MatchAllConstraint for none. You may use '.
                'MultiConstraint::create() which optimizes and handles those cases automatically.'
            );
        }

        $this->constraints = $constraints;
        $this->conjunctive = $conjunctive;
    }

    /**
     * @return ConstraintInterface[]
     */
    public function getConstraints()
    {
        return $this->constraints;
    }

    /**
     * @return bool
     */
    public function isConjunctive()
    {
        return $this->conjunctive;
    }

    /**
     * @return bool
     */
    public function isDisjunctive()
    {
        return !$this->conjunctive;
    }

    /**
     * {@inheritDoc}
     */
    public function compile($otherOperator)
    {
        $parts = array();
        foreach ($this->constraints as $constraint) {
            $code = $constraint->compile($otherOperator);
            if ($code === 'true') {
                if (!$this->conjunctive) {
                    return 'true';
                }
            } elseif ($code === 'false') {
                if ($this->conjunctive) {
                    return 'false';
                }
            } else {
                $parts[] = '('.$code.')';
            }
        }

        if (!$parts) {
            return $this->conjunctive ? 'true' : 'false';
        }

        return $this->conjunctive ? implode('&&', $parts) : implode('||', $parts);
    }

    /**
     * @param ConstraintInterface $provider
     *
     * @return bool
     */
    public function matches(ConstraintInterface $provider)
    {
        if (false === $this->conjunctive) {
            foreach ($this->constraints as $constraint) {
                if ($provider->matches($constraint)) {
                    return true;
                }
            }

            return false;
        }

        // when matching a conjunctive and a disjunctive multi constraint we have to iterate over the disjunctive one
        // otherwise we'd return true if different parts of the disjunctive constraint match the conjunctive one
        // which would lead to incorrect results, e.g. [>1 and <2] would match [<1 or >2] although they do not intersect
        if ($provider instanceof MultiConstraint && $provider->isDisjunctive()) {
            return $provider->matches($this);
        }

        foreach ($this->constraints as $constraint) {
            if (!$provider->matches($constraint)) {
                return false;
            }
        }

        return true;
    }

    /**
     * {@inheritDoc}
     */
    public function setPrettyString($prettyString)
    {
        $this->prettyString = $prettyString;
    }

    /**
     * {@inheritDoc}
     */
    public function getPrettyString()
    {
        if ($this->prettyString) {
            return $this->prettyString;
        }

        return (string) $this;
    }

    /**
     * {@inheritDoc}
     */
    public function __toString()
    {
        if ($this->string !== null) {
            return $this->string;
        }

        $constraints = array();
        foreach ($this->constraints as $constraint) {
            $constraints[] = (string) $constraint;
        }

        return $this->string = '[' . implode($this->conjunctive ? ' ' : ' || ', $constraints) . ']';
    }

    /**
     * {@inheritDoc}
     */
    public function getLowerBound()
    {
        $this->extractBounds();

        if (null === $this->lowerBound) {
            throw new \LogicException('extractBounds should have populated the lowerBound property');
        }

        return $this->lowerBound;
    }

    /**
     * {@inheritDoc}
     */
    public function getUpperBound()
    {
        $this->extractBounds();

        if (null === $this->upperBound) {
            throw new \LogicException('extractBounds should have populated the upperBound property');
        }

        return $this->upperBound;
    }

    /**
     * Tries to optimize the constraints as much as possible, meaning
     * reducing/collapsing congruent constraints etc.
     * Does not necessarily return a MultiConstraint instance if
     * things can be reduced to a simple constraint
     *
     * @param ConstraintInterface[] $constraints A set of constraints
     * @param bool                  $conjunctive Whether the constraints should be treated as conjunctive or disjunctive
     *
     * @return ConstraintInterface
     */
    public static function create(array $constraints, $conjunctive = true)
    {
        if (0 === \count($constraints)) {
            return new MatchAllConstraint();
        }

        if (1 === \count($constraints)) {
            return $constraints[0];
        }

        $optimized = self::optimizeConstraints($constraints, $conjunctive);
        if ($optimized !== null) {
            list($constraints, $conjunctive) = $optimized;
            if (\count($constraints) === 1) {
                return $constraints[0];
            }
        }

        return new self($constraints, $conjunctive);
    }

    /**
     * @param  ConstraintInterface[] $constraints
     * @param  bool                  $conjunctive
     * @return ?array
     *
     * @phpstan-return array{0: list<ConstraintInterface>, 1: bool}|null
     */
    private static function optimizeConstraints(array $constraints, $conjunctive)
    {
        // parse the two OR groups and if they are contiguous we collapse
        // them into one constraint
        // [>= 1 < 2] || [>= 2 < 3] || [>= 3 < 4] => [>= 1 < 4]
        if (!$conjunctive) {
            $left = $constraints[0];
            $mergedConstraints = array();
            $optimized = false;
            for ($i = 1, $l = \count($constraints); $i < $l; $i++) {
                $right = $constraints[$i];
                if (
                    $left instanceof self
                    && $left->conjunctive
                    && $right instanceof self
                    && $right->conjunctive
                    && \count($left->constraints) === 2
                    && \count($right->constraints) === 2
                    && ($left0 = (string) $left->constraints[0])
                    && $left0[0] === '>' && $left0[1] === '='
                    && ($left1 = (string) $left->constraints[1])
                    && $left1[0] === '<'
                    && ($right0 = (string) $right->constraints[0])
                    && $right0[0] === '>' && $right0[1] === '='
                    && ($right1 = (string) $right->constraints[1])
                    && $right1[0] === '<'
                    && substr($left1, 2) === substr($right0, 3)
                ) {
                    $optimized = true;
                    $left = new MultiConstraint(
                        array(
                            $left->constraints[0],
                            $right->constraints[1],
                        ),
                        true);
                } else {
                    $mergedConstraints[] = $left;
                    $left = $right;
                }
            }
            if ($optimized) {
                $mergedConstraints[] = $left;
                return array($mergedConstraints, false);
            }
        }

        // TODO: Here's the place to put more optimizations

        return null;
    }

    /**
     * @return void
     */
    private function extractBounds()
    {
        if (null !== $this->lowerBound) {
            return;
        }

        foreach ($this->constraints as $constraint) {
            if (null === $this->lowerBound || null === $this->upperBound) {
                $this->lowerBound = $constraint->getLowerBound();
                $this->upperBound = $constraint->getUpperBound();
                continue;
            }

            if ($constraint->getLowerBound()->compareTo($this->lowerBound, $this->isConjunctive() ? '>' : '<')) {
                $this->lowerBound = $constraint->getLowerBound();
            }

            if ($constraint->getUpperBound()->compareTo($this->upperBound, $this->isConjunctive() ? '<' : '>')) {
                $this->upperBound = $constraint->getUpperBound();
            }
        }
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver\Constraint;

/**
 * DO NOT IMPLEMENT this interface. It is only meant for usage as a type hint
 * in libraries relying on composer/semver but creating your own constraint class
 * that implements this interface is not a supported use case and will cause the
 * composer/semver components to return unexpected results.
 */
interface ConstraintInterface
{
    /**
     * Checks whether the given constraint intersects in any way with this constraint
     *
     * @param ConstraintInterface $provider
     *
     * @return bool
     */
    public function matches(ConstraintInterface $provider);

    /**
     * Provides a compiled version of the constraint for the given operator
     * The compiled version must be a PHP expression.
     * Executor of compile version must provide 2 variables:
     * - $v = the string version to compare with
     * - $b = whether or not the version is a non-comparable branch (starts with "dev-")
     *
     * @see Constraint::OP_* for the list of available operators.
     * @example return '!$b && version_compare($v, '1.0', '>')';
     *
     * @param int $otherOperator one Constraint::OP_*
     *
     * @return string
     *
     * @phpstan-param Constraint::OP_* $otherOperator
     */
    public function compile($otherOperator);

    /**
     * @return Bound
     */
    public function getUpperBound();

    /**
     * @return Bound
     */
    public function getLowerBound();

    /**
     * @return string
     */
    public function getPrettyString();

    /**
     * @param string|null $prettyString
     *
     * @return void
     */
    public function setPrettyString($prettyString);

    /**
     * @return string
     */
    public function __toString();
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver\Constraint;

/**
 * Blackhole of constraints, nothing escapes it
 */
class MatchNoneConstraint implements ConstraintInterface
{
    /** @var string|null */
    protected $prettyString;

    /**
     * @param ConstraintInterface $provider
     *
     * @return bool
     */
    public function matches(ConstraintInterface $provider)
    {
        return false;
    }

    /**
     * {@inheritDoc}
     */
    public function compile($otherOperator)
    {
        return 'false';
    }

    /**
     * {@inheritDoc}
     */
    public function setPrettyString($prettyString)
    {
        $this->prettyString = $prettyString;
    }

    /**
     * {@inheritDoc}
     */
    public function getPrettyString()
    {
        if ($this->prettyString) {
            return $this->prettyString;
        }

        return (string) $this;
    }

    /**
     * {@inheritDoc}
     */
    public function __toString()
    {
        return '[]';
    }

    /**
     * {@inheritDoc}
     */
    public function getUpperBound()
    {
        return new Bound('0.0.0.0-dev', false);
    }

    /**
     * {@inheritDoc}
     */
    public function getLowerBound()
    {
        return new Bound('0.0.0.0-dev', false);
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver;

use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\MatchAllConstraint;
use Composer\Semver\Constraint\MatchNoneConstraint;
use Composer\Semver\Constraint\MultiConstraint;

/**
 * Helper class generating intervals from constraints
 *
 * This contains utilities for:
 *
 *  - compacting an existing constraint which can be used to combine several into one
 * by creating a MultiConstraint out of the many constraints you have.
 *
 *  - checking whether one subset is a subset of another.
 *
 * Note: You should call clear to free memoization memory  usage when you are done using this class
 */
class Intervals
{
    /**
     * @phpstan-var array<string, array{'numeric': Interval[], 'branches': array{'names': string[], 'exclude': bool}}>
     */
    private static $intervalsCache = array();

    /**
     * @phpstan-var array<string, int>
     */
    private static $opSortOrder = array(
        '>=' => -3,
        '<' => -2,
        '>' => 2,
        '<=' => 3,
    );

    /**
     * Clears the memoization cache once you are done
     *
     * @return void
     */
    public static function clear()
    {
        self::$intervalsCache = array();
    }

    /**
     * Checks whether $candidate is a subset of $constraint
     *
     * @return bool
     */
    public static function isSubsetOf(ConstraintInterface $candidate, ConstraintInterface $constraint)
    {
        if ($constraint instanceof MatchAllConstraint) {
            return true;
        }

        if ($candidate instanceof MatchNoneConstraint || $constraint instanceof MatchNoneConstraint) {
            return false;
        }

        $intersectionIntervals = self::get(new MultiConstraint(array($candidate, $constraint), true));
        $candidateIntervals = self::get($candidate);
        if (\count($intersectionIntervals['numeric']) !== \count($candidateIntervals['numeric'])) {
            return false;
        }

        foreach ($intersectionIntervals['numeric'] as $index => $interval) {
            if (!isset($candidateIntervals['numeric'][$index])) {
                return false;
            }

            if ((string) $candidateIntervals['numeric'][$index]->getStart() !== (string) $interval->getStart()) {
                return false;
            }

            if ((string) $candidateIntervals['numeric'][$index]->getEnd() !== (string) $interval->getEnd()) {
                return false;
            }
        }

        if ($intersectionIntervals['branches']['exclude'] !== $candidateIntervals['branches']['exclude']) {
            return false;
        }
        if (\count($intersectionIntervals['branches']['names']) !== \count($candidateIntervals['branches']['names'])) {
            return false;
        }
        foreach ($intersectionIntervals['branches']['names'] as $index => $name) {
            if ($name !== $candidateIntervals['branches']['names'][$index]) {
                return false;
            }
        }

        return true;
    }

    /**
     * Checks whether $a and $b have any intersection, equivalent to $a->matches($b)
     *
     * @return bool
     */
    public static function haveIntersections(ConstraintInterface $a, ConstraintInterface $b)
    {
        if ($a instanceof MatchAllConstraint || $b instanceof MatchAllConstraint) {
            return true;
        }

        if ($a instanceof MatchNoneConstraint || $b instanceof MatchNoneConstraint) {
            return false;
        }

        $intersectionIntervals = self::generateIntervals(new MultiConstraint(array($a, $b), true), true);

        return \count($intersectionIntervals['numeric']) > 0 || $intersectionIntervals['branches']['exclude'] || \count($intersectionIntervals['branches']['names']) > 0;
    }

    /**
     * Attempts to optimize a MultiConstraint
     *
     * When merging MultiConstraints together they can get very large, this will
     * compact it by looking at the real intervals covered by all the constraints
     * and then creates a new constraint containing only the smallest amount of rules
     * to match the same intervals.
     *
     * @return ConstraintInterface
     */
    public static function compactConstraint(ConstraintInterface $constraint)
    {
        if (!$constraint instanceof MultiConstraint) {
            return $constraint;
        }

        $intervals = self::generateIntervals($constraint);
        $constraints = array();
        $hasNumericMatchAll = false;

        if (\count($intervals['numeric']) === 1 && (string) $intervals['numeric'][0]->getStart() === (string) Interval::fromZero() && (string) $intervals['numeric'][0]->getEnd() === (string) Interval::untilPositiveInfinity()) {
            $constraints[] = $intervals['numeric'][0]->getStart();
            $hasNumericMatchAll = true;
        } else {
            $unEqualConstraints = array();
            for ($i = 0, $count = \count($intervals['numeric']); $i < $count; $i++) {
                $interval = $intervals['numeric'][$i];

                // if current interval ends with < N and next interval begins with > N we can swap this out for != N
                // but this needs to happen as a conjunctive expression together with the start of the current interval
                // and end of next interval, so [>=M, <N] || [>N, <P] => [>=M, !=N, <P] but M/P can be skipped if
                // they are zero/+inf
                if ($interval->getEnd()->getOperator() === '<' && $i+1 < $count) {
                    $nextInterval = $intervals['numeric'][$i+1];
                    if ($interval->getEnd()->getVersion() === $nextInterval->getStart()->getVersion() && $nextInterval->getStart()->getOperator() === '>') {
                        // only add a start if we didn't already do so, can be skipped if we're looking at second
                        // interval in [>=M, <N] || [>N, <P] || [>P, <Q] where unEqualConstraints currently contains
                        // [>=M, !=N] already and we only want to add !=P right now
                        if (\count($unEqualConstraints) === 0 && (string) $interval->getStart() !== (string) Interval::fromZero()) {
                            $unEqualConstraints[] = $interval->getStart();
                        }
                        $unEqualConstraints[] = new Constraint('!=', $interval->getEnd()->getVersion());
                        continue;
                    }
                }

                if (\count($unEqualConstraints) > 0) {
                    // this is where the end of the following interval of a != constraint is added as explained above
                    if ((string) $interval->getEnd() !== (string) Interval::untilPositiveInfinity()) {
                        $unEqualConstraints[] = $interval->getEnd();
                    }

                    // count is 1 if entire constraint is just one != expression
                    if (\count($unEqualConstraints) > 1) {
                        $constraints[] = new MultiConstraint($unEqualConstraints, true);
                    } else {
                        $constraints[] = $unEqualConstraints[0];
                    }

                    $unEqualConstraints = array();
                    continue;
                }

                // convert back >= x - <= x intervals to == x
                if ($interval->getStart()->getVersion() === $interval->getEnd()->getVersion() && $interval->getStart()->getOperator() === '>=' && $interval->getEnd()->getOperator() === '<=') {
                    $constraints[] = new Constraint('==', $interval->getStart()->getVersion());
                    continue;
                }

                if ((string) $interval->getStart() === (string) Interval::fromZero()) {
                    $constraints[] = $interval->getEnd();
                } elseif ((string) $interval->getEnd() === (string) Interval::untilPositiveInfinity()) {
                    $constraints[] = $interval->getStart();
                } else {
                    $constraints[] = new MultiConstraint(array($interval->getStart(), $interval->getEnd()), true);
                }
            }
        }

        $devConstraints = array();

        if (0 === \count($intervals['branches']['names'])) {
            if ($intervals['branches']['exclude']) {
                if ($hasNumericMatchAll) {
                    return new MatchAllConstraint;
                }
                // otherwise constraint should contain a != operator and already cover this
            }
        } else {
            foreach ($intervals['branches']['names'] as $branchName) {
                if ($intervals['branches']['exclude']) {
                    $devConstraints[] = new Constraint('!=', $branchName);
                } else {
                    $devConstraints[] = new Constraint('==', $branchName);
                }
            }

            // excluded branches, e.g. != dev-foo are conjunctive with the interval, so
            // > 2.0 != dev-foo must return a conjunctive constraint
            if ($intervals['branches']['exclude']) {
                if (\count($constraints) > 1) {
                    return new MultiConstraint(array_merge(
                        array(new MultiConstraint($constraints, false)),
                        $devConstraints
                    ), true);
                }

                if (\count($constraints) === 1 && (string)$constraints[0] === (string)Interval::fromZero()) {
                    if (\count($devConstraints) > 1) {
                        return new MultiConstraint($devConstraints, true);
                    }
                    return $devConstraints[0];
                }

                return new MultiConstraint(array_merge($constraints, $devConstraints), true);
            }

            // otherwise devConstraints contains a list of == operators for branches which are disjunctive with the
            // rest of the constraint
            $constraints = array_merge($constraints, $devConstraints);
        }

        if (\count($constraints) > 1) {
            return new MultiConstraint($constraints, false);
        }

        if (\count($constraints) === 1) {
            return $constraints[0];
        }

        return new MatchNoneConstraint;
    }

    /**
     * Creates an array of numeric intervals and branch constraints representing a given constraint
     *
     * if the returned numeric array is empty it means the constraint matches nothing in the numeric range (0 - +inf)
     * if the returned branches array is empty it means no dev-* versions are matched
     * if a constraint matches all possible dev-* versions, branches will contain Interval::anyDev()
     *
     * @return array
     * @phpstan-return array{'numeric': Interval[], 'branches': array{'names': string[], 'exclude': bool}}
     */
    public static function get(ConstraintInterface $constraint)
    {
        $key = (string) $constraint;

        if (!isset(self::$intervalsCache[$key])) {
            self::$intervalsCache[$key] = self::generateIntervals($constraint);
        }

        return self::$intervalsCache[$key];
    }

    /**
     * @param bool $stopOnFirstValidInterval
     *
     * @phpstan-return array{'numeric': Interval[], 'branches': array{'names': string[], 'exclude': bool}}
     */
    private static function generateIntervals(ConstraintInterface $constraint, $stopOnFirstValidInterval = false)
    {
        if ($constraint instanceof MatchAllConstraint) {
            return array('numeric' => array(new Interval(Interval::fromZero(), Interval::untilPositiveInfinity())), 'branches' => Interval::anyDev());
        }

        if ($constraint instanceof MatchNoneConstraint) {
            return array('numeric' => array(), 'branches' => array('names' => array(), 'exclude' => false));
        }

        if ($constraint instanceof Constraint) {
            return self::generateSingleConstraintIntervals($constraint);
        }

        if (!$constraint instanceof MultiConstraint) {
            throw new \UnexpectedValueException('The constraint passed in should be an MatchAllConstraint, Constraint or MultiConstraint instance, got '.\get_class($constraint).'.');
        }

        $constraints = $constraint->getConstraints();

        $numericGroups = array();
        $constraintBranches = array();
        foreach ($constraints as $c) {
            $res = self::get($c);
            $numericGroups[] = $res['numeric'];
            $constraintBranches[] = $res['branches'];
        }

        if ($constraint->isDisjunctive()) {
            $branches = Interval::noDev();
            foreach ($constraintBranches as $b) {
                if ($b['exclude']) {
                    if ($branches['exclude']) {
                        // disjunctive constraint, so only exclude what's excluded in all constraints
                        // !=a,!=b || !=b,!=c => !=b
                        $branches['names'] = array_intersect($branches['names'], $b['names']);
                    } else {
                        // disjunctive constraint so exclude all names which are not explicitly included in the alternative
                        // (==b || ==c) || !=a,!=b => !=a
                        $branches['exclude'] = true;
                        $branches['names'] = array_diff($b['names'], $branches['names']);
                    }
                } else {
                    if ($branches['exclude']) {
                        // disjunctive constraint so exclude all names which are not explicitly included in the alternative
                        // !=a,!=b || (==b || ==c) => !=a
                        $branches['names'] = array_diff($branches['names'], $b['names']);
                    } else {
                        // disjunctive constraint, so just add all the other branches
                        // (==a || ==b) || ==c => ==a || ==b || ==c
                        $branches['names'] = array_merge($branches['names'], $b['names']);
                    }
                }
            }
        } else {
            $branches = Interval::anyDev();
            foreach ($constraintBranches as $b) {
                if ($b['exclude']) {
                    if ($branches['exclude']) {
                        // conjunctive, so just add all branch names to be excluded
                        // !=a && !=b => !=a,!=b
                        $branches['names'] = array_merge($branches['names'], $b['names']);
                    } else {
                        // conjunctive, so only keep included names which are not excluded
                        // (==a||==c) && !=a,!=b => ==c
                        $branches['names'] = array_diff($branches['names'], $b['names']);
                    }
                } else {
                    if ($branches['exclude']) {
                        // conjunctive, so only keep included names which are not excluded
                        // !=a,!=b && (==a||==c) => ==c
                        $branches['names'] = array_diff($b['names'], $branches['names']);
                        $branches['exclude'] = false;
                    } else {
                        // conjunctive, so only keep names that are included in both
                        // (==a||==b) && (==a||==c) => ==a
                        $branches['names'] = array_intersect($branches['names'], $b['names']);
                    }
                }
            }
        }

        $branches['names'] = array_unique($branches['names']);

        if (\count($numericGroups) === 1) {
            return array('numeric' => $numericGroups[0], 'branches' => $branches);
        }

        $borders = array();
        foreach ($numericGroups as $group) {
            foreach ($group as $interval) {
                $borders[] = array('version' => $interval->getStart()->getVersion(), 'operator' => $interval->getStart()->getOperator(), 'side' => 'start');
                $borders[] = array('version' => $interval->getEnd()->getVersion(), 'operator' => $interval->getEnd()->getOperator(), 'side' => 'end');
            }
        }

        $opSortOrder = self::$opSortOrder;
        usort($borders, function ($a, $b) use ($opSortOrder) {
            $order = version_compare($a['version'], $b['version']);
            if ($order === 0) {
                return $opSortOrder[$a['operator']] - $opSortOrder[$b['operator']];
            }

            return $order;
        });

        $activeIntervals = 0;
        $intervals = array();
        $index = 0;
        $activationThreshold = $constraint->isConjunctive() ? \count($numericGroups) : 1;
        $start = null;
        foreach ($borders as $border) {
            if ($border['side'] === 'start') {
                $activeIntervals++;
            } else {
                $activeIntervals--;
            }
            if (!$start && $activeIntervals >= $activationThreshold) {
                $start = new Constraint($border['operator'], $border['version']);
            } elseif ($start && $activeIntervals < $activationThreshold) {
                // filter out invalid intervals like > x - <= x, or >= x - < x
                if (
                    version_compare($start->getVersion(), $border['version'], '=')
                    && (
                        ($start->getOperator() === '>' && $border['operator'] === '<=')
                        || ($start->getOperator() === '>=' && $border['operator'] === '<')
                    )
                ) {
                    unset($intervals[$index]);
                } else {
                    $intervals[$index] = new Interval($start, new Constraint($border['operator'], $border['version']));
                    $index++;

                    if ($stopOnFirstValidInterval) {
                        break;
                    }
                }

                $start = null;
            }
        }

        return array('numeric' => $intervals, 'branches' => $branches);
    }

    /**
     * @phpstan-return array{'numeric': Interval[], 'branches': array{'names': string[], 'exclude': bool}}
     */
    private static function generateSingleConstraintIntervals(Constraint $constraint)
    {
        $op = $constraint->getOperator();

        // handle branch constraints first
        if (strpos($constraint->getVersion(), 'dev-') === 0) {
            $intervals = array();
            $branches = array('names' => array(), 'exclude' => false);

            // != dev-foo means any numeric version may match, we treat >/< like != they are not really defined for branches
            if ($op === '!=') {
                $intervals[] = new Interval(Interval::fromZero(), Interval::untilPositiveInfinity());
                $branches = array('names' => array($constraint->getVersion()), 'exclude' => true);
            } elseif ($op === '==') {
                $branches['names'][] = $constraint->getVersion();
            }

            return array(
                'numeric' => $intervals,
                'branches' => $branches,
            );
        }

        if ($op[0] === '>') { // > & >=
            return array('numeric' => array(new Interval($constraint, Interval::untilPositiveInfinity())), 'branches' => Interval::noDev());
        }
        if ($op[0] === '<') { // < & <=
            return array('numeric' => array(new Interval(Interval::fromZero(), $constraint)), 'branches' => Interval::noDev());
        }
        if ($op === '!=') {
            // convert !=x to intervals of 0 - <x && >x - +inf + dev*
            return array('numeric' => array(
                new Interval(Interval::fromZero(), new Constraint('<', $constraint->getVersion())),
                new Interval(new Constraint('>', $constraint->getVersion()), Interval::untilPositiveInfinity()),
            ), 'branches' => Interval::anyDev());
        }

        // convert ==x to an interval of >=x - <=x
        return array('numeric' => array(
            new Interval(new Constraint('>=', $constraint->getVersion()), new Constraint('<=', $constraint->getVersion())),
        ), 'branches' => Interval::noDev());
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver;

use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\MatchAllConstraint;
use Composer\Semver\Constraint\MultiConstraint;
use Composer\Semver\Constraint\Constraint;

/**
 * Version parser.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class VersionParser
{
    /**
     * Regex to match pre-release data (sort of).
     *
     * Due to backwards compatibility:
     *   - Instead of enforcing hyphen, an underscore, dot or nothing at all are also accepted.
     *   - Only stabilities as recognized by Composer are allowed to precede a numerical identifier.
     *   - Numerical-only pre-release identifiers are not supported, see tests.
     *
     *                        |--------------|
     * [major].[minor].[patch] -[pre-release] +[build-metadata]
     *
     * @var string
     */
    private static $modifierRegex = '[._-]?(?:(stable|beta|b|RC|alpha|a|patch|pl|p)((?:[.-]?\d+)*+)?)?([.-]?dev)?';

    /** @var string */
    private static $stabilitiesRegex = 'stable|RC|beta|alpha|dev';

    /**
     * Returns the stability of a version.
     *
     * @param string $version
     *
     * @return string
     * @phpstan-return 'stable'|'RC'|'beta'|'alpha'|'dev'
     */
    public static function parseStability($version)
    {
        $version = (string) preg_replace('{#.+$}', '', (string) $version);

        if (strpos($version, 'dev-') === 0 || '-dev' === substr($version, -4)) {
            return 'dev';
        }

        preg_match('{' . self::$modifierRegex . '(?:\+.*)?$}i', strtolower($version), $match);

        if (!empty($match[3])) {
            return 'dev';
        }

        if (!empty($match[1])) {
            if ('beta' === $match[1] || 'b' === $match[1]) {
                return 'beta';
            }
            if ('alpha' === $match[1] || 'a' === $match[1]) {
                return 'alpha';
            }
            if ('rc' === $match[1]) {
                return 'RC';
            }
        }

        return 'stable';
    }

    /**
     * @param string $stability
     *
     * @return string
     * @phpstan-return 'stable'|'RC'|'beta'|'alpha'|'dev'
     */
    public static function normalizeStability($stability)
    {
        $stability = strtolower((string) $stability);

        if (!in_array($stability, array('stable', 'rc', 'beta', 'alpha', 'dev'), true)) {
            throw new \InvalidArgumentException('Invalid stability string "'.$stability.'", expected one of stable, RC, beta, alpha or dev');
        }

        return $stability === 'rc' ? 'RC' : $stability;
    }

    /**
     * Normalizes a version string to be able to perform comparisons on it.
     *
     * @param string $version
     * @param ?string $fullVersion optional complete version string to give more context
     *
     * @throws \UnexpectedValueException
     *
     * @return string
     */
    public function normalize($version, $fullVersion = null)
    {
        $version = trim((string) $version);
        $origVersion = $version;
        if (null === $fullVersion) {
            $fullVersion = $version;
        }

        // strip off aliasing
        if (preg_match('{^([^,\s]++) ++as ++([^,\s]++)$}', $version, $match)) {
            $version = $match[1];
        }

        // strip off stability flag
        if (preg_match('{@(?:' . self::$stabilitiesRegex . ')$}i', $version, $match)) {
            $version = substr($version, 0, strlen($version) - strlen($match[0]));
        }

        // normalize master/trunk/default branches to dev-name for BC with 1.x as these used to be valid constraints
        if (\in_array($version, array('master', 'trunk', 'default'), true)) {
            $version = 'dev-' . $version;
        }

        // if requirement is branch-like, use full name
        if (stripos($version, 'dev-') === 0) {
            return 'dev-' . substr($version, 4);
        }

        // strip off build metadata
        if (preg_match('{^([^,\s+]++)\+[^\s]++$}', $version, $match)) {
            $version = $match[1];
        }

        // match classical versioning
        if (preg_match('{^v?(\d{1,5}+)(\.\d++)?(\.\d++)?(\.\d++)?' . self::$modifierRegex . '$}i', $version, $matches)) {
            $version = $matches[1]
                . (!empty($matches[2]) ? $matches[2] : '.0')
                . (!empty($matches[3]) ? $matches[3] : '.0')
                . (!empty($matches[4]) ? $matches[4] : '.0');
            $index = 5;
        // match date(time) based versioning
        } elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3}){0,2})' . self::$modifierRegex . '$}i', $version, $matches)) {
            $version = (string) preg_replace('{\D}', '.', $matches[1]);
            $index = 2;
        }

        // add version modifiers if a version was matched
        if (isset($index)) {
            if (!empty($matches[$index])) {
                if ('stable' === $matches[$index]) {
                    return $version;
                }
                $version .= '-' . $this->expandStability($matches[$index]) . (isset($matches[$index + 1]) && '' !== $matches[$index + 1] ? ltrim($matches[$index + 1], '.-') : '');
            }

            if (!empty($matches[$index + 2])) {
                $version .= '-dev';
            }

            return $version;
        }

        // match dev branches
        if (preg_match('{(.*?)[.-]?dev$}i', $version, $match)) {
            try {
                $normalized = $this->normalizeBranch($match[1]);
                // a branch ending with -dev is only valid if it is numeric
                // if it gets prefixed with dev- it means the branch name should
                // have had a dev- prefix already when passed to normalize
                if (strpos($normalized, 'dev-') === false) {
                    return $normalized;
                }
            } catch (\Exception $e) {
            }
        }

        $extraMessage = '';
        if (preg_match('{ +as +' . preg_quote($version) . '(?:@(?:'.self::$stabilitiesRegex.'))?$}', $fullVersion)) {
            $extraMessage = ' in "' . $fullVersion . '", the alias must be an exact version';
        } elseif (preg_match('{^' . preg_quote($version) . '(?:@(?:'.self::$stabilitiesRegex.'))? +as +}', $fullVersion)) {
            $extraMessage = ' in "' . $fullVersion . '", the alias source must be an exact version, if it is a branch name you should prefix it with dev-';
        }

        throw new \UnexpectedValueException('Invalid version string "' . $origVersion . '"' . $extraMessage);
    }

    /**
     * Extract numeric prefix from alias, if it is in numeric format, suitable for version comparison.
     *
     * @param string $branch Branch name (e.g. 2.1.x-dev)
     *
     * @return string|false Numeric prefix if present (e.g. 2.1.) or false
     */
    public function parseNumericAliasPrefix($branch)
    {
        if (preg_match('{^(?P<version>(\d++\\.)*\d++)(?:\.x)?-dev$}i', (string) $branch, $matches)) {
            return $matches['version'] . '.';
        }

        return false;
    }

    /**
     * Normalizes a branch name to be able to perform comparisons on it.
     *
     * @param string $name
     *
     * @return string
     */
    public function normalizeBranch($name)
    {
        $name = trim((string) $name);

        if (preg_match('{^v?(\d++)(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?$}i', $name, $matches)) {
            $version = '';
            for ($i = 1; $i < 5; ++$i) {
                $version .= isset($matches[$i]) ? str_replace(array('*', 'X'), 'x', $matches[$i]) : '.x';
            }

            return str_replace('x', '9999999', $version) . '-dev';
        }

        return 'dev-' . $name;
    }

    /**
     * Normalizes a default branch name (i.e. master on git) to 9999999-dev.
     *
     * @param string $name
     *
     * @return string
     *
     * @deprecated No need to use this anymore in theory, Composer 2 does not normalize any branch names to 9999999-dev anymore
     */
    public function normalizeDefaultBranch($name)
    {
        if ($name === 'dev-master' || $name === 'dev-default' || $name === 'dev-trunk') {
            return '9999999-dev';
        }

        return (string) $name;
    }

    /**
     * Parses a constraint string into MultiConstraint and/or Constraint objects.
     *
     * @param string $constraints
     *
     * @return ConstraintInterface
     */
    public function parseConstraints($constraints)
    {
        $prettyConstraint = (string) $constraints;

        $orConstraints = preg_split('{\s*\|\|?\s*}', trim((string) $constraints));
        if (false === $orConstraints) {
            throw new \RuntimeException('Failed to preg_split string: '.$constraints);
        }
        $orGroups = array();

        foreach ($orConstraints as $orConstraint) {
            $andConstraints = preg_split('{(?<!^|as|[=>< ,]) *(?<!-)[, ](?!-) *(?!,|as|$)}', $orConstraint);
            if (false === $andConstraints) {
                throw new \RuntimeException('Failed to preg_split string: '.$orConstraint);
            }
            if (\count($andConstraints) > 1) {
                $constraintObjects = array();
                foreach ($andConstraints as $andConstraint) {
                    foreach ($this->parseConstraint($andConstraint) as $parsedAndConstraint) {
                        $constraintObjects[] = $parsedAndConstraint;
                    }
                }
            } else {
                $constraintObjects = $this->parseConstraint($andConstraints[0]);
            }

            if (1 === \count($constraintObjects)) {
                $constraint = $constraintObjects[0];
            } else {
                $constraint = new MultiConstraint($constraintObjects);
            }

            $orGroups[] = $constraint;
        }

        $parsedConstraint = MultiConstraint::create($orGroups, false);

        $parsedConstraint->setPrettyString($prettyConstraint);

        return $parsedConstraint;
    }

    /**
     * @param string $constraint
     *
     * @throws \UnexpectedValueException
     *
     * @return array
     *
     * @phpstan-return non-empty-array<ConstraintInterface>
     */
    private function parseConstraint($constraint)
    {
        // strip off aliasing
        if (preg_match('{^([^,\s]++) ++as ++([^,\s]++)$}', $constraint, $match)) {
            $constraint = $match[1];
        }

        // strip @stability flags, and keep it for later use
        if (preg_match('{^([^,\s]*?)@(' . self::$stabilitiesRegex . ')$}i', $constraint, $match)) {
            $constraint = '' !== $match[1] ? $match[1] : '*';
            if ($match[2] !== 'stable') {
                $stabilityModifier = $match[2];
            }
        }

        // get rid of #refs as those are used by composer only
        if (preg_match('{^(dev-[^,\s@]+?|[^,\s@]+?\.x-dev)#.+$}i', $constraint, $match)) {
            $constraint = $match[1];
        }

        if (preg_match('{^(v)?[xX*](\.[xX*])*$}i', $constraint, $match)) {
            if (!empty($match[1]) || !empty($match[2])) {
                return array(new Constraint('>=', '0.0.0.0-dev'));
            }

            return array(new MatchAllConstraint());
        }

        $versionRegex = 'v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.(\d++))?(?:' . self::$modifierRegex . '|\.([xX*][.-]?dev))(?:\+[^\s]+)?';

        // Tilde Range
        //
        // Like wildcard constraints, unsuffixed tilde constraints say that they must be greater than the previous
        // version, to ensure that unstable instances of the current version are allowed. However, if a stability
        // suffix is added to the constraint, then a >= match on the current version is used instead.
        if (preg_match('{^~>?' . $versionRegex . '$}i', $constraint, $matches)) {
            if (strpos($constraint, '~>') === 0) {
                throw new \UnexpectedValueException(
                    'Could not parse version constraint ' . $constraint . ': ' .
                    'Invalid operator "~>", you probably meant to use the "~" operator'
                );
            }

            // Work out which position in the version we are operating at
            if (isset($matches[4]) && '' !== $matches[4] && null !== $matches[4]) {
                $position = 4;
            } elseif (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) {
                $position = 3;
            } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) {
                $position = 2;
            } else {
                $position = 1;
            }

            // when matching 2.x-dev or 3.0.x-dev we have to shift the second or third number, despite no second/third number matching above
            if (!empty($matches[8])) {
                $position++;
            }

            // Calculate the stability suffix
            $stabilitySuffix = '';
            if (empty($matches[5]) && empty($matches[7]) && empty($matches[8])) {
                $stabilitySuffix .= '-dev';
            }

            $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1));
            $lowerBound = new Constraint('>=', $lowVersion);

            // For upper bound, we increment the position of one more significance,
            // but highPosition = 0 would be illegal
            $highPosition = max(1, $position - 1);
            $highVersion = $this->manipulateVersionString($matches, $highPosition, 1) . '-dev';
            $upperBound = new Constraint('<', $highVersion);

            return array(
                $lowerBound,
                $upperBound,
            );
        }

        // Caret Range
        //
        // Allows changes that do not modify the left-most non-zero digit in the [major, minor, patch] tuple.
        // In other words, this allows patch and minor updates for versions 1.0.0 and above, patch updates for
        // versions 0.X >=0.1.0, and no updates for versions 0.0.X
        if (preg_match('{^\^' . $versionRegex . '($)}i', $constraint, $matches)) {
            // Work out which position in the version we are operating at
            if ('0' !== $matches[1] || '' === $matches[2] || null === $matches[2]) {
                $position = 1;
            } elseif ('0' !== $matches[2] || '' === $matches[3] || null === $matches[3]) {
                $position = 2;
            } else {
                $position = 3;
            }

            // Calculate the stability suffix
            $stabilitySuffix = '';
            if (empty($matches[5]) && empty($matches[7]) && empty($matches[8])) {
                $stabilitySuffix .= '-dev';
            }

            $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1));
            $lowerBound = new Constraint('>=', $lowVersion);

            // For upper bound, we increment the position of one more significance,
            // but highPosition = 0 would be illegal
            $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev';
            $upperBound = new Constraint('<', $highVersion);

            return array(
                $lowerBound,
                $upperBound,
            );
        }

        // X Range
        //
        // Any of X, x, or * may be used to "stand in" for one of the numeric values in the [major, minor, patch] tuple.
        // A partial version range is treated as an X-Range, so the special character is in fact optional.
        if (preg_match('{^v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.[xX*])++$}', $constraint, $matches)) {
            if (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) {
                $position = 3;
            } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) {
                $position = 2;
            } else {
                $position = 1;
            }

            $lowVersion = $this->manipulateVersionString($matches, $position) . '-dev';
            $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev';

            if ($lowVersion === '0.0.0.0-dev') {
                return array(new Constraint('<', $highVersion));
            }

            return array(
                new Constraint('>=', $lowVersion),
                new Constraint('<', $highVersion),
            );
        }

        // Hyphen Range
        //
        // Specifies an inclusive set. If a partial version is provided as the first version in the inclusive range,
        // then the missing pieces are replaced with zeroes. If a partial version is provided as the second version in
        // the inclusive range, then all versions that start with the supplied parts of the tuple are accepted, but
        // nothing that would be greater than the provided tuple parts.
        if (preg_match('{^(?P<from>' . $versionRegex . ') +- +(?P<to>' . $versionRegex . ')($)}i', $constraint, $matches)) {
            // Calculate the stability suffix
            $lowStabilitySuffix = '';
            if (empty($matches[6]) && empty($matches[8]) && empty($matches[9])) {
                $lowStabilitySuffix = '-dev';
            }

            $lowVersion = $this->normalize($matches['from']);
            $lowerBound = new Constraint('>=', $lowVersion . $lowStabilitySuffix);

            $empty = function ($x) {
                return ($x === 0 || $x === '0') ? false : empty($x);
            };

            if ((!$empty($matches[12]) && !$empty($matches[13])) || !empty($matches[15]) || !empty($matches[17]) || !empty($matches[18])) {
                $highVersion = $this->normalize($matches['to']);
                $upperBound = new Constraint('<=', $highVersion);
            } else {
                $highMatch = array('', $matches[11], $matches[12], $matches[13], $matches[14]);

                // validate to version
                $this->normalize($matches['to']);

                $highVersion = $this->manipulateVersionString($highMatch, $empty($matches[12]) ? 1 : 2, 1) . '-dev';
                $upperBound = new Constraint('<', $highVersion);
            }

            return array(
                $lowerBound,
                $upperBound,
            );
        }

        // Basic Comparators
        if (preg_match('{^(<>|!=|>=?|<=?|==?)?\s*(.*)}', $constraint, $matches)) {
            try {
                try {
                    $version = $this->normalize($matches[2]);
                } catch (\UnexpectedValueException $e) {
                    // recover from an invalid constraint like foobar-dev which should be dev-foobar
                    // except if the constraint uses a known operator, in which case it must be a parse error
                    if (substr($matches[2], -4) === '-dev' && preg_match('{^[0-9a-zA-Z-./]+$}', $matches[2])) {
                        $version = $this->normalize('dev-'.substr($matches[2], 0, -4));
                    } else {
                        throw $e;
                    }
                }

                $op = $matches[1] ?: '=';

                if ($op !== '==' && $op !== '=' && !empty($stabilityModifier) && self::parseStability($version) === 'stable') {
                    $version .= '-' . $stabilityModifier;
                } elseif ('<' === $op || '>=' === $op) {
                    if (!preg_match('/-' . self::$modifierRegex . '$/', strtolower($matches[2]))) {
                        if (strpos($matches[2], 'dev-') !== 0) {
                            $version .= '-dev';
                        }
                    }
                }

                return array(new Constraint($matches[1] ?: '=', $version));
            } catch (\Exception $e) {
            }
        }

        $message = 'Could not parse version constraint ' . $constraint;
        if (isset($e)) {
            $message .= ': ' . $e->getMessage();
        }

        throw new \UnexpectedValueException($message);
    }

    /**
     * Increment, decrement, or simply pad a version number.
     *
     * Support function for {@link parseConstraint()}
     *
     * @param array  $matches   Array with version parts in array indexes 1,2,3,4
     * @param int    $position  1,2,3,4 - which segment of the version to increment/decrement
     * @param int    $increment
     * @param string $pad       The string to pad version parts after $position
     *
     * @return string|null The new version
     *
     * @phpstan-param string[] $matches
     */
    private function manipulateVersionString(array $matches, $position, $increment = 0, $pad = '0')
    {
        for ($i = 4; $i > 0; --$i) {
            if ($i > $position) {
                $matches[$i] = $pad;
            } elseif ($i === $position && $increment) {
                $matches[$i] += $increment;
                // If $matches[$i] was 0, carry the decrement
                if ($matches[$i] < 0) {
                    $matches[$i] = $pad;
                    --$position;

                    // Return null on a carry overflow
                    if ($i === 1) {
                        return null;
                    }
                }
            }
        }

        return $matches[1] . '.' . $matches[2] . '.' . $matches[3] . '.' . $matches[4];
    }

    /**
     * Expand shorthand stability string to long version.
     *
     * @param string $stability
     *
     * @return string
     */
    private function expandStability($stability)
    {
        $stability = strtolower($stability);

        switch ($stability) {
            case 'a':
                return 'alpha';
            case 'b':
                return 'beta';
            case 'p':
            case 'pl':
                return 'patch';
            case 'rc':
                return 'RC';
            default:
                return $stability;
        }
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver;

use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\ConstraintInterface;

/**
 * Helper class to evaluate constraint by compiling and reusing the code to evaluate
 */
class CompilingMatcher
{
    /**
     * @var array
     * @phpstan-var array<string, callable>
     */
    private static $compiledCheckerCache = array();
    /**
     * @var array
     * @phpstan-var array<string, bool>
     */
    private static $resultCache = array();

    /** @var bool */
    private static $enabled;

    /**
     * @phpstan-var array<Constraint::OP_*, Constraint::STR_OP_*>
     */
    private static $transOpInt = array(
        Constraint::OP_EQ => Constraint::STR_OP_EQ,
        Constraint::OP_LT => Constraint::STR_OP_LT,
        Constraint::OP_LE => Constraint::STR_OP_LE,
        Constraint::OP_GT => Constraint::STR_OP_GT,
        Constraint::OP_GE => Constraint::STR_OP_GE,
        Constraint::OP_NE => Constraint::STR_OP_NE,
    );

    /**
     * Clears the memoization cache once you are done
     *
     * @return void
     */
    public static function clear()
    {
        self::$resultCache = array();
        self::$compiledCheckerCache = array();
    }

    /**
     * Evaluates the expression: $constraint match $operator $version
     *
     * @param ConstraintInterface $constraint
     * @param int                 $operator
     * @phpstan-param Constraint::OP_*  $operator
     * @param string              $version
     *
     * @return bool
     */
    public static function match(ConstraintInterface $constraint, $operator, $version)
    {
        $resultCacheKey = $operator.$constraint.';'.$version;

        if (isset(self::$resultCache[$resultCacheKey])) {
            return self::$resultCache[$resultCacheKey];
        }

        if (self::$enabled === null) {
            self::$enabled = !\in_array('eval', explode(',', (string) ini_get('disable_functions')), true);
        }
        if (!self::$enabled) {
            return self::$resultCache[$resultCacheKey] = $constraint->matches(new Constraint(self::$transOpInt[$operator], $version));
        }

        $cacheKey = $operator.$constraint;
        if (!isset(self::$compiledCheckerCache[$cacheKey])) {
            $code = $constraint->compile($operator);
            self::$compiledCheckerCache[$cacheKey] = $function = eval('return function($v, $b){return '.$code.';};');
        } else {
            $function = self::$compiledCheckerCache[$cacheKey];
        }

        return self::$resultCache[$resultCacheKey] = $function($version, strpos($version, 'dev-') === 0);
    }
}
Copyright (C) 2021 Composer

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.
<?php

/*
 * This file is part of composer/pcre.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Pcre;

final class MatchResult
{
    /**
     * An array of match group => string matched
     *
     * @readonly
     * @var array<int|string, string|null>
     */
    public $matches;

    /**
     * @readonly
     * @var bool
     */
    public $matched;

    /**
     * @param 0|positive-int $count
     * @param array<string|null> $matches
     */
    public function __construct(int $count, array $matches)
    {
        $this->matches = $matches;
        $this->matched = (bool) $count;
    }
}
<?php

/*
 * This file is part of composer/pcre.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Pcre;

class PcreException extends \RuntimeException
{
    /**
     * @param  string $function
     * @param  string|string[] $pattern
     * @return self
     */
    public static function fromFunction($function, $pattern)
    {
        $code = preg_last_error();

        if (is_array($pattern)) {
            $pattern = implode(', ', $pattern);
        }

        return new PcreException($function.'(): failed executing "'.$pattern.'": '.self::pcreLastErrorMessage($code), $code);
    }

    /**
     * @param  int $code
     * @return string
     */
    private static function pcreLastErrorMessage($code)
    {
        if (function_exists('preg_last_error_msg')) {
            return preg_last_error_msg();
        }

        $constants = get_defined_constants(true);
        if (!isset($constants['pcre']) || !is_array($constants['pcre'])) {
            return 'UNDEFINED_ERROR';
        }

        foreach ($constants['pcre'] as $const => $val) {
            if ($val === $code && substr($const, -6) === '_ERROR') {
                return $const;
            }
        }

        return 'UNDEFINED_ERROR';
    }
}
<?php

/*
 * This file is part of composer/pcre.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Pcre;

final class MatchAllWithOffsetsResult
{
    /**
     * An array of match group => list of matches, every match being a pair of string matched + offset in bytes (or -1 if no match)
     *
     * @readonly
     * @var array<int|string, list<array{string|null, int}>>
     * @phpstan-var array<int|string, list<array{string|null, int<-1, max>}>>
     */
    public $matches;

    /**
     * @readonly
     * @var 0|positive-int
     */
    public $count;

    /**
     * @readonly
     * @var bool
     */
    public $matched;

    /**
     * @param 0|positive-int $count
     * @param array<int|string, list<array{string|null, int}>> $matches
     * @phpstan-param array<int|string, list<array{string|null, int<-1, max>}>> $matches
     */
    public function __construct(int $count, array $matches)
    {
        $this->matches = $matches;
        $this->matched = (bool) $count;
        $this->count = $count;
    }
}
<?php

/*
 * This file is part of composer/pcre.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Pcre;

class Preg
{
    /** @internal */
    public const ARRAY_MSG = '$subject as an array is not supported. You can use \'foreach\' instead.';
    /** @internal */
    public const INVALID_TYPE_MSG = '$subject must be a string, %s given.';

    /**
     * @param non-empty-string   $pattern
     * @param array<mixed> $matches Set by method
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
     * @return 0|1
     *
     * @param-out array<int|string, string|null> $matches
     */
    public static function match(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int
    {
        self::checkOffsetCapture($flags, 'matchWithOffsets');

        $result = preg_match($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL, $offset);
        if ($result === false) {
            throw PcreException::fromFunction('preg_match', $pattern);
        }

        return $result;
    }

    /**
     * Variant of `match()` which outputs non-null matches (or throws)
     *
     * @param non-empty-string $pattern
     * @param array<mixed> $matches Set by method
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
     * @return 0|1
     * @throws UnexpectedNullMatchException
     *
     * @param-out array<int|string, string> $matches
     */
    public static function matchStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int
    {
        $result = self::match($pattern, $subject, $matchesInternal, $flags, $offset);
        $matches = self::enforceNonNullMatches($pattern, $matchesInternal, 'match');

        return $result;
    }

    /**
     * Runs preg_match with PREG_OFFSET_CAPTURE
     *
     * @param non-empty-string   $pattern
     * @param array<mixed> $matches Set by method
     * @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_UNMATCHED_AS_NULL and PREG_OFFSET_CAPTURE are always set, no other flags are supported
     * @return 0|1
     *
     * @param-out array<int|string, array{string|null, int<-1, max>}> $matches
     */
    public static function matchWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): int
    {
        $result = preg_match($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL | PREG_OFFSET_CAPTURE, $offset);
        if ($result === false) {
            throw PcreException::fromFunction('preg_match', $pattern);
        }

        return $result;
    }

    /**
     * @param non-empty-string   $pattern
     * @param array<mixed> $matches Set by method
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
     * @return 0|positive-int
     *
     * @param-out array<int|string, list<string|null>> $matches
     */
    public static function matchAll(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int
    {
        self::checkOffsetCapture($flags, 'matchAllWithOffsets');
        self::checkSetOrder($flags);

        $result = preg_match_all($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL, $offset);
        if (!is_int($result)) { // PHP < 8 may return null, 8+ returns int|false
            throw PcreException::fromFunction('preg_match_all', $pattern);
        }

        return $result;
    }

    /**
     * Variant of `match()` which outputs non-null matches (or throws)
     *
     * @param non-empty-string   $pattern
     * @param array<mixed> $matches Set by method
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
     * @return 0|positive-int
     * @throws UnexpectedNullMatchException
     *
     * @param-out array<int|string, list<string>> $matches
     */
    public static function matchAllStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int
    {
        $result = self::matchAll($pattern, $subject, $matchesInternal, $flags, $offset);
        $matches = self::enforceNonNullMatchAll($pattern, $matchesInternal, 'matchAll');

        return $result;
    }

    /**
     * Runs preg_match_all with PREG_OFFSET_CAPTURE
     *
     * @param non-empty-string   $pattern
     * @param array<mixed> $matches Set by method
     * @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_UNMATCHED_AS_NULL and PREG_MATCH_OFFSET are always set, no other flags are supported
     * @return 0|positive-int
     *
     * @param-out array<int|string, list<array{string|null, int<-1, max>}>> $matches
     */
    public static function matchAllWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): int
    {
        self::checkSetOrder($flags);

        $result = preg_match_all($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL | PREG_OFFSET_CAPTURE, $offset);
        if (!is_int($result)) { // PHP < 8 may return null, 8+ returns int|false
            throw PcreException::fromFunction('preg_match_all', $pattern);
        }

        return $result;
    }

    /**
     * @param string|string[] $pattern
     * @param string|string[] $replacement
     * @param string $subject
     * @param int             $count Set by method
     *
     * @param-out int<0, max> $count
     */
    public static function replace($pattern, $replacement, $subject, int $limit = -1, ?int &$count = null): string
    {
        if (!is_scalar($subject)) {
            if (is_array($subject)) {
                throw new \InvalidArgumentException(static::ARRAY_MSG);
            }

            throw new \TypeError(sprintf(static::INVALID_TYPE_MSG, gettype($subject)));
        }

        $result = preg_replace($pattern, $replacement, $subject, $limit, $count);
        if ($result === null) {
            throw PcreException::fromFunction('preg_replace', $pattern);
        }

        return $result;
    }

    /**
     * @param string|string[] $pattern
     * @param ($flags is PREG_OFFSET_CAPTURE ? (callable(array<int|string, array{string|null, int<-1, max>}>): string) : callable(array<int|string, string|null>): string) $replacement
     * @param string $subject
     * @param int             $count Set by method
     * @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set
     *
     * @param-out int<0, max> $count
     */
    public static function replaceCallback($pattern, callable $replacement, $subject, int $limit = -1, ?int &$count = null, int $flags = 0): string
    {
        if (!is_scalar($subject)) {
            if (is_array($subject)) {
                throw new \InvalidArgumentException(static::ARRAY_MSG);
            }

            throw new \TypeError(sprintf(static::INVALID_TYPE_MSG, gettype($subject)));
        }

        $result = preg_replace_callback($pattern, $replacement, $subject, $limit, $count, $flags | PREG_UNMATCHED_AS_NULL);
        if ($result === null) {
            throw PcreException::fromFunction('preg_replace_callback', $pattern);
        }

        return $result;
    }

    /**
     * Variant of `replaceCallback()` which outputs non-null matches (or throws)
     *
     * @param string $pattern
     * @param ($flags is PREG_OFFSET_CAPTURE ? (callable(array<int|string, array{string, int<0, max>}>): string) : callable(array<int|string, string>): string) $replacement
     * @param string $subject
     * @param int $count Set by method
     * @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set
     *
     * @param-out int<0, max> $count
     */
    public static function replaceCallbackStrictGroups(string $pattern, callable $replacement, $subject, int $limit = -1, ?int &$count = null, int $flags = 0): string
    {
        return self::replaceCallback($pattern, function (array $matches) use ($pattern, $replacement) {
            return $replacement(self::enforceNonNullMatches($pattern, $matches, 'replaceCallback'));
        }, $subject, $limit, $count, $flags);
    }

    /**
     * @param ($flags is PREG_OFFSET_CAPTURE ? (array<string, callable(array<int|string, array{string|null, int<-1, max>}>): string>) : array<string, callable(array<int|string, string|null>): string>) $pattern
     * @param string $subject
     * @param int    $count Set by method
     * @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set
     *
     * @param-out int<0, max> $count
     */
    public static function replaceCallbackArray(array $pattern, $subject, int $limit = -1, ?int &$count = null, int $flags = 0): string
    {
        if (!is_scalar($subject)) {
            if (is_array($subject)) {
                throw new \InvalidArgumentException(static::ARRAY_MSG);
            }

            throw new \TypeError(sprintf(static::INVALID_TYPE_MSG, gettype($subject)));
        }

        $result = preg_replace_callback_array($pattern, $subject, $limit, $count, $flags | PREG_UNMATCHED_AS_NULL);
        if ($result === null) {
            $pattern = array_keys($pattern);
            throw PcreException::fromFunction('preg_replace_callback_array', $pattern);
        }

        return $result;
    }

    /**
     * @param int-mask<PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_OFFSET_CAPTURE> $flags PREG_SPLIT_NO_EMPTY or PREG_SPLIT_DELIM_CAPTURE
     * @return list<string>
     */
    public static function split(string $pattern, string $subject, int $limit = -1, int $flags = 0): array
    {
        if (($flags & PREG_SPLIT_OFFSET_CAPTURE) !== 0) {
            throw new \InvalidArgumentException('PREG_SPLIT_OFFSET_CAPTURE is not supported as it changes the type of $matches, use splitWithOffsets() instead');
        }

        $result = preg_split($pattern, $subject, $limit, $flags);
        if ($result === false) {
            throw PcreException::fromFunction('preg_split', $pattern);
        }

        return $result;
    }

    /**
     * @param int-mask<PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_OFFSET_CAPTURE> $flags PREG_SPLIT_NO_EMPTY or PREG_SPLIT_DELIM_CAPTURE, PREG_SPLIT_OFFSET_CAPTURE is always set
     * @return list<array{string, int}>
     * @phpstan-return list<array{string, int<0, max>}>
     */
    public static function splitWithOffsets(string $pattern, string $subject, int $limit = -1, int $flags = 0): array
    {
        $result = preg_split($pattern, $subject, $limit, $flags | PREG_SPLIT_OFFSET_CAPTURE);
        if ($result === false) {
            throw PcreException::fromFunction('preg_split', $pattern);
        }

        return $result;
    }

    /**
     * @template T of string|\Stringable
     * @param string   $pattern
     * @param array<T> $array
     * @param int-mask<PREG_GREP_INVERT> $flags PREG_GREP_INVERT
     * @return array<T>
     */
    public static function grep(string $pattern, array $array, int $flags = 0): array
    {
        $result = preg_grep($pattern, $array, $flags);
        if ($result === false) {
            throw PcreException::fromFunction('preg_grep', $pattern);
        }

        return $result;
    }

    /**
     * Variant of match() which returns a bool instead of int
     *
     * @param non-empty-string   $pattern
     * @param array<mixed> $matches Set by method
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
     *
     * @param-out array<int|string, string|null> $matches
     */
    public static function isMatch(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool
    {
        return (bool) static::match($pattern, $subject, $matches, $flags, $offset);
    }

    /**
     * Variant of `isMatch()` which outputs non-null matches (or throws)
     *
     * @param non-empty-string $pattern
     * @param array<mixed> $matches Set by method
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
     * @throws UnexpectedNullMatchException
     *
     * @param-out array<int|string, string> $matches
     */
    public static function isMatchStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool
    {
        return (bool) self::matchStrictGroups($pattern, $subject, $matches, $flags, $offset);
    }

    /**
     * Variant of matchAll() which returns a bool instead of int
     *
     * @param non-empty-string   $pattern
     * @param array<mixed> $matches Set by method
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
     *
     * @param-out array<int|string, list<string|null>> $matches
     */
    public static function isMatchAll(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool
    {
        return (bool) static::matchAll($pattern, $subject, $matches, $flags, $offset);
    }

    /**
     * Variant of `isMatchAll()` which outputs non-null matches (or throws)
     *
     * @param non-empty-string $pattern
     * @param array<mixed> $matches Set by method
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
     *
     * @param-out array<int|string, list<string>> $matches
     */
    public static function isMatchAllStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool
    {
        return (bool) self::matchAllStrictGroups($pattern, $subject, $matches, $flags, $offset);
    }

    /**
     * Variant of matchWithOffsets() which returns a bool instead of int
     *
     * Runs preg_match with PREG_OFFSET_CAPTURE
     *
     * @param non-empty-string   $pattern
     * @param array<mixed> $matches Set by method
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
     *
     * @param-out array<int|string, array{string|null, int<-1, max>}> $matches
     */
    public static function isMatchWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): bool
    {
        return (bool) static::matchWithOffsets($pattern, $subject, $matches, $flags, $offset);
    }

    /**
     * Variant of matchAllWithOffsets() which returns a bool instead of int
     *
     * Runs preg_match_all with PREG_OFFSET_CAPTURE
     *
     * @param non-empty-string   $pattern
     * @param array<mixed> $matches Set by method
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
     *
     * @param-out array<int|string, list<array{string|null, int<-1, max>}>> $matches
     */
    public static function isMatchAllWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): bool
    {
        return (bool) static::matchAllWithOffsets($pattern, $subject, $matches, $flags, $offset);
    }

    private static function checkOffsetCapture(int $flags, string $useFunctionName): void
    {
        if (($flags & PREG_OFFSET_CAPTURE) !== 0) {
            throw new \InvalidArgumentException('PREG_OFFSET_CAPTURE is not supported as it changes the type of $matches, use ' . $useFunctionName . '() instead');
        }
    }

    private static function checkSetOrder(int $flags): void
    {
        if (($flags & PREG_SET_ORDER) !== 0) {
            throw new \InvalidArgumentException('PREG_SET_ORDER is not supported as it changes the type of $matches');
        }
    }

    /**
     * @param array<int|string, string|null|array{string|null, int}> $matches
     * @return array<int|string, string>
     * @throws UnexpectedNullMatchException
     */
    private static function enforceNonNullMatches(string $pattern, array $matches, string $variantMethod)
    {
        foreach ($matches as $group => $match) {
            if (is_string($match) || (is_array($match) && is_string($match[0]))) {
                continue;
            }

            throw new UnexpectedNullMatchException('Pattern "'.$pattern.'" had an unexpected unmatched group "'.$group.'", make sure the pattern always matches or use '.$variantMethod.'() instead.');
        }

        /** @var array<string> */
        return $matches;
    }

    /**
     * @param array<int|string, list<string|null>> $matches
     * @return array<int|string, list<string>>
     * @throws UnexpectedNullMatchException
     */
    private static function enforceNonNullMatchAll(string $pattern, array $matches, string $variantMethod)
    {
        foreach ($matches as $group => $groupMatches) {
            foreach ($groupMatches as $match) {
                if (null === $match) {
                    throw new UnexpectedNullMatchException('Pattern "'.$pattern.'" had an unexpected unmatched group "'.$group.'", make sure the pattern always matches or use '.$variantMethod.'() instead.');
                }
            }
        }

        /** @var array<int|string, list<string>> */
        return $matches;
    }
}
<?php

/*
 * This file is part of composer/pcre.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Pcre;

final class MatchWithOffsetsResult
{
    /**
     * An array of match group => pair of string matched + offset in bytes (or -1 if no match)
     *
     * @readonly
     * @var array<int|string, array{string|null, int}>
     * @phpstan-var array<int|string, array{string|null, int<-1, max>}>
     */
    public $matches;

    /**
     * @readonly
     * @var bool
     */
    public $matched;

    /**
     * @param 0|positive-int $count
     * @param array<array{string|null, int}> $matches
     * @phpstan-param array<int|string, array{string|null, int<-1, max>}> $matches
     */
    public function __construct(int $count, array $matches)
    {
        $this->matches = $matches;
        $this->matched = (bool) $count;
    }
}
<?php

/*
 * This file is part of composer/pcre.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Pcre;

final class MatchStrictGroupsResult
{
    /**
     * An array of match group => string matched
     *
     * @readonly
     * @var array<int|string, string>
     */
    public $matches;

    /**
     * @readonly
     * @var bool
     */
    public $matched;

    /**
     * @param 0|positive-int $count
     * @param array<string> $matches
     */
    public function __construct(int $count, array $matches)
    {
        $this->matches = $matches;
        $this->matched = (bool) $count;
    }
}
<?php

/*
 * This file is part of composer/pcre.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Pcre;

class UnexpectedNullMatchException extends PcreException
{
    public static function fromFunction($function, $pattern)
    {
        throw new \LogicException('fromFunction should not be called on '.self::class.', use '.PcreException::class);
    }
}
<?php

/*
 * This file is part of composer/pcre.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Pcre;

final class MatchAllStrictGroupsResult
{
    /**
     * An array of match group => list of matched strings
     *
     * @readonly
     * @var array<int|string, list<string>>
     */
    public $matches;

    /**
     * @readonly
     * @var 0|positive-int
     */
    public $count;

    /**
     * @readonly
     * @var bool
     */
    public $matched;

    /**
     * @param 0|positive-int $count
     * @param array<list<string>> $matches
     */
    public function __construct(int $count, array $matches)
    {
        $this->matches = $matches;
        $this->matched = (bool) $count;
        $this->count = $count;
    }
}
<?php declare(strict_types=1);

namespace Composer\Pcre\PHPStan;

use Composer\Pcre\Preg;
use Composer\Pcre\Regex;
use PhpParser\Node;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Name\FullyQualified;
use PHPStan\Analyser\Scope;
use PHPStan\Analyser\SpecifiedTypes;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\TrinaryLogic;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\Php\RegexArrayShapeMatcher;
use function sprintf;

/**
 * @implements Rule<StaticCall>
 */
final class UnsafeStrictGroupsCallRule implements Rule
{
    /**
     * @var RegexArrayShapeMatcher
     */
    private $regexShapeMatcher;

    public function __construct(RegexArrayShapeMatcher $regexShapeMatcher)
    {
        $this->regexShapeMatcher = $regexShapeMatcher;
    }

    public function getNodeType(): string
    {
        return StaticCall::class;
    }

    public function processNode(Node $node, Scope $scope): array
    {
        if (!$node->class instanceof FullyQualified) {
            return [];
        }
        $isRegex = $node->class->toString() === Regex::class;
        $isPreg = $node->class->toString() === Preg::class;
        if (!$isRegex && !$isPreg) {
            return [];
        }
        if (!$node->name instanceof Node\Identifier || !in_array($node->name->name, ['matchStrictGroups', 'isMatchStrictGroups', 'matchAllStrictGroups', 'isMatchAllStrictGroups'], true)) {
            return [];
        }

        $args = $node->getArgs();
        if (!isset($args[0])) {
            return [];
        }

        $patternArg = $args[0] ?? null;
        if ($isPreg) {
            if (!isset($args[2])) { // no matches set, skip as the matches won't be used anyway
                return [];
            }
            $flagsArg = $args[3] ?? null;
        } else {
            $flagsArg = $args[2] ?? null;
        }

        if ($patternArg === null) {
            return [];
        }

        $flagsType = PregMatchFlags::getType($flagsArg, $scope);
        if ($flagsType === null) {
            return [];
        }

        $matchedType = $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createYes(), $scope);
        if ($matchedType === null) {
            return [
                RuleErrorBuilder::message(sprintf('The %s call is potentially unsafe as $matches\' type could not be inferred.', $node->name->name))
                    ->identifier('composerPcre.maybeUnsafeStrictGroups')
                    ->build(),
            ];
        }

        if (count($matchedType->getConstantArrays()) === 1) {
            $matchedType = $matchedType->getConstantArrays()[0];
            $nullableGroups = [];
            foreach ($matchedType->getValueTypes() as $index => $type) {
                if (TypeCombinator::containsNull($type)) {
                    $nullableGroups[] = $matchedType->getKeyTypes()[$index]->getValue();
                }
            }

            if (\count($nullableGroups) > 0) {
                return [
                    RuleErrorBuilder::message(sprintf(
                        'The %s call is unsafe as match group%s "%s" %s optional and may be null.',
                        $node->name->name,
                        \count($nullableGroups) > 1 ? 's' : '',
                        implode('", "', $nullableGroups),
                        \count($nullableGroups) > 1 ? 'are' : 'is'
                    ))->identifier('composerPcre.unsafeStrictGroups')->build(),
                ];
            }
        }

        return [];
    }
}
<?php declare(strict_types=1);

namespace Composer\Pcre\PHPStan;

use Composer\Pcre\Preg;
use PhpParser\Node\Expr\StaticCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParameterReflection;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Php\RegexArrayShapeMatcher;
use PHPStan\Type\StaticMethodParameterOutTypeExtension;
use PHPStan\Type\Type;

final class PregMatchParameterOutTypeExtension implements StaticMethodParameterOutTypeExtension
{
    /**
     * @var RegexArrayShapeMatcher
     */
    private $regexShapeMatcher;

    public function __construct(
        RegexArrayShapeMatcher $regexShapeMatcher
    )
    {
        $this->regexShapeMatcher = $regexShapeMatcher;
    }

    public function isStaticMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool
    {
        return
            $methodReflection->getDeclaringClass()->getName() === Preg::class
            && in_array($methodReflection->getName(), [
                'match', 'isMatch', 'matchStrictGroups', 'isMatchStrictGroups',
                'matchAll', 'isMatchAll', 'matchAllStrictGroups', 'isMatchAllStrictGroups'
            ], true)
            && $parameter->getName() === 'matches';
    }

    public function getParameterOutTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, ParameterReflection $parameter, Scope $scope): ?Type
    {
        $args = $methodCall->getArgs();
        $patternArg = $args[0] ?? null;
        $matchesArg = $args[2] ?? null;
        $flagsArg = $args[3] ?? null;

        if (
            $patternArg === null || $matchesArg === null
        ) {
            return null;
        }

        $flagsType = PregMatchFlags::getType($flagsArg, $scope);
        if ($flagsType === null) {
            return null;
        }

        if (stripos($methodReflection->getName(), 'matchAll') !== false) {
            return $this->regexShapeMatcher->matchAllExpr($patternArg->value, $flagsType, TrinaryLogic::createMaybe(), $scope);
        }

        return $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createMaybe(), $scope);
    }

}
<?php declare(strict_types=1);

namespace Composer\Pcre\PHPStan;

use PHPStan\Analyser\Scope;
use PHPStan\Type\ArrayType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\Type;
use PhpParser\Node\Arg;
use PHPStan\Type\Php\RegexArrayShapeMatcher;
use PHPStan\Type\TypeTraverser;
use PHPStan\Type\UnionType;

final class PregMatchFlags
{
    static public function getType(?Arg $flagsArg, Scope $scope): ?Type
    {
        if ($flagsArg === null) {
            return new ConstantIntegerType(PREG_UNMATCHED_AS_NULL);
        }

        $flagsType = $scope->getType($flagsArg->value);

        $constantScalars = $flagsType->getConstantScalarValues();
        if ($constantScalars === []) {
            return null;
        }

        $internalFlagsTypes = [];
        foreach ($flagsType->getConstantScalarValues() as $constantScalarValue) {
            if (!is_int($constantScalarValue)) {
                return null;
            }

            $internalFlagsTypes[] = new ConstantIntegerType($constantScalarValue | PREG_UNMATCHED_AS_NULL);
        }
        return TypeCombinator::union(...$internalFlagsTypes);
    }

    static public function removeNullFromMatches(Type $matchesType): Type
    {
        return TypeTraverser::map($matchesType, static function (Type $type, callable $traverse): Type {
            if ($type instanceof UnionType || $type instanceof IntersectionType) {
                return $traverse($type);
            }

            if ($type instanceof ConstantArrayType) {
                return new ConstantArrayType(
                    $type->getKeyTypes(),
                    array_map(static function (Type $valueType) use ($traverse): Type {
                        return $traverse($valueType);
                    }, $type->getValueTypes()),
                    $type->getNextAutoIndexes(),
                    [],
                    $type->isList()
                );
            }

            if ($type instanceof ArrayType) {
                return new ArrayType($type->getKeyType(), $traverse($type->getItemType()));
            }

            return TypeCombinator::removeNull($type);
        });
    }

}
<?php declare(strict_types=1);

namespace Composer\Pcre\PHPStan;

use Composer\Pcre\Preg;
use Composer\Pcre\Regex;
use PhpParser\Node\Expr\StaticCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\Native\NativeParameterReflection;
use PHPStan\Reflection\ParameterReflection;
use PHPStan\TrinaryLogic;
use PHPStan\Type\ClosureType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Php\RegexArrayShapeMatcher;
use PHPStan\Type\StaticMethodParameterClosureTypeExtension;
use PHPStan\Type\StringType;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\Type;

final class PregReplaceCallbackClosureTypeExtension implements StaticMethodParameterClosureTypeExtension
{
    /**
     * @var RegexArrayShapeMatcher
     */
    private $regexShapeMatcher;

    public function __construct(RegexArrayShapeMatcher $regexShapeMatcher)
    {
        $this->regexShapeMatcher = $regexShapeMatcher;
    }

    public function isStaticMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool
    {
        return in_array($methodReflection->getDeclaringClass()->getName(), [Preg::class, Regex::class], true)
            && in_array($methodReflection->getName(), ['replaceCallback', 'replaceCallbackStrictGroups'], true)
            && $parameter->getName() === 'replacement';
    }

    public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, ParameterReflection $parameter, Scope $scope): ?Type
    {
        $args = $methodCall->getArgs();
        $patternArg = $args[0] ?? null;
        $flagsArg = $args[5] ?? null;

        if (
            $patternArg === null
        ) {
            return null;
        }

        $flagsType = PregMatchFlags::getType($flagsArg, $scope);

        $matchesType = $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createYes(), $scope);
        if ($matchesType === null) {
            return null;
        }

        if ($methodReflection->getName() === 'replaceCallbackStrictGroups' && count($matchesType->getConstantArrays()) === 1) {
            $matchesType = $matchesType->getConstantArrays()[0];
            $matchesType = new ConstantArrayType(
                $matchesType->getKeyTypes(),
                array_map(static function (Type $valueType): Type {
                    if (count($valueType->getConstantArrays()) === 1) {
                        $valueTypeArray = $valueType->getConstantArrays()[0];
                        return new ConstantArrayType(
                            $valueTypeArray->getKeyTypes(),
                            array_map(static function (Type $valueType): Type {
                                return TypeCombinator::removeNull($valueType);
                            }, $valueTypeArray->getValueTypes()),
                            $valueTypeArray->getNextAutoIndexes(),
                            [],
                            $valueTypeArray->isList()
                        );
                    }
                    return TypeCombinator::removeNull($valueType);
                }, $matchesType->getValueTypes()),
                $matchesType->getNextAutoIndexes(),
                [],
                $matchesType->isList()
            );
        }

        return new ClosureType(
            [
                new NativeParameterReflection($parameter->getName(), $parameter->isOptional(), $matchesType, $parameter->passedByReference(), $parameter->isVariadic(), $parameter->getDefaultValue()),
            ],
            new StringType()
        );
    }
}
<?php declare(strict_types = 1);

namespace Composer\Pcre\PHPStan;

use Composer\Pcre\Preg;
use Composer\Pcre\Regex;
use Composer\Pcre\PcreException;
use Nette\Utils\RegexpException;
use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Name\FullyQualified;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use function in_array;
use function sprintf;

/**
 * Copy of PHPStan's RegularExpressionPatternRule
 *
 * @implements Rule<StaticCall>
 */
class InvalidRegexPatternRule implements Rule
{
    public function getNodeType(): string
    {
        return StaticCall::class;
    }

    public function processNode(Node $node, Scope $scope): array
    {
        $patterns = $this->extractPatterns($node, $scope);

        $errors = [];
        foreach ($patterns as $pattern) {
            $errorMessage = $this->validatePattern($pattern);
            if ($errorMessage === null) {
                continue;
            }

            $errors[] = RuleErrorBuilder::message(sprintf('Regex pattern is invalid: %s', $errorMessage))->identifier('regexp.pattern')->build();
        }

        return $errors;
    }

    /**
     * @return string[]
     */
    private function extractPatterns(StaticCall $node, Scope $scope): array
    {
        if (!$node->class instanceof FullyQualified) {
            return [];
        }
        $isRegex = $node->class->toString() === Regex::class;
        $isPreg = $node->class->toString() === Preg::class;
        if (!$isRegex && !$isPreg) {
            return [];
        }
        if (!$node->name instanceof Node\Identifier || !Preg::isMatch('{^(match|isMatch|grep|replace|split)}', $node->name->name)) {
            return [];
        }

        $functionName = $node->name->name;
        if (!isset($node->getArgs()[0])) {
            return [];
        }

        $patternNode = $node->getArgs()[0]->value;
        $patternType = $scope->getType($patternNode);

        $patternStrings = [];

        foreach ($patternType->getConstantStrings() as $constantStringType) {
            if ($functionName === 'replaceCallbackArray') {
                continue;
            }

            $patternStrings[] = $constantStringType->getValue();
        }

        foreach ($patternType->getConstantArrays() as $constantArrayType) {
            if (
                in_array($functionName, [
                    'replace',
                    'replaceCallback',
                ], true)
            ) {
                foreach ($constantArrayType->getValueTypes() as $arrayKeyType) {
                    foreach ($arrayKeyType->getConstantStrings() as $constantString) {
                        $patternStrings[] = $constantString->getValue();
                    }
                }
            }

            if ($functionName !== 'replaceCallbackArray') {
                continue;
            }

            foreach ($constantArrayType->getKeyTypes() as $arrayKeyType) {
                foreach ($arrayKeyType->getConstantStrings() as $constantString) {
                    $patternStrings[] = $constantString->getValue();
                }
            }
        }

        return $patternStrings;
    }

    private function validatePattern(string $pattern): ?string
    {
        try {
            $msg = null;
            $prev = set_error_handler(function (int $severity, string $message, string $file) use (&$msg): bool {
                $msg = preg_replace("#^preg_match(_all)?\\(.*?\\): #", '', $message);

                return true;
            });

            if ($pattern === '') {
                return 'Empty string is not a valid regular expression';
            }

            Preg::match($pattern, '');
            if ($msg !== null) {
                return $msg;
            }
        } catch (PcreException $e) {
            if ($e->getCode() === PREG_INTERNAL_ERROR && $msg !== null) {
                return $msg;
            }

            return preg_replace('{.*? failed executing ".*": }', '', $e->getMessage());
        } finally {
            restore_error_handler();
        }

        return null;
    }

}
<?php declare(strict_types=1);

namespace Composer\Pcre\PHPStan;

use Composer\Pcre\Preg;
use PhpParser\Node\Expr\StaticCall;
use PHPStan\Analyser\Scope;
use PHPStan\Analyser\SpecifiedTypes;
use PHPStan\Analyser\TypeSpecifier;
use PHPStan\Analyser\TypeSpecifierAwareExtension;
use PHPStan\Analyser\TypeSpecifierContext;
use PHPStan\Reflection\MethodReflection;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Php\RegexArrayShapeMatcher;
use PHPStan\Type\StaticMethodTypeSpecifyingExtension;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\Type;

final class PregMatchTypeSpecifyingExtension implements StaticMethodTypeSpecifyingExtension, TypeSpecifierAwareExtension
{
    /**
     * @var TypeSpecifier
     */
    private $typeSpecifier;

    /**
     * @var RegexArrayShapeMatcher
     */
    private $regexShapeMatcher;

    public function __construct(RegexArrayShapeMatcher $regexShapeMatcher)
    {
        $this->regexShapeMatcher = $regexShapeMatcher;
    }

    public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void
    {
        $this->typeSpecifier = $typeSpecifier;
    }

    public function getClass(): string
    {
        return Preg::class;
    }

    public function isStaticMethodSupported(MethodReflection $methodReflection, StaticCall $node, TypeSpecifierContext $context): bool
    {
        return in_array($methodReflection->getName(), [
                'match', 'isMatch', 'matchStrictGroups', 'isMatchStrictGroups',
                'matchAll', 'isMatchAll', 'matchAllStrictGroups', 'isMatchAllStrictGroups'
            ], true)
            && !$context->null();
    }

    public function specifyTypes(MethodReflection $methodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
    {
        $args = $node->getArgs();
        $patternArg = $args[0] ?? null;
        $matchesArg = $args[2] ?? null;
        $flagsArg = $args[3] ?? null;

        if (
            $patternArg === null || $matchesArg === null
        ) {
            return new SpecifiedTypes();
        }

        $flagsType = PregMatchFlags::getType($flagsArg, $scope);
        if ($flagsType === null) {
            return new SpecifiedTypes();
        }

        if (stripos($methodReflection->getName(), 'matchAll') !== false) {
            $matchedType = $this->regexShapeMatcher->matchAllExpr($patternArg->value, $flagsType, TrinaryLogic::createFromBoolean($context->true()), $scope);
        } else {
            $matchedType = $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createFromBoolean($context->true()), $scope);
        }

        if ($matchedType === null) {
            return new SpecifiedTypes();
        }

        if (
            in_array($methodReflection->getName(), ['matchStrictGroups', 'isMatchStrictGroups', 'matchAllStrictGroups', 'isMatchAllStrictGroups'], true)
        ) {
            $matchedType = PregMatchFlags::removeNullFromMatches($matchedType);
        }

        $overwrite = false;
        if ($context->false()) {
            $overwrite = true;
            $context = $context->negate();
        }

        // @phpstan-ignore function.alreadyNarrowedType
        if (method_exists('PHPStan\Analyser\SpecifiedTypes', 'setRootExpr')) {
            $typeSpecifier = $this->typeSpecifier->create(
                $matchesArg->value,
                $matchedType,
                $context,
                $scope
            )->setRootExpr($node);

            return $overwrite ? $typeSpecifier->setAlwaysOverwriteTypes() : $typeSpecifier;
        }

        // @phpstan-ignore arguments.count
        return $this->typeSpecifier->create(
            $matchesArg->value,
            $matchedType,
            $context,
            // @phpstan-ignore argument.type
            $overwrite,
            $scope,
            $node
        );
    }
}
<?php

/*
 * This file is part of composer/pcre.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Pcre;

final class ReplaceResult
{
    /**
     * @readonly
     * @var string
     */
    public $result;

    /**
     * @readonly
     * @var 0|positive-int
     */
    public $count;

    /**
     * @readonly
     * @var bool
     */
    public $matched;

    /**
     * @param 0|positive-int $count
     */
    public function __construct(int $count, string $result)
    {
        $this->count = $count;
        $this->matched = (bool) $count;
        $this->result = $result;
    }
}
<?php

/*
 * This file is part of composer/pcre.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Pcre;

final class MatchAllResult
{
    /**
     * An array of match group => list of matched strings
     *
     * @readonly
     * @var array<int|string, list<string|null>>
     */
    public $matches;

    /**
     * @readonly
     * @var 0|positive-int
     */
    public $count;

    /**
     * @readonly
     * @var bool
     */
    public $matched;

    /**
     * @param 0|positive-int $count
     * @param array<int|string, list<string|null>> $matches
     */
    public function __construct(int $count, array $matches)
    {
        $this->matches = $matches;
        $this->matched = (bool) $count;
        $this->count = $count;
    }
}
<?php

/*
 * This file is part of composer/pcre.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Pcre;

class Regex
{
    /**
     * @param non-empty-string $pattern
     */
    public static function isMatch(string $pattern, string $subject, int $offset = 0): bool
    {
        return (bool) Preg::match($pattern, $subject, $matches, 0, $offset);
    }

    /**
     * @param non-empty-string $pattern
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
     */
    public static function match(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchResult
    {
        self::checkOffsetCapture($flags, 'matchWithOffsets');

        $count = Preg::match($pattern, $subject, $matches, $flags, $offset);

        return new MatchResult($count, $matches);
    }

    /**
     * Variant of `match()` which returns non-null matches (or throws)
     *
     * @param non-empty-string $pattern
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
     * @throws UnexpectedNullMatchException
     */
    public static function matchStrictGroups(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchStrictGroupsResult
    {
        // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups
        $count = Preg::matchStrictGroups($pattern, $subject, $matches, $flags, $offset);

        return new MatchStrictGroupsResult($count, $matches);
    }

    /**
     * Runs preg_match with PREG_OFFSET_CAPTURE
     *
     * @param non-empty-string $pattern
     * @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_UNMATCHED_AS_NULL and PREG_MATCH_OFFSET are always set, no other flags are supported
     */
    public static function matchWithOffsets(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchWithOffsetsResult
    {
        $count = Preg::matchWithOffsets($pattern, $subject, $matches, $flags, $offset);

        return new MatchWithOffsetsResult($count, $matches);
    }

    /**
     * @param non-empty-string $pattern
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
     */
    public static function matchAll(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchAllResult
    {
        self::checkOffsetCapture($flags, 'matchAllWithOffsets');
        self::checkSetOrder($flags);

        $count = Preg::matchAll($pattern, $subject, $matches, $flags, $offset);

        return new MatchAllResult($count, $matches);
    }

    /**
     * Variant of `matchAll()` which returns non-null matches (or throws)
     *
     * @param non-empty-string $pattern
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
     * @throws UnexpectedNullMatchException
     */
    public static function matchAllStrictGroups(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchAllStrictGroupsResult
    {
        self::checkOffsetCapture($flags, 'matchAllWithOffsets');
        self::checkSetOrder($flags);

        // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups
        $count = Preg::matchAllStrictGroups($pattern, $subject, $matches, $flags, $offset);

        return new MatchAllStrictGroupsResult($count, $matches);
    }

    /**
     * Runs preg_match_all with PREG_OFFSET_CAPTURE
     *
     * @param non-empty-string $pattern
     * @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_UNMATCHED_AS_NULL and PREG_MATCH_OFFSET are always set, no other flags are supported
     */
    public static function matchAllWithOffsets(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchAllWithOffsetsResult
    {
        self::checkSetOrder($flags);

        $count = Preg::matchAllWithOffsets($pattern, $subject, $matches, $flags, $offset);

        return new MatchAllWithOffsetsResult($count, $matches);
    }
    /**
     * @param string|string[] $pattern
     * @param string|string[] $replacement
     * @param string          $subject
     */
    public static function replace($pattern, $replacement, $subject, int $limit = -1): ReplaceResult
    {
        $result = Preg::replace($pattern, $replacement, $subject, $limit, $count);

        return new ReplaceResult($count, $result);
    }

    /**
     * @param string|string[] $pattern
     * @param ($flags is PREG_OFFSET_CAPTURE ? (callable(array<int|string, array{string|null, int<-1, max>}>): string) : callable(array<int|string, string|null>): string) $replacement
     * @param string          $subject
     * @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set
     */
    public static function replaceCallback($pattern, callable $replacement, $subject, int $limit = -1, int $flags = 0): ReplaceResult
    {
        $result = Preg::replaceCallback($pattern, $replacement, $subject, $limit, $count, $flags);

        return new ReplaceResult($count, $result);
    }

    /**
     * Variant of `replaceCallback()` which outputs non-null matches (or throws)
     *
     * @param string $pattern
     * @param ($flags is PREG_OFFSET_CAPTURE ? (callable(array<int|string, array{string, int<0, max>}>): string) : callable(array<int|string, string>): string) $replacement
     * @param string          $subject
     * @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set
     */
    public static function replaceCallbackStrictGroups($pattern, callable $replacement, $subject, int $limit = -1, int $flags = 0): ReplaceResult
    {
        $result = Preg::replaceCallbackStrictGroups($pattern, $replacement, $subject, $limit, $count, $flags);

        return new ReplaceResult($count, $result);
    }

    /**
     * @param ($flags is PREG_OFFSET_CAPTURE ? (array<string, callable(array<int|string, array{string|null, int<-1, max>}>): string>) : array<string, callable(array<int|string, string|null>): string>) $pattern
     * @param string $subject
     * @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set
     */
    public static function replaceCallbackArray(array $pattern, $subject, int $limit = -1, int $flags = 0): ReplaceResult
    {
        $result = Preg::replaceCallbackArray($pattern, $subject, $limit, $count, $flags);

        return new ReplaceResult($count, $result);
    }

    private static function checkOffsetCapture(int $flags, string $useFunctionName): void
    {
        if (($flags & PREG_OFFSET_CAPTURE) !== 0) {
            throw new \InvalidArgumentException('PREG_OFFSET_CAPTURE is not supported as it changes the return type, use '.$useFunctionName.'() instead');
        }
    }

    private static function checkSetOrder(int $flags): void
    {
        if (($flags & PREG_SET_ORDER) !== 0) {
            throw new \InvalidArgumentException('PREG_SET_ORDER is not supported as it changes the return type');
        }
    }
}
# composer/pcre PHPStan extensions
#
# These can be reused by third party packages by including 'vendor/composer/pcre/extension.neon'
# in your phpstan config

services:
    -
        class: Composer\Pcre\PHPStan\PregMatchParameterOutTypeExtension
        tags:
            - phpstan.staticMethodParameterOutTypeExtension
    -
        class: Composer\Pcre\PHPStan\PregMatchTypeSpecifyingExtension
        tags:
            - phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension
    -
        class: Composer\Pcre\PHPStan\PregReplaceCallbackClosureTypeExtension
        tags:
            - phpstan.staticMethodParameterClosureTypeExtension

rules:
    - Composer\Pcre\PHPStan\UnsafeStrictGroupsCallRule
    - Composer\Pcre\PHPStan\InvalidRegexPatternRule
MIT License

Copyright (c) 2017 Composer

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.
<?php

declare(strict_types=1);

/*
 * This file is part of composer/xdebug-handler.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\XdebugHandler;

/**
 * @author John Stevenson <john-stevenson@blueyonder.co.uk>
 *
 * @phpstan-type restartData array{tmpIni: string, scannedInis: bool, scanDir: false|string, phprc: false|string, inis: string[], skipped: string}
 */
class PhpConfig
{
    /**
     * Use the original PHP configuration
     *
     * @return string[] Empty array of PHP cli options
     */
    public function useOriginal(): array
    {
        $this->getDataAndReset();
        return [];
    }

    /**
     * Use standard restart settings
     *
     * @return string[] PHP cli options
     */
    public function useStandard(): array
    {
        $data = $this->getDataAndReset();
        if ($data !== null) {
            return ['-n', '-c', $data['tmpIni']];
        }

        return [];
    }

    /**
     * Use environment variables to persist settings
     *
     * @return string[] Empty array of PHP cli options
     */
    public function usePersistent(): array
    {
        $data = $this->getDataAndReset();
        if ($data !== null) {
            $this->updateEnv('PHPRC', $data['tmpIni']);
            $this->updateEnv('PHP_INI_SCAN_DIR', '');
        }

        return [];
    }

    /**
     * Returns restart data if available and resets the environment
     *
     * @phpstan-return restartData|null
     */
    private function getDataAndReset(): ?array
    {
        $data = XdebugHandler::getRestartSettings();
        if ($data !== null) {
            $this->updateEnv('PHPRC', $data['phprc']);
            $this->updateEnv('PHP_INI_SCAN_DIR', $data['scanDir']);
        }

        return $data;
    }

    /**
     * Updates a restart settings value in the environment
     *
     * @param string $name
     * @param string|false $value
     */
    private function updateEnv(string $name, $value): void
    {
        Process::setEnv($name, false !== $value ? $value : null);
    }
}
<?php

/*
 * This file is part of composer/xdebug-handler.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Composer\XdebugHandler;

use Composer\Pcre\Preg;

/**
 * Process utility functions
 *
 * @author John Stevenson <john-stevenson@blueyonder.co.uk>
 */
class Process
{
    /**
     * Escapes a string to be used as a shell argument.
     *
     * From https://github.com/johnstevenson/winbox-args
     * MIT Licensed (c) John Stevenson <john-stevenson@blueyonder.co.uk>
     *
     * @param string $arg  The argument to be escaped
     * @param bool $meta Additionally escape cmd.exe meta characters
     * @param bool $module The argument is the module to invoke
     */
    public static function escape(string $arg, bool $meta = true, bool $module = false): string
    {
        if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
            return "'".str_replace("'", "'\\''", $arg)."'";
        }

        $quote = strpbrk($arg, " \t") !== false || $arg === '';

        $arg = Preg::replace('/(\\\\*)"/', '$1$1\\"', $arg, -1, $dquotes);
        $dquotes = (bool) $dquotes;

        if ($meta) {
            $meta = $dquotes || Preg::isMatch('/%[^%]+%/', $arg);

            if (!$meta) {
                $quote = $quote || strpbrk($arg, '^&|<>()') !== false;
            } elseif ($module && !$dquotes && $quote) {
                $meta = false;
            }
        }

        if ($quote) {
            $arg = '"'.(Preg::replace('/(\\\\*)$/', '$1$1', $arg)).'"';
        }

        if ($meta) {
            $arg = Preg::replace('/(["^&|<>()%])/', '^$1', $arg);
        }

        return $arg;
    }

    /**
     * Escapes an array of arguments that make up a shell command
     *
     * @param string[] $args Argument list, with the module name first
     */
    public static function escapeShellCommand(array $args): string
    {
        $command = '';
        $module = array_shift($args);

        if ($module !== null) {
            $command = self::escape($module, true, true);

            foreach ($args as $arg) {
                $command .= ' '.self::escape($arg);
            }
        }

        return $command;
    }

    /**
     * Makes putenv environment changes available in $_SERVER and $_ENV
     *
     * @param string $name
     * @param ?string $value A null value unsets the variable
      */
    public static function setEnv(string $name, ?string $value = null): bool
    {
        $unset = null === $value;

        if (!putenv($unset ? $name : $name.'='.$value)) {
            return false;
        }

        if ($unset) {
            unset($_SERVER[$name]);
        } else {
            $_SERVER[$name] = $value;
        }

        // Update $_ENV if it is being used
        if (false !== stripos((string) ini_get('variables_order'), 'E')) {
            if ($unset) {
                unset($_ENV[$name]);
            } else {
                $_ENV[$name] = $value;
            }
        }

        return true;
    }
}
<?php

/*
 * This file is part of composer/xdebug-handler.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Composer\XdebugHandler;

use Composer\Pcre\Preg;
use Psr\Log\LoggerInterface;

/**
 * @author John Stevenson <john-stevenson@blueyonder.co.uk>
 *
 * @phpstan-import-type restartData from PhpConfig
 */
class XdebugHandler
{
    const SUFFIX_ALLOW = '_ALLOW_XDEBUG';
    const SUFFIX_INIS = '_ORIGINAL_INIS';
    const RESTART_ID = 'internal';
    const RESTART_SETTINGS = 'XDEBUG_HANDLER_SETTINGS';
    const DEBUG = 'XDEBUG_HANDLER_DEBUG';

    /** @var string|null */
    protected $tmpIni;

    /** @var bool */
    private static $inRestart;

    /** @var string */
    private static $name;

    /** @var string|null */
    private static $skipped;

    /** @var bool */
    private static $xdebugActive;

    /** @var string|null */
    private static $xdebugMode;

    /** @var string|null */
    private static $xdebugVersion;

    /** @var bool */
    private $cli;

    /** @var string|null */
    private $debug;

    /** @var string */
    private $envAllowXdebug;

    /** @var string */
    private $envOriginalInis;

    /** @var bool */
    private $persistent;

    /** @var string|null */
    private $script;

    /** @var Status */
    private $statusWriter;

    /**
     * Constructor
     *
     * The $envPrefix is used to create distinct environment variables. It is
     * uppercased and prepended to the default base values. For example 'myapp'
     * would result in MYAPP_ALLOW_XDEBUG and MYAPP_ORIGINAL_INIS.
     *
     * @param string $envPrefix Value used in environment variables
     * @throws \RuntimeException If the parameter is invalid
     */
    public function __construct(string $envPrefix)
    {
        if ($envPrefix === '') {
            throw new \RuntimeException('Invalid constructor parameter');
        }

        self::$name = strtoupper($envPrefix);
        $this->envAllowXdebug = self::$name.self::SUFFIX_ALLOW;
        $this->envOriginalInis = self::$name.self::SUFFIX_INIS;

        self::setXdebugDetails();
        self::$inRestart = false;

        if ($this->cli = PHP_SAPI === 'cli') {
            $this->debug = (string) getenv(self::DEBUG);
        }

        $this->statusWriter = new Status($this->envAllowXdebug, (bool) $this->debug);
    }

    /**
     * Activates status message output to a PSR3 logger
     */
    public function setLogger(LoggerInterface $logger): self
    {
        $this->statusWriter->setLogger($logger);
        return $this;
    }

    /**
     * Sets the main script location if it cannot be called from argv
     */
    public function setMainScript(string $script): self
    {
        $this->script = $script;
        return $this;
    }

    /**
     * Persist the settings to keep Xdebug out of sub-processes
     */
    public function setPersistent(): self
    {
        $this->persistent = true;
        return $this;
    }

    /**
     * Checks if Xdebug is loaded and the process needs to be restarted
     *
     * This behaviour can be disabled by setting the MYAPP_ALLOW_XDEBUG
     * environment variable to 1. This variable is used internally so that
     * the restarted process is created only once.
     */
    public function check(): void
    {
        $this->notify(Status::CHECK, self::$xdebugVersion.'|'.self::$xdebugMode);
        $envArgs = explode('|', (string) getenv($this->envAllowXdebug));

        if (!((bool) $envArgs[0]) && $this->requiresRestart(self::$xdebugActive)) {
            // Restart required
            $this->notify(Status::RESTART);
            $command = $this->prepareRestart();

            if ($command !== null) {
                $this->restart($command);
            }
            return;
        }

        if (self::RESTART_ID === $envArgs[0] && count($envArgs) === 5) {
            // Restarted, so unset environment variable and use saved values
            $this->notify(Status::RESTARTED);

            Process::setEnv($this->envAllowXdebug);
            self::$inRestart = true;

            if (self::$xdebugVersion === null) {
                // Skipped version is only set if Xdebug is not loaded
                self::$skipped = $envArgs[1];
            }

            $this->tryEnableSignals();

            // Put restart settings in the environment
            $this->setEnvRestartSettings($envArgs);
            return;
        }

        $this->notify(Status::NORESTART);
        $settings = self::getRestartSettings();

        if ($settings !== null) {
            // Called with existing settings, so sync our settings
            $this->syncSettings($settings);
        }
    }

    /**
     * Returns an array of php.ini locations with at least one entry
     *
     * The equivalent of calling php_ini_loaded_file then php_ini_scanned_files.
     * The loaded ini location is the first entry and may be an empty string.
     *
     * @return non-empty-list<string>
     */
    public static function getAllIniFiles(): array
    {
        if (self::$name !== null) {
            $env = getenv(self::$name.self::SUFFIX_INIS);

            if (false !== $env) {
                return explode(PATH_SEPARATOR, $env);
            }
        }

        $paths = [(string) php_ini_loaded_file()];
        $scanned = php_ini_scanned_files();

        if ($scanned !== false) {
            $paths = array_merge($paths, array_map('trim', explode(',', $scanned)));
        }

        return $paths;
    }

    /**
     * Returns an array of restart settings or null
     *
     * Settings will be available if the current process was restarted, or
     * called with the settings from an existing restart.
     *
     * @phpstan-return restartData|null
     */
    public static function getRestartSettings(): ?array
    {
        $envArgs = explode('|', (string) getenv(self::RESTART_SETTINGS));

        if (count($envArgs) !== 6
            || (!self::$inRestart && php_ini_loaded_file() !== $envArgs[0])) {
            return null;
        }

        return [
            'tmpIni' => $envArgs[0],
            'scannedInis' => (bool) $envArgs[1],
            'scanDir' => '*' === $envArgs[2] ? false : $envArgs[2],
            'phprc' => '*' === $envArgs[3] ? false : $envArgs[3],
            'inis' => explode(PATH_SEPARATOR, $envArgs[4]),
            'skipped' => $envArgs[5],
        ];
    }

    /**
     * Returns the Xdebug version that triggered a successful restart
     */
    public static function getSkippedVersion(): string
    {
        return (string) self::$skipped;
    }

    /**
     * Returns whether Xdebug is loaded and active
     *
     * true: if Xdebug is loaded and is running in an active mode.
     * false: if Xdebug is not loaded, or it is running with xdebug.mode=off.
     */
    public static function isXdebugActive(): bool
    {
        self::setXdebugDetails();
        return self::$xdebugActive;
    }

    /**
     * Allows an extending class to decide if there should be a restart
     *
     * The default is to restart if Xdebug is loaded and its mode is not "off".
     */
    protected function requiresRestart(bool $default): bool
    {
        return $default;
    }

    /**
     * Allows an extending class to access the tmpIni
     *
     * @param non-empty-list<string> $command
     */
    protected function restart(array $command): void
    {
        $this->doRestart($command);
    }

    /**
     * Executes the restarted command then deletes the tmp ini
     *
     * @param non-empty-list<string> $command
     * @phpstan-return never
     */
    private function doRestart(array $command): void
    {
        if (PHP_VERSION_ID >= 70400) {
            $cmd = $command;
            $displayCmd = sprintf('[%s]', implode(', ', $cmd));
        } else {
            $cmd = Process::escapeShellCommand($command);
            if (defined('PHP_WINDOWS_VERSION_BUILD')) {
                // Outer quotes required on cmd string below PHP 8
                $cmd = '"'.$cmd.'"';
            }
            $displayCmd = $cmd;
        }

        $this->tryEnableSignals();
        $this->notify(Status::RESTARTING, $displayCmd);

        $process = proc_open($cmd, [], $pipes);
        if (is_resource($process)) {
            $exitCode = proc_close($process);
        }

        if (!isset($exitCode)) {
            // Unlikely that php or the default shell cannot be invoked
            $this->notify(Status::ERROR, 'Unable to restart process');
            $exitCode = -1;
        } else {
            $this->notify(Status::INFO, 'Restarted process exited '.$exitCode);
        }

        if ($this->debug === '2') {
            $this->notify(Status::INFO, 'Temp ini saved: '.$this->tmpIni);
        } else {
            @unlink((string) $this->tmpIni);
        }

        exit($exitCode);
    }

    /**
     * Returns the command line array if everything was written for the restart
     *
     * If any of the following fails (however unlikely) we must return false to
     * stop potential recursion:
     *   - tmp ini file creation
     *   - environment variable creation
     *
     * @return non-empty-list<string>|null
     */
    private function prepareRestart(): ?array
    {
        if (!$this->cli) {
            $this->notify(Status::ERROR, 'Unsupported SAPI: '.PHP_SAPI);
            return null;
        }

        if (($argv = $this->checkServerArgv()) === null) {
            $this->notify(Status::ERROR, '$_SERVER[argv] is not as expected');
            return null;
        }

        if (!$this->checkConfiguration($info)) {
            $this->notify(Status::ERROR, $info);
            return null;
        }

        $mainScript = (string) $this->script;
        if (!$this->checkMainScript($mainScript, $argv)) {
            $this->notify(Status::ERROR, 'Unable to access main script: '.$mainScript);
            return null;
        }

        $tmpDir = sys_get_temp_dir();
        $iniError = 'Unable to create temp ini file at: '.$tmpDir;

        if (($tmpfile = @tempnam($tmpDir, '')) === false) {
            $this->notify(Status::ERROR, $iniError);
            return null;
        }

        $error = null;
        $iniFiles = self::getAllIniFiles();
        $scannedInis = count($iniFiles) > 1;

        if (!$this->writeTmpIni($tmpfile, $iniFiles, $error)) {
            $this->notify(Status::ERROR, $error ?? $iniError);
            @unlink($tmpfile);
            return null;
        }

        if (!$this->setEnvironment($scannedInis, $iniFiles, $tmpfile)) {
            $this->notify(Status::ERROR, 'Unable to set environment variables');
            @unlink($tmpfile);
            return null;
        }

        $this->tmpIni = $tmpfile;

        return $this->getCommand($argv, $tmpfile, $mainScript);
    }

    /**
     * Returns true if the tmp ini file was written
     *
     * @param non-empty-list<string> $iniFiles All ini files used in the current process
     */
    private function writeTmpIni(string $tmpFile, array $iniFiles, ?string &$error): bool
    {
        // $iniFiles has at least one item and it may be empty
        if ($iniFiles[0] === '') {
            array_shift($iniFiles);
        }

        $content = '';
        $sectionRegex = '/^\s*\[(?:PATH|HOST)\s*=/mi';
        $xdebugRegex = '/^\s*(zend_extension\s*=.*xdebug.*)$/mi';

        foreach ($iniFiles as $file) {
            // Check for inaccessible ini files
            if (($data = @file_get_contents($file)) === false) {
                $error = 'Unable to read ini: '.$file;
                return false;
            }
            // Check and remove directives after HOST and PATH sections
            if (Preg::isMatchWithOffsets($sectionRegex, $data, $matches)) {
                $data = substr($data, 0, $matches[0][1]);
            }
            $content .= Preg::replace($xdebugRegex, ';$1', $data).PHP_EOL;
        }

        // Merge loaded settings into our ini content, if it is valid
        $config = parse_ini_string($content);
        $loaded = ini_get_all(null, false);

        if (false === $config || false === $loaded) {
            $error = 'Unable to parse ini data';
            return false;
        }

        $content .= $this->mergeLoadedConfig($loaded, $config);

        // Work-around for https://bugs.php.net/bug.php?id=75932
        $content .= 'opcache.enable_cli=0'.PHP_EOL;

        return (bool) @file_put_contents($tmpFile, $content);
    }

    /**
     * Returns the command line arguments for the restart
     *
     * @param non-empty-list<string> $argv
     * @return non-empty-list<string>
     */
    private function getCommand(array $argv, string $tmpIni, string $mainScript): array
    {
        $php = [PHP_BINARY];
        $args = array_slice($argv, 1);

        if (!$this->persistent) {
            // Use command-line options
            array_push($php, '-n', '-c', $tmpIni);
        }

        return array_merge($php, [$mainScript], $args);
    }

    /**
     * Returns true if the restart environment variables were set
     *
     * No need to update $_SERVER since this is set in the restarted process.
     *
     * @param non-empty-list<string> $iniFiles All ini files used in the current process
     */
    private function setEnvironment(bool $scannedInis, array $iniFiles, string $tmpIni): bool
    {
        $scanDir = getenv('PHP_INI_SCAN_DIR');
        $phprc = getenv('PHPRC');

        // Make original inis available to restarted process
        if (!putenv($this->envOriginalInis.'='.implode(PATH_SEPARATOR, $iniFiles))) {
            return false;
        }

        if ($this->persistent) {
            // Use the environment to persist the settings
            if (!putenv('PHP_INI_SCAN_DIR=') || !putenv('PHPRC='.$tmpIni)) {
                return false;
            }
        }

        // Flag restarted process and save values for it to use
        $envArgs = [
            self::RESTART_ID,
            self::$xdebugVersion,
            (int) $scannedInis,
            false === $scanDir ? '*' : $scanDir,
            false === $phprc ? '*' : $phprc,
        ];

        return putenv($this->envAllowXdebug.'='.implode('|', $envArgs));
    }

    /**
     * Logs status messages
     */
    private function notify(string $op, ?string $data = null): void
    {
        $this->statusWriter->report($op, $data);
    }

    /**
     * Returns default, changed and command-line ini settings
     *
     * @param mixed[] $loadedConfig All current ini settings
     * @param mixed[] $iniConfig Settings from user ini files
     *
     */
    private function mergeLoadedConfig(array $loadedConfig, array $iniConfig): string
    {
        $content = '';

        foreach ($loadedConfig as $name => $value) {
            // Value will either be null, string or array (HHVM only)
            if (!is_string($value)
                || strpos($name, 'xdebug') === 0
                || $name === 'apc.mmap_file_mask') {
                continue;
            }

            if (!isset($iniConfig[$name]) || $iniConfig[$name] !== $value) {
                // Double-quote escape each value
                $content .= $name.'="'.addcslashes($value, '\\"').'"'.PHP_EOL;
            }
        }

        return $content;
    }

    /**
     * Returns true if the script name can be used
     *
     * @param non-empty-list<string> $argv
     */
    private function checkMainScript(string &$mainScript, array $argv): bool
    {
        if ($mainScript !== '') {
            // Allow an application to set -- for standard input
            return file_exists($mainScript) || '--' === $mainScript;
        }

        if (file_exists($mainScript = $argv[0])) {
            return true;
        }

        // Use a backtrace to resolve Phar and chdir issues.
        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
        $main = end($trace);

        if ($main !== false && isset($main['file'])) {
            return file_exists($mainScript = $main['file']);
        }

        return false;
    }

    /**
     * Adds restart settings to the environment
     *
     * @param non-empty-list<string> $envArgs
     */
    private function setEnvRestartSettings(array $envArgs): void
    {
        $settings = [
            php_ini_loaded_file(),
            $envArgs[2],
            $envArgs[3],
            $envArgs[4],
            getenv($this->envOriginalInis),
            self::$skipped,
        ];

        Process::setEnv(self::RESTART_SETTINGS, implode('|', $settings));
    }

    /**
     * Syncs settings and the environment if called with existing settings
     *
     * @phpstan-param restartData $settings
     */
    private function syncSettings(array $settings): void
    {
        if (false === getenv($this->envOriginalInis)) {
            // Called by another app, so make original inis available
            Process::setEnv($this->envOriginalInis, implode(PATH_SEPARATOR, $settings['inis']));
        }

        self::$skipped = $settings['skipped'];
        $this->notify(Status::INFO, 'Process called with existing restart settings');
    }

    /**
     * Returns true if there are no known configuration issues
     */
    private function checkConfiguration(?string &$info): bool
    {
        if (!function_exists('proc_open')) {
            $info = 'proc_open function is disabled';
            return false;
        }

        if (!file_exists(PHP_BINARY)) {
            $info = 'PHP_BINARY is not available';
            return false;
        }

        if (extension_loaded('uopz') && !((bool) ini_get('uopz.disable'))) {
            // uopz works at opcode level and disables exit calls
            if (function_exists('uopz_allow_exit')) {
                @uopz_allow_exit(true);
            } else {
                $info = 'uopz extension is not compatible';
                return false;
            }
        }

        // Check UNC paths when using cmd.exe
        if (defined('PHP_WINDOWS_VERSION_BUILD') && PHP_VERSION_ID < 70400) {
            $workingDir = getcwd();

            if ($workingDir === false) {
                $info = 'unable to determine working directory';
                return false;
            }

            if (0 === strpos($workingDir, '\\\\')) {
                $info = 'cmd.exe does not support UNC paths: '.$workingDir;
                return false;
            }
        }

        return true;
    }

    /**
     * Enables async signals and control interrupts in the restarted process
     *
     * Available on Unix PHP 7.1+ with the pcntl extension and Windows PHP 7.4+.
     */
    private function tryEnableSignals(): void
    {
        if (function_exists('pcntl_async_signals') && function_exists('pcntl_signal')) {
            pcntl_async_signals(true);
            $message = 'Async signals enabled';

            if (!self::$inRestart) {
                // Restarting, so ignore SIGINT in parent
                pcntl_signal(SIGINT, SIG_IGN);
            } elseif (is_int(pcntl_signal_get_handler(SIGINT))) {
                // Restarted, no handler set so force default action
                pcntl_signal(SIGINT, SIG_DFL);
            }
        }

        if (!self::$inRestart && function_exists('sapi_windows_set_ctrl_handler')) {
            // Restarting, so set a handler to ignore CTRL events in the parent.
            // This ensures that CTRL+C events will be available in the child
            // process without having to enable them there, which is unreliable.
            sapi_windows_set_ctrl_handler(function ($evt) {});
        }
    }

    /**
     * Returns $_SERVER['argv'] if it is as expected
     *
     * @return non-empty-list<string>|null
     */
    private function checkServerArgv(): ?array
    {
        $result = [];

        if (isset($_SERVER['argv']) && is_array($_SERVER['argv'])) {
            foreach ($_SERVER['argv'] as $value) {
                if (!is_string($value)) {
                    return null;
                }

                $result[] = $value;
            }
        }

        return count($result) > 0 ? $result : null;
    }

    /**
     * Sets static properties $xdebugActive, $xdebugVersion and $xdebugMode
     */
    private static function setXdebugDetails(): void
    {
        if (self::$xdebugActive !== null) {
            return;
        }

        self::$xdebugActive = false;
        if (!extension_loaded('xdebug')) {
            return;
        }

        $version = phpversion('xdebug');
        self::$xdebugVersion = $version !== false ? $version : 'unknown';

        if (version_compare(self::$xdebugVersion, '3.1', '>=')) {
            $modes = xdebug_info('mode');
            self::$xdebugMode = count($modes) === 0 ? 'off' : implode(',', $modes);
            self::$xdebugActive = self::$xdebugMode !== 'off';
            return;
        }

        // See if xdebug.mode is supported in this version
        $iniMode = ini_get('xdebug.mode');
        if ($iniMode === false) {
            self::$xdebugActive = true;
            return;
        }

        // Environment value wins but cannot be empty
        $envMode = (string) getenv('XDEBUG_MODE');
        if ($envMode !== '') {
            self::$xdebugMode = $envMode;
        } else {
            self::$xdebugMode = $iniMode !== '' ? $iniMode : 'off';
        }

        // An empty comma-separated list is treated as mode 'off'
        if (Preg::isMatch('/^,+$/', str_replace(' ', '', self::$xdebugMode))) {
            self::$xdebugMode = 'off';
        }

        self::$xdebugActive = self::$xdebugMode !== 'off';
    }
}
<?php

/*
 * This file is part of composer/xdebug-handler.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Composer\XdebugHandler;

use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;

/**
 * @author John Stevenson <john-stevenson@blueyonder.co.uk>
 * @internal
 */
class Status
{
    const ENV_RESTART = 'XDEBUG_HANDLER_RESTART';
    const CHECK = 'Check';
    const ERROR = 'Error';
    const INFO = 'Info';
    const NORESTART = 'NoRestart';
    const RESTART = 'Restart';
    const RESTARTING = 'Restarting';
    const RESTARTED = 'Restarted';

    /** @var bool */
    private $debug;

    /** @var string */
    private $envAllowXdebug;

    /** @var string|null */
    private $loaded;

    /** @var LoggerInterface|null */
    private $logger;

    /** @var bool */
    private $modeOff;

     /** @var float */
    private $time;

    /**
     * @param string $envAllowXdebug Prefixed _ALLOW_XDEBUG name
     * @param bool $debug Whether debug output is required
     */
    public function __construct(string $envAllowXdebug, bool $debug)
    {
        $start = getenv(self::ENV_RESTART);
        Process::setEnv(self::ENV_RESTART);
        $this->time = is_numeric($start) ? round((microtime(true) - $start) * 1000) : 0;

        $this->envAllowXdebug = $envAllowXdebug;
        $this->debug = $debug && defined('STDERR');
        $this->modeOff = false;
    }

    /**
     * Activates status message output to a PSR3 logger
     *
     * @return void
     */
    public function setLogger(LoggerInterface $logger): void
    {
        $this->logger = $logger;
    }

    /**
     * Calls a handler method to report a message
     *
     * @throws \InvalidArgumentException If $op is not known
     */
    public function report(string $op, ?string $data): void
    {
        if ($this->logger !== null || $this->debug) {
            $param = (string) $data;

            switch($op) {
                case self::CHECK:
                    $this->reportCheck($param);
                    break;
                case self::ERROR:
                    $this->reportError($param);
                    break;
                case self::INFO:
                    $this->reportInfo($param);
                    break;
                case self::NORESTART:
                    $this->reportNoRestart();
                    break;
                case self::RESTART:
                    $this->reportRestart();
                    break;
                case self::RESTARTED:
                    $this->reportRestarted();
                    break;
                case self::RESTARTING:
                    $this->reportRestarting($param);
                    break;
                default:
                    throw new \InvalidArgumentException('Unknown op handler: '.$op);
            }
        }
    }

    /**
     * Outputs a status message
     */
    private function output(string $text, ?string $level = null): void
    {
        if ($this->logger !== null) {
            $this->logger->log($level !== null ? $level: LogLevel::DEBUG, $text);
        }

        if ($this->debug) {
            fwrite(STDERR, sprintf('xdebug-handler[%d] %s', getmypid(), $text.PHP_EOL));
        }
    }

    /**
     * Checking status message
     */
    private function reportCheck(string $loaded): void
    {
        list($version, $mode) = explode('|', $loaded);

        if ($version !== '') {
            $this->loaded = '('.$version.')'.($mode !== '' ? ' xdebug.mode='.$mode : '');
        }
        $this->modeOff = $mode === 'off';
        $this->output('Checking '.$this->envAllowXdebug);
    }

    /**
     * Error status message
     */
    private function reportError(string $error): void
    {
        $this->output(sprintf('No restart (%s)', $error), LogLevel::WARNING);
    }

    /**
     * Info status message
     */
    private function reportInfo(string $info): void
    {
        $this->output($info);
    }

    /**
     * No restart status message
     */
    private function reportNoRestart(): void
    {
        $this->output($this->getLoadedMessage());

        if ($this->loaded !== null) {
            $text = sprintf('No restart (%s)', $this->getEnvAllow());
            if (!((bool) getenv($this->envAllowXdebug))) {
                $text .= ' Allowed by '.($this->modeOff ? 'xdebug.mode' : 'application');
            }
            $this->output($text);
        }
    }

    /**
     * Restart status message
     */
    private function reportRestart(): void
    {
        $this->output($this->getLoadedMessage());
        Process::setEnv(self::ENV_RESTART, (string) microtime(true));
    }

    /**
     * Restarted status message
     */
    private function reportRestarted(): void
    {
        $loaded = $this->getLoadedMessage();
        $text = sprintf('Restarted (%d ms). %s', $this->time, $loaded);
        $level = $this->loaded !== null ? LogLevel::WARNING : null;
        $this->output($text, $level);
    }

    /**
     * Restarting status message
     */
    private function reportRestarting(string $command): void
    {
        $text = sprintf('Process restarting (%s)', $this->getEnvAllow());
        $this->output($text);
        $text = 'Running: '.$command;
        $this->output($text);
    }

    /**
     * Returns the _ALLOW_XDEBUG environment variable as name=value
     */
    private function getEnvAllow(): string
    {
        return $this->envAllowXdebug.'='.getenv($this->envAllowXdebug);
    }

    /**
     * Returns the Xdebug status and version
     */
    private function getLoadedMessage(): string
    {
        $loaded = $this->loaded !== null ? sprintf('loaded %s', $this->loaded) : 'not loaded';
        return 'The Xdebug extension is '.$loaded;
    }
}
<?php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer;

use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;

/**
 * This class is copied in every Composer installed project and available to all
 *
 * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
 *
 * To require its presence, you can require `composer-runtime-api ^2.0`
 *
 * @final
 */
class InstalledVersions
{
    /**
     * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
     * @internal
     */
    private static $selfDir = null;

    /**
     * @var mixed[]|null
     * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
     */
    private static $installed;

    /**
     * @var bool
     */
    private static $installedIsLocalDir;

    /**
     * @var bool|null
     */
    private static $canGetVendors;

    /**
     * @var array[]
     * @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
     */
    private static $installedByVendor = array();

    /**
     * Returns a list of all package names which are present, either by being installed, replaced or provided
     *
     * @return string[]
     * @psalm-return list<string>
     */
    public static function getInstalledPackages()
    {
        $packages = array();
        foreach (self::getInstalled() as $installed) {
            $packages[] = array_keys($installed['versions']);
        }

        if (1 === \count($packages)) {
            return $packages[0];
        }

        return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
    }

    /**
     * Returns a list of all package names with a specific type e.g. 'library'
     *
     * @param  string   $type
     * @return string[]
     * @psalm-return list<string>
     */
    public static function getInstalledPackagesByType($type)
    {
        $packagesByType = array();

        foreach (self::getInstalled() as $installed) {
            foreach ($installed['versions'] as $name => $package) {
                if (isset($package['type']) && $package['type'] === $type) {
                    $packagesByType[] = $name;
                }
            }
        }

        return $packagesByType;
    }

    /**
     * Checks whether the given package is installed
     *
     * This also returns true if the package name is provided or replaced by another package
     *
     * @param  string $packageName
     * @param  bool   $includeDevRequirements
     * @return bool
     */
    public static function isInstalled($packageName, $includeDevRequirements = true)
    {
        foreach (self::getInstalled() as $installed) {
            if (isset($installed['versions'][$packageName])) {
                return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
            }
        }

        return false;
    }

    /**
     * Checks whether the given package satisfies a version constraint
     *
     * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
     *
     *   Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
     *
     * @param  VersionParser $parser      Install composer/semver to have access to this class and functionality
     * @param  string        $packageName
     * @param  string|null   $constraint  A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
     * @return bool
     */
    public static function satisfies(VersionParser $parser, $packageName, $constraint)
    {
        $constraint = $parser->parseConstraints((string) $constraint);
        $provided = $parser->parseConstraints(self::getVersionRanges($packageName));

        return $provided->matches($constraint);
    }

    /**
     * Returns a version constraint representing all the range(s) which are installed for a given package
     *
     * It is easier to use this via isInstalled() with the $constraint argument if you need to check
     * whether a given version of a package is installed, and not just whether it exists
     *
     * @param  string $packageName
     * @return string Version constraint usable with composer/semver
     */
    public static function getVersionRanges($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            $ranges = array();
            if (isset($installed['versions'][$packageName]['pretty_version'])) {
                $ranges[] = $installed['versions'][$packageName]['pretty_version'];
            }
            if (array_key_exists('aliases', $installed['versions'][$packageName])) {
                $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
            }
            if (array_key_exists('replaced', $installed['versions'][$packageName])) {
                $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
            }
            if (array_key_exists('provided', $installed['versions'][$packageName])) {
                $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
            }

            return implode(' || ', $ranges);
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

    /**
     * @param  string      $packageName
     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
     */
    public static function getVersion($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            if (!isset($installed['versions'][$packageName]['version'])) {
                return null;
            }

            return $installed['versions'][$packageName]['version'];
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

    /**
     * @param  string      $packageName
     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
     */
    public static function getPrettyVersion($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            if (!isset($installed['versions'][$packageName]['pretty_version'])) {
                return null;
            }

            return $installed['versions'][$packageName]['pretty_version'];
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

    /**
     * @param  string      $packageName
     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
     */
    public static function getReference($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            if (!isset($installed['versions'][$packageName]['reference'])) {
                return null;
            }

            return $installed['versions'][$packageName]['reference'];
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

    /**
     * @param  string      $packageName
     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
     */
    public static function getInstallPath($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

    /**
     * @return array
     * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
     */
    public static function getRootPackage()
    {
        $installed = self::getInstalled();

        return $installed[0]['root'];
    }

    /**
     * Returns the raw installed.php data for custom implementations
     *
     * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
     * @return array[]
     * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
     */
    public static function getRawData()
    {
        @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);

        if (null === self::$installed) {
            // only require the installed.php file if this file is loaded from its dumped location,
            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
            if (substr(__DIR__, -8, 1) !== 'C') {
                self::$installed = include __DIR__ . '/installed.php';
            } else {
                self::$installed = array();
            }
        }

        return self::$installed;
    }

    /**
     * Returns the raw data of all installed.php which are currently loaded for custom implementations
     *
     * @return array[]
     * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
     */
    public static function getAllRawData()
    {
        return self::getInstalled();
    }

    /**
     * Lets you reload the static array from another file
     *
     * This is only useful for complex integrations in which a project needs to use
     * this class but then also needs to execute another project's autoloader in process,
     * and wants to ensure both projects have access to their version of installed.php.
     *
     * A typical case would be PHPUnit, where it would need to make sure it reads all
     * the data it needs from this class, then call reload() with
     * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
     * the project in which it runs can then also use this class safely, without
     * interference between PHPUnit's dependencies and the project's dependencies.
     *
     * @param  array[] $data A vendor/composer/installed.php data set
     * @return void
     *
     * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
     */
    public static function reload($data)
    {
        self::$installed = $data;
        self::$installedByVendor = array();

        // when using reload, we disable the duplicate protection to ensure that self::$installed data is
        // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
        // so we have to assume it does not, and that may result in duplicate data being returned when listing
        // all installed packages for example
        self::$installedIsLocalDir = false;
    }

    /**
     * @return string
     */
    private static function getSelfDir()
    {
        if (self::$selfDir === null) {
            self::$selfDir = strtr(__DIR__, '\\', '/');
        }

        return self::$selfDir;
    }

    /**
     * @return array[]
     * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
     */
    private static function getInstalled()
    {
        if (null === self::$canGetVendors) {
            self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
        }

        $installed = array();
        $copiedLocalDir = false;

        if (self::$canGetVendors) {
            $selfDir = self::getSelfDir();
            foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
                $vendorDir = strtr($vendorDir, '\\', '/');
                if (isset(self::$installedByVendor[$vendorDir])) {
                    $installed[] = self::$installedByVendor[$vendorDir];
                } elseif (is_file($vendorDir.'/composer/installed.php')) {
                    /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
                    $required = require $vendorDir.'/composer/installed.php';
                    self::$installedByVendor[$vendorDir] = $required;
                    $installed[] = $required;
                    if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
                        self::$installed = $required;
                        self::$installedIsLocalDir = true;
                    }
                }
                if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
                    $copiedLocalDir = true;
                }
            }
        }

        if (null === self::$installed) {
            // only require the installed.php file if this file is loaded from its dumped location,
            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
            if (substr(__DIR__, -8, 1) !== 'C') {
                /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
                $required = require __DIR__ . '/installed.php';
                self::$installed = $required;
            } else {
                self::$installed = array();
            }
        }

        if (self::$installed !== array() && !$copiedLocalDir) {
            $installed[] = self::$installed;
        }

        return $installed;
    }
}
Copyright (c) 2011 Jordi Boggiano

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.
<?php

/*
 * This file is part of the JSON Lint package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Seld\JsonLint;

/**
 * Lexer class
 *
 * Ported from https://github.com/zaach/jsonlint
 */
class Lexer
{
    /** @internal */
    const EOF = 1;
    /** @internal */
    const T_INVALID = -1;
    const T_SKIP_WHITESPACE = 0;
    const T_ERROR = 2;
    /** @internal */
    const T_BREAK_LINE = 3;
    /** @internal */
    const T_COMMENT = 30;
    /** @internal */
    const T_OPEN_COMMENT = 31;
    /** @internal */
    const T_CLOSE_COMMENT = 32;

    /**
     * @phpstan-var array<int<0,17>, string>
     * @const
     */
    private $rules = array(
        0 => '/\G\s*\n\r?/',
        1 => '/\G\s+/',
        2 => '/\G-?([0-9]|[1-9][0-9]+)(\.[0-9]+)?([eE][+-]?[0-9]+)?\b/',
        3 => '{\G"(?>\\\\["bfnrt/\\\\]|\\\\u[a-fA-F0-9]{4}|[^\0-\x1f\\\\"]++)*+"}',
        4 => '/\G\{/',
        5 => '/\G\}/',
        6 => '/\G\[/',
        7 => '/\G\]/',
        8 => '/\G,/',
        9 => '/\G:/',
        10 => '/\Gtrue\b/',
        11 => '/\Gfalse\b/',
        12 => '/\Gnull\b/',
        13 => '/\G$/',
        14 => '/\G\/\//',
        15 => '/\G\/\*/',
        16 => '/\G\*\//',
        17 => '/\G./',
    );

    /** @var string */
    private $input;
    /** @var bool */
    private $more;
    /** @var bool */
    private $done;
    /** @var 0|positive-int */
    private $offset;
    /** @var int */
    private $flags;

    /** @var string */
    public $match;
    /** @var 0|positive-int */
    public $yylineno;
    /** @var 0|positive-int */
    public $yyleng;
    /** @var string */
    public $yytext;
    /** @var array{first_line: 0|positive-int, first_column: 0|positive-int, last_line: 0|positive-int, last_column: 0|positive-int} */
    public $yylloc;

    /**
     * @param int $flags
     */
    public function __construct($flags = 0)
    {
        $this->flags = $flags;
    }

    /**
     * @return 0|1|4|6|8|10|11|14|17|18|21|22|23|24|30|-1
     */
    public function lex()
    {
        while (true) {
            $symbol = $this->next();
            switch ($symbol) {
                case self::T_SKIP_WHITESPACE:
                case self::T_BREAK_LINE:
                    break;
                case self::T_COMMENT:
                case self::T_OPEN_COMMENT:
                    if (!($this->flags & JsonParser::ALLOW_COMMENTS)) {
                        $this->parseError('Lexical error on line ' . ($this->yylineno+1) . ". Comments are not allowed.\n" . $this->showPosition());
                    }
                    $this->skipUntil($symbol === self::T_COMMENT ? self::T_BREAK_LINE : self::T_CLOSE_COMMENT);
                    if ($this->done) {
                        // last symbol '/\G$/' before EOF
                        return 14;
                    }
                    break;
                case self::T_CLOSE_COMMENT:
                    $this->parseError('Lexical error on line ' . ($this->yylineno+1) . ". Unexpected token.\n" . $this->showPosition());
                default:
                    return $symbol;
            }
        }
    }

    /**
     * @param string $input
     * @return $this
     */
    public function setInput($input)
    {
        $this->input = $input;
        $this->more = false;
        $this->done = false;
        $this->offset = 0;
        $this->yylineno = $this->yyleng = 0;
        $this->yytext = $this->match = '';
        $this->yylloc = array('first_line' => 1, 'first_column' => 0, 'last_line' => 1, 'last_column' => 0);

        return $this;
    }

    /**
     * @return string
     */
    public function showPosition()
    {
        if ($this->yylineno === 0 && $this->offset === 1 && $this->match !== '{') {
            return $this->match.'...' . "\n^";
        }

        $pre = str_replace("\n", '', $this->getPastInput());
        $c = str_repeat('-', max(0, \strlen($pre) - 1)); // new Array(pre.length + 1).join("-");

        return $pre . str_replace("\n", '', $this->getUpcomingInput()) . "\n" . $c . "^";
    }

    /**
     * @return string
     */
    public function getPastInput()
    {
        $pastLength = $this->offset - \strlen($this->match);

        return ($pastLength > 20 ? '...' : '') . substr($this->input, max(0, $pastLength - 20), min(20, $pastLength));
    }

    /**
     * @return string
     */
    public function getUpcomingInput()
    {
        $next = $this->match;
        if (\strlen($next) < 20) {
            $next .= substr($this->input, $this->offset, 20 - \strlen($next));
        }

        return substr($next, 0, 20) . (\strlen($next) > 20 ? '...' : '');
    }

    /**
     * @return string
     */
    public function getFullUpcomingInput()
    {
        $next = $this->match;
        if (substr($next, 0, 1) === '"' && substr_count($next, '"') === 1) {
            $len = \strlen($this->input);
            if ($len === $this->offset) {
                $strEnd = $len;
            } else {
                $strEnd = min(strpos($this->input, '"', $this->offset + 1) ?: $len, strpos($this->input, "\n", $this->offset + 1) ?: $len);
            }
            $next .= substr($this->input, $this->offset, $strEnd - $this->offset);
        } elseif (\strlen($next) < 20) {
            $next .= substr($this->input, $this->offset, 20 - \strlen($next));
        }

        return $next;
    }

    /**
     * @param string $str
     * @return never
     */
    protected function parseError($str)
    {
        throw new ParsingException($str);
    }

    /**
     * @param int $token
     * @return void
     */
    private function skipUntil($token)
    {
        $symbol = $this->next();
        while ($symbol !== $token && false === $this->done) {
            $symbol = $this->next();
        }
    }

    /**
     * @return 0|1|3|4|6|8|10|11|14|17|18|21|22|23|24|30|31|32|-1
     */
    private function next()
    {
        if ($this->done) {
            return self::EOF;
        }
        if ($this->offset === \strlen($this->input)) {
            $this->done = true;
        }

        $token = null;
        $match = null;
        $col = null;
        $lines = null;

        if (!$this->more) {
            $this->yytext = '';
            $this->match = '';
        }

        $rulesLen = count($this->rules);

        for ($i=0; $i < $rulesLen; $i++) {
            if (preg_match($this->rules[$i], $this->input, $match, 0, $this->offset)) {
                $lines = explode("\n", $match[0]);
                array_shift($lines);
                $lineCount = \count($lines);
                $this->yylineno += $lineCount;
                $this->yylloc = array(
                    'first_line' => $this->yylloc['last_line'],
                    'last_line' => $this->yylineno+1,
                    'first_column' => $this->yylloc['last_column'],
                    'last_column' => $lineCount > 0 ? \strlen($lines[$lineCount - 1]) : $this->yylloc['last_column'] + \strlen($match[0]),
                );
                $this->yytext .= $match[0];
                $this->match .= $match[0];
                $this->yyleng = \strlen($this->yytext);
                $this->more = false;
                $this->offset += \strlen($match[0]);
                return $this->performAction($i);
            }
        }

        if ($this->offset === \strlen($this->input)) {
            return self::EOF;
        }

        $this->parseError(
            'Lexical error on line ' . ($this->yylineno+1) . ". Unrecognized text.\n" . $this->showPosition()
        );
    }

    /**
     * @param  int $rule
     * @return 0|3|4|6|8|10|11|14|17|18|21|22|23|24|30|31|32|-1
     */
    private function performAction($rule)
    {
        switch ($rule) {
        case 0:/* skip break line */
            return self::T_BREAK_LINE;
        case 1:/* skip whitespace */
            return self::T_SKIP_WHITESPACE;
        case 2:
            return 6;
        case 3:
            $this->yytext = substr($this->yytext, 1, $this->yyleng-2);
            return 4;
        case 4:
            return 17;
        case 5:
            return 18;
        case 6:
            return 23;
        case 7:
            return 24;
        case 8:
            return 22;
        case 9:
            return 21;
        case 10:
            return 10;
        case 11:
            return 11;
        case 12:
            return 8;
        case 13:
            return 14;
        case 14:
            return self::T_COMMENT;
        case 15:
            return self::T_OPEN_COMMENT;
        case 16:
            return self::T_CLOSE_COMMENT;
        case 17:
            return self::T_INVALID;
        default:
            throw new \LogicException('Unsupported rule '.$rule);
        }
    }
}
<?php

/*
 * This file is part of the JSON Lint package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Seld\JsonLint;

class Undefined
{
}
<?php

/*
 * This file is part of the JSON Lint package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Seld\JsonLint;

class ParsingException extends \Exception
{
    /**
     * @var array{text?: string, token?: string|int, line?: int, loc?: array{first_line: int, first_column: int, last_line: int, last_column: int}, expected?: string[]}
     */
    protected $details;

    /**
     * @param string $message
     * @phpstan-param array{text?: string, token?: string|int, line?: int, loc?: array{first_line: int, first_column: int, last_line: int, last_column: int}, expected?: string[]} $details
     */
    public function __construct($message, $details = array())
    {
        $this->details = $details;
        parent::__construct($message);
    }

    /**
     * @phpstan-return array{text?: string, token?: string|int, line?: int, loc?: array{first_line: int, first_column: int, last_line: int, last_column: int}, expected?: string[]}
     */
    public function getDetails()
    {
        return $this->details;
    }
}
<?php

/*
 * This file is part of the JSON Lint package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Seld\JsonLint;
use stdClass;

/**
 * Parser class
 *
 * Example:
 *
 * $parser = new JsonParser();
 * // returns null if it's valid json, or an error object
 * $parser->lint($json);
 * // returns parsed json, like json_decode does, but slower, throws exceptions on failure.
 * $parser->parse($json);
 *
 * Ported from https://github.com/zaach/jsonlint
 */
class JsonParser
{
    const DETECT_KEY_CONFLICTS = 1;
    const ALLOW_DUPLICATE_KEYS = 2;
    const PARSE_TO_ASSOC = 4;
    const ALLOW_COMMENTS = 8;
    const ALLOW_DUPLICATE_KEYS_TO_ARRAY = 16;

    /** @var Lexer */
    private $lexer;

    /**
     * @var int
     * @phpstan-var int-mask-of<self::*>
     */
    private $flags;
    /** @var list<int> */
    private $stack;
    /** @var list<stdClass|array<mixed>|int|bool|float|string|null> */
    private $vstack; // semantic value stack
    /** @var list<array{first_line: int, first_column: int, last_line: int, last_column: int}> */
    private $lstack; // location stack

    /**
     * @phpstan-var array<string, int>
     */
    private $symbols = array(
        'error'                 => 2,
        'JSONString'            => 3,
        'STRING'                => 4,
        'JSONNumber'            => 5,
        'NUMBER'                => 6,
        'JSONNullLiteral'       => 7,
        'NULL'                  => 8,
        'JSONBooleanLiteral'    => 9,
        'TRUE'                  => 10,
        'FALSE'                 => 11,
        'JSONText'              => 12,
        'JSONValue'             => 13,
        'EOF'                   => 14,
        'JSONObject'            => 15,
        'JSONArray'             => 16,
        '{'                     => 17,
        '}'                     => 18,
        'JSONMemberList'        => 19,
        'JSONMember'            => 20,
        ':'                     => 21,
        ','                     => 22,
        '['                     => 23,
        ']'                     => 24,
        'JSONElementList'       => 25,
        '$accept'               => 0,
        '$end'                  => 1,
    );

    /**
     * @phpstan-var array<int, string>
     * @const
     */
    private $terminals_ = array(
        2   => "error",
        4   => "STRING",
        6   => "NUMBER",
        8   => "NULL",
        10  => "TRUE",
        11  => "FALSE",
        14  => "EOF",
        17  => "{",
        18  => "}",
        21  => ":",
        22  => ",",
        23  => "[",
        24  => "]",
    );

    /**
     * @phpstan-var array<int<1,21>, array{int, int}>
     * @const
     */
    private $productions_ = array(
        1 => array(3, 1),
        2 => array(5, 1),
        3 => array(7, 1),
        4 => array(9, 1),
        5 => array(9, 1),
        6 => array(12, 2),
        7 => array(13, 1),
        8 => array(13, 1),
        9 => array(13, 1),
        10 => array(13, 1),
        11 => array(13, 1),
        12 => array(13, 1),
        13 => array(15, 2),
        14 => array(15, 3),
        15 => array(20, 3),
        16 => array(19, 1),
        17 => array(19, 3),
        18 => array(16, 2),
        19 => array(16, 3),
        20 => array(25, 1),
        21 => array(25, 3)
    );

    /**
     * @var array<int<0, 31>, array<int, array<int>|int>> List of stateID=>symbolID=>actionIDs|actionID
     * @const
     */
    private $table = array(
        0 => array( 3 => 5, 4 => array(1,12), 5 => 6, 6 => array(1,13), 7 => 3, 8 => array(1,9), 9 => 4, 10 => array(1,10), 11 => array(1,11), 12 => 1, 13 => 2, 15 => 7, 16 => 8, 17 => array(1,14), 23 => array(1,15)),
        1 => array( 1 => array(3)),
        2 => array( 14 => array(1,16)),
        3 => array( 14 => array(2,7), 18 => array(2,7), 22 => array(2,7), 24 => array(2,7)),
        4 => array( 14 => array(2,8), 18 => array(2,8), 22 => array(2,8), 24 => array(2,8)),
        5 => array( 14 => array(2,9), 18 => array(2,9), 22 => array(2,9), 24 => array(2,9)),
        6 => array( 14 => array(2,10), 18 => array(2,10), 22 => array(2,10), 24 => array(2,10)),
        7 => array( 14 => array(2,11), 18 => array(2,11), 22 => array(2,11), 24 => array(2,11)),
        8 => array( 14 => array(2,12), 18 => array(2,12), 22 => array(2,12), 24 => array(2,12)),
        9 => array( 14 => array(2,3), 18 => array(2,3), 22 => array(2,3), 24 => array(2,3)),
        10 => array( 14 => array(2,4), 18 => array(2,4), 22 => array(2,4), 24 => array(2,4)),
        11 => array( 14 => array(2,5), 18 => array(2,5), 22 => array(2,5), 24 => array(2,5)),
        12 => array( 14 => array(2,1), 18 => array(2,1), 21 => array(2,1), 22 => array(2,1), 24 => array(2,1)),
        13 => array( 14 => array(2,2), 18 => array(2,2), 22 => array(2,2), 24 => array(2,2)),
        14 => array( 3 => 20, 4 => array(1,12), 18 => array(1,17), 19 => 18, 20 => 19 ),
        15 => array( 3 => 5, 4 => array(1,12), 5 => 6, 6 => array(1,13), 7 => 3, 8 => array(1,9), 9 => 4, 10 => array(1,10), 11 => array(1,11), 13 => 23, 15 => 7, 16 => 8, 17 => array(1,14), 23 => array(1,15), 24 => array(1,21), 25 => 22 ),
        16 => array( 1 => array(2,6)),
        17 => array( 14 => array(2,13), 18 => array(2,13), 22 => array(2,13), 24 => array(2,13)),
        18 => array( 18 => array(1,24), 22 => array(1,25)),
        19 => array( 18 => array(2,16), 22 => array(2,16)),
        20 => array( 21 => array(1,26)),
        21 => array( 14 => array(2,18), 18 => array(2,18), 22 => array(2,18), 24 => array(2,18)),
        22 => array( 22 => array(1,28), 24 => array(1,27)),
        23 => array( 22 => array(2,20), 24 => array(2,20)),
        24 => array( 14 => array(2,14), 18 => array(2,14), 22 => array(2,14), 24 => array(2,14)),
        25 => array( 3 => 20, 4 => array(1,12), 20 => 29 ),
        26 => array( 3 => 5, 4 => array(1,12), 5 => 6, 6 => array(1,13), 7 => 3, 8 => array(1,9), 9 => 4, 10 => array(1,10), 11 => array(1,11), 13 => 30, 15 => 7, 16 => 8, 17 => array(1,14), 23 => array(1,15)),
        27 => array( 14 => array(2,19), 18 => array(2,19), 22 => array(2,19), 24 => array(2,19)),
        28 => array( 3 => 5, 4 => array(1,12), 5 => 6, 6 => array(1,13), 7 => 3, 8 => array(1,9), 9 => 4, 10 => array(1,10), 11 => array(1,11), 13 => 31, 15 => 7, 16 => 8, 17 => array(1,14), 23 => array(1,15)),
        29 => array( 18 => array(2,17), 22 => array(2,17)),
        30 => array( 18 => array(2,15), 22 => array(2,15)),
        31 => array( 22 => array(2,21), 24 => array(2,21)),
    );

    /**
     * @var array{16: array{2, 6}}
     * @const
     */
    private $defaultActions = array(
        16 => array(2, 6)
    );

    /**
     * @param  string                $input JSON string
     * @param  int                   $flags Bitmask of parse/lint options (see constants of this class)
     * @return null|ParsingException null if no error is found, a ParsingException containing all details otherwise
     *
     * @phpstan-param int-mask-of<self::*> $flags
     */
    public function lint($input, $flags = 0)
    {
        try {
            $this->parse($input, $flags);
        } catch (ParsingException $e) {
            return $e;
        }
        return null;
    }

    /**
     * @param  string           $input JSON string
     * @param  int              $flags Bitmask of parse/lint options (see constants of this class)
     * @return mixed
     * @throws ParsingException
     *
     * @phpstan-param int-mask-of<self::*> $flags
     */
    public function parse($input, $flags = 0)
    {
        if (($flags & self::ALLOW_DUPLICATE_KEYS_TO_ARRAY) && ($flags & self::ALLOW_DUPLICATE_KEYS)) {
            throw new \InvalidArgumentException('Only one of ALLOW_DUPLICATE_KEYS and ALLOW_DUPLICATE_KEYS_TO_ARRAY can be used, you passed in both.');
        }

        $this->failOnBOM($input);

        $this->flags = $flags;

        $this->stack = array(0);
        $this->vstack = array(null);
        $this->lstack = array();

        $yytext = '';
        $yylineno = 0;
        $yyleng = 0;
        /** @var int<0,3> */
        $recovering = 0;

        $this->lexer = new Lexer($flags);
        $this->lexer->setInput($input);

        $yyloc = $this->lexer->yylloc;
        $this->lstack[] = $yyloc;

        $symbol = null;
        $preErrorSymbol = null;
        $action = null;
        $a = null;
        $r = null;
        $p = null;
        $len = null;
        $newState = null;
        $expected = null;
        /** @var string|null */
        $errStr = null;

        while (true) {
            // retrieve state number from top of stack
            $state = $this->stack[\count($this->stack)-1];

            // use default actions if available
            if (isset($this->defaultActions[$state])) {
                $action = $this->defaultActions[$state];
            } else {
                if ($symbol === null) {
                    $symbol = $this->lexer->lex();
                }
                // read action for current state and first input
                /** @var array<int, int>|false */
                $action = isset($this->table[$state][$symbol]) ? $this->table[$state][$symbol] : false;
            }

            // handle parse error
            if (!$action || !$action[0]) {
                assert(isset($symbol));
                if (!$recovering) {
                    // Report error
                    $expected = array();
                    foreach ($this->table[$state] as $p => $ignore) {
                        if (isset($this->terminals_[$p]) && $p > 2) {
                            $expected[] = "'" . $this->terminals_[$p] . "'";
                        }
                    }

                    $message = null;
                    if (\in_array("'STRING'", $expected) && \in_array(substr($this->lexer->match, 0, 1), array('"', "'"))) {
                        $message = "Invalid string";
                        if ("'" === substr($this->lexer->match, 0, 1)) {
                            $message .= ", it appears you used single quotes instead of double quotes";
                        } elseif (preg_match('{".+?(\\\\[^"bfnrt/\\\\u](...)?)}', $this->lexer->getFullUpcomingInput(), $match)) {
                            $message .= ", it appears you have an unescaped backslash at: ".$match[1];
                        } elseif (preg_match('{"(?:[^"]+|\\\\")*$}m', $this->lexer->getFullUpcomingInput())) {
                            $message .= ", it appears you forgot to terminate a string, or attempted to write a multiline string which is invalid";
                        }
                    }

                    $errStr = 'Parse error on line ' . ($yylineno+1) . ":\n";
                    $errStr .= $this->lexer->showPosition() . "\n";
                    if ($message) {
                        $errStr .= $message;
                    } else {
                        $errStr .= (\count($expected) > 1) ? "Expected one of: " : "Expected: ";
                        $errStr .= implode(', ', $expected);
                    }

                    if (',' === substr(trim($this->lexer->getPastInput()), -1)) {
                        $errStr .= " - It appears you have an extra trailing comma";
                    }

                    $this->parseError($errStr, array(
                        'text' => $this->lexer->match,
                        'token' => isset($this->terminals_[$symbol]) ? $this->terminals_[$symbol] : $symbol,
                        'line' => $this->lexer->yylineno,
                        'loc' => $yyloc,
                        'expected' => $expected,
                    ));
                }

                // just recovered from another error
                if ($recovering == 3) {
                    if ($symbol === Lexer::EOF) {
                        throw new ParsingException($errStr ?: 'Parsing halted.');
                    }

                    // discard current lookahead and grab another
                    $yyleng = $this->lexer->yyleng;
                    $yytext = $this->lexer->yytext;
                    $yylineno = $this->lexer->yylineno;
                    $yyloc = $this->lexer->yylloc;
                    $symbol = $this->lexer->lex();
                }

                // try to recover from error
                while (true) {
                    // check for error recovery rule in this state
                    if (\array_key_exists(Lexer::T_ERROR, $this->table[$state])) {
                        break;
                    }
                    if ($state == 0) {
                        throw new ParsingException($errStr ?: 'Parsing halted.');
                    }
                    $this->popStack(1);
                    $state = $this->stack[\count($this->stack)-1];
                }

                $preErrorSymbol = $symbol; // save the lookahead token
                $symbol = Lexer::T_ERROR;         // insert generic error symbol as new lookahead
                $state = $this->stack[\count($this->stack)-1];
                /** @var array<int, int>|false */
                $action = isset($this->table[$state][Lexer::T_ERROR]) ? $this->table[$state][Lexer::T_ERROR] : false;
                if ($action === false) {
                    throw new \LogicException('No table value found for '.$state.' => '.Lexer::T_ERROR);
                }
                $recovering = 3; // allow 3 real symbols to be shifted before reporting a new error
            }

            // this shouldn't happen, unless resolve defaults are off
            if (\is_array($action[0]) && \count($action) > 1) {
                throw new ParsingException('Parse Error: multiple actions possible at state: ' . $state . ', token: ' . $symbol);
            }

            switch ($action[0]) {
                case 1: // shift
                    assert(isset($symbol));
                    $this->stack[] = $symbol;
                    $this->vstack[] = $this->lexer->yytext;
                    $this->lstack[] = $this->lexer->yylloc;
                    $this->stack[] = $action[1]; // push state
                    $symbol = null;
                    if (!$preErrorSymbol) { // normal execution/no error
                        $yyleng = $this->lexer->yyleng;
                        $yytext = $this->lexer->yytext;
                        $yylineno = $this->lexer->yylineno;
                        $yyloc = $this->lexer->yylloc;
                        if ($recovering > 0) {
                            $recovering--;
                        }
                    } else { // error just occurred, resume old lookahead from before error
                        $symbol = $preErrorSymbol;
                        $preErrorSymbol = null;
                    }
                    break;

                case 2: // reduce
                    $len = $this->productions_[$action[1]][1];

                    // perform semantic action
                    $currentToken = $this->vstack[\count($this->vstack) - $len]; // default to $$ = $1
                    // default location, uses first token for firsts, last for lasts
                    $position = array( // _$ = store
                        'first_line' => $this->lstack[\count($this->lstack) - ($len ?: 1)]['first_line'],
                        'last_line' => $this->lstack[\count($this->lstack) - 1]['last_line'],
                        'first_column' => $this->lstack[\count($this->lstack) - ($len ?: 1)]['first_column'],
                        'last_column' => $this->lstack[\count($this->lstack) - 1]['last_column'],
                    );
                    list($newToken, $actionResult) = $this->performAction($currentToken, $yytext, $yyleng, $yylineno, $action[1]);

                    if (!$actionResult instanceof Undefined) {
                        return $actionResult;
                    }

                    if ($len) {
                        $this->popStack($len);
                    }

                    $this->stack[] = $this->productions_[$action[1]][0];    // push nonterminal (reduce)
                    $this->vstack[] = $newToken;
                    $this->lstack[] = $position;
                    /** @var int */
                    $newState = $this->table[$this->stack[\count($this->stack)-2]][$this->stack[\count($this->stack)-1]];
                    $this->stack[] = $newState;
                    break;

                case 3: // accept

                    return true;
            }
        }
    }

    /**
     * @param  string $str
     * @param  array{text: string, token: string|int, line: int, loc: array{first_line: int, first_column: int, last_line: int, last_column: int}, expected: string[]}|null $hash
     * @return never
     */
    protected function parseError($str, $hash = null)
    {
        throw new ParsingException($str, $hash ?: array());
    }

    /**
     * @param  stdClass|array<mixed>|int|bool|float|string|null $currentToken
     * @param  string   $yytext
     * @param  int      $yyleng
     * @param  int      $yylineno
     * @param  int      $yystate
     * @return array{stdClass|array<mixed>|int|bool|float|string|null, stdClass|array<mixed>|int|bool|float|string|null|Undefined}
     */
    private function performAction($currentToken, $yytext, $yyleng, $yylineno, $yystate)
    {
        $token = $currentToken;

        $len = \count($this->vstack) - 1;
        switch ($yystate) {
        case 1:
            $yytext = preg_replace_callback('{(?:\\\\["bfnrt/\\\\]|\\\\u[a-fA-F0-9]{4})}', array($this, 'stringInterpolation'), $yytext);
            $token = $yytext;
            break;
        case 2:
            if (strpos($yytext, 'e') !== false || strpos($yytext, 'E') !== false) {
                $token = \floatval($yytext);
            } else {
                $token = strpos($yytext, '.') === false ? \intval($yytext) : \floatval($yytext);
            }
            break;
        case 3:
            $token = null;
            break;
        case 4:
            $token = true;
            break;
        case 5:
            $token = false;
            break;
        case 6:
            $token = $this->vstack[$len-1];

            return array($token, $token);
        case 13:
            if ($this->flags & self::PARSE_TO_ASSOC) {
                $token = array();
            } else {
                $token = new stdClass;
            }
            break;
        case 14:
            $token = $this->vstack[$len-1];
            break;
        case 15:
            $token = array($this->vstack[$len-2], $this->vstack[$len]);
            break;
        case 16:
            assert(\is_array($this->vstack[$len]));
            if (PHP_VERSION_ID < 70100) {
                $property = $this->vstack[$len][0] === '' ? '_empty_' : $this->vstack[$len][0];
            } else {
                $property = $this->vstack[$len][0];
            }
            if ($this->flags & self::PARSE_TO_ASSOC) {
                $token = array();
                $token[$property] = $this->vstack[$len][1];
            } else {
                $token = new stdClass;
                $token->$property = $this->vstack[$len][1];
            }
            break;
        case 17:
            assert(\is_array($this->vstack[$len]));
            if ($this->flags & self::PARSE_TO_ASSOC) {
                assert(\is_array($this->vstack[$len-2]));
                $token =& $this->vstack[$len-2];
                $key = $this->vstack[$len][0];
                if (($this->flags & self::DETECT_KEY_CONFLICTS) && isset($this->vstack[$len-2][$key])) {
                    $errStr = 'Parse error on line ' . ($yylineno+1) . ":\n";
                    $errStr .= $this->lexer->showPosition() . "\n";
                    $errStr .= "Duplicate key: ".$this->vstack[$len][0];
                    throw new DuplicateKeyException($errStr, $this->vstack[$len][0], array('line' => $yylineno+1));
                }
                if (($this->flags & self::ALLOW_DUPLICATE_KEYS) && isset($this->vstack[$len-2][$key])) {
                    $duplicateCount = 1;
                    do {
                        $duplicateKey = $key . '.' . $duplicateCount++;
                    } while (isset($this->vstack[$len-2][$duplicateKey]));
                    $this->vstack[$len-2][$duplicateKey] = $this->vstack[$len][1];
                } elseif (($this->flags & self::ALLOW_DUPLICATE_KEYS_TO_ARRAY) && isset($this->vstack[$len-2][$key])) {
                    if (!isset($this->vstack[$len-2][$key]['__duplicates__']) || !is_array($this->vstack[$len-2][$key]['__duplicates__'])) {
                        $this->vstack[$len-2][$key] = array('__duplicates__' => array($this->vstack[$len-2][$key]));
                    }
                    $this->vstack[$len-2][$key]['__duplicates__'][] = $this->vstack[$len][1];
                } else {
                    $this->vstack[$len-2][$key] = $this->vstack[$len][1];
                }
            } else {
                assert($this->vstack[$len-2] instanceof stdClass);
                $token = $this->vstack[$len-2];
                if (PHP_VERSION_ID < 70100) {
                    $key = $this->vstack[$len][0] === '' ? '_empty_' : $this->vstack[$len][0];
                } else {
                    $key = $this->vstack[$len][0];
                }
                if (($this->flags & self::DETECT_KEY_CONFLICTS) && isset($this->vstack[$len-2]->$key)) {
                    $errStr = 'Parse error on line ' . ($yylineno+1) . ":\n";
                    $errStr .= $this->lexer->showPosition() . "\n";
                    $errStr .= "Duplicate key: ".$this->vstack[$len][0];
                    throw new DuplicateKeyException($errStr, $this->vstack[$len][0], array('line' => $yylineno+1));
                }
                if (($this->flags & self::ALLOW_DUPLICATE_KEYS) && isset($this->vstack[$len-2]->$key)) {
                    $duplicateCount = 1;
                    do {
                        $duplicateKey = $key . '.' . $duplicateCount++;
                    } while (isset($this->vstack[$len-2]->$duplicateKey));
                    $this->vstack[$len-2]->$duplicateKey = $this->vstack[$len][1];
                } elseif (($this->flags & self::ALLOW_DUPLICATE_KEYS_TO_ARRAY) && isset($this->vstack[$len-2]->$key)) {
                    if (!isset($this->vstack[$len-2]->$key->__duplicates__)) {
                        $this->vstack[$len-2]->$key = (object) array('__duplicates__' => array($this->vstack[$len-2]->$key));
                    }
                    $this->vstack[$len-2]->$key->__duplicates__[] = $this->vstack[$len][1];
                } else {
                    $this->vstack[$len-2]->$key = $this->vstack[$len][1];
                }
            }
            break;
        case 18:
            $token = array();
            break;
        case 19:
            $token = $this->vstack[$len-1];
            break;
        case 20:
            $token = array($this->vstack[$len]);
            break;
        case 21:
            assert(\is_array($this->vstack[$len-2]));
            $this->vstack[$len-2][] = $this->vstack[$len];
            $token = $this->vstack[$len-2];
            break;
        }

        return array($token, new Undefined());
    }

    /**
     * @param  string $match
     * @return string
     */
    private function stringInterpolation($match)
    {
        switch ($match[0]) {
        case '\\\\':
            return '\\';
        case '\"':
            return '"';
        case '\b':
            return \chr(8);
        case '\f':
            return \chr(12);
        case '\n':
            return "\n";
        case '\r':
            return "\r";
        case '\t':
            return "\t";
        case '\/':
            return "/";
        default:
            return html_entity_decode('&#x'.ltrim(substr($match[0], 2), '0').';', ENT_QUOTES, 'UTF-8');
        }
    }

    /**
     * @param  int $n
     * @return void
     */
    private function popStack($n)
    {
        $this->stack = \array_slice($this->stack, 0, - (2 * $n));
        $this->vstack = \array_slice($this->vstack, 0, - $n);
        $this->lstack = \array_slice($this->lstack, 0, - $n);
    }

    /**
     * @param  string $input
     * @return void
     */
    private function failOnBOM($input)
    {
        // UTF-8 ByteOrderMark sequence
        $bom = "\xEF\xBB\xBF";

        if (substr($input, 0, 3) === $bom) {
            $this->parseError("BOM detected, make sure your input does not include a Unicode Byte-Order-Mark");
        }
    }
}
<?php

/*
 * This file is part of the JSON Lint package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Seld\JsonLint;

class DuplicateKeyException extends ParsingException
{
    /**
     * @var array{key: string, line: int}
     */
    protected $details;

    /**
     * @param string $message
     * @param string $key
     * @phpstan-param array{line: int} $details
     */
    public function __construct($message, $key, array $details)
    {
        $details['key'] = $key;
        parent::__construct($message, $details);
    }

    /**
     * @return string
     */
    public function getKey()
    {
        return $this->details['key'];
    }

    /**
     * @phpstan-return array{key: string, line: int}
     */
    public function getDetails()
    {
        return $this->details;
    }
}
#!/usr/bin/env php
<?php

/*
 * This file is part of the JSON Lint package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

function includeIfExists($file)
{
    if (file_exists($file)) {
        return include $file;
    }
}

if (!includeIfExists(__DIR__.'/../vendor/autoload.php') && !includeIfExists(__DIR__.'/../../../autoload.php')) {
    $msg = 'You must set up the project dependencies, run the following commands:'.PHP_EOL.
           'curl -sS https://getcomposer.org/installer | php'.PHP_EOL.
           'php composer.phar install'.PHP_EOL;
    fwrite(STDERR, $msg);
    exit(1);
}

use Seld\JsonLint\JsonParser;

$files = array();
$quiet = false;

if (isset($_SERVER['argc']) && $_SERVER['argc'] > 1) {
    for ($i = 1; $i < $_SERVER['argc']; $i++) {
        $arg = $_SERVER['argv'][$i];
        if ($arg == '-q' || $arg == '--quiet') {
            $quiet = true;
        } else {
            if ($arg == '-h' || $arg == '--help') {
                showUsage($_SERVER['argv'][0]);
            } else {
                $files[] = $arg;
            }
        }
    }
}

if (!empty($files)) {
    // file linting
    $exitCode = 0;
    foreach ($files as $file) {
        $result = lintFile($file, $quiet);
        if ($result === false) {
            $exitCode = 1;
        }
    }
    exit($exitCode);
} else {
    //stdin linting
    if ($contents = file_get_contents('php://stdin')) {
        lint($contents, $quiet);
    } else {
        fwrite(STDERR, 'No file name or json input given' . PHP_EOL);
        exit(1);
    }
}

// stdin lint function
function lint($content, $quiet = false)
{
    $parser = new JsonParser();
    if ($err = $parser->lint($content)) {
        fwrite(STDERR, $err->getMessage() . ' (stdin)' . PHP_EOL);
        exit(1);
    }
    if (!$quiet) {
        echo 'Valid JSON (stdin)' . PHP_EOL;
        exit(0);
    }
}

// file lint function
function lintFile($file, $quiet = false)
{
    if (!preg_match('{^https?://}i', $file)) {
        if (!file_exists($file)) {
            fwrite(STDERR, 'File not found: ' . $file . PHP_EOL);
            return false;
        }
        if (!is_readable($file)) {
            fwrite(STDERR, 'File not readable: ' . $file . PHP_EOL);
            return false;
        }
    }

    $content = file_get_contents($file);
    $parser = new JsonParser();
    if ($err = $parser->lint($content)) {
        fwrite(STDERR, $file . ': ' . $err->getMessage() . PHP_EOL);
        return false;
    }
    if (!$quiet) {
        echo 'Valid JSON (' . $file . ')' . PHP_EOL;
    }
    return true;
}

// usage text function
function showUsage($programPath)
{
    echo 'Usage: '.$programPath.' file [options]'.PHP_EOL;
    echo PHP_EOL;
    echo 'Options:'.PHP_EOL;
    echo '  -q, --quiet     Cause jsonlint to be quiet when no errors are found'.PHP_EOL;
    echo '  -h, --help      Show this message'.PHP_EOL;
    exit(0);
}
Copyright (c) 2015 Jordi Boggiano

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.
<?php

/*
 * This file is part of PHAR Utils.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Seld\PharUtils;

class Linter
{
    /**
     * Lints all php files inside a given phar with the current PHP version
     *
     * @param string $path Phar file path
     * @param list<string> $excludedPaths Paths which should be skipped by the linter
     */
    public static function lint($path, array $excludedPaths = array())
    {
        $php = defined('PHP_BINARY') ? PHP_BINARY : 'php';

        if ($isWindows = defined('PHP_WINDOWS_VERSION_BUILD')) {
            $tmpFile = @tempnam(sys_get_temp_dir(), '');

            if (!$tmpFile || !is_writable($tmpFile)) {
                throw new \RuntimeException('Unable to create temp file');
            }

            $php = self::escapeWindowsPath($php);
            $tmpFile = self::escapeWindowsPath($tmpFile);

            // PHP 8 encloses the command in double-quotes
            if (PHP_VERSION_ID >= 80000) {
                $format = '%s -l %s';
            } else {
                $format = '"%s -l %s"';
            }

            $command = sprintf($format, $php, $tmpFile);
        } else {
            $command = "'".$php."' -l";
        }

        $descriptorspec = array(
            0 => array('pipe', 'r'),
            1 => array('pipe', 'w'),
            2 => array('pipe', 'w')
        );

        // path to phar + phar:// + trailing slash
        $baseLen = strlen(realpath($path)) + 7 + 1;
        foreach (new \RecursiveIteratorIterator(new \Phar($path)) as $file) {
            if ($file->isDir()) {
                continue;
            }
            if (substr($file, -4) === '.php') {
                $filename = (string) $file;
                if (in_array(substr($filename, $baseLen), $excludedPaths, true)) {
                    continue;
                }
                if ($isWindows) {
                    file_put_contents($tmpFile, file_get_contents($filename));
                }

                $process = proc_open($command, $descriptorspec, $pipes);
                if (is_resource($process)) {
                    if (!$isWindows) {
                        fwrite($pipes[0], file_get_contents($filename));
                    }
                    fclose($pipes[0]);

                    $stdout = stream_get_contents($pipes[1]);
                    fclose($pipes[1]);
                    $stderr = stream_get_contents($pipes[2]);
                    fclose($pipes[2]);

                    $exitCode = proc_close($process);

                    if ($exitCode !== 0) {
                        if ($isWindows) {
                            $stderr = str_replace($tmpFile, $filename, $stderr);
                        }
                        throw new \UnexpectedValueException('Failed linting '.$file.': '.$stderr);
                    }
                } else {
                    throw new \RuntimeException('Could not start linter process');
                }
            }
        }

        if ($isWindows) {
            @unlink($tmpFile);
        }
    }

    /**
     * Escapes a Windows file path
     *
     * @param string $path
     * @return string The escaped path
     */
    private static function escapeWindowsPath($path)
    {
        // Quote if path contains spaces or brackets
        if (strpbrk($path, " ()") !== false) {
            $path = '"'.$path.'"';
        }

        return $path;
    }
}
<?php

/*
 * This file is part of PHAR Utils.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Seld\PharUtils;

class Timestamps
{
    private $contents;

    /**
     * @param string $file path to the phar file to use
     */
    public function __construct($file)
    {
        $this->contents = file_get_contents($file);
    }

    /**
     * Updates each file's unix timestamps in the PHAR
     *
     * The PHAR signature can then be produced in a reproducible manner.
     *
     * @param int|\DateTimeInterface|string $timestamp Date string or DateTime or unix timestamp to use
     */
    public function updateTimestamps($timestamp = null)
    {
        if ($timestamp instanceof \DateTime || $timestamp instanceof \DateTimeInterface) {
            $timestamp = $timestamp->getTimestamp();
        } elseif (is_string($timestamp)) {
            $timestamp = strtotime($timestamp);
        } elseif (!is_int($timestamp)) {
            $timestamp = strtotime('1984-12-24T00:00:00Z');
        }

        // detect manifest offset / end of stub
        if (!preg_match('{__HALT_COMPILER\(\);(?: +\?>)?\r?\n}', $this->contents, $match, PREG_OFFSET_CAPTURE)) {
            throw new \RuntimeException('Could not detect the stub\'s end in the phar');
        }

        // set starting position and skip past manifest length
        $pos = $match[0][1] + strlen($match[0][0]);
        $stubEnd = $pos + $this->readUint($pos, 4);
        $pos += 4;

        $numFiles = $this->readUint($pos, 4);
        $pos += 4;

        // skip API version (YOLO)
        $pos += 2;

        // skip PHAR flags
        $pos += 4;

        $aliasLength = $this->readUint($pos, 4);
        $pos += 4 + $aliasLength;

        $metadataLength = $this->readUint($pos, 4);
        $pos += 4 + $metadataLength;

        while ($pos < $stubEnd) {
            $filenameLength = $this->readUint($pos, 4);
            $pos += 4 + $filenameLength;

            // skip filesize
            $pos += 4;

            // update timestamp to a fixed value
            $timeStampBytes = pack('L', $timestamp);
            $this->contents[$pos + 0] = $timeStampBytes[0];
            $this->contents[$pos + 1] = $timeStampBytes[1];
            $this->contents[$pos + 2] = $timeStampBytes[2];
            $this->contents[$pos + 3] = $timeStampBytes[3];

            // skip timestamp, compressed file size, crc32 checksum and file flags
            $pos += 4*4;

            $metadataLength = $this->readUint($pos, 4);
            $pos += 4 + $metadataLength;

            $numFiles--;
        }

        if ($numFiles !== 0) {
            throw new \LogicException('All files were not processed, something must have gone wrong');
        }
    }

    /**
     * Saves the updated phar file, optionally with an updated signature.
     *
     * @param  string $path
     * @param  int $signatureAlgo One of Phar::MD5, Phar::SHA1, Phar::SHA256 or Phar::SHA512
     * @return bool
     */
    public function save($path, $signatureAlgo)
    {
        $pos = $this->determineSignatureBegin();

        $algos = array(
            \Phar::MD5 => 'md5',
            \Phar::SHA1 => 'sha1',
            \Phar::SHA256 => 'sha256',
            \Phar::SHA512 => 'sha512',
        );

        if (!isset($algos[$signatureAlgo])) {
            throw new \UnexpectedValueException('Invalid hash algorithm given: '.$signatureAlgo.' expected one of Phar::MD5, Phar::SHA1, Phar::SHA256 or Phar::SHA512');
        }
        $algo = $algos[$signatureAlgo];

        // re-sign phar
        //           signature
        $signature = hash($algo, substr($this->contents, 0, $pos), true)
            // sig type
            . pack('L', $signatureAlgo)
            // ohai Greg & Marcus
            . 'GBMB';

        $this->contents = substr($this->contents, 0, $pos) . $signature;

        return file_put_contents($path, $this->contents);
    }

    private function readUint($pos, $bytes)
    {
        $res = unpack('V', substr($this->contents, $pos, $bytes));

        return $res[1];
    }

    /**
     * Determine the beginning of the signature.
     *
     * @return int
     */
    private function determineSignatureBegin()
    {
        // detect signature position
        if (!preg_match('{__HALT_COMPILER\(\);(?: +\?>)?\r?\n}', $this->contents, $match, PREG_OFFSET_CAPTURE)) {
            throw new \RuntimeException('Could not detect the stub\'s end in the phar');
        }

        // set starting position and skip past manifest length
        $pos = $match[0][1] + strlen($match[0][0]);
        $manifestEnd = $pos + 4 + $this->readUint($pos, 4);

        $pos += 4;
        $numFiles = $this->readUint($pos, 4);

        $pos += 4;

        // skip API version (YOLO)
        $pos += 2;

        // skip PHAR flags
        $pos += 4;

        $aliasLength = $this->readUint($pos, 4);
        $pos += 4 + $aliasLength;

        $metadataLength = $this->readUint($pos, 4);
        $pos += 4 + $metadataLength;

        $compressedSizes = 0;
        while (($numFiles > 0) && ($pos < $manifestEnd - 24)) {
            $filenameLength = $this->readUint($pos, 4);
            $pos += 4 + $filenameLength;

            // skip filesize and timestamp
            $pos += 2*4;

            $compressedSizes += $this->readUint($pos, 4);
            // skip compressed file size, crc32 checksum and file flags
            $pos += 3*4;

            $metadataLength = $this->readUint($pos, 4);
            $pos += 4 + $metadataLength;

            $numFiles--;
        }

        if ($numFiles !== 0) {
            throw new \LogicException('All files were not processed, something must have gone wrong');
        }

        return $manifestEnd + $compressedSizes;
    }
}
Copyright (c) 2015 Jordi Boggiano

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.
<?php

/*
 * This file is part of signal-handler.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Seld\Signal;

use Psr\Log\LoggerInterface;
use Closure;
use WeakReference;

/**
 * SignalHandler and factory
 */
final class SignalHandler
{
    /**
     * The SIGHUP signal is sent to a process when its controlling terminal is closed. It was originally designed to
     * notify the process of a serial line drop (a hangup). In modern systems, this signal usually means that the
     * controlling pseudo or virtual terminal has been closed. Many daemons will reload their configuration files and
     * reopen their logfiles instead of exiting when receiving this signal. nohup is a command to make a command ignore
     * the signal.
     */
    public const SIGHUP = 'SIGHUP';

    /**
     * The SIGINT signal is sent to a process by its controlling terminal when a user wishes to interrupt the process.
     * This is typically initiated by pressing Ctrl-C, but on some systems, the "delete" character or "break" key can be
     * used.
     *
     * On Windows this is used to denote a PHP_WINDOWS_EVENT_CTRL_C
     */
    public const SIGINT = 'SIGINT';

    /**
     * The SIGQUIT signal is sent to a process by its controlling terminal when the user requests that the process quit
     * and perform a core dump.
     */
    public const SIGQUIT = 'SIGQUIT';

    /**
     * The SIGILL signal is sent to a process when it attempts to execute an illegal, malformed, unknown, or privileged
     * instruction.
     */
    public const SIGILL = 'SIGILL';

    /**
     * The SIGTRAP signal is sent to a process when an exception (or trap) occurs: a condition that a debugger has
     * requested to be informed of — for example, when a particular function is executed, or when a particular variable
     * changes value.
     */
    public const SIGTRAP = 'SIGTRAP';

    /**
     * The SIGABRT signal is sent to a process to tell it to abort, i.e. to terminate. The signal is usually initiated
     * by the process itself when it calls abort function of the C Standard Library, but it can be sent to the process
     * from outside like any other signal.
     */
    public const SIGABRT = 'SIGABRT';

    public const SIGIOT = 'SIGIOT';

    /**
     * The SIGBUS signal is sent to a process when it causes a bus error. The conditions that lead to the signal being
     * sent are, for example, incorrect memory access alignment or non-existent physical address.
     */
    public const SIGBUS = 'SIGBUS';

    public const SIGFPE = 'SIGFPE';

    /**
     * The SIGKILL signal is sent to a process to cause it to terminate immediately (kill). In contrast to SIGTERM and
     * SIGINT, this signal cannot be caught or ignored, and the receiving process cannot perform any clean-up upon
     * receiving this signal.
     */
    public const SIGKILL = 'SIGKILL';

    /**
     * The SIGUSR1 signal is sent to a process to indicate user-defined conditions.
     */
    public const SIGUSR1 = 'SIGUSR1';

    /**
     * The SIGUSR1 signa2 is sent to a process to indicate user-defined conditions.
     */
    public const SIGUSR2 = 'SIGUSR2';

    /**
     * The SIGSEGV signal is sent to a process when it makes an invalid virtual memory reference, or segmentation fault,
     * i.e. when it performs a segmentation violation.
     */
    public const SIGSEGV = 'SIGSEGV';

    /**
     * The SIGPIPE signal is sent to a process when it attempts to write to a pipe without a process connected to the
     * other end.
     */
    public const SIGPIPE = 'SIGPIPE';

    /**
     * The SIGALRM, SIGVTALRM and SIGPROF signal is sent to a process when the time limit specified in a call to a
     * preceding alarm setting function (such as setitimer) elapses. SIGALRM is sent when real or clock time elapses.
     * SIGVTALRM is sent when CPU time used by the process elapses. SIGPROF is sent when CPU time used by the process
     * and by the system on behalf of the process elapses.
     */
    public const SIGALRM = 'SIGALRM';

    /**
     * The SIGTERM signal is sent to a process to request its termination. Unlike the SIGKILL signal, it can be caught
     * and interpreted or ignored by the process. This allows the process to perform nice termination releasing
     * resources and saving state if appropriate. SIGINT is nearly identical to SIGTERM.
     */
    public const SIGTERM = 'SIGTERM';

    public const SIGSTKFLT = 'SIGSTKFLT';
    public const SIGCLD = 'SIGCLD';

    /**
     * The SIGCHLD signal is sent to a process when a child process terminates, is interrupted, or resumes after being
     * interrupted. One common usage of the signal is to instruct the operating system to clean up the resources used by
     * a child process after its termination without an explicit call to the wait system call.
     */
    public const SIGCHLD = 'SIGCHLD';

    /**
     * The SIGCONT signal instructs the operating system to continue (restart) a process previously paused by the
     * SIGSTOP or SIGTSTP signal. One important use of this signal is in job control in the Unix shell.
     */
    public const SIGCONT = 'SIGCONT';

    /**
     * The SIGSTOP signal instructs the operating system to stop a process for later resumption.
     */
    public const SIGSTOP = 'SIGSTOP';

    /**
     * The SIGTSTP signal is sent to a process by its controlling terminal to request it to stop (terminal stop). It is
     * commonly initiated by the user pressing Ctrl+Z. Unlike SIGSTOP, the process can register a signal handler for or
     * ignore the signal.
     */
    public const SIGTSTP = 'SIGTSTP';

    /**
     * The SIGTTIN signal is sent to a process when it attempts to read in from the tty while in the background.
     * Typically, this signal is received only by processes under job control; daemons do not have controlling
     */
    public const SIGTTIN = 'SIGTTIN';

    /**
     * The SIGTTOU signal is sent to a process when it attempts to write out from the tty while in the background.
     * Typically, this signal is received only by processes under job control; daemons do not have controlling
     */
    public const SIGTTOU = 'SIGTTOU';

    /**
     * The SIGURG signal is sent to a process when a socket has urgent or out-of-band data available to read.
     */
    public const SIGURG = 'SIGURG';

    /**
     * The SIGXCPU signal is sent to a process when it has used up the CPU for a duration that exceeds a certain
     * predetermined user-settable value. The arrival of a SIGXCPU signal provides the receiving process a chance to
     * quickly save any intermediate results and to exit gracefully, before it is terminated by the operating system
     * using the SIGKILL signal.
     */
    public const SIGXCPU = 'SIGXCPU';

    /**
     * The SIGXFSZ signal is sent to a process when it grows a file larger than the maximum allowed size
     */
    public const SIGXFSZ = 'SIGXFSZ';

    /**
     * The SIGVTALRM signal is sent to a process when the time limit specified in a call to a preceding alarm setting
     * function (such as setitimer) elapses. SIGVTALRM is sent when CPU time used by the process elapses.
     */
    public const SIGVTALRM = 'SIGVTALRM';

    /**
     * The SIGPROF signal is sent to a process when the time limit specified in a call to a preceding alarm setting
     * function (such as setitimer) elapses. SIGPROF is sent when CPU time used by the process and by the system on
     * behalf of the process elapses.
     */
    public const SIGPROF = 'SIGPROF';

    /**
     * The SIGWINCH signal is sent to a process when its controlling terminal changes its size (a window change).
     */
    public const SIGWINCH = 'SIGWINCH';

    /**
     * The SIGPOLL signal is sent when an event occurred on an explicitly watched file descriptor.Using it effectively
     * leads to making asynchronous I/O requests since the kernel will poll the descriptor in place of the caller. It
     * provides an alternative to active polling.
     */
    public const SIGPOLL = 'SIGPOLL';

    public const SIGIO = 'SIGIO';

    /**
     * The SIGPWR signal is sent to a process when the system experiences a power failure.
     */
    public const SIGPWR = 'SIGPWR';

    /**
     * The SIGSYS signal is sent to a process when it passes a bad argument to a system call. In practice, this kind of
     * signal is rarely encountered since applications rely on libraries (e.g. libc) to make the call for them.
     */
    public const SIGSYS = 'SIGSYS';

    public const SIGBABY = 'SIGBABY';

    /**
     * CTRL+Break support, available on Windows only for PHP_WINDOWS_EVENT_CTRL_BREAK
     */
    public const SIGBREAK = 'SIGBREAK';

    private const ALL_SIGNALS = [
        self::SIGHUP, self::SIGINT, self::SIGQUIT, self::SIGILL, self::SIGTRAP, self::SIGABRT, self::SIGIOT, self::SIGBUS,
        self::SIGFPE, self::SIGKILL, self::SIGUSR1, self::SIGUSR2, self::SIGSEGV, self::SIGPIPE, self::SIGALRM, self::SIGTERM,
        self::SIGSTKFLT, self::SIGCLD, self::SIGCHLD, self::SIGCONT, self::SIGSTOP, self::SIGTSTP, self::SIGTTIN, self::SIGTTOU,
        self::SIGURG, self::SIGXCPU, self::SIGXFSZ, self::SIGVTALRM, self::SIGPROF, self::SIGWINCH, self::SIGPOLL, self::SIGIO,
        self::SIGPWR, self::SIGSYS, self::SIGBABY, self::SIGBREAK
    ];

    /**
     * @var self::SIG*|null
     */
    private $triggered = null;

    /**
     * @var list<self::SIG*>
     * @readonly
     */
    private $signals;

    /**
     * @var LoggerInterface|(callable(self::SIG* $name, SignalHandler $self): void)|null
     * @readonly
     */
    private $loggerOrCallback;

    /**
     * @var array<int, self|WeakReference<self>>
     */
    private static $handlers = [];

    /** @var Closure|null */
    private static $windowsHandler = null;

    /**
     * @param array<self::SIG*> $signals
     * @param LoggerInterface|(callable(self::SIG* $name, SignalHandler $self): void)|null $loggerOrCallback
     */
    private function __construct(array $signals, $loggerOrCallback)
    {
        if (!is_callable($loggerOrCallback) && !$loggerOrCallback instanceof LoggerInterface && $loggerOrCallback !== null) {
            throw new \InvalidArgumentException('$loggerOrCallback must be a '.LoggerInterface::class.' instance, a callable, or null, '.(is_object($loggerOrCallback) ? get_class($loggerOrCallback) : gettype($loggerOrCallback)).' received.');
        }

        $this->signals = $signals;
        $this->loggerOrCallback = $loggerOrCallback;
    }

    /**
     * @param self::SIG* $signalName
     */
    private function trigger(string $signalName): void
    {
        $this->triggered = $signalName;

        if ($this->loggerOrCallback instanceof LoggerInterface) {
            $this->loggerOrCallback->info('Received '.$signalName);
        } elseif ($this->loggerOrCallback !== null) {
            ($this->loggerOrCallback)($signalName, $this);
        }
    }

    /**
     * Fetches the triggered state of the handler
     *
     * @phpstan-impure
     */
    public function isTriggered(): bool
    {
        return $this->triggered !== null;
    }

    /**
     * Exits the process while communicating that the handled signal was what killed the process
     *
     * This is different from doing exit(SIGINT), and is also different to a successful exit(0).
     *
     * This should only be used when you received a signal and then handled it to gracefully shutdown and are now ready to shutdown.
     *
     * ```
     * $signal = SignalHandler::create([SignalHandler::SIGINT], function (string $signal, SignalHandler $handler) {
     *     // do cleanup here..
     *
     *     $handler->exitWithLastSignal();
     * });
     *
     * // or...
     *
     * $signal = SignalHandler::create([SignalHandler::SIGINT]);
     *
     * while ($doingThings) {
     *     if ($signal->isTriggered()) {
     *         $signal->exitWithLastSignal();
     *     }
     *
     *     // do more things
     * }
     * ```
     *
     * @see https://www.cons.org/cracauer/sigint.html
     * @return never
     */
    public function exitWithLastSignal(): void
    {
        $signal = $this->triggered ?? 'SIGINT';
        $signal = defined($signal) ? constant($signal) : 2;

        if (function_exists('posix_kill') && function_exists('posix_getpid')) {
            pcntl_signal($signal, SIG_DFL);
            posix_kill(posix_getpid(), $signal);
        }

        // just in case posix_kill above could not run
        // not strictly correct but it's the best we can do here
        exit(128 + $signal);
    }

    /**
     * Resets the state to let a handler accept a signal again
     */
    public function reset(): void
    {
        $this->triggered = null;
    }

    public function __destruct()
    {
        $this->unregister();
    }

    /**
     * @param (string|int)[] $signals array of signal names (more portable, see SignalHandler::SIG*) or constants - defaults to [SIGINT, SIGTERM]
     * @param LoggerInterface|callable $loggerOrCallback A PSR-3 Logger or a callback($signal, $signalName)
     * @return self A handler on which you can call isTriggered to know if the signal was received, and reset() to forget
     *
     * @phpstan-param list<self::SIG*|int> $signals
     * @phpstan-param LoggerInterface|(callable(self::SIG* $name, SignalHandler $self): void) $loggerOrCallback
     */
    public static function create(?array $signals = null, $loggerOrCallback = null): self
    {
        if ($signals === null) {
            $signals = [self::SIGINT, self::SIGTERM];
        }
        $signals = array_map(function ($signal) {
            if (is_int($signal)) {
                return self::getSignalName($signal);
            } elseif (!in_array($signal, self::ALL_SIGNALS, true)) {
                throw new \InvalidArgumentException('$signals must be an array of SIG* constants or self::SIG* constants, got '.var_export($signal, true));
            }
            return $signal;
        }, (array) $signals);

        $handler = new self($signals, $loggerOrCallback);

        if (PHP_VERSION_ID >= 80000) {
            array_unshift(self::$handlers, WeakReference::create($handler));
        } else {
            array_unshift(self::$handlers, $handler);
        }

        if (function_exists('sapi_windows_set_ctrl_handler') && PHP_SAPI === 'cli' && (in_array(self::SIGINT, $signals, true) || in_array(self::SIGBREAK, $signals, true))) {
            if (null === self::$windowsHandler) {
                self::$windowsHandler = Closure::fromCallable([self::class, 'handleWindowsSignal']);
                sapi_windows_set_ctrl_handler(self::$windowsHandler);
            }
        }

        if (function_exists('pcntl_signal') && function_exists('pcntl_async_signals')) {
            pcntl_async_signals(true);

            self::registerPcntlHandler($signals);
        }

        return $handler;
    }

    /**
     * Clears the signal handler
     *
     * On PHP 8+ this is not necessary and it will happen automatically on __destruct, but PHP 7 does not
     * support weak references and thus there you need to manually do this.
     *
     * If another handler was registered previously to this one, it becomes active again
     */
    public function unregister(): void
    {
        $signals = $this->signals;

        $index = false;
        foreach (self::$handlers as $key => $handler) {
            if (($handler instanceof WeakReference && $handler->get() === $this) || $handler === $this) {
                $index = $key;
                break;
            }
        }
        if ($index === false) {
            // guard against double-unregistration when __destruct happens
            return;
        }

        unset(self::$handlers[$index]);

        if (self::$windowsHandler !== null && (in_array(self::SIGINT, $signals, true) || in_array(self::SIGBREAK, $signals, true))) {
            if (self::getHandlerFor(self::SIGINT) === null && self::getHandlerFor(self::SIGBREAK) === null) {
                sapi_windows_set_ctrl_handler(self::$windowsHandler, false);
                self::$windowsHandler = null;
            }
        }

        if (function_exists('pcntl_signal')) {
            foreach ($signals as $signal) {
                // skip missing signals, for example OSX does not have all signals
                if (!defined($signal)) {
                    continue;
                }

                // keep listening to signals where we have a handler registered
                if (self::getHandlerFor($signal) !== null) {
                    continue;
                }

                pcntl_signal(constant($signal), SIG_DFL);
            }
        }
    }

    /**
     * Clears all signal handlers
     *
     * On PHP 8+ this should not be necessary as it will happen automatically on __destruct, but PHP 7 does not
     * support weak references and thus there you need to manually do this.
     *
     * This can be done to reset the global state, but ideally you should always call ->unregister() in a try/finally block to ensure it happens.
     */
    public static function unregisterAll(): void
    {
        if (self::$windowsHandler !== null) {
            sapi_windows_set_ctrl_handler(self::$windowsHandler, false);
            self::$windowsHandler = null;
        }

        foreach (self::$handlers as $key => $handler) {
            if ($handler instanceof WeakReference) {
                $handler = $handler->get();
                if ($handler === null) {
                    unset(self::$handlers[$key]);
                    continue;
                }
            }
            $handler->unregister();
        }
    }

    /**
     * @param list<self::SIG*> $signals
     */
    private static function registerPcntlHandler(array $signals): void
    {
        static $callable;
        if ($callable === null) {
            $callable = Closure::fromCallable([self::class, 'handlePcntlSignal']);
        }
        foreach ($signals as $signal) {
            // skip missing signals, for example OSX does not have all signals
            if (!defined($signal)) {
                continue;
            }

            pcntl_signal(constant($signal), $callable);
        }
    }

    private static function handleWindowsSignal(int $event): void
    {
        if (PHP_WINDOWS_EVENT_CTRL_C === $event) {
            self::callHandlerFor(self::SIGINT);
        } elseif (PHP_WINDOWS_EVENT_CTRL_BREAK === $event) {
            self::callHandlerFor(self::SIGBREAK);
        }
    }

    private static function handlePcntlSignal(int $signal): void
    {
        self::callHandlerFor(self::getSignalName($signal));
    }

    /**
     * Calls the first handler from the top of the stack that can handle a given signal
     *
     * @param self::SIG* $signal
     */
    private static function callHandlerFor(string $signal): void
    {
        $handler = self::getHandlerFor($signal);
        if ($handler !== null) {
            $handler->trigger($signal);
        }
    }

    /**
     * Returns the first handler from the top of the stack that can handle a given signal
     *
     * @param self::SIG* $signal
     * @return self|null
     */
    private static function getHandlerFor(string $signal): ?self
    {
        foreach (self::$handlers as $key => $handler) {
            if ($handler instanceof WeakReference) {
                $handler = $handler->get();
                if ($handler === null) {
                    unset(self::$handlers[$key]);
                    continue;
                }
            }
            if (in_array($signal, $handler->signals, true)) {
                return $handler;
            }
        }

        return null;
    }

    /**
     * @return self::SIG*
     */
    private static function getSignalName(int $signo): string
    {
        static $signals = null;
        if ($signals === null) {
            $signals = [];
            foreach (self::ALL_SIGNALS as $value) {
                if (defined($value)) {
                    $signals[constant($value)] = $value;
                }
            }
        }

        if (isset($signals[$signo])) {
            return $signals[$signo];
        }

        throw new \InvalidArgumentException('Unknown signal #'.$signo);
    }
}
<?php

declare(strict_types=1);

namespace MabeEnum;

use ArrayAccess;
use Countable;
use InvalidArgumentException;
use Iterator;
use IteratorAggregate;
use UnexpectedValueException;

/**
 * A map of enumerators and data values (EnumMap<T of Enum, mixed>).
 *
 * @template T of Enum
 * @implements ArrayAccess<T, mixed>
 * @implements IteratorAggregate<T, mixed>
 *
 * @copyright 2020, Marc Bennewitz
 * @license http://github.com/marc-mabe/php-enum/blob/master/LICENSE.txt New BSD License
 * @link http://github.com/marc-mabe/php-enum for the canonical source repository
 */
class EnumMap implements ArrayAccess, Countable, IteratorAggregate
{
    /**
     * The classname of the enumeration type
     * @var class-string<T>
     */
    private $enumeration;

    /**
     * Internal map of ordinal number and data value
     * @var array<int, mixed>
     */
    private $map = [];

    /**
     * Constructor
     * @param class-string<T> $enumeration The classname of the enumeration type
     * @param null|iterable<T|null|bool|int|float|string|array<mixed>, mixed> $map Initialize map
     * @throws InvalidArgumentException
     */
    public function __construct(string $enumeration, ?iterable $map = null)
    {
        if (!\is_subclass_of($enumeration, Enum::class)) {
            throw new InvalidArgumentException(\sprintf(
                '%s can handle subclasses of %s only',
                 __CLASS__,
                Enum::class
            ));
        }
        $this->enumeration = $enumeration;

        if ($map) {
            $this->addIterable($map);
        }
    }

    /**
     * Add virtual private property "__pairs" with a list of key-value-pairs
     * to the result of var_dump.
     *
     * This helps debugging as internally the map is using the ordinal number.
     *
     * @return array<string, mixed>
     */
    public function __debugInfo(): array {
        $dbg = (array)$this;
        $dbg["\0" . self::class . "\0__pairs"] = array_map(function ($k, $v) {
            return [$k, $v];
        }, $this->getKeys(), $this->getValues());
        return $dbg;
    }

    /* write access (mutable) */

    /**
     * Adds the given enumerator (object or value) mapping to the specified data value.
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
     * @param mixed                                                 $value
     * @throws InvalidArgumentException On an invalid given enumerator
     * @see offsetSet()
     */
    public function add($enumerator, $value): void
    {
        $ord = ($this->enumeration)::get($enumerator)->getOrdinal();
        $this->map[$ord] = $value;
    }

    /**
     * Adds the given iterable, mapping enumerators (objects or values) to data values.
     * @param iterable<T|null|bool|int|float|string|array<mixed>, mixed> $map
     * @throws InvalidArgumentException On an invalid given enumerator
     */
    public function addIterable(iterable $map): void
    {
        $innerMap = $this->map;
        foreach ($map as $enumerator => $value) {
            $ord = ($this->enumeration)::get($enumerator)->getOrdinal();
            $innerMap[$ord] = $value;
        }
        $this->map = $innerMap;
    }

    /**
     * Removes the given enumerator (object or value) mapping.
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
     * @throws InvalidArgumentException On an invalid given enumerator
     * @see offsetUnset()
     */
    public function remove($enumerator): void
    {
        $ord = ($this->enumeration)::get($enumerator)->getOrdinal();
        unset($this->map[$ord]);
    }

    /**
     * Removes the given iterable enumerator (object or value) mappings.
     * @param iterable<T|null|bool|int|float|string|array<mixed>> $enumerators
     * @throws InvalidArgumentException On an invalid given enumerator
     */
    public function removeIterable(iterable $enumerators): void
    {
        $map = $this->map;
        foreach ($enumerators as $enumerator) {
            $ord = ($this->enumeration)::get($enumerator)->getOrdinal();
            unset($map[$ord]);
        }

        $this->map = $map;
    }

    /* write access (immutable) */

    /**
     * Creates a new map with the given enumerator (object or value) mapping to the specified data value added.
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
     * @param mixed                                                 $value
     * @return static
     * @throws InvalidArgumentException On an invalid given enumerator
     */
    public function with($enumerator, $value): self
    {
        $clone = clone $this;
        $clone->add($enumerator, $value);
        return $clone;
    }

    /**
     * Creates a new map with the given iterable mapping enumerators (objects or values) to data values added.
     * @param iterable<T|null|bool|int|float|string|array<mixed>, mixed> $map
     * @return static
     * @throws InvalidArgumentException On an invalid given enumerator
     */
    public function withIterable(iterable $map): self
    {
        $clone = clone $this;
        $clone->addIterable($map);
        return $clone;
    }

    /**
     * Create a new map with the given enumerator mapping removed.
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
     * @return static
     * @throws InvalidArgumentException On an invalid given enumerator
     */
    public function without($enumerator): self
    {
        $clone = clone $this;
        $clone->remove($enumerator);
        return $clone;
    }

    /**
     * Creates a new map with the given iterable enumerator (object or value) mappings removed.
     * @param iterable<T|null|bool|int|float|string|array<mixed>> $enumerators
     * @return static
     * @throws InvalidArgumentException On an invalid given enumerator
     */
    public function withoutIterable(iterable $enumerators): self
    {
        $clone = clone $this;
        $clone->removeIterable($enumerators);
        return $clone;
    }

    /* read access */

    /**
     * Get the classname of the enumeration type.
     * @return class-string<T>
     */
    public function getEnumeration(): string
    {
        return $this->enumeration;
    }

    /**
     * Get the mapped data value of the given enumerator (object or value).
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
     * @return mixed
     * @throws InvalidArgumentException On an invalid given enumerator
     * @throws UnexpectedValueException If the given enumerator does not exist in this map
     * @see offsetGet()
     */
    public function get($enumerator)
    {
        $enumerator = ($this->enumeration)::get($enumerator);
        $ord = $enumerator->getOrdinal();
        if (!\array_key_exists($ord, $this->map)) {
            throw new UnexpectedValueException(sprintf(
                'Enumerator %s could not be found',
                \var_export($enumerator->getValue(), true)
            ));
        }

        return $this->map[$ord];
    }

    /**
     * Get a list of enumerator keys.
     * @return T[]
     *
     * @phpstan-return array<int, T>
     * @psalm-return list<T>
     */
    public function getKeys(): array
    {
        /** @var callable $byOrdinalFn */
        $byOrdinalFn = [$this->enumeration, 'byOrdinal'];

        return \array_map($byOrdinalFn, \array_keys($this->map));
    }

    /**
     * Get a list of mapped data values.
     * @return mixed[]
     *
     * @phpstan-return array<int, mixed>
     * @psalm-return list<mixed>
     */
    public function getValues(): array
    {
        return \array_values($this->map);
    }

    /**
     * Search for the given data value.
     * @param mixed $value
     * @param bool $strict Use strict type comparison
     * @return T|null The enumerator object of the first matching data value or NULL
     */
    public function search($value, bool $strict = false)
    {
        /** @var false|int $ord */
        $ord = \array_search($value, $this->map, $strict);
        if ($ord !== false) {
            return ($this->enumeration)::byOrdinal($ord);
        }

        return null;
    }

    /**
     * Test if the given enumerator key (object or value) exists.
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
     * @return bool
     * @see offsetExists()
     */
    public function has($enumerator): bool
    {
        try {
            $ord = ($this->enumeration)::get($enumerator)->getOrdinal();
            return \array_key_exists($ord, $this->map);
        } catch (InvalidArgumentException $e) {
            // An invalid enumerator can't be contained in this map
            return false;
        }
    }

    /**
     * Test if the given enumerator key (object or value) exists.
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
     * @return bool
     * @see offsetExists()
     * @see has()
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
     */
    public function contains($enumerator): bool
    {
        return $this->has($enumerator);
    }

    /* ArrayAccess */

    /**
     * Test if the given enumerator key (object or value) exists and is not NULL
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
     * @return bool
     * @see contains()
     */
    public function offsetExists($enumerator): bool
    {
        try {
            return isset($this->map[($this->enumeration)::get($enumerator)->getOrdinal()]);
        } catch (InvalidArgumentException $e) {
            // An invalid enumerator can't be an offset of this map
            return false;
        }
    }

    /**
     * Get the mapped data value of the given enumerator (object or value).
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
     * @return mixed The mapped date value of the given enumerator or NULL
     * @throws InvalidArgumentException On an invalid given enumerator
     * @see get()
     */
    #[\ReturnTypeWillChange]
    public function offsetGet($enumerator)
    {
        try {
            return $this->get($enumerator);
        } catch (UnexpectedValueException $e) {
            return null;
        }
    }

    /**
     * Adds the given enumerator (object or value) mapping to the specified data value.
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
     * @param mixed                                     $value
     * @return void
     * @throws InvalidArgumentException On an invalid given enumerator
     * @see add()
     */
    public function offsetSet($enumerator, $value = null): void
    {
        $this->add($enumerator, $value);
    }

    /**
     * Removes the given enumerator (object or value) mapping.
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
     * @return void
     * @throws InvalidArgumentException On an invalid given enumerator
     * @see remove()
     */
    public function offsetUnset($enumerator): void
    {
        $this->remove($enumerator);
    }

    /* IteratorAggregate */

    /**
     * Get a new Iterator.
     *
     * @return Iterator<T, mixed> Iterator<K extends Enum, V>
     */
    public function getIterator(): Iterator
    {
        $map = $this->map;
        foreach ($map as $ordinal => $value) {
            yield ($this->enumeration)::byOrdinal($ordinal) => $value;
        }
    }

    /* Countable */

    /**
     * Count the number of elements
     *
     * @return int
     */
    public function count(): int
    {
        return \count($this->map);
    }

    /**
     * Tests if the map is empty
     *
     * @return bool
     */
    public function isEmpty(): bool
    {
        return empty($this->map);
    }
}
<?php

declare(strict_types=1);

namespace MabeEnum;

use RuntimeException;
use LogicException;

/**
 * Trait to make enumerations serializable
 *
 * This trait is a workaround to make enumerations serializable.
 *
 * Please note that this feature breaks singleton behaviour of your enumerations
 * if an enumeration will be unserialized after it was instantiated already.
 *
 * @copyright 2020, Marc Bennewitz
 * @license http://github.com/marc-mabe/php-enum/blob/master/LICENSE.txt New BSD License
 * @link http://github.com/marc-mabe/php-enum for the canonical source repository
 * @link https://github.com/marc-mabe/php-enum/issues/52 for further information about this feature
 */
trait EnumSerializableTrait
{
    /**
     * The method will be defined by MabeEnum\Enum
     * @return null|bool|int|float|string|array<mixed>
     */
    abstract public function getValue();

    /**
     * Returns an array of data to be serialized.
     * This magic method will be called by serialize() in PHP >= 7.4
     *
     * @return array<string, null|bool|int|float|string|array<mixed>>
     */
    public function __serialize(): array
    {
        return ['value' => $this->getValue()];
    }

    /**
     * Receives an array of data to be unserialized on a new instance without constructor.
     * This magic method will be called in PHP >= 7.4 is the data where serialized with PHP >= 7.4.
     *
     * @throws RuntimeException On missing, unknown or invalid value
     * @throws LogicException   On calling this method on an already initialized enumerator
     *
     * @param array<string, mixed> $data
     * @return void
     */
    public function __unserialize(array $data): void
    {
        if (!\array_key_exists('value', $data)) {
            throw new RuntimeException('Missing array key "value"');
        }

        /** @var mixed $value */
        $value     = $data['value'];
        $constants = self::getConstants();
        $name      = \array_search($value, $constants, true);
        if ($name === false) {
            $message = \is_scalar($value)
                ? 'Unknown value ' . \var_export($value, true)
                : 'Invalid value of type ' . (\is_object($value) ? \get_class($value) : \gettype($value));
            throw new RuntimeException($message);
        }

        $class      = static::class;
        $enumerator = $this;
        $closure    = function () use ($class, $name, $value, $enumerator) {
            if ($value !== null && $this->value !== null) {
                throw new LogicException('Do not call this directly - please use unserialize($enum) instead');
            }

            $this->value = $value;

            if (!isset(self::$instances[$class][$name])) {
                self::$instances[$class][$name] = $enumerator;
            }
        };
        $closure->bindTo($this, Enum::class)();
    }

    /**
     * Serialize the value of the enumeration
     * This will be called automatically on `serialize()` if the enumeration implements the `Serializable` interface
     *
     * @return string
     * @deprecated Since PHP 7.4
     */
    public function serialize(): string
    {
        return \serialize($this->getValue());
    }

    /**
     * Unserializes a given serialized value and push it into the current instance
     * This will be called automatically on `unserialize()` if the enumeration implements the `Serializable` interface
     * @param string $serialized
     * @return void
     * @throws RuntimeException On an unknown or invalid value
     * @throws LogicException   On calling this method on an already initialized enumerator
     * @deprecated Since PHP 7.4
     */
    public function unserialize($serialized): void
    {
        $this->__unserialize(['value' => \unserialize($serialized)]);
    }
}
<?php

declare(strict_types=1);

namespace MabeEnum;

use ReflectionClass;
use InvalidArgumentException;
use LogicException;

/**
 * Abstract base enumeration class.
 *
 * @copyright 2020, Marc Bennewitz
 * @license http://github.com/marc-mabe/php-enum/blob/master/LICENSE.txt New BSD License
 * @link http://github.com/marc-mabe/php-enum for the canonical source repository
 *
 * @psalm-immutable
 */
abstract class Enum implements \Stringable
{
    /**
     * The selected enumerator value
     *
     * @var null|bool|int|float|string|array<mixed>
     */
    private $value;

    /**
     * The ordinal number of the enumerator
     *
     * @var null|int
     */
    private $ordinal;

    /**
     * A map of enumerator names and values by enumeration class
     *
     * @var array<class-string<Enum>, array<string, null|bool|int|float|string|array<mixed>>>
     */
    private static $constants = [];

    /**
     * A List of available enumerator names by enumeration class
     *
     * @var array<class-string<Enum>, string[]>
     */
    private static $names = [];

    /**
     * A map of enumerator names and instances by enumeration class
     *
     * @var array<class-string<Enum>, array<string, Enum>>
     */
    private static $instances = [];

    /**
     * Constructor
     *
     * @param null|bool|int|float|string|array<mixed> $value   The value of the enumerator
     * @param int|null                                $ordinal The ordinal number of the enumerator
     */
    final private function __construct($value, $ordinal = null)
    {
        $this->value   = $value;
        $this->ordinal = $ordinal;
    }

    /**
     * Get the name of the enumerator
     *
     * @return string
     * @see getName()
     */
    public function __toString(): string
    {
        return $this->getName();
    }

    /**
     * @throws LogicException Enums are not cloneable
     *                        because instances are implemented as singletons
     */
    final public function __clone()
    {
        throw new LogicException('Enums are not cloneable');
    }

    /**
     * @throws LogicException Serialization is not supported by default in this pseudo-enum implementation
     *
     * @psalm-return never-return
     */
    final public function __sleep()
    {
        throw new LogicException('Serialization is not supported by default in this pseudo-enum implementation');
    }

    /**
     * @throws LogicException Serialization is not supported by default in this pseudo-enum implementation
     *
     * @return array<string, mixed>
     */
    public function __serialize(): array
    {
        throw new LogicException('Serialization is not supported by default in this pseudo-enum implementation');
    }

    /**
     * @throws LogicException Serialization is not supported by default in this pseudo-enum implementation
     *
     * @psalm-return never-return
     */
    final public function __wakeup()
    {
        throw new LogicException('Serialization is not supported by default in this pseudo-enum implementation');
    }

    /**
     * @throws LogicException Serialization is not supported by default in this pseudo-enum implementation
     *
     * @param array<string, mixed> $data
     */
    public function __unserialize(array $data): void
    {
        throw new LogicException('Serialization is not supported by default in this pseudo-enum implementation');
    }
    
    /**
     * Get the value of the enumerator
     *
     * @return null|bool|int|float|string|array<mixed>
     */
    final public function getValue()
    {
        return $this->value;
    }

    /**
     * Get the name of the enumerator
     *
     * @return string
     *
     * @phpstan-return string
     * @psalm-return non-empty-string
     */
    final public function getName()
    {
        return self::$names[static::class][$this->ordinal ?? $this->getOrdinal()];
    }

    /**
     * Get the ordinal number of the enumerator
     *
     * @return int
     */
    final public function getOrdinal()
    {
        if ($this->ordinal === null) {
            $ordinal   = 0;
            $value     = $this->value;
            $constants = self::$constants[static::class] ?? static::getConstants();
            foreach ($constants as $constValue) {
                if ($value === $constValue) {
                    break;
                }
                ++$ordinal;
            }

            $this->ordinal = $ordinal;
        }

        return $this->ordinal;
    }

    /**
     * Compare this enumerator against another and check if it's the same.
     *
     * @param static|null|bool|int|float|string|array<mixed> $enumerator An enumerator object or value
     * @return bool
     */
    final public function is($enumerator)
    {
        return $this === $enumerator || $this->value === $enumerator

            // The following additional conditions are required only because of the issue of serializable singletons
            || ($enumerator instanceof static
                && \get_class($enumerator) === static::class
                && $enumerator->value === $this->value
            );
    }

    /**
     * Get an enumerator instance of the given enumerator value or instance
     *
     * @param static|null|bool|int|float|string|array<mixed> $enumerator An enumerator object or value
     * @return static
     * @throws InvalidArgumentException On an unknown or invalid value
     * @throws LogicException           On ambiguous constant values
     *
     * @psalm-pure
     */
    final public static function get($enumerator)
    {
        if ($enumerator instanceof static) {
            if (\get_class($enumerator) !== static::class) {
                throw new InvalidArgumentException(sprintf(
                    'Invalid value of type %s for enumeration %s',
                    \get_class($enumerator),
                    static::class
                ));
            }

            return $enumerator;
        }

        return static::byValue($enumerator);
    }

    /**
     * Get an enumerator instance by the given value
     *
     * @param null|bool|int|float|string|array<mixed> $value Enumerator value
     * @return static
     * @throws InvalidArgumentException On an unknown or invalid value
     * @throws LogicException           On ambiguous constant values
     *
     * @psalm-pure
     */
    final public static function byValue($value)
    {
        /** @var mixed $value */

        $constants = self::$constants[static::class] ?? static::getConstants();

        $name = \array_search($value, $constants, true);
        if ($name === false) {
            throw new InvalidArgumentException(sprintf(
                'Unknown value %s for enumeration %s',
                \is_scalar($value)
                    ? \var_export($value, true)
                    : 'of type ' . (\is_object($value) ? \get_class($value) : \gettype($value)),
                static::class
            ));
        }

        /** @var static $instance */
        $instance = self::$instances[static::class][$name]
            ?? self::$instances[static::class][$name] = new static($constants[$name]);

        return $instance;
    }

    /**
     * Get an enumerator instance by the given name
     *
     * @param string $name The name of the enumerator
     * @return static
     * @throws InvalidArgumentException On an invalid or unknown name
     * @throws LogicException           On ambiguous values
     *
     * @psalm-pure
     */
    final public static function byName(string $name)
    {
        if (isset(self::$instances[static::class][$name])) {
            /** @var static $instance */
            $instance = self::$instances[static::class][$name];
            return $instance;
        }

        $const = static::class . "::{$name}";
        if (!\defined($const)) {
            throw new InvalidArgumentException("{$const} not defined");
        }

        assert(
            self::noAmbiguousValues(static::getConstants()),
            'Ambiguous enumerator values detected for ' . static::class
        );

        /** @var array<int|string, mixed>|bool|float|int|string|null $value */
        $value = \constant($const);
        return self::$instances[static::class][$name] = new static($value);
    }

    /**
     * Get an enumeration instance by the given ordinal number
     *
     * @param int $ordinal The ordinal number of the enumerator
     * @return static
     * @throws InvalidArgumentException On an invalid ordinal number
     * @throws LogicException           On ambiguous values
     *
     * @psalm-pure
     */
    final public static function byOrdinal(int $ordinal)
    {
        $constants = self::$constants[static::class] ?? static::getConstants();

        if (!isset(self::$names[static::class][$ordinal])) {
            throw new InvalidArgumentException(\sprintf(
                'Invalid ordinal number %s, must between 0 and %s',
                $ordinal,
                \count(self::$names[static::class]) - 1
            ));
        }

        $name = self::$names[static::class][$ordinal];

        /** @var static $instance */
        $instance = self::$instances[static::class][$name]
            ?? self::$instances[static::class][$name] = new static($constants[$name], $ordinal);

        return $instance;
    }

    /**
     * Get a list of enumerator instances ordered by ordinal number
     *
     * @return static[]
     *
     * @phpstan-return array<int, static>
     * @psalm-return list<static>
     * @psalm-pure
     */
    final public static function getEnumerators()
    {
        if (!isset(self::$names[static::class])) {
            static::getConstants();
        }

        /** @var callable $byNameFn */
        $byNameFn = [static::class, 'byName'];
        return \array_map($byNameFn, self::$names[static::class]);
    }

    /**
     * Get a list of enumerator values ordered by ordinal number
     *
     * @return (null|bool|int|float|string|array)[]
     *
     * @phpstan-return array<int, null|bool|int|float|string|array<int|string, mixed>>
     * @psalm-return list<null|bool|int|float|string|array>
     * @psalm-pure
     */
    final public static function getValues()
    {
        return \array_values(self::$constants[static::class] ?? static::getConstants());
    }

    /**
     * Get a list of enumerator names ordered by ordinal number
     *
     * @return string[]
     *
     * @phpstan-return array<int, string>
     * @psalm-return list<non-empty-string>
     * @psalm-pure
     */
    final public static function getNames()
    {
        if (!isset(self::$names[static::class])) {
            static::getConstants();
        }
        return self::$names[static::class];
    }

    /**
     * Get a list of enumerator ordinal numbers
     *
     * @return int[]
     *
     * @phpstan-return array<int, int>
     * @psalm-return list<int>
     * @psalm-pure
     */
    final public static function getOrdinals()
    {
        $count = \count(self::$constants[static::class] ?? static::getConstants());
        return $count ? \range(0, $count - 1) : [];
    }

    /**
     * Get all available constants of the called class
     *
     * @return (null|bool|int|float|string|array)[]
     * @throws LogicException On ambiguous constant values
     *
     * @phpstan-return array<string, null|bool|int|float|string|array<int|string, mixed>>
     * @psalm-return array<non-empty-string, null|bool|int|float|string|array>
     * @psalm-pure
     */
    final public static function getConstants()
    {
        if (isset(self::$constants[static::class])) {
            return self::$constants[static::class];
        }

        $reflection = new ReflectionClass(static::class);
        $constants  = [];

        do {
            $scopeConstants = [];
            // Enumerators must be defined as public class constants
            foreach ($reflection->getReflectionConstants() as $reflConstant) {
                if ($reflConstant->isPublic()) {
                    $scopeConstants[ $reflConstant->getName() ] = $reflConstant->getValue();
                }
            }

            $constants = $scopeConstants + $constants;
        } while (($reflection = $reflection->getParentClass()) && $reflection->name !== __CLASS__);

        /** @var array<string, null|bool|int|float|string|array<mixed>> $constants */

        assert(
            self::noAmbiguousValues($constants),
            'Ambiguous enumerator values detected for ' . static::class
        );

        self::$names[static::class] = \array_keys($constants);
        return self::$constants[static::class] = $constants;
    }

    /**
     * Test that the given constants does not contain ambiguous values
     * @param array<string, null|bool|int|float|string|array<mixed>> $constants
     * @return bool
     */
    private static function noAmbiguousValues($constants)
    {
        foreach ($constants as $value) {
            $names = \array_keys($constants, $value, true);
            if (\count($names) > 1) {
                return false;
            }
        }

        return true;
    }

    /**
     * Test if the given enumerator is part of this enumeration
     *
     * @param static|null|bool|int|float|string|array<mixed> $enumerator
     * @return bool
     *
     * @psalm-pure
     */
    final public static function has($enumerator)
    {
        if ($enumerator instanceof static) {
            return \get_class($enumerator) === static::class;
        }

        return static::hasValue($enumerator);
    }

    /**
     * Test if the given enumerator value is part of this enumeration
     *
     * @param null|bool|int|float|string|array<mixed> $value
     * @return bool
     *
     * @psalm-pure
     */
    final public static function hasValue($value)
    {
        return \in_array($value, self::$constants[static::class] ?? static::getConstants(), true);
    }

    /**
     * Test if the given enumerator name is part of this enumeration
     *
     * @param string $name
     * @return bool
     *
     * @psalm-pure
     */
    final public static function hasName(string $name)
    {
        return \defined("static::{$name}");
    }

    /**
     * Get an enumerator instance by the given name.
     *
     * This will be called automatically on calling a method
     * with the same name of a defined enumerator.
     *
     * @param string       $method The name of the enumerator (called as method)
     * @param array<mixed> $args   There should be no arguments
     * @return static
     * @throws InvalidArgumentException On an invalid or unknown name
     * @throws LogicException           On ambiguous constant values
     *
     * @psalm-pure
     */
    final public static function __callStatic(string $method, array $args)
    {
        return static::byName($method);
    }
}
<?php

declare(strict_types=1);

namespace MabeEnum;

use Countable;
use InvalidArgumentException;
use Iterator;
use IteratorAggregate;

/**
 * A set of enumerators of the given enumeration (EnumSet<T of Enum>)
 * based on an integer or binary bitset depending of given enumeration size.
 *
 * @template T of Enum
 * @implements IteratorAggregate<int, T>
 *
 * @copyright 2020, Marc Bennewitz
 * @license http://github.com/marc-mabe/php-enum/blob/master/LICENSE.txt New BSD License
 * @link http://github.com/marc-mabe/php-enum for the canonical source repository
 */
class EnumSet implements IteratorAggregate, Countable
{
    /**
     * The classname of the Enumeration
     * @var class-string<T>
     */
    private $enumeration;

    /**
     * Number of enumerators defined in the enumeration
     * @var int
     */
    private $enumerationCount;

    /**
     * Integer or binary (little endian) bitset
     * @var int|string
     */
    private $bitset = 0;

    /**
     * Integer or binary (little endian) empty bitset
     *
     * @var int|string
     */
    private $emptyBitset = 0;

    /**#@+
     * Defines private method names to be called depended of how the bitset type was set too.
     * ... Integer or binary bitset.
     * ... *Int or *Bin method
     *
     * @var string
     */
    /** @var string */
    private $fnDoGetIterator       = 'doGetIteratorInt';

    /** @var string */
    private $fnDoCount             = 'doCountInt';

    /** @var string */
    private $fnDoGetOrdinals       = 'doGetOrdinalsInt';

    /** @var string */
    private $fnDoGetBit            = 'doGetBitInt';

    /** @var string */
    private $fnDoSetBit            = 'doSetBitInt';

    /** @var string */
    private $fnDoUnsetBit          = 'doUnsetBitInt';

    /** @var string */
    private $fnDoGetBinaryBitsetLe = 'doGetBinaryBitsetLeInt';

    /** @var string */
    private $fnDoSetBinaryBitsetLe = 'doSetBinaryBitsetLeInt';
    /**#@-*/

    /**
     * Constructor
     *
     * @param class-string<T> $enumeration The classname of the enumeration
     * @param iterable<T|null|bool|int|float|string|array<mixed>>|null $enumerators iterable list of enumerators initializing the set
     * @throws InvalidArgumentException
     */
    public function __construct(string $enumeration, ?iterable $enumerators = null)
    {
        if (!\is_subclass_of($enumeration, Enum::class)) {
            throw new InvalidArgumentException(\sprintf(
                '%s can handle subclasses of %s only',
                __METHOD__,
                Enum::class
            ));
        }

        $this->enumeration      = $enumeration;
        $this->enumerationCount = \count($enumeration::getConstants());

        // By default the bitset is initialized as integer bitset
        // in case the enumeration has more enumerators then integer bits
        // we will switch this into a binary bitset
        if ($this->enumerationCount > \PHP_INT_SIZE * 8) {
            // init binary bitset with zeros
            $this->bitset = $this->emptyBitset = \str_repeat("\0", (int)\ceil($this->enumerationCount / 8));

            // switch internal binary bitset functions
            $this->fnDoGetIterator       = 'doGetIteratorBin';
            $this->fnDoCount             = 'doCountBin';
            $this->fnDoGetOrdinals       = 'doGetOrdinalsBin';
            $this->fnDoGetBit            = 'doGetBitBin';
            $this->fnDoSetBit            = 'doSetBitBin';
            $this->fnDoUnsetBit          = 'doUnsetBitBin';
            $this->fnDoGetBinaryBitsetLe = 'doGetBinaryBitsetLeBin';
            $this->fnDoSetBinaryBitsetLe = 'doSetBinaryBitsetLeBin';
        }

        if ($enumerators !== null) {
            foreach ($enumerators as $enumerator) {
                $this->{$this->fnDoSetBit}($enumeration::get($enumerator)->getOrdinal());
            }
        }
    }

    /**
     * Add virtual private property "__enumerators" with a list of enumerator values set
     * to the result of var_dump.
     *
     * This helps debugging as internally the enumerators of this EnumSet gets stored
     * as either integer or binary bit-array.
     *
     * @return array<string, mixed>
     */
    public function __debugInfo() {
        $dbg = (array)$this;
        $dbg["\0" . self::class . "\0__enumerators"] = $this->getValues();
        return $dbg;
    }

    /**
     * Get the classname of the enumeration
     * @return class-string<T>
     */
    public function getEnumeration(): string
    {
        return $this->enumeration;
    }

    /* write access (mutable) */

    /**
     * Adds an enumerator object or value
     * @param T|null|bool|int|float|string|array<mixed> $enumerator Enumerator object or value
     * @return void
     * @throws InvalidArgumentException On an invalid given enumerator
     */
    public function add($enumerator): void
    {
        $this->{$this->fnDoSetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
    }

    /**
     * Adds all enumerator objects or values of the given iterable
     * @param iterable<T|null|bool|int|float|string|array<mixed>> $enumerators Iterable list of enumerator objects or values
     * @return void
     * @throws InvalidArgumentException On an invalid given enumerator
     */
    public function addIterable(iterable $enumerators): void
    {
        $bitset = $this->bitset;

        try {
            foreach ($enumerators as $enumerator) {
                $this->{$this->fnDoSetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
            }
        } catch (\Throwable $e) {
            // reset all changes until error happened
            $this->bitset = $bitset;
            throw $e;
        }
    }

    /**
     * Removes the given enumerator object or value
     * @param T|null|bool|int|float|string|array<mixed> $enumerator Enumerator object or value
     * @return void
     * @throws InvalidArgumentException On an invalid given enumerator
     */
    public function remove($enumerator): void
    {
        $this->{$this->fnDoUnsetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
    }

    /**
     * Adds an enumerator object or value
     * @param T|null|bool|int|float|string|array<mixed> $enumerator Enumerator object or value
     * @return void
     * @throws InvalidArgumentException On an invalid given enumerator
     * @see add()
     * @see with()
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
     */
    public function attach($enumerator): void
    {
        $this->add($enumerator);
    }

    /**
     * Removes the given enumerator object or value
     * @param T|null|bool|int|float|string|array<mixed> $enumerator Enumerator object or value
     * @return void
     * @throws InvalidArgumentException On an invalid given enumerator
     * @see remove()
     * @see without()
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
     */
    public function detach($enumerator): void
    {
        $this->remove($enumerator);
    }

    /**
     * Removes all enumerator objects or values of the given iterable
     * @param iterable<T|null|bool|int|float|string|array<mixed>> $enumerators Iterable list of enumerator objects or values
     * @return void
     * @throws InvalidArgumentException On an invalid given enumerator
     */
    public function removeIterable(iterable $enumerators): void
    {
        $bitset = $this->bitset;

        try {
            foreach ($enumerators as $enumerator) {
                $this->{$this->fnDoUnsetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
            }
        } catch (\Throwable $e) {
            // reset all changes until error happened
            $this->bitset = $bitset;
            throw $e;
        }
    }

    /**
     * Modify this set from both this and other (this | other)
     *
     * @param EnumSet<T> $other EnumSet of the same enumeration to produce the union
     * @return void
     * @throws InvalidArgumentException If $other doesn't match the enumeration
     */
    public function setUnion(EnumSet $other): void
    {
        if ($this->enumeration !== $other->enumeration) {
            throw new InvalidArgumentException(\sprintf(
                'Other should be of the same enumeration as this %s',
                $this->enumeration
            ));
        }

        $this->bitset = $this->bitset | $other->bitset;
    }

    /**
     * Modify this set with enumerators common to both this and other (this & other)
     *
     * @param EnumSet<T> $other EnumSet of the same enumeration to produce the intersect
     * @return void
     * @throws InvalidArgumentException If $other doesn't match the enumeration
     */
    public function setIntersect(EnumSet $other): void
    {
        if ($this->enumeration !== $other->enumeration) {
            throw new InvalidArgumentException(\sprintf(
                'Other should be of the same enumeration as this %s',
                $this->enumeration
            ));
        }

        $this->bitset = $this->bitset & $other->bitset;
    }

    /**
     * Modify this set with enumerators in this but not in other (this - other)
     *
     * @param EnumSet<T> $other EnumSet of the same enumeration to produce the diff
     * @return void
     * @throws InvalidArgumentException If $other doesn't match the enumeration
     */
    public function setDiff(EnumSet $other): void
    {
        if ($this->enumeration !== $other->enumeration) {
            throw new InvalidArgumentException(\sprintf(
                'Other should be of the same enumeration as this %s',
                $this->enumeration
            ));
        }

        $this->bitset = $this->bitset & ~$other->bitset;
    }

    /**
     * Modify this set with enumerators in either this and other but not in both (this ^ other)
     *
     * @param EnumSet<T> $other EnumSet of the same enumeration to produce the symmetric difference
     * @return void
     * @throws InvalidArgumentException If $other doesn't match the enumeration
     */
    public function setSymDiff(EnumSet $other): void
    {
        if ($this->enumeration !== $other->enumeration) {
            throw new InvalidArgumentException(\sprintf(
                'Other should be of the same enumeration as this %s',
                $this->enumeration
            ));
        }

        $this->bitset = $this->bitset ^ $other->bitset;
    }

    /**
     * Set the given binary bitset in little-endian order
     *
     * @param string $bitset
     * @return void
     * @throws InvalidArgumentException On out-of-range bits given as input bitset
     * @uses doSetBinaryBitsetLeBin()
     * @uses doSetBinaryBitsetLeInt()
     */
    public function setBinaryBitsetLe(string $bitset): void
    {
        $this->{$this->fnDoSetBinaryBitsetLe}($bitset);
    }

    /**
     * Set binary bitset in little-endian order
     *
     * @param string $bitset
     * @return void
     * @throws InvalidArgumentException On out-of-range bits given as input bitset
     * @see setBinaryBitsetLeBin()
     * @see doSetBinaryBitsetLeInt()
     */
    private function doSetBinaryBitsetLeBin($bitset): void
    {
        /** @var string $thisBitset */
        $thisBitset = $this->bitset;

        $size   = \strlen($thisBitset);
        $sizeIn = \strlen($bitset);

        if ($sizeIn < $size) {
            // add "\0" if the given bitset is not long enough
            $bitset .= \str_repeat("\0", $size - $sizeIn);
        } elseif ($sizeIn > $size) {
            if (\ltrim(\substr($bitset, $size), "\0") !== '') {
                throw new InvalidArgumentException('out-of-range bits detected');
            }
            $bitset = \substr($bitset, 0, $size);
        }

        // truncate out-of-range bits of last byte
        $lastByteMaxOrd = $this->enumerationCount % 8;
        if ($lastByteMaxOrd !== 0) {
            $lastByte         = $bitset[-1];
            $lastByteExpected = \chr((1 << $lastByteMaxOrd) - 1) & $lastByte;
            if ($lastByte !== $lastByteExpected) {
                throw new InvalidArgumentException('out-of-range bits detected');
            }

            $this->bitset = \substr($bitset, 0, -1) . $lastByteExpected;
        }

        $this->bitset = $bitset;
    }

    /**
     * Set binary bitset in little-endian order
     *
     * @param string $bitset
     * @return void
     * @throws InvalidArgumentException On out-of-range bits given as input bitset
     * @see setBinaryBitsetLeBin()
     * @see doSetBinaryBitsetLeBin()
     */
    private function doSetBinaryBitsetLeInt($bitset): void
    {
        $len = \strlen($bitset);
        $int = 0;
        for ($i = 0; $i < $len; ++$i) {
            $ord = \ord($bitset[$i]);

            if ($ord && $i > \PHP_INT_SIZE - 1) {
                throw new InvalidArgumentException('out-of-range bits detected');
            }

            $int |= $ord << (8 * $i);
        }

        if ($int & (~0 << $this->enumerationCount)) {
            throw new InvalidArgumentException('out-of-range bits detected');
        }

        $this->bitset = $int;
    }

    /**
     * Set the given binary bitset in big-endian order
     *
     * @param string $bitset
     * @return void
     * @throws InvalidArgumentException On out-of-range bits given as input bitset
     */
    public function setBinaryBitsetBe(string $bitset): void
    {
        $this->{$this->fnDoSetBinaryBitsetLe}(\strrev($bitset));
    }

    /**
     * Set a bit at the given ordinal number
     *
     * @param int $ordinal Ordinal number of bit to set
     * @param bool $bit    The bit to set
     * @return void
     * @throws InvalidArgumentException If the given ordinal number is out-of-range
     * @uses doSetBitBin()
     * @uses doSetBitInt()
     * @uses doUnsetBitBin()
     * @uses doUnsetBitInt()
     */
    public function setBit(int $ordinal, bool $bit): void
    {
        if ($ordinal < 0 || $ordinal > $this->enumerationCount) {
            throw new InvalidArgumentException("Ordinal number must be between 0 and {$this->enumerationCount}");
        }

        if ($bit) {
            $this->{$this->fnDoSetBit}($ordinal);
        } else {
            $this->{$this->fnDoUnsetBit}($ordinal);
        }
    }

    /**
     * Set a bit at the given ordinal number.
     *
     * This is the binary bitset implementation.
     *
     * @param int $ordinal Ordinal number of bit to set
     * @return void
     * @see setBit()
     * @see doSetBitInt()
     */
    private function doSetBitBin($ordinal): void
    {
        /** @var string $thisBitset */
        $thisBitset = $this->bitset;

        $byte = (int) ($ordinal / 8);
        $thisBitset[$byte] = $thisBitset[$byte] | \chr(1 << ($ordinal % 8));

        $this->bitset = $thisBitset;
    }

    /**
     * Set a bit at the given ordinal number.
     *
     * This is the binary bitset implementation.
     *
     * @param int $ordinal Ordinal number of bit to set
     * @return void
     * @see setBit()
     * @see doSetBitBin()
     */
    private function doSetBitInt($ordinal): void
    {
        /** @var int $thisBitset */
        $thisBitset = $this->bitset;

        $this->bitset = $thisBitset | (1 << $ordinal);
    }

    /**
     * Unset a bit at the given ordinal number.
     *
     * This is the binary bitset implementation.
     *
     * @param int $ordinal Ordinal number of bit to unset
     * @return void
     * @see setBit()
     * @see doUnsetBitInt()
     */
    private function doUnsetBitBin($ordinal): void
    {
        /** @var string $thisBitset */
        $thisBitset = $this->bitset;

        $byte = (int) ($ordinal / 8);
        $thisBitset[$byte] = $thisBitset[$byte] & \chr(~(1 << ($ordinal % 8)));

        $this->bitset = $thisBitset;
    }

    /**
     * Unset a bit at the given ordinal number.
     *
     * This is the integer bitset implementation.
     *
     * @param int $ordinal Ordinal number of bit to unset
     * @return void
     * @see setBit()
     * @see doUnsetBitBin()
     */
    private function doUnsetBitInt($ordinal): void
    {
        /** @var int $thisBitset */
        $thisBitset = $this->bitset;

        $this->bitset = $thisBitset & ~(1 << $ordinal);
    }

    /* write access (immutable) */

    /**
     * Creates a new set with the given enumerator object or value added
     * @param T|null|bool|int|float|string|array<mixed> $enumerator Enumerator object or value
     * @return static
     * @throws InvalidArgumentException On an invalid given enumerator
     */
    public function with($enumerator): self
    {
        $clone = clone $this;
        $clone->{$this->fnDoSetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
        return $clone;
    }

    /**
     * Creates a new set with the given enumeration objects or values added
     * @param iterable<T|null|bool|int|float|string|array<mixed>> $enumerators Iterable list of enumerator objects or values
     * @return static
     * @throws InvalidArgumentException On an invalid given enumerator
     */
    public function withIterable(iterable $enumerators): self
    {
        $clone = clone $this;
        foreach ($enumerators as $enumerator) {
            $clone->{$this->fnDoSetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
        }
        return $clone;
    }

    /**
     * Create a new set with the given enumerator object or value removed
     * @param T|null|bool|int|float|string|array<mixed> $enumerator Enumerator object or value
     * @return static
     * @throws InvalidArgumentException On an invalid given enumerator
     */
    public function without($enumerator): self
    {
        $clone = clone $this;
        $clone->{$this->fnDoUnsetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
        return $clone;
    }

    /**
     * Creates a new set with the given enumeration objects or values removed
     * @param iterable<T|null|bool|int|float|string|array<mixed>> $enumerators Iterable list of enumerator objects or values
     * @return static
     * @throws InvalidArgumentException On an invalid given enumerator
     */
    public function withoutIterable(iterable $enumerators): self
    {
        $clone = clone $this;
        foreach ($enumerators as $enumerator) {
            $clone->{$this->fnDoUnsetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
        }
        return $clone;
    }

    /**
     * Create a new set with enumerators from both this and other (this | other)
     *
     * @param EnumSet<T> $other EnumSet of the same enumeration to produce the union
     * @return static
     * @throws InvalidArgumentException If $other doesn't match the enumeration
     */
    public function withUnion(EnumSet $other): self
    {
        $clone = clone $this;
        $clone->setUnion($other);
        return $clone;
    }

    /**
     * Create a new set with enumerators from both this and other (this | other)
     *
     * @param EnumSet<T> $other EnumSet of the same enumeration to produce the union
     * @return static
     * @throws InvalidArgumentException If $other doesn't match the enumeration
     * @see withUnion()
     * @see setUnion()
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
     */
    public function union(EnumSet $other): self
    {
        return $this->withUnion($other);
    }

    /**
     * Create a new set with enumerators common to both this and other (this & other)
     *
     * @param EnumSet<T> $other EnumSet of the same enumeration to produce the intersect
     * @return static
     * @throws InvalidArgumentException If $other doesn't match the enumeration
     */
    public function withIntersect(EnumSet $other): self
    {
        $clone = clone $this;
        $clone->setIntersect($other);
        return $clone;
    }

    /**
     * Create a new set with enumerators common to both this and other (this & other)
     *
     * @param EnumSet<T> $other EnumSet of the same enumeration to produce the intersect
     * @return static
     * @throws InvalidArgumentException If $other doesn't match the enumeration
     * @see withIntersect()
     * @see setIntersect()
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
     */
    public function intersect(EnumSet $other): self
    {
        return $this->withIntersect($other);
    }

    /**
     * Create a new set with enumerators in this but not in other (this - other)
     *
     * @param EnumSet<T> $other EnumSet of the same enumeration to produce the diff
     * @return static
     * @throws InvalidArgumentException If $other doesn't match the enumeration
     */
    public function withDiff(EnumSet $other): self
    {
        $clone = clone $this;
        $clone->setDiff($other);
        return $clone;
    }

    /**
     * Create a new set with enumerators in this but not in other (this - other)
     *
     * @param EnumSet<T> $other EnumSet of the same enumeration to produce the diff
     * @return static
     * @throws InvalidArgumentException If $other doesn't match the enumeration
     * @see withDiff()
     * @see setDiff()
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
     */
    public function diff(EnumSet $other): self
    {
        return $this->withDiff($other);
    }

    /**
     * Create a new set with enumerators in either this and other but not in both (this ^ other)
     *
     * @param EnumSet<T> $other EnumSet of the same enumeration to produce the symmetric difference
     * @return static
     * @throws InvalidArgumentException If $other doesn't match the enumeration
     */
    public function withSymDiff(EnumSet $other): self
    {
        $clone = clone $this;
        $clone->setSymDiff($other);
        return $clone;
    }

    /**
     * Create a new set with enumerators in either this and other but not in both (this ^ other)
     *
     * @param EnumSet<T> $other EnumSet of the same enumeration to produce the symmetric difference
     * @return static
     * @throws InvalidArgumentException If $other doesn't match the enumeration
     * @see withSymDiff()
     * @see setSymDiff()
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
     */
    public function symDiff(EnumSet $other): self
    {
        return $this->withSymDiff($other);
    }

    /**
     * Create a new set with the given binary bitset in little-endian order
     *
     * @param string $bitset
     * @return static
     * @throws InvalidArgumentException On out-of-range bits given as input bitset
     * @uses doSetBinaryBitsetLeBin()
     * @uses doSetBinaryBitsetLeInt()
     */
    public function withBinaryBitsetLe(string $bitset): self
    {
        $clone = clone $this;
        $clone->{$this->fnDoSetBinaryBitsetLe}($bitset);
        return $clone;
    }

    /**
     * Create a new set with the given binary bitset in big-endian order
     *
     * @param string $bitset
     * @return static
     * @throws InvalidArgumentException On out-of-range bits given as input bitset
     */
    public function withBinaryBitsetBe(string $bitset): self
    {
        $clone = $this;
        $clone->{$this->fnDoSetBinaryBitsetLe}(\strrev($bitset));
        return $clone;
    }

    /**
     * Create a new set with the bit at the given ordinal number set
     *
     * @param int $ordinal Ordinal number of bit to set
     * @param bool $bit    The bit to set
     * @return static
     * @throws InvalidArgumentException If the given ordinal number is out-of-range
     * @uses doSetBitBin()
     * @uses doSetBitInt()
     * @uses doUnsetBitBin()
     * @uses doUnsetBitInt()
     */
    public function withBit(int $ordinal, bool $bit): self
    {
        $clone = clone $this;
        $clone->setBit($ordinal, $bit);
        return $clone;
    }

    /* read access */

    /**
     * Test if the given enumerator exists
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
     * @return bool
     */
    public function has($enumerator): bool
    {
        return $this->{$this->fnDoGetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
    }

    /**
     * Test if the given enumerator exists
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
     * @return bool
     * @see has()
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
     */
    public function contains($enumerator): bool
    {
        return $this->has($enumerator);
    }

    /* IteratorAggregate */

    /**
     * Get a new iterator
     * @return Iterator<int, T>
     * @uses doGetIteratorInt()
     * @uses doGetIteratorBin()
     */
    public function getIterator(): Iterator
    {
        return $this->{$this->fnDoGetIterator}();
    }

    /**
     * Get a new Iterator.
     *
     * This is the binary bitset implementation.
     *
     * @return Iterator<int, T>
     * @see getIterator()
     * @see goGetIteratorInt()
     */
    private function doGetIteratorBin()
    {
        /** @var string $bitset */
        $bitset   = $this->bitset;
        $byteLen  = \strlen($bitset);
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
            if ($bitset[$bytePos] === "\0") {
                // fast skip null byte
                continue;
            }

            $ord = \ord($bitset[$bytePos]);
            for ($bitPos = 0; $bitPos < 8; ++$bitPos) {
                if ($ord & (1 << $bitPos)) {
                    $ordinal = $bytePos * 8 + $bitPos;
                    yield $ordinal => ($this->enumeration)::byOrdinal($ordinal);
                }
            }
        }
    }

    /**
     * Get a new Iterator.
     *
     * This is the integer bitset implementation.
     *
     * @return Iterator<int, T>
     * @see getIterator()
     * @see doGetIteratorBin()
     */
    private function doGetIteratorInt()
    {
        /** @var int $bitset */
        $bitset = $this->bitset;
        $count  = $this->enumerationCount;
        for ($ordinal = 0; $ordinal < $count; ++$ordinal) {
            if ($bitset & (1 << $ordinal)) {
                yield $ordinal => ($this->enumeration)::byOrdinal($ordinal);
            }
        }
    }

    /* Countable */

    /**
     * Count the number of elements
     *
     * @return int
     * @uses doCountBin()
     * @uses doCountInt()
     */
    public function count(): int
    {
        return $this->{$this->fnDoCount}();
    }

    /**
     * Count the number of elements.
     *
     * This is the binary bitset implementation.
     *
     * @return int
     * @see count()
     * @see doCountInt()
     */
    private function doCountBin()
    {
        /** @var string $bitset */
        $bitset  = $this->bitset;
        $count   = 0;
        $byteLen = \strlen($bitset);
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
            if ($bitset[$bytePos] === "\0") {
                // fast skip null byte
                continue;
            }

            $ord = \ord($bitset[$bytePos]);
            if ($ord & 0b00000001) ++$count;
            if ($ord & 0b00000010) ++$count;
            if ($ord & 0b00000100) ++$count;
            if ($ord & 0b00001000) ++$count;
            if ($ord & 0b00010000) ++$count;
            if ($ord & 0b00100000) ++$count;
            if ($ord & 0b01000000) ++$count;
            if ($ord & 0b10000000) ++$count;
        }
        return $count;
    }

    /**
     * Count the number of elements.
     *
     * This is the integer bitset implementation.
     *
     * @return int
     * @see count()
     * @see doCountBin()
     */
    private function doCountInt()
    {
        /** @var int $bitset */
        $bitset = $this->bitset;
        $count  = 0;

        // PHP does not support right shift unsigned
        if ($bitset < 0) {
            $count  = 1;
            $bitset = $bitset & \PHP_INT_MAX;
        }

        // iterate byte by byte and count set bits
        $phpIntBitSize = \PHP_INT_SIZE * 8;
        for ($bitPos = 0; $bitPos < $phpIntBitSize; $bitPos += 8) {
            $bitChk = 0xff << $bitPos;
            $byte = $bitset & $bitChk;
            if ($byte) {
                $byte = $byte >> $bitPos;
                if ($byte & 0b00000001) ++$count;
                if ($byte & 0b00000010) ++$count;
                if ($byte & 0b00000100) ++$count;
                if ($byte & 0b00001000) ++$count;
                if ($byte & 0b00010000) ++$count;
                if ($byte & 0b00100000) ++$count;
                if ($byte & 0b01000000) ++$count;
                if ($byte & 0b10000000) ++$count;
            }

            if ($bitset <= $bitChk) {
                break;
            }
        }

        return $count;
    }

    /**
     * Check if this EnumSet is the same as other
     * @param EnumSet<T> $other
     * @return bool
     */
    public function isEqual(EnumSet $other): bool
    {
        return $this->enumeration === $other->enumeration
            && $this->bitset === $other->bitset;
    }

    /**
     * Check if this EnumSet is a subset of other
     * @param EnumSet<T> $other
     * @return bool
     */
    public function isSubset(EnumSet $other): bool
    {
        return $this->enumeration === $other->enumeration
            && ($this->bitset & $other->bitset) === $this->bitset;
    }

    /**
     * Check if this EnumSet is a superset of other
     * @param EnumSet<T> $other
     * @return bool
     */
    public function isSuperset(EnumSet $other): bool
    {
        return $this->enumeration === $other->enumeration
            && ($this->bitset | $other->bitset) === $this->bitset;
    }

    /**
     * Tests if the set is empty
     *
     * @return bool
     */
    public function isEmpty(): bool
    {
        return $this->bitset === $this->emptyBitset;
    }

    /**
     * Get ordinal numbers of the defined enumerators as array
     * @return array<int, int>
     * @uses  doGetOrdinalsBin()
     * @uses  doGetOrdinalsInt()
     */
    public function getOrdinals(): array
    {
        return $this->{$this->fnDoGetOrdinals}();
    }

    /**
     * Get ordinal numbers of the defined enumerators as array.
     *
     * This is the binary bitset implementation.
     *
     * @return array<int, int>
     * @see getOrdinals()
     * @see goGetOrdinalsInt()
     */
    private function doGetOrdinalsBin()
    {
        /** @var string $bitset */
        $bitset   = $this->bitset;
        $ordinals = [];
        $byteLen  = \strlen($bitset);
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
            if ($bitset[$bytePos] === "\0") {
                // fast skip null byte
                continue;
            }

            $ord = \ord($bitset[$bytePos]);
            for ($bitPos = 0; $bitPos < 8; ++$bitPos) {
                if ($ord & (1 << $bitPos)) {
                    $ordinals[] = $bytePos * 8 + $bitPos;
                }
            }
        }
        return $ordinals;
    }

    /**
     * Get ordinal numbers of the defined enumerators as array.
     *
     * This is the integer bitset implementation.
     *
     * @return array<int, int>
     * @see getOrdinals()
     * @see doGetOrdinalsBin()
     */
    private function doGetOrdinalsInt()
    {
        /** @var int $bitset */
        $bitset   = $this->bitset;
        $ordinals = [];
        $count    = $this->enumerationCount;
        for ($ordinal = 0; $ordinal < $count; ++$ordinal) {
            if ($bitset & (1 << $ordinal)) {
                $ordinals[] = $ordinal;
            }
        }
        return $ordinals;
    }

    /**
     * Get values of the defined enumerators as array
     * @return (null|bool|int|float|string|array)[]
     *
     * @phpstan-return array<int, null|bool|int|float|string|array<mixed>>
     * @psalm-return list<null|bool|int|float|string|array>
     */
    public function getValues(): array
    {
        $enumeration = $this->enumeration;
        $values      = [];
        foreach ($this->getOrdinals() as $ord) {
            $values[] = $enumeration::byOrdinal($ord)->getValue();
        }
        return $values;
    }

    /**
     * Get names of the defined enumerators as array
     * @return string[]
     *
     * @phpstan-return array<int, string>
     * @psalm-return list<string>
     */
    public function getNames(): array
    {
        $enumeration = $this->enumeration;
        $names       = [];
        foreach ($this->getOrdinals() as $ord) {
            $names[] = $enumeration::byOrdinal($ord)->getName();
        }
        return $names;
    }

    /**
     * Get the defined enumerators as array
     * @return Enum[]
     *
     * @phpstan-return array<int, T>
     * @psalm-return list<T>
     */
    public function getEnumerators(): array
    {
        $enumeration = $this->enumeration;
        $enumerators = [];
        foreach ($this->getOrdinals() as $ord) {
            $enumerators[] = $enumeration::byOrdinal($ord);
        }
        return $enumerators;
    }

    /**
     * Get binary bitset in little-endian order
     *
     * @return string
     * @uses doGetBinaryBitsetLeBin()
     * @uses doGetBinaryBitsetLeInt()
     */
    public function getBinaryBitsetLe(): string
    {
        return $this->{$this->fnDoGetBinaryBitsetLe}();
    }

    /**
     * Get binary bitset in little-endian order.
     *
     * This is the binary bitset implementation.
     *
     * @return string
     * @see getBinaryBitsetLe()
     * @see doGetBinaryBitsetLeInt()
     */
    private function doGetBinaryBitsetLeBin()
    {
        /** @var string $bitset */
        $bitset = $this->bitset;

        return $bitset;
    }

    /**
     * Get binary bitset in little-endian order.
     *
     * This is the integer bitset implementation.
     *
     * @return string
     * @see getBinaryBitsetLe()
     * @see doGetBinaryBitsetLeBin()
     */
    private function doGetBinaryBitsetLeInt()
    {
        $bin = \pack(\PHP_INT_SIZE === 8 ? 'P' : 'V', $this->bitset);
        return \substr($bin, 0, (int)\ceil($this->enumerationCount / 8));
    }

    /**
     * Get binary bitset in big-endian order
     *
     * @return string
     */
    public function getBinaryBitsetBe(): string
    {
        return \strrev($this->getBinaryBitsetLe());
    }

    /**
     * Get a bit at the given ordinal number
     *
     * @param int $ordinal Ordinal number of bit to get
     * @return bool
     * @throws InvalidArgumentException If the given ordinal number is out-of-range
     * @uses doGetBitBin()
     * @uses doGetBitInt()
     */
    public function getBit(int $ordinal): bool
    {
        if ($ordinal < 0 || $ordinal > $this->enumerationCount) {
            throw new InvalidArgumentException("Ordinal number must be between 0 and {$this->enumerationCount}");
        }

        return $this->{$this->fnDoGetBit}($ordinal);
    }

    /**
     * Get a bit at the given ordinal number.
     *
     * This is the binary bitset implementation.
     *
     * @param int $ordinal Ordinal number of bit to get
     * @return bool
     * @see getBit()
     * @see doGetBitInt()
     */
    private function doGetBitBin($ordinal)
    {
        /** @var string $bitset */
        $bitset = $this->bitset;

        return (\ord($bitset[(int) ($ordinal / 8)]) & 1 << ($ordinal % 8)) !== 0;
    }

    /**
     * Get a bit at the given ordinal number.
     *
     * This is the integer bitset implementation.
     *
     * @param int $ordinal Ordinal number of bit to get
     * @return bool
     * @see getBit()
     * @see doGetBitBin()
     */
    private function doGetBitInt($ordinal)
    {
        /** @var int $bitset */
        $bitset = $this->bitset;

        return (bool)($bitset & (1 << $ordinal));
    }
}
Copyright (c) 2020, Marc Bennewitz
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 organisation 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 HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
<?php

if (\PHP_VERSION_ID < 80000) {
    interface Stringable
    {
        /**
         * @return string
         */
        public function __toString();
    }
}
The MIT License (MIT)

Copyright (c) 2013-2016 container-interop
Copyright (c) 2016 PHP Framework Interoperability Group

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
<?php

declare(strict_types=1);

namespace Psr\Container;

/**
 * Describes the interface of a container that exposes methods to read its entries.
 */
interface ContainerInterface
{
    /**
     * Finds an entry of the container by its identifier and returns it.
     *
     * @param string $id Identifier of the entry to look for.
     *
     * @throws NotFoundExceptionInterface  No entry was found for **this** identifier.
     * @throws ContainerExceptionInterface Error while retrieving the entry.
     *
     * @return mixed Entry.
     */
    public function get(string $id);

    /**
     * Returns true if the container can return an entry for the given identifier.
     * Returns false otherwise.
     *
     * `has($id)` returning true does not mean that `get($id)` will not throw an exception.
     * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
     *
     * @param string $id Identifier of the entry to look for.
     *
     * @return bool
     */
    public function has(string $id): bool;
}
<?php

namespace Psr\Container;

use Throwable;

/**
 * Base interface representing a generic exception in a container.
 */
interface ContainerExceptionInterface extends Throwable
{
}
<?php

namespace Psr\Container;

/**
 * No entry was found in the container.
 */
interface NotFoundExceptionInterface extends ContainerExceptionInterface
{
}
MIT License

Copyright (c) 2018 PHP-FIG

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.
<?php
declare(strict_types=1);

namespace Psr\EventDispatcher;

/**
 * Defines a dispatcher for events.
 */
interface EventDispatcherInterface
{
    /**
     * Provide all relevant listeners with an event to process.
     *
     * @param object $event
     *   The object to process.
     *
     * @return object
     *   The Event that was passed, now modified by listeners.
     */
    public function dispatch(object $event);
}
<?php
declare(strict_types=1);

namespace Psr\EventDispatcher;

/**
 * An Event whose processing may be interrupted when the event has been handled.
 *
 * A Dispatcher implementation MUST check to determine if an Event
 * is marked as stopped after each listener is called.  If it is then it should
 * return immediately without calling any further Listeners.
 */
interface StoppableEventInterface
{
    /**
     * Is propagation stopped?
     *
     * This will typically only be used by the Dispatcher to determine if the
     * previous listener halted propagation.
     *
     * @return bool
     *   True if the Event is complete and no further listeners should be called.
     *   False to continue calling listeners.
     */
    public function isPropagationStopped() : bool;
}
<?php
declare(strict_types=1);

namespace Psr\EventDispatcher;

/**
 * Mapper from an event to the listeners that are applicable to that event.
 */
interface ListenerProviderInterface
{
    /**
     * @param object $event
     *   An event for which to return the relevant listeners.
     * @return iterable[callable]
     *   An iterable (array, iterator, or generator) of callables.  Each
     *   callable MUST be type-compatible with $event.
     */
    public function getListenersForEvent(object $event) : iterable;
}
Copyright (c) 2012 PHP Framework Interoperability Group

Permission is hereby granted, free of charge, to any person obtaining a copy 
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights 
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
copies of the Software, and to permit persons to whom the Software is 
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in 
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php

namespace Psr\Log;

/**
 * Describes log levels.
 */
class LogLevel
{
    const EMERGENCY = 'emergency';
    const ALERT     = 'alert';
    const CRITICAL  = 'critical';
    const ERROR     = 'error';
    const WARNING   = 'warning';
    const NOTICE    = 'notice';
    const INFO      = 'info';
    const DEBUG     = 'debug';
}
<?php

namespace Psr\Log;

/**
 * This Logger can be used to avoid conditional log calls.
 *
 * Logging should always be optional, and if no logger is provided to your
 * library creating a NullLogger instance to have something to throw logs at
 * is a good way to avoid littering your code with `if ($this->logger) { }`
 * blocks.
 */
class NullLogger extends AbstractLogger
{
    /**
     * Logs with an arbitrary level.
     *
     * @param mixed[] $context
     *
     * @throws \Psr\Log\InvalidArgumentException
     */
    public function log($level, string|\Stringable $message, array $context = []): void
    {
        // noop
    }
}
<?php

namespace Psr\Log;

/**
 * This is a simple Logger trait that classes unable to extend AbstractLogger
 * (because they extend another class, etc) can include.
 *
 * It simply delegates all log-level-specific methods to the `log` method to
 * reduce boilerplate code that a simple Logger that does the same thing with
 * messages regardless of the error level has to implement.
 */
trait LoggerTrait
{
    /**
     * System is unusable.
     */
    public function emergency(string|\Stringable $message, array $context = []): void
    {
        $this->log(LogLevel::EMERGENCY, $message, $context);
    }

    /**
     * Action must be taken immediately.
     *
     * Example: Entire website down, database unavailable, etc. This should
     * trigger the SMS alerts and wake you up.
     */
    public function alert(string|\Stringable $message, array $context = []): void
    {
        $this->log(LogLevel::ALERT, $message, $context);
    }

    /**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     */
    public function critical(string|\Stringable $message, array $context = []): void
    {
        $this->log(LogLevel::CRITICAL, $message, $context);
    }

    /**
     * Runtime errors that do not require immediate action but should typically
     * be logged and monitored.
     */
    public function error(string|\Stringable $message, array $context = []): void
    {
        $this->log(LogLevel::ERROR, $message, $context);
    }

    /**
     * Exceptional occurrences that are not errors.
     *
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
     * that are not necessarily wrong.
     */
    public function warning(string|\Stringable $message, array $context = []): void
    {
        $this->log(LogLevel::WARNING, $message, $context);
    }

    /**
     * Normal but significant events.
     */
    public function notice(string|\Stringable $message, array $context = []): void
    {
        $this->log(LogLevel::NOTICE, $message, $context);
    }

    /**
     * Interesting events.
     *
     * Example: User logs in, SQL logs.
     */
    public function info(string|\Stringable $message, array $context = []): void
    {
        $this->log(LogLevel::INFO, $message, $context);
    }

    /**
     * Detailed debug information.
     */
    public function debug(string|\Stringable $message, array $context = []): void
    {
        $this->log(LogLevel::DEBUG, $message, $context);
    }

    /**
     * Logs with an arbitrary level.
     *
     * @param mixed $level
     *
     * @throws \Psr\Log\InvalidArgumentException
     */
    abstract public function log($level, string|\Stringable $message, array $context = []): void;
}
<?php

namespace Psr\Log;

/**
 * This is a simple Logger implementation that other Loggers can inherit from.
 *
 * It simply delegates all log-level-specific methods to the `log` method to
 * reduce boilerplate code that a simple Logger that does the same thing with
 * messages regardless of the error level has to implement.
 */
abstract class AbstractLogger implements LoggerInterface
{
    use LoggerTrait;
}
<?php

namespace Psr\Log;

/**
 * Basic Implementation of LoggerAwareInterface.
 */
trait LoggerAwareTrait
{
    /**
     * The logger instance.
     */
    protected ?LoggerInterface $logger = null;

    /**
     * Sets a logger.
     */
    public function setLogger(LoggerInterface $logger): void
    {
        $this->logger = $logger;
    }
}
<?php

namespace Psr\Log;

/**
 * Describes a logger instance.
 *
 * The message MUST be a string or object implementing __toString().
 *
 * The message MAY contain placeholders in the form: {foo} where foo
 * will be replaced by the context data in key "foo".
 *
 * The context array can contain arbitrary data. The only assumption that
 * can be made by implementors is that if an Exception instance is given
 * to produce a stack trace, it MUST be in a key named "exception".
 *
 * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
 * for the full interface specification.
 */
interface LoggerInterface
{
    /**
     * System is unusable.
     *
     * @param mixed[] $context
     */
    public function emergency(string|\Stringable $message, array $context = []): void;

    /**
     * Action must be taken immediately.
     *
     * Example: Entire website down, database unavailable, etc. This should
     * trigger the SMS alerts and wake you up.
     *
     * @param mixed[] $context
     */
    public function alert(string|\Stringable $message, array $context = []): void;

    /**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     *
     * @param mixed[] $context
     */
    public function critical(string|\Stringable $message, array $context = []): void;

    /**
     * Runtime errors that do not require immediate action but should typically
     * be logged and monitored.
     *
     * @param mixed[] $context
     */
    public function error(string|\Stringable $message, array $context = []): void;

    /**
     * Exceptional occurrences that are not errors.
     *
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
     * that are not necessarily wrong.
     *
     * @param mixed[] $context
     */
    public function warning(string|\Stringable $message, array $context = []): void;

    /**
     * Normal but significant events.
     *
     * @param mixed[] $context
     */
    public function notice(string|\Stringable $message, array $context = []): void;

    /**
     * Interesting events.
     *
     * Example: User logs in, SQL logs.
     *
     * @param mixed[] $context
     */
    public function info(string|\Stringable $message, array $context = []): void;

    /**
     * Detailed debug information.
     *
     * @param mixed[] $context
     */
    public function debug(string|\Stringable $message, array $context = []): void;

    /**
     * Logs with an arbitrary level.
     *
     * @param mixed $level
     * @param mixed[] $context
     *
     * @throws \Psr\Log\InvalidArgumentException
     */
    public function log($level, string|\Stringable $message, array $context = []): void;
}
<?php

namespace Psr\Log;

class InvalidArgumentException extends \InvalidArgumentException
{
}
<?php

namespace Psr\Log;

/**
 * Describes a logger-aware instance.
 */
interface LoggerAwareInterface
{
    /**
     * Sets a logger instance on the object.
     */
    public function setLogger(LoggerInterface $logger): void;
}
<?php

namespace Psr\SimpleCache;

/**
 * Interface used for all types of exceptions thrown by the implementing library.
 */
interface CacheException extends \Throwable
{
}
<?php

namespace Psr\SimpleCache;

interface CacheInterface
{
    /**
     * Fetches a value from the cache.
     *
     * @param string $key     The unique key of this item in the cache.
     * @param mixed  $default Default value to return if the key does not exist.
     *
     * @return mixed The value of the item from the cache, or $default in case of cache miss.
     *
     * @throws \Psr\SimpleCache\InvalidArgumentException
     *   MUST be thrown if the $key string is not a legal value.
     */
    public function get(string $key, mixed $default = null): mixed;

    /**
     * Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time.
     *
     * @param string                 $key   The key of the item to store.
     * @param mixed                  $value The value of the item to store, must be serializable.
     * @param null|int|\DateInterval $ttl   Optional. The TTL value of this item. If no value is sent and
     *                                      the driver supports TTL then the library may set a default value
     *                                      for it or let the driver take care of that.
     *
     * @return bool True on success and false on failure.
     *
     * @throws \Psr\SimpleCache\InvalidArgumentException
     *   MUST be thrown if the $key string is not a legal value.
     */
    public function set(string $key, mixed $value, null|int|\DateInterval $ttl = null): bool;

    /**
     * Delete an item from the cache by its unique key.
     *
     * @param string $key The unique cache key of the item to delete.
     *
     * @return bool True if the item was successfully removed. False if there was an error.
     *
     * @throws \Psr\SimpleCache\InvalidArgumentException
     *   MUST be thrown if the $key string is not a legal value.
     */
    public function delete(string $key): bool;

    /**
     * Wipes clean the entire cache's keys.
     *
     * @return bool True on success and false on failure.
     */
    public function clear(): bool;

    /**
     * Obtains multiple cache items by their unique keys.
     *
     * @param iterable<string> $keys    A list of keys that can be obtained in a single operation.
     * @param mixed            $default Default value to return for keys that do not exist.
     *
     * @return iterable<string, mixed> A list of key => value pairs. Cache keys that do not exist or are stale will have $default as value.
     *
     * @throws \Psr\SimpleCache\InvalidArgumentException
     *   MUST be thrown if $keys is neither an array nor a Traversable,
     *   or if any of the $keys are not a legal value.
     */
    public function getMultiple(iterable $keys, mixed $default = null): iterable;

    /**
     * Persists a set of key => value pairs in the cache, with an optional TTL.
     *
     * @param iterable               $values A list of key => value pairs for a multiple-set operation.
     * @param null|int|\DateInterval $ttl    Optional. The TTL value of this item. If no value is sent and
     *                                       the driver supports TTL then the library may set a default value
     *                                       for it or let the driver take care of that.
     *
     * @return bool True on success and false on failure.
     *
     * @throws \Psr\SimpleCache\InvalidArgumentException
     *   MUST be thrown if $values is neither an array nor a Traversable,
     *   or if any of the $values are not a legal value.
     */
    public function setMultiple(iterable $values, null|int|\DateInterval $ttl = null): bool;

    /**
     * Deletes multiple cache items in a single operation.
     *
     * @param iterable<string> $keys A list of string-based keys to be deleted.
     *
     * @return bool True if the items were successfully removed. False if there was an error.
     *
     * @throws \Psr\SimpleCache\InvalidArgumentException
     *   MUST be thrown if $keys is neither an array nor a Traversable,
     *   or if any of the $keys are not a legal value.
     */
    public function deleteMultiple(iterable $keys): bool;

    /**
     * Determines whether an item is present in the cache.
     *
     * NOTE: It is recommended that has() is only to be used for cache warming type purposes
     * and not to be used within your live applications operations for get/set, as this method
     * is subject to a race condition where your has() will return true and immediately after,
     * another script can remove it making the state of your app out of date.
     *
     * @param string $key The cache item key.
     *
     * @return bool
     *
     * @throws \Psr\SimpleCache\InvalidArgumentException
     *   MUST be thrown if the $key string is not a legal value.
     */
    public function has(string $key): bool;
}
<?php

namespace Psr\SimpleCache;

/**
 * Exception interface for invalid cache arguments.
 *
 * When an invalid argument is passed it must throw an exception which implements
 * this interface
 */
interface InvalidArgumentException extends CacheException
{
}
<?php

/**
 * This file contains all the functions that could not be dealt with automatically using the code generator.
 * If you add a function in this list, do not forget to add it in the generator/config/specialCasesFunctions.php
 *
 */

namespace Safe;

use Safe\Exceptions\ExecException;
use Safe\Exceptions\FtpException;
use Safe\Exceptions\MiscException;
use Safe\Exceptions\PosixException;
use Safe\Exceptions\SocketsException;
use Safe\Exceptions\ApcuException;
use Safe\Exceptions\JsonException;
use Safe\Exceptions\OpensslException;
use Safe\Exceptions\PcreException;
use Safe\Exceptions\SimplexmlException;
use Safe\Exceptions\FilesystemException;
use Safe\Exceptions\HashException;

use const PREG_NO_ERROR;

/**
 * Wrapper for json_decode that throws when an error occurs.
 *
 * @param string $json    JSON data to parse
 * @param bool|null $associative  true for arrays, false for objects, null to defer to $flags
 * @param int<1, max> $depth   User specified recursion depth.
 * @param int $flags Bitmask of JSON decode options.
 *
 * @return mixed
 * @throws JsonException if the JSON cannot be decoded.
 * @link http://www.php.net/manual/en/function.json-decode.php
 */
function json_decode(string $json, ?bool $associative = null, int $depth = 512, int $flags = 0): mixed
{
    $data = \json_decode($json, $associative, $depth, $flags);
    if (!($flags & JSON_THROW_ON_ERROR) && JSON_ERROR_NONE !== json_last_error()) {
        throw JsonException::createFromPhpError();
    }
    return $data;
}

/**
 * Fetches an entry from the cache.
 *
 * @param string|string[] $key The key used to store the value (with
 * apcu_store). If an array is passed then each
 * element is fetched and returned.
 * @return mixed The stored variable or array of variables on success
 * @throws ApcuException
 *
 */
function apcu_fetch($key)
{
    error_clear_last();
    $result = \apcu_fetch($key, $success);
    if ($success === false) {
        throw ApcuException::createFromPhpError();
    }
    return $result;
}

/**
 * Searches subject for matches to
 * pattern and replaces them with
 * replacement.
 *
 * @param string[]|string $pattern The pattern to search for. It can be either a string or an array with
 * strings.
 *
 * Several PCRE modifiers
 * are also available.
 * @param string[]|string $replacement The string or an array with strings to replace. If this parameter is a
 * string and the pattern parameter is an array,
 * all patterns will be replaced by that string. If both
 * pattern and replacement
 * parameters are arrays, each pattern will be
 * replaced by the replacement counterpart. If
 * there are fewer elements in the replacement
 * array than in the pattern array, any extra
 * patterns will be replaced by an empty string.
 *
 * replacement may contain references of the form
 * \\n or
 * $n, with the latter form
 * being the preferred one. Every such reference will be replaced by the text
 * captured by the n'th parenthesized pattern.
 * n can be from 0 to 99, and
 * \\0 or $0 refers to the text matched
 * by the whole pattern. Opening parentheses are counted from left to right
 * (starting from 1) to obtain the number of the capturing subpattern.
 * To use backslash in replacement, it must be doubled
 * ("\\\\" PHP string).
 *
 * When working with a replacement pattern where a backreference is
 * immediately followed by another number (i.e.: placing a literal number
 * immediately after a matched pattern), you cannot use the familiar
 * \\1 notation for your backreference.
 * \\11, for example, would confuse
 * preg_replace since it does not know whether you
 * want the \\1 backreference followed by a literal
 * 1, or the \\11 backreference
 * followed by nothing.  In this case the solution is to use
 * ${1}1.  This creates an isolated
 * $1 backreference, leaving the 1
 * as a literal.
 *
 * When using the deprecated e modifier, this function escapes
 * some characters (namely ', ",
 * \ and NULL) in the strings that replace the
 * backreferences. This is done to ensure that no syntax errors arise
 * from backreference usage with either single or double quotes (e.g.
 * 'strlen(\'$1\')+strlen("$2")'). Make sure you are
 * aware of PHP's string
 * syntax to know exactly how the interpreted string will look.
 * @param string|array|string[] $subject The string or an array with strings to search and replace.
 *
 * If subject is an array, then the search and
 * replace is performed on every entry of subject,
 * and the return value is an array as well.
 * @param int $limit The maximum possible replacements for each pattern in each
 * subject string. Defaults to
 * -1 (no limit).
 * @param int $count If specified, this variable will be filled with the number of
 * replacements done.
 * @param-out int $count
 *
 * @return string|array|string[] preg_replace returns an array if the
 * subject parameter is an array, or a string
 * otherwise.
 *
 * If matches are found, the new subject will
 * be returned, otherwise subject will be
 * returned unchanged.
 *
 * @throws PcreException
 *
 */
function preg_replace($pattern, $replacement, $subject, int $limit = -1, ?int &$count = null)
{
    error_clear_last();
    $result = \preg_replace($pattern, $replacement, $subject, $limit, $count);
    if (preg_last_error() !== PREG_NO_ERROR || $result === null) {
        throw PcreException::createFromPhpError();
    }
    return $result;
}

/**
 * Encrypts given data with given method and key, returns a raw
 * or base64 encoded string
 *
 * @param string $data The plaintext message data to be encrypted.
 * @param string $method The cipher method. For a list of available cipher methods, use openssl_get_cipher_methods.
 * @param string $key The key.
 * @param int $options options is a bitwise disjunction of the flags
 * OPENSSL_RAW_DATA and
 * OPENSSL_ZERO_PADDING.
 * @param string $iv A non-NULL Initialization Vector.
 * @param string $tag The authentication tag passed by reference when using AEAD cipher mode (GCM or CCM).
 * @param string $aad Additional authentication data.
 * @param int $tag_length The length of the authentication tag. Its value can be between 4 and 16 for GCM mode.
 * @return string Returns the encrypted string.
 * @throws OpensslException
 *
 */
function openssl_encrypt(string $data, string $method, string $key, int $options = 0, string $iv = "", ?string &$tag = "", string $aad = "", int $tag_length = 16): string
{
    error_clear_last();
    // The $tag parameter is handled in a weird way by openssl_encrypt. It cannot be provided unless encoding is AEAD
    if (func_num_args() <= 5) {
        $result = \openssl_encrypt($data, $method, $key, $options, $iv);
    } else {
        $result = \openssl_encrypt($data, $method, $key, $options, $iv, $tag, $aad, $tag_length);
    }
    if ($result === false) {
        throw OpensslException::createFromPhpError();
    }
    return $result;
}

/**
 * The function socket_write writes to the
 * socket from the given
 * buffer.
 *
 * @param \Socket $socket
 * @param string $buffer The buffer to be written.
 * @param int $length The optional parameter length can specify an
 * alternate length of bytes written to the socket. If this length is
 * greater than the buffer length, it is silently truncated to the length
 * of the buffer.
 * @return int Returns the number of bytes successfully written to the socket.
 * The error code can be retrieved with
 * socket_last_error. This code may be passed to
 * socket_strerror to get a textual explanation of the
 * error.
 * @throws SocketsException
 *
 */
function socket_write(\Socket $socket, string $buffer, int $length = 0): int
{
    error_clear_last();
    $result = $length === 0 ? \socket_write($socket, $buffer) : \socket_write($socket, $buffer, $length);
    if ($result === false) {
        throw SocketsException::createFromPhpError();
    }
    return $result;
}

/**
 * This function takes a node of a DOM
 * document and makes it into a SimpleXML node. This new object can
 * then be used as a native SimpleXML element.
 *
 * @param \DOMNode $node A DOM Element node
 * @param string $class_name You may use this optional parameter so that
 * simplexml_import_dom will return an object of
 * the specified class. That class should extend the
 * SimpleXMLElement class.
 * @return \SimpleXMLElement Returns a SimpleXMLElement.
 * @throws SimplexmlException
 *
 */
function simplexml_import_dom(\DOMNode $node, string $class_name = \SimpleXMLElement::class): \SimpleXMLElement
{
    error_clear_last();
    $result = \simplexml_import_dom($node, $class_name);
    if ($result === null) {
        throw SimplexmlException::createFromPhpError();
    }
    return $result;
}

/**
 * Convert the well-formed XML document in the given file to an object.
 *
 * @param string $filename Path to the XML file
 * @param string $class_name You may use this optional parameter so that
 * simplexml_load_file will return an object of
 * the specified class. That class should extend the
 * SimpleXMLElement class.
 * @param int $options Since Libxml 2.6.0, you may also use the
 * options parameter to specify additional Libxml parameters.
 * @param string $namespace_or_prefix Namespace prefix or URI.
 * @param bool $is_prefix TRUE if namespace_or_prefix is a prefix, FALSE if it's a URI;
 * defaults to FALSE.
 * @return \SimpleXMLElement Returns an object of class SimpleXMLElement with
 * properties containing the data held within the XML document.
 * @throws SimplexmlException
 *
 */
function simplexml_load_file(string $filename, string $class_name = \SimpleXMLElement::class, int $options = 0, string $namespace_or_prefix = "", bool $is_prefix = false): \SimpleXMLElement
{
    error_clear_last();
    $result = \simplexml_load_file($filename, $class_name, $options, $namespace_or_prefix, $is_prefix);
    if ($result === false) {
        throw SimplexmlException::createFromPhpError();
    }
    return $result;
}


/**
 * Takes a well-formed XML string and returns it as an object.
 *
 * @param string $data A well-formed XML string
 * @param string $class_name You may use this optional parameter so that
 * simplexml_load_string will return an object of
 * the specified class. That class should extend the
 * SimpleXMLElement class.
 * @param int $options Since Libxml 2.6.0, you may also use the
 * options parameter to specify additional Libxml parameters.
 * @param string $namespace_or_prefix Namespace prefix or URI.
 * @param bool $is_prefix TRUE if namespace_or_prefix is a prefix, FALSE if it's a URI;
 * defaults to FALSE.
 * @return \SimpleXMLElement Returns an object of class SimpleXMLElement with
 * properties containing the data held within the xml document.
 * @throws SimplexmlException
 *
 */
function simplexml_load_string(string $data, string $class_name = \SimpleXMLElement::class, int $options = 0, string $namespace_or_prefix = "", bool $is_prefix = false): \SimpleXMLElement
{
    error_clear_last();
    $result = \simplexml_load_string($data, $class_name, $options, $namespace_or_prefix, $is_prefix);
    if ($result === false) {
        throw SimplexmlException::createFromPhpError();
    }
    return $result;
}

/**
 * Returns three samples representing the average system load
 * (the number of processes in the system run queue) over the last 1, 5 and 15
 * minutes, respectively. Returns FALSE on failure.
 *
 * @return array<int,float> Returns an array with three samples (last 1, 5 and 15
 * minutes).
 * @throws MiscException
 *
 */
function sys_getloadavg(): array
{
    error_clear_last();
    $result = \sys_getloadavg();
    if ($result === false) {
        throw MiscException::createFromPhpError();
    }
    return $result;
}

/**
 * Returns the process group identifier of the process
 * process_id.
 *
 * @param int $process_id The process id.
 * @return int Returns the identifier, as an int.
 * @throws PosixException
 *
 */
function posix_getpgid(int $process_id): int
{
    error_clear_last();
    $result = \posix_getpgid($process_id);
    if ($result === false) {
        throw PosixException::createFromPhpError();
    }
    return $result;
}


/**
 * fputcsv formats a line (passed as a
 * fields array) as CSV and writes it (terminated by a
 * newline) to the specified file stream.
 *
 * @param resource $stream The file pointer must be valid, and must point to
 * a file successfully opened by fopen or
 * fsockopen (and not yet closed by
 * fclose).
 * @phpstan-param (scalar|\Stringable|null)[] $fields
 * @param array $fields An array of strings.
 * @param string $separator The optional separator parameter sets the field
 * delimiter (one single-byte character only).
 * @param string $enclosure The optional enclosure parameter sets the field
 * enclosure (one single-byte character only).
 * @param string $escape The optional escape parameter sets the
 * escape character (at most one single-byte character).
 * An empty string ("") disables the proprietary escape mechanism.
 * @param string $eol The optional eol parameter sets
 * a custom End of Line sequence.
 * @return int Returns the length of the written string.
 * @throws FilesystemException
 *
 */
function fputcsv($stream, array $fields, string $separator = ",", string $enclosure = "\"", string $escape = "\\", string $eol = "\n"): int
{
    error_clear_last();
    $result = \fputcsv($stream, $fields, $separator, $enclosure, $escape, $eol);

    if ($result === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $result;
}

/**
 * Similar to fgets except that
 * fgetcsv parses the line it reads for fields in
 * CSV format and returns an array containing the fields
 * read.
 *
 * @param resource $stream A valid file pointer to a file successfully opened by
 * fopen, popen, or
 * fsockopen.
 * @param int<0, max>|null $length Must be greater than the longest line (in characters) to be found in
 * the CSV file (allowing for trailing line-end characters). Otherwise the
 * line is split in chunks of length characters,
 * unless the split would occur inside an enclosure.
 *
 * Omitting this parameter (or setting it to 0,
 * or NULL in PHP 8.0.0 or later) the maximum line length is not limited,
 * which is slightly slower.
 * @param string $separator The optional separator parameter sets the field separator (one single-byte character only).
 * @param string $enclosure The optional enclosure parameter sets the field enclosure character (one single-byte character only).
 * @param string $escape The optional escape parameter sets the escape character (at most one single-byte character).
 * An empty string ("") disables the proprietary escape mechanism.
 * @return mixed[]|false Returns an indexed array containing the fields read on success or false when there is no more lines.
 * @throws FilesystemException
 *
 */
function fgetcsv($stream, ?int $length = null, string $separator = ",", string $enclosure = "\"", string $escape = "\\"): array|false
{
    error_clear_last();
    $safeResult = \fgetcsv($stream, $length, $separator, $enclosure, $escape);
    if ($safeResult === false && \feof($stream) === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}

/**
 * The passthru function is similar to the
 * exec function in that it executes a
 * command. This function
 * should be used in place of exec or
 * system when the output from the Unix command
 * is binary data which needs to be passed directly back to the
 * browser.  A common use for this is to execute something like the
 * pbmplus utilities that can output an image stream directly.  By
 * setting the Content-type to image/gif and
 * then calling a pbmplus program to output a gif, you can create
 * PHP scripts that output images directly.
 *
 * @param string $command The command that will be executed.
 * @param int|null $result_code If the result_code argument is present, the
 * return status of the Unix command will be placed here.
 * @throws ExecException
 *
 */
function passthru(string $command, ?int &$result_code = null): void
{
    error_clear_last();

    $safeResult = \passthru($command, $result_code);
    if ($safeResult === false) {
        throw ExecException::createFromPhpError();
    }
}

/**
 *
 *
 * @param string $algo Name of selected hashing algorithm (e.g. "sha256").
 * For a list of supported algorithms see hash_algos.
 * @param string $filename URL describing location of file to be hashed; Supports fopen wrappers.
 * @param bool $binary When set to TRUE, outputs raw binary data.
 * FALSE outputs lowercase hexits.
 * @phpstan-param array<string, mixed> $options
 * @param array $options An array of options for the various hashing algorithms.
 * Currently, only the "seed" parameter is
 * supported by the MurmurHash variants.
 * @return non-falsy-string Returns a string containing the calculated message digest as lowercase hexits
 * unless binary is set to true in which case the raw
 * binary representation of the message digest is returned.
 * @throws HashException
 *
 */
function hash_file(string $algo, string $filename, bool $binary = false, array $options = []): string
{
    error_clear_last();
    $safeResult = \hash_file($algo, $filename, $binary, $options);
    if ($safeResult === false) {
        throw HashException::createFromPhpError();
    }
    return $safeResult;
}

/**
 *
 *
 * @param string $algo Name of selected hashing algorithm (e.g. "sha256").
 * For a list of supported algorithms see hash_hmac_algos.
 *
 *
 * Non-cryptographic hash functions are not allowed.
 *
 *
 *
 * Non-cryptographic hash functions are not allowed.
 * @param string $filename URL describing location of file to be hashed; Supports fopen wrappers.
 * @param string $key Shared secret key used for generating the HMAC variant of the message digest.
 * @param bool $binary When set to TRUE, outputs raw binary data.
 * FALSE outputs lowercase hexits.
 * @return non-falsy-string Returns a string containing the calculated message digest as lowercase hexits
 * unless binary is set to true in which case the raw
 * binary representation of the message digest is returned.
 * Returns FALSE if the file filename cannot be read.
 * @throws HashException
 *
 */
function hash_hmac_file(string $algo, string $filename, string $key, bool $binary = false): string
{
    error_clear_last();
    $safeResult = \hash_hmac_file($algo, $filename, $key, $binary);
    if ($safeResult === false) {
        throw HashException::createFromPhpError();
    }
    return $safeResult;
}

/**
 * Sends an arbitrary command to the FTP server.
 *
 * @param \FTP\Connection $ftp An FTP\Connection instance.
 * @param string $command The command to execute.
 * @phpstan-return string[]
 * @return array Returns the server's response as an array of strings.
 * No parsing is performed on the response string, nor does
 * ftp_raw determine if the command succeeded.
 * @throws FtpException
 *
 */
function ftp_raw(\FTP\Connection $ftp, string $command): array
{
    error_clear_last();
    $safeResult = \ftp_raw($ftp, $command);
    if ($safeResult === null) {
        throw FtpException::createFromPhpError();
    }
    return $safeResult;
}

/**
 * Creates a PHP value from a stored representation
 *
 * @param string $data <p>
 * The serialized string.
 *
 * If the variable being unserialized is an object, after successfully
 * reconstructing the object PHP will automatically attempt to call the
 * __wakeup member function (if it exists).
 *
 * unserialize_callback_func directive
 *
 * It's possible to set a callback-function which will be called,
 * if an undefined class should be instantiated during unserializing.
 * (to prevent getting an incomplete object "__PHP_Incomplete_Class".)
 * Use your "php.ini", ini_set or ".htaccess"
 * to define 'unserialize_callback_func'. Everytime an undefined class
 * should be instantiated, it'll be called. To disable this feature just
 * empty this setting.
 *
 * @param mixed[] $options [optional]
 * Any options to be provided to unserialize(), as an associative array.
 *
 * The 'allowed_classes' option key may be set to a value that is
 * either an array of class names which should be accepted, FALSE to
 * accept no classes, or TRUE to accept all classes. If this option is defined
 * and unserialize() encounters an object of a class that isn't to be accepted,
 * then the object will be instantiated as __PHP_Incomplete_Class instead.
 * Omitting this option is the same as defining it as TRUE: PHP will attempt
 * to instantiate objects of any class.
 *
 * @return mixed The converted value is returned, and can be a boolean,
 * integer, float, string, array or object.
 *
 * In case the passed value is not unserializeable, an \ErrorException will
 * be thrown.
 */
function unserialize(string $data, array $options = []): mixed
{
    error_clear_last();

    $previous = set_error_handler(function ($severity, $message, $file, $line) use (&$previous) {
        $unserialize_error_msg_prefix = 'unserialize():';
        if (str_starts_with($message, $unserialize_error_msg_prefix)) {
            throw new \ErrorException($message, 0, $severity, $file, $line);
        }

        if (!$previous) {
            return false;
        }

        return $previous($severity, $message, $file, $line);
    });

    try {
        return \unserialize($data, $options);
    } finally {
        restore_error_handler();
    }
}
<?php

namespace Safe\Exceptions;

class PcreException extends \Exception implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $errorMap = [
            PREG_INTERNAL_ERROR => 'PREG_INTERNAL_ERROR: Internal error',
            PREG_BACKTRACK_LIMIT_ERROR => 'PREG_BACKTRACK_LIMIT_ERROR: Backtrack limit reached',
            PREG_RECURSION_LIMIT_ERROR => 'PREG_RECURSION_LIMIT_ERROR: Recursion limit reached',
            PREG_BAD_UTF8_ERROR => 'PREG_BAD_UTF8_ERROR: Invalid UTF8 character',
            PREG_BAD_UTF8_OFFSET_ERROR => 'PREG_BAD_UTF8_OFFSET_ERROR',
            PREG_JIT_STACKLIMIT_ERROR => 'PREG_JIT_STACKLIMIT_ERROR',
        ];
        $errMsg = $errorMap[preg_last_error()] ?? 'Unknown PCRE error: ' . preg_last_error();
        return new self($errMsg, \preg_last_error());
    }
}
<?php

namespace Safe\Exceptions;

class SimplexmlException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = \error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php

namespace Safe\Exceptions;

class OpensslException extends \Exception implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        return new self(\openssl_error_string() ?: '', 0);
    }
}
<?php

namespace Safe\Exceptions;

interface SafeExceptionInterface extends \Throwable
{
}
<?php

namespace Safe\Exceptions;

class JsonException extends \JsonException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        return new self(\json_last_error_msg(), \json_last_error());
    }
}
<?php

namespace Safe\Exceptions;

class CurlException extends \Exception implements SafeExceptionInterface
{
    /**
     * @param \CurlHandle $ch
     */
    public static function createFromPhpError($ch = null): self
    {
        return new self($ch ? \curl_error($ch) : '', $ch ? \curl_errno($ch) : 0);
    }
}
<?php

namespace Safe;

use Safe\Exceptions\DatetimeException;

/**
 * This class is used to implement a safe version of the DatetimeImmutable class.
 * While it technically overloads \DateTimeImmutable for typehint compatibility,
 * it is actually used as a wrapper of \DateTimeImmutable, mostly to be able to overwrite functions like getTimestamp() while still being able to edit milliseconds via setTime().
 */
class DateTimeImmutable extends \DateTimeImmutable
{
    /**
     * @var \DateTimeImmutable
     */
    private $innerDateTime;

    /**
     * DateTimeImmutable constructor.
     * @param string $time
     * @param \DateTimeZone|null $timezone
     * @throws \Exception
     */
    public function __construct($time = 'now', $timezone = null)
    {
        parent::__construct($time, $timezone);
        $this->innerDateTime = new parent($time, $timezone);
    }

    //switch between regular datetime and safe version
    public static function createFromRegular(\DateTimeImmutable $datetime): self
    {
        $safeDatetime = new self($datetime->format('Y-m-d H:i:s.u'), $datetime->getTimezone()); //we need to also update the wrapper to not break the operators '<' and '>'
        $safeDatetime->innerDateTime = $datetime; //to make sure we don't lose information because of the format().
        return $safeDatetime;
    }

    //useful if you need to switch back to regular DateTimeImmutable (for example when using DatePeriod)
    public function getInnerDateTime(): \DateTimeImmutable
    {
        return $this->innerDateTime;
    }

    /////////////////////////////////////////////////////////////////////////////
    // overload functions with false errors

    /**
     * @param string $format
     * @param string $time
     * @param \DateTimeZone|null $timezone
     * @throws DatetimeException
     */
    public static function createFromFormat($format, $time, $timezone = null): self
    {
        $datetime = \DateTimeImmutable::createFromFormat($format, $time, $timezone);
        if ($datetime === false) {
            throw DatetimeException::createFromPhpError();
        }
        return self::createFromRegular($datetime);
    }

    /**
     * @param string $format
     * @return string
     * @throws DatetimeException
     */
    public function format($format): string
    {
        /** @var string|false $result */
        $result = $this->innerDateTime->format($format);
        if ($result === false) {
            throw DatetimeException::createFromPhpError();
        }
        return $result;
    }

    /**
     * @param \DateTimeInterface $datetime2
     * @param bool $absolute
     * @return \DateInterval
     * @throws DatetimeException
     */
    public function diff($datetime2, $absolute = false): \DateInterval
    {
        /** @var \DateInterval|false $result */
        $result = $this->innerDateTime->diff($datetime2, $absolute);
        if ($result === false) {
            throw DatetimeException::createFromPhpError();
        }
        return $result;
    }

    /**
     * @param string $modify
     * @return DateTimeImmutable
     * @throws DatetimeException
     */
    public function modify($modify): self
    {
        /** @var \DateTimeImmutable|false $result */
        $result = $this->innerDateTime->modify($modify);
        if ($result === false) {
            throw DatetimeException::createFromPhpError();
        }
        return self::createFromRegular($result); //we have to recreate a safe datetime because modify create a new instance of \DateTimeImmutable
    }

    /**
     * @param int $year
     * @param int $month
     * @param int $day
     * @return DateTimeImmutable
     * @throws DatetimeException
     */
    public function setDate($year, $month, $day): self
    {
        /** @var \DateTimeImmutable|false $result */
        $result = $this->innerDateTime->setDate($year, $month, $day);
        if ($result === false) {
            throw DatetimeException::createFromPhpError();
        }
        return self::createFromRegular($result); //we have to recreate a safe datetime because modify create a new instance of \DateTimeImmutable
    }

    /**
     * @param int $year
     * @param int $week
     * @param int $day
     * @return DateTimeImmutable
     * @throws DatetimeException
     */
    public function setISODate($year, $week, $day = 1): self
    {
        /** @var \DateTimeImmutable|false $result */
        $result = $this->innerDateTime->setISODate($year, $week, $day);
        if ($result === false) {
            throw DatetimeException::createFromPhpError();
        }
        return self::createFromRegular($result); //we have to recreate a safe datetime because modify create a new instance of \DateTimeImmutable
    }

    /**
     * @param int $hour
     * @param int $minute
     * @param int $second
     * @param int $microseconds
     * @return DateTimeImmutable
     * @throws DatetimeException
     */
    public function setTime($hour, $minute, $second = 0, $microseconds = 0): self
    {
        /** @var \DateTimeImmutable|false $result */
        $result = $this->innerDateTime->setTime($hour, $minute, $second, $microseconds);
        if ($result === false) {
            throw DatetimeException::createFromPhpError();
        }
        return self::createFromRegular($result);
    }

    /**
     * @param int $unixtimestamp
     * @return DateTimeImmutable
     * @throws DatetimeException
     */
    public function setTimestamp($unixtimestamp): self
    {
        /** @var \DateTimeImmutable|false $result */
        $result = $this->innerDateTime->setTimestamp($unixtimestamp);
        if ($result === false) {
            throw DatetimeException::createFromPhpError();
        }
        return self::createFromRegular($result);
    }

    /**
     * @param \DateTimeZone $timezone
     * @return DateTimeImmutable
     * @throws DatetimeException
     */
    public function setTimezone($timezone): self
    {
        /** @var \DateTimeImmutable|false $result */
        $result = $this->innerDateTime->setTimezone($timezone);
        if ($result === false) {
            throw DatetimeException::createFromPhpError();
        }
        return self::createFromRegular($result);
    }

    /**
     * @param \DateInterval $interval
     * @return DateTimeImmutable
     * @throws DatetimeException
     */
    public function sub($interval): self
    {
        /** @var \DateTimeImmutable|false $result */
        $result = $this->innerDateTime->sub($interval);
        if ($result === false) {
            throw DatetimeException::createFromPhpError();
        }
        return self::createFromRegular($result);
    }

    /**
     * @throws DatetimeException
     */
    public function getOffset(): int
    {
        /** @var int|false $result */
        $result = $this->innerDateTime->getOffset();
        if ($result === false) {
            throw DatetimeException::createFromPhpError();
        }
        return $result;
    }

    //////////////////////////////////////////////////////////////////////////////////////////
    //overload getters to use the inner datetime immutable instead of itself

    /**
     * @param \DateInterval $interval
     * @return DateTimeImmutable
     */
    public function add($interval): self
    {
        return self::createFromRegular($this->innerDateTime->add($interval));
    }

    /**
     * @param \DateTime $dateTime
     * @return DateTimeImmutable
     */
    #[\ReturnTypeWillChange]
    public static function createFromMutable($dateTime): self
    {
        $date = \DateTimeImmutable::createFromMutable($dateTime);

        return self::createFromRegular($date);
    }

    /**
     * @return DateTimeImmutable
     */
    public static function createFromInterface(\DateTimeInterface $object): self
    {
        if ($object instanceof \DateTime) {
            $object = self::createFromMutable($object);
        } elseif ($object instanceof DateTimeImmutable) {
            $object = $object->getInnerDateTime();
        }
        return self::createFromRegular($object);
    }

    /**
     * @param mixed[] $array
     * @return DateTimeImmutable
     */
    public static function __set_state($array): self
    {
        return self::createFromRegular(parent::__set_state($array));
    }

    public function getTimezone(): \DateTimeZone
    {
        return $this->innerDateTime->getTimezone();
    }

    public function getTimestamp(): int
    {
        return $this->innerDateTime->getTimestamp();
    }
}
<?php

namespace Safe;

use DateInterval;
use DateTimeInterface;
use DateTimeZone;
use Safe\Exceptions\DatetimeException;

/** this class implements a safe version of the Datetime class */
class DateTime extends \DateTime
{
    //switch from regular datetime to safe version
    private static function createFromRegular(\DateTime $datetime): self
    {
        return new self($datetime->format('Y-m-d H:i:s.u'), $datetime->getTimezone());
    }

    /**
     * @param string $format
     * @param string $time
     * @param DateTimeZone|null $timezone
     * @throws DatetimeException
     */
    public static function createFromFormat($format, $time, $timezone = null): self
    {
        $datetime = \DateTime::createFromFormat($format, $time, $timezone);
        if ($datetime === false) {
            throw DatetimeException::createFromPhpError();
        }
        return self::createFromRegular($datetime);
    }

    /**
     * @param DateTimeInterface $datetime2 The date to compare to.
     * @param boolean $absolute [optional] Whether to return absolute difference.
     * @return DateInterval The DateInterval object representing the difference between the two dates.
     * @throws DatetimeException
     */
    public function diff($datetime2, $absolute = false): DateInterval
    {
        /** @var \DateInterval|false $result */
        $result = parent::diff($datetime2, $absolute);
        if ($result === false) {
            throw DatetimeException::createFromPhpError();
        }
        return $result;
    }

    /**
     * @param string $modify A date/time string. Valid formats are explained in <a href="https://secure.php.net/manual/en/datetime.formats.php">Date and Time Formats</a>.
     * @return DateTime Returns the DateTime object for method chaining.
     * @throws DatetimeException
     */
    public function modify($modify): self
    {
        /** @var DateTime|false $result */
        $result = parent::modify($modify);
        if ($result === false) {
            throw DatetimeException::createFromPhpError();
        }
        return $result;
    }

    /**
     * @param int $year
     * @param int $month
     * @param int $day
     * @return DateTime
     * @throws DatetimeException
     */
    public function setDate($year, $month, $day): self
    {
        /** @var DateTime|false $result */
        $result = parent::setDate($year, $month, $day);
        if ($result === false) {
            throw DatetimeException::createFromPhpError();
        }
        return $result;
    }
}
MIT License

Copyright (c) 2018 TheCodingMachine

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.<?php

if (strpos(PHP_VERSION, "8.1.") === 0) {
    return require_once __DIR__ . '/generated/8.1/rector-migrate.php';
}
if (strpos(PHP_VERSION, "8.2.") === 0) {
    return require_once __DIR__ . '/generated/8.2/rector-migrate.php';
}
if (strpos(PHP_VERSION, "8.3.") === 0) {
    return require_once __DIR__ . '/generated/8.3/rector-migrate.php';
}
if (strpos(PHP_VERSION, "8.4.") === 0) {
    return require_once __DIR__ . '/generated/8.4/rector-migrate.php';
}
if (strpos(PHP_VERSION, "8.5.") === 0) {
    return require_once __DIR__ . '/generated/8.5/rector-migrate.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/libxml.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/libxml.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/libxml.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/libxml.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/libxml.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/libxml.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/uodbc.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/uodbc.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/uodbc.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.4/uodbc.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.5/uodbc.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.5/uodbc.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/xdiff.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/xdiff.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/xdiff.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/xdiff.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.5/xdiff.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.5/xdiff.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/pgsql.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/pgsql.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/pgsql.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.4/pgsql.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.4/pgsql.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.4/pgsql.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/inotify.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/inotify.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/inotify.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.4/inotify.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.4/inotify.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.4/inotify.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/uopz.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/uopz.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/uopz.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/uopz.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/uopz.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/uopz.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/datetime.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.2/datetime.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.2/datetime.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.4/datetime.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.4/datetime.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.4/datetime.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/info.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/info.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/info.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.4/info.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.4/info.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.4/info.php';
}
<?php
namespace Safe\Exceptions;

class PgsqlException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class IbmDb2Exception extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class SolrException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class PsException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class ComException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class InotifyException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class CubridException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class Oci8Exception extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class DatetimeException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class SodiumException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class SocketsException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class ArrayException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class UopzException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class HashException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class RrdException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class XdiffException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class FtpException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class RnpException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class ZlibException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class LzfException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class ShmopException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class IconvException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class GnupgException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class NetworkException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class IbaseException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class GmpException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class PosixException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class MailparseException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class ClassobjException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class MiscException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class SsdeepException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class FileinfoException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class RpminfoException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class FpmException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class FilesystemException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class SplException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class EioException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class SessionException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class LdapException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class ReadlineException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class LibxmlException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class UodbcException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class ApacheException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class OpcacheException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class SemException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class FunchandException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class FilterException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class YamlException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class StringsException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class XmlException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class InfoException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class PspellException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class ExecException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class DirException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class Bzip2Exception extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class GettextException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class MysqlException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class CalendarException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class SqlsrvException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class XmlrpcException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class ImageException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class ApcuException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class PcntlException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class StreamException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class YazException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class ImapException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class OutcontrolException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class MysqliException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class VarException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class ErrorfuncException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class UrlException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class ZipException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class Ssh2Exception extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class MbstringException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php
namespace Safe\Exceptions;

class SwooleException extends \ErrorException implements SafeExceptionInterface
{
    public static function createFromPhpError(): self
    {
        $error = error_get_last();
        return new self($error['message'] ?? 'An error occurred', 0, $error['type'] ?? 1);
    }
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/openssl.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.2/openssl.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.2/openssl.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.4/openssl.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.4/openssl.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.6/openssl.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/pcntl.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/pcntl.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/pcntl.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/pcntl.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.5/pcntl.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.6/pcntl.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/stream.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.2/stream.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.2/stream.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.4/stream.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.4/stream.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.4/stream.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/fpm.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.2/fpm.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.2/fpm.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.2/fpm.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.2/fpm.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.2/fpm.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/apcu.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/apcu.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/apcu.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/apcu.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/apcu.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/apcu.php';
}
<?php

namespace Safe;

use Safe\Exceptions\OpensslException;

/**
 * @param string $cipher_algo
 * @return int
 * @throws OpensslException
 *
 */
function openssl_cipher_iv_length(string $cipher_algo): int
{
    error_clear_last();
    $safeResult = \openssl_cipher_iv_length($cipher_algo);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $cipher_algo
 * @return int
 * @throws OpensslException
 *
 */
function openssl_cipher_key_length(string $cipher_algo): int
{
    error_clear_last();
    $safeResult = \openssl_cipher_key_length($cipher_algo);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $input_filename
 * @param string $output_filename
 * @param \OpenSSLCertificate|string $certificate
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|null|string $private_key
 * @param int $encoding
 * @throws OpensslException
 *
 */
function openssl_cms_decrypt(string $input_filename, string $output_filename, $certificate, $private_key = null, int $encoding = OPENSSL_ENCODING_SMIME): void
{
    error_clear_last();
    if ($encoding !== OPENSSL_ENCODING_SMIME) {
        $safeResult = \openssl_cms_decrypt($input_filename, $output_filename, $certificate, $private_key, $encoding);
    } elseif ($private_key !== null) {
        $safeResult = \openssl_cms_decrypt($input_filename, $output_filename, $certificate, $private_key);
    } else {
        $safeResult = \openssl_cms_decrypt($input_filename, $output_filename, $certificate);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $input_filename
 * @param string $output_filename
 * @param \OpenSSLCertificate|array|string $certificate
 * @param array|null $headers
 * @param int $flags
 * @param int $encoding
 * @param int|string $cipher_algo
 * @throws OpensslException
 *
 */
function openssl_cms_encrypt(string $input_filename, string $output_filename, $certificate, ?array $headers, int $flags = 0, int $encoding = OPENSSL_ENCODING_SMIME, $cipher_algo = OPENSSL_CIPHER_AES_128_CBC): void
{
    error_clear_last();
    $safeResult = \openssl_cms_encrypt($input_filename, $output_filename, $certificate, $headers, $flags, $encoding, $cipher_algo);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $input_filename
 * @param array $certificates
 * @throws OpensslException
 *
 */
function openssl_cms_read(string $input_filename, array &$certificates): void
{
    error_clear_last();
    $safeResult = \openssl_cms_read($input_filename, $certificates);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $input_filename
 * @param string $output_filename
 * @param \OpenSSLCertificate|string $certificate
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param array|null $headers
 * @param int $flags
 * @param int $encoding
 * @param null|string $untrusted_certificates_filename
 * @throws OpensslException
 *
 */
function openssl_cms_sign(string $input_filename, string $output_filename, $certificate, $private_key, ?array $headers, int $flags = 0, int $encoding = OPENSSL_ENCODING_SMIME, ?string $untrusted_certificates_filename = null): void
{
    error_clear_last();
    if ($untrusted_certificates_filename !== null) {
        $safeResult = \openssl_cms_sign($input_filename, $output_filename, $certificate, $private_key, $headers, $flags, $encoding, $untrusted_certificates_filename);
    } else {
        $safeResult = \openssl_cms_sign($input_filename, $output_filename, $certificate, $private_key, $headers, $flags, $encoding);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $input_filename
 * @param int $flags
 * @param null|string $certificates
 * @param array $ca_info
 * @param null|string $untrusted_certificates_filename
 * @param null|string $content
 * @param null|string $pk7
 * @param null|string $sigfile
 * @param int $encoding
 * @throws OpensslException
 *
 */
function openssl_cms_verify(string $input_filename, int $flags = 0, ?string $certificates = null, array $ca_info = [], ?string $untrusted_certificates_filename = null, ?string $content = null, ?string $pk7 = null, ?string $sigfile = null, int $encoding = OPENSSL_ENCODING_SMIME): void
{
    error_clear_last();
    if ($encoding !== OPENSSL_ENCODING_SMIME) {
        $safeResult = \openssl_cms_verify($input_filename, $flags, $certificates, $ca_info, $untrusted_certificates_filename, $content, $pk7, $sigfile, $encoding);
    } elseif ($sigfile !== null) {
        $safeResult = \openssl_cms_verify($input_filename, $flags, $certificates, $ca_info, $untrusted_certificates_filename, $content, $pk7, $sigfile);
    } elseif ($pk7 !== null) {
        $safeResult = \openssl_cms_verify($input_filename, $flags, $certificates, $ca_info, $untrusted_certificates_filename, $content, $pk7);
    } elseif ($content !== null) {
        $safeResult = \openssl_cms_verify($input_filename, $flags, $certificates, $ca_info, $untrusted_certificates_filename, $content);
    } elseif ($untrusted_certificates_filename !== null) {
        $safeResult = \openssl_cms_verify($input_filename, $flags, $certificates, $ca_info, $untrusted_certificates_filename);
    } elseif ($ca_info !== []) {
        $safeResult = \openssl_cms_verify($input_filename, $flags, $certificates, $ca_info);
    } elseif ($certificates !== null) {
        $safeResult = \openssl_cms_verify($input_filename, $flags, $certificates);
    } else {
        $safeResult = \openssl_cms_verify($input_filename, $flags);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLCertificateSigningRequest|string $csr
 * @param string $output_filename
 * @param bool $no_text
 * @throws OpensslException
 *
 */
function openssl_csr_export_to_file($csr, string $output_filename, bool $no_text = true): void
{
    error_clear_last();
    $safeResult = \openssl_csr_export_to_file($csr, $output_filename, $no_text);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLCertificateSigningRequest|string $csr
 * @param null|string $output
 * @param bool $no_text
 * @throws OpensslException
 *
 */
function openssl_csr_export($csr, ?string &$output, bool $no_text = true): void
{
    error_clear_last();
    $safeResult = \openssl_csr_export($csr, $output, $no_text);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLCertificateSigningRequest|string $csr
 * @param bool $short_names
 * @return \OpenSSLAsymmetricKey
 * @throws OpensslException
 *
 */
function openssl_csr_get_public_key($csr, bool $short_names = true): \OpenSSLAsymmetricKey
{
    error_clear_last();
    $safeResult = \openssl_csr_get_public_key($csr, $short_names);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLCertificateSigningRequest|string $csr
 * @param bool $short_names
 * @return array
 * @throws OpensslException
 *
 */
function openssl_csr_get_subject($csr, bool $short_names = true): array
{
    error_clear_last();
    $safeResult = \openssl_csr_get_subject($csr, $short_names);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param array $distinguished_names
 * @param \OpenSSLAsymmetricKey|null $private_key
 * @param array|null $options
 * @param array|null $extra_attributes
 * @return \OpenSSLCertificateSigningRequest|bool
 * @throws OpensslException
 *
 */
function openssl_csr_new(array $distinguished_names, ?\OpenSSLAsymmetricKey &$private_key, ?array $options = null, ?array $extra_attributes = null)
{
    error_clear_last();
    if ($extra_attributes !== null) {
        $safeResult = \openssl_csr_new($distinguished_names, $private_key, $options, $extra_attributes);
    } elseif ($options !== null) {
        $safeResult = \openssl_csr_new($distinguished_names, $private_key, $options);
    } else {
        $safeResult = \openssl_csr_new($distinguished_names, $private_key);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLCertificateSigningRequest|string $csr
 * @param \OpenSSLCertificate|null|string $ca_certificate
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param int $days
 * @param array|null $options
 * @param int $serial
 * @param null|string $serial_hex
 * @return \OpenSSLCertificate
 * @throws OpensslException
 *
 */
function openssl_csr_sign($csr, $ca_certificate, $private_key, int $days, ?array $options = null, int $serial = 0, ?string $serial_hex = null): \OpenSSLCertificate
{
    error_clear_last();
    if ($serial_hex !== null) {
        $safeResult = \openssl_csr_sign($csr, $ca_certificate, $private_key, $days, $options, $serial, $serial_hex);
    } elseif ($serial !== 0) {
        $safeResult = \openssl_csr_sign($csr, $ca_certificate, $private_key, $days, $options, $serial);
    } elseif ($options !== null) {
        $safeResult = \openssl_csr_sign($csr, $ca_certificate, $private_key, $days, $options);
    } else {
        $safeResult = \openssl_csr_sign($csr, $ca_certificate, $private_key, $days);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param string $cipher_algo
 * @param string $passphrase
 * @param int $options
 * @param string $iv
 * @param null|string $tag
 * @param string $aad
 * @return string
 * @throws OpensslException
 *
 */
function openssl_decrypt(string $data, string $cipher_algo, string $passphrase, int $options = 0, string $iv = "", ?string $tag = null, string $aad = ""): string
{
    error_clear_last();
    if ($aad !== "") {
        $safeResult = \openssl_decrypt($data, $cipher_algo, $passphrase, $options, $iv, $tag, $aad);
    } elseif ($tag !== null) {
        $safeResult = \openssl_decrypt($data, $cipher_algo, $passphrase, $options, $iv, $tag);
    } else {
        $safeResult = \openssl_decrypt($data, $cipher_algo, $passphrase, $options, $iv);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $public_key
 * @param \OpenSSLAsymmetricKey $private_key
 * @return string
 * @throws OpensslException
 *
 */
function openssl_dh_compute_key(string $public_key, \OpenSSLAsymmetricKey $private_key): string
{
    error_clear_last();
    $safeResult = \openssl_dh_compute_key($public_key, $private_key);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param string $digest_algo
 * @param bool $binary
 * @return string
 * @throws OpensslException
 *
 */
function openssl_digest(string $data, string $digest_algo, bool $binary = false): string
{
    error_clear_last();
    $safeResult = \openssl_digest($data, $digest_algo, $binary);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return list
 * @throws OpensslException
 *
 */
function openssl_get_curve_names(): array
{
    error_clear_last();
    $safeResult = \openssl_get_curve_names();
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param null|string $output
 * @param string $encrypted_key
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param string $cipher_algo
 * @param null|string $iv
 * @throws OpensslException
 *
 */
function openssl_open(string $data, ?string &$output, string $encrypted_key, $private_key, string $cipher_algo, ?string $iv = null): void
{
    error_clear_last();
    if ($iv !== null) {
        $safeResult = \openssl_open($data, $output, $encrypted_key, $private_key, $cipher_algo, $iv);
    } else {
        $safeResult = \openssl_open($data, $output, $encrypted_key, $private_key, $cipher_algo);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $password
 * @param string $salt
 * @param int $key_length
 * @param int $iterations
 * @param string $digest_algo
 * @return string
 * @throws OpensslException
 *
 */
function openssl_pbkdf2(string $password, string $salt, int $key_length, int $iterations, string $digest_algo = "sha1"): string
{
    error_clear_last();
    $safeResult = \openssl_pbkdf2($password, $salt, $key_length, $iterations, $digest_algo);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLCertificate|string $certificate
 * @param string $output_filename
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param string $passphrase
 * @param array $options
 * @throws OpensslException
 *
 */
function openssl_pkcs12_export_to_file($certificate, string $output_filename, $private_key, string $passphrase, array $options = []): void
{
    error_clear_last();
    $safeResult = \openssl_pkcs12_export_to_file($certificate, $output_filename, $private_key, $passphrase, $options);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLCertificate|string $certificate
 * @param null|string $output
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param string $passphrase
 * @param array $options
 * @throws OpensslException
 *
 */
function openssl_pkcs12_export($certificate, ?string &$output, $private_key, string $passphrase, array $options = []): void
{
    error_clear_last();
    $safeResult = \openssl_pkcs12_export($certificate, $output, $private_key, $passphrase, $options);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $pkcs12
 * @param array|null $certificates
 * @param string $passphrase
 * @throws OpensslException
 *
 */
function openssl_pkcs12_read(string $pkcs12, ?array &$certificates, string $passphrase): void
{
    error_clear_last();
    $safeResult = \openssl_pkcs12_read($pkcs12, $certificates, $passphrase);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $input_filename
 * @param string $output_filename
 * @param \OpenSSLCertificate|string $certificate
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|null|string $private_key
 * @throws OpensslException
 *
 */
function openssl_pkcs7_decrypt(string $input_filename, string $output_filename, $certificate, $private_key = null): void
{
    error_clear_last();
    if ($private_key !== null) {
        $safeResult = \openssl_pkcs7_decrypt($input_filename, $output_filename, $certificate, $private_key);
    } else {
        $safeResult = \openssl_pkcs7_decrypt($input_filename, $output_filename, $certificate);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $input_filename
 * @param string $output_filename
 * @param \OpenSSLCertificate|array|string $certificate
 * @param array $headers
 * @param int $flags
 * @param int $cipher_algo
 * @throws OpensslException
 *
 */
function openssl_pkcs7_encrypt(string $input_filename, string $output_filename, $certificate, array $headers, int $flags = 0, int $cipher_algo = OPENSSL_CIPHER_AES_128_CBC): void
{
    error_clear_last();
    $safeResult = \openssl_pkcs7_encrypt($input_filename, $output_filename, $certificate, $headers, $flags, $cipher_algo);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $data
 * @param array|null $certificates
 * @throws OpensslException
 *
 */
function openssl_pkcs7_read(string $data, ?array &$certificates): void
{
    error_clear_last();
    $safeResult = \openssl_pkcs7_read($data, $certificates);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $input_filename
 * @param string $output_filename
 * @param \OpenSSLCertificate|string $certificate
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param array $headers
 * @param int $flags
 * @param null|string $untrusted_certificates_filename
 * @throws OpensslException
 *
 */
function openssl_pkcs7_sign(string $input_filename, string $output_filename, $certificate, $private_key, array $headers, int $flags = PKCS7_DETACHED, ?string $untrusted_certificates_filename = null): void
{
    error_clear_last();
    if ($untrusted_certificates_filename !== null) {
        $safeResult = \openssl_pkcs7_sign($input_filename, $output_filename, $certificate, $private_key, $headers, $flags, $untrusted_certificates_filename);
    } else {
        $safeResult = \openssl_pkcs7_sign($input_filename, $output_filename, $certificate, $private_key, $headers, $flags);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $public_key
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param int $key_length
 * @return string
 * @throws OpensslException
 *
 */
function openssl_pkey_derive($public_key, $private_key, int $key_length = 0): string
{
    error_clear_last();
    $safeResult = \openssl_pkey_derive($public_key, $private_key, $key_length);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $key
 * @param string $output_filename
 * @param null|string $passphrase
 * @param array|null $options
 * @throws OpensslException
 *
 */
function openssl_pkey_export_to_file($key, string $output_filename, ?string $passphrase = null, ?array $options = null): void
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \openssl_pkey_export_to_file($key, $output_filename, $passphrase, $options);
    } elseif ($passphrase !== null) {
        $safeResult = \openssl_pkey_export_to_file($key, $output_filename, $passphrase);
    } else {
        $safeResult = \openssl_pkey_export_to_file($key, $output_filename);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $key
 * @param null|string $output
 * @param null|string $passphrase
 * @param array|null $options
 * @throws OpensslException
 *
 */
function openssl_pkey_export($key, ?string &$output, ?string $passphrase = null, ?array $options = null): void
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \openssl_pkey_export($key, $output, $passphrase, $options);
    } elseif ($passphrase !== null) {
        $safeResult = \openssl_pkey_export($key, $output, $passphrase);
    } else {
        $safeResult = \openssl_pkey_export($key, $output);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLAsymmetricKey $key
 * @return array
 * @throws OpensslException
 *
 */
function openssl_pkey_get_details(\OpenSSLAsymmetricKey $key): array
{
    error_clear_last();
    $safeResult = \openssl_pkey_get_details($key);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param null|string $passphrase
 * @return \OpenSSLAsymmetricKey
 * @throws OpensslException
 *
 */
function openssl_pkey_get_private($private_key, ?string $passphrase = null): \OpenSSLAsymmetricKey
{
    error_clear_last();
    if ($passphrase !== null) {
        $safeResult = \openssl_pkey_get_private($private_key, $passphrase);
    } else {
        $safeResult = \openssl_pkey_get_private($private_key);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $public_key
 * @return \OpenSSLAsymmetricKey
 * @throws OpensslException
 *
 */
function openssl_pkey_get_public($public_key): \OpenSSLAsymmetricKey
{
    error_clear_last();
    $safeResult = \openssl_pkey_get_public($public_key);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param array|null $options
 * @return \OpenSSLAsymmetricKey
 * @throws OpensslException
 *
 */
function openssl_pkey_new(?array $options = null): \OpenSSLAsymmetricKey
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \openssl_pkey_new($options);
    } else {
        $safeResult = \openssl_pkey_new();
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param null|string $decrypted_data
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param int $padding
 * @param null|string $digest_algo
 * @throws OpensslException
 *
 */
function openssl_private_decrypt(string $data, ?string &$decrypted_data, $private_key, int $padding = OPENSSL_PKCS1_PADDING, ?string $digest_algo = null): void
{
    error_clear_last();
    if ($digest_algo !== null) {
        $safeResult = \openssl_private_decrypt($data, $decrypted_data, $private_key, $padding, $digest_algo);
    } else {
        $safeResult = \openssl_private_decrypt($data, $decrypted_data, $private_key, $padding);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $data
 * @param null|string $encrypted_data
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param int $padding
 * @throws OpensslException
 *
 */
function openssl_private_encrypt(string $data, ?string &$encrypted_data, $private_key, int $padding = OPENSSL_PKCS1_PADDING): void
{
    error_clear_last();
    $safeResult = \openssl_private_encrypt($data, $encrypted_data, $private_key, $padding);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $data
 * @param null|string $decrypted_data
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $public_key
 * @param int $padding
 * @throws OpensslException
 *
 */
function openssl_public_decrypt(string $data, ?string &$decrypted_data, $public_key, int $padding = OPENSSL_PKCS1_PADDING): void
{
    error_clear_last();
    $safeResult = \openssl_public_decrypt($data, $decrypted_data, $public_key, $padding);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $data
 * @param null|string $encrypted_data
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $public_key
 * @param int $padding
 * @param null|string $digest_algo
 * @throws OpensslException
 *
 */
function openssl_public_encrypt(string $data, ?string &$encrypted_data, $public_key, int $padding = OPENSSL_PKCS1_PADDING, ?string $digest_algo = null): void
{
    error_clear_last();
    if ($digest_algo !== null) {
        $safeResult = \openssl_public_encrypt($data, $encrypted_data, $public_key, $padding, $digest_algo);
    } else {
        $safeResult = \openssl_public_encrypt($data, $encrypted_data, $public_key, $padding);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param int $length
 * @param bool|null $strong_result
 * @return string
 *
 */
function openssl_random_pseudo_bytes(int $length, ?bool &$strong_result = null): string
{
    error_clear_last();
    $safeResult = \openssl_random_pseudo_bytes($length, $strong_result);
    return $safeResult;
}


/**
 * @param string $data
 * @param null|string $sealed_data
 * @param array|null $encrypted_keys
 * @param array $public_key
 * @param string $cipher_algo
 * @param null|string $iv
 * @return int
 * @throws OpensslException
 *
 */
function openssl_seal(string $data, ?string &$sealed_data, ?array &$encrypted_keys, array $public_key, string $cipher_algo, ?string &$iv = null): int
{
    error_clear_last();
    $safeResult = \openssl_seal($data, $sealed_data, $encrypted_keys, $public_key, $cipher_algo, $iv);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param null|string $signature
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param int|string $algorithm
 * @param int $padding
 * @throws OpensslException
 *
 */
function openssl_sign(string $data, ?string &$signature, $private_key, $algorithm = OPENSSL_ALGO_SHA1, int $padding = 0): void
{
    error_clear_last();
    $safeResult = \openssl_sign($data, $signature, $private_key, $algorithm, $padding);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $spki
 * @return null|string
 * @throws OpensslException
 *
 */
function openssl_spki_export_challenge(string $spki): ?string
{
    error_clear_last();
    $safeResult = \openssl_spki_export_challenge($spki);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $spki
 * @return null|string
 * @throws OpensslException
 *
 */
function openssl_spki_export(string $spki): ?string
{
    error_clear_last();
    $safeResult = \openssl_spki_export($spki);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLAsymmetricKey $private_key
 * @param string $challenge
 * @param int $digest_algo
 * @return null|string
 * @throws OpensslException
 *
 */
function openssl_spki_new(\OpenSSLAsymmetricKey $private_key, string $challenge, int $digest_algo = OPENSSL_ALGO_MD5): ?string
{
    error_clear_last();
    $safeResult = \openssl_spki_new($private_key, $challenge, $digest_algo);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $spki
 * @throws OpensslException
 *
 */
function openssl_spki_verify(string $spki): void
{
    error_clear_last();
    $safeResult = \openssl_spki_verify($spki);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $data
 * @param string $signature
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $public_key
 * @param int|string $algorithm
 * @param int $padding
 * @return -1|0|1
 * @throws OpensslException
 *
 */
function openssl_verify(string $data, string $signature, $public_key, $algorithm = OPENSSL_ALGO_SHA1, int $padding = 0): int
{
    error_clear_last();
    $safeResult = \openssl_verify($data, $signature, $public_key, $algorithm, $padding);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLCertificate|string $certificate
 * @param int $purpose
 * @param array $ca_info
 * @param null|string $untrusted_certificates_file
 * @return bool|int
 * @throws OpensslException
 *
 */
function openssl_x509_checkpurpose($certificate, int $purpose, array $ca_info = [], ?string $untrusted_certificates_file = null)
{
    error_clear_last();
    if ($untrusted_certificates_file !== null) {
        $safeResult = \openssl_x509_checkpurpose($certificate, $purpose, $ca_info, $untrusted_certificates_file);
    } else {
        $safeResult = \openssl_x509_checkpurpose($certificate, $purpose, $ca_info);
    }
    if ($safeResult === -1) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLCertificate|string $certificate
 * @param string $output_filename
 * @param bool $no_text
 * @throws OpensslException
 *
 */
function openssl_x509_export_to_file($certificate, string $output_filename, bool $no_text = true): void
{
    error_clear_last();
    $safeResult = \openssl_x509_export_to_file($certificate, $output_filename, $no_text);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLCertificate|string $certificate
 * @param null|string $output
 * @param bool $no_text
 * @throws OpensslException
 *
 */
function openssl_x509_export($certificate, ?string &$output, bool $no_text = true): void
{
    error_clear_last();
    $safeResult = \openssl_x509_export($certificate, $output, $no_text);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLCertificate|string $certificate
 * @param string $digest_algo
 * @param bool $binary
 * @return string
 * @throws OpensslException
 *
 */
function openssl_x509_fingerprint($certificate, string $digest_algo = "sha1", bool $binary = false): string
{
    error_clear_last();
    $safeResult = \openssl_x509_fingerprint($certificate, $digest_algo, $binary);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLCertificate|string $certificate
 * @return \OpenSSLCertificate
 * @throws OpensslException
 *
 */
function openssl_x509_read($certificate): \OpenSSLCertificate
{
    error_clear_last();
    $safeResult = \openssl_x509_read($certificate);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\PcntlException;

/**
 * @param int|null $process_id
 * @return array
 * @throws PcntlException
 *
 */
function pcntl_getcpuaffinity(?int $process_id = null): array
{
    error_clear_last();
    if ($process_id !== null) {
        $safeResult = \pcntl_getcpuaffinity($process_id);
    } else {
        $safeResult = \pcntl_getcpuaffinity();
    }
    if ($safeResult === false) {
        throw PcntlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int|null $process_id
 * @param int $mode
 * @return int
 * @throws PcntlException
 *
 */
function pcntl_getpriority(?int $process_id = null, int $mode = PRIO_PROCESS): int
{
    error_clear_last();
    if ($mode !== PRIO_PROCESS) {
        $safeResult = \pcntl_getpriority($process_id, $mode);
    } elseif ($process_id !== null) {
        $safeResult = \pcntl_getpriority($process_id);
    } else {
        $safeResult = \pcntl_getpriority();
    }
    if ($safeResult === false) {
        throw PcntlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int|null $process_id
 * @param array $cpu_ids
 * @throws PcntlException
 *
 */
function pcntl_setcpuaffinity(?int $process_id = null, array $cpu_ids = []): void
{
    error_clear_last();
    if ($cpu_ids !== []) {
        $safeResult = \pcntl_setcpuaffinity($process_id, $cpu_ids);
    } elseif ($process_id !== null) {
        $safeResult = \pcntl_setcpuaffinity($process_id);
    } else {
        $safeResult = \pcntl_setcpuaffinity();
    }
    if ($safeResult === false) {
        throw PcntlException::createFromPhpError();
    }
}


/**
 * @param int $priority
 * @param int|null $process_id
 * @param int $mode
 * @throws PcntlException
 *
 */
function pcntl_setpriority(int $priority, ?int $process_id = null, int $mode = PRIO_PROCESS): void
{
    error_clear_last();
    if ($mode !== PRIO_PROCESS) {
        $safeResult = \pcntl_setpriority($priority, $process_id, $mode);
    } elseif ($process_id !== null) {
        $safeResult = \pcntl_setpriority($priority, $process_id);
    } else {
        $safeResult = \pcntl_setpriority($priority);
    }
    if ($safeResult === false) {
        throw PcntlException::createFromPhpError();
    }
}


/**
 * @throws PcntlException
 *
 */
function pcntl_signal_dispatch(): void
{
    error_clear_last();
    $safeResult = \pcntl_signal_dispatch();
    if ($safeResult === false) {
        throw PcntlException::createFromPhpError();
    }
}


/**
 * @param int $signal
 * @param callable|int $handler
 * @param bool $restart_syscalls
 * @throws PcntlException
 *
 */
function pcntl_signal(int $signal, $handler, bool $restart_syscalls = true): void
{
    error_clear_last();
    $safeResult = \pcntl_signal($signal, $handler, $restart_syscalls);
    if ($safeResult === false) {
        throw PcntlException::createFromPhpError();
    }
}


/**
 * @param int $mode
 * @param array $signals
 * @param array|null $old_signals
 * @throws PcntlException
 *
 */
function pcntl_sigprocmask(int $mode, array $signals, ?array &$old_signals = null): void
{
    error_clear_last();
    $safeResult = \pcntl_sigprocmask($mode, $signals, $old_signals);
    if ($safeResult === false) {
        throw PcntlException::createFromPhpError();
    }
}


/**
 * @param array $signals
 * @param array|null $info
 * @param int $seconds
 * @param int $nanoseconds
 * @return int
 * @throws PcntlException
 *
 */
function pcntl_sigtimedwait(array $signals, ?array &$info = [], int $seconds = 0, int $nanoseconds = 0): int
{
    error_clear_last();
    $safeResult = \pcntl_sigtimedwait($signals, $info, $seconds, $nanoseconds);
    if ($safeResult === false) {
        throw PcntlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param array $signals
 * @param array|null $info
 * @return int
 * @throws PcntlException
 *
 */
function pcntl_sigwaitinfo(array $signals, ?array &$info = []): int
{
    error_clear_last();
    $safeResult = \pcntl_sigwaitinfo($signals, $info);
    if ($safeResult === false) {
        throw PcntlException::createFromPhpError();
    }
    return $safeResult;
}
<?php

return [
    'apache_getenv',
    'apache_get_version',
    'apache_lookup_uri',
    'apache_setenv',
    'apcu_cache_info',
    'apcu_cas',
    'apcu_dec',
    'apcu_fetch',
    'apcu_inc',
    'apcu_sma_info',
    'base64_decode',
    'bindtextdomain',
    'bzclose',
    'bzflush',
    'bzopen',
    'bzread',
    'bzwrite',
    'chdir',
    'chgrp',
    'chmod',
    'chown',
    'chroot',
    'class_alias',
    'class_implements',
    'class_parents',
    'class_uses',
    'cli_set_process_title',
    'com_create_guid',
    'com_event_sink',
    'com_load_typelib',
    'com_print_typeinfo',
    'convert_uudecode',
    'copy',
    'cubrid_bind',
    'cubrid_column_names',
    'cubrid_column_types',
    'cubrid_col_size',
    'cubrid_commit',
    'cubrid_connect',
    'cubrid_connect_with_url',
    'cubrid_current_oid',
    'cubrid_disconnect',
    'cubrid_drop',
    'cubrid_free_result',
    'cubrid_get_charset',
    'cubrid_get_class_name',
    'cubrid_get_client_info',
    'cubrid_get_db_parameter',
    'cubrid_get_query_timeout',
    'cubrid_get_server_info',
    'cubrid_insert_id',
    'cubrid_lob2_bind',
    'cubrid_lob2_close',
    'cubrid_lob2_export',
    'cubrid_lob2_import',
    'cubrid_lob2_new',
    'cubrid_lob2_read',
    'cubrid_lob2_seek',
    'cubrid_lob2_seek64',
    'cubrid_lob2_size',
    'cubrid_lob2_size64',
    'cubrid_lob2_tell',
    'cubrid_lob2_tell64',
    'cubrid_lob2_write',
    'cubrid_lob_close',
    'cubrid_lob_export',
    'cubrid_lob_get',
    'cubrid_lob_send',
    'cubrid_lob_size',
    'cubrid_lock_read',
    'cubrid_lock_write',
    'cubrid_move_cursor',
    'cubrid_next_result',
    'cubrid_pconnect',
    'cubrid_pconnect_with_url',
    'cubrid_prepare',
    'cubrid_put',
    'cubrid_rollback',
    'cubrid_schema',
    'cubrid_seq_drop',
    'cubrid_seq_insert',
    'cubrid_seq_put',
    'cubrid_set_add',
    'cubrid_set_autocommit',
    'cubrid_set_db_parameter',
    'cubrid_set_drop',
    'cubrid_set_query_timeout',
    'curl_copy_handle',
    'curl_escape',
    'curl_exec',
    'curl_getinfo',
    'curl_init',
    'curl_multi_info_read',
    'curl_multi_setopt',
    'curl_setopt',
    'curl_share_setopt',
    'curl_unescape',
    'curl_upkeep',
    'date_create',
    'date_create_immutable',
    'date_parse_from_format',
    'date_sunrise',
    'date_sunset',
    'db2_autocommit',
    'db2_bind_param',
    'db2_client_info',
    'db2_close',
    'db2_commit',
    'db2_execute',
    'db2_free_result',
    'db2_free_stmt',
    'db2_get_option',
    'db2_num_rows',
    'db2_pclose',
    'db2_rollback',
    'db2_server_info',
    'db2_set_option',
    'define',
    'deflate_add',
    'deflate_init',
    'dir',
    'disk_free_space',
    'disk_total_space',
    'dl',
    'dns_get_record',
    'eio_busy',
    'eio_chmod',
    'eio_chown',
    'eio_close',
    'eio_custom',
    'eio_dup2',
    'eio_event_loop',
    'eio_fallocate',
    'eio_fchmod',
    'eio_fchown',
    'eio_fdatasync',
    'eio_fstat',
    'eio_fstatvfs',
    'eio_fsync',
    'eio_ftruncate',
    'eio_futime',
    'eio_grp',
    'eio_lstat',
    'eio_mkdir',
    'eio_mknod',
    'eio_nop',
    'eio_readahead',
    'eio_readdir',
    'eio_readlink',
    'eio_rename',
    'eio_rmdir',
    'eio_seek',
    'eio_sendfile',
    'eio_stat',
    'eio_statvfs',
    'eio_symlink',
    'eio_sync',
    'eio_syncfs',
    'eio_sync_file_range',
    'eio_truncate',
    'eio_unlink',
    'eio_utime',
    'eio_write',
    'error_log',
    'exec',
    'fastcgi_finish_request',
    'fbird_blob_cancel',
    'fclose',
    'fdatasync',
    'fflush',
    'fgetcsv',
    'file',
    'fileatime',
    'filectime',
    'fileinode',
    'filemtime',
    'fileowner',
    'fileperms',
    'filesize',
    'filetype',
    'file_get_contents',
    'file_put_contents',
    'filter_input_array',
    'filter_var_array',
    'finfo_close',
    'finfo_open',
    'flock',
    'fopen',
    'fpm_get_status',
    'fputcsv',
    'fread',
    'fsockopen',
    'fstat',
    'fsync',
    'ftell',
    'ftp_alloc',
    'ftp_append',
    'ftp_cdup',
    'ftp_chdir',
    'ftp_chmod',
    'ftp_close',
    'ftp_connect',
    'ftp_delete',
    'ftp_fget',
    'ftp_fput',
    'ftp_get',
    'ftp_login',
    'ftp_mkdir',
    'ftp_mlsd',
    'ftp_nb_get',
    'ftp_nb_put',
    'ftp_nlist',
    'ftp_pasv',
    'ftp_put',
    'ftp_pwd',
    'ftp_raw',
    'ftp_rename',
    'ftp_rmdir',
    'ftp_site',
    'ftp_size',
    'ftp_ssl_connect',
    'ftp_systype',
    'ftruncate',
    'fwrite',
    'getcwd',
    'gethostname',
    'getimagesize',
    'getlastmod',
    'getmygid',
    'getmyinode',
    'getmypid',
    'getmyuid',
    'getopt',
    'getprotobyname',
    'getprotobynumber',
    'getrusage',
    'getservbyport',
    'get_cfg_var',
    'get_headers',
    'get_include_path',
    'get_meta_tags',
    'glob',
    'gmmktime',
    'gmstrftime',
    'gnupg_adddecryptkey',
    'gnupg_addencryptkey',
    'gnupg_addsignkey',
    'gnupg_cleardecryptkeys',
    'gnupg_clearencryptkeys',
    'gnupg_clearsignkeys',
    'gnupg_deletekey',
    'gnupg_setarmor',
    'gnupg_setsignmode',
    'gzclose',
    'gzcompress',
    'gzdecode',
    'gzdeflate',
    'gzencode',
    'gzfile',
    'gzgets',
    'gzinflate',
    'gzopen',
    'gzread',
    'gzrewind',
    'gztell',
    'gzuncompress',
    'gzwrite',
    'hash_file',
    'hash_hmac_file',
    'hash_update_file',
    'header_register_callback',
    'hex2bin',
    'highlight_file',
    'hrtime',
    'ibase_add_user',
    'ibase_backup',
    'ibase_blob_cancel',
    'ibase_blob_create',
    'ibase_blob_get',
    'ibase_close',
    'ibase_commit',
    'ibase_commit_ret',
    'ibase_connect',
    'ibase_delete_user',
    'ibase_drop_db',
    'ibase_free_event_handler',
    'ibase_free_query',
    'ibase_free_result',
    'ibase_maintain_db',
    'ibase_modify_user',
    'ibase_name_result',
    'ibase_pconnect',
    'ibase_restore',
    'ibase_rollback',
    'ibase_rollback_ret',
    'ibase_service_attach',
    'ibase_service_detach',
    'iconv',
    'iconv_get_encoding',
    'iconv_mime_decode',
    'iconv_mime_encode',
    'iconv_set_encoding',
    'iconv_strlen',
    'idate',
    'imageaffine',
    'imageaffinematrixconcat',
    'imageaffinematrixget',
    'imagealphablending',
    'imageantialias',
    'imagearc',
    'imageavif',
    'imagebmp',
    'imagechar',
    'imagecharup',
    'imagecolorat',
    'imagecolordeallocate',
    'imagecolormatch',
    'imagecolorset',
    'imageconvolution',
    'imagecopy',
    'imagecopymerge',
    'imagecopymergegray',
    'imagecopyresampled',
    'imagecopyresized',
    'imagecreate',
    'imagecreatefromavif',
    'imagecreatefrombmp',
    'imagecreatefromgd',
    'imagecreatefromgd2',
    'imagecreatefromgd2part',
    'imagecreatefromgif',
    'imagecreatefromjpeg',
    'imagecreatefrompng',
    'imagecreatefromstring',
    'imagecreatefromtga',
    'imagecreatefromwbmp',
    'imagecreatefromwebp',
    'imagecreatefromxbm',
    'imagecreatefromxpm',
    'imagecreatetruecolor',
    'imagecrop',
    'imagecropauto',
    'imagedashedline',
    'imagedestroy',
    'imageellipse',
    'imagefill',
    'imagefilledarc',
    'imagefilledellipse',
    'imagefilledrectangle',
    'imagefilltoborder',
    'imagefilter',
    'imageflip',
    'imageftbbox',
    'imagefttext',
    'imagegammacorrect',
    'imagegd',
    'imagegd2',
    'imagegif',
    'imagegrabscreen',
    'imagegrabwindow',
    'imagejpeg',
    'imagelayereffect',
    'imageline',
    'imageloadfont',
    'imagepng',
    'imagerectangle',
    'imageresolution',
    'imagerotate',
    'imagesavealpha',
    'imagescale',
    'imagesetbrush',
    'imagesetclip',
    'imagesetinterpolation',
    'imagesetpixel',
    'imagesetstyle',
    'imagesetthickness',
    'imagesettile',
    'imagestring',
    'imagestringup',
    'imagetruecolortopalette',
    'imagettfbbox',
    'imagettftext',
    'imagewbmp',
    'imagewebp',
    'imagexbm',
    'image_type_to_extension',
    'imap_8bit',
    'imap_append',
    'imap_base64',
    'imap_binary',
    'imap_body',
    'imap_bodystruct',
    'imap_check',
    'imap_createmailbox',
    'imap_deletemailbox',
    'imap_fetchbody',
    'imap_fetchheader',
    'imap_fetchmime',
    'imap_fetchstructure',
    'imap_fetch_overview',
    'imap_getacl',
    'imap_getmailboxes',
    'imap_getsubscribed',
    'imap_headerinfo',
    'imap_headers',
    'imap_listscan',
    'imap_lsub',
    'imap_mail',
    'imap_mail_compose',
    'imap_mail_copy',
    'imap_mail_move',
    'imap_mime_header_decode',
    'imap_mutf7_to_utf8',
    'imap_num_msg',
    'imap_open',
    'imap_qprint',
    'imap_renamemailbox',
    'imap_rfc822_write_address',
    'imap_savebody',
    'imap_setacl',
    'imap_set_quota',
    'imap_sort',
    'imap_status',
    'imap_subscribe',
    'imap_thread',
    'imap_timeout',
    'imap_unsubscribe',
    'imap_utf8_to_mutf7',
    'inet_ntop',
    'inet_pton',
    'inflate_add',
    'inflate_get_read_len',
    'inflate_init',
    'ini_get',
    'ini_set',
    'inotify_add_watch',
    'inotify_init',
    'inotify_rm_watch',
    'iptcembed',
    'iptcparse',
    'json_decode',
    'json_encode',
    'lchgrp',
    'lchown',
    'ldap_8859_to_t61',
    'ldap_add',
    'ldap_bind',
    'ldap_compare',
    'ldap_control_paged_result',
    'ldap_control_paged_result_response',
    'ldap_count_entries',
    'ldap_delete',
    'ldap_dn2ufn',
    'ldap_exop',
    'ldap_exop_passwd',
    'ldap_exop_whoami',
    'ldap_explode_dn',
    'ldap_first_attribute',
    'ldap_first_entry',
    'ldap_free_result',
    'ldap_get_dn',
    'ldap_get_entries',
    'ldap_get_option',
    'ldap_get_values',
    'ldap_get_values_len',
    'ldap_modify_batch',
    'ldap_mod_add',
    'ldap_mod_del',
    'ldap_mod_replace',
    'ldap_next_attribute',
    'ldap_parse_exop',
    'ldap_parse_result',
    'ldap_rename',
    'ldap_sasl_bind',
    'ldap_set_option',
    'ldap_unbind',
    'libxml_set_external_entity_loader',
    'link',
    'lstat',
    'lzf_compress',
    'lzf_decompress',
    'mailparse_msg_extract_part_file',
    'mailparse_msg_free',
    'mailparse_msg_parse',
    'mailparse_msg_parse_file',
    'mailparse_stream_encode',
    'mb_chr',
    'mb_convert_encoding',
    'mb_convert_variables',
    'mb_detect_order',
    'mb_eregi_replace',
    'mb_ereg_replace',
    'mb_ereg_replace_callback',
    'mb_ereg_search_getregs',
    'mb_ereg_search_init',
    'mb_ereg_search_regs',
    'mb_ereg_search_setpos',
    'mb_get_info',
    'mb_http_output',
    'mb_internal_encoding',
    'mb_ord',
    'mb_parse_str',
    'mb_regex_encoding',
    'mb_send_mail',
    'mb_split',
    'md5_file',
    'mime_content_type',
    'mkdir',
    'mktime',
    'msg_get_queue',
    'msg_queue_exists',
    'msg_receive',
    'msg_remove_queue',
    'msg_send',
    'msg_set_queue',
    'msg_stat_queue',
    'mysql_close',
    'mysql_connect',
    'mysql_create_db',
    'mysql_data_seek',
    'mysql_db_name',
    'mysql_db_query',
    'mysql_drop_db',
    'mysql_fetch_lengths',
    'mysql_field_flags',
    'mysql_field_len',
    'mysql_field_name',
    'mysql_field_seek',
    'mysql_free_result',
    'mysql_get_host_info',
    'mysql_get_proto_info',
    'mysql_get_server_info',
    'mysql_info',
    'mysql_list_dbs',
    'mysql_list_fields',
    'mysql_list_processes',
    'mysql_list_tables',
    'mysql_num_fields',
    'mysql_num_rows',
    'mysql_query',
    'mysql_real_escape_string',
    'mysql_result',
    'mysql_select_db',
    'mysql_set_charset',
    'mysql_tablename',
    'mysql_thread_id',
    'mysql_unbuffered_query',
    'net_get_interfaces',
    'ob_clean',
    'ob_end_clean',
    'ob_end_flush',
    'ob_flush',
    'ob_get_clean',
    'ob_get_flush',
    'ob_start',
    'oci_bind_array_by_name',
    'oci_bind_by_name',
    'oci_cancel',
    'oci_commit',
    'oci_connect',
    'oci_define_by_name',
    'oci_execute',
    'oci_field_name',
    'oci_field_precision',
    'oci_field_scale',
    'oci_field_size',
    'oci_field_type',
    'oci_field_type_raw',
    'oci_free_descriptor',
    'oci_free_statement',
    'oci_new_collection',
    'oci_new_connect',
    'oci_new_cursor',
    'oci_new_descriptor',
    'oci_num_rows',
    'oci_parse',
    'oci_pconnect',
    'oci_register_taf_callback',
    'oci_result',
    'oci_rollback',
    'oci_server_version',
    'oci_set_action',
    'oci_set_call_timeout',
    'oci_set_client_identifier',
    'oci_set_client_info',
    'oci_set_db_operation',
    'oci_set_edition',
    'oci_set_module_name',
    'oci_set_prefetch',
    'oci_set_prefetch_lob',
    'oci_statement_type',
    'oci_unregister_taf_callback',
    'odbc_autocommit',
    'odbc_commit',
    'odbc_connect',
    'odbc_cursor',
    'odbc_data_source',
    'odbc_exec',
    'odbc_execute',
    'odbc_fetch_into',
    'odbc_field_len',
    'odbc_field_name',
    'odbc_field_num',
    'odbc_field_scale',
    'odbc_field_type',
    'odbc_num_fields',
    'odbc_pconnect',
    'odbc_prepare',
    'odbc_procedures',
    'odbc_result',
    'odbc_result_all',
    'odbc_rollback',
    'odbc_setoption',
    'odbc_tables',
    'opcache_compile_file',
    'opcache_get_status',
    'opendir',
    'openssl_cipher_iv_length',
    'openssl_cipher_key_length',
    'openssl_cms_decrypt',
    'openssl_cms_encrypt',
    'openssl_cms_read',
    'openssl_cms_sign',
    'openssl_cms_verify',
    'openssl_csr_export',
    'openssl_csr_export_to_file',
    'openssl_csr_get_public_key',
    'openssl_csr_get_subject',
    'openssl_csr_new',
    'openssl_csr_sign',
    'openssl_decrypt',
    'openssl_dh_compute_key',
    'openssl_digest',
    'openssl_encrypt',
    'openssl_get_curve_names',
    'openssl_open',
    'openssl_pbkdf2',
    'openssl_pkcs7_decrypt',
    'openssl_pkcs7_encrypt',
    'openssl_pkcs7_read',
    'openssl_pkcs7_sign',
    'openssl_pkcs12_export',
    'openssl_pkcs12_export_to_file',
    'openssl_pkcs12_read',
    'openssl_pkey_derive',
    'openssl_pkey_export',
    'openssl_pkey_export_to_file',
    'openssl_pkey_get_details',
    'openssl_pkey_get_private',
    'openssl_pkey_get_public',
    'openssl_pkey_new',
    'openssl_private_decrypt',
    'openssl_private_encrypt',
    'openssl_public_decrypt',
    'openssl_public_encrypt',
    'openssl_seal',
    'openssl_sign',
    'openssl_spki_export',
    'openssl_spki_export_challenge',
    'openssl_spki_new',
    'openssl_spki_verify',
    'openssl_verify',
    'openssl_x509_checkpurpose',
    'openssl_x509_export',
    'openssl_x509_export_to_file',
    'openssl_x509_fingerprint',
    'openssl_x509_read',
    'output_add_rewrite_var',
    'output_reset_rewrite_vars',
    'parse_ini_file',
    'parse_ini_string',
    'parse_url',
    'passthru',
    'pclose',
    'pcntl_getcpuaffinity',
    'pcntl_getpriority',
    'pcntl_setcpuaffinity',
    'pcntl_setpriority',
    'pcntl_signal',
    'pcntl_signal_dispatch',
    'pcntl_sigprocmask',
    'pcntl_sigtimedwait',
    'pcntl_sigwaitinfo',
    'pfsockopen',
    'pg_cancel_query',
    'pg_connect',
    'pg_connection_reset',
    'pg_convert',
    'pg_copy_from',
    'pg_copy_to',
    'pg_delete',
    'pg_end_copy',
    'pg_execute',
    'pg_field_num',
    'pg_field_table',
    'pg_flush',
    'pg_free_result',
    'pg_host',
    'pg_insert',
    'pg_last_oid',
    'pg_lo_close',
    'pg_lo_export',
    'pg_lo_import',
    'pg_lo_open',
    'pg_lo_read',
    'pg_lo_seek',
    'pg_lo_truncate',
    'pg_lo_unlink',
    'pg_lo_write',
    'pg_meta_data',
    'pg_parameter_status',
    'pg_pconnect',
    'pg_ping',
    'pg_prepare',
    'pg_put_line',
    'pg_query',
    'pg_query_params',
    'pg_result_error_field',
    'pg_result_seek',
    'pg_select',
    'pg_set_chunked_rows_size',
    'pg_socket',
    'pg_trace',
    'pg_update',
    'php_sapi_name',
    'popen',
    'posix_access',
    'posix_eaccess',
    'posix_getgrgid',
    'posix_getgrnam',
    'posix_getgroups',
    'posix_getlogin',
    'posix_getpgid',
    'posix_getpwuid',
    'posix_getrlimit',
    'posix_getsid',
    'posix_initgroups',
    'posix_kill',
    'posix_mkfifo',
    'posix_mknod',
    'posix_setegid',
    'posix_seteuid',
    'posix_setgid',
    'posix_setpgid',
    'posix_setrlimit',
    'posix_setsid',
    'posix_setuid',
    'posix_times',
    'posix_uname',
    'preg_grep',
    'preg_match',
    'preg_match_all',
    'preg_replace',
    'preg_replace_callback',
    'preg_replace_callback_array',
    'preg_split',
    'proc_close',
    'proc_nice',
    'proc_open',
    'pspell_add_to_personal',
    'pspell_add_to_session',
    'pspell_clear_session',
    'pspell_config_data_dir',
    'pspell_config_dict_dir',
    'pspell_config_ignore',
    'pspell_config_mode',
    'pspell_config_personal',
    'pspell_config_repl',
    'pspell_config_runtogether',
    'pspell_config_save_repl',
    'pspell_new',
    'pspell_new_config',
    'pspell_new_personal',
    'pspell_save_wordlist',
    'pspell_store_replacement',
    'ps_add_launchlink',
    'ps_add_locallink',
    'ps_add_note',
    'ps_add_pdflink',
    'ps_add_weblink',
    'ps_arc',
    'ps_arcn',
    'ps_begin_page',
    'ps_begin_pattern',
    'ps_begin_template',
    'ps_circle',
    'ps_clip',
    'ps_close',
    'ps_closepath',
    'ps_closepath_stroke',
    'ps_close_image',
    'ps_continue_text',
    'ps_curveto',
    'ps_delete',
    'ps_end_page',
    'ps_end_pattern',
    'ps_end_template',
    'ps_fill',
    'ps_fill_stroke',
    'ps_get_parameter',
    'ps_hyphenate',
    'ps_include_file',
    'ps_lineto',
    'ps_moveto',
    'ps_new',
    'ps_open_file',
    'ps_place_image',
    'ps_rect',
    'ps_restore',
    'ps_rotate',
    'ps_save',
    'ps_scale',
    'ps_setcolor',
    'ps_setdash',
    'ps_setflat',
    'ps_setfont',
    'ps_setgray',
    'ps_setlinecap',
    'ps_setlinejoin',
    'ps_setlinewidth',
    'ps_setmiterlimit',
    'ps_setoverprintmode',
    'ps_setpolydash',
    'ps_set_border_color',
    'ps_set_border_dash',
    'ps_set_border_style',
    'ps_set_info',
    'ps_set_parameter',
    'ps_set_text_pos',
    'ps_set_value',
    'ps_shading',
    'ps_shading_pattern',
    'ps_shfill',
    'ps_show',
    'ps_show2',
    'ps_show_xy',
    'ps_show_xy2',
    'ps_stroke',
    'ps_symbol',
    'ps_translate',
    'putenv',
    'readfile',
    'readgzfile',
    'readline_completion_function',
    'readline_read_history',
    'readline_write_history',
    'readlink',
    'realpath',
    'register_tick_function',
    'rename',
    'rewind',
    'rmdir',
    'rnp_decrypt',
    'rnp_dump_packets',
    'rnp_dump_packets_to_json',
    'rnp_ffi_create',
    'rnp_ffi_set_pass_provider',
    'rnp_import_keys',
    'rnp_import_signatures',
    'rnp_key_export',
    'rnp_key_export_autocrypt',
    'rnp_key_export_revocation',
    'rnp_key_get_info',
    'rnp_key_remove',
    'rnp_key_revoke',
    'rnp_list_keys',
    'rnp_load_keys',
    'rnp_load_keys_from_path',
    'rnp_locate_key',
    'rnp_op_encrypt',
    'rnp_op_generate_key',
    'rnp_op_sign',
    'rnp_op_sign_cleartext',
    'rnp_op_sign_detached',
    'rnp_op_verify',
    'rnp_op_verify_detached',
    'rnp_save_keys',
    'rnp_save_keys_to_path',
    'rnp_supported_features',
    'rpmaddtag',
    'rpmdefine',
    'rrd_create',
    'rrd_first',
    'rrd_graph',
    'rrd_info',
    'rrd_lastupdate',
    'rrd_restore',
    'rrd_tune',
    'rrd_update',
    'rrd_xport',
    'sapi_windows_cp_conv',
    'sapi_windows_cp_set',
    'sapi_windows_generate_ctrl_event',
    'sapi_windows_set_ctrl_handler',
    'sapi_windows_vt100_support',
    'scandir',
    'sem_acquire',
    'sem_get',
    'sem_release',
    'sem_remove',
    'session_abort',
    'session_cache_expire',
    'session_cache_limiter',
    'session_create_id',
    'session_decode',
    'session_destroy',
    'session_encode',
    'session_gc',
    'session_id',
    'session_module_name',
    'session_name',
    'session_regenerate_id',
    'session_reset',
    'session_save_path',
    'session_start',
    'session_unset',
    'session_write_close',
    'settype',
    'set_include_path',
    'set_time_limit',
    'sha1_file',
    'shell_exec',
    'shmop_delete',
    'shm_attach',
    'shm_detach',
    'shm_put_var',
    'shm_remove',
    'shm_remove_var',
    'simplexml_import_dom',
    'simplexml_load_file',
    'simplexml_load_string',
    'socket_accept',
    'socket_addrinfo_bind',
    'socket_addrinfo_connect',
    'socket_addrinfo_lookup',
    'socket_atmark',
    'socket_bind',
    'socket_connect',
    'socket_create',
    'socket_create_listen',
    'socket_create_pair',
    'socket_export_stream',
    'socket_getpeername',
    'socket_getsockname',
    'socket_get_option',
    'socket_import_stream',
    'socket_listen',
    'socket_read',
    'socket_send',
    'socket_sendmsg',
    'socket_sendto',
    'socket_set_block',
    'socket_set_nonblock',
    'socket_set_option',
    'socket_shutdown',
    'socket_write',
    'socket_wsaprotocol_info_export',
    'socket_wsaprotocol_info_import',
    'socket_wsaprotocol_info_release',
    'sodium_crypto_aead_aegis128l_decrypt',
    'sodium_crypto_aead_aegis256_decrypt',
    'sodium_crypto_aead_aes256gcm_decrypt',
    'sodium_crypto_aead_chacha20poly1305_decrypt',
    'sodium_crypto_aead_chacha20poly1305_encrypt',
    'sodium_crypto_aead_chacha20poly1305_ietf_decrypt',
    'sodium_crypto_aead_chacha20poly1305_ietf_encrypt',
    'sodium_crypto_aead_xchacha20poly1305_ietf_decrypt',
    'sodium_crypto_aead_xchacha20poly1305_ietf_encrypt',
    'sodium_crypto_box_open',
    'sodium_crypto_box_seal_open',
    'sodium_crypto_secretbox_open',
    'sodium_crypto_sign_open',
    'sodium_crypto_stream_xchacha20_xor_ic',
    'solr_get_version',
    'spl_autoload_register',
    'spl_autoload_unregister',
    'sqlsrv_begin_transaction',
    'sqlsrv_cancel',
    'sqlsrv_client_info',
    'sqlsrv_close',
    'sqlsrv_commit',
    'sqlsrv_configure',
    'sqlsrv_execute',
    'sqlsrv_fetch',
    'sqlsrv_fetch_array',
    'sqlsrv_fetch_object',
    'sqlsrv_free_stmt',
    'sqlsrv_get_field',
    'sqlsrv_next_result',
    'sqlsrv_num_fields',
    'sqlsrv_num_rows',
    'sqlsrv_prepare',
    'sqlsrv_query',
    'sqlsrv_rollback',
    'ssdeep_fuzzy_compare',
    'ssdeep_fuzzy_hash',
    'ssdeep_fuzzy_hash_filename',
    'ssh2_auth_agent',
    'ssh2_auth_hostbased_file',
    'ssh2_auth_password',
    'ssh2_auth_pubkey_file',
    'ssh2_connect',
    'ssh2_disconnect',
    'ssh2_exec',
    'ssh2_forward_accept',
    'ssh2_forward_listen',
    'ssh2_publickey_add',
    'ssh2_publickey_init',
    'ssh2_publickey_remove',
    'ssh2_scp_recv',
    'ssh2_scp_send',
    'ssh2_send_eof',
    'ssh2_sftp',
    'ssh2_sftp_chmod',
    'ssh2_sftp_mkdir',
    'ssh2_sftp_rename',
    'ssh2_sftp_rmdir',
    'ssh2_sftp_symlink',
    'ssh2_sftp_unlink',
    'ssh2_shell',
    'stream_context_set_options',
    'stream_context_set_params',
    'stream_copy_to_stream',
    'stream_filter_append',
    'stream_filter_prepend',
    'stream_filter_register',
    'stream_filter_remove',
    'stream_get_contents',
    'stream_get_line',
    'stream_isatty',
    'stream_resolve_include_path',
    'stream_set_blocking',
    'stream_set_timeout',
    'stream_socket_accept',
    'stream_socket_client',
    'stream_socket_get_name',
    'stream_socket_pair',
    'stream_socket_recvfrom',
    'stream_socket_sendto',
    'stream_socket_server',
    'stream_socket_shutdown',
    'stream_supports_lock',
    'stream_wrapper_register',
    'stream_wrapper_restore',
    'stream_wrapper_unregister',
    'strftime',
    'strptime',
    'strtotime',
    'swoole_async_dns_lookup',
    'swoole_async_readfile',
    'swoole_async_write',
    'swoole_async_writefile',
    'swoole_event_defer',
    'swoole_event_del',
    'swoole_event_write',
    'symlink',
    'system',
    'sys_getloadavg',
    'tempnam',
    'timezone_name_from_abbr',
    'time_nanosleep',
    'time_sleep_until',
    'tmpfile',
    'touch',
    'unixtojd',
    'unlink',
    'unpack',
    'unserialize',
    'uopz_extend',
    'uopz_implement',
    'variant_date_to_timestamp',
    'variant_round',
    'virtual',
    'xdiff_file_bdiff',
    'xdiff_file_bpatch',
    'xdiff_file_diff',
    'xdiff_file_diff_binary',
    'xdiff_file_patch_binary',
    'xdiff_file_rabdiff',
    'xdiff_string_bpatch',
    'xdiff_string_patch',
    'xdiff_string_patch_binary',
    'xdiff_string_rabdiff',
    'xmlrpc_set_type',
    'xml_parser_free',
    'xml_parser_set_option',
    'yaml_parse',
    'yaml_parse_url',
    'yaz_ccl_parse',
    'yaz_close',
    'yaz_connect',
    'yaz_database',
    'yaz_element',
    'yaz_present',
    'yaz_search',
    'yaz_wait',
    'zip_entry_close',
    'zip_entry_compressedsize',
    'zip_entry_compressionmethod',
    'zip_entry_filesize',
    'zip_entry_name',
    'zip_entry_open',
    'zip_entry_read',
    'zlib_decode',
];
<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\Renaming\Rector\FuncCall\RenameFunctionRector;

// This file configures rector/rector to replace all PHP functions with their equivalent "safe" functions.
return static function (RectorConfig $rectorConfig): void {
    $rectorConfig->ruleWithConfiguration(
        RenameFunctionRector::class,
        [            'apache_getenv' => 'Safe\apache_getenv',
            'apache_get_version' => 'Safe\apache_get_version',
            'apache_lookup_uri' => 'Safe\apache_lookup_uri',
            'apache_setenv' => 'Safe\apache_setenv',
            'apcu_cache_info' => 'Safe\apcu_cache_info',
            'apcu_cas' => 'Safe\apcu_cas',
            'apcu_dec' => 'Safe\apcu_dec',
            'apcu_fetch' => 'Safe\apcu_fetch',
            'apcu_inc' => 'Safe\apcu_inc',
            'apcu_sma_info' => 'Safe\apcu_sma_info',
            'base64_decode' => 'Safe\base64_decode',
            'bindtextdomain' => 'Safe\bindtextdomain',
            'bzclose' => 'Safe\bzclose',
            'bzflush' => 'Safe\bzflush',
            'bzopen' => 'Safe\bzopen',
            'bzread' => 'Safe\bzread',
            'bzwrite' => 'Safe\bzwrite',
            'chdir' => 'Safe\chdir',
            'chgrp' => 'Safe\chgrp',
            'chmod' => 'Safe\chmod',
            'chown' => 'Safe\chown',
            'chroot' => 'Safe\chroot',
            'class_alias' => 'Safe\class_alias',
            'class_implements' => 'Safe\class_implements',
            'class_parents' => 'Safe\class_parents',
            'class_uses' => 'Safe\class_uses',
            'cli_set_process_title' => 'Safe\cli_set_process_title',
            'com_create_guid' => 'Safe\com_create_guid',
            'com_event_sink' => 'Safe\com_event_sink',
            'com_load_typelib' => 'Safe\com_load_typelib',
            'com_print_typeinfo' => 'Safe\com_print_typeinfo',
            'convert_uudecode' => 'Safe\convert_uudecode',
            'copy' => 'Safe\copy',
            'cubrid_bind' => 'Safe\cubrid_bind',
            'cubrid_column_names' => 'Safe\cubrid_column_names',
            'cubrid_column_types' => 'Safe\cubrid_column_types',
            'cubrid_col_size' => 'Safe\cubrid_col_size',
            'cubrid_commit' => 'Safe\cubrid_commit',
            'cubrid_connect' => 'Safe\cubrid_connect',
            'cubrid_connect_with_url' => 'Safe\cubrid_connect_with_url',
            'cubrid_current_oid' => 'Safe\cubrid_current_oid',
            'cubrid_disconnect' => 'Safe\cubrid_disconnect',
            'cubrid_drop' => 'Safe\cubrid_drop',
            'cubrid_free_result' => 'Safe\cubrid_free_result',
            'cubrid_get_charset' => 'Safe\cubrid_get_charset',
            'cubrid_get_class_name' => 'Safe\cubrid_get_class_name',
            'cubrid_get_client_info' => 'Safe\cubrid_get_client_info',
            'cubrid_get_db_parameter' => 'Safe\cubrid_get_db_parameter',
            'cubrid_get_query_timeout' => 'Safe\cubrid_get_query_timeout',
            'cubrid_get_server_info' => 'Safe\cubrid_get_server_info',
            'cubrid_insert_id' => 'Safe\cubrid_insert_id',
            'cubrid_lob2_bind' => 'Safe\cubrid_lob2_bind',
            'cubrid_lob2_close' => 'Safe\cubrid_lob2_close',
            'cubrid_lob2_export' => 'Safe\cubrid_lob2_export',
            'cubrid_lob2_import' => 'Safe\cubrid_lob2_import',
            'cubrid_lob2_new' => 'Safe\cubrid_lob2_new',
            'cubrid_lob2_read' => 'Safe\cubrid_lob2_read',
            'cubrid_lob2_seek' => 'Safe\cubrid_lob2_seek',
            'cubrid_lob2_seek64' => 'Safe\cubrid_lob2_seek64',
            'cubrid_lob2_size' => 'Safe\cubrid_lob2_size',
            'cubrid_lob2_size64' => 'Safe\cubrid_lob2_size64',
            'cubrid_lob2_tell' => 'Safe\cubrid_lob2_tell',
            'cubrid_lob2_tell64' => 'Safe\cubrid_lob2_tell64',
            'cubrid_lob2_write' => 'Safe\cubrid_lob2_write',
            'cubrid_lob_close' => 'Safe\cubrid_lob_close',
            'cubrid_lob_export' => 'Safe\cubrid_lob_export',
            'cubrid_lob_get' => 'Safe\cubrid_lob_get',
            'cubrid_lob_send' => 'Safe\cubrid_lob_send',
            'cubrid_lob_size' => 'Safe\cubrid_lob_size',
            'cubrid_lock_read' => 'Safe\cubrid_lock_read',
            'cubrid_lock_write' => 'Safe\cubrid_lock_write',
            'cubrid_move_cursor' => 'Safe\cubrid_move_cursor',
            'cubrid_next_result' => 'Safe\cubrid_next_result',
            'cubrid_pconnect' => 'Safe\cubrid_pconnect',
            'cubrid_pconnect_with_url' => 'Safe\cubrid_pconnect_with_url',
            'cubrid_prepare' => 'Safe\cubrid_prepare',
            'cubrid_put' => 'Safe\cubrid_put',
            'cubrid_rollback' => 'Safe\cubrid_rollback',
            'cubrid_schema' => 'Safe\cubrid_schema',
            'cubrid_seq_drop' => 'Safe\cubrid_seq_drop',
            'cubrid_seq_insert' => 'Safe\cubrid_seq_insert',
            'cubrid_seq_put' => 'Safe\cubrid_seq_put',
            'cubrid_set_add' => 'Safe\cubrid_set_add',
            'cubrid_set_autocommit' => 'Safe\cubrid_set_autocommit',
            'cubrid_set_db_parameter' => 'Safe\cubrid_set_db_parameter',
            'cubrid_set_drop' => 'Safe\cubrid_set_drop',
            'cubrid_set_query_timeout' => 'Safe\cubrid_set_query_timeout',
            'curl_copy_handle' => 'Safe\curl_copy_handle',
            'curl_escape' => 'Safe\curl_escape',
            'curl_exec' => 'Safe\curl_exec',
            'curl_getinfo' => 'Safe\curl_getinfo',
            'curl_init' => 'Safe\curl_init',
            'curl_multi_info_read' => 'Safe\curl_multi_info_read',
            'curl_multi_setopt' => 'Safe\curl_multi_setopt',
            'curl_setopt' => 'Safe\curl_setopt',
            'curl_share_setopt' => 'Safe\curl_share_setopt',
            'curl_unescape' => 'Safe\curl_unescape',
            'curl_upkeep' => 'Safe\curl_upkeep',
            'date_create' => 'Safe\date_create',
            'date_create_immutable' => 'Safe\date_create_immutable',
            'date_parse_from_format' => 'Safe\date_parse_from_format',
            'date_sunrise' => 'Safe\date_sunrise',
            'date_sunset' => 'Safe\date_sunset',
            'db2_autocommit' => 'Safe\db2_autocommit',
            'db2_bind_param' => 'Safe\db2_bind_param',
            'db2_client_info' => 'Safe\db2_client_info',
            'db2_close' => 'Safe\db2_close',
            'db2_commit' => 'Safe\db2_commit',
            'db2_execute' => 'Safe\db2_execute',
            'db2_free_result' => 'Safe\db2_free_result',
            'db2_free_stmt' => 'Safe\db2_free_stmt',
            'db2_get_option' => 'Safe\db2_get_option',
            'db2_num_rows' => 'Safe\db2_num_rows',
            'db2_pclose' => 'Safe\db2_pclose',
            'db2_rollback' => 'Safe\db2_rollback',
            'db2_server_info' => 'Safe\db2_server_info',
            'db2_set_option' => 'Safe\db2_set_option',
            'define' => 'Safe\define',
            'deflate_add' => 'Safe\deflate_add',
            'deflate_init' => 'Safe\deflate_init',
            'dir' => 'Safe\dir',
            'disk_free_space' => 'Safe\disk_free_space',
            'disk_total_space' => 'Safe\disk_total_space',
            'dl' => 'Safe\dl',
            'dns_get_record' => 'Safe\dns_get_record',
            'eio_busy' => 'Safe\eio_busy',
            'eio_chmod' => 'Safe\eio_chmod',
            'eio_chown' => 'Safe\eio_chown',
            'eio_close' => 'Safe\eio_close',
            'eio_custom' => 'Safe\eio_custom',
            'eio_dup2' => 'Safe\eio_dup2',
            'eio_event_loop' => 'Safe\eio_event_loop',
            'eio_fallocate' => 'Safe\eio_fallocate',
            'eio_fchmod' => 'Safe\eio_fchmod',
            'eio_fchown' => 'Safe\eio_fchown',
            'eio_fdatasync' => 'Safe\eio_fdatasync',
            'eio_fstat' => 'Safe\eio_fstat',
            'eio_fstatvfs' => 'Safe\eio_fstatvfs',
            'eio_fsync' => 'Safe\eio_fsync',
            'eio_ftruncate' => 'Safe\eio_ftruncate',
            'eio_futime' => 'Safe\eio_futime',
            'eio_grp' => 'Safe\eio_grp',
            'eio_lstat' => 'Safe\eio_lstat',
            'eio_mkdir' => 'Safe\eio_mkdir',
            'eio_mknod' => 'Safe\eio_mknod',
            'eio_nop' => 'Safe\eio_nop',
            'eio_readahead' => 'Safe\eio_readahead',
            'eio_readdir' => 'Safe\eio_readdir',
            'eio_readlink' => 'Safe\eio_readlink',
            'eio_rename' => 'Safe\eio_rename',
            'eio_rmdir' => 'Safe\eio_rmdir',
            'eio_seek' => 'Safe\eio_seek',
            'eio_sendfile' => 'Safe\eio_sendfile',
            'eio_stat' => 'Safe\eio_stat',
            'eio_statvfs' => 'Safe\eio_statvfs',
            'eio_symlink' => 'Safe\eio_symlink',
            'eio_sync' => 'Safe\eio_sync',
            'eio_syncfs' => 'Safe\eio_syncfs',
            'eio_sync_file_range' => 'Safe\eio_sync_file_range',
            'eio_truncate' => 'Safe\eio_truncate',
            'eio_unlink' => 'Safe\eio_unlink',
            'eio_utime' => 'Safe\eio_utime',
            'eio_write' => 'Safe\eio_write',
            'error_log' => 'Safe\error_log',
            'exec' => 'Safe\exec',
            'fastcgi_finish_request' => 'Safe\fastcgi_finish_request',
            'fbird_blob_cancel' => 'Safe\fbird_blob_cancel',
            'fclose' => 'Safe\fclose',
            'fdatasync' => 'Safe\fdatasync',
            'fflush' => 'Safe\fflush',
            'fgetcsv' => 'Safe\fgetcsv',
            'file' => 'Safe\file',
            'fileatime' => 'Safe\fileatime',
            'filectime' => 'Safe\filectime',
            'fileinode' => 'Safe\fileinode',
            'filemtime' => 'Safe\filemtime',
            'fileowner' => 'Safe\fileowner',
            'fileperms' => 'Safe\fileperms',
            'filesize' => 'Safe\filesize',
            'filetype' => 'Safe\filetype',
            'file_get_contents' => 'Safe\file_get_contents',
            'file_put_contents' => 'Safe\file_put_contents',
            'filter_input_array' => 'Safe\filter_input_array',
            'filter_var_array' => 'Safe\filter_var_array',
            'finfo_close' => 'Safe\finfo_close',
            'finfo_open' => 'Safe\finfo_open',
            'flock' => 'Safe\flock',
            'fopen' => 'Safe\fopen',
            'fpm_get_status' => 'Safe\fpm_get_status',
            'fputcsv' => 'Safe\fputcsv',
            'fread' => 'Safe\fread',
            'fsockopen' => 'Safe\fsockopen',
            'fstat' => 'Safe\fstat',
            'fsync' => 'Safe\fsync',
            'ftell' => 'Safe\ftell',
            'ftp_alloc' => 'Safe\ftp_alloc',
            'ftp_append' => 'Safe\ftp_append',
            'ftp_cdup' => 'Safe\ftp_cdup',
            'ftp_chdir' => 'Safe\ftp_chdir',
            'ftp_chmod' => 'Safe\ftp_chmod',
            'ftp_close' => 'Safe\ftp_close',
            'ftp_connect' => 'Safe\ftp_connect',
            'ftp_delete' => 'Safe\ftp_delete',
            'ftp_fget' => 'Safe\ftp_fget',
            'ftp_fput' => 'Safe\ftp_fput',
            'ftp_get' => 'Safe\ftp_get',
            'ftp_login' => 'Safe\ftp_login',
            'ftp_mkdir' => 'Safe\ftp_mkdir',
            'ftp_mlsd' => 'Safe\ftp_mlsd',
            'ftp_nb_get' => 'Safe\ftp_nb_get',
            'ftp_nb_put' => 'Safe\ftp_nb_put',
            'ftp_nlist' => 'Safe\ftp_nlist',
            'ftp_pasv' => 'Safe\ftp_pasv',
            'ftp_put' => 'Safe\ftp_put',
            'ftp_pwd' => 'Safe\ftp_pwd',
            'ftp_raw' => 'Safe\ftp_raw',
            'ftp_rename' => 'Safe\ftp_rename',
            'ftp_rmdir' => 'Safe\ftp_rmdir',
            'ftp_site' => 'Safe\ftp_site',
            'ftp_size' => 'Safe\ftp_size',
            'ftp_ssl_connect' => 'Safe\ftp_ssl_connect',
            'ftp_systype' => 'Safe\ftp_systype',
            'ftruncate' => 'Safe\ftruncate',
            'fwrite' => 'Safe\fwrite',
            'getcwd' => 'Safe\getcwd',
            'gethostname' => 'Safe\gethostname',
            'getimagesize' => 'Safe\getimagesize',
            'getlastmod' => 'Safe\getlastmod',
            'getmygid' => 'Safe\getmygid',
            'getmyinode' => 'Safe\getmyinode',
            'getmypid' => 'Safe\getmypid',
            'getmyuid' => 'Safe\getmyuid',
            'getopt' => 'Safe\getopt',
            'getprotobyname' => 'Safe\getprotobyname',
            'getprotobynumber' => 'Safe\getprotobynumber',
            'getrusage' => 'Safe\getrusage',
            'getservbyport' => 'Safe\getservbyport',
            'get_cfg_var' => 'Safe\get_cfg_var',
            'get_headers' => 'Safe\get_headers',
            'get_include_path' => 'Safe\get_include_path',
            'get_meta_tags' => 'Safe\get_meta_tags',
            'glob' => 'Safe\glob',
            'gmmktime' => 'Safe\gmmktime',
            'gmstrftime' => 'Safe\gmstrftime',
            'gnupg_adddecryptkey' => 'Safe\gnupg_adddecryptkey',
            'gnupg_addencryptkey' => 'Safe\gnupg_addencryptkey',
            'gnupg_addsignkey' => 'Safe\gnupg_addsignkey',
            'gnupg_cleardecryptkeys' => 'Safe\gnupg_cleardecryptkeys',
            'gnupg_clearencryptkeys' => 'Safe\gnupg_clearencryptkeys',
            'gnupg_clearsignkeys' => 'Safe\gnupg_clearsignkeys',
            'gnupg_deletekey' => 'Safe\gnupg_deletekey',
            'gnupg_setarmor' => 'Safe\gnupg_setarmor',
            'gnupg_setsignmode' => 'Safe\gnupg_setsignmode',
            'gzclose' => 'Safe\gzclose',
            'gzcompress' => 'Safe\gzcompress',
            'gzdecode' => 'Safe\gzdecode',
            'gzdeflate' => 'Safe\gzdeflate',
            'gzencode' => 'Safe\gzencode',
            'gzfile' => 'Safe\gzfile',
            'gzgets' => 'Safe\gzgets',
            'gzinflate' => 'Safe\gzinflate',
            'gzopen' => 'Safe\gzopen',
            'gzread' => 'Safe\gzread',
            'gzrewind' => 'Safe\gzrewind',
            'gztell' => 'Safe\gztell',
            'gzuncompress' => 'Safe\gzuncompress',
            'gzwrite' => 'Safe\gzwrite',
            'hash_file' => 'Safe\hash_file',
            'hash_hmac_file' => 'Safe\hash_hmac_file',
            'hash_update_file' => 'Safe\hash_update_file',
            'header_register_callback' => 'Safe\header_register_callback',
            'hex2bin' => 'Safe\hex2bin',
            'highlight_file' => 'Safe\highlight_file',
            'hrtime' => 'Safe\hrtime',
            'ibase_add_user' => 'Safe\ibase_add_user',
            'ibase_backup' => 'Safe\ibase_backup',
            'ibase_blob_cancel' => 'Safe\ibase_blob_cancel',
            'ibase_blob_create' => 'Safe\ibase_blob_create',
            'ibase_blob_get' => 'Safe\ibase_blob_get',
            'ibase_close' => 'Safe\ibase_close',
            'ibase_commit' => 'Safe\ibase_commit',
            'ibase_commit_ret' => 'Safe\ibase_commit_ret',
            'ibase_connect' => 'Safe\ibase_connect',
            'ibase_delete_user' => 'Safe\ibase_delete_user',
            'ibase_drop_db' => 'Safe\ibase_drop_db',
            'ibase_free_event_handler' => 'Safe\ibase_free_event_handler',
            'ibase_free_query' => 'Safe\ibase_free_query',
            'ibase_free_result' => 'Safe\ibase_free_result',
            'ibase_maintain_db' => 'Safe\ibase_maintain_db',
            'ibase_modify_user' => 'Safe\ibase_modify_user',
            'ibase_name_result' => 'Safe\ibase_name_result',
            'ibase_pconnect' => 'Safe\ibase_pconnect',
            'ibase_restore' => 'Safe\ibase_restore',
            'ibase_rollback' => 'Safe\ibase_rollback',
            'ibase_rollback_ret' => 'Safe\ibase_rollback_ret',
            'ibase_service_attach' => 'Safe\ibase_service_attach',
            'ibase_service_detach' => 'Safe\ibase_service_detach',
            'iconv' => 'Safe\iconv',
            'iconv_get_encoding' => 'Safe\iconv_get_encoding',
            'iconv_mime_decode' => 'Safe\iconv_mime_decode',
            'iconv_mime_encode' => 'Safe\iconv_mime_encode',
            'iconv_set_encoding' => 'Safe\iconv_set_encoding',
            'iconv_strlen' => 'Safe\iconv_strlen',
            'idate' => 'Safe\idate',
            'imageaffine' => 'Safe\imageaffine',
            'imageaffinematrixconcat' => 'Safe\imageaffinematrixconcat',
            'imageaffinematrixget' => 'Safe\imageaffinematrixget',
            'imagealphablending' => 'Safe\imagealphablending',
            'imageantialias' => 'Safe\imageantialias',
            'imagearc' => 'Safe\imagearc',
            'imageavif' => 'Safe\imageavif',
            'imagebmp' => 'Safe\imagebmp',
            'imagechar' => 'Safe\imagechar',
            'imagecharup' => 'Safe\imagecharup',
            'imagecolorat' => 'Safe\imagecolorat',
            'imagecolordeallocate' => 'Safe\imagecolordeallocate',
            'imagecolormatch' => 'Safe\imagecolormatch',
            'imagecolorset' => 'Safe\imagecolorset',
            'imageconvolution' => 'Safe\imageconvolution',
            'imagecopy' => 'Safe\imagecopy',
            'imagecopymerge' => 'Safe\imagecopymerge',
            'imagecopymergegray' => 'Safe\imagecopymergegray',
            'imagecopyresampled' => 'Safe\imagecopyresampled',
            'imagecopyresized' => 'Safe\imagecopyresized',
            'imagecreate' => 'Safe\imagecreate',
            'imagecreatefromavif' => 'Safe\imagecreatefromavif',
            'imagecreatefrombmp' => 'Safe\imagecreatefrombmp',
            'imagecreatefromgd' => 'Safe\imagecreatefromgd',
            'imagecreatefromgd2' => 'Safe\imagecreatefromgd2',
            'imagecreatefromgd2part' => 'Safe\imagecreatefromgd2part',
            'imagecreatefromgif' => 'Safe\imagecreatefromgif',
            'imagecreatefromjpeg' => 'Safe\imagecreatefromjpeg',
            'imagecreatefrompng' => 'Safe\imagecreatefrompng',
            'imagecreatefromstring' => 'Safe\imagecreatefromstring',
            'imagecreatefromtga' => 'Safe\imagecreatefromtga',
            'imagecreatefromwbmp' => 'Safe\imagecreatefromwbmp',
            'imagecreatefromwebp' => 'Safe\imagecreatefromwebp',
            'imagecreatefromxbm' => 'Safe\imagecreatefromxbm',
            'imagecreatefromxpm' => 'Safe\imagecreatefromxpm',
            'imagecreatetruecolor' => 'Safe\imagecreatetruecolor',
            'imagecrop' => 'Safe\imagecrop',
            'imagecropauto' => 'Safe\imagecropauto',
            'imagedashedline' => 'Safe\imagedashedline',
            'imagedestroy' => 'Safe\imagedestroy',
            'imageellipse' => 'Safe\imageellipse',
            'imagefill' => 'Safe\imagefill',
            'imagefilledarc' => 'Safe\imagefilledarc',
            'imagefilledellipse' => 'Safe\imagefilledellipse',
            'imagefilledrectangle' => 'Safe\imagefilledrectangle',
            'imagefilltoborder' => 'Safe\imagefilltoborder',
            'imagefilter' => 'Safe\imagefilter',
            'imageflip' => 'Safe\imageflip',
            'imageftbbox' => 'Safe\imageftbbox',
            'imagefttext' => 'Safe\imagefttext',
            'imagegammacorrect' => 'Safe\imagegammacorrect',
            'imagegd' => 'Safe\imagegd',
            'imagegd2' => 'Safe\imagegd2',
            'imagegif' => 'Safe\imagegif',
            'imagegrabscreen' => 'Safe\imagegrabscreen',
            'imagegrabwindow' => 'Safe\imagegrabwindow',
            'imagejpeg' => 'Safe\imagejpeg',
            'imagelayereffect' => 'Safe\imagelayereffect',
            'imageline' => 'Safe\imageline',
            'imageloadfont' => 'Safe\imageloadfont',
            'imagepng' => 'Safe\imagepng',
            'imagerectangle' => 'Safe\imagerectangle',
            'imageresolution' => 'Safe\imageresolution',
            'imagerotate' => 'Safe\imagerotate',
            'imagesavealpha' => 'Safe\imagesavealpha',
            'imagescale' => 'Safe\imagescale',
            'imagesetbrush' => 'Safe\imagesetbrush',
            'imagesetclip' => 'Safe\imagesetclip',
            'imagesetinterpolation' => 'Safe\imagesetinterpolation',
            'imagesetpixel' => 'Safe\imagesetpixel',
            'imagesetstyle' => 'Safe\imagesetstyle',
            'imagesetthickness' => 'Safe\imagesetthickness',
            'imagesettile' => 'Safe\imagesettile',
            'imagestring' => 'Safe\imagestring',
            'imagestringup' => 'Safe\imagestringup',
            'imagetruecolortopalette' => 'Safe\imagetruecolortopalette',
            'imagettfbbox' => 'Safe\imagettfbbox',
            'imagettftext' => 'Safe\imagettftext',
            'imagewbmp' => 'Safe\imagewbmp',
            'imagewebp' => 'Safe\imagewebp',
            'imagexbm' => 'Safe\imagexbm',
            'image_type_to_extension' => 'Safe\image_type_to_extension',
            'imap_8bit' => 'Safe\imap_8bit',
            'imap_append' => 'Safe\imap_append',
            'imap_base64' => 'Safe\imap_base64',
            'imap_binary' => 'Safe\imap_binary',
            'imap_body' => 'Safe\imap_body',
            'imap_bodystruct' => 'Safe\imap_bodystruct',
            'imap_check' => 'Safe\imap_check',
            'imap_createmailbox' => 'Safe\imap_createmailbox',
            'imap_deletemailbox' => 'Safe\imap_deletemailbox',
            'imap_fetchbody' => 'Safe\imap_fetchbody',
            'imap_fetchheader' => 'Safe\imap_fetchheader',
            'imap_fetchmime' => 'Safe\imap_fetchmime',
            'imap_fetchstructure' => 'Safe\imap_fetchstructure',
            'imap_fetch_overview' => 'Safe\imap_fetch_overview',
            'imap_getacl' => 'Safe\imap_getacl',
            'imap_getmailboxes' => 'Safe\imap_getmailboxes',
            'imap_getsubscribed' => 'Safe\imap_getsubscribed',
            'imap_headerinfo' => 'Safe\imap_headerinfo',
            'imap_headers' => 'Safe\imap_headers',
            'imap_listscan' => 'Safe\imap_listscan',
            'imap_lsub' => 'Safe\imap_lsub',
            'imap_mail' => 'Safe\imap_mail',
            'imap_mail_compose' => 'Safe\imap_mail_compose',
            'imap_mail_copy' => 'Safe\imap_mail_copy',
            'imap_mail_move' => 'Safe\imap_mail_move',
            'imap_mime_header_decode' => 'Safe\imap_mime_header_decode',
            'imap_mutf7_to_utf8' => 'Safe\imap_mutf7_to_utf8',
            'imap_num_msg' => 'Safe\imap_num_msg',
            'imap_open' => 'Safe\imap_open',
            'imap_qprint' => 'Safe\imap_qprint',
            'imap_renamemailbox' => 'Safe\imap_renamemailbox',
            'imap_rfc822_write_address' => 'Safe\imap_rfc822_write_address',
            'imap_savebody' => 'Safe\imap_savebody',
            'imap_setacl' => 'Safe\imap_setacl',
            'imap_set_quota' => 'Safe\imap_set_quota',
            'imap_sort' => 'Safe\imap_sort',
            'imap_status' => 'Safe\imap_status',
            'imap_subscribe' => 'Safe\imap_subscribe',
            'imap_thread' => 'Safe\imap_thread',
            'imap_timeout' => 'Safe\imap_timeout',
            'imap_unsubscribe' => 'Safe\imap_unsubscribe',
            'imap_utf8_to_mutf7' => 'Safe\imap_utf8_to_mutf7',
            'inet_ntop' => 'Safe\inet_ntop',
            'inet_pton' => 'Safe\inet_pton',
            'inflate_add' => 'Safe\inflate_add',
            'inflate_get_read_len' => 'Safe\inflate_get_read_len',
            'inflate_init' => 'Safe\inflate_init',
            'ini_get' => 'Safe\ini_get',
            'ini_set' => 'Safe\ini_set',
            'inotify_add_watch' => 'Safe\inotify_add_watch',
            'inotify_init' => 'Safe\inotify_init',
            'inotify_rm_watch' => 'Safe\inotify_rm_watch',
            'iptcembed' => 'Safe\iptcembed',
            'iptcparse' => 'Safe\iptcparse',
            'json_decode' => 'Safe\json_decode',
            'json_encode' => 'Safe\json_encode',
            'lchgrp' => 'Safe\lchgrp',
            'lchown' => 'Safe\lchown',
            'ldap_8859_to_t61' => 'Safe\ldap_8859_to_t61',
            'ldap_add' => 'Safe\ldap_add',
            'ldap_bind' => 'Safe\ldap_bind',
            'ldap_compare' => 'Safe\ldap_compare',
            'ldap_control_paged_result' => 'Safe\ldap_control_paged_result',
            'ldap_control_paged_result_response' => 'Safe\ldap_control_paged_result_response',
            'ldap_count_entries' => 'Safe\ldap_count_entries',
            'ldap_delete' => 'Safe\ldap_delete',
            'ldap_dn2ufn' => 'Safe\ldap_dn2ufn',
            'ldap_exop' => 'Safe\ldap_exop',
            'ldap_exop_passwd' => 'Safe\ldap_exop_passwd',
            'ldap_exop_whoami' => 'Safe\ldap_exop_whoami',
            'ldap_explode_dn' => 'Safe\ldap_explode_dn',
            'ldap_first_attribute' => 'Safe\ldap_first_attribute',
            'ldap_first_entry' => 'Safe\ldap_first_entry',
            'ldap_free_result' => 'Safe\ldap_free_result',
            'ldap_get_dn' => 'Safe\ldap_get_dn',
            'ldap_get_entries' => 'Safe\ldap_get_entries',
            'ldap_get_option' => 'Safe\ldap_get_option',
            'ldap_get_values' => 'Safe\ldap_get_values',
            'ldap_get_values_len' => 'Safe\ldap_get_values_len',
            'ldap_modify_batch' => 'Safe\ldap_modify_batch',
            'ldap_mod_add' => 'Safe\ldap_mod_add',
            'ldap_mod_del' => 'Safe\ldap_mod_del',
            'ldap_mod_replace' => 'Safe\ldap_mod_replace',
            'ldap_next_attribute' => 'Safe\ldap_next_attribute',
            'ldap_parse_exop' => 'Safe\ldap_parse_exop',
            'ldap_parse_result' => 'Safe\ldap_parse_result',
            'ldap_rename' => 'Safe\ldap_rename',
            'ldap_sasl_bind' => 'Safe\ldap_sasl_bind',
            'ldap_set_option' => 'Safe\ldap_set_option',
            'ldap_unbind' => 'Safe\ldap_unbind',
            'libxml_set_external_entity_loader' => 'Safe\libxml_set_external_entity_loader',
            'link' => 'Safe\link',
            'lstat' => 'Safe\lstat',
            'lzf_compress' => 'Safe\lzf_compress',
            'lzf_decompress' => 'Safe\lzf_decompress',
            'mailparse_msg_extract_part_file' => 'Safe\mailparse_msg_extract_part_file',
            'mailparse_msg_free' => 'Safe\mailparse_msg_free',
            'mailparse_msg_parse' => 'Safe\mailparse_msg_parse',
            'mailparse_msg_parse_file' => 'Safe\mailparse_msg_parse_file',
            'mailparse_stream_encode' => 'Safe\mailparse_stream_encode',
            'mb_chr' => 'Safe\mb_chr',
            'mb_convert_encoding' => 'Safe\mb_convert_encoding',
            'mb_convert_variables' => 'Safe\mb_convert_variables',
            'mb_detect_order' => 'Safe\mb_detect_order',
            'mb_eregi_replace' => 'Safe\mb_eregi_replace',
            'mb_ereg_replace' => 'Safe\mb_ereg_replace',
            'mb_ereg_replace_callback' => 'Safe\mb_ereg_replace_callback',
            'mb_ereg_search_getregs' => 'Safe\mb_ereg_search_getregs',
            'mb_ereg_search_init' => 'Safe\mb_ereg_search_init',
            'mb_ereg_search_regs' => 'Safe\mb_ereg_search_regs',
            'mb_ereg_search_setpos' => 'Safe\mb_ereg_search_setpos',
            'mb_get_info' => 'Safe\mb_get_info',
            'mb_http_output' => 'Safe\mb_http_output',
            'mb_internal_encoding' => 'Safe\mb_internal_encoding',
            'mb_ord' => 'Safe\mb_ord',
            'mb_parse_str' => 'Safe\mb_parse_str',
            'mb_regex_encoding' => 'Safe\mb_regex_encoding',
            'mb_send_mail' => 'Safe\mb_send_mail',
            'mb_split' => 'Safe\mb_split',
            'md5_file' => 'Safe\md5_file',
            'mime_content_type' => 'Safe\mime_content_type',
            'mkdir' => 'Safe\mkdir',
            'mktime' => 'Safe\mktime',
            'msg_get_queue' => 'Safe\msg_get_queue',
            'msg_queue_exists' => 'Safe\msg_queue_exists',
            'msg_receive' => 'Safe\msg_receive',
            'msg_remove_queue' => 'Safe\msg_remove_queue',
            'msg_send' => 'Safe\msg_send',
            'msg_set_queue' => 'Safe\msg_set_queue',
            'msg_stat_queue' => 'Safe\msg_stat_queue',
            'mysql_close' => 'Safe\mysql_close',
            'mysql_connect' => 'Safe\mysql_connect',
            'mysql_create_db' => 'Safe\mysql_create_db',
            'mysql_data_seek' => 'Safe\mysql_data_seek',
            'mysql_db_name' => 'Safe\mysql_db_name',
            'mysql_db_query' => 'Safe\mysql_db_query',
            'mysql_drop_db' => 'Safe\mysql_drop_db',
            'mysql_fetch_lengths' => 'Safe\mysql_fetch_lengths',
            'mysql_field_flags' => 'Safe\mysql_field_flags',
            'mysql_field_len' => 'Safe\mysql_field_len',
            'mysql_field_name' => 'Safe\mysql_field_name',
            'mysql_field_seek' => 'Safe\mysql_field_seek',
            'mysql_free_result' => 'Safe\mysql_free_result',
            'mysql_get_host_info' => 'Safe\mysql_get_host_info',
            'mysql_get_proto_info' => 'Safe\mysql_get_proto_info',
            'mysql_get_server_info' => 'Safe\mysql_get_server_info',
            'mysql_info' => 'Safe\mysql_info',
            'mysql_list_dbs' => 'Safe\mysql_list_dbs',
            'mysql_list_fields' => 'Safe\mysql_list_fields',
            'mysql_list_processes' => 'Safe\mysql_list_processes',
            'mysql_list_tables' => 'Safe\mysql_list_tables',
            'mysql_num_fields' => 'Safe\mysql_num_fields',
            'mysql_num_rows' => 'Safe\mysql_num_rows',
            'mysql_query' => 'Safe\mysql_query',
            'mysql_real_escape_string' => 'Safe\mysql_real_escape_string',
            'mysql_result' => 'Safe\mysql_result',
            'mysql_select_db' => 'Safe\mysql_select_db',
            'mysql_set_charset' => 'Safe\mysql_set_charset',
            'mysql_tablename' => 'Safe\mysql_tablename',
            'mysql_thread_id' => 'Safe\mysql_thread_id',
            'mysql_unbuffered_query' => 'Safe\mysql_unbuffered_query',
            'net_get_interfaces' => 'Safe\net_get_interfaces',
            'ob_clean' => 'Safe\ob_clean',
            'ob_end_clean' => 'Safe\ob_end_clean',
            'ob_end_flush' => 'Safe\ob_end_flush',
            'ob_flush' => 'Safe\ob_flush',
            'ob_get_clean' => 'Safe\ob_get_clean',
            'ob_get_flush' => 'Safe\ob_get_flush',
            'ob_start' => 'Safe\ob_start',
            'oci_bind_array_by_name' => 'Safe\oci_bind_array_by_name',
            'oci_bind_by_name' => 'Safe\oci_bind_by_name',
            'oci_cancel' => 'Safe\oci_cancel',
            'oci_commit' => 'Safe\oci_commit',
            'oci_connect' => 'Safe\oci_connect',
            'oci_define_by_name' => 'Safe\oci_define_by_name',
            'oci_execute' => 'Safe\oci_execute',
            'oci_field_name' => 'Safe\oci_field_name',
            'oci_field_precision' => 'Safe\oci_field_precision',
            'oci_field_scale' => 'Safe\oci_field_scale',
            'oci_field_size' => 'Safe\oci_field_size',
            'oci_field_type' => 'Safe\oci_field_type',
            'oci_field_type_raw' => 'Safe\oci_field_type_raw',
            'oci_free_descriptor' => 'Safe\oci_free_descriptor',
            'oci_free_statement' => 'Safe\oci_free_statement',
            'oci_new_collection' => 'Safe\oci_new_collection',
            'oci_new_connect' => 'Safe\oci_new_connect',
            'oci_new_cursor' => 'Safe\oci_new_cursor',
            'oci_new_descriptor' => 'Safe\oci_new_descriptor',
            'oci_num_rows' => 'Safe\oci_num_rows',
            'oci_parse' => 'Safe\oci_parse',
            'oci_pconnect' => 'Safe\oci_pconnect',
            'oci_register_taf_callback' => 'Safe\oci_register_taf_callback',
            'oci_result' => 'Safe\oci_result',
            'oci_rollback' => 'Safe\oci_rollback',
            'oci_server_version' => 'Safe\oci_server_version',
            'oci_set_action' => 'Safe\oci_set_action',
            'oci_set_call_timeout' => 'Safe\oci_set_call_timeout',
            'oci_set_client_identifier' => 'Safe\oci_set_client_identifier',
            'oci_set_client_info' => 'Safe\oci_set_client_info',
            'oci_set_db_operation' => 'Safe\oci_set_db_operation',
            'oci_set_edition' => 'Safe\oci_set_edition',
            'oci_set_module_name' => 'Safe\oci_set_module_name',
            'oci_set_prefetch' => 'Safe\oci_set_prefetch',
            'oci_set_prefetch_lob' => 'Safe\oci_set_prefetch_lob',
            'oci_statement_type' => 'Safe\oci_statement_type',
            'oci_unregister_taf_callback' => 'Safe\oci_unregister_taf_callback',
            'odbc_autocommit' => 'Safe\odbc_autocommit',
            'odbc_commit' => 'Safe\odbc_commit',
            'odbc_connect' => 'Safe\odbc_connect',
            'odbc_cursor' => 'Safe\odbc_cursor',
            'odbc_data_source' => 'Safe\odbc_data_source',
            'odbc_exec' => 'Safe\odbc_exec',
            'odbc_execute' => 'Safe\odbc_execute',
            'odbc_fetch_into' => 'Safe\odbc_fetch_into',
            'odbc_field_len' => 'Safe\odbc_field_len',
            'odbc_field_name' => 'Safe\odbc_field_name',
            'odbc_field_num' => 'Safe\odbc_field_num',
            'odbc_field_scale' => 'Safe\odbc_field_scale',
            'odbc_field_type' => 'Safe\odbc_field_type',
            'odbc_num_fields' => 'Safe\odbc_num_fields',
            'odbc_pconnect' => 'Safe\odbc_pconnect',
            'odbc_prepare' => 'Safe\odbc_prepare',
            'odbc_procedures' => 'Safe\odbc_procedures',
            'odbc_result' => 'Safe\odbc_result',
            'odbc_result_all' => 'Safe\odbc_result_all',
            'odbc_rollback' => 'Safe\odbc_rollback',
            'odbc_setoption' => 'Safe\odbc_setoption',
            'odbc_tables' => 'Safe\odbc_tables',
            'opcache_compile_file' => 'Safe\opcache_compile_file',
            'opcache_get_status' => 'Safe\opcache_get_status',
            'opendir' => 'Safe\opendir',
            'openssl_cipher_iv_length' => 'Safe\openssl_cipher_iv_length',
            'openssl_cipher_key_length' => 'Safe\openssl_cipher_key_length',
            'openssl_cms_decrypt' => 'Safe\openssl_cms_decrypt',
            'openssl_cms_encrypt' => 'Safe\openssl_cms_encrypt',
            'openssl_cms_read' => 'Safe\openssl_cms_read',
            'openssl_cms_sign' => 'Safe\openssl_cms_sign',
            'openssl_cms_verify' => 'Safe\openssl_cms_verify',
            'openssl_csr_export' => 'Safe\openssl_csr_export',
            'openssl_csr_export_to_file' => 'Safe\openssl_csr_export_to_file',
            'openssl_csr_get_public_key' => 'Safe\openssl_csr_get_public_key',
            'openssl_csr_get_subject' => 'Safe\openssl_csr_get_subject',
            'openssl_csr_new' => 'Safe\openssl_csr_new',
            'openssl_csr_sign' => 'Safe\openssl_csr_sign',
            'openssl_decrypt' => 'Safe\openssl_decrypt',
            'openssl_dh_compute_key' => 'Safe\openssl_dh_compute_key',
            'openssl_digest' => 'Safe\openssl_digest',
            'openssl_encrypt' => 'Safe\openssl_encrypt',
            'openssl_get_curve_names' => 'Safe\openssl_get_curve_names',
            'openssl_open' => 'Safe\openssl_open',
            'openssl_pbkdf2' => 'Safe\openssl_pbkdf2',
            'openssl_pkcs7_decrypt' => 'Safe\openssl_pkcs7_decrypt',
            'openssl_pkcs7_encrypt' => 'Safe\openssl_pkcs7_encrypt',
            'openssl_pkcs7_read' => 'Safe\openssl_pkcs7_read',
            'openssl_pkcs7_sign' => 'Safe\openssl_pkcs7_sign',
            'openssl_pkcs12_export' => 'Safe\openssl_pkcs12_export',
            'openssl_pkcs12_export_to_file' => 'Safe\openssl_pkcs12_export_to_file',
            'openssl_pkcs12_read' => 'Safe\openssl_pkcs12_read',
            'openssl_pkey_derive' => 'Safe\openssl_pkey_derive',
            'openssl_pkey_export' => 'Safe\openssl_pkey_export',
            'openssl_pkey_export_to_file' => 'Safe\openssl_pkey_export_to_file',
            'openssl_pkey_get_details' => 'Safe\openssl_pkey_get_details',
            'openssl_pkey_get_private' => 'Safe\openssl_pkey_get_private',
            'openssl_pkey_get_public' => 'Safe\openssl_pkey_get_public',
            'openssl_pkey_new' => 'Safe\openssl_pkey_new',
            'openssl_private_decrypt' => 'Safe\openssl_private_decrypt',
            'openssl_private_encrypt' => 'Safe\openssl_private_encrypt',
            'openssl_public_decrypt' => 'Safe\openssl_public_decrypt',
            'openssl_public_encrypt' => 'Safe\openssl_public_encrypt',
            'openssl_seal' => 'Safe\openssl_seal',
            'openssl_sign' => 'Safe\openssl_sign',
            'openssl_spki_export' => 'Safe\openssl_spki_export',
            'openssl_spki_export_challenge' => 'Safe\openssl_spki_export_challenge',
            'openssl_spki_new' => 'Safe\openssl_spki_new',
            'openssl_spki_verify' => 'Safe\openssl_spki_verify',
            'openssl_verify' => 'Safe\openssl_verify',
            'openssl_x509_checkpurpose' => 'Safe\openssl_x509_checkpurpose',
            'openssl_x509_export' => 'Safe\openssl_x509_export',
            'openssl_x509_export_to_file' => 'Safe\openssl_x509_export_to_file',
            'openssl_x509_fingerprint' => 'Safe\openssl_x509_fingerprint',
            'openssl_x509_read' => 'Safe\openssl_x509_read',
            'output_add_rewrite_var' => 'Safe\output_add_rewrite_var',
            'output_reset_rewrite_vars' => 'Safe\output_reset_rewrite_vars',
            'parse_ini_file' => 'Safe\parse_ini_file',
            'parse_ini_string' => 'Safe\parse_ini_string',
            'parse_url' => 'Safe\parse_url',
            'passthru' => 'Safe\passthru',
            'pclose' => 'Safe\pclose',
            'pcntl_getcpuaffinity' => 'Safe\pcntl_getcpuaffinity',
            'pcntl_getpriority' => 'Safe\pcntl_getpriority',
            'pcntl_setcpuaffinity' => 'Safe\pcntl_setcpuaffinity',
            'pcntl_setpriority' => 'Safe\pcntl_setpriority',
            'pcntl_signal' => 'Safe\pcntl_signal',
            'pcntl_signal_dispatch' => 'Safe\pcntl_signal_dispatch',
            'pcntl_sigprocmask' => 'Safe\pcntl_sigprocmask',
            'pcntl_sigtimedwait' => 'Safe\pcntl_sigtimedwait',
            'pcntl_sigwaitinfo' => 'Safe\pcntl_sigwaitinfo',
            'pfsockopen' => 'Safe\pfsockopen',
            'pg_cancel_query' => 'Safe\pg_cancel_query',
            'pg_connect' => 'Safe\pg_connect',
            'pg_connection_reset' => 'Safe\pg_connection_reset',
            'pg_convert' => 'Safe\pg_convert',
            'pg_copy_from' => 'Safe\pg_copy_from',
            'pg_copy_to' => 'Safe\pg_copy_to',
            'pg_delete' => 'Safe\pg_delete',
            'pg_end_copy' => 'Safe\pg_end_copy',
            'pg_execute' => 'Safe\pg_execute',
            'pg_field_num' => 'Safe\pg_field_num',
            'pg_field_table' => 'Safe\pg_field_table',
            'pg_flush' => 'Safe\pg_flush',
            'pg_free_result' => 'Safe\pg_free_result',
            'pg_host' => 'Safe\pg_host',
            'pg_insert' => 'Safe\pg_insert',
            'pg_last_oid' => 'Safe\pg_last_oid',
            'pg_lo_close' => 'Safe\pg_lo_close',
            'pg_lo_export' => 'Safe\pg_lo_export',
            'pg_lo_import' => 'Safe\pg_lo_import',
            'pg_lo_open' => 'Safe\pg_lo_open',
            'pg_lo_read' => 'Safe\pg_lo_read',
            'pg_lo_seek' => 'Safe\pg_lo_seek',
            'pg_lo_truncate' => 'Safe\pg_lo_truncate',
            'pg_lo_unlink' => 'Safe\pg_lo_unlink',
            'pg_lo_write' => 'Safe\pg_lo_write',
            'pg_meta_data' => 'Safe\pg_meta_data',
            'pg_parameter_status' => 'Safe\pg_parameter_status',
            'pg_pconnect' => 'Safe\pg_pconnect',
            'pg_ping' => 'Safe\pg_ping',
            'pg_prepare' => 'Safe\pg_prepare',
            'pg_put_line' => 'Safe\pg_put_line',
            'pg_query' => 'Safe\pg_query',
            'pg_query_params' => 'Safe\pg_query_params',
            'pg_result_error_field' => 'Safe\pg_result_error_field',
            'pg_result_seek' => 'Safe\pg_result_seek',
            'pg_select' => 'Safe\pg_select',
            'pg_set_chunked_rows_size' => 'Safe\pg_set_chunked_rows_size',
            'pg_socket' => 'Safe\pg_socket',
            'pg_trace' => 'Safe\pg_trace',
            'pg_update' => 'Safe\pg_update',
            'php_sapi_name' => 'Safe\php_sapi_name',
            'popen' => 'Safe\popen',
            'posix_access' => 'Safe\posix_access',
            'posix_eaccess' => 'Safe\posix_eaccess',
            'posix_getgrgid' => 'Safe\posix_getgrgid',
            'posix_getgrnam' => 'Safe\posix_getgrnam',
            'posix_getgroups' => 'Safe\posix_getgroups',
            'posix_getlogin' => 'Safe\posix_getlogin',
            'posix_getpgid' => 'Safe\posix_getpgid',
            'posix_getpwuid' => 'Safe\posix_getpwuid',
            'posix_getrlimit' => 'Safe\posix_getrlimit',
            'posix_getsid' => 'Safe\posix_getsid',
            'posix_initgroups' => 'Safe\posix_initgroups',
            'posix_kill' => 'Safe\posix_kill',
            'posix_mkfifo' => 'Safe\posix_mkfifo',
            'posix_mknod' => 'Safe\posix_mknod',
            'posix_setegid' => 'Safe\posix_setegid',
            'posix_seteuid' => 'Safe\posix_seteuid',
            'posix_setgid' => 'Safe\posix_setgid',
            'posix_setpgid' => 'Safe\posix_setpgid',
            'posix_setrlimit' => 'Safe\posix_setrlimit',
            'posix_setsid' => 'Safe\posix_setsid',
            'posix_setuid' => 'Safe\posix_setuid',
            'posix_times' => 'Safe\posix_times',
            'posix_uname' => 'Safe\posix_uname',
            'preg_grep' => 'Safe\preg_grep',
            'preg_match' => 'Safe\preg_match',
            'preg_match_all' => 'Safe\preg_match_all',
            'preg_replace' => 'Safe\preg_replace',
            'preg_replace_callback' => 'Safe\preg_replace_callback',
            'preg_replace_callback_array' => 'Safe\preg_replace_callback_array',
            'preg_split' => 'Safe\preg_split',
            'proc_close' => 'Safe\proc_close',
            'proc_nice' => 'Safe\proc_nice',
            'proc_open' => 'Safe\proc_open',
            'pspell_add_to_personal' => 'Safe\pspell_add_to_personal',
            'pspell_add_to_session' => 'Safe\pspell_add_to_session',
            'pspell_clear_session' => 'Safe\pspell_clear_session',
            'pspell_config_data_dir' => 'Safe\pspell_config_data_dir',
            'pspell_config_dict_dir' => 'Safe\pspell_config_dict_dir',
            'pspell_config_ignore' => 'Safe\pspell_config_ignore',
            'pspell_config_mode' => 'Safe\pspell_config_mode',
            'pspell_config_personal' => 'Safe\pspell_config_personal',
            'pspell_config_repl' => 'Safe\pspell_config_repl',
            'pspell_config_runtogether' => 'Safe\pspell_config_runtogether',
            'pspell_config_save_repl' => 'Safe\pspell_config_save_repl',
            'pspell_new' => 'Safe\pspell_new',
            'pspell_new_config' => 'Safe\pspell_new_config',
            'pspell_new_personal' => 'Safe\pspell_new_personal',
            'pspell_save_wordlist' => 'Safe\pspell_save_wordlist',
            'pspell_store_replacement' => 'Safe\pspell_store_replacement',
            'ps_add_launchlink' => 'Safe\ps_add_launchlink',
            'ps_add_locallink' => 'Safe\ps_add_locallink',
            'ps_add_note' => 'Safe\ps_add_note',
            'ps_add_pdflink' => 'Safe\ps_add_pdflink',
            'ps_add_weblink' => 'Safe\ps_add_weblink',
            'ps_arc' => 'Safe\ps_arc',
            'ps_arcn' => 'Safe\ps_arcn',
            'ps_begin_page' => 'Safe\ps_begin_page',
            'ps_begin_pattern' => 'Safe\ps_begin_pattern',
            'ps_begin_template' => 'Safe\ps_begin_template',
            'ps_circle' => 'Safe\ps_circle',
            'ps_clip' => 'Safe\ps_clip',
            'ps_close' => 'Safe\ps_close',
            'ps_closepath' => 'Safe\ps_closepath',
            'ps_closepath_stroke' => 'Safe\ps_closepath_stroke',
            'ps_close_image' => 'Safe\ps_close_image',
            'ps_continue_text' => 'Safe\ps_continue_text',
            'ps_curveto' => 'Safe\ps_curveto',
            'ps_delete' => 'Safe\ps_delete',
            'ps_end_page' => 'Safe\ps_end_page',
            'ps_end_pattern' => 'Safe\ps_end_pattern',
            'ps_end_template' => 'Safe\ps_end_template',
            'ps_fill' => 'Safe\ps_fill',
            'ps_fill_stroke' => 'Safe\ps_fill_stroke',
            'ps_get_parameter' => 'Safe\ps_get_parameter',
            'ps_hyphenate' => 'Safe\ps_hyphenate',
            'ps_include_file' => 'Safe\ps_include_file',
            'ps_lineto' => 'Safe\ps_lineto',
            'ps_moveto' => 'Safe\ps_moveto',
            'ps_new' => 'Safe\ps_new',
            'ps_open_file' => 'Safe\ps_open_file',
            'ps_place_image' => 'Safe\ps_place_image',
            'ps_rect' => 'Safe\ps_rect',
            'ps_restore' => 'Safe\ps_restore',
            'ps_rotate' => 'Safe\ps_rotate',
            'ps_save' => 'Safe\ps_save',
            'ps_scale' => 'Safe\ps_scale',
            'ps_setcolor' => 'Safe\ps_setcolor',
            'ps_setdash' => 'Safe\ps_setdash',
            'ps_setflat' => 'Safe\ps_setflat',
            'ps_setfont' => 'Safe\ps_setfont',
            'ps_setgray' => 'Safe\ps_setgray',
            'ps_setlinecap' => 'Safe\ps_setlinecap',
            'ps_setlinejoin' => 'Safe\ps_setlinejoin',
            'ps_setlinewidth' => 'Safe\ps_setlinewidth',
            'ps_setmiterlimit' => 'Safe\ps_setmiterlimit',
            'ps_setoverprintmode' => 'Safe\ps_setoverprintmode',
            'ps_setpolydash' => 'Safe\ps_setpolydash',
            'ps_set_border_color' => 'Safe\ps_set_border_color',
            'ps_set_border_dash' => 'Safe\ps_set_border_dash',
            'ps_set_border_style' => 'Safe\ps_set_border_style',
            'ps_set_info' => 'Safe\ps_set_info',
            'ps_set_parameter' => 'Safe\ps_set_parameter',
            'ps_set_text_pos' => 'Safe\ps_set_text_pos',
            'ps_set_value' => 'Safe\ps_set_value',
            'ps_shading' => 'Safe\ps_shading',
            'ps_shading_pattern' => 'Safe\ps_shading_pattern',
            'ps_shfill' => 'Safe\ps_shfill',
            'ps_show' => 'Safe\ps_show',
            'ps_show2' => 'Safe\ps_show2',
            'ps_show_xy' => 'Safe\ps_show_xy',
            'ps_show_xy2' => 'Safe\ps_show_xy2',
            'ps_stroke' => 'Safe\ps_stroke',
            'ps_symbol' => 'Safe\ps_symbol',
            'ps_translate' => 'Safe\ps_translate',
            'putenv' => 'Safe\putenv',
            'readfile' => 'Safe\readfile',
            'readgzfile' => 'Safe\readgzfile',
            'readline_completion_function' => 'Safe\readline_completion_function',
            'readline_read_history' => 'Safe\readline_read_history',
            'readline_write_history' => 'Safe\readline_write_history',
            'readlink' => 'Safe\readlink',
            'realpath' => 'Safe\realpath',
            'register_tick_function' => 'Safe\register_tick_function',
            'rename' => 'Safe\rename',
            'rewind' => 'Safe\rewind',
            'rmdir' => 'Safe\rmdir',
            'rnp_decrypt' => 'Safe\rnp_decrypt',
            'rnp_dump_packets' => 'Safe\rnp_dump_packets',
            'rnp_dump_packets_to_json' => 'Safe\rnp_dump_packets_to_json',
            'rnp_ffi_create' => 'Safe\rnp_ffi_create',
            'rnp_ffi_set_pass_provider' => 'Safe\rnp_ffi_set_pass_provider',
            'rnp_import_keys' => 'Safe\rnp_import_keys',
            'rnp_import_signatures' => 'Safe\rnp_import_signatures',
            'rnp_key_export' => 'Safe\rnp_key_export',
            'rnp_key_export_autocrypt' => 'Safe\rnp_key_export_autocrypt',
            'rnp_key_export_revocation' => 'Safe\rnp_key_export_revocation',
            'rnp_key_get_info' => 'Safe\rnp_key_get_info',
            'rnp_key_remove' => 'Safe\rnp_key_remove',
            'rnp_key_revoke' => 'Safe\rnp_key_revoke',
            'rnp_list_keys' => 'Safe\rnp_list_keys',
            'rnp_load_keys' => 'Safe\rnp_load_keys',
            'rnp_load_keys_from_path' => 'Safe\rnp_load_keys_from_path',
            'rnp_locate_key' => 'Safe\rnp_locate_key',
            'rnp_op_encrypt' => 'Safe\rnp_op_encrypt',
            'rnp_op_generate_key' => 'Safe\rnp_op_generate_key',
            'rnp_op_sign' => 'Safe\rnp_op_sign',
            'rnp_op_sign_cleartext' => 'Safe\rnp_op_sign_cleartext',
            'rnp_op_sign_detached' => 'Safe\rnp_op_sign_detached',
            'rnp_op_verify' => 'Safe\rnp_op_verify',
            'rnp_op_verify_detached' => 'Safe\rnp_op_verify_detached',
            'rnp_save_keys' => 'Safe\rnp_save_keys',
            'rnp_save_keys_to_path' => 'Safe\rnp_save_keys_to_path',
            'rnp_supported_features' => 'Safe\rnp_supported_features',
            'rpmaddtag' => 'Safe\rpmaddtag',
            'rpmdefine' => 'Safe\rpmdefine',
            'rrd_create' => 'Safe\rrd_create',
            'rrd_first' => 'Safe\rrd_first',
            'rrd_graph' => 'Safe\rrd_graph',
            'rrd_info' => 'Safe\rrd_info',
            'rrd_lastupdate' => 'Safe\rrd_lastupdate',
            'rrd_restore' => 'Safe\rrd_restore',
            'rrd_tune' => 'Safe\rrd_tune',
            'rrd_update' => 'Safe\rrd_update',
            'rrd_xport' => 'Safe\rrd_xport',
            'sapi_windows_cp_conv' => 'Safe\sapi_windows_cp_conv',
            'sapi_windows_cp_set' => 'Safe\sapi_windows_cp_set',
            'sapi_windows_generate_ctrl_event' => 'Safe\sapi_windows_generate_ctrl_event',
            'sapi_windows_set_ctrl_handler' => 'Safe\sapi_windows_set_ctrl_handler',
            'sapi_windows_vt100_support' => 'Safe\sapi_windows_vt100_support',
            'scandir' => 'Safe\scandir',
            'sem_acquire' => 'Safe\sem_acquire',
            'sem_get' => 'Safe\sem_get',
            'sem_release' => 'Safe\sem_release',
            'sem_remove' => 'Safe\sem_remove',
            'session_abort' => 'Safe\session_abort',
            'session_cache_expire' => 'Safe\session_cache_expire',
            'session_cache_limiter' => 'Safe\session_cache_limiter',
            'session_create_id' => 'Safe\session_create_id',
            'session_decode' => 'Safe\session_decode',
            'session_destroy' => 'Safe\session_destroy',
            'session_encode' => 'Safe\session_encode',
            'session_gc' => 'Safe\session_gc',
            'session_id' => 'Safe\session_id',
            'session_module_name' => 'Safe\session_module_name',
            'session_name' => 'Safe\session_name',
            'session_regenerate_id' => 'Safe\session_regenerate_id',
            'session_reset' => 'Safe\session_reset',
            'session_save_path' => 'Safe\session_save_path',
            'session_start' => 'Safe\session_start',
            'session_unset' => 'Safe\session_unset',
            'session_write_close' => 'Safe\session_write_close',
            'settype' => 'Safe\settype',
            'set_include_path' => 'Safe\set_include_path',
            'set_time_limit' => 'Safe\set_time_limit',
            'sha1_file' => 'Safe\sha1_file',
            'shell_exec' => 'Safe\shell_exec',
            'shmop_delete' => 'Safe\shmop_delete',
            'shm_attach' => 'Safe\shm_attach',
            'shm_detach' => 'Safe\shm_detach',
            'shm_put_var' => 'Safe\shm_put_var',
            'shm_remove' => 'Safe\shm_remove',
            'shm_remove_var' => 'Safe\shm_remove_var',
            'simplexml_import_dom' => 'Safe\simplexml_import_dom',
            'simplexml_load_file' => 'Safe\simplexml_load_file',
            'simplexml_load_string' => 'Safe\simplexml_load_string',
            'socket_accept' => 'Safe\socket_accept',
            'socket_addrinfo_bind' => 'Safe\socket_addrinfo_bind',
            'socket_addrinfo_connect' => 'Safe\socket_addrinfo_connect',
            'socket_addrinfo_lookup' => 'Safe\socket_addrinfo_lookup',
            'socket_atmark' => 'Safe\socket_atmark',
            'socket_bind' => 'Safe\socket_bind',
            'socket_connect' => 'Safe\socket_connect',
            'socket_create' => 'Safe\socket_create',
            'socket_create_listen' => 'Safe\socket_create_listen',
            'socket_create_pair' => 'Safe\socket_create_pair',
            'socket_export_stream' => 'Safe\socket_export_stream',
            'socket_getpeername' => 'Safe\socket_getpeername',
            'socket_getsockname' => 'Safe\socket_getsockname',
            'socket_get_option' => 'Safe\socket_get_option',
            'socket_import_stream' => 'Safe\socket_import_stream',
            'socket_listen' => 'Safe\socket_listen',
            'socket_read' => 'Safe\socket_read',
            'socket_send' => 'Safe\socket_send',
            'socket_sendmsg' => 'Safe\socket_sendmsg',
            'socket_sendto' => 'Safe\socket_sendto',
            'socket_set_block' => 'Safe\socket_set_block',
            'socket_set_nonblock' => 'Safe\socket_set_nonblock',
            'socket_set_option' => 'Safe\socket_set_option',
            'socket_shutdown' => 'Safe\socket_shutdown',
            'socket_write' => 'Safe\socket_write',
            'socket_wsaprotocol_info_export' => 'Safe\socket_wsaprotocol_info_export',
            'socket_wsaprotocol_info_import' => 'Safe\socket_wsaprotocol_info_import',
            'socket_wsaprotocol_info_release' => 'Safe\socket_wsaprotocol_info_release',
            'sodium_crypto_aead_aegis128l_decrypt' => 'Safe\sodium_crypto_aead_aegis128l_decrypt',
            'sodium_crypto_aead_aegis256_decrypt' => 'Safe\sodium_crypto_aead_aegis256_decrypt',
            'sodium_crypto_aead_aes256gcm_decrypt' => 'Safe\sodium_crypto_aead_aes256gcm_decrypt',
            'sodium_crypto_aead_chacha20poly1305_decrypt' => 'Safe\sodium_crypto_aead_chacha20poly1305_decrypt',
            'sodium_crypto_aead_chacha20poly1305_encrypt' => 'Safe\sodium_crypto_aead_chacha20poly1305_encrypt',
            'sodium_crypto_aead_chacha20poly1305_ietf_decrypt' => 'Safe\sodium_crypto_aead_chacha20poly1305_ietf_decrypt',
            'sodium_crypto_aead_chacha20poly1305_ietf_encrypt' => 'Safe\sodium_crypto_aead_chacha20poly1305_ietf_encrypt',
            'sodium_crypto_aead_xchacha20poly1305_ietf_decrypt' => 'Safe\sodium_crypto_aead_xchacha20poly1305_ietf_decrypt',
            'sodium_crypto_aead_xchacha20poly1305_ietf_encrypt' => 'Safe\sodium_crypto_aead_xchacha20poly1305_ietf_encrypt',
            'sodium_crypto_box_open' => 'Safe\sodium_crypto_box_open',
            'sodium_crypto_box_seal_open' => 'Safe\sodium_crypto_box_seal_open',
            'sodium_crypto_secretbox_open' => 'Safe\sodium_crypto_secretbox_open',
            'sodium_crypto_sign_open' => 'Safe\sodium_crypto_sign_open',
            'sodium_crypto_stream_xchacha20_xor_ic' => 'Safe\sodium_crypto_stream_xchacha20_xor_ic',
            'solr_get_version' => 'Safe\solr_get_version',
            'spl_autoload_register' => 'Safe\spl_autoload_register',
            'spl_autoload_unregister' => 'Safe\spl_autoload_unregister',
            'sqlsrv_begin_transaction' => 'Safe\sqlsrv_begin_transaction',
            'sqlsrv_cancel' => 'Safe\sqlsrv_cancel',
            'sqlsrv_client_info' => 'Safe\sqlsrv_client_info',
            'sqlsrv_close' => 'Safe\sqlsrv_close',
            'sqlsrv_commit' => 'Safe\sqlsrv_commit',
            'sqlsrv_configure' => 'Safe\sqlsrv_configure',
            'sqlsrv_execute' => 'Safe\sqlsrv_execute',
            'sqlsrv_fetch' => 'Safe\sqlsrv_fetch',
            'sqlsrv_fetch_array' => 'Safe\sqlsrv_fetch_array',
            'sqlsrv_fetch_object' => 'Safe\sqlsrv_fetch_object',
            'sqlsrv_free_stmt' => 'Safe\sqlsrv_free_stmt',
            'sqlsrv_get_field' => 'Safe\sqlsrv_get_field',
            'sqlsrv_next_result' => 'Safe\sqlsrv_next_result',
            'sqlsrv_num_fields' => 'Safe\sqlsrv_num_fields',
            'sqlsrv_num_rows' => 'Safe\sqlsrv_num_rows',
            'sqlsrv_prepare' => 'Safe\sqlsrv_prepare',
            'sqlsrv_query' => 'Safe\sqlsrv_query',
            'sqlsrv_rollback' => 'Safe\sqlsrv_rollback',
            'ssdeep_fuzzy_compare' => 'Safe\ssdeep_fuzzy_compare',
            'ssdeep_fuzzy_hash' => 'Safe\ssdeep_fuzzy_hash',
            'ssdeep_fuzzy_hash_filename' => 'Safe\ssdeep_fuzzy_hash_filename',
            'ssh2_auth_agent' => 'Safe\ssh2_auth_agent',
            'ssh2_auth_hostbased_file' => 'Safe\ssh2_auth_hostbased_file',
            'ssh2_auth_password' => 'Safe\ssh2_auth_password',
            'ssh2_auth_pubkey_file' => 'Safe\ssh2_auth_pubkey_file',
            'ssh2_connect' => 'Safe\ssh2_connect',
            'ssh2_disconnect' => 'Safe\ssh2_disconnect',
            'ssh2_exec' => 'Safe\ssh2_exec',
            'ssh2_forward_accept' => 'Safe\ssh2_forward_accept',
            'ssh2_forward_listen' => 'Safe\ssh2_forward_listen',
            'ssh2_publickey_add' => 'Safe\ssh2_publickey_add',
            'ssh2_publickey_init' => 'Safe\ssh2_publickey_init',
            'ssh2_publickey_remove' => 'Safe\ssh2_publickey_remove',
            'ssh2_scp_recv' => 'Safe\ssh2_scp_recv',
            'ssh2_scp_send' => 'Safe\ssh2_scp_send',
            'ssh2_send_eof' => 'Safe\ssh2_send_eof',
            'ssh2_sftp' => 'Safe\ssh2_sftp',
            'ssh2_sftp_chmod' => 'Safe\ssh2_sftp_chmod',
            'ssh2_sftp_mkdir' => 'Safe\ssh2_sftp_mkdir',
            'ssh2_sftp_rename' => 'Safe\ssh2_sftp_rename',
            'ssh2_sftp_rmdir' => 'Safe\ssh2_sftp_rmdir',
            'ssh2_sftp_symlink' => 'Safe\ssh2_sftp_symlink',
            'ssh2_sftp_unlink' => 'Safe\ssh2_sftp_unlink',
            'ssh2_shell' => 'Safe\ssh2_shell',
            'stream_context_set_options' => 'Safe\stream_context_set_options',
            'stream_context_set_params' => 'Safe\stream_context_set_params',
            'stream_copy_to_stream' => 'Safe\stream_copy_to_stream',
            'stream_filter_append' => 'Safe\stream_filter_append',
            'stream_filter_prepend' => 'Safe\stream_filter_prepend',
            'stream_filter_register' => 'Safe\stream_filter_register',
            'stream_filter_remove' => 'Safe\stream_filter_remove',
            'stream_get_contents' => 'Safe\stream_get_contents',
            'stream_get_line' => 'Safe\stream_get_line',
            'stream_isatty' => 'Safe\stream_isatty',
            'stream_resolve_include_path' => 'Safe\stream_resolve_include_path',
            'stream_set_blocking' => 'Safe\stream_set_blocking',
            'stream_set_timeout' => 'Safe\stream_set_timeout',
            'stream_socket_accept' => 'Safe\stream_socket_accept',
            'stream_socket_client' => 'Safe\stream_socket_client',
            'stream_socket_get_name' => 'Safe\stream_socket_get_name',
            'stream_socket_pair' => 'Safe\stream_socket_pair',
            'stream_socket_recvfrom' => 'Safe\stream_socket_recvfrom',
            'stream_socket_sendto' => 'Safe\stream_socket_sendto',
            'stream_socket_server' => 'Safe\stream_socket_server',
            'stream_socket_shutdown' => 'Safe\stream_socket_shutdown',
            'stream_supports_lock' => 'Safe\stream_supports_lock',
            'stream_wrapper_register' => 'Safe\stream_wrapper_register',
            'stream_wrapper_restore' => 'Safe\stream_wrapper_restore',
            'stream_wrapper_unregister' => 'Safe\stream_wrapper_unregister',
            'strftime' => 'Safe\strftime',
            'strptime' => 'Safe\strptime',
            'strtotime' => 'Safe\strtotime',
            'swoole_async_dns_lookup' => 'Safe\swoole_async_dns_lookup',
            'swoole_async_readfile' => 'Safe\swoole_async_readfile',
            'swoole_async_write' => 'Safe\swoole_async_write',
            'swoole_async_writefile' => 'Safe\swoole_async_writefile',
            'swoole_event_defer' => 'Safe\swoole_event_defer',
            'swoole_event_del' => 'Safe\swoole_event_del',
            'swoole_event_write' => 'Safe\swoole_event_write',
            'symlink' => 'Safe\symlink',
            'system' => 'Safe\system',
            'sys_getloadavg' => 'Safe\sys_getloadavg',
            'tempnam' => 'Safe\tempnam',
            'timezone_name_from_abbr' => 'Safe\timezone_name_from_abbr',
            'time_nanosleep' => 'Safe\time_nanosleep',
            'time_sleep_until' => 'Safe\time_sleep_until',
            'tmpfile' => 'Safe\tmpfile',
            'touch' => 'Safe\touch',
            'unixtojd' => 'Safe\unixtojd',
            'unlink' => 'Safe\unlink',
            'unpack' => 'Safe\unpack',
            'unserialize' => 'Safe\unserialize',
            'uopz_extend' => 'Safe\uopz_extend',
            'uopz_implement' => 'Safe\uopz_implement',
            'variant_date_to_timestamp' => 'Safe\variant_date_to_timestamp',
            'variant_round' => 'Safe\variant_round',
            'virtual' => 'Safe\virtual',
            'xdiff_file_bdiff' => 'Safe\xdiff_file_bdiff',
            'xdiff_file_bpatch' => 'Safe\xdiff_file_bpatch',
            'xdiff_file_diff' => 'Safe\xdiff_file_diff',
            'xdiff_file_diff_binary' => 'Safe\xdiff_file_diff_binary',
            'xdiff_file_patch_binary' => 'Safe\xdiff_file_patch_binary',
            'xdiff_file_rabdiff' => 'Safe\xdiff_file_rabdiff',
            'xdiff_string_bpatch' => 'Safe\xdiff_string_bpatch',
            'xdiff_string_patch' => 'Safe\xdiff_string_patch',
            'xdiff_string_patch_binary' => 'Safe\xdiff_string_patch_binary',
            'xdiff_string_rabdiff' => 'Safe\xdiff_string_rabdiff',
            'xmlrpc_set_type' => 'Safe\xmlrpc_set_type',
            'xml_parser_free' => 'Safe\xml_parser_free',
            'xml_parser_set_option' => 'Safe\xml_parser_set_option',
            'yaml_parse' => 'Safe\yaml_parse',
            'yaml_parse_url' => 'Safe\yaml_parse_url',
            'yaz_ccl_parse' => 'Safe\yaz_ccl_parse',
            'yaz_close' => 'Safe\yaz_close',
            'yaz_connect' => 'Safe\yaz_connect',
            'yaz_database' => 'Safe\yaz_database',
            'yaz_element' => 'Safe\yaz_element',
            'yaz_present' => 'Safe\yaz_present',
            'yaz_search' => 'Safe\yaz_search',
            'yaz_wait' => 'Safe\yaz_wait',
            'zip_entry_close' => 'Safe\zip_entry_close',
            'zip_entry_compressedsize' => 'Safe\zip_entry_compressedsize',
            'zip_entry_compressionmethod' => 'Safe\zip_entry_compressionmethod',
            'zip_entry_filesize' => 'Safe\zip_entry_filesize',
            'zip_entry_name' => 'Safe\zip_entry_name',
            'zip_entry_open' => 'Safe\zip_entry_open',
            'zip_entry_read' => 'Safe\zip_entry_read',
            'zlib_decode' => 'Safe\zlib_decode',
        ]
    );
};
<?php

namespace Safe;

use Safe\Exceptions\YamlException;

/**
 * @param string $filename
 * @param int $pos
 * @param int|null $ndocs
 * @param array|null $callbacks
 * @return mixed
 *
 */
function yaml_parse_file(string $filename, int $pos = 0, ?int &$ndocs = null, ?array $callbacks = null)
{
    error_clear_last();
    if ($callbacks !== null) {
        $safeResult = \yaml_parse_file($filename, $pos, $ndocs, $callbacks);
    } else {
        $safeResult = \yaml_parse_file($filename, $pos, $ndocs);
    }
    return $safeResult;
}


/**
 * @param string $url
 * @param int $pos
 * @param int|null $ndocs
 * @param array|null $callbacks
 * @return mixed
 * @throws YamlException
 *
 */
function yaml_parse_url(string $url, int $pos = 0, ?int &$ndocs = null, ?array $callbacks = null)
{
    error_clear_last();
    if ($callbacks !== null) {
        $safeResult = \yaml_parse_url($url, $pos, $ndocs, $callbacks);
    } else {
        $safeResult = \yaml_parse_url($url, $pos, $ndocs);
    }
    if ($safeResult === false) {
        throw YamlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $input
 * @param int $pos
 * @param int|null $ndocs
 * @param array|null $callbacks
 * @return mixed
 * @throws YamlException
 *
 */
function yaml_parse(string $input, int $pos = 0, ?int &$ndocs = null, ?array $callbacks = null)
{
    error_clear_last();
    if ($callbacks !== null) {
        $safeResult = \yaml_parse($input, $pos, $ndocs, $callbacks);
    } else {
        $safeResult = \yaml_parse($input, $pos, $ndocs);
    }
    if ($safeResult === false) {
        throw YamlException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\ZlibException;

/**
 * @param \DeflateContext $context
 * @param string $data
 * @param int $flush_mode
 * @return string
 * @throws ZlibException
 *
 */
function deflate_add(\DeflateContext $context, string $data, int $flush_mode = ZLIB_SYNC_FLUSH): string
{
    error_clear_last();
    $safeResult = \deflate_add($context, $data, $flush_mode);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $encoding
 * @param array $options
 * @return \DeflateContext
 * @throws ZlibException
 *
 */
function deflate_init(int $encoding, array $options = []): \DeflateContext
{
    error_clear_last();
    $safeResult = \deflate_init($encoding, $options);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @throws ZlibException
 *
 */
function gzclose($stream): void
{
    error_clear_last();
    $safeResult = \gzclose($stream);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
}


/**
 * @param string $data
 * @param int $level
 * @param int $encoding
 * @return string
 * @throws ZlibException
 *
 */
function gzcompress(string $data, int $level = -1, int $encoding = ZLIB_ENCODING_DEFLATE): string
{
    error_clear_last();
    $safeResult = \gzcompress($data, $level, $encoding);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param int $max_length
 * @return string
 * @throws ZlibException
 *
 */
function gzdecode(string $data, int $max_length = 0): string
{
    error_clear_last();
    $safeResult = \gzdecode($data, $max_length);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param int $level
 * @param int $encoding
 * @return string
 * @throws ZlibException
 *
 */
function gzdeflate(string $data, int $level = -1, int $encoding = ZLIB_ENCODING_RAW): string
{
    error_clear_last();
    $safeResult = \gzdeflate($data, $level, $encoding);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param int $level
 * @param int $encoding
 * @return string
 * @throws ZlibException
 *
 */
function gzencode(string $data, int $level = -1, int $encoding = ZLIB_ENCODING_GZIP): string
{
    error_clear_last();
    $safeResult = \gzencode($data, $level, $encoding);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param  $use_include_path
 * @return list
 * @throws ZlibException
 *
 */
function gzfile(string $filename, $use_include_path = false): array
{
    error_clear_last();
    $safeResult = \gzfile($filename, $use_include_path);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @param int|null $length
 * @return string
 * @throws ZlibException
 *
 */
function gzgets($stream, ?int $length = null): string
{
    error_clear_last();
    if ($length !== null) {
        $safeResult = \gzgets($stream, $length);
    } else {
        $safeResult = \gzgets($stream);
    }
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param int $max_length
 * @return string
 * @throws ZlibException
 *
 */
function gzinflate(string $data, int $max_length = 0): string
{
    error_clear_last();
    $safeResult = \gzinflate($data, $max_length);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param string $mode
 * @param  $use_include_path
 * @return resource
 * @throws ZlibException
 *
 */
function gzopen(string $filename, string $mode, $use_include_path = false)
{
    error_clear_last();
    $safeResult = \gzopen($filename, $mode, $use_include_path);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @return false|int
 *
 */
function gzpassthru($stream)
{
    error_clear_last();
    $safeResult = \gzpassthru($stream);
    return $safeResult;
}


/**
 * @param resource $stream
 * @param int $length
 * @return string
 * @throws ZlibException
 *
 */
function gzread($stream, int $length): string
{
    error_clear_last();
    $safeResult = \gzread($stream, $length);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @throws ZlibException
 *
 */
function gzrewind($stream): void
{
    error_clear_last();
    $safeResult = \gzrewind($stream);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
}


/**
 * @param resource $stream
 * @return int
 * @throws ZlibException
 *
 */
function gztell($stream): int
{
    error_clear_last();
    $safeResult = \gztell($stream);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param int $max_length
 * @return string
 * @throws ZlibException
 *
 */
function gzuncompress(string $data, int $max_length = 0): string
{
    error_clear_last();
    $safeResult = \gzuncompress($data, $max_length);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @param string $data
 * @param int|null $length
 * @return int
 * @throws ZlibException
 *
 */
function gzwrite($stream, string $data, ?int $length = null): int
{
    error_clear_last();
    if ($length !== null) {
        $safeResult = \gzwrite($stream, $data, $length);
    } else {
        $safeResult = \gzwrite($stream, $data);
    }
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \InflateContext $context
 * @return int
 * @throws ZlibException
 *
 */
function inflate_get_read_len(\InflateContext $context): int
{
    error_clear_last();
    $safeResult = \inflate_get_read_len($context);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \InflateContext $context
 * @return false|int
 *
 */
function inflate_get_status(\InflateContext $context)
{
    error_clear_last();
    $safeResult = \inflate_get_status($context);
    return $safeResult;
}


/**
 * @param \InflateContext $context
 * @param string $data
 * @param int $flush_mode
 * @return string
 * @throws ZlibException
 *
 */
function inflate_add(\InflateContext $context, string $data, int $flush_mode = ZLIB_SYNC_FLUSH): string
{
    error_clear_last();
    $safeResult = \inflate_add($context, $data, $flush_mode);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $encoding
 * @param array $options
 * @return \InflateContext
 * @throws ZlibException
 *
 */
function inflate_init(int $encoding, array $options = []): \InflateContext
{
    error_clear_last();
    $safeResult = \inflate_init($encoding, $options);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param  $use_include_path
 * @return 0|positive-int
 * @throws ZlibException
 *
 */
function readgzfile(string $filename, $use_include_path = false): int
{
    error_clear_last();
    $safeResult = \readgzfile($filename, $use_include_path);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param int $max_length
 * @return string
 * @throws ZlibException
 *
 */
function zlib_decode(string $data, int $max_length = 0): string
{
    error_clear_last();
    $safeResult = \zlib_decode($data, $max_length);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\LdapException;

/**
 * @param string $value
 * @return string
 * @throws LdapException
 *
 */
function ldap_8859_to_t61(string $value): string
{
    error_clear_last();
    $safeResult = \ldap_8859_to_t61($value);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param array $entry
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_add(\LDAP\Connection $ldap, string $dn, array $entry, ?array $controls = null): void
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_add($ldap, $dn, $entry, $controls);
    } else {
        $safeResult = \ldap_add($ldap, $dn, $entry);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param null|string $dn
 * @param null|string $password
 * @throws LdapException
 *
 */
function ldap_bind(\LDAP\Connection $ldap, ?string $dn = null, ?string $password = null): void
{
    error_clear_last();
    if ($password !== null) {
        $safeResult = \ldap_bind($ldap, $dn, $password);
    } elseif ($dn !== null) {
        $safeResult = \ldap_bind($ldap, $dn);
    } else {
        $safeResult = \ldap_bind($ldap);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param string $attribute
 * @param string $value
 * @param array|null $controls
 * @return bool
 * @throws LdapException
 *
 */
function ldap_compare(\LDAP\Connection $ldap, string $dn, string $attribute, string $value, ?array $controls = null): bool
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_compare($ldap, $dn, $attribute, $value, $controls);
    } else {
        $safeResult = \ldap_compare($ldap, $dn, $attribute, $value);
    }
    if ($safeResult === -1) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $link
 * @param resource $result
 * @param null|string $cookie
 * @param int|null $estimated
 * @throws LdapException
 *
 */
function ldap_control_paged_result_response($link, $result, ?string &$cookie = null, ?int &$estimated = null): void
{
    error_clear_last();
    $safeResult = \ldap_control_paged_result_response($link, $result, $cookie, $estimated);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param resource $link
 * @param int $pagesize
 * @param bool $iscritical
 * @param string $cookie
 * @throws LdapException
 *
 */
function ldap_control_paged_result($link, int $pagesize, bool $iscritical = false, string $cookie = ""): void
{
    error_clear_last();
    $safeResult = \ldap_control_paged_result($link, $pagesize, $iscritical, $cookie);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\Result $result
 * @return int
 * @throws LdapException
 *
 */
function ldap_count_entries(\LDAP\Connection $ldap, \LDAP\Result $result): int
{
    error_clear_last();
    $safeResult = \ldap_count_entries($ldap, $result);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_delete(\LDAP\Connection $ldap, string $dn, ?array $controls = null): void
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_delete($ldap, $dn, $controls);
    } else {
        $safeResult = \ldap_delete($ldap, $dn);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param string $dn
 * @return string
 * @throws LdapException
 *
 */
function ldap_dn2ufn(string $dn): string
{
    error_clear_last();
    $safeResult = \ldap_dn2ufn($dn);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $user
 * @param string $old_password
 * @param string $new_password
 * @param array|null $controls
 * @return bool|string
 * @throws LdapException
 *
 */
function ldap_exop_passwd(\LDAP\Connection $ldap, string $user = "", string $old_password = "", string $new_password = "", ?array &$controls = null)
{
    error_clear_last();
    $safeResult = \ldap_exop_passwd($ldap, $user, $old_password, $new_password, $controls);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @return bool|string
 * @throws LdapException
 *
 */
function ldap_exop_whoami(\LDAP\Connection $ldap)
{
    error_clear_last();
    $safeResult = \ldap_exop_whoami($ldap);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $request_oid
 * @param null|string $request_data
 * @param array|null $controls
 * @param null|string $response_data
 * @param null|string $response_oid
 * @return bool|resource
 * @throws LdapException
 *
 */
function ldap_exop(\LDAP\Connection $ldap, string $request_oid, ?string $request_data = null, ?array $controls = null, ?string &$response_data = null, ?string &$response_oid = null)
{
    error_clear_last();
    if ($response_oid !== null) {
        $safeResult = \ldap_exop($ldap, $request_oid, $request_data, $controls, $response_data, $response_oid);
    } elseif ($response_data !== null) {
        $safeResult = \ldap_exop($ldap, $request_oid, $request_data, $controls, $response_data);
    } elseif ($controls !== null) {
        $safeResult = \ldap_exop($ldap, $request_oid, $request_data, $controls);
    } elseif ($request_data !== null) {
        $safeResult = \ldap_exop($ldap, $request_oid, $request_data);
    } else {
        $safeResult = \ldap_exop($ldap, $request_oid);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $dn
 * @param int $with_attrib
 * @return array
 * @throws LdapException
 *
 */
function ldap_explode_dn(string $dn, int $with_attrib): array
{
    error_clear_last();
    $safeResult = \ldap_explode_dn($dn, $with_attrib);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\ResultEntry $entry
 * @return string
 * @throws LdapException
 *
 */
function ldap_first_attribute(\LDAP\Connection $ldap, \LDAP\ResultEntry $entry): string
{
    error_clear_last();
    $safeResult = \ldap_first_attribute($ldap, $entry);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\Result $result
 * @return \LDAP\ResultEntry
 * @throws LdapException
 *
 */
function ldap_first_entry(\LDAP\Connection $ldap, \LDAP\Result $result): \LDAP\ResultEntry
{
    error_clear_last();
    $safeResult = \ldap_first_entry($ldap, $result);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Result $result
 * @throws LdapException
 *
 */
function ldap_free_result(\LDAP\Result $result): void
{
    error_clear_last();
    $safeResult = \ldap_free_result($result);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\ResultEntry $entry
 * @return array
 *
 */
function ldap_get_attributes(\LDAP\Connection $ldap, \LDAP\ResultEntry $entry): array
{
    error_clear_last();
    $safeResult = \ldap_get_attributes($ldap, $entry);
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\ResultEntry $entry
 * @return string
 * @throws LdapException
 *
 */
function ldap_get_dn(\LDAP\Connection $ldap, \LDAP\ResultEntry $entry): string
{
    error_clear_last();
    $safeResult = \ldap_get_dn($ldap, $entry);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\Result $result
 * @return array
 * @throws LdapException
 *
 */
function ldap_get_entries(\LDAP\Connection $ldap, \LDAP\Result $result): array
{
    error_clear_last();
    $safeResult = \ldap_get_entries($ldap, $result);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection|null $ldap
 * @param int $option
 * @param mixed $value
 * @throws LdapException
 *
 */
function ldap_get_option(?\LDAP\Connection $ldap, int $option, &$value = null): void
{
    error_clear_last();
    $safeResult = \ldap_get_option($ldap, $option, $value);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\ResultEntry $entry
 * @param string $attribute
 * @return array
 * @throws LdapException
 *
 */
function ldap_get_values_len(\LDAP\Connection $ldap, \LDAP\ResultEntry $entry, string $attribute): array
{
    error_clear_last();
    $safeResult = \ldap_get_values_len($ldap, $entry, $attribute);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\ResultEntry $entry
 * @param string $attribute
 * @return array
 * @throws LdapException
 *
 */
function ldap_get_values(\LDAP\Connection $ldap, \LDAP\ResultEntry $entry, string $attribute): array
{
    error_clear_last();
    $safeResult = \ldap_get_values($ldap, $entry, $attribute);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param array $entry
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_mod_add(\LDAP\Connection $ldap, string $dn, array $entry, ?array $controls = null): void
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_mod_add($ldap, $dn, $entry, $controls);
    } else {
        $safeResult = \ldap_mod_add($ldap, $dn, $entry);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param array $entry
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_mod_del(\LDAP\Connection $ldap, string $dn, array $entry, ?array $controls = null): void
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_mod_del($ldap, $dn, $entry, $controls);
    } else {
        $safeResult = \ldap_mod_del($ldap, $dn, $entry);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param array $entry
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_mod_replace(\LDAP\Connection $ldap, string $dn, array $entry, ?array $controls = null): void
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_mod_replace($ldap, $dn, $entry, $controls);
    } else {
        $safeResult = \ldap_mod_replace($ldap, $dn, $entry);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param array $modifications_info
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_modify_batch(\LDAP\Connection $ldap, string $dn, array $modifications_info, ?array $controls = null): void
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_modify_batch($ldap, $dn, $modifications_info, $controls);
    } else {
        $safeResult = \ldap_modify_batch($ldap, $dn, $modifications_info);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\ResultEntry $entry
 * @return string
 * @throws LdapException
 *
 */
function ldap_next_attribute(\LDAP\Connection $ldap, \LDAP\ResultEntry $entry): string
{
    error_clear_last();
    $safeResult = \ldap_next_attribute($ldap, $entry);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\Result $result
 * @param null|string $response_data
 * @param null|string $response_oid
 * @throws LdapException
 *
 */
function ldap_parse_exop(\LDAP\Connection $ldap, \LDAP\Result $result, ?string &$response_data = null, ?string &$response_oid = null): void
{
    error_clear_last();
    $safeResult = \ldap_parse_exop($ldap, $result, $response_data, $response_oid);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\Result $result
 * @param int|null $error_code
 * @param null|string $matched_dn
 * @param null|string $error_message
 * @param array|null $referrals
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_parse_result(\LDAP\Connection $ldap, \LDAP\Result $result, ?int &$error_code, ?string &$matched_dn = null, ?string &$error_message = null, ?array &$referrals = null, ?array &$controls = null): void
{
    error_clear_last();
    $safeResult = \ldap_parse_result($ldap, $result, $error_code, $matched_dn, $error_message, $referrals, $controls);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param string $new_rdn
 * @param string $new_parent
 * @param bool $delete_old_rdn
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_rename(\LDAP\Connection $ldap, string $dn, string $new_rdn, string $new_parent, bool $delete_old_rdn, ?array $controls = null): void
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_rename($ldap, $dn, $new_rdn, $new_parent, $delete_old_rdn, $controls);
    } else {
        $safeResult = \ldap_rename($ldap, $dn, $new_rdn, $new_parent, $delete_old_rdn);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param null|string $dn
 * @param null|string $password
 * @param null|string $mech
 * @param null|string $realm
 * @param null|string $authc_id
 * @param null|string $authz_id
 * @param null|string $props
 * @throws LdapException
 *
 */
function ldap_sasl_bind(\LDAP\Connection $ldap, ?string $dn = null, ?string $password = null, ?string $mech = null, ?string $realm = null, ?string $authc_id = null, ?string $authz_id = null, ?string $props = null): void
{
    error_clear_last();
    if ($props !== null) {
        $safeResult = \ldap_sasl_bind($ldap, $dn, $password, $mech, $realm, $authc_id, $authz_id, $props);
    } elseif ($authz_id !== null) {
        $safeResult = \ldap_sasl_bind($ldap, $dn, $password, $mech, $realm, $authc_id, $authz_id);
    } elseif ($authc_id !== null) {
        $safeResult = \ldap_sasl_bind($ldap, $dn, $password, $mech, $realm, $authc_id);
    } elseif ($realm !== null) {
        $safeResult = \ldap_sasl_bind($ldap, $dn, $password, $mech, $realm);
    } elseif ($mech !== null) {
        $safeResult = \ldap_sasl_bind($ldap, $dn, $password, $mech);
    } elseif ($password !== null) {
        $safeResult = \ldap_sasl_bind($ldap, $dn, $password);
    } elseif ($dn !== null) {
        $safeResult = \ldap_sasl_bind($ldap, $dn);
    } else {
        $safeResult = \ldap_sasl_bind($ldap);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param null|resource $ldap
 * @param int $option
 * @param mixed $value
 * @throws LdapException
 *
 */
function ldap_set_option($ldap, int $option, $value): void
{
    error_clear_last();
    $safeResult = \ldap_set_option($ldap, $option, $value);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @throws LdapException
 *
 */
function ldap_unbind(\LDAP\Connection $ldap): void
{
    error_clear_last();
    $safeResult = \ldap_unbind($ldap);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/session.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/session.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/session.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/session.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/session.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/session.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/fileinfo.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/fileinfo.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/fileinfo.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/fileinfo.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/fileinfo.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/fileinfo.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/oci8.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.2/oci8.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.2/oci8.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.2/oci8.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.2/oci8.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.2/oci8.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/bzip2.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/bzip2.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/bzip2.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/bzip2.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/bzip2.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/bzip2.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/var.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/var.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/var.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/var.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/var.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/var.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/com.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/com.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/com.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/com.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/com.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/com.php';
}
<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\Renaming\Rector\FuncCall\RenameFunctionRector;

// This file configures rector/rector to replace all PHP functions with their equivalent "safe" functions.
return static function (RectorConfig $rectorConfig): void {
    $rectorConfig->ruleWithConfiguration(
        RenameFunctionRector::class,
        [            'apache_getenv' => 'Safe\apache_getenv',
            'apache_get_version' => 'Safe\apache_get_version',
            'apache_lookup_uri' => 'Safe\apache_lookup_uri',
            'apache_setenv' => 'Safe\apache_setenv',
            'apcu_cache_info' => 'Safe\apcu_cache_info',
            'apcu_cas' => 'Safe\apcu_cas',
            'apcu_dec' => 'Safe\apcu_dec',
            'apcu_fetch' => 'Safe\apcu_fetch',
            'apcu_inc' => 'Safe\apcu_inc',
            'apcu_sma_info' => 'Safe\apcu_sma_info',
            'assert_options' => 'Safe\assert_options',
            'base64_decode' => 'Safe\base64_decode',
            'bindtextdomain' => 'Safe\bindtextdomain',
            'bzclose' => 'Safe\bzclose',
            'bzflush' => 'Safe\bzflush',
            'bzopen' => 'Safe\bzopen',
            'bzread' => 'Safe\bzread',
            'bzwrite' => 'Safe\bzwrite',
            'chdir' => 'Safe\chdir',
            'chgrp' => 'Safe\chgrp',
            'chmod' => 'Safe\chmod',
            'chown' => 'Safe\chown',
            'chroot' => 'Safe\chroot',
            'class_alias' => 'Safe\class_alias',
            'class_implements' => 'Safe\class_implements',
            'class_parents' => 'Safe\class_parents',
            'class_uses' => 'Safe\class_uses',
            'cli_set_process_title' => 'Safe\cli_set_process_title',
            'closelog' => 'Safe\closelog',
            'com_create_guid' => 'Safe\com_create_guid',
            'com_event_sink' => 'Safe\com_event_sink',
            'com_load_typelib' => 'Safe\com_load_typelib',
            'com_print_typeinfo' => 'Safe\com_print_typeinfo',
            'convert_uudecode' => 'Safe\convert_uudecode',
            'copy' => 'Safe\copy',
            'cubrid_bind' => 'Safe\cubrid_bind',
            'cubrid_column_names' => 'Safe\cubrid_column_names',
            'cubrid_column_types' => 'Safe\cubrid_column_types',
            'cubrid_col_size' => 'Safe\cubrid_col_size',
            'cubrid_commit' => 'Safe\cubrid_commit',
            'cubrid_connect' => 'Safe\cubrid_connect',
            'cubrid_connect_with_url' => 'Safe\cubrid_connect_with_url',
            'cubrid_current_oid' => 'Safe\cubrid_current_oid',
            'cubrid_disconnect' => 'Safe\cubrid_disconnect',
            'cubrid_drop' => 'Safe\cubrid_drop',
            'cubrid_free_result' => 'Safe\cubrid_free_result',
            'cubrid_get_charset' => 'Safe\cubrid_get_charset',
            'cubrid_get_class_name' => 'Safe\cubrid_get_class_name',
            'cubrid_get_client_info' => 'Safe\cubrid_get_client_info',
            'cubrid_get_db_parameter' => 'Safe\cubrid_get_db_parameter',
            'cubrid_get_query_timeout' => 'Safe\cubrid_get_query_timeout',
            'cubrid_get_server_info' => 'Safe\cubrid_get_server_info',
            'cubrid_insert_id' => 'Safe\cubrid_insert_id',
            'cubrid_lob2_bind' => 'Safe\cubrid_lob2_bind',
            'cubrid_lob2_close' => 'Safe\cubrid_lob2_close',
            'cubrid_lob2_export' => 'Safe\cubrid_lob2_export',
            'cubrid_lob2_import' => 'Safe\cubrid_lob2_import',
            'cubrid_lob2_new' => 'Safe\cubrid_lob2_new',
            'cubrid_lob2_read' => 'Safe\cubrid_lob2_read',
            'cubrid_lob2_seek' => 'Safe\cubrid_lob2_seek',
            'cubrid_lob2_seek64' => 'Safe\cubrid_lob2_seek64',
            'cubrid_lob2_size' => 'Safe\cubrid_lob2_size',
            'cubrid_lob2_size64' => 'Safe\cubrid_lob2_size64',
            'cubrid_lob2_tell' => 'Safe\cubrid_lob2_tell',
            'cubrid_lob2_tell64' => 'Safe\cubrid_lob2_tell64',
            'cubrid_lob2_write' => 'Safe\cubrid_lob2_write',
            'cubrid_lob_close' => 'Safe\cubrid_lob_close',
            'cubrid_lob_export' => 'Safe\cubrid_lob_export',
            'cubrid_lob_get' => 'Safe\cubrid_lob_get',
            'cubrid_lob_send' => 'Safe\cubrid_lob_send',
            'cubrid_lob_size' => 'Safe\cubrid_lob_size',
            'cubrid_lock_read' => 'Safe\cubrid_lock_read',
            'cubrid_lock_write' => 'Safe\cubrid_lock_write',
            'cubrid_move_cursor' => 'Safe\cubrid_move_cursor',
            'cubrid_next_result' => 'Safe\cubrid_next_result',
            'cubrid_pconnect' => 'Safe\cubrid_pconnect',
            'cubrid_pconnect_with_url' => 'Safe\cubrid_pconnect_with_url',
            'cubrid_prepare' => 'Safe\cubrid_prepare',
            'cubrid_put' => 'Safe\cubrid_put',
            'cubrid_rollback' => 'Safe\cubrid_rollback',
            'cubrid_schema' => 'Safe\cubrid_schema',
            'cubrid_seq_drop' => 'Safe\cubrid_seq_drop',
            'cubrid_seq_insert' => 'Safe\cubrid_seq_insert',
            'cubrid_seq_put' => 'Safe\cubrid_seq_put',
            'cubrid_set_add' => 'Safe\cubrid_set_add',
            'cubrid_set_autocommit' => 'Safe\cubrid_set_autocommit',
            'cubrid_set_db_parameter' => 'Safe\cubrid_set_db_parameter',
            'cubrid_set_drop' => 'Safe\cubrid_set_drop',
            'cubrid_set_query_timeout' => 'Safe\cubrid_set_query_timeout',
            'curl_copy_handle' => 'Safe\curl_copy_handle',
            'curl_escape' => 'Safe\curl_escape',
            'curl_exec' => 'Safe\curl_exec',
            'curl_getinfo' => 'Safe\curl_getinfo',
            'curl_init' => 'Safe\curl_init',
            'curl_multi_info_read' => 'Safe\curl_multi_info_read',
            'curl_multi_init' => 'Safe\curl_multi_init',
            'curl_multi_setopt' => 'Safe\curl_multi_setopt',
            'curl_setopt' => 'Safe\curl_setopt',
            'curl_share_setopt' => 'Safe\curl_share_setopt',
            'curl_unescape' => 'Safe\curl_unescape',
            'curl_upkeep' => 'Safe\curl_upkeep',
            'date_create' => 'Safe\date_create',
            'date_parse_from_format' => 'Safe\date_parse_from_format',
            'date_sunrise' => 'Safe\date_sunrise',
            'date_sunset' => 'Safe\date_sunset',
            'db2_autocommit' => 'Safe\db2_autocommit',
            'db2_bind_param' => 'Safe\db2_bind_param',
            'db2_client_info' => 'Safe\db2_client_info',
            'db2_close' => 'Safe\db2_close',
            'db2_commit' => 'Safe\db2_commit',
            'db2_execute' => 'Safe\db2_execute',
            'db2_free_result' => 'Safe\db2_free_result',
            'db2_free_stmt' => 'Safe\db2_free_stmt',
            'db2_get_option' => 'Safe\db2_get_option',
            'db2_pclose' => 'Safe\db2_pclose',
            'db2_rollback' => 'Safe\db2_rollback',
            'db2_server_info' => 'Safe\db2_server_info',
            'db2_set_option' => 'Safe\db2_set_option',
            'define' => 'Safe\define',
            'deflate_add' => 'Safe\deflate_add',
            'deflate_init' => 'Safe\deflate_init',
            'dir' => 'Safe\dir',
            'disk_free_space' => 'Safe\disk_free_space',
            'disk_total_space' => 'Safe\disk_total_space',
            'dl' => 'Safe\dl',
            'dns_get_record' => 'Safe\dns_get_record',
            'eio_busy' => 'Safe\eio_busy',
            'eio_chmod' => 'Safe\eio_chmod',
            'eio_chown' => 'Safe\eio_chown',
            'eio_close' => 'Safe\eio_close',
            'eio_custom' => 'Safe\eio_custom',
            'eio_dup2' => 'Safe\eio_dup2',
            'eio_event_loop' => 'Safe\eio_event_loop',
            'eio_fallocate' => 'Safe\eio_fallocate',
            'eio_fchmod' => 'Safe\eio_fchmod',
            'eio_fchown' => 'Safe\eio_fchown',
            'eio_fdatasync' => 'Safe\eio_fdatasync',
            'eio_fstat' => 'Safe\eio_fstat',
            'eio_fstatvfs' => 'Safe\eio_fstatvfs',
            'eio_fsync' => 'Safe\eio_fsync',
            'eio_ftruncate' => 'Safe\eio_ftruncate',
            'eio_futime' => 'Safe\eio_futime',
            'eio_grp' => 'Safe\eio_grp',
            'eio_lstat' => 'Safe\eio_lstat',
            'eio_mkdir' => 'Safe\eio_mkdir',
            'eio_mknod' => 'Safe\eio_mknod',
            'eio_nop' => 'Safe\eio_nop',
            'eio_readahead' => 'Safe\eio_readahead',
            'eio_readdir' => 'Safe\eio_readdir',
            'eio_readlink' => 'Safe\eio_readlink',
            'eio_rename' => 'Safe\eio_rename',
            'eio_rmdir' => 'Safe\eio_rmdir',
            'eio_seek' => 'Safe\eio_seek',
            'eio_sendfile' => 'Safe\eio_sendfile',
            'eio_stat' => 'Safe\eio_stat',
            'eio_statvfs' => 'Safe\eio_statvfs',
            'eio_symlink' => 'Safe\eio_symlink',
            'eio_sync' => 'Safe\eio_sync',
            'eio_syncfs' => 'Safe\eio_syncfs',
            'eio_sync_file_range' => 'Safe\eio_sync_file_range',
            'eio_truncate' => 'Safe\eio_truncate',
            'eio_unlink' => 'Safe\eio_unlink',
            'eio_utime' => 'Safe\eio_utime',
            'eio_write' => 'Safe\eio_write',
            'error_log' => 'Safe\error_log',
            'exec' => 'Safe\exec',
            'fastcgi_finish_request' => 'Safe\fastcgi_finish_request',
            'fbird_blob_cancel' => 'Safe\fbird_blob_cancel',
            'fclose' => 'Safe\fclose',
            'fdatasync' => 'Safe\fdatasync',
            'fflush' => 'Safe\fflush',
            'fgetcsv' => 'Safe\fgetcsv',
            'file' => 'Safe\file',
            'fileatime' => 'Safe\fileatime',
            'filectime' => 'Safe\filectime',
            'fileinode' => 'Safe\fileinode',
            'filemtime' => 'Safe\filemtime',
            'fileowner' => 'Safe\fileowner',
            'fileperms' => 'Safe\fileperms',
            'filesize' => 'Safe\filesize',
            'filetype' => 'Safe\filetype',
            'file_get_contents' => 'Safe\file_get_contents',
            'file_put_contents' => 'Safe\file_put_contents',
            'filter_input_array' => 'Safe\filter_input_array',
            'filter_var_array' => 'Safe\filter_var_array',
            'finfo_close' => 'Safe\finfo_close',
            'finfo_open' => 'Safe\finfo_open',
            'flock' => 'Safe\flock',
            'fopen' => 'Safe\fopen',
            'fpm_get_status' => 'Safe\fpm_get_status',
            'fputcsv' => 'Safe\fputcsv',
            'fread' => 'Safe\fread',
            'fsockopen' => 'Safe\fsockopen',
            'fstat' => 'Safe\fstat',
            'fsync' => 'Safe\fsync',
            'ftell' => 'Safe\ftell',
            'ftp_alloc' => 'Safe\ftp_alloc',
            'ftp_append' => 'Safe\ftp_append',
            'ftp_cdup' => 'Safe\ftp_cdup',
            'ftp_chdir' => 'Safe\ftp_chdir',
            'ftp_chmod' => 'Safe\ftp_chmod',
            'ftp_close' => 'Safe\ftp_close',
            'ftp_connect' => 'Safe\ftp_connect',
            'ftp_delete' => 'Safe\ftp_delete',
            'ftp_fget' => 'Safe\ftp_fget',
            'ftp_fput' => 'Safe\ftp_fput',
            'ftp_get' => 'Safe\ftp_get',
            'ftp_login' => 'Safe\ftp_login',
            'ftp_mkdir' => 'Safe\ftp_mkdir',
            'ftp_mlsd' => 'Safe\ftp_mlsd',
            'ftp_nb_put' => 'Safe\ftp_nb_put',
            'ftp_nlist' => 'Safe\ftp_nlist',
            'ftp_pasv' => 'Safe\ftp_pasv',
            'ftp_put' => 'Safe\ftp_put',
            'ftp_pwd' => 'Safe\ftp_pwd',
            'ftp_raw' => 'Safe\ftp_raw',
            'ftp_rename' => 'Safe\ftp_rename',
            'ftp_rmdir' => 'Safe\ftp_rmdir',
            'ftp_site' => 'Safe\ftp_site',
            'ftp_size' => 'Safe\ftp_size',
            'ftp_ssl_connect' => 'Safe\ftp_ssl_connect',
            'ftp_systype' => 'Safe\ftp_systype',
            'ftruncate' => 'Safe\ftruncate',
            'fwrite' => 'Safe\fwrite',
            'getcwd' => 'Safe\getcwd',
            'gethostname' => 'Safe\gethostname',
            'getimagesize' => 'Safe\getimagesize',
            'getlastmod' => 'Safe\getlastmod',
            'getmygid' => 'Safe\getmygid',
            'getmyinode' => 'Safe\getmyinode',
            'getmypid' => 'Safe\getmypid',
            'getmyuid' => 'Safe\getmyuid',
            'getopt' => 'Safe\getopt',
            'getprotobyname' => 'Safe\getprotobyname',
            'getprotobynumber' => 'Safe\getprotobynumber',
            'getrusage' => 'Safe\getrusage',
            'getservbyport' => 'Safe\getservbyport',
            'get_cfg_var' => 'Safe\get_cfg_var',
            'get_headers' => 'Safe\get_headers',
            'get_include_path' => 'Safe\get_include_path',
            'get_meta_tags' => 'Safe\get_meta_tags',
            'glob' => 'Safe\glob',
            'gmmktime' => 'Safe\gmmktime',
            'gmstrftime' => 'Safe\gmstrftime',
            'gnupg_adddecryptkey' => 'Safe\gnupg_adddecryptkey',
            'gnupg_addencryptkey' => 'Safe\gnupg_addencryptkey',
            'gnupg_addsignkey' => 'Safe\gnupg_addsignkey',
            'gnupg_cleardecryptkeys' => 'Safe\gnupg_cleardecryptkeys',
            'gnupg_clearencryptkeys' => 'Safe\gnupg_clearencryptkeys',
            'gnupg_clearsignkeys' => 'Safe\gnupg_clearsignkeys',
            'gnupg_deletekey' => 'Safe\gnupg_deletekey',
            'gnupg_setarmor' => 'Safe\gnupg_setarmor',
            'gnupg_setsignmode' => 'Safe\gnupg_setsignmode',
            'gzclose' => 'Safe\gzclose',
            'gzcompress' => 'Safe\gzcompress',
            'gzdecode' => 'Safe\gzdecode',
            'gzdeflate' => 'Safe\gzdeflate',
            'gzencode' => 'Safe\gzencode',
            'gzfile' => 'Safe\gzfile',
            'gzgets' => 'Safe\gzgets',
            'gzinflate' => 'Safe\gzinflate',
            'gzopen' => 'Safe\gzopen',
            'gzpassthru' => 'Safe\gzpassthru',
            'gzread' => 'Safe\gzread',
            'gzrewind' => 'Safe\gzrewind',
            'gztell' => 'Safe\gztell',
            'gzuncompress' => 'Safe\gzuncompress',
            'gzwrite' => 'Safe\gzwrite',
            'hash_file' => 'Safe\hash_file',
            'hash_hmac_file' => 'Safe\hash_hmac_file',
            'hash_update_file' => 'Safe\hash_update_file',
            'header_register_callback' => 'Safe\header_register_callback',
            'hex2bin' => 'Safe\hex2bin',
            'highlight_file' => 'Safe\highlight_file',
            'highlight_string' => 'Safe\highlight_string',
            'hrtime' => 'Safe\hrtime',
            'ibase_add_user' => 'Safe\ibase_add_user',
            'ibase_backup' => 'Safe\ibase_backup',
            'ibase_blob_cancel' => 'Safe\ibase_blob_cancel',
            'ibase_blob_create' => 'Safe\ibase_blob_create',
            'ibase_blob_get' => 'Safe\ibase_blob_get',
            'ibase_close' => 'Safe\ibase_close',
            'ibase_commit' => 'Safe\ibase_commit',
            'ibase_commit_ret' => 'Safe\ibase_commit_ret',
            'ibase_connect' => 'Safe\ibase_connect',
            'ibase_delete_user' => 'Safe\ibase_delete_user',
            'ibase_drop_db' => 'Safe\ibase_drop_db',
            'ibase_free_event_handler' => 'Safe\ibase_free_event_handler',
            'ibase_free_query' => 'Safe\ibase_free_query',
            'ibase_free_result' => 'Safe\ibase_free_result',
            'ibase_maintain_db' => 'Safe\ibase_maintain_db',
            'ibase_modify_user' => 'Safe\ibase_modify_user',
            'ibase_name_result' => 'Safe\ibase_name_result',
            'ibase_pconnect' => 'Safe\ibase_pconnect',
            'ibase_restore' => 'Safe\ibase_restore',
            'ibase_rollback' => 'Safe\ibase_rollback',
            'ibase_rollback_ret' => 'Safe\ibase_rollback_ret',
            'ibase_service_attach' => 'Safe\ibase_service_attach',
            'ibase_service_detach' => 'Safe\ibase_service_detach',
            'iconv' => 'Safe\iconv',
            'iconv_get_encoding' => 'Safe\iconv_get_encoding',
            'iconv_mime_decode' => 'Safe\iconv_mime_decode',
            'iconv_mime_encode' => 'Safe\iconv_mime_encode',
            'iconv_set_encoding' => 'Safe\iconv_set_encoding',
            'iconv_strlen' => 'Safe\iconv_strlen',
            'idate' => 'Safe\idate',
            'imageaffine' => 'Safe\imageaffine',
            'imageaffinematrixconcat' => 'Safe\imageaffinematrixconcat',
            'imageaffinematrixget' => 'Safe\imageaffinematrixget',
            'imagealphablending' => 'Safe\imagealphablending',
            'imageantialias' => 'Safe\imageantialias',
            'imagearc' => 'Safe\imagearc',
            'imageavif' => 'Safe\imageavif',
            'imagebmp' => 'Safe\imagebmp',
            'imagechar' => 'Safe\imagechar',
            'imagecharup' => 'Safe\imagecharup',
            'imagecolorat' => 'Safe\imagecolorat',
            'imagecolordeallocate' => 'Safe\imagecolordeallocate',
            'imagecolormatch' => 'Safe\imagecolormatch',
            'imagecolorset' => 'Safe\imagecolorset',
            'imageconvolution' => 'Safe\imageconvolution',
            'imagecopy' => 'Safe\imagecopy',
            'imagecopymerge' => 'Safe\imagecopymerge',
            'imagecopymergegray' => 'Safe\imagecopymergegray',
            'imagecopyresampled' => 'Safe\imagecopyresampled',
            'imagecopyresized' => 'Safe\imagecopyresized',
            'imagecreate' => 'Safe\imagecreate',
            'imagecreatefromavif' => 'Safe\imagecreatefromavif',
            'imagecreatefrombmp' => 'Safe\imagecreatefrombmp',
            'imagecreatefromgd' => 'Safe\imagecreatefromgd',
            'imagecreatefromgd2' => 'Safe\imagecreatefromgd2',
            'imagecreatefromgd2part' => 'Safe\imagecreatefromgd2part',
            'imagecreatefromgif' => 'Safe\imagecreatefromgif',
            'imagecreatefromjpeg' => 'Safe\imagecreatefromjpeg',
            'imagecreatefrompng' => 'Safe\imagecreatefrompng',
            'imagecreatefromstring' => 'Safe\imagecreatefromstring',
            'imagecreatefromtga' => 'Safe\imagecreatefromtga',
            'imagecreatefromwbmp' => 'Safe\imagecreatefromwbmp',
            'imagecreatefromwebp' => 'Safe\imagecreatefromwebp',
            'imagecreatefromxbm' => 'Safe\imagecreatefromxbm',
            'imagecreatefromxpm' => 'Safe\imagecreatefromxpm',
            'imagecreatetruecolor' => 'Safe\imagecreatetruecolor',
            'imagecrop' => 'Safe\imagecrop',
            'imagecropauto' => 'Safe\imagecropauto',
            'imagedashedline' => 'Safe\imagedashedline',
            'imagedestroy' => 'Safe\imagedestroy',
            'imageellipse' => 'Safe\imageellipse',
            'imagefill' => 'Safe\imagefill',
            'imagefilledarc' => 'Safe\imagefilledarc',
            'imagefilledellipse' => 'Safe\imagefilledellipse',
            'imagefilledrectangle' => 'Safe\imagefilledrectangle',
            'imagefilltoborder' => 'Safe\imagefilltoborder',
            'imagefilter' => 'Safe\imagefilter',
            'imageflip' => 'Safe\imageflip',
            'imageftbbox' => 'Safe\imageftbbox',
            'imagefttext' => 'Safe\imagefttext',
            'imagegammacorrect' => 'Safe\imagegammacorrect',
            'imagegd' => 'Safe\imagegd',
            'imagegd2' => 'Safe\imagegd2',
            'imagegif' => 'Safe\imagegif',
            'imagegrabscreen' => 'Safe\imagegrabscreen',
            'imagegrabwindow' => 'Safe\imagegrabwindow',
            'imagejpeg' => 'Safe\imagejpeg',
            'imagelayereffect' => 'Safe\imagelayereffect',
            'imageline' => 'Safe\imageline',
            'imageloadfont' => 'Safe\imageloadfont',
            'imagepng' => 'Safe\imagepng',
            'imagerectangle' => 'Safe\imagerectangle',
            'imageresolution' => 'Safe\imageresolution',
            'imagerotate' => 'Safe\imagerotate',
            'imagesavealpha' => 'Safe\imagesavealpha',
            'imagescale' => 'Safe\imagescale',
            'imagesetbrush' => 'Safe\imagesetbrush',
            'imagesetclip' => 'Safe\imagesetclip',
            'imagesetinterpolation' => 'Safe\imagesetinterpolation',
            'imagesetpixel' => 'Safe\imagesetpixel',
            'imagesetstyle' => 'Safe\imagesetstyle',
            'imagesetthickness' => 'Safe\imagesetthickness',
            'imagesettile' => 'Safe\imagesettile',
            'imagestring' => 'Safe\imagestring',
            'imagestringup' => 'Safe\imagestringup',
            'imagetruecolortopalette' => 'Safe\imagetruecolortopalette',
            'imagettfbbox' => 'Safe\imagettfbbox',
            'imagettftext' => 'Safe\imagettftext',
            'imagewbmp' => 'Safe\imagewbmp',
            'imagewebp' => 'Safe\imagewebp',
            'imagexbm' => 'Safe\imagexbm',
            'image_type_to_extension' => 'Safe\image_type_to_extension',
            'imap_8bit' => 'Safe\imap_8bit',
            'imap_append' => 'Safe\imap_append',
            'imap_base64' => 'Safe\imap_base64',
            'imap_binary' => 'Safe\imap_binary',
            'imap_body' => 'Safe\imap_body',
            'imap_bodystruct' => 'Safe\imap_bodystruct',
            'imap_check' => 'Safe\imap_check',
            'imap_clearflag_full' => 'Safe\imap_clearflag_full',
            'imap_close' => 'Safe\imap_close',
            'imap_createmailbox' => 'Safe\imap_createmailbox',
            'imap_deletemailbox' => 'Safe\imap_deletemailbox',
            'imap_fetchbody' => 'Safe\imap_fetchbody',
            'imap_fetchheader' => 'Safe\imap_fetchheader',
            'imap_fetchmime' => 'Safe\imap_fetchmime',
            'imap_fetchstructure' => 'Safe\imap_fetchstructure',
            'imap_fetch_overview' => 'Safe\imap_fetch_overview',
            'imap_gc' => 'Safe\imap_gc',
            'imap_getacl' => 'Safe\imap_getacl',
            'imap_getmailboxes' => 'Safe\imap_getmailboxes',
            'imap_getsubscribed' => 'Safe\imap_getsubscribed',
            'imap_headerinfo' => 'Safe\imap_headerinfo',
            'imap_headers' => 'Safe\imap_headers',
            'imap_listscan' => 'Safe\imap_listscan',
            'imap_lsub' => 'Safe\imap_lsub',
            'imap_mail' => 'Safe\imap_mail',
            'imap_mailboxmsginfo' => 'Safe\imap_mailboxmsginfo',
            'imap_mail_compose' => 'Safe\imap_mail_compose',
            'imap_mail_copy' => 'Safe\imap_mail_copy',
            'imap_mail_move' => 'Safe\imap_mail_move',
            'imap_mime_header_decode' => 'Safe\imap_mime_header_decode',
            'imap_mutf7_to_utf8' => 'Safe\imap_mutf7_to_utf8',
            'imap_num_msg' => 'Safe\imap_num_msg',
            'imap_open' => 'Safe\imap_open',
            'imap_qprint' => 'Safe\imap_qprint',
            'imap_renamemailbox' => 'Safe\imap_renamemailbox',
            'imap_rfc822_write_address' => 'Safe\imap_rfc822_write_address',
            'imap_savebody' => 'Safe\imap_savebody',
            'imap_setacl' => 'Safe\imap_setacl',
            'imap_setflag_full' => 'Safe\imap_setflag_full',
            'imap_set_quota' => 'Safe\imap_set_quota',
            'imap_sort' => 'Safe\imap_sort',
            'imap_status' => 'Safe\imap_status',
            'imap_subscribe' => 'Safe\imap_subscribe',
            'imap_thread' => 'Safe\imap_thread',
            'imap_timeout' => 'Safe\imap_timeout',
            'imap_undelete' => 'Safe\imap_undelete',
            'imap_unsubscribe' => 'Safe\imap_unsubscribe',
            'imap_utf8_to_mutf7' => 'Safe\imap_utf8_to_mutf7',
            'inet_ntop' => 'Safe\inet_ntop',
            'inet_pton' => 'Safe\inet_pton',
            'inflate_add' => 'Safe\inflate_add',
            'inflate_get_read_len' => 'Safe\inflate_get_read_len',
            'inflate_get_status' => 'Safe\inflate_get_status',
            'inflate_init' => 'Safe\inflate_init',
            'ini_get' => 'Safe\ini_get',
            'ini_set' => 'Safe\ini_set',
            'inotify_init' => 'Safe\inotify_init',
            'inotify_rm_watch' => 'Safe\inotify_rm_watch',
            'iptcembed' => 'Safe\iptcembed',
            'iptcparse' => 'Safe\iptcparse',
            'json_decode' => 'Safe\json_decode',
            'json_encode' => 'Safe\json_encode',
            'lchgrp' => 'Safe\lchgrp',
            'lchown' => 'Safe\lchown',
            'ldap_8859_to_t61' => 'Safe\ldap_8859_to_t61',
            'ldap_add' => 'Safe\ldap_add',
            'ldap_bind' => 'Safe\ldap_bind',
            'ldap_compare' => 'Safe\ldap_compare',
            'ldap_control_paged_result' => 'Safe\ldap_control_paged_result',
            'ldap_control_paged_result_response' => 'Safe\ldap_control_paged_result_response',
            'ldap_count_entries' => 'Safe\ldap_count_entries',
            'ldap_delete' => 'Safe\ldap_delete',
            'ldap_dn2ufn' => 'Safe\ldap_dn2ufn',
            'ldap_exop' => 'Safe\ldap_exop',
            'ldap_exop_passwd' => 'Safe\ldap_exop_passwd',
            'ldap_exop_whoami' => 'Safe\ldap_exop_whoami',
            'ldap_explode_dn' => 'Safe\ldap_explode_dn',
            'ldap_first_attribute' => 'Safe\ldap_first_attribute',
            'ldap_first_entry' => 'Safe\ldap_first_entry',
            'ldap_free_result' => 'Safe\ldap_free_result',
            'ldap_get_attributes' => 'Safe\ldap_get_attributes',
            'ldap_get_dn' => 'Safe\ldap_get_dn',
            'ldap_get_entries' => 'Safe\ldap_get_entries',
            'ldap_get_option' => 'Safe\ldap_get_option',
            'ldap_get_values' => 'Safe\ldap_get_values',
            'ldap_get_values_len' => 'Safe\ldap_get_values_len',
            'ldap_modify_batch' => 'Safe\ldap_modify_batch',
            'ldap_mod_add' => 'Safe\ldap_mod_add',
            'ldap_mod_del' => 'Safe\ldap_mod_del',
            'ldap_mod_replace' => 'Safe\ldap_mod_replace',
            'ldap_next_attribute' => 'Safe\ldap_next_attribute',
            'ldap_parse_exop' => 'Safe\ldap_parse_exop',
            'ldap_parse_result' => 'Safe\ldap_parse_result',
            'ldap_rename' => 'Safe\ldap_rename',
            'ldap_sasl_bind' => 'Safe\ldap_sasl_bind',
            'ldap_set_option' => 'Safe\ldap_set_option',
            'ldap_unbind' => 'Safe\ldap_unbind',
            'libxml_set_external_entity_loader' => 'Safe\libxml_set_external_entity_loader',
            'link' => 'Safe\link',
            'lstat' => 'Safe\lstat',
            'lzf_compress' => 'Safe\lzf_compress',
            'lzf_decompress' => 'Safe\lzf_decompress',
            'mailparse_msg_extract_part_file' => 'Safe\mailparse_msg_extract_part_file',
            'mailparse_msg_free' => 'Safe\mailparse_msg_free',
            'mailparse_msg_parse' => 'Safe\mailparse_msg_parse',
            'mailparse_msg_parse_file' => 'Safe\mailparse_msg_parse_file',
            'mailparse_stream_encode' => 'Safe\mailparse_stream_encode',
            'mb_chr' => 'Safe\mb_chr',
            'mb_convert_encoding' => 'Safe\mb_convert_encoding',
            'mb_convert_variables' => 'Safe\mb_convert_variables',
            'mb_detect_order' => 'Safe\mb_detect_order',
            'mb_encoding_aliases' => 'Safe\mb_encoding_aliases',
            'mb_eregi_replace' => 'Safe\mb_eregi_replace',
            'mb_ereg_replace' => 'Safe\mb_ereg_replace',
            'mb_ereg_replace_callback' => 'Safe\mb_ereg_replace_callback',
            'mb_ereg_search_getregs' => 'Safe\mb_ereg_search_getregs',
            'mb_ereg_search_init' => 'Safe\mb_ereg_search_init',
            'mb_ereg_search_regs' => 'Safe\mb_ereg_search_regs',
            'mb_ereg_search_setpos' => 'Safe\mb_ereg_search_setpos',
            'mb_get_info' => 'Safe\mb_get_info',
            'mb_http_output' => 'Safe\mb_http_output',
            'mb_internal_encoding' => 'Safe\mb_internal_encoding',
            'mb_ord' => 'Safe\mb_ord',
            'mb_parse_str' => 'Safe\mb_parse_str',
            'mb_regex_encoding' => 'Safe\mb_regex_encoding',
            'mb_send_mail' => 'Safe\mb_send_mail',
            'mb_split' => 'Safe\mb_split',
            'md5_file' => 'Safe\md5_file',
            'mime_content_type' => 'Safe\mime_content_type',
            'mkdir' => 'Safe\mkdir',
            'mktime' => 'Safe\mktime',
            'msg_get_queue' => 'Safe\msg_get_queue',
            'msg_queue_exists' => 'Safe\msg_queue_exists',
            'msg_receive' => 'Safe\msg_receive',
            'msg_remove_queue' => 'Safe\msg_remove_queue',
            'msg_send' => 'Safe\msg_send',
            'msg_set_queue' => 'Safe\msg_set_queue',
            'msg_stat_queue' => 'Safe\msg_stat_queue',
            'mysql_close' => 'Safe\mysql_close',
            'mysql_connect' => 'Safe\mysql_connect',
            'mysql_create_db' => 'Safe\mysql_create_db',
            'mysql_data_seek' => 'Safe\mysql_data_seek',
            'mysql_db_name' => 'Safe\mysql_db_name',
            'mysql_db_query' => 'Safe\mysql_db_query',
            'mysql_drop_db' => 'Safe\mysql_drop_db',
            'mysql_fetch_lengths' => 'Safe\mysql_fetch_lengths',
            'mysql_field_flags' => 'Safe\mysql_field_flags',
            'mysql_field_len' => 'Safe\mysql_field_len',
            'mysql_field_name' => 'Safe\mysql_field_name',
            'mysql_field_seek' => 'Safe\mysql_field_seek',
            'mysql_free_result' => 'Safe\mysql_free_result',
            'mysql_get_host_info' => 'Safe\mysql_get_host_info',
            'mysql_get_proto_info' => 'Safe\mysql_get_proto_info',
            'mysql_get_server_info' => 'Safe\mysql_get_server_info',
            'mysql_info' => 'Safe\mysql_info',
            'mysql_list_dbs' => 'Safe\mysql_list_dbs',
            'mysql_list_fields' => 'Safe\mysql_list_fields',
            'mysql_list_processes' => 'Safe\mysql_list_processes',
            'mysql_list_tables' => 'Safe\mysql_list_tables',
            'mysql_num_fields' => 'Safe\mysql_num_fields',
            'mysql_num_rows' => 'Safe\mysql_num_rows',
            'mysql_query' => 'Safe\mysql_query',
            'mysql_real_escape_string' => 'Safe\mysql_real_escape_string',
            'mysql_result' => 'Safe\mysql_result',
            'mysql_select_db' => 'Safe\mysql_select_db',
            'mysql_set_charset' => 'Safe\mysql_set_charset',
            'mysql_tablename' => 'Safe\mysql_tablename',
            'mysql_thread_id' => 'Safe\mysql_thread_id',
            'mysql_unbuffered_query' => 'Safe\mysql_unbuffered_query',
            'net_get_interfaces' => 'Safe\net_get_interfaces',
            'ob_clean' => 'Safe\ob_clean',
            'ob_end_clean' => 'Safe\ob_end_clean',
            'ob_end_flush' => 'Safe\ob_end_flush',
            'ob_flush' => 'Safe\ob_flush',
            'ob_get_clean' => 'Safe\ob_get_clean',
            'ob_start' => 'Safe\ob_start',
            'oci_bind_array_by_name' => 'Safe\oci_bind_array_by_name',
            'oci_bind_by_name' => 'Safe\oci_bind_by_name',
            'oci_cancel' => 'Safe\oci_cancel',
            'oci_commit' => 'Safe\oci_commit',
            'oci_connect' => 'Safe\oci_connect',
            'oci_define_by_name' => 'Safe\oci_define_by_name',
            'oci_execute' => 'Safe\oci_execute',
            'oci_field_name' => 'Safe\oci_field_name',
            'oci_field_precision' => 'Safe\oci_field_precision',
            'oci_field_scale' => 'Safe\oci_field_scale',
            'oci_field_size' => 'Safe\oci_field_size',
            'oci_field_type' => 'Safe\oci_field_type',
            'oci_field_type_raw' => 'Safe\oci_field_type_raw',
            'oci_free_descriptor' => 'Safe\oci_free_descriptor',
            'oci_free_statement' => 'Safe\oci_free_statement',
            'oci_new_collection' => 'Safe\oci_new_collection',
            'oci_new_connect' => 'Safe\oci_new_connect',
            'oci_new_cursor' => 'Safe\oci_new_cursor',
            'oci_new_descriptor' => 'Safe\oci_new_descriptor',
            'oci_num_rows' => 'Safe\oci_num_rows',
            'oci_parse' => 'Safe\oci_parse',
            'oci_pconnect' => 'Safe\oci_pconnect',
            'oci_register_taf_callback' => 'Safe\oci_register_taf_callback',
            'oci_result' => 'Safe\oci_result',
            'oci_rollback' => 'Safe\oci_rollback',
            'oci_server_version' => 'Safe\oci_server_version',
            'oci_set_action' => 'Safe\oci_set_action',
            'oci_set_call_timeout' => 'Safe\oci_set_call_timeout',
            'oci_set_client_identifier' => 'Safe\oci_set_client_identifier',
            'oci_set_client_info' => 'Safe\oci_set_client_info',
            'oci_set_db_operation' => 'Safe\oci_set_db_operation',
            'oci_set_edition' => 'Safe\oci_set_edition',
            'oci_set_module_name' => 'Safe\oci_set_module_name',
            'oci_set_prefetch' => 'Safe\oci_set_prefetch',
            'oci_set_prefetch_lob' => 'Safe\oci_set_prefetch_lob',
            'oci_statement_type' => 'Safe\oci_statement_type',
            'oci_unregister_taf_callback' => 'Safe\oci_unregister_taf_callback',
            'odbc_autocommit' => 'Safe\odbc_autocommit',
            'odbc_binmode' => 'Safe\odbc_binmode',
            'odbc_columnprivileges' => 'Safe\odbc_columnprivileges',
            'odbc_columns' => 'Safe\odbc_columns',
            'odbc_commit' => 'Safe\odbc_commit',
            'odbc_connect' => 'Safe\odbc_connect',
            'odbc_cursor' => 'Safe\odbc_cursor',
            'odbc_data_source' => 'Safe\odbc_data_source',
            'odbc_exec' => 'Safe\odbc_exec',
            'odbc_execute' => 'Safe\odbc_execute',
            'odbc_fetch_into' => 'Safe\odbc_fetch_into',
            'odbc_field_len' => 'Safe\odbc_field_len',
            'odbc_field_name' => 'Safe\odbc_field_name',
            'odbc_field_num' => 'Safe\odbc_field_num',
            'odbc_field_scale' => 'Safe\odbc_field_scale',
            'odbc_field_type' => 'Safe\odbc_field_type',
            'odbc_foreignkeys' => 'Safe\odbc_foreignkeys',
            'odbc_gettypeinfo' => 'Safe\odbc_gettypeinfo',
            'odbc_longreadlen' => 'Safe\odbc_longreadlen',
            'odbc_num_fields' => 'Safe\odbc_num_fields',
            'odbc_pconnect' => 'Safe\odbc_pconnect',
            'odbc_prepare' => 'Safe\odbc_prepare',
            'odbc_primarykeys' => 'Safe\odbc_primarykeys',
            'odbc_procedurecolumns' => 'Safe\odbc_procedurecolumns',
            'odbc_procedures' => 'Safe\odbc_procedures',
            'odbc_result' => 'Safe\odbc_result',
            'odbc_result_all' => 'Safe\odbc_result_all',
            'odbc_rollback' => 'Safe\odbc_rollback',
            'odbc_setoption' => 'Safe\odbc_setoption',
            'odbc_specialcolumns' => 'Safe\odbc_specialcolumns',
            'odbc_statistics' => 'Safe\odbc_statistics',
            'odbc_tableprivileges' => 'Safe\odbc_tableprivileges',
            'odbc_tables' => 'Safe\odbc_tables',
            'opcache_compile_file' => 'Safe\opcache_compile_file',
            'opcache_get_status' => 'Safe\opcache_get_status',
            'opendir' => 'Safe\opendir',
            'openlog' => 'Safe\openlog',
            'openssl_cipher_iv_length' => 'Safe\openssl_cipher_iv_length',
            'openssl_cipher_key_length' => 'Safe\openssl_cipher_key_length',
            'openssl_cms_decrypt' => 'Safe\openssl_cms_decrypt',
            'openssl_cms_encrypt' => 'Safe\openssl_cms_encrypt',
            'openssl_cms_read' => 'Safe\openssl_cms_read',
            'openssl_cms_sign' => 'Safe\openssl_cms_sign',
            'openssl_cms_verify' => 'Safe\openssl_cms_verify',
            'openssl_csr_export' => 'Safe\openssl_csr_export',
            'openssl_csr_export_to_file' => 'Safe\openssl_csr_export_to_file',
            'openssl_csr_get_public_key' => 'Safe\openssl_csr_get_public_key',
            'openssl_csr_get_subject' => 'Safe\openssl_csr_get_subject',
            'openssl_csr_new' => 'Safe\openssl_csr_new',
            'openssl_csr_sign' => 'Safe\openssl_csr_sign',
            'openssl_decrypt' => 'Safe\openssl_decrypt',
            'openssl_dh_compute_key' => 'Safe\openssl_dh_compute_key',
            'openssl_digest' => 'Safe\openssl_digest',
            'openssl_encrypt' => 'Safe\openssl_encrypt',
            'openssl_get_curve_names' => 'Safe\openssl_get_curve_names',
            'openssl_open' => 'Safe\openssl_open',
            'openssl_pbkdf2' => 'Safe\openssl_pbkdf2',
            'openssl_pkcs7_decrypt' => 'Safe\openssl_pkcs7_decrypt',
            'openssl_pkcs7_encrypt' => 'Safe\openssl_pkcs7_encrypt',
            'openssl_pkcs7_read' => 'Safe\openssl_pkcs7_read',
            'openssl_pkcs7_sign' => 'Safe\openssl_pkcs7_sign',
            'openssl_pkcs12_export' => 'Safe\openssl_pkcs12_export',
            'openssl_pkcs12_export_to_file' => 'Safe\openssl_pkcs12_export_to_file',
            'openssl_pkcs12_read' => 'Safe\openssl_pkcs12_read',
            'openssl_pkey_derive' => 'Safe\openssl_pkey_derive',
            'openssl_pkey_export' => 'Safe\openssl_pkey_export',
            'openssl_pkey_export_to_file' => 'Safe\openssl_pkey_export_to_file',
            'openssl_pkey_get_details' => 'Safe\openssl_pkey_get_details',
            'openssl_pkey_get_private' => 'Safe\openssl_pkey_get_private',
            'openssl_pkey_get_public' => 'Safe\openssl_pkey_get_public',
            'openssl_pkey_new' => 'Safe\openssl_pkey_new',
            'openssl_private_decrypt' => 'Safe\openssl_private_decrypt',
            'openssl_private_encrypt' => 'Safe\openssl_private_encrypt',
            'openssl_public_decrypt' => 'Safe\openssl_public_decrypt',
            'openssl_public_encrypt' => 'Safe\openssl_public_encrypt',
            'openssl_seal' => 'Safe\openssl_seal',
            'openssl_sign' => 'Safe\openssl_sign',
            'openssl_spki_export' => 'Safe\openssl_spki_export',
            'openssl_spki_export_challenge' => 'Safe\openssl_spki_export_challenge',
            'openssl_spki_new' => 'Safe\openssl_spki_new',
            'openssl_spki_verify' => 'Safe\openssl_spki_verify',
            'openssl_verify' => 'Safe\openssl_verify',
            'openssl_x509_checkpurpose' => 'Safe\openssl_x509_checkpurpose',
            'openssl_x509_export' => 'Safe\openssl_x509_export',
            'openssl_x509_export_to_file' => 'Safe\openssl_x509_export_to_file',
            'openssl_x509_fingerprint' => 'Safe\openssl_x509_fingerprint',
            'openssl_x509_read' => 'Safe\openssl_x509_read',
            'output_add_rewrite_var' => 'Safe\output_add_rewrite_var',
            'output_reset_rewrite_vars' => 'Safe\output_reset_rewrite_vars',
            'parse_ini_file' => 'Safe\parse_ini_file',
            'parse_ini_string' => 'Safe\parse_ini_string',
            'parse_url' => 'Safe\parse_url',
            'passthru' => 'Safe\passthru',
            'pclose' => 'Safe\pclose',
            'pcntl_getpriority' => 'Safe\pcntl_getpriority',
            'pcntl_setpriority' => 'Safe\pcntl_setpriority',
            'pcntl_signal' => 'Safe\pcntl_signal',
            'pcntl_signal_dispatch' => 'Safe\pcntl_signal_dispatch',
            'pcntl_sigprocmask' => 'Safe\pcntl_sigprocmask',
            'pcntl_sigtimedwait' => 'Safe\pcntl_sigtimedwait',
            'pcntl_sigwaitinfo' => 'Safe\pcntl_sigwaitinfo',
            'pfsockopen' => 'Safe\pfsockopen',
            'pg_cancel_query' => 'Safe\pg_cancel_query',
            'pg_connect' => 'Safe\pg_connect',
            'pg_connection_reset' => 'Safe\pg_connection_reset',
            'pg_convert' => 'Safe\pg_convert',
            'pg_copy_from' => 'Safe\pg_copy_from',
            'pg_copy_to' => 'Safe\pg_copy_to',
            'pg_delete' => 'Safe\pg_delete',
            'pg_end_copy' => 'Safe\pg_end_copy',
            'pg_execute' => 'Safe\pg_execute',
            'pg_field_num' => 'Safe\pg_field_num',
            'pg_field_table' => 'Safe\pg_field_table',
            'pg_flush' => 'Safe\pg_flush',
            'pg_free_result' => 'Safe\pg_free_result',
            'pg_host' => 'Safe\pg_host',
            'pg_insert' => 'Safe\pg_insert',
            'pg_last_oid' => 'Safe\pg_last_oid',
            'pg_lo_close' => 'Safe\pg_lo_close',
            'pg_lo_export' => 'Safe\pg_lo_export',
            'pg_lo_import' => 'Safe\pg_lo_import',
            'pg_lo_open' => 'Safe\pg_lo_open',
            'pg_lo_read' => 'Safe\pg_lo_read',
            'pg_lo_seek' => 'Safe\pg_lo_seek',
            'pg_lo_truncate' => 'Safe\pg_lo_truncate',
            'pg_lo_unlink' => 'Safe\pg_lo_unlink',
            'pg_lo_write' => 'Safe\pg_lo_write',
            'pg_meta_data' => 'Safe\pg_meta_data',
            'pg_parameter_status' => 'Safe\pg_parameter_status',
            'pg_pconnect' => 'Safe\pg_pconnect',
            'pg_ping' => 'Safe\pg_ping',
            'pg_prepare' => 'Safe\pg_prepare',
            'pg_put_line' => 'Safe\pg_put_line',
            'pg_query' => 'Safe\pg_query',
            'pg_query_params' => 'Safe\pg_query_params',
            'pg_result_error_field' => 'Safe\pg_result_error_field',
            'pg_result_seek' => 'Safe\pg_result_seek',
            'pg_select' => 'Safe\pg_select',
            'pg_socket' => 'Safe\pg_socket',
            'pg_trace' => 'Safe\pg_trace',
            'pg_update' => 'Safe\pg_update',
            'phpcredits' => 'Safe\phpcredits',
            'phpinfo' => 'Safe\phpinfo',
            'php_sapi_name' => 'Safe\php_sapi_name',
            'popen' => 'Safe\popen',
            'posix_access' => 'Safe\posix_access',
            'posix_getgrgid' => 'Safe\posix_getgrgid',
            'posix_getgrnam' => 'Safe\posix_getgrnam',
            'posix_getgroups' => 'Safe\posix_getgroups',
            'posix_getlogin' => 'Safe\posix_getlogin',
            'posix_getpgid' => 'Safe\posix_getpgid',
            'posix_getpwuid' => 'Safe\posix_getpwuid',
            'posix_getrlimit' => 'Safe\posix_getrlimit',
            'posix_getsid' => 'Safe\posix_getsid',
            'posix_initgroups' => 'Safe\posix_initgroups',
            'posix_kill' => 'Safe\posix_kill',
            'posix_mkfifo' => 'Safe\posix_mkfifo',
            'posix_mknod' => 'Safe\posix_mknod',
            'posix_setegid' => 'Safe\posix_setegid',
            'posix_seteuid' => 'Safe\posix_seteuid',
            'posix_setgid' => 'Safe\posix_setgid',
            'posix_setpgid' => 'Safe\posix_setpgid',
            'posix_setrlimit' => 'Safe\posix_setrlimit',
            'posix_setsid' => 'Safe\posix_setsid',
            'posix_setuid' => 'Safe\posix_setuid',
            'posix_times' => 'Safe\posix_times',
            'posix_uname' => 'Safe\posix_uname',
            'preg_grep' => 'Safe\preg_grep',
            'preg_match' => 'Safe\preg_match',
            'preg_match_all' => 'Safe\preg_match_all',
            'preg_replace' => 'Safe\preg_replace',
            'preg_replace_callback' => 'Safe\preg_replace_callback',
            'preg_replace_callback_array' => 'Safe\preg_replace_callback_array',
            'preg_split' => 'Safe\preg_split',
            'proc_close' => 'Safe\proc_close',
            'proc_nice' => 'Safe\proc_nice',
            'proc_open' => 'Safe\proc_open',
            'pspell_add_to_personal' => 'Safe\pspell_add_to_personal',
            'pspell_add_to_session' => 'Safe\pspell_add_to_session',
            'pspell_clear_session' => 'Safe\pspell_clear_session',
            'pspell_config_create' => 'Safe\pspell_config_create',
            'pspell_config_data_dir' => 'Safe\pspell_config_data_dir',
            'pspell_config_dict_dir' => 'Safe\pspell_config_dict_dir',
            'pspell_config_ignore' => 'Safe\pspell_config_ignore',
            'pspell_config_mode' => 'Safe\pspell_config_mode',
            'pspell_config_personal' => 'Safe\pspell_config_personal',
            'pspell_config_repl' => 'Safe\pspell_config_repl',
            'pspell_config_runtogether' => 'Safe\pspell_config_runtogether',
            'pspell_config_save_repl' => 'Safe\pspell_config_save_repl',
            'pspell_new' => 'Safe\pspell_new',
            'pspell_new_config' => 'Safe\pspell_new_config',
            'pspell_new_personal' => 'Safe\pspell_new_personal',
            'pspell_save_wordlist' => 'Safe\pspell_save_wordlist',
            'pspell_store_replacement' => 'Safe\pspell_store_replacement',
            'ps_add_launchlink' => 'Safe\ps_add_launchlink',
            'ps_add_locallink' => 'Safe\ps_add_locallink',
            'ps_add_note' => 'Safe\ps_add_note',
            'ps_add_pdflink' => 'Safe\ps_add_pdflink',
            'ps_add_weblink' => 'Safe\ps_add_weblink',
            'ps_arc' => 'Safe\ps_arc',
            'ps_arcn' => 'Safe\ps_arcn',
            'ps_begin_page' => 'Safe\ps_begin_page',
            'ps_begin_pattern' => 'Safe\ps_begin_pattern',
            'ps_begin_template' => 'Safe\ps_begin_template',
            'ps_circle' => 'Safe\ps_circle',
            'ps_clip' => 'Safe\ps_clip',
            'ps_close' => 'Safe\ps_close',
            'ps_closepath' => 'Safe\ps_closepath',
            'ps_closepath_stroke' => 'Safe\ps_closepath_stroke',
            'ps_close_image' => 'Safe\ps_close_image',
            'ps_continue_text' => 'Safe\ps_continue_text',
            'ps_curveto' => 'Safe\ps_curveto',
            'ps_delete' => 'Safe\ps_delete',
            'ps_end_page' => 'Safe\ps_end_page',
            'ps_end_pattern' => 'Safe\ps_end_pattern',
            'ps_end_template' => 'Safe\ps_end_template',
            'ps_fill' => 'Safe\ps_fill',
            'ps_fill_stroke' => 'Safe\ps_fill_stroke',
            'ps_get_parameter' => 'Safe\ps_get_parameter',
            'ps_hyphenate' => 'Safe\ps_hyphenate',
            'ps_include_file' => 'Safe\ps_include_file',
            'ps_lineto' => 'Safe\ps_lineto',
            'ps_moveto' => 'Safe\ps_moveto',
            'ps_new' => 'Safe\ps_new',
            'ps_open_file' => 'Safe\ps_open_file',
            'ps_place_image' => 'Safe\ps_place_image',
            'ps_rect' => 'Safe\ps_rect',
            'ps_restore' => 'Safe\ps_restore',
            'ps_rotate' => 'Safe\ps_rotate',
            'ps_save' => 'Safe\ps_save',
            'ps_scale' => 'Safe\ps_scale',
            'ps_setcolor' => 'Safe\ps_setcolor',
            'ps_setdash' => 'Safe\ps_setdash',
            'ps_setflat' => 'Safe\ps_setflat',
            'ps_setfont' => 'Safe\ps_setfont',
            'ps_setgray' => 'Safe\ps_setgray',
            'ps_setlinecap' => 'Safe\ps_setlinecap',
            'ps_setlinejoin' => 'Safe\ps_setlinejoin',
            'ps_setlinewidth' => 'Safe\ps_setlinewidth',
            'ps_setmiterlimit' => 'Safe\ps_setmiterlimit',
            'ps_setoverprintmode' => 'Safe\ps_setoverprintmode',
            'ps_setpolydash' => 'Safe\ps_setpolydash',
            'ps_set_border_color' => 'Safe\ps_set_border_color',
            'ps_set_border_dash' => 'Safe\ps_set_border_dash',
            'ps_set_border_style' => 'Safe\ps_set_border_style',
            'ps_set_info' => 'Safe\ps_set_info',
            'ps_set_parameter' => 'Safe\ps_set_parameter',
            'ps_set_text_pos' => 'Safe\ps_set_text_pos',
            'ps_set_value' => 'Safe\ps_set_value',
            'ps_shading' => 'Safe\ps_shading',
            'ps_shading_pattern' => 'Safe\ps_shading_pattern',
            'ps_shfill' => 'Safe\ps_shfill',
            'ps_show' => 'Safe\ps_show',
            'ps_show2' => 'Safe\ps_show2',
            'ps_show_xy' => 'Safe\ps_show_xy',
            'ps_show_xy2' => 'Safe\ps_show_xy2',
            'ps_stroke' => 'Safe\ps_stroke',
            'ps_symbol' => 'Safe\ps_symbol',
            'ps_translate' => 'Safe\ps_translate',
            'putenv' => 'Safe\putenv',
            'readfile' => 'Safe\readfile',
            'readgzfile' => 'Safe\readgzfile',
            'readline_add_history' => 'Safe\readline_add_history',
            'readline_callback_handler_install' => 'Safe\readline_callback_handler_install',
            'readline_clear_history' => 'Safe\readline_clear_history',
            'readline_completion_function' => 'Safe\readline_completion_function',
            'readline_read_history' => 'Safe\readline_read_history',
            'readline_write_history' => 'Safe\readline_write_history',
            'readlink' => 'Safe\readlink',
            'realpath' => 'Safe\realpath',
            'register_tick_function' => 'Safe\register_tick_function',
            'rename' => 'Safe\rename',
            'rewind' => 'Safe\rewind',
            'rmdir' => 'Safe\rmdir',
            'rnp_decrypt' => 'Safe\rnp_decrypt',
            'rnp_dump_packets' => 'Safe\rnp_dump_packets',
            'rnp_dump_packets_to_json' => 'Safe\rnp_dump_packets_to_json',
            'rnp_ffi_create' => 'Safe\rnp_ffi_create',
            'rnp_ffi_set_pass_provider' => 'Safe\rnp_ffi_set_pass_provider',
            'rnp_import_keys' => 'Safe\rnp_import_keys',
            'rnp_import_signatures' => 'Safe\rnp_import_signatures',
            'rnp_key_export' => 'Safe\rnp_key_export',
            'rnp_key_export_autocrypt' => 'Safe\rnp_key_export_autocrypt',
            'rnp_key_export_revocation' => 'Safe\rnp_key_export_revocation',
            'rnp_key_get_info' => 'Safe\rnp_key_get_info',
            'rnp_key_remove' => 'Safe\rnp_key_remove',
            'rnp_key_revoke' => 'Safe\rnp_key_revoke',
            'rnp_list_keys' => 'Safe\rnp_list_keys',
            'rnp_load_keys' => 'Safe\rnp_load_keys',
            'rnp_load_keys_from_path' => 'Safe\rnp_load_keys_from_path',
            'rnp_locate_key' => 'Safe\rnp_locate_key',
            'rnp_op_encrypt' => 'Safe\rnp_op_encrypt',
            'rnp_op_generate_key' => 'Safe\rnp_op_generate_key',
            'rnp_op_sign' => 'Safe\rnp_op_sign',
            'rnp_op_sign_cleartext' => 'Safe\rnp_op_sign_cleartext',
            'rnp_op_sign_detached' => 'Safe\rnp_op_sign_detached',
            'rnp_op_verify' => 'Safe\rnp_op_verify',
            'rnp_op_verify_detached' => 'Safe\rnp_op_verify_detached',
            'rnp_save_keys' => 'Safe\rnp_save_keys',
            'rnp_save_keys_to_path' => 'Safe\rnp_save_keys_to_path',
            'rnp_supported_features' => 'Safe\rnp_supported_features',
            'rpmaddtag' => 'Safe\rpmaddtag',
            'rrd_create' => 'Safe\rrd_create',
            'rrd_first' => 'Safe\rrd_first',
            'rrd_graph' => 'Safe\rrd_graph',
            'rrd_info' => 'Safe\rrd_info',
            'rrd_lastupdate' => 'Safe\rrd_lastupdate',
            'rrd_restore' => 'Safe\rrd_restore',
            'rrd_tune' => 'Safe\rrd_tune',
            'rrd_update' => 'Safe\rrd_update',
            'rrd_xport' => 'Safe\rrd_xport',
            'sapi_windows_cp_conv' => 'Safe\sapi_windows_cp_conv',
            'sapi_windows_cp_set' => 'Safe\sapi_windows_cp_set',
            'sapi_windows_generate_ctrl_event' => 'Safe\sapi_windows_generate_ctrl_event',
            'sapi_windows_set_ctrl_handler' => 'Safe\sapi_windows_set_ctrl_handler',
            'sapi_windows_vt100_support' => 'Safe\sapi_windows_vt100_support',
            'scandir' => 'Safe\scandir',
            'sem_acquire' => 'Safe\sem_acquire',
            'sem_get' => 'Safe\sem_get',
            'sem_release' => 'Safe\sem_release',
            'sem_remove' => 'Safe\sem_remove',
            'session_abort' => 'Safe\session_abort',
            'session_cache_expire' => 'Safe\session_cache_expire',
            'session_cache_limiter' => 'Safe\session_cache_limiter',
            'session_create_id' => 'Safe\session_create_id',
            'session_decode' => 'Safe\session_decode',
            'session_destroy' => 'Safe\session_destroy',
            'session_encode' => 'Safe\session_encode',
            'session_gc' => 'Safe\session_gc',
            'session_id' => 'Safe\session_id',
            'session_module_name' => 'Safe\session_module_name',
            'session_name' => 'Safe\session_name',
            'session_regenerate_id' => 'Safe\session_regenerate_id',
            'session_reset' => 'Safe\session_reset',
            'session_save_path' => 'Safe\session_save_path',
            'session_start' => 'Safe\session_start',
            'session_unset' => 'Safe\session_unset',
            'session_write_close' => 'Safe\session_write_close',
            'settype' => 'Safe\settype',
            'set_include_path' => 'Safe\set_include_path',
            'set_time_limit' => 'Safe\set_time_limit',
            'sha1_file' => 'Safe\sha1_file',
            'shell_exec' => 'Safe\shell_exec',
            'shmop_delete' => 'Safe\shmop_delete',
            'shmop_read' => 'Safe\shmop_read',
            'shm_attach' => 'Safe\shm_attach',
            'shm_detach' => 'Safe\shm_detach',
            'shm_put_var' => 'Safe\shm_put_var',
            'shm_remove' => 'Safe\shm_remove',
            'shm_remove_var' => 'Safe\shm_remove_var',
            'simplexml_import_dom' => 'Safe\simplexml_import_dom',
            'simplexml_load_file' => 'Safe\simplexml_load_file',
            'simplexml_load_string' => 'Safe\simplexml_load_string',
            'socket_accept' => 'Safe\socket_accept',
            'socket_addrinfo_bind' => 'Safe\socket_addrinfo_bind',
            'socket_addrinfo_connect' => 'Safe\socket_addrinfo_connect',
            'socket_addrinfo_lookup' => 'Safe\socket_addrinfo_lookup',
            'socket_bind' => 'Safe\socket_bind',
            'socket_connect' => 'Safe\socket_connect',
            'socket_create' => 'Safe\socket_create',
            'socket_create_listen' => 'Safe\socket_create_listen',
            'socket_create_pair' => 'Safe\socket_create_pair',
            'socket_export_stream' => 'Safe\socket_export_stream',
            'socket_getpeername' => 'Safe\socket_getpeername',
            'socket_getsockname' => 'Safe\socket_getsockname',
            'socket_get_option' => 'Safe\socket_get_option',
            'socket_import_stream' => 'Safe\socket_import_stream',
            'socket_listen' => 'Safe\socket_listen',
            'socket_read' => 'Safe\socket_read',
            'socket_send' => 'Safe\socket_send',
            'socket_sendmsg' => 'Safe\socket_sendmsg',
            'socket_sendto' => 'Safe\socket_sendto',
            'socket_set_block' => 'Safe\socket_set_block',
            'socket_set_nonblock' => 'Safe\socket_set_nonblock',
            'socket_set_option' => 'Safe\socket_set_option',
            'socket_shutdown' => 'Safe\socket_shutdown',
            'socket_write' => 'Safe\socket_write',
            'socket_wsaprotocol_info_export' => 'Safe\socket_wsaprotocol_info_export',
            'socket_wsaprotocol_info_import' => 'Safe\socket_wsaprotocol_info_import',
            'socket_wsaprotocol_info_release' => 'Safe\socket_wsaprotocol_info_release',
            'sodium_crypto_aead_aes256gcm_decrypt' => 'Safe\sodium_crypto_aead_aes256gcm_decrypt',
            'sodium_crypto_aead_chacha20poly1305_decrypt' => 'Safe\sodium_crypto_aead_chacha20poly1305_decrypt',
            'sodium_crypto_aead_chacha20poly1305_encrypt' => 'Safe\sodium_crypto_aead_chacha20poly1305_encrypt',
            'sodium_crypto_aead_chacha20poly1305_ietf_decrypt' => 'Safe\sodium_crypto_aead_chacha20poly1305_ietf_decrypt',
            'sodium_crypto_aead_chacha20poly1305_ietf_encrypt' => 'Safe\sodium_crypto_aead_chacha20poly1305_ietf_encrypt',
            'sodium_crypto_aead_xchacha20poly1305_ietf_decrypt' => 'Safe\sodium_crypto_aead_xchacha20poly1305_ietf_decrypt',
            'sodium_crypto_aead_xchacha20poly1305_ietf_encrypt' => 'Safe\sodium_crypto_aead_xchacha20poly1305_ietf_encrypt',
            'sodium_crypto_box_open' => 'Safe\sodium_crypto_box_open',
            'sodium_crypto_box_seal_open' => 'Safe\sodium_crypto_box_seal_open',
            'sodium_crypto_generichash_update' => 'Safe\sodium_crypto_generichash_update',
            'sodium_crypto_secretbox_open' => 'Safe\sodium_crypto_secretbox_open',
            'sodium_crypto_sign_open' => 'Safe\sodium_crypto_sign_open',
            'sodium_crypto_stream_xchacha20_xor_ic' => 'Safe\sodium_crypto_stream_xchacha20_xor_ic',
            'solr_get_version' => 'Safe\solr_get_version',
            'spl_autoload_register' => 'Safe\spl_autoload_register',
            'spl_autoload_unregister' => 'Safe\spl_autoload_unregister',
            'sqlsrv_begin_transaction' => 'Safe\sqlsrv_begin_transaction',
            'sqlsrv_cancel' => 'Safe\sqlsrv_cancel',
            'sqlsrv_client_info' => 'Safe\sqlsrv_client_info',
            'sqlsrv_close' => 'Safe\sqlsrv_close',
            'sqlsrv_commit' => 'Safe\sqlsrv_commit',
            'sqlsrv_configure' => 'Safe\sqlsrv_configure',
            'sqlsrv_execute' => 'Safe\sqlsrv_execute',
            'sqlsrv_fetch' => 'Safe\sqlsrv_fetch',
            'sqlsrv_fetch_array' => 'Safe\sqlsrv_fetch_array',
            'sqlsrv_fetch_object' => 'Safe\sqlsrv_fetch_object',
            'sqlsrv_free_stmt' => 'Safe\sqlsrv_free_stmt',
            'sqlsrv_get_field' => 'Safe\sqlsrv_get_field',
            'sqlsrv_next_result' => 'Safe\sqlsrv_next_result',
            'sqlsrv_num_fields' => 'Safe\sqlsrv_num_fields',
            'sqlsrv_num_rows' => 'Safe\sqlsrv_num_rows',
            'sqlsrv_prepare' => 'Safe\sqlsrv_prepare',
            'sqlsrv_query' => 'Safe\sqlsrv_query',
            'sqlsrv_rollback' => 'Safe\sqlsrv_rollback',
            'ssdeep_fuzzy_compare' => 'Safe\ssdeep_fuzzy_compare',
            'ssdeep_fuzzy_hash' => 'Safe\ssdeep_fuzzy_hash',
            'ssdeep_fuzzy_hash_filename' => 'Safe\ssdeep_fuzzy_hash_filename',
            'ssh2_auth_agent' => 'Safe\ssh2_auth_agent',
            'ssh2_auth_hostbased_file' => 'Safe\ssh2_auth_hostbased_file',
            'ssh2_auth_password' => 'Safe\ssh2_auth_password',
            'ssh2_auth_pubkey_file' => 'Safe\ssh2_auth_pubkey_file',
            'ssh2_connect' => 'Safe\ssh2_connect',
            'ssh2_disconnect' => 'Safe\ssh2_disconnect',
            'ssh2_exec' => 'Safe\ssh2_exec',
            'ssh2_forward_accept' => 'Safe\ssh2_forward_accept',
            'ssh2_forward_listen' => 'Safe\ssh2_forward_listen',
            'ssh2_publickey_add' => 'Safe\ssh2_publickey_add',
            'ssh2_publickey_init' => 'Safe\ssh2_publickey_init',
            'ssh2_publickey_remove' => 'Safe\ssh2_publickey_remove',
            'ssh2_scp_recv' => 'Safe\ssh2_scp_recv',
            'ssh2_scp_send' => 'Safe\ssh2_scp_send',
            'ssh2_send_eof' => 'Safe\ssh2_send_eof',
            'ssh2_sftp' => 'Safe\ssh2_sftp',
            'ssh2_sftp_chmod' => 'Safe\ssh2_sftp_chmod',
            'ssh2_sftp_mkdir' => 'Safe\ssh2_sftp_mkdir',
            'ssh2_sftp_rename' => 'Safe\ssh2_sftp_rename',
            'ssh2_sftp_rmdir' => 'Safe\ssh2_sftp_rmdir',
            'ssh2_sftp_symlink' => 'Safe\ssh2_sftp_symlink',
            'ssh2_sftp_unlink' => 'Safe\ssh2_sftp_unlink',
            'ssh2_shell' => 'Safe\ssh2_shell',
            'stream_context_set_params' => 'Safe\stream_context_set_params',
            'stream_copy_to_stream' => 'Safe\stream_copy_to_stream',
            'stream_filter_append' => 'Safe\stream_filter_append',
            'stream_filter_prepend' => 'Safe\stream_filter_prepend',
            'stream_filter_register' => 'Safe\stream_filter_register',
            'stream_filter_remove' => 'Safe\stream_filter_remove',
            'stream_get_contents' => 'Safe\stream_get_contents',
            'stream_get_line' => 'Safe\stream_get_line',
            'stream_isatty' => 'Safe\stream_isatty',
            'stream_resolve_include_path' => 'Safe\stream_resolve_include_path',
            'stream_set_blocking' => 'Safe\stream_set_blocking',
            'stream_set_timeout' => 'Safe\stream_set_timeout',
            'stream_socket_accept' => 'Safe\stream_socket_accept',
            'stream_socket_client' => 'Safe\stream_socket_client',
            'stream_socket_get_name' => 'Safe\stream_socket_get_name',
            'stream_socket_pair' => 'Safe\stream_socket_pair',
            'stream_socket_recvfrom' => 'Safe\stream_socket_recvfrom',
            'stream_socket_sendto' => 'Safe\stream_socket_sendto',
            'stream_socket_server' => 'Safe\stream_socket_server',
            'stream_socket_shutdown' => 'Safe\stream_socket_shutdown',
            'stream_supports_lock' => 'Safe\stream_supports_lock',
            'stream_wrapper_register' => 'Safe\stream_wrapper_register',
            'stream_wrapper_restore' => 'Safe\stream_wrapper_restore',
            'stream_wrapper_unregister' => 'Safe\stream_wrapper_unregister',
            'strftime' => 'Safe\strftime',
            'strptime' => 'Safe\strptime',
            'strtotime' => 'Safe\strtotime',
            'swoole_async_dns_lookup' => 'Safe\swoole_async_dns_lookup',
            'swoole_async_readfile' => 'Safe\swoole_async_readfile',
            'swoole_async_write' => 'Safe\swoole_async_write',
            'swoole_async_writefile' => 'Safe\swoole_async_writefile',
            'swoole_event_defer' => 'Safe\swoole_event_defer',
            'swoole_event_del' => 'Safe\swoole_event_del',
            'swoole_event_write' => 'Safe\swoole_event_write',
            'symlink' => 'Safe\symlink',
            'syslog' => 'Safe\syslog',
            'system' => 'Safe\system',
            'sys_getloadavg' => 'Safe\sys_getloadavg',
            'tempnam' => 'Safe\tempnam',
            'timezone_name_from_abbr' => 'Safe\timezone_name_from_abbr',
            'time_nanosleep' => 'Safe\time_nanosleep',
            'time_sleep_until' => 'Safe\time_sleep_until',
            'tmpfile' => 'Safe\tmpfile',
            'touch' => 'Safe\touch',
            'unixtojd' => 'Safe\unixtojd',
            'unlink' => 'Safe\unlink',
            'unpack' => 'Safe\unpack',
            'unserialize' => 'Safe\unserialize',
            'uopz_extend' => 'Safe\uopz_extend',
            'uopz_implement' => 'Safe\uopz_implement',
            'variant_date_to_timestamp' => 'Safe\variant_date_to_timestamp',
            'variant_round' => 'Safe\variant_round',
            'virtual' => 'Safe\virtual',
            'xdiff_file_bdiff' => 'Safe\xdiff_file_bdiff',
            'xdiff_file_bpatch' => 'Safe\xdiff_file_bpatch',
            'xdiff_file_diff' => 'Safe\xdiff_file_diff',
            'xdiff_file_diff_binary' => 'Safe\xdiff_file_diff_binary',
            'xdiff_file_patch_binary' => 'Safe\xdiff_file_patch_binary',
            'xdiff_file_rabdiff' => 'Safe\xdiff_file_rabdiff',
            'xdiff_string_bpatch' => 'Safe\xdiff_string_bpatch',
            'xdiff_string_patch' => 'Safe\xdiff_string_patch',
            'xdiff_string_patch_binary' => 'Safe\xdiff_string_patch_binary',
            'xmlrpc_set_type' => 'Safe\xmlrpc_set_type',
            'xml_parser_free' => 'Safe\xml_parser_free',
            'xml_set_character_data_handler' => 'Safe\xml_set_character_data_handler',
            'xml_set_default_handler' => 'Safe\xml_set_default_handler',
            'xml_set_element_handler' => 'Safe\xml_set_element_handler',
            'xml_set_end_namespace_decl_handler' => 'Safe\xml_set_end_namespace_decl_handler',
            'xml_set_external_entity_ref_handler' => 'Safe\xml_set_external_entity_ref_handler',
            'xml_set_notation_decl_handler' => 'Safe\xml_set_notation_decl_handler',
            'xml_set_object' => 'Safe\xml_set_object',
            'xml_set_processing_instruction_handler' => 'Safe\xml_set_processing_instruction_handler',
            'xml_set_start_namespace_decl_handler' => 'Safe\xml_set_start_namespace_decl_handler',
            'xml_set_unparsed_entity_decl_handler' => 'Safe\xml_set_unparsed_entity_decl_handler',
            'yaml_parse' => 'Safe\yaml_parse',
            'yaml_parse_file' => 'Safe\yaml_parse_file',
            'yaml_parse_url' => 'Safe\yaml_parse_url',
            'yaz_ccl_parse' => 'Safe\yaz_ccl_parse',
            'yaz_close' => 'Safe\yaz_close',
            'yaz_connect' => 'Safe\yaz_connect',
            'yaz_database' => 'Safe\yaz_database',
            'yaz_element' => 'Safe\yaz_element',
            'yaz_present' => 'Safe\yaz_present',
            'yaz_search' => 'Safe\yaz_search',
            'yaz_wait' => 'Safe\yaz_wait',
            'zip_entry_close' => 'Safe\zip_entry_close',
            'zip_entry_compressedsize' => 'Safe\zip_entry_compressedsize',
            'zip_entry_compressionmethod' => 'Safe\zip_entry_compressionmethod',
            'zip_entry_filesize' => 'Safe\zip_entry_filesize',
            'zip_entry_name' => 'Safe\zip_entry_name',
            'zip_entry_open' => 'Safe\zip_entry_open',
            'zip_entry_read' => 'Safe\zip_entry_read',
            'zlib_decode' => 'Safe\zlib_decode',
        ]
    );
};
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/iconv.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/iconv.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/iconv.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/iconv.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/iconv.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/iconv.php';
}
<?php

namespace Safe;

use Safe\Exceptions\DatetimeException;

/**
 * @param null|string $datetime
 * @param \DateTimeZone|null $timezone
 * @return \DateTime
 * @throws DatetimeException
 *
 */
function date_create(?string $datetime = "now", ?\DateTimeZone $timezone = null): \DateTime
{
    error_clear_last();
    if ($timezone !== null) {
        $safeResult = \date_create($datetime, $timezone);
    } else {
        $safeResult = \date_create($datetime);
    }
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $format
 * @param string $datetime
 * @return array{year: int|false, month: int|false, day: int|false, hour: int|false, minute: int|false, second: int|false, fraction: float|false, warning_count: int, warnings: string[], error_count: int, errors: string[], is_localtime: bool, zone_type: int|bool, zone: int|bool, is_dst: bool, tz_abbr: string, tz_id: string, relative: array{year: int, month: int, day: int, hour: int, minute: int, second: int, weekday: int, weekdays: int, first_day_of_month: bool, last_day_of_month: bool}}|null
 * @throws DatetimeException
 *
 */
function date_parse_from_format(string $format, string $datetime): ?array
{
    error_clear_last();
    $safeResult = \date_parse_from_format($format, $datetime);
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $datetime
 * @return array{year: int|false, month: int|false, day: int|false, hour: int|false, minute: int|false, second: int|false, fraction: float|false, warning_count: int, warnings: string[], error_count: int, errors: string[], is_localtime: bool, zone_type: int|bool, zone: int|bool, is_dst: bool, tz_abbr: string, tz_id: string, relative: array{year: int, month: int, day: int, hour: int, minute: int, second: int, weekday: int, weekdays: int, first_day_of_month: bool, last_day_of_month: bool}}|null
 * @throws DatetimeException
 *
 */
function date_parse(string $datetime): ?array
{
    error_clear_last();
    $safeResult = \date_parse($datetime);
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $timestamp
 * @param float $latitude
 * @param float $longitude
 * @return array{sunrise: int|bool,sunset: int|bool,transit: int|bool,civil_twilight_begin: int|bool,civil_twilight_end: int|bool,nautical_twilight_begin: int|bool,nautical_twilight_end: int|bool,astronomical_twilight_begin: int|bool,astronomical_twilight_end: int|bool}
 * @throws DatetimeException
 *
 */
function date_sun_info(int $timestamp, float $latitude, float $longitude): array
{
    error_clear_last();
    $safeResult = \date_sun_info($timestamp, $latitude, $longitude);
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $timestamp
 * @param int $returnFormat
 * @param float|null $latitude
 * @param float|null $longitude
 * @param float|null $zenith
 * @param float|null $utcOffset
 * @return mixed
 * @throws DatetimeException
 *
 */
function date_sunrise(int $timestamp, int $returnFormat = SUNFUNCS_RET_STRING, ?float $latitude = null, ?float $longitude = null, ?float $zenith = null, ?float $utcOffset = null)
{
    error_clear_last();
    if ($utcOffset !== null) {
        $safeResult = \date_sunrise($timestamp, $returnFormat, $latitude, $longitude, $zenith, $utcOffset);
    } elseif ($zenith !== null) {
        $safeResult = \date_sunrise($timestamp, $returnFormat, $latitude, $longitude, $zenith);
    } elseif ($longitude !== null) {
        $safeResult = \date_sunrise($timestamp, $returnFormat, $latitude, $longitude);
    } elseif ($latitude !== null) {
        $safeResult = \date_sunrise($timestamp, $returnFormat, $latitude);
    } else {
        $safeResult = \date_sunrise($timestamp, $returnFormat);
    }
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $timestamp
 * @param int $returnFormat
 * @param float|null $latitude
 * @param float|null $longitude
 * @param float|null $zenith
 * @param float|null $utcOffset
 * @return mixed
 * @throws DatetimeException
 *
 */
function date_sunset(int $timestamp, int $returnFormat = SUNFUNCS_RET_STRING, ?float $latitude = null, ?float $longitude = null, ?float $zenith = null, ?float $utcOffset = null)
{
    error_clear_last();
    if ($utcOffset !== null) {
        $safeResult = \date_sunset($timestamp, $returnFormat, $latitude, $longitude, $zenith, $utcOffset);
    } elseif ($zenith !== null) {
        $safeResult = \date_sunset($timestamp, $returnFormat, $latitude, $longitude, $zenith);
    } elseif ($longitude !== null) {
        $safeResult = \date_sunset($timestamp, $returnFormat, $latitude, $longitude);
    } elseif ($latitude !== null) {
        $safeResult = \date_sunset($timestamp, $returnFormat, $latitude);
    } else {
        $safeResult = \date_sunset($timestamp, $returnFormat);
    }
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $format
 * @param int|null $timestamp
 * @return string
 * @throws DatetimeException
 *
 */
function date(string $format, ?int $timestamp = null): string
{
    error_clear_last();
    if ($timestamp !== null) {
        $safeResult = \date($format, $timestamp);
    } else {
        $safeResult = \date($format);
    }
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $hour
 * @param int|null $minute
 * @param int|null $second
 * @param int|null $month
 * @param int|null $day
 * @param int|null $year
 * @return int
 * @throws DatetimeException
 *
 */
function gmmktime(int $hour, ?int $minute = null, ?int $second = null, ?int $month = null, ?int $day = null, ?int $year = null): int
{
    error_clear_last();
    if ($year !== null) {
        $safeResult = \gmmktime($hour, $minute, $second, $month, $day, $year);
    } elseif ($day !== null) {
        $safeResult = \gmmktime($hour, $minute, $second, $month, $day);
    } elseif ($month !== null) {
        $safeResult = \gmmktime($hour, $minute, $second, $month);
    } elseif ($second !== null) {
        $safeResult = \gmmktime($hour, $minute, $second);
    } elseif ($minute !== null) {
        $safeResult = \gmmktime($hour, $minute);
    } else {
        $safeResult = \gmmktime($hour);
    }
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $format
 * @param int|null $timestamp
 * @return string
 * @throws DatetimeException
 *
 */
function gmstrftime(string $format, ?int $timestamp = null): string
{
    error_clear_last();
    if ($timestamp !== null) {
        $safeResult = \gmstrftime($format, $timestamp);
    } else {
        $safeResult = \gmstrftime($format);
    }
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $format
 * @param int|null $timestamp
 * @return int
 * @throws DatetimeException
 *
 */
function idate(string $format, ?int $timestamp = null): int
{
    error_clear_last();
    if ($timestamp !== null) {
        $safeResult = \idate($format, $timestamp);
    } else {
        $safeResult = \idate($format);
    }
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $hour
 * @param int|null $minute
 * @param int|null $second
 * @param int|null $month
 * @param int|null $day
 * @param int|null $year
 * @return int
 * @throws DatetimeException
 *
 */
function mktime(int $hour, ?int $minute = null, ?int $second = null, ?int $month = null, ?int $day = null, ?int $year = null): int
{
    error_clear_last();
    if ($year !== null) {
        $safeResult = \mktime($hour, $minute, $second, $month, $day, $year);
    } elseif ($day !== null) {
        $safeResult = \mktime($hour, $minute, $second, $month, $day);
    } elseif ($month !== null) {
        $safeResult = \mktime($hour, $minute, $second, $month);
    } elseif ($second !== null) {
        $safeResult = \mktime($hour, $minute, $second);
    } elseif ($minute !== null) {
        $safeResult = \mktime($hour, $minute);
    } else {
        $safeResult = \mktime($hour);
    }
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $format
 * @param int|null $timestamp
 * @return string
 * @throws DatetimeException
 *
 */
function strftime(string $format, ?int $timestamp = null): string
{
    error_clear_last();
    if ($timestamp !== null) {
        $safeResult = \strftime($format, $timestamp);
    } else {
        $safeResult = \strftime($format);
    }
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $timestamp
 * @param string $format
 * @return array
 * @throws DatetimeException
 *
 */
function strptime(string $timestamp, string $format): array
{
    error_clear_last();
    $safeResult = \strptime($timestamp, $format);
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $datetime
 * @param int|null $baseTimestamp
 * @return int
 * @throws DatetimeException
 *
 */
function strtotime(string $datetime, ?int $baseTimestamp = null): int
{
    error_clear_last();
    if ($baseTimestamp !== null) {
        $safeResult = \strtotime($datetime, $baseTimestamp);
    } else {
        $safeResult = \strtotime($datetime);
    }
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $abbr
 * @param int $utcOffset
 * @param int $isDST
 * @return string
 * @throws DatetimeException
 *
 */
function timezone_name_from_abbr(string $abbr, int $utcOffset = -1, int $isDST = -1): string
{
    error_clear_last();
    $safeResult = \timezone_name_from_abbr($abbr, $utcOffset, $isDST);
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\OpensslException;

/**
 * @param string $cipher_algo
 * @return int
 * @throws OpensslException
 *
 */
function openssl_cipher_iv_length(string $cipher_algo): int
{
    error_clear_last();
    $safeResult = \openssl_cipher_iv_length($cipher_algo);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $cipher_algo
 * @return int
 * @throws OpensslException
 *
 */
function openssl_cipher_key_length(string $cipher_algo): int
{
    error_clear_last();
    $safeResult = \openssl_cipher_key_length($cipher_algo);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $input_filename
 * @param string $output_filename
 * @param \OpenSSLCertificate|string $certificate
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|null|string $private_key
 * @param int $encoding
 * @throws OpensslException
 *
 */
function openssl_cms_decrypt(string $input_filename, string $output_filename, $certificate, $private_key = null, int $encoding = OPENSSL_ENCODING_SMIME): void
{
    error_clear_last();
    if ($encoding !== OPENSSL_ENCODING_SMIME) {
        $safeResult = \openssl_cms_decrypt($input_filename, $output_filename, $certificate, $private_key, $encoding);
    } elseif ($private_key !== null) {
        $safeResult = \openssl_cms_decrypt($input_filename, $output_filename, $certificate, $private_key);
    } else {
        $safeResult = \openssl_cms_decrypt($input_filename, $output_filename, $certificate);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $input_filename
 * @param string $output_filename
 * @param \OpenSSLCertificate|array|string $certificate
 * @param array|null $headers
 * @param int $flags
 * @param int $encoding
 * @param int $cipher_algo
 * @throws OpensslException
 *
 */
function openssl_cms_encrypt(string $input_filename, string $output_filename, $certificate, ?array $headers, int $flags = 0, int $encoding = OPENSSL_ENCODING_SMIME, int $cipher_algo = OPENSSL_CIPHER_AES_128_CBC): void
{
    error_clear_last();
    $safeResult = \openssl_cms_encrypt($input_filename, $output_filename, $certificate, $headers, $flags, $encoding, $cipher_algo);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $input_filename
 * @param array $certificates
 * @throws OpensslException
 *
 */
function openssl_cms_read(string $input_filename, array &$certificates): void
{
    error_clear_last();
    $safeResult = \openssl_cms_read($input_filename, $certificates);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $input_filename
 * @param string $output_filename
 * @param \OpenSSLCertificate|string $certificate
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param array|null $headers
 * @param int $flags
 * @param int $encoding
 * @param null|string $untrusted_certificates_filename
 * @throws OpensslException
 *
 */
function openssl_cms_sign(string $input_filename, string $output_filename, $certificate, $private_key, ?array $headers, int $flags = 0, int $encoding = OPENSSL_ENCODING_SMIME, ?string $untrusted_certificates_filename = null): void
{
    error_clear_last();
    if ($untrusted_certificates_filename !== null) {
        $safeResult = \openssl_cms_sign($input_filename, $output_filename, $certificate, $private_key, $headers, $flags, $encoding, $untrusted_certificates_filename);
    } else {
        $safeResult = \openssl_cms_sign($input_filename, $output_filename, $certificate, $private_key, $headers, $flags, $encoding);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $input_filename
 * @param int $flags
 * @param null|string $certificates
 * @param array $ca_info
 * @param null|string $untrusted_certificates_filename
 * @param null|string $content
 * @param null|string $pk7
 * @param null|string $sigfile
 * @param int $encoding
 * @throws OpensslException
 *
 */
function openssl_cms_verify(string $input_filename, int $flags = 0, ?string $certificates = null, array $ca_info = [], ?string $untrusted_certificates_filename = null, ?string $content = null, ?string $pk7 = null, ?string $sigfile = null, int $encoding = OPENSSL_ENCODING_SMIME): void
{
    error_clear_last();
    if ($encoding !== OPENSSL_ENCODING_SMIME) {
        $safeResult = \openssl_cms_verify($input_filename, $flags, $certificates, $ca_info, $untrusted_certificates_filename, $content, $pk7, $sigfile, $encoding);
    } elseif ($sigfile !== null) {
        $safeResult = \openssl_cms_verify($input_filename, $flags, $certificates, $ca_info, $untrusted_certificates_filename, $content, $pk7, $sigfile);
    } elseif ($pk7 !== null) {
        $safeResult = \openssl_cms_verify($input_filename, $flags, $certificates, $ca_info, $untrusted_certificates_filename, $content, $pk7);
    } elseif ($content !== null) {
        $safeResult = \openssl_cms_verify($input_filename, $flags, $certificates, $ca_info, $untrusted_certificates_filename, $content);
    } elseif ($untrusted_certificates_filename !== null) {
        $safeResult = \openssl_cms_verify($input_filename, $flags, $certificates, $ca_info, $untrusted_certificates_filename);
    } elseif ($ca_info !== []) {
        $safeResult = \openssl_cms_verify($input_filename, $flags, $certificates, $ca_info);
    } elseif ($certificates !== null) {
        $safeResult = \openssl_cms_verify($input_filename, $flags, $certificates);
    } else {
        $safeResult = \openssl_cms_verify($input_filename, $flags);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLCertificateSigningRequest|string $csr
 * @param string $output_filename
 * @param bool $no_text
 * @throws OpensslException
 *
 */
function openssl_csr_export_to_file($csr, string $output_filename, bool $no_text = true): void
{
    error_clear_last();
    $safeResult = \openssl_csr_export_to_file($csr, $output_filename, $no_text);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLCertificateSigningRequest|string $csr
 * @param null|string $output
 * @param bool $no_text
 * @throws OpensslException
 *
 */
function openssl_csr_export($csr, ?string &$output, bool $no_text = true): void
{
    error_clear_last();
    $safeResult = \openssl_csr_export($csr, $output, $no_text);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLCertificateSigningRequest|string $csr
 * @param bool $short_names
 * @return \OpenSSLAsymmetricKey
 * @throws OpensslException
 *
 */
function openssl_csr_get_public_key($csr, bool $short_names = true): \OpenSSLAsymmetricKey
{
    error_clear_last();
    $safeResult = \openssl_csr_get_public_key($csr, $short_names);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLCertificateSigningRequest|string $csr
 * @param bool $short_names
 * @return array
 * @throws OpensslException
 *
 */
function openssl_csr_get_subject($csr, bool $short_names = true): array
{
    error_clear_last();
    $safeResult = \openssl_csr_get_subject($csr, $short_names);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param array $distinguished_names
 * @param \OpenSSLAsymmetricKey $private_key
 * @param array|null $options
 * @param array|null $extra_attributes
 * @return \OpenSSLCertificateSigningRequest
 * @throws OpensslException
 *
 */
function openssl_csr_new(array $distinguished_names, \OpenSSLAsymmetricKey &$private_key, ?array $options = null, ?array $extra_attributes = null): \OpenSSLCertificateSigningRequest
{
    error_clear_last();
    if ($extra_attributes !== null) {
        $safeResult = \openssl_csr_new($distinguished_names, $private_key, $options, $extra_attributes);
    } elseif ($options !== null) {
        $safeResult = \openssl_csr_new($distinguished_names, $private_key, $options);
    } else {
        $safeResult = \openssl_csr_new($distinguished_names, $private_key);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLCertificateSigningRequest|string $csr
 * @param \OpenSSLCertificate|null|string $ca_certificate
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param int $days
 * @param array|null $options
 * @param int $serial
 * @return \OpenSSLCertificate
 * @throws OpensslException
 *
 */
function openssl_csr_sign($csr, $ca_certificate, $private_key, int $days, ?array $options = null, int $serial = 0): \OpenSSLCertificate
{
    error_clear_last();
    if ($serial !== 0) {
        $safeResult = \openssl_csr_sign($csr, $ca_certificate, $private_key, $days, $options, $serial);
    } elseif ($options !== null) {
        $safeResult = \openssl_csr_sign($csr, $ca_certificate, $private_key, $days, $options);
    } else {
        $safeResult = \openssl_csr_sign($csr, $ca_certificate, $private_key, $days);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param string $cipher_algo
 * @param string $passphrase
 * @param int $options
 * @param string $iv
 * @param null|string $tag
 * @param string $aad
 * @return string
 * @throws OpensslException
 *
 */
function openssl_decrypt(string $data, string $cipher_algo, string $passphrase, int $options = 0, string $iv = "", ?string $tag = null, string $aad = ""): string
{
    error_clear_last();
    if ($aad !== "") {
        $safeResult = \openssl_decrypt($data, $cipher_algo, $passphrase, $options, $iv, $tag, $aad);
    } elseif ($tag !== null) {
        $safeResult = \openssl_decrypt($data, $cipher_algo, $passphrase, $options, $iv, $tag);
    } else {
        $safeResult = \openssl_decrypt($data, $cipher_algo, $passphrase, $options, $iv);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $public_key
 * @param \OpenSSLAsymmetricKey $private_key
 * @return string
 * @throws OpensslException
 *
 */
function openssl_dh_compute_key(string $public_key, \OpenSSLAsymmetricKey $private_key): string
{
    error_clear_last();
    $safeResult = \openssl_dh_compute_key($public_key, $private_key);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param string $digest_algo
 * @param bool $binary
 * @return string
 * @throws OpensslException
 *
 */
function openssl_digest(string $data, string $digest_algo, bool $binary = false): string
{
    error_clear_last();
    $safeResult = \openssl_digest($data, $digest_algo, $binary);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return list
 * @throws OpensslException
 *
 */
function openssl_get_curve_names(): array
{
    error_clear_last();
    $safeResult = \openssl_get_curve_names();
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param null|string $output
 * @param string $encrypted_key
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param string $cipher_algo
 * @param null|string $iv
 * @throws OpensslException
 *
 */
function openssl_open(string $data, ?string &$output, string $encrypted_key, $private_key, string $cipher_algo, ?string $iv = null): void
{
    error_clear_last();
    if ($iv !== null) {
        $safeResult = \openssl_open($data, $output, $encrypted_key, $private_key, $cipher_algo, $iv);
    } else {
        $safeResult = \openssl_open($data, $output, $encrypted_key, $private_key, $cipher_algo);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $password
 * @param string $salt
 * @param int $key_length
 * @param int $iterations
 * @param string $digest_algo
 * @return string
 * @throws OpensslException
 *
 */
function openssl_pbkdf2(string $password, string $salt, int $key_length, int $iterations, string $digest_algo = "sha1"): string
{
    error_clear_last();
    $safeResult = \openssl_pbkdf2($password, $salt, $key_length, $iterations, $digest_algo);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLCertificate|string $certificate
 * @param string $output_filename
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param string $passphrase
 * @param array $options
 * @throws OpensslException
 *
 */
function openssl_pkcs12_export_to_file($certificate, string $output_filename, $private_key, string $passphrase, array $options = []): void
{
    error_clear_last();
    $safeResult = \openssl_pkcs12_export_to_file($certificate, $output_filename, $private_key, $passphrase, $options);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLCertificate|string $certificate
 * @param null|string $output
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param string $passphrase
 * @param array $options
 * @throws OpensslException
 *
 */
function openssl_pkcs12_export($certificate, ?string &$output, $private_key, string $passphrase, array $options = []): void
{
    error_clear_last();
    $safeResult = \openssl_pkcs12_export($certificate, $output, $private_key, $passphrase, $options);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $pkcs12
 * @param array|null $certificates
 * @param string $passphrase
 * @throws OpensslException
 *
 */
function openssl_pkcs12_read(string $pkcs12, ?array &$certificates, string $passphrase): void
{
    error_clear_last();
    $safeResult = \openssl_pkcs12_read($pkcs12, $certificates, $passphrase);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $input_filename
 * @param string $output_filename
 * @param \OpenSSLCertificate|string $certificate
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|null|string $private_key
 * @throws OpensslException
 *
 */
function openssl_pkcs7_decrypt(string $input_filename, string $output_filename, $certificate, $private_key = null): void
{
    error_clear_last();
    if ($private_key !== null) {
        $safeResult = \openssl_pkcs7_decrypt($input_filename, $output_filename, $certificate, $private_key);
    } else {
        $safeResult = \openssl_pkcs7_decrypt($input_filename, $output_filename, $certificate);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $input_filename
 * @param string $output_filename
 * @param \OpenSSLCertificate|array|string $certificate
 * @param array $headers
 * @param int $flags
 * @param int $cipher_algo
 * @throws OpensslException
 *
 */
function openssl_pkcs7_encrypt(string $input_filename, string $output_filename, $certificate, array $headers, int $flags = 0, int $cipher_algo = OPENSSL_CIPHER_AES_128_CBC): void
{
    error_clear_last();
    $safeResult = \openssl_pkcs7_encrypt($input_filename, $output_filename, $certificate, $headers, $flags, $cipher_algo);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $data
 * @param array|null $certificates
 * @throws OpensslException
 *
 */
function openssl_pkcs7_read(string $data, ?array &$certificates): void
{
    error_clear_last();
    $safeResult = \openssl_pkcs7_read($data, $certificates);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $input_filename
 * @param string $output_filename
 * @param \OpenSSLCertificate|string $certificate
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param array $headers
 * @param int $flags
 * @param null|string $untrusted_certificates_filename
 * @throws OpensslException
 *
 */
function openssl_pkcs7_sign(string $input_filename, string $output_filename, $certificate, $private_key, array $headers, int $flags = PKCS7_DETACHED, ?string $untrusted_certificates_filename = null): void
{
    error_clear_last();
    if ($untrusted_certificates_filename !== null) {
        $safeResult = \openssl_pkcs7_sign($input_filename, $output_filename, $certificate, $private_key, $headers, $flags, $untrusted_certificates_filename);
    } else {
        $safeResult = \openssl_pkcs7_sign($input_filename, $output_filename, $certificate, $private_key, $headers, $flags);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $public_key
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param int $key_length
 * @return string
 * @throws OpensslException
 *
 */
function openssl_pkey_derive($public_key, $private_key, int $key_length = 0): string
{
    error_clear_last();
    $safeResult = \openssl_pkey_derive($public_key, $private_key, $key_length);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $key
 * @param string $output_filename
 * @param null|string $passphrase
 * @param array|null $options
 * @throws OpensslException
 *
 */
function openssl_pkey_export_to_file($key, string $output_filename, ?string $passphrase = null, ?array $options = null): void
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \openssl_pkey_export_to_file($key, $output_filename, $passphrase, $options);
    } elseif ($passphrase !== null) {
        $safeResult = \openssl_pkey_export_to_file($key, $output_filename, $passphrase);
    } else {
        $safeResult = \openssl_pkey_export_to_file($key, $output_filename);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $key
 * @param null|string $output
 * @param null|string $passphrase
 * @param array|null $options
 * @throws OpensslException
 *
 */
function openssl_pkey_export($key, ?string &$output, ?string $passphrase = null, ?array $options = null): void
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \openssl_pkey_export($key, $output, $passphrase, $options);
    } elseif ($passphrase !== null) {
        $safeResult = \openssl_pkey_export($key, $output, $passphrase);
    } else {
        $safeResult = \openssl_pkey_export($key, $output);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLAsymmetricKey $key
 * @return array
 * @throws OpensslException
 *
 */
function openssl_pkey_get_details(\OpenSSLAsymmetricKey $key): array
{
    error_clear_last();
    $safeResult = \openssl_pkey_get_details($key);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param null|string $passphrase
 * @return \OpenSSLAsymmetricKey
 * @throws OpensslException
 *
 */
function openssl_pkey_get_private($private_key, ?string $passphrase = null): \OpenSSLAsymmetricKey
{
    error_clear_last();
    if ($passphrase !== null) {
        $safeResult = \openssl_pkey_get_private($private_key, $passphrase);
    } else {
        $safeResult = \openssl_pkey_get_private($private_key);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $public_key
 * @return \OpenSSLAsymmetricKey
 * @throws OpensslException
 *
 */
function openssl_pkey_get_public($public_key): \OpenSSLAsymmetricKey
{
    error_clear_last();
    $safeResult = \openssl_pkey_get_public($public_key);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param array|null $options
 * @return \OpenSSLAsymmetricKey
 * @throws OpensslException
 *
 */
function openssl_pkey_new(?array $options = null): \OpenSSLAsymmetricKey
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \openssl_pkey_new($options);
    } else {
        $safeResult = \openssl_pkey_new();
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param null|string $decrypted_data
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param int $padding
 * @throws OpensslException
 *
 */
function openssl_private_decrypt(string $data, ?string &$decrypted_data, $private_key, int $padding = OPENSSL_PKCS1_PADDING): void
{
    error_clear_last();
    $safeResult = \openssl_private_decrypt($data, $decrypted_data, $private_key, $padding);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $data
 * @param null|string $encrypted_data
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param int $padding
 * @throws OpensslException
 *
 */
function openssl_private_encrypt(string $data, ?string &$encrypted_data, $private_key, int $padding = OPENSSL_PKCS1_PADDING): void
{
    error_clear_last();
    $safeResult = \openssl_private_encrypt($data, $encrypted_data, $private_key, $padding);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $data
 * @param null|string $decrypted_data
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $public_key
 * @param int $padding
 * @throws OpensslException
 *
 */
function openssl_public_decrypt(string $data, ?string &$decrypted_data, $public_key, int $padding = OPENSSL_PKCS1_PADDING): void
{
    error_clear_last();
    $safeResult = \openssl_public_decrypt($data, $decrypted_data, $public_key, $padding);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $data
 * @param null|string $encrypted_data
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $public_key
 * @param int $padding
 * @throws OpensslException
 *
 */
function openssl_public_encrypt(string $data, ?string &$encrypted_data, $public_key, int $padding = OPENSSL_PKCS1_PADDING): void
{
    error_clear_last();
    $safeResult = \openssl_public_encrypt($data, $encrypted_data, $public_key, $padding);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param int $length
 * @param bool|null $strong_result
 * @return string
 *
 */
function openssl_random_pseudo_bytes(int $length, ?bool &$strong_result = null): string
{
    error_clear_last();
    $safeResult = \openssl_random_pseudo_bytes($length, $strong_result);
    return $safeResult;
}


/**
 * @param string $data
 * @param null|string $sealed_data
 * @param array|null $encrypted_keys
 * @param array $public_key
 * @param string $cipher_algo
 * @param null|string $iv
 * @return int
 * @throws OpensslException
 *
 */
function openssl_seal(string $data, ?string &$sealed_data, ?array &$encrypted_keys, array $public_key, string $cipher_algo, ?string &$iv = null): int
{
    error_clear_last();
    $safeResult = \openssl_seal($data, $sealed_data, $encrypted_keys, $public_key, $cipher_algo, $iv);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param null|string $signature
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param int|string $algorithm
 * @throws OpensslException
 *
 */
function openssl_sign(string $data, ?string &$signature, $private_key, $algorithm = OPENSSL_ALGO_SHA1): void
{
    error_clear_last();
    $safeResult = \openssl_sign($data, $signature, $private_key, $algorithm);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $spki
 * @return null|string
 * @throws OpensslException
 *
 */
function openssl_spki_export_challenge(string $spki): ?string
{
    error_clear_last();
    $safeResult = \openssl_spki_export_challenge($spki);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $spki
 * @return null|string
 * @throws OpensslException
 *
 */
function openssl_spki_export(string $spki): ?string
{
    error_clear_last();
    $safeResult = \openssl_spki_export($spki);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLAsymmetricKey $private_key
 * @param string $challenge
 * @param int $digest_algo
 * @return null|string
 * @throws OpensslException
 *
 */
function openssl_spki_new(\OpenSSLAsymmetricKey $private_key, string $challenge, int $digest_algo = OPENSSL_ALGO_MD5): ?string
{
    error_clear_last();
    $safeResult = \openssl_spki_new($private_key, $challenge, $digest_algo);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $spki
 * @throws OpensslException
 *
 */
function openssl_spki_verify(string $spki): void
{
    error_clear_last();
    $safeResult = \openssl_spki_verify($spki);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $data
 * @param string $signature
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $public_key
 * @param int|string $algorithm
 * @return -1|0|1
 * @throws OpensslException
 *
 */
function openssl_verify(string $data, string $signature, $public_key, $algorithm = OPENSSL_ALGO_SHA1): int
{
    error_clear_last();
    $safeResult = \openssl_verify($data, $signature, $public_key, $algorithm);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLCertificate|string $certificate
 * @param int $purpose
 * @param array $ca_info
 * @param null|string $untrusted_certificates_file
 * @return bool|int
 * @throws OpensslException
 *
 */
function openssl_x509_checkpurpose($certificate, int $purpose, array $ca_info = [], ?string $untrusted_certificates_file = null)
{
    error_clear_last();
    if ($untrusted_certificates_file !== null) {
        $safeResult = \openssl_x509_checkpurpose($certificate, $purpose, $ca_info, $untrusted_certificates_file);
    } else {
        $safeResult = \openssl_x509_checkpurpose($certificate, $purpose, $ca_info);
    }
    if ($safeResult === -1) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLCertificate|string $certificate
 * @param string $output_filename
 * @param bool $no_text
 * @throws OpensslException
 *
 */
function openssl_x509_export_to_file($certificate, string $output_filename, bool $no_text = true): void
{
    error_clear_last();
    $safeResult = \openssl_x509_export_to_file($certificate, $output_filename, $no_text);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLCertificate|string $certificate
 * @param null|string $output
 * @param bool $no_text
 * @throws OpensslException
 *
 */
function openssl_x509_export($certificate, ?string &$output, bool $no_text = true): void
{
    error_clear_last();
    $safeResult = \openssl_x509_export($certificate, $output, $no_text);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLCertificate|string $certificate
 * @param string $digest_algo
 * @param bool $binary
 * @return string
 * @throws OpensslException
 *
 */
function openssl_x509_fingerprint($certificate, string $digest_algo = "sha1", bool $binary = false): string
{
    error_clear_last();
    $safeResult = \openssl_x509_fingerprint($certificate, $digest_algo, $binary);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLCertificate|string $certificate
 * @return \OpenSSLCertificate
 * @throws OpensslException
 *
 */
function openssl_x509_read($certificate): \OpenSSLCertificate
{
    error_clear_last();
    $safeResult = \openssl_x509_read($certificate);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\StreamException;

/**
 * @param resource $context
 * @param array $params
 * @throws StreamException
 *
 */
function stream_context_set_params($context, array $params): void
{
    error_clear_last();
    $safeResult = \stream_context_set_params($context, $params);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param resource $from
 * @param resource $to
 * @param int|null $length
 * @param int $offset
 * @return int
 * @throws StreamException
 *
 */
function stream_copy_to_stream($from, $to, ?int $length = null, int $offset = 0): int
{
    error_clear_last();
    if ($offset !== 0) {
        $safeResult = \stream_copy_to_stream($from, $to, $length, $offset);
    } elseif ($length !== null) {
        $safeResult = \stream_copy_to_stream($from, $to, $length);
    } else {
        $safeResult = \stream_copy_to_stream($from, $to);
    }
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @param string $filtername
 * @param int $read_write
 * @param mixed $params
 * @return resource
 * @throws StreamException
 *
 */
function stream_filter_append($stream, string $filtername, ?int $read_write = null, $params = null)
{
    error_clear_last();
    if ($params !== null) {
        $safeResult = \stream_filter_append($stream, $filtername, $read_write, $params);
    } elseif ($read_write !== null) {
        $safeResult = \stream_filter_append($stream, $filtername, $read_write);
    } else {
        $safeResult = \stream_filter_append($stream, $filtername);
    }
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @param string $filtername
 * @param int $read_write
 * @param mixed $params
 * @return resource
 * @throws StreamException
 *
 */
function stream_filter_prepend($stream, string $filtername, ?int $read_write = null, $params = null)
{
    error_clear_last();
    if ($params !== null) {
        $safeResult = \stream_filter_prepend($stream, $filtername, $read_write, $params);
    } elseif ($read_write !== null) {
        $safeResult = \stream_filter_prepend($stream, $filtername, $read_write);
    } else {
        $safeResult = \stream_filter_prepend($stream, $filtername);
    }
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filter_name
 * @param string $class
 * @throws StreamException
 *
 */
function stream_filter_register(string $filter_name, string $class): void
{
    error_clear_last();
    $safeResult = \stream_filter_register($filter_name, $class);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param resource $stream_filter
 * @throws StreamException
 *
 */
function stream_filter_remove($stream_filter): void
{
    error_clear_last();
    $safeResult = \stream_filter_remove($stream_filter);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param resource $stream
 * @param int|null $length
 * @param int $offset
 * @return string
 * @throws StreamException
 *
 */
function stream_get_contents($stream, ?int $length = null, int $offset = -1): string
{
    error_clear_last();
    if ($offset !== -1) {
        $safeResult = \stream_get_contents($stream, $length, $offset);
    } elseif ($length !== null) {
        $safeResult = \stream_get_contents($stream, $length);
    } else {
        $safeResult = \stream_get_contents($stream);
    }
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @param int $length
 * @param string $ending
 * @return string
 * @throws StreamException
 *
 */
function stream_get_line($stream, int $length, string $ending = ""): string
{
    error_clear_last();
    $safeResult = \stream_get_line($stream, $length, $ending);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @throws StreamException
 *
 */
function stream_isatty($stream): void
{
    error_clear_last();
    $safeResult = \stream_isatty($stream);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @return string
 * @throws StreamException
 *
 */
function stream_resolve_include_path(string $filename): string
{
    error_clear_last();
    $safeResult = \stream_resolve_include_path($filename);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @param bool $enable
 * @throws StreamException
 *
 */
function stream_set_blocking($stream, bool $enable): void
{
    error_clear_last();
    $safeResult = \stream_set_blocking($stream, $enable);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param resource $stream
 * @param int $seconds
 * @param int $microseconds
 * @throws StreamException
 *
 */
function stream_set_timeout($stream, int $seconds, int $microseconds = 0): void
{
    error_clear_last();
    $safeResult = \stream_set_timeout($stream, $seconds, $microseconds);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param resource $socket
 * @param float|null $timeout
 * @param null|string $peer_name
 * @return resource
 * @throws StreamException
 *
 */
function stream_socket_accept($socket, ?float $timeout = null, ?string &$peer_name = null)
{
    error_clear_last();
    if ($peer_name !== null) {
        $safeResult = \stream_socket_accept($socket, $timeout, $peer_name);
    } elseif ($timeout !== null) {
        $safeResult = \stream_socket_accept($socket, $timeout);
    } else {
        $safeResult = \stream_socket_accept($socket);
    }
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $address
 * @param int|null $error_code
 * @param null|string $error_message
 * @param float|null $timeout
 * @param int-mask $flags
 * @param null|resource $context
 * @return resource
 * @throws StreamException
 *
 */
function stream_socket_client(string $address, ?int &$error_code = null, ?string &$error_message = null, ?float $timeout = null, int $flags = STREAM_CLIENT_CONNECT, $context = null)
{
    error_clear_last();
    if ($context !== null) {
        $safeResult = \stream_socket_client($address, $error_code, $error_message, $timeout, $flags, $context);
    } elseif ($flags !== STREAM_CLIENT_CONNECT) {
        $safeResult = \stream_socket_client($address, $error_code, $error_message, $timeout, $flags);
    } elseif ($timeout !== null) {
        $safeResult = \stream_socket_client($address, $error_code, $error_message, $timeout);
    } else {
        $safeResult = \stream_socket_client($address, $error_code, $error_message);
    }
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $socket
 * @param bool $remote
 * @return string
 * @throws StreamException
 *
 */
function stream_socket_get_name($socket, bool $remote): string
{
    error_clear_last();
    $safeResult = \stream_socket_get_name($socket, $remote);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $domain
 * @param int $type
 * @param int $protocol
 * @return resource[]
 * @throws StreamException
 *
 */
function stream_socket_pair(int $domain, int $type, int $protocol): array
{
    error_clear_last();
    $safeResult = \stream_socket_pair($domain, $type, $protocol);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $socket
 * @param int $length
 * @param int $flags
 * @param null|string $address
 * @return string
 * @throws StreamException
 *
 */
function stream_socket_recvfrom($socket, int $length, int $flags = 0, ?string &$address = null): string
{
    error_clear_last();
    $safeResult = \stream_socket_recvfrom($socket, $length, $flags, $address);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $socket
 * @param string $data
 * @param int $flags
 * @param string $address
 * @return int
 * @throws StreamException
 *
 */
function stream_socket_sendto($socket, string $data, int $flags = 0, string $address = ""): int
{
    error_clear_last();
    $safeResult = \stream_socket_sendto($socket, $data, $flags, $address);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $address
 * @param int|null $error_code
 * @param null|string $error_message
 * @param int $flags
 * @param null|resource $context
 * @return resource
 * @throws StreamException
 *
 */
function stream_socket_server(string $address, ?int &$error_code = null, ?string &$error_message = null, int $flags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $context = null)
{
    error_clear_last();
    if ($context !== null) {
        $safeResult = \stream_socket_server($address, $error_code, $error_message, $flags, $context);
    } else {
        $safeResult = \stream_socket_server($address, $error_code, $error_message, $flags);
    }
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @param int $mode
 * @throws StreamException
 *
 */
function stream_socket_shutdown($stream, int $mode): void
{
    error_clear_last();
    $safeResult = \stream_socket_shutdown($stream, $mode);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param resource $stream
 * @throws StreamException
 *
 */
function stream_supports_lock($stream): void
{
    error_clear_last();
    $safeResult = \stream_supports_lock($stream);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param string $protocol
 * @param string $class
 * @param int $flags
 * @throws StreamException
 *
 */
function stream_wrapper_register(string $protocol, string $class, int $flags = 0): void
{
    error_clear_last();
    $safeResult = \stream_wrapper_register($protocol, $class, $flags);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param string $protocol
 * @throws StreamException
 *
 */
function stream_wrapper_restore(string $protocol): void
{
    error_clear_last();
    $safeResult = \stream_wrapper_restore($protocol);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param string $protocol
 * @throws StreamException
 *
 */
function stream_wrapper_unregister(string $protocol): void
{
    error_clear_last();
    $safeResult = \stream_wrapper_unregister($protocol);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\FpmException;

/**
 * @throws FpmException
 *
 */
function fastcgi_finish_request(): void
{
    error_clear_last();
    $safeResult = \fastcgi_finish_request();
    if ($safeResult === false) {
        throw FpmException::createFromPhpError();
    }
}


/**
 * @return array{pool: string, process-manager: 'dynamic'|'ondemand'|'static', start-time: int, start-since: int, accepted-conn: int, listen-queue: int, max-listen-queue: int, listen-queue-len: int, idle-processes: int, active-processes: int, total-processes: int, max-active-processes: int, max-children-reached: 0|1, slow-requests: int, procs: array}
 * @throws FpmException
 *
 */
function fpm_get_status(): array
{
    error_clear_last();
    $safeResult = \fpm_get_status();
    if ($safeResult === false) {
        throw FpmException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\Oci8Exception;

/**
 * @param resource $statement
 * @param string $param
 * @param array $var
 * @param int $max_array_length
 * @param int $max_item_length
 * @param int $type
 * @throws Oci8Exception
 *
 */
function oci_bind_array_by_name($statement, string $param, array &$var, int $max_array_length, int $max_item_length = -1, int $type = SQLT_AFC): void
{
    error_clear_last();
    $safeResult = \oci_bind_array_by_name($statement, $param, $var, $max_array_length, $max_item_length, $type);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $statement
 * @param string $param
 * @param mixed $var
 * @param int $max_length
 * @param int $type
 * @throws Oci8Exception
 *
 */
function oci_bind_by_name($statement, string $param, &$var, int $max_length = -1, int $type = 0): void
{
    error_clear_last();
    $safeResult = \oci_bind_by_name($statement, $param, $var, $max_length, $type);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $statement
 * @throws Oci8Exception
 *
 */
function oci_cancel($statement): void
{
    error_clear_last();
    $safeResult = \oci_cancel($statement);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $connection
 * @throws Oci8Exception
 *
 */
function oci_commit($connection): void
{
    error_clear_last();
    $safeResult = \oci_commit($connection);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param string $username
 * @param string $password
 * @param null|string $connection_string
 * @param string $encoding
 * @param int $session_mode
 * @return resource
 * @throws Oci8Exception
 *
 */
function oci_connect(string $username, string $password, ?string $connection_string = null, string $encoding = "", int $session_mode = OCI_DEFAULT)
{
    error_clear_last();
    if ($session_mode !== OCI_DEFAULT) {
        $safeResult = \oci_connect($username, $password, $connection_string, $encoding, $session_mode);
    } elseif ($encoding !== "") {
        $safeResult = \oci_connect($username, $password, $connection_string, $encoding);
    } elseif ($connection_string !== null) {
        $safeResult = \oci_connect($username, $password, $connection_string);
    } else {
        $safeResult = \oci_connect($username, $password);
    }
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $statement
 * @param string $column
 * @param mixed $var
 * @param int $type
 * @throws Oci8Exception
 *
 */
function oci_define_by_name($statement, string $column, &$var, int $type = 0): void
{
    error_clear_last();
    $safeResult = \oci_define_by_name($statement, $column, $var, $type);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $statement
 * @param int $mode
 * @throws Oci8Exception
 *
 */
function oci_execute($statement, int $mode = OCI_COMMIT_ON_SUCCESS): void
{
    error_clear_last();
    $safeResult = \oci_execute($statement, $mode);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $statement
 * @param mixed $column
 * @return string
 * @throws Oci8Exception
 *
 */
function oci_field_name($statement, $column): string
{
    error_clear_last();
    $safeResult = \oci_field_name($statement, $column);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $statement
 * @param mixed $column
 * @return int
 * @throws Oci8Exception
 *
 */
function oci_field_precision($statement, $column): int
{
    error_clear_last();
    $safeResult = \oci_field_precision($statement, $column);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $statement
 * @param mixed $column
 * @return int
 * @throws Oci8Exception
 *
 */
function oci_field_scale($statement, $column): int
{
    error_clear_last();
    $safeResult = \oci_field_scale($statement, $column);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $statement
 * @param mixed $column
 * @return int
 * @throws Oci8Exception
 *
 */
function oci_field_size($statement, $column): int
{
    error_clear_last();
    $safeResult = \oci_field_size($statement, $column);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $statement
 * @param mixed $column
 * @return int
 * @throws Oci8Exception
 *
 */
function oci_field_type_raw($statement, $column): int
{
    error_clear_last();
    $safeResult = \oci_field_type_raw($statement, $column);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $statement
 * @param mixed $column
 * @return mixed
 * @throws Oci8Exception
 *
 */
function oci_field_type($statement, $column)
{
    error_clear_last();
    $safeResult = \oci_field_type($statement, $column);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OCILob $lob
 * @throws Oci8Exception
 *
 */
function oci_free_descriptor(\OCILob $lob): void
{
    error_clear_last();
    $safeResult = \oci_free_descriptor($lob);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $statement
 * @throws Oci8Exception
 *
 */
function oci_free_statement($statement): void
{
    error_clear_last();
    $safeResult = \oci_free_statement($statement);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $connection
 * @param string $type_name
 * @param null|string $schema
 * @return \OCI-Collection
 * @throws Oci8Exception
 *
 */
function oci_new_collection($connection, string $type_name, ?string $schema = null)
{
    error_clear_last();
    if ($schema !== null) {
        $safeResult = \oci_new_collection($connection, $type_name, $schema);
    } else {
        $safeResult = \oci_new_collection($connection, $type_name);
    }
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $username
 * @param string $password
 * @param null|string $connection_string
 * @param string $encoding
 * @param int $session_mode
 * @return resource
 * @throws Oci8Exception
 *
 */
function oci_new_connect(string $username, string $password, ?string $connection_string = null, string $encoding = "", int $session_mode = OCI_DEFAULT)
{
    error_clear_last();
    if ($session_mode !== OCI_DEFAULT) {
        $safeResult = \oci_new_connect($username, $password, $connection_string, $encoding, $session_mode);
    } elseif ($encoding !== "") {
        $safeResult = \oci_new_connect($username, $password, $connection_string, $encoding);
    } elseif ($connection_string !== null) {
        $safeResult = \oci_new_connect($username, $password, $connection_string);
    } else {
        $safeResult = \oci_new_connect($username, $password);
    }
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $connection
 * @return resource
 * @throws Oci8Exception
 *
 */
function oci_new_cursor($connection)
{
    error_clear_last();
    $safeResult = \oci_new_cursor($connection);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $connection
 * @param int $type
 * @return \OCI-Lob|false
 * @throws Oci8Exception
 *
 */
function oci_new_descriptor($connection, int $type = OCI_DTYPE_LOB)
{
    error_clear_last();
    $safeResult = \oci_new_descriptor($connection, $type);
    if ($safeResult === null) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $statement
 * @return 0|positive-int
 * @throws Oci8Exception
 *
 */
function oci_num_rows($statement): int
{
    error_clear_last();
    $safeResult = \oci_num_rows($statement);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $connection
 * @param string $sql
 * @return resource
 * @throws Oci8Exception
 *
 */
function oci_parse($connection, string $sql)
{
    error_clear_last();
    $safeResult = \oci_parse($connection, $sql);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $username
 * @param string $password
 * @param null|string $connection_string
 * @param string $encoding
 * @param int $session_mode
 * @return resource
 * @throws Oci8Exception
 *
 */
function oci_pconnect(string $username, string $password, ?string $connection_string = null, string $encoding = "", int $session_mode = OCI_DEFAULT)
{
    error_clear_last();
    if ($session_mode !== OCI_DEFAULT) {
        $safeResult = \oci_pconnect($username, $password, $connection_string, $encoding, $session_mode);
    } elseif ($encoding !== "") {
        $safeResult = \oci_pconnect($username, $password, $connection_string, $encoding);
    } elseif ($connection_string !== null) {
        $safeResult = \oci_pconnect($username, $password, $connection_string);
    } else {
        $safeResult = \oci_pconnect($username, $password);
    }
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $connection
 * @param callable $callback
 * @throws Oci8Exception
 *
 */
function oci_register_taf_callback($connection, callable $callback): void
{
    error_clear_last();
    $safeResult = \oci_register_taf_callback($connection, $callback);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $statement
 * @param mixed $column
 * @return string
 * @throws Oci8Exception
 *
 */
function oci_result($statement, $column): string
{
    error_clear_last();
    $safeResult = \oci_result($statement, $column);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $connection
 * @throws Oci8Exception
 *
 */
function oci_rollback($connection): void
{
    error_clear_last();
    $safeResult = \oci_rollback($connection);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $connection
 * @return string
 * @throws Oci8Exception
 *
 */
function oci_server_version($connection): string
{
    error_clear_last();
    $safeResult = \oci_server_version($connection);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $connection
 * @param string $action
 * @throws Oci8Exception
 *
 */
function oci_set_action($connection, string $action): void
{
    error_clear_last();
    $safeResult = \oci_set_action($connection, $action);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $connection
 * @param int $timeout
 * @throws Oci8Exception
 *
 */
function oci_set_call_timeout($connection, int $timeout): void
{
    error_clear_last();
    $safeResult = \oci_set_call_timeout($connection, $timeout);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $connection
 * @param string $client_id
 * @throws Oci8Exception
 *
 */
function oci_set_client_identifier($connection, string $client_id): void
{
    error_clear_last();
    $safeResult = \oci_set_client_identifier($connection, $client_id);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $connection
 * @param string $client_info
 * @throws Oci8Exception
 *
 */
function oci_set_client_info($connection, string $client_info): void
{
    error_clear_last();
    $safeResult = \oci_set_client_info($connection, $client_info);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $connection
 * @param string $action
 * @throws Oci8Exception
 *
 */
function oci_set_db_operation($connection, string $action): void
{
    error_clear_last();
    $safeResult = \oci_set_db_operation($connection, $action);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param string $edition
 * @throws Oci8Exception
 *
 */
function oci_set_edition(string $edition): void
{
    error_clear_last();
    $safeResult = \oci_set_edition($edition);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $connection
 * @param string $name
 * @throws Oci8Exception
 *
 */
function oci_set_module_name($connection, string $name): void
{
    error_clear_last();
    $safeResult = \oci_set_module_name($connection, $name);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $statement
 * @param int $prefetch_lob_size
 * @throws Oci8Exception
 *
 */
function oci_set_prefetch_lob($statement, int $prefetch_lob_size): void
{
    error_clear_last();
    $safeResult = \oci_set_prefetch_lob($statement, $prefetch_lob_size);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $statement
 * @param int $rows
 * @throws Oci8Exception
 *
 */
function oci_set_prefetch($statement, int $rows): void
{
    error_clear_last();
    $safeResult = \oci_set_prefetch($statement, $rows);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $statement
 * @return string
 * @throws Oci8Exception
 *
 */
function oci_statement_type($statement): string
{
    error_clear_last();
    $safeResult = \oci_statement_type($statement);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $connection
 * @throws Oci8Exception
 *
 */
function oci_unregister_taf_callback($connection): void
{
    error_clear_last();
    $safeResult = \oci_unregister_taf_callback($connection);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}
<?php

return [
    'apache_getenv',
    'apache_get_version',
    'apache_lookup_uri',
    'apache_setenv',
    'apcu_cache_info',
    'apcu_cas',
    'apcu_dec',
    'apcu_fetch',
    'apcu_inc',
    'apcu_sma_info',
    'assert_options',
    'base64_decode',
    'bindtextdomain',
    'bzclose',
    'bzflush',
    'bzopen',
    'bzread',
    'bzwrite',
    'chdir',
    'chgrp',
    'chmod',
    'chown',
    'chroot',
    'class_alias',
    'class_implements',
    'class_parents',
    'class_uses',
    'cli_set_process_title',
    'closelog',
    'com_create_guid',
    'com_event_sink',
    'com_load_typelib',
    'com_print_typeinfo',
    'convert_uudecode',
    'copy',
    'cubrid_bind',
    'cubrid_column_names',
    'cubrid_column_types',
    'cubrid_col_size',
    'cubrid_commit',
    'cubrid_connect',
    'cubrid_connect_with_url',
    'cubrid_current_oid',
    'cubrid_disconnect',
    'cubrid_drop',
    'cubrid_free_result',
    'cubrid_get_charset',
    'cubrid_get_class_name',
    'cubrid_get_client_info',
    'cubrid_get_db_parameter',
    'cubrid_get_query_timeout',
    'cubrid_get_server_info',
    'cubrid_insert_id',
    'cubrid_lob2_bind',
    'cubrid_lob2_close',
    'cubrid_lob2_export',
    'cubrid_lob2_import',
    'cubrid_lob2_new',
    'cubrid_lob2_read',
    'cubrid_lob2_seek',
    'cubrid_lob2_seek64',
    'cubrid_lob2_size',
    'cubrid_lob2_size64',
    'cubrid_lob2_tell',
    'cubrid_lob2_tell64',
    'cubrid_lob2_write',
    'cubrid_lob_close',
    'cubrid_lob_export',
    'cubrid_lob_get',
    'cubrid_lob_send',
    'cubrid_lob_size',
    'cubrid_lock_read',
    'cubrid_lock_write',
    'cubrid_move_cursor',
    'cubrid_next_result',
    'cubrid_pconnect',
    'cubrid_pconnect_with_url',
    'cubrid_prepare',
    'cubrid_put',
    'cubrid_rollback',
    'cubrid_schema',
    'cubrid_seq_drop',
    'cubrid_seq_insert',
    'cubrid_seq_put',
    'cubrid_set_add',
    'cubrid_set_autocommit',
    'cubrid_set_db_parameter',
    'cubrid_set_drop',
    'cubrid_set_query_timeout',
    'curl_copy_handle',
    'curl_escape',
    'curl_exec',
    'curl_getinfo',
    'curl_init',
    'curl_multi_info_read',
    'curl_multi_init',
    'curl_multi_setopt',
    'curl_setopt',
    'curl_share_setopt',
    'curl_unescape',
    'curl_upkeep',
    'date_create',
    'date_parse_from_format',
    'date_sunrise',
    'date_sunset',
    'db2_autocommit',
    'db2_bind_param',
    'db2_client_info',
    'db2_close',
    'db2_commit',
    'db2_execute',
    'db2_free_result',
    'db2_free_stmt',
    'db2_get_option',
    'db2_pclose',
    'db2_rollback',
    'db2_server_info',
    'db2_set_option',
    'define',
    'deflate_add',
    'deflate_init',
    'dir',
    'disk_free_space',
    'disk_total_space',
    'dl',
    'dns_get_record',
    'eio_busy',
    'eio_chmod',
    'eio_chown',
    'eio_close',
    'eio_custom',
    'eio_dup2',
    'eio_event_loop',
    'eio_fallocate',
    'eio_fchmod',
    'eio_fchown',
    'eio_fdatasync',
    'eio_fstat',
    'eio_fstatvfs',
    'eio_fsync',
    'eio_ftruncate',
    'eio_futime',
    'eio_grp',
    'eio_lstat',
    'eio_mkdir',
    'eio_mknod',
    'eio_nop',
    'eio_readahead',
    'eio_readdir',
    'eio_readlink',
    'eio_rename',
    'eio_rmdir',
    'eio_seek',
    'eio_sendfile',
    'eio_stat',
    'eio_statvfs',
    'eio_symlink',
    'eio_sync',
    'eio_syncfs',
    'eio_sync_file_range',
    'eio_truncate',
    'eio_unlink',
    'eio_utime',
    'eio_write',
    'error_log',
    'exec',
    'fastcgi_finish_request',
    'fbird_blob_cancel',
    'fclose',
    'fdatasync',
    'fflush',
    'fgetcsv',
    'file',
    'fileatime',
    'filectime',
    'fileinode',
    'filemtime',
    'fileowner',
    'fileperms',
    'filesize',
    'filetype',
    'file_get_contents',
    'file_put_contents',
    'filter_input_array',
    'filter_var_array',
    'finfo_close',
    'finfo_open',
    'flock',
    'fopen',
    'fpm_get_status',
    'fputcsv',
    'fread',
    'fsockopen',
    'fstat',
    'fsync',
    'ftell',
    'ftp_alloc',
    'ftp_append',
    'ftp_cdup',
    'ftp_chdir',
    'ftp_chmod',
    'ftp_close',
    'ftp_connect',
    'ftp_delete',
    'ftp_fget',
    'ftp_fput',
    'ftp_get',
    'ftp_login',
    'ftp_mkdir',
    'ftp_mlsd',
    'ftp_nb_put',
    'ftp_nlist',
    'ftp_pasv',
    'ftp_put',
    'ftp_pwd',
    'ftp_raw',
    'ftp_rename',
    'ftp_rmdir',
    'ftp_site',
    'ftp_size',
    'ftp_ssl_connect',
    'ftp_systype',
    'ftruncate',
    'fwrite',
    'getcwd',
    'gethostname',
    'getimagesize',
    'getlastmod',
    'getmygid',
    'getmyinode',
    'getmypid',
    'getmyuid',
    'getopt',
    'getprotobyname',
    'getprotobynumber',
    'getrusage',
    'getservbyport',
    'get_cfg_var',
    'get_headers',
    'get_include_path',
    'get_meta_tags',
    'glob',
    'gmmktime',
    'gmstrftime',
    'gnupg_adddecryptkey',
    'gnupg_addencryptkey',
    'gnupg_addsignkey',
    'gnupg_cleardecryptkeys',
    'gnupg_clearencryptkeys',
    'gnupg_clearsignkeys',
    'gnupg_deletekey',
    'gnupg_setarmor',
    'gnupg_setsignmode',
    'gzclose',
    'gzcompress',
    'gzdecode',
    'gzdeflate',
    'gzencode',
    'gzfile',
    'gzgets',
    'gzinflate',
    'gzopen',
    'gzpassthru',
    'gzread',
    'gzrewind',
    'gztell',
    'gzuncompress',
    'gzwrite',
    'hash_file',
    'hash_hmac_file',
    'hash_update_file',
    'header_register_callback',
    'hex2bin',
    'highlight_file',
    'highlight_string',
    'hrtime',
    'ibase_add_user',
    'ibase_backup',
    'ibase_blob_cancel',
    'ibase_blob_create',
    'ibase_blob_get',
    'ibase_close',
    'ibase_commit',
    'ibase_commit_ret',
    'ibase_connect',
    'ibase_delete_user',
    'ibase_drop_db',
    'ibase_free_event_handler',
    'ibase_free_query',
    'ibase_free_result',
    'ibase_maintain_db',
    'ibase_modify_user',
    'ibase_name_result',
    'ibase_pconnect',
    'ibase_restore',
    'ibase_rollback',
    'ibase_rollback_ret',
    'ibase_service_attach',
    'ibase_service_detach',
    'iconv',
    'iconv_get_encoding',
    'iconv_mime_decode',
    'iconv_mime_encode',
    'iconv_set_encoding',
    'iconv_strlen',
    'idate',
    'imageaffine',
    'imageaffinematrixconcat',
    'imageaffinematrixget',
    'imagealphablending',
    'imageantialias',
    'imagearc',
    'imageavif',
    'imagebmp',
    'imagechar',
    'imagecharup',
    'imagecolorat',
    'imagecolordeallocate',
    'imagecolormatch',
    'imagecolorset',
    'imageconvolution',
    'imagecopy',
    'imagecopymerge',
    'imagecopymergegray',
    'imagecopyresampled',
    'imagecopyresized',
    'imagecreate',
    'imagecreatefromavif',
    'imagecreatefrombmp',
    'imagecreatefromgd',
    'imagecreatefromgd2',
    'imagecreatefromgd2part',
    'imagecreatefromgif',
    'imagecreatefromjpeg',
    'imagecreatefrompng',
    'imagecreatefromstring',
    'imagecreatefromtga',
    'imagecreatefromwbmp',
    'imagecreatefromwebp',
    'imagecreatefromxbm',
    'imagecreatefromxpm',
    'imagecreatetruecolor',
    'imagecrop',
    'imagecropauto',
    'imagedashedline',
    'imagedestroy',
    'imageellipse',
    'imagefill',
    'imagefilledarc',
    'imagefilledellipse',
    'imagefilledrectangle',
    'imagefilltoborder',
    'imagefilter',
    'imageflip',
    'imageftbbox',
    'imagefttext',
    'imagegammacorrect',
    'imagegd',
    'imagegd2',
    'imagegif',
    'imagegrabscreen',
    'imagegrabwindow',
    'imagejpeg',
    'imagelayereffect',
    'imageline',
    'imageloadfont',
    'imagepng',
    'imagerectangle',
    'imageresolution',
    'imagerotate',
    'imagesavealpha',
    'imagescale',
    'imagesetbrush',
    'imagesetclip',
    'imagesetinterpolation',
    'imagesetpixel',
    'imagesetstyle',
    'imagesetthickness',
    'imagesettile',
    'imagestring',
    'imagestringup',
    'imagetruecolortopalette',
    'imagettfbbox',
    'imagettftext',
    'imagewbmp',
    'imagewebp',
    'imagexbm',
    'image_type_to_extension',
    'imap_8bit',
    'imap_append',
    'imap_base64',
    'imap_binary',
    'imap_body',
    'imap_bodystruct',
    'imap_check',
    'imap_clearflag_full',
    'imap_close',
    'imap_createmailbox',
    'imap_deletemailbox',
    'imap_fetchbody',
    'imap_fetchheader',
    'imap_fetchmime',
    'imap_fetchstructure',
    'imap_fetch_overview',
    'imap_gc',
    'imap_getacl',
    'imap_getmailboxes',
    'imap_getsubscribed',
    'imap_headerinfo',
    'imap_headers',
    'imap_listscan',
    'imap_lsub',
    'imap_mail',
    'imap_mailboxmsginfo',
    'imap_mail_compose',
    'imap_mail_copy',
    'imap_mail_move',
    'imap_mime_header_decode',
    'imap_mutf7_to_utf8',
    'imap_num_msg',
    'imap_open',
    'imap_qprint',
    'imap_renamemailbox',
    'imap_rfc822_write_address',
    'imap_savebody',
    'imap_setacl',
    'imap_setflag_full',
    'imap_set_quota',
    'imap_sort',
    'imap_status',
    'imap_subscribe',
    'imap_thread',
    'imap_timeout',
    'imap_undelete',
    'imap_unsubscribe',
    'imap_utf8_to_mutf7',
    'inet_ntop',
    'inet_pton',
    'inflate_add',
    'inflate_get_read_len',
    'inflate_get_status',
    'inflate_init',
    'ini_get',
    'ini_set',
    'inotify_init',
    'inotify_rm_watch',
    'iptcembed',
    'iptcparse',
    'json_decode',
    'json_encode',
    'lchgrp',
    'lchown',
    'ldap_8859_to_t61',
    'ldap_add',
    'ldap_bind',
    'ldap_compare',
    'ldap_control_paged_result',
    'ldap_control_paged_result_response',
    'ldap_count_entries',
    'ldap_delete',
    'ldap_dn2ufn',
    'ldap_exop',
    'ldap_exop_passwd',
    'ldap_exop_whoami',
    'ldap_explode_dn',
    'ldap_first_attribute',
    'ldap_first_entry',
    'ldap_free_result',
    'ldap_get_attributes',
    'ldap_get_dn',
    'ldap_get_entries',
    'ldap_get_option',
    'ldap_get_values',
    'ldap_get_values_len',
    'ldap_modify_batch',
    'ldap_mod_add',
    'ldap_mod_del',
    'ldap_mod_replace',
    'ldap_next_attribute',
    'ldap_parse_exop',
    'ldap_parse_result',
    'ldap_rename',
    'ldap_sasl_bind',
    'ldap_set_option',
    'ldap_unbind',
    'libxml_set_external_entity_loader',
    'link',
    'lstat',
    'lzf_compress',
    'lzf_decompress',
    'mailparse_msg_extract_part_file',
    'mailparse_msg_free',
    'mailparse_msg_parse',
    'mailparse_msg_parse_file',
    'mailparse_stream_encode',
    'mb_chr',
    'mb_convert_encoding',
    'mb_convert_variables',
    'mb_detect_order',
    'mb_encoding_aliases',
    'mb_eregi_replace',
    'mb_ereg_replace',
    'mb_ereg_replace_callback',
    'mb_ereg_search_getregs',
    'mb_ereg_search_init',
    'mb_ereg_search_regs',
    'mb_ereg_search_setpos',
    'mb_get_info',
    'mb_http_output',
    'mb_internal_encoding',
    'mb_ord',
    'mb_parse_str',
    'mb_regex_encoding',
    'mb_send_mail',
    'mb_split',
    'md5_file',
    'mime_content_type',
    'mkdir',
    'mktime',
    'msg_get_queue',
    'msg_queue_exists',
    'msg_receive',
    'msg_remove_queue',
    'msg_send',
    'msg_set_queue',
    'msg_stat_queue',
    'mysql_close',
    'mysql_connect',
    'mysql_create_db',
    'mysql_data_seek',
    'mysql_db_name',
    'mysql_db_query',
    'mysql_drop_db',
    'mysql_fetch_lengths',
    'mysql_field_flags',
    'mysql_field_len',
    'mysql_field_name',
    'mysql_field_seek',
    'mysql_free_result',
    'mysql_get_host_info',
    'mysql_get_proto_info',
    'mysql_get_server_info',
    'mysql_info',
    'mysql_list_dbs',
    'mysql_list_fields',
    'mysql_list_processes',
    'mysql_list_tables',
    'mysql_num_fields',
    'mysql_num_rows',
    'mysql_query',
    'mysql_real_escape_string',
    'mysql_result',
    'mysql_select_db',
    'mysql_set_charset',
    'mysql_tablename',
    'mysql_thread_id',
    'mysql_unbuffered_query',
    'net_get_interfaces',
    'ob_clean',
    'ob_end_clean',
    'ob_end_flush',
    'ob_flush',
    'ob_get_clean',
    'ob_start',
    'oci_bind_array_by_name',
    'oci_bind_by_name',
    'oci_cancel',
    'oci_commit',
    'oci_connect',
    'oci_define_by_name',
    'oci_execute',
    'oci_field_name',
    'oci_field_precision',
    'oci_field_scale',
    'oci_field_size',
    'oci_field_type',
    'oci_field_type_raw',
    'oci_free_descriptor',
    'oci_free_statement',
    'oci_new_collection',
    'oci_new_connect',
    'oci_new_cursor',
    'oci_new_descriptor',
    'oci_num_rows',
    'oci_parse',
    'oci_pconnect',
    'oci_register_taf_callback',
    'oci_result',
    'oci_rollback',
    'oci_server_version',
    'oci_set_action',
    'oci_set_call_timeout',
    'oci_set_client_identifier',
    'oci_set_client_info',
    'oci_set_db_operation',
    'oci_set_edition',
    'oci_set_module_name',
    'oci_set_prefetch',
    'oci_set_prefetch_lob',
    'oci_statement_type',
    'oci_unregister_taf_callback',
    'odbc_autocommit',
    'odbc_binmode',
    'odbc_columnprivileges',
    'odbc_columns',
    'odbc_commit',
    'odbc_connect',
    'odbc_cursor',
    'odbc_data_source',
    'odbc_exec',
    'odbc_execute',
    'odbc_fetch_into',
    'odbc_field_len',
    'odbc_field_name',
    'odbc_field_num',
    'odbc_field_scale',
    'odbc_field_type',
    'odbc_foreignkeys',
    'odbc_gettypeinfo',
    'odbc_longreadlen',
    'odbc_num_fields',
    'odbc_pconnect',
    'odbc_prepare',
    'odbc_primarykeys',
    'odbc_procedurecolumns',
    'odbc_procedures',
    'odbc_result',
    'odbc_result_all',
    'odbc_rollback',
    'odbc_setoption',
    'odbc_specialcolumns',
    'odbc_statistics',
    'odbc_tableprivileges',
    'odbc_tables',
    'opcache_compile_file',
    'opcache_get_status',
    'opendir',
    'openlog',
    'openssl_cipher_iv_length',
    'openssl_cipher_key_length',
    'openssl_cms_decrypt',
    'openssl_cms_encrypt',
    'openssl_cms_read',
    'openssl_cms_sign',
    'openssl_cms_verify',
    'openssl_csr_export',
    'openssl_csr_export_to_file',
    'openssl_csr_get_public_key',
    'openssl_csr_get_subject',
    'openssl_csr_new',
    'openssl_csr_sign',
    'openssl_decrypt',
    'openssl_dh_compute_key',
    'openssl_digest',
    'openssl_encrypt',
    'openssl_get_curve_names',
    'openssl_open',
    'openssl_pbkdf2',
    'openssl_pkcs7_decrypt',
    'openssl_pkcs7_encrypt',
    'openssl_pkcs7_read',
    'openssl_pkcs7_sign',
    'openssl_pkcs12_export',
    'openssl_pkcs12_export_to_file',
    'openssl_pkcs12_read',
    'openssl_pkey_derive',
    'openssl_pkey_export',
    'openssl_pkey_export_to_file',
    'openssl_pkey_get_details',
    'openssl_pkey_get_private',
    'openssl_pkey_get_public',
    'openssl_pkey_new',
    'openssl_private_decrypt',
    'openssl_private_encrypt',
    'openssl_public_decrypt',
    'openssl_public_encrypt',
    'openssl_seal',
    'openssl_sign',
    'openssl_spki_export',
    'openssl_spki_export_challenge',
    'openssl_spki_new',
    'openssl_spki_verify',
    'openssl_verify',
    'openssl_x509_checkpurpose',
    'openssl_x509_export',
    'openssl_x509_export_to_file',
    'openssl_x509_fingerprint',
    'openssl_x509_read',
    'output_add_rewrite_var',
    'output_reset_rewrite_vars',
    'parse_ini_file',
    'parse_ini_string',
    'parse_url',
    'passthru',
    'pclose',
    'pcntl_getpriority',
    'pcntl_setpriority',
    'pcntl_signal',
    'pcntl_signal_dispatch',
    'pcntl_sigprocmask',
    'pcntl_sigtimedwait',
    'pcntl_sigwaitinfo',
    'pfsockopen',
    'pg_cancel_query',
    'pg_connect',
    'pg_connection_reset',
    'pg_convert',
    'pg_copy_from',
    'pg_copy_to',
    'pg_delete',
    'pg_end_copy',
    'pg_execute',
    'pg_field_num',
    'pg_field_table',
    'pg_flush',
    'pg_free_result',
    'pg_host',
    'pg_insert',
    'pg_last_oid',
    'pg_lo_close',
    'pg_lo_export',
    'pg_lo_import',
    'pg_lo_open',
    'pg_lo_read',
    'pg_lo_seek',
    'pg_lo_truncate',
    'pg_lo_unlink',
    'pg_lo_write',
    'pg_meta_data',
    'pg_parameter_status',
    'pg_pconnect',
    'pg_ping',
    'pg_prepare',
    'pg_put_line',
    'pg_query',
    'pg_query_params',
    'pg_result_error_field',
    'pg_result_seek',
    'pg_select',
    'pg_socket',
    'pg_trace',
    'pg_update',
    'phpcredits',
    'phpinfo',
    'php_sapi_name',
    'popen',
    'posix_access',
    'posix_getgrgid',
    'posix_getgrnam',
    'posix_getgroups',
    'posix_getlogin',
    'posix_getpgid',
    'posix_getpwuid',
    'posix_getrlimit',
    'posix_getsid',
    'posix_initgroups',
    'posix_kill',
    'posix_mkfifo',
    'posix_mknod',
    'posix_setegid',
    'posix_seteuid',
    'posix_setgid',
    'posix_setpgid',
    'posix_setrlimit',
    'posix_setsid',
    'posix_setuid',
    'posix_times',
    'posix_uname',
    'preg_grep',
    'preg_match',
    'preg_match_all',
    'preg_replace',
    'preg_replace_callback',
    'preg_replace_callback_array',
    'preg_split',
    'proc_close',
    'proc_nice',
    'proc_open',
    'pspell_add_to_personal',
    'pspell_add_to_session',
    'pspell_clear_session',
    'pspell_config_create',
    'pspell_config_data_dir',
    'pspell_config_dict_dir',
    'pspell_config_ignore',
    'pspell_config_mode',
    'pspell_config_personal',
    'pspell_config_repl',
    'pspell_config_runtogether',
    'pspell_config_save_repl',
    'pspell_new',
    'pspell_new_config',
    'pspell_new_personal',
    'pspell_save_wordlist',
    'pspell_store_replacement',
    'ps_add_launchlink',
    'ps_add_locallink',
    'ps_add_note',
    'ps_add_pdflink',
    'ps_add_weblink',
    'ps_arc',
    'ps_arcn',
    'ps_begin_page',
    'ps_begin_pattern',
    'ps_begin_template',
    'ps_circle',
    'ps_clip',
    'ps_close',
    'ps_closepath',
    'ps_closepath_stroke',
    'ps_close_image',
    'ps_continue_text',
    'ps_curveto',
    'ps_delete',
    'ps_end_page',
    'ps_end_pattern',
    'ps_end_template',
    'ps_fill',
    'ps_fill_stroke',
    'ps_get_parameter',
    'ps_hyphenate',
    'ps_include_file',
    'ps_lineto',
    'ps_moveto',
    'ps_new',
    'ps_open_file',
    'ps_place_image',
    'ps_rect',
    'ps_restore',
    'ps_rotate',
    'ps_save',
    'ps_scale',
    'ps_setcolor',
    'ps_setdash',
    'ps_setflat',
    'ps_setfont',
    'ps_setgray',
    'ps_setlinecap',
    'ps_setlinejoin',
    'ps_setlinewidth',
    'ps_setmiterlimit',
    'ps_setoverprintmode',
    'ps_setpolydash',
    'ps_set_border_color',
    'ps_set_border_dash',
    'ps_set_border_style',
    'ps_set_info',
    'ps_set_parameter',
    'ps_set_text_pos',
    'ps_set_value',
    'ps_shading',
    'ps_shading_pattern',
    'ps_shfill',
    'ps_show',
    'ps_show2',
    'ps_show_xy',
    'ps_show_xy2',
    'ps_stroke',
    'ps_symbol',
    'ps_translate',
    'putenv',
    'readfile',
    'readgzfile',
    'readline_add_history',
    'readline_callback_handler_install',
    'readline_clear_history',
    'readline_completion_function',
    'readline_read_history',
    'readline_write_history',
    'readlink',
    'realpath',
    'register_tick_function',
    'rename',
    'rewind',
    'rmdir',
    'rnp_decrypt',
    'rnp_dump_packets',
    'rnp_dump_packets_to_json',
    'rnp_ffi_create',
    'rnp_ffi_set_pass_provider',
    'rnp_import_keys',
    'rnp_import_signatures',
    'rnp_key_export',
    'rnp_key_export_autocrypt',
    'rnp_key_export_revocation',
    'rnp_key_get_info',
    'rnp_key_remove',
    'rnp_key_revoke',
    'rnp_list_keys',
    'rnp_load_keys',
    'rnp_load_keys_from_path',
    'rnp_locate_key',
    'rnp_op_encrypt',
    'rnp_op_generate_key',
    'rnp_op_sign',
    'rnp_op_sign_cleartext',
    'rnp_op_sign_detached',
    'rnp_op_verify',
    'rnp_op_verify_detached',
    'rnp_save_keys',
    'rnp_save_keys_to_path',
    'rnp_supported_features',
    'rpmaddtag',
    'rrd_create',
    'rrd_first',
    'rrd_graph',
    'rrd_info',
    'rrd_lastupdate',
    'rrd_restore',
    'rrd_tune',
    'rrd_update',
    'rrd_xport',
    'sapi_windows_cp_conv',
    'sapi_windows_cp_set',
    'sapi_windows_generate_ctrl_event',
    'sapi_windows_set_ctrl_handler',
    'sapi_windows_vt100_support',
    'scandir',
    'sem_acquire',
    'sem_get',
    'sem_release',
    'sem_remove',
    'session_abort',
    'session_cache_expire',
    'session_cache_limiter',
    'session_create_id',
    'session_decode',
    'session_destroy',
    'session_encode',
    'session_gc',
    'session_id',
    'session_module_name',
    'session_name',
    'session_regenerate_id',
    'session_reset',
    'session_save_path',
    'session_start',
    'session_unset',
    'session_write_close',
    'settype',
    'set_include_path',
    'set_time_limit',
    'sha1_file',
    'shell_exec',
    'shmop_delete',
    'shmop_read',
    'shm_attach',
    'shm_detach',
    'shm_put_var',
    'shm_remove',
    'shm_remove_var',
    'simplexml_import_dom',
    'simplexml_load_file',
    'simplexml_load_string',
    'socket_accept',
    'socket_addrinfo_bind',
    'socket_addrinfo_connect',
    'socket_addrinfo_lookup',
    'socket_bind',
    'socket_connect',
    'socket_create',
    'socket_create_listen',
    'socket_create_pair',
    'socket_export_stream',
    'socket_getpeername',
    'socket_getsockname',
    'socket_get_option',
    'socket_import_stream',
    'socket_listen',
    'socket_read',
    'socket_send',
    'socket_sendmsg',
    'socket_sendto',
    'socket_set_block',
    'socket_set_nonblock',
    'socket_set_option',
    'socket_shutdown',
    'socket_write',
    'socket_wsaprotocol_info_export',
    'socket_wsaprotocol_info_import',
    'socket_wsaprotocol_info_release',
    'sodium_crypto_aead_aes256gcm_decrypt',
    'sodium_crypto_aead_chacha20poly1305_decrypt',
    'sodium_crypto_aead_chacha20poly1305_encrypt',
    'sodium_crypto_aead_chacha20poly1305_ietf_decrypt',
    'sodium_crypto_aead_chacha20poly1305_ietf_encrypt',
    'sodium_crypto_aead_xchacha20poly1305_ietf_decrypt',
    'sodium_crypto_aead_xchacha20poly1305_ietf_encrypt',
    'sodium_crypto_box_open',
    'sodium_crypto_box_seal_open',
    'sodium_crypto_generichash_update',
    'sodium_crypto_secretbox_open',
    'sodium_crypto_sign_open',
    'sodium_crypto_stream_xchacha20_xor_ic',
    'solr_get_version',
    'spl_autoload_register',
    'spl_autoload_unregister',
    'sqlsrv_begin_transaction',
    'sqlsrv_cancel',
    'sqlsrv_client_info',
    'sqlsrv_close',
    'sqlsrv_commit',
    'sqlsrv_configure',
    'sqlsrv_execute',
    'sqlsrv_fetch',
    'sqlsrv_fetch_array',
    'sqlsrv_fetch_object',
    'sqlsrv_free_stmt',
    'sqlsrv_get_field',
    'sqlsrv_next_result',
    'sqlsrv_num_fields',
    'sqlsrv_num_rows',
    'sqlsrv_prepare',
    'sqlsrv_query',
    'sqlsrv_rollback',
    'ssdeep_fuzzy_compare',
    'ssdeep_fuzzy_hash',
    'ssdeep_fuzzy_hash_filename',
    'ssh2_auth_agent',
    'ssh2_auth_hostbased_file',
    'ssh2_auth_password',
    'ssh2_auth_pubkey_file',
    'ssh2_connect',
    'ssh2_disconnect',
    'ssh2_exec',
    'ssh2_forward_accept',
    'ssh2_forward_listen',
    'ssh2_publickey_add',
    'ssh2_publickey_init',
    'ssh2_publickey_remove',
    'ssh2_scp_recv',
    'ssh2_scp_send',
    'ssh2_send_eof',
    'ssh2_sftp',
    'ssh2_sftp_chmod',
    'ssh2_sftp_mkdir',
    'ssh2_sftp_rename',
    'ssh2_sftp_rmdir',
    'ssh2_sftp_symlink',
    'ssh2_sftp_unlink',
    'ssh2_shell',
    'stream_context_set_params',
    'stream_copy_to_stream',
    'stream_filter_append',
    'stream_filter_prepend',
    'stream_filter_register',
    'stream_filter_remove',
    'stream_get_contents',
    'stream_get_line',
    'stream_isatty',
    'stream_resolve_include_path',
    'stream_set_blocking',
    'stream_set_timeout',
    'stream_socket_accept',
    'stream_socket_client',
    'stream_socket_get_name',
    'stream_socket_pair',
    'stream_socket_recvfrom',
    'stream_socket_sendto',
    'stream_socket_server',
    'stream_socket_shutdown',
    'stream_supports_lock',
    'stream_wrapper_register',
    'stream_wrapper_restore',
    'stream_wrapper_unregister',
    'strftime',
    'strptime',
    'strtotime',
    'swoole_async_dns_lookup',
    'swoole_async_readfile',
    'swoole_async_write',
    'swoole_async_writefile',
    'swoole_event_defer',
    'swoole_event_del',
    'swoole_event_write',
    'symlink',
    'syslog',
    'system',
    'sys_getloadavg',
    'tempnam',
    'timezone_name_from_abbr',
    'time_nanosleep',
    'time_sleep_until',
    'tmpfile',
    'touch',
    'unixtojd',
    'unlink',
    'unpack',
    'unserialize',
    'uopz_extend',
    'uopz_implement',
    'variant_date_to_timestamp',
    'variant_round',
    'virtual',
    'xdiff_file_bdiff',
    'xdiff_file_bpatch',
    'xdiff_file_diff',
    'xdiff_file_diff_binary',
    'xdiff_file_patch_binary',
    'xdiff_file_rabdiff',
    'xdiff_string_bpatch',
    'xdiff_string_patch',
    'xdiff_string_patch_binary',
    'xmlrpc_set_type',
    'xml_parser_free',
    'xml_set_character_data_handler',
    'xml_set_default_handler',
    'xml_set_element_handler',
    'xml_set_end_namespace_decl_handler',
    'xml_set_external_entity_ref_handler',
    'xml_set_notation_decl_handler',
    'xml_set_object',
    'xml_set_processing_instruction_handler',
    'xml_set_start_namespace_decl_handler',
    'xml_set_unparsed_entity_decl_handler',
    'yaml_parse',
    'yaml_parse_file',
    'yaml_parse_url',
    'yaz_ccl_parse',
    'yaz_close',
    'yaz_connect',
    'yaz_database',
    'yaz_element',
    'yaz_present',
    'yaz_search',
    'yaz_wait',
    'zip_entry_close',
    'zip_entry_compressedsize',
    'zip_entry_compressionmethod',
    'zip_entry_filesize',
    'zip_entry_name',
    'zip_entry_open',
    'zip_entry_read',
    'zlib_decode',
];
<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\Renaming\Rector\FuncCall\RenameFunctionRector;

// This file configures rector/rector to replace all PHP functions with their equivalent "safe" functions.
return static function (RectorConfig $rectorConfig): void {
    $rectorConfig->ruleWithConfiguration(
        RenameFunctionRector::class,
        [            'apache_getenv' => 'Safe\apache_getenv',
            'apache_get_version' => 'Safe\apache_get_version',
            'apache_lookup_uri' => 'Safe\apache_lookup_uri',
            'apache_setenv' => 'Safe\apache_setenv',
            'apcu_cache_info' => 'Safe\apcu_cache_info',
            'apcu_cas' => 'Safe\apcu_cas',
            'apcu_dec' => 'Safe\apcu_dec',
            'apcu_fetch' => 'Safe\apcu_fetch',
            'apcu_inc' => 'Safe\apcu_inc',
            'apcu_sma_info' => 'Safe\apcu_sma_info',
            'assert_options' => 'Safe\assert_options',
            'base64_decode' => 'Safe\base64_decode',
            'bindtextdomain' => 'Safe\bindtextdomain',
            'bzclose' => 'Safe\bzclose',
            'bzflush' => 'Safe\bzflush',
            'bzopen' => 'Safe\bzopen',
            'bzread' => 'Safe\bzread',
            'bzwrite' => 'Safe\bzwrite',
            'chdir' => 'Safe\chdir',
            'chgrp' => 'Safe\chgrp',
            'chmod' => 'Safe\chmod',
            'chown' => 'Safe\chown',
            'chroot' => 'Safe\chroot',
            'class_alias' => 'Safe\class_alias',
            'class_implements' => 'Safe\class_implements',
            'class_parents' => 'Safe\class_parents',
            'class_uses' => 'Safe\class_uses',
            'cli_set_process_title' => 'Safe\cli_set_process_title',
            'closelog' => 'Safe\closelog',
            'com_create_guid' => 'Safe\com_create_guid',
            'com_event_sink' => 'Safe\com_event_sink',
            'com_load_typelib' => 'Safe\com_load_typelib',
            'com_print_typeinfo' => 'Safe\com_print_typeinfo',
            'convert_uudecode' => 'Safe\convert_uudecode',
            'copy' => 'Safe\copy',
            'cubrid_bind' => 'Safe\cubrid_bind',
            'cubrid_column_names' => 'Safe\cubrid_column_names',
            'cubrid_column_types' => 'Safe\cubrid_column_types',
            'cubrid_col_size' => 'Safe\cubrid_col_size',
            'cubrid_commit' => 'Safe\cubrid_commit',
            'cubrid_connect' => 'Safe\cubrid_connect',
            'cubrid_connect_with_url' => 'Safe\cubrid_connect_with_url',
            'cubrid_current_oid' => 'Safe\cubrid_current_oid',
            'cubrid_disconnect' => 'Safe\cubrid_disconnect',
            'cubrid_drop' => 'Safe\cubrid_drop',
            'cubrid_free_result' => 'Safe\cubrid_free_result',
            'cubrid_get_charset' => 'Safe\cubrid_get_charset',
            'cubrid_get_class_name' => 'Safe\cubrid_get_class_name',
            'cubrid_get_client_info' => 'Safe\cubrid_get_client_info',
            'cubrid_get_db_parameter' => 'Safe\cubrid_get_db_parameter',
            'cubrid_get_query_timeout' => 'Safe\cubrid_get_query_timeout',
            'cubrid_get_server_info' => 'Safe\cubrid_get_server_info',
            'cubrid_insert_id' => 'Safe\cubrid_insert_id',
            'cubrid_lob2_bind' => 'Safe\cubrid_lob2_bind',
            'cubrid_lob2_close' => 'Safe\cubrid_lob2_close',
            'cubrid_lob2_export' => 'Safe\cubrid_lob2_export',
            'cubrid_lob2_import' => 'Safe\cubrid_lob2_import',
            'cubrid_lob2_new' => 'Safe\cubrid_lob2_new',
            'cubrid_lob2_read' => 'Safe\cubrid_lob2_read',
            'cubrid_lob2_seek' => 'Safe\cubrid_lob2_seek',
            'cubrid_lob2_seek64' => 'Safe\cubrid_lob2_seek64',
            'cubrid_lob2_size' => 'Safe\cubrid_lob2_size',
            'cubrid_lob2_size64' => 'Safe\cubrid_lob2_size64',
            'cubrid_lob2_tell' => 'Safe\cubrid_lob2_tell',
            'cubrid_lob2_tell64' => 'Safe\cubrid_lob2_tell64',
            'cubrid_lob2_write' => 'Safe\cubrid_lob2_write',
            'cubrid_lob_close' => 'Safe\cubrid_lob_close',
            'cubrid_lob_export' => 'Safe\cubrid_lob_export',
            'cubrid_lob_get' => 'Safe\cubrid_lob_get',
            'cubrid_lob_send' => 'Safe\cubrid_lob_send',
            'cubrid_lob_size' => 'Safe\cubrid_lob_size',
            'cubrid_lock_read' => 'Safe\cubrid_lock_read',
            'cubrid_lock_write' => 'Safe\cubrid_lock_write',
            'cubrid_move_cursor' => 'Safe\cubrid_move_cursor',
            'cubrid_next_result' => 'Safe\cubrid_next_result',
            'cubrid_pconnect' => 'Safe\cubrid_pconnect',
            'cubrid_pconnect_with_url' => 'Safe\cubrid_pconnect_with_url',
            'cubrid_prepare' => 'Safe\cubrid_prepare',
            'cubrid_put' => 'Safe\cubrid_put',
            'cubrid_rollback' => 'Safe\cubrid_rollback',
            'cubrid_schema' => 'Safe\cubrid_schema',
            'cubrid_seq_drop' => 'Safe\cubrid_seq_drop',
            'cubrid_seq_insert' => 'Safe\cubrid_seq_insert',
            'cubrid_seq_put' => 'Safe\cubrid_seq_put',
            'cubrid_set_add' => 'Safe\cubrid_set_add',
            'cubrid_set_autocommit' => 'Safe\cubrid_set_autocommit',
            'cubrid_set_db_parameter' => 'Safe\cubrid_set_db_parameter',
            'cubrid_set_drop' => 'Safe\cubrid_set_drop',
            'cubrid_set_query_timeout' => 'Safe\cubrid_set_query_timeout',
            'curl_copy_handle' => 'Safe\curl_copy_handle',
            'curl_escape' => 'Safe\curl_escape',
            'curl_exec' => 'Safe\curl_exec',
            'curl_getinfo' => 'Safe\curl_getinfo',
            'curl_init' => 'Safe\curl_init',
            'curl_multi_info_read' => 'Safe\curl_multi_info_read',
            'curl_multi_init' => 'Safe\curl_multi_init',
            'curl_multi_setopt' => 'Safe\curl_multi_setopt',
            'curl_setopt' => 'Safe\curl_setopt',
            'curl_share_setopt' => 'Safe\curl_share_setopt',
            'curl_unescape' => 'Safe\curl_unescape',
            'curl_upkeep' => 'Safe\curl_upkeep',
            'date_create' => 'Safe\date_create',
            'date_parse_from_format' => 'Safe\date_parse_from_format',
            'date_sunrise' => 'Safe\date_sunrise',
            'date_sunset' => 'Safe\date_sunset',
            'db2_autocommit' => 'Safe\db2_autocommit',
            'db2_bind_param' => 'Safe\db2_bind_param',
            'db2_client_info' => 'Safe\db2_client_info',
            'db2_close' => 'Safe\db2_close',
            'db2_commit' => 'Safe\db2_commit',
            'db2_execute' => 'Safe\db2_execute',
            'db2_free_result' => 'Safe\db2_free_result',
            'db2_free_stmt' => 'Safe\db2_free_stmt',
            'db2_get_option' => 'Safe\db2_get_option',
            'db2_pclose' => 'Safe\db2_pclose',
            'db2_rollback' => 'Safe\db2_rollback',
            'db2_server_info' => 'Safe\db2_server_info',
            'db2_set_option' => 'Safe\db2_set_option',
            'define' => 'Safe\define',
            'deflate_add' => 'Safe\deflate_add',
            'deflate_init' => 'Safe\deflate_init',
            'dir' => 'Safe\dir',
            'disk_free_space' => 'Safe\disk_free_space',
            'disk_total_space' => 'Safe\disk_total_space',
            'dl' => 'Safe\dl',
            'dns_get_record' => 'Safe\dns_get_record',
            'eio_busy' => 'Safe\eio_busy',
            'eio_chmod' => 'Safe\eio_chmod',
            'eio_chown' => 'Safe\eio_chown',
            'eio_close' => 'Safe\eio_close',
            'eio_custom' => 'Safe\eio_custom',
            'eio_dup2' => 'Safe\eio_dup2',
            'eio_event_loop' => 'Safe\eio_event_loop',
            'eio_fallocate' => 'Safe\eio_fallocate',
            'eio_fchmod' => 'Safe\eio_fchmod',
            'eio_fchown' => 'Safe\eio_fchown',
            'eio_fdatasync' => 'Safe\eio_fdatasync',
            'eio_fstat' => 'Safe\eio_fstat',
            'eio_fstatvfs' => 'Safe\eio_fstatvfs',
            'eio_fsync' => 'Safe\eio_fsync',
            'eio_ftruncate' => 'Safe\eio_ftruncate',
            'eio_futime' => 'Safe\eio_futime',
            'eio_grp' => 'Safe\eio_grp',
            'eio_lstat' => 'Safe\eio_lstat',
            'eio_mkdir' => 'Safe\eio_mkdir',
            'eio_mknod' => 'Safe\eio_mknod',
            'eio_nop' => 'Safe\eio_nop',
            'eio_readahead' => 'Safe\eio_readahead',
            'eio_readdir' => 'Safe\eio_readdir',
            'eio_readlink' => 'Safe\eio_readlink',
            'eio_rename' => 'Safe\eio_rename',
            'eio_rmdir' => 'Safe\eio_rmdir',
            'eio_seek' => 'Safe\eio_seek',
            'eio_sendfile' => 'Safe\eio_sendfile',
            'eio_stat' => 'Safe\eio_stat',
            'eio_statvfs' => 'Safe\eio_statvfs',
            'eio_symlink' => 'Safe\eio_symlink',
            'eio_sync' => 'Safe\eio_sync',
            'eio_syncfs' => 'Safe\eio_syncfs',
            'eio_sync_file_range' => 'Safe\eio_sync_file_range',
            'eio_truncate' => 'Safe\eio_truncate',
            'eio_unlink' => 'Safe\eio_unlink',
            'eio_utime' => 'Safe\eio_utime',
            'eio_write' => 'Safe\eio_write',
            'error_log' => 'Safe\error_log',
            'exec' => 'Safe\exec',
            'fastcgi_finish_request' => 'Safe\fastcgi_finish_request',
            'fbird_blob_cancel' => 'Safe\fbird_blob_cancel',
            'fclose' => 'Safe\fclose',
            'fdatasync' => 'Safe\fdatasync',
            'fflush' => 'Safe\fflush',
            'fgetcsv' => 'Safe\fgetcsv',
            'file' => 'Safe\file',
            'fileatime' => 'Safe\fileatime',
            'filectime' => 'Safe\filectime',
            'fileinode' => 'Safe\fileinode',
            'filemtime' => 'Safe\filemtime',
            'fileowner' => 'Safe\fileowner',
            'fileperms' => 'Safe\fileperms',
            'filesize' => 'Safe\filesize',
            'filetype' => 'Safe\filetype',
            'file_get_contents' => 'Safe\file_get_contents',
            'file_put_contents' => 'Safe\file_put_contents',
            'filter_input_array' => 'Safe\filter_input_array',
            'filter_var_array' => 'Safe\filter_var_array',
            'finfo_close' => 'Safe\finfo_close',
            'finfo_open' => 'Safe\finfo_open',
            'flock' => 'Safe\flock',
            'fopen' => 'Safe\fopen',
            'fpm_get_status' => 'Safe\fpm_get_status',
            'fputcsv' => 'Safe\fputcsv',
            'fread' => 'Safe\fread',
            'fsockopen' => 'Safe\fsockopen',
            'fstat' => 'Safe\fstat',
            'fsync' => 'Safe\fsync',
            'ftell' => 'Safe\ftell',
            'ftp_alloc' => 'Safe\ftp_alloc',
            'ftp_append' => 'Safe\ftp_append',
            'ftp_cdup' => 'Safe\ftp_cdup',
            'ftp_chdir' => 'Safe\ftp_chdir',
            'ftp_chmod' => 'Safe\ftp_chmod',
            'ftp_close' => 'Safe\ftp_close',
            'ftp_connect' => 'Safe\ftp_connect',
            'ftp_delete' => 'Safe\ftp_delete',
            'ftp_fget' => 'Safe\ftp_fget',
            'ftp_fput' => 'Safe\ftp_fput',
            'ftp_get' => 'Safe\ftp_get',
            'ftp_login' => 'Safe\ftp_login',
            'ftp_mkdir' => 'Safe\ftp_mkdir',
            'ftp_mlsd' => 'Safe\ftp_mlsd',
            'ftp_nb_put' => 'Safe\ftp_nb_put',
            'ftp_nlist' => 'Safe\ftp_nlist',
            'ftp_pasv' => 'Safe\ftp_pasv',
            'ftp_put' => 'Safe\ftp_put',
            'ftp_pwd' => 'Safe\ftp_pwd',
            'ftp_raw' => 'Safe\ftp_raw',
            'ftp_rename' => 'Safe\ftp_rename',
            'ftp_rmdir' => 'Safe\ftp_rmdir',
            'ftp_site' => 'Safe\ftp_site',
            'ftp_size' => 'Safe\ftp_size',
            'ftp_ssl_connect' => 'Safe\ftp_ssl_connect',
            'ftp_systype' => 'Safe\ftp_systype',
            'ftruncate' => 'Safe\ftruncate',
            'fwrite' => 'Safe\fwrite',
            'getcwd' => 'Safe\getcwd',
            'gethostname' => 'Safe\gethostname',
            'getimagesize' => 'Safe\getimagesize',
            'getlastmod' => 'Safe\getlastmod',
            'getmygid' => 'Safe\getmygid',
            'getmyinode' => 'Safe\getmyinode',
            'getmypid' => 'Safe\getmypid',
            'getmyuid' => 'Safe\getmyuid',
            'getopt' => 'Safe\getopt',
            'getprotobyname' => 'Safe\getprotobyname',
            'getprotobynumber' => 'Safe\getprotobynumber',
            'getrusage' => 'Safe\getrusage',
            'getservbyport' => 'Safe\getservbyport',
            'get_cfg_var' => 'Safe\get_cfg_var',
            'get_headers' => 'Safe\get_headers',
            'get_include_path' => 'Safe\get_include_path',
            'get_meta_tags' => 'Safe\get_meta_tags',
            'glob' => 'Safe\glob',
            'gmmktime' => 'Safe\gmmktime',
            'gmstrftime' => 'Safe\gmstrftime',
            'gnupg_adddecryptkey' => 'Safe\gnupg_adddecryptkey',
            'gnupg_addencryptkey' => 'Safe\gnupg_addencryptkey',
            'gnupg_addsignkey' => 'Safe\gnupg_addsignkey',
            'gnupg_cleardecryptkeys' => 'Safe\gnupg_cleardecryptkeys',
            'gnupg_clearencryptkeys' => 'Safe\gnupg_clearencryptkeys',
            'gnupg_clearsignkeys' => 'Safe\gnupg_clearsignkeys',
            'gnupg_deletekey' => 'Safe\gnupg_deletekey',
            'gnupg_setarmor' => 'Safe\gnupg_setarmor',
            'gnupg_setsignmode' => 'Safe\gnupg_setsignmode',
            'gzclose' => 'Safe\gzclose',
            'gzcompress' => 'Safe\gzcompress',
            'gzdecode' => 'Safe\gzdecode',
            'gzdeflate' => 'Safe\gzdeflate',
            'gzencode' => 'Safe\gzencode',
            'gzfile' => 'Safe\gzfile',
            'gzgets' => 'Safe\gzgets',
            'gzinflate' => 'Safe\gzinflate',
            'gzopen' => 'Safe\gzopen',
            'gzpassthru' => 'Safe\gzpassthru',
            'gzread' => 'Safe\gzread',
            'gzrewind' => 'Safe\gzrewind',
            'gztell' => 'Safe\gztell',
            'gzuncompress' => 'Safe\gzuncompress',
            'gzwrite' => 'Safe\gzwrite',
            'hash_file' => 'Safe\hash_file',
            'hash_hmac_file' => 'Safe\hash_hmac_file',
            'hash_update_file' => 'Safe\hash_update_file',
            'header_register_callback' => 'Safe\header_register_callback',
            'hex2bin' => 'Safe\hex2bin',
            'highlight_file' => 'Safe\highlight_file',
            'highlight_string' => 'Safe\highlight_string',
            'hrtime' => 'Safe\hrtime',
            'ibase_add_user' => 'Safe\ibase_add_user',
            'ibase_backup' => 'Safe\ibase_backup',
            'ibase_blob_cancel' => 'Safe\ibase_blob_cancel',
            'ibase_blob_create' => 'Safe\ibase_blob_create',
            'ibase_blob_get' => 'Safe\ibase_blob_get',
            'ibase_close' => 'Safe\ibase_close',
            'ibase_commit' => 'Safe\ibase_commit',
            'ibase_commit_ret' => 'Safe\ibase_commit_ret',
            'ibase_connect' => 'Safe\ibase_connect',
            'ibase_delete_user' => 'Safe\ibase_delete_user',
            'ibase_drop_db' => 'Safe\ibase_drop_db',
            'ibase_free_event_handler' => 'Safe\ibase_free_event_handler',
            'ibase_free_query' => 'Safe\ibase_free_query',
            'ibase_free_result' => 'Safe\ibase_free_result',
            'ibase_maintain_db' => 'Safe\ibase_maintain_db',
            'ibase_modify_user' => 'Safe\ibase_modify_user',
            'ibase_name_result' => 'Safe\ibase_name_result',
            'ibase_pconnect' => 'Safe\ibase_pconnect',
            'ibase_restore' => 'Safe\ibase_restore',
            'ibase_rollback' => 'Safe\ibase_rollback',
            'ibase_rollback_ret' => 'Safe\ibase_rollback_ret',
            'ibase_service_attach' => 'Safe\ibase_service_attach',
            'ibase_service_detach' => 'Safe\ibase_service_detach',
            'iconv' => 'Safe\iconv',
            'iconv_get_encoding' => 'Safe\iconv_get_encoding',
            'iconv_mime_decode' => 'Safe\iconv_mime_decode',
            'iconv_mime_encode' => 'Safe\iconv_mime_encode',
            'iconv_set_encoding' => 'Safe\iconv_set_encoding',
            'iconv_strlen' => 'Safe\iconv_strlen',
            'idate' => 'Safe\idate',
            'imageaffine' => 'Safe\imageaffine',
            'imageaffinematrixconcat' => 'Safe\imageaffinematrixconcat',
            'imageaffinematrixget' => 'Safe\imageaffinematrixget',
            'imagealphablending' => 'Safe\imagealphablending',
            'imageantialias' => 'Safe\imageantialias',
            'imagearc' => 'Safe\imagearc',
            'imageavif' => 'Safe\imageavif',
            'imagebmp' => 'Safe\imagebmp',
            'imagechar' => 'Safe\imagechar',
            'imagecharup' => 'Safe\imagecharup',
            'imagecolorat' => 'Safe\imagecolorat',
            'imagecolordeallocate' => 'Safe\imagecolordeallocate',
            'imagecolormatch' => 'Safe\imagecolormatch',
            'imagecolorset' => 'Safe\imagecolorset',
            'imageconvolution' => 'Safe\imageconvolution',
            'imagecopy' => 'Safe\imagecopy',
            'imagecopymerge' => 'Safe\imagecopymerge',
            'imagecopymergegray' => 'Safe\imagecopymergegray',
            'imagecopyresampled' => 'Safe\imagecopyresampled',
            'imagecopyresized' => 'Safe\imagecopyresized',
            'imagecreate' => 'Safe\imagecreate',
            'imagecreatefromavif' => 'Safe\imagecreatefromavif',
            'imagecreatefrombmp' => 'Safe\imagecreatefrombmp',
            'imagecreatefromgd' => 'Safe\imagecreatefromgd',
            'imagecreatefromgd2' => 'Safe\imagecreatefromgd2',
            'imagecreatefromgd2part' => 'Safe\imagecreatefromgd2part',
            'imagecreatefromgif' => 'Safe\imagecreatefromgif',
            'imagecreatefromjpeg' => 'Safe\imagecreatefromjpeg',
            'imagecreatefrompng' => 'Safe\imagecreatefrompng',
            'imagecreatefromstring' => 'Safe\imagecreatefromstring',
            'imagecreatefromtga' => 'Safe\imagecreatefromtga',
            'imagecreatefromwbmp' => 'Safe\imagecreatefromwbmp',
            'imagecreatefromwebp' => 'Safe\imagecreatefromwebp',
            'imagecreatefromxbm' => 'Safe\imagecreatefromxbm',
            'imagecreatefromxpm' => 'Safe\imagecreatefromxpm',
            'imagecreatetruecolor' => 'Safe\imagecreatetruecolor',
            'imagecrop' => 'Safe\imagecrop',
            'imagecropauto' => 'Safe\imagecropauto',
            'imagedashedline' => 'Safe\imagedashedline',
            'imagedestroy' => 'Safe\imagedestroy',
            'imageellipse' => 'Safe\imageellipse',
            'imagefill' => 'Safe\imagefill',
            'imagefilledarc' => 'Safe\imagefilledarc',
            'imagefilledellipse' => 'Safe\imagefilledellipse',
            'imagefilledrectangle' => 'Safe\imagefilledrectangle',
            'imagefilltoborder' => 'Safe\imagefilltoborder',
            'imagefilter' => 'Safe\imagefilter',
            'imageflip' => 'Safe\imageflip',
            'imageftbbox' => 'Safe\imageftbbox',
            'imagefttext' => 'Safe\imagefttext',
            'imagegammacorrect' => 'Safe\imagegammacorrect',
            'imagegd' => 'Safe\imagegd',
            'imagegd2' => 'Safe\imagegd2',
            'imagegif' => 'Safe\imagegif',
            'imagegrabscreen' => 'Safe\imagegrabscreen',
            'imagegrabwindow' => 'Safe\imagegrabwindow',
            'imagejpeg' => 'Safe\imagejpeg',
            'imagelayereffect' => 'Safe\imagelayereffect',
            'imageline' => 'Safe\imageline',
            'imageloadfont' => 'Safe\imageloadfont',
            'imagepng' => 'Safe\imagepng',
            'imagerectangle' => 'Safe\imagerectangle',
            'imageresolution' => 'Safe\imageresolution',
            'imagerotate' => 'Safe\imagerotate',
            'imagesavealpha' => 'Safe\imagesavealpha',
            'imagescale' => 'Safe\imagescale',
            'imagesetbrush' => 'Safe\imagesetbrush',
            'imagesetclip' => 'Safe\imagesetclip',
            'imagesetinterpolation' => 'Safe\imagesetinterpolation',
            'imagesetpixel' => 'Safe\imagesetpixel',
            'imagesetstyle' => 'Safe\imagesetstyle',
            'imagesetthickness' => 'Safe\imagesetthickness',
            'imagesettile' => 'Safe\imagesettile',
            'imagestring' => 'Safe\imagestring',
            'imagestringup' => 'Safe\imagestringup',
            'imagetruecolortopalette' => 'Safe\imagetruecolortopalette',
            'imagettfbbox' => 'Safe\imagettfbbox',
            'imagettftext' => 'Safe\imagettftext',
            'imagewbmp' => 'Safe\imagewbmp',
            'imagewebp' => 'Safe\imagewebp',
            'imagexbm' => 'Safe\imagexbm',
            'image_type_to_extension' => 'Safe\image_type_to_extension',
            'imap_8bit' => 'Safe\imap_8bit',
            'imap_append' => 'Safe\imap_append',
            'imap_base64' => 'Safe\imap_base64',
            'imap_binary' => 'Safe\imap_binary',
            'imap_body' => 'Safe\imap_body',
            'imap_bodystruct' => 'Safe\imap_bodystruct',
            'imap_check' => 'Safe\imap_check',
            'imap_clearflag_full' => 'Safe\imap_clearflag_full',
            'imap_close' => 'Safe\imap_close',
            'imap_createmailbox' => 'Safe\imap_createmailbox',
            'imap_deletemailbox' => 'Safe\imap_deletemailbox',
            'imap_fetchbody' => 'Safe\imap_fetchbody',
            'imap_fetchheader' => 'Safe\imap_fetchheader',
            'imap_fetchmime' => 'Safe\imap_fetchmime',
            'imap_fetchstructure' => 'Safe\imap_fetchstructure',
            'imap_fetch_overview' => 'Safe\imap_fetch_overview',
            'imap_gc' => 'Safe\imap_gc',
            'imap_getacl' => 'Safe\imap_getacl',
            'imap_getmailboxes' => 'Safe\imap_getmailboxes',
            'imap_getsubscribed' => 'Safe\imap_getsubscribed',
            'imap_headerinfo' => 'Safe\imap_headerinfo',
            'imap_headers' => 'Safe\imap_headers',
            'imap_listscan' => 'Safe\imap_listscan',
            'imap_lsub' => 'Safe\imap_lsub',
            'imap_mail' => 'Safe\imap_mail',
            'imap_mailboxmsginfo' => 'Safe\imap_mailboxmsginfo',
            'imap_mail_compose' => 'Safe\imap_mail_compose',
            'imap_mail_copy' => 'Safe\imap_mail_copy',
            'imap_mail_move' => 'Safe\imap_mail_move',
            'imap_mime_header_decode' => 'Safe\imap_mime_header_decode',
            'imap_mutf7_to_utf8' => 'Safe\imap_mutf7_to_utf8',
            'imap_num_msg' => 'Safe\imap_num_msg',
            'imap_open' => 'Safe\imap_open',
            'imap_qprint' => 'Safe\imap_qprint',
            'imap_renamemailbox' => 'Safe\imap_renamemailbox',
            'imap_rfc822_write_address' => 'Safe\imap_rfc822_write_address',
            'imap_savebody' => 'Safe\imap_savebody',
            'imap_setacl' => 'Safe\imap_setacl',
            'imap_setflag_full' => 'Safe\imap_setflag_full',
            'imap_set_quota' => 'Safe\imap_set_quota',
            'imap_sort' => 'Safe\imap_sort',
            'imap_status' => 'Safe\imap_status',
            'imap_subscribe' => 'Safe\imap_subscribe',
            'imap_thread' => 'Safe\imap_thread',
            'imap_timeout' => 'Safe\imap_timeout',
            'imap_undelete' => 'Safe\imap_undelete',
            'imap_unsubscribe' => 'Safe\imap_unsubscribe',
            'imap_utf8_to_mutf7' => 'Safe\imap_utf8_to_mutf7',
            'inet_ntop' => 'Safe\inet_ntop',
            'inet_pton' => 'Safe\inet_pton',
            'inflate_add' => 'Safe\inflate_add',
            'inflate_get_read_len' => 'Safe\inflate_get_read_len',
            'inflate_get_status' => 'Safe\inflate_get_status',
            'inflate_init' => 'Safe\inflate_init',
            'ini_get' => 'Safe\ini_get',
            'ini_set' => 'Safe\ini_set',
            'inotify_init' => 'Safe\inotify_init',
            'inotify_rm_watch' => 'Safe\inotify_rm_watch',
            'iptcembed' => 'Safe\iptcembed',
            'iptcparse' => 'Safe\iptcparse',
            'json_decode' => 'Safe\json_decode',
            'json_encode' => 'Safe\json_encode',
            'lchgrp' => 'Safe\lchgrp',
            'lchown' => 'Safe\lchown',
            'ldap_8859_to_t61' => 'Safe\ldap_8859_to_t61',
            'ldap_add' => 'Safe\ldap_add',
            'ldap_bind' => 'Safe\ldap_bind',
            'ldap_compare' => 'Safe\ldap_compare',
            'ldap_control_paged_result' => 'Safe\ldap_control_paged_result',
            'ldap_control_paged_result_response' => 'Safe\ldap_control_paged_result_response',
            'ldap_count_entries' => 'Safe\ldap_count_entries',
            'ldap_delete' => 'Safe\ldap_delete',
            'ldap_dn2ufn' => 'Safe\ldap_dn2ufn',
            'ldap_exop' => 'Safe\ldap_exop',
            'ldap_exop_passwd' => 'Safe\ldap_exop_passwd',
            'ldap_exop_whoami' => 'Safe\ldap_exop_whoami',
            'ldap_explode_dn' => 'Safe\ldap_explode_dn',
            'ldap_first_attribute' => 'Safe\ldap_first_attribute',
            'ldap_first_entry' => 'Safe\ldap_first_entry',
            'ldap_free_result' => 'Safe\ldap_free_result',
            'ldap_get_attributes' => 'Safe\ldap_get_attributes',
            'ldap_get_dn' => 'Safe\ldap_get_dn',
            'ldap_get_entries' => 'Safe\ldap_get_entries',
            'ldap_get_option' => 'Safe\ldap_get_option',
            'ldap_get_values' => 'Safe\ldap_get_values',
            'ldap_get_values_len' => 'Safe\ldap_get_values_len',
            'ldap_modify_batch' => 'Safe\ldap_modify_batch',
            'ldap_mod_add' => 'Safe\ldap_mod_add',
            'ldap_mod_del' => 'Safe\ldap_mod_del',
            'ldap_mod_replace' => 'Safe\ldap_mod_replace',
            'ldap_next_attribute' => 'Safe\ldap_next_attribute',
            'ldap_parse_exop' => 'Safe\ldap_parse_exop',
            'ldap_parse_result' => 'Safe\ldap_parse_result',
            'ldap_rename' => 'Safe\ldap_rename',
            'ldap_sasl_bind' => 'Safe\ldap_sasl_bind',
            'ldap_set_option' => 'Safe\ldap_set_option',
            'ldap_unbind' => 'Safe\ldap_unbind',
            'libxml_set_external_entity_loader' => 'Safe\libxml_set_external_entity_loader',
            'link' => 'Safe\link',
            'lstat' => 'Safe\lstat',
            'lzf_compress' => 'Safe\lzf_compress',
            'lzf_decompress' => 'Safe\lzf_decompress',
            'mailparse_msg_extract_part_file' => 'Safe\mailparse_msg_extract_part_file',
            'mailparse_msg_free' => 'Safe\mailparse_msg_free',
            'mailparse_msg_parse' => 'Safe\mailparse_msg_parse',
            'mailparse_msg_parse_file' => 'Safe\mailparse_msg_parse_file',
            'mailparse_stream_encode' => 'Safe\mailparse_stream_encode',
            'mb_chr' => 'Safe\mb_chr',
            'mb_convert_encoding' => 'Safe\mb_convert_encoding',
            'mb_convert_variables' => 'Safe\mb_convert_variables',
            'mb_detect_order' => 'Safe\mb_detect_order',
            'mb_encoding_aliases' => 'Safe\mb_encoding_aliases',
            'mb_eregi_replace' => 'Safe\mb_eregi_replace',
            'mb_ereg_replace' => 'Safe\mb_ereg_replace',
            'mb_ereg_replace_callback' => 'Safe\mb_ereg_replace_callback',
            'mb_ereg_search_getregs' => 'Safe\mb_ereg_search_getregs',
            'mb_ereg_search_init' => 'Safe\mb_ereg_search_init',
            'mb_ereg_search_regs' => 'Safe\mb_ereg_search_regs',
            'mb_ereg_search_setpos' => 'Safe\mb_ereg_search_setpos',
            'mb_get_info' => 'Safe\mb_get_info',
            'mb_http_output' => 'Safe\mb_http_output',
            'mb_internal_encoding' => 'Safe\mb_internal_encoding',
            'mb_ord' => 'Safe\mb_ord',
            'mb_parse_str' => 'Safe\mb_parse_str',
            'mb_regex_encoding' => 'Safe\mb_regex_encoding',
            'mb_send_mail' => 'Safe\mb_send_mail',
            'mb_split' => 'Safe\mb_split',
            'md5_file' => 'Safe\md5_file',
            'mime_content_type' => 'Safe\mime_content_type',
            'mkdir' => 'Safe\mkdir',
            'mktime' => 'Safe\mktime',
            'msg_get_queue' => 'Safe\msg_get_queue',
            'msg_queue_exists' => 'Safe\msg_queue_exists',
            'msg_receive' => 'Safe\msg_receive',
            'msg_remove_queue' => 'Safe\msg_remove_queue',
            'msg_send' => 'Safe\msg_send',
            'msg_set_queue' => 'Safe\msg_set_queue',
            'msg_stat_queue' => 'Safe\msg_stat_queue',
            'mysql_close' => 'Safe\mysql_close',
            'mysql_connect' => 'Safe\mysql_connect',
            'mysql_create_db' => 'Safe\mysql_create_db',
            'mysql_data_seek' => 'Safe\mysql_data_seek',
            'mysql_db_name' => 'Safe\mysql_db_name',
            'mysql_db_query' => 'Safe\mysql_db_query',
            'mysql_drop_db' => 'Safe\mysql_drop_db',
            'mysql_fetch_lengths' => 'Safe\mysql_fetch_lengths',
            'mysql_field_flags' => 'Safe\mysql_field_flags',
            'mysql_field_len' => 'Safe\mysql_field_len',
            'mysql_field_name' => 'Safe\mysql_field_name',
            'mysql_field_seek' => 'Safe\mysql_field_seek',
            'mysql_free_result' => 'Safe\mysql_free_result',
            'mysql_get_host_info' => 'Safe\mysql_get_host_info',
            'mysql_get_proto_info' => 'Safe\mysql_get_proto_info',
            'mysql_get_server_info' => 'Safe\mysql_get_server_info',
            'mysql_info' => 'Safe\mysql_info',
            'mysql_list_dbs' => 'Safe\mysql_list_dbs',
            'mysql_list_fields' => 'Safe\mysql_list_fields',
            'mysql_list_processes' => 'Safe\mysql_list_processes',
            'mysql_list_tables' => 'Safe\mysql_list_tables',
            'mysql_num_fields' => 'Safe\mysql_num_fields',
            'mysql_num_rows' => 'Safe\mysql_num_rows',
            'mysql_query' => 'Safe\mysql_query',
            'mysql_real_escape_string' => 'Safe\mysql_real_escape_string',
            'mysql_result' => 'Safe\mysql_result',
            'mysql_select_db' => 'Safe\mysql_select_db',
            'mysql_set_charset' => 'Safe\mysql_set_charset',
            'mysql_tablename' => 'Safe\mysql_tablename',
            'mysql_thread_id' => 'Safe\mysql_thread_id',
            'mysql_unbuffered_query' => 'Safe\mysql_unbuffered_query',
            'net_get_interfaces' => 'Safe\net_get_interfaces',
            'ob_clean' => 'Safe\ob_clean',
            'ob_end_clean' => 'Safe\ob_end_clean',
            'ob_end_flush' => 'Safe\ob_end_flush',
            'ob_flush' => 'Safe\ob_flush',
            'ob_get_clean' => 'Safe\ob_get_clean',
            'ob_start' => 'Safe\ob_start',
            'oci_bind_array_by_name' => 'Safe\oci_bind_array_by_name',
            'oci_bind_by_name' => 'Safe\oci_bind_by_name',
            'oci_cancel' => 'Safe\oci_cancel',
            'oci_commit' => 'Safe\oci_commit',
            'oci_connect' => 'Safe\oci_connect',
            'oci_define_by_name' => 'Safe\oci_define_by_name',
            'oci_execute' => 'Safe\oci_execute',
            'oci_field_name' => 'Safe\oci_field_name',
            'oci_field_precision' => 'Safe\oci_field_precision',
            'oci_field_scale' => 'Safe\oci_field_scale',
            'oci_field_size' => 'Safe\oci_field_size',
            'oci_field_type' => 'Safe\oci_field_type',
            'oci_field_type_raw' => 'Safe\oci_field_type_raw',
            'oci_free_descriptor' => 'Safe\oci_free_descriptor',
            'oci_free_statement' => 'Safe\oci_free_statement',
            'oci_new_collection' => 'Safe\oci_new_collection',
            'oci_new_connect' => 'Safe\oci_new_connect',
            'oci_new_cursor' => 'Safe\oci_new_cursor',
            'oci_new_descriptor' => 'Safe\oci_new_descriptor',
            'oci_num_rows' => 'Safe\oci_num_rows',
            'oci_parse' => 'Safe\oci_parse',
            'oci_pconnect' => 'Safe\oci_pconnect',
            'oci_register_taf_callback' => 'Safe\oci_register_taf_callback',
            'oci_result' => 'Safe\oci_result',
            'oci_rollback' => 'Safe\oci_rollback',
            'oci_server_version' => 'Safe\oci_server_version',
            'oci_set_action' => 'Safe\oci_set_action',
            'oci_set_call_timeout' => 'Safe\oci_set_call_timeout',
            'oci_set_client_identifier' => 'Safe\oci_set_client_identifier',
            'oci_set_client_info' => 'Safe\oci_set_client_info',
            'oci_set_db_operation' => 'Safe\oci_set_db_operation',
            'oci_set_edition' => 'Safe\oci_set_edition',
            'oci_set_module_name' => 'Safe\oci_set_module_name',
            'oci_set_prefetch' => 'Safe\oci_set_prefetch',
            'oci_set_prefetch_lob' => 'Safe\oci_set_prefetch_lob',
            'oci_statement_type' => 'Safe\oci_statement_type',
            'oci_unregister_taf_callback' => 'Safe\oci_unregister_taf_callback',
            'odbc_autocommit' => 'Safe\odbc_autocommit',
            'odbc_binmode' => 'Safe\odbc_binmode',
            'odbc_columnprivileges' => 'Safe\odbc_columnprivileges',
            'odbc_columns' => 'Safe\odbc_columns',
            'odbc_commit' => 'Safe\odbc_commit',
            'odbc_connect' => 'Safe\odbc_connect',
            'odbc_cursor' => 'Safe\odbc_cursor',
            'odbc_data_source' => 'Safe\odbc_data_source',
            'odbc_exec' => 'Safe\odbc_exec',
            'odbc_execute' => 'Safe\odbc_execute',
            'odbc_fetch_into' => 'Safe\odbc_fetch_into',
            'odbc_field_len' => 'Safe\odbc_field_len',
            'odbc_field_name' => 'Safe\odbc_field_name',
            'odbc_field_num' => 'Safe\odbc_field_num',
            'odbc_field_scale' => 'Safe\odbc_field_scale',
            'odbc_field_type' => 'Safe\odbc_field_type',
            'odbc_foreignkeys' => 'Safe\odbc_foreignkeys',
            'odbc_gettypeinfo' => 'Safe\odbc_gettypeinfo',
            'odbc_longreadlen' => 'Safe\odbc_longreadlen',
            'odbc_num_fields' => 'Safe\odbc_num_fields',
            'odbc_pconnect' => 'Safe\odbc_pconnect',
            'odbc_prepare' => 'Safe\odbc_prepare',
            'odbc_primarykeys' => 'Safe\odbc_primarykeys',
            'odbc_procedurecolumns' => 'Safe\odbc_procedurecolumns',
            'odbc_procedures' => 'Safe\odbc_procedures',
            'odbc_result' => 'Safe\odbc_result',
            'odbc_result_all' => 'Safe\odbc_result_all',
            'odbc_rollback' => 'Safe\odbc_rollback',
            'odbc_setoption' => 'Safe\odbc_setoption',
            'odbc_specialcolumns' => 'Safe\odbc_specialcolumns',
            'odbc_statistics' => 'Safe\odbc_statistics',
            'odbc_tableprivileges' => 'Safe\odbc_tableprivileges',
            'odbc_tables' => 'Safe\odbc_tables',
            'opcache_compile_file' => 'Safe\opcache_compile_file',
            'opcache_get_status' => 'Safe\opcache_get_status',
            'opendir' => 'Safe\opendir',
            'openlog' => 'Safe\openlog',
            'openssl_cipher_iv_length' => 'Safe\openssl_cipher_iv_length',
            'openssl_cipher_key_length' => 'Safe\openssl_cipher_key_length',
            'openssl_cms_decrypt' => 'Safe\openssl_cms_decrypt',
            'openssl_cms_encrypt' => 'Safe\openssl_cms_encrypt',
            'openssl_cms_read' => 'Safe\openssl_cms_read',
            'openssl_cms_sign' => 'Safe\openssl_cms_sign',
            'openssl_cms_verify' => 'Safe\openssl_cms_verify',
            'openssl_csr_export' => 'Safe\openssl_csr_export',
            'openssl_csr_export_to_file' => 'Safe\openssl_csr_export_to_file',
            'openssl_csr_get_public_key' => 'Safe\openssl_csr_get_public_key',
            'openssl_csr_get_subject' => 'Safe\openssl_csr_get_subject',
            'openssl_csr_new' => 'Safe\openssl_csr_new',
            'openssl_csr_sign' => 'Safe\openssl_csr_sign',
            'openssl_decrypt' => 'Safe\openssl_decrypt',
            'openssl_dh_compute_key' => 'Safe\openssl_dh_compute_key',
            'openssl_digest' => 'Safe\openssl_digest',
            'openssl_encrypt' => 'Safe\openssl_encrypt',
            'openssl_get_curve_names' => 'Safe\openssl_get_curve_names',
            'openssl_open' => 'Safe\openssl_open',
            'openssl_pbkdf2' => 'Safe\openssl_pbkdf2',
            'openssl_pkcs7_decrypt' => 'Safe\openssl_pkcs7_decrypt',
            'openssl_pkcs7_encrypt' => 'Safe\openssl_pkcs7_encrypt',
            'openssl_pkcs7_read' => 'Safe\openssl_pkcs7_read',
            'openssl_pkcs7_sign' => 'Safe\openssl_pkcs7_sign',
            'openssl_pkcs12_export' => 'Safe\openssl_pkcs12_export',
            'openssl_pkcs12_export_to_file' => 'Safe\openssl_pkcs12_export_to_file',
            'openssl_pkcs12_read' => 'Safe\openssl_pkcs12_read',
            'openssl_pkey_derive' => 'Safe\openssl_pkey_derive',
            'openssl_pkey_export' => 'Safe\openssl_pkey_export',
            'openssl_pkey_export_to_file' => 'Safe\openssl_pkey_export_to_file',
            'openssl_pkey_get_details' => 'Safe\openssl_pkey_get_details',
            'openssl_pkey_get_private' => 'Safe\openssl_pkey_get_private',
            'openssl_pkey_get_public' => 'Safe\openssl_pkey_get_public',
            'openssl_pkey_new' => 'Safe\openssl_pkey_new',
            'openssl_private_decrypt' => 'Safe\openssl_private_decrypt',
            'openssl_private_encrypt' => 'Safe\openssl_private_encrypt',
            'openssl_public_decrypt' => 'Safe\openssl_public_decrypt',
            'openssl_public_encrypt' => 'Safe\openssl_public_encrypt',
            'openssl_seal' => 'Safe\openssl_seal',
            'openssl_sign' => 'Safe\openssl_sign',
            'openssl_spki_export' => 'Safe\openssl_spki_export',
            'openssl_spki_export_challenge' => 'Safe\openssl_spki_export_challenge',
            'openssl_spki_new' => 'Safe\openssl_spki_new',
            'openssl_spki_verify' => 'Safe\openssl_spki_verify',
            'openssl_verify' => 'Safe\openssl_verify',
            'openssl_x509_checkpurpose' => 'Safe\openssl_x509_checkpurpose',
            'openssl_x509_export' => 'Safe\openssl_x509_export',
            'openssl_x509_export_to_file' => 'Safe\openssl_x509_export_to_file',
            'openssl_x509_fingerprint' => 'Safe\openssl_x509_fingerprint',
            'openssl_x509_read' => 'Safe\openssl_x509_read',
            'output_add_rewrite_var' => 'Safe\output_add_rewrite_var',
            'output_reset_rewrite_vars' => 'Safe\output_reset_rewrite_vars',
            'parse_ini_file' => 'Safe\parse_ini_file',
            'parse_ini_string' => 'Safe\parse_ini_string',
            'parse_url' => 'Safe\parse_url',
            'passthru' => 'Safe\passthru',
            'pclose' => 'Safe\pclose',
            'pcntl_getpriority' => 'Safe\pcntl_getpriority',
            'pcntl_setpriority' => 'Safe\pcntl_setpriority',
            'pcntl_signal' => 'Safe\pcntl_signal',
            'pcntl_signal_dispatch' => 'Safe\pcntl_signal_dispatch',
            'pcntl_sigprocmask' => 'Safe\pcntl_sigprocmask',
            'pcntl_sigtimedwait' => 'Safe\pcntl_sigtimedwait',
            'pcntl_sigwaitinfo' => 'Safe\pcntl_sigwaitinfo',
            'pfsockopen' => 'Safe\pfsockopen',
            'pg_cancel_query' => 'Safe\pg_cancel_query',
            'pg_connect' => 'Safe\pg_connect',
            'pg_connection_reset' => 'Safe\pg_connection_reset',
            'pg_convert' => 'Safe\pg_convert',
            'pg_copy_from' => 'Safe\pg_copy_from',
            'pg_copy_to' => 'Safe\pg_copy_to',
            'pg_delete' => 'Safe\pg_delete',
            'pg_end_copy' => 'Safe\pg_end_copy',
            'pg_execute' => 'Safe\pg_execute',
            'pg_field_num' => 'Safe\pg_field_num',
            'pg_field_table' => 'Safe\pg_field_table',
            'pg_flush' => 'Safe\pg_flush',
            'pg_free_result' => 'Safe\pg_free_result',
            'pg_host' => 'Safe\pg_host',
            'pg_insert' => 'Safe\pg_insert',
            'pg_last_oid' => 'Safe\pg_last_oid',
            'pg_lo_close' => 'Safe\pg_lo_close',
            'pg_lo_export' => 'Safe\pg_lo_export',
            'pg_lo_import' => 'Safe\pg_lo_import',
            'pg_lo_open' => 'Safe\pg_lo_open',
            'pg_lo_read' => 'Safe\pg_lo_read',
            'pg_lo_seek' => 'Safe\pg_lo_seek',
            'pg_lo_truncate' => 'Safe\pg_lo_truncate',
            'pg_lo_unlink' => 'Safe\pg_lo_unlink',
            'pg_lo_write' => 'Safe\pg_lo_write',
            'pg_meta_data' => 'Safe\pg_meta_data',
            'pg_parameter_status' => 'Safe\pg_parameter_status',
            'pg_pconnect' => 'Safe\pg_pconnect',
            'pg_ping' => 'Safe\pg_ping',
            'pg_prepare' => 'Safe\pg_prepare',
            'pg_put_line' => 'Safe\pg_put_line',
            'pg_query' => 'Safe\pg_query',
            'pg_query_params' => 'Safe\pg_query_params',
            'pg_result_error_field' => 'Safe\pg_result_error_field',
            'pg_result_seek' => 'Safe\pg_result_seek',
            'pg_select' => 'Safe\pg_select',
            'pg_socket' => 'Safe\pg_socket',
            'pg_trace' => 'Safe\pg_trace',
            'pg_update' => 'Safe\pg_update',
            'phpcredits' => 'Safe\phpcredits',
            'phpinfo' => 'Safe\phpinfo',
            'php_sapi_name' => 'Safe\php_sapi_name',
            'popen' => 'Safe\popen',
            'posix_access' => 'Safe\posix_access',
            'posix_getgrgid' => 'Safe\posix_getgrgid',
            'posix_getgrnam' => 'Safe\posix_getgrnam',
            'posix_getgroups' => 'Safe\posix_getgroups',
            'posix_getlogin' => 'Safe\posix_getlogin',
            'posix_getpgid' => 'Safe\posix_getpgid',
            'posix_getpwuid' => 'Safe\posix_getpwuid',
            'posix_getrlimit' => 'Safe\posix_getrlimit',
            'posix_getsid' => 'Safe\posix_getsid',
            'posix_initgroups' => 'Safe\posix_initgroups',
            'posix_kill' => 'Safe\posix_kill',
            'posix_mkfifo' => 'Safe\posix_mkfifo',
            'posix_mknod' => 'Safe\posix_mknod',
            'posix_setegid' => 'Safe\posix_setegid',
            'posix_seteuid' => 'Safe\posix_seteuid',
            'posix_setgid' => 'Safe\posix_setgid',
            'posix_setpgid' => 'Safe\posix_setpgid',
            'posix_setrlimit' => 'Safe\posix_setrlimit',
            'posix_setsid' => 'Safe\posix_setsid',
            'posix_setuid' => 'Safe\posix_setuid',
            'posix_times' => 'Safe\posix_times',
            'posix_uname' => 'Safe\posix_uname',
            'preg_grep' => 'Safe\preg_grep',
            'preg_match' => 'Safe\preg_match',
            'preg_match_all' => 'Safe\preg_match_all',
            'preg_replace' => 'Safe\preg_replace',
            'preg_replace_callback' => 'Safe\preg_replace_callback',
            'preg_replace_callback_array' => 'Safe\preg_replace_callback_array',
            'preg_split' => 'Safe\preg_split',
            'proc_close' => 'Safe\proc_close',
            'proc_nice' => 'Safe\proc_nice',
            'proc_open' => 'Safe\proc_open',
            'pspell_add_to_personal' => 'Safe\pspell_add_to_personal',
            'pspell_add_to_session' => 'Safe\pspell_add_to_session',
            'pspell_clear_session' => 'Safe\pspell_clear_session',
            'pspell_config_create' => 'Safe\pspell_config_create',
            'pspell_config_data_dir' => 'Safe\pspell_config_data_dir',
            'pspell_config_dict_dir' => 'Safe\pspell_config_dict_dir',
            'pspell_config_ignore' => 'Safe\pspell_config_ignore',
            'pspell_config_mode' => 'Safe\pspell_config_mode',
            'pspell_config_personal' => 'Safe\pspell_config_personal',
            'pspell_config_repl' => 'Safe\pspell_config_repl',
            'pspell_config_runtogether' => 'Safe\pspell_config_runtogether',
            'pspell_config_save_repl' => 'Safe\pspell_config_save_repl',
            'pspell_new' => 'Safe\pspell_new',
            'pspell_new_config' => 'Safe\pspell_new_config',
            'pspell_new_personal' => 'Safe\pspell_new_personal',
            'pspell_save_wordlist' => 'Safe\pspell_save_wordlist',
            'pspell_store_replacement' => 'Safe\pspell_store_replacement',
            'ps_add_launchlink' => 'Safe\ps_add_launchlink',
            'ps_add_locallink' => 'Safe\ps_add_locallink',
            'ps_add_note' => 'Safe\ps_add_note',
            'ps_add_pdflink' => 'Safe\ps_add_pdflink',
            'ps_add_weblink' => 'Safe\ps_add_weblink',
            'ps_arc' => 'Safe\ps_arc',
            'ps_arcn' => 'Safe\ps_arcn',
            'ps_begin_page' => 'Safe\ps_begin_page',
            'ps_begin_pattern' => 'Safe\ps_begin_pattern',
            'ps_begin_template' => 'Safe\ps_begin_template',
            'ps_circle' => 'Safe\ps_circle',
            'ps_clip' => 'Safe\ps_clip',
            'ps_close' => 'Safe\ps_close',
            'ps_closepath' => 'Safe\ps_closepath',
            'ps_closepath_stroke' => 'Safe\ps_closepath_stroke',
            'ps_close_image' => 'Safe\ps_close_image',
            'ps_continue_text' => 'Safe\ps_continue_text',
            'ps_curveto' => 'Safe\ps_curveto',
            'ps_delete' => 'Safe\ps_delete',
            'ps_end_page' => 'Safe\ps_end_page',
            'ps_end_pattern' => 'Safe\ps_end_pattern',
            'ps_end_template' => 'Safe\ps_end_template',
            'ps_fill' => 'Safe\ps_fill',
            'ps_fill_stroke' => 'Safe\ps_fill_stroke',
            'ps_get_parameter' => 'Safe\ps_get_parameter',
            'ps_hyphenate' => 'Safe\ps_hyphenate',
            'ps_include_file' => 'Safe\ps_include_file',
            'ps_lineto' => 'Safe\ps_lineto',
            'ps_moveto' => 'Safe\ps_moveto',
            'ps_new' => 'Safe\ps_new',
            'ps_open_file' => 'Safe\ps_open_file',
            'ps_place_image' => 'Safe\ps_place_image',
            'ps_rect' => 'Safe\ps_rect',
            'ps_restore' => 'Safe\ps_restore',
            'ps_rotate' => 'Safe\ps_rotate',
            'ps_save' => 'Safe\ps_save',
            'ps_scale' => 'Safe\ps_scale',
            'ps_setcolor' => 'Safe\ps_setcolor',
            'ps_setdash' => 'Safe\ps_setdash',
            'ps_setflat' => 'Safe\ps_setflat',
            'ps_setfont' => 'Safe\ps_setfont',
            'ps_setgray' => 'Safe\ps_setgray',
            'ps_setlinecap' => 'Safe\ps_setlinecap',
            'ps_setlinejoin' => 'Safe\ps_setlinejoin',
            'ps_setlinewidth' => 'Safe\ps_setlinewidth',
            'ps_setmiterlimit' => 'Safe\ps_setmiterlimit',
            'ps_setoverprintmode' => 'Safe\ps_setoverprintmode',
            'ps_setpolydash' => 'Safe\ps_setpolydash',
            'ps_set_border_color' => 'Safe\ps_set_border_color',
            'ps_set_border_dash' => 'Safe\ps_set_border_dash',
            'ps_set_border_style' => 'Safe\ps_set_border_style',
            'ps_set_info' => 'Safe\ps_set_info',
            'ps_set_parameter' => 'Safe\ps_set_parameter',
            'ps_set_text_pos' => 'Safe\ps_set_text_pos',
            'ps_set_value' => 'Safe\ps_set_value',
            'ps_shading' => 'Safe\ps_shading',
            'ps_shading_pattern' => 'Safe\ps_shading_pattern',
            'ps_shfill' => 'Safe\ps_shfill',
            'ps_show' => 'Safe\ps_show',
            'ps_show2' => 'Safe\ps_show2',
            'ps_show_xy' => 'Safe\ps_show_xy',
            'ps_show_xy2' => 'Safe\ps_show_xy2',
            'ps_stroke' => 'Safe\ps_stroke',
            'ps_symbol' => 'Safe\ps_symbol',
            'ps_translate' => 'Safe\ps_translate',
            'putenv' => 'Safe\putenv',
            'readfile' => 'Safe\readfile',
            'readgzfile' => 'Safe\readgzfile',
            'readline_add_history' => 'Safe\readline_add_history',
            'readline_callback_handler_install' => 'Safe\readline_callback_handler_install',
            'readline_clear_history' => 'Safe\readline_clear_history',
            'readline_completion_function' => 'Safe\readline_completion_function',
            'readline_read_history' => 'Safe\readline_read_history',
            'readline_write_history' => 'Safe\readline_write_history',
            'readlink' => 'Safe\readlink',
            'realpath' => 'Safe\realpath',
            'register_tick_function' => 'Safe\register_tick_function',
            'rename' => 'Safe\rename',
            'rewind' => 'Safe\rewind',
            'rmdir' => 'Safe\rmdir',
            'rnp_decrypt' => 'Safe\rnp_decrypt',
            'rnp_dump_packets' => 'Safe\rnp_dump_packets',
            'rnp_dump_packets_to_json' => 'Safe\rnp_dump_packets_to_json',
            'rnp_ffi_create' => 'Safe\rnp_ffi_create',
            'rnp_ffi_set_pass_provider' => 'Safe\rnp_ffi_set_pass_provider',
            'rnp_import_keys' => 'Safe\rnp_import_keys',
            'rnp_import_signatures' => 'Safe\rnp_import_signatures',
            'rnp_key_export' => 'Safe\rnp_key_export',
            'rnp_key_export_autocrypt' => 'Safe\rnp_key_export_autocrypt',
            'rnp_key_export_revocation' => 'Safe\rnp_key_export_revocation',
            'rnp_key_get_info' => 'Safe\rnp_key_get_info',
            'rnp_key_remove' => 'Safe\rnp_key_remove',
            'rnp_key_revoke' => 'Safe\rnp_key_revoke',
            'rnp_list_keys' => 'Safe\rnp_list_keys',
            'rnp_load_keys' => 'Safe\rnp_load_keys',
            'rnp_load_keys_from_path' => 'Safe\rnp_load_keys_from_path',
            'rnp_locate_key' => 'Safe\rnp_locate_key',
            'rnp_op_encrypt' => 'Safe\rnp_op_encrypt',
            'rnp_op_generate_key' => 'Safe\rnp_op_generate_key',
            'rnp_op_sign' => 'Safe\rnp_op_sign',
            'rnp_op_sign_cleartext' => 'Safe\rnp_op_sign_cleartext',
            'rnp_op_sign_detached' => 'Safe\rnp_op_sign_detached',
            'rnp_op_verify' => 'Safe\rnp_op_verify',
            'rnp_op_verify_detached' => 'Safe\rnp_op_verify_detached',
            'rnp_save_keys' => 'Safe\rnp_save_keys',
            'rnp_save_keys_to_path' => 'Safe\rnp_save_keys_to_path',
            'rnp_supported_features' => 'Safe\rnp_supported_features',
            'rpmaddtag' => 'Safe\rpmaddtag',
            'rrd_create' => 'Safe\rrd_create',
            'rrd_first' => 'Safe\rrd_first',
            'rrd_graph' => 'Safe\rrd_graph',
            'rrd_info' => 'Safe\rrd_info',
            'rrd_lastupdate' => 'Safe\rrd_lastupdate',
            'rrd_restore' => 'Safe\rrd_restore',
            'rrd_tune' => 'Safe\rrd_tune',
            'rrd_update' => 'Safe\rrd_update',
            'rrd_xport' => 'Safe\rrd_xport',
            'sapi_windows_cp_conv' => 'Safe\sapi_windows_cp_conv',
            'sapi_windows_cp_set' => 'Safe\sapi_windows_cp_set',
            'sapi_windows_generate_ctrl_event' => 'Safe\sapi_windows_generate_ctrl_event',
            'sapi_windows_set_ctrl_handler' => 'Safe\sapi_windows_set_ctrl_handler',
            'sapi_windows_vt100_support' => 'Safe\sapi_windows_vt100_support',
            'scandir' => 'Safe\scandir',
            'sem_acquire' => 'Safe\sem_acquire',
            'sem_get' => 'Safe\sem_get',
            'sem_release' => 'Safe\sem_release',
            'sem_remove' => 'Safe\sem_remove',
            'session_abort' => 'Safe\session_abort',
            'session_cache_expire' => 'Safe\session_cache_expire',
            'session_cache_limiter' => 'Safe\session_cache_limiter',
            'session_create_id' => 'Safe\session_create_id',
            'session_decode' => 'Safe\session_decode',
            'session_destroy' => 'Safe\session_destroy',
            'session_encode' => 'Safe\session_encode',
            'session_gc' => 'Safe\session_gc',
            'session_id' => 'Safe\session_id',
            'session_module_name' => 'Safe\session_module_name',
            'session_name' => 'Safe\session_name',
            'session_regenerate_id' => 'Safe\session_regenerate_id',
            'session_reset' => 'Safe\session_reset',
            'session_save_path' => 'Safe\session_save_path',
            'session_start' => 'Safe\session_start',
            'session_unset' => 'Safe\session_unset',
            'session_write_close' => 'Safe\session_write_close',
            'settype' => 'Safe\settype',
            'set_include_path' => 'Safe\set_include_path',
            'set_time_limit' => 'Safe\set_time_limit',
            'sha1_file' => 'Safe\sha1_file',
            'shell_exec' => 'Safe\shell_exec',
            'shmop_delete' => 'Safe\shmop_delete',
            'shmop_read' => 'Safe\shmop_read',
            'shm_attach' => 'Safe\shm_attach',
            'shm_detach' => 'Safe\shm_detach',
            'shm_put_var' => 'Safe\shm_put_var',
            'shm_remove' => 'Safe\shm_remove',
            'shm_remove_var' => 'Safe\shm_remove_var',
            'simplexml_import_dom' => 'Safe\simplexml_import_dom',
            'simplexml_load_file' => 'Safe\simplexml_load_file',
            'simplexml_load_string' => 'Safe\simplexml_load_string',
            'socket_accept' => 'Safe\socket_accept',
            'socket_addrinfo_bind' => 'Safe\socket_addrinfo_bind',
            'socket_addrinfo_connect' => 'Safe\socket_addrinfo_connect',
            'socket_addrinfo_lookup' => 'Safe\socket_addrinfo_lookup',
            'socket_bind' => 'Safe\socket_bind',
            'socket_connect' => 'Safe\socket_connect',
            'socket_create' => 'Safe\socket_create',
            'socket_create_listen' => 'Safe\socket_create_listen',
            'socket_create_pair' => 'Safe\socket_create_pair',
            'socket_export_stream' => 'Safe\socket_export_stream',
            'socket_getpeername' => 'Safe\socket_getpeername',
            'socket_getsockname' => 'Safe\socket_getsockname',
            'socket_get_option' => 'Safe\socket_get_option',
            'socket_import_stream' => 'Safe\socket_import_stream',
            'socket_listen' => 'Safe\socket_listen',
            'socket_read' => 'Safe\socket_read',
            'socket_send' => 'Safe\socket_send',
            'socket_sendmsg' => 'Safe\socket_sendmsg',
            'socket_sendto' => 'Safe\socket_sendto',
            'socket_set_block' => 'Safe\socket_set_block',
            'socket_set_nonblock' => 'Safe\socket_set_nonblock',
            'socket_set_option' => 'Safe\socket_set_option',
            'socket_shutdown' => 'Safe\socket_shutdown',
            'socket_write' => 'Safe\socket_write',
            'socket_wsaprotocol_info_export' => 'Safe\socket_wsaprotocol_info_export',
            'socket_wsaprotocol_info_import' => 'Safe\socket_wsaprotocol_info_import',
            'socket_wsaprotocol_info_release' => 'Safe\socket_wsaprotocol_info_release',
            'sodium_crypto_aead_aes256gcm_decrypt' => 'Safe\sodium_crypto_aead_aes256gcm_decrypt',
            'sodium_crypto_aead_chacha20poly1305_decrypt' => 'Safe\sodium_crypto_aead_chacha20poly1305_decrypt',
            'sodium_crypto_aead_chacha20poly1305_encrypt' => 'Safe\sodium_crypto_aead_chacha20poly1305_encrypt',
            'sodium_crypto_aead_chacha20poly1305_ietf_decrypt' => 'Safe\sodium_crypto_aead_chacha20poly1305_ietf_decrypt',
            'sodium_crypto_aead_chacha20poly1305_ietf_encrypt' => 'Safe\sodium_crypto_aead_chacha20poly1305_ietf_encrypt',
            'sodium_crypto_aead_xchacha20poly1305_ietf_decrypt' => 'Safe\sodium_crypto_aead_xchacha20poly1305_ietf_decrypt',
            'sodium_crypto_aead_xchacha20poly1305_ietf_encrypt' => 'Safe\sodium_crypto_aead_xchacha20poly1305_ietf_encrypt',
            'sodium_crypto_box_open' => 'Safe\sodium_crypto_box_open',
            'sodium_crypto_box_seal_open' => 'Safe\sodium_crypto_box_seal_open',
            'sodium_crypto_generichash_update' => 'Safe\sodium_crypto_generichash_update',
            'sodium_crypto_secretbox_open' => 'Safe\sodium_crypto_secretbox_open',
            'sodium_crypto_sign_open' => 'Safe\sodium_crypto_sign_open',
            'sodium_crypto_stream_xchacha20_xor_ic' => 'Safe\sodium_crypto_stream_xchacha20_xor_ic',
            'solr_get_version' => 'Safe\solr_get_version',
            'spl_autoload_register' => 'Safe\spl_autoload_register',
            'spl_autoload_unregister' => 'Safe\spl_autoload_unregister',
            'sqlsrv_begin_transaction' => 'Safe\sqlsrv_begin_transaction',
            'sqlsrv_cancel' => 'Safe\sqlsrv_cancel',
            'sqlsrv_client_info' => 'Safe\sqlsrv_client_info',
            'sqlsrv_close' => 'Safe\sqlsrv_close',
            'sqlsrv_commit' => 'Safe\sqlsrv_commit',
            'sqlsrv_configure' => 'Safe\sqlsrv_configure',
            'sqlsrv_execute' => 'Safe\sqlsrv_execute',
            'sqlsrv_fetch' => 'Safe\sqlsrv_fetch',
            'sqlsrv_fetch_array' => 'Safe\sqlsrv_fetch_array',
            'sqlsrv_fetch_object' => 'Safe\sqlsrv_fetch_object',
            'sqlsrv_free_stmt' => 'Safe\sqlsrv_free_stmt',
            'sqlsrv_get_field' => 'Safe\sqlsrv_get_field',
            'sqlsrv_next_result' => 'Safe\sqlsrv_next_result',
            'sqlsrv_num_fields' => 'Safe\sqlsrv_num_fields',
            'sqlsrv_num_rows' => 'Safe\sqlsrv_num_rows',
            'sqlsrv_prepare' => 'Safe\sqlsrv_prepare',
            'sqlsrv_query' => 'Safe\sqlsrv_query',
            'sqlsrv_rollback' => 'Safe\sqlsrv_rollback',
            'ssdeep_fuzzy_compare' => 'Safe\ssdeep_fuzzy_compare',
            'ssdeep_fuzzy_hash' => 'Safe\ssdeep_fuzzy_hash',
            'ssdeep_fuzzy_hash_filename' => 'Safe\ssdeep_fuzzy_hash_filename',
            'ssh2_auth_agent' => 'Safe\ssh2_auth_agent',
            'ssh2_auth_hostbased_file' => 'Safe\ssh2_auth_hostbased_file',
            'ssh2_auth_password' => 'Safe\ssh2_auth_password',
            'ssh2_auth_pubkey_file' => 'Safe\ssh2_auth_pubkey_file',
            'ssh2_connect' => 'Safe\ssh2_connect',
            'ssh2_disconnect' => 'Safe\ssh2_disconnect',
            'ssh2_exec' => 'Safe\ssh2_exec',
            'ssh2_forward_accept' => 'Safe\ssh2_forward_accept',
            'ssh2_forward_listen' => 'Safe\ssh2_forward_listen',
            'ssh2_publickey_add' => 'Safe\ssh2_publickey_add',
            'ssh2_publickey_init' => 'Safe\ssh2_publickey_init',
            'ssh2_publickey_remove' => 'Safe\ssh2_publickey_remove',
            'ssh2_scp_recv' => 'Safe\ssh2_scp_recv',
            'ssh2_scp_send' => 'Safe\ssh2_scp_send',
            'ssh2_send_eof' => 'Safe\ssh2_send_eof',
            'ssh2_sftp' => 'Safe\ssh2_sftp',
            'ssh2_sftp_chmod' => 'Safe\ssh2_sftp_chmod',
            'ssh2_sftp_mkdir' => 'Safe\ssh2_sftp_mkdir',
            'ssh2_sftp_rename' => 'Safe\ssh2_sftp_rename',
            'ssh2_sftp_rmdir' => 'Safe\ssh2_sftp_rmdir',
            'ssh2_sftp_symlink' => 'Safe\ssh2_sftp_symlink',
            'ssh2_sftp_unlink' => 'Safe\ssh2_sftp_unlink',
            'ssh2_shell' => 'Safe\ssh2_shell',
            'stream_context_set_params' => 'Safe\stream_context_set_params',
            'stream_copy_to_stream' => 'Safe\stream_copy_to_stream',
            'stream_filter_append' => 'Safe\stream_filter_append',
            'stream_filter_prepend' => 'Safe\stream_filter_prepend',
            'stream_filter_register' => 'Safe\stream_filter_register',
            'stream_filter_remove' => 'Safe\stream_filter_remove',
            'stream_get_contents' => 'Safe\stream_get_contents',
            'stream_get_line' => 'Safe\stream_get_line',
            'stream_isatty' => 'Safe\stream_isatty',
            'stream_resolve_include_path' => 'Safe\stream_resolve_include_path',
            'stream_set_blocking' => 'Safe\stream_set_blocking',
            'stream_set_timeout' => 'Safe\stream_set_timeout',
            'stream_socket_accept' => 'Safe\stream_socket_accept',
            'stream_socket_client' => 'Safe\stream_socket_client',
            'stream_socket_get_name' => 'Safe\stream_socket_get_name',
            'stream_socket_pair' => 'Safe\stream_socket_pair',
            'stream_socket_recvfrom' => 'Safe\stream_socket_recvfrom',
            'stream_socket_sendto' => 'Safe\stream_socket_sendto',
            'stream_socket_server' => 'Safe\stream_socket_server',
            'stream_socket_shutdown' => 'Safe\stream_socket_shutdown',
            'stream_supports_lock' => 'Safe\stream_supports_lock',
            'stream_wrapper_register' => 'Safe\stream_wrapper_register',
            'stream_wrapper_restore' => 'Safe\stream_wrapper_restore',
            'stream_wrapper_unregister' => 'Safe\stream_wrapper_unregister',
            'strftime' => 'Safe\strftime',
            'strptime' => 'Safe\strptime',
            'strtotime' => 'Safe\strtotime',
            'swoole_async_dns_lookup' => 'Safe\swoole_async_dns_lookup',
            'swoole_async_readfile' => 'Safe\swoole_async_readfile',
            'swoole_async_write' => 'Safe\swoole_async_write',
            'swoole_async_writefile' => 'Safe\swoole_async_writefile',
            'swoole_event_defer' => 'Safe\swoole_event_defer',
            'swoole_event_del' => 'Safe\swoole_event_del',
            'swoole_event_write' => 'Safe\swoole_event_write',
            'symlink' => 'Safe\symlink',
            'syslog' => 'Safe\syslog',
            'system' => 'Safe\system',
            'sys_getloadavg' => 'Safe\sys_getloadavg',
            'tempnam' => 'Safe\tempnam',
            'timezone_name_from_abbr' => 'Safe\timezone_name_from_abbr',
            'time_nanosleep' => 'Safe\time_nanosleep',
            'time_sleep_until' => 'Safe\time_sleep_until',
            'tmpfile' => 'Safe\tmpfile',
            'touch' => 'Safe\touch',
            'unixtojd' => 'Safe\unixtojd',
            'unlink' => 'Safe\unlink',
            'unpack' => 'Safe\unpack',
            'unserialize' => 'Safe\unserialize',
            'uopz_extend' => 'Safe\uopz_extend',
            'uopz_implement' => 'Safe\uopz_implement',
            'variant_date_to_timestamp' => 'Safe\variant_date_to_timestamp',
            'variant_round' => 'Safe\variant_round',
            'virtual' => 'Safe\virtual',
            'xdiff_file_bdiff' => 'Safe\xdiff_file_bdiff',
            'xdiff_file_bpatch' => 'Safe\xdiff_file_bpatch',
            'xdiff_file_diff' => 'Safe\xdiff_file_diff',
            'xdiff_file_diff_binary' => 'Safe\xdiff_file_diff_binary',
            'xdiff_file_patch_binary' => 'Safe\xdiff_file_patch_binary',
            'xdiff_file_rabdiff' => 'Safe\xdiff_file_rabdiff',
            'xdiff_string_bpatch' => 'Safe\xdiff_string_bpatch',
            'xdiff_string_patch' => 'Safe\xdiff_string_patch',
            'xdiff_string_patch_binary' => 'Safe\xdiff_string_patch_binary',
            'xmlrpc_set_type' => 'Safe\xmlrpc_set_type',
            'xml_parser_free' => 'Safe\xml_parser_free',
            'xml_set_character_data_handler' => 'Safe\xml_set_character_data_handler',
            'xml_set_default_handler' => 'Safe\xml_set_default_handler',
            'xml_set_element_handler' => 'Safe\xml_set_element_handler',
            'xml_set_end_namespace_decl_handler' => 'Safe\xml_set_end_namespace_decl_handler',
            'xml_set_external_entity_ref_handler' => 'Safe\xml_set_external_entity_ref_handler',
            'xml_set_notation_decl_handler' => 'Safe\xml_set_notation_decl_handler',
            'xml_set_object' => 'Safe\xml_set_object',
            'xml_set_processing_instruction_handler' => 'Safe\xml_set_processing_instruction_handler',
            'xml_set_start_namespace_decl_handler' => 'Safe\xml_set_start_namespace_decl_handler',
            'xml_set_unparsed_entity_decl_handler' => 'Safe\xml_set_unparsed_entity_decl_handler',
            'yaml_parse' => 'Safe\yaml_parse',
            'yaml_parse_file' => 'Safe\yaml_parse_file',
            'yaml_parse_url' => 'Safe\yaml_parse_url',
            'yaz_ccl_parse' => 'Safe\yaz_ccl_parse',
            'yaz_close' => 'Safe\yaz_close',
            'yaz_connect' => 'Safe\yaz_connect',
            'yaz_database' => 'Safe\yaz_database',
            'yaz_element' => 'Safe\yaz_element',
            'yaz_present' => 'Safe\yaz_present',
            'yaz_search' => 'Safe\yaz_search',
            'yaz_wait' => 'Safe\yaz_wait',
            'zip_entry_close' => 'Safe\zip_entry_close',
            'zip_entry_compressedsize' => 'Safe\zip_entry_compressedsize',
            'zip_entry_compressionmethod' => 'Safe\zip_entry_compressionmethod',
            'zip_entry_filesize' => 'Safe\zip_entry_filesize',
            'zip_entry_name' => 'Safe\zip_entry_name',
            'zip_entry_open' => 'Safe\zip_entry_open',
            'zip_entry_read' => 'Safe\zip_entry_read',
            'zlib_decode' => 'Safe\zlib_decode',
        ]
    );
};
<?php

namespace Safe;

use Safe\Exceptions\NetworkException;

/**
 * @throws NetworkException
 *
 */
function closelog(): void
{
    error_clear_last();
    $safeResult = \closelog();
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
}


/**
 * @param string $hostname
 * @param int $type
 * @param array|null $authoritative_name_servers
 * @param array|null $additional_records
 * @param bool $raw
 * @return list
 * @throws NetworkException
 *
 */
function dns_get_record(string $hostname, int $type = DNS_ANY, ?array &$authoritative_name_servers = null, ?array &$additional_records = null, bool $raw = false): array
{
    error_clear_last();
    $safeResult = \dns_get_record($hostname, $type, $authoritative_name_servers, $additional_records, $raw);
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $hostname
 * @param int $port
 * @param int|null $error_code
 * @param null|string $error_message
 * @param float|null $timeout
 * @return resource
 * @throws NetworkException
 *
 */
function fsockopen(string $hostname, int $port = -1, ?int &$error_code = null, ?string &$error_message = null, ?float $timeout = null)
{
    error_clear_last();
    if ($timeout !== null) {
        $safeResult = \fsockopen($hostname, $port, $error_code, $error_message, $timeout);
    } else {
        $safeResult = \fsockopen($hostname, $port, $error_code, $error_message);
    }
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return string
 * @throws NetworkException
 *
 */
function gethostname(): string
{
    error_clear_last();
    $safeResult = \gethostname();
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $protocol
 * @return int
 * @throws NetworkException
 *
 */
function getprotobyname(string $protocol): int
{
    error_clear_last();
    $safeResult = \getprotobyname($protocol);
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $protocol
 * @return string
 * @throws NetworkException
 *
 */
function getprotobynumber(int $protocol): string
{
    error_clear_last();
    $safeResult = \getprotobynumber($protocol);
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $port
 * @param string $protocol
 * @return string
 * @throws NetworkException
 *
 */
function getservbyport(int $port, string $protocol): string
{
    error_clear_last();
    $safeResult = \getservbyport($port, $protocol);
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param callable $callback
 * @throws NetworkException
 *
 */
function header_register_callback(callable $callback): void
{
    error_clear_last();
    $safeResult = \header_register_callback($callback);
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
}


/**
 * @param string $ip
 * @return string
 * @throws NetworkException
 *
 */
function inet_ntop(string $ip): string
{
    error_clear_last();
    $safeResult = \inet_ntop($ip);
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $ip
 * @return string
 * @throws NetworkException
 *
 */
function inet_pton(string $ip): string
{
    error_clear_last();
    $safeResult = \inet_pton($ip);
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $ip
 * @return string
 * @throws NetworkException
 *
 */
function long2ip(int $ip): string
{
    error_clear_last();
    $safeResult = \long2ip($ip);
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return array
 * @throws NetworkException
 *
 */
function net_get_interfaces(): array
{
    error_clear_last();
    $safeResult = \net_get_interfaces();
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $prefix
 * @param int $flags
 * @param int $facility
 * @throws NetworkException
 *
 */
function openlog(string $prefix, int $flags, int $facility): void
{
    error_clear_last();
    $safeResult = \openlog($prefix, $flags, $facility);
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
}


/**
 * @param string $hostname
 * @param int $port
 * @param int|null $error_code
 * @param null|string $error_message
 * @param float|null $timeout
 * @return resource
 * @throws NetworkException
 *
 */
function pfsockopen(string $hostname, int $port = -1, ?int &$error_code = null, ?string &$error_message = null, ?float $timeout = null)
{
    error_clear_last();
    if ($timeout !== null) {
        $safeResult = \pfsockopen($hostname, $port, $error_code, $error_message, $timeout);
    } else {
        $safeResult = \pfsockopen($hostname, $port, $error_code, $error_message);
    }
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $priority
 * @param string $message
 * @throws NetworkException
 *
 */
function syslog(int $priority, string $message): void
{
    error_clear_last();
    $safeResult = \syslog($priority, $message);
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\ImapException;

/**
 * @param string $string
 * @return string
 * @throws ImapException
 *
 */
function imap_8bit(string $string): string
{
    error_clear_last();
    $safeResult = \imap_8bit($string);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $folder
 * @param string $message
 * @param null|string $options
 * @param null|string $internal_date
 * @throws ImapException
 *
 */
function imap_append(\IMAP\Connection $imap, string $folder, string $message, ?string $options = null, ?string $internal_date = null): void
{
    error_clear_last();
    if ($internal_date !== null) {
        $safeResult = \imap_append($imap, $folder, $message, $options, $internal_date);
    } elseif ($options !== null) {
        $safeResult = \imap_append($imap, $folder, $message, $options);
    } else {
        $safeResult = \imap_append($imap, $folder, $message);
    }
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param string $string
 * @return string
 * @throws ImapException
 *
 */
function imap_base64(string $string): string
{
    error_clear_last();
    $safeResult = \imap_base64($string);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $string
 * @return string
 * @throws ImapException
 *
 */
function imap_binary(string $string): string
{
    error_clear_last();
    $safeResult = \imap_binary($string);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param int $message_num
 * @param int $flags
 * @return string
 * @throws ImapException
 *
 */
function imap_body(\IMAP\Connection $imap, int $message_num, int $flags = 0): string
{
    error_clear_last();
    $safeResult = \imap_body($imap, $message_num, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param int $message_num
 * @param string $section
 * @return \stdClass
 * @throws ImapException
 *
 */
function imap_bodystruct(\IMAP\Connection $imap, int $message_num, string $section): \stdClass
{
    error_clear_last();
    $safeResult = \imap_bodystruct($imap, $message_num, $section);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @return \stdClass
 * @throws ImapException
 *
 */
function imap_check(\IMAP\Connection $imap): \stdClass
{
    error_clear_last();
    $safeResult = \imap_check($imap);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $sequence
 * @param string $flag
 * @param int $options
 * @throws ImapException
 *
 */
function imap_clearflag_full(\IMAP\Connection $imap, string $sequence, string $flag, int $options = 0): void
{
    error_clear_last();
    $safeResult = \imap_clearflag_full($imap, $sequence, $flag, $options);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param int $flags
 * @throws ImapException
 *
 */
function imap_close(\IMAP\Connection $imap, int $flags = 0): void
{
    error_clear_last();
    $safeResult = \imap_close($imap, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param string $mailbox
 * @throws ImapException
 *
 */
function imap_createmailbox(\IMAP\Connection $imap, string $mailbox): void
{
    error_clear_last();
    $safeResult = \imap_createmailbox($imap, $mailbox);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param string $mailbox
 * @throws ImapException
 *
 */
function imap_deletemailbox(\IMAP\Connection $imap, string $mailbox): void
{
    error_clear_last();
    $safeResult = \imap_deletemailbox($imap, $mailbox);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param string $sequence
 * @param int $flags
 * @return array
 * @throws ImapException
 *
 */
function imap_fetch_overview(\IMAP\Connection $imap, string $sequence, int $flags = 0): array
{
    error_clear_last();
    $safeResult = \imap_fetch_overview($imap, $sequence, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param int $message_num
 * @param string $section
 * @param int $flags
 * @return string
 * @throws ImapException
 *
 */
function imap_fetchbody(\IMAP\Connection $imap, int $message_num, string $section, int $flags = 0): string
{
    error_clear_last();
    $safeResult = \imap_fetchbody($imap, $message_num, $section, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param int $message_num
 * @param int $flags
 * @return string
 * @throws ImapException
 *
 */
function imap_fetchheader(\IMAP\Connection $imap, int $message_num, int $flags = 0): string
{
    error_clear_last();
    $safeResult = \imap_fetchheader($imap, $message_num, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param int $message_num
 * @param string $section
 * @param int $flags
 * @return string
 * @throws ImapException
 *
 */
function imap_fetchmime(\IMAP\Connection $imap, int $message_num, string $section, int $flags = 0): string
{
    error_clear_last();
    $safeResult = \imap_fetchmime($imap, $message_num, $section, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param int $message_num
 * @param int $flags
 * @return \stdClass
 * @throws ImapException
 *
 */
function imap_fetchstructure(\IMAP\Connection $imap, int $message_num, int $flags = 0): \stdClass
{
    error_clear_last();
    $safeResult = \imap_fetchstructure($imap, $message_num, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param int $flags
 * @throws ImapException
 *
 */
function imap_gc(\IMAP\Connection $imap, int $flags): void
{
    error_clear_last();
    $safeResult = \imap_gc($imap, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param string $mailbox
 * @return array
 * @throws ImapException
 *
 */
function imap_getacl(\IMAP\Connection $imap, string $mailbox): array
{
    error_clear_last();
    $safeResult = \imap_getacl($imap, $mailbox);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $reference
 * @param string $pattern
 * @return array
 * @throws ImapException
 *
 */
function imap_getmailboxes(\IMAP\Connection $imap, string $reference, string $pattern): array
{
    error_clear_last();
    $safeResult = \imap_getmailboxes($imap, $reference, $pattern);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $reference
 * @param string $pattern
 * @return array
 * @throws ImapException
 *
 */
function imap_getsubscribed(\IMAP\Connection $imap, string $reference, string $pattern): array
{
    error_clear_last();
    $safeResult = \imap_getsubscribed($imap, $reference, $pattern);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param int $message_num
 * @param int $from_length
 * @param int $subject_length
 * @return \stdClass
 * @throws ImapException
 *
 */
function imap_headerinfo(\IMAP\Connection $imap, int $message_num, int $from_length = 0, int $subject_length = 0): \stdClass
{
    error_clear_last();
    $safeResult = \imap_headerinfo($imap, $message_num, $from_length, $subject_length);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @return array
 * @throws ImapException
 *
 */
function imap_headers(\IMAP\Connection $imap): array
{
    error_clear_last();
    $safeResult = \imap_headers($imap);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $reference
 * @param string $pattern
 * @param string $content
 * @return array
 * @throws ImapException
 *
 */
function imap_listscan(\IMAP\Connection $imap, string $reference, string $pattern, string $content): array
{
    error_clear_last();
    $safeResult = \imap_listscan($imap, $reference, $pattern, $content);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $reference
 * @param string $pattern
 * @return array
 * @throws ImapException
 *
 */
function imap_lsub(\IMAP\Connection $imap, string $reference, string $pattern): array
{
    error_clear_last();
    $safeResult = \imap_lsub($imap, $reference, $pattern);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param array $envelope
 * @param array $bodies
 * @return string
 * @throws ImapException
 *
 */
function imap_mail_compose(array $envelope, array $bodies): string
{
    error_clear_last();
    $safeResult = \imap_mail_compose($envelope, $bodies);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $message_nums
 * @param string $mailbox
 * @param int $flags
 * @throws ImapException
 *
 */
function imap_mail_copy(\IMAP\Connection $imap, string $message_nums, string $mailbox, int $flags = 0): void
{
    error_clear_last();
    $safeResult = \imap_mail_copy($imap, $message_nums, $mailbox, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param string $message_nums
 * @param string $mailbox
 * @param int $flags
 * @throws ImapException
 *
 */
function imap_mail_move(\IMAP\Connection $imap, string $message_nums, string $mailbox, int $flags = 0): void
{
    error_clear_last();
    $safeResult = \imap_mail_move($imap, $message_nums, $mailbox, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param string $to
 * @param string $subject
 * @param string $message
 * @param null|string $additional_headers
 * @param null|string $cc
 * @param null|string $bcc
 * @param null|string $return_path
 * @throws ImapException
 *
 */
function imap_mail(string $to, string $subject, string $message, ?string $additional_headers = null, ?string $cc = null, ?string $bcc = null, ?string $return_path = null): void
{
    error_clear_last();
    if ($return_path !== null) {
        $safeResult = \imap_mail($to, $subject, $message, $additional_headers, $cc, $bcc, $return_path);
    } elseif ($bcc !== null) {
        $safeResult = \imap_mail($to, $subject, $message, $additional_headers, $cc, $bcc);
    } elseif ($cc !== null) {
        $safeResult = \imap_mail($to, $subject, $message, $additional_headers, $cc);
    } elseif ($additional_headers !== null) {
        $safeResult = \imap_mail($to, $subject, $message, $additional_headers);
    } else {
        $safeResult = \imap_mail($to, $subject, $message);
    }
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @return \stdClass
 * @throws ImapException
 *
 */
function imap_mailboxmsginfo(\IMAP\Connection $imap): \stdClass
{
    error_clear_last();
    $safeResult = \imap_mailboxmsginfo($imap);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $string
 * @return array
 * @throws ImapException
 *
 */
function imap_mime_header_decode(string $string): array
{
    error_clear_last();
    $safeResult = \imap_mime_header_decode($string);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $string
 * @return string
 * @throws ImapException
 *
 */
function imap_mutf7_to_utf8(string $string): string
{
    error_clear_last();
    $safeResult = \imap_mutf7_to_utf8($string);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @return int
 * @throws ImapException
 *
 */
function imap_num_msg(\IMAP\Connection $imap): int
{
    error_clear_last();
    $safeResult = \imap_num_msg($imap);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $mailbox
 * @param string $user
 * @param string $password
 * @param int $flags
 * @param int $retries
 * @param array $options
 * @return \IMAP\Connection
 * @throws ImapException
 *
 */
function imap_open(string $mailbox, string $user, string $password, int $flags = 0, int $retries = 0, array $options = []): \IMAP\Connection
{
    error_clear_last();
    $safeResult = \imap_open($mailbox, $user, $password, $flags, $retries, $options);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $string
 * @return string
 * @throws ImapException
 *
 */
function imap_qprint(string $string): string
{
    error_clear_last();
    $safeResult = \imap_qprint($string);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $from
 * @param string $to
 * @throws ImapException
 *
 */
function imap_renamemailbox(\IMAP\Connection $imap, string $from, string $to): void
{
    error_clear_last();
    $safeResult = \imap_renamemailbox($imap, $from, $to);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param null|string $mailbox
 * @param null|string $hostname
 * @param null|string $personal
 * @return string
 * @throws ImapException
 *
 */
function imap_rfc822_write_address(?string $mailbox, ?string $hostname, ?string $personal): string
{
    error_clear_last();
    $safeResult = \imap_rfc822_write_address($mailbox, $hostname, $personal);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param int|resource|string $file
 * @param int $message_num
 * @param string $section
 * @param int $flags
 * @throws ImapException
 *
 */
function imap_savebody(\IMAP\Connection $imap, $file, int $message_num, string $section = "", int $flags = 0): void
{
    error_clear_last();
    $safeResult = \imap_savebody($imap, $file, $message_num, $section, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param string $quota_root
 * @param int $mailbox_size
 * @throws ImapException
 *
 */
function imap_set_quota(\IMAP\Connection $imap, string $quota_root, int $mailbox_size): void
{
    error_clear_last();
    $safeResult = \imap_set_quota($imap, $quota_root, $mailbox_size);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param string $mailbox
 * @param string $user_id
 * @param string $rights
 * @throws ImapException
 *
 */
function imap_setacl(\IMAP\Connection $imap, string $mailbox, string $user_id, string $rights): void
{
    error_clear_last();
    $safeResult = \imap_setacl($imap, $mailbox, $user_id, $rights);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param string $sequence
 * @param string $flag
 * @param int $options
 * @throws ImapException
 *
 */
function imap_setflag_full(\IMAP\Connection $imap, string $sequence, string $flag, int $options = 0): void
{
    error_clear_last();
    $safeResult = \imap_setflag_full($imap, $sequence, $flag, $options);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param int $criteria
 * @param int $reverse
 * @param int $flags
 * @param null|string $search_criteria
 * @param null|string $charset
 * @return array
 * @throws ImapException
 *
 */
function imap_sort(\IMAP\Connection $imap, int $criteria, int $reverse, int $flags = 0, ?string $search_criteria = null, ?string $charset = null): array
{
    error_clear_last();
    if ($charset !== null) {
        $safeResult = \imap_sort($imap, $criteria, $reverse, $flags, $search_criteria, $charset);
    } elseif ($search_criteria !== null) {
        $safeResult = \imap_sort($imap, $criteria, $reverse, $flags, $search_criteria);
    } else {
        $safeResult = \imap_sort($imap, $criteria, $reverse, $flags);
    }
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $mailbox
 * @param int $flags
 * @return \stdClass
 * @throws ImapException
 *
 */
function imap_status(\IMAP\Connection $imap, string $mailbox, int $flags): \stdClass
{
    error_clear_last();
    $safeResult = \imap_status($imap, $mailbox, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $mailbox
 * @throws ImapException
 *
 */
function imap_subscribe(\IMAP\Connection $imap, string $mailbox): void
{
    error_clear_last();
    $safeResult = \imap_subscribe($imap, $mailbox);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param int $flags
 * @return array
 * @throws ImapException
 *
 */
function imap_thread(\IMAP\Connection $imap, int $flags = SE_FREE): array
{
    error_clear_last();
    $safeResult = \imap_thread($imap, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $timeout_type
 * @param int $timeout
 * @return mixed
 * @throws ImapException
 *
 */
function imap_timeout(int $timeout_type, int $timeout = -1)
{
    error_clear_last();
    $safeResult = \imap_timeout($timeout_type, $timeout);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $message_nums
 * @param int $flags
 * @throws ImapException
 *
 */
function imap_undelete(\IMAP\Connection $imap, string $message_nums, int $flags = 0): void
{
    error_clear_last();
    $safeResult = \imap_undelete($imap, $message_nums, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param string $mailbox
 * @throws ImapException
 *
 */
function imap_unsubscribe(\IMAP\Connection $imap, string $mailbox): void
{
    error_clear_last();
    $safeResult = \imap_unsubscribe($imap, $mailbox);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param string $string
 * @return string
 * @throws ImapException
 *
 */
function imap_utf8_to_mutf7(string $string): string
{
    error_clear_last();
    $safeResult = \imap_utf8_to_mutf7($string);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\ErrorfuncException;

/**
 * @param string $message
 * @param 0|1|2|3|4 $message_type
 * @param null|string $destination
 * @param null|string $additional_headers
 * @throws ErrorfuncException
 *
 */
function error_log(string $message, int $message_type = 0, ?string $destination = null, ?string $additional_headers = null): void
{
    error_clear_last();
    if ($additional_headers !== null) {
        $safeResult = \error_log($message, $message_type, $destination, $additional_headers);
    } elseif ($destination !== null) {
        $safeResult = \error_log($message, $message_type, $destination);
    } else {
        $safeResult = \error_log($message, $message_type);
    }
    if ($safeResult === false) {
        throw ErrorfuncException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\MysqliException;

/**
 * @return array|false
 *
 */
function mysqli_get_client_stats()
{
    error_clear_last();
    $safeResult = \mysqli_get_client_stats();
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\FilesystemException;

/**
 * @param string $filename
 * @param int|string $group
 * @throws FilesystemException
 *
 */
function chgrp(string $filename, $group): void
{
    error_clear_last();
    $safeResult = \chgrp($filename, $group);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @param int $permissions
 * @throws FilesystemException
 *
 */
function chmod(string $filename, int $permissions): void
{
    error_clear_last();
    $safeResult = \chmod($filename, $permissions);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @param int|string $user
 * @throws FilesystemException
 *
 */
function chown(string $filename, $user): void
{
    error_clear_last();
    $safeResult = \chown($filename, $user);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param string $from
 * @param string $to
 * @param null|resource $context
 * @throws FilesystemException
 *
 */
function copy(string $from, string $to, $context = null): void
{
    error_clear_last();
    if ($context !== null) {
        $safeResult = \copy($from, $to, $context);
    } else {
        $safeResult = \copy($from, $to);
    }
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param string $directory
 * @return float
 * @throws FilesystemException
 *
 */
function disk_free_space(string $directory): float
{
    error_clear_last();
    $safeResult = \disk_free_space($directory);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $directory
 * @return float
 * @throws FilesystemException
 *
 */
function disk_total_space(string $directory): float
{
    error_clear_last();
    $safeResult = \disk_total_space($directory);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @throws FilesystemException
 *
 */
function fclose($stream): void
{
    error_clear_last();
    $safeResult = \fclose($stream);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param resource $stream
 * @throws FilesystemException
 *
 */
function fdatasync($stream): void
{
    error_clear_last();
    $safeResult = \fdatasync($stream);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param resource $stream
 * @throws FilesystemException
 *
 */
function fflush($stream): void
{
    error_clear_last();
    $safeResult = \fflush($stream);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @param bool $use_include_path
 * @param null|resource $context
 * @param int $offset
 * @param 0|null|positive-int $length
 * @return string
 * @throws FilesystemException
 *
 */
function file_get_contents(string $filename, bool $use_include_path = false, $context = null, int $offset = 0, ?int $length = null): string
{
    error_clear_last();
    if ($length !== null) {
        $safeResult = \file_get_contents($filename, $use_include_path, $context, $offset, $length);
    } elseif ($offset !== 0) {
        $safeResult = \file_get_contents($filename, $use_include_path, $context, $offset);
    } elseif ($context !== null) {
        $safeResult = \file_get_contents($filename, $use_include_path, $context);
    } else {
        $safeResult = \file_get_contents($filename, $use_include_path);
    }
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param mixed $data
 * @param int $flags
 * @param null|resource $context
 * @return 0|positive-int
 * @throws FilesystemException
 *
 */
function file_put_contents(string $filename, $data, int $flags = 0, $context = null): int
{
    error_clear_last();
    if ($context !== null) {
        $safeResult = \file_put_contents($filename, $data, $flags, $context);
    } else {
        $safeResult = \file_put_contents($filename, $data, $flags);
    }
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param int-mask $flags
 * @param null|resource $context
 * @return list
 * @throws FilesystemException
 *
 */
function file(string $filename, int $flags = 0, $context = null): array
{
    error_clear_last();
    if ($context !== null) {
        $safeResult = \file($filename, $flags, $context);
    } else {
        $safeResult = \file($filename, $flags);
    }
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return int
 * @throws FilesystemException
 *
 */
function fileatime(string $filename): int
{
    error_clear_last();
    $safeResult = \fileatime($filename);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return int
 * @throws FilesystemException
 *
 */
function filectime(string $filename): int
{
    error_clear_last();
    $safeResult = \filectime($filename);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return int
 * @throws FilesystemException
 *
 */
function fileinode(string $filename): int
{
    error_clear_last();
    $safeResult = \fileinode($filename);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return int
 * @throws FilesystemException
 *
 */
function filemtime(string $filename): int
{
    error_clear_last();
    $safeResult = \filemtime($filename);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return int
 * @throws FilesystemException
 *
 */
function fileowner(string $filename): int
{
    error_clear_last();
    $safeResult = \fileowner($filename);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return int
 * @throws FilesystemException
 *
 */
function fileperms(string $filename): int
{
    error_clear_last();
    $safeResult = \fileperms($filename);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return 0|positive-int
 * @throws FilesystemException
 *
 */
function filesize(string $filename): int
{
    error_clear_last();
    $safeResult = \filesize($filename);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return string
 * @throws FilesystemException
 *
 */
function filetype(string $filename): string
{
    error_clear_last();
    $safeResult = \filetype($filename);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @param int-mask $operation
 * @param 0|1|null $would_block
 * @throws FilesystemException
 *
 */
function flock($stream, int $operation, ?int &$would_block = null): void
{
    error_clear_last();
    $safeResult = \flock($stream, $operation, $would_block);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @param string $mode
 * @param bool $use_include_path
 * @param null|resource $context
 * @return resource
 * @throws FilesystemException
 *
 */
function fopen(string $filename, string $mode, bool $use_include_path = false, $context = null)
{
    error_clear_last();
    if ($context !== null) {
        $safeResult = \fopen($filename, $mode, $use_include_path, $context);
    } else {
        $safeResult = \fopen($filename, $mode, $use_include_path);
    }
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @param positive-int $length
 * @return string
 * @throws FilesystemException
 *
 */
function fread($stream, int $length): string
{
    error_clear_last();
    $safeResult = \fread($stream, $length);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @return array
 * @throws FilesystemException
 *
 */
function fstat($stream): array
{
    error_clear_last();
    $safeResult = \fstat($stream);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @throws FilesystemException
 *
 */
function fsync($stream): void
{
    error_clear_last();
    $safeResult = \fsync($stream);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param resource $stream
 * @return int
 * @throws FilesystemException
 *
 */
function ftell($stream): int
{
    error_clear_last();
    $safeResult = \ftell($stream);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @param 0|positive-int $size
 * @throws FilesystemException
 *
 */
function ftruncate($stream, int $size): void
{
    error_clear_last();
    $safeResult = \ftruncate($stream, $size);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param resource $stream
 * @param string $data
 * @param 0|null|positive-int $length
 * @return 0|positive-int
 * @throws FilesystemException
 *
 */
function fwrite($stream, string $data, ?int $length = null): int
{
    error_clear_last();
    if ($length !== null) {
        $safeResult = \fwrite($stream, $data, $length);
    } else {
        $safeResult = \fwrite($stream, $data);
    }
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $pattern
 * @param int $flags
 * @return list
 * @throws FilesystemException
 *
 */
function glob(string $pattern, int $flags = 0): array
{
    error_clear_last();
    $safeResult = \glob($pattern, $flags);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param int|string $group
 * @throws FilesystemException
 *
 */
function lchgrp(string $filename, $group): void
{
    error_clear_last();
    $safeResult = \lchgrp($filename, $group);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @param int|string $user
 * @throws FilesystemException
 *
 */
function lchown(string $filename, $user): void
{
    error_clear_last();
    $safeResult = \lchown($filename, $user);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param string $target
 * @param string $link
 * @throws FilesystemException
 *
 */
function link(string $target, string $link): void
{
    error_clear_last();
    $safeResult = \link($target, $link);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @return array
 * @throws FilesystemException
 *
 */
function lstat(string $filename): array
{
    error_clear_last();
    $safeResult = \lstat($filename);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $directory
 * @param int $permissions
 * @param bool $recursive
 * @param null|resource $context
 * @throws FilesystemException
 *
 */
function mkdir(string $directory, int $permissions = 0777, bool $recursive = false, $context = null): void
{
    error_clear_last();
    if ($context !== null) {
        $safeResult = \mkdir($directory, $permissions, $recursive, $context);
    } else {
        $safeResult = \mkdir($directory, $permissions, $recursive);
    }
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @param bool $process_sections
 * @param int $scanner_mode
 * @return array
 * @throws FilesystemException
 *
 */
function parse_ini_file(string $filename, bool $process_sections = false, int $scanner_mode = INI_SCANNER_NORMAL): array
{
    error_clear_last();
    $safeResult = \parse_ini_file($filename, $process_sections, $scanner_mode);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $ini_string
 * @param bool $process_sections
 * @param int $scanner_mode
 * @return array
 * @throws FilesystemException
 *
 */
function parse_ini_string(string $ini_string, bool $process_sections = false, int $scanner_mode = INI_SCANNER_NORMAL): array
{
    error_clear_last();
    $safeResult = \parse_ini_string($ini_string, $process_sections, $scanner_mode);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $handle
 * @return int
 * @throws FilesystemException
 *
 */
function pclose($handle): int
{
    error_clear_last();
    $safeResult = \pclose($handle);
    if ($safeResult === -1) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $command
 * @param string $mode
 * @return resource
 * @throws FilesystemException
 *
 */
function popen(string $command, string $mode)
{
    error_clear_last();
    $safeResult = \popen($command, $mode);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param bool $use_include_path
 * @param null|resource $context
 * @return 0|positive-int
 * @throws FilesystemException
 *
 */
function readfile(string $filename, bool $use_include_path = false, $context = null): int
{
    error_clear_last();
    if ($context !== null) {
        $safeResult = \readfile($filename, $use_include_path, $context);
    } else {
        $safeResult = \readfile($filename, $use_include_path);
    }
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $path
 * @return string
 * @throws FilesystemException
 *
 */
function readlink(string $path): string
{
    error_clear_last();
    $safeResult = \readlink($path);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $path
 * @return non-empty-string
 * @throws FilesystemException
 *
 */
function realpath(string $path): string
{
    error_clear_last();
    $safeResult = \realpath($path);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $from
 * @param string $to
 * @param null|resource $context
 * @throws FilesystemException
 *
 */
function rename(string $from, string $to, $context = null): void
{
    error_clear_last();
    if ($context !== null) {
        $safeResult = \rename($from, $to, $context);
    } else {
        $safeResult = \rename($from, $to);
    }
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param resource $stream
 * @throws FilesystemException
 *
 */
function rewind($stream): void
{
    error_clear_last();
    $safeResult = \rewind($stream);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param string $directory
 * @param null|resource $context
 * @throws FilesystemException
 *
 */
function rmdir(string $directory, $context = null): void
{
    error_clear_last();
    if ($context !== null) {
        $safeResult = \rmdir($directory, $context);
    } else {
        $safeResult = \rmdir($directory);
    }
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param string $target
 * @param string $link
 * @throws FilesystemException
 *
 */
function symlink(string $target, string $link): void
{
    error_clear_last();
    $safeResult = \symlink($target, $link);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param string $directory
 * @param string $prefix
 * @return non-falsy-string
 * @throws FilesystemException
 *
 */
function tempnam(string $directory, string $prefix): string
{
    error_clear_last();
    $safeResult = \tempnam($directory, $prefix);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return resource
 * @throws FilesystemException
 *
 */
function tmpfile()
{
    error_clear_last();
    $safeResult = \tmpfile();
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param int|null $mtime
 * @param int|null $atime
 * @throws FilesystemException
 *
 */
function touch(string $filename, ?int $mtime = null, ?int $atime = null): void
{
    error_clear_last();
    if ($atime !== null) {
        $safeResult = \touch($filename, $mtime, $atime);
    } elseif ($mtime !== null) {
        $safeResult = \touch($filename, $mtime);
    } else {
        $safeResult = \touch($filename);
    }
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @param null|resource $context
 * @throws FilesystemException
 *
 */
function unlink(string $filename, $context = null): void
{
    error_clear_last();
    if ($context !== null) {
        $safeResult = \unlink($filename, $context);
    } else {
        $safeResult = \unlink($filename);
    }
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\ArrayException;

/**
 * @param array $keys
 * @param array $values
 * @return array
 *
 */
function array_combine(array $keys, array $values): array
{
    error_clear_last();
    $safeResult = \array_combine($keys, $values);
    return $safeResult;
}


/**
 * @param array $array
 * @return array
 *
 */
function array_flip(array $array): array
{
    error_clear_last();
    $safeResult = \array_flip($array);
    return $safeResult;
}


/**
 * @param array $array
 * @param array $replacements
 * @return array
 *
 */
function array_replace_recursive(array $array, array ...$replacements): array
{
    error_clear_last();
    if ($replacements !== []) {
        $safeResult = \array_replace_recursive($array, ...$replacements);
    } else {
        $safeResult = \array_replace_recursive($array);
    }
    return $safeResult;
}


/**
 * @param array $array
 * @param array $replacements
 * @return array
 *
 */
function array_replace(array $array, array ...$replacements): array
{
    error_clear_last();
    if ($replacements !== []) {
        $safeResult = \array_replace($array, ...$replacements);
    } else {
        $safeResult = \array_replace($array);
    }
    return $safeResult;
}


/**
 * @param array|object $array
 * @param callable $callback
 * @param mixed $arg
 * @throws ArrayException
 *
 */
function array_walk_recursive(&$array, callable $callback, $arg = null): void
{
    error_clear_last();
    if ($arg !== null) {
        $safeResult = \array_walk_recursive($array, $callback, $arg);
    } else {
        $safeResult = \array_walk_recursive($array, $callback);
    }
    if ($safeResult === false) {
        throw ArrayException::createFromPhpError();
    }
}


/**
 * @param array $array
 * @throws ArrayException
 *
 */
function shuffle(array &$array): void
{
    error_clear_last();
    $safeResult = \shuffle($array);
    if ($safeResult === false) {
        throw ArrayException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\GnupgException;

/**
 * @param resource $identifier
 * @param string $fingerprint
 * @param string $passphrase
 * @throws GnupgException
 *
 */
function gnupg_adddecryptkey($identifier, string $fingerprint, string $passphrase): void
{
    error_clear_last();
    $safeResult = \gnupg_adddecryptkey($identifier, $fingerprint, $passphrase);
    if ($safeResult === false) {
        throw GnupgException::createFromPhpError();
    }
}


/**
 * @param resource $identifier
 * @param string $fingerprint
 * @throws GnupgException
 *
 */
function gnupg_addencryptkey($identifier, string $fingerprint): void
{
    error_clear_last();
    $safeResult = \gnupg_addencryptkey($identifier, $fingerprint);
    if ($safeResult === false) {
        throw GnupgException::createFromPhpError();
    }
}


/**
 * @param resource $identifier
 * @param string $fingerprint
 * @param string $passphrase
 * @throws GnupgException
 *
 */
function gnupg_addsignkey($identifier, string $fingerprint, ?string $passphrase = null): void
{
    error_clear_last();
    if ($passphrase !== null) {
        $safeResult = \gnupg_addsignkey($identifier, $fingerprint, $passphrase);
    } else {
        $safeResult = \gnupg_addsignkey($identifier, $fingerprint);
    }
    if ($safeResult === false) {
        throw GnupgException::createFromPhpError();
    }
}


/**
 * @param resource $identifier
 * @throws GnupgException
 *
 */
function gnupg_cleardecryptkeys($identifier): void
{
    error_clear_last();
    $safeResult = \gnupg_cleardecryptkeys($identifier);
    if ($safeResult === false) {
        throw GnupgException::createFromPhpError();
    }
}


/**
 * @param resource $identifier
 * @throws GnupgException
 *
 */
function gnupg_clearencryptkeys($identifier): void
{
    error_clear_last();
    $safeResult = \gnupg_clearencryptkeys($identifier);
    if ($safeResult === false) {
        throw GnupgException::createFromPhpError();
    }
}


/**
 * @param resource $identifier
 * @throws GnupgException
 *
 */
function gnupg_clearsignkeys($identifier): void
{
    error_clear_last();
    $safeResult = \gnupg_clearsignkeys($identifier);
    if ($safeResult === false) {
        throw GnupgException::createFromPhpError();
    }
}


/**
 * @param resource $identifier
 * @param string $key
 * @param bool $allow_secret
 * @throws GnupgException
 *
 */
function gnupg_deletekey($identifier, string $key, bool $allow_secret): void
{
    error_clear_last();
    $safeResult = \gnupg_deletekey($identifier, $key, $allow_secret);
    if ($safeResult === false) {
        throw GnupgException::createFromPhpError();
    }
}


/**
 * @param resource $identifier
 * @param int $armor
 * @throws GnupgException
 *
 */
function gnupg_setarmor($identifier, int $armor): void
{
    error_clear_last();
    $safeResult = \gnupg_setarmor($identifier, $armor);
    if ($safeResult === false) {
        throw GnupgException::createFromPhpError();
    }
}


/**
 * @param resource $identifier
 * @param int $signmode
 * @throws GnupgException
 *
 */
function gnupg_setsignmode($identifier, int $signmode): void
{
    error_clear_last();
    $safeResult = \gnupg_setsignmode($identifier, $signmode);
    if ($safeResult === false) {
        throw GnupgException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\GmpException;

/**
 * @param \GMP|int|string $seed
 *
 */
function gmp_random_seed($seed): void
{
    error_clear_last();
    $safeResult = \gmp_random_seed($seed);
}
<?php

namespace Safe;

use Safe\Exceptions\RnpException;

/**
 * @param \RnpFFI $ffi
 * @param string $input
 * @return string
 * @throws RnpException
 *
 */
function rnp_decrypt(\RnpFFI $ffi, string $input): string
{
    error_clear_last();
    $safeResult = \rnp_decrypt($ffi, $input);
    if ($safeResult === false) {
        throw RnpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $input
 * @param int $flags
 * @return string
 * @throws RnpException
 *
 */
function rnp_dump_packets_to_json(string $input, int $flags): string
{
    error_clear_last();
    $safeResult = \rnp_dump_packets_to_json($input, $flags);
    if ($safeResult === false) {
        throw RnpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $input
 * @param int $flags
 * @return string
 * @throws RnpException
 *
 */
function rnp_dump_packets(string $input, int $flags): string
{
    error_clear_last();
    $safeResult = \rnp_dump_packets($input, $flags);
    if ($safeResult === false) {
        throw RnpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $pub_format
 * @param string $sec_format
 * @return \RnpFFI
 * @throws RnpException
 *
 */
function rnp_ffi_create(string $pub_format, string $sec_format): \RnpFFI
{
    error_clear_last();
    $safeResult = \rnp_ffi_create($pub_format, $sec_format);
    if ($safeResult === false) {
        throw RnpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \RnpFFI $ffi
 * @param callable $password_callback
 * @throws RnpException
 *
 */
function rnp_ffi_set_pass_provider(\RnpFFI $ffi, callable $password_callback): void
{
    error_clear_last();
    $safeResult = \rnp_ffi_set_pass_provider($ffi, $password_callback);
    if ($safeResult === false) {
        throw RnpException::createFromPhpError();
    }
}


/**
 * @param \RnpFFI $ffi
 * @param string $input
 * @param int $flags
 * @return string
 * @throws RnpException
 *
 */
function rnp_import_keys(\RnpFFI $ffi, string $input, int $flags): string
{
    error_clear_last();
    $safeResult = \rnp_import_keys($ffi, $input, $flags);
    if ($safeResult === false) {
        throw RnpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \RnpFFI $ffi
 * @param string $input
 * @param int $flags
 * @return string
 * @throws RnpException
 *
 */
function rnp_import_signatures(\RnpFFI $ffi, string $input, int $flags): string
{
    error_clear_last();
    $safeResult = \rnp_import_signatures($ffi, $input, $flags);
    if ($safeResult === false) {
        throw RnpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \RnpFFI $ffi
 * @param string $key_fp
 * @param string $subkey_fp
 * @param string $uid
 * @param int $flags
 * @return string
 * @throws RnpException
 *
 */
function rnp_key_export_autocrypt(\RnpFFI $ffi, string $key_fp, string $subkey_fp, string $uid, int $flags): string
{
    error_clear_last();
    $safeResult = \rnp_key_export_autocrypt($ffi, $key_fp, $subkey_fp, $uid, $flags);
    if ($safeResult === false) {
        throw RnpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \RnpFFI $ffi
 * @param string $key_fp
 * @param int $flags
 * @param array $options
 * @return string
 * @throws RnpException
 *
 */
function rnp_key_export_revocation(\RnpFFI $ffi, string $key_fp, int $flags, ?array $options = null): string
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \rnp_key_export_revocation($ffi, $key_fp, $flags, $options);
    } else {
        $safeResult = \rnp_key_export_revocation($ffi, $key_fp, $flags);
    }
    if ($safeResult === false) {
        throw RnpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \RnpFFI $ffi
 * @param string $key_fp
 * @param int $flags
 * @return string
 * @throws RnpException
 *
 */
function rnp_key_export(\RnpFFI $ffi, string $key_fp, int $flags): string
{
    error_clear_last();
    $safeResult = \rnp_key_export($ffi, $key_fp, $flags);
    if ($safeResult === false) {
        throw RnpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \RnpFFI $ffi
 * @param string $key_fp
 * @return array
 * @throws RnpException
 *
 */
function rnp_key_get_info(\RnpFFI $ffi, string $key_fp): array
{
    error_clear_last();
    $safeResult = \rnp_key_get_info($ffi, $key_fp);
    if ($safeResult === false) {
        throw RnpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \RnpFFI $ffi
 * @param string $key_fp
 * @param int $flags
 * @throws RnpException
 *
 */
function rnp_key_remove(\RnpFFI $ffi, string $key_fp, int $flags): void
{
    error_clear_last();
    $safeResult = \rnp_key_remove($ffi, $key_fp, $flags);
    if ($safeResult === false) {
        throw RnpException::createFromPhpError();
    }
}


/**
 * @param \RnpFFI $ffi
 * @param string $key_fp
 * @param int $flags
 * @param array $options
 * @throws RnpException
 *
 */
function rnp_key_revoke(\RnpFFI $ffi, string $key_fp, int $flags, ?array $options = null): void
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \rnp_key_revoke($ffi, $key_fp, $flags, $options);
    } else {
        $safeResult = \rnp_key_revoke($ffi, $key_fp, $flags);
    }
    if ($safeResult === false) {
        throw RnpException::createFromPhpError();
    }
}


/**
 * @param \RnpFFI $ffi
 * @param string $identifier_type
 * @return array
 * @throws RnpException
 *
 */
function rnp_list_keys(\RnpFFI $ffi, string $identifier_type): array
{
    error_clear_last();
    $safeResult = \rnp_list_keys($ffi, $identifier_type);
    if ($safeResult === false) {
        throw RnpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \RnpFFI $ffi
 * @param string $format
 * @param string $input_path
 * @param int $flags
 * @throws RnpException
 *
 */
function rnp_load_keys_from_path(\RnpFFI $ffi, string $format, string $input_path, int $flags): void
{
    error_clear_last();
    $safeResult = \rnp_load_keys_from_path($ffi, $format, $input_path, $flags);
    if ($safeResult === false) {
        throw RnpException::createFromPhpError();
    }
}


/**
 * @param \RnpFFI $ffi
 * @param string $format
 * @param string $input
 * @param int $flags
 * @throws RnpException
 *
 */
function rnp_load_keys(\RnpFFI $ffi, string $format, string $input, int $flags): void
{
    error_clear_last();
    $safeResult = \rnp_load_keys($ffi, $format, $input, $flags);
    if ($safeResult === false) {
        throw RnpException::createFromPhpError();
    }
}


/**
 * @param \RnpFFI $ffi
 * @param string $identifier_type
 * @param string $identifier
 * @return string
 * @throws RnpException
 *
 */
function rnp_locate_key(\RnpFFI $ffi, string $identifier_type, string $identifier): string
{
    error_clear_last();
    $safeResult = \rnp_locate_key($ffi, $identifier_type, $identifier);
    if ($safeResult === false) {
        throw RnpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \RnpFFI $ffi
 * @param string $message
 * @param array $recipient_keys_fp
 * @param array $options
 * @return string
 * @throws RnpException
 *
 */
function rnp_op_encrypt(\RnpFFI $ffi, string $message, array $recipient_keys_fp, ?array $options = null): string
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \rnp_op_encrypt($ffi, $message, $recipient_keys_fp, $options);
    } else {
        $safeResult = \rnp_op_encrypt($ffi, $message, $recipient_keys_fp);
    }
    if ($safeResult === false) {
        throw RnpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \RnpFFI $ffi
 * @param string $userid
 * @param string $key_alg
 * @param string $sub_alg
 * @param array $options
 * @return string
 * @throws RnpException
 *
 */
function rnp_op_generate_key(\RnpFFI $ffi, string $userid, string $key_alg, ?string $sub_alg = null, ?array $options = null): string
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \rnp_op_generate_key($ffi, $userid, $key_alg, $sub_alg, $options);
    } elseif ($sub_alg !== null) {
        $safeResult = \rnp_op_generate_key($ffi, $userid, $key_alg, $sub_alg);
    } else {
        $safeResult = \rnp_op_generate_key($ffi, $userid, $key_alg);
    }
    if ($safeResult === false) {
        throw RnpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \RnpFFI $ffi
 * @param string $data
 * @param array $keys_fp
 * @param array $options
 * @return string
 * @throws RnpException
 *
 */
function rnp_op_sign_cleartext(\RnpFFI $ffi, string $data, array $keys_fp, ?array $options = null): string
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \rnp_op_sign_cleartext($ffi, $data, $keys_fp, $options);
    } else {
        $safeResult = \rnp_op_sign_cleartext($ffi, $data, $keys_fp);
    }
    if ($safeResult === false) {
        throw RnpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \RnpFFI $ffi
 * @param string $data
 * @param array $keys_fp
 * @param array $options
 * @return string
 * @throws RnpException
 *
 */
function rnp_op_sign_detached(\RnpFFI $ffi, string $data, array $keys_fp, ?array $options = null): string
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \rnp_op_sign_detached($ffi, $data, $keys_fp, $options);
    } else {
        $safeResult = \rnp_op_sign_detached($ffi, $data, $keys_fp);
    }
    if ($safeResult === false) {
        throw RnpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \RnpFFI $ffi
 * @param string $data
 * @param array $keys_fp
 * @param array $options
 * @return string
 * @throws RnpException
 *
 */
function rnp_op_sign(\RnpFFI $ffi, string $data, array $keys_fp, ?array $options = null): string
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \rnp_op_sign($ffi, $data, $keys_fp, $options);
    } else {
        $safeResult = \rnp_op_sign($ffi, $data, $keys_fp);
    }
    if ($safeResult === false) {
        throw RnpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \RnpFFI $ffi
 * @param string $data
 * @param string $signature
 * @return array
 * @throws RnpException
 *
 */
function rnp_op_verify_detached(\RnpFFI $ffi, string $data, string $signature): array
{
    error_clear_last();
    $safeResult = \rnp_op_verify_detached($ffi, $data, $signature);
    if ($safeResult === false) {
        throw RnpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \RnpFFI $ffi
 * @param string $data
 * @return array
 * @throws RnpException
 *
 */
function rnp_op_verify(\RnpFFI $ffi, string $data): array
{
    error_clear_last();
    $safeResult = \rnp_op_verify($ffi, $data);
    if ($safeResult === false) {
        throw RnpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \RnpFFI $ffi
 * @param string $format
 * @param string $output_path
 * @param int $flags
 * @throws RnpException
 *
 */
function rnp_save_keys_to_path(\RnpFFI $ffi, string $format, string $output_path, int $flags): void
{
    error_clear_last();
    $safeResult = \rnp_save_keys_to_path($ffi, $format, $output_path, $flags);
    if ($safeResult === false) {
        throw RnpException::createFromPhpError();
    }
}


/**
 * @param \RnpFFI $ffi
 * @param string $format
 * @param string $output
 * @param int $flags
 * @throws RnpException
 *
 */
function rnp_save_keys(\RnpFFI $ffi, string $format, string &$output, int $flags): void
{
    error_clear_last();
    $safeResult = \rnp_save_keys($ffi, $format, $output, $flags);
    if ($safeResult === false) {
        throw RnpException::createFromPhpError();
    }
}


/**
 * @param string $type
 * @return string
 * @throws RnpException
 *
 */
function rnp_supported_features(string $type): string
{
    error_clear_last();
    $safeResult = \rnp_supported_features($type);
    if ($safeResult === false) {
        throw RnpException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\Ssh2Exception;

/**
 * @param resource $session
 * @param string $username
 * @throws Ssh2Exception
 *
 */
function ssh2_auth_agent($session, string $username): void
{
    error_clear_last();
    $safeResult = \ssh2_auth_agent($session, $username);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param resource $session
 * @param string $username
 * @param string $hostname
 * @param string $pubkeyfile
 * @param string $privkeyfile
 * @param string $passphrase
 * @param string $local_username
 * @throws Ssh2Exception
 *
 */
function ssh2_auth_hostbased_file($session, string $username, string $hostname, string $pubkeyfile, string $privkeyfile, ?string $passphrase = null, ?string $local_username = null): void
{
    error_clear_last();
    if ($local_username !== null) {
        $safeResult = \ssh2_auth_hostbased_file($session, $username, $hostname, $pubkeyfile, $privkeyfile, $passphrase, $local_username);
    } elseif ($passphrase !== null) {
        $safeResult = \ssh2_auth_hostbased_file($session, $username, $hostname, $pubkeyfile, $privkeyfile, $passphrase);
    } else {
        $safeResult = \ssh2_auth_hostbased_file($session, $username, $hostname, $pubkeyfile, $privkeyfile);
    }
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param resource $session
 * @param string $username
 * @param string $password
 * @throws Ssh2Exception
 *
 */
function ssh2_auth_password($session, string $username, string $password): void
{
    error_clear_last();
    $safeResult = \ssh2_auth_password($session, $username, $password);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param resource $session
 * @param string $username
 * @param string $pubkeyfile
 * @param string $privkeyfile
 * @param string $passphrase
 * @throws Ssh2Exception
 *
 */
function ssh2_auth_pubkey_file($session, string $username, string $pubkeyfile, string $privkeyfile, ?string $passphrase = null): void
{
    error_clear_last();
    if ($passphrase !== null) {
        $safeResult = \ssh2_auth_pubkey_file($session, $username, $pubkeyfile, $privkeyfile, $passphrase);
    } else {
        $safeResult = \ssh2_auth_pubkey_file($session, $username, $pubkeyfile, $privkeyfile);
    }
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param string $host
 * @param int $port
 * @param array $methods
 * @param array $callbacks
 * @return resource
 * @throws Ssh2Exception
 *
 */
function ssh2_connect(string $host, int $port = 22, ?array $methods = null, ?array $callbacks = null)
{
    error_clear_last();
    if ($callbacks !== null) {
        $safeResult = \ssh2_connect($host, $port, $methods, $callbacks);
    } elseif ($methods !== null) {
        $safeResult = \ssh2_connect($host, $port, $methods);
    } else {
        $safeResult = \ssh2_connect($host, $port);
    }
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $session
 * @throws Ssh2Exception
 *
 */
function ssh2_disconnect($session): void
{
    error_clear_last();
    $safeResult = \ssh2_disconnect($session);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param resource $session
 * @param string $command
 * @param string $pty
 * @param array $env
 * @param int $width
 * @param int $height
 * @param int $width_height_type
 * @return resource
 * @throws Ssh2Exception
 *
 */
function ssh2_exec($session, string $command, ?string $pty = null, ?array $env = null, int $width = 80, int $height = 25, int $width_height_type = SSH2_TERM_UNIT_CHARS)
{
    error_clear_last();
    if ($width_height_type !== SSH2_TERM_UNIT_CHARS) {
        $safeResult = \ssh2_exec($session, $command, $pty, $env, $width, $height, $width_height_type);
    } elseif ($height !== 25) {
        $safeResult = \ssh2_exec($session, $command, $pty, $env, $width, $height);
    } elseif ($width !== 80) {
        $safeResult = \ssh2_exec($session, $command, $pty, $env, $width);
    } elseif ($env !== null) {
        $safeResult = \ssh2_exec($session, $command, $pty, $env);
    } elseif ($pty !== null) {
        $safeResult = \ssh2_exec($session, $command, $pty);
    } else {
        $safeResult = \ssh2_exec($session, $command);
    }
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $listener
 * @return resource
 * @throws Ssh2Exception
 *
 */
function ssh2_forward_accept($listener)
{
    error_clear_last();
    $safeResult = \ssh2_forward_accept($listener);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $session
 * @param int $port
 * @param string $host
 * @param int $max_connections
 * @return resource
 * @throws Ssh2Exception
 *
 */
function ssh2_forward_listen($session, int $port, ?string $host = null, int $max_connections = 16)
{
    error_clear_last();
    if ($max_connections !== 16) {
        $safeResult = \ssh2_forward_listen($session, $port, $host, $max_connections);
    } elseif ($host !== null) {
        $safeResult = \ssh2_forward_listen($session, $port, $host);
    } else {
        $safeResult = \ssh2_forward_listen($session, $port);
    }
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $pkey
 * @param string $algoname
 * @param string $blob
 * @param bool $overwrite
 * @param array $attributes
 * @throws Ssh2Exception
 *
 */
function ssh2_publickey_add($pkey, string $algoname, string $blob, bool $overwrite = false, ?array $attributes = null): void
{
    error_clear_last();
    if ($attributes !== null) {
        $safeResult = \ssh2_publickey_add($pkey, $algoname, $blob, $overwrite, $attributes);
    } else {
        $safeResult = \ssh2_publickey_add($pkey, $algoname, $blob, $overwrite);
    }
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param resource $session
 * @return resource
 * @throws Ssh2Exception
 *
 */
function ssh2_publickey_init($session)
{
    error_clear_last();
    $safeResult = \ssh2_publickey_init($session);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $pkey
 * @param string $algoname
 * @param string $blob
 * @throws Ssh2Exception
 *
 */
function ssh2_publickey_remove($pkey, string $algoname, string $blob): void
{
    error_clear_last();
    $safeResult = \ssh2_publickey_remove($pkey, $algoname, $blob);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param resource $session
 * @param string $remote_file
 * @param string $local_file
 * @throws Ssh2Exception
 *
 */
function ssh2_scp_recv($session, string $remote_file, string $local_file): void
{
    error_clear_last();
    $safeResult = \ssh2_scp_recv($session, $remote_file, $local_file);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param resource $session
 * @param string $local_file
 * @param string $remote_file
 * @param int $create_mode
 * @throws Ssh2Exception
 *
 */
function ssh2_scp_send($session, string $local_file, string $remote_file, int $create_mode = 0644): void
{
    error_clear_last();
    $safeResult = \ssh2_scp_send($session, $local_file, $remote_file, $create_mode);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param resource $channel
 * @throws Ssh2Exception
 *
 */
function ssh2_send_eof($channel): void
{
    error_clear_last();
    $safeResult = \ssh2_send_eof($channel);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param resource $sftp
 * @param string $filename
 * @param int $mode
 * @throws Ssh2Exception
 *
 */
function ssh2_sftp_chmod($sftp, string $filename, int $mode): void
{
    error_clear_last();
    $safeResult = \ssh2_sftp_chmod($sftp, $filename, $mode);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param resource $sftp
 * @param string $dirname
 * @param int $mode
 * @param bool $recursive
 * @throws Ssh2Exception
 *
 */
function ssh2_sftp_mkdir($sftp, string $dirname, int $mode = 0777, bool $recursive = false): void
{
    error_clear_last();
    $safeResult = \ssh2_sftp_mkdir($sftp, $dirname, $mode, $recursive);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param resource $sftp
 * @param string $from
 * @param string $to
 * @throws Ssh2Exception
 *
 */
function ssh2_sftp_rename($sftp, string $from, string $to): void
{
    error_clear_last();
    $safeResult = \ssh2_sftp_rename($sftp, $from, $to);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param resource $sftp
 * @param string $dirname
 * @throws Ssh2Exception
 *
 */
function ssh2_sftp_rmdir($sftp, string $dirname): void
{
    error_clear_last();
    $safeResult = \ssh2_sftp_rmdir($sftp, $dirname);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param resource $sftp
 * @param string $target
 * @param string $link
 * @throws Ssh2Exception
 *
 */
function ssh2_sftp_symlink($sftp, string $target, string $link): void
{
    error_clear_last();
    $safeResult = \ssh2_sftp_symlink($sftp, $target, $link);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param resource $sftp
 * @param string $filename
 * @throws Ssh2Exception
 *
 */
function ssh2_sftp_unlink($sftp, string $filename): void
{
    error_clear_last();
    $safeResult = \ssh2_sftp_unlink($sftp, $filename);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param resource $session
 * @return resource
 * @throws Ssh2Exception
 *
 */
function ssh2_sftp($session)
{
    error_clear_last();
    $safeResult = \ssh2_sftp($session);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $session
 * @param string $termtype
 * @param array|null $env
 * @param int $width
 * @param int $height
 * @param int $width_height_type
 * @return resource
 * @throws Ssh2Exception
 *
 */
function ssh2_shell($session, string $termtype = "vanilla", ?array $env = null, int $width = 80, int $height = 25, int $width_height_type = SSH2_TERM_UNIT_CHARS)
{
    error_clear_last();
    if ($width_height_type !== SSH2_TERM_UNIT_CHARS) {
        $safeResult = \ssh2_shell($session, $termtype, $env, $width, $height, $width_height_type);
    } elseif ($height !== 25) {
        $safeResult = \ssh2_shell($session, $termtype, $env, $width, $height);
    } elseif ($width !== 80) {
        $safeResult = \ssh2_shell($session, $termtype, $env, $width);
    } elseif ($env !== null) {
        $safeResult = \ssh2_shell($session, $termtype, $env);
    } else {
        $safeResult = \ssh2_shell($session, $termtype);
    }
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\HashException;

/**
 * @param string $algo
 * @param string $key
 * @param int $length
 * @param string $info
 * @param string $salt
 * @return false|non-falsy-string
 *
 */
function hash_hkdf(string $algo, string $key, int $length = 0, string $info = "", string $salt = "")
{
    error_clear_last();
    $safeResult = \hash_hkdf($algo, $key, $length, $info, $salt);
    return $safeResult;
}


/**
 * @param \HashContext $context
 * @param string $filename
 * @param \HashContext|null $stream_context
 * @throws HashException
 *
 */
function hash_update_file(\HashContext $context, string $filename, ?\HashContext $stream_context = null): void
{
    error_clear_last();
    if ($stream_context !== null) {
        $safeResult = \hash_update_file($context, $filename, $stream_context);
    } else {
        $safeResult = \hash_update_file($context, $filename);
    }
    if ($safeResult === false) {
        throw HashException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\MiscException;

/**
 * @param string $constant_name
 * @param mixed $value
 * @param bool $case_insensitive
 * @throws MiscException
 *
 */
function define(string $constant_name, $value, bool $case_insensitive = false): void
{
    error_clear_last();
    $safeResult = \define($constant_name, $value, $case_insensitive);
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @param bool $return
 * @return bool|string
 * @throws MiscException
 *
 */
function highlight_file(string $filename, bool $return = false)
{
    error_clear_last();
    $safeResult = \highlight_file($filename, $return);
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $string
 * @param bool $return
 * @return bool|string
 * @throws MiscException
 *
 */
function highlight_string(string $string, bool $return = false)
{
    error_clear_last();
    $safeResult = \highlight_string($string, $return);
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param bool $as_number
 * @return array{0:int,1:int}|float|int
 * @throws MiscException
 *
 */
function hrtime(bool $as_number = false)
{
    error_clear_last();
    $safeResult = \hrtime($as_number);
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $format
 * @param mixed $values
 * @return string
 * @throws MiscException
 *
 */
function pack(string $format, ...$values): string
{
    error_clear_last();
    if ($values !== []) {
        $safeResult = \pack($format, ...$values);
    } else {
        $safeResult = \pack($format);
    }
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int|string $in_codepage
 * @param int|string $out_codepage
 * @param string $subject
 * @return string
 * @throws MiscException
 *
 */
function sapi_windows_cp_conv($in_codepage, $out_codepage, string $subject): string
{
    error_clear_last();
    $safeResult = \sapi_windows_cp_conv($in_codepage, $out_codepage, $subject);
    if ($safeResult === null) {
        throw MiscException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $codepage
 * @throws MiscException
 *
 */
function sapi_windows_cp_set(int $codepage): void
{
    error_clear_last();
    $safeResult = \sapi_windows_cp_set($codepage);
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
}


/**
 * @param int $event
 * @param int $pid
 * @throws MiscException
 *
 */
function sapi_windows_generate_ctrl_event(int $event, int $pid = 0): void
{
    error_clear_last();
    $safeResult = \sapi_windows_generate_ctrl_event($event, $pid);
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
}


/**
 * @param callable|null $handler
 * @param bool $add
 * @throws MiscException
 *
 */
function sapi_windows_set_ctrl_handler(?callable $handler, bool $add = true): void
{
    error_clear_last();
    $safeResult = \sapi_windows_set_ctrl_handler($handler, $add);
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
}


/**
 * @param resource $stream
 * @param bool|null $enable
 * @throws MiscException
 *
 */
function sapi_windows_vt100_support($stream, ?bool $enable = null): void
{
    error_clear_last();
    if ($enable !== null) {
        $safeResult = \sapi_windows_vt100_support($stream, $enable);
    } else {
        $safeResult = \sapi_windows_vt100_support($stream);
    }
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
}


/**
 * @param int $seconds
 * @return false|int
 *
 */
function sleep(int $seconds)
{
    error_clear_last();
    $safeResult = \sleep($seconds);
    return $safeResult;
}


/**
 * @param int $seconds
 * @param int $nanoseconds
 * @return array{seconds:0|positive-int,nanoseconds:0|positive-int}|bool
 * @throws MiscException
 *
 */
function time_nanosleep(int $seconds, int $nanoseconds)
{
    error_clear_last();
    $safeResult = \time_nanosleep($seconds, $nanoseconds);
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param float $timestamp
 * @throws MiscException
 *
 */
function time_sleep_until(float $timestamp): void
{
    error_clear_last();
    $safeResult = \time_sleep_until($timestamp);
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
}


/**
 * @param string $format
 * @param string $string
 * @param int $offset
 * @return array
 * @throws MiscException
 *
 */
function unpack(string $format, string $string, int $offset = 0): array
{
    error_clear_last();
    $safeResult = \unpack($format, $string, $offset);
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\CurlException;

/**
 * @param \CurlHandle $handle
 * @return \CurlHandle
 * @throws CurlException
 *
 */
function curl_copy_handle(\CurlHandle $handle): \CurlHandle
{
    error_clear_last();
    $safeResult = \curl_copy_handle($handle);
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($handle);
    }
    return $safeResult;
}


/**
 * @param \CurlHandle $handle
 * @param string $string
 * @return string
 * @throws CurlException
 *
 */
function curl_escape(\CurlHandle $handle, string $string): string
{
    error_clear_last();
    $safeResult = \curl_escape($handle, $string);
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($handle);
    }
    return $safeResult;
}


/**
 * @param \CurlHandle $handle
 * @return bool|string
 * @throws CurlException
 *
 */
function curl_exec(\CurlHandle $handle)
{
    error_clear_last();
    $safeResult = \curl_exec($handle);
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($handle);
    }
    return $safeResult;
}


/**
 * @param \CurlHandle $handle
 * @param int|null $option
 * @return mixed
 * @throws CurlException
 *
 */
function curl_getinfo(\CurlHandle $handle, ?int $option = null)
{
    error_clear_last();
    if ($option !== null) {
        $safeResult = \curl_getinfo($handle, $option);
    } else {
        $safeResult = \curl_getinfo($handle);
    }
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($handle);
    }
    return $safeResult;
}


/**
 * @param null|string $url
 * @return \CurlHandle
 * @throws CurlException
 *
 */
function curl_init(?string $url = null): \CurlHandle
{
    error_clear_last();
    if ($url !== null) {
        $safeResult = \curl_init($url);
    } else {
        $safeResult = \curl_init();
    }
    if ($safeResult === false) {
        throw CurlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \CurlMultiHandle $multi_handle
 * @param int|null $queued_messages
 * @return array
 * @throws CurlException
 *
 */
function curl_multi_info_read(\CurlMultiHandle $multi_handle, ?int &$queued_messages = null): array
{
    error_clear_last();
    $safeResult = \curl_multi_info_read($multi_handle, $queued_messages);
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($multi_handle);
    }
    return $safeResult;
}


/**
 * @return \CurlMultiHandle
 * @throws CurlException
 *
 */
function curl_multi_init(): \CurlMultiHandle
{
    error_clear_last();
    $safeResult = \curl_multi_init();
    if ($safeResult === false) {
        throw CurlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \CurlMultiHandle $multi_handle
 * @param int $option
 * @param mixed $value
 * @throws CurlException
 *
 */
function curl_multi_setopt(\CurlMultiHandle $multi_handle, int $option, $value): void
{
    error_clear_last();
    $safeResult = \curl_multi_setopt($multi_handle, $option, $value);
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($multi_handle);
    }
}


/**
 * @param \CurlHandle $handle
 * @param int $option
 * @param mixed $value
 * @throws CurlException
 *
 */
function curl_setopt(\CurlHandle $handle, int $option, $value): void
{
    error_clear_last();
    $safeResult = \curl_setopt($handle, $option, $value);
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($handle);
    }
}


/**
 * @param \CurlShareHandle $share_handle
 * @return int
 * @throws CurlException
 *
 */
function curl_share_errno(\CurlShareHandle $share_handle): int
{
    error_clear_last();
    $safeResult = \curl_share_errno($share_handle);
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($share_handle);
    }
    return $safeResult;
}


/**
 * @param \CurlShareHandle $share_handle
 * @param int $option
 * @param mixed $value
 * @throws CurlException
 *
 */
function curl_share_setopt(\CurlShareHandle $share_handle, int $option, $value): void
{
    error_clear_last();
    $safeResult = \curl_share_setopt($share_handle, $option, $value);
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($share_handle);
    }
}


/**
 * @param \CurlHandle $handle
 * @param string $string
 * @return string
 * @throws CurlException
 *
 */
function curl_unescape(\CurlHandle $handle, string $string): string
{
    error_clear_last();
    $safeResult = \curl_unescape($handle, $string);
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($handle);
    }
    return $safeResult;
}


/**
 * @param \CurlHandle $handle
 * @throws CurlException
 *
 */
function curl_upkeep(\CurlHandle $handle): void
{
    error_clear_last();
    $safeResult = \curl_upkeep($handle);
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($handle);
    }
}
<?php

namespace Safe;

use Safe\Exceptions\SodiumException;

/**
 * @param string $ciphertext
 * @param string $additional_data
 * @param string $nonce
 * @param string $key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_aead_aes256gcm_decrypt(string $ciphertext, string $additional_data, string $nonce, string $key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $additional_data, $nonce, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $ciphertext
 * @param string $additional_data
 * @param string $nonce
 * @param string $key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_aead_chacha20poly1305_decrypt(string $ciphertext, string $additional_data, string $nonce, string $key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_aead_chacha20poly1305_decrypt($ciphertext, $additional_data, $nonce, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $message
 * @param string $additional_data
 * @param string $nonce
 * @param string $key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_aead_chacha20poly1305_encrypt(string $message, string $additional_data, string $nonce, string $key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_aead_chacha20poly1305_encrypt($message, $additional_data, $nonce, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $ciphertext
 * @param string $additional_data
 * @param string $nonce
 * @param string $key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_aead_chacha20poly1305_ietf_decrypt(string $ciphertext, string $additional_data, string $nonce, string $key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_aead_chacha20poly1305_ietf_decrypt($ciphertext, $additional_data, $nonce, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $message
 * @param string $additional_data
 * @param string $nonce
 * @param string $key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_aead_chacha20poly1305_ietf_encrypt(string $message, string $additional_data, string $nonce, string $key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_aead_chacha20poly1305_ietf_encrypt($message, $additional_data, $nonce, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $ciphertext
 * @param string $additional_data
 * @param string $nonce
 * @param string $key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_aead_xchacha20poly1305_ietf_decrypt(string $ciphertext, string $additional_data, string $nonce, string $key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_aead_xchacha20poly1305_ietf_decrypt($ciphertext, $additional_data, $nonce, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $message
 * @param string $additional_data
 * @param string $nonce
 * @param string $key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_aead_xchacha20poly1305_ietf_encrypt(string $message, string $additional_data, string $nonce, string $key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_aead_xchacha20poly1305_ietf_encrypt($message, $additional_data, $nonce, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $mac
 * @param string $message
 * @param string $key
 * @throws SodiumException
 *
 */
function sodium_crypto_auth_verify(string $mac, string $message, string $key): void
{
    error_clear_last();
    $safeResult = \sodium_crypto_auth_verify($mac, $message, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
}


/**
 * @param string $ciphertext
 * @param string $nonce
 * @param string $key_pair
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_box_open(string $ciphertext, string $nonce, string $key_pair): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_box_open($ciphertext, $nonce, $key_pair);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $ciphertext
 * @param string $key_pair
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_box_seal_open(string $ciphertext, string $key_pair): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_box_seal_open($ciphertext, $key_pair);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param non-empty-string $state
 * @param string $message
 * @throws SodiumException
 *
 */
function sodium_crypto_generichash_update(string &$state, string $message): void
{
    error_clear_last();
    $safeResult = \sodium_crypto_generichash_update($state, $message);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
}


/**
 * @param string $ciphertext
 * @param string $nonce
 * @param string $key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_secretbox_open(string $ciphertext, string $nonce, string $key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_secretbox_open($ciphertext, $nonce, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $signed_message
 * @param non-empty-string $public_key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_sign_open(string $signed_message, string $public_key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_sign_open($signed_message, $public_key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param non-empty-string $signature
 * @param string $message
 * @param non-empty-string $public_key
 * @throws SodiumException
 *
 */
function sodium_crypto_sign_verify_detached(string $signature, string $message, string $public_key): void
{
    error_clear_last();
    $safeResult = \sodium_crypto_sign_verify_detached($signature, $message, $public_key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
}


/**
 * @param string $message
 * @param string $nonce
 * @param int $counter
 * @param string $key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_stream_xchacha20_xor_ic(string $message, string $nonce, int $counter, string $key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_stream_xchacha20_xor_ic($message, $nonce, $counter, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\ImageException;

/**
 * @param string $filename
 * @param array|null $image_info
 * @return array{0: 0|positive-int, 1: 0|positive-int, 2: int, 3: string, mime: string, channels: int, bits: int}|null
 * @throws ImageException
 *
 */
function getimagesize(string $filename, ?array &$image_info = null): ?array
{
    error_clear_last();
    $safeResult = \getimagesize($filename, $image_info);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $image_type
 * @param bool $include_dot
 * @return string
 * @throws ImageException
 *
 */
function image_type_to_extension(int $image_type, bool $include_dot = true): string
{
    error_clear_last();
    $safeResult = \image_type_to_extension($image_type, $include_dot);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param array $affine
 * @param array|null $clip
 * @return \GdImage
 * @throws ImageException
 *
 */
function imageaffine(\GdImage $image, array $affine, ?array $clip = null): \GdImage
{
    error_clear_last();
    if ($clip !== null) {
        $safeResult = \imageaffine($image, $affine, $clip);
    } else {
        $safeResult = \imageaffine($image, $affine);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param array $matrix1
 * @param array $matrix2
 * @return array{0:float,1:float,2:float,3:float,4:float,5:float}
 * @throws ImageException
 *
 */
function imageaffinematrixconcat(array $matrix1, array $matrix2): array
{
    error_clear_last();
    $safeResult = \imageaffinematrixconcat($matrix1, $matrix2);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $type
 * @param array|float $options
 * @return array{0:float,1:float,2:float,3:float,4:float,5:float}
 * @throws ImageException
 *
 */
function imageaffinematrixget(int $type, $options): array
{
    error_clear_last();
    $safeResult = \imageaffinematrixget($type, $options);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param bool $enable
 * @throws ImageException
 *
 */
function imagealphablending(\GdImage $image, bool $enable): void
{
    error_clear_last();
    $safeResult = \imagealphablending($image, $enable);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param bool $enable
 * @throws ImageException
 *
 */
function imageantialias(\GdImage $image, bool $enable): void
{
    error_clear_last();
    $safeResult = \imageantialias($image, $enable);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $center_x
 * @param int $center_y
 * @param int $width
 * @param int $height
 * @param int $start_angle
 * @param int $end_angle
 * @param int $color
 * @throws ImageException
 *
 */
function imagearc(\GdImage $image, int $center_x, int $center_y, int $width, int $height, int $start_angle, int $end_angle, int $color): void
{
    error_clear_last();
    $safeResult = \imagearc($image, $center_x, $center_y, $width, $height, $start_angle, $end_angle, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param null|resource|string $file
 * @param int $quality
 * @param int $speed
 * @throws ImageException
 *
 */
function imageavif(\GdImage $image, $file = null, int $quality = -1, int $speed = -1): void
{
    error_clear_last();
    if ($speed !== -1) {
        $safeResult = \imageavif($image, $file, $quality, $speed);
    } elseif ($quality !== -1) {
        $safeResult = \imageavif($image, $file, $quality);
    } elseif ($file !== null) {
        $safeResult = \imageavif($image, $file);
    } else {
        $safeResult = \imageavif($image);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param null|resource|string $file
 * @param bool $compressed
 * @throws ImageException
 *
 */
function imagebmp(\GdImage $image, $file = null, bool $compressed = true): void
{
    error_clear_last();
    if ($compressed !== true) {
        $safeResult = \imagebmp($image, $file, $compressed);
    } elseif ($file !== null) {
        $safeResult = \imagebmp($image, $file);
    } else {
        $safeResult = \imagebmp($image);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $font
 * @param int $x
 * @param int $y
 * @param string $char
 * @param int $color
 * @throws ImageException
 *
 */
function imagechar(\GdImage $image, int $font, int $x, int $y, string $char, int $color): void
{
    error_clear_last();
    $safeResult = \imagechar($image, $font, $x, $y, $char, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $font
 * @param int $x
 * @param int $y
 * @param string $char
 * @param int $color
 * @throws ImageException
 *
 */
function imagecharup(\GdImage $image, int $font, int $x, int $y, string $char, int $color): void
{
    error_clear_last();
    $safeResult = \imagecharup($image, $font, $x, $y, $char, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $x
 * @param int $y
 * @return int
 * @throws ImageException
 *
 */
function imagecolorat(\GdImage $image, int $x, int $y): int
{
    error_clear_last();
    $safeResult = \imagecolorat($image, $x, $y);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param int $color
 * @throws ImageException
 *
 */
function imagecolordeallocate(\GdImage $image, int $color): void
{
    error_clear_last();
    $safeResult = \imagecolordeallocate($image, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image1
 * @param \GdImage $image2
 * @throws ImageException
 *
 */
function imagecolormatch(\GdImage $image1, \GdImage $image2): void
{
    error_clear_last();
    $safeResult = \imagecolormatch($image1, $image2);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $color
 * @param int $red
 * @param int $green
 * @param int $blue
 * @param int $alpha
 * @throws ImageException
 *
 */
function imagecolorset(\GdImage $image, int $color, int $red, int $green, int $blue, int $alpha = 0): void
{
    error_clear_last();
    $safeResult = \imagecolorset($image, $color, $red, $green, $blue, $alpha);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $color
 * @return array{red: int, green: int, blue: int, alpha: int}
 *
 */
function imagecolorsforindex(\GdImage $image, int $color): array
{
    error_clear_last();
    $safeResult = \imagecolorsforindex($image, $color);
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param array $matrix
 * @param float $divisor
 * @param float $offset
 * @throws ImageException
 *
 */
function imageconvolution(\GdImage $image, array $matrix, float $divisor, float $offset): void
{
    error_clear_last();
    $safeResult = \imageconvolution($image, $matrix, $divisor, $offset);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $dst_image
 * @param \GdImage $src_image
 * @param int $dst_x
 * @param int $dst_y
 * @param int $src_x
 * @param int $src_y
 * @param int $src_width
 * @param int $src_height
 * @throws ImageException
 *
 */
function imagecopy(\GdImage $dst_image, \GdImage $src_image, int $dst_x, int $dst_y, int $src_x, int $src_y, int $src_width, int $src_height): void
{
    error_clear_last();
    $safeResult = \imagecopy($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $src_width, $src_height);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $dst_image
 * @param \GdImage $src_image
 * @param int $dst_x
 * @param int $dst_y
 * @param int $src_x
 * @param int $src_y
 * @param int $src_width
 * @param int $src_height
 * @param int $pct
 * @throws ImageException
 *
 */
function imagecopymerge(\GdImage $dst_image, \GdImage $src_image, int $dst_x, int $dst_y, int $src_x, int $src_y, int $src_width, int $src_height, int $pct): void
{
    error_clear_last();
    $safeResult = \imagecopymerge($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $src_width, $src_height, $pct);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $dst_image
 * @param \GdImage $src_image
 * @param int $dst_x
 * @param int $dst_y
 * @param int $src_x
 * @param int $src_y
 * @param int $src_width
 * @param int $src_height
 * @param int $pct
 * @throws ImageException
 *
 */
function imagecopymergegray(\GdImage $dst_image, \GdImage $src_image, int $dst_x, int $dst_y, int $src_x, int $src_y, int $src_width, int $src_height, int $pct): void
{
    error_clear_last();
    $safeResult = \imagecopymergegray($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $src_width, $src_height, $pct);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $dst_image
 * @param \GdImage $src_image
 * @param int $dst_x
 * @param int $dst_y
 * @param int $src_x
 * @param int $src_y
 * @param int $dst_width
 * @param int $dst_height
 * @param int $src_width
 * @param int $src_height
 * @throws ImageException
 *
 */
function imagecopyresampled(\GdImage $dst_image, \GdImage $src_image, int $dst_x, int $dst_y, int $src_x, int $src_y, int $dst_width, int $dst_height, int $src_width, int $src_height): void
{
    error_clear_last();
    $safeResult = \imagecopyresampled($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_width, $dst_height, $src_width, $src_height);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $dst_image
 * @param \GdImage $src_image
 * @param int $dst_x
 * @param int $dst_y
 * @param int $src_x
 * @param int $src_y
 * @param int $dst_width
 * @param int $dst_height
 * @param int $src_width
 * @param int $src_height
 * @throws ImageException
 *
 */
function imagecopyresized(\GdImage $dst_image, \GdImage $src_image, int $dst_x, int $dst_y, int $src_x, int $src_y, int $dst_width, int $dst_height, int $src_width, int $src_height): void
{
    error_clear_last();
    $safeResult = \imagecopyresized($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_width, $dst_height, $src_width, $src_height);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param int $width
 * @param int $height
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreate(int $width, int $height): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreate($width, $height);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromavif(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromavif($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefrombmp(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefrombmp($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromgd(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromgd($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromgd2(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromgd2($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param int $x
 * @param int $y
 * @param int $width
 * @param int $height
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromgd2part(string $filename, int $x, int $y, int $width, int $height): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromgd2part($filename, $x, $y, $width, $height);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromgif(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromgif($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromjpeg(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromjpeg($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefrompng(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefrompng($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromstring(string $data): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromstring($data);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromtga(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromtga($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromwbmp(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromwbmp($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromwebp(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromwebp($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromxbm(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromxbm($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromxpm(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromxpm($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $width
 * @param int $height
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatetruecolor(int $width, int $height): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatetruecolor($width, $height);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param array $rectangle
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecrop(\GdImage $image, array $rectangle): \GdImage
{
    error_clear_last();
    $safeResult = \imagecrop($image, $rectangle);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param int $mode
 * @param float $threshold
 * @param int $color
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecropauto(\GdImage $image, int $mode = IMG_CROP_DEFAULT, float $threshold = 0.5, int $color = -1): \GdImage
{
    error_clear_last();
    $safeResult = \imagecropauto($image, $mode, $threshold, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param int $x1
 * @param int $y1
 * @param int $x2
 * @param int $y2
 * @param int $color
 * @throws ImageException
 *
 */
function imagedashedline(\GdImage $image, int $x1, int $y1, int $x2, int $y2, int $color): void
{
    error_clear_last();
    $safeResult = \imagedashedline($image, $x1, $y1, $x2, $y2, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @throws ImageException
 *
 */
function imagedestroy(\GdImage $image): void
{
    error_clear_last();
    $safeResult = \imagedestroy($image);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $center_x
 * @param int $center_y
 * @param int $width
 * @param int $height
 * @param int $color
 * @throws ImageException
 *
 */
function imageellipse(\GdImage $image, int $center_x, int $center_y, int $width, int $height, int $color): void
{
    error_clear_last();
    $safeResult = \imageellipse($image, $center_x, $center_y, $width, $height, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $x
 * @param int $y
 * @param int $color
 * @throws ImageException
 *
 */
function imagefill(\GdImage $image, int $x, int $y, int $color): void
{
    error_clear_last();
    $safeResult = \imagefill($image, $x, $y, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $center_x
 * @param int $center_y
 * @param int $width
 * @param int $height
 * @param int $start_angle
 * @param int $end_angle
 * @param int $color
 * @param int $style
 * @throws ImageException
 *
 */
function imagefilledarc(\GdImage $image, int $center_x, int $center_y, int $width, int $height, int $start_angle, int $end_angle, int $color, int $style): void
{
    error_clear_last();
    $safeResult = \imagefilledarc($image, $center_x, $center_y, $width, $height, $start_angle, $end_angle, $color, $style);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $center_x
 * @param int $center_y
 * @param int $width
 * @param int $height
 * @param int $color
 * @throws ImageException
 *
 */
function imagefilledellipse(\GdImage $image, int $center_x, int $center_y, int $width, int $height, int $color): void
{
    error_clear_last();
    $safeResult = \imagefilledellipse($image, $center_x, $center_y, $width, $height, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $x1
 * @param int $y1
 * @param int $x2
 * @param int $y2
 * @param int $color
 * @throws ImageException
 *
 */
function imagefilledrectangle(\GdImage $image, int $x1, int $y1, int $x2, int $y2, int $color): void
{
    error_clear_last();
    $safeResult = \imagefilledrectangle($image, $x1, $y1, $x2, $y2, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $x
 * @param int $y
 * @param int $border_color
 * @param int $color
 * @throws ImageException
 *
 */
function imagefilltoborder(\GdImage $image, int $x, int $y, int $border_color, int $color): void
{
    error_clear_last();
    $safeResult = \imagefilltoborder($image, $x, $y, $border_color, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $filter
 * @param int $args
 * @throws ImageException
 *
 */
function imagefilter(\GdImage $image, int $filter, int ...$args): void
{
    error_clear_last();
    if ($args !== []) {
        $safeResult = \imagefilter($image, $filter, ...$args);
    } else {
        $safeResult = \imagefilter($image, $filter);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $mode
 * @throws ImageException
 *
 */
function imageflip(\GdImage $image, int $mode): void
{
    error_clear_last();
    $safeResult = \imageflip($image, $mode);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param float $size
 * @param float $angle
 * @param string $font_filename
 * @param string $string
 * @param array $options
 * @return array
 * @throws ImageException
 *
 */
function imageftbbox(float $size, float $angle, string $font_filename, string $string, array $options = []): array
{
    error_clear_last();
    $safeResult = \imageftbbox($size, $angle, $font_filename, $string, $options);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param float $size
 * @param float $angle
 * @param int $x
 * @param int $y
 * @param int $color
 * @param string $font_filename
 * @param string $text
 * @param array $options
 * @return array
 * @throws ImageException
 *
 */
function imagefttext(\GdImage $image, float $size, float $angle, int $x, int $y, int $color, string $font_filename, string $text, array $options = []): array
{
    error_clear_last();
    $safeResult = \imagefttext($image, $size, $angle, $x, $y, $color, $font_filename, $text, $options);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param float $input_gamma
 * @param float $output_gamma
 * @throws ImageException
 *
 */
function imagegammacorrect(\GdImage $image, float $input_gamma, float $output_gamma): void
{
    error_clear_last();
    $safeResult = \imagegammacorrect($image, $input_gamma, $output_gamma);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param null|resource|string $file
 * @throws ImageException
 *
 */
function imagegd(\GdImage $image, $file = null): void
{
    error_clear_last();
    if ($file !== null) {
        $safeResult = \imagegd($image, $file);
    } else {
        $safeResult = \imagegd($image);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param null|resource|string $file
 * @param int $chunk_size
 * @param int $mode
 * @throws ImageException
 *
 */
function imagegd2(\GdImage $image, $file = null, int $chunk_size = 128, int $mode = IMG_GD2_RAW): void
{
    error_clear_last();
    if ($mode !== IMG_GD2_RAW) {
        $safeResult = \imagegd2($image, $file, $chunk_size, $mode);
    } elseif ($chunk_size !== 128) {
        $safeResult = \imagegd2($image, $file, $chunk_size);
    } elseif ($file !== null) {
        $safeResult = \imagegd2($image, $file);
    } else {
        $safeResult = \imagegd2($image);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param null|resource|string $file
 * @throws ImageException
 *
 */
function imagegif(\GdImage $image, $file = null): void
{
    error_clear_last();
    if ($file !== null) {
        $safeResult = \imagegif($image, $file);
    } else {
        $safeResult = \imagegif($image);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagegrabscreen(): \GdImage
{
    error_clear_last();
    $safeResult = \imagegrabscreen();
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $handle
 * @param bool $client_area
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagegrabwindow(int $handle, bool $client_area = false): \GdImage
{
    error_clear_last();
    $safeResult = \imagegrabwindow($handle, $client_area);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param null|resource|string $file
 * @param int $quality
 * @throws ImageException
 *
 */
function imagejpeg(\GdImage $image, $file = null, int $quality = -1): void
{
    error_clear_last();
    if ($quality !== -1) {
        $safeResult = \imagejpeg($image, $file, $quality);
    } elseif ($file !== null) {
        $safeResult = \imagejpeg($image, $file);
    } else {
        $safeResult = \imagejpeg($image);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $effect
 * @throws ImageException
 *
 */
function imagelayereffect(\GdImage $image, int $effect): void
{
    error_clear_last();
    $safeResult = \imagelayereffect($image, $effect);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $x1
 * @param int $y1
 * @param int $x2
 * @param int $y2
 * @param int $color
 * @throws ImageException
 *
 */
function imageline(\GdImage $image, int $x1, int $y1, int $x2, int $y2, int $color): void
{
    error_clear_last();
    $safeResult = \imageline($image, $x1, $y1, $x2, $y2, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @return int
 * @throws ImageException
 *
 */
function imageloadfont(string $filename): int
{
    error_clear_last();
    $safeResult = \imageloadfont($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param null|resource|string $file
 * @param int $quality
 * @param int $filters
 * @throws ImageException
 *
 */
function imagepng(\GdImage $image, $file = null, int $quality = -1, int $filters = -1): void
{
    error_clear_last();
    if ($filters !== -1) {
        $safeResult = \imagepng($image, $file, $quality, $filters);
    } elseif ($quality !== -1) {
        $safeResult = \imagepng($image, $file, $quality);
    } elseif ($file !== null) {
        $safeResult = \imagepng($image, $file);
    } else {
        $safeResult = \imagepng($image);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $x1
 * @param int $y1
 * @param int $x2
 * @param int $y2
 * @param int $color
 * @throws ImageException
 *
 */
function imagerectangle(\GdImage $image, int $x1, int $y1, int $x2, int $y2, int $color): void
{
    error_clear_last();
    $safeResult = \imagerectangle($image, $x1, $y1, $x2, $y2, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int|null $resolution_x
 * @param int|null $resolution_y
 * @return mixed
 * @throws ImageException
 *
 */
function imageresolution(\GdImage $image, ?int $resolution_x = null, ?int $resolution_y = null)
{
    error_clear_last();
    if ($resolution_y !== null) {
        $safeResult = \imageresolution($image, $resolution_x, $resolution_y);
    } elseif ($resolution_x !== null) {
        $safeResult = \imageresolution($image, $resolution_x);
    } else {
        $safeResult = \imageresolution($image);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param float $angle
 * @param int $background_color
 * @param bool $ignore_transparent
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagerotate(\GdImage $image, float $angle, int $background_color, bool $ignore_transparent = false): \GdImage
{
    error_clear_last();
    $safeResult = \imagerotate($image, $angle, $background_color, $ignore_transparent);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param bool $enable
 * @throws ImageException
 *
 */
function imagesavealpha(\GdImage $image, bool $enable): void
{
    error_clear_last();
    $safeResult = \imagesavealpha($image, $enable);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $width
 * @param int $height
 * @param int $mode
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagescale(\GdImage $image, int $width, int $height = -1, int $mode = IMG_BILINEAR_FIXED): \GdImage
{
    error_clear_last();
    $safeResult = \imagescale($image, $width, $height, $mode);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param \GdImage $brush
 * @throws ImageException
 *
 */
function imagesetbrush(\GdImage $image, \GdImage $brush): void
{
    error_clear_last();
    $safeResult = \imagesetbrush($image, $brush);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $x1
 * @param int $y1
 * @param int $x2
 * @param int $y2
 * @throws ImageException
 *
 */
function imagesetclip(\GdImage $image, int $x1, int $y1, int $x2, int $y2): void
{
    error_clear_last();
    $safeResult = \imagesetclip($image, $x1, $y1, $x2, $y2);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $method
 * @throws ImageException
 *
 */
function imagesetinterpolation(\GdImage $image, int $method = IMG_BILINEAR_FIXED): void
{
    error_clear_last();
    $safeResult = \imagesetinterpolation($image, $method);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $x
 * @param int $y
 * @param int $color
 * @throws ImageException
 *
 */
function imagesetpixel(\GdImage $image, int $x, int $y, int $color): void
{
    error_clear_last();
    $safeResult = \imagesetpixel($image, $x, $y, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param array $style
 * @throws ImageException
 *
 */
function imagesetstyle(\GdImage $image, array $style): void
{
    error_clear_last();
    $safeResult = \imagesetstyle($image, $style);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $thickness
 * @throws ImageException
 *
 */
function imagesetthickness(\GdImage $image, int $thickness): void
{
    error_clear_last();
    $safeResult = \imagesetthickness($image, $thickness);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param \GdImage $tile
 * @throws ImageException
 *
 */
function imagesettile(\GdImage $image, \GdImage $tile): void
{
    error_clear_last();
    $safeResult = \imagesettile($image, $tile);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $font
 * @param int $x
 * @param int $y
 * @param string $string
 * @param int $color
 * @throws ImageException
 *
 */
function imagestring(\GdImage $image, int $font, int $x, int $y, string $string, int $color): void
{
    error_clear_last();
    $safeResult = \imagestring($image, $font, $x, $y, $string, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $font
 * @param int $x
 * @param int $y
 * @param string $string
 * @param int $color
 * @throws ImageException
 *
 */
function imagestringup(\GdImage $image, int $font, int $x, int $y, string $string, int $color): void
{
    error_clear_last();
    $safeResult = \imagestringup($image, $font, $x, $y, $string, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @return int
 * @throws ImageException
 *
 */
function imagesx(\GdImage $image): int
{
    error_clear_last();
    $safeResult = \imagesx($image);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @return int
 * @throws ImageException
 *
 */
function imagesy(\GdImage $image): int
{
    error_clear_last();
    $safeResult = \imagesy($image);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param bool $dither
 * @param int $num_colors
 * @throws ImageException
 *
 */
function imagetruecolortopalette(\GdImage $image, bool $dither, int $num_colors): void
{
    error_clear_last();
    $safeResult = \imagetruecolortopalette($image, $dither, $num_colors);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param float $size
 * @param float $angle
 * @param string $font_filename
 * @param string $string
 * @param array $options
 * @return array
 * @throws ImageException
 *
 */
function imagettfbbox(float $size, float $angle, string $font_filename, string $string, array $options = []): array
{
    error_clear_last();
    $safeResult = \imagettfbbox($size, $angle, $font_filename, $string, $options);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param float $size
 * @param float $angle
 * @param int $x
 * @param int $y
 * @param int $color
 * @param string $font_filename
 * @param string $text
 * @param array $options
 * @return array
 * @throws ImageException
 *
 */
function imagettftext(\GdImage $image, float $size, float $angle, int $x, int $y, int $color, string $font_filename, string $text, array $options = []): array
{
    error_clear_last();
    $safeResult = \imagettftext($image, $size, $angle, $x, $y, $color, $font_filename, $text, $options);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param null|resource|string $file
 * @param int|null $foreground_color
 * @throws ImageException
 *
 */
function imagewbmp(\GdImage $image, $file = null, ?int $foreground_color = null): void
{
    error_clear_last();
    if ($foreground_color !== null) {
        $safeResult = \imagewbmp($image, $file, $foreground_color);
    } elseif ($file !== null) {
        $safeResult = \imagewbmp($image, $file);
    } else {
        $safeResult = \imagewbmp($image);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param null|resource|string $file
 * @param int $quality
 * @throws ImageException
 *
 */
function imagewebp(\GdImage $image, $file = null, int $quality = -1): void
{
    error_clear_last();
    if ($quality !== -1) {
        $safeResult = \imagewebp($image, $file, $quality);
    } elseif ($file !== null) {
        $safeResult = \imagewebp($image, $file);
    } else {
        $safeResult = \imagewebp($image);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param null|resource|string $filename
 * @param int|null $foreground_color
 * @throws ImageException
 *
 */
function imagexbm(\GdImage $image, $filename, ?int $foreground_color = null): void
{
    error_clear_last();
    if ($foreground_color !== null) {
        $safeResult = \imagexbm($image, $filename, $foreground_color);
    } else {
        $safeResult = \imagexbm($image, $filename);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param string $iptc_data
 * @param string $filename
 * @param int $spool
 * @return bool|string
 * @throws ImageException
 *
 */
function iptcembed(string $iptc_data, string $filename, int $spool = 0)
{
    error_clear_last();
    $safeResult = \iptcembed($iptc_data, $filename, $spool);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $iptc_block
 * @return array
 * @throws ImageException
 *
 */
function iptcparse(string $iptc_block): array
{
    error_clear_last();
    $safeResult = \iptcparse($iptc_block);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\ExecException;

/**
 * @param string $command
 * @param array|null $output
 * @param int|null $result_code
 * @return string
 * @throws ExecException
 *
 */
function exec(string $command, ?array &$output = null, ?int &$result_code = null): string
{
    error_clear_last();
    $safeResult = \exec($command, $output, $result_code);
    if ($safeResult === false) {
        throw ExecException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $process
 * @return int
 * @throws ExecException
 *
 */
function proc_close($process): int
{
    error_clear_last();
    $safeResult = \proc_close($process);
    if ($safeResult === -1) {
        throw ExecException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $priority
 * @throws ExecException
 *
 */
function proc_nice(int $priority): void
{
    error_clear_last();
    $safeResult = \proc_nice($priority);
    if ($safeResult === false) {
        throw ExecException::createFromPhpError();
    }
}


/**
 * @param string $command
 * @param array $descriptor_spec
 * @param null|resource[] $pipes
 * @param null|string $cwd
 * @param array|null $env_vars
 * @param array|null $options
 * @return resource
 * @throws ExecException
 *
 */
function proc_open(string $command, array $descriptor_spec, ?array &$pipes, ?string $cwd = null, ?array $env_vars = null, ?array $options = null)
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \proc_open($command, $descriptor_spec, $pipes, $cwd, $env_vars, $options);
    } elseif ($env_vars !== null) {
        $safeResult = \proc_open($command, $descriptor_spec, $pipes, $cwd, $env_vars);
    } elseif ($cwd !== null) {
        $safeResult = \proc_open($command, $descriptor_spec, $pipes, $cwd);
    } else {
        $safeResult = \proc_open($command, $descriptor_spec, $pipes);
    }
    if ($safeResult === false) {
        throw ExecException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $command
 * @return null|string
 * @throws ExecException
 *
 */
function shell_exec(string $command): ?string
{
    error_clear_last();
    $safeResult = \shell_exec($command);
    if ($safeResult === false) {
        throw ExecException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $command
 * @param int|null $result_code
 * @return string
 * @throws ExecException
 *
 */
function system(string $command, ?int &$result_code = null): string
{
    error_clear_last();
    $safeResult = \system($command, $result_code);
    if ($safeResult === false) {
        throw ExecException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\LdapException;

/**
 * @param string $value
 * @return string
 * @throws LdapException
 *
 */
function ldap_8859_to_t61(string $value): string
{
    error_clear_last();
    $safeResult = \ldap_8859_to_t61($value);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param array $entry
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_add(\LDAP\Connection $ldap, string $dn, array $entry, ?array $controls = null): void
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_add($ldap, $dn, $entry, $controls);
    } else {
        $safeResult = \ldap_add($ldap, $dn, $entry);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param null|string $dn
 * @param null|string $password
 * @throws LdapException
 *
 */
function ldap_bind(\LDAP\Connection $ldap, ?string $dn = null, ?string $password = null): void
{
    error_clear_last();
    if ($password !== null) {
        $safeResult = \ldap_bind($ldap, $dn, $password);
    } elseif ($dn !== null) {
        $safeResult = \ldap_bind($ldap, $dn);
    } else {
        $safeResult = \ldap_bind($ldap);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param string $attribute
 * @param string $value
 * @param array|null $controls
 * @return bool
 * @throws LdapException
 *
 */
function ldap_compare(\LDAP\Connection $ldap, string $dn, string $attribute, string $value, ?array $controls = null): bool
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_compare($ldap, $dn, $attribute, $value, $controls);
    } else {
        $safeResult = \ldap_compare($ldap, $dn, $attribute, $value);
    }
    if ($safeResult === -1) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $link
 * @param resource $result
 * @param null|string $cookie
 * @param int|null $estimated
 * @throws LdapException
 *
 */
function ldap_control_paged_result_response($link, $result, ?string &$cookie = null, ?int &$estimated = null): void
{
    error_clear_last();
    $safeResult = \ldap_control_paged_result_response($link, $result, $cookie, $estimated);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param resource $link
 * @param int $pagesize
 * @param bool $iscritical
 * @param string $cookie
 * @throws LdapException
 *
 */
function ldap_control_paged_result($link, int $pagesize, bool $iscritical = false, string $cookie = ""): void
{
    error_clear_last();
    $safeResult = \ldap_control_paged_result($link, $pagesize, $iscritical, $cookie);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\Result $result
 * @return int
 * @throws LdapException
 *
 */
function ldap_count_entries(\LDAP\Connection $ldap, \LDAP\Result $result): int
{
    error_clear_last();
    $safeResult = \ldap_count_entries($ldap, $result);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_delete(\LDAP\Connection $ldap, string $dn, ?array $controls = null): void
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_delete($ldap, $dn, $controls);
    } else {
        $safeResult = \ldap_delete($ldap, $dn);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param string $dn
 * @return string
 * @throws LdapException
 *
 */
function ldap_dn2ufn(string $dn): string
{
    error_clear_last();
    $safeResult = \ldap_dn2ufn($dn);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $user
 * @param string $old_password
 * @param string $new_password
 * @param array|null $controls
 * @return bool|string
 * @throws LdapException
 *
 */
function ldap_exop_passwd(\LDAP\Connection $ldap, string $user = "", string $old_password = "", string $new_password = "", ?array &$controls = null)
{
    error_clear_last();
    $safeResult = \ldap_exop_passwd($ldap, $user, $old_password, $new_password, $controls);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @return bool|string
 * @throws LdapException
 *
 */
function ldap_exop_whoami(\LDAP\Connection $ldap)
{
    error_clear_last();
    $safeResult = \ldap_exop_whoami($ldap);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $request_oid
 * @param null|string $request_data
 * @param array|null $controls
 * @param null|string $response_data
 * @param null|string $response_oid
 * @return bool|resource
 * @throws LdapException
 *
 */
function ldap_exop(\LDAP\Connection $ldap, string $request_oid, ?string $request_data = null, ?array $controls = null, ?string &$response_data = null, ?string &$response_oid = null)
{
    error_clear_last();
    if ($response_oid !== null) {
        $safeResult = \ldap_exop($ldap, $request_oid, $request_data, $controls, $response_data, $response_oid);
    } elseif ($response_data !== null) {
        $safeResult = \ldap_exop($ldap, $request_oid, $request_data, $controls, $response_data);
    } elseif ($controls !== null) {
        $safeResult = \ldap_exop($ldap, $request_oid, $request_data, $controls);
    } elseif ($request_data !== null) {
        $safeResult = \ldap_exop($ldap, $request_oid, $request_data);
    } else {
        $safeResult = \ldap_exop($ldap, $request_oid);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $dn
 * @param int $with_attrib
 * @return array
 * @throws LdapException
 *
 */
function ldap_explode_dn(string $dn, int $with_attrib): array
{
    error_clear_last();
    $safeResult = \ldap_explode_dn($dn, $with_attrib);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\ResultEntry $entry
 * @return string
 * @throws LdapException
 *
 */
function ldap_first_attribute(\LDAP\Connection $ldap, \LDAP\ResultEntry $entry): string
{
    error_clear_last();
    $safeResult = \ldap_first_attribute($ldap, $entry);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\Result $result
 * @return \LDAP\ResultEntry
 * @throws LdapException
 *
 */
function ldap_first_entry(\LDAP\Connection $ldap, \LDAP\Result $result): \LDAP\ResultEntry
{
    error_clear_last();
    $safeResult = \ldap_first_entry($ldap, $result);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Result $result
 * @throws LdapException
 *
 */
function ldap_free_result(\LDAP\Result $result): void
{
    error_clear_last();
    $safeResult = \ldap_free_result($result);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\ResultEntry $entry
 * @return array
 * @throws LdapException
 *
 */
function ldap_get_attributes(\LDAP\Connection $ldap, \LDAP\ResultEntry $entry): array
{
    error_clear_last();
    $safeResult = \ldap_get_attributes($ldap, $entry);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\ResultEntry $entry
 * @return string
 * @throws LdapException
 *
 */
function ldap_get_dn(\LDAP\Connection $ldap, \LDAP\ResultEntry $entry): string
{
    error_clear_last();
    $safeResult = \ldap_get_dn($ldap, $entry);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\Result $result
 * @return array
 * @throws LdapException
 *
 */
function ldap_get_entries(\LDAP\Connection $ldap, \LDAP\Result $result): array
{
    error_clear_last();
    $safeResult = \ldap_get_entries($ldap, $result);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param int $option
 * @param mixed $value
 * @throws LdapException
 *
 */
function ldap_get_option(\LDAP\Connection $ldap, int $option, &$value = null): void
{
    error_clear_last();
    $safeResult = \ldap_get_option($ldap, $option, $value);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\ResultEntry $entry
 * @param string $attribute
 * @return array
 * @throws LdapException
 *
 */
function ldap_get_values_len(\LDAP\Connection $ldap, \LDAP\ResultEntry $entry, string $attribute): array
{
    error_clear_last();
    $safeResult = \ldap_get_values_len($ldap, $entry, $attribute);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\ResultEntry $entry
 * @param string $attribute
 * @return array
 * @throws LdapException
 *
 */
function ldap_get_values(\LDAP\Connection $ldap, \LDAP\ResultEntry $entry, string $attribute): array
{
    error_clear_last();
    $safeResult = \ldap_get_values($ldap, $entry, $attribute);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param array $entry
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_mod_add(\LDAP\Connection $ldap, string $dn, array $entry, ?array $controls = null): void
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_mod_add($ldap, $dn, $entry, $controls);
    } else {
        $safeResult = \ldap_mod_add($ldap, $dn, $entry);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param array $entry
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_mod_del(\LDAP\Connection $ldap, string $dn, array $entry, ?array $controls = null): void
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_mod_del($ldap, $dn, $entry, $controls);
    } else {
        $safeResult = \ldap_mod_del($ldap, $dn, $entry);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param array $entry
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_mod_replace(\LDAP\Connection $ldap, string $dn, array $entry, ?array $controls = null): void
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_mod_replace($ldap, $dn, $entry, $controls);
    } else {
        $safeResult = \ldap_mod_replace($ldap, $dn, $entry);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param array $modifications_info
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_modify_batch(\LDAP\Connection $ldap, string $dn, array $modifications_info, ?array $controls = null): void
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_modify_batch($ldap, $dn, $modifications_info, $controls);
    } else {
        $safeResult = \ldap_modify_batch($ldap, $dn, $modifications_info);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\ResultEntry $entry
 * @return string
 * @throws LdapException
 *
 */
function ldap_next_attribute(\LDAP\Connection $ldap, \LDAP\ResultEntry $entry): string
{
    error_clear_last();
    $safeResult = \ldap_next_attribute($ldap, $entry);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\Result $result
 * @param null|string $response_data
 * @param null|string $response_oid
 * @throws LdapException
 *
 */
function ldap_parse_exop(\LDAP\Connection $ldap, \LDAP\Result $result, ?string &$response_data = null, ?string &$response_oid = null): void
{
    error_clear_last();
    $safeResult = \ldap_parse_exop($ldap, $result, $response_data, $response_oid);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\Result $result
 * @param int|null $error_code
 * @param null|string $matched_dn
 * @param null|string $error_message
 * @param array|null $referrals
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_parse_result(\LDAP\Connection $ldap, \LDAP\Result $result, ?int &$error_code, ?string &$matched_dn = null, ?string &$error_message = null, ?array &$referrals = null, ?array &$controls = null): void
{
    error_clear_last();
    $safeResult = \ldap_parse_result($ldap, $result, $error_code, $matched_dn, $error_message, $referrals, $controls);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param string $new_rdn
 * @param string $new_parent
 * @param bool $delete_old_rdn
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_rename(\LDAP\Connection $ldap, string $dn, string $new_rdn, string $new_parent, bool $delete_old_rdn, ?array $controls = null): void
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_rename($ldap, $dn, $new_rdn, $new_parent, $delete_old_rdn, $controls);
    } else {
        $safeResult = \ldap_rename($ldap, $dn, $new_rdn, $new_parent, $delete_old_rdn);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param null|string $dn
 * @param null|string $password
 * @param null|string $mech
 * @param null|string $realm
 * @param null|string $authc_id
 * @param null|string $authz_id
 * @param null|string $props
 * @throws LdapException
 *
 */
function ldap_sasl_bind(\LDAP\Connection $ldap, ?string $dn = null, ?string $password = null, ?string $mech = null, ?string $realm = null, ?string $authc_id = null, ?string $authz_id = null, ?string $props = null): void
{
    error_clear_last();
    if ($props !== null) {
        $safeResult = \ldap_sasl_bind($ldap, $dn, $password, $mech, $realm, $authc_id, $authz_id, $props);
    } elseif ($authz_id !== null) {
        $safeResult = \ldap_sasl_bind($ldap, $dn, $password, $mech, $realm, $authc_id, $authz_id);
    } elseif ($authc_id !== null) {
        $safeResult = \ldap_sasl_bind($ldap, $dn, $password, $mech, $realm, $authc_id);
    } elseif ($realm !== null) {
        $safeResult = \ldap_sasl_bind($ldap, $dn, $password, $mech, $realm);
    } elseif ($mech !== null) {
        $safeResult = \ldap_sasl_bind($ldap, $dn, $password, $mech);
    } elseif ($password !== null) {
        $safeResult = \ldap_sasl_bind($ldap, $dn, $password);
    } elseif ($dn !== null) {
        $safeResult = \ldap_sasl_bind($ldap, $dn);
    } else {
        $safeResult = \ldap_sasl_bind($ldap);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param null|resource $ldap
 * @param int $option
 * @param mixed $value
 * @throws LdapException
 *
 */
function ldap_set_option($ldap, int $option, $value): void
{
    error_clear_last();
    $safeResult = \ldap_set_option($ldap, $option, $value);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @throws LdapException
 *
 */
function ldap_unbind(\LDAP\Connection $ldap): void
{
    error_clear_last();
    $safeResult = \ldap_unbind($ldap);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/swoole.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/swoole.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/swoole.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/swoole.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/swoole.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/swoole.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    return require_once __DIR__ . '/8.1/functionsList.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    return require_once __DIR__ . '/8.2/functionsList.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    return require_once __DIR__ . '/8.2/functionsList.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    return require_once __DIR__ . '/8.4/functionsList.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    return require_once __DIR__ . '/8.5/functionsList.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    return require_once __DIR__ . '/8.6/functionsList.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/network.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.2/network.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.2/network.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.4/network.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.4/network.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.4/network.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/eio.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/eio.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/eio.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/eio.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/eio.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/eio.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/opcache.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/opcache.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/opcache.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/opcache.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/opcache.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/opcache.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/imap.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.2/imap.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.2/imap.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.4/imap.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.4/imap.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.4/imap.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/solr.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/solr.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/solr.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/solr.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/solr.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/solr.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/gettext.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/gettext.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/gettext.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.4/gettext.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.4/gettext.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.4/gettext.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/url.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/url.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/url.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/url.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/url.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/url.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/errorfunc.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.2/errorfunc.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.2/errorfunc.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.2/errorfunc.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.2/errorfunc.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.2/errorfunc.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/lzf.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/lzf.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/lzf.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/lzf.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/lzf.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/lzf.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/mysqli.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.2/mysqli.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.2/mysqli.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.2/mysqli.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.2/mysqli.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.2/mysqli.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/json.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/json.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/json.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/json.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/json.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/json.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/pspell.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/pspell.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/pspell.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.4/pspell.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.4/pspell.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.4/pspell.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/filesystem.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.2/filesystem.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.2/filesystem.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.2/filesystem.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.2/filesystem.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.2/filesystem.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/ftp.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/ftp.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/ftp.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.4/ftp.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.4/ftp.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.4/ftp.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/array.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.2/array.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.2/array.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.4/array.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.4/array.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.4/array.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/ps.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/ps.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/ps.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/ps.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/ps.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/ps.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/gnupg.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.2/gnupg.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.2/gnupg.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.2/gnupg.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.2/gnupg.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.2/gnupg.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/gmp.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.2/gmp.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.2/gmp.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.2/gmp.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.2/gmp.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.2/gmp.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.2/rnp.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.2/rnp.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.2/rnp.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.2/rnp.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.2/rnp.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/zip.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/zip.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/zip.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/zip.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/zip.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/zip.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/sockets.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/sockets.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/sockets.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.4/sockets.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.4/sockets.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.4/sockets.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/filter.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/filter.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/filter.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/filter.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/filter.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/filter.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/ssh2.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.2/ssh2.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.2/ssh2.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.2/ssh2.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.2/ssh2.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.2/ssh2.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/rpminfo.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/rpminfo.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/rpminfo.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/rpminfo.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.5/rpminfo.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.5/rpminfo.php';
}
<?php

namespace Safe;

use Safe\Exceptions\UodbcException;

/**
 * @param \Odbc\Connection $odbc
 * @param bool|null $enable
 * @return mixed
 * @throws UodbcException
 *
 */
function odbc_autocommit(\Odbc\Connection $odbc, ?bool $enable = null)
{
    error_clear_last();
    if ($enable !== null) {
        $safeResult = \odbc_autocommit($odbc, $enable);
    } else {
        $safeResult = \odbc_autocommit($odbc);
    }
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $statement
 * @param int $mode
 * @return bool
 *
 */
function odbc_binmode(int $statement, int $mode): bool
{
    error_clear_last();
    $safeResult = \odbc_binmode($statement, $mode);
    return $safeResult;
}


/**
 * @param \Odbc\Connection $odbc
 * @param string $catalog
 * @param string $schema
 * @param string $table
 * @param string $column
 * @return \Odbc\Result|false
 *
 */
function odbc_columnprivileges(\Odbc\Connection $odbc, string $catalog, string $schema, string $table, string $column)
{
    error_clear_last();
    $safeResult = \odbc_columnprivileges($odbc, $catalog, $schema, $table, $column);
    return $safeResult;
}


/**
 * @param \Odbc\Connection $odbc
 * @param null|string $catalog
 * @param null|string $schema
 * @param null|string $table
 * @param null|string $column
 * @return \Odbc\Result|false
 *
 */
function odbc_columns(\Odbc\Connection $odbc, ?string $catalog = null, ?string $schema = null, ?string $table = null, ?string $column = null)
{
    error_clear_last();
    if ($column !== null) {
        $safeResult = \odbc_columns($odbc, $catalog, $schema, $table, $column);
    } elseif ($table !== null) {
        $safeResult = \odbc_columns($odbc, $catalog, $schema, $table);
    } elseif ($schema !== null) {
        $safeResult = \odbc_columns($odbc, $catalog, $schema);
    } elseif ($catalog !== null) {
        $safeResult = \odbc_columns($odbc, $catalog);
    } else {
        $safeResult = \odbc_columns($odbc);
    }
    return $safeResult;
}


/**
 * @param \Odbc\Connection $odbc
 * @throws UodbcException
 *
 */
function odbc_commit(\Odbc\Connection $odbc): void
{
    error_clear_last();
    $safeResult = \odbc_commit($odbc);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
}


/**
 * @param string $dsn
 * @param string $user
 * @param string $password
 * @param int $cursor_option
 * @return \Odbc\Connection
 * @throws UodbcException
 *
 */
function odbc_connect(string $dsn, string $user, string $password, int $cursor_option = SQL_CUR_USE_DRIVER): \Odbc\Connection
{
    error_clear_last();
    $safeResult = \odbc_connect($dsn, $user, $password, $cursor_option);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Odbc\Result $statement
 * @return string
 * @throws UodbcException
 *
 */
function odbc_cursor(\Odbc\Result $statement): string
{
    error_clear_last();
    $safeResult = \odbc_cursor($statement);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Odbc\Connection $odbc
 * @param int $fetch_type
 * @return array
 * @throws UodbcException
 *
 */
function odbc_data_source(\Odbc\Connection $odbc, int $fetch_type): array
{
    error_clear_last();
    $safeResult = \odbc_data_source($odbc, $fetch_type);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Odbc\Connection $odbc
 * @param string $query
 * @return \Odbc\Result
 * @throws UodbcException
 *
 */
function odbc_exec(\Odbc\Connection $odbc, string $query): \Odbc\Result
{
    error_clear_last();
    $safeResult = \odbc_exec($odbc, $query);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Odbc\Result $statement
 * @param array $params
 * @throws UodbcException
 *
 */
function odbc_execute(\Odbc\Result $statement, array $params = []): void
{
    error_clear_last();
    $safeResult = \odbc_execute($statement, $params);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
}


/**
 * @param \Odbc\Result $statement
 * @param array|null $array
 * @param int|null $row
 * @return int
 * @throws UodbcException
 *
 */
function odbc_fetch_into(\Odbc\Result $statement, ?array &$array, ?int $row = null): int
{
    error_clear_last();
    if ($row !== null) {
        $safeResult = \odbc_fetch_into($statement, $array, $row);
    } else {
        $safeResult = \odbc_fetch_into($statement, $array);
    }
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Odbc\Result $statement
 * @param int $field
 * @return int
 * @throws UodbcException
 *
 */
function odbc_field_len(\Odbc\Result $statement, int $field): int
{
    error_clear_last();
    $safeResult = \odbc_field_len($statement, $field);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Odbc\Result $statement
 * @param int $field
 * @return string
 * @throws UodbcException
 *
 */
function odbc_field_name(\Odbc\Result $statement, int $field): string
{
    error_clear_last();
    $safeResult = \odbc_field_name($statement, $field);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Odbc\Result $statement
 * @param string $field
 * @return int
 * @throws UodbcException
 *
 */
function odbc_field_num(\Odbc\Result $statement, string $field): int
{
    error_clear_last();
    $safeResult = \odbc_field_num($statement, $field);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Odbc\Result $statement
 * @param int $field
 * @return int
 * @throws UodbcException
 *
 */
function odbc_field_scale(\Odbc\Result $statement, int $field): int
{
    error_clear_last();
    $safeResult = \odbc_field_scale($statement, $field);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Odbc\Result $statement
 * @param int $field
 * @return string
 * @throws UodbcException
 *
 */
function odbc_field_type(\Odbc\Result $statement, int $field): string
{
    error_clear_last();
    $safeResult = \odbc_field_type($statement, $field);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Odbc\Connection $odbc
 * @param string $pk_catalog
 * @param string $pk_schema
 * @param string $pk_table
 * @param string $fk_catalog
 * @param string $fk_schema
 * @param string $fk_table
 * @return \Odbc\Result|false
 *
 */
function odbc_foreignkeys(\Odbc\Connection $odbc, string $pk_catalog, string $pk_schema, string $pk_table, string $fk_catalog, string $fk_schema, string $fk_table)
{
    error_clear_last();
    $safeResult = \odbc_foreignkeys($odbc, $pk_catalog, $pk_schema, $pk_table, $fk_catalog, $fk_schema, $fk_table);
    return $safeResult;
}


/**
 * @param \Odbc\Connection $odbc
 * @param int $data_type
 * @return \Odbc\Result|false
 *
 */
function odbc_gettypeinfo(\Odbc\Connection $odbc, int $data_type = 0)
{
    error_clear_last();
    $safeResult = \odbc_gettypeinfo($odbc, $data_type);
    return $safeResult;
}


/**
 * @param \Odbc\Result $statement
 * @param int $length
 * @return bool
 *
 */
function odbc_longreadlen(\Odbc\Result $statement, int $length): bool
{
    error_clear_last();
    $safeResult = \odbc_longreadlen($statement, $length);
    return $safeResult;
}


/**
 * @param \Odbc\Result $statement
 * @return int
 * @throws UodbcException
 *
 */
function odbc_num_fields(\Odbc\Result $statement): int
{
    error_clear_last();
    $safeResult = \odbc_num_fields($statement);
    if ($safeResult === -1) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $dsn
 * @param string $user
 * @param string $password
 * @param int $cursor_option
 * @return \Odbc\Connection
 * @throws UodbcException
 *
 */
function odbc_pconnect(string $dsn, string $user, string $password, int $cursor_option = SQL_CUR_USE_DRIVER): \Odbc\Connection
{
    error_clear_last();
    $safeResult = \odbc_pconnect($dsn, $user, $password, $cursor_option);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Odbc\Connection $odbc
 * @param string $query
 * @return \Odbc\Result
 * @throws UodbcException
 *
 */
function odbc_prepare(\Odbc\Connection $odbc, string $query): \Odbc\Result
{
    error_clear_last();
    $safeResult = \odbc_prepare($odbc, $query);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Odbc\Connection $odbc
 * @param string $catalog
 * @param string $schema
 * @param string $table
 * @return \Odbc\Result|false
 *
 */
function odbc_primarykeys(\Odbc\Connection $odbc, string $catalog, string $schema, string $table)
{
    error_clear_last();
    $safeResult = \odbc_primarykeys($odbc, $catalog, $schema, $table);
    return $safeResult;
}


/**
 * @param  $odbc
 * @param null|string $catalog
 * @param null|string $schema
 * @param null|string $procedure
 * @param null|string $column
 * @return \Odbc\Result|false
 *
 */
function odbc_procedurecolumns($odbc, ?string $catalog = null, ?string $schema = null, ?string $procedure = null, ?string $column = null)
{
    error_clear_last();
    if ($column !== null) {
        $safeResult = \odbc_procedurecolumns($odbc, $catalog, $schema, $procedure, $column);
    } elseif ($procedure !== null) {
        $safeResult = \odbc_procedurecolumns($odbc, $catalog, $schema, $procedure);
    } elseif ($schema !== null) {
        $safeResult = \odbc_procedurecolumns($odbc, $catalog, $schema);
    } elseif ($catalog !== null) {
        $safeResult = \odbc_procedurecolumns($odbc, $catalog);
    } else {
        $safeResult = \odbc_procedurecolumns($odbc);
    }
    return $safeResult;
}


/**
 * @param  $odbc
 * @param null|string $catalog
 * @param null|string $schema
 * @param null|string $procedure
 * @return \Odbc\Result
 * @throws UodbcException
 *
 */
function odbc_procedures($odbc, ?string $catalog = null, ?string $schema = null, ?string $procedure = null): \Odbc\Result
{
    error_clear_last();
    if ($procedure !== null) {
        $safeResult = \odbc_procedures($odbc, $catalog, $schema, $procedure);
    } elseif ($schema !== null) {
        $safeResult = \odbc_procedures($odbc, $catalog, $schema);
    } elseif ($catalog !== null) {
        $safeResult = \odbc_procedures($odbc, $catalog);
    } else {
        $safeResult = \odbc_procedures($odbc);
    }
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Odbc\Result $statement
 * @param string $format
 * @return int
 * @throws UodbcException
 *
 */
function odbc_result_all(\Odbc\Result $statement, string $format = ""): int
{
    error_clear_last();
    $safeResult = \odbc_result_all($statement, $format);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Odbc\Result $statement
 * @param mixed $field
 * @return mixed
 * @throws UodbcException
 *
 */
function odbc_result(\Odbc\Result $statement, $field)
{
    error_clear_last();
    $safeResult = \odbc_result($statement, $field);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Odbc\Connection $odbc
 * @throws UodbcException
 *
 */
function odbc_rollback(\Odbc\Connection $odbc): void
{
    error_clear_last();
    $safeResult = \odbc_rollback($odbc);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
}


/**
 * @param \Odbc\Connection|\Odbc\Result $odbc
 * @param int $which
 * @param int $option
 * @param int $value
 * @throws UodbcException
 *
 */
function odbc_setoption($odbc, int $which, int $option, int $value): void
{
    error_clear_last();
    $safeResult = \odbc_setoption($odbc, $which, $option, $value);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
}


/**
 * @param \Odbc\Connection $odbc
 * @param int $type
 * @param string $catalog
 * @param string $schema
 * @param string $table
 * @param int $scope
 * @param int $nullable
 * @return \Odbc\Result|false
 *
 */
function odbc_specialcolumns(\Odbc\Connection $odbc, int $type, string $catalog, string $schema, string $table, int $scope, int $nullable)
{
    error_clear_last();
    $safeResult = \odbc_specialcolumns($odbc, $type, $catalog, $schema, $table, $scope, $nullable);
    return $safeResult;
}


/**
 * @param \Odbc\Connection $odbc
 * @param string $catalog
 * @param string $schema
 * @param string $table
 * @param int $unique
 * @param int $accuracy
 * @return \Odbc\Result|false
 *
 */
function odbc_statistics(\Odbc\Connection $odbc, string $catalog, string $schema, string $table, int $unique, int $accuracy)
{
    error_clear_last();
    $safeResult = \odbc_statistics($odbc, $catalog, $schema, $table, $unique, $accuracy);
    return $safeResult;
}


/**
 * @param \Odbc\Connection $odbc
 * @param string $catalog
 * @param string $schema
 * @param string $table
 * @return \Odbc\Result|false
 *
 */
function odbc_tableprivileges(\Odbc\Connection $odbc, string $catalog, string $schema, string $table)
{
    error_clear_last();
    $safeResult = \odbc_tableprivileges($odbc, $catalog, $schema, $table);
    return $safeResult;
}


/**
 * @param \Odbc\Connection $odbc
 * @param null|string $catalog
 * @param null|string $schema
 * @param null|string $table
 * @param null|string $types
 * @return \Odbc\Result
 * @throws UodbcException
 *
 */
function odbc_tables(\Odbc\Connection $odbc, ?string $catalog = null, ?string $schema = null, ?string $table = null, ?string $types = null): \Odbc\Result
{
    error_clear_last();
    if ($types !== null) {
        $safeResult = \odbc_tables($odbc, $catalog, $schema, $table, $types);
    } elseif ($table !== null) {
        $safeResult = \odbc_tables($odbc, $catalog, $schema, $table);
    } elseif ($schema !== null) {
        $safeResult = \odbc_tables($odbc, $catalog, $schema);
    } elseif ($catalog !== null) {
        $safeResult = \odbc_tables($odbc, $catalog);
    } else {
        $safeResult = \odbc_tables($odbc);
    }
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\PgsqlException;

/**
 * @param \PgSql\Connection $connection
 * @throws PgsqlException
 *
 */
function pg_cancel_query(\PgSql\Connection $connection): void
{
    error_clear_last();
    $safeResult = \pg_cancel_query($connection);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
}


/**
 * @param string $connection_string
 * @param int $flags
 * @return \PgSql\Connection
 * @throws PgsqlException
 *
 */
function pg_connect(string $connection_string, int $flags = 0): \PgSql\Connection
{
    error_clear_last();
    $safeResult = \pg_connect($connection_string, $flags);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Connection $connection
 * @throws PgsqlException
 *
 */
function pg_connection_reset(\PgSql\Connection $connection): void
{
    error_clear_last();
    $safeResult = \pg_connection_reset($connection);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
}


/**
 * @param \PgSql\Connection $connection
 * @param string $table_name
 * @param array $values
 * @param int $flags
 * @return array
 * @throws PgsqlException
 *
 */
function pg_convert(\PgSql\Connection $connection, string $table_name, array $values, int $flags = 0): array
{
    error_clear_last();
    $safeResult = \pg_convert($connection, $table_name, $values, $flags);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Connection $connection
 * @param string $table_name
 * @param array $rows
 * @param string $separator
 * @param string $null_as
 * @throws PgsqlException
 *
 */
function pg_copy_from(\PgSql\Connection $connection, string $table_name, array $rows, string $separator = "\t", string $null_as = "\\\\N"): void
{
    error_clear_last();
    $safeResult = \pg_copy_from($connection, $table_name, $rows, $separator, $null_as);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
}


/**
 * @param \PgSql\Connection $connection
 * @param string $table_name
 * @param string $separator
 * @param string $null_as
 * @return array
 * @throws PgsqlException
 *
 */
function pg_copy_to(\PgSql\Connection $connection, string $table_name, string $separator = "\t", string $null_as = "\\\\N"): array
{
    error_clear_last();
    $safeResult = \pg_copy_to($connection, $table_name, $separator, $null_as);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Connection $connection
 * @param string $table_name
 * @param array $conditions
 * @param int $flags
 * @return mixed
 * @throws PgsqlException
 *
 */
function pg_delete(\PgSql\Connection $connection, string $table_name, array $conditions, int $flags = PGSQL_DML_EXEC)
{
    error_clear_last();
    $safeResult = \pg_delete($connection, $table_name, $conditions, $flags);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Connection|null $connection
 * @throws PgsqlException
 *
 */
function pg_end_copy(?\PgSql\Connection $connection = null): void
{
    error_clear_last();
    if ($connection !== null) {
        $safeResult = \pg_end_copy($connection);
    } else {
        $safeResult = \pg_end_copy();
    }
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
}


/**
 * @param \PgSql\Connection $connection
 * @param string $stmtname
 * @param array $params
 * @return \PgSql\Result
 * @throws PgsqlException
 *
 */
function pg_execute(?\PgSql\Connection $connection = null, ?string $stmtname = null, ?array $params = null): \PgSql\Result
{
    error_clear_last();
    if ($params !== null) {
        $safeResult = \pg_execute($connection, $stmtname, $params);
    } elseif ($stmtname !== null) {
        $safeResult = \pg_execute($connection, $stmtname);
    } elseif ($connection !== null) {
        $safeResult = \pg_execute($connection);
    } else {
        $safeResult = \pg_execute();
    }
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Result $result
 * @param string $field
 * @return int
 * @throws PgsqlException
 *
 */
function pg_field_num(\PgSql\Result $result, string $field): int
{
    error_clear_last();
    $safeResult = \pg_field_num($result, $field);
    if ($safeResult === -1) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Result $result
 * @param int $field
 * @param bool $oid_only
 * @return mixed
 * @throws PgsqlException
 *
 */
function pg_field_table(\PgSql\Result $result, int $field, bool $oid_only = false)
{
    error_clear_last();
    $safeResult = \pg_field_table($result, $field, $oid_only);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Connection $connection
 * @return mixed
 * @throws PgsqlException
 *
 */
function pg_flush(\PgSql\Connection $connection)
{
    error_clear_last();
    $safeResult = \pg_flush($connection);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Result $result
 * @throws PgsqlException
 *
 */
function pg_free_result(\PgSql\Result $result): void
{
    error_clear_last();
    $safeResult = \pg_free_result($result);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
}


/**
 * @param \PgSql\Connection|null $connection
 * @return string
 * @throws PgsqlException
 *
 */
function pg_host(?\PgSql\Connection $connection = null): string
{
    error_clear_last();
    if ($connection !== null) {
        $safeResult = \pg_host($connection);
    } else {
        $safeResult = \pg_host();
    }
    if ($safeResult === '') {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Connection $connection
 * @param string $table_name
 * @param array $values
 * @param int $flags
 * @return mixed
 * @throws PgsqlException
 *
 */
function pg_insert(\PgSql\Connection $connection, string $table_name, array $values, int $flags = PGSQL_DML_EXEC)
{
    error_clear_last();
    $safeResult = \pg_insert($connection, $table_name, $values, $flags);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Result $result
 * @return string
 * @throws PgsqlException
 *
 */
function pg_last_oid(\PgSql\Result $result): string
{
    error_clear_last();
    $safeResult = \pg_last_oid($result);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Lob $lob
 * @throws PgsqlException
 *
 */
function pg_lo_close(\PgSql\Lob $lob): void
{
    error_clear_last();
    $safeResult = \pg_lo_close($lob);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
}


/**
 * @param \PgSql\Connection $connection
 * @param int $oid
 * @param string $pathname
 * @throws PgsqlException
 *
 */
function pg_lo_export(?\PgSql\Connection $connection = null, ?int $oid = null, ?string $pathname = null): void
{
    error_clear_last();
    if ($pathname !== null) {
        $safeResult = \pg_lo_export($connection, $oid, $pathname);
    } elseif ($oid !== null) {
        $safeResult = \pg_lo_export($connection, $oid);
    } elseif ($connection !== null) {
        $safeResult = \pg_lo_export($connection);
    } else {
        $safeResult = \pg_lo_export();
    }
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
}


/**
 * @param \PgSql\Connection $connection
 * @param string $pathname
 * @param  $object_id
 * @return int
 * @throws PgsqlException
 *
 */
function pg_lo_import(?\PgSql\Connection $connection = null, ?string $pathname = null, $object_id = null): int
{
    error_clear_last();
    if ($object_id !== null) {
        $safeResult = \pg_lo_import($connection, $pathname, $object_id);
    } elseif ($pathname !== null) {
        $safeResult = \pg_lo_import($connection, $pathname);
    } elseif ($connection !== null) {
        $safeResult = \pg_lo_import($connection);
    } else {
        $safeResult = \pg_lo_import();
    }
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Connection $connection
 * @param int $oid
 * @param string $mode
 * @return \PgSql\Lob
 * @throws PgsqlException
 *
 */
function pg_lo_open(\PgSql\Connection $connection, int $oid, string $mode): \PgSql\Lob
{
    error_clear_last();
    $safeResult = \pg_lo_open($connection, $oid, $mode);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Lob $lob
 * @param int $length
 * @return string
 * @throws PgsqlException
 *
 */
function pg_lo_read(\PgSql\Lob $lob, int $length = 8192): string
{
    error_clear_last();
    $safeResult = \pg_lo_read($lob, $length);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Lob $lob
 * @param int $offset
 * @param int $whence
 * @throws PgsqlException
 *
 */
function pg_lo_seek(\PgSql\Lob $lob, int $offset, int $whence = SEEK_CUR): void
{
    error_clear_last();
    $safeResult = \pg_lo_seek($lob, $offset, $whence);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
}


/**
 * @param \PgSql\Lob $lob
 * @param int $size
 * @throws PgsqlException
 *
 */
function pg_lo_truncate(\PgSql\Lob $lob, int $size): void
{
    error_clear_last();
    $safeResult = \pg_lo_truncate($lob, $size);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
}


/**
 * @param \PgSql\Connection $connection
 * @param int $oid
 * @throws PgsqlException
 *
 */
function pg_lo_unlink(\PgSql\Connection $connection, int $oid): void
{
    error_clear_last();
    $safeResult = \pg_lo_unlink($connection, $oid);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
}


/**
 * @param \PgSql\Lob $lob
 * @param string $data
 * @param int|null $length
 * @return int
 * @throws PgsqlException
 *
 */
function pg_lo_write(\PgSql\Lob $lob, string $data, ?int $length = null): int
{
    error_clear_last();
    if ($length !== null) {
        $safeResult = \pg_lo_write($lob, $data, $length);
    } else {
        $safeResult = \pg_lo_write($lob, $data);
    }
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Connection $connection
 * @param string $table_name
 * @param bool $extended
 * @return array
 * @throws PgsqlException
 *
 */
function pg_meta_data(\PgSql\Connection $connection, string $table_name, bool $extended = false): array
{
    error_clear_last();
    $safeResult = \pg_meta_data($connection, $table_name, $extended);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Connection $connection
 * @param string $param_name
 * @return string
 * @throws PgsqlException
 *
 */
function pg_parameter_status(?\PgSql\Connection $connection = null, ?string $param_name = null): string
{
    error_clear_last();
    if ($param_name !== null) {
        $safeResult = \pg_parameter_status($connection, $param_name);
    } elseif ($connection !== null) {
        $safeResult = \pg_parameter_status($connection);
    } else {
        $safeResult = \pg_parameter_status();
    }
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $connection_string
 * @param int $flags
 * @return \PgSql\Connection
 * @throws PgsqlException
 *
 */
function pg_pconnect(string $connection_string, int $flags = 0): \PgSql\Connection
{
    error_clear_last();
    $safeResult = \pg_pconnect($connection_string, $flags);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Connection|null $connection
 * @throws PgsqlException
 *
 */
function pg_ping(?\PgSql\Connection $connection = null): void
{
    error_clear_last();
    if ($connection !== null) {
        $safeResult = \pg_ping($connection);
    } else {
        $safeResult = \pg_ping();
    }
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
}


/**
 * @param \PgSql\Connection $connection
 * @param string $stmtname
 * @param string $query
 * @return \PgSql\Result
 * @throws PgsqlException
 *
 */
function pg_prepare(?\PgSql\Connection $connection = null, ?string $stmtname = null, ?string $query = null): \PgSql\Result
{
    error_clear_last();
    if ($query !== null) {
        $safeResult = \pg_prepare($connection, $stmtname, $query);
    } elseif ($stmtname !== null) {
        $safeResult = \pg_prepare($connection, $stmtname);
    } elseif ($connection !== null) {
        $safeResult = \pg_prepare($connection);
    } else {
        $safeResult = \pg_prepare();
    }
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Connection $connection
 * @param string $data
 * @throws PgsqlException
 *
 */
function pg_put_line(?\PgSql\Connection $connection = null, ?string $data = null): void
{
    error_clear_last();
    if ($data !== null) {
        $safeResult = \pg_put_line($connection, $data);
    } elseif ($connection !== null) {
        $safeResult = \pg_put_line($connection);
    } else {
        $safeResult = \pg_put_line();
    }
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
}


/**
 * @param \PgSql\Connection $connection
 * @param string $query
 * @param array $params
 * @return \PgSql\Result
 * @throws PgsqlException
 *
 */
function pg_query_params(?\PgSql\Connection $connection = null, ?string $query = null, ?array $params = null): \PgSql\Result
{
    error_clear_last();
    if ($params !== null) {
        $safeResult = \pg_query_params($connection, $query, $params);
    } elseif ($query !== null) {
        $safeResult = \pg_query_params($connection, $query);
    } elseif ($connection !== null) {
        $safeResult = \pg_query_params($connection);
    } else {
        $safeResult = \pg_query_params();
    }
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Connection $connection
 * @param string $query
 * @return \PgSql\Result
 * @throws PgsqlException
 *
 */
function pg_query(?\PgSql\Connection $connection = null, ?string $query = null): \PgSql\Result
{
    error_clear_last();
    if ($query !== null) {
        $safeResult = \pg_query($connection, $query);
    } elseif ($connection !== null) {
        $safeResult = \pg_query($connection);
    } else {
        $safeResult = \pg_query();
    }
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Result $result
 * @param int $field_code
 * @return null|string
 * @throws PgsqlException
 *
 */
function pg_result_error_field(\PgSql\Result $result, int $field_code): ?string
{
    error_clear_last();
    $safeResult = \pg_result_error_field($result, $field_code);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Result $result
 * @param int $row
 * @throws PgsqlException
 *
 */
function pg_result_seek(\PgSql\Result $result, int $row): void
{
    error_clear_last();
    $safeResult = \pg_result_seek($result, $row);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
}


/**
 * @param \PgSql\Connection $connection
 * @param string $table_name
 * @param array $conditions
 * @param int $flags
 * @param int $mode
 * @return mixed
 * @throws PgsqlException
 *
 */
function pg_select(\PgSql\Connection $connection, string $table_name, array $conditions = [], int $flags = PGSQL_DML_EXEC, int $mode = PGSQL_ASSOC)
{
    error_clear_last();
    $safeResult = \pg_select($connection, $table_name, $conditions, $flags, $mode);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Connection $connection
 * @param int $size
 * @throws PgsqlException
 *
 */
function pg_set_chunked_rows_size(\PgSql\Connection $connection, int $size): void
{
    error_clear_last();
    $safeResult = \pg_set_chunked_rows_size($connection, $size);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
}


/**
 * @param \PgSql\Connection $connection
 * @return resource
 * @throws PgsqlException
 *
 */
function pg_socket(\PgSql\Connection $connection)
{
    error_clear_last();
    $safeResult = \pg_socket($connection);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param string $mode
 * @param \PgSql\Connection|null $connection
 * @param int $trace_mode
 * @throws PgsqlException
 *
 */
function pg_trace(string $filename, string $mode = "w", ?\PgSql\Connection $connection = null, int $trace_mode = 0): void
{
    error_clear_last();
    if ($trace_mode !== 0) {
        $safeResult = \pg_trace($filename, $mode, $connection, $trace_mode);
    } elseif ($connection !== null) {
        $safeResult = \pg_trace($filename, $mode, $connection);
    } else {
        $safeResult = \pg_trace($filename, $mode);
    }
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
}


/**
 * @param \PgSql\Connection $connection
 * @param string $table_name
 * @param array $values
 * @param array $conditions
 * @param int $flags
 * @return mixed
 * @throws PgsqlException
 *
 */
function pg_update(\PgSql\Connection $connection, string $table_name, array $values, array $conditions, int $flags = PGSQL_DML_EXEC)
{
    error_clear_last();
    $safeResult = \pg_update($connection, $table_name, $values, $conditions, $flags);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\InotifyException;

/**
 * @param resource $inotify_instance
 * @param string $pathname
 * @param int $mask
 * @return int
 * @throws InotifyException
 *
 */
function inotify_add_watch($inotify_instance, string $pathname, int $mask): int
{
    error_clear_last();
    $safeResult = \inotify_add_watch($inotify_instance, $pathname, $mask);
    if ($safeResult === false) {
        throw InotifyException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return resource
 * @throws InotifyException
 *
 */
function inotify_init()
{
    error_clear_last();
    $safeResult = \inotify_init();
    if ($safeResult === false) {
        throw InotifyException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $inotify_instance
 * @param int $watch_descriptor
 * @throws InotifyException
 *
 */
function inotify_rm_watch($inotify_instance, int $watch_descriptor): void
{
    error_clear_last();
    $safeResult = \inotify_rm_watch($inotify_instance, $watch_descriptor);
    if ($safeResult === false) {
        throw InotifyException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\DatetimeException;

/**
 * @param string $datetime
 * @param \DateTimeZone|null $timezone
 * @return \DateTimeImmutable
 * @throws DatetimeException
 *
 */
function date_create_immutable(string $datetime = "now", ?\DateTimeZone $timezone = null): \DateTimeImmutable
{
    error_clear_last();
    if ($timezone !== null) {
        $safeResult = \date_create_immutable($datetime, $timezone);
    } else {
        $safeResult = \date_create_immutable($datetime);
    }
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param null|string $datetime
 * @param \DateTimeZone|null $timezone
 * @return \DateTime
 * @throws DatetimeException
 *
 */
function date_create(?string $datetime = "now", ?\DateTimeZone $timezone = null): \DateTime
{
    error_clear_last();
    if ($timezone !== null) {
        $safeResult = \date_create($datetime, $timezone);
    } else {
        $safeResult = \date_create($datetime);
    }
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $format
 * @param string $datetime
 * @return array{year: int|false, month: int|false, day: int|false, hour: int|false, minute: int|false, second: int|false, fraction: float|false, warning_count: int, warnings: string[], error_count: int, errors: string[], is_localtime: bool, zone_type: int|bool, zone: int|bool, is_dst: bool, tz_abbr: string, tz_id: string, relative: array{year: int, month: int, day: int, hour: int, minute: int, second: int, weekday: int, weekdays: int, first_day_of_month: bool, last_day_of_month: bool}}|null
 * @throws DatetimeException
 *
 */
function date_parse_from_format(string $format, string $datetime): ?array
{
    error_clear_last();
    $safeResult = \date_parse_from_format($format, $datetime);
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $datetime
 * @return array{year: int|false, month: int|false, day: int|false, hour: int|false, minute: int|false, second: int|false, fraction: float|false, warning_count: int, warnings: string[], error_count: int, errors: string[], is_localtime: bool, zone_type: int|bool, zone: int|bool, is_dst: bool, tz_abbr: string, tz_id: string, relative: array{year: int, month: int, day: int, hour: int, minute: int, second: int, weekday: int, weekdays: int, first_day_of_month: bool, last_day_of_month: bool}}|null
 *
 */
function date_parse(string $datetime): ?array
{
    error_clear_last();
    $safeResult = \date_parse($datetime);
    return $safeResult;
}


/**
 * @param int $timestamp
 * @param float $latitude
 * @param float $longitude
 * @return array{sunrise: int|bool,sunset: int|bool,transit: int|bool,civil_twilight_begin: int|bool,civil_twilight_end: int|bool,nautical_twilight_begin: int|bool,nautical_twilight_end: int|bool,astronomical_twilight_begin: int|bool,astronomical_twilight_end: int|bool}|false
 *
 */
function date_sun_info(int $timestamp, float $latitude, float $longitude)
{
    error_clear_last();
    $safeResult = \date_sun_info($timestamp, $latitude, $longitude);
    return $safeResult;
}


/**
 * @param int $timestamp
 * @param int $returnFormat
 * @param float|null $latitude
 * @param float|null $longitude
 * @param float|null $zenith
 * @param float|null $utcOffset
 * @return mixed
 * @throws DatetimeException
 *
 */
function date_sunrise(int $timestamp, int $returnFormat = SUNFUNCS_RET_STRING, ?float $latitude = null, ?float $longitude = null, ?float $zenith = null, ?float $utcOffset = null)
{
    error_clear_last();
    if ($utcOffset !== null) {
        $safeResult = \date_sunrise($timestamp, $returnFormat, $latitude, $longitude, $zenith, $utcOffset);
    } elseif ($zenith !== null) {
        $safeResult = \date_sunrise($timestamp, $returnFormat, $latitude, $longitude, $zenith);
    } elseif ($longitude !== null) {
        $safeResult = \date_sunrise($timestamp, $returnFormat, $latitude, $longitude);
    } elseif ($latitude !== null) {
        $safeResult = \date_sunrise($timestamp, $returnFormat, $latitude);
    } else {
        $safeResult = \date_sunrise($timestamp, $returnFormat);
    }
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $timestamp
 * @param int $returnFormat
 * @param float|null $latitude
 * @param float|null $longitude
 * @param float|null $zenith
 * @param float|null $utcOffset
 * @return mixed
 * @throws DatetimeException
 *
 */
function date_sunset(int $timestamp, int $returnFormat = SUNFUNCS_RET_STRING, ?float $latitude = null, ?float $longitude = null, ?float $zenith = null, ?float $utcOffset = null)
{
    error_clear_last();
    if ($utcOffset !== null) {
        $safeResult = \date_sunset($timestamp, $returnFormat, $latitude, $longitude, $zenith, $utcOffset);
    } elseif ($zenith !== null) {
        $safeResult = \date_sunset($timestamp, $returnFormat, $latitude, $longitude, $zenith);
    } elseif ($longitude !== null) {
        $safeResult = \date_sunset($timestamp, $returnFormat, $latitude, $longitude);
    } elseif ($latitude !== null) {
        $safeResult = \date_sunset($timestamp, $returnFormat, $latitude);
    } else {
        $safeResult = \date_sunset($timestamp, $returnFormat);
    }
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $format
 * @param int|null $timestamp
 * @return string
 *
 */
function date(string $format, ?int $timestamp = null): string
{
    error_clear_last();
    if ($timestamp !== null) {
        $safeResult = \date($format, $timestamp);
    } else {
        $safeResult = \date($format);
    }
    return $safeResult;
}


/**
 * @param int $hour
 * @param int|null $minute
 * @param int|null $second
 * @param int|null $month
 * @param int|null $day
 * @param int|null $year
 * @return int
 * @throws DatetimeException
 *
 */
function gmmktime(int $hour, ?int $minute = null, ?int $second = null, ?int $month = null, ?int $day = null, ?int $year = null): int
{
    error_clear_last();
    if ($year !== null) {
        $safeResult = \gmmktime($hour, $minute, $second, $month, $day, $year);
    } elseif ($day !== null) {
        $safeResult = \gmmktime($hour, $minute, $second, $month, $day);
    } elseif ($month !== null) {
        $safeResult = \gmmktime($hour, $minute, $second, $month);
    } elseif ($second !== null) {
        $safeResult = \gmmktime($hour, $minute, $second);
    } elseif ($minute !== null) {
        $safeResult = \gmmktime($hour, $minute);
    } else {
        $safeResult = \gmmktime($hour);
    }
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $format
 * @param int|null $timestamp
 * @return string
 * @throws DatetimeException
 *
 */
function gmstrftime(string $format, ?int $timestamp = null): string
{
    error_clear_last();
    if ($timestamp !== null) {
        $safeResult = \gmstrftime($format, $timestamp);
    } else {
        $safeResult = \gmstrftime($format);
    }
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $format
 * @param int|null $timestamp
 * @return int
 * @throws DatetimeException
 *
 */
function idate(string $format, ?int $timestamp = null): int
{
    error_clear_last();
    if ($timestamp !== null) {
        $safeResult = \idate($format, $timestamp);
    } else {
        $safeResult = \idate($format);
    }
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $hour
 * @param int|null $minute
 * @param int|null $second
 * @param int|null $month
 * @param int|null $day
 * @param int|null $year
 * @return int
 * @throws DatetimeException
 *
 */
function mktime(int $hour, ?int $minute = null, ?int $second = null, ?int $month = null, ?int $day = null, ?int $year = null): int
{
    error_clear_last();
    if ($year !== null) {
        $safeResult = \mktime($hour, $minute, $second, $month, $day, $year);
    } elseif ($day !== null) {
        $safeResult = \mktime($hour, $minute, $second, $month, $day);
    } elseif ($month !== null) {
        $safeResult = \mktime($hour, $minute, $second, $month);
    } elseif ($second !== null) {
        $safeResult = \mktime($hour, $minute, $second);
    } elseif ($minute !== null) {
        $safeResult = \mktime($hour, $minute);
    } else {
        $safeResult = \mktime($hour);
    }
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $format
 * @param int|null $timestamp
 * @return string
 * @throws DatetimeException
 *
 */
function strftime(string $format, ?int $timestamp = null): string
{
    error_clear_last();
    if ($timestamp !== null) {
        $safeResult = \strftime($format, $timestamp);
    } else {
        $safeResult = \strftime($format);
    }
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $timestamp
 * @param string $format
 * @return array
 * @throws DatetimeException
 *
 */
function strptime(string $timestamp, string $format): array
{
    error_clear_last();
    $safeResult = \strptime($timestamp, $format);
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $datetime
 * @param int|null $baseTimestamp
 * @return int
 * @throws DatetimeException
 *
 */
function strtotime(string $datetime, ?int $baseTimestamp = null): int
{
    error_clear_last();
    if ($baseTimestamp !== null) {
        $safeResult = \strtotime($datetime, $baseTimestamp);
    } else {
        $safeResult = \strtotime($datetime);
    }
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $abbr
 * @param int $utcOffset
 * @param int $isDST
 * @return string
 * @throws DatetimeException
 *
 */
function timezone_name_from_abbr(string $abbr, int $utcOffset = -1, int $isDST = -1): string
{
    error_clear_last();
    $safeResult = \timezone_name_from_abbr($abbr, $utcOffset, $isDST);
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\InfoException;

/**
 * @param int $option
 * @param mixed $value
 * @return mixed
 *
 */
function assert_options(int $option, $value = null)
{
    error_clear_last();
    if ($value !== null) {
        $safeResult = \assert_options($option, $value);
    } else {
        $safeResult = \assert_options($option);
    }
    return $safeResult;
}


/**
 * @param string $title
 * @throws InfoException
 *
 */
function cli_set_process_title(string $title): void
{
    error_clear_last();
    $safeResult = \cli_set_process_title($title);
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
}


/**
 * @param string $extension_filename
 * @throws InfoException
 *
 */
function dl(string $extension_filename): void
{
    error_clear_last();
    $safeResult = \dl($extension_filename);
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
}


/**
 * @param string $option
 * @return mixed
 * @throws InfoException
 *
 */
function get_cfg_var(string $option)
{
    error_clear_last();
    $safeResult = \get_cfg_var($option);
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return string
 * @throws InfoException
 *
 */
function get_include_path(): string
{
    error_clear_last();
    $safeResult = \get_include_path();
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return int
 * @throws InfoException
 *
 */
function getlastmod(): int
{
    error_clear_last();
    $safeResult = \getlastmod();
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return int
 * @throws InfoException
 *
 */
function getmygid(): int
{
    error_clear_last();
    $safeResult = \getmygid();
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return int
 * @throws InfoException
 *
 */
function getmyinode(): int
{
    error_clear_last();
    $safeResult = \getmyinode();
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return int
 * @throws InfoException
 *
 */
function getmypid(): int
{
    error_clear_last();
    $safeResult = \getmypid();
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return int
 * @throws InfoException
 *
 */
function getmyuid(): int
{
    error_clear_last();
    $safeResult = \getmyuid();
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $short_options
 * @param array $long_options
 * @param int|null $rest_index
 * @return array|array|array
 * @throws InfoException
 *
 */
function getopt(string $short_options, array $long_options = [], ?int &$rest_index = null): array
{
    error_clear_last();
    $safeResult = \getopt($short_options, $long_options, $rest_index);
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $mode
 * @return array
 * @throws InfoException
 *
 */
function getrusage(int $mode = 0): array
{
    error_clear_last();
    $safeResult = \getrusage($mode);
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $option
 * @return string
 * @throws InfoException
 *
 */
function ini_get(string $option): string
{
    error_clear_last();
    $safeResult = \ini_get($option);
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $option
 * @param bool|float|int|null|string $value
 * @return string
 * @throws InfoException
 *
 */
function ini_set(string $option, $value): string
{
    error_clear_last();
    $safeResult = \ini_set($option, $value);
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return non-empty-string
 * @throws InfoException
 *
 */
function php_sapi_name(): string
{
    error_clear_last();
    $safeResult = \php_sapi_name();
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $flags
 * @return bool
 *
 */
function phpcredits(int $flags = CREDITS_ALL): bool
{
    error_clear_last();
    $safeResult = \phpcredits($flags);
    return $safeResult;
}


/**
 * @param int $flags
 * @return bool
 *
 */
function phpinfo(int $flags = INFO_ALL): bool
{
    error_clear_last();
    $safeResult = \phpinfo($flags);
    return $safeResult;
}


/**
 * @param string $assignment
 * @throws InfoException
 *
 */
function putenv(string $assignment): void
{
    error_clear_last();
    $safeResult = \putenv($assignment);
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
}


/**
 * @param string $include_path
 * @return string
 * @throws InfoException
 *
 */
function set_include_path(string $include_path): string
{
    error_clear_last();
    $safeResult = \set_include_path($include_path);
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $seconds
 * @throws InfoException
 *
 */
function set_time_limit(int $seconds): void
{
    error_clear_last();
    $safeResult = \set_time_limit($seconds);
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\OpensslException;

/**
 * @param string $cipher_algo
 * @return int
 * @throws OpensslException
 *
 */
function openssl_cipher_iv_length(string $cipher_algo): int
{
    error_clear_last();
    $safeResult = \openssl_cipher_iv_length($cipher_algo);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $cipher_algo
 * @return int
 * @throws OpensslException
 *
 */
function openssl_cipher_key_length(string $cipher_algo): int
{
    error_clear_last();
    $safeResult = \openssl_cipher_key_length($cipher_algo);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $input_filename
 * @param string $output_filename
 * @param \OpenSSLCertificate|string $certificate
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|null|string $private_key
 * @param int $encoding
 * @throws OpensslException
 *
 */
function openssl_cms_decrypt(string $input_filename, string $output_filename, $certificate, $private_key = null, int $encoding = OPENSSL_ENCODING_SMIME): void
{
    error_clear_last();
    if ($encoding !== OPENSSL_ENCODING_SMIME) {
        $safeResult = \openssl_cms_decrypt($input_filename, $output_filename, $certificate, $private_key, $encoding);
    } elseif ($private_key !== null) {
        $safeResult = \openssl_cms_decrypt($input_filename, $output_filename, $certificate, $private_key);
    } else {
        $safeResult = \openssl_cms_decrypt($input_filename, $output_filename, $certificate);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $input_filename
 * @param string $output_filename
 * @param \OpenSSLCertificate|array|string $certificate
 * @param array|null $headers
 * @param int $flags
 * @param int $encoding
 * @param int $cipher_algo
 * @throws OpensslException
 *
 */
function openssl_cms_encrypt(string $input_filename, string $output_filename, $certificate, ?array $headers, int $flags = 0, int $encoding = OPENSSL_ENCODING_SMIME, int $cipher_algo = OPENSSL_CIPHER_AES_128_CBC): void
{
    error_clear_last();
    $safeResult = \openssl_cms_encrypt($input_filename, $output_filename, $certificate, $headers, $flags, $encoding, $cipher_algo);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $input_filename
 * @param array $certificates
 * @throws OpensslException
 *
 */
function openssl_cms_read(string $input_filename, array &$certificates): void
{
    error_clear_last();
    $safeResult = \openssl_cms_read($input_filename, $certificates);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $input_filename
 * @param string $output_filename
 * @param \OpenSSLCertificate|string $certificate
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param array|null $headers
 * @param int $flags
 * @param int $encoding
 * @param null|string $untrusted_certificates_filename
 * @throws OpensslException
 *
 */
function openssl_cms_sign(string $input_filename, string $output_filename, $certificate, $private_key, ?array $headers, int $flags = 0, int $encoding = OPENSSL_ENCODING_SMIME, ?string $untrusted_certificates_filename = null): void
{
    error_clear_last();
    if ($untrusted_certificates_filename !== null) {
        $safeResult = \openssl_cms_sign($input_filename, $output_filename, $certificate, $private_key, $headers, $flags, $encoding, $untrusted_certificates_filename);
    } else {
        $safeResult = \openssl_cms_sign($input_filename, $output_filename, $certificate, $private_key, $headers, $flags, $encoding);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $input_filename
 * @param int $flags
 * @param null|string $certificates
 * @param array $ca_info
 * @param null|string $untrusted_certificates_filename
 * @param null|string $content
 * @param null|string $pk7
 * @param null|string $sigfile
 * @param int $encoding
 * @throws OpensslException
 *
 */
function openssl_cms_verify(string $input_filename, int $flags = 0, ?string $certificates = null, array $ca_info = [], ?string $untrusted_certificates_filename = null, ?string $content = null, ?string $pk7 = null, ?string $sigfile = null, int $encoding = OPENSSL_ENCODING_SMIME): void
{
    error_clear_last();
    if ($encoding !== OPENSSL_ENCODING_SMIME) {
        $safeResult = \openssl_cms_verify($input_filename, $flags, $certificates, $ca_info, $untrusted_certificates_filename, $content, $pk7, $sigfile, $encoding);
    } elseif ($sigfile !== null) {
        $safeResult = \openssl_cms_verify($input_filename, $flags, $certificates, $ca_info, $untrusted_certificates_filename, $content, $pk7, $sigfile);
    } elseif ($pk7 !== null) {
        $safeResult = \openssl_cms_verify($input_filename, $flags, $certificates, $ca_info, $untrusted_certificates_filename, $content, $pk7);
    } elseif ($content !== null) {
        $safeResult = \openssl_cms_verify($input_filename, $flags, $certificates, $ca_info, $untrusted_certificates_filename, $content);
    } elseif ($untrusted_certificates_filename !== null) {
        $safeResult = \openssl_cms_verify($input_filename, $flags, $certificates, $ca_info, $untrusted_certificates_filename);
    } elseif ($ca_info !== []) {
        $safeResult = \openssl_cms_verify($input_filename, $flags, $certificates, $ca_info);
    } elseif ($certificates !== null) {
        $safeResult = \openssl_cms_verify($input_filename, $flags, $certificates);
    } else {
        $safeResult = \openssl_cms_verify($input_filename, $flags);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLCertificateSigningRequest|string $csr
 * @param string $output_filename
 * @param bool $no_text
 * @throws OpensslException
 *
 */
function openssl_csr_export_to_file($csr, string $output_filename, bool $no_text = true): void
{
    error_clear_last();
    $safeResult = \openssl_csr_export_to_file($csr, $output_filename, $no_text);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLCertificateSigningRequest|string $csr
 * @param null|string $output
 * @param bool $no_text
 * @throws OpensslException
 *
 */
function openssl_csr_export($csr, ?string &$output, bool $no_text = true): void
{
    error_clear_last();
    $safeResult = \openssl_csr_export($csr, $output, $no_text);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLCertificateSigningRequest|string $csr
 * @param bool $short_names
 * @return \OpenSSLAsymmetricKey
 * @throws OpensslException
 *
 */
function openssl_csr_get_public_key($csr, bool $short_names = true): \OpenSSLAsymmetricKey
{
    error_clear_last();
    $safeResult = \openssl_csr_get_public_key($csr, $short_names);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLCertificateSigningRequest|string $csr
 * @param bool $short_names
 * @return array
 * @throws OpensslException
 *
 */
function openssl_csr_get_subject($csr, bool $short_names = true): array
{
    error_clear_last();
    $safeResult = \openssl_csr_get_subject($csr, $short_names);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param array $distinguished_names
 * @param \OpenSSLAsymmetricKey|null $private_key
 * @param array|null $options
 * @param array|null $extra_attributes
 * @return \OpenSSLCertificateSigningRequest|bool
 * @throws OpensslException
 *
 */
function openssl_csr_new(array $distinguished_names, ?\OpenSSLAsymmetricKey &$private_key, ?array $options = null, ?array $extra_attributes = null)
{
    error_clear_last();
    if ($extra_attributes !== null) {
        $safeResult = \openssl_csr_new($distinguished_names, $private_key, $options, $extra_attributes);
    } elseif ($options !== null) {
        $safeResult = \openssl_csr_new($distinguished_names, $private_key, $options);
    } else {
        $safeResult = \openssl_csr_new($distinguished_names, $private_key);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLCertificateSigningRequest|string $csr
 * @param \OpenSSLCertificate|null|string $ca_certificate
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param int $days
 * @param array|null $options
 * @param int $serial
 * @param null|string $serial_hex
 * @return \OpenSSLCertificate
 * @throws OpensslException
 *
 */
function openssl_csr_sign($csr, $ca_certificate, $private_key, int $days, ?array $options = null, int $serial = 0, ?string $serial_hex = null): \OpenSSLCertificate
{
    error_clear_last();
    if ($serial_hex !== null) {
        $safeResult = \openssl_csr_sign($csr, $ca_certificate, $private_key, $days, $options, $serial, $serial_hex);
    } elseif ($serial !== 0) {
        $safeResult = \openssl_csr_sign($csr, $ca_certificate, $private_key, $days, $options, $serial);
    } elseif ($options !== null) {
        $safeResult = \openssl_csr_sign($csr, $ca_certificate, $private_key, $days, $options);
    } else {
        $safeResult = \openssl_csr_sign($csr, $ca_certificate, $private_key, $days);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param string $cipher_algo
 * @param string $passphrase
 * @param int $options
 * @param string $iv
 * @param null|string $tag
 * @param string $aad
 * @return string
 * @throws OpensslException
 *
 */
function openssl_decrypt(string $data, string $cipher_algo, string $passphrase, int $options = 0, string $iv = "", ?string $tag = null, string $aad = ""): string
{
    error_clear_last();
    if ($aad !== "") {
        $safeResult = \openssl_decrypt($data, $cipher_algo, $passphrase, $options, $iv, $tag, $aad);
    } elseif ($tag !== null) {
        $safeResult = \openssl_decrypt($data, $cipher_algo, $passphrase, $options, $iv, $tag);
    } else {
        $safeResult = \openssl_decrypt($data, $cipher_algo, $passphrase, $options, $iv);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $public_key
 * @param \OpenSSLAsymmetricKey $private_key
 * @return string
 * @throws OpensslException
 *
 */
function openssl_dh_compute_key(string $public_key, \OpenSSLAsymmetricKey $private_key): string
{
    error_clear_last();
    $safeResult = \openssl_dh_compute_key($public_key, $private_key);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param string $digest_algo
 * @param bool $binary
 * @return string
 * @throws OpensslException
 *
 */
function openssl_digest(string $data, string $digest_algo, bool $binary = false): string
{
    error_clear_last();
    $safeResult = \openssl_digest($data, $digest_algo, $binary);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return list
 * @throws OpensslException
 *
 */
function openssl_get_curve_names(): array
{
    error_clear_last();
    $safeResult = \openssl_get_curve_names();
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param null|string $output
 * @param string $encrypted_key
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param string $cipher_algo
 * @param null|string $iv
 * @throws OpensslException
 *
 */
function openssl_open(string $data, ?string &$output, string $encrypted_key, $private_key, string $cipher_algo, ?string $iv = null): void
{
    error_clear_last();
    if ($iv !== null) {
        $safeResult = \openssl_open($data, $output, $encrypted_key, $private_key, $cipher_algo, $iv);
    } else {
        $safeResult = \openssl_open($data, $output, $encrypted_key, $private_key, $cipher_algo);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $password
 * @param string $salt
 * @param int $key_length
 * @param int $iterations
 * @param string $digest_algo
 * @return string
 * @throws OpensslException
 *
 */
function openssl_pbkdf2(string $password, string $salt, int $key_length, int $iterations, string $digest_algo = "sha1"): string
{
    error_clear_last();
    $safeResult = \openssl_pbkdf2($password, $salt, $key_length, $iterations, $digest_algo);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLCertificate|string $certificate
 * @param string $output_filename
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param string $passphrase
 * @param array $options
 * @throws OpensslException
 *
 */
function openssl_pkcs12_export_to_file($certificate, string $output_filename, $private_key, string $passphrase, array $options = []): void
{
    error_clear_last();
    $safeResult = \openssl_pkcs12_export_to_file($certificate, $output_filename, $private_key, $passphrase, $options);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLCertificate|string $certificate
 * @param null|string $output
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param string $passphrase
 * @param array $options
 * @throws OpensslException
 *
 */
function openssl_pkcs12_export($certificate, ?string &$output, $private_key, string $passphrase, array $options = []): void
{
    error_clear_last();
    $safeResult = \openssl_pkcs12_export($certificate, $output, $private_key, $passphrase, $options);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $pkcs12
 * @param array|null $certificates
 * @param string $passphrase
 * @throws OpensslException
 *
 */
function openssl_pkcs12_read(string $pkcs12, ?array &$certificates, string $passphrase): void
{
    error_clear_last();
    $safeResult = \openssl_pkcs12_read($pkcs12, $certificates, $passphrase);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $input_filename
 * @param string $output_filename
 * @param \OpenSSLCertificate|string $certificate
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|null|string $private_key
 * @throws OpensslException
 *
 */
function openssl_pkcs7_decrypt(string $input_filename, string $output_filename, $certificate, $private_key = null): void
{
    error_clear_last();
    if ($private_key !== null) {
        $safeResult = \openssl_pkcs7_decrypt($input_filename, $output_filename, $certificate, $private_key);
    } else {
        $safeResult = \openssl_pkcs7_decrypt($input_filename, $output_filename, $certificate);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $input_filename
 * @param string $output_filename
 * @param \OpenSSLCertificate|array|string $certificate
 * @param array $headers
 * @param int $flags
 * @param int $cipher_algo
 * @throws OpensslException
 *
 */
function openssl_pkcs7_encrypt(string $input_filename, string $output_filename, $certificate, array $headers, int $flags = 0, int $cipher_algo = OPENSSL_CIPHER_AES_128_CBC): void
{
    error_clear_last();
    $safeResult = \openssl_pkcs7_encrypt($input_filename, $output_filename, $certificate, $headers, $flags, $cipher_algo);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $data
 * @param array|null $certificates
 * @throws OpensslException
 *
 */
function openssl_pkcs7_read(string $data, ?array &$certificates): void
{
    error_clear_last();
    $safeResult = \openssl_pkcs7_read($data, $certificates);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $input_filename
 * @param string $output_filename
 * @param \OpenSSLCertificate|string $certificate
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param array $headers
 * @param int $flags
 * @param null|string $untrusted_certificates_filename
 * @throws OpensslException
 *
 */
function openssl_pkcs7_sign(string $input_filename, string $output_filename, $certificate, $private_key, array $headers, int $flags = PKCS7_DETACHED, ?string $untrusted_certificates_filename = null): void
{
    error_clear_last();
    if ($untrusted_certificates_filename !== null) {
        $safeResult = \openssl_pkcs7_sign($input_filename, $output_filename, $certificate, $private_key, $headers, $flags, $untrusted_certificates_filename);
    } else {
        $safeResult = \openssl_pkcs7_sign($input_filename, $output_filename, $certificate, $private_key, $headers, $flags);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $public_key
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param int $key_length
 * @return string
 * @throws OpensslException
 *
 */
function openssl_pkey_derive($public_key, $private_key, int $key_length = 0): string
{
    error_clear_last();
    $safeResult = \openssl_pkey_derive($public_key, $private_key, $key_length);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $key
 * @param string $output_filename
 * @param null|string $passphrase
 * @param array|null $options
 * @throws OpensslException
 *
 */
function openssl_pkey_export_to_file($key, string $output_filename, ?string $passphrase = null, ?array $options = null): void
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \openssl_pkey_export_to_file($key, $output_filename, $passphrase, $options);
    } elseif ($passphrase !== null) {
        $safeResult = \openssl_pkey_export_to_file($key, $output_filename, $passphrase);
    } else {
        $safeResult = \openssl_pkey_export_to_file($key, $output_filename);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $key
 * @param null|string $output
 * @param null|string $passphrase
 * @param array|null $options
 * @throws OpensslException
 *
 */
function openssl_pkey_export($key, ?string &$output, ?string $passphrase = null, ?array $options = null): void
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \openssl_pkey_export($key, $output, $passphrase, $options);
    } elseif ($passphrase !== null) {
        $safeResult = \openssl_pkey_export($key, $output, $passphrase);
    } else {
        $safeResult = \openssl_pkey_export($key, $output);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLAsymmetricKey $key
 * @return array
 * @throws OpensslException
 *
 */
function openssl_pkey_get_details(\OpenSSLAsymmetricKey $key): array
{
    error_clear_last();
    $safeResult = \openssl_pkey_get_details($key);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param null|string $passphrase
 * @return \OpenSSLAsymmetricKey
 * @throws OpensslException
 *
 */
function openssl_pkey_get_private($private_key, ?string $passphrase = null): \OpenSSLAsymmetricKey
{
    error_clear_last();
    if ($passphrase !== null) {
        $safeResult = \openssl_pkey_get_private($private_key, $passphrase);
    } else {
        $safeResult = \openssl_pkey_get_private($private_key);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $public_key
 * @return \OpenSSLAsymmetricKey
 * @throws OpensslException
 *
 */
function openssl_pkey_get_public($public_key): \OpenSSLAsymmetricKey
{
    error_clear_last();
    $safeResult = \openssl_pkey_get_public($public_key);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param array|null $options
 * @return \OpenSSLAsymmetricKey
 * @throws OpensslException
 *
 */
function openssl_pkey_new(?array $options = null): \OpenSSLAsymmetricKey
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \openssl_pkey_new($options);
    } else {
        $safeResult = \openssl_pkey_new();
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param null|string $decrypted_data
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param int $padding
 * @throws OpensslException
 *
 */
function openssl_private_decrypt(string $data, ?string &$decrypted_data, $private_key, int $padding = OPENSSL_PKCS1_PADDING): void
{
    error_clear_last();
    $safeResult = \openssl_private_decrypt($data, $decrypted_data, $private_key, $padding);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $data
 * @param null|string $encrypted_data
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param int $padding
 * @throws OpensslException
 *
 */
function openssl_private_encrypt(string $data, ?string &$encrypted_data, $private_key, int $padding = OPENSSL_PKCS1_PADDING): void
{
    error_clear_last();
    $safeResult = \openssl_private_encrypt($data, $encrypted_data, $private_key, $padding);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $data
 * @param null|string $decrypted_data
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $public_key
 * @param int $padding
 * @throws OpensslException
 *
 */
function openssl_public_decrypt(string $data, ?string &$decrypted_data, $public_key, int $padding = OPENSSL_PKCS1_PADDING): void
{
    error_clear_last();
    $safeResult = \openssl_public_decrypt($data, $decrypted_data, $public_key, $padding);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $data
 * @param null|string $encrypted_data
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $public_key
 * @param int $padding
 * @throws OpensslException
 *
 */
function openssl_public_encrypt(string $data, ?string &$encrypted_data, $public_key, int $padding = OPENSSL_PKCS1_PADDING): void
{
    error_clear_last();
    $safeResult = \openssl_public_encrypt($data, $encrypted_data, $public_key, $padding);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param int $length
 * @param bool|null $strong_result
 * @return string
 *
 */
function openssl_random_pseudo_bytes(int $length, ?bool &$strong_result = null): string
{
    error_clear_last();
    $safeResult = \openssl_random_pseudo_bytes($length, $strong_result);
    return $safeResult;
}


/**
 * @param string $data
 * @param null|string $sealed_data
 * @param array|null $encrypted_keys
 * @param array $public_key
 * @param string $cipher_algo
 * @param null|string $iv
 * @return int
 * @throws OpensslException
 *
 */
function openssl_seal(string $data, ?string &$sealed_data, ?array &$encrypted_keys, array $public_key, string $cipher_algo, ?string &$iv = null): int
{
    error_clear_last();
    $safeResult = \openssl_seal($data, $sealed_data, $encrypted_keys, $public_key, $cipher_algo, $iv);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param null|string $signature
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param int|string $algorithm
 * @throws OpensslException
 *
 */
function openssl_sign(string $data, ?string &$signature, $private_key, $algorithm = OPENSSL_ALGO_SHA1): void
{
    error_clear_last();
    $safeResult = \openssl_sign($data, $signature, $private_key, $algorithm);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $spki
 * @return null|string
 * @throws OpensslException
 *
 */
function openssl_spki_export_challenge(string $spki): ?string
{
    error_clear_last();
    $safeResult = \openssl_spki_export_challenge($spki);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $spki
 * @return null|string
 * @throws OpensslException
 *
 */
function openssl_spki_export(string $spki): ?string
{
    error_clear_last();
    $safeResult = \openssl_spki_export($spki);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLAsymmetricKey $private_key
 * @param string $challenge
 * @param int $digest_algo
 * @return null|string
 * @throws OpensslException
 *
 */
function openssl_spki_new(\OpenSSLAsymmetricKey $private_key, string $challenge, int $digest_algo = OPENSSL_ALGO_MD5): ?string
{
    error_clear_last();
    $safeResult = \openssl_spki_new($private_key, $challenge, $digest_algo);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $spki
 * @throws OpensslException
 *
 */
function openssl_spki_verify(string $spki): void
{
    error_clear_last();
    $safeResult = \openssl_spki_verify($spki);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $data
 * @param string $signature
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $public_key
 * @param int|string $algorithm
 * @return -1|0|1
 * @throws OpensslException
 *
 */
function openssl_verify(string $data, string $signature, $public_key, $algorithm = OPENSSL_ALGO_SHA1): int
{
    error_clear_last();
    $safeResult = \openssl_verify($data, $signature, $public_key, $algorithm);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLCertificate|string $certificate
 * @param int $purpose
 * @param array $ca_info
 * @param null|string $untrusted_certificates_file
 * @return bool|int
 * @throws OpensslException
 *
 */
function openssl_x509_checkpurpose($certificate, int $purpose, array $ca_info = [], ?string $untrusted_certificates_file = null)
{
    error_clear_last();
    if ($untrusted_certificates_file !== null) {
        $safeResult = \openssl_x509_checkpurpose($certificate, $purpose, $ca_info, $untrusted_certificates_file);
    } else {
        $safeResult = \openssl_x509_checkpurpose($certificate, $purpose, $ca_info);
    }
    if ($safeResult === -1) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLCertificate|string $certificate
 * @param string $output_filename
 * @param bool $no_text
 * @throws OpensslException
 *
 */
function openssl_x509_export_to_file($certificate, string $output_filename, bool $no_text = true): void
{
    error_clear_last();
    $safeResult = \openssl_x509_export_to_file($certificate, $output_filename, $no_text);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLCertificate|string $certificate
 * @param null|string $output
 * @param bool $no_text
 * @throws OpensslException
 *
 */
function openssl_x509_export($certificate, ?string &$output, bool $no_text = true): void
{
    error_clear_last();
    $safeResult = \openssl_x509_export($certificate, $output, $no_text);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLCertificate|string $certificate
 * @param string $digest_algo
 * @param bool $binary
 * @return string
 * @throws OpensslException
 *
 */
function openssl_x509_fingerprint($certificate, string $digest_algo = "sha1", bool $binary = false): string
{
    error_clear_last();
    $safeResult = \openssl_x509_fingerprint($certificate, $digest_algo, $binary);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLCertificate|string $certificate
 * @return \OpenSSLCertificate
 * @throws OpensslException
 *
 */
function openssl_x509_read($certificate): \OpenSSLCertificate
{
    error_clear_last();
    $safeResult = \openssl_x509_read($certificate);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\StreamException;

/**
 * @param resource $context
 * @param array $options
 * @return true
 * @throws StreamException
 *
 */
function stream_context_set_options($context, array $options): void
{
    error_clear_last();
    $safeResult = \stream_context_set_options($context, $options);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param resource $context
 * @param array $params
 * @throws StreamException
 *
 */
function stream_context_set_params($context, array $params): void
{
    error_clear_last();
    $safeResult = \stream_context_set_params($context, $params);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param resource $from
 * @param resource $to
 * @param int|null $length
 * @param int $offset
 * @return int
 * @throws StreamException
 *
 */
function stream_copy_to_stream($from, $to, ?int $length = null, int $offset = 0): int
{
    error_clear_last();
    if ($offset !== 0) {
        $safeResult = \stream_copy_to_stream($from, $to, $length, $offset);
    } elseif ($length !== null) {
        $safeResult = \stream_copy_to_stream($from, $to, $length);
    } else {
        $safeResult = \stream_copy_to_stream($from, $to);
    }
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @param string $filtername
 * @param int $read_write
 * @param mixed $params
 * @return resource
 * @throws StreamException
 *
 */
function stream_filter_append($stream, string $filtername, ?int $read_write = null, $params = null)
{
    error_clear_last();
    if ($params !== null) {
        $safeResult = \stream_filter_append($stream, $filtername, $read_write, $params);
    } elseif ($read_write !== null) {
        $safeResult = \stream_filter_append($stream, $filtername, $read_write);
    } else {
        $safeResult = \stream_filter_append($stream, $filtername);
    }
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @param string $filtername
 * @param int $read_write
 * @param mixed $params
 * @return resource
 * @throws StreamException
 *
 */
function stream_filter_prepend($stream, string $filtername, ?int $read_write = null, $params = null)
{
    error_clear_last();
    if ($params !== null) {
        $safeResult = \stream_filter_prepend($stream, $filtername, $read_write, $params);
    } elseif ($read_write !== null) {
        $safeResult = \stream_filter_prepend($stream, $filtername, $read_write);
    } else {
        $safeResult = \stream_filter_prepend($stream, $filtername);
    }
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filter_name
 * @param string $class
 * @throws StreamException
 *
 */
function stream_filter_register(string $filter_name, string $class): void
{
    error_clear_last();
    $safeResult = \stream_filter_register($filter_name, $class);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param resource $stream_filter
 * @throws StreamException
 *
 */
function stream_filter_remove($stream_filter): void
{
    error_clear_last();
    $safeResult = \stream_filter_remove($stream_filter);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param resource $stream
 * @param int|null $length
 * @param int $offset
 * @return string
 * @throws StreamException
 *
 */
function stream_get_contents($stream, ?int $length = null, int $offset = -1): string
{
    error_clear_last();
    if ($offset !== -1) {
        $safeResult = \stream_get_contents($stream, $length, $offset);
    } elseif ($length !== null) {
        $safeResult = \stream_get_contents($stream, $length);
    } else {
        $safeResult = \stream_get_contents($stream);
    }
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @param int $length
 * @param string $ending
 * @return string
 * @throws StreamException
 *
 */
function stream_get_line($stream, int $length, string $ending = ""): string
{
    error_clear_last();
    $safeResult = \stream_get_line($stream, $length, $ending);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @throws StreamException
 *
 */
function stream_isatty($stream): void
{
    error_clear_last();
    $safeResult = \stream_isatty($stream);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @return string
 * @throws StreamException
 *
 */
function stream_resolve_include_path(string $filename): string
{
    error_clear_last();
    $safeResult = \stream_resolve_include_path($filename);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @param bool $enable
 * @throws StreamException
 *
 */
function stream_set_blocking($stream, bool $enable): void
{
    error_clear_last();
    $safeResult = \stream_set_blocking($stream, $enable);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param resource $stream
 * @param int $seconds
 * @param int $microseconds
 * @throws StreamException
 *
 */
function stream_set_timeout($stream, int $seconds, int $microseconds = 0): void
{
    error_clear_last();
    $safeResult = \stream_set_timeout($stream, $seconds, $microseconds);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param resource $socket
 * @param float|null $timeout
 * @param null|string $peer_name
 * @return resource
 * @throws StreamException
 *
 */
function stream_socket_accept($socket, ?float $timeout = null, ?string &$peer_name = null)
{
    error_clear_last();
    if ($peer_name !== null) {
        $safeResult = \stream_socket_accept($socket, $timeout, $peer_name);
    } elseif ($timeout !== null) {
        $safeResult = \stream_socket_accept($socket, $timeout);
    } else {
        $safeResult = \stream_socket_accept($socket);
    }
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $address
 * @param int|null $error_code
 * @param null|string $error_message
 * @param float|null $timeout
 * @param int-mask $flags
 * @param null|resource $context
 * @return resource
 * @throws StreamException
 *
 */
function stream_socket_client(string $address, ?int &$error_code = null, ?string &$error_message = null, ?float $timeout = null, int $flags = STREAM_CLIENT_CONNECT, $context = null)
{
    error_clear_last();
    if ($context !== null) {
        $safeResult = \stream_socket_client($address, $error_code, $error_message, $timeout, $flags, $context);
    } elseif ($flags !== STREAM_CLIENT_CONNECT) {
        $safeResult = \stream_socket_client($address, $error_code, $error_message, $timeout, $flags);
    } elseif ($timeout !== null) {
        $safeResult = \stream_socket_client($address, $error_code, $error_message, $timeout);
    } else {
        $safeResult = \stream_socket_client($address, $error_code, $error_message);
    }
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $socket
 * @param bool $remote
 * @return string
 * @throws StreamException
 *
 */
function stream_socket_get_name($socket, bool $remote): string
{
    error_clear_last();
    $safeResult = \stream_socket_get_name($socket, $remote);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $domain
 * @param int $type
 * @param int $protocol
 * @return resource[]
 * @throws StreamException
 *
 */
function stream_socket_pair(int $domain, int $type, int $protocol): array
{
    error_clear_last();
    $safeResult = \stream_socket_pair($domain, $type, $protocol);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $socket
 * @param int $length
 * @param int $flags
 * @param null|string $address
 * @return string
 * @throws StreamException
 *
 */
function stream_socket_recvfrom($socket, int $length, int $flags = 0, ?string &$address = null): string
{
    error_clear_last();
    $safeResult = \stream_socket_recvfrom($socket, $length, $flags, $address);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $socket
 * @param string $data
 * @param int $flags
 * @param string $address
 * @return int
 * @throws StreamException
 *
 */
function stream_socket_sendto($socket, string $data, int $flags = 0, string $address = ""): int
{
    error_clear_last();
    $safeResult = \stream_socket_sendto($socket, $data, $flags, $address);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $address
 * @param int|null $error_code
 * @param null|string $error_message
 * @param int $flags
 * @param null|resource $context
 * @return resource
 * @throws StreamException
 *
 */
function stream_socket_server(string $address, ?int &$error_code = null, ?string &$error_message = null, int $flags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $context = null)
{
    error_clear_last();
    if ($context !== null) {
        $safeResult = \stream_socket_server($address, $error_code, $error_message, $flags, $context);
    } else {
        $safeResult = \stream_socket_server($address, $error_code, $error_message, $flags);
    }
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @param int $mode
 * @throws StreamException
 *
 */
function stream_socket_shutdown($stream, int $mode): void
{
    error_clear_last();
    $safeResult = \stream_socket_shutdown($stream, $mode);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param resource $stream
 * @throws StreamException
 *
 */
function stream_supports_lock($stream): void
{
    error_clear_last();
    $safeResult = \stream_supports_lock($stream);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param string $protocol
 * @param string $class
 * @param int $flags
 * @throws StreamException
 *
 */
function stream_wrapper_register(string $protocol, string $class, int $flags = 0): void
{
    error_clear_last();
    $safeResult = \stream_wrapper_register($protocol, $class, $flags);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param string $protocol
 * @throws StreamException
 *
 */
function stream_wrapper_restore(string $protocol): void
{
    error_clear_last();
    $safeResult = \stream_wrapper_restore($protocol);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param string $protocol
 * @throws StreamException
 *
 */
function stream_wrapper_unregister(string $protocol): void
{
    error_clear_last();
    $safeResult = \stream_wrapper_unregister($protocol);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}
<?php

return [
    'apache_getenv',
    'apache_get_version',
    'apache_lookup_uri',
    'apache_setenv',
    'apcu_cache_info',
    'apcu_cas',
    'apcu_dec',
    'apcu_fetch',
    'apcu_inc',
    'apcu_sma_info',
    'base64_decode',
    'bindtextdomain',
    'bzclose',
    'bzflush',
    'bzopen',
    'bzread',
    'bzwrite',
    'chdir',
    'chgrp',
    'chmod',
    'chown',
    'chroot',
    'class_alias',
    'class_implements',
    'class_parents',
    'class_uses',
    'cli_set_process_title',
    'com_create_guid',
    'com_event_sink',
    'com_load_typelib',
    'com_print_typeinfo',
    'convert_uudecode',
    'copy',
    'cubrid_bind',
    'cubrid_column_names',
    'cubrid_column_types',
    'cubrid_col_size',
    'cubrid_commit',
    'cubrid_connect',
    'cubrid_connect_with_url',
    'cubrid_current_oid',
    'cubrid_disconnect',
    'cubrid_drop',
    'cubrid_free_result',
    'cubrid_get_charset',
    'cubrid_get_class_name',
    'cubrid_get_client_info',
    'cubrid_get_db_parameter',
    'cubrid_get_query_timeout',
    'cubrid_get_server_info',
    'cubrid_insert_id',
    'cubrid_lob2_bind',
    'cubrid_lob2_close',
    'cubrid_lob2_export',
    'cubrid_lob2_import',
    'cubrid_lob2_new',
    'cubrid_lob2_read',
    'cubrid_lob2_seek',
    'cubrid_lob2_seek64',
    'cubrid_lob2_size',
    'cubrid_lob2_size64',
    'cubrid_lob2_tell',
    'cubrid_lob2_tell64',
    'cubrid_lob2_write',
    'cubrid_lob_close',
    'cubrid_lob_export',
    'cubrid_lob_get',
    'cubrid_lob_send',
    'cubrid_lob_size',
    'cubrid_lock_read',
    'cubrid_lock_write',
    'cubrid_move_cursor',
    'cubrid_next_result',
    'cubrid_pconnect',
    'cubrid_pconnect_with_url',
    'cubrid_prepare',
    'cubrid_put',
    'cubrid_rollback',
    'cubrid_schema',
    'cubrid_seq_drop',
    'cubrid_seq_insert',
    'cubrid_seq_put',
    'cubrid_set_add',
    'cubrid_set_autocommit',
    'cubrid_set_db_parameter',
    'cubrid_set_drop',
    'cubrid_set_query_timeout',
    'curl_copy_handle',
    'curl_escape',
    'curl_exec',
    'curl_getinfo',
    'curl_init',
    'curl_multi_info_read',
    'curl_multi_setopt',
    'curl_setopt',
    'curl_share_setopt',
    'curl_unescape',
    'curl_upkeep',
    'date_create',
    'date_create_immutable',
    'date_parse_from_format',
    'date_sunrise',
    'date_sunset',
    'db2_autocommit',
    'db2_bind_param',
    'db2_client_info',
    'db2_close',
    'db2_commit',
    'db2_execute',
    'db2_free_result',
    'db2_free_stmt',
    'db2_get_option',
    'db2_num_rows',
    'db2_pclose',
    'db2_rollback',
    'db2_server_info',
    'db2_set_option',
    'define',
    'deflate_add',
    'deflate_init',
    'dir',
    'disk_free_space',
    'disk_total_space',
    'dl',
    'dns_get_record',
    'eio_busy',
    'eio_chmod',
    'eio_chown',
    'eio_close',
    'eio_custom',
    'eio_dup2',
    'eio_event_loop',
    'eio_fallocate',
    'eio_fchmod',
    'eio_fchown',
    'eio_fdatasync',
    'eio_fstat',
    'eio_fstatvfs',
    'eio_fsync',
    'eio_ftruncate',
    'eio_futime',
    'eio_grp',
    'eio_lstat',
    'eio_mkdir',
    'eio_mknod',
    'eio_nop',
    'eio_readahead',
    'eio_readdir',
    'eio_readlink',
    'eio_rename',
    'eio_rmdir',
    'eio_seek',
    'eio_sendfile',
    'eio_stat',
    'eio_statvfs',
    'eio_symlink',
    'eio_sync',
    'eio_syncfs',
    'eio_sync_file_range',
    'eio_truncate',
    'eio_unlink',
    'eio_utime',
    'eio_write',
    'error_log',
    'exec',
    'fastcgi_finish_request',
    'fbird_blob_cancel',
    'fclose',
    'fdatasync',
    'fflush',
    'fgetcsv',
    'file',
    'fileatime',
    'filectime',
    'fileinode',
    'filemtime',
    'fileowner',
    'fileperms',
    'filesize',
    'filetype',
    'file_get_contents',
    'file_put_contents',
    'filter_input_array',
    'filter_var_array',
    'finfo_close',
    'finfo_open',
    'flock',
    'fopen',
    'fpm_get_status',
    'fputcsv',
    'fread',
    'fsockopen',
    'fstat',
    'fsync',
    'ftell',
    'ftp_alloc',
    'ftp_append',
    'ftp_cdup',
    'ftp_chdir',
    'ftp_chmod',
    'ftp_close',
    'ftp_connect',
    'ftp_delete',
    'ftp_fget',
    'ftp_fput',
    'ftp_get',
    'ftp_login',
    'ftp_mkdir',
    'ftp_mlsd',
    'ftp_nb_get',
    'ftp_nb_put',
    'ftp_nlist',
    'ftp_pasv',
    'ftp_put',
    'ftp_pwd',
    'ftp_raw',
    'ftp_rename',
    'ftp_rmdir',
    'ftp_site',
    'ftp_size',
    'ftp_ssl_connect',
    'ftp_systype',
    'ftruncate',
    'fwrite',
    'getcwd',
    'gethostname',
    'getimagesize',
    'getlastmod',
    'getmygid',
    'getmyinode',
    'getmypid',
    'getmyuid',
    'getopt',
    'getprotobyname',
    'getprotobynumber',
    'getrusage',
    'getservbyport',
    'get_cfg_var',
    'get_headers',
    'get_include_path',
    'get_meta_tags',
    'glob',
    'gmmktime',
    'gmstrftime',
    'gnupg_adddecryptkey',
    'gnupg_addencryptkey',
    'gnupg_addsignkey',
    'gnupg_cleardecryptkeys',
    'gnupg_clearencryptkeys',
    'gnupg_clearsignkeys',
    'gnupg_deletekey',
    'gnupg_setarmor',
    'gnupg_setsignmode',
    'gzclose',
    'gzcompress',
    'gzdecode',
    'gzdeflate',
    'gzencode',
    'gzfile',
    'gzgets',
    'gzinflate',
    'gzopen',
    'gzread',
    'gzrewind',
    'gztell',
    'gzuncompress',
    'gzwrite',
    'hash_file',
    'hash_hmac_file',
    'hash_update_file',
    'header_register_callback',
    'hex2bin',
    'highlight_file',
    'hrtime',
    'ibase_add_user',
    'ibase_backup',
    'ibase_blob_cancel',
    'ibase_blob_create',
    'ibase_blob_get',
    'ibase_close',
    'ibase_commit',
    'ibase_commit_ret',
    'ibase_connect',
    'ibase_delete_user',
    'ibase_drop_db',
    'ibase_free_event_handler',
    'ibase_free_query',
    'ibase_free_result',
    'ibase_maintain_db',
    'ibase_modify_user',
    'ibase_name_result',
    'ibase_pconnect',
    'ibase_restore',
    'ibase_rollback',
    'ibase_rollback_ret',
    'ibase_service_attach',
    'ibase_service_detach',
    'iconv',
    'iconv_get_encoding',
    'iconv_mime_decode',
    'iconv_mime_encode',
    'iconv_set_encoding',
    'iconv_strlen',
    'idate',
    'imageaffine',
    'imageaffinematrixconcat',
    'imageaffinematrixget',
    'imagealphablending',
    'imageantialias',
    'imagearc',
    'imageavif',
    'imagebmp',
    'imagechar',
    'imagecharup',
    'imagecolorat',
    'imagecolordeallocate',
    'imagecolormatch',
    'imagecolorset',
    'imageconvolution',
    'imagecopy',
    'imagecopymerge',
    'imagecopymergegray',
    'imagecopyresampled',
    'imagecopyresized',
    'imagecreate',
    'imagecreatefromavif',
    'imagecreatefrombmp',
    'imagecreatefromgd',
    'imagecreatefromgd2',
    'imagecreatefromgd2part',
    'imagecreatefromgif',
    'imagecreatefromjpeg',
    'imagecreatefrompng',
    'imagecreatefromstring',
    'imagecreatefromtga',
    'imagecreatefromwbmp',
    'imagecreatefromwebp',
    'imagecreatefromxbm',
    'imagecreatefromxpm',
    'imagecreatetruecolor',
    'imagecrop',
    'imagecropauto',
    'imagedashedline',
    'imagedestroy',
    'imageellipse',
    'imagefill',
    'imagefilledarc',
    'imagefilledellipse',
    'imagefilledrectangle',
    'imagefilltoborder',
    'imagefilter',
    'imageflip',
    'imageftbbox',
    'imagefttext',
    'imagegammacorrect',
    'imagegd',
    'imagegd2',
    'imagegif',
    'imagegrabscreen',
    'imagegrabwindow',
    'imagejpeg',
    'imagelayereffect',
    'imageline',
    'imageloadfont',
    'imagepng',
    'imagerectangle',
    'imageresolution',
    'imagerotate',
    'imagesavealpha',
    'imagescale',
    'imagesetbrush',
    'imagesetclip',
    'imagesetinterpolation',
    'imagesetpixel',
    'imagesetstyle',
    'imagesetthickness',
    'imagesettile',
    'imagestring',
    'imagestringup',
    'imagetruecolortopalette',
    'imagettfbbox',
    'imagettftext',
    'imagewbmp',
    'imagewebp',
    'imagexbm',
    'image_type_to_extension',
    'imap_8bit',
    'imap_append',
    'imap_base64',
    'imap_binary',
    'imap_body',
    'imap_bodystruct',
    'imap_check',
    'imap_createmailbox',
    'imap_deletemailbox',
    'imap_fetchbody',
    'imap_fetchheader',
    'imap_fetchmime',
    'imap_fetchstructure',
    'imap_fetch_overview',
    'imap_getacl',
    'imap_getmailboxes',
    'imap_getsubscribed',
    'imap_headerinfo',
    'imap_headers',
    'imap_listscan',
    'imap_lsub',
    'imap_mail',
    'imap_mail_compose',
    'imap_mail_copy',
    'imap_mail_move',
    'imap_mime_header_decode',
    'imap_mutf7_to_utf8',
    'imap_num_msg',
    'imap_open',
    'imap_qprint',
    'imap_renamemailbox',
    'imap_rfc822_write_address',
    'imap_savebody',
    'imap_setacl',
    'imap_set_quota',
    'imap_sort',
    'imap_status',
    'imap_subscribe',
    'imap_thread',
    'imap_timeout',
    'imap_unsubscribe',
    'imap_utf8_to_mutf7',
    'inet_ntop',
    'inet_pton',
    'inflate_add',
    'inflate_get_read_len',
    'inflate_init',
    'ini_get',
    'ini_set',
    'inotify_add_watch',
    'inotify_init',
    'inotify_rm_watch',
    'iptcembed',
    'iptcparse',
    'json_decode',
    'json_encode',
    'lchgrp',
    'lchown',
    'ldap_8859_to_t61',
    'ldap_add',
    'ldap_bind',
    'ldap_compare',
    'ldap_control_paged_result',
    'ldap_control_paged_result_response',
    'ldap_count_entries',
    'ldap_delete',
    'ldap_dn2ufn',
    'ldap_exop',
    'ldap_exop_passwd',
    'ldap_exop_whoami',
    'ldap_explode_dn',
    'ldap_first_attribute',
    'ldap_first_entry',
    'ldap_free_result',
    'ldap_get_dn',
    'ldap_get_entries',
    'ldap_get_option',
    'ldap_get_values',
    'ldap_get_values_len',
    'ldap_modify_batch',
    'ldap_mod_add',
    'ldap_mod_del',
    'ldap_mod_replace',
    'ldap_next_attribute',
    'ldap_parse_exop',
    'ldap_parse_result',
    'ldap_rename',
    'ldap_sasl_bind',
    'ldap_set_option',
    'ldap_unbind',
    'libxml_set_external_entity_loader',
    'link',
    'lstat',
    'lzf_compress',
    'lzf_decompress',
    'mailparse_msg_extract_part_file',
    'mailparse_msg_free',
    'mailparse_msg_parse',
    'mailparse_msg_parse_file',
    'mailparse_stream_encode',
    'mb_chr',
    'mb_convert_encoding',
    'mb_convert_variables',
    'mb_detect_order',
    'mb_eregi_replace',
    'mb_ereg_replace',
    'mb_ereg_replace_callback',
    'mb_ereg_search_getregs',
    'mb_ereg_search_init',
    'mb_ereg_search_regs',
    'mb_ereg_search_setpos',
    'mb_get_info',
    'mb_http_output',
    'mb_internal_encoding',
    'mb_ord',
    'mb_parse_str',
    'mb_regex_encoding',
    'mb_send_mail',
    'mb_split',
    'md5_file',
    'mime_content_type',
    'mkdir',
    'mktime',
    'msg_get_queue',
    'msg_queue_exists',
    'msg_receive',
    'msg_remove_queue',
    'msg_send',
    'msg_set_queue',
    'msg_stat_queue',
    'mysql_close',
    'mysql_connect',
    'mysql_create_db',
    'mysql_data_seek',
    'mysql_db_name',
    'mysql_db_query',
    'mysql_drop_db',
    'mysql_fetch_lengths',
    'mysql_field_flags',
    'mysql_field_len',
    'mysql_field_name',
    'mysql_field_seek',
    'mysql_free_result',
    'mysql_get_host_info',
    'mysql_get_proto_info',
    'mysql_get_server_info',
    'mysql_info',
    'mysql_list_dbs',
    'mysql_list_fields',
    'mysql_list_processes',
    'mysql_list_tables',
    'mysql_num_fields',
    'mysql_num_rows',
    'mysql_query',
    'mysql_real_escape_string',
    'mysql_result',
    'mysql_select_db',
    'mysql_set_charset',
    'mysql_tablename',
    'mysql_thread_id',
    'mysql_unbuffered_query',
    'net_get_interfaces',
    'ob_clean',
    'ob_end_clean',
    'ob_end_flush',
    'ob_flush',
    'ob_get_clean',
    'ob_get_flush',
    'ob_start',
    'oci_bind_array_by_name',
    'oci_bind_by_name',
    'oci_cancel',
    'oci_commit',
    'oci_connect',
    'oci_define_by_name',
    'oci_execute',
    'oci_field_name',
    'oci_field_precision',
    'oci_field_scale',
    'oci_field_size',
    'oci_field_type',
    'oci_field_type_raw',
    'oci_free_descriptor',
    'oci_free_statement',
    'oci_new_collection',
    'oci_new_connect',
    'oci_new_cursor',
    'oci_new_descriptor',
    'oci_num_rows',
    'oci_parse',
    'oci_pconnect',
    'oci_register_taf_callback',
    'oci_result',
    'oci_rollback',
    'oci_server_version',
    'oci_set_action',
    'oci_set_call_timeout',
    'oci_set_client_identifier',
    'oci_set_client_info',
    'oci_set_db_operation',
    'oci_set_edition',
    'oci_set_module_name',
    'oci_set_prefetch',
    'oci_set_prefetch_lob',
    'oci_statement_type',
    'oci_unregister_taf_callback',
    'odbc_autocommit',
    'odbc_commit',
    'odbc_connect',
    'odbc_cursor',
    'odbc_data_source',
    'odbc_exec',
    'odbc_execute',
    'odbc_fetch_into',
    'odbc_field_len',
    'odbc_field_name',
    'odbc_field_num',
    'odbc_field_scale',
    'odbc_field_type',
    'odbc_num_fields',
    'odbc_pconnect',
    'odbc_prepare',
    'odbc_procedures',
    'odbc_result',
    'odbc_result_all',
    'odbc_rollback',
    'odbc_setoption',
    'odbc_tables',
    'opcache_compile_file',
    'opcache_get_status',
    'opendir',
    'openssl_cipher_iv_length',
    'openssl_cipher_key_length',
    'openssl_cms_decrypt',
    'openssl_cms_encrypt',
    'openssl_cms_read',
    'openssl_cms_sign',
    'openssl_cms_verify',
    'openssl_csr_export',
    'openssl_csr_export_to_file',
    'openssl_csr_get_public_key',
    'openssl_csr_get_subject',
    'openssl_csr_new',
    'openssl_csr_sign',
    'openssl_decrypt',
    'openssl_dh_compute_key',
    'openssl_digest',
    'openssl_encrypt',
    'openssl_get_curve_names',
    'openssl_open',
    'openssl_pbkdf2',
    'openssl_pkcs7_decrypt',
    'openssl_pkcs7_encrypt',
    'openssl_pkcs7_read',
    'openssl_pkcs7_sign',
    'openssl_pkcs12_export',
    'openssl_pkcs12_export_to_file',
    'openssl_pkcs12_read',
    'openssl_pkey_derive',
    'openssl_pkey_export',
    'openssl_pkey_export_to_file',
    'openssl_pkey_get_details',
    'openssl_pkey_get_private',
    'openssl_pkey_get_public',
    'openssl_pkey_new',
    'openssl_private_decrypt',
    'openssl_private_encrypt',
    'openssl_public_decrypt',
    'openssl_public_encrypt',
    'openssl_seal',
    'openssl_sign',
    'openssl_spki_export',
    'openssl_spki_export_challenge',
    'openssl_spki_new',
    'openssl_spki_verify',
    'openssl_verify',
    'openssl_x509_checkpurpose',
    'openssl_x509_export',
    'openssl_x509_export_to_file',
    'openssl_x509_fingerprint',
    'openssl_x509_read',
    'output_add_rewrite_var',
    'output_reset_rewrite_vars',
    'parse_ini_file',
    'parse_ini_string',
    'parse_url',
    'passthru',
    'pclose',
    'pcntl_getpriority',
    'pcntl_setpriority',
    'pcntl_signal',
    'pcntl_signal_dispatch',
    'pcntl_sigprocmask',
    'pcntl_sigtimedwait',
    'pcntl_sigwaitinfo',
    'pfsockopen',
    'pg_cancel_query',
    'pg_connect',
    'pg_connection_reset',
    'pg_convert',
    'pg_copy_from',
    'pg_copy_to',
    'pg_delete',
    'pg_end_copy',
    'pg_execute',
    'pg_field_num',
    'pg_field_table',
    'pg_flush',
    'pg_free_result',
    'pg_host',
    'pg_insert',
    'pg_last_oid',
    'pg_lo_close',
    'pg_lo_export',
    'pg_lo_import',
    'pg_lo_open',
    'pg_lo_read',
    'pg_lo_seek',
    'pg_lo_truncate',
    'pg_lo_unlink',
    'pg_lo_write',
    'pg_meta_data',
    'pg_parameter_status',
    'pg_pconnect',
    'pg_ping',
    'pg_prepare',
    'pg_put_line',
    'pg_query',
    'pg_query_params',
    'pg_result_error_field',
    'pg_result_seek',
    'pg_select',
    'pg_set_chunked_rows_size',
    'pg_socket',
    'pg_trace',
    'pg_update',
    'php_sapi_name',
    'popen',
    'posix_access',
    'posix_eaccess',
    'posix_getgrgid',
    'posix_getgrnam',
    'posix_getgroups',
    'posix_getlogin',
    'posix_getpgid',
    'posix_getpwuid',
    'posix_getrlimit',
    'posix_getsid',
    'posix_initgroups',
    'posix_kill',
    'posix_mkfifo',
    'posix_mknod',
    'posix_setegid',
    'posix_seteuid',
    'posix_setgid',
    'posix_setpgid',
    'posix_setrlimit',
    'posix_setsid',
    'posix_setuid',
    'posix_times',
    'posix_uname',
    'preg_grep',
    'preg_match',
    'preg_match_all',
    'preg_replace',
    'preg_replace_callback',
    'preg_replace_callback_array',
    'preg_split',
    'proc_close',
    'proc_nice',
    'proc_open',
    'pspell_add_to_personal',
    'pspell_add_to_session',
    'pspell_clear_session',
    'pspell_config_data_dir',
    'pspell_config_dict_dir',
    'pspell_config_ignore',
    'pspell_config_mode',
    'pspell_config_personal',
    'pspell_config_repl',
    'pspell_config_runtogether',
    'pspell_config_save_repl',
    'pspell_new',
    'pspell_new_config',
    'pspell_new_personal',
    'pspell_save_wordlist',
    'pspell_store_replacement',
    'ps_add_launchlink',
    'ps_add_locallink',
    'ps_add_note',
    'ps_add_pdflink',
    'ps_add_weblink',
    'ps_arc',
    'ps_arcn',
    'ps_begin_page',
    'ps_begin_pattern',
    'ps_begin_template',
    'ps_circle',
    'ps_clip',
    'ps_close',
    'ps_closepath',
    'ps_closepath_stroke',
    'ps_close_image',
    'ps_continue_text',
    'ps_curveto',
    'ps_delete',
    'ps_end_page',
    'ps_end_pattern',
    'ps_end_template',
    'ps_fill',
    'ps_fill_stroke',
    'ps_get_parameter',
    'ps_hyphenate',
    'ps_include_file',
    'ps_lineto',
    'ps_moveto',
    'ps_new',
    'ps_open_file',
    'ps_place_image',
    'ps_rect',
    'ps_restore',
    'ps_rotate',
    'ps_save',
    'ps_scale',
    'ps_setcolor',
    'ps_setdash',
    'ps_setflat',
    'ps_setfont',
    'ps_setgray',
    'ps_setlinecap',
    'ps_setlinejoin',
    'ps_setlinewidth',
    'ps_setmiterlimit',
    'ps_setoverprintmode',
    'ps_setpolydash',
    'ps_set_border_color',
    'ps_set_border_dash',
    'ps_set_border_style',
    'ps_set_info',
    'ps_set_parameter',
    'ps_set_text_pos',
    'ps_set_value',
    'ps_shading',
    'ps_shading_pattern',
    'ps_shfill',
    'ps_show',
    'ps_show2',
    'ps_show_xy',
    'ps_show_xy2',
    'ps_stroke',
    'ps_symbol',
    'ps_translate',
    'putenv',
    'readfile',
    'readgzfile',
    'readline_completion_function',
    'readline_read_history',
    'readline_write_history',
    'readlink',
    'realpath',
    'register_tick_function',
    'rename',
    'rewind',
    'rmdir',
    'rnp_decrypt',
    'rnp_dump_packets',
    'rnp_dump_packets_to_json',
    'rnp_ffi_create',
    'rnp_ffi_set_pass_provider',
    'rnp_import_keys',
    'rnp_import_signatures',
    'rnp_key_export',
    'rnp_key_export_autocrypt',
    'rnp_key_export_revocation',
    'rnp_key_get_info',
    'rnp_key_remove',
    'rnp_key_revoke',
    'rnp_list_keys',
    'rnp_load_keys',
    'rnp_load_keys_from_path',
    'rnp_locate_key',
    'rnp_op_encrypt',
    'rnp_op_generate_key',
    'rnp_op_sign',
    'rnp_op_sign_cleartext',
    'rnp_op_sign_detached',
    'rnp_op_verify',
    'rnp_op_verify_detached',
    'rnp_save_keys',
    'rnp_save_keys_to_path',
    'rnp_supported_features',
    'rpmaddtag',
    'rrd_create',
    'rrd_first',
    'rrd_graph',
    'rrd_info',
    'rrd_lastupdate',
    'rrd_restore',
    'rrd_tune',
    'rrd_update',
    'rrd_xport',
    'sapi_windows_cp_conv',
    'sapi_windows_cp_set',
    'sapi_windows_generate_ctrl_event',
    'sapi_windows_set_ctrl_handler',
    'sapi_windows_vt100_support',
    'scandir',
    'sem_acquire',
    'sem_get',
    'sem_release',
    'sem_remove',
    'session_abort',
    'session_cache_expire',
    'session_cache_limiter',
    'session_create_id',
    'session_decode',
    'session_destroy',
    'session_encode',
    'session_gc',
    'session_id',
    'session_module_name',
    'session_name',
    'session_regenerate_id',
    'session_reset',
    'session_save_path',
    'session_start',
    'session_unset',
    'session_write_close',
    'settype',
    'set_include_path',
    'set_time_limit',
    'sha1_file',
    'shell_exec',
    'shmop_delete',
    'shmop_read',
    'shm_attach',
    'shm_detach',
    'shm_put_var',
    'shm_remove',
    'shm_remove_var',
    'simplexml_import_dom',
    'simplexml_load_file',
    'simplexml_load_string',
    'socket_accept',
    'socket_addrinfo_bind',
    'socket_addrinfo_connect',
    'socket_addrinfo_lookup',
    'socket_atmark',
    'socket_bind',
    'socket_connect',
    'socket_create',
    'socket_create_listen',
    'socket_create_pair',
    'socket_export_stream',
    'socket_getpeername',
    'socket_getsockname',
    'socket_get_option',
    'socket_import_stream',
    'socket_listen',
    'socket_read',
    'socket_send',
    'socket_sendmsg',
    'socket_sendto',
    'socket_set_block',
    'socket_set_nonblock',
    'socket_set_option',
    'socket_shutdown',
    'socket_write',
    'socket_wsaprotocol_info_export',
    'socket_wsaprotocol_info_import',
    'socket_wsaprotocol_info_release',
    'sodium_crypto_aead_aegis128l_decrypt',
    'sodium_crypto_aead_aegis256_decrypt',
    'sodium_crypto_aead_aes256gcm_decrypt',
    'sodium_crypto_aead_chacha20poly1305_decrypt',
    'sodium_crypto_aead_chacha20poly1305_encrypt',
    'sodium_crypto_aead_chacha20poly1305_ietf_decrypt',
    'sodium_crypto_aead_chacha20poly1305_ietf_encrypt',
    'sodium_crypto_aead_xchacha20poly1305_ietf_decrypt',
    'sodium_crypto_aead_xchacha20poly1305_ietf_encrypt',
    'sodium_crypto_box_open',
    'sodium_crypto_box_seal_open',
    'sodium_crypto_secretbox_open',
    'sodium_crypto_sign_open',
    'sodium_crypto_stream_xchacha20_xor_ic',
    'solr_get_version',
    'spl_autoload_register',
    'spl_autoload_unregister',
    'sqlsrv_begin_transaction',
    'sqlsrv_cancel',
    'sqlsrv_client_info',
    'sqlsrv_close',
    'sqlsrv_commit',
    'sqlsrv_configure',
    'sqlsrv_execute',
    'sqlsrv_fetch',
    'sqlsrv_fetch_array',
    'sqlsrv_fetch_object',
    'sqlsrv_free_stmt',
    'sqlsrv_get_field',
    'sqlsrv_next_result',
    'sqlsrv_num_fields',
    'sqlsrv_num_rows',
    'sqlsrv_prepare',
    'sqlsrv_query',
    'sqlsrv_rollback',
    'ssdeep_fuzzy_compare',
    'ssdeep_fuzzy_hash',
    'ssdeep_fuzzy_hash_filename',
    'ssh2_auth_agent',
    'ssh2_auth_hostbased_file',
    'ssh2_auth_password',
    'ssh2_auth_pubkey_file',
    'ssh2_connect',
    'ssh2_disconnect',
    'ssh2_exec',
    'ssh2_forward_accept',
    'ssh2_forward_listen',
    'ssh2_publickey_add',
    'ssh2_publickey_init',
    'ssh2_publickey_remove',
    'ssh2_scp_recv',
    'ssh2_scp_send',
    'ssh2_send_eof',
    'ssh2_sftp',
    'ssh2_sftp_chmod',
    'ssh2_sftp_mkdir',
    'ssh2_sftp_rename',
    'ssh2_sftp_rmdir',
    'ssh2_sftp_symlink',
    'ssh2_sftp_unlink',
    'ssh2_shell',
    'stream_context_set_options',
    'stream_context_set_params',
    'stream_copy_to_stream',
    'stream_filter_append',
    'stream_filter_prepend',
    'stream_filter_register',
    'stream_filter_remove',
    'stream_get_contents',
    'stream_get_line',
    'stream_isatty',
    'stream_resolve_include_path',
    'stream_set_blocking',
    'stream_set_timeout',
    'stream_socket_accept',
    'stream_socket_client',
    'stream_socket_get_name',
    'stream_socket_pair',
    'stream_socket_recvfrom',
    'stream_socket_sendto',
    'stream_socket_server',
    'stream_socket_shutdown',
    'stream_supports_lock',
    'stream_wrapper_register',
    'stream_wrapper_restore',
    'stream_wrapper_unregister',
    'strftime',
    'strptime',
    'strtotime',
    'swoole_async_dns_lookup',
    'swoole_async_readfile',
    'swoole_async_write',
    'swoole_async_writefile',
    'swoole_event_defer',
    'swoole_event_del',
    'swoole_event_write',
    'symlink',
    'system',
    'sys_getloadavg',
    'tempnam',
    'timezone_name_from_abbr',
    'time_nanosleep',
    'time_sleep_until',
    'tmpfile',
    'touch',
    'unixtojd',
    'unlink',
    'unpack',
    'unserialize',
    'uopz_extend',
    'uopz_implement',
    'variant_date_to_timestamp',
    'variant_round',
    'virtual',
    'xdiff_file_bdiff',
    'xdiff_file_bpatch',
    'xdiff_file_diff',
    'xdiff_file_diff_binary',
    'xdiff_file_patch_binary',
    'xdiff_file_rabdiff',
    'xdiff_string_bpatch',
    'xdiff_string_patch',
    'xdiff_string_patch_binary',
    'xmlrpc_set_type',
    'xml_parser_free',
    'xml_parser_set_option',
    'yaml_parse',
    'yaml_parse_file',
    'yaml_parse_url',
    'yaz_ccl_parse',
    'yaz_close',
    'yaz_connect',
    'yaz_database',
    'yaz_element',
    'yaz_present',
    'yaz_search',
    'yaz_wait',
    'zip_entry_close',
    'zip_entry_compressedsize',
    'zip_entry_compressionmethod',
    'zip_entry_filesize',
    'zip_entry_name',
    'zip_entry_open',
    'zip_entry_read',
    'zlib_decode',
];
<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\Renaming\Rector\FuncCall\RenameFunctionRector;

// This file configures rector/rector to replace all PHP functions with their equivalent "safe" functions.
return static function (RectorConfig $rectorConfig): void {
    $rectorConfig->ruleWithConfiguration(
        RenameFunctionRector::class,
        [            'apache_getenv' => 'Safe\apache_getenv',
            'apache_get_version' => 'Safe\apache_get_version',
            'apache_lookup_uri' => 'Safe\apache_lookup_uri',
            'apache_setenv' => 'Safe\apache_setenv',
            'apcu_cache_info' => 'Safe\apcu_cache_info',
            'apcu_cas' => 'Safe\apcu_cas',
            'apcu_dec' => 'Safe\apcu_dec',
            'apcu_fetch' => 'Safe\apcu_fetch',
            'apcu_inc' => 'Safe\apcu_inc',
            'apcu_sma_info' => 'Safe\apcu_sma_info',
            'base64_decode' => 'Safe\base64_decode',
            'bindtextdomain' => 'Safe\bindtextdomain',
            'bzclose' => 'Safe\bzclose',
            'bzflush' => 'Safe\bzflush',
            'bzopen' => 'Safe\bzopen',
            'bzread' => 'Safe\bzread',
            'bzwrite' => 'Safe\bzwrite',
            'chdir' => 'Safe\chdir',
            'chgrp' => 'Safe\chgrp',
            'chmod' => 'Safe\chmod',
            'chown' => 'Safe\chown',
            'chroot' => 'Safe\chroot',
            'class_alias' => 'Safe\class_alias',
            'class_implements' => 'Safe\class_implements',
            'class_parents' => 'Safe\class_parents',
            'class_uses' => 'Safe\class_uses',
            'cli_set_process_title' => 'Safe\cli_set_process_title',
            'com_create_guid' => 'Safe\com_create_guid',
            'com_event_sink' => 'Safe\com_event_sink',
            'com_load_typelib' => 'Safe\com_load_typelib',
            'com_print_typeinfo' => 'Safe\com_print_typeinfo',
            'convert_uudecode' => 'Safe\convert_uudecode',
            'copy' => 'Safe\copy',
            'cubrid_bind' => 'Safe\cubrid_bind',
            'cubrid_column_names' => 'Safe\cubrid_column_names',
            'cubrid_column_types' => 'Safe\cubrid_column_types',
            'cubrid_col_size' => 'Safe\cubrid_col_size',
            'cubrid_commit' => 'Safe\cubrid_commit',
            'cubrid_connect' => 'Safe\cubrid_connect',
            'cubrid_connect_with_url' => 'Safe\cubrid_connect_with_url',
            'cubrid_current_oid' => 'Safe\cubrid_current_oid',
            'cubrid_disconnect' => 'Safe\cubrid_disconnect',
            'cubrid_drop' => 'Safe\cubrid_drop',
            'cubrid_free_result' => 'Safe\cubrid_free_result',
            'cubrid_get_charset' => 'Safe\cubrid_get_charset',
            'cubrid_get_class_name' => 'Safe\cubrid_get_class_name',
            'cubrid_get_client_info' => 'Safe\cubrid_get_client_info',
            'cubrid_get_db_parameter' => 'Safe\cubrid_get_db_parameter',
            'cubrid_get_query_timeout' => 'Safe\cubrid_get_query_timeout',
            'cubrid_get_server_info' => 'Safe\cubrid_get_server_info',
            'cubrid_insert_id' => 'Safe\cubrid_insert_id',
            'cubrid_lob2_bind' => 'Safe\cubrid_lob2_bind',
            'cubrid_lob2_close' => 'Safe\cubrid_lob2_close',
            'cubrid_lob2_export' => 'Safe\cubrid_lob2_export',
            'cubrid_lob2_import' => 'Safe\cubrid_lob2_import',
            'cubrid_lob2_new' => 'Safe\cubrid_lob2_new',
            'cubrid_lob2_read' => 'Safe\cubrid_lob2_read',
            'cubrid_lob2_seek' => 'Safe\cubrid_lob2_seek',
            'cubrid_lob2_seek64' => 'Safe\cubrid_lob2_seek64',
            'cubrid_lob2_size' => 'Safe\cubrid_lob2_size',
            'cubrid_lob2_size64' => 'Safe\cubrid_lob2_size64',
            'cubrid_lob2_tell' => 'Safe\cubrid_lob2_tell',
            'cubrid_lob2_tell64' => 'Safe\cubrid_lob2_tell64',
            'cubrid_lob2_write' => 'Safe\cubrid_lob2_write',
            'cubrid_lob_close' => 'Safe\cubrid_lob_close',
            'cubrid_lob_export' => 'Safe\cubrid_lob_export',
            'cubrid_lob_get' => 'Safe\cubrid_lob_get',
            'cubrid_lob_send' => 'Safe\cubrid_lob_send',
            'cubrid_lob_size' => 'Safe\cubrid_lob_size',
            'cubrid_lock_read' => 'Safe\cubrid_lock_read',
            'cubrid_lock_write' => 'Safe\cubrid_lock_write',
            'cubrid_move_cursor' => 'Safe\cubrid_move_cursor',
            'cubrid_next_result' => 'Safe\cubrid_next_result',
            'cubrid_pconnect' => 'Safe\cubrid_pconnect',
            'cubrid_pconnect_with_url' => 'Safe\cubrid_pconnect_with_url',
            'cubrid_prepare' => 'Safe\cubrid_prepare',
            'cubrid_put' => 'Safe\cubrid_put',
            'cubrid_rollback' => 'Safe\cubrid_rollback',
            'cubrid_schema' => 'Safe\cubrid_schema',
            'cubrid_seq_drop' => 'Safe\cubrid_seq_drop',
            'cubrid_seq_insert' => 'Safe\cubrid_seq_insert',
            'cubrid_seq_put' => 'Safe\cubrid_seq_put',
            'cubrid_set_add' => 'Safe\cubrid_set_add',
            'cubrid_set_autocommit' => 'Safe\cubrid_set_autocommit',
            'cubrid_set_db_parameter' => 'Safe\cubrid_set_db_parameter',
            'cubrid_set_drop' => 'Safe\cubrid_set_drop',
            'cubrid_set_query_timeout' => 'Safe\cubrid_set_query_timeout',
            'curl_copy_handle' => 'Safe\curl_copy_handle',
            'curl_escape' => 'Safe\curl_escape',
            'curl_exec' => 'Safe\curl_exec',
            'curl_getinfo' => 'Safe\curl_getinfo',
            'curl_init' => 'Safe\curl_init',
            'curl_multi_info_read' => 'Safe\curl_multi_info_read',
            'curl_multi_setopt' => 'Safe\curl_multi_setopt',
            'curl_setopt' => 'Safe\curl_setopt',
            'curl_share_setopt' => 'Safe\curl_share_setopt',
            'curl_unescape' => 'Safe\curl_unescape',
            'curl_upkeep' => 'Safe\curl_upkeep',
            'date_create' => 'Safe\date_create',
            'date_create_immutable' => 'Safe\date_create_immutable',
            'date_parse_from_format' => 'Safe\date_parse_from_format',
            'date_sunrise' => 'Safe\date_sunrise',
            'date_sunset' => 'Safe\date_sunset',
            'db2_autocommit' => 'Safe\db2_autocommit',
            'db2_bind_param' => 'Safe\db2_bind_param',
            'db2_client_info' => 'Safe\db2_client_info',
            'db2_close' => 'Safe\db2_close',
            'db2_commit' => 'Safe\db2_commit',
            'db2_execute' => 'Safe\db2_execute',
            'db2_free_result' => 'Safe\db2_free_result',
            'db2_free_stmt' => 'Safe\db2_free_stmt',
            'db2_get_option' => 'Safe\db2_get_option',
            'db2_num_rows' => 'Safe\db2_num_rows',
            'db2_pclose' => 'Safe\db2_pclose',
            'db2_rollback' => 'Safe\db2_rollback',
            'db2_server_info' => 'Safe\db2_server_info',
            'db2_set_option' => 'Safe\db2_set_option',
            'define' => 'Safe\define',
            'deflate_add' => 'Safe\deflate_add',
            'deflate_init' => 'Safe\deflate_init',
            'dir' => 'Safe\dir',
            'disk_free_space' => 'Safe\disk_free_space',
            'disk_total_space' => 'Safe\disk_total_space',
            'dl' => 'Safe\dl',
            'dns_get_record' => 'Safe\dns_get_record',
            'eio_busy' => 'Safe\eio_busy',
            'eio_chmod' => 'Safe\eio_chmod',
            'eio_chown' => 'Safe\eio_chown',
            'eio_close' => 'Safe\eio_close',
            'eio_custom' => 'Safe\eio_custom',
            'eio_dup2' => 'Safe\eio_dup2',
            'eio_event_loop' => 'Safe\eio_event_loop',
            'eio_fallocate' => 'Safe\eio_fallocate',
            'eio_fchmod' => 'Safe\eio_fchmod',
            'eio_fchown' => 'Safe\eio_fchown',
            'eio_fdatasync' => 'Safe\eio_fdatasync',
            'eio_fstat' => 'Safe\eio_fstat',
            'eio_fstatvfs' => 'Safe\eio_fstatvfs',
            'eio_fsync' => 'Safe\eio_fsync',
            'eio_ftruncate' => 'Safe\eio_ftruncate',
            'eio_futime' => 'Safe\eio_futime',
            'eio_grp' => 'Safe\eio_grp',
            'eio_lstat' => 'Safe\eio_lstat',
            'eio_mkdir' => 'Safe\eio_mkdir',
            'eio_mknod' => 'Safe\eio_mknod',
            'eio_nop' => 'Safe\eio_nop',
            'eio_readahead' => 'Safe\eio_readahead',
            'eio_readdir' => 'Safe\eio_readdir',
            'eio_readlink' => 'Safe\eio_readlink',
            'eio_rename' => 'Safe\eio_rename',
            'eio_rmdir' => 'Safe\eio_rmdir',
            'eio_seek' => 'Safe\eio_seek',
            'eio_sendfile' => 'Safe\eio_sendfile',
            'eio_stat' => 'Safe\eio_stat',
            'eio_statvfs' => 'Safe\eio_statvfs',
            'eio_symlink' => 'Safe\eio_symlink',
            'eio_sync' => 'Safe\eio_sync',
            'eio_syncfs' => 'Safe\eio_syncfs',
            'eio_sync_file_range' => 'Safe\eio_sync_file_range',
            'eio_truncate' => 'Safe\eio_truncate',
            'eio_unlink' => 'Safe\eio_unlink',
            'eio_utime' => 'Safe\eio_utime',
            'eio_write' => 'Safe\eio_write',
            'error_log' => 'Safe\error_log',
            'exec' => 'Safe\exec',
            'fastcgi_finish_request' => 'Safe\fastcgi_finish_request',
            'fbird_blob_cancel' => 'Safe\fbird_blob_cancel',
            'fclose' => 'Safe\fclose',
            'fdatasync' => 'Safe\fdatasync',
            'fflush' => 'Safe\fflush',
            'fgetcsv' => 'Safe\fgetcsv',
            'file' => 'Safe\file',
            'fileatime' => 'Safe\fileatime',
            'filectime' => 'Safe\filectime',
            'fileinode' => 'Safe\fileinode',
            'filemtime' => 'Safe\filemtime',
            'fileowner' => 'Safe\fileowner',
            'fileperms' => 'Safe\fileperms',
            'filesize' => 'Safe\filesize',
            'filetype' => 'Safe\filetype',
            'file_get_contents' => 'Safe\file_get_contents',
            'file_put_contents' => 'Safe\file_put_contents',
            'filter_input_array' => 'Safe\filter_input_array',
            'filter_var_array' => 'Safe\filter_var_array',
            'finfo_close' => 'Safe\finfo_close',
            'finfo_open' => 'Safe\finfo_open',
            'flock' => 'Safe\flock',
            'fopen' => 'Safe\fopen',
            'fpm_get_status' => 'Safe\fpm_get_status',
            'fputcsv' => 'Safe\fputcsv',
            'fread' => 'Safe\fread',
            'fsockopen' => 'Safe\fsockopen',
            'fstat' => 'Safe\fstat',
            'fsync' => 'Safe\fsync',
            'ftell' => 'Safe\ftell',
            'ftp_alloc' => 'Safe\ftp_alloc',
            'ftp_append' => 'Safe\ftp_append',
            'ftp_cdup' => 'Safe\ftp_cdup',
            'ftp_chdir' => 'Safe\ftp_chdir',
            'ftp_chmod' => 'Safe\ftp_chmod',
            'ftp_close' => 'Safe\ftp_close',
            'ftp_connect' => 'Safe\ftp_connect',
            'ftp_delete' => 'Safe\ftp_delete',
            'ftp_fget' => 'Safe\ftp_fget',
            'ftp_fput' => 'Safe\ftp_fput',
            'ftp_get' => 'Safe\ftp_get',
            'ftp_login' => 'Safe\ftp_login',
            'ftp_mkdir' => 'Safe\ftp_mkdir',
            'ftp_mlsd' => 'Safe\ftp_mlsd',
            'ftp_nb_get' => 'Safe\ftp_nb_get',
            'ftp_nb_put' => 'Safe\ftp_nb_put',
            'ftp_nlist' => 'Safe\ftp_nlist',
            'ftp_pasv' => 'Safe\ftp_pasv',
            'ftp_put' => 'Safe\ftp_put',
            'ftp_pwd' => 'Safe\ftp_pwd',
            'ftp_raw' => 'Safe\ftp_raw',
            'ftp_rename' => 'Safe\ftp_rename',
            'ftp_rmdir' => 'Safe\ftp_rmdir',
            'ftp_site' => 'Safe\ftp_site',
            'ftp_size' => 'Safe\ftp_size',
            'ftp_ssl_connect' => 'Safe\ftp_ssl_connect',
            'ftp_systype' => 'Safe\ftp_systype',
            'ftruncate' => 'Safe\ftruncate',
            'fwrite' => 'Safe\fwrite',
            'getcwd' => 'Safe\getcwd',
            'gethostname' => 'Safe\gethostname',
            'getimagesize' => 'Safe\getimagesize',
            'getlastmod' => 'Safe\getlastmod',
            'getmygid' => 'Safe\getmygid',
            'getmyinode' => 'Safe\getmyinode',
            'getmypid' => 'Safe\getmypid',
            'getmyuid' => 'Safe\getmyuid',
            'getopt' => 'Safe\getopt',
            'getprotobyname' => 'Safe\getprotobyname',
            'getprotobynumber' => 'Safe\getprotobynumber',
            'getrusage' => 'Safe\getrusage',
            'getservbyport' => 'Safe\getservbyport',
            'get_cfg_var' => 'Safe\get_cfg_var',
            'get_headers' => 'Safe\get_headers',
            'get_include_path' => 'Safe\get_include_path',
            'get_meta_tags' => 'Safe\get_meta_tags',
            'glob' => 'Safe\glob',
            'gmmktime' => 'Safe\gmmktime',
            'gmstrftime' => 'Safe\gmstrftime',
            'gnupg_adddecryptkey' => 'Safe\gnupg_adddecryptkey',
            'gnupg_addencryptkey' => 'Safe\gnupg_addencryptkey',
            'gnupg_addsignkey' => 'Safe\gnupg_addsignkey',
            'gnupg_cleardecryptkeys' => 'Safe\gnupg_cleardecryptkeys',
            'gnupg_clearencryptkeys' => 'Safe\gnupg_clearencryptkeys',
            'gnupg_clearsignkeys' => 'Safe\gnupg_clearsignkeys',
            'gnupg_deletekey' => 'Safe\gnupg_deletekey',
            'gnupg_setarmor' => 'Safe\gnupg_setarmor',
            'gnupg_setsignmode' => 'Safe\gnupg_setsignmode',
            'gzclose' => 'Safe\gzclose',
            'gzcompress' => 'Safe\gzcompress',
            'gzdecode' => 'Safe\gzdecode',
            'gzdeflate' => 'Safe\gzdeflate',
            'gzencode' => 'Safe\gzencode',
            'gzfile' => 'Safe\gzfile',
            'gzgets' => 'Safe\gzgets',
            'gzinflate' => 'Safe\gzinflate',
            'gzopen' => 'Safe\gzopen',
            'gzread' => 'Safe\gzread',
            'gzrewind' => 'Safe\gzrewind',
            'gztell' => 'Safe\gztell',
            'gzuncompress' => 'Safe\gzuncompress',
            'gzwrite' => 'Safe\gzwrite',
            'hash_file' => 'Safe\hash_file',
            'hash_hmac_file' => 'Safe\hash_hmac_file',
            'hash_update_file' => 'Safe\hash_update_file',
            'header_register_callback' => 'Safe\header_register_callback',
            'hex2bin' => 'Safe\hex2bin',
            'highlight_file' => 'Safe\highlight_file',
            'hrtime' => 'Safe\hrtime',
            'ibase_add_user' => 'Safe\ibase_add_user',
            'ibase_backup' => 'Safe\ibase_backup',
            'ibase_blob_cancel' => 'Safe\ibase_blob_cancel',
            'ibase_blob_create' => 'Safe\ibase_blob_create',
            'ibase_blob_get' => 'Safe\ibase_blob_get',
            'ibase_close' => 'Safe\ibase_close',
            'ibase_commit' => 'Safe\ibase_commit',
            'ibase_commit_ret' => 'Safe\ibase_commit_ret',
            'ibase_connect' => 'Safe\ibase_connect',
            'ibase_delete_user' => 'Safe\ibase_delete_user',
            'ibase_drop_db' => 'Safe\ibase_drop_db',
            'ibase_free_event_handler' => 'Safe\ibase_free_event_handler',
            'ibase_free_query' => 'Safe\ibase_free_query',
            'ibase_free_result' => 'Safe\ibase_free_result',
            'ibase_maintain_db' => 'Safe\ibase_maintain_db',
            'ibase_modify_user' => 'Safe\ibase_modify_user',
            'ibase_name_result' => 'Safe\ibase_name_result',
            'ibase_pconnect' => 'Safe\ibase_pconnect',
            'ibase_restore' => 'Safe\ibase_restore',
            'ibase_rollback' => 'Safe\ibase_rollback',
            'ibase_rollback_ret' => 'Safe\ibase_rollback_ret',
            'ibase_service_attach' => 'Safe\ibase_service_attach',
            'ibase_service_detach' => 'Safe\ibase_service_detach',
            'iconv' => 'Safe\iconv',
            'iconv_get_encoding' => 'Safe\iconv_get_encoding',
            'iconv_mime_decode' => 'Safe\iconv_mime_decode',
            'iconv_mime_encode' => 'Safe\iconv_mime_encode',
            'iconv_set_encoding' => 'Safe\iconv_set_encoding',
            'iconv_strlen' => 'Safe\iconv_strlen',
            'idate' => 'Safe\idate',
            'imageaffine' => 'Safe\imageaffine',
            'imageaffinematrixconcat' => 'Safe\imageaffinematrixconcat',
            'imageaffinematrixget' => 'Safe\imageaffinematrixget',
            'imagealphablending' => 'Safe\imagealphablending',
            'imageantialias' => 'Safe\imageantialias',
            'imagearc' => 'Safe\imagearc',
            'imageavif' => 'Safe\imageavif',
            'imagebmp' => 'Safe\imagebmp',
            'imagechar' => 'Safe\imagechar',
            'imagecharup' => 'Safe\imagecharup',
            'imagecolorat' => 'Safe\imagecolorat',
            'imagecolordeallocate' => 'Safe\imagecolordeallocate',
            'imagecolormatch' => 'Safe\imagecolormatch',
            'imagecolorset' => 'Safe\imagecolorset',
            'imageconvolution' => 'Safe\imageconvolution',
            'imagecopy' => 'Safe\imagecopy',
            'imagecopymerge' => 'Safe\imagecopymerge',
            'imagecopymergegray' => 'Safe\imagecopymergegray',
            'imagecopyresampled' => 'Safe\imagecopyresampled',
            'imagecopyresized' => 'Safe\imagecopyresized',
            'imagecreate' => 'Safe\imagecreate',
            'imagecreatefromavif' => 'Safe\imagecreatefromavif',
            'imagecreatefrombmp' => 'Safe\imagecreatefrombmp',
            'imagecreatefromgd' => 'Safe\imagecreatefromgd',
            'imagecreatefromgd2' => 'Safe\imagecreatefromgd2',
            'imagecreatefromgd2part' => 'Safe\imagecreatefromgd2part',
            'imagecreatefromgif' => 'Safe\imagecreatefromgif',
            'imagecreatefromjpeg' => 'Safe\imagecreatefromjpeg',
            'imagecreatefrompng' => 'Safe\imagecreatefrompng',
            'imagecreatefromstring' => 'Safe\imagecreatefromstring',
            'imagecreatefromtga' => 'Safe\imagecreatefromtga',
            'imagecreatefromwbmp' => 'Safe\imagecreatefromwbmp',
            'imagecreatefromwebp' => 'Safe\imagecreatefromwebp',
            'imagecreatefromxbm' => 'Safe\imagecreatefromxbm',
            'imagecreatefromxpm' => 'Safe\imagecreatefromxpm',
            'imagecreatetruecolor' => 'Safe\imagecreatetruecolor',
            'imagecrop' => 'Safe\imagecrop',
            'imagecropauto' => 'Safe\imagecropauto',
            'imagedashedline' => 'Safe\imagedashedline',
            'imagedestroy' => 'Safe\imagedestroy',
            'imageellipse' => 'Safe\imageellipse',
            'imagefill' => 'Safe\imagefill',
            'imagefilledarc' => 'Safe\imagefilledarc',
            'imagefilledellipse' => 'Safe\imagefilledellipse',
            'imagefilledrectangle' => 'Safe\imagefilledrectangle',
            'imagefilltoborder' => 'Safe\imagefilltoborder',
            'imagefilter' => 'Safe\imagefilter',
            'imageflip' => 'Safe\imageflip',
            'imageftbbox' => 'Safe\imageftbbox',
            'imagefttext' => 'Safe\imagefttext',
            'imagegammacorrect' => 'Safe\imagegammacorrect',
            'imagegd' => 'Safe\imagegd',
            'imagegd2' => 'Safe\imagegd2',
            'imagegif' => 'Safe\imagegif',
            'imagegrabscreen' => 'Safe\imagegrabscreen',
            'imagegrabwindow' => 'Safe\imagegrabwindow',
            'imagejpeg' => 'Safe\imagejpeg',
            'imagelayereffect' => 'Safe\imagelayereffect',
            'imageline' => 'Safe\imageline',
            'imageloadfont' => 'Safe\imageloadfont',
            'imagepng' => 'Safe\imagepng',
            'imagerectangle' => 'Safe\imagerectangle',
            'imageresolution' => 'Safe\imageresolution',
            'imagerotate' => 'Safe\imagerotate',
            'imagesavealpha' => 'Safe\imagesavealpha',
            'imagescale' => 'Safe\imagescale',
            'imagesetbrush' => 'Safe\imagesetbrush',
            'imagesetclip' => 'Safe\imagesetclip',
            'imagesetinterpolation' => 'Safe\imagesetinterpolation',
            'imagesetpixel' => 'Safe\imagesetpixel',
            'imagesetstyle' => 'Safe\imagesetstyle',
            'imagesetthickness' => 'Safe\imagesetthickness',
            'imagesettile' => 'Safe\imagesettile',
            'imagestring' => 'Safe\imagestring',
            'imagestringup' => 'Safe\imagestringup',
            'imagetruecolortopalette' => 'Safe\imagetruecolortopalette',
            'imagettfbbox' => 'Safe\imagettfbbox',
            'imagettftext' => 'Safe\imagettftext',
            'imagewbmp' => 'Safe\imagewbmp',
            'imagewebp' => 'Safe\imagewebp',
            'imagexbm' => 'Safe\imagexbm',
            'image_type_to_extension' => 'Safe\image_type_to_extension',
            'imap_8bit' => 'Safe\imap_8bit',
            'imap_append' => 'Safe\imap_append',
            'imap_base64' => 'Safe\imap_base64',
            'imap_binary' => 'Safe\imap_binary',
            'imap_body' => 'Safe\imap_body',
            'imap_bodystruct' => 'Safe\imap_bodystruct',
            'imap_check' => 'Safe\imap_check',
            'imap_createmailbox' => 'Safe\imap_createmailbox',
            'imap_deletemailbox' => 'Safe\imap_deletemailbox',
            'imap_fetchbody' => 'Safe\imap_fetchbody',
            'imap_fetchheader' => 'Safe\imap_fetchheader',
            'imap_fetchmime' => 'Safe\imap_fetchmime',
            'imap_fetchstructure' => 'Safe\imap_fetchstructure',
            'imap_fetch_overview' => 'Safe\imap_fetch_overview',
            'imap_getacl' => 'Safe\imap_getacl',
            'imap_getmailboxes' => 'Safe\imap_getmailboxes',
            'imap_getsubscribed' => 'Safe\imap_getsubscribed',
            'imap_headerinfo' => 'Safe\imap_headerinfo',
            'imap_headers' => 'Safe\imap_headers',
            'imap_listscan' => 'Safe\imap_listscan',
            'imap_lsub' => 'Safe\imap_lsub',
            'imap_mail' => 'Safe\imap_mail',
            'imap_mail_compose' => 'Safe\imap_mail_compose',
            'imap_mail_copy' => 'Safe\imap_mail_copy',
            'imap_mail_move' => 'Safe\imap_mail_move',
            'imap_mime_header_decode' => 'Safe\imap_mime_header_decode',
            'imap_mutf7_to_utf8' => 'Safe\imap_mutf7_to_utf8',
            'imap_num_msg' => 'Safe\imap_num_msg',
            'imap_open' => 'Safe\imap_open',
            'imap_qprint' => 'Safe\imap_qprint',
            'imap_renamemailbox' => 'Safe\imap_renamemailbox',
            'imap_rfc822_write_address' => 'Safe\imap_rfc822_write_address',
            'imap_savebody' => 'Safe\imap_savebody',
            'imap_setacl' => 'Safe\imap_setacl',
            'imap_set_quota' => 'Safe\imap_set_quota',
            'imap_sort' => 'Safe\imap_sort',
            'imap_status' => 'Safe\imap_status',
            'imap_subscribe' => 'Safe\imap_subscribe',
            'imap_thread' => 'Safe\imap_thread',
            'imap_timeout' => 'Safe\imap_timeout',
            'imap_unsubscribe' => 'Safe\imap_unsubscribe',
            'imap_utf8_to_mutf7' => 'Safe\imap_utf8_to_mutf7',
            'inet_ntop' => 'Safe\inet_ntop',
            'inet_pton' => 'Safe\inet_pton',
            'inflate_add' => 'Safe\inflate_add',
            'inflate_get_read_len' => 'Safe\inflate_get_read_len',
            'inflate_init' => 'Safe\inflate_init',
            'ini_get' => 'Safe\ini_get',
            'ini_set' => 'Safe\ini_set',
            'inotify_add_watch' => 'Safe\inotify_add_watch',
            'inotify_init' => 'Safe\inotify_init',
            'inotify_rm_watch' => 'Safe\inotify_rm_watch',
            'iptcembed' => 'Safe\iptcembed',
            'iptcparse' => 'Safe\iptcparse',
            'json_decode' => 'Safe\json_decode',
            'json_encode' => 'Safe\json_encode',
            'lchgrp' => 'Safe\lchgrp',
            'lchown' => 'Safe\lchown',
            'ldap_8859_to_t61' => 'Safe\ldap_8859_to_t61',
            'ldap_add' => 'Safe\ldap_add',
            'ldap_bind' => 'Safe\ldap_bind',
            'ldap_compare' => 'Safe\ldap_compare',
            'ldap_control_paged_result' => 'Safe\ldap_control_paged_result',
            'ldap_control_paged_result_response' => 'Safe\ldap_control_paged_result_response',
            'ldap_count_entries' => 'Safe\ldap_count_entries',
            'ldap_delete' => 'Safe\ldap_delete',
            'ldap_dn2ufn' => 'Safe\ldap_dn2ufn',
            'ldap_exop' => 'Safe\ldap_exop',
            'ldap_exop_passwd' => 'Safe\ldap_exop_passwd',
            'ldap_exop_whoami' => 'Safe\ldap_exop_whoami',
            'ldap_explode_dn' => 'Safe\ldap_explode_dn',
            'ldap_first_attribute' => 'Safe\ldap_first_attribute',
            'ldap_first_entry' => 'Safe\ldap_first_entry',
            'ldap_free_result' => 'Safe\ldap_free_result',
            'ldap_get_dn' => 'Safe\ldap_get_dn',
            'ldap_get_entries' => 'Safe\ldap_get_entries',
            'ldap_get_option' => 'Safe\ldap_get_option',
            'ldap_get_values' => 'Safe\ldap_get_values',
            'ldap_get_values_len' => 'Safe\ldap_get_values_len',
            'ldap_modify_batch' => 'Safe\ldap_modify_batch',
            'ldap_mod_add' => 'Safe\ldap_mod_add',
            'ldap_mod_del' => 'Safe\ldap_mod_del',
            'ldap_mod_replace' => 'Safe\ldap_mod_replace',
            'ldap_next_attribute' => 'Safe\ldap_next_attribute',
            'ldap_parse_exop' => 'Safe\ldap_parse_exop',
            'ldap_parse_result' => 'Safe\ldap_parse_result',
            'ldap_rename' => 'Safe\ldap_rename',
            'ldap_sasl_bind' => 'Safe\ldap_sasl_bind',
            'ldap_set_option' => 'Safe\ldap_set_option',
            'ldap_unbind' => 'Safe\ldap_unbind',
            'libxml_set_external_entity_loader' => 'Safe\libxml_set_external_entity_loader',
            'link' => 'Safe\link',
            'lstat' => 'Safe\lstat',
            'lzf_compress' => 'Safe\lzf_compress',
            'lzf_decompress' => 'Safe\lzf_decompress',
            'mailparse_msg_extract_part_file' => 'Safe\mailparse_msg_extract_part_file',
            'mailparse_msg_free' => 'Safe\mailparse_msg_free',
            'mailparse_msg_parse' => 'Safe\mailparse_msg_parse',
            'mailparse_msg_parse_file' => 'Safe\mailparse_msg_parse_file',
            'mailparse_stream_encode' => 'Safe\mailparse_stream_encode',
            'mb_chr' => 'Safe\mb_chr',
            'mb_convert_encoding' => 'Safe\mb_convert_encoding',
            'mb_convert_variables' => 'Safe\mb_convert_variables',
            'mb_detect_order' => 'Safe\mb_detect_order',
            'mb_eregi_replace' => 'Safe\mb_eregi_replace',
            'mb_ereg_replace' => 'Safe\mb_ereg_replace',
            'mb_ereg_replace_callback' => 'Safe\mb_ereg_replace_callback',
            'mb_ereg_search_getregs' => 'Safe\mb_ereg_search_getregs',
            'mb_ereg_search_init' => 'Safe\mb_ereg_search_init',
            'mb_ereg_search_regs' => 'Safe\mb_ereg_search_regs',
            'mb_ereg_search_setpos' => 'Safe\mb_ereg_search_setpos',
            'mb_get_info' => 'Safe\mb_get_info',
            'mb_http_output' => 'Safe\mb_http_output',
            'mb_internal_encoding' => 'Safe\mb_internal_encoding',
            'mb_ord' => 'Safe\mb_ord',
            'mb_parse_str' => 'Safe\mb_parse_str',
            'mb_regex_encoding' => 'Safe\mb_regex_encoding',
            'mb_send_mail' => 'Safe\mb_send_mail',
            'mb_split' => 'Safe\mb_split',
            'md5_file' => 'Safe\md5_file',
            'mime_content_type' => 'Safe\mime_content_type',
            'mkdir' => 'Safe\mkdir',
            'mktime' => 'Safe\mktime',
            'msg_get_queue' => 'Safe\msg_get_queue',
            'msg_queue_exists' => 'Safe\msg_queue_exists',
            'msg_receive' => 'Safe\msg_receive',
            'msg_remove_queue' => 'Safe\msg_remove_queue',
            'msg_send' => 'Safe\msg_send',
            'msg_set_queue' => 'Safe\msg_set_queue',
            'msg_stat_queue' => 'Safe\msg_stat_queue',
            'mysql_close' => 'Safe\mysql_close',
            'mysql_connect' => 'Safe\mysql_connect',
            'mysql_create_db' => 'Safe\mysql_create_db',
            'mysql_data_seek' => 'Safe\mysql_data_seek',
            'mysql_db_name' => 'Safe\mysql_db_name',
            'mysql_db_query' => 'Safe\mysql_db_query',
            'mysql_drop_db' => 'Safe\mysql_drop_db',
            'mysql_fetch_lengths' => 'Safe\mysql_fetch_lengths',
            'mysql_field_flags' => 'Safe\mysql_field_flags',
            'mysql_field_len' => 'Safe\mysql_field_len',
            'mysql_field_name' => 'Safe\mysql_field_name',
            'mysql_field_seek' => 'Safe\mysql_field_seek',
            'mysql_free_result' => 'Safe\mysql_free_result',
            'mysql_get_host_info' => 'Safe\mysql_get_host_info',
            'mysql_get_proto_info' => 'Safe\mysql_get_proto_info',
            'mysql_get_server_info' => 'Safe\mysql_get_server_info',
            'mysql_info' => 'Safe\mysql_info',
            'mysql_list_dbs' => 'Safe\mysql_list_dbs',
            'mysql_list_fields' => 'Safe\mysql_list_fields',
            'mysql_list_processes' => 'Safe\mysql_list_processes',
            'mysql_list_tables' => 'Safe\mysql_list_tables',
            'mysql_num_fields' => 'Safe\mysql_num_fields',
            'mysql_num_rows' => 'Safe\mysql_num_rows',
            'mysql_query' => 'Safe\mysql_query',
            'mysql_real_escape_string' => 'Safe\mysql_real_escape_string',
            'mysql_result' => 'Safe\mysql_result',
            'mysql_select_db' => 'Safe\mysql_select_db',
            'mysql_set_charset' => 'Safe\mysql_set_charset',
            'mysql_tablename' => 'Safe\mysql_tablename',
            'mysql_thread_id' => 'Safe\mysql_thread_id',
            'mysql_unbuffered_query' => 'Safe\mysql_unbuffered_query',
            'net_get_interfaces' => 'Safe\net_get_interfaces',
            'ob_clean' => 'Safe\ob_clean',
            'ob_end_clean' => 'Safe\ob_end_clean',
            'ob_end_flush' => 'Safe\ob_end_flush',
            'ob_flush' => 'Safe\ob_flush',
            'ob_get_clean' => 'Safe\ob_get_clean',
            'ob_get_flush' => 'Safe\ob_get_flush',
            'ob_start' => 'Safe\ob_start',
            'oci_bind_array_by_name' => 'Safe\oci_bind_array_by_name',
            'oci_bind_by_name' => 'Safe\oci_bind_by_name',
            'oci_cancel' => 'Safe\oci_cancel',
            'oci_commit' => 'Safe\oci_commit',
            'oci_connect' => 'Safe\oci_connect',
            'oci_define_by_name' => 'Safe\oci_define_by_name',
            'oci_execute' => 'Safe\oci_execute',
            'oci_field_name' => 'Safe\oci_field_name',
            'oci_field_precision' => 'Safe\oci_field_precision',
            'oci_field_scale' => 'Safe\oci_field_scale',
            'oci_field_size' => 'Safe\oci_field_size',
            'oci_field_type' => 'Safe\oci_field_type',
            'oci_field_type_raw' => 'Safe\oci_field_type_raw',
            'oci_free_descriptor' => 'Safe\oci_free_descriptor',
            'oci_free_statement' => 'Safe\oci_free_statement',
            'oci_new_collection' => 'Safe\oci_new_collection',
            'oci_new_connect' => 'Safe\oci_new_connect',
            'oci_new_cursor' => 'Safe\oci_new_cursor',
            'oci_new_descriptor' => 'Safe\oci_new_descriptor',
            'oci_num_rows' => 'Safe\oci_num_rows',
            'oci_parse' => 'Safe\oci_parse',
            'oci_pconnect' => 'Safe\oci_pconnect',
            'oci_register_taf_callback' => 'Safe\oci_register_taf_callback',
            'oci_result' => 'Safe\oci_result',
            'oci_rollback' => 'Safe\oci_rollback',
            'oci_server_version' => 'Safe\oci_server_version',
            'oci_set_action' => 'Safe\oci_set_action',
            'oci_set_call_timeout' => 'Safe\oci_set_call_timeout',
            'oci_set_client_identifier' => 'Safe\oci_set_client_identifier',
            'oci_set_client_info' => 'Safe\oci_set_client_info',
            'oci_set_db_operation' => 'Safe\oci_set_db_operation',
            'oci_set_edition' => 'Safe\oci_set_edition',
            'oci_set_module_name' => 'Safe\oci_set_module_name',
            'oci_set_prefetch' => 'Safe\oci_set_prefetch',
            'oci_set_prefetch_lob' => 'Safe\oci_set_prefetch_lob',
            'oci_statement_type' => 'Safe\oci_statement_type',
            'oci_unregister_taf_callback' => 'Safe\oci_unregister_taf_callback',
            'odbc_autocommit' => 'Safe\odbc_autocommit',
            'odbc_commit' => 'Safe\odbc_commit',
            'odbc_connect' => 'Safe\odbc_connect',
            'odbc_cursor' => 'Safe\odbc_cursor',
            'odbc_data_source' => 'Safe\odbc_data_source',
            'odbc_exec' => 'Safe\odbc_exec',
            'odbc_execute' => 'Safe\odbc_execute',
            'odbc_fetch_into' => 'Safe\odbc_fetch_into',
            'odbc_field_len' => 'Safe\odbc_field_len',
            'odbc_field_name' => 'Safe\odbc_field_name',
            'odbc_field_num' => 'Safe\odbc_field_num',
            'odbc_field_scale' => 'Safe\odbc_field_scale',
            'odbc_field_type' => 'Safe\odbc_field_type',
            'odbc_num_fields' => 'Safe\odbc_num_fields',
            'odbc_pconnect' => 'Safe\odbc_pconnect',
            'odbc_prepare' => 'Safe\odbc_prepare',
            'odbc_procedures' => 'Safe\odbc_procedures',
            'odbc_result' => 'Safe\odbc_result',
            'odbc_result_all' => 'Safe\odbc_result_all',
            'odbc_rollback' => 'Safe\odbc_rollback',
            'odbc_setoption' => 'Safe\odbc_setoption',
            'odbc_tables' => 'Safe\odbc_tables',
            'opcache_compile_file' => 'Safe\opcache_compile_file',
            'opcache_get_status' => 'Safe\opcache_get_status',
            'opendir' => 'Safe\opendir',
            'openssl_cipher_iv_length' => 'Safe\openssl_cipher_iv_length',
            'openssl_cipher_key_length' => 'Safe\openssl_cipher_key_length',
            'openssl_cms_decrypt' => 'Safe\openssl_cms_decrypt',
            'openssl_cms_encrypt' => 'Safe\openssl_cms_encrypt',
            'openssl_cms_read' => 'Safe\openssl_cms_read',
            'openssl_cms_sign' => 'Safe\openssl_cms_sign',
            'openssl_cms_verify' => 'Safe\openssl_cms_verify',
            'openssl_csr_export' => 'Safe\openssl_csr_export',
            'openssl_csr_export_to_file' => 'Safe\openssl_csr_export_to_file',
            'openssl_csr_get_public_key' => 'Safe\openssl_csr_get_public_key',
            'openssl_csr_get_subject' => 'Safe\openssl_csr_get_subject',
            'openssl_csr_new' => 'Safe\openssl_csr_new',
            'openssl_csr_sign' => 'Safe\openssl_csr_sign',
            'openssl_decrypt' => 'Safe\openssl_decrypt',
            'openssl_dh_compute_key' => 'Safe\openssl_dh_compute_key',
            'openssl_digest' => 'Safe\openssl_digest',
            'openssl_encrypt' => 'Safe\openssl_encrypt',
            'openssl_get_curve_names' => 'Safe\openssl_get_curve_names',
            'openssl_open' => 'Safe\openssl_open',
            'openssl_pbkdf2' => 'Safe\openssl_pbkdf2',
            'openssl_pkcs7_decrypt' => 'Safe\openssl_pkcs7_decrypt',
            'openssl_pkcs7_encrypt' => 'Safe\openssl_pkcs7_encrypt',
            'openssl_pkcs7_read' => 'Safe\openssl_pkcs7_read',
            'openssl_pkcs7_sign' => 'Safe\openssl_pkcs7_sign',
            'openssl_pkcs12_export' => 'Safe\openssl_pkcs12_export',
            'openssl_pkcs12_export_to_file' => 'Safe\openssl_pkcs12_export_to_file',
            'openssl_pkcs12_read' => 'Safe\openssl_pkcs12_read',
            'openssl_pkey_derive' => 'Safe\openssl_pkey_derive',
            'openssl_pkey_export' => 'Safe\openssl_pkey_export',
            'openssl_pkey_export_to_file' => 'Safe\openssl_pkey_export_to_file',
            'openssl_pkey_get_details' => 'Safe\openssl_pkey_get_details',
            'openssl_pkey_get_private' => 'Safe\openssl_pkey_get_private',
            'openssl_pkey_get_public' => 'Safe\openssl_pkey_get_public',
            'openssl_pkey_new' => 'Safe\openssl_pkey_new',
            'openssl_private_decrypt' => 'Safe\openssl_private_decrypt',
            'openssl_private_encrypt' => 'Safe\openssl_private_encrypt',
            'openssl_public_decrypt' => 'Safe\openssl_public_decrypt',
            'openssl_public_encrypt' => 'Safe\openssl_public_encrypt',
            'openssl_seal' => 'Safe\openssl_seal',
            'openssl_sign' => 'Safe\openssl_sign',
            'openssl_spki_export' => 'Safe\openssl_spki_export',
            'openssl_spki_export_challenge' => 'Safe\openssl_spki_export_challenge',
            'openssl_spki_new' => 'Safe\openssl_spki_new',
            'openssl_spki_verify' => 'Safe\openssl_spki_verify',
            'openssl_verify' => 'Safe\openssl_verify',
            'openssl_x509_checkpurpose' => 'Safe\openssl_x509_checkpurpose',
            'openssl_x509_export' => 'Safe\openssl_x509_export',
            'openssl_x509_export_to_file' => 'Safe\openssl_x509_export_to_file',
            'openssl_x509_fingerprint' => 'Safe\openssl_x509_fingerprint',
            'openssl_x509_read' => 'Safe\openssl_x509_read',
            'output_add_rewrite_var' => 'Safe\output_add_rewrite_var',
            'output_reset_rewrite_vars' => 'Safe\output_reset_rewrite_vars',
            'parse_ini_file' => 'Safe\parse_ini_file',
            'parse_ini_string' => 'Safe\parse_ini_string',
            'parse_url' => 'Safe\parse_url',
            'passthru' => 'Safe\passthru',
            'pclose' => 'Safe\pclose',
            'pcntl_getpriority' => 'Safe\pcntl_getpriority',
            'pcntl_setpriority' => 'Safe\pcntl_setpriority',
            'pcntl_signal' => 'Safe\pcntl_signal',
            'pcntl_signal_dispatch' => 'Safe\pcntl_signal_dispatch',
            'pcntl_sigprocmask' => 'Safe\pcntl_sigprocmask',
            'pcntl_sigtimedwait' => 'Safe\pcntl_sigtimedwait',
            'pcntl_sigwaitinfo' => 'Safe\pcntl_sigwaitinfo',
            'pfsockopen' => 'Safe\pfsockopen',
            'pg_cancel_query' => 'Safe\pg_cancel_query',
            'pg_connect' => 'Safe\pg_connect',
            'pg_connection_reset' => 'Safe\pg_connection_reset',
            'pg_convert' => 'Safe\pg_convert',
            'pg_copy_from' => 'Safe\pg_copy_from',
            'pg_copy_to' => 'Safe\pg_copy_to',
            'pg_delete' => 'Safe\pg_delete',
            'pg_end_copy' => 'Safe\pg_end_copy',
            'pg_execute' => 'Safe\pg_execute',
            'pg_field_num' => 'Safe\pg_field_num',
            'pg_field_table' => 'Safe\pg_field_table',
            'pg_flush' => 'Safe\pg_flush',
            'pg_free_result' => 'Safe\pg_free_result',
            'pg_host' => 'Safe\pg_host',
            'pg_insert' => 'Safe\pg_insert',
            'pg_last_oid' => 'Safe\pg_last_oid',
            'pg_lo_close' => 'Safe\pg_lo_close',
            'pg_lo_export' => 'Safe\pg_lo_export',
            'pg_lo_import' => 'Safe\pg_lo_import',
            'pg_lo_open' => 'Safe\pg_lo_open',
            'pg_lo_read' => 'Safe\pg_lo_read',
            'pg_lo_seek' => 'Safe\pg_lo_seek',
            'pg_lo_truncate' => 'Safe\pg_lo_truncate',
            'pg_lo_unlink' => 'Safe\pg_lo_unlink',
            'pg_lo_write' => 'Safe\pg_lo_write',
            'pg_meta_data' => 'Safe\pg_meta_data',
            'pg_parameter_status' => 'Safe\pg_parameter_status',
            'pg_pconnect' => 'Safe\pg_pconnect',
            'pg_ping' => 'Safe\pg_ping',
            'pg_prepare' => 'Safe\pg_prepare',
            'pg_put_line' => 'Safe\pg_put_line',
            'pg_query' => 'Safe\pg_query',
            'pg_query_params' => 'Safe\pg_query_params',
            'pg_result_error_field' => 'Safe\pg_result_error_field',
            'pg_result_seek' => 'Safe\pg_result_seek',
            'pg_select' => 'Safe\pg_select',
            'pg_set_chunked_rows_size' => 'Safe\pg_set_chunked_rows_size',
            'pg_socket' => 'Safe\pg_socket',
            'pg_trace' => 'Safe\pg_trace',
            'pg_update' => 'Safe\pg_update',
            'php_sapi_name' => 'Safe\php_sapi_name',
            'popen' => 'Safe\popen',
            'posix_access' => 'Safe\posix_access',
            'posix_eaccess' => 'Safe\posix_eaccess',
            'posix_getgrgid' => 'Safe\posix_getgrgid',
            'posix_getgrnam' => 'Safe\posix_getgrnam',
            'posix_getgroups' => 'Safe\posix_getgroups',
            'posix_getlogin' => 'Safe\posix_getlogin',
            'posix_getpgid' => 'Safe\posix_getpgid',
            'posix_getpwuid' => 'Safe\posix_getpwuid',
            'posix_getrlimit' => 'Safe\posix_getrlimit',
            'posix_getsid' => 'Safe\posix_getsid',
            'posix_initgroups' => 'Safe\posix_initgroups',
            'posix_kill' => 'Safe\posix_kill',
            'posix_mkfifo' => 'Safe\posix_mkfifo',
            'posix_mknod' => 'Safe\posix_mknod',
            'posix_setegid' => 'Safe\posix_setegid',
            'posix_seteuid' => 'Safe\posix_seteuid',
            'posix_setgid' => 'Safe\posix_setgid',
            'posix_setpgid' => 'Safe\posix_setpgid',
            'posix_setrlimit' => 'Safe\posix_setrlimit',
            'posix_setsid' => 'Safe\posix_setsid',
            'posix_setuid' => 'Safe\posix_setuid',
            'posix_times' => 'Safe\posix_times',
            'posix_uname' => 'Safe\posix_uname',
            'preg_grep' => 'Safe\preg_grep',
            'preg_match' => 'Safe\preg_match',
            'preg_match_all' => 'Safe\preg_match_all',
            'preg_replace' => 'Safe\preg_replace',
            'preg_replace_callback' => 'Safe\preg_replace_callback',
            'preg_replace_callback_array' => 'Safe\preg_replace_callback_array',
            'preg_split' => 'Safe\preg_split',
            'proc_close' => 'Safe\proc_close',
            'proc_nice' => 'Safe\proc_nice',
            'proc_open' => 'Safe\proc_open',
            'pspell_add_to_personal' => 'Safe\pspell_add_to_personal',
            'pspell_add_to_session' => 'Safe\pspell_add_to_session',
            'pspell_clear_session' => 'Safe\pspell_clear_session',
            'pspell_config_data_dir' => 'Safe\pspell_config_data_dir',
            'pspell_config_dict_dir' => 'Safe\pspell_config_dict_dir',
            'pspell_config_ignore' => 'Safe\pspell_config_ignore',
            'pspell_config_mode' => 'Safe\pspell_config_mode',
            'pspell_config_personal' => 'Safe\pspell_config_personal',
            'pspell_config_repl' => 'Safe\pspell_config_repl',
            'pspell_config_runtogether' => 'Safe\pspell_config_runtogether',
            'pspell_config_save_repl' => 'Safe\pspell_config_save_repl',
            'pspell_new' => 'Safe\pspell_new',
            'pspell_new_config' => 'Safe\pspell_new_config',
            'pspell_new_personal' => 'Safe\pspell_new_personal',
            'pspell_save_wordlist' => 'Safe\pspell_save_wordlist',
            'pspell_store_replacement' => 'Safe\pspell_store_replacement',
            'ps_add_launchlink' => 'Safe\ps_add_launchlink',
            'ps_add_locallink' => 'Safe\ps_add_locallink',
            'ps_add_note' => 'Safe\ps_add_note',
            'ps_add_pdflink' => 'Safe\ps_add_pdflink',
            'ps_add_weblink' => 'Safe\ps_add_weblink',
            'ps_arc' => 'Safe\ps_arc',
            'ps_arcn' => 'Safe\ps_arcn',
            'ps_begin_page' => 'Safe\ps_begin_page',
            'ps_begin_pattern' => 'Safe\ps_begin_pattern',
            'ps_begin_template' => 'Safe\ps_begin_template',
            'ps_circle' => 'Safe\ps_circle',
            'ps_clip' => 'Safe\ps_clip',
            'ps_close' => 'Safe\ps_close',
            'ps_closepath' => 'Safe\ps_closepath',
            'ps_closepath_stroke' => 'Safe\ps_closepath_stroke',
            'ps_close_image' => 'Safe\ps_close_image',
            'ps_continue_text' => 'Safe\ps_continue_text',
            'ps_curveto' => 'Safe\ps_curveto',
            'ps_delete' => 'Safe\ps_delete',
            'ps_end_page' => 'Safe\ps_end_page',
            'ps_end_pattern' => 'Safe\ps_end_pattern',
            'ps_end_template' => 'Safe\ps_end_template',
            'ps_fill' => 'Safe\ps_fill',
            'ps_fill_stroke' => 'Safe\ps_fill_stroke',
            'ps_get_parameter' => 'Safe\ps_get_parameter',
            'ps_hyphenate' => 'Safe\ps_hyphenate',
            'ps_include_file' => 'Safe\ps_include_file',
            'ps_lineto' => 'Safe\ps_lineto',
            'ps_moveto' => 'Safe\ps_moveto',
            'ps_new' => 'Safe\ps_new',
            'ps_open_file' => 'Safe\ps_open_file',
            'ps_place_image' => 'Safe\ps_place_image',
            'ps_rect' => 'Safe\ps_rect',
            'ps_restore' => 'Safe\ps_restore',
            'ps_rotate' => 'Safe\ps_rotate',
            'ps_save' => 'Safe\ps_save',
            'ps_scale' => 'Safe\ps_scale',
            'ps_setcolor' => 'Safe\ps_setcolor',
            'ps_setdash' => 'Safe\ps_setdash',
            'ps_setflat' => 'Safe\ps_setflat',
            'ps_setfont' => 'Safe\ps_setfont',
            'ps_setgray' => 'Safe\ps_setgray',
            'ps_setlinecap' => 'Safe\ps_setlinecap',
            'ps_setlinejoin' => 'Safe\ps_setlinejoin',
            'ps_setlinewidth' => 'Safe\ps_setlinewidth',
            'ps_setmiterlimit' => 'Safe\ps_setmiterlimit',
            'ps_setoverprintmode' => 'Safe\ps_setoverprintmode',
            'ps_setpolydash' => 'Safe\ps_setpolydash',
            'ps_set_border_color' => 'Safe\ps_set_border_color',
            'ps_set_border_dash' => 'Safe\ps_set_border_dash',
            'ps_set_border_style' => 'Safe\ps_set_border_style',
            'ps_set_info' => 'Safe\ps_set_info',
            'ps_set_parameter' => 'Safe\ps_set_parameter',
            'ps_set_text_pos' => 'Safe\ps_set_text_pos',
            'ps_set_value' => 'Safe\ps_set_value',
            'ps_shading' => 'Safe\ps_shading',
            'ps_shading_pattern' => 'Safe\ps_shading_pattern',
            'ps_shfill' => 'Safe\ps_shfill',
            'ps_show' => 'Safe\ps_show',
            'ps_show2' => 'Safe\ps_show2',
            'ps_show_xy' => 'Safe\ps_show_xy',
            'ps_show_xy2' => 'Safe\ps_show_xy2',
            'ps_stroke' => 'Safe\ps_stroke',
            'ps_symbol' => 'Safe\ps_symbol',
            'ps_translate' => 'Safe\ps_translate',
            'putenv' => 'Safe\putenv',
            'readfile' => 'Safe\readfile',
            'readgzfile' => 'Safe\readgzfile',
            'readline_completion_function' => 'Safe\readline_completion_function',
            'readline_read_history' => 'Safe\readline_read_history',
            'readline_write_history' => 'Safe\readline_write_history',
            'readlink' => 'Safe\readlink',
            'realpath' => 'Safe\realpath',
            'register_tick_function' => 'Safe\register_tick_function',
            'rename' => 'Safe\rename',
            'rewind' => 'Safe\rewind',
            'rmdir' => 'Safe\rmdir',
            'rnp_decrypt' => 'Safe\rnp_decrypt',
            'rnp_dump_packets' => 'Safe\rnp_dump_packets',
            'rnp_dump_packets_to_json' => 'Safe\rnp_dump_packets_to_json',
            'rnp_ffi_create' => 'Safe\rnp_ffi_create',
            'rnp_ffi_set_pass_provider' => 'Safe\rnp_ffi_set_pass_provider',
            'rnp_import_keys' => 'Safe\rnp_import_keys',
            'rnp_import_signatures' => 'Safe\rnp_import_signatures',
            'rnp_key_export' => 'Safe\rnp_key_export',
            'rnp_key_export_autocrypt' => 'Safe\rnp_key_export_autocrypt',
            'rnp_key_export_revocation' => 'Safe\rnp_key_export_revocation',
            'rnp_key_get_info' => 'Safe\rnp_key_get_info',
            'rnp_key_remove' => 'Safe\rnp_key_remove',
            'rnp_key_revoke' => 'Safe\rnp_key_revoke',
            'rnp_list_keys' => 'Safe\rnp_list_keys',
            'rnp_load_keys' => 'Safe\rnp_load_keys',
            'rnp_load_keys_from_path' => 'Safe\rnp_load_keys_from_path',
            'rnp_locate_key' => 'Safe\rnp_locate_key',
            'rnp_op_encrypt' => 'Safe\rnp_op_encrypt',
            'rnp_op_generate_key' => 'Safe\rnp_op_generate_key',
            'rnp_op_sign' => 'Safe\rnp_op_sign',
            'rnp_op_sign_cleartext' => 'Safe\rnp_op_sign_cleartext',
            'rnp_op_sign_detached' => 'Safe\rnp_op_sign_detached',
            'rnp_op_verify' => 'Safe\rnp_op_verify',
            'rnp_op_verify_detached' => 'Safe\rnp_op_verify_detached',
            'rnp_save_keys' => 'Safe\rnp_save_keys',
            'rnp_save_keys_to_path' => 'Safe\rnp_save_keys_to_path',
            'rnp_supported_features' => 'Safe\rnp_supported_features',
            'rpmaddtag' => 'Safe\rpmaddtag',
            'rrd_create' => 'Safe\rrd_create',
            'rrd_first' => 'Safe\rrd_first',
            'rrd_graph' => 'Safe\rrd_graph',
            'rrd_info' => 'Safe\rrd_info',
            'rrd_lastupdate' => 'Safe\rrd_lastupdate',
            'rrd_restore' => 'Safe\rrd_restore',
            'rrd_tune' => 'Safe\rrd_tune',
            'rrd_update' => 'Safe\rrd_update',
            'rrd_xport' => 'Safe\rrd_xport',
            'sapi_windows_cp_conv' => 'Safe\sapi_windows_cp_conv',
            'sapi_windows_cp_set' => 'Safe\sapi_windows_cp_set',
            'sapi_windows_generate_ctrl_event' => 'Safe\sapi_windows_generate_ctrl_event',
            'sapi_windows_set_ctrl_handler' => 'Safe\sapi_windows_set_ctrl_handler',
            'sapi_windows_vt100_support' => 'Safe\sapi_windows_vt100_support',
            'scandir' => 'Safe\scandir',
            'sem_acquire' => 'Safe\sem_acquire',
            'sem_get' => 'Safe\sem_get',
            'sem_release' => 'Safe\sem_release',
            'sem_remove' => 'Safe\sem_remove',
            'session_abort' => 'Safe\session_abort',
            'session_cache_expire' => 'Safe\session_cache_expire',
            'session_cache_limiter' => 'Safe\session_cache_limiter',
            'session_create_id' => 'Safe\session_create_id',
            'session_decode' => 'Safe\session_decode',
            'session_destroy' => 'Safe\session_destroy',
            'session_encode' => 'Safe\session_encode',
            'session_gc' => 'Safe\session_gc',
            'session_id' => 'Safe\session_id',
            'session_module_name' => 'Safe\session_module_name',
            'session_name' => 'Safe\session_name',
            'session_regenerate_id' => 'Safe\session_regenerate_id',
            'session_reset' => 'Safe\session_reset',
            'session_save_path' => 'Safe\session_save_path',
            'session_start' => 'Safe\session_start',
            'session_unset' => 'Safe\session_unset',
            'session_write_close' => 'Safe\session_write_close',
            'settype' => 'Safe\settype',
            'set_include_path' => 'Safe\set_include_path',
            'set_time_limit' => 'Safe\set_time_limit',
            'sha1_file' => 'Safe\sha1_file',
            'shell_exec' => 'Safe\shell_exec',
            'shmop_delete' => 'Safe\shmop_delete',
            'shmop_read' => 'Safe\shmop_read',
            'shm_attach' => 'Safe\shm_attach',
            'shm_detach' => 'Safe\shm_detach',
            'shm_put_var' => 'Safe\shm_put_var',
            'shm_remove' => 'Safe\shm_remove',
            'shm_remove_var' => 'Safe\shm_remove_var',
            'simplexml_import_dom' => 'Safe\simplexml_import_dom',
            'simplexml_load_file' => 'Safe\simplexml_load_file',
            'simplexml_load_string' => 'Safe\simplexml_load_string',
            'socket_accept' => 'Safe\socket_accept',
            'socket_addrinfo_bind' => 'Safe\socket_addrinfo_bind',
            'socket_addrinfo_connect' => 'Safe\socket_addrinfo_connect',
            'socket_addrinfo_lookup' => 'Safe\socket_addrinfo_lookup',
            'socket_atmark' => 'Safe\socket_atmark',
            'socket_bind' => 'Safe\socket_bind',
            'socket_connect' => 'Safe\socket_connect',
            'socket_create' => 'Safe\socket_create',
            'socket_create_listen' => 'Safe\socket_create_listen',
            'socket_create_pair' => 'Safe\socket_create_pair',
            'socket_export_stream' => 'Safe\socket_export_stream',
            'socket_getpeername' => 'Safe\socket_getpeername',
            'socket_getsockname' => 'Safe\socket_getsockname',
            'socket_get_option' => 'Safe\socket_get_option',
            'socket_import_stream' => 'Safe\socket_import_stream',
            'socket_listen' => 'Safe\socket_listen',
            'socket_read' => 'Safe\socket_read',
            'socket_send' => 'Safe\socket_send',
            'socket_sendmsg' => 'Safe\socket_sendmsg',
            'socket_sendto' => 'Safe\socket_sendto',
            'socket_set_block' => 'Safe\socket_set_block',
            'socket_set_nonblock' => 'Safe\socket_set_nonblock',
            'socket_set_option' => 'Safe\socket_set_option',
            'socket_shutdown' => 'Safe\socket_shutdown',
            'socket_write' => 'Safe\socket_write',
            'socket_wsaprotocol_info_export' => 'Safe\socket_wsaprotocol_info_export',
            'socket_wsaprotocol_info_import' => 'Safe\socket_wsaprotocol_info_import',
            'socket_wsaprotocol_info_release' => 'Safe\socket_wsaprotocol_info_release',
            'sodium_crypto_aead_aegis128l_decrypt' => 'Safe\sodium_crypto_aead_aegis128l_decrypt',
            'sodium_crypto_aead_aegis256_decrypt' => 'Safe\sodium_crypto_aead_aegis256_decrypt',
            'sodium_crypto_aead_aes256gcm_decrypt' => 'Safe\sodium_crypto_aead_aes256gcm_decrypt',
            'sodium_crypto_aead_chacha20poly1305_decrypt' => 'Safe\sodium_crypto_aead_chacha20poly1305_decrypt',
            'sodium_crypto_aead_chacha20poly1305_encrypt' => 'Safe\sodium_crypto_aead_chacha20poly1305_encrypt',
            'sodium_crypto_aead_chacha20poly1305_ietf_decrypt' => 'Safe\sodium_crypto_aead_chacha20poly1305_ietf_decrypt',
            'sodium_crypto_aead_chacha20poly1305_ietf_encrypt' => 'Safe\sodium_crypto_aead_chacha20poly1305_ietf_encrypt',
            'sodium_crypto_aead_xchacha20poly1305_ietf_decrypt' => 'Safe\sodium_crypto_aead_xchacha20poly1305_ietf_decrypt',
            'sodium_crypto_aead_xchacha20poly1305_ietf_encrypt' => 'Safe\sodium_crypto_aead_xchacha20poly1305_ietf_encrypt',
            'sodium_crypto_box_open' => 'Safe\sodium_crypto_box_open',
            'sodium_crypto_box_seal_open' => 'Safe\sodium_crypto_box_seal_open',
            'sodium_crypto_secretbox_open' => 'Safe\sodium_crypto_secretbox_open',
            'sodium_crypto_sign_open' => 'Safe\sodium_crypto_sign_open',
            'sodium_crypto_stream_xchacha20_xor_ic' => 'Safe\sodium_crypto_stream_xchacha20_xor_ic',
            'solr_get_version' => 'Safe\solr_get_version',
            'spl_autoload_register' => 'Safe\spl_autoload_register',
            'spl_autoload_unregister' => 'Safe\spl_autoload_unregister',
            'sqlsrv_begin_transaction' => 'Safe\sqlsrv_begin_transaction',
            'sqlsrv_cancel' => 'Safe\sqlsrv_cancel',
            'sqlsrv_client_info' => 'Safe\sqlsrv_client_info',
            'sqlsrv_close' => 'Safe\sqlsrv_close',
            'sqlsrv_commit' => 'Safe\sqlsrv_commit',
            'sqlsrv_configure' => 'Safe\sqlsrv_configure',
            'sqlsrv_execute' => 'Safe\sqlsrv_execute',
            'sqlsrv_fetch' => 'Safe\sqlsrv_fetch',
            'sqlsrv_fetch_array' => 'Safe\sqlsrv_fetch_array',
            'sqlsrv_fetch_object' => 'Safe\sqlsrv_fetch_object',
            'sqlsrv_free_stmt' => 'Safe\sqlsrv_free_stmt',
            'sqlsrv_get_field' => 'Safe\sqlsrv_get_field',
            'sqlsrv_next_result' => 'Safe\sqlsrv_next_result',
            'sqlsrv_num_fields' => 'Safe\sqlsrv_num_fields',
            'sqlsrv_num_rows' => 'Safe\sqlsrv_num_rows',
            'sqlsrv_prepare' => 'Safe\sqlsrv_prepare',
            'sqlsrv_query' => 'Safe\sqlsrv_query',
            'sqlsrv_rollback' => 'Safe\sqlsrv_rollback',
            'ssdeep_fuzzy_compare' => 'Safe\ssdeep_fuzzy_compare',
            'ssdeep_fuzzy_hash' => 'Safe\ssdeep_fuzzy_hash',
            'ssdeep_fuzzy_hash_filename' => 'Safe\ssdeep_fuzzy_hash_filename',
            'ssh2_auth_agent' => 'Safe\ssh2_auth_agent',
            'ssh2_auth_hostbased_file' => 'Safe\ssh2_auth_hostbased_file',
            'ssh2_auth_password' => 'Safe\ssh2_auth_password',
            'ssh2_auth_pubkey_file' => 'Safe\ssh2_auth_pubkey_file',
            'ssh2_connect' => 'Safe\ssh2_connect',
            'ssh2_disconnect' => 'Safe\ssh2_disconnect',
            'ssh2_exec' => 'Safe\ssh2_exec',
            'ssh2_forward_accept' => 'Safe\ssh2_forward_accept',
            'ssh2_forward_listen' => 'Safe\ssh2_forward_listen',
            'ssh2_publickey_add' => 'Safe\ssh2_publickey_add',
            'ssh2_publickey_init' => 'Safe\ssh2_publickey_init',
            'ssh2_publickey_remove' => 'Safe\ssh2_publickey_remove',
            'ssh2_scp_recv' => 'Safe\ssh2_scp_recv',
            'ssh2_scp_send' => 'Safe\ssh2_scp_send',
            'ssh2_send_eof' => 'Safe\ssh2_send_eof',
            'ssh2_sftp' => 'Safe\ssh2_sftp',
            'ssh2_sftp_chmod' => 'Safe\ssh2_sftp_chmod',
            'ssh2_sftp_mkdir' => 'Safe\ssh2_sftp_mkdir',
            'ssh2_sftp_rename' => 'Safe\ssh2_sftp_rename',
            'ssh2_sftp_rmdir' => 'Safe\ssh2_sftp_rmdir',
            'ssh2_sftp_symlink' => 'Safe\ssh2_sftp_symlink',
            'ssh2_sftp_unlink' => 'Safe\ssh2_sftp_unlink',
            'ssh2_shell' => 'Safe\ssh2_shell',
            'stream_context_set_options' => 'Safe\stream_context_set_options',
            'stream_context_set_params' => 'Safe\stream_context_set_params',
            'stream_copy_to_stream' => 'Safe\stream_copy_to_stream',
            'stream_filter_append' => 'Safe\stream_filter_append',
            'stream_filter_prepend' => 'Safe\stream_filter_prepend',
            'stream_filter_register' => 'Safe\stream_filter_register',
            'stream_filter_remove' => 'Safe\stream_filter_remove',
            'stream_get_contents' => 'Safe\stream_get_contents',
            'stream_get_line' => 'Safe\stream_get_line',
            'stream_isatty' => 'Safe\stream_isatty',
            'stream_resolve_include_path' => 'Safe\stream_resolve_include_path',
            'stream_set_blocking' => 'Safe\stream_set_blocking',
            'stream_set_timeout' => 'Safe\stream_set_timeout',
            'stream_socket_accept' => 'Safe\stream_socket_accept',
            'stream_socket_client' => 'Safe\stream_socket_client',
            'stream_socket_get_name' => 'Safe\stream_socket_get_name',
            'stream_socket_pair' => 'Safe\stream_socket_pair',
            'stream_socket_recvfrom' => 'Safe\stream_socket_recvfrom',
            'stream_socket_sendto' => 'Safe\stream_socket_sendto',
            'stream_socket_server' => 'Safe\stream_socket_server',
            'stream_socket_shutdown' => 'Safe\stream_socket_shutdown',
            'stream_supports_lock' => 'Safe\stream_supports_lock',
            'stream_wrapper_register' => 'Safe\stream_wrapper_register',
            'stream_wrapper_restore' => 'Safe\stream_wrapper_restore',
            'stream_wrapper_unregister' => 'Safe\stream_wrapper_unregister',
            'strftime' => 'Safe\strftime',
            'strptime' => 'Safe\strptime',
            'strtotime' => 'Safe\strtotime',
            'swoole_async_dns_lookup' => 'Safe\swoole_async_dns_lookup',
            'swoole_async_readfile' => 'Safe\swoole_async_readfile',
            'swoole_async_write' => 'Safe\swoole_async_write',
            'swoole_async_writefile' => 'Safe\swoole_async_writefile',
            'swoole_event_defer' => 'Safe\swoole_event_defer',
            'swoole_event_del' => 'Safe\swoole_event_del',
            'swoole_event_write' => 'Safe\swoole_event_write',
            'symlink' => 'Safe\symlink',
            'system' => 'Safe\system',
            'sys_getloadavg' => 'Safe\sys_getloadavg',
            'tempnam' => 'Safe\tempnam',
            'timezone_name_from_abbr' => 'Safe\timezone_name_from_abbr',
            'time_nanosleep' => 'Safe\time_nanosleep',
            'time_sleep_until' => 'Safe\time_sleep_until',
            'tmpfile' => 'Safe\tmpfile',
            'touch' => 'Safe\touch',
            'unixtojd' => 'Safe\unixtojd',
            'unlink' => 'Safe\unlink',
            'unpack' => 'Safe\unpack',
            'unserialize' => 'Safe\unserialize',
            'uopz_extend' => 'Safe\uopz_extend',
            'uopz_implement' => 'Safe\uopz_implement',
            'variant_date_to_timestamp' => 'Safe\variant_date_to_timestamp',
            'variant_round' => 'Safe\variant_round',
            'virtual' => 'Safe\virtual',
            'xdiff_file_bdiff' => 'Safe\xdiff_file_bdiff',
            'xdiff_file_bpatch' => 'Safe\xdiff_file_bpatch',
            'xdiff_file_diff' => 'Safe\xdiff_file_diff',
            'xdiff_file_diff_binary' => 'Safe\xdiff_file_diff_binary',
            'xdiff_file_patch_binary' => 'Safe\xdiff_file_patch_binary',
            'xdiff_file_rabdiff' => 'Safe\xdiff_file_rabdiff',
            'xdiff_string_bpatch' => 'Safe\xdiff_string_bpatch',
            'xdiff_string_patch' => 'Safe\xdiff_string_patch',
            'xdiff_string_patch_binary' => 'Safe\xdiff_string_patch_binary',
            'xmlrpc_set_type' => 'Safe\xmlrpc_set_type',
            'xml_parser_free' => 'Safe\xml_parser_free',
            'xml_parser_set_option' => 'Safe\xml_parser_set_option',
            'yaml_parse' => 'Safe\yaml_parse',
            'yaml_parse_file' => 'Safe\yaml_parse_file',
            'yaml_parse_url' => 'Safe\yaml_parse_url',
            'yaz_ccl_parse' => 'Safe\yaz_ccl_parse',
            'yaz_close' => 'Safe\yaz_close',
            'yaz_connect' => 'Safe\yaz_connect',
            'yaz_database' => 'Safe\yaz_database',
            'yaz_element' => 'Safe\yaz_element',
            'yaz_present' => 'Safe\yaz_present',
            'yaz_search' => 'Safe\yaz_search',
            'yaz_wait' => 'Safe\yaz_wait',
            'zip_entry_close' => 'Safe\zip_entry_close',
            'zip_entry_compressedsize' => 'Safe\zip_entry_compressedsize',
            'zip_entry_compressionmethod' => 'Safe\zip_entry_compressionmethod',
            'zip_entry_filesize' => 'Safe\zip_entry_filesize',
            'zip_entry_name' => 'Safe\zip_entry_name',
            'zip_entry_open' => 'Safe\zip_entry_open',
            'zip_entry_read' => 'Safe\zip_entry_read',
            'zlib_decode' => 'Safe\zlib_decode',
        ]
    );
};
<?php

namespace Safe;

use Safe\Exceptions\NetworkException;

/**
 * @return bool
 *
 */
function closelog(): bool
{
    error_clear_last();
    $safeResult = \closelog();
    return $safeResult;
}


/**
 * @param string $hostname
 * @param int $type
 * @param array|null $authoritative_name_servers
 * @param array|null $additional_records
 * @param bool $raw
 * @return list
 * @throws NetworkException
 *
 */
function dns_get_record(string $hostname, int $type = DNS_ANY, ?array &$authoritative_name_servers = null, ?array &$additional_records = null, bool $raw = false): array
{
    error_clear_last();
    $safeResult = \dns_get_record($hostname, $type, $authoritative_name_servers, $additional_records, $raw);
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $hostname
 * @param int $port
 * @param int|null $error_code
 * @param null|string $error_message
 * @param float|null $timeout
 * @return resource
 * @throws NetworkException
 *
 */
function fsockopen(string $hostname, int $port = -1, ?int &$error_code = null, ?string &$error_message = null, ?float $timeout = null)
{
    error_clear_last();
    if ($timeout !== null) {
        $safeResult = \fsockopen($hostname, $port, $error_code, $error_message, $timeout);
    } else {
        $safeResult = \fsockopen($hostname, $port, $error_code, $error_message);
    }
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return string
 * @throws NetworkException
 *
 */
function gethostname(): string
{
    error_clear_last();
    $safeResult = \gethostname();
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $protocol
 * @return int
 * @throws NetworkException
 *
 */
function getprotobyname(string $protocol): int
{
    error_clear_last();
    $safeResult = \getprotobyname($protocol);
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $protocol
 * @return string
 * @throws NetworkException
 *
 */
function getprotobynumber(int $protocol): string
{
    error_clear_last();
    $safeResult = \getprotobynumber($protocol);
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $port
 * @param string $protocol
 * @return string
 * @throws NetworkException
 *
 */
function getservbyport(int $port, string $protocol): string
{
    error_clear_last();
    $safeResult = \getservbyport($port, $protocol);
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param callable $callback
 * @throws NetworkException
 *
 */
function header_register_callback(callable $callback): void
{
    error_clear_last();
    $safeResult = \header_register_callback($callback);
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
}


/**
 * @param string $ip
 * @return string
 * @throws NetworkException
 *
 */
function inet_ntop(string $ip): string
{
    error_clear_last();
    $safeResult = \inet_ntop($ip);
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $ip
 * @return string
 * @throws NetworkException
 *
 */
function inet_pton(string $ip): string
{
    error_clear_last();
    $safeResult = \inet_pton($ip);
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $ip
 * @return false|string
 *
 */
function long2ip(int $ip)
{
    error_clear_last();
    $safeResult = \long2ip($ip);
    return $safeResult;
}


/**
 * @return array
 * @throws NetworkException
 *
 */
function net_get_interfaces(): array
{
    error_clear_last();
    $safeResult = \net_get_interfaces();
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $prefix
 * @param int $flags
 * @param int $facility
 * @return bool
 *
 */
function openlog(string $prefix, int $flags, int $facility): bool
{
    error_clear_last();
    $safeResult = \openlog($prefix, $flags, $facility);
    return $safeResult;
}


/**
 * @param string $hostname
 * @param int $port
 * @param int|null $error_code
 * @param null|string $error_message
 * @param float|null $timeout
 * @return resource
 * @throws NetworkException
 *
 */
function pfsockopen(string $hostname, int $port = -1, ?int &$error_code = null, ?string &$error_message = null, ?float $timeout = null)
{
    error_clear_last();
    if ($timeout !== null) {
        $safeResult = \pfsockopen($hostname, $port, $error_code, $error_message, $timeout);
    } else {
        $safeResult = \pfsockopen($hostname, $port, $error_code, $error_message);
    }
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $priority
 * @param string $message
 * @return bool
 *
 */
function syslog(int $priority, string $message): bool
{
    error_clear_last();
    $safeResult = \syslog($priority, $message);
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\ImapException;

/**
 * @param string $string
 * @return string
 * @throws ImapException
 *
 */
function imap_8bit(string $string): string
{
    error_clear_last();
    $safeResult = \imap_8bit($string);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $folder
 * @param string $message
 * @param null|string $options
 * @param null|string $internal_date
 * @throws ImapException
 *
 */
function imap_append(\IMAP\Connection $imap, string $folder, string $message, ?string $options = null, ?string $internal_date = null): void
{
    error_clear_last();
    if ($internal_date !== null) {
        $safeResult = \imap_append($imap, $folder, $message, $options, $internal_date);
    } elseif ($options !== null) {
        $safeResult = \imap_append($imap, $folder, $message, $options);
    } else {
        $safeResult = \imap_append($imap, $folder, $message);
    }
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param string $string
 * @return string
 * @throws ImapException
 *
 */
function imap_base64(string $string): string
{
    error_clear_last();
    $safeResult = \imap_base64($string);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $string
 * @return string
 * @throws ImapException
 *
 */
function imap_binary(string $string): string
{
    error_clear_last();
    $safeResult = \imap_binary($string);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param int $message_num
 * @param int $flags
 * @return string
 * @throws ImapException
 *
 */
function imap_body(\IMAP\Connection $imap, int $message_num, int $flags = 0): string
{
    error_clear_last();
    $safeResult = \imap_body($imap, $message_num, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param int $message_num
 * @param string $section
 * @return \stdClass
 * @throws ImapException
 *
 */
function imap_bodystruct(\IMAP\Connection $imap, int $message_num, string $section): \stdClass
{
    error_clear_last();
    $safeResult = \imap_bodystruct($imap, $message_num, $section);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @return \stdClass
 * @throws ImapException
 *
 */
function imap_check(\IMAP\Connection $imap): \stdClass
{
    error_clear_last();
    $safeResult = \imap_check($imap);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $sequence
 * @param string $flag
 * @param int $options
 * @return bool
 *
 */
function imap_clearflag_full(\IMAP\Connection $imap, string $sequence, string $flag, int $options = 0): bool
{
    error_clear_last();
    $safeResult = \imap_clearflag_full($imap, $sequence, $flag, $options);
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param int $flags
 * @return bool
 *
 */
function imap_close(\IMAP\Connection $imap, int $flags = 0): bool
{
    error_clear_last();
    $safeResult = \imap_close($imap, $flags);
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $mailbox
 * @throws ImapException
 *
 */
function imap_createmailbox(\IMAP\Connection $imap, string $mailbox): void
{
    error_clear_last();
    $safeResult = \imap_createmailbox($imap, $mailbox);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param string $mailbox
 * @throws ImapException
 *
 */
function imap_deletemailbox(\IMAP\Connection $imap, string $mailbox): void
{
    error_clear_last();
    $safeResult = \imap_deletemailbox($imap, $mailbox);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param string $sequence
 * @param int $flags
 * @return array
 * @throws ImapException
 *
 */
function imap_fetch_overview(\IMAP\Connection $imap, string $sequence, int $flags = 0): array
{
    error_clear_last();
    $safeResult = \imap_fetch_overview($imap, $sequence, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param int $message_num
 * @param string $section
 * @param int $flags
 * @return string
 * @throws ImapException
 *
 */
function imap_fetchbody(\IMAP\Connection $imap, int $message_num, string $section, int $flags = 0): string
{
    error_clear_last();
    $safeResult = \imap_fetchbody($imap, $message_num, $section, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param int $message_num
 * @param int $flags
 * @return string
 * @throws ImapException
 *
 */
function imap_fetchheader(\IMAP\Connection $imap, int $message_num, int $flags = 0): string
{
    error_clear_last();
    $safeResult = \imap_fetchheader($imap, $message_num, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param int $message_num
 * @param string $section
 * @param int $flags
 * @return string
 * @throws ImapException
 *
 */
function imap_fetchmime(\IMAP\Connection $imap, int $message_num, string $section, int $flags = 0): string
{
    error_clear_last();
    $safeResult = \imap_fetchmime($imap, $message_num, $section, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param int $message_num
 * @param int $flags
 * @return \stdClass
 * @throws ImapException
 *
 */
function imap_fetchstructure(\IMAP\Connection $imap, int $message_num, int $flags = 0): \stdClass
{
    error_clear_last();
    $safeResult = \imap_fetchstructure($imap, $message_num, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param int $flags
 * @return bool
 *
 */
function imap_gc(\IMAP\Connection $imap, int $flags): bool
{
    error_clear_last();
    $safeResult = \imap_gc($imap, $flags);
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $mailbox
 * @return array
 * @throws ImapException
 *
 */
function imap_getacl(\IMAP\Connection $imap, string $mailbox): array
{
    error_clear_last();
    $safeResult = \imap_getacl($imap, $mailbox);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $reference
 * @param string $pattern
 * @return array
 * @throws ImapException
 *
 */
function imap_getmailboxes(\IMAP\Connection $imap, string $reference, string $pattern): array
{
    error_clear_last();
    $safeResult = \imap_getmailboxes($imap, $reference, $pattern);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $reference
 * @param string $pattern
 * @return array
 * @throws ImapException
 *
 */
function imap_getsubscribed(\IMAP\Connection $imap, string $reference, string $pattern): array
{
    error_clear_last();
    $safeResult = \imap_getsubscribed($imap, $reference, $pattern);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param int $message_num
 * @param int $from_length
 * @param int $subject_length
 * @return \stdClass
 * @throws ImapException
 *
 */
function imap_headerinfo(\IMAP\Connection $imap, int $message_num, int $from_length = 0, int $subject_length = 0): \stdClass
{
    error_clear_last();
    $safeResult = \imap_headerinfo($imap, $message_num, $from_length, $subject_length);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @return array
 * @throws ImapException
 *
 */
function imap_headers(\IMAP\Connection $imap): array
{
    error_clear_last();
    $safeResult = \imap_headers($imap);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $reference
 * @param string $pattern
 * @param string $content
 * @return array
 * @throws ImapException
 *
 */
function imap_listscan(\IMAP\Connection $imap, string $reference, string $pattern, string $content): array
{
    error_clear_last();
    $safeResult = \imap_listscan($imap, $reference, $pattern, $content);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $reference
 * @param string $pattern
 * @return array
 * @throws ImapException
 *
 */
function imap_lsub(\IMAP\Connection $imap, string $reference, string $pattern): array
{
    error_clear_last();
    $safeResult = \imap_lsub($imap, $reference, $pattern);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param array $envelope
 * @param array $bodies
 * @return string
 * @throws ImapException
 *
 */
function imap_mail_compose(array $envelope, array $bodies): string
{
    error_clear_last();
    $safeResult = \imap_mail_compose($envelope, $bodies);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $message_nums
 * @param string $mailbox
 * @param int $flags
 * @throws ImapException
 *
 */
function imap_mail_copy(\IMAP\Connection $imap, string $message_nums, string $mailbox, int $flags = 0): void
{
    error_clear_last();
    $safeResult = \imap_mail_copy($imap, $message_nums, $mailbox, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param string $message_nums
 * @param string $mailbox
 * @param int $flags
 * @throws ImapException
 *
 */
function imap_mail_move(\IMAP\Connection $imap, string $message_nums, string $mailbox, int $flags = 0): void
{
    error_clear_last();
    $safeResult = \imap_mail_move($imap, $message_nums, $mailbox, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param string $to
 * @param string $subject
 * @param string $message
 * @param null|string $additional_headers
 * @param null|string $cc
 * @param null|string $bcc
 * @param null|string $return_path
 * @throws ImapException
 *
 */
function imap_mail(string $to, string $subject, string $message, ?string $additional_headers = null, ?string $cc = null, ?string $bcc = null, ?string $return_path = null): void
{
    error_clear_last();
    if ($return_path !== null) {
        $safeResult = \imap_mail($to, $subject, $message, $additional_headers, $cc, $bcc, $return_path);
    } elseif ($bcc !== null) {
        $safeResult = \imap_mail($to, $subject, $message, $additional_headers, $cc, $bcc);
    } elseif ($cc !== null) {
        $safeResult = \imap_mail($to, $subject, $message, $additional_headers, $cc);
    } elseif ($additional_headers !== null) {
        $safeResult = \imap_mail($to, $subject, $message, $additional_headers);
    } else {
        $safeResult = \imap_mail($to, $subject, $message);
    }
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @return \stdClass|false
 *
 */
function imap_mailboxmsginfo(\IMAP\Connection $imap)
{
    error_clear_last();
    $safeResult = \imap_mailboxmsginfo($imap);
    return $safeResult;
}


/**
 * @param string $string
 * @return array
 * @throws ImapException
 *
 */
function imap_mime_header_decode(string $string): array
{
    error_clear_last();
    $safeResult = \imap_mime_header_decode($string);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $string
 * @return string
 * @throws ImapException
 *
 */
function imap_mutf7_to_utf8(string $string): string
{
    error_clear_last();
    $safeResult = \imap_mutf7_to_utf8($string);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @return int
 * @throws ImapException
 *
 */
function imap_num_msg(\IMAP\Connection $imap): int
{
    error_clear_last();
    $safeResult = \imap_num_msg($imap);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $mailbox
 * @param string $user
 * @param string $password
 * @param int $flags
 * @param int $retries
 * @param array $options
 * @return \IMAP\Connection
 * @throws ImapException
 *
 */
function imap_open(string $mailbox, string $user, string $password, int $flags = 0, int $retries = 0, array $options = []): \IMAP\Connection
{
    error_clear_last();
    $safeResult = \imap_open($mailbox, $user, $password, $flags, $retries, $options);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $string
 * @return string
 * @throws ImapException
 *
 */
function imap_qprint(string $string): string
{
    error_clear_last();
    $safeResult = \imap_qprint($string);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $from
 * @param string $to
 * @throws ImapException
 *
 */
function imap_renamemailbox(\IMAP\Connection $imap, string $from, string $to): void
{
    error_clear_last();
    $safeResult = \imap_renamemailbox($imap, $from, $to);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param null|string $mailbox
 * @param null|string $hostname
 * @param null|string $personal
 * @return string
 * @throws ImapException
 *
 */
function imap_rfc822_write_address(?string $mailbox, ?string $hostname, ?string $personal): string
{
    error_clear_last();
    $safeResult = \imap_rfc822_write_address($mailbox, $hostname, $personal);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param int|resource|string $file
 * @param int $message_num
 * @param string $section
 * @param int $flags
 * @throws ImapException
 *
 */
function imap_savebody(\IMAP\Connection $imap, $file, int $message_num, string $section = "", int $flags = 0): void
{
    error_clear_last();
    $safeResult = \imap_savebody($imap, $file, $message_num, $section, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param string $quota_root
 * @param int $mailbox_size
 * @throws ImapException
 *
 */
function imap_set_quota(\IMAP\Connection $imap, string $quota_root, int $mailbox_size): void
{
    error_clear_last();
    $safeResult = \imap_set_quota($imap, $quota_root, $mailbox_size);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param string $mailbox
 * @param string $user_id
 * @param string $rights
 * @throws ImapException
 *
 */
function imap_setacl(\IMAP\Connection $imap, string $mailbox, string $user_id, string $rights): void
{
    error_clear_last();
    $safeResult = \imap_setacl($imap, $mailbox, $user_id, $rights);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param string $sequence
 * @param string $flag
 * @param int $options
 * @return bool
 *
 */
function imap_setflag_full(\IMAP\Connection $imap, string $sequence, string $flag, int $options = 0): bool
{
    error_clear_last();
    $safeResult = \imap_setflag_full($imap, $sequence, $flag, $options);
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param int $criteria
 * @param int $reverse
 * @param int $flags
 * @param null|string $search_criteria
 * @param null|string $charset
 * @return array
 * @throws ImapException
 *
 */
function imap_sort(\IMAP\Connection $imap, int $criteria, int $reverse, int $flags = 0, ?string $search_criteria = null, ?string $charset = null): array
{
    error_clear_last();
    if ($charset !== null) {
        $safeResult = \imap_sort($imap, $criteria, $reverse, $flags, $search_criteria, $charset);
    } elseif ($search_criteria !== null) {
        $safeResult = \imap_sort($imap, $criteria, $reverse, $flags, $search_criteria);
    } else {
        $safeResult = \imap_sort($imap, $criteria, $reverse, $flags);
    }
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $mailbox
 * @param int $flags
 * @return \stdClass
 * @throws ImapException
 *
 */
function imap_status(\IMAP\Connection $imap, string $mailbox, int $flags): \stdClass
{
    error_clear_last();
    $safeResult = \imap_status($imap, $mailbox, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $mailbox
 * @throws ImapException
 *
 */
function imap_subscribe(\IMAP\Connection $imap, string $mailbox): void
{
    error_clear_last();
    $safeResult = \imap_subscribe($imap, $mailbox);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param int $flags
 * @return array
 * @throws ImapException
 *
 */
function imap_thread(\IMAP\Connection $imap, int $flags = SE_FREE): array
{
    error_clear_last();
    $safeResult = \imap_thread($imap, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $timeout_type
 * @param int $timeout
 * @return mixed
 * @throws ImapException
 *
 */
function imap_timeout(int $timeout_type, int $timeout = -1)
{
    error_clear_last();
    $safeResult = \imap_timeout($timeout_type, $timeout);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $message_nums
 * @param int $flags
 * @return bool
 *
 */
function imap_undelete(\IMAP\Connection $imap, string $message_nums, int $flags = 0): bool
{
    error_clear_last();
    $safeResult = \imap_undelete($imap, $message_nums, $flags);
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $mailbox
 * @throws ImapException
 *
 */
function imap_unsubscribe(\IMAP\Connection $imap, string $mailbox): void
{
    error_clear_last();
    $safeResult = \imap_unsubscribe($imap, $mailbox);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param string $string
 * @return string
 * @throws ImapException
 *
 */
function imap_utf8_to_mutf7(string $string): string
{
    error_clear_last();
    $safeResult = \imap_utf8_to_mutf7($string);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\GettextException;

/**
 * @param string $domain
 * @param null|string $directory
 * @return string
 * @throws GettextException
 *
 */
function bindtextdomain(string $domain, ?string $directory = null): string
{
    error_clear_last();
    if ($directory !== null) {
        $safeResult = \bindtextdomain($domain, $directory);
    } else {
        $safeResult = \bindtextdomain($domain);
    }
    if ($safeResult === false) {
        throw GettextException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\PspellException;

/**
 * @param int $dictionary
 * @param string $word
 * @throws PspellException
 *
 */
function pspell_add_to_personal(int $dictionary, string $word): void
{
    error_clear_last();
    $safeResult = \pspell_add_to_personal($dictionary, $word);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
}


/**
 * @param int $dictionary
 * @param string $word
 * @throws PspellException
 *
 */
function pspell_add_to_session(int $dictionary, string $word): void
{
    error_clear_last();
    $safeResult = \pspell_add_to_session($dictionary, $word);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
}


/**
 * @param int $dictionary
 * @throws PspellException
 *
 */
function pspell_clear_session(int $dictionary): void
{
    error_clear_last();
    $safeResult = \pspell_clear_session($dictionary);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
}


/**
 * @param string $language
 * @param string $spelling
 * @param string $jargon
 * @param string $encoding
 * @return false|int
 *
 */
function pspell_config_create(string $language, string $spelling = "", string $jargon = "", string $encoding = "")
{
    error_clear_last();
    $safeResult = \pspell_config_create($language, $spelling, $jargon, $encoding);
    return $safeResult;
}


/**
 * @param int $config
 * @param string $directory
 * @throws PspellException
 *
 */
function pspell_config_data_dir(int $config, string $directory): void
{
    error_clear_last();
    $safeResult = \pspell_config_data_dir($config, $directory);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
}


/**
 * @param int $config
 * @param string $directory
 * @throws PspellException
 *
 */
function pspell_config_dict_dir(int $config, string $directory): void
{
    error_clear_last();
    $safeResult = \pspell_config_dict_dir($config, $directory);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
}


/**
 * @param int $config
 * @param int $min_length
 * @throws PspellException
 *
 */
function pspell_config_ignore(int $config, int $min_length): void
{
    error_clear_last();
    $safeResult = \pspell_config_ignore($config, $min_length);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
}


/**
 * @param int $config
 * @param int $mode
 * @throws PspellException
 *
 */
function pspell_config_mode(int $config, int $mode): void
{
    error_clear_last();
    $safeResult = \pspell_config_mode($config, $mode);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
}


/**
 * @param int $config
 * @param string $filename
 * @throws PspellException
 *
 */
function pspell_config_personal(int $config, string $filename): void
{
    error_clear_last();
    $safeResult = \pspell_config_personal($config, $filename);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
}


/**
 * @param int $config
 * @param string $filename
 * @throws PspellException
 *
 */
function pspell_config_repl(int $config, string $filename): void
{
    error_clear_last();
    $safeResult = \pspell_config_repl($config, $filename);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
}


/**
 * @param int $config
 * @param bool $allow
 * @throws PspellException
 *
 */
function pspell_config_runtogether(int $config, bool $allow): void
{
    error_clear_last();
    $safeResult = \pspell_config_runtogether($config, $allow);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
}


/**
 * @param int $config
 * @param bool $save
 * @throws PspellException
 *
 */
function pspell_config_save_repl(int $config, bool $save): void
{
    error_clear_last();
    $safeResult = \pspell_config_save_repl($config, $save);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
}


/**
 * @param int $config
 * @return int
 * @throws PspellException
 *
 */
function pspell_new_config(int $config): int
{
    error_clear_last();
    $safeResult = \pspell_new_config($config);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param string $language
 * @param string $spelling
 * @param string $jargon
 * @param string $encoding
 * @param int $mode
 * @return int
 * @throws PspellException
 *
 */
function pspell_new_personal(string $filename, string $language, string $spelling = "", string $jargon = "", string $encoding = "", int $mode = 0): int
{
    error_clear_last();
    $safeResult = \pspell_new_personal($filename, $language, $spelling, $jargon, $encoding, $mode);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $language
 * @param string $spelling
 * @param string $jargon
 * @param string $encoding
 * @param int $mode
 * @return int
 * @throws PspellException
 *
 */
function pspell_new(string $language, string $spelling = "", string $jargon = "", string $encoding = "", int $mode = 0): int
{
    error_clear_last();
    $safeResult = \pspell_new($language, $spelling, $jargon, $encoding, $mode);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $dictionary
 * @throws PspellException
 *
 */
function pspell_save_wordlist(int $dictionary): void
{
    error_clear_last();
    $safeResult = \pspell_save_wordlist($dictionary);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
}


/**
 * @param int $dictionary
 * @param string $misspelled
 * @param string $correct
 * @throws PspellException
 *
 */
function pspell_store_replacement(int $dictionary, string $misspelled, string $correct): void
{
    error_clear_last();
    $safeResult = \pspell_store_replacement($dictionary, $misspelled, $correct);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\FtpException;

/**
 * @param \FTP\Connection $ftp
 * @param int $size
 * @param null|string $response
 * @throws FtpException
 *
 */
function ftp_alloc(\FTP\Connection $ftp, int $size, ?string &$response = null): void
{
    error_clear_last();
    $safeResult = \ftp_alloc($ftp, $size, $response);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
}


/**
 * @param \FTP\Connection $ftp
 * @param string $remote_filename
 * @param string $local_filename
 * @param FTP_ASCII|FTP_BINARY $mode
 * @throws FtpException
 *
 */
function ftp_append(\FTP\Connection $ftp, string $remote_filename, string $local_filename, int $mode = FTP_BINARY): void
{
    error_clear_last();
    $safeResult = \ftp_append($ftp, $remote_filename, $local_filename, $mode);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
}


/**
 * @param \FTP\Connection $ftp
 * @throws FtpException
 *
 */
function ftp_cdup(\FTP\Connection $ftp): void
{
    error_clear_last();
    $safeResult = \ftp_cdup($ftp);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
}


/**
 * @param \FTP\Connection $ftp
 * @param string $directory
 * @throws FtpException
 *
 */
function ftp_chdir(\FTP\Connection $ftp, string $directory): void
{
    error_clear_last();
    $safeResult = \ftp_chdir($ftp, $directory);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
}


/**
 * @param \FTP\Connection $ftp
 * @param int $permissions
 * @param string $filename
 * @return int
 * @throws FtpException
 *
 */
function ftp_chmod(\FTP\Connection $ftp, int $permissions, string $filename): int
{
    error_clear_last();
    $safeResult = \ftp_chmod($ftp, $permissions, $filename);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \FTP\Connection $ftp
 * @throws FtpException
 *
 */
function ftp_close(\FTP\Connection $ftp): void
{
    error_clear_last();
    $safeResult = \ftp_close($ftp);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
}


/**
 * @param string $hostname
 * @param int $port
 * @param int $timeout
 * @return \FTP\Connection
 * @throws FtpException
 *
 */
function ftp_connect(string $hostname, int $port = 21, int $timeout = 90): \FTP\Connection
{
    error_clear_last();
    $safeResult = \ftp_connect($hostname, $port, $timeout);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \FTP\Connection $ftp
 * @param string $filename
 * @throws FtpException
 *
 */
function ftp_delete(\FTP\Connection $ftp, string $filename): void
{
    error_clear_last();
    $safeResult = \ftp_delete($ftp, $filename);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
}


/**
 * @param \FTP\Connection $ftp
 * @param resource $stream
 * @param string $remote_filename
 * @param FTP_ASCII|FTP_BINARY $mode
 * @param int $offset
 * @throws FtpException
 *
 */
function ftp_fget(\FTP\Connection $ftp, $stream, string $remote_filename, int $mode = FTP_BINARY, int $offset = 0): void
{
    error_clear_last();
    $safeResult = \ftp_fget($ftp, $stream, $remote_filename, $mode, $offset);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
}


/**
 * @param \FTP\Connection $ftp
 * @param string $remote_filename
 * @param resource $stream
 * @param FTP_ASCII|FTP_BINARY $mode
 * @param int $offset
 * @throws FtpException
 *
 */
function ftp_fput(\FTP\Connection $ftp, string $remote_filename, $stream, int $mode = FTP_BINARY, int $offset = 0): void
{
    error_clear_last();
    $safeResult = \ftp_fput($ftp, $remote_filename, $stream, $mode, $offset);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
}


/**
 * @param \FTP\Connection $ftp
 * @param string $local_filename
 * @param string $remote_filename
 * @param FTP_ASCII|FTP_BINARY $mode
 * @param int $offset
 * @throws FtpException
 *
 */
function ftp_get(\FTP\Connection $ftp, string $local_filename, string $remote_filename, int $mode = FTP_BINARY, int $offset = 0): void
{
    error_clear_last();
    $safeResult = \ftp_get($ftp, $local_filename, $remote_filename, $mode, $offset);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
}


/**
 * @param \FTP\Connection $ftp
 * @param string $username
 * @param string $password
 * @throws FtpException
 *
 */
function ftp_login(\FTP\Connection $ftp, string $username, string $password): void
{
    error_clear_last();
    $safeResult = \ftp_login($ftp, $username, $password);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
}


/**
 * @param \FTP\Connection $ftp
 * @param string $directory
 * @return string
 * @throws FtpException
 *
 */
function ftp_mkdir(\FTP\Connection $ftp, string $directory): string
{
    error_clear_last();
    $safeResult = \ftp_mkdir($ftp, $directory);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \FTP\Connection $ftp
 * @param string $directory
 * @return array
 * @throws FtpException
 *
 */
function ftp_mlsd(\FTP\Connection $ftp, string $directory): array
{
    error_clear_last();
    $safeResult = \ftp_mlsd($ftp, $directory);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \FTP\Connection $ftp
 * @param string $local_filename
 * @param string $remote_filename
 * @param FTP_ASCII|FTP_BINARY $mode
 * @param int $offset
 * @return int
 * @throws FtpException
 *
 */
function ftp_nb_get(\FTP\Connection $ftp, string $local_filename, string $remote_filename, int $mode = FTP_BINARY, int $offset = 0): int
{
    error_clear_last();
    $safeResult = \ftp_nb_get($ftp, $local_filename, $remote_filename, $mode, $offset);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \FTP\Connection $ftp
 * @param string $remote_filename
 * @param string $local_filename
 * @param FTP_ASCII|FTP_BINARY $mode
 * @param int $offset
 * @return int
 * @throws FtpException
 *
 */
function ftp_nb_put(\FTP\Connection $ftp, string $remote_filename, string $local_filename, int $mode = FTP_BINARY, int $offset = 0): int
{
    error_clear_last();
    $safeResult = \ftp_nb_put($ftp, $remote_filename, $local_filename, $mode, $offset);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \FTP\Connection $ftp
 * @param string $directory
 * @return array
 * @throws FtpException
 *
 */
function ftp_nlist(\FTP\Connection $ftp, string $directory): array
{
    error_clear_last();
    $safeResult = \ftp_nlist($ftp, $directory);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \FTP\Connection $ftp
 * @param bool $enable
 * @throws FtpException
 *
 */
function ftp_pasv(\FTP\Connection $ftp, bool $enable): void
{
    error_clear_last();
    $safeResult = \ftp_pasv($ftp, $enable);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
}


/**
 * @param \FTP\Connection $ftp
 * @param string $remote_filename
 * @param string $local_filename
 * @param FTP_ASCII|FTP_BINARY $mode
 * @param int $offset
 * @throws FtpException
 *
 */
function ftp_put(\FTP\Connection $ftp, string $remote_filename, string $local_filename, int $mode = FTP_BINARY, int $offset = 0): void
{
    error_clear_last();
    $safeResult = \ftp_put($ftp, $remote_filename, $local_filename, $mode, $offset);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
}


/**
 * @param \FTP\Connection $ftp
 * @return string
 * @throws FtpException
 *
 */
function ftp_pwd(\FTP\Connection $ftp): string
{
    error_clear_last();
    $safeResult = \ftp_pwd($ftp);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \FTP\Connection $ftp
 * @param string $from
 * @param string $to
 * @throws FtpException
 *
 */
function ftp_rename(\FTP\Connection $ftp, string $from, string $to): void
{
    error_clear_last();
    $safeResult = \ftp_rename($ftp, $from, $to);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
}


/**
 * @param \FTP\Connection $ftp
 * @param string $directory
 * @throws FtpException
 *
 */
function ftp_rmdir(\FTP\Connection $ftp, string $directory): void
{
    error_clear_last();
    $safeResult = \ftp_rmdir($ftp, $directory);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
}


/**
 * @param \FTP\Connection $ftp
 * @param string $command
 * @throws FtpException
 *
 */
function ftp_site(\FTP\Connection $ftp, string $command): void
{
    error_clear_last();
    $safeResult = \ftp_site($ftp, $command);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
}


/**
 * @param \FTP\Connection $ftp
 * @param string $filename
 * @return int
 * @throws FtpException
 *
 */
function ftp_size(\FTP\Connection $ftp, string $filename): int
{
    error_clear_last();
    $safeResult = \ftp_size($ftp, $filename);
    if ($safeResult === -1) {
        throw FtpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $hostname
 * @param int $port
 * @param int $timeout
 * @return \FTP\Connection
 * @throws FtpException
 *
 */
function ftp_ssl_connect(string $hostname, int $port = 21, int $timeout = 90): \FTP\Connection
{
    error_clear_last();
    $safeResult = \ftp_ssl_connect($hostname, $port, $timeout);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \FTP\Connection $ftp
 * @return string
 * @throws FtpException
 *
 */
function ftp_systype(\FTP\Connection $ftp): string
{
    error_clear_last();
    $safeResult = \ftp_systype($ftp);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\ArrayException;

/**
 * @param array $array
 * @param callable $callback
 * @throws ArrayException
 *
 */
function array_all(array $array, callable $callback): void
{
    error_clear_last();
    $safeResult = \array_all($array, $callback);
    if ($safeResult === false) {
        throw ArrayException::createFromPhpError();
    }
}


/**
 * @param array $keys
 * @param array $values
 * @return array
 *
 */
function array_combine(array $keys, array $values): array
{
    error_clear_last();
    $safeResult = \array_combine($keys, $values);
    return $safeResult;
}


/**
 * @param array $array
 * @return array
 *
 */
function array_flip(array $array): array
{
    error_clear_last();
    $safeResult = \array_flip($array);
    return $safeResult;
}


/**
 * @param array $array
 * @param array $replacements
 * @return array
 *
 */
function array_replace_recursive(array $array, array ...$replacements): array
{
    error_clear_last();
    if ($replacements !== []) {
        $safeResult = \array_replace_recursive($array, ...$replacements);
    } else {
        $safeResult = \array_replace_recursive($array);
    }
    return $safeResult;
}


/**
 * @param array $array
 * @param array $replacements
 * @return array
 *
 */
function array_replace(array $array, array ...$replacements): array
{
    error_clear_last();
    if ($replacements !== []) {
        $safeResult = \array_replace($array, ...$replacements);
    } else {
        $safeResult = \array_replace($array);
    }
    return $safeResult;
}


/**
 * @param array|object $array
 * @param callable $callback
 * @param mixed $arg
 * @return bool
 *
 */
function array_walk_recursive(&$array, callable $callback, $arg = null): bool
{
    error_clear_last();
    if ($arg !== null) {
        $safeResult = \array_walk_recursive($array, $callback, $arg);
    } else {
        $safeResult = \array_walk_recursive($array, $callback);
    }
    return $safeResult;
}


/**
 * @param array $array
 * @return bool
 *
 */
function shuffle(array &$array): bool
{
    error_clear_last();
    $safeResult = \shuffle($array);
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\SocketsException;

/**
 * @param \Socket $socket
 * @return \Socket
 * @throws SocketsException
 *
 */
function socket_accept(\Socket $socket): \Socket
{
    error_clear_last();
    $safeResult = \socket_accept($socket);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \AddressInfo $address
 * @return \Socket
 * @throws SocketsException
 *
 */
function socket_addrinfo_bind(\AddressInfo $address): \Socket
{
    error_clear_last();
    $safeResult = \socket_addrinfo_bind($address);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \AddressInfo $address
 * @return \Socket
 * @throws SocketsException
 *
 */
function socket_addrinfo_connect(\AddressInfo $address): \Socket
{
    error_clear_last();
    $safeResult = \socket_addrinfo_connect($address);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $host
 * @param mixed $service
 * @param array $hints
 * @return \AddressInfo[]
 * @throws SocketsException
 *
 */
function socket_addrinfo_lookup(string $host, $service = null, array $hints = []): array
{
    error_clear_last();
    if ($hints !== []) {
        $safeResult = \socket_addrinfo_lookup($host, $service, $hints);
    } elseif ($service !== null) {
        $safeResult = \socket_addrinfo_lookup($host, $service);
    } else {
        $safeResult = \socket_addrinfo_lookup($host);
    }
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Socket $socket
 * @throws SocketsException
 *
 */
function socket_atmark(\Socket $socket): void
{
    error_clear_last();
    $safeResult = \socket_atmark($socket);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
}


/**
 * @param \Socket $socket
 * @param string $address
 * @param int $port
 * @throws SocketsException
 *
 */
function socket_bind(\Socket $socket, string $address, int $port = 0): void
{
    error_clear_last();
    $safeResult = \socket_bind($socket, $address, $port);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
}


/**
 * @param \Socket $socket
 * @param string $address
 * @param int|null $port
 * @throws SocketsException
 *
 */
function socket_connect(\Socket $socket, string $address, ?int $port = null): void
{
    error_clear_last();
    if ($port !== null) {
        $safeResult = \socket_connect($socket, $address, $port);
    } else {
        $safeResult = \socket_connect($socket, $address);
    }
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
}


/**
 * @param int $port
 * @param int $backlog
 * @return \Socket
 * @throws SocketsException
 *
 */
function socket_create_listen(int $port, int $backlog = SOMAXCONN): \Socket
{
    error_clear_last();
    $safeResult = \socket_create_listen($port, $backlog);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $domain
 * @param int $type
 * @param int $protocol
 * @param \Socket[]|null $pair
 * @throws SocketsException
 *
 */
function socket_create_pair(int $domain, int $type, int $protocol, ?array &$pair): void
{
    error_clear_last();
    $safeResult = \socket_create_pair($domain, $type, $protocol, $pair);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
}


/**
 * @param int $domain
 * @param int $type
 * @param int $protocol
 * @return \Socket
 * @throws SocketsException
 *
 */
function socket_create(int $domain, int $type, int $protocol): \Socket
{
    error_clear_last();
    $safeResult = \socket_create($domain, $type, $protocol);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Socket $socket
 * @return resource
 * @throws SocketsException
 *
 */
function socket_export_stream(\Socket $socket)
{
    error_clear_last();
    $safeResult = \socket_export_stream($socket);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Socket $socket
 * @param int $level
 * @param int $option
 * @return mixed
 * @throws SocketsException
 *
 */
function socket_get_option(\Socket $socket, int $level, int $option)
{
    error_clear_last();
    $safeResult = \socket_get_option($socket, $level, $option);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Socket $socket
 * @param null|string $address
 * @param int|null $port
 * @throws SocketsException
 *
 */
function socket_getpeername(\Socket $socket, ?string &$address, ?int &$port = null): void
{
    error_clear_last();
    $safeResult = \socket_getpeername($socket, $address, $port);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
}


/**
 * @param \Socket $socket
 * @param null|string $address
 * @param int|null $port
 * @throws SocketsException
 *
 */
function socket_getsockname(\Socket $socket, ?string &$address, ?int &$port = null): void
{
    error_clear_last();
    $safeResult = \socket_getsockname($socket, $address, $port);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
}


/**
 * @param resource $stream
 * @return \Socket
 * @throws SocketsException
 *
 */
function socket_import_stream($stream): \Socket
{
    error_clear_last();
    $safeResult = \socket_import_stream($stream);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Socket $socket
 * @param int $backlog
 * @throws SocketsException
 *
 */
function socket_listen(\Socket $socket, int $backlog = 0): void
{
    error_clear_last();
    $safeResult = \socket_listen($socket, $backlog);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
}


/**
 * @param \Socket $socket
 * @param int $length
 * @param int $mode
 * @return string
 * @throws SocketsException
 *
 */
function socket_read(\Socket $socket, int $length, int $mode = PHP_BINARY_READ): string
{
    error_clear_last();
    $safeResult = \socket_read($socket, $length, $mode);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Socket $socket
 * @param string $data
 * @param int $length
 * @param int $flags
 * @return int
 * @throws SocketsException
 *
 */
function socket_send(\Socket $socket, string $data, int $length, int $flags): int
{
    error_clear_last();
    $safeResult = \socket_send($socket, $data, $length, $flags);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Socket $socket
 * @param array $message
 * @param int $flags
 * @return int
 * @throws SocketsException
 *
 */
function socket_sendmsg(\Socket $socket, array $message, int $flags = 0): int
{
    error_clear_last();
    $safeResult = \socket_sendmsg($socket, $message, $flags);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Socket $socket
 * @param string $data
 * @param int $length
 * @param int $flags
 * @param string $address
 * @param int|null $port
 * @return int
 * @throws SocketsException
 *
 */
function socket_sendto(\Socket $socket, string $data, int $length, int $flags, string $address, ?int $port = null): int
{
    error_clear_last();
    if ($port !== null) {
        $safeResult = \socket_sendto($socket, $data, $length, $flags, $address, $port);
    } else {
        $safeResult = \socket_sendto($socket, $data, $length, $flags, $address);
    }
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Socket $socket
 * @throws SocketsException
 *
 */
function socket_set_block(\Socket $socket): void
{
    error_clear_last();
    $safeResult = \socket_set_block($socket);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
}


/**
 * @param \Socket $socket
 * @throws SocketsException
 *
 */
function socket_set_nonblock(\Socket $socket): void
{
    error_clear_last();
    $safeResult = \socket_set_nonblock($socket);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
}


/**
 * @param \Socket $socket
 * @param int $level
 * @param int $option
 * @param array|int|string $value
 * @throws SocketsException
 *
 */
function socket_set_option(\Socket $socket, int $level, int $option, $value): void
{
    error_clear_last();
    $safeResult = \socket_set_option($socket, $level, $option, $value);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
}


/**
 * @param \Socket $socket
 * @param int $mode
 * @throws SocketsException
 *
 */
function socket_shutdown(\Socket $socket, int $mode = 2): void
{
    error_clear_last();
    $safeResult = \socket_shutdown($socket, $mode);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
}


/**
 * @param \Socket $socket
 * @param int $process_id
 * @return string
 * @throws SocketsException
 *
 */
function socket_wsaprotocol_info_export(\Socket $socket, int $process_id): string
{
    error_clear_last();
    $safeResult = \socket_wsaprotocol_info_export($socket, $process_id);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $info_id
 * @return \Socket
 * @throws SocketsException
 *
 */
function socket_wsaprotocol_info_import(string $info_id): \Socket
{
    error_clear_last();
    $safeResult = \socket_wsaprotocol_info_import($info_id);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $info_id
 * @throws SocketsException
 *
 */
function socket_wsaprotocol_info_release(string $info_id): void
{
    error_clear_last();
    $safeResult = \socket_wsaprotocol_info_release($info_id);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\MiscException;

/**
 * @param string $constant_name
 * @param mixed $value
 * @param bool $case_insensitive
 * @throws MiscException
 *
 */
function define(string $constant_name, $value, bool $case_insensitive = false): void
{
    error_clear_last();
    $safeResult = \define($constant_name, $value, $case_insensitive);
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @param bool $return
 * @return bool|string
 * @throws MiscException
 *
 */
function highlight_file(string $filename, bool $return = false)
{
    error_clear_last();
    $safeResult = \highlight_file($filename, $return);
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $string
 * @param bool $return
 * @return bool|string
 *
 */
function highlight_string(string $string, bool $return = false)
{
    error_clear_last();
    $safeResult = \highlight_string($string, $return);
    return $safeResult;
}


/**
 * @param bool $as_number
 * @return array{0:int,1:int}|float|int
 * @throws MiscException
 *
 */
function hrtime(bool $as_number = false)
{
    error_clear_last();
    $safeResult = \hrtime($as_number);
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $format
 * @param mixed $values
 * @return string
 *
 */
function pack(string $format, ...$values): string
{
    error_clear_last();
    if ($values !== []) {
        $safeResult = \pack($format, ...$values);
    } else {
        $safeResult = \pack($format);
    }
    return $safeResult;
}


/**
 * @param int|string $in_codepage
 * @param int|string $out_codepage
 * @param string $subject
 * @return string
 * @throws MiscException
 *
 */
function sapi_windows_cp_conv($in_codepage, $out_codepage, string $subject): string
{
    error_clear_last();
    $safeResult = \sapi_windows_cp_conv($in_codepage, $out_codepage, $subject);
    if ($safeResult === null) {
        throw MiscException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $codepage
 * @throws MiscException
 *
 */
function sapi_windows_cp_set(int $codepage): void
{
    error_clear_last();
    $safeResult = \sapi_windows_cp_set($codepage);
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
}


/**
 * @param int $event
 * @param int $pid
 * @throws MiscException
 *
 */
function sapi_windows_generate_ctrl_event(int $event, int $pid = 0): void
{
    error_clear_last();
    $safeResult = \sapi_windows_generate_ctrl_event($event, $pid);
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
}


/**
 * @param callable|null $handler
 * @param bool $add
 * @throws MiscException
 *
 */
function sapi_windows_set_ctrl_handler(?callable $handler, bool $add = true): void
{
    error_clear_last();
    $safeResult = \sapi_windows_set_ctrl_handler($handler, $add);
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
}


/**
 * @param resource $stream
 * @param bool|null $enable
 * @throws MiscException
 *
 */
function sapi_windows_vt100_support($stream, ?bool $enable = null): void
{
    error_clear_last();
    if ($enable !== null) {
        $safeResult = \sapi_windows_vt100_support($stream, $enable);
    } else {
        $safeResult = \sapi_windows_vt100_support($stream);
    }
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
}


/**
 * @param int $seconds
 * @return false|int
 *
 */
function sleep(int $seconds)
{
    error_clear_last();
    $safeResult = \sleep($seconds);
    return $safeResult;
}


/**
 * @param int $seconds
 * @param int $nanoseconds
 * @return array{seconds:0|positive-int,nanoseconds:0|positive-int}|bool
 * @throws MiscException
 *
 */
function time_nanosleep(int $seconds, int $nanoseconds)
{
    error_clear_last();
    $safeResult = \time_nanosleep($seconds, $nanoseconds);
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param float $timestamp
 * @throws MiscException
 *
 */
function time_sleep_until(float $timestamp): void
{
    error_clear_last();
    $safeResult = \time_sleep_until($timestamp);
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
}


/**
 * @param string $format
 * @param string $string
 * @param int $offset
 * @return array
 * @throws MiscException
 *
 */
function unpack(string $format, string $string, int $offset = 0): array
{
    error_clear_last();
    $safeResult = \unpack($format, $string, $offset);
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\CurlException;

/**
 * @param \CurlHandle $handle
 * @return \CurlHandle
 * @throws CurlException
 *
 */
function curl_copy_handle(\CurlHandle $handle): \CurlHandle
{
    error_clear_last();
    $safeResult = \curl_copy_handle($handle);
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($handle);
    }
    return $safeResult;
}


/**
 * @param \CurlHandle $handle
 * @param string $string
 * @return string
 * @throws CurlException
 *
 */
function curl_escape(\CurlHandle $handle, string $string): string
{
    error_clear_last();
    $safeResult = \curl_escape($handle, $string);
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($handle);
    }
    return $safeResult;
}


/**
 * @param \CurlHandle $handle
 * @return bool|string
 * @throws CurlException
 *
 */
function curl_exec(\CurlHandle $handle)
{
    error_clear_last();
    $safeResult = \curl_exec($handle);
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($handle);
    }
    return $safeResult;
}


/**
 * @param \CurlHandle $handle
 * @param int|null $option
 * @return mixed
 * @throws CurlException
 *
 */
function curl_getinfo(\CurlHandle $handle, ?int $option = null)
{
    error_clear_last();
    if ($option !== null) {
        $safeResult = \curl_getinfo($handle, $option);
    } else {
        $safeResult = \curl_getinfo($handle);
    }
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($handle);
    }
    return $safeResult;
}


/**
 * @param null|string $url
 * @return \CurlHandle
 * @throws CurlException
 *
 */
function curl_init(?string $url = null): \CurlHandle
{
    error_clear_last();
    if ($url !== null) {
        $safeResult = \curl_init($url);
    } else {
        $safeResult = \curl_init();
    }
    if ($safeResult === false) {
        throw CurlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \CurlMultiHandle $multi_handle
 * @param int|null $queued_messages
 * @return array
 * @throws CurlException
 *
 */
function curl_multi_info_read(\CurlMultiHandle $multi_handle, ?int &$queued_messages = null): array
{
    error_clear_last();
    $safeResult = \curl_multi_info_read($multi_handle, $queued_messages);
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($multi_handle);
    }
    return $safeResult;
}


/**
 * @return \CurlMultiHandle
 *
 */
function curl_multi_init(): \CurlMultiHandle
{
    error_clear_last();
    $safeResult = \curl_multi_init();
    return $safeResult;
}


/**
 * @param \CurlMultiHandle $multi_handle
 * @param int $option
 * @param mixed $value
 * @throws CurlException
 *
 */
function curl_multi_setopt(\CurlMultiHandle $multi_handle, int $option, $value): void
{
    error_clear_last();
    $safeResult = \curl_multi_setopt($multi_handle, $option, $value);
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($multi_handle);
    }
}


/**
 * @param \CurlHandle $handle
 * @param int $option
 * @param mixed $value
 * @throws CurlException
 *
 */
function curl_setopt(\CurlHandle $handle, int $option, $value): void
{
    error_clear_last();
    $safeResult = \curl_setopt($handle, $option, $value);
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($handle);
    }
}


/**
 * @param \CurlShareHandle $share_handle
 * @return int
 *
 */
function curl_share_errno(\CurlShareHandle $share_handle): int
{
    error_clear_last();
    $safeResult = \curl_share_errno($share_handle);
    return $safeResult;
}


/**
 * @param \CurlShareHandle $share_handle
 * @param int $option
 * @param mixed $value
 * @throws CurlException
 *
 */
function curl_share_setopt(\CurlShareHandle $share_handle, int $option, $value): void
{
    error_clear_last();
    $safeResult = \curl_share_setopt($share_handle, $option, $value);
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($share_handle);
    }
}


/**
 * @param \CurlHandle $handle
 * @param string $string
 * @return string
 * @throws CurlException
 *
 */
function curl_unescape(\CurlHandle $handle, string $string): string
{
    error_clear_last();
    $safeResult = \curl_unescape($handle, $string);
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($handle);
    }
    return $safeResult;
}


/**
 * @param \CurlHandle $handle
 * @throws CurlException
 *
 */
function curl_upkeep(\CurlHandle $handle): void
{
    error_clear_last();
    $safeResult = \curl_upkeep($handle);
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($handle);
    }
}
<?php

namespace Safe;

use Safe\Exceptions\SodiumException;

/**
 * @param string $ciphertext
 * @param string $additional_data
 * @param string $nonce
 * @param string $key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_aead_aegis128l_decrypt(string $ciphertext, string $additional_data, string $nonce, string $key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_aead_aegis128l_decrypt($ciphertext, $additional_data, $nonce, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $ciphertext
 * @param string $additional_data
 * @param string $nonce
 * @param string $key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_aead_aegis256_decrypt(string $ciphertext, string $additional_data, string $nonce, string $key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_aead_aegis256_decrypt($ciphertext, $additional_data, $nonce, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $ciphertext
 * @param string $additional_data
 * @param string $nonce
 * @param string $key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_aead_aes256gcm_decrypt(string $ciphertext, string $additional_data, string $nonce, string $key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $additional_data, $nonce, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $ciphertext
 * @param string $additional_data
 * @param string $nonce
 * @param string $key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_aead_chacha20poly1305_decrypt(string $ciphertext, string $additional_data, string $nonce, string $key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_aead_chacha20poly1305_decrypt($ciphertext, $additional_data, $nonce, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $message
 * @param string $additional_data
 * @param string $nonce
 * @param string $key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_aead_chacha20poly1305_encrypt(string $message, string $additional_data, string $nonce, string $key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_aead_chacha20poly1305_encrypt($message, $additional_data, $nonce, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $ciphertext
 * @param string $additional_data
 * @param string $nonce
 * @param string $key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_aead_chacha20poly1305_ietf_decrypt(string $ciphertext, string $additional_data, string $nonce, string $key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_aead_chacha20poly1305_ietf_decrypt($ciphertext, $additional_data, $nonce, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $message
 * @param string $additional_data
 * @param string $nonce
 * @param string $key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_aead_chacha20poly1305_ietf_encrypt(string $message, string $additional_data, string $nonce, string $key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_aead_chacha20poly1305_ietf_encrypt($message, $additional_data, $nonce, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $ciphertext
 * @param string $additional_data
 * @param string $nonce
 * @param string $key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_aead_xchacha20poly1305_ietf_decrypt(string $ciphertext, string $additional_data, string $nonce, string $key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_aead_xchacha20poly1305_ietf_decrypt($ciphertext, $additional_data, $nonce, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $message
 * @param string $additional_data
 * @param string $nonce
 * @param string $key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_aead_xchacha20poly1305_ietf_encrypt(string $message, string $additional_data, string $nonce, string $key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_aead_xchacha20poly1305_ietf_encrypt($message, $additional_data, $nonce, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $mac
 * @param string $message
 * @param string $key
 * @throws SodiumException
 *
 */
function sodium_crypto_auth_verify(string $mac, string $message, string $key): void
{
    error_clear_last();
    $safeResult = \sodium_crypto_auth_verify($mac, $message, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
}


/**
 * @param string $ciphertext
 * @param string $nonce
 * @param string $key_pair
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_box_open(string $ciphertext, string $nonce, string $key_pair): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_box_open($ciphertext, $nonce, $key_pair);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $ciphertext
 * @param string $key_pair
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_box_seal_open(string $ciphertext, string $key_pair): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_box_seal_open($ciphertext, $key_pair);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param non-empty-string $state
 * @param string $message
 * @return bool
 *
 */
function sodium_crypto_generichash_update(string &$state, string $message): bool
{
    error_clear_last();
    $safeResult = \sodium_crypto_generichash_update($state, $message);
    return $safeResult;
}


/**
 * @param string $ciphertext
 * @param string $nonce
 * @param string $key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_secretbox_open(string $ciphertext, string $nonce, string $key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_secretbox_open($ciphertext, $nonce, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $signed_message
 * @param non-empty-string $public_key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_sign_open(string $signed_message, string $public_key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_sign_open($signed_message, $public_key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param non-empty-string $signature
 * @param string $message
 * @param non-empty-string $public_key
 * @throws SodiumException
 *
 */
function sodium_crypto_sign_verify_detached(string $signature, string $message, string $public_key): void
{
    error_clear_last();
    $safeResult = \sodium_crypto_sign_verify_detached($signature, $message, $public_key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
}


/**
 * @param string $message
 * @param string $nonce
 * @param int $counter
 * @param string $key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_stream_xchacha20_xor_ic(string $message, string $nonce, int $counter, string $key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_stream_xchacha20_xor_ic($message, $nonce, $counter, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\ZlibException;

/**
 * @param \DeflateContext $context
 * @param string $data
 * @param int $flush_mode
 * @return string
 * @throws ZlibException
 *
 */
function deflate_add(\DeflateContext $context, string $data, int $flush_mode = ZLIB_SYNC_FLUSH): string
{
    error_clear_last();
    $safeResult = \deflate_add($context, $data, $flush_mode);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $encoding
 * @param array $options
 * @return \DeflateContext
 * @throws ZlibException
 *
 */
function deflate_init(int $encoding, array $options = []): \DeflateContext
{
    error_clear_last();
    $safeResult = \deflate_init($encoding, $options);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @throws ZlibException
 *
 */
function gzclose($stream): void
{
    error_clear_last();
    $safeResult = \gzclose($stream);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
}


/**
 * @param string $data
 * @param int $level
 * @param int $encoding
 * @return string
 * @throws ZlibException
 *
 */
function gzcompress(string $data, int $level = -1, int $encoding = ZLIB_ENCODING_DEFLATE): string
{
    error_clear_last();
    $safeResult = \gzcompress($data, $level, $encoding);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param int $max_length
 * @return string
 * @throws ZlibException
 *
 */
function gzdecode(string $data, int $max_length = 0): string
{
    error_clear_last();
    $safeResult = \gzdecode($data, $max_length);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param int $level
 * @param int $encoding
 * @return string
 * @throws ZlibException
 *
 */
function gzdeflate(string $data, int $level = -1, int $encoding = ZLIB_ENCODING_RAW): string
{
    error_clear_last();
    $safeResult = \gzdeflate($data, $level, $encoding);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param int $level
 * @param int $encoding
 * @return string
 * @throws ZlibException
 *
 */
function gzencode(string $data, int $level = -1, int $encoding = ZLIB_ENCODING_GZIP): string
{
    error_clear_last();
    $safeResult = \gzencode($data, $level, $encoding);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param  $use_include_path
 * @return list
 * @throws ZlibException
 *
 */
function gzfile(string $filename, $use_include_path = 0): array
{
    error_clear_last();
    $safeResult = \gzfile($filename, $use_include_path);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @param int|null $length
 * @return string
 * @throws ZlibException
 *
 */
function gzgets($stream, ?int $length = null): string
{
    error_clear_last();
    if ($length !== null) {
        $safeResult = \gzgets($stream, $length);
    } else {
        $safeResult = \gzgets($stream);
    }
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param int $max_length
 * @return string
 * @throws ZlibException
 *
 */
function gzinflate(string $data, int $max_length = 0): string
{
    error_clear_last();
    $safeResult = \gzinflate($data, $max_length);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param string $mode
 * @param  $use_include_path
 * @return resource
 * @throws ZlibException
 *
 */
function gzopen(string $filename, string $mode, $use_include_path = 0)
{
    error_clear_last();
    $safeResult = \gzopen($filename, $mode, $use_include_path);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @return false|int
 *
 */
function gzpassthru($stream)
{
    error_clear_last();
    $safeResult = \gzpassthru($stream);
    return $safeResult;
}


/**
 * @param resource $stream
 * @param int $length
 * @return string
 * @throws ZlibException
 *
 */
function gzread($stream, int $length): string
{
    error_clear_last();
    $safeResult = \gzread($stream, $length);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @throws ZlibException
 *
 */
function gzrewind($stream): void
{
    error_clear_last();
    $safeResult = \gzrewind($stream);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
}


/**
 * @param resource $stream
 * @return int
 * @throws ZlibException
 *
 */
function gztell($stream): int
{
    error_clear_last();
    $safeResult = \gztell($stream);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param int $max_length
 * @return string
 * @throws ZlibException
 *
 */
function gzuncompress(string $data, int $max_length = 0): string
{
    error_clear_last();
    $safeResult = \gzuncompress($data, $max_length);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @param string $data
 * @param int|null $length
 * @return int
 * @throws ZlibException
 *
 */
function gzwrite($stream, string $data, ?int $length = null): int
{
    error_clear_last();
    if ($length !== null) {
        $safeResult = \gzwrite($stream, $data, $length);
    } else {
        $safeResult = \gzwrite($stream, $data);
    }
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \InflateContext $context
 * @return int
 * @throws ZlibException
 *
 */
function inflate_get_read_len(\InflateContext $context): int
{
    error_clear_last();
    $safeResult = \inflate_get_read_len($context);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \InflateContext $context
 * @return false|int
 *
 */
function inflate_get_status(\InflateContext $context)
{
    error_clear_last();
    $safeResult = \inflate_get_status($context);
    return $safeResult;
}


/**
 * @param \InflateContext $context
 * @param string $data
 * @param int $flush_mode
 * @return string
 * @throws ZlibException
 *
 */
function inflate_add(\InflateContext $context, string $data, int $flush_mode = ZLIB_SYNC_FLUSH): string
{
    error_clear_last();
    $safeResult = \inflate_add($context, $data, $flush_mode);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $encoding
 * @param array $options
 * @return \InflateContext
 * @throws ZlibException
 *
 */
function inflate_init(int $encoding, array $options = []): \InflateContext
{
    error_clear_last();
    $safeResult = \inflate_init($encoding, $options);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param  $use_include_path
 * @return 0|positive-int
 * @throws ZlibException
 *
 */
function readgzfile(string $filename, $use_include_path = 0): int
{
    error_clear_last();
    $safeResult = \readgzfile($filename, $use_include_path);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param int $max_length
 * @return string
 * @throws ZlibException
 *
 */
function zlib_decode(string $data, int $max_length = 0): string
{
    error_clear_last();
    $safeResult = \zlib_decode($data, $max_length);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\ImageException;

/**
 * @param string $filename
 * @param array|null $image_info
 * @return array{0: 0|positive-int, 1: 0|positive-int, 2: int, 3: string, mime: string, channels: int, bits: int}|null
 * @throws ImageException
 *
 */
function getimagesize(string $filename, ?array &$image_info = null): ?array
{
    error_clear_last();
    $safeResult = \getimagesize($filename, $image_info);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $image_type
 * @param bool $include_dot
 * @return string
 * @throws ImageException
 *
 */
function image_type_to_extension(int $image_type, bool $include_dot = true): string
{
    error_clear_last();
    $safeResult = \image_type_to_extension($image_type, $include_dot);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param array $affine
 * @param array|null $clip
 * @return \GdImage
 * @throws ImageException
 *
 */
function imageaffine(\GdImage $image, array $affine, ?array $clip = null): \GdImage
{
    error_clear_last();
    if ($clip !== null) {
        $safeResult = \imageaffine($image, $affine, $clip);
    } else {
        $safeResult = \imageaffine($image, $affine);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param array $matrix1
 * @param array $matrix2
 * @return array{0:float,1:float,2:float,3:float,4:float,5:float}
 * @throws ImageException
 *
 */
function imageaffinematrixconcat(array $matrix1, array $matrix2): array
{
    error_clear_last();
    $safeResult = \imageaffinematrixconcat($matrix1, $matrix2);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $type
 * @param array|float $options
 * @return array{0:float,1:float,2:float,3:float,4:float,5:float}
 * @throws ImageException
 *
 */
function imageaffinematrixget(int $type, $options): array
{
    error_clear_last();
    $safeResult = \imageaffinematrixget($type, $options);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param bool $enable
 * @throws ImageException
 *
 */
function imagealphablending(\GdImage $image, bool $enable): void
{
    error_clear_last();
    $safeResult = \imagealphablending($image, $enable);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param bool $enable
 * @throws ImageException
 *
 */
function imageantialias(\GdImage $image, bool $enable): void
{
    error_clear_last();
    $safeResult = \imageantialias($image, $enable);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $center_x
 * @param int $center_y
 * @param int $width
 * @param int $height
 * @param int $start_angle
 * @param int $end_angle
 * @param int $color
 * @throws ImageException
 *
 */
function imagearc(\GdImage $image, int $center_x, int $center_y, int $width, int $height, int $start_angle, int $end_angle, int $color): void
{
    error_clear_last();
    $safeResult = \imagearc($image, $center_x, $center_y, $width, $height, $start_angle, $end_angle, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param null|resource|string $file
 * @param int $quality
 * @param int $speed
 * @throws ImageException
 *
 */
function imageavif(\GdImage $image, $file = null, int $quality = -1, int $speed = -1): void
{
    error_clear_last();
    if ($speed !== -1) {
        $safeResult = \imageavif($image, $file, $quality, $speed);
    } elseif ($quality !== -1) {
        $safeResult = \imageavif($image, $file, $quality);
    } elseif ($file !== null) {
        $safeResult = \imageavif($image, $file);
    } else {
        $safeResult = \imageavif($image);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param null|resource|string $file
 * @param bool $compressed
 * @throws ImageException
 *
 */
function imagebmp(\GdImage $image, $file = null, bool $compressed = true): void
{
    error_clear_last();
    if ($compressed !== true) {
        $safeResult = \imagebmp($image, $file, $compressed);
    } elseif ($file !== null) {
        $safeResult = \imagebmp($image, $file);
    } else {
        $safeResult = \imagebmp($image);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $font
 * @param int $x
 * @param int $y
 * @param string $char
 * @param int $color
 * @throws ImageException
 *
 */
function imagechar(\GdImage $image, int $font, int $x, int $y, string $char, int $color): void
{
    error_clear_last();
    $safeResult = \imagechar($image, $font, $x, $y, $char, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $font
 * @param int $x
 * @param int $y
 * @param string $char
 * @param int $color
 * @throws ImageException
 *
 */
function imagecharup(\GdImage $image, int $font, int $x, int $y, string $char, int $color): void
{
    error_clear_last();
    $safeResult = \imagecharup($image, $font, $x, $y, $char, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $x
 * @param int $y
 * @return int
 * @throws ImageException
 *
 */
function imagecolorat(\GdImage $image, int $x, int $y): int
{
    error_clear_last();
    $safeResult = \imagecolorat($image, $x, $y);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param int $color
 * @throws ImageException
 *
 */
function imagecolordeallocate(\GdImage $image, int $color): void
{
    error_clear_last();
    $safeResult = \imagecolordeallocate($image, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image1
 * @param \GdImage $image2
 * @throws ImageException
 *
 */
function imagecolormatch(\GdImage $image1, \GdImage $image2): void
{
    error_clear_last();
    $safeResult = \imagecolormatch($image1, $image2);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $color
 * @param int $red
 * @param int $green
 * @param int $blue
 * @param int $alpha
 * @throws ImageException
 *
 */
function imagecolorset(\GdImage $image, int $color, int $red, int $green, int $blue, int $alpha = 0): void
{
    error_clear_last();
    $safeResult = \imagecolorset($image, $color, $red, $green, $blue, $alpha);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $color
 * @return array{red: int, green: int, blue: int, alpha: int}
 *
 */
function imagecolorsforindex(\GdImage $image, int $color): array
{
    error_clear_last();
    $safeResult = \imagecolorsforindex($image, $color);
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param array $matrix
 * @param float $divisor
 * @param float $offset
 * @throws ImageException
 *
 */
function imageconvolution(\GdImage $image, array $matrix, float $divisor, float $offset): void
{
    error_clear_last();
    $safeResult = \imageconvolution($image, $matrix, $divisor, $offset);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $dst_image
 * @param \GdImage $src_image
 * @param int $dst_x
 * @param int $dst_y
 * @param int $src_x
 * @param int $src_y
 * @param int $src_width
 * @param int $src_height
 * @throws ImageException
 *
 */
function imagecopy(\GdImage $dst_image, \GdImage $src_image, int $dst_x, int $dst_y, int $src_x, int $src_y, int $src_width, int $src_height): void
{
    error_clear_last();
    $safeResult = \imagecopy($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $src_width, $src_height);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $dst_image
 * @param \GdImage $src_image
 * @param int $dst_x
 * @param int $dst_y
 * @param int $src_x
 * @param int $src_y
 * @param int $src_width
 * @param int $src_height
 * @param int $pct
 * @throws ImageException
 *
 */
function imagecopymerge(\GdImage $dst_image, \GdImage $src_image, int $dst_x, int $dst_y, int $src_x, int $src_y, int $src_width, int $src_height, int $pct): void
{
    error_clear_last();
    $safeResult = \imagecopymerge($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $src_width, $src_height, $pct);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $dst_image
 * @param \GdImage $src_image
 * @param int $dst_x
 * @param int $dst_y
 * @param int $src_x
 * @param int $src_y
 * @param int $src_width
 * @param int $src_height
 * @param int $pct
 * @throws ImageException
 *
 */
function imagecopymergegray(\GdImage $dst_image, \GdImage $src_image, int $dst_x, int $dst_y, int $src_x, int $src_y, int $src_width, int $src_height, int $pct): void
{
    error_clear_last();
    $safeResult = \imagecopymergegray($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $src_width, $src_height, $pct);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $dst_image
 * @param \GdImage $src_image
 * @param int $dst_x
 * @param int $dst_y
 * @param int $src_x
 * @param int $src_y
 * @param int $dst_width
 * @param int $dst_height
 * @param int $src_width
 * @param int $src_height
 * @throws ImageException
 *
 */
function imagecopyresampled(\GdImage $dst_image, \GdImage $src_image, int $dst_x, int $dst_y, int $src_x, int $src_y, int $dst_width, int $dst_height, int $src_width, int $src_height): void
{
    error_clear_last();
    $safeResult = \imagecopyresampled($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_width, $dst_height, $src_width, $src_height);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $dst_image
 * @param \GdImage $src_image
 * @param int $dst_x
 * @param int $dst_y
 * @param int $src_x
 * @param int $src_y
 * @param int $dst_width
 * @param int $dst_height
 * @param int $src_width
 * @param int $src_height
 * @throws ImageException
 *
 */
function imagecopyresized(\GdImage $dst_image, \GdImage $src_image, int $dst_x, int $dst_y, int $src_x, int $src_y, int $dst_width, int $dst_height, int $src_width, int $src_height): void
{
    error_clear_last();
    $safeResult = \imagecopyresized($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_width, $dst_height, $src_width, $src_height);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param int $width
 * @param int $height
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreate(int $width, int $height): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreate($width, $height);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromavif(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromavif($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefrombmp(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefrombmp($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromgd(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromgd($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromgd2(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromgd2($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param int $x
 * @param int $y
 * @param int $width
 * @param int $height
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromgd2part(string $filename, int $x, int $y, int $width, int $height): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromgd2part($filename, $x, $y, $width, $height);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromgif(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromgif($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromjpeg(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromjpeg($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefrompng(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefrompng($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromstring(string $data): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromstring($data);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromtga(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromtga($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromwbmp(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromwbmp($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromwebp(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromwebp($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromxbm(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromxbm($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromxpm(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromxpm($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $width
 * @param int $height
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatetruecolor(int $width, int $height): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatetruecolor($width, $height);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param array $rectangle
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecrop(\GdImage $image, array $rectangle): \GdImage
{
    error_clear_last();
    $safeResult = \imagecrop($image, $rectangle);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param int $mode
 * @param float $threshold
 * @param int $color
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecropauto(\GdImage $image, int $mode = IMG_CROP_DEFAULT, float $threshold = 0.5, int $color = -1): \GdImage
{
    error_clear_last();
    $safeResult = \imagecropauto($image, $mode, $threshold, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param int $x1
 * @param int $y1
 * @param int $x2
 * @param int $y2
 * @param int $color
 * @throws ImageException
 *
 */
function imagedashedline(\GdImage $image, int $x1, int $y1, int $x2, int $y2, int $color): void
{
    error_clear_last();
    $safeResult = \imagedashedline($image, $x1, $y1, $x2, $y2, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @throws ImageException
 *
 */
function imagedestroy(\GdImage $image): void
{
    error_clear_last();
    $safeResult = \imagedestroy($image);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $center_x
 * @param int $center_y
 * @param int $width
 * @param int $height
 * @param int $color
 * @throws ImageException
 *
 */
function imageellipse(\GdImage $image, int $center_x, int $center_y, int $width, int $height, int $color): void
{
    error_clear_last();
    $safeResult = \imageellipse($image, $center_x, $center_y, $width, $height, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $x
 * @param int $y
 * @param int $color
 * @throws ImageException
 *
 */
function imagefill(\GdImage $image, int $x, int $y, int $color): void
{
    error_clear_last();
    $safeResult = \imagefill($image, $x, $y, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $center_x
 * @param int $center_y
 * @param int $width
 * @param int $height
 * @param int $start_angle
 * @param int $end_angle
 * @param int $color
 * @param int $style
 * @throws ImageException
 *
 */
function imagefilledarc(\GdImage $image, int $center_x, int $center_y, int $width, int $height, int $start_angle, int $end_angle, int $color, int $style): void
{
    error_clear_last();
    $safeResult = \imagefilledarc($image, $center_x, $center_y, $width, $height, $start_angle, $end_angle, $color, $style);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $center_x
 * @param int $center_y
 * @param int $width
 * @param int $height
 * @param int $color
 * @throws ImageException
 *
 */
function imagefilledellipse(\GdImage $image, int $center_x, int $center_y, int $width, int $height, int $color): void
{
    error_clear_last();
    $safeResult = \imagefilledellipse($image, $center_x, $center_y, $width, $height, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $x1
 * @param int $y1
 * @param int $x2
 * @param int $y2
 * @param int $color
 * @throws ImageException
 *
 */
function imagefilledrectangle(\GdImage $image, int $x1, int $y1, int $x2, int $y2, int $color): void
{
    error_clear_last();
    $safeResult = \imagefilledrectangle($image, $x1, $y1, $x2, $y2, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $x
 * @param int $y
 * @param int $border_color
 * @param int $color
 * @throws ImageException
 *
 */
function imagefilltoborder(\GdImage $image, int $x, int $y, int $border_color, int $color): void
{
    error_clear_last();
    $safeResult = \imagefilltoborder($image, $x, $y, $border_color, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $filter
 * @param int $args
 * @throws ImageException
 *
 */
function imagefilter(\GdImage $image, int $filter, int ...$args): void
{
    error_clear_last();
    if ($args !== []) {
        $safeResult = \imagefilter($image, $filter, ...$args);
    } else {
        $safeResult = \imagefilter($image, $filter);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $mode
 * @throws ImageException
 *
 */
function imageflip(\GdImage $image, int $mode): void
{
    error_clear_last();
    $safeResult = \imageflip($image, $mode);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param float $size
 * @param float $angle
 * @param string $font_filename
 * @param string $string
 * @param array $options
 * @return array
 * @throws ImageException
 *
 */
function imageftbbox(float $size, float $angle, string $font_filename, string $string, array $options = []): array
{
    error_clear_last();
    $safeResult = \imageftbbox($size, $angle, $font_filename, $string, $options);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param float $size
 * @param float $angle
 * @param int $x
 * @param int $y
 * @param int $color
 * @param string $font_filename
 * @param string $text
 * @param array $options
 * @return array
 * @throws ImageException
 *
 */
function imagefttext(\GdImage $image, float $size, float $angle, int $x, int $y, int $color, string $font_filename, string $text, array $options = []): array
{
    error_clear_last();
    $safeResult = \imagefttext($image, $size, $angle, $x, $y, $color, $font_filename, $text, $options);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param float $input_gamma
 * @param float $output_gamma
 * @throws ImageException
 *
 */
function imagegammacorrect(\GdImage $image, float $input_gamma, float $output_gamma): void
{
    error_clear_last();
    $safeResult = \imagegammacorrect($image, $input_gamma, $output_gamma);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param null|resource|string $file
 * @throws ImageException
 *
 */
function imagegd(\GdImage $image, $file = null): void
{
    error_clear_last();
    if ($file !== null) {
        $safeResult = \imagegd($image, $file);
    } else {
        $safeResult = \imagegd($image);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param null|resource|string $file
 * @param int $chunk_size
 * @param int $mode
 * @throws ImageException
 *
 */
function imagegd2(\GdImage $image, $file = null, int $chunk_size = 128, int $mode = IMG_GD2_RAW): void
{
    error_clear_last();
    if ($mode !== IMG_GD2_RAW) {
        $safeResult = \imagegd2($image, $file, $chunk_size, $mode);
    } elseif ($chunk_size !== 128) {
        $safeResult = \imagegd2($image, $file, $chunk_size);
    } elseif ($file !== null) {
        $safeResult = \imagegd2($image, $file);
    } else {
        $safeResult = \imagegd2($image);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param null|resource|string $file
 * @throws ImageException
 *
 */
function imagegif(\GdImage $image, $file = null): void
{
    error_clear_last();
    if ($file !== null) {
        $safeResult = \imagegif($image, $file);
    } else {
        $safeResult = \imagegif($image);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagegrabscreen(): \GdImage
{
    error_clear_last();
    $safeResult = \imagegrabscreen();
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $handle
 * @param bool $client_area
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagegrabwindow(int $handle, bool $client_area = false): \GdImage
{
    error_clear_last();
    $safeResult = \imagegrabwindow($handle, $client_area);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param null|resource|string $file
 * @param int $quality
 * @throws ImageException
 *
 */
function imagejpeg(\GdImage $image, $file = null, int $quality = -1): void
{
    error_clear_last();
    if ($quality !== -1) {
        $safeResult = \imagejpeg($image, $file, $quality);
    } elseif ($file !== null) {
        $safeResult = \imagejpeg($image, $file);
    } else {
        $safeResult = \imagejpeg($image);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $effect
 * @throws ImageException
 *
 */
function imagelayereffect(\GdImage $image, int $effect): void
{
    error_clear_last();
    $safeResult = \imagelayereffect($image, $effect);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $x1
 * @param int $y1
 * @param int $x2
 * @param int $y2
 * @param int $color
 * @throws ImageException
 *
 */
function imageline(\GdImage $image, int $x1, int $y1, int $x2, int $y2, int $color): void
{
    error_clear_last();
    $safeResult = \imageline($image, $x1, $y1, $x2, $y2, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @return int
 * @throws ImageException
 *
 */
function imageloadfont(string $filename): int
{
    error_clear_last();
    $safeResult = \imageloadfont($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param null|resource|string $file
 * @param int $quality
 * @param int $filters
 * @throws ImageException
 *
 */
function imagepng(\GdImage $image, $file = null, int $quality = -1, int $filters = -1): void
{
    error_clear_last();
    if ($filters !== -1) {
        $safeResult = \imagepng($image, $file, $quality, $filters);
    } elseif ($quality !== -1) {
        $safeResult = \imagepng($image, $file, $quality);
    } elseif ($file !== null) {
        $safeResult = \imagepng($image, $file);
    } else {
        $safeResult = \imagepng($image);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $x1
 * @param int $y1
 * @param int $x2
 * @param int $y2
 * @param int $color
 * @throws ImageException
 *
 */
function imagerectangle(\GdImage $image, int $x1, int $y1, int $x2, int $y2, int $color): void
{
    error_clear_last();
    $safeResult = \imagerectangle($image, $x1, $y1, $x2, $y2, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int|null $resolution_x
 * @param int|null $resolution_y
 * @return mixed
 * @throws ImageException
 *
 */
function imageresolution(\GdImage $image, ?int $resolution_x = null, ?int $resolution_y = null)
{
    error_clear_last();
    if ($resolution_y !== null) {
        $safeResult = \imageresolution($image, $resolution_x, $resolution_y);
    } elseif ($resolution_x !== null) {
        $safeResult = \imageresolution($image, $resolution_x);
    } else {
        $safeResult = \imageresolution($image);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param float $angle
 * @param int $background_color
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagerotate(\GdImage $image, float $angle, int $background_color): \GdImage
{
    error_clear_last();
    $safeResult = \imagerotate($image, $angle, $background_color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param bool $enable
 * @throws ImageException
 *
 */
function imagesavealpha(\GdImage $image, bool $enable): void
{
    error_clear_last();
    $safeResult = \imagesavealpha($image, $enable);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $width
 * @param int $height
 * @param int $mode
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagescale(\GdImage $image, int $width, int $height = -1, int $mode = IMG_BILINEAR_FIXED): \GdImage
{
    error_clear_last();
    $safeResult = \imagescale($image, $width, $height, $mode);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param \GdImage $brush
 * @throws ImageException
 *
 */
function imagesetbrush(\GdImage $image, \GdImage $brush): void
{
    error_clear_last();
    $safeResult = \imagesetbrush($image, $brush);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $x1
 * @param int $y1
 * @param int $x2
 * @param int $y2
 * @throws ImageException
 *
 */
function imagesetclip(\GdImage $image, int $x1, int $y1, int $x2, int $y2): void
{
    error_clear_last();
    $safeResult = \imagesetclip($image, $x1, $y1, $x2, $y2);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $method
 * @throws ImageException
 *
 */
function imagesetinterpolation(\GdImage $image, int $method = IMG_BILINEAR_FIXED): void
{
    error_clear_last();
    $safeResult = \imagesetinterpolation($image, $method);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $x
 * @param int $y
 * @param int $color
 * @throws ImageException
 *
 */
function imagesetpixel(\GdImage $image, int $x, int $y, int $color): void
{
    error_clear_last();
    $safeResult = \imagesetpixel($image, $x, $y, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param array $style
 * @throws ImageException
 *
 */
function imagesetstyle(\GdImage $image, array $style): void
{
    error_clear_last();
    $safeResult = \imagesetstyle($image, $style);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $thickness
 * @throws ImageException
 *
 */
function imagesetthickness(\GdImage $image, int $thickness): void
{
    error_clear_last();
    $safeResult = \imagesetthickness($image, $thickness);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param \GdImage $tile
 * @throws ImageException
 *
 */
function imagesettile(\GdImage $image, \GdImage $tile): void
{
    error_clear_last();
    $safeResult = \imagesettile($image, $tile);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $font
 * @param int $x
 * @param int $y
 * @param string $string
 * @param int $color
 * @throws ImageException
 *
 */
function imagestring(\GdImage $image, int $font, int $x, int $y, string $string, int $color): void
{
    error_clear_last();
    $safeResult = \imagestring($image, $font, $x, $y, $string, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $font
 * @param int $x
 * @param int $y
 * @param string $string
 * @param int $color
 * @throws ImageException
 *
 */
function imagestringup(\GdImage $image, int $font, int $x, int $y, string $string, int $color): void
{
    error_clear_last();
    $safeResult = \imagestringup($image, $font, $x, $y, $string, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @return int
 *
 */
function imagesx(\GdImage $image): int
{
    error_clear_last();
    $safeResult = \imagesx($image);
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @return int
 *
 */
function imagesy(\GdImage $image): int
{
    error_clear_last();
    $safeResult = \imagesy($image);
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param bool $dither
 * @param int $num_colors
 * @throws ImageException
 *
 */
function imagetruecolortopalette(\GdImage $image, bool $dither, int $num_colors): void
{
    error_clear_last();
    $safeResult = \imagetruecolortopalette($image, $dither, $num_colors);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param float $size
 * @param float $angle
 * @param string $font_filename
 * @param string $string
 * @param array $options
 * @return array
 * @throws ImageException
 *
 */
function imagettfbbox(float $size, float $angle, string $font_filename, string $string, array $options = []): array
{
    error_clear_last();
    $safeResult = \imagettfbbox($size, $angle, $font_filename, $string, $options);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param float $size
 * @param float $angle
 * @param int $x
 * @param int $y
 * @param int $color
 * @param string $font_filename
 * @param string $text
 * @param array $options
 * @return array
 * @throws ImageException
 *
 */
function imagettftext(\GdImage $image, float $size, float $angle, int $x, int $y, int $color, string $font_filename, string $text, array $options = []): array
{
    error_clear_last();
    $safeResult = \imagettftext($image, $size, $angle, $x, $y, $color, $font_filename, $text, $options);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param null|resource|string $file
 * @param int|null $foreground_color
 * @throws ImageException
 *
 */
function imagewbmp(\GdImage $image, $file = null, ?int $foreground_color = null): void
{
    error_clear_last();
    if ($foreground_color !== null) {
        $safeResult = \imagewbmp($image, $file, $foreground_color);
    } elseif ($file !== null) {
        $safeResult = \imagewbmp($image, $file);
    } else {
        $safeResult = \imagewbmp($image);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param null|resource|string $file
 * @param int $quality
 * @throws ImageException
 *
 */
function imagewebp(\GdImage $image, $file = null, int $quality = -1): void
{
    error_clear_last();
    if ($quality !== -1) {
        $safeResult = \imagewebp($image, $file, $quality);
    } elseif ($file !== null) {
        $safeResult = \imagewebp($image, $file);
    } else {
        $safeResult = \imagewebp($image);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param null|resource|string $filename
 * @param int|null $foreground_color
 * @throws ImageException
 *
 */
function imagexbm(\GdImage $image, $filename, ?int $foreground_color = null): void
{
    error_clear_last();
    if ($foreground_color !== null) {
        $safeResult = \imagexbm($image, $filename, $foreground_color);
    } else {
        $safeResult = \imagexbm($image, $filename);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param string $iptc_data
 * @param string $filename
 * @param int $spool
 * @return bool|string
 * @throws ImageException
 *
 */
function iptcembed(string $iptc_data, string $filename, int $spool = 0)
{
    error_clear_last();
    $safeResult = \iptcembed($iptc_data, $filename, $spool);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $iptc_block
 * @return array
 * @throws ImageException
 *
 */
function iptcparse(string $iptc_block): array
{
    error_clear_last();
    $safeResult = \iptcparse($iptc_block);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\XmlException;

/**
 * @param \XMLParser $parser
 * @throws XmlException
 *
 */
function xml_parser_free(\XMLParser $parser): void
{
    error_clear_last();
    $safeResult = \xml_parser_free($parser);
    if ($safeResult === false) {
        throw XmlException::createFromPhpError();
    }
}


/**
 * @param \XMLParser $parser
 * @param int $option
 * @param mixed $value
 * @throws XmlException
 *
 */
function xml_parser_set_option(\XMLParser $parser, int $option, $value): void
{
    error_clear_last();
    $safeResult = \xml_parser_set_option($parser, $option, $value);
    if ($safeResult === false) {
        throw XmlException::createFromPhpError();
    }
}


/**
 * @param \XMLParser $parser
 * @param callable $handler
 * @return bool
 *
 */
function xml_set_character_data_handler(\XMLParser $parser, callable $handler): bool
{
    error_clear_last();
    $safeResult = \xml_set_character_data_handler($parser, $handler);
    return $safeResult;
}


/**
 * @param \XMLParser $parser
 * @param callable $handler
 * @return bool
 *
 */
function xml_set_default_handler(\XMLParser $parser, callable $handler): bool
{
    error_clear_last();
    $safeResult = \xml_set_default_handler($parser, $handler);
    return $safeResult;
}


/**
 * @param \XMLParser $parser
 * @param callable $start_handler
 * @param callable $end_handler
 * @return bool
 *
 */
function xml_set_element_handler(\XMLParser $parser, callable $start_handler, callable $end_handler): bool
{
    error_clear_last();
    $safeResult = \xml_set_element_handler($parser, $start_handler, $end_handler);
    return $safeResult;
}


/**
 * @param \XMLParser $parser
 * @param callable $handler
 * @return bool
 *
 */
function xml_set_end_namespace_decl_handler(\XMLParser $parser, callable $handler): bool
{
    error_clear_last();
    $safeResult = \xml_set_end_namespace_decl_handler($parser, $handler);
    return $safeResult;
}


/**
 * @param \XMLParser $parser
 * @param callable $handler
 * @return bool
 *
 */
function xml_set_external_entity_ref_handler(\XMLParser $parser, callable $handler): bool
{
    error_clear_last();
    $safeResult = \xml_set_external_entity_ref_handler($parser, $handler);
    return $safeResult;
}


/**
 * @param \XMLParser $parser
 * @param callable $handler
 * @return bool
 *
 */
function xml_set_notation_decl_handler(\XMLParser $parser, callable $handler): bool
{
    error_clear_last();
    $safeResult = \xml_set_notation_decl_handler($parser, $handler);
    return $safeResult;
}


/**
 * @param \XMLParser $parser
 * @param object $object
 * @return bool
 *
 */
function xml_set_object(\XMLParser $parser, object $object): bool
{
    error_clear_last();
    $safeResult = \xml_set_object($parser, $object);
    return $safeResult;
}


/**
 * @param \XMLParser $parser
 * @param callable $handler
 * @return bool
 *
 */
function xml_set_processing_instruction_handler(\XMLParser $parser, callable $handler): bool
{
    error_clear_last();
    $safeResult = \xml_set_processing_instruction_handler($parser, $handler);
    return $safeResult;
}


/**
 * @param \XMLParser $parser
 * @param callable $handler
 * @return bool
 *
 */
function xml_set_start_namespace_decl_handler(\XMLParser $parser, callable $handler): bool
{
    error_clear_last();
    $safeResult = \xml_set_start_namespace_decl_handler($parser, $handler);
    return $safeResult;
}


/**
 * @param \XMLParser $parser
 * @param callable $handler
 * @return bool
 *
 */
function xml_set_unparsed_entity_decl_handler(\XMLParser $parser, callable $handler): bool
{
    error_clear_last();
    $safeResult = \xml_set_unparsed_entity_decl_handler($parser, $handler);
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\MbstringException;

/**
 * @param int $codepoint
 * @param null|string $encoding
 * @return string
 * @throws MbstringException
 *
 */
function mb_chr(int $codepoint, ?string $encoding = null): string
{
    error_clear_last();
    if ($encoding !== null) {
        $safeResult = \mb_chr($codepoint, $encoding);
    } else {
        $safeResult = \mb_chr($codepoint);
    }
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param array|string $string
 * @param string $to_encoding
 * @param mixed $from_encoding
 * @return array|string
 * @throws MbstringException
 *
 */
function mb_convert_encoding($string, string $to_encoding, $from_encoding = null)
{
    error_clear_last();
    if ($from_encoding !== null) {
        $safeResult = \mb_convert_encoding($string, $to_encoding, $from_encoding);
    } else {
        $safeResult = \mb_convert_encoding($string, $to_encoding);
    }
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $to_encoding
 * @param array|string $from_encoding
 * @param array|object|string $var
 * @param array|object|string $vars
 * @return string
 * @throws MbstringException
 *
 */
function mb_convert_variables(string $to_encoding, $from_encoding, &$var, ...$vars): string
{
    error_clear_last();
    $safeResult = \mb_convert_variables($to_encoding, $from_encoding, $var, ...$vars);
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param non-empty-list|non-falsy-string|null $encoding
 * @return bool|list
 * @throws MbstringException
 *
 */
function mb_detect_order($encoding = null)
{
    error_clear_last();
    if ($encoding !== null) {
        $safeResult = \mb_detect_order($encoding);
    } else {
        $safeResult = \mb_detect_order();
    }
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $encoding
 * @return false|list
 *
 */
function mb_encoding_aliases(string $encoding)
{
    error_clear_last();
    $safeResult = \mb_encoding_aliases($encoding);
    return $safeResult;
}


/**
 * @param string $pattern
 * @param callable(array):string $callback
 * @param string $string
 * @param null|string $options
 * @return null|string
 * @throws MbstringException
 *
 */
function mb_ereg_replace_callback(string $pattern, callable $callback, string $string, ?string $options = null): ?string
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \mb_ereg_replace_callback($pattern, $callback, $string, $options);
    } else {
        $safeResult = \mb_ereg_replace_callback($pattern, $callback, $string);
    }
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $pattern
 * @param string $replacement
 * @param string $string
 * @param null|string $options
 * @return null|string
 * @throws MbstringException
 *
 */
function mb_ereg_replace(string $pattern, string $replacement, string $string, ?string $options = null): ?string
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \mb_ereg_replace($pattern, $replacement, $string, $options);
    } else {
        $safeResult = \mb_ereg_replace($pattern, $replacement, $string);
    }
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return array
 * @throws MbstringException
 *
 */
function mb_ereg_search_getregs(): array
{
    error_clear_last();
    $safeResult = \mb_ereg_search_getregs();
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $string
 * @param null|string $pattern
 * @param null|string $options
 * @throws MbstringException
 *
 */
function mb_ereg_search_init(string $string, ?string $pattern = null, ?string $options = null): void
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \mb_ereg_search_init($string, $pattern, $options);
    } elseif ($pattern !== null) {
        $safeResult = \mb_ereg_search_init($string, $pattern);
    } else {
        $safeResult = \mb_ereg_search_init($string);
    }
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
}


/**
 * @param null|string $pattern
 * @param null|string $options
 * @return array
 * @throws MbstringException
 *
 */
function mb_ereg_search_regs(?string $pattern = null, ?string $options = null): array
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \mb_ereg_search_regs($pattern, $options);
    } elseif ($pattern !== null) {
        $safeResult = \mb_ereg_search_regs($pattern);
    } else {
        $safeResult = \mb_ereg_search_regs();
    }
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $offset
 * @throws MbstringException
 *
 */
function mb_ereg_search_setpos(int $offset): void
{
    error_clear_last();
    $safeResult = \mb_ereg_search_setpos($offset);
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
}


/**
 * @param string $pattern
 * @param string $replacement
 * @param string $string
 * @param null|string $options
 * @return string
 * @throws MbstringException
 *
 */
function mb_eregi_replace(string $pattern, string $replacement, string $string, ?string $options = null): string
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \mb_eregi_replace($pattern, $replacement, $string, $options);
    } else {
        $safeResult = \mb_eregi_replace($pattern, $replacement, $string);
    }
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $type
 * @return mixed
 * @throws MbstringException
 *
 */
function mb_get_info(string $type = "all")
{
    error_clear_last();
    $safeResult = \mb_get_info($type);
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param null|string $encoding
 * @return bool|string
 * @throws MbstringException
 *
 */
function mb_http_output(?string $encoding = null)
{
    error_clear_last();
    if ($encoding !== null) {
        $safeResult = \mb_http_output($encoding);
    } else {
        $safeResult = \mb_http_output();
    }
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param null|string $encoding
 * @return bool|string
 * @throws MbstringException
 *
 */
function mb_internal_encoding(?string $encoding = null)
{
    error_clear_last();
    if ($encoding !== null) {
        $safeResult = \mb_internal_encoding($encoding);
    } else {
        $safeResult = \mb_internal_encoding();
    }
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $string
 * @param null|string $encoding
 * @return int
 * @throws MbstringException
 *
 */
function mb_ord(string $string, ?string $encoding = null): int
{
    error_clear_last();
    if ($encoding !== null) {
        $safeResult = \mb_ord($string, $encoding);
    } else {
        $safeResult = \mb_ord($string);
    }
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $string
 * @param array|null $result
 * @throws MbstringException
 *
 */
function mb_parse_str(string $string, ?array &$result): void
{
    error_clear_last();
    $safeResult = \mb_parse_str($string, $result);
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
}


/**
 * @param null|string $encoding
 * @return bool|string
 * @throws MbstringException
 *
 */
function mb_regex_encoding(?string $encoding = null)
{
    error_clear_last();
    if ($encoding !== null) {
        $safeResult = \mb_regex_encoding($encoding);
    } else {
        $safeResult = \mb_regex_encoding();
    }
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $to
 * @param string $subject
 * @param string $message
 * @param array|null|string $additional_headers
 * @param null|string $additional_params
 * @throws MbstringException
 *
 */
function mb_send_mail(string $to, string $subject, string $message, $additional_headers = [], ?string $additional_params = null): void
{
    error_clear_last();
    if ($additional_params !== null) {
        $safeResult = \mb_send_mail($to, $subject, $message, $additional_headers, $additional_params);
    } else {
        $safeResult = \mb_send_mail($to, $subject, $message, $additional_headers);
    }
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
}


/**
 * @param string $pattern
 * @param string $string
 * @param int $limit
 * @return list
 * @throws MbstringException
 *
 */
function mb_split(string $pattern, string $string, int $limit = -1): array
{
    error_clear_last();
    $safeResult = \mb_split($pattern, $string, $limit);
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\PosixException;

/**
 * @param string $filename
 * @param int $flags
 * @throws PosixException
 *
 */
function posix_access(string $filename, int $flags = 0): void
{
    error_clear_last();
    $safeResult = \posix_access($filename, $flags);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @param int $flags
 * @throws PosixException
 *
 */
function posix_eaccess(string $filename, int $flags = 0): void
{
    error_clear_last();
    $safeResult = \posix_eaccess($filename, $flags);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
}


/**
 * @param int $group_id
 * @return array{name: string, passwd: string, gid: int, members: list}
 * @throws PosixException
 *
 */
function posix_getgrgid(int $group_id): array
{
    error_clear_last();
    $safeResult = \posix_getgrgid($group_id);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $name
 * @return array{name: string, passwd: string, gid: int, members: list}
 * @throws PosixException
 *
 */
function posix_getgrnam(string $name): array
{
    error_clear_last();
    $safeResult = \posix_getgrnam($name);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return list
 * @throws PosixException
 *
 */
function posix_getgroups(): array
{
    error_clear_last();
    $safeResult = \posix_getgroups();
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return string
 * @throws PosixException
 *
 */
function posix_getlogin(): string
{
    error_clear_last();
    $safeResult = \posix_getlogin();
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $user_id
 * @return array{name: string, passwd: string, uid: int, gid: int, gecos: string, dir: string, shell: string}
 * @throws PosixException
 *
 */
function posix_getpwuid(int $user_id): array
{
    error_clear_last();
    $safeResult = \posix_getpwuid($user_id);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int|null $resource
 * @return array
 * @throws PosixException
 *
 */
function posix_getrlimit(?int $resource = null): array
{
    error_clear_last();
    if ($resource !== null) {
        $safeResult = \posix_getrlimit($resource);
    } else {
        $safeResult = \posix_getrlimit();
    }
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $process_id
 * @return int
 * @throws PosixException
 *
 */
function posix_getsid(int $process_id): int
{
    error_clear_last();
    $safeResult = \posix_getsid($process_id);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $username
 * @param int $group_id
 * @throws PosixException
 *
 */
function posix_initgroups(string $username, int $group_id): void
{
    error_clear_last();
    $safeResult = \posix_initgroups($username, $group_id);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
}


/**
 * @param int $process_id
 * @param int $signal
 * @throws PosixException
 *
 */
function posix_kill(int $process_id, int $signal): void
{
    error_clear_last();
    $safeResult = \posix_kill($process_id, $signal);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @param int $permissions
 * @throws PosixException
 *
 */
function posix_mkfifo(string $filename, int $permissions): void
{
    error_clear_last();
    $safeResult = \posix_mkfifo($filename, $permissions);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @param int $flags
 * @param int $major
 * @param int $minor
 * @throws PosixException
 *
 */
function posix_mknod(string $filename, int $flags, int $major = 0, int $minor = 0): void
{
    error_clear_last();
    $safeResult = \posix_mknod($filename, $flags, $major, $minor);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
}


/**
 * @param int $group_id
 * @throws PosixException
 *
 */
function posix_setegid(int $group_id): void
{
    error_clear_last();
    $safeResult = \posix_setegid($group_id);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
}


/**
 * @param int $user_id
 * @throws PosixException
 *
 */
function posix_seteuid(int $user_id): void
{
    error_clear_last();
    $safeResult = \posix_seteuid($user_id);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
}


/**
 * @param int $group_id
 * @throws PosixException
 *
 */
function posix_setgid(int $group_id): void
{
    error_clear_last();
    $safeResult = \posix_setgid($group_id);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
}


/**
 * @param int $process_id
 * @param int $process_group_id
 * @throws PosixException
 *
 */
function posix_setpgid(int $process_id, int $process_group_id): void
{
    error_clear_last();
    $safeResult = \posix_setpgid($process_id, $process_group_id);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
}


/**
 * @param int $resource
 * @param int $soft_limit
 * @param int $hard_limit
 * @throws PosixException
 *
 */
function posix_setrlimit(int $resource, int $soft_limit, int $hard_limit): void
{
    error_clear_last();
    $safeResult = \posix_setrlimit($resource, $soft_limit, $hard_limit);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
}


/**
 * @return int
 * @throws PosixException
 *
 */
function posix_setsid(): int
{
    error_clear_last();
    $safeResult = \posix_setsid();
    if ($safeResult === -1) {
        throw PosixException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $user_id
 * @throws PosixException
 *
 */
function posix_setuid(int $user_id): void
{
    error_clear_last();
    $safeResult = \posix_setuid($user_id);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
}


/**
 * @return array
 * @throws PosixException
 *
 */
function posix_times(): array
{
    error_clear_last();
    $safeResult = \posix_times();
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return array
 * @throws PosixException
 *
 */
function posix_uname(): array
{
    error_clear_last();
    $safeResult = \posix_uname();
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\ReadlineException;

/**
 * @param string $prompt
 * @return bool
 *
 */
function readline_add_history(string $prompt): bool
{
    error_clear_last();
    $safeResult = \readline_add_history($prompt);
    return $safeResult;
}


/**
 * @param string $prompt
 * @param callable $callback
 * @return bool
 *
 */
function readline_callback_handler_install(string $prompt, callable $callback): bool
{
    error_clear_last();
    $safeResult = \readline_callback_handler_install($prompt, $callback);
    return $safeResult;
}


/**
 * @return bool
 *
 */
function readline_clear_history(): bool
{
    error_clear_last();
    $safeResult = \readline_clear_history();
    return $safeResult;
}


/**
 * @param callable $callback
 * @throws ReadlineException
 *
 */
function readline_completion_function(callable $callback): void
{
    error_clear_last();
    $safeResult = \readline_completion_function($callback);
    if ($safeResult === false) {
        throw ReadlineException::createFromPhpError();
    }
}


/**
 * @param null|string $filename
 * @throws ReadlineException
 *
 */
function readline_read_history(?string $filename = null): void
{
    error_clear_last();
    if ($filename !== null) {
        $safeResult = \readline_read_history($filename);
    } else {
        $safeResult = \readline_read_history();
    }
    if ($safeResult === false) {
        throw ReadlineException::createFromPhpError();
    }
}


/**
 * @param null|string $filename
 * @throws ReadlineException
 *
 */
function readline_write_history(?string $filename = null): void
{
    error_clear_last();
    if ($filename !== null) {
        $safeResult = \readline_write_history($filename);
    } else {
        $safeResult = \readline_write_history();
    }
    if ($safeResult === false) {
        throw ReadlineException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\IbmDb2Exception;

/**
 * @param resource $connection
 * @param \DB2_AUTOCOMMIT_OFF|\DB2_AUTOCOMMIT_ON $value
 * @return \DB2_AUTOCOMMIT_OFF|\DB2_AUTOCOMMIT_ON|bool
 * @throws IbmDb2Exception
 *
 */
function db2_autocommit($connection, $value = null)
{
    error_clear_last();
    if ($value !== null) {
        $safeResult = \db2_autocommit($connection, $value);
    } else {
        $safeResult = \db2_autocommit($connection);
    }
    if ($safeResult === false) {
        throw IbmDb2Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stmt
 * @param int $parameter_number
 * @param string $variable_name
 * @param int $parameter_type
 * @param int $data_type
 * @param int $precision
 * @param int $scale
 * @throws IbmDb2Exception
 *
 */
function db2_bind_param($stmt, int $parameter_number, string $variable_name, int $parameter_type = DB2_PARAM_IN, int $data_type = 0, int $precision = -1, int $scale = 0): void
{
    error_clear_last();
    $safeResult = \db2_bind_param($stmt, $parameter_number, $variable_name, $parameter_type, $data_type, $precision, $scale);
    if ($safeResult === false) {
        throw IbmDb2Exception::createFromPhpError();
    }
}


/**
 * @param resource $connection
 * @return \stdClass
 * @throws IbmDb2Exception
 *
 */
function db2_client_info($connection): \stdClass
{
    error_clear_last();
    $safeResult = \db2_client_info($connection);
    if ($safeResult === false) {
        throw IbmDb2Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $connection
 * @throws IbmDb2Exception
 *
 */
function db2_close($connection): void
{
    error_clear_last();
    $safeResult = \db2_close($connection);
    if ($safeResult === false) {
        throw IbmDb2Exception::createFromPhpError();
    }
}


/**
 * @param resource $connection
 * @throws IbmDb2Exception
 *
 */
function db2_commit($connection): void
{
    error_clear_last();
    $safeResult = \db2_commit($connection);
    if ($safeResult === false) {
        throw IbmDb2Exception::createFromPhpError();
    }
}


/**
 * @param resource $stmt
 * @param array $parameters
 * @throws IbmDb2Exception
 *
 */
function db2_execute($stmt, array $parameters = []): void
{
    error_clear_last();
    $safeResult = \db2_execute($stmt, $parameters);
    if ($safeResult === false) {
        throw IbmDb2Exception::createFromPhpError();
    }
}


/**
 * @param resource $stmt
 * @throws IbmDb2Exception
 *
 */
function db2_free_result($stmt): void
{
    error_clear_last();
    $safeResult = \db2_free_result($stmt);
    if ($safeResult === false) {
        throw IbmDb2Exception::createFromPhpError();
    }
}


/**
 * @param resource $stmt
 * @throws IbmDb2Exception
 *
 */
function db2_free_stmt($stmt): void
{
    error_clear_last();
    $safeResult = \db2_free_stmt($stmt);
    if ($safeResult === false) {
        throw IbmDb2Exception::createFromPhpError();
    }
}


/**
 * @param resource $resource
 * @param string $option
 * @return string
 * @throws IbmDb2Exception
 *
 */
function db2_get_option($resource, string $option): string
{
    error_clear_last();
    $safeResult = \db2_get_option($resource, $option);
    if ($safeResult === false) {
        throw IbmDb2Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stmt
 * @return 0|positive-int
 * @throws IbmDb2Exception
 *
 */
function db2_num_rows($stmt): int
{
    error_clear_last();
    $safeResult = \db2_num_rows($stmt);
    if ($safeResult === false) {
        throw IbmDb2Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $connection
 * @throws IbmDb2Exception
 *
 */
function db2_pclose($connection): void
{
    error_clear_last();
    $safeResult = \db2_pclose($connection);
    if ($safeResult === false) {
        throw IbmDb2Exception::createFromPhpError();
    }
}


/**
 * @param resource $connection
 * @throws IbmDb2Exception
 *
 */
function db2_rollback($connection): void
{
    error_clear_last();
    $safeResult = \db2_rollback($connection);
    if ($safeResult === false) {
        throw IbmDb2Exception::createFromPhpError();
    }
}


/**
 * @param resource $connection
 * @return \stdClass
 * @throws IbmDb2Exception
 *
 */
function db2_server_info($connection): \stdClass
{
    error_clear_last();
    $safeResult = \db2_server_info($connection);
    if ($safeResult === false) {
        throw IbmDb2Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $resource
 * @param array $options
 * @param int $type
 * @throws IbmDb2Exception
 *
 */
function db2_set_option($resource, array $options, int $type): void
{
    error_clear_last();
    $safeResult = \db2_set_option($resource, $options, $type);
    if ($safeResult === false) {
        throw IbmDb2Exception::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\LdapException;

/**
 * @param string $value
 * @return string
 * @throws LdapException
 *
 */
function ldap_8859_to_t61(string $value): string
{
    error_clear_last();
    $safeResult = \ldap_8859_to_t61($value);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param array $entry
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_add(\LDAP\Connection $ldap, string $dn, array $entry, ?array $controls = null): void
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_add($ldap, $dn, $entry, $controls);
    } else {
        $safeResult = \ldap_add($ldap, $dn, $entry);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param null|string $dn
 * @param null|string $password
 * @throws LdapException
 *
 */
function ldap_bind(\LDAP\Connection $ldap, ?string $dn = null, ?string $password = null): void
{
    error_clear_last();
    if ($password !== null) {
        $safeResult = \ldap_bind($ldap, $dn, $password);
    } elseif ($dn !== null) {
        $safeResult = \ldap_bind($ldap, $dn);
    } else {
        $safeResult = \ldap_bind($ldap);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param string $attribute
 * @param string $value
 * @param array|null $controls
 * @return bool
 * @throws LdapException
 *
 */
function ldap_compare(\LDAP\Connection $ldap, string $dn, string $attribute, string $value, ?array $controls = null): bool
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_compare($ldap, $dn, $attribute, $value, $controls);
    } else {
        $safeResult = \ldap_compare($ldap, $dn, $attribute, $value);
    }
    if ($safeResult === -1) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $link
 * @param resource $result
 * @param null|string $cookie
 * @param int|null $estimated
 * @throws LdapException
 *
 */
function ldap_control_paged_result_response($link, $result, ?string &$cookie = null, ?int &$estimated = null): void
{
    error_clear_last();
    $safeResult = \ldap_control_paged_result_response($link, $result, $cookie, $estimated);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param resource $link
 * @param int $pagesize
 * @param bool $iscritical
 * @param string $cookie
 * @throws LdapException
 *
 */
function ldap_control_paged_result($link, int $pagesize, bool $iscritical = false, string $cookie = ""): void
{
    error_clear_last();
    $safeResult = \ldap_control_paged_result($link, $pagesize, $iscritical, $cookie);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\Result $result
 * @return int
 * @throws LdapException
 *
 */
function ldap_count_entries(\LDAP\Connection $ldap, \LDAP\Result $result): int
{
    error_clear_last();
    $safeResult = \ldap_count_entries($ldap, $result);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_delete(\LDAP\Connection $ldap, string $dn, ?array $controls = null): void
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_delete($ldap, $dn, $controls);
    } else {
        $safeResult = \ldap_delete($ldap, $dn);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param string $dn
 * @return string
 * @throws LdapException
 *
 */
function ldap_dn2ufn(string $dn): string
{
    error_clear_last();
    $safeResult = \ldap_dn2ufn($dn);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $user
 * @param string $old_password
 * @param string $new_password
 * @param array|null $controls
 * @return bool|string
 * @throws LdapException
 *
 */
function ldap_exop_passwd(\LDAP\Connection $ldap, string $user = "", string $old_password = "", string $new_password = "", ?array &$controls = null)
{
    error_clear_last();
    $safeResult = \ldap_exop_passwd($ldap, $user, $old_password, $new_password, $controls);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @return bool|string
 * @throws LdapException
 *
 */
function ldap_exop_whoami(\LDAP\Connection $ldap)
{
    error_clear_last();
    $safeResult = \ldap_exop_whoami($ldap);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $request_oid
 * @param null|string $request_data
 * @param array|null $controls
 * @param null|string $response_data
 * @param null|string $response_oid
 * @return bool|resource
 * @throws LdapException
 *
 */
function ldap_exop(\LDAP\Connection $ldap, string $request_oid, ?string $request_data = null, ?array $controls = null, ?string &$response_data = null, ?string &$response_oid = null)
{
    error_clear_last();
    if ($response_oid !== null) {
        $safeResult = \ldap_exop($ldap, $request_oid, $request_data, $controls, $response_data, $response_oid);
    } elseif ($response_data !== null) {
        $safeResult = \ldap_exop($ldap, $request_oid, $request_data, $controls, $response_data);
    } elseif ($controls !== null) {
        $safeResult = \ldap_exop($ldap, $request_oid, $request_data, $controls);
    } elseif ($request_data !== null) {
        $safeResult = \ldap_exop($ldap, $request_oid, $request_data);
    } else {
        $safeResult = \ldap_exop($ldap, $request_oid);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $dn
 * @param int $with_attrib
 * @return array
 * @throws LdapException
 *
 */
function ldap_explode_dn(string $dn, int $with_attrib): array
{
    error_clear_last();
    $safeResult = \ldap_explode_dn($dn, $with_attrib);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\ResultEntry $entry
 * @return string
 * @throws LdapException
 *
 */
function ldap_first_attribute(\LDAP\Connection $ldap, \LDAP\ResultEntry $entry): string
{
    error_clear_last();
    $safeResult = \ldap_first_attribute($ldap, $entry);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\Result $result
 * @return \LDAP\ResultEntry
 * @throws LdapException
 *
 */
function ldap_first_entry(\LDAP\Connection $ldap, \LDAP\Result $result): \LDAP\ResultEntry
{
    error_clear_last();
    $safeResult = \ldap_first_entry($ldap, $result);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Result $result
 * @throws LdapException
 *
 */
function ldap_free_result(\LDAP\Result $result): void
{
    error_clear_last();
    $safeResult = \ldap_free_result($result);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\ResultEntry $entry
 * @return array
 *
 */
function ldap_get_attributes(\LDAP\Connection $ldap, \LDAP\ResultEntry $entry): array
{
    error_clear_last();
    $safeResult = \ldap_get_attributes($ldap, $entry);
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\ResultEntry $entry
 * @return string
 * @throws LdapException
 *
 */
function ldap_get_dn(\LDAP\Connection $ldap, \LDAP\ResultEntry $entry): string
{
    error_clear_last();
    $safeResult = \ldap_get_dn($ldap, $entry);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\Result $result
 * @return array
 * @throws LdapException
 *
 */
function ldap_get_entries(\LDAP\Connection $ldap, \LDAP\Result $result): array
{
    error_clear_last();
    $safeResult = \ldap_get_entries($ldap, $result);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param int $option
 * @param mixed $value
 * @throws LdapException
 *
 */
function ldap_get_option(\LDAP\Connection $ldap, int $option, &$value = null): void
{
    error_clear_last();
    $safeResult = \ldap_get_option($ldap, $option, $value);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\ResultEntry $entry
 * @param string $attribute
 * @return array
 * @throws LdapException
 *
 */
function ldap_get_values_len(\LDAP\Connection $ldap, \LDAP\ResultEntry $entry, string $attribute): array
{
    error_clear_last();
    $safeResult = \ldap_get_values_len($ldap, $entry, $attribute);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\ResultEntry $entry
 * @param string $attribute
 * @return array
 * @throws LdapException
 *
 */
function ldap_get_values(\LDAP\Connection $ldap, \LDAP\ResultEntry $entry, string $attribute): array
{
    error_clear_last();
    $safeResult = \ldap_get_values($ldap, $entry, $attribute);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param array $entry
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_mod_add(\LDAP\Connection $ldap, string $dn, array $entry, ?array $controls = null): void
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_mod_add($ldap, $dn, $entry, $controls);
    } else {
        $safeResult = \ldap_mod_add($ldap, $dn, $entry);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param array $entry
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_mod_del(\LDAP\Connection $ldap, string $dn, array $entry, ?array $controls = null): void
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_mod_del($ldap, $dn, $entry, $controls);
    } else {
        $safeResult = \ldap_mod_del($ldap, $dn, $entry);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param array $entry
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_mod_replace(\LDAP\Connection $ldap, string $dn, array $entry, ?array $controls = null): void
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_mod_replace($ldap, $dn, $entry, $controls);
    } else {
        $safeResult = \ldap_mod_replace($ldap, $dn, $entry);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param array $modifications_info
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_modify_batch(\LDAP\Connection $ldap, string $dn, array $modifications_info, ?array $controls = null): void
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_modify_batch($ldap, $dn, $modifications_info, $controls);
    } else {
        $safeResult = \ldap_modify_batch($ldap, $dn, $modifications_info);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\ResultEntry $entry
 * @return string
 * @throws LdapException
 *
 */
function ldap_next_attribute(\LDAP\Connection $ldap, \LDAP\ResultEntry $entry): string
{
    error_clear_last();
    $safeResult = \ldap_next_attribute($ldap, $entry);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\Result $result
 * @param null|string $response_data
 * @param null|string $response_oid
 * @throws LdapException
 *
 */
function ldap_parse_exop(\LDAP\Connection $ldap, \LDAP\Result $result, ?string &$response_data = null, ?string &$response_oid = null): void
{
    error_clear_last();
    $safeResult = \ldap_parse_exop($ldap, $result, $response_data, $response_oid);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\Result $result
 * @param int|null $error_code
 * @param null|string $matched_dn
 * @param null|string $error_message
 * @param array|null $referrals
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_parse_result(\LDAP\Connection $ldap, \LDAP\Result $result, ?int &$error_code, ?string &$matched_dn = null, ?string &$error_message = null, ?array &$referrals = null, ?array &$controls = null): void
{
    error_clear_last();
    $safeResult = \ldap_parse_result($ldap, $result, $error_code, $matched_dn, $error_message, $referrals, $controls);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param string $new_rdn
 * @param string $new_parent
 * @param bool $delete_old_rdn
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_rename(\LDAP\Connection $ldap, string $dn, string $new_rdn, string $new_parent, bool $delete_old_rdn, ?array $controls = null): void
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_rename($ldap, $dn, $new_rdn, $new_parent, $delete_old_rdn, $controls);
    } else {
        $safeResult = \ldap_rename($ldap, $dn, $new_rdn, $new_parent, $delete_old_rdn);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param null|string $dn
 * @param null|string $password
 * @param null|string $mech
 * @param null|string $realm
 * @param null|string $authc_id
 * @param null|string $authz_id
 * @param null|string $props
 * @throws LdapException
 *
 */
function ldap_sasl_bind(\LDAP\Connection $ldap, ?string $dn = null, ?string $password = null, ?string $mech = null, ?string $realm = null, ?string $authc_id = null, ?string $authz_id = null, ?string $props = null): void
{
    error_clear_last();
    if ($props !== null) {
        $safeResult = \ldap_sasl_bind($ldap, $dn, $password, $mech, $realm, $authc_id, $authz_id, $props);
    } elseif ($authz_id !== null) {
        $safeResult = \ldap_sasl_bind($ldap, $dn, $password, $mech, $realm, $authc_id, $authz_id);
    } elseif ($authc_id !== null) {
        $safeResult = \ldap_sasl_bind($ldap, $dn, $password, $mech, $realm, $authc_id);
    } elseif ($realm !== null) {
        $safeResult = \ldap_sasl_bind($ldap, $dn, $password, $mech, $realm);
    } elseif ($mech !== null) {
        $safeResult = \ldap_sasl_bind($ldap, $dn, $password, $mech);
    } elseif ($password !== null) {
        $safeResult = \ldap_sasl_bind($ldap, $dn, $password);
    } elseif ($dn !== null) {
        $safeResult = \ldap_sasl_bind($ldap, $dn);
    } else {
        $safeResult = \ldap_sasl_bind($ldap);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param null|resource $ldap
 * @param int $option
 * @param mixed $value
 * @throws LdapException
 *
 */
function ldap_set_option($ldap, int $option, $value): void
{
    error_clear_last();
    $safeResult = \ldap_set_option($ldap, $option, $value);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @throws LdapException
 *
 */
function ldap_unbind(\LDAP\Connection $ldap): void
{
    error_clear_last();
    $safeResult = \ldap_unbind($ldap);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\OutcontrolException;

/**
 * @throws OutcontrolException
 *
 */
function ob_clean(): void
{
    error_clear_last();
    $safeResult = \ob_clean();
    if ($safeResult === false) {
        throw OutcontrolException::createFromPhpError();
    }
}


/**
 * @throws OutcontrolException
 *
 */
function ob_end_clean(): void
{
    error_clear_last();
    $safeResult = \ob_end_clean();
    if ($safeResult === false) {
        throw OutcontrolException::createFromPhpError();
    }
}


/**
 * @throws OutcontrolException
 *
 */
function ob_end_flush(): void
{
    error_clear_last();
    $safeResult = \ob_end_flush();
    if ($safeResult === false) {
        throw OutcontrolException::createFromPhpError();
    }
}


/**
 * @throws OutcontrolException
 *
 */
function ob_flush(): void
{
    error_clear_last();
    $safeResult = \ob_flush();
    if ($safeResult === false) {
        throw OutcontrolException::createFromPhpError();
    }
}


/**
 * @return string
 * @throws OutcontrolException
 *
 */
function ob_get_clean(): string
{
    error_clear_last();
    $safeResult = \ob_get_clean();
    if ($safeResult === false) {
        throw OutcontrolException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return string
 * @throws OutcontrolException
 *
 */
function ob_get_flush(): string
{
    error_clear_last();
    $safeResult = \ob_get_flush();
    if ($safeResult === false) {
        throw OutcontrolException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param array|callable|null|string $callback
 * @param int $chunk_size
 * @param int $flags
 * @throws OutcontrolException
 *
 */
function ob_start($callback = null, int $chunk_size = 0, int $flags = PHP_OUTPUT_HANDLER_STDFLAGS): void
{
    error_clear_last();
    if ($flags !== PHP_OUTPUT_HANDLER_STDFLAGS) {
        $safeResult = \ob_start($callback, $chunk_size, $flags);
    } elseif ($chunk_size !== 0) {
        $safeResult = \ob_start($callback, $chunk_size);
    } elseif ($callback !== null) {
        $safeResult = \ob_start($callback);
    } else {
        $safeResult = \ob_start();
    }
    if ($safeResult === false) {
        throw OutcontrolException::createFromPhpError();
    }
}


/**
 * @param string $name
 * @param string $value
 * @throws OutcontrolException
 *
 */
function output_add_rewrite_var(string $name, string $value): void
{
    error_clear_last();
    $safeResult = \output_add_rewrite_var($name, $value);
    if ($safeResult === false) {
        throw OutcontrolException::createFromPhpError();
    }
}


/**
 * @throws OutcontrolException
 *
 */
function output_reset_rewrite_vars(): void
{
    error_clear_last();
    $safeResult = \output_reset_rewrite_vars();
    if ($safeResult === false) {
        throw OutcontrolException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\ApacheException;

/**
 * @return string
 * @throws ApacheException
 *
 */
function apache_get_version(): string
{
    error_clear_last();
    $safeResult = \apache_get_version();
    if ($safeResult === false) {
        throw ApacheException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $variable
 * @param bool $walk_to_top
 * @return string
 * @throws ApacheException
 *
 */
function apache_getenv(string $variable, bool $walk_to_top = false): string
{
    error_clear_last();
    $safeResult = \apache_getenv($variable, $walk_to_top);
    if ($safeResult === false) {
        throw ApacheException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return object
 * @throws ApacheException
 *
 */
function apache_lookup_uri(string $filename): object
{
    error_clear_last();
    $safeResult = \apache_lookup_uri($filename);
    if ($safeResult === false) {
        throw ApacheException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return array|false
 *
 */
function apache_request_headers()
{
    error_clear_last();
    $safeResult = \apache_request_headers();
    return $safeResult;
}


/**
 * @return array|false
 *
 */
function apache_response_headers()
{
    error_clear_last();
    $safeResult = \apache_response_headers();
    return $safeResult;
}


/**
 * @param string $variable
 * @param string $value
 * @param bool $walk_to_top
 * @throws ApacheException
 *
 */
function apache_setenv(string $variable, string $value, bool $walk_to_top = false): void
{
    error_clear_last();
    $safeResult = \apache_setenv($variable, $value, $walk_to_top);
    if ($safeResult === false) {
        throw ApacheException::createFromPhpError();
    }
}


/**
 * @return array
 *
 */
function getallheaders(): array
{
    error_clear_last();
    $safeResult = \getallheaders();
    return $safeResult;
}


/**
 * @param string $uri
 * @throws ApacheException
 *
 */
function virtual(string $uri): void
{
    error_clear_last();
    $safeResult = \virtual($uri);
    if ($safeResult === false) {
        throw ApacheException::createFromPhpError();
    }
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/yaml.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/yaml.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/yaml.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/yaml.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/yaml.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.6/yaml.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/mysql.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/mysql.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/mysql.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/mysql.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/mysql.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/mysql.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/funchand.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/funchand.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/funchand.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/funchand.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/funchand.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/funchand.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/hash.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.2/hash.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.2/hash.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.2/hash.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.2/hash.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.2/hash.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/misc.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.2/misc.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.2/misc.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.4/misc.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.4/misc.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.4/misc.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/curl.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.2/curl.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.2/curl.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.4/curl.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.4/curl.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.4/curl.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/calendar.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/calendar.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/calendar.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/calendar.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/calendar.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/calendar.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/sodium.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.2/sodium.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.2/sodium.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.4/sodium.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.4/sodium.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.4/sodium.php';
}
<?php

namespace Safe;

use Safe\Exceptions\UodbcException;

/**
 * @param \Odbc\Connection $odbc
 * @param bool|null $enable
 * @return mixed
 * @throws UodbcException
 *
 */
function odbc_autocommit(\Odbc\Connection $odbc, ?bool $enable = null)
{
    error_clear_last();
    if ($enable !== null) {
        $safeResult = \odbc_autocommit($odbc, $enable);
    } else {
        $safeResult = \odbc_autocommit($odbc);
    }
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $statement
 * @param int $mode
 * @return bool
 *
 */
function odbc_binmode(int $statement, int $mode): bool
{
    error_clear_last();
    $safeResult = \odbc_binmode($statement, $mode);
    return $safeResult;
}


/**
 * @param \Odbc\Connection $odbc
 * @param string $catalog
 * @param string $schema
 * @param string $table
 * @param string $column
 * @return \Odbc\Result|false
 *
 */
function odbc_columnprivileges(\Odbc\Connection $odbc, string $catalog, string $schema, string $table, string $column)
{
    error_clear_last();
    $safeResult = \odbc_columnprivileges($odbc, $catalog, $schema, $table, $column);
    return $safeResult;
}


/**
 * @param \Odbc\Connection $odbc
 * @param null|string $catalog
 * @param null|string $schema
 * @param null|string $table
 * @param null|string $column
 * @return \Odbc\Result|false
 *
 */
function odbc_columns(\Odbc\Connection $odbc, ?string $catalog = null, ?string $schema = null, ?string $table = null, ?string $column = null)
{
    error_clear_last();
    if ($column !== null) {
        $safeResult = \odbc_columns($odbc, $catalog, $schema, $table, $column);
    } elseif ($table !== null) {
        $safeResult = \odbc_columns($odbc, $catalog, $schema, $table);
    } elseif ($schema !== null) {
        $safeResult = \odbc_columns($odbc, $catalog, $schema);
    } elseif ($catalog !== null) {
        $safeResult = \odbc_columns($odbc, $catalog);
    } else {
        $safeResult = \odbc_columns($odbc);
    }
    return $safeResult;
}


/**
 * @param \Odbc\Connection $odbc
 * @throws UodbcException
 *
 */
function odbc_commit(\Odbc\Connection $odbc): void
{
    error_clear_last();
    $safeResult = \odbc_commit($odbc);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
}


/**
 * @param string $dsn
 * @param null|string $user
 * @param null|string $password
 * @param int $cursor_option
 * @return \Odbc\Connection
 * @throws UodbcException
 *
 */
function odbc_connect(string $dsn, ?string $user = null, ?string $password = null, int $cursor_option = SQL_CUR_USE_DRIVER): \Odbc\Connection
{
    error_clear_last();
    if ($cursor_option !== SQL_CUR_USE_DRIVER) {
        $safeResult = \odbc_connect($dsn, $user, $password, $cursor_option);
    } elseif ($password !== null) {
        $safeResult = \odbc_connect($dsn, $user, $password);
    } elseif ($user !== null) {
        $safeResult = \odbc_connect($dsn, $user);
    } else {
        $safeResult = \odbc_connect($dsn);
    }
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Odbc\Result $statement
 * @return string
 * @throws UodbcException
 *
 */
function odbc_cursor(\Odbc\Result $statement): string
{
    error_clear_last();
    $safeResult = \odbc_cursor($statement);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Odbc\Connection $odbc
 * @param int $fetch_type
 * @return array
 * @throws UodbcException
 *
 */
function odbc_data_source(\Odbc\Connection $odbc, int $fetch_type): array
{
    error_clear_last();
    $safeResult = \odbc_data_source($odbc, $fetch_type);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Odbc\Connection $odbc
 * @param string $query
 * @return \Odbc\Result
 * @throws UodbcException
 *
 */
function odbc_exec(\Odbc\Connection $odbc, string $query): \Odbc\Result
{
    error_clear_last();
    $safeResult = \odbc_exec($odbc, $query);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Odbc\Result $statement
 * @param array $params
 * @throws UodbcException
 *
 */
function odbc_execute(\Odbc\Result $statement, array $params = []): void
{
    error_clear_last();
    $safeResult = \odbc_execute($statement, $params);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
}


/**
 * @param \Odbc\Result $statement
 * @param array|null $array
 * @param int|null $row
 * @return int
 * @throws UodbcException
 *
 */
function odbc_fetch_into(\Odbc\Result $statement, ?array &$array, ?int $row = null): int
{
    error_clear_last();
    if ($row !== null) {
        $safeResult = \odbc_fetch_into($statement, $array, $row);
    } else {
        $safeResult = \odbc_fetch_into($statement, $array);
    }
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Odbc\Result $statement
 * @param int $field
 * @return int
 * @throws UodbcException
 *
 */
function odbc_field_len(\Odbc\Result $statement, int $field): int
{
    error_clear_last();
    $safeResult = \odbc_field_len($statement, $field);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Odbc\Result $statement
 * @param int $field
 * @return string
 * @throws UodbcException
 *
 */
function odbc_field_name(\Odbc\Result $statement, int $field): string
{
    error_clear_last();
    $safeResult = \odbc_field_name($statement, $field);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Odbc\Result $statement
 * @param string $field
 * @return int
 * @throws UodbcException
 *
 */
function odbc_field_num(\Odbc\Result $statement, string $field): int
{
    error_clear_last();
    $safeResult = \odbc_field_num($statement, $field);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Odbc\Result $statement
 * @param int $field
 * @return int
 * @throws UodbcException
 *
 */
function odbc_field_scale(\Odbc\Result $statement, int $field): int
{
    error_clear_last();
    $safeResult = \odbc_field_scale($statement, $field);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Odbc\Result $statement
 * @param int $field
 * @return string
 * @throws UodbcException
 *
 */
function odbc_field_type(\Odbc\Result $statement, int $field): string
{
    error_clear_last();
    $safeResult = \odbc_field_type($statement, $field);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Odbc\Connection $odbc
 * @param string $pk_catalog
 * @param string $pk_schema
 * @param string $pk_table
 * @param string $fk_catalog
 * @param string $fk_schema
 * @param string $fk_table
 * @return \Odbc\Result|false
 *
 */
function odbc_foreignkeys(\Odbc\Connection $odbc, string $pk_catalog, string $pk_schema, string $pk_table, string $fk_catalog, string $fk_schema, string $fk_table)
{
    error_clear_last();
    $safeResult = \odbc_foreignkeys($odbc, $pk_catalog, $pk_schema, $pk_table, $fk_catalog, $fk_schema, $fk_table);
    return $safeResult;
}


/**
 * @param \Odbc\Connection $odbc
 * @param int $data_type
 * @return \Odbc\Result|false
 *
 */
function odbc_gettypeinfo(\Odbc\Connection $odbc, int $data_type = 0)
{
    error_clear_last();
    $safeResult = \odbc_gettypeinfo($odbc, $data_type);
    return $safeResult;
}


/**
 * @param \Odbc\Result $statement
 * @param int $length
 * @return bool
 *
 */
function odbc_longreadlen(\Odbc\Result $statement, int $length): bool
{
    error_clear_last();
    $safeResult = \odbc_longreadlen($statement, $length);
    return $safeResult;
}


/**
 * @param \Odbc\Result $statement
 * @return int
 * @throws UodbcException
 *
 */
function odbc_num_fields(\Odbc\Result $statement): int
{
    error_clear_last();
    $safeResult = \odbc_num_fields($statement);
    if ($safeResult === -1) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $dsn
 * @param null|string $user
 * @param null|string $password
 * @param int $cursor_option
 * @return \Odbc\Connection
 * @throws UodbcException
 *
 */
function odbc_pconnect(string $dsn, ?string $user = null, ?string $password = null, int $cursor_option = SQL_CUR_USE_DRIVER): \Odbc\Connection
{
    error_clear_last();
    if ($cursor_option !== SQL_CUR_USE_DRIVER) {
        $safeResult = \odbc_pconnect($dsn, $user, $password, $cursor_option);
    } elseif ($password !== null) {
        $safeResult = \odbc_pconnect($dsn, $user, $password);
    } elseif ($user !== null) {
        $safeResult = \odbc_pconnect($dsn, $user);
    } else {
        $safeResult = \odbc_pconnect($dsn);
    }
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Odbc\Connection $odbc
 * @param string $query
 * @return \Odbc\Result
 * @throws UodbcException
 *
 */
function odbc_prepare(\Odbc\Connection $odbc, string $query): \Odbc\Result
{
    error_clear_last();
    $safeResult = \odbc_prepare($odbc, $query);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Odbc\Connection $odbc
 * @param string $catalog
 * @param string $schema
 * @param string $table
 * @return \Odbc\Result|false
 *
 */
function odbc_primarykeys(\Odbc\Connection $odbc, string $catalog, string $schema, string $table)
{
    error_clear_last();
    $safeResult = \odbc_primarykeys($odbc, $catalog, $schema, $table);
    return $safeResult;
}


/**
 * @param  $odbc
 * @param null|string $catalog
 * @param null|string $schema
 * @param null|string $procedure
 * @param null|string $column
 * @return \Odbc\Result|false
 *
 */
function odbc_procedurecolumns($odbc, ?string $catalog = null, ?string $schema = null, ?string $procedure = null, ?string $column = null)
{
    error_clear_last();
    if ($column !== null) {
        $safeResult = \odbc_procedurecolumns($odbc, $catalog, $schema, $procedure, $column);
    } elseif ($procedure !== null) {
        $safeResult = \odbc_procedurecolumns($odbc, $catalog, $schema, $procedure);
    } elseif ($schema !== null) {
        $safeResult = \odbc_procedurecolumns($odbc, $catalog, $schema);
    } elseif ($catalog !== null) {
        $safeResult = \odbc_procedurecolumns($odbc, $catalog);
    } else {
        $safeResult = \odbc_procedurecolumns($odbc);
    }
    return $safeResult;
}


/**
 * @param  $odbc
 * @param null|string $catalog
 * @param null|string $schema
 * @param null|string $procedure
 * @return \Odbc\Result
 * @throws UodbcException
 *
 */
function odbc_procedures($odbc, ?string $catalog = null, ?string $schema = null, ?string $procedure = null): \Odbc\Result
{
    error_clear_last();
    if ($procedure !== null) {
        $safeResult = \odbc_procedures($odbc, $catalog, $schema, $procedure);
    } elseif ($schema !== null) {
        $safeResult = \odbc_procedures($odbc, $catalog, $schema);
    } elseif ($catalog !== null) {
        $safeResult = \odbc_procedures($odbc, $catalog);
    } else {
        $safeResult = \odbc_procedures($odbc);
    }
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Odbc\Result $statement
 * @param string $format
 * @return int
 * @throws UodbcException
 *
 */
function odbc_result_all(\Odbc\Result $statement, string $format = ""): int
{
    error_clear_last();
    $safeResult = \odbc_result_all($statement, $format);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Odbc\Result $statement
 * @param mixed $field
 * @return mixed
 * @throws UodbcException
 *
 */
function odbc_result(\Odbc\Result $statement, $field)
{
    error_clear_last();
    $safeResult = \odbc_result($statement, $field);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Odbc\Connection $odbc
 * @throws UodbcException
 *
 */
function odbc_rollback(\Odbc\Connection $odbc): void
{
    error_clear_last();
    $safeResult = \odbc_rollback($odbc);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
}


/**
 * @param \Odbc\Connection|\Odbc\Result $odbc
 * @param int $which
 * @param int $option
 * @param int $value
 * @throws UodbcException
 *
 */
function odbc_setoption($odbc, int $which, int $option, int $value): void
{
    error_clear_last();
    $safeResult = \odbc_setoption($odbc, $which, $option, $value);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
}


/**
 * @param \Odbc\Connection $odbc
 * @param int $type
 * @param string $catalog
 * @param string $schema
 * @param string $table
 * @param int $scope
 * @param int $nullable
 * @return \Odbc\Result|false
 *
 */
function odbc_specialcolumns(\Odbc\Connection $odbc, int $type, string $catalog, string $schema, string $table, int $scope, int $nullable)
{
    error_clear_last();
    $safeResult = \odbc_specialcolumns($odbc, $type, $catalog, $schema, $table, $scope, $nullable);
    return $safeResult;
}


/**
 * @param \Odbc\Connection $odbc
 * @param string $catalog
 * @param string $schema
 * @param string $table
 * @param int $unique
 * @param int $accuracy
 * @return \Odbc\Result|false
 *
 */
function odbc_statistics(\Odbc\Connection $odbc, string $catalog, string $schema, string $table, int $unique, int $accuracy)
{
    error_clear_last();
    $safeResult = \odbc_statistics($odbc, $catalog, $schema, $table, $unique, $accuracy);
    return $safeResult;
}


/**
 * @param \Odbc\Connection $odbc
 * @param string $catalog
 * @param string $schema
 * @param string $table
 * @return \Odbc\Result|false
 *
 */
function odbc_tableprivileges(\Odbc\Connection $odbc, string $catalog, string $schema, string $table)
{
    error_clear_last();
    $safeResult = \odbc_tableprivileges($odbc, $catalog, $schema, $table);
    return $safeResult;
}


/**
 * @param \Odbc\Connection $odbc
 * @param null|string $catalog
 * @param null|string $schema
 * @param null|string $table
 * @param null|string $types
 * @return \Odbc\Result
 * @throws UodbcException
 *
 */
function odbc_tables(\Odbc\Connection $odbc, ?string $catalog = null, ?string $schema = null, ?string $table = null, ?string $types = null): \Odbc\Result
{
    error_clear_last();
    if ($types !== null) {
        $safeResult = \odbc_tables($odbc, $catalog, $schema, $table, $types);
    } elseif ($table !== null) {
        $safeResult = \odbc_tables($odbc, $catalog, $schema, $table);
    } elseif ($schema !== null) {
        $safeResult = \odbc_tables($odbc, $catalog, $schema);
    } elseif ($catalog !== null) {
        $safeResult = \odbc_tables($odbc, $catalog);
    } else {
        $safeResult = \odbc_tables($odbc);
    }
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\XdiffException;

/**
 * @param string $old_file
 * @param string $new_file
 * @param string $dest
 * @throws XdiffException
 *
 */
function xdiff_file_bdiff(string $old_file, string $new_file, string $dest): void
{
    error_clear_last();
    $safeResult = \xdiff_file_bdiff($old_file, $new_file, $dest);
    if ($safeResult === false) {
        throw XdiffException::createFromPhpError();
    }
}


/**
 * @param string $file
 * @param string $patch
 * @param string $dest
 * @throws XdiffException
 *
 */
function xdiff_file_bpatch(string $file, string $patch, string $dest): void
{
    error_clear_last();
    $safeResult = \xdiff_file_bpatch($file, $patch, $dest);
    if ($safeResult === false) {
        throw XdiffException::createFromPhpError();
    }
}


/**
 * @param string $old_file
 * @param string $new_file
 * @param string $dest
 * @throws XdiffException
 *
 */
function xdiff_file_diff_binary(string $old_file, string $new_file, string $dest): void
{
    error_clear_last();
    $safeResult = \xdiff_file_diff_binary($old_file, $new_file, $dest);
    if ($safeResult === false) {
        throw XdiffException::createFromPhpError();
    }
}


/**
 * @param string $old_file
 * @param string $new_file
 * @param string $dest
 * @param int $context
 * @param bool $minimal
 * @throws XdiffException
 *
 */
function xdiff_file_diff(string $old_file, string $new_file, string $dest, int $context = 3, bool $minimal = false): void
{
    error_clear_last();
    $safeResult = \xdiff_file_diff($old_file, $new_file, $dest, $context, $minimal);
    if ($safeResult === false) {
        throw XdiffException::createFromPhpError();
    }
}


/**
 * @param string $file
 * @param string $patch
 * @param string $dest
 * @throws XdiffException
 *
 */
function xdiff_file_patch_binary(string $file, string $patch, string $dest): void
{
    error_clear_last();
    $safeResult = \xdiff_file_patch_binary($file, $patch, $dest);
    if ($safeResult === false) {
        throw XdiffException::createFromPhpError();
    }
}


/**
 * @param string $old_file
 * @param string $new_file
 * @param string $dest
 * @throws XdiffException
 *
 */
function xdiff_file_rabdiff(string $old_file, string $new_file, string $dest): void
{
    error_clear_last();
    $safeResult = \xdiff_file_rabdiff($old_file, $new_file, $dest);
    if ($safeResult === false) {
        throw XdiffException::createFromPhpError();
    }
}


/**
 * @param string $str
 * @param string $patch
 * @return string
 * @throws XdiffException
 *
 */
function xdiff_string_bpatch(string $str, string $patch): string
{
    error_clear_last();
    $safeResult = \xdiff_string_bpatch($str, $patch);
    if ($safeResult === false) {
        throw XdiffException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $str
 * @param string $patch
 * @return string
 * @throws XdiffException
 *
 */
function xdiff_string_patch_binary(string $str, string $patch): string
{
    error_clear_last();
    $safeResult = \xdiff_string_patch_binary($str, $patch);
    if ($safeResult === false) {
        throw XdiffException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $str
 * @param string $patch
 * @param int $flags
 * @param null|string $error
 * @return string
 * @throws XdiffException
 *
 */
function xdiff_string_patch(string $str, string $patch, ?int $flags = null, ?string &$error = null): string
{
    error_clear_last();
    if ($error !== null) {
        $safeResult = \xdiff_string_patch($str, $patch, $flags, $error);
    } elseif ($flags !== null) {
        $safeResult = \xdiff_string_patch($str, $patch, $flags);
    } else {
        $safeResult = \xdiff_string_patch($str, $patch);
    }
    if ($safeResult === false) {
        throw XdiffException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $old_data
 * @param string $new_data
 * @return string
 * @throws XdiffException
 *
 */
function xdiff_string_rabdiff(string $old_data, string $new_data): string
{
    error_clear_last();
    $safeResult = \xdiff_string_rabdiff($old_data, $new_data);
    if ($safeResult === false) {
        throw XdiffException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\PcntlException;

/**
 * @param int|null $pid
 * @return array|bool
 * @throws PcntlException
 *
 */
function pcntl_getcpuaffinity(?int $pid = null)
{
    error_clear_last();
    if ($pid !== null) {
        $safeResult = \pcntl_getcpuaffinity($pid);
    } else {
        $safeResult = \pcntl_getcpuaffinity();
    }
    if ($safeResult === false) {
        throw PcntlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int|null $process_id
 * @param int $mode
 * @return int
 * @throws PcntlException
 *
 */
function pcntl_getpriority(?int $process_id = null, int $mode = PRIO_PROCESS): int
{
    error_clear_last();
    if ($mode !== PRIO_PROCESS) {
        $safeResult = \pcntl_getpriority($process_id, $mode);
    } elseif ($process_id !== null) {
        $safeResult = \pcntl_getpriority($process_id);
    } else {
        $safeResult = \pcntl_getpriority();
    }
    if ($safeResult === false) {
        throw PcntlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int|null $pid
 * @param array $hmask
 * @throws PcntlException
 *
 */
function pcntl_setcpuaffinity(?int $pid = null, ?array $hmask = null): void
{
    error_clear_last();
    if ($hmask !== null) {
        $safeResult = \pcntl_setcpuaffinity($pid, $hmask);
    } elseif ($pid !== null) {
        $safeResult = \pcntl_setcpuaffinity($pid);
    } else {
        $safeResult = \pcntl_setcpuaffinity();
    }
    if ($safeResult === false) {
        throw PcntlException::createFromPhpError();
    }
}


/**
 * @param int $priority
 * @param int|null $process_id
 * @param int $mode
 * @throws PcntlException
 *
 */
function pcntl_setpriority(int $priority, ?int $process_id = null, int $mode = PRIO_PROCESS): void
{
    error_clear_last();
    if ($mode !== PRIO_PROCESS) {
        $safeResult = \pcntl_setpriority($priority, $process_id, $mode);
    } elseif ($process_id !== null) {
        $safeResult = \pcntl_setpriority($priority, $process_id);
    } else {
        $safeResult = \pcntl_setpriority($priority);
    }
    if ($safeResult === false) {
        throw PcntlException::createFromPhpError();
    }
}


/**
 * @throws PcntlException
 *
 */
function pcntl_signal_dispatch(): void
{
    error_clear_last();
    $safeResult = \pcntl_signal_dispatch();
    if ($safeResult === false) {
        throw PcntlException::createFromPhpError();
    }
}


/**
 * @param int $signal
 * @param callable|int $handler
 * @param bool $restart_syscalls
 * @throws PcntlException
 *
 */
function pcntl_signal(int $signal, $handler, bool $restart_syscalls = true): void
{
    error_clear_last();
    $safeResult = \pcntl_signal($signal, $handler, $restart_syscalls);
    if ($safeResult === false) {
        throw PcntlException::createFromPhpError();
    }
}


/**
 * @param int $mode
 * @param array $signals
 * @param array|null $old_signals
 * @throws PcntlException
 *
 */
function pcntl_sigprocmask(int $mode, array $signals, ?array &$old_signals = null): void
{
    error_clear_last();
    $safeResult = \pcntl_sigprocmask($mode, $signals, $old_signals);
    if ($safeResult === false) {
        throw PcntlException::createFromPhpError();
    }
}


/**
 * @param array $signals
 * @param array|null $info
 * @param int $seconds
 * @param int $nanoseconds
 * @return int
 * @throws PcntlException
 *
 */
function pcntl_sigtimedwait(array $signals, ?array &$info = [], int $seconds = 0, int $nanoseconds = 0): int
{
    error_clear_last();
    $safeResult = \pcntl_sigtimedwait($signals, $info, $seconds, $nanoseconds);
    if ($safeResult === false) {
        throw PcntlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param array $signals
 * @param array|null $info
 * @return int
 * @throws PcntlException
 *
 */
function pcntl_sigwaitinfo(array $signals, ?array &$info = []): int
{
    error_clear_last();
    $safeResult = \pcntl_sigwaitinfo($signals, $info);
    if ($safeResult === false) {
        throw PcntlException::createFromPhpError();
    }
    return $safeResult;
}
<?php

return [
    'apache_getenv',
    'apache_get_version',
    'apache_lookup_uri',
    'apache_setenv',
    'apcu_cache_info',
    'apcu_cas',
    'apcu_dec',
    'apcu_fetch',
    'apcu_inc',
    'apcu_sma_info',
    'base64_decode',
    'bindtextdomain',
    'bzclose',
    'bzflush',
    'bzopen',
    'bzread',
    'bzwrite',
    'chdir',
    'chgrp',
    'chmod',
    'chown',
    'chroot',
    'class_alias',
    'class_implements',
    'class_parents',
    'class_uses',
    'cli_set_process_title',
    'com_create_guid',
    'com_event_sink',
    'com_load_typelib',
    'com_print_typeinfo',
    'convert_uudecode',
    'copy',
    'cubrid_bind',
    'cubrid_column_names',
    'cubrid_column_types',
    'cubrid_col_size',
    'cubrid_commit',
    'cubrid_connect',
    'cubrid_connect_with_url',
    'cubrid_current_oid',
    'cubrid_disconnect',
    'cubrid_drop',
    'cubrid_free_result',
    'cubrid_get_charset',
    'cubrid_get_class_name',
    'cubrid_get_client_info',
    'cubrid_get_db_parameter',
    'cubrid_get_query_timeout',
    'cubrid_get_server_info',
    'cubrid_insert_id',
    'cubrid_lob2_bind',
    'cubrid_lob2_close',
    'cubrid_lob2_export',
    'cubrid_lob2_import',
    'cubrid_lob2_new',
    'cubrid_lob2_read',
    'cubrid_lob2_seek',
    'cubrid_lob2_seek64',
    'cubrid_lob2_size',
    'cubrid_lob2_size64',
    'cubrid_lob2_tell',
    'cubrid_lob2_tell64',
    'cubrid_lob2_write',
    'cubrid_lob_close',
    'cubrid_lob_export',
    'cubrid_lob_get',
    'cubrid_lob_send',
    'cubrid_lob_size',
    'cubrid_lock_read',
    'cubrid_lock_write',
    'cubrid_move_cursor',
    'cubrid_next_result',
    'cubrid_pconnect',
    'cubrid_pconnect_with_url',
    'cubrid_prepare',
    'cubrid_put',
    'cubrid_rollback',
    'cubrid_schema',
    'cubrid_seq_drop',
    'cubrid_seq_insert',
    'cubrid_seq_put',
    'cubrid_set_add',
    'cubrid_set_autocommit',
    'cubrid_set_db_parameter',
    'cubrid_set_drop',
    'cubrid_set_query_timeout',
    'curl_copy_handle',
    'curl_escape',
    'curl_exec',
    'curl_getinfo',
    'curl_init',
    'curl_multi_info_read',
    'curl_multi_setopt',
    'curl_setopt',
    'curl_share_setopt',
    'curl_unescape',
    'curl_upkeep',
    'date_create',
    'date_create_immutable',
    'date_parse_from_format',
    'date_sunrise',
    'date_sunset',
    'db2_autocommit',
    'db2_bind_param',
    'db2_client_info',
    'db2_close',
    'db2_commit',
    'db2_execute',
    'db2_free_result',
    'db2_free_stmt',
    'db2_get_option',
    'db2_num_rows',
    'db2_pclose',
    'db2_rollback',
    'db2_server_info',
    'db2_set_option',
    'define',
    'deflate_add',
    'deflate_init',
    'dir',
    'disk_free_space',
    'disk_total_space',
    'dl',
    'dns_get_record',
    'eio_busy',
    'eio_chmod',
    'eio_chown',
    'eio_close',
    'eio_custom',
    'eio_dup2',
    'eio_event_loop',
    'eio_fallocate',
    'eio_fchmod',
    'eio_fchown',
    'eio_fdatasync',
    'eio_fstat',
    'eio_fstatvfs',
    'eio_fsync',
    'eio_ftruncate',
    'eio_futime',
    'eio_grp',
    'eio_lstat',
    'eio_mkdir',
    'eio_mknod',
    'eio_nop',
    'eio_readahead',
    'eio_readdir',
    'eio_readlink',
    'eio_rename',
    'eio_rmdir',
    'eio_seek',
    'eio_sendfile',
    'eio_stat',
    'eio_statvfs',
    'eio_symlink',
    'eio_sync',
    'eio_syncfs',
    'eio_sync_file_range',
    'eio_truncate',
    'eio_unlink',
    'eio_utime',
    'eio_write',
    'error_log',
    'exec',
    'fastcgi_finish_request',
    'fbird_blob_cancel',
    'fclose',
    'fdatasync',
    'fflush',
    'fgetcsv',
    'file',
    'fileatime',
    'filectime',
    'fileinode',
    'filemtime',
    'fileowner',
    'fileperms',
    'filesize',
    'filetype',
    'file_get_contents',
    'file_put_contents',
    'filter_input_array',
    'filter_var_array',
    'finfo_close',
    'finfo_open',
    'flock',
    'fopen',
    'fpm_get_status',
    'fputcsv',
    'fread',
    'fsockopen',
    'fstat',
    'fsync',
    'ftell',
    'ftp_alloc',
    'ftp_append',
    'ftp_cdup',
    'ftp_chdir',
    'ftp_chmod',
    'ftp_close',
    'ftp_connect',
    'ftp_delete',
    'ftp_fget',
    'ftp_fput',
    'ftp_get',
    'ftp_login',
    'ftp_mkdir',
    'ftp_mlsd',
    'ftp_nb_get',
    'ftp_nb_put',
    'ftp_nlist',
    'ftp_pasv',
    'ftp_put',
    'ftp_pwd',
    'ftp_raw',
    'ftp_rename',
    'ftp_rmdir',
    'ftp_site',
    'ftp_size',
    'ftp_ssl_connect',
    'ftp_systype',
    'ftruncate',
    'fwrite',
    'getcwd',
    'gethostname',
    'getimagesize',
    'getlastmod',
    'getmygid',
    'getmyinode',
    'getmypid',
    'getmyuid',
    'getopt',
    'getprotobyname',
    'getprotobynumber',
    'getrusage',
    'getservbyport',
    'get_cfg_var',
    'get_headers',
    'get_include_path',
    'get_meta_tags',
    'glob',
    'gmmktime',
    'gmstrftime',
    'gnupg_adddecryptkey',
    'gnupg_addencryptkey',
    'gnupg_addsignkey',
    'gnupg_cleardecryptkeys',
    'gnupg_clearencryptkeys',
    'gnupg_clearsignkeys',
    'gnupg_deletekey',
    'gnupg_setarmor',
    'gnupg_setsignmode',
    'gzclose',
    'gzcompress',
    'gzdecode',
    'gzdeflate',
    'gzencode',
    'gzfile',
    'gzgets',
    'gzinflate',
    'gzopen',
    'gzread',
    'gzrewind',
    'gztell',
    'gzuncompress',
    'gzwrite',
    'hash_file',
    'hash_hmac_file',
    'hash_update_file',
    'header_register_callback',
    'hex2bin',
    'highlight_file',
    'hrtime',
    'ibase_add_user',
    'ibase_backup',
    'ibase_blob_cancel',
    'ibase_blob_create',
    'ibase_blob_get',
    'ibase_close',
    'ibase_commit',
    'ibase_commit_ret',
    'ibase_connect',
    'ibase_delete_user',
    'ibase_drop_db',
    'ibase_free_event_handler',
    'ibase_free_query',
    'ibase_free_result',
    'ibase_maintain_db',
    'ibase_modify_user',
    'ibase_name_result',
    'ibase_pconnect',
    'ibase_restore',
    'ibase_rollback',
    'ibase_rollback_ret',
    'ibase_service_attach',
    'ibase_service_detach',
    'iconv',
    'iconv_get_encoding',
    'iconv_mime_decode',
    'iconv_mime_encode',
    'iconv_set_encoding',
    'iconv_strlen',
    'idate',
    'imageaffine',
    'imageaffinematrixconcat',
    'imageaffinematrixget',
    'imagealphablending',
    'imageantialias',
    'imagearc',
    'imageavif',
    'imagebmp',
    'imagechar',
    'imagecharup',
    'imagecolorat',
    'imagecolordeallocate',
    'imagecolormatch',
    'imagecolorset',
    'imageconvolution',
    'imagecopy',
    'imagecopymerge',
    'imagecopymergegray',
    'imagecopyresampled',
    'imagecopyresized',
    'imagecreate',
    'imagecreatefromavif',
    'imagecreatefrombmp',
    'imagecreatefromgd',
    'imagecreatefromgd2',
    'imagecreatefromgd2part',
    'imagecreatefromgif',
    'imagecreatefromjpeg',
    'imagecreatefrompng',
    'imagecreatefromstring',
    'imagecreatefromtga',
    'imagecreatefromwbmp',
    'imagecreatefromwebp',
    'imagecreatefromxbm',
    'imagecreatefromxpm',
    'imagecreatetruecolor',
    'imagecrop',
    'imagecropauto',
    'imagedashedline',
    'imagedestroy',
    'imageellipse',
    'imagefill',
    'imagefilledarc',
    'imagefilledellipse',
    'imagefilledrectangle',
    'imagefilltoborder',
    'imagefilter',
    'imageflip',
    'imageftbbox',
    'imagefttext',
    'imagegammacorrect',
    'imagegd',
    'imagegd2',
    'imagegif',
    'imagegrabscreen',
    'imagegrabwindow',
    'imagejpeg',
    'imagelayereffect',
    'imageline',
    'imageloadfont',
    'imagepng',
    'imagerectangle',
    'imageresolution',
    'imagerotate',
    'imagesavealpha',
    'imagescale',
    'imagesetbrush',
    'imagesetclip',
    'imagesetinterpolation',
    'imagesetpixel',
    'imagesetstyle',
    'imagesetthickness',
    'imagesettile',
    'imagestring',
    'imagestringup',
    'imagetruecolortopalette',
    'imagettfbbox',
    'imagettftext',
    'imagewbmp',
    'imagewebp',
    'imagexbm',
    'image_type_to_extension',
    'imap_8bit',
    'imap_append',
    'imap_base64',
    'imap_binary',
    'imap_body',
    'imap_bodystruct',
    'imap_check',
    'imap_createmailbox',
    'imap_deletemailbox',
    'imap_fetchbody',
    'imap_fetchheader',
    'imap_fetchmime',
    'imap_fetchstructure',
    'imap_fetch_overview',
    'imap_getacl',
    'imap_getmailboxes',
    'imap_getsubscribed',
    'imap_headerinfo',
    'imap_headers',
    'imap_listscan',
    'imap_lsub',
    'imap_mail',
    'imap_mail_compose',
    'imap_mail_copy',
    'imap_mail_move',
    'imap_mime_header_decode',
    'imap_mutf7_to_utf8',
    'imap_num_msg',
    'imap_open',
    'imap_qprint',
    'imap_renamemailbox',
    'imap_rfc822_write_address',
    'imap_savebody',
    'imap_setacl',
    'imap_set_quota',
    'imap_sort',
    'imap_status',
    'imap_subscribe',
    'imap_thread',
    'imap_timeout',
    'imap_unsubscribe',
    'imap_utf8_to_mutf7',
    'inet_ntop',
    'inet_pton',
    'inflate_add',
    'inflate_get_read_len',
    'inflate_init',
    'ini_get',
    'ini_set',
    'inotify_add_watch',
    'inotify_init',
    'inotify_rm_watch',
    'iptcembed',
    'iptcparse',
    'json_decode',
    'json_encode',
    'lchgrp',
    'lchown',
    'ldap_8859_to_t61',
    'ldap_add',
    'ldap_bind',
    'ldap_compare',
    'ldap_control_paged_result',
    'ldap_control_paged_result_response',
    'ldap_count_entries',
    'ldap_delete',
    'ldap_dn2ufn',
    'ldap_exop',
    'ldap_exop_passwd',
    'ldap_exop_whoami',
    'ldap_explode_dn',
    'ldap_first_attribute',
    'ldap_first_entry',
    'ldap_free_result',
    'ldap_get_dn',
    'ldap_get_entries',
    'ldap_get_option',
    'ldap_get_values',
    'ldap_get_values_len',
    'ldap_modify_batch',
    'ldap_mod_add',
    'ldap_mod_del',
    'ldap_mod_replace',
    'ldap_next_attribute',
    'ldap_parse_exop',
    'ldap_parse_result',
    'ldap_rename',
    'ldap_sasl_bind',
    'ldap_set_option',
    'ldap_unbind',
    'libxml_set_external_entity_loader',
    'link',
    'lstat',
    'lzf_compress',
    'lzf_decompress',
    'mailparse_msg_extract_part_file',
    'mailparse_msg_free',
    'mailparse_msg_parse',
    'mailparse_msg_parse_file',
    'mailparse_stream_encode',
    'mb_chr',
    'mb_convert_encoding',
    'mb_convert_variables',
    'mb_detect_order',
    'mb_eregi_replace',
    'mb_ereg_replace',
    'mb_ereg_replace_callback',
    'mb_ereg_search_getregs',
    'mb_ereg_search_init',
    'mb_ereg_search_regs',
    'mb_ereg_search_setpos',
    'mb_get_info',
    'mb_http_output',
    'mb_internal_encoding',
    'mb_ord',
    'mb_parse_str',
    'mb_regex_encoding',
    'mb_send_mail',
    'mb_split',
    'md5_file',
    'mime_content_type',
    'mkdir',
    'mktime',
    'msg_get_queue',
    'msg_queue_exists',
    'msg_receive',
    'msg_remove_queue',
    'msg_send',
    'msg_set_queue',
    'msg_stat_queue',
    'mysql_close',
    'mysql_connect',
    'mysql_create_db',
    'mysql_data_seek',
    'mysql_db_name',
    'mysql_db_query',
    'mysql_drop_db',
    'mysql_fetch_lengths',
    'mysql_field_flags',
    'mysql_field_len',
    'mysql_field_name',
    'mysql_field_seek',
    'mysql_free_result',
    'mysql_get_host_info',
    'mysql_get_proto_info',
    'mysql_get_server_info',
    'mysql_info',
    'mysql_list_dbs',
    'mysql_list_fields',
    'mysql_list_processes',
    'mysql_list_tables',
    'mysql_num_fields',
    'mysql_num_rows',
    'mysql_query',
    'mysql_real_escape_string',
    'mysql_result',
    'mysql_select_db',
    'mysql_set_charset',
    'mysql_tablename',
    'mysql_thread_id',
    'mysql_unbuffered_query',
    'net_get_interfaces',
    'ob_clean',
    'ob_end_clean',
    'ob_end_flush',
    'ob_flush',
    'ob_get_clean',
    'ob_get_flush',
    'ob_start',
    'oci_bind_array_by_name',
    'oci_bind_by_name',
    'oci_cancel',
    'oci_commit',
    'oci_connect',
    'oci_define_by_name',
    'oci_execute',
    'oci_field_name',
    'oci_field_precision',
    'oci_field_scale',
    'oci_field_size',
    'oci_field_type',
    'oci_field_type_raw',
    'oci_free_descriptor',
    'oci_free_statement',
    'oci_new_collection',
    'oci_new_connect',
    'oci_new_cursor',
    'oci_new_descriptor',
    'oci_num_rows',
    'oci_parse',
    'oci_pconnect',
    'oci_register_taf_callback',
    'oci_result',
    'oci_rollback',
    'oci_server_version',
    'oci_set_action',
    'oci_set_call_timeout',
    'oci_set_client_identifier',
    'oci_set_client_info',
    'oci_set_db_operation',
    'oci_set_edition',
    'oci_set_module_name',
    'oci_set_prefetch',
    'oci_set_prefetch_lob',
    'oci_statement_type',
    'oci_unregister_taf_callback',
    'odbc_autocommit',
    'odbc_commit',
    'odbc_connect',
    'odbc_cursor',
    'odbc_data_source',
    'odbc_exec',
    'odbc_execute',
    'odbc_fetch_into',
    'odbc_field_len',
    'odbc_field_name',
    'odbc_field_num',
    'odbc_field_scale',
    'odbc_field_type',
    'odbc_num_fields',
    'odbc_pconnect',
    'odbc_prepare',
    'odbc_procedures',
    'odbc_result',
    'odbc_result_all',
    'odbc_rollback',
    'odbc_setoption',
    'odbc_tables',
    'opcache_compile_file',
    'opcache_get_status',
    'opendir',
    'openssl_cipher_iv_length',
    'openssl_cipher_key_length',
    'openssl_cms_decrypt',
    'openssl_cms_encrypt',
    'openssl_cms_read',
    'openssl_cms_sign',
    'openssl_cms_verify',
    'openssl_csr_export',
    'openssl_csr_export_to_file',
    'openssl_csr_get_public_key',
    'openssl_csr_get_subject',
    'openssl_csr_new',
    'openssl_csr_sign',
    'openssl_decrypt',
    'openssl_dh_compute_key',
    'openssl_digest',
    'openssl_encrypt',
    'openssl_get_curve_names',
    'openssl_open',
    'openssl_pbkdf2',
    'openssl_pkcs7_decrypt',
    'openssl_pkcs7_encrypt',
    'openssl_pkcs7_read',
    'openssl_pkcs7_sign',
    'openssl_pkcs12_export',
    'openssl_pkcs12_export_to_file',
    'openssl_pkcs12_read',
    'openssl_pkey_derive',
    'openssl_pkey_export',
    'openssl_pkey_export_to_file',
    'openssl_pkey_get_details',
    'openssl_pkey_get_private',
    'openssl_pkey_get_public',
    'openssl_pkey_new',
    'openssl_private_decrypt',
    'openssl_private_encrypt',
    'openssl_public_decrypt',
    'openssl_public_encrypt',
    'openssl_seal',
    'openssl_sign',
    'openssl_spki_export',
    'openssl_spki_export_challenge',
    'openssl_spki_new',
    'openssl_spki_verify',
    'openssl_verify',
    'openssl_x509_checkpurpose',
    'openssl_x509_export',
    'openssl_x509_export_to_file',
    'openssl_x509_fingerprint',
    'openssl_x509_read',
    'output_add_rewrite_var',
    'output_reset_rewrite_vars',
    'parse_ini_file',
    'parse_ini_string',
    'parse_url',
    'passthru',
    'pclose',
    'pcntl_getcpuaffinity',
    'pcntl_getpriority',
    'pcntl_setcpuaffinity',
    'pcntl_setpriority',
    'pcntl_signal',
    'pcntl_signal_dispatch',
    'pcntl_sigprocmask',
    'pcntl_sigtimedwait',
    'pcntl_sigwaitinfo',
    'pfsockopen',
    'pg_cancel_query',
    'pg_connect',
    'pg_connection_reset',
    'pg_convert',
    'pg_copy_from',
    'pg_copy_to',
    'pg_delete',
    'pg_end_copy',
    'pg_execute',
    'pg_field_num',
    'pg_field_table',
    'pg_flush',
    'pg_free_result',
    'pg_host',
    'pg_insert',
    'pg_last_oid',
    'pg_lo_close',
    'pg_lo_export',
    'pg_lo_import',
    'pg_lo_open',
    'pg_lo_read',
    'pg_lo_seek',
    'pg_lo_truncate',
    'pg_lo_unlink',
    'pg_lo_write',
    'pg_meta_data',
    'pg_parameter_status',
    'pg_pconnect',
    'pg_ping',
    'pg_prepare',
    'pg_put_line',
    'pg_query',
    'pg_query_params',
    'pg_result_error_field',
    'pg_result_seek',
    'pg_select',
    'pg_set_chunked_rows_size',
    'pg_socket',
    'pg_trace',
    'pg_update',
    'php_sapi_name',
    'popen',
    'posix_access',
    'posix_eaccess',
    'posix_getgrgid',
    'posix_getgrnam',
    'posix_getgroups',
    'posix_getlogin',
    'posix_getpgid',
    'posix_getpwuid',
    'posix_getrlimit',
    'posix_getsid',
    'posix_initgroups',
    'posix_kill',
    'posix_mkfifo',
    'posix_mknod',
    'posix_setegid',
    'posix_seteuid',
    'posix_setgid',
    'posix_setpgid',
    'posix_setrlimit',
    'posix_setsid',
    'posix_setuid',
    'posix_times',
    'posix_uname',
    'preg_grep',
    'preg_match',
    'preg_match_all',
    'preg_replace',
    'preg_replace_callback',
    'preg_replace_callback_array',
    'preg_split',
    'proc_close',
    'proc_nice',
    'proc_open',
    'pspell_add_to_personal',
    'pspell_add_to_session',
    'pspell_clear_session',
    'pspell_config_data_dir',
    'pspell_config_dict_dir',
    'pspell_config_ignore',
    'pspell_config_mode',
    'pspell_config_personal',
    'pspell_config_repl',
    'pspell_config_runtogether',
    'pspell_config_save_repl',
    'pspell_new',
    'pspell_new_config',
    'pspell_new_personal',
    'pspell_save_wordlist',
    'pspell_store_replacement',
    'ps_add_launchlink',
    'ps_add_locallink',
    'ps_add_note',
    'ps_add_pdflink',
    'ps_add_weblink',
    'ps_arc',
    'ps_arcn',
    'ps_begin_page',
    'ps_begin_pattern',
    'ps_begin_template',
    'ps_circle',
    'ps_clip',
    'ps_close',
    'ps_closepath',
    'ps_closepath_stroke',
    'ps_close_image',
    'ps_continue_text',
    'ps_curveto',
    'ps_delete',
    'ps_end_page',
    'ps_end_pattern',
    'ps_end_template',
    'ps_fill',
    'ps_fill_stroke',
    'ps_get_parameter',
    'ps_hyphenate',
    'ps_include_file',
    'ps_lineto',
    'ps_moveto',
    'ps_new',
    'ps_open_file',
    'ps_place_image',
    'ps_rect',
    'ps_restore',
    'ps_rotate',
    'ps_save',
    'ps_scale',
    'ps_setcolor',
    'ps_setdash',
    'ps_setflat',
    'ps_setfont',
    'ps_setgray',
    'ps_setlinecap',
    'ps_setlinejoin',
    'ps_setlinewidth',
    'ps_setmiterlimit',
    'ps_setoverprintmode',
    'ps_setpolydash',
    'ps_set_border_color',
    'ps_set_border_dash',
    'ps_set_border_style',
    'ps_set_info',
    'ps_set_parameter',
    'ps_set_text_pos',
    'ps_set_value',
    'ps_shading',
    'ps_shading_pattern',
    'ps_shfill',
    'ps_show',
    'ps_show2',
    'ps_show_xy',
    'ps_show_xy2',
    'ps_stroke',
    'ps_symbol',
    'ps_translate',
    'putenv',
    'readfile',
    'readgzfile',
    'readline_completion_function',
    'readline_read_history',
    'readline_write_history',
    'readlink',
    'realpath',
    'register_tick_function',
    'rename',
    'rewind',
    'rmdir',
    'rnp_decrypt',
    'rnp_dump_packets',
    'rnp_dump_packets_to_json',
    'rnp_ffi_create',
    'rnp_ffi_set_pass_provider',
    'rnp_import_keys',
    'rnp_import_signatures',
    'rnp_key_export',
    'rnp_key_export_autocrypt',
    'rnp_key_export_revocation',
    'rnp_key_get_info',
    'rnp_key_remove',
    'rnp_key_revoke',
    'rnp_list_keys',
    'rnp_load_keys',
    'rnp_load_keys_from_path',
    'rnp_locate_key',
    'rnp_op_encrypt',
    'rnp_op_generate_key',
    'rnp_op_sign',
    'rnp_op_sign_cleartext',
    'rnp_op_sign_detached',
    'rnp_op_verify',
    'rnp_op_verify_detached',
    'rnp_save_keys',
    'rnp_save_keys_to_path',
    'rnp_supported_features',
    'rpmaddtag',
    'rpmdefine',
    'rrd_create',
    'rrd_first',
    'rrd_graph',
    'rrd_info',
    'rrd_lastupdate',
    'rrd_restore',
    'rrd_tune',
    'rrd_update',
    'rrd_xport',
    'sapi_windows_cp_conv',
    'sapi_windows_cp_set',
    'sapi_windows_generate_ctrl_event',
    'sapi_windows_set_ctrl_handler',
    'sapi_windows_vt100_support',
    'scandir',
    'sem_acquire',
    'sem_get',
    'sem_release',
    'sem_remove',
    'session_abort',
    'session_cache_expire',
    'session_cache_limiter',
    'session_create_id',
    'session_decode',
    'session_destroy',
    'session_encode',
    'session_gc',
    'session_id',
    'session_module_name',
    'session_name',
    'session_regenerate_id',
    'session_reset',
    'session_save_path',
    'session_start',
    'session_unset',
    'session_write_close',
    'settype',
    'set_include_path',
    'set_time_limit',
    'sha1_file',
    'shell_exec',
    'shmop_delete',
    'shm_attach',
    'shm_detach',
    'shm_put_var',
    'shm_remove',
    'shm_remove_var',
    'simplexml_import_dom',
    'simplexml_load_file',
    'simplexml_load_string',
    'socket_accept',
    'socket_addrinfo_bind',
    'socket_addrinfo_connect',
    'socket_addrinfo_lookup',
    'socket_atmark',
    'socket_bind',
    'socket_connect',
    'socket_create',
    'socket_create_listen',
    'socket_create_pair',
    'socket_export_stream',
    'socket_getpeername',
    'socket_getsockname',
    'socket_get_option',
    'socket_import_stream',
    'socket_listen',
    'socket_read',
    'socket_send',
    'socket_sendmsg',
    'socket_sendto',
    'socket_set_block',
    'socket_set_nonblock',
    'socket_set_option',
    'socket_shutdown',
    'socket_write',
    'socket_wsaprotocol_info_export',
    'socket_wsaprotocol_info_import',
    'socket_wsaprotocol_info_release',
    'sodium_crypto_aead_aegis128l_decrypt',
    'sodium_crypto_aead_aegis256_decrypt',
    'sodium_crypto_aead_aes256gcm_decrypt',
    'sodium_crypto_aead_chacha20poly1305_decrypt',
    'sodium_crypto_aead_chacha20poly1305_encrypt',
    'sodium_crypto_aead_chacha20poly1305_ietf_decrypt',
    'sodium_crypto_aead_chacha20poly1305_ietf_encrypt',
    'sodium_crypto_aead_xchacha20poly1305_ietf_decrypt',
    'sodium_crypto_aead_xchacha20poly1305_ietf_encrypt',
    'sodium_crypto_box_open',
    'sodium_crypto_box_seal_open',
    'sodium_crypto_secretbox_open',
    'sodium_crypto_sign_open',
    'sodium_crypto_stream_xchacha20_xor_ic',
    'solr_get_version',
    'spl_autoload_register',
    'spl_autoload_unregister',
    'sqlsrv_begin_transaction',
    'sqlsrv_cancel',
    'sqlsrv_client_info',
    'sqlsrv_close',
    'sqlsrv_commit',
    'sqlsrv_configure',
    'sqlsrv_execute',
    'sqlsrv_fetch',
    'sqlsrv_fetch_array',
    'sqlsrv_fetch_object',
    'sqlsrv_free_stmt',
    'sqlsrv_get_field',
    'sqlsrv_next_result',
    'sqlsrv_num_fields',
    'sqlsrv_num_rows',
    'sqlsrv_prepare',
    'sqlsrv_query',
    'sqlsrv_rollback',
    'ssdeep_fuzzy_compare',
    'ssdeep_fuzzy_hash',
    'ssdeep_fuzzy_hash_filename',
    'ssh2_auth_agent',
    'ssh2_auth_hostbased_file',
    'ssh2_auth_password',
    'ssh2_auth_pubkey_file',
    'ssh2_connect',
    'ssh2_disconnect',
    'ssh2_exec',
    'ssh2_forward_accept',
    'ssh2_forward_listen',
    'ssh2_publickey_add',
    'ssh2_publickey_init',
    'ssh2_publickey_remove',
    'ssh2_scp_recv',
    'ssh2_scp_send',
    'ssh2_send_eof',
    'ssh2_sftp',
    'ssh2_sftp_chmod',
    'ssh2_sftp_mkdir',
    'ssh2_sftp_rename',
    'ssh2_sftp_rmdir',
    'ssh2_sftp_symlink',
    'ssh2_sftp_unlink',
    'ssh2_shell',
    'stream_context_set_options',
    'stream_context_set_params',
    'stream_copy_to_stream',
    'stream_filter_append',
    'stream_filter_prepend',
    'stream_filter_register',
    'stream_filter_remove',
    'stream_get_contents',
    'stream_get_line',
    'stream_isatty',
    'stream_resolve_include_path',
    'stream_set_blocking',
    'stream_set_timeout',
    'stream_socket_accept',
    'stream_socket_client',
    'stream_socket_get_name',
    'stream_socket_pair',
    'stream_socket_recvfrom',
    'stream_socket_sendto',
    'stream_socket_server',
    'stream_socket_shutdown',
    'stream_supports_lock',
    'stream_wrapper_register',
    'stream_wrapper_restore',
    'stream_wrapper_unregister',
    'strftime',
    'strptime',
    'strtotime',
    'swoole_async_dns_lookup',
    'swoole_async_readfile',
    'swoole_async_write',
    'swoole_async_writefile',
    'swoole_event_defer',
    'swoole_event_del',
    'swoole_event_write',
    'symlink',
    'system',
    'sys_getloadavg',
    'tempnam',
    'timezone_name_from_abbr',
    'time_nanosleep',
    'time_sleep_until',
    'tmpfile',
    'touch',
    'unixtojd',
    'unlink',
    'unpack',
    'unserialize',
    'uopz_extend',
    'uopz_implement',
    'variant_date_to_timestamp',
    'variant_round',
    'virtual',
    'xdiff_file_bdiff',
    'xdiff_file_bpatch',
    'xdiff_file_diff',
    'xdiff_file_diff_binary',
    'xdiff_file_patch_binary',
    'xdiff_file_rabdiff',
    'xdiff_string_bpatch',
    'xdiff_string_patch',
    'xdiff_string_patch_binary',
    'xdiff_string_rabdiff',
    'xmlrpc_set_type',
    'xml_parser_free',
    'xml_parser_set_option',
    'yaml_parse',
    'yaml_parse_file',
    'yaml_parse_url',
    'yaz_ccl_parse',
    'yaz_close',
    'yaz_connect',
    'yaz_database',
    'yaz_element',
    'yaz_present',
    'yaz_search',
    'yaz_wait',
    'zip_entry_close',
    'zip_entry_compressedsize',
    'zip_entry_compressionmethod',
    'zip_entry_filesize',
    'zip_entry_name',
    'zip_entry_open',
    'zip_entry_read',
    'zlib_decode',
];
<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\Renaming\Rector\FuncCall\RenameFunctionRector;

// This file configures rector/rector to replace all PHP functions with their equivalent "safe" functions.
return static function (RectorConfig $rectorConfig): void {
    $rectorConfig->ruleWithConfiguration(
        RenameFunctionRector::class,
        [            'apache_getenv' => 'Safe\apache_getenv',
            'apache_get_version' => 'Safe\apache_get_version',
            'apache_lookup_uri' => 'Safe\apache_lookup_uri',
            'apache_setenv' => 'Safe\apache_setenv',
            'apcu_cache_info' => 'Safe\apcu_cache_info',
            'apcu_cas' => 'Safe\apcu_cas',
            'apcu_dec' => 'Safe\apcu_dec',
            'apcu_fetch' => 'Safe\apcu_fetch',
            'apcu_inc' => 'Safe\apcu_inc',
            'apcu_sma_info' => 'Safe\apcu_sma_info',
            'base64_decode' => 'Safe\base64_decode',
            'bindtextdomain' => 'Safe\bindtextdomain',
            'bzclose' => 'Safe\bzclose',
            'bzflush' => 'Safe\bzflush',
            'bzopen' => 'Safe\bzopen',
            'bzread' => 'Safe\bzread',
            'bzwrite' => 'Safe\bzwrite',
            'chdir' => 'Safe\chdir',
            'chgrp' => 'Safe\chgrp',
            'chmod' => 'Safe\chmod',
            'chown' => 'Safe\chown',
            'chroot' => 'Safe\chroot',
            'class_alias' => 'Safe\class_alias',
            'class_implements' => 'Safe\class_implements',
            'class_parents' => 'Safe\class_parents',
            'class_uses' => 'Safe\class_uses',
            'cli_set_process_title' => 'Safe\cli_set_process_title',
            'com_create_guid' => 'Safe\com_create_guid',
            'com_event_sink' => 'Safe\com_event_sink',
            'com_load_typelib' => 'Safe\com_load_typelib',
            'com_print_typeinfo' => 'Safe\com_print_typeinfo',
            'convert_uudecode' => 'Safe\convert_uudecode',
            'copy' => 'Safe\copy',
            'cubrid_bind' => 'Safe\cubrid_bind',
            'cubrid_column_names' => 'Safe\cubrid_column_names',
            'cubrid_column_types' => 'Safe\cubrid_column_types',
            'cubrid_col_size' => 'Safe\cubrid_col_size',
            'cubrid_commit' => 'Safe\cubrid_commit',
            'cubrid_connect' => 'Safe\cubrid_connect',
            'cubrid_connect_with_url' => 'Safe\cubrid_connect_with_url',
            'cubrid_current_oid' => 'Safe\cubrid_current_oid',
            'cubrid_disconnect' => 'Safe\cubrid_disconnect',
            'cubrid_drop' => 'Safe\cubrid_drop',
            'cubrid_free_result' => 'Safe\cubrid_free_result',
            'cubrid_get_charset' => 'Safe\cubrid_get_charset',
            'cubrid_get_class_name' => 'Safe\cubrid_get_class_name',
            'cubrid_get_client_info' => 'Safe\cubrid_get_client_info',
            'cubrid_get_db_parameter' => 'Safe\cubrid_get_db_parameter',
            'cubrid_get_query_timeout' => 'Safe\cubrid_get_query_timeout',
            'cubrid_get_server_info' => 'Safe\cubrid_get_server_info',
            'cubrid_insert_id' => 'Safe\cubrid_insert_id',
            'cubrid_lob2_bind' => 'Safe\cubrid_lob2_bind',
            'cubrid_lob2_close' => 'Safe\cubrid_lob2_close',
            'cubrid_lob2_export' => 'Safe\cubrid_lob2_export',
            'cubrid_lob2_import' => 'Safe\cubrid_lob2_import',
            'cubrid_lob2_new' => 'Safe\cubrid_lob2_new',
            'cubrid_lob2_read' => 'Safe\cubrid_lob2_read',
            'cubrid_lob2_seek' => 'Safe\cubrid_lob2_seek',
            'cubrid_lob2_seek64' => 'Safe\cubrid_lob2_seek64',
            'cubrid_lob2_size' => 'Safe\cubrid_lob2_size',
            'cubrid_lob2_size64' => 'Safe\cubrid_lob2_size64',
            'cubrid_lob2_tell' => 'Safe\cubrid_lob2_tell',
            'cubrid_lob2_tell64' => 'Safe\cubrid_lob2_tell64',
            'cubrid_lob2_write' => 'Safe\cubrid_lob2_write',
            'cubrid_lob_close' => 'Safe\cubrid_lob_close',
            'cubrid_lob_export' => 'Safe\cubrid_lob_export',
            'cubrid_lob_get' => 'Safe\cubrid_lob_get',
            'cubrid_lob_send' => 'Safe\cubrid_lob_send',
            'cubrid_lob_size' => 'Safe\cubrid_lob_size',
            'cubrid_lock_read' => 'Safe\cubrid_lock_read',
            'cubrid_lock_write' => 'Safe\cubrid_lock_write',
            'cubrid_move_cursor' => 'Safe\cubrid_move_cursor',
            'cubrid_next_result' => 'Safe\cubrid_next_result',
            'cubrid_pconnect' => 'Safe\cubrid_pconnect',
            'cubrid_pconnect_with_url' => 'Safe\cubrid_pconnect_with_url',
            'cubrid_prepare' => 'Safe\cubrid_prepare',
            'cubrid_put' => 'Safe\cubrid_put',
            'cubrid_rollback' => 'Safe\cubrid_rollback',
            'cubrid_schema' => 'Safe\cubrid_schema',
            'cubrid_seq_drop' => 'Safe\cubrid_seq_drop',
            'cubrid_seq_insert' => 'Safe\cubrid_seq_insert',
            'cubrid_seq_put' => 'Safe\cubrid_seq_put',
            'cubrid_set_add' => 'Safe\cubrid_set_add',
            'cubrid_set_autocommit' => 'Safe\cubrid_set_autocommit',
            'cubrid_set_db_parameter' => 'Safe\cubrid_set_db_parameter',
            'cubrid_set_drop' => 'Safe\cubrid_set_drop',
            'cubrid_set_query_timeout' => 'Safe\cubrid_set_query_timeout',
            'curl_copy_handle' => 'Safe\curl_copy_handle',
            'curl_escape' => 'Safe\curl_escape',
            'curl_exec' => 'Safe\curl_exec',
            'curl_getinfo' => 'Safe\curl_getinfo',
            'curl_init' => 'Safe\curl_init',
            'curl_multi_info_read' => 'Safe\curl_multi_info_read',
            'curl_multi_setopt' => 'Safe\curl_multi_setopt',
            'curl_setopt' => 'Safe\curl_setopt',
            'curl_share_setopt' => 'Safe\curl_share_setopt',
            'curl_unescape' => 'Safe\curl_unescape',
            'curl_upkeep' => 'Safe\curl_upkeep',
            'date_create' => 'Safe\date_create',
            'date_create_immutable' => 'Safe\date_create_immutable',
            'date_parse_from_format' => 'Safe\date_parse_from_format',
            'date_sunrise' => 'Safe\date_sunrise',
            'date_sunset' => 'Safe\date_sunset',
            'db2_autocommit' => 'Safe\db2_autocommit',
            'db2_bind_param' => 'Safe\db2_bind_param',
            'db2_client_info' => 'Safe\db2_client_info',
            'db2_close' => 'Safe\db2_close',
            'db2_commit' => 'Safe\db2_commit',
            'db2_execute' => 'Safe\db2_execute',
            'db2_free_result' => 'Safe\db2_free_result',
            'db2_free_stmt' => 'Safe\db2_free_stmt',
            'db2_get_option' => 'Safe\db2_get_option',
            'db2_num_rows' => 'Safe\db2_num_rows',
            'db2_pclose' => 'Safe\db2_pclose',
            'db2_rollback' => 'Safe\db2_rollback',
            'db2_server_info' => 'Safe\db2_server_info',
            'db2_set_option' => 'Safe\db2_set_option',
            'define' => 'Safe\define',
            'deflate_add' => 'Safe\deflate_add',
            'deflate_init' => 'Safe\deflate_init',
            'dir' => 'Safe\dir',
            'disk_free_space' => 'Safe\disk_free_space',
            'disk_total_space' => 'Safe\disk_total_space',
            'dl' => 'Safe\dl',
            'dns_get_record' => 'Safe\dns_get_record',
            'eio_busy' => 'Safe\eio_busy',
            'eio_chmod' => 'Safe\eio_chmod',
            'eio_chown' => 'Safe\eio_chown',
            'eio_close' => 'Safe\eio_close',
            'eio_custom' => 'Safe\eio_custom',
            'eio_dup2' => 'Safe\eio_dup2',
            'eio_event_loop' => 'Safe\eio_event_loop',
            'eio_fallocate' => 'Safe\eio_fallocate',
            'eio_fchmod' => 'Safe\eio_fchmod',
            'eio_fchown' => 'Safe\eio_fchown',
            'eio_fdatasync' => 'Safe\eio_fdatasync',
            'eio_fstat' => 'Safe\eio_fstat',
            'eio_fstatvfs' => 'Safe\eio_fstatvfs',
            'eio_fsync' => 'Safe\eio_fsync',
            'eio_ftruncate' => 'Safe\eio_ftruncate',
            'eio_futime' => 'Safe\eio_futime',
            'eio_grp' => 'Safe\eio_grp',
            'eio_lstat' => 'Safe\eio_lstat',
            'eio_mkdir' => 'Safe\eio_mkdir',
            'eio_mknod' => 'Safe\eio_mknod',
            'eio_nop' => 'Safe\eio_nop',
            'eio_readahead' => 'Safe\eio_readahead',
            'eio_readdir' => 'Safe\eio_readdir',
            'eio_readlink' => 'Safe\eio_readlink',
            'eio_rename' => 'Safe\eio_rename',
            'eio_rmdir' => 'Safe\eio_rmdir',
            'eio_seek' => 'Safe\eio_seek',
            'eio_sendfile' => 'Safe\eio_sendfile',
            'eio_stat' => 'Safe\eio_stat',
            'eio_statvfs' => 'Safe\eio_statvfs',
            'eio_symlink' => 'Safe\eio_symlink',
            'eio_sync' => 'Safe\eio_sync',
            'eio_syncfs' => 'Safe\eio_syncfs',
            'eio_sync_file_range' => 'Safe\eio_sync_file_range',
            'eio_truncate' => 'Safe\eio_truncate',
            'eio_unlink' => 'Safe\eio_unlink',
            'eio_utime' => 'Safe\eio_utime',
            'eio_write' => 'Safe\eio_write',
            'error_log' => 'Safe\error_log',
            'exec' => 'Safe\exec',
            'fastcgi_finish_request' => 'Safe\fastcgi_finish_request',
            'fbird_blob_cancel' => 'Safe\fbird_blob_cancel',
            'fclose' => 'Safe\fclose',
            'fdatasync' => 'Safe\fdatasync',
            'fflush' => 'Safe\fflush',
            'fgetcsv' => 'Safe\fgetcsv',
            'file' => 'Safe\file',
            'fileatime' => 'Safe\fileatime',
            'filectime' => 'Safe\filectime',
            'fileinode' => 'Safe\fileinode',
            'filemtime' => 'Safe\filemtime',
            'fileowner' => 'Safe\fileowner',
            'fileperms' => 'Safe\fileperms',
            'filesize' => 'Safe\filesize',
            'filetype' => 'Safe\filetype',
            'file_get_contents' => 'Safe\file_get_contents',
            'file_put_contents' => 'Safe\file_put_contents',
            'filter_input_array' => 'Safe\filter_input_array',
            'filter_var_array' => 'Safe\filter_var_array',
            'finfo_close' => 'Safe\finfo_close',
            'finfo_open' => 'Safe\finfo_open',
            'flock' => 'Safe\flock',
            'fopen' => 'Safe\fopen',
            'fpm_get_status' => 'Safe\fpm_get_status',
            'fputcsv' => 'Safe\fputcsv',
            'fread' => 'Safe\fread',
            'fsockopen' => 'Safe\fsockopen',
            'fstat' => 'Safe\fstat',
            'fsync' => 'Safe\fsync',
            'ftell' => 'Safe\ftell',
            'ftp_alloc' => 'Safe\ftp_alloc',
            'ftp_append' => 'Safe\ftp_append',
            'ftp_cdup' => 'Safe\ftp_cdup',
            'ftp_chdir' => 'Safe\ftp_chdir',
            'ftp_chmod' => 'Safe\ftp_chmod',
            'ftp_close' => 'Safe\ftp_close',
            'ftp_connect' => 'Safe\ftp_connect',
            'ftp_delete' => 'Safe\ftp_delete',
            'ftp_fget' => 'Safe\ftp_fget',
            'ftp_fput' => 'Safe\ftp_fput',
            'ftp_get' => 'Safe\ftp_get',
            'ftp_login' => 'Safe\ftp_login',
            'ftp_mkdir' => 'Safe\ftp_mkdir',
            'ftp_mlsd' => 'Safe\ftp_mlsd',
            'ftp_nb_get' => 'Safe\ftp_nb_get',
            'ftp_nb_put' => 'Safe\ftp_nb_put',
            'ftp_nlist' => 'Safe\ftp_nlist',
            'ftp_pasv' => 'Safe\ftp_pasv',
            'ftp_put' => 'Safe\ftp_put',
            'ftp_pwd' => 'Safe\ftp_pwd',
            'ftp_raw' => 'Safe\ftp_raw',
            'ftp_rename' => 'Safe\ftp_rename',
            'ftp_rmdir' => 'Safe\ftp_rmdir',
            'ftp_site' => 'Safe\ftp_site',
            'ftp_size' => 'Safe\ftp_size',
            'ftp_ssl_connect' => 'Safe\ftp_ssl_connect',
            'ftp_systype' => 'Safe\ftp_systype',
            'ftruncate' => 'Safe\ftruncate',
            'fwrite' => 'Safe\fwrite',
            'getcwd' => 'Safe\getcwd',
            'gethostname' => 'Safe\gethostname',
            'getimagesize' => 'Safe\getimagesize',
            'getlastmod' => 'Safe\getlastmod',
            'getmygid' => 'Safe\getmygid',
            'getmyinode' => 'Safe\getmyinode',
            'getmypid' => 'Safe\getmypid',
            'getmyuid' => 'Safe\getmyuid',
            'getopt' => 'Safe\getopt',
            'getprotobyname' => 'Safe\getprotobyname',
            'getprotobynumber' => 'Safe\getprotobynumber',
            'getrusage' => 'Safe\getrusage',
            'getservbyport' => 'Safe\getservbyport',
            'get_cfg_var' => 'Safe\get_cfg_var',
            'get_headers' => 'Safe\get_headers',
            'get_include_path' => 'Safe\get_include_path',
            'get_meta_tags' => 'Safe\get_meta_tags',
            'glob' => 'Safe\glob',
            'gmmktime' => 'Safe\gmmktime',
            'gmstrftime' => 'Safe\gmstrftime',
            'gnupg_adddecryptkey' => 'Safe\gnupg_adddecryptkey',
            'gnupg_addencryptkey' => 'Safe\gnupg_addencryptkey',
            'gnupg_addsignkey' => 'Safe\gnupg_addsignkey',
            'gnupg_cleardecryptkeys' => 'Safe\gnupg_cleardecryptkeys',
            'gnupg_clearencryptkeys' => 'Safe\gnupg_clearencryptkeys',
            'gnupg_clearsignkeys' => 'Safe\gnupg_clearsignkeys',
            'gnupg_deletekey' => 'Safe\gnupg_deletekey',
            'gnupg_setarmor' => 'Safe\gnupg_setarmor',
            'gnupg_setsignmode' => 'Safe\gnupg_setsignmode',
            'gzclose' => 'Safe\gzclose',
            'gzcompress' => 'Safe\gzcompress',
            'gzdecode' => 'Safe\gzdecode',
            'gzdeflate' => 'Safe\gzdeflate',
            'gzencode' => 'Safe\gzencode',
            'gzfile' => 'Safe\gzfile',
            'gzgets' => 'Safe\gzgets',
            'gzinflate' => 'Safe\gzinflate',
            'gzopen' => 'Safe\gzopen',
            'gzread' => 'Safe\gzread',
            'gzrewind' => 'Safe\gzrewind',
            'gztell' => 'Safe\gztell',
            'gzuncompress' => 'Safe\gzuncompress',
            'gzwrite' => 'Safe\gzwrite',
            'hash_file' => 'Safe\hash_file',
            'hash_hmac_file' => 'Safe\hash_hmac_file',
            'hash_update_file' => 'Safe\hash_update_file',
            'header_register_callback' => 'Safe\header_register_callback',
            'hex2bin' => 'Safe\hex2bin',
            'highlight_file' => 'Safe\highlight_file',
            'hrtime' => 'Safe\hrtime',
            'ibase_add_user' => 'Safe\ibase_add_user',
            'ibase_backup' => 'Safe\ibase_backup',
            'ibase_blob_cancel' => 'Safe\ibase_blob_cancel',
            'ibase_blob_create' => 'Safe\ibase_blob_create',
            'ibase_blob_get' => 'Safe\ibase_blob_get',
            'ibase_close' => 'Safe\ibase_close',
            'ibase_commit' => 'Safe\ibase_commit',
            'ibase_commit_ret' => 'Safe\ibase_commit_ret',
            'ibase_connect' => 'Safe\ibase_connect',
            'ibase_delete_user' => 'Safe\ibase_delete_user',
            'ibase_drop_db' => 'Safe\ibase_drop_db',
            'ibase_free_event_handler' => 'Safe\ibase_free_event_handler',
            'ibase_free_query' => 'Safe\ibase_free_query',
            'ibase_free_result' => 'Safe\ibase_free_result',
            'ibase_maintain_db' => 'Safe\ibase_maintain_db',
            'ibase_modify_user' => 'Safe\ibase_modify_user',
            'ibase_name_result' => 'Safe\ibase_name_result',
            'ibase_pconnect' => 'Safe\ibase_pconnect',
            'ibase_restore' => 'Safe\ibase_restore',
            'ibase_rollback' => 'Safe\ibase_rollback',
            'ibase_rollback_ret' => 'Safe\ibase_rollback_ret',
            'ibase_service_attach' => 'Safe\ibase_service_attach',
            'ibase_service_detach' => 'Safe\ibase_service_detach',
            'iconv' => 'Safe\iconv',
            'iconv_get_encoding' => 'Safe\iconv_get_encoding',
            'iconv_mime_decode' => 'Safe\iconv_mime_decode',
            'iconv_mime_encode' => 'Safe\iconv_mime_encode',
            'iconv_set_encoding' => 'Safe\iconv_set_encoding',
            'iconv_strlen' => 'Safe\iconv_strlen',
            'idate' => 'Safe\idate',
            'imageaffine' => 'Safe\imageaffine',
            'imageaffinematrixconcat' => 'Safe\imageaffinematrixconcat',
            'imageaffinematrixget' => 'Safe\imageaffinematrixget',
            'imagealphablending' => 'Safe\imagealphablending',
            'imageantialias' => 'Safe\imageantialias',
            'imagearc' => 'Safe\imagearc',
            'imageavif' => 'Safe\imageavif',
            'imagebmp' => 'Safe\imagebmp',
            'imagechar' => 'Safe\imagechar',
            'imagecharup' => 'Safe\imagecharup',
            'imagecolorat' => 'Safe\imagecolorat',
            'imagecolordeallocate' => 'Safe\imagecolordeallocate',
            'imagecolormatch' => 'Safe\imagecolormatch',
            'imagecolorset' => 'Safe\imagecolorset',
            'imageconvolution' => 'Safe\imageconvolution',
            'imagecopy' => 'Safe\imagecopy',
            'imagecopymerge' => 'Safe\imagecopymerge',
            'imagecopymergegray' => 'Safe\imagecopymergegray',
            'imagecopyresampled' => 'Safe\imagecopyresampled',
            'imagecopyresized' => 'Safe\imagecopyresized',
            'imagecreate' => 'Safe\imagecreate',
            'imagecreatefromavif' => 'Safe\imagecreatefromavif',
            'imagecreatefrombmp' => 'Safe\imagecreatefrombmp',
            'imagecreatefromgd' => 'Safe\imagecreatefromgd',
            'imagecreatefromgd2' => 'Safe\imagecreatefromgd2',
            'imagecreatefromgd2part' => 'Safe\imagecreatefromgd2part',
            'imagecreatefromgif' => 'Safe\imagecreatefromgif',
            'imagecreatefromjpeg' => 'Safe\imagecreatefromjpeg',
            'imagecreatefrompng' => 'Safe\imagecreatefrompng',
            'imagecreatefromstring' => 'Safe\imagecreatefromstring',
            'imagecreatefromtga' => 'Safe\imagecreatefromtga',
            'imagecreatefromwbmp' => 'Safe\imagecreatefromwbmp',
            'imagecreatefromwebp' => 'Safe\imagecreatefromwebp',
            'imagecreatefromxbm' => 'Safe\imagecreatefromxbm',
            'imagecreatefromxpm' => 'Safe\imagecreatefromxpm',
            'imagecreatetruecolor' => 'Safe\imagecreatetruecolor',
            'imagecrop' => 'Safe\imagecrop',
            'imagecropauto' => 'Safe\imagecropauto',
            'imagedashedline' => 'Safe\imagedashedline',
            'imagedestroy' => 'Safe\imagedestroy',
            'imageellipse' => 'Safe\imageellipse',
            'imagefill' => 'Safe\imagefill',
            'imagefilledarc' => 'Safe\imagefilledarc',
            'imagefilledellipse' => 'Safe\imagefilledellipse',
            'imagefilledrectangle' => 'Safe\imagefilledrectangle',
            'imagefilltoborder' => 'Safe\imagefilltoborder',
            'imagefilter' => 'Safe\imagefilter',
            'imageflip' => 'Safe\imageflip',
            'imageftbbox' => 'Safe\imageftbbox',
            'imagefttext' => 'Safe\imagefttext',
            'imagegammacorrect' => 'Safe\imagegammacorrect',
            'imagegd' => 'Safe\imagegd',
            'imagegd2' => 'Safe\imagegd2',
            'imagegif' => 'Safe\imagegif',
            'imagegrabscreen' => 'Safe\imagegrabscreen',
            'imagegrabwindow' => 'Safe\imagegrabwindow',
            'imagejpeg' => 'Safe\imagejpeg',
            'imagelayereffect' => 'Safe\imagelayereffect',
            'imageline' => 'Safe\imageline',
            'imageloadfont' => 'Safe\imageloadfont',
            'imagepng' => 'Safe\imagepng',
            'imagerectangle' => 'Safe\imagerectangle',
            'imageresolution' => 'Safe\imageresolution',
            'imagerotate' => 'Safe\imagerotate',
            'imagesavealpha' => 'Safe\imagesavealpha',
            'imagescale' => 'Safe\imagescale',
            'imagesetbrush' => 'Safe\imagesetbrush',
            'imagesetclip' => 'Safe\imagesetclip',
            'imagesetinterpolation' => 'Safe\imagesetinterpolation',
            'imagesetpixel' => 'Safe\imagesetpixel',
            'imagesetstyle' => 'Safe\imagesetstyle',
            'imagesetthickness' => 'Safe\imagesetthickness',
            'imagesettile' => 'Safe\imagesettile',
            'imagestring' => 'Safe\imagestring',
            'imagestringup' => 'Safe\imagestringup',
            'imagetruecolortopalette' => 'Safe\imagetruecolortopalette',
            'imagettfbbox' => 'Safe\imagettfbbox',
            'imagettftext' => 'Safe\imagettftext',
            'imagewbmp' => 'Safe\imagewbmp',
            'imagewebp' => 'Safe\imagewebp',
            'imagexbm' => 'Safe\imagexbm',
            'image_type_to_extension' => 'Safe\image_type_to_extension',
            'imap_8bit' => 'Safe\imap_8bit',
            'imap_append' => 'Safe\imap_append',
            'imap_base64' => 'Safe\imap_base64',
            'imap_binary' => 'Safe\imap_binary',
            'imap_body' => 'Safe\imap_body',
            'imap_bodystruct' => 'Safe\imap_bodystruct',
            'imap_check' => 'Safe\imap_check',
            'imap_createmailbox' => 'Safe\imap_createmailbox',
            'imap_deletemailbox' => 'Safe\imap_deletemailbox',
            'imap_fetchbody' => 'Safe\imap_fetchbody',
            'imap_fetchheader' => 'Safe\imap_fetchheader',
            'imap_fetchmime' => 'Safe\imap_fetchmime',
            'imap_fetchstructure' => 'Safe\imap_fetchstructure',
            'imap_fetch_overview' => 'Safe\imap_fetch_overview',
            'imap_getacl' => 'Safe\imap_getacl',
            'imap_getmailboxes' => 'Safe\imap_getmailboxes',
            'imap_getsubscribed' => 'Safe\imap_getsubscribed',
            'imap_headerinfo' => 'Safe\imap_headerinfo',
            'imap_headers' => 'Safe\imap_headers',
            'imap_listscan' => 'Safe\imap_listscan',
            'imap_lsub' => 'Safe\imap_lsub',
            'imap_mail' => 'Safe\imap_mail',
            'imap_mail_compose' => 'Safe\imap_mail_compose',
            'imap_mail_copy' => 'Safe\imap_mail_copy',
            'imap_mail_move' => 'Safe\imap_mail_move',
            'imap_mime_header_decode' => 'Safe\imap_mime_header_decode',
            'imap_mutf7_to_utf8' => 'Safe\imap_mutf7_to_utf8',
            'imap_num_msg' => 'Safe\imap_num_msg',
            'imap_open' => 'Safe\imap_open',
            'imap_qprint' => 'Safe\imap_qprint',
            'imap_renamemailbox' => 'Safe\imap_renamemailbox',
            'imap_rfc822_write_address' => 'Safe\imap_rfc822_write_address',
            'imap_savebody' => 'Safe\imap_savebody',
            'imap_setacl' => 'Safe\imap_setacl',
            'imap_set_quota' => 'Safe\imap_set_quota',
            'imap_sort' => 'Safe\imap_sort',
            'imap_status' => 'Safe\imap_status',
            'imap_subscribe' => 'Safe\imap_subscribe',
            'imap_thread' => 'Safe\imap_thread',
            'imap_timeout' => 'Safe\imap_timeout',
            'imap_unsubscribe' => 'Safe\imap_unsubscribe',
            'imap_utf8_to_mutf7' => 'Safe\imap_utf8_to_mutf7',
            'inet_ntop' => 'Safe\inet_ntop',
            'inet_pton' => 'Safe\inet_pton',
            'inflate_add' => 'Safe\inflate_add',
            'inflate_get_read_len' => 'Safe\inflate_get_read_len',
            'inflate_init' => 'Safe\inflate_init',
            'ini_get' => 'Safe\ini_get',
            'ini_set' => 'Safe\ini_set',
            'inotify_add_watch' => 'Safe\inotify_add_watch',
            'inotify_init' => 'Safe\inotify_init',
            'inotify_rm_watch' => 'Safe\inotify_rm_watch',
            'iptcembed' => 'Safe\iptcembed',
            'iptcparse' => 'Safe\iptcparse',
            'json_decode' => 'Safe\json_decode',
            'json_encode' => 'Safe\json_encode',
            'lchgrp' => 'Safe\lchgrp',
            'lchown' => 'Safe\lchown',
            'ldap_8859_to_t61' => 'Safe\ldap_8859_to_t61',
            'ldap_add' => 'Safe\ldap_add',
            'ldap_bind' => 'Safe\ldap_bind',
            'ldap_compare' => 'Safe\ldap_compare',
            'ldap_control_paged_result' => 'Safe\ldap_control_paged_result',
            'ldap_control_paged_result_response' => 'Safe\ldap_control_paged_result_response',
            'ldap_count_entries' => 'Safe\ldap_count_entries',
            'ldap_delete' => 'Safe\ldap_delete',
            'ldap_dn2ufn' => 'Safe\ldap_dn2ufn',
            'ldap_exop' => 'Safe\ldap_exop',
            'ldap_exop_passwd' => 'Safe\ldap_exop_passwd',
            'ldap_exop_whoami' => 'Safe\ldap_exop_whoami',
            'ldap_explode_dn' => 'Safe\ldap_explode_dn',
            'ldap_first_attribute' => 'Safe\ldap_first_attribute',
            'ldap_first_entry' => 'Safe\ldap_first_entry',
            'ldap_free_result' => 'Safe\ldap_free_result',
            'ldap_get_dn' => 'Safe\ldap_get_dn',
            'ldap_get_entries' => 'Safe\ldap_get_entries',
            'ldap_get_option' => 'Safe\ldap_get_option',
            'ldap_get_values' => 'Safe\ldap_get_values',
            'ldap_get_values_len' => 'Safe\ldap_get_values_len',
            'ldap_modify_batch' => 'Safe\ldap_modify_batch',
            'ldap_mod_add' => 'Safe\ldap_mod_add',
            'ldap_mod_del' => 'Safe\ldap_mod_del',
            'ldap_mod_replace' => 'Safe\ldap_mod_replace',
            'ldap_next_attribute' => 'Safe\ldap_next_attribute',
            'ldap_parse_exop' => 'Safe\ldap_parse_exop',
            'ldap_parse_result' => 'Safe\ldap_parse_result',
            'ldap_rename' => 'Safe\ldap_rename',
            'ldap_sasl_bind' => 'Safe\ldap_sasl_bind',
            'ldap_set_option' => 'Safe\ldap_set_option',
            'ldap_unbind' => 'Safe\ldap_unbind',
            'libxml_set_external_entity_loader' => 'Safe\libxml_set_external_entity_loader',
            'link' => 'Safe\link',
            'lstat' => 'Safe\lstat',
            'lzf_compress' => 'Safe\lzf_compress',
            'lzf_decompress' => 'Safe\lzf_decompress',
            'mailparse_msg_extract_part_file' => 'Safe\mailparse_msg_extract_part_file',
            'mailparse_msg_free' => 'Safe\mailparse_msg_free',
            'mailparse_msg_parse' => 'Safe\mailparse_msg_parse',
            'mailparse_msg_parse_file' => 'Safe\mailparse_msg_parse_file',
            'mailparse_stream_encode' => 'Safe\mailparse_stream_encode',
            'mb_chr' => 'Safe\mb_chr',
            'mb_convert_encoding' => 'Safe\mb_convert_encoding',
            'mb_convert_variables' => 'Safe\mb_convert_variables',
            'mb_detect_order' => 'Safe\mb_detect_order',
            'mb_eregi_replace' => 'Safe\mb_eregi_replace',
            'mb_ereg_replace' => 'Safe\mb_ereg_replace',
            'mb_ereg_replace_callback' => 'Safe\mb_ereg_replace_callback',
            'mb_ereg_search_getregs' => 'Safe\mb_ereg_search_getregs',
            'mb_ereg_search_init' => 'Safe\mb_ereg_search_init',
            'mb_ereg_search_regs' => 'Safe\mb_ereg_search_regs',
            'mb_ereg_search_setpos' => 'Safe\mb_ereg_search_setpos',
            'mb_get_info' => 'Safe\mb_get_info',
            'mb_http_output' => 'Safe\mb_http_output',
            'mb_internal_encoding' => 'Safe\mb_internal_encoding',
            'mb_ord' => 'Safe\mb_ord',
            'mb_parse_str' => 'Safe\mb_parse_str',
            'mb_regex_encoding' => 'Safe\mb_regex_encoding',
            'mb_send_mail' => 'Safe\mb_send_mail',
            'mb_split' => 'Safe\mb_split',
            'md5_file' => 'Safe\md5_file',
            'mime_content_type' => 'Safe\mime_content_type',
            'mkdir' => 'Safe\mkdir',
            'mktime' => 'Safe\mktime',
            'msg_get_queue' => 'Safe\msg_get_queue',
            'msg_queue_exists' => 'Safe\msg_queue_exists',
            'msg_receive' => 'Safe\msg_receive',
            'msg_remove_queue' => 'Safe\msg_remove_queue',
            'msg_send' => 'Safe\msg_send',
            'msg_set_queue' => 'Safe\msg_set_queue',
            'msg_stat_queue' => 'Safe\msg_stat_queue',
            'mysql_close' => 'Safe\mysql_close',
            'mysql_connect' => 'Safe\mysql_connect',
            'mysql_create_db' => 'Safe\mysql_create_db',
            'mysql_data_seek' => 'Safe\mysql_data_seek',
            'mysql_db_name' => 'Safe\mysql_db_name',
            'mysql_db_query' => 'Safe\mysql_db_query',
            'mysql_drop_db' => 'Safe\mysql_drop_db',
            'mysql_fetch_lengths' => 'Safe\mysql_fetch_lengths',
            'mysql_field_flags' => 'Safe\mysql_field_flags',
            'mysql_field_len' => 'Safe\mysql_field_len',
            'mysql_field_name' => 'Safe\mysql_field_name',
            'mysql_field_seek' => 'Safe\mysql_field_seek',
            'mysql_free_result' => 'Safe\mysql_free_result',
            'mysql_get_host_info' => 'Safe\mysql_get_host_info',
            'mysql_get_proto_info' => 'Safe\mysql_get_proto_info',
            'mysql_get_server_info' => 'Safe\mysql_get_server_info',
            'mysql_info' => 'Safe\mysql_info',
            'mysql_list_dbs' => 'Safe\mysql_list_dbs',
            'mysql_list_fields' => 'Safe\mysql_list_fields',
            'mysql_list_processes' => 'Safe\mysql_list_processes',
            'mysql_list_tables' => 'Safe\mysql_list_tables',
            'mysql_num_fields' => 'Safe\mysql_num_fields',
            'mysql_num_rows' => 'Safe\mysql_num_rows',
            'mysql_query' => 'Safe\mysql_query',
            'mysql_real_escape_string' => 'Safe\mysql_real_escape_string',
            'mysql_result' => 'Safe\mysql_result',
            'mysql_select_db' => 'Safe\mysql_select_db',
            'mysql_set_charset' => 'Safe\mysql_set_charset',
            'mysql_tablename' => 'Safe\mysql_tablename',
            'mysql_thread_id' => 'Safe\mysql_thread_id',
            'mysql_unbuffered_query' => 'Safe\mysql_unbuffered_query',
            'net_get_interfaces' => 'Safe\net_get_interfaces',
            'ob_clean' => 'Safe\ob_clean',
            'ob_end_clean' => 'Safe\ob_end_clean',
            'ob_end_flush' => 'Safe\ob_end_flush',
            'ob_flush' => 'Safe\ob_flush',
            'ob_get_clean' => 'Safe\ob_get_clean',
            'ob_get_flush' => 'Safe\ob_get_flush',
            'ob_start' => 'Safe\ob_start',
            'oci_bind_array_by_name' => 'Safe\oci_bind_array_by_name',
            'oci_bind_by_name' => 'Safe\oci_bind_by_name',
            'oci_cancel' => 'Safe\oci_cancel',
            'oci_commit' => 'Safe\oci_commit',
            'oci_connect' => 'Safe\oci_connect',
            'oci_define_by_name' => 'Safe\oci_define_by_name',
            'oci_execute' => 'Safe\oci_execute',
            'oci_field_name' => 'Safe\oci_field_name',
            'oci_field_precision' => 'Safe\oci_field_precision',
            'oci_field_scale' => 'Safe\oci_field_scale',
            'oci_field_size' => 'Safe\oci_field_size',
            'oci_field_type' => 'Safe\oci_field_type',
            'oci_field_type_raw' => 'Safe\oci_field_type_raw',
            'oci_free_descriptor' => 'Safe\oci_free_descriptor',
            'oci_free_statement' => 'Safe\oci_free_statement',
            'oci_new_collection' => 'Safe\oci_new_collection',
            'oci_new_connect' => 'Safe\oci_new_connect',
            'oci_new_cursor' => 'Safe\oci_new_cursor',
            'oci_new_descriptor' => 'Safe\oci_new_descriptor',
            'oci_num_rows' => 'Safe\oci_num_rows',
            'oci_parse' => 'Safe\oci_parse',
            'oci_pconnect' => 'Safe\oci_pconnect',
            'oci_register_taf_callback' => 'Safe\oci_register_taf_callback',
            'oci_result' => 'Safe\oci_result',
            'oci_rollback' => 'Safe\oci_rollback',
            'oci_server_version' => 'Safe\oci_server_version',
            'oci_set_action' => 'Safe\oci_set_action',
            'oci_set_call_timeout' => 'Safe\oci_set_call_timeout',
            'oci_set_client_identifier' => 'Safe\oci_set_client_identifier',
            'oci_set_client_info' => 'Safe\oci_set_client_info',
            'oci_set_db_operation' => 'Safe\oci_set_db_operation',
            'oci_set_edition' => 'Safe\oci_set_edition',
            'oci_set_module_name' => 'Safe\oci_set_module_name',
            'oci_set_prefetch' => 'Safe\oci_set_prefetch',
            'oci_set_prefetch_lob' => 'Safe\oci_set_prefetch_lob',
            'oci_statement_type' => 'Safe\oci_statement_type',
            'oci_unregister_taf_callback' => 'Safe\oci_unregister_taf_callback',
            'odbc_autocommit' => 'Safe\odbc_autocommit',
            'odbc_commit' => 'Safe\odbc_commit',
            'odbc_connect' => 'Safe\odbc_connect',
            'odbc_cursor' => 'Safe\odbc_cursor',
            'odbc_data_source' => 'Safe\odbc_data_source',
            'odbc_exec' => 'Safe\odbc_exec',
            'odbc_execute' => 'Safe\odbc_execute',
            'odbc_fetch_into' => 'Safe\odbc_fetch_into',
            'odbc_field_len' => 'Safe\odbc_field_len',
            'odbc_field_name' => 'Safe\odbc_field_name',
            'odbc_field_num' => 'Safe\odbc_field_num',
            'odbc_field_scale' => 'Safe\odbc_field_scale',
            'odbc_field_type' => 'Safe\odbc_field_type',
            'odbc_num_fields' => 'Safe\odbc_num_fields',
            'odbc_pconnect' => 'Safe\odbc_pconnect',
            'odbc_prepare' => 'Safe\odbc_prepare',
            'odbc_procedures' => 'Safe\odbc_procedures',
            'odbc_result' => 'Safe\odbc_result',
            'odbc_result_all' => 'Safe\odbc_result_all',
            'odbc_rollback' => 'Safe\odbc_rollback',
            'odbc_setoption' => 'Safe\odbc_setoption',
            'odbc_tables' => 'Safe\odbc_tables',
            'opcache_compile_file' => 'Safe\opcache_compile_file',
            'opcache_get_status' => 'Safe\opcache_get_status',
            'opendir' => 'Safe\opendir',
            'openssl_cipher_iv_length' => 'Safe\openssl_cipher_iv_length',
            'openssl_cipher_key_length' => 'Safe\openssl_cipher_key_length',
            'openssl_cms_decrypt' => 'Safe\openssl_cms_decrypt',
            'openssl_cms_encrypt' => 'Safe\openssl_cms_encrypt',
            'openssl_cms_read' => 'Safe\openssl_cms_read',
            'openssl_cms_sign' => 'Safe\openssl_cms_sign',
            'openssl_cms_verify' => 'Safe\openssl_cms_verify',
            'openssl_csr_export' => 'Safe\openssl_csr_export',
            'openssl_csr_export_to_file' => 'Safe\openssl_csr_export_to_file',
            'openssl_csr_get_public_key' => 'Safe\openssl_csr_get_public_key',
            'openssl_csr_get_subject' => 'Safe\openssl_csr_get_subject',
            'openssl_csr_new' => 'Safe\openssl_csr_new',
            'openssl_csr_sign' => 'Safe\openssl_csr_sign',
            'openssl_decrypt' => 'Safe\openssl_decrypt',
            'openssl_dh_compute_key' => 'Safe\openssl_dh_compute_key',
            'openssl_digest' => 'Safe\openssl_digest',
            'openssl_encrypt' => 'Safe\openssl_encrypt',
            'openssl_get_curve_names' => 'Safe\openssl_get_curve_names',
            'openssl_open' => 'Safe\openssl_open',
            'openssl_pbkdf2' => 'Safe\openssl_pbkdf2',
            'openssl_pkcs7_decrypt' => 'Safe\openssl_pkcs7_decrypt',
            'openssl_pkcs7_encrypt' => 'Safe\openssl_pkcs7_encrypt',
            'openssl_pkcs7_read' => 'Safe\openssl_pkcs7_read',
            'openssl_pkcs7_sign' => 'Safe\openssl_pkcs7_sign',
            'openssl_pkcs12_export' => 'Safe\openssl_pkcs12_export',
            'openssl_pkcs12_export_to_file' => 'Safe\openssl_pkcs12_export_to_file',
            'openssl_pkcs12_read' => 'Safe\openssl_pkcs12_read',
            'openssl_pkey_derive' => 'Safe\openssl_pkey_derive',
            'openssl_pkey_export' => 'Safe\openssl_pkey_export',
            'openssl_pkey_export_to_file' => 'Safe\openssl_pkey_export_to_file',
            'openssl_pkey_get_details' => 'Safe\openssl_pkey_get_details',
            'openssl_pkey_get_private' => 'Safe\openssl_pkey_get_private',
            'openssl_pkey_get_public' => 'Safe\openssl_pkey_get_public',
            'openssl_pkey_new' => 'Safe\openssl_pkey_new',
            'openssl_private_decrypt' => 'Safe\openssl_private_decrypt',
            'openssl_private_encrypt' => 'Safe\openssl_private_encrypt',
            'openssl_public_decrypt' => 'Safe\openssl_public_decrypt',
            'openssl_public_encrypt' => 'Safe\openssl_public_encrypt',
            'openssl_seal' => 'Safe\openssl_seal',
            'openssl_sign' => 'Safe\openssl_sign',
            'openssl_spki_export' => 'Safe\openssl_spki_export',
            'openssl_spki_export_challenge' => 'Safe\openssl_spki_export_challenge',
            'openssl_spki_new' => 'Safe\openssl_spki_new',
            'openssl_spki_verify' => 'Safe\openssl_spki_verify',
            'openssl_verify' => 'Safe\openssl_verify',
            'openssl_x509_checkpurpose' => 'Safe\openssl_x509_checkpurpose',
            'openssl_x509_export' => 'Safe\openssl_x509_export',
            'openssl_x509_export_to_file' => 'Safe\openssl_x509_export_to_file',
            'openssl_x509_fingerprint' => 'Safe\openssl_x509_fingerprint',
            'openssl_x509_read' => 'Safe\openssl_x509_read',
            'output_add_rewrite_var' => 'Safe\output_add_rewrite_var',
            'output_reset_rewrite_vars' => 'Safe\output_reset_rewrite_vars',
            'parse_ini_file' => 'Safe\parse_ini_file',
            'parse_ini_string' => 'Safe\parse_ini_string',
            'parse_url' => 'Safe\parse_url',
            'passthru' => 'Safe\passthru',
            'pclose' => 'Safe\pclose',
            'pcntl_getcpuaffinity' => 'Safe\pcntl_getcpuaffinity',
            'pcntl_getpriority' => 'Safe\pcntl_getpriority',
            'pcntl_setcpuaffinity' => 'Safe\pcntl_setcpuaffinity',
            'pcntl_setpriority' => 'Safe\pcntl_setpriority',
            'pcntl_signal' => 'Safe\pcntl_signal',
            'pcntl_signal_dispatch' => 'Safe\pcntl_signal_dispatch',
            'pcntl_sigprocmask' => 'Safe\pcntl_sigprocmask',
            'pcntl_sigtimedwait' => 'Safe\pcntl_sigtimedwait',
            'pcntl_sigwaitinfo' => 'Safe\pcntl_sigwaitinfo',
            'pfsockopen' => 'Safe\pfsockopen',
            'pg_cancel_query' => 'Safe\pg_cancel_query',
            'pg_connect' => 'Safe\pg_connect',
            'pg_connection_reset' => 'Safe\pg_connection_reset',
            'pg_convert' => 'Safe\pg_convert',
            'pg_copy_from' => 'Safe\pg_copy_from',
            'pg_copy_to' => 'Safe\pg_copy_to',
            'pg_delete' => 'Safe\pg_delete',
            'pg_end_copy' => 'Safe\pg_end_copy',
            'pg_execute' => 'Safe\pg_execute',
            'pg_field_num' => 'Safe\pg_field_num',
            'pg_field_table' => 'Safe\pg_field_table',
            'pg_flush' => 'Safe\pg_flush',
            'pg_free_result' => 'Safe\pg_free_result',
            'pg_host' => 'Safe\pg_host',
            'pg_insert' => 'Safe\pg_insert',
            'pg_last_oid' => 'Safe\pg_last_oid',
            'pg_lo_close' => 'Safe\pg_lo_close',
            'pg_lo_export' => 'Safe\pg_lo_export',
            'pg_lo_import' => 'Safe\pg_lo_import',
            'pg_lo_open' => 'Safe\pg_lo_open',
            'pg_lo_read' => 'Safe\pg_lo_read',
            'pg_lo_seek' => 'Safe\pg_lo_seek',
            'pg_lo_truncate' => 'Safe\pg_lo_truncate',
            'pg_lo_unlink' => 'Safe\pg_lo_unlink',
            'pg_lo_write' => 'Safe\pg_lo_write',
            'pg_meta_data' => 'Safe\pg_meta_data',
            'pg_parameter_status' => 'Safe\pg_parameter_status',
            'pg_pconnect' => 'Safe\pg_pconnect',
            'pg_ping' => 'Safe\pg_ping',
            'pg_prepare' => 'Safe\pg_prepare',
            'pg_put_line' => 'Safe\pg_put_line',
            'pg_query' => 'Safe\pg_query',
            'pg_query_params' => 'Safe\pg_query_params',
            'pg_result_error_field' => 'Safe\pg_result_error_field',
            'pg_result_seek' => 'Safe\pg_result_seek',
            'pg_select' => 'Safe\pg_select',
            'pg_set_chunked_rows_size' => 'Safe\pg_set_chunked_rows_size',
            'pg_socket' => 'Safe\pg_socket',
            'pg_trace' => 'Safe\pg_trace',
            'pg_update' => 'Safe\pg_update',
            'php_sapi_name' => 'Safe\php_sapi_name',
            'popen' => 'Safe\popen',
            'posix_access' => 'Safe\posix_access',
            'posix_eaccess' => 'Safe\posix_eaccess',
            'posix_getgrgid' => 'Safe\posix_getgrgid',
            'posix_getgrnam' => 'Safe\posix_getgrnam',
            'posix_getgroups' => 'Safe\posix_getgroups',
            'posix_getlogin' => 'Safe\posix_getlogin',
            'posix_getpgid' => 'Safe\posix_getpgid',
            'posix_getpwuid' => 'Safe\posix_getpwuid',
            'posix_getrlimit' => 'Safe\posix_getrlimit',
            'posix_getsid' => 'Safe\posix_getsid',
            'posix_initgroups' => 'Safe\posix_initgroups',
            'posix_kill' => 'Safe\posix_kill',
            'posix_mkfifo' => 'Safe\posix_mkfifo',
            'posix_mknod' => 'Safe\posix_mknod',
            'posix_setegid' => 'Safe\posix_setegid',
            'posix_seteuid' => 'Safe\posix_seteuid',
            'posix_setgid' => 'Safe\posix_setgid',
            'posix_setpgid' => 'Safe\posix_setpgid',
            'posix_setrlimit' => 'Safe\posix_setrlimit',
            'posix_setsid' => 'Safe\posix_setsid',
            'posix_setuid' => 'Safe\posix_setuid',
            'posix_times' => 'Safe\posix_times',
            'posix_uname' => 'Safe\posix_uname',
            'preg_grep' => 'Safe\preg_grep',
            'preg_match' => 'Safe\preg_match',
            'preg_match_all' => 'Safe\preg_match_all',
            'preg_replace' => 'Safe\preg_replace',
            'preg_replace_callback' => 'Safe\preg_replace_callback',
            'preg_replace_callback_array' => 'Safe\preg_replace_callback_array',
            'preg_split' => 'Safe\preg_split',
            'proc_close' => 'Safe\proc_close',
            'proc_nice' => 'Safe\proc_nice',
            'proc_open' => 'Safe\proc_open',
            'pspell_add_to_personal' => 'Safe\pspell_add_to_personal',
            'pspell_add_to_session' => 'Safe\pspell_add_to_session',
            'pspell_clear_session' => 'Safe\pspell_clear_session',
            'pspell_config_data_dir' => 'Safe\pspell_config_data_dir',
            'pspell_config_dict_dir' => 'Safe\pspell_config_dict_dir',
            'pspell_config_ignore' => 'Safe\pspell_config_ignore',
            'pspell_config_mode' => 'Safe\pspell_config_mode',
            'pspell_config_personal' => 'Safe\pspell_config_personal',
            'pspell_config_repl' => 'Safe\pspell_config_repl',
            'pspell_config_runtogether' => 'Safe\pspell_config_runtogether',
            'pspell_config_save_repl' => 'Safe\pspell_config_save_repl',
            'pspell_new' => 'Safe\pspell_new',
            'pspell_new_config' => 'Safe\pspell_new_config',
            'pspell_new_personal' => 'Safe\pspell_new_personal',
            'pspell_save_wordlist' => 'Safe\pspell_save_wordlist',
            'pspell_store_replacement' => 'Safe\pspell_store_replacement',
            'ps_add_launchlink' => 'Safe\ps_add_launchlink',
            'ps_add_locallink' => 'Safe\ps_add_locallink',
            'ps_add_note' => 'Safe\ps_add_note',
            'ps_add_pdflink' => 'Safe\ps_add_pdflink',
            'ps_add_weblink' => 'Safe\ps_add_weblink',
            'ps_arc' => 'Safe\ps_arc',
            'ps_arcn' => 'Safe\ps_arcn',
            'ps_begin_page' => 'Safe\ps_begin_page',
            'ps_begin_pattern' => 'Safe\ps_begin_pattern',
            'ps_begin_template' => 'Safe\ps_begin_template',
            'ps_circle' => 'Safe\ps_circle',
            'ps_clip' => 'Safe\ps_clip',
            'ps_close' => 'Safe\ps_close',
            'ps_closepath' => 'Safe\ps_closepath',
            'ps_closepath_stroke' => 'Safe\ps_closepath_stroke',
            'ps_close_image' => 'Safe\ps_close_image',
            'ps_continue_text' => 'Safe\ps_continue_text',
            'ps_curveto' => 'Safe\ps_curveto',
            'ps_delete' => 'Safe\ps_delete',
            'ps_end_page' => 'Safe\ps_end_page',
            'ps_end_pattern' => 'Safe\ps_end_pattern',
            'ps_end_template' => 'Safe\ps_end_template',
            'ps_fill' => 'Safe\ps_fill',
            'ps_fill_stroke' => 'Safe\ps_fill_stroke',
            'ps_get_parameter' => 'Safe\ps_get_parameter',
            'ps_hyphenate' => 'Safe\ps_hyphenate',
            'ps_include_file' => 'Safe\ps_include_file',
            'ps_lineto' => 'Safe\ps_lineto',
            'ps_moveto' => 'Safe\ps_moveto',
            'ps_new' => 'Safe\ps_new',
            'ps_open_file' => 'Safe\ps_open_file',
            'ps_place_image' => 'Safe\ps_place_image',
            'ps_rect' => 'Safe\ps_rect',
            'ps_restore' => 'Safe\ps_restore',
            'ps_rotate' => 'Safe\ps_rotate',
            'ps_save' => 'Safe\ps_save',
            'ps_scale' => 'Safe\ps_scale',
            'ps_setcolor' => 'Safe\ps_setcolor',
            'ps_setdash' => 'Safe\ps_setdash',
            'ps_setflat' => 'Safe\ps_setflat',
            'ps_setfont' => 'Safe\ps_setfont',
            'ps_setgray' => 'Safe\ps_setgray',
            'ps_setlinecap' => 'Safe\ps_setlinecap',
            'ps_setlinejoin' => 'Safe\ps_setlinejoin',
            'ps_setlinewidth' => 'Safe\ps_setlinewidth',
            'ps_setmiterlimit' => 'Safe\ps_setmiterlimit',
            'ps_setoverprintmode' => 'Safe\ps_setoverprintmode',
            'ps_setpolydash' => 'Safe\ps_setpolydash',
            'ps_set_border_color' => 'Safe\ps_set_border_color',
            'ps_set_border_dash' => 'Safe\ps_set_border_dash',
            'ps_set_border_style' => 'Safe\ps_set_border_style',
            'ps_set_info' => 'Safe\ps_set_info',
            'ps_set_parameter' => 'Safe\ps_set_parameter',
            'ps_set_text_pos' => 'Safe\ps_set_text_pos',
            'ps_set_value' => 'Safe\ps_set_value',
            'ps_shading' => 'Safe\ps_shading',
            'ps_shading_pattern' => 'Safe\ps_shading_pattern',
            'ps_shfill' => 'Safe\ps_shfill',
            'ps_show' => 'Safe\ps_show',
            'ps_show2' => 'Safe\ps_show2',
            'ps_show_xy' => 'Safe\ps_show_xy',
            'ps_show_xy2' => 'Safe\ps_show_xy2',
            'ps_stroke' => 'Safe\ps_stroke',
            'ps_symbol' => 'Safe\ps_symbol',
            'ps_translate' => 'Safe\ps_translate',
            'putenv' => 'Safe\putenv',
            'readfile' => 'Safe\readfile',
            'readgzfile' => 'Safe\readgzfile',
            'readline_completion_function' => 'Safe\readline_completion_function',
            'readline_read_history' => 'Safe\readline_read_history',
            'readline_write_history' => 'Safe\readline_write_history',
            'readlink' => 'Safe\readlink',
            'realpath' => 'Safe\realpath',
            'register_tick_function' => 'Safe\register_tick_function',
            'rename' => 'Safe\rename',
            'rewind' => 'Safe\rewind',
            'rmdir' => 'Safe\rmdir',
            'rnp_decrypt' => 'Safe\rnp_decrypt',
            'rnp_dump_packets' => 'Safe\rnp_dump_packets',
            'rnp_dump_packets_to_json' => 'Safe\rnp_dump_packets_to_json',
            'rnp_ffi_create' => 'Safe\rnp_ffi_create',
            'rnp_ffi_set_pass_provider' => 'Safe\rnp_ffi_set_pass_provider',
            'rnp_import_keys' => 'Safe\rnp_import_keys',
            'rnp_import_signatures' => 'Safe\rnp_import_signatures',
            'rnp_key_export' => 'Safe\rnp_key_export',
            'rnp_key_export_autocrypt' => 'Safe\rnp_key_export_autocrypt',
            'rnp_key_export_revocation' => 'Safe\rnp_key_export_revocation',
            'rnp_key_get_info' => 'Safe\rnp_key_get_info',
            'rnp_key_remove' => 'Safe\rnp_key_remove',
            'rnp_key_revoke' => 'Safe\rnp_key_revoke',
            'rnp_list_keys' => 'Safe\rnp_list_keys',
            'rnp_load_keys' => 'Safe\rnp_load_keys',
            'rnp_load_keys_from_path' => 'Safe\rnp_load_keys_from_path',
            'rnp_locate_key' => 'Safe\rnp_locate_key',
            'rnp_op_encrypt' => 'Safe\rnp_op_encrypt',
            'rnp_op_generate_key' => 'Safe\rnp_op_generate_key',
            'rnp_op_sign' => 'Safe\rnp_op_sign',
            'rnp_op_sign_cleartext' => 'Safe\rnp_op_sign_cleartext',
            'rnp_op_sign_detached' => 'Safe\rnp_op_sign_detached',
            'rnp_op_verify' => 'Safe\rnp_op_verify',
            'rnp_op_verify_detached' => 'Safe\rnp_op_verify_detached',
            'rnp_save_keys' => 'Safe\rnp_save_keys',
            'rnp_save_keys_to_path' => 'Safe\rnp_save_keys_to_path',
            'rnp_supported_features' => 'Safe\rnp_supported_features',
            'rpmaddtag' => 'Safe\rpmaddtag',
            'rpmdefine' => 'Safe\rpmdefine',
            'rrd_create' => 'Safe\rrd_create',
            'rrd_first' => 'Safe\rrd_first',
            'rrd_graph' => 'Safe\rrd_graph',
            'rrd_info' => 'Safe\rrd_info',
            'rrd_lastupdate' => 'Safe\rrd_lastupdate',
            'rrd_restore' => 'Safe\rrd_restore',
            'rrd_tune' => 'Safe\rrd_tune',
            'rrd_update' => 'Safe\rrd_update',
            'rrd_xport' => 'Safe\rrd_xport',
            'sapi_windows_cp_conv' => 'Safe\sapi_windows_cp_conv',
            'sapi_windows_cp_set' => 'Safe\sapi_windows_cp_set',
            'sapi_windows_generate_ctrl_event' => 'Safe\sapi_windows_generate_ctrl_event',
            'sapi_windows_set_ctrl_handler' => 'Safe\sapi_windows_set_ctrl_handler',
            'sapi_windows_vt100_support' => 'Safe\sapi_windows_vt100_support',
            'scandir' => 'Safe\scandir',
            'sem_acquire' => 'Safe\sem_acquire',
            'sem_get' => 'Safe\sem_get',
            'sem_release' => 'Safe\sem_release',
            'sem_remove' => 'Safe\sem_remove',
            'session_abort' => 'Safe\session_abort',
            'session_cache_expire' => 'Safe\session_cache_expire',
            'session_cache_limiter' => 'Safe\session_cache_limiter',
            'session_create_id' => 'Safe\session_create_id',
            'session_decode' => 'Safe\session_decode',
            'session_destroy' => 'Safe\session_destroy',
            'session_encode' => 'Safe\session_encode',
            'session_gc' => 'Safe\session_gc',
            'session_id' => 'Safe\session_id',
            'session_module_name' => 'Safe\session_module_name',
            'session_name' => 'Safe\session_name',
            'session_regenerate_id' => 'Safe\session_regenerate_id',
            'session_reset' => 'Safe\session_reset',
            'session_save_path' => 'Safe\session_save_path',
            'session_start' => 'Safe\session_start',
            'session_unset' => 'Safe\session_unset',
            'session_write_close' => 'Safe\session_write_close',
            'settype' => 'Safe\settype',
            'set_include_path' => 'Safe\set_include_path',
            'set_time_limit' => 'Safe\set_time_limit',
            'sha1_file' => 'Safe\sha1_file',
            'shell_exec' => 'Safe\shell_exec',
            'shmop_delete' => 'Safe\shmop_delete',
            'shm_attach' => 'Safe\shm_attach',
            'shm_detach' => 'Safe\shm_detach',
            'shm_put_var' => 'Safe\shm_put_var',
            'shm_remove' => 'Safe\shm_remove',
            'shm_remove_var' => 'Safe\shm_remove_var',
            'simplexml_import_dom' => 'Safe\simplexml_import_dom',
            'simplexml_load_file' => 'Safe\simplexml_load_file',
            'simplexml_load_string' => 'Safe\simplexml_load_string',
            'socket_accept' => 'Safe\socket_accept',
            'socket_addrinfo_bind' => 'Safe\socket_addrinfo_bind',
            'socket_addrinfo_connect' => 'Safe\socket_addrinfo_connect',
            'socket_addrinfo_lookup' => 'Safe\socket_addrinfo_lookup',
            'socket_atmark' => 'Safe\socket_atmark',
            'socket_bind' => 'Safe\socket_bind',
            'socket_connect' => 'Safe\socket_connect',
            'socket_create' => 'Safe\socket_create',
            'socket_create_listen' => 'Safe\socket_create_listen',
            'socket_create_pair' => 'Safe\socket_create_pair',
            'socket_export_stream' => 'Safe\socket_export_stream',
            'socket_getpeername' => 'Safe\socket_getpeername',
            'socket_getsockname' => 'Safe\socket_getsockname',
            'socket_get_option' => 'Safe\socket_get_option',
            'socket_import_stream' => 'Safe\socket_import_stream',
            'socket_listen' => 'Safe\socket_listen',
            'socket_read' => 'Safe\socket_read',
            'socket_send' => 'Safe\socket_send',
            'socket_sendmsg' => 'Safe\socket_sendmsg',
            'socket_sendto' => 'Safe\socket_sendto',
            'socket_set_block' => 'Safe\socket_set_block',
            'socket_set_nonblock' => 'Safe\socket_set_nonblock',
            'socket_set_option' => 'Safe\socket_set_option',
            'socket_shutdown' => 'Safe\socket_shutdown',
            'socket_write' => 'Safe\socket_write',
            'socket_wsaprotocol_info_export' => 'Safe\socket_wsaprotocol_info_export',
            'socket_wsaprotocol_info_import' => 'Safe\socket_wsaprotocol_info_import',
            'socket_wsaprotocol_info_release' => 'Safe\socket_wsaprotocol_info_release',
            'sodium_crypto_aead_aegis128l_decrypt' => 'Safe\sodium_crypto_aead_aegis128l_decrypt',
            'sodium_crypto_aead_aegis256_decrypt' => 'Safe\sodium_crypto_aead_aegis256_decrypt',
            'sodium_crypto_aead_aes256gcm_decrypt' => 'Safe\sodium_crypto_aead_aes256gcm_decrypt',
            'sodium_crypto_aead_chacha20poly1305_decrypt' => 'Safe\sodium_crypto_aead_chacha20poly1305_decrypt',
            'sodium_crypto_aead_chacha20poly1305_encrypt' => 'Safe\sodium_crypto_aead_chacha20poly1305_encrypt',
            'sodium_crypto_aead_chacha20poly1305_ietf_decrypt' => 'Safe\sodium_crypto_aead_chacha20poly1305_ietf_decrypt',
            'sodium_crypto_aead_chacha20poly1305_ietf_encrypt' => 'Safe\sodium_crypto_aead_chacha20poly1305_ietf_encrypt',
            'sodium_crypto_aead_xchacha20poly1305_ietf_decrypt' => 'Safe\sodium_crypto_aead_xchacha20poly1305_ietf_decrypt',
            'sodium_crypto_aead_xchacha20poly1305_ietf_encrypt' => 'Safe\sodium_crypto_aead_xchacha20poly1305_ietf_encrypt',
            'sodium_crypto_box_open' => 'Safe\sodium_crypto_box_open',
            'sodium_crypto_box_seal_open' => 'Safe\sodium_crypto_box_seal_open',
            'sodium_crypto_secretbox_open' => 'Safe\sodium_crypto_secretbox_open',
            'sodium_crypto_sign_open' => 'Safe\sodium_crypto_sign_open',
            'sodium_crypto_stream_xchacha20_xor_ic' => 'Safe\sodium_crypto_stream_xchacha20_xor_ic',
            'solr_get_version' => 'Safe\solr_get_version',
            'spl_autoload_register' => 'Safe\spl_autoload_register',
            'spl_autoload_unregister' => 'Safe\spl_autoload_unregister',
            'sqlsrv_begin_transaction' => 'Safe\sqlsrv_begin_transaction',
            'sqlsrv_cancel' => 'Safe\sqlsrv_cancel',
            'sqlsrv_client_info' => 'Safe\sqlsrv_client_info',
            'sqlsrv_close' => 'Safe\sqlsrv_close',
            'sqlsrv_commit' => 'Safe\sqlsrv_commit',
            'sqlsrv_configure' => 'Safe\sqlsrv_configure',
            'sqlsrv_execute' => 'Safe\sqlsrv_execute',
            'sqlsrv_fetch' => 'Safe\sqlsrv_fetch',
            'sqlsrv_fetch_array' => 'Safe\sqlsrv_fetch_array',
            'sqlsrv_fetch_object' => 'Safe\sqlsrv_fetch_object',
            'sqlsrv_free_stmt' => 'Safe\sqlsrv_free_stmt',
            'sqlsrv_get_field' => 'Safe\sqlsrv_get_field',
            'sqlsrv_next_result' => 'Safe\sqlsrv_next_result',
            'sqlsrv_num_fields' => 'Safe\sqlsrv_num_fields',
            'sqlsrv_num_rows' => 'Safe\sqlsrv_num_rows',
            'sqlsrv_prepare' => 'Safe\sqlsrv_prepare',
            'sqlsrv_query' => 'Safe\sqlsrv_query',
            'sqlsrv_rollback' => 'Safe\sqlsrv_rollback',
            'ssdeep_fuzzy_compare' => 'Safe\ssdeep_fuzzy_compare',
            'ssdeep_fuzzy_hash' => 'Safe\ssdeep_fuzzy_hash',
            'ssdeep_fuzzy_hash_filename' => 'Safe\ssdeep_fuzzy_hash_filename',
            'ssh2_auth_agent' => 'Safe\ssh2_auth_agent',
            'ssh2_auth_hostbased_file' => 'Safe\ssh2_auth_hostbased_file',
            'ssh2_auth_password' => 'Safe\ssh2_auth_password',
            'ssh2_auth_pubkey_file' => 'Safe\ssh2_auth_pubkey_file',
            'ssh2_connect' => 'Safe\ssh2_connect',
            'ssh2_disconnect' => 'Safe\ssh2_disconnect',
            'ssh2_exec' => 'Safe\ssh2_exec',
            'ssh2_forward_accept' => 'Safe\ssh2_forward_accept',
            'ssh2_forward_listen' => 'Safe\ssh2_forward_listen',
            'ssh2_publickey_add' => 'Safe\ssh2_publickey_add',
            'ssh2_publickey_init' => 'Safe\ssh2_publickey_init',
            'ssh2_publickey_remove' => 'Safe\ssh2_publickey_remove',
            'ssh2_scp_recv' => 'Safe\ssh2_scp_recv',
            'ssh2_scp_send' => 'Safe\ssh2_scp_send',
            'ssh2_send_eof' => 'Safe\ssh2_send_eof',
            'ssh2_sftp' => 'Safe\ssh2_sftp',
            'ssh2_sftp_chmod' => 'Safe\ssh2_sftp_chmod',
            'ssh2_sftp_mkdir' => 'Safe\ssh2_sftp_mkdir',
            'ssh2_sftp_rename' => 'Safe\ssh2_sftp_rename',
            'ssh2_sftp_rmdir' => 'Safe\ssh2_sftp_rmdir',
            'ssh2_sftp_symlink' => 'Safe\ssh2_sftp_symlink',
            'ssh2_sftp_unlink' => 'Safe\ssh2_sftp_unlink',
            'ssh2_shell' => 'Safe\ssh2_shell',
            'stream_context_set_options' => 'Safe\stream_context_set_options',
            'stream_context_set_params' => 'Safe\stream_context_set_params',
            'stream_copy_to_stream' => 'Safe\stream_copy_to_stream',
            'stream_filter_append' => 'Safe\stream_filter_append',
            'stream_filter_prepend' => 'Safe\stream_filter_prepend',
            'stream_filter_register' => 'Safe\stream_filter_register',
            'stream_filter_remove' => 'Safe\stream_filter_remove',
            'stream_get_contents' => 'Safe\stream_get_contents',
            'stream_get_line' => 'Safe\stream_get_line',
            'stream_isatty' => 'Safe\stream_isatty',
            'stream_resolve_include_path' => 'Safe\stream_resolve_include_path',
            'stream_set_blocking' => 'Safe\stream_set_blocking',
            'stream_set_timeout' => 'Safe\stream_set_timeout',
            'stream_socket_accept' => 'Safe\stream_socket_accept',
            'stream_socket_client' => 'Safe\stream_socket_client',
            'stream_socket_get_name' => 'Safe\stream_socket_get_name',
            'stream_socket_pair' => 'Safe\stream_socket_pair',
            'stream_socket_recvfrom' => 'Safe\stream_socket_recvfrom',
            'stream_socket_sendto' => 'Safe\stream_socket_sendto',
            'stream_socket_server' => 'Safe\stream_socket_server',
            'stream_socket_shutdown' => 'Safe\stream_socket_shutdown',
            'stream_supports_lock' => 'Safe\stream_supports_lock',
            'stream_wrapper_register' => 'Safe\stream_wrapper_register',
            'stream_wrapper_restore' => 'Safe\stream_wrapper_restore',
            'stream_wrapper_unregister' => 'Safe\stream_wrapper_unregister',
            'strftime' => 'Safe\strftime',
            'strptime' => 'Safe\strptime',
            'strtotime' => 'Safe\strtotime',
            'swoole_async_dns_lookup' => 'Safe\swoole_async_dns_lookup',
            'swoole_async_readfile' => 'Safe\swoole_async_readfile',
            'swoole_async_write' => 'Safe\swoole_async_write',
            'swoole_async_writefile' => 'Safe\swoole_async_writefile',
            'swoole_event_defer' => 'Safe\swoole_event_defer',
            'swoole_event_del' => 'Safe\swoole_event_del',
            'swoole_event_write' => 'Safe\swoole_event_write',
            'symlink' => 'Safe\symlink',
            'system' => 'Safe\system',
            'sys_getloadavg' => 'Safe\sys_getloadavg',
            'tempnam' => 'Safe\tempnam',
            'timezone_name_from_abbr' => 'Safe\timezone_name_from_abbr',
            'time_nanosleep' => 'Safe\time_nanosleep',
            'time_sleep_until' => 'Safe\time_sleep_until',
            'tmpfile' => 'Safe\tmpfile',
            'touch' => 'Safe\touch',
            'unixtojd' => 'Safe\unixtojd',
            'unlink' => 'Safe\unlink',
            'unpack' => 'Safe\unpack',
            'unserialize' => 'Safe\unserialize',
            'uopz_extend' => 'Safe\uopz_extend',
            'uopz_implement' => 'Safe\uopz_implement',
            'variant_date_to_timestamp' => 'Safe\variant_date_to_timestamp',
            'variant_round' => 'Safe\variant_round',
            'virtual' => 'Safe\virtual',
            'xdiff_file_bdiff' => 'Safe\xdiff_file_bdiff',
            'xdiff_file_bpatch' => 'Safe\xdiff_file_bpatch',
            'xdiff_file_diff' => 'Safe\xdiff_file_diff',
            'xdiff_file_diff_binary' => 'Safe\xdiff_file_diff_binary',
            'xdiff_file_patch_binary' => 'Safe\xdiff_file_patch_binary',
            'xdiff_file_rabdiff' => 'Safe\xdiff_file_rabdiff',
            'xdiff_string_bpatch' => 'Safe\xdiff_string_bpatch',
            'xdiff_string_patch' => 'Safe\xdiff_string_patch',
            'xdiff_string_patch_binary' => 'Safe\xdiff_string_patch_binary',
            'xdiff_string_rabdiff' => 'Safe\xdiff_string_rabdiff',
            'xmlrpc_set_type' => 'Safe\xmlrpc_set_type',
            'xml_parser_free' => 'Safe\xml_parser_free',
            'xml_parser_set_option' => 'Safe\xml_parser_set_option',
            'yaml_parse' => 'Safe\yaml_parse',
            'yaml_parse_file' => 'Safe\yaml_parse_file',
            'yaml_parse_url' => 'Safe\yaml_parse_url',
            'yaz_ccl_parse' => 'Safe\yaz_ccl_parse',
            'yaz_close' => 'Safe\yaz_close',
            'yaz_connect' => 'Safe\yaz_connect',
            'yaz_database' => 'Safe\yaz_database',
            'yaz_element' => 'Safe\yaz_element',
            'yaz_present' => 'Safe\yaz_present',
            'yaz_search' => 'Safe\yaz_search',
            'yaz_wait' => 'Safe\yaz_wait',
            'zip_entry_close' => 'Safe\zip_entry_close',
            'zip_entry_compressedsize' => 'Safe\zip_entry_compressedsize',
            'zip_entry_compressionmethod' => 'Safe\zip_entry_compressionmethod',
            'zip_entry_filesize' => 'Safe\zip_entry_filesize',
            'zip_entry_name' => 'Safe\zip_entry_name',
            'zip_entry_open' => 'Safe\zip_entry_open',
            'zip_entry_read' => 'Safe\zip_entry_read',
            'zlib_decode' => 'Safe\zlib_decode',
        ]
    );
};
<?php

namespace Safe;

use Safe\Exceptions\RpminfoException;

/**
 * @param int $tag
 * @throws RpminfoException
 *
 */
function rpmaddtag(int $tag): void
{
    error_clear_last();
    $safeResult = \rpmaddtag($tag);
    if ($safeResult === false) {
        throw RpminfoException::createFromPhpError();
    }
}


/**
 * @param string $text
 * @throws RpminfoException
 *
 */
function rpmdefine(string $text): void
{
    error_clear_last();
    $safeResult = \rpmdefine($text);
    if ($safeResult === false) {
        throw RpminfoException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\ZlibException;

/**
 * @param \DeflateContext $context
 * @param string $data
 * @param int $flush_mode
 * @return string
 * @throws ZlibException
 *
 */
function deflate_add(\DeflateContext $context, string $data, int $flush_mode = ZLIB_SYNC_FLUSH): string
{
    error_clear_last();
    $safeResult = \deflate_add($context, $data, $flush_mode);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $encoding
 * @param array $options
 * @return \DeflateContext
 * @throws ZlibException
 *
 */
function deflate_init(int $encoding, array $options = []): \DeflateContext
{
    error_clear_last();
    $safeResult = \deflate_init($encoding, $options);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @throws ZlibException
 *
 */
function gzclose($stream): void
{
    error_clear_last();
    $safeResult = \gzclose($stream);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
}


/**
 * @param string $data
 * @param int $level
 * @param int $encoding
 * @return string
 * @throws ZlibException
 *
 */
function gzcompress(string $data, int $level = -1, int $encoding = ZLIB_ENCODING_DEFLATE): string
{
    error_clear_last();
    $safeResult = \gzcompress($data, $level, $encoding);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param int $max_length
 * @return string
 * @throws ZlibException
 *
 */
function gzdecode(string $data, int $max_length = 0): string
{
    error_clear_last();
    $safeResult = \gzdecode($data, $max_length);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param int $level
 * @param int $encoding
 * @return string
 * @throws ZlibException
 *
 */
function gzdeflate(string $data, int $level = -1, int $encoding = ZLIB_ENCODING_RAW): string
{
    error_clear_last();
    $safeResult = \gzdeflate($data, $level, $encoding);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param int $level
 * @param int $encoding
 * @return string
 * @throws ZlibException
 *
 */
function gzencode(string $data, int $level = -1, int $encoding = ZLIB_ENCODING_GZIP): string
{
    error_clear_last();
    $safeResult = \gzencode($data, $level, $encoding);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param  $use_include_path
 * @return list
 * @throws ZlibException
 *
 */
function gzfile(string $filename, $use_include_path = 0): array
{
    error_clear_last();
    $safeResult = \gzfile($filename, $use_include_path);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @param int|null $length
 * @return string
 * @throws ZlibException
 *
 */
function gzgets($stream, ?int $length = null): string
{
    error_clear_last();
    if ($length !== null) {
        $safeResult = \gzgets($stream, $length);
    } else {
        $safeResult = \gzgets($stream);
    }
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param int $max_length
 * @return string
 * @throws ZlibException
 *
 */
function gzinflate(string $data, int $max_length = 0): string
{
    error_clear_last();
    $safeResult = \gzinflate($data, $max_length);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param string $mode
 * @param  $use_include_path
 * @return resource
 * @throws ZlibException
 *
 */
function gzopen(string $filename, string $mode, $use_include_path = 0)
{
    error_clear_last();
    $safeResult = \gzopen($filename, $mode, $use_include_path);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @return false|int
 *
 */
function gzpassthru($stream)
{
    error_clear_last();
    $safeResult = \gzpassthru($stream);
    return $safeResult;
}


/**
 * @param resource $stream
 * @param int $length
 * @return string
 * @throws ZlibException
 *
 */
function gzread($stream, int $length): string
{
    error_clear_last();
    $safeResult = \gzread($stream, $length);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @throws ZlibException
 *
 */
function gzrewind($stream): void
{
    error_clear_last();
    $safeResult = \gzrewind($stream);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
}


/**
 * @param resource $stream
 * @return int
 * @throws ZlibException
 *
 */
function gztell($stream): int
{
    error_clear_last();
    $safeResult = \gztell($stream);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param int $max_length
 * @return string
 * @throws ZlibException
 *
 */
function gzuncompress(string $data, int $max_length = 0): string
{
    error_clear_last();
    $safeResult = \gzuncompress($data, $max_length);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @param string $data
 * @param int|null $length
 * @return int
 * @throws ZlibException
 *
 */
function gzwrite($stream, string $data, ?int $length = null): int
{
    error_clear_last();
    if ($length !== null) {
        $safeResult = \gzwrite($stream, $data, $length);
    } else {
        $safeResult = \gzwrite($stream, $data);
    }
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \InflateContext $context
 * @return int
 * @throws ZlibException
 *
 */
function inflate_get_read_len(\InflateContext $context): int
{
    error_clear_last();
    $safeResult = \inflate_get_read_len($context);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \InflateContext $context
 * @return false|int
 *
 */
function inflate_get_status(\InflateContext $context)
{
    error_clear_last();
    $safeResult = \inflate_get_status($context);
    return $safeResult;
}


/**
 * @param \InflateContext $context
 * @param string $data
 * @param int $flush_mode
 * @return string
 * @throws ZlibException
 *
 */
function inflate_add(\InflateContext $context, string $data, int $flush_mode = ZLIB_SYNC_FLUSH): string
{
    error_clear_last();
    $safeResult = \inflate_add($context, $data, $flush_mode);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $encoding
 * @param array $options
 * @return \InflateContext
 * @throws ZlibException
 *
 */
function inflate_init(int $encoding, array $options = []): \InflateContext
{
    error_clear_last();
    $safeResult = \inflate_init($encoding, $options);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param  $use_include_path
 * @return 0|positive-int
 * @throws ZlibException
 *
 */
function readgzfile(string $filename, $use_include_path = false): int
{
    error_clear_last();
    $safeResult = \readgzfile($filename, $use_include_path);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param int $max_length
 * @return string
 * @throws ZlibException
 *
 */
function zlib_decode(string $data, int $max_length = 0): string
{
    error_clear_last();
    $safeResult = \zlib_decode($data, $max_length);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\ShmopException;

/**
 * @param \Shmop $shmop
 * @throws ShmopException
 *
 */
function shmop_delete(\Shmop $shmop): void
{
    error_clear_last();
    $safeResult = \shmop_delete($shmop);
    if ($safeResult === false) {
        throw ShmopException::createFromPhpError();
    }
}


/**
 * @param \Shmop $shmop
 * @param int $offset
 * @param int $size
 * @return string
 *
 */
function shmop_read(\Shmop $shmop, int $offset, int $size): string
{
    error_clear_last();
    $safeResult = \shmop_read($shmop, $offset, $size);
    return $safeResult;
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/zlib.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/zlib.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/zlib.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.4/zlib.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.5/zlib.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.6/zlib.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/image.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.2/image.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.2/image.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.4/image.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.4/image.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.4/image.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/spl.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/spl.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/spl.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/spl.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/spl.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/spl.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/shmop.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/shmop.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/shmop.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/shmop.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.5/shmop.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.5/shmop.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/sem.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/sem.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/sem.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/sem.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/sem.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/sem.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/rrd.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/rrd.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/rrd.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/rrd.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/rrd.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/rrd.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/exec.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.2/exec.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.2/exec.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.2/exec.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.2/exec.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.2/exec.php';
}
<?php

namespace Safe;

use Safe\Exceptions\LibxmlException;

/**
 * @param callable $resolver_function
 * @throws LibxmlException
 *
 */
function libxml_set_external_entity_loader(callable $resolver_function): void
{
    error_clear_last();
    $safeResult = \libxml_set_external_entity_loader($resolver_function);
    if ($safeResult === false) {
        throw LibxmlException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\UodbcException;

/**
 * @param resource $odbc
 * @param bool $enable
 * @return mixed
 * @throws UodbcException
 *
 */
function odbc_autocommit($odbc, bool $enable = false)
{
    error_clear_last();
    $safeResult = \odbc_autocommit($odbc, $enable);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $statement
 * @param int $mode
 * @throws UodbcException
 *
 */
function odbc_binmode(int $statement, int $mode): void
{
    error_clear_last();
    $safeResult = \odbc_binmode($statement, $mode);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
}


/**
 * @param resource $odbc
 * @param string $catalog
 * @param string $schema
 * @param string $table
 * @param string $column
 * @return resource
 * @throws UodbcException
 *
 */
function odbc_columnprivileges($odbc, string $catalog, string $schema, string $table, string $column)
{
    error_clear_last();
    $safeResult = \odbc_columnprivileges($odbc, $catalog, $schema, $table, $column);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $odbc
 * @param null|string $catalog
 * @param null|string $schema
 * @param null|string $table
 * @param null|string $column
 * @return resource
 * @throws UodbcException
 *
 */
function odbc_columns($odbc, ?string $catalog = null, ?string $schema = null, ?string $table = null, ?string $column = null)
{
    error_clear_last();
    if ($column !== null) {
        $safeResult = \odbc_columns($odbc, $catalog, $schema, $table, $column);
    } elseif ($table !== null) {
        $safeResult = \odbc_columns($odbc, $catalog, $schema, $table);
    } elseif ($schema !== null) {
        $safeResult = \odbc_columns($odbc, $catalog, $schema);
    } elseif ($catalog !== null) {
        $safeResult = \odbc_columns($odbc, $catalog);
    } else {
        $safeResult = \odbc_columns($odbc);
    }
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $odbc
 * @throws UodbcException
 *
 */
function odbc_commit($odbc): void
{
    error_clear_last();
    $safeResult = \odbc_commit($odbc);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
}


/**
 * @param string $dsn
 * @param string $user
 * @param string $password
 * @param int $cursor_option
 * @return resource
 * @throws UodbcException
 *
 */
function odbc_connect(string $dsn, string $user, string $password, int $cursor_option = SQL_CUR_USE_DRIVER)
{
    error_clear_last();
    $safeResult = \odbc_connect($dsn, $user, $password, $cursor_option);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $statement
 * @return string
 * @throws UodbcException
 *
 */
function odbc_cursor($statement): string
{
    error_clear_last();
    $safeResult = \odbc_cursor($statement);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $odbc
 * @param int $fetch_type
 * @return array
 * @throws UodbcException
 *
 */
function odbc_data_source($odbc, int $fetch_type): array
{
    error_clear_last();
    $safeResult = \odbc_data_source($odbc, $fetch_type);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $odbc
 * @param string $query
 * @return resource
 * @throws UodbcException
 *
 */
function odbc_exec($odbc, string $query)
{
    error_clear_last();
    $safeResult = \odbc_exec($odbc, $query);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $statement
 * @param array $params
 * @throws UodbcException
 *
 */
function odbc_execute($statement, array $params = []): void
{
    error_clear_last();
    $safeResult = \odbc_execute($statement, $params);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
}


/**
 * @param resource $statement
 * @param array|null $array
 * @param int $row
 * @return int
 * @throws UodbcException
 *
 */
function odbc_fetch_into($statement, ?array &$array, int $row = 0): int
{
    error_clear_last();
    $safeResult = \odbc_fetch_into($statement, $array, $row);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $statement
 * @param int $field
 * @return int
 * @throws UodbcException
 *
 */
function odbc_field_len($statement, int $field): int
{
    error_clear_last();
    $safeResult = \odbc_field_len($statement, $field);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $statement
 * @param int $field
 * @return string
 * @throws UodbcException
 *
 */
function odbc_field_name($statement, int $field): string
{
    error_clear_last();
    $safeResult = \odbc_field_name($statement, $field);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $statement
 * @param string $field
 * @return int
 * @throws UodbcException
 *
 */
function odbc_field_num($statement, string $field): int
{
    error_clear_last();
    $safeResult = \odbc_field_num($statement, $field);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $statement
 * @param int $field
 * @return int
 * @throws UodbcException
 *
 */
function odbc_field_scale($statement, int $field): int
{
    error_clear_last();
    $safeResult = \odbc_field_scale($statement, $field);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $statement
 * @param int $field
 * @return string
 * @throws UodbcException
 *
 */
function odbc_field_type($statement, int $field): string
{
    error_clear_last();
    $safeResult = \odbc_field_type($statement, $field);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $odbc
 * @param string $pk_catalog
 * @param string $pk_schema
 * @param string $pk_table
 * @param string $fk_catalog
 * @param string $fk_schema
 * @param string $fk_table
 * @return resource
 * @throws UodbcException
 *
 */
function odbc_foreignkeys($odbc, string $pk_catalog, string $pk_schema, string $pk_table, string $fk_catalog, string $fk_schema, string $fk_table)
{
    error_clear_last();
    $safeResult = \odbc_foreignkeys($odbc, $pk_catalog, $pk_schema, $pk_table, $fk_catalog, $fk_schema, $fk_table);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $odbc
 * @param int $data_type
 * @return resource
 * @throws UodbcException
 *
 */
function odbc_gettypeinfo($odbc, int $data_type = 0)
{
    error_clear_last();
    $safeResult = \odbc_gettypeinfo($odbc, $data_type);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $statement
 * @param int $length
 * @throws UodbcException
 *
 */
function odbc_longreadlen($statement, int $length): void
{
    error_clear_last();
    $safeResult = \odbc_longreadlen($statement, $length);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
}


/**
 * @param resource $statement
 * @return int
 * @throws UodbcException
 *
 */
function odbc_num_fields($statement): int
{
    error_clear_last();
    $safeResult = \odbc_num_fields($statement);
    if ($safeResult === -1) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $dsn
 * @param string $user
 * @param string $password
 * @param int $cursor_option
 * @return resource
 * @throws UodbcException
 *
 */
function odbc_pconnect(string $dsn, string $user, string $password, int $cursor_option = SQL_CUR_USE_DRIVER)
{
    error_clear_last();
    $safeResult = \odbc_pconnect($dsn, $user, $password, $cursor_option);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $odbc
 * @param string $query
 * @return resource
 * @throws UodbcException
 *
 */
function odbc_prepare($odbc, string $query)
{
    error_clear_last();
    $safeResult = \odbc_prepare($odbc, $query);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $odbc
 * @param string $catalog
 * @param string $schema
 * @param string $table
 * @return resource
 * @throws UodbcException
 *
 */
function odbc_primarykeys($odbc, string $catalog, string $schema, string $table)
{
    error_clear_last();
    $safeResult = \odbc_primarykeys($odbc, $catalog, $schema, $table);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param  $odbc
 * @param null|string $catalog
 * @param null|string $schema
 * @param null|string $procedure
 * @param null|string $column
 * @return resource
 * @throws UodbcException
 *
 */
function odbc_procedurecolumns($odbc, ?string $catalog = null, ?string $schema = null, ?string $procedure = null, ?string $column = null)
{
    error_clear_last();
    if ($column !== null) {
        $safeResult = \odbc_procedurecolumns($odbc, $catalog, $schema, $procedure, $column);
    } elseif ($procedure !== null) {
        $safeResult = \odbc_procedurecolumns($odbc, $catalog, $schema, $procedure);
    } elseif ($schema !== null) {
        $safeResult = \odbc_procedurecolumns($odbc, $catalog, $schema);
    } elseif ($catalog !== null) {
        $safeResult = \odbc_procedurecolumns($odbc, $catalog);
    } else {
        $safeResult = \odbc_procedurecolumns($odbc);
    }
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param  $odbc
 * @param null|string $catalog
 * @param null|string $schema
 * @param null|string $procedure
 * @return resource
 * @throws UodbcException
 *
 */
function odbc_procedures($odbc, ?string $catalog = null, ?string $schema = null, ?string $procedure = null)
{
    error_clear_last();
    if ($procedure !== null) {
        $safeResult = \odbc_procedures($odbc, $catalog, $schema, $procedure);
    } elseif ($schema !== null) {
        $safeResult = \odbc_procedures($odbc, $catalog, $schema);
    } elseif ($catalog !== null) {
        $safeResult = \odbc_procedures($odbc, $catalog);
    } else {
        $safeResult = \odbc_procedures($odbc);
    }
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $statement
 * @param string $format
 * @return int
 * @throws UodbcException
 *
 */
function odbc_result_all($statement, string $format = ""): int
{
    error_clear_last();
    $safeResult = \odbc_result_all($statement, $format);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $statement
 * @param mixed $field
 * @return mixed
 * @throws UodbcException
 *
 */
function odbc_result($statement, $field)
{
    error_clear_last();
    $safeResult = \odbc_result($statement, $field);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $odbc
 * @throws UodbcException
 *
 */
function odbc_rollback($odbc): void
{
    error_clear_last();
    $safeResult = \odbc_rollback($odbc);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
}


/**
 * @param resource $odbc
 * @param int $which
 * @param int $option
 * @param int $value
 * @throws UodbcException
 *
 */
function odbc_setoption($odbc, int $which, int $option, int $value): void
{
    error_clear_last();
    $safeResult = \odbc_setoption($odbc, $which, $option, $value);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
}


/**
 * @param resource $odbc
 * @param int $type
 * @param string $catalog
 * @param string $schema
 * @param string $table
 * @param int $scope
 * @param int $nullable
 * @return resource
 * @throws UodbcException
 *
 */
function odbc_specialcolumns($odbc, int $type, string $catalog, string $schema, string $table, int $scope, int $nullable)
{
    error_clear_last();
    $safeResult = \odbc_specialcolumns($odbc, $type, $catalog, $schema, $table, $scope, $nullable);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $odbc
 * @param string $catalog
 * @param string $schema
 * @param string $table
 * @param int $unique
 * @param int $accuracy
 * @return resource
 * @throws UodbcException
 *
 */
function odbc_statistics($odbc, string $catalog, string $schema, string $table, int $unique, int $accuracy)
{
    error_clear_last();
    $safeResult = \odbc_statistics($odbc, $catalog, $schema, $table, $unique, $accuracy);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $odbc
 * @param string $catalog
 * @param string $schema
 * @param string $table
 * @return resource
 * @throws UodbcException
 *
 */
function odbc_tableprivileges($odbc, string $catalog, string $schema, string $table)
{
    error_clear_last();
    $safeResult = \odbc_tableprivileges($odbc, $catalog, $schema, $table);
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $odbc
 * @param null|string $catalog
 * @param null|string $schema
 * @param null|string $table
 * @param null|string $types
 * @return resource
 * @throws UodbcException
 *
 */
function odbc_tables($odbc, ?string $catalog = null, ?string $schema = null, ?string $table = null, ?string $types = null)
{
    error_clear_last();
    if ($types !== null) {
        $safeResult = \odbc_tables($odbc, $catalog, $schema, $table, $types);
    } elseif ($table !== null) {
        $safeResult = \odbc_tables($odbc, $catalog, $schema, $table);
    } elseif ($schema !== null) {
        $safeResult = \odbc_tables($odbc, $catalog, $schema);
    } elseif ($catalog !== null) {
        $safeResult = \odbc_tables($odbc, $catalog);
    } else {
        $safeResult = \odbc_tables($odbc);
    }
    if ($safeResult === false) {
        throw UodbcException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\XdiffException;

/**
 * @param string $old_file
 * @param string $new_file
 * @param string $dest
 * @throws XdiffException
 *
 */
function xdiff_file_bdiff(string $old_file, string $new_file, string $dest): void
{
    error_clear_last();
    $safeResult = \xdiff_file_bdiff($old_file, $new_file, $dest);
    if ($safeResult === false) {
        throw XdiffException::createFromPhpError();
    }
}


/**
 * @param string $file
 * @param string $patch
 * @param string $dest
 * @throws XdiffException
 *
 */
function xdiff_file_bpatch(string $file, string $patch, string $dest): void
{
    error_clear_last();
    $safeResult = \xdiff_file_bpatch($file, $patch, $dest);
    if ($safeResult === false) {
        throw XdiffException::createFromPhpError();
    }
}


/**
 * @param string $old_file
 * @param string $new_file
 * @param string $dest
 * @throws XdiffException
 *
 */
function xdiff_file_diff_binary(string $old_file, string $new_file, string $dest): void
{
    error_clear_last();
    $safeResult = \xdiff_file_diff_binary($old_file, $new_file, $dest);
    if ($safeResult === false) {
        throw XdiffException::createFromPhpError();
    }
}


/**
 * @param string $old_file
 * @param string $new_file
 * @param string $dest
 * @param int $context
 * @param bool $minimal
 * @throws XdiffException
 *
 */
function xdiff_file_diff(string $old_file, string $new_file, string $dest, int $context = 3, bool $minimal = false): void
{
    error_clear_last();
    $safeResult = \xdiff_file_diff($old_file, $new_file, $dest, $context, $minimal);
    if ($safeResult === false) {
        throw XdiffException::createFromPhpError();
    }
}


/**
 * @param string $file
 * @param string $patch
 * @param string $dest
 * @throws XdiffException
 *
 */
function xdiff_file_patch_binary(string $file, string $patch, string $dest): void
{
    error_clear_last();
    $safeResult = \xdiff_file_patch_binary($file, $patch, $dest);
    if ($safeResult === false) {
        throw XdiffException::createFromPhpError();
    }
}


/**
 * @param string $old_file
 * @param string $new_file
 * @param string $dest
 * @throws XdiffException
 *
 */
function xdiff_file_rabdiff(string $old_file, string $new_file, string $dest): void
{
    error_clear_last();
    $safeResult = \xdiff_file_rabdiff($old_file, $new_file, $dest);
    if ($safeResult === false) {
        throw XdiffException::createFromPhpError();
    }
}


/**
 * @param string $str
 * @param string $patch
 * @return string
 * @throws XdiffException
 *
 */
function xdiff_string_bpatch(string $str, string $patch): string
{
    error_clear_last();
    $safeResult = \xdiff_string_bpatch($str, $patch);
    if ($safeResult === false) {
        throw XdiffException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $str
 * @param string $patch
 * @return string
 * @throws XdiffException
 *
 */
function xdiff_string_patch_binary(string $str, string $patch): string
{
    error_clear_last();
    $safeResult = \xdiff_string_patch_binary($str, $patch);
    if ($safeResult === false) {
        throw XdiffException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $str
 * @param string $patch
 * @param int $flags
 * @param null|string $error
 * @return string
 * @throws XdiffException
 *
 */
function xdiff_string_patch(string $str, string $patch, ?int $flags = null, ?string &$error = null): string
{
    error_clear_last();
    if ($error !== null) {
        $safeResult = \xdiff_string_patch($str, $patch, $flags, $error);
    } elseif ($flags !== null) {
        $safeResult = \xdiff_string_patch($str, $patch, $flags);
    } else {
        $safeResult = \xdiff_string_patch($str, $patch);
    }
    if ($safeResult === false) {
        throw XdiffException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\PgsqlException;

/**
 * @param \PgSql\Connection $connection
 * @throws PgsqlException
 *
 */
function pg_cancel_query(\PgSql\Connection $connection): void
{
    error_clear_last();
    $safeResult = \pg_cancel_query($connection);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
}


/**
 * @param string $connection_string
 * @param int $flags
 * @return \PgSql\Connection
 * @throws PgsqlException
 *
 */
function pg_connect(string $connection_string, int $flags = 0): \PgSql\Connection
{
    error_clear_last();
    $safeResult = \pg_connect($connection_string, $flags);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Connection $connection
 * @throws PgsqlException
 *
 */
function pg_connection_reset(\PgSql\Connection $connection): void
{
    error_clear_last();
    $safeResult = \pg_connection_reset($connection);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
}


/**
 * @param \PgSql\Connection $connection
 * @param string $table_name
 * @param array $values
 * @param int $flags
 * @return array
 * @throws PgsqlException
 *
 */
function pg_convert(\PgSql\Connection $connection, string $table_name, array $values, int $flags = 0): array
{
    error_clear_last();
    $safeResult = \pg_convert($connection, $table_name, $values, $flags);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Connection $connection
 * @param string $table_name
 * @param array $rows
 * @param string $separator
 * @param string $null_as
 * @throws PgsqlException
 *
 */
function pg_copy_from(\PgSql\Connection $connection, string $table_name, array $rows, string $separator = "\t", string $null_as = "\\\\N"): void
{
    error_clear_last();
    $safeResult = \pg_copy_from($connection, $table_name, $rows, $separator, $null_as);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
}


/**
 * @param \PgSql\Connection $connection
 * @param string $table_name
 * @param string $separator
 * @param string $null_as
 * @return array
 * @throws PgsqlException
 *
 */
function pg_copy_to(\PgSql\Connection $connection, string $table_name, string $separator = "\t", string $null_as = "\\\\N"): array
{
    error_clear_last();
    $safeResult = \pg_copy_to($connection, $table_name, $separator, $null_as);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Connection $connection
 * @param string $table_name
 * @param array $conditions
 * @param int $flags
 * @return mixed
 * @throws PgsqlException
 *
 */
function pg_delete(\PgSql\Connection $connection, string $table_name, array $conditions, int $flags = PGSQL_DML_EXEC)
{
    error_clear_last();
    $safeResult = \pg_delete($connection, $table_name, $conditions, $flags);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Connection|null $connection
 * @throws PgsqlException
 *
 */
function pg_end_copy(?\PgSql\Connection $connection = null): void
{
    error_clear_last();
    if ($connection !== null) {
        $safeResult = \pg_end_copy($connection);
    } else {
        $safeResult = \pg_end_copy();
    }
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
}


/**
 * @param \PgSql\Connection $connection
 * @param string $stmtname
 * @param array $params
 * @return \PgSql\Result
 * @throws PgsqlException
 *
 */
function pg_execute(?\PgSql\Connection $connection = null, ?string $stmtname = null, ?array $params = null): \PgSql\Result
{
    error_clear_last();
    if ($params !== null) {
        $safeResult = \pg_execute($connection, $stmtname, $params);
    } elseif ($stmtname !== null) {
        $safeResult = \pg_execute($connection, $stmtname);
    } elseif ($connection !== null) {
        $safeResult = \pg_execute($connection);
    } else {
        $safeResult = \pg_execute();
    }
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Result $result
 * @param string $field
 * @return int
 * @throws PgsqlException
 *
 */
function pg_field_num(\PgSql\Result $result, string $field): int
{
    error_clear_last();
    $safeResult = \pg_field_num($result, $field);
    if ($safeResult === -1) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Result $result
 * @param int $field
 * @param bool $oid_only
 * @return mixed
 * @throws PgsqlException
 *
 */
function pg_field_table(\PgSql\Result $result, int $field, bool $oid_only = false)
{
    error_clear_last();
    $safeResult = \pg_field_table($result, $field, $oid_only);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Connection $connection
 * @return mixed
 * @throws PgsqlException
 *
 */
function pg_flush(\PgSql\Connection $connection)
{
    error_clear_last();
    $safeResult = \pg_flush($connection);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Result $result
 * @throws PgsqlException
 *
 */
function pg_free_result(\PgSql\Result $result): void
{
    error_clear_last();
    $safeResult = \pg_free_result($result);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
}


/**
 * @param \PgSql\Connection|null $connection
 * @return string
 * @throws PgsqlException
 *
 */
function pg_host(?\PgSql\Connection $connection = null): string
{
    error_clear_last();
    if ($connection !== null) {
        $safeResult = \pg_host($connection);
    } else {
        $safeResult = \pg_host();
    }
    if ($safeResult === '') {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Connection $connection
 * @param string $table_name
 * @param array $values
 * @param int $flags
 * @return mixed
 * @throws PgsqlException
 *
 */
function pg_insert(\PgSql\Connection $connection, string $table_name, array $values, int $flags = PGSQL_DML_EXEC)
{
    error_clear_last();
    $safeResult = \pg_insert($connection, $table_name, $values, $flags);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Result $result
 * @return string
 * @throws PgsqlException
 *
 */
function pg_last_oid(\PgSql\Result $result): string
{
    error_clear_last();
    $safeResult = \pg_last_oid($result);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Lob $lob
 * @throws PgsqlException
 *
 */
function pg_lo_close(\PgSql\Lob $lob): void
{
    error_clear_last();
    $safeResult = \pg_lo_close($lob);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
}


/**
 * @param \PgSql\Connection $connection
 * @param int $oid
 * @param string $pathname
 * @throws PgsqlException
 *
 */
function pg_lo_export(?\PgSql\Connection $connection = null, ?int $oid = null, ?string $pathname = null): void
{
    error_clear_last();
    if ($pathname !== null) {
        $safeResult = \pg_lo_export($connection, $oid, $pathname);
    } elseif ($oid !== null) {
        $safeResult = \pg_lo_export($connection, $oid);
    } elseif ($connection !== null) {
        $safeResult = \pg_lo_export($connection);
    } else {
        $safeResult = \pg_lo_export();
    }
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
}


/**
 * @param \PgSql\Connection $connection
 * @param string $pathname
 * @param  $object_id
 * @return int
 * @throws PgsqlException
 *
 */
function pg_lo_import(?\PgSql\Connection $connection = null, ?string $pathname = null, $object_id = null): int
{
    error_clear_last();
    if ($object_id !== null) {
        $safeResult = \pg_lo_import($connection, $pathname, $object_id);
    } elseif ($pathname !== null) {
        $safeResult = \pg_lo_import($connection, $pathname);
    } elseif ($connection !== null) {
        $safeResult = \pg_lo_import($connection);
    } else {
        $safeResult = \pg_lo_import();
    }
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Connection $connection
 * @param int $oid
 * @param string $mode
 * @return \PgSql\Lob
 * @throws PgsqlException
 *
 */
function pg_lo_open(\PgSql\Connection $connection, int $oid, string $mode): \PgSql\Lob
{
    error_clear_last();
    $safeResult = \pg_lo_open($connection, $oid, $mode);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Lob $lob
 * @param int $length
 * @return string
 * @throws PgsqlException
 *
 */
function pg_lo_read(\PgSql\Lob $lob, int $length = 8192): string
{
    error_clear_last();
    $safeResult = \pg_lo_read($lob, $length);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Lob $lob
 * @param int $offset
 * @param int $whence
 * @throws PgsqlException
 *
 */
function pg_lo_seek(\PgSql\Lob $lob, int $offset, int $whence = SEEK_CUR): void
{
    error_clear_last();
    $safeResult = \pg_lo_seek($lob, $offset, $whence);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
}


/**
 * @param \PgSql\Lob $lob
 * @param int $size
 * @throws PgsqlException
 *
 */
function pg_lo_truncate(\PgSql\Lob $lob, int $size): void
{
    error_clear_last();
    $safeResult = \pg_lo_truncate($lob, $size);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
}


/**
 * @param \PgSql\Connection $connection
 * @param int $oid
 * @throws PgsqlException
 *
 */
function pg_lo_unlink(\PgSql\Connection $connection, int $oid): void
{
    error_clear_last();
    $safeResult = \pg_lo_unlink($connection, $oid);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
}


/**
 * @param \PgSql\Lob $lob
 * @param string $data
 * @param int|null $length
 * @return int
 * @throws PgsqlException
 *
 */
function pg_lo_write(\PgSql\Lob $lob, string $data, ?int $length = null): int
{
    error_clear_last();
    if ($length !== null) {
        $safeResult = \pg_lo_write($lob, $data, $length);
    } else {
        $safeResult = \pg_lo_write($lob, $data);
    }
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Connection $connection
 * @param string $table_name
 * @param bool $extended
 * @return array
 * @throws PgsqlException
 *
 */
function pg_meta_data(\PgSql\Connection $connection, string $table_name, bool $extended = false): array
{
    error_clear_last();
    $safeResult = \pg_meta_data($connection, $table_name, $extended);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Connection $connection
 * @param string $param_name
 * @return string
 * @throws PgsqlException
 *
 */
function pg_parameter_status(?\PgSql\Connection $connection = null, ?string $param_name = null): string
{
    error_clear_last();
    if ($param_name !== null) {
        $safeResult = \pg_parameter_status($connection, $param_name);
    } elseif ($connection !== null) {
        $safeResult = \pg_parameter_status($connection);
    } else {
        $safeResult = \pg_parameter_status();
    }
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $connection_string
 * @param int $flags
 * @return \PgSql\Connection
 * @throws PgsqlException
 *
 */
function pg_pconnect(string $connection_string, int $flags = 0): \PgSql\Connection
{
    error_clear_last();
    $safeResult = \pg_pconnect($connection_string, $flags);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Connection|null $connection
 * @throws PgsqlException
 *
 */
function pg_ping(?\PgSql\Connection $connection = null): void
{
    error_clear_last();
    if ($connection !== null) {
        $safeResult = \pg_ping($connection);
    } else {
        $safeResult = \pg_ping();
    }
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
}


/**
 * @param \PgSql\Connection $connection
 * @param string $stmtname
 * @param string $query
 * @return \PgSql\Result
 * @throws PgsqlException
 *
 */
function pg_prepare(?\PgSql\Connection $connection = null, ?string $stmtname = null, ?string $query = null): \PgSql\Result
{
    error_clear_last();
    if ($query !== null) {
        $safeResult = \pg_prepare($connection, $stmtname, $query);
    } elseif ($stmtname !== null) {
        $safeResult = \pg_prepare($connection, $stmtname);
    } elseif ($connection !== null) {
        $safeResult = \pg_prepare($connection);
    } else {
        $safeResult = \pg_prepare();
    }
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Connection $connection
 * @param string $data
 * @throws PgsqlException
 *
 */
function pg_put_line(?\PgSql\Connection $connection = null, ?string $data = null): void
{
    error_clear_last();
    if ($data !== null) {
        $safeResult = \pg_put_line($connection, $data);
    } elseif ($connection !== null) {
        $safeResult = \pg_put_line($connection);
    } else {
        $safeResult = \pg_put_line();
    }
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
}


/**
 * @param \PgSql\Connection $connection
 * @param string $query
 * @param array $params
 * @return \PgSql\Result
 * @throws PgsqlException
 *
 */
function pg_query_params(?\PgSql\Connection $connection = null, ?string $query = null, ?array $params = null): \PgSql\Result
{
    error_clear_last();
    if ($params !== null) {
        $safeResult = \pg_query_params($connection, $query, $params);
    } elseif ($query !== null) {
        $safeResult = \pg_query_params($connection, $query);
    } elseif ($connection !== null) {
        $safeResult = \pg_query_params($connection);
    } else {
        $safeResult = \pg_query_params();
    }
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Connection $connection
 * @param string $query
 * @return \PgSql\Result
 * @throws PgsqlException
 *
 */
function pg_query(?\PgSql\Connection $connection = null, ?string $query = null): \PgSql\Result
{
    error_clear_last();
    if ($query !== null) {
        $safeResult = \pg_query($connection, $query);
    } elseif ($connection !== null) {
        $safeResult = \pg_query($connection);
    } else {
        $safeResult = \pg_query();
    }
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Result $result
 * @param int $field_code
 * @return null|string
 * @throws PgsqlException
 *
 */
function pg_result_error_field(\PgSql\Result $result, int $field_code): ?string
{
    error_clear_last();
    $safeResult = \pg_result_error_field($result, $field_code);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Result $result
 * @param int $row
 * @throws PgsqlException
 *
 */
function pg_result_seek(\PgSql\Result $result, int $row): void
{
    error_clear_last();
    $safeResult = \pg_result_seek($result, $row);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
}


/**
 * @param \PgSql\Connection $connection
 * @param string $table_name
 * @param array $conditions
 * @param int $flags
 * @param int $mode
 * @return mixed
 * @throws PgsqlException
 *
 */
function pg_select(\PgSql\Connection $connection, string $table_name, array $conditions, int $flags = PGSQL_DML_EXEC, int $mode = PGSQL_ASSOC)
{
    error_clear_last();
    $safeResult = \pg_select($connection, $table_name, $conditions, $flags, $mode);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \PgSql\Connection $connection
 * @return resource
 * @throws PgsqlException
 *
 */
function pg_socket(\PgSql\Connection $connection)
{
    error_clear_last();
    $safeResult = \pg_socket($connection);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param string $mode
 * @param \PgSql\Connection|null $connection
 * @throws PgsqlException
 *
 */
function pg_trace(string $filename, string $mode = "w", ?\PgSql\Connection $connection = null): void
{
    error_clear_last();
    if ($connection !== null) {
        $safeResult = \pg_trace($filename, $mode, $connection);
    } else {
        $safeResult = \pg_trace($filename, $mode);
    }
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
}


/**
 * @param \PgSql\Connection $connection
 * @param string $table_name
 * @param array $values
 * @param array $conditions
 * @param int $flags
 * @return mixed
 * @throws PgsqlException
 *
 */
function pg_update(\PgSql\Connection $connection, string $table_name, array $values, array $conditions, int $flags = PGSQL_DML_EXEC)
{
    error_clear_last();
    $safeResult = \pg_update($connection, $table_name, $values, $conditions, $flags);
    if ($safeResult === false) {
        throw PgsqlException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\InotifyException;

/**
 * @return resource
 * @throws InotifyException
 *
 */
function inotify_init()
{
    error_clear_last();
    $safeResult = \inotify_init();
    if ($safeResult === false) {
        throw InotifyException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $inotify_instance
 * @param int $watch_descriptor
 * @throws InotifyException
 *
 */
function inotify_rm_watch($inotify_instance, int $watch_descriptor): void
{
    error_clear_last();
    $safeResult = \inotify_rm_watch($inotify_instance, $watch_descriptor);
    if ($safeResult === false) {
        throw InotifyException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\UopzException;

/**
 * @param string $class
 * @param string $parent
 * @throws UopzException
 *
 */
function uopz_extend(string $class, string $parent): void
{
    error_clear_last();
    $safeResult = \uopz_extend($class, $parent);
    if ($safeResult === false) {
        throw UopzException::createFromPhpError();
    }
}


/**
 * @param string $class
 * @param string $interface
 * @throws UopzException
 *
 */
function uopz_implement(string $class, string $interface): void
{
    error_clear_last();
    $safeResult = \uopz_implement($class, $interface);
    if ($safeResult === false) {
        throw UopzException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\DatetimeException;

/**
 * @param string $format
 * @param string $datetime
 * @return array{year: int|false, month: int|false, day: int|false, hour: int|false, minute: int|false, second: int|false, fraction: float|false, warning_count: int, warnings: string[], error_count: int, errors: string[], is_localtime: bool, zone_type: int|bool, zone: int|bool, is_dst: bool, tz_abbr: string, tz_id: string, relative: array{year: int, month: int, day: int, hour: int, minute: int, second: int, weekday: int, weekdays: int, first_day_of_month: bool, last_day_of_month: bool}}|null
 * @throws DatetimeException
 *
 */
function date_parse_from_format(string $format, string $datetime): ?array
{
    error_clear_last();
    $safeResult = \date_parse_from_format($format, $datetime);
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $datetime
 * @return array{year: int|false, month: int|false, day: int|false, hour: int|false, minute: int|false, second: int|false, fraction: float|false, warning_count: int, warnings: string[], error_count: int, errors: string[], is_localtime: bool, zone_type: int|bool, zone: int|bool, is_dst: bool, tz_abbr: string, tz_id: string, relative: array{year: int, month: int, day: int, hour: int, minute: int, second: int, weekday: int, weekdays: int, first_day_of_month: bool, last_day_of_month: bool}}|null
 * @throws DatetimeException
 *
 */
function date_parse(string $datetime): ?array
{
    error_clear_last();
    $safeResult = \date_parse($datetime);
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $timestamp
 * @param float $latitude
 * @param float $longitude
 * @return array{sunrise: int|bool,sunset: int|bool,transit: int|bool,civil_twilight_begin: int|bool,civil_twilight_end: int|bool,nautical_twilight_begin: int|bool,nautical_twilight_end: int|bool,astronomical_twilight_begin: int|bool,astronomical_twilight_end: int|bool}
 * @throws DatetimeException
 *
 */
function date_sun_info(int $timestamp, float $latitude, float $longitude): array
{
    error_clear_last();
    $safeResult = \date_sun_info($timestamp, $latitude, $longitude);
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $timestamp
 * @param int $returnFormat
 * @param float|null $latitude
 * @param float|null $longitude
 * @param float|null $zenith
 * @param float|null $utcOffset
 * @return mixed
 * @throws DatetimeException
 *
 */
function date_sunrise(int $timestamp, int $returnFormat = SUNFUNCS_RET_STRING, ?float $latitude = null, ?float $longitude = null, ?float $zenith = null, ?float $utcOffset = null)
{
    error_clear_last();
    if ($utcOffset !== null) {
        $safeResult = \date_sunrise($timestamp, $returnFormat, $latitude, $longitude, $zenith, $utcOffset);
    } elseif ($zenith !== null) {
        $safeResult = \date_sunrise($timestamp, $returnFormat, $latitude, $longitude, $zenith);
    } elseif ($longitude !== null) {
        $safeResult = \date_sunrise($timestamp, $returnFormat, $latitude, $longitude);
    } elseif ($latitude !== null) {
        $safeResult = \date_sunrise($timestamp, $returnFormat, $latitude);
    } else {
        $safeResult = \date_sunrise($timestamp, $returnFormat);
    }
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $timestamp
 * @param int $returnFormat
 * @param float|null $latitude
 * @param float|null $longitude
 * @param float|null $zenith
 * @param float|null $utcOffset
 * @return mixed
 * @throws DatetimeException
 *
 */
function date_sunset(int $timestamp, int $returnFormat = SUNFUNCS_RET_STRING, ?float $latitude = null, ?float $longitude = null, ?float $zenith = null, ?float $utcOffset = null)
{
    error_clear_last();
    if ($utcOffset !== null) {
        $safeResult = \date_sunset($timestamp, $returnFormat, $latitude, $longitude, $zenith, $utcOffset);
    } elseif ($zenith !== null) {
        $safeResult = \date_sunset($timestamp, $returnFormat, $latitude, $longitude, $zenith);
    } elseif ($longitude !== null) {
        $safeResult = \date_sunset($timestamp, $returnFormat, $latitude, $longitude);
    } elseif ($latitude !== null) {
        $safeResult = \date_sunset($timestamp, $returnFormat, $latitude);
    } else {
        $safeResult = \date_sunset($timestamp, $returnFormat);
    }
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $format
 * @param int|null $timestamp
 * @return string
 * @throws DatetimeException
 *
 */
function date(string $format, ?int $timestamp = null): string
{
    error_clear_last();
    if ($timestamp !== null) {
        $safeResult = \date($format, $timestamp);
    } else {
        $safeResult = \date($format);
    }
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $hour
 * @param int|null $minute
 * @param int|null $second
 * @param int|null $month
 * @param int|null $day
 * @param int|null $year
 * @return int
 * @throws DatetimeException
 *
 */
function gmmktime(int $hour, ?int $minute = null, ?int $second = null, ?int $month = null, ?int $day = null, ?int $year = null): int
{
    error_clear_last();
    if ($year !== null) {
        $safeResult = \gmmktime($hour, $minute, $second, $month, $day, $year);
    } elseif ($day !== null) {
        $safeResult = \gmmktime($hour, $minute, $second, $month, $day);
    } elseif ($month !== null) {
        $safeResult = \gmmktime($hour, $minute, $second, $month);
    } elseif ($second !== null) {
        $safeResult = \gmmktime($hour, $minute, $second);
    } elseif ($minute !== null) {
        $safeResult = \gmmktime($hour, $minute);
    } else {
        $safeResult = \gmmktime($hour);
    }
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $format
 * @param int|null $timestamp
 * @return string
 * @throws DatetimeException
 *
 */
function gmstrftime(string $format, ?int $timestamp = null): string
{
    error_clear_last();
    if ($timestamp !== null) {
        $safeResult = \gmstrftime($format, $timestamp);
    } else {
        $safeResult = \gmstrftime($format);
    }
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $format
 * @param int|null $timestamp
 * @return int
 * @throws DatetimeException
 *
 */
function idate(string $format, ?int $timestamp = null): int
{
    error_clear_last();
    if ($timestamp !== null) {
        $safeResult = \idate($format, $timestamp);
    } else {
        $safeResult = \idate($format);
    }
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $hour
 * @param int|null $minute
 * @param int|null $second
 * @param int|null $month
 * @param int|null $day
 * @param int|null $year
 * @return int
 * @throws DatetimeException
 *
 */
function mktime(int $hour, ?int $minute = null, ?int $second = null, ?int $month = null, ?int $day = null, ?int $year = null): int
{
    error_clear_last();
    if ($year !== null) {
        $safeResult = \mktime($hour, $minute, $second, $month, $day, $year);
    } elseif ($day !== null) {
        $safeResult = \mktime($hour, $minute, $second, $month, $day);
    } elseif ($month !== null) {
        $safeResult = \mktime($hour, $minute, $second, $month);
    } elseif ($second !== null) {
        $safeResult = \mktime($hour, $minute, $second);
    } elseif ($minute !== null) {
        $safeResult = \mktime($hour, $minute);
    } else {
        $safeResult = \mktime($hour);
    }
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $format
 * @param int|null $timestamp
 * @return string
 * @throws DatetimeException
 *
 */
function strftime(string $format, ?int $timestamp = null): string
{
    error_clear_last();
    if ($timestamp !== null) {
        $safeResult = \strftime($format, $timestamp);
    } else {
        $safeResult = \strftime($format);
    }
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $timestamp
 * @param string $format
 * @return array
 * @throws DatetimeException
 *
 */
function strptime(string $timestamp, string $format): array
{
    error_clear_last();
    $safeResult = \strptime($timestamp, $format);
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $datetime
 * @param int|null $baseTimestamp
 * @return int
 * @throws DatetimeException
 *
 */
function strtotime(string $datetime, ?int $baseTimestamp = null): int
{
    error_clear_last();
    if ($baseTimestamp !== null) {
        $safeResult = \strtotime($datetime, $baseTimestamp);
    } else {
        $safeResult = \strtotime($datetime);
    }
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $abbr
 * @param int $utcOffset
 * @param int $isDST
 * @return string
 * @throws DatetimeException
 *
 */
function timezone_name_from_abbr(string $abbr, int $utcOffset = -1, int $isDST = -1): string
{
    error_clear_last();
    $safeResult = \timezone_name_from_abbr($abbr, $utcOffset, $isDST);
    if ($safeResult === false) {
        throw DatetimeException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\InfoException;

/**
 * @param int $what
 * @param mixed $value
 * @return mixed
 * @throws InfoException
 *
 */
function assert_options(int $what, $value = null)
{
    error_clear_last();
    if ($value !== null) {
        $safeResult = \assert_options($what, $value);
    } else {
        $safeResult = \assert_options($what);
    }
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $title
 * @throws InfoException
 *
 */
function cli_set_process_title(string $title): void
{
    error_clear_last();
    $safeResult = \cli_set_process_title($title);
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
}


/**
 * @param string $extension_filename
 * @throws InfoException
 *
 */
function dl(string $extension_filename): void
{
    error_clear_last();
    $safeResult = \dl($extension_filename);
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
}


/**
 * @param string $option
 * @return mixed
 * @throws InfoException
 *
 */
function get_cfg_var(string $option)
{
    error_clear_last();
    $safeResult = \get_cfg_var($option);
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return string
 * @throws InfoException
 *
 */
function get_include_path(): string
{
    error_clear_last();
    $safeResult = \get_include_path();
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return int
 * @throws InfoException
 *
 */
function getlastmod(): int
{
    error_clear_last();
    $safeResult = \getlastmod();
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return int
 * @throws InfoException
 *
 */
function getmygid(): int
{
    error_clear_last();
    $safeResult = \getmygid();
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return int
 * @throws InfoException
 *
 */
function getmyinode(): int
{
    error_clear_last();
    $safeResult = \getmyinode();
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return int
 * @throws InfoException
 *
 */
function getmypid(): int
{
    error_clear_last();
    $safeResult = \getmypid();
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return int
 * @throws InfoException
 *
 */
function getmyuid(): int
{
    error_clear_last();
    $safeResult = \getmyuid();
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $short_options
 * @param array $long_options
 * @param int|null $rest_index
 * @return array|array|array
 * @throws InfoException
 *
 */
function getopt(string $short_options, array $long_options = [], ?int &$rest_index = null): array
{
    error_clear_last();
    $safeResult = \getopt($short_options, $long_options, $rest_index);
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $mode
 * @return array
 * @throws InfoException
 *
 */
function getrusage(int $mode = 0): array
{
    error_clear_last();
    $safeResult = \getrusage($mode);
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $option
 * @return string
 * @throws InfoException
 *
 */
function ini_get(string $option): string
{
    error_clear_last();
    $safeResult = \ini_get($option);
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $option
 * @param bool|float|int|null|string $value
 * @return string
 * @throws InfoException
 *
 */
function ini_set(string $option, $value): string
{
    error_clear_last();
    $safeResult = \ini_set($option, $value);
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return non-empty-string
 * @throws InfoException
 *
 */
function php_sapi_name(): string
{
    error_clear_last();
    $safeResult = \php_sapi_name();
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $flags
 * @throws InfoException
 *
 */
function phpcredits(int $flags = CREDITS_ALL): void
{
    error_clear_last();
    $safeResult = \phpcredits($flags);
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
}


/**
 * @param int $flags
 * @throws InfoException
 *
 */
function phpinfo(int $flags = INFO_ALL): void
{
    error_clear_last();
    $safeResult = \phpinfo($flags);
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
}


/**
 * @param string $assignment
 * @throws InfoException
 *
 */
function putenv(string $assignment): void
{
    error_clear_last();
    $safeResult = \putenv($assignment);
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
}


/**
 * @param string $include_path
 * @return string
 * @throws InfoException
 *
 */
function set_include_path(string $include_path): string
{
    error_clear_last();
    $safeResult = \set_include_path($include_path);
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $seconds
 * @throws InfoException
 *
 */
function set_time_limit(int $seconds): void
{
    error_clear_last();
    $safeResult = \set_time_limit($seconds);
    if ($safeResult === false) {
        throw InfoException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\OpensslException;

/**
 * @param string $cipher_algo
 * @return int
 * @throws OpensslException
 *
 */
function openssl_cipher_iv_length(string $cipher_algo): int
{
    error_clear_last();
    $safeResult = \openssl_cipher_iv_length($cipher_algo);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $input_filename
 * @param string $output_filename
 * @param \OpenSSLCertificate|string $certificate
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|null|string $private_key
 * @param int $encoding
 * @throws OpensslException
 *
 */
function openssl_cms_decrypt(string $input_filename, string $output_filename, $certificate, $private_key = null, int $encoding = OPENSSL_ENCODING_SMIME): void
{
    error_clear_last();
    if ($encoding !== OPENSSL_ENCODING_SMIME) {
        $safeResult = \openssl_cms_decrypt($input_filename, $output_filename, $certificate, $private_key, $encoding);
    } elseif ($private_key !== null) {
        $safeResult = \openssl_cms_decrypt($input_filename, $output_filename, $certificate, $private_key);
    } else {
        $safeResult = \openssl_cms_decrypt($input_filename, $output_filename, $certificate);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $input_filename
 * @param string $output_filename
 * @param \OpenSSLCertificate|array|string $certificate
 * @param array|null $headers
 * @param int $flags
 * @param int $encoding
 * @param int $cipher_algo
 * @throws OpensslException
 *
 */
function openssl_cms_encrypt(string $input_filename, string $output_filename, $certificate, ?array $headers, int $flags = 0, int $encoding = OPENSSL_ENCODING_SMIME, int $cipher_algo = OPENSSL_CIPHER_RC2_40): void
{
    error_clear_last();
    $safeResult = \openssl_cms_encrypt($input_filename, $output_filename, $certificate, $headers, $flags, $encoding, $cipher_algo);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $input_filename
 * @param array $certificates
 * @throws OpensslException
 *
 */
function openssl_cms_read(string $input_filename, array &$certificates): void
{
    error_clear_last();
    $safeResult = \openssl_cms_read($input_filename, $certificates);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $input_filename
 * @param string $output_filename
 * @param \OpenSSLCertificate|string $certificate
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param array|null $headers
 * @param int $flags
 * @param int $encoding
 * @param null|string $untrusted_certificates_filename
 * @throws OpensslException
 *
 */
function openssl_cms_sign(string $input_filename, string $output_filename, $certificate, $private_key, ?array $headers, int $flags = 0, int $encoding = OPENSSL_ENCODING_SMIME, ?string $untrusted_certificates_filename = null): void
{
    error_clear_last();
    if ($untrusted_certificates_filename !== null) {
        $safeResult = \openssl_cms_sign($input_filename, $output_filename, $certificate, $private_key, $headers, $flags, $encoding, $untrusted_certificates_filename);
    } else {
        $safeResult = \openssl_cms_sign($input_filename, $output_filename, $certificate, $private_key, $headers, $flags, $encoding);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $input_filename
 * @param int $flags
 * @param null|string $certificates
 * @param array $ca_info
 * @param null|string $untrusted_certificates_filename
 * @param null|string $content
 * @param null|string $pk7
 * @param null|string $sigfile
 * @param int $encoding
 * @throws OpensslException
 *
 */
function openssl_cms_verify(string $input_filename, int $flags = 0, ?string $certificates = null, array $ca_info = [], ?string $untrusted_certificates_filename = null, ?string $content = null, ?string $pk7 = null, ?string $sigfile = null, int $encoding = OPENSSL_ENCODING_SMIME): void
{
    error_clear_last();
    if ($encoding !== OPENSSL_ENCODING_SMIME) {
        $safeResult = \openssl_cms_verify($input_filename, $flags, $certificates, $ca_info, $untrusted_certificates_filename, $content, $pk7, $sigfile, $encoding);
    } elseif ($sigfile !== null) {
        $safeResult = \openssl_cms_verify($input_filename, $flags, $certificates, $ca_info, $untrusted_certificates_filename, $content, $pk7, $sigfile);
    } elseif ($pk7 !== null) {
        $safeResult = \openssl_cms_verify($input_filename, $flags, $certificates, $ca_info, $untrusted_certificates_filename, $content, $pk7);
    } elseif ($content !== null) {
        $safeResult = \openssl_cms_verify($input_filename, $flags, $certificates, $ca_info, $untrusted_certificates_filename, $content);
    } elseif ($untrusted_certificates_filename !== null) {
        $safeResult = \openssl_cms_verify($input_filename, $flags, $certificates, $ca_info, $untrusted_certificates_filename);
    } elseif ($ca_info !== []) {
        $safeResult = \openssl_cms_verify($input_filename, $flags, $certificates, $ca_info);
    } elseif ($certificates !== null) {
        $safeResult = \openssl_cms_verify($input_filename, $flags, $certificates);
    } else {
        $safeResult = \openssl_cms_verify($input_filename, $flags);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLCertificateSigningRequest|string $csr
 * @param string $output_filename
 * @param bool $no_text
 * @throws OpensslException
 *
 */
function openssl_csr_export_to_file($csr, string $output_filename, bool $no_text = true): void
{
    error_clear_last();
    $safeResult = \openssl_csr_export_to_file($csr, $output_filename, $no_text);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLCertificateSigningRequest|string $csr
 * @param null|string $output
 * @param bool $no_text
 * @throws OpensslException
 *
 */
function openssl_csr_export($csr, ?string &$output, bool $no_text = true): void
{
    error_clear_last();
    $safeResult = \openssl_csr_export($csr, $output, $no_text);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLCertificateSigningRequest|string $csr
 * @param bool $short_names
 * @return \OpenSSLAsymmetricKey
 * @throws OpensslException
 *
 */
function openssl_csr_get_public_key($csr, bool $short_names = true): \OpenSSLAsymmetricKey
{
    error_clear_last();
    $safeResult = \openssl_csr_get_public_key($csr, $short_names);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLCertificateSigningRequest|string $csr
 * @param bool $short_names
 * @return array
 * @throws OpensslException
 *
 */
function openssl_csr_get_subject($csr, bool $short_names = true): array
{
    error_clear_last();
    $safeResult = \openssl_csr_get_subject($csr, $short_names);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param array $distinguished_names
 * @param \OpenSSLAsymmetricKey $private_key
 * @param array|null $options
 * @param array|null $extra_attributes
 * @return \OpenSSLCertificateSigningRequest
 * @throws OpensslException
 *
 */
function openssl_csr_new(array $distinguished_names, \OpenSSLAsymmetricKey &$private_key, ?array $options = null, ?array $extra_attributes = null): \OpenSSLCertificateSigningRequest
{
    error_clear_last();
    if ($extra_attributes !== null) {
        $safeResult = \openssl_csr_new($distinguished_names, $private_key, $options, $extra_attributes);
    } elseif ($options !== null) {
        $safeResult = \openssl_csr_new($distinguished_names, $private_key, $options);
    } else {
        $safeResult = \openssl_csr_new($distinguished_names, $private_key);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLCertificateSigningRequest|string $csr
 * @param \OpenSSLCertificate|null|string $ca_certificate
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param int $days
 * @param array|null $options
 * @param int $serial
 * @return \OpenSSLCertificate
 * @throws OpensslException
 *
 */
function openssl_csr_sign($csr, $ca_certificate, $private_key, int $days, ?array $options = null, int $serial = 0): \OpenSSLCertificate
{
    error_clear_last();
    if ($serial !== 0) {
        $safeResult = \openssl_csr_sign($csr, $ca_certificate, $private_key, $days, $options, $serial);
    } elseif ($options !== null) {
        $safeResult = \openssl_csr_sign($csr, $ca_certificate, $private_key, $days, $options);
    } else {
        $safeResult = \openssl_csr_sign($csr, $ca_certificate, $private_key, $days);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param string $cipher_algo
 * @param string $passphrase
 * @param int $options
 * @param string $iv
 * @param string $tag
 * @param string $aad
 * @return string
 * @throws OpensslException
 *
 */
function openssl_decrypt(string $data, string $cipher_algo, string $passphrase, int $options = 0, string $iv = "", string $tag = "", string $aad = ""): string
{
    error_clear_last();
    $safeResult = \openssl_decrypt($data, $cipher_algo, $passphrase, $options, $iv, $tag, $aad);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $public_key
 * @param \OpenSSLAsymmetricKey $private_key
 * @return string
 * @throws OpensslException
 *
 */
function openssl_dh_compute_key(string $public_key, \OpenSSLAsymmetricKey $private_key): string
{
    error_clear_last();
    $safeResult = \openssl_dh_compute_key($public_key, $private_key);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param string $digest_algo
 * @param bool $binary
 * @return string
 * @throws OpensslException
 *
 */
function openssl_digest(string $data, string $digest_algo, bool $binary = false): string
{
    error_clear_last();
    $safeResult = \openssl_digest($data, $digest_algo, $binary);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return list
 * @throws OpensslException
 *
 */
function openssl_get_curve_names(): array
{
    error_clear_last();
    $safeResult = \openssl_get_curve_names();
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param null|string $output
 * @param string $encrypted_key
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param string $cipher_algo
 * @param null|string $iv
 * @throws OpensslException
 *
 */
function openssl_open(string $data, ?string &$output, string $encrypted_key, $private_key, string $cipher_algo, ?string $iv = null): void
{
    error_clear_last();
    if ($iv !== null) {
        $safeResult = \openssl_open($data, $output, $encrypted_key, $private_key, $cipher_algo, $iv);
    } else {
        $safeResult = \openssl_open($data, $output, $encrypted_key, $private_key, $cipher_algo);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $password
 * @param string $salt
 * @param int $key_length
 * @param int $iterations
 * @param string $digest_algo
 * @return string
 * @throws OpensslException
 *
 */
function openssl_pbkdf2(string $password, string $salt, int $key_length, int $iterations, string $digest_algo = "sha1"): string
{
    error_clear_last();
    $safeResult = \openssl_pbkdf2($password, $salt, $key_length, $iterations, $digest_algo);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLCertificate|string $certificate
 * @param string $output_filename
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param string $passphrase
 * @param array $options
 * @throws OpensslException
 *
 */
function openssl_pkcs12_export_to_file($certificate, string $output_filename, $private_key, string $passphrase, array $options = []): void
{
    error_clear_last();
    $safeResult = \openssl_pkcs12_export_to_file($certificate, $output_filename, $private_key, $passphrase, $options);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLCertificate|string $certificate
 * @param null|string $output
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param string $passphrase
 * @param array $options
 * @throws OpensslException
 *
 */
function openssl_pkcs12_export($certificate, ?string &$output, $private_key, string $passphrase, array $options = []): void
{
    error_clear_last();
    $safeResult = \openssl_pkcs12_export($certificate, $output, $private_key, $passphrase, $options);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $pkcs12
 * @param array|null $certificates
 * @param string $passphrase
 * @throws OpensslException
 *
 */
function openssl_pkcs12_read(string $pkcs12, ?array &$certificates, string $passphrase): void
{
    error_clear_last();
    $safeResult = \openssl_pkcs12_read($pkcs12, $certificates, $passphrase);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $input_filename
 * @param string $output_filename
 * @param \OpenSSLCertificate|string $certificate
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|null|string $private_key
 * @throws OpensslException
 *
 */
function openssl_pkcs7_decrypt(string $input_filename, string $output_filename, $certificate, $private_key = null): void
{
    error_clear_last();
    if ($private_key !== null) {
        $safeResult = \openssl_pkcs7_decrypt($input_filename, $output_filename, $certificate, $private_key);
    } else {
        $safeResult = \openssl_pkcs7_decrypt($input_filename, $output_filename, $certificate);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $input_filename
 * @param string $output_filename
 * @param \OpenSSLCertificate|array|string $certificate
 * @param array $headers
 * @param int $flags
 * @param int $cipher_algo
 * @throws OpensslException
 *
 */
function openssl_pkcs7_encrypt(string $input_filename, string $output_filename, $certificate, array $headers, int $flags = 0, int $cipher_algo = OPENSSL_CIPHER_RC2_40): void
{
    error_clear_last();
    $safeResult = \openssl_pkcs7_encrypt($input_filename, $output_filename, $certificate, $headers, $flags, $cipher_algo);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $data
 * @param array|null $certificates
 * @throws OpensslException
 *
 */
function openssl_pkcs7_read(string $data, ?array &$certificates): void
{
    error_clear_last();
    $safeResult = \openssl_pkcs7_read($data, $certificates);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $input_filename
 * @param string $output_filename
 * @param \OpenSSLCertificate|string $certificate
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param array $headers
 * @param int $flags
 * @param null|string $untrusted_certificates_filename
 * @throws OpensslException
 *
 */
function openssl_pkcs7_sign(string $input_filename, string $output_filename, $certificate, $private_key, array $headers, int $flags = PKCS7_DETACHED, ?string $untrusted_certificates_filename = null): void
{
    error_clear_last();
    if ($untrusted_certificates_filename !== null) {
        $safeResult = \openssl_pkcs7_sign($input_filename, $output_filename, $certificate, $private_key, $headers, $flags, $untrusted_certificates_filename);
    } else {
        $safeResult = \openssl_pkcs7_sign($input_filename, $output_filename, $certificate, $private_key, $headers, $flags);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $public_key
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param int $key_length
 * @return string
 * @throws OpensslException
 *
 */
function openssl_pkey_derive($public_key, $private_key, int $key_length = 0): string
{
    error_clear_last();
    $safeResult = \openssl_pkey_derive($public_key, $private_key, $key_length);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $key
 * @param string $output_filename
 * @param null|string $passphrase
 * @param array|null $options
 * @throws OpensslException
 *
 */
function openssl_pkey_export_to_file($key, string $output_filename, ?string $passphrase = null, ?array $options = null): void
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \openssl_pkey_export_to_file($key, $output_filename, $passphrase, $options);
    } elseif ($passphrase !== null) {
        $safeResult = \openssl_pkey_export_to_file($key, $output_filename, $passphrase);
    } else {
        $safeResult = \openssl_pkey_export_to_file($key, $output_filename);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $key
 * @param null|string $output
 * @param null|string $passphrase
 * @param array|null $options
 * @throws OpensslException
 *
 */
function openssl_pkey_export($key, ?string &$output, ?string $passphrase = null, ?array $options = null): void
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \openssl_pkey_export($key, $output, $passphrase, $options);
    } elseif ($passphrase !== null) {
        $safeResult = \openssl_pkey_export($key, $output, $passphrase);
    } else {
        $safeResult = \openssl_pkey_export($key, $output);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLAsymmetricKey $key
 * @return array
 * @throws OpensslException
 *
 */
function openssl_pkey_get_details(\OpenSSLAsymmetricKey $key): array
{
    error_clear_last();
    $safeResult = \openssl_pkey_get_details($key);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param null|string $passphrase
 * @return \OpenSSLAsymmetricKey
 * @throws OpensslException
 *
 */
function openssl_pkey_get_private($private_key, ?string $passphrase = null): \OpenSSLAsymmetricKey
{
    error_clear_last();
    if ($passphrase !== null) {
        $safeResult = \openssl_pkey_get_private($private_key, $passphrase);
    } else {
        $safeResult = \openssl_pkey_get_private($private_key);
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $public_key
 * @return \OpenSSLAsymmetricKey
 * @throws OpensslException
 *
 */
function openssl_pkey_get_public($public_key): \OpenSSLAsymmetricKey
{
    error_clear_last();
    $safeResult = \openssl_pkey_get_public($public_key);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param array|null $options
 * @return \OpenSSLAsymmetricKey
 * @throws OpensslException
 *
 */
function openssl_pkey_new(?array $options = null): \OpenSSLAsymmetricKey
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \openssl_pkey_new($options);
    } else {
        $safeResult = \openssl_pkey_new();
    }
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param null|string $decrypted_data
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param int $padding
 * @throws OpensslException
 *
 */
function openssl_private_decrypt(string $data, ?string &$decrypted_data, $private_key, int $padding = OPENSSL_PKCS1_PADDING): void
{
    error_clear_last();
    $safeResult = \openssl_private_decrypt($data, $decrypted_data, $private_key, $padding);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $data
 * @param null|string $encrypted_data
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param int $padding
 * @throws OpensslException
 *
 */
function openssl_private_encrypt(string $data, ?string &$encrypted_data, $private_key, int $padding = OPENSSL_PKCS1_PADDING): void
{
    error_clear_last();
    $safeResult = \openssl_private_encrypt($data, $encrypted_data, $private_key, $padding);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $data
 * @param null|string $decrypted_data
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $public_key
 * @param int $padding
 * @throws OpensslException
 *
 */
function openssl_public_decrypt(string $data, ?string &$decrypted_data, $public_key, int $padding = OPENSSL_PKCS1_PADDING): void
{
    error_clear_last();
    $safeResult = \openssl_public_decrypt($data, $decrypted_data, $public_key, $padding);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $data
 * @param null|string $encrypted_data
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $public_key
 * @param int $padding
 * @throws OpensslException
 *
 */
function openssl_public_encrypt(string $data, ?string &$encrypted_data, $public_key, int $padding = OPENSSL_PKCS1_PADDING): void
{
    error_clear_last();
    $safeResult = \openssl_public_encrypt($data, $encrypted_data, $public_key, $padding);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param int $length
 * @param bool|null $strong_result
 * @return string
 * @throws OpensslException
 *
 */
function openssl_random_pseudo_bytes(int $length, ?bool &$strong_result = null): string
{
    error_clear_last();
    $safeResult = \openssl_random_pseudo_bytes($length, $strong_result);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param null|string $sealed_data
 * @param array|null $encrypted_keys
 * @param array $public_key
 * @param string $cipher_algo
 * @param null|string $iv
 * @return int
 * @throws OpensslException
 *
 */
function openssl_seal(string $data, ?string &$sealed_data, ?array &$encrypted_keys, array $public_key, string $cipher_algo, ?string &$iv = null): int
{
    error_clear_last();
    $safeResult = \openssl_seal($data, $sealed_data, $encrypted_keys, $public_key, $cipher_algo, $iv);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param null|string $signature
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $private_key
 * @param int|string $algorithm
 * @throws OpensslException
 *
 */
function openssl_sign(string $data, ?string &$signature, $private_key, $algorithm = OPENSSL_ALGO_SHA1): void
{
    error_clear_last();
    $safeResult = \openssl_sign($data, $signature, $private_key, $algorithm);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $spki
 * @return null|string
 * @throws OpensslException
 *
 */
function openssl_spki_export_challenge(string $spki): ?string
{
    error_clear_last();
    $safeResult = \openssl_spki_export_challenge($spki);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $spki
 * @return null|string
 * @throws OpensslException
 *
 */
function openssl_spki_export(string $spki): ?string
{
    error_clear_last();
    $safeResult = \openssl_spki_export($spki);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLAsymmetricKey $private_key
 * @param string $challenge
 * @param int $digest_algo
 * @return null|string
 * @throws OpensslException
 *
 */
function openssl_spki_new(\OpenSSLAsymmetricKey $private_key, string $challenge, int $digest_algo = OPENSSL_ALGO_MD5): ?string
{
    error_clear_last();
    $safeResult = \openssl_spki_new($private_key, $challenge, $digest_algo);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $spki
 * @throws OpensslException
 *
 */
function openssl_spki_verify(string $spki): void
{
    error_clear_last();
    $safeResult = \openssl_spki_verify($spki);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param string $data
 * @param string $signature
 * @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|array|string $public_key
 * @param int|string $algorithm
 * @return -1|0|1
 * @throws OpensslException
 *
 */
function openssl_verify(string $data, string $signature, $public_key, $algorithm = OPENSSL_ALGO_SHA1): int
{
    error_clear_last();
    $safeResult = \openssl_verify($data, $signature, $public_key, $algorithm);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLCertificate|string $certificate
 * @param int $purpose
 * @param array $ca_info
 * @param null|string $untrusted_certificates_file
 * @return bool|int
 * @throws OpensslException
 *
 */
function openssl_x509_checkpurpose($certificate, int $purpose, array $ca_info = [], ?string $untrusted_certificates_file = null)
{
    error_clear_last();
    if ($untrusted_certificates_file !== null) {
        $safeResult = \openssl_x509_checkpurpose($certificate, $purpose, $ca_info, $untrusted_certificates_file);
    } else {
        $safeResult = \openssl_x509_checkpurpose($certificate, $purpose, $ca_info);
    }
    if ($safeResult === -1) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLCertificate|string $certificate
 * @param string $output_filename
 * @param bool $no_text
 * @throws OpensslException
 *
 */
function openssl_x509_export_to_file($certificate, string $output_filename, bool $no_text = true): void
{
    error_clear_last();
    $safeResult = \openssl_x509_export_to_file($certificate, $output_filename, $no_text);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLCertificate|string $certificate
 * @param null|string $output
 * @param bool $no_text
 * @throws OpensslException
 *
 */
function openssl_x509_export($certificate, ?string &$output, bool $no_text = true): void
{
    error_clear_last();
    $safeResult = \openssl_x509_export($certificate, $output, $no_text);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
}


/**
 * @param \OpenSSLCertificate|string $certificate
 * @param string $digest_algo
 * @param bool $binary
 * @return string
 * @throws OpensslException
 *
 */
function openssl_x509_fingerprint($certificate, string $digest_algo = "sha1", bool $binary = false): string
{
    error_clear_last();
    $safeResult = \openssl_x509_fingerprint($certificate, $digest_algo, $binary);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OpenSSLCertificate|string $certificate
 * @return \OpenSSLCertificate
 * @throws OpensslException
 *
 */
function openssl_x509_read($certificate): \OpenSSLCertificate
{
    error_clear_last();
    $safeResult = \openssl_x509_read($certificate);
    if ($safeResult === false) {
        throw OpensslException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\PcntlException;

/**
 * @param int|null $process_id
 * @param int $mode
 * @return int
 * @throws PcntlException
 *
 */
function pcntl_getpriority(?int $process_id = null, int $mode = PRIO_PROCESS): int
{
    error_clear_last();
    if ($mode !== PRIO_PROCESS) {
        $safeResult = \pcntl_getpriority($process_id, $mode);
    } elseif ($process_id !== null) {
        $safeResult = \pcntl_getpriority($process_id);
    } else {
        $safeResult = \pcntl_getpriority();
    }
    if ($safeResult === false) {
        throw PcntlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $priority
 * @param int|null $process_id
 * @param int $mode
 * @throws PcntlException
 *
 */
function pcntl_setpriority(int $priority, ?int $process_id = null, int $mode = PRIO_PROCESS): void
{
    error_clear_last();
    if ($mode !== PRIO_PROCESS) {
        $safeResult = \pcntl_setpriority($priority, $process_id, $mode);
    } elseif ($process_id !== null) {
        $safeResult = \pcntl_setpriority($priority, $process_id);
    } else {
        $safeResult = \pcntl_setpriority($priority);
    }
    if ($safeResult === false) {
        throw PcntlException::createFromPhpError();
    }
}


/**
 * @throws PcntlException
 *
 */
function pcntl_signal_dispatch(): void
{
    error_clear_last();
    $safeResult = \pcntl_signal_dispatch();
    if ($safeResult === false) {
        throw PcntlException::createFromPhpError();
    }
}


/**
 * @param int $signal
 * @param callable|int $handler
 * @param bool $restart_syscalls
 * @throws PcntlException
 *
 */
function pcntl_signal(int $signal, $handler, bool $restart_syscalls = true): void
{
    error_clear_last();
    $safeResult = \pcntl_signal($signal, $handler, $restart_syscalls);
    if ($safeResult === false) {
        throw PcntlException::createFromPhpError();
    }
}


/**
 * @param int $mode
 * @param array $signals
 * @param array|null $old_signals
 * @throws PcntlException
 *
 */
function pcntl_sigprocmask(int $mode, array $signals, ?array &$old_signals = null): void
{
    error_clear_last();
    $safeResult = \pcntl_sigprocmask($mode, $signals, $old_signals);
    if ($safeResult === false) {
        throw PcntlException::createFromPhpError();
    }
}


/**
 * @param array $signals
 * @param array|null $info
 * @param int $seconds
 * @param int $nanoseconds
 * @return int
 * @throws PcntlException
 *
 */
function pcntl_sigtimedwait(array $signals, ?array &$info = [], int $seconds = 0, int $nanoseconds = 0): int
{
    error_clear_last();
    $safeResult = \pcntl_sigtimedwait($signals, $info, $seconds, $nanoseconds);
    if ($safeResult === false) {
        throw PcntlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param array $signals
 * @param array|null $info
 * @return int
 * @throws PcntlException
 *
 */
function pcntl_sigwaitinfo(array $signals, ?array &$info = []): int
{
    error_clear_last();
    $safeResult = \pcntl_sigwaitinfo($signals, $info);
    if ($safeResult === false) {
        throw PcntlException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\StreamException;

/**
 * @param resource $context
 * @param array $params
 * @throws StreamException
 *
 */
function stream_context_set_params($context, array $params): void
{
    error_clear_last();
    $safeResult = \stream_context_set_params($context, $params);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param resource $from
 * @param resource $to
 * @param int|null $length
 * @param int $offset
 * @return int
 * @throws StreamException
 *
 */
function stream_copy_to_stream($from, $to, ?int $length = null, int $offset = 0): int
{
    error_clear_last();
    if ($offset !== 0) {
        $safeResult = \stream_copy_to_stream($from, $to, $length, $offset);
    } elseif ($length !== null) {
        $safeResult = \stream_copy_to_stream($from, $to, $length);
    } else {
        $safeResult = \stream_copy_to_stream($from, $to);
    }
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @param string $filtername
 * @param int $read_write
 * @param mixed $params
 * @return resource
 * @throws StreamException
 *
 */
function stream_filter_append($stream, string $filtername, ?int $read_write = null, $params = null)
{
    error_clear_last();
    if ($params !== null) {
        $safeResult = \stream_filter_append($stream, $filtername, $read_write, $params);
    } elseif ($read_write !== null) {
        $safeResult = \stream_filter_append($stream, $filtername, $read_write);
    } else {
        $safeResult = \stream_filter_append($stream, $filtername);
    }
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @param string $filtername
 * @param int $read_write
 * @param mixed $params
 * @return resource
 * @throws StreamException
 *
 */
function stream_filter_prepend($stream, string $filtername, ?int $read_write = null, $params = null)
{
    error_clear_last();
    if ($params !== null) {
        $safeResult = \stream_filter_prepend($stream, $filtername, $read_write, $params);
    } elseif ($read_write !== null) {
        $safeResult = \stream_filter_prepend($stream, $filtername, $read_write);
    } else {
        $safeResult = \stream_filter_prepend($stream, $filtername);
    }
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filter_name
 * @param string $class
 * @throws StreamException
 *
 */
function stream_filter_register(string $filter_name, string $class): void
{
    error_clear_last();
    $safeResult = \stream_filter_register($filter_name, $class);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param resource $stream_filter
 * @throws StreamException
 *
 */
function stream_filter_remove($stream_filter): void
{
    error_clear_last();
    $safeResult = \stream_filter_remove($stream_filter);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param resource $handle
 * @param int $maxlength
 * @param int $offset
 * @return string
 * @throws StreamException
 *
 */
function stream_get_contents($handle, int $maxlength = -1, int $offset = -1): string
{
    error_clear_last();
    $safeResult = \stream_get_contents($handle, $maxlength, $offset);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $handle
 * @param int $length
 * @param string $ending
 * @return string
 * @throws StreamException
 *
 */
function stream_get_line($handle, int $length, string $ending = ""): string
{
    error_clear_last();
    $safeResult = \stream_get_line($handle, $length, $ending);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @throws StreamException
 *
 */
function stream_isatty($stream): void
{
    error_clear_last();
    $safeResult = \stream_isatty($stream);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @return string
 * @throws StreamException
 *
 */
function stream_resolve_include_path(string $filename): string
{
    error_clear_last();
    $safeResult = \stream_resolve_include_path($filename);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @param bool $enable
 * @throws StreamException
 *
 */
function stream_set_blocking($stream, bool $enable): void
{
    error_clear_last();
    $safeResult = \stream_set_blocking($stream, $enable);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param resource $stream
 * @param int $seconds
 * @param int $microseconds
 * @throws StreamException
 *
 */
function stream_set_timeout($stream, int $seconds, int $microseconds = 0): void
{
    error_clear_last();
    $safeResult = \stream_set_timeout($stream, $seconds, $microseconds);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param resource $server_socket
 * @param float $timeout
 * @param null|string $peername
 * @return resource
 * @throws StreamException
 *
 */
function stream_socket_accept($server_socket, ?float $timeout = null, ?string &$peername = null)
{
    error_clear_last();
    if ($peername !== null) {
        $safeResult = \stream_socket_accept($server_socket, $timeout, $peername);
    } elseif ($timeout !== null) {
        $safeResult = \stream_socket_accept($server_socket, $timeout);
    } else {
        $safeResult = \stream_socket_accept($server_socket);
    }
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $remote_socket
 * @param int|null $errno
 * @param null|string $errstr
 * @param float $timeout
 * @param int-mask $flags
 * @param resource $context
 * @return resource
 * @throws StreamException
 *
 */
function stream_socket_client(string $remote_socket, ?int &$errno = null, ?string &$errstr = null, ?float $timeout = null, int $flags = STREAM_CLIENT_CONNECT, $context = null)
{
    error_clear_last();
    if ($context !== null) {
        $safeResult = \stream_socket_client($remote_socket, $errno, $errstr, $timeout, $flags, $context);
    } elseif ($flags !== STREAM_CLIENT_CONNECT) {
        $safeResult = \stream_socket_client($remote_socket, $errno, $errstr, $timeout, $flags);
    } elseif ($timeout !== null) {
        $safeResult = \stream_socket_client($remote_socket, $errno, $errstr, $timeout);
    } else {
        $safeResult = \stream_socket_client($remote_socket, $errno, $errstr);
    }
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $handle
 * @param bool $want_peer
 * @return string
 * @throws StreamException
 *
 */
function stream_socket_get_name($handle, bool $want_peer): string
{
    error_clear_last();
    $safeResult = \stream_socket_get_name($handle, $want_peer);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $domain
 * @param int $type
 * @param int $protocol
 * @return resource[]
 * @throws StreamException
 *
 */
function stream_socket_pair(int $domain, int $type, int $protocol): array
{
    error_clear_last();
    $safeResult = \stream_socket_pair($domain, $type, $protocol);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $local_socket
 * @param int|null $errno
 * @param null|string $errstr
 * @param int $flags
 * @param resource $context
 * @return resource
 * @throws StreamException
 *
 */
function stream_socket_server(string $local_socket, ?int &$errno = null, ?string &$errstr = null, int $flags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $context = null)
{
    error_clear_last();
    if ($context !== null) {
        $safeResult = \stream_socket_server($local_socket, $errno, $errstr, $flags, $context);
    } else {
        $safeResult = \stream_socket_server($local_socket, $errno, $errstr, $flags);
    }
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @param int $mode
 * @throws StreamException
 *
 */
function stream_socket_shutdown($stream, int $mode): void
{
    error_clear_last();
    $safeResult = \stream_socket_shutdown($stream, $mode);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param resource $stream
 * @throws StreamException
 *
 */
function stream_supports_lock($stream): void
{
    error_clear_last();
    $safeResult = \stream_supports_lock($stream);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param string $protocol
 * @param string $class
 * @param int $flags
 * @throws StreamException
 *
 */
function stream_wrapper_register(string $protocol, string $class, int $flags = 0): void
{
    error_clear_last();
    $safeResult = \stream_wrapper_register($protocol, $class, $flags);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param string $protocol
 * @throws StreamException
 *
 */
function stream_wrapper_restore(string $protocol): void
{
    error_clear_last();
    $safeResult = \stream_wrapper_restore($protocol);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}


/**
 * @param string $protocol
 * @throws StreamException
 *
 */
function stream_wrapper_unregister(string $protocol): void
{
    error_clear_last();
    $safeResult = \stream_wrapper_unregister($protocol);
    if ($safeResult === false) {
        throw StreamException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\FpmException;

/**
 * @throws FpmException
 *
 */
function fastcgi_finish_request(): void
{
    error_clear_last();
    $safeResult = \fastcgi_finish_request();
    if ($safeResult === false) {
        throw FpmException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\ApcuException;

/**
 * @param bool $limited
 * @return array
 * @throws ApcuException
 *
 */
function apcu_cache_info(bool $limited = false): array
{
    error_clear_last();
    $safeResult = \apcu_cache_info($limited);
    if ($safeResult === false) {
        throw ApcuException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $key
 * @param int $old
 * @param int $new
 * @throws ApcuException
 *
 */
function apcu_cas(string $key, int $old, int $new): void
{
    error_clear_last();
    $safeResult = \apcu_cas($key, $old, $new);
    if ($safeResult === false) {
        throw ApcuException::createFromPhpError();
    }
}


/**
 * @param string $key
 * @param int $step
 * @param bool|null $success
 * @param int $ttl
 * @return int
 * @throws ApcuException
 *
 */
function apcu_dec(string $key, int $step = 1, ?bool &$success = null, int $ttl = 0): int
{
    error_clear_last();
    $safeResult = \apcu_dec($key, $step, $success, $ttl);
    if ($safeResult === false) {
        throw ApcuException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $key
 * @param int $step
 * @param bool|null $success
 * @param int $ttl
 * @return int
 * @throws ApcuException
 *
 */
function apcu_inc(string $key, int $step = 1, ?bool &$success = null, int $ttl = 0): int
{
    error_clear_last();
    $safeResult = \apcu_inc($key, $step, $success, $ttl);
    if ($safeResult === false) {
        throw ApcuException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param bool $limited
 * @return array
 * @throws ApcuException
 *
 */
function apcu_sma_info(bool $limited = false): array
{
    error_clear_last();
    $safeResult = \apcu_sma_info($limited);
    if ($safeResult === false) {
        throw ApcuException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\SessionException;

/**
 * @throws SessionException
 *
 */
function session_abort(): void
{
    error_clear_last();
    $safeResult = \session_abort();
    if ($safeResult === false) {
        throw SessionException::createFromPhpError();
    }
}


/**
 * @param int|null $value
 * @return int
 * @throws SessionException
 *
 */
function session_cache_expire(?int $value = null): int
{
    error_clear_last();
    if ($value !== null) {
        $safeResult = \session_cache_expire($value);
    } else {
        $safeResult = \session_cache_expire();
    }
    if ($safeResult === false) {
        throw SessionException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param null|string $value
 * @return string
 * @throws SessionException
 *
 */
function session_cache_limiter(?string $value = null): string
{
    error_clear_last();
    if ($value !== null) {
        $safeResult = \session_cache_limiter($value);
    } else {
        $safeResult = \session_cache_limiter();
    }
    if ($safeResult === false) {
        throw SessionException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $prefix
 * @return string
 * @throws SessionException
 *
 */
function session_create_id(string $prefix = ""): string
{
    error_clear_last();
    $safeResult = \session_create_id($prefix);
    if ($safeResult === false) {
        throw SessionException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @throws SessionException
 *
 */
function session_decode(string $data): void
{
    error_clear_last();
    $safeResult = \session_decode($data);
    if ($safeResult === false) {
        throw SessionException::createFromPhpError();
    }
}


/**
 * @throws SessionException
 *
 */
function session_destroy(): void
{
    error_clear_last();
    $safeResult = \session_destroy();
    if ($safeResult === false) {
        throw SessionException::createFromPhpError();
    }
}


/**
 * @return string
 * @throws SessionException
 *
 */
function session_encode(): string
{
    error_clear_last();
    $safeResult = \session_encode();
    if ($safeResult === false) {
        throw SessionException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return int
 * @throws SessionException
 *
 */
function session_gc(): int
{
    error_clear_last();
    $safeResult = \session_gc();
    if ($safeResult === false) {
        throw SessionException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param null|string $id
 * @return string
 * @throws SessionException
 *
 */
function session_id(?string $id = null): string
{
    error_clear_last();
    if ($id !== null) {
        $safeResult = \session_id($id);
    } else {
        $safeResult = \session_id();
    }
    if ($safeResult === false) {
        throw SessionException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param null|string $module
 * @return string
 * @throws SessionException
 *
 */
function session_module_name(?string $module = null): string
{
    error_clear_last();
    if ($module !== null) {
        $safeResult = \session_module_name($module);
    } else {
        $safeResult = \session_module_name();
    }
    if ($safeResult === false) {
        throw SessionException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param null|string $name
 * @return non-falsy-string
 * @throws SessionException
 *
 */
function session_name(?string $name = null): string
{
    error_clear_last();
    if ($name !== null) {
        $safeResult = \session_name($name);
    } else {
        $safeResult = \session_name();
    }
    if ($safeResult === false) {
        throw SessionException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param bool $delete_old_session
 * @throws SessionException
 *
 */
function session_regenerate_id(bool $delete_old_session = false): void
{
    error_clear_last();
    $safeResult = \session_regenerate_id($delete_old_session);
    if ($safeResult === false) {
        throw SessionException::createFromPhpError();
    }
}


/**
 * @throws SessionException
 *
 */
function session_reset(): void
{
    error_clear_last();
    $safeResult = \session_reset();
    if ($safeResult === false) {
        throw SessionException::createFromPhpError();
    }
}


/**
 * @param null|string $path
 * @return string
 * @throws SessionException
 *
 */
function session_save_path(?string $path = null): string
{
    error_clear_last();
    if ($path !== null) {
        $safeResult = \session_save_path($path);
    } else {
        $safeResult = \session_save_path();
    }
    if ($safeResult === false) {
        throw SessionException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param array $options
 * @throws SessionException
 *
 */
function session_start(array $options = []): void
{
    error_clear_last();
    $safeResult = \session_start($options);
    if ($safeResult === false) {
        throw SessionException::createFromPhpError();
    }
}


/**
 * @throws SessionException
 *
 */
function session_unset(): void
{
    error_clear_last();
    $safeResult = \session_unset();
    if ($safeResult === false) {
        throw SessionException::createFromPhpError();
    }
}


/**
 * @throws SessionException
 *
 */
function session_write_close(): void
{
    error_clear_last();
    $safeResult = \session_write_close();
    if ($safeResult === false) {
        throw SessionException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\FileinfoException;

/**
 * @param \finfo $finfo
 * @throws FileinfoException
 *
 */
function finfo_close(\finfo $finfo): void
{
    error_clear_last();
    $safeResult = \finfo_close($finfo);
    if ($safeResult === false) {
        throw FileinfoException::createFromPhpError();
    }
}


/**
 * @param int $flags
 * @param null|string $magic_database
 * @return \finfo
 * @throws FileinfoException
 *
 */
function finfo_open(int $flags = FILEINFO_NONE, ?string $magic_database = null): \finfo
{
    error_clear_last();
    if ($magic_database !== null) {
        $safeResult = \finfo_open($flags, $magic_database);
    } else {
        $safeResult = \finfo_open($flags);
    }
    if ($safeResult === false) {
        throw FileinfoException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource|string $filename
 * @return string
 * @throws FileinfoException
 *
 */
function mime_content_type($filename): string
{
    error_clear_last();
    $safeResult = \mime_content_type($filename);
    if ($safeResult === false) {
        throw FileinfoException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\Oci8Exception;

/**
 * @param resource $statement
 * @param string $param
 * @param array $var
 * @param int $max_array_length
 * @param int $max_item_length
 * @param int $type
 * @throws Oci8Exception
 *
 */
function oci_bind_array_by_name($statement, string $param, array &$var, int $max_array_length, int $max_item_length = -1, int $type = SQLT_AFC): void
{
    error_clear_last();
    $safeResult = \oci_bind_array_by_name($statement, $param, $var, $max_array_length, $max_item_length, $type);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $statement
 * @param string $param
 * @param mixed $var
 * @param int $max_length
 * @param int $type
 * @throws Oci8Exception
 *
 */
function oci_bind_by_name($statement, string $param, &$var, int $max_length = -1, int $type = 0): void
{
    error_clear_last();
    $safeResult = \oci_bind_by_name($statement, $param, $var, $max_length, $type);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $statement
 * @throws Oci8Exception
 *
 */
function oci_cancel($statement): void
{
    error_clear_last();
    $safeResult = \oci_cancel($statement);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $connection
 * @throws Oci8Exception
 *
 */
function oci_commit($connection): void
{
    error_clear_last();
    $safeResult = \oci_commit($connection);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param string $username
 * @param string $password
 * @param null|string $connection_string
 * @param string $encoding
 * @param int $session_mode
 * @return resource
 * @throws Oci8Exception
 *
 */
function oci_connect(string $username, string $password, ?string $connection_string = null, string $encoding = "", int $session_mode = OCI_DEFAULT)
{
    error_clear_last();
    if ($session_mode !== OCI_DEFAULT) {
        $safeResult = \oci_connect($username, $password, $connection_string, $encoding, $session_mode);
    } elseif ($encoding !== "") {
        $safeResult = \oci_connect($username, $password, $connection_string, $encoding);
    } elseif ($connection_string !== null) {
        $safeResult = \oci_connect($username, $password, $connection_string);
    } else {
        $safeResult = \oci_connect($username, $password);
    }
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $statement
 * @param string $column
 * @param mixed $var
 * @param int $type
 * @throws Oci8Exception
 *
 */
function oci_define_by_name($statement, string $column, &$var, int $type = 0): void
{
    error_clear_last();
    $safeResult = \oci_define_by_name($statement, $column, $var, $type);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $statement
 * @param int $mode
 * @throws Oci8Exception
 *
 */
function oci_execute($statement, int $mode = OCI_COMMIT_ON_SUCCESS): void
{
    error_clear_last();
    $safeResult = \oci_execute($statement, $mode);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $statement
 * @param mixed $column
 * @return string
 * @throws Oci8Exception
 *
 */
function oci_field_name($statement, $column): string
{
    error_clear_last();
    $safeResult = \oci_field_name($statement, $column);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $statement
 * @param mixed $column
 * @return int
 * @throws Oci8Exception
 *
 */
function oci_field_precision($statement, $column): int
{
    error_clear_last();
    $safeResult = \oci_field_precision($statement, $column);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $statement
 * @param mixed $column
 * @return int
 * @throws Oci8Exception
 *
 */
function oci_field_scale($statement, $column): int
{
    error_clear_last();
    $safeResult = \oci_field_scale($statement, $column);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $statement
 * @param mixed $column
 * @return int
 * @throws Oci8Exception
 *
 */
function oci_field_size($statement, $column): int
{
    error_clear_last();
    $safeResult = \oci_field_size($statement, $column);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $statement
 * @param mixed $column
 * @return int
 * @throws Oci8Exception
 *
 */
function oci_field_type_raw($statement, $column): int
{
    error_clear_last();
    $safeResult = \oci_field_type_raw($statement, $column);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $statement
 * @param mixed $column
 * @return mixed
 * @throws Oci8Exception
 *
 */
function oci_field_type($statement, $column)
{
    error_clear_last();
    $safeResult = \oci_field_type($statement, $column);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \OCILob $lob
 * @throws Oci8Exception
 *
 */
function oci_free_descriptor(\OCILob $lob): void
{
    error_clear_last();
    $safeResult = \oci_free_descriptor($lob);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $statement
 * @throws Oci8Exception
 *
 */
function oci_free_statement($statement): void
{
    error_clear_last();
    $safeResult = \oci_free_statement($statement);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $connection
 * @param string $type_name
 * @param null|string $schema
 * @return \OCI-Collection
 * @throws Oci8Exception
 *
 */
function oci_new_collection($connection, string $type_name, ?string $schema = null)
{
    error_clear_last();
    if ($schema !== null) {
        $safeResult = \oci_new_collection($connection, $type_name, $schema);
    } else {
        $safeResult = \oci_new_collection($connection, $type_name);
    }
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $username
 * @param string $password
 * @param null|string $connection_string
 * @param string $encoding
 * @param int $session_mode
 * @return resource
 * @throws Oci8Exception
 *
 */
function oci_new_connect(string $username, string $password, ?string $connection_string = null, string $encoding = "", int $session_mode = OCI_DEFAULT)
{
    error_clear_last();
    if ($session_mode !== OCI_DEFAULT) {
        $safeResult = \oci_new_connect($username, $password, $connection_string, $encoding, $session_mode);
    } elseif ($encoding !== "") {
        $safeResult = \oci_new_connect($username, $password, $connection_string, $encoding);
    } elseif ($connection_string !== null) {
        $safeResult = \oci_new_connect($username, $password, $connection_string);
    } else {
        $safeResult = \oci_new_connect($username, $password);
    }
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $connection
 * @return resource
 * @throws Oci8Exception
 *
 */
function oci_new_cursor($connection)
{
    error_clear_last();
    $safeResult = \oci_new_cursor($connection);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $connection
 * @param int $type
 * @return \OCI-Lob|false
 * @throws Oci8Exception
 *
 */
function oci_new_descriptor($connection, int $type = OCI_DTYPE_LOB)
{
    error_clear_last();
    $safeResult = \oci_new_descriptor($connection, $type);
    if ($safeResult === null) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $statement
 * @return 0|positive-int
 * @throws Oci8Exception
 *
 */
function oci_num_rows($statement): int
{
    error_clear_last();
    $safeResult = \oci_num_rows($statement);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $connection
 * @param string $sql
 * @return resource
 * @throws Oci8Exception
 *
 */
function oci_parse($connection, string $sql)
{
    error_clear_last();
    $safeResult = \oci_parse($connection, $sql);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $username
 * @param string $password
 * @param null|string $connection_string
 * @param string $encoding
 * @param int $session_mode
 * @return resource
 * @throws Oci8Exception
 *
 */
function oci_pconnect(string $username, string $password, ?string $connection_string = null, string $encoding = "", int $session_mode = OCI_DEFAULT)
{
    error_clear_last();
    if ($session_mode !== OCI_DEFAULT) {
        $safeResult = \oci_pconnect($username, $password, $connection_string, $encoding, $session_mode);
    } elseif ($encoding !== "") {
        $safeResult = \oci_pconnect($username, $password, $connection_string, $encoding);
    } elseif ($connection_string !== null) {
        $safeResult = \oci_pconnect($username, $password, $connection_string);
    } else {
        $safeResult = \oci_pconnect($username, $password);
    }
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $connection
 * @param callable $callback
 * @throws Oci8Exception
 *
 */
function oci_register_taf_callback($connection, callable $callback): void
{
    error_clear_last();
    $safeResult = \oci_register_taf_callback($connection, $callback);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $statement
 * @param mixed $column
 * @return string
 * @throws Oci8Exception
 *
 */
function oci_result($statement, $column): string
{
    error_clear_last();
    $safeResult = \oci_result($statement, $column);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $connection
 * @throws Oci8Exception
 *
 */
function oci_rollback($connection): void
{
    error_clear_last();
    $safeResult = \oci_rollback($connection);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $connection
 * @return string
 * @throws Oci8Exception
 *
 */
function oci_server_version($connection): string
{
    error_clear_last();
    $safeResult = \oci_server_version($connection);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $connection
 * @param string $action
 * @throws Oci8Exception
 *
 */
function oci_set_action($connection, string $action): void
{
    error_clear_last();
    $safeResult = \oci_set_action($connection, $action);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $connection
 * @param int $timeout
 * @throws Oci8Exception
 *
 */
function oci_set_call_timeout($connection, int $timeout): void
{
    error_clear_last();
    $safeResult = \oci_set_call_timeout($connection, $timeout);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $connection
 * @param string $client_id
 * @throws Oci8Exception
 *
 */
function oci_set_client_identifier($connection, string $client_id): void
{
    error_clear_last();
    $safeResult = \oci_set_client_identifier($connection, $client_id);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $connection
 * @param string $client_info
 * @throws Oci8Exception
 *
 */
function oci_set_client_info($connection, string $client_info): void
{
    error_clear_last();
    $safeResult = \oci_set_client_info($connection, $client_info);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $connection
 * @param string $action
 * @throws Oci8Exception
 *
 */
function oci_set_db_operation($connection, string $action): void
{
    error_clear_last();
    $safeResult = \oci_set_db_operation($connection, $action);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param string $edition
 * @throws Oci8Exception
 *
 */
function oci_set_edition(string $edition): void
{
    error_clear_last();
    $safeResult = \oci_set_edition($edition);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $connection
 * @param string $name
 * @throws Oci8Exception
 *
 */
function oci_set_module_name($connection, string $name): void
{
    error_clear_last();
    $safeResult = \oci_set_module_name($connection, $name);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $statement
 * @param int $rows
 * @throws Oci8Exception
 *
 */
function oci_set_prefetch($statement, int $rows): void
{
    error_clear_last();
    $safeResult = \oci_set_prefetch($statement, $rows);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}


/**
 * @param resource $statement
 * @return string
 * @throws Oci8Exception
 *
 */
function oci_statement_type($statement): string
{
    error_clear_last();
    $safeResult = \oci_statement_type($statement);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $connection
 * @throws Oci8Exception
 *
 */
function oci_unregister_taf_callback($connection): void
{
    error_clear_last();
    $safeResult = \oci_unregister_taf_callback($connection);
    if ($safeResult === false) {
        throw Oci8Exception::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\Bzip2Exception;

/**
 * @param resource $bz
 * @throws Bzip2Exception
 *
 */
function bzclose($bz): void
{
    error_clear_last();
    $safeResult = \bzclose($bz);
    if ($safeResult === false) {
        throw Bzip2Exception::createFromPhpError();
    }
}


/**
 * @param resource $bz
 * @throws Bzip2Exception
 *
 */
function bzflush($bz): void
{
    error_clear_last();
    $safeResult = \bzflush($bz);
    if ($safeResult === false) {
        throw Bzip2Exception::createFromPhpError();
    }
}


/**
 * @param resource|string $file
 * @param string $mode
 * @return resource
 * @throws Bzip2Exception
 *
 */
function bzopen($file, string $mode)
{
    error_clear_last();
    $safeResult = \bzopen($file, $mode);
    if ($safeResult === false) {
        throw Bzip2Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $bz
 * @param int $length
 * @return string
 * @throws Bzip2Exception
 *
 */
function bzread($bz, int $length = 1024): string
{
    error_clear_last();
    $safeResult = \bzread($bz, $length);
    if ($safeResult === false) {
        throw Bzip2Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $bz
 * @param string $data
 * @param int|null $length
 * @return int
 * @throws Bzip2Exception
 *
 */
function bzwrite($bz, string $data, ?int $length = null): int
{
    error_clear_last();
    if ($length !== null) {
        $safeResult = \bzwrite($bz, $data, $length);
    } else {
        $safeResult = \bzwrite($bz, $data);
    }
    if ($safeResult === false) {
        throw Bzip2Exception::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\VarException;

/**
 * @param mixed $var
 * @param string $type
 * @throws VarException
 *
 */
function settype(&$var, string $type): void
{
    error_clear_last();
    $safeResult = \settype($var, $type);
    if ($safeResult === false) {
        throw VarException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\ComException;

/**
 * @return string
 * @throws ComException
 *
 */
function com_create_guid(): string
{
    error_clear_last();
    $safeResult = \com_create_guid();
    if ($safeResult === false) {
        throw ComException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param object $variant
 * @param object $sink_object
 * @param mixed $sink_interface
 * @throws ComException
 *
 */
function com_event_sink(object $variant, object $sink_object, $sink_interface = null): void
{
    error_clear_last();
    if ($sink_interface !== null) {
        $safeResult = \com_event_sink($variant, $sink_object, $sink_interface);
    } else {
        $safeResult = \com_event_sink($variant, $sink_object);
    }
    if ($safeResult === false) {
        throw ComException::createFromPhpError();
    }
}


/**
 * @param string $typelib
 * @param bool $case_insensitive
 * @throws ComException
 *
 */
function com_load_typelib(string $typelib, bool $case_insensitive = true): void
{
    error_clear_last();
    $safeResult = \com_load_typelib($typelib, $case_insensitive);
    if ($safeResult === false) {
        throw ComException::createFromPhpError();
    }
}


/**
 * @param object $variant
 * @param null|string $dispatch_interface
 * @param bool $display_sink
 * @throws ComException
 *
 */
function com_print_typeinfo(object $variant, ?string $dispatch_interface = null, bool $display_sink = false): void
{
    error_clear_last();
    if ($display_sink !== false) {
        $safeResult = \com_print_typeinfo($variant, $dispatch_interface, $display_sink);
    } elseif ($dispatch_interface !== null) {
        $safeResult = \com_print_typeinfo($variant, $dispatch_interface);
    } else {
        $safeResult = \com_print_typeinfo($variant);
    }
    if ($safeResult === false) {
        throw ComException::createFromPhpError();
    }
}


/**
 * @param object $variant
 * @return int
 * @throws ComException
 *
 */
function variant_date_to_timestamp(object $variant): int
{
    error_clear_last();
    $safeResult = \variant_date_to_timestamp($variant);
    if ($safeResult === null) {
        throw ComException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param mixed $value
 * @param int $decimals
 * @return mixed
 * @throws ComException
 *
 */
function variant_round($value, int $decimals)
{
    error_clear_last();
    $safeResult = \variant_round($value, $decimals);
    if ($safeResult === null) {
        throw ComException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\IconvException;

/**
 * @param string $type
 * @return mixed
 * @throws IconvException
 *
 */
function iconv_get_encoding(string $type = "all")
{
    error_clear_last();
    $safeResult = \iconv_get_encoding($type);
    if ($safeResult === false) {
        throw IconvException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $string
 * @param int $mode
 * @param null|string $encoding
 * @return string
 * @throws IconvException
 *
 */
function iconv_mime_decode(string $string, int $mode = 0, ?string $encoding = null): string
{
    error_clear_last();
    if ($encoding !== null) {
        $safeResult = \iconv_mime_decode($string, $mode, $encoding);
    } else {
        $safeResult = \iconv_mime_decode($string, $mode);
    }
    if ($safeResult === false) {
        throw IconvException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $field_name
 * @param string $field_value
 * @param array $options
 * @return string
 * @throws IconvException
 *
 */
function iconv_mime_encode(string $field_name, string $field_value, array $options = []): string
{
    error_clear_last();
    $safeResult = \iconv_mime_encode($field_name, $field_value, $options);
    if ($safeResult === false) {
        throw IconvException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $type
 * @param string $encoding
 * @throws IconvException
 *
 */
function iconv_set_encoding(string $type, string $encoding): void
{
    error_clear_last();
    $safeResult = \iconv_set_encoding($type, $encoding);
    if ($safeResult === false) {
        throw IconvException::createFromPhpError();
    }
}


/**
 * @param string $string
 * @param null|string $encoding
 * @return 0|positive-int
 * @throws IconvException
 *
 */
function iconv_strlen(string $string, ?string $encoding = null): int
{
    error_clear_last();
    if ($encoding !== null) {
        $safeResult = \iconv_strlen($string, $encoding);
    } else {
        $safeResult = \iconv_strlen($string);
    }
    if ($safeResult === false) {
        throw IconvException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $from_encoding
 * @param string $to_encoding
 * @param string $string
 * @return string
 * @throws IconvException
 *
 */
function iconv(string $from_encoding, string $to_encoding, string $string): string
{
    error_clear_last();
    $safeResult = \iconv($from_encoding, $to_encoding, $string);
    if ($safeResult === false) {
        throw IconvException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\SwooleException;

/**
 * @param string $hostname
 * @param callable $callback
 * @throws SwooleException
 *
 */
function swoole_async_dns_lookup(string $hostname, callable $callback): void
{
    error_clear_last();
    $safeResult = \swoole_async_dns_lookup($hostname, $callback);
    if ($safeResult === false) {
        throw SwooleException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @param string $callback
 * @throws SwooleException
 *
 */
function swoole_async_readfile(string $filename, string $callback): void
{
    error_clear_last();
    $safeResult = \swoole_async_readfile($filename, $callback);
    if ($safeResult === false) {
        throw SwooleException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @param string $content
 * @param int $offset
 * @param callable $callback
 * @throws SwooleException
 *
 */
function swoole_async_write(string $filename, string $content, ?int $offset = null, ?callable $callback = null): void
{
    error_clear_last();
    if ($callback !== null) {
        $safeResult = \swoole_async_write($filename, $content, $offset, $callback);
    } elseif ($offset !== null) {
        $safeResult = \swoole_async_write($filename, $content, $offset);
    } else {
        $safeResult = \swoole_async_write($filename, $content);
    }
    if ($safeResult === false) {
        throw SwooleException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @param string $content
 * @param callable $callback
 * @param int $flags
 * @throws SwooleException
 *
 */
function swoole_async_writefile(string $filename, string $content, ?callable $callback = null, int $flags = 0): void
{
    error_clear_last();
    if ($flags !== 0) {
        $safeResult = \swoole_async_writefile($filename, $content, $callback, $flags);
    } elseif ($callback !== null) {
        $safeResult = \swoole_async_writefile($filename, $content, $callback);
    } else {
        $safeResult = \swoole_async_writefile($filename, $content);
    }
    if ($safeResult === false) {
        throw SwooleException::createFromPhpError();
    }
}


/**
 * @param callable $callback
 * @throws SwooleException
 *
 */
function swoole_event_defer(callable $callback): void
{
    error_clear_last();
    $safeResult = \swoole_event_defer($callback);
    if ($safeResult === false) {
        throw SwooleException::createFromPhpError();
    }
}


/**
 * @param int $fd
 * @throws SwooleException
 *
 */
function swoole_event_del(int $fd): void
{
    error_clear_last();
    $safeResult = \swoole_event_del($fd);
    if ($safeResult === false) {
        throw SwooleException::createFromPhpError();
    }
}


/**
 * @param int $fd
 * @param string $data
 * @throws SwooleException
 *
 */
function swoole_event_write(int $fd, string $data): void
{
    error_clear_last();
    $safeResult = \swoole_event_write($fd, $data);
    if ($safeResult === false) {
        throw SwooleException::createFromPhpError();
    }
}
<?php

return [
    'apache_getenv',
    'apache_get_version',
    'apache_lookup_uri',
    'apache_setenv',
    'apcu_cache_info',
    'apcu_cas',
    'apcu_dec',
    'apcu_fetch',
    'apcu_inc',
    'apcu_sma_info',
    'assert_options',
    'base64_decode',
    'bindtextdomain',
    'bzclose',
    'bzflush',
    'bzopen',
    'bzread',
    'bzwrite',
    'chdir',
    'chgrp',
    'chmod',
    'chown',
    'chroot',
    'class_alias',
    'class_implements',
    'class_parents',
    'class_uses',
    'cli_set_process_title',
    'closelog',
    'com_create_guid',
    'com_event_sink',
    'com_load_typelib',
    'com_print_typeinfo',
    'convert_uudecode',
    'copy',
    'cubrid_bind',
    'cubrid_column_names',
    'cubrid_column_types',
    'cubrid_col_size',
    'cubrid_commit',
    'cubrid_connect',
    'cubrid_connect_with_url',
    'cubrid_current_oid',
    'cubrid_disconnect',
    'cubrid_drop',
    'cubrid_free_result',
    'cubrid_get_charset',
    'cubrid_get_class_name',
    'cubrid_get_client_info',
    'cubrid_get_db_parameter',
    'cubrid_get_query_timeout',
    'cubrid_get_server_info',
    'cubrid_insert_id',
    'cubrid_lob2_bind',
    'cubrid_lob2_close',
    'cubrid_lob2_export',
    'cubrid_lob2_import',
    'cubrid_lob2_new',
    'cubrid_lob2_read',
    'cubrid_lob2_seek',
    'cubrid_lob2_seek64',
    'cubrid_lob2_size',
    'cubrid_lob2_size64',
    'cubrid_lob2_tell',
    'cubrid_lob2_tell64',
    'cubrid_lob2_write',
    'cubrid_lob_close',
    'cubrid_lob_export',
    'cubrid_lob_get',
    'cubrid_lob_send',
    'cubrid_lob_size',
    'cubrid_lock_read',
    'cubrid_lock_write',
    'cubrid_move_cursor',
    'cubrid_next_result',
    'cubrid_pconnect',
    'cubrid_pconnect_with_url',
    'cubrid_prepare',
    'cubrid_put',
    'cubrid_rollback',
    'cubrid_schema',
    'cubrid_seq_drop',
    'cubrid_seq_insert',
    'cubrid_seq_put',
    'cubrid_set_add',
    'cubrid_set_autocommit',
    'cubrid_set_db_parameter',
    'cubrid_set_drop',
    'cubrid_set_query_timeout',
    'curl_copy_handle',
    'curl_escape',
    'curl_exec',
    'curl_getinfo',
    'curl_init',
    'curl_multi_info_read',
    'curl_multi_init',
    'curl_multi_setopt',
    'curl_setopt',
    'curl_share_setopt',
    'curl_unescape',
    'date_parse_from_format',
    'date_sunrise',
    'date_sunset',
    'db2_autocommit',
    'db2_bind_param',
    'db2_client_info',
    'db2_close',
    'db2_commit',
    'db2_execute',
    'db2_free_result',
    'db2_free_stmt',
    'db2_get_option',
    'db2_pclose',
    'db2_rollback',
    'db2_server_info',
    'db2_set_option',
    'define',
    'deflate_add',
    'deflate_init',
    'dir',
    'disk_free_space',
    'disk_total_space',
    'dl',
    'dns_get_record',
    'eio_busy',
    'eio_chmod',
    'eio_chown',
    'eio_close',
    'eio_custom',
    'eio_dup2',
    'eio_event_loop',
    'eio_fallocate',
    'eio_fchmod',
    'eio_fchown',
    'eio_fdatasync',
    'eio_fstat',
    'eio_fstatvfs',
    'eio_fsync',
    'eio_ftruncate',
    'eio_futime',
    'eio_grp',
    'eio_lstat',
    'eio_mkdir',
    'eio_mknod',
    'eio_nop',
    'eio_readahead',
    'eio_readdir',
    'eio_readlink',
    'eio_rename',
    'eio_rmdir',
    'eio_seek',
    'eio_sendfile',
    'eio_stat',
    'eio_statvfs',
    'eio_symlink',
    'eio_sync',
    'eio_syncfs',
    'eio_sync_file_range',
    'eio_truncate',
    'eio_unlink',
    'eio_utime',
    'eio_write',
    'error_log',
    'exec',
    'fastcgi_finish_request',
    'fbird_blob_cancel',
    'fclose',
    'fdatasync',
    'fflush',
    'fgetcsv',
    'file',
    'fileatime',
    'filectime',
    'fileinode',
    'filemtime',
    'fileowner',
    'fileperms',
    'filesize',
    'filetype',
    'file_get_contents',
    'file_put_contents',
    'filter_input_array',
    'filter_var_array',
    'finfo_close',
    'finfo_open',
    'flock',
    'fopen',
    'fputcsv',
    'fread',
    'fsockopen',
    'fstat',
    'fsync',
    'ftell',
    'ftp_alloc',
    'ftp_append',
    'ftp_cdup',
    'ftp_chdir',
    'ftp_chmod',
    'ftp_close',
    'ftp_connect',
    'ftp_delete',
    'ftp_fget',
    'ftp_fput',
    'ftp_get',
    'ftp_login',
    'ftp_mkdir',
    'ftp_mlsd',
    'ftp_nb_put',
    'ftp_nlist',
    'ftp_pasv',
    'ftp_put',
    'ftp_pwd',
    'ftp_raw',
    'ftp_rename',
    'ftp_rmdir',
    'ftp_site',
    'ftp_size',
    'ftp_ssl_connect',
    'ftp_systype',
    'ftruncate',
    'fwrite',
    'getcwd',
    'gethostname',
    'getimagesize',
    'getlastmod',
    'getmygid',
    'getmyinode',
    'getmypid',
    'getmyuid',
    'getopt',
    'getprotobyname',
    'getprotobynumber',
    'getrusage',
    'getservbyport',
    'get_cfg_var',
    'get_headers',
    'get_include_path',
    'get_meta_tags',
    'glob',
    'gmmktime',
    'gmstrftime',
    'gnupg_adddecryptkey',
    'gnupg_addencryptkey',
    'gnupg_addsignkey',
    'gnupg_cleardecryptkeys',
    'gnupg_clearencryptkeys',
    'gnupg_clearsignkeys',
    'gnupg_setarmor',
    'gnupg_setsignmode',
    'gzclose',
    'gzcompress',
    'gzdecode',
    'gzdeflate',
    'gzencode',
    'gzfile',
    'gzgets',
    'gzinflate',
    'gzopen',
    'gzpassthru',
    'gzread',
    'gzrewind',
    'gztell',
    'gzuncompress',
    'gzwrite',
    'hash_file',
    'hash_hmac_file',
    'hash_update_file',
    'header_register_callback',
    'hex2bin',
    'highlight_file',
    'highlight_string',
    'hrtime',
    'ibase_add_user',
    'ibase_backup',
    'ibase_blob_cancel',
    'ibase_blob_create',
    'ibase_blob_get',
    'ibase_close',
    'ibase_commit',
    'ibase_commit_ret',
    'ibase_connect',
    'ibase_delete_user',
    'ibase_drop_db',
    'ibase_free_event_handler',
    'ibase_free_query',
    'ibase_free_result',
    'ibase_maintain_db',
    'ibase_modify_user',
    'ibase_name_result',
    'ibase_pconnect',
    'ibase_restore',
    'ibase_rollback',
    'ibase_rollback_ret',
    'ibase_service_attach',
    'ibase_service_detach',
    'iconv',
    'iconv_get_encoding',
    'iconv_mime_decode',
    'iconv_mime_encode',
    'iconv_set_encoding',
    'iconv_strlen',
    'idate',
    'imageaffine',
    'imageaffinematrixconcat',
    'imageaffinematrixget',
    'imagealphablending',
    'imageantialias',
    'imagearc',
    'imageavif',
    'imagebmp',
    'imagechar',
    'imagecharup',
    'imagecolorat',
    'imagecolordeallocate',
    'imagecolormatch',
    'imagecolorset',
    'imageconvolution',
    'imagecopy',
    'imagecopymerge',
    'imagecopymergegray',
    'imagecopyresampled',
    'imagecopyresized',
    'imagecreate',
    'imagecreatefromavif',
    'imagecreatefrombmp',
    'imagecreatefromgd',
    'imagecreatefromgd2',
    'imagecreatefromgd2part',
    'imagecreatefromgif',
    'imagecreatefromjpeg',
    'imagecreatefrompng',
    'imagecreatefromstring',
    'imagecreatefromtga',
    'imagecreatefromwbmp',
    'imagecreatefromwebp',
    'imagecreatefromxbm',
    'imagecreatefromxpm',
    'imagecreatetruecolor',
    'imagecrop',
    'imagecropauto',
    'imagedashedline',
    'imagedestroy',
    'imageellipse',
    'imagefill',
    'imagefilledarc',
    'imagefilledellipse',
    'imagefilledrectangle',
    'imagefilltoborder',
    'imagefilter',
    'imageflip',
    'imageftbbox',
    'imagefttext',
    'imagegammacorrect',
    'imagegd',
    'imagegd2',
    'imagegif',
    'imagegrabscreen',
    'imagegrabwindow',
    'imagejpeg',
    'imagelayereffect',
    'imageline',
    'imageloadfont',
    'imagepng',
    'imagerectangle',
    'imageresolution',
    'imagerotate',
    'imagesavealpha',
    'imagescale',
    'imagesetbrush',
    'imagesetclip',
    'imagesetinterpolation',
    'imagesetpixel',
    'imagesetstyle',
    'imagesetthickness',
    'imagesettile',
    'imagestring',
    'imagestringup',
    'imagetruecolortopalette',
    'imagettfbbox',
    'imagettftext',
    'imagewbmp',
    'imagewebp',
    'imagexbm',
    'image_type_to_extension',
    'imap_8bit',
    'imap_append',
    'imap_base64',
    'imap_binary',
    'imap_body',
    'imap_bodystruct',
    'imap_check',
    'imap_clearflag_full',
    'imap_close',
    'imap_createmailbox',
    'imap_deletemailbox',
    'imap_fetchbody',
    'imap_fetchheader',
    'imap_fetchmime',
    'imap_fetchstructure',
    'imap_fetch_overview',
    'imap_gc',
    'imap_getacl',
    'imap_getmailboxes',
    'imap_getsubscribed',
    'imap_headerinfo',
    'imap_headers',
    'imap_listscan',
    'imap_lsub',
    'imap_mail',
    'imap_mailboxmsginfo',
    'imap_mail_compose',
    'imap_mail_copy',
    'imap_mail_move',
    'imap_mime_header_decode',
    'imap_mutf7_to_utf8',
    'imap_num_msg',
    'imap_open',
    'imap_qprint',
    'imap_renamemailbox',
    'imap_rfc822_write_address',
    'imap_savebody',
    'imap_setacl',
    'imap_setflag_full',
    'imap_set_quota',
    'imap_sort',
    'imap_status',
    'imap_subscribe',
    'imap_thread',
    'imap_timeout',
    'imap_undelete',
    'imap_unsubscribe',
    'imap_utf8_to_mutf7',
    'inet_ntop',
    'inet_pton',
    'inflate_add',
    'inflate_get_read_len',
    'inflate_get_status',
    'inflate_init',
    'ini_get',
    'ini_set',
    'inotify_init',
    'inotify_rm_watch',
    'iptcembed',
    'iptcparse',
    'json_decode',
    'json_encode',
    'lchgrp',
    'lchown',
    'ldap_8859_to_t61',
    'ldap_add',
    'ldap_bind',
    'ldap_compare',
    'ldap_control_paged_result',
    'ldap_control_paged_result_response',
    'ldap_count_entries',
    'ldap_delete',
    'ldap_dn2ufn',
    'ldap_exop',
    'ldap_exop_passwd',
    'ldap_exop_whoami',
    'ldap_explode_dn',
    'ldap_first_attribute',
    'ldap_first_entry',
    'ldap_free_result',
    'ldap_get_attributes',
    'ldap_get_dn',
    'ldap_get_entries',
    'ldap_get_option',
    'ldap_get_values',
    'ldap_get_values_len',
    'ldap_modify_batch',
    'ldap_mod_add',
    'ldap_mod_del',
    'ldap_mod_replace',
    'ldap_next_attribute',
    'ldap_parse_exop',
    'ldap_parse_result',
    'ldap_rename',
    'ldap_sasl_bind',
    'ldap_set_option',
    'ldap_unbind',
    'libxml_set_external_entity_loader',
    'link',
    'lstat',
    'lzf_compress',
    'lzf_decompress',
    'mailparse_msg_extract_part_file',
    'mailparse_msg_free',
    'mailparse_msg_parse',
    'mailparse_msg_parse_file',
    'mailparse_stream_encode',
    'mb_chr',
    'mb_convert_encoding',
    'mb_convert_variables',
    'mb_detect_order',
    'mb_encoding_aliases',
    'mb_eregi_replace',
    'mb_ereg_replace',
    'mb_ereg_replace_callback',
    'mb_ereg_search_getregs',
    'mb_ereg_search_init',
    'mb_ereg_search_regs',
    'mb_ereg_search_setpos',
    'mb_get_info',
    'mb_http_output',
    'mb_internal_encoding',
    'mb_ord',
    'mb_parse_str',
    'mb_regex_encoding',
    'mb_send_mail',
    'mb_split',
    'md5_file',
    'mime_content_type',
    'mkdir',
    'mktime',
    'msg_get_queue',
    'msg_queue_exists',
    'msg_receive',
    'msg_remove_queue',
    'msg_send',
    'msg_set_queue',
    'msg_stat_queue',
    'mysql_close',
    'mysql_connect',
    'mysql_create_db',
    'mysql_data_seek',
    'mysql_db_name',
    'mysql_db_query',
    'mysql_drop_db',
    'mysql_fetch_lengths',
    'mysql_field_flags',
    'mysql_field_len',
    'mysql_field_name',
    'mysql_field_seek',
    'mysql_free_result',
    'mysql_get_host_info',
    'mysql_get_proto_info',
    'mysql_get_server_info',
    'mysql_info',
    'mysql_list_dbs',
    'mysql_list_fields',
    'mysql_list_processes',
    'mysql_list_tables',
    'mysql_num_fields',
    'mysql_num_rows',
    'mysql_query',
    'mysql_real_escape_string',
    'mysql_result',
    'mysql_select_db',
    'mysql_set_charset',
    'mysql_tablename',
    'mysql_thread_id',
    'mysql_unbuffered_query',
    'ob_clean',
    'ob_end_clean',
    'ob_end_flush',
    'ob_flush',
    'ob_get_clean',
    'ob_start',
    'oci_bind_array_by_name',
    'oci_bind_by_name',
    'oci_cancel',
    'oci_commit',
    'oci_connect',
    'oci_define_by_name',
    'oci_execute',
    'oci_field_name',
    'oci_field_precision',
    'oci_field_scale',
    'oci_field_size',
    'oci_field_type',
    'oci_field_type_raw',
    'oci_free_descriptor',
    'oci_free_statement',
    'oci_new_collection',
    'oci_new_connect',
    'oci_new_cursor',
    'oci_new_descriptor',
    'oci_num_rows',
    'oci_parse',
    'oci_pconnect',
    'oci_register_taf_callback',
    'oci_result',
    'oci_rollback',
    'oci_server_version',
    'oci_set_action',
    'oci_set_call_timeout',
    'oci_set_client_identifier',
    'oci_set_client_info',
    'oci_set_db_operation',
    'oci_set_edition',
    'oci_set_module_name',
    'oci_set_prefetch',
    'oci_statement_type',
    'oci_unregister_taf_callback',
    'odbc_autocommit',
    'odbc_binmode',
    'odbc_columnprivileges',
    'odbc_columns',
    'odbc_commit',
    'odbc_connect',
    'odbc_cursor',
    'odbc_data_source',
    'odbc_exec',
    'odbc_execute',
    'odbc_fetch_into',
    'odbc_field_len',
    'odbc_field_name',
    'odbc_field_num',
    'odbc_field_scale',
    'odbc_field_type',
    'odbc_foreignkeys',
    'odbc_gettypeinfo',
    'odbc_longreadlen',
    'odbc_num_fields',
    'odbc_pconnect',
    'odbc_prepare',
    'odbc_primarykeys',
    'odbc_procedurecolumns',
    'odbc_procedures',
    'odbc_result',
    'odbc_result_all',
    'odbc_rollback',
    'odbc_setoption',
    'odbc_specialcolumns',
    'odbc_statistics',
    'odbc_tableprivileges',
    'odbc_tables',
    'opcache_compile_file',
    'opcache_get_status',
    'opendir',
    'openlog',
    'openssl_cipher_iv_length',
    'openssl_cms_decrypt',
    'openssl_cms_encrypt',
    'openssl_cms_read',
    'openssl_cms_sign',
    'openssl_cms_verify',
    'openssl_csr_export',
    'openssl_csr_export_to_file',
    'openssl_csr_get_public_key',
    'openssl_csr_get_subject',
    'openssl_csr_new',
    'openssl_csr_sign',
    'openssl_decrypt',
    'openssl_dh_compute_key',
    'openssl_digest',
    'openssl_encrypt',
    'openssl_get_curve_names',
    'openssl_open',
    'openssl_pbkdf2',
    'openssl_pkcs7_decrypt',
    'openssl_pkcs7_encrypt',
    'openssl_pkcs7_read',
    'openssl_pkcs7_sign',
    'openssl_pkcs12_export',
    'openssl_pkcs12_export_to_file',
    'openssl_pkcs12_read',
    'openssl_pkey_derive',
    'openssl_pkey_export',
    'openssl_pkey_export_to_file',
    'openssl_pkey_get_details',
    'openssl_pkey_get_private',
    'openssl_pkey_get_public',
    'openssl_pkey_new',
    'openssl_private_decrypt',
    'openssl_private_encrypt',
    'openssl_public_decrypt',
    'openssl_public_encrypt',
    'openssl_seal',
    'openssl_sign',
    'openssl_spki_export',
    'openssl_spki_export_challenge',
    'openssl_spki_new',
    'openssl_spki_verify',
    'openssl_verify',
    'openssl_x509_checkpurpose',
    'openssl_x509_export',
    'openssl_x509_export_to_file',
    'openssl_x509_fingerprint',
    'openssl_x509_read',
    'output_add_rewrite_var',
    'output_reset_rewrite_vars',
    'parse_ini_file',
    'parse_ini_string',
    'parse_url',
    'passthru',
    'pclose',
    'pcntl_getpriority',
    'pcntl_setpriority',
    'pcntl_signal',
    'pcntl_signal_dispatch',
    'pcntl_sigprocmask',
    'pcntl_sigtimedwait',
    'pcntl_sigwaitinfo',
    'pfsockopen',
    'pg_cancel_query',
    'pg_connect',
    'pg_connection_reset',
    'pg_convert',
    'pg_copy_from',
    'pg_copy_to',
    'pg_delete',
    'pg_end_copy',
    'pg_execute',
    'pg_field_num',
    'pg_field_table',
    'pg_flush',
    'pg_free_result',
    'pg_host',
    'pg_insert',
    'pg_last_oid',
    'pg_lo_close',
    'pg_lo_export',
    'pg_lo_import',
    'pg_lo_open',
    'pg_lo_read',
    'pg_lo_seek',
    'pg_lo_truncate',
    'pg_lo_unlink',
    'pg_lo_write',
    'pg_meta_data',
    'pg_parameter_status',
    'pg_pconnect',
    'pg_ping',
    'pg_prepare',
    'pg_put_line',
    'pg_query',
    'pg_query_params',
    'pg_result_error_field',
    'pg_result_seek',
    'pg_select',
    'pg_socket',
    'pg_trace',
    'pg_update',
    'phpcredits',
    'phpinfo',
    'php_sapi_name',
    'popen',
    'posix_access',
    'posix_getgrgid',
    'posix_getgrnam',
    'posix_getgroups',
    'posix_getlogin',
    'posix_getpgid',
    'posix_getpwuid',
    'posix_getrlimit',
    'posix_getsid',
    'posix_initgroups',
    'posix_kill',
    'posix_mkfifo',
    'posix_mknod',
    'posix_setegid',
    'posix_seteuid',
    'posix_setgid',
    'posix_setpgid',
    'posix_setrlimit',
    'posix_setsid',
    'posix_setuid',
    'posix_times',
    'posix_uname',
    'preg_grep',
    'preg_match',
    'preg_match_all',
    'preg_replace',
    'preg_replace_callback',
    'preg_replace_callback_array',
    'preg_split',
    'proc_close',
    'proc_nice',
    'proc_open',
    'pspell_add_to_personal',
    'pspell_add_to_session',
    'pspell_clear_session',
    'pspell_config_create',
    'pspell_config_data_dir',
    'pspell_config_dict_dir',
    'pspell_config_ignore',
    'pspell_config_mode',
    'pspell_config_personal',
    'pspell_config_repl',
    'pspell_config_runtogether',
    'pspell_config_save_repl',
    'pspell_new',
    'pspell_new_config',
    'pspell_new_personal',
    'pspell_save_wordlist',
    'pspell_store_replacement',
    'ps_add_launchlink',
    'ps_add_locallink',
    'ps_add_note',
    'ps_add_pdflink',
    'ps_add_weblink',
    'ps_arc',
    'ps_arcn',
    'ps_begin_page',
    'ps_begin_pattern',
    'ps_begin_template',
    'ps_circle',
    'ps_clip',
    'ps_close',
    'ps_closepath',
    'ps_closepath_stroke',
    'ps_close_image',
    'ps_continue_text',
    'ps_curveto',
    'ps_delete',
    'ps_end_page',
    'ps_end_pattern',
    'ps_end_template',
    'ps_fill',
    'ps_fill_stroke',
    'ps_get_parameter',
    'ps_hyphenate',
    'ps_include_file',
    'ps_lineto',
    'ps_moveto',
    'ps_new',
    'ps_open_file',
    'ps_place_image',
    'ps_rect',
    'ps_restore',
    'ps_rotate',
    'ps_save',
    'ps_scale',
    'ps_setcolor',
    'ps_setdash',
    'ps_setflat',
    'ps_setfont',
    'ps_setgray',
    'ps_setlinecap',
    'ps_setlinejoin',
    'ps_setlinewidth',
    'ps_setmiterlimit',
    'ps_setoverprintmode',
    'ps_setpolydash',
    'ps_set_border_color',
    'ps_set_border_dash',
    'ps_set_border_style',
    'ps_set_info',
    'ps_set_parameter',
    'ps_set_text_pos',
    'ps_set_value',
    'ps_shading',
    'ps_shading_pattern',
    'ps_shfill',
    'ps_show',
    'ps_show2',
    'ps_show_xy',
    'ps_show_xy2',
    'ps_stroke',
    'ps_symbol',
    'ps_translate',
    'putenv',
    'readfile',
    'readgzfile',
    'readline_add_history',
    'readline_callback_handler_install',
    'readline_clear_history',
    'readline_completion_function',
    'readline_read_history',
    'readline_write_history',
    'readlink',
    'realpath',
    'register_tick_function',
    'rename',
    'rewind',
    'rmdir',
    'rpmaddtag',
    'rrd_create',
    'rrd_first',
    'rrd_graph',
    'rrd_info',
    'rrd_lastupdate',
    'rrd_restore',
    'rrd_tune',
    'rrd_update',
    'rrd_xport',
    'sapi_windows_cp_conv',
    'sapi_windows_cp_set',
    'sapi_windows_generate_ctrl_event',
    'sapi_windows_set_ctrl_handler',
    'sapi_windows_vt100_support',
    'scandir',
    'sem_acquire',
    'sem_get',
    'sem_release',
    'sem_remove',
    'session_abort',
    'session_cache_expire',
    'session_cache_limiter',
    'session_create_id',
    'session_decode',
    'session_destroy',
    'session_encode',
    'session_gc',
    'session_id',
    'session_module_name',
    'session_name',
    'session_regenerate_id',
    'session_reset',
    'session_save_path',
    'session_start',
    'session_unset',
    'session_write_close',
    'settype',
    'set_include_path',
    'set_time_limit',
    'sha1_file',
    'shell_exec',
    'shmop_delete',
    'shmop_read',
    'shm_attach',
    'shm_detach',
    'shm_put_var',
    'shm_remove',
    'shm_remove_var',
    'simplexml_import_dom',
    'simplexml_load_file',
    'simplexml_load_string',
    'socket_accept',
    'socket_addrinfo_bind',
    'socket_addrinfo_connect',
    'socket_addrinfo_lookup',
    'socket_bind',
    'socket_connect',
    'socket_create',
    'socket_create_listen',
    'socket_create_pair',
    'socket_export_stream',
    'socket_getpeername',
    'socket_getsockname',
    'socket_get_option',
    'socket_import_stream',
    'socket_listen',
    'socket_read',
    'socket_send',
    'socket_sendmsg',
    'socket_sendto',
    'socket_set_block',
    'socket_set_nonblock',
    'socket_set_option',
    'socket_shutdown',
    'socket_write',
    'socket_wsaprotocol_info_export',
    'socket_wsaprotocol_info_import',
    'socket_wsaprotocol_info_release',
    'sodium_crypto_aead_aes256gcm_decrypt',
    'sodium_crypto_aead_chacha20poly1305_decrypt',
    'sodium_crypto_aead_chacha20poly1305_encrypt',
    'sodium_crypto_aead_chacha20poly1305_ietf_decrypt',
    'sodium_crypto_aead_chacha20poly1305_ietf_encrypt',
    'sodium_crypto_aead_xchacha20poly1305_ietf_decrypt',
    'sodium_crypto_aead_xchacha20poly1305_ietf_encrypt',
    'sodium_crypto_box_open',
    'sodium_crypto_box_seal_open',
    'sodium_crypto_generichash_update',
    'sodium_crypto_secretbox_open',
    'sodium_crypto_sign_open',
    'solr_get_version',
    'spl_autoload_register',
    'spl_autoload_unregister',
    'sqlsrv_begin_transaction',
    'sqlsrv_cancel',
    'sqlsrv_client_info',
    'sqlsrv_close',
    'sqlsrv_commit',
    'sqlsrv_configure',
    'sqlsrv_execute',
    'sqlsrv_fetch',
    'sqlsrv_fetch_array',
    'sqlsrv_fetch_object',
    'sqlsrv_free_stmt',
    'sqlsrv_get_field',
    'sqlsrv_next_result',
    'sqlsrv_num_fields',
    'sqlsrv_num_rows',
    'sqlsrv_prepare',
    'sqlsrv_query',
    'sqlsrv_rollback',
    'ssdeep_fuzzy_compare',
    'ssdeep_fuzzy_hash',
    'ssdeep_fuzzy_hash_filename',
    'ssh2_auth_agent',
    'ssh2_auth_hostbased_file',
    'ssh2_auth_password',
    'ssh2_auth_pubkey_file',
    'ssh2_connect',
    'ssh2_disconnect',
    'ssh2_exec',
    'ssh2_forward_accept',
    'ssh2_forward_listen',
    'ssh2_publickey_add',
    'ssh2_publickey_init',
    'ssh2_publickey_remove',
    'ssh2_scp_recv',
    'ssh2_scp_send',
    'ssh2_send_eof',
    'ssh2_sftp',
    'ssh2_sftp_chmod',
    'ssh2_sftp_mkdir',
    'ssh2_sftp_rename',
    'ssh2_sftp_rmdir',
    'ssh2_sftp_symlink',
    'ssh2_sftp_unlink',
    'ssh2_shell',
    'stream_context_set_params',
    'stream_copy_to_stream',
    'stream_filter_append',
    'stream_filter_prepend',
    'stream_filter_register',
    'stream_filter_remove',
    'stream_get_contents',
    'stream_get_line',
    'stream_isatty',
    'stream_resolve_include_path',
    'stream_set_blocking',
    'stream_set_timeout',
    'stream_socket_accept',
    'stream_socket_client',
    'stream_socket_get_name',
    'stream_socket_pair',
    'stream_socket_server',
    'stream_socket_shutdown',
    'stream_supports_lock',
    'stream_wrapper_register',
    'stream_wrapper_restore',
    'stream_wrapper_unregister',
    'strftime',
    'strptime',
    'strtotime',
    'swoole_async_dns_lookup',
    'swoole_async_readfile',
    'swoole_async_write',
    'swoole_async_writefile',
    'swoole_event_defer',
    'swoole_event_del',
    'swoole_event_write',
    'symlink',
    'syslog',
    'system',
    'sys_getloadavg',
    'tempnam',
    'timezone_name_from_abbr',
    'time_nanosleep',
    'time_sleep_until',
    'tmpfile',
    'touch',
    'unixtojd',
    'unlink',
    'unpack',
    'unserialize',
    'uopz_extend',
    'uopz_implement',
    'variant_date_to_timestamp',
    'variant_round',
    'virtual',
    'xdiff_file_bdiff',
    'xdiff_file_bpatch',
    'xdiff_file_diff',
    'xdiff_file_diff_binary',
    'xdiff_file_patch_binary',
    'xdiff_file_rabdiff',
    'xdiff_string_bpatch',
    'xdiff_string_patch',
    'xdiff_string_patch_binary',
    'xmlrpc_set_type',
    'xml_parser_free',
    'xml_set_character_data_handler',
    'xml_set_default_handler',
    'xml_set_element_handler',
    'xml_set_end_namespace_decl_handler',
    'xml_set_external_entity_ref_handler',
    'xml_set_notation_decl_handler',
    'xml_set_object',
    'xml_set_processing_instruction_handler',
    'xml_set_start_namespace_decl_handler',
    'xml_set_unparsed_entity_decl_handler',
    'yaml_parse',
    'yaml_parse_file',
    'yaml_parse_url',
    'yaz_ccl_parse',
    'yaz_close',
    'yaz_connect',
    'yaz_database',
    'yaz_element',
    'yaz_present',
    'yaz_search',
    'yaz_wait',
    'zip_entry_close',
    'zip_entry_compressedsize',
    'zip_entry_compressionmethod',
    'zip_entry_filesize',
    'zip_entry_name',
    'zip_entry_open',
    'zip_entry_read',
    'zlib_decode',
];
<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\Renaming\Rector\FuncCall\RenameFunctionRector;

// This file configures rector/rector to replace all PHP functions with their equivalent "safe" functions.
return static function (RectorConfig $rectorConfig): void {
    $rectorConfig->ruleWithConfiguration(
        RenameFunctionRector::class,
        [            'apache_getenv' => 'Safe\apache_getenv',
            'apache_get_version' => 'Safe\apache_get_version',
            'apache_lookup_uri' => 'Safe\apache_lookup_uri',
            'apache_setenv' => 'Safe\apache_setenv',
            'apcu_cache_info' => 'Safe\apcu_cache_info',
            'apcu_cas' => 'Safe\apcu_cas',
            'apcu_dec' => 'Safe\apcu_dec',
            'apcu_fetch' => 'Safe\apcu_fetch',
            'apcu_inc' => 'Safe\apcu_inc',
            'apcu_sma_info' => 'Safe\apcu_sma_info',
            'assert_options' => 'Safe\assert_options',
            'base64_decode' => 'Safe\base64_decode',
            'bindtextdomain' => 'Safe\bindtextdomain',
            'bzclose' => 'Safe\bzclose',
            'bzflush' => 'Safe\bzflush',
            'bzopen' => 'Safe\bzopen',
            'bzread' => 'Safe\bzread',
            'bzwrite' => 'Safe\bzwrite',
            'chdir' => 'Safe\chdir',
            'chgrp' => 'Safe\chgrp',
            'chmod' => 'Safe\chmod',
            'chown' => 'Safe\chown',
            'chroot' => 'Safe\chroot',
            'class_alias' => 'Safe\class_alias',
            'class_implements' => 'Safe\class_implements',
            'class_parents' => 'Safe\class_parents',
            'class_uses' => 'Safe\class_uses',
            'cli_set_process_title' => 'Safe\cli_set_process_title',
            'closelog' => 'Safe\closelog',
            'com_create_guid' => 'Safe\com_create_guid',
            'com_event_sink' => 'Safe\com_event_sink',
            'com_load_typelib' => 'Safe\com_load_typelib',
            'com_print_typeinfo' => 'Safe\com_print_typeinfo',
            'convert_uudecode' => 'Safe\convert_uudecode',
            'copy' => 'Safe\copy',
            'cubrid_bind' => 'Safe\cubrid_bind',
            'cubrid_column_names' => 'Safe\cubrid_column_names',
            'cubrid_column_types' => 'Safe\cubrid_column_types',
            'cubrid_col_size' => 'Safe\cubrid_col_size',
            'cubrid_commit' => 'Safe\cubrid_commit',
            'cubrid_connect' => 'Safe\cubrid_connect',
            'cubrid_connect_with_url' => 'Safe\cubrid_connect_with_url',
            'cubrid_current_oid' => 'Safe\cubrid_current_oid',
            'cubrid_disconnect' => 'Safe\cubrid_disconnect',
            'cubrid_drop' => 'Safe\cubrid_drop',
            'cubrid_free_result' => 'Safe\cubrid_free_result',
            'cubrid_get_charset' => 'Safe\cubrid_get_charset',
            'cubrid_get_class_name' => 'Safe\cubrid_get_class_name',
            'cubrid_get_client_info' => 'Safe\cubrid_get_client_info',
            'cubrid_get_db_parameter' => 'Safe\cubrid_get_db_parameter',
            'cubrid_get_query_timeout' => 'Safe\cubrid_get_query_timeout',
            'cubrid_get_server_info' => 'Safe\cubrid_get_server_info',
            'cubrid_insert_id' => 'Safe\cubrid_insert_id',
            'cubrid_lob2_bind' => 'Safe\cubrid_lob2_bind',
            'cubrid_lob2_close' => 'Safe\cubrid_lob2_close',
            'cubrid_lob2_export' => 'Safe\cubrid_lob2_export',
            'cubrid_lob2_import' => 'Safe\cubrid_lob2_import',
            'cubrid_lob2_new' => 'Safe\cubrid_lob2_new',
            'cubrid_lob2_read' => 'Safe\cubrid_lob2_read',
            'cubrid_lob2_seek' => 'Safe\cubrid_lob2_seek',
            'cubrid_lob2_seek64' => 'Safe\cubrid_lob2_seek64',
            'cubrid_lob2_size' => 'Safe\cubrid_lob2_size',
            'cubrid_lob2_size64' => 'Safe\cubrid_lob2_size64',
            'cubrid_lob2_tell' => 'Safe\cubrid_lob2_tell',
            'cubrid_lob2_tell64' => 'Safe\cubrid_lob2_tell64',
            'cubrid_lob2_write' => 'Safe\cubrid_lob2_write',
            'cubrid_lob_close' => 'Safe\cubrid_lob_close',
            'cubrid_lob_export' => 'Safe\cubrid_lob_export',
            'cubrid_lob_get' => 'Safe\cubrid_lob_get',
            'cubrid_lob_send' => 'Safe\cubrid_lob_send',
            'cubrid_lob_size' => 'Safe\cubrid_lob_size',
            'cubrid_lock_read' => 'Safe\cubrid_lock_read',
            'cubrid_lock_write' => 'Safe\cubrid_lock_write',
            'cubrid_move_cursor' => 'Safe\cubrid_move_cursor',
            'cubrid_next_result' => 'Safe\cubrid_next_result',
            'cubrid_pconnect' => 'Safe\cubrid_pconnect',
            'cubrid_pconnect_with_url' => 'Safe\cubrid_pconnect_with_url',
            'cubrid_prepare' => 'Safe\cubrid_prepare',
            'cubrid_put' => 'Safe\cubrid_put',
            'cubrid_rollback' => 'Safe\cubrid_rollback',
            'cubrid_schema' => 'Safe\cubrid_schema',
            'cubrid_seq_drop' => 'Safe\cubrid_seq_drop',
            'cubrid_seq_insert' => 'Safe\cubrid_seq_insert',
            'cubrid_seq_put' => 'Safe\cubrid_seq_put',
            'cubrid_set_add' => 'Safe\cubrid_set_add',
            'cubrid_set_autocommit' => 'Safe\cubrid_set_autocommit',
            'cubrid_set_db_parameter' => 'Safe\cubrid_set_db_parameter',
            'cubrid_set_drop' => 'Safe\cubrid_set_drop',
            'cubrid_set_query_timeout' => 'Safe\cubrid_set_query_timeout',
            'curl_copy_handle' => 'Safe\curl_copy_handle',
            'curl_escape' => 'Safe\curl_escape',
            'curl_exec' => 'Safe\curl_exec',
            'curl_getinfo' => 'Safe\curl_getinfo',
            'curl_init' => 'Safe\curl_init',
            'curl_multi_info_read' => 'Safe\curl_multi_info_read',
            'curl_multi_init' => 'Safe\curl_multi_init',
            'curl_multi_setopt' => 'Safe\curl_multi_setopt',
            'curl_setopt' => 'Safe\curl_setopt',
            'curl_share_setopt' => 'Safe\curl_share_setopt',
            'curl_unescape' => 'Safe\curl_unescape',
            'date_parse_from_format' => 'Safe\date_parse_from_format',
            'date_sunrise' => 'Safe\date_sunrise',
            'date_sunset' => 'Safe\date_sunset',
            'db2_autocommit' => 'Safe\db2_autocommit',
            'db2_bind_param' => 'Safe\db2_bind_param',
            'db2_client_info' => 'Safe\db2_client_info',
            'db2_close' => 'Safe\db2_close',
            'db2_commit' => 'Safe\db2_commit',
            'db2_execute' => 'Safe\db2_execute',
            'db2_free_result' => 'Safe\db2_free_result',
            'db2_free_stmt' => 'Safe\db2_free_stmt',
            'db2_get_option' => 'Safe\db2_get_option',
            'db2_pclose' => 'Safe\db2_pclose',
            'db2_rollback' => 'Safe\db2_rollback',
            'db2_server_info' => 'Safe\db2_server_info',
            'db2_set_option' => 'Safe\db2_set_option',
            'define' => 'Safe\define',
            'deflate_add' => 'Safe\deflate_add',
            'deflate_init' => 'Safe\deflate_init',
            'dir' => 'Safe\dir',
            'disk_free_space' => 'Safe\disk_free_space',
            'disk_total_space' => 'Safe\disk_total_space',
            'dl' => 'Safe\dl',
            'dns_get_record' => 'Safe\dns_get_record',
            'eio_busy' => 'Safe\eio_busy',
            'eio_chmod' => 'Safe\eio_chmod',
            'eio_chown' => 'Safe\eio_chown',
            'eio_close' => 'Safe\eio_close',
            'eio_custom' => 'Safe\eio_custom',
            'eio_dup2' => 'Safe\eio_dup2',
            'eio_event_loop' => 'Safe\eio_event_loop',
            'eio_fallocate' => 'Safe\eio_fallocate',
            'eio_fchmod' => 'Safe\eio_fchmod',
            'eio_fchown' => 'Safe\eio_fchown',
            'eio_fdatasync' => 'Safe\eio_fdatasync',
            'eio_fstat' => 'Safe\eio_fstat',
            'eio_fstatvfs' => 'Safe\eio_fstatvfs',
            'eio_fsync' => 'Safe\eio_fsync',
            'eio_ftruncate' => 'Safe\eio_ftruncate',
            'eio_futime' => 'Safe\eio_futime',
            'eio_grp' => 'Safe\eio_grp',
            'eio_lstat' => 'Safe\eio_lstat',
            'eio_mkdir' => 'Safe\eio_mkdir',
            'eio_mknod' => 'Safe\eio_mknod',
            'eio_nop' => 'Safe\eio_nop',
            'eio_readahead' => 'Safe\eio_readahead',
            'eio_readdir' => 'Safe\eio_readdir',
            'eio_readlink' => 'Safe\eio_readlink',
            'eio_rename' => 'Safe\eio_rename',
            'eio_rmdir' => 'Safe\eio_rmdir',
            'eio_seek' => 'Safe\eio_seek',
            'eio_sendfile' => 'Safe\eio_sendfile',
            'eio_stat' => 'Safe\eio_stat',
            'eio_statvfs' => 'Safe\eio_statvfs',
            'eio_symlink' => 'Safe\eio_symlink',
            'eio_sync' => 'Safe\eio_sync',
            'eio_syncfs' => 'Safe\eio_syncfs',
            'eio_sync_file_range' => 'Safe\eio_sync_file_range',
            'eio_truncate' => 'Safe\eio_truncate',
            'eio_unlink' => 'Safe\eio_unlink',
            'eio_utime' => 'Safe\eio_utime',
            'eio_write' => 'Safe\eio_write',
            'error_log' => 'Safe\error_log',
            'exec' => 'Safe\exec',
            'fastcgi_finish_request' => 'Safe\fastcgi_finish_request',
            'fbird_blob_cancel' => 'Safe\fbird_blob_cancel',
            'fclose' => 'Safe\fclose',
            'fdatasync' => 'Safe\fdatasync',
            'fflush' => 'Safe\fflush',
            'fgetcsv' => 'Safe\fgetcsv',
            'file' => 'Safe\file',
            'fileatime' => 'Safe\fileatime',
            'filectime' => 'Safe\filectime',
            'fileinode' => 'Safe\fileinode',
            'filemtime' => 'Safe\filemtime',
            'fileowner' => 'Safe\fileowner',
            'fileperms' => 'Safe\fileperms',
            'filesize' => 'Safe\filesize',
            'filetype' => 'Safe\filetype',
            'file_get_contents' => 'Safe\file_get_contents',
            'file_put_contents' => 'Safe\file_put_contents',
            'filter_input_array' => 'Safe\filter_input_array',
            'filter_var_array' => 'Safe\filter_var_array',
            'finfo_close' => 'Safe\finfo_close',
            'finfo_open' => 'Safe\finfo_open',
            'flock' => 'Safe\flock',
            'fopen' => 'Safe\fopen',
            'fputcsv' => 'Safe\fputcsv',
            'fread' => 'Safe\fread',
            'fsockopen' => 'Safe\fsockopen',
            'fstat' => 'Safe\fstat',
            'fsync' => 'Safe\fsync',
            'ftell' => 'Safe\ftell',
            'ftp_alloc' => 'Safe\ftp_alloc',
            'ftp_append' => 'Safe\ftp_append',
            'ftp_cdup' => 'Safe\ftp_cdup',
            'ftp_chdir' => 'Safe\ftp_chdir',
            'ftp_chmod' => 'Safe\ftp_chmod',
            'ftp_close' => 'Safe\ftp_close',
            'ftp_connect' => 'Safe\ftp_connect',
            'ftp_delete' => 'Safe\ftp_delete',
            'ftp_fget' => 'Safe\ftp_fget',
            'ftp_fput' => 'Safe\ftp_fput',
            'ftp_get' => 'Safe\ftp_get',
            'ftp_login' => 'Safe\ftp_login',
            'ftp_mkdir' => 'Safe\ftp_mkdir',
            'ftp_mlsd' => 'Safe\ftp_mlsd',
            'ftp_nb_put' => 'Safe\ftp_nb_put',
            'ftp_nlist' => 'Safe\ftp_nlist',
            'ftp_pasv' => 'Safe\ftp_pasv',
            'ftp_put' => 'Safe\ftp_put',
            'ftp_pwd' => 'Safe\ftp_pwd',
            'ftp_raw' => 'Safe\ftp_raw',
            'ftp_rename' => 'Safe\ftp_rename',
            'ftp_rmdir' => 'Safe\ftp_rmdir',
            'ftp_site' => 'Safe\ftp_site',
            'ftp_size' => 'Safe\ftp_size',
            'ftp_ssl_connect' => 'Safe\ftp_ssl_connect',
            'ftp_systype' => 'Safe\ftp_systype',
            'ftruncate' => 'Safe\ftruncate',
            'fwrite' => 'Safe\fwrite',
            'getcwd' => 'Safe\getcwd',
            'gethostname' => 'Safe\gethostname',
            'getimagesize' => 'Safe\getimagesize',
            'getlastmod' => 'Safe\getlastmod',
            'getmygid' => 'Safe\getmygid',
            'getmyinode' => 'Safe\getmyinode',
            'getmypid' => 'Safe\getmypid',
            'getmyuid' => 'Safe\getmyuid',
            'getopt' => 'Safe\getopt',
            'getprotobyname' => 'Safe\getprotobyname',
            'getprotobynumber' => 'Safe\getprotobynumber',
            'getrusage' => 'Safe\getrusage',
            'getservbyport' => 'Safe\getservbyport',
            'get_cfg_var' => 'Safe\get_cfg_var',
            'get_headers' => 'Safe\get_headers',
            'get_include_path' => 'Safe\get_include_path',
            'get_meta_tags' => 'Safe\get_meta_tags',
            'glob' => 'Safe\glob',
            'gmmktime' => 'Safe\gmmktime',
            'gmstrftime' => 'Safe\gmstrftime',
            'gnupg_adddecryptkey' => 'Safe\gnupg_adddecryptkey',
            'gnupg_addencryptkey' => 'Safe\gnupg_addencryptkey',
            'gnupg_addsignkey' => 'Safe\gnupg_addsignkey',
            'gnupg_cleardecryptkeys' => 'Safe\gnupg_cleardecryptkeys',
            'gnupg_clearencryptkeys' => 'Safe\gnupg_clearencryptkeys',
            'gnupg_clearsignkeys' => 'Safe\gnupg_clearsignkeys',
            'gnupg_setarmor' => 'Safe\gnupg_setarmor',
            'gnupg_setsignmode' => 'Safe\gnupg_setsignmode',
            'gzclose' => 'Safe\gzclose',
            'gzcompress' => 'Safe\gzcompress',
            'gzdecode' => 'Safe\gzdecode',
            'gzdeflate' => 'Safe\gzdeflate',
            'gzencode' => 'Safe\gzencode',
            'gzfile' => 'Safe\gzfile',
            'gzgets' => 'Safe\gzgets',
            'gzinflate' => 'Safe\gzinflate',
            'gzopen' => 'Safe\gzopen',
            'gzpassthru' => 'Safe\gzpassthru',
            'gzread' => 'Safe\gzread',
            'gzrewind' => 'Safe\gzrewind',
            'gztell' => 'Safe\gztell',
            'gzuncompress' => 'Safe\gzuncompress',
            'gzwrite' => 'Safe\gzwrite',
            'hash_file' => 'Safe\hash_file',
            'hash_hmac_file' => 'Safe\hash_hmac_file',
            'hash_update_file' => 'Safe\hash_update_file',
            'header_register_callback' => 'Safe\header_register_callback',
            'hex2bin' => 'Safe\hex2bin',
            'highlight_file' => 'Safe\highlight_file',
            'highlight_string' => 'Safe\highlight_string',
            'hrtime' => 'Safe\hrtime',
            'ibase_add_user' => 'Safe\ibase_add_user',
            'ibase_backup' => 'Safe\ibase_backup',
            'ibase_blob_cancel' => 'Safe\ibase_blob_cancel',
            'ibase_blob_create' => 'Safe\ibase_blob_create',
            'ibase_blob_get' => 'Safe\ibase_blob_get',
            'ibase_close' => 'Safe\ibase_close',
            'ibase_commit' => 'Safe\ibase_commit',
            'ibase_commit_ret' => 'Safe\ibase_commit_ret',
            'ibase_connect' => 'Safe\ibase_connect',
            'ibase_delete_user' => 'Safe\ibase_delete_user',
            'ibase_drop_db' => 'Safe\ibase_drop_db',
            'ibase_free_event_handler' => 'Safe\ibase_free_event_handler',
            'ibase_free_query' => 'Safe\ibase_free_query',
            'ibase_free_result' => 'Safe\ibase_free_result',
            'ibase_maintain_db' => 'Safe\ibase_maintain_db',
            'ibase_modify_user' => 'Safe\ibase_modify_user',
            'ibase_name_result' => 'Safe\ibase_name_result',
            'ibase_pconnect' => 'Safe\ibase_pconnect',
            'ibase_restore' => 'Safe\ibase_restore',
            'ibase_rollback' => 'Safe\ibase_rollback',
            'ibase_rollback_ret' => 'Safe\ibase_rollback_ret',
            'ibase_service_attach' => 'Safe\ibase_service_attach',
            'ibase_service_detach' => 'Safe\ibase_service_detach',
            'iconv' => 'Safe\iconv',
            'iconv_get_encoding' => 'Safe\iconv_get_encoding',
            'iconv_mime_decode' => 'Safe\iconv_mime_decode',
            'iconv_mime_encode' => 'Safe\iconv_mime_encode',
            'iconv_set_encoding' => 'Safe\iconv_set_encoding',
            'iconv_strlen' => 'Safe\iconv_strlen',
            'idate' => 'Safe\idate',
            'imageaffine' => 'Safe\imageaffine',
            'imageaffinematrixconcat' => 'Safe\imageaffinematrixconcat',
            'imageaffinematrixget' => 'Safe\imageaffinematrixget',
            'imagealphablending' => 'Safe\imagealphablending',
            'imageantialias' => 'Safe\imageantialias',
            'imagearc' => 'Safe\imagearc',
            'imageavif' => 'Safe\imageavif',
            'imagebmp' => 'Safe\imagebmp',
            'imagechar' => 'Safe\imagechar',
            'imagecharup' => 'Safe\imagecharup',
            'imagecolorat' => 'Safe\imagecolorat',
            'imagecolordeallocate' => 'Safe\imagecolordeallocate',
            'imagecolormatch' => 'Safe\imagecolormatch',
            'imagecolorset' => 'Safe\imagecolorset',
            'imageconvolution' => 'Safe\imageconvolution',
            'imagecopy' => 'Safe\imagecopy',
            'imagecopymerge' => 'Safe\imagecopymerge',
            'imagecopymergegray' => 'Safe\imagecopymergegray',
            'imagecopyresampled' => 'Safe\imagecopyresampled',
            'imagecopyresized' => 'Safe\imagecopyresized',
            'imagecreate' => 'Safe\imagecreate',
            'imagecreatefromavif' => 'Safe\imagecreatefromavif',
            'imagecreatefrombmp' => 'Safe\imagecreatefrombmp',
            'imagecreatefromgd' => 'Safe\imagecreatefromgd',
            'imagecreatefromgd2' => 'Safe\imagecreatefromgd2',
            'imagecreatefromgd2part' => 'Safe\imagecreatefromgd2part',
            'imagecreatefromgif' => 'Safe\imagecreatefromgif',
            'imagecreatefromjpeg' => 'Safe\imagecreatefromjpeg',
            'imagecreatefrompng' => 'Safe\imagecreatefrompng',
            'imagecreatefromstring' => 'Safe\imagecreatefromstring',
            'imagecreatefromtga' => 'Safe\imagecreatefromtga',
            'imagecreatefromwbmp' => 'Safe\imagecreatefromwbmp',
            'imagecreatefromwebp' => 'Safe\imagecreatefromwebp',
            'imagecreatefromxbm' => 'Safe\imagecreatefromxbm',
            'imagecreatefromxpm' => 'Safe\imagecreatefromxpm',
            'imagecreatetruecolor' => 'Safe\imagecreatetruecolor',
            'imagecrop' => 'Safe\imagecrop',
            'imagecropauto' => 'Safe\imagecropauto',
            'imagedashedline' => 'Safe\imagedashedline',
            'imagedestroy' => 'Safe\imagedestroy',
            'imageellipse' => 'Safe\imageellipse',
            'imagefill' => 'Safe\imagefill',
            'imagefilledarc' => 'Safe\imagefilledarc',
            'imagefilledellipse' => 'Safe\imagefilledellipse',
            'imagefilledrectangle' => 'Safe\imagefilledrectangle',
            'imagefilltoborder' => 'Safe\imagefilltoborder',
            'imagefilter' => 'Safe\imagefilter',
            'imageflip' => 'Safe\imageflip',
            'imageftbbox' => 'Safe\imageftbbox',
            'imagefttext' => 'Safe\imagefttext',
            'imagegammacorrect' => 'Safe\imagegammacorrect',
            'imagegd' => 'Safe\imagegd',
            'imagegd2' => 'Safe\imagegd2',
            'imagegif' => 'Safe\imagegif',
            'imagegrabscreen' => 'Safe\imagegrabscreen',
            'imagegrabwindow' => 'Safe\imagegrabwindow',
            'imagejpeg' => 'Safe\imagejpeg',
            'imagelayereffect' => 'Safe\imagelayereffect',
            'imageline' => 'Safe\imageline',
            'imageloadfont' => 'Safe\imageloadfont',
            'imagepng' => 'Safe\imagepng',
            'imagerectangle' => 'Safe\imagerectangle',
            'imageresolution' => 'Safe\imageresolution',
            'imagerotate' => 'Safe\imagerotate',
            'imagesavealpha' => 'Safe\imagesavealpha',
            'imagescale' => 'Safe\imagescale',
            'imagesetbrush' => 'Safe\imagesetbrush',
            'imagesetclip' => 'Safe\imagesetclip',
            'imagesetinterpolation' => 'Safe\imagesetinterpolation',
            'imagesetpixel' => 'Safe\imagesetpixel',
            'imagesetstyle' => 'Safe\imagesetstyle',
            'imagesetthickness' => 'Safe\imagesetthickness',
            'imagesettile' => 'Safe\imagesettile',
            'imagestring' => 'Safe\imagestring',
            'imagestringup' => 'Safe\imagestringup',
            'imagetruecolortopalette' => 'Safe\imagetruecolortopalette',
            'imagettfbbox' => 'Safe\imagettfbbox',
            'imagettftext' => 'Safe\imagettftext',
            'imagewbmp' => 'Safe\imagewbmp',
            'imagewebp' => 'Safe\imagewebp',
            'imagexbm' => 'Safe\imagexbm',
            'image_type_to_extension' => 'Safe\image_type_to_extension',
            'imap_8bit' => 'Safe\imap_8bit',
            'imap_append' => 'Safe\imap_append',
            'imap_base64' => 'Safe\imap_base64',
            'imap_binary' => 'Safe\imap_binary',
            'imap_body' => 'Safe\imap_body',
            'imap_bodystruct' => 'Safe\imap_bodystruct',
            'imap_check' => 'Safe\imap_check',
            'imap_clearflag_full' => 'Safe\imap_clearflag_full',
            'imap_close' => 'Safe\imap_close',
            'imap_createmailbox' => 'Safe\imap_createmailbox',
            'imap_deletemailbox' => 'Safe\imap_deletemailbox',
            'imap_fetchbody' => 'Safe\imap_fetchbody',
            'imap_fetchheader' => 'Safe\imap_fetchheader',
            'imap_fetchmime' => 'Safe\imap_fetchmime',
            'imap_fetchstructure' => 'Safe\imap_fetchstructure',
            'imap_fetch_overview' => 'Safe\imap_fetch_overview',
            'imap_gc' => 'Safe\imap_gc',
            'imap_getacl' => 'Safe\imap_getacl',
            'imap_getmailboxes' => 'Safe\imap_getmailboxes',
            'imap_getsubscribed' => 'Safe\imap_getsubscribed',
            'imap_headerinfo' => 'Safe\imap_headerinfo',
            'imap_headers' => 'Safe\imap_headers',
            'imap_listscan' => 'Safe\imap_listscan',
            'imap_lsub' => 'Safe\imap_lsub',
            'imap_mail' => 'Safe\imap_mail',
            'imap_mailboxmsginfo' => 'Safe\imap_mailboxmsginfo',
            'imap_mail_compose' => 'Safe\imap_mail_compose',
            'imap_mail_copy' => 'Safe\imap_mail_copy',
            'imap_mail_move' => 'Safe\imap_mail_move',
            'imap_mime_header_decode' => 'Safe\imap_mime_header_decode',
            'imap_mutf7_to_utf8' => 'Safe\imap_mutf7_to_utf8',
            'imap_num_msg' => 'Safe\imap_num_msg',
            'imap_open' => 'Safe\imap_open',
            'imap_qprint' => 'Safe\imap_qprint',
            'imap_renamemailbox' => 'Safe\imap_renamemailbox',
            'imap_rfc822_write_address' => 'Safe\imap_rfc822_write_address',
            'imap_savebody' => 'Safe\imap_savebody',
            'imap_setacl' => 'Safe\imap_setacl',
            'imap_setflag_full' => 'Safe\imap_setflag_full',
            'imap_set_quota' => 'Safe\imap_set_quota',
            'imap_sort' => 'Safe\imap_sort',
            'imap_status' => 'Safe\imap_status',
            'imap_subscribe' => 'Safe\imap_subscribe',
            'imap_thread' => 'Safe\imap_thread',
            'imap_timeout' => 'Safe\imap_timeout',
            'imap_undelete' => 'Safe\imap_undelete',
            'imap_unsubscribe' => 'Safe\imap_unsubscribe',
            'imap_utf8_to_mutf7' => 'Safe\imap_utf8_to_mutf7',
            'inet_ntop' => 'Safe\inet_ntop',
            'inet_pton' => 'Safe\inet_pton',
            'inflate_add' => 'Safe\inflate_add',
            'inflate_get_read_len' => 'Safe\inflate_get_read_len',
            'inflate_get_status' => 'Safe\inflate_get_status',
            'inflate_init' => 'Safe\inflate_init',
            'ini_get' => 'Safe\ini_get',
            'ini_set' => 'Safe\ini_set',
            'inotify_init' => 'Safe\inotify_init',
            'inotify_rm_watch' => 'Safe\inotify_rm_watch',
            'iptcembed' => 'Safe\iptcembed',
            'iptcparse' => 'Safe\iptcparse',
            'json_decode' => 'Safe\json_decode',
            'json_encode' => 'Safe\json_encode',
            'lchgrp' => 'Safe\lchgrp',
            'lchown' => 'Safe\lchown',
            'ldap_8859_to_t61' => 'Safe\ldap_8859_to_t61',
            'ldap_add' => 'Safe\ldap_add',
            'ldap_bind' => 'Safe\ldap_bind',
            'ldap_compare' => 'Safe\ldap_compare',
            'ldap_control_paged_result' => 'Safe\ldap_control_paged_result',
            'ldap_control_paged_result_response' => 'Safe\ldap_control_paged_result_response',
            'ldap_count_entries' => 'Safe\ldap_count_entries',
            'ldap_delete' => 'Safe\ldap_delete',
            'ldap_dn2ufn' => 'Safe\ldap_dn2ufn',
            'ldap_exop' => 'Safe\ldap_exop',
            'ldap_exop_passwd' => 'Safe\ldap_exop_passwd',
            'ldap_exop_whoami' => 'Safe\ldap_exop_whoami',
            'ldap_explode_dn' => 'Safe\ldap_explode_dn',
            'ldap_first_attribute' => 'Safe\ldap_first_attribute',
            'ldap_first_entry' => 'Safe\ldap_first_entry',
            'ldap_free_result' => 'Safe\ldap_free_result',
            'ldap_get_attributes' => 'Safe\ldap_get_attributes',
            'ldap_get_dn' => 'Safe\ldap_get_dn',
            'ldap_get_entries' => 'Safe\ldap_get_entries',
            'ldap_get_option' => 'Safe\ldap_get_option',
            'ldap_get_values' => 'Safe\ldap_get_values',
            'ldap_get_values_len' => 'Safe\ldap_get_values_len',
            'ldap_modify_batch' => 'Safe\ldap_modify_batch',
            'ldap_mod_add' => 'Safe\ldap_mod_add',
            'ldap_mod_del' => 'Safe\ldap_mod_del',
            'ldap_mod_replace' => 'Safe\ldap_mod_replace',
            'ldap_next_attribute' => 'Safe\ldap_next_attribute',
            'ldap_parse_exop' => 'Safe\ldap_parse_exop',
            'ldap_parse_result' => 'Safe\ldap_parse_result',
            'ldap_rename' => 'Safe\ldap_rename',
            'ldap_sasl_bind' => 'Safe\ldap_sasl_bind',
            'ldap_set_option' => 'Safe\ldap_set_option',
            'ldap_unbind' => 'Safe\ldap_unbind',
            'libxml_set_external_entity_loader' => 'Safe\libxml_set_external_entity_loader',
            'link' => 'Safe\link',
            'lstat' => 'Safe\lstat',
            'lzf_compress' => 'Safe\lzf_compress',
            'lzf_decompress' => 'Safe\lzf_decompress',
            'mailparse_msg_extract_part_file' => 'Safe\mailparse_msg_extract_part_file',
            'mailparse_msg_free' => 'Safe\mailparse_msg_free',
            'mailparse_msg_parse' => 'Safe\mailparse_msg_parse',
            'mailparse_msg_parse_file' => 'Safe\mailparse_msg_parse_file',
            'mailparse_stream_encode' => 'Safe\mailparse_stream_encode',
            'mb_chr' => 'Safe\mb_chr',
            'mb_convert_encoding' => 'Safe\mb_convert_encoding',
            'mb_convert_variables' => 'Safe\mb_convert_variables',
            'mb_detect_order' => 'Safe\mb_detect_order',
            'mb_encoding_aliases' => 'Safe\mb_encoding_aliases',
            'mb_eregi_replace' => 'Safe\mb_eregi_replace',
            'mb_ereg_replace' => 'Safe\mb_ereg_replace',
            'mb_ereg_replace_callback' => 'Safe\mb_ereg_replace_callback',
            'mb_ereg_search_getregs' => 'Safe\mb_ereg_search_getregs',
            'mb_ereg_search_init' => 'Safe\mb_ereg_search_init',
            'mb_ereg_search_regs' => 'Safe\mb_ereg_search_regs',
            'mb_ereg_search_setpos' => 'Safe\mb_ereg_search_setpos',
            'mb_get_info' => 'Safe\mb_get_info',
            'mb_http_output' => 'Safe\mb_http_output',
            'mb_internal_encoding' => 'Safe\mb_internal_encoding',
            'mb_ord' => 'Safe\mb_ord',
            'mb_parse_str' => 'Safe\mb_parse_str',
            'mb_regex_encoding' => 'Safe\mb_regex_encoding',
            'mb_send_mail' => 'Safe\mb_send_mail',
            'mb_split' => 'Safe\mb_split',
            'md5_file' => 'Safe\md5_file',
            'mime_content_type' => 'Safe\mime_content_type',
            'mkdir' => 'Safe\mkdir',
            'mktime' => 'Safe\mktime',
            'msg_get_queue' => 'Safe\msg_get_queue',
            'msg_queue_exists' => 'Safe\msg_queue_exists',
            'msg_receive' => 'Safe\msg_receive',
            'msg_remove_queue' => 'Safe\msg_remove_queue',
            'msg_send' => 'Safe\msg_send',
            'msg_set_queue' => 'Safe\msg_set_queue',
            'msg_stat_queue' => 'Safe\msg_stat_queue',
            'mysql_close' => 'Safe\mysql_close',
            'mysql_connect' => 'Safe\mysql_connect',
            'mysql_create_db' => 'Safe\mysql_create_db',
            'mysql_data_seek' => 'Safe\mysql_data_seek',
            'mysql_db_name' => 'Safe\mysql_db_name',
            'mysql_db_query' => 'Safe\mysql_db_query',
            'mysql_drop_db' => 'Safe\mysql_drop_db',
            'mysql_fetch_lengths' => 'Safe\mysql_fetch_lengths',
            'mysql_field_flags' => 'Safe\mysql_field_flags',
            'mysql_field_len' => 'Safe\mysql_field_len',
            'mysql_field_name' => 'Safe\mysql_field_name',
            'mysql_field_seek' => 'Safe\mysql_field_seek',
            'mysql_free_result' => 'Safe\mysql_free_result',
            'mysql_get_host_info' => 'Safe\mysql_get_host_info',
            'mysql_get_proto_info' => 'Safe\mysql_get_proto_info',
            'mysql_get_server_info' => 'Safe\mysql_get_server_info',
            'mysql_info' => 'Safe\mysql_info',
            'mysql_list_dbs' => 'Safe\mysql_list_dbs',
            'mysql_list_fields' => 'Safe\mysql_list_fields',
            'mysql_list_processes' => 'Safe\mysql_list_processes',
            'mysql_list_tables' => 'Safe\mysql_list_tables',
            'mysql_num_fields' => 'Safe\mysql_num_fields',
            'mysql_num_rows' => 'Safe\mysql_num_rows',
            'mysql_query' => 'Safe\mysql_query',
            'mysql_real_escape_string' => 'Safe\mysql_real_escape_string',
            'mysql_result' => 'Safe\mysql_result',
            'mysql_select_db' => 'Safe\mysql_select_db',
            'mysql_set_charset' => 'Safe\mysql_set_charset',
            'mysql_tablename' => 'Safe\mysql_tablename',
            'mysql_thread_id' => 'Safe\mysql_thread_id',
            'mysql_unbuffered_query' => 'Safe\mysql_unbuffered_query',
            'ob_clean' => 'Safe\ob_clean',
            'ob_end_clean' => 'Safe\ob_end_clean',
            'ob_end_flush' => 'Safe\ob_end_flush',
            'ob_flush' => 'Safe\ob_flush',
            'ob_get_clean' => 'Safe\ob_get_clean',
            'ob_start' => 'Safe\ob_start',
            'oci_bind_array_by_name' => 'Safe\oci_bind_array_by_name',
            'oci_bind_by_name' => 'Safe\oci_bind_by_name',
            'oci_cancel' => 'Safe\oci_cancel',
            'oci_commit' => 'Safe\oci_commit',
            'oci_connect' => 'Safe\oci_connect',
            'oci_define_by_name' => 'Safe\oci_define_by_name',
            'oci_execute' => 'Safe\oci_execute',
            'oci_field_name' => 'Safe\oci_field_name',
            'oci_field_precision' => 'Safe\oci_field_precision',
            'oci_field_scale' => 'Safe\oci_field_scale',
            'oci_field_size' => 'Safe\oci_field_size',
            'oci_field_type' => 'Safe\oci_field_type',
            'oci_field_type_raw' => 'Safe\oci_field_type_raw',
            'oci_free_descriptor' => 'Safe\oci_free_descriptor',
            'oci_free_statement' => 'Safe\oci_free_statement',
            'oci_new_collection' => 'Safe\oci_new_collection',
            'oci_new_connect' => 'Safe\oci_new_connect',
            'oci_new_cursor' => 'Safe\oci_new_cursor',
            'oci_new_descriptor' => 'Safe\oci_new_descriptor',
            'oci_num_rows' => 'Safe\oci_num_rows',
            'oci_parse' => 'Safe\oci_parse',
            'oci_pconnect' => 'Safe\oci_pconnect',
            'oci_register_taf_callback' => 'Safe\oci_register_taf_callback',
            'oci_result' => 'Safe\oci_result',
            'oci_rollback' => 'Safe\oci_rollback',
            'oci_server_version' => 'Safe\oci_server_version',
            'oci_set_action' => 'Safe\oci_set_action',
            'oci_set_call_timeout' => 'Safe\oci_set_call_timeout',
            'oci_set_client_identifier' => 'Safe\oci_set_client_identifier',
            'oci_set_client_info' => 'Safe\oci_set_client_info',
            'oci_set_db_operation' => 'Safe\oci_set_db_operation',
            'oci_set_edition' => 'Safe\oci_set_edition',
            'oci_set_module_name' => 'Safe\oci_set_module_name',
            'oci_set_prefetch' => 'Safe\oci_set_prefetch',
            'oci_statement_type' => 'Safe\oci_statement_type',
            'oci_unregister_taf_callback' => 'Safe\oci_unregister_taf_callback',
            'odbc_autocommit' => 'Safe\odbc_autocommit',
            'odbc_binmode' => 'Safe\odbc_binmode',
            'odbc_columnprivileges' => 'Safe\odbc_columnprivileges',
            'odbc_columns' => 'Safe\odbc_columns',
            'odbc_commit' => 'Safe\odbc_commit',
            'odbc_connect' => 'Safe\odbc_connect',
            'odbc_cursor' => 'Safe\odbc_cursor',
            'odbc_data_source' => 'Safe\odbc_data_source',
            'odbc_exec' => 'Safe\odbc_exec',
            'odbc_execute' => 'Safe\odbc_execute',
            'odbc_fetch_into' => 'Safe\odbc_fetch_into',
            'odbc_field_len' => 'Safe\odbc_field_len',
            'odbc_field_name' => 'Safe\odbc_field_name',
            'odbc_field_num' => 'Safe\odbc_field_num',
            'odbc_field_scale' => 'Safe\odbc_field_scale',
            'odbc_field_type' => 'Safe\odbc_field_type',
            'odbc_foreignkeys' => 'Safe\odbc_foreignkeys',
            'odbc_gettypeinfo' => 'Safe\odbc_gettypeinfo',
            'odbc_longreadlen' => 'Safe\odbc_longreadlen',
            'odbc_num_fields' => 'Safe\odbc_num_fields',
            'odbc_pconnect' => 'Safe\odbc_pconnect',
            'odbc_prepare' => 'Safe\odbc_prepare',
            'odbc_primarykeys' => 'Safe\odbc_primarykeys',
            'odbc_procedurecolumns' => 'Safe\odbc_procedurecolumns',
            'odbc_procedures' => 'Safe\odbc_procedures',
            'odbc_result' => 'Safe\odbc_result',
            'odbc_result_all' => 'Safe\odbc_result_all',
            'odbc_rollback' => 'Safe\odbc_rollback',
            'odbc_setoption' => 'Safe\odbc_setoption',
            'odbc_specialcolumns' => 'Safe\odbc_specialcolumns',
            'odbc_statistics' => 'Safe\odbc_statistics',
            'odbc_tableprivileges' => 'Safe\odbc_tableprivileges',
            'odbc_tables' => 'Safe\odbc_tables',
            'opcache_compile_file' => 'Safe\opcache_compile_file',
            'opcache_get_status' => 'Safe\opcache_get_status',
            'opendir' => 'Safe\opendir',
            'openlog' => 'Safe\openlog',
            'openssl_cipher_iv_length' => 'Safe\openssl_cipher_iv_length',
            'openssl_cms_decrypt' => 'Safe\openssl_cms_decrypt',
            'openssl_cms_encrypt' => 'Safe\openssl_cms_encrypt',
            'openssl_cms_read' => 'Safe\openssl_cms_read',
            'openssl_cms_sign' => 'Safe\openssl_cms_sign',
            'openssl_cms_verify' => 'Safe\openssl_cms_verify',
            'openssl_csr_export' => 'Safe\openssl_csr_export',
            'openssl_csr_export_to_file' => 'Safe\openssl_csr_export_to_file',
            'openssl_csr_get_public_key' => 'Safe\openssl_csr_get_public_key',
            'openssl_csr_get_subject' => 'Safe\openssl_csr_get_subject',
            'openssl_csr_new' => 'Safe\openssl_csr_new',
            'openssl_csr_sign' => 'Safe\openssl_csr_sign',
            'openssl_decrypt' => 'Safe\openssl_decrypt',
            'openssl_dh_compute_key' => 'Safe\openssl_dh_compute_key',
            'openssl_digest' => 'Safe\openssl_digest',
            'openssl_encrypt' => 'Safe\openssl_encrypt',
            'openssl_get_curve_names' => 'Safe\openssl_get_curve_names',
            'openssl_open' => 'Safe\openssl_open',
            'openssl_pbkdf2' => 'Safe\openssl_pbkdf2',
            'openssl_pkcs7_decrypt' => 'Safe\openssl_pkcs7_decrypt',
            'openssl_pkcs7_encrypt' => 'Safe\openssl_pkcs7_encrypt',
            'openssl_pkcs7_read' => 'Safe\openssl_pkcs7_read',
            'openssl_pkcs7_sign' => 'Safe\openssl_pkcs7_sign',
            'openssl_pkcs12_export' => 'Safe\openssl_pkcs12_export',
            'openssl_pkcs12_export_to_file' => 'Safe\openssl_pkcs12_export_to_file',
            'openssl_pkcs12_read' => 'Safe\openssl_pkcs12_read',
            'openssl_pkey_derive' => 'Safe\openssl_pkey_derive',
            'openssl_pkey_export' => 'Safe\openssl_pkey_export',
            'openssl_pkey_export_to_file' => 'Safe\openssl_pkey_export_to_file',
            'openssl_pkey_get_details' => 'Safe\openssl_pkey_get_details',
            'openssl_pkey_get_private' => 'Safe\openssl_pkey_get_private',
            'openssl_pkey_get_public' => 'Safe\openssl_pkey_get_public',
            'openssl_pkey_new' => 'Safe\openssl_pkey_new',
            'openssl_private_decrypt' => 'Safe\openssl_private_decrypt',
            'openssl_private_encrypt' => 'Safe\openssl_private_encrypt',
            'openssl_public_decrypt' => 'Safe\openssl_public_decrypt',
            'openssl_public_encrypt' => 'Safe\openssl_public_encrypt',
            'openssl_seal' => 'Safe\openssl_seal',
            'openssl_sign' => 'Safe\openssl_sign',
            'openssl_spki_export' => 'Safe\openssl_spki_export',
            'openssl_spki_export_challenge' => 'Safe\openssl_spki_export_challenge',
            'openssl_spki_new' => 'Safe\openssl_spki_new',
            'openssl_spki_verify' => 'Safe\openssl_spki_verify',
            'openssl_verify' => 'Safe\openssl_verify',
            'openssl_x509_checkpurpose' => 'Safe\openssl_x509_checkpurpose',
            'openssl_x509_export' => 'Safe\openssl_x509_export',
            'openssl_x509_export_to_file' => 'Safe\openssl_x509_export_to_file',
            'openssl_x509_fingerprint' => 'Safe\openssl_x509_fingerprint',
            'openssl_x509_read' => 'Safe\openssl_x509_read',
            'output_add_rewrite_var' => 'Safe\output_add_rewrite_var',
            'output_reset_rewrite_vars' => 'Safe\output_reset_rewrite_vars',
            'parse_ini_file' => 'Safe\parse_ini_file',
            'parse_ini_string' => 'Safe\parse_ini_string',
            'parse_url' => 'Safe\parse_url',
            'passthru' => 'Safe\passthru',
            'pclose' => 'Safe\pclose',
            'pcntl_getpriority' => 'Safe\pcntl_getpriority',
            'pcntl_setpriority' => 'Safe\pcntl_setpriority',
            'pcntl_signal' => 'Safe\pcntl_signal',
            'pcntl_signal_dispatch' => 'Safe\pcntl_signal_dispatch',
            'pcntl_sigprocmask' => 'Safe\pcntl_sigprocmask',
            'pcntl_sigtimedwait' => 'Safe\pcntl_sigtimedwait',
            'pcntl_sigwaitinfo' => 'Safe\pcntl_sigwaitinfo',
            'pfsockopen' => 'Safe\pfsockopen',
            'pg_cancel_query' => 'Safe\pg_cancel_query',
            'pg_connect' => 'Safe\pg_connect',
            'pg_connection_reset' => 'Safe\pg_connection_reset',
            'pg_convert' => 'Safe\pg_convert',
            'pg_copy_from' => 'Safe\pg_copy_from',
            'pg_copy_to' => 'Safe\pg_copy_to',
            'pg_delete' => 'Safe\pg_delete',
            'pg_end_copy' => 'Safe\pg_end_copy',
            'pg_execute' => 'Safe\pg_execute',
            'pg_field_num' => 'Safe\pg_field_num',
            'pg_field_table' => 'Safe\pg_field_table',
            'pg_flush' => 'Safe\pg_flush',
            'pg_free_result' => 'Safe\pg_free_result',
            'pg_host' => 'Safe\pg_host',
            'pg_insert' => 'Safe\pg_insert',
            'pg_last_oid' => 'Safe\pg_last_oid',
            'pg_lo_close' => 'Safe\pg_lo_close',
            'pg_lo_export' => 'Safe\pg_lo_export',
            'pg_lo_import' => 'Safe\pg_lo_import',
            'pg_lo_open' => 'Safe\pg_lo_open',
            'pg_lo_read' => 'Safe\pg_lo_read',
            'pg_lo_seek' => 'Safe\pg_lo_seek',
            'pg_lo_truncate' => 'Safe\pg_lo_truncate',
            'pg_lo_unlink' => 'Safe\pg_lo_unlink',
            'pg_lo_write' => 'Safe\pg_lo_write',
            'pg_meta_data' => 'Safe\pg_meta_data',
            'pg_parameter_status' => 'Safe\pg_parameter_status',
            'pg_pconnect' => 'Safe\pg_pconnect',
            'pg_ping' => 'Safe\pg_ping',
            'pg_prepare' => 'Safe\pg_prepare',
            'pg_put_line' => 'Safe\pg_put_line',
            'pg_query' => 'Safe\pg_query',
            'pg_query_params' => 'Safe\pg_query_params',
            'pg_result_error_field' => 'Safe\pg_result_error_field',
            'pg_result_seek' => 'Safe\pg_result_seek',
            'pg_select' => 'Safe\pg_select',
            'pg_socket' => 'Safe\pg_socket',
            'pg_trace' => 'Safe\pg_trace',
            'pg_update' => 'Safe\pg_update',
            'phpcredits' => 'Safe\phpcredits',
            'phpinfo' => 'Safe\phpinfo',
            'php_sapi_name' => 'Safe\php_sapi_name',
            'popen' => 'Safe\popen',
            'posix_access' => 'Safe\posix_access',
            'posix_getgrgid' => 'Safe\posix_getgrgid',
            'posix_getgrnam' => 'Safe\posix_getgrnam',
            'posix_getgroups' => 'Safe\posix_getgroups',
            'posix_getlogin' => 'Safe\posix_getlogin',
            'posix_getpgid' => 'Safe\posix_getpgid',
            'posix_getpwuid' => 'Safe\posix_getpwuid',
            'posix_getrlimit' => 'Safe\posix_getrlimit',
            'posix_getsid' => 'Safe\posix_getsid',
            'posix_initgroups' => 'Safe\posix_initgroups',
            'posix_kill' => 'Safe\posix_kill',
            'posix_mkfifo' => 'Safe\posix_mkfifo',
            'posix_mknod' => 'Safe\posix_mknod',
            'posix_setegid' => 'Safe\posix_setegid',
            'posix_seteuid' => 'Safe\posix_seteuid',
            'posix_setgid' => 'Safe\posix_setgid',
            'posix_setpgid' => 'Safe\posix_setpgid',
            'posix_setrlimit' => 'Safe\posix_setrlimit',
            'posix_setsid' => 'Safe\posix_setsid',
            'posix_setuid' => 'Safe\posix_setuid',
            'posix_times' => 'Safe\posix_times',
            'posix_uname' => 'Safe\posix_uname',
            'preg_grep' => 'Safe\preg_grep',
            'preg_match' => 'Safe\preg_match',
            'preg_match_all' => 'Safe\preg_match_all',
            'preg_replace' => 'Safe\preg_replace',
            'preg_replace_callback' => 'Safe\preg_replace_callback',
            'preg_replace_callback_array' => 'Safe\preg_replace_callback_array',
            'preg_split' => 'Safe\preg_split',
            'proc_close' => 'Safe\proc_close',
            'proc_nice' => 'Safe\proc_nice',
            'proc_open' => 'Safe\proc_open',
            'pspell_add_to_personal' => 'Safe\pspell_add_to_personal',
            'pspell_add_to_session' => 'Safe\pspell_add_to_session',
            'pspell_clear_session' => 'Safe\pspell_clear_session',
            'pspell_config_create' => 'Safe\pspell_config_create',
            'pspell_config_data_dir' => 'Safe\pspell_config_data_dir',
            'pspell_config_dict_dir' => 'Safe\pspell_config_dict_dir',
            'pspell_config_ignore' => 'Safe\pspell_config_ignore',
            'pspell_config_mode' => 'Safe\pspell_config_mode',
            'pspell_config_personal' => 'Safe\pspell_config_personal',
            'pspell_config_repl' => 'Safe\pspell_config_repl',
            'pspell_config_runtogether' => 'Safe\pspell_config_runtogether',
            'pspell_config_save_repl' => 'Safe\pspell_config_save_repl',
            'pspell_new' => 'Safe\pspell_new',
            'pspell_new_config' => 'Safe\pspell_new_config',
            'pspell_new_personal' => 'Safe\pspell_new_personal',
            'pspell_save_wordlist' => 'Safe\pspell_save_wordlist',
            'pspell_store_replacement' => 'Safe\pspell_store_replacement',
            'ps_add_launchlink' => 'Safe\ps_add_launchlink',
            'ps_add_locallink' => 'Safe\ps_add_locallink',
            'ps_add_note' => 'Safe\ps_add_note',
            'ps_add_pdflink' => 'Safe\ps_add_pdflink',
            'ps_add_weblink' => 'Safe\ps_add_weblink',
            'ps_arc' => 'Safe\ps_arc',
            'ps_arcn' => 'Safe\ps_arcn',
            'ps_begin_page' => 'Safe\ps_begin_page',
            'ps_begin_pattern' => 'Safe\ps_begin_pattern',
            'ps_begin_template' => 'Safe\ps_begin_template',
            'ps_circle' => 'Safe\ps_circle',
            'ps_clip' => 'Safe\ps_clip',
            'ps_close' => 'Safe\ps_close',
            'ps_closepath' => 'Safe\ps_closepath',
            'ps_closepath_stroke' => 'Safe\ps_closepath_stroke',
            'ps_close_image' => 'Safe\ps_close_image',
            'ps_continue_text' => 'Safe\ps_continue_text',
            'ps_curveto' => 'Safe\ps_curveto',
            'ps_delete' => 'Safe\ps_delete',
            'ps_end_page' => 'Safe\ps_end_page',
            'ps_end_pattern' => 'Safe\ps_end_pattern',
            'ps_end_template' => 'Safe\ps_end_template',
            'ps_fill' => 'Safe\ps_fill',
            'ps_fill_stroke' => 'Safe\ps_fill_stroke',
            'ps_get_parameter' => 'Safe\ps_get_parameter',
            'ps_hyphenate' => 'Safe\ps_hyphenate',
            'ps_include_file' => 'Safe\ps_include_file',
            'ps_lineto' => 'Safe\ps_lineto',
            'ps_moveto' => 'Safe\ps_moveto',
            'ps_new' => 'Safe\ps_new',
            'ps_open_file' => 'Safe\ps_open_file',
            'ps_place_image' => 'Safe\ps_place_image',
            'ps_rect' => 'Safe\ps_rect',
            'ps_restore' => 'Safe\ps_restore',
            'ps_rotate' => 'Safe\ps_rotate',
            'ps_save' => 'Safe\ps_save',
            'ps_scale' => 'Safe\ps_scale',
            'ps_setcolor' => 'Safe\ps_setcolor',
            'ps_setdash' => 'Safe\ps_setdash',
            'ps_setflat' => 'Safe\ps_setflat',
            'ps_setfont' => 'Safe\ps_setfont',
            'ps_setgray' => 'Safe\ps_setgray',
            'ps_setlinecap' => 'Safe\ps_setlinecap',
            'ps_setlinejoin' => 'Safe\ps_setlinejoin',
            'ps_setlinewidth' => 'Safe\ps_setlinewidth',
            'ps_setmiterlimit' => 'Safe\ps_setmiterlimit',
            'ps_setoverprintmode' => 'Safe\ps_setoverprintmode',
            'ps_setpolydash' => 'Safe\ps_setpolydash',
            'ps_set_border_color' => 'Safe\ps_set_border_color',
            'ps_set_border_dash' => 'Safe\ps_set_border_dash',
            'ps_set_border_style' => 'Safe\ps_set_border_style',
            'ps_set_info' => 'Safe\ps_set_info',
            'ps_set_parameter' => 'Safe\ps_set_parameter',
            'ps_set_text_pos' => 'Safe\ps_set_text_pos',
            'ps_set_value' => 'Safe\ps_set_value',
            'ps_shading' => 'Safe\ps_shading',
            'ps_shading_pattern' => 'Safe\ps_shading_pattern',
            'ps_shfill' => 'Safe\ps_shfill',
            'ps_show' => 'Safe\ps_show',
            'ps_show2' => 'Safe\ps_show2',
            'ps_show_xy' => 'Safe\ps_show_xy',
            'ps_show_xy2' => 'Safe\ps_show_xy2',
            'ps_stroke' => 'Safe\ps_stroke',
            'ps_symbol' => 'Safe\ps_symbol',
            'ps_translate' => 'Safe\ps_translate',
            'putenv' => 'Safe\putenv',
            'readfile' => 'Safe\readfile',
            'readgzfile' => 'Safe\readgzfile',
            'readline_add_history' => 'Safe\readline_add_history',
            'readline_callback_handler_install' => 'Safe\readline_callback_handler_install',
            'readline_clear_history' => 'Safe\readline_clear_history',
            'readline_completion_function' => 'Safe\readline_completion_function',
            'readline_read_history' => 'Safe\readline_read_history',
            'readline_write_history' => 'Safe\readline_write_history',
            'readlink' => 'Safe\readlink',
            'realpath' => 'Safe\realpath',
            'register_tick_function' => 'Safe\register_tick_function',
            'rename' => 'Safe\rename',
            'rewind' => 'Safe\rewind',
            'rmdir' => 'Safe\rmdir',
            'rpmaddtag' => 'Safe\rpmaddtag',
            'rrd_create' => 'Safe\rrd_create',
            'rrd_first' => 'Safe\rrd_first',
            'rrd_graph' => 'Safe\rrd_graph',
            'rrd_info' => 'Safe\rrd_info',
            'rrd_lastupdate' => 'Safe\rrd_lastupdate',
            'rrd_restore' => 'Safe\rrd_restore',
            'rrd_tune' => 'Safe\rrd_tune',
            'rrd_update' => 'Safe\rrd_update',
            'rrd_xport' => 'Safe\rrd_xport',
            'sapi_windows_cp_conv' => 'Safe\sapi_windows_cp_conv',
            'sapi_windows_cp_set' => 'Safe\sapi_windows_cp_set',
            'sapi_windows_generate_ctrl_event' => 'Safe\sapi_windows_generate_ctrl_event',
            'sapi_windows_set_ctrl_handler' => 'Safe\sapi_windows_set_ctrl_handler',
            'sapi_windows_vt100_support' => 'Safe\sapi_windows_vt100_support',
            'scandir' => 'Safe\scandir',
            'sem_acquire' => 'Safe\sem_acquire',
            'sem_get' => 'Safe\sem_get',
            'sem_release' => 'Safe\sem_release',
            'sem_remove' => 'Safe\sem_remove',
            'session_abort' => 'Safe\session_abort',
            'session_cache_expire' => 'Safe\session_cache_expire',
            'session_cache_limiter' => 'Safe\session_cache_limiter',
            'session_create_id' => 'Safe\session_create_id',
            'session_decode' => 'Safe\session_decode',
            'session_destroy' => 'Safe\session_destroy',
            'session_encode' => 'Safe\session_encode',
            'session_gc' => 'Safe\session_gc',
            'session_id' => 'Safe\session_id',
            'session_module_name' => 'Safe\session_module_name',
            'session_name' => 'Safe\session_name',
            'session_regenerate_id' => 'Safe\session_regenerate_id',
            'session_reset' => 'Safe\session_reset',
            'session_save_path' => 'Safe\session_save_path',
            'session_start' => 'Safe\session_start',
            'session_unset' => 'Safe\session_unset',
            'session_write_close' => 'Safe\session_write_close',
            'settype' => 'Safe\settype',
            'set_include_path' => 'Safe\set_include_path',
            'set_time_limit' => 'Safe\set_time_limit',
            'sha1_file' => 'Safe\sha1_file',
            'shell_exec' => 'Safe\shell_exec',
            'shmop_delete' => 'Safe\shmop_delete',
            'shmop_read' => 'Safe\shmop_read',
            'shm_attach' => 'Safe\shm_attach',
            'shm_detach' => 'Safe\shm_detach',
            'shm_put_var' => 'Safe\shm_put_var',
            'shm_remove' => 'Safe\shm_remove',
            'shm_remove_var' => 'Safe\shm_remove_var',
            'simplexml_import_dom' => 'Safe\simplexml_import_dom',
            'simplexml_load_file' => 'Safe\simplexml_load_file',
            'simplexml_load_string' => 'Safe\simplexml_load_string',
            'socket_accept' => 'Safe\socket_accept',
            'socket_addrinfo_bind' => 'Safe\socket_addrinfo_bind',
            'socket_addrinfo_connect' => 'Safe\socket_addrinfo_connect',
            'socket_addrinfo_lookup' => 'Safe\socket_addrinfo_lookup',
            'socket_bind' => 'Safe\socket_bind',
            'socket_connect' => 'Safe\socket_connect',
            'socket_create' => 'Safe\socket_create',
            'socket_create_listen' => 'Safe\socket_create_listen',
            'socket_create_pair' => 'Safe\socket_create_pair',
            'socket_export_stream' => 'Safe\socket_export_stream',
            'socket_getpeername' => 'Safe\socket_getpeername',
            'socket_getsockname' => 'Safe\socket_getsockname',
            'socket_get_option' => 'Safe\socket_get_option',
            'socket_import_stream' => 'Safe\socket_import_stream',
            'socket_listen' => 'Safe\socket_listen',
            'socket_read' => 'Safe\socket_read',
            'socket_send' => 'Safe\socket_send',
            'socket_sendmsg' => 'Safe\socket_sendmsg',
            'socket_sendto' => 'Safe\socket_sendto',
            'socket_set_block' => 'Safe\socket_set_block',
            'socket_set_nonblock' => 'Safe\socket_set_nonblock',
            'socket_set_option' => 'Safe\socket_set_option',
            'socket_shutdown' => 'Safe\socket_shutdown',
            'socket_write' => 'Safe\socket_write',
            'socket_wsaprotocol_info_export' => 'Safe\socket_wsaprotocol_info_export',
            'socket_wsaprotocol_info_import' => 'Safe\socket_wsaprotocol_info_import',
            'socket_wsaprotocol_info_release' => 'Safe\socket_wsaprotocol_info_release',
            'sodium_crypto_aead_aes256gcm_decrypt' => 'Safe\sodium_crypto_aead_aes256gcm_decrypt',
            'sodium_crypto_aead_chacha20poly1305_decrypt' => 'Safe\sodium_crypto_aead_chacha20poly1305_decrypt',
            'sodium_crypto_aead_chacha20poly1305_encrypt' => 'Safe\sodium_crypto_aead_chacha20poly1305_encrypt',
            'sodium_crypto_aead_chacha20poly1305_ietf_decrypt' => 'Safe\sodium_crypto_aead_chacha20poly1305_ietf_decrypt',
            'sodium_crypto_aead_chacha20poly1305_ietf_encrypt' => 'Safe\sodium_crypto_aead_chacha20poly1305_ietf_encrypt',
            'sodium_crypto_aead_xchacha20poly1305_ietf_decrypt' => 'Safe\sodium_crypto_aead_xchacha20poly1305_ietf_decrypt',
            'sodium_crypto_aead_xchacha20poly1305_ietf_encrypt' => 'Safe\sodium_crypto_aead_xchacha20poly1305_ietf_encrypt',
            'sodium_crypto_box_open' => 'Safe\sodium_crypto_box_open',
            'sodium_crypto_box_seal_open' => 'Safe\sodium_crypto_box_seal_open',
            'sodium_crypto_generichash_update' => 'Safe\sodium_crypto_generichash_update',
            'sodium_crypto_secretbox_open' => 'Safe\sodium_crypto_secretbox_open',
            'sodium_crypto_sign_open' => 'Safe\sodium_crypto_sign_open',
            'solr_get_version' => 'Safe\solr_get_version',
            'spl_autoload_register' => 'Safe\spl_autoload_register',
            'spl_autoload_unregister' => 'Safe\spl_autoload_unregister',
            'sqlsrv_begin_transaction' => 'Safe\sqlsrv_begin_transaction',
            'sqlsrv_cancel' => 'Safe\sqlsrv_cancel',
            'sqlsrv_client_info' => 'Safe\sqlsrv_client_info',
            'sqlsrv_close' => 'Safe\sqlsrv_close',
            'sqlsrv_commit' => 'Safe\sqlsrv_commit',
            'sqlsrv_configure' => 'Safe\sqlsrv_configure',
            'sqlsrv_execute' => 'Safe\sqlsrv_execute',
            'sqlsrv_fetch' => 'Safe\sqlsrv_fetch',
            'sqlsrv_fetch_array' => 'Safe\sqlsrv_fetch_array',
            'sqlsrv_fetch_object' => 'Safe\sqlsrv_fetch_object',
            'sqlsrv_free_stmt' => 'Safe\sqlsrv_free_stmt',
            'sqlsrv_get_field' => 'Safe\sqlsrv_get_field',
            'sqlsrv_next_result' => 'Safe\sqlsrv_next_result',
            'sqlsrv_num_fields' => 'Safe\sqlsrv_num_fields',
            'sqlsrv_num_rows' => 'Safe\sqlsrv_num_rows',
            'sqlsrv_prepare' => 'Safe\sqlsrv_prepare',
            'sqlsrv_query' => 'Safe\sqlsrv_query',
            'sqlsrv_rollback' => 'Safe\sqlsrv_rollback',
            'ssdeep_fuzzy_compare' => 'Safe\ssdeep_fuzzy_compare',
            'ssdeep_fuzzy_hash' => 'Safe\ssdeep_fuzzy_hash',
            'ssdeep_fuzzy_hash_filename' => 'Safe\ssdeep_fuzzy_hash_filename',
            'ssh2_auth_agent' => 'Safe\ssh2_auth_agent',
            'ssh2_auth_hostbased_file' => 'Safe\ssh2_auth_hostbased_file',
            'ssh2_auth_password' => 'Safe\ssh2_auth_password',
            'ssh2_auth_pubkey_file' => 'Safe\ssh2_auth_pubkey_file',
            'ssh2_connect' => 'Safe\ssh2_connect',
            'ssh2_disconnect' => 'Safe\ssh2_disconnect',
            'ssh2_exec' => 'Safe\ssh2_exec',
            'ssh2_forward_accept' => 'Safe\ssh2_forward_accept',
            'ssh2_forward_listen' => 'Safe\ssh2_forward_listen',
            'ssh2_publickey_add' => 'Safe\ssh2_publickey_add',
            'ssh2_publickey_init' => 'Safe\ssh2_publickey_init',
            'ssh2_publickey_remove' => 'Safe\ssh2_publickey_remove',
            'ssh2_scp_recv' => 'Safe\ssh2_scp_recv',
            'ssh2_scp_send' => 'Safe\ssh2_scp_send',
            'ssh2_send_eof' => 'Safe\ssh2_send_eof',
            'ssh2_sftp' => 'Safe\ssh2_sftp',
            'ssh2_sftp_chmod' => 'Safe\ssh2_sftp_chmod',
            'ssh2_sftp_mkdir' => 'Safe\ssh2_sftp_mkdir',
            'ssh2_sftp_rename' => 'Safe\ssh2_sftp_rename',
            'ssh2_sftp_rmdir' => 'Safe\ssh2_sftp_rmdir',
            'ssh2_sftp_symlink' => 'Safe\ssh2_sftp_symlink',
            'ssh2_sftp_unlink' => 'Safe\ssh2_sftp_unlink',
            'ssh2_shell' => 'Safe\ssh2_shell',
            'stream_context_set_params' => 'Safe\stream_context_set_params',
            'stream_copy_to_stream' => 'Safe\stream_copy_to_stream',
            'stream_filter_append' => 'Safe\stream_filter_append',
            'stream_filter_prepend' => 'Safe\stream_filter_prepend',
            'stream_filter_register' => 'Safe\stream_filter_register',
            'stream_filter_remove' => 'Safe\stream_filter_remove',
            'stream_get_contents' => 'Safe\stream_get_contents',
            'stream_get_line' => 'Safe\stream_get_line',
            'stream_isatty' => 'Safe\stream_isatty',
            'stream_resolve_include_path' => 'Safe\stream_resolve_include_path',
            'stream_set_blocking' => 'Safe\stream_set_blocking',
            'stream_set_timeout' => 'Safe\stream_set_timeout',
            'stream_socket_accept' => 'Safe\stream_socket_accept',
            'stream_socket_client' => 'Safe\stream_socket_client',
            'stream_socket_get_name' => 'Safe\stream_socket_get_name',
            'stream_socket_pair' => 'Safe\stream_socket_pair',
            'stream_socket_server' => 'Safe\stream_socket_server',
            'stream_socket_shutdown' => 'Safe\stream_socket_shutdown',
            'stream_supports_lock' => 'Safe\stream_supports_lock',
            'stream_wrapper_register' => 'Safe\stream_wrapper_register',
            'stream_wrapper_restore' => 'Safe\stream_wrapper_restore',
            'stream_wrapper_unregister' => 'Safe\stream_wrapper_unregister',
            'strftime' => 'Safe\strftime',
            'strptime' => 'Safe\strptime',
            'strtotime' => 'Safe\strtotime',
            'swoole_async_dns_lookup' => 'Safe\swoole_async_dns_lookup',
            'swoole_async_readfile' => 'Safe\swoole_async_readfile',
            'swoole_async_write' => 'Safe\swoole_async_write',
            'swoole_async_writefile' => 'Safe\swoole_async_writefile',
            'swoole_event_defer' => 'Safe\swoole_event_defer',
            'swoole_event_del' => 'Safe\swoole_event_del',
            'swoole_event_write' => 'Safe\swoole_event_write',
            'symlink' => 'Safe\symlink',
            'syslog' => 'Safe\syslog',
            'system' => 'Safe\system',
            'sys_getloadavg' => 'Safe\sys_getloadavg',
            'tempnam' => 'Safe\tempnam',
            'timezone_name_from_abbr' => 'Safe\timezone_name_from_abbr',
            'time_nanosleep' => 'Safe\time_nanosleep',
            'time_sleep_until' => 'Safe\time_sleep_until',
            'tmpfile' => 'Safe\tmpfile',
            'touch' => 'Safe\touch',
            'unixtojd' => 'Safe\unixtojd',
            'unlink' => 'Safe\unlink',
            'unpack' => 'Safe\unpack',
            'unserialize' => 'Safe\unserialize',
            'uopz_extend' => 'Safe\uopz_extend',
            'uopz_implement' => 'Safe\uopz_implement',
            'variant_date_to_timestamp' => 'Safe\variant_date_to_timestamp',
            'variant_round' => 'Safe\variant_round',
            'virtual' => 'Safe\virtual',
            'xdiff_file_bdiff' => 'Safe\xdiff_file_bdiff',
            'xdiff_file_bpatch' => 'Safe\xdiff_file_bpatch',
            'xdiff_file_diff' => 'Safe\xdiff_file_diff',
            'xdiff_file_diff_binary' => 'Safe\xdiff_file_diff_binary',
            'xdiff_file_patch_binary' => 'Safe\xdiff_file_patch_binary',
            'xdiff_file_rabdiff' => 'Safe\xdiff_file_rabdiff',
            'xdiff_string_bpatch' => 'Safe\xdiff_string_bpatch',
            'xdiff_string_patch' => 'Safe\xdiff_string_patch',
            'xdiff_string_patch_binary' => 'Safe\xdiff_string_patch_binary',
            'xmlrpc_set_type' => 'Safe\xmlrpc_set_type',
            'xml_parser_free' => 'Safe\xml_parser_free',
            'xml_set_character_data_handler' => 'Safe\xml_set_character_data_handler',
            'xml_set_default_handler' => 'Safe\xml_set_default_handler',
            'xml_set_element_handler' => 'Safe\xml_set_element_handler',
            'xml_set_end_namespace_decl_handler' => 'Safe\xml_set_end_namespace_decl_handler',
            'xml_set_external_entity_ref_handler' => 'Safe\xml_set_external_entity_ref_handler',
            'xml_set_notation_decl_handler' => 'Safe\xml_set_notation_decl_handler',
            'xml_set_object' => 'Safe\xml_set_object',
            'xml_set_processing_instruction_handler' => 'Safe\xml_set_processing_instruction_handler',
            'xml_set_start_namespace_decl_handler' => 'Safe\xml_set_start_namespace_decl_handler',
            'xml_set_unparsed_entity_decl_handler' => 'Safe\xml_set_unparsed_entity_decl_handler',
            'yaml_parse' => 'Safe\yaml_parse',
            'yaml_parse_file' => 'Safe\yaml_parse_file',
            'yaml_parse_url' => 'Safe\yaml_parse_url',
            'yaz_ccl_parse' => 'Safe\yaz_ccl_parse',
            'yaz_close' => 'Safe\yaz_close',
            'yaz_connect' => 'Safe\yaz_connect',
            'yaz_database' => 'Safe\yaz_database',
            'yaz_element' => 'Safe\yaz_element',
            'yaz_present' => 'Safe\yaz_present',
            'yaz_search' => 'Safe\yaz_search',
            'yaz_wait' => 'Safe\yaz_wait',
            'zip_entry_close' => 'Safe\zip_entry_close',
            'zip_entry_compressedsize' => 'Safe\zip_entry_compressedsize',
            'zip_entry_compressionmethod' => 'Safe\zip_entry_compressionmethod',
            'zip_entry_filesize' => 'Safe\zip_entry_filesize',
            'zip_entry_name' => 'Safe\zip_entry_name',
            'zip_entry_open' => 'Safe\zip_entry_open',
            'zip_entry_read' => 'Safe\zip_entry_read',
            'zlib_decode' => 'Safe\zlib_decode',
        ]
    );
};
<?php

namespace Safe;

use Safe\Exceptions\NetworkException;

/**
 * @throws NetworkException
 *
 */
function closelog(): void
{
    error_clear_last();
    $safeResult = \closelog();
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
}


/**
 * @param string $hostname
 * @param int $type
 * @param array|null $authoritative_name_servers
 * @param array|null $additional_records
 * @param bool $raw
 * @return list
 * @throws NetworkException
 *
 */
function dns_get_record(string $hostname, int $type = DNS_ANY, ?array &$authoritative_name_servers = null, ?array &$additional_records = null, bool $raw = false): array
{
    error_clear_last();
    $safeResult = \dns_get_record($hostname, $type, $authoritative_name_servers, $additional_records, $raw);
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $hostname
 * @param int $port
 * @param int|null $error_code
 * @param null|string $error_message
 * @param float|null $timeout
 * @return resource
 * @throws NetworkException
 *
 */
function fsockopen(string $hostname, int $port = -1, ?int &$error_code = null, ?string &$error_message = null, ?float $timeout = null)
{
    error_clear_last();
    if ($timeout !== null) {
        $safeResult = \fsockopen($hostname, $port, $error_code, $error_message, $timeout);
    } else {
        $safeResult = \fsockopen($hostname, $port, $error_code, $error_message);
    }
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return string
 * @throws NetworkException
 *
 */
function gethostname(): string
{
    error_clear_last();
    $safeResult = \gethostname();
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $protocol
 * @return int
 * @throws NetworkException
 *
 */
function getprotobyname(string $protocol): int
{
    error_clear_last();
    $safeResult = \getprotobyname($protocol);
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $protocol
 * @return string
 * @throws NetworkException
 *
 */
function getprotobynumber(int $protocol): string
{
    error_clear_last();
    $safeResult = \getprotobynumber($protocol);
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $port
 * @param string $protocol
 * @return string
 * @throws NetworkException
 *
 */
function getservbyport(int $port, string $protocol): string
{
    error_clear_last();
    $safeResult = \getservbyport($port, $protocol);
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param callable $callback
 * @throws NetworkException
 *
 */
function header_register_callback(callable $callback): void
{
    error_clear_last();
    $safeResult = \header_register_callback($callback);
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
}


/**
 * @param string $ip
 * @return string
 * @throws NetworkException
 *
 */
function inet_ntop(string $ip): string
{
    error_clear_last();
    $safeResult = \inet_ntop($ip);
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $ip
 * @return string
 * @throws NetworkException
 *
 */
function inet_pton(string $ip): string
{
    error_clear_last();
    $safeResult = \inet_pton($ip);
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $ip
 * @return string
 * @throws NetworkException
 *
 */
function long2ip(int $ip): string
{
    error_clear_last();
    $safeResult = \long2ip($ip);
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $prefix
 * @param int $flags
 * @param int $facility
 * @throws NetworkException
 *
 */
function openlog(string $prefix, int $flags, int $facility): void
{
    error_clear_last();
    $safeResult = \openlog($prefix, $flags, $facility);
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
}


/**
 * @param string $hostname
 * @param int $port
 * @param int|null $error_code
 * @param null|string $error_message
 * @param float|null $timeout
 * @return resource
 * @throws NetworkException
 *
 */
function pfsockopen(string $hostname, int $port = -1, ?int &$error_code = null, ?string &$error_message = null, ?float $timeout = null)
{
    error_clear_last();
    if ($timeout !== null) {
        $safeResult = \pfsockopen($hostname, $port, $error_code, $error_message, $timeout);
    } else {
        $safeResult = \pfsockopen($hostname, $port, $error_code, $error_message);
    }
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $priority
 * @param string $message
 * @throws NetworkException
 *
 */
function syslog(int $priority, string $message): void
{
    error_clear_last();
    $safeResult = \syslog($priority, $message);
    if ($safeResult === false) {
        throw NetworkException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\EioException;

/**
 * @param int $delay
 * @param int $pri
 * @param callable|null $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_busy(int $delay, int $pri = EIO_PRI_DEFAULT, ?callable $callback = null, $data = null)
{
    error_clear_last();
    $safeResult = \eio_busy($delay, $pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $path
 * @param int $mode
 * @param int $pri
 * @param callable|null $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_chmod(string $path, int $mode, int $pri = EIO_PRI_DEFAULT, ?callable $callback = null, $data = null)
{
    error_clear_last();
    $safeResult = \eio_chmod($path, $mode, $pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $path
 * @param int $uid
 * @param int $gid
 * @param int $pri
 * @param callable|null $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_chown(string $path, int $uid, int $gid = -1, int $pri = EIO_PRI_DEFAULT, ?callable $callback = null, $data = null)
{
    error_clear_last();
    $safeResult = \eio_chown($path, $uid, $gid, $pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param mixed $fd
 * @param int $pri
 * @param callable|null $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_close($fd, int $pri = EIO_PRI_DEFAULT, ?callable $callback = null, $data = null)
{
    error_clear_last();
    $safeResult = \eio_close($fd, $pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param callable $execute
 * @param int $pri
 * @param callable $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_custom(callable $execute, int $pri, callable $callback, $data = null)
{
    error_clear_last();
    $safeResult = \eio_custom($execute, $pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param mixed $fd
 * @param mixed $fd2
 * @param int $pri
 * @param callable|null $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_dup2($fd, $fd2, int $pri = EIO_PRI_DEFAULT, ?callable $callback = null, $data = null)
{
    error_clear_last();
    $safeResult = \eio_dup2($fd, $fd2, $pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @throws EioException
 *
 */
function eio_event_loop(): void
{
    error_clear_last();
    $safeResult = \eio_event_loop();
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
}


/**
 * @param mixed $fd
 * @param int $mode
 * @param int $offset
 * @param int $length
 * @param int $pri
 * @param callable|null $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_fallocate($fd, int $mode, int $offset, int $length, int $pri = EIO_PRI_DEFAULT, ?callable $callback = null, $data = null)
{
    error_clear_last();
    $safeResult = \eio_fallocate($fd, $mode, $offset, $length, $pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param mixed $fd
 * @param int $mode
 * @param int $pri
 * @param callable|null $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_fchmod($fd, int $mode, int $pri = EIO_PRI_DEFAULT, ?callable $callback = null, $data = null)
{
    error_clear_last();
    $safeResult = \eio_fchmod($fd, $mode, $pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param mixed $fd
 * @param int $uid
 * @param int $gid
 * @param int $pri
 * @param callable|null $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_fchown($fd, int $uid, int $gid = -1, int $pri = EIO_PRI_DEFAULT, ?callable $callback = null, $data = null)
{
    error_clear_last();
    $safeResult = \eio_fchown($fd, $uid, $gid, $pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param mixed $fd
 * @param int $pri
 * @param callable|null $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_fdatasync($fd, int $pri = EIO_PRI_DEFAULT, ?callable $callback = null, $data = null)
{
    error_clear_last();
    $safeResult = \eio_fdatasync($fd, $pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param mixed $fd
 * @param int $pri
 * @param callable $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_fstat($fd, int $pri, callable $callback, $data = null)
{
    error_clear_last();
    if ($data !== null) {
        $safeResult = \eio_fstat($fd, $pri, $callback, $data);
    } else {
        $safeResult = \eio_fstat($fd, $pri, $callback);
    }
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param mixed $fd
 * @param int $pri
 * @param callable $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_fstatvfs($fd, int $pri, callable $callback, $data = null)
{
    error_clear_last();
    if ($data !== null) {
        $safeResult = \eio_fstatvfs($fd, $pri, $callback, $data);
    } else {
        $safeResult = \eio_fstatvfs($fd, $pri, $callback);
    }
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param mixed $fd
 * @param int $pri
 * @param callable|null $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_fsync($fd, int $pri = EIO_PRI_DEFAULT, ?callable $callback = null, $data = null)
{
    error_clear_last();
    $safeResult = \eio_fsync($fd, $pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param mixed $fd
 * @param int $offset
 * @param int $pri
 * @param callable|null $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_ftruncate($fd, int $offset = 0, int $pri = EIO_PRI_DEFAULT, ?callable $callback = null, $data = null)
{
    error_clear_last();
    $safeResult = \eio_ftruncate($fd, $offset, $pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param mixed $fd
 * @param float $atime
 * @param float $mtime
 * @param int $pri
 * @param callable|null $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_futime($fd, float $atime, float $mtime, int $pri = EIO_PRI_DEFAULT, ?callable $callback = null, $data = null)
{
    error_clear_last();
    $safeResult = \eio_futime($fd, $atime, $mtime, $pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param callable $callback
 * @param null|string $data
 * @return resource
 * @throws EioException
 *
 */
function eio_grp(callable $callback, ?string $data = null)
{
    error_clear_last();
    $safeResult = \eio_grp($callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $path
 * @param int $pri
 * @param callable $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_lstat(string $path, int $pri, callable $callback, $data = null)
{
    error_clear_last();
    $safeResult = \eio_lstat($path, $pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $path
 * @param int $mode
 * @param int $pri
 * @param callable|null $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_mkdir(string $path, int $mode, int $pri = EIO_PRI_DEFAULT, ?callable $callback = null, $data = null)
{
    error_clear_last();
    $safeResult = \eio_mkdir($path, $mode, $pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $path
 * @param int $mode
 * @param int $dev
 * @param int $pri
 * @param callable|null $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_mknod(string $path, int $mode, int $dev, int $pri = EIO_PRI_DEFAULT, ?callable $callback = null, $data = null)
{
    error_clear_last();
    $safeResult = \eio_mknod($path, $mode, $dev, $pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $pri
 * @param callable|null $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_nop(int $pri = EIO_PRI_DEFAULT, ?callable $callback = null, $data = null)
{
    error_clear_last();
    $safeResult = \eio_nop($pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param mixed $fd
 * @param int $offset
 * @param int $length
 * @param int $pri
 * @param callable|null $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_readahead($fd, int $offset, int $length, int $pri = EIO_PRI_DEFAULT, ?callable $callback = null, $data = null)
{
    error_clear_last();
    $safeResult = \eio_readahead($fd, $offset, $length, $pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $path
 * @param int $flags
 * @param int $pri
 * @param callable $callback
 * @param null|string $data
 * @return resource
 * @throws EioException
 *
 */
function eio_readdir(string $path, int $flags, int $pri, callable $callback, ?string $data = null)
{
    error_clear_last();
    $safeResult = \eio_readdir($path, $flags, $pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $path
 * @param int $pri
 * @param callable $callback
 * @param null|string $data
 * @return resource
 * @throws EioException
 *
 */
function eio_readlink(string $path, int $pri, callable $callback, ?string $data = null)
{
    error_clear_last();
    $safeResult = \eio_readlink($path, $pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $path
 * @param string $new_path
 * @param int $pri
 * @param callable|null $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_rename(string $path, string $new_path, int $pri = EIO_PRI_DEFAULT, ?callable $callback = null, $data = null)
{
    error_clear_last();
    $safeResult = \eio_rename($path, $new_path, $pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $path
 * @param int $pri
 * @param callable|null $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_rmdir(string $path, int $pri = EIO_PRI_DEFAULT, ?callable $callback = null, $data = null)
{
    error_clear_last();
    $safeResult = \eio_rmdir($path, $pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param mixed $fd
 * @param int $offset
 * @param int $whence
 * @param int $pri
 * @param callable|null $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_seek($fd, int $offset, int $whence, int $pri = EIO_PRI_DEFAULT, ?callable $callback = null, $data = null)
{
    error_clear_last();
    $safeResult = \eio_seek($fd, $offset, $whence, $pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param mixed $out_fd
 * @param mixed $in_fd
 * @param int $offset
 * @param int $length
 * @param int $pri
 * @param callable $callback
 * @param string $data
 * @return resource
 * @throws EioException
 *
 */
function eio_sendfile($out_fd, $in_fd, int $offset, int $length, ?int $pri = null, ?callable $callback = null, ?string $data = null)
{
    error_clear_last();
    if ($data !== null) {
        $safeResult = \eio_sendfile($out_fd, $in_fd, $offset, $length, $pri, $callback, $data);
    } elseif ($callback !== null) {
        $safeResult = \eio_sendfile($out_fd, $in_fd, $offset, $length, $pri, $callback);
    } elseif ($pri !== null) {
        $safeResult = \eio_sendfile($out_fd, $in_fd, $offset, $length, $pri);
    } else {
        $safeResult = \eio_sendfile($out_fd, $in_fd, $offset, $length);
    }
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $path
 * @param int $pri
 * @param callable $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_stat(string $path, int $pri, callable $callback, $data = null)
{
    error_clear_last();
    $safeResult = \eio_stat($path, $pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $path
 * @param int $pri
 * @param callable $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_statvfs(string $path, int $pri, callable $callback, $data = null)
{
    error_clear_last();
    if ($data !== null) {
        $safeResult = \eio_statvfs($path, $pri, $callback, $data);
    } else {
        $safeResult = \eio_statvfs($path, $pri, $callback);
    }
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $path
 * @param string $new_path
 * @param int $pri
 * @param callable|null $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_symlink(string $path, string $new_path, int $pri = EIO_PRI_DEFAULT, ?callable $callback = null, $data = null)
{
    error_clear_last();
    $safeResult = \eio_symlink($path, $new_path, $pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param mixed $fd
 * @param int $offset
 * @param int $nbytes
 * @param int $flags
 * @param int $pri
 * @param callable|null $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_sync_file_range($fd, int $offset, int $nbytes, int $flags, int $pri = EIO_PRI_DEFAULT, ?callable $callback = null, $data = null)
{
    error_clear_last();
    $safeResult = \eio_sync_file_range($fd, $offset, $nbytes, $flags, $pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $pri
 * @param callable|null $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_sync(int $pri = EIO_PRI_DEFAULT, ?callable $callback = null, $data = null)
{
    error_clear_last();
    $safeResult = \eio_sync($pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param mixed $fd
 * @param int $pri
 * @param callable|null $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_syncfs($fd, int $pri = EIO_PRI_DEFAULT, ?callable $callback = null, $data = null)
{
    error_clear_last();
    $safeResult = \eio_syncfs($fd, $pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $path
 * @param int $offset
 * @param int $pri
 * @param callable|null $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_truncate(string $path, int $offset = 0, int $pri = EIO_PRI_DEFAULT, ?callable $callback = null, $data = null)
{
    error_clear_last();
    $safeResult = \eio_truncate($path, $offset, $pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $path
 * @param int $pri
 * @param callable|null $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_unlink(string $path, int $pri = EIO_PRI_DEFAULT, ?callable $callback = null, $data = null)
{
    error_clear_last();
    $safeResult = \eio_unlink($path, $pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $path
 * @param float $atime
 * @param float $mtime
 * @param int $pri
 * @param callable|null $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_utime(string $path, float $atime, float $mtime, int $pri = EIO_PRI_DEFAULT, ?callable $callback = null, $data = null)
{
    error_clear_last();
    $safeResult = \eio_utime($path, $atime, $mtime, $pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param mixed $fd
 * @param string $str
 * @param int $length
 * @param int $offset
 * @param int $pri
 * @param callable|null $callback
 * @param mixed $data
 * @return resource
 * @throws EioException
 *
 */
function eio_write($fd, string $str, int $length = 0, int $offset = 0, int $pri = EIO_PRI_DEFAULT, ?callable $callback = null, $data = null)
{
    error_clear_last();
    $safeResult = \eio_write($fd, $str, $length, $offset, $pri, $callback, $data);
    if ($safeResult === false) {
        throw EioException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\OpcacheException;

/**
 * @param string $filename
 * @throws OpcacheException
 *
 */
function opcache_compile_file(string $filename): void
{
    error_clear_last();
    $safeResult = \opcache_compile_file($filename);
    if ($safeResult === false) {
        throw OpcacheException::createFromPhpError();
    }
}


/**
 * @param bool $include_scripts
 * @return array
 * @throws OpcacheException
 *
 */
function opcache_get_status(bool $include_scripts = true): array
{
    error_clear_last();
    $safeResult = \opcache_get_status($include_scripts);
    if ($safeResult === false) {
        throw OpcacheException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\ImapException;

/**
 * @param string $string
 * @return string
 * @throws ImapException
 *
 */
function imap_8bit(string $string): string
{
    error_clear_last();
    $safeResult = \imap_8bit($string);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $folder
 * @param string $message
 * @param null|string $options
 * @param null|string $internal_date
 * @throws ImapException
 *
 */
function imap_append(\IMAP\Connection $imap, string $folder, string $message, ?string $options = null, ?string $internal_date = null): void
{
    error_clear_last();
    if ($internal_date !== null) {
        $safeResult = \imap_append($imap, $folder, $message, $options, $internal_date);
    } elseif ($options !== null) {
        $safeResult = \imap_append($imap, $folder, $message, $options);
    } else {
        $safeResult = \imap_append($imap, $folder, $message);
    }
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param string $string
 * @return string
 * @throws ImapException
 *
 */
function imap_base64(string $string): string
{
    error_clear_last();
    $safeResult = \imap_base64($string);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $string
 * @return string
 * @throws ImapException
 *
 */
function imap_binary(string $string): string
{
    error_clear_last();
    $safeResult = \imap_binary($string);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param int $message_num
 * @param int $flags
 * @return string
 * @throws ImapException
 *
 */
function imap_body(\IMAP\Connection $imap, int $message_num, int $flags = 0): string
{
    error_clear_last();
    $safeResult = \imap_body($imap, $message_num, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param int $message_num
 * @param string $section
 * @return \stdClass
 * @throws ImapException
 *
 */
function imap_bodystruct(\IMAP\Connection $imap, int $message_num, string $section): \stdClass
{
    error_clear_last();
    $safeResult = \imap_bodystruct($imap, $message_num, $section);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @return \stdClass
 * @throws ImapException
 *
 */
function imap_check(\IMAP\Connection $imap): \stdClass
{
    error_clear_last();
    $safeResult = \imap_check($imap);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $sequence
 * @param string $flag
 * @param int $options
 * @throws ImapException
 *
 */
function imap_clearflag_full(\IMAP\Connection $imap, string $sequence, string $flag, int $options = 0): void
{
    error_clear_last();
    $safeResult = \imap_clearflag_full($imap, $sequence, $flag, $options);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param int $flags
 * @throws ImapException
 *
 */
function imap_close(\IMAP\Connection $imap, int $flags = 0): void
{
    error_clear_last();
    $safeResult = \imap_close($imap, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param string $mailbox
 * @throws ImapException
 *
 */
function imap_createmailbox(\IMAP\Connection $imap, string $mailbox): void
{
    error_clear_last();
    $safeResult = \imap_createmailbox($imap, $mailbox);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param string $mailbox
 * @throws ImapException
 *
 */
function imap_deletemailbox(\IMAP\Connection $imap, string $mailbox): void
{
    error_clear_last();
    $safeResult = \imap_deletemailbox($imap, $mailbox);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param string $sequence
 * @param int $flags
 * @return array
 * @throws ImapException
 *
 */
function imap_fetch_overview(\IMAP\Connection $imap, string $sequence, int $flags = 0): array
{
    error_clear_last();
    $safeResult = \imap_fetch_overview($imap, $sequence, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param int $message_num
 * @param string $section
 * @param int $flags
 * @return string
 * @throws ImapException
 *
 */
function imap_fetchbody(\IMAP\Connection $imap, int $message_num, string $section, int $flags = 0): string
{
    error_clear_last();
    $safeResult = \imap_fetchbody($imap, $message_num, $section, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param int $message_num
 * @param int $flags
 * @return string
 * @throws ImapException
 *
 */
function imap_fetchheader(\IMAP\Connection $imap, int $message_num, int $flags = 0): string
{
    error_clear_last();
    $safeResult = \imap_fetchheader($imap, $message_num, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param int $message_num
 * @param string $section
 * @param int $flags
 * @return string
 * @throws ImapException
 *
 */
function imap_fetchmime(\IMAP\Connection $imap, int $message_num, string $section, int $flags = 0): string
{
    error_clear_last();
    $safeResult = \imap_fetchmime($imap, $message_num, $section, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param int $message_num
 * @param int $flags
 * @return \stdClass
 * @throws ImapException
 *
 */
function imap_fetchstructure(\IMAP\Connection $imap, int $message_num, int $flags = 0): \stdClass
{
    error_clear_last();
    $safeResult = \imap_fetchstructure($imap, $message_num, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param int $flags
 * @throws ImapException
 *
 */
function imap_gc(\IMAP\Connection $imap, int $flags): void
{
    error_clear_last();
    $safeResult = \imap_gc($imap, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param string $mailbox
 * @return array
 * @throws ImapException
 *
 */
function imap_getacl(\IMAP\Connection $imap, string $mailbox): array
{
    error_clear_last();
    $safeResult = \imap_getacl($imap, $mailbox);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $reference
 * @param string $pattern
 * @return array
 * @throws ImapException
 *
 */
function imap_getmailboxes(\IMAP\Connection $imap, string $reference, string $pattern): array
{
    error_clear_last();
    $safeResult = \imap_getmailboxes($imap, $reference, $pattern);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $reference
 * @param string $pattern
 * @return array
 * @throws ImapException
 *
 */
function imap_getsubscribed(\IMAP\Connection $imap, string $reference, string $pattern): array
{
    error_clear_last();
    $safeResult = \imap_getsubscribed($imap, $reference, $pattern);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param int $message_num
 * @param int $from_length
 * @param int $subject_length
 * @return \stdClass
 * @throws ImapException
 *
 */
function imap_headerinfo(\IMAP\Connection $imap, int $message_num, int $from_length = 0, int $subject_length = 0): \stdClass
{
    error_clear_last();
    $safeResult = \imap_headerinfo($imap, $message_num, $from_length, $subject_length);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @return array
 * @throws ImapException
 *
 */
function imap_headers(\IMAP\Connection $imap): array
{
    error_clear_last();
    $safeResult = \imap_headers($imap);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $reference
 * @param string $pattern
 * @param string $content
 * @return array
 * @throws ImapException
 *
 */
function imap_listscan(\IMAP\Connection $imap, string $reference, string $pattern, string $content): array
{
    error_clear_last();
    $safeResult = \imap_listscan($imap, $reference, $pattern, $content);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $reference
 * @param string $pattern
 * @return array
 * @throws ImapException
 *
 */
function imap_lsub(\IMAP\Connection $imap, string $reference, string $pattern): array
{
    error_clear_last();
    $safeResult = \imap_lsub($imap, $reference, $pattern);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param array $envelope
 * @param array $bodies
 * @return string
 * @throws ImapException
 *
 */
function imap_mail_compose(array $envelope, array $bodies): string
{
    error_clear_last();
    $safeResult = \imap_mail_compose($envelope, $bodies);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $message_nums
 * @param string $mailbox
 * @param int $flags
 * @throws ImapException
 *
 */
function imap_mail_copy(\IMAP\Connection $imap, string $message_nums, string $mailbox, int $flags = 0): void
{
    error_clear_last();
    $safeResult = \imap_mail_copy($imap, $message_nums, $mailbox, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param string $message_nums
 * @param string $mailbox
 * @param int $flags
 * @throws ImapException
 *
 */
function imap_mail_move(\IMAP\Connection $imap, string $message_nums, string $mailbox, int $flags = 0): void
{
    error_clear_last();
    $safeResult = \imap_mail_move($imap, $message_nums, $mailbox, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param string $to
 * @param string $subject
 * @param string $message
 * @param null|string $additional_headers
 * @param null|string $cc
 * @param null|string $bcc
 * @param null|string $return_path
 * @throws ImapException
 *
 */
function imap_mail(string $to, string $subject, string $message, ?string $additional_headers = null, ?string $cc = null, ?string $bcc = null, ?string $return_path = null): void
{
    error_clear_last();
    if ($return_path !== null) {
        $safeResult = \imap_mail($to, $subject, $message, $additional_headers, $cc, $bcc, $return_path);
    } elseif ($bcc !== null) {
        $safeResult = \imap_mail($to, $subject, $message, $additional_headers, $cc, $bcc);
    } elseif ($cc !== null) {
        $safeResult = \imap_mail($to, $subject, $message, $additional_headers, $cc);
    } elseif ($additional_headers !== null) {
        $safeResult = \imap_mail($to, $subject, $message, $additional_headers);
    } else {
        $safeResult = \imap_mail($to, $subject, $message);
    }
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @return \stdClass
 * @throws ImapException
 *
 */
function imap_mailboxmsginfo(\IMAP\Connection $imap): \stdClass
{
    error_clear_last();
    $safeResult = \imap_mailboxmsginfo($imap);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $string
 * @return array
 * @throws ImapException
 *
 */
function imap_mime_header_decode(string $string): array
{
    error_clear_last();
    $safeResult = \imap_mime_header_decode($string);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $string
 * @return string
 * @throws ImapException
 *
 */
function imap_mutf7_to_utf8(string $string): string
{
    error_clear_last();
    $safeResult = \imap_mutf7_to_utf8($string);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @return int
 * @throws ImapException
 *
 */
function imap_num_msg(\IMAP\Connection $imap): int
{
    error_clear_last();
    $safeResult = \imap_num_msg($imap);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $mailbox
 * @param string $user
 * @param string $password
 * @param int $flags
 * @param int $retries
 * @param array $options
 * @return \IMAP\Connection
 * @throws ImapException
 *
 */
function imap_open(string $mailbox, string $user, string $password, int $flags = 0, int $retries = 0, array $options = []): \IMAP\Connection
{
    error_clear_last();
    $safeResult = \imap_open($mailbox, $user, $password, $flags, $retries, $options);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $string
 * @return string
 * @throws ImapException
 *
 */
function imap_qprint(string $string): string
{
    error_clear_last();
    $safeResult = \imap_qprint($string);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $from
 * @param string $to
 * @throws ImapException
 *
 */
function imap_renamemailbox(\IMAP\Connection $imap, string $from, string $to): void
{
    error_clear_last();
    $safeResult = \imap_renamemailbox($imap, $from, $to);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param null|string $mailbox
 * @param null|string $hostname
 * @param null|string $personal
 * @return string
 * @throws ImapException
 *
 */
function imap_rfc822_write_address(?string $mailbox, ?string $hostname, ?string $personal): string
{
    error_clear_last();
    $safeResult = \imap_rfc822_write_address($mailbox, $hostname, $personal);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param int|resource|string $file
 * @param int $message_num
 * @param string $section
 * @param int $flags
 * @throws ImapException
 *
 */
function imap_savebody(\IMAP\Connection $imap, $file, int $message_num, string $section = "", int $flags = 0): void
{
    error_clear_last();
    $safeResult = \imap_savebody($imap, $file, $message_num, $section, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param string $quota_root
 * @param int $mailbox_size
 * @throws ImapException
 *
 */
function imap_set_quota(\IMAP\Connection $imap, string $quota_root, int $mailbox_size): void
{
    error_clear_last();
    $safeResult = \imap_set_quota($imap, $quota_root, $mailbox_size);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param string $mailbox
 * @param string $user_id
 * @param string $rights
 * @throws ImapException
 *
 */
function imap_setacl(\IMAP\Connection $imap, string $mailbox, string $user_id, string $rights): void
{
    error_clear_last();
    $safeResult = \imap_setacl($imap, $mailbox, $user_id, $rights);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param string $sequence
 * @param string $flag
 * @param int $options
 * @throws ImapException
 *
 */
function imap_setflag_full(\IMAP\Connection $imap, string $sequence, string $flag, int $options = 0): void
{
    error_clear_last();
    $safeResult = \imap_setflag_full($imap, $sequence, $flag, $options);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param int $criteria
 * @param int $reverse
 * @param int $flags
 * @param null|string $search_criteria
 * @param null|string $charset
 * @return array
 * @throws ImapException
 *
 */
function imap_sort(\IMAP\Connection $imap, int $criteria, int $reverse, int $flags = 0, ?string $search_criteria = null, ?string $charset = null): array
{
    error_clear_last();
    if ($charset !== null) {
        $safeResult = \imap_sort($imap, $criteria, $reverse, $flags, $search_criteria, $charset);
    } elseif ($search_criteria !== null) {
        $safeResult = \imap_sort($imap, $criteria, $reverse, $flags, $search_criteria);
    } else {
        $safeResult = \imap_sort($imap, $criteria, $reverse, $flags);
    }
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $mailbox
 * @param int $flags
 * @return \stdClass
 * @throws ImapException
 *
 */
function imap_status(\IMAP\Connection $imap, string $mailbox, int $flags): \stdClass
{
    error_clear_last();
    $safeResult = \imap_status($imap, $mailbox, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $mailbox
 * @throws ImapException
 *
 */
function imap_subscribe(\IMAP\Connection $imap, string $mailbox): void
{
    error_clear_last();
    $safeResult = \imap_subscribe($imap, $mailbox);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param int $flags
 * @return array
 * @throws ImapException
 *
 */
function imap_thread(\IMAP\Connection $imap, int $flags = SE_FREE): array
{
    error_clear_last();
    $safeResult = \imap_thread($imap, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $timeout_type
 * @param int $timeout
 * @return mixed
 * @throws ImapException
 *
 */
function imap_timeout(int $timeout_type, int $timeout = -1)
{
    error_clear_last();
    $safeResult = \imap_timeout($timeout_type, $timeout);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \IMAP\Connection $imap
 * @param string $message_num
 * @param int $flags
 * @throws ImapException
 *
 */
function imap_undelete(\IMAP\Connection $imap, string $message_num, int $flags = 0): void
{
    error_clear_last();
    $safeResult = \imap_undelete($imap, $message_num, $flags);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param \IMAP\Connection $imap
 * @param string $mailbox
 * @throws ImapException
 *
 */
function imap_unsubscribe(\IMAP\Connection $imap, string $mailbox): void
{
    error_clear_last();
    $safeResult = \imap_unsubscribe($imap, $mailbox);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
}


/**
 * @param string $string
 * @return string
 * @throws ImapException
 *
 */
function imap_utf8_to_mutf7(string $string): string
{
    error_clear_last();
    $safeResult = \imap_utf8_to_mutf7($string);
    if ($safeResult === false) {
        throw ImapException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\SolrException;

/**
 * @return string
 * @throws SolrException
 *
 */
function solr_get_version(): string
{
    error_clear_last();
    $safeResult = \solr_get_version();
    if ($safeResult === false) {
        throw SolrException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\GettextException;

/**
 * @param string $domain
 * @param string $directory
 * @return string
 * @throws GettextException
 *
 */
function bindtextdomain(string $domain, string $directory): string
{
    error_clear_last();
    $safeResult = \bindtextdomain($domain, $directory);
    if ($safeResult === false) {
        throw GettextException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\UrlException;

/**
 * @param string $string
 * @param bool $strict
 * @return string
 * @throws UrlException
 *
 */
function base64_decode(string $string, bool $strict = false): string
{
    error_clear_last();
    $safeResult = \base64_decode($string, $strict);
    if ($safeResult === false) {
        throw UrlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $url
 * @param bool $associative
 * @param null|resource $context
 * @return array
 * @throws UrlException
 *
 */
function get_headers(string $url, bool $associative = false, $context = null): array
{
    error_clear_last();
    if ($context !== null) {
        $safeResult = \get_headers($url, $associative, $context);
    } else {
        $safeResult = \get_headers($url, $associative);
    }
    if ($safeResult === false) {
        throw UrlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param bool $use_include_path
 * @return array
 * @throws UrlException
 *
 */
function get_meta_tags(string $filename, bool $use_include_path = false): array
{
    error_clear_last();
    $safeResult = \get_meta_tags($filename, $use_include_path);
    if ($safeResult === false) {
        throw UrlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $url
 * @param int $component
 * @return array|int|null|string
 * @throws UrlException
 *
 */
function parse_url(string $url, int $component = -1)
{
    error_clear_last();
    $safeResult = \parse_url($url, $component);
    if ($safeResult === false) {
        throw UrlException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\ErrorfuncException;

/**
 * @param string $message
 * @param 0|1|2|3|4 $message_type
 * @param string $destination
 * @param string $extra_headers
 * @throws ErrorfuncException
 *
 */
function error_log(string $message, int $message_type = 0, ?string $destination = null, ?string $extra_headers = null): void
{
    error_clear_last();
    if ($extra_headers !== null) {
        $safeResult = \error_log($message, $message_type, $destination, $extra_headers);
    } elseif ($destination !== null) {
        $safeResult = \error_log($message, $message_type, $destination);
    } else {
        $safeResult = \error_log($message, $message_type);
    }
    if ($safeResult === false) {
        throw ErrorfuncException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\LzfException;

/**
 * @param string $data
 * @return string
 * @throws LzfException
 *
 */
function lzf_compress(string $data): string
{
    error_clear_last();
    $safeResult = \lzf_compress($data);
    if ($safeResult === false) {
        throw LzfException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @return string
 * @throws LzfException
 *
 */
function lzf_decompress(string $data): string
{
    error_clear_last();
    $safeResult = \lzf_decompress($data);
    if ($safeResult === false) {
        throw LzfException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\MysqliException;

/**
 * @return array
 * @throws MysqliException
 *
 */
function mysqli_get_client_stats(): array
{
    error_clear_last();
    $safeResult = \mysqli_get_client_stats();
    if ($safeResult === false) {
        throw MysqliException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\JsonException;

/**
 * @param mixed $value
 * @param int $flags
 * @param positive-int $depth
 * @return non-empty-string
 * @throws JsonException
 *
 */
function json_encode($value, int $flags = 0, int $depth = 512): string
{
    error_clear_last();
    $safeResult = \json_encode($value, $flags, $depth);
    if ($safeResult === false) {
        throw JsonException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\PspellException;

/**
 * @param int $dictionary
 * @param string $word
 * @throws PspellException
 *
 */
function pspell_add_to_personal(int $dictionary, string $word): void
{
    error_clear_last();
    $safeResult = \pspell_add_to_personal($dictionary, $word);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
}


/**
 * @param int $dictionary
 * @param string $word
 * @throws PspellException
 *
 */
function pspell_add_to_session(int $dictionary, string $word): void
{
    error_clear_last();
    $safeResult = \pspell_add_to_session($dictionary, $word);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
}


/**
 * @param int $dictionary
 * @throws PspellException
 *
 */
function pspell_clear_session(int $dictionary): void
{
    error_clear_last();
    $safeResult = \pspell_clear_session($dictionary);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
}


/**
 * @param string $language
 * @param string $spelling
 * @param string $jargon
 * @param string $encoding
 * @return int
 * @throws PspellException
 *
 */
function pspell_config_create(string $language, string $spelling = "", string $jargon = "", string $encoding = ""): int
{
    error_clear_last();
    $safeResult = \pspell_config_create($language, $spelling, $jargon, $encoding);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $config
 * @param string $directory
 * @throws PspellException
 *
 */
function pspell_config_data_dir(int $config, string $directory): void
{
    error_clear_last();
    $safeResult = \pspell_config_data_dir($config, $directory);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
}


/**
 * @param int $config
 * @param string $directory
 * @throws PspellException
 *
 */
function pspell_config_dict_dir(int $config, string $directory): void
{
    error_clear_last();
    $safeResult = \pspell_config_dict_dir($config, $directory);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
}


/**
 * @param int $config
 * @param int $min_length
 * @throws PspellException
 *
 */
function pspell_config_ignore(int $config, int $min_length): void
{
    error_clear_last();
    $safeResult = \pspell_config_ignore($config, $min_length);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
}


/**
 * @param int $config
 * @param int $mode
 * @throws PspellException
 *
 */
function pspell_config_mode(int $config, int $mode): void
{
    error_clear_last();
    $safeResult = \pspell_config_mode($config, $mode);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
}


/**
 * @param int $config
 * @param string $filename
 * @throws PspellException
 *
 */
function pspell_config_personal(int $config, string $filename): void
{
    error_clear_last();
    $safeResult = \pspell_config_personal($config, $filename);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
}


/**
 * @param int $config
 * @param string $filename
 * @throws PspellException
 *
 */
function pspell_config_repl(int $config, string $filename): void
{
    error_clear_last();
    $safeResult = \pspell_config_repl($config, $filename);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
}


/**
 * @param int $config
 * @param bool $allow
 * @throws PspellException
 *
 */
function pspell_config_runtogether(int $config, bool $allow): void
{
    error_clear_last();
    $safeResult = \pspell_config_runtogether($config, $allow);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
}


/**
 * @param int $config
 * @param bool $save
 * @throws PspellException
 *
 */
function pspell_config_save_repl(int $config, bool $save): void
{
    error_clear_last();
    $safeResult = \pspell_config_save_repl($config, $save);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
}


/**
 * @param int $config
 * @return int
 * @throws PspellException
 *
 */
function pspell_new_config(int $config): int
{
    error_clear_last();
    $safeResult = \pspell_new_config($config);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param string $language
 * @param string $spelling
 * @param string $jargon
 * @param string $encoding
 * @param int $mode
 * @return int
 * @throws PspellException
 *
 */
function pspell_new_personal(string $filename, string $language, string $spelling = "", string $jargon = "", string $encoding = "", int $mode = 0): int
{
    error_clear_last();
    $safeResult = \pspell_new_personal($filename, $language, $spelling, $jargon, $encoding, $mode);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $language
 * @param string $spelling
 * @param string $jargon
 * @param string $encoding
 * @param int $mode
 * @return int
 * @throws PspellException
 *
 */
function pspell_new(string $language, string $spelling = "", string $jargon = "", string $encoding = "", int $mode = 0): int
{
    error_clear_last();
    $safeResult = \pspell_new($language, $spelling, $jargon, $encoding, $mode);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $dictionary
 * @throws PspellException
 *
 */
function pspell_save_wordlist(int $dictionary): void
{
    error_clear_last();
    $safeResult = \pspell_save_wordlist($dictionary);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
}


/**
 * @param int $dictionary
 * @param string $misspelled
 * @param string $correct
 * @throws PspellException
 *
 */
function pspell_store_replacement(int $dictionary, string $misspelled, string $correct): void
{
    error_clear_last();
    $safeResult = \pspell_store_replacement($dictionary, $misspelled, $correct);
    if ($safeResult === false) {
        throw PspellException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\FilesystemException;

/**
 * @param string $filename
 * @param int|string $group
 * @throws FilesystemException
 *
 */
function chgrp(string $filename, $group): void
{
    error_clear_last();
    $safeResult = \chgrp($filename, $group);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @param int $permissions
 * @throws FilesystemException
 *
 */
function chmod(string $filename, int $permissions): void
{
    error_clear_last();
    $safeResult = \chmod($filename, $permissions);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @param int|string $user
 * @throws FilesystemException
 *
 */
function chown(string $filename, $user): void
{
    error_clear_last();
    $safeResult = \chown($filename, $user);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param string $source
 * @param string $dest
 * @param resource $context
 * @throws FilesystemException
 *
 */
function copy(string $source, string $dest, $context = null): void
{
    error_clear_last();
    if ($context !== null) {
        $safeResult = \copy($source, $dest, $context);
    } else {
        $safeResult = \copy($source, $dest);
    }
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param string $directory
 * @return float
 * @throws FilesystemException
 *
 */
function disk_free_space(string $directory): float
{
    error_clear_last();
    $safeResult = \disk_free_space($directory);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $directory
 * @return float
 * @throws FilesystemException
 *
 */
function disk_total_space(string $directory): float
{
    error_clear_last();
    $safeResult = \disk_total_space($directory);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @throws FilesystemException
 *
 */
function fclose($stream): void
{
    error_clear_last();
    $safeResult = \fclose($stream);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param resource $stream
 * @throws FilesystemException
 *
 */
function fdatasync($stream): void
{
    error_clear_last();
    $safeResult = \fdatasync($stream);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param resource $stream
 * @throws FilesystemException
 *
 */
function fflush($stream): void
{
    error_clear_last();
    $safeResult = \fflush($stream);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @param bool $use_include_path
 * @param null|resource $context
 * @param int $offset
 * @param 0|positive-int $length
 * @return string
 * @throws FilesystemException
 *
 */
function file_get_contents(string $filename, bool $use_include_path = false, $context = null, int $offset = 0, ?int $length = null): string
{
    error_clear_last();
    if ($length !== null) {
        $safeResult = \file_get_contents($filename, $use_include_path, $context, $offset, $length);
    } elseif ($offset !== 0) {
        $safeResult = \file_get_contents($filename, $use_include_path, $context, $offset);
    } elseif ($context !== null) {
        $safeResult = \file_get_contents($filename, $use_include_path, $context);
    } else {
        $safeResult = \file_get_contents($filename, $use_include_path);
    }
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param mixed $data
 * @param int $flags
 * @param null|resource $context
 * @return 0|positive-int
 * @throws FilesystemException
 *
 */
function file_put_contents(string $filename, $data, int $flags = 0, $context = null): int
{
    error_clear_last();
    if ($context !== null) {
        $safeResult = \file_put_contents($filename, $data, $flags, $context);
    } else {
        $safeResult = \file_put_contents($filename, $data, $flags);
    }
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param int-mask $flags
 * @param resource $context
 * @return list
 * @throws FilesystemException
 *
 */
function file(string $filename, int $flags = 0, $context = null): array
{
    error_clear_last();
    if ($context !== null) {
        $safeResult = \file($filename, $flags, $context);
    } else {
        $safeResult = \file($filename, $flags);
    }
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return int
 * @throws FilesystemException
 *
 */
function fileatime(string $filename): int
{
    error_clear_last();
    $safeResult = \fileatime($filename);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return int
 * @throws FilesystemException
 *
 */
function filectime(string $filename): int
{
    error_clear_last();
    $safeResult = \filectime($filename);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return int
 * @throws FilesystemException
 *
 */
function fileinode(string $filename): int
{
    error_clear_last();
    $safeResult = \fileinode($filename);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return int
 * @throws FilesystemException
 *
 */
function filemtime(string $filename): int
{
    error_clear_last();
    $safeResult = \filemtime($filename);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return int
 * @throws FilesystemException
 *
 */
function fileowner(string $filename): int
{
    error_clear_last();
    $safeResult = \fileowner($filename);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return int
 * @throws FilesystemException
 *
 */
function fileperms(string $filename): int
{
    error_clear_last();
    $safeResult = \fileperms($filename);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return 0|positive-int
 * @throws FilesystemException
 *
 */
function filesize(string $filename): int
{
    error_clear_last();
    $safeResult = \filesize($filename);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return string
 * @throws FilesystemException
 *
 */
function filetype(string $filename): string
{
    error_clear_last();
    $safeResult = \filetype($filename);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @param int-mask $operation
 * @param 0|1|null $would_block
 * @throws FilesystemException
 *
 */
function flock($stream, int $operation, ?int &$would_block = null): void
{
    error_clear_last();
    $safeResult = \flock($stream, $operation, $would_block);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @param string $mode
 * @param bool $use_include_path
 * @param null|resource $context
 * @return resource
 * @throws FilesystemException
 *
 */
function fopen(string $filename, string $mode, bool $use_include_path = false, $context = null)
{
    error_clear_last();
    if ($context !== null) {
        $safeResult = \fopen($filename, $mode, $use_include_path, $context);
    } else {
        $safeResult = \fopen($filename, $mode, $use_include_path);
    }
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @param positive-int $length
 * @return string
 * @throws FilesystemException
 *
 */
function fread($stream, int $length): string
{
    error_clear_last();
    $safeResult = \fread($stream, $length);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @return array
 * @throws FilesystemException
 *
 */
function fstat($stream): array
{
    error_clear_last();
    $safeResult = \fstat($stream);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @throws FilesystemException
 *
 */
function fsync($stream): void
{
    error_clear_last();
    $safeResult = \fsync($stream);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param resource $stream
 * @return int
 * @throws FilesystemException
 *
 */
function ftell($stream): int
{
    error_clear_last();
    $safeResult = \ftell($stream);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @param 0|positive-int $size
 * @throws FilesystemException
 *
 */
function ftruncate($stream, int $size): void
{
    error_clear_last();
    $safeResult = \ftruncate($stream, $size);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param resource $handle
 * @param string $string
 * @param 0|positive-int $length
 * @return 0|positive-int
 * @throws FilesystemException
 *
 */
function fwrite($handle, string $string, ?int $length = null): int
{
    error_clear_last();
    if ($length !== null) {
        $safeResult = \fwrite($handle, $string, $length);
    } else {
        $safeResult = \fwrite($handle, $string);
    }
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $pattern
 * @param int $flags
 * @return list
 * @throws FilesystemException
 *
 */
function glob(string $pattern, int $flags = 0): array
{
    error_clear_last();
    $safeResult = \glob($pattern, $flags);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param int|string $group
 * @throws FilesystemException
 *
 */
function lchgrp(string $filename, $group): void
{
    error_clear_last();
    $safeResult = \lchgrp($filename, $group);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @param int|string $user
 * @throws FilesystemException
 *
 */
function lchown(string $filename, $user): void
{
    error_clear_last();
    $safeResult = \lchown($filename, $user);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param string $target
 * @param string $link
 * @throws FilesystemException
 *
 */
function link(string $target, string $link): void
{
    error_clear_last();
    $safeResult = \link($target, $link);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @return array
 * @throws FilesystemException
 *
 */
function lstat(string $filename): array
{
    error_clear_last();
    $safeResult = \lstat($filename);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $directory
 * @param int $permissions
 * @param bool $recursive
 * @param resource $context
 * @throws FilesystemException
 *
 */
function mkdir(string $directory, int $permissions = 0777, bool $recursive = false, $context = null): void
{
    error_clear_last();
    if ($context !== null) {
        $safeResult = \mkdir($directory, $permissions, $recursive, $context);
    } else {
        $safeResult = \mkdir($directory, $permissions, $recursive);
    }
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @param bool $process_sections
 * @param int $scanner_mode
 * @return array
 * @throws FilesystemException
 *
 */
function parse_ini_file(string $filename, bool $process_sections = false, int $scanner_mode = INI_SCANNER_NORMAL): array
{
    error_clear_last();
    $safeResult = \parse_ini_file($filename, $process_sections, $scanner_mode);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $ini_string
 * @param bool $process_sections
 * @param int $scanner_mode
 * @return array
 * @throws FilesystemException
 *
 */
function parse_ini_string(string $ini_string, bool $process_sections = false, int $scanner_mode = INI_SCANNER_NORMAL): array
{
    error_clear_last();
    $safeResult = \parse_ini_string($ini_string, $process_sections, $scanner_mode);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $handle
 * @return int
 * @throws FilesystemException
 *
 */
function pclose($handle): int
{
    error_clear_last();
    $safeResult = \pclose($handle);
    if ($safeResult === -1) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $command
 * @param string $mode
 * @return resource
 * @throws FilesystemException
 *
 */
function popen(string $command, string $mode)
{
    error_clear_last();
    $safeResult = \popen($command, $mode);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param bool $use_include_path
 * @param resource $context
 * @return 0|positive-int
 * @throws FilesystemException
 *
 */
function readfile(string $filename, bool $use_include_path = false, $context = null): int
{
    error_clear_last();
    if ($context !== null) {
        $safeResult = \readfile($filename, $use_include_path, $context);
    } else {
        $safeResult = \readfile($filename, $use_include_path);
    }
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $path
 * @return string
 * @throws FilesystemException
 *
 */
function readlink(string $path): string
{
    error_clear_last();
    $safeResult = \readlink($path);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $path
 * @return non-empty-string
 * @throws FilesystemException
 *
 */
function realpath(string $path): string
{
    error_clear_last();
    $safeResult = \realpath($path);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $oldname
 * @param string $newname
 * @param resource $context
 * @throws FilesystemException
 *
 */
function rename(string $oldname, string $newname, $context = null): void
{
    error_clear_last();
    if ($context !== null) {
        $safeResult = \rename($oldname, $newname, $context);
    } else {
        $safeResult = \rename($oldname, $newname);
    }
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param resource $stream
 * @throws FilesystemException
 *
 */
function rewind($stream): void
{
    error_clear_last();
    $safeResult = \rewind($stream);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param string $directory
 * @param resource $context
 * @throws FilesystemException
 *
 */
function rmdir(string $directory, $context = null): void
{
    error_clear_last();
    if ($context !== null) {
        $safeResult = \rmdir($directory, $context);
    } else {
        $safeResult = \rmdir($directory);
    }
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param string $target
 * @param string $link
 * @throws FilesystemException
 *
 */
function symlink(string $target, string $link): void
{
    error_clear_last();
    $safeResult = \symlink($target, $link);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param string $directory
 * @param string $prefix
 * @return non-falsy-string
 * @throws FilesystemException
 *
 */
function tempnam(string $directory, string $prefix): string
{
    error_clear_last();
    $safeResult = \tempnam($directory, $prefix);
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return resource
 * @throws FilesystemException
 *
 */
function tmpfile()
{
    error_clear_last();
    $safeResult = \tmpfile();
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param int $time
 * @param int $atime
 * @throws FilesystemException
 *
 */
function touch(string $filename, ?int $time = null, ?int $atime = null): void
{
    error_clear_last();
    if ($atime !== null) {
        $safeResult = \touch($filename, $time, $atime);
    } elseif ($time !== null) {
        $safeResult = \touch($filename, $time);
    } else {
        $safeResult = \touch($filename);
    }
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @param resource $context
 * @throws FilesystemException
 *
 */
function unlink(string $filename, $context = null): void
{
    error_clear_last();
    if ($context !== null) {
        $safeResult = \unlink($filename, $context);
    } else {
        $safeResult = \unlink($filename);
    }
    if ($safeResult === false) {
        throw FilesystemException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\FtpException;

/**
 * @param \FTP\Connection $ftp
 * @param int $size
 * @param null|string $response
 * @throws FtpException
 *
 */
function ftp_alloc(\FTP\Connection $ftp, int $size, ?string &$response = null): void
{
    error_clear_last();
    $safeResult = \ftp_alloc($ftp, $size, $response);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
}


/**
 * @param \FTP\Connection $ftp
 * @param string $remote_filename
 * @param string $local_filename
 * @param FTP_ASCII|FTP_BINARY $mode
 * @throws FtpException
 *
 */
function ftp_append(\FTP\Connection $ftp, string $remote_filename, string $local_filename, int $mode = FTP_BINARY): void
{
    error_clear_last();
    $safeResult = \ftp_append($ftp, $remote_filename, $local_filename, $mode);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
}


/**
 * @param \FTP\Connection $ftp
 * @throws FtpException
 *
 */
function ftp_cdup(\FTP\Connection $ftp): void
{
    error_clear_last();
    $safeResult = \ftp_cdup($ftp);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
}


/**
 * @param \FTP\Connection $ftp
 * @param string $directory
 * @throws FtpException
 *
 */
function ftp_chdir(\FTP\Connection $ftp, string $directory): void
{
    error_clear_last();
    $safeResult = \ftp_chdir($ftp, $directory);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
}


/**
 * @param \FTP\Connection $ftp
 * @param int $permissions
 * @param string $filename
 * @return int
 * @throws FtpException
 *
 */
function ftp_chmod(\FTP\Connection $ftp, int $permissions, string $filename): int
{
    error_clear_last();
    $safeResult = \ftp_chmod($ftp, $permissions, $filename);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \FTP\Connection $ftp
 * @throws FtpException
 *
 */
function ftp_close(\FTP\Connection $ftp): void
{
    error_clear_last();
    $safeResult = \ftp_close($ftp);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
}


/**
 * @param string $hostname
 * @param int $port
 * @param int $timeout
 * @return \FTP\Connection
 * @throws FtpException
 *
 */
function ftp_connect(string $hostname, int $port = 21, int $timeout = 90): \FTP\Connection
{
    error_clear_last();
    $safeResult = \ftp_connect($hostname, $port, $timeout);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \FTP\Connection $ftp
 * @param string $filename
 * @throws FtpException
 *
 */
function ftp_delete(\FTP\Connection $ftp, string $filename): void
{
    error_clear_last();
    $safeResult = \ftp_delete($ftp, $filename);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
}


/**
 * @param \FTP\Connection $ftp
 * @param resource $stream
 * @param string $remote_filename
 * @param FTP_ASCII|FTP_BINARY $mode
 * @param int $offset
 * @throws FtpException
 *
 */
function ftp_fget(\FTP\Connection $ftp, $stream, string $remote_filename, int $mode = FTP_BINARY, int $offset = 0): void
{
    error_clear_last();
    $safeResult = \ftp_fget($ftp, $stream, $remote_filename, $mode, $offset);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
}


/**
 * @param \FTP\Connection $ftp
 * @param string $remote_filename
 * @param resource $stream
 * @param FTP_ASCII|FTP_BINARY $mode
 * @param int $offset
 * @throws FtpException
 *
 */
function ftp_fput(\FTP\Connection $ftp, string $remote_filename, $stream, int $mode = FTP_BINARY, int $offset = 0): void
{
    error_clear_last();
    $safeResult = \ftp_fput($ftp, $remote_filename, $stream, $mode, $offset);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
}


/**
 * @param \FTP\Connection $ftp
 * @param string $local_filename
 * @param string $remote_filename
 * @param FTP_ASCII|FTP_BINARY $mode
 * @param int $offset
 * @throws FtpException
 *
 */
function ftp_get(\FTP\Connection $ftp, string $local_filename, string $remote_filename, int $mode = FTP_BINARY, int $offset = 0): void
{
    error_clear_last();
    $safeResult = \ftp_get($ftp, $local_filename, $remote_filename, $mode, $offset);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
}


/**
 * @param \FTP\Connection $ftp
 * @param string $username
 * @param string $password
 * @throws FtpException
 *
 */
function ftp_login(\FTP\Connection $ftp, string $username, string $password): void
{
    error_clear_last();
    $safeResult = \ftp_login($ftp, $username, $password);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
}


/**
 * @param \FTP\Connection $ftp
 * @param string $directory
 * @return string
 * @throws FtpException
 *
 */
function ftp_mkdir(\FTP\Connection $ftp, string $directory): string
{
    error_clear_last();
    $safeResult = \ftp_mkdir($ftp, $directory);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \FTP\Connection $ftp
 * @param string $directory
 * @return array
 * @throws FtpException
 *
 */
function ftp_mlsd(\FTP\Connection $ftp, string $directory): array
{
    error_clear_last();
    $safeResult = \ftp_mlsd($ftp, $directory);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \FTP\Connection $ftp
 * @param string $remote_filename
 * @param string $local_filename
 * @param FTP_ASCII|FTP_BINARY $mode
 * @param int $offset
 * @return int
 * @throws FtpException
 *
 */
function ftp_nb_put(\FTP\Connection $ftp, string $remote_filename, string $local_filename, int $mode = FTP_BINARY, int $offset = 0): int
{
    error_clear_last();
    $safeResult = \ftp_nb_put($ftp, $remote_filename, $local_filename, $mode, $offset);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \FTP\Connection $ftp
 * @param string $directory
 * @return array
 * @throws FtpException
 *
 */
function ftp_nlist(\FTP\Connection $ftp, string $directory): array
{
    error_clear_last();
    $safeResult = \ftp_nlist($ftp, $directory);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \FTP\Connection $ftp
 * @param bool $enable
 * @throws FtpException
 *
 */
function ftp_pasv(\FTP\Connection $ftp, bool $enable): void
{
    error_clear_last();
    $safeResult = \ftp_pasv($ftp, $enable);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
}


/**
 * @param \FTP\Connection $ftp
 * @param string $remote_filename
 * @param string $local_filename
 * @param FTP_ASCII|FTP_BINARY $mode
 * @param int $offset
 * @throws FtpException
 *
 */
function ftp_put(\FTP\Connection $ftp, string $remote_filename, string $local_filename, int $mode = FTP_BINARY, int $offset = 0): void
{
    error_clear_last();
    $safeResult = \ftp_put($ftp, $remote_filename, $local_filename, $mode, $offset);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
}


/**
 * @param \FTP\Connection $ftp
 * @return string
 * @throws FtpException
 *
 */
function ftp_pwd(\FTP\Connection $ftp): string
{
    error_clear_last();
    $safeResult = \ftp_pwd($ftp);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \FTP\Connection $ftp
 * @param string $from
 * @param string $to
 * @throws FtpException
 *
 */
function ftp_rename(\FTP\Connection $ftp, string $from, string $to): void
{
    error_clear_last();
    $safeResult = \ftp_rename($ftp, $from, $to);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
}


/**
 * @param \FTP\Connection $ftp
 * @param string $directory
 * @throws FtpException
 *
 */
function ftp_rmdir(\FTP\Connection $ftp, string $directory): void
{
    error_clear_last();
    $safeResult = \ftp_rmdir($ftp, $directory);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
}


/**
 * @param \FTP\Connection $ftp
 * @param string $command
 * @throws FtpException
 *
 */
function ftp_site(\FTP\Connection $ftp, string $command): void
{
    error_clear_last();
    $safeResult = \ftp_site($ftp, $command);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
}


/**
 * @param \FTP\Connection $ftp
 * @param string $filename
 * @return int
 * @throws FtpException
 *
 */
function ftp_size(\FTP\Connection $ftp, string $filename): int
{
    error_clear_last();
    $safeResult = \ftp_size($ftp, $filename);
    if ($safeResult === -1) {
        throw FtpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $hostname
 * @param int $port
 * @param int $timeout
 * @return \FTP\Connection
 * @throws FtpException
 *
 */
function ftp_ssl_connect(string $hostname, int $port = 21, int $timeout = 90): \FTP\Connection
{
    error_clear_last();
    $safeResult = \ftp_ssl_connect($hostname, $port, $timeout);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \FTP\Connection $ftp
 * @return string
 * @throws FtpException
 *
 */
function ftp_systype(\FTP\Connection $ftp): string
{
    error_clear_last();
    $safeResult = \ftp_systype($ftp);
    if ($safeResult === false) {
        throw FtpException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\ArrayException;

/**
 * @param array $keys
 * @param array $values
 * @return array
 * @throws ArrayException
 *
 */
function array_combine(array $keys, array $values): array
{
    error_clear_last();
    $safeResult = \array_combine($keys, $values);
    if ($safeResult === false) {
        throw ArrayException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param array $array
 * @return array
 * @throws ArrayException
 *
 */
function array_flip(array $array): array
{
    error_clear_last();
    $safeResult = \array_flip($array);
    if ($safeResult === null) {
        throw ArrayException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param array $array
 * @param array $replacements
 * @return array
 * @throws ArrayException
 *
 */
function array_replace_recursive(array $array, array ...$replacements): array
{
    error_clear_last();
    if ($replacements !== []) {
        $safeResult = \array_replace_recursive($array, ...$replacements);
    } else {
        $safeResult = \array_replace_recursive($array);
    }
    if ($safeResult === null) {
        throw ArrayException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param array $array
 * @param array $replacements
 * @return array
 * @throws ArrayException
 *
 */
function array_replace(array $array, array ...$replacements): array
{
    error_clear_last();
    if ($replacements !== []) {
        $safeResult = \array_replace($array, ...$replacements);
    } else {
        $safeResult = \array_replace($array);
    }
    if ($safeResult === null) {
        throw ArrayException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param array|object $array
 * @param callable $callback
 * @param mixed $arg
 * @throws ArrayException
 *
 */
function array_walk_recursive(&$array, callable $callback, $arg = null): void
{
    error_clear_last();
    if ($arg !== null) {
        $safeResult = \array_walk_recursive($array, $callback, $arg);
    } else {
        $safeResult = \array_walk_recursive($array, $callback);
    }
    if ($safeResult === false) {
        throw ArrayException::createFromPhpError();
    }
}


/**
 * @param array $array
 * @throws ArrayException
 *
 */
function shuffle(array &$array): void
{
    error_clear_last();
    $safeResult = \shuffle($array);
    if ($safeResult === false) {
        throw ArrayException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\PsException;

/**
 * @param resource $psdoc
 * @param float $llx
 * @param float $lly
 * @param float $urx
 * @param float $ury
 * @param string $filename
 * @throws PsException
 *
 */
function ps_add_launchlink($psdoc, float $llx, float $lly, float $urx, float $ury, string $filename): void
{
    error_clear_last();
    $safeResult = \ps_add_launchlink($psdoc, $llx, $lly, $urx, $ury, $filename);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param float $llx
 * @param float $lly
 * @param float $urx
 * @param float $ury
 * @param int $page
 * @param string $dest
 * @throws PsException
 *
 */
function ps_add_locallink($psdoc, float $llx, float $lly, float $urx, float $ury, int $page, string $dest): void
{
    error_clear_last();
    $safeResult = \ps_add_locallink($psdoc, $llx, $lly, $urx, $ury, $page, $dest);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param float $llx
 * @param float $lly
 * @param float $urx
 * @param float $ury
 * @param string $contents
 * @param string $title
 * @param string $icon
 * @param int $open
 * @throws PsException
 *
 */
function ps_add_note($psdoc, float $llx, float $lly, float $urx, float $ury, string $contents, string $title, string $icon, int $open): void
{
    error_clear_last();
    $safeResult = \ps_add_note($psdoc, $llx, $lly, $urx, $ury, $contents, $title, $icon, $open);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param float $llx
 * @param float $lly
 * @param float $urx
 * @param float $ury
 * @param string $filename
 * @param int $page
 * @param string $dest
 * @throws PsException
 *
 */
function ps_add_pdflink($psdoc, float $llx, float $lly, float $urx, float $ury, string $filename, int $page, string $dest): void
{
    error_clear_last();
    $safeResult = \ps_add_pdflink($psdoc, $llx, $lly, $urx, $ury, $filename, $page, $dest);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param float $llx
 * @param float $lly
 * @param float $urx
 * @param float $ury
 * @param string $url
 * @throws PsException
 *
 */
function ps_add_weblink($psdoc, float $llx, float $lly, float $urx, float $ury, string $url): void
{
    error_clear_last();
    $safeResult = \ps_add_weblink($psdoc, $llx, $lly, $urx, $ury, $url);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param float $x
 * @param float $y
 * @param float $radius
 * @param float $alpha
 * @param float $beta
 * @throws PsException
 *
 */
function ps_arc($psdoc, float $x, float $y, float $radius, float $alpha, float $beta): void
{
    error_clear_last();
    $safeResult = \ps_arc($psdoc, $x, $y, $radius, $alpha, $beta);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param float $x
 * @param float $y
 * @param float $radius
 * @param float $alpha
 * @param float $beta
 * @throws PsException
 *
 */
function ps_arcn($psdoc, float $x, float $y, float $radius, float $alpha, float $beta): void
{
    error_clear_last();
    $safeResult = \ps_arcn($psdoc, $x, $y, $radius, $alpha, $beta);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param float $width
 * @param float $height
 * @throws PsException
 *
 */
function ps_begin_page($psdoc, float $width, float $height): void
{
    error_clear_last();
    $safeResult = \ps_begin_page($psdoc, $width, $height);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param float $width
 * @param float $height
 * @param float $xstep
 * @param float $ystep
 * @param int $painttype
 * @return int
 * @throws PsException
 *
 */
function ps_begin_pattern($psdoc, float $width, float $height, float $xstep, float $ystep, int $painttype): int
{
    error_clear_last();
    $safeResult = \ps_begin_pattern($psdoc, $width, $height, $xstep, $ystep, $painttype);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $psdoc
 * @param float $width
 * @param float $height
 * @return int
 * @throws PsException
 *
 */
function ps_begin_template($psdoc, float $width, float $height): int
{
    error_clear_last();
    $safeResult = \ps_begin_template($psdoc, $width, $height);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $psdoc
 * @param float $x
 * @param float $y
 * @param float $radius
 * @throws PsException
 *
 */
function ps_circle($psdoc, float $x, float $y, float $radius): void
{
    error_clear_last();
    $safeResult = \ps_circle($psdoc, $x, $y, $radius);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @throws PsException
 *
 */
function ps_clip($psdoc): void
{
    error_clear_last();
    $safeResult = \ps_clip($psdoc);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param int $imageid
 * @throws PsException
 *
 */
function ps_close_image($psdoc, int $imageid): void
{
    error_clear_last();
    $safeResult = \ps_close_image($psdoc, $imageid);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @throws PsException
 *
 */
function ps_close($psdoc): void
{
    error_clear_last();
    $safeResult = \ps_close($psdoc);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @throws PsException
 *
 */
function ps_closepath_stroke($psdoc): void
{
    error_clear_last();
    $safeResult = \ps_closepath_stroke($psdoc);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @throws PsException
 *
 */
function ps_closepath($psdoc): void
{
    error_clear_last();
    $safeResult = \ps_closepath($psdoc);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param string $text
 * @throws PsException
 *
 */
function ps_continue_text($psdoc, string $text): void
{
    error_clear_last();
    $safeResult = \ps_continue_text($psdoc, $text);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param float $x1
 * @param float $y1
 * @param float $x2
 * @param float $y2
 * @param float $x3
 * @param float $y3
 * @throws PsException
 *
 */
function ps_curveto($psdoc, float $x1, float $y1, float $x2, float $y2, float $x3, float $y3): void
{
    error_clear_last();
    $safeResult = \ps_curveto($psdoc, $x1, $y1, $x2, $y2, $x3, $y3);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @throws PsException
 *
 */
function ps_delete($psdoc): void
{
    error_clear_last();
    $safeResult = \ps_delete($psdoc);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @throws PsException
 *
 */
function ps_end_page($psdoc): void
{
    error_clear_last();
    $safeResult = \ps_end_page($psdoc);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @throws PsException
 *
 */
function ps_end_pattern($psdoc): void
{
    error_clear_last();
    $safeResult = \ps_end_pattern($psdoc);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @throws PsException
 *
 */
function ps_end_template($psdoc): void
{
    error_clear_last();
    $safeResult = \ps_end_template($psdoc);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @throws PsException
 *
 */
function ps_fill_stroke($psdoc): void
{
    error_clear_last();
    $safeResult = \ps_fill_stroke($psdoc);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @throws PsException
 *
 */
function ps_fill($psdoc): void
{
    error_clear_last();
    $safeResult = \ps_fill($psdoc);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param string $name
 * @param float $modifier
 * @return string
 * @throws PsException
 *
 */
function ps_get_parameter($psdoc, string $name, ?float $modifier = null): string
{
    error_clear_last();
    if ($modifier !== null) {
        $safeResult = \ps_get_parameter($psdoc, $name, $modifier);
    } else {
        $safeResult = \ps_get_parameter($psdoc, $name);
    }
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $psdoc
 * @param string $text
 * @return array
 * @throws PsException
 *
 */
function ps_hyphenate($psdoc, string $text): array
{
    error_clear_last();
    $safeResult = \ps_hyphenate($psdoc, $text);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $psdoc
 * @param string $file
 * @throws PsException
 *
 */
function ps_include_file($psdoc, string $file): void
{
    error_clear_last();
    $safeResult = \ps_include_file($psdoc, $file);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param float $x
 * @param float $y
 * @throws PsException
 *
 */
function ps_lineto($psdoc, float $x, float $y): void
{
    error_clear_last();
    $safeResult = \ps_lineto($psdoc, $x, $y);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param float $x
 * @param float $y
 * @throws PsException
 *
 */
function ps_moveto($psdoc, float $x, float $y): void
{
    error_clear_last();
    $safeResult = \ps_moveto($psdoc, $x, $y);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @return resource
 * @throws PsException
 *
 */
function ps_new()
{
    error_clear_last();
    $safeResult = \ps_new();
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $psdoc
 * @param string $filename
 * @throws PsException
 *
 */
function ps_open_file($psdoc, ?string $filename = null): void
{
    error_clear_last();
    if ($filename !== null) {
        $safeResult = \ps_open_file($psdoc, $filename);
    } else {
        $safeResult = \ps_open_file($psdoc);
    }
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param int $imageid
 * @param float $x
 * @param float $y
 * @param float $scale
 * @throws PsException
 *
 */
function ps_place_image($psdoc, int $imageid, float $x, float $y, float $scale): void
{
    error_clear_last();
    $safeResult = \ps_place_image($psdoc, $imageid, $x, $y, $scale);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param float $x
 * @param float $y
 * @param float $width
 * @param float $height
 * @throws PsException
 *
 */
function ps_rect($psdoc, float $x, float $y, float $width, float $height): void
{
    error_clear_last();
    $safeResult = \ps_rect($psdoc, $x, $y, $width, $height);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @throws PsException
 *
 */
function ps_restore($psdoc): void
{
    error_clear_last();
    $safeResult = \ps_restore($psdoc);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param float $rot
 * @throws PsException
 *
 */
function ps_rotate($psdoc, float $rot): void
{
    error_clear_last();
    $safeResult = \ps_rotate($psdoc, $rot);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @throws PsException
 *
 */
function ps_save($psdoc): void
{
    error_clear_last();
    $safeResult = \ps_save($psdoc);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param float $x
 * @param float $y
 * @throws PsException
 *
 */
function ps_scale($psdoc, float $x, float $y): void
{
    error_clear_last();
    $safeResult = \ps_scale($psdoc, $x, $y);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param float $red
 * @param float $green
 * @param float $blue
 * @throws PsException
 *
 */
function ps_set_border_color($psdoc, float $red, float $green, float $blue): void
{
    error_clear_last();
    $safeResult = \ps_set_border_color($psdoc, $red, $green, $blue);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param float $black
 * @param float $white
 * @throws PsException
 *
 */
function ps_set_border_dash($psdoc, float $black, float $white): void
{
    error_clear_last();
    $safeResult = \ps_set_border_dash($psdoc, $black, $white);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param string $style
 * @param float $width
 * @throws PsException
 *
 */
function ps_set_border_style($psdoc, string $style, float $width): void
{
    error_clear_last();
    $safeResult = \ps_set_border_style($psdoc, $style, $width);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $p
 * @param string $key
 * @param string $val
 * @throws PsException
 *
 */
function ps_set_info($p, string $key, string $val): void
{
    error_clear_last();
    $safeResult = \ps_set_info($p, $key, $val);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param string $name
 * @param string $value
 * @throws PsException
 *
 */
function ps_set_parameter($psdoc, string $name, string $value): void
{
    error_clear_last();
    $safeResult = \ps_set_parameter($psdoc, $name, $value);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param float $x
 * @param float $y
 * @throws PsException
 *
 */
function ps_set_text_pos($psdoc, float $x, float $y): void
{
    error_clear_last();
    $safeResult = \ps_set_text_pos($psdoc, $x, $y);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param string $name
 * @param float $value
 * @throws PsException
 *
 */
function ps_set_value($psdoc, string $name, float $value): void
{
    error_clear_last();
    $safeResult = \ps_set_value($psdoc, $name, $value);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param string $type
 * @param string $colorspace
 * @param float $c1
 * @param float $c2
 * @param float $c3
 * @param float $c4
 * @throws PsException
 *
 */
function ps_setcolor($psdoc, string $type, string $colorspace, float $c1, float $c2, float $c3, float $c4): void
{
    error_clear_last();
    $safeResult = \ps_setcolor($psdoc, $type, $colorspace, $c1, $c2, $c3, $c4);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param float $on
 * @param float $off
 * @throws PsException
 *
 */
function ps_setdash($psdoc, float $on, float $off): void
{
    error_clear_last();
    $safeResult = \ps_setdash($psdoc, $on, $off);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param float $value
 * @throws PsException
 *
 */
function ps_setflat($psdoc, float $value): void
{
    error_clear_last();
    $safeResult = \ps_setflat($psdoc, $value);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param int $fontid
 * @param float $size
 * @throws PsException
 *
 */
function ps_setfont($psdoc, int $fontid, float $size): void
{
    error_clear_last();
    $safeResult = \ps_setfont($psdoc, $fontid, $size);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param float $gray
 * @throws PsException
 *
 */
function ps_setgray($psdoc, float $gray): void
{
    error_clear_last();
    $safeResult = \ps_setgray($psdoc, $gray);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param int $type
 * @throws PsException
 *
 */
function ps_setlinecap($psdoc, int $type): void
{
    error_clear_last();
    $safeResult = \ps_setlinecap($psdoc, $type);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param int $type
 * @throws PsException
 *
 */
function ps_setlinejoin($psdoc, int $type): void
{
    error_clear_last();
    $safeResult = \ps_setlinejoin($psdoc, $type);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param float $width
 * @throws PsException
 *
 */
function ps_setlinewidth($psdoc, float $width): void
{
    error_clear_last();
    $safeResult = \ps_setlinewidth($psdoc, $width);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param float $value
 * @throws PsException
 *
 */
function ps_setmiterlimit($psdoc, float $value): void
{
    error_clear_last();
    $safeResult = \ps_setmiterlimit($psdoc, $value);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param int $mode
 * @throws PsException
 *
 */
function ps_setoverprintmode($psdoc, int $mode): void
{
    error_clear_last();
    $safeResult = \ps_setoverprintmode($psdoc, $mode);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param float $arr
 * @throws PsException
 *
 */
function ps_setpolydash($psdoc, float $arr): void
{
    error_clear_last();
    $safeResult = \ps_setpolydash($psdoc, $arr);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param int $shadingid
 * @param string $optlist
 * @return int
 * @throws PsException
 *
 */
function ps_shading_pattern($psdoc, int $shadingid, string $optlist): int
{
    error_clear_last();
    $safeResult = \ps_shading_pattern($psdoc, $shadingid, $optlist);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $psdoc
 * @param string $type
 * @param float $x0
 * @param float $y0
 * @param float $x1
 * @param float $y1
 * @param float $c1
 * @param float $c2
 * @param float $c3
 * @param float $c4
 * @param string $optlist
 * @return int
 * @throws PsException
 *
 */
function ps_shading($psdoc, string $type, float $x0, float $y0, float $x1, float $y1, float $c1, float $c2, float $c3, float $c4, string $optlist): int
{
    error_clear_last();
    $safeResult = \ps_shading($psdoc, $type, $x0, $y0, $x1, $y1, $c1, $c2, $c3, $c4, $optlist);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $psdoc
 * @param int $shadingid
 * @throws PsException
 *
 */
function ps_shfill($psdoc, int $shadingid): void
{
    error_clear_last();
    $safeResult = \ps_shfill($psdoc, $shadingid);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param string $text
 * @param float $x
 * @param float $y
 * @throws PsException
 *
 */
function ps_show_xy($psdoc, string $text, float $x, float $y): void
{
    error_clear_last();
    $safeResult = \ps_show_xy($psdoc, $text, $x, $y);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param string $text
 * @param int $len
 * @param float $xcoor
 * @param float $ycoor
 * @throws PsException
 *
 */
function ps_show_xy2($psdoc, string $text, int $len, float $xcoor, float $ycoor): void
{
    error_clear_last();
    $safeResult = \ps_show_xy2($psdoc, $text, $len, $xcoor, $ycoor);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param string $text
 * @throws PsException
 *
 */
function ps_show($psdoc, string $text): void
{
    error_clear_last();
    $safeResult = \ps_show($psdoc, $text);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param string $text
 * @param int $len
 * @throws PsException
 *
 */
function ps_show2($psdoc, string $text, int $len): void
{
    error_clear_last();
    $safeResult = \ps_show2($psdoc, $text, $len);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @throws PsException
 *
 */
function ps_stroke($psdoc): void
{
    error_clear_last();
    $safeResult = \ps_stroke($psdoc);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param int $ord
 * @throws PsException
 *
 */
function ps_symbol($psdoc, int $ord): void
{
    error_clear_last();
    $safeResult = \ps_symbol($psdoc, $ord);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}


/**
 * @param resource $psdoc
 * @param float $x
 * @param float $y
 * @throws PsException
 *
 */
function ps_translate($psdoc, float $x, float $y): void
{
    error_clear_last();
    $safeResult = \ps_translate($psdoc, $x, $y);
    if ($safeResult === false) {
        throw PsException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\GnupgException;

/**
 * @param resource $identifier
 * @param string $fingerprint
 * @param string $passphrase
 * @throws GnupgException
 *
 */
function gnupg_adddecryptkey($identifier, string $fingerprint, string $passphrase): void
{
    error_clear_last();
    $safeResult = \gnupg_adddecryptkey($identifier, $fingerprint, $passphrase);
    if ($safeResult === false) {
        throw GnupgException::createFromPhpError();
    }
}


/**
 * @param resource $identifier
 * @param string $fingerprint
 * @throws GnupgException
 *
 */
function gnupg_addencryptkey($identifier, string $fingerprint): void
{
    error_clear_last();
    $safeResult = \gnupg_addencryptkey($identifier, $fingerprint);
    if ($safeResult === false) {
        throw GnupgException::createFromPhpError();
    }
}


/**
 * @param resource $identifier
 * @param string $fingerprint
 * @param string $passphrase
 * @throws GnupgException
 *
 */
function gnupg_addsignkey($identifier, string $fingerprint, ?string $passphrase = null): void
{
    error_clear_last();
    if ($passphrase !== null) {
        $safeResult = \gnupg_addsignkey($identifier, $fingerprint, $passphrase);
    } else {
        $safeResult = \gnupg_addsignkey($identifier, $fingerprint);
    }
    if ($safeResult === false) {
        throw GnupgException::createFromPhpError();
    }
}


/**
 * @param resource $identifier
 * @throws GnupgException
 *
 */
function gnupg_cleardecryptkeys($identifier): void
{
    error_clear_last();
    $safeResult = \gnupg_cleardecryptkeys($identifier);
    if ($safeResult === false) {
        throw GnupgException::createFromPhpError();
    }
}


/**
 * @param resource $identifier
 * @throws GnupgException
 *
 */
function gnupg_clearencryptkeys($identifier): void
{
    error_clear_last();
    $safeResult = \gnupg_clearencryptkeys($identifier);
    if ($safeResult === false) {
        throw GnupgException::createFromPhpError();
    }
}


/**
 * @param resource $identifier
 * @throws GnupgException
 *
 */
function gnupg_clearsignkeys($identifier): void
{
    error_clear_last();
    $safeResult = \gnupg_clearsignkeys($identifier);
    if ($safeResult === false) {
        throw GnupgException::createFromPhpError();
    }
}


/**
 * @param resource $identifier
 * @param int $armor
 * @throws GnupgException
 *
 */
function gnupg_setarmor($identifier, int $armor): void
{
    error_clear_last();
    $safeResult = \gnupg_setarmor($identifier, $armor);
    if ($safeResult === false) {
        throw GnupgException::createFromPhpError();
    }
}


/**
 * @param resource $identifier
 * @param int $signmode
 * @throws GnupgException
 *
 */
function gnupg_setsignmode($identifier, int $signmode): void
{
    error_clear_last();
    $safeResult = \gnupg_setsignmode($identifier, $signmode);
    if ($safeResult === false) {
        throw GnupgException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\GmpException;

/**
 * @param \GMP|int|string $seed
 * @throws GmpException
 *
 */
function gmp_random_seed($seed): void
{
    error_clear_last();
    $safeResult = \gmp_random_seed($seed);
    if ($safeResult === false) {
        throw GmpException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\ZipException;

/**
 * @param resource $zip_entry
 * @throws ZipException
 *
 */
function zip_entry_close($zip_entry): void
{
    error_clear_last();
    $safeResult = \zip_entry_close($zip_entry);
    if ($safeResult === false) {
        throw ZipException::createFromPhpError();
    }
}


/**
 * @param resource $zip_entry
 * @return int
 * @throws ZipException
 *
 */
function zip_entry_compressedsize($zip_entry): int
{
    error_clear_last();
    $safeResult = \zip_entry_compressedsize($zip_entry);
    if ($safeResult === false) {
        throw ZipException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $zip_entry
 * @return string
 * @throws ZipException
 *
 */
function zip_entry_compressionmethod($zip_entry): string
{
    error_clear_last();
    $safeResult = \zip_entry_compressionmethod($zip_entry);
    if ($safeResult === false) {
        throw ZipException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $zip_entry
 * @return int
 * @throws ZipException
 *
 */
function zip_entry_filesize($zip_entry): int
{
    error_clear_last();
    $safeResult = \zip_entry_filesize($zip_entry);
    if ($safeResult === false) {
        throw ZipException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $zip_entry
 * @return string
 * @throws ZipException
 *
 */
function zip_entry_name($zip_entry): string
{
    error_clear_last();
    $safeResult = \zip_entry_name($zip_entry);
    if ($safeResult === false) {
        throw ZipException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $zip_dp
 * @param resource $zip_entry
 * @param string $mode
 * @throws ZipException
 *
 */
function zip_entry_open($zip_dp, $zip_entry, string $mode = "rb"): void
{
    error_clear_last();
    $safeResult = \zip_entry_open($zip_dp, $zip_entry, $mode);
    if ($safeResult === false) {
        throw ZipException::createFromPhpError();
    }
}


/**
 * @param resource $zip_entry
 * @param int $len
 * @return string
 * @throws ZipException
 *
 */
function zip_entry_read($zip_entry, int $len = 1024): string
{
    error_clear_last();
    $safeResult = \zip_entry_read($zip_entry, $len);
    if ($safeResult === false) {
        throw ZipException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\SocketsException;

/**
 * @param \Socket $socket
 * @return \Socket
 * @throws SocketsException
 *
 */
function socket_accept(\Socket $socket): \Socket
{
    error_clear_last();
    $safeResult = \socket_accept($socket);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \AddressInfo $address
 * @return \Socket
 * @throws SocketsException
 *
 */
function socket_addrinfo_bind(\AddressInfo $address): \Socket
{
    error_clear_last();
    $safeResult = \socket_addrinfo_bind($address);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \AddressInfo $address
 * @return \Socket
 * @throws SocketsException
 *
 */
function socket_addrinfo_connect(\AddressInfo $address): \Socket
{
    error_clear_last();
    $safeResult = \socket_addrinfo_connect($address);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $host
 * @param mixed $service
 * @param array $hints
 * @return \AddressInfo[]
 * @throws SocketsException
 *
 */
function socket_addrinfo_lookup(string $host, $service = null, array $hints = []): array
{
    error_clear_last();
    if ($hints !== []) {
        $safeResult = \socket_addrinfo_lookup($host, $service, $hints);
    } elseif ($service !== null) {
        $safeResult = \socket_addrinfo_lookup($host, $service);
    } else {
        $safeResult = \socket_addrinfo_lookup($host);
    }
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Socket $socket
 * @param string $address
 * @param int $port
 * @throws SocketsException
 *
 */
function socket_bind(\Socket $socket, string $address, int $port = 0): void
{
    error_clear_last();
    $safeResult = \socket_bind($socket, $address, $port);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
}


/**
 * @param \Socket $socket
 * @param string $address
 * @param int|null $port
 * @throws SocketsException
 *
 */
function socket_connect(\Socket $socket, string $address, ?int $port = null): void
{
    error_clear_last();
    if ($port !== null) {
        $safeResult = \socket_connect($socket, $address, $port);
    } else {
        $safeResult = \socket_connect($socket, $address);
    }
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
}


/**
 * @param int $port
 * @param int $backlog
 * @return \Socket
 * @throws SocketsException
 *
 */
function socket_create_listen(int $port, int $backlog = 128): \Socket
{
    error_clear_last();
    $safeResult = \socket_create_listen($port, $backlog);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $domain
 * @param int $type
 * @param int $protocol
 * @param \Socket[]|null $pair
 * @throws SocketsException
 *
 */
function socket_create_pair(int $domain, int $type, int $protocol, ?array &$pair): void
{
    error_clear_last();
    $safeResult = \socket_create_pair($domain, $type, $protocol, $pair);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
}


/**
 * @param int $domain
 * @param int $type
 * @param int $protocol
 * @return \Socket
 * @throws SocketsException
 *
 */
function socket_create(int $domain, int $type, int $protocol): \Socket
{
    error_clear_last();
    $safeResult = \socket_create($domain, $type, $protocol);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Socket $socket
 * @return resource
 * @throws SocketsException
 *
 */
function socket_export_stream(\Socket $socket)
{
    error_clear_last();
    $safeResult = \socket_export_stream($socket);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Socket $socket
 * @param int $level
 * @param int $option
 * @return mixed
 * @throws SocketsException
 *
 */
function socket_get_option(\Socket $socket, int $level, int $option)
{
    error_clear_last();
    $safeResult = \socket_get_option($socket, $level, $option);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Socket $socket
 * @param null|string $address
 * @param int|null $port
 * @throws SocketsException
 *
 */
function socket_getpeername(\Socket $socket, ?string &$address, ?int &$port = null): void
{
    error_clear_last();
    $safeResult = \socket_getpeername($socket, $address, $port);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
}


/**
 * @param \Socket $socket
 * @param null|string $address
 * @param int|null $port
 * @throws SocketsException
 *
 */
function socket_getsockname(\Socket $socket, ?string &$address, ?int &$port = null): void
{
    error_clear_last();
    $safeResult = \socket_getsockname($socket, $address, $port);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
}


/**
 * @param resource $stream
 * @return \Socket
 * @throws SocketsException
 *
 */
function socket_import_stream($stream): \Socket
{
    error_clear_last();
    $safeResult = \socket_import_stream($stream);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Socket $socket
 * @param int $backlog
 * @throws SocketsException
 *
 */
function socket_listen(\Socket $socket, int $backlog = 0): void
{
    error_clear_last();
    $safeResult = \socket_listen($socket, $backlog);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
}


/**
 * @param \Socket $socket
 * @param int $length
 * @param int $mode
 * @return string
 * @throws SocketsException
 *
 */
function socket_read(\Socket $socket, int $length, int $mode = PHP_BINARY_READ): string
{
    error_clear_last();
    $safeResult = \socket_read($socket, $length, $mode);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Socket $socket
 * @param string $data
 * @param int $length
 * @param int $flags
 * @return int
 * @throws SocketsException
 *
 */
function socket_send(\Socket $socket, string $data, int $length, int $flags): int
{
    error_clear_last();
    $safeResult = \socket_send($socket, $data, $length, $flags);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Socket $socket
 * @param array $message
 * @param int $flags
 * @return int
 * @throws SocketsException
 *
 */
function socket_sendmsg(\Socket $socket, array $message, int $flags = 0): int
{
    error_clear_last();
    $safeResult = \socket_sendmsg($socket, $message, $flags);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Socket $socket
 * @param string $data
 * @param int $length
 * @param int $flags
 * @param string $address
 * @param int|null $port
 * @return int
 * @throws SocketsException
 *
 */
function socket_sendto(\Socket $socket, string $data, int $length, int $flags, string $address, ?int $port = null): int
{
    error_clear_last();
    if ($port !== null) {
        $safeResult = \socket_sendto($socket, $data, $length, $flags, $address, $port);
    } else {
        $safeResult = \socket_sendto($socket, $data, $length, $flags, $address);
    }
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \Socket $socket
 * @throws SocketsException
 *
 */
function socket_set_block(\Socket $socket): void
{
    error_clear_last();
    $safeResult = \socket_set_block($socket);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
}


/**
 * @param \Socket $socket
 * @throws SocketsException
 *
 */
function socket_set_nonblock(\Socket $socket): void
{
    error_clear_last();
    $safeResult = \socket_set_nonblock($socket);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
}


/**
 * @param \Socket $socket
 * @param int $level
 * @param int $option
 * @param array|int|string $value
 * @throws SocketsException
 *
 */
function socket_set_option(\Socket $socket, int $level, int $option, $value): void
{
    error_clear_last();
    $safeResult = \socket_set_option($socket, $level, $option, $value);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
}


/**
 * @param \Socket $socket
 * @param int $mode
 * @throws SocketsException
 *
 */
function socket_shutdown(\Socket $socket, int $mode = 2): void
{
    error_clear_last();
    $safeResult = \socket_shutdown($socket, $mode);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
}


/**
 * @param \Socket $socket
 * @param int $process_id
 * @return string
 * @throws SocketsException
 *
 */
function socket_wsaprotocol_info_export(\Socket $socket, int $process_id): string
{
    error_clear_last();
    $safeResult = \socket_wsaprotocol_info_export($socket, $process_id);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $info_id
 * @return \Socket
 * @throws SocketsException
 *
 */
function socket_wsaprotocol_info_import(string $info_id): \Socket
{
    error_clear_last();
    $safeResult = \socket_wsaprotocol_info_import($info_id);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $info_id
 * @throws SocketsException
 *
 */
function socket_wsaprotocol_info_release(string $info_id): void
{
    error_clear_last();
    $safeResult = \socket_wsaprotocol_info_release($info_id);
    if ($safeResult === false) {
        throw SocketsException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\FilterException;

/**
 * @param int $type
 * @param array|int $options
 * @param bool $add_empty
 * @return array|null
 * @throws FilterException
 *
 */
function filter_input_array(int $type, $options = FILTER_DEFAULT, bool $add_empty = true): ?array
{
    error_clear_last();
    $safeResult = \filter_input_array($type, $options, $add_empty);
    if ($safeResult === false) {
        throw FilterException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param array $array
 * @param mixed $options
 * @param bool $add_empty
 * @return array|null
 * @throws FilterException
 *
 */
function filter_var_array(array $array, $options = FILTER_DEFAULT, bool $add_empty = true): ?array
{
    error_clear_last();
    $safeResult = \filter_var_array($array, $options, $add_empty);
    if ($safeResult === false) {
        throw FilterException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\Ssh2Exception;

/**
 * @param resource $session
 * @param string $username
 * @throws Ssh2Exception
 *
 */
function ssh2_auth_agent($session, string $username): void
{
    error_clear_last();
    $safeResult = \ssh2_auth_agent($session, $username);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param resource $session
 * @param string $username
 * @param string $hostname
 * @param string $pubkeyfile
 * @param string $privkeyfile
 * @param string $passphrase
 * @param string $local_username
 * @throws Ssh2Exception
 *
 */
function ssh2_auth_hostbased_file($session, string $username, string $hostname, string $pubkeyfile, string $privkeyfile, ?string $passphrase = null, ?string $local_username = null): void
{
    error_clear_last();
    if ($local_username !== null) {
        $safeResult = \ssh2_auth_hostbased_file($session, $username, $hostname, $pubkeyfile, $privkeyfile, $passphrase, $local_username);
    } elseif ($passphrase !== null) {
        $safeResult = \ssh2_auth_hostbased_file($session, $username, $hostname, $pubkeyfile, $privkeyfile, $passphrase);
    } else {
        $safeResult = \ssh2_auth_hostbased_file($session, $username, $hostname, $pubkeyfile, $privkeyfile);
    }
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param resource $session
 * @param string $username
 * @param string $password
 * @throws Ssh2Exception
 *
 */
function ssh2_auth_password($session, string $username, string $password): void
{
    error_clear_last();
    $safeResult = \ssh2_auth_password($session, $username, $password);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param resource $session
 * @param string $username
 * @param string $pubkeyfile
 * @param string $privkeyfile
 * @param string $passphrase
 * @throws Ssh2Exception
 *
 */
function ssh2_auth_pubkey_file($session, string $username, string $pubkeyfile, string $privkeyfile, ?string $passphrase = null): void
{
    error_clear_last();
    if ($passphrase !== null) {
        $safeResult = \ssh2_auth_pubkey_file($session, $username, $pubkeyfile, $privkeyfile, $passphrase);
    } else {
        $safeResult = \ssh2_auth_pubkey_file($session, $username, $pubkeyfile, $privkeyfile);
    }
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param string $host
 * @param int $port
 * @param array $methods
 * @param array $callbacks
 * @return resource
 * @throws Ssh2Exception
 *
 */
function ssh2_connect(string $host, int $port = 22, ?array $methods = null, ?array $callbacks = null)
{
    error_clear_last();
    if ($callbacks !== null) {
        $safeResult = \ssh2_connect($host, $port, $methods, $callbacks);
    } elseif ($methods !== null) {
        $safeResult = \ssh2_connect($host, $port, $methods);
    } else {
        $safeResult = \ssh2_connect($host, $port);
    }
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $session
 * @throws Ssh2Exception
 *
 */
function ssh2_disconnect($session): void
{
    error_clear_last();
    $safeResult = \ssh2_disconnect($session);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param resource $session
 * @param string $command
 * @param string $pty
 * @param array $env
 * @param int $width
 * @param int $height
 * @param int $width_height_type
 * @return resource
 * @throws Ssh2Exception
 *
 */
function ssh2_exec($session, string $command, ?string $pty = null, ?array $env = null, int $width = 80, int $height = 25, int $width_height_type = SSH2_TERM_UNIT_CHARS)
{
    error_clear_last();
    if ($width_height_type !== SSH2_TERM_UNIT_CHARS) {
        $safeResult = \ssh2_exec($session, $command, $pty, $env, $width, $height, $width_height_type);
    } elseif ($height !== 25) {
        $safeResult = \ssh2_exec($session, $command, $pty, $env, $width, $height);
    } elseif ($width !== 80) {
        $safeResult = \ssh2_exec($session, $command, $pty, $env, $width);
    } elseif ($env !== null) {
        $safeResult = \ssh2_exec($session, $command, $pty, $env);
    } elseif ($pty !== null) {
        $safeResult = \ssh2_exec($session, $command, $pty);
    } else {
        $safeResult = \ssh2_exec($session, $command);
    }
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $listener
 * @return resource
 * @throws Ssh2Exception
 *
 */
function ssh2_forward_accept($listener)
{
    error_clear_last();
    $safeResult = \ssh2_forward_accept($listener);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $session
 * @param int $port
 * @param string $host
 * @param int $max_connections
 * @return resource
 * @throws Ssh2Exception
 *
 */
function ssh2_forward_listen($session, int $port, ?string $host = null, int $max_connections = 16)
{
    error_clear_last();
    if ($max_connections !== 16) {
        $safeResult = \ssh2_forward_listen($session, $port, $host, $max_connections);
    } elseif ($host !== null) {
        $safeResult = \ssh2_forward_listen($session, $port, $host);
    } else {
        $safeResult = \ssh2_forward_listen($session, $port);
    }
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $pkey
 * @param string $algoname
 * @param string $blob
 * @param bool $overwrite
 * @param array $attributes
 * @throws Ssh2Exception
 *
 */
function ssh2_publickey_add($pkey, string $algoname, string $blob, bool $overwrite = false, ?array $attributes = null): void
{
    error_clear_last();
    if ($attributes !== null) {
        $safeResult = \ssh2_publickey_add($pkey, $algoname, $blob, $overwrite, $attributes);
    } else {
        $safeResult = \ssh2_publickey_add($pkey, $algoname, $blob, $overwrite);
    }
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param resource $session
 * @return resource
 * @throws Ssh2Exception
 *
 */
function ssh2_publickey_init($session)
{
    error_clear_last();
    $safeResult = \ssh2_publickey_init($session);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $pkey
 * @param string $algoname
 * @param string $blob
 * @throws Ssh2Exception
 *
 */
function ssh2_publickey_remove($pkey, string $algoname, string $blob): void
{
    error_clear_last();
    $safeResult = \ssh2_publickey_remove($pkey, $algoname, $blob);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param resource $session
 * @param string $remote_file
 * @param string $local_file
 * @throws Ssh2Exception
 *
 */
function ssh2_scp_recv($session, string $remote_file, string $local_file): void
{
    error_clear_last();
    $safeResult = \ssh2_scp_recv($session, $remote_file, $local_file);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param resource $session
 * @param string $local_file
 * @param string $remote_file
 * @param int $create_mode
 * @throws Ssh2Exception
 *
 */
function ssh2_scp_send($session, string $local_file, string $remote_file, int $create_mode = 0644): void
{
    error_clear_last();
    $safeResult = \ssh2_scp_send($session, $local_file, $remote_file, $create_mode);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param resource $channel
 * @throws Ssh2Exception
 *
 */
function ssh2_send_eof($channel): void
{
    error_clear_last();
    $safeResult = \ssh2_send_eof($channel);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param resource $sftp
 * @param string $filename
 * @param int $mode
 * @throws Ssh2Exception
 *
 */
function ssh2_sftp_chmod($sftp, string $filename, int $mode): void
{
    error_clear_last();
    $safeResult = \ssh2_sftp_chmod($sftp, $filename, $mode);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param resource $sftp
 * @param string $dirname
 * @param int $mode
 * @param bool $recursive
 * @throws Ssh2Exception
 *
 */
function ssh2_sftp_mkdir($sftp, string $dirname, int $mode = 0777, bool $recursive = false): void
{
    error_clear_last();
    $safeResult = \ssh2_sftp_mkdir($sftp, $dirname, $mode, $recursive);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param resource $sftp
 * @param string $from
 * @param string $to
 * @throws Ssh2Exception
 *
 */
function ssh2_sftp_rename($sftp, string $from, string $to): void
{
    error_clear_last();
    $safeResult = \ssh2_sftp_rename($sftp, $from, $to);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param resource $sftp
 * @param string $dirname
 * @throws Ssh2Exception
 *
 */
function ssh2_sftp_rmdir($sftp, string $dirname): void
{
    error_clear_last();
    $safeResult = \ssh2_sftp_rmdir($sftp, $dirname);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param resource $sftp
 * @param string $target
 * @param string $link
 * @throws Ssh2Exception
 *
 */
function ssh2_sftp_symlink($sftp, string $target, string $link): void
{
    error_clear_last();
    $safeResult = \ssh2_sftp_symlink($sftp, $target, $link);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param resource $sftp
 * @param string $filename
 * @throws Ssh2Exception
 *
 */
function ssh2_sftp_unlink($sftp, string $filename): void
{
    error_clear_last();
    $safeResult = \ssh2_sftp_unlink($sftp, $filename);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
}


/**
 * @param resource $session
 * @return resource
 * @throws Ssh2Exception
 *
 */
function ssh2_sftp($session)
{
    error_clear_last();
    $safeResult = \ssh2_sftp($session);
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $session
 * @param string $term_type
 * @param array|null $env
 * @param int $width
 * @param int $height
 * @param int $width_height_type
 * @return resource
 * @throws Ssh2Exception
 *
 */
function ssh2_shell($session, string $term_type = "vanilla", ?array $env = null, int $width = 80, int $height = 25, int $width_height_type = SSH2_TERM_UNIT_CHARS)
{
    error_clear_last();
    if ($width_height_type !== SSH2_TERM_UNIT_CHARS) {
        $safeResult = \ssh2_shell($session, $term_type, $env, $width, $height, $width_height_type);
    } elseif ($height !== 25) {
        $safeResult = \ssh2_shell($session, $term_type, $env, $width, $height);
    } elseif ($width !== 80) {
        $safeResult = \ssh2_shell($session, $term_type, $env, $width);
    } elseif ($env !== null) {
        $safeResult = \ssh2_shell($session, $term_type, $env);
    } else {
        $safeResult = \ssh2_shell($session, $term_type);
    }
    if ($safeResult === false) {
        throw Ssh2Exception::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\RpminfoException;

/**
 * @param int $tag
 * @throws RpminfoException
 *
 */
function rpmaddtag(int $tag): void
{
    error_clear_last();
    $safeResult = \rpmaddtag($tag);
    if ($safeResult === false) {
        throw RpminfoException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\YamlException;

/**
 * @param string $filename
 * @param int $pos
 * @param int|null $ndocs
 * @param array|null $callbacks
 * @return mixed
 * @throws YamlException
 *
 */
function yaml_parse_file(string $filename, int $pos = 0, ?int &$ndocs = null, ?array $callbacks = null)
{
    error_clear_last();
    if ($callbacks !== null) {
        $safeResult = \yaml_parse_file($filename, $pos, $ndocs, $callbacks);
    } else {
        $safeResult = \yaml_parse_file($filename, $pos, $ndocs);
    }
    if ($safeResult === false) {
        throw YamlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $url
 * @param int $pos
 * @param int|null $ndocs
 * @param array|null $callbacks
 * @return mixed
 * @throws YamlException
 *
 */
function yaml_parse_url(string $url, int $pos = 0, ?int &$ndocs = null, ?array $callbacks = null)
{
    error_clear_last();
    if ($callbacks !== null) {
        $safeResult = \yaml_parse_url($url, $pos, $ndocs, $callbacks);
    } else {
        $safeResult = \yaml_parse_url($url, $pos, $ndocs);
    }
    if ($safeResult === false) {
        throw YamlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $input
 * @param int $pos
 * @param int|null $ndocs
 * @param array|null $callbacks
 * @return mixed
 * @throws YamlException
 *
 */
function yaml_parse(string $input, int $pos = 0, ?int &$ndocs = null, ?array $callbacks = null)
{
    error_clear_last();
    if ($callbacks !== null) {
        $safeResult = \yaml_parse($input, $pos, $ndocs, $callbacks);
    } else {
        $safeResult = \yaml_parse($input, $pos, $ndocs);
    }
    if ($safeResult === false) {
        throw YamlException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\MysqlException;

/**
 * @param null|resource $link_identifier
 * @throws MysqlException
 *
 */
function mysql_close($link_identifier = null): void
{
    error_clear_last();
    $safeResult = \mysql_close($link_identifier);
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
}


/**
 * @param string $server
 * @param string $username
 * @param string $password
 * @param bool $new_link
 * @param int $client_flags
 * @return resource
 * @throws MysqlException
 *
 */
function mysql_connect(?string $server = null, ?string $username = null, ?string $password = null, bool $new_link = false, int $client_flags = 0)
{
    error_clear_last();
    if ($client_flags !== 0) {
        $safeResult = \mysql_connect($server, $username, $password, $new_link, $client_flags);
    } elseif ($new_link !== false) {
        $safeResult = \mysql_connect($server, $username, $password, $new_link);
    } elseif ($password !== null) {
        $safeResult = \mysql_connect($server, $username, $password);
    } elseif ($username !== null) {
        $safeResult = \mysql_connect($server, $username);
    } elseif ($server !== null) {
        $safeResult = \mysql_connect($server);
    } else {
        $safeResult = \mysql_connect();
    }
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $database_name
 * @param null|resource $link_identifier
 * @throws MysqlException
 *
 */
function mysql_create_db(string $database_name, $link_identifier = null): void
{
    error_clear_last();
    $safeResult = \mysql_create_db($database_name, $link_identifier);
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
}


/**
 * @param resource $result
 * @param int $row_number
 * @throws MysqlException
 *
 */
function mysql_data_seek($result, int $row_number): void
{
    error_clear_last();
    $safeResult = \mysql_data_seek($result, $row_number);
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
}


/**
 * @param resource $result
 * @param int $row
 * @param mixed $field
 * @return string
 * @throws MysqlException
 *
 */
function mysql_db_name($result, int $row, $field = null): string
{
    error_clear_last();
    $safeResult = \mysql_db_name($result, $row, $field);
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $database
 * @param string $query
 * @param null|resource $link_identifier
 * @return bool|resource
 * @throws MysqlException
 *
 */
function mysql_db_query(string $database, string $query, $link_identifier = null)
{
    error_clear_last();
    $safeResult = \mysql_db_query($database, $query, $link_identifier);
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $database_name
 * @param null|resource $link_identifier
 * @throws MysqlException
 *
 */
function mysql_drop_db(string $database_name, $link_identifier = null): void
{
    error_clear_last();
    $safeResult = \mysql_drop_db($database_name, $link_identifier);
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
}


/**
 * @param resource $result
 * @return array
 * @throws MysqlException
 *
 */
function mysql_fetch_lengths($result): array
{
    error_clear_last();
    $safeResult = \mysql_fetch_lengths($result);
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $result
 * @param int $field_offset
 * @return string
 * @throws MysqlException
 *
 */
function mysql_field_flags($result, int $field_offset): string
{
    error_clear_last();
    $safeResult = \mysql_field_flags($result, $field_offset);
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $result
 * @param int $field_offset
 * @return int
 * @throws MysqlException
 *
 */
function mysql_field_len($result, int $field_offset): int
{
    error_clear_last();
    $safeResult = \mysql_field_len($result, $field_offset);
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $result
 * @param int $field_offset
 * @return string
 * @throws MysqlException
 *
 */
function mysql_field_name($result, int $field_offset): string
{
    error_clear_last();
    $safeResult = \mysql_field_name($result, $field_offset);
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $result
 * @param int $field_offset
 * @throws MysqlException
 *
 */
function mysql_field_seek($result, int $field_offset): void
{
    error_clear_last();
    $safeResult = \mysql_field_seek($result, $field_offset);
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
}


/**
 * @param resource $result
 * @throws MysqlException
 *
 */
function mysql_free_result($result): void
{
    error_clear_last();
    $safeResult = \mysql_free_result($result);
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
}


/**
 * @param null|resource $link_identifier
 * @return string
 * @throws MysqlException
 *
 */
function mysql_get_host_info($link_identifier = null): string
{
    error_clear_last();
    $safeResult = \mysql_get_host_info($link_identifier);
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param null|resource $link_identifier
 * @return int
 * @throws MysqlException
 *
 */
function mysql_get_proto_info($link_identifier = null): int
{
    error_clear_last();
    $safeResult = \mysql_get_proto_info($link_identifier);
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param null|resource $link_identifier
 * @return string
 * @throws MysqlException
 *
 */
function mysql_get_server_info($link_identifier = null): string
{
    error_clear_last();
    $safeResult = \mysql_get_server_info($link_identifier);
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param null|resource $link_identifier
 * @return string
 * @throws MysqlException
 *
 */
function mysql_info($link_identifier = null): string
{
    error_clear_last();
    $safeResult = \mysql_info($link_identifier);
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param null|resource $link_identifier
 * @return resource
 * @throws MysqlException
 *
 */
function mysql_list_dbs($link_identifier = null)
{
    error_clear_last();
    $safeResult = \mysql_list_dbs($link_identifier);
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $database_name
 * @param string $table_name
 * @param null|resource $link_identifier
 * @return resource
 * @throws MysqlException
 *
 */
function mysql_list_fields(string $database_name, string $table_name, $link_identifier = null)
{
    error_clear_last();
    $safeResult = \mysql_list_fields($database_name, $table_name, $link_identifier);
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param null|resource $link_identifier
 * @return resource
 * @throws MysqlException
 *
 */
function mysql_list_processes($link_identifier = null)
{
    error_clear_last();
    $safeResult = \mysql_list_processes($link_identifier);
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $database
 * @param null|resource $link_identifier
 * @return resource
 * @throws MysqlException
 *
 */
function mysql_list_tables(string $database, $link_identifier = null)
{
    error_clear_last();
    $safeResult = \mysql_list_tables($database, $link_identifier);
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $result
 * @return int
 * @throws MysqlException
 *
 */
function mysql_num_fields($result): int
{
    error_clear_last();
    $safeResult = \mysql_num_fields($result);
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $result
 * @return int
 * @throws MysqlException
 *
 */
function mysql_num_rows($result): int
{
    error_clear_last();
    $safeResult = \mysql_num_rows($result);
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $query
 * @param null|resource $link_identifier
 * @return bool|resource
 * @throws MysqlException
 *
 */
function mysql_query(string $query, $link_identifier = null)
{
    error_clear_last();
    $safeResult = \mysql_query($query, $link_identifier);
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $unescaped_string
 * @param null|resource $link_identifier
 * @return string
 * @throws MysqlException
 *
 */
function mysql_real_escape_string(string $unescaped_string, $link_identifier = null): string
{
    error_clear_last();
    $safeResult = \mysql_real_escape_string($unescaped_string, $link_identifier);
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $result
 * @param int $row
 * @param mixed $field
 * @return string
 * @throws MysqlException
 *
 */
function mysql_result($result, int $row, $field = 0): string
{
    error_clear_last();
    $safeResult = \mysql_result($result, $row, $field);
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $database_name
 * @param null|resource $link_identifier
 * @throws MysqlException
 *
 */
function mysql_select_db(string $database_name, $link_identifier = null): void
{
    error_clear_last();
    $safeResult = \mysql_select_db($database_name, $link_identifier);
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
}


/**
 * @param string $charset
 * @param null|resource $link_identifier
 * @throws MysqlException
 *
 */
function mysql_set_charset(string $charset, $link_identifier = null): void
{
    error_clear_last();
    $safeResult = \mysql_set_charset($charset, $link_identifier);
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
}


/**
 * @param resource $result
 * @param int $i
 * @return string
 * @throws MysqlException
 *
 */
function mysql_tablename($result, int $i): string
{
    error_clear_last();
    $safeResult = \mysql_tablename($result, $i);
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param null|resource $link_identifier
 * @return int
 * @throws MysqlException
 *
 */
function mysql_thread_id($link_identifier = null): int
{
    error_clear_last();
    $safeResult = \mysql_thread_id($link_identifier);
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $query
 * @param null|resource $link_identifier
 * @return bool|resource
 * @throws MysqlException
 *
 */
function mysql_unbuffered_query(string $query, $link_identifier = null)
{
    error_clear_last();
    $safeResult = \mysql_unbuffered_query($query, $link_identifier);
    if ($safeResult === false) {
        throw MysqlException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\FunchandException;

/**
 * @param callable(): void $callback
 * @param mixed $args
 * @throws FunchandException
 *
 */
function register_tick_function(callable $callback, ...$args): void
{
    error_clear_last();
    if ($args !== []) {
        $safeResult = \register_tick_function($callback, ...$args);
    } else {
        $safeResult = \register_tick_function($callback);
    }
    if ($safeResult === false) {
        throw FunchandException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\HashException;

/**
 * @param string $algo
 * @param string $key
 * @param int $length
 * @param string $info
 * @param string $salt
 * @return non-falsy-string
 * @throws HashException
 *
 */
function hash_hkdf(string $algo, string $key, int $length = 0, string $info = "", string $salt = ""): string
{
    error_clear_last();
    $safeResult = \hash_hkdf($algo, $key, $length, $info, $salt);
    if ($safeResult === false) {
        throw HashException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \HashContext $context
 * @param string $filename
 * @param \HashContext|null $stream_context
 * @throws HashException
 *
 */
function hash_update_file(\HashContext $context, string $filename, ?\HashContext $stream_context = null): void
{
    error_clear_last();
    if ($stream_context !== null) {
        $safeResult = \hash_update_file($context, $filename, $stream_context);
    } else {
        $safeResult = \hash_update_file($context, $filename);
    }
    if ($safeResult === false) {
        throw HashException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\MiscException;

/**
 * @param string $constant_name
 * @param mixed $value
 * @param bool $case_insensitive
 * @throws MiscException
 *
 */
function define(string $constant_name, $value, bool $case_insensitive = false): void
{
    error_clear_last();
    $safeResult = \define($constant_name, $value, $case_insensitive);
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @param bool $return
 * @return bool|string
 * @throws MiscException
 *
 */
function highlight_file(string $filename, bool $return = false)
{
    error_clear_last();
    $safeResult = \highlight_file($filename, $return);
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $string
 * @param bool $return
 * @return bool|string
 * @throws MiscException
 *
 */
function highlight_string(string $string, bool $return = false)
{
    error_clear_last();
    $safeResult = \highlight_string($string, $return);
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param bool $as_number
 * @return array{0:int,1:int}|float|int
 * @throws MiscException
 *
 */
function hrtime(bool $as_number = false)
{
    error_clear_last();
    $safeResult = \hrtime($as_number);
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $format
 * @param mixed $values
 * @return string
 * @throws MiscException
 *
 */
function pack(string $format, ...$values): string
{
    error_clear_last();
    if ($values !== []) {
        $safeResult = \pack($format, ...$values);
    } else {
        $safeResult = \pack($format);
    }
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int|string $in_codepage
 * @param int|string $out_codepage
 * @param string $subject
 * @return string
 * @throws MiscException
 *
 */
function sapi_windows_cp_conv($in_codepage, $out_codepage, string $subject): string
{
    error_clear_last();
    $safeResult = \sapi_windows_cp_conv($in_codepage, $out_codepage, $subject);
    if ($safeResult === null) {
        throw MiscException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $codepage
 * @throws MiscException
 *
 */
function sapi_windows_cp_set(int $codepage): void
{
    error_clear_last();
    $safeResult = \sapi_windows_cp_set($codepage);
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
}


/**
 * @param int $event
 * @param int $pid
 * @throws MiscException
 *
 */
function sapi_windows_generate_ctrl_event(int $event, int $pid = 0): void
{
    error_clear_last();
    $safeResult = \sapi_windows_generate_ctrl_event($event, $pid);
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
}


/**
 * @param callable|null $handler
 * @param bool $add
 * @throws MiscException
 *
 */
function sapi_windows_set_ctrl_handler(?callable $handler, bool $add = true): void
{
    error_clear_last();
    $safeResult = \sapi_windows_set_ctrl_handler($handler, $add);
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
}


/**
 * @param resource $stream
 * @param bool|null $enable
 * @throws MiscException
 *
 */
function sapi_windows_vt100_support($stream, ?bool $enable = null): void
{
    error_clear_last();
    if ($enable !== null) {
        $safeResult = \sapi_windows_vt100_support($stream, $enable);
    } else {
        $safeResult = \sapi_windows_vt100_support($stream);
    }
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
}


/**
 * @param int $seconds
 * @return int
 * @throws MiscException
 *
 */
function sleep(int $seconds): int
{
    error_clear_last();
    $safeResult = \sleep($seconds);
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $seconds
 * @param int $nanoseconds
 * @return array{seconds:0|positive-int,nanoseconds:0|positive-int}|bool
 * @throws MiscException
 *
 */
function time_nanosleep(int $seconds, int $nanoseconds)
{
    error_clear_last();
    $safeResult = \time_nanosleep($seconds, $nanoseconds);
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param float $timestamp
 * @throws MiscException
 *
 */
function time_sleep_until(float $timestamp): void
{
    error_clear_last();
    $safeResult = \time_sleep_until($timestamp);
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
}


/**
 * @param string $format
 * @param string $string
 * @param int $offset
 * @return array
 * @throws MiscException
 *
 */
function unpack(string $format, string $string, int $offset = 0): array
{
    error_clear_last();
    $safeResult = \unpack($format, $string, $offset);
    if ($safeResult === false) {
        throw MiscException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\CurlException;

/**
 * @param \CurlHandle $handle
 * @return \CurlHandle
 * @throws CurlException
 *
 */
function curl_copy_handle(\CurlHandle $handle): \CurlHandle
{
    error_clear_last();
    $safeResult = \curl_copy_handle($handle);
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($handle);
    }
    return $safeResult;
}


/**
 * @param \CurlHandle $handle
 * @param string $string
 * @return string
 * @throws CurlException
 *
 */
function curl_escape(\CurlHandle $handle, string $string): string
{
    error_clear_last();
    $safeResult = \curl_escape($handle, $string);
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($handle);
    }
    return $safeResult;
}


/**
 * @param \CurlHandle $handle
 * @return bool|string
 * @throws CurlException
 *
 */
function curl_exec(\CurlHandle $handle)
{
    error_clear_last();
    $safeResult = \curl_exec($handle);
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($handle);
    }
    return $safeResult;
}


/**
 * @param \CurlHandle $handle
 * @param int|null $option
 * @return mixed
 * @throws CurlException
 *
 */
function curl_getinfo(\CurlHandle $handle, ?int $option = null)
{
    error_clear_last();
    if ($option !== null) {
        $safeResult = \curl_getinfo($handle, $option);
    } else {
        $safeResult = \curl_getinfo($handle);
    }
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($handle);
    }
    return $safeResult;
}


/**
 * @param null|string $url
 * @return \CurlHandle
 * @throws CurlException
 *
 */
function curl_init(?string $url = null): \CurlHandle
{
    error_clear_last();
    if ($url !== null) {
        $safeResult = \curl_init($url);
    } else {
        $safeResult = \curl_init();
    }
    if ($safeResult === false) {
        throw CurlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \CurlMultiHandle $multi_handle
 * @param int|null $queued_messages
 * @return array
 * @throws CurlException
 *
 */
function curl_multi_info_read(\CurlMultiHandle $multi_handle, ?int &$queued_messages = null): array
{
    error_clear_last();
    $safeResult = \curl_multi_info_read($multi_handle, $queued_messages);
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($multi_handle);
    }
    return $safeResult;
}


/**
 * @return \CurlMultiHandle
 * @throws CurlException
 *
 */
function curl_multi_init(): \CurlMultiHandle
{
    error_clear_last();
    $safeResult = \curl_multi_init();
    if ($safeResult === false) {
        throw CurlException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \CurlMultiHandle $multi_handle
 * @param int $option
 * @param mixed $value
 * @throws CurlException
 *
 */
function curl_multi_setopt(\CurlMultiHandle $multi_handle, int $option, $value): void
{
    error_clear_last();
    $safeResult = \curl_multi_setopt($multi_handle, $option, $value);
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($multi_handle);
    }
}


/**
 * @param \CurlHandle $handle
 * @param int $option
 * @param mixed $value
 * @throws CurlException
 *
 */
function curl_setopt(\CurlHandle $handle, int $option, $value): void
{
    error_clear_last();
    $safeResult = \curl_setopt($handle, $option, $value);
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($handle);
    }
}


/**
 * @param \CurlShareHandle $share_handle
 * @return int
 * @throws CurlException
 *
 */
function curl_share_errno(\CurlShareHandle $share_handle): int
{
    error_clear_last();
    $safeResult = \curl_share_errno($share_handle);
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($share_handle);
    }
    return $safeResult;
}


/**
 * @param \CurlShareHandle $share_handle
 * @param int $option
 * @param mixed $value
 * @throws CurlException
 *
 */
function curl_share_setopt(\CurlShareHandle $share_handle, int $option, $value): void
{
    error_clear_last();
    $safeResult = \curl_share_setopt($share_handle, $option, $value);
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($share_handle);
    }
}


/**
 * @param \CurlHandle $handle
 * @param string $string
 * @return string
 * @throws CurlException
 *
 */
function curl_unescape(\CurlHandle $handle, string $string): string
{
    error_clear_last();
    $safeResult = \curl_unescape($handle, $string);
    if ($safeResult === false) {
        throw CurlException::createFromPhpError($handle);
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\CalendarException;

/**
 * @param int|null $timestamp
 * @return int
 * @throws CalendarException
 *
 */
function unixtojd(?int $timestamp = null): int
{
    error_clear_last();
    if ($timestamp !== null) {
        $safeResult = \unixtojd($timestamp);
    } else {
        $safeResult = \unixtojd();
    }
    if ($safeResult === false) {
        throw CalendarException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\SodiumException;

/**
 * @param string $ciphertext
 * @param string $additional_data
 * @param string $nonce
 * @param string $key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_aead_aes256gcm_decrypt(string $ciphertext, string $additional_data, string $nonce, string $key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $additional_data, $nonce, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $ciphertext
 * @param string $additional_data
 * @param string $nonce
 * @param string $key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_aead_chacha20poly1305_decrypt(string $ciphertext, string $additional_data, string $nonce, string $key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_aead_chacha20poly1305_decrypt($ciphertext, $additional_data, $nonce, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $message
 * @param string $additional_data
 * @param string $nonce
 * @param string $key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_aead_chacha20poly1305_encrypt(string $message, string $additional_data, string $nonce, string $key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_aead_chacha20poly1305_encrypt($message, $additional_data, $nonce, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $ciphertext
 * @param string $additional_data
 * @param string $nonce
 * @param string $key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_aead_chacha20poly1305_ietf_decrypt(string $ciphertext, string $additional_data, string $nonce, string $key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_aead_chacha20poly1305_ietf_decrypt($ciphertext, $additional_data, $nonce, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $message
 * @param string $additional_data
 * @param string $nonce
 * @param string $key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_aead_chacha20poly1305_ietf_encrypt(string $message, string $additional_data, string $nonce, string $key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_aead_chacha20poly1305_ietf_encrypt($message, $additional_data, $nonce, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $ciphertext
 * @param string $additional_data
 * @param string $nonce
 * @param string $key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_aead_xchacha20poly1305_ietf_decrypt(string $ciphertext, string $additional_data, string $nonce, string $key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_aead_xchacha20poly1305_ietf_decrypt($ciphertext, $additional_data, $nonce, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $message
 * @param string $additional_data
 * @param string $nonce
 * @param string $key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_aead_xchacha20poly1305_ietf_encrypt(string $message, string $additional_data, string $nonce, string $key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_aead_xchacha20poly1305_ietf_encrypt($message, $additional_data, $nonce, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $mac
 * @param string $message
 * @param string $key
 * @throws SodiumException
 *
 */
function sodium_crypto_auth_verify(string $mac, string $message, string $key): void
{
    error_clear_last();
    $safeResult = \sodium_crypto_auth_verify($mac, $message, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
}


/**
 * @param string $ciphertext
 * @param string $nonce
 * @param string $key_pair
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_box_open(string $ciphertext, string $nonce, string $key_pair): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_box_open($ciphertext, $nonce, $key_pair);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $ciphertext
 * @param string $key_pair
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_box_seal_open(string $ciphertext, string $key_pair): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_box_seal_open($ciphertext, $key_pair);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param non-empty-string $state
 * @param string $message
 * @throws SodiumException
 *
 */
function sodium_crypto_generichash_update(string &$state, string $message): void
{
    error_clear_last();
    $safeResult = \sodium_crypto_generichash_update($state, $message);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
}


/**
 * @param string $ciphertext
 * @param string $nonce
 * @param string $key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_secretbox_open(string $ciphertext, string $nonce, string $key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_secretbox_open($ciphertext, $nonce, $key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $signed_message
 * @param non-empty-string $public_key
 * @return string
 * @throws SodiumException
 *
 */
function sodium_crypto_sign_open(string $signed_message, string $public_key): string
{
    error_clear_last();
    $safeResult = \sodium_crypto_sign_open($signed_message, $public_key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param non-empty-string $signature
 * @param string $message
 * @param non-empty-string $public_key
 * @throws SodiumException
 *
 */
function sodium_crypto_sign_verify_detached(string $signature, string $message, string $public_key): void
{
    error_clear_last();
    $safeResult = \sodium_crypto_sign_verify_detached($signature, $message, $public_key);
    if ($safeResult === false) {
        throw SodiumException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\ZlibException;

/**
 * @param \DeflateContext $context
 * @param string $data
 * @param int $flush_mode
 * @return string
 * @throws ZlibException
 *
 */
function deflate_add(\DeflateContext $context, string $data, int $flush_mode = ZLIB_SYNC_FLUSH): string
{
    error_clear_last();
    $safeResult = \deflate_add($context, $data, $flush_mode);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $encoding
 * @param array $options
 * @return \DeflateContext
 * @throws ZlibException
 *
 */
function deflate_init(int $encoding, array $options = []): \DeflateContext
{
    error_clear_last();
    $safeResult = \deflate_init($encoding, $options);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @throws ZlibException
 *
 */
function gzclose($stream): void
{
    error_clear_last();
    $safeResult = \gzclose($stream);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
}


/**
 * @param string $data
 * @param int $level
 * @param int $encoding
 * @return string
 * @throws ZlibException
 *
 */
function gzcompress(string $data, int $level = -1, int $encoding = ZLIB_ENCODING_DEFLATE): string
{
    error_clear_last();
    $safeResult = \gzcompress($data, $level, $encoding);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param int $max_length
 * @return string
 * @throws ZlibException
 *
 */
function gzdecode(string $data, int $max_length = 0): string
{
    error_clear_last();
    $safeResult = \gzdecode($data, $max_length);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param int $level
 * @param int $encoding
 * @return string
 * @throws ZlibException
 *
 */
function gzdeflate(string $data, int $level = -1, int $encoding = ZLIB_ENCODING_RAW): string
{
    error_clear_last();
    $safeResult = \gzdeflate($data, $level, $encoding);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param int $level
 * @param int $encoding
 * @return string
 * @throws ZlibException
 *
 */
function gzencode(string $data, int $level = -1, int $encoding = ZLIB_ENCODING_GZIP): string
{
    error_clear_last();
    $safeResult = \gzencode($data, $level, $encoding);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param  $use_include_path
 * @return list
 * @throws ZlibException
 *
 */
function gzfile(string $filename, $use_include_path = 0): array
{
    error_clear_last();
    $safeResult = \gzfile($filename, $use_include_path);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @param int|null $length
 * @return string
 * @throws ZlibException
 *
 */
function gzgets($stream, ?int $length = null): string
{
    error_clear_last();
    if ($length !== null) {
        $safeResult = \gzgets($stream, $length);
    } else {
        $safeResult = \gzgets($stream);
    }
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param int $max_length
 * @return string
 * @throws ZlibException
 *
 */
function gzinflate(string $data, int $max_length = 0): string
{
    error_clear_last();
    $safeResult = \gzinflate($data, $max_length);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param string $mode
 * @param  $use_include_path
 * @return resource
 * @throws ZlibException
 *
 */
function gzopen(string $filename, string $mode, $use_include_path = 0)
{
    error_clear_last();
    $safeResult = \gzopen($filename, $mode, $use_include_path);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @return int
 * @throws ZlibException
 *
 */
function gzpassthru($stream): int
{
    error_clear_last();
    $safeResult = \gzpassthru($stream);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @param int $length
 * @return string
 * @throws ZlibException
 *
 */
function gzread($stream, int $length): string
{
    error_clear_last();
    $safeResult = \gzread($stream, $length);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @throws ZlibException
 *
 */
function gzrewind($stream): void
{
    error_clear_last();
    $safeResult = \gzrewind($stream);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
}


/**
 * @param resource $stream
 * @return int
 * @throws ZlibException
 *
 */
function gztell($stream): int
{
    error_clear_last();
    $safeResult = \gztell($stream);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param int $max_length
 * @return string
 * @throws ZlibException
 *
 */
function gzuncompress(string $data, int $max_length = 0): string
{
    error_clear_last();
    $safeResult = \gzuncompress($data, $max_length);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stream
 * @param string $data
 * @param int|null $length
 * @return int
 * @throws ZlibException
 *
 */
function gzwrite($stream, string $data, ?int $length = null): int
{
    error_clear_last();
    if ($length !== null) {
        $safeResult = \gzwrite($stream, $data, $length);
    } else {
        $safeResult = \gzwrite($stream, $data);
    }
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \InflateContext $context
 * @return int
 * @throws ZlibException
 *
 */
function inflate_get_read_len(\InflateContext $context): int
{
    error_clear_last();
    $safeResult = \inflate_get_read_len($context);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \InflateContext $context
 * @return int
 * @throws ZlibException
 *
 */
function inflate_get_status(\InflateContext $context): int
{
    error_clear_last();
    $safeResult = \inflate_get_status($context);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \InflateContext $context
 * @param string $data
 * @param int $flush_mode
 * @return string
 * @throws ZlibException
 *
 */
function inflate_add(\InflateContext $context, string $data, int $flush_mode = ZLIB_SYNC_FLUSH): string
{
    error_clear_last();
    $safeResult = \inflate_add($context, $data, $flush_mode);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $encoding
 * @param array $options
 * @return \InflateContext
 * @throws ZlibException
 *
 */
function inflate_init(int $encoding, array $options = []): \InflateContext
{
    error_clear_last();
    $safeResult = \inflate_init($encoding, $options);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param  $use_include_path
 * @return 0|positive-int
 * @throws ZlibException
 *
 */
function readgzfile(string $filename, $use_include_path = 0): int
{
    error_clear_last();
    $safeResult = \readgzfile($filename, $use_include_path);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @param int $max_length
 * @return string
 * @throws ZlibException
 *
 */
function zlib_decode(string $data, int $max_length = 0): string
{
    error_clear_last();
    $safeResult = \zlib_decode($data, $max_length);
    if ($safeResult === false) {
        throw ZlibException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\ImageException;

/**
 * @param string $filename
 * @param array|null $image_info
 * @return array{0: 0|positive-int, 1: 0|positive-int, 2: int, 3: string, mime: string, channels: int, bits: int}|null
 * @throws ImageException
 *
 */
function getimagesize(string $filename, ?array &$image_info = null): ?array
{
    error_clear_last();
    $safeResult = \getimagesize($filename, $image_info);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $image_type
 * @param bool $include_dot
 * @return string
 * @throws ImageException
 *
 */
function image_type_to_extension(int $image_type, bool $include_dot = true): string
{
    error_clear_last();
    $safeResult = \image_type_to_extension($image_type, $include_dot);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param array $affine
 * @param array|null $clip
 * @return \GdImage
 * @throws ImageException
 *
 */
function imageaffine(\GdImage $image, array $affine, ?array $clip = null): \GdImage
{
    error_clear_last();
    if ($clip !== null) {
        $safeResult = \imageaffine($image, $affine, $clip);
    } else {
        $safeResult = \imageaffine($image, $affine);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param array $matrix1
 * @param array $matrix2
 * @return array{0:float,1:float,2:float,3:float,4:float,5:float}
 * @throws ImageException
 *
 */
function imageaffinematrixconcat(array $matrix1, array $matrix2): array
{
    error_clear_last();
    $safeResult = \imageaffinematrixconcat($matrix1, $matrix2);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $type
 * @param array|float $options
 * @return array{0:float,1:float,2:float,3:float,4:float,5:float}
 * @throws ImageException
 *
 */
function imageaffinematrixget(int $type, $options): array
{
    error_clear_last();
    $safeResult = \imageaffinematrixget($type, $options);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param bool $enable
 * @throws ImageException
 *
 */
function imagealphablending(\GdImage $image, bool $enable): void
{
    error_clear_last();
    $safeResult = \imagealphablending($image, $enable);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param bool $enable
 * @throws ImageException
 *
 */
function imageantialias(\GdImage $image, bool $enable): void
{
    error_clear_last();
    $safeResult = \imageantialias($image, $enable);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $center_x
 * @param int $center_y
 * @param int $width
 * @param int $height
 * @param int $start_angle
 * @param int $end_angle
 * @param int $color
 * @throws ImageException
 *
 */
function imagearc(\GdImage $image, int $center_x, int $center_y, int $width, int $height, int $start_angle, int $end_angle, int $color): void
{
    error_clear_last();
    $safeResult = \imagearc($image, $center_x, $center_y, $width, $height, $start_angle, $end_angle, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param null|resource|string $file
 * @param int $quality
 * @param int $speed
 * @throws ImageException
 *
 */
function imageavif(\GdImage $image, $file = null, int $quality = -1, int $speed = -1): void
{
    error_clear_last();
    if ($speed !== -1) {
        $safeResult = \imageavif($image, $file, $quality, $speed);
    } elseif ($quality !== -1) {
        $safeResult = \imageavif($image, $file, $quality);
    } elseif ($file !== null) {
        $safeResult = \imageavif($image, $file);
    } else {
        $safeResult = \imageavif($image);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param null|resource|string $file
 * @param bool $compressed
 * @throws ImageException
 *
 */
function imagebmp(\GdImage $image, $file = null, bool $compressed = true): void
{
    error_clear_last();
    if ($compressed !== true) {
        $safeResult = \imagebmp($image, $file, $compressed);
    } elseif ($file !== null) {
        $safeResult = \imagebmp($image, $file);
    } else {
        $safeResult = \imagebmp($image);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $font
 * @param int $x
 * @param int $y
 * @param string $char
 * @param int $color
 * @throws ImageException
 *
 */
function imagechar(\GdImage $image, int $font, int $x, int $y, string $char, int $color): void
{
    error_clear_last();
    $safeResult = \imagechar($image, $font, $x, $y, $char, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $font
 * @param int $x
 * @param int $y
 * @param string $char
 * @param int $color
 * @throws ImageException
 *
 */
function imagecharup(\GdImage $image, int $font, int $x, int $y, string $char, int $color): void
{
    error_clear_last();
    $safeResult = \imagecharup($image, $font, $x, $y, $char, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $x
 * @param int $y
 * @return int
 * @throws ImageException
 *
 */
function imagecolorat(\GdImage $image, int $x, int $y): int
{
    error_clear_last();
    $safeResult = \imagecolorat($image, $x, $y);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param int $color
 * @throws ImageException
 *
 */
function imagecolordeallocate(\GdImage $image, int $color): void
{
    error_clear_last();
    $safeResult = \imagecolordeallocate($image, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image1
 * @param \GdImage $image2
 * @throws ImageException
 *
 */
function imagecolormatch(\GdImage $image1, \GdImage $image2): void
{
    error_clear_last();
    $safeResult = \imagecolormatch($image1, $image2);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $color
 * @param int $red
 * @param int $green
 * @param int $blue
 * @param int $alpha
 * @throws ImageException
 *
 */
function imagecolorset(\GdImage $image, int $color, int $red, int $green, int $blue, int $alpha = 0): void
{
    error_clear_last();
    $safeResult = \imagecolorset($image, $color, $red, $green, $blue, $alpha);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $color
 * @return array{red: int, green: int, blue: int, alpha: int}
 * @throws ImageException
 *
 */
function imagecolorsforindex(\GdImage $image, int $color): array
{
    error_clear_last();
    $safeResult = \imagecolorsforindex($image, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param array $matrix
 * @param float $divisor
 * @param float $offset
 * @throws ImageException
 *
 */
function imageconvolution(\GdImage $image, array $matrix, float $divisor, float $offset): void
{
    error_clear_last();
    $safeResult = \imageconvolution($image, $matrix, $divisor, $offset);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $dst_image
 * @param \GdImage $src_image
 * @param int $dst_x
 * @param int $dst_y
 * @param int $src_x
 * @param int $src_y
 * @param int $src_width
 * @param int $src_height
 * @throws ImageException
 *
 */
function imagecopy(\GdImage $dst_image, \GdImage $src_image, int $dst_x, int $dst_y, int $src_x, int $src_y, int $src_width, int $src_height): void
{
    error_clear_last();
    $safeResult = \imagecopy($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $src_width, $src_height);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $dst_image
 * @param \GdImage $src_image
 * @param int $dst_x
 * @param int $dst_y
 * @param int $src_x
 * @param int $src_y
 * @param int $src_width
 * @param int $src_height
 * @param int $pct
 * @throws ImageException
 *
 */
function imagecopymerge(\GdImage $dst_image, \GdImage $src_image, int $dst_x, int $dst_y, int $src_x, int $src_y, int $src_width, int $src_height, int $pct): void
{
    error_clear_last();
    $safeResult = \imagecopymerge($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $src_width, $src_height, $pct);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $dst_image
 * @param \GdImage $src_image
 * @param int $dst_x
 * @param int $dst_y
 * @param int $src_x
 * @param int $src_y
 * @param int $src_width
 * @param int $src_height
 * @param int $pct
 * @throws ImageException
 *
 */
function imagecopymergegray(\GdImage $dst_image, \GdImage $src_image, int $dst_x, int $dst_y, int $src_x, int $src_y, int $src_width, int $src_height, int $pct): void
{
    error_clear_last();
    $safeResult = \imagecopymergegray($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $src_width, $src_height, $pct);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $dst_image
 * @param \GdImage $src_image
 * @param int $dst_x
 * @param int $dst_y
 * @param int $src_x
 * @param int $src_y
 * @param int $dst_width
 * @param int $dst_height
 * @param int $src_width
 * @param int $src_height
 * @throws ImageException
 *
 */
function imagecopyresampled(\GdImage $dst_image, \GdImage $src_image, int $dst_x, int $dst_y, int $src_x, int $src_y, int $dst_width, int $dst_height, int $src_width, int $src_height): void
{
    error_clear_last();
    $safeResult = \imagecopyresampled($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_width, $dst_height, $src_width, $src_height);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $dst_image
 * @param \GdImage $src_image
 * @param int $dst_x
 * @param int $dst_y
 * @param int $src_x
 * @param int $src_y
 * @param int $dst_width
 * @param int $dst_height
 * @param int $src_width
 * @param int $src_height
 * @throws ImageException
 *
 */
function imagecopyresized(\GdImage $dst_image, \GdImage $src_image, int $dst_x, int $dst_y, int $src_x, int $src_y, int $dst_width, int $dst_height, int $src_width, int $src_height): void
{
    error_clear_last();
    $safeResult = \imagecopyresized($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_width, $dst_height, $src_width, $src_height);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param int $width
 * @param int $height
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreate(int $width, int $height): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreate($width, $height);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromavif(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromavif($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefrombmp(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefrombmp($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromgd(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromgd($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromgd2(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromgd2($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param int $x
 * @param int $y
 * @param int $width
 * @param int $height
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromgd2part(string $filename, int $x, int $y, int $width, int $height): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromgd2part($filename, $x, $y, $width, $height);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromgif(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromgif($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromjpeg(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromjpeg($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefrompng(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefrompng($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $data
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromstring(string $data): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromstring($data);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromtga(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromtga($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromwbmp(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromwbmp($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromwebp(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromwebp($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromxbm(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromxbm($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatefromxpm(string $filename): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatefromxpm($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $width
 * @param int $height
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecreatetruecolor(int $width, int $height): \GdImage
{
    error_clear_last();
    $safeResult = \imagecreatetruecolor($width, $height);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param array $rectangle
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecrop(\GdImage $image, array $rectangle): \GdImage
{
    error_clear_last();
    $safeResult = \imagecrop($image, $rectangle);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param int $mode
 * @param float $threshold
 * @param int $color
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagecropauto(\GdImage $image, int $mode = IMG_CROP_DEFAULT, float $threshold = 0.5, int $color = -1): \GdImage
{
    error_clear_last();
    $safeResult = \imagecropauto($image, $mode, $threshold, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param int $x1
 * @param int $y1
 * @param int $x2
 * @param int $y2
 * @param int $color
 * @throws ImageException
 *
 */
function imagedashedline(\GdImage $image, int $x1, int $y1, int $x2, int $y2, int $color): void
{
    error_clear_last();
    $safeResult = \imagedashedline($image, $x1, $y1, $x2, $y2, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @throws ImageException
 *
 */
function imagedestroy(\GdImage $image): void
{
    error_clear_last();
    $safeResult = \imagedestroy($image);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $center_x
 * @param int $center_y
 * @param int $width
 * @param int $height
 * @param int $color
 * @throws ImageException
 *
 */
function imageellipse(\GdImage $image, int $center_x, int $center_y, int $width, int $height, int $color): void
{
    error_clear_last();
    $safeResult = \imageellipse($image, $center_x, $center_y, $width, $height, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $x
 * @param int $y
 * @param int $color
 * @throws ImageException
 *
 */
function imagefill(\GdImage $image, int $x, int $y, int $color): void
{
    error_clear_last();
    $safeResult = \imagefill($image, $x, $y, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $center_x
 * @param int $center_y
 * @param int $width
 * @param int $height
 * @param int $start_angle
 * @param int $end_angle
 * @param int $color
 * @param int $style
 * @throws ImageException
 *
 */
function imagefilledarc(\GdImage $image, int $center_x, int $center_y, int $width, int $height, int $start_angle, int $end_angle, int $color, int $style): void
{
    error_clear_last();
    $safeResult = \imagefilledarc($image, $center_x, $center_y, $width, $height, $start_angle, $end_angle, $color, $style);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $center_x
 * @param int $center_y
 * @param int $width
 * @param int $height
 * @param int $color
 * @throws ImageException
 *
 */
function imagefilledellipse(\GdImage $image, int $center_x, int $center_y, int $width, int $height, int $color): void
{
    error_clear_last();
    $safeResult = \imagefilledellipse($image, $center_x, $center_y, $width, $height, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $x1
 * @param int $y1
 * @param int $x2
 * @param int $y2
 * @param int $color
 * @throws ImageException
 *
 */
function imagefilledrectangle(\GdImage $image, int $x1, int $y1, int $x2, int $y2, int $color): void
{
    error_clear_last();
    $safeResult = \imagefilledrectangle($image, $x1, $y1, $x2, $y2, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $x
 * @param int $y
 * @param int $border_color
 * @param int $color
 * @throws ImageException
 *
 */
function imagefilltoborder(\GdImage $image, int $x, int $y, int $border_color, int $color): void
{
    error_clear_last();
    $safeResult = \imagefilltoborder($image, $x, $y, $border_color, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $filter
 * @param int $args
 * @throws ImageException
 *
 */
function imagefilter(\GdImage $image, int $filter, int ...$args): void
{
    error_clear_last();
    if ($args !== []) {
        $safeResult = \imagefilter($image, $filter, ...$args);
    } else {
        $safeResult = \imagefilter($image, $filter);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $mode
 * @throws ImageException
 *
 */
function imageflip(\GdImage $image, int $mode): void
{
    error_clear_last();
    $safeResult = \imageflip($image, $mode);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param float $size
 * @param float $angle
 * @param string $font_filename
 * @param string $string
 * @param array $options
 * @return array
 * @throws ImageException
 *
 */
function imageftbbox(float $size, float $angle, string $font_filename, string $string, array $options = []): array
{
    error_clear_last();
    $safeResult = \imageftbbox($size, $angle, $font_filename, $string, $options);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param float $size
 * @param float $angle
 * @param int $x
 * @param int $y
 * @param int $color
 * @param string $font_filename
 * @param string $text
 * @param array $options
 * @return array
 * @throws ImageException
 *
 */
function imagefttext(\GdImage $image, float $size, float $angle, int $x, int $y, int $color, string $font_filename, string $text, array $options = []): array
{
    error_clear_last();
    $safeResult = \imagefttext($image, $size, $angle, $x, $y, $color, $font_filename, $text, $options);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param float $input_gamma
 * @param float $output_gamma
 * @throws ImageException
 *
 */
function imagegammacorrect(\GdImage $image, float $input_gamma, float $output_gamma): void
{
    error_clear_last();
    $safeResult = \imagegammacorrect($image, $input_gamma, $output_gamma);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param null|resource|string $file
 * @throws ImageException
 *
 */
function imagegd(\GdImage $image, $file = null): void
{
    error_clear_last();
    if ($file !== null) {
        $safeResult = \imagegd($image, $file);
    } else {
        $safeResult = \imagegd($image);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param null|resource|string $file
 * @param int $chunk_size
 * @param int $mode
 * @throws ImageException
 *
 */
function imagegd2(\GdImage $image, $file = null, int $chunk_size = 128, int $mode = IMG_GD2_RAW): void
{
    error_clear_last();
    if ($mode !== IMG_GD2_RAW) {
        $safeResult = \imagegd2($image, $file, $chunk_size, $mode);
    } elseif ($chunk_size !== 128) {
        $safeResult = \imagegd2($image, $file, $chunk_size);
    } elseif ($file !== null) {
        $safeResult = \imagegd2($image, $file);
    } else {
        $safeResult = \imagegd2($image);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param null|resource|string $file
 * @throws ImageException
 *
 */
function imagegif(\GdImage $image, $file = null): void
{
    error_clear_last();
    if ($file !== null) {
        $safeResult = \imagegif($image, $file);
    } else {
        $safeResult = \imagegif($image);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagegrabscreen(): \GdImage
{
    error_clear_last();
    $safeResult = \imagegrabscreen();
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $handle
 * @param bool $client_area
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagegrabwindow(int $handle, bool $client_area = false): \GdImage
{
    error_clear_last();
    $safeResult = \imagegrabwindow($handle, $client_area);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param null|resource|string $file
 * @param int $quality
 * @throws ImageException
 *
 */
function imagejpeg(\GdImage $image, $file = null, int $quality = -1): void
{
    error_clear_last();
    if ($quality !== -1) {
        $safeResult = \imagejpeg($image, $file, $quality);
    } elseif ($file !== null) {
        $safeResult = \imagejpeg($image, $file);
    } else {
        $safeResult = \imagejpeg($image);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $effect
 * @throws ImageException
 *
 */
function imagelayereffect(\GdImage $image, int $effect): void
{
    error_clear_last();
    $safeResult = \imagelayereffect($image, $effect);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $x1
 * @param int $y1
 * @param int $x2
 * @param int $y2
 * @param int $color
 * @throws ImageException
 *
 */
function imageline(\GdImage $image, int $x1, int $y1, int $x2, int $y2, int $color): void
{
    error_clear_last();
    $safeResult = \imageline($image, $x1, $y1, $x2, $y2, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @return int
 * @throws ImageException
 *
 */
function imageloadfont(string $filename): int
{
    error_clear_last();
    $safeResult = \imageloadfont($filename);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param null|resource|string $file
 * @param int $quality
 * @param int $filters
 * @throws ImageException
 *
 */
function imagepng(\GdImage $image, $file = null, int $quality = -1, int $filters = -1): void
{
    error_clear_last();
    if ($filters !== -1) {
        $safeResult = \imagepng($image, $file, $quality, $filters);
    } elseif ($quality !== -1) {
        $safeResult = \imagepng($image, $file, $quality);
    } elseif ($file !== null) {
        $safeResult = \imagepng($image, $file);
    } else {
        $safeResult = \imagepng($image);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $x1
 * @param int $y1
 * @param int $x2
 * @param int $y2
 * @param int $color
 * @throws ImageException
 *
 */
function imagerectangle(\GdImage $image, int $x1, int $y1, int $x2, int $y2, int $color): void
{
    error_clear_last();
    $safeResult = \imagerectangle($image, $x1, $y1, $x2, $y2, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int|null $resolution_x
 * @param int|null $resolution_y
 * @return mixed
 * @throws ImageException
 *
 */
function imageresolution(\GdImage $image, ?int $resolution_x = null, ?int $resolution_y = null)
{
    error_clear_last();
    if ($resolution_y !== null) {
        $safeResult = \imageresolution($image, $resolution_x, $resolution_y);
    } elseif ($resolution_x !== null) {
        $safeResult = \imageresolution($image, $resolution_x);
    } else {
        $safeResult = \imageresolution($image);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param float $angle
 * @param int $background_color
 * @param bool $ignore_transparent
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagerotate(\GdImage $image, float $angle, int $background_color, bool $ignore_transparent = false): \GdImage
{
    error_clear_last();
    $safeResult = \imagerotate($image, $angle, $background_color, $ignore_transparent);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param bool $enable
 * @throws ImageException
 *
 */
function imagesavealpha(\GdImage $image, bool $enable): void
{
    error_clear_last();
    $safeResult = \imagesavealpha($image, $enable);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $width
 * @param int $height
 * @param int $mode
 * @return \GdImage
 * @throws ImageException
 *
 */
function imagescale(\GdImage $image, int $width, int $height = -1, int $mode = IMG_BILINEAR_FIXED): \GdImage
{
    error_clear_last();
    $safeResult = \imagescale($image, $width, $height, $mode);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param \GdImage $brush
 * @throws ImageException
 *
 */
function imagesetbrush(\GdImage $image, \GdImage $brush): void
{
    error_clear_last();
    $safeResult = \imagesetbrush($image, $brush);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $x1
 * @param int $y1
 * @param int $x2
 * @param int $y2
 * @throws ImageException
 *
 */
function imagesetclip(\GdImage $image, int $x1, int $y1, int $x2, int $y2): void
{
    error_clear_last();
    $safeResult = \imagesetclip($image, $x1, $y1, $x2, $y2);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $method
 * @throws ImageException
 *
 */
function imagesetinterpolation(\GdImage $image, int $method = IMG_BILINEAR_FIXED): void
{
    error_clear_last();
    $safeResult = \imagesetinterpolation($image, $method);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $x
 * @param int $y
 * @param int $color
 * @throws ImageException
 *
 */
function imagesetpixel(\GdImage $image, int $x, int $y, int $color): void
{
    error_clear_last();
    $safeResult = \imagesetpixel($image, $x, $y, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param array $style
 * @throws ImageException
 *
 */
function imagesetstyle(\GdImage $image, array $style): void
{
    error_clear_last();
    $safeResult = \imagesetstyle($image, $style);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $thickness
 * @throws ImageException
 *
 */
function imagesetthickness(\GdImage $image, int $thickness): void
{
    error_clear_last();
    $safeResult = \imagesetthickness($image, $thickness);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param \GdImage $tile
 * @throws ImageException
 *
 */
function imagesettile(\GdImage $image, \GdImage $tile): void
{
    error_clear_last();
    $safeResult = \imagesettile($image, $tile);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $font
 * @param int $x
 * @param int $y
 * @param string $string
 * @param int $color
 * @throws ImageException
 *
 */
function imagestring(\GdImage $image, int $font, int $x, int $y, string $string, int $color): void
{
    error_clear_last();
    $safeResult = \imagestring($image, $font, $x, $y, $string, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param int $font
 * @param int $x
 * @param int $y
 * @param string $string
 * @param int $color
 * @throws ImageException
 *
 */
function imagestringup(\GdImage $image, int $font, int $x, int $y, string $string, int $color): void
{
    error_clear_last();
    $safeResult = \imagestringup($image, $font, $x, $y, $string, $color);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @return int
 * @throws ImageException
 *
 */
function imagesx(\GdImage $image): int
{
    error_clear_last();
    $safeResult = \imagesx($image);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @return int
 * @throws ImageException
 *
 */
function imagesy(\GdImage $image): int
{
    error_clear_last();
    $safeResult = \imagesy($image);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param bool $dither
 * @param int $num_colors
 * @throws ImageException
 *
 */
function imagetruecolortopalette(\GdImage $image, bool $dither, int $num_colors): void
{
    error_clear_last();
    $safeResult = \imagetruecolortopalette($image, $dither, $num_colors);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param float $size
 * @param float $angle
 * @param string $font_filename
 * @param string $string
 * @param array $options
 * @return array
 * @throws ImageException
 *
 */
function imagettfbbox(float $size, float $angle, string $font_filename, string $string, array $options = []): array
{
    error_clear_last();
    $safeResult = \imagettfbbox($size, $angle, $font_filename, $string, $options);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param float $size
 * @param float $angle
 * @param int $x
 * @param int $y
 * @param int $color
 * @param string $font_filename
 * @param string $text
 * @param array $options
 * @return array
 * @throws ImageException
 *
 */
function imagettftext(\GdImage $image, float $size, float $angle, int $x, int $y, int $color, string $font_filename, string $text, array $options = []): array
{
    error_clear_last();
    $safeResult = \imagettftext($image, $size, $angle, $x, $y, $color, $font_filename, $text, $options);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \GdImage $image
 * @param null|resource|string $file
 * @param int|null $foreground_color
 * @throws ImageException
 *
 */
function imagewbmp(\GdImage $image, $file = null, ?int $foreground_color = null): void
{
    error_clear_last();
    if ($foreground_color !== null) {
        $safeResult = \imagewbmp($image, $file, $foreground_color);
    } elseif ($file !== null) {
        $safeResult = \imagewbmp($image, $file);
    } else {
        $safeResult = \imagewbmp($image);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param null|resource|string $file
 * @param int $quality
 * @throws ImageException
 *
 */
function imagewebp(\GdImage $image, $file = null, int $quality = -1): void
{
    error_clear_last();
    if ($quality !== -1) {
        $safeResult = \imagewebp($image, $file, $quality);
    } elseif ($file !== null) {
        $safeResult = \imagewebp($image, $file);
    } else {
        $safeResult = \imagewebp($image);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param \GdImage $image
 * @param null|resource|string $filename
 * @param int|null $foreground_color
 * @throws ImageException
 *
 */
function imagexbm(\GdImage $image, $filename, ?int $foreground_color = null): void
{
    error_clear_last();
    if ($foreground_color !== null) {
        $safeResult = \imagexbm($image, $filename, $foreground_color);
    } else {
        $safeResult = \imagexbm($image, $filename);
    }
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
}


/**
 * @param string $iptc_data
 * @param string $filename
 * @param int $spool
 * @return bool|string
 * @throws ImageException
 *
 */
function iptcembed(string $iptc_data, string $filename, int $spool = 0)
{
    error_clear_last();
    $safeResult = \iptcembed($iptc_data, $filename, $spool);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $iptc_block
 * @return array
 * @throws ImageException
 *
 */
function iptcparse(string $iptc_block): array
{
    error_clear_last();
    $safeResult = \iptcparse($iptc_block);
    if ($safeResult === false) {
        throw ImageException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\SplException;

/**
 * @param object|string $object_or_class
 * @param bool $autoload
 * @return array
 * @throws SplException
 *
 */
function class_implements($object_or_class, bool $autoload = true): array
{
    error_clear_last();
    $safeResult = \class_implements($object_or_class, $autoload);
    if ($safeResult === false) {
        throw SplException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param object|string $object_or_class
 * @param bool $autoload
 * @return array
 * @throws SplException
 *
 */
function class_parents($object_or_class, bool $autoload = true): array
{
    error_clear_last();
    $safeResult = \class_parents($object_or_class, $autoload);
    if ($safeResult === false) {
        throw SplException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param object|string $object_or_class
 * @param bool $autoload
 * @return array
 * @throws SplException
 *
 */
function class_uses($object_or_class, bool $autoload = true): array
{
    error_clear_last();
    $safeResult = \class_uses($object_or_class, $autoload);
    if ($safeResult === false) {
        throw SplException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param callable(string):void|null $callback
 * @param bool $throw
 * @param bool $prepend
 * @throws SplException
 *
 */
function spl_autoload_register(?callable $callback = null, bool $throw = true, bool $prepend = false): void
{
    error_clear_last();
    if ($prepend !== false) {
        $safeResult = \spl_autoload_register($callback, $throw, $prepend);
    } elseif ($throw !== true) {
        $safeResult = \spl_autoload_register($callback, $throw);
    } elseif ($callback !== null) {
        $safeResult = \spl_autoload_register($callback);
    } else {
        $safeResult = \spl_autoload_register();
    }
    if ($safeResult === false) {
        throw SplException::createFromPhpError();
    }
}


/**
 * @param mixed $callback
 * @throws SplException
 *
 */
function spl_autoload_unregister($callback): void
{
    error_clear_last();
    $safeResult = \spl_autoload_unregister($callback);
    if ($safeResult === false) {
        throw SplException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\ShmopException;

/**
 * @param \Shmop $shmop
 * @throws ShmopException
 *
 */
function shmop_delete(\Shmop $shmop): void
{
    error_clear_last();
    $safeResult = \shmop_delete($shmop);
    if ($safeResult === false) {
        throw ShmopException::createFromPhpError();
    }
}


/**
 * @param \Shmop $shmop
 * @param int $offset
 * @param int $size
 * @return string
 * @throws ShmopException
 *
 */
function shmop_read(\Shmop $shmop, int $offset, int $size): string
{
    error_clear_last();
    $safeResult = \shmop_read($shmop, $offset, $size);
    if ($safeResult === false) {
        throw ShmopException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\SemException;

/**
 * @param int $key
 * @param int $permissions
 * @return \SysvMessageQueue
 * @throws SemException
 *
 */
function msg_get_queue(int $key, int $permissions = 0666): \SysvMessageQueue
{
    error_clear_last();
    $safeResult = \msg_get_queue($key, $permissions);
    if ($safeResult === false) {
        throw SemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $key
 * @throws SemException
 *
 */
function msg_queue_exists(int $key): void
{
    error_clear_last();
    $safeResult = \msg_queue_exists($key);
    if ($safeResult === false) {
        throw SemException::createFromPhpError();
    }
}


/**
 * @param \SysvMessageQueue $queue
 * @param int $desired_message_type
 * @param int|null $received_message_type
 * @param int $max_message_size
 * @param mixed $message
 * @param bool $unserialize
 * @param int $flags
 * @param int|null $error_code
 * @throws SemException
 *
 */
function msg_receive(\SysvMessageQueue $queue, int $desired_message_type, ?int &$received_message_type, int $max_message_size, &$message, bool $unserialize = true, int $flags = 0, ?int &$error_code = null): void
{
    error_clear_last();
    $safeResult = \msg_receive($queue, $desired_message_type, $received_message_type, $max_message_size, $message, $unserialize, $flags, $error_code);
    if ($safeResult === false) {
        throw SemException::createFromPhpError();
    }
}


/**
 * @param \SysvMessageQueue $queue
 * @throws SemException
 *
 */
function msg_remove_queue(\SysvMessageQueue $queue): void
{
    error_clear_last();
    $safeResult = \msg_remove_queue($queue);
    if ($safeResult === false) {
        throw SemException::createFromPhpError();
    }
}


/**
 * @param \SysvMessageQueue $queue
 * @param int $message_type
 * @param mixed $message
 * @param bool $serialize
 * @param bool $blocking
 * @param int|null $error_code
 * @throws SemException
 *
 */
function msg_send(\SysvMessageQueue $queue, int $message_type, $message, bool $serialize = true, bool $blocking = true, ?int &$error_code = null): void
{
    error_clear_last();
    $safeResult = \msg_send($queue, $message_type, $message, $serialize, $blocking, $error_code);
    if ($safeResult === false) {
        throw SemException::createFromPhpError();
    }
}


/**
 * @param \SysvMessageQueue $queue
 * @param array $data
 * @throws SemException
 *
 */
function msg_set_queue(\SysvMessageQueue $queue, array $data): void
{
    error_clear_last();
    $safeResult = \msg_set_queue($queue, $data);
    if ($safeResult === false) {
        throw SemException::createFromPhpError();
    }
}


/**
 * @param \SysvMessageQueue $queue
 * @return array
 * @throws SemException
 *
 */
function msg_stat_queue(\SysvMessageQueue $queue): array
{
    error_clear_last();
    $safeResult = \msg_stat_queue($queue);
    if ($safeResult === false) {
        throw SemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \SysvSemaphore $semaphore
 * @param bool $non_blocking
 * @throws SemException
 *
 */
function sem_acquire(\SysvSemaphore $semaphore, bool $non_blocking = false): void
{
    error_clear_last();
    $safeResult = \sem_acquire($semaphore, $non_blocking);
    if ($safeResult === false) {
        throw SemException::createFromPhpError();
    }
}


/**
 * @param int $key
 * @param int $max_acquire
 * @param int $permissions
 * @param bool $auto_release
 * @return \SysvSemaphore
 * @throws SemException
 *
 */
function sem_get(int $key, int $max_acquire = 1, int $permissions = 0666, bool $auto_release = true): \SysvSemaphore
{
    error_clear_last();
    $safeResult = \sem_get($key, $max_acquire, $permissions, $auto_release);
    if ($safeResult === false) {
        throw SemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \SysvSemaphore $semaphore
 * @throws SemException
 *
 */
function sem_release(\SysvSemaphore $semaphore): void
{
    error_clear_last();
    $safeResult = \sem_release($semaphore);
    if ($safeResult === false) {
        throw SemException::createFromPhpError();
    }
}


/**
 * @param \SysvSemaphore $semaphore
 * @throws SemException
 *
 */
function sem_remove(\SysvSemaphore $semaphore): void
{
    error_clear_last();
    $safeResult = \sem_remove($semaphore);
    if ($safeResult === false) {
        throw SemException::createFromPhpError();
    }
}


/**
 * @param int $key
 * @param int|null $size
 * @param int $permissions
 * @return \SysvSharedMemory
 * @throws SemException
 *
 */
function shm_attach(int $key, ?int $size = null, int $permissions = 0666): \SysvSharedMemory
{
    error_clear_last();
    if ($permissions !== 0666) {
        $safeResult = \shm_attach($key, $size, $permissions);
    } elseif ($size !== null) {
        $safeResult = \shm_attach($key, $size);
    } else {
        $safeResult = \shm_attach($key);
    }
    if ($safeResult === false) {
        throw SemException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \SysvSharedMemory $shm
 * @throws SemException
 *
 */
function shm_detach(\SysvSharedMemory $shm): void
{
    error_clear_last();
    $safeResult = \shm_detach($shm);
    if ($safeResult === false) {
        throw SemException::createFromPhpError();
    }
}


/**
 * @param \SysvSharedMemory $shm
 * @param int $key
 * @param mixed $value
 * @throws SemException
 *
 */
function shm_put_var(\SysvSharedMemory $shm, int $key, $value): void
{
    error_clear_last();
    $safeResult = \shm_put_var($shm, $key, $value);
    if ($safeResult === false) {
        throw SemException::createFromPhpError();
    }
}


/**
 * @param \SysvSharedMemory $shm
 * @param int $key
 * @throws SemException
 *
 */
function shm_remove_var(\SysvSharedMemory $shm, int $key): void
{
    error_clear_last();
    $safeResult = \shm_remove_var($shm, $key);
    if ($safeResult === false) {
        throw SemException::createFromPhpError();
    }
}


/**
 * @param \SysvSharedMemory $shm
 * @throws SemException
 *
 */
function shm_remove(\SysvSharedMemory $shm): void
{
    error_clear_last();
    $safeResult = \shm_remove($shm);
    if ($safeResult === false) {
        throw SemException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\RrdException;

/**
 * @param string $filename
 * @param array $options
 * @throws RrdException
 *
 */
function rrd_create(string $filename, array $options): void
{
    error_clear_last();
    $safeResult = \rrd_create($filename, $options);
    if ($safeResult === false) {
        throw RrdException::createFromPhpError();
    }
}


/**
 * @param string $file
 * @param int $raaindex
 * @return int
 * @throws RrdException
 *
 */
function rrd_first(string $file, int $raaindex = 0): int
{
    error_clear_last();
    $safeResult = \rrd_first($file, $raaindex);
    if ($safeResult === false) {
        throw RrdException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param array $options
 * @return array
 * @throws RrdException
 *
 */
function rrd_graph(string $filename, array $options): array
{
    error_clear_last();
    $safeResult = \rrd_graph($filename, $options);
    if ($safeResult === false) {
        throw RrdException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return array
 * @throws RrdException
 *
 */
function rrd_info(string $filename): array
{
    error_clear_last();
    $safeResult = \rrd_info($filename);
    if ($safeResult === false) {
        throw RrdException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return array
 * @throws RrdException
 *
 */
function rrd_lastupdate(string $filename): array
{
    error_clear_last();
    $safeResult = \rrd_lastupdate($filename);
    if ($safeResult === false) {
        throw RrdException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $xml_file
 * @param string $rrd_file
 * @param array $options
 * @throws RrdException
 *
 */
function rrd_restore(string $xml_file, string $rrd_file, ?array $options = null): void
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \rrd_restore($xml_file, $rrd_file, $options);
    } else {
        $safeResult = \rrd_restore($xml_file, $rrd_file);
    }
    if ($safeResult === false) {
        throw RrdException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @param array $options
 * @throws RrdException
 *
 */
function rrd_tune(string $filename, array $options): void
{
    error_clear_last();
    $safeResult = \rrd_tune($filename, $options);
    if ($safeResult === false) {
        throw RrdException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @param array $options
 * @throws RrdException
 *
 */
function rrd_update(string $filename, array $options): void
{
    error_clear_last();
    $safeResult = \rrd_update($filename, $options);
    if ($safeResult === false) {
        throw RrdException::createFromPhpError();
    }
}


/**
 * @param array $options
 * @return array
 * @throws RrdException
 *
 */
function rrd_xport(array $options): array
{
    error_clear_last();
    $safeResult = \rrd_xport($options);
    if ($safeResult === false) {
        throw RrdException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\ExecException;

/**
 * @param string $command
 * @param array|null $output
 * @param int|null $result_code
 * @return string
 * @throws ExecException
 *
 */
function exec(string $command, ?array &$output = null, ?int &$result_code = null): string
{
    error_clear_last();
    $safeResult = \exec($command, $output, $result_code);
    if ($safeResult === false) {
        throw ExecException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $process
 * @return int
 * @throws ExecException
 *
 */
function proc_close($process): int
{
    error_clear_last();
    $safeResult = \proc_close($process);
    if ($safeResult === -1) {
        throw ExecException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $priority
 * @throws ExecException
 *
 */
function proc_nice(int $priority): void
{
    error_clear_last();
    $safeResult = \proc_nice($priority);
    if ($safeResult === false) {
        throw ExecException::createFromPhpError();
    }
}


/**
 * @param string $cmd
 * @param array $descriptorspec
 * @param null|resource[] $pipes
 * @param null|string $cwd
 * @param array|null $env
 * @param array|null $other_options
 * @return resource
 * @throws ExecException
 *
 */
function proc_open(string $cmd, array $descriptorspec, ?array &$pipes, ?string $cwd = null, ?array $env = null, ?array $other_options = null)
{
    error_clear_last();
    if ($other_options !== null) {
        $safeResult = \proc_open($cmd, $descriptorspec, $pipes, $cwd, $env, $other_options);
    } elseif ($env !== null) {
        $safeResult = \proc_open($cmd, $descriptorspec, $pipes, $cwd, $env);
    } elseif ($cwd !== null) {
        $safeResult = \proc_open($cmd, $descriptorspec, $pipes, $cwd);
    } else {
        $safeResult = \proc_open($cmd, $descriptorspec, $pipes);
    }
    if ($safeResult === false) {
        throw ExecException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $command
 * @return null|string
 * @throws ExecException
 *
 */
function shell_exec(string $command): ?string
{
    error_clear_last();
    $safeResult = \shell_exec($command);
    if ($safeResult === false) {
        throw ExecException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $command
 * @param int|null $result_code
 * @return string
 * @throws ExecException
 *
 */
function system(string $command, ?int &$result_code = null): string
{
    error_clear_last();
    $safeResult = \system($command, $result_code);
    if ($safeResult === false) {
        throw ExecException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\XmlException;

/**
 * @param \XMLParser $parser
 * @throws XmlException
 *
 */
function xml_parser_free(\XMLParser $parser): void
{
    error_clear_last();
    $safeResult = \xml_parser_free($parser);
    if ($safeResult === false) {
        throw XmlException::createFromPhpError();
    }
}


/**
 * @param \XMLParser $parser
 * @param callable $handler
 * @throws XmlException
 *
 */
function xml_set_character_data_handler(\XMLParser $parser, callable $handler): void
{
    error_clear_last();
    $safeResult = \xml_set_character_data_handler($parser, $handler);
    if ($safeResult === false) {
        throw XmlException::createFromPhpError();
    }
}


/**
 * @param \XMLParser $parser
 * @param callable $handler
 * @throws XmlException
 *
 */
function xml_set_default_handler(\XMLParser $parser, callable $handler): void
{
    error_clear_last();
    $safeResult = \xml_set_default_handler($parser, $handler);
    if ($safeResult === false) {
        throw XmlException::createFromPhpError();
    }
}


/**
 * @param \XMLParser $parser
 * @param callable $start_handler
 * @param callable $end_handler
 * @throws XmlException
 *
 */
function xml_set_element_handler(\XMLParser $parser, callable $start_handler, callable $end_handler): void
{
    error_clear_last();
    $safeResult = \xml_set_element_handler($parser, $start_handler, $end_handler);
    if ($safeResult === false) {
        throw XmlException::createFromPhpError();
    }
}


/**
 * @param \XMLParser $parser
 * @param callable $handler
 * @throws XmlException
 *
 */
function xml_set_end_namespace_decl_handler(\XMLParser $parser, callable $handler): void
{
    error_clear_last();
    $safeResult = \xml_set_end_namespace_decl_handler($parser, $handler);
    if ($safeResult === false) {
        throw XmlException::createFromPhpError();
    }
}


/**
 * @param \XMLParser $parser
 * @param callable $handler
 * @throws XmlException
 *
 */
function xml_set_external_entity_ref_handler(\XMLParser $parser, callable $handler): void
{
    error_clear_last();
    $safeResult = \xml_set_external_entity_ref_handler($parser, $handler);
    if ($safeResult === false) {
        throw XmlException::createFromPhpError();
    }
}


/**
 * @param \XMLParser $parser
 * @param callable $handler
 * @throws XmlException
 *
 */
function xml_set_notation_decl_handler(\XMLParser $parser, callable $handler): void
{
    error_clear_last();
    $safeResult = \xml_set_notation_decl_handler($parser, $handler);
    if ($safeResult === false) {
        throw XmlException::createFromPhpError();
    }
}


/**
 * @param \XMLParser $parser
 * @param object $object
 * @throws XmlException
 *
 */
function xml_set_object(\XMLParser $parser, object $object): void
{
    error_clear_last();
    $safeResult = \xml_set_object($parser, $object);
    if ($safeResult === false) {
        throw XmlException::createFromPhpError();
    }
}


/**
 * @param \XMLParser $parser
 * @param callable $handler
 * @throws XmlException
 *
 */
function xml_set_processing_instruction_handler(\XMLParser $parser, callable $handler): void
{
    error_clear_last();
    $safeResult = \xml_set_processing_instruction_handler($parser, $handler);
    if ($safeResult === false) {
        throw XmlException::createFromPhpError();
    }
}


/**
 * @param \XMLParser $parser
 * @param callable $handler
 * @throws XmlException
 *
 */
function xml_set_start_namespace_decl_handler(\XMLParser $parser, callable $handler): void
{
    error_clear_last();
    $safeResult = \xml_set_start_namespace_decl_handler($parser, $handler);
    if ($safeResult === false) {
        throw XmlException::createFromPhpError();
    }
}


/**
 * @param \XMLParser $parser
 * @param callable $handler
 * @throws XmlException
 *
 */
function xml_set_unparsed_entity_decl_handler(\XMLParser $parser, callable $handler): void
{
    error_clear_last();
    $safeResult = \xml_set_unparsed_entity_decl_handler($parser, $handler);
    if ($safeResult === false) {
        throw XmlException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\SsdeepException;

/**
 * @param string $signature1
 * @param string $signature2
 * @return int
 * @throws SsdeepException
 *
 */
function ssdeep_fuzzy_compare(string $signature1, string $signature2): int
{
    error_clear_last();
    $safeResult = \ssdeep_fuzzy_compare($signature1, $signature2);
    if ($safeResult === false) {
        throw SsdeepException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $file_name
 * @return string
 * @throws SsdeepException
 *
 */
function ssdeep_fuzzy_hash_filename(string $file_name): string
{
    error_clear_last();
    $safeResult = \ssdeep_fuzzy_hash_filename($file_name);
    if ($safeResult === false) {
        throw SsdeepException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $to_hash
 * @return string
 * @throws SsdeepException
 *
 */
function ssdeep_fuzzy_hash(string $to_hash): string
{
    error_clear_last();
    $safeResult = \ssdeep_fuzzy_hash($to_hash);
    if ($safeResult === false) {
        throw SsdeepException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\StringsException;

/**
 * @param string $string
 * @return string
 * @throws StringsException
 *
 */
function convert_uudecode(string $string): string
{
    error_clear_last();
    $safeResult = \convert_uudecode($string);
    if ($safeResult === false) {
        throw StringsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $string
 * @return string
 * @throws StringsException
 *
 */
function hex2bin(string $string): string
{
    error_clear_last();
    $safeResult = \hex2bin($string);
    if ($safeResult === false) {
        throw StringsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param bool $binary
 * @return non-falsy-string&lowercase-string
 * @throws StringsException
 *
 */
function md5_file(string $filename, bool $binary = false): string
{
    error_clear_last();
    $safeResult = \md5_file($filename, $binary);
    if ($safeResult === false) {
        throw StringsException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @param bool $binary
 * @return non-falsy-string&lowercase-string
 * @throws StringsException
 *
 */
function sha1_file(string $filename, bool $binary = false): string
{
    error_clear_last();
    $safeResult = \sha1_file($filename, $binary);
    if ($safeResult === false) {
        throw StringsException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\MbstringException;

/**
 * @param int $codepoint
 * @param null|string $encoding
 * @return string
 * @throws MbstringException
 *
 */
function mb_chr(int $codepoint, ?string $encoding = null): string
{
    error_clear_last();
    if ($encoding !== null) {
        $safeResult = \mb_chr($codepoint, $encoding);
    } else {
        $safeResult = \mb_chr($codepoint);
    }
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param array|string $string
 * @param string $to_encoding
 * @param mixed $from_encoding
 * @return array|string
 * @throws MbstringException
 *
 */
function mb_convert_encoding($string, string $to_encoding, $from_encoding = null)
{
    error_clear_last();
    if ($from_encoding !== null) {
        $safeResult = \mb_convert_encoding($string, $to_encoding, $from_encoding);
    } else {
        $safeResult = \mb_convert_encoding($string, $to_encoding);
    }
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $to_encoding
 * @param array|string $from_encoding
 * @param array|object|string $var
 * @param array|object|string $vars
 * @return string
 * @throws MbstringException
 *
 */
function mb_convert_variables(string $to_encoding, $from_encoding, &$var, ...$vars): string
{
    error_clear_last();
    $safeResult = \mb_convert_variables($to_encoding, $from_encoding, $var, ...$vars);
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param non-empty-list|non-falsy-string|null $encoding
 * @return bool|list
 * @throws MbstringException
 *
 */
function mb_detect_order($encoding = null)
{
    error_clear_last();
    if ($encoding !== null) {
        $safeResult = \mb_detect_order($encoding);
    } else {
        $safeResult = \mb_detect_order();
    }
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $encoding
 * @return list
 * @throws MbstringException
 *
 */
function mb_encoding_aliases(string $encoding): array
{
    error_clear_last();
    $safeResult = \mb_encoding_aliases($encoding);
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $pattern
 * @param callable(array):string $callback
 * @param string $string
 * @param null|string $options
 * @return null|string
 * @throws MbstringException
 *
 */
function mb_ereg_replace_callback(string $pattern, callable $callback, string $string, ?string $options = null): ?string
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \mb_ereg_replace_callback($pattern, $callback, $string, $options);
    } else {
        $safeResult = \mb_ereg_replace_callback($pattern, $callback, $string);
    }
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $pattern
 * @param string $replacement
 * @param string $string
 * @param null|string $options
 * @return null|string
 * @throws MbstringException
 *
 */
function mb_ereg_replace(string $pattern, string $replacement, string $string, ?string $options = null): ?string
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \mb_ereg_replace($pattern, $replacement, $string, $options);
    } else {
        $safeResult = \mb_ereg_replace($pattern, $replacement, $string);
    }
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return array
 * @throws MbstringException
 *
 */
function mb_ereg_search_getregs(): array
{
    error_clear_last();
    $safeResult = \mb_ereg_search_getregs();
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $string
 * @param null|string $pattern
 * @param null|string $options
 * @throws MbstringException
 *
 */
function mb_ereg_search_init(string $string, ?string $pattern = null, ?string $options = null): void
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \mb_ereg_search_init($string, $pattern, $options);
    } elseif ($pattern !== null) {
        $safeResult = \mb_ereg_search_init($string, $pattern);
    } else {
        $safeResult = \mb_ereg_search_init($string);
    }
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
}


/**
 * @param null|string $pattern
 * @param null|string $options
 * @return array
 * @throws MbstringException
 *
 */
function mb_ereg_search_regs(?string $pattern = null, ?string $options = null): array
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \mb_ereg_search_regs($pattern, $options);
    } elseif ($pattern !== null) {
        $safeResult = \mb_ereg_search_regs($pattern);
    } else {
        $safeResult = \mb_ereg_search_regs();
    }
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $offset
 * @throws MbstringException
 *
 */
function mb_ereg_search_setpos(int $offset): void
{
    error_clear_last();
    $safeResult = \mb_ereg_search_setpos($offset);
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
}


/**
 * @param string $pattern
 * @param string $replacement
 * @param string $string
 * @param null|string $options
 * @return string
 * @throws MbstringException
 *
 */
function mb_eregi_replace(string $pattern, string $replacement, string $string, ?string $options = null): string
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \mb_eregi_replace($pattern, $replacement, $string, $options);
    } else {
        $safeResult = \mb_eregi_replace($pattern, $replacement, $string);
    }
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $type
 * @return mixed
 * @throws MbstringException
 *
 */
function mb_get_info(string $type = "all")
{
    error_clear_last();
    $safeResult = \mb_get_info($type);
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param null|string $encoding
 * @return bool|string
 * @throws MbstringException
 *
 */
function mb_http_output(?string $encoding = null)
{
    error_clear_last();
    if ($encoding !== null) {
        $safeResult = \mb_http_output($encoding);
    } else {
        $safeResult = \mb_http_output();
    }
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param null|string $encoding
 * @return bool|string
 * @throws MbstringException
 *
 */
function mb_internal_encoding(?string $encoding = null)
{
    error_clear_last();
    if ($encoding !== null) {
        $safeResult = \mb_internal_encoding($encoding);
    } else {
        $safeResult = \mb_internal_encoding();
    }
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $string
 * @param null|string $encoding
 * @return int
 * @throws MbstringException
 *
 */
function mb_ord(string $string, ?string $encoding = null): int
{
    error_clear_last();
    if ($encoding !== null) {
        $safeResult = \mb_ord($string, $encoding);
    } else {
        $safeResult = \mb_ord($string);
    }
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $string
 * @param array|null $result
 * @throws MbstringException
 *
 */
function mb_parse_str(string $string, ?array &$result): void
{
    error_clear_last();
    $safeResult = \mb_parse_str($string, $result);
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
}


/**
 * @param null|string $encoding
 * @return bool|string
 * @throws MbstringException
 *
 */
function mb_regex_encoding(?string $encoding = null)
{
    error_clear_last();
    if ($encoding !== null) {
        $safeResult = \mb_regex_encoding($encoding);
    } else {
        $safeResult = \mb_regex_encoding();
    }
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $to
 * @param string $subject
 * @param string $message
 * @param array|null|string $additional_headers
 * @param null|string $additional_params
 * @throws MbstringException
 *
 */
function mb_send_mail(string $to, string $subject, string $message, $additional_headers = [], ?string $additional_params = null): void
{
    error_clear_last();
    if ($additional_params !== null) {
        $safeResult = \mb_send_mail($to, $subject, $message, $additional_headers, $additional_params);
    } else {
        $safeResult = \mb_send_mail($to, $subject, $message, $additional_headers);
    }
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
}


/**
 * @param string $pattern
 * @param string $string
 * @param int $limit
 * @return list
 * @throws MbstringException
 *
 */
function mb_split(string $pattern, string $string, int $limit = -1): array
{
    error_clear_last();
    $safeResult = \mb_split($pattern, $string, $limit);
    if ($safeResult === false) {
        throw MbstringException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\PcreException;

/**
 * @param string $pattern
 * @param array $array
 * @param int $flags
 * @return array
 * @throws PcreException
 *
 */
function preg_grep(string $pattern, array $array, int $flags = 0): array
{
    error_clear_last();
    $safeResult = \preg_grep($pattern, $array, $flags);
    if ($safeResult === false) {
        throw PcreException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $pattern
 * @param string $subject
 * @param array|null $matches
 * @param int $flags
 * @param int $offset
 * @return 0|positive-int
 * @throws PcreException
 *
 */
function preg_match_all(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int
{
    error_clear_last();
    $safeResult = \preg_match_all($pattern, $subject, $matches, $flags, $offset);
    if ($safeResult === false) {
        throw PcreException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $pattern
 * @param string $subject
 * @param null|string[] $matches
 * @param int $flags
 * @param int $offset
 * @return 0|1
 * @throws PcreException
 *
 */
function preg_match(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int
{
    error_clear_last();
    $safeResult = \preg_match($pattern, $subject, $matches, $flags, $offset);
    if ($safeResult === false) {
        throw PcreException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param array $pattern
 * @param array|string $subject
 * @param int $limit
 * @param int|null $count
 * @param int $flags
 * @return array|string
 * @throws PcreException
 *
 */
function preg_replace_callback_array(array $pattern, $subject, int $limit = -1, ?int &$count = null, int $flags = 0)
{
    error_clear_last();
    $safeResult = \preg_replace_callback_array($pattern, $subject, $limit, $count, $flags);
    if ($safeResult === null) {
        throw PcreException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param array|string $pattern
 * @param callable(array):string $callback
 * @param array|string $subject
 * @param int $limit
 * @param int|null $count
 * @param int $flags
 * @return array|string
 * @throws PcreException
 *
 */
function preg_replace_callback($pattern, callable $callback, $subject, int $limit = -1, ?int &$count = null, int $flags = 0)
{
    error_clear_last();
    $safeResult = \preg_replace_callback($pattern, $callback, $subject, $limit, $count, $flags);
    if ($safeResult === null) {
        throw PcreException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $pattern
 * @param string $subject
 * @param int|null $limit
 * @param int $flags
 * @return list
 * @throws PcreException
 *
 */
function preg_split(string $pattern, string $subject, ?int $limit = -1, int $flags = 0): array
{
    error_clear_last();
    $safeResult = \preg_split($pattern, $subject, $limit, $flags);
    if ($safeResult === false) {
        throw PcreException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\PosixException;

/**
 * @param string $filename
 * @param int $flags
 * @throws PosixException
 *
 */
function posix_access(string $filename, int $flags = 0): void
{
    error_clear_last();
    $safeResult = \posix_access($filename, $flags);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
}


/**
 * @param int $group_id
 * @return array{name: string, passwd: string, gid: int, members: list}
 * @throws PosixException
 *
 */
function posix_getgrgid(int $group_id): array
{
    error_clear_last();
    $safeResult = \posix_getgrgid($group_id);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $name
 * @return array{name: string, passwd: string, gid: int, members: list}
 * @throws PosixException
 *
 */
function posix_getgrnam(string $name): array
{
    error_clear_last();
    $safeResult = \posix_getgrnam($name);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return list
 * @throws PosixException
 *
 */
function posix_getgroups(): array
{
    error_clear_last();
    $safeResult = \posix_getgroups();
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return string
 * @throws PosixException
 *
 */
function posix_getlogin(): string
{
    error_clear_last();
    $safeResult = \posix_getlogin();
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $user_id
 * @return array{name: string, passwd: string, uid: int, gid: int, gecos: string, dir: string, shell: string}
 * @throws PosixException
 *
 */
function posix_getpwuid(int $user_id): array
{
    error_clear_last();
    $safeResult = \posix_getpwuid($user_id);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return array
 * @throws PosixException
 *
 */
function posix_getrlimit(): array
{
    error_clear_last();
    $safeResult = \posix_getrlimit();
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $process_id
 * @return int
 * @throws PosixException
 *
 */
function posix_getsid(int $process_id): int
{
    error_clear_last();
    $safeResult = \posix_getsid($process_id);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $username
 * @param int $group_id
 * @throws PosixException
 *
 */
function posix_initgroups(string $username, int $group_id): void
{
    error_clear_last();
    $safeResult = \posix_initgroups($username, $group_id);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
}


/**
 * @param int $process_id
 * @param int $signal
 * @throws PosixException
 *
 */
function posix_kill(int $process_id, int $signal): void
{
    error_clear_last();
    $safeResult = \posix_kill($process_id, $signal);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @param int $permissions
 * @throws PosixException
 *
 */
function posix_mkfifo(string $filename, int $permissions): void
{
    error_clear_last();
    $safeResult = \posix_mkfifo($filename, $permissions);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @param int $flags
 * @param int $major
 * @param int $minor
 * @throws PosixException
 *
 */
function posix_mknod(string $filename, int $flags, int $major = 0, int $minor = 0): void
{
    error_clear_last();
    $safeResult = \posix_mknod($filename, $flags, $major, $minor);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
}


/**
 * @param int $group_id
 * @throws PosixException
 *
 */
function posix_setegid(int $group_id): void
{
    error_clear_last();
    $safeResult = \posix_setegid($group_id);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
}


/**
 * @param int $user_id
 * @throws PosixException
 *
 */
function posix_seteuid(int $user_id): void
{
    error_clear_last();
    $safeResult = \posix_seteuid($user_id);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
}


/**
 * @param int $group_id
 * @throws PosixException
 *
 */
function posix_setgid(int $group_id): void
{
    error_clear_last();
    $safeResult = \posix_setgid($group_id);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
}


/**
 * @param int $process_id
 * @param int $process_group_id
 * @throws PosixException
 *
 */
function posix_setpgid(int $process_id, int $process_group_id): void
{
    error_clear_last();
    $safeResult = \posix_setpgid($process_id, $process_group_id);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
}


/**
 * @param int $resource
 * @param int $soft_limit
 * @param int $hard_limit
 * @throws PosixException
 *
 */
function posix_setrlimit(int $resource, int $soft_limit, int $hard_limit): void
{
    error_clear_last();
    $safeResult = \posix_setrlimit($resource, $soft_limit, $hard_limit);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
}


/**
 * @return int
 * @throws PosixException
 *
 */
function posix_setsid(): int
{
    error_clear_last();
    $safeResult = \posix_setsid();
    if ($safeResult === -1) {
        throw PosixException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param int $user_id
 * @throws PosixException
 *
 */
function posix_setuid(int $user_id): void
{
    error_clear_last();
    $safeResult = \posix_setuid($user_id);
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
}


/**
 * @return array
 * @throws PosixException
 *
 */
function posix_times(): array
{
    error_clear_last();
    $safeResult = \posix_times();
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return array
 * @throws PosixException
 *
 */
function posix_uname(): array
{
    error_clear_last();
    $safeResult = \posix_uname();
    if ($safeResult === false) {
        throw PosixException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\CubridException;

/**
 * @param resource $req_identifier
 * @param int $bind_index
 * @param mixed $bind_value
 * @param string $bind_value_type
 * @throws CubridException
 *
 */
function cubrid_bind($req_identifier, int $bind_index, $bind_value, ?string $bind_value_type = null): void
{
    error_clear_last();
    if ($bind_value_type !== null) {
        $safeResult = \cubrid_bind($req_identifier, $bind_index, $bind_value, $bind_value_type);
    } else {
        $safeResult = \cubrid_bind($req_identifier, $bind_index, $bind_value);
    }
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
}


/**
 * @param resource $conn_identifier
 * @param string $oid
 * @param string $attr_name
 * @return int
 * @throws CubridException
 *
 */
function cubrid_col_size($conn_identifier, string $oid, string $attr_name): int
{
    error_clear_last();
    $safeResult = \cubrid_col_size($conn_identifier, $oid, $attr_name);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $req_identifier
 * @return array
 * @throws CubridException
 *
 */
function cubrid_column_names($req_identifier): array
{
    error_clear_last();
    $safeResult = \cubrid_column_names($req_identifier);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $req_identifier
 * @return array
 * @throws CubridException
 *
 */
function cubrid_column_types($req_identifier): array
{
    error_clear_last();
    $safeResult = \cubrid_column_types($req_identifier);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $conn_identifier
 * @throws CubridException
 *
 */
function cubrid_commit($conn_identifier): void
{
    error_clear_last();
    $safeResult = \cubrid_commit($conn_identifier);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
}


/**
 * @param string $conn_url
 * @param string $userid
 * @param string $passwd
 * @param bool $new_link
 * @return resource
 * @throws CubridException
 *
 */
function cubrid_connect_with_url(string $conn_url, ?string $userid = null, ?string $passwd = null, bool $new_link = false)
{
    error_clear_last();
    if ($new_link !== false) {
        $safeResult = \cubrid_connect_with_url($conn_url, $userid, $passwd, $new_link);
    } elseif ($passwd !== null) {
        $safeResult = \cubrid_connect_with_url($conn_url, $userid, $passwd);
    } elseif ($userid !== null) {
        $safeResult = \cubrid_connect_with_url($conn_url, $userid);
    } else {
        $safeResult = \cubrid_connect_with_url($conn_url);
    }
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $host
 * @param int $port
 * @param string $dbname
 * @param string $userid
 * @param string $passwd
 * @param bool $new_link
 * @return resource
 * @throws CubridException
 *
 */
function cubrid_connect(string $host, int $port, string $dbname, ?string $userid = null, ?string $passwd = null, bool $new_link = false)
{
    error_clear_last();
    if ($new_link !== false) {
        $safeResult = \cubrid_connect($host, $port, $dbname, $userid, $passwd, $new_link);
    } elseif ($passwd !== null) {
        $safeResult = \cubrid_connect($host, $port, $dbname, $userid, $passwd);
    } elseif ($userid !== null) {
        $safeResult = \cubrid_connect($host, $port, $dbname, $userid);
    } else {
        $safeResult = \cubrid_connect($host, $port, $dbname);
    }
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $req_identifier
 * @return string
 * @throws CubridException
 *
 */
function cubrid_current_oid($req_identifier): string
{
    error_clear_last();
    $safeResult = \cubrid_current_oid($req_identifier);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $conn_identifier
 * @throws CubridException
 *
 */
function cubrid_disconnect($conn_identifier = null): void
{
    error_clear_last();
    if ($conn_identifier !== null) {
        $safeResult = \cubrid_disconnect($conn_identifier);
    } else {
        $safeResult = \cubrid_disconnect();
    }
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
}


/**
 * @param resource $conn_identifier
 * @param string $oid
 * @throws CubridException
 *
 */
function cubrid_drop($conn_identifier, string $oid): void
{
    error_clear_last();
    $safeResult = \cubrid_drop($conn_identifier, $oid);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
}


/**
 * @param resource $req_identifier
 * @throws CubridException
 *
 */
function cubrid_free_result($req_identifier): void
{
    error_clear_last();
    $safeResult = \cubrid_free_result($req_identifier);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
}


/**
 * @param resource $conn_identifier
 * @return string
 * @throws CubridException
 *
 */
function cubrid_get_charset($conn_identifier): string
{
    error_clear_last();
    $safeResult = \cubrid_get_charset($conn_identifier);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $conn_identifier
 * @param string $oid
 * @return string
 * @throws CubridException
 *
 */
function cubrid_get_class_name($conn_identifier, string $oid): string
{
    error_clear_last();
    $safeResult = \cubrid_get_class_name($conn_identifier, $oid);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return string
 * @throws CubridException
 *
 */
function cubrid_get_client_info(): string
{
    error_clear_last();
    $safeResult = \cubrid_get_client_info();
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $conn_identifier
 * @return array
 * @throws CubridException
 *
 */
function cubrid_get_db_parameter($conn_identifier): array
{
    error_clear_last();
    $safeResult = \cubrid_get_db_parameter($conn_identifier);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $req_identifier
 * @return int
 * @throws CubridException
 *
 */
function cubrid_get_query_timeout($req_identifier): int
{
    error_clear_last();
    $safeResult = \cubrid_get_query_timeout($req_identifier);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $conn_identifier
 * @return string
 * @throws CubridException
 *
 */
function cubrid_get_server_info($conn_identifier): string
{
    error_clear_last();
    $safeResult = \cubrid_get_server_info($conn_identifier);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $conn_identifier
 * @return string
 * @throws CubridException
 *
 */
function cubrid_insert_id($conn_identifier = null): string
{
    error_clear_last();
    if ($conn_identifier !== null) {
        $safeResult = \cubrid_insert_id($conn_identifier);
    } else {
        $safeResult = \cubrid_insert_id();
    }
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param array $lob_identifier_array
 * @throws CubridException
 *
 */
function cubrid_lob_close(array $lob_identifier_array): void
{
    error_clear_last();
    $safeResult = \cubrid_lob_close($lob_identifier_array);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
}


/**
 * @param resource $conn_identifier
 * @param resource $lob_identifier
 * @param string $path_name
 * @throws CubridException
 *
 */
function cubrid_lob_export($conn_identifier, $lob_identifier, string $path_name): void
{
    error_clear_last();
    $safeResult = \cubrid_lob_export($conn_identifier, $lob_identifier, $path_name);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
}


/**
 * @param resource $conn_identifier
 * @param string $sql
 * @return array
 * @throws CubridException
 *
 */
function cubrid_lob_get($conn_identifier, string $sql): array
{
    error_clear_last();
    $safeResult = \cubrid_lob_get($conn_identifier, $sql);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $conn_identifier
 * @param resource $lob_identifier
 * @throws CubridException
 *
 */
function cubrid_lob_send($conn_identifier, $lob_identifier): void
{
    error_clear_last();
    $safeResult = \cubrid_lob_send($conn_identifier, $lob_identifier);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
}


/**
 * @param resource $lob_identifier
 * @return string
 * @throws CubridException
 *
 */
function cubrid_lob_size($lob_identifier): string
{
    error_clear_last();
    $safeResult = \cubrid_lob_size($lob_identifier);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $req_identifier
 * @param int $bind_index
 * @param mixed $bind_value
 * @param string $bind_value_type
 * @throws CubridException
 *
 */
function cubrid_lob2_bind($req_identifier, int $bind_index, $bind_value, ?string $bind_value_type = null): void
{
    error_clear_last();
    if ($bind_value_type !== null) {
        $safeResult = \cubrid_lob2_bind($req_identifier, $bind_index, $bind_value, $bind_value_type);
    } else {
        $safeResult = \cubrid_lob2_bind($req_identifier, $bind_index, $bind_value);
    }
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
}


/**
 * @param resource $lob_identifier
 * @throws CubridException
 *
 */
function cubrid_lob2_close($lob_identifier): void
{
    error_clear_last();
    $safeResult = \cubrid_lob2_close($lob_identifier);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
}


/**
 * @param resource $lob_identifier
 * @param string $file_name
 * @throws CubridException
 *
 */
function cubrid_lob2_export($lob_identifier, string $file_name): void
{
    error_clear_last();
    $safeResult = \cubrid_lob2_export($lob_identifier, $file_name);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
}


/**
 * @param resource $lob_identifier
 * @param string $file_name
 * @throws CubridException
 *
 */
function cubrid_lob2_import($lob_identifier, string $file_name): void
{
    error_clear_last();
    $safeResult = \cubrid_lob2_import($lob_identifier, $file_name);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
}


/**
 * @param resource $conn_identifier
 * @param string $type
 * @return resource
 * @throws CubridException
 *
 */
function cubrid_lob2_new($conn_identifier = null, string $type = "BLOB")
{
    error_clear_last();
    if ($type !== "BLOB") {
        $safeResult = \cubrid_lob2_new($conn_identifier, $type);
    } elseif ($conn_identifier !== null) {
        $safeResult = \cubrid_lob2_new($conn_identifier);
    } else {
        $safeResult = \cubrid_lob2_new();
    }
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $lob_identifier
 * @param int $len
 * @return string
 * @throws CubridException
 *
 */
function cubrid_lob2_read($lob_identifier, int $len): string
{
    error_clear_last();
    $safeResult = \cubrid_lob2_read($lob_identifier, $len);
    if ($safeResult === null) {
        throw CubridException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $lob_identifier
 * @param int $offset
 * @param int $origin
 * @throws CubridException
 *
 */
function cubrid_lob2_seek($lob_identifier, int $offset, int $origin = CUBRID_CURSOR_CURRENT): void
{
    error_clear_last();
    $safeResult = \cubrid_lob2_seek($lob_identifier, $offset, $origin);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
}


/**
 * @param resource $lob_identifier
 * @param string $offset
 * @param int $origin
 * @throws CubridException
 *
 */
function cubrid_lob2_seek64($lob_identifier, string $offset, int $origin = CUBRID_CURSOR_CURRENT): void
{
    error_clear_last();
    $safeResult = \cubrid_lob2_seek64($lob_identifier, $offset, $origin);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
}


/**
 * @param resource $lob_identifier
 * @return int
 * @throws CubridException
 *
 */
function cubrid_lob2_size($lob_identifier): int
{
    error_clear_last();
    $safeResult = \cubrid_lob2_size($lob_identifier);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $lob_identifier
 * @return string
 * @throws CubridException
 *
 */
function cubrid_lob2_size64($lob_identifier): string
{
    error_clear_last();
    $safeResult = \cubrid_lob2_size64($lob_identifier);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $lob_identifier
 * @return int
 * @throws CubridException
 *
 */
function cubrid_lob2_tell($lob_identifier): int
{
    error_clear_last();
    $safeResult = \cubrid_lob2_tell($lob_identifier);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $lob_identifier
 * @return string
 * @throws CubridException
 *
 */
function cubrid_lob2_tell64($lob_identifier): string
{
    error_clear_last();
    $safeResult = \cubrid_lob2_tell64($lob_identifier);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $lob_identifier
 * @param string $buf
 * @throws CubridException
 *
 */
function cubrid_lob2_write($lob_identifier, string $buf): void
{
    error_clear_last();
    $safeResult = \cubrid_lob2_write($lob_identifier, $buf);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
}


/**
 * @param resource $conn_identifier
 * @param string $oid
 * @throws CubridException
 *
 */
function cubrid_lock_read($conn_identifier, string $oid): void
{
    error_clear_last();
    $safeResult = \cubrid_lock_read($conn_identifier, $oid);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
}


/**
 * @param resource $conn_identifier
 * @param string $oid
 * @throws CubridException
 *
 */
function cubrid_lock_write($conn_identifier, string $oid): void
{
    error_clear_last();
    $safeResult = \cubrid_lock_write($conn_identifier, $oid);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
}


/**
 * @param resource $req_identifier
 * @param int $offset
 * @param int $origin
 * @return int
 * @throws CubridException
 *
 */
function cubrid_move_cursor($req_identifier, int $offset, int $origin = CUBRID_CURSOR_CURRENT): int
{
    error_clear_last();
    $safeResult = \cubrid_move_cursor($req_identifier, $offset, $origin);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $result
 * @throws CubridException
 *
 */
function cubrid_next_result($result): void
{
    error_clear_last();
    $safeResult = \cubrid_next_result($result);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
}


/**
 * @param string $conn_url
 * @param string $userid
 * @param string $passwd
 * @return resource
 * @throws CubridException
 *
 */
function cubrid_pconnect_with_url(string $conn_url, ?string $userid = null, ?string $passwd = null)
{
    error_clear_last();
    if ($passwd !== null) {
        $safeResult = \cubrid_pconnect_with_url($conn_url, $userid, $passwd);
    } elseif ($userid !== null) {
        $safeResult = \cubrid_pconnect_with_url($conn_url, $userid);
    } else {
        $safeResult = \cubrid_pconnect_with_url($conn_url);
    }
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $host
 * @param int $port
 * @param string $dbname
 * @param string $userid
 * @param string $passwd
 * @return resource
 * @throws CubridException
 *
 */
function cubrid_pconnect(string $host, int $port, string $dbname, ?string $userid = null, ?string $passwd = null)
{
    error_clear_last();
    if ($passwd !== null) {
        $safeResult = \cubrid_pconnect($host, $port, $dbname, $userid, $passwd);
    } elseif ($userid !== null) {
        $safeResult = \cubrid_pconnect($host, $port, $dbname, $userid);
    } else {
        $safeResult = \cubrid_pconnect($host, $port, $dbname);
    }
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $conn_identifier
 * @param string $prepare_stmt
 * @param int $option
 * @return resource
 * @throws CubridException
 *
 */
function cubrid_prepare($conn_identifier, string $prepare_stmt, int $option = 0)
{
    error_clear_last();
    $safeResult = \cubrid_prepare($conn_identifier, $prepare_stmt, $option);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $conn_identifier
 * @param string $oid
 * @param string $attr
 * @param mixed $value
 * @throws CubridException
 *
 */
function cubrid_put($conn_identifier, string $oid, ?string $attr = null, $value = null): void
{
    error_clear_last();
    if ($value !== null) {
        $safeResult = \cubrid_put($conn_identifier, $oid, $attr, $value);
    } elseif ($attr !== null) {
        $safeResult = \cubrid_put($conn_identifier, $oid, $attr);
    } else {
        $safeResult = \cubrid_put($conn_identifier, $oid);
    }
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
}


/**
 * @param resource $conn_identifier
 * @throws CubridException
 *
 */
function cubrid_rollback($conn_identifier): void
{
    error_clear_last();
    $safeResult = \cubrid_rollback($conn_identifier);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
}


/**
 * @param resource $conn_identifier
 * @param int $schema_type
 * @param string $class_name
 * @param string $attr_name
 * @return array
 * @throws CubridException
 *
 */
function cubrid_schema($conn_identifier, int $schema_type, ?string $class_name = null, ?string $attr_name = null): array
{
    error_clear_last();
    if ($attr_name !== null) {
        $safeResult = \cubrid_schema($conn_identifier, $schema_type, $class_name, $attr_name);
    } elseif ($class_name !== null) {
        $safeResult = \cubrid_schema($conn_identifier, $schema_type, $class_name);
    } else {
        $safeResult = \cubrid_schema($conn_identifier, $schema_type);
    }
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $conn_identifier
 * @param string $oid
 * @param string $attr_name
 * @param int $index
 * @throws CubridException
 *
 */
function cubrid_seq_drop($conn_identifier, string $oid, string $attr_name, int $index): void
{
    error_clear_last();
    $safeResult = \cubrid_seq_drop($conn_identifier, $oid, $attr_name, $index);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
}


/**
 * @param resource $conn_identifier
 * @param string $oid
 * @param string $attr_name
 * @param int $index
 * @param string $seq_element
 * @throws CubridException
 *
 */
function cubrid_seq_insert($conn_identifier, string $oid, string $attr_name, int $index, string $seq_element): void
{
    error_clear_last();
    $safeResult = \cubrid_seq_insert($conn_identifier, $oid, $attr_name, $index, $seq_element);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
}


/**
 * @param resource $conn_identifier
 * @param string $oid
 * @param string $attr_name
 * @param int $index
 * @param string $seq_element
 * @throws CubridException
 *
 */
function cubrid_seq_put($conn_identifier, string $oid, string $attr_name, int $index, string $seq_element): void
{
    error_clear_last();
    $safeResult = \cubrid_seq_put($conn_identifier, $oid, $attr_name, $index, $seq_element);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
}


/**
 * @param resource $conn_identifier
 * @param string $oid
 * @param string $attr_name
 * @param string $set_element
 * @throws CubridException
 *
 */
function cubrid_set_add($conn_identifier, string $oid, string $attr_name, string $set_element): void
{
    error_clear_last();
    $safeResult = \cubrid_set_add($conn_identifier, $oid, $attr_name, $set_element);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
}


/**
 * @param resource $conn_identifier
 * @param bool $mode
 * @throws CubridException
 *
 */
function cubrid_set_autocommit($conn_identifier, bool $mode): void
{
    error_clear_last();
    $safeResult = \cubrid_set_autocommit($conn_identifier, $mode);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
}


/**
 * @param resource $conn_identifier
 * @param int $param_type
 * @param int $param_value
 * @throws CubridException
 *
 */
function cubrid_set_db_parameter($conn_identifier, int $param_type, int $param_value): void
{
    error_clear_last();
    $safeResult = \cubrid_set_db_parameter($conn_identifier, $param_type, $param_value);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
}


/**
 * @param resource $conn_identifier
 * @param string $oid
 * @param string $attr_name
 * @param string $set_element
 * @throws CubridException
 *
 */
function cubrid_set_drop($conn_identifier, string $oid, string $attr_name, string $set_element): void
{
    error_clear_last();
    $safeResult = \cubrid_set_drop($conn_identifier, $oid, $attr_name, $set_element);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
}


/**
 * @param resource $req_identifier
 * @param int $timeout
 * @throws CubridException
 *
 */
function cubrid_set_query_timeout($req_identifier, int $timeout): void
{
    error_clear_last();
    $safeResult = \cubrid_set_query_timeout($req_identifier, $timeout);
    if ($safeResult === false) {
        throw CubridException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\ReadlineException;

/**
 * @param string $prompt
 * @throws ReadlineException
 *
 */
function readline_add_history(string $prompt): void
{
    error_clear_last();
    $safeResult = \readline_add_history($prompt);
    if ($safeResult === false) {
        throw ReadlineException::createFromPhpError();
    }
}


/**
 * @param string $prompt
 * @param callable $callback
 * @throws ReadlineException
 *
 */
function readline_callback_handler_install(string $prompt, callable $callback): void
{
    error_clear_last();
    $safeResult = \readline_callback_handler_install($prompt, $callback);
    if ($safeResult === false) {
        throw ReadlineException::createFromPhpError();
    }
}


/**
 * @throws ReadlineException
 *
 */
function readline_clear_history(): void
{
    error_clear_last();
    $safeResult = \readline_clear_history();
    if ($safeResult === false) {
        throw ReadlineException::createFromPhpError();
    }
}


/**
 * @param callable $callback
 * @throws ReadlineException
 *
 */
function readline_completion_function(callable $callback): void
{
    error_clear_last();
    $safeResult = \readline_completion_function($callback);
    if ($safeResult === false) {
        throw ReadlineException::createFromPhpError();
    }
}


/**
 * @param null|string $filename
 * @throws ReadlineException
 *
 */
function readline_read_history(?string $filename = null): void
{
    error_clear_last();
    if ($filename !== null) {
        $safeResult = \readline_read_history($filename);
    } else {
        $safeResult = \readline_read_history();
    }
    if ($safeResult === false) {
        throw ReadlineException::createFromPhpError();
    }
}


/**
 * @param null|string $filename
 * @throws ReadlineException
 *
 */
function readline_write_history(?string $filename = null): void
{
    error_clear_last();
    if ($filename !== null) {
        $safeResult = \readline_write_history($filename);
    } else {
        $safeResult = \readline_write_history();
    }
    if ($safeResult === false) {
        throw ReadlineException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\IbmDb2Exception;

/**
 * @param resource $connection
 * @param \DB2_AUTOCOMMIT_OFF|\DB2_AUTOCOMMIT_ON $value
 * @return \DB2_AUTOCOMMIT_OFF|\DB2_AUTOCOMMIT_ON|bool
 * @throws IbmDb2Exception
 *
 */
function db2_autocommit($connection, $value = null)
{
    error_clear_last();
    if ($value !== null) {
        $safeResult = \db2_autocommit($connection, $value);
    } else {
        $safeResult = \db2_autocommit($connection);
    }
    if ($safeResult === false) {
        throw IbmDb2Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stmt
 * @param int $parameter_number
 * @param string $variable_name
 * @param int $parameter_type
 * @param int $data_type
 * @param int $precision
 * @param int $scale
 * @throws IbmDb2Exception
 *
 */
function db2_bind_param($stmt, int $parameter_number, string $variable_name, ?int $parameter_type = null, int $data_type = 0, int $precision = -1, int $scale = 0): void
{
    error_clear_last();
    if ($scale !== 0) {
        $safeResult = \db2_bind_param($stmt, $parameter_number, $variable_name, $parameter_type, $data_type, $precision, $scale);
    } elseif ($precision !== -1) {
        $safeResult = \db2_bind_param($stmt, $parameter_number, $variable_name, $parameter_type, $data_type, $precision);
    } elseif ($data_type !== 0) {
        $safeResult = \db2_bind_param($stmt, $parameter_number, $variable_name, $parameter_type, $data_type);
    } elseif ($parameter_type !== null) {
        $safeResult = \db2_bind_param($stmt, $parameter_number, $variable_name, $parameter_type);
    } else {
        $safeResult = \db2_bind_param($stmt, $parameter_number, $variable_name);
    }
    if ($safeResult === false) {
        throw IbmDb2Exception::createFromPhpError();
    }
}


/**
 * @param resource $connection
 * @return \stdClass
 * @throws IbmDb2Exception
 *
 */
function db2_client_info($connection): \stdClass
{
    error_clear_last();
    $safeResult = \db2_client_info($connection);
    if ($safeResult === false) {
        throw IbmDb2Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $connection
 * @throws IbmDb2Exception
 *
 */
function db2_close($connection): void
{
    error_clear_last();
    $safeResult = \db2_close($connection);
    if ($safeResult === false) {
        throw IbmDb2Exception::createFromPhpError();
    }
}


/**
 * @param resource $connection
 * @throws IbmDb2Exception
 *
 */
function db2_commit($connection): void
{
    error_clear_last();
    $safeResult = \db2_commit($connection);
    if ($safeResult === false) {
        throw IbmDb2Exception::createFromPhpError();
    }
}


/**
 * @param resource $stmt
 * @param array $parameters
 * @throws IbmDb2Exception
 *
 */
function db2_execute($stmt, ?array $parameters = null): void
{
    error_clear_last();
    if ($parameters !== null) {
        $safeResult = \db2_execute($stmt, $parameters);
    } else {
        $safeResult = \db2_execute($stmt);
    }
    if ($safeResult === false) {
        throw IbmDb2Exception::createFromPhpError();
    }
}


/**
 * @param resource $stmt
 * @throws IbmDb2Exception
 *
 */
function db2_free_result($stmt): void
{
    error_clear_last();
    $safeResult = \db2_free_result($stmt);
    if ($safeResult === false) {
        throw IbmDb2Exception::createFromPhpError();
    }
}


/**
 * @param resource $stmt
 * @throws IbmDb2Exception
 *
 */
function db2_free_stmt($stmt): void
{
    error_clear_last();
    $safeResult = \db2_free_stmt($stmt);
    if ($safeResult === false) {
        throw IbmDb2Exception::createFromPhpError();
    }
}


/**
 * @param resource $resource
 * @param string $option
 * @return string
 * @throws IbmDb2Exception
 *
 */
function db2_get_option($resource, string $option): string
{
    error_clear_last();
    $safeResult = \db2_get_option($resource, $option);
    if ($safeResult === false) {
        throw IbmDb2Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $resource
 * @throws IbmDb2Exception
 *
 */
function db2_pclose($resource): void
{
    error_clear_last();
    $safeResult = \db2_pclose($resource);
    if ($safeResult === false) {
        throw IbmDb2Exception::createFromPhpError();
    }
}


/**
 * @param resource $connection
 * @throws IbmDb2Exception
 *
 */
function db2_rollback($connection): void
{
    error_clear_last();
    $safeResult = \db2_rollback($connection);
    if ($safeResult === false) {
        throw IbmDb2Exception::createFromPhpError();
    }
}


/**
 * @param resource $connection
 * @return \stdClass
 * @throws IbmDb2Exception
 *
 */
function db2_server_info($connection): \stdClass
{
    error_clear_last();
    $safeResult = \db2_server_info($connection);
    if ($safeResult === false) {
        throw IbmDb2Exception::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $resource
 * @param array $options
 * @param int $type
 * @throws IbmDb2Exception
 *
 */
function db2_set_option($resource, array $options, int $type): void
{
    error_clear_last();
    $safeResult = \db2_set_option($resource, $options, $type);
    if ($safeResult === false) {
        throw IbmDb2Exception::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\IbaseException;

/**
 * @param resource $blob_handle
 * @throws IbaseException
 *
 */
function fbird_blob_cancel($blob_handle): void
{
    error_clear_last();
    $safeResult = \fbird_blob_cancel($blob_handle);
    if ($safeResult === false) {
        throw IbaseException::createFromPhpError();
    }
}


/**
 * @param resource $service_handle
 * @param string $user_name
 * @param string $password
 * @param string $first_name
 * @param string $middle_name
 * @param string $last_name
 * @throws IbaseException
 *
 */
function ibase_add_user($service_handle, string $user_name, string $password, ?string $first_name = null, ?string $middle_name = null, ?string $last_name = null): void
{
    error_clear_last();
    if ($last_name !== null) {
        $safeResult = \ibase_add_user($service_handle, $user_name, $password, $first_name, $middle_name, $last_name);
    } elseif ($middle_name !== null) {
        $safeResult = \ibase_add_user($service_handle, $user_name, $password, $first_name, $middle_name);
    } elseif ($first_name !== null) {
        $safeResult = \ibase_add_user($service_handle, $user_name, $password, $first_name);
    } else {
        $safeResult = \ibase_add_user($service_handle, $user_name, $password);
    }
    if ($safeResult === false) {
        throw IbaseException::createFromPhpError();
    }
}


/**
 * @param resource $service_handle
 * @param string $source_db
 * @param string $dest_file
 * @param int $options
 * @param bool $verbose
 * @return mixed
 * @throws IbaseException
 *
 */
function ibase_backup($service_handle, string $source_db, string $dest_file, int $options = 0, bool $verbose = false)
{
    error_clear_last();
    $safeResult = \ibase_backup($service_handle, $source_db, $dest_file, $options, $verbose);
    if ($safeResult === false) {
        throw IbaseException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $blob_handle
 * @throws IbaseException
 *
 */
function ibase_blob_cancel($blob_handle): void
{
    error_clear_last();
    $safeResult = \ibase_blob_cancel($blob_handle);
    if ($safeResult === false) {
        throw IbaseException::createFromPhpError();
    }
}


/**
 * @param null|resource $link_identifier
 * @return resource
 * @throws IbaseException
 *
 */
function ibase_blob_create($link_identifier = null)
{
    error_clear_last();
    if ($link_identifier !== null) {
        $safeResult = \ibase_blob_create($link_identifier);
    } else {
        $safeResult = \ibase_blob_create();
    }
    if ($safeResult === false) {
        throw IbaseException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $blob_handle
 * @param int $len
 * @return string
 * @throws IbaseException
 *
 */
function ibase_blob_get($blob_handle, int $len): string
{
    error_clear_last();
    $safeResult = \ibase_blob_get($blob_handle, $len);
    if ($safeResult === false) {
        throw IbaseException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param null|resource $connection_id
 * @throws IbaseException
 *
 */
function ibase_close($connection_id = null): void
{
    error_clear_last();
    if ($connection_id !== null) {
        $safeResult = \ibase_close($connection_id);
    } else {
        $safeResult = \ibase_close();
    }
    if ($safeResult === false) {
        throw IbaseException::createFromPhpError();
    }
}


/**
 * @param null|resource $link_or_trans_identifier
 * @throws IbaseException
 *
 */
function ibase_commit_ret($link_or_trans_identifier = null): void
{
    error_clear_last();
    if ($link_or_trans_identifier !== null) {
        $safeResult = \ibase_commit_ret($link_or_trans_identifier);
    } else {
        $safeResult = \ibase_commit_ret();
    }
    if ($safeResult === false) {
        throw IbaseException::createFromPhpError();
    }
}


/**
 * @param null|resource $link_or_trans_identifier
 * @throws IbaseException
 *
 */
function ibase_commit($link_or_trans_identifier = null): void
{
    error_clear_last();
    if ($link_or_trans_identifier !== null) {
        $safeResult = \ibase_commit($link_or_trans_identifier);
    } else {
        $safeResult = \ibase_commit();
    }
    if ($safeResult === false) {
        throw IbaseException::createFromPhpError();
    }
}


/**
 * @param string $database
 * @param string $username
 * @param string $password
 * @param string $charset
 * @param int $buffers
 * @param int $dialect
 * @param string $role
 * @param int $sync
 * @return resource
 * @throws IbaseException
 *
 */
function ibase_connect(?string $database = null, ?string $username = null, ?string $password = null, ?string $charset = null, ?int $buffers = null, ?int $dialect = null, ?string $role = null, ?int $sync = null)
{
    error_clear_last();
    if ($sync !== null) {
        $safeResult = \ibase_connect($database, $username, $password, $charset, $buffers, $dialect, $role, $sync);
    } elseif ($role !== null) {
        $safeResult = \ibase_connect($database, $username, $password, $charset, $buffers, $dialect, $role);
    } elseif ($dialect !== null) {
        $safeResult = \ibase_connect($database, $username, $password, $charset, $buffers, $dialect);
    } elseif ($buffers !== null) {
        $safeResult = \ibase_connect($database, $username, $password, $charset, $buffers);
    } elseif ($charset !== null) {
        $safeResult = \ibase_connect($database, $username, $password, $charset);
    } elseif ($password !== null) {
        $safeResult = \ibase_connect($database, $username, $password);
    } elseif ($username !== null) {
        $safeResult = \ibase_connect($database, $username);
    } elseif ($database !== null) {
        $safeResult = \ibase_connect($database);
    } else {
        $safeResult = \ibase_connect();
    }
    if ($safeResult === false) {
        throw IbaseException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $service_handle
 * @param string $user_name
 * @throws IbaseException
 *
 */
function ibase_delete_user($service_handle, string $user_name): void
{
    error_clear_last();
    $safeResult = \ibase_delete_user($service_handle, $user_name);
    if ($safeResult === false) {
        throw IbaseException::createFromPhpError();
    }
}


/**
 * @param null|resource $connection
 * @throws IbaseException
 *
 */
function ibase_drop_db($connection = null): void
{
    error_clear_last();
    if ($connection !== null) {
        $safeResult = \ibase_drop_db($connection);
    } else {
        $safeResult = \ibase_drop_db();
    }
    if ($safeResult === false) {
        throw IbaseException::createFromPhpError();
    }
}


/**
 * @param resource $event
 * @throws IbaseException
 *
 */
function ibase_free_event_handler($event): void
{
    error_clear_last();
    $safeResult = \ibase_free_event_handler($event);
    if ($safeResult === false) {
        throw IbaseException::createFromPhpError();
    }
}


/**
 * @param resource $query
 * @throws IbaseException
 *
 */
function ibase_free_query($query): void
{
    error_clear_last();
    $safeResult = \ibase_free_query($query);
    if ($safeResult === false) {
        throw IbaseException::createFromPhpError();
    }
}


/**
 * @param resource $result_identifier
 * @throws IbaseException
 *
 */
function ibase_free_result($result_identifier): void
{
    error_clear_last();
    $safeResult = \ibase_free_result($result_identifier);
    if ($safeResult === false) {
        throw IbaseException::createFromPhpError();
    }
}


/**
 * @param resource $service_handle
 * @param string $db
 * @param int $action
 * @param int $argument
 * @throws IbaseException
 *
 */
function ibase_maintain_db($service_handle, string $db, int $action, int $argument = 0): void
{
    error_clear_last();
    $safeResult = \ibase_maintain_db($service_handle, $db, $action, $argument);
    if ($safeResult === false) {
        throw IbaseException::createFromPhpError();
    }
}


/**
 * @param resource $service_handle
 * @param string $user_name
 * @param string $password
 * @param string $first_name
 * @param string $middle_name
 * @param string $last_name
 * @throws IbaseException
 *
 */
function ibase_modify_user($service_handle, string $user_name, string $password, ?string $first_name = null, ?string $middle_name = null, ?string $last_name = null): void
{
    error_clear_last();
    if ($last_name !== null) {
        $safeResult = \ibase_modify_user($service_handle, $user_name, $password, $first_name, $middle_name, $last_name);
    } elseif ($middle_name !== null) {
        $safeResult = \ibase_modify_user($service_handle, $user_name, $password, $first_name, $middle_name);
    } elseif ($first_name !== null) {
        $safeResult = \ibase_modify_user($service_handle, $user_name, $password, $first_name);
    } else {
        $safeResult = \ibase_modify_user($service_handle, $user_name, $password);
    }
    if ($safeResult === false) {
        throw IbaseException::createFromPhpError();
    }
}


/**
 * @param resource $result
 * @param string $name
 * @throws IbaseException
 *
 */
function ibase_name_result($result, string $name): void
{
    error_clear_last();
    $safeResult = \ibase_name_result($result, $name);
    if ($safeResult === false) {
        throw IbaseException::createFromPhpError();
    }
}


/**
 * @param string $database
 * @param string $username
 * @param string $password
 * @param string $charset
 * @param int $buffers
 * @param int $dialect
 * @param string $role
 * @param int $sync
 * @return resource
 * @throws IbaseException
 *
 */
function ibase_pconnect(?string $database = null, ?string $username = null, ?string $password = null, ?string $charset = null, ?int $buffers = null, ?int $dialect = null, ?string $role = null, ?int $sync = null)
{
    error_clear_last();
    if ($sync !== null) {
        $safeResult = \ibase_pconnect($database, $username, $password, $charset, $buffers, $dialect, $role, $sync);
    } elseif ($role !== null) {
        $safeResult = \ibase_pconnect($database, $username, $password, $charset, $buffers, $dialect, $role);
    } elseif ($dialect !== null) {
        $safeResult = \ibase_pconnect($database, $username, $password, $charset, $buffers, $dialect);
    } elseif ($buffers !== null) {
        $safeResult = \ibase_pconnect($database, $username, $password, $charset, $buffers);
    } elseif ($charset !== null) {
        $safeResult = \ibase_pconnect($database, $username, $password, $charset);
    } elseif ($password !== null) {
        $safeResult = \ibase_pconnect($database, $username, $password);
    } elseif ($username !== null) {
        $safeResult = \ibase_pconnect($database, $username);
    } elseif ($database !== null) {
        $safeResult = \ibase_pconnect($database);
    } else {
        $safeResult = \ibase_pconnect();
    }
    if ($safeResult === false) {
        throw IbaseException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $service_handle
 * @param string $source_file
 * @param string $dest_db
 * @param int $options
 * @param bool $verbose
 * @return mixed
 * @throws IbaseException
 *
 */
function ibase_restore($service_handle, string $source_file, string $dest_db, int $options = 0, bool $verbose = false)
{
    error_clear_last();
    $safeResult = \ibase_restore($service_handle, $source_file, $dest_db, $options, $verbose);
    if ($safeResult === false) {
        throw IbaseException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param null|resource $link_or_trans_identifier
 * @throws IbaseException
 *
 */
function ibase_rollback_ret($link_or_trans_identifier = null): void
{
    error_clear_last();
    if ($link_or_trans_identifier !== null) {
        $safeResult = \ibase_rollback_ret($link_or_trans_identifier);
    } else {
        $safeResult = \ibase_rollback_ret();
    }
    if ($safeResult === false) {
        throw IbaseException::createFromPhpError();
    }
}


/**
 * @param null|resource $link_or_trans_identifier
 * @throws IbaseException
 *
 */
function ibase_rollback($link_or_trans_identifier = null): void
{
    error_clear_last();
    if ($link_or_trans_identifier !== null) {
        $safeResult = \ibase_rollback($link_or_trans_identifier);
    } else {
        $safeResult = \ibase_rollback();
    }
    if ($safeResult === false) {
        throw IbaseException::createFromPhpError();
    }
}


/**
 * @param string $host
 * @param string $dba_username
 * @param string $dba_password
 * @return resource
 * @throws IbaseException
 *
 */
function ibase_service_attach(string $host, string $dba_username, string $dba_password)
{
    error_clear_last();
    $safeResult = \ibase_service_attach($host, $dba_username, $dba_password);
    if ($safeResult === false) {
        throw IbaseException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $service_handle
 * @throws IbaseException
 *
 */
function ibase_service_detach($service_handle): void
{
    error_clear_last();
    $safeResult = \ibase_service_detach($service_handle);
    if ($safeResult === false) {
        throw IbaseException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\LdapException;

/**
 * @param string $value
 * @return string
 * @throws LdapException
 *
 */
function ldap_8859_to_t61(string $value): string
{
    error_clear_last();
    $safeResult = \ldap_8859_to_t61($value);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param array $entry
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_add(\LDAP\Connection $ldap, string $dn, array $entry, ?array $controls = null): void
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_add($ldap, $dn, $entry, $controls);
    } else {
        $safeResult = \ldap_add($ldap, $dn, $entry);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param null|string $dn
 * @param null|string $password
 * @throws LdapException
 *
 */
function ldap_bind(\LDAP\Connection $ldap, ?string $dn = null, ?string $password = null): void
{
    error_clear_last();
    if ($password !== null) {
        $safeResult = \ldap_bind($ldap, $dn, $password);
    } elseif ($dn !== null) {
        $safeResult = \ldap_bind($ldap, $dn);
    } else {
        $safeResult = \ldap_bind($ldap);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param string $attribute
 * @param string $value
 * @param array|null $controls
 * @return bool
 * @throws LdapException
 *
 */
function ldap_compare(\LDAP\Connection $ldap, string $dn, string $attribute, string $value, ?array $controls = null): bool
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_compare($ldap, $dn, $attribute, $value, $controls);
    } else {
        $safeResult = \ldap_compare($ldap, $dn, $attribute, $value);
    }
    if ($safeResult === -1) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $link
 * @param resource $result
 * @param null|string $cookie
 * @param int|null $estimated
 * @throws LdapException
 *
 */
function ldap_control_paged_result_response($link, $result, ?string &$cookie = null, ?int &$estimated = null): void
{
    error_clear_last();
    $safeResult = \ldap_control_paged_result_response($link, $result, $cookie, $estimated);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param resource $link
 * @param int $pagesize
 * @param bool $iscritical
 * @param string $cookie
 * @throws LdapException
 *
 */
function ldap_control_paged_result($link, int $pagesize, bool $iscritical = false, string $cookie = ""): void
{
    error_clear_last();
    $safeResult = \ldap_control_paged_result($link, $pagesize, $iscritical, $cookie);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\Result $result
 * @return int
 * @throws LdapException
 *
 */
function ldap_count_entries(\LDAP\Connection $ldap, \LDAP\Result $result): int
{
    error_clear_last();
    $safeResult = \ldap_count_entries($ldap, $result);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_delete(\LDAP\Connection $ldap, string $dn, ?array $controls = null): void
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_delete($ldap, $dn, $controls);
    } else {
        $safeResult = \ldap_delete($ldap, $dn);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param string $dn
 * @return string
 * @throws LdapException
 *
 */
function ldap_dn2ufn(string $dn): string
{
    error_clear_last();
    $safeResult = \ldap_dn2ufn($dn);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $user
 * @param string $old_password
 * @param string $new_password
 * @param array|null $controls
 * @return bool|string
 * @throws LdapException
 *
 */
function ldap_exop_passwd(\LDAP\Connection $ldap, string $user = "", string $old_password = "", string $new_password = "", ?array &$controls = null)
{
    error_clear_last();
    $safeResult = \ldap_exop_passwd($ldap, $user, $old_password, $new_password, $controls);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @return bool|string
 * @throws LdapException
 *
 */
function ldap_exop_whoami(\LDAP\Connection $ldap)
{
    error_clear_last();
    $safeResult = \ldap_exop_whoami($ldap);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $reqoid
 * @param null|string $reqdata
 * @param array|null $serverctrls
 * @param null|string $retdata
 * @param null|string $retoid
 * @return bool|resource
 * @throws LdapException
 *
 */
function ldap_exop(\LDAP\Connection $ldap, string $reqoid, ?string $reqdata = null, ?array $serverctrls = null, ?string &$retdata = null, ?string &$retoid = null)
{
    error_clear_last();
    if ($retoid !== null) {
        $safeResult = \ldap_exop($ldap, $reqoid, $reqdata, $serverctrls, $retdata, $retoid);
    } elseif ($retdata !== null) {
        $safeResult = \ldap_exop($ldap, $reqoid, $reqdata, $serverctrls, $retdata);
    } elseif ($serverctrls !== null) {
        $safeResult = \ldap_exop($ldap, $reqoid, $reqdata, $serverctrls);
    } elseif ($reqdata !== null) {
        $safeResult = \ldap_exop($ldap, $reqoid, $reqdata);
    } else {
        $safeResult = \ldap_exop($ldap, $reqoid);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $dn
 * @param int $with_attrib
 * @return array
 * @throws LdapException
 *
 */
function ldap_explode_dn(string $dn, int $with_attrib): array
{
    error_clear_last();
    $safeResult = \ldap_explode_dn($dn, $with_attrib);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\ResultEntry $entry
 * @return string
 * @throws LdapException
 *
 */
function ldap_first_attribute(\LDAP\Connection $ldap, \LDAP\ResultEntry $entry): string
{
    error_clear_last();
    $safeResult = \ldap_first_attribute($ldap, $entry);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\Result $result
 * @return \LDAP\ResultEntry
 * @throws LdapException
 *
 */
function ldap_first_entry(\LDAP\Connection $ldap, \LDAP\Result $result): \LDAP\ResultEntry
{
    error_clear_last();
    $safeResult = \ldap_first_entry($ldap, $result);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Result $result
 * @throws LdapException
 *
 */
function ldap_free_result(\LDAP\Result $result): void
{
    error_clear_last();
    $safeResult = \ldap_free_result($result);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\ResultEntry $entry
 * @return array
 * @throws LdapException
 *
 */
function ldap_get_attributes(\LDAP\Connection $ldap, \LDAP\ResultEntry $entry): array
{
    error_clear_last();
    $safeResult = \ldap_get_attributes($ldap, $entry);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\ResultEntry $entry
 * @return string
 * @throws LdapException
 *
 */
function ldap_get_dn(\LDAP\Connection $ldap, \LDAP\ResultEntry $entry): string
{
    error_clear_last();
    $safeResult = \ldap_get_dn($ldap, $entry);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\Result $result
 * @return array
 * @throws LdapException
 *
 */
function ldap_get_entries(\LDAP\Connection $ldap, \LDAP\Result $result): array
{
    error_clear_last();
    $safeResult = \ldap_get_entries($ldap, $result);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param int $option
 * @param mixed $value
 * @throws LdapException
 *
 */
function ldap_get_option(\LDAP\Connection $ldap, int $option, &$value = null): void
{
    error_clear_last();
    $safeResult = \ldap_get_option($ldap, $option, $value);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\ResultEntry $entry
 * @param string $attribute
 * @return array
 * @throws LdapException
 *
 */
function ldap_get_values_len(\LDAP\Connection $ldap, \LDAP\ResultEntry $entry, string $attribute): array
{
    error_clear_last();
    $safeResult = \ldap_get_values_len($ldap, $entry, $attribute);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\ResultEntry $entry
 * @param string $attribute
 * @return array
 * @throws LdapException
 *
 */
function ldap_get_values(\LDAP\Connection $ldap, \LDAP\ResultEntry $entry, string $attribute): array
{
    error_clear_last();
    $safeResult = \ldap_get_values($ldap, $entry, $attribute);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param array $entry
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_mod_add(\LDAP\Connection $ldap, string $dn, array $entry, ?array $controls = null): void
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_mod_add($ldap, $dn, $entry, $controls);
    } else {
        $safeResult = \ldap_mod_add($ldap, $dn, $entry);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param array $entry
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_mod_del(\LDAP\Connection $ldap, string $dn, array $entry, ?array $controls = null): void
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_mod_del($ldap, $dn, $entry, $controls);
    } else {
        $safeResult = \ldap_mod_del($ldap, $dn, $entry);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param array $entry
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_mod_replace(\LDAP\Connection $ldap, string $dn, array $entry, ?array $controls = null): void
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_mod_replace($ldap, $dn, $entry, $controls);
    } else {
        $safeResult = \ldap_mod_replace($ldap, $dn, $entry);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param array $modifications_info
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_modify_batch(\LDAP\Connection $ldap, string $dn, array $modifications_info, ?array $controls = null): void
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_modify_batch($ldap, $dn, $modifications_info, $controls);
    } else {
        $safeResult = \ldap_modify_batch($ldap, $dn, $modifications_info);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\ResultEntry $entry
 * @return string
 * @throws LdapException
 *
 */
function ldap_next_attribute(\LDAP\Connection $ldap, \LDAP\ResultEntry $entry): string
{
    error_clear_last();
    $safeResult = \ldap_next_attribute($ldap, $entry);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\Result $result
 * @param null|string $response_data
 * @param null|string $response_oid
 * @throws LdapException
 *
 */
function ldap_parse_exop(\LDAP\Connection $ldap, \LDAP\Result $result, ?string &$response_data = null, ?string &$response_oid = null): void
{
    error_clear_last();
    $safeResult = \ldap_parse_exop($ldap, $result, $response_data, $response_oid);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param \LDAP\Result $result
 * @param int|null $error_code
 * @param null|string $matched_dn
 * @param null|string $error_message
 * @param array|null $referrals
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_parse_result(\LDAP\Connection $ldap, \LDAP\Result $result, ?int &$error_code, ?string &$matched_dn = null, ?string &$error_message = null, ?array &$referrals = null, ?array &$controls = null): void
{
    error_clear_last();
    $safeResult = \ldap_parse_result($ldap, $result, $error_code, $matched_dn, $error_message, $referrals, $controls);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param string $dn
 * @param string $new_rdn
 * @param string $new_parent
 * @param bool $delete_old_rdn
 * @param array|null $controls
 * @throws LdapException
 *
 */
function ldap_rename(\LDAP\Connection $ldap, string $dn, string $new_rdn, string $new_parent, bool $delete_old_rdn, ?array $controls = null): void
{
    error_clear_last();
    if ($controls !== null) {
        $safeResult = \ldap_rename($ldap, $dn, $new_rdn, $new_parent, $delete_old_rdn, $controls);
    } else {
        $safeResult = \ldap_rename($ldap, $dn, $new_rdn, $new_parent, $delete_old_rdn);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @param null|string $dn
 * @param null|string $password
 * @param null|string $mech
 * @param null|string $realm
 * @param null|string $authc_id
 * @param null|string $authz_id
 * @param null|string $props
 * @throws LdapException
 *
 */
function ldap_sasl_bind(\LDAP\Connection $ldap, ?string $dn = null, ?string $password = null, ?string $mech = null, ?string $realm = null, ?string $authc_id = null, ?string $authz_id = null, ?string $props = null): void
{
    error_clear_last();
    if ($props !== null) {
        $safeResult = \ldap_sasl_bind($ldap, $dn, $password, $mech, $realm, $authc_id, $authz_id, $props);
    } elseif ($authz_id !== null) {
        $safeResult = \ldap_sasl_bind($ldap, $dn, $password, $mech, $realm, $authc_id, $authz_id);
    } elseif ($authc_id !== null) {
        $safeResult = \ldap_sasl_bind($ldap, $dn, $password, $mech, $realm, $authc_id);
    } elseif ($realm !== null) {
        $safeResult = \ldap_sasl_bind($ldap, $dn, $password, $mech, $realm);
    } elseif ($mech !== null) {
        $safeResult = \ldap_sasl_bind($ldap, $dn, $password, $mech);
    } elseif ($password !== null) {
        $safeResult = \ldap_sasl_bind($ldap, $dn, $password);
    } elseif ($dn !== null) {
        $safeResult = \ldap_sasl_bind($ldap, $dn);
    } else {
        $safeResult = \ldap_sasl_bind($ldap);
    }
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param null|resource $ldap
 * @param int $option
 * @param mixed $value
 * @throws LdapException
 *
 */
function ldap_set_option($ldap, int $option, $value): void
{
    error_clear_last();
    $safeResult = \ldap_set_option($ldap, $option, $value);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}


/**
 * @param \LDAP\Connection $ldap
 * @throws LdapException
 *
 */
function ldap_unbind(\LDAP\Connection $ldap): void
{
    error_clear_last();
    $safeResult = \ldap_unbind($ldap);
    if ($safeResult === false) {
        throw LdapException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\OutcontrolException;

/**
 * @throws OutcontrolException
 *
 */
function ob_clean(): void
{
    error_clear_last();
    $safeResult = \ob_clean();
    if ($safeResult === false) {
        throw OutcontrolException::createFromPhpError();
    }
}


/**
 * @throws OutcontrolException
 *
 */
function ob_end_clean(): void
{
    error_clear_last();
    $safeResult = \ob_end_clean();
    if ($safeResult === false) {
        throw OutcontrolException::createFromPhpError();
    }
}


/**
 * @throws OutcontrolException
 *
 */
function ob_end_flush(): void
{
    error_clear_last();
    $safeResult = \ob_end_flush();
    if ($safeResult === false) {
        throw OutcontrolException::createFromPhpError();
    }
}


/**
 * @throws OutcontrolException
 *
 */
function ob_flush(): void
{
    error_clear_last();
    $safeResult = \ob_flush();
    if ($safeResult === false) {
        throw OutcontrolException::createFromPhpError();
    }
}


/**
 * @return string
 * @throws OutcontrolException
 *
 */
function ob_get_clean(): string
{
    error_clear_last();
    $safeResult = \ob_get_clean();
    if ($safeResult === false) {
        throw OutcontrolException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param array|callable|null|string $callback
 * @param int $chunk_size
 * @param int $flags
 * @throws OutcontrolException
 *
 */
function ob_start($callback = null, int $chunk_size = 0, int $flags = PHP_OUTPUT_HANDLER_STDFLAGS): void
{
    error_clear_last();
    if ($flags !== PHP_OUTPUT_HANDLER_STDFLAGS) {
        $safeResult = \ob_start($callback, $chunk_size, $flags);
    } elseif ($chunk_size !== 0) {
        $safeResult = \ob_start($callback, $chunk_size);
    } elseif ($callback !== null) {
        $safeResult = \ob_start($callback);
    } else {
        $safeResult = \ob_start();
    }
    if ($safeResult === false) {
        throw OutcontrolException::createFromPhpError();
    }
}


/**
 * @param string $name
 * @param string $value
 * @throws OutcontrolException
 *
 */
function output_add_rewrite_var(string $name, string $value): void
{
    error_clear_last();
    $safeResult = \output_add_rewrite_var($name, $value);
    if ($safeResult === false) {
        throw OutcontrolException::createFromPhpError();
    }
}


/**
 * @throws OutcontrolException
 *
 */
function output_reset_rewrite_vars(): void
{
    error_clear_last();
    $safeResult = \output_reset_rewrite_vars();
    if ($safeResult === false) {
        throw OutcontrolException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\XmlrpcException;

/**
 * @param \DateTime|string $value
 * @param string $type
 * @throws XmlrpcException
 *
 */
function xmlrpc_set_type(&$value, string $type): void
{
    error_clear_last();
    $safeResult = \xmlrpc_set_type($value, $type);
    if ($safeResult === false) {
        throw XmlrpcException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\DirException;

/**
 * @param string $directory
 * @throws DirException
 *
 */
function chdir(string $directory): void
{
    error_clear_last();
    $safeResult = \chdir($directory);
    if ($safeResult === false) {
        throw DirException::createFromPhpError();
    }
}


/**
 * @param string $directory
 * @throws DirException
 *
 */
function chroot(string $directory): void
{
    error_clear_last();
    $safeResult = \chroot($directory);
    if ($safeResult === false) {
        throw DirException::createFromPhpError();
    }
}


/**
 * @param string $directory
 * @param null|resource $context
 * @return \Directory
 * @throws DirException
 *
 */
function dir(string $directory, $context = null): \Directory
{
    error_clear_last();
    if ($context !== null) {
        $safeResult = \dir($directory, $context);
    } else {
        $safeResult = \dir($directory);
    }
    if ($safeResult === false) {
        throw DirException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return non-empty-string
 * @throws DirException
 *
 */
function getcwd(): string
{
    error_clear_last();
    $safeResult = \getcwd();
    if ($safeResult === false) {
        throw DirException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $directory
 * @param null|resource $context
 * @return resource
 * @throws DirException
 *
 */
function opendir(string $directory, $context = null)
{
    error_clear_last();
    if ($context !== null) {
        $safeResult = \opendir($directory, $context);
    } else {
        $safeResult = \opendir($directory);
    }
    if ($safeResult === false) {
        throw DirException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $directory
 * @param SCANDIR_SORT_ASCENDING|SCANDIR_SORT_DESCENDING|SCANDIR_SORT_NONE $sorting_order
 * @param null|resource $context
 * @return list
 * @throws DirException
 *
 */
function scandir(string $directory, int $sorting_order = SCANDIR_SORT_ASCENDING, $context = null): array
{
    error_clear_last();
    if ($context !== null) {
        $safeResult = \scandir($directory, $sorting_order, $context);
    } else {
        $safeResult = \scandir($directory, $sorting_order);
    }
    if ($safeResult === false) {
        throw DirException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\MailparseException;

/**
 * @param resource $mimemail
 * @param mixed $filename
 * @param callable $callbackfunc
 * @return string
 * @throws MailparseException
 *
 */
function mailparse_msg_extract_part_file($mimemail, $filename, ?callable $callbackfunc = null): string
{
    error_clear_last();
    if ($callbackfunc !== null) {
        $safeResult = \mailparse_msg_extract_part_file($mimemail, $filename, $callbackfunc);
    } else {
        $safeResult = \mailparse_msg_extract_part_file($mimemail, $filename);
    }
    if ($safeResult === false) {
        throw MailparseException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $mimemail
 * @throws MailparseException
 *
 */
function mailparse_msg_free($mimemail): void
{
    error_clear_last();
    $safeResult = \mailparse_msg_free($mimemail);
    if ($safeResult === false) {
        throw MailparseException::createFromPhpError();
    }
}


/**
 * @param string $filename
 * @return resource
 * @throws MailparseException
 *
 */
function mailparse_msg_parse_file(string $filename)
{
    error_clear_last();
    $safeResult = \mailparse_msg_parse_file($filename);
    if ($safeResult === false) {
        throw MailparseException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $mimemail
 * @param string $data
 * @throws MailparseException
 *
 */
function mailparse_msg_parse($mimemail, string $data): void
{
    error_clear_last();
    $safeResult = \mailparse_msg_parse($mimemail, $data);
    if ($safeResult === false) {
        throw MailparseException::createFromPhpError();
    }
}


/**
 * @param resource $sourcefp
 * @param resource $destfp
 * @param string $encoding
 * @throws MailparseException
 *
 */
function mailparse_stream_encode($sourcefp, $destfp, string $encoding): void
{
    error_clear_last();
    $safeResult = \mailparse_stream_encode($sourcefp, $destfp, $encoding);
    if ($safeResult === false) {
        throw MailparseException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\SqlsrvException;

/**
 * @param resource $conn
 * @throws SqlsrvException
 *
 */
function sqlsrv_begin_transaction($conn): void
{
    error_clear_last();
    $safeResult = \sqlsrv_begin_transaction($conn);
    if ($safeResult === false) {
        throw SqlsrvException::createFromPhpError();
    }
}


/**
 * @param resource $stmt
 * @throws SqlsrvException
 *
 */
function sqlsrv_cancel($stmt): void
{
    error_clear_last();
    $safeResult = \sqlsrv_cancel($stmt);
    if ($safeResult === false) {
        throw SqlsrvException::createFromPhpError();
    }
}


/**
 * @param resource $conn
 * @return array
 * @throws SqlsrvException
 *
 */
function sqlsrv_client_info($conn): array
{
    error_clear_last();
    $safeResult = \sqlsrv_client_info($conn);
    if ($safeResult === false) {
        throw SqlsrvException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $conn
 * @throws SqlsrvException
 *
 */
function sqlsrv_close($conn): void
{
    error_clear_last();
    $safeResult = \sqlsrv_close($conn);
    if ($safeResult === false) {
        throw SqlsrvException::createFromPhpError();
    }
}


/**
 * @param resource $conn
 * @throws SqlsrvException
 *
 */
function sqlsrv_commit($conn): void
{
    error_clear_last();
    $safeResult = \sqlsrv_commit($conn);
    if ($safeResult === false) {
        throw SqlsrvException::createFromPhpError();
    }
}


/**
 * @param string $setting
 * @param mixed $value
 * @throws SqlsrvException
 *
 */
function sqlsrv_configure(string $setting, $value): void
{
    error_clear_last();
    $safeResult = \sqlsrv_configure($setting, $value);
    if ($safeResult === false) {
        throw SqlsrvException::createFromPhpError();
    }
}


/**
 * @param resource $stmt
 * @throws SqlsrvException
 *
 */
function sqlsrv_execute($stmt): void
{
    error_clear_last();
    $safeResult = \sqlsrv_execute($stmt);
    if ($safeResult === false) {
        throw SqlsrvException::createFromPhpError();
    }
}


/**
 * @param resource $stmt
 * @param int $fetchType
 * @param int $row
 * @param int $offset
 * @return array|null
 * @throws SqlsrvException
 *
 */
function sqlsrv_fetch_array($stmt, ?int $fetchType = null, ?int $row = null, ?int $offset = null): ?array
{
    error_clear_last();
    if ($offset !== null) {
        $safeResult = \sqlsrv_fetch_array($stmt, $fetchType, $row, $offset);
    } elseif ($row !== null) {
        $safeResult = \sqlsrv_fetch_array($stmt, $fetchType, $row);
    } elseif ($fetchType !== null) {
        $safeResult = \sqlsrv_fetch_array($stmt, $fetchType);
    } else {
        $safeResult = \sqlsrv_fetch_array($stmt);
    }
    if ($safeResult === false) {
        throw SqlsrvException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stmt
 * @param string $className
 * @param array $ctorParams
 * @param int $row
 * @param int $offset
 * @return null|object
 * @throws SqlsrvException
 *
 */
function sqlsrv_fetch_object($stmt, ?string $className = null, ?array $ctorParams = null, ?int $row = null, ?int $offset = null): ?object
{
    error_clear_last();
    if ($offset !== null) {
        $safeResult = \sqlsrv_fetch_object($stmt, $className, $ctorParams, $row, $offset);
    } elseif ($row !== null) {
        $safeResult = \sqlsrv_fetch_object($stmt, $className, $ctorParams, $row);
    } elseif ($ctorParams !== null) {
        $safeResult = \sqlsrv_fetch_object($stmt, $className, $ctorParams);
    } elseif ($className !== null) {
        $safeResult = \sqlsrv_fetch_object($stmt, $className);
    } else {
        $safeResult = \sqlsrv_fetch_object($stmt);
    }
    if ($safeResult === false) {
        throw SqlsrvException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stmt
 * @param int $row
 * @param int $offset
 * @return bool|null
 * @throws SqlsrvException
 *
 */
function sqlsrv_fetch($stmt, ?int $row = null, ?int $offset = null): ?bool
{
    error_clear_last();
    if ($offset !== null) {
        $safeResult = \sqlsrv_fetch($stmt, $row, $offset);
    } elseif ($row !== null) {
        $safeResult = \sqlsrv_fetch($stmt, $row);
    } else {
        $safeResult = \sqlsrv_fetch($stmt);
    }
    if ($safeResult === false) {
        throw SqlsrvException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stmt
 * @throws SqlsrvException
 *
 */
function sqlsrv_free_stmt($stmt): void
{
    error_clear_last();
    $safeResult = \sqlsrv_free_stmt($stmt);
    if ($safeResult === false) {
        throw SqlsrvException::createFromPhpError();
    }
}


/**
 * @param resource $stmt
 * @param int $fieldIndex
 * @param int $getAsType
 * @return mixed
 * @throws SqlsrvException
 *
 */
function sqlsrv_get_field($stmt, int $fieldIndex, ?int $getAsType = null)
{
    error_clear_last();
    if ($getAsType !== null) {
        $safeResult = \sqlsrv_get_field($stmt, $fieldIndex, $getAsType);
    } else {
        $safeResult = \sqlsrv_get_field($stmt, $fieldIndex);
    }
    if ($safeResult === false) {
        throw SqlsrvException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stmt
 * @return bool|null
 * @throws SqlsrvException
 *
 */
function sqlsrv_next_result($stmt): ?bool
{
    error_clear_last();
    $safeResult = \sqlsrv_next_result($stmt);
    if ($safeResult === false) {
        throw SqlsrvException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stmt
 * @return int
 * @throws SqlsrvException
 *
 */
function sqlsrv_num_fields($stmt): int
{
    error_clear_last();
    $safeResult = \sqlsrv_num_fields($stmt);
    if ($safeResult === false) {
        throw SqlsrvException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $stmt
 * @return int
 * @throws SqlsrvException
 *
 */
function sqlsrv_num_rows($stmt): int
{
    error_clear_last();
    $safeResult = \sqlsrv_num_rows($stmt);
    if ($safeResult === false) {
        throw SqlsrvException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $conn
 * @param string $sql
 * @param array $params
 * @param array $options
 * @return mixed
 * @throws SqlsrvException
 *
 */
function sqlsrv_prepare($conn, string $sql, ?array $params = null, ?array $options = null)
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \sqlsrv_prepare($conn, $sql, $params, $options);
    } elseif ($params !== null) {
        $safeResult = \sqlsrv_prepare($conn, $sql, $params);
    } else {
        $safeResult = \sqlsrv_prepare($conn, $sql);
    }
    if ($safeResult === false) {
        throw SqlsrvException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $conn
 * @param string $sql
 * @param array $params
 * @param array $options
 * @return mixed
 * @throws SqlsrvException
 *
 */
function sqlsrv_query($conn, string $sql, ?array $params = null, ?array $options = null)
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \sqlsrv_query($conn, $sql, $params, $options);
    } elseif ($params !== null) {
        $safeResult = \sqlsrv_query($conn, $sql, $params);
    } else {
        $safeResult = \sqlsrv_query($conn, $sql);
    }
    if ($safeResult === false) {
        throw SqlsrvException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $conn
 * @throws SqlsrvException
 *
 */
function sqlsrv_rollback($conn): void
{
    error_clear_last();
    $safeResult = \sqlsrv_rollback($conn);
    if ($safeResult === false) {
        throw SqlsrvException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\YazException;

/**
 * @param resource $id
 * @param string $query
 * @param array|null $result
 * @throws YazException
 *
 */
function yaz_ccl_parse($id, string $query, ?array &$result): void
{
    error_clear_last();
    $safeResult = \yaz_ccl_parse($id, $query, $result);
    if ($safeResult === false) {
        throw YazException::createFromPhpError();
    }
}


/**
 * @param resource $id
 * @throws YazException
 *
 */
function yaz_close($id): void
{
    error_clear_last();
    $safeResult = \yaz_close($id);
    if ($safeResult === false) {
        throw YazException::createFromPhpError();
    }
}


/**
 * @param string $zurl
 * @param mixed $options
 * @return mixed
 * @throws YazException
 *
 */
function yaz_connect(string $zurl, $options = null)
{
    error_clear_last();
    if ($options !== null) {
        $safeResult = \yaz_connect($zurl, $options);
    } else {
        $safeResult = \yaz_connect($zurl);
    }
    if ($safeResult === false) {
        throw YazException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param resource $id
 * @param string $databases
 * @throws YazException
 *
 */
function yaz_database($id, string $databases): void
{
    error_clear_last();
    $safeResult = \yaz_database($id, $databases);
    if ($safeResult === false) {
        throw YazException::createFromPhpError();
    }
}


/**
 * @param resource $id
 * @param string $elementset
 * @throws YazException
 *
 */
function yaz_element($id, string $elementset): void
{
    error_clear_last();
    $safeResult = \yaz_element($id, $elementset);
    if ($safeResult === false) {
        throw YazException::createFromPhpError();
    }
}


/**
 * @param resource $id
 * @throws YazException
 *
 */
function yaz_present($id): void
{
    error_clear_last();
    $safeResult = \yaz_present($id);
    if ($safeResult === false) {
        throw YazException::createFromPhpError();
    }
}


/**
 * @param resource $id
 * @param string $type
 * @param string $query
 * @throws YazException
 *
 */
function yaz_search($id, string $type, string $query): void
{
    error_clear_last();
    $safeResult = \yaz_search($id, $type, $query);
    if ($safeResult === false) {
        throw YazException::createFromPhpError();
    }
}


/**
 * @param array $options
 * @return mixed
 * @throws YazException
 *
 */
function yaz_wait(?array &$options = null)
{
    error_clear_last();
    $safeResult = \yaz_wait($options);
    if ($safeResult === false) {
        throw YazException::createFromPhpError();
    }
    return $safeResult;
}
<?php

namespace Safe;

use Safe\Exceptions\ClassobjException;

/**
 * @param string $class
 * @param string $alias
 * @param bool $autoload
 * @throws ClassobjException
 *
 */
function class_alias(string $class, string $alias, bool $autoload = true): void
{
    error_clear_last();
    $safeResult = \class_alias($class, $alias, $autoload);
    if ($safeResult === false) {
        throw ClassobjException::createFromPhpError();
    }
}
<?php

namespace Safe;

use Safe\Exceptions\ApacheException;

/**
 * @return string
 * @throws ApacheException
 *
 */
function apache_get_version(): string
{
    error_clear_last();
    $safeResult = \apache_get_version();
    if ($safeResult === false) {
        throw ApacheException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $variable
 * @param bool $walk_to_top
 * @return string
 * @throws ApacheException
 *
 */
function apache_getenv(string $variable, bool $walk_to_top = false): string
{
    error_clear_last();
    $safeResult = \apache_getenv($variable, $walk_to_top);
    if ($safeResult === false) {
        throw ApacheException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $filename
 * @return object
 * @throws ApacheException
 *
 */
function apache_lookup_uri(string $filename): object
{
    error_clear_last();
    $safeResult = \apache_lookup_uri($filename);
    if ($safeResult === false) {
        throw ApacheException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return array
 * @throws ApacheException
 *
 */
function apache_request_headers(): array
{
    error_clear_last();
    $safeResult = \apache_request_headers();
    if ($safeResult === false) {
        throw ApacheException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @return array
 * @throws ApacheException
 *
 */
function apache_response_headers(): array
{
    error_clear_last();
    $safeResult = \apache_response_headers();
    if ($safeResult === false) {
        throw ApacheException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $variable
 * @param string $value
 * @param bool $walk_to_top
 * @throws ApacheException
 *
 */
function apache_setenv(string $variable, string $value, bool $walk_to_top = false): void
{
    error_clear_last();
    $safeResult = \apache_setenv($variable, $value, $walk_to_top);
    if ($safeResult === false) {
        throw ApacheException::createFromPhpError();
    }
}


/**
 * @return array
 * @throws ApacheException
 *
 */
function getallheaders(): array
{
    error_clear_last();
    $safeResult = \getallheaders();
    if ($safeResult === false) {
        throw ApacheException::createFromPhpError();
    }
    return $safeResult;
}


/**
 * @param string $uri
 * @throws ApacheException
 *
 */
function virtual(string $uri): void
{
    error_clear_last();
    $safeResult = \virtual($uri);
    if ($safeResult === false) {
        throw ApacheException::createFromPhpError();
    }
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/xml.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/xml.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/xml.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.4/xml.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.4/xml.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.4/xml.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/ssdeep.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/ssdeep.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/ssdeep.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/ssdeep.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/ssdeep.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/ssdeep.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/strings.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/strings.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/strings.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/strings.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/strings.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/strings.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/mbstring.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/mbstring.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/mbstring.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.4/mbstring.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.4/mbstring.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.4/mbstring.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/pcre.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/pcre.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/pcre.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/pcre.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/pcre.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/pcre.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/posix.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/posix.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/posix.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.4/posix.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.4/posix.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.4/posix.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/cubrid.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/cubrid.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/cubrid.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/cubrid.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/cubrid.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/cubrid.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/readline.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/readline.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/readline.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.4/readline.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.4/readline.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.4/readline.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/ibmDb2.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/ibmDb2.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/ibmDb2.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.4/ibmDb2.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.4/ibmDb2.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.4/ibmDb2.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/ibase.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/ibase.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/ibase.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/ibase.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/ibase.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/ibase.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/ldap.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.2/ldap.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.2/ldap.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.4/ldap.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.4/ldap.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.6/ldap.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/outcontrol.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/outcontrol.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/outcontrol.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.4/outcontrol.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.4/outcontrol.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.4/outcontrol.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/xmlrpc.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/xmlrpc.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/xmlrpc.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/xmlrpc.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/xmlrpc.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/xmlrpc.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/dir.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/dir.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/dir.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/dir.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/dir.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/dir.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/mailparse.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/mailparse.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/mailparse.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/mailparse.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/mailparse.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/mailparse.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/sqlsrv.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/sqlsrv.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/sqlsrv.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/sqlsrv.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/sqlsrv.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/sqlsrv.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/yaz.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/yaz.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/yaz.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/yaz.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/yaz.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/yaz.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/classobj.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/classobj.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/classobj.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.1/classobj.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.1/classobj.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.1/classobj.php';
}
<?php

if (str_starts_with(PHP_VERSION, "8.1.")) {
    require_once __DIR__ . '/8.1/apache.php';
}
if (str_starts_with(PHP_VERSION, "8.2.")) {
    require_once __DIR__ . '/8.1/apache.php';
}
if (str_starts_with(PHP_VERSION, "8.3.")) {
    require_once __DIR__ . '/8.1/apache.php';
}
if (str_starts_with(PHP_VERSION, "8.4.")) {
    require_once __DIR__ . '/8.4/apache.php';
}
if (str_starts_with(PHP_VERSION, "8.5.")) {
    require_once __DIR__ . '/8.4/apache.php';
}
if (str_starts_with(PHP_VERSION, "8.6.")) {
    require_once __DIR__ . '/8.4/apache.php';
}
<?php

namespace Illuminate\Container;

use ArrayAccess;
use Closure;
use Exception;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Contracts\Container\CircularDependencyException;
use Illuminate\Contracts\Container\Container as ContainerContract;
use LogicException;
use ReflectionClass;
use ReflectionException;
use ReflectionFunction;
use ReflectionParameter;
use TypeError;

class Container implements ArrayAccess, ContainerContract
{
    /**
     * The current globally available container (if any).
     *
     * @var static
     */
    protected static $instance;

    /**
     * An array of the types that have been resolved.
     *
     * @var bool[]
     */
    protected $resolved = [];

    /**
     * The container's bindings.
     *
     * @var array[]
     */
    protected $bindings = [];

    /**
     * The container's method bindings.
     *
     * @var \Closure[]
     */
    protected $methodBindings = [];

    /**
     * The container's shared instances.
     *
     * @var object[]
     */
    protected $instances = [];

    /**
     * The container's scoped instances.
     *
     * @var array
     */
    protected $scopedInstances = [];

    /**
     * The registered type aliases.
     *
     * @var string[]
     */
    protected $aliases = [];

    /**
     * The registered aliases keyed by the abstract name.
     *
     * @var array[]
     */
    protected $abstractAliases = [];

    /**
     * The extension closures for services.
     *
     * @var array[]
     */
    protected $extenders = [];

    /**
     * All of the registered tags.
     *
     * @var array[]
     */
    protected $tags = [];

    /**
     * The stack of concretions currently being built.
     *
     * @var array[]
     */
    protected $buildStack = [];

    /**
     * The parameter override stack.
     *
     * @var array[]
     */
    protected $with = [];

    /**
     * The contextual binding map.
     *
     * @var array[]
     */
    public $contextual = [];

    /**
     * All of the registered rebound callbacks.
     *
     * @var array[]
     */
    protected $reboundCallbacks = [];

    /**
     * All of the global before resolving callbacks.
     *
     * @var \Closure[]
     */
    protected $globalBeforeResolvingCallbacks = [];

    /**
     * All of the global resolving callbacks.
     *
     * @var \Closure[]
     */
    protected $globalResolvingCallbacks = [];

    /**
     * All of the global after resolving callbacks.
     *
     * @var \Closure[]
     */
    protected $globalAfterResolvingCallbacks = [];

    /**
     * All of the before resolving callbacks by class type.
     *
     * @var array[]
     */
    protected $beforeResolvingCallbacks = [];

    /**
     * All of the resolving callbacks by class type.
     *
     * @var array[]
     */
    protected $resolvingCallbacks = [];

    /**
     * All of the after resolving callbacks by class type.
     *
     * @var array[]
     */
    protected $afterResolvingCallbacks = [];

    /**
     * Define a contextual binding.
     *
     * @param  array|string  $concrete
     * @return \Illuminate\Contracts\Container\ContextualBindingBuilder
     */
    public function when($concrete)
    {
        $aliases = [];

        foreach (Util::arrayWrap($concrete) as $c) {
            $aliases[] = $this->getAlias($c);
        }

        return new ContextualBindingBuilder($this, $aliases);
    }

    /**
     * Determine if the given abstract type has been bound.
     *
     * @param  string  $abstract
     * @return bool
     */
    public function bound($abstract)
    {
        return isset($this->bindings[$abstract]) ||
               isset($this->instances[$abstract]) ||
               $this->isAlias($abstract);
    }

    /**
     * {@inheritdoc}
     *
     * @return bool
     */
    public function has(string $id): bool
    {
        return $this->bound($id);
    }

    /**
     * Determine if the given abstract type has been resolved.
     *
     * @param  string  $abstract
     * @return bool
     */
    public function resolved($abstract)
    {
        if ($this->isAlias($abstract)) {
            $abstract = $this->getAlias($abstract);
        }

        return isset($this->resolved[$abstract]) ||
               isset($this->instances[$abstract]);
    }

    /**
     * Determine if a given type is shared.
     *
     * @param  string  $abstract
     * @return bool
     */
    public function isShared($abstract)
    {
        return isset($this->instances[$abstract]) ||
               (isset($this->bindings[$abstract]['shared']) &&
               $this->bindings[$abstract]['shared'] === true);
    }

    /**
     * Determine if a given string is an alias.
     *
     * @param  string  $name
     * @return bool
     */
    public function isAlias($name)
    {
        return isset($this->aliases[$name]);
    }

    /**
     * Register a binding with the container.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @param  bool  $shared
     * @return void
     *
     * @throws \TypeError
     */
    public function bind($abstract, $concrete = null, $shared = false)
    {
        $this->dropStaleInstances($abstract);

        // If no concrete type was given, we will simply set the concrete type to the
        // abstract type. After that, the concrete type to be registered as shared
        // without being forced to state their classes in both of the parameters.
        if (is_null($concrete)) {
            $concrete = $abstract;
        }

        // If the factory is not a Closure, it means it is just a class name which is
        // bound into this container to the abstract type and we will just wrap it
        // up inside its own Closure to give us more convenience when extending.
        if (! $concrete instanceof Closure) {
            if (! is_string($concrete)) {
                throw new TypeError(self::class.'::bind(): Argument #2 ($concrete) must be of type Closure|string|null');
            }

            $concrete = $this->getClosure($abstract, $concrete);
        }

        $this->bindings[$abstract] = compact('concrete', 'shared');

        // If the abstract type was already resolved in this container we'll fire the
        // rebound listener so that any objects which have already gotten resolved
        // can have their copy of the object updated via the listener callbacks.
        if ($this->resolved($abstract)) {
            $this->rebound($abstract);
        }
    }

    /**
     * Get the Closure to be used when building a type.
     *
     * @param  string  $abstract
     * @param  string  $concrete
     * @return \Closure
     */
    protected function getClosure($abstract, $concrete)
    {
        return function ($container, $parameters = []) use ($abstract, $concrete) {
            if ($abstract == $concrete) {
                return $container->build($concrete);
            }

            return $container->resolve(
                $concrete, $parameters, $raiseEvents = false
            );
        };
    }

    /**
     * Determine if the container has a method binding.
     *
     * @param  string  $method
     * @return bool
     */
    public function hasMethodBinding($method)
    {
        return isset($this->methodBindings[$method]);
    }

    /**
     * Bind a callback to resolve with Container::call.
     *
     * @param  array|string  $method
     * @param  \Closure  $callback
     * @return void
     */
    public function bindMethod($method, $callback)
    {
        $this->methodBindings[$this->parseBindMethod($method)] = $callback;
    }

    /**
     * Get the method to be bound in class@method format.
     *
     * @param  array|string  $method
     * @return string
     */
    protected function parseBindMethod($method)
    {
        if (is_array($method)) {
            return $method[0].'@'.$method[1];
        }

        return $method;
    }

    /**
     * Get the method binding for the given method.
     *
     * @param  string  $method
     * @param  mixed  $instance
     * @return mixed
     */
    public function callMethodBinding($method, $instance)
    {
        return call_user_func($this->methodBindings[$method], $instance, $this);
    }

    /**
     * Add a contextual binding to the container.
     *
     * @param  string  $concrete
     * @param  string  $abstract
     * @param  \Closure|string  $implementation
     * @return void
     */
    public function addContextualBinding($concrete, $abstract, $implementation)
    {
        $this->contextual[$concrete][$this->getAlias($abstract)] = $implementation;
    }

    /**
     * Register a binding if it hasn't already been registered.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @param  bool  $shared
     * @return void
     */
    public function bindIf($abstract, $concrete = null, $shared = false)
    {
        if (! $this->bound($abstract)) {
            $this->bind($abstract, $concrete, $shared);
        }
    }

    /**
     * Register a shared binding in the container.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @return void
     */
    public function singleton($abstract, $concrete = null)
    {
        $this->bind($abstract, $concrete, true);
    }

    /**
     * Register a shared binding if it hasn't already been registered.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @return void
     */
    public function singletonIf($abstract, $concrete = null)
    {
        if (! $this->bound($abstract)) {
            $this->singleton($abstract, $concrete);
        }
    }

    /**
     * Register a scoped binding in the container.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @return void
     */
    public function scoped($abstract, $concrete = null)
    {
        $this->scopedInstances[] = $abstract;

        $this->singleton($abstract, $concrete);
    }

    /**
     * Register a scoped binding if it hasn't already been registered.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @return void
     */
    public function scopedIf($abstract, $concrete = null)
    {
        if (! $this->bound($abstract)) {
            $this->scoped($abstract, $concrete);
        }
    }

    /**
     * "Extend" an abstract type in the container.
     *
     * @param  string  $abstract
     * @param  \Closure  $closure
     * @return void
     *
     * @throws \InvalidArgumentException
     */
    public function extend($abstract, Closure $closure)
    {
        $abstract = $this->getAlias($abstract);

        if (isset($this->instances[$abstract])) {
            $this->instances[$abstract] = $closure($this->instances[$abstract], $this);

            $this->rebound($abstract);
        } else {
            $this->extenders[$abstract][] = $closure;

            if ($this->resolved($abstract)) {
                $this->rebound($abstract);
            }
        }
    }

    /**
     * Register an existing instance as shared in the container.
     *
     * @param  string  $abstract
     * @param  mixed  $instance
     * @return mixed
     */
    public function instance($abstract, $instance)
    {
        $this->removeAbstractAlias($abstract);

        $isBound = $this->bound($abstract);

        unset($this->aliases[$abstract]);

        // We'll check to determine if this type has been bound before, and if it has
        // we will fire the rebound callbacks registered with the container and it
        // can be updated with consuming classes that have gotten resolved here.
        $this->instances[$abstract] = $instance;

        if ($isBound) {
            $this->rebound($abstract);
        }

        return $instance;
    }

    /**
     * Remove an alias from the contextual binding alias cache.
     *
     * @param  string  $searched
     * @return void
     */
    protected function removeAbstractAlias($searched)
    {
        if (! isset($this->aliases[$searched])) {
            return;
        }

        foreach ($this->abstractAliases as $abstract => $aliases) {
            foreach ($aliases as $index => $alias) {
                if ($alias == $searched) {
                    unset($this->abstractAliases[$abstract][$index]);
                }
            }
        }
    }

    /**
     * Assign a set of tags to a given binding.
     *
     * @param  array|string  $abstracts
     * @param  array|mixed  ...$tags
     * @return void
     */
    public function tag($abstracts, $tags)
    {
        $tags = is_array($tags) ? $tags : array_slice(func_get_args(), 1);

        foreach ($tags as $tag) {
            if (! isset($this->tags[$tag])) {
                $this->tags[$tag] = [];
            }

            foreach ((array) $abstracts as $abstract) {
                $this->tags[$tag][] = $abstract;
            }
        }
    }

    /**
     * Resolve all of the bindings for a given tag.
     *
     * @param  string  $tag
     * @return iterable
     */
    public function tagged($tag)
    {
        if (! isset($this->tags[$tag])) {
            return [];
        }

        return new RewindableGenerator(function () use ($tag) {
            foreach ($this->tags[$tag] as $abstract) {
                yield $this->make($abstract);
            }
        }, count($this->tags[$tag]));
    }

    /**
     * Alias a type to a different name.
     *
     * @param  string  $abstract
     * @param  string  $alias
     * @return void
     *
     * @throws \LogicException
     */
    public function alias($abstract, $alias)
    {
        if ($alias === $abstract) {
            throw new LogicException("[{$abstract}] is aliased to itself.");
        }

        $this->aliases[$alias] = $abstract;

        $this->abstractAliases[$abstract][] = $alias;
    }

    /**
     * Bind a new callback to an abstract's rebind event.
     *
     * @param  string  $abstract
     * @param  \Closure  $callback
     * @return mixed
     */
    public function rebinding($abstract, Closure $callback)
    {
        $this->reboundCallbacks[$abstract = $this->getAlias($abstract)][] = $callback;

        if ($this->bound($abstract)) {
            return $this->make($abstract);
        }
    }

    /**
     * Refresh an instance on the given target and method.
     *
     * @param  string  $abstract
     * @param  mixed  $target
     * @param  string  $method
     * @return mixed
     */
    public function refresh($abstract, $target, $method)
    {
        return $this->rebinding($abstract, function ($app, $instance) use ($target, $method) {
            $target->{$method}($instance);
        });
    }

    /**
     * Fire the "rebound" callbacks for the given abstract type.
     *
     * @param  string  $abstract
     * @return void
     */
    protected function rebound($abstract)
    {
        $instance = $this->make($abstract);

        foreach ($this->getReboundCallbacks($abstract) as $callback) {
            $callback($this, $instance);
        }
    }

    /**
     * Get the rebound callbacks for a given type.
     *
     * @param  string  $abstract
     * @return array
     */
    protected function getReboundCallbacks($abstract)
    {
        return $this->reboundCallbacks[$abstract] ?? [];
    }

    /**
     * Wrap the given closure such that its dependencies will be injected when executed.
     *
     * @param  \Closure  $callback
     * @param  array  $parameters
     * @return \Closure
     */
    public function wrap(Closure $callback, array $parameters = [])
    {
        return fn () => $this->call($callback, $parameters);
    }

    /**
     * Call the given Closure / class@method and inject its dependencies.
     *
     * @param  callable|string  $callback
     * @param  array<string, mixed>  $parameters
     * @param  string|null  $defaultMethod
     * @return mixed
     *
     * @throws \InvalidArgumentException
     */
    public function call($callback, array $parameters = [], $defaultMethod = null)
    {
        $pushedToBuildStack = false;

        if (($className = $this->getClassForCallable($callback)) && ! in_array(
            $className,
            $this->buildStack,
            true
        )) {
            $this->buildStack[] = $className;

            $pushedToBuildStack = true;
        }

        $result = BoundMethod::call($this, $callback, $parameters, $defaultMethod);

        if ($pushedToBuildStack) {
            array_pop($this->buildStack);
        }

        return $result;
    }

    /**
     * Get the class name for the given callback, if one can be determined.
     *
     * @param  callable|string  $callback
     * @return string|false
     */
    protected function getClassForCallable($callback)
    {
        if (PHP_VERSION_ID >= 80200) {
            if (is_callable($callback) &&
                ! ($reflector = new ReflectionFunction($callback(...)))->isAnonymous()) {
                return $reflector->getClosureScopeClass()->name ?? false;
            }

            return false;
        }

        if (! is_array($callback)) {
            return false;
        }

        return is_string($callback[0]) ? $callback[0] : get_class($callback[0]);
    }

    /**
     * Get a closure to resolve the given type from the container.
     *
     * @param  string  $abstract
     * @return \Closure
     */
    public function factory($abstract)
    {
        return fn () => $this->make($abstract);
    }

    /**
     * An alias function name for make().
     *
     * @param  string|callable  $abstract
     * @param  array  $parameters
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    public function makeWith($abstract, array $parameters = [])
    {
        return $this->make($abstract, $parameters);
    }

    /**
     * Resolve the given type from the container.
     *
     * @param  string|callable  $abstract
     * @param  array  $parameters
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    public function make($abstract, array $parameters = [])
    {
        return $this->resolve($abstract, $parameters);
    }

    /**
     * {@inheritdoc}
     *
     * @return mixed
     */
    public function get(string $id)
    {
        try {
            return $this->resolve($id);
        } catch (Exception $e) {
            if ($this->has($id) || $e instanceof CircularDependencyException) {
                throw $e;
            }

            throw new EntryNotFoundException($id, is_int($e->getCode()) ? $e->getCode() : 0, $e);
        }
    }

    /**
     * Resolve the given type from the container.
     *
     * @param  string|callable  $abstract
     * @param  array  $parameters
     * @param  bool  $raiseEvents
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     * @throws \Illuminate\Contracts\Container\CircularDependencyException
     */
    protected function resolve($abstract, $parameters = [], $raiseEvents = true)
    {
        $abstract = $this->getAlias($abstract);

        // First we'll fire any event handlers which handle the "before" resolving of
        // specific types. This gives some hooks the chance to add various extends
        // calls to change the resolution of objects that they're interested in.
        if ($raiseEvents) {
            $this->fireBeforeResolvingCallbacks($abstract, $parameters);
        }

        $concrete = $this->getContextualConcrete($abstract);

        $needsContextualBuild = ! empty($parameters) || ! is_null($concrete);

        // If an instance of the type is currently being managed as a singleton we'll
        // just return an existing instance instead of instantiating new instances
        // so the developer can keep using the same objects instance every time.
        if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
            return $this->instances[$abstract];
        }

        $this->with[] = $parameters;

        if (is_null($concrete)) {
            $concrete = $this->getConcrete($abstract);
        }

        // We're ready to instantiate an instance of the concrete type registered for
        // the binding. This will instantiate the types, as well as resolve any of
        // its "nested" dependencies recursively until all have gotten resolved.
        $object = $this->isBuildable($concrete, $abstract)
            ? $this->build($concrete)
            : $this->make($concrete);

        // If we defined any extenders for this type, we'll need to spin through them
        // and apply them to the object being built. This allows for the extension
        // of services, such as changing configuration or decorating the object.
        foreach ($this->getExtenders($abstract) as $extender) {
            $object = $extender($object, $this);
        }

        // If the requested type is registered as a singleton we'll want to cache off
        // the instances in "memory" so we can return it later without creating an
        // entirely new instance of an object on each subsequent request for it.
        if ($this->isShared($abstract) && ! $needsContextualBuild) {
            $this->instances[$abstract] = $object;
        }

        if ($raiseEvents) {
            $this->fireResolvingCallbacks($abstract, $object);
        }

        // Before returning, we will also set the resolved flag to "true" and pop off
        // the parameter overrides for this build. After those two things are done
        // we will be ready to return back the fully constructed class instance.
        $this->resolved[$abstract] = true;

        array_pop($this->with);

        return $object;
    }

    /**
     * Get the concrete type for a given abstract.
     *
     * @param  string|callable  $abstract
     * @return mixed
     */
    protected function getConcrete($abstract)
    {
        // If we don't have a registered resolver or concrete for the type, we'll just
        // assume each type is a concrete name and will attempt to resolve it as is
        // since the container should be able to resolve concretes automatically.
        if (isset($this->bindings[$abstract])) {
            return $this->bindings[$abstract]['concrete'];
        }

        return $abstract;
    }

    /**
     * Get the contextual concrete binding for the given abstract.
     *
     * @param  string|callable  $abstract
     * @return \Closure|string|array|null
     */
    protected function getContextualConcrete($abstract)
    {
        if (! is_null($binding = $this->findInContextualBindings($abstract))) {
            return $binding;
        }

        // Next we need to see if a contextual binding might be bound under an alias of the
        // given abstract type. So, we will need to check if any aliases exist with this
        // type and then spin through them and check for contextual bindings on these.
        if (empty($this->abstractAliases[$abstract])) {
            return;
        }

        foreach ($this->abstractAliases[$abstract] as $alias) {
            if (! is_null($binding = $this->findInContextualBindings($alias))) {
                return $binding;
            }
        }
    }

    /**
     * Find the concrete binding for the given abstract in the contextual binding array.
     *
     * @param  string|callable  $abstract
     * @return \Closure|string|null
     */
    protected function findInContextualBindings($abstract)
    {
        return $this->contextual[end($this->buildStack)][$abstract] ?? null;
    }

    /**
     * Determine if the given concrete is buildable.
     *
     * @param  mixed  $concrete
     * @param  string  $abstract
     * @return bool
     */
    protected function isBuildable($concrete, $abstract)
    {
        return $concrete === $abstract || $concrete instanceof Closure;
    }

    /**
     * Instantiate a concrete instance of the given type.
     *
     * @param  \Closure|string  $concrete
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     * @throws \Illuminate\Contracts\Container\CircularDependencyException
     */
    public function build($concrete)
    {
        // If the concrete type is actually a Closure, we will just execute it and
        // hand back the results of the functions, which allows functions to be
        // used as resolvers for more fine-tuned resolution of these objects.
        if ($concrete instanceof Closure) {
            return $concrete($this, $this->getLastParameterOverride());
        }

        try {
            $reflector = new ReflectionClass($concrete);
        } catch (ReflectionException $e) {
            throw new BindingResolutionException("Target class [$concrete] does not exist.", 0, $e);
        }

        // If the type is not instantiable, the developer is attempting to resolve
        // an abstract type such as an Interface or Abstract Class and there is
        // no binding registered for the abstractions so we need to bail out.
        if (! $reflector->isInstantiable()) {
            return $this->notInstantiable($concrete);
        }

        $this->buildStack[] = $concrete;

        $constructor = $reflector->getConstructor();

        // If there are no constructors, that means there are no dependencies then
        // we can just resolve the instances of the objects right away, without
        // resolving any other types or dependencies out of these containers.
        if (is_null($constructor)) {
            array_pop($this->buildStack);

            return new $concrete;
        }

        $dependencies = $constructor->getParameters();

        // Once we have all the constructor's parameters we can create each of the
        // dependency instances and then use the reflection instances to make a
        // new instance of this class, injecting the created dependencies in.
        try {
            $instances = $this->resolveDependencies($dependencies);
        } catch (BindingResolutionException $e) {
            array_pop($this->buildStack);

            throw $e;
        }

        array_pop($this->buildStack);

        return $reflector->newInstanceArgs($instances);
    }

    /**
     * Resolve all of the dependencies from the ReflectionParameters.
     *
     * @param  \ReflectionParameter[]  $dependencies
     * @return array
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    protected function resolveDependencies(array $dependencies)
    {
        $results = [];

        foreach ($dependencies as $dependency) {
            // If the dependency has an override for this particular build we will use
            // that instead as the value. Otherwise, we will continue with this run
            // of resolutions and let reflection attempt to determine the result.
            if ($this->hasParameterOverride($dependency)) {
                $results[] = $this->getParameterOverride($dependency);

                continue;
            }

            // If the class is null, it means the dependency is a string or some other
            // primitive type which we can not resolve since it is not a class and
            // we will just bomb out with an error since we have no-where to go.
            $result = is_null(Util::getParameterClassName($dependency))
                            ? $this->resolvePrimitive($dependency)
                            : $this->resolveClass($dependency);

            if ($dependency->isVariadic()) {
                $results = array_merge($results, $result);
            } else {
                $results[] = $result;
            }
        }

        return $results;
    }

    /**
     * Determine if the given dependency has a parameter override.
     *
     * @param  \ReflectionParameter  $dependency
     * @return bool
     */
    protected function hasParameterOverride($dependency)
    {
        return array_key_exists(
            $dependency->name, $this->getLastParameterOverride()
        );
    }

    /**
     * Get a parameter override for a dependency.
     *
     * @param  \ReflectionParameter  $dependency
     * @return mixed
     */
    protected function getParameterOverride($dependency)
    {
        return $this->getLastParameterOverride()[$dependency->name];
    }

    /**
     * Get the last parameter override.
     *
     * @return array
     */
    protected function getLastParameterOverride()
    {
        return count($this->with) ? end($this->with) : [];
    }

    /**
     * Resolve a non-class hinted primitive dependency.
     *
     * @param  \ReflectionParameter  $parameter
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    protected function resolvePrimitive(ReflectionParameter $parameter)
    {
        if (! is_null($concrete = $this->getContextualConcrete('$'.$parameter->getName()))) {
            return Util::unwrapIfClosure($concrete, $this);
        }

        if ($parameter->isDefaultValueAvailable()) {
            return $parameter->getDefaultValue();
        }

        if ($parameter->isVariadic()) {
            return [];
        }

        $this->unresolvablePrimitive($parameter);
    }

    /**
     * Resolve a class based dependency from the container.
     *
     * @param  \ReflectionParameter  $parameter
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    protected function resolveClass(ReflectionParameter $parameter)
    {
        try {
            return $parameter->isVariadic()
                        ? $this->resolveVariadicClass($parameter)
                        : $this->make(Util::getParameterClassName($parameter));
        }

        // If we can not resolve the class instance, we will check to see if the value
        // is optional, and if it is we will return the optional parameter value as
        // the value of the dependency, similarly to how we do this with scalars.
        catch (BindingResolutionException $e) {
            if ($parameter->isDefaultValueAvailable()) {
                array_pop($this->with);

                return $parameter->getDefaultValue();
            }

            if ($parameter->isVariadic()) {
                array_pop($this->with);

                return [];
            }

            throw $e;
        }
    }

    /**
     * Resolve a class based variadic dependency from the container.
     *
     * @param  \ReflectionParameter  $parameter
     * @return mixed
     */
    protected function resolveVariadicClass(ReflectionParameter $parameter)
    {
        $className = Util::getParameterClassName($parameter);

        $abstract = $this->getAlias($className);

        if (! is_array($concrete = $this->getContextualConcrete($abstract))) {
            return $this->make($className);
        }

        return array_map(fn ($abstract) => $this->resolve($abstract), $concrete);
    }

    /**
     * Throw an exception that the concrete is not instantiable.
     *
     * @param  string  $concrete
     * @return void
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    protected function notInstantiable($concrete)
    {
        if (! empty($this->buildStack)) {
            $previous = implode(', ', $this->buildStack);

            $message = "Target [$concrete] is not instantiable while building [$previous].";
        } else {
            $message = "Target [$concrete] is not instantiable.";
        }

        throw new BindingResolutionException($message);
    }

    /**
     * Throw an exception for an unresolvable primitive.
     *
     * @param  \ReflectionParameter  $parameter
     * @return void
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    protected function unresolvablePrimitive(ReflectionParameter $parameter)
    {
        $message = "Unresolvable dependency resolving [$parameter] in class {$parameter->getDeclaringClass()->getName()}";

        throw new BindingResolutionException($message);
    }

    /**
     * Register a new before resolving callback for all types.
     *
     * @param  \Closure|string  $abstract
     * @param  \Closure|null  $callback
     * @return void
     */
    public function beforeResolving($abstract, ?Closure $callback = null)
    {
        if (is_string($abstract)) {
            $abstract = $this->getAlias($abstract);
        }

        if ($abstract instanceof Closure && is_null($callback)) {
            $this->globalBeforeResolvingCallbacks[] = $abstract;
        } else {
            $this->beforeResolvingCallbacks[$abstract][] = $callback;
        }
    }

    /**
     * Register a new resolving callback.
     *
     * @param  \Closure|string  $abstract
     * @param  \Closure|null  $callback
     * @return void
     */
    public function resolving($abstract, ?Closure $callback = null)
    {
        if (is_string($abstract)) {
            $abstract = $this->getAlias($abstract);
        }

        if (is_null($callback) && $abstract instanceof Closure) {
            $this->globalResolvingCallbacks[] = $abstract;
        } else {
            $this->resolvingCallbacks[$abstract][] = $callback;
        }
    }

    /**
     * Register a new after resolving callback for all types.
     *
     * @param  \Closure|string  $abstract
     * @param  \Closure|null  $callback
     * @return void
     */
    public function afterResolving($abstract, ?Closure $callback = null)
    {
        if (is_string($abstract)) {
            $abstract = $this->getAlias($abstract);
        }

        if ($abstract instanceof Closure && is_null($callback)) {
            $this->globalAfterResolvingCallbacks[] = $abstract;
        } else {
            $this->afterResolvingCallbacks[$abstract][] = $callback;
        }
    }

    /**
     * Fire all of the before resolving callbacks.
     *
     * @param  string  $abstract
     * @param  array  $parameters
     * @return void
     */
    protected function fireBeforeResolvingCallbacks($abstract, $parameters = [])
    {
        $this->fireBeforeCallbackArray($abstract, $parameters, $this->globalBeforeResolvingCallbacks);

        foreach ($this->beforeResolvingCallbacks as $type => $callbacks) {
            if ($type === $abstract || is_subclass_of($abstract, $type)) {
                $this->fireBeforeCallbackArray($abstract, $parameters, $callbacks);
            }
        }
    }

    /**
     * Fire an array of callbacks with an object.
     *
     * @param  string  $abstract
     * @param  array  $parameters
     * @param  array  $callbacks
     * @return void
     */
    protected function fireBeforeCallbackArray($abstract, $parameters, array $callbacks)
    {
        foreach ($callbacks as $callback) {
            $callback($abstract, $parameters, $this);
        }
    }

    /**
     * Fire all of the resolving callbacks.
     *
     * @param  string  $abstract
     * @param  mixed  $object
     * @return void
     */
    protected function fireResolvingCallbacks($abstract, $object)
    {
        $this->fireCallbackArray($object, $this->globalResolvingCallbacks);

        $this->fireCallbackArray(
            $object, $this->getCallbacksForType($abstract, $object, $this->resolvingCallbacks)
        );

        $this->fireAfterResolvingCallbacks($abstract, $object);
    }

    /**
     * Fire all of the after resolving callbacks.
     *
     * @param  string  $abstract
     * @param  mixed  $object
     * @return void
     */
    protected function fireAfterResolvingCallbacks($abstract, $object)
    {
        $this->fireCallbackArray($object, $this->globalAfterResolvingCallbacks);

        $this->fireCallbackArray(
            $object, $this->getCallbacksForType($abstract, $object, $this->afterResolvingCallbacks)
        );
    }

    /**
     * Get all callbacks for a given type.
     *
     * @param  string  $abstract
     * @param  object  $object
     * @param  array  $callbacksPerType
     * @return array
     */
    protected function getCallbacksForType($abstract, $object, array $callbacksPerType)
    {
        $results = [];

        foreach ($callbacksPerType as $type => $callbacks) {
            if ($type === $abstract || $object instanceof $type) {
                $results = array_merge($results, $callbacks);
            }
        }

        return $results;
    }

    /**
     * Fire an array of callbacks with an object.
     *
     * @param  mixed  $object
     * @param  array  $callbacks
     * @return void
     */
    protected function fireCallbackArray($object, array $callbacks)
    {
        foreach ($callbacks as $callback) {
            $callback($object, $this);
        }
    }

    /**
     * Get the container's bindings.
     *
     * @return array
     */
    public function getBindings()
    {
        return $this->bindings;
    }

    /**
     * Get the alias for an abstract if available.
     *
     * @param  string  $abstract
     * @return string
     */
    public function getAlias($abstract)
    {
        return isset($this->aliases[$abstract])
                    ? $this->getAlias($this->aliases[$abstract])
                    : $abstract;
    }

    /**
     * Get the extender callbacks for a given type.
     *
     * @param  string  $abstract
     * @return array
     */
    protected function getExtenders($abstract)
    {
        return $this->extenders[$this->getAlias($abstract)] ?? [];
    }

    /**
     * Remove all of the extender callbacks for a given type.
     *
     * @param  string  $abstract
     * @return void
     */
    public function forgetExtenders($abstract)
    {
        unset($this->extenders[$this->getAlias($abstract)]);
    }

    /**
     * Drop all of the stale instances and aliases.
     *
     * @param  string  $abstract
     * @return void
     */
    protected function dropStaleInstances($abstract)
    {
        unset($this->instances[$abstract], $this->aliases[$abstract]);
    }

    /**
     * Remove a resolved instance from the instance cache.
     *
     * @param  string  $abstract
     * @return void
     */
    public function forgetInstance($abstract)
    {
        unset($this->instances[$abstract]);
    }

    /**
     * Clear all of the instances from the container.
     *
     * @return void
     */
    public function forgetInstances()
    {
        $this->instances = [];
    }

    /**
     * Clear all of the scoped instances from the container.
     *
     * @return void
     */
    public function forgetScopedInstances()
    {
        foreach ($this->scopedInstances as $scoped) {
            unset($this->instances[$scoped]);
        }
    }

    /**
     * Flush the container of all bindings and resolved instances.
     *
     * @return void
     */
    public function flush()
    {
        $this->aliases = [];
        $this->resolved = [];
        $this->bindings = [];
        $this->instances = [];
        $this->abstractAliases = [];
        $this->scopedInstances = [];
    }

    /**
     * Get the globally available instance of the container.
     *
     * @return static
     */
    public static function getInstance()
    {
        if (is_null(static::$instance)) {
            static::$instance = new static;
        }

        return static::$instance;
    }

    /**
     * Set the shared instance of the container.
     *
     * @param  \Illuminate\Contracts\Container\Container|null  $container
     * @return \Illuminate\Contracts\Container\Container|static
     */
    public static function setInstance(?ContainerContract $container = null)
    {
        return static::$instance = $container;
    }

    /**
     * Determine if a given offset exists.
     *
     * @param  string  $key
     * @return bool
     */
    public function offsetExists($key): bool
    {
        return $this->bound($key);
    }

    /**
     * Get the value at a given offset.
     *
     * @param  string  $key
     * @return mixed
     */
    public function offsetGet($key): mixed
    {
        return $this->make($key);
    }

    /**
     * Set the value at a given offset.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return void
     */
    public function offsetSet($key, $value): void
    {
        $this->bind($key, $value instanceof Closure ? $value : fn () => $value);
    }

    /**
     * Unset the value at a given offset.
     *
     * @param  string  $key
     * @return void
     */
    public function offsetUnset($key): void
    {
        unset($this->bindings[$key], $this->instances[$key], $this->resolved[$key]);
    }

    /**
     * Dynamically access container services.
     *
     * @param  string  $key
     * @return mixed
     */
    public function __get($key)
    {
        return $this[$key];
    }

    /**
     * Dynamically set container services.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return void
     */
    public function __set($key, $value)
    {
        $this[$key] = $value;
    }
}
<?php

namespace Illuminate\Container;

use Countable;
use IteratorAggregate;
use Traversable;

class RewindableGenerator implements Countable, IteratorAggregate
{
    /**
     * The generator callback.
     *
     * @var callable
     */
    protected $generator;

    /**
     * The number of tagged services.
     *
     * @var callable|int
     */
    protected $count;

    /**
     * Create a new generator instance.
     *
     * @param  callable  $generator
     * @param  callable|int  $count
     * @return void
     */
    public function __construct(callable $generator, $count)
    {
        $this->count = $count;
        $this->generator = $generator;
    }

    /**
     * Get an iterator from the generator.
     *
     * @return \Traversable
     */
    public function getIterator(): Traversable
    {
        return ($this->generator)();
    }

    /**
     * Get the total number of tagged services.
     *
     * @return int
     */
    public function count(): int
    {
        if (is_callable($count = $this->count)) {
            $this->count = $count();
        }

        return $this->count;
    }
}
<?php

namespace Illuminate\Container;

use Exception;
use Psr\Container\NotFoundExceptionInterface;

class EntryNotFoundException extends Exception implements NotFoundExceptionInterface
{
    //
}
<?php

namespace Illuminate\Container;

use Closure;
use ReflectionNamedType;

/**
 * @internal
 */
class Util
{
    /**
     * If the given value is not an array and not null, wrap it in one.
     *
     * From Arr::wrap() in Illuminate\Support.
     *
     * @param  mixed  $value
     * @return array
     */
    public static function arrayWrap($value)
    {
        if (is_null($value)) {
            return [];
        }

        return is_array($value) ? $value : [$value];
    }

    /**
     * Return the default value of the given value.
     *
     * From global value() helper in Illuminate\Support.
     *
     * @param  mixed  $value
     * @param  mixed  ...$args
     * @return mixed
     */
    public static function unwrapIfClosure($value, ...$args)
    {
        return $value instanceof Closure ? $value(...$args) : $value;
    }

    /**
     * Get the class name of the given parameter's type, if possible.
     *
     * From Reflector::getParameterClassName() in Illuminate\Support.
     *
     * @param  \ReflectionParameter  $parameter
     * @return string|null
     */
    public static function getParameterClassName($parameter)
    {
        $type = $parameter->getType();

        if (! $type instanceof ReflectionNamedType || $type->isBuiltin()) {
            return null;
        }

        $name = $type->getName();

        if (! is_null($class = $parameter->getDeclaringClass())) {
            if ($name === 'self') {
                return $class->getName();
            }

            if ($name === 'parent' && $parent = $class->getParentClass()) {
                return $parent->getName();
            }
        }

        return $name;
    }
}
<?php

namespace Illuminate\Container;

use Closure;
use Illuminate\Contracts\Container\BindingResolutionException;
use InvalidArgumentException;
use ReflectionFunction;
use ReflectionMethod;

class BoundMethod
{
    /**
     * Call the given Closure / class@method and inject its dependencies.
     *
     * @param  \Illuminate\Container\Container  $container
     * @param  callable|string  $callback
     * @param  array  $parameters
     * @param  string|null  $defaultMethod
     * @return mixed
     *
     * @throws \ReflectionException
     * @throws \InvalidArgumentException
     */
    public static function call($container, $callback, array $parameters = [], $defaultMethod = null)
    {
        if (is_string($callback) && ! $defaultMethod && method_exists($callback, '__invoke')) {
            $defaultMethod = '__invoke';
        }

        if (static::isCallableWithAtSign($callback) || $defaultMethod) {
            return static::callClass($container, $callback, $parameters, $defaultMethod);
        }

        return static::callBoundMethod($container, $callback, function () use ($container, $callback, $parameters) {
            return $callback(...array_values(static::getMethodDependencies($container, $callback, $parameters)));
        });
    }

    /**
     * Call a string reference to a class using Class@method syntax.
     *
     * @param  \Illuminate\Container\Container  $container
     * @param  string  $target
     * @param  array  $parameters
     * @param  string|null  $defaultMethod
     * @return mixed
     *
     * @throws \InvalidArgumentException
     */
    protected static function callClass($container, $target, array $parameters = [], $defaultMethod = null)
    {
        $segments = explode('@', $target);

        // We will assume an @ sign is used to delimit the class name from the method
        // name. We will split on this @ sign and then build a callable array that
        // we can pass right back into the "call" method for dependency binding.
        $method = count($segments) === 2
                        ? $segments[1] : $defaultMethod;

        if (is_null($method)) {
            throw new InvalidArgumentException('Method not provided.');
        }

        return static::call(
            $container, [$container->make($segments[0]), $method], $parameters
        );
    }

    /**
     * Call a method that has been bound to the container.
     *
     * @param  \Illuminate\Container\Container  $container
     * @param  callable  $callback
     * @param  mixed  $default
     * @return mixed
     */
    protected static function callBoundMethod($container, $callback, $default)
    {
        if (! is_array($callback)) {
            return Util::unwrapIfClosure($default);
        }

        // Here we need to turn the array callable into a Class@method string we can use to
        // examine the container and see if there are any method bindings for this given
        // method. If there are, we can call this method binding callback immediately.
        $method = static::normalizeMethod($callback);

        if ($container->hasMethodBinding($method)) {
            return $container->callMethodBinding($method, $callback[0]);
        }

        return Util::unwrapIfClosure($default);
    }

    /**
     * Normalize the given callback into a Class@method string.
     *
     * @param  callable  $callback
     * @return string
     */
    protected static function normalizeMethod($callback)
    {
        $class = is_string($callback[0]) ? $callback[0] : get_class($callback[0]);

        return "{$class}@{$callback[1]}";
    }

    /**
     * Get all dependencies for a given method.
     *
     * @param  \Illuminate\Container\Container  $container
     * @param  callable|string  $callback
     * @param  array  $parameters
     * @return array
     *
     * @throws \ReflectionException
     */
    protected static function getMethodDependencies($container, $callback, array $parameters = [])
    {
        $dependencies = [];

        foreach (static::getCallReflector($callback)->getParameters() as $parameter) {
            static::addDependencyForCallParameter($container, $parameter, $parameters, $dependencies);
        }

        return array_merge($dependencies, array_values($parameters));
    }

    /**
     * Get the proper reflection instance for the given callback.
     *
     * @param  callable|string  $callback
     * @return \ReflectionFunctionAbstract
     *
     * @throws \ReflectionException
     */
    protected static function getCallReflector($callback)
    {
        if (is_string($callback) && str_contains($callback, '::')) {
            $callback = explode('::', $callback);
        } elseif (is_object($callback) && ! $callback instanceof Closure) {
            $callback = [$callback, '__invoke'];
        }

        return is_array($callback)
                        ? new ReflectionMethod($callback[0], $callback[1])
                        : new ReflectionFunction($callback);
    }

    /**
     * Get the dependency for the given call parameter.
     *
     * @param  \Illuminate\Container\Container  $container
     * @param  \ReflectionParameter  $parameter
     * @param  array  $parameters
     * @param  array  $dependencies
     * @return void
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    protected static function addDependencyForCallParameter($container, $parameter,
                                                            array &$parameters, &$dependencies)
    {
        if (array_key_exists($paramName = $parameter->getName(), $parameters)) {
            $dependencies[] = $parameters[$paramName];

            unset($parameters[$paramName]);
        } elseif (! is_null($className = Util::getParameterClassName($parameter))) {
            if (array_key_exists($className, $parameters)) {
                $dependencies[] = $parameters[$className];

                unset($parameters[$className]);
            } elseif ($parameter->isVariadic()) {
                $variadicDependencies = $container->make($className);

                $dependencies = array_merge($dependencies, is_array($variadicDependencies)
                            ? $variadicDependencies
                            : [$variadicDependencies]);
            } else {
                $dependencies[] = $container->make($className);
            }
        } elseif ($parameter->isDefaultValueAvailable()) {
            $dependencies[] = $parameter->getDefaultValue();
        } elseif (! $parameter->isOptional() && ! array_key_exists($paramName, $parameters)) {
            $message = "Unable to resolve dependency [{$parameter}] in class {$parameter->getDeclaringClass()->getName()}";

            throw new BindingResolutionException($message);
        }
    }

    /**
     * Determine if the given string is in Class@method syntax.
     *
     * @param  mixed  $callback
     * @return bool
     */
    protected static function isCallableWithAtSign($callback)
    {
        return is_string($callback) && str_contains($callback, '@');
    }
}
<?php

namespace Illuminate\Container;

use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Container\ContextualBindingBuilder as ContextualBindingBuilderContract;

class ContextualBindingBuilder implements ContextualBindingBuilderContract
{
    /**
     * The underlying container instance.
     *
     * @var \Illuminate\Contracts\Container\Container
     */
    protected $container;

    /**
     * The concrete instance.
     *
     * @var string|array
     */
    protected $concrete;

    /**
     * The abstract target.
     *
     * @var string
     */
    protected $needs;

    /**
     * Create a new contextual binding builder.
     *
     * @param  \Illuminate\Contracts\Container\Container  $container
     * @param  string|array  $concrete
     * @return void
     */
    public function __construct(Container $container, $concrete)
    {
        $this->concrete = $concrete;
        $this->container = $container;
    }

    /**
     * Define the abstract target that depends on the context.
     *
     * @param  string  $abstract
     * @return $this
     */
    public function needs($abstract)
    {
        $this->needs = $abstract;

        return $this;
    }

    /**
     * Define the implementation for the contextual binding.
     *
     * @param  \Closure|string|array  $implementation
     * @return void
     */
    public function give($implementation)
    {
        foreach (Util::arrayWrap($this->concrete) as $concrete) {
            $this->container->addContextualBinding($concrete, $this->needs, $implementation);
        }
    }

    /**
     * Define tagged services to be used as the implementation for the contextual binding.
     *
     * @param  string  $tag
     * @return void
     */
    public function giveTagged($tag)
    {
        $this->give(function ($container) use ($tag) {
            $taggedServices = $container->tagged($tag);

            return is_array($taggedServices) ? $taggedServices : iterator_to_array($taggedServices);
        });
    }

    /**
     * Specify the configuration item to bind as a primitive.
     *
     * @param  string  $key
     * @param  mixed  $default
     * @return void
     */
    public function giveConfig($key, $default = null)
    {
        $this->give(fn ($container) => $container->get('config')->get($key, $default));
    }
}
<?php

namespace Illuminate\Contracts\Support;

interface CanBeEscapedWhenCastToString
{
    /**
     * Indicate that the object's string representation should be escaped when __toString is invoked.
     *
     * @param  bool  $escape
     * @return $this
     */
    public function escapeWhenCastingToString($escape = true);
}
<?php

namespace Illuminate\Contracts\Support;

interface Responsable
{
    /**
     * Create an HTTP response that represents the object.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function toResponse($request);
}
<?php

namespace Illuminate\Contracts\Support;

use ArrayAccess;
use IteratorAggregate;

interface ValidatedData extends Arrayable, ArrayAccess, IteratorAggregate
{
    //
}
<?php

namespace Illuminate\Contracts\Support;

interface MessageProvider
{
    /**
     * Get the messages for the instance.
     *
     * @return \Illuminate\Contracts\Support\MessageBag
     */
    public function getMessageBag();
}
<?php

namespace Illuminate\Contracts\Support;

interface Htmlable
{
    /**
     * Get content as a string of HTML.
     *
     * @return string
     */
    public function toHtml();
}
<?php

namespace Illuminate\Contracts\Support;

/**
 * @template TKey of array-key
 * @template TValue
 */
interface Arrayable
{
    /**
     * Get the instance as an array.
     *
     * @return array<TKey, TValue>
     */
    public function toArray();
}
<?php

namespace Illuminate\Contracts\Support;

use Countable;

interface MessageBag extends Arrayable, Countable
{
    /**
     * Get the keys present in the message bag.
     *
     * @return array
     */
    public function keys();

    /**
     * Add a message to the bag.
     *
     * @param  string  $key
     * @param  string  $message
     * @return $this
     */
    public function add($key, $message);

    /**
     * Merge a new array of messages into the bag.
     *
     * @param  \Illuminate\Contracts\Support\MessageProvider|array  $messages
     * @return $this
     */
    public function merge($messages);

    /**
     * Determine if messages exist for a given key.
     *
     * @param  string|array  $key
     * @return bool
     */
    public function has($key);

    /**
     * Get the first message from the bag for a given key.
     *
     * @param  string|null  $key
     * @param  string|null  $format
     * @return string
     */
    public function first($key = null, $format = null);

    /**
     * Get all of the messages from the bag for a given key.
     *
     * @param  string  $key
     * @param  string|null  $format
     * @return array
     */
    public function get($key, $format = null);

    /**
     * Get all of the messages for every key in the bag.
     *
     * @param  string|null  $format
     * @return array
     */
    public function all($format = null);

    /**
     * Remove a message from the bag.
     *
     * @param  string  $key
     * @return $this
     */
    public function forget($key);

    /**
     * Get the raw messages in the container.
     *
     * @return array
     */
    public function getMessages();

    /**
     * Get the default message format.
     *
     * @return string
     */
    public function getFormat();

    /**
     * Set the default message format.
     *
     * @param  string  $format
     * @return $this
     */
    public function setFormat($format = ':message');

    /**
     * Determine if the message bag has any messages.
     *
     * @return bool
     */
    public function isEmpty();

    /**
     * Determine if the message bag has any messages.
     *
     * @return bool
     */
    public function isNotEmpty();
}
<?php

namespace Illuminate\Contracts\Support;

interface Renderable
{
    /**
     * Get the evaluated contents of the object.
     *
     * @return string
     */
    public function render();
}
<?php

namespace Illuminate\Contracts\Support;

interface DeferrableProvider
{
    /**
     * Get the services provided by the provider.
     *
     * @return array
     */
    public function provides();
}
<?php

namespace Illuminate\Contracts\Support;

interface Jsonable
{
    /**
     * Convert the object to its JSON representation.
     *
     * @param  int  $options
     * @return string
     */
    public function toJson($options = 0);
}
<?php

namespace Illuminate\Contracts\Support;

interface DeferringDisplayableValue
{
    /**
     * Resolve the displayable value that the class is deferring.
     *
     * @return \Illuminate\Contracts\Support\Htmlable|string
     */
    public function resolveDisplayableValue();
}
<?php

namespace Illuminate\Contracts\Notifications;

interface Factory
{
    /**
     * Get a channel instance by name.
     *
     * @param  string|null  $name
     * @return mixed
     */
    public function channel($name = null);

    /**
     * Send the given notification to the given notifiable entities.
     *
     * @param  \Illuminate\Support\Collection|array|mixed  $notifiables
     * @param  mixed  $notification
     * @return void
     */
    public function send($notifiables, $notification);

    /**
     * Send the given notification immediately.
     *
     * @param  \Illuminate\Support\Collection|array|mixed  $notifiables
     * @param  mixed  $notification
     * @return void
     */
    public function sendNow($notifiables, $notification);
}
<?php

namespace Illuminate\Contracts\Notifications;

interface Dispatcher
{
    /**
     * Send the given notification to the given notifiable entities.
     *
     * @param  \Illuminate\Support\Collection|array|mixed  $notifiables
     * @param  mixed  $notification
     * @return void
     */
    public function send($notifiables, $notification);

    /**
     * Send the given notification immediately.
     *
     * @param  \Illuminate\Support\Collection|array|mixed  $notifiables
     * @param  mixed  $notification
     * @return void
     */
    public function sendNow($notifiables, $notification);
}
<?php

namespace Illuminate\Contracts\Bus;

interface Dispatcher
{
    /**
     * Dispatch a command to its appropriate handler.
     *
     * @param  mixed  $command
     * @return mixed
     */
    public function dispatch($command);

    /**
     * Dispatch a command to its appropriate handler in the current process.
     *
     * Queueable jobs will be dispatched to the "sync" queue.
     *
     * @param  mixed  $command
     * @param  mixed  $handler
     * @return mixed
     */
    public function dispatchSync($command, $handler = null);

    /**
     * Dispatch a command to its appropriate handler in the current process.
     *
     * @param  mixed  $command
     * @param  mixed  $handler
     * @return mixed
     */
    public function dispatchNow($command, $handler = null);

    /**
     * Determine if the given command has a handler.
     *
     * @param  mixed  $command
     * @return bool
     */
    public function hasCommandHandler($command);

    /**
     * Retrieve the handler for a command.
     *
     * @param  mixed  $command
     * @return bool|mixed
     */
    public function getCommandHandler($command);

    /**
     * Set the pipes commands should be piped through before dispatching.
     *
     * @param  array  $pipes
     * @return $this
     */
    public function pipeThrough(array $pipes);

    /**
     * Map a command to a handler.
     *
     * @param  array  $map
     * @return $this
     */
    public function map(array $map);
}
<?php

namespace Illuminate\Contracts\Bus;

interface QueueingDispatcher extends Dispatcher
{
    /**
     * Attempt to find the batch with the given ID.
     *
     * @param  string  $batchId
     * @return \Illuminate\Bus\Batch|null
     */
    public function findBatch(string $batchId);

    /**
     * Create a new batch of queueable jobs.
     *
     * @param  \Illuminate\Support\Collection|array  $jobs
     * @return \Illuminate\Bus\PendingBatch
     */
    public function batch($jobs);

    /**
     * Dispatch a command to its appropriate handler behind a queue.
     *
     * @param  mixed  $command
     * @return mixed
     */
    public function dispatchToQueue($command);
}
<?php

namespace Illuminate\Contracts\Validation;

/**
 * @deprecated see ValidationRule
 */
interface Rule
{
    /**
     * Determine if the validation rule passes.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @return bool
     */
    public function passes($attribute, $value);

    /**
     * Get the validation error message.
     *
     * @return string|array
     */
    public function message();
}
<?php

namespace Illuminate\Contracts\Validation;

use Closure;

interface ValidationRule
{
    /**
     * Run the validation rule.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @param  \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString  $fail
     * @return void
     */
    public function validate(string $attribute, mixed $value, Closure $fail): void;
}
<?php

namespace Illuminate\Contracts\Validation;

interface Factory
{
    /**
     * Create a new Validator instance.
     *
     * @param  array  $data
     * @param  array  $rules
     * @param  array  $messages
     * @param  array  $attributes
     * @return \Illuminate\Contracts\Validation\Validator
     */
    public function make(array $data, array $rules, array $messages = [], array $attributes = []);

    /**
     * Register a custom validator extension.
     *
     * @param  string  $rule
     * @param  \Closure|string  $extension
     * @param  string|null  $message
     * @return void
     */
    public function extend($rule, $extension, $message = null);

    /**
     * Register a custom implicit validator extension.
     *
     * @param  string  $rule
     * @param  \Closure|string  $extension
     * @param  string|null  $message
     * @return void
     */
    public function extendImplicit($rule, $extension, $message = null);

    /**
     * Register a custom implicit validator message replacer.
     *
     * @param  string  $rule
     * @param  \Closure|string  $replacer
     * @return void
     */
    public function replacer($rule, $replacer);
}
<?php

namespace Illuminate\Contracts\Validation;

interface DataAwareRule
{
    /**
     * Set the data under validation.
     *
     * @param  array  $data
     * @return $this
     */
    public function setData(array $data);
}
<?php

namespace Illuminate\Contracts\Validation;

interface UncompromisedVerifier
{
    /**
     * Verify that the given data has not been compromised in data leaks.
     *
     * @param  array  $data
     * @return bool
     */
    public function verify($data);
}
<?php

namespace Illuminate\Contracts\Validation;

use Illuminate\Contracts\Support\MessageProvider;

interface Validator extends MessageProvider
{
    /**
     * Run the validator's rules against its data.
     *
     * @return array
     *
     * @throws \Illuminate\Validation\ValidationException
     */
    public function validate();

    /**
     * Get the attributes and values that were validated.
     *
     * @return array
     *
     * @throws \Illuminate\Validation\ValidationException
     */
    public function validated();

    /**
     * Determine if the data fails the validation rules.
     *
     * @return bool
     */
    public function fails();

    /**
     * Get the failed validation rules.
     *
     * @return array
     */
    public function failed();

    /**
     * Add conditions to a given field based on a Closure.
     *
     * @param  string|array  $attribute
     * @param  string|array  $rules
     * @param  callable  $callback
     * @return $this
     */
    public function sometimes($attribute, $rules, callable $callback);

    /**
     * Add an after validation callback.
     *
     * @param  callable|string  $callback
     * @return $this
     */
    public function after($callback);

    /**
     * Get all of the validation error messages.
     *
     * @return \Illuminate\Support\MessageBag
     */
    public function errors();
}
<?php

namespace Illuminate\Contracts\Validation;

use Illuminate\Validation\Validator;

interface ValidatorAwareRule
{
    /**
     * Set the current validator.
     *
     * @param  \Illuminate\Validation\Validator  $validator
     * @return $this
     */
    public function setValidator(Validator $validator);
}
<?php

namespace Illuminate\Contracts\Validation;

use Closure;

/**
 * @deprecated see ValidationRule
 */
interface InvokableRule
{
    /**
     * Run the validation rule.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @param  \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString  $fail
     * @return void
     */
    public function __invoke(string $attribute, mixed $value, Closure $fail);
}
<?php

namespace Illuminate\Contracts\Validation;

interface ValidatesWhenResolved
{
    /**
     * Validate the given class instance.
     *
     * @return void
     */
    public function validateResolved();
}
<?php

namespace Illuminate\Contracts\Validation;

/**
 * @deprecated see ValidationRule
 */
interface ImplicitRule extends Rule
{
    //
}
<?php

namespace Illuminate\Contracts\Session\Middleware;

interface AuthenticatesSessions
{
    //
}
<?php

namespace Illuminate\Contracts\Session;

interface Session
{
    /**
     * Get the name of the session.
     *
     * @return string
     */
    public function getName();

    /**
     * Set the name of the session.
     *
     * @param  string  $name
     * @return void
     */
    public function setName($name);

    /**
     * Get the current session ID.
     *
     * @return string
     */
    public function getId();

    /**
     * Set the session ID.
     *
     * @param  string  $id
     * @return void
     */
    public function setId($id);

    /**
     * Start the session, reading the data from a handler.
     *
     * @return bool
     */
    public function start();

    /**
     * Save the session data to storage.
     *
     * @return void
     */
    public function save();

    /**
     * Get all of the session data.
     *
     * @return array
     */
    public function all();

    /**
     * Checks if a key exists.
     *
     * @param  string|array  $key
     * @return bool
     */
    public function exists($key);

    /**
     * Checks if a key is present and not null.
     *
     * @param  string|array  $key
     * @return bool
     */
    public function has($key);

    /**
     * Get an item from the session.
     *
     * @param  string  $key
     * @param  mixed  $default
     * @return mixed
     */
    public function get($key, $default = null);

    /**
     * Get the value of a given key and then forget it.
     *
     * @param  string  $key
     * @param  mixed  $default
     * @return mixed
     */
    public function pull($key, $default = null);

    /**
     * Put a key / value pair or array of key / value pairs in the session.
     *
     * @param  string|array  $key
     * @param  mixed  $value
     * @return void
     */
    public function put($key, $value = null);

    /**
     * Get the CSRF token value.
     *
     * @return string
     */
    public function token();

    /**
     * Regenerate the CSRF token value.
     *
     * @return void
     */
    public function regenerateToken();

    /**
     * Remove an item from the session, returning its value.
     *
     * @param  string  $key
     * @return mixed
     */
    public function remove($key);

    /**
     * Remove one or many items from the session.
     *
     * @param  string|array  $keys
     * @return void
     */
    public function forget($keys);

    /**
     * Remove all of the items from the session.
     *
     * @return void
     */
    public function flush();

    /**
     * Flush the session data and regenerate the ID.
     *
     * @return bool
     */
    public function invalidate();

    /**
     * Generate a new session identifier.
     *
     * @param  bool  $destroy
     * @return bool
     */
    public function regenerate($destroy = false);

    /**
     * Generate a new session ID for the session.
     *
     * @param  bool  $destroy
     * @return bool
     */
    public function migrate($destroy = false);

    /**
     * Determine if the session has been started.
     *
     * @return bool
     */
    public function isStarted();

    /**
     * Get the previous URL from the session.
     *
     * @return string|null
     */
    public function previousUrl();

    /**
     * Set the "previous" URL in the session.
     *
     * @param  string  $url
     * @return void
     */
    public function setPreviousUrl($url);

    /**
     * Get the session handler instance.
     *
     * @return \SessionHandlerInterface
     */
    public function getHandler();

    /**
     * Determine if the session handler needs a request.
     *
     * @return bool
     */
    public function handlerNeedsRequest();

    /**
     * Set the request on the handler instance.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return void
     */
    public function setRequestOnHandler($request);
}
<?php

namespace Illuminate\Contracts\Config;

interface Repository
{
    /**
     * Determine if the given configuration value exists.
     *
     * @param  string  $key
     * @return bool
     */
    public function has($key);

    /**
     * Get the specified configuration value.
     *
     * @param  array|string  $key
     * @param  mixed  $default
     * @return mixed
     */
    public function get($key, $default = null);

    /**
     * Get all of the configuration items for the application.
     *
     * @return array
     */
    public function all();

    /**
     * Set a given configuration value.
     *
     * @param  array|string  $key
     * @param  mixed  $value
     * @return void
     */
    public function set($key, $value = null);

    /**
     * Prepend a value onto an array configuration value.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return void
     */
    public function prepend($key, $value);

    /**
     * Push a value onto an array configuration value.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return void
     */
    public function push($key, $value);
}
<?php

namespace Illuminate\Contracts\Auth;

interface Factory
{
    /**
     * Get a guard instance by name.
     *
     * @param  string|null  $name
     * @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard
     */
    public function guard($name = null);

    /**
     * Set the default guard the factory should serve.
     *
     * @param  string  $name
     * @return void
     */
    public function shouldUse($name);
}
<?php

namespace Illuminate\Contracts\Auth;

interface UserProvider
{
    /**
     * Retrieve a user by their unique identifier.
     *
     * @param  mixed  $identifier
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveById($identifier);

    /**
     * Retrieve a user by their unique identifier and "remember me" token.
     *
     * @param  mixed  $identifier
     * @param  string  $token
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveByToken($identifier, $token);

    /**
     * Update the "remember me" token for the given user in storage.
     *
     * @param  \Illuminate\Contracts\Auth\Authenticatable  $user
     * @param  string  $token
     * @return void
     */
    public function updateRememberToken(Authenticatable $user, $token);

    /**
     * Retrieve a user by the given credentials.
     *
     * @param  array  $credentials
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveByCredentials(array $credentials);

    /**
     * Validate a user against the given credentials.
     *
     * @param  \Illuminate\Contracts\Auth\Authenticatable  $user
     * @param  array  $credentials
     * @return bool
     */
    public function validateCredentials(Authenticatable $user, array $credentials);
}
<?php

namespace Illuminate\Contracts\Auth;

interface Authenticatable
{
    /**
     * Get the name of the unique identifier for the user.
     *
     * @return string
     */
    public function getAuthIdentifierName();

    /**
     * Get the unique identifier for the user.
     *
     * @return mixed
     */
    public function getAuthIdentifier();

    /**
     * Get the password for the user.
     *
     * @return string
     */
    public function getAuthPassword();

    /**
     * Get the token value for the "remember me" session.
     *
     * @return string
     */
    public function getRememberToken();

    /**
     * Set the token value for the "remember me" session.
     *
     * @param  string  $value
     * @return void
     */
    public function setRememberToken($value);

    /**
     * Get the column name for the "remember me" token.
     *
     * @return string
     */
    public function getRememberTokenName();
}
<?php

namespace Illuminate\Contracts\Auth;

interface StatefulGuard extends Guard
{
    /**
     * Attempt to authenticate a user using the given credentials.
     *
     * @param  array  $credentials
     * @param  bool  $remember
     * @return bool
     */
    public function attempt(array $credentials = [], $remember = false);

    /**
     * Log a user into the application without sessions or cookies.
     *
     * @param  array  $credentials
     * @return bool
     */
    public function once(array $credentials = []);

    /**
     * Log a user into the application.
     *
     * @param  \Illuminate\Contracts\Auth\Authenticatable  $user
     * @param  bool  $remember
     * @return void
     */
    public function login(Authenticatable $user, $remember = false);

    /**
     * Log the given user ID into the application.
     *
     * @param  mixed  $id
     * @param  bool  $remember
     * @return \Illuminate\Contracts\Auth\Authenticatable|bool
     */
    public function loginUsingId($id, $remember = false);

    /**
     * Log the given user ID into the application without sessions or cookies.
     *
     * @param  mixed  $id
     * @return \Illuminate\Contracts\Auth\Authenticatable|bool
     */
    public function onceUsingId($id);

    /**
     * Determine if the user was authenticated via "remember me" cookie.
     *
     * @return bool
     */
    public function viaRemember();

    /**
     * Log the user out of the application.
     *
     * @return void
     */
    public function logout();
}
<?php

namespace Illuminate\Contracts\Auth\Middleware;

interface AuthenticatesRequests
{
    //
}
<?php

namespace Illuminate\Contracts\Auth;

interface CanResetPassword
{
    /**
     * Get the e-mail address where password reset links are sent.
     *
     * @return string
     */
    public function getEmailForPasswordReset();

    /**
     * Send the password reset notification.
     *
     * @param  string  $token
     * @return void
     */
    public function sendPasswordResetNotification($token);
}
<?php

namespace Illuminate\Contracts\Auth;

interface PasswordBrokerFactory
{
    /**
     * Get a password broker instance by name.
     *
     * @param  string|null  $name
     * @return \Illuminate\Contracts\Auth\PasswordBroker
     */
    public function broker($name = null);
}
<?php

namespace Illuminate\Contracts\Auth;

use Closure;

interface PasswordBroker
{
    /**
     * Constant representing a successfully sent reminder.
     *
     * @var string
     */
    const RESET_LINK_SENT = 'passwords.sent';

    /**
     * Constant representing a successfully reset password.
     *
     * @var string
     */
    const PASSWORD_RESET = 'passwords.reset';

    /**
     * Constant representing the user not found response.
     *
     * @var string
     */
    const INVALID_USER = 'passwords.user';

    /**
     * Constant representing an invalid token.
     *
     * @var string
     */
    const INVALID_TOKEN = 'passwords.token';

    /**
     * Constant representing a throttled reset attempt.
     *
     * @var string
     */
    const RESET_THROTTLED = 'passwords.throttled';

    /**
     * Send a password reset link to a user.
     *
     * @param  array  $credentials
     * @param  \Closure|null  $callback
     * @return string
     */
    public function sendResetLink(array $credentials, ?Closure $callback = null);

    /**
     * Reset the password for the given token.
     *
     * @param  array  $credentials
     * @param  \Closure  $callback
     * @return mixed
     */
    public function reset(array $credentials, Closure $callback);
}
<?php

namespace Illuminate\Contracts\Auth;

interface SupportsBasicAuth
{
    /**
     * Attempt to authenticate using HTTP Basic Auth.
     *
     * @param  string  $field
     * @param  array  $extraConditions
     * @return \Symfony\Component\HttpFoundation\Response|null
     */
    public function basic($field = 'email', $extraConditions = []);

    /**
     * Perform a stateless HTTP Basic login attempt.
     *
     * @param  string  $field
     * @param  array  $extraConditions
     * @return \Symfony\Component\HttpFoundation\Response|null
     */
    public function onceBasic($field = 'email', $extraConditions = []);
}
<?php

namespace Illuminate\Contracts\Auth\Access;

interface Authorizable
{
    /**
     * Determine if the entity has a given ability.
     *
     * @param  iterable|string  $abilities
     * @param  array|mixed  $arguments
     * @return bool
     */
    public function can($abilities, $arguments = []);
}
<?php

namespace Illuminate\Contracts\Auth\Access;

interface Gate
{
    /**
     * Determine if a given ability has been defined.
     *
     * @param  string  $ability
     * @return bool
     */
    public function has($ability);

    /**
     * Define a new ability.
     *
     * @param  string  $ability
     * @param  callable|string  $callback
     * @return $this
     */
    public function define($ability, $callback);

    /**
     * Define abilities for a resource.
     *
     * @param  string  $name
     * @param  string  $class
     * @param  array|null  $abilities
     * @return $this
     */
    public function resource($name, $class, ?array $abilities = null);

    /**
     * Define a policy class for a given class type.
     *
     * @param  string  $class
     * @param  string  $policy
     * @return $this
     */
    public function policy($class, $policy);

    /**
     * Register a callback to run before all Gate checks.
     *
     * @param  callable  $callback
     * @return $this
     */
    public function before(callable $callback);

    /**
     * Register a callback to run after all Gate checks.
     *
     * @param  callable  $callback
     * @return $this
     */
    public function after(callable $callback);

    /**
     * Determine if all of the given abilities should be granted for the current user.
     *
     * @param  iterable|string  $ability
     * @param  array|mixed  $arguments
     * @return bool
     */
    public function allows($ability, $arguments = []);

    /**
     * Determine if any of the given abilities should be denied for the current user.
     *
     * @param  iterable|string  $ability
     * @param  array|mixed  $arguments
     * @return bool
     */
    public function denies($ability, $arguments = []);

    /**
     * Determine if all of the given abilities should be granted for the current user.
     *
     * @param  iterable|string  $abilities
     * @param  array|mixed  $arguments
     * @return bool
     */
    public function check($abilities, $arguments = []);

    /**
     * Determine if any one of the given abilities should be granted for the current user.
     *
     * @param  iterable|string  $abilities
     * @param  array|mixed  $arguments
     * @return bool
     */
    public function any($abilities, $arguments = []);

    /**
     * Determine if the given ability should be granted for the current user.
     *
     * @param  string  $ability
     * @param  array|mixed  $arguments
     * @return \Illuminate\Auth\Access\Response
     *
     * @throws \Illuminate\Auth\Access\AuthorizationException
     */
    public function authorize($ability, $arguments = []);

    /**
     * Inspect the user for the given ability.
     *
     * @param  string  $ability
     * @param  array|mixed  $arguments
     * @return \Illuminate\Auth\Access\Response
     */
    public function inspect($ability, $arguments = []);

    /**
     * Get the raw result from the authorization callback.
     *
     * @param  string  $ability
     * @param  array|mixed  $arguments
     * @return mixed
     *
     * @throws \Illuminate\Auth\Access\AuthorizationException
     */
    public function raw($ability, $arguments = []);

    /**
     * Get a policy instance for a given class.
     *
     * @param  object|string  $class
     * @return mixed
     *
     * @throws \InvalidArgumentException
     */
    public function getPolicyFor($class);

    /**
     * Get a guard instance for the given user.
     *
     * @param  \Illuminate\Contracts\Auth\Authenticatable|mixed  $user
     * @return static
     */
    public function forUser($user);

    /**
     * Get all of the defined abilities.
     *
     * @return array
     */
    public function abilities();
}
<?php

namespace Illuminate\Contracts\Auth;

interface Guard
{
    /**
     * Determine if the current user is authenticated.
     *
     * @return bool
     */
    public function check();

    /**
     * Determine if the current user is a guest.
     *
     * @return bool
     */
    public function guest();

    /**
     * Get the currently authenticated user.
     *
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function user();

    /**
     * Get the ID for the currently authenticated user.
     *
     * @return int|string|null
     */
    public function id();

    /**
     * Validate a user's credentials.
     *
     * @param  array  $credentials
     * @return bool
     */
    public function validate(array $credentials = []);

    /**
     * Determine if the guard has a user instance.
     *
     * @return bool
     */
    public function hasUser();

    /**
     * Set the current user.
     *
     * @param  \Illuminate\Contracts\Auth\Authenticatable  $user
     * @return void
     */
    public function setUser(Authenticatable $user);
}
<?php

namespace Illuminate\Contracts\Auth;

interface MustVerifyEmail
{
    /**
     * Determine if the user has verified their email address.
     *
     * @return bool
     */
    public function hasVerifiedEmail();

    /**
     * Mark the given user's email as verified.
     *
     * @return bool
     */
    public function markEmailAsVerified();

    /**
     * Send the email verification notification.
     *
     * @return void
     */
    public function sendEmailVerificationNotification();

    /**
     * Get the email address that should be used for verification.
     *
     * @return string
     */
    public function getEmailForVerification();
}
<?php

namespace Illuminate\Contracts\Debug;

use Throwable;

interface ExceptionHandler
{
    /**
     * Report or log an exception.
     *
     * @param  \Throwable  $e
     * @return void
     *
     * @throws \Throwable
     */
    public function report(Throwable $e);

    /**
     * Determine if the exception should be reported.
     *
     * @param  \Throwable  $e
     * @return bool
     */
    public function shouldReport(Throwable $e);

    /**
     * Render an exception into an HTTP response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Throwable  $e
     * @return \Symfony\Component\HttpFoundation\Response
     *
     * @throws \Throwable
     */
    public function render($request, Throwable $e);

    /**
     * Render an exception to the console.
     *
     * @param  \Symfony\Component\Console\Output\OutputInterface  $output
     * @param  \Throwable  $e
     * @return void
     *
     * @internal This method is not meant to be used or overwritten outside the framework.
     */
    public function renderForConsole($output, Throwable $e);
}
<?php

namespace Illuminate\Contracts\Redis;

use Exception;

class LimiterTimeoutException extends Exception
{
    //
}
<?php

namespace Illuminate\Contracts\Redis;

interface Factory
{
    /**
     * Get a Redis connection by name.
     *
     * @param  string|null  $name
     * @return \Illuminate\Redis\Connections\Connection
     */
    public function connection($name = null);
}
<?php

namespace Illuminate\Contracts\Redis;

interface Connector
{
    /**
     * Create a connection to a Redis cluster.
     *
     * @param  array  $config
     * @param  array  $options
     * @return \Illuminate\Redis\Connections\Connection
     */
    public function connect(array $config, array $options);

    /**
     * Create a connection to a Redis instance.
     *
     * @param  array  $config
     * @param  array  $clusterOptions
     * @param  array  $options
     * @return \Illuminate\Redis\Connections\Connection
     */
    public function connectToCluster(array $config, array $clusterOptions, array $options);
}
<?php

namespace Illuminate\Contracts\Redis;

use Closure;

interface Connection
{
    /**
     * Subscribe to a set of given channels for messages.
     *
     * @param  array|string  $channels
     * @param  \Closure  $callback
     * @return void
     */
    public function subscribe($channels, Closure $callback);

    /**
     * Subscribe to a set of given channels with wildcards.
     *
     * @param  array|string  $channels
     * @param  \Closure  $callback
     * @return void
     */
    public function psubscribe($channels, Closure $callback);

    /**
     * Run a command against the Redis database.
     *
     * @param  string  $method
     * @param  array  $parameters
     * @return mixed
     */
    public function command($method, array $parameters = []);
}
<?php

namespace Illuminate\Contracts\Console;

interface PromptsForMissingInput
{
    //
}
<?php

namespace Illuminate\Contracts\Console;

interface Kernel
{
    /**
     * Bootstrap the application for artisan commands.
     *
     * @return void
     */
    public function bootstrap();

    /**
     * Handle an incoming console command.
     *
     * @param  \Symfony\Component\Console\Input\InputInterface  $input
     * @param  \Symfony\Component\Console\Output\OutputInterface|null  $output
     * @return int
     */
    public function handle($input, $output = null);

    /**
     * Run an Artisan console command by name.
     *
     * @param  string  $command
     * @param  array  $parameters
     * @param  \Symfony\Component\Console\Output\OutputInterface|null  $outputBuffer
     * @return int
     */
    public function call($command, array $parameters = [], $outputBuffer = null);

    /**
     * Queue an Artisan console command by name.
     *
     * @param  string  $command
     * @param  array  $parameters
     * @return \Illuminate\Foundation\Bus\PendingDispatch
     */
    public function queue($command, array $parameters = []);

    /**
     * Get all of the commands registered with the console.
     *
     * @return array
     */
    public function all();

    /**
     * Get the output for the last run command.
     *
     * @return string
     */
    public function output();

    /**
     * Terminate the application.
     *
     * @param  \Symfony\Component\Console\Input\InputInterface  $input
     * @param  int  $status
     * @return void
     */
    public function terminate($input, $status);
}
<?php

namespace Illuminate\Contracts\Console;

interface Isolatable
{
    //
}
<?php

namespace Illuminate\Contracts\Console;

interface Application
{
    /**
     * Run an Artisan console command by name.
     *
     * @param  string  $command
     * @param  array  $parameters
     * @param  \Symfony\Component\Console\Output\OutputInterface|null  $outputBuffer
     * @return int
     */
    public function call($command, array $parameters = [], $outputBuffer = null);

    /**
     * Get the output from the last command.
     *
     * @return string
     */
    public function output();
}
<?php

namespace Illuminate\Contracts\Container;

use Closure;
use Psr\Container\ContainerInterface;

interface Container extends ContainerInterface
{
    /**
     * Determine if the given abstract type has been bound.
     *
     * @param  string  $abstract
     * @return bool
     */
    public function bound($abstract);

    /**
     * Alias a type to a different name.
     *
     * @param  string  $abstract
     * @param  string  $alias
     * @return void
     *
     * @throws \LogicException
     */
    public function alias($abstract, $alias);

    /**
     * Assign a set of tags to a given binding.
     *
     * @param  array|string  $abstracts
     * @param  array|mixed  ...$tags
     * @return void
     */
    public function tag($abstracts, $tags);

    /**
     * Resolve all of the bindings for a given tag.
     *
     * @param  string  $tag
     * @return iterable
     */
    public function tagged($tag);

    /**
     * Register a binding with the container.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @param  bool  $shared
     * @return void
     */
    public function bind($abstract, $concrete = null, $shared = false);

    /**
     * Bind a callback to resolve with Container::call.
     *
     * @param  array|string  $method
     * @param  \Closure  $callback
     * @return void
     */
    public function bindMethod($method, $callback);

    /**
     * Register a binding if it hasn't already been registered.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @param  bool  $shared
     * @return void
     */
    public function bindIf($abstract, $concrete = null, $shared = false);

    /**
     * Register a shared binding in the container.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @return void
     */
    public function singleton($abstract, $concrete = null);

    /**
     * Register a shared binding if it hasn't already been registered.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @return void
     */
    public function singletonIf($abstract, $concrete = null);

    /**
     * Register a scoped binding in the container.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @return void
     */
    public function scoped($abstract, $concrete = null);

    /**
     * Register a scoped binding if it hasn't already been registered.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @return void
     */
    public function scopedIf($abstract, $concrete = null);

    /**
     * "Extend" an abstract type in the container.
     *
     * @param  string  $abstract
     * @param  \Closure  $closure
     * @return void
     *
     * @throws \InvalidArgumentException
     */
    public function extend($abstract, Closure $closure);

    /**
     * Register an existing instance as shared in the container.
     *
     * @param  string  $abstract
     * @param  mixed  $instance
     * @return mixed
     */
    public function instance($abstract, $instance);

    /**
     * Add a contextual binding to the container.
     *
     * @param  string  $concrete
     * @param  string  $abstract
     * @param  \Closure|string  $implementation
     * @return void
     */
    public function addContextualBinding($concrete, $abstract, $implementation);

    /**
     * Define a contextual binding.
     *
     * @param  string|array  $concrete
     * @return \Illuminate\Contracts\Container\ContextualBindingBuilder
     */
    public function when($concrete);

    /**
     * Get a closure to resolve the given type from the container.
     *
     * @param  string  $abstract
     * @return \Closure
     */
    public function factory($abstract);

    /**
     * Flush the container of all bindings and resolved instances.
     *
     * @return void
     */
    public function flush();

    /**
     * Resolve the given type from the container.
     *
     * @param  string  $abstract
     * @param  array  $parameters
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    public function make($abstract, array $parameters = []);

    /**
     * Call the given Closure / class@method and inject its dependencies.
     *
     * @param  callable|string  $callback
     * @param  array  $parameters
     * @param  string|null  $defaultMethod
     * @return mixed
     */
    public function call($callback, array $parameters = [], $defaultMethod = null);

    /**
     * Determine if the given abstract type has been resolved.
     *
     * @param  string  $abstract
     * @return bool
     */
    public function resolved($abstract);

    /**
     * Register a new before resolving callback.
     *
     * @param  \Closure|string  $abstract
     * @param  \Closure|null  $callback
     * @return void
     */
    public function beforeResolving($abstract, ?Closure $callback = null);

    /**
     * Register a new resolving callback.
     *
     * @param  \Closure|string  $abstract
     * @param  \Closure|null  $callback
     * @return void
     */
    public function resolving($abstract, ?Closure $callback = null);

    /**
     * Register a new after resolving callback.
     *
     * @param  \Closure|string  $abstract
     * @param  \Closure|null  $callback
     * @return void
     */
    public function afterResolving($abstract, ?Closure $callback = null);
}
<?php

namespace Illuminate\Contracts\Container;

use Exception;
use Psr\Container\ContainerExceptionInterface;

class CircularDependencyException extends Exception implements ContainerExceptionInterface
{
    //
}
<?php

namespace Illuminate\Contracts\Container;

use Exception;
use Psr\Container\ContainerExceptionInterface;

class BindingResolutionException extends Exception implements ContainerExceptionInterface
{
    //
}
<?php

namespace Illuminate\Contracts\Container;

interface ContextualBindingBuilder
{
    /**
     * Define the abstract target that depends on the context.
     *
     * @param  string  $abstract
     * @return $this
     */
    public function needs($abstract);

    /**
     * Define the implementation for the contextual binding.
     *
     * @param  \Closure|string|array  $implementation
     * @return void
     */
    public function give($implementation);

    /**
     * Define tagged services to be used as the implementation for the contextual binding.
     *
     * @param  string  $tag
     * @return void
     */
    public function giveTagged($tag);

    /**
     * Specify the configuration item to bind as a primitive.
     *
     * @param  string  $key
     * @param  mixed  $default
     * @return void
     */
    public function giveConfig($key, $default = null);
}
<?php

namespace Illuminate\Contracts\Pagination;

interface LengthAwarePaginator extends Paginator
{
    /**
     * Create a range of pagination URLs.
     *
     * @param  int  $start
     * @param  int  $end
     * @return array
     */
    public function getUrlRange($start, $end);

    /**
     * Determine the total number of items in the data store.
     *
     * @return int
     */
    public function total();

    /**
     * Get the page number of the last available page.
     *
     * @return int
     */
    public function lastPage();
}
<?php

namespace Illuminate\Contracts\Pagination;

interface CursorPaginator
{
    /**
     * Get the URL for a given cursor.
     *
     * @param  \Illuminate\Pagination\Cursor|null  $cursor
     * @return string
     */
    public function url($cursor);

    /**
     * Add a set of query string values to the paginator.
     *
     * @param  array|string|null  $key
     * @param  string|null  $value
     * @return $this
     */
    public function appends($key, $value = null);

    /**
     * Get / set the URL fragment to be appended to URLs.
     *
     * @param  string|null  $fragment
     * @return $this|string|null
     */
    public function fragment($fragment = null);

    /**
     * Add all current query string values to the paginator.
     *
     * @return $this
     */
    public function withQueryString();

    /**
     * Get the URL for the previous page, or null.
     *
     * @return string|null
     */
    public function previousPageUrl();

    /**
     * The URL for the next page, or null.
     *
     * @return string|null
     */
    public function nextPageUrl();

    /**
     * Get all of the items being paginated.
     *
     * @return array
     */
    public function items();

    /**
     * Get the "cursor" of the previous set of items.
     *
     * @return \Illuminate\Pagination\Cursor|null
     */
    public function previousCursor();

    /**
     * Get the "cursor" of the next set of items.
     *
     * @return \Illuminate\Pagination\Cursor|null
     */
    public function nextCursor();

    /**
     * Determine how many items are being shown per page.
     *
     * @return int
     */
    public function perPage();

    /**
     * Get the current cursor being paginated.
     *
     * @return \Illuminate\Pagination\Cursor|null
     */
    public function cursor();

    /**
     * Determine if there are enough items to split into multiple pages.
     *
     * @return bool
     */
    public function hasPages();

    /**
     * Get the base path for paginator generated URLs.
     *
     * @return string|null
     */
    public function path();

    /**
     * Determine if the list of items is empty or not.
     *
     * @return bool
     */
    public function isEmpty();

    /**
     * Determine if the list of items is not empty.
     *
     * @return bool
     */
    public function isNotEmpty();

    /**
     * Render the paginator using a given view.
     *
     * @param  string|null  $view
     * @param  array  $data
     * @return string
     */
    public function render($view = null, $data = []);
}
<?php

namespace Illuminate\Contracts\Pagination;

interface Paginator
{
    /**
     * Get the URL for a given page.
     *
     * @param  int  $page
     * @return string
     */
    public function url($page);

    /**
     * Add a set of query string values to the paginator.
     *
     * @param  array|string|null  $key
     * @param  string|null  $value
     * @return $this
     */
    public function appends($key, $value = null);

    /**
     * Get / set the URL fragment to be appended to URLs.
     *
     * @param  string|null  $fragment
     * @return $this|string|null
     */
    public function fragment($fragment = null);

    /**
     * The URL for the next page, or null.
     *
     * @return string|null
     */
    public function nextPageUrl();

    /**
     * Get the URL for the previous page, or null.
     *
     * @return string|null
     */
    public function previousPageUrl();

    /**
     * Get all of the items being paginated.
     *
     * @return array
     */
    public function items();

    /**
     * Get the "index" of the first item being paginated.
     *
     * @return int|null
     */
    public function firstItem();

    /**
     * Get the "index" of the last item being paginated.
     *
     * @return int|null
     */
    public function lastItem();

    /**
     * Determine how many items are being shown per page.
     *
     * @return int
     */
    public function perPage();

    /**
     * Determine the current page being paginated.
     *
     * @return int
     */
    public function currentPage();

    /**
     * Determine if there are enough items to split into multiple pages.
     *
     * @return bool
     */
    public function hasPages();

    /**
     * Determine if there are more items in the data store.
     *
     * @return bool
     */
    public function hasMorePages();

    /**
     * Get the base path for paginator generated URLs.
     *
     * @return string|null
     */
    public function path();

    /**
     * Determine if the list of items is empty or not.
     *
     * @return bool
     */
    public function isEmpty();

    /**
     * Determine if the list of items is not empty.
     *
     * @return bool
     */
    public function isNotEmpty();

    /**
     * Render the paginator using a given view.
     *
     * @param  string|null  $view
     * @param  array  $data
     * @return string
     */
    public function render($view = null, $data = []);
}
<?php

namespace Illuminate\Contracts\Cookie;

interface Factory
{
    /**
     * Create a new cookie instance.
     *
     * @param  string  $name
     * @param  string  $value
     * @param  int  $minutes
     * @param  string|null  $path
     * @param  string|null  $domain
     * @param  bool|null  $secure
     * @param  bool  $httpOnly
     * @param  bool  $raw
     * @param  string|null  $sameSite
     * @return \Symfony\Component\HttpFoundation\Cookie
     */
    public function make($name, $value, $minutes = 0, $path = null, $domain = null, $secure = null, $httpOnly = true, $raw = false, $sameSite = null);

    /**
     * Create a cookie that lasts "forever" (five years).
     *
     * @param  string  $name
     * @param  string  $value
     * @param  string|null  $path
     * @param  string|null  $domain
     * @param  bool|null  $secure
     * @param  bool  $httpOnly
     * @param  bool  $raw
     * @param  string|null  $sameSite
     * @return \Symfony\Component\HttpFoundation\Cookie
     */
    public function forever($name, $value, $path = null, $domain = null, $secure = null, $httpOnly = true, $raw = false, $sameSite = null);

    /**
     * Expire the given cookie.
     *
     * @param  string  $name
     * @param  string|null  $path
     * @param  string|null  $domain
     * @return \Symfony\Component\HttpFoundation\Cookie
     */
    public function forget($name, $path = null, $domain = null);
}
<?php

namespace Illuminate\Contracts\Cookie;

interface QueueingFactory extends Factory
{
    /**
     * Queue a cookie to send with the next response.
     *
     * @param  mixed  ...$parameters
     * @return void
     */
    public function queue(...$parameters);

    /**
     * Remove a cookie from the queue.
     *
     * @param  string  $name
     * @param  string|null  $path
     * @return void
     */
    public function unqueue($name, $path = null);

    /**
     * Get the cookies which have been queued for the next request.
     *
     * @return array
     */
    public function getQueuedCookies();
}
<?php

namespace Illuminate\Contracts\Database;

class ModelIdentifier
{
    /**
     * The class name of the model.
     *
     * @var string
     */
    public $class;

    /**
     * The unique identifier of the model.
     *
     * This may be either a single ID or an array of IDs.
     *
     * @var mixed
     */
    public $id;

    /**
     * The relationships loaded on the model.
     *
     * @var array
     */
    public $relations;

    /**
     * The connection name of the model.
     *
     * @var string|null
     */
    public $connection;

    /**
     * The class name of the model collection.
     *
     * @var string|null
     */
    public $collectionClass;

    /**
     * Create a new model identifier.
     *
     * @param  string  $class
     * @param  mixed  $id
     * @param  array  $relations
     * @param  mixed  $connection
     * @return void
     */
    public function __construct($class, $id, array $relations, $connection)
    {
        $this->id = $id;
        $this->class = $class;
        $this->relations = $relations;
        $this->connection = $connection;
    }

    /**
     * Specify the collection class that should be used when serializing / restoring collections.
     *
     * @param  string|null  $collectionClass
     * @return $this
     */
    public function useCollectionClass(?string $collectionClass)
    {
        $this->collectionClass = $collectionClass;

        return $this;
    }
}
<?php

namespace Illuminate\Contracts\Database\Eloquent;

use Illuminate\Database\Eloquent\Model;

interface CastsInboundAttributes
{
    /**
     * Transform the attribute to its underlying model values.
     *
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @param  string  $key
     * @param  mixed  $value
     * @param  array  $attributes
     * @return mixed
     */
    public function set(Model $model, string $key, mixed $value, array $attributes);
}
<?php

namespace Illuminate\Contracts\Database\Eloquent;

use Illuminate\Database\Eloquent\Model;

/**
 * @template TGet
 * @template TSet
 */
interface CastsAttributes
{
    /**
     * Transform the attribute from the underlying model values.
     *
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @param  string  $key
     * @param  mixed  $value
     * @param  array<string, mixed>  $attributes
     * @return TGet|null
     */
    public function get(Model $model, string $key, mixed $value, array $attributes);

    /**
     * Transform the attribute to its underlying model values.
     *
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @param  string  $key
     * @param  TSet|null  $value
     * @param  array<string, mixed>  $attributes
     * @return mixed
     */
    public function set(Model $model, string $key, mixed $value, array $attributes);
}
<?php

namespace Illuminate\Contracts\Database\Eloquent;

interface SupportsPartialRelations
{
    /**
     * Indicate that the relation is a single result of a larger one-to-many relationship.
     *
     * @param  string|null  $column
     * @param  string|\Closure|null  $aggregate
     * @param  string  $relation
     * @return $this
     */
    public function ofMany($column = 'id', $aggregate = 'MAX', $relation = null);

    /**
     * Determine whether the relationship is a one-of-many relationship.
     *
     * @return bool
     */
    public function isOneOfMany();

    /**
     * Get the one of many inner join subselect query builder instance.
     *
     * @return \Illuminate\Database\Eloquent\Builder|void
     */
    public function getOneOfManySubQuery();
}
<?php

namespace Illuminate\Contracts\Database\Eloquent;

interface Castable
{
    /**
     * Get the name of the caster class to use when casting from / to this cast target.
     *
     * @param  array  $arguments
     * @return class-string<CastsAttributes|CastsInboundAttributes>|CastsAttributes|CastsInboundAttributes
     */
    public static function castUsing(array $arguments);
}
<?php

namespace Illuminate\Contracts\Database\Eloquent;

use Illuminate\Database\Eloquent\Model;

interface SerializesCastableAttributes
{
    /**
     * Serialize the attribute when converting the model to an array.
     *
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @param  string  $key
     * @param  mixed  $value
     * @param  array  $attributes
     * @return mixed
     */
    public function serialize(Model $model, string $key, mixed $value, array $attributes);
}
<?php

namespace Illuminate\Contracts\Database\Eloquent;

use Illuminate\Contracts\Database\Query\Builder as BaseContract;

/**
 * This interface is intentionally empty and exists to improve IDE support.
 *
 * @mixin \Illuminate\Database\Eloquent\Builder
 */
interface Builder extends BaseContract
{
}
<?php

namespace Illuminate\Contracts\Database\Eloquent;

interface DeviatesCastableAttributes
{
    /**
     * Increment the attribute.
     *
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @param  string  $key
     * @param  mixed  $value
     * @param  array  $attributes
     * @return mixed
     */
    public function increment($model, string $key, $value, array $attributes);

    /**
     * Decrement the attribute.
     *
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @param  string  $key
     * @param  mixed  $value
     * @param  array  $attributes
     * @return mixed
     */
    public function decrement($model, string $key, $value, array $attributes);
}
<?php

namespace Illuminate\Contracts\Database\Query;

use Illuminate\Database\Grammar;

interface Expression
{
    /**
     * Get the value of the expression.
     *
     * @param  \Illuminate\Database\Grammar  $grammar
     * @return string|int|float
     */
    public function getValue(Grammar $grammar);
}
<?php

namespace Illuminate\Contracts\Database\Query;

interface ConditionExpression extends Expression
{
}
<?php

namespace Illuminate\Contracts\Database\Query;

/**
 * This interface is intentionally empty and exists to improve IDE support.
 *
 * @mixin \Illuminate\Database\Query\Builder
 */
interface Builder
{
}
<?php

namespace Illuminate\Contracts\Database\Events;

interface MigrationEvent
{
    //
}
<?php

namespace Illuminate\Contracts\Http;

interface Kernel
{
    /**
     * Bootstrap the application for HTTP requests.
     *
     * @return void
     */
    public function bootstrap();

    /**
     * Handle an incoming HTTP request.
     *
     * @param  \Symfony\Component\HttpFoundation\Request  $request
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function handle($request);

    /**
     * Perform any final actions for the request lifecycle.
     *
     * @param  \Symfony\Component\HttpFoundation\Request  $request
     * @param  \Symfony\Component\HttpFoundation\Response  $response
     * @return void
     */
    public function terminate($request, $response);

    /**
     * Get the Laravel application instance.
     *
     * @return \Illuminate\Contracts\Foundation\Application
     */
    public function getApplication();
}
<?php

namespace Illuminate\Contracts\Routing;

interface Registrar
{
    /**
     * Register a new GET route with the router.
     *
     * @param  string  $uri
     * @param  array|string|callable  $action
     * @return \Illuminate\Routing\Route
     */
    public function get($uri, $action);

    /**
     * Register a new POST route with the router.
     *
     * @param  string  $uri
     * @param  array|string|callable  $action
     * @return \Illuminate\Routing\Route
     */
    public function post($uri, $action);

    /**
     * Register a new PUT route with the router.
     *
     * @param  string  $uri
     * @param  array|string|callable  $action
     * @return \Illuminate\Routing\Route
     */
    public function put($uri, $action);

    /**
     * Register a new DELETE route with the router.
     *
     * @param  string  $uri
     * @param  array|string|callable  $action
     * @return \Illuminate\Routing\Route
     */
    public function delete($uri, $action);

    /**
     * Register a new PATCH route with the router.
     *
     * @param  string  $uri
     * @param  array|string|callable  $action
     * @return \Illuminate\Routing\Route
     */
    public function patch($uri, $action);

    /**
     * Register a new OPTIONS route with the router.
     *
     * @param  string  $uri
     * @param  array|string|callable  $action
     * @return \Illuminate\Routing\Route
     */
    public function options($uri, $action);

    /**
     * Register a new route with the given verbs.
     *
     * @param  array|string  $methods
     * @param  string  $uri
     * @param  array|string|callable  $action
     * @return \Illuminate\Routing\Route
     */
    public function match($methods, $uri, $action);

    /**
     * Route a resource to a controller.
     *
     * @param  string  $name
     * @param  string  $controller
     * @param  array  $options
     * @return \Illuminate\Routing\PendingResourceRegistration
     */
    public function resource($name, $controller, array $options = []);

    /**
     * Create a route group with shared attributes.
     *
     * @param  array  $attributes
     * @param  \Closure|string  $routes
     * @return void
     */
    public function group(array $attributes, $routes);

    /**
     * Substitute the route bindings onto the route.
     *
     * @param  \Illuminate\Routing\Route  $route
     * @return \Illuminate\Routing\Route
     */
    public function substituteBindings($route);

    /**
     * Substitute the implicit Eloquent model bindings for the route.
     *
     * @param  \Illuminate\Routing\Route  $route
     * @return void
     */
    public function substituteImplicitBindings($route);
}
<?php

namespace Illuminate\Contracts\Routing;

interface UrlRoutable
{
    /**
     * Get the value of the model's route key.
     *
     * @return mixed
     */
    public function getRouteKey();

    /**
     * Get the route key for the model.
     *
     * @return string
     */
    public function getRouteKeyName();

    /**
     * Retrieve the model for a bound value.
     *
     * @param  mixed  $value
     * @param  string|null  $field
     * @return \Illuminate\Database\Eloquent\Model|null
     */
    public function resolveRouteBinding($value, $field = null);

    /**
     * Retrieve the child model for a bound value.
     *
     * @param  string  $childType
     * @param  mixed  $value
     * @param  string|null  $field
     * @return \Illuminate\Database\Eloquent\Model|null
     */
    public function resolveChildRouteBinding($childType, $value, $field);
}
<?php

namespace Illuminate\Contracts\Routing;

interface BindingRegistrar
{
    /**
     * Add a new route parameter binder.
     *
     * @param  string  $key
     * @param  string|callable  $binder
     * @return void
     */
    public function bind($key, $binder);

    /**
     * Get the binding callback for a given binding.
     *
     * @param  string  $key
     * @return \Closure
     */
    public function getBindingCallback($key);
}
<?php

namespace Illuminate\Contracts\Routing;

interface UrlGenerator
{
    /**
     * Get the current URL for the request.
     *
     * @return string
     */
    public function current();

    /**
     * Get the URL for the previous request.
     *
     * @param  mixed  $fallback
     * @return string
     */
    public function previous($fallback = false);

    /**
     * Generate an absolute URL to the given path.
     *
     * @param  string  $path
     * @param  mixed  $extra
     * @param  bool|null  $secure
     * @return string
     */
    public function to($path, $extra = [], $secure = null);

    /**
     * Generate a secure, absolute URL to the given path.
     *
     * @param  string  $path
     * @param  array  $parameters
     * @return string
     */
    public function secure($path, $parameters = []);

    /**
     * Generate the URL to an application asset.
     *
     * @param  string  $path
     * @param  bool|null  $secure
     * @return string
     */
    public function asset($path, $secure = null);

    /**
     * Get the URL to a named route.
     *
     * @param  string  $name
     * @param  mixed  $parameters
     * @param  bool  $absolute
     * @return string
     *
     * @throws \InvalidArgumentException
     */
    public function route($name, $parameters = [], $absolute = true);

    /**
     * Get the URL to a controller action.
     *
     * @param  string|array  $action
     * @param  mixed  $parameters
     * @param  bool  $absolute
     * @return string
     */
    public function action($action, $parameters = [], $absolute = true);

    /**
     * Get the root controller namespace.
     *
     * @return string
     */
    public function getRootControllerNamespace();

    /**
     * Set the root controller namespace.
     *
     * @param  string  $rootNamespace
     * @return $this
     */
    public function setRootControllerNamespace($rootNamespace);
}
<?php

namespace Illuminate\Contracts\Routing;

interface ResponseFactory
{
    /**
     * Create a new response instance.
     *
     * @param  array|string  $content
     * @param  int  $status
     * @param  array  $headers
     * @return \Illuminate\Http\Response
     */
    public function make($content = '', $status = 200, array $headers = []);

    /**
     * Create a new "no content" response.
     *
     * @param  int  $status
     * @param  array  $headers
     * @return \Illuminate\Http\Response
     */
    public function noContent($status = 204, array $headers = []);

    /**
     * Create a new response for a given view.
     *
     * @param  string|array  $view
     * @param  array  $data
     * @param  int  $status
     * @param  array  $headers
     * @return \Illuminate\Http\Response
     */
    public function view($view, $data = [], $status = 200, array $headers = []);

    /**
     * Create a new JSON response instance.
     *
     * @param  mixed  $data
     * @param  int  $status
     * @param  array  $headers
     * @param  int  $options
     * @return \Illuminate\Http\JsonResponse
     */
    public function json($data = [], $status = 200, array $headers = [], $options = 0);

    /**
     * Create a new JSONP response instance.
     *
     * @param  string  $callback
     * @param  mixed  $data
     * @param  int  $status
     * @param  array  $headers
     * @param  int  $options
     * @return \Illuminate\Http\JsonResponse
     */
    public function jsonp($callback, $data = [], $status = 200, array $headers = [], $options = 0);

    /**
     * Create a new streamed response instance.
     *
     * @param  callable  $callback
     * @param  int  $status
     * @param  array  $headers
     * @return \Symfony\Component\HttpFoundation\StreamedResponse
     */
    public function stream($callback, $status = 200, array $headers = []);

    /**
     * Create a new streamed response instance as a file download.
     *
     * @param  callable  $callback
     * @param  string|null  $name
     * @param  array  $headers
     * @param  string|null  $disposition
     * @return \Symfony\Component\HttpFoundation\StreamedResponse
     */
    public function streamDownload($callback, $name = null, array $headers = [], $disposition = 'attachment');

    /**
     * Create a new file download response.
     *
     * @param  \SplFileInfo|string  $file
     * @param  string|null  $name
     * @param  array  $headers
     * @param  string|null  $disposition
     * @return \Symfony\Component\HttpFoundation\BinaryFileResponse
     */
    public function download($file, $name = null, array $headers = [], $disposition = 'attachment');

    /**
     * Return the raw contents of a binary file.
     *
     * @param  \SplFileInfo|string  $file
     * @param  array  $headers
     * @return \Symfony\Component\HttpFoundation\BinaryFileResponse
     */
    public function file($file, array $headers = []);

    /**
     * Create a new redirect response to the given path.
     *
     * @param  string  $path
     * @param  int  $status
     * @param  array  $headers
     * @param  bool|null  $secure
     * @return \Illuminate\Http\RedirectResponse
     */
    public function redirectTo($path, $status = 302, $headers = [], $secure = null);

    /**
     * Create a new redirect response to a named route.
     *
     * @param  string  $route
     * @param  mixed  $parameters
     * @param  int  $status
     * @param  array  $headers
     * @return \Illuminate\Http\RedirectResponse
     */
    public function redirectToRoute($route, $parameters = [], $status = 302, $headers = []);

    /**
     * Create a new redirect response to a controller action.
     *
     * @param  array|string  $action
     * @param  mixed  $parameters
     * @param  int  $status
     * @param  array  $headers
     * @return \Illuminate\Http\RedirectResponse
     */
    public function redirectToAction($action, $parameters = [], $status = 302, $headers = []);

    /**
     * Create a new redirect response, while putting the current URL in the session.
     *
     * @param  string  $path
     * @param  int  $status
     * @param  array  $headers
     * @param  bool|null  $secure
     * @return \Illuminate\Http\RedirectResponse
     */
    public function redirectGuest($path, $status = 302, $headers = [], $secure = null);

    /**
     * Create a new redirect response to the previously intended location.
     *
     * @param  string  $default
     * @param  int  $status
     * @param  array  $headers
     * @param  bool|null  $secure
     * @return \Illuminate\Http\RedirectResponse
     */
    public function redirectToIntended($default = '/', $status = 302, $headers = [], $secure = null);
}
<?php

namespace Illuminate\Contracts\Encryption;

interface StringEncrypter
{
    /**
     * Encrypt a string without serialization.
     *
     * @param  string  $value
     * @return string
     *
     * @throws \Illuminate\Contracts\Encryption\EncryptException
     */
    public function encryptString($value);

    /**
     * Decrypt the given string without unserialization.
     *
     * @param  string  $payload
     * @return string
     *
     * @throws \Illuminate\Contracts\Encryption\DecryptException
     */
    public function decryptString($payload);
}
<?php

namespace Illuminate\Contracts\Encryption;

use RuntimeException;

class EncryptException extends RuntimeException
{
    //
}
<?php

namespace Illuminate\Contracts\Encryption;

use RuntimeException;

class DecryptException extends RuntimeException
{
    //
}
<?php

namespace Illuminate\Contracts\Encryption;

interface Encrypter
{
    /**
     * Encrypt the given value.
     *
     * @param  mixed  $value
     * @param  bool  $serialize
     * @return string
     *
     * @throws \Illuminate\Contracts\Encryption\EncryptException
     */
    public function encrypt($value, $serialize = true);

    /**
     * Decrypt the given value.
     *
     * @param  string  $payload
     * @param  bool  $unserialize
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Encryption\DecryptException
     */
    public function decrypt($payload, $unserialize = true);

    /**
     * Get the encryption key that the encrypter is currently using.
     *
     * @return string
     */
    public function getKey();
}
<?php

namespace Illuminate\Contracts\Filesystem;

interface Factory
{
    /**
     * Get a filesystem implementation.
     *
     * @param  string|null  $name
     * @return \Illuminate\Contracts\Filesystem\Filesystem
     */
    public function disk($name = null);
}
<?php

namespace Illuminate\Contracts\Filesystem;

use Exception;

class FileNotFoundException extends Exception
{
    //
}
<?php

namespace Illuminate\Contracts\Filesystem;

use Exception;

class LockTimeoutException extends Exception
{
    //
}
<?php

namespace Illuminate\Contracts\Filesystem;

interface Filesystem
{
    /**
     * The public visibility setting.
     *
     * @var string
     */
    const VISIBILITY_PUBLIC = 'public';

    /**
     * The private visibility setting.
     *
     * @var string
     */
    const VISIBILITY_PRIVATE = 'private';

    /**
     * Determine if a file exists.
     *
     * @param  string  $path
     * @return bool
     */
    public function exists($path);

    /**
     * Get the contents of a file.
     *
     * @param  string  $path
     * @return string|null
     */
    public function get($path);

    /**
     * Get a resource to read the file.
     *
     * @param  string  $path
     * @return resource|null The path resource or null on failure.
     */
    public function readStream($path);

    /**
     * Write the contents of a file.
     *
     * @param  string  $path
     * @param  \Psr\Http\Message\StreamInterface|\Illuminate\Http\File|\Illuminate\Http\UploadedFile|string|resource  $contents
     * @param  mixed  $options
     * @return bool
     */
    public function put($path, $contents, $options = []);

    /**
     * Write a new file using a stream.
     *
     * @param  string  $path
     * @param  resource  $resource
     * @param  array  $options
     * @return bool
     */
    public function writeStream($path, $resource, array $options = []);

    /**
     * Get the visibility for the given path.
     *
     * @param  string  $path
     * @return string
     */
    public function getVisibility($path);

    /**
     * Set the visibility for the given path.
     *
     * @param  string  $path
     * @param  string  $visibility
     * @return bool
     */
    public function setVisibility($path, $visibility);

    /**
     * Prepend to a file.
     *
     * @param  string  $path
     * @param  string  $data
     * @return bool
     */
    public function prepend($path, $data);

    /**
     * Append to a file.
     *
     * @param  string  $path
     * @param  string  $data
     * @return bool
     */
    public function append($path, $data);

    /**
     * Delete the file at a given path.
     *
     * @param  string|array  $paths
     * @return bool
     */
    public function delete($paths);

    /**
     * Copy a file to a new location.
     *
     * @param  string  $from
     * @param  string  $to
     * @return bool
     */
    public function copy($from, $to);

    /**
     * Move a file to a new location.
     *
     * @param  string  $from
     * @param  string  $to
     * @return bool
     */
    public function move($from, $to);

    /**
     * Get the file size of a given file.
     *
     * @param  string  $path
     * @return int
     */
    public function size($path);

    /**
     * Get the file's last modification time.
     *
     * @param  string  $path
     * @return int
     */
    public function lastModified($path);

    /**
     * Get an array of all files in a directory.
     *
     * @param  string|null  $directory
     * @param  bool  $recursive
     * @return array
     */
    public function files($directory = null, $recursive = false);

    /**
     * Get all of the files from the given directory (recursive).
     *
     * @param  string|null  $directory
     * @return array
     */
    public function allFiles($directory = null);

    /**
     * Get all of the directories within a given directory.
     *
     * @param  string|null  $directory
     * @param  bool  $recursive
     * @return array
     */
    public function directories($directory = null, $recursive = false);

    /**
     * Get all (recursive) of the directories within a given directory.
     *
     * @param  string|null  $directory
     * @return array
     */
    public function allDirectories($directory = null);

    /**
     * Create a directory.
     *
     * @param  string  $path
     * @return bool
     */
    public function makeDirectory($path);

    /**
     * Recursively delete a directory.
     *
     * @param  string  $directory
     * @return bool
     */
    public function deleteDirectory($directory);
}
<?php

namespace Illuminate\Contracts\Filesystem;

interface Cloud extends Filesystem
{
    /**
     * Get the URL for the file at the given path.
     *
     * @param  string  $path
     * @return string
     */
    public function url($path);
}
<?php

namespace Illuminate\Contracts\Mail;

interface Factory
{
    /**
     * Get a mailer instance by name.
     *
     * @param  string|null  $name
     * @return \Illuminate\Contracts\Mail\Mailer
     */
    public function mailer($name = null);
}
<?php

namespace Illuminate\Contracts\Mail;

interface MailQueue
{
    /**
     * Queue a new e-mail message for sending.
     *
     * @param  \Illuminate\Contracts\Mail\Mailable|string|array  $view
     * @param  string|null  $queue
     * @return mixed
     */
    public function queue($view, $queue = null);

    /**
     * Queue a new e-mail message for sending after (n) seconds.
     *
     * @param  \DateTimeInterface|\DateInterval|int  $delay
     * @param  \Illuminate\Contracts\Mail\Mailable|string|array  $view
     * @param  string|null  $queue
     * @return mixed
     */
    public function later($delay, $view, $queue = null);
}
<?php

namespace Illuminate\Contracts\Mail;

interface Mailer
{
    /**
     * Begin the process of mailing a mailable class instance.
     *
     * @param  mixed  $users
     * @return \Illuminate\Mail\PendingMail
     */
    public function to($users);

    /**
     * Begin the process of mailing a mailable class instance.
     *
     * @param  mixed  $users
     * @return \Illuminate\Mail\PendingMail
     */
    public function bcc($users);

    /**
     * Send a new message with only a raw text part.
     *
     * @param  string  $text
     * @param  mixed  $callback
     * @return \Illuminate\Mail\SentMessage|null
     */
    public function raw($text, $callback);

    /**
     * Send a new message using a view.
     *
     * @param  \Illuminate\Contracts\Mail\Mailable|string|array  $view
     * @param  array  $data
     * @param  \Closure|string|null  $callback
     * @return \Illuminate\Mail\SentMessage|null
     */
    public function send($view, array $data = [], $callback = null);
}
<?php

namespace Illuminate\Contracts\Mail;

use Illuminate\Contracts\Queue\Factory as Queue;

interface Mailable
{
    /**
     * Send the message using the given mailer.
     *
     * @param  \Illuminate\Contracts\Mail\Factory|\Illuminate\Contracts\Mail\Mailer  $mailer
     * @return \Illuminate\Mail\SentMessage|null
     */
    public function send($mailer);

    /**
     * Queue the given message.
     *
     * @param  \Illuminate\Contracts\Queue\Factory  $queue
     * @return mixed
     */
    public function queue(Queue $queue);

    /**
     * Deliver the queued message after (n) seconds.
     *
     * @param  \DateTimeInterface|\DateInterval|int  $delay
     * @param  \Illuminate\Contracts\Queue\Factory  $queue
     * @return mixed
     */
    public function later($delay, Queue $queue);

    /**
     * Set the recipients of the message.
     *
     * @param  object|array|string  $address
     * @param  string|null  $name
     * @return self
     */
    public function cc($address, $name = null);

    /**
     * Set the recipients of the message.
     *
     * @param  object|array|string  $address
     * @param  string|null  $name
     * @return $this
     */
    public function bcc($address, $name = null);

    /**
     * Set the recipients of the message.
     *
     * @param  object|array|string  $address
     * @param  string|null  $name
     * @return $this
     */
    public function to($address, $name = null);

    /**
     * Set the locale of the message.
     *
     * @param  string  $locale
     * @return $this
     */
    public function locale($locale);

    /**
     * Set the name of the mailer that should be used to send the message.
     *
     * @param  string  $mailer
     * @return $this
     */
    public function mailer($mailer);
}
<?php

namespace Illuminate\Contracts\Mail;

interface Attachable
{
    /**
     * Get an attachment instance for this entity.
     *
     * @return \Illuminate\Mail\Attachment
     */
    public function toMailAttachment();
}
<?php

namespace Illuminate\Contracts\Events;

interface Dispatcher
{
    /**
     * Register an event listener with the dispatcher.
     *
     * @param  \Closure|string|array  $events
     * @param  \Closure|string|array|null  $listener
     * @return void
     */
    public function listen($events, $listener = null);

    /**
     * Determine if a given event has listeners.
     *
     * @param  string  $eventName
     * @return bool
     */
    public function hasListeners($eventName);

    /**
     * Register an event subscriber with the dispatcher.
     *
     * @param  object|string  $subscriber
     * @return void
     */
    public function subscribe($subscriber);

    /**
     * Dispatch an event until the first non-null response is returned.
     *
     * @param  string|object  $event
     * @param  mixed  $payload
     * @return mixed
     */
    public function until($event, $payload = []);

    /**
     * Dispatch an event and call the listeners.
     *
     * @param  string|object  $event
     * @param  mixed  $payload
     * @param  bool  $halt
     * @return array|null
     */
    public function dispatch($event, $payload = [], $halt = false);

    /**
     * Register an event and payload to be fired later.
     *
     * @param  string  $event
     * @param  array  $payload
     * @return void
     */
    public function push($event, $payload = []);

    /**
     * Flush a set of pushed events.
     *
     * @param  string  $event
     * @return void
     */
    public function flush($event);

    /**
     * Remove a set of listeners from the dispatcher.
     *
     * @param  string  $event
     * @return void
     */
    public function forget($event);

    /**
     * Forget all of the queued listeners.
     *
     * @return void
     */
    public function forgetPushed();
}
<?php

namespace Illuminate\Contracts\Events;

interface ShouldDispatchAfterCommit
{
    //
}
<?php

namespace Illuminate\Contracts\Events;

interface ShouldHandleEventsAfterCommit
{
    //
}
<?php

namespace Illuminate\Contracts\Broadcasting;

interface ShouldBroadcast
{
    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|\Illuminate\Broadcasting\Channel[]|string[]|string
     */
    public function broadcastOn();
}
<?php

namespace Illuminate\Contracts\Broadcasting;

interface Factory
{
    /**
     * Get a broadcaster implementation by name.
     *
     * @param  string|null  $name
     * @return \Illuminate\Contracts\Broadcasting\Broadcaster
     */
    public function connection($name = null);
}
<?php

namespace Illuminate\Contracts\Broadcasting;

interface HasBroadcastChannel
{
    /**
     * Get the broadcast channel route definition that is associated with the given entity.
     *
     * @return string
     */
    public function broadcastChannelRoute();

    /**
     * Get the broadcast channel name that is associated with the given entity.
     *
     * @return string
     */
    public function broadcastChannel();
}
<?php

namespace Illuminate\Contracts\Broadcasting;

interface ShouldBroadcastNow extends ShouldBroadcast
{
    //
}
<?php

namespace Illuminate\Contracts\Broadcasting;

interface Broadcaster
{
    /**
     * Authenticate the incoming request for a given channel.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return mixed
     */
    public function auth($request);

    /**
     * Return the valid authentication response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  mixed  $result
     * @return mixed
     */
    public function validAuthenticationResponse($request, $result);

    /**
     * Broadcast the given event.
     *
     * @param  array  $channels
     * @param  string  $event
     * @param  array  $payload
     * @return void
     *
     * @throws \Illuminate\Broadcasting\BroadcastException
     */
    public function broadcast(array $channels, $event, array $payload = []);
}
<?php

namespace Illuminate\Contracts\Broadcasting;

interface ShouldBeUnique
{
    //
}
<?php

namespace Illuminate\Contracts\Process;

interface ProcessResult
{
    /**
     * Get the original command executed by the process.
     *
     * @return string
     */
    public function command();

    /**
     * Determine if the process was successful.
     *
     * @return bool
     */
    public function successful();

    /**
     * Determine if the process failed.
     *
     * @return bool
     */
    public function failed();

    /**
     * Get the exit code of the process.
     *
     * @return int|null
     */
    public function exitCode();

    /**
     * Get the standard output of the process.
     *
     * @return string
     */
    public function output();

    /**
     * Get the error output of the process.
     *
     * @return string
     */
    public function errorOutput();

    /**
     * Throw an exception if the process failed.
     *
     * @param  callable|null  $callback
     * @return $this
     */
    public function throw(?callable $callback = null);

    /**
     * Throw an exception if the process failed and the given condition is true.
     *
     * @param  bool  $condition
     * @param  callable|null  $callback
     * @return $this
     */
    public function throwIf(bool $condition, ?callable $callback = null);
}
<?php

namespace Illuminate\Contracts\Process;

interface InvokedProcess
{
    /**
     * Get the process ID if the process is still running.
     *
     * @return int|null
     */
    public function id();

    /**
     * Send a signal to the process.
     *
     * @param  int  $signal
     * @return $this
     */
    public function signal(int $signal);

    /**
     * Determine if the process is still running.
     *
     * @return bool
     */
    public function running();

    /**
     * Get the standard output for the process.
     *
     * @return string
     */
    public function output();

    /**
     * Get the error output for the process.
     *
     * @return string
     */
    public function errorOutput();

    /**
     * Get the latest standard output for the process.
     *
     * @return string
     */
    public function latestOutput();

    /**
     * Get the latest error output for the process.
     *
     * @return string
     */
    public function latestErrorOutput();

    /**
     * Wait for the process to finish.
     *
     * @param  callable|null  $output
     * @return \Illuminate\Console\Process\ProcessResult
     */
    public function wait(?callable $output = null);
}
<?php

namespace Illuminate\Contracts\Cache;

interface Factory
{
    /**
     * Get a cache store instance by name.
     *
     * @param  string|null  $name
     * @return \Illuminate\Contracts\Cache\Repository
     */
    public function store($name = null);
}
<?php

namespace Illuminate\Contracts\Cache;

use Exception;

class LockTimeoutException extends Exception
{
    //
}
<?php

namespace Illuminate\Contracts\Cache;

interface Store
{
    /**
     * Retrieve an item from the cache by key.
     *
     * @param  string|array  $key
     * @return mixed
     */
    public function get($key);

    /**
     * Retrieve multiple items from the cache by key.
     *
     * Items not found in the cache will have a null value.
     *
     * @param  array  $keys
     * @return array
     */
    public function many(array $keys);

    /**
     * Store an item in the cache for a given number of seconds.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @param  int  $seconds
     * @return bool
     */
    public function put($key, $value, $seconds);

    /**
     * Store multiple items in the cache for a given number of seconds.
     *
     * @param  array  $values
     * @param  int  $seconds
     * @return bool
     */
    public function putMany(array $values, $seconds);

    /**
     * Increment the value of an item in the cache.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return int|bool
     */
    public function increment($key, $value = 1);

    /**
     * Decrement the value of an item in the cache.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return int|bool
     */
    public function decrement($key, $value = 1);

    /**
     * Store an item in the cache indefinitely.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return bool
     */
    public function forever($key, $value);

    /**
     * Remove an item from the cache.
     *
     * @param  string  $key
     * @return bool
     */
    public function forget($key);

    /**
     * Remove all items from the cache.
     *
     * @return bool
     */
    public function flush();

    /**
     * Get the cache key prefix.
     *
     * @return string
     */
    public function getPrefix();
}
<?php

namespace Illuminate\Contracts\Cache;

use Closure;
use Psr\SimpleCache\CacheInterface;

interface Repository extends CacheInterface
{
    /**
     * Retrieve an item from the cache and delete it.
     *
     * @template TCacheValue
     *
     * @param  array|string  $key
     * @param  TCacheValue|(\Closure(): TCacheValue)  $default
     * @return (TCacheValue is null ? mixed : TCacheValue)
     */
    public function pull($key, $default = null);

    /**
     * Store an item in the cache.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @param  \DateTimeInterface|\DateInterval|int|null  $ttl
     * @return bool
     */
    public function put($key, $value, $ttl = null);

    /**
     * Store an item in the cache if the key does not exist.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @param  \DateTimeInterface|\DateInterval|int|null  $ttl
     * @return bool
     */
    public function add($key, $value, $ttl = null);

    /**
     * Increment the value of an item in the cache.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return int|bool
     */
    public function increment($key, $value = 1);

    /**
     * Decrement the value of an item in the cache.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return int|bool
     */
    public function decrement($key, $value = 1);

    /**
     * Store an item in the cache indefinitely.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return bool
     */
    public function forever($key, $value);

    /**
     * Get an item from the cache, or execute the given Closure and store the result.
     *
     * @template TCacheValue
     *
     * @param  string  $key
     * @param  \DateTimeInterface|\DateInterval|\Closure|int|null  $ttl
     * @param  \Closure(): TCacheValue  $callback
     * @return TCacheValue
     */
    public function remember($key, $ttl, Closure $callback);

    /**
     * Get an item from the cache, or execute the given Closure and store the result forever.
     *
     * @template TCacheValue
     *
     * @param  string  $key
     * @param  \Closure(): TCacheValue  $callback
     * @return TCacheValue
     */
    public function sear($key, Closure $callback);

    /**
     * Get an item from the cache, or execute the given Closure and store the result forever.
     *
     * @template TCacheValue
     *
     * @param  string  $key
     * @param  \Closure(): TCacheValue  $callback
     * @return TCacheValue
     */
    public function rememberForever($key, Closure $callback);

    /**
     * Remove an item from the cache.
     *
     * @param  string  $key
     * @return bool
     */
    public function forget($key);

    /**
     * Get the cache store implementation.
     *
     * @return \Illuminate\Contracts\Cache\Store
     */
    public function getStore();
}
<?php

namespace Illuminate\Contracts\Cache;

interface LockProvider
{
    /**
     * Get a lock instance.
     *
     * @param  string  $name
     * @param  int  $seconds
     * @param  string|null  $owner
     * @return \Illuminate\Contracts\Cache\Lock
     */
    public function lock($name, $seconds = 0, $owner = null);

    /**
     * Restore a lock instance using the owner identifier.
     *
     * @param  string  $name
     * @param  string  $owner
     * @return \Illuminate\Contracts\Cache\Lock
     */
    public function restoreLock($name, $owner);
}
<?php

namespace Illuminate\Contracts\Cache;

interface Lock
{
    /**
     * Attempt to acquire the lock.
     *
     * @param  callable|null  $callback
     * @return mixed
     */
    public function get($callback = null);

    /**
     * Attempt to acquire the lock for the given number of seconds.
     *
     * @param  int  $seconds
     * @param  callable|null  $callback
     * @return mixed
     */
    public function block($seconds, $callback = null);

    /**
     * Release the lock.
     *
     * @return bool
     */
    public function release();

    /**
     * Returns the current owner of the lock.
     *
     * @return string
     */
    public function owner();

    /**
     * Releases this lock in disregard of ownership.
     *
     * @return void
     */
    public function forceRelease();
}
<?php

namespace Illuminate\Contracts\Foundation;

interface CachesRoutes
{
    /**
     * Determine if the application routes are cached.
     *
     * @return bool
     */
    public function routesAreCached();

    /**
     * Get the path to the routes cache file.
     *
     * @return string
     */
    public function getCachedRoutesPath();
}
<?php

namespace Illuminate\Contracts\Foundation;

interface ExceptionRenderer
{
    /**
     * Renders the given exception as HTML.
     *
     * @param  \Throwable  $throwable
     * @return string
     */
    public function render($throwable);
}
<?php

namespace Illuminate\Contracts\Foundation;

use Illuminate\Contracts\Container\Container;

interface Application extends Container
{
    /**
     * Get the version number of the application.
     *
     * @return string
     */
    public function version();

    /**
     * Get the base path of the Laravel installation.
     *
     * @param  string  $path
     * @return string
     */
    public function basePath($path = '');

    /**
     * Get the path to the bootstrap directory.
     *
     * @param  string  $path
     * @return string
     */
    public function bootstrapPath($path = '');

    /**
     * Get the path to the application configuration files.
     *
     * @param  string  $path
     * @return string
     */
    public function configPath($path = '');

    /**
     * Get the path to the database directory.
     *
     * @param  string  $path
     * @return string
     */
    public function databasePath($path = '');

    /**
     * Get the path to the language files.
     *
     * @param  string  $path
     * @return string
     */
    public function langPath($path = '');

    /**
     * Get the path to the public directory.
     *
     * @param  string  $path
     * @return string
     */
    public function publicPath($path = '');

    /**
     * Get the path to the resources directory.
     *
     * @param  string  $path
     * @return string
     */
    public function resourcePath($path = '');

    /**
     * Get the path to the storage directory.
     *
     * @param  string  $path
     * @return string
     */
    public function storagePath($path = '');

    /**
     * Get or check the current application environment.
     *
     * @param  string|array  ...$environments
     * @return string|bool
     */
    public function environment(...$environments);

    /**
     * Determine if the application is running in the console.
     *
     * @return bool
     */
    public function runningInConsole();

    /**
     * Determine if the application is running unit tests.
     *
     * @return bool
     */
    public function runningUnitTests();

    /**
     * Determine if the application is running with debug mode enabled.
     *
     * @return bool
     */
    public function hasDebugModeEnabled();

    /**
     * Get an instance of the maintenance mode manager implementation.
     *
     * @return \Illuminate\Contracts\Foundation\MaintenanceMode
     */
    public function maintenanceMode();

    /**
     * Determine if the application is currently down for maintenance.
     *
     * @return bool
     */
    public function isDownForMaintenance();

    /**
     * Register all of the configured providers.
     *
     * @return void
     */
    public function registerConfiguredProviders();

    /**
     * Register a service provider with the application.
     *
     * @param  \Illuminate\Support\ServiceProvider|string  $provider
     * @param  bool  $force
     * @return \Illuminate\Support\ServiceProvider
     */
    public function register($provider, $force = false);

    /**
     * Register a deferred provider and service.
     *
     * @param  string  $provider
     * @param  string|null  $service
     * @return void
     */
    public function registerDeferredProvider($provider, $service = null);

    /**
     * Resolve a service provider instance from the class name.
     *
     * @param  string  $provider
     * @return \Illuminate\Support\ServiceProvider
     */
    public function resolveProvider($provider);

    /**
     * Boot the application's service providers.
     *
     * @return void
     */
    public function boot();

    /**
     * Register a new boot listener.
     *
     * @param  callable  $callback
     * @return void
     */
    public function booting($callback);

    /**
     * Register a new "booted" listener.
     *
     * @param  callable  $callback
     * @return void
     */
    public function booted($callback);

    /**
     * Run the given array of bootstrap classes.
     *
     * @param  array  $bootstrappers
     * @return void
     */
    public function bootstrapWith(array $bootstrappers);

    /**
     * Get the current application locale.
     *
     * @return string
     */
    public function getLocale();

    /**
     * Get the application namespace.
     *
     * @return string
     *
     * @throws \RuntimeException
     */
    public function getNamespace();

    /**
     * Get the registered service provider instances if any exist.
     *
     * @param  \Illuminate\Support\ServiceProvider|string  $provider
     * @return array
     */
    public function getProviders($provider);

    /**
     * Determine if the application has been bootstrapped before.
     *
     * @return bool
     */
    public function hasBeenBootstrapped();

    /**
     * Load and boot all of the remaining deferred providers.
     *
     * @return void
     */
    public function loadDeferredProviders();

    /**
     * Set the current application locale.
     *
     * @param  string  $locale
     * @return void
     */
    public function setLocale($locale);

    /**
     * Determine if middleware has been disabled for the application.
     *
     * @return bool
     */
    public function shouldSkipMiddleware();

    /**
     * Register a terminating callback with the application.
     *
     * @param  callable|string  $callback
     * @return \Illuminate\Contracts\Foundation\Application
     */
    public function terminating($callback);

    /**
     * Terminate the application.
     *
     * @return void
     */
    public function terminate();
}
<?php

namespace Illuminate\Contracts\Foundation;

interface MaintenanceMode
{
    /**
     * Take the application down for maintenance.
     *
     * @param  array  $payload
     * @return void
     */
    public function activate(array $payload): void;

    /**
     * Take the application out of maintenance.
     *
     * @return void
     */
    public function deactivate(): void;

    /**
     * Determine if the application is currently down for maintenance.
     *
     * @return bool
     */
    public function active(): bool;

    /**
     * Get the data array which was provided when the application was placed into maintenance.
     *
     * @return array
     */
    public function data(): array;
}
<?php

namespace Illuminate\Contracts\Foundation;

interface CachesConfiguration
{
    /**
     * Determine if the application configuration is cached.
     *
     * @return bool
     */
    public function configurationIsCached();

    /**
     * Get the path to the configuration cache file.
     *
     * @return string
     */
    public function getCachedConfigPath();

    /**
     * Get the path to the cached services.php file.
     *
     * @return string
     */
    public function getCachedServicesPath();
}
<?php

namespace Illuminate\Contracts\Hashing;

interface Hasher
{
    /**
     * Get information about the given hashed value.
     *
     * @param  string  $hashedValue
     * @return array
     */
    public function info($hashedValue);

    /**
     * Hash the given value.
     *
     * @param  string  $value
     * @param  array  $options
     * @return string
     */
    public function make($value, array $options = []);

    /**
     * Check the given plain value against a hash.
     *
     * @param  string  $value
     * @param  string  $hashedValue
     * @param  array  $options
     * @return bool
     */
    public function check($value, $hashedValue, array $options = []);

    /**
     * Check if the given hash has been hashed using the given options.
     *
     * @param  string  $hashedValue
     * @param  array  $options
     * @return bool
     */
    public function needsRehash($hashedValue, array $options = []);
}
<?php

namespace Illuminate\Contracts\Queue;

interface ShouldQueue
{
    //
}
<?php

namespace Illuminate\Contracts\Queue;

interface Factory
{
    /**
     * Resolve a queue connection instance.
     *
     * @param  string|null  $name
     * @return \Illuminate\Contracts\Queue\Queue
     */
    public function connection($name = null);
}
<?php

namespace Illuminate\Contracts\Queue;

interface ShouldBeEncrypted
{
    //
}
<?php

namespace Illuminate\Contracts\Queue;

interface Queue
{
    /**
     * Get the size of the queue.
     *
     * @param  string|null  $queue
     * @return int
     */
    public function size($queue = null);

    /**
     * Push a new job onto the queue.
     *
     * @param  string|object  $job
     * @param  mixed  $data
     * @param  string|null  $queue
     * @return mixed
     */
    public function push($job, $data = '', $queue = null);

    /**
     * Push a new job onto the queue.
     *
     * @param  string  $queue
     * @param  string|object  $job
     * @param  mixed  $data
     * @return mixed
     */
    public function pushOn($queue, $job, $data = '');

    /**
     * Push a raw payload onto the queue.
     *
     * @param  string  $payload
     * @param  string|null  $queue
     * @param  array  $options
     * @return mixed
     */
    public function pushRaw($payload, $queue = null, array $options = []);

    /**
     * Push a new job onto the queue after (n) seconds.
     *
     * @param  \DateTimeInterface|\DateInterval|int  $delay
     * @param  string|object  $job
     * @param  mixed  $data
     * @param  string|null  $queue
     * @return mixed
     */
    public function later($delay, $job, $data = '', $queue = null);

    /**
     * Push a new job onto a specific queue after (n) seconds.
     *
     * @param  string  $queue
     * @param  \DateTimeInterface|\DateInterval|int  $delay
     * @param  string|object  $job
     * @param  mixed  $data
     * @return mixed
     */
    public function laterOn($queue, $delay, $job, $data = '');

    /**
     * Push an array of jobs onto the queue.
     *
     * @param  array  $jobs
     * @param  mixed  $data
     * @param  string|null  $queue
     * @return mixed
     */
    public function bulk($jobs, $data = '', $queue = null);

    /**
     * Pop the next job off of the queue.
     *
     * @param  string|null  $queue
     * @return \Illuminate\Contracts\Queue\Job|null
     */
    public function pop($queue = null);

    /**
     * Get the connection name for the queue.
     *
     * @return string
     */
    public function getConnectionName();

    /**
     * Set the connection name for the queue.
     *
     * @param  string  $name
     * @return $this
     */
    public function setConnectionName($name);
}
<?php

namespace Illuminate\Contracts\Queue;

interface QueueableEntity
{
    /**
     * Get the queueable identity for the entity.
     *
     * @return mixed
     */
    public function getQueueableId();

    /**
     * Get the relationships for the entity.
     *
     * @return array
     */
    public function getQueueableRelations();

    /**
     * Get the connection of the entity.
     *
     * @return string|null
     */
    public function getQueueableConnection();
}
<?php

namespace Illuminate\Contracts\Queue;

interface QueueableCollection
{
    /**
     * Get the type of the entities being queued.
     *
     * @return string|null
     */
    public function getQueueableClass();

    /**
     * Get the identifiers for all of the entities.
     *
     * @return array<int, mixed>
     */
    public function getQueueableIds();

    /**
     * Get the relationships of the entities being queued.
     *
     * @return array<int, string>
     */
    public function getQueueableRelations();

    /**
     * Get the connection of the entities being queued.
     *
     * @return string|null
     */
    public function getQueueableConnection();
}
<?php

namespace Illuminate\Contracts\Queue;

interface EntityResolver
{
    /**
     * Resolve the entity for the given ID.
     *
     * @param  string  $type
     * @param  mixed  $id
     * @return mixed
     */
    public function resolve($type, $id);
}
<?php

namespace Illuminate\Contracts\Queue;

interface ShouldQueueAfterCommit extends ShouldQueue
{
    //
}
<?php

namespace Illuminate\Contracts\Queue;

interface ShouldBeUniqueUntilProcessing extends ShouldBeUnique
{
    //
}
<?php

namespace Illuminate\Contracts\Queue;

interface Monitor
{
    /**
     * Register a callback to be executed on every iteration through the queue loop.
     *
     * @param  mixed  $callback
     * @return void
     */
    public function looping($callback);

    /**
     * Register a callback to be executed when a job fails after the maximum number of retries.
     *
     * @param  mixed  $callback
     * @return void
     */
    public function failing($callback);

    /**
     * Register a callback to be executed when a daemon queue is stopping.
     *
     * @param  mixed  $callback
     * @return void
     */
    public function stopping($callback);
}
<?php

namespace Illuminate\Contracts\Queue;

use InvalidArgumentException;

class EntityNotFoundException extends InvalidArgumentException
{
    /**
     * Create a new exception instance.
     *
     * @param  string  $type
     * @param  mixed  $id
     * @return void
     */
    public function __construct($type, $id)
    {
        $id = (string) $id;

        parent::__construct("Queueable entity [{$type}] not found for ID [{$id}].");
    }
}
<?php

namespace Illuminate\Contracts\Queue;

interface ClearableQueue
{
    /**
     * Delete all of the jobs from the queue.
     *
     * @param  string  $queue
     * @return int
     */
    public function clear($queue);
}
<?php

namespace Illuminate\Contracts\Queue;

interface Job
{
    /**
     * Get the UUID of the job.
     *
     * @return string|null
     */
    public function uuid();

    /**
     * Get the job identifier.
     *
     * @return string
     */
    public function getJobId();

    /**
     * Get the decoded body of the job.
     *
     * @return array
     */
    public function payload();

    /**
     * Fire the job.
     *
     * @return void
     */
    public function fire();

    /**
     * Release the job back into the queue after (n) seconds.
     *
     * @param  int  $delay
     * @return void
     */
    public function release($delay = 0);

    /**
     * Determine if the job was released back into the queue.
     *
     * @return bool
     */
    public function isReleased();

    /**
     * Delete the job from the queue.
     *
     * @return void
     */
    public function delete();

    /**
     * Determine if the job has been deleted.
     *
     * @return bool
     */
    public function isDeleted();

    /**
     * Determine if the job has been deleted or released.
     *
     * @return bool
     */
    public function isDeletedOrReleased();

    /**
     * Get the number of times the job has been attempted.
     *
     * @return int
     */
    public function attempts();

    /**
     * Determine if the job has been marked as a failure.
     *
     * @return bool
     */
    public function hasFailed();

    /**
     * Mark the job as "failed".
     *
     * @return void
     */
    public function markAsFailed();

    /**
     * Delete the job, call the "failed" method, and raise the failed job event.
     *
     * @param  \Throwable|null  $e
     * @return void
     */
    public function fail($e = null);

    /**
     * Get the number of times to attempt a job.
     *
     * @return int|null
     */
    public function maxTries();

    /**
     * Get the maximum number of exceptions allowed, regardless of attempts.
     *
     * @return int|null
     */
    public function maxExceptions();

    /**
     * Get the number of seconds the job can run.
     *
     * @return int|null
     */
    public function timeout();

    /**
     * Get the timestamp indicating when the job should timeout.
     *
     * @return int|null
     */
    public function retryUntil();

    /**
     * Get the name of the queued job class.
     *
     * @return string
     */
    public function getName();

    /**
     * Get the resolved name of the queued job class.
     *
     * Resolves the name of "wrapped" jobs such as class-based handlers.
     *
     * @return string
     */
    public function resolveName();

    /**
     * Get the name of the connection the job belongs to.
     *
     * @return string
     */
    public function getConnectionName();

    /**
     * Get the name of the queue the job belongs to.
     *
     * @return string
     */
    public function getQueue();

    /**
     * Get the raw body string for the job.
     *
     * @return string
     */
    public function getRawBody();
}
<?php

namespace Illuminate\Contracts\Queue;

interface ShouldBeUnique
{
    //
}
<?php

namespace Illuminate\Contracts\View;

interface Factory
{
    /**
     * Determine if a given view exists.
     *
     * @param  string  $view
     * @return bool
     */
    public function exists($view);

    /**
     * Get the evaluated view contents for the given path.
     *
     * @param  string  $path
     * @param  \Illuminate\Contracts\Support\Arrayable|array  $data
     * @param  array  $mergeData
     * @return \Illuminate\Contracts\View\View
     */
    public function file($path, $data = [], $mergeData = []);

    /**
     * Get the evaluated view contents for the given view.
     *
     * @param  string  $view
     * @param  \Illuminate\Contracts\Support\Arrayable|array  $data
     * @param  array  $mergeData
     * @return \Illuminate\Contracts\View\View
     */
    public function make($view, $data = [], $mergeData = []);

    /**
     * Add a piece of shared data to the environment.
     *
     * @param  array|string  $key
     * @param  mixed  $value
     * @return mixed
     */
    public function share($key, $value = null);

    /**
     * Register a view composer event.
     *
     * @param  array|string  $views
     * @param  \Closure|string  $callback
     * @return array
     */
    public function composer($views, $callback);

    /**
     * Register a view creator event.
     *
     * @param  array|string  $views
     * @param  \Closure|string  $callback
     * @return array
     */
    public function creator($views, $callback);

    /**
     * Add a new namespace to the loader.
     *
     * @param  string  $namespace
     * @param  string|array  $hints
     * @return $this
     */
    public function addNamespace($namespace, $hints);

    /**
     * Replace the namespace hints for the given namespace.
     *
     * @param  string  $namespace
     * @param  string|array  $hints
     * @return $this
     */
    public function replaceNamespace($namespace, $hints);
}
<?php

namespace Illuminate\Contracts\View;

interface Engine
{
    /**
     * Get the evaluated contents of the view.
     *
     * @param  string  $path
     * @param  array  $data
     * @return string
     */
    public function get($path, array $data = []);
}
<?php

namespace Illuminate\Contracts\View;

use Illuminate\Contracts\Support\Renderable;

interface View extends Renderable
{
    /**
     * Get the name of the view.
     *
     * @return string
     */
    public function name();

    /**
     * Add a piece of data to the view.
     *
     * @param  string|array  $key
     * @param  mixed  $value
     * @return $this
     */
    public function with($key, $value = null);

    /**
     * Get the array of view data.
     *
     * @return array
     */
    public function getData();
}
<?php

namespace Illuminate\Contracts\View;

use Exception;

class ViewCompilationException extends Exception
{
    //
}
<?php

namespace Illuminate\Contracts\Translation;

interface Loader
{
    /**
     * Load the messages for the given locale.
     *
     * @param  string  $locale
     * @param  string  $group
     * @param  string|null  $namespace
     * @return array
     */
    public function load($locale, $group, $namespace = null);

    /**
     * Add a new namespace to the loader.
     *
     * @param  string  $namespace
     * @param  string  $hint
     * @return void
     */
    public function addNamespace($namespace, $hint);

    /**
     * Add a new JSON path to the loader.
     *
     * @param  string  $path
     * @return void
     */
    public function addJsonPath($path);

    /**
     * Get an array of all the registered namespaces.
     *
     * @return array
     */
    public function namespaces();
}
<?php

namespace Illuminate\Contracts\Translation;

interface Translator
{
    /**
     * Get the translation for a given key.
     *
     * @param  string  $key
     * @param  array  $replace
     * @param  string|null  $locale
     * @return mixed
     */
    public function get($key, array $replace = [], $locale = null);

    /**
     * Get a translation according to an integer value.
     *
     * @param  string  $key
     * @param  \Countable|int|float|array  $number
     * @param  array  $replace
     * @param  string|null  $locale
     * @return string
     */
    public function choice($key, $number, array $replace = [], $locale = null);

    /**
     * Get the default locale being used.
     *
     * @return string
     */
    public function getLocale();

    /**
     * Set the default locale.
     *
     * @param  string  $locale
     * @return void
     */
    public function setLocale($locale);
}
<?php

namespace Illuminate\Contracts\Translation;

interface HasLocalePreference
{
    /**
     * Get the preferred locale of the entity.
     *
     * @return string|null
     */
    public function preferredLocale();
}
<?php

namespace Illuminate\Contracts\Pipeline;

interface Hub
{
    /**
     * Send an object through one of the available pipelines.
     *
     * @param  mixed  $object
     * @param  string|null  $pipeline
     * @return mixed
     */
    public function pipe($object, $pipeline = null);
}
<?php

namespace Illuminate\Contracts\Pipeline;

use Closure;

interface Pipeline
{
    /**
     * Set the traveler object being sent on the pipeline.
     *
     * @param  mixed  $traveler
     * @return $this
     */
    public function send($traveler);

    /**
     * Set the stops of the pipeline.
     *
     * @param  dynamic|array  $stops
     * @return $this
     */
    public function through($stops);

    /**
     * Set the method to call on the stops.
     *
     * @param  string  $method
     * @return $this
     */
    public function via($method);

    /**
     * Run the pipeline with a final destination callback.
     *
     * @param  \Closure  $destination
     * @return mixed
     */
    public function then(Closure $destination);
}
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.
<?php

/*
 * This file is part of the webmozart/assert package.
 *
 * (c) Bernhard Schussek <bschussek@gmail.com>
 *
 * 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 <bschussek@gmail.com>
 */
class Assert
{
    use Mixin;

    /**
     * @psalm-pure
     *
     * @psalm-assert string $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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),
                $type // User supplied message might include the second placeholder.
            ));
        }

        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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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<ExpectedType> $class
     *
     * @psalm-assert ExpectedType $value
     *
     * @param mixed         $value
     * @param string|object $class
     * @param string        $message
     *
     * @return void
     *
     * @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<ExpectedType> $class
     *
     * @psalm-assert !ExpectedType $value
     *
     * @param mixed         $value
     * @param string|object $class
     * @param string        $message
     *
     * @return void
     *
     * @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<class-string> $classes
     *
     * @param mixed                $value
     * @param array<object|string> $classes
     * @param string               $message
     *
     * @return void
     *
     * @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<ExpectedType> $class
     *
     * @psalm-assert ExpectedType|class-string<ExpectedType> $value
     *
     * @param object|string $value
     * @param string        $class
     * @param string        $message
     *
     * @return void
     *
     * @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<UnexpectedType> $class
     *
     * @psalm-assert !UnexpectedType $value
     * @psalm-assert !class-string<UnexpectedType> $value
     *
     * @param object|string $value
     * @param string        $class
     * @param string        $message
     *
     * @return void
     *
     * @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<class-string> $classes
     *
     * @param object|string $value
     * @param string[]      $classes
     * @param string        $message
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function digits($value, $message = '')
    {
        static::string($value);

        $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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function alnum($value, $message = '')
    {
        static::string($value);

        $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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function lower($value, $message = '')
    {
        static::string($value);

        $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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function upper($value, $message = '')
    {
        static::string($value);

        $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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function fileExists($value, $message = '')
    {
        if (!\file_exists($value)) {
            static::reportInvalidArgument(\sprintf(
                $message ?: 'The path %s does not exist.',
                static::valueToString($value)
            ));
        }
    }

    /**
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function file($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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function directory($value, $message = '')
    {
        if (!\is_dir($value)) {
            static::reportInvalidArgument(\sprintf(
                $message ?: 'The path %s is not a directory.',
                static::valueToString($value)
            ));
        }
    }

    /**
     * @param string $value
     * @param string $message
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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<ExpectedType> $class
     *
     * @psalm-assert class-string<ExpectedType>|ExpectedType $value
     *
     * @param mixed         $value
     * @param string|object $class
     * @param string        $message
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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<ExpectedType> $interface
     *
     * @psalm-assert class-string<ExpectedType>|ExpectedType $value
     *
     * @param mixed  $value
     * @param mixed  $interface
     * @param string $message
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function isList($array, $message = '')
    {
        if (!\is_array($array)) {
            static::reportInvalidArgument(
                $message ?: 'Expected list - non-associative array.'
            );
        }

        if (\function_exists('array_is_list')) {
            if (!\array_is_list($array)) {
                static::reportInvalidArgument(
                    $message ?: 'Expected list - non-associative array.'
                );
            }

            return;
        }

        if (array() === $array) {
            return;
        }

        $keys = array_keys($array);
        if (array_keys($keys) !== $keys) {
            static::reportInvalidArgument(
                $message ?: 'Expected list - non-associative array.'
            );
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert non-empty-list $array
     *
     * @param mixed  $array
     * @param string $message
     *
     * @return void
     *
     * @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<T> $array
     *
     * @psalm-assert array<string, T> $array
     *
     * @param mixed  $array
     * @param string $message
     *
     * @return void
     *
     * @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<T> $array
     *
     * @psalm-assert array<string, T> $array
     * @psalm-assert !empty $array
     *
     * @param mixed  $array
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function isNonEmptyMap($array, $message = '')
    {
        static::isMap($array, $message);
        static::notEmpty($array, $message);
    }

    /**
     * @psalm-pure
     *
     * @param string $value
     * @param string $message
     *
     * @return void
     *
     * @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}$/D', $value)) {
            static::reportInvalidArgument(\sprintf(
                $message ?: 'Value %s is not a valid UUID.',
                static::valueToString($value)
            ));
        }
    }

    /**
     * @psalm-param class-string<Throwable> $class
     *
     * @param Closure $expression
     * @param string  $class
     * @param string  $message
     *
     * @return void
     *
     * @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'));
            }

            if (\function_exists('enum_exists') && \enum_exists(\get_class($value))) {
                return \get_class($value).'::'.$value->name;
            }

            return \get_class($value);
        }

        if (\is_resource($value)) {
            return 'resource';
        }

        if (\is_string($value)) {
            return '"'.$value.'"';
        }

        return (string) $value;
    }

    /**
     * @psalm-pure
     *
     * @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()
    {
    }
}
<?php

namespace Webmozart\Assert;

use ArrayAccess;
use Closure;
use Countable;
use Throwable;

/**
 * This trait provides nullOr*, all* and allNullOr* variants of assertion base methods.
 * Do not use this trait directly: it will change, and is not designed for reuse.
 */
trait Mixin
{
    /**
     * @psalm-pure
     *
     * @psalm-assert string|null $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrString($value, $message = '')
    {
        null === $value || static::string($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<string> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allString($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::string($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<string|null> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrStringNotEmpty($value, $message = '')
    {
        null === $value || static::stringNotEmpty($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<non-empty-string> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allStringNotEmpty($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::stringNotEmpty($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<non-empty-string|null> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrInteger($value, $message = '')
    {
        null === $value || static::integer($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<int> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allInteger($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::integer($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<int|null> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrIntegerish($value, $message = '')
    {
        null === $value || static::integerish($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<numeric> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allIntegerish($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::integerish($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<numeric|null> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrPositiveInteger($value, $message = '')
    {
        null === $value || static::positiveInteger($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<positive-int> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allPositiveInteger($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::positiveInteger($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<positive-int|null> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrFloat($value, $message = '')
    {
        null === $value || static::float($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<float> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allFloat($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::float($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<float|null> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrNumeric($value, $message = '')
    {
        null === $value || static::numeric($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<numeric> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allNumeric($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::numeric($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<numeric|null> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrNatural($value, $message = '')
    {
        null === $value || static::natural($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<positive-int|0> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allNatural($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::natural($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<positive-int|0|null> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrBoolean($value, $message = '')
    {
        null === $value || static::boolean($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<bool> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allBoolean($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::boolean($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<bool|null> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrScalar($value, $message = '')
    {
        null === $value || static::scalar($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<scalar> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allScalar($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::scalar($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<scalar|null> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrObject($value, $message = '')
    {
        null === $value || static::object($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<object> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allObject($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::object($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<object|null> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrResource($value, $type = null, $message = '')
    {
        null === $value || static::resource($value, $type, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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<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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrIsCallable($value, $message = '')
    {
        null === $value || static::isCallable($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<callable> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allIsCallable($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::isCallable($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<callable|null> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrIsArray($value, $message = '')
    {
        null === $value || static::isArray($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<array> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allIsArray($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::isArray($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<array|null> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrIsTraversable($value, $message = '')
    {
        null === $value || static::isTraversable($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<iterable> $value
     *
     * @deprecated use "isIterable" or "isInstanceOf" instead
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allIsTraversable($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::isTraversable($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<iterable|null> $value
     *
     * @deprecated use "isIterable" or "isInstanceOf" instead
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrIsArrayAccessible($value, $message = '')
    {
        null === $value || static::isArrayAccessible($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<array|ArrayAccess> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allIsArrayAccessible($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::isArrayAccessible($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<array|ArrayAccess|null> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrIsCountable($value, $message = '')
    {
        null === $value || static::isCountable($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<countable> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allIsCountable($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::isCountable($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<countable|null> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrIsIterable($value, $message = '')
    {
        null === $value || static::isIterable($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<iterable> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allIsIterable($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::isIterable($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<iterable|null> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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<ExpectedType> $class
     * @psalm-assert ExpectedType|null $value
     *
     * @param mixed         $value
     * @param string|object $class
     * @param string        $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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<ExpectedType> $class
     * @psalm-assert iterable<ExpectedType> $value
     *
     * @param mixed         $value
     * @param string|object $class
     * @param string        $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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<ExpectedType> $class
     * @psalm-assert iterable<ExpectedType|null> $value
     *
     * @param mixed         $value
     * @param string|object $class
     * @param string        $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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<ExpectedType> $class
     *
     * @param mixed         $value
     * @param string|object $class
     * @param string        $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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<ExpectedType> $class
     *
     * @param mixed         $value
     * @param string|object $class
     * @param string        $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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<ExpectedType> $class
     * @psalm-assert iterable<!ExpectedType|null> $value
     *
     * @param mixed         $value
     * @param string|object $class
     * @param string        $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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<class-string> $classes
     *
     * @param mixed                $value
     * @param array<object|string> $classes
     * @param string               $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrIsInstanceOfAny($value, $classes, $message = '')
    {
        null === $value || static::isInstanceOfAny($value, $classes, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-param array<class-string> $classes
     *
     * @param mixed                $value
     * @param array<object|string> $classes
     * @param string               $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allIsInstanceOfAny($value, $classes, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::isInstanceOfAny($entry, $classes, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-param array<class-string> $classes
     *
     * @param mixed                $value
     * @param array<object|string> $classes
     * @param string               $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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<ExpectedType> $class
     * @psalm-assert ExpectedType|class-string<ExpectedType>|null $value
     *
     * @param object|string|null $value
     * @param string             $class
     * @param string             $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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<ExpectedType> $class
     * @psalm-assert iterable<ExpectedType|class-string<ExpectedType>> $value
     *
     * @param iterable<object|string> $value
     * @param string                  $class
     * @param string                  $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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<ExpectedType> $class
     * @psalm-assert iterable<ExpectedType|class-string<ExpectedType>|null> $value
     *
     * @param iterable<object|string|null> $value
     * @param string                       $class
     * @param string                       $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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<UnexpectedType> $class
     *
     * @param object|string|null $value
     * @param string             $class
     * @param string             $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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<UnexpectedType> $class
     *
     * @param iterable<object|string> $value
     * @param string                  $class
     * @param string                  $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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<UnexpectedType> $class
     * @psalm-assert iterable<!UnexpectedType|null> $value
     * @psalm-assert iterable<!class-string<UnexpectedType>|null> $value
     *
     * @param iterable<object|string|null> $value
     * @param string                       $class
     * @param string                       $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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<class-string> $classes
     *
     * @param object|string|null $value
     * @param string[]           $classes
     * @param string             $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrIsAnyOf($value, $classes, $message = '')
    {
        null === $value || static::isAnyOf($value, $classes, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-param array<class-string> $classes
     *
     * @param iterable<object|string> $value
     * @param string[]                $classes
     * @param string                  $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allIsAnyOf($value, $classes, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::isAnyOf($entry, $classes, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-param array<class-string> $classes
     *
     * @param iterable<object|string|null> $value
     * @param string[]                     $classes
     * @param string                       $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrIsEmpty($value, $message = '')
    {
        null === $value || static::isEmpty($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<empty> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allIsEmpty($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::isEmpty($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<empty|null> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrNotEmpty($value, $message = '')
    {
        null === $value || static::notEmpty($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allNotEmpty($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::notEmpty($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<!empty|null> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allNullOrNotEmpty($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            null === $entry || static::notEmpty($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<null> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrTrue($value, $message = '')
    {
        null === $value || static::true($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<true> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allTrue($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::true($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<true|null> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrFalse($value, $message = '')
    {
        null === $value || static::false($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<false> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allFalse($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::false($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<false|null> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrNotFalse($value, $message = '')
    {
        null === $value || static::notFalse($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allNotFalse($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::notFalse($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<!false|null> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrIp($value, $message = '')
    {
        null === $value || static::ip($value, $message);
    }

    /**
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allIp($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::ip($entry, $message);
        }
    }

    /**
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrIpv4($value, $message = '')
    {
        null === $value || static::ipv4($value, $message);
    }

    /**
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allIpv4($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::ipv4($entry, $message);
        }
    }

    /**
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrIpv6($value, $message = '')
    {
        null === $value || static::ipv6($value, $message);
    }

    /**
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allIpv6($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::ipv6($entry, $message);
        }
    }

    /**
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrEmail($value, $message = '')
    {
        null === $value || static::email($value, $message);
    }

    /**
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allEmail($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::email($entry, $message);
        }
    }

    /**
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrUniqueValues($values, $message = '')
    {
        null === $values || static::uniqueValues($values, $message);
    }

    /**
     * @param iterable<array> $values
     * @param string          $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allUniqueValues($values, $message = '')
    {
        static::isIterable($values);

        foreach ($values as $entry) {
            static::uniqueValues($entry, $message);
        }
    }

    /**
     * @param iterable<array|null> $values
     * @param string               $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrEq($value, $expect, $message = '')
    {
        null === $value || static::eq($value, $expect, $message);
    }

    /**
     * @param mixed  $value
     * @param mixed  $expect
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrNotEq($value, $expect, $message = '')
    {
        null === $value || static::notEq($value, $expect, $message);
    }

    /**
     * @param mixed  $value
     * @param mixed  $expect
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrContains($value, $subString, $message = '')
    {
        null === $value || static::contains($value, $subString, $message);
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string> $value
     * @param string           $subString
     * @param string           $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allContains($value, $subString, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::contains($entry, $subString, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string|null> $value
     * @param string                $subString
     * @param string                $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrNotContains($value, $subString, $message = '')
    {
        null === $value || static::notContains($value, $subString, $message);
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string> $value
     * @param string           $subString
     * @param string           $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allNotContains($value, $subString, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::notContains($entry, $subString, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string|null> $value
     * @param string                $subString
     * @param string                $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrNotWhitespaceOnly($value, $message = '')
    {
        null === $value || static::notWhitespaceOnly($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string> $value
     * @param string           $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allNotWhitespaceOnly($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::notWhitespaceOnly($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string|null> $value
     * @param string                $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrStartsWith($value, $prefix, $message = '')
    {
        null === $value || static::startsWith($value, $prefix, $message);
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string> $value
     * @param string           $prefix
     * @param string           $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allStartsWith($value, $prefix, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::startsWith($entry, $prefix, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string|null> $value
     * @param string                $prefix
     * @param string                $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrNotStartsWith($value, $prefix, $message = '')
    {
        null === $value || static::notStartsWith($value, $prefix, $message);
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string> $value
     * @param string           $prefix
     * @param string           $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allNotStartsWith($value, $prefix, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::notStartsWith($entry, $prefix, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string|null> $value
     * @param string                $prefix
     * @param string                $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrStartsWithLetter($value, $message = '')
    {
        null === $value || static::startsWithLetter($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrEndsWith($value, $suffix, $message = '')
    {
        null === $value || static::endsWith($value, $suffix, $message);
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string> $value
     * @param string           $suffix
     * @param string           $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allEndsWith($value, $suffix, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::endsWith($entry, $suffix, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string|null> $value
     * @param string                $suffix
     * @param string                $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrNotEndsWith($value, $suffix, $message = '')
    {
        null === $value || static::notEndsWith($value, $suffix, $message);
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string> $value
     * @param string           $suffix
     * @param string           $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allNotEndsWith($value, $suffix, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::notEndsWith($entry, $suffix, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string|null> $value
     * @param string                $suffix
     * @param string                $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrRegex($value, $pattern, $message = '')
    {
        null === $value || static::regex($value, $pattern, $message);
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string> $value
     * @param string           $pattern
     * @param string           $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allRegex($value, $pattern, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::regex($entry, $pattern, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string|null> $value
     * @param string                $pattern
     * @param string                $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrNotRegex($value, $pattern, $message = '')
    {
        null === $value || static::notRegex($value, $pattern, $message);
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string> $value
     * @param string           $pattern
     * @param string           $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allNotRegex($value, $pattern, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::notRegex($entry, $pattern, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string|null> $value
     * @param string                $pattern
     * @param string                $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrUnicodeLetters($value, $message = '')
    {
        null === $value || static::unicodeLetters($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrAlpha($value, $message = '')
    {
        null === $value || static::alpha($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrDigits($value, $message = '')
    {
        null === $value || static::digits($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string> $value
     * @param string           $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allDigits($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::digits($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string|null> $value
     * @param string                $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrAlnum($value, $message = '')
    {
        null === $value || static::alnum($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string> $value
     * @param string           $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allAlnum($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::alnum($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string|null> $value
     * @param string                $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrLower($value, $message = '')
    {
        null === $value || static::lower($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<lowercase-string> $value
     *
     * @param iterable<string> $value
     * @param string           $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allLower($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::lower($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<lowercase-string|null> $value
     *
     * @param iterable<string|null> $value
     * @param string                $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrUpper($value, $message = '')
    {
        null === $value || static::upper($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string> $value
     * @param string           $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allUpper($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::upper($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<!lowercase-string|null> $value
     *
     * @param iterable<string|null> $value
     * @param string                $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrLength($value, $length, $message = '')
    {
        null === $value || static::length($value, $length, $message);
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string> $value
     * @param int              $length
     * @param string           $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allLength($value, $length, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::length($entry, $length, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string|null> $value
     * @param int                   $length
     * @param string                $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrMinLength($value, $min, $message = '')
    {
        null === $value || static::minLength($value, $min, $message);
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string> $value
     * @param int|float        $min
     * @param string           $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allMinLength($value, $min, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::minLength($entry, $min, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string|null> $value
     * @param int|float             $min
     * @param string                $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrMaxLength($value, $max, $message = '')
    {
        null === $value || static::maxLength($value, $max, $message);
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string> $value
     * @param int|float        $max
     * @param string           $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allMaxLength($value, $max, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::maxLength($entry, $max, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string|null> $value
     * @param int|float             $max
     * @param string                $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrLengthBetween($value, $min, $max, $message = '')
    {
        null === $value || static::lengthBetween($value, $min, $max, $message);
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string> $value
     * @param int|float        $min
     * @param int|float        $max
     * @param string           $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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<string|null> $value
     * @param int|float             $min
     * @param int|float             $max
     * @param string                $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrFileExists($value, $message = '')
    {
        null === $value || static::fileExists($value, $message);
    }

    /**
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allFileExists($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::fileExists($entry, $message);
        }
    }

    /**
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrFile($value, $message = '')
    {
        null === $value || static::file($value, $message);
    }

    /**
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allFile($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::file($entry, $message);
        }
    }

    /**
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrDirectory($value, $message = '')
    {
        null === $value || static::directory($value, $message);
    }

    /**
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allDirectory($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::directory($entry, $message);
        }
    }

    /**
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrReadable($value, $message = '')
    {
        null === $value || static::readable($value, $message);
    }

    /**
     * @param iterable<string> $value
     * @param string           $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allReadable($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::readable($entry, $message);
        }
    }

    /**
     * @param iterable<string|null> $value
     * @param string                $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrWritable($value, $message = '')
    {
        null === $value || static::writable($value, $message);
    }

    /**
     * @param iterable<string> $value
     * @param string           $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allWritable($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::writable($entry, $message);
        }
    }

    /**
     * @param iterable<string|null> $value
     * @param string                $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrClassExists($value, $message = '')
    {
        null === $value || static::classExists($value, $message);
    }

    /**
     * @psalm-assert iterable<class-string> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allClassExists($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::classExists($entry, $message);
        }
    }

    /**
     * @psalm-assert iterable<class-string|null> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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<ExpectedType> $class
     * @psalm-assert class-string<ExpectedType>|ExpectedType|null $value
     *
     * @param mixed         $value
     * @param string|object $class
     * @param string        $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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<ExpectedType> $class
     * @psalm-assert iterable<class-string<ExpectedType>|ExpectedType> $value
     *
     * @param mixed         $value
     * @param string|object $class
     * @param string        $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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<ExpectedType> $class
     * @psalm-assert iterable<class-string<ExpectedType>|ExpectedType|null> $value
     *
     * @param mixed         $value
     * @param string|object $class
     * @param string        $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrInterfaceExists($value, $message = '')
    {
        null === $value || static::interfaceExists($value, $message);
    }

    /**
     * @psalm-assert iterable<class-string> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allInterfaceExists($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::interfaceExists($entry, $message);
        }
    }

    /**
     * @psalm-assert iterable<class-string|null> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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<ExpectedType> $interface
     * @psalm-assert class-string<ExpectedType>|ExpectedType|null $value
     *
     * @param mixed  $value
     * @param mixed  $interface
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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<ExpectedType> $interface
     * @psalm-assert iterable<class-string<ExpectedType>|ExpectedType> $value
     *
     * @param mixed  $value
     * @param mixed  $interface
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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<ExpectedType> $interface
     * @psalm-assert iterable<class-string<ExpectedType>|ExpectedType|null> $value
     *
     * @param mixed  $value
     * @param mixed  $interface
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrPropertyExists($classOrObject, $property, $message = '')
    {
        null === $classOrObject || static::propertyExists($classOrObject, $property, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-param iterable<class-string|object> $classOrObject
     *
     * @param iterable<string|object> $classOrObject
     * @param mixed                   $property
     * @param string                  $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allPropertyExists($classOrObject, $property, $message = '')
    {
        static::isIterable($classOrObject);

        foreach ($classOrObject as $entry) {
            static::propertyExists($entry, $property, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-param iterable<class-string|object|null> $classOrObject
     *
     * @param iterable<string|object|null> $classOrObject
     * @param mixed                        $property
     * @param string                       $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrPropertyNotExists($classOrObject, $property, $message = '')
    {
        null === $classOrObject || static::propertyNotExists($classOrObject, $property, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-param iterable<class-string|object> $classOrObject
     *
     * @param iterable<string|object> $classOrObject
     * @param mixed                   $property
     * @param string                  $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allPropertyNotExists($classOrObject, $property, $message = '')
    {
        static::isIterable($classOrObject);

        foreach ($classOrObject as $entry) {
            static::propertyNotExists($entry, $property, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-param iterable<class-string|object|null> $classOrObject
     *
     * @param iterable<string|object|null> $classOrObject
     * @param mixed                        $property
     * @param string                       $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrMethodExists($classOrObject, $method, $message = '')
    {
        null === $classOrObject || static::methodExists($classOrObject, $method, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-param iterable<class-string|object> $classOrObject
     *
     * @param iterable<string|object> $classOrObject
     * @param mixed                   $method
     * @param string                  $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allMethodExists($classOrObject, $method, $message = '')
    {
        static::isIterable($classOrObject);

        foreach ($classOrObject as $entry) {
            static::methodExists($entry, $method, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-param iterable<class-string|object|null> $classOrObject
     *
     * @param iterable<string|object|null> $classOrObject
     * @param mixed                        $method
     * @param string                       $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrMethodNotExists($classOrObject, $method, $message = '')
    {
        null === $classOrObject || static::methodNotExists($classOrObject, $method, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-param iterable<class-string|object> $classOrObject
     *
     * @param iterable<string|object> $classOrObject
     * @param mixed                   $method
     * @param string                  $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allMethodNotExists($classOrObject, $method, $message = '')
    {
        static::isIterable($classOrObject);

        foreach ($classOrObject as $entry) {
            static::methodNotExists($entry, $method, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-param iterable<class-string|object|null> $classOrObject
     *
     * @param iterable<string|object|null> $classOrObject
     * @param mixed                        $method
     * @param string                       $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrKeyExists($array, $key, $message = '')
    {
        null === $array || static::keyExists($array, $key, $message);
    }

    /**
     * @psalm-pure
     *
     * @param iterable<array> $array
     * @param string|int      $key
     * @param string          $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allKeyExists($array, $key, $message = '')
    {
        static::isIterable($array);

        foreach ($array as $entry) {
            static::keyExists($entry, $key, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @param iterable<array|null> $array
     * @param string|int           $key
     * @param string               $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrKeyNotExists($array, $key, $message = '')
    {
        null === $array || static::keyNotExists($array, $key, $message);
    }

    /**
     * @psalm-pure
     *
     * @param iterable<array> $array
     * @param string|int      $key
     * @param string          $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allKeyNotExists($array, $key, $message = '')
    {
        static::isIterable($array);

        foreach ($array as $entry) {
            static::keyNotExists($entry, $key, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @param iterable<array|null> $array
     * @param string|int           $key
     * @param string               $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrValidArrayKey($value, $message = '')
    {
        null === $value || static::validArrayKey($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<array-key> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allValidArrayKey($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::validArrayKey($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<array-key|null> $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrCount($array, $number, $message = '')
    {
        null === $array || static::count($array, $number, $message);
    }

    /**
     * @param iterable<Countable|array> $array
     * @param int                       $number
     * @param string                    $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allCount($array, $number, $message = '')
    {
        static::isIterable($array);

        foreach ($array as $entry) {
            static::count($entry, $number, $message);
        }
    }

    /**
     * @param iterable<Countable|array|null> $array
     * @param int                            $number
     * @param string                         $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrMinCount($array, $min, $message = '')
    {
        null === $array || static::minCount($array, $min, $message);
    }

    /**
     * @param iterable<Countable|array> $array
     * @param int|float                 $min
     * @param string                    $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allMinCount($array, $min, $message = '')
    {
        static::isIterable($array);

        foreach ($array as $entry) {
            static::minCount($entry, $min, $message);
        }
    }

    /**
     * @param iterable<Countable|array|null> $array
     * @param int|float                      $min
     * @param string                         $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrMaxCount($array, $max, $message = '')
    {
        null === $array || static::maxCount($array, $max, $message);
    }

    /**
     * @param iterable<Countable|array> $array
     * @param int|float                 $max
     * @param string                    $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allMaxCount($array, $max, $message = '')
    {
        static::isIterable($array);

        foreach ($array as $entry) {
            static::maxCount($entry, $max, $message);
        }
    }

    /**
     * @param iterable<Countable|array|null> $array
     * @param int|float                      $max
     * @param string                         $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrCountBetween($array, $min, $max, $message = '')
    {
        null === $array || static::countBetween($array, $min, $max, $message);
    }

    /**
     * @param iterable<Countable|array> $array
     * @param int|float                 $min
     * @param int|float                 $max
     * @param string                    $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allCountBetween($array, $min, $max, $message = '')
    {
        static::isIterable($array);

        foreach ($array as $entry) {
            static::countBetween($entry, $min, $max, $message);
        }
    }

    /**
     * @param iterable<Countable|array|null> $array
     * @param int|float                      $min
     * @param int|float                      $max
     * @param string                         $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrIsList($array, $message = '')
    {
        null === $array || static::isList($array, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<list> $array
     *
     * @param mixed  $array
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allIsList($array, $message = '')
    {
        static::isIterable($array);

        foreach ($array as $entry) {
            static::isList($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<list|null> $array
     *
     * @param mixed  $array
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrIsNonEmptyList($array, $message = '')
    {
        null === $array || static::isNonEmptyList($array, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<non-empty-list> $array
     *
     * @param mixed  $array
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allIsNonEmptyList($array, $message = '')
    {
        static::isIterable($array);

        foreach ($array as $entry) {
            static::isNonEmptyList($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @psalm-assert iterable<non-empty-list|null> $array
     *
     * @param mixed  $array
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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<T>|null $array
     * @psalm-assert array<string, T>|null $array
     *
     * @param mixed  $array
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrIsMap($array, $message = '')
    {
        null === $array || static::isMap($array, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-template T
     * @psalm-param iterable<mixed|array<T>> $array
     * @psalm-assert iterable<array<string, T>> $array
     *
     * @param mixed  $array
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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<mixed|array<T>|null> $array
     * @psalm-assert iterable<array<string, T>|null> $array
     *
     * @param mixed  $array
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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<T>|null $array
     *
     * @param mixed  $array
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrIsNonEmptyMap($array, $message = '')
    {
        null === $array || static::isNonEmptyMap($array, $message);
    }

    /**
     * @psalm-pure
     *
     * @psalm-template T
     * @psalm-param iterable<mixed|array<T>> $array
     *
     * @param mixed  $array
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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<mixed|array<T>|null> $array
     * @psalm-assert iterable<array<string, T>|null> $array
     * @psalm-assert iterable<!empty|null> $array
     *
     * @param mixed  $array
     * @param string $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    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
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrUuid($value, $message = '')
    {
        null === $value || static::uuid($value, $message);
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string> $value
     * @param string           $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allUuid($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            static::uuid($entry, $message);
        }
    }

    /**
     * @psalm-pure
     *
     * @param iterable<string|null> $value
     * @param string                $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allNullOrUuid($value, $message = '')
    {
        static::isIterable($value);

        foreach ($value as $entry) {
            null === $entry || static::uuid($entry, $message);
        }
    }

    /**
     * @psalm-param class-string<Throwable> $class
     *
     * @param Closure|null $expression
     * @param string       $class
     * @param string       $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function nullOrThrows($expression, $class = 'Exception', $message = '')
    {
        null === $expression || static::throws($expression, $class, $message);
    }

    /**
     * @psalm-param class-string<Throwable> $class
     *
     * @param iterable<Closure> $expression
     * @param string            $class
     * @param string            $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allThrows($expression, $class = 'Exception', $message = '')
    {
        static::isIterable($expression);

        foreach ($expression as $entry) {
            static::throws($entry, $class, $message);
        }
    }

    /**
     * @psalm-param class-string<Throwable> $class
     *
     * @param iterable<Closure|null> $expression
     * @param string                 $class
     * @param string                 $message
     *
     * @return void
     *
     * @throws InvalidArgumentException
     */
    public static function allNullOrThrows($expression, $class = 'Exception', $message = '')
    {
        static::isIterable($expression);

        foreach ($expression as $entry) {
            null === $entry || static::throws($entry, $class, $message);
        }
    }
}
<?php

/*
 * This file is part of the webmozart/assert package.
 *
 * (c) Bernhard Schussek <bschussek@gmail.com>
 *
 * 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
{
}
{"mediaType":"application/vnd.dev.sigstore.trustedroot+json;version=0.1","tlogs":[{"baseUrl":"https://rekor.sigstore.dev","hashAlgorithm":"SHA2_256","publicKey":{"rawBytes":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2G2Y+2tabdTV5BcGiBIx0a9fAFwrkBbmLSGtks4L3qX6yYY0zufBnhC8Ur/iy55GhWP/9A/bY2LhC30M9+RYtw==","keyDetails":"PKIX_ECDSA_P256_SHA_256","validFor":{"start":"2021-01-12T11:53:27Z"}},"logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="}},{"baseUrl":"https://log2025-1.rekor.sigstore.dev","hashAlgorithm":"SHA2_256","publicKey":{"rawBytes":"MCowBQYDK2VwAyEAt8rlp1knGwjfbcXAYPYAkn0XiLz1x8O4t0YkEhie244=","keyDetails":"PKIX_ED25519","validFor":{"start":"2025-09-23T00:00:00Z"}},"logId":{"keyId":"zxGZFVvd0FEmjR8WrFwMdcAJ9vtaY/QXf44Y1wUeP6A="}}],"certificateAuthorities":[{"subject":{"organization":"sigstore.dev","commonName":"sigstore"},"uri":"https://fulcio.sigstore.dev","certChain":{"certificates":[{"rawBytes":"MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAqMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIxMDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSyA7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0JcastaRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6NmMGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2uSu1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJxVe/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uupHr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ=="}]},"validFor":{"start":"2021-03-07T03:20:29Z","end":"2022-12-31T23:59:59.999Z"}},{"subject":{"organization":"sigstore.dev","commonName":"sigstore"},"uri":"https://fulcio.sigstore.dev","certChain":{"certificates":[{"rawBytes":"MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV77LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjpKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZIzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJRnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsPmygUY7Ii2zbdCdliiow="},{"rawBytes":"MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxexX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92jYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRYwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCMWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ"}]},"validFor":{"start":"2022-04-13T20:06:15Z"}}],"ctlogs":[{"baseUrl":"https://ctfe.sigstore.dev/test","hashAlgorithm":"SHA2_256","publicKey":{"rawBytes":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbfwR+RJudXscgRBRpKX1XFDy3PyudDxz/SfnRi1fT8ekpfBd2O1uoz7jr3Z8nKzxA69EUQ+eFCFI3zeubPWU7w==","keyDetails":"PKIX_ECDSA_P256_SHA_256","validFor":{"start":"2021-03-14T00:00:00Z","end":"2022-10-31T23:59:59.999Z"}},"logId":{"keyId":"CGCS8ChS/2hF0dFrJ4ScRWcYrBY9wzjSbea8IgY2b3I="}},{"baseUrl":"https://ctfe.sigstore.dev/2022","hashAlgorithm":"SHA2_256","publicKey":{"rawBytes":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEiPSlFi0CmFTfEjCUqF9HuCEcYXNKAaYalIJmBZ8yyezPjTqhxrKBpMnaocVtLJBI1eM3uXnQzQGAJdJ4gs9Fyw==","keyDetails":"PKIX_ECDSA_P256_SHA_256","validFor":{"start":"2022-10-20T00:00:00Z"}},"logId":{"keyId":"3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4="}}],"timestampAuthorities":[{"subject":{"organization":"sigstore.dev","commonName":"sigstore-tsa-selfsigned"},"uri":"https://timestamp.sigstore.dev/api/v1/timestamp","certChain":{"certificates":[{"rawBytes":"MIICEDCCAZagAwIBAgIUOhNULwyQYe68wUMvy4qOiyojiwwwCgYIKoZIzj0EAwMwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZDAeFw0yNTA0MDgwNjU5NDNaFw0zNTA0MDYwNjU5NDNaMC4xFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEVMBMGA1UEAxMMc2lnc3RvcmUtdHNhMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE4ra2Z8hKNig2T9kFjCAToGG30jky+WQv3BzL+mKvh1SKNR/UwuwsfNCg4sryoYAd8E6isovVA3M4aoNdm9QDi50Z8nTEyvqgfDPtTIwXItfiW/AFf1V7uwkbkAoj0xxco2owaDAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFIn9eUOHz9BlRsMCRscsc1t9tOsDMB8GA1UdIwQYMBaAFJjsAe9/u1H/1JUeb4qImFMHic6/MBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMAoGCCqGSM49BAMDA2gAMGUCMDtpsV/6KaO0qyF/UMsX2aSUXKQFdoGTptQGc0ftq1csulHPGG6dsmyMNd3JB+G3EQIxAOajvBcjpJmKb4Nv+2Taoj8Uc5+b6ih6FXCCKraSqupe07zqswMcXJTe1cExvHvvlw=="},{"rawBytes":"MIIB9zCCAXygAwIBAgIUV7f0GLDOoEzIh8LXSW80OJiUp14wCgYIKoZIzj0EAwMwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZDAeFw0yNTA0MDgwNjU5NDNaFw0zNTA0MDYwNjU5NDNaMDkxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEgMB4GA1UEAxMXc2lnc3RvcmUtdHNhLXNlbGZzaWduZWQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQUQNtfRT/ou3YATa6wB/kKTe70cfJwyRIBovMnt8RcJph/COE82uyS6FmppLLL1VBPGcPfpQPYJNXzWwi8icwhKQ6W/Qe2h3oebBb2FHpwNJDqo+TMaC/tdfkv/ElJB72jRTBDMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBSY7AHvf7tR/9SVHm+KiJhTB4nOvzAKBggqhkjOPQQDAwNpADBmAjEAwGEGrfGZR1cen1R8/DTVMI943LssZmJRtDp/i7SfGHmGRP6gRbuj9vOK3b67Z0QQAjEAuT2H673LQEaHTcyQSZrkp4mX7WwkmF+sVbkYY5mXN+RMH13KUEHHOqASaemYWK/E"}]},"validFor":{"start":"2025-07-04T00:00:00Z"}}]}
{"mediaType":"application/vnd.dev.sigstore.trustedroot+json;version=0.1","certificateAuthorities":[{"subject":{"organization":"GitHub, Inc.","commonName":"Internal Services Root"},"uri":"fulcio.githubapp.com","certChain":{"certificates":[{"rawBytes":"MIICKjCCAbCgAwIBAgIUW3TJVeOvr+NSvJXdOw8nEEn7HhQwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMB4XDTIzMDkxMjE0MDY1NFoXDTI0MDkxMTE0MDY1NFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEsosodObhuHG6Pr5vp5y+pmnKawS1h2hwv3r3hBwqh3ZHJAw64mhDnDs9fw4jKkZEBYRSVyOHyZppz4day8hgpTIDwdj44Oan4RDb+wmj04jfhVLjLsQ4Q/X4K/ynRgNXo3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUm0vkDkQZ29hutYdayJobIRmf/iMwHwYDVR0jBBgwFoAUwOG4UqRLTz7eejgRBs9JjqFFmzMwCgYIKoZIzj0EAwMDaAAwZQIwIBl93E7vkWTvdeIm1WSIM4qNsj0ApE8LCj3k1vrY5x6/7yhAZs7QlO3/FBCoEeaZAjEAlJcNr37uZq9BYHODHBeO/gP+6EfbzsNaLV22ASBlhF/a9y83ESLuqCNN7IxGxmWT"},{"rawBytes":"MIICFTCCAZugAwIBAgIUD3Jlqt4qhrcZI4UnGfPGrEq/pjQwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDkxMTEyMDAwMFoXDTI4MDkwOTEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7X7nK0wC7uEmDjW+on0sXIX3FacL3hhcrhneA+M/kl1OtvQiPmFrH9lbUQqOj/AfspJ8uGY3jaq8WuSg6ghatzYfuuzLAJIK4nGpCBafncF8EynOssPq64/Dz+JUWXqlo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUwOG4UqRLTz7eejgRBs9JjqFFmzMwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAI8HWLrke7uzhOpwlD1cNixPmoX9XFKe7bEPozo0D+vKi0Gt6VlC7xPedFIw4/AypAIwQP+FGRWvfx0IAH5/n0aRiN7/LVpyFA5RkJASZOVOib2Y8pNuhXa9V3ZbWO6v6kW/"},{"rawBytes":"MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="}]},"validFor":{"start":"2023-10-27T16:30:00Z","end":"2024-05-25T00:00:00Z"}},{"subject":{"organization":"GitHub, Inc.","commonName":"Internal Services Root"},"uri":"fulcio.githubapp.com","certChain":{"certificates":[{"rawBytes":"MIICKzCCAbCgAwIBAgIUOpyw2HaZefsj/4SPXutGof8E2CkwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMB4XDTI0MDUxMzAwMDAwMFoXDTI1MDUxMzAwMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEJNJWvW8fckfk/oQmh+qCeIlFXl9YLEkKSjZCgcVB92Fi1HQnvmpCiyqpvP91SmT1/G6QbrmTGV7MmIQlDnBWHNUT+jwZ3elGu/yfr/v8U0uhZTIli/BMj5Y4ICHK/j4do3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUD0fF3cs+ldPyiWohHJ3JmO91V7gwHwYDVR0jBBgwFoAUwOG4UqRLTz7eejgRBs9JjqFFmzMwCgYIKoZIzj0EAwMDaQAwZgIxAO7BRC9i7oGUHjjlcHU/bfqk2NLy7t6wm3K5W+jBLFbAj6sVjYcY+rrYhop/OjclbQIxALafBKLPIPjoCI29BUHwLBFP6e92ZlyaoFtoqccceXAevRaDjXFvb5+M7wnD6AuAJw=="},{"rawBytes":"MIICFTCCAZugAwIBAgIUD3Jlqt4qhrcZI4UnGfPGrEq/pjQwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDkxMTEyMDAwMFoXDTI4MDkwOTEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7X7nK0wC7uEmDjW+on0sXIX3FacL3hhcrhneA+M/kl1OtvQiPmFrH9lbUQqOj/AfspJ8uGY3jaq8WuSg6ghatzYfuuzLAJIK4nGpCBafncF8EynOssPq64/Dz+JUWXqlo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUwOG4UqRLTz7eejgRBs9JjqFFmzMwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAI8HWLrke7uzhOpwlD1cNixPmoX9XFKe7bEPozo0D+vKi0Gt6VlC7xPedFIw4/AypAIwQP+FGRWvfx0IAH5/n0aRiN7/LVpyFA5RkJASZOVOib2Y8pNuhXa9V3ZbWO6v6kW/"},{"rawBytes":"MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="}]},"validFor":{"start":"2024-05-13T00:00:00Z","end":"2024-10-25T00:00:00Z"}},{"subject":{"organization":"GitHub, Inc.","commonName":"Internal Services Root"},"uri":"fulcio.githubapp.com","certChain":{"certificates":[{"rawBytes":"MIICKzCCAbCgAwIBAgIUQeyd9UH06yZ63pDuqjgUZ58CnpMwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMB4XDTI0MTAwMzEyMDAwMFoXDTI1MTAwMzEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEwvbET2w+j9j9j50iTInH1gb9GSXkpsCvWz5orX1zgme+/Qh/5gMkpfmgfOSLV2ZRgT1hzujYmnKQvP2mCxYnbwQELAkAf+VhEY/7Uw3zZvguGQSdF1cxzRHiMTOha5eFo3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUMib9z4ZYBcQANTVvVCa3KoTGbBUwHwYDVR0jBBgwFoAUwOG4UqRLTz7eejgRBs9JjqFFmzMwCgYIKoZIzj0EAwMDaQAwZgIxAPIU/zlJiJrxn6oTWNdEAD/YBSnhyxcvpq1D2DzFy8E8hbkEfMZPErYL7HyoL/BkdwIxAN9KDEKyktEUBrfHehfcLAzI2kERJx+8DSslXswOIbLaeqYfWsmrQAt5C0X/nOWxXA=="},{"rawBytes":"MIICFTCCAZugAwIBAgIUD3Jlqt4qhrcZI4UnGfPGrEq/pjQwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDkxMTEyMDAwMFoXDTI4MDkwOTEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7X7nK0wC7uEmDjW+on0sXIX3FacL3hhcrhneA+M/kl1OtvQiPmFrH9lbUQqOj/AfspJ8uGY3jaq8WuSg6ghatzYfuuzLAJIK4nGpCBafncF8EynOssPq64/Dz+JUWXqlo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUwOG4UqRLTz7eejgRBs9JjqFFmzMwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAI8HWLrke7uzhOpwlD1cNixPmoX9XFKe7bEPozo0D+vKi0Gt6VlC7xPedFIw4/AypAIwQP+FGRWvfx0IAH5/n0aRiN7/LVpyFA5RkJASZOVOib2Y8pNuhXa9V3ZbWO6v6kW/"},{"rawBytes":"MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="}]},"validFor":{"start":"2024-10-07T00:00:00Z","end":"2025-06-19T00:00:00Z"}},{"subject":{"organization":"GitHub, Inc.","commonName":"Internal Services Root"},"uri":"fulcio.githubapp.com","certChain":{"certificates":[{"rawBytes":"MIICKTCCAbCgAwIBAgIUU7Y2PESb8FK88XCRQvI2rKDB5skwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMB4XDTI1MDUyNzAwMDAwMFoXDTI2MDUyNzAwMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEevMuOTGccTTXLu65xinRJP4gxkOemyAPCmUXEvhXKtLd+aDgN87PI0QxutOmPuQD2dLZVfxQmt5lvwJpUDIVYkKpH3mDxYbF6MJPMbq+A/EhkMaOS2+mcJRJdY2uam0Vo3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU8tE1L3b7FV3qJKzSqy1w97vPt6AwHwYDVR0jBBgwFoAUwOG4UqRLTz7eejgRBs9JjqFFmzMwCgYIKoZIzj0EAwMDZwAwZAIwSIsnfcDIvp+9SGBe9Es4xLDgZ6/B3wtODu+jtNUznQFPw1/WHa3iBMp3vYPkIqZDAjBBD4AKu166Hg7tDd5E8ckSCNNDkXD0Q7Xdl4yujgqv0goVMeOgRA1pKDGszhunjAQ="},{"rawBytes":"MIICFTCCAZugAwIBAgIUD3Jlqt4qhrcZI4UnGfPGrEq/pjQwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDkxMTEyMDAwMFoXDTI4MDkwOTEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7X7nK0wC7uEmDjW+on0sXIX3FacL3hhcrhneA+M/kl1OtvQiPmFrH9lbUQqOj/AfspJ8uGY3jaq8WuSg6ghatzYfuuzLAJIK4nGpCBafncF8EynOssPq64/Dz+JUWXqlo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUwOG4UqRLTz7eejgRBs9JjqFFmzMwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAI8HWLrke7uzhOpwlD1cNixPmoX9XFKe7bEPozo0D+vKi0Gt6VlC7xPedFIw4/AypAIwQP+FGRWvfx0IAH5/n0aRiN7/LVpyFA5RkJASZOVOib2Y8pNuhXa9V3ZbWO6v6kW/"},{"rawBytes":"MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="}]},"validFor":{"start":"2025-05-27T00:00:00Z","end":"2025-12-09T00:00:00Z"}},{"subject":{"organization":"GitHub, Inc.","commonName":"Internal Services Root"},"uri":"fulcio.githubapp.com","certChain":{"certificates":[{"rawBytes":"MIICKjCCAbCgAwIBAgIUNansGfOzaol+jXgh/wsz0X36zTMwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMB4XDTI1MTExMzAwMDAwMFoXDTI2MTExMzAwMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE24Un0srydTqXkWtrVrNdgPRNa1cCZqBRpFImYqItuVs/mszkn+RSlwcMzg/LwphU84qwA8Xv2z8ZaIETHspoQgMO4dP5zhJ5W8M+9crL1kR4Co2vS3TJZF9Qpsg+HiTro3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUGO6KtZB8c9gbXS2uNF3tXCtFNhEwHwYDVR0jBBgwFoAUwOG4UqRLTz7eejgRBs9JjqFFmzMwCgYIKoZIzj0EAwMDaAAwZQIxALmOdTRy/SiOc+QSf+eAPz24xWGhMryvNpyNvBZDeyqthC9KaMKmX+kHwJ0A6/bTXQIwE1mPbkH6tBXuWxlR/2Bj7UZ3MADlHSYGQUjpHMdQqJk17UVBywad5U6uA/f0pFdU"},{"rawBytes":"MIICFTCCAZugAwIBAgIUD3Jlqt4qhrcZI4UnGfPGrEq/pjQwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDkxMTEyMDAwMFoXDTI4MDkwOTEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7X7nK0wC7uEmDjW+on0sXIX3FacL3hhcrhneA+M/kl1OtvQiPmFrH9lbUQqOj/AfspJ8uGY3jaq8WuSg6ghatzYfuuzLAJIK4nGpCBafncF8EynOssPq64/Dz+JUWXqlo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUwOG4UqRLTz7eejgRBs9JjqFFmzMwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAI8HWLrke7uzhOpwlD1cNixPmoX9XFKe7bEPozo0D+vKi0Gt6VlC7xPedFIw4/AypAIwQP+FGRWvfx0IAH5/n0aRiN7/LVpyFA5RkJASZOVOib2Y8pNuhXa9V3ZbWO6v6kW/"},{"rawBytes":"MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="}]},"validFor":{"start":"2025-11-13T00:00:00Z"}}],"timestampAuthorities":[{"subject":{"organization":"GitHub, Inc.","commonName":"Internal Services Root"},"uri":"timestamp.githubapp.com","certChain":{"certificates":[{"rawBytes":"MIICHDCCAaGgAwIBAgIUNDVlmtZuvoujn4KwiC/oxIr8hxAwCgYIKoZIzj0EAwMwMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMB4XDTIzMDgzMTEyMDAwMFoXDTI0MDgzMDEyMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgVGltZXN0YW1waW5nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEV/zJhNTdu0Fa9hGCUih/JvqEoE81tEWrAVwUXXhdRgIY9hIFErLhNo6sSOpV9d7Zuy0KWMHhcimCUr41a1732ByVRy3f+Z4QhqpsgFMh5b5J90HJLK7HOyUZjehAnvSno3gwdjAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUGwE6T5ZIh6lY9wP6vt42UHyVMewwHwYDVR0jBBgwFoAUdh+GTP65aetHLVLs9hdhGgDIKIwwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwCgYIKoZIzj0EAwMDaQAwZgIxAJo48LtrSsn0UmLtqGiUKg2EUvso+aDN5EyjpvMmobZ/Oq9zjnR7Of369hoABW4/1gIxANg5ZW4FqijhsXnA3md6jM9yLrLCI9QL+KnuZnXq6WgAcNQaAN7PNNjVDKV3iJEklw=="},{"rawBytes":"MIICJDCCAaqgAwIBAgIUckXVHpiw7iJY1V/jY8LYLj5TgqAwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTI4MDgwNTEyMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEISv96hTQ58QroEzzu4K+o9p8YkwDCBia2U7Y+VBNbOG/w1mLRibve9hSeUE1FSyLBMkiFSSm6MexcsbjyqOoNtRxuMinyYt6DSEox+/It2s/bTPyNAN0QP0DCQQOpnTZo3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUdh+GTP65aetHLVLs9hdhGgDIKIwwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAIhf+2E5W2yOb/fCDAjhL/G/jerf74M0tG/zyo32U2keawxkzZosDdwnPaHaGLynAQIwa8nr3en4fZz1AdOZm6nK5hr1qK2F94nifgnAJ/WeT0fZnK/oHan0R28x363qYuYH"},{"rawBytes":"MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="}]},"validFor":{"start":"2023-10-27T16:30:00Z","end":"2024-05-25T00:00:00Z"}},{"subject":{"organization":"GitHub, Inc.","commonName":"Internal Services Root"},"uri":"timestamp.githubapp.com","certChain":{"certificates":[{"rawBytes":"MIICGzCCAaGgAwIBAgIUPPgn6ner1PU/75CQ+62fdBuazaAwCgYIKoZIzj0EAwMwMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMB4XDTI0MDUxMzAwMDAwMFoXDTI1MDUxMzAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgVGltZXN0YW1waW5nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEA0pG1mAC4qafk1JJuOoIvhnMME9XmBDxjGFreDLnyzaexIzRw+UHUFy8C2gE6Me+0tIGQt4Ftbu66NGmfvBkR6boPMYQSU2O5X5ykZBm/9LR/Aqz0lgmBy/OlXvTJjglo3gwdjAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUDa4GVhd97Z2V8kiVl9DB0kC53CMwHwYDVR0jBBgwFoAUdh+GTP65aetHLVLs9hdhGgDIKIwwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwCgYIKoZIzj0EAwMDaAAwZQIwEQHd++b7IBAAuqT2/1i/wXf1WM2XrkFF6qd1c3kFcBVvdLQyJ5KoyNUHnfCCVJROAjEA+FoASOEcARlU6RqVcif9JthHwzh6nNwz0AfAHvO8xantN/7HjiLmFrFEGR/g0kN/"},{"rawBytes":"MIICJDCCAaqgAwIBAgIUckXVHpiw7iJY1V/jY8LYLj5TgqAwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTI4MDgwNTEyMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEISv96hTQ58QroEzzu4K+o9p8YkwDCBia2U7Y+VBNbOG/w1mLRibve9hSeUE1FSyLBMkiFSSm6MexcsbjyqOoNtRxuMinyYt6DSEox+/It2s/bTPyNAN0QP0DCQQOpnTZo3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUdh+GTP65aetHLVLs9hdhGgDIKIwwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAIhf+2E5W2yOb/fCDAjhL/G/jerf74M0tG/zyo32U2keawxkzZosDdwnPaHaGLynAQIwa8nr3en4fZz1AdOZm6nK5hr1qK2F94nifgnAJ/WeT0fZnK/oHan0R28x363qYuYH"},{"rawBytes":"MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="}]},"validFor":{"start":"2024-05-13T00:00:00Z","end":"2024-10-25T00:00:00Z"}},{"subject":{"organization":"GitHub, Inc.","commonName":"Internal Services Root"},"uri":"timestamp.githubapp.com","certChain":{"certificates":[{"rawBytes":"MIICGzCCAaGgAwIBAgIUH7swiMTn+svhcDh80OeZccDTj7AwCgYIKoZIzj0EAwMwMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMB4XDTI0MTAwNDEyMDAwMFoXDTI1MTAwNDEyMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgVGltZXN0YW1waW5nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEM7jdYNBTeD6hjym2/y73b50u2AFQsf8305Sr1NleOqamH9aWt6obhJQH3NoNUw9iFzHcDvafYWQFMu7SmOxS5n3aqwwfR8oJxKnEl36uCmGB+8TXS3B76SVTHEhG5rzOo3gwdjAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUQvz9YbWX3S6a+jruBkhRBiE2RCkwHwYDVR0jBBgwFoAUdh+GTP65aetHLVLs9hdhGgDIKIwwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwCgYIKoZIzj0EAwMDaAAwZQIxAI4dhu5iyx/g+z1vKAAWvHtebl1ZwsC+Vwgjm6Ttlq5yLNHHvYEnJ/h15Qv2IuXvdgIwZ8H/iy4lXsFJdFYSsB1/zavl24EgxSzxK/pCpihXMetYYDA/lX3xMyquisMx45rN"},{"rawBytes":"MIICJDCCAaqgAwIBAgIUckXVHpiw7iJY1V/jY8LYLj5TgqAwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTI4MDgwNTEyMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEISv96hTQ58QroEzzu4K+o9p8YkwDCBia2U7Y+VBNbOG/w1mLRibve9hSeUE1FSyLBMkiFSSm6MexcsbjyqOoNtRxuMinyYt6DSEox+/It2s/bTPyNAN0QP0DCQQOpnTZo3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUdh+GTP65aetHLVLs9hdhGgDIKIwwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAIhf+2E5W2yOb/fCDAjhL/G/jerf74M0tG/zyo32U2keawxkzZosDdwnPaHaGLynAQIwa8nr3en4fZz1AdOZm6nK5hr1qK2F94nifgnAJ/WeT0fZnK/oHan0R28x363qYuYH"},{"rawBytes":"MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="}]},"validFor":{"start":"2024-10-07T00:00:00Z","end":"2025-06-19T00:00:00Z"}},{"subject":{"organization":"GitHub, Inc.","commonName":"Internal Services Root"},"uri":"timestamp.githubapp.com","certChain":{"certificates":[{"rawBytes":"MIICGzCCAaGgAwIBAgIUaaAaeAO3NzYXR/muq5YMnGY34IUwCgYIKoZIzj0EAwMwMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMB4XDTI1MDUyNzAwMDAwMFoXDTI2MDUyNzAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgVGltZXN0YW1waW5nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEAjyAb+jWITMjkgbjWC02N05Zf5/kPs73ICf5/8lHSyHnoqqItP2z3fkMpWiRpelX8eIjzCjZ0Zht+DpGxtJUNh5jMa+fFv2qoDLGbnQ17DXKXCr9nWHF4vCMflHZK5JHo3gwdjAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUyvAcv8s1s37A/IWZAwdEptzjfWIwHwYDVR0jBBgwFoAUdh+GTP65aetHLVLs9hdhGgDIKIwwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwCgYIKoZIzj0EAwMDaAAwZQIwMnoHA4qDtR3eRohUIhvHGG70xT9XMfEnuj0kRvi9wu9VumL4pXqz1ktJgTBm6DrYAjEA5SYnRfUm2yAYmGEkJr9RvSCV9O7X+fL8KqfM4TOPrXJLMERQjhRXbWZ4DjxvifnF"},{"rawBytes":"MIICJDCCAaqgAwIBAgIUckXVHpiw7iJY1V/jY8LYLj5TgqAwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTI4MDgwNTEyMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEISv96hTQ58QroEzzu4K+o9p8YkwDCBia2U7Y+VBNbOG/w1mLRibve9hSeUE1FSyLBMkiFSSm6MexcsbjyqOoNtRxuMinyYt6DSEox+/It2s/bTPyNAN0QP0DCQQOpnTZo3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUdh+GTP65aetHLVLs9hdhGgDIKIwwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAIhf+2E5W2yOb/fCDAjhL/G/jerf74M0tG/zyo32U2keawxkzZosDdwnPaHaGLynAQIwa8nr3en4fZz1AdOZm6nK5hr1qK2F94nifgnAJ/WeT0fZnK/oHan0R28x363qYuYH"},{"rawBytes":"MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="}]},"validFor":{"start":"2025-05-27T00:00:00Z","end":"2025-12-09T00:00:00Z"}},{"subject":{"organization":"GitHub, Inc.","commonName":"Internal Services Root"},"uri":"timestamp.githubapp.com","certChain":{"certificates":[{"rawBytes":"MIICGzCCAaGgAwIBAgIUHiljSG9Nx/Qf/gPpl7V1kyRh/jYwCgYIKoZIzj0EAwMwMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMB4XDTI1MTExMzAwMDAwMFoXDTI2MTExMzAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgVGltZXN0YW1waW5nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEG1ZUJgbtNpInC7jR9hi4OMWK5yF5VeRpmCs6ADS5lx23MEHo3ek32O123e3YwH+J3F9Vbpd7ePQHnpdc6tsBcQ/y/NPw2Vf+VwMI/E35C5/3Y9A5kRXq9CFXC1bU6Fj6o3gwdjAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUXa9JaNrcN+1Je5aHEemVnH2XXtMwHwYDVR0jBBgwFoAUdh+GTP65aetHLVLs9hdhGgDIKIwwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwCgYIKoZIzj0EAwMDaAAwZQIwJyVOay8lriDg4Up0VFWc/o19mzuQeoEItzM2dG50eXF4KbPo9zfdviS3D6StPFRGAjEA5IHCXJ8qP8CsAsQmT3IPimUTAvDNJZI4GFIxs+od/R/EUHGu4NxuwPX3A1ZnJP7z"},{"rawBytes":"MIICJDCCAaqgAwIBAgIUckXVHpiw7iJY1V/jY8LYLj5TgqAwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTI4MDgwNTEyMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEISv96hTQ58QroEzzu4K+o9p8YkwDCBia2U7Y+VBNbOG/w1mLRibve9hSeUE1FSyLBMkiFSSm6MexcsbjyqOoNtRxuMinyYt6DSEox+/It2s/bTPyNAN0QP0DCQQOpnTZo3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUdh+GTP65aetHLVLs9hdhGgDIKIwwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAIhf+2E5W2yOb/fCDAjhL/G/jerf74M0tG/zyo32U2keawxkzZosDdwnPaHaGLynAQIwa8nr3en4fZz1AdOZm6nK5hr1qK2F94nifgnAJ/WeT0fZnK/oHan0R28x363qYuYH"},{"rawBytes":"MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="}]},"validFor":{"start":"2025-11-13T00:00:00Z"}}]}
<?php

declare(strict_types=1);

namespace ThePhpFoundation\Attestation;

/**
 * Some of the Fulcio Sigstore OID extensions.
 *
 * @link https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#136141572641--fulcio
 */
final class FulcioSigstoreOidExtensions
{
    /** @link https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#1361415726418--issuer-v2 */
    public const ISSUER_V2 = '1.3.6.1.4.1.57264.1.8';

    /** @link https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#13614157264112--source-repository-uri */
    public const SOURCE_REPOSITORY_URI = '1.3.6.1.4.1.57264.1.12';

    /** @link https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#13614157264116--source-repository-owner-uri */
    public const SOURCE_REPOSITORY_OWNER_URI = '1.3.6.1.4.1.57264.1.16';
}
<?php

declare(strict_types=1);

namespace ThePhpFoundation\Attestation;

use Webmozart\Assert\Assert;

use function base64_decode;
use function wordwrap;

/** @internal This is not a public API, so should not be depended upon unless you accept the risk of BC breaks */
final class Attestation
{
    /** @var non-empty-string */
    public string $certificate;
    /** @var non-empty-string */
    public string $dsseEnvelopePayload;
    /** @var non-empty-string */
    public string $dsseEnvelopePayloadType;
    /** @var non-empty-string */
    public string $dsseEnvelopeSignature;

    /**
     * @param non-empty-string $certificate
     * @param non-empty-string $dsseEnvelopePayload
     * @param non-empty-string $dsseEnvelopePayloadType
     * @param non-empty-string $dsseEnvelopeSignature
     */
    private function __construct(
        string $certificate,
        string $dsseEnvelopePayload,
        string $dsseEnvelopePayloadType,
        string $dsseEnvelopeSignature
    ) {
        $this->dsseEnvelopeSignature   = $dsseEnvelopeSignature;
        $this->dsseEnvelopePayloadType = $dsseEnvelopePayloadType;
        $this->dsseEnvelopePayload     = $dsseEnvelopePayload;
        $this->certificate             = $certificate;
    }

    /** @param array<array-key, mixed> $attestation */
    public static function fromAttestationBundleWithDsseEnvelope(array $attestation): self
    {
        Assert::keyExists($attestation, 'bundle');
        Assert::isArray($attestation['bundle']);

        Assert::keyExists($attestation['bundle'], 'verificationMaterial');
        Assert::isArray($attestation['bundle']['verificationMaterial']);
        Assert::keyExists($attestation['bundle']['verificationMaterial'], 'certificate');
        Assert::isArray($attestation['bundle']['verificationMaterial']['certificate']);
        Assert::keyExists($attestation['bundle']['verificationMaterial']['certificate'], 'rawBytes');
        Assert::stringNotEmpty($attestation['bundle']['verificationMaterial']['certificate']['rawBytes']);

        Assert::keyExists($attestation['bundle'], 'dsseEnvelope');
        Assert::isArray($attestation['bundle']['dsseEnvelope']);
        Assert::keyExists($attestation['bundle']['dsseEnvelope'], 'payload');
        Assert::stringNotEmpty($attestation['bundle']['dsseEnvelope']['payload']);
        Assert::keyExists($attestation['bundle']['dsseEnvelope'], 'payloadType');
        Assert::stringNotEmpty($attestation['bundle']['dsseEnvelope']['payloadType']);
        Assert::keyExists($attestation['bundle']['dsseEnvelope'], 'signatures');
        Assert::isNonEmptyList($attestation['bundle']['dsseEnvelope']['signatures']);
        Assert::count($attestation['bundle']['dsseEnvelope']['signatures'], 1);
        Assert::keyExists($attestation['bundle']['dsseEnvelope']['signatures'], 0);
        Assert::isArray($attestation['bundle']['dsseEnvelope']['signatures'][0]);
        Assert::keyExists($attestation['bundle']['dsseEnvelope']['signatures'][0], 'sig');
        Assert::stringNotEmpty($attestation['bundle']['dsseEnvelope']['signatures'][0]['sig']);

        $decoratedCertificate = "-----BEGIN CERTIFICATE-----\n"
            . wordwrap($attestation['bundle']['verificationMaterial']['certificate']['rawBytes'], 67, "\n", true) . "\n"
            . "-----END CERTIFICATE-----\n";

        $decodedPayload = base64_decode($attestation['bundle']['dsseEnvelope']['payload']);
        Assert::stringNotEmpty($decodedPayload);

        $decodedSignature = base64_decode($attestation['bundle']['dsseEnvelope']['signatures'][0]['sig']);
        Assert::stringNotEmpty($decodedSignature);

        return new self(
            $decoratedCertificate,
            $decodedPayload,
            $attestation['bundle']['dsseEnvelope']['payloadType'],
            $decodedSignature,
        );
    }
}
<?php

declare(strict_types=1);

namespace ThePhpFoundation\Attestation;

use Webmozart\Assert\Assert;

use function hash_file;

final class FilenameWithChecksum
{
    private const HASH_TYPE_SHA256 = 'sha256';

    /** @var non-empty-string */
    private string $filename;
    /** @var non-empty-string */
    private string $checksum;

    /**
     * @param non-empty-string $filename
     * @param non-empty-string $checksum
     */
    private function __construct(string $filename, string $checksum)
    {
        $this->filename = $filename;
        $this->checksum = $checksum;
    }

    /** @param non-empty-string $filename */
    public static function fromFilename(string $filename): self
    {
        $hash = hash_file(self::HASH_TYPE_SHA256, $filename);
        Assert::stringNotEmpty($hash);

        return new self($filename, $hash);
    }

    /**
     * @param non-empty-string $filename
     * @param non-empty-string $checksum
     */
    public static function fromFilenameAndChecksum(string $filename, string $checksum): self
    {
        return new self($filename, $checksum);
    }

    /** @return non-empty-string */
    public function checksum(): string
    {
        return $this->checksum;
    }

    /** @return non-empty-string */
    public function filename(): string
    {
        return $this->filename;
    }
}
<?php

declare(strict_types=1);

namespace ThePhpFoundation\Attestation;

use Exception;

abstract class AttestationException extends Exception
{
}
<?php

declare(strict_types=1);

namespace ThePhpFoundation\Attestation\Verification;

use Composer\Downloader\TransportException;
use Composer\Factory;
use Composer\IO\NullIO;
use Composer\Util\HttpDownloader;
use ThePhpFoundation\Attestation\Attestation;
use ThePhpFoundation\Attestation\FilenameWithChecksum;
use ThePhpFoundation\Attestation\Verification\Exception\DigestMismatch;
use ThePhpFoundation\Attestation\Verification\Exception\InvalidDerEncodedStringLength;
use ThePhpFoundation\Attestation\Verification\Exception\InvalidSubjectDefinition;
use ThePhpFoundation\Attestation\Verification\Exception\IssuerCertificateVerificationFailed;
use ThePhpFoundation\Attestation\Verification\Exception\MismatchingExtensionValues;
use ThePhpFoundation\Attestation\Verification\Exception\MissingAttestation;
use ThePhpFoundation\Attestation\Verification\Exception\NoIssuerCertificateInTrustedRoot;
use ThePhpFoundation\Attestation\Verification\Exception\NoOpenSsl;
use ThePhpFoundation\Attestation\Verification\Exception\SignatureVerificationFailed;
use Webmozart\Assert\Assert;

use function array_key_exists;
use function array_map;
use function count;
use function explode;
use function extension_loaded;
use function file_get_contents;
use function hash_equals;
use function is_array;
use function is_string;
use function json_decode;
use function openssl_pkey_get_public;
use function openssl_verify;
use function openssl_x509_parse;
use function openssl_x509_verify;
use function ord;
use function sprintf;
use function strlen;
use function substr;
use function trim;
use function wordwrap;

use const OPENSSL_ALGO_SHA256;

class VerifyAttestationWithOpenSsl implements VerifyAttestation
{
    public const TRUSTED_ROOT_FILE_PATH = __DIR__ . '/../../resources/trusted-root.jsonl';

    private const GITHUB_API_URL = 'https://api.github.com';

    /** @var non-empty-string */
    private string $trustedRootFilePath;
    /** @var non-empty-string */
    private string $githubApiBaseUrl;
    private HttpDownloader $httpDownloader;

    /**
     * @param non-empty-string $trustedRootFilePath
     * @param non-empty-string $githubApiBaseUrl
     */
    public function __construct(
        string $trustedRootFilePath,
        string $githubApiBaseUrl,
        HttpDownloader $httpDownloader
    ) {
        $this->httpDownloader      = $httpDownloader;
        $this->githubApiBaseUrl    = $githubApiBaseUrl;
        $this->trustedRootFilePath = $trustedRootFilePath;
    }

    public static function factory(): self
    {
        $io     = new NullIO();
        $config = Factory::createConfig();
        $io->loadConfiguration($config);
        $http = Factory::createHttpDownloader($io, $config);

        return new self(
            self::TRUSTED_ROOT_FILE_PATH,
            self::GITHUB_API_URL,
            $http,
        );
    }

    /** @inheritDoc */
    public function verify(
        FilenameWithChecksum $file,
        string $owner,
        string $expectedSubjectName,
        array $extensionsToVerify
    ): void {
        $attestations = $this->downloadAttestations($file, $owner);

        foreach ($attestations as $attestationIndex => $attestation) {
            /**
             * Useful references. Whilst we don't do the full verification that
             * `gh attestation verify` would (since we don't want to re-invent
             * the wheel), we can do some basic check of the DSSE Envelope.
             * We'll check the payload digest matches our expectation, and
             * verify the signature with the certificate.
             *
             *  - https://github.com/cli/cli/blob/234d2effd545fb9d72ea77aa648caa499aecaa6e/pkg/cmd/attestation/verify/verify.go#L225-L256
             *  - https://docs.sigstore.dev/logging/verify-release/
             *  - https://github.com/secure-systems-lab/dsse/blob/master/protocol.md#protocol
             */
            $this->assertCertificateSignedByTrustedRoot($attestation);

            $this->assertCertificateExtensionClaims($attestation, $extensionsToVerify);

            $this->assertDigestFromAttestationMatchesActual($file, $expectedSubjectName, $attestation);

            $this->verifyDsseEnvelopeSignature($attestationIndex, $attestation);
        }
    }

    private function assertCertificateSignedByTrustedRoot(Attestation $attestation): void
    {
        $attestationCertificateInfo = openssl_x509_parse($attestation->certificate);
        Assert::isArray($attestationCertificateInfo);
        Assert::keyExists($attestationCertificateInfo, 'issuer');
        if (is_array($attestationCertificateInfo['issuer'])) {
            Assert::allStringNotEmpty($attestationCertificateInfo['issuer']);
        } else {
            Assert::stringNotEmpty($attestationCertificateInfo['issuer']);
        }

        $trustedRootCert = file_get_contents($this->trustedRootFilePath);
        Assert::stringNotEmpty($trustedRootCert);
        $trustedRootJsonLines = explode("\n", trim($trustedRootCert));

        /**
         * Now go through our trusted root certificates and attempt to verify that the certificate was signed by an
         * in-date trusted root certificate. The root certificates should be periodically and frequently updated using:
         *
         *     gh attestation trusted-root > resources/trusted-root.jsonl
         *
         * And verifying the contents afterwards to ensure they have not been compromised. This list of JSON blobs may
         * have multiple certificates (e.g. root certificates, intermediate certificates, expired certificates, etc.)
         * so we should loop over to find the correct certificate used to sign the attestation certificate.
         */
        foreach ($trustedRootJsonLines as $jsonLine) {
            /** @var mixed $decoded */
            $decoded = json_decode($jsonLine, true);

            // No certificate authorities defined in this JSON line, skip it...
            if (
                ! is_array($decoded)
                || ! array_key_exists('certificateAuthorities', $decoded)
                || ! is_array($decoded['certificateAuthorities'])
            ) {
                continue;
            }

            /** @var mixed $certificateAuthority */
            foreach ($decoded['certificateAuthorities'] as $certificateAuthority) {
                // We don't have a certificate chain defined, skip it...
                if (
                    ! is_array($certificateAuthority)
                    || ! array_key_exists('certChain', $certificateAuthority)
                    || ! is_array($certificateAuthority['certChain'])
                    || ! array_key_exists('certificates', $certificateAuthority['certChain'])
                    || ! is_array($certificateAuthority['certChain']['certificates'])
                ) {
                    continue;
                }

                /** @var mixed $caCertificateWrapper */
                foreach ($certificateAuthority['certChain']['certificates'] as $caCertificateWrapper) {
                    // Certificate is not in the expected format, i.e. no rawBytes key, skip it...
                    if (
                        ! is_array($caCertificateWrapper)
                        || ! array_key_exists('rawBytes', $caCertificateWrapper)
                        || ! is_string($caCertificateWrapper['rawBytes'])
                        || $caCertificateWrapper['rawBytes'] === ''
                    ) {
                        continue;
                    }

                    // Embed the base64-encoded DER into a PEM envelope for consumption by OpenSSL.
                    $caCertificateString = sprintf(
                        <<<'EOT'
                        -----BEGIN CERTIFICATE-----
                        %s
                        -----END CERTIFICATE-----
                        EOT,
                        wordwrap($caCertificateWrapper['rawBytes'], 67, "\n", true),
                    );

                    $caCertificateInfo = openssl_x509_parse($caCertificateString);
                    Assert::isArray($caCertificateInfo);
                    Assert::keyExists($caCertificateInfo, 'subject');

                    // If the CA certificate subject is not the issuer of the attestation certificate,
                    // this was not the cert we were looking for, skip it...
                    if ($caCertificateInfo['subject'] !== $attestationCertificateInfo['issuer']) {
                        continue;
                    }

                    // Finally, verify that the located CA cert was used to sign the attestation certificate
                    if (openssl_x509_verify($attestation->certificate, $caCertificateString) !== 1) {
                        /** @psalm-suppress MixedArgument */
                        throw IssuerCertificateVerificationFailed::fromIssuer($attestationCertificateInfo['issuer']);
                    }

                    return;
                }
            }
        }

        /**
         * If we got here, we skipped all the certificates in the trusted root collection for various reasons; so we
         * therefore cannot trust the attestation certificate.
         *
         * @psalm-suppress MixedArgument
         */
        throw NoIssuerCertificateInTrustedRoot::fromIssuer($attestationCertificateInfo['issuer']);
    }

    /** @param array<non-empty-string, string> $extensions */
    private function assertCertificateExtensionClaims(Attestation $attestation, array $extensions): void
    {
        $attestationCertificateInfo = openssl_x509_parse($attestation->certificate);
        Assert::isArray($attestationCertificateInfo);
        Assert::keyExists($attestationCertificateInfo, 'extensions');
        Assert::isArray($attestationCertificateInfo['extensions']);

        /**
         * See {@link https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#136141572641--fulcio} for details
         * on the Fulcio extension keys; note the values are DER-encoded strings; the ASN.1 tag is UTF8String (0x0C).
         *
         * Check the extension values are what we expect; these are hard-coded, as we don't expect them
         * to change unless the namespace/repo name change, etc.
         */
        foreach ($extensions as $extension => $expectedValue) {
            Assert::keyExists($attestationCertificateInfo['extensions'], $extension);
            Assert::stringNotEmpty($attestationCertificateInfo['extensions'][$extension]);
            $actualValue = $attestationCertificateInfo['extensions'][$extension];

            // First character (the ASN.1 tag) is expected to be UTF8String (0x0C)
            if (ord($actualValue[0]) !== 0x0C) {
                throw MismatchingExtensionValues::from($extension, $expectedValue, $actualValue);
            }

            /**
             * Second character is expected to be the length of the actual value
             * as long as they are less than 127 bytes (short form)
             *
             * @link https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/basic-encoding-rules.html#Lengths
             */
            $expectedValueLength = ord($actualValue[1]);
            if (strlen($actualValue) !== 2 + $expectedValueLength) {
                throw InvalidDerEncodedStringLength::fromDerString($actualValue, 2 + $expectedValueLength);
            }

            $derDecodedValue = substr($actualValue, 2, $expectedValueLength);
            if ($derDecodedValue !== $expectedValue) {
                throw MismatchingExtensionValues::from($extension, $expectedValue, $derDecodedValue);
            }
        }
    }

    private function verifyDsseEnvelopeSignature(int $attestationIndex, Attestation $attestation): void
    {
        if (! extension_loaded('openssl')) {
            throw NoOpenssl::new();
        }

        $publicKey = openssl_pkey_get_public($attestation->certificate);
        Assert::notFalse($publicKey);

        $preAuthenticationEncoding = sprintf(
            'DSSEv1 %d %s %d %s',
            strlen($attestation->dsseEnvelopePayloadType),
            $attestation->dsseEnvelopePayloadType,
            strlen($attestation->dsseEnvelopePayload),
            $attestation->dsseEnvelopePayload,
        );

        if (openssl_verify($preAuthenticationEncoding, $attestation->dsseEnvelopeSignature, $publicKey, OPENSSL_ALGO_SHA256) !== 1) {
            throw SignatureVerificationFailed::forIndex($attestationIndex);
        }
    }

    /** @param non-empty-string $expectedSubjectName */
    private function assertDigestFromAttestationMatchesActual(FilenameWithChecksum $file, string $expectedSubjectName, Attestation $attestation): void
    {
        /** @var mixed $decodedPayload */
        $decodedPayload = json_decode($attestation->dsseEnvelopePayload, true);

        if (
            ! is_array($decodedPayload)
            || ! array_key_exists('subject', $decodedPayload)
            || ! is_array($decodedPayload['subject'])
            || count($decodedPayload['subject']) !== 1
            || ! array_key_exists(0, $decodedPayload['subject'])
            || ! is_array($decodedPayload['subject'][0])
            || ! array_key_exists('name', $decodedPayload['subject'][0])
            || $decodedPayload['subject'][0]['name'] !== $expectedSubjectName
            || ! array_key_exists('digest', $decodedPayload['subject'][0])
            || ! is_array($decodedPayload['subject'][0]['digest'])
            || ! array_key_exists('sha256', $decodedPayload['subject'][0]['digest'])
            || ! is_string($decodedPayload['subject'][0]['digest']['sha256'])
            || $decodedPayload['subject'][0]['digest']['sha256'] === ''
        ) {
            throw InvalidSubjectDefinition::new();
        }

        $expected = $file->checksum();
        $actual   = $decodedPayload['subject'][0]['digest']['sha256'];
        if (! hash_equals($expected, $actual)) {
            throw DigestMismatch::fromChecksumMismatch($expected, $actual);
        }
    }

    /**
     * @param non-empty-string $owner
     *
     * @return non-empty-list<Attestation>
     */
    private function downloadAttestations(FilenameWithChecksum $file, string $owner): array
    {
        $attestationUrl = sprintf(
            '%s/orgs/%s/attestations/sha256:%s?predicate_type=provenance',
            $this->githubApiBaseUrl,
            $owner,
            $file->checksum(),
        );

        try {
            $decodedJson = $this->httpDownloader->get(
                $attestationUrl,
                [
                    'retry-auth-failure' => true,
                    'http' => [
                        'method' => 'GET',
                        'header' => [],
                    ],
                ],
            )->decodeJson();

            Assert::isArray($decodedJson);
            Assert::keyExists($decodedJson, 'attestations');
            Assert::isNonEmptyList($decodedJson['attestations']);

            return array_map(
                /** @param mixed $attestation */
                static function ($attestation): Attestation {
                    Assert::isArray($attestation);

                    return Attestation::fromAttestationBundleWithDsseEnvelope($attestation);
                },
                $decodedJson['attestations'],
            );
        } catch (TransportException $transportException) {
            if ($transportException->getStatusCode() === 404) {
                throw MissingAttestation::from($file);
            }

            throw $transportException;
        }
    }
}
<?php

declare(strict_types=1);

namespace ThePhpFoundation\Attestation\Verification;

use ThePhpFoundation\Attestation\FilenameWithChecksum;
use ThePhpFoundation\Attestation\Verification\Exception\FailedToVerifyArtifact;

interface VerifyAttestation
{
    /**
     * @param non-empty-string                $owner
     * @param non-empty-string                $expectedSubjectName
     * @param array<non-empty-string, string> $extensionsToVerify
     *
     * @throws FailedToVerifyArtifact
     */
    public function verify(
        FilenameWithChecksum $file,
        string $owner,
        string $expectedSubjectName,
        array $extensionsToVerify
    ): void;
}
<?php

declare(strict_types=1);

namespace ThePhpFoundation\Attestation\Verification\Exception;

use function implode;
use function is_array;
use function sprintf;

class IssuerCertificateVerificationFailed extends FailedToVerifyArtifact
{
    /** @param array<array-key,string>|string $issuer */
    public static function fromIssuer($issuer): self
    {
        return new self(sprintf(
            'Failed to verify the attestation certificate was issued by trusted root %s',
            is_array($issuer) ? implode(',', $issuer) : $issuer,
        ));
    }
}
<?php

declare(strict_types=1);

namespace ThePhpFoundation\Attestation\Verification\Exception;

use function sprintf;
use function substr;

class DigestMismatch extends FailedToVerifyArtifact
{
    public static function fromChecksumMismatch(string $expected, string $actual): self
    {
        return new self(sprintf(
            'Failed checksum verification. Expected %s..., was %s...',
            substr($expected, 0, 8),
            substr($actual, 0, 8),
        ));
    }
}
<?php

declare(strict_types=1);

namespace ThePhpFoundation\Attestation\Verification\Exception;

use ThePhpFoundation\Attestation\AttestationException;

abstract class FailedToVerifyArtifact extends AttestationException
{
}
<?php

declare(strict_types=1);

namespace ThePhpFoundation\Attestation\Verification\Exception;

use ThePhpFoundation\Attestation\FilenameWithChecksum;

use function sprintf;

class MissingAttestation extends FailedToVerifyArtifact
{
    public static function from(FilenameWithChecksum $filenameWithChecksum): self
    {
        return new self(sprintf(
            'Attestation for %s (sha256:%s) was not found',
            $filenameWithChecksum->filename(),
            $filenameWithChecksum->checksum(),
        ));
    }
}
<?php

declare(strict_types=1);

namespace ThePhpFoundation\Attestation\Verification\Exception;

use function sprintf;

class MismatchingExtensionValues extends FailedToVerifyArtifact
{
    public static function from(string $extension, string $expected, string $actual): self
    {
        return new self(sprintf(
            'Attestation certificate extension %s mismatch; expected "%s", was "%s"',
            $extension,
            $expected,
            $actual,
        ));
    }
}
<?php

declare(strict_types=1);

namespace ThePhpFoundation\Attestation\Verification\Exception;

use function sprintf;
use function strlen;

class InvalidDerEncodedStringLength extends FailedToVerifyArtifact
{
    public static function fromDerString(string $derEncodedString, int $expectedLength): self
    {
        return new self(sprintf(
            'DER encoded string length of "%s" was wrong; expected %d characters, was actually %d characters',
            $derEncodedString,
            $expectedLength,
            strlen($derEncodedString),
        ));
    }
}
<?php

declare(strict_types=1);

namespace ThePhpFoundation\Attestation\Verification\Exception;

use function sprintf;

class SignatureVerificationFailed extends FailedToVerifyArtifact
{
    public static function forIndex(int $attestationIndex): self
    {
        return new self(sprintf(
            'Failed to verify DSSE Envelope payload signature for attestation %d',
            $attestationIndex,
        ));
    }
}
<?php

declare(strict_types=1);

namespace ThePhpFoundation\Attestation\Verification\Exception;

use function implode;
use function is_array;
use function sprintf;

class NoIssuerCertificateInTrustedRoot extends FailedToVerifyArtifact
{
    /** @param array<array-key,string>|string $issuer */
    public static function fromIssuer($issuer): self
    {
        return new self(sprintf(
            'Could not find a trusted root certificate for issuer %s',
            is_array($issuer) ? implode(',', $issuer) : $issuer,
        ));
    }
}
<?php

declare(strict_types=1);

namespace ThePhpFoundation\Attestation\Verification\Exception;

class InvalidSubjectDefinition extends FailedToVerifyArtifact
{
    public static function new(): self
    {
        return new self('Unable to extract subject digest from the dsseEnvelope in the attestation.');
    }
}
<?php

declare(strict_types=1);

namespace ThePhpFoundation\Attestation\Verification\Exception;

class NoOpenSsl extends FailedToVerifyArtifact
{
    public static function new(): self
    {
        return new self('Unable to verify without the openssl extension.');
    }
}
<?php

declare(strict_types=1);

namespace ThePhpFoundation\IntegrationTest\Attestation\Verification;

use PHPUnit\Framework\TestCase;
use ThePhpFoundation\Attestation\FilenameWithChecksum;
use ThePhpFoundation\Attestation\FulcioSigstoreOidExtensions;
use ThePhpFoundation\Attestation\Verification\Exception\DigestMismatch;
use ThePhpFoundation\Attestation\Verification\Exception\IssuerCertificateVerificationFailed;
use ThePhpFoundation\Attestation\Verification\Exception\MismatchingExtensionValues;
use ThePhpFoundation\Attestation\Verification\Exception\MissingAttestation;
use ThePhpFoundation\Attestation\Verification\Exception\NoIssuerCertificateInTrustedRoot;
use ThePhpFoundation\Attestation\Verification\Exception\SignatureVerificationFailed;
use ThePhpFoundation\Attestation\Verification\VerifyAttestationWithOpenSsl;

class VerifyAttestationWithOpenSslTest extends TestCase
{
    private const GENUINE_PIE_PHAR                         = __DIR__ . '/../../fixture/genuine-pie.phar';
    private const PIE_WITH_ATTESTATION_FOR_DIFFERENT_OWNER = __DIR__ . '/../../fixture/pie-with-attestation-for-different-owner.phar';

    private VerifyAttestationWithOpenSsl $verifier;

    public function setUp(): void
    {
        $this->verifier = VerifyAttestationWithOpenSsl::factory();
    }

    public function testSuccessfulVerification(): void
    {
        $this->expectNotToPerformAssertions();
        $this->verifier->verify(
            FilenameWithChecksum::fromFilename(self::GENUINE_PIE_PHAR),
            'php',
            'pie.phar',
            [
                FulcioSigstoreOidExtensions::ISSUER_V2 => 'https://token.actions.githubusercontent.com',
                FulcioSigstoreOidExtensions::SOURCE_REPOSITORY_URI => 'https://github.com/php/pie',
                FulcioSigstoreOidExtensions::SOURCE_REPOSITORY_OWNER_URI => 'https://github.com/php',
            ],
        );
    }

    public function testMissingAttestation(): void
    {
        $this->expectException(MissingAttestation::class);
        $this->verifier->verify(
            FilenameWithChecksum::fromFilename(__FILE__),
            'php',
            'pie.phar',
            [
                FulcioSigstoreOidExtensions::ISSUER_V2 => 'https://token.actions.githubusercontent.com',
                FulcioSigstoreOidExtensions::SOURCE_REPOSITORY_URI => 'https://github.com/php/pie',
                FulcioSigstoreOidExtensions::SOURCE_REPOSITORY_OWNER_URI => 'https://github.com/php',
            ],
        );
    }

    public function testArtifactWithAttestationFromDifferentOwner(): void
    {
        $this->expectException(MismatchingExtensionValues::class);
        $this->verifier->verify(
            FilenameWithChecksum::fromFilename(self::PIE_WITH_ATTESTATION_FOR_DIFFERENT_OWNER),
            'asgrim',
            'pie.phar',
            [
                FulcioSigstoreOidExtensions::ISSUER_V2 => 'https://token.actions.githubusercontent.com',
                FulcioSigstoreOidExtensions::SOURCE_REPOSITORY_URI => 'https://github.com/php/pie',
                FulcioSigstoreOidExtensions::SOURCE_REPOSITORY_OWNER_URI => 'https://github.com/php',
            ],
        );
    }

    public function testCertificateWasNotVerifiedFromTrustedRoot(): void
    {
        $this->expectException(IssuerCertificateVerificationFailed::class);
        self::markTestIncomplete();
    }

    public function testCertificateWasNotFoundInAnyTrustedRoot(): void
    {
        $this->expectException(NoIssuerCertificateInTrustedRoot::class);
        self::markTestIncomplete();
    }

    public function testDsseEnvelopetSignatureVerificationFailed(): void
    {
        $this->expectException(SignatureVerificationFailed::class);
        self::markTestIncomplete();
    }

    public function testDigestMismatchInAttestation(): void
    {
        $this->expectException(DigestMismatch::class);
        self::markTestIncomplete();
    }
}
<?php

declare(strict_types=1);

namespace ThePhpFoundation\UnitTest\Attestation;

use PHPUnit\Framework\TestCase;
use ThePhpFoundation\Attestation\FilenameWithChecksum;

/** @covers \ThePhpFoundation\Attestation\FilenameWithChecksum */
final class FilenameWithChecksumTest extends TestCase
{
    public function testFromFilenameAndChecksum(): void
    {
        $filename = FilenameWithChecksum::fromFilenameAndChecksum('foo.bar', 'baz');
        self::assertSame('foo.bar', $filename->filename());
        self::assertSame('baz', $filename->checksum());
    }
}
#!/usr/bin/env php
<?php

/*
 * Generated by Humbug Box 4.6.5@0964604.
 *
 * @link https://github.com/humbug/box
 */

Phar::mapPhar('pie.phar');

require 'phar://pie.phar/.box/bin/check-requirements.php';

$_SERVER['SCRIPT_FILENAME'] = 'phar://pie.phar/bin/pie';
require 'phar://pie.phar/bin/pie';

__HALT_COMPILER(); ?>
R                  vendor/autoload.php        dm      (   vendor/justinrainbow/json-schema/LICENSE                J   vendor/justinrainbow/json-schema/src/JsonSchema/SchemaStorageInterface.php        ]      I   vendor/justinrainbow/json-schema/src/JsonSchema/UriRetrieverInterface.php        Mx      =   vendor/justinrainbow/json-schema/src/JsonSchema/Validator.php        nSA      ]   vendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaMediaTypeException.phpq      q  \      T   vendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidConfigException.phpk      k  ʻ      R   vendor/justinrainbow/json-schema/src/JsonSchema/Exception/UriResolverException.phpd      d  {      ^   vendor/justinrainbow/json-schema/src/JsonSchema/Exception/UnresolvableJsonPointerException.php        eŤ      S   vendor/justinrainbow/json-schema/src/JsonSchema/Exception/JsonDecodingException.php        xF      V   vendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidArgumentException.php        <Э      N   vendor/justinrainbow/json-schema/src/JsonSchema/Exception/RuntimeException.php{      {  #/      W   vendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSourceUriException.phpv      v  LYx      Q   vendor/justinrainbow/json-schema/src/JsonSchema/Exception/ValidationException.php3      3        W   vendor/justinrainbow/json-schema/src/JsonSchema/Exception/ResourceNotFoundException.phpn      n  Mפ      P   vendor/justinrainbow/json-schema/src/JsonSchema/Exception/ExceptionInterface.phpc       c   Wf      T   vendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaException.phph      h  3$      P   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/NumberConstraint.phpI      I  80n      T   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/CollectionConstraint.php        G      O   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstConstraint.phpu      u  '      P   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/StringConstraint.phpo      o  e
      S   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/UndefinedConstraint.phpP@      P@  _@      S   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstraintInterface.php        8Ɗ      J   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Constraint.phpY      Y  .      P   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/SchemaConstraint.php        jfM      N   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/EnumConstraint.php        |      \   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/TypeCheckInterface.php        Mi̤      X   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/LooseTypeCheck.phpD      D  ˳      Y   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/StrictTypeCheck.phpE      E  bI      N   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeConstraint.php3+      3+  \<4      N   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/BaseConstraint.php              P   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ObjectConstraint.php        ή      G   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.php          d      P   vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php        $      F   vendor/justinrainbow/json-schema/src/JsonSchema/Entity/JsonPointer.php        F      X   vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/UriRetrieverInterface.php        ף      R   vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/FileGetContents.php        uAˤ      R   vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/PredefinedArray.php.      .  ٔ      T   vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/AbstractRetriever.php              G   vendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/Curl.php        'S      D   vendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php$      $  ,cĤ      C   vendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriResolver.php        
~       K   vendor/justinrainbow/json-schema/src/JsonSchema/Iterator/ObjectIterator.php+      +  TA      C   vendor/justinrainbow/json-schema/src/JsonSchema/ConstraintError.php        (      8   vendor/justinrainbow/json-schema/src/JsonSchema/Enum.phpg       g   /      A   vendor/justinrainbow/json-schema/src/JsonSchema/SchemaStorage.php        ,O      ;   vendor/justinrainbow/json-schema/src/JsonSchema/Rfc3339.php        (P      ]   vendor/justinrainbow/json-schema/src/JsonSchema/Tool/Validator/RelativeReferenceValidator.php        J7>s      O   vendor/justinrainbow/json-schema/src/JsonSchema/Tool/Validator/UriValidator.php	      	        E   vendor/justinrainbow/json-schema/src/JsonSchema/Tool/DeepComparer.php        Q      A   vendor/justinrainbow/json-schema/src/JsonSchema/Tool/DeepCopy.phpi      i  _      H   vendor/justinrainbow/json-schema/src/JsonSchema/UriResolverInterface.php,      ,  JF      2   vendor/justinrainbow/json-schema/bin/validate-json        w         vendor/react/promise/LICENSEg      g  F      $   vendor/react/promise/src/Promise.php(      (  w%      %   vendor/react/promise/src/Deferred.php        j1      .   vendor/react/promise/src/functions_include.phpa       a   |      -   vendor/react/promise/src/PromiseInterface.phpY      Y  aW      6   vendor/react/promise/src/Exception/LengthException.php^       ^   ?q      9   vendor/react/promise/src/Exception/CompositeException.phpc      c  &*&      6   vendor/react/promise/src/Internal/FulfilledPromise.php=	      =	  m      7   vendor/react/promise/src/Internal/CancellationQueue.php        p      5   vendor/react/promise/src/Internal/RejectedPromise.phpw      w  H_*l      &   vendor/react/promise/src/functions.php-      -        "   vendor/composer/autoload_files.phpF      F  ޖi          vendor/composer/composer/LICENSE,      ,  Vg      3   vendor/composer/composer/src/Composer/Installer.php        z@濤      ?   vendor/composer/composer/src/Composer/Util/RemoteFilesystem.phpl      l  K      9   vendor/composer/composer/src/Composer/Util/Filesystem.phpz      z  aW      9   vendor/composer/composer/src/Composer/Util/SyncHelper.php
      
  'N      2   vendor/composer/composer/src/Composer/Util/Tar.php        uD      5   vendor/composer/composer/src/Composer/Util/GitLab.php1      1  ʤ      2   vendor/composer/composer/src/Composer/Util/Git.php;l      ;l  ߛɤ      7   vendor/composer/composer/src/Composer/Util/Perforce.phpJ      J  =ODg      :   vendor/composer/composer/src/Composer/Util/PackageInfo.phpD      D  rqb3      2   vendor/composer/composer/src/Composer/Util/Url.php              9   vendor/composer/composer/src/Composer/Util/AuthHelper.php29      29  ؤ      3   vendor/composer/composer/src/Composer/Util/Loop.php        tF      C   vendor/composer/composer/src/Composer/Util/StreamContextFactory.phpn%      n%  $      2   vendor/composer/composer/src/Composer/Util/Svn.php&      &  >      8   vendor/composer/composer/src/Composer/Util/Bitbucket.php&      &  ̤      <   vendor/composer/composer/src/Composer/Util/PackageSorter.phpW      W  -!      =   vendor/composer/composer/src/Composer/Util/NoProxyPattern.phpw*      w*  H8      ;   vendor/composer/composer/src/Composer/Util/ErrorHandler.php        1      >   vendor/composer/composer/src/Composer/Util/ProcessExecutor.phpG      G  ky      7   vendor/composer/composer/src/Composer/Util/Silencer.php(      (  |      2   vendor/composer/composer/src/Composer/Util/Zip.phpA      A  T#s      ?   vendor/composer/composer/src/Composer/Util/MetadataMinifier.php        Ϊ      7   vendor/composer/composer/src/Composer/Util/Platform.php*      *  E!`      =   vendor/composer/composer/src/Composer/Util/HttpDownloader.phpG      G        1   vendor/composer/composer/src/Composer/Util/Hg.php        0      =   vendor/composer/composer/src/Composer/Util/ComposerMirror.phpp	      p	  P      >   vendor/composer/composer/src/Composer/Util/ConfigValidator.php%      %  hÔ      8   vendor/composer/composer/src/Composer/Util/IniHelper.phpn      n  $      5   vendor/composer/composer/src/Composer/Util/GitHub.php"      "        @   vendor/composer/composer/src/Composer/Util/Http/CurlResponse.php        Bߝ      @   vendor/composer/composer/src/Composer/Util/Http/ProxyManager.phpW      W        =   vendor/composer/composer/src/Composer/Util/Http/ProxyItem.phpg      g  p      <   vendor/composer/composer/src/Composer/Util/Http/Response.php]      ]  }<      B   vendor/composer/composer/src/Composer/Util/Http/CurlDownloader.phpJ|      J|  2j      @   vendor/composer/composer/src/Composer/Util/Http/RequestProxy.phpe      e   C      8   vendor/composer/composer/src/Composer/Util/TlsHelper.php(      (  k      6   vendor/composer/composer/src/Composer/Script/Event.php
      
  -4֤      =   vendor/composer/composer/src/Composer/Script/ScriptEvents.php        Fs      C   vendor/composer/composer/src/Composer/Command/SelfUpdateCommand.phpk      k  R      ?   vendor/composer/composer/src/Composer/Command/RemoveCommand.php=      =        =   vendor/composer/composer/src/Composer/Command/InitCommand.php_      _  Ս      J   vendor/composer/composer/src/Composer/Command/CheckPlatformReqsCommand.phpY#      Y#  }E      @   vendor/composer/composer/src/Composer/Command/DependsCommand.php        3V7      F   vendor/composer/composer/src/Composer/Command/CreateProjectCommand.phpKb      Kb  H      >   vendor/composer/composer/src/Composer/Command/AboutCommand.php        	zɤ      A   vendor/composer/composer/src/Composer/Command/SuggestsCommand.php        !      ?   vendor/composer/composer/src/Composer/Command/SearchCommand.php        4      C   vendor/composer/composer/src/Composer/Command/ClearCacheCommand.php        8)u      G   vendor/composer/composer/src/Composer/Command/BaseDependencyCommand.php(3      (3  ,5      ?   vendor/composer/composer/src/Composer/Command/ConfigCommand.php        Qc      =   vendor/composer/composer/src/Composer/Command/BumpCommand.php}'      }'  A      ?   vendor/composer/composer/src/Composer/Command/UpdateCommand.phpR      R  f      ?   vendor/composer/composer/src/Composer/Command/GlobalCommand.php        Ҥ      >   vendor/composer/composer/src/Composer/Command/AuditCommand.php)      )  /      A   vendor/composer/composer/src/Composer/Command/OutdatedCommand.php^      ^        B   vendor/composer/composer/src/Composer/Command/RunScriptCommand.php)      )   $Q      ?   vendor/composer/composer/src/Composer/Command/StatusCommand.php!      !        =   vendor/composer/composer/src/Composer/Command/ShowCommand.php      NS      @   vendor/composer/composer/src/Composer/Command/ArchiveCommand.php"      "  At!      =   vendor/composer/composer/src/Composer/Command/HomeCommand.php        JՓ      =   vendor/composer/composer/src/Composer/Command/FundCommand.phpV      V  S      =   vendor/composer/composer/src/Composer/Command/ExecCommand.php        ݸg      A   vendor/composer/composer/src/Composer/Command/DiagnoseCommand.php#      #  *yä      E   vendor/composer/composer/src/Composer/Command/DumpAutoloadCommand.phpr      r  ɤ      A   vendor/composer/composer/src/Composer/Command/LicensesCommand.php&      &  |      @   vendor/composer/composer/src/Composer/Command/InstallCommand.php          ǚ      =   vendor/composer/composer/src/Composer/Command/BaseCommand.phpQB      QB  ۀ      B   vendor/composer/composer/src/Composer/Command/ProhibitsCommand.php8      8  ie٤      A   vendor/composer/composer/src/Composer/Command/ValidateCommand.php%      %  +      B   vendor/composer/composer/src/Composer/Command/ReinstallCommand.php9%      9%  
 S      A   vendor/composer/composer/src/Composer/Command/CompletionTrait.php#      #  _m      G   vendor/composer/composer/src/Composer/Command/PackageDiscoveryTrait.phptS      tS  q      D   vendor/composer/composer/src/Composer/Command/ScriptAliasCommand.phpV      V  z      @   vendor/composer/composer/src/Composer/Command/RequireCommand.php)w      )w  sQ      /   vendor/composer/composer/src/Composer/Cache.php[,      [,  A48      B   vendor/composer/composer/src/Composer/Package/PackageInterface.php/0      /0  3      G   vendor/composer/composer/src/Composer/Package/Version/VersionBumper.php        tmV      H   vendor/composer/composer/src/Composer/Package/Version/VersionGuesser.phpH      H  5      G   vendor/composer/composer/src/Composer/Package/Version/VersionParser.phpS      S  v|      I   vendor/composer/composer/src/Composer/Package/Version/StabilityFilter.php        f      I   vendor/composer/composer/src/Composer/Package/Version/VersionSelector.php/      /  wB      8   vendor/composer/composer/src/Composer/Package/Locker.phpY      Y  #      >   vendor/composer/composer/src/Composer/Package/AliasPackage.php&      &   	      D   vendor/composer/composer/src/Composer/Package/Dumper/ArrayDumper.php        }MI      B   vendor/composer/composer/src/Composer/Package/RootAliasPackage.phpO      O  /      F   vendor/composer/composer/src/Composer/Package/CompleteAliasPackage.phpY      Y  sou      6   vendor/composer/composer/src/Composer/Package/Link.php4      4  bCH      =   vendor/composer/composer/src/Composer/Package/RootPackage.php6      6  (      A   vendor/composer/composer/src/Composer/Package/CompletePackage.php        P2      F   vendor/composer/composer/src/Composer/Package/RootPackageInterface.php	      	  q;k      J   vendor/composer/composer/src/Composer/Package/CompletePackageInterface.php        ,      9   vendor/composer/composer/src/Composer/Package/Package.phpD      D  S      N   vendor/composer/composer/src/Composer/Package/Loader/ValidatingArrayLoader.phpv      v  %(      J   vendor/composer/composer/src/Composer/Package/Loader/RootPackageLoader.php/      /  xW      P   vendor/composer/composer/src/Composer/Package/Loader/InvalidPackageException.phpl      l  l4ؤ      D   vendor/composer/composer/src/Composer/Package/Loader/ArrayLoader.phpG      G  [DX      C   vendor/composer/composer/src/Composer/Package/Loader/JsonLoader.phpv      v  ZV      H   vendor/composer/composer/src/Composer/Package/Loader/LoaderInterface.phph      h        C   vendor/composer/composer/src/Composer/Package/Comparer/Comparer.php_      _  s*      =   vendor/composer/composer/src/Composer/Package/BasePackage.php        M      F   vendor/composer/composer/src/Composer/Package/Archiver/ZipArchiver.php&      &  Q      P   vendor/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFilter.php              P   vendor/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFinder.php        ^>      P   vendor/composer/composer/src/Composer/Package/Archiver/ComposerExcludeFilter.phpz      z  -n5      K   vendor/composer/composer/src/Composer/Package/Archiver/GitExcludeFilter.php        i      G   vendor/composer/composer/src/Composer/Package/Archiver/PharArchiver.php;      ;  b#      L   vendor/composer/composer/src/Composer/Package/Archiver/BaseExcludeFilter.php(      (  ӗ      L   vendor/composer/composer/src/Composer/Package/Archiver/ArchiverInterface.php        Ln      I   vendor/composer/composer/src/Composer/Package/Archiver/ArchiveManager.php%      %  k      C   vendor/composer/composer/src/Composer/Installer/PluginInstaller.phpL      L  ޟ      D   vendor/composer/composer/src/Composer/Installer/LibraryInstaller.php,      ,  hw      G   vendor/composer/composer/src/Composer/Installer/InstallationManager.phpNb      Nb  No      F   vendor/composer/composer/src/Composer/Installer/InstallerInterface.php:      :  [      A   vendor/composer/composer/src/Composer/Installer/PackageEvents.php'      '  Օ      B   vendor/composer/composer/src/Composer/Installer/InstallerEvent.php        i      A   vendor/composer/composer/src/Composer/Installer/NoopInstaller.phpL      L        C   vendor/composer/composer/src/Composer/Installer/BinaryInstaller.php8      8  R      @   vendor/composer/composer/src/Composer/Installer/PackageEvent.php        .C      H   vendor/composer/composer/src/Composer/Installer/MetapackageInstaller.php        ~       C   vendor/composer/composer/src/Composer/Installer/InstallerEvents.php        d2A      D   vendor/composer/composer/src/Composer/Installer/ProjectInstaller.php        T      M   vendor/composer/composer/src/Composer/Installer/SuggestedPackagesReporter.php          d      K   vendor/composer/composer/src/Composer/Installer/BinaryPresenceInterface.php        #p      7   vendor/composer/composer/src/Composer/Json/JsonFile.php~4      ~4        <   vendor/composer/composer/src/Composer/Json/JsonFormatter.php        Wm      F   vendor/composer/composer/src/Composer/Json/JsonValidationException.phpn      n  @YZ      >   vendor/composer/composer/src/Composer/Json/JsonManipulator.phpqS      qS  ]1       D   vendor/composer/composer/src/Composer/DependencyResolver/RuleSet.phpf      f  n_FK      D   vendor/composer/composer/src/Composer/DependencyResolver/Problem.php0      0  52      O   vendor/composer/composer/src/Composer/DependencyResolver/SolverBugException.php        $S      D   vendor/composer/composer/src/Composer/DependencyResolver/Request.php!      !  BQ      A   vendor/composer/composer/src/Composer/DependencyResolver/Pool.php          [;      J   vendor/composer/composer/src/Composer/DependencyResolver/Rule2Literals.phpy
      y
  }      Q   vendor/composer/composer/src/Composer/DependencyResolver/LocalRepoTransaction.php.      .  C      A   vendor/composer/composer/src/Composer/DependencyResolver/Rule.phpM      M  :"      b   vendor/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php        4XE      V   vendor/composer/composer/src/Composer/DependencyResolver/Operation/UpdateOperation.php        t      V   vendor/composer/composer/src/Composer/DependencyResolver/Operation/SolverOperation.phpe      e  e7      W   vendor/composer/composer/src/Composer/DependencyResolver/Operation/InstallOperation.php$      $  '<      d   vendor/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php#      #  4T      Y   vendor/composer/composer/src/Composer/DependencyResolver/Operation/UninstallOperation.php        mP8      Y   vendor/composer/composer/src/Composer/DependencyResolver/Operation/OperationInterface.php        \ܽ      J   vendor/composer/composer/src/Composer/DependencyResolver/RuleWatchNode.phpo      o  eW      J   vendor/composer/composer/src/Composer/DependencyResolver/PoolOptimizer.phpK      K        T   vendor/composer/composer/src/Composer/DependencyResolver/SolverProblemsException.php4      4  W      K   vendor/composer/composer/src/Composer/DependencyResolver/RuleWatchGraph.phpz      z  Ť      K   vendor/composer/composer/src/Composer/DependencyResolver/RuleWatchChain.php        nl      M   vendor/composer/composer/src/Composer/DependencyResolver/RuleSetGenerator.php3      3  qV      L   vendor/composer/composer/src/Composer/DependencyResolver/RuleSetIterator.php0
      0
  3      C   vendor/composer/composer/src/Composer/DependencyResolver/Solver.phpc      c  y      L   vendor/composer/composer/src/Composer/DependencyResolver/LockTransaction.php~      ~  60      L   vendor/composer/composer/src/Composer/DependencyResolver/PolicyInterface.phpw      w  Dp)Ҥ      F   vendor/composer/composer/src/Composer/DependencyResolver/Decisions.php        4      H   vendor/composer/composer/src/Composer/DependencyResolver/PoolBuilder.php0      0  \/      H   vendor/composer/composer/src/Composer/DependencyResolver/GenericRule.php_      _  3]      N   vendor/composer/composer/src/Composer/DependencyResolver/MultiConflictRule.php        㓂      H   vendor/composer/composer/src/Composer/DependencyResolver/Transaction.php6      6  c      J   vendor/composer/composer/src/Composer/DependencyResolver/DefaultPolicy.php'      '  6I&      K   vendor/composer/composer/src/Composer/PHPStan/ConfigReturnTypeExtension.php !       !  3g      S   vendor/composer/composer/src/Composer/PHPStan/RuleReasonDataReturnTypeExtension.phpf
      f
  }       D   vendor/composer/composer/src/Composer/Autoload/AutoloadGenerator.phpu      u   c      D   vendor/composer/composer/src/Composer/Autoload/ClassMapGenerator.php	      	  ZiI      >   vendor/composer/composer/src/Composer/Autoload/ClassLoader.php?      ?  2@u      0   vendor/composer/composer/src/Composer/Config.php%b      %b  b7      C   vendor/composer/composer/src/Composer/Downloader/GzipDownloader.php        uco      G   vendor/composer/composer/src/Composer/Downloader/PerforceDownloader.phpp      p        C   vendor/composer/composer/src/Composer/Downloader/PathDownloader.php6      6  l      H   vendor/composer/composer/src/Composer/Downloader/DownloaderInterface.php        _(      B   vendor/composer/composer/src/Composer/Downloader/RarDownloader.php	      	  2S      R   vendor/composer/composer/src/Composer/Downloader/VcsCapableDownloaderInterface.phpC      C  .(      B   vendor/composer/composer/src/Composer/Downloader/ZipDownloader.php9      9  /t      C   vendor/composer/composer/src/Composer/Downloader/FileDownloader.phpR      R  ]/      A   vendor/composer/composer/src/Composer/Downloader/XzDownloader.php>      >  =D      B   vendor/composer/composer/src/Composer/Downloader/TarDownloader.php        36      F   vendor/composer/composer/src/Composer/Downloader/ArchiveDownloader.php!      !  #      E   vendor/composer/composer/src/Composer/Downloader/FossilDownloader.php              B   vendor/composer/composer/src/Composer/Downloader/VcsDownloader.phpm3      m3  +B      C   vendor/composer/composer/src/Composer/Downloader/PharDownloader.phpu      u        Q   vendor/composer/composer/src/Composer/Downloader/MaxFileSizeExceededException.php        C      H   vendor/composer/composer/src/Composer/Downloader/FilesystemException.php        ._      J   vendor/composer/composer/src/Composer/Downloader/ChangeReportInterface.php'      '  ½2      B   vendor/composer/composer/src/Composer/Downloader/GitDownloader.phpb      b        B   vendor/composer/composer/src/Composer/Downloader/SvnDownloader.php#      #  ړIڤ      A   vendor/composer/composer/src/Composer/Downloader/HgDownloader.php        ;      G   vendor/composer/composer/src/Composer/Downloader/TransportException.php        Sٗ      D   vendor/composer/composer/src/Composer/Downloader/DownloadManager.php5>      5>  .      L   vendor/composer/composer/src/Composer/Downloader/DvcsDownloaderInterface.php6      6  7      M   vendor/composer/composer/src/Composer/Question/StrictConfirmationQuestion.php
      
        2   vendor/composer/composer/src/Composer/Composer.php        G~0      E   vendor/composer/composer/src/Composer/Console/Input/InputArgument.php
      
  [1      C   vendor/composer/composer/src/Composer/Console/Input/InputOption.php^      ^  T\̤      =   vendor/composer/composer/src/Composer/Console/Application.phpt      t  Ȣ      E   vendor/composer/composer/src/Composer/Console/HtmlOutputFormatter.php        x      C   vendor/composer/composer/src/Composer/Console/GithubActionError.phpa      a  	z      I   vendor/composer/composer/src/Composer/EventDispatcher/EventDispatcher.phpd      d  x;m      R   vendor/composer/composer/src/Composer/EventDispatcher/EventSubscriberInterface.phpu      u  i+       R   vendor/composer/composer/src/Composer/EventDispatcher/ScriptExecutionException.php        _!F      ?   vendor/composer/composer/src/Composer/EventDispatcher/Event.php              K   vendor/composer/composer/src/Composer/Plugin/Capability/CommandProvider.phpx      x  Ƨ      F   vendor/composer/composer/src/Composer/Plugin/Capability/Capability.php        (      @   vendor/composer/composer/src/Composer/Plugin/PluginInterface.phpC      C  B      F   vendor/composer/composer/src/Composer/Plugin/PostFileDownloadEvent.php        i      8   vendor/composer/composer/src/Composer/Plugin/Capable.php        F)      =   vendor/composer/composer/src/Composer/Plugin/PluginEvents.phpj      j  U      C   vendor/composer/composer/src/Composer/Plugin/PreCommandRunEvent.php&      &  ;~9e      =   vendor/composer/composer/src/Composer/Plugin/CommandEvent.php_      _         C   vendor/composer/composer/src/Composer/Plugin/PrePoolCreateEvent.php        Zv      >   vendor/composer/composer/src/Composer/Plugin/PluginManager.php[      [  i      G   vendor/composer/composer/src/Composer/Plugin/PluginBlockedException.php        MÀZ      E   vendor/composer/composer/src/Composer/Plugin/PreFileDownloadEvent.phpA      A  w&      9   vendor/composer/composer/src/Composer/PartialComposer.php
      
  .gQ      B   vendor/composer/composer/src/Composer/Exception/NoSslException.php        2      R   vendor/composer/composer/src/Composer/Exception/IrrecoverableDownloadException.php        (1      J   vendor/composer/composer/src/Composer/Advisory/PartialSecurityAdvisory.php8
      8
  r      C   vendor/composer/composer/src/Composer/Advisory/SecurityAdvisory.php	      	  W֤      :   vendor/composer/composer/src/Composer/Advisory/Auditor.php?      ?  i,^      J   vendor/composer/composer/src/Composer/Advisory/IgnoredSecurityAdvisory.php^      ^        =   vendor/composer/composer/src/Composer/SelfUpdate/Versions.php        G`      9   vendor/composer/composer/src/Composer/SelfUpdate/Keys.php        z	      F   vendor/composer/composer/src/Composer/Config/ConfigSourceInterface.phpd      d  3      A   vendor/composer/composer/src/Composer/Config/JsonConfigSource.php<)      <)  X      ;   vendor/composer/composer/src/Composer/InstalledVersions.phpC      C  <nw      :   vendor/composer/composer/src/Composer/Platform/Version.php
      
  nZ͏      ?   vendor/composer/composer/src/Composer/Platform/HhvmDetector.php        $7      :   vendor/composer/composer/src/Composer/Platform/Runtime.php        	i      P   vendor/composer/composer/src/Composer/Repository/RepositorySecurityException.php        v      N   vendor/composer/composer/src/Composer/Repository/AdvisoryProviderInterface.php        i      H   vendor/composer/composer/src/Composer/Repository/LockArrayRepository.php        n.      R   vendor/composer/composer/src/Composer/Repository/InstalledFilesystemRepository.php        \^      B   vendor/composer/composer/src/Composer/Repository/VcsRepository.phpQ      Q        C   vendor/composer/composer/src/Composer/Repository/PathRepository.php"      "  Rq      P   vendor/composer/composer/src/Composer/Repository/WritableRepositoryInterface.php2      2  2      O   vendor/composer/composer/src/Composer/Repository/InvalidRepositoryException.php        .*      G   vendor/composer/composer/src/Composer/Repository/PlatformRepository.php	      	  lu      D   vendor/composer/composer/src/Composer/Repository/ArrayRepository.php)      )  R      T   vendor/composer/composer/src/Composer/Repository/ConfigurableRepositoryInterface.php        -=      B   vendor/composer/composer/src/Composer/Repository/RepositorySet.phpVA      VA  {g      I   vendor/composer/composer/src/Composer/Repository/FilesystemRepository.phpD      D  .F      E   vendor/composer/composer/src/Composer/Repository/FilterRepository.phpt      t  @,      G   vendor/composer/composer/src/Composer/Repository/ArtifactRepository.php}      }  p{l      L   vendor/composer/composer/src/Composer/Repository/WritableArrayRepository.php        1-      F   vendor/composer/composer/src/Composer/Repository/RepositoryManager.phpd      d  L      K   vendor/composer/composer/src/Composer/Repository/CanonicalPackagesTrait.php        a1      H   vendor/composer/composer/src/Composer/Repository/InstalledRepository.php3      3  *ܤ      Q   vendor/composer/composer/src/Composer/Repository/InstalledRepositoryInterface.php}      }  H@      G   vendor/composer/composer/src/Composer/Repository/ComposerRepository.php      x      H   vendor/composer/composer/src/Composer/Repository/RepositoryInterface.php              H   vendor/composer/composer/src/Composer/Repository/CompositeRepository.php        L O      J   vendor/composer/composer/src/Composer/Repository/RootPackageRepository.php&      &  @\      B   vendor/composer/composer/src/Composer/Repository/Vcs/SvnDriver.php4      4  <[?      K   vendor/composer/composer/src/Composer/Repository/Vcs/VcsDriverInterface.php
      
        K   vendor/composer/composer/src/Composer/Repository/Vcs/GitBitbucketDriver.php>      >  /[      E   vendor/composer/composer/src/Composer/Repository/Vcs/GitHubDriver.php-[      -[  ~\      A   vendor/composer/composer/src/Composer/Repository/Vcs/HgDriver.php?      ?  Eጤ      E   vendor/composer/composer/src/Composer/Repository/Vcs/GitLabDriver.php:R      :R  'J      E   vendor/composer/composer/src/Composer/Repository/Vcs/FossilDriver.php/      /  SꙤ      B   vendor/composer/composer/src/Composer/Repository/Vcs/VcsDriver.php        2LD      B   vendor/composer/composer/src/Composer/Repository/Vcs/GitDriver.php!      !  Zs      G   vendor/composer/composer/src/Composer/Repository/Vcs/PerforceDriver.php        }X      C   vendor/composer/composer/src/Composer/Repository/PearRepository.phpK      K  R      F   vendor/composer/composer/src/Composer/Repository/PackageRepository.php<      <  #5      J   vendor/composer/composer/src/Composer/Repository/VersionCacheInterface.phpd      d  ͖:      D   vendor/composer/composer/src/Composer/Repository/RepositoryUtils.phpj
      j
  /      M   vendor/composer/composer/src/Composer/Repository/InstalledArrayRepository.php        0(Y      F   vendor/composer/composer/src/Composer/Repository/RepositoryFactory.php        *T      m   vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterInterface.php        +      n   vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilter.php        |[X      q   vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilter.php        g-fM      k   vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterFactory.phpT      T  rS.      m   vendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreAllPlatformRequirementFilter.php        }      1   vendor/composer/composer/src/Composer/Factory.php        W`      8   vendor/composer/composer/src/Composer/IO/IOInterface.phpX       X   ?0i      3   vendor/composer/composer/src/Composer/IO/NullIO.phpz	      z	  P      6   vendor/composer/composer/src/Composer/IO/ConsoleIO.php(      (  oM      3   vendor/composer/composer/src/Composer/IO/BaseIO.phpt#      t#  Pu      5   vendor/composer/composer/src/Composer/IO/BufferIO.phpe      e  4fۤ      2   vendor/composer/composer/src/Composer/Compiler.php/      /  }F      *   vendor/composer/composer/src/bootstrap.php        ?Ф      %   vendor/composer/composer/bin/composer        y\      $   vendor/composer/composer/bin/compile        eK"      +   vendor/composer/composer/phpstan/rules.neon        ~d      1   vendor/composer/composer/res/composer-schema.jsonZ      Z  P      <   vendor/composer/composer/res/composer-repository-schema.json          븂      6   vendor/composer/composer/res/composer-lock-schema.json        rf         vendor/composer/LICENSE.      .            vendor/composer/installed.php                 vendor/composer/semver/LICENSE        Bh      )   vendor/composer/semver/src/Comparator.phpD
      D
  Ș܁      ,   vendor/composer/semver/src/VersionParser.phpAV      AV  n!Z      /   vendor/composer/semver/src/CompilingMatcher.php          }~@      %   vendor/composer/semver/src/Semver.phpO      O  @      '   vendor/composer/semver/src/Interval.php~      ~  E|V      (   vendor/composer/semver/src/Intervals.phpO      O  3      =   vendor/composer/semver/src/Constraint/ConstraintInterface.php        vx      4   vendor/composer/semver/src/Constraint/Constraint.php1      1  .      /   vendor/composer/semver/src/Constraint/Bound.phpe
      e
  M      9   vendor/composer/semver/src/Constraint/MultiConstraint.phpA%      A%  w      =   vendor/composer/semver/src/Constraint/MatchNoneConstraint.php        ?T>      <   vendor/composer/semver/src/Constraint/MatchAllConstraint.php        D      +   vendor/composer/class-map-generator/LICENSE        DӤ      4   vendor/composer/class-map-generator/src/FileList.phpb      b  -      =   vendor/composer/class-map-generator/src/ClassMapGenerator.phpN6      N6  i      :   vendor/composer/class-map-generator/src/PhpFileCleaner.php        ε      4   vendor/composer/class-map-generator/src/ClassMap.php          p      9   vendor/composer/class-map-generator/src/PhpFileParser.php.      .        !   vendor/composer/ca-bundle/LICENSE        *!^`      *   vendor/composer/ca-bundle/src/CaBundle.php+      +  N      (   vendor/composer/ca-bundle/res/cacert.pemOz     Oz %ߤ      %   vendor/composer/InstalledVersions.phpC      C  <nw         vendor/composer/ClassLoader.php?      ?  2@u      !   vendor/composer/autoload_psr4.phpo      o        "   vendor/composer/platform_check.php        <E      &   vendor/composer/xdebug-handler/LICENSE)      )  #;^      -   vendor/composer/xdebug-handler/src/Status.php        :T      .   vendor/composer/xdebug-handler/src/Process.php:      :  7      4   vendor/composer/xdebug-handler/src/XdebugHandler.phpT      T  [      0   vendor/composer/xdebug-handler/src/PhpConfig.php              #   vendor/composer/pcre/extension.neon        &&1         vendor/composer/pcre/LICENSE        hg^      3   vendor/composer/pcre/src/MatchWithOffsetsResult.php        Q/Ju      <   vendor/composer/pcre/src/PHPStan/InvalidRegexPatternRule.phpC      C  n       ?   vendor/composer/pcre/src/PHPStan/UnsafeStrictGroupsCallRule.php        <B      G   vendor/composer/pcre/src/PHPStan/PregMatchParameterOutTypeExtension.php        SS=      E   vendor/composer/pcre/src/PHPStan/PregMatchTypeSpecifyingExtension.phpC      C  KV      3   vendor/composer/pcre/src/PHPStan/PregMatchFlags.php        zk]      L   vendor/composer/pcre/src/PHPStan/PregReplaceCallbackClosureTypeExtension.php        ɻ      6   vendor/composer/pcre/src/MatchAllWithOffsetsResult.php              9   vendor/composer/pcre/src/UnexpectedNullMatchException.php              !   vendor/composer/pcre/src/Preg.phpE      E  ho      (   vendor/composer/pcre/src/MatchResult.php        	'      7   vendor/composer/pcre/src/MatchAllStrictGroupsResult.php{      {  B@ޤ      *   vendor/composer/pcre/src/PcreException.php^      ^  T      +   vendor/composer/pcre/src/MatchAllResult.php        Ti      *   vendor/composer/pcre/src/ReplaceResult.php        L=      4   vendor/composer/pcre/src/MatchStrictGroupsResult.php        B)|ˤ      "   vendor/composer/pcre/src/Regex.php        
      %   vendor/composer/autoload_classmap.php      (i^      #   vendor/composer/autoload_static.php      w      )   vendor/composer/metadata-minifier/LICENSE        hg^      :   vendor/composer/metadata-minifier/src/MetadataMinifier.php
      
  l3      !   vendor/composer/autoload_real.php        gs      %   vendor/composer/spdx-licenses/LICENSE        Bh      2   vendor/composer/spdx-licenses/src/SpdxLicenses.phpw&      w&  e K      6   vendor/composer/spdx-licenses/res/spdx-exceptions.json        L(      4   vendor/composer/spdx-licenses/res/spdx-licenses.json!     !       '   vendor/composer/autoload_namespaces.php          /t      "   vendor/seld/signal-handler/LICENSE"      "  ?e      0   vendor/seld/signal-handler/src/SignalHandler.phpO      O  ֘         vendor/seld/jsonlint/LICENSE"      "  asy      @   vendor/seld/jsonlint/src/Seld/JsonLint/DuplicateKeyException.php        >f	      4   vendor/seld/jsonlint/src/Seld/JsonLint/Undefined.php        g      0   vendor/seld/jsonlint/src/Seld/JsonLint/Lexer.php"      "  H*      ;   vendor/seld/jsonlint/src/Seld/JsonLint/ParsingException.php        #      5   vendor/seld/jsonlint/src/Seld/JsonLint/JsonParser.phpt`      t`  S`=      !   vendor/seld/jsonlint/bin/jsonlint        )ä         vendor/seld/phar-utils/LICENSE"      "  ?e      )   vendor/seld/phar-utils/src/Timestamps.php        (6      %   vendor/seld/phar-utils/src/Linter.php        '4#      ?   vendor/symfony/service-contracts/ServiceCollectionInterface.phpr      r  K0      =   vendor/symfony/service-contracts/ServiceProviderInterface.phpf      f  Τ      (   vendor/symfony/service-contracts/LICENSE,      ,        ;   vendor/symfony/service-contracts/ServiceSubscriberTrait.php        ^1      ?   vendor/symfony/service-contracts/ServiceSubscriberInterface.php
      
  Ŵ      3   vendor/symfony/service-contracts/ResetInterface.php        񁒳      B   vendor/symfony/service-contracts/ServiceMethodsSubscriberTrait.php        {      8   vendor/symfony/service-contracts/ServiceLocatorTrait.php        v|      7   vendor/symfony/service-contracts/Attribute/Required.php        e;Z      @   vendor/symfony/service-contracts/Attribute/SubscribedService.php              <   vendor/symfony/service-contracts/Test/ServiceLocatorTest.php        _8      @   vendor/symfony/service-contracts/Test/ServiceLocatorTestCase.phpj      j  vʤ      -   vendor/symfony/polyfill-intl-grapheme/LICENSE,      ,  H      5   vendor/symfony/polyfill-intl-grapheme/bootstrap80.phpR      R  i`      2   vendor/symfony/polyfill-intl-grapheme/Grapheme.php)      )  Qΐ      3   vendor/symfony/polyfill-intl-grapheme/bootstrap.phpY	      Y	  IH1      .   vendor/symfony/process/PhpExecutableFinder.php	      	  6˼         vendor/symfony/process/LICENSE,      ,  U      &   vendor/symfony/process/InputStream.php	      	  HgK      =   vendor/symfony/process/Exception/InvalidArgumentException.php        ˅      =   vendor/symfony/process/Exception/ProcessTimedOutException.php        j#a      3   vendor/symfony/process/Exception/LogicException.php        W      5   vendor/symfony/process/Exception/RuntimeException.php        >H      ;   vendor/symfony/process/Exception/ProcessFailedException.php        Y'      7   vendor/symfony/process/Exception/ExceptionInterface.php        +      =   vendor/symfony/process/Exception/ProcessSignaledException.php        4      >   vendor/symfony/process/Exception/RunProcessFailedException.php        U-.      +   vendor/symfony/process/ExecutableFinder.php        [bk      *   vendor/symfony/process/Pipes/UnixPipes.php!      !  ?b      /   vendor/symfony/process/Pipes/PipesInterface.php        \	      .   vendor/symfony/process/Pipes/AbstractPipes.php        X      -   vendor/symfony/process/Pipes/WindowsPipes.php        ZϤ      6   vendor/symfony/process/Messenger/RunProcessContext.phpt      t  䌻p      6   vendor/symfony/process/Messenger/RunProcessMessage.php        Q      =   vendor/symfony/process/Messenger/RunProcessMessageHandler.php        ^|Ĥ      '   vendor/symfony/process/ProcessUtils.php        0      %   vendor/symfony/process/PhpProcess.php	      	        "   vendor/symfony/process/Process.php        G[K      (   vendor/symfony/process/PhpSubprocess.php        gsg      B   vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.phpa	      a	  |ⳤ      @   vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php_      _  d      @   vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.phpf      f  P      F   vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php9      9  >|zK      (   vendor/symfony/polyfill-mbstring/LICENSE,      ,  H      -   vendor/symfony/polyfill-mbstring/Mbstring.phpK      K  WѤ      0   vendor/symfony/polyfill-mbstring/bootstrap80.php'      '  	~
      .   vendor/symfony/polyfill-mbstring/bootstrap.php!      !  d      0   vendor/symfony/event-dispatcher/GenericEvent.php        m      9   vendor/symfony/event-dispatcher/Debug/WrappedListener.php        yG      B   vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.phpi0      i0  I!      <   vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php}      }   Ǥ      '   vendor/symfony/event-dispatcher/LICENSE,      ,  U      =   vendor/symfony/event-dispatcher/Attribute/AsEventListener.php        U      3   vendor/symfony/event-dispatcher/EventDispatcher.php'#      '#  IJ      K   vendor/symfony/event-dispatcher/DependencyInjection/AddEventAliasesPass.php{      {  פ      M   vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php!      !  x̤      <   vendor/symfony/event-dispatcher/EventSubscriberInterface.php        s      <   vendor/symfony/event-dispatcher/EventDispatcherInterface.php        a(\      (   vendor/symfony/filesystem/Filesystem.phpxt      xt  G[      !   vendor/symfony/filesystem/LICENSE,      ,  U      3   vendor/symfony/filesystem/Exception/IOException.php              <   vendor/symfony/filesystem/Exception/IOExceptionInterface.php        '~i      @   vendor/symfony/filesystem/Exception/InvalidArgumentException.php        *      =   vendor/symfony/filesystem/Exception/FileNotFoundException.php        <\P      8   vendor/symfony/filesystem/Exception/RuntimeException.php        iҤ      :   vendor/symfony/filesystem/Exception/ExceptionInterface.php        nj      "   vendor/symfony/filesystem/Path.phpe      e  ׾          vendor/symfony/finder/Finder.php_      _  zp      %   vendor/symfony/finder/SplFileInfo.php        }      5   vendor/symfony/finder/Comparator/NumberComparator.php
      
  hMLۤ      /   vendor/symfony/finder/Comparator/Comparator.php        2TG      3   vendor/symfony/finder/Comparator/DateComparator.php        Y         vendor/symfony/finder/LICENSE,      ,  U      #   vendor/symfony/finder/Gitignore.php        k'      9   vendor/symfony/finder/Exception/AccessDeniedException.php        cWޤ      >   vendor/symfony/finder/Exception/DirectoryNotFoundException.php        RI      =   vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php(      (   襤      ;   vendor/symfony/finder/Iterator/DepthRangeFilterIterator.phpb      b  ,      <   vendor/symfony/finder/Iterator/FilecontentFilterIterator.php        ~       ;   vendor/symfony/finder/Iterator/VcsIgnoredFilterIterator.phpn      n  كt      9   vendor/symfony/finder/Iterator/FilenameFilterIterator.phpQ      Q        3   vendor/symfony/finder/Iterator/SortableIterator.php,      ,        9   vendor/symfony/finder/Iterator/FileTypeFilterIterator.phpo      o  "f      :   vendor/symfony/finder/Iterator/DateRangeFilterIterator.phpy      y        =   vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php        D      5   vendor/symfony/finder/Iterator/PathFilterIterator.php        dސ{      A   vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php        f1      :   vendor/symfony/finder/Iterator/SizeRangeFilterIterator.phpP      P  gɤ      /   vendor/symfony/finder/Iterator/LazyIterator.php        -9      7   vendor/symfony/finder/Iterator/CustomFilterIterator.php        #         vendor/symfony/finder/Glob.php        3<      ,   vendor/symfony/deprecation-contracts/LICENSE,      ,  K      1   vendor/symfony/deprecation-contracts/function.php        Oݤ      1   vendor/symfony/event-dispatcher-contracts/LICENSE,      ,        F   vendor/symfony/event-dispatcher-contracts/EventDispatcherInterface.php        A      3   vendor/symfony/event-dispatcher-contracts/Event.php        с@      '   vendor/symfony/polyfill-ctype/Ctype.php        xs      %   vendor/symfony/polyfill-ctype/LICENSE,      ,        -   vendor/symfony/polyfill-ctype/bootstrap80.phpr      r  F)      +   vendor/symfony/polyfill-ctype/bootstrap.php@      @  jQ9      6   vendor/symfony/polyfill-intl-normalizer/Normalizer.phpd%      d%  u      R   vendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalComposition.phpD      D  'CԤ      T   vendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php{      {  je      X   vendor/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.phpo     o c,      L   vendor/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.phpD5      D5        F   vendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php        %      /   vendor/symfony/polyfill-intl-normalizer/LICENSE,      ,  H      7   vendor/symfony/polyfill-intl-normalizer/bootstrap80.php        ,      5   vendor/symfony/polyfill-intl-normalizer/bootstrap.php        #p      /   vendor/symfony/string/AbstractUnicodeString.phpe      e         (   vendor/symfony/string/AbstractString.phpI      I  ,	      <   vendor/symfony/string/Resources/data/wcswidth_table_zero.php]<      ]<  ?p      <   vendor/symfony/string/Resources/data/wcswidth_table_wide.php2      2  ?Y      -   vendor/symfony/string/Resources/functions.phpT      T  0      )   vendor/symfony/string/CodePointString.php(      (  M      $   vendor/symfony/string/ByteString.php>9      >9           vendor/symfony/string/LICENSE,      ,  զ_Ϥ      4   vendor/symfony/string/Inflector/EnglishInflector.phpD      D  դ      3   vendor/symfony/string/Inflector/FrenchInflector.phpK      K  n@      6   vendor/symfony/string/Inflector/InflectorInterface.phpC      C  Qcc      <   vendor/symfony/string/Exception/InvalidArgumentException.php        e      4   vendor/symfony/string/Exception/RuntimeException.phpp      p  0ʤ      6   vendor/symfony/string/Exception/ExceptionInterface.phpQ      Q  $դ      $   vendor/symfony/string/LazyString.phpo      o  jݤ      '   vendor/symfony/string/UnicodeString.php1      1  W緤      2   vendor/symfony/string/Slugger/SluggerInterface.php        ^      .   vendor/symfony/string/Slugger/AsciiSlugger.php        -Pi      +   vendor/symfony/console/Debug/CliRequest.phpU      U  vr      .   vendor/symfony/console/Command/HelpCommand.php	      	  ^r      *   vendor/symfony/console/Command/Command.phpY      Y  +:C      =   vendor/symfony/console/Command/SignalableCommandInterface.php^      ^  9P      8   vendor/symfony/console/Command/DumpCompletionCommand.phpB      B  ^oE      0   vendor/symfony/console/Command/LockableTrait.php        1      .   vendor/symfony/console/Command/ListCommand.php
      
  ǥB      .   vendor/symfony/console/Command/LazyCommand.php        x      2   vendor/symfony/console/Command/CompleteCommand.php7$      7$        3   vendor/symfony/console/Command/TraceableCommand.phpH(      H(  ϕq      &   vendor/symfony/console/Input/Input.phpJ      J  y~      .   vendor/symfony/console/Input/InputArgument.php        \      4   vendor/symfony/console/Input/InputAwareInterface.phpU      U  }U      9   vendor/symfony/console/Input/StreamableInputInterface.php         &      0   vendor/symfony/console/Input/InputDefinition.php.      .        /   vendor/symfony/console/Input/InputInterface.phpU      U  @-x      *   vendor/symfony/console/Input/ArgvInput.php0      0  	e}      +   vendor/symfony/console/Input/ArrayInput.php        GA      ,   vendor/symfony/console/Input/InputOption.phpu!      u!  RSX;      ,   vendor/symfony/console/Input/StringInput.php
      
  Ɨg      -   vendor/symfony/console/Tester/TesterTrait.php        p^ʩ      3   vendor/symfony/console/Tester/ApplicationTester.phpm
      m
  ǣnf      /   vendor/symfony/console/Tester/CommandTester.phpG	      G	  Sb      @   vendor/symfony/console/Tester/Constraint/CommandIsSuccessful.php!      !  к*      9   vendor/symfony/console/Tester/CommandCompletionTester.php        R      0   vendor/symfony/console/Resources/completion.bash        %}      0   vendor/symfony/console/Resources/completion.fish        Keդ      /   vendor/symfony/console/Resources/completion.zsh        ʤ      4   vendor/symfony/console/Resources/bin/hiddeninput.exe $       $  v      6   vendor/symfony/console/EventListener/ErrorListener.phpA      A  %sӤ      (   vendor/symfony/console/ConsoleEvents.php~      ~  ~6      +   vendor/symfony/console/Helper/HelperSet.phpK      K  U>ߤ      2   vendor/symfony/console/Helper/InputAwareHelper.php              '   vendor/symfony/console/Helper/Table.phpg      g  F¤      7   vendor/symfony/console/Helper/SymfonyQuestionHelper.php        N	      1   vendor/symfony/console/Helper/FormatterHelper.php        FN      2   vendor/symfony/console/Helper/DescriptorHelper.phpN
      N
  D'TB      ,   vendor/symfony/console/Helper/TableStyle.php1      1  3h      0   vendor/symfony/console/Helper/TableCellStyle.php        c      (   vendor/symfony/console/Helper/Dumper.php&      &  zD      1   vendor/symfony/console/Helper/HelperInterface.phpI      I  @Ɔ      6   vendor/symfony/console/Helper/DebugFormatterHelper.php          3      3   vendor/symfony/console/Helper/ProgressIndicator.phpR      R  Ѥ      0   vendor/symfony/console/Helper/TableSeparator.php        &      0   vendor/symfony/console/Helper/QuestionHelper.phpXL      XL  ':3      +   vendor/symfony/console/Helper/TableRows.phpN      N  $      (   vendor/symfony/console/Helper/Helper.phps      s  !q      -   vendor/symfony/console/Helper/ProgressBar.phpO      O  kBA9      /   vendor/symfony/console/Helper/ProcessHelper.php        6      /   vendor/symfony/console/Helper/OutputWrapper.php        Ro      +   vendor/symfony/console/Helper/TableCell.php        c'      2   vendor/symfony/console/Event/ConsoleErrorEvent.php        aM      4   vendor/symfony/console/Event/ConsoleCommandEvent.php.      .        6   vendor/symfony/console/Event/ConsoleTerminateEvent.php        99      -   vendor/symfony/console/Event/ConsoleEvent.phpP      P  ^O      3   vendor/symfony/console/Event/ConsoleSignalEvent.php        _W      &   vendor/symfony/console/Application.php              =   vendor/symfony/console/DataCollector/CommandDataCollector.php        O      0   vendor/symfony/console/Completion/Suggestion.php<      <  <Mؖ      @   vendor/symfony/console/Completion/Output/ZshCompletionOutput.php        yZ1      F   vendor/symfony/console/Completion/Output/CompletionOutputInterface.php        3O      A   vendor/symfony/console/Completion/Output/BashCompletionOutput.php        S      A   vendor/symfony/console/Completion/Output/FishCompletionOutput.php        +Ǥ      5   vendor/symfony/console/Completion/CompletionInput.php        p,9      ;   vendor/symfony/console/Completion/CompletionSuggestions.phpF      F  $*      0   vendor/symfony/console/Descriptor/Descriptor.php`
      `
  %|      4   vendor/symfony/console/Descriptor/TextDescriptor.php 0       0  Ҥ      9   vendor/symfony/console/Descriptor/DescriptorInterface.phpQ      Q  m'      @   vendor/symfony/console/Descriptor/ReStructuredTextDescriptor.php)      )  p      3   vendor/symfony/console/Descriptor/XmlDescriptor.php0&      0&  ww      <   vendor/symfony/console/Descriptor/ApplicationDescription.php        xۜԤ      8   vendor/symfony/console/Descriptor/MarkdownDescriptor.php              4   vendor/symfony/console/Descriptor/JsonDescriptor.phpq      q  x꽤         vendor/symfony/console/LICENSE,      ,  U      /   vendor/symfony/console/Output/AnsiColorMode.php        PE      1   vendor/symfony/console/Output/OutputInterface.php        '      .   vendor/symfony/console/Output/StreamOutput.php         v      5   vendor/symfony/console/Output/TrimmedBufferOutput.php2      2  T      /   vendor/symfony/console/Output/ConsoleOutput.php              ,   vendor/symfony/console/Output/NullOutput.php#      #  S      0   vendor/symfony/console/Output/BufferedOutput.phpF      F  +n      8   vendor/symfony/console/Output/ConsoleOutputInterface.php/      /  RФ      (   vendor/symfony/console/Output/Output.php        }Y0      6   vendor/symfony/console/Output/ConsoleSectionOutput.php!      !  1w"      8   vendor/symfony/console/Question/ConfirmationQuestion.php!      !  O!      2   vendor/symfony/console/Question/ChoiceQuestion.php        vk      ,   vendor/symfony/console/Question/Question.php              #   vendor/symfony/console/Terminal.phpp      p  C9פ      >   vendor/symfony/console/Exception/RunCommandFailedException.phpQ      Q  9QS?      ;   vendor/symfony/console/Exception/InvalidOptionException.php        /el      =   vendor/symfony/console/Exception/InvalidArgumentException.php        u i      3   vendor/symfony/console/Exception/LogicException.php        SML      5   vendor/symfony/console/Exception/RuntimeException.php        *b      ?   vendor/symfony/console/Exception/NamespaceNotFoundException.php        BLH      7   vendor/symfony/console/Exception/ExceptionInterface.php        l      =   vendor/symfony/console/Exception/CommandNotFoundException.php        fZj      :   vendor/symfony/console/Exception/MissingInputException.php        Qg;      ,   vendor/symfony/console/Style/OutputStyle.phps      s  K8L      -   vendor/symfony/console/Style/SymfonyStyle.php;      ;  GNE      /   vendor/symfony/console/Style/StyleInterface.php
      
  $      .   vendor/symfony/console/Attribute/AsCommand.php]      ]  ֚      6   vendor/symfony/console/Messenger/RunCommandContext.php&      &  b7      =   vendor/symfony/console/Messenger/RunCommandMessageHandler.php+      +  &VCǤ      6   vendor/symfony/console/Messenger/RunCommandMessage.php        mr      2   vendor/symfony/console/CI/GithubActionReporter.php]      ]  총      D   vendor/symfony/console/DependencyInjection/AddConsoleCommandPass.phpw      w  #LҤ      !   vendor/symfony/console/Cursor.php        u      ?   vendor/symfony/console/CommandLoader/CommandLoaderInterface.php*      *  8      =   vendor/symfony/console/CommandLoader/FactoryCommandLoader.php"      "  J6\      ?   vendor/symfony/console/CommandLoader/ContainerCommandLoader.php        hH      3   vendor/symfony/console/SignalRegistry/SignalMap.php        (Դ      8   vendor/symfony/console/SignalRegistry/SignalRegistry.php        D'O#      F   vendor/symfony/console/Formatter/WrappableOutputFormatterInterface.php        #ó      4   vendor/symfony/console/Formatter/OutputFormatter.phpn"      n"  g      8   vendor/symfony/console/Formatter/NullOutputFormatter.php        %      =   vendor/symfony/console/Formatter/OutputFormatterInterface.php        `)      B   vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php        &      =   vendor/symfony/console/Formatter/NullOutputFormatterStyle.php[      [  }8a      9   vendor/symfony/console/Formatter/OutputFormatterStyle.php              >   vendor/symfony/console/Formatter/OutputFormatterStyleStack.php
      
  ¤      /   vendor/symfony/console/Logger/ConsoleLogger.php        @      3   vendor/symfony/console/SingleCommandApplication.php        SUʤ          vendor/symfony/console/Color.php        .{j      )   vendor/marc-mabe/php-enum/src/EnumMap.php-      -  lQ      )   vendor/marc-mabe/php-enum/src/EnumSet.php        PG{      7   vendor/marc-mabe/php-enum/src/EnumSerializableTrait.php        W      &   vendor/marc-mabe/php-enum/src/Enum.phpw7      w7  ?      .   vendor/marc-mabe/php-enum/stubs/Stringable.php          9      %   vendor/marc-mabe/php-enum/LICENSE.txt        CѤ      C   vendor/fidry/cpu-core-counter/src/Finder/CmiCmdletLogicalFinder.php        t[#      D   vendor/fidry/cpu-core-counter/src/Finder/CmiCmdletPhysicalFinder.php        q      8   vendor/fidry/cpu-core-counter/src/Finder/NProcFinder.php          cM      :   vendor/fidry/cpu-core-counter/src/Finder/CpuInfoFinder.php	      	  z      ?   vendor/fidry/cpu-core-counter/src/Finder/LscpuLogicalFinder.php=      =  7k      =   vendor/fidry/cpu-core-counter/src/Finder/HwPhysicalFinder.phpg      g  GȤ      A   vendor/fidry/cpu-core-counter/src/Finder/OnlyOnOSFamilyFinder.php	      	  *Ue      ?   vendor/fidry/cpu-core-counter/src/Finder/WmicPhysicalFinder.php        jhS      >   vendor/fidry/cpu-core-counter/src/Finder/_NProcessorFinder.php        8      @   vendor/fidry/cpu-core-counter/src/Finder/ProcOpenBasedFinder.php}
      }
  @Pq      ;   vendor/fidry/cpu-core-counter/src/Finder/FinderRegistry.phpB
      B
  %q      >   vendor/fidry/cpu-core-counter/src/Finder/NullCpuCoreFinder.php              <   vendor/fidry/cpu-core-counter/src/Finder/HwLogicalFinder.phpc      c  l      :   vendor/fidry/cpu-core-counter/src/Finder/CpuCoreFinder.php        c#      @   vendor/fidry/cpu-core-counter/src/Finder/LscpuPhysicalFinder.php        1V;      A   vendor/fidry/cpu-core-counter/src/Finder/SkipOnOSFamilyFinder.php	      	  })      >   vendor/fidry/cpu-core-counter/src/Finder/EnvVariableFinder.php        ͈[e      =   vendor/fidry/cpu-core-counter/src/Finder/NProcessorFinder.php        s      >   vendor/fidry/cpu-core-counter/src/Finder/WmicLogicalFinder.php        "b      ?   vendor/fidry/cpu-core-counter/src/Finder/DummyCpuCoreFinder.php        3      C   vendor/fidry/cpu-core-counter/src/Finder/OnlyInPowerShellFinder.php\      \  J̤      I   vendor/fidry/cpu-core-counter/src/Finder/WindowsRegistryLogicalFinder.php        fY      =   vendor/fidry/cpu-core-counter/src/NumberOfCpuCoreNotFound.php(      (  J      ;   vendor/fidry/cpu-core-counter/src/ParallelisationResult.php        BJפ      /   vendor/fidry/cpu-core-counter/src/Diagnoser.php        H3ԇ      4   vendor/fidry/cpu-core-counter/src/CpuCoreCounter.php#      #  mX      >   vendor/fidry/cpu-core-counter/src/Executor/ProcessExecutor.php        KS      ?   vendor/fidry/cpu-core-counter/src/Executor/ProcOpenExecutor.php/      /  ^1 d      +   vendor/fidry/cpu-core-counter/bin/trace.php        M$      -   vendor/fidry/cpu-core-counter/bin/execute.php        <=      .   vendor/fidry/cpu-core-counter/bin/diagnose.php)      )  n         vendor/webmozart/assert/LICENSE<      <  t}      %   vendor/webmozart/assert/src/Mixin.php      .A      8   vendor/webmozart/assert/src/InvalidArgumentException.php[      [  *i      &   vendor/webmozart/assert/src/Assert.php6      6  \1      6   vendor/illuminate/container/EntryNotFoundException.php          1Ҥ      8   vendor/illuminate/container/ContextualBindingBuilder.php(	      (	  Ф      $   vendor/illuminate/container/Util.php        `      3   vendor/illuminate/container/RewindableGenerator.phpi      i        +   vendor/illuminate/container/BoundMethod.php        t      )   vendor/illuminate/container/Container.php2      2        6   vendor/illuminate/contracts/Debug/ExceptionHandler.phpK      K  a      .   vendor/illuminate/contracts/Hashing/Hasher.php        T      H   vendor/illuminate/contracts/Session/Middleware/AuthenticatesSessions.phpf       f   鲤      /   vendor/illuminate/contracts/Session/Session.php7      7  v      5   vendor/illuminate/contracts/Filesystem/Filesystem.php        I      ?   vendor/illuminate/contracts/Filesystem/LockTimeoutException.php{       {   q      @   vendor/illuminate/contracts/Filesystem/FileNotFoundException.php|       |   1įd      0   vendor/illuminate/contracts/Filesystem/Cloud.php           f      2   vendor/illuminate/contracts/Filesystem/Factory.php        Pk      +   vendor/illuminate/contracts/View/Engine.php	      	  _4      )   vendor/illuminate/contracts/View/View.php        Q      =   vendor/illuminate/contracts/View/ViewCompilationException.phpy       y   xt+פ      ,   vendor/illuminate/contracts/View/Factory.php        !ʤ      3   vendor/illuminate/contracts/Console/Application.php        \.K      >   vendor/illuminate/contracts/Console/PromptsForMissingInput.php\       \   S      .   vendor/illuminate/contracts/Console/Kernel.php        t      2   vendor/illuminate/contracts/Console/Isolatable.phpP       P   kdO      1   vendor/illuminate/contracts/Queue/ShouldQueue.phpO       O   at      C   vendor/illuminate/contracts/Queue/ShouldBeUniqueUntilProcessing.phpx       x   0Q      7   vendor/illuminate/contracts/Queue/ShouldBeEncrypted.phpU       U   +      4   vendor/illuminate/contracts/Queue/ShouldBeUnique.phpR       R   ql      -   vendor/illuminate/contracts/Queue/Monitor.php        '      )   vendor/illuminate/contracts/Queue/Job.php        d`      4   vendor/illuminate/contracts/Queue/ClearableQueue.php          	      =   vendor/illuminate/contracts/Queue/EntityNotFoundException.php        >pܤ      4   vendor/illuminate/contracts/Queue/EntityResolver.php        Zɤ      +   vendor/illuminate/contracts/Queue/Queue.php	      	  ?      <   vendor/illuminate/contracts/Queue/ShouldQueueAfterCommit.phpn       n         9   vendor/illuminate/contracts/Queue/QueueableCollection.php        U7{      -   vendor/illuminate/contracts/Queue/Factory.php        ;D      5   vendor/illuminate/contracts/Queue/QueueableEntity.php        Y      1   vendor/illuminate/contracts/Pipeline/Pipeline.php        (.+      ,   vendor/illuminate/contracts/Pipeline/Hub.php&      &  jB      2   vendor/illuminate/contracts/Translation/Loader.php0      0  !@      6   vendor/illuminate/contracts/Translation/Translator.php        px      ?   vendor/illuminate/contracts/Translation/HasLocalePreference.php          p      0   vendor/illuminate/contracts/Support/Htmlable.php          뤏#      0   vendor/illuminate/contracts/Support/Jsonable.php          RQؤ      5   vendor/illuminate/contracts/Support/ValidatedData.php                D   vendor/illuminate/contracts/Support/CanBeEscapedWhenCastToString.phpI      I  ŝ_0      A   vendor/illuminate/contracts/Support/DeferringDisplayableValue.php               7   vendor/illuminate/contracts/Support/MessageProvider.php          A       2   vendor/illuminate/contracts/Support/MessageBag.php        g      :   vendor/illuminate/contracts/Support/DeferrableProvider.php          Tܤ      2   vendor/illuminate/contracts/Support/Renderable.php          _ޤ      3   vendor/illuminate/contracts/Support/Responsable.php2      2  e      1   vendor/illuminate/contracts/Support/Arrayable.php        PT      /   vendor/illuminate/contracts/Mail/Attachable.php          @</      .   vendor/illuminate/contracts/Mail/MailQueue.php        !`4      +   vendor/illuminate/contracts/Mail/Mailer.php        >(ͤ      -   vendor/illuminate/contracts/Mail/Mailable.php        aP      ,   vendor/illuminate/contracts/Mail/Factory.php          3(F      9   vendor/illuminate/contracts/Validation/ValidationRule.php        +       =   vendor/illuminate/contracts/Validation/ValidatorAwareRule.php<      <  ΅      /   vendor/illuminate/contracts/Validation/Rule.php        7:      @   vendor/illuminate/contracts/Validation/ValidatesWhenResolved.php          >OG      4   vendor/illuminate/contracts/Validation/Validator.phpm      m  D}ۤ      8   vendor/illuminate/contracts/Validation/DataAwareRule.php          qܤ      7   vendor/illuminate/contracts/Validation/ImplicitRule.php                @   vendor/illuminate/contracts/Validation/UncompromisedVerifier.php        GL      2   vendor/illuminate/contracts/Validation/Factory.php        9{      8   vendor/illuminate/contracts/Validation/InvokableRule.php        Գ      <   vendor/illuminate/contracts/Foundation/ExceptionRenderer.php          Eސ      6   vendor/illuminate/contracts/Foundation/Application.php        R&      7   vendor/illuminate/contracts/Foundation/CachesRoutes.php^      ^        >   vendor/illuminate/contracts/Foundation/CachesConfiguration.php        .      :   vendor/illuminate/contracts/Foundation/MaintenanceMode.php        b)      6   vendor/illuminate/contracts/Process/InvokedProcess.php        r(      5   vendor/illuminate/contracts/Process/ProcessResult.php              1   vendor/illuminate/contracts/Config/Repository.php        jBä      ;   vendor/illuminate/contracts/Encryption/DecryptException.php           2v      4   vendor/illuminate/contracts/Encryption/Encrypter.php        nvԤ      :   vendor/illuminate/contracts/Encryption/StringEncrypter.php:      :  b)      ;   vendor/illuminate/contracts/Encryption/EncryptException.php          jJ      8   vendor/illuminate/contracts/Auth/Access/Authorizable.php6      6  e      0   vendor/illuminate/contracts/Auth/Access/Gate.php        Ǥ      :   vendor/illuminate/contracts/Auth/PasswordBrokerFactory.php        mM      1   vendor/illuminate/contracts/Auth/UserProvider.phpZ      Z  N0      3   vendor/illuminate/contracts/Auth/PasswordBroker.php	      	  +r      E   vendor/illuminate/contracts/Auth/Middleware/AuthenticatesRequests.phpc       c   4v      5   vendor/illuminate/contracts/Auth/CanResetPassword.php        y<(      6   vendor/illuminate/contracts/Auth/SupportsBasicAuth.php{      {  ycf      4   vendor/illuminate/contracts/Auth/MustVerifyEmail.php        w      2   vendor/illuminate/contracts/Auth/StatefulGuard.php                *   vendor/illuminate/contracts/Auth/Guard.phpK      K  o<      4   vendor/illuminate/contracts/Auth/Authenticatable.php        ,ƾ      ,   vendor/illuminate/contracts/Auth/Factory.php        "      8   vendor/illuminate/contracts/Routing/BindingRegistrar.php        A6      1   vendor/illuminate/contracts/Routing/Registrar.php
      
        7   vendor/illuminate/contracts/Routing/ResponseFactory.php        Q      3   vendor/illuminate/contracts/Routing/UrlRoutable.phpq      q  'ݤ      4   vendor/illuminate/contracts/Routing/UrlGenerator.php        
      6   vendor/illuminate/contracts/Cookie/QueueingFactory.phpk      k  Fz      .   vendor/illuminate/contracts/Cookie/Factory.php        #      @   vendor/illuminate/contracts/Events/ShouldDispatchAfterCommit.php^       ^   1A      D   vendor/illuminate/contracts/Events/ShouldHandleEventsAfterCommit.phpb       b   `x      1   vendor/illuminate/contracts/Events/Dispatcher.php          y      ;   vendor/illuminate/contracts/Broadcasting/ShouldBeUnique.phpY       Y   ~      @   vendor/illuminate/contracts/Broadcasting/HasBroadcastChannel.php        vc7R      ?   vendor/illuminate/contracts/Broadcasting/ShouldBroadcastNow.phpu       u   G      8   vendor/illuminate/contracts/Broadcasting/Broadcaster.php;      ;  m$!      4   vendor/illuminate/contracts/Broadcasting/Factory.php!      !  zΫ      <   vendor/illuminate/contracts/Broadcasting/ShouldBroadcast.php'      '  6      5   vendor/illuminate/contracts/Notifications/Factory.php              8   vendor/illuminate/contracts/Notifications/Dispatcher.phpd      d  U
=      B   vendor/illuminate/contracts/Container/ContextualBindingBuilder.phpz      z  r      E   vendor/illuminate/contracts/Container/CircularDependencyException.php          .      D   vendor/illuminate/contracts/Container/BindingResolutionException.php          k      3   vendor/illuminate/contracts/Container/Container.php        9=C      :   vendor/illuminate/contracts/Cache/LockTimeoutException.phpv       v   
      +   vendor/illuminate/contracts/Cache/Store.phpm      m  hWΤ      2   vendor/illuminate/contracts/Cache/LockProvider.php5      5  o@      *   vendor/illuminate/contracts/Cache/Lock.php3      3  g:o      0   vendor/illuminate/contracts/Cache/Repository.phpU      U  Ԥ      -   vendor/illuminate/contracts/Cache/Factory.php        AA      6   vendor/illuminate/contracts/Bus/QueueingDispatcher.php        *&      .   vendor/illuminate/contracts/Bus/Dispatcher.php        F"      +   vendor/illuminate/contracts/Http/Kernel.phpp      p  $      9   vendor/illuminate/contracts/Database/Query/Expression.php7      7  y)      B   vendor/illuminate/contracts/Database/Query/ConditionExpression.phpl       l   +bc      6   vendor/illuminate/contracts/Database/Query/Builder.php          )(Z      8   vendor/illuminate/contracts/Database/ModelIdentifier.php        ʄW      >   vendor/illuminate/contracts/Database/Events/MigrationEvent.php\       \   ;ä      L   vendor/illuminate/contracts/Database/Eloquent/DeviatesCastableAttributes.php        s      A   vendor/illuminate/contracts/Database/Eloquent/CastsAttributes.php        >H      :   vendor/illuminate/contracts/Database/Eloquent/Castable.php              N   vendor/illuminate/contracts/Database/Eloquent/SerializesCastableAttributes.php        /6B:      9   vendor/illuminate/contracts/Database/Eloquent/Builder.php.      .  _      J   vendor/illuminate/contracts/Database/Eloquent/SupportsPartialRelations.php        @j      H   vendor/illuminate/contracts/Database/Eloquent/CastsInboundAttributes.php        ߈c      =   vendor/illuminate/contracts/Redis/LimiterTimeoutException.phpy       y   c{      /   vendor/illuminate/contracts/Redis/Connector.php|      |        0   vendor/illuminate/contracts/Redis/Connection.php              -   vendor/illuminate/contracts/Redis/Factory.php
      
  +      :   vendor/illuminate/contracts/Pagination/CursorPaginator.php
      
  z      ?   vendor/illuminate/contracts/Pagination/LengthAwarePaginator.php)      )  xPU      4   vendor/illuminate/contracts/Pagination/Paginator.php	      	  $:         vendor/psr/log/LICENSE=      =  pO      "   vendor/psr/log/src/LoggerTrait.php
      
  WP      /   vendor/psr/log/src/InvalidArgumentException.php`       `    X1      &   vendor/psr/log/src/LoggerInterface.php
      
  q      +   vendor/psr/log/src/LoggerAwareInterface.php           {         vendor/psr/log/src/LogLevel.phpP      P        %   vendor/psr/log/src/AbstractLogger.php        ۛ      '   vendor/psr/log/src/LoggerAwareTrait.php[      [  ۤ      !   vendor/psr/log/src/NullLogger.php        (/      .   vendor/psr/simple-cache/src/CacheException.php          ٭q      8   vendor/psr/simple-cache/src/InvalidArgumentException.php        3      .   vendor/psr/simple-cache/src/CacheInterface.php        {)         vendor/psr/container/LICENSEy      y  Op      7   vendor/psr/container/src/NotFoundExceptionInterface.php          >悤      /   vendor/psr/container/src/ContainerInterface.php              8   vendor/psr/container/src/ContainerExceptionInterface.php          ^33      #   vendor/psr/event-dispatcher/LICENSE(      (  }]      ;   vendor/psr/event-dispatcher/src/StoppableEventInterface.php              <   vendor/psr/event-dispatcher/src/EventDispatcherInterface.php        'a      =   vendor/psr/event-dispatcher/src/ListenerProviderInterface.php        e         src/Util/FileNotFound.phpP      P  d         src/Util/PieVersion.php	      	  5&         src/Util/Process.phpV      V  x45         src/Util/Emoji.phpK      K  ܤ         src/Util/CaptureErrors.php/      /  0      !   src/Command/SelfUpdateCommand.phpe      e  [*o         src/Command/CommandHelper.phpD      D  aoW         src/Command/InfoCommand.phpR      R  }2I      $   src/Command/RepositoryAddCommand.php        BȲ         src/Command/DownloadCommand.php        Z      !   src/Command/SelfVerifyCommand.phpO      O  ٧         src/Command/ShowCommand.php=#      =#  &L{          src/Command/UninstallCommand.php        );         src/Command/BuildCommand.php              %   src/Command/RepositoryListCommand.php"      "  q/¤      2   src/Command/InstallExtensionsForProjectCommand.php)      )  u         src/Command/InstallCommand.php          %/      '   src/Command/RepositoryRemoveCommand.php        	YK          src/Command/InvokeSubCommand.phpo      o        /   src/Installing/Ini/AddExtensionToTheIniFile.php         E      %   src/Installing/Ini/RemoveIniEntry.php3      3  z|N      '   src/Installing/Ini/SetupIniApproach.php        -܃      5   src/Installing/Ini/IsExtensionAlreadyInTheIniFile.phpq      q  7      %   src/Installing/Ini/OndrejPhpenmod.php        Ҿ j      8   src/Installing/Ini/RemoveIniEntryWithFileGetContents.php        Sm      5   src/Installing/Ini/PreCheckExtensionAlreadyLoaded.php9      9  [      /   src/Installing/Ini/PickBestSetupIniApproach.php	      	  l'R      8   src/Installing/Ini/CheckAndAddExtensionToIniIfNeeded.phpp
      p
  N      )   src/Installing/Ini/DockerPhpExtEnable.php*      *  ޑ,C      +   src/Installing/Ini/StandardSinglePhpIni.php@      @  |+j      8   src/Installing/Ini/StandardAdditionalPhpIniDirectory.php        O      )   src/Installing/PackageMetadataMissing.php^      ^  sۤ         src/Installing/Install.php          ߤ      !   src/Installing/WindowsInstall.php        Ԥ         src/Installing/Uninstall.phpR      R  BD         src/Installing/SetupIniFile.php        Oy%      C   src/Installing/InstallForPhpProject/DetermineExtensionsRequired.php        
.      <   src/Installing/InstallForPhpProject/FindMatchingPackages.php        F      >   src/Installing/InstallForPhpProject/InstallSelectedPackage.php	      	  R~`[      A   src/Installing/InstallForPhpProject/InstallPiePackageFromPath.phpN      N  .	      A   src/Installing/InstallForPhpProject/ComposerFactoryForProject.phpp      p  ,+      '   src/Installing/UninstallUsingUnlink.php#	      #	  eC      *   src/Installing/FailedToRemoveExtension.php        OĤ         src/Installing/UnixInstall.php        |L         src/Building/Build.php        4w         src/Building/UnixBuild.php          y         src/Building/WindowsBuild.phpM      M  /	      (   src/Building/ExtensionBinaryNotFound.php        {      7   src/DependencyResolver/IncompatibleThreadSafetyMode.php        +Ƥ      5   src/DependencyResolver/BundledPhpExtensionRefusal.php        Ӥ      -   src/DependencyResolver/InvalidPackageName.php        !S      -   src/DependencyResolver/DependencyResolver.php(      (  L:z      5   src/DependencyResolver/UnableToResolveRequirement.phpX      X  ,      <   src/DependencyResolver/IncompatibleOperatingSystemFamily.phpO      O  'z      4   src/DependencyResolver/DetermineMinimumStability.php(      (  .z      8   src/DependencyResolver/ResolveDependencyWithComposer.phpN      N        "   src/DependencyResolver/Package.php        Z$      5   src/DependencyResolver/RequestedPackageAndVersion.php        &&"         src/ExtensionName.php        Ŋi         src/File/SudoFilePut.phpe      e  -x         src/File/WindowsDelete.php*      *  ht-      !   src/File/SudoNotFoundOnSystem.php        ?K5         src/File/FailedToWriteFile.phpi      i  L٤         src/File/FullPathToSelf.php        Ć      ,   src/File/SudoRequiresInteractiveTerminal.php        hA         src/File/BinaryFile.php                   src/File/Sudo.php        Hj*         src/File/SudoUnlink.phpo      o  N\¤         src/File/FailedToUnlinkFile.php        Y%         src/File/FailedToCreateFile.php        *.         src/File/SudoCreate.php        5cM      )   src/File/BinaryFileFailedVerification.php9      9  Q         src/ExtensionType.php        j>      %   src/Downloading/DownloadUrlMethod.phpy      y  ~:       6   src/Downloading/Exception/CouldNotFindReleaseAsset.php	      	  cu      (   src/Downloading/PackageReleaseAssets.php        ߤ      %   src/Downloading/DownloadedPackage.phpt      t  0)      .   src/Downloading/GithubPackageReleaseAssets.php,      ,           src/Platform.phpp      p        &   src/Platform/OperatingSystemFamily.php        +'9פ      !   src/Platform/ThreadSafetyMode.php               9   src/Platform/TargetPhp/Exception/ExtensionIsNotLoaded.php6      6  9b,      9   src/Platform/TargetPhp/Exception/InvalidPhpBinaryPath.php`      `        %   src/Platform/TargetPhp/PhpizePath.php7      7  )1      (   src/Platform/TargetPhp/PhpBinaryPath.php/      /  =          src/Platform/OperatingSystem.php        [mǤ      *   src/Platform/WindowsExtensionAssetName.php        C1V         src/Platform/Architecture.php        Tbus          src/Platform/WindowsCompiler.phpD      D  ߖ         src/Platform/TargetPlatform.phpZ      Z  T`      +   src/Platform/PrePackagedSourceAssetName.php        (ڦ      %   src/Platform/InstalledPiePackages.php        !Sa      @   src/ComposerIntegration/PhpBinaryPathBasedPlatformRepository.php        b0      ,   src/ComposerIntegration/QuieterConsoleIO.php	      	  mfd      2   src/ComposerIntegration/InstallAndBuildProcess.php
      
  PJĤ      .   src/ComposerIntegration/PieComposerRequest.php        *      )   src/ComposerIntegration/PieJsonEditor.phpK      K  !      8   src/ComposerIntegration/PieInstalledJsonMetadataKeys.php-
      -
  پ      )   src/ComposerIntegration/VendorCleanup.php	      	  P̱ɤ      -   src/ComposerIntegration/ComposerRunFailed.php        ೊ      .   src/ComposerIntegration/PieComposerFactory.php        %k      :   src/ComposerIntegration/BundledPhpExtensionsRepository.phpR%      R%  n      /   src/ComposerIntegration/PiePackageInstaller.php0      0  P	Q      ,   src/ComposerIntegration/UninstallProcess.php              (   src/ComposerIntegration/PieOperation.php        *̤      2   src/ComposerIntegration/VersionSelectorFactory.php              6   src/ComposerIntegration/ComposerIntegrationHandler.php        mw      ,   src/ComposerIntegration/MinimalHelperSet.php        	VV      1   src/ComposerIntegration/InstalledJsonMetadata.phpb      b  a      H   src/ComposerIntegration/Listeners/OverrideDownloadUrlInstallListener.php        $      F   src/ComposerIntegration/Listeners/RemoveUnrelatedInstallOperations.phpd      d  1      0   src/ComposerIntegration/PieComposerInstaller.php?	      ?	  $V48         src/Container.php1      1  1\S      3   src/SelfManage/Update/FetchPieReleaseFromGitHub.php        ס%      )   src/SelfManage/Update/FetchPieRelease.php        \[ݤ      )   src/SelfManage/Update/ReleaseMetadata.php        Ф      :   src/SelfManage/Verify/VerifyPieReleaseUsingAttestation.phpg      g  0      %   src/SelfManage/Verify/Attestation.phpa      a  #Aܤ      /   src/SelfManage/Verify/GithubCliNotAvailable.php        Cڤ      /   src/SelfManage/Verify/FailedToVerifyRelease.php        uKJ      '   src/SelfManage/Verify/VerifyPiePhar.php        ʺ<      :   src/SelfManage/Verify/GithubCliAttestationVerification.php	      	  4      :   src/SelfManage/Verify/FallbackVerificationUsingOpenSsl.php>9      >9  TW         src/ConfigureOption.php        69m         bin/pie        Cl         resources/trusted-root.jsonl`      `  Lg      +   resources/composer-json-php-ext-schema.json        Ge         .box/vendor/autoload.php        :Ǥ         .box/vendor/composer/LICENSE.      .         "   .box/vendor/composer/installed.php,      ,  P      #   .box/vendor/composer/semver/LICENSE        Bh      .   .box/vendor/composer/semver/src/Comparator.php        N0      1   .box/vendor/composer/semver/src/VersionParser.php:      :  |ƭ      4   .box/vendor/composer/semver/src/CompilingMatcher.php        ;3      *   .box/vendor/composer/semver/src/Semver.php        :ۤ      ,   .box/vendor/composer/semver/src/Interval.php        V      -   .box/vendor/composer/semver/src/Intervals.php{9      {9  H_      B   .box/vendor/composer/semver/src/Constraint/ConstraintInterface.php        D%      9   .box/vendor/composer/semver/src/Constraint/Constraint.php&      &  k      4   .box/vendor/composer/semver/src/Constraint/Bound.phpl      l  z       >   .box/vendor/composer/semver/src/Constraint/MultiConstraint.phpS      S  c      B   .box/vendor/composer/semver/src/Constraint/MatchNoneConstraint.phpY      Y  ac      A   .box/vendor/composer/semver/src/Constraint/MatchAllConstraint.php;      ;  :X      *   .box/vendor/composer/InstalledVersions.php        (ɤ      $   .box/vendor/composer/ClassLoader.php?      ?  2@u      &   .box/vendor/composer/autoload_psr4.php*      *        *   .box/vendor/composer/autoload_classmap.php
      
  !      (   .box/vendor/composer/autoload_static.phpu      u  $[      &   .box/vendor/composer/autoload_real.php          /[s      ,   .box/vendor/composer/autoload_namespaces.php          /t      )   .box/src/IsExtensionConflictFulfilled.php        [         .box/src/Terminal.php        ڂ         .box/src/Printer.php        *      !   .box/src/IsExtensionFulfilled.php        G%         .box/src/Requirement.php        눗         .box/src/Checker.php        xlѤ         .box/src/IO.php        5      "   .box/src/RequirementCollection.php        䖤      "   .box/src/IsPhpVersionFulfilled.php        DzCu         .box/src/IsFulfilled.php          67         .box/bin/check-requirements.php        r1L         .box/.requirements.php        ?      <?php

// autoload.php @generated by Composer

if (PHP_VERSION_ID < 50600) {
    if (!headers_sent()) {
        header('HTTP/1.1 500 Internal Server Error');
    }
    $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
    if (!ini_get('display_errors')) {
        if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
            fwrite(STDERR, $err);
        } elseif (!headers_sent()) {
            echo $err;
        }
    }
    throw new RuntimeException($err);
}

require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInitf542bd5a549fa468020c82327ba6fd88::getLoader();
MIT License

Copyright (c) 2016

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.
<?php

declare(strict_types=1);

namespace JsonSchema;

interface SchemaStorageInterface
{
    /**
     * Adds schema with given identifier
     *
     * @param object $schema
     */
    public function addSchema(string $id, $schema = null): void;

    /**
     * Returns schema for given identifier, or null if it does not exist
     *
     * @return object
     */
    public function getSchema(string $id);

    /**
     * Returns schema for given reference with all sub-references resolved
     *
     * @return object
     */
    public function resolveRef(string $ref);

    /**
     * Returns schema referenced by '$ref' property
     *
     * @param mixed $refSchema
     *
     * @return object
     */
    public function resolveRefSchema($refSchema);
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema;

/**
 * @package JsonSchema
 */
interface UriRetrieverInterface
{
    /**
     * Retrieve a URI
     *
     * @param string      $uri     JSON Schema URI
     * @param null|string $baseUri
     *
     * @return object JSON Schema contents
     */
    public function retrieve($uri, $baseUri = null);
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema;

use JsonSchema\Constraints\BaseConstraint;
use JsonSchema\Constraints\Constraint;
use JsonSchema\Constraints\TypeCheck\LooseTypeCheck;

/**
 * A JsonSchema Constraint
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 *
 * @see    README.md
 */
class Validator extends BaseConstraint
{
    public const SCHEMA_MEDIA_TYPE = 'application/schema+json';

    public const ERROR_NONE                    = 0;
    public const ERROR_ALL                     = -1;
    public const ERROR_DOCUMENT_VALIDATION     = 1;
    public const ERROR_SCHEMA_VALIDATION       = 2;

    /**
     * Validates the given data against the schema and returns an object containing the results
     * Both the php object and the schema are supposed to be a result of a json_decode call.
     * The validation works as defined by the schema proposal in http://json-schema.org.
     *
     * Note that the first argument is passed by reference, so you must pass in a variable.
     *
     * @param mixed $value
     * @param mixed $schema
     *
     * @phpstan-param int-mask-of<Constraint::CHECK_MODE_*> $checkMode
     * @phpstan-return int-mask-of<Validator::ERROR_*>
     */
    public function validate(&$value, $schema = null, ?int $checkMode = null): int
    {
        // reset errors prior to validation
        $this->reset();

        // set checkMode
        $initialCheckMode = $this->factory->getConfig();
        if ($checkMode !== null) {
            $this->factory->setConfig($checkMode);
        }

        // add provided schema to SchemaStorage with internal URI to allow internal $ref resolution
        $schemaURI = SchemaStorage::INTERNAL_PROVIDED_SCHEMA_URI;
        if (LooseTypeCheck::propertyExists($schema, 'id')) {
            $schemaURI = LooseTypeCheck::propertyGet($schema, 'id');
        }
        $this->factory->getSchemaStorage()->addSchema($schemaURI, $schema);

        $validator = $this->factory->createInstanceFor('schema');
        $validator->check(
            $value,
            $this->factory->getSchemaStorage()->getSchema($schemaURI)
        );

        $this->factory->setConfig($initialCheckMode);

        $this->addErrors(array_unique($validator->getErrors(), SORT_REGULAR));

        return $validator->getErrorMask();
    }

    /**
     * Alias to validate(), to maintain backwards-compatibility with the previous API
     *
     * @deprecated since 6.0.0, use Validator::validate() instead, to be removed in 7.0
     *
     * @param mixed $value
     * @param mixed $schema
     *
     * @phpstan-return int-mask-of<Validator::ERROR_*>
     */
    public function check($value, $schema): int
    {
        return $this->validate($value, $schema);
    }

    /**
     * Alias to validate(), to maintain backwards-compatibility with the previous API
     *
     * @deprecated since 6.0.0, use Validator::validate() instead, to be removed in 7.0
     *
     * @param mixed $value
     * @param mixed $schema
     *
     * @phpstan-return int-mask-of<Validator::ERROR_*>
     */
    public function coerce(&$value, $schema): int
    {
        return $this->validate($value, $schema, Constraint::CHECK_MODE_COERCE_TYPES);
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

/**
 * Wrapper for the InvalidSchemaMediaType
 */
class InvalidSchemaMediaTypeException extends RuntimeException
{
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

/**
 * Wrapper for the ResourceNotFoundException
 */
class InvalidConfigException extends RuntimeException
{
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

/**
 * Wrapper for the UriResolverException
 */
class UriResolverException extends RuntimeException
{
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

/**
 * @package JsonSchema\Exception
 *
 * @author Joost Nijhuis <jnijhuis81@gmail.com>
 */
class UnresolvableJsonPointerException extends InvalidArgumentException
{
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

/**
 * Wrapper for the JsonDecodingException
 */
class JsonDecodingException extends RuntimeException
{
    public function __construct($code = JSON_ERROR_NONE, ?\Exception $previous = null)
    {
        switch ($code) {
            case JSON_ERROR_DEPTH:
                $message = 'The maximum stack depth has been exceeded';
                break;
            case JSON_ERROR_STATE_MISMATCH:
                $message = 'Invalid or malformed JSON';
                break;
            case JSON_ERROR_CTRL_CHAR:
                $message = 'Control character error, possibly incorrectly encoded';
                break;
            case JSON_ERROR_UTF8:
                $message = 'Malformed UTF-8 characters, possibly incorrectly encoded';
                break;
            case JSON_ERROR_SYNTAX:
                $message = 'JSON syntax is malformed';
                break;
            default:
                $message = 'Syntax error';
        }
        parent::__construct($message, $code, $previous);
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

/**
 * Wrapper for the InvalidArgumentException
 */
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

/**
 * Wrapper for the RuntimeException
 */
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

/**
 * Wrapper for the InvalidSourceUriException
 */
class InvalidSourceUriException extends InvalidArgumentException
{
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

class ValidationException extends RuntimeException
{
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

/**
 * Wrapper for the ResourceNotFoundException
 */
class ResourceNotFoundException extends RuntimeException
{
}
<?php

declare(strict_types=1);

namespace JsonSchema\Exception;

interface ExceptionInterface
{
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Exception;

/**
 * Wrapper for the InvalidSchemaMediaType
 */
class InvalidSchemaException extends RuntimeException
{
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\ConstraintError;
use JsonSchema\Entity\JsonPointer;

/**
 * The NumberConstraint Constraints, validates an number against a given schema
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 */
class NumberConstraint extends Constraint
{
    /**
     * {@inheritdoc}
     */
    public function check(&$element, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        // Verify minimum
        if (isset($schema->exclusiveMinimum)) {
            if (isset($schema->minimum)) {
                if ($schema->exclusiveMinimum && $element <= $schema->minimum) {
                    $this->addError(ConstraintError::EXCLUSIVE_MINIMUM(), $path, ['minimum' => $schema->minimum]);
                } elseif ($element < $schema->minimum) {
                    $this->addError(ConstraintError::MINIMUM(), $path, ['minimum' => $schema->minimum]);
                }
            } else {
                $this->addError(ConstraintError::MISSING_MINIMUM(), $path);
            }
        } elseif (isset($schema->minimum) && $element < $schema->minimum) {
            $this->addError(ConstraintError::MINIMUM(), $path, ['minimum' => $schema->minimum]);
        }

        // Verify maximum
        if (isset($schema->exclusiveMaximum)) {
            if (isset($schema->maximum)) {
                if ($schema->exclusiveMaximum && $element >= $schema->maximum) {
                    $this->addError(ConstraintError::EXCLUSIVE_MAXIMUM(), $path, ['maximum' => $schema->maximum]);
                } elseif ($element > $schema->maximum) {
                    $this->addError(ConstraintError::MAXIMUM(), $path, ['maximum' => $schema->maximum]);
                }
            } else {
                $this->addError(ConstraintError::MISSING_MAXIMUM(), $path);
            }
        } elseif (isset($schema->maximum) && $element > $schema->maximum) {
            $this->addError(ConstraintError::MAXIMUM(), $path, ['maximum' => $schema->maximum]);
        }

        // Verify divisibleBy - Draft v3
        if (isset($schema->divisibleBy) && $this->fmod($element, $schema->divisibleBy) != 0) {
            $this->addError(ConstraintError::DIVISIBLE_BY(), $path, ['divisibleBy' => $schema->divisibleBy]);
        }

        // Verify multipleOf - Draft v4
        if (isset($schema->multipleOf) && $this->fmod($element, $schema->multipleOf) != 0) {
            $this->addError(ConstraintError::MULTIPLE_OF(), $path, ['multipleOf' => $schema->multipleOf]);
        }

        $this->checkFormat($element, $schema, $path, $i);
    }

    private function fmod($number1, $number2)
    {
        $modulus = ($number1 - round($number1 / $number2) * $number2);
        $precision = 0.0000000001;

        if (-$precision < $modulus && $modulus < $precision) {
            return 0.0;
        }

        return $modulus;
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\ConstraintError;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Tool\DeepComparer;

/**
 * The CollectionConstraint Constraints, validates an array against a given schema
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 */
class CollectionConstraint extends Constraint
{
    /**
     * {@inheritdoc}
     */
    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        // Verify minItems
        if (isset($schema->minItems) && count($value) < $schema->minItems) {
            $this->addError(ConstraintError::MIN_ITEMS(), $path, ['minItems' => $schema->minItems, 'found' => count($value)]);
        }

        // Verify maxItems
        if (isset($schema->maxItems) && count($value) > $schema->maxItems) {
            $this->addError(ConstraintError::MAX_ITEMS(), $path, ['maxItems' => $schema->maxItems, 'found' => count($value)]);
        }

        // Verify uniqueItems
        if (isset($schema->uniqueItems) && $schema->uniqueItems) {
            $count = count($value);
            for ($x = 0; $x < $count - 1; $x++) {
                for ($y = $x + 1; $y < $count; $y++) {
                    if (DeepComparer::isEqual($value[$x], $value[$y])) {
                        $this->addError(ConstraintError::UNIQUE_ITEMS(), $path);
                        break 2;
                    }
                }
            }
        }

        $this->validateItems($value, $schema, $path, $i);
    }

    /**
     * Validates the items
     *
     * @param array     $value
     * @param \stdClass $schema
     * @param string    $i
     */
    protected function validateItems(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (\is_null($schema) || !isset($schema->items)) {
            return;
        }

        if ($schema->items === true) {
            return;
        }

        if (is_object($schema->items)) {
            // just one type definition for the whole array
            foreach ($value as $k => &$v) {
                $initErrors = $this->getErrors();

                // First check if its defined in "items"
                $this->checkUndefined($v, $schema->items, $path, $k);

                // Recheck with "additionalItems" if the first test fails
                if (count($initErrors) < count($this->getErrors()) && (isset($schema->additionalItems) && $schema->additionalItems !== false)) {
                    $secondErrors = $this->getErrors();
                    $this->checkUndefined($v, $schema->additionalItems, $path, $k);
                }

                // Reset errors if needed
                if (isset($secondErrors) && count($secondErrors) < count($this->getErrors())) {
                    $this->errors = $secondErrors;
                } elseif (isset($secondErrors) && count($secondErrors) === count($this->getErrors())) {
                    $this->errors = $initErrors;
                }
            }
            unset($v); /* remove dangling reference to prevent any future bugs
                        * caused by accidentally using $v elsewhere */
        } else {
            // Defined item type definitions
            foreach ($value as $k => &$v) {
                if (array_key_exists($k, $schema->items)) {
                    $this->checkUndefined($v, $schema->items[$k], $path, $k);
                } else {
                    // Additional items
                    if (property_exists($schema, 'additionalItems')) {
                        if ($schema->additionalItems !== false) {
                            $this->checkUndefined($v, $schema->additionalItems, $path, $k);
                        } else {
                            $this->addError(
                                ConstraintError::ADDITIONAL_ITEMS(),
                                $path,
                                [
                                    'item' => $i,
                                    'property' => $k,
                                    'additionalItems' => $schema->additionalItems
                                ]
                            );
                        }
                    } else {
                        // Should be valid against an empty schema
                        $this->checkUndefined($v, new \stdClass(), $path, $k);
                    }
                }
            }
            unset($v); /* remove dangling reference to prevent any future bugs
                        * caused by accidentally using $v elsewhere */

            // Treat when we have more schema definitions than values, not for empty arrays
            if (count($value) > 0) {
                for ($k = count($value); $k < count($schema->items); $k++) {
                    $undefinedInstance = $this->factory->createInstanceFor('undefined');
                    $this->checkUndefined($undefinedInstance, $schema->items[$k], $path, $k);
                }
            }
        }
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\ConstraintError;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Tool\DeepComparer;

/**
 * The ConstConstraint Constraints, validates an element against a constant value
 *
 * @author Martin Helmich <martin@helmich.me>
 */
class ConstConstraint extends Constraint
{
    /**
     * {@inheritdoc}
     */
    public function check(&$element, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        // Only validate const if the attribute exists
        if ($element instanceof UndefinedConstraint && (!isset($schema->required) || !$schema->required)) {
            return;
        }
        $const = $schema->const;

        $type = gettype($element);
        $constType = gettype($const);

        if ($this->factory->getConfig(self::CHECK_MODE_TYPE_CAST) && $type === 'array' && $constType === 'object') {
            if (DeepComparer::isEqual((object) $element, $const)) {
                return;
            }
        }

        if (DeepComparer::isEqual($element, $const)) {
            return;
        }

        $this->addError(ConstraintError::CONSTANT(), $path, ['const' => $schema->const]);
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\ConstraintError;
use JsonSchema\Entity\JsonPointer;

/**
 * The StringConstraint Constraints, validates an string against a given schema
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 */
class StringConstraint extends Constraint
{
    /**
     * {@inheritdoc}
     */
    public function check(&$element, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        // Verify maxLength
        if (isset($schema->maxLength) && $this->strlen($element) > $schema->maxLength) {
            $this->addError(ConstraintError::LENGTH_MAX(), $path, [
                'maxLength' => $schema->maxLength,
            ]);
        }

        //verify minLength
        if (isset($schema->minLength) && $this->strlen($element) < $schema->minLength) {
            $this->addError(ConstraintError::LENGTH_MIN(), $path, [
                'minLength' => $schema->minLength,
            ]);
        }

        // Verify a regex pattern
        if (isset($schema->pattern) && !preg_match(self::jsonPatternToPhpRegex($schema->pattern), $element)) {
            $this->addError(ConstraintError::PATTERN(), $path, [
                'pattern' => $schema->pattern,
            ]);
        }

        $this->checkFormat($element, $schema, $path, $i);
    }

    private function strlen($string)
    {
        if (extension_loaded('mbstring')) {
            return mb_strlen($string, mb_detect_encoding($string));
        }

        // mbstring is present on all test platforms, so strlen() can be ignored for coverage
        return strlen($string); // @codeCoverageIgnore
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints;

use JsonSchema\ConstraintError;
use JsonSchema\Constraints\TypeCheck\LooseTypeCheck;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Exception\ValidationException;
use JsonSchema\Tool\DeepCopy;
use JsonSchema\Uri\UriResolver;

#[\AllowDynamicProperties]
class UndefinedConstraint extends Constraint
{
    /**
     * @var list<string> List of properties to which a default value has been applied
     */
    protected $appliedDefaults = [];

    /**
     * {@inheritdoc}
     * */
    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null, bool $fromDefault = false): void
    {
        if (is_null($schema) || !is_object($schema)) {
            return;
        }

        $path = $this->incrementPath($path, $i);
        if ($fromDefault) {
            $path->setFromDefault();
        }

        // check special properties
        $this->validateCommonProperties($value, $schema, $path, $i);

        // check allOf, anyOf, and oneOf properties
        $this->validateOfProperties($value, $schema, $path, '');

        // check known types
        $this->validateTypes($value, $schema, $path, $i);
    }

    /**
     * Validates the value against the types
     *
     * @param mixed       $value
     * @param mixed       $schema
     * @param JsonPointer $path
     * @param string      $i
     */
    public function validateTypes(&$value, $schema, JsonPointer $path, $i = null)
    {
        // check array
        if ($this->getTypeCheck()->isArray($value)) {
            $this->checkArray($value, $schema, $path, $i);
        }

        // check object
        if (LooseTypeCheck::isObject($value)) {
            // object processing should always be run on assoc arrays,
            // so use LooseTypeCheck here even if CHECK_MODE_TYPE_CAST
            // is not set (i.e. don't use $this->getTypeCheck() here).
            $this->checkObject(
                $value,
                $schema,
                $path,
                isset($schema->properties) ? $schema->properties : null,
                isset($schema->additionalProperties) ? $schema->additionalProperties : null,
                isset($schema->patternProperties) ? $schema->patternProperties : null,
                $this->appliedDefaults
            );
        }

        // check string
        if (is_string($value)) {
            $this->checkString($value, $schema, $path, $i);
        }

        // check numeric
        if (is_numeric($value)) {
            $this->checkNumber($value, $schema, $path, $i);
        }

        // check enum
        if (isset($schema->enum)) {
            $this->checkEnum($value, $schema, $path, $i);
        }

        // check const
        if (isset($schema->const)) {
            $this->checkConst($value, $schema, $path, $i);
        }
    }

    /**
     * Validates common properties
     *
     * @param mixed       $value
     * @param mixed       $schema
     * @param JsonPointer $path
     * @param string      $i
     */
    protected function validateCommonProperties(&$value, $schema, JsonPointer $path, $i = '')
    {
        // if it extends another schema, it must pass that schema as well
        if (isset($schema->extends)) {
            if (is_string($schema->extends)) {
                $schema->extends = $this->validateUri($schema, $schema->extends);
            }
            if (is_array($schema->extends)) {
                foreach ($schema->extends as $extends) {
                    $this->checkUndefined($value, $extends, $path, $i);
                }
            } else {
                $this->checkUndefined($value, $schema->extends, $path, $i);
            }
        }

        // Apply default values from schema
        if (!$path->fromDefault()) {
            $this->applyDefaultValues($value, $schema, $path);
        }

        // Verify required values
        if ($this->getTypeCheck()->isObject($value)) {
            if (!($value instanceof self) && isset($schema->required) && is_array($schema->required)) {
                // Draft 4 - Required is an array of strings - e.g. "required": ["foo", ...]
                foreach ($schema->required as $required) {
                    if (!$this->getTypeCheck()->propertyExists($value, $required)) {
                        $this->addError(
                            ConstraintError::REQUIRED(),
                            $this->incrementPath($path, $required), [
                                'property' => $required
                            ]
                        );
                    }
                }
            } elseif (isset($schema->required) && !is_array($schema->required)) {
                // Draft 3 - Required attribute - e.g. "foo": {"type": "string", "required": true}
                if ($schema->required && $value instanceof self) {
                    $propertyPaths = $path->getPropertyPaths();
                    $propertyName = end($propertyPaths);
                    $this->addError(ConstraintError::REQUIRED(), $path, ['property' => $propertyName]);
                }
            } else {
                // if the value is both undefined and not required, skip remaining checks
                // in this method which assume an actual, defined instance when validating.
                if ($value instanceof self) {
                    return;
                }
            }
        }

        // Verify type
        if (!($value instanceof self)) {
            $this->checkType($value, $schema, $path, $i);
        }

        // Verify disallowed items
        if (isset($schema->disallow)) {
            $initErrors = $this->getErrors();

            $typeSchema = new \stdClass();
            $typeSchema->type = $schema->disallow;
            $this->checkType($value, $typeSchema, $path);

            // if no new errors were raised it must be a disallowed value
            if (count($this->getErrors()) == count($initErrors)) {
                $this->addError(ConstraintError::DISALLOW(), $path);
            } else {
                $this->errors = $initErrors;
            }
        }

        if (isset($schema->not)) {
            $initErrors = $this->getErrors();
            $this->checkUndefined($value, $schema->not, $path, $i);

            // if no new errors were raised then the instance validated against the "not" schema
            if (count($this->getErrors()) == count($initErrors)) {
                $this->addError(ConstraintError::NOT(), $path);
            } else {
                $this->errors = $initErrors;
            }
        }

        // Verify that dependencies are met
        if (isset($schema->dependencies) && $this->getTypeCheck()->isObject($value)) {
            $this->validateDependencies($value, $schema->dependencies, $path);
        }
    }

    /**
     * Check whether a default should be applied for this value
     *
     * @param mixed $schema
     * @param mixed $parentSchema
     * @param bool  $requiredOnly
     *
     * @return bool
     */
    private function shouldApplyDefaultValue($requiredOnly, $schema, $name = null, $parentSchema = null)
    {
        // required-only mode is off
        if (!$requiredOnly) {
            return true;
        }
        // draft-04 required is set
        if (
            $name !== null
            && isset($parentSchema->required)
            && is_array($parentSchema->required)
            && in_array($name, $parentSchema->required)
        ) {
            return true;
        }
        // draft-03 required is set
        if (isset($schema->required) && !is_array($schema->required) && $schema->required) {
            return true;
        }
        // default case
        return false;
    }

    /**
     * Apply default values
     *
     * @param mixed       $value
     * @param mixed       $schema
     * @param JsonPointer $path
     */
    protected function applyDefaultValues(&$value, $schema, $path): void
    {
        // only apply defaults if feature is enabled
        if (!$this->factory->getConfig(self::CHECK_MODE_APPLY_DEFAULTS)) {
            return;
        }

        // apply defaults if appropriate
        $requiredOnly = (bool) $this->factory->getConfig(self::CHECK_MODE_ONLY_REQUIRED_DEFAULTS);
        if (isset($schema->properties) && LooseTypeCheck::isObject($value)) {
            // $value is an object or assoc array, and properties are defined - treat as an object
            foreach ($schema->properties as $currentProperty => $propertyDefinition) {
                $propertyDefinition = $this->factory->getSchemaStorage()->resolveRefSchema($propertyDefinition);
                if (
                    !LooseTypeCheck::propertyExists($value, $currentProperty)
                    && property_exists($propertyDefinition, 'default')
                    && $this->shouldApplyDefaultValue($requiredOnly, $propertyDefinition, $currentProperty, $schema)
                ) {
                    // assign default value
                    if (is_object($propertyDefinition->default)) {
                        LooseTypeCheck::propertySet($value, $currentProperty, clone $propertyDefinition->default);
                    } else {
                        LooseTypeCheck::propertySet($value, $currentProperty, $propertyDefinition->default);
                    }
                    $this->appliedDefaults[] = $currentProperty;
                }
            }
        } elseif (isset($schema->items) && LooseTypeCheck::isArray($value)) {
            $items = [];
            if (LooseTypeCheck::isArray($schema->items)) {
                $items = $schema->items;
            } elseif (isset($schema->minItems) && count($value) < $schema->minItems) {
                $items = array_fill(count($value), $schema->minItems - count($value), $schema->items);
            }
            // $value is an array, and items are defined - treat as plain array
            foreach ($items as $currentItem => $itemDefinition) {
                $itemDefinition = $this->factory->getSchemaStorage()->resolveRefSchema($itemDefinition);
                if (
                    !array_key_exists($currentItem, $value)
                    && property_exists($itemDefinition, 'default')
                    && $this->shouldApplyDefaultValue($requiredOnly, $itemDefinition)) {
                    if (is_object($itemDefinition->default)) {
                        $value[$currentItem] = clone $itemDefinition->default;
                    } else {
                        $value[$currentItem] = $itemDefinition->default;
                    }
                }
                $path->setFromDefault();
            }
        } elseif (
            $value instanceof self
            && property_exists($schema, 'default')
            && $this->shouldApplyDefaultValue($requiredOnly, $schema)) {
            // $value is a leaf, not a container - apply the default directly
            $value = is_object($schema->default) ? clone $schema->default : $schema->default;
            $path->setFromDefault();
        }
    }

    /**
     * Validate allOf, anyOf, and oneOf properties
     *
     * @param mixed       $value
     * @param mixed       $schema
     * @param JsonPointer $path
     * @param string      $i
     */
    protected function validateOfProperties(&$value, $schema, JsonPointer $path, $i = '')
    {
        // Verify type
        if ($value instanceof self) {
            return;
        }

        if (isset($schema->allOf)) {
            $isValid = true;
            foreach ($schema->allOf as $allOf) {
                $initErrors = $this->getErrors();
                $this->checkUndefined($value, $allOf, $path, $i);
                $isValid = $isValid && (count($this->getErrors()) == count($initErrors));
            }
            if (!$isValid) {
                $this->addError(ConstraintError::ALL_OF(), $path);
            }
        }

        if (isset($schema->anyOf)) {
            $isValid = false;
            $startErrors = $this->getErrors();
            $coerceOrDefaults = $this->factory->getConfig(self::CHECK_MODE_COERCE_TYPES | self::CHECK_MODE_APPLY_DEFAULTS);

            foreach ($schema->anyOf as $anyOf) {
                $initErrors = $this->getErrors();
                try {
                    $anyOfValue = $coerceOrDefaults ? DeepCopy::copyOf($value) : $value;
                    $this->checkUndefined($anyOfValue, $anyOf, $path, $i);
                    if ($isValid = (count($this->getErrors()) === count($initErrors))) {
                        $value = $anyOfValue;
                        break;
                    }
                } catch (ValidationException $e) {
                    $isValid = false;
                }
            }
            if (!$isValid) {
                $this->addError(ConstraintError::ANY_OF(), $path);
            } else {
                $this->errors = $startErrors;
            }
        }

        if (isset($schema->oneOf)) {
            $allErrors = [];
            $matchedSchemas = [];
            $startErrors = $this->getErrors();
            $coerceOrDefaults = $this->factory->getConfig(self::CHECK_MODE_COERCE_TYPES | self::CHECK_MODE_APPLY_DEFAULTS);

            foreach ($schema->oneOf as $oneOf) {
                try {
                    $this->errors = [];

                    $oneOfValue = $coerceOrDefaults ? DeepCopy::copyOf($value) : $value;
                    $this->checkUndefined($oneOfValue, $oneOf, $path, $i);
                    if (count($this->getErrors()) === 0) {
                        $matchedSchemas[] = ['schema' => $oneOf, 'value' => $oneOfValue];
                    }
                    $allErrors = array_merge($allErrors, array_values($this->getErrors()));
                } catch (ValidationException $e) {
                    // deliberately do nothing here - validation failed, but we want to check
                    // other schema options in the OneOf field.
                }
            }
            if (count($matchedSchemas) !== 1) {
                $this->addErrors(array_merge($allErrors, $startErrors));
                $this->addError(ConstraintError::ONE_OF(), $path);
            } else {
                $this->errors = $startErrors;
                $value = $matchedSchemas[0]['value'];
            }
        }
    }

    /**
     * Validate dependencies
     *
     * @param mixed       $value
     * @param mixed       $dependencies
     * @param JsonPointer $path
     * @param string      $i
     */
    protected function validateDependencies($value, $dependencies, JsonPointer $path, $i = '')
    {
        foreach ($dependencies as $key => $dependency) {
            if ($this->getTypeCheck()->propertyExists($value, $key)) {
                if (is_string($dependency)) {
                    // Draft 3 string is allowed - e.g. "dependencies": {"bar": "foo"}
                    if (!$this->getTypeCheck()->propertyExists($value, $dependency)) {
                        $this->addError(ConstraintError::DEPENDENCIES(), $path, [
                            'key' => $key,
                            'dependency' => $dependency
                        ]);
                    }
                } elseif (is_array($dependency)) {
                    // Draft 4 must be an array - e.g. "dependencies": {"bar": ["foo"]}
                    foreach ($dependency as $d) {
                        if (!$this->getTypeCheck()->propertyExists($value, $d)) {
                            $this->addError(ConstraintError::DEPENDENCIES(), $path, [
                                'key' => $key,
                                'dependency' => $dependency
                            ]);
                        }
                    }
                } elseif (is_object($dependency)) {
                    // Schema - e.g. "dependencies": {"bar": {"properties": {"foo": {...}}}}
                    $this->checkUndefined($value, $dependency, $path, $i);
                }
            }
        }
    }

    protected function validateUri($schema, $schemaUri = null)
    {
        $resolver = new UriResolver();
        $retriever = $this->factory->getUriRetriever();

        $jsonSchema = null;
        if ($resolver->isValid($schemaUri)) {
            $schemaId = property_exists($schema, 'id') ? $schema->id : null;
            $jsonSchema = $retriever->retrieve($schemaId, $schemaUri);
        }

        return $jsonSchema;
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\ConstraintError;
use JsonSchema\Entity\JsonPointer;

/**
 * The Constraints Interface
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 */
interface ConstraintInterface
{
    /**
     * returns all collected errors
     */
    public function getErrors(): array;

    /**
     * adds errors to this validator
     */
    public function addErrors(array $errors): void;

    /**
     * adds an error
     *
     * @param ConstraintError $constraint the constraint/rule that is broken, e.g.: ConstraintErrors::LENGTH_MIN()
     * @param array           $more       more array elements to add to the error
     */
    public function addError(ConstraintError $constraint, ?JsonPointer $path = null, array $more = []): void;

    /**
     * checks if the validator has not raised errors
     */
    public function isValid(): bool;

    /**
     * invokes the validation of an element
     *
     * @abstract
     *
     * @param mixed $value
     * @param mixed $schema
     * @param mixed $i
     *
     * @throws \JsonSchema\Exception\ExceptionInterface
     */
    public function check(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void;
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints;

use JsonSchema\Entity\JsonPointer;

abstract class Constraint extends BaseConstraint implements ConstraintInterface
{
    /** @var string */
    protected $inlineSchemaProperty = '$schema';

    public const CHECK_MODE_NONE =             0x00000000;
    public const CHECK_MODE_NORMAL =           0x00000001;
    public const CHECK_MODE_TYPE_CAST =        0x00000002;
    public const CHECK_MODE_COERCE_TYPES =     0x00000004;
    public const CHECK_MODE_APPLY_DEFAULTS =   0x00000008;
    public const CHECK_MODE_EXCEPTIONS =       0x00000010;
    public const CHECK_MODE_DISABLE_FORMAT =   0x00000020;
    public const CHECK_MODE_EARLY_COERCE =     0x00000040;
    public const CHECK_MODE_ONLY_REQUIRED_DEFAULTS   = 0x00000080;
    public const CHECK_MODE_VALIDATE_SCHEMA =  0x00000100;

    /**
     * Bubble down the path
     *
     * @param JsonPointer|null $path Current path
     * @param mixed            $i    What to append to the path
     */
    protected function incrementPath(?JsonPointer $path, $i): JsonPointer
    {
        $path = $path ?? new JsonPointer('');

        if ($i === null || $i === '') {
            return $path;
        }

        return $path->withPropertyPaths(
            array_merge(
                $path->getPropertyPaths(),
                [$i]
            )
        );
    }

    /**
     * Validates an array
     *
     * @param mixed $value
     * @param mixed $schema
     * @param mixed $i
     */
    protected function checkArray(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        $validator = $this->factory->createInstanceFor('collection');
        $validator->check($value, $schema, $path, $i);

        $this->addErrors($validator->getErrors());
    }

    /**
     * Validates an object
     *
     * @param mixed         $value
     * @param mixed         $schema
     * @param mixed         $properties
     * @param mixed         $additionalProperties
     * @param mixed         $patternProperties
     * @param array<string> $appliedDefaults
     */
    protected function checkObject(
        &$value,
        $schema = null,
        ?JsonPointer $path = null,
        $properties = null,
        $additionalProperties = null,
        $patternProperties = null,
        array $appliedDefaults = []
    ): void {
        /** @var ObjectConstraint $validator */
        $validator = $this->factory->createInstanceFor('object');
        $validator->check($value, $schema, $path, $properties, $additionalProperties, $patternProperties, $appliedDefaults);

        $this->addErrors($validator->getErrors());
    }

    /**
     * Validates the type of the value
     *
     * @param mixed $value
     * @param mixed $schema
     * @param mixed $i
     */
    protected function checkType(&$value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        $validator = $this->factory->createInstanceFor('type');
        $validator->check($value, $schema, $path, $i);

        $this->addErrors($validator->getErrors());
    }

    /**
     * Checks a undefined element
     *
     * @param mixed $value
     * @param mixed $schema
     * @param mixed $i
     */
    protected function checkUndefined(&$value, $schema = null, ?JsonPointer $path = null, $i = null, bool $fromDefault = false): void
    {
        /** @var UndefinedConstraint $validator */
        $validator = $this->factory->createInstanceFor('undefined');

        $validator->check($value, $this->factory->getSchemaStorage()->resolveRefSchema($schema), $path, $i, $fromDefault);

        $this->addErrors($validator->getErrors());
    }

    /**
     * Checks a string element
     *
     * @param mixed $value
     * @param mixed $schema
     * @param mixed $i
     */
    protected function checkString($value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        $validator = $this->factory->createInstanceFor('string');
        $validator->check($value, $schema, $path, $i);

        $this->addErrors($validator->getErrors());
    }

    /**
     * Checks a number element
     *
     * @param mixed $value
     * @param mixed $schema
     * @param mixed $i
     */
    protected function checkNumber($value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        $validator = $this->factory->createInstanceFor('number');
        $validator->check($value, $schema, $path, $i);

        $this->addErrors($validator->getErrors());
    }

    /**
     * Checks a enum element
     *
     * @param mixed $value
     * @param mixed $schema
     * @param mixed $i
     */
    protected function checkEnum($value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        $validator = $this->factory->createInstanceFor('enum');
        $validator->check($value, $schema, $path, $i);

        $this->addErrors($validator->getErrors());
    }

    /**
     * Checks a const element
     *
     * @param mixed $value
     * @param mixed $schema
     * @param mixed $i
     */
    protected function checkConst($value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        $validator = $this->factory->createInstanceFor('const');
        $validator->check($value, $schema, $path, $i);

        $this->addErrors($validator->getErrors());
    }

    /**
     * Checks format of an element
     *
     * @param mixed $value
     * @param mixed $schema
     * @param mixed $i
     */
    protected function checkFormat($value, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        $validator = $this->factory->createInstanceFor('format');
        $validator->check($value, $schema, $path, $i);

        $this->addErrors($validator->getErrors());
    }

    /**
     * Get the type check based on the set check mode.
     */
    protected function getTypeCheck(): TypeCheck\TypeCheckInterface
    {
        return $this->factory->getTypeCheck();
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\ConstraintError;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Exception\InvalidArgumentException;
use JsonSchema\Exception\InvalidSchemaException;
use JsonSchema\Exception\RuntimeException;
use JsonSchema\Validator;

/**
 * The SchemaConstraint Constraints, validates an element against a given schema
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 */
class SchemaConstraint extends Constraint
{
    private const DEFAULT_SCHEMA_SPEC = 'http://json-schema.org/draft-04/schema#';

    /**
     * {@inheritdoc}
     */
    public function check(&$element, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if ($schema !== null) {
            // passed schema
            $validationSchema = $schema;
        } elseif ($this->getTypeCheck()->propertyExists($element, $this->inlineSchemaProperty)) {
            // inline schema
            $validationSchema = $this->getTypeCheck()->propertyGet($element, $this->inlineSchemaProperty);
        } else {
            throw new InvalidArgumentException('no schema found to verify against');
        }

        // cast array schemas to object
        if (is_array($validationSchema)) {
            $validationSchema = BaseConstraint::arrayToObjectRecursive($validationSchema);
        }

        // validate schema against whatever is defined in $validationSchema->$schema. If no
        // schema is defined, assume self::DEFAULT_SCHEMA_SPEC (currently draft-04).
        if ($this->factory->getConfig(self::CHECK_MODE_VALIDATE_SCHEMA)) {
            if (!$this->getTypeCheck()->isObject($validationSchema)) {
                throw new RuntimeException('Cannot validate the schema of a non-object');
            }
            if ($this->getTypeCheck()->propertyExists($validationSchema, '$schema')) {
                $schemaSpec = $this->getTypeCheck()->propertyGet($validationSchema, '$schema');
            } else {
                $schemaSpec = self::DEFAULT_SCHEMA_SPEC;
            }

            // get the spec schema
            $schemaStorage = $this->factory->getSchemaStorage();
            if (!$this->getTypeCheck()->isObject($schemaSpec)) {
                $schemaSpec = $schemaStorage->getSchema($schemaSpec);
            }

            // save error count, config & subtract CHECK_MODE_VALIDATE_SCHEMA
            $initialErrorCount = $this->numErrors();
            $initialConfig = $this->factory->getConfig();
            $initialContext = $this->factory->getErrorContext();
            $this->factory->removeConfig(self::CHECK_MODE_VALIDATE_SCHEMA | self::CHECK_MODE_APPLY_DEFAULTS);
            $this->factory->addConfig(self::CHECK_MODE_TYPE_CAST);
            $this->factory->setErrorContext(Validator::ERROR_SCHEMA_VALIDATION);

            // validate schema
            try {
                $this->check($validationSchema, $schemaSpec);
            } catch (\Exception $e) {
                if ($this->factory->getConfig(self::CHECK_MODE_EXCEPTIONS)) {
                    throw new InvalidSchemaException('Schema did not pass validation', 0, $e);
                }
            }
            if ($this->numErrors() > $initialErrorCount) {
                $this->addError(ConstraintError::INVALID_SCHEMA(), $path);
            }

            // restore the initial config
            $this->factory->setConfig($initialConfig);
            $this->factory->setErrorContext($initialContext);
        }

        // validate element against $validationSchema
        $this->checkUndefined($element, $validationSchema, $path, $i);
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\ConstraintError;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Tool\DeepComparer;

/**
 * The EnumConstraint Constraints, validates an element against a given set of possibilities
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 */
class EnumConstraint extends Constraint
{
    /**
     * {@inheritdoc}
     */
    public function check(&$element, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        // Only validate enum if the attribute exists
        if ($element instanceof UndefinedConstraint && (!isset($schema->required) || !$schema->required)) {
            return;
        }
        $type = gettype($element);

        foreach ($schema->enum as $enum) {
            $enumType = gettype($enum);

            if ($enumType === 'object'
                && $type === 'array'
                && $this->factory->getConfig(self::CHECK_MODE_TYPE_CAST)
                && DeepComparer::isEqual((object) $element, $enum)
            ) {
                return;
            }

            if (($type === $enumType) && DeepComparer::isEqual($element, $enum)) {
                return;
            }

            if (is_numeric($element) && is_numeric($enum) && DeepComparer::isEqual((float) $element, (float) $enum)) {
                return;
            }
        }

        $this->addError(ConstraintError::ENUM(), $path, ['enum' => $schema->enum]);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\TypeCheck;

interface TypeCheckInterface
{
    public static function isObject($value);

    public static function isArray($value);

    public static function propertyGet($value, $property);

    public static function propertySet(&$value, $property, $data);

    public static function propertyExists($value, $property);

    public static function propertyCount($value);
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\TypeCheck;

class LooseTypeCheck implements TypeCheckInterface
{
    public static function isObject($value)
    {
        return
            is_object($value) ||
            (is_array($value) && (count($value) == 0 || self::isAssociativeArray($value)));
    }

    public static function isArray($value)
    {
        return
            is_array($value) &&
            (count($value) == 0 || !self::isAssociativeArray($value));
    }

    public static function propertyGet($value, $property)
    {
        if (is_object($value)) {
            return $value->{$property};
        }

        return $value[$property];
    }

    public static function propertySet(&$value, $property, $data)
    {
        if (is_object($value)) {
            $value->{$property} = $data;
        } else {
            $value[$property] = $data;
        }
    }

    public static function propertyExists($value, $property)
    {
        if (is_object($value)) {
            return property_exists($value, $property);
        }

        return is_array($value) && array_key_exists($property, $value);
    }

    public static function propertyCount($value)
    {
        if (is_object($value)) {
            return count(get_object_vars($value));
        }

        return count($value);
    }

    /**
     * Check if the provided array is associative or not
     *
     * @param array $arr
     *
     * @return bool
     */
    private static function isAssociativeArray($arr)
    {
        return array_keys($arr) !== range(0, count($arr) - 1);
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints\TypeCheck;

class StrictTypeCheck implements TypeCheckInterface
{
    public static function isObject($value)
    {
        return is_object($value);
    }

    public static function isArray($value)
    {
        return is_array($value);
    }

    public static function propertyGet($value, $property)
    {
        return $value->{$property};
    }

    public static function propertySet(&$value, $property, $data)
    {
        $value->{$property} = $data;
    }

    public static function propertyExists($value, $property)
    {
        return property_exists($value, $property);
    }

    public static function propertyCount($value)
    {
        if (!is_object($value)) {
            return 0;
        }

        return count(get_object_vars($value));
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\ConstraintError;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Exception\InvalidArgumentException;
use UnexpectedValueException as StandardUnexpectedValueException;

/**
 * The TypeConstraint Constraints, validates an element against a given type
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 */
class TypeConstraint extends Constraint
{
    /**
     * @var array|string[] type wordings for validation error messages
     */
    public static $wording = [
        'integer' => 'an integer',
        'number'  => 'a number',
        'boolean' => 'a boolean',
        'object'  => 'an object',
        'array'   => 'an array',
        'string'  => 'a string',
        'null'    => 'a null',
        'any'     => null, // validation of 'any' is always true so is not needed in message wording
        0         => null, // validation of a false-y value is always true, so not needed as well
    ];

    /**
     * {@inheritdoc}
     */
    public function check(&$value = null, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        $type = isset($schema->type) ? $schema->type : null;
        $isValid = false;
        $coerce = $this->factory->getConfig(self::CHECK_MODE_COERCE_TYPES);
        $earlyCoerce = $this->factory->getConfig(self::CHECK_MODE_EARLY_COERCE);
        $wording = [];

        if (is_array($type)) {
            $this->validateTypesArray($value, $type, $wording, $isValid, $path, $coerce && $earlyCoerce);
            if (!$isValid && $coerce && !$earlyCoerce) {
                $this->validateTypesArray($value, $type, $wording, $isValid, $path, true);
            }
        } elseif (is_object($type)) {
            $this->checkUndefined($value, $type, $path);

            return;
        } else {
            $isValid = $this->validateType($value, $type, $coerce && $earlyCoerce);
            if (!$isValid && $coerce && !$earlyCoerce) {
                $isValid = $this->validateType($value, $type, true);
            }
        }

        if ($isValid === false) {
            if (!is_array($type)) {
                $this->validateTypeNameWording($type);
                $wording[] = self::$wording[$type];
            }
            $this->addError(ConstraintError::TYPE(), $path, [
                    'found' => gettype($value),
                    'expected' => $this->implodeWith($wording, ', ', 'or')
            ]);
        }
    }

    /**
     * Validates the given $value against the array of types in $type. Sets the value
     * of $isValid to true, if at least one $type mateches the type of $value or the value
     * passed as $isValid is already true.
     *
     * @param mixed        $value             Value to validate
     * @param array        $type              TypeConstraints to check against
     * @param array        $validTypesWording An array of wordings of the valid types of the array $type
     * @param bool         $isValid           The current validation value
     * @param ?JsonPointer $path
     * @param bool         $coerce
     */
    protected function validateTypesArray(&$value, array $type, &$validTypesWording, &$isValid, $path, $coerce = false)
    {
        foreach ($type as $tp) {
            // already valid, so no need to waste cycles looping over everything
            if ($isValid) {
                return;
            }

            // $tp can be an object, if it's a schema instead of a simple type, validate it
            // with a new type constraint
            if (is_object($tp)) {
                if (!$isValid) {
                    $validator = $this->factory->createInstanceFor('type');
                    $subSchema = new \stdClass();
                    $subSchema->type = $tp;
                    $validator->check($value, $subSchema, $path, null);
                    $error = $validator->getErrors();
                    $isValid = !(bool) $error;
                    $validTypesWording[] = self::$wording['object'];
                }
            } else {
                $this->validateTypeNameWording($tp);
                $validTypesWording[] = self::$wording[$tp];
                if (!$isValid) {
                    $isValid = $this->validateType($value, $tp, $coerce);
                }
            }
        }
    }

    /**
     * Implodes the given array like implode() with turned around parameters and with the
     * difference, that, if $listEnd isn't false, the last element delimiter is $listEnd instead of
     * $delimiter.
     *
     * @param array  $elements  The elements to implode
     * @param string $delimiter The delimiter to use
     * @param bool   $listEnd   The last delimiter to use (defaults to $delimiter)
     *
     * @return string
     */
    protected function implodeWith(array $elements, $delimiter = ', ', $listEnd = false)
    {
        if ($listEnd === false || !isset($elements[1])) {
            return implode($delimiter, $elements);
        }
        $lastElement  = array_slice($elements, -1);
        $firsElements = join($delimiter, array_slice($elements, 0, -1));
        $implodedElements = array_merge([$firsElements], $lastElement);

        return join(" $listEnd ", $implodedElements);
    }

    /**
     * Validates the given $type, if there's an associated self::$wording. If not, throws an
     * exception.
     *
     * @param string $type The type to validate
     *
     * @throws StandardUnexpectedValueException
     */
    protected function validateTypeNameWording($type)
    {
        if (!array_key_exists($type, self::$wording)) {
            throw new StandardUnexpectedValueException(
                sprintf(
                    'No wording for %s available, expected wordings are: [%s]',
                    var_export($type, true),
                    implode(', ', array_filter(self::$wording)))
            );
        }
    }

    /**
     * Verifies that a given value is of a certain type
     *
     * @param mixed  $value Value to validate
     * @param string $type  TypeConstraint to check against
     *
     * @throws InvalidArgumentException
     *
     * @return bool
     */
    protected function validateType(&$value, $type, $coerce = false)
    {
        //mostly the case for inline schema
        if (!$type) {
            return true;
        }

        if ('any' === $type) {
            return true;
        }

        if ('object' === $type) {
            return $this->getTypeCheck()->isObject($value);
        }

        if ('array' === $type) {
            if ($coerce) {
                $value = $this->toArray($value);
            }

            return $this->getTypeCheck()->isArray($value);
        }

        if ('integer' === $type) {
            if ($coerce) {
                $value = $this->toInteger($value);
            }

            return is_int($value);
        }

        if ('number' === $type) {
            if ($coerce) {
                $value = $this->toNumber($value);
            }

            return is_numeric($value) && !is_string($value);
        }

        if ('boolean' === $type) {
            if ($coerce) {
                $value = $this->toBoolean($value);
            }

            return is_bool($value);
        }

        if ('string' === $type) {
            if ($coerce) {
                $value = $this->toString($value);
            }

            return is_string($value);
        }

        if ('null' === $type) {
            if ($coerce) {
                $value = $this->toNull($value);
            }

            return is_null($value);
        }

        throw new InvalidArgumentException((is_object($value) ? 'object' : $value) . ' is an invalid type for ' . $type);
    }

    /**
     * Converts a value to boolean. For example, "true" becomes true.
     *
     * @param mixed $value The value to convert to boolean
     *
     * @return bool|mixed
     */
    protected function toBoolean($value)
    {
        if ($value === 1 || $value === 'true') {
            return true;
        }
        if (is_null($value) || $value === 0 || $value === 'false') {
            return false;
        }
        if ($this->getTypeCheck()->isArray($value) && count($value) === 1) {
            return $this->toBoolean(reset($value));
        }

        return $value;
    }

    /**
     * Converts a value to a number. For example, "4.5" becomes 4.5.
     *
     * @param mixed $value the value to convert to a number
     *
     * @return int|float|mixed
     */
    protected function toNumber($value)
    {
        if (is_numeric($value)) {
            return $value + 0; // cast to number
        }
        if (is_bool($value) || is_null($value)) {
            return (int) $value;
        }
        if ($this->getTypeCheck()->isArray($value) && count($value) === 1) {
            return $this->toNumber(reset($value));
        }

        return $value;
    }

    /**
     * Converts a value to an integer. For example, "4" becomes 4.
     *
     * @param mixed $value
     *
     * @return int|mixed
     */
    protected function toInteger($value)
    {
        $numberValue = $this->toNumber($value);
        if (is_numeric($numberValue) && (int) $numberValue == $numberValue) {
            return (int) $numberValue; // cast to number
        }

        return $value;
    }

    /**
     * Converts a value to an array containing that value. For example, [4] becomes 4.
     *
     * @param mixed $value
     *
     * @return array|mixed
     */
    protected function toArray($value)
    {
        if (is_scalar($value) || is_null($value)) {
            return [$value];
        }

        return $value;
    }

    /**
     * Convert a value to a string representation of that value. For example, null becomes "".
     *
     * @param mixed $value
     *
     * @return string|mixed
     */
    protected function toString($value)
    {
        if (is_numeric($value)) {
            return "$value";
        }
        if ($value === true) {
            return 'true';
        }
        if ($value === false) {
            return 'false';
        }
        if (is_null($value)) {
            return '';
        }
        if ($this->getTypeCheck()->isArray($value) && count($value) === 1) {
            return $this->toString(reset($value));
        }

        return $value;
    }

    /**
     * Convert a value to a null. For example, 0 becomes null.
     *
     * @param mixed $value
     *
     * @return null|mixed
     */
    protected function toNull($value)
    {
        if ($value === 0 || $value === false || $value === '') {
            return null;
        }
        if ($this->getTypeCheck()->isArray($value) && count($value) === 1) {
            return $this->toNull(reset($value));
        }

        return $value;
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints;

use const JSON_ERROR_NONE;
use JsonSchema\ConstraintError;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Exception\InvalidArgumentException;
use JsonSchema\Exception\ValidationException;
use JsonSchema\Validator;

/**
 * A more basic constraint definition - used for the public
 * interface to avoid exposing library internals.
 */
class BaseConstraint
{
    /**
     * @var array Errors
     */
    protected $errors = [];

    /**
     * @var int All error types which have occurred
     * @phpstan-var int-mask-of<Validator::ERROR_*>
     */
    protected $errorMask = Validator::ERROR_NONE;

    /**
     * @var Factory
     */
    protected $factory;

    public function __construct(?Factory $factory = null)
    {
        $this->factory = $factory ?: new Factory();
    }

    public function addError(ConstraintError $constraint, ?JsonPointer $path = null, array $more = []): void
    {
        $message = $constraint->getMessage();
        $name = $constraint->getValue();
        $error = [
            'property' => $this->convertJsonPointerIntoPropertyPath($path ?: new JsonPointer('')),
            'pointer' => ltrim((string) ($path ?: new JsonPointer('')), '#'),
            'message' => ucfirst(vsprintf($message, array_map(static function ($val) {
                if (is_scalar($val)) {
                    return is_bool($val) ? var_export($val, true) : $val;
                }

                return json_encode($val);
            }, array_values($more)))),
            'constraint' => [
                'name' => $name,
                'params' => $more
            ],
            'context' => $this->factory->getErrorContext(),
        ];

        if ($this->factory->getConfig(Constraint::CHECK_MODE_EXCEPTIONS)) {
            throw new ValidationException(sprintf('Error validating %s: %s', $error['pointer'], $error['message']));
        }

        $this->errors[] = $error;
        $this->errorMask |= $error['context'];
    }

    public function addErrors(array $errors): void
    {
        if ($errors) {
            $this->errors = array_merge($this->errors, $errors);
            $errorMask = &$this->errorMask;
            array_walk($errors, static function ($error) use (&$errorMask) {
                if (isset($error['context'])) {
                    $errorMask |= $error['context'];
                }
            });
        }
    }

    /**
     * @phpstan-param int-mask-of<Validator::ERROR_*> $errorContext
     */
    public function getErrors(int $errorContext = Validator::ERROR_ALL): array
    {
        if ($errorContext === Validator::ERROR_ALL) {
            return $this->errors;
        }

        return array_filter($this->errors, static function ($error) use ($errorContext) {
            return (bool) ($errorContext & $error['context']);
        });
    }

    /**
     * @phpstan-param int-mask-of<Validator::ERROR_*> $errorContext
     */
    public function numErrors(int $errorContext = Validator::ERROR_ALL): int
    {
        if ($errorContext === Validator::ERROR_ALL) {
            return count($this->errors);
        }

        return count($this->getErrors($errorContext));
    }

    public function isValid(): bool
    {
        return !$this->getErrors();
    }

    /**
     * Clears any reported errors. Should be used between
     * multiple validation checks.
     */
    public function reset(): void
    {
        $this->errors = [];
        $this->errorMask = Validator::ERROR_NONE;
    }

    /**
     * Get the error mask
     *
     * @phpstan-return int-mask-of<Validator::ERROR_*>
     */
    public function getErrorMask(): int
    {
        return $this->errorMask;
    }

    /**
     * Recursively cast an associative array to an object
     */
    public static function arrayToObjectRecursive(array $array): object
    {
        $json = json_encode($array);
        if (json_last_error() !== JSON_ERROR_NONE) {
            $message = 'Unable to encode schema array as JSON';
            if (function_exists('json_last_error_msg')) {
                $message .= ': ' . json_last_error_msg();
            }
            throw new InvalidArgumentException($message);
        }

        return (object) json_decode($json, false);
    }

    /**
     * Transform a JSON pattern into a PCRE regex
     */
    public static function jsonPatternToPhpRegex(string $pattern): string
    {
        return '~' . str_replace('~', '\\~', $pattern) . '~u';
    }

    protected function convertJsonPointerIntoPropertyPath(JsonPointer $pointer): string
    {
        $result = array_map(
            static function ($path) {
                return sprintf(is_numeric($path) ? '[%d]' : '.%s', $path);
            },
            $pointer->getPropertyPaths()
        );

        return trim(implode('', $result), '.');
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Constraints;

use JsonSchema\ConstraintError;
use JsonSchema\Entity\JsonPointer;

class ObjectConstraint extends Constraint
{
    /**
     * @var list<string> List of properties to which a default value has been applied
     */
    protected $appliedDefaults = [];

    /**
     * {@inheritdoc}
     *
     * @param list<string> $appliedDefaults
     */
    public function check(
        &$element,
        $schema = null,
        ?JsonPointer $path = null,
        $properties = null,
        $additionalProp = null,
        $patternProperties = null,
        $appliedDefaults = []
    ): void {
        if ($element instanceof UndefinedConstraint) {
            return;
        }

        $this->appliedDefaults = $appliedDefaults;

        $matches = [];
        if ($patternProperties) {
            // validate the element pattern properties
            $matches = $this->validatePatternProperties($element, $path, $patternProperties);
        }

        if ($properties) {
            // validate the element properties
            $this->validateProperties($element, $properties, $path);
        }

        // validate additional element properties & constraints
        $this->validateElement($element, $matches, $schema, $path, $properties, $additionalProp);
    }

    public function validatePatternProperties($element, ?JsonPointer $path, $patternProperties)
    {
        $matches = [];
        foreach ($patternProperties as $pregex => $schema) {
            $fullRegex = self::jsonPatternToPhpRegex($pregex);

            // Validate the pattern before using it to test for matches
            if (@preg_match($fullRegex, '') === false) {
                $this->addError(ConstraintError::PREGEX_INVALID(), $path, ['pregex' => $pregex]);
                continue;
            }
            foreach ($element as $i => $value) {
                if (preg_match($fullRegex, $i)) {
                    $matches[] = $i;
                    $this->checkUndefined($value, $schema ?: new \stdClass(), $path, $i, in_array($i, $this->appliedDefaults));
                }
            }
        }

        return $matches;
    }

    /**
     * Validates the element properties
     *
     * @param \StdClass        $element        Element to validate
     * @param array            $matches        Matches from patternProperties (if any)
     * @param \StdClass        $schema         ObjectConstraint definition
     * @param JsonPointer|null $path           Current test path
     * @param \StdClass        $properties     Properties
     * @param mixed            $additionalProp Additional properties
     */
    public function validateElement($element, $matches, $schema = null, ?JsonPointer $path = null,
        $properties = null, $additionalProp = null)
    {
        $this->validateMinMaxConstraint($element, $schema, $path);

        foreach ($element as $i => $value) {
            $definition = $this->getProperty($properties, $i);

            // no additional properties allowed
            if (!in_array($i, $matches) && $additionalProp === false && $this->inlineSchemaProperty !== $i && !$definition) {
                $this->addError(ConstraintError::ADDITIONAL_PROPERTIES(), $path, ['property' => $i]);
            }

            // additional properties defined
            if (!in_array($i, $matches) && $additionalProp && !$definition) {
                if ($additionalProp === true) {
                    $this->checkUndefined($value, null, $path, $i, in_array($i, $this->appliedDefaults));
                } else {
                    $this->checkUndefined($value, $additionalProp, $path, $i, in_array($i, $this->appliedDefaults));
                }
            }

            // property requires presence of another
            $require = $this->getProperty($definition, 'requires');
            if ($require && !$this->getProperty($element, $require)) {
                $this->addError(ConstraintError::REQUIRES(), $path, [
                    'property' => $i,
                    'requiredProperty' => $require
                ]);
            }

            $property = $this->getProperty($element, $i, $this->factory->createInstanceFor('undefined'));
            if (is_object($property)) {
                $this->validateMinMaxConstraint(!($property instanceof UndefinedConstraint) ? $property : $element, $definition, $path);
            }
        }
    }

    /**
     * Validates the definition properties
     *
     * @param \stdClass        $element    Element to validate
     * @param \stdClass        $properties Property definitions
     * @param JsonPointer|null $path       Path?
     */
    public function validateProperties(&$element, $properties = null, ?JsonPointer $path = null)
    {
        $undefinedConstraint = $this->factory->createInstanceFor('undefined');

        foreach ($properties as $i => $value) {
            $property = &$this->getProperty($element, $i, $undefinedConstraint);
            $definition = $this->getProperty($properties, $i);

            if (is_object($definition)) {
                // Undefined constraint will check for is_object() and quit if is not - so why pass it?
                $this->checkUndefined($property, $definition, $path, $i, in_array($i, $this->appliedDefaults));
            }
        }
    }

    /**
     * retrieves a property from an object or array
     *
     * @param mixed  $element  Element to validate
     * @param string $property Property to retrieve
     * @param mixed  $fallback Default value if property is not found
     *
     * @return mixed
     */
    protected function &getProperty(&$element, $property, $fallback = null)
    {
        if (is_array($element) && (isset($element[$property]) || array_key_exists($property, $element)) /*$this->checkMode == self::CHECK_MODE_TYPE_CAST*/) {
            return $element[$property];
        } elseif (is_object($element) && property_exists($element, (string) $property)) {
            return $element->$property;
        }

        return $fallback;
    }

    /**
     * validating minimum and maximum property constraints (if present) against an element
     *
     * @param \stdClass        $element          Element to validate
     * @param \stdClass        $objectDefinition ObjectConstraint definition
     * @param JsonPointer|null $path             Path to test?
     */
    protected function validateMinMaxConstraint($element, $objectDefinition, ?JsonPointer $path = null)
    {
        if (!$this->getTypeCheck()::isObject($element)) {
            return;
        }

        // Verify minimum number of properties
        if (isset($objectDefinition->minProperties) && is_int($objectDefinition->minProperties)) {
            if ($this->getTypeCheck()->propertyCount($element) < max(0, $objectDefinition->minProperties)) {
                $this->addError(ConstraintError::PROPERTIES_MIN(), $path, ['minProperties' => $objectDefinition->minProperties]);
            }
        }
        // Verify maximum number of properties
        if (isset($objectDefinition->maxProperties) && is_int($objectDefinition->maxProperties)) {
            if ($this->getTypeCheck()->propertyCount($element) > max(0, $objectDefinition->maxProperties)) {
                $this->addError(ConstraintError::PROPERTIES_MAX(), $path, ['maxProperties' => $objectDefinition->maxProperties]);
            }
        }
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\Exception\InvalidArgumentException;
use JsonSchema\SchemaStorage;
use JsonSchema\SchemaStorageInterface;
use JsonSchema\Uri\UriRetriever;
use JsonSchema\UriRetrieverInterface;
use JsonSchema\Validator;

/**
 * Factory for centralize constraint initialization.
 */
class Factory
{
    /**
     * @var SchemaStorageInterface
     */
    protected $schemaStorage;

    /**
     * @var UriRetriever
     */
    protected $uriRetriever;

    /**
     * @var int
     * @phpstan-var int-mask-of<Constraint::CHECK_MODE_*>
     */
    private $checkMode = Constraint::CHECK_MODE_NORMAL;

    /**
     * @var array<int, TypeCheck\TypeCheckInterface>
     * @phpstan-var array<int-mask-of<Constraint::CHECK_MODE_*>, TypeCheck\TypeCheckInterface>
     */
    private $typeCheck = [];

    /**
     * @var int Validation context
     */
    protected $errorContext = Validator::ERROR_DOCUMENT_VALIDATION;

    /**
     * @var array
     */
    protected $constraintMap = [
        'array' => 'JsonSchema\Constraints\CollectionConstraint',
        'collection' => 'JsonSchema\Constraints\CollectionConstraint',
        'object' => 'JsonSchema\Constraints\ObjectConstraint',
        'type' => 'JsonSchema\Constraints\TypeConstraint',
        'undefined' => 'JsonSchema\Constraints\UndefinedConstraint',
        'string' => 'JsonSchema\Constraints\StringConstraint',
        'number' => 'JsonSchema\Constraints\NumberConstraint',
        'enum' => 'JsonSchema\Constraints\EnumConstraint',
        'const' => 'JsonSchema\Constraints\ConstConstraint',
        'format' => 'JsonSchema\Constraints\FormatConstraint',
        'schema' => 'JsonSchema\Constraints\SchemaConstraint',
        'validator' => 'JsonSchema\Validator'
    ];

    /**
     * @var array<ConstraintInterface>
     */
    private $instanceCache = [];

    /**
     * @phpstan-param int-mask-of<Constraint::CHECK_MODE_*> $checkMode
     */
    public function __construct(
        ?SchemaStorageInterface $schemaStorage = null,
        ?UriRetrieverInterface $uriRetriever = null,
        int $checkMode = Constraint::CHECK_MODE_NORMAL
    ) {
        // set provided config options
        $this->setConfig($checkMode);

        $this->uriRetriever = $uriRetriever ?: new UriRetriever();
        $this->schemaStorage = $schemaStorage ?: new SchemaStorage($this->uriRetriever);
    }

    /**
     * Set config values
     *
     * @param int $checkMode Set checkMode options - does not preserve existing flags
     * @phpstan-param int-mask-of<Constraint::CHECK_MODE_*> $checkMode
     */
    public function setConfig(int $checkMode = Constraint::CHECK_MODE_NORMAL): void
    {
        $this->checkMode = $checkMode;
    }

    /**
     * Enable checkMode flags
     *
     * @phpstan-param int-mask-of<Constraint::CHECK_MODE_*> $options
     */
    public function addConfig(int $options): void
    {
        $this->checkMode |= $options;
    }

    /**
     * Disable checkMode flags
     *
     * @phpstan-param int-mask-of<Constraint::CHECK_MODE_*> $options
     */
    public function removeConfig(int $options): void
    {
        $this->checkMode &= ~$options;
    }

    /**
     * Get checkMode option
     *
     * @param int|null $options Options to get, if null then return entire bitmask
     * @phpstan-param int-mask-of<Constraint::CHECK_MODE_*>|null $options Options to get, if null then return entire bitmask
     *
     * @phpstan-return int-mask-of<Constraint::CHECK_MODE_*>
     */
    public function getConfig(?int $options = null): int
    {
        if ($options === null) {
            return $this->checkMode;
        }

        return $this->checkMode & $options;
    }

    public function getUriRetriever(): UriRetrieverInterface
    {
        return $this->uriRetriever;
    }

    public function getSchemaStorage(): SchemaStorageInterface
    {
        return $this->schemaStorage;
    }

    public function getTypeCheck(): TypeCheck\TypeCheckInterface
    {
        if (!isset($this->typeCheck[$this->checkMode])) {
            $this->typeCheck[$this->checkMode] = ($this->checkMode & Constraint::CHECK_MODE_TYPE_CAST)
                ? new TypeCheck\LooseTypeCheck()
                : new TypeCheck\StrictTypeCheck();
        }

        return $this->typeCheck[$this->checkMode];
    }

    public function setConstraintClass(string $name, string $class): Factory
    {
        // Ensure class exists
        if (!class_exists($class)) {
            throw new InvalidArgumentException('Unknown constraint ' . $name);
        }
        // Ensure class is appropriate
        if (!in_array('JsonSchema\Constraints\ConstraintInterface', class_implements($class))) {
            throw new InvalidArgumentException('Invalid class ' . $name);
        }
        $this->constraintMap[$name] = $class;

        return $this;
    }

    /**
     * Create a constraint instance for the given constraint name.
     *
     * @param string $constraintName
     *
     * @throws InvalidArgumentException if is not possible create the constraint instance
     *
     * @return ConstraintInterface&BaseConstraint
     * @phpstan-return ConstraintInterface&BaseConstraint
     */
    public function createInstanceFor($constraintName)
    {
        if (!isset($this->constraintMap[$constraintName])) {
            throw new InvalidArgumentException('Unknown constraint ' . $constraintName);
        }

        if (!isset($this->instanceCache[$constraintName])) {
            $this->instanceCache[$constraintName] = new $this->constraintMap[$constraintName]($this);
        }

        return clone $this->instanceCache[$constraintName];
    }

    /**
     * Get the error context
     *
     * @phpstan-return Validator::ERROR_DOCUMENT_VALIDATION|Validator::ERROR_SCHEMA_VALIDATION
     */
    public function getErrorContext(): int
    {
        return $this->errorContext;
    }

    /**
     * Set the error context
     *
     * @phpstan-param Validator::ERROR_DOCUMENT_VALIDATION|Validator::ERROR_SCHEMA_VALIDATION $errorContext
     */
    public function setErrorContext(int $errorContext): void
    {
        $this->errorContext = $errorContext;
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Constraints;

use JsonSchema\ConstraintError;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Rfc3339;
use JsonSchema\Tool\Validator\RelativeReferenceValidator;
use JsonSchema\Tool\Validator\UriValidator;

/**
 * Validates against the "format" property
 *
 * @author Justin Rainbow <justin.rainbow@gmail.com>
 *
 * @see   http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.23
 */
class FormatConstraint extends Constraint
{
    /**
     * {@inheritdoc}
     */
    public function check(&$element, $schema = null, ?JsonPointer $path = null, $i = null): void
    {
        if (!isset($schema->format) || $this->factory->getConfig(self::CHECK_MODE_DISABLE_FORMAT)) {
            return;
        }

        switch ($schema->format) {
            case 'date':
                if (is_string($element) && !$date = $this->validateDateTime($element, 'Y-m-d')) {
                    $this->addError(ConstraintError::FORMAT_DATE(), $path, [
                            'date' => $element,
                            'format' => $schema->format
                        ]
                    );
                }
                break;

            case 'time':
                if (is_string($element) && !$this->validateDateTime($element, 'H:i:s')) {
                    $this->addError(ConstraintError::FORMAT_TIME(), $path, [
                            'time' => json_encode($element),
                            'format' => $schema->format,
                        ]
                    );
                }
                break;

            case 'date-time':
                if (is_string($element) && null === Rfc3339::createFromString($element)) {
                    $this->addError(ConstraintError::FORMAT_DATE_TIME(), $path, [
                            'dateTime' => json_encode($element),
                            'format' => $schema->format
                        ]
                    );
                }
                break;

            case 'utc-millisec':
                if (!$this->validateDateTime($element, 'U')) {
                    $this->addError(ConstraintError::FORMAT_DATE_UTC(), $path, [
                        'value' => $element,
                        'format' => $schema->format]);
                }
                break;

            case 'regex':
                if (!$this->validateRegex($element)) {
                    $this->addError(ConstraintError::FORMAT_REGEX(), $path, [
                            'value' => $element,
                            'format' => $schema->format
                        ]
                    );
                }
                break;

            case 'color':
                if (!$this->validateColor($element)) {
                    $this->addError(ConstraintError::FORMAT_COLOR(), $path, ['format' => $schema->format]);
                }
                break;

            case 'style':
                if (!$this->validateStyle($element)) {
                    $this->addError(ConstraintError::FORMAT_STYLE(), $path, ['format' => $schema->format]);
                }
                break;

            case 'phone':
                if (!$this->validatePhone($element)) {
                    $this->addError(ConstraintError::FORMAT_PHONE(), $path, ['format' => $schema->format]);
                }
                break;

            case 'uri':
                if (is_string($element) && !UriValidator::isValid($element)) {
                    $this->addError(ConstraintError::FORMAT_URL(), $path, ['format' => $schema->format]);
                }
                break;

            case 'uriref':
            case 'uri-reference':
                if (is_string($element) && !(UriValidator::isValid($element) || RelativeReferenceValidator::isValid($element))) {
                    $this->addError(ConstraintError::FORMAT_URL(), $path, ['format' => $schema->format]);
                }
                break;

            case 'email':
                if (is_string($element) && null === filter_var($element, FILTER_VALIDATE_EMAIL, FILTER_NULL_ON_FAILURE | FILTER_FLAG_EMAIL_UNICODE)) {
                    $this->addError(ConstraintError::FORMAT_EMAIL(), $path, ['format' => $schema->format]);
                }
                break;

            case 'ip-address':
            case 'ipv4':
                if (is_string($element) && null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4)) {
                    $this->addError(ConstraintError::FORMAT_IP(), $path, ['format' => $schema->format]);
                }
                break;

            case 'ipv6':
                if (is_string($element) && null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV6)) {
                    $this->addError(ConstraintError::FORMAT_IP(), $path, ['format' => $schema->format]);
                }
                break;

            case 'host-name':
            case 'hostname':
                if (!$this->validateHostname($element)) {
                    $this->addError(ConstraintError::FORMAT_HOSTNAME(), $path, ['format' => $schema->format]);
                }
                break;

            default:
                // Empty as it should be:
                // The value of this keyword is called a format attribute. It MUST be a string.
                // A format attribute can generally only validate a given set of instance types.
                // If the type of the instance to validate is not in this set, validation for
                // this format attribute and instance SHOULD succeed.
                // http://json-schema.org/latest/json-schema-validation.html#anchor105
                break;
        }
    }

    protected function validateDateTime($datetime, $format)
    {
        $dt = \DateTime::createFromFormat($format, (string) $datetime);

        if (!$dt) {
            return false;
        }

        if ($datetime === $dt->format($format)) {
            return true;
        }

        return false;
    }

    protected function validateRegex($regex)
    {
        if (!is_string($regex)) {
            return true;
        }

        return false !== @preg_match(self::jsonPatternToPhpRegex($regex), '');
    }

    protected function validateColor($color)
    {
        if (!is_string($color)) {
            return true;
        }

        if (in_array(strtolower($color), ['aqua', 'black', 'blue', 'fuchsia',
            'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'orange', 'purple',
            'red', 'silver', 'teal', 'white', 'yellow'])) {
            return true;
        }

        return preg_match('/^#([a-f0-9]{3}|[a-f0-9]{6})$/i', $color);
    }

    protected function validateStyle($style)
    {
        $properties     = explode(';', rtrim($style, ';'));
        $invalidEntries = preg_grep('/^\s*[-a-z]+\s*:\s*.+$/i', $properties, PREG_GREP_INVERT);

        return empty($invalidEntries);
    }

    protected function validatePhone($phone)
    {
        return preg_match('/^\+?(\(\d{3}\)|\d{3}) \d{3} \d{4}$/', $phone);
    }

    protected function validateHostname($host)
    {
        if (!is_string($host)) {
            return true;
        }

        $hostnameRegex = '/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/i';

        return preg_match($hostnameRegex, $host);
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Entity;

use JsonSchema\Exception\InvalidArgumentException;

/**
 * @package JsonSchema\Entity
 *
 * @author Joost Nijhuis <jnijhuis81@gmail.com>
 */
class JsonPointer
{
    /** @var string */
    private $filename;

    /** @var string[] */
    private $propertyPaths = [];

    /**
     * @var bool Whether the value at this path was set from a schema default
     */
    private $fromDefault = false;

    /**
     * @param string $value
     *
     * @throws InvalidArgumentException when $value is not a string
     */
    public function __construct($value)
    {
        if (!is_string($value)) {
            throw new InvalidArgumentException('Ref value must be a string');
        }

        $splitRef = explode('#', $value, 2);
        $this->filename = $splitRef[0];
        if (array_key_exists(1, $splitRef)) {
            $this->propertyPaths = $this->decodePropertyPaths($splitRef[1]);
        }
    }

    /**
     * @param string $propertyPathString
     *
     * @return string[]
     */
    private function decodePropertyPaths($propertyPathString)
    {
        $paths = [];
        foreach (explode('/', trim($propertyPathString, '/')) as $path) {
            $path = $this->decodePath($path);
            if (is_string($path) && '' !== $path) {
                $paths[] = $path;
            }
        }

        return $paths;
    }

    /**
     * @return array
     */
    private function encodePropertyPaths()
    {
        return array_map(
            [$this, 'encodePath'],
            $this->getPropertyPaths()
        );
    }

    /**
     * @param string $path
     *
     * @return string
     */
    private function decodePath($path)
    {
        return strtr($path, ['~1' => '/', '~0' => '~', '%25' => '%']);
    }

    /**
     * @param string $path
     *
     * @return string
     */
    private function encodePath($path)
    {
        return strtr($path, ['/' => '~1', '~' => '~0', '%' => '%25']);
    }

    /**
     * @return string
     */
    public function getFilename()
    {
        return $this->filename;
    }

    /**
     * @return string[]
     */
    public function getPropertyPaths()
    {
        return $this->propertyPaths;
    }

    /**
     * @param array $propertyPaths
     *
     * @return JsonPointer
     */
    public function withPropertyPaths(array $propertyPaths)
    {
        $new = clone $this;
        $new->propertyPaths = array_map(function ($p): string { return (string) $p; }, $propertyPaths);

        return $new;
    }

    /**
     * @return string
     */
    public function getPropertyPathAsString()
    {
        return rtrim('#/' . implode('/', $this->encodePropertyPaths()), '/');
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return $this->getFilename() . $this->getPropertyPathAsString();
    }

    /**
     * Mark the value at this path as being set from a schema default
     */
    public function setFromDefault(): void
    {
        $this->fromDefault = true;
    }

    /**
     * Check whether the value at this path was set from a schema default
     *
     * @return bool
     */
    public function fromDefault()
    {
        return $this->fromDefault;
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Uri\Retrievers;

/**
 * Interface for URI retrievers
 *
 * @author Sander Coolen <sander@jibber.nl>
 */
interface UriRetrieverInterface
{
    /**
     * Retrieve a schema from the specified URI
     *
     * @param string $uri URI that resolves to a JSON schema
     *
     * @throws \JsonSchema\Exception\ResourceNotFoundException
     *
     * @return mixed string|null
     */
    public function retrieve($uri);

    /**
     * Get media content type
     *
     * @return string
     */
    public function getContentType();
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Uri\Retrievers;

use JsonSchema\Exception\ResourceNotFoundException;

/**
 * Tries to retrieve JSON schemas from a URI using file_get_contents()
 *
 * @author Sander Coolen <sander@jibber.nl>
 */
class FileGetContents extends AbstractRetriever
{
    protected $messageBody;

    /**
     * {@inheritdoc}
     *
     * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve()
     */
    public function retrieve($uri)
    {
        $errorMessage = null;
        set_error_handler(function ($errno, $errstr) use (&$errorMessage) {
            $errorMessage = $errstr;
        });
        $response = file_get_contents($uri);
        restore_error_handler();

        if ($errorMessage) {
            throw new ResourceNotFoundException($errorMessage);
        }

        if (false === $response) {
            throw new ResourceNotFoundException('JSON schema not found at ' . $uri);
        }

        if ($response == ''
            && substr($uri, 0, 7) == 'file://' && substr($uri, -1) == '/'
        ) {
            throw new ResourceNotFoundException('JSON schema not found at ' . $uri);
        }

        $this->messageBody = $response;

        if (function_exists('http_get_last_response_headers')) {
            // Use http_get_last_response_headers() for compatibility with PHP 8.5+
            // where $http_response_header is deprecated.
            $httpResponseHeaders = http_get_last_response_headers();
        } else {
            /** @phpstan-ignore nullCoalesce.variable ($http_response_header can non-existing when no http request was done) */
            $httpResponseHeaders = $http_response_header ?? [];
        }

        if (!empty($httpResponseHeaders)) {
            $this->fetchContentType($httpResponseHeaders);
        } else {
            $this->contentType = null;
        }

        return $this->messageBody;
    }

    /**
     * @param array $headers HTTP Response Headers
     *
     * @return bool Whether the Content-Type header was found or not
     */
    private function fetchContentType(array $headers): bool
    {
        foreach (array_reverse($headers) as $header) {
            if ($this->contentType = self::getContentTypeMatchInHeader($header)) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param string $header
     *
     * @return string|null
     */
    protected static function getContentTypeMatchInHeader($header)
    {
        if (0 < preg_match("/Content-Type:(\V*)/ims", $header, $match)) {
            return trim($match[1]);
        }

        return null;
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Uri\Retrievers;

use JsonSchema\Validator;

/**
 * URI retrieved based on a predefined array of schemas
 *
 * @example
 *
 *      $retriever = new PredefinedArray(array(
 *          'http://acme.com/schemas/person#'  => '{ ... }',
 *          'http://acme.com/schemas/address#' => '{ ... }',
 *      ))
 *
 *      $schema = $retriever->retrieve('http://acme.com/schemas/person#');
 */
class PredefinedArray extends AbstractRetriever
{
    /**
     * Contains schemas as URI => JSON
     *
     * @var array
     */
    private $schemas;

    /**
     * Constructor
     *
     * @param array  $schemas
     * @param string $contentType
     */
    public function __construct(array $schemas, $contentType = Validator::SCHEMA_MEDIA_TYPE)
    {
        $this->schemas     = $schemas;
        $this->contentType = $contentType;
    }

    /**
     * {@inheritdoc}
     *
     * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve()
     */
    public function retrieve($uri)
    {
        if (!array_key_exists($uri, $this->schemas)) {
            throw new \JsonSchema\Exception\ResourceNotFoundException(sprintf(
                'The JSON schema "%s" was not found.',
                $uri
            ));
        }

        return $this->schemas[$uri];
    }
}
<?php

declare(strict_types=1);

/**
 * JsonSchema
 *
 * @filesource
 */

namespace JsonSchema\Uri\Retrievers;

/**
 * AbstractRetriever implements the default shared behavior
 * that all descendant Retrievers should inherit
 *
 * @author Steven Garcia <webwhammy@gmail.com>
 */
abstract class AbstractRetriever implements UriRetrieverInterface
{
    /**
     * Media content type
     *
     * @var string
     */
    protected $contentType;

    /**
     * {@inheritdoc}
     *
     * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::getContentType()
     */
    public function getContentType()
    {
        return $this->contentType;
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Uri\Retrievers;

use JsonSchema\Exception\RuntimeException;
use JsonSchema\Validator;

/**
 * Tries to retrieve JSON schemas from a URI using cURL library
 *
 * @author Sander Coolen <sander@jibber.nl>
 */
class Curl extends AbstractRetriever
{
    protected $messageBody;

    public function __construct()
    {
        if (!function_exists('curl_init')) {
            // Cannot test this, because curl_init is present on all test platforms plus mock
            throw new RuntimeException('cURL not installed'); // @codeCoverageIgnore
        }
    }

    /**
     * {@inheritdoc}
     *
     * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve()
     */
    public function retrieve($uri)
    {
        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, $uri);
        curl_setopt($ch, CURLOPT_HEADER, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, ['Accept: ' . Validator::SCHEMA_MEDIA_TYPE]);

        $response = curl_exec($ch);
        if (false === $response) {
            throw new \JsonSchema\Exception\ResourceNotFoundException('JSON schema not found');
        }

        $this->fetchMessageBody($response);
        $this->fetchContentType($response);

        if (PHP_VERSION_ID < 80000) {
            curl_close($ch);
        }

        return $this->messageBody;
    }

    /**
     * @param string $response cURL HTTP response
     */
    private function fetchMessageBody($response)
    {
        preg_match("/(?:\r\n){2}(.*)$/ms", $response, $match);
        $this->messageBody = $match[1];
    }

    /**
     * @param string $response cURL HTTP response
     *
     * @return bool Whether the Content-Type header was found or not
     */
    protected function fetchContentType($response)
    {
        if (0 < preg_match("/Content-Type:(\V*)/ims", $response, $match)) {
            $this->contentType = trim($match[1]);

            return true;
        }

        return false;
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Uri;

use JsonSchema\Exception\InvalidSchemaMediaTypeException;
use JsonSchema\Exception\JsonDecodingException;
use JsonSchema\Exception\ResourceNotFoundException;
use JsonSchema\Uri\Retrievers\FileGetContents;
use JsonSchema\Uri\Retrievers\UriRetrieverInterface;
use JsonSchema\UriRetrieverInterface as BaseUriRetrieverInterface;
use JsonSchema\Validator;

/**
 * Retrieves JSON Schema URIs
 *
 * @author Tyler Akins <fidian@rumkin.com>
 */
class UriRetriever implements BaseUriRetrieverInterface
{
    /**
     * @var array Map of URL translations
     */
    protected $translationMap = [
        // use local copies of the spec schemas
        '|^https?://json-schema.org/draft-(0[34])/schema#?|' => 'package://dist/schema/json-schema-draft-$1.json'
    ];

    /**
     * @var array A list of endpoints for media type check exclusion
     */
    protected $allowedInvalidContentTypeEndpoints = [
        'http://json-schema.org/',
        'https://json-schema.org/'
    ];

    /**
     * @var null|UriRetrieverInterface
     */
    protected $uriRetriever = null;

    /**
     * @var array|object[]
     *
     * @see loadSchema
     */
    private $schemaCache = [];

    /**
     * Adds an endpoint to the media type validation exclusion list
     *
     * @param string $endpoint
     */
    public function addInvalidContentTypeEndpoint($endpoint)
    {
        $this->allowedInvalidContentTypeEndpoints[] = $endpoint;
    }

    /**
     * Guarantee the correct media type was encountered
     *
     * @param UriRetrieverInterface $uriRetriever
     * @param string                $uri
     *
     * @return bool|void
     */
    public function confirmMediaType($uriRetriever, $uri)
    {
        $contentType = $uriRetriever->getContentType();

        if (is_null($contentType)) {
            // Well, we didn't get an invalid one
            return;
        }

        if (in_array($contentType, [Validator::SCHEMA_MEDIA_TYPE, 'application/json'])) {
            return;
        }

        foreach ($this->allowedInvalidContentTypeEndpoints as $endpoint) {
            if (!\is_null($uri) && strpos($uri, $endpoint) === 0) {
                return true;
            }
        }

        throw new InvalidSchemaMediaTypeException(sprintf('Media type %s expected', Validator::SCHEMA_MEDIA_TYPE));
    }

    /**
     * Get a URI Retriever
     *
     * If none is specified, sets a default FileGetContents retriever and
     * returns that object.
     *
     * @return UriRetrieverInterface
     */
    public function getUriRetriever()
    {
        if (is_null($this->uriRetriever)) {
            $this->setUriRetriever(new FileGetContents());
        }

        return $this->uriRetriever;
    }

    /**
     * Resolve a schema based on pointer
     *
     * URIs can have a fragment at the end in the format of
     * #/path/to/object and we are to look up the 'path' property of
     * the first object then the 'to' and 'object' properties.
     *
     * @param object $jsonSchema JSON Schema contents
     * @param string $uri        JSON Schema URI
     *
     * @throws ResourceNotFoundException
     *
     * @return object JSON Schema after walking down the fragment pieces
     */
    public function resolvePointer($jsonSchema, $uri)
    {
        $resolver = new UriResolver();
        $parsed = $resolver->parse($uri);
        if (empty($parsed['fragment'])) {
            return $jsonSchema;
        }

        $path = explode('/', $parsed['fragment']);
        while ($path) {
            $pathElement = array_shift($path);
            if (!empty($pathElement)) {
                $pathElement = str_replace('~1', '/', $pathElement);
                $pathElement = str_replace('~0', '~', $pathElement);
                if (!empty($jsonSchema->$pathElement)) {
                    $jsonSchema = $jsonSchema->$pathElement;
                } else {
                    throw new ResourceNotFoundException(
                        'Fragment "' . $parsed['fragment'] . '" not found'
                        . ' in ' . $uri
                    );
                }

                if (!is_object($jsonSchema)) {
                    throw new ResourceNotFoundException(
                        'Fragment part "' . $pathElement . '" is no object '
                        . ' in ' . $uri
                    );
                }
            }
        }

        return $jsonSchema;
    }

    /**
     * {@inheritdoc}
     */
    public function retrieve($uri, $baseUri = null, $translate = true)
    {
        $resolver = new UriResolver();
        $resolvedUri = $fetchUri = $resolver->resolve($uri, $baseUri);

        //fetch URL without #fragment
        $arParts = $resolver->parse($resolvedUri);
        if (isset($arParts['fragment'])) {
            unset($arParts['fragment']);
            $fetchUri = $resolver->generate($arParts);
        }

        // apply URI translations
        if ($translate) {
            $fetchUri = $this->translate($fetchUri);
        }

        $jsonSchema = $this->loadSchema($fetchUri);

        // Use the JSON pointer if specified
        $jsonSchema = $this->resolvePointer($jsonSchema, $resolvedUri);

        if ($jsonSchema instanceof \stdClass) {
            $jsonSchema->id = $resolvedUri;
        }

        return $jsonSchema;
    }

    /**
     * Fetch a schema from the given URI, json-decode it and return it.
     * Caches schema objects.
     *
     * @param string $fetchUri Absolute URI
     *
     * @return object JSON schema object
     */
    protected function loadSchema($fetchUri)
    {
        if (isset($this->schemaCache[$fetchUri])) {
            return $this->schemaCache[$fetchUri];
        }

        $uriRetriever = $this->getUriRetriever();
        $contents = $this->uriRetriever->retrieve($fetchUri);
        $this->confirmMediaType($uriRetriever, $fetchUri);
        $jsonSchema = json_decode($contents);

        if (JSON_ERROR_NONE < $error = json_last_error()) {
            throw new JsonDecodingException($error);
        }

        $this->schemaCache[$fetchUri] = $jsonSchema;

        return $jsonSchema;
    }

    /**
     * Set the URI Retriever
     *
     * @param UriRetrieverInterface $uriRetriever
     *
     * @return $this for chaining
     */
    public function setUriRetriever(UriRetrieverInterface $uriRetriever)
    {
        $this->uriRetriever = $uriRetriever;

        return $this;
    }

    /**
     * Parses a URI into five main components
     *
     * @param string $uri
     *
     * @return array
     */
    public function parse($uri)
    {
        preg_match('|^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?|', $uri, $match);

        $components = [];
        if (5 < count($match)) {
            $components =  [
                'scheme'    => $match[2],
                'authority' => $match[4],
                'path'      => $match[5]
            ];
        }

        if (7 < count($match)) {
            $components['query'] = $match[7];
        }

        if (9 < count($match)) {
            $components['fragment'] = $match[9];
        }

        return $components;
    }

    /**
     * Builds a URI based on n array with the main components
     *
     * @param array $components
     *
     * @return string
     */
    public function generate(array $components)
    {
        $uri = $components['scheme'] . '://'
             . $components['authority']
             . $components['path'];

        if (array_key_exists('query', $components)) {
            $uri .= $components['query'];
        }

        if (array_key_exists('fragment', $components)) {
            $uri .= $components['fragment'];
        }

        return $uri;
    }

    /**
     * Resolves a URI
     *
     * @param string $uri     Absolute or relative
     * @param string $baseUri Optional base URI
     *
     * @return string
     */
    public function resolve($uri, $baseUri = null)
    {
        $components = $this->parse($uri);
        $path = $components['path'];

        if ((array_key_exists('scheme', $components)) && ('http' === $components['scheme'])) {
            return $uri;
        }

        $baseComponents = $this->parse($baseUri);
        $basePath = $baseComponents['path'];

        $baseComponents['path'] = UriResolver::combineRelativePathWithBasePath($path, $basePath);

        return $this->generate($baseComponents);
    }

    /**
     * @param string $uri
     *
     * @return bool
     */
    public function isValid($uri)
    {
        $components = $this->parse($uri);

        return !empty($components);
    }

    /**
     * Set a URL translation rule
     */
    public function setTranslation($from, $to)
    {
        $this->translationMap[$from] = $to;
    }

    /**
     * Apply URI translation rules
     */
    public function translate($uri)
    {
        foreach ($this->translationMap as $from => $to) {
            $uri = preg_replace($from, $to, $uri);
        }

        // translate references to local files within the json-schema package
        $uri = preg_replace('|^package://|', sprintf('file://%s/', realpath(__DIR__ . '/../../..')), $uri);

        return $uri;
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Uri;

use JsonSchema\Exception\UriResolverException;
use JsonSchema\UriResolverInterface;

/**
 * Resolves JSON Schema URIs
 *
 * @author Sander Coolen <sander@jibber.nl>
 */
class UriResolver implements UriResolverInterface
{
    /**
     * Parses a URI into five main components
     *
     * @param string $uri
     *
     * @return array
     */
    public function parse($uri)
    {
        preg_match('|^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?|', (string) $uri, $match);

        $components = [];
        if (5 < count($match)) {
            $components =  [
                'scheme'    => $match[2],
                'authority' => $match[4],
                'path'      => $match[5]
            ];
        }
        if (7 < count($match)) {
            $components['query'] = $match[7];
        }
        if (9 < count($match)) {
            $components['fragment'] = $match[9];
        }

        return $components;
    }

    /**
     * Builds a URI based on n array with the main components
     *
     * @param array $components
     *
     * @return string
     */
    public function generate(array $components)
    {
        $uri = $components['scheme'] . '://'
             . $components['authority']
             . $components['path'];

        if (array_key_exists('query', $components) && strlen($components['query'])) {
            $uri .= '?' . $components['query'];
        }
        if (array_key_exists('fragment', $components)) {
            $uri .= '#' . $components['fragment'];
        }

        return $uri;
    }

    /**
     * {@inheritdoc}
     */
    public function resolve($uri, $baseUri = null)
    {
        // treat non-uri base as local file path
        if (
            !is_null($baseUri) &&
            !filter_var($baseUri, \FILTER_VALIDATE_URL) &&
            !preg_match('|^[^/]+://|u', $baseUri)
        ) {
            if (is_file($baseUri)) {
                $baseUri = 'file://' . realpath($baseUri);
            } elseif (is_dir($baseUri)) {
                $baseUri = 'file://' . realpath($baseUri) . '/';
            } else {
                $baseUri = 'file://' . getcwd() . '/' . $baseUri;
            }
        }

        if ($uri == '') {
            return $baseUri;
        }

        $components = $this->parse($uri);
        $path = $components['path'];

        if (!empty($components['scheme'])) {
            return $uri;
        }
        $baseComponents = $this->parse($baseUri);
        $basePath = $baseComponents['path'];

        $baseComponents['path'] = self::combineRelativePathWithBasePath($path, $basePath);
        if (isset($components['fragment'])) {
            $baseComponents['fragment'] = $components['fragment'];
        }

        return $this->generate($baseComponents);
    }

    /**
     * Tries to glue a relative path onto an absolute one
     *
     * @param string $relativePath
     * @param string $basePath
     *
     * @throws UriResolverException
     *
     * @return string Merged path
     */
    public static function combineRelativePathWithBasePath($relativePath, $basePath)
    {
        $relativePath = self::normalizePath($relativePath);
        if (!$relativePath) {
            return $basePath;
        }
        if ($relativePath[0] === '/') {
            return $relativePath;
        }
        if (!$basePath) {
            throw new UriResolverException(sprintf("Unable to resolve URI '%s' from base '%s'", $relativePath, $basePath));
        }

        $dirname = $basePath[strlen($basePath) - 1] === '/' ? $basePath : dirname($basePath);
        $combined = rtrim($dirname, '/') . '/' . ltrim($relativePath, '/');
        $combinedSegments = explode('/', $combined);
        $collapsedSegments = [];
        while ($combinedSegments) {
            $segment = array_shift($combinedSegments);
            if ($segment === '..') {
                if (count($collapsedSegments) <= 1) {
                    // Do not remove the top level (domain)
                    // This is not ideal - the domain should not be part of the path here. parse() and generate()
                    // should handle the "domain" separately, like the schema.
                    // Then the if-condition here would be `if (!$collapsedSegments) {`.
                    throw new UriResolverException(sprintf("Unable to resolve URI '%s' from base '%s'", $relativePath, $basePath));
                }
                array_pop($collapsedSegments);
            } else {
                $collapsedSegments[] = $segment;
            }
        }

        return implode('/', $collapsedSegments);
    }

    /**
     * Normalizes a URI path component by removing dot-slash and double slashes
     *
     * @param string $path
     *
     * @return string
     */
    private static function normalizePath($path)
    {
        $path = preg_replace('|((?<!\.)\./)*|', '', $path);
        $path = preg_replace('|//|', '/', $path);

        return $path;
    }

    /**
     * @param string $uri
     *
     * @return bool
     */
    public function isValid($uri)
    {
        $components = $this->parse($uri);

        return !empty($components);
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Iterator;

/**
 * @package JsonSchema\Iterator
 *
 * @author Joost Nijhuis <jnijhuis81@gmail.com>
 */
class ObjectIterator implements \Iterator, \Countable
{
    /** @var object */
    private $object;

    /** @var int */
    private $position = 0;

    /** @var array */
    private $data = [];

    /** @var bool */
    private $initialized = false;

    /**
     * @param object $object
     */
    public function __construct($object)
    {
        $this->object = $object;
    }

    /**
     * {@inheritdoc}
     */
    #[\ReturnTypeWillChange]
    public function current()
    {
        $this->initialize();

        return $this->data[$this->position];
    }

    /**
     * {@inheritdoc}
     */
    public function next(): void
    {
        $this->initialize();
        $this->position++;
    }

    /**
     * {@inheritdoc}
     */
    public function key(): int
    {
        $this->initialize();

        return $this->position;
    }

    /**
     * {@inheritdoc}
     */
    public function valid(): bool
    {
        $this->initialize();

        return isset($this->data[$this->position]);
    }

    /**
     * {@inheritdoc}
     */
    public function rewind(): void
    {
        $this->initialize();
        $this->position = 0;
    }

    /**
     * {@inheritdoc}
     */
    public function count(): int
    {
        $this->initialize();

        return count($this->data);
    }

    /**
     * Initializer
     */
    private function initialize()
    {
        if (!$this->initialized) {
            $this->data = $this->buildDataFromObject($this->object);
            $this->initialized = true;
        }
    }

    /**
     * @param object $object
     *
     * @return array
     */
    private function buildDataFromObject($object)
    {
        $result = [];

        $stack = new \SplStack();
        $stack->push($object);

        while (!$stack->isEmpty()) {
            $current = $stack->pop();
            if (is_object($current)) {
                array_push($result, $current);
            }

            foreach ($this->getDataFromItem($current) as $propertyName => $propertyValue) {
                if (is_object($propertyValue) || is_array($propertyValue)) {
                    $stack->push($propertyValue);
                }
            }
        }

        return $result;
    }

    /**
     * @param object|array $item
     *
     * @return array
     */
    private function getDataFromItem($item)
    {
        if (!is_object($item) && !is_array($item)) {
            return [];
        }

        return is_object($item) ? get_object_vars($item) : $item;
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema;

use JsonSchema\Exception\InvalidArgumentException;

class ConstraintError extends Enum
{
    public const ADDITIONAL_ITEMS = 'additionalItems';
    public const ADDITIONAL_PROPERTIES = 'additionalProp';
    public const ALL_OF = 'allOf';
    public const ANY_OF = 'anyOf';
    public const DEPENDENCIES = 'dependencies';
    public const DISALLOW = 'disallow';
    public const DIVISIBLE_BY = 'divisibleBy';
    public const ENUM = 'enum';
    public const CONSTANT = 'const';
    public const EXCLUSIVE_MINIMUM = 'exclusiveMinimum';
    public const EXCLUSIVE_MAXIMUM = 'exclusiveMaximum';
    public const FORMAT_COLOR = 'colorFormat';
    public const FORMAT_DATE = 'dateFormat';
    public const FORMAT_DATE_TIME = 'dateTimeFormat';
    public const FORMAT_DATE_UTC = 'dateUtcFormat';
    public const FORMAT_EMAIL = 'emailFormat';
    public const FORMAT_HOSTNAME = 'styleHostName';
    public const FORMAT_IP = 'ipFormat';
    public const FORMAT_PHONE = 'phoneFormat';
    public const FORMAT_REGEX= 'regexFormat';
    public const FORMAT_STYLE = 'styleFormat';
    public const FORMAT_TIME = 'timeFormat';
    public const FORMAT_URL = 'urlFormat';
    public const FORMAT_URL_REF = 'urlRefFormat';
    public const INVALID_SCHEMA = 'invalidSchema';
    public const LENGTH_MAX = 'maxLength';
    public const LENGTH_MIN = 'minLength';
    public const MAXIMUM = 'maximum';
    public const MIN_ITEMS = 'minItems';
    public const MINIMUM = 'minimum';
    public const MISSING_ERROR = 'missingError';
    public const MISSING_MAXIMUM = 'missingMaximum';
    public const MISSING_MINIMUM = 'missingMinimum';
    public const MAX_ITEMS = 'maxItems';
    public const MULTIPLE_OF = 'multipleOf';
    public const NOT = 'not';
    public const ONE_OF = 'oneOf';
    public const REQUIRED = 'required';
    public const REQUIRES = 'requires';
    public const PATTERN = 'pattern';
    public const PREGEX_INVALID = 'pregrex';
    public const PROPERTIES_MIN = 'minProperties';
    public const PROPERTIES_MAX = 'maxProperties';
    public const TYPE = 'type';
    public const UNIQUE_ITEMS = 'uniqueItems';

    /**
     * @return string
     */
    public function getMessage()
    {
        $name = $this->getValue();
        static $messages = [
            self::ADDITIONAL_ITEMS => 'The item %s[%s] is not defined and the definition does not allow additional items',
            self::ADDITIONAL_PROPERTIES => 'The property %s is not defined and the definition does not allow additional properties',
            self::ALL_OF => 'Failed to match all schemas',
            self::ANY_OF => 'Failed to match at least one schema',
            self::DEPENDENCIES => '%s depends on %s, which is missing',
            self::DISALLOW => 'Disallowed value was matched',
            self::DIVISIBLE_BY => 'Is not divisible by %d',
            self::ENUM => 'Does not have a value in the enumeration %s',
            self::CONSTANT => 'Does not have a value equal to %s',
            self::EXCLUSIVE_MINIMUM => 'Must have a minimum value greater than %d',
            self::EXCLUSIVE_MAXIMUM => 'Must have a maximum value less than %d',
            self::FORMAT_COLOR => 'Invalid color',
            self::FORMAT_DATE => 'Invalid date %s, expected format YYYY-MM-DD',
            self::FORMAT_DATE_TIME => 'Invalid date-time %s, expected format YYYY-MM-DDThh:mm:ssZ or YYYY-MM-DDThh:mm:ss+hh:mm',
            self::FORMAT_DATE_UTC => 'Invalid time %s, expected integer of milliseconds since Epoch',
            self::FORMAT_EMAIL => 'Invalid email',
            self::FORMAT_HOSTNAME => 'Invalid hostname',
            self::FORMAT_IP => 'Invalid IP address',
            self::FORMAT_PHONE => 'Invalid phone number',
            self::FORMAT_REGEX=> 'Invalid regex format %s',
            self::FORMAT_STYLE => 'Invalid style',
            self::FORMAT_TIME => 'Invalid time %s, expected format hh:mm:ss',
            self::FORMAT_URL => 'Invalid URL format',
            self::FORMAT_URL_REF => 'Invalid URL reference format',
            self::LENGTH_MAX => 'Must be at most %d characters long',
            self::INVALID_SCHEMA => 'Schema is not valid',
            self::LENGTH_MIN => 'Must be at least %d characters long',
            self::MAX_ITEMS => 'There must be a maximum of %d items in the array, %d found',
            self::MAXIMUM => 'Must have a maximum value less than or equal to %d',
            self::MIN_ITEMS => 'There must be a minimum of %d items in the array, %d found',
            self::MINIMUM => 'Must have a minimum value greater than or equal to %d',
            self::MISSING_MAXIMUM => 'Use of exclusiveMaximum requires presence of maximum',
            self::MISSING_MINIMUM => 'Use of exclusiveMinimum requires presence of minimum',
            /*self::MISSING_ERROR => 'Used for tests; this error is deliberately commented out',*/
            self::MULTIPLE_OF => 'Must be a multiple of %s',
            self::NOT => 'Matched a schema which it should not',
            self::ONE_OF => 'Failed to match exactly one schema',
            self::REQUIRED => 'The property %s is required',
            self::REQUIRES => 'The presence of the property %s requires that %s also be present',
            self::PATTERN => 'Does not match the regex pattern %s',
            self::PREGEX_INVALID => 'The pattern %s is invalid',
            self::PROPERTIES_MIN => 'Must contain a minimum of %d properties',
            self::PROPERTIES_MAX => 'Must contain no more than %d properties',
            self::TYPE => '%s value found, but %s is required',
            self::UNIQUE_ITEMS => 'There are no duplicates allowed in the array'
        ];

        if (!isset($messages[$name])) {
            throw new InvalidArgumentException('Missing error message for ' . $name);
        }

        return $messages[$name];
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema;

abstract class Enum extends \MabeEnum\Enum
{
}
<?php

declare(strict_types=1);

namespace JsonSchema;

use JsonSchema\Constraints\BaseConstraint;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Exception\UnresolvableJsonPointerException;
use JsonSchema\Uri\UriResolver;
use JsonSchema\Uri\UriRetriever;

class SchemaStorage implements SchemaStorageInterface
{
    public const INTERNAL_PROVIDED_SCHEMA_URI = 'internal://provided-schema/';

    protected $uriRetriever;
    protected $uriResolver;
    protected $schemas = [];

    public function __construct(
        ?UriRetrieverInterface $uriRetriever = null,
        ?UriResolverInterface $uriResolver = null
    ) {
        $this->uriRetriever = $uriRetriever ?: new UriRetriever();
        $this->uriResolver = $uriResolver ?: new UriResolver();
    }

    /**
     * @return UriRetrieverInterface
     */
    public function getUriRetriever()
    {
        return $this->uriRetriever;
    }

    /**
     * @return UriResolverInterface
     */
    public function getUriResolver()
    {
        return $this->uriResolver;
    }

    /**
     * {@inheritdoc}
     */
    public function addSchema(string $id, $schema = null): void
    {
        if (is_null($schema) && $id !== self::INTERNAL_PROVIDED_SCHEMA_URI) {
            // if the schema was user-provided to Validator and is still null, then assume this is
            // what the user intended, as there's no way for us to retrieve anything else. User-supplied
            // schemas do not have an associated URI when passed via Validator::validate().
            $schema = $this->uriRetriever->retrieve($id);
        }

        // cast array schemas to object
        if (is_array($schema)) {
            $schema = BaseConstraint::arrayToObjectRecursive($schema);
        }

        // workaround for bug in draft-03 & draft-04 meta-schemas (id & $ref defined with incorrect format)
        // see https://github.com/json-schema-org/JSON-Schema-Test-Suite/issues/177#issuecomment-293051367
        if (is_object($schema) && property_exists($schema, 'id')) {
            if ($schema->id === 'http://json-schema.org/draft-04/schema#') {
                $schema->properties->id->format = 'uri-reference';
            } elseif ($schema->id === 'http://json-schema.org/draft-03/schema#') {
                $schema->properties->id->format = 'uri-reference';
                $schema->properties->{'$ref'}->format = 'uri-reference';
            }
        }

        $this->scanForSubschemas($schema, $id);

        // resolve references
        $this->expandRefs($schema, $id);

        $this->schemas[$id] = $schema;
    }

    /**
     * Recursively resolve all references against the provided base
     *
     * @param mixed $schema
     */
    private function expandRefs(&$schema, ?string $parentId = null): void
    {
        if (!is_object($schema)) {
            if (is_array($schema)) {
                foreach ($schema as &$member) {
                    $this->expandRefs($member, $parentId);
                }
            }

            return;
        }

        if (property_exists($schema, '$ref') && is_string($schema->{'$ref'})) {
            $refPointer = new JsonPointer($this->uriResolver->resolve($schema->{'$ref'}, $parentId));
            $schema->{'$ref'} = (string) $refPointer;
        }

        foreach ($schema as $propertyName => &$member) {
            if (in_array($propertyName, ['enum', 'const'])) {
                // Enum and const don't allow $ref as a keyword, see https://github.com/json-schema-org/JSON-Schema-Test-Suite/pull/445
                continue;
            }

            $childId = $parentId;
            if (property_exists($schema, 'id') && is_string($schema->id) && $childId !== $schema->id) {
                $childId = $this->uriResolver->resolve($schema->id, $childId);
            }

            $this->expandRefs($member, $childId);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getSchema(string $id)
    {
        if (!array_key_exists($id, $this->schemas)) {
            $this->addSchema($id);
        }

        return $this->schemas[$id];
    }

    /**
     * {@inheritdoc}
     */
    public function resolveRef(string $ref, $resolveStack = [])
    {
        $jsonPointer = new JsonPointer($ref);

        // resolve filename for pointer
        $fileName = $jsonPointer->getFilename();
        if (!strlen($fileName)) {
            throw new UnresolvableJsonPointerException(sprintf(
                "Could not resolve fragment '%s': no file is defined",
                $jsonPointer->getPropertyPathAsString()
            ));
        }

        // get & process the schema
        $refSchema = $this->getSchema($fileName);
        foreach ($jsonPointer->getPropertyPaths() as $path) {
            if (is_object($refSchema) && property_exists($refSchema, $path)) {
                $refSchema = $this->resolveRefSchema($refSchema->{$path}, $resolveStack);
            } elseif (is_array($refSchema) && array_key_exists($path, $refSchema)) {
                $refSchema = $this->resolveRefSchema($refSchema[$path], $resolveStack);
            } else {
                throw new UnresolvableJsonPointerException(sprintf(
                    'File: %s is found, but could not resolve fragment: %s',
                    $jsonPointer->getFilename(),
                    $jsonPointer->getPropertyPathAsString()
                ));
            }
        }

        return $refSchema;
    }

    /**
     * {@inheritdoc}
     */
    public function resolveRefSchema($refSchema, $resolveStack = [])
    {
        if (is_object($refSchema) && property_exists($refSchema, '$ref') && is_string($refSchema->{'$ref'})) {
            if (in_array($refSchema, $resolveStack, true)) {
                throw new UnresolvableJsonPointerException(sprintf(
                    'Dereferencing a pointer to %s results in an infinite loop',
                    $refSchema->{'$ref'}
                ));
            }
            $resolveStack[] = $refSchema;

            return $this->resolveRef($refSchema->{'$ref'}, $resolveStack);
        }

        return $refSchema;
    }

    /**
     * @param mixed $schema
     */
    private function scanForSubschemas($schema, string $parentId): void
    {
        if (!$schema instanceof \stdClass  && !is_array($schema)) {
            return;
        }

        foreach ($schema as $propertyName => $potentialSubSchema) {
            if (!is_object($potentialSubSchema)) {
                continue;
            }

            if (property_exists($potentialSubSchema, 'id') && is_string($potentialSubSchema->id) && property_exists($potentialSubSchema, 'type')) {
                // Enum and const don't allow id as a keyword, see https://github.com/json-schema-org/JSON-Schema-Test-Suite/pull/471
                if (in_array($propertyName, ['enum', 'const'])) {
                    continue;
                }

                // Found sub schema
                $this->addSchema($this->uriResolver->resolve($potentialSubSchema->id, $parentId), $potentialSubSchema);
            }

            $this->scanForSubschemas($potentialSubSchema, $parentId);
        }
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema;

class Rfc3339
{
    private const REGEX = '/^(\d{4}-\d{2}-\d{2}[T ]{1}\d{2}:\d{2}:\d{2})(\.\d+)?(Z|([+-]\d{2}):?(\d{2}))$/';

    /**
     * Try creating a DateTime instance
     *
     * @param string $string
     *
     * @return \DateTime|null
     */
    public static function createFromString($string)
    {
        if (!preg_match(self::REGEX, strtoupper($string), $matches)) {
            return null;
        }

        $dateAndTime = $matches[1];
        $microseconds = $matches[2] ?: '.000000';
        $timeZone = 'Z' !== $matches[3] ? $matches[4] . ':' . $matches[5] : '+00:00';
        $dateFormat = strpos($dateAndTime, 'T') === false ? 'Y-m-d H:i:s.uP' : 'Y-m-d\TH:i:s.uP';
        $dateTime = \DateTime::createFromFormat($dateFormat, $dateAndTime . $microseconds . $timeZone, new \DateTimeZone('UTC'));

        return $dateTime ?: null;
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Tool\Validator;

class RelativeReferenceValidator
{
    public static function isValid(string $ref): bool
    {
        // Relative reference pattern as per RFC 3986, Section 4.1
        $pattern = '/^(([^\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/';

        if (preg_match($pattern, $ref) !== 1) {
            return false;
        }

        // Additional checks for invalid cases
        if (preg_match('/^(http|https):\/\//', $ref)) {
            return false; // Absolute URI
        }

        if (preg_match('/^:\/\//', $ref)) {
            return false; // Missing scheme in authority
        }

        if (preg_match('/^:\//', $ref)) {
            return false; // Invalid scheme separator
        }

        if (preg_match('/^\/\/$/', $ref)) {
            return false; // Empty authority
        }

        if (preg_match('/^\/\/\/[^\/]/', $ref)) {
            return false; // Invalid authority with three slashes
        }

        if (preg_match('/\s/', $ref)) {
            return false; // Spaces are not allowed in URIs
        }

        if (preg_match('/^\?#|^#$/', $ref)) {
            return false; // Missing path but having query and fragment
        }

        if ($ref === '#' || $ref === '?') {
            return false; // Missing path and having only fragment or query
        }

        return true;
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Tool\Validator;

class UriValidator
{
    public static function isValid(string $uri): bool
    {
        // RFC 3986: Hierarchical URIs (http, https, ftp, etc.)
        $hierarchicalPattern = '/^
            ([a-z][a-z0-9+\-.]*):\/\/                # Scheme (http, https, ftp, etc.)
            (?:([^:@\/?#]+)(?::([^@\/?#]*))?@)?      # Optional userinfo (user:pass@)
            ([a-z0-9.-]+|\[[a-f0-9:.]+\])            # Hostname or IPv6 in brackets
            (?::(\d{1,5}))?                          # Optional port
            (\/[a-zA-Z0-9._~!$&\'()*+,;=:@\/%-]*)*   # Path (valid characters only)
            (\?([^#]*))?                             # Optional query
            (\#(.*))?                                # Optional fragment
        $/ix';

        // RFC 3986: Non-Hierarchical URIs (mailto, data, urn)
        $nonHierarchicalPattern = '/^
            (mailto|data|urn):                        # Only allow known non-hierarchical schemes
            (.+)                                      # Must contain at least one character after scheme
        $/ix';

        // RFC 5322-compliant email validation for `mailto:` URIs
        $emailPattern = '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/';

        // First, check if it's a valid hierarchical URI
        if (preg_match($hierarchicalPattern, $uri, $matches) === 1) {
            // Validate domain name (no double dots like example..com)
            if (!empty($matches[4]) && preg_match('/\.\./', $matches[4])) {
                return false;
            }

            // Validate port (should be between 1 and 65535 if specified)
            if (!empty($matches[5]) && ($matches[5] < 1 || $matches[5] > 65535)) {
                return false;
            }

            // Validate path (reject illegal characters: < > { } | \ ^ `)
            if (!empty($matches[6]) && preg_match('/[<>{}|\\\^`]/', $matches[6])) {
                return false;
            }

            return true;
        }

        // If not hierarchical, check non-hierarchical URIs
        if (preg_match($nonHierarchicalPattern, $uri, $matches) === 1) {
            $scheme = strtolower($matches[1]); // Extract the scheme

            // Special case: `mailto:` must contain a **valid email address**
            if ($scheme === 'mailto') {
                return preg_match($emailPattern, $matches[2]) === 1;
            }

            return true; // Valid non-hierarchical URI
        }

        return false;
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Tool;

class DeepComparer
{
    /**
     * @param mixed $left
     * @param mixed $right
     */
    public static function isEqual($left, $right): bool
    {
        if ($left === null && $right === null) {
            return true;
        }

        $isLeftScalar = is_scalar($left);
        $isLeftNumber = is_int($left) || is_float($left);
        $isRightScalar = is_scalar($right);
        $isRightNumber = is_int($right) || is_float($right);

        if ($isLeftScalar && $isRightScalar) {
            /*
             * In Json-Schema mathematically equal numbers are compared equal
             */
            if ($isLeftNumber && $isRightNumber && (float) $left === (float) $right) {
                return true;
            }

            return $left === $right;
        }

        if ($isLeftScalar !== $isRightScalar) {
            return false;
        }

        if (is_array($left) && is_array($right)) {
            return self::isArrayEqual($left, $right);
        }

        if ($left instanceof \stdClass && $right instanceof \stdClass) {
            return self::isArrayEqual((array) $left, (array) $right);
        }

        return false;
    }

    /**
     * @param array<string|int, mixed> $left
     * @param array<string|int, mixed> $right
     */
    private static function isArrayEqual(array $left, array $right): bool
    {
        if (count($left) !== count($right)) {
            return false;
        }
        foreach ($left as $key => $value) {
            if (!array_key_exists($key, $right)) {
                return false;
            }

            if (!self::isEqual($value, $right[$key])) {
                return false;
            }
        }

        return true;
    }
}
<?php

declare(strict_types=1);

namespace JsonSchema\Tool;

use JsonSchema\Exception\JsonDecodingException;
use JsonSchema\Exception\RuntimeException;

class DeepCopy
{
    /**
     * @param mixed $input
     *
     * @return mixed
     */
    public static function copyOf($input)
    {
        $json = json_encode($input);
        if (JSON_ERROR_NONE < $error = json_last_error()) {
            throw new JsonDecodingException($error);
        }

        if ($json === false) {
            throw new RuntimeException('Failed to encode input to JSON: ' . json_last_error_msg());
        }

        return json_decode($json, self::isAssociativeArray($input));
    }

    /**
     * @param mixed $input
     */
    private static function isAssociativeArray($input): bool
    {
        return is_array($input) && array_keys($input) !== range(0, count($input) - 1);
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema;

/**
 * @package JsonSchema
 */
interface UriResolverInterface
{
    /**
     * Resolves a URI
     *
     * @param string      $uri     Absolute or relative
     * @param null|string $baseUri Optional base URI
     *
     * @return string Absolute URI
     */
    public function resolve($uri, $baseUri = null);
}
#!/usr/bin/env php
<?php
/**
 * JSON schema validator
 *
 * @author Christian Weiske <christian.weiske@netresearch.de>
 */

// support running this tool from git checkout
$projectDirectory = dirname(__DIR__);
if (is_dir($projectDirectory . DIRECTORY_SEPARATOR . 'vendor')) {
    set_include_path($projectDirectory . PATH_SEPARATOR . get_include_path());
}

// autoload composer classes
$composerAutoload = stream_resolve_include_path('vendor/autoload.php');
if (!$composerAutoload) {
    echo("Cannot load json-schema library\n");
    exit(1);
}
require($composerAutoload);

$arOptions = [];
$arArgs = [];
array_shift($argv);//script itself
foreach ($argv as $arg) {
    if ($arg[0] == '-') {
        $arOptions[$arg] = true;
    } else {
        $arArgs[] = $arg;
    }
}

if (count($arArgs) == 0
    || isset($arOptions['--help']) || isset($arOptions['-h'])
) {
    echo <<<HLP
Validate schema
Usage: validate-json data.json
   or: validate-json data.json schema.json

Options:
      --dump-schema     Output full schema and exit
      --dump-schema-url Output URL of schema
      --verbose         Show additional output
      --quiet           Suppress all output
   -h --help            Show this help

HLP;
    exit(1);
}

if (count($arArgs) == 1) {
    $pathData   = $arArgs[0];
    $pathSchema = null;
} else {
    $pathData   = $arArgs[0];
    $pathSchema = getUrlFromPath($arArgs[1]);
}

/**
 * Show the json parse error that happened last
 */
function showJsonError(): void
{
    $constants = get_defined_constants(true);
    $json_errors = [];
    foreach ($constants['json'] as $name => $value) {
        if (!strncmp($name, 'JSON_ERROR_', 11)) {
            $json_errors[$value] = $name;
        }
    }

    output('JSON parse error: ' . $json_errors[json_last_error()] . "\n");
}

function getUrlFromPath($path)
{
    if (parse_url($path, PHP_URL_SCHEME) !== null) {
        //already an URL
        return $path;
    }
    if ($path[0] == '/') {
        //absolute path
        return 'file://' . $path;
    }

    //relative path: make absolute
    return 'file://' . getcwd() . '/' . $path;
}

/**
 * Take a HTTP header value and split it up into parts.
 *
 * @param $headerValue
 * @return array Key "_value" contains the main value, all others
 *               as given in the header value
 */
function parseHeaderValue($headerValue)
{
    if (strpos($headerValue, ';') === false) {
        return ['_value' => $headerValue];
    }

    $parts = explode(';', $headerValue);
    $arData = ['_value' => array_shift($parts)];
    foreach ($parts as $part) {
        list($name, $value) = explode('=', $part);
        $arData[$name] = trim($value, ' "\'');
    }
    return $arData;
}

/**
 * Send a string to the output stream, but only if --quiet is not enabled
 *
 * @param $str string A string output
 */
function output($str) {
    global $arOptions;
    if (!isset($arOptions['--quiet'])) {
        echo $str;
    }
}

$urlData = getUrlFromPath($pathData);

$context = stream_context_create(
    [
        'http' => [
            'header'        => [
                'Accept: */*',
                'Connection: Close'
            ],
            'max_redirects' => 5
        ]
    ]
);
$dataString = file_get_contents($pathData, false, $context);
if ($dataString == '') {
    output("Data file is not readable or empty.\n");
    exit(3);
}

$data = json_decode($dataString);
unset($dataString);
if ($data === null) {
    output("Error loading JSON data file\n");
    showJsonError();
    exit(5);
}

if ($pathSchema === null) {
    if (isset($http_response_header)) {
        array_shift($http_response_header);//HTTP/1.0 line
        foreach ($http_response_header as $headerLine) {
            list($hName, $hValue) = explode(':', $headerLine, 2);
            $hName = strtolower($hName);
            if ($hName == 'link') {
                //Link: <http://example.org/schema#>; rel="describedBy"
                $hParts = parseHeaderValue($hValue);
                if (isset($hParts['rel']) && $hParts['rel'] == 'describedBy') {
                    $pathSchema = trim($hParts['_value'], ' <>');
                }
            } else if ($hName == 'content-type') {
                //Content-Type: application/my-media-type+json;
                //              profile=http://example.org/schema#
                $hParts = parseHeaderValue($hValue);
                if (isset($hParts['profile'])) {
                    $pathSchema = $hParts['profile'];
                }

            }
        }
    }
    if (is_object($data) && property_exists($data, '$schema')) {
        $pathSchema = $data->{'$schema'};
    }

    //autodetect schema
    if ($pathSchema === null) {
        output("JSON data must be an object and have a \$schema property.\n");
        output("You can pass the schema file on the command line as well.\n");
        output("Schema autodetection failed.\n");
        exit(6);
    }
}
if ($pathSchema[0] == '/') {
    $pathSchema = 'file://' . $pathSchema;
}

$resolver = new JsonSchema\Uri\UriResolver();
$retriever = new JsonSchema\Uri\UriRetriever();
try {
    $urlSchema = $resolver->resolve($pathSchema, $urlData);

    if (isset($arOptions['--dump-schema-url'])) {
        echo $urlSchema . "\n";
        exit();
    }
} catch (Exception $e) {
    output("Error loading JSON schema file\n");
    output($urlSchema . "\n");
    output($e->getMessage() . "\n");
    exit(2);
}
$refResolver = new JsonSchema\SchemaStorage($retriever, $resolver);
$schema = $refResolver->resolveRef($urlSchema);

if (isset($arOptions['--dump-schema'])) {
    $options = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0;
    echo json_encode($schema, $options) . "\n";
    exit();
}

try {
    $validator = new JsonSchema\Validator();
    $validator->validate($data, $schema);

    if ($validator->isValid()) {
        if(isset($arOptions['--verbose'])) {
            output("OK. The supplied JSON validates against the schema.\n");
        }
    } else {
        output("JSON does not validate. Violations:\n");
        foreach ($validator->getErrors() as $error) {
            output(sprintf("[%s] %s\n", $error['property'], $error['message']));
        }
        exit(23);
    }
} catch (Exception $e) {
    output("JSON does not validate. Error:\n");
    output($e->getMessage() . "\n");
    output("Error code: " . $e->getCode() . "\n");
    exit(24);
}
The MIT License (MIT)

Copyright (c) 2012 Jan Sorgalla, Christian Lück, Cees-Jan Kiewiet, Chris Boden

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.
<?php

namespace React\Promise;

use React\Promise\Internal\RejectedPromise;

/**
 * @template T
 * @template-implements PromiseInterface<T>
 */
final class Promise implements PromiseInterface
{
    /** @var (callable(callable(T):void,callable(\Throwable):void):void)|null */
    private $canceller;

    /** @var ?PromiseInterface<T> */
    private $result;

    /** @var list<callable(PromiseInterface<T>):void> */
    private $handlers = [];

    /** @var int */
    private $requiredCancelRequests = 0;

    /** @var bool */
    private $cancelled = false;

    /**
     * @param callable(callable(T):void,callable(\Throwable):void):void $resolver
     * @param (callable(callable(T):void,callable(\Throwable):void):void)|null $canceller
     */
    public function __construct(callable $resolver, ?callable $canceller = null)
    {
        $this->canceller = $canceller;

        // Explicitly overwrite arguments with null values before invoking
        // resolver function. This ensure that these arguments do not show up
        // in the stack trace in PHP 7+ only.
        $cb = $resolver;
        $resolver = $canceller = null;
        $this->call($cb);
    }

    public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface
    {
        if (null !== $this->result) {
            return $this->result->then($onFulfilled, $onRejected);
        }

        if (null === $this->canceller) {
            return new static($this->resolver($onFulfilled, $onRejected));
        }

        // This promise has a canceller, so we create a new child promise which
        // has a canceller that invokes the parent canceller if all other
        // followers are also cancelled. We keep a reference to this promise
        // instance for the static canceller function and clear this to avoid
        // keeping a cyclic reference between parent and follower.
        $parent = $this;
        ++$parent->requiredCancelRequests;

        return new static(
            $this->resolver($onFulfilled, $onRejected),
            static function () use (&$parent): void {
                assert($parent instanceof self);
                --$parent->requiredCancelRequests;

                if ($parent->requiredCancelRequests <= 0) {
                    $parent->cancel();
                }

                $parent = null;
            }
        );
    }

    /**
     * @template TThrowable of \Throwable
     * @template TRejected
     * @param callable(TThrowable): (PromiseInterface<TRejected>|TRejected) $onRejected
     * @return PromiseInterface<T|TRejected>
     */
    public function catch(callable $onRejected): PromiseInterface
    {
        return $this->then(null, static function (\Throwable $reason) use ($onRejected) {
            if (!_checkTypehint($onRejected, $reason)) {
                return new RejectedPromise($reason);
            }

            /**
             * @var callable(\Throwable):(PromiseInterface<TRejected>|TRejected) $onRejected
             */
            return $onRejected($reason);
        });
    }

    public function finally(callable $onFulfilledOrRejected): PromiseInterface
    {
        return $this->then(static function ($value) use ($onFulfilledOrRejected): PromiseInterface {
            /** @var T $value */
            return resolve($onFulfilledOrRejected())->then(function () use ($value) {
                return $value;
            });
        }, static function (\Throwable $reason) use ($onFulfilledOrRejected): PromiseInterface {
            return resolve($onFulfilledOrRejected())->then(function () use ($reason): RejectedPromise {
                return new RejectedPromise($reason);
            });
        });
    }

    public function cancel(): void
    {
        $this->cancelled = true;
        $canceller = $this->canceller;
        $this->canceller = null;

        $parentCanceller = null;

        if (null !== $this->result) {
            // Forward cancellation to rejected promise to avoid reporting unhandled rejection
            if ($this->result instanceof RejectedPromise) {
                $this->result->cancel();
            }

            // Go up the promise chain and reach the top most promise which is
            // itself not following another promise
            $root = $this->unwrap($this->result);

            // Return if the root promise is already resolved or a
            // FulfilledPromise or RejectedPromise
            if (!$root instanceof self || null !== $root->result) {
                return;
            }

            $root->requiredCancelRequests--;

            if ($root->requiredCancelRequests <= 0) {
                $parentCanceller = [$root, 'cancel'];
            }
        }

        if (null !== $canceller) {
            $this->call($canceller);
        }

        // For BC, we call the parent canceller after our own canceller
        if ($parentCanceller) {
            $parentCanceller();
        }
    }

    /**
     * @deprecated 3.0.0 Use `catch()` instead
     * @see self::catch()
     */
    public function otherwise(callable $onRejected): PromiseInterface
    {
        return $this->catch($onRejected);
    }

    /**
     * @deprecated 3.0.0 Use `finally()` instead
     * @see self::finally()
     */
    public function always(callable $onFulfilledOrRejected): PromiseInterface
    {
        return $this->finally($onFulfilledOrRejected);
    }

    private function resolver(?callable $onFulfilled = null, ?callable $onRejected = null): callable
    {
        return function (callable $resolve, callable $reject) use ($onFulfilled, $onRejected): void {
            $this->handlers[] = static function (PromiseInterface $promise) use ($onFulfilled, $onRejected, $resolve, $reject): void {
                $promise = $promise->then($onFulfilled, $onRejected);

                if ($promise instanceof self && $promise->result === null) {
                    $promise->handlers[] = static function (PromiseInterface $promise) use ($resolve, $reject): void {
                        $promise->then($resolve, $reject);
                    };
                } else {
                    $promise->then($resolve, $reject);
                }
            };
        };
    }

    private function reject(\Throwable $reason): void
    {
        if (null !== $this->result) {
            return;
        }

        $this->settle(reject($reason));
    }

    /**
     * @param PromiseInterface<T> $result
     */
    private function settle(PromiseInterface $result): void
    {
        $result = $this->unwrap($result);

        if ($result === $this) {
            $result = new RejectedPromise(
                new \LogicException('Cannot resolve a promise with itself.')
            );
        }

        if ($result instanceof self) {
            $result->requiredCancelRequests++;
        } else {
            // Unset canceller only when not following a pending promise
            $this->canceller = null;
        }

        $handlers = $this->handlers;

        $this->handlers = [];
        $this->result = $result;

        foreach ($handlers as $handler) {
            $handler($result);
        }

        // Forward cancellation to rejected promise to avoid reporting unhandled rejection
        if ($this->cancelled && $result instanceof RejectedPromise) {
            $result->cancel();
        }
    }

    /**
     * @param PromiseInterface<T> $promise
     * @return PromiseInterface<T>
     */
    private function unwrap(PromiseInterface $promise): PromiseInterface
    {
        while ($promise instanceof self && null !== $promise->result) {
            /** @var PromiseInterface<T> $promise */
            $promise = $promise->result;
        }

        return $promise;
    }

    /**
     * @param callable(callable(mixed):void,callable(\Throwable):void):void $cb
     */
    private function call(callable $cb): void
    {
        // Explicitly overwrite argument with null value. This ensure that this
        // argument does not show up in the stack trace in PHP 7+ only.
        $callback = $cb;
        $cb = null;

        // Use reflection to inspect number of arguments expected by this callback.
        // We did some careful benchmarking here: Using reflection to avoid unneeded
        // function arguments is actually faster than blindly passing them.
        // Also, this helps avoiding unnecessary function arguments in the call stack
        // if the callback creates an Exception (creating garbage cycles).
        if (\is_array($callback)) {
            $ref = new \ReflectionMethod($callback[0], $callback[1]);
        } elseif (\is_object($callback) && !$callback instanceof \Closure) {
            $ref = new \ReflectionMethod($callback, '__invoke');
        } else {
            assert($callback instanceof \Closure || \is_string($callback));
            $ref = new \ReflectionFunction($callback);
        }
        $args = $ref->getNumberOfParameters();

        try {
            if ($args === 0) {
                $callback();
            } else {
                // Keep references to this promise instance for the static resolve/reject functions.
                // By using static callbacks that are not bound to this instance
                // and passing the target promise instance by reference, we can
                // still execute its resolving logic and still clear this
                // reference when settling the promise. This helps avoiding
                // garbage cycles if any callback creates an Exception.
                // These assumptions are covered by the test suite, so if you ever feel like
                // refactoring this, go ahead, any alternative suggestions are welcome!
                $target =& $this;

                $callback(
                    static function ($value) use (&$target): void {
                        if ($target !== null) {
                            $target->settle(resolve($value));
                            $target = null;
                        }
                    },
                    static function (\Throwable $reason) use (&$target): void {
                        if ($target !== null) {
                            $target->reject($reason);
                            $target = null;
                        }
                    }
                );
            }
        } catch (\Throwable $e) {
            $target = null;
            $this->reject($e);
        }
    }
}
<?php

namespace React\Promise;

/**
 * @template T
 */
final class Deferred
{
    /**
     * @var PromiseInterface<T>
     */
    private $promise;

    /** @var callable(T):void */
    private $resolveCallback;

    /** @var callable(\Throwable):void */
    private $rejectCallback;

    /**
     * @param (callable(callable(T):void,callable(\Throwable):void):void)|null $canceller
     */
    public function __construct(?callable $canceller = null)
    {
        $this->promise = new Promise(function ($resolve, $reject): void {
            $this->resolveCallback = $resolve;
            $this->rejectCallback  = $reject;
        }, $canceller);
    }

    /**
     * @return PromiseInterface<T>
     */
    public function promise(): PromiseInterface
    {
        return $this->promise;
    }

    /**
     * @param T $value
     */
    public function resolve($value): void
    {
        ($this->resolveCallback)($value);
    }

    public function reject(\Throwable $reason): void
    {
        ($this->rejectCallback)($reason);
    }
}
<?php

if (!\function_exists('React\Promise\resolve')) {
    require __DIR__.'/functions.php';
}
<?php

namespace React\Promise;

/**
 * @template-covariant T
 */
interface PromiseInterface
{
    /**
     * Transforms a promise's value by applying a function to the promise's fulfillment
     * or rejection value. Returns a new promise for the transformed result.
     *
     * The `then()` method registers new fulfilled and rejection handlers with a promise
     * (all parameters are optional):
     *
     *  * `$onFulfilled` will be invoked once the promise is fulfilled and passed
     *     the result as the first argument.
     *  * `$onRejected` will be invoked once the promise is rejected and passed the
     *     reason as the first argument.
     *
     * It returns a new promise that will fulfill with the return value of either
     * `$onFulfilled` or `$onRejected`, whichever is called, or will reject with
     * the thrown exception if either throws.
     *
     * A promise makes the following guarantees about handlers registered in
     * the same call to `then()`:
     *
     *  1. Only one of `$onFulfilled` or `$onRejected` will be called,
     *      never both.
     *  2. `$onFulfilled` and `$onRejected` will never be called more
     *      than once.
     *
     * @template TFulfilled
     * @template TRejected
     * @param ?(callable((T is void ? null : T)): (PromiseInterface<TFulfilled>|TFulfilled)) $onFulfilled
     * @param ?(callable(\Throwable): (PromiseInterface<TRejected>|TRejected)) $onRejected
     * @return PromiseInterface<($onRejected is null ? ($onFulfilled is null ? T : TFulfilled) : ($onFulfilled is null ? T|TRejected : TFulfilled|TRejected))>
     */
    public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface;

    /**
     * Registers a rejection handler for promise. It is a shortcut for:
     *
     * ```php
     * $promise->then(null, $onRejected);
     * ```
     *
     * Additionally, you can type hint the `$reason` argument of `$onRejected` to catch
     * only specific errors.
     *
     * @template TThrowable of \Throwable
     * @template TRejected
     * @param callable(TThrowable): (PromiseInterface<TRejected>|TRejected) $onRejected
     * @return PromiseInterface<T|TRejected>
     */
    public function catch(callable $onRejected): PromiseInterface;

    /**
     * Allows you to execute "cleanup" type tasks in a promise chain.
     *
     * It arranges for `$onFulfilledOrRejected` to be called, with no arguments,
     * when the promise is either fulfilled or rejected.
     *
     * * If `$promise` fulfills, and `$onFulfilledOrRejected` returns successfully,
     *    `$newPromise` will fulfill with the same value as `$promise`.
     * * If `$promise` fulfills, and `$onFulfilledOrRejected` throws or returns a
     *    rejected promise, `$newPromise` will reject with the thrown exception or
     *    rejected promise's reason.
     * * If `$promise` rejects, and `$onFulfilledOrRejected` returns successfully,
     *    `$newPromise` will reject with the same reason as `$promise`.
     * * If `$promise` rejects, and `$onFulfilledOrRejected` throws or returns a
     *    rejected promise, `$newPromise` will reject with the thrown exception or
     *    rejected promise's reason.
     *
     * `finally()` behaves similarly to the synchronous finally statement. When combined
     * with `catch()`, `finally()` allows you to write code that is similar to the familiar
     * synchronous catch/finally pair.
     *
     * Consider the following synchronous code:
     *
     * ```php
     * try {
     *     return doSomething();
     * } catch(\Exception $e) {
     *     return handleError($e);
     * } finally {
     *     cleanup();
     * }
     * ```
     *
     * Similar asynchronous code (with `doSomething()` that returns a promise) can be
     * written:
     *
     * ```php
     * return doSomething()
     *     ->catch('handleError')
     *     ->finally('cleanup');
     * ```
     *
     * @param callable(): (void|PromiseInterface<void>) $onFulfilledOrRejected
     * @return PromiseInterface<T>
     */
    public function finally(callable $onFulfilledOrRejected): PromiseInterface;

    /**
     * The `cancel()` method notifies the creator of the promise that there is no
     * further interest in the results of the operation.
     *
     * Once a promise is settled (either fulfilled or rejected), calling `cancel()` on
     * a promise has no effect.
     *
     * @return void
     */
    public function cancel(): void;

    /**
     * [Deprecated] Registers a rejection handler for a promise.
     *
     * This method continues to exist only for BC reasons and to ease upgrading
     * between versions. It is an alias for:
     *
     * ```php
     * $promise->catch($onRejected);
     * ```
     *
     * @template TThrowable of \Throwable
     * @template TRejected
     * @param callable(TThrowable): (PromiseInterface<TRejected>|TRejected) $onRejected
     * @return PromiseInterface<T|TRejected>
     * @deprecated 3.0.0 Use catch() instead
     * @see self::catch()
     */
    public function otherwise(callable $onRejected): PromiseInterface;

    /**
     * [Deprecated] Allows you to execute "cleanup" type tasks in a promise chain.
     *
     * This method continues to exist only for BC reasons and to ease upgrading
     * between versions. It is an alias for:
     *
     * ```php
     * $promise->finally($onFulfilledOrRejected);
     * ```
     *
     * @param callable(): (void|PromiseInterface<void>) $onFulfilledOrRejected
     * @return PromiseInterface<T>
     * @deprecated 3.0.0 Use finally() instead
     * @see self::finally()
     */
    public function always(callable $onFulfilledOrRejected): PromiseInterface;
}
<?php

namespace React\Promise\Exception;

class LengthException extends \LengthException
{
}
<?php

namespace React\Promise\Exception;

/**
 * Represents an exception that is a composite of one or more other exceptions.
 *
 * This exception is useful in situations where a promise must be rejected
 * with multiple exceptions. It is used for example to reject the returned
 * promise from `some()` and `any()` when too many input promises reject.
 */
class CompositeException extends \Exception
{
    /** @var \Throwable[] */
    private $throwables;

    /** @param \Throwable[] $throwables */
    public function __construct(array $throwables, string $message = '', int $code = 0, ?\Throwable $previous = null)
    {
        parent::__construct($message, $code, $previous);

        $this->throwables = $throwables;
    }

    /**
     * @return \Throwable[]
     */
    public function getThrowables(): array
    {
        return $this->throwables;
    }
}
<?php

namespace React\Promise\Internal;

use React\Promise\PromiseInterface;
use function React\Promise\resolve;

/**
 * @internal
 *
 * @template T
 * @template-implements PromiseInterface<T>
 */
final class FulfilledPromise implements PromiseInterface
{
    /** @var T */
    private $value;

    /**
     * @param T $value
     * @throws \InvalidArgumentException
     */
    public function __construct($value = null)
    {
        if ($value instanceof PromiseInterface) {
            throw new \InvalidArgumentException('You cannot create React\Promise\FulfilledPromise with a promise. Use React\Promise\resolve($promiseOrValue) instead.');
        }

        $this->value = $value;
    }

    /**
     * @template TFulfilled
     * @param ?(callable((T is void ? null : T)): (PromiseInterface<TFulfilled>|TFulfilled)) $onFulfilled
     * @return PromiseInterface<($onFulfilled is null ? T : TFulfilled)>
     */
    public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface
    {
        if (null === $onFulfilled) {
            return $this;
        }

        try {
            /**
             * @var PromiseInterface<T>|T $result
             */
            $result = $onFulfilled($this->value);
            return resolve($result);
        } catch (\Throwable $exception) {
            return new RejectedPromise($exception);
        }
    }

    public function catch(callable $onRejected): PromiseInterface
    {
        return $this;
    }

    public function finally(callable $onFulfilledOrRejected): PromiseInterface
    {
        return $this->then(function ($value) use ($onFulfilledOrRejected): PromiseInterface {
            /** @var T $value */
            return resolve($onFulfilledOrRejected())->then(function () use ($value) {
                return $value;
            });
        });
    }

    public function cancel(): void
    {
    }

    /**
     * @deprecated 3.0.0 Use `catch()` instead
     * @see self::catch()
     */
    public function otherwise(callable $onRejected): PromiseInterface
    {
        return $this->catch($onRejected);
    }

    /**
     * @deprecated 3.0.0 Use `finally()` instead
     * @see self::finally()
     */
    public function always(callable $onFulfilledOrRejected): PromiseInterface
    {
        return $this->finally($onFulfilledOrRejected);
    }
}
<?php

namespace React\Promise\Internal;

/**
 * @internal
 */
final class CancellationQueue
{
    /** @var bool */
    private $started = false;

    /** @var object[] */
    private $queue = [];

    public function __invoke(): void
    {
        if ($this->started) {
            return;
        }

        $this->started = true;
        $this->drain();
    }

    /**
     * @param mixed $cancellable
     */
    public function enqueue($cancellable): void
    {
        if (!\is_object($cancellable) || !\method_exists($cancellable, 'then') || !\method_exists($cancellable, 'cancel')) {
            return;
        }

        $length = \array_push($this->queue, $cancellable);

        if ($this->started && 1 === $length) {
            $this->drain();
        }
    }

    private function drain(): void
    {
        for ($i = \key($this->queue); isset($this->queue[$i]); $i++) {
            $cancellable = $this->queue[$i];
            assert(\method_exists($cancellable, 'cancel'));

            $exception = null;

            try {
                $cancellable->cancel();
            } catch (\Throwable $exception) {
            }

            unset($this->queue[$i]);

            if ($exception) {
                throw $exception;
            }
        }

        $this->queue = [];
    }
}
<?php

namespace React\Promise\Internal;

use React\Promise\PromiseInterface;
use function React\Promise\_checkTypehint;
use function React\Promise\resolve;
use function React\Promise\set_rejection_handler;

/**
 * @internal
 *
 * @template-implements PromiseInterface<never>
 */
final class RejectedPromise implements PromiseInterface
{
    /** @var \Throwable */
    private $reason;

    /** @var bool */
    private $handled = false;

    /**
     * @param \Throwable $reason
     */
    public function __construct(\Throwable $reason)
    {
        $this->reason = $reason;
    }

    /** @throws void */
    public function __destruct()
    {
        if ($this->handled) {
            return;
        }

        $handler = set_rejection_handler(null);
        if ($handler === null) {
            $message = 'Unhandled promise rejection with ' . $this->reason;

            \error_log($message);
            return;
        }

        try {
            $handler($this->reason);
        } catch (\Throwable $e) {
            \preg_match('/^([^:\s]++)(.*+)$/sm', (string) $e, $match);
            \assert(isset($match[1], $match[2]));
            $message = 'Fatal error: Uncaught ' . $match[1] . ' from unhandled promise rejection handler' . $match[2];

            \error_log($message);
            exit(255);
        }
    }

    /**
     * @template TRejected
     * @param ?callable $onFulfilled
     * @param ?(callable(\Throwable): (PromiseInterface<TRejected>|TRejected)) $onRejected
     * @return PromiseInterface<($onRejected is null ? never : TRejected)>
     */
    public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface
    {
        if (null === $onRejected) {
            return $this;
        }

        $this->handled = true;

        try {
            return resolve($onRejected($this->reason));
        } catch (\Throwable $exception) {
            return new RejectedPromise($exception);
        }
    }

    /**
     * @template TThrowable of \Throwable
     * @template TRejected
     * @param callable(TThrowable): (PromiseInterface<TRejected>|TRejected) $onRejected
     * @return PromiseInterface<TRejected>
     */
    public function catch(callable $onRejected): PromiseInterface
    {
        if (!_checkTypehint($onRejected, $this->reason)) {
            return $this;
        }

        /**
         * @var callable(\Throwable):(PromiseInterface<TRejected>|TRejected) $onRejected
         */
        return $this->then(null, $onRejected);
    }

    public function finally(callable $onFulfilledOrRejected): PromiseInterface
    {
        return $this->then(null, function (\Throwable $reason) use ($onFulfilledOrRejected): PromiseInterface {
            return resolve($onFulfilledOrRejected())->then(function () use ($reason): PromiseInterface {
                return new RejectedPromise($reason);
            });
        });
    }

    public function cancel(): void
    {
        $this->handled = true;
    }

    /**
     * @deprecated 3.0.0 Use `catch()` instead
     * @see self::catch()
     */
    public function otherwise(callable $onRejected): PromiseInterface
    {
        return $this->catch($onRejected);
    }

    /**
     * @deprecated 3.0.0 Use `always()` instead
     * @see self::always()
     */
    public function always(callable $onFulfilledOrRejected): PromiseInterface
    {
        return $this->finally($onFulfilledOrRejected);
    }
}
<?php

namespace React\Promise;

use React\Promise\Exception\CompositeException;
use React\Promise\Internal\FulfilledPromise;
use React\Promise\Internal\RejectedPromise;

/**
 * Creates a promise for the supplied `$promiseOrValue`.
 *
 * If `$promiseOrValue` is a value, it will be the resolution value of the
 * returned promise.
 *
 * If `$promiseOrValue` is a thenable (any object that provides a `then()` method),
 * a trusted promise that follows the state of the thenable is returned.
 *
 * If `$promiseOrValue` is a promise, it will be returned as is.
 *
 * @template T
 * @param PromiseInterface<T>|T $promiseOrValue
 * @return PromiseInterface<T>
 */
function resolve($promiseOrValue): PromiseInterface
{
    if ($promiseOrValue instanceof PromiseInterface) {
        return $promiseOrValue;
    }

    if (\is_object($promiseOrValue) && \method_exists($promiseOrValue, 'then')) {
        $canceller = null;

        if (\method_exists($promiseOrValue, 'cancel')) {
            $canceller = [$promiseOrValue, 'cancel'];
            assert(\is_callable($canceller));
        }

        /** @var Promise<T> */
        return new Promise(function (callable $resolve, callable $reject) use ($promiseOrValue): void {
            $promiseOrValue->then($resolve, $reject);
        }, $canceller);
    }

    return new FulfilledPromise($promiseOrValue);
}

/**
 * Creates a rejected promise for the supplied `$reason`.
 *
 * If `$reason` is a value, it will be the rejection value of the
 * returned promise.
 *
 * If `$reason` is a promise, its completion value will be the rejected
 * value of the returned promise.
 *
 * This can be useful in situations where you need to reject a promise without
 * throwing an exception. For example, it allows you to propagate a rejection with
 * the value of another promise.
 *
 * @return PromiseInterface<never>
 */
function reject(\Throwable $reason): PromiseInterface
{
    return new RejectedPromise($reason);
}

/**
 * Returns a promise that will resolve only once all the items in
 * `$promisesOrValues` have resolved. The resolution value of the returned promise
 * will be an array containing the resolution values of each of the items in
 * `$promisesOrValues`.
 *
 * @template T
 * @param iterable<PromiseInterface<T>|T> $promisesOrValues
 * @return PromiseInterface<array<T>>
 */
function all(iterable $promisesOrValues): PromiseInterface
{
    $cancellationQueue = new Internal\CancellationQueue();

    /** @var Promise<array<T>> */
    return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void {
        $toResolve = 0;
        /** @var bool */
        $continue  = true;
        $values    = [];

        foreach ($promisesOrValues as $i => $promiseOrValue) {
            $cancellationQueue->enqueue($promiseOrValue);
            $values[$i] = null;
            ++$toResolve;

            resolve($promiseOrValue)->then(
                function ($value) use ($i, &$values, &$toResolve, &$continue, $resolve): void {
                    $values[$i] = $value;

                    if (0 === --$toResolve && !$continue) {
                        $resolve($values);
                    }
                },
                function (\Throwable $reason) use (&$continue, $reject): void {
                    $continue = false;
                    $reject($reason);
                }
            );

            if (!$continue && !\is_array($promisesOrValues)) {
                break;
            }
        }

        $continue = false;
        if ($toResolve === 0) {
            $resolve($values);
        }
    }, $cancellationQueue);
}

/**
 * Initiates a competitive race that allows one winner. Returns a promise which is
 * resolved in the same way the first settled promise resolves.
 *
 * The returned promise will become **infinitely pending** if  `$promisesOrValues`
 * contains 0 items.
 *
 * @template T
 * @param iterable<PromiseInterface<T>|T> $promisesOrValues
 * @return PromiseInterface<T>
 */
function race(iterable $promisesOrValues): PromiseInterface
{
    $cancellationQueue = new Internal\CancellationQueue();

    /** @var Promise<T> */
    return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void {
        $continue = true;

        foreach ($promisesOrValues as $promiseOrValue) {
            $cancellationQueue->enqueue($promiseOrValue);

            resolve($promiseOrValue)->then($resolve, $reject)->finally(function () use (&$continue): void {
                $continue = false;
            });

            if (!$continue && !\is_array($promisesOrValues)) {
                break;
            }
        }
    }, $cancellationQueue);
}

/**
 * Returns a promise that will resolve when any one of the items in
 * `$promisesOrValues` resolves. The resolution value of the returned promise
 * will be the resolution value of the triggering item.
 *
 * The returned promise will only reject if *all* items in `$promisesOrValues` are
 * rejected. The rejection value will be an array of all rejection reasons.
 *
 * The returned promise will also reject with a `React\Promise\Exception\LengthException`
 * if `$promisesOrValues` contains 0 items.
 *
 * @template T
 * @param iterable<PromiseInterface<T>|T> $promisesOrValues
 * @return PromiseInterface<T>
 */
function any(iterable $promisesOrValues): PromiseInterface
{
    $cancellationQueue = new Internal\CancellationQueue();

    /** @var Promise<T> */
    return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void {
        $toReject = 0;
        $continue = true;
        $reasons  = [];

        foreach ($promisesOrValues as $i => $promiseOrValue) {
            $cancellationQueue->enqueue($promiseOrValue);
            ++$toReject;

            resolve($promiseOrValue)->then(
                function ($value) use ($resolve, &$continue): void {
                    $continue = false;
                    $resolve($value);
                },
                function (\Throwable $reason) use ($i, &$reasons, &$toReject, $reject, &$continue): void {
                    $reasons[$i] = $reason;

                    if (0 === --$toReject && !$continue) {
                        $reject(new CompositeException(
                            $reasons,
                            'All promises rejected.'
                        ));
                    }
                }
            );

            if (!$continue && !\is_array($promisesOrValues)) {
                break;
            }
        }

        $continue = false;
        if ($toReject === 0 && !$reasons) {
            $reject(new Exception\LengthException(
                'Must contain at least 1 item but contains only 0 items.'
            ));
        } elseif ($toReject === 0) {
            $reject(new CompositeException(
                $reasons,
                'All promises rejected.'
            ));
        }
    }, $cancellationQueue);
}

/**
 * Sets the global rejection handler for unhandled promise rejections.
 *
 * Note that rejected promises should always be handled similar to how any
 * exceptions should always be caught in a `try` + `catch` block. If you remove
 * the last reference to a rejected promise that has not been handled, it will
 * report an unhandled promise rejection. See also the [`reject()` function](#reject)
 * for more details.
 *
 * The `?callable $callback` argument MUST be a valid callback function that
 * accepts a single `Throwable` argument or a `null` value to restore the
 * default promise rejection handler. The return value of the callback function
 * will be ignored and has no effect, so you SHOULD return a `void` value. The
 * callback function MUST NOT throw or the program will be terminated with a
 * fatal error.
 *
 * The function returns the previous rejection handler or `null` if using the
 * default promise rejection handler.
 *
 * The default promise rejection handler will log an error message plus its
 * stack trace:
 *
 * ```php
 * // Unhandled promise rejection with RuntimeException: Unhandled in example.php:2
 * React\Promise\reject(new RuntimeException('Unhandled'));
 * ```
 *
 * The promise rejection handler may be used to use customize the log message or
 * write to custom log targets. As a rule of thumb, this function should only be
 * used as a last resort and promise rejections are best handled with either the
 * [`then()` method](#promiseinterfacethen), the
 * [`catch()` method](#promiseinterfacecatch), or the
 * [`finally()` method](#promiseinterfacefinally).
 * See also the [`reject()` function](#reject) for more details.
 *
 * @param callable(\Throwable):void|null $callback
 * @return callable(\Throwable):void|null
 */
function set_rejection_handler(?callable $callback): ?callable
{
    static $current = null;
    $previous = $current;
    $current = $callback;

    return $previous;
}

/**
 * @internal
 */
function _checkTypehint(callable $callback, \Throwable $reason): bool
{
    if (\is_array($callback)) {
        $callbackReflection = new \ReflectionMethod($callback[0], $callback[1]);
    } elseif (\is_object($callback) && !$callback instanceof \Closure) {
        $callbackReflection = new \ReflectionMethod($callback, '__invoke');
    } else {
        assert($callback instanceof \Closure || \is_string($callback));
        $callbackReflection = new \ReflectionFunction($callback);
    }

    $parameters = $callbackReflection->getParameters();

    if (!isset($parameters[0])) {
        return true;
    }

    $expectedException = $parameters[0];

    // Extract the type of the argument and handle different possibilities
    $type = $expectedException->getType();

    $isTypeUnion = true;
    $types = [];

    switch (true) {
        case $type === null:
            break;
        case $type instanceof \ReflectionNamedType:
            $types = [$type];
            break;
        case $type instanceof \ReflectionIntersectionType:
            $isTypeUnion = false;
        case $type instanceof \ReflectionUnionType:
            $types = $type->getTypes();
            break;
        default:
            throw new \LogicException('Unexpected return value of ReflectionParameter::getType');
    }

    // If there is no type restriction, it matches
    if (empty($types)) {
        return true;
    }

    foreach ($types as $type) {

        if ($type instanceof \ReflectionIntersectionType) {
            foreach ($type->getTypes() as $typeToMatch) {
                assert($typeToMatch instanceof \ReflectionNamedType);
                $name = $typeToMatch->getName();
                if (!($matches = (!$typeToMatch->isBuiltin() && $reason instanceof $name))) {
                    break;
                }
            }
            assert(isset($matches));
        } else {
            assert($type instanceof \ReflectionNamedType);
            $name = $type->getName();
            $matches = !$type->isBuiltin() && $reason instanceof $name;
        }

        // If we look for a single match (union), we can return early on match
        // If we look for a full match (intersection), we can return early on mismatch
        if ($matches) {
            if ($isTypeUnion) {
                return true;
            }
        } else {
            if (!$isTypeUnion) {
                return false;
            }
        }
    }

    // If we look for a single match (union) and did not return early, we matched no type and are false
    // If we look for a full match (intersection) and did not return early, we matched all types and are true
    return $isTypeUnion ? false : true;
}
<?php

// autoload_files.php @generated by Composer

$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);

return array(
    '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
    '6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
    '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
    '8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php',
    'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
    'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php',
    'ad155f8f1cf0d418fe49e248db8c661b' => $vendorDir . '/react/promise/src/functions_include.php',
);
Copyright (c) Nils Adermann, Jordi Boggiano

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.
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer;

use Composer\Autoload\AutoloadGenerator;
use Composer\Console\GithubActionError;
use Composer\DependencyResolver\DefaultPolicy;
use Composer\DependencyResolver\LocalRepoTransaction;
use Composer\DependencyResolver\LockTransaction;
use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\UninstallOperation;
use Composer\DependencyResolver\PoolOptimizer;
use Composer\DependencyResolver\Pool;
use Composer\DependencyResolver\Request;
use Composer\DependencyResolver\Solver;
use Composer\DependencyResolver\SolverProblemsException;
use Composer\DependencyResolver\PolicyInterface;
use Composer\Downloader\DownloadManager;
use Composer\Downloader\TransportException;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Filter\PlatformRequirementFilter\IgnoreListPlatformRequirementFilter;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
use Composer\Installer\InstallationManager;
use Composer\Installer\InstallerEvents;
use Composer\Installer\SuggestedPackagesReporter;
use Composer\IO\IOInterface;
use Composer\Package\AliasPackage;
use Composer\Package\RootAliasPackage;
use Composer\Package\BasePackage;
use Composer\Package\CompletePackage;
use Composer\Package\CompletePackageInterface;
use Composer\Package\Link;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Dumper\ArrayDumper;
use Composer\Package\Version\VersionParser;
use Composer\Package\Package;
use Composer\Repository\ArrayRepository;
use Composer\Repository\RepositorySet;
use Composer\Repository\CompositeRepository;
use Composer\Semver\Constraint\Constraint;
use Composer\Package\Locker;
use Composer\Package\RootPackageInterface;
use Composer\Repository\InstalledArrayRepository;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Repository\InstalledRepository;
use Composer\Repository\RootPackageRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\RepositoryManager;
use Composer\Repository\LockArrayRepository;
use Composer\Script\ScriptEvents;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Advisory\Auditor;
use Composer\Util\Platform;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Beau Simensen <beau@dflydev.com>
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Nils Adermann <naderman@naderman.de>
 */
class Installer
{
    public const ERROR_NONE = 0; // no error/success state
    public const ERROR_GENERIC_FAILURE = 1;
    public const ERROR_NO_LOCK_FILE_FOR_PARTIAL_UPDATE = 3;
    public const ERROR_LOCK_FILE_INVALID = 4;
    // used/declared in SolverProblemsException, carried over here for completeness
    public const ERROR_DEPENDENCY_RESOLUTION_FAILED = 2;
    public const ERROR_AUDIT_FAILED = 5;
    // technically exceptions are thrown with various status codes >400, but the process exit code is normalized to 100
    public const ERROR_TRANSPORT_EXCEPTION = 100;

    /**
     * @var IOInterface
     */
    protected $io;

    /**
     * @var Config
     */
    protected $config;

    /**
     * @var RootPackageInterface&BasePackage
     */
    protected $package;

    // TODO can we get rid of the below and just use the package itself?
    /**
     * @var RootPackageInterface&BasePackage
     */
    protected $fixedRootPackage;

    /**
     * @var DownloadManager
     */
    protected $downloadManager;

    /**
     * @var RepositoryManager
     */
    protected $repositoryManager;

    /**
     * @var Locker
     */
    protected $locker;

    /**
     * @var InstallationManager
     */
    protected $installationManager;

    /**
     * @var EventDispatcher
     */
    protected $eventDispatcher;

    /**
     * @var AutoloadGenerator
     */
    protected $autoloadGenerator;

    /** @var bool */
    protected $preferSource = false;
    /** @var bool */
    protected $preferDist = false;
    /** @var bool */
    protected $optimizeAutoloader = false;
    /** @var bool */
    protected $classMapAuthoritative = false;
    /** @var bool */
    protected $apcuAutoloader = false;
    /** @var string|null */
    protected $apcuAutoloaderPrefix = null;
    /** @var bool */
    protected $devMode = false;
    /** @var bool */
    protected $dryRun = false;
    /** @var bool */
    protected $downloadOnly = false;
    /** @var bool */
    protected $verbose = false;
    /** @var bool */
    protected $update = false;
    /** @var bool */
    protected $install = true;
    /** @var bool */
    protected $dumpAutoloader = true;
    /** @var bool */
    protected $runScripts = true;
    /** @var bool */
    protected $preferStable = false;
    /** @var bool */
    protected $preferLowest = false;
    /** @var bool */
    protected $minimalUpdate = false;
    /** @var bool */
    protected $writeLock;
    /** @var bool */
    protected $executeOperations = true;
    /** @var bool */
    protected $audit = true;
    /** @var bool */
    protected $errorOnAudit = false;
    /** @var Auditor::FORMAT_* */
    protected $auditFormat = Auditor::FORMAT_SUMMARY;
    /** @var list<string> */
    private $ignoredTypes = ['php-ext', 'php-ext-zend'];
    /** @var list<string>|null */
    private $allowedTypes = null;

    /** @var bool */
    protected $updateMirrors = false;
    /**
     * Array of package names/globs flagged for update
     *
     * @var non-empty-list<string>|null
     */
    protected $updateAllowList = null;
    /** @var Request::UPDATE_* */
    protected $updateAllowTransitiveDependencies = Request::UPDATE_ONLY_LISTED;

    /**
     * @var SuggestedPackagesReporter
     */
    protected $suggestedPackagesReporter;

    /**
     * @var PlatformRequirementFilterInterface
     */
    protected $platformRequirementFilter;

    /**
     * @var ?RepositoryInterface
     */
    protected $additionalFixedRepository;

    /** @var array<string, ConstraintInterface> */
    protected $temporaryConstraints = [];

    /**
     * Constructor
     *
     * @param RootPackageInterface&BasePackage $package
     */
    public function __construct(IOInterface $io, Config $config, RootPackageInterface $package, DownloadManager $downloadManager, RepositoryManager $repositoryManager, Locker $locker, InstallationManager $installationManager, EventDispatcher $eventDispatcher, AutoloadGenerator $autoloadGenerator)
    {
        $this->io = $io;
        $this->config = $config;
        $this->package = $package;
        $this->downloadManager = $downloadManager;
        $this->repositoryManager = $repositoryManager;
        $this->locker = $locker;
        $this->installationManager = $installationManager;
        $this->eventDispatcher = $eventDispatcher;
        $this->autoloadGenerator = $autoloadGenerator;
        $this->suggestedPackagesReporter = new SuggestedPackagesReporter($this->io);
        $this->platformRequirementFilter = PlatformRequirementFilterFactory::ignoreNothing();

        $this->writeLock = $config->get('lock');
    }

    /**
     * Run installation (or update)
     *
     * @throws \Exception
     * @return int        0 on success or a positive error code on failure
     * @phpstan-return self::ERROR_*
     */
    public function run(): int
    {
        // Disable GC to save CPU cycles, as the dependency solver can create hundreds of thousands
        // of PHP objects, the GC can spend quite some time walking the tree of references looking
        // for stuff to collect while there is nothing to collect. This slows things down dramatically
        // and turning it off results in much better performance. Do not try this at home however.
        gc_collect_cycles();
        gc_disable();

        if ($this->updateAllowList !== null && $this->updateMirrors) {
            throw new \RuntimeException("The installer options updateMirrors and updateAllowList are mutually exclusive.");
        }

        $isFreshInstall = $this->repositoryManager->getLocalRepository()->isFresh();

        // Force update if there is no lock file present
        if (!$this->update && !$this->locker->isLocked()) {
            $this->io->writeError('<warning>No composer.lock file present. Updating dependencies to latest instead of installing from lock file. See https://getcomposer.org/install for more information.</warning>');
            $this->update = true;
        }

        if ($this->dryRun) {
            $this->verbose = true;
            $this->runScripts = false;
            $this->executeOperations = false;
            $this->writeLock = false;
            $this->dumpAutoloader = false;
            $this->mockLocalRepositories($this->repositoryManager);
        }

        if ($this->downloadOnly) {
            $this->dumpAutoloader = false;
        }

        if ($this->update && !$this->install) {
            $this->dumpAutoloader = false;
        }

        if ($this->runScripts) {
            Platform::putEnv('COMPOSER_DEV_MODE', $this->devMode ? '1' : '0');

            // dispatch pre event
            // should we treat this more strictly as running an update and then running an install, triggering events multiple times?
            $eventName = $this->update ? ScriptEvents::PRE_UPDATE_CMD : ScriptEvents::PRE_INSTALL_CMD;
            $this->eventDispatcher->dispatchScript($eventName, $this->devMode);
        }

        $this->downloadManager->setPreferSource($this->preferSource);
        $this->downloadManager->setPreferDist($this->preferDist);

        $localRepo = $this->repositoryManager->getLocalRepository();

        try {
            if ($this->update) {
                $res = $this->doUpdate($localRepo, $this->install);
            } else {
                $res = $this->doInstall($localRepo);
            }
            if ($res !== 0) {
                return $res;
            }
        } catch (\Exception $e) {
            if ($this->executeOperations && $this->install && $this->config->get('notify-on-install')) {
                $this->installationManager->notifyInstalls($this->io);
            }

            throw $e;
        }
        if ($this->executeOperations && $this->install && $this->config->get('notify-on-install')) {
            $this->installationManager->notifyInstalls($this->io);
        }

        if ($this->update) {
            $installedRepo = new InstalledRepository([
                $this->locker->getLockedRepository($this->devMode),
                $this->createPlatformRepo(false),
                new RootPackageRepository(clone $this->package),
            ]);
            if ($isFreshInstall) {
                $this->suggestedPackagesReporter->addSuggestionsFromPackage($this->package);
            }
            $this->suggestedPackagesReporter->outputMinimalistic($installedRepo);
        }

        // Find abandoned packages and warn user
        $lockedRepository = $this->locker->getLockedRepository(true);
        foreach ($lockedRepository->getPackages() as $package) {
            if (!$package instanceof CompletePackage || !$package->isAbandoned()) {
                continue;
            }

            $replacement = is_string($package->getReplacementPackage())
                ? 'Use ' . $package->getReplacementPackage() . ' instead'
                : 'No replacement was suggested';

            $this->io->writeError(
                sprintf(
                    "<warning>Package %s is abandoned, you should avoid using it. %s.</warning>",
                    $package->getPrettyName(),
                    $replacement
                )
            );
        }

        if ($this->dumpAutoloader) {
            // write autoloader
            if ($this->optimizeAutoloader) {
                $this->io->writeError('<info>Generating optimized autoload files</info>');
            } else {
                $this->io->writeError('<info>Generating autoload files</info>');
            }

            $this->autoloadGenerator->setClassMapAuthoritative($this->classMapAuthoritative);
            $this->autoloadGenerator->setApcu($this->apcuAutoloader, $this->apcuAutoloaderPrefix);
            $this->autoloadGenerator->setRunScripts($this->runScripts);
            $this->autoloadGenerator->setPlatformRequirementFilter($this->platformRequirementFilter);
            $this
                ->autoloadGenerator
                ->dump(
                    $this->config,
                    $localRepo,
                    $this->package,
                    $this->installationManager,
                    'composer',
                    $this->optimizeAutoloader,
                    null,
                    $this->locker
                );
        }

        if ($this->install && $this->executeOperations) {
            // force binaries re-generation in case they are missing
            foreach ($localRepo->getPackages() as $package) {
                $this->installationManager->ensureBinariesPresence($package);
            }
        }

        $fundEnv = Platform::getEnv('COMPOSER_FUND');
        $showFunding = true;
        if (is_numeric($fundEnv)) {
            $showFunding = intval($fundEnv) !== 0;
        }

        if ($showFunding) {
            $fundingCount = 0;
            foreach ($localRepo->getPackages() as $package) {
                if ($package instanceof CompletePackageInterface && !$package instanceof AliasPackage && $package->getFunding()) {
                    $fundingCount++;
                }
            }
            if ($fundingCount > 0) {
                $this->io->writeError([
                    sprintf(
                        "<info>%d package%s you are using %s looking for funding.</info>",
                        $fundingCount,
                        1 === $fundingCount ? '' : 's',
                        1 === $fundingCount ? 'is' : 'are'
                    ),
                    '<info>Use the `composer fund` command to find out more!</info>',
                ]);
            }
        }

        if ($this->runScripts) {
            // dispatch post event
            $eventName = $this->update ? ScriptEvents::POST_UPDATE_CMD : ScriptEvents::POST_INSTALL_CMD;
            $this->eventDispatcher->dispatchScript($eventName, $this->devMode);
        }

        // re-enable GC except on HHVM which triggers a warning here
        if (!defined('HHVM_VERSION')) {
            gc_enable();
        }

        if ($this->audit) {
            if ($this->update && !$this->install) {
                $packages = $lockedRepository->getCanonicalPackages();
                $target = 'locked';
            } else {
                $packages = $localRepo->getCanonicalPackages();
                $target = 'installed';
            }
            if (count($packages) > 0) {
                try {
                    $auditor = new Auditor();
                    $repoSet = new RepositorySet();
                    foreach ($this->repositoryManager->getRepositories() as $repo) {
                        $repoSet->addRepository($repo);
                    }

                    $auditConfig = $this->config->get('audit');

                    return $auditor->audit($this->io, $repoSet, $packages, $this->auditFormat, true, $auditConfig['ignore'] ?? [], $auditConfig['abandoned'] ?? Auditor::ABANDONED_FAIL) > 0 && $this->errorOnAudit ? self::ERROR_AUDIT_FAILED : 0;
                } catch (TransportException $e) {
                    $this->io->error('Failed to audit '.$target.' packages.');
                    if ($this->io->isVerbose()) {
                        $this->io->error('['.get_class($e).'] '.$e->getMessage());
                    }
                }
            } else {
                $this->io->writeError('No '.$target.' packages - skipping audit.');
            }
        }

        return 0;
    }

    /**
     * @phpstan-return self::ERROR_*
     */
    protected function doUpdate(InstalledRepositoryInterface $localRepo, bool $doInstall): int
    {
        $platformRepo = $this->createPlatformRepo(true);
        $aliases = $this->getRootAliases(true);

        $lockedRepository = null;

        try {
            if ($this->locker->isLocked()) {
                $lockedRepository = $this->locker->getLockedRepository(true);
            }
        } catch (\Seld\JsonLint\ParsingException $e) {
            if ($this->updateAllowList !== null || $this->updateMirrors) {
                // in case we are doing a partial update or updating mirrors, the lock file is needed so we error
                throw $e;
            }
            // otherwise, ignoring parse errors as the lock file will be regenerated from scratch when
            // doing a full update
        }

        if (($this->updateAllowList !== null || $this->updateMirrors) && !$lockedRepository) {
            $this->io->writeError('<error>Cannot update ' . ($this->updateMirrors ? 'lock file information' : 'only a partial set of packages') . ' without a lock file present. Run `composer update` to generate a lock file.</error>', true, IOInterface::QUIET);

            return self::ERROR_NO_LOCK_FILE_FOR_PARTIAL_UPDATE;
        }

        $this->io->writeError('<info>Loading composer repositories with package information</info>');

        // creating repository set
        $policy = $this->createPolicy(true, $lockedRepository);
        $repositorySet = $this->createRepositorySet(true, $platformRepo, $aliases);
        $repositories = $this->repositoryManager->getRepositories();
        foreach ($repositories as $repository) {
            $repositorySet->addRepository($repository);
        }
        if ($lockedRepository) {
            $repositorySet->addRepository($lockedRepository);
        }

        $request = $this->createRequest($this->fixedRootPackage, $platformRepo, $lockedRepository);
        $this->requirePackagesForUpdate($request, $lockedRepository, true);

        // pass the allow list into the request, so the pool builder can apply it
        if ($this->updateAllowList !== null) {
            $request->setUpdateAllowList($this->updateAllowList, $this->updateAllowTransitiveDependencies);
        }

        $pool = $repositorySet->createPool($request, $this->io, $this->eventDispatcher, $this->createPoolOptimizer($policy), $this->ignoredTypes, $this->allowedTypes);

        $this->io->writeError('<info>Updating dependencies</info>');

        // solve dependencies
        $solver = new Solver($policy, $pool, $this->io);
        try {
            $lockTransaction = $solver->solve($request, $this->platformRequirementFilter);
            $ruleSetSize = $solver->getRuleSetSize();
            $solver = null;
        } catch (SolverProblemsException $e) {
            $err = 'Your requirements could not be resolved to an installable set of packages.';
            $prettyProblem = $e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose());

            $this->io->writeError('<error>'. $err .'</error>', true, IOInterface::QUIET);
            $this->io->writeError($prettyProblem);
            if (!$this->devMode) {
                $this->io->writeError('<warning>Running update with --no-dev does not mean require-dev is ignored, it just means the packages will not be installed. If dev requirements are blocking the update you have to resolve those problems.</warning>', true, IOInterface::QUIET);
            }

            $ghe = new GithubActionError($this->io);
            $ghe->emit($err."\n".$prettyProblem);

            return max(self::ERROR_GENERIC_FAILURE, $e->getCode());
        }

        $this->io->writeError("Analyzed ".count($pool)." packages to resolve dependencies", true, IOInterface::VERBOSE);
        $this->io->writeError("Analyzed ".$ruleSetSize." rules to resolve dependencies", true, IOInterface::VERBOSE);

        $pool = null;

        if (!$lockTransaction->getOperations()) {
            $this->io->writeError('Nothing to modify in lock file');
        }

        $exitCode = $this->extractDevPackages($lockTransaction, $platformRepo, $aliases, $policy, $lockedRepository);
        if ($exitCode !== 0) {
            return $exitCode;
        }

        \Composer\Semver\CompilingMatcher::clear();

        // write lock
        $platformReqs = $this->extractPlatformRequirements($this->package->getRequires());
        $platformDevReqs = $this->extractPlatformRequirements($this->package->getDevRequires());

        $installsUpdates = $uninstalls = [];
        if ($lockTransaction->getOperations()) {
            $installNames = $updateNames = $uninstallNames = [];
            foreach ($lockTransaction->getOperations() as $operation) {
                if ($operation instanceof InstallOperation) {
                    $installsUpdates[] = $operation;
                    $installNames[] = $operation->getPackage()->getPrettyName().':'.$operation->getPackage()->getFullPrettyVersion();
                } elseif ($operation instanceof UpdateOperation) {
                    // when mirrors/metadata from a package gets updated we do not want to list it as an
                    // update in the output as it is only an internal lock file metadata update
                    if ($this->updateMirrors
                        && $operation->getInitialPackage()->getName() === $operation->getTargetPackage()->getName()
                        && $operation->getInitialPackage()->getVersion() === $operation->getTargetPackage()->getVersion()
                    ) {
                        continue;
                    }

                    $installsUpdates[] = $operation;
                    $updateNames[] = $operation->getTargetPackage()->getPrettyName().':'.$operation->getTargetPackage()->getFullPrettyVersion();
                } elseif ($operation instanceof UninstallOperation) {
                    $uninstalls[] = $operation;
                    $uninstallNames[] = $operation->getPackage()->getPrettyName();
                }
            }

            if ($this->config->get('lock')) {
                $this->io->writeError(sprintf(
                    "<info>Lock file operations: %d install%s, %d update%s, %d removal%s</info>",
                    count($installNames),
                    1 === count($installNames) ? '' : 's',
                    count($updateNames),
                    1 === count($updateNames) ? '' : 's',
                    count($uninstalls),
                    1 === count($uninstalls) ? '' : 's'
                ));
                if ($installNames) {
                    $this->io->writeError("Installs: ".implode(', ', $installNames), true, IOInterface::VERBOSE);
                }
                if ($updateNames) {
                    $this->io->writeError("Updates: ".implode(', ', $updateNames), true, IOInterface::VERBOSE);
                }
                if ($uninstalls) {
                    $this->io->writeError("Removals: ".implode(', ', $uninstallNames), true, IOInterface::VERBOSE);
                }
            }
        }

        $sortByName = static function ($a, $b): int {
            if ($a instanceof UpdateOperation) {
                $a = $a->getTargetPackage()->getName();
            } else {
                $a = $a->getPackage()->getName();
            }
            if ($b instanceof UpdateOperation) {
                $b = $b->getTargetPackage()->getName();
            } else {
                $b = $b->getPackage()->getName();
            }

            return strcmp($a, $b);
        };
        usort($uninstalls, $sortByName);
        usort($installsUpdates, $sortByName);

        foreach (array_merge($uninstalls, $installsUpdates) as $operation) {
            // collect suggestions
            if ($operation instanceof InstallOperation) {
                $this->suggestedPackagesReporter->addSuggestionsFromPackage($operation->getPackage());
            }

            // output op if lock file is enabled, but alias op only in debug verbosity
            if ($this->config->get('lock') && (false === strpos($operation->getOperationType(), 'Alias') || $this->io->isDebug())) {
                $sourceRepo = '';
                if ($this->io->isVeryVerbose() && false === strpos($operation->getOperationType(), 'Alias')) {
                    $operationPkg = ($operation instanceof UpdateOperation ? $operation->getTargetPackage() : $operation->getPackage());
                    if ($operationPkg->getRepository() !== null) {
                        $sourceRepo = ' from ' . $operationPkg->getRepository()->getRepoName();
                    }
                }
                $this->io->writeError('  - ' . $operation->show(true) . $sourceRepo);
            }
        }

        $updatedLock = $this->locker->setLockData(
            $lockTransaction->getNewLockPackages(false, $this->updateMirrors),
            $lockTransaction->getNewLockPackages(true, $this->updateMirrors),
            $platformReqs,
            $platformDevReqs,
            $lockTransaction->getAliases($aliases),
            $this->package->getMinimumStability(),
            $this->package->getStabilityFlags(),
            $this->preferStable || $this->package->getPreferStable(),
            $this->preferLowest,
            $this->config->get('platform') ?: [],
            $this->writeLock && $this->executeOperations
        );
        if ($updatedLock && $this->writeLock && $this->executeOperations) {
            $this->io->writeError('<info>Writing lock file</info>');
        }

        if ($doInstall) {
            // TODO ensure lock is used from locker as-is, since it may not have been written to disk in case of executeOperations == false
            return $this->doInstall($localRepo, true);
        }

        return 0;
    }

    /**
     * Run the solver a second time on top of the existing update result with only the current result set in the pool
     * and see what packages would get removed if we only had the non-dev packages in the solver request
     *
     * @param array<int, array<string, string>> $aliases
     *
     * @phpstan-param list<array{package: string, version: string, alias: string, alias_normalized: string}> $aliases
     * @phpstan-return self::ERROR_*
     */
    protected function extractDevPackages(LockTransaction $lockTransaction, PlatformRepository $platformRepo, array $aliases, PolicyInterface $policy, ?LockArrayRepository $lockedRepository = null): int
    {
        if (!$this->package->getDevRequires()) {
            return 0;
        }

        $resultRepo = new ArrayRepository([]);
        $loader = new ArrayLoader(null, true);
        $dumper = new ArrayDumper();
        foreach ($lockTransaction->getNewLockPackages(false) as $pkg) {
            $resultRepo->addPackage($loader->load($dumper->dump($pkg)));
        }

        $repositorySet = $this->createRepositorySet(true, $platformRepo, $aliases);
        $repositorySet->addRepository($resultRepo);

        $request = $this->createRequest($this->fixedRootPackage, $platformRepo);
        $this->requirePackagesForUpdate($request, $lockedRepository, false);

        $pool = $repositorySet->createPoolWithAllPackages();

        $solver = new Solver($policy, $pool, $this->io);
        try {
            $nonDevLockTransaction = $solver->solve($request, $this->platformRequirementFilter);
            $solver = null;
        } catch (SolverProblemsException $e) {
            $err = 'Unable to find a compatible set of packages based on your non-dev requirements alone.';
            $prettyProblem = $e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose(), true);

            $this->io->writeError('<error>'. $err .'</error>', true, IOInterface::QUIET);
            $this->io->writeError('Your requirements can be resolved successfully when require-dev packages are present.');
            $this->io->writeError('You may need to move packages from require-dev or some of their dependencies to require.');
            $this->io->writeError($prettyProblem);

            $ghe = new GithubActionError($this->io);
            $ghe->emit($err."\n".$prettyProblem);

            return $e->getCode();
        }

        $lockTransaction->setNonDevPackages($nonDevLockTransaction);

        return 0;
    }

    /**
     * @param  bool                         $alreadySolved Whether the function is called as part of an update command or independently
     * @return int                          exit code
     * @phpstan-return self::ERROR_*
     */
    protected function doInstall(InstalledRepositoryInterface $localRepo, bool $alreadySolved = false): int
    {
        if ($this->config->get('lock')) {
            $this->io->writeError('<info>Installing dependencies from lock file'.($this->devMode ? ' (including require-dev)' : '').'</info>');
        }

        $lockedRepository = $this->locker->getLockedRepository($this->devMode);

        // verify that the lock file works with the current platform repository
        // we can skip this part if we're doing this as the second step after an update
        if (!$alreadySolved) {
            $this->io->writeError('<info>Verifying lock file contents can be installed on current platform.</info>');

            $platformRepo = $this->createPlatformRepo(false);
            // creating repository set
            $policy = $this->createPolicy(false);
            // use aliases from lock file only, so empty root aliases here
            $repositorySet = $this->createRepositorySet(false, $platformRepo, [], $lockedRepository);
            $repositorySet->addRepository($lockedRepository);

            // creating requirements request
            $request = $this->createRequest($this->fixedRootPackage, $platformRepo, $lockedRepository);

            if (!$this->locker->isFresh()) {
                $this->io->writeError('<warning>Warning: The lock file is not up to date with the latest changes in composer.json. You may be getting outdated dependencies. It is recommended that you run `composer update` or `composer update <package name>`.</warning>', true, IOInterface::QUIET);
            }

            $missingRequirementInfo = $this->locker->getMissingRequirementInfo($this->package, $this->devMode);
            if ($missingRequirementInfo !== []) {
                $this->io->writeError($missingRequirementInfo);

                if (!$this->config->get('allow-missing-requirements')) {
                    return self::ERROR_LOCK_FILE_INVALID;
                }
            }

            foreach ($lockedRepository->getPackages() as $package) {
                $request->fixLockedPackage($package);
            }

            $rootRequires = $this->package->getRequires();
            if ($this->devMode) {
                $rootRequires = array_merge($rootRequires, $this->package->getDevRequires());
            }
            foreach ($rootRequires as $link) {
                if (PlatformRepository::isPlatformPackage($link->getTarget())) {
                    $request->requireName($link->getTarget(), $link->getConstraint());
                }
            }

            foreach ($this->locker->getPlatformRequirements($this->devMode) as $link) {
                if (!isset($rootRequires[$link->getTarget()])) {
                    $request->requireName($link->getTarget(), $link->getConstraint());
                }
            }
            unset($rootRequires, $link);

            $pool = $repositorySet->createPool($request, $this->io, $this->eventDispatcher, null, $this->ignoredTypes, $this->allowedTypes);

            // solve dependencies
            $solver = new Solver($policy, $pool, $this->io);
            try {
                $lockTransaction = $solver->solve($request, $this->platformRequirementFilter);
                $solver = null;

                // installing the locked packages on this platform resulted in lock modifying operations, there wasn't a conflict, but the lock file as-is seems to not work on this system
                if (0 !== count($lockTransaction->getOperations())) {
                    $this->io->writeError('<error>Your lock file cannot be installed on this system without changes. Please run composer update.</error>', true, IOInterface::QUIET);

                    return self::ERROR_LOCK_FILE_INVALID;
                }
            } catch (SolverProblemsException $e) {
                $err = 'Your lock file does not contain a compatible set of packages. Please run composer update.';
                $prettyProblem = $e->getPrettyString($repositorySet, $request, $pool, $this->io->isVerbose());

                $this->io->writeError('<error>'. $err .'</error>', true, IOInterface::QUIET);
                $this->io->writeError($prettyProblem);

                $ghe = new GithubActionError($this->io);
                $ghe->emit($err."\n".$prettyProblem);

                return max(self::ERROR_GENERIC_FAILURE, $e->getCode());
            }
        }

        // TODO in how far do we need to do anything here to ensure dev packages being updated to latest in lock without version change are treated correctly?
        $localRepoTransaction = new LocalRepoTransaction($lockedRepository, $localRepo);
        $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_OPERATIONS_EXEC, $this->devMode, $this->executeOperations, $localRepoTransaction);

        $installs = $updates = $uninstalls = [];
        foreach ($localRepoTransaction->getOperations() as $operation) {
            if ($operation instanceof InstallOperation) {
                $installs[] = $operation->getPackage()->getPrettyName().':'.$operation->getPackage()->getFullPrettyVersion();
            } elseif ($operation instanceof UpdateOperation) {
                $updates[] = $operation->getTargetPackage()->getPrettyName().':'.$operation->getTargetPackage()->getFullPrettyVersion();
            } elseif ($operation instanceof UninstallOperation) {
                $uninstalls[] = $operation->getPackage()->getPrettyName();
            }
        }

        if ($installs === [] && $updates === [] && $uninstalls === []) {
            $this->io->writeError('Nothing to install, update or remove');
        } else {
            $this->io->writeError(sprintf(
                "<info>Package operations: %d install%s, %d update%s, %d removal%s</info>",
                count($installs),
                1 === count($installs) ? '' : 's',
                count($updates),
                1 === count($updates) ? '' : 's',
                count($uninstalls),
                1 === count($uninstalls) ? '' : 's'
            ));
            if ($installs) {
                $this->io->writeError("Installs: ".implode(', ', $installs), true, IOInterface::VERBOSE);
            }
            if ($updates) {
                $this->io->writeError("Updates: ".implode(', ', $updates), true, IOInterface::VERBOSE);
            }
            if ($uninstalls) {
                $this->io->writeError("Removals: ".implode(', ', $uninstalls), true, IOInterface::VERBOSE);
            }
        }

        if ($this->executeOperations) {
            $localRepo->setDevPackageNames($this->locker->getDevPackageNames());
            $this->installationManager->execute($localRepo, $localRepoTransaction->getOperations(), $this->devMode, $this->runScripts, $this->downloadOnly);

            // see https://github.com/composer/composer/issues/2764
            if (count($localRepoTransaction->getOperations()) > 0) {
                $vendorDir = $this->config->get('vendor-dir');
                if (is_dir($vendorDir)) {
                    // suppress errors as this fails sometimes on OSX for no apparent reason
                    // see https://github.com/composer/composer/issues/4070#issuecomment-129792748
                    @touch($vendorDir);
                }
            }
        } else {
            foreach ($localRepoTransaction->getOperations() as $operation) {
                // output op, but alias op only in debug verbosity
                if (false === strpos($operation->getOperationType(), 'Alias') || $this->io->isDebug()) {
                    $this->io->writeError('  - ' . $operation->show(false));
                }
            }
        }

        return 0;
    }

    protected function createPlatformRepo(bool $forUpdate): PlatformRepository
    {
        if ($forUpdate) {
            $platformOverrides = $this->config->get('platform') ?: [];
        } else {
            $platformOverrides = $this->locker->getPlatformOverrides();
        }

        return new PlatformRepository([], $platformOverrides);
    }

    /**
     * @param  array<int, array<string, string>> $rootAliases
     *
     * @phpstan-param list<array{package: string, version: string, alias: string, alias_normalized: string}> $rootAliases
     */
    private function createRepositorySet(bool $forUpdate, PlatformRepository $platformRepo, array $rootAliases = [], ?RepositoryInterface $lockedRepository = null): RepositorySet
    {
        if ($forUpdate) {
            $minimumStability = $this->package->getMinimumStability();
            $stabilityFlags = $this->package->getStabilityFlags();

            $requires = array_merge($this->package->getRequires(), $this->package->getDevRequires());
        } else {
            $minimumStability = $this->locker->getMinimumStability();
            $stabilityFlags = $this->locker->getStabilityFlags();

            $requires = [];
            foreach ($lockedRepository->getPackages() as $package) {
                $constraint = new Constraint('=', $package->getVersion());
                $constraint->setPrettyString($package->getPrettyVersion());
                $requires[$package->getName()] = $constraint;
            }
        }

        $rootRequires = [];
        foreach ($requires as $req => $constraint) {
            if ($constraint instanceof Link) {
                $constraint = $constraint->getConstraint();
            }
            // skip platform requirements from the root package to avoid filtering out existing platform packages
            if ($this->platformRequirementFilter->isIgnored($req)) {
                continue;
            } elseif ($this->platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) {
                $constraint = $this->platformRequirementFilter->filterConstraint($req, $constraint);
            }
            $rootRequires[$req] = $constraint;
        }

        $this->fixedRootPackage = clone $this->package;
        $this->fixedRootPackage->setRequires([]);
        $this->fixedRootPackage->setDevRequires([]);

        $stabilityFlags[$this->package->getName()] = BasePackage::STABILITIES[VersionParser::parseStability($this->package->getVersion())];

        $repositorySet = new RepositorySet($minimumStability, $stabilityFlags, $rootAliases, $this->package->getReferences(), $rootRequires, $this->temporaryConstraints);
        $repositorySet->addRepository(new RootPackageRepository($this->fixedRootPackage));
        $repositorySet->addRepository($platformRepo);
        if ($this->additionalFixedRepository) {
            // allow using installed repos if needed to avoid warnings about installed repositories being used in the RepositorySet
            // see https://github.com/composer/composer/pull/9574
            $additionalFixedRepositories = $this->additionalFixedRepository;
            if ($additionalFixedRepositories instanceof CompositeRepository) {
                $additionalFixedRepositories = $additionalFixedRepositories->getRepositories();
            } else {
                $additionalFixedRepositories = [$additionalFixedRepositories];
            }
            foreach ($additionalFixedRepositories as $additionalFixedRepository) {
                if ($additionalFixedRepository instanceof InstalledRepository || $additionalFixedRepository instanceof InstalledRepositoryInterface) {
                    $repositorySet->allowInstalledRepositories();
                    break;
                }
            }

            $repositorySet->addRepository($this->additionalFixedRepository);
        }

        return $repositorySet;
    }

    private function createPolicy(bool $forUpdate, ?LockArrayRepository $lockedRepo = null): DefaultPolicy
    {
        $preferStable = null;
        $preferLowest = null;
        if (!$forUpdate) {
            $preferStable = $this->locker->getPreferStable();
            $preferLowest = $this->locker->getPreferLowest();
        }
        // old lock file without prefer stable/lowest will return null
        // so in this case we use the composer.json info
        if (null === $preferStable) {
            $preferStable = $this->preferStable || $this->package->getPreferStable();
        }
        if (null === $preferLowest) {
            $preferLowest = $this->preferLowest;
        }

        $preferredVersions = null;
        if ($forUpdate && $this->minimalUpdate && $this->updateAllowList !== null && $lockedRepo !== null) {
            $preferredVersions = [];
            foreach ($lockedRepo->getPackages() as $pkg) {
                if ($pkg instanceof AliasPackage || in_array($pkg->getName(), $this->updateAllowList, true)) {
                    continue;
                }
                $preferredVersions[$pkg->getName()] = $pkg->getVersion();
            }
        }

        return new DefaultPolicy($preferStable, $preferLowest, $preferredVersions);
    }

    /**
     * @param RootPackageInterface&BasePackage $rootPackage
     */
    private function createRequest(RootPackageInterface $rootPackage, PlatformRepository $platformRepo, ?LockArrayRepository $lockedRepository = null): Request
    {
        $request = new Request($lockedRepository);

        $request->fixPackage($rootPackage);
        if ($rootPackage instanceof RootAliasPackage) {
            $request->fixPackage($rootPackage->getAliasOf());
        }

        $fixedPackages = $platformRepo->getPackages();
        if ($this->additionalFixedRepository) {
            $fixedPackages = array_merge($fixedPackages, $this->additionalFixedRepository->getPackages());
        }

        // fix the version of all platform packages + additionally installed packages
        // to prevent the solver trying to remove or update those
        // TODO why not replaces?
        $provided = $rootPackage->getProvides();
        foreach ($fixedPackages as $package) {
            // skip platform packages that are provided by the root package
            if ($package->getRepository() !== $platformRepo
                || !isset($provided[$package->getName()])
                || !$provided[$package->getName()]->getConstraint()->matches(new Constraint('=', $package->getVersion()))
            ) {
                $request->fixPackage($package);
            }
        }

        return $request;
    }

    private function requirePackagesForUpdate(Request $request, ?LockArrayRepository $lockedRepository = null, bool $includeDevRequires = true): void
    {
        // if we're updating mirrors we want to keep exactly the same versions installed which are in the lock file, but we want current remote metadata
        if ($this->updateMirrors) {
            $excludedPackages = [];
            if (!$includeDevRequires) {
                $excludedPackages = array_flip($this->locker->getDevPackageNames());
            }

            foreach ($lockedRepository->getPackages() as $lockedPackage) {
                // exclude alias packages here as for root aliases, both alias and aliased are
                // present in the lock repo and we only want to require the aliased version
                if (!$lockedPackage instanceof AliasPackage && !isset($excludedPackages[$lockedPackage->getName()])) {
                    $request->requireName($lockedPackage->getName(), new Constraint('==', $lockedPackage->getVersion()));
                }
            }
        } else {
            $links = $this->package->getRequires();
            if ($includeDevRequires) {
                $links = array_merge($links, $this->package->getDevRequires());
            }
            foreach ($links as $link) {
                $request->requireName($link->getTarget(), $link->getConstraint());
            }
        }
    }

    /**
     * @return array<int, array<string, string>>
     *
     * @phpstan-return list<array{package: string, version: string, alias: string, alias_normalized: string}>
     */
    private function getRootAliases(bool $forUpdate): array
    {
        if ($forUpdate) {
            $aliases = $this->package->getAliases();
        } else {
            $aliases = $this->locker->getAliases();
        }

        return $aliases;
    }

    /**
     * @param Link[] $links
     *
     * @return array<string, string>
     */
    private function extractPlatformRequirements(array $links): array
    {
        $platformReqs = [];
        foreach ($links as $link) {
            if (PlatformRepository::isPlatformPackage($link->getTarget())) {
                $platformReqs[$link->getTarget()] = $link->getPrettyConstraint();
            }
        }

        return $platformReqs;
    }

    /**
     * Replace local repositories with InstalledArrayRepository instances
     *
     * This is to prevent any accidental modification of the existing repos on disk
     */
    private function mockLocalRepositories(RepositoryManager $rm): void
    {
        $packages = [];
        foreach ($rm->getLocalRepository()->getPackages() as $package) {
            $packages[(string) $package] = clone $package;
        }
        foreach ($packages as $key => $package) {
            if ($package instanceof AliasPackage) {
                $alias = (string) $package->getAliasOf();
                $className = get_class($package);
                $packages[$key] = new $className($packages[$alias], $package->getVersion(), $package->getPrettyVersion());
            }
        }
        $rm->setLocalRepository(
            new InstalledArrayRepository($packages)
        );
    }

    private function createPoolOptimizer(PolicyInterface $policy): ?PoolOptimizer
    {
        // Not the best architectural decision here, would need to be able
        // to configure from the outside of Installer but this is only
        // a debugging tool and should never be required in any other use case
        if ('0' === Platform::getEnv('COMPOSER_POOL_OPTIMIZER')) {
            $this->io->write('Pool Optimizer was disabled for debugging purposes.', true, IOInterface::DEBUG);

            return null;
        }

        return new PoolOptimizer($policy);
    }

    /**
     * Create Installer
     *
     * @return Installer
     */
    public static function create(IOInterface $io, Composer $composer): self
    {
        return new static(
            $io,
            $composer->getConfig(),
            $composer->getPackage(),
            $composer->getDownloadManager(),
            $composer->getRepositoryManager(),
            $composer->getLocker(),
            $composer->getInstallationManager(),
            $composer->getEventDispatcher(),
            $composer->getAutoloadGenerator()
        );
    }

    /**
     * Packages of those types are ignored, by default php-ext and php-ext-zend are ignored
     *
     * @param list<string> $types
     * @return $this
     */
    public function setIgnoredTypes(array $types): self
    {
        $this->ignoredTypes = $types;

        return $this;
    }

    /**
     * Only packages of those types are allowed if set to non-null
     *
     * @param list<string>|null $types
     * @return $this
     */
    public function setAllowedTypes(?array $types): self
    {
        $this->allowedTypes = $types;

        return $this;
    }

    /**
     * @return $this
     */
    public function setAdditionalFixedRepository(RepositoryInterface $additionalFixedRepository): self
    {
        $this->additionalFixedRepository = $additionalFixedRepository;

        return $this;
    }

    /**
     * @param array<string, ConstraintInterface> $constraints
     * @return Installer
     */
    public function setTemporaryConstraints(array $constraints): self
    {
        $this->temporaryConstraints = $constraints;

        return $this;
    }

    /**
     * Whether to run in drymode or not
     *
     * @return Installer
     */
    public function setDryRun(bool $dryRun = true): self
    {
        $this->dryRun = $dryRun;

        return $this;
    }

    /**
     * Checks, if this is a dry run (simulation mode).
     */
    public function isDryRun(): bool
    {
        return $this->dryRun;
    }

    /**
     * Whether to download only or not.
     *
     * @return Installer
     */
    public function setDownloadOnly(bool $downloadOnly = true): self
    {
        $this->downloadOnly = $downloadOnly;

        return $this;
    }

    /**
     * prefer source installation
     *
     * @return Installer
     */
    public function setPreferSource(bool $preferSource = true): self
    {
        $this->preferSource = $preferSource;

        return $this;
    }

    /**
     * prefer dist installation
     *
     * @return Installer
     */
    public function setPreferDist(bool $preferDist = true): self
    {
        $this->preferDist = $preferDist;

        return $this;
    }

    /**
     * Whether or not generated autoloader are optimized
     *
     * @return Installer
     */
    public function setOptimizeAutoloader(bool $optimizeAutoloader): self
    {
        $this->optimizeAutoloader = $optimizeAutoloader;
        if (!$this->optimizeAutoloader) {
            // Force classMapAuthoritative off when not optimizing the
            // autoloader
            $this->setClassMapAuthoritative(false);
        }

        return $this;
    }

    /**
     * Whether or not generated autoloader considers the class map
     * authoritative.
     *
     * @return Installer
     */
    public function setClassMapAuthoritative(bool $classMapAuthoritative): self
    {
        $this->classMapAuthoritative = $classMapAuthoritative;
        if ($this->classMapAuthoritative) {
            // Force optimizeAutoloader when classmap is authoritative
            $this->setOptimizeAutoloader(true);
        }

        return $this;
    }

    /**
     * Whether or not generated autoloader considers APCu caching.
     *
     * @return Installer
     */
    public function setApcuAutoloader(bool $apcuAutoloader, ?string $apcuAutoloaderPrefix = null): self
    {
        $this->apcuAutoloader = $apcuAutoloader;
        $this->apcuAutoloaderPrefix = $apcuAutoloaderPrefix;

        return $this;
    }

    /**
     * update packages
     *
     * @return Installer
     */
    public function setUpdate(bool $update): self
    {
        $this->update = $update;

        return $this;
    }

    /**
     * Allows disabling the install step after an update
     *
     * @return Installer
     */
    public function setInstall(bool $install): self
    {
        $this->install = $install;

        return $this;
    }

    /**
     * enables dev packages
     *
     * @return Installer
     */
    public function setDevMode(bool $devMode = true): self
    {
        $this->devMode = $devMode;

        return $this;
    }

    /**
     * set whether to run autoloader or not
     *
     * This is disabled implicitly when enabling dryRun
     *
     * @return Installer
     */
    public function setDumpAutoloader(bool $dumpAutoloader = true): self
    {
        $this->dumpAutoloader = $dumpAutoloader;

        return $this;
    }

    /**
     * set whether to run scripts or not
     *
     * This is disabled implicitly when enabling dryRun
     *
     * @return Installer
     * @deprecated Use setRunScripts(false) on the EventDispatcher instance being injected instead
     */
    public function setRunScripts(bool $runScripts = true): self
    {
        $this->runScripts = $runScripts;

        return $this;
    }

    /**
     * set the config instance
     *
     * @return Installer
     */
    public function setConfig(Config $config): self
    {
        $this->config = $config;

        return $this;
    }

    /**
     * run in verbose mode
     *
     * @return Installer
     */
    public function setVerbose(bool $verbose = true): self
    {
        $this->verbose = $verbose;

        return $this;
    }

    /**
     * Checks, if running in verbose mode.
     */
    public function isVerbose(): bool
    {
        return $this->verbose;
    }

    /**
     * set ignore Platform Package requirements
     *
     * If this is set to true, all platform requirements are ignored
     * If this is set to false, no platform requirements are ignored
     * If this is set to string[], those packages will be ignored
     *
     * @param  bool|string[] $ignorePlatformReqs
     *
     * @return Installer
     *
     * @deprecated use setPlatformRequirementFilter instead
     */
    public function setIgnorePlatformRequirements($ignorePlatformReqs): self
    {
        trigger_error('Installer::setIgnorePlatformRequirements is deprecated since Composer 2.2, use setPlatformRequirementFilter instead.', E_USER_DEPRECATED);

        return $this->setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs));
    }

    /**
     * @return Installer
     */
    public function setPlatformRequirementFilter(PlatformRequirementFilterInterface $platformRequirementFilter): self
    {
        $this->platformRequirementFilter = $platformRequirementFilter;

        return $this;
    }

    /**
     * Update the lock file to the exact same versions and references but use current remote metadata like URLs and mirror info
     *
     * @return Installer
     */
    public function setUpdateMirrors(bool $updateMirrors): self
    {
        $this->updateMirrors = $updateMirrors;

        return $this;
    }

    /**
     * restrict the update operation to a few packages, all other packages
     * that are already installed will be kept at their current version
     *
     * @param string[] $packages
     *
     * @return Installer
     */
    public function setUpdateAllowList(array $packages): self
    {
        if (count($packages) === 0) {
            $this->updateAllowList = null;
        } else {
            $this->updateAllowList = array_values(array_unique(array_map('strtolower', $packages)));
        }

        return $this;
    }

    /**
     * Should dependencies of packages marked for update be updated?
     *
     * Depending on the chosen constant this will either only update the directly named packages, all transitive
     * dependencies which are not root requirement or all transitive dependencies including root requirements
     *
     * @param  int       $updateAllowTransitiveDependencies One of the UPDATE_ constants on the Request class
     * @return Installer
     */
    public function setUpdateAllowTransitiveDependencies(int $updateAllowTransitiveDependencies): self
    {
        if (!in_array($updateAllowTransitiveDependencies, [Request::UPDATE_ONLY_LISTED, Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE, Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS], true)) {
            throw new \RuntimeException("Invalid value for updateAllowTransitiveDependencies supplied");
        }

        $this->updateAllowTransitiveDependencies = $updateAllowTransitiveDependencies;

        return $this;
    }

    /**
     * Should packages be preferred in a stable version when updating?
     *
     * @return Installer
     */
    public function setPreferStable(bool $preferStable = true): self
    {
        $this->preferStable = $preferStable;

        return $this;
    }

    /**
     * Should packages be preferred in a lowest version when updating?
     *
     * @return Installer
     */
    public function setPreferLowest(bool $preferLowest = true): self
    {
        $this->preferLowest = $preferLowest;

        return $this;
    }

    /**
     * Only relevant for partial updates (with setUpdateAllowList), if this is enabled currently locked versions will be preferred for packages which are not in the allowlist
     *
     * This reduces the update to
     *
     * @return Installer
     */
    public function setMinimalUpdate(bool $minimalUpdate = true): self
    {
        $this->minimalUpdate = $minimalUpdate;

        return $this;
    }

    /**
     * Should the lock file be updated when updating?
     *
     * This is disabled implicitly when enabling dryRun
     *
     * @return Installer
     */
    public function setWriteLock(bool $writeLock = true): self
    {
        $this->writeLock = $writeLock;

        return $this;
    }

    /**
     * Should the operations (package install, update and removal) be executed on disk?
     *
     * This is disabled implicitly when enabling dryRun
     *
     * @return Installer
     */
    public function setExecuteOperations(bool $executeOperations = true): self
    {
        $this->executeOperations = $executeOperations;

        return $this;
    }

    /**
     * Should an audit be run after installation is complete?
     *
     * @return Installer
     */
    public function setAudit(bool $audit): self
    {
        $this->audit = $audit;

        return $this;
    }

    /**
     * Should exit with status code 5 on audit error
     *
     * @param bool $errorOnAudit
     * @return Installer
     */
    public function setErrorOnAudit(bool $errorOnAudit): self
    {
        $this->errorOnAudit = $errorOnAudit;

        return $this;
    }

    /**
     * What format should be used for audit output?
     *
     * @param Auditor::FORMAT_* $auditFormat
     * @return Installer
     */
    public function setAuditFormat(string $auditFormat): self
    {
        $this->auditFormat = $auditFormat;

        return $this;
    }

    /**
     * Disables plugins.
     *
     * Call this if you want to ensure that third-party code never gets
     * executed. The default is to automatically install, and execute
     * custom third-party installers.
     *
     * @return Installer
     */
    public function disablePlugins(): self
    {
        $this->installationManager->disablePlugins();

        return $this;
    }

    /**
     * @return Installer
     */
    public function setSuggestedPackagesReporter(SuggestedPackagesReporter $suggestedPackagesReporter): self
    {
        $this->suggestedPackagesReporter = $suggestedPackagesReporter;

        return $this;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Closure;
use Composer\Config;
use Composer\Downloader\MaxFileSizeExceededException;
use Composer\IO\IOInterface;
use Composer\Downloader\TransportException;
use Composer\Pcre\Preg;
use Composer\Util\Http\Response;
use Composer\Util\Http\ProxyManager;

/**
 * @internal
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Nils Adermann <naderman@naderman.de>
 */
class RemoteFilesystem
{
    /** @var IOInterface */
    private $io;
    /** @var Config */
    private $config;
    /** @var string */
    private $scheme;
    /** @var int */
    private $bytesMax;
    /** @var string */
    private $originUrl;
    /** @var non-empty-string */
    private $fileUrl;
    /** @var ?string */
    private $fileName;
    /** @var bool */
    private $retry = false;
    /** @var bool */
    private $progress;
    /** @var ?int */
    private $lastProgress;
    /** @var mixed[] */
    private $options = [];
    /** @var bool */
    private $disableTls = false;
    /** @var list<string> */
    private $lastHeaders;
    /** @var bool */
    private $storeAuth = false;
    /** @var AuthHelper */
    private $authHelper;
    /** @var bool */
    private $degradedMode = false;
    /** @var int */
    private $redirects;
    /** @var int */
    private $maxRedirects = 20;

    /**
     * Constructor.
     *
     * @param IOInterface $io         The IO instance
     * @param Config      $config     The config
     * @param mixed[]     $options    The options
     * @param AuthHelper  $authHelper
     */
    public function __construct(IOInterface $io, Config $config, array $options = [], bool $disableTls = false, ?AuthHelper $authHelper = null)
    {
        $this->io = $io;

        // Setup TLS options
        // The cafile option can be set via config.json
        if ($disableTls === false) {
            $this->options = StreamContextFactory::getTlsDefaults($options, $io);
        } else {
            $this->disableTls = true;
        }

        // handle the other externally set options normally.
        $this->options = array_replace_recursive($this->options, $options);
        $this->config = $config;
        $this->authHelper = $authHelper ?? new AuthHelper($io, $config);
    }

    /**
     * Copy the remote file in local.
     *
     * @param string  $originUrl The origin URL
     * @param non-empty-string $fileUrl   The file URL
     * @param string  $fileName  the local filename
     * @param bool    $progress  Display the progression
     * @param mixed[] $options   Additional context options
     *
     * @return bool true
     */
    public function copy(string $originUrl, string $fileUrl, string $fileName, bool $progress = true, array $options = [])
    {
        return $this->get($originUrl, $fileUrl, $options, $fileName, $progress);
    }

    /**
     * Get the content.
     *
     * @param string  $originUrl The origin URL
     * @param non-empty-string $fileUrl   The file URL
     * @param bool    $progress  Display the progression
     * @param mixed[] $options   Additional context options
     *
     * @return bool|string The content
     */
    public function getContents(string $originUrl, string $fileUrl, bool $progress = true, array $options = [])
    {
        return $this->get($originUrl, $fileUrl, $options, null, $progress);
    }

    /**
     * Retrieve the options set in the constructor
     *
     * @return mixed[] Options
     */
    public function getOptions()
    {
        return $this->options;
    }

    /**
     * Merges new options
     *
     * @param  mixed[] $options
     * @return void
     */
    public function setOptions(array $options)
    {
        $this->options = array_replace_recursive($this->options, $options);
    }

    /**
     * Check is disable TLS.
     *
     * @return bool
     */
    public function isTlsDisabled()
    {
        return $this->disableTls === true;
    }

    /**
     * Returns the headers of the last request
     *
     * @return list<string>
     */
    public function getLastHeaders()
    {
        return $this->lastHeaders;
    }

    /**
     * @param  string[] $headers array of returned headers like from getLastHeaders()
     * @return int|null
     */
    public static function findStatusCode(array $headers)
    {
        $value = null;
        foreach ($headers as $header) {
            if (Preg::isMatch('{^HTTP/\S+ (\d+)}i', $header, $match)) {
                // In case of redirects, http_response_headers contains the headers of all responses
                // so we can not return directly and need to keep iterating
                $value = (int) $match[1];
            }
        }

        return $value;
    }

    /**
     * @param  string[]    $headers array of returned headers like from getLastHeaders()
     * @return string|null
     */
    public function findStatusMessage(array $headers)
    {
        $value = null;
        foreach ($headers as $header) {
            if (Preg::isMatch('{^HTTP/\S+ \d+}i', $header)) {
                // In case of redirects, http_response_headers contains the headers of all responses
                // so we can not return directly and need to keep iterating
                $value = $header;
            }
        }

        return $value;
    }

    /**
     * Get file content or copy action.
     *
     * @param string  $originUrl         The origin URL
     * @param non-empty-string $fileUrl  The file URL
     * @param mixed[] $additionalOptions context options
     * @param string  $fileName          the local filename
     * @param bool    $progress          Display the progression
     *
     * @throws TransportException|\Exception
     * @throws TransportException            When the file could not be downloaded
     *
     * @return bool|string
     */
    protected function get(string $originUrl, string $fileUrl, array $additionalOptions = [], ?string $fileName = null, bool $progress = true)
    {
        $this->scheme = parse_url(strtr($fileUrl, '\\', '/'), PHP_URL_SCHEME);
        $this->bytesMax = 0;
        $this->originUrl = $originUrl;
        $this->fileUrl = $fileUrl;
        $this->fileName = $fileName;
        $this->progress = $progress;
        $this->lastProgress = null;
        $retryAuthFailure = true;
        $this->lastHeaders = [];
        $this->redirects = 1; // The first request counts.

        $tempAdditionalOptions = $additionalOptions;
        if (isset($tempAdditionalOptions['retry-auth-failure'])) {
            $retryAuthFailure = (bool) $tempAdditionalOptions['retry-auth-failure'];

            unset($tempAdditionalOptions['retry-auth-failure']);
        }

        $isRedirect = false;
        if (isset($tempAdditionalOptions['redirects'])) {
            $this->redirects = $tempAdditionalOptions['redirects'];
            $isRedirect = true;

            unset($tempAdditionalOptions['redirects']);
        }

        $options = $this->getOptionsForUrl($originUrl, $tempAdditionalOptions);
        unset($tempAdditionalOptions);

        $origFileUrl = $fileUrl;

        if (isset($options['prevent_ip_access_callable'])) {
            throw new \RuntimeException("RemoteFilesystem doesn't support the 'prevent_ip_access_callable' config.");
        }

        if (isset($options['gitlab-token'])) {
            $fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token='.$options['gitlab-token'];
            unset($options['gitlab-token']);
        }

        if (isset($options['http'])) {
            $options['http']['ignore_errors'] = true;
        }

        if ($this->degradedMode && strpos($fileUrl, 'http://repo.packagist.org/') === 0) {
            // access packagist using the resolved IPv4 instead of the hostname to force IPv4 protocol
            $fileUrl = 'http://' . gethostbyname('repo.packagist.org') . substr($fileUrl, 20);
            $degradedPackagist = true;
        }

        $maxFileSize = null;
        if (isset($options['max_file_size'])) {
            $maxFileSize = $options['max_file_size'];
            unset($options['max_file_size']);
        }

        $ctx = StreamContextFactory::getContext($fileUrl, $options, ['notification' => Closure::fromCallable([$this, 'callbackGet'])]);

        $proxy = ProxyManager::getInstance()->getProxyForRequest($fileUrl);
        $usingProxy = $proxy->getStatus(' using proxy (%s)');
        $this->io->writeError((strpos($origFileUrl, 'http') === 0 ? 'Downloading ' : 'Reading ') . Url::sanitize($origFileUrl) . $usingProxy, true, IOInterface::DEBUG);
        unset($origFileUrl, $proxy, $usingProxy);

        // Check for secure HTTP, but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256
        if ((!Preg::isMatch('{^http://(repo\.)?packagist\.org/p/}', $fileUrl) || (false === strpos($fileUrl, '$') && false === strpos($fileUrl, '%24'))) && empty($degradedPackagist)) {
            $this->config->prohibitUrlByConfig($fileUrl, $this->io);
        }

        if ($this->progress && !$isRedirect) {
            $this->io->writeError("Downloading (<comment>connecting...</comment>)", false);
        }

        $errorMessage = '';
        $errorCode = 0;
        $result = false;
        set_error_handler(static function ($code, $msg) use (&$errorMessage): bool {
            if ($errorMessage) {
                $errorMessage .= "\n";
            }
            $errorMessage .= Preg::replace('{^file_get_contents\(.*?\): }', '', $msg);

            return true;
        });
        $http_response_header = [];
        try {
            $result = $this->getRemoteContents($originUrl, $fileUrl, $ctx, $http_response_header, $maxFileSize);

            if (!empty($http_response_header[0])) {
                $statusCode = self::findStatusCode($http_response_header);
                if ($statusCode >= 300 && Response::findHeaderValue($http_response_header, 'content-type') === 'application/json') {
                    HttpDownloader::outputWarnings($this->io, $originUrl, json_decode($result, true));
                }

                if (in_array($statusCode, [401, 403]) && $retryAuthFailure) {
                    $this->promptAuthAndRetry($statusCode, $this->findStatusMessage($http_response_header), $http_response_header);
                }
            }

            $contentLength = !empty($http_response_header[0]) ? Response::findHeaderValue($http_response_header, 'content-length') : null;
            if ($contentLength && Platform::strlen($result) < $contentLength) {
                // alas, this is not possible via the stream callback because STREAM_NOTIFY_COMPLETED is documented, but not implemented anywhere in PHP
                $e = new TransportException('Content-Length mismatch, received '.Platform::strlen($result).' bytes out of the expected '.$contentLength);
                $e->setHeaders($http_response_header);
                $e->setStatusCode(self::findStatusCode($http_response_header));
                try {
                    $e->setResponse($this->decodeResult($result, $http_response_header));
                } catch (\Exception $discarded) {
                    $e->setResponse($this->normalizeResult($result));
                }

                $this->io->writeError('Content-Length mismatch, received '.Platform::strlen($result).' out of '.$contentLength.' bytes: (' . base64_encode($result).')', true, IOInterface::DEBUG);

                throw $e;
            }
        } catch (\Exception $e) {
            if ($e instanceof TransportException && !empty($http_response_header[0])) {
                $e->setHeaders($http_response_header);
                $e->setStatusCode(self::findStatusCode($http_response_header));
            }
            if ($e instanceof TransportException && $result !== false) {
                $e->setResponse($this->decodeResult($result, $http_response_header));
            }
            $result = false;
        }
        if ($errorMessage && !filter_var(ini_get('allow_url_fopen'), FILTER_VALIDATE_BOOLEAN)) {
            $errorMessage = 'allow_url_fopen must be enabled in php.ini ('.$errorMessage.')';
        }
        restore_error_handler();
        if (isset($e) && !$this->retry) {
            if (!$this->degradedMode && false !== strpos($e->getMessage(), 'Operation timed out')) {
                $this->degradedMode = true;
                $this->io->writeError('');
                $this->io->writeError([
                    '<error>'.$e->getMessage().'</error>',
                    '<error>Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info</error>',
                ]);

                return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress);
            }

            throw $e;
        }

        $statusCode = null;
        $contentType = null;
        $locationHeader = null;
        if (!empty($http_response_header[0])) {
            $statusCode = self::findStatusCode($http_response_header);
            $contentType = Response::findHeaderValue($http_response_header, 'content-type');
            $locationHeader = Response::findHeaderValue($http_response_header, 'location');
        }

        // check for bitbucket login page asking to authenticate
        if ($originUrl === 'bitbucket.org'
            && !$this->authHelper->isPublicBitBucketDownload($fileUrl)
            && substr($fileUrl, -4) === '.zip'
            && (!$locationHeader || substr(parse_url($locationHeader, PHP_URL_PATH), -4) !== '.zip')
            && $contentType && Preg::isMatch('{^text/html\b}i', $contentType)
        ) {
            $result = false;
            if ($retryAuthFailure) {
                $this->promptAuthAndRetry(401);
            }
        }

        // check for gitlab 404 when downloading archives
        if ($statusCode === 404
            && in_array($originUrl, $this->config->get('gitlab-domains'), true)
            && false !== strpos($fileUrl, 'archive.zip')
        ) {
            $result = false;
            if ($retryAuthFailure) {
                $this->promptAuthAndRetry(401);
            }
        }

        // handle 3xx redirects, 304 Not Modified is excluded
        $hasFollowedRedirect = false;
        if ($statusCode >= 300 && $statusCode <= 399 && $statusCode !== 304 && $this->redirects < $this->maxRedirects) {
            $hasFollowedRedirect = true;
            $result = $this->handleRedirect($http_response_header, $additionalOptions, $result);
        }

        // fail 4xx and 5xx responses and capture the response
        if ($statusCode && $statusCode >= 400 && $statusCode <= 599) {
            if (!$this->retry) {
                if ($this->progress && !$isRedirect) {
                    $this->io->overwriteError("Downloading (<error>failed</error>)", false);
                }

                $e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded ('.$http_response_header[0].')', $statusCode);
                $e->setHeaders($http_response_header);
                $e->setResponse($this->decodeResult($result, $http_response_header));
                $e->setStatusCode($statusCode);
                throw $e;
            }
            $result = false;
        }

        if ($this->progress && !$this->retry && !$isRedirect) {
            $this->io->overwriteError("Downloading (".($result === false ? '<error>failed</error>' : '<comment>100%</comment>').")", false);
        }

        // decode gzip
        if ($result && extension_loaded('zlib') && strpos($fileUrl, 'http') === 0 && !$hasFollowedRedirect) {
            try {
                $result = $this->decodeResult($result, $http_response_header);
            } catch (\Exception $e) {
                if ($this->degradedMode) {
                    throw $e;
                }

                $this->degradedMode = true;
                $this->io->writeError([
                    '',
                    '<error>Failed to decode response: '.$e->getMessage().'</error>',
                    '<error>Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info</error>',
                ]);

                return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress);
            }
        }

        // handle copy command if download was successful
        if (false !== $result && null !== $fileName && !$isRedirect) {
            if ('' === $result) {
                throw new TransportException('"'.$this->fileUrl.'" appears broken, and returned an empty 200 response');
            }

            $errorMessage = '';
            set_error_handler(static function ($code, $msg) use (&$errorMessage): bool {
                if ($errorMessage) {
                    $errorMessage .= "\n";
                }
                $errorMessage .= Preg::replace('{^file_put_contents\(.*?\): }', '', $msg);

                return true;
            });
            $result = (bool) file_put_contents($fileName, $result);
            restore_error_handler();
            if (false === $result) {
                throw new TransportException('The "'.$this->fileUrl.'" file could not be written to '.$fileName.': '.$errorMessage);
            }
        }

        if ($this->retry) {
            $this->retry = false;

            $result = $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress);

            if ($this->storeAuth) {
                $this->authHelper->storeAuth($this->originUrl, $this->storeAuth);
                $this->storeAuth = false;
            }

            return $result;
        }

        if (false === $result) {
            $e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded: '.$errorMessage, $errorCode);
            if (!empty($http_response_header[0])) {
                $e->setHeaders($http_response_header);
            }

            if (!$this->degradedMode && false !== strpos($e->getMessage(), 'Operation timed out')) {
                $this->degradedMode = true;
                $this->io->writeError('');
                $this->io->writeError([
                    '<error>'.$e->getMessage().'</error>',
                    '<error>Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info</error>',
                ]);

                return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress);
            }

            throw $e;
        }

        if (!empty($http_response_header[0])) {
            $this->lastHeaders = $http_response_header;
        }

        return $result;
    }

    /**
     * Get contents of remote URL.
     *
     * @param string   $originUrl   The origin URL
     * @param string   $fileUrl     The file URL
     * @param resource $context     The stream context
     * @param string[] $responseHeaders
     * @param int      $maxFileSize The maximum allowed file size
     *
     * @return string|false The response contents or false on failure
     *
     * @param-out list<string> $responseHeaders
     */
    protected function getRemoteContents(string $originUrl, string $fileUrl, $context, ?array &$responseHeaders = null, ?int $maxFileSize = null)
    {
        $result = false;

        try {
            $e = null;
            if ($maxFileSize !== null) {
                $result = file_get_contents($fileUrl, false, $context, 0, $maxFileSize);
            } else {
                // passing `null` to file_get_contents will convert `null` to `0` and return 0 bytes
                $result = file_get_contents($fileUrl, false, $context);
            }
        } catch (\Throwable $e) {
        }

        if ($result !== false && $maxFileSize !== null && Platform::strlen($result) >= $maxFileSize) {
            throw new MaxFileSizeExceededException('Maximum allowed download size reached. Downloaded ' . Platform::strlen($result) . ' of allowed ' .  $maxFileSize . ' bytes');
        }

        // https://www.php.net/manual/en/reserved.variables.httpresponseheader.php
        if (\PHP_VERSION_ID >= 80400) {
            $responseHeaders = http_get_last_response_headers() ?? [];
            http_clear_last_response_headers();
        } else {
            $responseHeaders = $http_response_header ?? [];
        }

        if (null !== $e) {
            throw $e;
        }

        return $result;
    }

    /**
     * Get notification action.
     *
     * @param int    $notificationCode The notification code
     * @param int    $severity         The severity level
     * @param string $message          The message
     * @param int    $messageCode      The message code
     * @param int    $bytesTransferred The loaded size
     * @param int    $bytesMax         The total size
     *
     * @return void
     *
     * @throws TransportException
     */
    protected function callbackGet(int $notificationCode, int $severity, ?string $message, int $messageCode, int $bytesTransferred, int $bytesMax)
    {
        switch ($notificationCode) {
            case STREAM_NOTIFY_FAILURE:
                if (400 === $messageCode) {
                    // This might happen if your host is secured by ssl client certificate authentication
                    // but you do not send an appropriate certificate
                    throw new TransportException("The '" . $this->fileUrl . "' URL could not be accessed: " . $message, $messageCode);
                }
                break;

            case STREAM_NOTIFY_FILE_SIZE_IS:
                $this->bytesMax = $bytesMax;
                break;

            case STREAM_NOTIFY_PROGRESS:
                if ($this->bytesMax > 0 && $this->progress) {
                    $progression = min(100, (int) round($bytesTransferred / $this->bytesMax * 100));

                    if ((0 === $progression % 5) && 100 !== $progression && $progression !== $this->lastProgress) {
                        $this->lastProgress = $progression;
                        $this->io->overwriteError("Downloading (<comment>$progression%</comment>)", false);
                    }
                }
                break;

            default:
                break;
        }
    }

    /**
     * @param positive-int $httpStatus
     * @param string[]     $headers
     *
     * @return void
     */
    protected function promptAuthAndRetry($httpStatus, ?string $reason = null, array $headers = [])
    {
        $result = $this->authHelper->promptAuthIfNeeded($this->fileUrl, $this->originUrl, $httpStatus, $reason, $headers, 1 /** always pass 1 as RemoteFilesystem is single threaded there is no race condition possible */);

        $this->storeAuth = $result['storeAuth'];
        $this->retry = $result['retry'];

        if ($this->retry) {
            throw new TransportException('RETRY');
        }
    }

    /**
     * @param mixed[] $additionalOptions
     *
     * @return mixed[]
     */
    protected function getOptionsForUrl(string $originUrl, array $additionalOptions)
    {
        $tlsOptions = [];
        $headers = [];

        if (extension_loaded('zlib')) {
            $headers[] = 'Accept-Encoding: gzip';
        }

        $options = array_replace_recursive($this->options, $tlsOptions, $additionalOptions);
        if (!$this->degradedMode) {
            // degraded mode disables HTTP/1.1 which causes issues with some bad
            // proxies/software due to the use of chunked encoding
            $options['http']['protocol_version'] = 1.1;
            $headers[] = 'Connection: close';
        }

        $headers = $this->authHelper->addAuthenticationHeader($headers, $originUrl, $this->fileUrl);

        $options['http']['follow_location'] = 0;

        if (isset($options['http']['header']) && !is_array($options['http']['header'])) {
            $options['http']['header'] = explode("\r\n", trim($options['http']['header'], "\r\n"));
        }
        foreach ($headers as $header) {
            $options['http']['header'][] = $header;
        }

        return $options;
    }

    /**
     * @param string[]     $http_response_header
     * @param mixed[]      $additionalOptions
     * @param string|false $result
     *
     * @return bool|string
     */
    private function handleRedirect(array $http_response_header, array $additionalOptions, $result)
    {
        if ($locationHeader = Response::findHeaderValue($http_response_header, 'location')) {
            if (parse_url($locationHeader, PHP_URL_SCHEME)) {
                // Absolute URL; e.g. https://example.com/composer
                $targetUrl = $locationHeader;
            } elseif (parse_url($locationHeader, PHP_URL_HOST)) {
                // Scheme relative; e.g. //example.com/foo
                $targetUrl = $this->scheme.':'.$locationHeader;
            } elseif ('/' === $locationHeader[0]) {
                // Absolute path; e.g. /foo
                $urlHost = parse_url($this->fileUrl, PHP_URL_HOST);

                // Replace path using hostname as an anchor.
                $targetUrl = Preg::replace('{^(.+(?://|@)'.preg_quote($urlHost).'(?::\d+)?)(?:[/\?].*)?$}', '\1'.$locationHeader, $this->fileUrl);
            } else {
                // Relative path; e.g. foo
                // This actually differs from PHP which seems to add duplicate slashes.
                $targetUrl = Preg::replace('{^(.+/)[^/?]*(?:\?.*)?$}', '\1'.$locationHeader, $this->fileUrl);
            }
        }

        if (!empty($targetUrl)) {
            $this->redirects++;

            $this->io->writeError('', true, IOInterface::DEBUG);
            $this->io->writeError(sprintf('Following redirect (%u) %s', $this->redirects, Url::sanitize($targetUrl)), true, IOInterface::DEBUG);

            $additionalOptions['redirects'] = $this->redirects;

            return $this->get(parse_url($targetUrl, PHP_URL_HOST), $targetUrl, $additionalOptions, $this->fileName, $this->progress);
        }

        if (!$this->retry) {
            $e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded, got redirect without Location ('.$http_response_header[0].')');
            $e->setHeaders($http_response_header);
            $e->setResponse($this->decodeResult($result, $http_response_header));

            throw $e;
        }

        return false;
    }

    /**
     * @param string|false $result
     * @param string[]     $http_response_header
     */
    private function decodeResult($result, array $http_response_header): ?string
    {
        // decode gzip
        if ($result && extension_loaded('zlib')) {
            $contentEncoding = Response::findHeaderValue($http_response_header, 'content-encoding');
            $decode = $contentEncoding && 'gzip' === strtolower($contentEncoding);

            if ($decode) {
                $result = zlib_decode($result);

                if ($result === false) {
                    throw new TransportException('Failed to decode zlib stream');
                }
            }
        }

        return $this->normalizeResult($result);
    }

    /**
     * @param string|false $result
     */
    private function normalizeResult($result): ?string
    {
        if ($result === false) {
            return null;
        }

        return $result;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Pcre\Preg;
use ErrorException;
use React\Promise\PromiseInterface;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Finder\Finder;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class Filesystem
{
    /** @var ?ProcessExecutor */
    private $processExecutor;

    public function __construct(?ProcessExecutor $executor = null)
    {
        $this->processExecutor = $executor;
    }

    /**
     * @return bool
     */
    public function remove(string $file)
    {
        if (is_dir($file)) {
            return $this->removeDirectory($file);
        }

        if (file_exists($file)) {
            return $this->unlink($file);
        }

        return false;
    }

    /**
     * Checks if a directory is empty
     *
     * @return bool
     */
    public function isDirEmpty(string $dir)
    {
        $finder = Finder::create()
            ->ignoreVCS(false)
            ->ignoreDotFiles(false)
            ->depth(0)
            ->in($dir);

        return \count($finder) === 0;
    }

    /**
     * @return void
     */
    public function emptyDirectory(string $dir, bool $ensureDirectoryExists = true)
    {
        if (is_link($dir) && file_exists($dir)) {
            $this->unlink($dir);
        }

        if ($ensureDirectoryExists) {
            $this->ensureDirectoryExists($dir);
        }

        if (is_dir($dir)) {
            $finder = Finder::create()
                ->ignoreVCS(false)
                ->ignoreDotFiles(false)
                ->depth(0)
                ->in($dir);

            foreach ($finder as $path) {
                $this->remove((string) $path);
            }
        }
    }

    /**
     * Recursively remove a directory
     *
     * Uses the process component if proc_open is enabled on the PHP
     * installation.
     *
     * @throws \RuntimeException
     * @return bool
     */
    public function removeDirectory(string $directory)
    {
        $edgeCaseResult = $this->removeEdgeCases($directory);
        if ($edgeCaseResult !== null) {
            return $edgeCaseResult;
        }

        if (Platform::isWindows()) {
            $cmd = ['rmdir', '/S', '/Q', Platform::realpath($directory)];
        } else {
            $cmd = ['rm', '-rf', $directory];
        }

        $result = $this->getProcess()->execute($cmd, $output) === 0;

        // clear stat cache because external processes aren't tracked by the php stat cache
        clearstatcache();

        if ($result && !is_dir($directory)) {
            return true;
        }

        return $this->removeDirectoryPhp($directory);
    }

    /**
     * Recursively remove a directory asynchronously
     *
     * Uses the process component if proc_open is enabled on the PHP
     * installation.
     *
     * @throws \RuntimeException
     * @return PromiseInterface
     * @phpstan-return PromiseInterface<bool>
     */
    public function removeDirectoryAsync(string $directory)
    {
        $edgeCaseResult = $this->removeEdgeCases($directory);
        if ($edgeCaseResult !== null) {
            return \React\Promise\resolve($edgeCaseResult);
        }

        if (Platform::isWindows()) {
            $cmd = ['rmdir', '/S', '/Q', Platform::realpath($directory)];
        } else {
            $cmd = ['rm', '-rf', $directory];
        }

        $promise = $this->getProcess()->executeAsync($cmd);

        return $promise->then(function ($process) use ($directory) {
            // clear stat cache because external processes aren't tracked by the php stat cache
            clearstatcache();

            if ($process->isSuccessful()) {
                if (!is_dir($directory)) {
                    return \React\Promise\resolve(true);
                }
            }

            return \React\Promise\resolve($this->removeDirectoryPhp($directory));
        });
    }

    /**
     * @return bool|null Returns null, when no edge case was hit. Otherwise a bool whether removal was successful
     */
    private function removeEdgeCases(string $directory, bool $fallbackToPhp = true): ?bool
    {
        if ($this->isSymlinkedDirectory($directory)) {
            return $this->unlinkSymlinkedDirectory($directory);
        }

        if ($this->isJunction($directory)) {
            return $this->removeJunction($directory);
        }

        if (is_link($directory)) {
            return unlink($directory);
        }

        if (!is_dir($directory) || !file_exists($directory)) {
            return true;
        }

        if (Preg::isMatch('{^(?:[a-z]:)?[/\\\\]+$}i', $directory)) {
            throw new \RuntimeException('Aborting an attempted deletion of '.$directory.', this was probably not intended, if it is a real use case please report it.');
        }

        if (!\function_exists('proc_open') && $fallbackToPhp) {
            return $this->removeDirectoryPhp($directory);
        }

        return null;
    }

    /**
     * Recursively delete directory using PHP iterators.
     *
     * Uses a CHILD_FIRST RecursiveIteratorIterator to sort files
     * before directories, creating a single non-recursive loop
     * to delete files/directories in the correct order.
     *
     * @return bool
     */
    public function removeDirectoryPhp(string $directory)
    {
        $edgeCaseResult = $this->removeEdgeCases($directory, false);
        if ($edgeCaseResult !== null) {
            return $edgeCaseResult;
        }

        try {
            $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS);
        } catch (\UnexpectedValueException $e) {
            // re-try once after clearing the stat cache if it failed as it
            // sometimes fails without apparent reason, see https://github.com/composer/composer/issues/4009
            clearstatcache();
            usleep(100000);
            if (!is_dir($directory)) {
                return true;
            }
            $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS);
        }
        $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);

        foreach ($ri as $file) {
            if ($file->isDir()) {
                $this->rmdir($file->getPathname());
            } else {
                $this->unlink($file->getPathname());
            }
        }

        // release locks on the directory, see https://github.com/composer/composer/issues/9945
        unset($ri, $it, $file);

        return $this->rmdir($directory);
    }

    /**
     * @return void
     */
    public function ensureDirectoryExists(string $directory)
    {
        if (!is_dir($directory)) {
            if (file_exists($directory)) {
                throw new \RuntimeException(
                    $directory.' exists and is not a directory.'
                );
            }

            if (is_link($directory) && !@$this->unlinkImplementation($directory)) {
                throw new \RuntimeException('Could not delete symbolic link '.$directory.': '.(error_get_last()['message'] ?? ''));
            }

            if (!@mkdir($directory, 0777, true)) {
                $e = new \RuntimeException($directory.' does not exist and could not be created: '.(error_get_last()['message'] ?? ''));

                // in pathological cases with paths like path/to/broken-symlink/../foo is_dir will fail to detect path/to/foo
                // but normalizing the ../ away first makes it work so we attempt this just in case, and if it still fails we
                // report the initial error we had with the original path, and ignore the normalized path exception
                // see https://github.com/composer/composer/issues/11864
                $normalized = $this->normalizePath($directory);
                if ($normalized !== $directory) {
                    try {
                        $this->ensureDirectoryExists($normalized);
                        return;
                    } catch (\Throwable $ignoredEx) {}
                }

                throw $e;
            }
        }
    }

    /**
     * Attempts to unlink a file and in case of failure retries after 350ms on windows
     *
     * @throws \RuntimeException
     * @return bool
     */
    public function unlink(string $path)
    {
        $unlinked = @$this->unlinkImplementation($path);
        if (!$unlinked) {
            // retry after a bit on windows since it tends to be touchy with mass removals
            if (Platform::isWindows()) {
                usleep(350000);
                $unlinked = @$this->unlinkImplementation($path);
            }

            if (!$unlinked) {
                $error = error_get_last();
                $message = 'Could not delete '.$path.': ' . ($error['message'] ?? '');
                if (Platform::isWindows()) {
                    $message .= "\nThis can be due to an antivirus or the Windows Search Indexer locking the file while they are analyzed";
                }

                throw new \RuntimeException($message);
            }
        }

        return true;
    }

    /**
     * Attempts to rmdir a file and in case of failure retries after 350ms on windows
     *
     * @throws \RuntimeException
     * @return bool
     */
    public function rmdir(string $path)
    {
        $deleted = @rmdir($path);
        if (!$deleted) {
            // retry after a bit on windows since it tends to be touchy with mass removals
            if (Platform::isWindows()) {
                usleep(350000);
                $deleted = @rmdir($path);
            }

            if (!$deleted) {
                $error = error_get_last();
                $message = 'Could not delete '.$path.': ' . ($error['message'] ?? '');
                if (Platform::isWindows()) {
                    $message .= "\nThis can be due to an antivirus or the Windows Search Indexer locking the file while they are analyzed";
                }

                throw new \RuntimeException($message);
            }
        }

        return true;
    }

    /**
     * Copy then delete is a non-atomic version of {@link rename}.
     *
     * Some systems can't rename and also don't have proc_open,
     * which requires this solution.
     *
     * @return void
     */
    public function copyThenRemove(string $source, string $target)
    {
        $this->copy($source, $target);
        if (!is_dir($source)) {
            $this->unlink($source);

            return;
        }

        $this->removeDirectoryPhp($source);
    }

    /**
     * Copies a file or directory from $source to $target.
     *
     * @return bool
     */
    public function copy(string $source, string $target)
    {
        // refs https://github.com/composer/composer/issues/11864
        $target = $this->normalizePath($target);

        if (!is_dir($source)) {
            try {
                return copy($source, $target);
            } catch (ErrorException $e) {
                // if copy fails we attempt to copy it manually as this can help bypass issues with VirtualBox shared folders
                // see https://github.com/composer/composer/issues/12057
                if (str_contains($e->getMessage(), 'Bad address')) {
                    $sourceHandle = fopen($source, 'r');
                    $targetHandle = fopen($target, 'w');
                    if (false === $sourceHandle || false === $targetHandle) {
                        throw $e;
                    }
                    while (!feof($sourceHandle)) {
                        if (false === fwrite($targetHandle, (string) fread($sourceHandle, 1024 * 1024))) {
                            throw $e;
                        }
                    }
                    fclose($sourceHandle);
                    fclose($targetHandle);

                    return true;
                }
                throw $e;
            }
        }

        $it = new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS);
        $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::SELF_FIRST);
        $this->ensureDirectoryExists($target);

        $result = true;
        foreach ($ri as $file) {
            $targetPath = $target . DIRECTORY_SEPARATOR . $ri->getSubPathname();
            if ($file->isDir()) {
                $this->ensureDirectoryExists($targetPath);
            } else {
                $result = $result && copy($file->getPathname(), $targetPath);
            }
        }

        return $result;
    }

    /**
     * @return void
     */
    public function rename(string $source, string $target)
    {
        if (true === @rename($source, $target)) {
            return;
        }

        if (!\function_exists('proc_open')) {
            $this->copyThenRemove($source, $target);

            return;
        }

        if (Platform::isWindows()) {
            // Try to copy & delete - this is a workaround for random "Access denied" errors.
            $result = $this->getProcess()->execute(['xcopy', $source, $target, '/E', '/I', '/Q', '/Y'], $output);

            // clear stat cache because external processes aren't tracked by the php stat cache
            clearstatcache();

            if (0 === $result) {
                $this->remove($source);

                return;
            }
        } else {
            // We do not use PHP's "rename" function here since it does not support
            // the case where $source, and $target are located on different partitions.
            $result = $this->getProcess()->execute(['mv', $source, $target], $output);

            // clear stat cache because external processes aren't tracked by the php stat cache
            clearstatcache();

            if (0 === $result) {
                return;
            }
        }

        $this->copyThenRemove($source, $target);
    }

    /**
     * Returns the shortest path from $from to $to
     *
     * @param  bool                      $directories if true, the source/target are considered to be directories
     * @param  bool                      $preferRelative if true, relative paths will be preferred even if longer
     * @throws \InvalidArgumentException
     * @return string
     */
    public function findShortestPath(string $from, string $to, bool $directories = false, bool $preferRelative = false)
    {
        if (!$this->isAbsolutePath($from) || !$this->isAbsolutePath($to)) {
            throw new \InvalidArgumentException(sprintf('$from (%s) and $to (%s) must be absolute paths.', $from, $to));
        }

        $from = $this->normalizePath($from);
        $to = $this->normalizePath($to);

        if ($directories) {
            $from = rtrim($from, '/') . '/dummy_file';
        }

        if (\dirname($from) === \dirname($to)) {
            return './'.basename($to);
        }

        $commonPath = $to;
        while (strpos($from.'/', $commonPath.'/') !== 0 && '/' !== $commonPath && !Preg::isMatch('{^[A-Z]:/?$}i', $commonPath)) {
            $commonPath = strtr(\dirname($commonPath), '\\', '/');
        }

        // no commonality at all
        if (0 !== strpos($from, $commonPath)) {
            return $to;
        }

        $commonPath = rtrim($commonPath, '/') . '/';
        $sourcePathDepth = substr_count((string) substr($from, \strlen($commonPath)), '/');
        $commonPathCode = str_repeat('../', $sourcePathDepth);

        // allow top level /foo & /bar dirs to be addressed relatively as this is common in Docker setups
        if (!$preferRelative && '/' === $commonPath && $sourcePathDepth > 1) {
            return $to;
        }

        $result = $commonPathCode . substr($to, \strlen($commonPath));
        if (\strlen($result) === 0) {
            return './';
        }

        return $result;
    }

    /**
     * Returns PHP code that, when executed in $from, will return the path to $to
     *
     * @param  bool                      $directories if true, the source/target are considered to be directories
     * @param  bool                      $preferRelative if true, relative paths will be preferred even if longer
     * @throws \InvalidArgumentException
     * @return string
     */
    public function findShortestPathCode(string $from, string $to, bool $directories = false, bool $staticCode = false, bool $preferRelative = false)
    {
        if (!$this->isAbsolutePath($from) || !$this->isAbsolutePath($to)) {
            throw new \InvalidArgumentException(sprintf('$from (%s) and $to (%s) must be absolute paths.', $from, $to));
        }

        $from = $this->normalizePath($from);
        $to = $this->normalizePath($to);

        if ($from === $to) {
            return $directories ? '__DIR__' : '__FILE__';
        }

        $commonPath = $to;
        while (strpos($from.'/', $commonPath.'/') !== 0 && '/' !== $commonPath && !Preg::isMatch('{^[A-Z]:/?$}i', $commonPath) && '.' !== $commonPath) {
            $commonPath = strtr(\dirname($commonPath), '\\', '/');
        }

        // no commonality at all
        if (0 !== strpos($from, $commonPath) || '.' === $commonPath) {
            return var_export($to, true);
        }

        $commonPath = rtrim($commonPath, '/') . '/';
        if (str_starts_with($to, $from.'/')) {
            return '__DIR__ . '.var_export((string) substr($to, \strlen($from)), true);
        }
        $sourcePathDepth = substr_count((string) substr($from, \strlen($commonPath)), '/') + (int) $directories;

        // allow top level /foo & /bar dirs to be addressed relatively as this is common in Docker setups
        if (!$preferRelative && '/' === $commonPath && $sourcePathDepth > 1) {
            return var_export($to, true);
        }

        if ($staticCode) {
            $commonPathCode = "__DIR__ . '".str_repeat('/..', $sourcePathDepth)."'";
        } else {
            $commonPathCode = str_repeat('dirname(', $sourcePathDepth).'__DIR__'.str_repeat(')', $sourcePathDepth);
        }
        $relTarget = (string) substr($to, \strlen($commonPath));

        return $commonPathCode . (\strlen($relTarget) > 0 ? '.' . var_export('/' . $relTarget, true) : '');
    }

    /**
     * Checks if the given path is absolute
     *
     * @return bool
     */
    public function isAbsolutePath(string $path)
    {
        return strpos($path, '/') === 0 || substr($path, 1, 1) === ':' || strpos($path, '\\\\') === 0;
    }

    /**
     * Returns size of a file or directory specified by path. If a directory is
     * given, its size will be computed recursively.
     *
     * @param  string            $path Path to the file or directory
     * @throws \RuntimeException
     * @return int
     */
    public function size(string $path)
    {
        if (!file_exists($path)) {
            throw new \RuntimeException("$path does not exist.");
        }
        if (is_dir($path)) {
            return $this->directorySize($path);
        }

        return (int) filesize($path);
    }

    /**
     * Normalize a path. This replaces backslashes with slashes, removes ending
     * slash and collapses redundant separators and up-level references.
     *
     * @param  string $path Path to the file or directory
     * @return string
     */
    public function normalizePath(string $path)
    {
        $parts = [];
        $path = strtr($path, '\\', '/');
        $prefix = '';
        $absolute = '';

        // extract windows UNC paths e.g. \\foo\bar
        if (strpos($path, '//') === 0 && \strlen($path) > 2) {
            $absolute = '//';
            $path = substr($path, 2);
        }

        // extract a prefix being a protocol://, protocol:, protocol://drive: or simply drive:
        if (Preg::isMatchStrictGroups('{^( [0-9a-z]{2,}+: (?: // (?: [a-z]: )? )? | [a-z]: )}ix', $path, $match)) {
            $prefix = $match[1];
            $path = substr($path, \strlen($prefix));
        }

        if (strpos($path, '/') === 0) {
            $absolute = '/';
            $path = substr($path, 1);
        }

        $up = false;
        foreach (explode('/', $path) as $chunk) {
            if ('..' === $chunk && (\strlen($absolute) > 0 || $up)) {
                array_pop($parts);
                $up = !(\count($parts) === 0 || '..' === end($parts));
            } elseif ('.' !== $chunk && '' !== $chunk) {
                $parts[] = $chunk;
                $up = '..' !== $chunk;
            }
        }

        // ensure c: is normalized to C:
        $prefix = Preg::replaceCallback('{(^|://)[a-z]:$}i', static function (array $m) {
            return strtoupper($m[0]);
        }, $prefix);

        return $prefix.$absolute.implode('/', $parts);
    }

    /**
     * Remove trailing slashes if present to avoid issues with symlinks
     *
     * And other possible unforeseen disasters, see https://github.com/composer/composer/pull/9422
     *
     * @return string
     */
    public static function trimTrailingSlash(string $path)
    {
        if (!Preg::isMatch('{^[/\\\\]+$}', $path)) {
            $path = rtrim($path, '/\\');
        }

        return $path;
    }

    /**
     * Return if the given path is local
     *
     * @return bool
     */
    public static function isLocalPath(string $path)
    {
        // on windows, \\foo indicates network paths so we exclude those from local paths, however it is unsafe
        // on linux as file:////foo (which would be a network path \\foo on windows) will resolve to /foo which could be a local path
        if (Platform::isWindows()) {
            return Preg::isMatch('{^(file://(?!//)|/(?!/)|/?[a-z]:[\\\\/]|\.\.[\\\\/]|[a-z0-9_.-]+[\\\\/])}i', $path);
        }

        return Preg::isMatch('{^(file://|/|/?[a-z]:[\\\\/]|\.\.[\\\\/]|[a-z0-9_.-]+[\\\\/])}i', $path);
    }

    /**
     * @return string
     */
    public static function getPlatformPath(string $path)
    {
        if (Platform::isWindows()) {
            $path = Preg::replace('{^(?:file:///([a-z]):?/)}i', 'file://$1:/', $path);
        }

        return Preg::replace('{^file://}i', '', $path);
    }

    /**
     * Cross-platform safe version of is_readable()
     *
     * This will also check for readability by reading the file as is_readable can not be trusted on network-mounts
     * and \\wsl$ paths. See https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926
     *
     * @return bool
     */
    public static function isReadable(string $path)
    {
        if (is_readable($path)) {
            return true;
        }

        if (is_file($path)) {
            return false !== Silencer::call('file_get_contents', $path, false, null, 0, 1);
        }

        if (is_dir($path)) {
            return false !== Silencer::call('opendir', $path);
        }

        // assume false otherwise
        return false;
    }

    /**
     * @return int
     */
    protected function directorySize(string $directory)
    {
        $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS);
        $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);

        $size = 0;
        foreach ($ri as $file) {
            if ($file->isFile()) {
                $size += $file->getSize();
            }
        }

        return $size;
    }

    /**
     * @return ProcessExecutor
     */
    protected function getProcess()
    {
        if (null === $this->processExecutor) {
            $this->processExecutor = new ProcessExecutor();
        }

        return $this->processExecutor;
    }

    /**
     * delete symbolic link implementation (commonly known as "unlink()")
     *
     * symbolic links on windows which link to directories need rmdir instead of unlink
     */
    private function unlinkImplementation(string $path): bool
    {
        if (Platform::isWindows() && is_dir($path) && is_link($path)) {
            return rmdir($path);
        }

        return unlink($path);
    }

    /**
     * Creates a relative symlink from $link to $target
     *
     * @param  string $target The path of the binary file to be symlinked
     * @param  string $link   The path where the symlink should be created
     * @return bool
     */
    public function relativeSymlink(string $target, string $link)
    {
        if (!function_exists('symlink')) {
            return false;
        }

        $cwd = Platform::getCwd();

        $relativePath = $this->findShortestPath($link, $target);
        chdir(\dirname($link));
        $result = @symlink($relativePath, $link);

        chdir($cwd);

        return $result;
    }

    /**
     * return true if that directory is a symlink.
     *
     * @return bool
     */
    public function isSymlinkedDirectory(string $directory)
    {
        if (!is_dir($directory)) {
            return false;
        }

        $resolved = $this->resolveSymlinkedDirectorySymlink($directory);

        return is_link($resolved);
    }

    private function unlinkSymlinkedDirectory(string $directory): bool
    {
        $resolved = $this->resolveSymlinkedDirectorySymlink($directory);

        return $this->unlink($resolved);
    }

    /**
     * resolve pathname to symbolic link of a directory
     *
     * @param string $pathname directory path to resolve
     *
     * @return string resolved path to symbolic link or original pathname (unresolved)
     */
    private function resolveSymlinkedDirectorySymlink(string $pathname): string
    {
        if (!is_dir($pathname)) {
            return $pathname;
        }

        $resolved = rtrim($pathname, '/');

        if (0 === \strlen($resolved)) {
            return $pathname;
        }

        return $resolved;
    }

    /**
     * Creates an NTFS junction.
     *
     * @return void
     */
    public function junction(string $target, string $junction)
    {
        if (!Platform::isWindows()) {
            throw new \LogicException(sprintf('Function %s is not available on non-Windows platform', __CLASS__));
        }
        if (!is_dir($target)) {
            throw new IOException(sprintf('Cannot junction to "%s" as it is not a directory.', $target), 0, null, $target);
        }

        // Removing any previously junction to ensure clean execution.
        if (!is_dir($junction) || $this->isJunction($junction)) {
            @rmdir($junction);
        }

        $cmd = ['mklink', '/J', str_replace('/', DIRECTORY_SEPARATOR, $junction), Platform::realpath($target)];
        if ($this->getProcess()->execute($cmd, $output) !== 0) {
            throw new IOException(sprintf('Failed to create junction to "%s" at "%s".', $target, $junction), 0, null, $target);
        }
        clearstatcache(true, $junction);
    }

    /**
     * Returns whether the target directory is a Windows NTFS Junction.
     *
     * We test if the path is a directory and not an ordinary link, then check
     * that the mode value returned from lstat (which gives the status of the
     * link itself) is not a directory, by replicating the POSIX S_ISDIR test.
     *
     * This logic works because PHP does not set the mode value for a junction,
     * since there is no universal file type flag for it. Unfortunately an
     * uninitialized variable in PHP prior to 7.2.16 and 7.3.3 may cause a
     * random value to be returned. See https://bugs.php.net/bug.php?id=77552
     *
     * If this random value passes the S_ISDIR test, then a junction will not be
     * detected and a recursive delete operation could lead to loss of data in
     * the target directory. Note that Windows rmdir can handle this situation
     * and will only delete the junction (from Windows 7 onwards).
     *
     * @param  string $junction Path to check.
     * @return bool
     */
    public function isJunction(string $junction)
    {
        if (!Platform::isWindows()) {
            return false;
        }

        // Important to clear all caches first
        clearstatcache(true, $junction);

        if (!is_dir($junction) || is_link($junction)) {
            return false;
        }

        $stat = lstat($junction);

        // S_ISDIR test (S_IFDIR is 0x4000, S_IFMT is 0xF000 bitmask)
        return is_array($stat) ? 0x4000 !== ($stat['mode'] & 0xF000) : false;
    }

    /**
     * Removes a Windows NTFS junction.
     *
     * @return bool
     */
    public function removeJunction(string $junction)
    {
        if (!Platform::isWindows()) {
            return false;
        }
        $junction = rtrim(str_replace('/', DIRECTORY_SEPARATOR, $junction), DIRECTORY_SEPARATOR);
        if (!$this->isJunction($junction)) {
            throw new IOException(sprintf('%s is not a junction and thus cannot be removed as one', $junction));
        }

        return $this->rmdir($junction);
    }

    /**
     * @return int|false
     */
    public function filePutContentsIfModified(string $path, string $content)
    {
        $currentContent = Silencer::call('file_get_contents', $path);
        if (false === $currentContent || $currentContent !== $content) {
            return file_put_contents($path, $content);
        }

        return 0;
    }

    /**
     * Copy file using stream_copy_to_stream to work around https://bugs.php.net/bug.php?id=6463
     */
    public function safeCopy(string $source, string $target): void
    {
        if (!file_exists($target) || !file_exists($source) || !$this->filesAreEqual($source, $target)) {
            $sourceHandle = fopen($source, 'r');
            assert($sourceHandle !== false, 'Could not open "'.$source.'" for reading.');
            $targetHandle = fopen($target, 'w+');
            assert($targetHandle !== false, 'Could not open "'.$target.'" for writing.');

            stream_copy_to_stream($sourceHandle, $targetHandle);
            fclose($sourceHandle);
            fclose($targetHandle);

            touch($target, (int) filemtime($source), (int) fileatime($source));
        }
    }

    /**
     * compare 2 files
     * https://stackoverflow.com/questions/3060125/can-i-use-file-get-contents-to-compare-two-files
     */
    private function filesAreEqual(string $a, string $b): bool
    {
        // Check if filesize is different
        if (filesize($a) !== filesize($b)) {
            return false;
        }

        // Check if content is different
        $aHandle = fopen($a, 'rb');
        assert($aHandle !== false, 'Could not open "'.$a.'" for reading.');
        $bHandle = fopen($b, 'rb');
        assert($bHandle !== false, 'Could not open "'.$b.'" for reading.');

        $result = true;
        while (!feof($aHandle)) {
            if (fread($aHandle, 8192) !== fread($bHandle, 8192)) {
                $result = false;
                break;
            }
        }

        fclose($aHandle);
        fclose($bHandle);

        return $result;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Downloader\DownloaderInterface;
use Composer\Downloader\DownloadManager;
use Composer\Package\PackageInterface;
use React\Promise\PromiseInterface;

class SyncHelper
{
    /**
     * Helps you download + install a single package in a synchronous way
     *
     * This executes all the required steps and waits for promises to complete
     *
     * @param Loop                                $loop        Loop instance which you can get from $composer->getLoop()
     * @param DownloaderInterface|DownloadManager $downloader  DownloadManager instance or Downloader instance you can get from $composer->getDownloadManager()->getDownloader('zip') for example
     * @param string                              $path        The installation path for the package
     * @param PackageInterface                    $package     The package to install
     * @param PackageInterface|null               $prevPackage The previous package if this is an update and not an initial installation
     */
    public static function downloadAndInstallPackageSync(Loop $loop, $downloader, string $path, PackageInterface $package, ?PackageInterface $prevPackage = null): void
    {
        assert($downloader instanceof DownloaderInterface || $downloader instanceof DownloadManager);

        $type = $prevPackage !== null ? 'update' : 'install';

        try {
            self::await($loop, $downloader->download($package, $path, $prevPackage));

            self::await($loop, $downloader->prepare($type, $package, $path, $prevPackage));

            if ($type === 'update' && $prevPackage !== null) {
                self::await($loop, $downloader->update($package, $prevPackage, $path));
            } else {
                self::await($loop, $downloader->install($package, $path));
            }
        } catch (\Exception $e) {
            self::await($loop, $downloader->cleanup($type, $package, $path, $prevPackage));
            throw $e;
        }

        self::await($loop, $downloader->cleanup($type, $package, $path, $prevPackage));
    }

    /**
     * Waits for a promise to resolve
     *
     * @param Loop                  $loop    Loop instance which you can get from $composer->getLoop()
     * @phpstan-param PromiseInterface<mixed>|null $promise
     */
    public static function await(Loop $loop, ?PromiseInterface $promise = null): void
    {
        if ($promise !== null) {
            $loop->wait([$promise]);
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

/**
 * @author Wissem Riahi <wissemr@gmail.com>
 */
class Tar
{
    public static function getComposerJson(string $pathToArchive): ?string
    {
        $phar = new \PharData($pathToArchive);

        if (!$phar->valid()) {
            return null;
        }

        return self::extractComposerJsonFromFolder($phar);
    }

    /**
     * @throws \RuntimeException
     */
    private static function extractComposerJsonFromFolder(\PharData $phar): string
    {
        if (isset($phar['composer.json'])) {
            return $phar['composer.json']->getContent();
        }

        $topLevelPaths = [];
        foreach ($phar as $folderFile) {
            $name = $folderFile->getBasename();

            if ($folderFile->isDir()) {
                $topLevelPaths[$name] = true;
                if (\count($topLevelPaths) > 1) {
                    throw new \RuntimeException('Archive has more than one top level directories, and no composer.json was found on the top level, so it\'s an invalid archive. Top level paths found were: '.implode(',', array_keys($topLevelPaths)));
                }
            }
        }

        $composerJsonPath = key($topLevelPaths).'/composer.json';
        if (\count($topLevelPaths) > 0 && isset($phar[$composerJsonPath])) {
            return $phar[$composerJsonPath]->getContent();
        }

        throw new \RuntimeException('No composer.json found either at the top level or within the topmost directory');
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\IO\IOInterface;
use Composer\Config;
use Composer\Factory;
use Composer\Downloader\TransportException;
use Composer\Pcre\Preg;

/**
 * @author Roshan Gautam <roshan.gautam@hotmail.com>
 */
class GitLab
{
    /** @var IOInterface */
    protected $io;
    /** @var Config */
    protected $config;
    /** @var ProcessExecutor */
    protected $process;
    /** @var HttpDownloader */
    protected $httpDownloader;

    /**
     * Constructor.
     *
     * @param IOInterface     $io             The IO instance
     * @param Config          $config         The composer configuration
     * @param ProcessExecutor $process        Process instance, injectable for mocking
     * @param HttpDownloader  $httpDownloader Remote Filesystem, injectable for mocking
     */
    public function __construct(IOInterface $io, Config $config, ?ProcessExecutor $process = null, ?HttpDownloader $httpDownloader = null)
    {
        $this->io = $io;
        $this->config = $config;
        $this->process = $process ?: new ProcessExecutor($io);
        $this->httpDownloader = $httpDownloader ?: Factory::createHttpDownloader($this->io, $config);
    }

    /**
     * Attempts to authorize a GitLab domain via OAuth.
     *
     * @param string $originUrl The host this GitLab instance is located at
     *
     * @return bool true on success
     */
    public function authorizeOAuth(string $originUrl): bool
    {
        // before composer 1.9, origin URLs had no port number in them
        $bcOriginUrl = Preg::replace('{:\d+}', '', $originUrl);

        if (!in_array($originUrl, $this->config->get('gitlab-domains'), true) && !in_array($bcOriginUrl, $this->config->get('gitlab-domains'), true)) {
            return false;
        }

        // if available use token from git config
        if (0 === $this->process->execute(['git', 'config', 'gitlab.accesstoken'], $output)) {
            $this->io->setAuthentication($originUrl, trim($output), 'oauth2');

            return true;
        }

        // if available use deploy token from git config
        if (0 === $this->process->execute(['git', 'config', 'gitlab.deploytoken.user'], $tokenUser) && 0 === $this->process->execute(['git', 'config', 'gitlab.deploytoken.token'], $tokenPassword)) {
            $this->io->setAuthentication($originUrl, trim($tokenUser), trim($tokenPassword));

            return true;
        }

        // if available use token from composer config
        $authTokens = $this->config->get('gitlab-token');

        if (isset($authTokens[$originUrl])) {
            $token = $authTokens[$originUrl];
        }

        if (isset($authTokens[$bcOriginUrl])) {
            $token = $authTokens[$bcOriginUrl];
        }

        if (isset($token)) {
            $username = is_array($token) ? $token["username"] : $token;
            $password = is_array($token) ? $token["token"] : 'private-token';

            // Composer expects the GitLab token to be stored as username and 'private-token' or 'gitlab-ci-token' to be stored as password
            // Detect cases where this is reversed and resolve automatically resolve it
            if (in_array($username, ['private-token', 'gitlab-ci-token',  'oauth2'], true)) {
                $this->io->setAuthentication($originUrl, $password, $username);
            } else {
                $this->io->setAuthentication($originUrl, $username, $password);
            }

            return true;
        }

        return false;
    }

    /**
     * Authorizes a GitLab domain interactively via OAuth.
     *
     * @param string $scheme    Scheme used in the origin URL
     * @param string $originUrl The host this GitLab instance is located at
     * @param string $message   The reason this authorization is required
     *
     * @throws \RuntimeException
     * @throws TransportException|\Exception
     *
     * @return bool true on success
     */
    public function authorizeOAuthInteractively(string $scheme, string $originUrl, ?string $message = null): bool
    {
        if ($message) {
            $this->io->writeError($message);
        }

        $localAuthConfig = $this->config->getLocalAuthConfigSource();
        $personalAccessTokenLink = $scheme.'://'.$originUrl.'/-/user_settings/personal_access_tokens';
        $revokeLink = $scheme.'://'.$originUrl.'/-/user_settings/applications';
        $this->io->writeError(sprintf('A token will be created and stored in "%s", your password will never be stored', ($localAuthConfig !== null ? $localAuthConfig->getName() . ' OR ' : '') . $this->config->getAuthConfigSource()->getName()));
        $this->io->writeError('To revoke access to this token you can visit:');
        $this->io->writeError($revokeLink);
        $this->io->writeError('Alternatively you can setup an personal access token on:');
        $this->io->writeError($personalAccessTokenLink);
        $this->io->writeError('and store it under "gitlab-token" see https://getcomposer.org/doc/articles/authentication-for-private-packages.md#gitlab-token for more details.');
        $this->io->writeError('https://getcomposer.org/doc/articles/authentication-for-private-packages.md#gitlab-token');
        $this->io->writeError('for more details.');

        $storeInLocalAuthConfig = false;
        if ($localAuthConfig !== null) {
            $storeInLocalAuthConfig = $this->io->askConfirmation('A local auth config source was found, do you want to store the token there?', true);
        }

        $attemptCounter = 0;

        while ($attemptCounter++ < 5) {
            try {
                $response = $this->createToken($scheme, $originUrl);
            } catch (TransportException $e) {
                // 401 is bad credentials,
                // 403 is max login attempts exceeded
                if (in_array($e->getCode(), [403, 401])) {
                    if (401 === $e->getCode()) {
                        $response = json_decode($e->getResponse(), true);
                        if (isset($response['error']) && $response['error'] === 'invalid_grant') {
                            $this->io->writeError('Bad credentials. If you have two factor authentication enabled you will have to manually create a personal access token');
                        } else {
                            $this->io->writeError('Bad credentials.');
                        }
                    } else {
                        $this->io->writeError('Maximum number of login attempts exceeded. Please try again later.');
                    }

                    $this->io->writeError('You can also manually create a personal access token enabling the "read_api" scope at:');
                    $this->io->writeError($personalAccessTokenLink);
                    $this->io->writeError('Add it using "composer config --global --auth gitlab-token.'.$originUrl.' <token>"');

                    continue;
                }

                throw $e;
            }

            $this->io->setAuthentication($originUrl, $response['access_token'], 'oauth2');

            $authConfigSource = $storeInLocalAuthConfig && $localAuthConfig !== null ? $localAuthConfig : $this->config->getAuthConfigSource();
            // store value in user config in auth file
            if (isset($response['expires_in'])) {
                $authConfigSource->addConfigSetting(
                    'gitlab-oauth.'.$originUrl,
                    [
                        'expires-at' => intval($response['created_at']) + intval($response['expires_in']),
                        'refresh-token' => $response['refresh_token'],
                        'token' => $response['access_token'],
                    ]
                );
            } else {
                $authConfigSource->addConfigSetting('gitlab-oauth.'.$originUrl, $response['access_token']);
            }

            return true;
        }

        throw new \RuntimeException('Invalid GitLab credentials 5 times in a row, aborting.');
    }

    /**
     * Authorizes a GitLab domain interactively via OAuth.
     *
     * @param string $scheme    Scheme used in the origin URL
     * @param string $originUrl The host this GitLab instance is located at
     *
     * @throws \RuntimeException
     * @throws TransportException|\Exception
     *
     * @return bool true on success
     */
    public function authorizeOAuthRefresh(string $scheme, string $originUrl): bool
    {
        try {
            $response = $this->refreshToken($scheme, $originUrl);
        } catch (TransportException $e) {
            $this->io->writeError("Couldn't refresh access token: ".$e->getMessage());

            return false;
        }

        $this->io->setAuthentication($originUrl, $response['access_token'], 'oauth2');

        // store value in user config in auth file
        $this->config->getAuthConfigSource()->addConfigSetting(
            'gitlab-oauth.'.$originUrl,
            [
                'expires-at' => intval($response['created_at']) + intval($response['expires_in']),
                'refresh-token' => $response['refresh_token'],
                'token' => $response['access_token'],
            ]
        );

        return true;
    }

    /**
     * @return array{access_token: non-empty-string, refresh_token: non-empty-string, token_type: non-empty-string, expires_in?: positive-int, created_at: positive-int}
     *
     * @see https://docs.gitlab.com/ee/api/oauth2.html#resource-owner-password-credentials-flow
     */
    private function createToken(string $scheme, string $originUrl): array
    {
        $username = $this->io->ask('Username: ');
        $password = $this->io->askAndHideAnswer('Password: ');

        $headers = ['Content-Type: application/x-www-form-urlencoded'];

        $apiUrl = $originUrl;
        $data = http_build_query([
            'username' => $username,
            'password' => $password,
            'grant_type' => 'password',
        ], '', '&');
        $options = [
            'retry-auth-failure' => false,
            'http' => [
                'method' => 'POST',
                'header' => $headers,
                'content' => $data,
            ],
        ];

        $token = $this->httpDownloader->get($scheme.'://'.$apiUrl.'/oauth/token', $options)->decodeJson();

        $this->io->writeError('Token successfully created');

        return $token;
    }

    /**
     * Is the OAuth access token expired?
     *
     * @return bool true on expired token, false if token is fresh or expiration date is not set
     */
    public function isOAuthExpired(string $originUrl): bool
    {
        $authTokens = $this->config->get('gitlab-oauth');
        if (isset($authTokens[$originUrl]['expires-at'])) {
            if ($authTokens[$originUrl]['expires-at'] < time()) {
                return true;
            }
        }

        return false;
    }

    /**
     * @return array{access_token: non-empty-string, refresh_token: non-empty-string, token_type: non-empty-string, expires_in: positive-int, created_at: positive-int}
     *
     * @see https://docs.gitlab.com/ee/api/oauth2.html#resource-owner-password-credentials-flow
     */
    private function refreshToken(string $scheme, string $originUrl): array
    {
        $authTokens = $this->config->get('gitlab-oauth');
        if (!isset($authTokens[$originUrl]['refresh-token'])) {
            throw new \RuntimeException('No GitLab refresh token present for '.$originUrl.'.');
        }

        $refreshToken = $authTokens[$originUrl]['refresh-token'];
        $headers = ['Content-Type: application/x-www-form-urlencoded'];

        $data = http_build_query([
            'refresh_token' => $refreshToken,
            'grant_type' => 'refresh_token',
        ], '', '&');
        $options = [
            'retry-auth-failure' => false,
            'http' => [
                'method' => 'POST',
                'header' => $headers,
                'content' => $data,
            ],
        ];

        $token = $this->httpDownloader->get($scheme.'://'.$originUrl.'/oauth/token', $options)->decodeJson();
        $this->io->writeError('GitLab token successfully refreshed', true, IOInterface::VERY_VERBOSE);
        $this->io->writeError('To revoke access to this token you can visit '.$scheme.'://'.$originUrl.'/-/user_settings/applications', true, IOInterface::VERY_VERBOSE);

        return $token;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Pcre\Preg;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Git
{
    /** @var string|false|null */
    private static $version = false;

    /** @var IOInterface */
    protected $io;
    /** @var Config */
    protected $config;
    /** @var ProcessExecutor */
    protected $process;
    /** @var Filesystem */
    protected $filesystem;
    /** @var HttpDownloader */
    protected $httpDownloader;

    public function __construct(IOInterface $io, Config $config, ProcessExecutor $process, Filesystem $fs)
    {
        $this->io = $io;
        $this->config = $config;
        $this->process = $process;
        $this->filesystem = $fs;
    }

    /**
     * @param IOInterface|null $io If present, a warning is output there instead of throwing, so pass this in only for cases where this is a soft failure
     */
    public static function checkForRepoOwnershipError(string $output, string $path, ?IOInterface $io = null): void
    {
        if (str_contains($output, 'fatal: detected dubious ownership')) {
            $msg = 'The repository at "' . $path . '" does not have the correct ownership and git refuses to use it:' . PHP_EOL . PHP_EOL . $output;
            if ($io === null) {
                throw new \RuntimeException($msg);
            }
            $io->writeError('<warning>'.$msg.'</warning>');
        }
    }

    public function setHttpDownloader(HttpDownloader $httpDownloader): void
    {
        $this->httpDownloader = $httpDownloader;
    }

    /**
     * Runs a set of commands using the $url or a variation of it (with auth, ssh, ..)
     *
     * Commands should use %url% placeholders for the URL instead of inlining it to allow this function to do its job
     * %sanitizedUrl% is also automatically replaced by the url without user/pass
     *
     * As soon as a single command fails it will halt, so assume the commands are run as && in bash
     *
     * @param non-empty-array<non-empty-list<string>> $commands
     * @param mixed $commandOutput  the output will be written into this var if passed by ref
     *                              if a callable is passed it will be used as output handler
     */
    public function runCommands(array $commands, string $url, ?string $cwd, bool $initialClone = false, &$commandOutput = null): void
    {
        $callables = [];
        foreach ($commands as $cmd) {
            $callables[] = static function (string $url) use ($cmd): array {
                $map = [
                    '%url%' => $url,
                    '%sanitizedUrl%' => Preg::replace('{://([^@]+?):(.+?)@}', '://', $url),
                ];

                return array_map(static function ($value) use ($map): string {
                    return $map[$value] ?? $value;
                }, $cmd);
            };
        }

        // @phpstan-ignore method.deprecated
        $this->runCommand($callables, $url, $cwd, $initialClone, $commandOutput);
    }

    /**
     * @param callable|array<callable> $commandCallable
     * @param mixed       $commandOutput  the output will be written into this var if passed by ref
     *                                    if a callable is passed it will be used as output handler
     * @deprecated Use runCommands with placeholders instead of callbacks for simplicity
     */
    public function runCommand($commandCallable, string $url, ?string $cwd, bool $initialClone = false, &$commandOutput = null): void
    {
        $commandCallables = is_callable($commandCallable) ? [$commandCallable] : $commandCallable;
        $lastCommand = '';

        // Ensure we are allowed to use this URL by config
        $this->config->prohibitUrlByConfig($url, $this->io);

        if ($initialClone) {
            $origCwd = $cwd;
        }

        $runCommands = function ($url) use ($commandCallables, $cwd, &$commandOutput, &$lastCommand, $initialClone) {
            $collectOutputs = !is_callable($commandOutput);
            $outputs = [];

            $status = 0;
            $counter = 0;
            foreach ($commandCallables as $callable) {
                $lastCommand = $callable($url);
                if ($collectOutputs) {
                    $outputs[] = '';
                    $output = &$outputs[count($outputs) - 1];
                } else {
                    $output = &$commandOutput;
                }
                $status = $this->process->execute($lastCommand, $output, $initialClone && $counter === 0 ? null : $cwd);
                if ($status !== 0) {
                    break;
                }
                $counter++;
            }

            if ($collectOutputs) {
                $commandOutput = implode('', $outputs);
            }

            return $status;
        };

        if (Preg::isMatch('{^ssh://[^@]+@[^:]+:[^0-9]+}', $url)) {
            throw new \InvalidArgumentException('The source URL ' . $url . ' is invalid, ssh URLs should have a port number after ":".' . "\n" . 'Use ssh://git@example.com:22/path or just git@example.com:path if you do not want to provide a password or custom port.');
        }

        if (!$initialClone) {
            // capture username/password from URL if there is one and we have no auth configured yet
            $this->process->execute(['git', 'remote', '-v'], $output, $cwd);
            if (Preg::isMatchStrictGroups('{^(?:composer|origin)\s+https?://(.+):(.+)@([^/]+)}im', $output, $match) && !$this->io->hasAuthentication($match[3])) {
                $this->io->setAuthentication($match[3], rawurldecode($match[1]), rawurldecode($match[2]));
            }
        }

        $protocols = $this->config->get('github-protocols');
        // public github, autoswitch protocols
        // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups
        if (Preg::isMatchStrictGroups('{^(?:https?|git)://' . self::getGitHubDomainsRegex($this->config) . '/(.*)}', $url, $match)) {
            $messages = [];
            foreach ($protocols as $protocol) {
                if ('ssh' === $protocol) {
                    $protoUrl = "git@" . $match[1] . ":" . $match[2];
                } else {
                    $protoUrl = $protocol . "://" . $match[1] . "/" . $match[2];
                }

                if (0 === $runCommands($protoUrl)) {
                    return;
                }
                $messages[] = '- ' . $protoUrl . "\n" . Preg::replace('#^#m', '  ', $this->process->getErrorOutput());

                if ($initialClone && isset($origCwd)) {
                    $this->filesystem->removeDirectory($origCwd);
                }
            }

            // failed to checkout, first check git accessibility
            if (!$this->io->hasAuthentication($match[1]) && !$this->io->isInteractive()) {
                $this->throwException('Failed to clone ' . $url . ' via ' . implode(', ', $protocols) . ' protocols, aborting.' . "\n\n" . implode("\n", $messages), $url);
            }
        }

        // if we have a private github url and the ssh protocol is disabled then we skip it and directly fallback to https
        $bypassSshForGitHub = Preg::isMatch('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url) && !in_array('ssh', $protocols, true);

        $auth = null;
        $credentials = [];
        if ($bypassSshForGitHub || 0 !== $runCommands($url)) {
            $errorMsg = $this->process->getErrorOutput();
            // private github repository without ssh key access, try https with auth
            // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups
            if (Preg::isMatchStrictGroups('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url, $match)
                // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups
                || Preg::isMatchStrictGroups('{^https?://' . self::getGitHubDomainsRegex($this->config) . '/(.*?)(?:\.git)?$}i', $url, $match)
            ) {
                if (!$this->io->hasAuthentication($match[1])) {
                    $gitHubUtil = new GitHub($this->io, $this->config, $this->process);
                    $message = 'Cloning failed using an ssh key for authentication, enter your GitHub credentials to access private repos';

                    if (!$gitHubUtil->authorizeOAuth($match[1]) && $this->io->isInteractive()) {
                        $gitHubUtil->authorizeOAuthInteractively($match[1], $message);
                    }
                }

                if ($this->io->hasAuthentication($match[1])) {
                    $auth = $this->io->getAuthentication($match[1]);
                    $authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $match[1] . '/' . $match[2] . '.git';
                    if (0 === $runCommands($authUrl)) {
                        return;
                    }

                    $credentials = [rawurlencode($auth['username']), rawurlencode($auth['password'])];
                    $errorMsg = $this->process->getErrorOutput();
                }
            // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups
            } elseif (
                Preg::isMatchStrictGroups('{^(https?)://(bitbucket\.org)/(.*?)(?:\.git)?$}i', $url, $match)
                || Preg::isMatchStrictGroups('{^(git)@(bitbucket\.org):(.+?\.git)$}i', $url, $match)
            ) { //bitbucket either through oauth or app password, with fallback to ssh.
                $bitbucketUtil = new Bitbucket($this->io, $this->config, $this->process, $this->httpDownloader);

                $domain = $match[2];
                $repo_with_git_part = $match[3];
                if (!str_ends_with($repo_with_git_part, '.git')) {
                    $repo_with_git_part .= '.git';
                }
                if (!$this->io->hasAuthentication($domain)) {
                    $message = 'Enter your Bitbucket credentials to access private repos';

                    if (!$bitbucketUtil->authorizeOAuth($domain) && $this->io->isInteractive()) {
                        $bitbucketUtil->authorizeOAuthInteractively($domain, $message);
                        $accessToken = $bitbucketUtil->getToken();
                        $this->io->setAuthentication($domain, 'x-token-auth', $accessToken);
                    }
                }

                // First we try to authenticate with whatever we have stored.
                // This will be successful if there is for example an app
                // password in there.
                if ($this->io->hasAuthentication($domain)) {
                    $auth = $this->io->getAuthentication($domain);
                    $authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $domain . '/' . $repo_with_git_part;

                    if (0 === $runCommands($authUrl)) {
                        // Well if that succeeded on our first try, let's just
                        // take the win.
                        return;
                    }

                    //We already have an access_token from a previous request.
                    if ($auth['username'] !== 'x-token-auth') {
                        $accessToken = $bitbucketUtil->requestToken($domain, $auth['username'], $auth['password']);
                        if (!empty($accessToken)) {
                            $this->io->setAuthentication($domain, 'x-token-auth', $accessToken);
                        }
                    }
                }

                if ($this->io->hasAuthentication($domain)) {
                    $auth = $this->io->getAuthentication($domain);
                    $authUrl = 'https://' . rawurlencode($auth['username']) . ':' . rawurlencode($auth['password']) . '@' . $domain . '/' . $repo_with_git_part;
                    if (0 === $runCommands($authUrl)) {
                        return;
                    }

                    $credentials = [rawurlencode($auth['username']), rawurlencode($auth['password'])];
                }
                //Falling back to ssh
                $sshUrl = 'git@bitbucket.org:' . $repo_with_git_part;
                $this->io->writeError('    No bitbucket authentication configured. Falling back to ssh.');
                if (0 === $runCommands($sshUrl)) {
                    return;
                }

                $errorMsg = $this->process->getErrorOutput();
            } elseif (
                // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups
                Preg::isMatchStrictGroups('{^(git)@' . self::getGitLabDomainsRegex($this->config) . ':(.+?\.git)$}i', $url, $match)
                // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups
                || Preg::isMatchStrictGroups('{^(https?)://' . self::getGitLabDomainsRegex($this->config) . '/(.*)}i', $url, $match)
            ) {
                if ($match[1] === 'git') {
                    $match[1] = 'https';
                }

                if (!$this->io->hasAuthentication($match[2])) {
                    $gitLabUtil = new GitLab($this->io, $this->config, $this->process);
                    $message = 'Cloning failed, enter your GitLab credentials to access private repos';

                    if (!$gitLabUtil->authorizeOAuth($match[2]) && $this->io->isInteractive()) {
                        $gitLabUtil->authorizeOAuthInteractively($match[1], $match[2], $message);
                    }
                }

                if ($this->io->hasAuthentication($match[2])) {
                    $auth = $this->io->getAuthentication($match[2]);
                    if ($auth['password'] === 'private-token' || $auth['password'] === 'oauth2' || $auth['password'] === 'gitlab-ci-token') {
                        $authUrl = $match[1] . '://' . rawurlencode($auth['password']) . ':' . rawurlencode((string) $auth['username']) . '@' . $match[2] . '/' . $match[3]; // swap username and password
                    } else {
                        $authUrl = $match[1] . '://' . rawurlencode((string) $auth['username']) . ':' . rawurlencode((string) $auth['password']) . '@' . $match[2] . '/' . $match[3];
                    }

                    if (0 === $runCommands($authUrl)) {
                        return;
                    }

                    $credentials = [rawurlencode((string) $auth['username']), rawurlencode((string) $auth['password'])];
                    $errorMsg = $this->process->getErrorOutput();
                }
            } elseif (null !== ($match = $this->getAuthenticationFailure($url))) { // private non-github/gitlab/bitbucket repo that failed to authenticate
                if (str_contains($match[2], '@')) {
                    [$authParts, $match[2]] = explode('@', $match[2], 2);
                }

                $storeAuth = false;
                if ($this->io->hasAuthentication($match[2])) {
                    $auth = $this->io->getAuthentication($match[2]);
                } elseif ($this->io->isInteractive()) {
                    $defaultUsername = null;
                    if (isset($authParts) && $authParts !== '') {
                        if (str_contains($authParts, ':')) {
                            [$defaultUsername, ] = explode(':', $authParts, 2);
                        } else {
                            $defaultUsername = $authParts;
                        }
                    }

                    $this->io->writeError('    Authentication required (<info>' . $match[2] . '</info>):');
                    $this->io->writeError('<warning>' . trim($errorMsg) . '</warning>', true, IOInterface::VERBOSE);
                    $auth = [
                        'username' => $this->io->ask('      Username: ', $defaultUsername),
                        'password' => $this->io->askAndHideAnswer('      Password: '),
                    ];
                    $storeAuth = $this->config->get('store-auths');
                }

                if (null !== $auth) {
                    $authUrl = $match[1] . rawurlencode((string) $auth['username']) . ':' . rawurlencode((string) $auth['password']) . '@' . $match[2] . $match[3];

                    if (0 === $runCommands($authUrl)) {
                        $this->io->setAuthentication($match[2], $auth['username'], $auth['password']);
                        $authHelper = new AuthHelper($this->io, $this->config);
                        $authHelper->storeAuth($match[2], $storeAuth);

                        return;
                    }

                    $credentials = [rawurlencode((string) $auth['username']), rawurlencode((string) $auth['password'])];
                    $errorMsg = $this->process->getErrorOutput();
                }
            }

            if ($initialClone && isset($origCwd)) {
                $this->filesystem->removeDirectory($origCwd);
            }

            $lastCommand = implode(' ', $lastCommand);
            if (count($credentials) > 0) {
                $lastCommand = $this->maskCredentials($lastCommand, $credentials);
                $errorMsg = $this->maskCredentials($errorMsg, $credentials);
            }
            $this->throwException('Failed to execute ' . $lastCommand . "\n\n" . $errorMsg, $url);
        }
    }

    public function syncMirror(string $url, string $dir): bool
    {
        if ((bool) Platform::getEnv('COMPOSER_DISABLE_NETWORK') && Platform::getEnv('COMPOSER_DISABLE_NETWORK') !== 'prime') {
            $this->io->writeError('<warning>Aborting git mirror sync of '.$url.' as network is disabled</warning>');

            return false;
        }

        // update the repo if it is a valid git repository
        if (is_dir($dir) && 0 === $this->process->execute(['git', 'rev-parse', '--git-dir'], $output, $dir) && trim($output) === '.') {
            try {
                $commands = [
                    ['git', 'remote', 'set-url', 'origin', '--', '%url%'],
                    ['git', 'remote', 'update', '--prune', 'origin'],
                    ['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%'],
                    ['git', 'gc', '--auto'],
                ];

                $this->runCommands($commands, $url, $dir);
            } catch (\Exception $e) {
                $this->io->writeError('<error>Sync mirror failed: ' . $e->getMessage() . '</error>', true, IOInterface::DEBUG);

                return false;
            }

            return true;
        }
        self::checkForRepoOwnershipError($this->process->getErrorOutput(), $dir);

        // clean up directory and do a fresh clone into it
        $this->filesystem->removeDirectory($dir);

        $this->runCommands([['git', 'clone', '--mirror', '--', '%url%', $dir]], $url, $dir, true);

        return true;
    }

    public function fetchRefOrSyncMirror(string $url, string $dir, string $ref, ?string $prettyVersion = null): bool
    {
        if ($this->checkRefIsInMirror($dir, $ref)) {
            if (Preg::isMatch('{^[a-f0-9]{40}$}', $ref) && $prettyVersion !== null) {
                $branch = Preg::replace('{(?:^dev-|(?:\.x)?-dev$)}i', '', $prettyVersion);
                $branches = null;
                $tags = null;
                if (0 === $this->process->execute(['git', 'branch'], $output, $dir)) {
                    $branches = $output;
                }
                if (0 === $this->process->execute(['git', 'tag'], $output, $dir)) {
                    $tags = $output;
                }

                // if the pretty version cannot be found as a branch (nor branch with 'v' in front of the branch as it may have been stripped when generating pretty name),
                // nor as a tag, then we sync the mirror as otherwise it will likely fail during install.
                // this can occur if a git tag gets created *after* the reference is already put into the cache, as the ref check above will then not sync the new tags
                // see https://github.com/composer/composer/discussions/11002
                if (null !== $branches && !Preg::isMatch('{^[\s*]*v?'.preg_quote($branch).'$}m', $branches)
                    && null !== $tags && !Preg::isMatch('{^[\s*]*'.preg_quote($branch).'$}m', $tags)
                ) {
                    $this->syncMirror($url, $dir);
                }
            }

            return true;
        }

        if ($this->syncMirror($url, $dir)) {
            return $this->checkRefIsInMirror($dir, $ref);
        }

        return false;
    }

    public static function getNoShowSignatureFlag(ProcessExecutor $process): string
    {
        $gitVersion = self::getVersion($process);
        if ($gitVersion !== null && version_compare($gitVersion, '2.10.0-rc0', '>=')) {
            return ' --no-show-signature';
        }

        return '';
    }

    /**
     * @return list<string>
     */
    public static function getNoShowSignatureFlags(ProcessExecutor $process): array
    {
        $flags = static::getNoShowSignatureFlag($process);
        if ('' === $flags) {
            return [];
        }

        return explode(' ', substr($flags, 1));
    }

    private function checkRefIsInMirror(string $dir, string $ref): bool
    {
        if (is_dir($dir) && 0 === $this->process->execute(['git', 'rev-parse', '--git-dir'], $output, $dir) && trim($output) === '.') {
            $exitCode = $this->process->execute(['git', 'rev-parse', '--quiet', '--verify', $ref.'^{commit}'], $ignoredOutput, $dir);
            if ($exitCode === 0) {
                return true;
            }
        }
        self::checkForRepoOwnershipError($this->process->getErrorOutput(), $dir);

        return false;
    }

    /**
     * @return array<int, string>|null
     */
    private function getAuthenticationFailure(string $url): ?array
    {
        if (!Preg::isMatchStrictGroups('{^(https?://)([^/]+)(.*)$}i', $url, $match)) {
            return null;
        }

        $authFailures = [
            'fatal: Authentication failed',
            'remote error: Invalid username or password.',
            'error: 401 Unauthorized',
            'fatal: unable to access',
            'fatal: could not read Username',
        ];

        $errorOutput = $this->process->getErrorOutput();
        foreach ($authFailures as $authFailure) {
            if (strpos($errorOutput, $authFailure) !== false) {
                return $match;
            }
        }

        return null;
    }

    public function getMirrorDefaultBranch(string $url, string $dir, bool $isLocalPathRepository): ?string
    {
        if ((bool) Platform::getEnv('COMPOSER_DISABLE_NETWORK')) {
            return null;
        }

        try {
            if ($isLocalPathRepository) {
                $this->process->execute(['git', 'remote', 'show', 'origin'], $output, $dir);
            } else {
                $commands = [
                    ['git', 'remote', 'set-url', 'origin', '--', '%url%'],
                    ['git', 'remote', 'show', 'origin'],
                    ['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%'],
                ];

                $this->runCommands($commands, $url, $dir, false, $output);
            }

            $lines = $this->process->splitLines($output);
            foreach ($lines as $line) {
                if (Preg::isMatch('{^\s*HEAD branch:\s(.+)\s*$}m', $line, $matches)) {
                    return $matches[1];
                }
            }
        } catch (\Exception $e) {
            $this->io->writeError('<error>Failed to fetch root identifier from remote: ' . $e->getMessage() . '</error>', true, IOInterface::DEBUG);
        }

        return null;
    }

    public static function cleanEnv(): void
    {
        $gitVersion = self::getVersion(new ProcessExecutor());
        if ($gitVersion !== null && version_compare($gitVersion, '2.3.0', '>=')) {
            // added in git 2.3.0, prevents prompting the user for username/password
            if (Platform::getEnv('GIT_TERMINAL_PROMPT') !== '0') {
                Platform::putEnv('GIT_TERMINAL_PROMPT', '0');
            }
        } else {
            // added in git 1.7.1, prevents prompting the user for username/password
            if (Platform::getEnv('GIT_ASKPASS') !== 'echo') {
                Platform::putEnv('GIT_ASKPASS', 'echo');
            }
        }

        // clean up rogue git env vars in case this is running in a git hook
        if (Platform::getEnv('GIT_DIR')) {
            Platform::clearEnv('GIT_DIR');
        }
        if (Platform::getEnv('GIT_WORK_TREE')) {
            Platform::clearEnv('GIT_WORK_TREE');
        }

        // Run processes with predictable LANGUAGE
        if (Platform::getEnv('LANGUAGE') !== 'C') {
            Platform::putEnv('LANGUAGE', 'C');
        }

        // clean up env for OSX, see https://github.com/composer/composer/issues/2146#issuecomment-35478940
        Platform::clearEnv('DYLD_LIBRARY_PATH');
    }

    /**
     * @return non-empty-string
     */
    public static function getGitHubDomainsRegex(Config $config): string
    {
        return '(' . implode('|', array_map('preg_quote', $config->get('github-domains'))) . ')';
    }

    /**
     * @return non-empty-string
     */
    public static function getGitLabDomainsRegex(Config $config): string
    {
        return '(' . implode('|', array_map('preg_quote', $config->get('gitlab-domains'))) . ')';
    }

    /**
     * @param non-empty-string $message
     *
     * @return never
     */
    private function throwException($message, string $url): void
    {
        // git might delete a directory when it fails and php will not know
        clearstatcache();

        if (0 !== $this->process->execute(['git', '--version'], $ignoredOutput)) {
            throw new \RuntimeException(Url::sanitize('Failed to clone ' . $url . ', git was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput()));
        }

        throw new \RuntimeException(Url::sanitize($message));
    }

    /**
     * Retrieves the current git version.
     *
     * @return string|null The git version number, if present.
     */
    public static function getVersion(ProcessExecutor $process): ?string
    {
        if (false === self::$version) {
            self::$version = null;
            if (0 === $process->execute(['git', '--version'], $output) && Preg::isMatch('/^git version (\d+(?:\.\d+)+)/m', $output, $matches)) {
                self::$version = $matches[1];
            }
        }

        return self::$version;
    }

    /**
     * @param string[] $credentials
     */
    private function maskCredentials(string $error, array $credentials): string
    {
        $maskedCredentials = [];

        foreach ($credentials as $credential) {
            if (in_array($credential, ['private-token', 'x-token-auth', 'oauth2', 'gitlab-ci-token', 'x-oauth-basic'])) {
                $maskedCredentials[] = $credential;
            } elseif (strlen($credential) > 6) {
                $maskedCredentials[] = substr($credential, 0, 3) . '...' . substr($credential, -3);
            } elseif (strlen($credential) > 3) {
                $maskedCredentials[] = substr($credential, 0, 3) . '...';
            } else {
                $maskedCredentials[] = 'XXX';
            }
        }

        return str_replace($credentials, $maskedCredentials, $error);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\IO\IOInterface;
use Composer\Pcre\Preg;
use Symfony\Component\Process\ExecutableFinder;
use Symfony\Component\Process\Process;

/**
 * @author Matt Whittom <Matt.Whittom@veteransunited.com>
 *
 * @phpstan-type RepoConfig array{unique_perforce_client_name?: string, depot?: string, branch?: string, p4user?: string, p4password?: string}
 */
class Perforce
{
    /** @var string */
    protected $path;
    /** @var ?string */
    protected $p4Depot;
    /** @var ?string */
    protected $p4Client;
    /** @var ?string */
    protected $p4User;
    /** @var ?string */
    protected $p4Password;
    /** @var string */
    protected $p4Port;
    /** @var ?string */
    protected $p4Stream;
    /** @var string */
    protected $p4ClientSpec;
    /** @var ?string */
    protected $p4DepotType;
    /** @var ?string */
    protected $p4Branch;
    /** @var ProcessExecutor */
    protected $process;
    /** @var string */
    protected $uniquePerforceClientName;
    /** @var bool */
    protected $windowsFlag;
    /** @var string */
    protected $commandResult;

    /** @var IOInterface */
    protected $io;

    /** @var ?Filesystem */
    protected $filesystem;

    /**
     * @phpstan-param RepoConfig $repoConfig
     */
    public function __construct($repoConfig, string $port, string $path, ProcessExecutor $process, bool $isWindows, IOInterface $io)
    {
        $this->windowsFlag = $isWindows;
        $this->p4Port = $port;
        $this->initializePath($path);
        $this->process = $process;
        $this->initialize($repoConfig);
        $this->io = $io;
    }

    /**
     * @phpstan-param RepoConfig $repoConfig
     */
    public static function create($repoConfig, string $port, string $path, ProcessExecutor $process, IOInterface $io): self
    {
        return new Perforce($repoConfig, $port, $path, $process, Platform::isWindows(), $io);
    }

    public static function checkServerExists(string $url, ProcessExecutor $processExecutor): bool
    {
        return 0 === $processExecutor->execute(['p4', '-p', $url, 'info', '-s'], $ignoredOutput);
    }

    /**
     * @phpstan-param RepoConfig $repoConfig
     */
    public function initialize($repoConfig): void
    {
        $this->uniquePerforceClientName = $this->generateUniquePerforceClientName();
        if (!$repoConfig) {
            return;
        }
        if (isset($repoConfig['unique_perforce_client_name'])) {
            $this->uniquePerforceClientName = $repoConfig['unique_perforce_client_name'];
        }

        if (isset($repoConfig['depot'])) {
            $this->p4Depot = $repoConfig['depot'];
        }
        if (isset($repoConfig['branch'])) {
            $this->p4Branch = $repoConfig['branch'];
        }
        if (isset($repoConfig['p4user'])) {
            $this->p4User = $repoConfig['p4user'];
        } else {
            $this->p4User = $this->getP4variable('P4USER');
        }
        if (isset($repoConfig['p4password'])) {
            $this->p4Password = $repoConfig['p4password'];
        }
    }

    public function initializeDepotAndBranch(?string $depot, ?string $branch): void
    {
        if (isset($depot)) {
            $this->p4Depot = $depot;
        }
        if (isset($branch)) {
            $this->p4Branch = $branch;
        }
    }

    /**
     * @return non-empty-string
     */
    public function generateUniquePerforceClientName(): string
    {
        return gethostname() . "_" . time();
    }

    public function cleanupClientSpec(): void
    {
        $client = $this->getClient();
        $task = 'client -d ' . ProcessExecutor::escape($client);
        $useP4Client = false;
        $command = $this->generateP4Command($task, $useP4Client);
        $this->executeCommand($command);
        $clientSpec = $this->getP4ClientSpec();
        $fileSystem = $this->getFilesystem();
        $fileSystem->remove($clientSpec);
    }

    /**
     * @param non-empty-string $command
     */
    protected function executeCommand($command): int
    {
        $this->commandResult = '';

        return $this->process->execute($command, $this->commandResult);
    }

    public function getClient(): string
    {
        if (!isset($this->p4Client)) {
            $cleanStreamName = str_replace(['//', '/', '@'], ['', '_', ''], $this->getStream());
            $this->p4Client = 'composer_perforce_' . $this->uniquePerforceClientName . '_' . $cleanStreamName;
        }

        return $this->p4Client;
    }

    protected function getPath(): string
    {
        return $this->path;
    }

    public function initializePath(string $path): void
    {
        $this->path = $path;
        $fs = $this->getFilesystem();
        $fs->ensureDirectoryExists($path);
    }

    protected function getPort(): string
    {
        return $this->p4Port;
    }

    public function setStream(string $stream): void
    {
        $this->p4Stream = $stream;
        $index = strrpos($stream, '/');
        //Stream format is //depot/stream, while non-streaming depot is //depot
        if ($index > 2) {
            $this->p4DepotType = 'stream';
        }
    }

    public function isStream(): bool
    {
        return is_string($this->p4DepotType) && (strcmp($this->p4DepotType, 'stream') === 0);
    }

    public function getStream(): string
    {
        if (!isset($this->p4Stream)) {
            if ($this->isStream()) {
                $this->p4Stream = '//' . $this->p4Depot . '/' . $this->p4Branch;
            } else {
                $this->p4Stream = '//' . $this->p4Depot;
            }
        }

        return $this->p4Stream;
    }

    public function getStreamWithoutLabel(string $stream): string
    {
        $index = strpos($stream, '@');
        if ($index === false) {
            return $stream;
        }

        return substr($stream, 0, $index);
    }

    /**
     * @return non-empty-string
     */
    public function getP4ClientSpec(): string
    {
        return $this->path . '/' . $this->getClient() . '.p4.spec';
    }

    public function getUser(): ?string
    {
        return $this->p4User;
    }

    public function setUser(?string $user): void
    {
        $this->p4User = $user;
    }

    public function queryP4User(): void
    {
        $this->getUser();
        if (strlen((string) $this->p4User) > 0) {
            return;
        }
        $this->p4User = $this->getP4variable('P4USER');
        if (strlen((string) $this->p4User) > 0) {
            return;
        }
        $this->p4User = $this->io->ask('Enter P4 User:');
        if ($this->windowsFlag) {
            $command = $this->getP4Executable().' set P4USER=' . $this->p4User;
        } else {
            $command = 'export P4USER=' . $this->p4User;
        }
        $this->executeCommand($command);
    }

    /**
     * @return ?string
     */
    protected function getP4variable(string $name): ?string
    {
        if ($this->windowsFlag) {
            $command = $this->getP4Executable().' set';
            $this->executeCommand($command);
            $result = trim($this->commandResult);
            $resArray = explode(PHP_EOL, $result);
            foreach ($resArray as $line) {
                $fields = explode('=', $line);
                if (strcmp($name, $fields[0]) === 0) {
                    $index = strpos($fields[1], ' ');
                    if ($index === false) {
                        $value = $fields[1];
                    } else {
                        $value = substr($fields[1], 0, $index);
                    }
                    $value = trim($value);

                    return $value;
                }
            }

            return null;
        }

        $command = 'echo $' . $name;
        $this->executeCommand($command);
        $result = trim($this->commandResult);

        return $result;
    }

    public function queryP4Password(): ?string
    {
        if (isset($this->p4Password)) {
            return $this->p4Password;
        }
        $password = $this->getP4variable('P4PASSWD');
        if (strlen((string) $password) <= 0) {
            $password = $this->io->askAndHideAnswer('Enter password for Perforce user ' . $this->getUser() . ': ');
        }
        $this->p4Password = $password;

        return $password;
    }

    /**
     * @return non-empty-string
     */
    public function generateP4Command(string $command, bool $useClient = true): string
    {
        $p4Command = $this->getP4Executable().' ';
        $p4Command .= '-u ' . $this->getUser() . ' ';
        if ($useClient) {
            $p4Command .= '-c ' . $this->getClient() . ' ';
        }
        $p4Command .= '-p ' . $this->getPort() . ' ' . $command;

        return $p4Command;
    }

    public function isLoggedIn(): bool
    {
        $command = $this->generateP4Command('login -s', false);
        $exitCode = $this->executeCommand($command);
        if ($exitCode) {
            $errorOutput = $this->process->getErrorOutput();
            $index = strpos($errorOutput, $this->getUser());
            if ($index === false) {
                $index = strpos($errorOutput, 'p4');
                if ($index === false) {
                    return false;
                }
                throw new \Exception('p4 command not found in path: ' . $errorOutput);
            }
            throw new \Exception('Invalid user name: ' . $this->getUser());
        }

        return true;
    }

    public function connectClient(): void
    {
        $p4CreateClientCommand = $this->generateP4Command(
            'client -i < ' . ProcessExecutor::escape($this->getP4ClientSpec())
        );
        $this->executeCommand($p4CreateClientCommand);
    }

    public function syncCodeBase(?string $sourceReference): void
    {
        $prevDir = Platform::getCwd();
        chdir($this->path);
        $p4SyncCommand = $this->generateP4Command('sync -f ');
        if (null !== $sourceReference) {
            $p4SyncCommand .= '@' . $sourceReference;
        }
        $this->executeCommand($p4SyncCommand);
        chdir($prevDir);
    }

    /**
     * @param resource|false $spec
     */
    public function writeClientSpecToFile($spec): void
    {
        fwrite($spec, 'Client: ' . $this->getClient() . PHP_EOL . PHP_EOL);
        fwrite($spec, 'Update: ' . date('Y/m/d H:i:s') . PHP_EOL . PHP_EOL);
        fwrite($spec, 'Access: ' . date('Y/m/d H:i:s') . PHP_EOL);
        fwrite($spec, 'Owner:  ' . $this->getUser() . PHP_EOL . PHP_EOL);
        fwrite($spec, 'Description:' . PHP_EOL);
        fwrite($spec, '  Created by ' . $this->getUser() . ' from composer.' . PHP_EOL . PHP_EOL);
        fwrite($spec, 'Root: ' . $this->getPath() . PHP_EOL . PHP_EOL);
        fwrite($spec, 'Options:  noallwrite noclobber nocompress unlocked modtime rmdir' . PHP_EOL . PHP_EOL);
        fwrite($spec, 'SubmitOptions:  revertunchanged' . PHP_EOL . PHP_EOL);
        fwrite($spec, 'LineEnd:  local' . PHP_EOL . PHP_EOL);
        if ($this->isStream()) {
            fwrite($spec, 'Stream:' . PHP_EOL);
            fwrite($spec, '  ' . $this->getStreamWithoutLabel($this->p4Stream) . PHP_EOL);
        } else {
            fwrite(
                $spec,
                'View:  ' . $this->getStream() . '/...  //' . $this->getClient() . '/... ' . PHP_EOL
            );
        }
    }

    public function writeP4ClientSpec(): void
    {
        $clientSpec = $this->getP4ClientSpec();
        $spec = fopen($clientSpec, 'w');
        try {
            $this->writeClientSpecToFile($spec);
        } catch (\Exception $e) {
            fclose($spec);
            throw $e;
        }
        fclose($spec);
    }

    /**
     * @param resource $pipe
     * @param mixed    $name
     */
    protected function read($pipe, $name): void
    {
        if (feof($pipe)) {
            return;
        }
        $line = fgets($pipe);
        while ($line !== false) {
            $line = fgets($pipe);
        }
    }

    public function windowsLogin(?string $password): int
    {
        $command = $this->generateP4Command(' login -a');

        $process = Process::fromShellCommandline($command, null, null, $password);

        return $process->run();
    }

    public function p4Login(): void
    {
        $this->queryP4User();
        if (!$this->isLoggedIn()) {
            $password = $this->queryP4Password();
            if ($this->windowsFlag) {
                $this->windowsLogin($password);
            } else {
                $command = 'echo ' . ProcessExecutor::escape($password)  . ' | ' . $this->generateP4Command(' login -a', false);
                $exitCode = $this->executeCommand($command);
                if ($exitCode) {
                    throw new \Exception("Error logging in:" . $this->process->getErrorOutput());
                }
            }
        }
    }

    /**
     * @return mixed[]|null
     */
    public function getComposerInformation(string $identifier): ?array
    {
        $composerFileContent = $this->getFileContent('composer.json', $identifier);

        if (!$composerFileContent) {
            return null;
        }

        return json_decode($composerFileContent, true);
    }

    public function getFileContent(string $file, string $identifier): ?string
    {
        $path = $this->getFilePath($file, $identifier);

        $command = $this->generateP4Command(' print ' . ProcessExecutor::escape($path));
        $this->executeCommand($command);
        $result = $this->commandResult;

        if (!trim($result)) {
            return null;
        }

        return $result;
    }

    public function getFilePath(string $file, string $identifier): ?string
    {
        $index = strpos($identifier, '@');
        if ($index === false) {
            return $identifier. '/' . $file;
        }

        $path = substr($identifier, 0, $index) . '/' . $file . substr($identifier, $index);
        $command = $this->generateP4Command(' files ' . ProcessExecutor::escape($path), false);
        $this->executeCommand($command);
        $result = $this->commandResult;
        $index2 = strpos($result, 'no such file(s).');
        if ($index2 === false) {
            $index3 = strpos($result, 'change');
            if ($index3 !== false) {
                $phrase = trim(substr($result, $index3));
                $fields = explode(' ', $phrase);

                return substr($identifier, 0, $index) . '/' . $file . '@' . $fields[1];
            }
        }

        return null;
    }

    /**
     * @return array{master: string}
     */
    public function getBranches(): array
    {
        $possibleBranches = [];
        if (!$this->isStream()) {
            $possibleBranches[$this->p4Branch] = $this->getStream();
        } else {
            $command = $this->generateP4Command('streams '.ProcessExecutor::escape('//' . $this->p4Depot . '/...'));
            $this->executeCommand($command);
            $result = $this->commandResult;
            $resArray = explode(PHP_EOL, $result);
            foreach ($resArray as $line) {
                $resBits = explode(' ', $line);
                if (count($resBits) > 4) {
                    $branch = Preg::replace('/[^A-Za-z0-9 ]/', '', $resBits[4]);
                    $possibleBranches[$branch] = $resBits[1];
                }
            }
        }
        $command = $this->generateP4Command('changes '. ProcessExecutor::escape($this->getStream() . '/...'), false);
        $this->executeCommand($command);
        $result = $this->commandResult;
        $resArray = explode(PHP_EOL, $result);
        $lastCommit = $resArray[0];
        $lastCommitArr = explode(' ', $lastCommit);
        $lastCommitNum = $lastCommitArr[1];

        return ['master' => $possibleBranches[$this->p4Branch] . '@'. $lastCommitNum];
    }

    /**
     * @return array<string, string>
     */
    public function getTags(): array
    {
        $command = $this->generateP4Command('labels');
        $this->executeCommand($command);
        $result = $this->commandResult;
        $resArray = explode(PHP_EOL, $result);
        $tags = [];
        foreach ($resArray as $line) {
            if (strpos($line, 'Label') !== false) {
                $fields = explode(' ', $line);
                $tags[$fields[1]] = $this->getStream() . '@' . $fields[1];
            }
        }

        return $tags;
    }

    public function checkStream(): bool
    {
        $command = $this->generateP4Command('depots', false);
        $this->executeCommand($command);
        $result = $this->commandResult;
        $resArray = explode(PHP_EOL, $result);
        foreach ($resArray as $line) {
            if (strpos($line, 'Depot') !== false) {
                $fields = explode(' ', $line);
                if (strcmp($this->p4Depot, $fields[1]) === 0) {
                    $this->p4DepotType = $fields[3];

                    return $this->isStream();
                }
            }
        }

        return false;
    }

    /**
     * @return mixed|null
     */
    protected function getChangeList(string $reference): mixed
    {
        $index = strpos($reference, '@');
        if ($index === false) {
            return null;
        }
        $label = substr($reference, $index);
        $command = $this->generateP4Command(' changes -m1 ' . ProcessExecutor::escape($label));
        $this->executeCommand($command);
        $changes = $this->commandResult;
        if (strpos($changes, 'Change') !== 0) {
            return null;
        }
        $fields = explode(' ', $changes);

        return $fields[1];
    }

    /**
     * @return mixed|null
     */
    public function getCommitLogs(string $fromReference, string $toReference): mixed
    {
        $fromChangeList = $this->getChangeList($fromReference);
        if ($fromChangeList === null) {
            return null;
        }
        $toChangeList = $this->getChangeList($toReference);
        if ($toChangeList === null) {
            return null;
        }
        $index = strpos($fromReference, '@');
        $main = substr($fromReference, 0, $index) . '/...';
        $command = $this->generateP4Command('filelog ' . ProcessExecutor::escape($main . '@' . $fromChangeList. ',' . $toChangeList));
        $this->executeCommand($command);

        return $this->commandResult;
    }

    public function getFilesystem(): Filesystem
    {
        if (null === $this->filesystem) {
            $this->filesystem = new Filesystem($this->process);
        }

        return $this->filesystem;
    }

    public function setFilesystem(Filesystem $fs): void
    {
        $this->filesystem = $fs;
    }

    private function getP4Executable(): string
    {
        static $p4Executable;

        if ($p4Executable) {
            return $p4Executable;
        }

        $finder = new ExecutableFinder();

        return $p4Executable = $finder->find('p4') ?? 'p4';
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Package\CompletePackageInterface;
use Composer\Package\PackageInterface;

class PackageInfo
{
    public static function getViewSourceUrl(PackageInterface $package): ?string
    {
        if ($package instanceof CompletePackageInterface && isset($package->getSupport()['source']) && '' !== $package->getSupport()['source']) {
            return $package->getSupport()['source'];
        }

        return $package->getSourceUrl();
    }

    public static function getViewSourceOrHomepageUrl(PackageInterface $package): ?string
    {
        $url = self::getViewSourceUrl($package) ?? ($package instanceof CompletePackageInterface ? $package->getHomepage() : null);

        if ($url === '') {
            return null;
        }

        return $url;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Config;
use Composer\Pcre\Preg;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Url
{
    /**
     * @param non-empty-string $url
     * @return non-empty-string the updated URL
     */
    public static function updateDistReference(Config $config, string $url, string $ref): string
    {
        $host = parse_url($url, PHP_URL_HOST);

        if ($host === 'api.github.com' || $host === 'github.com' || $host === 'www.github.com') {
            if (Preg::isMatch('{^https?://(?:www\.)?github\.com/([^/]+)/([^/]+)/(zip|tar)ball/(.+)$}i', $url, $match)) {
                // update legacy github archives to API calls with the proper reference
                $url = 'https://api.github.com/repos/' . $match[1] . '/'. $match[2] . '/' . $match[3] . 'ball/' . $ref;
            } elseif (Preg::isMatch('{^https?://(?:www\.)?github\.com/([^/]+)/([^/]+)/archive/.+\.(zip|tar)(?:\.gz)?$}i', $url, $match)) {
                // update current github web archives to API calls with the proper reference
                $url = 'https://api.github.com/repos/' . $match[1] . '/'. $match[2] . '/' . $match[3] . 'ball/' . $ref;
            } elseif (Preg::isMatch('{^https?://api\.github\.com/repos/([^/]+)/([^/]+)/(zip|tar)ball(?:/.+)?$}i', $url, $match)) {
                // update api archives to the proper reference
                $url = 'https://api.github.com/repos/' . $match[1] . '/'. $match[2] . '/' . $match[3] . 'ball/' . $ref;
            }
        } elseif ($host === 'bitbucket.org' || $host === 'www.bitbucket.org') {
            if (Preg::isMatch('{^https?://(?:www\.)?bitbucket\.org/([^/]+)/([^/]+)/get/(.+)\.(zip|tar\.gz|tar\.bz2)$}i', $url, $match)) {
                // update Bitbucket archives to the proper reference
                $url = 'https://bitbucket.org/' . $match[1] . '/'. $match[2] . '/get/' . $ref . '.' . $match[4];
            }
        } elseif ($host === 'gitlab.com' || $host === 'www.gitlab.com') {
            if (Preg::isMatch('{^https?://(?:www\.)?gitlab\.com/api/v[34]/projects/([^/]+)/repository/archive\.(zip|tar\.gz|tar\.bz2|tar)\?sha=.+$}i', $url, $match)) {
                // update Gitlab archives to the proper reference
                $url = 'https://gitlab.com/api/v4/projects/' . $match[1] . '/repository/archive.' . $match[2] . '?sha=' . $ref;
            }
        } elseif (in_array($host, $config->get('github-domains'), true)) {
            $url = Preg::replace('{(/repos/[^/]+/[^/]+/(zip|tar)ball)(?:/.+)?$}i', '$1/'.$ref, $url);
        } elseif (in_array($host, $config->get('gitlab-domains'), true)) {
            $url = Preg::replace('{(/api/v[34]/projects/[^/]+/repository/archive\.(?:zip|tar\.gz|tar\.bz2|tar)\?sha=).+$}i', '${1}'.$ref, $url);
        }

        assert($url !== '');

        return $url;
    }

    /**
     * @param non-empty-string $url
     * @return non-empty-string
     */
    public static function getOrigin(Config $config, string $url): string
    {
        if (0 === strpos($url, 'file://')) {
            return $url;
        }

        $origin = (string) parse_url($url, PHP_URL_HOST);
        if ($port = parse_url($url, PHP_URL_PORT)) {
            $origin .= ':'.$port;
        }

        if (str_ends_with($origin, '.github.com') && $origin !== 'codeload.github.com') {
            return 'github.com';
        }

        if ($origin === 'repo.packagist.org') {
            return 'packagist.org';
        }

        if ($origin === '') {
            $origin = $url;
        }

        // Gitlab can be installed in a non-root context (i.e. gitlab.com/foo). When downloading archives the originUrl
        // is the host without the path, so we look for the registered gitlab-domains matching the host here
        if (
            false === strpos($origin, '/')
            && !in_array($origin, $config->get('gitlab-domains'), true)
        ) {
            foreach ($config->get('gitlab-domains') as $gitlabDomain) {
                if ($gitlabDomain !== '' && str_starts_with($gitlabDomain, $origin)) {
                    return $gitlabDomain;
                }
            }
        }

        return $origin;
    }

    public static function sanitize(string $url): string
    {
        // GitHub repository rename result in redirect locations containing the access_token as GET parameter
        // e.g. https://api.github.com/repositories/9999999999?access_token=github_token
        $url = Preg::replace('{([&?]access_token=)[^&]+}', '$1***', $url);

        $url = Preg::replaceCallback('{^(?P<prefix>[a-z0-9]+://)?(?P<user>[^:/\s@]+):(?P<password>[^@\s/]+)@}i', static function ($m): string {
            // if the username looks like a long (12char+) hex string, or a modern github token (e.g. ghp_xxx, github_pat_xxx) we obfuscate that
            if (Preg::isMatch(GitHub::GITHUB_TOKEN_REGEX, $m['user'])) {
                return $m['prefix'].'***:***@';
            }

            return $m['prefix'].$m['user'].':***@';
        }, $url);

        return $url;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Downloader\TransportException;
use Composer\Pcre\Preg;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class AuthHelper
{
    /** @var IOInterface */
    protected $io;
    /** @var Config */
    protected $config;
    /** @var array<string, string> Map of origins to message displayed */
    private $displayedOriginAuthentications = [];
    /** @var array<string, bool> Map of URLs and whether they already retried with authentication from Bitbucket */
    private $bitbucketRetry = [];

    public function __construct(IOInterface $io, Config $config)
    {
        $this->io = $io;
        $this->config = $config;
    }

    /**
     * @param 'prompt'|bool $storeAuth
     */
    public function storeAuth(string $origin, $storeAuth): void
    {
        $store = false;
        $configSource = $this->config->getAuthConfigSource();
        if ($storeAuth === true) {
            $store = $configSource;
        } elseif ($storeAuth === 'prompt') {
            $answer = $this->io->askAndValidate(
                'Do you want to store credentials for '.$origin.' in '.$configSource->getName().' ? [Yn] ',
                static function ($value): string {
                    $input = strtolower(substr(trim($value), 0, 1));
                    if (in_array($input, ['y','n'])) {
                        return $input;
                    }
                    throw new \RuntimeException('Please answer (y)es or (n)o');
                },
                null,
                'y'
            );

            if ($answer === 'y') {
                $store = $configSource;
            }
        }
        if ($store) {
            $store->addConfigSetting(
                'http-basic.'.$origin,
                $this->io->getAuthentication($origin)
            );
        }
    }

    /**
     * @param  int         $statusCode HTTP status code that triggered this call
     * @param  string|null $reason     a message/description explaining why this was called
     * @param  string[]    $headers
     * @param  int         $retryCount the amount of retries already done on this URL
     * @return array       containing retry (bool) and storeAuth (string|bool) keys, if retry is true the request should be
     *                                retried, if storeAuth is true then on a successful retry the authentication should be persisted to auth.json
     * @phpstan-return array{retry: bool, storeAuth: 'prompt'|bool}
     */
    public function promptAuthIfNeeded(string $url, string $origin, int $statusCode, ?string $reason = null, array $headers = [], int $retryCount = 0): array
    {
        $storeAuth = false;

        if (in_array($origin, $this->config->get('github-domains'), true)) {
            $gitHubUtil = new GitHub($this->io, $this->config, null);
            $message = "\n";

            $rateLimited = $gitHubUtil->isRateLimited($headers);
            $requiresSso = $gitHubUtil->requiresSso($headers);

            if ($requiresSso) {
                $ssoUrl = $gitHubUtil->getSsoUrl($headers);
                $message = 'GitHub API token requires SSO authorization. Authorize this token at ' . $ssoUrl . "\n";
                $this->io->writeError($message);
                if (!$this->io->isInteractive()) {
                    throw new TransportException('Could not authenticate against ' . $origin, 403);
                }
                $this->io->ask('After authorizing your token, confirm that you would like to retry the request');

                return ['retry' => true, 'storeAuth' => $storeAuth];
            }

            if ($rateLimited) {
                $rateLimit = $gitHubUtil->getRateLimit($headers);
                if ($this->io->hasAuthentication($origin)) {
                    $message = 'Review your configured GitHub OAuth token or enter a new one to go over the API rate limit.';
                } else {
                    $message = 'Create a GitHub OAuth token to go over the API rate limit.';
                }

                $message = sprintf(
                    'GitHub API limit (%d calls/hr) is exhausted, could not fetch '.$url.'. '.$message.' You can also wait until %s for the rate limit to reset.',
                    $rateLimit['limit'],
                    $rateLimit['reset']
                )."\n";
            } else {
                $message .= 'Could not fetch '.$url.', please ';
                if ($this->io->hasAuthentication($origin)) {
                    $message .= 'review your configured GitHub OAuth token or enter a new one to access private repos';
                } else {
                    $message .= 'create a GitHub OAuth token to access private repos';
                }
            }

            if (!$gitHubUtil->authorizeOAuth($origin)
                && (!$this->io->isInteractive() || !$gitHubUtil->authorizeOAuthInteractively($origin, $message))
            ) {
                throw new TransportException('Could not authenticate against '.$origin, 401);
            }
        } elseif (in_array($origin, $this->config->get('gitlab-domains'), true)) {
            $message = "\n".'Could not fetch '.$url.', enter your ' . $origin . ' credentials ' .($statusCode === 401 ? 'to access private repos' : 'to go over the API rate limit');
            $gitLabUtil = new GitLab($this->io, $this->config, null);

            $auth = null;
            if ($this->io->hasAuthentication($origin)) {
                $auth = $this->io->getAuthentication($origin);
                if (in_array($auth['password'], ['gitlab-ci-token', 'private-token', 'oauth2'], true)) {
                    throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $statusCode);
                }
            }

            if (!$gitLabUtil->authorizeOAuth($origin)
                && (!$this->io->isInteractive() || !$gitLabUtil->authorizeOAuthInteractively(parse_url($url, PHP_URL_SCHEME), $origin, $message))
            ) {
                throw new TransportException('Could not authenticate against '.$origin, 401);
            }

            if ($auth !== null && $this->io->hasAuthentication($origin)) {
                if ($auth === $this->io->getAuthentication($origin)) {
                    throw new TransportException("Invalid credentials for '" . $url . "', aborting.", $statusCode);
                }
            }
        } elseif ($origin === 'bitbucket.org' || $origin === 'api.bitbucket.org') {
            $askForOAuthToken = true;
            $origin = 'bitbucket.org';
            if ($this->io->hasAuthentication($origin)) {
                $auth = $this->io->getAuthentication($origin);
                if ($auth['username'] !== 'x-token-auth') {
                    $bitbucketUtil = new Bitbucket($this->io, $this->config);
                    $accessToken = $bitbucketUtil->requestToken($origin, $auth['username'], $auth['password']);
                    if (!empty($accessToken)) {
                        $this->io->setAuthentication($origin, 'x-token-auth', $accessToken);
                        $askForOAuthToken = false;
                    }
                } elseif (!isset($this->bitbucketRetry[$url])) {
                    // when multiple requests fire at the same time, they will all fail and the first one resets the token to be correct above but then the others
                    // reach the code path and without this fallback they would end up throwing below
                    // see https://github.com/composer/composer/pull/11464 for more details
                    $askForOAuthToken = false;
                    $this->bitbucketRetry[$url] = true;
                } else {
                    throw new TransportException('Could not authenticate against ' . $origin, 401);
                }
            }

            if ($askForOAuthToken) {
                $message = "\n".'Could not fetch ' . $url . ', please create a bitbucket OAuth token to ' . (($statusCode === 401 || $statusCode === 403) ? 'access private repos' : 'go over the API rate limit');
                $bitBucketUtil = new Bitbucket($this->io, $this->config);
                if (!$bitBucketUtil->authorizeOAuth($origin)
                    && (!$this->io->isInteractive() || !$bitBucketUtil->authorizeOAuthInteractively($origin, $message))
                ) {
                    throw new TransportException('Could not authenticate against ' . $origin, 401);
                }
            }
        } else {
            // 404s are only handled for github
            if ($statusCode === 404) {
                return ['retry' => false, 'storeAuth' => false];
            }

            // fail if the console is not interactive
            if (!$this->io->isInteractive()) {
                if ($statusCode === 401) {
                    $message = "The '" . $url . "' URL required authentication (HTTP 401).\nYou must be using the interactive console to authenticate";
                } elseif ($statusCode === 403) {
                    $message = "The '" . $url . "' URL could not be accessed (HTTP 403): " . $reason;
                } else {
                    $message = "Unknown error code '" . $statusCode . "', reason: " . $reason;
                }

                throw new TransportException($message, $statusCode);
            }

            // fail if we already have auth
            if ($this->io->hasAuthentication($origin)) {
                // if two or more requests are started together for the same host, and the first
                // received authentication already, we let the others retry before failing them
                if ($retryCount === 0) {
                    return ['retry' => true, 'storeAuth' => false];
                }

                throw new TransportException("Invalid credentials (HTTP $statusCode) for '$url', aborting.", $statusCode);
            }

            $this->io->writeError('    Authentication required (<info>'.$origin.'</info>):');
            $username = $this->io->ask('      Username: ');
            $password = $this->io->askAndHideAnswer('      Password: ');
            $this->io->setAuthentication($origin, $username, $password);
            $storeAuth = $this->config->get('store-auths');
        }

        return ['retry' => true, 'storeAuth' => $storeAuth];
    }

    /**
     * @param string[] $headers
     *
     * @return string[] updated headers array
     */
    public function addAuthenticationHeader(array $headers, string $origin, string $url): array
    {
        if ($this->io->hasAuthentication($origin)) {
            $authenticationDisplayMessage = null;
            $auth = $this->io->getAuthentication($origin);
            if ($auth['password'] === 'bearer') {
                $headers[] = 'Authorization: Bearer '.$auth['username'];
            } elseif ('github.com' === $origin && 'x-oauth-basic' === $auth['password']) {
                // only add the access_token if it is actually a github API URL
                if (Preg::isMatch('{^https?://api\.github\.com/}', $url)) {
                    $headers[] = 'Authorization: token '.$auth['username'];
                    $authenticationDisplayMessage = 'Using GitHub token authentication';
                }
            } elseif (
                in_array($origin, $this->config->get('gitlab-domains'), true)
                && in_array($auth['password'], ['oauth2', 'private-token', 'gitlab-ci-token'], true)
            ) {
                if ($auth['password'] === 'oauth2') {
                    $headers[] = 'Authorization: Bearer '.$auth['username'];
                    $authenticationDisplayMessage = 'Using GitLab OAuth token authentication';
                } else {
                    $headers[] = 'PRIVATE-TOKEN: '.$auth['username'];
                    $authenticationDisplayMessage = 'Using GitLab private token authentication';
                }
            } elseif (
                'bitbucket.org' === $origin
                && $url !== Bitbucket::OAUTH2_ACCESS_TOKEN_URL
                && 'x-token-auth' === $auth['username']
            ) {
                if (!$this->isPublicBitBucketDownload($url)) {
                    $headers[] = 'Authorization: Bearer ' . $auth['password'];
                    $authenticationDisplayMessage = 'Using Bitbucket OAuth token authentication';
                }
            } else {
                $authStr = base64_encode($auth['username'] . ':' . $auth['password']);
                $headers[] = 'Authorization: Basic '.$authStr;
                $authenticationDisplayMessage = 'Using HTTP basic authentication with username "' . $auth['username'] . '"';
            }

            if ($authenticationDisplayMessage && (!isset($this->displayedOriginAuthentications[$origin]) || $this->displayedOriginAuthentications[$origin] !== $authenticationDisplayMessage)) {
                $this->io->writeError($authenticationDisplayMessage, true, IOInterface::DEBUG);
                $this->displayedOriginAuthentications[$origin] = $authenticationDisplayMessage;
            }
        } elseif (in_array($origin, ['api.bitbucket.org', 'api.github.com'], true)) {
            return $this->addAuthenticationHeader($headers, str_replace('api.', '', $origin), $url);
        }

        return $headers;
    }

    /**
     * @link https://github.com/composer/composer/issues/5584
     *
     * @param string $urlToBitBucketFile URL to a file at bitbucket.org.
     *
     * @return bool Whether the given URL is a public BitBucket download which requires no authentication.
     */
    public function isPublicBitBucketDownload(string $urlToBitBucketFile): bool
    {
        $domain = parse_url($urlToBitBucketFile, PHP_URL_HOST);
        if (strpos($domain, 'bitbucket.org') === false) {
            // Bitbucket downloads are hosted on amazonaws.
            // We do not need to authenticate there at all
            return true;
        }

        $path = parse_url($urlToBitBucketFile, PHP_URL_PATH);

        // Path for a public download follows this pattern /{user}/{repo}/downloads/{whatever}
        // {@link https://blog.bitbucket.org/2009/04/12/new-feature-downloads/}
        $pathParts = explode('/', $path);

        return count($pathParts) >= 4 && $pathParts[3] === 'downloads';
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use React\Promise\CancellablePromiseInterface;
use Symfony\Component\Console\Helper\ProgressBar;
use React\Promise\PromiseInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Loop
{
    /** @var HttpDownloader */
    private $httpDownloader;
    /** @var ProcessExecutor|null */
    private $processExecutor;
    /** @var array<int, array<PromiseInterface<mixed>>> */
    private $currentPromises = [];
    /** @var int */
    private $waitIndex = 0;

    public function __construct(HttpDownloader $httpDownloader, ?ProcessExecutor $processExecutor = null)
    {
        $this->httpDownloader = $httpDownloader;
        $this->httpDownloader->enableAsync();

        $this->processExecutor = $processExecutor;
        if ($this->processExecutor) {
            $this->processExecutor->enableAsync();
        }
    }

    public function getHttpDownloader(): HttpDownloader
    {
        return $this->httpDownloader;
    }

    public function getProcessExecutor(): ?ProcessExecutor
    {
        return $this->processExecutor;
    }

    /**
     * @param array<PromiseInterface<mixed>> $promises
     * @param ProgressBar|null              $progress
     */
    public function wait(array $promises, ?ProgressBar $progress = null): void
    {
        $uncaught = null;

        \React\Promise\all($promises)->then(
            static function (): void {
            },
            static function (\Throwable $e) use (&$uncaught): void {
                $uncaught = $e;
            }
        );

        // keep track of every group of promises that is waited on, so abortJobs can
        // cancel them all, even if wait() was called within a wait()
        $waitIndex = $this->waitIndex++;
        $this->currentPromises[$waitIndex] = $promises;

        if ($progress) {
            $totalJobs = 0;
            $totalJobs += $this->httpDownloader->countActiveJobs();
            if ($this->processExecutor) {
                $totalJobs += $this->processExecutor->countActiveJobs();
            }
            $progress->start($totalJobs);
        }

        $lastUpdate = 0;
        while (true) {
            $activeJobs = 0;

            $activeJobs += $this->httpDownloader->countActiveJobs();
            if ($this->processExecutor) {
                $activeJobs += $this->processExecutor->countActiveJobs();
            }

            if ($progress && microtime(true) - $lastUpdate > 0.1) {
                $lastUpdate = microtime(true);
                $progress->setProgress($progress->getMaxSteps() - $activeJobs);
            }

            if (!$activeJobs) {
                break;
            }
        }

        // as we skip progress updates if they are too quick, make sure we do one last one here at 100%
        if ($progress) {
            $progress->finish();
        }

        unset($this->currentPromises[$waitIndex]);
        if (null !== $uncaught) {
            throw $uncaught;
        }
    }

    public function abortJobs(): void
    {
        foreach ($this->currentPromises as $promiseGroup) {
            foreach ($promiseGroup as $promise) {
                // to support react/promise 2.x we wrap the promise in a resolve() call for safety
                \React\Promise\resolve($promise)->cancel();
            }
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Composer;
use Composer\CaBundle\CaBundle;
use Composer\Downloader\TransportException;
use Composer\Repository\PlatformRepository;
use Composer\Util\Http\ProxyManager;
use Psr\Log\LoggerInterface;

/**
 * Allows the creation of a basic context supporting http proxy
 *
 * @author Jordan Alliot <jordan.alliot@gmail.com>
 * @author Markus Tacker <m@coderbyheart.de>
 */
final class StreamContextFactory
{
    /**
     * Creates a context supporting HTTP proxies
     *
     * @param non-empty-string $url URL the context is to be used for
     * @phpstan-param array{http?: array{follow_location?: int, max_redirects?: int, header?: string|array<string>}} $defaultOptions
     * @param  mixed[]           $defaultOptions Options to merge with the default
     * @param  mixed[]           $defaultParams  Parameters to specify on the context
     * @throws \RuntimeException if https proxy required and OpenSSL uninstalled
     * @return resource          Default context
     */
    public static function getContext(string $url, array $defaultOptions = [], array $defaultParams = [])
    {
        $options = ['http' => [
            // specify defaults again to try and work better with curlwrappers enabled
            'follow_location' => 1,
            'max_redirects' => 20,
        ]];

        $options = array_replace_recursive($options, self::initOptions($url, $defaultOptions));
        unset($defaultOptions['http']['header']);
        $options = array_replace_recursive($options, $defaultOptions);

        if (isset($options['http']['header'])) {
            $options['http']['header'] = self::fixHttpHeaderField($options['http']['header']);
        }

        return stream_context_create($options, $defaultParams);
    }

    /**
     * @param non-empty-string $url
     * @param mixed[] $options
     * @param bool    $forCurl When true, will not add proxy values as these are handled separately
     * @phpstan-return array{http: array{header: string[], proxy?: string, request_fulluri: bool}, ssl?: mixed[]}
     * @return array formatted as a stream context array
     */
    public static function initOptions(string $url, array $options, bool $forCurl = false): array
    {
        // Make sure the headers are in an array form
        if (!isset($options['http']['header'])) {
            $options['http']['header'] = [];
        }
        if (is_string($options['http']['header'])) {
            $options['http']['header'] = explode("\r\n", $options['http']['header']);
        }

        // Add stream proxy options if there is a proxy
        if (!$forCurl) {
            $proxy = ProxyManager::getInstance()->getProxyForRequest($url);
            $proxyOptions = $proxy->getContextOptions();
            if ($proxyOptions !== null) {
                $isHttpsRequest = 0 === strpos($url, 'https://');

                if ($proxy->isSecure()) {
                    if (!extension_loaded('openssl')) {
                        throw new TransportException('You must enable the openssl extension to use a secure proxy.');
                    }
                    if ($isHttpsRequest) {
                        throw new TransportException('You must enable the curl extension to make https requests through a secure proxy.');
                    }
                } elseif ($isHttpsRequest && !extension_loaded('openssl')) {
                    throw new TransportException('You must enable the openssl extension to make https requests through a proxy.');
                }

                // Header will be a Proxy-Authorization string or not set
                if (isset($proxyOptions['http']['header'])) {
                    $options['http']['header'][] = $proxyOptions['http']['header'];
                    unset($proxyOptions['http']['header']);
                }
                $options = array_replace_recursive($options, $proxyOptions);
            }
        }

        if (defined('HHVM_VERSION')) {
            $phpVersion = 'HHVM ' . HHVM_VERSION;
        } else {
            $phpVersion = 'PHP ' . PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION;
        }

        if ($forCurl) {
            $curl = curl_version();
            $httpVersion = 'cURL '.$curl['version'];
        } else {
            $httpVersion = 'streams';
        }

        if (!isset($options['http']['header']) || false === stripos(implode('', $options['http']['header']), 'user-agent')) {
            $platformPhpVersion = PlatformRepository::getPlatformPhpVersion();
            $options['http']['header'][] = sprintf(
                'User-Agent: Composer/%s (%s; %s; %s; %s%s%s)',
                Composer::getVersion(),
                function_exists('php_uname') ? php_uname('s') : 'Unknown',
                function_exists('php_uname') ? php_uname('r') : 'Unknown',
                $phpVersion,
                $httpVersion,
                $platformPhpVersion ? '; Platform-PHP '.$platformPhpVersion : '',
                Platform::getEnv('CI') ? '; CI' : ''
            );
        }

        return $options;
    }

    /**
     * @param mixed[] $options
     *
     * @return mixed[]
     */
    public static function getTlsDefaults(array $options, ?LoggerInterface $logger = null): array
    {
        $ciphers = implode(':', [
            'ECDHE-RSA-AES128-GCM-SHA256',
            'ECDHE-ECDSA-AES128-GCM-SHA256',
            'ECDHE-RSA-AES256-GCM-SHA384',
            'ECDHE-ECDSA-AES256-GCM-SHA384',
            'DHE-RSA-AES128-GCM-SHA256',
            'DHE-DSS-AES128-GCM-SHA256',
            'kEDH+AESGCM',
            'ECDHE-RSA-AES128-SHA256',
            'ECDHE-ECDSA-AES128-SHA256',
            'ECDHE-RSA-AES128-SHA',
            'ECDHE-ECDSA-AES128-SHA',
            'ECDHE-RSA-AES256-SHA384',
            'ECDHE-ECDSA-AES256-SHA384',
            'ECDHE-RSA-AES256-SHA',
            'ECDHE-ECDSA-AES256-SHA',
            'DHE-RSA-AES128-SHA256',
            'DHE-RSA-AES128-SHA',
            'DHE-DSS-AES128-SHA256',
            'DHE-RSA-AES256-SHA256',
            'DHE-DSS-AES256-SHA',
            'DHE-RSA-AES256-SHA',
            'AES128-GCM-SHA256',
            'AES256-GCM-SHA384',
            'AES128-SHA256',
            'AES256-SHA256',
            'AES128-SHA',
            'AES256-SHA',
            'AES',
            'CAMELLIA',
            'DES-CBC3-SHA',
            '!aNULL',
            '!eNULL',
            '!EXPORT',
            '!DES',
            '!RC4',
            '!MD5',
            '!PSK',
            '!aECDH',
            '!EDH-DSS-DES-CBC3-SHA',
            '!EDH-RSA-DES-CBC3-SHA',
            '!KRB5-DES-CBC3-SHA',
        ]);

        /**
         * CN_match and SNI_server_name are only known once a URL is passed.
         * They will be set in the getOptionsForUrl() method which receives a URL.
         *
         * cafile or capath can be overridden by passing in those options to constructor.
         */
        $defaults = [
            'ssl' => [
                'ciphers' => $ciphers,
                'verify_peer' => true,
                'verify_depth' => 7,
                'SNI_enabled' => true,
                'capture_peer_cert' => true,
            ],
        ];

        if (isset($options['ssl'])) {
            $defaults['ssl'] = array_replace_recursive($defaults['ssl'], $options['ssl']);
        }

        /**
         * Attempt to find a local cafile or throw an exception if none pre-set
         * The user may go download one if this occurs.
         */
        if (!isset($defaults['ssl']['cafile']) && !isset($defaults['ssl']['capath'])) {
            $result = CaBundle::getSystemCaRootBundlePath($logger);

            if (is_dir($result)) {
                $defaults['ssl']['capath'] = $result;
            } else {
                $defaults['ssl']['cafile'] = $result;
            }
        }

        if (isset($defaults['ssl']['cafile']) && (!Filesystem::isReadable($defaults['ssl']['cafile']) || !CaBundle::validateCaFile($defaults['ssl']['cafile'], $logger))) {
            throw new TransportException('The configured cafile was not valid or could not be read.');
        }

        if (isset($defaults['ssl']['capath']) && (!is_dir($defaults['ssl']['capath']) || !Filesystem::isReadable($defaults['ssl']['capath']))) {
            throw new TransportException('The configured capath was not valid or could not be read.');
        }

        /**
         * Disable TLS compression to prevent CRIME attacks where supported.
         */
        $defaults['ssl']['disable_compression'] = true;

        return $defaults;
    }

    /**
     * A bug in PHP prevents the headers from correctly being sent when a content-type header is present and
     * NOT at the end of the array
     *
     * This method fixes the array by moving the content-type header to the end
     *
     * @link https://bugs.php.net/bug.php?id=61548
     * @param  string|string[] $header
     * @return string[]
     */
    private static function fixHttpHeaderField($header): array
    {
        if (!is_array($header)) {
            $header = explode("\r\n", $header);
        }
        uasort($header, static function ($el): int {
            return stripos($el, 'content-type') === 0 ? 1 : -1;
        });

        return $header;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Pcre\Preg;

/**
 * @author Till Klampaeckel <till@php.net>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Svn
{
    private const MAX_QTY_AUTH_TRIES = 5;

    /**
     * @var ?array{username: string, password: string}
     */
    protected $credentials;

    /**
     * @var bool
     */
    protected $hasAuth;

    /**
     * @var \Composer\IO\IOInterface
     */
    protected $io;

    /**
     * @var string
     */
    protected $url;

    /**
     * @var bool
     */
    protected $cacheCredentials = true;

    /**
     * @var ProcessExecutor
     */
    protected $process;

    /**
     * @var int
     */
    protected $qtyAuthTries = 0;

    /**
     * @var \Composer\Config
     */
    protected $config;

    /**
     * @var string|null
     */
    private static $version;

    /**
     * @param ProcessExecutor          $process
     */
    public function __construct(string $url, IOInterface $io, Config $config, ?ProcessExecutor $process = null)
    {
        $this->url = $url;
        $this->io = $io;
        $this->config = $config;
        $this->process = $process ?: new ProcessExecutor($io);
    }

    public static function cleanEnv(): void
    {
        // clean up env for OSX, see https://github.com/composer/composer/issues/2146#issuecomment-35478940
        Platform::clearEnv('DYLD_LIBRARY_PATH');
    }

    /**
     * Execute an SVN remote command and try to fix up the process with credentials
     * if necessary.
     *
     * @param non-empty-list<string> $command SVN command to run
     * @param string  $url     SVN url
     * @param ?string $cwd     Working directory
     * @param ?string $path    Target for a checkout
     * @param bool    $verbose Output all output to the user
     *
     * @throws \RuntimeException
     */
    public function execute(array $command, string $url, ?string $cwd = null, ?string $path = null, bool $verbose = false): string
    {
        // Ensure we are allowed to use this URL by config
        $this->config->prohibitUrlByConfig($url, $this->io);

        return $this->executeWithAuthRetry($command, $cwd, $url, $path, $verbose);
    }

    /**
     * Execute an SVN local command and try to fix up the process with credentials
     * if necessary.
     *
     * @param non-empty-list<string> $command SVN command to run
     * @param string $path    Path argument passed thru to the command
     * @param string $cwd     Working directory
     * @param bool   $verbose Output all output to the user
     *
     * @throws \RuntimeException
     */
    public function executeLocal(array $command, string $path, ?string $cwd = null, bool $verbose = false): string
    {
        // A local command has no remote url
        return $this->executeWithAuthRetry($command, $cwd, '', $path, $verbose);
    }

    /**
     * @param non-empty-list<string> $svnCommand
     */
    private function executeWithAuthRetry(array $svnCommand, ?string $cwd, string $url, ?string $path, bool $verbose): ?string
    {
        // Regenerate the command at each try, to use the newly user-provided credentials
        $command = $this->getCommand($svnCommand, $url, $path);

        $output = null;
        $io = $this->io;
        $handler = static function ($type, $buffer) use (&$output, $io, $verbose) {
            if ($type !== 'out') {
                return null;
            }
            if (strpos($buffer, 'Redirecting to URL ') === 0) {
                return null;
            }
            $output .= $buffer;
            if ($verbose) {
                $io->writeError($buffer, false);
            }
        };
        $status = $this->process->execute($command, $handler, $cwd);
        if (0 === $status) {
            return $output;
        }

        $errorOutput = $this->process->getErrorOutput();
        $fullOutput = trim(implode("\n", [$output, $errorOutput]));

        // the error is not auth-related
        if (false === stripos($fullOutput, 'Could not authenticate to server:')
            && false === stripos($fullOutput, 'authorization failed')
            && false === stripos($fullOutput, 'svn: E170001:')
            && false === stripos($fullOutput, 'svn: E215004:')) {
            throw new \RuntimeException($fullOutput);
        }

        if (!$this->hasAuth()) {
            $this->doAuthDance();
        }

        // try to authenticate if maximum quantity of tries not reached
        if ($this->qtyAuthTries++ < self::MAX_QTY_AUTH_TRIES) {
            // restart the process
            return $this->executeWithAuthRetry($svnCommand, $cwd, $url, $path, $verbose);
        }

        throw new \RuntimeException(
            'wrong credentials provided ('.$fullOutput.')'
        );
    }

    public function setCacheCredentials(bool $cacheCredentials): void
    {
        $this->cacheCredentials = $cacheCredentials;
    }

    /**
     * Repositories requests credentials, let's put them in.
     *
     * @throws \RuntimeException
     * @return \Composer\Util\Svn
     */
    protected function doAuthDance(): Svn
    {
        // cannot ask for credentials in non interactive mode
        if (!$this->io->isInteractive()) {
            throw new \RuntimeException(
                'can not ask for authentication in non interactive mode'
            );
        }

        $this->io->writeError("The Subversion server ({$this->url}) requested credentials:");

        $this->hasAuth = true;
        $this->credentials = [
            'username' => (string) $this->io->ask("Username: ", ''),
            'password' => (string) $this->io->askAndHideAnswer("Password: "),
        ];

        $this->cacheCredentials = $this->io->askConfirmation("Should Subversion cache these credentials? (yes/no) ");

        return $this;
    }

    /**
     * A method to create the svn commands run.
     *
     * @param non-empty-list<string> $cmd  Usually 'svn ls' or something like that.
     * @param string $url  Repo URL.
     * @param string $path Target for a checkout
     *
     * @return non-empty-list<string>
     */
    protected function getCommand(array $cmd, string $url, ?string $path = null): array
    {
        $cmd = array_merge(
            $cmd,
            ['--non-interactive'],
            $this->getCredentialArgs(),
            ['--', $url]
        );

        if ($path !== null) {
            $cmd[] = $path;
        }

        return $cmd;
    }

    /**
     * Return the credential string for the svn command.
     *
     * Adds --no-auth-cache when credentials are present.
     *
     * @return list<string>
     */
    protected function getCredentialArgs(): array
    {
        if (!$this->hasAuth()) {
            return [];
        }

        return array_merge(
            $this->getAuthCacheArgs(),
            ['--username', $this->getUsername(), '--password', $this->getPassword()]
        );
    }

    /**
     * Get the password for the svn command. Can be empty.
     *
     * @throws \LogicException
     */
    protected function getPassword(): string
    {
        if ($this->credentials === null) {
            throw new \LogicException("No svn auth detected.");
        }

        return $this->credentials['password'];
    }

    /**
     * Get the username for the svn command.
     *
     * @throws \LogicException
     */
    protected function getUsername(): string
    {
        if ($this->credentials === null) {
            throw new \LogicException("No svn auth detected.");
        }

        return $this->credentials['username'];
    }

    /**
     * Detect Svn Auth.
     */
    protected function hasAuth(): bool
    {
        if (null !== $this->hasAuth) {
            return $this->hasAuth;
        }

        if (false === $this->createAuthFromConfig()) {
            $this->createAuthFromUrl();
        }

        return (bool) $this->hasAuth;
    }

    /**
     * Return the no-auth-cache switch.
     *
     * @return list<string>
     */
    protected function getAuthCacheArgs(): array
    {
        return $this->cacheCredentials ? [] : ['--no-auth-cache'];
    }

    /**
     * Create the auth params from the configuration file.
     */
    private function createAuthFromConfig(): bool
    {
        if (!$this->config->has('http-basic')) {
            return $this->hasAuth = false;
        }

        $authConfig = $this->config->get('http-basic');

        $host = parse_url($this->url, PHP_URL_HOST);
        if (isset($authConfig[$host])) {
            $this->credentials = [
                'username' => $authConfig[$host]['username'],
                'password' => $authConfig[$host]['password'],
            ];

            return $this->hasAuth = true;
        }

        return $this->hasAuth = false;
    }

    /**
     * Create the auth params from the url
     */
    private function createAuthFromUrl(): bool
    {
        $uri = parse_url($this->url);
        if (empty($uri['user'])) {
            return $this->hasAuth = false;
        }

        $this->credentials = [
            'username' => $uri['user'],
            'password' => !empty($uri['pass']) ? $uri['pass'] : '',
        ];

        return $this->hasAuth = true;
    }

    /**
     * Returns the version of the svn binary contained in PATH
     */
    public function binaryVersion(): ?string
    {
        if (!self::$version) {
            if (0 === $this->process->execute(['svn', '--version'], $output)) {
                if (Preg::isMatch('{(\d+(?:\.\d+)+)}', $output, $match)) {
                    self::$version = $match[1];
                }
            }
        }

        return self::$version;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\Config;
use Composer\Downloader\TransportException;

/**
 * @author Paul Wenke <wenke.paul@gmail.com>
 */
class Bitbucket
{
    /** @var IOInterface */
    private $io;
    /** @var Config */
    private $config;
    /** @var ProcessExecutor */
    private $process;
    /** @var HttpDownloader */
    private $httpDownloader;
    /** @var array{access_token: string, expires_in?: int}|null */
    private $token = null;
    /** @var int|null */
    private $time;

    public const OAUTH2_ACCESS_TOKEN_URL = 'https://bitbucket.org/site/oauth2/access_token';

    /**
     * Constructor.
     *
     * @param IOInterface     $io             The IO instance
     * @param Config          $config         The composer configuration
     * @param ProcessExecutor $process        Process instance, injectable for mocking
     * @param HttpDownloader  $httpDownloader Remote Filesystem, injectable for mocking
     * @param int             $time           Timestamp, injectable for mocking
     */
    public function __construct(IOInterface $io, Config $config, ?ProcessExecutor $process = null, ?HttpDownloader $httpDownloader = null, ?int $time = null)
    {
        $this->io = $io;
        $this->config = $config;
        $this->process = $process ?: new ProcessExecutor($io);
        $this->httpDownloader = $httpDownloader ?: Factory::createHttpDownloader($this->io, $config);
        $this->time = $time;
    }

    public function getToken(): string
    {
        if (!isset($this->token['access_token'])) {
            return '';
        }

        return $this->token['access_token'];
    }

    /**
     * Attempts to authorize a Bitbucket domain via OAuth
     *
     * @param  string $originUrl The host this Bitbucket instance is located at
     * @return bool   true on success
     */
    public function authorizeOAuth(string $originUrl): bool
    {
        if ($originUrl !== 'bitbucket.org') {
            return false;
        }

        // if available use token from git config
        if (0 === $this->process->execute(['git', 'config', 'bitbucket.accesstoken'], $output)) {
            $this->io->setAuthentication($originUrl, 'x-token-auth', trim($output));

            return true;
        }

        return false;
    }

    private function requestAccessToken(): bool
    {
        try {
            $response = $this->httpDownloader->get(self::OAUTH2_ACCESS_TOKEN_URL, [
                'retry-auth-failure' => false,
                'http' => [
                    'method' => 'POST',
                    'content' => 'grant_type=client_credentials',
                ],
            ]);

            $token = $response->decodeJson();
            if (!isset($token['expires_in']) || !isset($token['access_token'])) {
                throw new \LogicException('Expected a token configured with expires_in and access_token present, got '.json_encode($token));
            }

            $this->token = $token;
        } catch (TransportException $e) {
            if ($e->getCode() === 400) {
                $this->io->writeError('<error>Invalid OAuth consumer provided.</error>');
                $this->io->writeError('This can have three reasons:');
                $this->io->writeError('1. You are authenticating with a bitbucket username/password combination');
                $this->io->writeError('2. You are using an OAuth consumer, but didn\'t configure a (dummy) callback url');
                $this->io->writeError('3. You are using an OAuth consumer, but didn\'t configure it as private consumer');

                return false;
            }
            if (in_array($e->getCode(), [403, 401])) {
                $this->io->writeError('<error>Invalid OAuth consumer provided.</error>');
                $this->io->writeError('You can also add it manually later by using "composer config --global --auth bitbucket-oauth.bitbucket.org <consumer-key> <consumer-secret>"');

                return false;
            }

            throw $e;
        }

        return true;
    }

    /**
     * Authorizes a Bitbucket domain interactively via OAuth
     *
     * @param  string                        $originUrl The host this Bitbucket instance is located at
     * @param  string                        $message   The reason this authorization is required
     * @throws \RuntimeException
     * @throws TransportException|\Exception
     * @return bool                          true on success
     */
    public function authorizeOAuthInteractively(string $originUrl, ?string $message = null): bool
    {
        if ($message) {
            $this->io->writeError($message);
        }

        $localAuthConfig = $this->config->getLocalAuthConfigSource();
        $url = 'https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/';
        $this->io->writeError('Follow the instructions here:');
        $this->io->writeError($url);
        $this->io->writeError(sprintf('to create a consumer. It will be stored in "%s" for future use by Composer.', ($localAuthConfig !== null ? $localAuthConfig->getName() . ' OR ' : '') . $this->config->getAuthConfigSource()->getName()));
        $this->io->writeError('Ensure you enter a "Callback URL" (http://example.com is fine) or it will not be possible to create an Access Token (this callback url will not be used by composer)');

        $storeInLocalAuthConfig = false;
        if ($localAuthConfig !== null) {
            $storeInLocalAuthConfig = $this->io->askConfirmation('A local auth config source was found, do you want to store the token there?', true);
        }

        $consumerKey = trim((string) $this->io->askAndHideAnswer('Consumer Key (hidden): '));

        if (!$consumerKey) {
            $this->io->writeError('<warning>No consumer key given, aborting.</warning>');
            $this->io->writeError('You can also add it manually later by using "composer config --global --auth bitbucket-oauth.bitbucket.org <consumer-key> <consumer-secret>"');

            return false;
        }

        $consumerSecret = trim((string) $this->io->askAndHideAnswer('Consumer Secret (hidden): '));

        if (!$consumerSecret) {
            $this->io->writeError('<warning>No consumer secret given, aborting.</warning>');
            $this->io->writeError('You can also add it manually later by using "composer config --global --auth bitbucket-oauth.bitbucket.org <consumer-key> <consumer-secret>"');

            return false;
        }

        $this->io->setAuthentication($originUrl, $consumerKey, $consumerSecret);

        if (!$this->requestAccessToken()) {
            return false;
        }

        // store value in user config
        $authConfigSource = $storeInLocalAuthConfig && $localAuthConfig !== null ? $localAuthConfig : $this->config->getAuthConfigSource();
        $this->storeInAuthConfig($authConfigSource, $originUrl, $consumerKey, $consumerSecret);

        // Remove conflicting basic auth credentials (if available)
        $this->config->getAuthConfigSource()->removeConfigSetting('http-basic.' . $originUrl);

        $this->io->writeError('<info>Consumer stored successfully.</info>');

        return true;
    }

    /**
     * Retrieves an access token from Bitbucket.
     */
    public function requestToken(string $originUrl, string $consumerKey, string $consumerSecret): string
    {
        if ($this->token !== null || $this->getTokenFromConfig($originUrl)) {
            return $this->token['access_token'];
        }

        $this->io->setAuthentication($originUrl, $consumerKey, $consumerSecret);
        if (!$this->requestAccessToken()) {
            return '';
        }

        $this->storeInAuthConfig($this->config->getLocalAuthConfigSource() ?? $this->config->getAuthConfigSource(), $originUrl, $consumerKey, $consumerSecret);

        if (!isset($this->token['access_token'])) {
            throw new \LogicException('Failed to initialize token above');
        }

        return $this->token['access_token'];
    }

    /**
     * Store the new/updated credentials to the configuration
     */
    private function storeInAuthConfig(Config\ConfigSourceInterface $authConfigSource, string $originUrl, string $consumerKey, string $consumerSecret): void
    {
        $this->config->getConfigSource()->removeConfigSetting('bitbucket-oauth.'.$originUrl);

        if (null === $this->token || !isset($this->token['expires_in'])) {
            throw new \LogicException('Expected a token configured with expires_in present, got '.json_encode($this->token));
        }

        $time = null === $this->time ? time() : $this->time;
        $consumer = [
            "consumer-key" => $consumerKey,
            "consumer-secret" => $consumerSecret,
            "access-token" => $this->token['access_token'],
            "access-token-expiration" => $time + $this->token['expires_in'],
        ];

        $this->config->getAuthConfigSource()->addConfigSetting('bitbucket-oauth.'.$originUrl, $consumer);
    }

    /**
     * @phpstan-assert-if-true array{access_token: string} $this->token
     */
    private function getTokenFromConfig(string $originUrl): bool
    {
        $authConfig = $this->config->get('bitbucket-oauth');

        if (
            !isset($authConfig[$originUrl]['access-token'], $authConfig[$originUrl]['access-token-expiration'])
            || time() > $authConfig[$originUrl]['access-token-expiration']
        ) {
            return false;
        }

        $this->token = [
            'access_token' => $authConfig[$originUrl]['access-token'],
        ];

        return true;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Package\PackageInterface;
use Composer\Package\RootPackageInterface;

class PackageSorter
{
    /**
     * Returns the most recent version of a set of packages
     *
     * This is ideally the default branch version, or failing that it will return the package with the highest version
     *
     * @template T of PackageInterface
     * @param array<T> $packages
     * @return ($packages is non-empty-array<T> ? T : T|null)
     */
    public static function getMostCurrentVersion(array $packages): ?PackageInterface
    {
        if (count($packages) === 0) {
            return null;
        }

        $highest = reset($packages);
        foreach ($packages as $candidate) {
            if ($candidate->isDefaultBranch()) {
                return $candidate;
            }

            if (version_compare($highest->getVersion(), $candidate->getVersion(), '<')) {
                $highest = $candidate;
            }
        }

        return $highest;
    }

    /**
     * Sorts packages by name
     *
     * @template T of PackageInterface
     * @param array<T> $packages
     * @return array<T>
     */
    public static function sortPackagesAlphabetically(array $packages): array
    {
        usort($packages, static function (PackageInterface $a, PackageInterface $b) {
            return $a->getName() <=> $b->getName();
        });

        return $packages;
    }

    /**
     * Sorts packages by dependency weight
     *
     * Packages of equal weight are sorted alphabetically
     *
     * @param  PackageInterface[] $packages
     * @param  array<string, int> $weights Pre-set weights for some packages to give them more (negative number) or less (positive) weight offsets
     * @return PackageInterface[] sorted array
     */
    public static function sortPackages(array $packages, array $weights = []): array
    {
        $usageList = [];

        foreach ($packages as $package) {
            $links = $package->getRequires();
            if ($package instanceof RootPackageInterface) {
                $links = array_merge($links, $package->getDevRequires());
            }
            foreach ($links as $link) {
                $target = $link->getTarget();
                $usageList[$target][] = $package->getName();
            }
        }
        $computing = [];
        $computed = [];
        $computeImportance = static function ($name) use (&$computeImportance, &$computing, &$computed, $usageList, $weights) {
            // reusing computed importance
            if (isset($computed[$name])) {
                return $computed[$name];
            }

            // canceling circular dependency
            if (isset($computing[$name])) {
                return 0;
            }

            $computing[$name] = true;
            $weight = $weights[$name] ?? 0;

            if (isset($usageList[$name])) {
                foreach ($usageList[$name] as $user) {
                    $weight -= 1 - $computeImportance($user);
                }
            }

            unset($computing[$name]);
            $computed[$name] = $weight;

            return $weight;
        };

        $weightedPackages = [];

        foreach ($packages as $index => $package) {
            $name = $package->getName();
            $weight = $computeImportance($name);
            $weightedPackages[] = ['name' => $name, 'weight' => $weight, 'index' => $index];
        }

        usort($weightedPackages, static function (array $a, array $b): int {
            if ($a['weight'] !== $b['weight']) {
                return $a['weight'] - $b['weight'];
            }

            return strnatcasecmp($a['name'], $b['name']);
        });

        $sortedPackages = [];

        foreach ($weightedPackages as $pkg) {
            $sortedPackages[] = $packages[$pkg['index']];
        }

        return $sortedPackages;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Pcre\Preg;
use stdClass;

/**
 * Tests URLs against NO_PROXY patterns
 */
class NoProxyPattern
{
    /**
     * @var string[]
     */
    protected $hostNames = [];

    /**
     * @var (null|object)[]
     */
    protected $rules = [];

    /**
     * @var bool
     */
    protected $noproxy;

    /**
     * @param string $pattern NO_PROXY pattern
     */
    public function __construct(string $pattern)
    {
        $this->hostNames = Preg::split('{[\s,]+}', $pattern, -1, PREG_SPLIT_NO_EMPTY);
        $this->noproxy = empty($this->hostNames) || '*' === $this->hostNames[0];
    }

    /**
     * Returns true if a URL matches the NO_PROXY pattern
     */
    public function test(string $url): bool
    {
        if ($this->noproxy) {
            return true;
        }

        if (!$urlData = $this->getUrlData($url)) {
            return false;
        }

        foreach ($this->hostNames as $index => $hostName) {
            if ($this->match($index, $hostName, $urlData)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Returns false is the url cannot be parsed, otherwise a data object
     *
     * @return bool|stdClass
     */
    protected function getUrlData(string $url)
    {
        if (!$host = parse_url($url, PHP_URL_HOST)) {
            return false;
        }

        $port = parse_url($url, PHP_URL_PORT);

        if (empty($port)) {
            switch (parse_url($url, PHP_URL_SCHEME)) {
                case 'http':
                    $port = 80;
                    break;
                case 'https':
                    $port = 443;
                    break;
            }
        }

        $hostName = $host . ($port ? ':' . $port : '');
        [$host, $port, $err] = $this->splitHostPort($hostName);

        if ($err || !$this->ipCheckData($host, $ipdata)) {
            return false;
        }

        return $this->makeData($host, $port, $ipdata);
    }

    /**
     * Returns true if the url is matched by a rule
     */
    protected function match(int $index, string $hostName, stdClass $url): bool
    {
        if (!$rule = $this->getRule($index, $hostName)) {
            // Data must have been misformatted
            return false;
        }

        if ($rule->ipdata) {
            // Match ipdata first
            if (!$url->ipdata) {
                return false;
            }

            if ($rule->ipdata->netmask) {
                return $this->matchRange($rule->ipdata, $url->ipdata);
            }

            $match = $rule->ipdata->ip === $url->ipdata->ip;
        } else {
            // Match host and port
            $haystack = substr($url->name, -strlen($rule->name));
            $match = stripos($haystack, $rule->name) === 0;
        }

        if ($match && $rule->port) {
            $match = $rule->port === $url->port;
        }

        return $match;
    }

    /**
     * Returns true if the target ip is in the network range
     */
    protected function matchRange(stdClass $network, stdClass $target): bool
    {
        $net = unpack('C*', $network->ip);
        $mask = unpack('C*', $network->netmask);
        $ip = unpack('C*', $target->ip);
        if (false === $net) {
            throw new \RuntimeException('Could not parse network IP '.$network->ip);
        }
        if (false === $mask) {
            throw new \RuntimeException('Could not parse netmask '.$network->netmask);
        }
        if (false === $ip) {
            throw new \RuntimeException('Could not parse target IP '.$target->ip);
        }

        for ($i = 1; $i < 17; ++$i) {
            if (($net[$i] & $mask[$i]) !== ($ip[$i] & $mask[$i])) {
                return false;
            }
        }

        return true;
    }

    /**
     * Finds or creates rule data for a hostname
     *
     * @return null|stdClass Null if the hostname is invalid
     */
    private function getRule(int $index, string $hostName): ?stdClass
    {
        if (array_key_exists($index, $this->rules)) {
            return $this->rules[$index];
        }

        $this->rules[$index] = null;
        [$host, $port, $err] = $this->splitHostPort($hostName);

        if ($err || !$this->ipCheckData($host, $ipdata, true)) {
            return null;
        }

        $this->rules[$index] = $this->makeData($host, $port, $ipdata);

        return $this->rules[$index];
    }

    /**
     * Creates an object containing IP data if the host is an IP address
     *
     * @param null|stdClass $ipdata      Set by method if IP address found
     * @param bool          $allowPrefix Whether a CIDR prefix-length is expected
     *
     * @return bool False if the host contains invalid data
     */
    private function ipCheckData(string $host, ?stdClass &$ipdata, bool $allowPrefix = false): bool
    {
        $ipdata = null;
        $netmask = null;
        $prefix = null;
        $modified = false;

        // Check for a CIDR prefix-length
        if (strpos($host, '/') !== false) {
            [$host, $prefix] = explode('/', $host);

            if (!$allowPrefix || !$this->validateInt($prefix, 0, 128)) {
                return false;
            }
            $prefix = (int) $prefix;
            $modified = true;
        }

        // See if this is an ip address
        if (!filter_var($host, FILTER_VALIDATE_IP)) {
            return !$modified;
        }

        [$ip, $size] = $this->ipGetAddr($host);

        if ($prefix !== null) {
            // Check for a valid prefix
            if ($prefix > $size * 8) {
                return false;
            }

            [$ip, $netmask] = $this->ipGetNetwork($ip, $size, $prefix);
        }

        $ipdata = $this->makeIpData($ip, $size, $netmask);

        return true;
    }

    /**
     * Returns an array of the IP in_addr and its byte size
     *
     * IPv4 addresses are always mapped to IPv6, which simplifies handling
     * and comparison.
     *
     * @return mixed[] in_addr, size
     */
    private function ipGetAddr(string $host): array
    {
        $ip = inet_pton($host);
        $size = strlen($ip);
        $mapped = $this->ipMapTo6($ip, $size);

        return [$mapped, $size];
    }

    /**
     * Returns the binary network mask mapped to IPv6
     *
     * @param int $prefix CIDR prefix-length
     * @param int $size   Byte size of in_addr
     */
    private function ipGetMask(int $prefix, int $size): string
    {
        $mask = '';

        if ($ones = floor($prefix / 8)) {
            $mask = str_repeat(chr(255), (int) $ones);
        }

        if ($remainder = $prefix % 8) {
            $mask .= chr(0xff ^ (0xff >> $remainder));
        }

        $mask = str_pad($mask, $size, chr(0));

        return $this->ipMapTo6($mask, $size);
    }

    /**
     * Calculates and returns the network and mask
     *
     * @param string $rangeIp IP in_addr
     * @param int    $size    Byte size of in_addr
     * @param int    $prefix  CIDR prefix-length
     *
     * @return string[] network in_addr, binary mask
     */
    private function ipGetNetwork(string $rangeIp, int $size, int $prefix): array
    {
        $netmask = $this->ipGetMask($prefix, $size);

        // Get the network from the address and mask
        $mask = unpack('C*', $netmask);
        $ip = unpack('C*', $rangeIp);
        $net = '';
        if (false === $mask) {
            throw new \RuntimeException('Could not parse netmask '.$netmask);
        }
        if (false === $ip) {
            throw new \RuntimeException('Could not parse range IP '.$rangeIp);
        }

        for ($i = 1; $i < 17; ++$i) {
            $net .= chr($ip[$i] & $mask[$i]);
        }

        return [$net, $netmask];
    }

    /**
     * Maps an IPv4 address to IPv6
     *
     * @param string $binary in_addr
     * @param int    $size   Byte size of in_addr
     *
     * @return string Mapped or existing in_addr
     */
    private function ipMapTo6(string $binary, int $size): string
    {
        if ($size === 4) {
            $prefix = str_repeat(chr(0), 10) . str_repeat(chr(255), 2);
            $binary = $prefix . $binary;
        }

        return $binary;
    }

    /**
     * Creates a rule data object
     */
    private function makeData(string $host, int $port, ?stdClass $ipdata): stdClass
    {
        return (object) [
            'host' => $host,
            'name' => '.' . ltrim($host, '.'),
            'port' => $port,
            'ipdata' => $ipdata,
        ];
    }

    /**
     * Creates an ip data object
     *
     * @param string      $ip      in_addr
     * @param int         $size    Byte size of in_addr
     * @param null|string $netmask Network mask
     */
    private function makeIpData(string $ip, int $size, ?string $netmask): stdClass
    {
        return (object) [
            'ip' => $ip,
            'size' => $size,
            'netmask' => $netmask,
        ];
    }

    /**
     * Splits the hostname into host and port components
     *
     * @return mixed[] host, port, if there was error
     */
    private function splitHostPort(string $hostName): array
    {
        // host, port, err
        $error = ['', '', true];
        $port = 0;
        $ip6 = '';

        // Check for square-bracket notation
        if ($hostName[0] === '[') {
            $index = strpos($hostName, ']');

            // The smallest ip6 address is ::
            if (false === $index || $index < 3) {
                return $error;
            }

            $ip6 = substr($hostName, 1, $index - 1);
            $hostName = substr($hostName, $index + 1);

            if (strpbrk($hostName, '[]') !== false || substr_count($hostName, ':') > 1) {
                return $error;
            }
        }

        if (substr_count($hostName, ':') === 1) {
            $index = strpos($hostName, ':');
            $port = substr($hostName, $index + 1);
            $hostName = substr($hostName, 0, $index);

            if (!$this->validateInt($port, 1, 65535)) {
                return $error;
            }

            $port = (int) $port;
        }

        $host = $ip6 . $hostName;

        return [$host, $port, false];
    }

    /**
     * Wrapper around filter_var FILTER_VALIDATE_INT
     */
    private function validateInt(string $int, int $min, int $max): bool
    {
        $options = [
            'options' => [
                'min_range' => $min,
                'max_range' => $max,
            ],
        ];

        return false !== filter_var($int, FILTER_VALIDATE_INT, $options);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\IO\IOInterface;

/**
 * Convert PHP errors into exceptions
 *
 * @author Artem Lopata <biozshock@gmail.com>
 */
class ErrorHandler
{
    /** @var ?IOInterface */
    private static $io;

    /** @var int<0, 2> */
    private static $hasShownDeprecationNotice = 0;

    /**
     * Error handler
     *
     * @param int    $level   Level of the error raised
     * @param string $message Error message
     * @param string $file    Filename that the error was raised in
     * @param int    $line    Line number the error was raised at
     *
     * @static
     * @throws \ErrorException
     */
    public static function handle(int $level, string $message, string $file, int $line): bool
    {
        $isDeprecationNotice = $level === E_DEPRECATED || $level === E_USER_DEPRECATED;

        // error code is not included in error_reporting
        if (!$isDeprecationNotice && 0 === (error_reporting() & $level)) {
            return true;
        }

        if (filter_var(ini_get('xdebug.scream'), FILTER_VALIDATE_BOOLEAN)) {
            $message .= "\n\nWarning: You have xdebug.scream enabled, the warning above may be".
            "\na legitimately suppressed error that you were not supposed to see.";
        }

        if (!$isDeprecationNotice) {
            // ignore some newly introduced warnings in new php versions until dependencies
            // can be fixed as we do not want to abort execution for those
            if (in_array($level, [E_WARNING, E_USER_WARNING], true) && str_contains($message, 'should either be used or intentionally ignored by casting it as (void)')) {
                self::outputWarning('Ignored new PHP warning but it should be reported and fixed: '.$message.' in '.$file.':'.$line, true);
                return true;
            }

            throw new \ErrorException($message, 0, $level, $file, $line);
        }

        if (self::$io !== null) {
            if (self::$hasShownDeprecationNotice > 0 && !self::$io->isVerbose()) {
                if (self::$hasShownDeprecationNotice === 1) {
                    self::$io->writeError('<warning>More deprecation notices were hidden, run again with `-v` to show them.</warning>');
                    self::$hasShownDeprecationNotice = 2;
                }
                return true;
            }
            self::$hasShownDeprecationNotice = 1;
            self::outputWarning('Deprecation Notice: '.$message.' in '.$file.':'.$line);
        }

        return true;
    }

    /**
     * Register error handler.
     */
    public static function register(?IOInterface $io = null): void
    {
        set_error_handler([__CLASS__, 'handle']);
        error_reporting(E_ALL);
        self::$io = $io;
    }

    private static function outputWarning(string $message, bool $outputEvenWithoutIO = false): void
    {
        if (self::$io !== null) {
            self::$io->writeError('<warning>'.$message.'</warning>');
            if (self::$io->isVerbose()) {
                self::$io->writeError('<warning>Stack trace:</warning>');
                self::$io->writeError(array_filter(array_map(static function ($a): ?string {
                    if (isset($a['line'], $a['file'])) {
                        return '<warning> '.$a['file'].':'.$a['line'].'</warning>';
                    }

                    return null;
                }, array_slice(debug_backtrace(), 2)), function (?string $line) {
                    return $line !== null;
                }));
            }

            return;
        }

        if ($outputEvenWithoutIO) {
            if (defined('STDERR') && is_resource(STDERR)) {
                fwrite(STDERR, 'Warning: '.$message.PHP_EOL);
            } else {
                echo 'Warning: '.$message.PHP_EOL;
            }
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\IO\IOInterface;
use Composer\Pcre\Preg;
use Seld\Signal\SignalHandler;
use Symfony\Component\Process\Exception\ProcessSignaledException;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\RuntimeException;
use React\Promise\Promise;
use React\Promise\PromiseInterface;
use Symfony\Component\Process\ExecutableFinder;

/**
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ProcessExecutor
{
    private const STATUS_QUEUED = 1;
    private const STATUS_STARTED = 2;
    private const STATUS_COMPLETED = 3;
    private const STATUS_FAILED = 4;
    private const STATUS_ABORTED = 5;

    private const BUILTIN_CMD_COMMANDS = [
        'assoc', 'break', 'call', 'cd', 'chdir', 'cls', 'color', 'copy', 'date',
        'del', 'dir', 'echo', 'endlocal', 'erase', 'exit', 'for', 'ftype', 'goto',
        'help', 'if', 'label', 'md', 'mkdir', 'mklink', 'move', 'path', 'pause',
        'popd', 'prompt', 'pushd', 'rd', 'rem', 'ren', 'rename', 'rmdir', 'set',
        'setlocal', 'shift', 'start', 'time', 'title', 'type', 'ver', 'vol',
    ];

    private const GIT_CMDS_NEED_GIT_DIR = [
        ['show'],
        ['log'],
        ['branch'],
        ['remote', 'set-url']
    ];

    /** @var int */
    protected static $timeout = 300;

    /** @var bool */
    protected $captureOutput = false;
    /** @var string */
    protected $errorOutput = '';
    /** @var ?IOInterface */
    protected $io;

    /**
     * @phpstan-var array<int, array<string, mixed>>
     */
    private $jobs = [];
    /** @var int */
    private $runningJobs = 0;
    /** @var int */
    private $maxJobs = 10;
    /** @var int */
    private $idGen = 0;
    /** @var bool */
    private $allowAsync = false;

    /** @var array<string, string> */
    private static $executables = [];

    public function __construct(?IOInterface $io = null)
    {
        $this->io = $io;
        $this->resetMaxJobs();
    }

    /**
     * runs a process on the commandline
     *
     * @param  string|non-empty-list<string> $command the command to execute
     * @param  mixed   $output  the output will be written into this var if passed by ref
     *                          if a callable is passed it will be used as output handler
     * @param  null|string $cwd     the working directory
     * @return int     statuscode
     */
    public function execute($command, &$output = null, ?string $cwd = null): int
    {
        if (func_num_args() > 1) {
            return $this->doExecute($command, $cwd, false, $output);
        }

        return $this->doExecute($command, $cwd, false);
    }

    /**
     * runs a process on the commandline in TTY mode
     *
     * @param  string|non-empty-list<string>  $command the command to execute
     * @param  null|string $cwd     the working directory
     * @return int     statuscode
     */
    public function executeTty($command, ?string $cwd = null): int
    {
        if (Platform::isTty()) {
            return $this->doExecute($command, $cwd, true);
        }

        return $this->doExecute($command, $cwd, false);
    }

    /**
     * @param  string|non-empty-list<string> $command
     * @param  array<string, string>|null $env
     * @param  mixed   $output
     */
    private function runProcess($command, ?string $cwd, ?array $env, bool $tty, &$output = null): ?int
    {
        // On Windows, we don't rely on the OS to find the executable if possible to avoid lookups
        // in the current directory which could be untrusted. Instead we use the ExecutableFinder.

        if (is_string($command)) {
            if (Platform::isWindows() && Preg::isMatch('{^([^:/\\\\]++) }', $command, $match)) {
                $command = substr_replace($command, self::escape(self::getExecutable($match[1])), 0, strlen($match[1]));
            }

            $process = Process::fromShellCommandline($command, $cwd, $env, null, static::getTimeout());
        } else {
            if (Platform::isWindows() && \strlen($command[0]) === strcspn($command[0], ':/\\')) {
                $command[0] = self::getExecutable($command[0]);
            }

            $process = new Process($command, $cwd, $env, null, static::getTimeout());
        }

        if (! Platform::isWindows() && $tty) {
            try {
                $process->setTty(true);
            } catch (RuntimeException $e) {
                // ignore TTY enabling errors
            }
        }

        $callback = is_callable($output) ? $output : function (string $type, string $buffer): void {
            $this->outputHandler($type, $buffer);
        };

        $signalHandler = SignalHandler::create(
            [SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP],
            function (string $signal) {
                if ($this->io !== null) {
                    $this->io->writeError(
                        'Received '.$signal.', aborting when child process is done',
                        true,
                        IOInterface::DEBUG
                    );
                }
            }
        );

        try {
            $process->run($callback);

            if ($this->captureOutput && !is_callable($output)) {
                $output = $process->getOutput();
            }

            $this->errorOutput = $process->getErrorOutput();
        } catch (ProcessSignaledException $e) {
            if ($signalHandler->isTriggered()) {
                // exiting as we were signaled and the child process exited too due to the signal
                $signalHandler->exitWithLastSignal();
            }
        } finally {
            $signalHandler->unregister();
        }

        return $process->getExitCode();
    }

    /**
     * @param  string|non-empty-list<string> $command
     * @param  mixed   $output
     */
    private function doExecute($command, ?string $cwd, bool $tty, &$output = null): int
    {
        $this->outputCommandRun($command, $cwd, false);

        $this->captureOutput = func_num_args() > 3;
        $this->errorOutput = '';

        $env = null;

        $requiresGitDirEnv = $this->requiresGitDirEnv($command);
        if ($cwd !== null && $requiresGitDirEnv) {
            $isBareRepository = !is_dir(sprintf('%s/.git', rtrim($cwd, '/')));
            if ($isBareRepository) {
                $configValue = '';
                $this->runProcess(['git', 'config', 'safe.bareRepository'], $cwd, ['GIT_DIR' => $cwd], $tty, $configValue);
                $configValue = trim($configValue);
                if ($configValue === 'explicit') {
                    $env = ['GIT_DIR' => $cwd];
                }
            }
        }

        return $this->runProcess($command, $cwd, $env, $tty, $output);
    }

    /**
     * starts a process on the commandline in async mode
     *
     * @param  string|list<string> $command the command to execute
     * @param  string              $cwd     the working directory
     * @phpstan-return PromiseInterface<Process>
     */
    public function executeAsync($command, ?string $cwd = null): PromiseInterface
    {
        if (!$this->allowAsync) {
            throw new \LogicException('You must use the ProcessExecutor instance which is part of a Composer\Loop instance to be able to run async processes');
        }

        $job = [
            'id' => $this->idGen++,
            'status' => self::STATUS_QUEUED,
            'command' => $command,
            'cwd' => $cwd,
        ];

        $resolver = static function ($resolve, $reject) use (&$job): void {
            $job['status'] = ProcessExecutor::STATUS_QUEUED;
            $job['resolve'] = $resolve;
            $job['reject'] = $reject;
        };

        $canceler = static function () use (&$job): void {
            if ($job['status'] === ProcessExecutor::STATUS_QUEUED) {
                $job['status'] = ProcessExecutor::STATUS_ABORTED;
            }
            if ($job['status'] !== ProcessExecutor::STATUS_STARTED) {
                return;
            }
            $job['status'] = ProcessExecutor::STATUS_ABORTED;
            try {
                if (defined('SIGINT')) {
                    $job['process']->signal(SIGINT);
                }
            } catch (\Exception $e) {
                // signal can throw in various conditions, but we don't care if it fails
            }
            $job['process']->stop(1);

            throw new \RuntimeException('Aborted process');
        };

        $promise = new Promise($resolver, $canceler);
        $promise = $promise->then(function () use (&$job) {
            if ($job['process']->isSuccessful()) {
                $job['status'] = ProcessExecutor::STATUS_COMPLETED;
            } else {
                $job['status'] = ProcessExecutor::STATUS_FAILED;
            }

            $this->markJobDone();

            return $job['process'];
        }, function ($e) use (&$job): void {
            $job['status'] = ProcessExecutor::STATUS_FAILED;

            $this->markJobDone();

            throw $e;
        });
        $this->jobs[$job['id']] = &$job;

        if ($this->runningJobs < $this->maxJobs) {
            $this->startJob($job['id']);
        }

        return $promise;
    }

    protected function outputHandler(string $type, string $buffer): void
    {
        if ($this->captureOutput) {
            return;
        }

        if (null === $this->io) {
            echo $buffer;

            return;
        }

        if (Process::ERR === $type) {
            $this->io->writeErrorRaw($buffer, false);
        } else {
            $this->io->writeRaw($buffer, false);
        }
    }

    private function startJob(int $id): void
    {
        $job = &$this->jobs[$id];
        if ($job['status'] !== self::STATUS_QUEUED) {
            return;
        }

        // start job
        $job['status'] = self::STATUS_STARTED;
        $this->runningJobs++;

        $command = $job['command'];
        $cwd = $job['cwd'];

        $this->outputCommandRun($command, $cwd, true);

        try {
            if (is_string($command)) {
                $process = Process::fromShellCommandline($command, $cwd, null, null, static::getTimeout());
            } else {
                $process = new Process($command, $cwd, null, null, static::getTimeout());
            }
        } catch (\Throwable $e) {
            $job['reject']($e);

            return;
        }

        $job['process'] = $process;

        try {
            $process->start();
        } catch (\Throwable $e) {
            $job['reject']($e);

            return;
        }
    }

    public function setMaxJobs(int $maxJobs): void
    {
        $this->maxJobs = $maxJobs;
    }

    public function resetMaxJobs(): void
    {
        if (is_numeric($maxJobs = Platform::getEnv('COMPOSER_MAX_PARALLEL_PROCESSES'))) {
            $this->maxJobs = max(1, min(50, (int) $maxJobs));
        } else {
            $this->maxJobs = 10;
        }
    }

    /**
     * @param  ?int $index job id
     */
    public function wait($index = null): void
    {
        while (true) {
            if (0 === $this->countActiveJobs($index)) {
                return;
            }

            usleep(1000);
        }
    }

    /**
     * @internal
     */
    public function enableAsync(): void
    {
        $this->allowAsync = true;
    }

    /**
     * @internal
     *
     * @param  ?int $index job id
     * @return int         number of active (queued or started) jobs
     */
    public function countActiveJobs($index = null): int
    {
        // tick
        foreach ($this->jobs as $job) {
            if ($job['status'] === self::STATUS_STARTED) {
                if (!$job['process']->isRunning()) {
                    call_user_func($job['resolve'], $job['process']);
                }

                $job['process']->checkTimeout();
            }

            if ($this->runningJobs < $this->maxJobs) {
                if ($job['status'] === self::STATUS_QUEUED) {
                    $this->startJob($job['id']);
                }
            }
        }

        if (null !== $index) {
            return $this->jobs[$index]['status'] < self::STATUS_COMPLETED ? 1 : 0;
        }

        $active = 0;
        foreach ($this->jobs as $job) {
            if ($job['status'] < self::STATUS_COMPLETED) {
                $active++;
            } else {
                unset($this->jobs[$job['id']]);
            }
        }

        return $active;
    }

    private function markJobDone(): void
    {
        $this->runningJobs--;
    }

    /**
     * @return string[]
     */
    public function splitLines(?string $output): array
    {
        $output = trim((string) $output);

        return $output === '' ? [] : Preg::split('{\r?\n}', $output);
    }

    /**
     * Get any error output from the last command
     */
    public function getErrorOutput(): string
    {
        return $this->errorOutput;
    }

    /**
     * @return int the timeout in seconds
     */
    public static function getTimeout(): int
    {
        return static::$timeout;
    }

    /**
     * @param  int  $timeout the timeout in seconds
     */
    public static function setTimeout(int $timeout): void
    {
        static::$timeout = $timeout;
    }

    /**
     * Escapes a string to be used as a shell argument.
     *
     * @param string|false|null $argument The argument that will be escaped
     *
     * @return string The escaped argument
     */
    public static function escape($argument): string
    {
        return self::escapeArgument($argument);
    }

    /**
     * @param string|list<string> $command
     */
    private function outputCommandRun($command, ?string $cwd, bool $async): void
    {
        if (null === $this->io || !$this->io->isDebug()) {
            return;
        }

        $commandString = is_string($command) ? $command : implode(' ', array_map(self::class.'::escape', $command));
        $safeCommand = Preg::replaceCallback('{://(?P<user>[^:/\s]+):(?P<password>[^@\s/]+)@}i', static function ($m): string {
            // if the username looks like a long (12char+) hex string, or a modern github token (e.g. ghp_xxx, github_pat_xxx) we obfuscate that
            if (Preg::isMatch(GitHub::GITHUB_TOKEN_REGEX, $m['user'])) {
                return '://***:***@';
            }
            if (Preg::isMatch('{^[a-f0-9]{12,}$}', $m['user'])) {
                return '://***:***@';
            }

            return '://'.$m['user'].':***@';
        }, $commandString);
        $safeCommand = Preg::replace("{--password (.*[^\\\\]\') }", '--password \'***\' ', $safeCommand);
        $this->io->writeError('Executing'.($async ? ' async' : '').' command ('.($cwd ?: 'CWD').'): '.$safeCommand);
    }

    /**
     * Escapes a string to be used as a shell argument for Symfony Process.
     *
     * This method expects cmd.exe to be started with the /V:ON option, which
     * enables delayed environment variable expansion using ! as the delimiter.
     * If this is not the case, any escaped ^^!var^^! will be transformed to
     * ^!var^! and introduce two unintended carets.
     *
     * Modified from https://github.com/johnstevenson/winbox-args
     * MIT Licensed (c) John Stevenson <john-stevenson@blueyonder.co.uk>
     *
     * @param string|false|null $argument
     */
    private static function escapeArgument($argument): string
    {
        if ('' === ($argument = (string) $argument)) {
            return escapeshellarg($argument);
        }

        if (!Platform::isWindows()) {
            return "'".str_replace("'", "'\\''", $argument)."'";
        }

        // New lines break cmd.exe command parsing
        // and special chars like the fullwidth quote can be used to break out
        // of parameter encoding via "Best Fit" encoding conversion
        $argument = strtr($argument, [
            "\n" => ' ',
            "\u{ff02}" => '"',
            "\u{02ba}" => '"',
            "\u{301d}" => '"',
            "\u{301e}" => '"',
            "\u{030e}" => '"',
            "\u{ff1a}" => ':',
            "\u{0589}" => ':',
            "\u{2236}" => ':',
            "\u{ff0f}" => '/',
            "\u{2044}" => '/',
            "\u{2215}" => '/',
            "\u{00b4}" => '/',
        ]);

        // In addition to whitespace, commas need quoting to preserve paths
        $quote = strpbrk($argument, " \t,") !== false;
        $argument = Preg::replace('/(\\\\*)"/', '$1$1\\"', $argument, -1, $dquotes);
        $meta = $dquotes > 0 || Preg::isMatch('/%[^%]+%|![^!]+!/', $argument);

        if (!$meta && !$quote) {
            $quote = strpbrk($argument, '^&|<>()') !== false;
        }

        if ($quote) {
            $argument = '"'.Preg::replace('/(\\\\*)$/', '$1$1', $argument).'"';
        }

        if ($meta) {
            $argument = Preg::replace('/(["^&|<>()%])/', '^$1', $argument);
            $argument = Preg::replace('/(!)/', '^^$1', $argument);
        }

        return $argument;
    }

    /**
     * @param string[]|string $command
     */
    public function requiresGitDirEnv($command): bool
    {
        $cmd = !is_array($command) ? explode(' ', $command) : $command;
        if ($cmd[0] !== 'git') {
            return false;
        }

        foreach (self::GIT_CMDS_NEED_GIT_DIR as $gitCmd) {
            if (array_intersect($cmd, $gitCmd) === $gitCmd) {
                return true;
            }
        }

        return false;
    }

    /**
     * Resolves executable paths on Windows
     */
    private static function getExecutable(string $name): string
    {
        if (\in_array(strtolower($name), self::BUILTIN_CMD_COMMANDS, true)) {
            return $name;
        }

        if (!isset(self::$executables[$name])) {
            $path = (new ExecutableFinder())->find($name, $name);
            if ($path !== null) {
                self::$executables[$name] = $path;
            }
        }

        return self::$executables[$name] ?? $name;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

/**
 * Temporarily suppress PHP error reporting, usually warnings and below.
 *
 * @author Niels Keurentjes <niels.keurentjes@omines.com>
 */
class Silencer
{
    /**
     * @var int[] Unpop stack
     */
    private static $stack = [];

    /**
     * Suppresses given mask or errors.
     *
     * @param  int|null $mask Error levels to suppress, default value NULL indicates all warnings and below.
     * @return int      The old error reporting level.
     */
    public static function suppress(?int $mask = null): int
    {
        if (!isset($mask)) {
            $mask = E_WARNING | E_NOTICE | E_USER_WARNING | E_USER_NOTICE | E_DEPRECATED | E_USER_DEPRECATED;
        }
        $old = error_reporting();
        self::$stack[] = $old;
        error_reporting($old & ~$mask);

        return $old;
    }

    /**
     * Restores a single state.
     */
    public static function restore(): void
    {
        if (!empty(self::$stack)) {
            error_reporting(array_pop(self::$stack));
        }
    }

    /**
     * Calls a specified function while silencing warnings and below.
     *
     * @param  callable   $callable Function to execute.
     * @param  mixed      $parameters Function to execute.
     * @throws \Exception Any exceptions from the callback are rethrown.
     * @return mixed      Return value of the callback.
     */
    public static function call(callable $callable, ...$parameters)
    {
        try {
            self::suppress();
            $result = $callable(...$parameters);
            self::restore();

            return $result;
        } catch (\Exception $e) {
            // Use a finally block for this when requirements are raised to PHP 5.5
            self::restore();
            throw $e;
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

/**
 * @author Andreas Schempp <andreas.schempp@terminal42.ch>
 */
class Zip
{
    /**
     * Gets content of the root composer.json inside a ZIP archive.
     */
    public static function getComposerJson(string $pathToZip): ?string
    {
        if (!extension_loaded('zip')) {
            throw new \RuntimeException('The Zip Util requires PHP\'s zip extension');
        }

        $zip = new \ZipArchive();
        if ($zip->open($pathToZip) !== true) {
            return null;
        }

        if (0 === $zip->numFiles) {
            $zip->close();

            return null;
        }

        $foundFileIndex = self::locateFile($zip, 'composer.json');

        $content = null;
        $configurationFileName = $zip->getNameIndex($foundFileIndex);
        $stream = $zip->getStream($configurationFileName);

        if (false !== $stream) {
            $content = stream_get_contents($stream);
        }

        $zip->close();

        return $content;
    }

    /**
     * Find a file by name, returning the one that has the shortest path.
     *
     * @throws \RuntimeException
     */
    private static function locateFile(\ZipArchive $zip, string $filename): int
    {
        // return root composer.json if it is there and is a file
        if (false !== ($index = $zip->locateName($filename)) && $zip->getFromIndex($index) !== false) {
            return $index;
        }

        $topLevelPaths = [];
        for ($i = 0; $i < $zip->numFiles; $i++) {
            $name = $zip->getNameIndex($i);
            $dirname = dirname($name);

            // ignore OSX specific resource fork folder
            if (strpos($name, '__MACOSX') !== false) {
                continue;
            }

            // handle archives with proper TOC
            if ($dirname === '.') {
                $topLevelPaths[$name] = true;
                if (\count($topLevelPaths) > 1) {
                    throw new \RuntimeException('Archive has more than one top level directories, and no composer.json was found on the top level, so it\'s an invalid archive. Top level paths found were: '.implode(',', array_keys($topLevelPaths)));
                }
                continue;
            }

            // handle archives which do not have a TOC record for the directory itself
            if (false === strpos($dirname, '\\') && false === strpos($dirname, '/')) {
                $topLevelPaths[$dirname.'/'] = true;
                if (\count($topLevelPaths) > 1) {
                    throw new \RuntimeException('Archive has more than one top level directories, and no composer.json was found on the top level, so it\'s an invalid archive. Top level paths found were: '.implode(',', array_keys($topLevelPaths)));
                }
            }
        }

        if ($topLevelPaths && false !== ($index = $zip->locateName(key($topLevelPaths).$filename)) && $zip->getFromIndex($index) !== false) {
            return $index;
        }

        throw new \RuntimeException('No composer.json found either at the top level or within the topmost directory');
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

@trigger_error('Composer\Util\MetadataMinifier is deprecated, use Composer\MetadataMinifier\MetadataMinifier from composer/metadata-minifier instead.', E_USER_DEPRECATED);

/**
 * @deprecated Use Composer\MetadataMinifier\MetadataMinifier instead
 */
class MetadataMinifier extends \Composer\MetadataMinifier\MetadataMinifier
{
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Pcre\Preg;

/**
 * Platform helper for uniform platform-specific tests.
 *
 * @author Niels Keurentjes <niels.keurentjes@omines.com>
 */
class Platform
{
    /** @var ?bool */
    private static $isVirtualBoxGuest = null;
    /** @var ?bool */
    private static $isWindowsSubsystemForLinux = null;
    /** @var ?bool */
    private static $isDocker = null;

    /**
     * getcwd() equivalent which always returns a string
     *
     * @throws \RuntimeException
     */
    public static function getCwd(bool $allowEmpty = false): string
    {
        $cwd = getcwd();

        // fallback to realpath('') just in case this works but odds are it would break as well if we are in a case where getcwd fails
        if (false === $cwd) {
            $cwd = realpath('');
        }

        // crappy state, assume '' and hopefully relative paths allow things to continue
        if (false === $cwd) {
            if ($allowEmpty) {
                return '';
            }

            throw new \RuntimeException('Could not determine the current working directory');
        }

        return $cwd;
    }

    /**
     * Infallible realpath version that falls back on the given $path if realpath is not working
     */
    public static function realpath(string $path): string
    {
        $realPath = realpath($path);
        if ($realPath === false) {
            return $path;
        }

        return $realPath;
    }

    /**
     * getenv() equivalent but reads from the runtime global variables first
     *
     * @param non-empty-string $name
     *
     * @return string|false
     */
    public static function getEnv(string $name)
    {
        if (array_key_exists($name, $_SERVER)) {
            return (string) $_SERVER[$name];
        }
        if (array_key_exists($name, $_ENV)) {
            return (string) $_ENV[$name];
        }

        return getenv($name);
    }

    /**
     * putenv() equivalent but updates the runtime global variables too
     */
    public static function putEnv(string $name, string $value): void
    {
        putenv($name . '=' . $value);
        $_SERVER[$name] = $_ENV[$name] = $value;
    }

    /**
     * putenv('X') equivalent but updates the runtime global variables too
     */
    public static function clearEnv(string $name): void
    {
        putenv($name);
        unset($_SERVER[$name], $_ENV[$name]);
    }

    /**
     * Parses tildes and environment variables in paths.
     */
    public static function expandPath(string $path): string
    {
        if (Preg::isMatch('#^~[\\/]#', $path)) {
            return self::getUserDirectory() . substr($path, 1);
        }

        return Preg::replaceCallback('#^(\$|(?P<percent>%))(?P<var>\w++)(?(percent)%)(?P<path>.*)#', static function ($matches): string {
            // Treat HOME as an alias for USERPROFILE on Windows for legacy reasons
            if (Platform::isWindows() && $matches['var'] === 'HOME') {
                if ((bool) Platform::getEnv('HOME')) {
                    return Platform::getEnv('HOME') . $matches['path'];
                }
                return Platform::getEnv('USERPROFILE') . $matches['path'];
            }

            return Platform::getEnv($matches['var']) . $matches['path'];
        }, $path);
    }

    /**
     * @throws \RuntimeException If the user home could not reliably be determined
     * @return string            The formal user home as detected from environment parameters
     */
    public static function getUserDirectory(): string
    {
        if (false !== ($home = self::getEnv('HOME'))) {
            return $home;
        }

        if (self::isWindows() && false !== ($home = self::getEnv('USERPROFILE'))) {
            return $home;
        }

        if (\function_exists('posix_getuid') && \function_exists('posix_getpwuid')) {
            $info = posix_getpwuid(posix_getuid());

            if (is_array($info)) {
                return $info['dir'];
            }
        }

        throw new \RuntimeException('Could not determine user directory');
    }

    /**
     * @return bool Whether the host machine is running on the Windows Subsystem for Linux (WSL)
     */
    public static function isWindowsSubsystemForLinux(): bool
    {
        if (null === self::$isWindowsSubsystemForLinux) {
            self::$isWindowsSubsystemForLinux = false;

            // while WSL will be hosted within windows, WSL itself cannot be windows based itself.
            if (self::isWindows()) {
                return self::$isWindowsSubsystemForLinux = false;
            }

            if (
                !(bool) ini_get('open_basedir')
                && is_readable('/proc/version')
                && false !== stripos((string)Silencer::call('file_get_contents', '/proc/version'), 'microsoft')
                && !self::isDocker() // Docker and Podman running inside WSL should not be seen as WSL
            ) {
                return self::$isWindowsSubsystemForLinux = true;
            }
        }

        return self::$isWindowsSubsystemForLinux;
    }

    /**
     * @return bool Whether the host machine is running a Windows OS
     */
    public static function isWindows(): bool
    {
        return \defined('PHP_WINDOWS_VERSION_BUILD');
    }

    public static function isDocker(): bool
    {
        if (null !== self::$isDocker) {
            return self::$isDocker;
        }

        // cannot check so assume no
        if ((bool) ini_get('open_basedir')) {
            return self::$isDocker = false;
        }

        // .dockerenv and .containerenv are present in some cases but not reliably
        if (file_exists('/.dockerenv') || file_exists('/run/.containerenv') || file_exists('/var/run/.containerenv')) {
            return self::$isDocker = true;
        }

        // see https://www.baeldung.com/linux/is-process-running-inside-container
        $cgroups = [
            '/proc/self/mountinfo', // cgroup v2
            '/proc/1/cgroup', // cgroup v1
        ];
        foreach ($cgroups as $cgroup) {
            if (!is_readable($cgroup)) {
                continue;
            }
            // suppress errors as some environments have these files as readable but system restrictions prevent the read from succeeding
            // see https://github.com/composer/composer/issues/12095
            try {
                $data = @file_get_contents($cgroup);
            } catch (\Throwable $e) {
                break;
            }
            if (!is_string($data)) {
                continue;
            }
            // detect default mount points created by Docker/containerd
            if (str_contains($data, '/var/lib/docker/') || str_contains($data, '/io.containerd.snapshotter')) {
                return self::$isDocker = true;
            }
        }

        return self::$isDocker = false;
    }

    /**
     * @return int    return a guaranteed binary length of the string, regardless of silly mbstring configs
     */
    public static function strlen(string $str): int
    {
        static $useMbString = null;
        if (null === $useMbString) {
            $useMbString = \function_exists('mb_strlen') && (bool) ini_get('mbstring.func_overload');
        }

        if ($useMbString) {
            return mb_strlen($str, '8bit');
        }

        return \strlen($str);
    }

    /**
     * @param  ?resource $fd Open file descriptor or null to default to STDOUT
     */
    public static function isTty($fd = null): bool
    {
        if ($fd === null) {
            $fd = defined('STDOUT') ? STDOUT : fopen('php://stdout', 'w');
            if ($fd === false) {
                return false;
            }
        }

        // detect msysgit/mingw and assume this is a tty because detection
        // does not work correctly, see https://github.com/composer/composer/issues/9690
        if (in_array(strtoupper((string) self::getEnv('MSYSTEM')), ['MINGW32', 'MINGW64'], true)) {
            return true;
        }

        // modern cross-platform function, includes the fstat
        // fallback so if it is present we trust it
        if (function_exists('stream_isatty')) {
            return stream_isatty($fd);
        }

        // only trusting this if it is positive, otherwise prefer fstat fallback
        if (function_exists('posix_isatty') && posix_isatty($fd)) {
            return true;
        }

        $stat = @fstat($fd);
        if ($stat === false) {
            return false;
        }
        // Check if formatted mode is S_IFCHR
        return 0020000 === ($stat['mode'] & 0170000);
    }

    /**
     * @return bool Whether the current command is for bash completion
     */
    public static function isInputCompletionProcess(): bool
    {
        return '_complete' === ($_SERVER['argv'][1] ?? null);
    }

    public static function workaroundFilesystemIssues(): void
    {
        if (self::isVirtualBoxGuest()) {
            usleep(200000);
        }
    }

    /**
     * Attempts detection of VirtualBox guest VMs
     *
     * This works based on the process' user being "vagrant", the COMPOSER_RUNTIME_ENV env var being set to "virtualbox", or lsmod showing the virtualbox guest additions are loaded
     */
    private static function isVirtualBoxGuest(): bool
    {
        if (null === self::$isVirtualBoxGuest) {
            self::$isVirtualBoxGuest = false;
            if (self::isWindows()) {
                return self::$isVirtualBoxGuest;
            }

            if (function_exists('posix_getpwuid') && function_exists('posix_geteuid')) {
                $processUser = posix_getpwuid(posix_geteuid());
                if (is_array($processUser) && $processUser['name'] === 'vagrant') {
                    return self::$isVirtualBoxGuest = true;
                }
            }

            if (self::getEnv('COMPOSER_RUNTIME_ENV') === 'virtualbox') {
                return self::$isVirtualBoxGuest = true;
            }

            if (defined('PHP_OS_FAMILY') && PHP_OS_FAMILY === 'Linux') {
                $process = new ProcessExecutor();
                try {
                    if (0 === $process->execute(['lsmod'], $output) && str_contains($output, 'vboxguest')) {
                        return self::$isVirtualBoxGuest = true;
                    }
                } catch (\Exception $e) {
                    // noop
                }
            }
        }

        return self::$isVirtualBoxGuest;
    }

    /**
     * @return 'NUL'|'/dev/null'
     */
    public static function getDevNull(): string
    {
        if (self::isWindows()) {
            return 'NUL';
        }

        return '/dev/null';
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Downloader\TransportException;
use Composer\Pcre\Preg;
use Composer\Util\Http\Response;
use Composer\Util\Http\CurlDownloader;
use Composer\Composer;
use Composer\Package\Version\VersionParser;
use Composer\Semver\Constraint\Constraint;
use Composer\Exception\IrrecoverableDownloadException;
use React\Promise\Promise;
use React\Promise\PromiseInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @phpstan-type Request array{url: non-empty-string, options: mixed[], copyTo: string|null}
 * @phpstan-type Job array{id: int, status: int, request: Request, sync: bool, origin: string, resolve?: callable, reject?: callable, curl_id?: int, response?: Response, exception?: \Throwable}
 */
class HttpDownloader
{
    private const STATUS_QUEUED = 1;
    private const STATUS_STARTED = 2;
    private const STATUS_COMPLETED = 3;
    private const STATUS_FAILED = 4;
    private const STATUS_ABORTED = 5;

    /** @var IOInterface */
    private $io;
    /** @var Config */
    private $config;
    /** @var array<Job> */
    private $jobs = [];
    /** @var mixed[] */
    private $options = [];
    /** @var int */
    private $runningJobs = 0;
    /** @var int */
    private $maxJobs = 12;
    /** @var ?CurlDownloader */
    private $curl;
    /** @var ?RemoteFilesystem */
    private $rfs;
    /** @var int */
    private $idGen = 0;
    /** @var bool */
    private $disabled;
    /** @var bool */
    private $allowAsync = false;

    /**
     * @param IOInterface $io         The IO instance
     * @param Config      $config     The config
     * @param mixed[]     $options    The options
     */
    public function __construct(IOInterface $io, Config $config, array $options = [], bool $disableTls = false)
    {
        $this->io = $io;

        $this->disabled = (bool) Platform::getEnv('COMPOSER_DISABLE_NETWORK');

        // Setup TLS options
        // The cafile option can be set via config.json
        if ($disableTls === false) {
            $this->options = StreamContextFactory::getTlsDefaults($options, $io);
        }

        // handle the other externally set options normally.
        $this->options = array_replace_recursive($this->options, $options);
        $this->config = $config;

        if (self::isCurlEnabled()) {
            $this->curl = new CurlDownloader($io, $config, $options, $disableTls);
        }

        $this->rfs = new RemoteFilesystem($io, $config, $options, $disableTls);

        if (is_numeric($maxJobs = Platform::getEnv('COMPOSER_MAX_PARALLEL_HTTP'))) {
            $this->maxJobs = max(1, min(50, (int) $maxJobs));
        }
    }

    /**
     * Download a file synchronously
     *
     * @param  string             $url     URL to download
     * @param  mixed[]            $options Stream context options e.g. https://www.php.net/manual/en/context.http.php
     *                                     although not all options are supported when using the default curl downloader
     * @throws TransportException
     * @return Response
     */
    public function get(string $url, array $options = [])
    {
        if ('' === $url) {
            throw new \InvalidArgumentException('$url must not be an empty string');
        }
        [$job, $promise] = $this->addJob(['url' => $url, 'options' => $options, 'copyTo' => null], true);
        $promise->then(null, function (\Throwable $e) {
            // suppress error as it is rethrown to the caller by getResponse() a few lines below
        });
        $this->wait($job['id']);

        $response = $this->getResponse($job['id']);

        return $response;
    }

    /**
     * Create an async download operation
     *
     * @param  string             $url     URL to download
     * @param  mixed[]            $options Stream context options e.g. https://www.php.net/manual/en/context.http.php
     *                                     although not all options are supported when using the default curl downloader
     * @throws TransportException
     * @return PromiseInterface
     * @phpstan-return PromiseInterface<Http\Response>
     */
    public function add(string $url, array $options = [])
    {
        if ('' === $url) {
            throw new \InvalidArgumentException('$url must not be an empty string');
        }
        [, $promise] = $this->addJob(['url' => $url, 'options' => $options, 'copyTo' => null]);

        return $promise;
    }

    /**
     * Copy a file synchronously
     *
     * @param  string             $url     URL to download
     * @param  string             $to      Path to copy to
     * @param  mixed[]            $options Stream context options e.g. https://www.php.net/manual/en/context.http.php
     *                                     although not all options are supported when using the default curl downloader
     * @throws TransportException
     * @return Response
     */
    public function copy(string $url, string $to, array $options = [])
    {
        if ('' === $url) {
            throw new \InvalidArgumentException('$url must not be an empty string');
        }
        [$job] = $this->addJob(['url' => $url, 'options' => $options, 'copyTo' => $to], true);
        $this->wait($job['id']);

        return $this->getResponse($job['id']);
    }

    /**
     * Create an async copy operation
     *
     * @param  string             $url     URL to download
     * @param  string             $to      Path to copy to
     * @param  mixed[]            $options Stream context options e.g. https://www.php.net/manual/en/context.http.php
     *                                     although not all options are supported when using the default curl downloader
     * @throws TransportException
     * @return PromiseInterface
     * @phpstan-return PromiseInterface<Http\Response>
     */
    public function addCopy(string $url, string $to, array $options = [])
    {
        if ('' === $url) {
            throw new \InvalidArgumentException('$url must not be an empty string');
        }
        [, $promise] = $this->addJob(['url' => $url, 'options' => $options, 'copyTo' => $to]);

        return $promise;
    }

    /**
     * Retrieve the options set in the constructor
     *
     * @return mixed[] Options
     */
    public function getOptions()
    {
        return $this->options;
    }

    /**
     * Merges new options
     *
     * @param  mixed[] $options
     * @return void
     */
    public function setOptions(array $options)
    {
        $this->options = array_replace_recursive($this->options, $options);
    }

    /**
     * @phpstan-param Request $request
     * @return array{Job, PromiseInterface}
     * @phpstan-return array{Job, PromiseInterface<Http\Response>}
     */
    private function addJob(array $request, bool $sync = false): array
    {
        $request['options'] = array_replace_recursive($this->options, $request['options']);

        /** @var Job */
        $job = [
            'id' => $this->idGen++,
            'status' => self::STATUS_QUEUED,
            'request' => $request,
            'sync' => $sync,
            'origin' => Url::getOrigin($this->config, $request['url']),
        ];

        if (!$sync && !$this->allowAsync) {
            throw new \LogicException('You must use the HttpDownloader instance which is part of a Composer\Loop instance to be able to run async http requests');
        }

        // capture username/password from URL if there is one
        if (Preg::isMatchStrictGroups('{^https?://([^:/]+):([^@/]+)@([^/]+)}i', $request['url'], $match)) {
            $this->io->setAuthentication($job['origin'], rawurldecode($match[1]), rawurldecode($match[2]));
        }

        $rfs = $this->rfs;

        if ($this->canUseCurl($job)) {
            $resolver = static function ($resolve, $reject) use (&$job): void {
                $job['status'] = HttpDownloader::STATUS_QUEUED;
                $job['resolve'] = $resolve;
                $job['reject'] = $reject;
            };
        } else {
            $resolver = static function ($resolve, $reject) use (&$job, $rfs): void {
                // start job
                $url = $job['request']['url'];
                $options = $job['request']['options'];

                $job['status'] = HttpDownloader::STATUS_STARTED;

                if ($job['request']['copyTo']) {
                    $rfs->copy($job['origin'], $url, $job['request']['copyTo'], false /* TODO progress */, $options);

                    $headers = $rfs->getLastHeaders();
                    $response = new Http\Response($job['request'], $rfs->findStatusCode($headers), $headers, $job['request']['copyTo'].'~');

                    $resolve($response);
                } else {
                    $body = $rfs->getContents($job['origin'], $url, false /* TODO progress */, $options);
                    $headers = $rfs->getLastHeaders();
                    $response = new Http\Response($job['request'], $rfs->findStatusCode($headers), $headers, $body);

                    $resolve($response);
                }
            };
        }

        $curl = $this->curl;

        $canceler = static function () use (&$job, $curl): void {
            if ($job['status'] === HttpDownloader::STATUS_QUEUED) {
                $job['status'] = HttpDownloader::STATUS_ABORTED;
            }
            if ($job['status'] !== HttpDownloader::STATUS_STARTED) {
                return;
            }
            $job['status'] = HttpDownloader::STATUS_ABORTED;
            if (isset($job['curl_id'])) {
                $curl->abortRequest($job['curl_id']);
            }
            throw new IrrecoverableDownloadException('Download of ' . Url::sanitize($job['request']['url']) . ' canceled');
        };

        $promise = new Promise($resolver, $canceler);
        $promise = $promise->then(function ($response) use (&$job) {
            $job['status'] = HttpDownloader::STATUS_COMPLETED;
            $job['response'] = $response;

            $this->markJobDone();

            return $response;
        }, function ($e) use (&$job): void {
            $job['status'] = HttpDownloader::STATUS_FAILED;
            $job['exception'] = $e;

            $this->markJobDone();

            throw $e;
        });
        $this->jobs[$job['id']] = &$job;

        if ($this->runningJobs < $this->maxJobs) {
            $this->startJob($job['id']);
        }

        return [$job, $promise];
    }

    private function startJob(int $id): void
    {
        $job = &$this->jobs[$id];
        if ($job['status'] !== self::STATUS_QUEUED) {
            return;
        }

        // start job
        $job['status'] = self::STATUS_STARTED;
        $this->runningJobs++;

        assert(isset($job['resolve']));
        assert(isset($job['reject']));

        $resolve = $job['resolve'];
        $reject = $job['reject'];
        $url = $job['request']['url'];
        $options = $job['request']['options'];
        $origin = $job['origin'];

        if ($this->disabled) {
            if (isset($job['request']['options']['http']['header']) && false !== stripos(implode('', $job['request']['options']['http']['header']), 'if-modified-since')) {
                $resolve(new Response(['url' => $url], 304, [], ''));
            } else {
                $e = new TransportException('Network disabled, request canceled: '.Url::sanitize($url), 499);
                $e->setStatusCode(499);
                $reject($e);
            }

            return;
        }

        try {
            if ($job['request']['copyTo']) {
                $job['curl_id'] = $this->curl->download($resolve, $reject, $origin, $url, $options, $job['request']['copyTo']);
            } else {
                $job['curl_id'] = $this->curl->download($resolve, $reject, $origin, $url, $options);
            }
        } catch (\Exception $exception) {
            $reject($exception);
        }
    }

    private function markJobDone(): void
    {
        $this->runningJobs--;
    }

    /**
     * Wait for current async download jobs to complete
     *
     * @param int|null $index For internal use only, the job id
     *
     * @return void
     */
    public function wait(?int $index = null)
    {
        do {
            $jobCount = $this->countActiveJobs($index);
        } while ($jobCount);
    }

    /**
     * @internal
     */
    public function enableAsync(): void
    {
        $this->allowAsync = true;
    }

    /**
     * @internal
     *
     * @param  int|null $index For internal use only, the job id
     * @return int      number of active (queued or started) jobs
     */
    public function countActiveJobs(?int $index = null): int
    {
        if ($this->runningJobs < $this->maxJobs) {
            foreach ($this->jobs as $job) {
                if ($job['status'] === self::STATUS_QUEUED && $this->runningJobs < $this->maxJobs) {
                    $this->startJob($job['id']);
                }
            }
        }

        if ($this->curl) {
            $this->curl->tick();
        }

        if (null !== $index) {
            return $this->jobs[$index]['status'] < self::STATUS_COMPLETED ? 1 : 0;
        }

        $active = 0;
        foreach ($this->jobs as $job) {
            if ($job['status'] < self::STATUS_COMPLETED) {
                $active++;
            } elseif (!$job['sync']) {
                unset($this->jobs[$job['id']]);
            }
        }

        return $active;
    }

    /**
     * @param  int $index Job id
     */
    private function getResponse(int $index): Response
    {
        if (!isset($this->jobs[$index])) {
            throw new \LogicException('Invalid request id');
        }

        if ($this->jobs[$index]['status'] === self::STATUS_FAILED) {
            assert(isset($this->jobs[$index]['exception']));
            throw $this->jobs[$index]['exception'];
        }

        if (!isset($this->jobs[$index]['response'])) {
            throw new \LogicException('Response not available yet, call wait() first');
        }

        $resp = $this->jobs[$index]['response'];

        unset($this->jobs[$index]);

        return $resp;
    }

    /**
     * @internal
     *
     * @param  array{warning?: string, info?: string, warning-versions?: string, info-versions?: string, warnings?: array<array{versions: string, message: string}>, infos?: array<array{versions: string, message: string}>} $data
     */
    public static function outputWarnings(IOInterface $io, string $url, $data): void
    {
        $cleanMessage = static function ($msg) use ($io) {
            if (!$io->isDecorated()) {
                $msg = Preg::replace('{'.chr(27).'\\[[;\d]*m}u', '', $msg);
            }

            return $msg;
        };

        // legacy warning/info keys
        foreach (['warning', 'info'] as $type) {
            if (empty($data[$type])) {
                continue;
            }

            if (!empty($data[$type . '-versions'])) {
                $versionParser = new VersionParser();
                $constraint = $versionParser->parseConstraints($data[$type . '-versions']);
                $composer = new Constraint('==', $versionParser->normalize(Composer::getVersion()));
                if (!$constraint->matches($composer)) {
                    continue;
                }
            }

            $io->writeError('<'.$type.'>'.ucfirst($type).' from '.Url::sanitize($url).': '.$cleanMessage($data[$type]).'</'.$type.'>');
        }

        // modern Composer 2.2+ format with support for multiple warning/info messages
        foreach (['warnings', 'infos'] as $key) {
            if (empty($data[$key])) {
                continue;
            }

            $versionParser = new VersionParser();
            foreach ($data[$key] as $spec) {
                $type = substr($key, 0, -1);
                $constraint = $versionParser->parseConstraints($spec['versions']);
                $composer = new Constraint('==', $versionParser->normalize(Composer::getVersion()));
                if (!$constraint->matches($composer)) {
                    continue;
                }

                $io->writeError('<'.$type.'>'.ucfirst($type).' from '.Url::sanitize($url).': '.$cleanMessage($spec['message']).'</'.$type.'>');
            }
        }
    }

    /**
     * @internal
     *
     * @return ?string[]
     */
    public static function getExceptionHints(\Throwable $e): ?array
    {
        if (!$e instanceof TransportException) {
            return null;
        }

        if (
            false !== strpos($e->getMessage(), 'Resolving timed out')
            || false !== strpos($e->getMessage(), 'Could not resolve host')
        ) {
            Silencer::suppress();
            $testConnectivity = file_get_contents('https://8.8.8.8', false, stream_context_create([
                'ssl' => ['verify_peer' => false],
                'http' => ['follow_location' => false, 'ignore_errors' => true],
            ]));
            Silencer::restore();
            if (false !== $testConnectivity) {
                return [
                    '<error>The following exception probably indicates you have misconfigured DNS resolver(s)</error>',
                ];
            }

            return [
                '<error>The following exception probably indicates you are offline or have misconfigured DNS resolver(s)</error>',
            ];
        }

        return null;
    }

    /**
     * @param  Job  $job
     */
    private function canUseCurl(array $job): bool
    {
        if (!$this->curl) {
            return false;
        }

        if (!Preg::isMatch('{^https?://}i', $job['request']['url'])) {
            return false;
        }

        if (!empty($job['request']['options']['ssl']['allow_self_signed'])) {
            return false;
        }

        return true;
    }

    /**
     * @internal
     */
    public static function isCurlEnabled(): bool
    {
        return \extension_loaded('curl') && \function_exists('curl_multi_exec') && \function_exists('curl_multi_init');
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Pcre\Preg;

/**
 * @author Jonas Renaudot <jonas.renaudot@gmail.com>
 */
class Hg
{
    /** @var string|false|null */
    private static $version = false;

    /**
     * @var \Composer\IO\IOInterface
     */
    private $io;

    /**
     * @var \Composer\Config
     */
    private $config;

    /**
     * @var \Composer\Util\ProcessExecutor
     */
    private $process;

    public function __construct(IOInterface $io, Config $config, ProcessExecutor $process)
    {
        $this->io = $io;
        $this->config = $config;
        $this->process = $process;
    }

    public function runCommand(callable $commandCallable, string $url, ?string $cwd): void
    {
        $this->config->prohibitUrlByConfig($url, $this->io);

        // Try as is
        $command = $commandCallable($url);

        if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
            return;
        }

        // Try with the authentication information available
        if (
            Preg::isMatch('{^(?P<proto>ssh|https?)://(?:(?P<user>[^:@]+)(?::(?P<pass>[^:@]+))?@)?(?P<host>[^/]+)(?P<path>/.*)?}mi', $url, $matches)
            && $this->io->hasAuthentication($matches['host'])
        ) {
            if ($matches['proto'] === 'ssh') {
                $user = '';
                if ($matches['user'] !== null) {
                    $user = rawurlencode($matches['user']) . '@';
                }
                $authenticatedUrl = $matches['proto'] . '://' . $user . $matches['host'] . $matches['path'];
            } else {
                $auth = $this->io->getAuthentication($matches['host']);
                $authenticatedUrl = $matches['proto'] . '://' . rawurlencode((string) $auth['username']) . ':' . rawurlencode((string) $auth['password']) . '@' . $matches['host'] . $matches['path'];
            }
            $command = $commandCallable($authenticatedUrl);

            if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
                return;
            }

            $error = $this->process->getErrorOutput();
        } else {
            $error = 'The given URL (' .$url. ') does not match the required format (ssh|http(s)://(username:password@)example.com/path-to-repository)';
        }

        $this->throwException("Failed to clone $url, \n\n" . $error, $url);
    }

    /**
     * @param non-empty-string $message
     *
     * @return never
     */
    private function throwException($message, string $url): void
    {
        if (null === self::getVersion($this->process)) {
            throw new \RuntimeException(Url::sanitize(
                'Failed to clone ' . $url . ', hg was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput()
            ));
        }

        throw new \RuntimeException(Url::sanitize($message));
    }

    /**
     * Retrieves the current hg version.
     *
     * @return string|null The hg version number, if present.
     */
    public static function getVersion(ProcessExecutor $process): ?string
    {
        if (false === self::$version) {
            self::$version = null;
            if (0 === $process->execute(['hg', '--version'], $output) && Preg::isMatch('/^.+? (\d+(?:\.\d+)+)(?:\+.*?)?\)?\r?\n/', $output, $matches)) {
                self::$version = $matches[1];
            }
        }

        return self::$version;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Pcre\Preg;

/**
 * Composer mirror utilities
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ComposerMirror
{
    /**
     * @param non-empty-string $mirrorUrl
     * @return non-empty-string
     */
    public static function processUrl(string $mirrorUrl, string $packageName, string $version, ?string $reference, ?string $type, ?string $prettyVersion = null): string
    {
        if ($reference) {
            $reference = Preg::isMatch('{^([a-f0-9]*|%reference%)$}', $reference) ? $reference : hash('md5', $reference);
        }
        $version = strpos($version, '/') === false ? $version : hash('md5', $version);

        $from = ['%package%', '%version%', '%reference%', '%type%'];
        $to = [$packageName, $version, $reference, $type];
        if (null !== $prettyVersion) {
            $from[] = '%prettyVersion%';
            $to[] = $prettyVersion;
        }

        $url = str_replace($from, $to, $mirrorUrl);
        assert($url !== '');

        return $url;
    }

    /**
     * @param non-empty-string $mirrorUrl
     * @return string
     */
    public static function processGitUrl(string $mirrorUrl, string $packageName, string $url, ?string $type): string
    {
        if (Preg::isMatch('#^(?:(?:https?|git)://github\.com/|git@github\.com:)([^/]+)/(.+?)(?:\.git)?$#', $url, $match)) {
            $url = 'gh-'.$match[1].'/'.$match[2];
        } elseif (Preg::isMatch('#^https://bitbucket\.org/([^/]+)/(.+?)(?:\.git)?/?$#', $url, $match)) {
            $url = 'bb-'.$match[1].'/'.$match[2];
        } else {
            $url = Preg::replace('{[^a-z0-9_.-]}i', '-', trim($url, '/'));
        }

        return str_replace(
            ['%package%', '%normalizedUrl%', '%type%'],
            [$packageName, $url, $type],
            $mirrorUrl
        );
    }

    /**
     * @param non-empty-string $mirrorUrl
     * @return string
     */
    public static function processHgUrl(string $mirrorUrl, string $packageName, string $url, string $type): string
    {
        return self::processGitUrl($mirrorUrl, $packageName, $url, $type);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Loader\ValidatingArrayLoader;
use Composer\Package\Loader\InvalidPackageException;
use Composer\Json\JsonValidationException;
use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
use Composer\Pcre\Preg;
use Composer\Spdx\SpdxLicenses;
use Seld\JsonLint\DuplicateKeyException;
use Seld\JsonLint\JsonParser;

/**
 * Validates a composer configuration.
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ConfigValidator
{
    public const CHECK_VERSION = 1;

    /** @var IOInterface */
    private $io;

    public function __construct(IOInterface $io)
    {
        $this->io = $io;
    }

    /**
     * Validates the config, and returns the result.
     *
     * @param string $file                       The path to the file
     * @param int    $arrayLoaderValidationFlags Flags for ArrayLoader validation
     * @param int    $flags                      Flags for validation
     *
     * @return array{list<string>, list<string>, list<string>} a triple containing the errors, publishable errors, and warnings
     */
    public function validate(string $file, int $arrayLoaderValidationFlags = ValidatingArrayLoader::CHECK_ALL, int $flags = self::CHECK_VERSION): array
    {
        $errors = [];
        $publishErrors = [];
        $warnings = [];

        // validate json schema
        $laxValid = false;
        $manifest = null;
        try {
            $json = new JsonFile($file, null, $this->io);
            $manifest = $json->read();

            $json->validateSchema(JsonFile::LAX_SCHEMA);
            $laxValid = true;
            $json->validateSchema();
        } catch (JsonValidationException $e) {
            foreach ($e->getErrors() as $message) {
                if ($laxValid) {
                    $publishErrors[] = $message;
                } else {
                    $errors[] = $message;
                }
            }
        } catch (\Exception $e) {
            $errors[] = $e->getMessage();

            return [$errors, $publishErrors, $warnings];
        }

        if (is_array($manifest)) {
            $jsonParser = new JsonParser();
            try {
                $jsonParser->parse((string) file_get_contents($file), JsonParser::DETECT_KEY_CONFLICTS);
            } catch (DuplicateKeyException $e) {
                $details = $e->getDetails();
                $warnings[] = 'Key '.$details['key'].' is a duplicate in '.$file.' at line '.$details['line'];
            }
        }

        // validate actual data
        if (empty($manifest['license'])) {
            $warnings[] = 'No license specified, it is recommended to do so. For closed-source software you may use "proprietary" as license.';
        } else {
            $licenses = (array) $manifest['license'];

            // strip proprietary since it's not a valid SPDX identifier, but is accepted by composer
            foreach ($licenses as $key => $license) {
                if ('proprietary' === $license) {
                    unset($licenses[$key]);
                }
            }

            $licenseValidator = new SpdxLicenses();
            foreach ($licenses as $license) {
                $spdxLicense = $licenseValidator->getLicenseByIdentifier($license);
                if ($spdxLicense && $spdxLicense[3]) {
                    if (Preg::isMatch('{^[AL]?GPL-[123](\.[01])?\+$}i', $license)) {
                        $warnings[] = sprintf(
                            'License "%s" is a deprecated SPDX license identifier, use "'.str_replace('+', '', $license).'-or-later" instead',
                            $license
                        );
                    } elseif (Preg::isMatch('{^[AL]?GPL-[123](\.[01])?$}i', $license)) {
                        $warnings[] = sprintf(
                            'License "%s" is a deprecated SPDX license identifier, use "'.$license.'-only" or "'.$license.'-or-later" instead',
                            $license
                        );
                    } else {
                        $warnings[] = sprintf(
                            'License "%s" is a deprecated SPDX license identifier, see https://spdx.org/licenses/',
                            $license
                        );
                    }
                }
            }
        }

        if (($flags & self::CHECK_VERSION) && isset($manifest['version'])) {
            $warnings[] = 'The version field is present, it is recommended to leave it out if the package is published on Packagist.';
        }

        if (!empty($manifest['name']) && Preg::isMatch('{[A-Z]}', $manifest['name'])) {
            $suggestName = Preg::replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '\\1\\3-\\2\\4', $manifest['name']);
            $suggestName = strtolower($suggestName);

            $publishErrors[] = sprintf(
                'Name "%s" does not match the best practice (e.g. lower-cased/with-dashes). We suggest using "%s" instead. As such you will not be able to submit it to Packagist.',
                $manifest['name'],
                $suggestName
            );
        }

        if (!empty($manifest['type']) && $manifest['type'] === 'composer-installer') {
            $warnings[] = "The package type 'composer-installer' is deprecated. Please distribute your custom installers as plugins from now on. See https://getcomposer.org/doc/articles/plugins.md for plugin documentation.";
        }

        // check for require-dev overrides
        if (isset($manifest['require'], $manifest['require-dev'])) {
            $requireOverrides = array_intersect_key($manifest['require'], $manifest['require-dev']);

            if (!empty($requireOverrides)) {
                $plural = (count($requireOverrides) > 1) ? 'are' : 'is';
                $warnings[] = implode(', ', array_keys($requireOverrides)). " {$plural} required both in require and require-dev, this can lead to unexpected behavior";
            }
        }

        // check for meaningless provide/replace satisfying requirements
        foreach (['provide', 'replace'] as $linkType) {
            if (isset($manifest[$linkType])) {
                foreach (['require', 'require-dev'] as $requireType) {
                    if (isset($manifest[$requireType])) {
                        foreach ($manifest[$linkType] as $provide => $constraint) {
                            if (isset($manifest[$requireType][$provide])) {
                                $warnings[] = 'The package ' . $provide . ' in '.$requireType.' is also listed in '.$linkType.' which satisfies the requirement. Remove it from '.$linkType.' if you wish to install it.';
                            }
                        }
                    }
                }
            }
        }

        // check for commit references
        $require = $manifest['require'] ?? [];
        $requireDev = $manifest['require-dev'] ?? [];
        $packages = array_merge($require, $requireDev);
        foreach ($packages as $package => $version) {
            if (Preg::isMatch('/#/', $version)) {
                $warnings[] = sprintf(
                    'The package "%s" is pointing to a commit-ref, this is bad practice and can cause unforeseen issues.',
                    $package
                );
            }
        }

        // report scripts-descriptions for non-existent scripts
        $scriptsDescriptions = $manifest['scripts-descriptions'] ?? [];
        $scripts = $manifest['scripts'] ?? [];
        foreach ($scriptsDescriptions as $scriptName => $scriptDescription) {
            if (!array_key_exists($scriptName, $scripts)) {
                $warnings[] = sprintf(
                    'Description for non-existent script "%s" found in "scripts-descriptions"',
                    $scriptName
                );
            }
        }

        // report scripts-aliases for non-existent scripts
        $scriptAliases = $manifest['scripts-aliases'] ?? [];
        foreach ($scriptAliases as $scriptName => $scriptAlias) {
            if (!array_key_exists($scriptName, $scripts)) {
                $warnings[] = sprintf(
                    'Aliases for non-existent script "%s" found in "scripts-aliases"',
                    $scriptName
                );
            }
        }

        // check for empty psr-0/psr-4 namespace prefixes
        if (isset($manifest['autoload']['psr-0'][''])) {
            $warnings[] = "Defining autoload.psr-0 with an empty namespace prefix is a bad idea for performance";
        }
        if (isset($manifest['autoload']['psr-4'][''])) {
            $warnings[] = "Defining autoload.psr-4 with an empty namespace prefix is a bad idea for performance";
        }

        $loader = new ValidatingArrayLoader(new ArrayLoader(), true, null, $arrayLoaderValidationFlags);
        try {
            if (!isset($manifest['version'])) {
                $manifest['version'] = '1.0.0';
            }
            if (!isset($manifest['name'])) {
                $manifest['name'] = 'dummy/dummy';
            }
            $loader->load($manifest);
        } catch (InvalidPackageException $e) {
            $errors = array_merge($errors, $e->getErrors());
        }

        $warnings = array_merge($warnings, $loader->getWarnings());

        return [$errors, $publishErrors, $warnings];
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\XdebugHandler\XdebugHandler;

/**
 * Provides ini file location functions that work with and without a restart.
 * When the process has restarted it uses a tmp ini and stores the original
 * ini locations in an environment variable.
 *
 * @author John Stevenson <john-stevenson@blueyonder.co.uk>
 */
class IniHelper
{
    /**
     * Returns an array of php.ini locations with at least one entry
     *
     * The equivalent of calling php_ini_loaded_file then php_ini_scanned_files.
     * The loaded ini location is the first entry and may be empty.
     *
     * @return string[]
     */
    public static function getAll(): array
    {
        return XdebugHandler::getAllIniFiles();
    }

    /**
     * Describes the location of the loaded php.ini file(s)
     */
    public static function getMessage(): string
    {
        $paths = self::getAll();

        if (empty($paths[0])) {
            array_shift($paths);
        }

        $ini = array_shift($paths);

        if (empty($ini)) {
            return 'A php.ini file does not exist. You will have to create one.';
        }

        if (!empty($paths)) {
            return 'Your command-line PHP is using multiple ini files. Run `php --ini` to show them.';
        }

        return 'The php.ini used by your command-line PHP is: '.$ini;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\Config;
use Composer\Downloader\TransportException;
use Composer\Pcre\Preg;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class GitHub
{
    public const GITHUB_TOKEN_REGEX = '{^([a-f0-9]{12,}|gh[a-z]_[a-zA-Z0-9_]+|github_pat_[a-zA-Z0-9_]+)$}';

    /** @var IOInterface */
    protected $io;
    /** @var Config */
    protected $config;
    /** @var ProcessExecutor */
    protected $process;
    /** @var HttpDownloader */
    protected $httpDownloader;

    /**
     * Constructor.
     *
     * @param IOInterface     $io             The IO instance
     * @param Config          $config         The composer configuration
     * @param ProcessExecutor $process        Process instance, injectable for mocking
     * @param HttpDownloader  $httpDownloader Remote Filesystem, injectable for mocking
     */
    public function __construct(IOInterface $io, Config $config, ?ProcessExecutor $process = null, ?HttpDownloader $httpDownloader = null)
    {
        $this->io = $io;
        $this->config = $config;
        $this->process = $process ?: new ProcessExecutor($io);
        $this->httpDownloader = $httpDownloader ?: Factory::createHttpDownloader($this->io, $config);
    }

    /**
     * Attempts to authorize a GitHub domain via OAuth
     *
     * @param  string $originUrl The host this GitHub instance is located at
     * @return bool   true on success
     */
    public function authorizeOAuth(string $originUrl): bool
    {
        if (!in_array($originUrl, $this->config->get('github-domains'))) {
            return false;
        }

        // if available use token from git config
        if (0 === $this->process->execute(['git', 'config', 'github.accesstoken'], $output)) {
            $this->io->setAuthentication($originUrl, trim($output), 'x-oauth-basic');

            return true;
        }

        return false;
    }

    /**
     * Authorizes a GitHub domain interactively via OAuth
     *
     * @param  string                        $originUrl The host this GitHub instance is located at
     * @param  string                        $message   The reason this authorization is required
     * @throws \RuntimeException
     * @throws TransportException|\Exception
     * @return bool                          true on success
     */
    public function authorizeOAuthInteractively(string $originUrl, ?string $message = null): bool
    {
        if ($message) {
            $this->io->writeError($message);
        }

        $note = 'Composer';
        if ($this->config->get('github-expose-hostname') === true && 0 === $this->process->execute(['hostname'], $output)) {
            $note .= ' on ' . trim($output);
        }
        $note .= ' ' . date('Y-m-d Hi');

        $url = 'https://'.$originUrl.'/settings/tokens/new?scopes=&description=' . str_replace('%20', '+', rawurlencode($note));
        $this->io->writeError('When working with _public_ GitHub repositories only, head here to retrieve a token:');
        $this->io->writeError($url);
        $this->io->writeError('This token will have read-only permission for public information only.');

        $localAuthConfig = $this->config->getLocalAuthConfigSource();
        $url = 'https://'.$originUrl.'/settings/tokens/new?scopes=repo&description=' . str_replace('%20', '+', rawurlencode($note));
        $this->io->writeError('When you need to access _private_ GitHub repositories as well, go to:');
        $this->io->writeError($url);
        $this->io->writeError('Note that such tokens have broad read/write permissions on your behalf, even if not needed by Composer.');
        $this->io->writeError(sprintf('Tokens will be stored in plain text in "%s" for future use by Composer.', ($localAuthConfig !== null ? $localAuthConfig->getName() . ' OR ' : '') . $this->config->getAuthConfigSource()->getName()));
        $this->io->writeError('For additional information, check https://getcomposer.org/doc/articles/authentication-for-private-packages.md#github-oauth');

        $storeInLocalAuthConfig = false;
        if ($localAuthConfig !== null) {
            $storeInLocalAuthConfig = $this->io->askConfirmation('A local auth config source was found, do you want to store the token there?', true);
        }

        $token = trim((string) $this->io->askAndHideAnswer('Token (hidden): '));

        if ($token === '') {
            $this->io->writeError('<warning>No token given, aborting.</warning>');
            $this->io->writeError('You can also add it manually later by using "composer config --global --auth github-oauth.github.com <token>"');

            return false;
        }

        $this->io->setAuthentication($originUrl, $token, 'x-oauth-basic');

        try {
            $apiUrl = ('github.com' === $originUrl) ? 'api.github.com/' : $originUrl . '/api/v3/';

            $this->httpDownloader->get('https://'. $apiUrl, [
                'retry-auth-failure' => false,
            ]);
        } catch (TransportException $e) {
            if (in_array($e->getCode(), [403, 401])) {
                $this->io->writeError('<error>Invalid token provided.</error>');
                $this->io->writeError('You can also add it manually later by using "composer config --global --auth github-oauth.github.com <token>"');

                return false;
            }

            throw $e;
        }

        // store value in local/user config
        $authConfigSource = $storeInLocalAuthConfig && $localAuthConfig !== null ? $localAuthConfig : $this->config->getAuthConfigSource();
        $this->config->getConfigSource()->removeConfigSetting('github-oauth.'.$originUrl);
        $authConfigSource->addConfigSetting('github-oauth.'.$originUrl, $token);

        $this->io->writeError('<info>Token stored successfully.</info>');

        return true;
    }

    /**
     * Extract rate limit from response.
     *
     * @param string[] $headers Headers from Composer\Downloader\TransportException.
     *
     * @return array{limit: int|'?', reset: string}
     */
    public function getRateLimit(array $headers): array
    {
        $rateLimit = [
            'limit' => '?',
            'reset' => '?',
        ];

        foreach ($headers as $header) {
            $header = trim($header);
            if (false === stripos($header, 'x-ratelimit-')) {
                continue;
            }
            [$type, $value] = explode(':', $header, 2);
            switch (strtolower($type)) {
                case 'x-ratelimit-limit':
                    $rateLimit['limit'] = (int) trim($value);
                    break;
                case 'x-ratelimit-reset':
                    $rateLimit['reset'] = date('Y-m-d H:i:s', (int) trim($value));
                    break;
            }
        }

        return $rateLimit;
    }

    /**
     * Extract SSO URL from response.
     *
     * @param string[] $headers Headers from Composer\Downloader\TransportException.
     */
    public function getSsoUrl(array $headers): ?string
    {
        foreach ($headers as $header) {
            $header = trim($header);
            if (false === stripos($header, 'x-github-sso: required')) {
                continue;
            }
            if (Preg::isMatch('{\burl=(?P<url>[^\s;]+)}', $header, $match)) {
                return $match['url'];
            }
        }

        return null;
    }

    /**
     * Finds whether a request failed due to rate limiting
     *
     * @param string[] $headers Headers from Composer\Downloader\TransportException.
     */
    public function isRateLimited(array $headers): bool
    {
        foreach ($headers as $header) {
            if (Preg::isMatch('{^x-ratelimit-remaining: *0$}i', trim($header))) {
                return true;
            }
        }

        return false;
    }

    /**
     * Finds whether a request failed due to lacking SSO authorization
     *
     * @see https://docs.github.com/en/rest/overview/other-authentication-methods#authenticating-for-saml-sso
     *
     * @param string[] $headers Headers from Composer\Downloader\TransportException.
     */
    public function requiresSso(array $headers): bool
    {
        foreach ($headers as $header) {
            if (Preg::isMatch('{^x-github-sso: required}i', trim($header))) {
                return true;
            }
        }

        return false;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util\Http;

/**
 * @phpstan-type CurlInfo array{url: mixed, content_type: mixed, http_code: mixed, header_size: mixed, request_size: mixed, filetime: mixed, ssl_verify_result: mixed, redirect_count: mixed, total_time: mixed, namelookup_time: mixed, connect_time: mixed, pretransfer_time: mixed, size_upload: mixed, size_download: mixed, speed_download: mixed, speed_upload: mixed, download_content_length: mixed, upload_content_length: mixed, starttransfer_time: mixed, redirect_time: mixed, certinfo: mixed, primary_ip: mixed, primary_port: mixed, local_ip: mixed, local_port: mixed, redirect_url: mixed}
 */
class CurlResponse extends Response
{
    /**
     * @see https://www.php.net/curl_getinfo
     * @var array
     * @phpstan-var CurlInfo
     */
    private $curlInfo;

    /**
     * @phpstan-param CurlInfo $curlInfo
     */
    public function __construct(array $request, ?int $code, array $headers, ?string $body, array $curlInfo)
    {
        parent::__construct($request, $code, $headers, $body);
        $this->curlInfo = $curlInfo;
    }

    /**
     * @phpstan-return CurlInfo
     */
    public function getCurlInfo(): array
    {
        return $this->curlInfo;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util\Http;

use Composer\Downloader\TransportException;
use Composer\Util\NoProxyPattern;

/**
 * @internal
 * @author John Stevenson <john-stevenson@blueyonder.co.uk>
 */
class ProxyManager
{
    /** @var ?string */
    private $error = null;
    /** @var ?ProxyItem */
    private $httpProxy = null;
    /** @var ?ProxyItem */
    private $httpsProxy = null;
    /** @var ?NoProxyPattern */
    private $noProxyHandler = null;

    /** @var ?self */
    private static $instance = null;

    private function __construct()
    {
        try {
            $this->getProxyData();
        } catch (\RuntimeException $e) {
            $this->error = $e->getMessage();
        }
    }

    public static function getInstance(): ProxyManager
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }

        return self::$instance;
    }

    /**
     * Clears the persistent instance
     */
    public static function reset(): void
    {
        self::$instance = null;
    }

    public function hasProxy(): bool
    {
        return $this->httpProxy !== null || $this->httpsProxy !== null;
    }

    /**
     * Returns a RequestProxy instance for the request url
     *
     * @param non-empty-string $requestUrl
     */
    public function getProxyForRequest(string $requestUrl): RequestProxy
    {
        if ($this->error !== null) {
            throw new TransportException('Unable to use a proxy: '.$this->error);
        }

        $scheme = (string) parse_url($requestUrl, PHP_URL_SCHEME);
        $proxy = $this->getProxyForScheme($scheme);

        if ($proxy === null) {
            return RequestProxy::none();
        }

        if ($this->noProxy($requestUrl)) {
            return RequestProxy::noProxy();
        }

        return $proxy->toRequestProxy($scheme);
    }

    /**
     * Returns a ProxyItem if one is set for the scheme, otherwise null
     */
    private function getProxyForScheme(string $scheme): ?ProxyItem
    {
        if ($scheme === 'http') {
            return $this->httpProxy;
        }

        if ($scheme === 'https') {
            return $this->httpsProxy;
        }

        return null;
    }

    /**
     * Finds proxy values from the environment and sets class properties
     */
    private function getProxyData(): void
    {
        // Handle http_proxy/HTTP_PROXY on CLI only for security reasons
        if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
            [$env, $name] = $this->getProxyEnv('http_proxy');
            if ($env !== null) {
                $this->httpProxy = new ProxyItem($env, $name);
            }
        }

        // Handle cgi_http_proxy/CGI_HTTP_PROXY if needed
        if ($this->httpProxy === null) {
            [$env, $name] = $this->getProxyEnv('cgi_http_proxy');
            if ($env !== null) {
                $this->httpProxy = new ProxyItem($env, $name);
            }
        }

        // Handle https_proxy/HTTPS_PROXY
        [$env, $name] = $this->getProxyEnv('https_proxy');
        if ($env !== null) {
            $this->httpsProxy = new ProxyItem($env, $name);
        }

        // Handle no_proxy/NO_PROXY
        [$env, $name] = $this->getProxyEnv('no_proxy');
        if ($env !== null) {
            $this->noProxyHandler = new NoProxyPattern($env);
        }
    }

    /**
     * Searches $_SERVER for case-sensitive values
     *
     * @return array{0: string|null, 1: string} value, name
     */
    private function getProxyEnv(string $envName): array
    {
        $names = [strtolower($envName), strtoupper($envName)];

        foreach ($names as $name) {
            if (is_string($_SERVER[$name] ?? null)) {
                if ($_SERVER[$name] !== '') {
                    return [$_SERVER[$name], $name];
                }
            }
        }

        return [null, ''];
    }

    /**
     * Returns true if a url matches no_proxy value
     */
    private function noProxy(string $requestUrl): bool
    {
        if ($this->noProxyHandler === null) {
            return false;
        }

        return $this->noProxyHandler->test($requestUrl);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util\Http;

/**
 * @internal
 * @author John Stevenson <john-stevenson@blueyonder.co.uk>
 */
class ProxyItem
{
    /** @var non-empty-string */
    private $url;
    /** @var non-empty-string */
    private $safeUrl;
    /** @var ?non-empty-string */
    private $curlAuth;
    /** @var string */
    private $optionsProxy;
    /** @var ?non-empty-string */
    private $optionsAuth;

    /**
     * @param string $proxyUrl The value from the environment
     * @param string $envName The name of the environment variable
     * @throws \RuntimeException If the proxy url is invalid
     */
    public function __construct(string $proxyUrl, string $envName)
    {
        $syntaxError = sprintf('unsupported `%s` syntax', $envName);

        if (strpbrk($proxyUrl, "\r\n\t") !== false) {
            throw new \RuntimeException($syntaxError);
        }
        if (false === ($proxy = parse_url($proxyUrl))) {
            throw new \RuntimeException($syntaxError);
        }
        if (!isset($proxy['host'])) {
            throw new \RuntimeException('unable to find proxy host in ' . $envName);
        }

        $scheme = isset($proxy['scheme']) ? strtolower($proxy['scheme']) . '://' : 'http://';
        $safe = '';

        if (isset($proxy['user'])) {
            $safe = '***';
            $user = $proxy['user'];
            $auth = rawurldecode($proxy['user']);

            if (isset($proxy['pass'])) {
                $safe .= ':***';
                $user .= ':' . $proxy['pass'];
                $auth .= ':' . rawurldecode($proxy['pass']);
            }

            $safe .= '@';

            if (strlen($user) > 0) {
                $this->curlAuth = $user;
                $this->optionsAuth = 'Proxy-Authorization: Basic ' . base64_encode($auth);
            }
        }

        $host = $proxy['host'];
        $port = null;

        if (isset($proxy['port'])) {
            $port = $proxy['port'];
        } elseif ($scheme === 'http://') {
            $port = 80;
        } elseif ($scheme === 'https://') {
            $port = 443;
        }

        // We need a port because curl uses 1080 for http. Port 0 is reserved,
        // but is considered valid depending on the PHP or Curl version.
        if ($port === null) {
            throw new \RuntimeException('unable to find proxy port in ' . $envName);
        }
        if ($port === 0) {
            throw new \RuntimeException('port 0 is reserved in ' . $envName);
        }

        $this->url = sprintf('%s%s:%d', $scheme, $host, $port);
        $this->safeUrl = sprintf('%s%s%s:%d', $scheme, $safe, $host, $port);

        $scheme = str_replace(['http://', 'https://'], ['tcp://', 'ssl://'], $scheme);
        $this->optionsProxy = sprintf('%s%s:%d', $scheme, $host, $port);
    }

    /**
     * Returns a RequestProxy instance for the scheme of the request url
     *
     * @param string $scheme The scheme of the request url
     */
    public function toRequestProxy(string $scheme): RequestProxy
    {
        $options = ['http' => ['proxy' => $this->optionsProxy]];

        if ($this->optionsAuth !== null) {
            $options['http']['header'] = $this->optionsAuth;
        }

        if ($scheme === 'http') {
            $options['http']['request_fulluri'] = true;
        }

        return new RequestProxy($this->url, $this->curlAuth, $options, $this->safeUrl);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util\Http;

use Composer\Json\JsonFile;
use Composer\Pcre\Preg;
use Composer\Util\HttpDownloader;

/**
 * @phpstan-type Request array{url: non-empty-string, options?: mixed[], copyTo?: string|null}
 */
class Response
{
    /** @var Request */
    private $request;
    /** @var int */
    private $code;
    /** @var list<string> */
    private $headers;
    /** @var ?string */
    private $body;

    /**
     * @param Request $request
     * @param list<string> $headers
     */
    public function __construct(array $request, ?int $code, array $headers, ?string $body)
    {
        if (!isset($request['url'])) {
            throw new \LogicException('url key missing from request array');
        }
        $this->request = $request;
        $this->code = (int) $code;
        $this->headers = $headers;
        $this->body = $body;
    }

    public function getStatusCode(): int
    {
        return $this->code;
    }

    public function getStatusMessage(): ?string
    {
        $value = null;
        foreach ($this->headers as $header) {
            if (Preg::isMatch('{^HTTP/\S+ \d+}i', $header)) {
                // In case of redirects, headers contain the headers of all responses
                // so we can not return directly and need to keep iterating
                $value = $header;
            }
        }

        return $value;
    }

    /**
     * @return string[]
     */
    public function getHeaders(): array
    {
        return $this->headers;
    }

    /**
     * @return ?string
     */
    public function getHeader(string $name): ?string
    {
        return self::findHeaderValue($this->headers, $name);
    }

    /**
     * @return ?string
     */
    public function getBody(): ?string
    {
        return $this->body;
    }

    /**
     * @return mixed
     */
    public function decodeJson()
    {
        return JsonFile::parseJson($this->body, $this->request['url']);
    }

    /**
     * @phpstan-impure
     */
    public function collect(): void
    {
        unset($this->request, $this->code, $this->headers, $this->body);
    }

    /**
     * @param  string[]    $headers array of returned headers like from getLastHeaders()
     * @param  string      $name    header name (case insensitive)
     */
    public static function findHeaderValue(array $headers, string $name): ?string
    {
        $value = null;
        foreach ($headers as $header) {
            if (Preg::isMatch('{^'.preg_quote($name).':\s*(.+?)\s*$}i', $header, $match)) {
                $value = $match[1];
            }
        }

        return $value;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util\Http;

use Composer\Config;
use Composer\Downloader\MaxFileSizeExceededException;
use Composer\IO\IOInterface;
use Composer\Downloader\TransportException;
use Composer\Pcre\Preg;
use Composer\Util\Platform;
use Composer\Util\StreamContextFactory;
use Composer\Util\AuthHelper;
use Composer\Util\Url;
use Composer\Util\HttpDownloader;
use React\Promise\Promise;
use Symfony\Component\HttpFoundation\IpUtils;

/**
 * @internal
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Nicolas Grekas <p@tchwork.com>
 * @phpstan-type Attributes array{retryAuthFailure: bool, redirects: int<0, max>, retries: int<0, max>, storeAuth: 'prompt'|bool, ipResolve: 4|6|null}
 * @phpstan-type Job array{url: non-empty-string, origin: string, attributes: Attributes, options: mixed[], progress: mixed[], curlHandle: \CurlHandle, filename: string|null, headerHandle: resource, bodyHandle: resource, resolve: callable, reject: callable, primaryIp: string}
 */
class CurlDownloader
{
    /**
     * Known libcurl's broken versions when proxy is in use with HTTP/2
     * multiplexing.
     *
     * @var list<non-empty-string>
     */
    private const BAD_MULTIPLEXING_CURL_VERSIONS = ['7.87.0', '7.88.0', '7.88.1'];

    /** @var \CurlMultiHandle */
    private $multiHandle;
    /** @var \CurlShareHandle */
    private $shareHandle;
    /** @var Job[] */
    private $jobs = [];
    /** @var IOInterface */
    private $io;
    /** @var Config */
    private $config;
    /** @var AuthHelper */
    private $authHelper;
    /** @var float */
    private $selectTimeout = 5.0;
    /** @var int */
    private $maxRedirects = 20;
    /** @var int */
    private $maxRetries = 3;
    /** @var array<int, string[]> */
    protected $multiErrors = [
        CURLM_BAD_HANDLE => ['CURLM_BAD_HANDLE', 'The passed-in handle is not a valid CURLM handle.'],
        CURLM_BAD_EASY_HANDLE => ['CURLM_BAD_EASY_HANDLE', "An easy handle was not good/valid. It could mean that it isn't an easy handle at all, or possibly that the handle already is in used by this or another multi handle."],
        CURLM_OUT_OF_MEMORY => ['CURLM_OUT_OF_MEMORY', 'You are doomed.'],
        CURLM_INTERNAL_ERROR => ['CURLM_INTERNAL_ERROR', 'This can only be returned if libcurl bugs. Please report it to us!'],
    ];

    /** @var mixed[] */
    private static $options = [
        'http' => [
            'method' => CURLOPT_CUSTOMREQUEST,
            'content' => CURLOPT_POSTFIELDS,
            'header' => CURLOPT_HTTPHEADER,
            'timeout' => CURLOPT_TIMEOUT,
        ],
        'ssl' => [
            'cafile' => CURLOPT_CAINFO,
            'capath' => CURLOPT_CAPATH,
            'verify_peer' => CURLOPT_SSL_VERIFYPEER,
            'verify_peer_name' => CURLOPT_SSL_VERIFYHOST,
            'local_cert' => CURLOPT_SSLCERT,
            'local_pk' => CURLOPT_SSLKEY,
            'passphrase' => CURLOPT_SSLKEYPASSWD,
        ],
    ];

    /** @var array<string, true> */
    private static $timeInfo = [
        'total_time' => true,
        'namelookup_time' => true,
        'connect_time' => true,
        'pretransfer_time' => true,
        'starttransfer_time' => true,
        'redirect_time' => true,
    ];

    /**
     * @param mixed[] $options
     */
    public function __construct(IOInterface $io, Config $config, array $options = [], bool $disableTls = false)
    {
        $this->io = $io;
        $this->config = $config;

        $this->multiHandle = $mh = curl_multi_init();
        if (function_exists('curl_multi_setopt')) {
            if (ProxyManager::getInstance()->hasProxy() && ($version = curl_version()) !== false && in_array($version['version'], self::BAD_MULTIPLEXING_CURL_VERSIONS, true)) {
                /**
                 * Disable HTTP/2 multiplexing for some broken versions of libcurl.
                 *
                 * In certain versions of libcurl when proxy is in use with HTTP/2
                 * multiplexing, connections will continue stacking up. This was
                 * fixed in libcurl 8.0.0 in curl/curl@821f6e2a89de8aec1c7da3c0f381b92b2b801efc
                 */
                curl_multi_setopt($mh, CURLMOPT_PIPELINING, /* CURLPIPE_NOTHING */ 0);
            } else {
                curl_multi_setopt($mh, CURLMOPT_PIPELINING, \PHP_VERSION_ID >= 70400 ? /* CURLPIPE_MULTIPLEX */ 2 : /*CURLPIPE_HTTP1 | CURLPIPE_MULTIPLEX*/ 3);
            }
            if (defined('CURLMOPT_MAX_HOST_CONNECTIONS') && !defined('HHVM_VERSION')) {
                curl_multi_setopt($mh, CURLMOPT_MAX_HOST_CONNECTIONS, 8);
            }
        }

        if (function_exists('curl_share_init')) {
            $this->shareHandle = $sh = curl_share_init();
            curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
            curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
            curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
        }

        $this->authHelper = new AuthHelper($io, $config);
    }

    /**
     * @param mixed[]  $options
     * @param non-empty-string $url
     *
     * @return int internal job id
     */
    public function download(callable $resolve, callable $reject, string $origin, string $url, array $options, ?string $copyTo = null): int
    {
        $attributes = [];
        if (isset($options['retry-auth-failure'])) {
            $attributes['retryAuthFailure'] = $options['retry-auth-failure'];
            unset($options['retry-auth-failure']);
        }

        return $this->initDownload($resolve, $reject, $origin, $url, $options, $copyTo, $attributes);
    }

    /**
     * @param mixed[]  $options
     *
     * @param array{retryAuthFailure?: bool, redirects?: int<0, max>, retries?: int<0, max>, storeAuth?: 'prompt'|bool, ipResolve?: 4|6|null} $attributes
     * @param non-empty-string $url
     *
     * @return int internal job id
     */
    private function initDownload(callable $resolve, callable $reject, string $origin, string $url, array $options, ?string $copyTo = null, array $attributes = []): int
    {
        $attributes = array_merge([
            'retryAuthFailure' => true,
            'redirects' => 0,
            'retries' => 0,
            'storeAuth' => false,
            'ipResolve' => null,
        ], $attributes);

        if ($attributes['ipResolve'] === null && Platform::getEnv('COMPOSER_IPRESOLVE') === '4') {
            $attributes['ipResolve'] = 4;
        } elseif ($attributes['ipResolve'] === null && Platform::getEnv('COMPOSER_IPRESOLVE') === '6') {
            $attributes['ipResolve'] = 6;
        }

        $originalOptions = $options;

        // check URL can be accessed (i.e. is not insecure), but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256
        if (!Preg::isMatch('{^http://(repo\.)?packagist\.org/p/}', $url) || (false === strpos($url, '$') && false === strpos($url, '%24'))) {
            $this->config->prohibitUrlByConfig($url, $this->io, $options);
        }

        $curlHandle = curl_init();
        $headerHandle = fopen('php://temp/maxmemory:32768', 'w+b');
        if (false === $headerHandle) {
            throw new \RuntimeException('Failed to open a temp stream to store curl headers');
        }

        if ($copyTo !== null) {
            $bodyTarget = $copyTo.'~';
        } else {
            $bodyTarget = 'php://temp/maxmemory:524288';
        }

        $errorMessage = '';
        set_error_handler(static function (int $code, string $msg) use (&$errorMessage): bool {
            if ($errorMessage) {
                $errorMessage .= "\n";
            }
            $errorMessage .= Preg::replace('{^fopen\(.*?\): }', '', $msg);

            return true;
        });
        $bodyHandle = fopen($bodyTarget, 'w+b');
        restore_error_handler();
        if (false === $bodyHandle) {
            throw new TransportException('The "'.$url.'" file could not be written to '.($copyTo ?? 'a temporary file').': '.$errorMessage);
        }

        curl_setopt($curlHandle, CURLOPT_URL, $url);
        curl_setopt($curlHandle, CURLOPT_FOLLOWLOCATION, false);
        curl_setopt($curlHandle, CURLOPT_CONNECTTIMEOUT, 10);
        curl_setopt($curlHandle, CURLOPT_TIMEOUT, max((int) ini_get("default_socket_timeout"), 300));
        curl_setopt($curlHandle, CURLOPT_WRITEHEADER, $headerHandle);
        curl_setopt($curlHandle, CURLOPT_FILE, $bodyHandle);
        curl_setopt($curlHandle, CURLOPT_ENCODING, ""); // let cURL set the Accept-Encoding header to what it supports
        curl_setopt($curlHandle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);

        if ($attributes['ipResolve'] === 4) {
            curl_setopt($curlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
        } elseif ($attributes['ipResolve'] === 6) {
            curl_setopt($curlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6);
        }

        if (function_exists('curl_share_init')) {
            curl_setopt($curlHandle, CURLOPT_SHARE, $this->shareHandle);
        }

        if (!isset($options['http']['header'])) {
            $options['http']['header'] = [];
        }

        $options['http']['header'] = array_diff($options['http']['header'], ['Connection: close']);
        $options['http']['header'][] = 'Connection: keep-alive';

        $version = curl_version();
        $features = $version['features'];
        if (0 === strpos($url, 'https://') && \defined('CURL_VERSION_HTTP2') && \defined('CURL_HTTP_VERSION_2_0') && (CURL_VERSION_HTTP2 & $features) !== 0) {
            curl_setopt($curlHandle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
        }

        // curl 8.7.0 - 8.7.1 has a bug whereas automatic accept-encoding header results in an error when reading the response
        // https://github.com/composer/composer/issues/11913
        if (isset($version['version']) && in_array($version['version'], ['8.7.0', '8.7.1'], true) && \defined('CURL_VERSION_LIBZ') && (CURL_VERSION_LIBZ & $features) !== 0) {
            curl_setopt($curlHandle, CURLOPT_ENCODING, "gzip");
        }

        $options['http']['header'] = $this->authHelper->addAuthenticationHeader($options['http']['header'], $origin, $url);
        $options = StreamContextFactory::initOptions($url, $options, true);

        foreach (self::$options as $type => $curlOptions) {
            foreach ($curlOptions as $name => $curlOption) {
                if (isset($options[$type][$name])) {
                    if ($type === 'ssl' && $name === 'verify_peer_name') {
                        curl_setopt($curlHandle, $curlOption, $options[$type][$name] === true ? 2 : $options[$type][$name]);
                    } else {
                        curl_setopt($curlHandle, $curlOption, $options[$type][$name]);
                    }
                }
            }
        }

        $proxy = ProxyManager::getInstance()->getProxyForRequest($url);
        curl_setopt_array($curlHandle, $proxy->getCurlOptions($options['ssl'] ?? []));

        $progress = array_diff_key(curl_getinfo($curlHandle), self::$timeInfo);

        $this->jobs[(int) $curlHandle] = [
            'url' => $url,
            'origin' => $origin,
            'attributes' => $attributes,
            'options' => $originalOptions,
            'progress' => $progress,
            'curlHandle' => $curlHandle,
            'filename' => $copyTo,
            'headerHandle' => $headerHandle,
            'bodyHandle' => $bodyHandle,
            'resolve' => $resolve,
            'reject' => $reject,
            'primaryIp' => '',
        ];

        $usingProxy = $proxy->getStatus(' using proxy (%s)');
        $ifModified = false !== stripos(implode(',', $options['http']['header']), 'if-modified-since:') ? ' if modified' : '';
        if ($attributes['redirects'] === 0 && $attributes['retries'] === 0) {
            $this->io->writeError('Downloading ' . Url::sanitize($url) . $usingProxy . $ifModified, true, IOInterface::DEBUG);
        }

        $this->checkCurlResult(curl_multi_add_handle($this->multiHandle, $curlHandle));
        // TODO progress

        return (int) $curlHandle;
    }

    public function abortRequest(int $id): void
    {
        if (isset($this->jobs[$id], $this->jobs[$id]['curlHandle'])) {
            $job = $this->jobs[$id];
            curl_multi_remove_handle($this->multiHandle, $job['curlHandle']);
            if(\PHP_VERSION_ID < 80000) {
                curl_close($job['curlHandle']);
            }
            if (is_resource($job['headerHandle'])) {
                fclose($job['headerHandle']);
            }
            if (is_resource($job['bodyHandle'])) {
                fclose($job['bodyHandle']);
            }
            if (null !== $job['filename']) {
                @unlink($job['filename'].'~');
            }
            unset($this->jobs[$id]);
        }
    }

    public function tick(): void
    {
        static $timeoutWarning = false;

        if (count($this->jobs) === 0) {
            return;
        }

        $active = true;
        $this->checkCurlResult(curl_multi_exec($this->multiHandle, $active));
        if (-1 === curl_multi_select($this->multiHandle, $this->selectTimeout)) {
            // sleep in case select returns -1 as it can happen on old php versions or some platforms where curl does not manage to do the select
            usleep(150);
        }

        while ($progress = curl_multi_info_read($this->multiHandle)) {
            $curlHandle = $progress['handle'];
            $result = $progress['result'];
            $i = (int) $curlHandle;
            if (!isset($this->jobs[$i])) {
                continue;
            }

            $progress = curl_getinfo($curlHandle);
            if (false === $progress) {
                throw new \RuntimeException('Failed getting info from curl handle '.$i.' ('.$this->jobs[$i]['url'].')');
            }
            $job = $this->jobs[$i];
            unset($this->jobs[$i]);
            $error = curl_error($curlHandle);
            $errno = curl_errno($curlHandle);
            curl_multi_remove_handle($this->multiHandle, $curlHandle);
            if (\PHP_VERSION_ID < 80000) {
                curl_close($curlHandle);
            }

            $headers = null;
            $statusCode = null;
            $response = null;
            try {
                // TODO progress
                if (CURLE_OK !== $errno || $error || $result !== CURLE_OK) {
                    $errno = $errno ?: $result;
                    if (!$error && function_exists('curl_strerror')) {
                        $error = curl_strerror($errno);
                    }
                    $progress['error_code'] = $errno;

                    if (
                        (!isset($job['options']['http']['method']) || $job['options']['http']['method'] === 'GET')
                        && (
                            in_array($errno, [7 /* CURLE_COULDNT_CONNECT */, 16 /* CURLE_HTTP2 */, 92 /* CURLE_HTTP2_STREAM */, 6 /* CURLE_COULDNT_RESOLVE_HOST */], true)
                            || (in_array($errno, [56 /* CURLE_RECV_ERROR */, 35 /* CURLE_SSL_CONNECT_ERROR */], true) && str_contains((string) $error, 'Connection reset by peer'))
                        ) && $job['attributes']['retries'] < $this->maxRetries
                    ) {
                        $attributes = ['retries' => $job['attributes']['retries'] + 1];
                        if ($errno === 7 && !isset($job['attributes']['ipResolve'])) { // CURLE_COULDNT_CONNECT, retry forcing IPv4 if no IP stack was selected
                            $attributes['ipResolve'] = 4;
                        }
                        $this->io->writeError('Retrying ('.($job['attributes']['retries'] + 1).') ' . Url::sanitize($job['url']) . ' due to curl error '. $errno, true, IOInterface::DEBUG);
                        $this->restartJobWithDelay($job, $job['url'], $attributes);
                        continue;
                    }

                    // TODO: Remove this as soon as https://github.com/curl/curl/issues/10591 is resolved
                    if ($errno === 55 /* CURLE_SEND_ERROR */) {
                        $this->io->writeError('Retrying ('.($job['attributes']['retries'] + 1).') ' . Url::sanitize($job['url']) . ' due to curl error '. $errno, true, IOInterface::DEBUG);
                        $this->restartJobWithDelay($job, $job['url'], ['retries' => $job['attributes']['retries'] + 1]);
                        continue;
                    }

                    if ($errno === 28 /* CURLE_OPERATION_TIMEDOUT */ && \PHP_VERSION_ID >= 70300 && $progress['namelookup_time'] === 0.0 && !$timeoutWarning) {
                        $timeoutWarning = true;
                        $this->io->writeError('<warning>A connection timeout was encountered. If you intend to run Composer without connecting to the internet, run the command again prefixed with COMPOSER_DISABLE_NETWORK=1 to make Composer run in offline mode.</warning>');
                    }

                    throw new TransportException('curl error '.$errno.' while downloading '.Url::sanitize($progress['url']).': '.$error);
                }
                $statusCode = $progress['http_code'];
                rewind($job['headerHandle']);
                $headers = explode("\r\n", rtrim(stream_get_contents($job['headerHandle'])));
                fclose($job['headerHandle']);

                if ($statusCode === 0) {
                    throw new \LogicException('Received unexpected http status code 0 without error for '.Url::sanitize($progress['url']).': headers '.var_export($headers, true).' curl info '.var_export($progress, true));
                }

                // prepare response object
                if (null !== $job['filename']) {
                    $contents = $job['filename'].'~';
                    if ($statusCode >= 300) {
                        rewind($job['bodyHandle']);
                        $contents = stream_get_contents($job['bodyHandle']);
                    }
                    $response = new CurlResponse(['url' => $job['url']], $statusCode, $headers, $contents, $progress);
                    $this->io->writeError('['.$statusCode.'] '.Url::sanitize($job['url']), true, IOInterface::DEBUG);
                } else {
                    $maxFileSize = $job['options']['max_file_size'] ?? null;
                    rewind($job['bodyHandle']);
                    if ($maxFileSize !== null) {
                        $contents = stream_get_contents($job['bodyHandle'], $maxFileSize);
                        // Gzipped responses with missing Content-Length header cannot be detected during the file download
                        // because $progress['size_download'] refers to the gzipped size downloaded, not the actual file size
                        if ($contents !== false && Platform::strlen($contents) >= $maxFileSize) {
                            throw new MaxFileSizeExceededException('Maximum allowed download size reached. Downloaded ' . Platform::strlen($contents) . ' of allowed ' .  $maxFileSize . ' bytes');
                        }
                    } else {
                        $contents = stream_get_contents($job['bodyHandle']);
                    }

                    $response = new CurlResponse(['url' => $job['url']], $statusCode, $headers, $contents, $progress);
                    $this->io->writeError('['.$statusCode.'] '.Url::sanitize($job['url']), true, IOInterface::DEBUG);
                }
                fclose($job['bodyHandle']);

                if ($response->getStatusCode() >= 300 && $response->getHeader('content-type') === 'application/json') {
                    HttpDownloader::outputWarnings($this->io, $job['origin'], json_decode($response->getBody(), true));
                }

                $result = $this->isAuthenticatedRetryNeeded($job, $response);
                if ($result['retry']) {
                    $this->restartJob($job, $job['url'], ['storeAuth' => $result['storeAuth'], 'retries' => $job['attributes']['retries'] + 1]);
                    continue;
                }

                // handle 3xx redirects, 304 Not Modified is excluded
                if ($statusCode >= 300 && $statusCode <= 399 && $statusCode !== 304 && $job['attributes']['redirects'] < $this->maxRedirects) {
                    $location = $this->handleRedirect($job, $response);
                    if ($location) {
                        $this->restartJob($job, $location, ['redirects' => $job['attributes']['redirects'] + 1]);
                        continue;
                    }
                }

                // fail 4xx and 5xx responses and capture the response
                if ($statusCode >= 400 && $statusCode <= 599) {
                    if (
                        (!isset($job['options']['http']['method']) || $job['options']['http']['method'] === 'GET')
                        && in_array($statusCode, [423, 425, 500, 502, 503, 504, 507, 510], true)
                        && $job['attributes']['retries'] < $this->maxRetries
                    ) {
                        $this->io->writeError('Retrying ('.($job['attributes']['retries'] + 1).') ' . Url::sanitize($job['url']) . ' due to status code '. $statusCode, true, IOInterface::DEBUG);
                        $this->restartJobWithDelay($job, $job['url'], ['retries' => $job['attributes']['retries'] + 1]);
                        continue;
                    }

                    throw $this->failResponse($job, $response, $response->getStatusMessage());
                }

                if ($job['attributes']['storeAuth'] !== false) {
                    $this->authHelper->storeAuth($job['origin'], $job['attributes']['storeAuth']);
                }

                // resolve promise
                if (null !== $job['filename']) {
                    rename($job['filename'].'~', $job['filename']);
                    $job['resolve']($response);
                } else {
                    $job['resolve']($response);
                }
            } catch (\Exception $e) {
                if ($e instanceof TransportException) {
                    if (null !== $headers) {
                        $e->setHeaders($headers);
                        $e->setStatusCode($statusCode);
                    }
                    if (null !== $response) {
                        $e->setResponse($response->getBody());
                    }
                    $e->setResponseInfo($progress);
                }

                $this->rejectJob($job, $e);
            }
        }

        foreach ($this->jobs as $i => $curlHandle) {
            $curlHandle = $this->jobs[$i]['curlHandle'];
            $progress = array_diff_key(curl_getinfo($curlHandle), self::$timeInfo);

            if ($this->jobs[$i]['progress'] !== $progress) {
                $this->jobs[$i]['progress'] = $progress;

                if (isset($this->jobs[$i]['options']['max_file_size'])) {
                    // Compare max_file_size with the content-length header this value will be -1 until the header is parsed
                    if ($this->jobs[$i]['options']['max_file_size'] < $progress['download_content_length']) {
                        $this->rejectJob($this->jobs[$i], new MaxFileSizeExceededException('Maximum allowed download size reached. Content-length header indicates ' . $progress['download_content_length'] . ' bytes. Allowed ' .  $this->jobs[$i]['options']['max_file_size'] . ' bytes'));
                    }

                    // Compare max_file_size with the download size in bytes
                    if ($this->jobs[$i]['options']['max_file_size'] < $progress['size_download']) {
                        $this->rejectJob($this->jobs[$i], new MaxFileSizeExceededException('Maximum allowed download size reached. Downloaded ' . $progress['size_download'] . ' of allowed ' .  $this->jobs[$i]['options']['max_file_size'] . ' bytes'));
                    }
                }

                if (isset($progress['primary_ip']) && $progress['primary_ip'] !== $this->jobs[$i]['primaryIp']) {
                    if (
                        isset($this->jobs[$i]['options']['prevent_ip_access_callable']) &&
                        is_callable($this->jobs[$i]['options']['prevent_ip_access_callable']) &&
                        $this->jobs[$i]['options']['prevent_ip_access_callable']($progress['primary_ip'])
                    ) {
                        $this->rejectJob($this->jobs[$i], new TransportException(sprintf('IP "%s" is blocked for "%s".', $progress['primary_ip'], $progress['url'])));
                    }

                    $this->jobs[$i]['primaryIp'] = (string) $progress['primary_ip'];
                }

                // TODO progress
            }
        }
    }

    /**
     * @param  Job    $job
     */
    private function handleRedirect(array $job, Response $response): string
    {
        if ($locationHeader = $response->getHeader('location')) {
            if (parse_url($locationHeader, PHP_URL_SCHEME)) {
                // Absolute URL; e.g. https://example.com/composer
                $targetUrl = $locationHeader;
            } elseif (parse_url($locationHeader, PHP_URL_HOST)) {
                // Scheme relative; e.g. //example.com/foo
                $targetUrl = parse_url($job['url'], PHP_URL_SCHEME).':'.$locationHeader;
            } elseif ('/' === $locationHeader[0]) {
                // Absolute path; e.g. /foo
                $urlHost = parse_url($job['url'], PHP_URL_HOST);

                // Replace path using hostname as an anchor.
                $targetUrl = Preg::replace('{^(.+(?://|@)'.preg_quote($urlHost).'(?::\d+)?)(?:[/\?].*)?$}', '\1'.$locationHeader, $job['url']);
            } else {
                // Relative path; e.g. foo
                // This actually differs from PHP which seems to add duplicate slashes.
                $targetUrl = Preg::replace('{^(.+/)[^/?]*(?:\?.*)?$}', '\1'.$locationHeader, $job['url']);
            }
        }

        if (!empty($targetUrl)) {
            $this->io->writeError(sprintf('Following redirect (%u) %s', $job['attributes']['redirects'] + 1, Url::sanitize($targetUrl)), true, IOInterface::DEBUG);

            return $targetUrl;
        }

        throw new TransportException('The "'.$job['url'].'" file could not be downloaded, got redirect without Location ('.$response->getStatusMessage().')');
    }

    /**
     * @param  Job                                          $job
     * @return array{retry: bool, storeAuth: 'prompt'|bool}
     */
    private function isAuthenticatedRetryNeeded(array $job, Response $response): array
    {
        if (in_array($response->getStatusCode(), [401, 403]) && $job['attributes']['retryAuthFailure']) {
            $result = $this->authHelper->promptAuthIfNeeded($job['url'], $job['origin'], $response->getStatusCode(), $response->getStatusMessage(), $response->getHeaders(), $job['attributes']['retries']);

            if ($result['retry']) {
                return $result;
            }
        }

        $locationHeader = $response->getHeader('location');
        $needsAuthRetry = false;

        // check for bitbucket login page asking to authenticate
        if (
            $job['origin'] === 'bitbucket.org'
            && !$this->authHelper->isPublicBitBucketDownload($job['url'])
            && substr($job['url'], -4) === '.zip'
            && (!$locationHeader || substr($locationHeader, -4) !== '.zip')
            && Preg::isMatch('{^text/html\b}i', $response->getHeader('content-type'))
        ) {
            $needsAuthRetry = 'Bitbucket requires authentication and it was not provided';
        }

        // check for gitlab 404 when downloading archives
        if (
            $response->getStatusCode() === 404
            && in_array($job['origin'], $this->config->get('gitlab-domains'), true)
            && false !== strpos($job['url'], 'archive.zip')
        ) {
            $needsAuthRetry = 'GitLab requires authentication and it was not provided';
        }

        if ($needsAuthRetry) {
            if ($job['attributes']['retryAuthFailure']) {
                $result = $this->authHelper->promptAuthIfNeeded($job['url'], $job['origin'], 401, null, [], $job['attributes']['retries']);
                if ($result['retry']) {
                    return $result;
                }
            }

            throw $this->failResponse($job, $response, $needsAuthRetry);
        }

        return ['retry' => false, 'storeAuth' => false];
    }

    /**
     * @param  Job    $job
     * @param non-empty-string $url
     *
     * @param  array{retryAuthFailure?: bool, redirects?: int<0, max>, storeAuth?: 'prompt'|bool, retries?: int<1, max>, ipResolve?: 4|6} $attributes
     */
    private function restartJob(array $job, string $url, array $attributes = []): void
    {
        if (null !== $job['filename']) {
            @unlink($job['filename'].'~');
        }

        $attributes = array_merge($job['attributes'], $attributes);
        $origin = Url::getOrigin($this->config, $url);

        $this->initDownload($job['resolve'], $job['reject'], $origin, $url, $job['options'], $job['filename'], $attributes);
    }

    /**
     * @param  Job    $job
     * @param non-empty-string $url
     *
     * @param  array{retryAuthFailure?: bool, redirects?: int<0, max>, storeAuth?: 'prompt'|bool, retries: int<1, max>, ipResolve?: 4|6} $attributes
     */
    private function restartJobWithDelay(array $job, string $url, array $attributes): void
    {
        if ($attributes['retries'] >= 3) {
            usleep(500000); // half a second delay for 3rd retry and beyond
        } elseif ($attributes['retries'] >= 2) {
            usleep(100000); // 100ms delay for 2nd retry
        } // no sleep for the first retry

        $this->restartJob($job, $url, $attributes);
    }

    /**
     * @param  Job                $job
     */
    private function failResponse(array $job, Response $response, string $errorMessage): TransportException
    {
        if (null !== $job['filename']) {
            @unlink($job['filename'].'~');
        }

        $details = '';
        if (in_array(strtolower((string) $response->getHeader('content-type')), ['application/json', 'application/json; charset=utf-8'], true)) {
            $details = ':'.PHP_EOL.substr($response->getBody(), 0, 200).(strlen($response->getBody()) > 200 ? '...' : '');
        }

        return new TransportException('The "'.$job['url'].'" file could not be downloaded ('.$errorMessage.')' . $details, $response->getStatusCode());
    }

    /**
     * @param  Job                $job
     */
    private function rejectJob(array $job, \Exception $e): void
    {
        if (is_resource($job['headerHandle'])) {
            fclose($job['headerHandle']);
        }
        if (is_resource($job['bodyHandle'])) {
            fclose($job['bodyHandle']);
        }
        if (null !== $job['filename']) {
            @unlink($job['filename'].'~');
        }
        $job['reject']($e);
    }

    private function checkCurlResult(int $code): void
    {
        if ($code !== CURLM_OK && $code !== CURLM_CALL_MULTI_PERFORM) {
            throw new \RuntimeException(
                isset($this->multiErrors[$code])
                ? "cURL error: {$code} ({$this->multiErrors[$code][0]}): cURL message: {$this->multiErrors[$code][1]}"
                : 'Unexpected cURL error: ' . $code
            );
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util\Http;

use Composer\Downloader\TransportException;

/**
 * @internal
 * @author John Stevenson <john-stevenson@blueyonder.co.uk>
 *
 * @phpstan-type contextOptions array{http: array{proxy: string, header?: string, request_fulluri?: bool}}
 */
class RequestProxy
{
    /** @var ?contextOptions */
    private $contextOptions;
    /** @var ?non-empty-string */
    private $status;
    /** @var ?non-empty-string */
    private $url;
    /** @var ?non-empty-string */
    private $auth;

    /**
     * @param ?non-empty-string $url The proxy url, without authorization
     * @param ?non-empty-string $auth Authorization for curl
     * @param ?contextOptions $contextOptions
     * @param ?non-empty-string $status
     */
    public function __construct(?string $url, ?string $auth, ?array $contextOptions, ?string $status)
    {
        $this->url = $url;
        $this->auth = $auth;
        $this->contextOptions = $contextOptions;
        $this->status = $status;
    }

    public static function none(): RequestProxy
    {
        return new self(null, null, null, null);
    }

    public static function noProxy(): RequestProxy
    {
        return new self(null, null, null, 'excluded by no_proxy');
    }

    /**
     * Returns the context options to use for this request, otherwise null
     *
     * @return ?contextOptions
     */
    public function getContextOptions(): ?array
    {
        return $this->contextOptions;
    }

    /**
     * Returns an array of curl proxy options
     *
     * @param array<string, string|int> $sslOptions
     * @return array<int, string|int>
     */
    public function getCurlOptions(array $sslOptions): array
    {
        if ($this->isSecure() && !$this->supportsSecureProxy()) {
            throw new TransportException('Cannot use an HTTPS proxy. PHP >= 7.3 and cUrl >= 7.52.0 are required.');
        }

        // Always set a proxy url, even an empty value, because it tells curl
        // to ignore proxy environment variables
        $options = [CURLOPT_PROXY => (string) $this->url];

        // If using a proxy, tell curl to ignore no_proxy environment variables
        if ($this->url !== null) {
            $options[CURLOPT_NOPROXY] = '';
        }

        // Set any authorization
        if ($this->auth !== null) {
            $options[CURLOPT_PROXYAUTH] = CURLAUTH_BASIC;
            $options[CURLOPT_PROXYUSERPWD] = $this->auth;
        }

        if ($this->isSecure()) {
            if (isset($sslOptions['cafile'])) {
                $options[CURLOPT_PROXY_CAINFO] = $sslOptions['cafile'];
            }
            if (isset($sslOptions['capath'])) {
                $options[CURLOPT_PROXY_CAPATH] = $sslOptions['capath'];
            }
        }

        return $options;
    }

    /**
     * Returns proxy info associated with this request
     *
     * An empty return value means that the user has not set a proxy.
     * A non-empty value will either be the sanitized proxy url if a proxy is
     * required, or a message indicating that a no_proxy value has disabled the
     * proxy.
     *
     * @param ?string $format Output format specifier
     */
    public function getStatus(?string $format = null): string
    {
        if ($this->status === null) {
            return '';
        }

        $format = $format ?? '%s';
        if (strpos($format, '%s') !== false) {
            return sprintf($format, $this->status);
        }

        throw new \InvalidArgumentException('String format specifier is missing');
    }

    /**
     * Returns true if the request url has been excluded by a no_proxy value
     *
     * A false value can also mean that the user has not set a proxy.
     */
    public function isExcludedByNoProxy(): bool
    {
        return $this->status !== null && $this->url === null;
    }

    /**
     * Returns true if this is a secure (HTTPS) proxy
     *
     * A false value means that this is either an HTTP proxy, or that a proxy
     * is not required for this request, or that the user has not set a proxy.
     */
    public function isSecure(): bool
    {
        return 0 === strpos((string) $this->url, 'https://');
    }

    /**
     * Returns true if an HTTPS proxy can be used.
     *
     * This depends on PHP7.3+ for CURL_VERSION_HTTPS_PROXY
     * and curl including the feature (from version 7.52.0)
     */
    public function supportsSecureProxy(): bool
    {
        if (false === ($version = curl_version()) || !defined('CURL_VERSION_HTTPS_PROXY')) {
            return false;
        }

        $features = $version['features'];

        return (bool) ($features & CURL_VERSION_HTTPS_PROXY);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\CaBundle\CaBundle;
use Composer\Pcre\Preg;

/**
 * @author Chris Smith <chris@cs278.org>
 * @deprecated Use composer/ca-bundle and composer/composer 2.2 if you still need PHP 5 compatibility, this class will be removed in Composer 3.0
 */
final class TlsHelper
{
    /**
     * Match hostname against a certificate.
     *
     * @param mixed  $certificate X.509 certificate
     * @param string $hostname    Hostname in the URL
     * @param string $cn          Set to the common name of the certificate iff match found
     */
    public static function checkCertificateHost($certificate, string $hostname, ?string &$cn = null): bool
    {
        $names = self::getCertificateNames($certificate);

        if (empty($names)) {
            return false;
        }

        $combinedNames = array_merge($names['san'], [$names['cn']]);
        $hostname = strtolower($hostname);

        foreach ($combinedNames as $certName) {
            $matcher = self::certNameMatcher($certName);

            if ($matcher && $matcher($hostname)) {
                $cn = $names['cn'];

                return true;
            }
        }

        return false;
    }

    /**
     * Extract DNS names out of an X.509 certificate.
     *
     * @param mixed $certificate X.509 certificate
     *
     * @return array{cn: string, san: string[]}|null
     */
    public static function getCertificateNames($certificate): ?array
    {
        if (is_array($certificate)) {
            $info = $certificate;
        } elseif (CaBundle::isOpensslParseSafe()) {
            $info = openssl_x509_parse($certificate, false);
        }

        if (!isset($info['subject']['commonName'])) {
            return null;
        }

        $commonName = strtolower($info['subject']['commonName']);
        $subjectAltNames = [];

        if (isset($info['extensions']['subjectAltName'])) {
            $subjectAltNames = Preg::split('{\s*,\s*}', $info['extensions']['subjectAltName']);
            $subjectAltNames = array_filter(
                array_map(static function ($name): ?string {
                    if (0 === strpos($name, 'DNS:')) {
                        return strtolower(ltrim(substr($name, 4)));
                    }

                    return null;
                }, $subjectAltNames),
                function (?string $san) {
                    return $san !== null;
                }
            );
            $subjectAltNames = array_values($subjectAltNames);
        }

        return [
            'cn' => $commonName,
            'san' => $subjectAltNames,
        ];
    }

    /**
     * Get the certificate pin.
     *
     * By Kevin McArthur of StormTide Digital Studios Inc.
     * @KevinSMcArthur / https://github.com/StormTide
     *
     * See https://tools.ietf.org/html/draft-ietf-websec-key-pinning-02
     *
     * This method was adapted from Sslurp.
     * https://github.com/EvanDotPro/Sslurp
     *
     * (c) Evan Coury <me@evancoury.com>
     *
     * For the full copyright and license information, please see below:
     *
     * Copyright (c) 2013, Evan Coury
     * 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.
     */
    public static function getCertificateFingerprint(string $certificate): string
    {
        $pubkey = openssl_get_publickey($certificate);
        if ($pubkey === false) {
            throw new \RuntimeException('Failed to retrieve the public key from certificate');
        }
        $pubkeydetails = openssl_pkey_get_details($pubkey);
        $pubkeypem = $pubkeydetails['key'];
        //Convert PEM to DER before SHA1'ing
        $start = '-----BEGIN PUBLIC KEY-----';
        $end = '-----END PUBLIC KEY-----';
        $pemtrim = substr($pubkeypem, strpos($pubkeypem, $start) + strlen($start), (strlen($pubkeypem) - strpos($pubkeypem, $end)) * (-1));
        $der = base64_decode($pemtrim);

        return hash('sha1', $der);
    }

    /**
     * Test if it is safe to use the PHP function openssl_x509_parse().
     *
     * This checks if OpenSSL extensions is vulnerable to remote code execution
     * via the exploit documented as CVE-2013-6420.
     */
    public static function isOpensslParseSafe(): bool
    {
        return CaBundle::isOpensslParseSafe();
    }

    /**
     * Convert certificate name into matching function.
     *
     * @param string $certName CN/SAN
     */
    private static function certNameMatcher(string $certName): ?callable
    {
        $wildcards = substr_count($certName, '*');

        if (0 === $wildcards) {
            // Literal match.
            return static function ($hostname) use ($certName): bool {
                return $hostname === $certName;
            };
        }

        if (1 === $wildcards) {
            $components = explode('.', $certName);

            if (3 > count($components)) {
                // Must have 3+ components
                return null;
            }

            $firstComponent = $components[0];

            // Wildcard must be the last character.
            if ('*' !== $firstComponent[strlen($firstComponent) - 1]) {
                return null;
            }

            $wildcardRegex = preg_quote($certName);
            $wildcardRegex = str_replace('\\*', '[a-z0-9-]+', $wildcardRegex);
            $wildcardRegex = "{^{$wildcardRegex}$}";

            return static function ($hostname) use ($wildcardRegex): bool {
                return Preg::isMatch($wildcardRegex, $hostname);
            };
        }

        return null;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Script;

use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\EventDispatcher\Event as BaseEvent;

/**
 * The script event class
 *
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 * @author Nils Adermann <naderman@naderman.de>
 */
class Event extends BaseEvent
{
    /**
     * @var Composer The composer instance
     */
    private $composer;

    /**
     * @var IOInterface The IO instance
     */
    private $io;

    /**
     * @var bool Dev mode flag
     */
    private $devMode;

    /**
     * @var BaseEvent|null
     */
    private $originatingEvent;

    /**
     * Constructor.
     *
     * @param string $name The event name
     * @param Composer $composer The composer object
     * @param IOInterface $io The IOInterface object
     * @param bool $devMode Whether or not we are in dev mode
     * @param array<string|int|float|bool|null> $args Arguments passed by the user
     * @param mixed[] $flags Optional flags to pass data not as argument
     */
    public function __construct(string $name, Composer $composer, IOInterface $io, bool $devMode = false, array $args = [], array $flags = [])
    {
        parent::__construct($name, $args, $flags);
        $this->composer = $composer;
        $this->io = $io;
        $this->devMode = $devMode;
    }

    /**
     * Returns the composer instance.
     */
    public function getComposer(): Composer
    {
        return $this->composer;
    }

    /**
     * Returns the IO instance.
     */
    public function getIO(): IOInterface
    {
        return $this->io;
    }

    /**
     * Return the dev mode flag
     */
    public function isDevMode(): bool
    {
        return $this->devMode;
    }

    /**
     * Set the originating event.
     *
     * @return ?BaseEvent
     */
    public function getOriginatingEvent(): ?BaseEvent
    {
        return $this->originatingEvent;
    }

    /**
     * Set the originating event.
     *
     * @return $this
     */
    public function setOriginatingEvent(BaseEvent $event): self
    {
        $this->originatingEvent = $this->calculateOriginatingEvent($event);

        return $this;
    }

    /**
     * Returns the upper-most event in chain.
     */
    private function calculateOriginatingEvent(BaseEvent $event): BaseEvent
    {
        if ($event instanceof Event && $event->getOriginatingEvent()) {
            return $this->calculateOriginatingEvent($event->getOriginatingEvent());
        }

        return $event;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Script;

/**
 * The Script Events.
 *
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ScriptEvents
{
    /**
     * The PRE_INSTALL_CMD event occurs before the install command is executed.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    public const PRE_INSTALL_CMD = 'pre-install-cmd';

    /**
     * The POST_INSTALL_CMD event occurs after the install command is executed.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    public const POST_INSTALL_CMD = 'post-install-cmd';

    /**
     * The PRE_UPDATE_CMD event occurs before the update command is executed.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    public const PRE_UPDATE_CMD = 'pre-update-cmd';

    /**
     * The POST_UPDATE_CMD event occurs after the update command is executed.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    public const POST_UPDATE_CMD = 'post-update-cmd';

    /**
     * The PRE_STATUS_CMD event occurs before the status command is executed.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    public const PRE_STATUS_CMD = 'pre-status-cmd';

    /**
     * The POST_STATUS_CMD event occurs after the status command is executed.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    public const POST_STATUS_CMD = 'post-status-cmd';

    /**
     * The PRE_AUTOLOAD_DUMP event occurs before the autoload file is generated.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    public const PRE_AUTOLOAD_DUMP = 'pre-autoload-dump';

    /**
     * The POST_AUTOLOAD_DUMP event occurs after the autoload file has been generated.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    public const POST_AUTOLOAD_DUMP = 'post-autoload-dump';

    /**
     * The POST_ROOT_PACKAGE_INSTALL event occurs after the root package has been installed.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    public const POST_ROOT_PACKAGE_INSTALL = 'post-root-package-install';

    /**
     * The POST_CREATE_PROJECT event occurs after the create-project command has been executed.
     * Note: Event occurs after POST_INSTALL_CMD
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    public const POST_CREATE_PROJECT_CMD = 'post-create-project-cmd';

    /**
     * The PRE_ARCHIVE_CMD event occurs before the update command is executed.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    public const PRE_ARCHIVE_CMD = 'pre-archive-cmd';

    /**
     * The POST_ARCHIVE_CMD event occurs after the status command is executed.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    public const POST_ARCHIVE_CMD = 'post-archive-cmd';
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Composer;
use Composer\Factory;
use Composer\Config;
use Composer\Pcre\Preg;
use Composer\Util\Filesystem;
use Composer\Util\Platform;
use Composer\SelfUpdate\Keys;
use Composer\SelfUpdate\Versions;
use Composer\IO\IOInterface;
use Composer\Downloader\FilesystemException;
use Composer\Downloader\TransportException;
use Phar;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Composer\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Finder\Finder;

/**
 * @author Igor Wiedler <igor@wiedler.ch>
 * @author Kevin Ran <kran@adobe.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class SelfUpdateCommand extends BaseCommand
{
    private const HOMEPAGE = 'getcomposer.org';
    private const OLD_INSTALL_EXT = '-old.phar';

    protected function configure(): void
    {
        $this
            ->setName('self-update')
            ->setAliases(['selfupdate'])
            ->setDescription('Updates composer.phar to the latest version')
            ->setDefinition([
                new InputOption('rollback', 'r', InputOption::VALUE_NONE, 'Revert to an older installation of composer'),
                new InputOption('clean-backups', null, InputOption::VALUE_NONE, 'Delete old backups during an update. This makes the current version of composer the only backup available after the update'),
                new InputArgument('version', InputArgument::OPTIONAL, 'The version to update to'),
                new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                new InputOption('update-keys', null, InputOption::VALUE_NONE, 'Prompt user for a key update'),
                new InputOption('stable', null, InputOption::VALUE_NONE, 'Force an update to the stable channel'),
                new InputOption('preview', null, InputOption::VALUE_NONE, 'Force an update to the preview channel'),
                new InputOption('snapshot', null, InputOption::VALUE_NONE, 'Force an update to the snapshot channel'),
                new InputOption('1', null, InputOption::VALUE_NONE, 'Force an update to the stable channel, but only use 1.x versions'),
                new InputOption('2', null, InputOption::VALUE_NONE, 'Force an update to the stable channel, but only use 2.x versions'),
                new InputOption('2.2', null, InputOption::VALUE_NONE, 'Force an update to the stable channel, but only use 2.2.x LTS versions'),
                new InputOption('set-channel-only', null, InputOption::VALUE_NONE, 'Only store the channel as the default one and then exit'),
            ])
            ->setHelp(
                <<<EOT
The <info>self-update</info> command checks getcomposer.org for newer
versions of composer and if found, installs the latest.

<info>php composer.phar self-update</info>

Read more at https://getcomposer.org/doc/03-cli.md#self-update-selfupdate
EOT
            )
        ;
    }

    /**
     * @throws FilesystemException
     */
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        if ($_SERVER['argv'][0] === 'Standard input code') {
            return 1;
        }

        // trigger autoloading of a few classes which may be needed when verifying/swapping the phar file
        // to ensure we do not try to load them from the new phar, see https://github.com/composer/composer/issues/10252
        class_exists('Composer\Util\Platform');
        class_exists('Composer\Downloader\FilesystemException');

        $config = Factory::createConfig();

        if ($config->get('disable-tls') === true) {
            $baseUrl = 'http://' . self::HOMEPAGE;
        } else {
            $baseUrl = 'https://' . self::HOMEPAGE;
        }

        $io = $this->getIO();
        $httpDownloader = Factory::createHttpDownloader($io, $config);

        $versionsUtil = new Versions($config, $httpDownloader);

        // switch channel if requested
        $requestedChannel = null;
        foreach (Versions::CHANNELS as $channel) {
            if ($input->getOption($channel)) {
                $requestedChannel = $channel;
                $versionsUtil->setChannel($channel, $io);
                break;
            }
        }

        if ($input->getOption('set-channel-only')) {
            return 0;
        }

        $cacheDir = $config->get('cache-dir');
        $rollbackDir = $config->get('data-dir');
        $home = $config->get('home');
        $localFilename = Phar::running(false);
        if ('' === $localFilename) {
            throw new \RuntimeException('Could not determine the location of the composer.phar file as it appears you are not running this code from a phar archive.');
        }

        if ($input->getOption('update-keys')) {
            $this->fetchKeys($io, $config);

            return 0;
        }

        // ensure composer.phar location is accessible
        if (!file_exists($localFilename)) {
            throw new FilesystemException('Composer update failed: the "'.$localFilename.'" is not accessible');
        }

        // check if current dir is writable and if not try the cache dir from settings
        $tmpDir = is_writable(dirname($localFilename)) ? dirname($localFilename) : $cacheDir;

        // check for permissions in local filesystem before start connection process
        if (!is_writable($tmpDir)) {
            throw new FilesystemException('Composer update failed: the "'.$tmpDir.'" directory used to download the temp file could not be written');
        }

        // check if composer is running as the same user that owns the directory root, only if POSIX is defined and callable
        if (function_exists('posix_getpwuid') && function_exists('posix_geteuid')) {
            $composerUser = posix_getpwuid(posix_geteuid());
            $homeDirOwnerId = fileowner($home);
            if (is_array($composerUser) && $homeDirOwnerId !== false) {
                $homeOwner = posix_getpwuid($homeDirOwnerId);
                if (is_array($homeOwner) && $composerUser['name'] !== $homeOwner['name']) {
                    $io->writeError('<warning>You are running Composer as "'.$composerUser['name'].'", while "'.$home.'" is owned by "'.$homeOwner['name'].'"</warning>');
                }
            }
        }

        if ($input->getOption('rollback')) {
            return $this->rollback($output, $rollbackDir, $localFilename);
        }

        if ($input->getArgument('command') === 'self' && $input->getArgument('version') === 'update') {
            $input->setArgument('version', null);
        }

        $latest = $versionsUtil->getLatest();
        $latestStable = $versionsUtil->getLatest('stable');
        try {
            $latestPreview = $versionsUtil->getLatest('preview');
        } catch (\UnexpectedValueException $e) {
            $latestPreview = $latestStable;
        }
        $latestVersion = $latest['version'];
        $updateVersion = $input->getArgument('version') ?? $latestVersion;
        $currentMajorVersion = Preg::replace('{^(\d+).*}', '$1', Composer::getVersion());
        $updateMajorVersion = Preg::replace('{^(\d+).*}', '$1', $updateVersion);
        $previewMajorVersion = Preg::replace('{^(\d+).*}', '$1', $latestPreview['version']);

        if ($versionsUtil->getChannel() === 'stable' && null === $input->getArgument('version')) {
            // if requesting stable channel and no specific version, avoid automatically upgrading to the next major
            // simply output a warning that the next major stable is available and let users upgrade to it manually
            if ($currentMajorVersion < $updateMajorVersion) {
                $skippedVersion = $updateVersion;

                $versionsUtil->setChannel($currentMajorVersion);

                $latest = $versionsUtil->getLatest();
                $latestStable = $versionsUtil->getLatest('stable');
                $latestVersion = $latest['version'];
                $updateVersion = $latestVersion;

                $io->writeError('<warning>A new stable major version of Composer is available ('.$skippedVersion.'), run "composer self-update --'.$updateMajorVersion.'" to update to it. See also https://getcomposer.org/'.$updateMajorVersion.'</warning>');
            } elseif ($currentMajorVersion < $previewMajorVersion) {
                // promote next major version if available in preview
                $io->writeError('<warning>A preview release of the next major version of Composer is available ('.$latestPreview['version'].'), run "composer self-update --preview" to give it a try. See also https://github.com/composer/composer/releases for changelogs.</warning>');
            }
        }

        $effectiveChannel = $requestedChannel === null ? $versionsUtil->getChannel() : $requestedChannel;
        if (is_numeric($effectiveChannel) && strpos($latestStable['version'], $effectiveChannel) !== 0) {
            $io->writeError('<warning>Warning: You forced the install of '.$latestVersion.' via --'.$effectiveChannel.', but '.$latestStable['version'].' is the latest stable version. Updating to it via composer self-update --stable is recommended.</warning>');
        }
        if (isset($latest['eol'])) {
            $io->writeError('<warning>Warning: Version '.$latestVersion.' is EOL / End of Life. '.$latestStable['version'].' is the latest stable version. Updating to it via composer self-update --stable is recommended.</warning>');
        }

        if (Preg::isMatch('{^[0-9a-f]{40}$}', $updateVersion) && $updateVersion !== $latestVersion) {
            $io->writeError('<error>You can not update to a specific SHA-1 as those phars are not available for download</error>');

            return 1;
        }

        $channelString = $versionsUtil->getChannel();
        if (is_numeric($channelString)) {
            $channelString .= '.x';
        }

        if (Composer::VERSION === $updateVersion) {
            $io->writeError(
                sprintf(
                    '<info>You are already using the latest available Composer version %s (%s channel).</info>',
                    $updateVersion,
                    $channelString
                )
            );

            // remove all backups except for the most recent, if any
            if ($input->getOption('clean-backups')) {
                $this->cleanBackups($rollbackDir, $this->getLastBackupVersion($rollbackDir));
            }

            return 0;
        }

        $tempFilename = $tmpDir . '/' . basename($localFilename, '.phar').'-temp'.random_int(0, 10000000).'.phar';
        $backupFile = sprintf(
            '%s/%s-%s%s',
            $rollbackDir,
            strtr(Composer::RELEASE_DATE, ' :', '_-'),
            Preg::replace('{^([0-9a-f]{7})[0-9a-f]{33}$}', '$1', Composer::VERSION),
            self::OLD_INSTALL_EXT
        );

        $updatingToTag = !Preg::isMatch('{^[0-9a-f]{40}$}', $updateVersion);

        $io->write(sprintf("Upgrading to version <info>%s</info> (%s channel).", $updateVersion, $channelString));
        $remoteFilename = $baseUrl . ($updatingToTag ? "/download/{$updateVersion}/composer.phar" : '/composer.phar');
        try {
            $signature = $httpDownloader->get($remoteFilename.'.sig')->getBody();
        } catch (TransportException $e) {
            if ($e->getStatusCode() === 404) {
                throw new \InvalidArgumentException('Version "'.$updateVersion.'" could not be found.', 0, $e);
            }
            throw $e;
        }
        $io->writeError('   ', false);
        $httpDownloader->copy($remoteFilename, $tempFilename);
        $io->writeError('');

        if (!file_exists($tempFilename) || null === $signature || '' === $signature) {
            $io->writeError('<error>The download of the new composer version failed for an unexpected reason</error>');

            return 1;
        }

        // verify phar signature
        if (!extension_loaded('openssl') && $config->get('disable-tls')) {
            $io->writeError('<warning>Skipping phar signature verification as you have disabled OpenSSL via config.disable-tls</warning>');
        } else {
            if (!extension_loaded('openssl')) {
                throw new \RuntimeException('The openssl extension is required for phar signatures to be verified but it is not available. '
                . 'If you can not enable the openssl extension, you can disable this error, at your own risk, by setting the \'disable-tls\' option to true.');
            }

            $sigFile = 'file://'.$home.'/' . ($updatingToTag ? 'keys.tags.pub' : 'keys.dev.pub');
            if (!file_exists($sigFile)) {
                file_put_contents(
                    $home.'/keys.dev.pub',
                    <<<DEVPUBKEY
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnBDHjZS6e0ZMoK3xTD7f
FNCzlXjX/Aie2dit8QXA03pSrOTbaMnxON3hUL47Lz3g1SC6YJEMVHr0zYq4elWi
i3ecFEgzLcj+pZM5X6qWu2Ozz4vWx3JYo1/a/HYdOuW9e3lwS8VtS0AVJA+U8X0A
hZnBmGpltHhO8hPKHgkJtkTUxCheTcbqn4wGHl8Z2SediDcPTLwqezWKUfrYzu1f
o/j3WFwFs6GtK4wdYtiXr+yspBZHO3y1udf8eFFGcb2V3EaLOrtfur6XQVizjOuk
8lw5zzse1Qp/klHqbDRsjSzJ6iL6F4aynBc6Euqt/8ccNAIz0rLjLhOraeyj4eNn
8iokwMKiXpcrQLTKH+RH1JCuOVxQ436bJwbSsp1VwiqftPQieN+tzqy+EiHJJmGf
TBAbWcncicCk9q2md+AmhNbvHO4PWbbz9TzC7HJb460jyWeuMEvw3gNIpEo2jYa9
pMV6cVqnSa+wOc0D7pC9a6bne0bvLcm3S+w6I5iDB3lZsb3A9UtRiSP7aGSo7D72
8tC8+cIgZcI7k9vjvOqH+d7sdOU2yPCnRY6wFh62/g8bDnUpr56nZN1G89GwM4d4
r/TU7BQQIzsZgAiqOGXvVklIgAMiV0iucgf3rNBLjjeNEwNSTTG9F0CtQ+7JLwaE
wSEuAuRm+pRqi8BRnQ/GKUcCAwEAAQ==
-----END PUBLIC KEY-----
DEVPUBKEY
                );

                file_put_contents(
                    $home.'/keys.tags.pub',
                    <<<TAGSPUBKEY
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0Vi/2K6apCVj76nCnCl2
MQUPdK+A9eqkYBacXo2wQBYmyVlXm2/n/ZsX6pCLYPQTHyr5jXbkQzBw8SKqPdlh
vA7NpbMeNCz7wP/AobvUXM8xQuXKbMDTY2uZ4O7sM+PfGbptKPBGLe8Z8d2sUnTO
bXtX6Lrj13wkRto7st/w/Yp33RHe9SlqkiiS4MsH1jBkcIkEHsRaveZzedUaxY0M
mba0uPhGUInpPzEHwrYqBBEtWvP97t2vtfx8I5qv28kh0Y6t+jnjL1Urid2iuQZf
noCMFIOu4vksK5HxJxxrN0GOmGmwVQjOOtxkwikNiotZGPR4KsVj8NnBrLX7oGuM
nQvGciiu+KoC2r3HDBrpDeBVdOWxDzT5R4iI0KoLzFh2pKqwbY+obNPS2bj+2dgJ
rV3V5Jjry42QOCBN3c88wU1PKftOLj2ECpewY6vnE478IipiEu7EAdK8Zwj2LmTr
RKQUSa9k7ggBkYZWAeO/2Ag0ey3g2bg7eqk+sHEq5ynIXd5lhv6tC5PBdHlWipDK
tl2IxiEnejnOmAzGVivE1YGduYBjN+mjxDVy8KGBrjnz1JPgAvgdwJ2dYw4Rsc/e
TzCFWGk/HM6a4f0IzBWbJ5ot0PIi4amk07IotBXDWwqDiQTwyuGCym5EqWQ2BD95
RGv89BPD+2DLnJysngsvVaUCAwEAAQ==
-----END PUBLIC KEY-----
TAGSPUBKEY
                );
            }

            $pubkeyid = openssl_pkey_get_public($sigFile);
            if (false === $pubkeyid) {
                throw new \RuntimeException('Failed loading the public key from '.$sigFile);
            }
            $algo = defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'SHA384';
            if (!in_array('sha384', array_map('strtolower', openssl_get_md_methods()), true)) {
                throw new \RuntimeException('SHA384 is not supported by your openssl extension, could not verify the phar file integrity');
            }
            $signatureData = json_decode($signature, true);
            $signatureSha384 = base64_decode($signatureData['sha384'], true);
            if (false === $signatureSha384) {
                throw new \RuntimeException('Failed loading the phar signature from '.$remoteFilename.'.sig, got '.$signature);
            }
            $verified = 1 === openssl_verify((string) file_get_contents($tempFilename), $signatureSha384, $pubkeyid, $algo);

            // PHP 8 automatically frees the key instance and deprecates the function
            if (\PHP_VERSION_ID < 80000) {
                // @phpstan-ignore function.deprecated
                openssl_free_key($pubkeyid);
            }

            if (!$verified) {
                throw new \RuntimeException('The phar signature did not match the file you downloaded, this means your public keys are outdated or that the phar file is corrupt/has been modified');
            }
        }

        // remove saved installations of composer
        if ($input->getOption('clean-backups')) {
            $this->cleanBackups($rollbackDir);
        }

        if (!$this->setLocalPhar($localFilename, $tempFilename, $backupFile)) {
            @unlink($tempFilename);

            return 1;
        }

        if (file_exists($backupFile)) {
            $io->writeError(sprintf(
                'Use <info>composer self-update --rollback</info> to return to version <comment>%s</comment>',
                Composer::VERSION
            ));
        } else {
            $io->writeError('<warning>A backup of the current version could not be written to '.$backupFile.', no rollback possible</warning>');
        }

        return 0;
    }

    /**
     * @throws \Exception
     */
    protected function fetchKeys(IOInterface $io, Config $config): void
    {
        if (!$io->isInteractive()) {
            throw new \RuntimeException('Public keys can not be fetched in non-interactive mode, please run Composer interactively');
        }

        $io->write('Open <info>https://composer.github.io/pubkeys.html</info> to find the latest keys');

        $validator = static function ($value): string {
            $value = (string) $value;
            if (!Preg::isMatch('{^-----BEGIN PUBLIC KEY-----$}', trim($value))) {
                throw new \UnexpectedValueException('Invalid input');
            }

            return trim($value)."\n";
        };

        $devKey = '';
        while (!Preg::isMatch('{(-----BEGIN PUBLIC KEY-----.+?-----END PUBLIC KEY-----)}s', $devKey, $match)) {
            $devKey = $io->askAndValidate('Enter Dev / Snapshot Public Key (including lines with -----): ', $validator);
            while ($line = $io->ask('', '')) {
                $devKey .= trim($line)."\n";
                if (trim($line) === '-----END PUBLIC KEY-----') {
                    break;
                }
            }
        }
        file_put_contents($keyPath = $config->get('home').'/keys.dev.pub', $match[0]);
        $io->write('Stored key with fingerprint: ' . Keys::fingerprint($keyPath));

        $tagsKey = '';
        while (!Preg::isMatch('{(-----BEGIN PUBLIC KEY-----.+?-----END PUBLIC KEY-----)}s', $tagsKey, $match)) {
            $tagsKey = $io->askAndValidate('Enter Tags Public Key (including lines with -----): ', $validator);
            while ($line = $io->ask('', '')) {
                $tagsKey .= trim($line)."\n";
                if (trim($line) === '-----END PUBLIC KEY-----') {
                    break;
                }
            }
        }
        file_put_contents($keyPath = $config->get('home').'/keys.tags.pub', $match[0]);
        $io->write('Stored key with fingerprint: ' . Keys::fingerprint($keyPath));

        $io->write('Public keys stored in '.$config->get('home'));
    }

    /**
     * @throws FilesystemException
     */
    protected function rollback(OutputInterface $output, string $rollbackDir, string $localFilename): int
    {
        $rollbackVersion = $this->getLastBackupVersion($rollbackDir);
        if (null === $rollbackVersion) {
            throw new \UnexpectedValueException('Composer rollback failed: no installation to roll back to in "'.$rollbackDir.'"');
        }

        $oldFile = $rollbackDir . '/' . $rollbackVersion . self::OLD_INSTALL_EXT;

        if (!is_file($oldFile)) {
            throw new FilesystemException('Composer rollback failed: "'.$oldFile.'" could not be found');
        }
        if (!Filesystem::isReadable($oldFile)) {
            throw new FilesystemException('Composer rollback failed: "'.$oldFile.'" could not be read');
        }

        $io = $this->getIO();
        $io->writeError(sprintf("Rolling back to version <info>%s</info>.", $rollbackVersion));
        if (!$this->setLocalPhar($localFilename, $oldFile)) {
            return 1;
        }

        return 0;
    }

    /**
     * Checks if the downloaded/rollback phar is valid then moves it
     *
     * @param  string              $localFilename The composer.phar location
     * @param  string              $newFilename   The downloaded or backup phar
     * @param  string              $backupTarget  The filename to use for the backup
     * @throws FilesystemException If the file cannot be moved
     * @return bool                Whether the phar is valid and has been moved
     */
    protected function setLocalPhar(string $localFilename, string $newFilename, ?string $backupTarget = null): bool
    {
        $io = $this->getIO();
        $perms = @fileperms($localFilename);
        if ($perms !== false) {
            @chmod($newFilename, $perms);
        }

        // check phar validity
        if (!$this->validatePhar($newFilename, $error)) {
            $io->writeError('<error>The '.($backupTarget !== null ? 'update' : 'backup').' file is corrupted ('.$error.')</error>');

            if ($backupTarget !== null) {
                $io->writeError('<error>Please re-run the self-update command to try again.</error>');
            }

            return false;
        }

        // copy current file into backups dir
        if ($backupTarget !== null) {
            @copy($localFilename, $backupTarget);
        }

        try {
            if (Platform::isWindows()) {
                // use copy to apply permissions from the destination directory
                // as rename uses source permissions and may block other users
                copy($newFilename, $localFilename);
                @unlink($newFilename);
            } else {
                rename($newFilename, $localFilename);
            }

            return true;
        } catch (\Exception $e) {
            // see if we can run this operation as an Admin on Windows
            if (!is_writable(dirname($localFilename))
                && $io->isInteractive()
                && $this->isWindowsNonAdminUser()) {
                return $this->tryAsWindowsAdmin($localFilename, $newFilename);
            }

            @unlink($newFilename);
            $action = 'Composer '.($backupTarget !== null ? 'update' : 'rollback');
            throw new FilesystemException($action.' failed: "'.$localFilename.'" could not be written.'.PHP_EOL.$e->getMessage());
        }
    }

    protected function cleanBackups(string $rollbackDir, ?string $except = null): void
    {
        $finder = $this->getOldInstallationFinder($rollbackDir);
        $io = $this->getIO();
        $fs = new Filesystem;

        foreach ($finder as $file) {
            if ($file->getBasename(self::OLD_INSTALL_EXT) === $except) {
                continue;
            }
            $file = (string) $file;
            $io->writeError('<info>Removing: '.$file.'</info>');
            $fs->remove($file);
        }
    }

    protected function getLastBackupVersion(string $rollbackDir): ?string
    {
        $finder = $this->getOldInstallationFinder($rollbackDir);
        $finder->sortByName();
        $files = iterator_to_array($finder);

        if (count($files) > 0) {
            return end($files)->getBasename(self::OLD_INSTALL_EXT);
        }

        return null;
    }

    protected function getOldInstallationFinder(string $rollbackDir): Finder
    {
        return Finder::create()
            ->depth(0)
            ->files()
            ->name('*' . self::OLD_INSTALL_EXT)
            ->in($rollbackDir);
    }

    /**
     * Validates the downloaded/backup phar file
     *
     * @param string      $pharFile The downloaded or backup phar
     * @param null|string $error    Set by method on failure
     *
     * Code taken from getcomposer.org/installer. Any changes should be made
     * there and replicated here
     *
     * @throws \Exception
     * @return bool       If the operation succeeded
     */
    protected function validatePhar(string $pharFile, ?string &$error): bool
    {
        if ((bool) ini_get('phar.readonly')) {
            return true;
        }

        try {
            // Test the phar validity
            $phar = new \Phar($pharFile);
            // Free the variable to unlock the file
            unset($phar);
            $result = true;
        } catch (\Exception $e) {
            if (!$e instanceof \UnexpectedValueException && !$e instanceof \PharException) {
                throw $e;
            }
            $error = $e->getMessage();
            $result = false;
        }

        return $result;
    }

    /**
     * Returns true if this is a non-admin Windows user account
     */
    protected function isWindowsNonAdminUser(): bool
    {
        if (!Platform::isWindows()) {
            return false;
        }

        // fltmc.exe manages filter drivers and errors without admin privileges
        exec('fltmc.exe filters', $output, $exitCode);

        return $exitCode !== 0;
    }

    /**
     * Invokes a UAC prompt to update composer.phar as an admin
     *
     * Uses a .vbs script to elevate and run the cmd.exe copy command.
     *
     * @param  string $localFilename The composer.phar location
     * @param  string $newFilename   The downloaded or backup phar
     * @return bool   Whether composer.phar has been updated
     */
    protected function tryAsWindowsAdmin(string $localFilename, string $newFilename): bool
    {
        $io = $this->getIO();

        $io->writeError('<error>Unable to write "'.$localFilename.'". Access is denied.</error>');
        $helpMessage = 'Please run the self-update command as an Administrator.';
        $question = 'Complete this operation with Administrator privileges [<comment>Y,n</comment>]? ';

        if (!$io->askConfirmation($question, true)) {
            $io->writeError('<warning>Operation cancelled. '.$helpMessage.'</warning>');

            return false;
        }

        $tmpFile = tempnam(sys_get_temp_dir(), '');
        if (false === $tmpFile) {
            $io->writeError('<error>Operation failed.'.$helpMessage.'</error>');

            return false;
        }
        $script = $tmpFile.'.vbs';
        rename($tmpFile, $script);

        $checksum = hash_file('sha256', $newFilename);

        // cmd's internal copy is fussy about backslashes
        $source = str_replace('/', '\\', $newFilename);
        $destination = str_replace('/', '\\', $localFilename);

        $vbs = <<<EOT
Set UAC = CreateObject("Shell.Application")
UAC.ShellExecute "cmd.exe", "/c copy /b /y ""$source"" ""$destination""", "", "runas", 0
Wscript.Sleep(300)
EOT;

        file_put_contents($script, $vbs);
        exec('"'.$script.'"');
        @unlink($script);

        // see if the file was copied and is still accessible
        if ($result = Filesystem::isReadable($localFilename) && (hash_file('sha256', $localFilename) === $checksum)) {
            $io->writeError('<info>Operation succeeded.</info>');
            @unlink($newFilename);
        } else {
            $io->writeError('<error>Operation failed.'.$helpMessage.'</error>');
        }

        return $result;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Config\JsonConfigSource;
use Composer\DependencyResolver\Request;
use Composer\Installer;
use Composer\Pcre\Preg;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Json\JsonFile;
use Composer\Factory;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Composer\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Package\BasePackage;
use Composer\Advisory\Auditor;

/**
 * @author Pierre du Plessis <pdples@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class RemoveCommand extends BaseCommand
{
    use CompletionTrait;

    /**
     * @return void
     */
    protected function configure()
    {
        $this
            ->setName('remove')
            ->setAliases(['rm', 'uninstall'])
            ->setDescription('Removes a package from the require or require-dev')
            ->setDefinition([
                new InputArgument('packages', InputArgument::IS_ARRAY, 'Packages that should be removed.', null, $this->suggestRootRequirement()),
                new InputOption('dev', null, InputOption::VALUE_NONE, 'Removes a package from the require-dev section.'),
                new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'),
                new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies (implies --no-install).'),
                new InputOption('no-install', null, InputOption::VALUE_NONE, 'Skip the install step after updating the composer.lock file.'),
                new InputOption('no-audit', null, InputOption::VALUE_NONE, 'Skip the audit step after updating the composer.lock file (can also be set via the COMPOSER_NO_AUDIT=1 env var).'),
                new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS),
                new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'),
                new InputOption('update-with-dependencies', 'w', InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated with explicit dependencies (can also be set via the COMPOSER_WITH_DEPENDENCIES=1 env var). (Deprecated, is now default behavior)'),
                new InputOption('update-with-all-dependencies', 'W', InputOption::VALUE_NONE, 'Allows all inherited dependencies to be updated, including those that are root requirements (can also be set via the COMPOSER_WITH_ALL_DEPENDENCIES=1 env var).'),
                new InputOption('with-all-dependencies', null, InputOption::VALUE_NONE, 'Alias for --update-with-all-dependencies'),
                new InputOption('no-update-with-dependencies', null, InputOption::VALUE_NONE, 'Does not allow inherited dependencies to be updated with explicit dependencies.'),
                new InputOption('minimal-changes', 'm', InputOption::VALUE_NONE, 'During an update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).'),
                new InputOption('unused', null, InputOption::VALUE_NONE, 'Remove all packages which are locked but not required by any other package.'),
                new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'),
                new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'),
                new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'),
                new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'),
                new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'),
                new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'),
            ])
            ->setHelp(
                <<<EOT
The <info>remove</info> command removes a package from the current
list of installed packages

<info>php composer.phar remove</info>

Read more at https://getcomposer.org/doc/03-cli.md#remove-rm
EOT
            )
        ;
    }

    /**
     * @throws \Seld\JsonLint\ParsingException
     */
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        if ($input->getArgument('packages') === [] && !$input->getOption('unused')) {
            throw new InvalidArgumentException('Not enough arguments (missing: "packages").');
        }

        $packages = $input->getArgument('packages');
        $packages = array_map('strtolower', $packages);

        if ($input->getOption('unused')) {
            $composer = $this->requireComposer();
            $locker = $composer->getLocker();
            if (!$locker->isLocked()) {
                throw new \UnexpectedValueException('A valid composer.lock file is required to run this command with --unused');
            }

            $lockedPackages = $locker->getLockedRepository()->getPackages();

            $required = [];
            foreach (array_merge($composer->getPackage()->getRequires(), $composer->getPackage()->getDevRequires()) as $link) {
                $required[$link->getTarget()] = true;
            }

            do {
                $found = false;
                foreach ($lockedPackages as $index => $package) {
                    foreach ($package->getNames() as $name) {
                        if (isset($required[$name])) {
                            foreach ($package->getRequires() as $link) {
                                $required[$link->getTarget()] = true;
                            }
                            $found = true;
                            unset($lockedPackages[$index]);
                            break;
                        }
                    }
                }
            } while ($found);

            $unused = [];
            foreach ($lockedPackages as $package) {
                $unused[] = $package->getName();
            }
            $packages = array_merge($packages, $unused);

            if (count($packages) === 0) {
                $this->getIO()->writeError('<info>No unused packages to remove</info>');

                return 0;
            }
        }

        $file = Factory::getComposerFile();

        $jsonFile = new JsonFile($file);
        /** @var array{require?: array<string, string>, require-dev?: array<string, string>} $composer */
        $composer = $jsonFile->read();
        $composerBackup = file_get_contents($jsonFile->getPath());

        $json = new JsonConfigSource($jsonFile);

        $type = $input->getOption('dev') ? 'require-dev' : 'require';
        $altType = !$input->getOption('dev') ? 'require-dev' : 'require';
        $io = $this->getIO();

        if ($input->getOption('update-with-dependencies')) {
            $io->writeError('<warning>You are using the deprecated option "update-with-dependencies". This is now default behaviour. The --no-update-with-dependencies option can be used to remove a package without its dependencies.</warning>');
        }

        // make sure name checks are done case insensitively
        foreach (['require', 'require-dev'] as $linkType) {
            if (isset($composer[$linkType])) {
                foreach ($composer[$linkType] as $name => $version) {
                    $composer[$linkType][strtolower($name)] = $name;
                }
            }
        }

        $dryRun = $input->getOption('dry-run');
        $toRemove = [];
        foreach ($packages as $package) {
            if (isset($composer[$type][$package])) {
                if ($dryRun) {
                    $toRemove[$type][] = $composer[$type][$package];
                } else {
                    $json->removeLink($type, $composer[$type][$package]);
                }
            } elseif (isset($composer[$altType][$package])) {
                $io->writeError('<warning>' . $composer[$altType][$package] . ' could not be found in ' . $type . ' but it is present in ' . $altType . '</warning>');
                if ($io->isInteractive()) {
                    if ($io->askConfirmation('Do you want to remove it from ' . $altType . ' [<comment>yes</comment>]? ')) {
                        if ($dryRun) {
                            $toRemove[$altType][] = $composer[$altType][$package];
                        } else {
                            $json->removeLink($altType, $composer[$altType][$package]);
                        }
                    }
                }
            } elseif (isset($composer[$type]) && count($matches = Preg::grep(BasePackage::packageNameToRegexp($package), array_keys($composer[$type]))) > 0) {
                foreach ($matches as $matchedPackage) {
                    if ($dryRun) {
                        $toRemove[$type][] = $matchedPackage;
                    } else {
                        $json->removeLink($type, $matchedPackage);
                    }
                }
            } elseif (isset($composer[$altType]) && count($matches = Preg::grep(BasePackage::packageNameToRegexp($package), array_keys($composer[$altType]))) > 0) {
                foreach ($matches as $matchedPackage) {
                    $io->writeError('<warning>' . $matchedPackage . ' could not be found in ' . $type . ' but it is present in ' . $altType . '</warning>');
                    if ($io->isInteractive()) {
                        if ($io->askConfirmation('Do you want to remove it from ' . $altType . ' [<comment>yes</comment>]? ')) {
                            if ($dryRun) {
                                $toRemove[$altType][] = $matchedPackage;
                            } else {
                                $json->removeLink($altType, $matchedPackage);
                            }
                        }
                    }
                }
            } else {
                $io->writeError('<warning>'.$package.' is not required in your composer.json and has not been removed</warning>');
            }
        }

        $io->writeError('<info>'.$file.' has been updated</info>');

        if ($input->getOption('no-update')) {
            return 0;
        }

        if ($composer = $this->tryComposer()) {
            $composer->getPluginManager()->deactivateInstalledPlugins();
        }

        // Update packages
        $this->resetComposer();
        $composer = $this->requireComposer();

        if ($dryRun) {
            $rootPackage = $composer->getPackage();
            $links = [
                'require' => $rootPackage->getRequires(),
                'require-dev' => $rootPackage->getDevRequires(),
            ];
            foreach ($toRemove as $type => $names) {
                foreach ($names as $name) {
                    unset($links[$type][$name]);
                }
            }
            $rootPackage->setRequires($links['require']);
            $rootPackage->setDevRequires($links['require-dev']);
        }

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'remove', $input, $output);
        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        $allowPlugins = $composer->getConfig()->get('allow-plugins');
        $removedPlugins = is_array($allowPlugins) ? array_intersect(array_keys($allowPlugins), $packages) : [];
        if (!$dryRun && is_array($allowPlugins) && count($removedPlugins) > 0) {
            if (count($allowPlugins) === count($removedPlugins)) {
                $json->removeConfigSetting('allow-plugins');
            } else {
                foreach ($removedPlugins as $plugin) {
                    $json->removeConfigSetting('allow-plugins.'.$plugin);
                }
            }
        }

        $composer->getInstallationManager()->setOutputProgress(!$input->getOption('no-progress'));

        $install = Installer::create($io, $composer);

        $updateDevMode = !$input->getOption('update-no-dev');
        $optimize = $input->getOption('optimize-autoloader') || $composer->getConfig()->get('optimize-autoloader');
        $authoritative = $input->getOption('classmap-authoritative') || $composer->getConfig()->get('classmap-authoritative');
        $apcuPrefix = $input->getOption('apcu-autoloader-prefix');
        $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $composer->getConfig()->get('apcu-autoloader');

        $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE;
        $flags = '';
        if ($input->getOption('update-with-all-dependencies') || $input->getOption('with-all-dependencies')) {
            $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS;
            $flags .= ' --with-all-dependencies';
        } elseif ($input->getOption('no-update-with-dependencies')) {
            $updateAllowTransitiveDependencies = Request::UPDATE_ONLY_LISTED;
            $flags .= ' --with-dependencies';
        }

        $io->writeError('<info>Running composer update '.implode(' ', $packages).$flags.'</info>');

        $install
            ->setVerbose($input->getOption('verbose'))
            ->setDevMode($updateDevMode)
            ->setOptimizeAutoloader($optimize)
            ->setClassMapAuthoritative($authoritative)
            ->setApcuAutoloader($apcu, $apcuPrefix)
            ->setUpdate(true)
            ->setInstall(!$input->getOption('no-install'))
            ->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies)
            ->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input))
            ->setDryRun($dryRun)
            ->setAudit(!$input->getOption('no-audit'))
            ->setAuditFormat($this->getAuditFormat($input))
            ->setMinimalUpdate($input->getOption('minimal-changes'))
        ;

        // if no lock is present, we do not do a partial update as
        // this is not supported by the Installer
        if ($composer->getLocker()->isLocked()) {
            $install->setUpdateAllowList($packages);
        }

        $status = $install->run();
        if ($status !== 0) {
            $io->writeError("\n".'<error>Removal failed, reverting '.$file.' to its original content.</error>');
            file_put_contents($jsonFile->getPath(), $composerBackup);
        }

        if (!$dryRun) {
            foreach ($packages as $package) {
                if ($composer->getRepositoryManager()->getLocalRepository()->findPackages($package)) {
                    $io->writeError('<error>Removal failed, '.$package.' is still present, it may be required by another package. See `composer why '.$package.'`.</error>');

                    return 2;
                }
            }
        }

        return $status;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Factory;
use Composer\Json\JsonFile;
use Composer\Json\JsonValidationException;
use Composer\Package\BasePackage;
use Composer\Package\Package;
use Composer\Pcre\Preg;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryFactory;
use Composer\Spdx\SpdxLicenses;
use Composer\Util\Filesystem;
use Composer\Util\Silencer;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Composer\Util\ProcessExecutor;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\ExecutableFinder;
use Symfony\Component\Process\Process;
use Symfony\Component\Console\Helper\FormatterHelper;

/**
 * @author Justin Rainbow <justin.rainbow@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class InitCommand extends BaseCommand
{
    use CompletionTrait;
    use PackageDiscoveryTrait;

    /** @var array<string, string> */
    private $gitConfig;

    /**
     * @inheritDoc
     *
     * @return void
     */
    protected function configure()
    {
        $this
            ->setName('init')
            ->setDescription('Creates a basic composer.json file in current directory')
            ->setDefinition([
                new InputOption('name', null, InputOption::VALUE_REQUIRED, 'Name of the package'),
                new InputOption('description', null, InputOption::VALUE_REQUIRED, 'Description of package'),
                new InputOption('author', null, InputOption::VALUE_REQUIRED, 'Author name of package'),
                new InputOption('type', null, InputOption::VALUE_REQUIRED, 'Type of package (e.g. library, project, metapackage, composer-plugin)'),
                new InputOption('homepage', null, InputOption::VALUE_REQUIRED, 'Homepage of package'),
                new InputOption('require', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"', null, $this->suggestAvailablePackageInclPlatform()),
                new InputOption('require-dev', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require for development with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"', null, $this->suggestAvailablePackageInclPlatform()),
                new InputOption('stability', 's', InputOption::VALUE_REQUIRED, 'Minimum stability (empty or one of: '.implode(', ', array_keys(BasePackage::STABILITIES)).')'),
                new InputOption('license', 'l', InputOption::VALUE_REQUIRED, 'License of package'),
                new InputOption('repository', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Add custom repositories, either by URL or using JSON arrays'),
                new InputOption('autoload', 'a', InputOption::VALUE_REQUIRED, 'Add PSR-4 autoload mapping. Maps your package\'s namespace to the provided directory. (Expects a relative path, e.g. src/)'),
            ])
            ->setHelp(
                <<<EOT
The <info>init</info> command creates a basic composer.json file
in the current directory.

<info>php composer.phar init</info>

Read more at https://getcomposer.org/doc/03-cli.md#init
EOT
            )
        ;
    }

    /**
     * @throws \Seld\JsonLint\ParsingException
     */
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = $this->getIO();

        $allowlist = ['name', 'description', 'author', 'type', 'homepage', 'require', 'require-dev', 'stability', 'license', 'autoload'];
        $options = array_filter(array_intersect_key($input->getOptions(), array_flip($allowlist)), function ($val) { return $val !== null && $val !== []; });

        if (isset($options['name']) && !Preg::isMatch('{^[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9](([_.]|-{1,2})?[a-z0-9]+)*$}D', $options['name'])) {
            throw new \InvalidArgumentException(
                'The package name '.$options['name'].' is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name, matching: [a-z0-9_.-]+/[a-z0-9_.-]+'
            );
        }

        if (isset($options['author'])) {
            $options['authors'] = $this->formatAuthors($options['author']);
            unset($options['author']);
        }

        $repositories = $input->getOption('repository');
        if (count($repositories) > 0) {
            $config = Factory::createConfig($io);
            foreach ($repositories as $repo) {
                $options['repositories'][] = RepositoryFactory::configFromString($io, $config, $repo, true);
            }
        }

        if (isset($options['stability'])) {
            $options['minimum-stability'] = $options['stability'];
            unset($options['stability']);
        }

        $options['require'] = isset($options['require']) ? $this->formatRequirements($options['require']) : new \stdClass;
        if ([] === $options['require']) {
            $options['require'] = new \stdClass;
        }

        if (isset($options['require-dev'])) {
            $options['require-dev'] = $this->formatRequirements($options['require-dev']);
            if ([] === $options['require-dev']) {
                $options['require-dev'] = new \stdClass;
            }
        }

        // --autoload - create autoload object
        $autoloadPath = null;
        if (isset($options['autoload'])) {
            $autoloadPath = $options['autoload'];
            $namespace = $this->namespaceFromPackageName((string) $input->getOption('name'));
            $options['autoload'] = (object) [
                'psr-4' => [
                    $namespace . '\\' => $autoloadPath,
                ],
            ];
        }

        $file = new JsonFile(Factory::getComposerFile());
        $json = JsonFile::encode($options);

        if ($input->isInteractive()) {
            $io->writeError(['', $json, '']);
            if (!$io->askConfirmation('Do you confirm generation [<comment>yes</comment>]? ')) {
                $io->writeError('<error>Command aborted</error>');

                return 1;
            }
        } else {
            if (json_encode($options) === '{"require":{}}') {
                throw new \RuntimeException('You have to run this command in interactive mode, or specify at least some data using --name, --require, etc.');
            }

            $io->writeError('Writing '.$file->getPath());
        }

        $file->write($options);
        try {
            $file->validateSchema(JsonFile::LAX_SCHEMA);
        } catch (JsonValidationException $e) {
            $io->writeError('<error>Schema validation error, aborting</error>');
            $errors = ' - ' . implode(PHP_EOL . ' - ', $e->getErrors());
            $io->writeError($e->getMessage() . ':' . PHP_EOL . $errors);
            Silencer::call('unlink', $file->getPath());

            return 1;
        }

        // --autoload - Create src folder
        if ($autoloadPath) {
            $filesystem = new Filesystem();
            $filesystem->ensureDirectoryExists($autoloadPath);

            // dump-autoload only for projects without added dependencies.
            if (!$this->hasDependencies($options)) {
                $this->runDumpAutoloadCommand($output);
            }
        }

        if ($input->isInteractive() && is_dir('.git')) {
            $ignoreFile = realpath('.gitignore');

            if (false === $ignoreFile) {
                $ignoreFile = realpath('.') . '/.gitignore';
            }

            if (!$this->hasVendorIgnore($ignoreFile)) {
                $question = 'Would you like the <info>vendor</info> directory added to your <info>.gitignore</info> [<comment>yes</comment>]? ';

                if ($io->askConfirmation($question)) {
                    $this->addVendorIgnore($ignoreFile);
                }
            }
        }

        $question = 'Would you like to install dependencies now [<comment>yes</comment>]? ';
        if ($input->isInteractive() && $this->hasDependencies($options) && $io->askConfirmation($question)) {
            $this->updateDependencies($output);
        }

        // --autoload - Show post-install configuration info
        if ($autoloadPath) {
            $namespace = $this->namespaceFromPackageName((string) $input->getOption('name'));

            $io->writeError('PSR-4 autoloading configured. Use "<comment>namespace '.$namespace.';</comment>" in '.$autoloadPath);
            $io->writeError('Include the Composer autoloader with: <comment>require \'vendor/autoload.php\';</comment>');
        }

        return 0;
    }

    /**
     * @inheritDoc
     *
     * @return void
     */
    protected function interact(InputInterface $input, OutputInterface $output)
    {
        $git = $this->getGitConfig();
        $io = $this->getIO();
        /** @var FormatterHelper $formatter */
        $formatter = $this->getHelperSet()->get('formatter');

        // initialize repos if configured
        $repositories = $input->getOption('repository');
        if (count($repositories) > 0) {
            $config = Factory::createConfig($io);
            $io->loadConfiguration($config);
            $repoManager = RepositoryFactory::manager($io, $config);

            $repos = [new PlatformRepository];
            $createDefaultPackagistRepo = true;
            foreach ($repositories as $repo) {
                $repoConfig = RepositoryFactory::configFromString($io, $config, $repo, true);
                if (
                    (isset($repoConfig['packagist']) && $repoConfig === ['packagist' => false])
                    || (isset($repoConfig['packagist.org']) && $repoConfig === ['packagist.org' => false])
                ) {
                    $createDefaultPackagistRepo = false;
                    continue;
                }
                $repos[] = RepositoryFactory::createRepo($io, $config, $repoConfig, $repoManager);
            }

            if ($createDefaultPackagistRepo) {
                $repos[] = RepositoryFactory::createRepo($io, $config, [
                    'type' => 'composer',
                    'url' => 'https://repo.packagist.org',
                ], $repoManager);
            }

            $this->repos = new CompositeRepository($repos);
            unset($repos, $config, $repositories);
        }

        $io->writeError([
            '',
            $formatter->formatBlock('Welcome to the Composer config generator', 'bg=blue;fg=white', true),
            '',
        ]);

        // namespace
        $io->writeError([
            '',
            'This command will guide you through creating your composer.json config.',
            '',
        ]);

        $cwd = realpath(".");

        $name = $input->getOption('name');
        if (null === $name) {
            $name = basename($cwd);
            $name = $this->sanitizePackageNameComponent($name);

            $vendor = $name;
            if (!empty($_SERVER['COMPOSER_DEFAULT_VENDOR'])) {
                $vendor = $_SERVER['COMPOSER_DEFAULT_VENDOR'];
            } elseif (isset($git['github.user'])) {
                $vendor = $git['github.user'];
            } elseif (!empty($_SERVER['USERNAME'])) {
                $vendor = $_SERVER['USERNAME'];
            } elseif (!empty($_SERVER['USER'])) {
                $vendor = $_SERVER['USER'];
            } elseif (get_current_user()) {
                $vendor = get_current_user();
            }

            $vendor = $this->sanitizePackageNameComponent($vendor);

            $name = $vendor . '/' . $name;
        }

        $name = $io->askAndValidate(
            'Package name (<vendor>/<name>) [<comment>'.$name.'</comment>]: ',
            static function ($value) use ($name) {
                if (null === $value) {
                    return $name;
                }

                if (!Preg::isMatch('{^[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9](([_.]|-{1,2})?[a-z0-9]+)*$}D', $value)) {
                    throw new \InvalidArgumentException(
                        'The package name '.$value.' is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name, matching: [a-z0-9_.-]+/[a-z0-9_.-]+'
                    );
                }

                return $value;
            },
            null,
            $name
        );
        $input->setOption('name', $name);

        $description = $input->getOption('description') ?: null;
        $description = $io->ask(
            'Description [<comment>'.$description.'</comment>]: ',
            $description
        );
        $input->setOption('description', $description);

        if (null === $author = $input->getOption('author')) {
            if (!empty($_SERVER['COMPOSER_DEFAULT_AUTHOR'])) {
                $author_name = $_SERVER['COMPOSER_DEFAULT_AUTHOR'];
            } elseif (isset($git['user.name'])) {
                $author_name = $git['user.name'];
            }

            if (!empty($_SERVER['COMPOSER_DEFAULT_EMAIL'])) {
                $author_email = $_SERVER['COMPOSER_DEFAULT_EMAIL'];
            } elseif (isset($git['user.email'])) {
                $author_email = $git['user.email'];
            }

            if (isset($author_name, $author_email)) {
                $author = sprintf('%s <%s>', $author_name, $author_email);
            }
        }

        $author = $io->askAndValidate(
            'Author ['.(is_string($author) ? '<comment>'.$author.'</comment>, ' : '') . 'n to skip]: ',
            function ($value) use ($author) {
                if ($value === 'n' || $value === 'no') {
                    return;
                }
                $value = $value ?: $author;
                $author = $this->parseAuthorString($value ?? '');

                if ($author['email'] === null) {
                    return $author['name'];
                }

                return sprintf('%s <%s>', $author['name'], $author['email']);
            },
            null,
            $author
        );
        $input->setOption('author', $author);

        $minimumStability = $input->getOption('stability') ?: null;
        $minimumStability = $io->askAndValidate(
            'Minimum Stability [<comment>'.$minimumStability.'</comment>]: ',
            static function ($value) use ($minimumStability) {
                if (null === $value) {
                    return $minimumStability;
                }

                if (!isset(BasePackage::STABILITIES[$value])) {
                    throw new \InvalidArgumentException(
                        'Invalid minimum stability "'.$value.'". Must be empty or one of: '.
                        implode(', ', array_keys(BasePackage::STABILITIES))
                    );
                }

                return $value;
            },
            null,
            $minimumStability
        );
        $input->setOption('stability', $minimumStability);

        $type = $input->getOption('type');
        $type = $io->ask(
            'Package Type (e.g. library, project, metapackage, composer-plugin) [<comment>'.$type.'</comment>]: ',
            $type
        );
        if ($type === '' || $type === false) {
            $type = null;
        }
        $input->setOption('type', $type);

        if (null === $license = $input->getOption('license')) {
            if (!empty($_SERVER['COMPOSER_DEFAULT_LICENSE'])) {
                $license = $_SERVER['COMPOSER_DEFAULT_LICENSE'];
            }
        }

        $license = $io->ask(
            'License [<comment>'.$license.'</comment>]: ',
            $license
        );
        $spdx = new SpdxLicenses();
        if (null !== $license && !$spdx->validate($license) && $license !== 'proprietary') {
            throw new \InvalidArgumentException('Invalid license provided: '.$license.'. Only SPDX license identifiers (https://spdx.org/licenses/) or "proprietary" are accepted.');
        }
        $input->setOption('license', $license);

        $io->writeError(['', 'Define your dependencies.', '']);

        // prepare to resolve dependencies
        $repos = $this->getRepos();
        $preferredStability = $minimumStability ?: 'stable';
        $platformRepo = null;
        if ($repos instanceof CompositeRepository) {
            foreach ($repos->getRepositories() as $candidateRepo) {
                if ($candidateRepo instanceof PlatformRepository) {
                    $platformRepo = $candidateRepo;
                    break;
                }
            }
        }

        $question = 'Would you like to define your dependencies (require) interactively [<comment>yes</comment>]? ';
        $require = $input->getOption('require');
        $requirements = [];
        if (count($require) > 0 || $io->askConfirmation($question)) {
            $requirements = $this->determineRequirements($input, $output, $require, $platformRepo, $preferredStability);
        }
        $input->setOption('require', $requirements);

        $question = 'Would you like to define your dev dependencies (require-dev) interactively [<comment>yes</comment>]? ';
        $requireDev = $input->getOption('require-dev');
        $devRequirements = [];
        if (count($requireDev) > 0 || $io->askConfirmation($question)) {
            $devRequirements = $this->determineRequirements($input, $output, $requireDev, $platformRepo, $preferredStability);
        }
        $input->setOption('require-dev', $devRequirements);

        // --autoload - input and validation
        $autoload = $input->getOption('autoload') ?: 'src/';
        $namespace = $this->namespaceFromPackageName((string) $input->getOption('name'));
        $autoload = $io->askAndValidate(
            'Add PSR-4 autoload mapping? Maps namespace "'.$namespace.'" to the entered relative path. [<comment>'.$autoload.'</comment>, n to skip]: ',
            static function ($value) use ($autoload) {
                if (null === $value) {
                    return $autoload;
                }

                if ($value === 'n' || $value === 'no') {
                    return;
                }

                $value = $value ?: $autoload;

                if (!Preg::isMatch('{^[^/][A-Za-z0-9\-_/]+/$}', $value)) {
                    throw new \InvalidArgumentException(sprintf(
                        'The src folder name "%s" is invalid. Please add a relative path with tailing forward slash. [A-Za-z0-9_-/]+/',
                        $value
                    ));
                }

                return $value;
            },
            null,
            $autoload
        );
        $input->setOption('autoload', $autoload);
    }

    /**
     * @return array{name: string, email: string|null}
     */
    private function parseAuthorString(string $author): array
    {
        if (Preg::isMatch('/^(?P<name>[- .,\p{L}\p{N}\p{Mn}\'’"()]+)(?:\s+<(?P<email>.+?)>)?$/u', $author, $match)) {
            if (null !== $match['email'] && !$this->isValidEmail($match['email'])) {
                throw new \InvalidArgumentException('Invalid email "'.$match['email'].'"');
            }

            return [
                'name' => trim($match['name']),
                'email' => $match['email'],
            ];
        }

        throw new \InvalidArgumentException(
            'Invalid author string.  Must be in the formats: '.
            'Jane Doe or John Smith <john@example.com>'
        );
    }

    /**
     * @return array<int, array{name: string, email?: string}>
     */
    protected function formatAuthors(string $author): array
    {
        $author = $this->parseAuthorString($author);
        if (null === $author['email']) {
            unset($author['email']);
        }

        return [$author];
    }

    /**
     * Extract namespace from package's vendor name.
     *
     * new_projects.acme-extra/package-name becomes "NewProjectsAcmeExtra\PackageName"
     */
    public function namespaceFromPackageName(string $packageName): ?string
    {
        if (!$packageName || strpos($packageName, '/') === false) {
            return null;
        }

        $namespace = array_map(
            static function ($part): string {
                $part = Preg::replace('/[^a-z0-9]/i', ' ', $part);
                $part = ucwords($part);

                return str_replace(' ', '', $part);
            },
            explode('/', $packageName)
        );

        return implode('\\', $namespace);
    }

    /**
     * @return array<string, string>
     */
    protected function getGitConfig(): array
    {
        if (null !== $this->gitConfig) {
            return $this->gitConfig;
        }

        $process = new ProcessExecutor($this->getIO());

        if (0 === $process->execute(['git', 'config', '-l'], $output)) {
            $this->gitConfig = [];
            Preg::matchAllStrictGroups('{^([^=]+)=(.*)$}m', $output, $matches);
            foreach ($matches[1] as $key => $match) {
                $this->gitConfig[$match] = $matches[2][$key];
            }

            return $this->gitConfig;
        }

        return $this->gitConfig = [];
    }

    /**
     * Checks the local .gitignore file for the Composer vendor directory.
     *
     * Tested patterns include:
     *  "/$vendor"
     *  "$vendor"
     *  "$vendor/"
     *  "/$vendor/"
     *  "/$vendor/*"
     *  "$vendor/*"
     */
    protected function hasVendorIgnore(string $ignoreFile, string $vendor = 'vendor'): bool
    {
        if (!file_exists($ignoreFile)) {
            return false;
        }

        $pattern = sprintf('{^/?%s(/\*?)?$}', preg_quote($vendor));

        $lines = file($ignoreFile, FILE_IGNORE_NEW_LINES);
        foreach ($lines as $line) {
            if (Preg::isMatch($pattern, $line)) {
                return true;
            }
        }

        return false;
    }

    protected function addVendorIgnore(string $ignoreFile, string $vendor = '/vendor/'): void
    {
        $contents = "";
        if (file_exists($ignoreFile)) {
            $contents = file_get_contents($ignoreFile);

            if (strpos($contents, "\n") !== 0) {
                $contents .= "\n";
            }
        }

        file_put_contents($ignoreFile, $contents . $vendor. "\n");
    }

    protected function isValidEmail(string $email): bool
    {
        // assume it's valid if we can't validate it
        if (!function_exists('filter_var')) {
            return true;
        }

        return false !== filter_var($email, FILTER_VALIDATE_EMAIL);
    }

    private function updateDependencies(OutputInterface $output): void
    {
        try {
            $updateCommand = $this->getApplication()->find('update');
            $this->getApplication()->resetComposer();
            $updateCommand->run(new ArrayInput([]), $output);
        } catch (\Exception $e) {
            $this->getIO()->writeError('Could not update dependencies. Run `composer update` to see more information.');
        }
    }

    private function runDumpAutoloadCommand(OutputInterface $output): void
    {
        try {
            $command = $this->getApplication()->find('dump-autoload');
            $this->getApplication()->resetComposer();
            $command->run(new ArrayInput([]), $output);
        } catch (\Exception $e) {
            $this->getIO()->writeError('Could not run dump-autoload.');
        }
    }

    /**
     * @param array<string, string|array<string>> $options
     */
    private function hasDependencies(array $options): bool
    {
        $requires = (array) $options['require'];
        $devRequires = isset($options['require-dev']) ? (array) $options['require-dev'] : [];

        return !empty($requires) || !empty($devRequires);
    }

    private function sanitizePackageNameComponent(string $name): string
    {
        $name = Preg::replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '\\1\\3-\\2\\4', $name);
        $name = strtolower($name);
        $name = Preg::replace('{^[_.-]+|[_.-]+$|[^a-z0-9_.-]}u', '', $name);
        $name = Preg::replace('{([_.-]){2,}}u', '$1', $name);

        return $name;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Package\Link;
use Composer\Semver\Constraint\Constraint;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RootPackageRepository;
use Composer\Repository\InstalledRepository;
use Composer\Json\JsonFile;

class CheckPlatformReqsCommand extends BaseCommand
{
    protected function configure(): void
    {
        $this->setName('check-platform-reqs')
            ->setDescription('Check that platform requirements are satisfied')
            ->setDefinition([
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables checking of require-dev packages requirements.'),
                new InputOption('lock', null, InputOption::VALUE_NONE, 'Checks requirements only from the lock file, not from installed packages.'),
                new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']),
            ])
            ->setHelp(
                <<<EOT
Checks that your PHP and extensions versions match the platform requirements of the installed packages.

Unlike update/install, this command will ignore config.platform settings and check the real platform packages so you can be certain you have the required platform dependencies.

<info>php composer.phar check-platform-reqs</info>

EOT
            );
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $composer = $this->requireComposer();

        $requires = [];
        $removePackages = [];
        if ($input->getOption('lock')) {
            $this->getIO()->writeError('<info>Checking '.($input->getOption('no-dev') ? 'non-dev ' : '').'platform requirements using the lock file</info>');
            $installedRepo = $composer->getLocker()->getLockedRepository(!$input->getOption('no-dev'));
        } else {
            $installedRepo = $composer->getRepositoryManager()->getLocalRepository();
            // fallback to lockfile if installed repo is empty
            if (!$installedRepo->getPackages()) {
                $this->getIO()->writeError('<warning>No vendor dir present, checking '.($input->getOption('no-dev') ? 'non-dev ' : '').'platform requirements from the lock file</warning>');
                $installedRepo = $composer->getLocker()->getLockedRepository(!$input->getOption('no-dev'));
            } else {
                if ($input->getOption('no-dev')) {
                    $removePackages = $installedRepo->getDevPackageNames();
                }

                $this->getIO()->writeError('<info>Checking '.($input->getOption('no-dev') ? 'non-dev ' : '').'platform requirements for packages in the vendor dir</info>');
            }
        }
        if (!$input->getOption('no-dev')) {
            foreach ($composer->getPackage()->getDevRequires() as $require => $link) {
                $requires[$require] = [$link];
            }
        }

        $installedRepo = new InstalledRepository([$installedRepo, new RootPackageRepository(clone $composer->getPackage())]);
        foreach ($installedRepo->getPackages() as $package) {
            if (in_array($package->getName(), $removePackages, true)) {
                continue;
            }
            foreach ($package->getRequires() as $require => $link) {
                $requires[$require][] = $link;
            }
        }

        ksort($requires);

        $installedRepo->addRepository(new PlatformRepository([], []));

        $results = [];
        $exitCode = 0;

        /**
         * @var Link[] $links
         */
        foreach ($requires as $require => $links) {
            if (PlatformRepository::isPlatformPackage($require)) {
                $candidates = $installedRepo->findPackagesWithReplacersAndProviders($require);
                if ($candidates) {
                    $reqResults = [];
                    foreach ($candidates as $candidate) {
                        $candidateConstraint = null;
                        if ($candidate->getName() === $require) {
                            $candidateConstraint = new Constraint('=', $candidate->getVersion());
                            $candidateConstraint->setPrettyString($candidate->getPrettyVersion());
                        } else {
                            foreach (array_merge($candidate->getProvides(), $candidate->getReplaces()) as $link) {
                                if ($link->getTarget() === $require) {
                                    $candidateConstraint = $link->getConstraint();
                                    break;
                                }
                            }
                        }

                        // safety check for phpstan, but it should not be possible to get a candidate out of findPackagesWithReplacersAndProviders without a constraint matching $require
                        if (!$candidateConstraint) {
                            continue;
                        }

                        foreach ($links as $link) {
                            if (!$link->getConstraint()->matches($candidateConstraint)) {
                                $reqResults[] = [
                                    $candidate->getName() === $require ? $candidate->getPrettyName() : $require,
                                    $candidateConstraint->getPrettyString(),
                                    $link,
                                    '<error>failed</error>',
                                    $candidate->getName() === $require ? '' : '<comment>provided by '.$candidate->getPrettyName().'</comment>',
                                ];

                                // skip to next candidate
                                continue 2;
                            }
                        }

                        $results[] = [
                            $candidate->getName() === $require ? $candidate->getPrettyName() : $require,
                            $candidateConstraint->getPrettyString(),
                            null,
                            '<info>success</info>',
                            $candidate->getName() === $require ? '' : '<comment>provided by '.$candidate->getPrettyName().'</comment>',
                        ];

                        // candidate matched, skip to next requirement
                        continue 2;
                    }

                    // show the first error from every failed candidate
                    $results = array_merge($results, $reqResults);
                    $exitCode = max($exitCode, 1);

                    continue;
                }

                $results[] = [
                    $require,
                    'n/a',
                    $links[0],
                    '<error>missing</error>',
                    '',
                ];

                $exitCode = max($exitCode, 2);
            }
        }

        $this->printTable($output, $results, $input->getOption('format'));

        return $exitCode;
    }

    /**
     * @param mixed[] $results
     */
    protected function printTable(OutputInterface $output, array $results, string $format): void
    {
        $rows = [];
        foreach ($results as $result) {
            /**
             * @var Link|null $link
             */
            [$platformPackage, $version, $link, $status, $provider] = $result;

            if ('json' === $format) {
                $rows[] = [
                    "name" => $platformPackage,
                    "version" => $version,
                    "status" => strip_tags($status),
                    "failed_requirement" => $link instanceof Link ? [
                        'source' => $link->getSource(),
                        'type' => $link->getDescription(),
                        'target' => $link->getTarget(),
                        'constraint' => $link->getPrettyConstraint(),
                    ] : null,
                    "provider" => $provider === '' ? null : strip_tags($provider),
                ];
            } else {
                $rows[] = [
                    $platformPackage,
                    $version,
                    $link,
                    $link ? sprintf('%s %s %s (%s)', $link->getSource(), $link->getDescription(), $link->getTarget(), $link->getPrettyConstraint()) : '',
                    rtrim($status.' '.$provider),
                ];
            }
        }

        if ('json' === $format) {
            $this->getIO()->write(JsonFile::encode($rows));
        } else {
            $this->renderTable($rows, $output);
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Console\Input\InputArgument;
use Composer\Console\Input\InputOption;

/**
 * @author Niels Keurentjes <niels.keurentjes@omines.com>
 */
class DependsCommand extends BaseDependencyCommand
{
    use CompletionTrait;

    /**
     * Configure command metadata.
     */
    protected function configure(): void
    {
        $this
            ->setName('depends')
            ->setAliases(['why'])
            ->setDescription('Shows which packages cause the given package to be installed')
            ->setDefinition([
                new InputArgument(self::ARGUMENT_PACKAGE, InputArgument::REQUIRED, 'Package to inspect', null, $this->suggestInstalledPackage(true, true)),
                new InputOption(self::OPTION_RECURSIVE, 'r', InputOption::VALUE_NONE, 'Recursively resolves up to the root package'),
                new InputOption(self::OPTION_TREE, 't', InputOption::VALUE_NONE, 'Prints the results as a nested tree'),
                new InputOption('locked', null, InputOption::VALUE_NONE, 'Read dependency information from composer.lock'),
            ])
            ->setHelp(
                <<<EOT
Displays detailed information about where a package is referenced.

<info>php composer.phar depends composer/composer</info>

Read more at https://getcomposer.org/doc/03-cli.md#depends-why
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        return parent::doExecute($input, $output);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Config;
use Composer\Factory;
use Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
use Composer\Installer;
use Composer\Installer\ProjectInstaller;
use Composer\Installer\SuggestedPackagesReporter;
use Composer\IO\IOInterface;
use Composer\Package\BasePackage;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\Package\Version\VersionSelector;
use Composer\Package\AliasPackage;
use Composer\Pcre\Preg;
use Composer\Plugin\PluginBlockedException;
use Composer\Repository\RepositoryFactory;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\InstalledArrayRepository;
use Composer\Repository\RepositorySet;
use Composer\Script\ScriptEvents;
use Composer\Util\Silencer;
use Composer\Console\Input\InputArgument;
use Seld\Signal\SignalHandler;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Finder\Finder;
use Composer\Json\JsonFile;
use Composer\Config\JsonConfigSource;
use Composer\Util\Filesystem;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Package\Version\VersionParser;
use Composer\Advisory\Auditor;

/**
 * Install a package as new project into new directory.
 *
 * @author Benjamin Eberlei <kontakt@beberlei.de>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Tobias Munk <schmunk@usrbin.de>
 * @author Nils Adermann <naderman@naderman.de>
 */
class CreateProjectCommand extends BaseCommand
{
    use CompletionTrait;

    /**
     * @var SuggestedPackagesReporter
     */
    protected $suggestedPackagesReporter;

    protected function configure(): void
    {
        $this
            ->setName('create-project')
            ->setDescription('Creates new project from a package into given directory')
            ->setDefinition([
                new InputArgument('package', InputArgument::OPTIONAL, 'Package name to be installed', null, $this->suggestAvailablePackage()),
                new InputArgument('directory', InputArgument::OPTIONAL, 'Directory where the files should be created'),
                new InputArgument('version', InputArgument::OPTIONAL, 'Version, will default to latest'),
                new InputOption('stability', 's', InputOption::VALUE_REQUIRED, 'Minimum-stability allowed (unless a version is specified).'),
                new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
                new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'),
                new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()),
                new InputOption('repository', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Add custom repositories to look the package up, either by URL or using JSON arrays'),
                new InputOption('repository-url', null, InputOption::VALUE_REQUIRED, 'DEPRECATED: Use --repository instead.'),
                new InputOption('add-repository', null, InputOption::VALUE_NONE, 'Add the custom repository in the composer.json. If a lock file is present it will be deleted and an update will be run instead of install.'),
                new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of require-dev packages (enabled by default, only present for BC).'),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'),
                new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'),
                new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Whether to prevent execution of all defined scripts in the root package.'),
                new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                new InputOption('no-secure-http', null, InputOption::VALUE_NONE, 'Disable the secure-http config option temporarily while installing the root package. Use at your own risk. Using this flag is a bad idea.'),
                new InputOption('keep-vcs', null, InputOption::VALUE_NONE, 'Whether to prevent deleting the vcs folder.'),
                new InputOption('remove-vcs', null, InputOption::VALUE_NONE, 'Whether to force deletion of the vcs folder without prompting.'),
                new InputOption('no-install', null, InputOption::VALUE_NONE, 'Whether to skip installation of the package dependencies.'),
                new InputOption('no-audit', null, InputOption::VALUE_NONE, 'Whether to skip auditing of the installed package dependencies (can also be set via the COMPOSER_NO_AUDIT=1 env var).'),
                new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", "json" or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS),
                new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'),
                new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'),
                new InputOption('ask', null, InputOption::VALUE_NONE, 'Whether to ask for project directory.'),
            ])
            ->setHelp(
                <<<EOT
The <info>create-project</info> command creates a new project from a given
package into a new directory. If executed without params and in a directory
with a composer.json file it installs the packages for the current project.

You can use this command to bootstrap new projects or setup a clean
version-controlled installation for developers of your project.

<info>php composer.phar create-project vendor/project target-directory [version]</info>

You can also specify the version with the package name using = or : as separator.

<info>php composer.phar create-project vendor/project:version target-directory</info>

To install unstable packages, either specify the version you want, or use the
--stability=dev (where dev can be one of RC, beta, alpha or dev).

To setup a developer workable version you should create the project using the source
controlled code by appending the <info>'--prefer-source'</info> flag.

To install a package from another repository than the default one you
can pass the <info>'--repository=https://myrepository.org'</info> flag.

Read more at https://getcomposer.org/doc/03-cli.md#create-project
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $config = Factory::createConfig();
        $io = $this->getIO();

        [$preferSource, $preferDist] = $this->getPreferredInstallOptions($config, $input, true);

        if ($input->getOption('dev')) {
            $io->writeError('<warning>You are using the deprecated option "dev". Dev packages are installed by default now.</warning>');
        }
        if ($input->getOption('no-custom-installers')) {
            $io->writeError('<warning>You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.</warning>');
            $input->setOption('no-plugins', true);
        }

        if ($input->isInteractive() && $input->getOption('ask')) {
            $package = $input->getArgument('package');
            if (null === $package) {
                throw new \RuntimeException('Not enough arguments (missing: "package").');
            }
            $parts = explode("/", strtolower($package), 2);
            $input->setArgument('directory', $io->ask('New project directory [<comment>'.array_pop($parts).'</comment>]: '));
        }

        return $this->installProject(
            $io,
            $config,
            $input,
            $input->getArgument('package'),
            $input->getArgument('directory'),
            $input->getArgument('version'),
            $input->getOption('stability'),
            $preferSource,
            $preferDist,
            !$input->getOption('no-dev'),
            \count($input->getOption('repository')) > 0 ? $input->getOption('repository') : $input->getOption('repository-url'),
            $input->getOption('no-plugins'),
            $input->getOption('no-scripts'),
            $input->getOption('no-progress'),
            $input->getOption('no-install'),
            $this->getPlatformRequirementFilter($input),
            !$input->getOption('no-secure-http'),
            $input->getOption('add-repository')
        );
    }

    /**
     * @param string|array<string>|null $repositories
     *
     * @throws \Exception
     */
    public function installProject(IOInterface $io, Config $config, InputInterface $input, ?string $packageName = null, ?string $directory = null, ?string $packageVersion = null, ?string $stability = 'stable', bool $preferSource = false, bool $preferDist = false, bool $installDevPackages = false, $repositories = null, bool $disablePlugins = false, bool $disableScripts = false, bool $noProgress = false, bool $noInstall = false, ?PlatformRequirementFilterInterface $platformRequirementFilter = null, bool $secureHttp = true, bool $addRepository = false): int
    {
        $oldCwd = Platform::getCwd();

        if ($repositories !== null && !is_array($repositories)) {
            $repositories = (array) $repositories;
        }

        $platformRequirementFilter = $platformRequirementFilter ?? PlatformRequirementFilterFactory::ignoreNothing();

        // we need to manually load the configuration to pass the auth credentials to the io interface!
        $io->loadConfiguration($config);

        $this->suggestedPackagesReporter = new SuggestedPackagesReporter($io);

        if ($packageName !== null) {
            $installedFromVcs = $this->installRootPackage($input, $io, $config, $packageName, $platformRequirementFilter, $directory, $packageVersion, $stability, $preferSource, $preferDist, $installDevPackages, $repositories, $disablePlugins, $disableScripts, $noProgress, $secureHttp);
        } else {
            $installedFromVcs = false;
        }

        if ($repositories !== null && $addRepository && is_file('composer.lock')) {
            unlink('composer.lock');
        }

        $composer = $this->createComposerInstance($input, $io, null, $disablePlugins, $disableScripts);

        // add the repository to the composer.json and use it for the install run later
        if ($repositories !== null && $addRepository) {
            foreach ($repositories as $index => $repo) {
                $repoConfig = RepositoryFactory::configFromString($io, $composer->getConfig(), $repo, true);
                $composerJsonRepositoriesConfig = $composer->getConfig()->getRepositories();
                $name = RepositoryFactory::generateRepositoryName($index, $repoConfig, $composerJsonRepositoriesConfig);
                $configSource = new JsonConfigSource(new JsonFile('composer.json'));

                if (
                    (isset($repoConfig['packagist']) && $repoConfig === ['packagist' => false])
                    || (isset($repoConfig['packagist.org']) && $repoConfig === ['packagist.org' => false])
                ) {
                    $configSource->addRepository('packagist.org', false);
                } else {
                    $configSource->addRepository($name, $repoConfig, false);
                }

                $composer = $this->createComposerInstance($input, $io, null, $disablePlugins);
            }
        }

        $process = $composer->getLoop()->getProcessExecutor();
        $fs = new Filesystem($process);

        // dispatch event
        $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_ROOT_PACKAGE_INSTALL, $installDevPackages);

        // use the new config including the newly installed project
        $config = $composer->getConfig();
        [$preferSource, $preferDist] = $this->getPreferredInstallOptions($config, $input);

        // install dependencies of the created project
        if ($noInstall === false) {
            $composer->getInstallationManager()->setOutputProgress(!$noProgress);

            $installer = Installer::create($io, $composer);
            $installer->setPreferSource($preferSource)
                ->setPreferDist($preferDist)
                ->setDevMode($installDevPackages)
                ->setPlatformRequirementFilter($platformRequirementFilter)
                ->setSuggestedPackagesReporter($this->suggestedPackagesReporter)
                ->setOptimizeAutoloader($config->get('optimize-autoloader'))
                ->setClassMapAuthoritative($config->get('classmap-authoritative'))
                ->setApcuAutoloader($config->get('apcu-autoloader'))
                ->setAudit(!$input->getOption('no-audit'))
                ->setAuditFormat($this->getAuditFormat($input));

            if (!$composer->getLocker()->isLocked()) {
                $installer->setUpdate(true);
            }

            if ($disablePlugins) {
                $installer->disablePlugins();
            }

            try {
                $status = $installer->run();
                if (0 !== $status) {
                    return $status;
                }
            } catch (PluginBlockedException $e) {
                $io->writeError('<error>Hint: To allow running the config command recommended below before dependencies are installed, run create-project with --no-install.</error>');
                $io->writeError('<error>You can then cd into '.getcwd().', configure allow-plugins, and finally run a composer install to complete the process.</error>');
                throw $e;
            }
        }

        $hasVcs = $installedFromVcs;
        if (
            !$input->getOption('keep-vcs')
            && $installedFromVcs
            && (
                $input->getOption('remove-vcs')
                || !$io->isInteractive()
                || $io->askConfirmation('<info>Do you want to remove the existing VCS (.git, .svn..) history?</info> [<comment>Y,n</comment>]? ')
            )
        ) {
            $finder = new Finder();
            $finder->depth(0)->directories()->in(Platform::getCwd())->ignoreVCS(false)->ignoreDotFiles(false);
            foreach (['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg', '.fslckout', '_FOSSIL_'] as $vcsName) {
                $finder->name($vcsName);
            }

            try {
                $dirs = iterator_to_array($finder);
                unset($finder);
                foreach ($dirs as $dir) {
                    if (!$fs->removeDirectory((string) $dir)) {
                        throw new \RuntimeException('Could not remove '.$dir);
                    }
                }
            } catch (\Exception $e) {
                $io->writeError('<error>An error occurred while removing the VCS metadata: '.$e->getMessage().'</error>');
            }

            $hasVcs = false;
        }

        // rewriting self.version dependencies with explicit version numbers if the package's vcs metadata is gone
        if (!$hasVcs) {
            $package = $composer->getPackage();
            $configSource = new JsonConfigSource(new JsonFile('composer.json'));
            foreach (BasePackage::$supportedLinkTypes as $type => $meta) {
                foreach ($package->{'get'.$meta['method']}() as $link) {
                    if ($link->getPrettyConstraint() === 'self.version') {
                        $configSource->addLink($type, $link->getTarget(), $package->getPrettyVersion());
                    }
                }
            }
        }

        // dispatch event
        $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_CREATE_PROJECT_CMD, $installDevPackages);

        chdir($oldCwd);

        return 0;
    }

    /**
     * @param array<string>|null $repositories
     *
     * @throws \Exception
     */
    protected function installRootPackage(InputInterface $input, IOInterface $io, Config $config, string $packageName, PlatformRequirementFilterInterface $platformRequirementFilter, ?string $directory = null, ?string $packageVersion = null, ?string $stability = 'stable', bool $preferSource = false, bool $preferDist = false, bool $installDevPackages = false, ?array $repositories = null, bool $disablePlugins = false, bool $disableScripts = false, bool $noProgress = false, bool $secureHttp = true): bool
    {
        $parser = new VersionParser();
        $requirements = $parser->parseNameVersionPairs([$packageName]);
        $name = strtolower($requirements[0]['name']);
        if (!$packageVersion && isset($requirements[0]['version'])) {
            $packageVersion = $requirements[0]['version'];
        }

        // if no directory was specified, use the 2nd part of the package name
        if (null === $directory) {
            $parts = explode("/", $name, 2);
            $directory = Platform::getCwd() . DIRECTORY_SEPARATOR . array_pop($parts);
        }
        $directory = rtrim($directory, '/\\');

        $process = new ProcessExecutor($io);
        $fs = new Filesystem($process);
        if (!$fs->isAbsolutePath($directory)) {
            $directory = Platform::getCwd() . DIRECTORY_SEPARATOR . $directory;
        }
        if ('' === $directory) {
            throw new \UnexpectedValueException('Got an empty target directory, something went wrong');
        }

        // set the base dir to ensure $config->all() below resolves the correct absolute paths to vendor-dir etc
        $config->setBaseDir($directory);
        if (!$secureHttp) {
            $config->merge(['config' => ['secure-http' => false]], Config::SOURCE_COMMAND);
        }

        $io->writeError('<info>Creating a "' . $packageName . '" project at "' . $fs->findShortestPath(Platform::getCwd(), $directory, true) . '"</info>');

        if (file_exists($directory)) {
            if (!is_dir($directory)) {
                throw new \InvalidArgumentException('Cannot create project directory at "'.$directory.'", it exists as a file.');
            }
            if (!$fs->isDirEmpty($directory)) {
                throw new \InvalidArgumentException('Project directory "'.$directory.'" is not empty.');
            }
        }

        if (null === $stability) {
            if (null === $packageVersion) {
                $stability = 'stable';
            } elseif (Preg::isMatchStrictGroups('{^[^,\s]*?@('.implode('|', array_keys(BasePackage::STABILITIES)).')$}i', $packageVersion, $match)) {
                $stability = $match[1];
            } else {
                $stability = VersionParser::parseStability($packageVersion);
            }
        }

        $stability = VersionParser::normalizeStability($stability);

        if (!isset(BasePackage::STABILITIES[$stability])) {
            throw new \InvalidArgumentException('Invalid stability provided ('.$stability.'), must be one of: '.implode(', ', array_keys(BasePackage::STABILITIES)));
        }

        $composer = $this->createComposerInstance($input, $io, $config->all(), $disablePlugins, $disableScripts);
        $config = $composer->getConfig();
        // set the base dir here again on the new config instance, as otherwise in case the vendor dir is defined in an env var for example it would still override the value set above by $config->all()
        $config->setBaseDir($directory);
        $rm = $composer->getRepositoryManager();

        $repositorySet = new RepositorySet($stability);
        if (null === $repositories) {
            $repositorySet->addRepository(new CompositeRepository(RepositoryFactory::defaultRepos($io, $config, $rm)));
        } else {
            foreach ($repositories as $repo) {
                $repoConfig = RepositoryFactory::configFromString($io, $config, $repo, true);
                if (
                    (isset($repoConfig['packagist']) && $repoConfig === ['packagist' => false])
                    || (isset($repoConfig['packagist.org']) && $repoConfig === ['packagist.org' => false])
                ) {
                    continue;
                }

                // disable symlinking for the root package by default as that most likely makes no sense
                if (($repoConfig['type'] ?? null) === 'path' && !isset($repoConfig['options']['symlink'])) {
                    $repoConfig['options']['symlink'] = false;
                }

                $repositorySet->addRepository(RepositoryFactory::createRepo($io, $config, $repoConfig, $rm));
            }
        }

        $platformOverrides = $config->get('platform');
        $platformRepo = new PlatformRepository([], $platformOverrides);

        // find the latest version if there are multiple
        $versionSelector = new VersionSelector($repositorySet, $platformRepo);
        $package = $versionSelector->findBestCandidate($name, $packageVersion, $stability, $platformRequirementFilter, 0, $io);

        if (!$package) {
            $errorMessage = "Could not find package $name with " . ($packageVersion ? "version $packageVersion" : "stability $stability");
            if (!($platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter) && $versionSelector->findBestCandidate($name, $packageVersion, $stability, PlatformRequirementFilterFactory::ignoreAll())) {
                throw new \InvalidArgumentException($errorMessage .' in a version installable using your PHP version, PHP extensions and Composer version.');
            }

            throw new \InvalidArgumentException($errorMessage .'.');
        }

        // handler Ctrl+C aborts gracefully
        @mkdir($directory, 0777, true);
        if (false !== ($realDir = realpath($directory))) {
            $signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) use ($realDir) {
                $this->getIO()->writeError('Received '.$signal.', aborting', true, IOInterface::DEBUG);
                $fs = new Filesystem();
                $fs->removeDirectory($realDir);
                $handler->exitWithLastSignal();
            });
        }

        // avoid displaying 9999999-dev as version if default-branch was selected
        if ($package instanceof AliasPackage && $package->getPrettyVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) {
            $package = $package->getAliasOf();
        }

        $io->writeError('<info>Installing ' . $package->getName() . ' (' . $package->getFullPrettyVersion(false) . ')</info>');

        if ($disablePlugins) {
            $io->writeError('<info>Plugins have been disabled.</info>');
        }

        if ($package instanceof AliasPackage) {
            $package = $package->getAliasOf();
        }

        $dm = $composer->getDownloadManager();
        $dm->setPreferSource($preferSource)
            ->setPreferDist($preferDist);

        $projectInstaller = new ProjectInstaller($directory, $dm, $fs);
        $im = $composer->getInstallationManager();
        $im->setOutputProgress(!$noProgress);
        $im->addInstaller($projectInstaller);
        $im->execute(new InstalledArrayRepository(), [new InstallOperation($package)]);
        $im->notifyInstalls($io);

        // collect suggestions
        $this->suggestedPackagesReporter->addSuggestionsFromPackage($package);

        $installedFromVcs = 'source' === $package->getInstallationSource();

        $io->writeError('<info>Created project in ' . $directory . '</info>');
        chdir($directory);

        // ensure that the env var being set does not interfere with create-project
        // as it is probably not meant to be used here, so we do not use it if a composer.json can be found
        // in the project
        if (file_exists($directory.'/composer.json') && Platform::getEnv('COMPOSER') !== false) {
            Platform::clearEnv('COMPOSER');
        }

        Platform::putEnv('COMPOSER_ROOT_VERSION', $package->getPrettyVersion());

        // once the root project is fully initialized, we do not need to wipe everything on user abort anymore even if it happens during deps install
        if (isset($signalHandler)) {
            $signalHandler->unregister();
        }

        return $installedFromVcs;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Composer;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class AboutCommand extends BaseCommand
{
    protected function configure(): void
    {
        $this
            ->setName('about')
            ->setDescription('Shows a short information about Composer')
            ->setHelp(
                <<<EOT
<info>php composer.phar about</info>
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $composerVersion = Composer::getVersion();

        $this->getIO()->write(
            <<<EOT
<info>Composer - Dependency Manager for PHP - version $composerVersion</info>
<comment>Composer is a dependency manager tracking local dependencies of your projects and libraries.
See https://getcomposer.org/ for more information.</comment>
EOT
        );

        return 0;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Repository\PlatformRepository;
use Composer\Repository\RootPackageRepository;
use Composer\Repository\InstalledRepository;
use Composer\Installer\SuggestedPackagesReporter;
use Composer\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class SuggestsCommand extends BaseCommand
{
    use CompletionTrait;

    protected function configure(): void
    {
        $this
            ->setName('suggests')
            ->setDescription('Shows package suggestions')
            ->setDefinition([
                new InputOption('by-package', null, InputOption::VALUE_NONE, 'Groups output by suggesting package (default)'),
                new InputOption('by-suggestion', null, InputOption::VALUE_NONE, 'Groups output by suggested package'),
                new InputOption('all', 'a', InputOption::VALUE_NONE, 'Show suggestions from all dependencies, including transitive ones'),
                new InputOption('list', null, InputOption::VALUE_NONE, 'Show only list of suggested package names'),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Exclude suggestions from require-dev packages'),
                new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Packages that you want to list suggestions from.', null, $this->suggestInstalledPackage()),
            ])
            ->setHelp(
                <<<EOT

The <info>%command.name%</info> command shows a sorted list of suggested packages.

Read more at https://getcomposer.org/doc/03-cli.md#suggests
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $composer = $this->requireComposer();

        $installedRepos = [
            new RootPackageRepository(clone $composer->getPackage()),
        ];

        $locker = $composer->getLocker();
        if ($locker->isLocked()) {
            $installedRepos[] = new PlatformRepository([], $locker->getPlatformOverrides());
            $installedRepos[] = $locker->getLockedRepository(!$input->getOption('no-dev'));
        } else {
            $installedRepos[] = new PlatformRepository([], $composer->getConfig()->get('platform'));
            $installedRepos[] = $composer->getRepositoryManager()->getLocalRepository();
        }

        $installedRepo = new InstalledRepository($installedRepos);
        $reporter = new SuggestedPackagesReporter($this->getIO());

        $filter = $input->getArgument('packages');
        $packages = $installedRepo->getPackages();
        $packages[] = $composer->getPackage();
        foreach ($packages as $package) {
            if (!empty($filter) && !in_array($package->getName(), $filter)) {
                continue;
            }

            $reporter->addSuggestionsFromPackage($package);
        }

        // Determine output mode, default is by-package
        $mode = SuggestedPackagesReporter::MODE_BY_PACKAGE;

        // if by-suggestion is given we override the default
        if ($input->getOption('by-suggestion')) {
            $mode = SuggestedPackagesReporter::MODE_BY_SUGGESTION;
        }
        // unless by-package is also present then we enable both
        if ($input->getOption('by-package')) {
            $mode |= SuggestedPackagesReporter::MODE_BY_PACKAGE;
        }
        // list is exclusive and overrides everything else
        if ($input->getOption('list')) {
            $mode = SuggestedPackagesReporter::MODE_LIST;
        }

        $reporter->output($mode, $installedRepo, empty($filter) && !$input->getOption('all') ? $composer->getPackage() : null);

        return 0;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Factory;
use Composer\Json\JsonFile;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputArgument;
use Composer\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryInterface;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;

/**
 * @author Robert Schönthal <seroscho@googlemail.com>
 */
class SearchCommand extends BaseCommand
{
    protected function configure(): void
    {
        $this
            ->setName('search')
            ->setDescription('Searches for packages')
            ->setDefinition([
                new InputOption('only-name', 'N', InputOption::VALUE_NONE, 'Search only in package names'),
                new InputOption('only-vendor', 'O', InputOption::VALUE_NONE, 'Search only for vendor / organization names, returns only "vendor" as result'),
                new InputOption('type', 't', InputOption::VALUE_REQUIRED, 'Search for a specific package type'),
                new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']),
                new InputArgument('tokens', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'tokens to search for'),
            ])
            ->setHelp(
                <<<EOT
The search command searches for packages by its name
<info>php composer.phar search symfony composer</info>

Read more at https://getcomposer.org/doc/03-cli.md#search
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        // init repos
        $platformRepo = new PlatformRepository;
        $io = $this->getIO();

        $format = $input->getOption('format');
        if (!in_array($format, ['text', 'json'])) {
            $io->writeError(sprintf('Unsupported format "%s". See help for supported formats.', $format));

            return 1;
        }

        if (!($composer = $this->tryComposer())) {
            $composer = $this->createComposerInstance($input, $this->getIO(), []);
        }
        $localRepo = $composer->getRepositoryManager()->getLocalRepository();
        $installedRepo = new CompositeRepository([$localRepo, $platformRepo]);
        $repos = new CompositeRepository(array_merge([$installedRepo], $composer->getRepositoryManager()->getRepositories()));

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'search', $input, $output);
        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        $mode = RepositoryInterface::SEARCH_FULLTEXT;
        if ($input->getOption('only-name') === true) {
            if ($input->getOption('only-vendor') === true) {
                throw new \InvalidArgumentException('--only-name and --only-vendor cannot be used together');
            }
            $mode = RepositoryInterface::SEARCH_NAME;
        } elseif ($input->getOption('only-vendor') === true) {
            $mode = RepositoryInterface::SEARCH_VENDOR;
        }

        $type = $input->getOption('type');

        $query = implode(' ', $input->getArgument('tokens'));
        if ($mode !== RepositoryInterface::SEARCH_FULLTEXT) {
            $query = preg_quote($query);
        }

        $results = $repos->search($query, $mode, $type);

        if (\count($results) > 0 && $format === 'text') {
            $width = $this->getTerminalWidth();

            $nameLength = 0;
            foreach ($results as $result) {
                $nameLength = max(strlen($result['name']), $nameLength);
            }
            $nameLength += 1;
            foreach ($results as $result) {
                $description = $result['description'] ?? '';
                $warning = !empty($result['abandoned']) ? '<warning>! Abandoned !</warning> ' : '';
                $remaining = $width - $nameLength - strlen($warning) - 2;
                if (strlen($description) > $remaining) {
                    $description = substr($description, 0, $remaining - 3) . '...';
                }

                $link = $result['url'] ?? null;
                if ($link !== null) {
                    $io->write('<href='.OutputFormatter::escape($link).'>'.$result['name'].'</>'. str_repeat(' ', $nameLength - strlen($result['name'])) . $warning . $description);
                } else {
                    $io->write(str_pad($result['name'], $nameLength, ' ') . $warning . $description);
                }
            }
        } elseif ($format === 'json') {
            $io->write(JsonFile::encode($results));
        }

        return 0;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Cache;
use Composer\Factory;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author David Neilsen <petah.p@gmail.com>
 */
class ClearCacheCommand extends BaseCommand
{
    protected function configure(): void
    {
        $this
            ->setName('clear-cache')
            ->setAliases(['clearcache', 'cc'])
            ->setDescription('Clears composer\'s internal package cache')
            ->setDefinition([
                new InputOption('gc', null, InputOption::VALUE_NONE, 'Only run garbage collection, not a full cache clear'),
            ])
            ->setHelp(
                <<<EOT
The <info>clear-cache</info> deletes all cached packages from composer's
cache directory.

Read more at https://getcomposer.org/doc/03-cli.md#clear-cache-clearcache-cc
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $composer = $this->tryComposer();
        if ($composer !== null) {
            $config = $composer->getConfig();
        } else {
            $config = Factory::createConfig();
        }

        $io = $this->getIO();

        $cachePaths = [
            'cache-vcs-dir' => $config->get('cache-vcs-dir'),
            'cache-repo-dir' => $config->get('cache-repo-dir'),
            'cache-files-dir' => $config->get('cache-files-dir'),
            'cache-dir' => $config->get('cache-dir'),
        ];

        foreach ($cachePaths as $key => $cachePath) {
            // only individual dirs get garbage collected
            if ($key === 'cache-dir' && $input->getOption('gc')) {
                continue;
            }

            $cachePath = realpath($cachePath);
            if (!$cachePath) {
                $io->writeError("<info>Cache directory does not exist ($key): $cachePath</info>");

                continue;
            }
            $cache = new Cache($io, $cachePath);
            $cache->setReadOnly($config->get('cache-read-only'));
            if (!$cache->isEnabled()) {
                $io->writeError("<info>Cache is not enabled ($key): $cachePath</info>");

                continue;
            }

            if ($input->getOption('gc')) {
                $io->writeError("<info>Garbage-collecting cache ($key): $cachePath</info>");
                if ($key === 'cache-files-dir') {
                    $cache->gc($config->get('cache-files-ttl'), $config->get('cache-files-maxsize'));
                } elseif ($key === 'cache-repo-dir') {
                    $cache->gc($config->get('cache-ttl'), 1024 * 1024 * 1024 /* 1GB, this should almost never clear anything that is not outdated */);
                } elseif ($key === 'cache-vcs-dir') {
                    $cache->gcVcsCache($config->get('cache-ttl'));
                }
            } else {
                $io->writeError("<info>Clearing cache ($key): $cachePath</info>");
                $cache->clear();
            }
        }

        if ($input->getOption('gc')) {
            $io->writeError('<info>All caches garbage-collected.</info>');
        } else {
            $io->writeError('<info>All caches cleared.</info>');
        }

        return 0;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Package\Link;
use Composer\Package\Package;
use Composer\Package\PackageInterface;
use Composer\Package\CompletePackageInterface;
use Composer\Package\RootPackage;
use Composer\Repository\InstalledArrayRepository;
use Composer\Repository\CompositeRepository;
use Composer\Repository\RootPackageRepository;
use Composer\Repository\InstalledRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryFactory;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Semver\Constraint\Bound;
use Composer\Util\Platform;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Composer\Package\Version\VersionParser;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Util\PackageInfo;

/**
 * Base implementation for commands mapping dependency relationships.
 *
 * @author Niels Keurentjes <niels.keurentjes@omines.com>
 */
abstract class BaseDependencyCommand extends BaseCommand
{
    protected const ARGUMENT_PACKAGE = 'package';
    protected const ARGUMENT_CONSTRAINT = 'version';
    protected const OPTION_RECURSIVE = 'recursive';
    protected const OPTION_TREE = 'tree';

    /** @var string[] */
    protected $colors;

    /**
     * Execute the command.
     *
     * @param  bool            $inverted Whether to invert matching process (why-not vs why behaviour)
     * @return int             Exit code of the operation.
     */
    protected function doExecute(InputInterface $input, OutputInterface $output, bool $inverted = false): int
    {
        // Emit command event on startup
        $composer = $this->requireComposer();
        $commandEvent = new CommandEvent(PluginEvents::COMMAND, $this->getName(), $input, $output);
        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        $repos = [];

        $repos[] = new RootPackageRepository(clone $composer->getPackage());

        if ($input->getOption('locked')) {
            $locker = $composer->getLocker();

            if (!$locker->isLocked()) {
                throw new \UnexpectedValueException('A valid composer.lock file is required to run this command with --locked');
            }

            $repos[] = $locker->getLockedRepository(true);
            $repos[] = new PlatformRepository([], $locker->getPlatformOverrides());
        } else {
            $localRepo = $composer->getRepositoryManager()->getLocalRepository();
            $rootPkg = $composer->getPackage();

            if (count($localRepo->getPackages()) === 0 && (count($rootPkg->getRequires()) > 0 || count($rootPkg->getDevRequires()) > 0)) {
                $output->writeln('<warning>No dependencies installed. Try running composer install or update, or use --locked.</warning>');

                return 1;
            }

            $repos[] = $localRepo;

            $platformOverrides = $composer->getConfig()->get('platform') ?: [];
            $repos[] = new PlatformRepository([], $platformOverrides);
        }

        $installedRepo = new InstalledRepository($repos);

        // Parse package name and constraint
        $needle = $input->getArgument(self::ARGUMENT_PACKAGE);
        $textConstraint = $input->hasArgument(self::ARGUMENT_CONSTRAINT) ? $input->getArgument(self::ARGUMENT_CONSTRAINT) : '*';

        // Find packages that are or provide the requested package first
        $packages = $installedRepo->findPackagesWithReplacersAndProviders($needle);
        if (empty($packages)) {
            throw new \InvalidArgumentException(sprintf('Could not find package "%s" in your project', $needle));
        }

        // If the version we ask for is not installed then we need to locate it in remote repos and add it.
        // This is needed for why-not to resolve conflicts from an uninstalled version against installed packages.
        $matchedPackage = $installedRepo->findPackage($needle, $textConstraint);
        if (!$matchedPackage) {
            $defaultRepos = new CompositeRepository(RepositoryFactory::defaultRepos($this->getIO(), $composer->getConfig(), $composer->getRepositoryManager()));
            if ($match = $defaultRepos->findPackage($needle, $textConstraint)) {
                $installedRepo->addRepository(new InstalledArrayRepository([clone $match]));
            } elseif (PlatformRepository::isPlatformPackage($needle)) {
                $parser = new VersionParser();
                $constraint = $parser->parseConstraints($textConstraint);
                if ($constraint->getLowerBound() !== Bound::zero()) {
                    $tempPlatformPkg = new Package($needle, $constraint->getLowerBound()->getVersion(), $constraint->getLowerBound()->getVersion());
                    $installedRepo->addRepository(new InstalledArrayRepository([$tempPlatformPkg]));
                }
            } else {
                $this->getIO()->writeError('<error>Package "'.$needle.'" could not be found with constraint "'.$textConstraint.'", results below will most likely be incomplete.</error>');
            }
        } elseif (PlatformRepository::isPlatformPackage($needle)) {
            $extraNotice = '';
            if (($matchedPackage->getExtra()['config.platform'] ?? false) === true) {
                $extraNotice = ' (version provided by config.platform)';
            }
            $this->getIO()->writeError('<info>Package "'.$needle.' '.$textConstraint.'" found in version "'.$matchedPackage->getPrettyVersion().'"'.$extraNotice.'.</info>');
        } elseif ($inverted) {
            $this->getIO()->write('<comment>Package "'.$needle.'" '.$matchedPackage->getPrettyVersion().' is already installed! To find out why, run `composer why '.$needle.'`</comment>');
            return 0;
        }

        // Include replaced packages for inverted lookups as they are then the actual starting point to consider
        $needles = [$needle];
        if ($inverted) {
            foreach ($packages as $package) {
                $needles = array_merge($needles, array_map(static function (Link $link): string {
                    return $link->getTarget();
                }, $package->getReplaces()));
            }
        }

        // Parse constraint if one was supplied
        if ('*' !== $textConstraint) {
            $versionParser = new VersionParser();
            $constraint = $versionParser->parseConstraints($textConstraint);
        } else {
            $constraint = null;
        }

        // Parse rendering options
        $renderTree = $input->getOption(self::OPTION_TREE);
        $recursive = $renderTree || $input->getOption(self::OPTION_RECURSIVE);

        $return = $inverted ? 1 : 0;

        // Resolve dependencies
        $results = $installedRepo->getDependents($needles, $constraint, $inverted, $recursive);
        if (empty($results)) {
            $extra = (null !== $constraint) ? sprintf(' in versions %smatching %s', $inverted ? 'not ' : '', $textConstraint) : '';
            $this->getIO()->writeError(sprintf(
                '<info>There is no installed package depending on "%s"%s</info>',
                $needle,
                $extra
            ));
            $return = $inverted ? 0 : 1;
        } elseif ($renderTree) {
            $this->initStyles($output);
            $root = $packages[0];
            $this->getIO()->write(sprintf('<info>%s</info> %s %s', $root->getPrettyName(), $root->getPrettyVersion(), $root instanceof CompletePackageInterface ? $root->getDescription() : ''));
            $this->printTree($results);
        } else {
            $this->printTable($output, $results);
        }

        if ($inverted && $input->hasArgument(self::ARGUMENT_CONSTRAINT) && !PlatformRepository::isPlatformPackage($needle)) {
            $composerCommand = 'update';

            foreach ($composer->getPackage()->getRequires() as $rootRequirement) {
                if ($rootRequirement->getTarget() === $needle) {
                    $composerCommand = 'require';
                    break;
                }
            }

            foreach ($composer->getPackage()->getDevRequires() as $rootRequirement) {
                if ($rootRequirement->getTarget() === $needle) {
                    $composerCommand = 'require --dev';
                    break;
                }
            }

            $this->getIO()->writeError('Not finding what you were looking for? Try calling `composer '.$composerCommand.' "'.$needle.':'.$textConstraint.'" --dry-run` to get another view on the problem.');
        }

        return $return;
    }

    /**
     * Assembles and prints a bottom-up table of the dependencies.
     *
     * @param array{PackageInterface, Link, array<mixed>|false}[] $results
     */
    protected function printTable(OutputInterface $output, array $results): void
    {
        $table = [];
        $doubles = [];
        do {
            $queue = [];
            $rows = [];
            foreach ($results as $result) {
                /**
                 * @var PackageInterface $package
                 * @var Link             $link
                 */
                [$package, $link, $children] = $result;
                $unique = (string) $link;
                if (isset($doubles[$unique])) {
                    continue;
                }
                $doubles[$unique] = true;
                $version = $package->getPrettyVersion() === RootPackage::DEFAULT_PRETTY_VERSION ? '-' : $package->getPrettyVersion();
                $packageUrl = PackageInfo::getViewSourceOrHomepageUrl($package);
                $nameWithLink = $packageUrl !== null ? '<href=' . OutputFormatter::escape($packageUrl) . '>' . $package->getPrettyName() . '</>' : $package->getPrettyName();
                $rows[] = [$nameWithLink, $version, $link->getDescription(), sprintf('%s (%s)', $link->getTarget(), $link->getPrettyConstraint())];
                if (is_array($children)) {
                    $queue = array_merge($queue, $children);
                }
            }
            $results = $queue;
            $table = array_merge($rows, $table);
        } while (\count($results) > 0);

        $this->renderTable($table, $output);
    }

    /**
     * Init styles for tree
     */
    protected function initStyles(OutputInterface $output): void
    {
        $this->colors = [
            'green',
            'yellow',
            'cyan',
            'magenta',
            'blue',
        ];

        foreach ($this->colors as $color) {
            $style = new OutputFormatterStyle($color);
            $output->getFormatter()->setStyle($color, $style);
        }
    }

    /**
     * Recursively prints a tree of the selected results.
     *
     * @param array{PackageInterface, Link, array<mixed>|false}[] $results Results to be printed at this level.
     * @param string  $prefix  Prefix of the current tree level.
     * @param int     $level   Current level of recursion.
     */
    protected function printTree(array $results, string $prefix = '', int $level = 1): void
    {
        $count = count($results);
        $idx = 0;
        foreach ($results as $result) {
            [$package, $link, $children] = $result;

            $color = $this->colors[$level % count($this->colors)];
            $prevColor = $this->colors[($level - 1) % count($this->colors)];
            $isLast = (++$idx === $count);
            $versionText = $package->getPrettyVersion() === RootPackage::DEFAULT_PRETTY_VERSION ? '' : $package->getPrettyVersion();
            $packageUrl = PackageInfo::getViewSourceOrHomepageUrl($package);
            $nameWithLink = $packageUrl !== null ? '<href=' . OutputFormatter::escape($packageUrl) . '>' . $package->getPrettyName() . '</>' : $package->getPrettyName();
            $packageText = rtrim(sprintf('<%s>%s</%1$s> %s', $color, $nameWithLink, $versionText));
            $linkText = sprintf('%s <%s>%s</%2$s> %s', $link->getDescription(), $prevColor, $link->getTarget(), $link->getPrettyConstraint());
            $circularWarn = $children === false ? '(circular dependency aborted here)' : '';
            $this->writeTreeLine(rtrim(sprintf("%s%s%s (%s) %s", $prefix, $isLast ? '└──' : '├──', $packageText, $linkText, $circularWarn)));
            if (is_array($children)) {
                $this->printTree($children, $prefix . ($isLast ? '   ' : '│  '), $level + 1);
            }
        }
    }

    private function writeTreeLine(string $line): void
    {
        $io = $this->getIO();
        if (!$io->isDecorated()) {
            $line = str_replace(['└', '├', '──', '│'], ['`-', '|-', '-', '|'], $line);
        }

        $io->write($line);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Advisory\Auditor;
use Composer\Pcre\Preg;
use Composer\Util\Filesystem;
use Composer\Util\Platform;
use Composer\Util\Silencer;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputArgument;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Config;
use Composer\Config\JsonConfigSource;
use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
use Composer\Semver\VersionParser;
use Composer\Package\BasePackage;

/**
 * @author Joshua Estes <Joshua.Estes@iostudio.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ConfigCommand extends BaseCommand
{
    /**
     * List of additional configurable package-properties
     *
     * @var string[]
     */
    protected const CONFIGURABLE_PACKAGE_PROPERTIES = [
        'name',
        'type',
        'description',
        'homepage',
        'version',
        'minimum-stability',
        'prefer-stable',
        'keywords',
        'license',
        'repositories',
        'suggest',
        'extra',
    ];

    /**
     * @var Config
     */
    protected $config;

    /**
     * @var JsonFile
     */
    protected $configFile;

    /**
     * @var JsonConfigSource
     */
    protected $configSource;

    /**
     * @var JsonFile
     */
    protected $authConfigFile;

    /**
     * @var JsonConfigSource
     */
    protected $authConfigSource;

    protected function configure(): void
    {
        $this
            ->setName('config')
            ->setDescription('Sets config options')
            ->setDefinition([
                new InputOption('global', 'g', InputOption::VALUE_NONE, 'Apply command to the global config file'),
                new InputOption('editor', 'e', InputOption::VALUE_NONE, 'Open editor'),
                new InputOption('auth', 'a', InputOption::VALUE_NONE, 'Affect auth config file (only used for --editor)'),
                new InputOption('unset', null, InputOption::VALUE_NONE, 'Unset the given setting-key'),
                new InputOption('list', 'l', InputOption::VALUE_NONE, 'List configuration settings'),
                new InputOption('file', 'f', InputOption::VALUE_REQUIRED, 'If you want to choose a different composer.json or config.json'),
                new InputOption('absolute', null, InputOption::VALUE_NONE, 'Returns absolute paths when fetching *-dir config values instead of relative'),
                new InputOption('json', 'j', InputOption::VALUE_NONE, 'JSON decode the setting value, to be used with extra.* keys'),
                new InputOption('merge', 'm', InputOption::VALUE_NONE, 'Merge the setting value with the current value, to be used with extra.* keys in combination with --json'),
                new InputOption('append', null, InputOption::VALUE_NONE, 'When adding a repository, append it (lowest priority) to the existing ones instead of prepending it (highest priority)'),
                new InputOption('source', null, InputOption::VALUE_NONE, 'Display where the config value is loaded from'),
                new InputArgument('setting-key', null, 'Setting key', null, $this->suggestSettingKeys()),
                new InputArgument('setting-value', InputArgument::IS_ARRAY, 'Setting value'),
            ])
            ->setHelp(
                <<<EOT
This command allows you to edit composer config settings and repositories
in either the local composer.json file or the global config.json file.

Additionally it lets you edit most properties in the local composer.json.

To set a config setting:

    <comment>%command.full_name% bin-dir bin/</comment>

To read a config setting:

    <comment>%command.full_name% bin-dir</comment>
    Outputs: <info>bin</info>

To edit the global config.json file:

    <comment>%command.full_name% --global</comment>

To add a repository:

    <comment>%command.full_name% repositories.foo vcs https://bar.com</comment>

To remove a repository (repo is a short alias for repositories):

    <comment>%command.full_name% --unset repo.foo</comment>

To disable packagist:

    <comment>%command.full_name% repo.packagist false</comment>

You can alter repositories in the global config.json file by passing in the
<info>--global</info> option.

To add or edit suggested packages you can use:

    <comment>%command.full_name% suggest.package reason for the suggestion</comment>

To add or edit extra properties you can use:

    <comment>%command.full_name% extra.property value</comment>

Or to add a complex value you can use json with:

    <comment>%command.full_name% extra.property --json '{"foo":true, "bar": []}'</comment>

To edit the file in an external editor:

    <comment>%command.full_name% --editor</comment>

To choose your editor you can set the "EDITOR" env variable.

To get a list of configuration values in the file:

    <comment>%command.full_name% --list</comment>

You can always pass more than one option. As an example, if you want to edit the
global config.json file.

    <comment>%command.full_name% --editor --global</comment>

Read more at https://getcomposer.org/doc/03-cli.md#config
EOT
            )
        ;
    }

    /**
     * @throws \Exception
     */
    protected function initialize(InputInterface $input, OutputInterface $output): void
    {
        parent::initialize($input, $output);

        if ($input->getOption('global') && null !== $input->getOption('file')) {
            throw new \RuntimeException('--file and --global can not be combined');
        }

        $io = $this->getIO();
        $this->config = Factory::createConfig($io);

        $configFile = $this->getComposerConfigFile($input, $this->config);

        // Create global composer.json if this was invoked using `composer global config`
        if (
            ($configFile === 'composer.json' || $configFile === './composer.json')
            && !file_exists($configFile)
            && realpath(Platform::getCwd()) === realpath($this->config->get('home'))
        ) {
            file_put_contents($configFile, "{\n}\n");
        }

        $this->configFile = new JsonFile($configFile, null, $io);
        $this->configSource = new JsonConfigSource($this->configFile);

        $authConfigFile = $this->getAuthConfigFile($input, $this->config);

        $this->authConfigFile = new JsonFile($authConfigFile, null, $io);
        $this->authConfigSource = new JsonConfigSource($this->authConfigFile, true);

        // Initialize the global file if it's not there, ignoring any warnings or notices
        if ($input->getOption('global') && !$this->configFile->exists()) {
            touch($this->configFile->getPath());
            $this->configFile->write(['config' => new \ArrayObject]);
            Silencer::call('chmod', $this->configFile->getPath(), 0600);
        }
        if ($input->getOption('global') && !$this->authConfigFile->exists()) {
            touch($this->authConfigFile->getPath());
            $this->authConfigFile->write(['bitbucket-oauth' => new \ArrayObject, 'github-oauth' => new \ArrayObject, 'gitlab-oauth' => new \ArrayObject, 'gitlab-token' => new \ArrayObject, 'http-basic' => new \ArrayObject, 'bearer' => new \ArrayObject]);
            Silencer::call('chmod', $this->authConfigFile->getPath(), 0600);
        }

        if (!$this->configFile->exists()) {
            throw new \RuntimeException(sprintf('File "%s" cannot be found in the current directory', $configFile));
        }
    }

    /**
     * @throws \Seld\JsonLint\ParsingException
     */
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        // Open file in editor
        if (true === $input->getOption('editor')) {
            $editor = Platform::getEnv('EDITOR');
            if (false === $editor || '' === $editor) {
                if (Platform::isWindows()) {
                    $editor = 'notepad';
                } else {
                    foreach (['editor', 'vim', 'vi', 'nano', 'pico', 'ed'] as $candidate) {
                        if (exec('which '.$candidate)) {
                            $editor = $candidate;
                            break;
                        }
                    }
                }
            } else {
                $editor = escapeshellcmd($editor);
            }

            $file = $input->getOption('auth') ? $this->authConfigFile->getPath() : $this->configFile->getPath();
            system($editor . ' ' . $file . (Platform::isWindows() ? '' : ' > `tty`'));

            return 0;
        }

        if (false === $input->getOption('global')) {
            $this->config->merge($this->configFile->read(), $this->configFile->getPath());
            $this->config->merge(['config' => $this->authConfigFile->exists() ? $this->authConfigFile->read() : []], $this->authConfigFile->getPath());
        }

        $this->getIO()->loadConfiguration($this->config);

        // List the configuration of the file settings
        if (true === $input->getOption('list')) {
            $this->listConfiguration($this->config->all(), $this->config->raw(), $output, null, $input->getOption('source'));

            return 0;
        }

        $settingKey = $input->getArgument('setting-key');
        if (!is_string($settingKey)) {
            return 0;
        }

        // If the user enters in a config variable, parse it and save to file
        if ([] !== $input->getArgument('setting-value') && $input->getOption('unset')) {
            throw new \RuntimeException('You can not combine a setting value with --unset');
        }

        // show the value if no value is provided
        if ([] === $input->getArgument('setting-value') && !$input->getOption('unset')) {
            $properties = self::CONFIGURABLE_PACKAGE_PROPERTIES;
            $propertiesDefaults = [
                'type' => 'library',
                'description' => '',
                'homepage' => '',
                'minimum-stability' => 'stable',
                'prefer-stable' => false,
                'keywords' => [],
                'license' => [],
                'suggest' => [],
                'extra' => [],
            ];
            $rawData = $this->configFile->read();
            $data = $this->config->all();
            $source = $this->config->getSourceOfValue($settingKey);

            if (Preg::isMatch('/^repos?(?:itories)?(?:\.(.+))?/', $settingKey, $matches)) {
                if (!isset($matches[1])) {
                    $value = $data['repositories'] ?? [];
                } else {
                    if (!isset($data['repositories'][$matches[1]])) {
                        throw new \InvalidArgumentException('There is no '.$matches[1].' repository defined');
                    }

                    $value = $data['repositories'][$matches[1]];
                }
            } elseif (strpos($settingKey, '.')) {
                $bits = explode('.', $settingKey);
                if ($bits[0] === 'extra' || $bits[0] === 'suggest') {
                    $data = $rawData;
                } else {
                    $data = $data['config'];
                }
                $match = false;
                foreach ($bits as $bit) {
                    $key = isset($key) ? $key.'.'.$bit : $bit;
                    $match = false;
                    if (isset($data[$key])) {
                        $match = true;
                        $data = $data[$key];
                        unset($key);
                    }
                }

                if (!$match) {
                    throw new \RuntimeException($settingKey.' is not defined.');
                }

                $value = $data;
            } elseif (isset($data['config'][$settingKey])) {
                $value = $this->config->get($settingKey, $input->getOption('absolute') ? 0 : Config::RELATIVE_PATHS);
                // ensure we get {} output for properties which are objects
                if ($value === []) {
                    $schema = JsonFile::parseJson((string) file_get_contents(JsonFile::COMPOSER_SCHEMA_PATH));
                    if (
                        isset($schema['properties']['config']['properties'][$settingKey]['type'])
                        && in_array('object', (array) $schema['properties']['config']['properties'][$settingKey]['type'], true)
                    ) {
                        $value = new \stdClass;
                    }
                }
            } elseif (isset($rawData[$settingKey]) && in_array($settingKey, $properties, true)) {
                $value = $rawData[$settingKey];
                $source = $this->configFile->getPath();
            } elseif (isset($propertiesDefaults[$settingKey])) {
                $value = $propertiesDefaults[$settingKey];
                $source = 'defaults';
            } else {
                throw new \RuntimeException($settingKey.' is not defined');
            }

            if (is_array($value) || is_object($value) || is_bool($value)) {
                $value = JsonFile::encode($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
            }

            $sourceOfConfigValue = '';
            if ($input->getOption('source')) {
                $sourceOfConfigValue = ' (' . $source . ')';
            }

            $this->getIO()->write($value . $sourceOfConfigValue, true, IOInterface::QUIET);

            return 0;
        }

        $values = $input->getArgument('setting-value'); // what the user is trying to add/change

        $booleanValidator = static function ($val): bool {
            return in_array($val, ['true', 'false', '1', '0'], true);
        };
        $booleanNormalizer = static function ($val): bool {
            return $val !== 'false' && (bool) $val;
        };

        // handle config values
        $uniqueConfigValues = [
            'process-timeout' => ['is_numeric', 'intval'],
            'use-include-path' => [$booleanValidator, $booleanNormalizer],
            'use-github-api' => [$booleanValidator, $booleanNormalizer],
            'preferred-install' => [
                static function ($val): bool {
                    return in_array($val, ['auto', 'source', 'dist'], true);
                },
                static function ($val) {
                    return $val;
                },
            ],
            'gitlab-protocol' => [
                static function ($val): bool {
                    return in_array($val, ['git', 'http', 'https'], true);
                },
                static function ($val) {
                    return $val;
                },
            ],
            'store-auths' => [
                static function ($val): bool {
                    return in_array($val, ['true', 'false', 'prompt'], true);
                },
                static function ($val) {
                    if ('prompt' === $val) {
                        return 'prompt';
                    }

                    return $val !== 'false' && (bool) $val;
                },
            ],
            'notify-on-install' => [$booleanValidator, $booleanNormalizer],
            'vendor-dir' => ['is_string', static function ($val) {
                return $val;
            }],
            'bin-dir' => ['is_string', static function ($val) {
                return $val;
            }],
            'archive-dir' => ['is_string', static function ($val) {
                return $val;
            }],
            'archive-format' => ['is_string', static function ($val) {
                return $val;
            }],
            'data-dir' => ['is_string', static function ($val) {
                return $val;
            }],
            'cache-dir' => ['is_string', static function ($val) {
                return $val;
            }],
            'cache-files-dir' => ['is_string', static function ($val) {
                return $val;
            }],
            'cache-repo-dir' => ['is_string', static function ($val) {
                return $val;
            }],
            'cache-vcs-dir' => ['is_string', static function ($val) {
                return $val;
            }],
            'cache-ttl' => ['is_numeric', 'intval'],
            'cache-files-ttl' => ['is_numeric', 'intval'],
            'cache-files-maxsize' => [
                static function ($val): bool {
                    return Preg::isMatch('/^\s*([0-9.]+)\s*(?:([kmg])(?:i?b)?)?\s*$/i', $val);
                },
                static function ($val) {
                    return $val;
                },
            ],
            'bin-compat' => [
                static function ($val): bool {
                    return in_array($val, ['auto', 'full', 'proxy', 'symlink']);
                },
                static function ($val) {
                    return $val;
                },
            ],
            'discard-changes' => [
                static function ($val): bool {
                    return in_array($val, ['stash', 'true', 'false', '1', '0'], true);
                },
                static function ($val) {
                    if ('stash' === $val) {
                        return 'stash';
                    }

                    return $val !== 'false' && (bool) $val;
                },
            ],
            'autoloader-suffix' => ['is_string', static function ($val) {
                return $val === 'null' ? null : $val;
            }],
            'sort-packages' => [$booleanValidator, $booleanNormalizer],
            'optimize-autoloader' => [$booleanValidator, $booleanNormalizer],
            'classmap-authoritative' => [$booleanValidator, $booleanNormalizer],
            'apcu-autoloader' => [$booleanValidator, $booleanNormalizer],
            'prepend-autoloader' => [$booleanValidator, $booleanNormalizer],
            'disable-tls' => [$booleanValidator, $booleanNormalizer],
            'secure-http' => [$booleanValidator, $booleanNormalizer],
            'bump-after-update' => [
                static function ($val): bool {
                    return in_array($val, ['dev', 'no-dev', 'true', 'false', '1', '0'], true);
                },
                static function ($val) {
                    if ('dev' === $val || 'no-dev' === $val) {
                        return $val;
                    }

                    return $val !== 'false' && (bool) $val;
                },
            ],
            'cafile' => [
                static function ($val): bool {
                    return file_exists($val) && Filesystem::isReadable($val);
                },
                static function ($val) {
                    return $val === 'null' ? null : $val;
                },
            ],
            'capath' => [
                static function ($val): bool {
                    return is_dir($val) && Filesystem::isReadable($val);
                },
                static function ($val) {
                    return $val === 'null' ? null : $val;
                },
            ],
            'github-expose-hostname' => [$booleanValidator, $booleanNormalizer],
            'htaccess-protect' => [$booleanValidator, $booleanNormalizer],
            'lock' => [$booleanValidator, $booleanNormalizer],
            'allow-plugins' => [$booleanValidator, $booleanNormalizer],
            'platform-check' => [
                static function ($val): bool {
                    return in_array($val, ['php-only', 'true', 'false', '1', '0'], true);
                },
                static function ($val) {
                    if ('php-only' === $val) {
                        return 'php-only';
                    }

                    return $val !== 'false' && (bool) $val;
                },
            ],
            'use-parent-dir' => [
                static function ($val): bool {
                    return in_array($val, ['true', 'false', 'prompt'], true);
                },
                static function ($val) {
                    if ('prompt' === $val) {
                        return 'prompt';
                    }

                    return $val !== 'false' && (bool) $val;
                },
            ],
            'audit.abandoned' => [
                static function ($val): bool {
                    return in_array($val, [Auditor::ABANDONED_IGNORE, Auditor::ABANDONED_REPORT, Auditor::ABANDONED_FAIL], true);
                },
                static function ($val) {
                    return $val;
                },
            ],
        ];
        $multiConfigValues = [
            'github-protocols' => [
                static function ($vals) {
                    if (!is_array($vals)) {
                        return 'array expected';
                    }

                    foreach ($vals as $val) {
                        if (!in_array($val, ['git', 'https', 'ssh'])) {
                            return 'valid protocols include: git, https, ssh';
                        }
                    }

                    return true;
                },
                static function ($vals) {
                    return $vals;
                },
            ],
            'github-domains' => [
                static function ($vals) {
                    if (!is_array($vals)) {
                        return 'array expected';
                    }

                    return true;
                },
                static function ($vals) {
                    return $vals;
                },
            ],
            'gitlab-domains' => [
                static function ($vals) {
                    if (!is_array($vals)) {
                        return 'array expected';
                    }

                    return true;
                },
                static function ($vals) {
                    return $vals;
                },
            ],
            'audit.ignore' => [
                static function ($vals) {
                    if (!is_array($vals)) {
                        return 'array expected';
                    }

                    return true;
                },
                static function ($vals) {
                    return $vals;
                },
            ],
        ];

        // allow unsetting audit config entirely
        if ($input->getOption('unset') && $settingKey === 'audit') {
            $this->configSource->removeConfigSetting($settingKey);

            return 0;
        }

        if ($input->getOption('unset') && (isset($uniqueConfigValues[$settingKey]) || isset($multiConfigValues[$settingKey]))) {
            if ($settingKey === 'disable-tls' && $this->config->get('disable-tls')) {
                $this->getIO()->writeError('<info>You are now running Composer with SSL/TLS protection enabled.</info>');
            }

            $this->configSource->removeConfigSetting($settingKey);

            return 0;
        }
        if (isset($uniqueConfigValues[$settingKey])) {
            $this->handleSingleValue($settingKey, $uniqueConfigValues[$settingKey], $values, 'addConfigSetting');

            return 0;
        }
        if (isset($multiConfigValues[$settingKey])) {
            $this->handleMultiValue($settingKey, $multiConfigValues[$settingKey], $values, 'addConfigSetting');

            return 0;
        }
        // handle preferred-install per-package config
        if (Preg::isMatch('/^preferred-install\.(.+)/', $settingKey, $matches)) {
            if ($input->getOption('unset')) {
                $this->configSource->removeConfigSetting($settingKey);

                return 0;
            }

            [$validator] = $uniqueConfigValues['preferred-install'];
            if (!$validator($values[0])) {
                throw new \RuntimeException('Invalid value for '.$settingKey.'. Should be one of: auto, source, or dist');
            }

            $this->configSource->addConfigSetting($settingKey, $values[0]);

            return 0;
        }

        // handle allow-plugins config setting elements true or false to add/remove
        if (Preg::isMatch('{^allow-plugins\.([a-zA-Z0-9/*-]+)}', $settingKey, $matches)) {
            if ($input->getOption('unset')) {
                $this->configSource->removeConfigSetting($settingKey);

                return 0;
            }

            if (true !== $booleanValidator($values[0])) {
                throw new \RuntimeException(sprintf(
                    '"%s" is an invalid value',
                    $values[0]
                ));
            }

            $normalizedValue = $booleanNormalizer($values[0]);

            $this->configSource->addConfigSetting($settingKey, $normalizedValue);

            return 0;
        }

        // handle properties
        $uniqueProps = [
            'name' => ['is_string', static function ($val) {
                return $val;
            }],
            'type' => ['is_string', static function ($val) {
                return $val;
            }],
            'description' => ['is_string', static function ($val) {
                return $val;
            }],
            'homepage' => ['is_string', static function ($val) {
                return $val;
            }],
            'version' => ['is_string', static function ($val) {
                return $val;
            }],
            'minimum-stability' => [
                static function ($val): bool {
                    return isset(BasePackage::STABILITIES[VersionParser::normalizeStability($val)]);
                },
                static function ($val): string {
                    return VersionParser::normalizeStability($val);
                },
            ],
            'prefer-stable' => [$booleanValidator, $booleanNormalizer],
        ];
        $multiProps = [
            'keywords' => [
                static function ($vals) {
                    if (!is_array($vals)) {
                        return 'array expected';
                    }

                    return true;
                },
                static function ($vals) {
                    return $vals;
                },
            ],
            'license' => [
                static function ($vals) {
                    if (!is_array($vals)) {
                        return 'array expected';
                    }

                    return true;
                },
                static function ($vals) {
                    return $vals;
                },
            ],
        ];

        if ($input->getOption('global') && (isset($uniqueProps[$settingKey]) || isset($multiProps[$settingKey]) || strpos($settingKey, 'extra.') === 0)) {
            throw new \InvalidArgumentException('The ' . $settingKey . ' property can not be set in the global config.json file. Use `composer global config` to apply changes to the global composer.json');
        }
        if ($input->getOption('unset') && (isset($uniqueProps[$settingKey]) || isset($multiProps[$settingKey]))) {
            $this->configSource->removeProperty($settingKey);

            return 0;
        }
        if (isset($uniqueProps[$settingKey])) {
            $this->handleSingleValue($settingKey, $uniqueProps[$settingKey], $values, 'addProperty');

            return 0;
        }
        if (isset($multiProps[$settingKey])) {
            $this->handleMultiValue($settingKey, $multiProps[$settingKey], $values, 'addProperty');

            return 0;
        }

        // handle repositories
        if (Preg::isMatchStrictGroups('/^repos?(?:itories)?\.(.+)/', $settingKey, $matches)) {
            if ($input->getOption('unset')) {
                $this->configSource->removeRepository($matches[1]);

                return 0;
            }

            if (2 === count($values)) {
                $this->configSource->addRepository($matches[1], [
                    'type' => $values[0],
                    'url' => $values[1],
                ], $input->getOption('append'));

                return 0;
            }

            if (1 === count($values)) {
                $value = strtolower($values[0]);
                if (true === $booleanValidator($value)) {
                    if (false === $booleanNormalizer($value)) {
                        $this->configSource->addRepository($matches[1], false, $input->getOption('append'));

                        return 0;
                    }
                } else {
                    $value = JsonFile::parseJson($values[0]);
                    $this->configSource->addRepository($matches[1], $value, $input->getOption('append'));

                    return 0;
                }
            }

            throw new \RuntimeException('You must pass the type and a url. Example: php composer.phar config repositories.foo vcs https://bar.com');
        }

        // handle extra
        if (Preg::isMatch('/^extra\.(.+)/', $settingKey, $matches)) {
            if ($input->getOption('unset')) {
                $this->configSource->removeProperty($settingKey);

                return 0;
            }

            $value = $values[0];
            if ($input->getOption('json')) {
                $value = JsonFile::parseJson($value);
                if ($input->getOption('merge')) {
                    $currentValue = $this->configFile->read();
                    $bits = explode('.', $settingKey);
                    foreach ($bits as $bit) {
                        $currentValue = $currentValue[$bit] ?? null;
                    }
                    if (is_array($currentValue) && is_array($value)) {
                        if (array_is_list($currentValue) && array_is_list($value)) {
                            $value = array_merge($currentValue, $value);
                        } else {
                            $value = $value + $currentValue;
                        }
                    }
                }
            }
            $this->configSource->addProperty($settingKey, $value);

            return 0;
        }

        // handle suggest
        if (Preg::isMatch('/^suggest\.(.+)/', $settingKey, $matches)) {
            if ($input->getOption('unset')) {
                $this->configSource->removeProperty($settingKey);

                return 0;
            }

            $this->configSource->addProperty($settingKey, implode(' ', $values));

            return 0;
        }

        // handle unsetting extra/suggest
        if (in_array($settingKey, ['suggest', 'extra'], true) && $input->getOption('unset')) {
            $this->configSource->removeProperty($settingKey);

            return 0;
        }

        // handle platform
        if (Preg::isMatch('/^platform\.(.+)/', $settingKey, $matches)) {
            if ($input->getOption('unset')) {
                $this->configSource->removeConfigSetting($settingKey);

                return 0;
            }

            $this->configSource->addConfigSetting($settingKey, $values[0] === 'false' ? false : $values[0]);

            return 0;
        }

        // handle unsetting platform
        if ($settingKey === 'platform' && $input->getOption('unset')) {
            $this->configSource->removeConfigSetting($settingKey);

            return 0;
        }

        // handle auth
        if (Preg::isMatch('/^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|http-basic|bearer)\.(.+)/', $settingKey, $matches)) {
            if ($input->getOption('unset')) {
                $this->authConfigSource->removeConfigSetting($matches[1].'.'.$matches[2]);
                $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);

                return 0;
            }

            if ($matches[1] === 'bitbucket-oauth') {
                if (2 !== count($values)) {
                    throw new \RuntimeException('Expected two arguments (consumer-key, consumer-secret), got '.count($values));
                }
                $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
                $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], ['consumer-key' => $values[0], 'consumer-secret' => $values[1]]);
            } elseif ($matches[1] === 'gitlab-token' && 2 === count($values)) {
                $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
                $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], ['username' => $values[0], 'token' => $values[1]]);
            } elseif (in_array($matches[1], ['github-oauth', 'gitlab-oauth', 'gitlab-token', 'bearer'], true)) {
                if (1 !== count($values)) {
                    throw new \RuntimeException('Too many arguments, expected only one token');
                }
                $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
                $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], $values[0]);
            } elseif ($matches[1] === 'http-basic') {
                if (2 !== count($values)) {
                    throw new \RuntimeException('Expected two arguments (username, password), got '.count($values));
                }
                $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
                $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], ['username' => $values[0], 'password' => $values[1]]);
            }

            return 0;
        }

        // handle script
        if (Preg::isMatch('/^scripts\.(.+)/', $settingKey, $matches)) {
            if ($input->getOption('unset')) {
                $this->configSource->removeProperty($settingKey);

                return 0;
            }

            $this->configSource->addProperty($settingKey, count($values) > 1 ? $values : $values[0]);

            return 0;
        }

        // handle unsetting other top level properties
        if ($input->getOption('unset')) {
            $this->configSource->removeProperty($settingKey);

            return 0;
        }

        throw new \InvalidArgumentException('Setting '.$settingKey.' does not exist or is not supported by this command');
    }

    /**
     * @param array{callable, callable} $callbacks Validator and normalizer callbacks
     * @param array<string> $values
     */
    protected function handleSingleValue(string $key, array $callbacks, array $values, string $method): void
    {
        [$validator, $normalizer] = $callbacks;
        if (1 !== count($values)) {
            throw new \RuntimeException('You can only pass one value. Example: php composer.phar config process-timeout 300');
        }

        if (true !== $validation = $validator($values[0])) {
            throw new \RuntimeException(sprintf(
                '"%s" is an invalid value'.($validation ? ' ('.$validation.')' : ''),
                $values[0]
            ));
        }

        $normalizedValue = $normalizer($values[0]);

        if ($key === 'disable-tls') {
            if (!$normalizedValue && $this->config->get('disable-tls')) {
                $this->getIO()->writeError('<info>You are now running Composer with SSL/TLS protection enabled.</info>');
            } elseif ($normalizedValue && !$this->config->get('disable-tls')) {
                $this->getIO()->writeError('<warning>You are now running Composer with SSL/TLS protection disabled.</warning>');
            }
        }

        call_user_func([$this->configSource, $method], $key, $normalizedValue);
    }

    /**
     * @param array{callable, callable} $callbacks Validator and normalizer callbacks
     * @param array<string> $values
     */
    protected function handleMultiValue(string $key, array $callbacks, array $values, string $method): void
    {
        [$validator, $normalizer] = $callbacks;
        if (true !== $validation = $validator($values)) {
            throw new \RuntimeException(sprintf(
                '%s is an invalid value'.($validation ? ' ('.$validation.')' : ''),
                json_encode($values)
            ));
        }

        call_user_func([$this->configSource, $method], $key, $normalizer($values));
    }

    /**
     * Display the contents of the file in a pretty formatted way
     *
     * @param array<mixed[]|bool|string> $contents
     * @param array<mixed[]|string>      $rawContents
     */
    protected function listConfiguration(array $contents, array $rawContents, OutputInterface $output, ?string $k = null, bool $showSource = false): void
    {
        $origK = $k;
        $io = $this->getIO();
        foreach ($contents as $key => $value) {
            if ($k === null && !in_array($key, ['config', 'repositories'])) {
                continue;
            }

            $rawVal = $rawContents[$key] ?? null;

            if (is_array($value) && (!is_numeric(key($value)) || ($key === 'repositories' && null === $k))) {
                $k .= Preg::replace('{^config\.}', '', $key . '.');
                $this->listConfiguration($value, $rawVal, $output, $k, $showSource);
                $k = $origK;

                continue;
            }

            if (is_array($value)) {
                $value = array_map(static function ($val) {
                    return is_array($val) ? json_encode($val) : $val;
                }, $value);

                $value = '['.implode(', ', $value).']';
            }

            if (is_bool($value)) {
                $value = var_export($value, true);
            }

            $source = '';
            if ($showSource) {
                $source = ' (' . $this->config->getSourceOfValue($k . $key) . ')';
            }

            if (null !== $k && 0 === strpos($k, 'repositories')) {
                $link = 'https://getcomposer.org/doc/05-repositories.md';
            } else {
                $id = Preg::replace('{\..*$}', '', $k === '' || $k === null ? (string) $key : $k);
                $id = Preg::replace('{[^a-z0-9]}i', '-', strtolower(trim($id)));
                $id = Preg::replace('{-+}', '-', $id);
                $link = 'https://getcomposer.org/doc/06-config.md#' . $id;
            }
            if (is_string($rawVal) && $rawVal !== $value) {
                $io->write('[<fg=yellow;href=' . $link .'>' . $k . $key . '</>] <info>' . $rawVal . ' (' . $value . ')</info>' . $source, true, IOInterface::QUIET);
            } else {
                $io->write('[<fg=yellow;href=' . $link .'>' . $k . $key . '</>] <info>' . $value . '</info>' . $source, true, IOInterface::QUIET);
            }
        }
    }

    /**
     * Get the local composer.json, global config.json, or the file passed by the user
     */
    private function getComposerConfigFile(InputInterface $input, Config $config): string
    {
        return $input->getOption('global')
            ? ($config->get('home') . '/config.json')
            : ($input->getOption('file') ?: Factory::getComposerFile())
        ;
    }

    /**
     * Get the local auth.json or global auth.json, or if the user passed in a file to use,
     * the corresponding auth.json
     */
    private function getAuthConfigFile(InputInterface $input, Config $config): string
    {
        return $input->getOption('global')
            ? ($config->get('home') . '/auth.json')
            : dirname($this->getComposerConfigFile($input, $config)) . '/auth.json'
        ;
    }

    /**
     * Suggest setting-keys, while taking given options in account.
     */
    private function suggestSettingKeys(): \Closure
    {
        return function (CompletionInput $input): array {
            if ($input->getOption('list') || $input->getOption('editor') || $input->getOption('auth')) {
                return [];
            }

            // initialize configuration
            $config = Factory::createConfig();

            // load configuration
            $configFile = new JsonFile($this->getComposerConfigFile($input, $config));
            if ($configFile->exists()) {
                $config->merge($configFile->read(), $configFile->getPath());
            }

            // load auth-configuration
            $authConfigFile = new JsonFile($this->getAuthConfigFile($input, $config));
            if ($authConfigFile->exists()) {
                $config->merge(['config' => $authConfigFile->read()], $authConfigFile->getPath());
            }

            // collect all configuration setting-keys
            $rawConfig = $config->raw();
            $keys = array_merge(
                $this->flattenSettingKeys($rawConfig['config']),
                $this->flattenSettingKeys($rawConfig['repositories'], 'repositories.')
            );

            // if unsetting …
            if ($input->getOption('unset')) {
                // … keep only the currently customized setting-keys …
                $sources = [$configFile->getPath(), $authConfigFile->getPath()];
                $keys = array_filter(
                    $keys,
                    static function (string $key) use ($config, $sources): bool {
                        return in_array($config->getSourceOfValue($key), $sources, true);
                    }
                );

            // … else if showing or setting a value …
            } else {
                // … add all configurable package-properties, no matter if it exist
                $keys = array_merge($keys, self::CONFIGURABLE_PACKAGE_PROPERTIES);

                // it would be nice to distinguish between showing and setting
                // a value, but that makes the implementation much more complex
                // and partially impossible because symfony's implementation
                // does not complete arguments followed by other arguments
            }

            // add all existing configurable package-properties
            if ($configFile->exists()) {
                $properties = array_filter(
                    $configFile->read(),
                    static function (string $key): bool {
                        return in_array($key, self::CONFIGURABLE_PACKAGE_PROPERTIES, true);
                    },
                    ARRAY_FILTER_USE_KEY
                );

                $keys = array_merge(
                    $keys,
                    $this->flattenSettingKeys($properties)
                );
            }

            // filter settings-keys by completion value
            $completionValue = $input->getCompletionValue();

            if ($completionValue !== '') {
                $keys = array_filter(
                    $keys,
                    static function (string $key) use ($completionValue): bool {
                        return str_starts_with($key, $completionValue);
                    }
                );
            }

            sort($keys);

            return array_unique($keys);
        };
    }

    /**
     * build a flat list of dot-separated setting-keys from given config
     *
     * @param array<mixed[]|string>  $config
     * @return string[]
     */
    private function flattenSettingKeys(array $config, string $prefix = ''): array
    {
        $keys = [];
        foreach ($config as $key => $value) {
            $keys[] = [$prefix . $key];
            // array-lists must not be added to completion
            // sub-keys of repository-keys must not be added to completion
            if (is_array($value) && !array_is_list($value) && $prefix !== 'repositories.') {
                $keys[] = $this->flattenSettingKeys($value, $prefix . $key . '.');
            }
        }

        return array_merge(...$keys);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\IO\IOInterface;
use Composer\Package\AliasPackage;
use Composer\Package\BasePackage;
use Composer\Package\Locker;
use Composer\Package\Version\VersionBumper;
use Composer\Pcre\Preg;
use Composer\Util\Filesystem;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputArgument;
use Composer\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Factory;
use Composer\Json\JsonFile;
use Composer\Json\JsonManipulator;
use Composer\Repository\PlatformRepository;
use Composer\Util\Silencer;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
final class BumpCommand extends BaseCommand
{
    private const ERROR_GENERIC = 1;
    private const ERROR_LOCK_OUTDATED = 2;

    use CompletionTrait;

    protected function configure(): void
    {
        $this
            ->setName('bump')
            ->setDescription('Increases the lower limit of your composer.json requirements to the currently installed versions')
            ->setDefinition([
                new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Optional package name(s) to restrict which packages are bumped.', null, $this->suggestRootRequirement()),
                new InputOption('dev-only', 'D', InputOption::VALUE_NONE, 'Only bump requirements in "require-dev".'),
                new InputOption('no-dev-only', 'R', InputOption::VALUE_NONE, 'Only bump requirements in "require".'),
                new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the packages to bump, but will not execute anything.'),
            ])
            ->setHelp(
                <<<EOT
The <info>bump</info> command increases the lower limit of your composer.json requirements
to the currently installed versions. This helps to ensure your dependencies do not
accidentally get downgraded due to some other conflict, and can slightly improve
dependency resolution performance as it limits the amount of package versions
Composer has to look at.

Running this blindly on libraries is **NOT** recommended as it will narrow down
your allowed dependencies, which may cause dependency hell for your users.
Running it with <info>--dev-only</info> on libraries may be fine however as dev requirements
are local to the library and do not affect consumers of the package.

EOT
            )
        ;
    }

    /**
     * @throws \Seld\JsonLint\ParsingException
     */
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        return $this->doBump(
            $this->getIO(),
            $input->getOption('dev-only'),
            $input->getOption('no-dev-only'),
            $input->getOption('dry-run'),
            $input->getArgument('packages')
        );
    }

    /**
     * @param string[] $packagesFilter
     * @throws \Seld\JsonLint\ParsingException
     */
    public function doBump(
        IOInterface $io,
        bool $devOnly,
        bool $noDevOnly,
        bool $dryRun,
        array $packagesFilter
    ): int {
        /** @readonly */
        $composerJsonPath = Factory::getComposerFile();

        if (!Filesystem::isReadable($composerJsonPath)) {
            $io->writeError('<error>'.$composerJsonPath.' is not readable.</error>');

            return self::ERROR_GENERIC;
        }

        $composerJson = new JsonFile($composerJsonPath);
        $contents = file_get_contents($composerJson->getPath());
        if (false === $contents) {
            $io->writeError('<error>'.$composerJsonPath.' is not readable.</error>');

            return self::ERROR_GENERIC;
        }

        // check for writability by writing to the file as is_writable can not be trusted on network-mounts
        // see https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926
        if (!is_writable($composerJsonPath) && false === Silencer::call('file_put_contents', $composerJsonPath, $contents)) {
            $io->writeError('<error>'.$composerJsonPath.' is not writable.</error>');

            return self::ERROR_GENERIC;
        }
        unset($contents);

        $composer = $this->requireComposer();
        if ($composer->getLocker()->isLocked()) {
            if (!$composer->getLocker()->isFresh()) {
                $io->writeError('<error>The lock file is not up to date with the latest changes in composer.json. Run the appropriate `update` to fix that before you use the `bump` command.</error>');

                return self::ERROR_LOCK_OUTDATED;
            }

            $repo = $composer->getLocker()->getLockedRepository(true);
        } else {
            $repo = $composer->getRepositoryManager()->getLocalRepository();
        }

        if ($composer->getPackage()->getType() !== 'project' && !$devOnly) {
            $io->writeError('<warning>Warning: Bumping dependency constraints is not recommended for libraries as it will narrow down your dependencies and may cause problems for your users.</warning>');

            $contents = $composerJson->read();
            if (!isset($contents['type'])) {
                $io->writeError('<warning>If your package is not a library, you can explicitly specify the "type" by using "composer config type project".</warning>');
                $io->writeError('<warning>Alternatively you can use --dev-only to only bump dependencies within "require-dev".</warning>');
            }
            unset($contents);
        }

        $bumper = new VersionBumper();
        $tasks = [];
        if (!$devOnly) {
            $tasks['require'] = $composer->getPackage()->getRequires();
        }
        if (!$noDevOnly) {
            $tasks['require-dev'] = $composer->getPackage()->getDevRequires();
        }

        if (count($packagesFilter) > 0) {
            // support proxied args from the update command that contain constraints together with the package names
            $packagesFilter = array_map(function ($constraint) {
                return Preg::replace('{[:= ].+}', '', $constraint);
            }, $packagesFilter);
            $pattern = BasePackage::packageNamesToRegexp(array_unique(array_map('strtolower', $packagesFilter)));
            foreach ($tasks as $key => $reqs) {
                foreach ($reqs as $pkgName => $link) {
                    if (!Preg::isMatch($pattern, $pkgName)) {
                        unset($tasks[$key][$pkgName]);
                    }
                }
            }
        }

        $updates = [];
        foreach ($tasks as $key => $reqs) {
            foreach ($reqs as $pkgName => $link) {
                if (PlatformRepository::isPlatformPackage($pkgName)) {
                    continue;
                }
                $currentConstraint = $link->getPrettyConstraint();

                $package = $repo->findPackage($pkgName, '*');
                // name must be provided or replaced
                if (null === $package) {
                    continue;
                }
                while ($package instanceof AliasPackage) {
                    $package = $package->getAliasOf();
                }

                $bumped = $bumper->bumpRequirement($link->getConstraint(), $package);

                if ($bumped === $currentConstraint) {
                    continue;
                }

                $updates[$key][$pkgName] = $bumped;
            }
        }

        if (!$dryRun && !$this->updateFileCleanly($composerJson, $updates)) {
            $composerDefinition = $composerJson->read();
            foreach ($updates as $key => $packages) {
                foreach ($packages as $package => $version) {
                    $composerDefinition[$key][$package] = $version;
                }
            }
            $composerJson->write($composerDefinition);
        }

        $changeCount = array_sum(array_map('count', $updates));
        if ($changeCount > 0) {
            if ($dryRun) {
                $io->write('<info>' . $composerJsonPath . ' would be updated with:</info>');
                foreach ($updates as $requireType => $packages) {
                    foreach ($packages as $package => $version) {
                        $io->write(sprintf('<info> - %s.%s: %s</info>', $requireType, $package, $version));
                    }
                }
            } else {
                $io->write('<info>' . $composerJsonPath . ' has been updated (' . $changeCount . ' changes).</info>');
            }
        } else {
            $io->write('<info>No requirements to update in '.$composerJsonPath.'.</info>');
        }

        if (!$dryRun && $composer->getLocker()->isLocked() && $composer->getConfig()->get('lock') && $changeCount > 0) {
            $composer->getLocker()->updateHash($composerJson);
        }

        if ($dryRun && $changeCount > 0) {
            return self::ERROR_GENERIC;
        }

        return 0;
    }

    /**
     * @param array<'require'|'require-dev', array<string, string>> $updates
     */
    private function updateFileCleanly(JsonFile $json, array $updates): bool
    {
        $contents = file_get_contents($json->getPath());
        if (false === $contents) {
            throw new \RuntimeException('Unable to read '.$json->getPath().' contents.');
        }

        $manipulator = new JsonManipulator($contents);

        foreach ($updates as $key => $packages) {
            foreach ($packages as $package => $version) {
                if (!$manipulator->addLink($key, $package, $version)) {
                    return false;
                }
            }
        }

        if (false === file_put_contents($json->getPath(), $manipulator->getContents())) {
            throw new \RuntimeException('Unable to write new '.$json->getPath().' contents.');
        }

        return true;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Composer;
use Composer\DependencyResolver\Request;
use Composer\Installer;
use Composer\IO\IOInterface;
use Composer\Package\BasePackage;
use Composer\Package\Loader\RootPackageLoader;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionSelector;
use Composer\Pcre\Preg;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Package\Version\VersionParser;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\RepositorySet;
use Composer\Semver\Constraint\MultiConstraint;
use Composer\Semver\Intervals;
use Composer\Util\HttpDownloader;
use Composer\Advisory\Auditor;
use Composer\Util\Platform;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Composer\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Nils Adermann <naderman@naderman.de>
 */
class UpdateCommand extends BaseCommand
{
    use CompletionTrait;

    /**
     * @return void
     */
    protected function configure()
    {
        $this
            ->setName('update')
            ->setAliases(['u', 'upgrade'])
            ->setDescription('Updates your dependencies to the latest version according to composer.json, and updates the composer.lock file')
            ->setDefinition([
                new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Packages that should be updated, if not provided all packages are.', null, $this->suggestInstalledPackage(false)),
                new InputOption('with', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Temporary version constraint to add, e.g. foo/bar:1.0.0 or foo/bar=1.0.0'),
                new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
                new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'),
                new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()),
                new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'),
                new InputOption('dev', null, InputOption::VALUE_NONE, 'DEPRECATED: Enables installation of require-dev packages (enabled by default, only present for BC).'),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'),
                new InputOption('lock', null, InputOption::VALUE_NONE, 'Overwrites the lock file hash to suppress warning about the lock file being out of date without updating package versions. Package metadata like mirrors and URLs are updated if they changed.'),
                new InputOption('no-install', null, InputOption::VALUE_NONE, 'Skip the install step after updating the composer.lock file.'),
                new InputOption('no-audit', null, InputOption::VALUE_NONE, 'Skip the audit step after updating the composer.lock file (can also be set via the COMPOSER_NO_AUDIT=1 env var).'),
                new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS),
                new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'),
                new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'DEPRECATED: This flag does not exist anymore.'),
                new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                new InputOption('with-dependencies', 'w', InputOption::VALUE_NONE, 'Update also dependencies of packages in the argument list, except those which are root requirements (can also be set via the COMPOSER_WITH_DEPENDENCIES=1 env var).'),
                new InputOption('with-all-dependencies', 'W', InputOption::VALUE_NONE, 'Update also dependencies of packages in the argument list, including those which are root requirements (can also be set via the COMPOSER_WITH_ALL_DEPENDENCIES=1 env var).'),
                new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'),
                new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump.'),
                new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'),
                new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'),
                new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'),
                new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'),
                new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'),
                new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies (can also be set via the COMPOSER_PREFER_STABLE=1 env var).'),
                new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies (can also be set via the COMPOSER_PREFER_LOWEST=1 env var).'),
                new InputOption('minimal-changes', 'm', InputOption::VALUE_NONE, 'During a partial update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).'),
                new InputOption('patch-only', null, InputOption::VALUE_NONE, 'Only allow patch version updates for currently installed dependencies.'),
                new InputOption('interactive', 'i', InputOption::VALUE_NONE, 'Interactive interface with autocompletion to select the packages to update.'),
                new InputOption('root-reqs', null, InputOption::VALUE_NONE, 'Restricts the update to your first degree dependencies.'),
                new InputOption('bump-after-update', null, InputOption::VALUE_OPTIONAL, 'Runs bump after performing the update.', false, ['dev', 'no-dev', 'all']),
            ])
            ->setHelp(
                <<<EOT
The <info>update</info> command reads the composer.json file from the
current directory, processes it, and updates, removes or installs all the
dependencies.

<info>php composer.phar update</info>

To limit the update operation to a few packages, you can list the package(s)
you want to update as such:

<info>php composer.phar update vendor/package1 foo/mypackage [...]</info>

You may also use an asterisk (*) pattern to limit the update operation to package(s)
from a specific vendor:

<info>php composer.phar update vendor/package1 foo/* [...]</info>

To run an update with more restrictive constraints you can use:

<info>php composer.phar update --with vendor/package:1.0.*</info>

To run a partial update with more restrictive constraints you can use the shorthand:

<info>php composer.phar update vendor/package:1.0.*</info>

To select packages names interactively with auto-completion use <info>-i</info>.

Read more at https://getcomposer.org/doc/03-cli.md#update-u-upgrade
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = $this->getIO();
        if ($input->getOption('dev')) {
            $io->writeError('<warning>You are using the deprecated option "--dev". It has no effect and will break in Composer 3.</warning>');
        }
        if ($input->getOption('no-suggest')) {
            $io->writeError('<warning>You are using the deprecated option "--no-suggest". It has no effect and will break in Composer 3.</warning>');
        }

        $composer = $this->requireComposer();

        if (!HttpDownloader::isCurlEnabled()) {
            $io->writeError('<warning>Composer is operating significantly slower than normal because you do not have the PHP curl extension enabled.</warning>');
        }

        $packages = $input->getArgument('packages');
        $reqs = $this->formatRequirements($input->getOption('with'));

        // extract --with shorthands from the allowlist
        if (count($packages) > 0) {
            $allowlistPackagesWithRequirements = array_filter($packages, static function ($pkg): bool {
                return Preg::isMatch('{\S+[ =:]\S+}', $pkg);
            });
            foreach ($this->formatRequirements($allowlistPackagesWithRequirements) as $package => $constraint) {
                $reqs[$package] = $constraint;
            }

            // replace the foo/bar:req by foo/bar in the allowlist
            foreach ($allowlistPackagesWithRequirements as $package) {
                $packageName = Preg::replace('{^([^ =:]+)[ =:].*$}', '$1', $package);
                $index = array_search($package, $packages);
                $packages[$index] = $packageName;
            }
        }

        $rootPackage = $composer->getPackage();
        $rootPackage->setReferences(RootPackageLoader::extractReferences($reqs, $rootPackage->getReferences()));
        $rootPackage->setStabilityFlags(RootPackageLoader::extractStabilityFlags($reqs, $rootPackage->getMinimumStability(), $rootPackage->getStabilityFlags()));

        $parser = new VersionParser;
        $temporaryConstraints = [];
        $rootRequirements = array_merge($rootPackage->getRequires(), $rootPackage->getDevRequires());
        foreach ($reqs as $package => $constraint) {
            $package = strtolower($package);
            $parsedConstraint = $parser->parseConstraints($constraint);
            $temporaryConstraints[$package] = $parsedConstraint;
            if (isset($rootRequirements[$package]) && !Intervals::haveIntersections($parsedConstraint, $rootRequirements[$package]->getConstraint())) {
                $io->writeError('<error>The temporary constraint "'.$constraint.'" for "'.$package.'" must be a subset of the constraint in your composer.json ('.$rootRequirements[$package]->getPrettyConstraint().')</error>');
                $io->write('<info>Run `composer require '.$package.'` or `composer require '.$package.':'.$constraint.'` instead to replace the constraint</info>');
                return self::FAILURE;
            }
        }

        if ($input->getOption('patch-only')) {
            if (!$composer->getLocker()->isLocked()) {
                throw new \InvalidArgumentException('patch-only can only be used with a lock file present');
            }
            foreach ($composer->getLocker()->getLockedRepository(true)->getCanonicalPackages() as $package) {
                if ($package->isDev()) {
                    continue;
                }
                if (!Preg::isMatch('{^(\d+\.\d+\.\d+)}', $package->getVersion(), $match)) {
                    continue;
                }
                $constraint = $parser->parseConstraints('~'.$match[1]);
                if (isset($temporaryConstraints[$package->getName()])) {
                    $temporaryConstraints[$package->getName()] = MultiConstraint::create([$temporaryConstraints[$package->getName()], $constraint], true);
                } else {
                    $temporaryConstraints[$package->getName()] = $constraint;
                }
            }
        }

        if ($input->getOption('interactive')) {
            $packages = $this->getPackagesInteractively($io, $input, $output, $composer, $packages);
        }

        if ($input->getOption('root-reqs')) {
            $requires = array_keys($rootPackage->getRequires());
            if (!$input->getOption('no-dev')) {
                $requires = array_merge($requires, array_keys($rootPackage->getDevRequires()));
            }

            if (!empty($packages)) {
                $packages = array_intersect($packages, $requires);
            } else {
                $packages = $requires;
            }
        }

        // the arguments lock/nothing/mirrors are not package names but trigger a mirror update instead
        // they are further mutually exclusive with listing actual package names
        $filteredPackages = array_filter($packages, static function ($package): bool {
            return !in_array($package, ['lock', 'nothing', 'mirrors'], true);
        });
        $updateMirrors = $input->getOption('lock') || count($filteredPackages) !== count($packages);
        $packages = $filteredPackages;

        if ($updateMirrors && !empty($packages)) {
            $io->writeError('<error>You cannot simultaneously update only a selection of packages and regenerate the lock file metadata.</error>');

            return -1;
        }

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'update', $input, $output);
        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        $composer->getInstallationManager()->setOutputProgress(!$input->getOption('no-progress'));

        $install = Installer::create($io, $composer);

        $config = $composer->getConfig();
        [$preferSource, $preferDist] = $this->getPreferredInstallOptions($config, $input);

        $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader');
        $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative');
        $apcuPrefix = $input->getOption('apcu-autoloader-prefix');
        $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader');

        $updateAllowTransitiveDependencies = Request::UPDATE_ONLY_LISTED;
        if ($input->getOption('with-all-dependencies')) {
            $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS;
        } elseif ($input->getOption('with-dependencies')) {
            $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE;
        }

        $install
            ->setDryRun($input->getOption('dry-run'))
            ->setVerbose($input->getOption('verbose'))
            ->setPreferSource($preferSource)
            ->setPreferDist($preferDist)
            ->setDevMode(!$input->getOption('no-dev'))
            ->setDumpAutoloader(!$input->getOption('no-autoloader'))
            ->setOptimizeAutoloader($optimize)
            ->setClassMapAuthoritative($authoritative)
            ->setApcuAutoloader($apcu, $apcuPrefix)
            ->setUpdate(true)
            ->setInstall(!$input->getOption('no-install'))
            ->setUpdateMirrors($updateMirrors)
            ->setUpdateAllowList($packages)
            ->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies)
            ->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input))
            ->setPreferStable($input->getOption('prefer-stable'))
            ->setPreferLowest($input->getOption('prefer-lowest'))
            ->setTemporaryConstraints($temporaryConstraints)
            ->setAudit(!$input->getOption('no-audit'))
            ->setAuditFormat($this->getAuditFormat($input))
            ->setMinimalUpdate($input->getOption('minimal-changes'))
        ;

        if ($input->getOption('no-plugins')) {
            $install->disablePlugins();
        }

        $result = $install->run();

        if ($result === 0 && !$input->getOption('lock')) {
            $bumpAfterUpdate = $input->getOption('bump-after-update');
            if (false === $bumpAfterUpdate) {
                $bumpAfterUpdate = $composer->getConfig()->get('bump-after-update');
            }

            if (false !== $bumpAfterUpdate) {
                $io->writeError('<info>Bumping dependencies</info>');
                $bumpCommand = new BumpCommand();
                $bumpCommand->setComposer($composer);
                $result = $bumpCommand->doBump(
                    $io,
                    $bumpAfterUpdate === 'dev',
                    $bumpAfterUpdate === 'no-dev',
                    $input->getOption('dry-run'),
                    $input->getArgument('packages')
                );
            }
        }
        return $result;
    }

    /**
     * @param array<string> $packages
     * @return array<string>
     */
    private function getPackagesInteractively(IOInterface $io, InputInterface $input, OutputInterface $output, Composer $composer, array $packages): array
    {
        if (!$input->isInteractive()) {
            throw new \InvalidArgumentException('--interactive cannot be used in non-interactive terminals.');
        }

        $platformReqFilter = $this->getPlatformRequirementFilter($input);
        $stabilityFlags = $composer->getPackage()->getStabilityFlags();
        $requires = array_merge(
            $composer->getPackage()->getRequires(),
            $composer->getPackage()->getDevRequires()
        );

        $filter = \count($packages) > 0 ? BasePackage::packageNamesToRegexp($packages) : null;

        $io->writeError('<info>Loading packages that can be updated...</info>');
        $autocompleterValues = [];
        $installedPackages = $composer->getLocker()->isLocked() ? $composer->getLocker()->getLockedRepository(true)->getPackages() : $composer->getRepositoryManager()->getLocalRepository()->getPackages();
        $versionSelector = $this->createVersionSelector($composer);
        foreach ($installedPackages as $package) {
            if ($filter !== null && !Preg::isMatch($filter, $package->getName())) {
                continue;
            }
            $currentVersion = $package->getPrettyVersion();
            $constraint = isset($requires[$package->getName()]) ? $requires[$package->getName()]->getPrettyConstraint() : null;
            $stability = isset($stabilityFlags[$package->getName()]) ? (string) array_search($stabilityFlags[$package->getName()], BasePackage::STABILITIES, true) : $composer->getPackage()->getMinimumStability();
            $latestVersion = $versionSelector->findBestCandidate($package->getName(), $constraint, $stability, $platformReqFilter);
            if ($latestVersion !== false && ($package->getVersion() !== $latestVersion->getVersion() || $latestVersion->isDev())) {
                $autocompleterValues[$package->getName()] = '<comment>' . $currentVersion . '</comment> => <comment>' . $latestVersion->getPrettyVersion() . '</comment>';
            }
        }
        if (0 === \count($installedPackages)) {
            foreach ($requires as $req => $constraint) {
                if (PlatformRepository::isPlatformPackage($req)) {
                    continue;
                }
                $autocompleterValues[$req] = '';
            }
        }

        if (0 === \count($autocompleterValues)) {
            throw new \RuntimeException('Could not find any package with new versions available');
        }

        $packages = $io->select(
            'Select packages: (Select more than one value separated by comma) ',
            $autocompleterValues,
            false,
            1,
            'No package named "%s" is installed.',
            true
        );

        $table = new Table($output);
        $table->setHeaders(['Selected packages']);
        foreach ($packages as $package) {
            $table->addRow([$package]);
        }
        $table->render();

        if ($io->askConfirmation(sprintf(
            'Would you like to continue and update the above package%s [<comment>yes</comment>]? ',
            1 === count($packages) ? '' : 's'
        ))) {
            return $packages;
        }

        throw new \RuntimeException('Installation aborted.');
    }

    private function createVersionSelector(Composer $composer): VersionSelector
    {
        $repositorySet = new RepositorySet();
        $repositorySet->addRepository(new CompositeRepository(array_filter($composer->getRepositoryManager()->getRepositories(), function (RepositoryInterface $repository) {
            return !$repository instanceof PlatformRepository;
        })));

        return new VersionSelector($repositorySet);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Factory;
use Composer\Pcre\Preg;
use Composer\Util\Filesystem;
use Composer\Util\Platform;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputArgument;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class GlobalCommand extends BaseCommand
{
    public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
    {
        $application = $this->getApplication();
        if ($input->mustSuggestArgumentValuesFor('command-name')) {
            $suggestions->suggestValues(array_values(array_filter(
                array_map(static function (Command $command) {
                    return $command->isHidden() ? null : $command->getName();
                }, $application->all()), function (?string $cmd) {
                    return $cmd !== null;
                }
            )));

            return;
        }

        if ($application->has($commandName = $input->getArgument('command-name'))) {
            $input = $this->prepareSubcommandInput($input, true);
            $input = CompletionInput::fromString($input->__toString(), 2);
            $command = $application->find($commandName);
            $command->mergeApplicationDefinition();

            $input->bind($command->getDefinition());
            $command->complete($input, $suggestions);
        }
    }

    protected function configure(): void
    {
        $this
            ->setName('global')
            ->setDescription('Allows running commands in the global composer dir ($COMPOSER_HOME)')
            ->setDefinition([
                new InputArgument('command-name', InputArgument::REQUIRED, ''),
                new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''),
            ])
            ->setHelp(
                <<<EOT
Use this command as a wrapper to run other Composer commands
within the global context of COMPOSER_HOME.

You can use this to install CLI utilities globally, all you need
is to add the COMPOSER_HOME/vendor/bin dir to your PATH env var.

COMPOSER_HOME is c:\Users\<user>\AppData\Roaming\Composer on Windows
and /home/<user>/.composer on unix systems.

If your system uses freedesktop.org standards, then it will first check
XDG_CONFIG_HOME or default to /home/<user>/.config/composer

Note: This path may vary depending on customizations to bin-dir in
composer.json or the environmental variable COMPOSER_BIN_DIR.

Read more at https://getcomposer.org/doc/03-cli.md#global
EOT
            )
        ;
    }

    /**
     * @throws \Symfony\Component\Console\Exception\ExceptionInterface
     */
    public function run(InputInterface $input, OutputInterface $output): int
    {
        // TODO remove for Symfony 6+ as it is then in the interface
        if (!method_exists($input, '__toString')) { // @phpstan-ignore-line
            throw new \LogicException('Expected an Input instance that is stringable, got '.get_class($input));
        }

        // extract real command name
        $tokens = Preg::split('{\s+}', $input->__toString());
        $args = [];
        foreach ($tokens as $token) {
            if ($token && $token[0] !== '-') {
                $args[] = $token;
                if (count($args) >= 2) {
                    break;
                }
            }
        }

        // show help for this command if no command was found
        if (count($args) < 2) {
            return parent::run($input, $output);
        }

        $input = $this->prepareSubcommandInput($input);

        return $this->getApplication()->run($input, $output);
    }

    private function prepareSubcommandInput(InputInterface $input, bool $quiet = false): StringInput
    {
        // TODO remove for Symfony 6+ as it is then in the interface
        if (!method_exists($input, '__toString')) { // @phpstan-ignore-line
            throw new \LogicException('Expected an Input instance that is stringable, got '.get_class($input));
        }

        // The COMPOSER env var should not apply to the global execution scope
        if (Platform::getEnv('COMPOSER')) {
            Platform::clearEnv('COMPOSER');
        }

        // change to global dir
        $config = Factory::createConfig();
        $home = $config->get('home');

        if (!is_dir($home)) {
            $fs = new Filesystem();
            $fs->ensureDirectoryExists($home);
            if (!is_dir($home)) {
                throw new \RuntimeException('Could not create home directory');
            }
        }

        try {
            chdir($home);
        } catch (\Exception $e) {
            throw new \RuntimeException('Could not switch to home directory "'.$home.'"', 0, $e);
        }
        if (!$quiet) {
            $this->getIO()->writeError('<info>Changed current directory to '.$home.'</info>');
        }

        // create new input without "global" command prefix
        $input = new StringInput(Preg::replace('{\bg(?:l(?:o(?:b(?:a(?:l)?)?)?)?)?\b}', '', $input->__toString(), 1));
        $this->getApplication()->resetComposer();

        return $input;
    }

    /**
     * @inheritDoc
     */
    public function isProxyCommand(): bool
    {
        return true;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Composer;
use Composer\Repository\RepositorySet;
use Composer\Repository\RepositoryUtils;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Package\PackageInterface;
use Composer\Repository\InstalledRepository;
use Composer\Advisory\Auditor;
use Composer\Console\Input\InputOption;

class AuditCommand extends BaseCommand
{
    protected function configure(): void
    {
        $this
            ->setName('audit')
            ->setDescription('Checks for security vulnerability advisories for installed packages')
            ->setDefinition([
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables auditing of require-dev packages.'),
                new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_TABLE, Auditor::FORMATS),
                new InputOption('locked', null, InputOption::VALUE_NONE, 'Audit based on the lock file instead of the installed packages.'),
                new InputOption('abandoned', null, InputOption::VALUE_REQUIRED, 'Behavior on abandoned packages. Must be "ignore", "report", or "fail".', null, Auditor::ABANDONEDS),
                new InputOption('ignore-severity', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Ignore advisories of a certain severity level.', [], ['low', 'medium', 'high', 'critical']),
            ])
            ->setHelp(
                <<<EOT
The <info>audit</info> command checks for security vulnerability advisories for installed packages.

If you do not want to include dev dependencies in the audit you can omit them with --no-dev

Read more at https://getcomposer.org/doc/03-cli.md#audit
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $composer = $this->requireComposer();
        $packages = $this->getPackages($composer, $input);

        if (count($packages) === 0) {
            $this->getIO()->writeError('No packages - skipping audit.');

            return 0;
        }

        $auditor = new Auditor();
        $repoSet = new RepositorySet();
        foreach ($composer->getRepositoryManager()->getRepositories() as $repo) {
            $repoSet->addRepository($repo);
        }

        $auditConfig = $composer->getConfig()->get('audit');

        $abandoned = $input->getOption('abandoned');
        if ($abandoned !== null && !in_array($abandoned, Auditor::ABANDONEDS, true)) {
            throw new \InvalidArgumentException('--audit must be one of '.implode(', ', Auditor::ABANDONEDS).'.');
        }

        $abandoned = $abandoned ?? $auditConfig['abandoned'] ?? Auditor::ABANDONED_FAIL;

        $ignoreSeverities = $input->getOption('ignore-severity') ?? [];

        return min(255, $auditor->audit(
            $this->getIO(),
            $repoSet,
            $packages,
            $this->getAuditFormat($input, 'format'),
            false,
            $auditConfig['ignore'] ?? [],
            $abandoned,
            $ignoreSeverities
        ));

    }

    /**
     * @return PackageInterface[]
     */
    private function getPackages(Composer $composer, InputInterface $input): array
    {
        if ($input->getOption('locked')) {
            if (!$composer->getLocker()->isLocked()) {
                throw new \UnexpectedValueException('Valid composer.json and composer.lock files are required to run this command with --locked');
            }
            $locker = $composer->getLocker();

            return $locker->getLockedRepository(!$input->getOption('no-dev'))->getPackages();
        }

        $rootPkg = $composer->getPackage();
        $installedRepo = new InstalledRepository([$composer->getRepositoryManager()->getLocalRepository()]);

        if ($input->getOption('no-dev')) {
            return RepositoryUtils::filterRequiredPackages($installedRepo->getPackages(), $rootPkg);
        }

        return $installedRepo->getPackages();
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputArgument;
use Symfony\Component\Console\Input\ArrayInput;
use Composer\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class OutdatedCommand extends BaseCommand
{
    use CompletionTrait;

    protected function configure(): void
    {
        $this
            ->setName('outdated')
            ->setDescription('Shows a list of installed packages that have updates available, including their latest version')
            ->setDefinition([
                new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect. Or a name including a wildcard (*) to filter lists of packages instead.', null, $this->suggestInstalledPackage(false)),
                new InputOption('outdated', 'o', InputOption::VALUE_NONE, 'Show only packages that are outdated (this is the default, but present here for compat with `show`'),
                new InputOption('all', 'a', InputOption::VALUE_NONE, 'Show all installed packages with their latest versions'),
                new InputOption('locked', null, InputOption::VALUE_NONE, 'Shows updates for packages from the lock file, regardless of what is currently in vendor dir'),
                new InputOption('direct', 'D', InputOption::VALUE_NONE, 'Shows only packages that are directly required by the root package'),
                new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'),
                new InputOption('major-only', 'M', InputOption::VALUE_NONE, 'Show only packages that have major SemVer-compatible updates.'),
                new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates.'),
                new InputOption('patch-only', 'p', InputOption::VALUE_NONE, 'Show only packages that have patch SemVer-compatible updates.'),
                new InputOption('sort-by-age', 'A', InputOption::VALUE_NONE, 'Displays the installed version\'s age, and sorts packages oldest first.'),
                new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']),
                new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Can contain wildcards (*). Use it if you don\'t want to be informed about new versions of some packages.', null, $this->suggestInstalledPackage(false)),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'),
                new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages). Use with the --outdated option'),
                new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages). Use with the --outdated option'),
            ])
            ->setHelp(
                <<<EOT
The outdated command is just a proxy for `composer show -l`

The color coding (or signage if you have ANSI colors disabled) for dependency versions is as such:

- <info>green</info> (=): Dependency is in the latest version and is up to date.
- <comment>yellow</comment> (~): Dependency has a new version available that includes backwards
  compatibility breaks according to semver, so upgrade when you can but it
  may involve work.
- <highlight>red</highlight> (!): Dependency has a new version that is semver-compatible and you should upgrade it.

Read more at https://getcomposer.org/doc/03-cli.md#outdated
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $args = [
            'command' => 'show',
            '--latest' => true,
        ];
        if ($input->getOption('no-interaction')) {
            $args['--no-interaction'] = true;
        }
        if ($input->getOption('no-plugins')) {
            $args['--no-plugins'] = true;
        }
        if ($input->getOption('no-scripts')) {
            $args['--no-scripts'] = true;
        }
        if ($input->getOption('no-cache')) {
            $args['--no-cache'] = true;
        }
        if (!$input->getOption('all')) {
            $args['--outdated'] = true;
        }
        if ($input->getOption('direct')) {
            $args['--direct'] = true;
        }
        if (null !== $input->getArgument('package')) {
            $args['package'] = $input->getArgument('package');
        }
        if ($input->getOption('strict')) {
            $args['--strict'] = true;
        }
        if ($input->getOption('major-only')) {
            $args['--major-only'] = true;
        }
        if ($input->getOption('minor-only')) {
            $args['--minor-only'] = true;
        }
        if ($input->getOption('patch-only')) {
            $args['--patch-only'] = true;
        }
        if ($input->getOption('locked')) {
            $args['--locked'] = true;
        }
        if ($input->getOption('no-dev')) {
            $args['--no-dev'] = true;
        }
        if ($input->getOption('sort-by-age')) {
            $args['--sort-by-age'] = true;
        }
        $args['--ignore-platform-req'] = $input->getOption('ignore-platform-req');
        if ($input->getOption('ignore-platform-reqs')) {
            $args['--ignore-platform-reqs'] = true;
        }
        $args['--format'] = $input->getOption('format');
        $args['--ignore'] = $input->getOption('ignore');

        $input = new ArrayInput($args);

        return $this->getApplication()->run($input, $output);
    }

    /**
     * @inheritDoc
     */
    public function isProxyCommand(): bool
    {
        return true;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Script\Event as ScriptEvent;
use Composer\Script\ScriptEvents;
use Composer\Util\ProcessExecutor;
use Composer\Util\Platform;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Composer\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Fabien Potencier <fabien.potencier@gmail.com>
 */
class RunScriptCommand extends BaseCommand
{
    /**
     * @var string[] Array with command events
     */
    protected $scriptEvents = [
        ScriptEvents::PRE_INSTALL_CMD,
        ScriptEvents::POST_INSTALL_CMD,
        ScriptEvents::PRE_UPDATE_CMD,
        ScriptEvents::POST_UPDATE_CMD,
        ScriptEvents::PRE_STATUS_CMD,
        ScriptEvents::POST_STATUS_CMD,
        ScriptEvents::POST_ROOT_PACKAGE_INSTALL,
        ScriptEvents::POST_CREATE_PROJECT_CMD,
        ScriptEvents::PRE_ARCHIVE_CMD,
        ScriptEvents::POST_ARCHIVE_CMD,
        ScriptEvents::PRE_AUTOLOAD_DUMP,
        ScriptEvents::POST_AUTOLOAD_DUMP,
    ];

    protected function configure(): void
    {
        $this
            ->setName('run-script')
            ->setAliases(['run'])
            ->setDescription('Runs the scripts defined in composer.json')
            ->setDefinition([
                new InputArgument('script', InputArgument::OPTIONAL, 'Script name to run.', null, function () {
                    return array_map(static function ($script) { return $script['name']; }, $this->getScripts());
                }),
                new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''),
                new InputOption('timeout', null, InputOption::VALUE_REQUIRED, 'Sets script timeout in seconds, or 0 for never.'),
                new InputOption('dev', null, InputOption::VALUE_NONE, 'Sets the dev mode.'),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables the dev mode.'),
                new InputOption('list', 'l', InputOption::VALUE_NONE, 'List scripts.'),
            ])
            ->setHelp(
                <<<EOT
The <info>run-script</info> command runs scripts defined in composer.json:

<info>php composer.phar run-script post-update-cmd</info>

Read more at https://getcomposer.org/doc/03-cli.md#run-script-run
EOT
            )
        ;
    }

    protected function interact(InputInterface $input, OutputInterface $output): void
    {
        $scripts = $this->getScripts();
        if (count($scripts) === 0) {
            return;
        }

        if ($input->getArgument('script') !== null || $input->getOption('list')) {
            return;
        }

        $options = [];
        foreach ($scripts as $script) {
            $options[$script['name']] = $script['description'];
        }
        $io = $this->getIO();
        $script = $io->select(
            'Script to run: ',
            $options,
            '',
            1,
            'Invalid script name "%s"'
        );

        $input->setArgument('script', $script);
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        if ($input->getOption('list')) {
            return $this->listScripts($output);
        }

        $script = $input->getArgument('script');
        if ($script === null) {
            throw new \RuntimeException('Missing required argument "script"');
        }

        if (!in_array($script, $this->scriptEvents)) {
            if (defined('Composer\Script\ScriptEvents::'.str_replace('-', '_', strtoupper($script)))) {
                throw new \InvalidArgumentException(sprintf('Script "%s" cannot be run with this command', $script));
            }
        }

        $composer = $this->requireComposer();
        $devMode = $input->getOption('dev') || !$input->getOption('no-dev');
        $event = new ScriptEvent($script, $composer, $this->getIO(), $devMode);
        $hasListeners = $composer->getEventDispatcher()->hasEventListeners($event);
        if (!$hasListeners) {
            throw new \InvalidArgumentException(sprintf('Script "%s" is not defined in this package', $script));
        }

        $args = $input->getArgument('args');

        if (null !== $timeout = $input->getOption('timeout')) {
            if (!ctype_digit($timeout)) {
                throw new \RuntimeException('Timeout value must be numeric and positive if defined, or 0 for forever');
            }
            // Override global timeout set before in Composer by environment or config
            ProcessExecutor::setTimeout((int) $timeout);
        }

        Platform::putEnv('COMPOSER_DEV_MODE', $devMode ? '1' : '0');

        return $composer->getEventDispatcher()->dispatchScript($script, $devMode, $args);
    }

    protected function listScripts(OutputInterface $output): int
    {
        $scripts = $this->getScripts();
        if (count($scripts) === 0) {
            return 0;
        }

        $io = $this->getIO();
        $io->writeError('<info>scripts:</info>');
        $table = [];
        foreach ($scripts as $script) {
            $table[] = ['  '.$script['name'], $script['description']];
        }

        $this->renderTable($table, $output);

        return 0;
    }

    /**
     * @return list<array{name: string, description: string}>
     */
    private function getScripts(): array
    {
        $scripts = $this->requireComposer()->getPackage()->getScripts();
        if (count($scripts) === 0) {
            return [];
        }

        $result = [];
        foreach ($scripts as $name => $script) {
            $description = '';
            try {
                $cmd = $this->getApplication()->find($name);
                if ($cmd instanceof ScriptAliasCommand) {
                    $description = $cmd->getDescription();
                }
            } catch (\Symfony\Component\Console\Exception\CommandNotFoundException $e) {
                // ignore scripts that have no command associated, like native Composer script listeners
            }
            $result[] = ['name' => $name, 'description' => $description];
        }

        return $result;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Downloader\ChangeReportInterface;
use Composer\Downloader\DvcsDownloaderInterface;
use Composer\Downloader\VcsCapableDownloaderInterface;
use Composer\Package\Dumper\ArrayDumper;
use Composer\Package\Version\VersionGuesser;
use Composer\Package\Version\VersionParser;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Script\ScriptEvents;
use Composer\Util\ProcessExecutor;

/**
 * @author Tiago Ribeiro <tiago.ribeiro@seegno.com>
 * @author Rui Marinho <rui.marinho@seegno.com>
 */
class StatusCommand extends BaseCommand
{
    private const EXIT_CODE_ERRORS = 1;
    private const EXIT_CODE_UNPUSHED_CHANGES = 2;
    private const EXIT_CODE_VERSION_CHANGES = 4;

    /**
     * @throws \Symfony\Component\Console\Exception\InvalidArgumentException
     */
    protected function configure(): void
    {
        $this
            ->setName('status')
            ->setDescription('Shows a list of locally modified packages')
            ->setDefinition([
                new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Show modified files for each directory that contains changes.'),
            ])
            ->setHelp(
                <<<EOT
The status command displays a list of dependencies that have
been modified locally.

Read more at https://getcomposer.org/doc/03-cli.md#status
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $composer = $this->requireComposer();

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'status', $input, $output);
        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        // Dispatch pre-status-command
        $composer->getEventDispatcher()->dispatchScript(ScriptEvents::PRE_STATUS_CMD, true);

        $exitCode = $this->doExecute($input);

        // Dispatch post-status-command
        $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_STATUS_CMD, true);

        return $exitCode;
    }

    private function doExecute(InputInterface $input): int
    {
        // init repos
        $composer = $this->requireComposer();

        $installedRepo = $composer->getRepositoryManager()->getLocalRepository();

        $dm = $composer->getDownloadManager();
        $im = $composer->getInstallationManager();

        $errors = [];
        $io = $this->getIO();
        $unpushedChanges = [];
        $vcsVersionChanges = [];

        $parser = new VersionParser;
        $guesser = new VersionGuesser($composer->getConfig(), $composer->getLoop()->getProcessExecutor() ?? new ProcessExecutor($io), $parser, $io);
        $dumper = new ArrayDumper;

        // list packages
        foreach ($installedRepo->getCanonicalPackages() as $package) {
            $downloader = $dm->getDownloaderForPackage($package);
            $targetDir = $im->getInstallPath($package);
            if ($targetDir === null) {
                continue;
            }

            if ($downloader instanceof ChangeReportInterface) {
                if (is_link($targetDir)) {
                    $errors[$targetDir] = $targetDir . ' is a symbolic link.';
                }

                if (null !== ($changes = $downloader->getLocalChanges($package, $targetDir))) {
                    $errors[$targetDir] = $changes;
                }
            }

            if ($downloader instanceof VcsCapableDownloaderInterface) {
                if ($downloader->getVcsReference($package, $targetDir)) {
                    switch ($package->getInstallationSource()) {
                        case 'source':
                            $previousRef = $package->getSourceReference();
                            break;
                        case 'dist':
                            $previousRef = $package->getDistReference();
                            break;
                        default:
                            $previousRef = null;
                    }

                    $currentVersion = $guesser->guessVersion($dumper->dump($package), $targetDir);

                    if ($previousRef && $currentVersion && $currentVersion['commit'] !== $previousRef && $currentVersion['pretty_version'] !== $previousRef) {
                        $vcsVersionChanges[$targetDir] = [
                            'previous' => [
                                'version' => $package->getPrettyVersion(),
                                'ref' => $previousRef,
                            ],
                            'current' => [
                                'version' => $currentVersion['pretty_version'],
                                'ref' => $currentVersion['commit'],
                            ],
                        ];
                    }
                }
            }

            if ($downloader instanceof DvcsDownloaderInterface) {
                if ($unpushed = $downloader->getUnpushedChanges($package, $targetDir)) {
                    $unpushedChanges[$targetDir] = $unpushed;
                }
            }
        }

        // output errors/warnings
        if (!$errors && !$unpushedChanges && !$vcsVersionChanges) {
            $io->writeError('<info>No local changes</info>');

            return 0;
        }

        if ($errors) {
            $io->writeError('<error>You have changes in the following dependencies:</error>');

            foreach ($errors as $path => $changes) {
                if ($input->getOption('verbose')) {
                    $indentedChanges = implode("\n", array_map(static function ($line): string {
                        return '    ' . ltrim($line);
                    }, explode("\n", $changes)));
                    $io->write('<info>'.$path.'</info>:');
                    $io->write($indentedChanges);
                } else {
                    $io->write($path);
                }
            }
        }

        if ($unpushedChanges) {
            $io->writeError('<warning>You have unpushed changes on the current branch in the following dependencies:</warning>');

            foreach ($unpushedChanges as $path => $changes) {
                if ($input->getOption('verbose')) {
                    $indentedChanges = implode("\n", array_map(static function ($line): string {
                        return '    ' . ltrim($line);
                    }, explode("\n", $changes)));
                    $io->write('<info>'.$path.'</info>:');
                    $io->write($indentedChanges);
                } else {
                    $io->write($path);
                }
            }
        }

        if ($vcsVersionChanges) {
            $io->writeError('<warning>You have version variations in the following dependencies:</warning>');

            foreach ($vcsVersionChanges as $path => $changes) {
                if ($input->getOption('verbose')) {
                    // If we don't can't find a version, use the ref instead.
                    $currentVersion = $changes['current']['version'] ?: $changes['current']['ref'];
                    $previousVersion = $changes['previous']['version'] ?: $changes['previous']['ref'];

                    if ($io->isVeryVerbose()) {
                        // Output the ref regardless of whether or not it's being used as the version
                        $currentVersion .= sprintf(' (%s)', $changes['current']['ref']);
                        $previousVersion .= sprintf(' (%s)', $changes['previous']['ref']);
                    }

                    $io->write('<info>'.$path.'</info>:');
                    $io->write(sprintf('    From <comment>%s</comment> to <comment>%s</comment>', $previousVersion, $currentVersion));
                } else {
                    $io->write($path);
                }
            }
        }

        if (($errors || $unpushedChanges || $vcsVersionChanges) && !$input->getOption('verbose')) {
            $io->writeError('Use --verbose (-v) to see a list of files');
        }

        return ($errors ? self::EXIT_CODE_ERRORS : 0) + ($unpushedChanges ? self::EXIT_CODE_UNPUSHED_CHANGES : 0) + ($vcsVersionChanges ? self::EXIT_CODE_VERSION_CHANGES : 0);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Composer;
use Composer\DependencyResolver\DefaultPolicy;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
use Composer\Package\BasePackage;
use Composer\Package\CompletePackageInterface;
use Composer\Package\Link;
use Composer\Package\AliasPackage;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\Package\Version\VersionSelector;
use Composer\Pcre\Preg;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Repository\ArrayRepository;
use Composer\Repository\InstalledArrayRepository;
use Composer\Repository\ComposerRepository;
use Composer\Repository\CompositeRepository;
use Composer\Repository\FilterRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryFactory;
use Composer\Repository\InstalledRepository;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\RepositorySet;
use Composer\Repository\RepositoryUtils;
use Composer\Repository\RootPackageRepository;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Semver;
use Composer\Spdx\SpdxLicenses;
use Composer\Util\PackageInfo;
use DateTimeInterface;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Composer\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Jérémy Romey <jeremyFreeAgent>
 * @author Mihai Plasoianu <mihai@plasoianu.de>
 *
 * @phpstan-import-type AutoloadRules from PackageInterface
 * @phpstan-type JsonStructure array<string, null|string|array<string|null>|AutoloadRules>
 */
class ShowCommand extends BaseCommand
{
    use CompletionTrait;

    /** @var VersionParser */
    protected $versionParser;
    /** @var string[] */
    protected $colors;

    /** @var ?RepositorySet */
    private $repositorySet;

    /**
     * @return void
     */
    protected function configure()
    {
        $this
            ->setName('show')
            ->setAliases(['info'])
            ->setDescription('Shows information about packages')
            ->setDefinition([
                new InputArgument('package', InputArgument::OPTIONAL, 'Package to inspect. Or a name including a wildcard (*) to filter lists of packages instead.', null, $this->suggestPackageBasedOnMode()),
                new InputArgument('version', InputArgument::OPTIONAL, 'Version or version constraint to inspect'),
                new InputOption('all', null, InputOption::VALUE_NONE, 'List all packages'),
                new InputOption('locked', null, InputOption::VALUE_NONE, 'List all locked packages'),
                new InputOption('installed', 'i', InputOption::VALUE_NONE, 'List installed packages only (enabled by default, only present for BC).'),
                new InputOption('platform', 'p', InputOption::VALUE_NONE, 'List platform packages only'),
                new InputOption('available', 'a', InputOption::VALUE_NONE, 'List available packages only'),
                new InputOption('self', 's', InputOption::VALUE_NONE, 'Show the root package information'),
                new InputOption('name-only', 'N', InputOption::VALUE_NONE, 'List package names only'),
                new InputOption('path', 'P', InputOption::VALUE_NONE, 'Show package paths'),
                new InputOption('tree', 't', InputOption::VALUE_NONE, 'List the dependencies as a tree'),
                new InputOption('latest', 'l', InputOption::VALUE_NONE, 'Show the latest version'),
                new InputOption('outdated', 'o', InputOption::VALUE_NONE, 'Show the latest version but only for packages that are outdated'),
                new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Can contain wildcards (*). Use it with the --outdated option if you don\'t want to be informed about new versions of some packages.', null, $this->suggestInstalledPackage(false)),
                new InputOption('major-only', 'M', InputOption::VALUE_NONE, 'Show only packages that have major SemVer-compatible updates. Use with the --latest or --outdated option.'),
                new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates. Use with the --latest or --outdated option.'),
                new InputOption('patch-only', null, InputOption::VALUE_NONE, 'Show only packages that have patch SemVer-compatible updates. Use with the --latest or --outdated option.'),
                new InputOption('sort-by-age', 'A', InputOption::VALUE_NONE, 'Displays the installed version\'s age, and sorts packages oldest first. Use with the --latest or --outdated option.'),
                new InputOption('direct', 'D', InputOption::VALUE_NONE, 'Shows only packages that are directly required by the root package'),
                new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'),
                new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'),
                new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages). Use with the --outdated option'),
                new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages). Use with the --outdated option'),
            ])
            ->setHelp(
                <<<EOT
The show command displays detailed information about a package, or
lists all packages available.

Read more at https://getcomposer.org/doc/03-cli.md#show-info
EOT
            )
        ;
    }

    protected function suggestPackageBasedOnMode(): \Closure
    {
        return function (CompletionInput $input) {
            if ($input->getOption('available') || $input->getOption('all')) {
                return $this->suggestAvailablePackageInclPlatform()($input);
            }

            if ($input->getOption('platform')) {
                return $this->suggestPlatformPackage()($input);
            }

            return $this->suggestInstalledPackage(false)($input);
        };
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $this->versionParser = new VersionParser;
        if ($input->getOption('tree')) {
            $this->initStyles($output);
        }

        $composer = $this->tryComposer();
        $io = $this->getIO();

        if ($input->getOption('installed') && !$input->getOption('self')) {
            $io->writeError('<warning>You are using the deprecated option "installed". Only installed packages are shown by default now. The --all option can be used to show all packages.</warning>');
        }

        if ($input->getOption('outdated')) {
            $input->setOption('latest', true);
        } elseif (count($input->getOption('ignore')) > 0) {
            $io->writeError('<warning>You are using the option "ignore" for action other than "outdated", it will be ignored.</warning>');
        }

        if ($input->getOption('direct') && ($input->getOption('all') || $input->getOption('available') || $input->getOption('platform'))) {
            $io->writeError('The --direct (-D) option is not usable in combination with --all, --platform (-p) or --available (-a)');

            return 1;
        }

        if ($input->getOption('tree') && ($input->getOption('all') || $input->getOption('available'))) {
            $io->writeError('The --tree (-t) option is not usable in combination with --all or --available (-a)');

            return 1;
        }

        if (count(array_filter([$input->getOption('patch-only'), $input->getOption('minor-only'), $input->getOption('major-only')])) > 1) {
            $io->writeError('Only one of --major-only, --minor-only or --patch-only can be used at once');

            return 1;
        }

        if ($input->getOption('tree') && $input->getOption('latest')) {
            $io->writeError('The --tree (-t) option is not usable in combination with --latest (-l)');

            return 1;
        }

        if ($input->getOption('tree') && $input->getOption('path')) {
            $io->writeError('The --tree (-t) option is not usable in combination with --path (-P)');

            return 1;
        }

        $format = $input->getOption('format');
        if (!in_array($format, ['text', 'json'])) {
            $io->writeError(sprintf('Unsupported format "%s". See help for supported formats.', $format));

            return 1;
        }

        $platformReqFilter = $this->getPlatformRequirementFilter($input);

        // init repos
        $platformOverrides = [];
        if ($composer) {
            $platformOverrides = $composer->getConfig()->get('platform');
        }
        $platformRepo = new PlatformRepository([], $platformOverrides);
        $lockedRepo = null;

        if ($input->getOption('self') && !$input->getOption('installed') && !$input->getOption('locked')) {
            $package = clone $this->requireComposer()->getPackage();
            if ($input->getOption('name-only')) {
                $io->write($package->getName());

                return 0;
            }
            if ($input->getArgument('package')) {
                throw new \InvalidArgumentException('You cannot use --self together with a package name');
            }
            $repos = $installedRepo = new InstalledRepository([new RootPackageRepository($package)]);
        } elseif ($input->getOption('platform')) {
            $repos = $installedRepo = new InstalledRepository([$platformRepo]);
        } elseif ($input->getOption('available')) {
            $installedRepo = new InstalledRepository([$platformRepo]);
            if ($composer) {
                $repos = new CompositeRepository($composer->getRepositoryManager()->getRepositories());
                $installedRepo->addRepository($composer->getRepositoryManager()->getLocalRepository());
            } else {
                $defaultRepos = RepositoryFactory::defaultReposWithDefaultManager($io);
                $repos = new CompositeRepository($defaultRepos);
                $io->writeError('No composer.json found in the current directory, showing available packages from ' . implode(', ', array_keys($defaultRepos)));
            }
        } elseif ($input->getOption('all') && $composer) {
            $localRepo = $composer->getRepositoryManager()->getLocalRepository();
            $locker = $composer->getLocker();
            if ($locker->isLocked()) {
                $lockedRepo = $locker->getLockedRepository(true);
                $installedRepo = new InstalledRepository([$lockedRepo, $localRepo, $platformRepo]);
            } else {
                $installedRepo = new InstalledRepository([$localRepo, $platformRepo]);
            }
            $repos = new CompositeRepository(array_merge([new FilterRepository($installedRepo, ['canonical' => false])], $composer->getRepositoryManager()->getRepositories()));
        } elseif ($input->getOption('all')) {
            $defaultRepos = RepositoryFactory::defaultReposWithDefaultManager($io);
            $io->writeError('No composer.json found in the current directory, showing available packages from ' . implode(', ', array_keys($defaultRepos)));
            $installedRepo = new InstalledRepository([$platformRepo]);
            $repos = new CompositeRepository(array_merge([$installedRepo], $defaultRepos));
        } elseif ($input->getOption('locked')) {
            if (!$composer || !$composer->getLocker()->isLocked()) {
                throw new \UnexpectedValueException('A valid composer.json and composer.lock files is required to run this command with --locked');
            }
            $locker = $composer->getLocker();
            $lockedRepo = $locker->getLockedRepository(!$input->getOption('no-dev'));
            if ($input->getOption('self')) {
                $lockedRepo->addPackage(clone $composer->getPackage());
            }
            $repos = $installedRepo = new InstalledRepository([$lockedRepo]);
        } else {
            // --installed / default case
            if (!$composer) {
                $composer = $this->requireComposer();
            }
            $rootPkg = $composer->getPackage();

            $rootRepo = new InstalledArrayRepository();
            if ($input->getOption('self')) {
                $rootRepo = new RootPackageRepository(clone $rootPkg);
            }
            if ($input->getOption('no-dev')) {
                $packages = RepositoryUtils::filterRequiredPackages($composer->getRepositoryManager()->getLocalRepository()->getPackages(), $rootPkg);
                $repos = $installedRepo = new InstalledRepository([$rootRepo, new InstalledArrayRepository(array_map(static function ($pkg): PackageInterface {
                    return clone $pkg;
                }, $packages))]);
            } else {
                $repos = $installedRepo = new InstalledRepository([$rootRepo, $composer->getRepositoryManager()->getLocalRepository()]);
            }

            if (!$installedRepo->getPackages()) {
                $hasNonPlatformReqs = static function (array $reqs): bool {
                    return (bool) array_filter(array_keys($reqs), function (string $name) {
                        return !PlatformRepository::isPlatformPackage($name);
                    });
                };

                if ($hasNonPlatformReqs($rootPkg->getRequires()) || $hasNonPlatformReqs($rootPkg->getDevRequires())) {
                    $io->writeError('<warning>No dependencies installed. Try running composer install or update.</warning>');
                }
            }
        }

        if ($composer) {
            $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'show', $input, $output);
            $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
        }

        if ($input->getOption('latest') && null === $composer) {
            $io->writeError('No composer.json found in the current directory, disabling "latest" option');
            $input->setOption('latest', false);
        }

        $packageFilter = $input->getArgument('package');

        // show single package or single version
        if (isset($package)) {
            $versions = [$package->getPrettyVersion() => $package->getVersion()];
        } elseif (null !== $packageFilter && !str_contains($packageFilter, '*')) {
            [$package, $versions] = $this->getPackage($installedRepo, $repos, $packageFilter, $input->getArgument('version'));

            if (isset($package) && $input->getOption('direct')) {
                if (!in_array($package->getName(), $this->getRootRequires(), true)) {
                    throw new \InvalidArgumentException('Package "' . $package->getName() . '" is installed but not a direct dependent of the root package.');
                }
            }

            if (!isset($package)) {
                $options = $input->getOptions();
                $hint = '';
                if ($input->getOption('locked')) {
                    $hint .= ' in lock file';
                }
                if (isset($options['working-dir'])) {
                    $hint .= ' in ' . $options['working-dir'] . '/composer.json';
                }
                if (PlatformRepository::isPlatformPackage($packageFilter) && !$input->getOption('platform')) {
                    $hint .= ', try using --platform (-p) to show platform packages';
                }
                if (!$input->getOption('all') && !$input->getOption('available')) {
                    $hint .= ', try using --available (-a) to show all available packages';
                }

                throw new \InvalidArgumentException('Package "' . $packageFilter . '" not found'.$hint.'.');
            }
        }

        if (isset($package)) {
            assert(isset($versions));

            $exitCode = 0;
            if ($input->getOption('tree')) {
                $arrayTree = $this->generatePackageTree($package, $installedRepo, $repos);

                if ('json' === $format) {
                    $io->write(JsonFile::encode(['installed' => [$arrayTree]]));
                } else {
                    $this->displayPackageTree([$arrayTree]);
                }

                return $exitCode;
            }

            $latestPackage = null;
            if ($input->getOption('latest')) {
                $latestPackage = $this->findLatestPackage($package, $composer, $platformRepo, $input->getOption('major-only'), $input->getOption('minor-only'), $input->getOption('patch-only'), $platformReqFilter);
            }
            if (
                $input->getOption('outdated')
                && $input->getOption('strict')
                && null !== $latestPackage
                && $latestPackage->getFullPrettyVersion() !== $package->getFullPrettyVersion()
                && (!$latestPackage instanceof CompletePackageInterface || !$latestPackage->isAbandoned())
            ) {
                $exitCode = 1;
            }
            if ($input->getOption('path')) {
                $io->write($package->getName(), false);
                $path = $composer->getInstallationManager()->getInstallPath($package);
                if (is_string($path)) {
                    $io->write(' ' . strtok(realpath($path), "\r\n"));
                } else {
                    $io->write(' null');
                }

                return $exitCode;
            }

            if ('json' === $format) {
                $this->printPackageInfoAsJson($package, $versions, $installedRepo, $latestPackage ?: null);
            } else {
                $this->printPackageInfo($package, $versions, $installedRepo, $latestPackage ?: null);
            }

            return $exitCode;
        }

        // show tree view if requested
        if ($input->getOption('tree')) {
            $rootRequires = $this->getRootRequires();
            $packages = $installedRepo->getPackages();
            usort($packages, static function (BasePackage $a, BasePackage $b): int {
                return strcmp((string) $a, (string) $b);
            });
            $arrayTree = [];
            foreach ($packages as $package) {
                if (in_array($package->getName(), $rootRequires, true)) {
                    $arrayTree[] = $this->generatePackageTree($package, $installedRepo, $repos);
                }
            }

            if ('json' === $format) {
                $io->write(JsonFile::encode(['installed' => $arrayTree]));
            } else {
                $this->displayPackageTree($arrayTree);
            }

            return 0;
        }

        // list packages
        /** @var array<string, array<string, string|CompletePackageInterface>> $packages */
        $packages = [];
        $packageFilterRegex = null;
        if (null !== $packageFilter) {
            $packageFilterRegex = '{^'.str_replace('\\*', '.*?', preg_quote($packageFilter)).'$}i';
        }

        $packageListFilter = null;
        if ($input->getOption('direct')) {
            $packageListFilter = $this->getRootRequires();
        }

        if ($input->getOption('path') && null === $composer) {
            $io->writeError('No composer.json found in the current directory, disabling "path" option');
            $input->setOption('path', false);
        }

        foreach (RepositoryUtils::flattenRepositories($repos) as $repo) {
            if ($repo === $platformRepo) {
                $type = 'platform';
            } elseif ($lockedRepo !== null && $repo === $lockedRepo) {
                $type = 'locked';
            } elseif ($repo === $installedRepo || in_array($repo, $installedRepo->getRepositories(), true)) {
                $type = 'installed';
            } else {
                $type = 'available';
            }
            if ($repo instanceof ComposerRepository) {
                foreach ($repo->getPackageNames($packageFilter) as $name) {
                    $packages[$type][$name] = $name;
                }
            } else {
                foreach ($repo->getPackages() as $package) {
                    if (!isset($packages[$type][$package->getName()])
                        || !is_object($packages[$type][$package->getName()])
                        || version_compare($packages[$type][$package->getName()]->getVersion(), $package->getVersion(), '<')
                    ) {
                        while ($package instanceof AliasPackage) {
                            $package = $package->getAliasOf();
                        }
                        if (!$packageFilterRegex || Preg::isMatch($packageFilterRegex, $package->getName())) {
                            if (null === $packageListFilter || in_array($package->getName(), $packageListFilter, true)) {
                                $packages[$type][$package->getName()] = $package;
                            }
                        }
                    }
                }
                if ($repo === $platformRepo) {
                    foreach ($platformRepo->getDisabledPackages() as $name => $package) {
                        $packages[$type][$name] = $package;
                    }
                }
            }
        }

        $showAllTypes = $input->getOption('all');
        $showLatest = $input->getOption('latest');
        $showMajorOnly = $input->getOption('major-only');
        $showMinorOnly = $input->getOption('minor-only');
        $showPatchOnly = $input->getOption('patch-only');
        $ignoredPackagesRegex = BasePackage::packageNamesToRegexp(array_map('strtolower', $input->getOption('ignore')));
        $indent = $showAllTypes ? '  ' : '';
        /** @var PackageInterface[] $latestPackages */
        $latestPackages = [];
        $exitCode = 0;
        $viewData = [];
        $viewMetaData = [];

        $writeVersion = false;
        $writeDescription = false;

        foreach (['platform' => true, 'locked' => true, 'available' => false, 'installed' => true] as $type => $showVersion) {
            if (isset($packages[$type])) {
                ksort($packages[$type]);

                $nameLength = $versionLength = $latestLength = $releaseDateLength = 0;

                if ($showLatest && $showVersion) {
                    foreach ($packages[$type] as $package) {
                        if (is_object($package) && !Preg::isMatch($ignoredPackagesRegex, $package->getPrettyName())) {
                            $latestPackage = $this->findLatestPackage($package, $composer, $platformRepo, $showMajorOnly, $showMinorOnly, $showPatchOnly, $platformReqFilter);
                            if ($latestPackage === null) {
                                continue;
                            }

                            $latestPackages[$package->getPrettyName()] = $latestPackage;
                        }
                    }
                }

                $writePath = !$input->getOption('name-only') && $input->getOption('path');
                $writeVersion = !$input->getOption('name-only') && !$input->getOption('path') && $showVersion;
                $writeLatest = $writeVersion && $showLatest;
                $writeDescription = !$input->getOption('name-only') && !$input->getOption('path');
                $writeReleaseDate = $writeLatest && ($input->getOption('sort-by-age') || $format === 'json');

                $hasOutdatedPackages = false;

                if ($input->getOption('sort-by-age')) {
                    usort($packages[$type], function ($a, $b) {
                        if (is_object($a) && is_object($b)) {
                            return $a->getReleaseDate() <=> $b->getReleaseDate();
                        }

                        return 0;
                    });
                }

                $viewData[$type] = [];
                foreach ($packages[$type] as $package) {
                    $packageViewData = [];
                    if (is_object($package)) {
                        $latestPackage = null;
                        if ($showLatest && isset($latestPackages[$package->getPrettyName()])) {
                            $latestPackage = $latestPackages[$package->getPrettyName()];
                        }

                        // Determine if Composer is checking outdated dependencies and if current package should trigger non-default exit code
                        $packageIsUpToDate = $latestPackage && $latestPackage->getFullPrettyVersion() === $package->getFullPrettyVersion() && (!$latestPackage instanceof CompletePackageInterface || !$latestPackage->isAbandoned());
                        // When using --major-only, and no bigger version than current major is found then it is considered up to date
                        $packageIsUpToDate = $packageIsUpToDate || ($latestPackage === null && $showMajorOnly);
                        $packageIsIgnored = Preg::isMatch($ignoredPackagesRegex, $package->getPrettyName());
                        if ($input->getOption('outdated') && ($packageIsUpToDate || $packageIsIgnored)) {
                            continue;
                        }

                        if ($input->getOption('outdated') || $input->getOption('strict')) {
                            $hasOutdatedPackages = true;
                        }

                        $packageViewData['name'] = $package->getPrettyName();
                        $packageViewData['direct-dependency'] = in_array($package->getName(), $this->getRootRequires(), true);
                        if ($format !== 'json' || true !== $input->getOption('name-only')) {
                            $packageViewData['homepage'] = $package instanceof CompletePackageInterface ? $package->getHomepage() : null;
                            $packageViewData['source'] = PackageInfo::getViewSourceUrl($package);
                        }
                        $nameLength = max($nameLength, strlen($packageViewData['name']));
                        if ($writeVersion) {
                            $packageViewData['version'] = $package->getFullPrettyVersion();
                            if ($format === 'text') {
                                $packageViewData['version'] = ltrim($packageViewData['version'], 'v');
                            }
                            $versionLength = max($versionLength, strlen($packageViewData['version']));
                        }
                        if ($writeReleaseDate) {
                            if ($package->getReleaseDate() !== null) {
                                $packageViewData['release-age'] = str_replace(' ago', ' old', $this->getRelativeTime($package->getReleaseDate()));
                                if (!str_contains($packageViewData['release-age'], ' old')) {
                                    $packageViewData['release-age'] = 'from '.$packageViewData['release-age'];
                                }
                                $releaseDateLength = max($releaseDateLength, strlen($packageViewData['release-age']));
                                $packageViewData['release-date'] = $package->getReleaseDate()->format(DateTimeInterface::ATOM);
                            } else {
                                $packageViewData['release-age'] = '';
                                $packageViewData['release-date'] = '';
                            }
                        }
                        if ($writeLatest && $latestPackage) {
                            $packageViewData['latest'] = $latestPackage->getFullPrettyVersion();
                            if ($format === 'text') {
                                $packageViewData['latest'] = ltrim($packageViewData['latest'], 'v');
                            }
                            $packageViewData['latest-status'] = $this->getUpdateStatus($latestPackage, $package);
                            $latestLength = max($latestLength, strlen($packageViewData['latest']));

                            if ($latestPackage->getReleaseDate() !== null) {
                                $packageViewData['latest-release-date'] = $latestPackage->getReleaseDate()->format(DateTimeInterface::ATOM);
                            } else {
                                $packageViewData['latest-release-date'] = '';
                            }
                        } elseif ($writeLatest) {
                            $packageViewData['latest'] = '[none matched]';
                            $packageViewData['latest-status'] = 'up-to-date';
                            $latestLength = max($latestLength, strlen($packageViewData['latest']));
                        }
                        if ($writeDescription && $package instanceof CompletePackageInterface) {
                            $packageViewData['description'] = $package->getDescription();
                        }
                        if ($writePath) {
                            $path = $composer->getInstallationManager()->getInstallPath($package);
                            if (is_string($path)) {
                                $packageViewData['path'] = strtok(realpath($path), "\r\n");
                            } else {
                                $packageViewData['path'] = null;
                            }
                        }

                        $packageIsAbandoned = false;
                        if ($latestPackage instanceof CompletePackageInterface && $latestPackage->isAbandoned()) {
                            $replacementPackageName = $latestPackage->getReplacementPackage();
                            $replacement = $replacementPackageName !== null
                                ? 'Use ' . $latestPackage->getReplacementPackage() . ' instead'
                                : 'No replacement was suggested';
                            $packageWarning = sprintf(
                                'Package %s is abandoned, you should avoid using it. %s.',
                                $package->getPrettyName(),
                                $replacement
                            );
                            $packageViewData['warning'] = $packageWarning;
                            $packageIsAbandoned = $replacementPackageName ?? true;
                        }

                        $packageViewData['abandoned'] = $packageIsAbandoned;
                    } else {
                        $packageViewData['name'] = $package;
                        $nameLength = max($nameLength, strlen($package));
                    }
                    $viewData[$type][] = $packageViewData;
                }
                $viewMetaData[$type] = [
                    'nameLength' => $nameLength,
                    'versionLength' => $versionLength,
                    'latestLength' => $latestLength,
                    'releaseDateLength' => $releaseDateLength,
                    'writeLatest' => $writeLatest,
                    'writeReleaseDate' => $writeReleaseDate,
                ];
                if ($input->getOption('strict') && $hasOutdatedPackages) {
                    $exitCode = 1;
                    break;
                }
            }
        }

        if ('json' === $format) {
            $io->write(JsonFile::encode($viewData));
        } else {
            if ($input->getOption('latest') && array_filter($viewData)) {
                if (!$io->isDecorated()) {
                    $io->writeError('Legend:');
                    $io->writeError('! patch or minor release available - update recommended');
                    $io->writeError('~ major release available - update possible');
                    if (!$input->getOption('outdated')) {
                        $io->writeError('= up to date version');
                    }
                } else {
                    $io->writeError('<info>Color legend:</info>');
                    $io->writeError('- <highlight>patch or minor</highlight> release available - update recommended');
                    $io->writeError('- <comment>major</comment> release available - update possible');
                    if (!$input->getOption('outdated')) {
                        $io->writeError('- <info>up to date</info> version');
                    }
                }
            }

            $width = $this->getTerminalWidth();

            foreach ($viewData as $type => $packages) {
                $nameLength = $viewMetaData[$type]['nameLength'];
                $versionLength = $viewMetaData[$type]['versionLength'];
                $latestLength = $viewMetaData[$type]['latestLength'];
                $releaseDateLength = $viewMetaData[$type]['releaseDateLength'];
                $writeLatest = $viewMetaData[$type]['writeLatest'];
                $writeReleaseDate = $viewMetaData[$type]['writeReleaseDate'];

                $versionFits = $nameLength + $versionLength + 3 <= $width;
                $latestFits = $nameLength + $versionLength + $latestLength + 3 <= $width;
                $releaseDateFits = $nameLength + $versionLength + $latestLength + $releaseDateLength + 3 <= $width;
                $descriptionFits = $nameLength + $versionLength + $latestLength + $releaseDateLength + 24 <= $width;

                if ($latestFits && !$io->isDecorated()) {
                    $latestLength += 2;
                }

                if ($showAllTypes) {
                    if ('available' === $type) {
                        $io->write('<comment>' . $type . '</comment>:');
                    } else {
                        $io->write('<info>' . $type . '</info>:');
                    }
                }

                if ($writeLatest && !$input->getOption('direct')) {
                    $directDeps = [];
                    $transitiveDeps = [];
                    foreach ($packages as $pkg) {
                        if ($pkg['direct-dependency'] ?? false) {
                            $directDeps[] = $pkg;
                        } else {
                            $transitiveDeps[] = $pkg;
                        }
                    }

                    $io->writeError('');
                    $io->writeError('<info>Direct dependencies required in composer.json:</>');
                    if (\count($directDeps) > 0) {
                        $this->printPackages($io, $directDeps, $indent, $writeVersion && $versionFits, $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength, $writeReleaseDate && $releaseDateFits, $releaseDateLength);
                    } else {
                        $io->writeError('Everything up to date');
                    }
                    $io->writeError('');
                    $io->writeError('<info>Transitive dependencies not required in composer.json:</>');
                    if (\count($transitiveDeps) > 0) {
                        $this->printPackages($io, $transitiveDeps, $indent, $writeVersion && $versionFits, $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength, $writeReleaseDate && $releaseDateFits, $releaseDateLength);
                    } else {
                        $io->writeError('Everything up to date');
                    }
                } else {
                    if ($writeLatest && \count($packages) === 0) {
                        $io->writeError('All your direct dependencies are up to date');
                    } else {
                        $this->printPackages($io, $packages, $indent, $writeVersion && $versionFits, $writeLatest && $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength, $writeReleaseDate && $releaseDateFits, $releaseDateLength);
                    }
                }

                if ($showAllTypes) {
                    $io->write('');
                }
            }
        }

        return $exitCode;
    }

    /**
     * @param array<array{name: string, direct-dependency?: bool, version?: string, latest?: string, latest-status?: string, description?: string|null, path?: string|null, source?: string|null, homepage?: string|null, warning?: string, abandoned?: bool|string}> $packages
     */
    private function printPackages(IOInterface $io, array $packages, string $indent, bool $writeVersion, bool $writeLatest, bool $writeDescription, int $width, int $versionLength, int $nameLength, int $latestLength, bool $writeReleaseDate, int $releaseDateLength): void
    {
        $padName = $writeVersion || $writeLatest || $writeReleaseDate || $writeDescription;
        $padVersion = $writeLatest || $writeReleaseDate || $writeDescription;
        $padLatest = $writeDescription || $writeReleaseDate;
        $padReleaseDate = $writeDescription;
        foreach ($packages as $package) {
            $link = $package['source'] ?? $package['homepage'] ?? '';
            if ($link !== '') {
                $io->write($indent . '<href='.OutputFormatter::escape($link).'>'.$package['name'].'</>'. str_repeat(' ', ($padName ? $nameLength - strlen($package['name']) : 0)), false);
            } else {
                $io->write($indent . str_pad($package['name'], ($padName ? $nameLength : 0), ' '), false);
            }
            if (isset($package['version']) && $writeVersion) {
                $io->write(' ' . str_pad($package['version'], ($padVersion ? $versionLength : 0), ' '), false);
            }
            if (isset($package['latest']) && isset($package['latest-status']) && $writeLatest) {
                $latestVersion = $package['latest'];
                $updateStatus = $package['latest-status'];
                $style = $this->updateStatusToVersionStyle($updateStatus);
                if (!$io->isDecorated()) {
                    $latestVersion = str_replace(['up-to-date', 'semver-safe-update', 'update-possible'], ['=', '!', '~'], $updateStatus) . ' ' . $latestVersion;
                }
                $io->write(' <' . $style . '>' . str_pad($latestVersion, ($padLatest ? $latestLength : 0), ' ') . '</' . $style . '>', false);
                if ($writeReleaseDate && isset($package['release-age'])) {
                    $io->write(' '.str_pad($package['release-age'], ($padReleaseDate ? $releaseDateLength : 0), ' '), false);
                }
            }
            if (isset($package['description']) && $writeDescription) {
                $description = strtok($package['description'], "\r\n");
                $remaining = $width - $nameLength - $versionLength - $releaseDateLength - 4;
                if ($writeLatest) {
                    $remaining -= $latestLength;
                }
                if (strlen($description) > $remaining) {
                    $description = substr($description, 0, $remaining - 3) . '...';
                }
                $io->write(' ' . $description, false);
            }
            if (array_key_exists('path', $package)) {
                $io->write(' '.(is_string($package['path']) ? $package['path'] : 'null'), false);
            }
            $io->write('');
            if (isset($package['warning'])) {
                $io->write('<warning>' . $package['warning'] . '</warning>');
            }
        }
    }

    /**
     * @return string[]
     */
    protected function getRootRequires(): array
    {
        $composer = $this->tryComposer();
        if ($composer === null) {
            return [];
        }

        $rootPackage = $composer->getPackage();

        return array_map(
            'strtolower',
            array_keys(array_merge($rootPackage->getRequires(), $rootPackage->getDevRequires()))
        );
    }

    /**
     * @return array|string|string[]
     */
    protected function getVersionStyle(PackageInterface $latestPackage, PackageInterface $package)
    {
        return $this->updateStatusToVersionStyle($this->getUpdateStatus($latestPackage, $package));
    }

    /**
     * finds a package by name and version if provided
     *
     * @param  ConstraintInterface|string $version
     * @throws \InvalidArgumentException
     * @return array{CompletePackageInterface|null, array<string, string>}
     */
    protected function getPackage(InstalledRepository $installedRepo, RepositoryInterface $repos, string $name, $version = null): array
    {
        $name = strtolower($name);
        $constraint = is_string($version) ? $this->versionParser->parseConstraints($version) : $version;

        $policy = new DefaultPolicy();
        $repositorySet = new RepositorySet('dev');
        $repositorySet->allowInstalledRepositories();
        $repositorySet->addRepository($repos);

        $matchedPackage = null;
        $versions = [];
        if (PlatformRepository::isPlatformPackage($name)) {
            $pool = $repositorySet->createPoolWithAllPackages();
        } else {
            $pool = $repositorySet->createPoolForPackage($name);
        }
        $matches = $pool->whatProvides($name, $constraint);
        $literals = [];
        foreach ($matches as $package) {
            // avoid showing the 9999999-dev alias if the default branch has no branch-alias set
            if ($package instanceof AliasPackage && $package->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) {
                $package = $package->getAliasOf();
            }

            // select an exact match if it is in the installed repo and no specific version was required
            if (null === $version && $installedRepo->hasPackage($package)) {
                $matchedPackage = $package;
            }

            $versions[$package->getPrettyVersion()] = $package->getVersion();
            $literals[] = $package->getId();
        }

        // select preferred package according to policy rules
        if (null === $matchedPackage && \count($literals) > 0) {
            $preferred = $policy->selectPreferredPackages($pool, $literals);
            $matchedPackage = $pool->literalToPackage($preferred[0]);
        }

        if ($matchedPackage !== null && !$matchedPackage instanceof CompletePackageInterface) {
            throw new \LogicException('ShowCommand::getPackage can only work with CompletePackageInterface, but got '.get_class($matchedPackage));
        }

        return [$matchedPackage, $versions];
    }

    /**
     * Prints package info.
     *
     * @param array<string, string>    $versions
     */
    protected function printPackageInfo(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo, ?PackageInterface $latestPackage = null): void
    {
        $io = $this->getIO();

        $this->printMeta($package, $versions, $installedRepo, $latestPackage ?: null);
        $this->printLinks($package, Link::TYPE_REQUIRE);
        $this->printLinks($package, Link::TYPE_DEV_REQUIRE, 'requires (dev)');

        if ($package->getSuggests()) {
            $io->write("\n<info>suggests</info>");
            foreach ($package->getSuggests() as $suggested => $reason) {
                $io->write($suggested . ' <comment>' . $reason . '</comment>');
            }
        }

        $this->printLinks($package, Link::TYPE_PROVIDE);
        $this->printLinks($package, Link::TYPE_CONFLICT);
        $this->printLinks($package, Link::TYPE_REPLACE);
    }

    /**
     * Prints package metadata.
     *
     * @param array<string, string>    $versions
     */
    protected function printMeta(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo, ?PackageInterface $latestPackage = null): void
    {
        $isInstalledPackage = !PlatformRepository::isPlatformPackage($package->getName()) && $installedRepo->hasPackage($package);

        $io = $this->getIO();
        $io->write('<info>name</info>     : ' . $package->getPrettyName());
        $io->write('<info>descrip.</info> : ' . $package->getDescription());
        $io->write('<info>keywords</info> : ' . implode(', ', $package->getKeywords() ?: []));
        $this->printVersions($package, $versions, $installedRepo);
        if ($isInstalledPackage && $package->getReleaseDate() !== null) {
            $io->write('<info>released</info> : ' . $package->getReleaseDate()->format('Y-m-d') . ', ' . $this->getRelativeTime($package->getReleaseDate()));
        }
        if ($latestPackage) {
            $style = $this->getVersionStyle($latestPackage, $package);
            $releasedTime = $latestPackage->getReleaseDate() === null ? '' : ' released ' . $latestPackage->getReleaseDate()->format('Y-m-d') . ', ' . $this->getRelativeTime($latestPackage->getReleaseDate());
            $io->write('<info>latest</info>   : <'.$style.'>' . $latestPackage->getPrettyVersion() . '</'.$style.'>' . $releasedTime);
        } else {
            $latestPackage = $package;
        }
        $io->write('<info>type</info>     : ' . $package->getType());
        $this->printLicenses($package);
        $io->write('<info>homepage</info> : ' . $package->getHomepage());
        $io->write('<info>source</info>   : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getSourceType(), $package->getSourceUrl(), $package->getSourceReference()));
        $io->write('<info>dist</info>     : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference()));
        if ($isInstalledPackage) {
            $path = $this->requireComposer()->getInstallationManager()->getInstallPath($package);
            if (is_string($path)) {
                $io->write('<info>path</info>     : ' . realpath($path));
            } else {
                $io->write('<info>path</info>     : null');
            }
        }
        $io->write('<info>names</info>    : ' . implode(', ', $package->getNames()));

        if ($latestPackage instanceof CompletePackageInterface && $latestPackage->isAbandoned()) {
            $replacement = ($latestPackage->getReplacementPackage() !== null)
                ? ' The author suggests using the ' . $latestPackage->getReplacementPackage(). ' package instead.'
                : null;

            $io->writeError(
                sprintf('<warning>Attention: This package is abandoned and no longer maintained.%s</warning>', $replacement)
            );
        }

        if ($package->getSupport()) {
            $io->write("\n<info>support</info>");
            foreach ($package->getSupport() as $type => $value) {
                $io->write('<comment>' . $type . '</comment> : '.$value);
            }
        }

        if (\count($package->getAutoload()) > 0) {
            $io->write("\n<info>autoload</info>");
            $autoloadConfig = $package->getAutoload();
            foreach ($autoloadConfig as $type => $autoloads) {
                $io->write('<comment>' . $type . '</comment>');

                if ($type === 'psr-0' || $type === 'psr-4') {
                    foreach ($autoloads as $name => $path) {
                        $io->write(($name ?: '*') . ' => ' . (is_array($path) ? implode(', ', $path) : ($path ?: '.')));
                    }
                } elseif ($type === 'classmap') {
                    $io->write(implode(', ', $autoloadConfig[$type]));
                }
            }
            if ($package->getIncludePaths()) {
                $io->write('<comment>include-path</comment>');
                $io->write(implode(', ', $package->getIncludePaths()));
            }
        }
    }

    /**
     * Prints all available versions of this package and highlights the installed one if any.
     *
     * @param array<string, string> $versions
     */
    protected function printVersions(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo): void
    {
        $versions = array_keys($versions);
        $versions = Semver::rsort($versions);

        // highlight installed version
        if ($installedPackages = $installedRepo->findPackages($package->getName())) {
            foreach ($installedPackages as $installedPackage) {
                $installedVersion = $installedPackage->getPrettyVersion();
                $key = array_search($installedVersion, $versions);
                if (false !== $key) {
                    $versions[$key] = '<info>* ' . $installedVersion . '</info>';
                }
            }
        }

        $versions = implode(', ', $versions);

        $this->getIO()->write('<info>versions</info> : ' . $versions);
    }

    /**
     * print link objects
     *
     * @param string                   $title
     */
    protected function printLinks(CompletePackageInterface $package, string $linkType, ?string $title = null): void
    {
        $title = $title ?: $linkType;
        $io = $this->getIO();
        if ($links = $package->{'get'.ucfirst($linkType)}()) {
            $io->write("\n<info>" . $title . "</info>");

            foreach ($links as $link) {
                $io->write($link->getTarget() . ' <comment>' . $link->getPrettyConstraint() . '</comment>');
            }
        }
    }

    /**
     * Prints the licenses of a package with metadata
     */
    protected function printLicenses(CompletePackageInterface $package): void
    {
        $spdxLicenses = new SpdxLicenses();

        $licenses = $package->getLicense();
        $io = $this->getIO();

        foreach ($licenses as $licenseId) {
            $license = $spdxLicenses->getLicenseByIdentifier($licenseId); // keys: 0 fullname, 1 osi, 2 url

            if (!$license) {
                $out = $licenseId;
            } else {
                // is license OSI approved?
                if ($license[1] === true) {
                    $out = sprintf('%s (%s) (OSI approved) %s', $license[0], $licenseId, $license[2]);
                } else {
                    $out = sprintf('%s (%s) %s', $license[0], $licenseId, $license[2]);
                }
            }

            $io->write('<info>license</info>  : ' . $out);
        }
    }

    /**
     * Prints package info in JSON format.
     *
     * @param array<string, string>    $versions
     */
    protected function printPackageInfoAsJson(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo, ?PackageInterface $latestPackage = null): void
    {
        $json = [
            'name' => $package->getPrettyName(),
            'description' => $package->getDescription(),
            'keywords' => $package->getKeywords() ?: [],
            'type' => $package->getType(),
            'homepage' => $package->getHomepage(),
            'names' => $package->getNames(),
        ];

        $json = $this->appendVersions($json, $versions);
        $json = $this->appendLicenses($json, $package);

        if ($latestPackage) {
            $json['latest'] = $latestPackage->getPrettyVersion();
        } else {
            $latestPackage = $package;
        }

        if (null !== $package->getSourceType()) {
            $json['source'] = [
                'type' => $package->getSourceType(),
                'url' => $package->getSourceUrl(),
                'reference' => $package->getSourceReference(),
            ];
        }

        if (null !== $package->getDistType()) {
            $json['dist'] = [
                'type' => $package->getDistType(),
                'url' => $package->getDistUrl(),
                'reference' => $package->getDistReference(),
            ];
        }

        if (!PlatformRepository::isPlatformPackage($package->getName()) && $installedRepo->hasPackage($package)) {
            $path = $this->requireComposer()->getInstallationManager()->getInstallPath($package);
            if (is_string($path)) {
                $path = realpath($path);
                if ($path !== false) {
                    $json['path'] = $path;
                }
            } else {
                $json['path'] = null;
            }

            if ($package->getReleaseDate() !== null) {
                $json['released'] = $package->getReleaseDate()->format(DATE_ATOM);
            }
        }

        if ($latestPackage instanceof CompletePackageInterface && $latestPackage->isAbandoned()) {
            $json['replacement'] = $latestPackage->getReplacementPackage();
        }

        if ($package->getSuggests()) {
            $json['suggests'] = $package->getSuggests();
        }

        if ($package->getSupport()) {
            $json['support'] = $package->getSupport();
        }

        $json = $this->appendAutoload($json, $package);

        if ($package->getIncludePaths()) {
            $json['include_path'] = $package->getIncludePaths();
        }

        $json = $this->appendLinks($json, $package);

        $this->getIO()->write(JsonFile::encode($json));
    }

    /**
     * @param JsonStructure $json
     * @param array<string, string> $versions
     * @return JsonStructure
     */
    private function appendVersions(array $json, array $versions): array
    {
        uasort($versions, 'version_compare');
        $versions = array_keys(array_reverse($versions));
        $json['versions'] = $versions;

        return $json;
    }

    /**
     * @param JsonStructure $json
     * @return JsonStructure
     */
    private function appendLicenses(array $json, CompletePackageInterface $package): array
    {
        if ($licenses = $package->getLicense()) {
            $spdxLicenses = new SpdxLicenses();

            $json['licenses'] = array_map(static function ($licenseId) use ($spdxLicenses) {
                $license = $spdxLicenses->getLicenseByIdentifier($licenseId); // keys: 0 fullname, 1 osi, 2 url

                if (!$license) {
                    return $licenseId;
                }

                return [
                    'name' => $license[0],
                    'osi' => $licenseId,
                    'url' => $license[2],
                ];
            }, $licenses);
        }

        return $json;
    }

    /**
     * @param JsonStructure $json
     * @return JsonStructure
     */
    private function appendAutoload(array $json, CompletePackageInterface $package): array
    {
        if (\count($package->getAutoload()) > 0) {
            $autoload = [];

            foreach ($package->getAutoload() as $type => $autoloads) {
                if ($type === 'psr-0' || $type === 'psr-4') {
                    $psr = [];

                    foreach ($autoloads as $name => $path) {
                        if (!$path) {
                            $path = '.';
                        }

                        $psr[$name ?: '*'] = $path;
                    }

                    $autoload[$type] = $psr;
                } elseif ($type === 'classmap') {
                    $autoload['classmap'] = $autoloads;
                }
            }

            $json['autoload'] = $autoload;
        }

        return $json;
    }

    /**
     * @param JsonStructure $json
     * @return JsonStructure
     */
    private function appendLinks(array $json, CompletePackageInterface $package): array
    {
        foreach (Link::$TYPES as $linkType) {
            $json = $this->appendLink($json, $package, $linkType);
        }

        return $json;
    }

    /**
     * @param JsonStructure $json
     * @return JsonStructure
     */
    private function appendLink(array $json, CompletePackageInterface $package, string $linkType): array
    {
        $links = $package->{'get' . ucfirst($linkType)}();

        if ($links) {
            $json[$linkType] = [];

            foreach ($links as $link) {
                $json[$linkType][$link->getTarget()] = $link->getPrettyConstraint();
            }
        }

        return $json;
    }

    /**
     * Init styles for tree
     */
    protected function initStyles(OutputInterface $output): void
    {
        $this->colors = [
            'green',
            'yellow',
            'cyan',
            'magenta',
            'blue',
        ];

        foreach ($this->colors as $color) {
            $style = new OutputFormatterStyle($color);
            $output->getFormatter()->setStyle($color, $style);
        }
    }

    /**
     * Display the tree
     *
     * @param array<int, array<string, string|mixed[]>> $arrayTree
     */
    protected function displayPackageTree(array $arrayTree): void
    {
        $io = $this->getIO();
        foreach ($arrayTree as $package) {
            $io->write(sprintf('<info>%s</info>', $package['name']), false);
            $io->write(' ' . $package['version'], false);
            if (isset($package['description'])) {
                $io->write(' ' . strtok($package['description'], "\r\n"));
            } else {
                // output newline
                $io->write('');
            }

            if (isset($package['requires'])) {
                $requires = $package['requires'];
                $treeBar = '├';
                $j = 0;
                $total = count($requires);
                foreach ($requires as $require) {
                    $requireName = $require['name'];
                    $j++;
                    if ($j === $total) {
                        $treeBar = '└';
                    }
                    $level = 1;
                    $color = $this->colors[$level];
                    $info = sprintf(
                        '%s──<%s>%s</%s> %s',
                        $treeBar,
                        $color,
                        $requireName,
                        $color,
                        $require['version']
                    );
                    $this->writeTreeLine($info);

                    $treeBar = str_replace('└', ' ', $treeBar);
                    $packagesInTree = [$package['name'], $requireName];

                    $this->displayTree($require, $packagesInTree, $treeBar, $level + 1);
                }
            }
        }
    }

    /**
     * Generate the package tree
     *
     * @return array<string, array<int, array<string, mixed[]|string>>|string|null>
     */
    protected function generatePackageTree(
        PackageInterface $package,
        InstalledRepository $installedRepo,
        RepositoryInterface $remoteRepos
    ): array {
        $requires = $package->getRequires();
        ksort($requires);
        $children = [];
        foreach ($requires as $requireName => $require) {
            $packagesInTree = [$package->getName(), $requireName];

            $treeChildDesc = [
                'name' => $requireName,
                'version' => $require->getPrettyConstraint(),
            ];

            $deepChildren = $this->addTree($requireName, $require, $installedRepo, $remoteRepos, $packagesInTree);

            if ($deepChildren) {
                $treeChildDesc['requires'] = $deepChildren;
            }

            $children[] = $treeChildDesc;
        }
        $tree = [
            'name' => $package->getPrettyName(),
            'version' => $package->getPrettyVersion(),
            'description' => $package instanceof CompletePackageInterface ? $package->getDescription() : '',
        ];

        if ($children) {
            $tree['requires'] = $children;
        }

        return $tree;
    }

    /**
     * Display a package tree
     *
     * @param array<string, array<int, array<string, mixed[]|string>>|string|null>|string $package
     * @param array<int, string|mixed[]> $packagesInTree
     */
    protected function displayTree(
        $package,
        array $packagesInTree,
        string $previousTreeBar = '├',
        int $level = 1
    ): void {
        $previousTreeBar = str_replace('├', '│', $previousTreeBar);
        if (is_array($package) && isset($package['requires'])) {
            $requires = $package['requires'];
            $treeBar = $previousTreeBar . '  ├';
            $i = 0;
            $total = count($requires);
            foreach ($requires as $require) {
                $currentTree = $packagesInTree;
                $i++;
                if ($i === $total) {
                    $treeBar = $previousTreeBar . '  └';
                }
                $colorIdent = $level % count($this->colors);
                $color = $this->colors[$colorIdent];

                assert(is_string($require['name']));
                assert(is_string($require['version']));

                $circularWarn = in_array(
                    $require['name'],
                    $currentTree,
                    true
                ) ? '(circular dependency aborted here)' : '';
                $info = rtrim(sprintf(
                    '%s──<%s>%s</%s> %s %s',
                    $treeBar,
                    $color,
                    $require['name'],
                    $color,
                    $require['version'],
                    $circularWarn
                ));
                $this->writeTreeLine($info);

                $treeBar = str_replace('└', ' ', $treeBar);

                $currentTree[] = $require['name'];
                $this->displayTree($require, $currentTree, $treeBar, $level + 1);
            }
        }
    }

    /**
     * Display a package tree
     *
     * @param  string[] $packagesInTree
     * @return array<int, array<string, array<int, array<string, string>>|string>>
     */
    protected function addTree(
        string $name,
        Link $link,
        InstalledRepository $installedRepo,
        RepositoryInterface $remoteRepos,
        array $packagesInTree
    ): array {
        $children = [];
        [$package] = $this->getPackage(
            $installedRepo,
            $remoteRepos,
            $name,
            $link->getPrettyConstraint() === 'self.version' ? $link->getConstraint() : $link->getPrettyConstraint()
        );
        if (is_object($package)) {
            $requires = $package->getRequires();
            ksort($requires);
            foreach ($requires as $requireName => $require) {
                $currentTree = $packagesInTree;

                $treeChildDesc = [
                    'name' => $requireName,
                    'version' => $require->getPrettyConstraint(),
                ];

                if (!in_array($requireName, $currentTree, true)) {
                    $currentTree[] = $requireName;
                    $deepChildren = $this->addTree($requireName, $require, $installedRepo, $remoteRepos, $currentTree);
                    if ($deepChildren) {
                        $treeChildDesc['requires'] = $deepChildren;
                    }
                }

                $children[] = $treeChildDesc;
            }
        }

        return $children;
    }

    private function updateStatusToVersionStyle(string $updateStatus): string
    {
        // 'up-to-date' is printed green
        // 'semver-safe-update' is printed red
        // 'update-possible' is printed yellow
        return str_replace(['up-to-date', 'semver-safe-update', 'update-possible'], ['info', 'highlight', 'comment'], $updateStatus);
    }

    private function getUpdateStatus(PackageInterface $latestPackage, PackageInterface $package): string
    {
        if ($latestPackage->getFullPrettyVersion() === $package->getFullPrettyVersion()) {
            return 'up-to-date';
        }

        $constraint = $package->getVersion();
        if (0 !== strpos($constraint, 'dev-')) {
            $constraint = '^'.$constraint;
        }
        if ($latestPackage->getVersion() && Semver::satisfies($latestPackage->getVersion(), $constraint)) {
            // it needs an immediate semver-compliant upgrade
            return 'semver-safe-update';
        }

        // it needs an upgrade but has potential BC breaks so is not urgent
        return 'update-possible';
    }

    private function writeTreeLine(string $line): void
    {
        $io = $this->getIO();
        if (!$io->isDecorated()) {
            $line = str_replace(['└', '├', '──', '│'], ['`-', '|-', '-', '|'], $line);
        }

        $io->write($line);
    }

    /**
     * Given a package, this finds the latest package matching it
     */
    private function findLatestPackage(PackageInterface $package, Composer $composer, PlatformRepository $platformRepo, bool $majorOnly, bool $minorOnly, bool $patchOnly, PlatformRequirementFilterInterface $platformReqFilter): ?PackageInterface
    {
        // find the latest version allowed in this repo set
        $name = $package->getName();
        $versionSelector = new VersionSelector($this->getRepositorySet($composer), $platformRepo);
        $stability = $composer->getPackage()->getMinimumStability();
        $flags = $composer->getPackage()->getStabilityFlags();
        if (isset($flags[$name])) {
            $stability = array_search($flags[$name], BasePackage::STABILITIES, true);
        }

        $bestStability = $stability;
        if ($composer->getPackage()->getPreferStable()) {
            $bestStability = $package->getStability();
        }

        $targetVersion = null;
        if (0 === strpos($package->getVersion(), 'dev-')) {
            $targetVersion = $package->getVersion();

            // dev-x branches are considered to be on the latest major version always, do not look up for a new commit as that is deemed a minor upgrade (albeit risky)
            if ($majorOnly) {
                return null;
            }
        }

        if ($targetVersion === null) {
            if ($majorOnly && Preg::isMatch('{^(?P<zero_major>(?:0\.)+)?(?P<first_meaningful>\d+)\.}', $package->getVersion(), $match)) {
                $targetVersion = '>='.$match['zero_major'].(((int) $match['first_meaningful']) + 1).',<9999999-dev';
            }

            if ($minorOnly) {
                $targetVersion = '^'.$package->getVersion();
            }

            if ($patchOnly) {
                $trimmedVersion = Preg::replace('{(\.0)+$}D', '', $package->getVersion());
                $partsNeeded = substr($trimmedVersion, 0, 1) === '0' ? 4 : 3;
                while (substr_count($trimmedVersion, '.') + 1 < $partsNeeded) {
                    $trimmedVersion .= '.0';
                }
                $targetVersion = '~'.$trimmedVersion;
            }
        }

        if ($this->getIO()->isVerbose()) {
            $showWarnings = true;
        } else {
            $showWarnings = static function (PackageInterface $candidate) use ($package): bool {
                if (str_starts_with($candidate->getVersion(), 'dev-') || str_starts_with($package->getVersion(), 'dev-')) {
                    return false;
                }
                return version_compare($candidate->getVersion(), $package->getVersion(), '<=');
            };
        }
        $candidate = $versionSelector->findBestCandidate($name, $targetVersion, $bestStability, $platformReqFilter, 0, $this->getIO(), $showWarnings);
        while ($candidate instanceof AliasPackage) {
            $candidate = $candidate->getAliasOf();
        }

        return $candidate !== false ? $candidate : null;
    }

    private function getRepositorySet(Composer $composer): RepositorySet
    {
        if (!$this->repositorySet) {
            $this->repositorySet = new RepositorySet($composer->getPackage()->getMinimumStability(), $composer->getPackage()->getStabilityFlags());
            $this->repositorySet->addRepository(new CompositeRepository($composer->getRepositoryManager()->getRepositories()));
        }

        return $this->repositorySet;
    }

    private function getRelativeTime(\DateTimeInterface $releaseDate): string
    {
        if ($releaseDate->format('Ymd') === date('Ymd')) {
            return 'today';
        }

        $diff = $releaseDate->diff(new \DateTimeImmutable());
        if ($diff->days < 7) {
            return 'this week';
        }

        if ($diff->days < 14) {
            return 'last week';
        }

        if ($diff->m < 1 && $diff->days < 31) {
            return floor($diff->days / 7) . ' weeks ago';
        }

        if ($diff->y < 1) {
            return $diff->m . ' month' . ($diff->m > 1 ? 's' : '') . ' ago';
        }

        return $diff->y . ' year' . ($diff->y > 1 ? 's' : '') . ' ago';
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\Config;
use Composer\Composer;
use Composer\Package\BasePackage;
use Composer\Package\CompletePackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\Package\Version\VersionSelector;
use Composer\Pcre\Preg;
use Composer\Repository\CompositeRepository;
use Composer\Repository\RepositoryFactory;
use Composer\Repository\RepositorySet;
use Composer\Script\ScriptEvents;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Util\Filesystem;
use Composer\Util\Loop;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Creates an archive of a package for distribution.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class ArchiveCommand extends BaseCommand
{
    use CompletionTrait;

    private const FORMATS = ['tar', 'tar.gz', 'tar.bz2', 'zip'];

    protected function configure(): void
    {
        $this
            ->setName('archive')
            ->setDescription('Creates an archive of this composer package')
            ->setDefinition([
                new InputArgument('package', InputArgument::OPTIONAL, 'The package to archive instead of the current project', null, $this->suggestAvailablePackage()),
                new InputArgument('version', InputArgument::OPTIONAL, 'A version constraint to find the package to archive'),
                new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the resulting archive: tar, tar.gz, tar.bz2 or zip (default tar)', null, self::FORMATS),
                new InputOption('dir', null, InputOption::VALUE_REQUIRED, 'Write the archive to this directory'),
                new InputOption('file', null, InputOption::VALUE_REQUIRED, 'Write the archive with the given file name.'
                    .' Note that the format will be appended.'),
                new InputOption('ignore-filters', null, InputOption::VALUE_NONE, 'Ignore filters when saving package'),
            ])
            ->setHelp(
                <<<EOT
The <info>archive</info> command creates an archive of the specified format
containing the files and directories of the Composer project or the specified
package in the specified version and writes it to the specified directory.

<info>php composer.phar archive [--format=zip] [--dir=/foo] [--file=filename] [package [version]]</info>

Read more at https://getcomposer.org/doc/03-cli.md#archive
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $composer = $this->tryComposer();
        $config = null;

        if ($composer) {
            $config = $composer->getConfig();
            $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'archive', $input, $output);
            $eventDispatcher = $composer->getEventDispatcher();
            $eventDispatcher->dispatch($commandEvent->getName(), $commandEvent);
            $eventDispatcher->dispatchScript(ScriptEvents::PRE_ARCHIVE_CMD);
        }

        if (!$config) {
            $config = Factory::createConfig();
        }

        $format = $input->getOption('format') ?? $config->get('archive-format');
        $dir = $input->getOption('dir') ?? $config->get('archive-dir');

        $returnCode = $this->archive(
            $this->getIO(),
            $config,
            $input->getArgument('package'),
            $input->getArgument('version'),
            $format,
            $dir,
            $input->getOption('file'),
            $input->getOption('ignore-filters'),
            $composer
        );

        if (0 === $returnCode && $composer) {
            $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_ARCHIVE_CMD);
        }

        return $returnCode;
    }

    /**
     * @throws \Exception
     */
    protected function archive(IOInterface $io, Config $config, ?string $packageName, ?string $version, string $format, string $dest, ?string $fileName, bool $ignoreFilters, ?Composer $composer): int
    {
        if ($composer) {
            $archiveManager = $composer->getArchiveManager();
        } else {
            $factory = new Factory;
            $process = new ProcessExecutor();
            $httpDownloader = Factory::createHttpDownloader($io, $config);
            $downloadManager = $factory->createDownloadManager($io, $config, $httpDownloader, $process);
            $archiveManager = $factory->createArchiveManager($config, $downloadManager, new Loop($httpDownloader, $process));
        }

        if ($packageName) {
            $package = $this->selectPackage($io, $packageName, $version);

            if (!$package) {
                return 1;
            }
        } else {
            $package = $this->requireComposer()->getPackage();
        }

        $io->writeError('<info>Creating the archive into "'.$dest.'".</info>');
        $packagePath = $archiveManager->archive($package, $format, $dest, $fileName, $ignoreFilters);
        $fs = new Filesystem;
        $shortPath = $fs->findShortestPath(Platform::getCwd(), $packagePath, true);

        $io->writeError('Created: ', false);
        $io->write(strlen($shortPath) < strlen($packagePath) ? $shortPath : $packagePath);

        return 0;
    }

    /**
     * @return (BasePackage&CompletePackageInterface)|false
     */
    protected function selectPackage(IOInterface $io, string $packageName, ?string $version = null)
    {
        $io->writeError('<info>Searching for the specified package.</info>');

        if ($composer = $this->tryComposer()) {
            $localRepo = $composer->getRepositoryManager()->getLocalRepository();
            $repo = new CompositeRepository(array_merge([$localRepo], $composer->getRepositoryManager()->getRepositories()));
            $minStability = $composer->getPackage()->getMinimumStability();
        } else {
            $defaultRepos = RepositoryFactory::defaultReposWithDefaultManager($io);
            $io->writeError('No composer.json found in the current directory, searching packages from ' . implode(', ', array_keys($defaultRepos)));
            $repo = new CompositeRepository($defaultRepos);
            $minStability = 'stable';
        }

        if ($version !== null && Preg::isMatchStrictGroups('{@(stable|RC|beta|alpha|dev)$}i', $version, $match)) {
            $minStability = VersionParser::normalizeStability($match[1]);
            $version = (string) substr($version, 0, -strlen($match[0]));
        }

        $repoSet = new RepositorySet($minStability);
        $repoSet->addRepository($repo);
        $parser = new VersionParser();
        $constraint = $version !== null ? $parser->parseConstraints($version) : null;
        $packages = $repoSet->findPackages(strtolower($packageName), $constraint);

        if (count($packages) > 1) {
            $versionSelector = new VersionSelector($repoSet);
            $package = $versionSelector->findBestCandidate(strtolower($packageName), $version, $minStability);
            if ($package === false) {
                $package = reset($packages);
            }

            $io->writeError('<info>Found multiple matches, selected '.$package->getPrettyString().'.</info>');
            $io->writeError('Alternatives were '.implode(', ', array_map(static function ($p): string {
                return $p->getPrettyString();
            }, $packages)).'.');
            $io->writeError('<comment>Please use a more specific constraint to pick a different package.</comment>');
        } elseif (count($packages) === 1) {
            $package = reset($packages);
            $io->writeError('<info>Found an exact match '.$package->getPrettyString().'.</info>');
        } else {
            $io->writeError('<error>Could not find a package matching '.$packageName.'.</error>');

            return false;
        }

        if (!$package instanceof CompletePackageInterface) {
            throw new \LogicException('Expected a CompletePackageInterface instance but found '.get_class($package));
        }
        if (!$package instanceof BasePackage) {
            throw new \LogicException('Expected a BasePackage instance but found '.get_class($package));
        }

        return $package;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Package\CompletePackageInterface;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\RootPackageRepository;
use Composer\Repository\RepositoryFactory;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Console\Input\InputArgument;
use Composer\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Robert Schönthal <seroscho@googlemail.com>
 */
class HomeCommand extends BaseCommand
{
    use CompletionTrait;

    /**
     * @inheritDoc
     */
    protected function configure(): void
    {
        $this
            ->setName('browse')
            ->setAliases(['home'])
            ->setDescription('Opens the package\'s repository URL or homepage in your browser')
            ->setDefinition([
                new InputArgument('packages', InputArgument::IS_ARRAY, 'Package(s) to browse to.', null, $this->suggestInstalledPackage()),
                new InputOption('homepage', 'H', InputOption::VALUE_NONE, 'Open the homepage instead of the repository URL.'),
                new InputOption('show', 's', InputOption::VALUE_NONE, 'Only show the homepage or repository URL.'),
            ])
            ->setHelp(
                <<<EOT
The home command opens or shows a package's repository URL or
homepage in your default browser.

To open the homepage by default, use -H or --homepage.
To show instead of open the repository or homepage URL, use -s or --show.

Read more at https://getcomposer.org/doc/03-cli.md#browse-home
EOT
            );
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $repos = $this->initializeRepos();
        $io = $this->getIO();
        $return = 0;

        $packages = $input->getArgument('packages');
        if (count($packages) === 0) {
            $io->writeError('No package specified, opening homepage for the root package');
            $packages = [$this->requireComposer()->getPackage()->getName()];
        }

        foreach ($packages as $packageName) {
            $handled = false;
            $packageExists = false;
            foreach ($repos as $repo) {
                foreach ($repo->findPackages($packageName) as $package) {
                    $packageExists = true;
                    if ($package instanceof CompletePackageInterface && $this->handlePackage($package, $input->getOption('homepage'), $input->getOption('show'))) {
                        $handled = true;
                        break 2;
                    }
                }
            }

            if (!$packageExists) {
                $return = 1;
                $io->writeError('<warning>Package '.$packageName.' not found</warning>');
            }

            if (!$handled) {
                $return = 1;
                $io->writeError('<warning>'.($input->getOption('homepage') ? 'Invalid or missing homepage' : 'Invalid or missing repository URL').' for '.$packageName.'</warning>');
            }
        }

        return $return;
    }

    private function handlePackage(CompletePackageInterface $package, bool $showHomepage, bool $showOnly): bool
    {
        $support = $package->getSupport();
        $url = $support['source'] ?? $package->getSourceUrl();
        if (!$url || $showHomepage) {
            $url = $package->getHomepage();
        }

        if (!$url || !filter_var($url, FILTER_VALIDATE_URL)) {
            return false;
        }

        if ($showOnly) {
            $this->getIO()->write(sprintf('<info>%s</info>', $url));
        } else {
            $this->openBrowser($url);
        }

        return true;
    }

    /**
     * opens a url in your system default browser
     */
    private function openBrowser(string $url): void
    {
        $process = new ProcessExecutor($this->getIO());
        if (Platform::isWindows()) {
            $process->execute(['start', '"web"', 'explorer', $url], $output);

            return;
        }

        $linux = $process->execute(['which', 'xdg-open'], $output);
        $osx = $process->execute(['which', 'open'], $output);

        if (0 === $linux) {
            $process->execute(['xdg-open', $url], $output);
        } elseif (0 === $osx) {
            $process->execute(['open', $url], $output);
        } else {
            $this->getIO()->writeError('No suitable browser opening command found, open yourself: ' . $url);
        }
    }

    /**
     * Initializes repositories
     *
     * Returns an array of repos in order they should be checked in
     *
     * @return RepositoryInterface[]
     */
    private function initializeRepos(): array
    {
        $composer = $this->tryComposer();

        if ($composer) {
            return array_merge(
                [new RootPackageRepository(clone $composer->getPackage())], // root package
                [$composer->getRepositoryManager()->getLocalRepository()], // installed packages
                $composer->getRepositoryManager()->getRepositories() // remotes
            );
        }

        return RepositoryFactory::defaultReposWithDefaultManager($this->getIO());
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Json\JsonFile;
use Composer\Package\AliasPackage;
use Composer\Package\BasePackage;
use Composer\Package\CompletePackageInterface;
use Composer\Pcre\Preg;
use Composer\Repository\CompositeRepository;
use Composer\Semver\Constraint\MatchAllConstraint;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Nicolas Grekas <p@tchwork.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class FundCommand extends BaseCommand
{
    protected function configure(): void
    {
        $this->setName('fund')
            ->setDescription('Discover how to help fund the maintenance of your dependencies')
            ->setDefinition([
                new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['text', 'json']),
            ])
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $composer = $this->requireComposer();

        $repo = $composer->getRepositoryManager()->getLocalRepository();
        $remoteRepos = new CompositeRepository($composer->getRepositoryManager()->getRepositories());
        $fundings = [];

        $packagesToLoad = [];
        foreach ($repo->getPackages() as $package) {
            if ($package instanceof AliasPackage) {
                continue;
            }
            $packagesToLoad[$package->getName()] = new MatchAllConstraint();
        }

        // load all packages dev versions in parallel
        $result = $remoteRepos->loadPackages($packagesToLoad, ['dev' => BasePackage::STABILITY_DEV], []);

        // collect funding data from default branches
        foreach ($result['packages'] as $package) {
            if (
                !$package instanceof AliasPackage
                && $package instanceof CompletePackageInterface
                && $package->isDefaultBranch()
                && $package->getFunding()
                && isset($packagesToLoad[$package->getName()])
            ) {
                $fundings = $this->insertFundingData($fundings, $package);
                unset($packagesToLoad[$package->getName()]);
            }
        }

        // collect funding from installed packages if none was found in the default branch above
        foreach ($repo->getPackages() as $package) {
            if ($package instanceof AliasPackage || !isset($packagesToLoad[$package->getName()])) {
                continue;
            }

            if ($package instanceof CompletePackageInterface && $package->getFunding()) {
                $fundings = $this->insertFundingData($fundings, $package);
            }
        }

        ksort($fundings);

        $io = $this->getIO();

        $format = $input->getOption('format');
        if (!in_array($format, ['text', 'json'])) {
            $io->writeError(sprintf('Unsupported format "%s". See help for supported formats.', $format));

            return 1;
        }

        if ($fundings && $format === 'text') {
            $prev = null;

            $io->write('The following packages were found in your dependencies which publish funding information:');

            foreach ($fundings as $vendor => $links) {
                $io->write('');
                $io->write(sprintf("<comment>%s</comment>", $vendor));
                foreach ($links as $url => $packages) {
                    $line = sprintf('  <info>%s</info>', implode(', ', $packages));

                    if ($prev !== $line) {
                        $io->write($line);
                        $prev = $line;
                    }

                    $io->write(sprintf('    <href=%s>%s</>', OutputFormatter::escape($url), $url));
                }
            }

            $io->write("");
            $io->write("Please consider following these links and sponsoring the work of package authors!");
            $io->write("Thank you!");
        } elseif ($format === 'json') {
            $io->write(JsonFile::encode($fundings));
        } else {
            $io->write("No funding links were found in your package dependencies. This doesn't mean they don't need your support!");
        }

        return 0;
    }

    /**
     * @param mixed[] $fundings
     * @return mixed[]
     */
    private function insertFundingData(array $fundings, CompletePackageInterface $package): array
    {
        foreach ($package->getFunding() as $fundingOption) {
            [$vendor, $packageName] = explode('/', $package->getPrettyName());
            // ignore malformed funding entries
            if (empty($fundingOption['url'])) {
                continue;
            }
            $url = $fundingOption['url'];
            if (!empty($fundingOption['type']) && $fundingOption['type'] === 'github' && Preg::isMatch('{^https://github.com/([^/]+)$}', $url, $match)) {
                $url = 'https://github.com/sponsors/'.$match[1];
            }
            $fundings[$vendor][$url][] = $packageName;
        }

        return $fundings;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Console\Input\InputArgument;

/**
 * @author Davey Shafik <me@daveyshafik.com>
 */
class ExecCommand extends BaseCommand
{
    /**
     * @return void
     */
    protected function configure()
    {
        $this
            ->setName('exec')
            ->setDescription('Executes a vendored binary/script')
            ->setDefinition([
                new InputOption('list', 'l', InputOption::VALUE_NONE),
                new InputArgument('binary', InputArgument::OPTIONAL, 'The binary to run, e.g. phpunit', null, function () {
                    return $this->getBinaries(false);
                }),
                new InputArgument(
                    'args',
                    InputArgument::IS_ARRAY | InputArgument::OPTIONAL,
                    'Arguments to pass to the binary. Use <info>--</info> to separate from composer arguments'
                ),
            ])
            ->setHelp(
                <<<EOT
Executes a vendored binary/script.

Read more at https://getcomposer.org/doc/03-cli.md#exec
EOT
            )
        ;
    }

    protected function interact(InputInterface $input, OutputInterface $output): void
    {
        $binaries = $this->getBinaries(false);
        if (count($binaries) === 0) {
            return;
        }

        if ($input->getArgument('binary') !== null || $input->getOption('list')) {
            return;
        }

        $io = $this->getIO();
        /** @var int $binary */
        $binary = $io->select(
            'Binary to run: ',
            $binaries,
            '',
            1,
            'Invalid binary name "%s"'
        );

        $input->setArgument('binary', $binaries[$binary]);
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $composer = $this->requireComposer();
        if ($input->getOption('list') || null === $input->getArgument('binary')) {
            $bins = $this->getBinaries(true);
            if ([] === $bins) {
                $binDir = $composer->getConfig()->get('bin-dir');

                throw new \RuntimeException("No binaries found in composer.json or in bin-dir ($binDir)");
            }

            $this->getIO()->write(
                <<<EOT
<comment>Available binaries:</comment>
EOT
            );

            foreach ($bins as $bin) {
                $this->getIO()->write(
                    <<<EOT
<info>- $bin</info>
EOT
                );
            }

            return 0;
        }

        $binary = $input->getArgument('binary');

        $dispatcher = $composer->getEventDispatcher();
        $dispatcher->addListener('__exec_command', $binary);

        // If the CWD was modified, we restore it to what it was initially, as it was
        // most likely modified by the global command, and we want exec to run in the local working directory
        // not the global one
        if (getcwd() !== $this->getApplication()->getInitialWorkingDirectory() && $this->getApplication()->getInitialWorkingDirectory() !== false) {
            try {
                chdir($this->getApplication()->getInitialWorkingDirectory());
            } catch (\Exception $e) {
                throw new \RuntimeException('Could not switch back to working directory "'.$this->getApplication()->getInitialWorkingDirectory().'"', 0, $e);
            }
        }

        return $dispatcher->dispatchScript('__exec_command', true, $input->getArgument('args'));
    }

    /**
     * @return list<string>
     */
    private function getBinaries(bool $forDisplay): array
    {
        $composer = $this->requireComposer();
        $binDir = $composer->getConfig()->get('bin-dir');
        $bins = glob($binDir . '/*');
        $localBins = $composer->getPackage()->getBinaries();
        if ($forDisplay) {
            $localBins = array_map(static function ($e) {
                return "$e (local)";
            }, $localBins);
        }

        $binaries = [];
        foreach (array_merge($bins, $localBins) as $bin) {
            // skip .bat copies
            if (isset($previousBin) && $bin === $previousBin.'.bat') {
                continue;
            }

            $previousBin = $bin;
            $binaries[] = basename($bin);
        }

        return $binaries;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Advisory\Auditor;
use Composer\Composer;
use Composer\Factory;
use Composer\Config;
use Composer\Downloader\TransportException;
use Composer\IO\BufferIO;
use Composer\Json\JsonFile;
use Composer\Json\JsonValidationException;
use Composer\Package\Locker;
use Composer\Package\RootPackage;
use Composer\Package\Version\VersionParser;
use Composer\Pcre\Preg;
use Composer\Repository\ComposerRepository;
use Composer\Repository\FilesystemRepository;
use Composer\Repository\PlatformRepository;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Repository\RepositorySet;
use Composer\Repository\RootPackageRepository;
use Composer\Util\ConfigValidator;
use Composer\Util\Git;
use Composer\Util\IniHelper;
use Composer\Util\ProcessExecutor;
use Composer\Util\HttpDownloader;
use Composer\Util\StreamContextFactory;
use Composer\Util\Platform;
use Composer\SelfUpdate\Keys;
use Composer\SelfUpdate\Versions;
use Composer\IO\NullIO;
use Composer\Package\CompletePackageInterface;
use Composer\XdebugHandler\XdebugHandler;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\ExecutableFinder;
use Composer\Util\Http\ProxyManager;
use Composer\Util\Http\RequestProxy;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class DiagnoseCommand extends BaseCommand
{
    /** @var HttpDownloader */
    protected $httpDownloader;

    /** @var ProcessExecutor */
    protected $process;

    /** @var int */
    protected $exitCode = 0;

    protected function configure(): void
    {
        $this
            ->setName('diagnose')
            ->setDescription('Diagnoses the system to identify common errors')
            ->setHelp(
                <<<EOT
The <info>diagnose</info> command checks common errors to help debugging problems.

The process exit code will be 1 in case of warnings and 2 for errors.

Read more at https://getcomposer.org/doc/03-cli.md#diagnose
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $composer = $this->tryComposer();
        $io = $this->getIO();

        if ($composer) {
            $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'diagnose', $input, $output);
            $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

            $io->write('Checking composer.json: ', false);
            $this->outputResult($this->checkComposerSchema());

            if ($composer->getLocker()->isLocked()) {
                $io->write('Checking composer.lock: ', false);
                $this->outputResult($this->checkComposerLockSchema($composer->getLocker()));
            }

            $this->process = $composer->getLoop()->getProcessExecutor() ?? new ProcessExecutor($io);
        } else {
            $this->process = new ProcessExecutor($io);
        }

        if ($composer) {
            $config = $composer->getConfig();
        } else {
            $config = Factory::createConfig();
        }

        $config->merge(['config' => ['secure-http' => false]], Config::SOURCE_COMMAND);
        $config->prohibitUrlByConfig('http://repo.packagist.org', new NullIO);

        $this->httpDownloader = Factory::createHttpDownloader($io, $config);

        $io->write('Checking platform settings: ', false);
        $this->outputResult($this->checkPlatform());

        $io->write('Checking git settings: ', false);
        $this->outputResult($this->checkGit());

        $io->write('Checking http connectivity to packagist: ', false);
        $this->outputResult($this->checkHttp('http', $config));

        $io->write('Checking https connectivity to packagist: ', false);
        $this->outputResult($this->checkHttp('https', $config));

        foreach ($config->getRepositories() as $repo) {
            if (($repo['type'] ?? null) === 'composer' && isset($repo['url'])) {
                $composerRepo = new ComposerRepository($repo, $this->getIO(), $config, $this->httpDownloader);
                $reflMethod = new \ReflectionMethod($composerRepo, 'getPackagesJsonUrl');
                if (PHP_VERSION_ID < 80100) {
                    $reflMethod->setAccessible(true);
                }
                $url = $reflMethod->invoke($composerRepo);
                if (!str_starts_with($url, 'http')) {
                    continue;
                }
                if (str_starts_with($url, 'https://repo.packagist.org')) {
                    continue;
                }
                $io->write('Checking connectivity to ' . $repo['url'].': ', false);
                $this->outputResult($this->checkComposerRepo($url, $config));
            }
        }

        $proxyManager = ProxyManager::getInstance();
        $protos = $config->get('disable-tls') === true ? ['http'] : ['http', 'https'];
        try {
            foreach ($protos as $proto) {
                $proxy = $proxyManager->getProxyForRequest($proto.'://repo.packagist.org');
                if ($proxy->getStatus() !== '') {
                    $type = $proxy->isSecure() ? 'HTTPS' : 'HTTP';
                    $io->write('Checking '.$type.' proxy with '.$proto.': ', false);
                    $this->outputResult($this->checkHttpProxy($proxy, $proto));
                }
            }
        } catch (TransportException $e) {
            $io->write('Checking HTTP proxy: ', false);
            $status = $this->checkConnectivityAndComposerNetworkHttpEnablement();
            $this->outputResult(is_string($status) ? $status : $e);
        }

        if (count($oauth = $config->get('github-oauth')) > 0) {
            foreach ($oauth as $domain => $token) {
                $io->write('Checking '.$domain.' oauth access: ', false);
                $this->outputResult($this->checkGithubOauth($domain, $token));
            }
        } else {
            $io->write('Checking github.com rate limit: ', false);
            try {
                $rate = $this->getGithubRateLimit('github.com');
                if (!is_array($rate)) {
                    $this->outputResult($rate);
                } elseif (10 > $rate['remaining']) {
                    $io->write('<warning>WARNING</warning>');
                    $io->write(sprintf(
                        '<comment>GitHub has a rate limit on their API. '
                        . 'You currently have <options=bold>%u</options=bold> '
                        . 'out of <options=bold>%u</options=bold> requests left.' . PHP_EOL
                        . 'See https://developer.github.com/v3/#rate-limiting and also' . PHP_EOL
                        . '    https://getcomposer.org/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens</comment>',
                        $rate['remaining'],
                        $rate['limit']
                    ));
                } else {
                    $this->outputResult(true);
                }
            } catch (\Exception $e) {
                if ($e instanceof TransportException && $e->getCode() === 401) {
                    $this->outputResult('<comment>The oauth token for github.com seems invalid, run "composer config --global --unset github-oauth.github.com" to remove it</comment>');
                } else {
                    $this->outputResult($e);
                }
            }
        }

        $io->write('Checking disk free space: ', false);
        $this->outputResult($this->checkDiskSpace($config));

        if (strpos(__FILE__, 'phar:') === 0) {
            $io->write('Checking pubkeys: ', false);
            $this->outputResult($this->checkPubKeys($config));

            $io->write('Checking Composer version: ', false);
            $this->outputResult($this->checkVersion($config));
        }

        $io->write('Checking Composer and its dependencies for vulnerabilities: ', false);
        $this->outputResult($this->checkComposerAudit($config));

        $io->write(sprintf('Composer version: <comment>%s</comment>', Composer::getVersion()));

        $platformOverrides = $config->get('platform') ?: [];
        $platformRepo = new PlatformRepository([], $platformOverrides);
        $phpPkg = $platformRepo->findPackage('php', '*');
        $phpVersion = $phpPkg->getPrettyVersion();
        if ($phpPkg instanceof CompletePackageInterface && str_contains((string) $phpPkg->getDescription(), 'overridden')) {
            $phpVersion .= ' - ' . $phpPkg->getDescription();
        }

        $io->write(sprintf('PHP version: <comment>%s</comment>', $phpVersion));

        if (defined('PHP_BINARY')) {
            $io->write(sprintf('PHP binary path: <comment>%s</comment>', PHP_BINARY));
        }

        $io->write('OpenSSL version: ' . (defined('OPENSSL_VERSION_TEXT') ? '<comment>'.OPENSSL_VERSION_TEXT.'</comment>' : '<error>missing</error>'));
        $io->write('curl version: ' . $this->getCurlVersion());

        $finder = new ExecutableFinder;
        $hasSystemUnzip = (bool) $finder->find('unzip');
        $bin7zip = '';
        if ($hasSystem7zip = (bool) $finder->find('7z', null, ['C:\Program Files\7-Zip'])) {
            $bin7zip = '7z';
        }
        if (!Platform::isWindows() && !$hasSystem7zip && $hasSystem7zip = (bool) $finder->find('7zz')) {
            $bin7zip = '7zz';
        }

        $io->write(
            'zip: ' . (extension_loaded('zip') ? '<comment>extension present</comment>' : '<comment>extension not loaded</comment>')
            . ', ' . ($hasSystemUnzip ? '<comment>unzip present</comment>' : '<comment>unzip not available</comment>')
            . ', ' . ($hasSystem7zip ? '<comment>7-Zip present ('.$bin7zip.')</comment>' : '<comment>7-Zip not available</comment>')
            . (($hasSystem7zip || $hasSystemUnzip) && !function_exists('proc_open') ? ', <warning>proc_open is disabled or not present, unzip/7-z will not be usable</warning>' : '')
        );

        return $this->exitCode;
    }

    /**
     * @return string|true
     */
    private function checkComposerSchema()
    {
        $validator = new ConfigValidator($this->getIO());
        [$errors, , $warnings] = $validator->validate(Factory::getComposerFile());

        if ($errors || $warnings) {
            $messages = [
                'error' => $errors,
                'warning' => $warnings,
            ];

            $output = '';
            foreach ($messages as $style => $msgs) {
                foreach ($msgs as $msg) {
                    $output .= '<' . $style . '>' . $msg . '</' . $style . '>' . PHP_EOL;
                }
            }

            return rtrim($output);
        }

        return true;
    }

    /**
     * @return string|true
     */
    private function checkComposerLockSchema(Locker $locker)
    {
        $json = $locker->getJsonFile();

        try {
            $json->validateSchema(JsonFile::LOCK_SCHEMA);
        } catch (JsonValidationException $e) {
            $output = '';
            foreach ($e->getErrors() as $error) {
                $output .= '<error>'.$error.'</error>'.PHP_EOL;
            }

            return trim($output);
        }

        return true;
    }

    private function checkGit(): string
    {
        if (!function_exists('proc_open')) {
            return '<comment>proc_open is not available, git cannot be used</comment>';
        }

        $this->process->execute(['git', 'config', 'color.ui'], $output);
        if (strtolower(trim($output)) === 'always') {
            return '<comment>Your git color.ui setting is set to always, this is known to create issues. Use "git config --global color.ui true" to set it correctly.</comment>';
        }

        $gitVersion = Git::getVersion($this->process);
        if (null === $gitVersion) {
            return '<comment>No git process found</>';
        }

        if (version_compare('2.24.0', $gitVersion, '>')) {
            return '<warning>Your git version ('.$gitVersion.') is too old and possibly will cause issues. Please upgrade to git 2.24 or above</>';
        }

        return '<info>OK</> <comment>git version '.$gitVersion.'</>';
    }

    /**
     * @return string|string[]|true
     */
    private function checkHttp(string $proto, Config $config)
    {
        $result = $this->checkConnectivityAndComposerNetworkHttpEnablement();
        if ($result !== true) {
            return $result;
        }

        $result = [];
        if ($proto === 'https' && $config->get('disable-tls') === true) {
            $tlsWarning = '<warning>Composer is configured to disable SSL/TLS protection. This will leave remote HTTPS requests vulnerable to Man-In-The-Middle attacks.</warning>';
        }

        try {
            $this->httpDownloader->get($proto . '://repo.packagist.org/packages.json');
        } catch (TransportException $e) {
            $hints = HttpDownloader::getExceptionHints($e);
            if (null !== $hints && count($hints) > 0) {
                foreach ($hints as $hint) {
                    $result[] = $hint;
                }
            }

            $result[] = '<error>[' . get_class($e) . '] ' . $e->getMessage() . '</error>';
        }

        if (isset($tlsWarning)) {
            $result[] = $tlsWarning;
        }

        if (count($result) > 0) {
            return $result;
        }

        return true;
    }

    /**
     * @return string|string[]|true
     */
    private function checkComposerRepo(string $url, Config $config)
    {
        $result = $this->checkConnectivityAndComposerNetworkHttpEnablement();
        if ($result !== true) {
            return $result;
        }

        $result = [];
        if (str_starts_with($url, 'https://') && $config->get('disable-tls') === true) {
            $tlsWarning = '<warning>Composer is configured to disable SSL/TLS protection. This will leave remote HTTPS requests vulnerable to Man-In-The-Middle attacks.</warning>';
        }

        try {
            $this->httpDownloader->get($url);
        } catch (TransportException $e) {
            $hints = HttpDownloader::getExceptionHints($e);
            if (null !== $hints && count($hints) > 0) {
                foreach ($hints as $hint) {
                    $result[] = $hint;
                }
            }

            $result[] = '<error>[' . get_class($e) . '] ' . $e->getMessage() . '</error>';
        }

        if (isset($tlsWarning)) {
            $result[] = $tlsWarning;
        }

        if (count($result) > 0) {
            return $result;
        }

        return true;
    }

    /**
     * @return string|\Exception
     */
    private function checkHttpProxy(RequestProxy $proxy, string $protocol)
    {
        $result = $this->checkConnectivityAndComposerNetworkHttpEnablement();
        if ($result !== true) {
            return $result;
        }

        try {
            $proxyStatus = $proxy->getStatus();

            if ($proxy->isExcludedByNoProxy()) {
                return '<info>SKIP</> <comment>Because repo.packagist.org is '.$proxyStatus.'</>';
            }

            $json = $this->httpDownloader->get($protocol.'://repo.packagist.org/packages.json')->decodeJson();
            if (isset($json['provider-includes'])) {
                $hash = reset($json['provider-includes']);
                $hash = $hash['sha256'];
                $path = str_replace('%hash%', $hash, key($json['provider-includes']));
                $provider = $this->httpDownloader->get($protocol.'://repo.packagist.org/'.$path)->getBody();

                if (hash('sha256', $provider) !== $hash) {
                    return '<warning>It seems that your proxy ('.$proxyStatus.') is modifying '.$protocol.' traffic on the fly</>';
                }
            }

            return '<info>OK</> <comment>'.$proxyStatus.'</>';
        } catch (\Exception $e) {
            return $e;
        }
    }

    /**
     * @return string|\Exception
     */
    private function checkGithubOauth(string $domain, string $token)
    {
        $result = $this->checkConnectivityAndComposerNetworkHttpEnablement();
        if ($result !== true) {
            return $result;
        }

        $this->getIO()->setAuthentication($domain, $token, 'x-oauth-basic');
        try {
            $url = $domain === 'github.com' ? 'https://api.'.$domain.'/' : 'https://'.$domain.'/api/v3/';

            $response = $this->httpDownloader->get($url, [
                'retry-auth-failure' => false,
            ]);

            $expiration = $response->getHeader('github-authentication-token-expiration');

            if ($expiration === null) {
                return '<info>OK</> <comment>does not expire</>';
            }

            return '<info>OK</> <comment>expires on '. $expiration .'</>';
        } catch (\Exception $e) {
            if ($e instanceof TransportException && $e->getCode() === 401) {
                return '<comment>The oauth token for '.$domain.' seems invalid, run "composer config --global --unset github-oauth.'.$domain.'" to remove it</comment>';
            }

            return $e;
        }
    }

    /**
     * @param  string             $token
     * @throws TransportException
     * @return mixed|string
     */
    private function getGithubRateLimit(string $domain, ?string $token = null)
    {
        $result = $this->checkConnectivityAndComposerNetworkHttpEnablement();
        if ($result !== true) {
            return $result;
        }

        if ($token) {
            $this->getIO()->setAuthentication($domain, $token, 'x-oauth-basic');
        }

        $url = $domain === 'github.com' ? 'https://api.'.$domain.'/rate_limit' : 'https://'.$domain.'/api/rate_limit';
        $data = $this->httpDownloader->get($url, ['retry-auth-failure' => false])->decodeJson();

        return $data['resources']['core'];
    }

    /**
     * @return string|true
     */
    private function checkDiskSpace(Config $config)
    {
        if (!function_exists('disk_free_space')) {
            return true;
        }

        $minSpaceFree = 1024 * 1024;
        if ((($df = @disk_free_space($dir = $config->get('home'))) !== false && $df < $minSpaceFree)
            || (($df = @disk_free_space($dir = $config->get('vendor-dir'))) !== false && $df < $minSpaceFree)
        ) {
            return '<error>The disk hosting '.$dir.' is full</error>';
        }

        return true;
    }

    /**
     * @return string[]|true
     */
    private function checkPubKeys(Config $config)
    {
        $home = $config->get('home');
        $errors = [];
        $io = $this->getIO();

        if (file_exists($home.'/keys.tags.pub') && file_exists($home.'/keys.dev.pub')) {
            $io->write('');
        }

        if (file_exists($home.'/keys.tags.pub')) {
            $io->write('Tags Public Key Fingerprint: ' . Keys::fingerprint($home.'/keys.tags.pub'));
        } else {
            $errors[] = '<error>Missing pubkey for tags verification</error>';
        }

        if (file_exists($home.'/keys.dev.pub')) {
            $io->write('Dev Public Key Fingerprint: ' . Keys::fingerprint($home.'/keys.dev.pub'));
        } else {
            $errors[] = '<error>Missing pubkey for dev verification</error>';
        }

        if ($errors) {
            $errors[] = '<error>Run composer self-update --update-keys to set them up</error>';
        }

        return $errors ?: true;
    }

    /**
     * @return string|\Exception|true
     */
    private function checkVersion(Config $config)
    {
        $result = $this->checkConnectivityAndComposerNetworkHttpEnablement();
        if ($result !== true) {
            return $result;
        }

        $versionsUtil = new Versions($config, $this->httpDownloader);
        try {
            $latest = $versionsUtil->getLatest();
        } catch (\Exception $e) {
            return $e;
        }

        if (Composer::VERSION !== $latest['version'] && Composer::VERSION !== '@package_version@') {
            return '<comment>You are not running the latest '.$versionsUtil->getChannel().' version, run `composer self-update` to update ('.Composer::VERSION.' => '.$latest['version'].')</comment>';
        }

        return true;
    }

    /**
     * @return string|true
     */
    private function checkComposerAudit(Config $config)
    {
        $result = $this->checkConnectivityAndComposerNetworkHttpEnablement();
        if ($result !== true) {
            return $result;
        }

        $auditor = new Auditor();
        $repoSet = new RepositorySet();
        $installedJson = new JsonFile(__DIR__ . '/../../../vendor/composer/installed.json');
        if (!$installedJson->exists()) {
            return '<warning>Could not find Composer\'s installed.json, this must be a non-standard Composer installation.</>';
        }

        $localRepo = new FilesystemRepository($installedJson);
        $version = Composer::getVersion();
        $packages = $localRepo->getCanonicalPackages();
        if ($version !== '@package_version@') {
            $versionParser = new VersionParser();
            $normalizedVersion = $versionParser->normalize($version);
            $rootPkg = new RootPackage('composer/composer', $normalizedVersion, $version);
            $packages[] = $rootPkg;
        }
        $repoSet->addRepository(new ComposerRepository(['type' => 'composer', 'url' => 'https://packagist.org'], new NullIO(), $config, $this->httpDownloader));

        try {
            $io = new BufferIO();
            $result = $auditor->audit($io, $repoSet, $packages, Auditor::FORMAT_TABLE, true, [], Auditor::ABANDONED_IGNORE);
        } catch (\Throwable $e) {
            return '<highlight>Failed performing audit: '.$e->getMessage().'</>';
        }

        if ($result > 0) {
            return '<highlight>Audit found some issues:</>' . PHP_EOL . $io->getOutput();
        }

        return true;
    }

    private function getCurlVersion(): string
    {
        if (extension_loaded('curl')) {
            if (!HttpDownloader::isCurlEnabled()) {
                return '<error>disabled via disable_functions, using php streams fallback, which reduces performance</error>';
            }

            $version = curl_version();
            $libzVersion = isset($version['libz_version']) && $version['libz_version'] !== '' ? $version['libz_version'] : 'missing';
            $brotliVersion = isset($version['brotli_version']) && $version['brotli_version'] !== '' ? $version['brotli_version'] : 'missing';
            $sslVersion = isset($version['ssl_version']) && $version['ssl_version'] !== '' ? $version['ssl_version'] : 'missing';
            $hasZstd = isset($version['features']) && defined('CURL_VERSION_ZSTD') && 0 !== ($version['features'] & CURL_VERSION_ZSTD);

            return '<comment>'.$version['version'].'</comment> '.
                'libz <comment>'.$libzVersion.'</comment> '.
                'brotli <comment>'.$brotliVersion.'</comment> '.
                'zstd <comment>'.($hasZstd ? 'supported' : 'missing').'</comment> '.
                'ssl <comment>'.$sslVersion.'</comment>';
        }

        return '<error>missing, using php streams fallback, which reduces performance</error>';
    }

    /**
     * @param bool|string|string[]|\Exception $result
     */
    private function outputResult($result): void
    {
        $io = $this->getIO();
        if (true === $result) {
            $io->write('<info>OK</info>');

            return;
        }

        $hadError = false;
        $hadWarning = false;
        if ($result instanceof \Exception) {
            $result = '<error>['.get_class($result).'] '.$result->getMessage().'</error>';
        }

        if (!$result) {
            // falsey results should be considered as an error, even if there is nothing to output
            $hadError = true;
        } else {
            if (!is_array($result)) {
                $result = [$result];
            }
            foreach ($result as $message) {
                if (false !== strpos($message, '<error>')) {
                    $hadError = true;
                } elseif (false !== strpos($message, '<warning>')) {
                    $hadWarning = true;
                }
            }
        }

        if ($hadError) {
            $io->write('<error>FAIL</error>');
            $this->exitCode = max($this->exitCode, 2);
        } elseif ($hadWarning) {
            $io->write('<warning>WARNING</warning>');
            $this->exitCode = max($this->exitCode, 1);
        }

        if ($result) {
            foreach ($result as $message) {
                $io->write(trim($message));
            }
        }
    }

    /**
     * @return string|true
     */
    private function checkPlatform()
    {
        $output = '';
        $out = static function ($msg, $style) use (&$output): void {
            $output .= '<'.$style.'>'.$msg.'</'.$style.'>'.PHP_EOL;
        };

        // code below taken from getcomposer.org/installer, any changes should be made there and replicated here
        $errors = [];
        $warnings = [];
        $displayIniMessage = false;

        $iniMessage = PHP_EOL.PHP_EOL.IniHelper::getMessage();
        $iniMessage .= PHP_EOL.'If you can not modify the ini file, you can also run `php -d option=value` to modify ini values on the fly. You can use -d multiple times.';

        if (!function_exists('json_decode')) {
            $errors['json'] = true;
        }

        if (!extension_loaded('Phar')) {
            $errors['phar'] = true;
        }

        if (!extension_loaded('filter')) {
            $errors['filter'] = true;
        }

        if (!extension_loaded('hash')) {
            $errors['hash'] = true;
        }

        if (!extension_loaded('iconv') && !extension_loaded('mbstring')) {
            $errors['iconv_mbstring'] = true;
        }

        if (!filter_var(ini_get('allow_url_fopen'), FILTER_VALIDATE_BOOLEAN)) {
            $errors['allow_url_fopen'] = true;
        }

        if (extension_loaded('ionCube Loader') && ioncube_loader_iversion() < 40009) {
            $errors['ioncube'] = ioncube_loader_version();
        }

        if (\PHP_VERSION_ID < 70205) {
            $errors['php'] = PHP_VERSION;
        }

        if (!extension_loaded('openssl')) {
            $errors['openssl'] = true;
        }

        if (extension_loaded('openssl') && OPENSSL_VERSION_NUMBER < 0x1000100f) {
            $warnings['openssl_version'] = true;
        }

        if (!defined('HHVM_VERSION') && !extension_loaded('apcu') && filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN)) {
            $warnings['apc_cli'] = true;
        }

        if (!extension_loaded('zlib')) {
            $warnings['zlib'] = true;
        }

        ob_start();
        phpinfo(INFO_GENERAL);
        $phpinfo = ob_get_clean();
        if (is_string($phpinfo) && Preg::isMatchStrictGroups('{Configure Command(?: *</td><td class="v">| *=> *)(.*?)(?:</td>|$)}m', $phpinfo, $match)) {
            $configure = $match[1];

            if (str_contains($configure, '--enable-sigchild')) {
                $warnings['sigchild'] = true;
            }

            if (str_contains($configure, '--with-curlwrappers')) {
                $warnings['curlwrappers'] = true;
            }
        }

        if (filter_var(ini_get('xdebug.profiler_enabled'), FILTER_VALIDATE_BOOLEAN)) {
            $warnings['xdebug_profile'] = true;
        } elseif (XdebugHandler::isXdebugActive()) {
            $warnings['xdebug_loaded'] = true;
        }

        if (defined('PHP_WINDOWS_VERSION_BUILD')
            && (version_compare(PHP_VERSION, '7.2.23', '<')
            || (version_compare(PHP_VERSION, '7.3.0', '>=')
            && version_compare(PHP_VERSION, '7.3.10', '<')))) {
            $warnings['onedrive'] = PHP_VERSION;
        }

        if (extension_loaded('uopz')
            && !(filter_var(ini_get('uopz.disable'), FILTER_VALIDATE_BOOLEAN)
            || filter_var(ini_get('uopz.exit'), FILTER_VALIDATE_BOOLEAN))) {
            $warnings['uopz'] = true;
        }

        if (!empty($errors)) {
            foreach ($errors as $error => $current) {
                switch ($error) {
                    case 'json':
                        $text = PHP_EOL."The json extension is missing.".PHP_EOL;
                        $text .= "Install it or recompile php without --disable-json";
                        break;

                    case 'phar':
                        $text = PHP_EOL."The phar extension is missing.".PHP_EOL;
                        $text .= "Install it or recompile php without --disable-phar";
                        break;

                    case 'filter':
                        $text = PHP_EOL."The filter extension is missing.".PHP_EOL;
                        $text .= "Install it or recompile php without --disable-filter";
                        break;

                    case 'hash':
                        $text = PHP_EOL."The hash extension is missing.".PHP_EOL;
                        $text .= "Install it or recompile php without --disable-hash";
                        break;

                    case 'iconv_mbstring':
                        $text = PHP_EOL."The iconv OR mbstring extension is required and both are missing.".PHP_EOL;
                        $text .= "Install either of them or recompile php without --disable-iconv";
                        break;

                    case 'php':
                        $text = PHP_EOL."Your PHP ({$current}) is too old, you must upgrade to PHP 7.2.5 or higher.";
                        break;

                    case 'allow_url_fopen':
                        $text = PHP_EOL."The allow_url_fopen setting is incorrect.".PHP_EOL;
                        $text .= "Add the following to the end of your `php.ini`:".PHP_EOL;
                        $text .= "    allow_url_fopen = On";
                        $displayIniMessage = true;
                        break;

                    case 'ioncube':
                        $text = PHP_EOL."Your ionCube Loader extension ($current) is incompatible with Phar files.".PHP_EOL;
                        $text .= "Upgrade to ionCube 4.0.9 or higher or remove this line (path may be different) from your `php.ini` to disable it:".PHP_EOL;
                        $text .= "    zend_extension = /usr/lib/php5/20090626+lfs/ioncube_loader_lin_5.3.so";
                        $displayIniMessage = true;
                        break;

                    case 'openssl':
                        $text = PHP_EOL."The openssl extension is missing, which means that secure HTTPS transfers are impossible.".PHP_EOL;
                        $text .= "If possible you should enable it or recompile php with --with-openssl";
                        break;

                    default:
                        throw new \InvalidArgumentException(sprintf("DiagnoseCommand: Unknown error type \"%s\". Please report at https://github.com/composer/composer/issues/new.", $error));
                }
                $out($text, 'error');
            }

            $output .= PHP_EOL;
        }

        if (!empty($warnings)) {
            foreach ($warnings as $warning => $current) {
                switch ($warning) {
                    case 'apc_cli':
                        $text = "The apc.enable_cli setting is incorrect.".PHP_EOL;
                        $text .= "Add the following to the end of your `php.ini`:".PHP_EOL;
                        $text .= "  apc.enable_cli = Off";
                        $displayIniMessage = true;
                        break;

                    case 'zlib':
                        $text = 'The zlib extension is not loaded, this can slow down Composer a lot.'.PHP_EOL;
                        $text .= 'If possible, enable it or recompile php with --with-zlib'.PHP_EOL;
                        $displayIniMessage = true;
                        break;

                    case 'sigchild':
                        $text = "PHP was compiled with --enable-sigchild which can cause issues on some platforms.".PHP_EOL;
                        $text .= "Recompile it without this flag if possible, see also:".PHP_EOL;
                        $text .= "  https://bugs.php.net/bug.php?id=22999";
                        break;

                    case 'curlwrappers':
                        $text = "PHP was compiled with --with-curlwrappers which will cause issues with HTTP authentication and GitHub.".PHP_EOL;
                        $text .= " Recompile it without this flag if possible";
                        break;

                    case 'openssl_version':
                        // Attempt to parse version number out, fallback to whole string value.
                        $opensslVersion = strstr(trim(strstr(OPENSSL_VERSION_TEXT, ' ')), ' ', true);
                        $opensslVersion = $opensslVersion ?: OPENSSL_VERSION_TEXT;

                        $text = "The OpenSSL library ({$opensslVersion}) used by PHP does not support TLSv1.2 or TLSv1.1.".PHP_EOL;
                        $text .= "If possible you should upgrade OpenSSL to version 1.0.1 or above.";
                        break;

                    case 'xdebug_loaded':
                        $text = "The xdebug extension is loaded, this can slow down Composer a little.".PHP_EOL;
                        $text .= " Disabling it when using Composer is recommended.";
                        break;

                    case 'xdebug_profile':
                        $text = "The xdebug.profiler_enabled setting is enabled, this can slow down Composer a lot.".PHP_EOL;
                        $text .= "Add the following to the end of your `php.ini` to disable it:".PHP_EOL;
                        $text .= "  xdebug.profiler_enabled = 0";
                        $displayIniMessage = true;
                        break;

                    case 'onedrive':
                        $text = "The Windows OneDrive folder is not supported on PHP versions below 7.2.23 and 7.3.10.".PHP_EOL;
                        $text .= "Upgrade your PHP ({$current}) to use this location with Composer.".PHP_EOL;
                        break;

                    case 'uopz':
                        $text = "The uopz extension ignores exit calls and may not work with all Composer commands.".PHP_EOL;
                        $text .= "Disabling it when using Composer is recommended.";
                        break;

                    default:
                        throw new \InvalidArgumentException(sprintf("DiagnoseCommand: Unknown warning type \"%s\". Please report at https://github.com/composer/composer/issues/new.", $warning));
                }
                $out($text, 'comment');
            }
        }

        if ($displayIniMessage) {
            $out($iniMessage, 'comment');
        }

        if (in_array(Platform::getEnv('COMPOSER_IPRESOLVE'), ['4', '6'], true)) {
            $warnings['ipresolve'] = true;
            $out('The COMPOSER_IPRESOLVE env var is set to ' . Platform::getEnv('COMPOSER_IPRESOLVE') .' which may result in network failures below.', 'comment');
        }

        return count($warnings) === 0 && count($errors) === 0 ? true : $output;
    }

    /**
     * Check if allow_url_fopen is ON
     *
     * @return string|true
     */
    private function checkConnectivity()
    {
        if (!ini_get('allow_url_fopen')) {
            return '<info>SKIP</> <comment>Because allow_url_fopen is missing.</>';
        }

        return true;
    }

    /**
     * @return string|true
     */
    private function checkConnectivityAndComposerNetworkHttpEnablement()
    {
        $result = $this->checkConnectivity();
        if ($result !== true) {
            return $result;
        }

        $result = $this->checkComposerNetworkHttpEnablement();
        if ($result !== true) {
            return $result;
        }

        return true;
    }

    /**
     * Check if Composer network is enabled for HTTP/S
     *
     * @return string|true
     */
    private function checkComposerNetworkHttpEnablement()
    {
        if ((bool) Platform::getEnv('COMPOSER_DISABLE_NETWORK')) {
            return '<info>SKIP</> <comment>Network is disabled by COMPOSER_DISABLE_NETWORK.</>';
        }

        return true;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Package\AliasPackage;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class DumpAutoloadCommand extends BaseCommand
{
    /**
     * @return void
     */
    protected function configure()
    {
        $this
            ->setName('dump-autoload')
            ->setAliases(['dumpautoload'])
            ->setDescription('Dumps the autoloader')
            ->setDefinition([
                new InputOption('optimize', 'o', InputOption::VALUE_NONE, 'Optimizes PSR0 and PSR4 packages to be loaded with classmaps too, good for production.'),
                new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize`.'),
                new InputOption('apcu', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'),
                new InputOption('apcu-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu'),
                new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything.'),
                new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables autoload-dev rules. Composer will by default infer this automatically according to the last install or update --no-dev state.'),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables autoload-dev rules. Composer will by default infer this automatically according to the last install or update --no-dev state.'),
                new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'),
                new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'),
                new InputOption('strict-psr', null, InputOption::VALUE_NONE, 'Return a failed status code (1) if PSR-4 or PSR-0 mapping errors are present. Requires --optimize to work.'),
                new InputOption('strict-ambiguous', null, InputOption::VALUE_NONE, 'Return a failed status code (2) if the same class is found in multiple files. Requires --optimize to work.'),
            ])
            ->setHelp(
                <<<EOT
<info>php composer.phar dump-autoload</info>

Read more at https://getcomposer.org/doc/03-cli.md#dump-autoload-dumpautoload
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $composer = $this->requireComposer();

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'dump-autoload', $input, $output);
        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        $installationManager = $composer->getInstallationManager();
        $localRepo = $composer->getRepositoryManager()->getLocalRepository();
        $package = $composer->getPackage();
        $config = $composer->getConfig();

        $missingDependencies = false;
        foreach ($localRepo->getCanonicalPackages() as $localPkg) {
            $installPath = $installationManager->getInstallPath($localPkg);
            if ($installPath !== null && file_exists($installPath) === false) {
                $missingDependencies = true;
                $this->getIO()->write('<warning>Not all dependencies are installed. Make sure to run a "composer install" to install missing dependencies</warning>');

                break;
            }
        }

        $optimize = $input->getOption('optimize') || $config->get('optimize-autoloader');
        $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative');
        $apcuPrefix = $input->getOption('apcu-prefix');
        $apcu = $apcuPrefix !== null || $input->getOption('apcu') || $config->get('apcu-autoloader');

        if ($input->getOption('strict-psr') && !$optimize && !$authoritative) {
            throw new \InvalidArgumentException('--strict-psr mode only works with optimized autoloader, use --optimize or --classmap-authoritative if you want a strict return value.');
        }
        if ($input->getOption('strict-ambiguous') && !$optimize && !$authoritative) {
            throw new \InvalidArgumentException('--strict-ambiguous mode only works with optimized autoloader, use --optimize or --classmap-authoritative if you want a strict return value.');
        }

        if ($authoritative) {
            $this->getIO()->write('<info>Generating optimized autoload files (authoritative)</info>');
        } elseif ($optimize) {
            $this->getIO()->write('<info>Generating optimized autoload files</info>');
        } else {
            $this->getIO()->write('<info>Generating autoload files</info>');
        }

        $generator = $composer->getAutoloadGenerator();
        if ($input->getOption('dry-run')) {
            $generator->setDryRun(true);
        }
        if ($input->getOption('no-dev')) {
            $generator->setDevMode(false);
        }
        if ($input->getOption('dev')) {
            if ($input->getOption('no-dev')) {
                throw new \InvalidArgumentException('You can not use both --no-dev and --dev as they conflict with each other.');
            }
            $generator->setDevMode(true);
        }
        $generator->setClassMapAuthoritative($authoritative);
        $generator->setRunScripts(true);
        $generator->setApcu($apcu, $apcuPrefix);
        $generator->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input));
        $classMap = $generator->dump(
            $config,
            $localRepo,
            $package,
            $installationManager,
            'composer',
            $optimize,
            null,
            $composer->getLocker(),
            $input->getOption('strict-ambiguous')
        );
        $numberOfClasses = count($classMap);

        if ($authoritative) {
            $this->getIO()->write('<info>Generated optimized autoload files (authoritative) containing '. $numberOfClasses .' classes</info>');
        } elseif ($optimize) {
            $this->getIO()->write('<info>Generated optimized autoload files containing '. $numberOfClasses .' classes</info>');
        } else {
            $this->getIO()->write('<info>Generated autoload files</info>');
        }

        if ($missingDependencies || ($input->getOption('strict-psr') && count($classMap->getPsrViolations()) > 0)) {
            return 1;
        }

        if ($input->getOption('strict-ambiguous') && count($classMap->getAmbiguousClasses(false)) > 0) {
            return 2;
        }

        return 0;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Console\Input\InputOption;
use Composer\Json\JsonFile;
use Composer\Package\CompletePackageInterface;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Repository\RepositoryUtils;
use Composer\Util\PackageInfo;
use Composer\Util\PackageSorter;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

/**
 * @author Benoît Merlet <benoit.merlet@gmail.com>
 */
class LicensesCommand extends BaseCommand
{
    protected function configure(): void
    {
        $this
            ->setName('licenses')
            ->setDescription('Shows information about licenses of dependencies')
            ->setDefinition([
                new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text, json or summary', 'text', ['text', 'json', 'summary']),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'),
            ])
            ->setHelp(
                <<<EOT
The license command displays detailed information about the licenses of
the installed dependencies.

Read more at https://getcomposer.org/doc/03-cli.md#licenses
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $composer = $this->requireComposer();

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'licenses', $input, $output);
        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        $root = $composer->getPackage();
        $repo = $composer->getRepositoryManager()->getLocalRepository();

        if ($input->getOption('no-dev')) {
            $packages = RepositoryUtils::filterRequiredPackages($repo->getPackages(), $root);
        } else {
            $packages = $repo->getPackages();
        }

        $packages = PackageSorter::sortPackagesAlphabetically($packages);
        $io = $this->getIO();

        switch ($format = $input->getOption('format')) {
            case 'text':
                $io->write('Name: <comment>'.$root->getPrettyName().'</comment>');
                $io->write('Version: <comment>'.$root->getFullPrettyVersion().'</comment>');
                $io->write('Licenses: <comment>'.(implode(', ', $root->getLicense()) ?: 'none').'</comment>');
                $io->write('Dependencies:');
                $io->write('');

                $table = new Table($output);
                $table->setStyle('compact');
                $table->setHeaders(['Name', 'Version', 'Licenses']);
                foreach ($packages as $package) {
                    $link = PackageInfo::getViewSourceOrHomepageUrl($package);
                    if ($link !== null) {
                        $name = '<href='.OutputFormatter::escape($link).'>'.$package->getPrettyName().'</>';
                    } else {
                        $name = $package->getPrettyName();
                    }

                    $table->addRow([
                        $name,
                        $package->getFullPrettyVersion(),
                        implode(', ', $package instanceof CompletePackageInterface ? $package->getLicense() : []) ?: 'none',
                    ]);
                }
                $table->render();
                break;

            case 'json':
                $dependencies = [];
                foreach ($packages as $package) {
                    $dependencies[$package->getPrettyName()] = [
                        'version' => $package->getFullPrettyVersion(),
                        'license' => $package instanceof CompletePackageInterface ? $package->getLicense() : [],
                    ];
                }

                $io->write(JsonFile::encode([
                    'name' => $root->getPrettyName(),
                    'version' => $root->getFullPrettyVersion(),
                    'license' => $root->getLicense(),
                    'dependencies' => $dependencies,
                ]));
                break;

            case 'summary':
                $usedLicenses = [];
                foreach ($packages as $package) {
                    $licenses = $package instanceof CompletePackageInterface ? $package->getLicense() : [];
                    if (count($licenses) === 0) {
                        $licenses[] = 'none';
                    }
                    foreach ($licenses as $licenseName) {
                        if (!isset($usedLicenses[$licenseName])) {
                            $usedLicenses[$licenseName] = 0;
                        }
                        $usedLicenses[$licenseName]++;
                    }
                }

                // Sort licenses so that the most used license will appear first
                arsort($usedLicenses, SORT_NUMERIC);

                $rows = [];
                foreach ($usedLicenses as $usedLicense => $numberOfDependencies) {
                    $rows[] = [$usedLicense, $numberOfDependencies];
                }

                $symfonyIo = new SymfonyStyle($input, $output);
                $symfonyIo->table(
                    ['License', 'Number of dependencies'],
                    $rows
                );
                break;
            default:
                throw new \RuntimeException(sprintf('Unsupported format "%s".  See help for supported formats.', $format));
        }

        return 0;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Installer;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Advisory\Auditor;
use Composer\Util\HttpDownloader;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Composer\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Ryan Weaver <ryan@knplabs.com>
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Nils Adermann <naderman@naderman.de>
 */
class InstallCommand extends BaseCommand
{
    use CompletionTrait;

    /**
     * @return void
     */
    protected function configure()
    {
        $this
            ->setName('install')
            ->setAliases(['i'])
            ->setDescription('Installs the project dependencies from the composer.lock file if present, or falls back on the composer.json')
            ->setDefinition([
                new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
                new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'),
                new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()),
                new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'),
                new InputOption('download-only', null, InputOption::VALUE_NONE, 'Download only, do not install packages.'),
                new InputOption('dev', null, InputOption::VALUE_NONE, 'DEPRECATED: Enables installation of require-dev packages (enabled by default, only present for BC).'),
                new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'DEPRECATED: This flag does not exist anymore.'),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'),
                new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'),
                new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                new InputOption('no-install', null, InputOption::VALUE_NONE, 'Do not use, only defined here to catch misuse of the install command.'),
                new InputOption('audit', null, InputOption::VALUE_NONE, 'Run an audit after installation is complete.'),
                new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS),
                new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'),
                new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'),
                new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'),
                new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'),
                new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'),
                new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'),
                new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'),
                new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Should not be provided, use composer require instead to add a given package to composer.json.'),
            ])
            ->setHelp(
                <<<EOT
The <info>install</info> command reads the composer.lock file from
the current directory, processes it, and downloads and installs all the
libraries and dependencies outlined in that file. If the file does not
exist it will look for composer.json and do the same.

<info>php composer.phar install</info>

Read more at https://getcomposer.org/doc/03-cli.md#install-i
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = $this->getIO();
        if ($input->getOption('dev')) {
            $io->writeError('<warning>You are using the deprecated option "--dev". It has no effect and will break in Composer 3.</warning>');
        }
        if ($input->getOption('no-suggest')) {
            $io->writeError('<warning>You are using the deprecated option "--no-suggest". It has no effect and will break in Composer 3.</warning>');
        }

        $args = $input->getArgument('packages');
        if (count($args) > 0) {
            $io->writeError('<error>Invalid argument '.implode(' ', $args).'. Use "composer require '.implode(' ', $args).'" instead to add packages to your composer.json.</error>');

            return 1;
        }

        if ($input->getOption('no-install')) {
            $io->writeError('<error>Invalid option "--no-install". Use "composer update --no-install" instead if you are trying to update the composer.lock file.</error>');

            return 1;
        }

        $composer = $this->requireComposer();

        if (!$composer->getLocker()->isLocked() && !HttpDownloader::isCurlEnabled()) {
            $io->writeError('<warning>Composer is operating significantly slower than normal because you do not have the PHP curl extension enabled.</warning>');
        }

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'install', $input, $output);
        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        $install = Installer::create($io, $composer);

        $config = $composer->getConfig();
        [$preferSource, $preferDist] = $this->getPreferredInstallOptions($config, $input);

        $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader');
        $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative');
        $apcuPrefix = $input->getOption('apcu-autoloader-prefix');
        $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader');

        $composer->getInstallationManager()->setOutputProgress(!$input->getOption('no-progress'));

        $install
            ->setDryRun($input->getOption('dry-run'))
            ->setDownloadOnly($input->getOption('download-only'))
            ->setVerbose($input->getOption('verbose'))
            ->setPreferSource($preferSource)
            ->setPreferDist($preferDist)
            ->setDevMode(!$input->getOption('no-dev'))
            ->setDumpAutoloader(!$input->getOption('no-autoloader'))
            ->setOptimizeAutoloader($optimize)
            ->setClassMapAuthoritative($authoritative)
            ->setApcuAutoloader($apcu, $apcuPrefix)
            ->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input))
            ->setAudit($input->getOption('audit'))
            ->setErrorOnAudit($input->getOption('audit'))
            ->setAuditFormat($this->getAuditFormat($input))
        ;

        if ($input->getOption('no-plugins')) {
            $install->disablePlugins();
        }

        return $install->run();
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Composer;
use Composer\Config;
use Composer\Console\Application;
use Composer\Console\Input\InputArgument;
use Composer\Console\Input\InputOption;
use Composer\Factory;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
use Composer\IO\IOInterface;
use Composer\IO\NullIO;
use Composer\Plugin\PreCommandRunEvent;
use Composer\Package\Version\VersionParser;
use Composer\Plugin\PluginEvents;
use Composer\Advisory\Auditor;
use Composer\Util\Platform;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableSeparator;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Terminal;

/**
 * Base class for Composer commands
 *
 * @author Ryan Weaver <ryan@knplabs.com>
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
abstract class BaseCommand extends Command
{
    /**
     * @var Composer|null
     */
    private $composer;

    /**
     * @var IOInterface
     */
    private $io;

    /**
     * Gets the application instance for this command.
     */
    public function getApplication(): Application
    {
        $application = parent::getApplication();
        if (!$application instanceof Application) {
            throw new \RuntimeException('Composer commands can only work with an '.Application::class.' instance set');
        }

        return $application;
    }

    /**
     * @param  bool              $required       Should be set to false, or use `requireComposer` instead
     * @param  bool|null         $disablePlugins If null, reads --no-plugins as default
     * @param  bool|null         $disableScripts If null, reads --no-scripts as default
     * @throws \RuntimeException
     * @return Composer|null
     * @deprecated since Composer 2.3.0 use requireComposer or tryComposer depending on whether you have $required set to true or false
     */
    public function getComposer(bool $required = true, ?bool $disablePlugins = null, ?bool $disableScripts = null)
    {
        if ($required) {
            return $this->requireComposer($disablePlugins, $disableScripts);
        }

        return $this->tryComposer($disablePlugins, $disableScripts);
    }

    /**
     * Retrieves the default Composer\Composer instance or throws
     *
     * Use this instead of getComposer if you absolutely need an instance
     *
     * @param bool|null $disablePlugins If null, reads --no-plugins as default
     * @param bool|null $disableScripts If null, reads --no-scripts as default
     * @throws \RuntimeException
     */
    public function requireComposer(?bool $disablePlugins = null, ?bool $disableScripts = null): Composer
    {
        if (null === $this->composer) {
            $application = parent::getApplication();
            if ($application instanceof Application) {
                $this->composer = $application->getComposer(true, $disablePlugins, $disableScripts);
                assert($this->composer instanceof Composer);
            } else {
                throw new \RuntimeException(
                    'Could not create a Composer\Composer instance, you must inject '.
                    'one if this command is not used with a Composer\Console\Application instance'
                );
            }
        }

        return $this->composer;
    }

    /**
     * Retrieves the default Composer\Composer instance or null
     *
     * Use this instead of getComposer(false)
     *
     * @param bool|null $disablePlugins If null, reads --no-plugins as default
     * @param bool|null $disableScripts If null, reads --no-scripts as default
     */
    public function tryComposer(?bool $disablePlugins = null, ?bool $disableScripts = null): ?Composer
    {
        if (null === $this->composer) {
            $application = parent::getApplication();
            if ($application instanceof Application) {
                $this->composer = $application->getComposer(false, $disablePlugins, $disableScripts);
            }
        }

        return $this->composer;
    }

    /**
     * @return void
     */
    public function setComposer(Composer $composer)
    {
        $this->composer = $composer;
    }

    /**
     * Removes the cached composer instance
     *
     * @return void
     */
    public function resetComposer()
    {
        $this->composer = null;
        $this->getApplication()->resetComposer();
    }

    /**
     * Whether or not this command is meant to call another command.
     *
     * This is mainly needed to avoid duplicated warnings messages.
     *
     * @return bool
     */
    public function isProxyCommand()
    {
        return false;
    }

    /**
     * @return IOInterface
     */
    public function getIO()
    {
        if (null === $this->io) {
            $application = parent::getApplication();
            if ($application instanceof Application) {
                $this->io = $application->getIO();
            } else {
                $this->io = new NullIO();
            }
        }

        return $this->io;
    }

    /**
     * @return void
     */
    public function setIO(IOInterface $io)
    {
        $this->io = $io;
    }

    /**
     * @inheritdoc
     *
     * Backport suggested values definition from symfony/console 6.1+
     *
     * TODO drop when PHP 8.1 / symfony 6.1+ can be required
     */
    public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
    {
        $definition = $this->getDefinition();
        $name = (string) $input->getCompletionName();
        if (CompletionInput::TYPE_OPTION_VALUE === $input->getCompletionType()
            && $definition->hasOption($name)
            && ($option = $definition->getOption($name)) instanceof InputOption
        ) {
            $option->complete($input, $suggestions);
        } elseif (CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType()
            && $definition->hasArgument($name)
            && ($argument = $definition->getArgument($name)) instanceof InputArgument
        ) {
            $argument->complete($input, $suggestions);
        } else {
            parent::complete($input, $suggestions);
        }
    }

    /**
     * @inheritDoc
     *
     * @return void
     */
    protected function initialize(InputInterface $input, OutputInterface $output)
    {
        // initialize a plugin-enabled Composer instance, either local or global
        $disablePlugins = $input->hasParameterOption('--no-plugins');
        $disableScripts = $input->hasParameterOption('--no-scripts');

        $application = parent::getApplication();
        if ($application instanceof Application && $application->getDisablePluginsByDefault()) {
            $disablePlugins = true;
        }
        if ($application instanceof Application && $application->getDisableScriptsByDefault()) {
            $disableScripts = true;
        }

        if ($this instanceof SelfUpdateCommand) {
            $disablePlugins = true;
            $disableScripts = true;
        }

        $composer = $this->tryComposer($disablePlugins, $disableScripts);
        $io = $this->getIO();

        if (null === $composer) {
            $composer = Factory::createGlobal($this->getIO(), $disablePlugins, $disableScripts);
        }
        if ($composer) {
            $preCommandRunEvent = new PreCommandRunEvent(PluginEvents::PRE_COMMAND_RUN, $input, $this->getName());
            $composer->getEventDispatcher()->dispatch($preCommandRunEvent->getName(), $preCommandRunEvent);
        }

        if (true === $input->hasParameterOption(['--no-ansi']) && $input->hasOption('no-progress')) {
            $input->setOption('no-progress', true);
        }

        $envOptions = [
            'COMPOSER_NO_AUDIT' => ['no-audit'],
            'COMPOSER_NO_DEV' => ['no-dev', 'update-no-dev'],
            'COMPOSER_PREFER_STABLE' => ['prefer-stable'],
            'COMPOSER_PREFER_LOWEST' => ['prefer-lowest'],
            'COMPOSER_MINIMAL_CHANGES' => ['minimal-changes'],
            'COMPOSER_WITH_DEPENDENCIES' => ['with-dependencies'],
            'COMPOSER_WITH_ALL_DEPENDENCIES' => ['with-all-dependencies'],
        ];
        foreach ($envOptions as $envName => $optionNames) {
            foreach ($optionNames as $optionName) {
                if (true === $input->hasOption($optionName)) {
                    if (false === $input->getOption($optionName) && (bool) Platform::getEnv($envName)) {
                        $input->setOption($optionName, true);
                    }
                }
            }
        }

        if (true === $input->hasOption('ignore-platform-reqs')) {
            if (!$input->getOption('ignore-platform-reqs') && (bool) Platform::getEnv('COMPOSER_IGNORE_PLATFORM_REQS')) {
                $input->setOption('ignore-platform-reqs', true);

                $io->writeError('<warning>COMPOSER_IGNORE_PLATFORM_REQS is set. You may experience unexpected errors.</warning>');
            }
        }

        if (true === $input->hasOption('ignore-platform-req') && (!$input->hasOption('ignore-platform-reqs') || !$input->getOption('ignore-platform-reqs'))) {
            $ignorePlatformReqEnv = Platform::getEnv('COMPOSER_IGNORE_PLATFORM_REQ');
            if (0 === count($input->getOption('ignore-platform-req')) && is_string($ignorePlatformReqEnv) && '' !== $ignorePlatformReqEnv) {
                $input->setOption('ignore-platform-req', explode(',', $ignorePlatformReqEnv));

                $io->writeError('<warning>COMPOSER_IGNORE_PLATFORM_REQ is set to ignore '.$ignorePlatformReqEnv.'. You may experience unexpected errors.</warning>');
            }
        }

        parent::initialize($input, $output);
    }

    /**
     * Calls {@see Factory::create()} with the given arguments, taking into account flags and default states for disabling scripts and plugins
     *
     * @param  mixed    $config either a configuration array or a filename to read from, if null it will read from
     *                          the default filename
     * @return Composer
     */
    protected function createComposerInstance(InputInterface $input, IOInterface $io, $config = null, ?bool $disablePlugins = null, ?bool $disableScripts = null): Composer
    {
        $disablePlugins = $disablePlugins === true || $input->hasParameterOption('--no-plugins');
        $disableScripts = $disableScripts === true || $input->hasParameterOption('--no-scripts');

        $application = parent::getApplication();
        if ($application instanceof Application && $application->getDisablePluginsByDefault()) {
            $disablePlugins = true;
        }
        if ($application instanceof Application && $application->getDisableScriptsByDefault()) {
            $disableScripts = true;
        }

        return Factory::create($io, $config, $disablePlugins, $disableScripts);
    }

    /**
     * Returns preferSource and preferDist values based on the configuration.
     *
     * @return bool[] An array composed of the preferSource and preferDist values
     */
    protected function getPreferredInstallOptions(Config $config, InputInterface $input, bool $keepVcsRequiresPreferSource = false)
    {
        $preferSource = false;
        $preferDist = false;

        switch ($config->get('preferred-install')) {
            case 'source':
                $preferSource = true;
                break;
            case 'dist':
                $preferDist = true;
                break;
            case 'auto':
            default:
                // noop
                break;
        }

        if (!$input->hasOption('prefer-dist') || !$input->hasOption('prefer-source')) {
            return [$preferSource, $preferDist];
        }

        if ($input->hasOption('prefer-install') && is_string($input->getOption('prefer-install'))) {
            if ($input->getOption('prefer-source')) {
                throw new \InvalidArgumentException('--prefer-source can not be used together with --prefer-install');
            }
            if ($input->getOption('prefer-dist')) {
                throw new \InvalidArgumentException('--prefer-dist can not be used together with --prefer-install');
            }
            switch ($input->getOption('prefer-install')) {
                case 'dist':
                    $input->setOption('prefer-dist', true);
                    break;
                case 'source':
                    $input->setOption('prefer-source', true);
                    break;
                case 'auto':
                    $preferDist = false;
                    $preferSource = false;
                    break;
                default:
                    throw new \UnexpectedValueException('--prefer-install accepts one of "dist", "source" or "auto", got '.$input->getOption('prefer-install'));
            }
        }

        if ($input->getOption('prefer-source') || $input->getOption('prefer-dist') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs'))) {
            $preferSource = $input->getOption('prefer-source') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs'));
            $preferDist = $input->getOption('prefer-dist');
        }

        return [$preferSource, $preferDist];
    }

    protected function getPlatformRequirementFilter(InputInterface $input): PlatformRequirementFilterInterface
    {
        if (!$input->hasOption('ignore-platform-reqs') || !$input->hasOption('ignore-platform-req')) {
            throw new \LogicException('Calling getPlatformRequirementFilter from a command which does not define the --ignore-platform-req[s] flags is not permitted.');
        }

        if (true === $input->getOption('ignore-platform-reqs')) {
            return PlatformRequirementFilterFactory::ignoreAll();
        }

        $ignores = $input->getOption('ignore-platform-req');
        if (count($ignores) > 0) {
            return PlatformRequirementFilterFactory::fromBoolOrList($ignores);
        }

        return PlatformRequirementFilterFactory::ignoreNothing();
    }

    /**
     * @param array<string> $requirements
     *
     * @return array<string, string>
     */
    protected function formatRequirements(array $requirements)
    {
        $requires = [];
        $requirements = $this->normalizeRequirements($requirements);
        foreach ($requirements as $requirement) {
            if (!isset($requirement['version'])) {
                throw new \UnexpectedValueException('Option '.$requirement['name'] .' is missing a version constraint, use e.g. '.$requirement['name'].':^1.0');
            }
            $requires[$requirement['name']] = $requirement['version'];
        }

        return $requires;
    }

    /**
     * @param array<string> $requirements
     *
     * @return list<array{name: string, version?: string}>
     */
    protected function normalizeRequirements(array $requirements)
    {
        $parser = new VersionParser();

        return $parser->parseNameVersionPairs($requirements);
    }

    /**
     * @param array<TableSeparator|mixed[]> $table
     *
     * @return void
     */
    protected function renderTable(array $table, OutputInterface $output)
    {
        $renderer = new Table($output);
        $renderer->setStyle('compact');
        $renderer->setRows($table)->render();
    }

    /**
     * @return int
     */
    protected function getTerminalWidth()
    {
        $terminal = new Terminal();
        $width = $terminal->getWidth();

        if (Platform::isWindows()) {
            $width--;
        } else {
            $width = max(80, $width);
        }

        return $width;
    }

    /**
     * @internal
     * @param 'format'|'audit-format' $optName
     * @return Auditor::FORMAT_*
     */
    protected function getAuditFormat(InputInterface $input, string $optName = 'audit-format'): string
    {
        if (!$input->hasOption($optName)) {
            throw new \LogicException('This should not be called on a Command which has no '.$optName.' option defined.');
        }

        $val = $input->getOption($optName);
        if (!in_array($val, Auditor::FORMATS, true)) {
            throw new \InvalidArgumentException('--'.$optName.' must be one of '.implode(', ', Auditor::FORMATS).'.');
        }

        return $val;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Console\Input\InputArgument;
use Composer\Console\Input\InputOption;

/**
 * @author Niels Keurentjes <niels.keurentjes@omines.com>
 */
class ProhibitsCommand extends BaseDependencyCommand
{
    use CompletionTrait;

    /**
     * Configure command metadata.
     */
    protected function configure(): void
    {
        $this
            ->setName('prohibits')
            ->setAliases(['why-not'])
            ->setDescription('Shows which packages prevent the given package from being installed')
            ->setDefinition([
                new InputArgument(self::ARGUMENT_PACKAGE, InputArgument::REQUIRED, 'Package to inspect', null, $this->suggestAvailablePackage()),
                new InputArgument(self::ARGUMENT_CONSTRAINT, InputArgument::REQUIRED, 'Version constraint, which version you expected to be installed'),
                new InputOption(self::OPTION_RECURSIVE, 'r', InputOption::VALUE_NONE, 'Recursively resolves up to the root package'),
                new InputOption(self::OPTION_TREE, 't', InputOption::VALUE_NONE, 'Prints the results as a nested tree'),
                new InputOption('locked', null, InputOption::VALUE_NONE, 'Read dependency information from composer.lock'),
            ])
            ->setHelp(
                <<<EOT
Displays detailed information about why a package cannot be installed.

<info>php composer.phar prohibits composer/composer</info>

Read more at https://getcomposer.org/doc/03-cli.md#prohibits-why-not
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        return parent::doExecute($input, $output, true);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\Package\Loader\ValidatingArrayLoader;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Repository\InstalledRepository;
use Composer\Repository\PlatformRepository;
use Composer\Util\ConfigValidator;
use Composer\Util\Filesystem;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * ValidateCommand
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ValidateCommand extends BaseCommand
{
    /**
     * configure
     */
    protected function configure(): void
    {
        $this
            ->setName('validate')
            ->setDescription('Validates a composer.json and composer.lock')
            ->setDefinition([
                new InputOption('no-check-all', null, InputOption::VALUE_NONE, 'Do not validate requires for overly strict/loose constraints'),
                new InputOption('check-lock', null, InputOption::VALUE_NONE, 'Check if lock file is up to date (even when config.lock is false)'),
                new InputOption('no-check-lock', null, InputOption::VALUE_NONE, 'Do not check if lock file is up to date'),
                new InputOption('no-check-publish', null, InputOption::VALUE_NONE, 'Do not check for publish errors'),
                new InputOption('no-check-version', null, InputOption::VALUE_NONE, 'Do not report a warning if the version field is present'),
                new InputOption('with-dependencies', 'A', InputOption::VALUE_NONE, 'Also validate the composer.json of all installed dependencies'),
                new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code for warnings as well as errors'),
                new InputArgument('file', InputArgument::OPTIONAL, 'path to composer.json file'),
            ])
            ->setHelp(
                <<<EOT
The validate command validates a given composer.json and composer.lock

Exit codes in case of errors are:
1 validation warning(s), only when --strict is given
2 validation error(s)
3 file unreadable or missing

Read more at https://getcomposer.org/doc/03-cli.md#validate
EOT
            );
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $file = $input->getArgument('file') ?? Factory::getComposerFile();
        $io = $this->getIO();

        if (!file_exists($file)) {
            $io->writeError('<error>' . $file . ' not found.</error>');

            return 3;
        }
        if (!Filesystem::isReadable($file)) {
            $io->writeError('<error>' . $file . ' is not readable.</error>');

            return 3;
        }

        $validator = new ConfigValidator($io);
        $checkAll = $input->getOption('no-check-all') ? 0 : ValidatingArrayLoader::CHECK_ALL;
        $checkPublish = !$input->getOption('no-check-publish');
        $checkLock = !$input->getOption('no-check-lock');
        $checkVersion = $input->getOption('no-check-version') ? 0 : ConfigValidator::CHECK_VERSION;
        $isStrict = $input->getOption('strict');
        [$errors, $publishErrors, $warnings] = $validator->validate($file, $checkAll, $checkVersion);

        $lockErrors = [];
        $composer = $this->createComposerInstance($input, $io, $file);
        // config.lock = false ~= implicit --no-check-lock; --check-lock overrides
        $checkLock = ($checkLock && $composer->getConfig()->get('lock')) || $input->getOption('check-lock');
        $locker = $composer->getLocker();
        if ($locker->isLocked() && !$locker->isFresh()) {
            $lockErrors[] = '- The lock file is not up to date with the latest changes in composer.json, it is recommended that you run `composer update` or `composer update <package name>`.';
        }

        if ($locker->isLocked()) {
            $lockErrors = array_merge($lockErrors, $locker->getMissingRequirementInfo($composer->getPackage(), true));
        }

        $this->outputResult($io, $file, $errors, $warnings, $checkPublish, $publishErrors, $checkLock, $lockErrors, true);

        // $errors include publish and lock errors when exists
        $exitCode = count($errors) > 0 ? 2 : (($isStrict && count($warnings) > 0) ? 1 : 0);

        if ($input->getOption('with-dependencies')) {
            $localRepo = $composer->getRepositoryManager()->getLocalRepository();
            foreach ($localRepo->getPackages() as $package) {
                $path = $composer->getInstallationManager()->getInstallPath($package);
                if (null === $path) {
                    continue;
                }
                $file = $path . '/composer.json';
                if (is_dir($path) && file_exists($file)) {
                    [$errors, $publishErrors, $warnings] = $validator->validate($file, $checkAll, $checkVersion);

                    $this->outputResult($io, $package->getPrettyName(), $errors, $warnings, $checkPublish, $publishErrors);

                    // $errors include publish errors when exists
                    $depCode = count($errors) > 0 ? 2 : (($isStrict && count($warnings) > 0) ? 1 : 0);
                    $exitCode = max($depCode, $exitCode);
                }
            }
        }

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'validate', $input, $output);
        $eventCode = $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        return max($eventCode, $exitCode);
    }

    /**
     * @param string[] $errors
     * @param string[] $warnings
     * @param string[] $publishErrors
     * @param string[] $lockErrors
     */
    private function outputResult(IOInterface $io, string $name, array &$errors, array &$warnings, bool $checkPublish = false, array $publishErrors = [], bool $checkLock = false, array $lockErrors = [], bool $printSchemaUrl = false): void
    {
        $doPrintSchemaUrl = false;

        if (\count($errors) > 0) {
            $io->writeError('<error>' . $name . ' is invalid, the following errors/warnings were found:</error>');
        } elseif (\count($publishErrors) > 0 && $checkPublish) {
            $io->writeError('<info>' . $name . ' is valid for simple usage with Composer but has</info>');
            $io->writeError('<info>strict errors that make it unable to be published as a package</info>');
            $doPrintSchemaUrl = $printSchemaUrl;
        } elseif (\count($warnings) > 0) {
            $io->writeError('<info>' . $name . ' is valid, but with a few warnings</info>');
            $doPrintSchemaUrl = $printSchemaUrl;
        } elseif (\count($lockErrors) > 0) {
            $io->write('<info>' . $name . ' is valid but your composer.lock has some '.($checkLock ? 'errors' : 'warnings').'</info>');
        } else {
            $io->write('<info>' . $name . ' is valid</info>');
        }

        if ($doPrintSchemaUrl) {
            $io->writeError('<warning>See https://getcomposer.org/doc/04-schema.md for details on the schema</warning>');
        }

        if (\count($errors) > 0) {
            $errors = array_map(static function ($err): string {
                return '- ' . $err;
            }, $errors);
            array_unshift($errors, '# General errors');
        }
        if (\count($warnings) > 0) {
            $warnings = array_map(static function ($err): string {
                return '- ' . $err;
            }, $warnings);
            array_unshift($warnings, '# General warnings');
        }

        // Avoid setting the exit code to 1 in case --strict and --no-check-publish/--no-check-lock are combined
        $extraWarnings = [];

        // If checking publish errors, display them as errors, otherwise just show them as warnings
        if (\count($publishErrors) > 0 && $checkPublish) {
            $publishErrors = array_map(static function ($err): string {
                return '- ' . $err;
            }, $publishErrors);

            array_unshift($publishErrors, '# Publish errors');
            $errors = array_merge($errors, $publishErrors);
        }

        // If checking lock errors, display them as errors, otherwise just show them as warnings
        if (\count($lockErrors) > 0) {
            if ($checkLock) {
                array_unshift($lockErrors, '# Lock file errors');
                $errors = array_merge($errors, $lockErrors);
            } else {
                array_unshift($lockErrors, '# Lock file warnings');
                $extraWarnings = array_merge($extraWarnings, $lockErrors);
            }
        }

        $messages = [
            'error' => $errors,
            'warning' => array_merge($warnings, $extraWarnings),
        ];

        foreach ($messages as $style => $msgs) {
            foreach ($msgs as $msg) {
                if (strpos($msg, '#') === 0) {
                    $io->writeError('<' . $style . '>' . $msg . '</' . $style . '>');
                } else {
                    $io->writeError($msg);
                }
            }
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\UninstallOperation;
use Composer\DependencyResolver\Transaction;
use Composer\Package\AliasPackage;
use Composer\Package\BasePackage;
use Composer\Pcre\Preg;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Script\ScriptEvents;
use Composer\Util\Platform;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Composer\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ReinstallCommand extends BaseCommand
{
    use CompletionTrait;

    protected function configure(): void
    {
        $this
            ->setName('reinstall')
            ->setDescription('Uninstalls and reinstalls the given package names')
            ->setDefinition([
                new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
                new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'),
                new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()),
                new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'),
                new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'),
                new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'),
                new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'),
                new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'),
                new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'),
                new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'),
                new InputOption('type', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Filter packages to reinstall by type(s)', null, $this->suggestInstalledPackageTypes(false)),
                new InputArgument('packages', InputArgument::IS_ARRAY, 'List of package names to reinstall, can include a wildcard (*) to match any substring.', null, $this->suggestInstalledPackage(false)),
            ])
            ->setHelp(
                <<<EOT
The <info>reinstall</info> command looks up installed packages by name,
uninstalls them and reinstalls them. This lets you do a clean install
of a package if you messed with its files, or if you wish to change
the installation type using --prefer-install.

<info>php composer.phar reinstall acme/foo "acme/bar-*"</info>

Read more at https://getcomposer.org/doc/03-cli.md#reinstall
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = $this->getIO();

        $composer = $this->requireComposer();

        $localRepo = $composer->getRepositoryManager()->getLocalRepository();
        $packagesToReinstall = [];
        $packageNamesToReinstall = [];
        if (\count($input->getOption('type')) > 0) {
            if (\count($input->getArgument('packages')) > 0) {
                throw new \InvalidArgumentException('You cannot specify package names and filter by type at the same time.');
            }
            foreach ($localRepo->getCanonicalPackages() as $package) {
                if (in_array($package->getType(), $input->getOption('type'), true)) {
                    $packagesToReinstall[] = $package;
                    $packageNamesToReinstall[] = $package->getName();
                }
            }
        } else {
            if (\count($input->getArgument('packages')) === 0) {
                throw new \InvalidArgumentException('You must pass one or more package names to be reinstalled.');
            }
            foreach ($input->getArgument('packages') as $pattern) {
                $patternRegexp = BasePackage::packageNameToRegexp($pattern);
                $matched = false;
                foreach ($localRepo->getCanonicalPackages() as $package) {
                    if (Preg::isMatch($patternRegexp, $package->getName())) {
                        $matched = true;
                        $packagesToReinstall[] = $package;
                        $packageNamesToReinstall[] = $package->getName();
                    }
                }

                if (!$matched) {
                    $io->writeError('<warning>Pattern "' . $pattern . '" does not match any currently installed packages.</warning>');
                }
            }
        }

        if (0 === \count($packagesToReinstall)) {
            $io->writeError('<warning>Found no packages to reinstall, aborting.</warning>');

            return 1;
        }

        $uninstallOperations = [];
        foreach ($packagesToReinstall as $package) {
            $uninstallOperations[] = new UninstallOperation($package);
        }

        // make sure we have a list of install operations ordered by dependency/plugins
        $presentPackages = $localRepo->getPackages();
        $resultPackages = $presentPackages;
        foreach ($presentPackages as $index => $package) {
            if (in_array($package->getName(), $packageNamesToReinstall, true)) {
                unset($presentPackages[$index]);
            }
        }
        $transaction = new Transaction($presentPackages, $resultPackages);
        $installOperations = $transaction->getOperations();

        // reverse-sort the uninstalls based on the install order
        $installOrder = [];
        foreach ($installOperations as $index => $op) {
            if ($op instanceof InstallOperation && !$op->getPackage() instanceof AliasPackage) {
                $installOrder[$op->getPackage()->getName()] = $index;
            }
        }
        usort($uninstallOperations, static function ($a, $b) use ($installOrder): int {
            return $installOrder[$b->getPackage()->getName()] - $installOrder[$a->getPackage()->getName()];
        });

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'reinstall', $input, $output);
        $eventDispatcher = $composer->getEventDispatcher();
        $eventDispatcher->dispatch($commandEvent->getName(), $commandEvent);

        $config = $composer->getConfig();
        [$preferSource, $preferDist] = $this->getPreferredInstallOptions($config, $input);

        $installationManager = $composer->getInstallationManager();
        $downloadManager = $composer->getDownloadManager();
        $package = $composer->getPackage();

        $installationManager->setOutputProgress(!$input->getOption('no-progress'));
        if ($input->getOption('no-plugins')) {
            $installationManager->disablePlugins();
        }

        $downloadManager->setPreferSource($preferSource);
        $downloadManager->setPreferDist($preferDist);

        $devMode = $localRepo->getDevMode() !== null ? $localRepo->getDevMode() : true;

        Platform::putEnv('COMPOSER_DEV_MODE', $devMode ? '1' : '0');
        $eventDispatcher->dispatchScript(ScriptEvents::PRE_INSTALL_CMD, $devMode);

        $installationManager->execute($localRepo, $uninstallOperations, $devMode);
        $installationManager->execute($localRepo, $installOperations, $devMode);

        if (!$input->getOption('no-autoloader')) {
            $optimize = $input->getOption('optimize-autoloader') || $config->get('optimize-autoloader');
            $authoritative = $input->getOption('classmap-authoritative') || $config->get('classmap-authoritative');
            $apcuPrefix = $input->getOption('apcu-autoloader-prefix');
            $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader');

            $generator = $composer->getAutoloadGenerator();
            $generator->setClassMapAuthoritative($authoritative);
            $generator->setApcu($apcu, $apcuPrefix);
            $generator->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input));
            $generator->dump(
                $config,
                $localRepo,
                $package,
                $installationManager,
                'composer',
                $optimize,
                null,
                $composer->getLocker()
            );
        }

        $eventDispatcher->dispatchScript(ScriptEvents::POST_INSTALL_CMD, $devMode);

        return 0;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Composer;
use Composer\Package\BasePackage;
use Composer\Package\PackageInterface;
use Composer\Pcre\Preg;
use Composer\Repository\CompositeRepository;
use Composer\Repository\InstalledRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\RootPackageRepository;
use Symfony\Component\Console\Completion\CompletionInput;

/**
 * Adds completion to arguments and options.
 *
 * @internal
 */
trait CompletionTrait
{
    /**
     * @see BaseCommand::requireComposer()
     */
    abstract public function requireComposer(?bool $disablePlugins = null, ?bool $disableScripts = null): Composer;

    /**
     * Suggestion values for "prefer-install" option
     *
     * @return list<string>
     */
    private function suggestPreferInstall(): array
    {
        return ['dist', 'source', 'auto'];
    }

    /**
     * Suggest package names from root requirements.
     */
    private function suggestRootRequirement(): \Closure
    {
        return function (CompletionInput $input): array {
            $composer = $this->requireComposer();

            return array_merge(array_keys($composer->getPackage()->getRequires()), array_keys($composer->getPackage()->getDevRequires()));
        };
    }

    /**
     * Suggest package names from installed.
     */
    private function suggestInstalledPackage(bool $includeRootPackage = true, bool $includePlatformPackages = false): \Closure
    {
        return function (CompletionInput $input) use ($includeRootPackage, $includePlatformPackages): array {
            $composer = $this->requireComposer();
            $installedRepos = [];

            if ($includeRootPackage) {
                $installedRepos[] = new RootPackageRepository(clone $composer->getPackage());
            }

            $locker = $composer->getLocker();
            if ($locker->isLocked()) {
                $installedRepos[] = $locker->getLockedRepository(true);
            } else {
                $installedRepos[] = $composer->getRepositoryManager()->getLocalRepository();
            }

            $platformHint = [];
            if ($includePlatformPackages) {
                if ($locker->isLocked()) {
                    $platformRepo = new PlatformRepository([], $locker->getPlatformOverrides());
                } else {
                    $platformRepo = new PlatformRepository([], $composer->getConfig()->get('platform'));
                }
                if ($input->getCompletionValue() === '') {
                    // to reduce noise, when no text is yet entered we list only two entries for ext- and lib- prefixes
                    $hintsToFind = ['ext-' => 0, 'lib-' => 0, 'php' => 99, 'composer' => 99];
                    foreach ($platformRepo->getPackages() as $pkg) {
                        foreach ($hintsToFind as $hintPrefix => $hintCount) {
                            if (str_starts_with($pkg->getName(), $hintPrefix)) {
                                if ($hintCount === 0 || $hintCount >= 99) {
                                    $platformHint[] = $pkg->getName();
                                    $hintsToFind[$hintPrefix]++;
                                } elseif ($hintCount === 1) {
                                    unset($hintsToFind[$hintPrefix]);
                                    $platformHint[] = substr($pkg->getName(), 0, max(strlen($pkg->getName()) - 3, strlen($hintPrefix) + 1)).'...';
                                }
                                continue 2;
                            }
                        }
                    }
                } else {
                    $installedRepos[] = $platformRepo;
                }
            }

            $installedRepo = new InstalledRepository($installedRepos);

            return array_merge(
                array_map(static function (PackageInterface $package) {
                    return $package->getName();
                }, $installedRepo->getPackages()),
                $platformHint
            );
        };
    }

    /**
     * Suggest package names from installed.
     */
    private function suggestInstalledPackageTypes(bool $includeRootPackage = true): \Closure
    {
        return function (CompletionInput $input) use ($includeRootPackage): array {
            $composer = $this->requireComposer();
            $installedRepos = [];

            if ($includeRootPackage) {
                $installedRepos[] = new RootPackageRepository(clone $composer->getPackage());
            }

            $locker = $composer->getLocker();
            if ($locker->isLocked()) {
                $installedRepos[] = $locker->getLockedRepository(true);
            } else {
                $installedRepos[] = $composer->getRepositoryManager()->getLocalRepository();
            }

            $installedRepo = new InstalledRepository($installedRepos);

            return array_values(array_unique(
                array_map(static function (PackageInterface $package) {
                    return $package->getType();
                }, $installedRepo->getPackages())
            ));
        };
    }

    /**
     * Suggest package names available on all configured repositories.
     */
    private function suggestAvailablePackage(int $max = 99): \Closure
    {
        return function (CompletionInput $input) use ($max): array {
            if ($max < 1) {
                return [];
            }

            $composer = $this->requireComposer();
            $repos = new CompositeRepository($composer->getRepositoryManager()->getRepositories());

            $results = [];
            $showVendors = false;
            if (!str_contains($input->getCompletionValue(), '/')) {
                $results = $repos->search('^' . preg_quote($input->getCompletionValue()), RepositoryInterface::SEARCH_VENDOR);
                $showVendors = true;
            }

            // if we get a single vendor, we expand it into its contents already
            if (\count($results) <= 1) {
                $results = $repos->search('^'.preg_quote($input->getCompletionValue()), RepositoryInterface::SEARCH_NAME);
                $showVendors = false;
            }

            $results = array_column($results, 'name');

            if ($showVendors) {
                $results = array_map(static function (string $name): string {
                    return $name.'/';
                }, $results);

                // sort shorter results first to avoid auto-expanding the completion to a longer string than needed
                usort($results, static function (string $a, string $b) {
                    $lenA = \strlen($a);
                    $lenB = \strlen($b);
                    if ($lenA === $lenB) {
                        return $a <=> $b;
                    }

                    return $lenA - $lenB;
                });

                $pinned = [];

                // ensure if the input is an exact match that it is always in the result set
                $completionInput = $input->getCompletionValue().'/';
                if (false !== ($exactIndex = array_search($completionInput, $results, true))) {
                    $pinned[] = $completionInput;
                    array_splice($results, $exactIndex, 1);
                }

                return array_merge($pinned, array_slice($results, 0, $max - \count($pinned)));
            }

            return array_slice($results, 0, $max);
        };
    }

    /**
     * Suggest package names available on all configured repositories or
     * platform packages from the ones available on the currently-running PHP
     */
    private function suggestAvailablePackageInclPlatform(): \Closure
    {
        return function (CompletionInput $input): array {
            if (Preg::isMatch('{^(ext|lib|php)(-|$)|^com}', $input->getCompletionValue())) {
                $matches = $this->suggestPlatformPackage()($input);
            } else {
                $matches = [];
            }

            return array_merge($matches, $this->suggestAvailablePackage(99 - \count($matches))($input));
        };
    }

    /**
     * Suggest platform packages from the ones available on the currently-running PHP
     */
    private function suggestPlatformPackage(): \Closure
    {
        return function (CompletionInput $input): array {
            $repos = new PlatformRepository([], $this->requireComposer()->getConfig()->get('platform'));

            $pattern = BasePackage::packageNameToRegexp($input->getCompletionValue().'*');

            return array_filter(array_map(static function (PackageInterface $package) {
                return $package->getName();
            }, $repos->getPackages()), static function (string $name) use ($pattern): bool {
                return Preg::isMatch($pattern, $name);
            });
        };
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Factory;
use Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\IO\IOInterface;
use Composer\Package\BasePackage;
use Composer\Package\CompletePackageInterface;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\Package\Version\VersionSelector;
use Composer\Pcre\Preg;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryFactory;
use Composer\Repository\RepositorySet;
use Composer\Semver\Constraint\Constraint;
use Composer\Util\Filesystem;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @internal
 */
trait PackageDiscoveryTrait
{
    /** @var ?CompositeRepository */
    private $repos;
    /** @var RepositorySet[] */
    private $repositorySets;

    protected function getRepos(): CompositeRepository
    {
        if (null === $this->repos) {
            $this->repos = new CompositeRepository(array_merge(
                [new PlatformRepository],
                RepositoryFactory::defaultReposWithDefaultManager($this->getIO())
            ));
        }

        return $this->repos;
    }

    /**
     * @param key-of<BasePackage::STABILITIES>|null $minimumStability
     */
    private function getRepositorySet(InputInterface $input, ?string $minimumStability = null): RepositorySet
    {
        $key = $minimumStability ?? 'default';

        if (!isset($this->repositorySets[$key])) {
            $this->repositorySets[$key] = $repositorySet = new RepositorySet($minimumStability ?? $this->getMinimumStability($input));
            $repositorySet->addRepository($this->getRepos());
        }

        return $this->repositorySets[$key];
    }

    /**
     * @return key-of<BasePackage::STABILITIES>
     */
    private function getMinimumStability(InputInterface $input): string
    {
        if ($input->hasOption('stability')) { // @phpstan-ignore-line as InitCommand does have this option but not all classes using this trait do
            return VersionParser::normalizeStability($input->getOption('stability') ?? 'stable');
        }

        // @phpstan-ignore-next-line as RequireCommand does not have the option above so this code is reachable there
        $file = Factory::getComposerFile();
        if (is_file($file) && Filesystem::isReadable($file) && is_array($composer = json_decode((string) file_get_contents($file), true))) {
            if (isset($composer['minimum-stability'])) {
                return VersionParser::normalizeStability($composer['minimum-stability']);
            }
        }

        return 'stable';
    }

    /**
     * @param array<string> $requires
     *
     * @return array<string>
     * @throws \Exception
     */
    final protected function determineRequirements(InputInterface $input, OutputInterface $output, array $requires = [], ?PlatformRepository $platformRepo = null, string $preferredStability = 'stable', bool $useBestVersionConstraint = true, bool $fixed = false): array
    {
        if (count($requires) > 0) {
            $requires = $this->normalizeRequirements($requires);
            $result = [];
            $io = $this->getIO();

            foreach ($requires as $requirement) {
                if (isset($requirement['version']) && Preg::isMatch('{^\d+(\.\d+)?$}', $requirement['version'])) {
                    $io->writeError('<warning>The "'.$requirement['version'].'" constraint for "'.$requirement['name'].'" appears too strict and will likely not match what you want. See https://getcomposer.org/constraints</warning>');
                }

                if (!isset($requirement['version'])) {
                    // determine the best version automatically
                    [$name, $version] = $this->findBestVersionAndNameForPackage($this->getIO(), $input, $requirement['name'], $platformRepo, $preferredStability, $fixed);

                    // replace package name from packagist.org
                    $requirement['name'] = $name;

                    if ($useBestVersionConstraint) {
                        $requirement['version'] = $version;
                        $io->writeError(sprintf(
                            'Using version <info>%s</info> for <info>%s</info>',
                            $requirement['version'],
                            $requirement['name']
                        ));
                    } else {
                        $requirement['version'] = 'guess';
                    }
                }

                $result[] = $requirement['name'] . ' ' . $requirement['version'];
            }

            return $result;
        }

        $versionParser = new VersionParser();

        // Collect existing packages
        $composer = $this->tryComposer();
        $installedRepo = null;
        if (null !== $composer) {
            $installedRepo = $composer->getRepositoryManager()->getLocalRepository();
        }
        $existingPackages = [];
        if (null !== $installedRepo) {
            foreach ($installedRepo->getPackages() as $package) {
                $existingPackages[] = $package->getName();
            }
        }
        unset($composer, $installedRepo);

        $io = $this->getIO();
        while (null !== $package = $io->ask('Search for a package: ')) {
            $matches = $this->getRepos()->search($package);

            if (count($matches) > 0) {
                // Remove existing packages from search results.
                foreach ($matches as $position => $foundPackage) {
                    if (in_array($foundPackage['name'], $existingPackages, true)) {
                        unset($matches[$position]);
                    }
                }
                $matches = array_values($matches);

                $exactMatch = false;
                foreach ($matches as $match) {
                    if ($match['name'] === $package) {
                        $exactMatch = true;
                        break;
                    }
                }

                // no match, prompt which to pick
                if (!$exactMatch) {
                    $providers = $this->getRepos()->getProviders($package);
                    if (count($providers) > 0) {
                        array_unshift($matches, ['name' => $package, 'description' => '']);
                    }

                    $choices = [];
                    foreach ($matches as $position => $foundPackage) {
                        $abandoned = '';
                        if (isset($foundPackage['abandoned'])) {
                            if (is_string($foundPackage['abandoned'])) {
                                $replacement = sprintf('Use %s instead', $foundPackage['abandoned']);
                            } else {
                                $replacement = 'No replacement was suggested';
                            }
                            $abandoned = sprintf('<warning>Abandoned. %s.</warning>', $replacement);
                        }

                        $choices[] = sprintf(' <info>%5s</info> %s %s', "[$position]", $foundPackage['name'], $abandoned);
                    }

                    $io->writeError([
                        '',
                        sprintf('Found <info>%s</info> packages matching <info>%s</info>', count($matches), $package),
                        '',
                    ]);

                    $io->writeError($choices);
                    $io->writeError('');

                    $validator = static function (string $selection) use ($matches, $versionParser) {
                        if ('' === $selection) {
                            return false;
                        }

                        if (is_numeric($selection) && isset($matches[(int) $selection])) {
                            $package = $matches[(int) $selection];

                            return $package['name'];
                        }

                        if (Preg::isMatch('{^\s*(?P<name>[\S/]+)(?:\s+(?P<version>\S+))?\s*$}', $selection, $packageMatches)) {
                            if (isset($packageMatches['version'])) {
                                // parsing `acme/example ~2.3`

                                // validate version constraint
                                $versionParser->parseConstraints($packageMatches['version']);

                                return $packageMatches['name'].' '.$packageMatches['version'];
                            }

                            // parsing `acme/example`
                            return $packageMatches['name'];
                        }

                        throw new \Exception('Not a valid selection');
                    };

                    $package = $io->askAndValidate(
                        'Enter package # to add, or the complete package name if it is not listed: ',
                        $validator,
                        3,
                        ''
                    );
                }

                // no constraint yet, determine the best version automatically
                if (false !== $package && false === strpos($package, ' ')) {
                    $validator = static function (string $input) {
                        $input = trim($input);

                        return strlen($input) > 0 ? $input : false;
                    };

                    $constraint = $io->askAndValidate(
                        'Enter the version constraint to require (or leave blank to use the latest version): ',
                        $validator,
                        3,
                        ''
                    );

                    if (false === $constraint) {
                        [, $constraint] = $this->findBestVersionAndNameForPackage($this->getIO(), $input, $package, $platformRepo, $preferredStability);

                        $io->writeError(sprintf(
                            'Using version <info>%s</info> for <info>%s</info>',
                            $constraint,
                            $package
                        ));
                    }

                    $package .= ' '.$constraint;
                }

                if (false !== $package) {
                    $requires[] = $package;
                    $existingPackages[] = explode(' ', $package)[0];
                }
            }
        }

        return $requires;
    }

    /**
     * Given a package name, this determines the best version to use in the require key.
     *
     * This returns a version with the ~ operator prefixed when possible.
     *
     * @throws \InvalidArgumentException
     * @return array{string, string}     name version
     */
    private function findBestVersionAndNameForPackage(IOInterface $io, InputInterface $input, string $name, ?PlatformRepository $platformRepo = null, string $preferredStability = 'stable', bool $fixed = false): array
    {
        // handle ignore-platform-reqs flag if present
        if ($input->hasOption('ignore-platform-reqs') && $input->hasOption('ignore-platform-req')) {
            $platformRequirementFilter = $this->getPlatformRequirementFilter($input);
        } else {
            $platformRequirementFilter = PlatformRequirementFilterFactory::ignoreNothing();
        }

        // find the latest version allowed in this repo set
        $repoSet = $this->getRepositorySet($input);
        $versionSelector = new VersionSelector($repoSet, $platformRepo);
        $effectiveMinimumStability = $this->getMinimumStability($input);

        $package = $versionSelector->findBestCandidate($name, null, $preferredStability, $platformRequirementFilter, 0, $this->getIO());

        if (false === $package) {
            // platform packages can not be found in the pool in versions other than the local platform's has
            // so if platform reqs are ignored we just take the user's word for it
            if ($platformRequirementFilter->isIgnored($name)) {
                return [$name, '*'];
            }

            // Check if it is a virtual package provided by others
            $providers = $repoSet->getProviders($name);
            if (count($providers) > 0) {
                $constraint = '*';
                if ($input->isInteractive()) {
                    $constraint = $this->getIO()->askAndValidate('Package "<info>'.$name.'</info>" does not exist but is provided by '.count($providers).' packages. Which version constraint would you like to use? [<info>*</info>] ', static function ($value) {
                        $parser = new VersionParser();
                        $parser->parseConstraints($value);

                        return $value;
                    }, 3, '*');
                }

                return [$name, $constraint];
            }

            // Check whether the package requirements were the problem
            if (!($platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter) && false !== ($candidate = $versionSelector->findBestCandidate($name, null, $preferredStability, PlatformRequirementFilterFactory::ignoreAll()))) {
                throw new \InvalidArgumentException(sprintf(
                    'Package %s has requirements incompatible with your PHP version, PHP extensions and Composer version' . $this->getPlatformExceptionDetails($candidate, $platformRepo),
                    $name
                ));
            }
            // Check whether the minimum stability was the problem but the package exists
            if (false !== ($package = $versionSelector->findBestCandidate($name, null, $preferredStability, $platformRequirementFilter, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES))) {
                // we must first verify if a valid package would be found in a lower priority repository
                if (false !== ($allReposPackage = $versionSelector->findBestCandidate($name, null, $preferredStability, $platformRequirementFilter, RepositorySet::ALLOW_SHADOWED_REPOSITORIES))) {
                    throw new \InvalidArgumentException(
                        'Package '.$name.' exists in '.$allReposPackage->getRepository()->getRepoName().' and '.$package->getRepository()->getRepoName().' which has a higher repository priority. The packages from the higher priority repository do not match your minimum-stability and are therefore not installable. That repository is canonical so the lower priority repo\'s packages are not installable. See https://getcomposer.org/repoprio for details and assistance.'
                    );
                }

                throw new \InvalidArgumentException(sprintf(
                    'Could not find a version of package %s matching your minimum-stability (%s). Require it with an explicit version constraint allowing its desired stability.',
                    $name,
                    $effectiveMinimumStability
                ));
            }
            // Check whether the PHP version was the problem for all versions
            if (!$platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter && false !== ($candidate = $versionSelector->findBestCandidate($name, null, $preferredStability, PlatformRequirementFilterFactory::ignoreAll(), RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES))) {
                $additional = '';
                if (false === $versionSelector->findBestCandidate($name, null, $preferredStability, PlatformRequirementFilterFactory::ignoreAll())) {
                    $additional = PHP_EOL.PHP_EOL.'Additionally, the package was only found with a stability of "'.$candidate->getStability().'" while your minimum stability is "'.$effectiveMinimumStability.'".';
                }

                throw new \InvalidArgumentException(sprintf(
                    'Could not find package %s in any version matching your PHP version, PHP extensions and Composer version' . $this->getPlatformExceptionDetails($candidate, $platformRepo) . '%s',
                    $name,
                    $additional
                ));
            }

            // Check for similar names/typos
            $similar = $this->findSimilar($name);
            if (count($similar) > 0) {
                if (in_array($name, $similar, true)) {
                    throw new \InvalidArgumentException(sprintf(
                        "Could not find package %s. It was however found via repository search, which indicates a consistency issue with the repository.",
                        $name
                    ));
                }

                if ($input->isInteractive()) {
                    $result = $io->select("<error>Could not find package $name.</error>\nPick one of these or leave empty to abort:", $similar, false, 1);
                    if ($result !== false) {
                        return $this->findBestVersionAndNameForPackage($io, $input, $similar[$result], $platformRepo, $preferredStability, $fixed);
                    }
                }

                throw new \InvalidArgumentException(sprintf(
                    "Could not find package %s.\n\nDid you mean " . (count($similar) > 1 ? 'one of these' : 'this') . "?\n    %s",
                    $name,
                    implode("\n    ", $similar)
                ));
            }

            throw new \InvalidArgumentException(sprintf(
                'Could not find a matching version of package %s. Check the package spelling, your version constraint and that the package is available in a stability which matches your minimum-stability (%s).',
                $name,
                $effectiveMinimumStability
            ));
        }

        return [
            $package->getPrettyName(),
            $fixed ? $package->getPrettyVersion() : $versionSelector->findRecommendedRequireVersion($package),
        ];
    }

    /**
     * @return array<string>
     */
    private function findSimilar(string $package): array
    {
        try {
            if (null === $this->repos) {
                throw new \LogicException('findSimilar was called before $this->repos was initialized');
            }
            $results = $this->repos->search($package);
        } catch (\Throwable $e) {
            if ($e instanceof \LogicException) {
                throw $e;
            }

            // ignore search errors
            return [];
        }
        $similarPackages = [];

        $installedRepo = $this->requireComposer()->getRepositoryManager()->getLocalRepository();

        foreach ($results as $result) {
            if (null !== $installedRepo->findPackage($result['name'], '*')) {
                // Ignore installed package
                continue;
            }
            $similarPackages[$result['name']] = levenshtein($package, $result['name']);
        }
        asort($similarPackages);

        return array_keys(array_slice($similarPackages, 0, 5));
    }

    private function getPlatformExceptionDetails(PackageInterface $candidate, ?PlatformRepository $platformRepo = null): string
    {
        $details = [];
        if (null === $platformRepo) {
            return '';
        }

        foreach ($candidate->getRequires() as $link) {
            if (!PlatformRepository::isPlatformPackage($link->getTarget())) {
                continue;
            }
            $platformPkg = $platformRepo->findPackage($link->getTarget(), '*');
            if (null === $platformPkg) {
                if ($platformRepo->isPlatformPackageDisabled($link->getTarget())) {
                    $details[] = $candidate->getPrettyName().' '.$candidate->getPrettyVersion().' requires '.$link->getTarget().' '.$link->getPrettyConstraint().' but it is disabled by your platform config. Enable it again with "composer config platform.'.$link->getTarget().' --unset".';
                } else {
                    $details[] = $candidate->getPrettyName().' '.$candidate->getPrettyVersion().' requires '.$link->getTarget().' '.$link->getPrettyConstraint().' but it is not present.';
                }
                continue;
            }
            if (!$link->getConstraint()->matches(new Constraint('==', $platformPkg->getVersion()))) {
                $platformPkgVersion = $platformPkg->getPrettyVersion();
                $platformExtra = $platformPkg->getExtra();
                if (isset($platformExtra['config.platform']) && $platformPkg instanceof CompletePackageInterface) {
                    $platformPkgVersion .= ' ('.$platformPkg->getDescription().')';
                }
                $details[] = $candidate->getPrettyName().' '.$candidate->getPrettyVersion().' requires '.$link->getTarget().' '.$link->getPrettyConstraint().' which does not match your installed version '.$platformPkgVersion.'.';
            }
        }

        if (count($details) === 0) {
            return '';
        }

        return ':'.PHP_EOL.'  - ' . implode(PHP_EOL.'  - ', $details);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Pcre\Preg;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputOption;
use Composer\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ScriptAliasCommand extends BaseCommand
{
    /** @var string */
    private $script;
    /** @var string */
    private $description;
    /** @var string[] */
    private $aliases;

    /**
     * @param string[] $aliases
     */
    public function __construct(string $script, ?string $description, array $aliases = [])
    {
        $this->script = $script;
        $this->description = $description ?? 'Runs the '.$script.' script as defined in composer.json';
        $this->aliases = $aliases;

        foreach ($this->aliases as $alias) {
            if (!is_string($alias)) {
                throw new \InvalidArgumentException('"scripts-aliases" element array values should contain only strings');
            }
        }

        $this->ignoreValidationErrors();

        parent::__construct();
    }

    protected function configure(): void
    {
        $this
            ->setName($this->script)
            ->setDescription($this->description)
            ->setAliases($this->aliases)
            ->setDefinition([
                new InputOption('dev', null, InputOption::VALUE_NONE, 'Sets the dev mode.'),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables the dev mode.'),
                new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''),
            ])
            ->setHelp(
                <<<EOT
The <info>run-script</info> command runs scripts defined in composer.json:

<info>php composer.phar run-script post-update-cmd</info>

Read more at https://getcomposer.org/doc/03-cli.md#run-script-run
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $composer = $this->requireComposer();

        $args = $input->getArguments();

        // TODO remove for Symfony 6+ as it is then in the interface
        if (!method_exists($input, '__toString')) { // @phpstan-ignore-line
            throw new \LogicException('Expected an Input instance that is stringable, got '.get_class($input));
        }

        return $composer->getEventDispatcher()->dispatchScript($this->script, $input->getOption('dev') || !$input->getOption('no-dev'), $args['args'], ['script-alias-input' => Preg::replace('{^\S+ ?}', '', $input->__toString(), 1)]);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\DependencyResolver\Request;
use Composer\Package\AliasPackage;
use Composer\Package\CompletePackageInterface;
use Composer\Package\Loader\RootPackageLoader;
use Composer\Package\Locker;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionBumper;
use Composer\Package\Version\VersionSelector;
use Composer\Pcre\Preg;
use Composer\Repository\RepositorySet;
use Composer\Util\Filesystem;
use Composer\Util\PackageSorter;
use Seld\Signal\SignalHandler;
use Symfony\Component\Console\Input\InputInterface;
use Composer\Console\Input\InputArgument;
use Composer\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Composer\Factory;
use Composer\Installer;
use Composer\Installer\InstallerEvents;
use Composer\Json\JsonFile;
use Composer\Json\JsonManipulator;
use Composer\Package\Version\VersionParser;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\BasePackage;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\IO\IOInterface;
use Composer\Advisory\Auditor;
use Composer\Util\Silencer;

/**
 * @author Jérémy Romey <jeremy@free-agent.fr>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class RequireCommand extends BaseCommand
{
    use CompletionTrait;
    use PackageDiscoveryTrait;

    /** @var bool */
    private $newlyCreated;
    /** @var bool */
    private $firstRequire;
    /** @var JsonFile */
    private $json;
    /** @var string */
    private $file;
    /** @var string */
    private $composerBackup;
    /** @var string file name */
    private $lock;
    /** @var ?string contents before modification if the lock file exists */
    private $lockBackup;
    /** @var bool */
    private $dependencyResolutionCompleted = false;

    /**
     * @return void
     */
    protected function configure()
    {
        $this
            ->setName('require')
            ->setAliases(['r'])
            ->setDescription('Adds required packages to your composer.json and installs them')
            ->setDefinition([
                new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Optional package name can also include a version constraint, e.g. foo/bar or foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"', null, $this->suggestAvailablePackageInclPlatform()),
                new InputOption('dev', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'),
                new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'),
                new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
                new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist (default behavior).'),
                new InputOption('prefer-install', null, InputOption::VALUE_REQUIRED, 'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).', null, $this->suggestPreferInstall()),
                new InputOption('fixed', null, InputOption::VALUE_NONE, 'Write fixed version to the composer.json.'),
                new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'DEPRECATED: This flag does not exist anymore.'),
                new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies (implies --no-install).'),
                new InputOption('no-install', null, InputOption::VALUE_NONE, 'Skip the install step after updating the composer.lock file.'),
                new InputOption('no-audit', null, InputOption::VALUE_NONE, 'Skip the audit step after updating the composer.lock file (can also be set via the COMPOSER_NO_AUDIT=1 env var).'),
                new InputOption('audit-format', null, InputOption::VALUE_REQUIRED, 'Audit output format. Must be "table", "plain", "json", or "summary".', Auditor::FORMAT_SUMMARY, Auditor::FORMATS),
                new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'),
                new InputOption('update-with-dependencies', 'w', InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated, except those that are root requirements (can also be set via the COMPOSER_WITH_DEPENDENCIES=1 env var).'),
                new InputOption('update-with-all-dependencies', 'W', InputOption::VALUE_NONE, 'Allows all inherited dependencies to be updated, including those that are root requirements (can also be set via the COMPOSER_WITH_ALL_DEPENDENCIES=1 env var).'),
                new InputOption('with-dependencies', null, InputOption::VALUE_NONE, 'Alias for --update-with-dependencies'),
                new InputOption('with-all-dependencies', null, InputOption::VALUE_NONE, 'Alias for --update-with-all-dependencies'),
                new InputOption('ignore-platform-req', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore a specific platform requirement (php & ext- packages).'),
                new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore all platform requirements (php & ext- packages).'),
                new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies (can also be set via the COMPOSER_PREFER_STABLE=1 env var).'),
                new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies (can also be set via the COMPOSER_PREFER_LOWEST=1 env var).'),
                new InputOption('minimal-changes', 'm', InputOption::VALUE_NONE, 'During an update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).'),
                new InputOption('sort-packages', null, InputOption::VALUE_NONE, 'Sorts packages when adding/updating a new dependency'),
                new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'),
                new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'),
                new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'),
                new InputOption('apcu-autoloader-prefix', null, InputOption::VALUE_REQUIRED, 'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'),
            ])
            ->setHelp(
                <<<EOT
The require command adds required packages to your composer.json and installs them.

If you do not specify a package, composer will prompt you to search for a package, and given results, provide a list of
matches to require.

If you do not specify a version constraint, composer will choose a suitable one based on the available package versions.

If you do not want to install the new dependencies immediately you can call it with --no-update

Read more at https://getcomposer.org/doc/03-cli.md#require-r
EOT
            )
        ;
    }

    /**
     * @throws \Seld\JsonLint\ParsingException
     */
    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $this->file = Factory::getComposerFile();
        $io = $this->getIO();

        if ($input->getOption('no-suggest')) {
            $io->writeError('<warning>You are using the deprecated option "--no-suggest". It has no effect and will break in Composer 3.</warning>');
        }

        $this->newlyCreated = !file_exists($this->file);
        if ($this->newlyCreated && !file_put_contents($this->file, "{\n}\n")) {
            $io->writeError('<error>'.$this->file.' could not be created.</error>');

            return 1;
        }
        if (!Filesystem::isReadable($this->file)) {
            $io->writeError('<error>'.$this->file.' is not readable.</error>');

            return 1;
        }

        if (filesize($this->file) === 0) {
            file_put_contents($this->file, "{\n}\n");
        }

        $this->json = new JsonFile($this->file);
        $this->lock = Factory::getLockFile($this->file);
        $this->composerBackup = file_get_contents($this->json->getPath());
        $this->lockBackup = file_exists($this->lock) ? file_get_contents($this->lock) : null;

        $signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) {
            $this->getIO()->writeError('Received '.$signal.', aborting', true, IOInterface::DEBUG);
            $this->revertComposerFile();
            $handler->exitWithLastSignal();
        });

        // check for writability by writing to the file as is_writable can not be trusted on network-mounts
        // see https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926
        if (!is_writable($this->file) && false === Silencer::call('file_put_contents', $this->file, $this->composerBackup)) {
            $io->writeError('<error>'.$this->file.' is not writable.</error>');

            return 1;
        }

        if ($input->getOption('fixed') === true) {
            $config = $this->json->read();

            $packageType = empty($config['type']) ? 'library' : $config['type'];

            /**
             * @see https://github.com/composer/composer/pull/8313#issuecomment-532637955
             */
            if ($packageType !== 'project' && !$input->getOption('dev')) {
                $io->writeError('<error>The "--fixed" option is only allowed for packages with a "project" type or for dev dependencies to prevent possible misuses.</error>');

                if (!isset($config['type'])) {
                    $io->writeError('<error>If your package is not a library, you can explicitly specify the "type" by using "composer config type project".</error>');
                }

                return 1;
            }
        }

        $composer = $this->requireComposer();
        $repos = $composer->getRepositoryManager()->getRepositories();

        $platformOverrides = $composer->getConfig()->get('platform');
        // initialize $this->repos as it is used by the PackageDiscoveryTrait
        $this->repos = new CompositeRepository(array_merge(
            [$platformRepo = new PlatformRepository([], $platformOverrides)],
            $repos
        ));

        if ($composer->getPackage()->getPreferStable()) {
            $preferredStability = 'stable';
        } else {
            $preferredStability = $composer->getPackage()->getMinimumStability();
        }

        try {
            $requirements = $this->determineRequirements(
                $input,
                $output,
                $input->getArgument('packages'),
                $platformRepo,
                $preferredStability,
                $input->getOption('no-update'), // if there is no update, we need to use the best possible version constraint directly as we cannot rely on the solver to guess the best constraint
                $input->getOption('fixed')
            );
        } catch (\Exception $e) {
            if ($this->newlyCreated) {
                $this->revertComposerFile();

                throw new \RuntimeException('No composer.json present in the current directory ('.$this->file.'), this may be the cause of the following exception.', 0, $e);
            }

            throw $e;
        }

        $requirements = $this->formatRequirements($requirements);

        if (!$input->getOption('dev') && $io->isInteractive() && !$composer->isGlobal()) {
            $devPackages = [];
            $devTags = ['dev', 'testing', 'static analysis'];
            $currentRequiresByKey = $this->getPackagesByRequireKey();
            foreach ($requirements as $name => $version) {
                // skip packages which are already in the composer.json as those have already been decided
                if (isset($currentRequiresByKey[$name])) {
                    continue;
                }

                $pkg = PackageSorter::getMostCurrentVersion($this->getRepos()->findPackages($name));
                if ($pkg instanceof CompletePackageInterface) {
                    $pkgDevTags = array_intersect($devTags, array_map('strtolower', $pkg->getKeywords()));
                    if (count($pkgDevTags) > 0) {
                        $devPackages[] = $pkgDevTags;
                    }
                }
            }

            if (count($devPackages) === count($requirements)) {
                $plural = count($requirements) > 1 ? 's' : '';
                $plural2 = count($requirements) > 1 ? 'are' : 'is';
                $plural3 = count($requirements) > 1 ? 'they are' : 'it is';
                $pkgDevTags = array_unique(array_merge(...$devPackages));
                $io->warning('The package'.$plural.' you required '.$plural2.' recommended to be placed in require-dev (because '.$plural3.' tagged as "'.implode('", "', $pkgDevTags).'") but you did not use --dev.');
                if ($io->askConfirmation('<info>Do you want to re-run the command with --dev?</> [<comment>yes</>]? ')) {
                    $input->setOption('dev', true);
                }
            }

            unset($devPackages, $pkgDevTags);
        }

        $requireKey = $input->getOption('dev') ? 'require-dev' : 'require';
        $removeKey = $input->getOption('dev') ? 'require' : 'require-dev';

        // check which requirements need the version guessed
        $requirementsToGuess = [];
        foreach ($requirements as $package => $constraint) {
            if ($constraint === 'guess') {
                $requirements[$package] = '*';
                $requirementsToGuess[] = $package;
            }
        }

        // validate requirements format
        $versionParser = new VersionParser();
        foreach ($requirements as $package => $constraint) {
            if (strtolower($package) === $composer->getPackage()->getName()) {
                $io->writeError(sprintf('<error>Root package \'%s\' cannot require itself in its composer.json</error>', $package));

                return 1;
            }
            if ($constraint === 'self.version') {
                continue;
            }
            $versionParser->parseConstraints($constraint);
        }

        $inconsistentRequireKeys = $this->getInconsistentRequireKeys($requirements, $requireKey);
        if (count($inconsistentRequireKeys) > 0) {
            foreach ($inconsistentRequireKeys as $package) {
                $io->warning(sprintf(
                    '%s is currently present in the %s key and you ran the command %s the --dev flag, which will move it to the %s key.',
                    $package,
                    $removeKey,
                    $input->getOption('dev') ? 'with' : 'without',
                    $requireKey
                ));
            }

            if ($io->isInteractive()) {
                if (!$io->askConfirmation(sprintf('<info>Do you want to move %s?</info> [<comment>no</comment>]? ', count($inconsistentRequireKeys) > 1 ? 'these requirements' : 'this requirement'), false)) {
                    if (!$io->askConfirmation(sprintf('<info>Do you want to re-run the command %s --dev?</info> [<comment>yes</comment>]? ', $input->getOption('dev') ? 'without' : 'with'), true)) {
                        return 0;
                    }

                    $input->setOption('dev', true);
                    [$requireKey, $removeKey] = [$removeKey, $requireKey];
                }
            }
        }

        $sortPackages = $input->getOption('sort-packages') || $composer->getConfig()->get('sort-packages');

        $this->firstRequire = $this->newlyCreated;
        if (!$this->firstRequire) {
            $composerDefinition = $this->json->read();
            if (count($composerDefinition['require'] ?? []) === 0 && count($composerDefinition['require-dev'] ?? []) === 0) {
                $this->firstRequire = true;
            }
        }

        if (!$input->getOption('dry-run')) {
            $this->updateFile($this->json, $requirements, $requireKey, $removeKey, $sortPackages);
        }

        $io->writeError('<info>'.$this->file.' has been '.($this->newlyCreated ? 'created' : 'updated').'</info>');

        if ($input->getOption('no-update')) {
            return 0;
        }

        $composer->getPluginManager()->deactivateInstalledPlugins();

        try {
            $result = $this->doUpdate($input, $output, $io, $requirements, $requireKey, $removeKey);
            if ($result === 0 && count($requirementsToGuess) > 0) {
                $result = $this->updateRequirementsAfterResolution($requirementsToGuess, $requireKey, $removeKey, $sortPackages, $input->getOption('dry-run'), $input->getOption('fixed'));
            }

            return $result;
        } catch (\Exception $e) {
            if (!$this->dependencyResolutionCompleted) {
                $this->revertComposerFile();
            }
            throw $e;
        } finally {
            if ($input->getOption('dry-run') && $this->newlyCreated) {
                @unlink($this->json->getPath());
            }

            $signalHandler->unregister();
        }
    }

    /**
     * @param array<string, string> $newRequirements
     * @return string[]
     */
    private function getInconsistentRequireKeys(array $newRequirements, string $requireKey): array
    {
        $requireKeys = $this->getPackagesByRequireKey();
        $inconsistentRequirements = [];
        foreach ($requireKeys as $package => $packageRequireKey) {
            if (!isset($newRequirements[$package])) {
                continue;
            }
            if ($requireKey !== $packageRequireKey) {
                $inconsistentRequirements[] = $package;
            }
        }

        return $inconsistentRequirements;
    }

    /**
     * @return array<string, string>
     */
    private function getPackagesByRequireKey(): array
    {
        $composerDefinition = $this->json->read();
        $require = [];
        $requireDev = [];

        if (isset($composerDefinition['require'])) {
            $require = $composerDefinition['require'];
        }

        if (isset($composerDefinition['require-dev'])) {
            $requireDev = $composerDefinition['require-dev'];
        }

        return array_merge(
            array_fill_keys(array_keys($require), 'require'),
            array_fill_keys(array_keys($requireDev), 'require-dev')
        );
    }

    /**
     * @param array<string, string> $requirements
     * @param 'require'|'require-dev' $requireKey
     * @param 'require'|'require-dev' $removeKey
     * @throws \Exception
     */
    private function doUpdate(InputInterface $input, OutputInterface $output, IOInterface $io, array $requirements, string $requireKey, string $removeKey): int
    {
        // Update packages
        $this->resetComposer();
        $composer = $this->requireComposer();

        $this->dependencyResolutionCompleted = false;
        $composer->getEventDispatcher()->addListener(InstallerEvents::PRE_OPERATIONS_EXEC, function (): void {
            $this->dependencyResolutionCompleted = true;
        }, 10000);

        if ($input->getOption('dry-run')) {
            $rootPackage = $composer->getPackage();
            $links = [
                'require' => $rootPackage->getRequires(),
                'require-dev' => $rootPackage->getDevRequires(),
            ];
            $loader = new ArrayLoader();
            $newLinks = $loader->parseLinks($rootPackage->getName(), $rootPackage->getPrettyVersion(), BasePackage::$supportedLinkTypes[$requireKey]['method'], $requirements);
            $links[$requireKey] = array_merge($links[$requireKey], $newLinks);
            foreach ($requirements as $package => $constraint) {
                unset($links[$removeKey][$package]);
            }
            $rootPackage->setRequires($links['require']);
            $rootPackage->setDevRequires($links['require-dev']);

            // extract stability flags & references as they weren't present when loading the unmodified composer.json
            $references = $rootPackage->getReferences();
            $references = RootPackageLoader::extractReferences($requirements, $references);
            $rootPackage->setReferences($references);
            $stabilityFlags = $rootPackage->getStabilityFlags();
            $stabilityFlags = RootPackageLoader::extractStabilityFlags($requirements, $rootPackage->getMinimumStability(), $stabilityFlags);
            $rootPackage->setStabilityFlags($stabilityFlags);
            unset($stabilityFlags, $references);
        }

        $updateDevMode = !$input->getOption('update-no-dev');
        $optimize = $input->getOption('optimize-autoloader') || $composer->getConfig()->get('optimize-autoloader');
        $authoritative = $input->getOption('classmap-authoritative') || $composer->getConfig()->get('classmap-authoritative');
        $apcuPrefix = $input->getOption('apcu-autoloader-prefix');
        $apcu = $apcuPrefix !== null || $input->getOption('apcu-autoloader') || $composer->getConfig()->get('apcu-autoloader');

        $updateAllowTransitiveDependencies = Request::UPDATE_ONLY_LISTED;
        $flags = '';
        if ($input->getOption('update-with-all-dependencies') || $input->getOption('with-all-dependencies')) {
            $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS;
            $flags .= ' --with-all-dependencies';
        } elseif ($input->getOption('update-with-dependencies') || $input->getOption('with-dependencies')) {
            $updateAllowTransitiveDependencies = Request::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE;
            $flags .= ' --with-dependencies';
        }

        $io->writeError('<info>Running composer update '.implode(' ', array_keys($requirements)).$flags.'</info>');

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'require', $input, $output);
        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        $composer->getInstallationManager()->setOutputProgress(!$input->getOption('no-progress'));

        $install = Installer::create($io, $composer);

        [$preferSource, $preferDist] = $this->getPreferredInstallOptions($composer->getConfig(), $input);

        $install
            ->setDryRun($input->getOption('dry-run'))
            ->setVerbose($input->getOption('verbose'))
            ->setPreferSource($preferSource)
            ->setPreferDist($preferDist)
            ->setDevMode($updateDevMode)
            ->setOptimizeAutoloader($optimize)
            ->setClassMapAuthoritative($authoritative)
            ->setApcuAutoloader($apcu, $apcuPrefix)
            ->setUpdate(true)
            ->setInstall(!$input->getOption('no-install'))
            ->setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies)
            ->setPlatformRequirementFilter($this->getPlatformRequirementFilter($input))
            ->setPreferStable($input->getOption('prefer-stable'))
            ->setPreferLowest($input->getOption('prefer-lowest'))
            ->setAudit(!$input->getOption('no-audit'))
            ->setAuditFormat($this->getAuditFormat($input))
            ->setMinimalUpdate($input->getOption('minimal-changes'))
        ;

        // if no lock is present, or the file is brand new, we do not do a
        // partial update as this is not supported by the Installer
        if (!$this->firstRequire && $composer->getLocker()->isLocked()) {
            $install->setUpdateAllowList(array_keys($requirements));
        }

        $status = $install->run();
        if ($status !== 0 && $status !== Installer::ERROR_AUDIT_FAILED) {
            if ($status === Installer::ERROR_DEPENDENCY_RESOLUTION_FAILED) {
                foreach ($this->normalizeRequirements($input->getArgument('packages')) as $req) {
                    if (!isset($req['version'])) {
                        $io->writeError('You can also try re-running composer require with an explicit version constraint, e.g. "composer require '.$req['name'].':*" to figure out if any version is installable, or "composer require '.$req['name'].':^2.1" if you know which you need.');
                        break;
                    }
                }
            }
            $this->revertComposerFile();
        }

        return $status;
    }

    /**
     * @param list<string> $requirementsToUpdate
     */
    private function updateRequirementsAfterResolution(array $requirementsToUpdate, string $requireKey, string $removeKey, bool $sortPackages, bool $dryRun, bool $fixed): int
    {
        $composer = $this->requireComposer();
        $locker = $composer->getLocker();
        $requirements = [];
        $versionSelector = new VersionSelector(new RepositorySet());
        $repo = $locker->isLocked() ? $composer->getLocker()->getLockedRepository(true) : $composer->getRepositoryManager()->getLocalRepository();
        foreach ($requirementsToUpdate as $packageName) {
            $package = $repo->findPackage($packageName, '*');
            while ($package instanceof AliasPackage) {
                $package = $package->getAliasOf();
            }

            if (!$package instanceof PackageInterface) {
                continue;
            }

            if ($fixed) {
                $requirements[$packageName] = $package->getPrettyVersion();
            } else {
                $requirements[$packageName] = $versionSelector->findRecommendedRequireVersion($package);
            }
            $this->getIO()->writeError(sprintf(
                'Using version <info>%s</info> for <info>%s</info>',
                $requirements[$packageName],
                $packageName
            ));

            if (Preg::isMatch('{^dev-(?!main$|master$|trunk$|latest$)}', $requirements[$packageName])) {
                $this->getIO()->warning('Version '.$requirements[$packageName].' looks like it may be a feature branch which is unlikely to keep working in the long run and may be in an unstable state');
                if ($this->getIO()->isInteractive() && !$this->getIO()->askConfirmation('Are you sure you want to use this constraint (<comment>Y</comment>) or would you rather abort (<comment>n</comment>) the whole operation [<comment>Y,n</comment>]? ')) {
                    $this->revertComposerFile();

                    return 1;
                }
            }
        }

        if (!$dryRun) {
            $this->updateFile($this->json, $requirements, $requireKey, $removeKey, $sortPackages);
            if ($locker->isLocked() && $composer->getConfig()->get('lock')) {
                $stabilityFlags = RootPackageLoader::extractStabilityFlags($requirements, $composer->getPackage()->getMinimumStability(), []);
                $locker->updateHash($this->json, function (array $lockData) use ($stabilityFlags) {
                    foreach ($stabilityFlags as $packageName => $flag) {
                        $lockData['stability-flags'][$packageName] = $flag;
                    }

                    return $lockData;
                });
            }
        }

        return 0;
    }

    /**
     * @param array<string, string> $new
     */
    private function updateFile(JsonFile $json, array $new, string $requireKey, string $removeKey, bool $sortPackages): void
    {
        if ($this->updateFileCleanly($json, $new, $requireKey, $removeKey, $sortPackages)) {
            return;
        }

        $composerDefinition = $this->json->read();
        foreach ($new as $package => $version) {
            $composerDefinition[$requireKey][$package] = $version;
            unset($composerDefinition[$removeKey][$package]);
            if (isset($composerDefinition[$removeKey]) && count($composerDefinition[$removeKey]) === 0) {
                unset($composerDefinition[$removeKey]);
            }
        }
        $this->json->write($composerDefinition);
    }

    /**
     * @param array<string, string> $new
     */
    private function updateFileCleanly(JsonFile $json, array $new, string $requireKey, string $removeKey, bool $sortPackages): bool
    {
        $contents = file_get_contents($json->getPath());

        $manipulator = new JsonManipulator($contents);

        foreach ($new as $package => $constraint) {
            if (!$manipulator->addLink($requireKey, $package, $constraint, $sortPackages)) {
                return false;
            }
            if (!$manipulator->removeSubNode($removeKey, $package)) {
                return false;
            }
        }

        $manipulator->removeMainKeyIfEmpty($removeKey);

        file_put_contents($json->getPath(), $manipulator->getContents());

        return true;
    }

    protected function interact(InputInterface $input, OutputInterface $output): void
    {
    }

    private function revertComposerFile(): void
    {
        $io = $this->getIO();

        if ($this->newlyCreated) {
            $io->writeError("\n".'<error>Installation failed, deleting '.$this->file.'.</error>');
            unlink($this->json->getPath());
            if (file_exists($this->lock)) {
                unlink($this->lock);
            }
        } else {
            $msg = ' to its ';
            if ($this->lockBackup) {
                $msg = ' and '.$this->lock.' to their ';
            }
            $io->writeError("\n".'<error>Installation failed, reverting '.$this->file.$msg.'original content.</error>');
            file_put_contents($this->json->getPath(), $this->composerBackup);
            if ($this->lockBackup) {
                file_put_contents($this->lock, $this->lockBackup);
            }
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer;

use Composer\IO\IOInterface;
use Composer\Pcre\Preg;
use Composer\Util\Filesystem;
use Composer\Util\Platform;
use Composer\Util\Silencer;
use Symfony\Component\Finder\Finder;

/**
 * Reads/writes to a filesystem cache
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Cache
{
    /** @var bool|null */
    private static $cacheCollected = null;
    /** @var IOInterface */
    private $io;
    /** @var string */
    private $root;
    /** @var ?bool */
    private $enabled = null;
    /** @var string */
    private $allowlist;
    /** @var Filesystem */
    private $filesystem;
    /** @var bool */
    private $readOnly;

    /**
     * @param string      $cacheDir   location of the cache
     * @param string      $allowlist  List of characters that are allowed in path names (used in a regex character class)
     * @param Filesystem  $filesystem optional filesystem instance
     * @param bool        $readOnly   whether the cache is in readOnly mode
     */
    public function __construct(IOInterface $io, string $cacheDir, string $allowlist = 'a-z0-9._', ?Filesystem $filesystem = null, bool $readOnly = false)
    {
        $this->io = $io;
        $this->root = rtrim($cacheDir, '/\\') . '/';
        $this->allowlist = $allowlist;
        $this->filesystem = $filesystem ?: new Filesystem();
        $this->readOnly = $readOnly;

        if (!self::isUsable($cacheDir)) {
            $this->enabled = false;
        }
    }

    /**
     * @return void
     */
    public function setReadOnly(bool $readOnly)
    {
        $this->readOnly = $readOnly;
    }

    /**
     * @return bool
     */
    public function isReadOnly()
    {
        return $this->readOnly;
    }

    /**
     * @return bool
     */
    public static function isUsable(string $path)
    {
        return !Preg::isMatch('{(^|[\\\\/])(\$null|nul|NUL|/dev/null)([\\\\/]|$)}', $path);
    }

    /**
     * @return bool
     */
    public function isEnabled()
    {
        if ($this->enabled === null) {
            $this->enabled = true;

            if (
                !$this->readOnly
                && (
                    (!is_dir($this->root) && !Silencer::call('mkdir', $this->root, 0777, true))
                    || !is_writable($this->root)
                )
            ) {
                $this->io->writeError('<warning>Cannot create cache directory ' . $this->root . ', or directory is not writable. Proceeding without cache. See also cache-read-only config if your filesystem is read-only.</warning>');
                $this->enabled = false;
            }
        }

        return $this->enabled;
    }

    /**
     * @return string
     */
    public function getRoot()
    {
        return $this->root;
    }

    /**
     * @return string|false
     */
    public function read(string $file)
    {
        if ($this->isEnabled()) {
            $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file);
            if (file_exists($this->root . $file)) {
                $this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG);

                return file_get_contents($this->root . $file);
            }
        }

        return false;
    }

    /**
     * @return bool
     */
    public function write(string $file, string $contents)
    {
        $wasEnabled = $this->enabled === true;

        if ($this->isEnabled() && !$this->readOnly) {
            $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file);

            $this->io->writeError('Writing '.$this->root . $file.' into cache', true, IOInterface::DEBUG);

            $tempFileName = $this->root . $file . bin2hex(random_bytes(5)) . '.tmp';
            try {
                return file_put_contents($tempFileName, $contents) !== false && rename($tempFileName, $this->root . $file);
            } catch (\ErrorException $e) {
                // If the write failed despite isEnabled checks passing earlier, rerun the isEnabled checks to
                // see if they are still current and recreate the cache dir if needed. Refs https://github.com/composer/composer/issues/11076
                if ($wasEnabled) {
                    clearstatcache();
                    $this->enabled = null;
                    return $this->write($file, $contents);
                }

                $this->io->writeError('<warning>Failed to write into cache: '.$e->getMessage().'</warning>', true, IOInterface::DEBUG);
                if (Preg::isMatch('{^file_put_contents\(\): Only ([0-9]+) of ([0-9]+) bytes written}', $e->getMessage(), $m)) {
                    // Remove partial file.
                    unlink($tempFileName);

                    $message = sprintf(
                        '<warning>Writing %1$s into cache failed after %2$u of %3$u bytes written, only %4$s bytes of free space available</warning>',
                        $tempFileName,
                        $m[1],
                        $m[2],
                        function_exists('disk_free_space') ? @disk_free_space(dirname($tempFileName)) : 'unknown'
                    );

                    $this->io->writeError($message);

                    return false;
                }

                throw $e;
            }
        }

        return false;
    }

    /**
     * Copy a file into the cache
     *
     * @return bool
     */
    public function copyFrom(string $file, string $source)
    {
        if ($this->isEnabled() && !$this->readOnly) {
            $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file);
            $this->filesystem->ensureDirectoryExists(dirname($this->root . $file));

            if (!file_exists($source)) {
                $this->io->writeError('<error>'.$source.' does not exist, can not write into cache</error>');
            } elseif ($this->io->isDebug()) {
                $this->io->writeError('Writing '.$this->root . $file.' into cache from '.$source);
            }

            return $this->filesystem->copy($source, $this->root . $file);
        }

        return false;
    }

    /**
     * Copy a file out of the cache
     *
     * @return bool
     */
    public function copyTo(string $file, string $target)
    {
        if ($this->isEnabled()) {
            $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file);
            if (file_exists($this->root . $file)) {
                try {
                    touch($this->root . $file, (int) filemtime($this->root . $file), time());
                } catch (\ErrorException $e) {
                    // fallback in case the above failed due to incorrect ownership
                    // see https://github.com/composer/composer/issues/4070
                    Silencer::call('touch', $this->root . $file);
                }

                $this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG);

                return $this->filesystem->copy($this->root . $file, $target);
            }
        }

        return false;
    }

    /**
     * @return bool
     */
    public function gcIsNecessary()
    {
        if (self::$cacheCollected) {
            return false;
        }

        self::$cacheCollected = true;
        if (Platform::getEnv('COMPOSER_TEST_SUITE')) {
            return false;
        }

        if (Platform::isInputCompletionProcess()) {
            return false;
        }

        return !random_int(0, 50);
    }

    /**
     * @return bool
     */
    public function remove(string $file)
    {
        if ($this->isEnabled() && !$this->readOnly) {
            $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file);
            if (file_exists($this->root . $file)) {
                return $this->filesystem->unlink($this->root . $file);
            }
        }

        return false;
    }

    /**
     * @return bool
     */
    public function clear()
    {
        if ($this->isEnabled() && !$this->readOnly) {
            $this->filesystem->emptyDirectory($this->root);

            return true;
        }

        return false;
    }

    /**
     * @return int|false
     * @phpstan-return int<0, max>|false
     */
    public function getAge(string $file)
    {
        if ($this->isEnabled()) {
            $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file);
            if (file_exists($this->root . $file) && ($mtime = filemtime($this->root . $file)) !== false) {
                return abs(time() - $mtime);
            }
        }

        return false;
    }

    /**
     * @return bool
     */
    public function gc(int $ttl, int $maxSize)
    {
        if ($this->isEnabled() && !$this->readOnly) {
            $expire = new \DateTime();
            $expire->modify('-'.$ttl.' seconds');

            $finder = $this->getFinder()->date('until '.$expire->format('Y-m-d H:i:s'));
            foreach ($finder as $file) {
                $this->filesystem->unlink($file->getPathname());
            }

            $totalSize = $this->filesystem->size($this->root);
            if ($totalSize > $maxSize) {
                $iterator = $this->getFinder()->sortByAccessedTime()->getIterator();
                while ($totalSize > $maxSize && $iterator->valid()) {
                    $filepath = $iterator->current()->getPathname();
                    $totalSize -= $this->filesystem->size($filepath);
                    $this->filesystem->unlink($filepath);
                    $iterator->next();
                }
            }

            self::$cacheCollected = true;

            return true;
        }

        return false;
    }

    public function gcVcsCache(int $ttl): bool
    {
        if ($this->isEnabled()) {
            $expire = new \DateTime();
            $expire->modify('-'.$ttl.' seconds');

            $finder = Finder::create()->in($this->root)->directories()->depth(0)->date('until '.$expire->format('Y-m-d H:i:s'));
            foreach ($finder as $file) {
                $this->filesystem->removeDirectory($file->getPathname());
            }

            self::$cacheCollected = true;

            return true;
        }

        return false;
    }

    /**
     * @return string|false
     */
    public function sha1(string $file)
    {
        if ($this->isEnabled()) {
            $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file);
            if (file_exists($this->root . $file)) {
                return hash_file('sha1', $this->root . $file);
            }
        }

        return false;
    }

    /**
     * @return string|false
     */
    public function sha256(string $file)
    {
        if ($this->isEnabled()) {
            $file = Preg::replace('{[^'.$this->allowlist.']}i', '-', $file);
            if (file_exists($this->root . $file)) {
                return hash_file('sha256', $this->root . $file);
            }
        }

        return false;
    }

    /**
     * @return Finder
     */
    protected function getFinder()
    {
        return Finder::create()->in($this->root)->files();
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package;

use Composer\Repository\RepositoryInterface;

/**
 * Defines the essential information a package has that is used during solving/installation
 *
 * PackageInterface & derivatives are considered internal, you may use them in type hints but extending/implementing them is not recommended and not supported. Things may change without notice.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 *
 * @phpstan-type AutoloadRules    array{psr-0?: array<string, string|string[]>, psr-4?: array<string, string|string[]>, classmap?: list<string>, files?: list<string>, exclude-from-classmap?: list<string>}
 * @phpstan-type DevAutoloadRules array{psr-0?: array<string, string|string[]>, psr-4?: array<string, string|string[]>, classmap?: list<string>, files?: list<string>}
 * @phpstan-type PhpExtConfig     array{extension-name?: string, priority?: int, support-zts?: bool, support-nts?: bool, build-path?: string|null, download-url-method?: string, os-families?: non-empty-list<non-empty-string>, os-families-exclude?: non-empty-list<non-empty-string>, configure-options?: list<array{name: string, description?: string}>}
 */
interface PackageInterface
{
    public const DISPLAY_SOURCE_REF_IF_DEV = 0;
    public const DISPLAY_SOURCE_REF = 1;
    public const DISPLAY_DIST_REF = 2;

    /**
     * Returns the package's name without version info, thus not a unique identifier
     *
     * @return string package name
     */
    public function getName(): string;

    /**
     * Returns the package's pretty (i.e. with proper case) name
     *
     * @return string package name
     */
    public function getPrettyName(): string;

    /**
     * Returns a set of names that could refer to this package
     *
     * No version or release type information should be included in any of the
     * names. Provided or replaced package names need to be returned as well.
     *
     * @param bool $provides Whether provided names should be included
     *
     * @return string[] An array of strings referring to this package
     */
    public function getNames(bool $provides = true): array;

    /**
     * Allows the solver to set an id for this package to refer to it.
     */
    public function setId(int $id): void;

    /**
     * Retrieves the package's id set through setId
     *
     * @return int The previously set package id
     */
    public function getId(): int;

    /**
     * Returns whether the package is a development virtual package or a concrete one
     */
    public function isDev(): bool;

    /**
     * Returns the package type, e.g. library
     *
     * @return string The package type
     */
    public function getType(): string;

    /**
     * Returns the package targetDir property
     *
     * @return ?string The package targetDir
     */
    public function getTargetDir(): ?string;

    /**
     * Returns the package extra data
     *
     * @return mixed[] The package extra data
     */
    public function getExtra(): array;

    /**
     * Sets source from which this package was installed (source/dist).
     *
     * @param ?string $type source/dist
     * @phpstan-param 'source'|'dist'|null $type
     */
    public function setInstallationSource(?string $type): void;

    /**
     * Returns source from which this package was installed (source/dist).
     *
     * @return ?string source/dist
     * @phpstan-return 'source'|'dist'|null
     */
    public function getInstallationSource(): ?string;

    /**
     * Returns the repository type of this package, e.g. git, svn
     *
     * @return ?string The repository type
     */
    public function getSourceType(): ?string;

    /**
     * Returns the repository url of this package, e.g. git://github.com/naderman/composer.git
     *
     * @return ?string The repository url
     */
    public function getSourceUrl(): ?string;

    /**
     * Returns the repository urls of this package including mirrors, e.g. git://github.com/naderman/composer.git
     *
     * @return list<string>
     */
    public function getSourceUrls(): array;

    /**
     * Returns the repository reference of this package, e.g. master, 1.0.0 or a commit hash for git
     *
     * @return ?string The repository reference
     */
    public function getSourceReference(): ?string;

    /**
     * Returns the source mirrors of this package
     *
     * @return ?list<array{url: non-empty-string, preferred: bool}>
     */
    public function getSourceMirrors(): ?array;

    /**
     * @param  null|list<array{url: non-empty-string, preferred: bool}> $mirrors
     */
    public function setSourceMirrors(?array $mirrors): void;

    /**
     * Returns the type of the distribution archive of this version, e.g. zip, tarball
     *
     * @return ?string The repository type
     */
    public function getDistType(): ?string;

    /**
     * Returns the url of the distribution archive of this version
     *
     * @return ?non-empty-string
     */
    public function getDistUrl(): ?string;

    /**
     * Returns the urls of the distribution archive of this version, including mirrors
     *
     * @return non-empty-string[]
     */
    public function getDistUrls(): array;

    /**
     * Returns the reference of the distribution archive of this version, e.g. master, 1.0.0 or a commit hash for git
     *
     * @return ?string
     */
    public function getDistReference(): ?string;

    /**
     * Returns the sha1 checksum for the distribution archive of this version
     *
     * Can be an empty string which should be treated as null
     *
     * @return ?string
     */
    public function getDistSha1Checksum(): ?string;

    /**
     * Returns the dist mirrors of this package
     *
     * @return ?list<array{url: non-empty-string, preferred: bool}>
     */
    public function getDistMirrors(): ?array;

    /**
     * @param  null|list<array{url: non-empty-string, preferred: bool}> $mirrors
     */
    public function setDistMirrors(?array $mirrors): void;

    /**
     * Returns the version of this package
     *
     * @return string version
     */
    public function getVersion(): string;

    /**
     * Returns the pretty (i.e. non-normalized) version string of this package
     *
     * @return string version
     */
    public function getPrettyVersion(): string;

    /**
     * Returns the pretty version string plus a git or hg commit hash of this package
     *
     * @see getPrettyVersion
     *
     * @param  bool   $truncate    If the source reference is a sha1 hash, truncate it
     * @param  int    $displayMode One of the DISPLAY_ constants on this interface determining display of references
     * @return string version
     *
     * @phpstan-param self::DISPLAY_SOURCE_REF_IF_DEV|self::DISPLAY_SOURCE_REF|self::DISPLAY_DIST_REF $displayMode
     */
    public function getFullPrettyVersion(bool $truncate = true, int $displayMode = self::DISPLAY_SOURCE_REF_IF_DEV): string;

    /**
     * Returns the release date of the package
     *
     * @return ?\DateTimeInterface
     */
    public function getReleaseDate(): ?\DateTimeInterface;

    /**
     * Returns the stability of this package: one of (dev, alpha, beta, RC, stable)
     *
     * @phpstan-return 'stable'|'RC'|'beta'|'alpha'|'dev'
     */
    public function getStability(): string;

    /**
     * Returns a set of links to packages which need to be installed before
     * this package can be installed
     *
     * @return array<string, Link> A map of package links defining required packages, indexed by the require package's name
     */
    public function getRequires(): array;

    /**
     * Returns a set of links to packages which must not be installed at the
     * same time as this package
     *
     * @return Link[] An array of package links defining conflicting packages
     */
    public function getConflicts(): array;

    /**
     * Returns a set of links to virtual packages that are provided through
     * this package
     *
     * @return Link[] An array of package links defining provided packages
     */
    public function getProvides(): array;

    /**
     * Returns a set of links to packages which can alternatively be
     * satisfied by installing this package
     *
     * @return Link[] An array of package links defining replaced packages
     */
    public function getReplaces(): array;

    /**
     * Returns a set of links to packages which are required to develop
     * this package. These are installed if in dev mode.
     *
     * @return array<string, Link> A map of package links defining packages required for development, indexed by the require package's name
     */
    public function getDevRequires(): array;

    /**
     * Returns a set of package names and reasons why they are useful in
     * combination with this package.
     *
     * @return array An array of package suggestions with descriptions
     * @phpstan-return array<string, string>
     */
    public function getSuggests(): array;

    /**
     * Returns an associative array of autoloading rules
     *
     * {"<type>": {"<namespace": "<directory>"}}
     *
     * Type is either "psr-4", "psr-0", "classmap" or "files". Namespaces are mapped to
     * directories for autoloading using the type specified.
     *
     * @return array Mapping of autoloading rules
     * @phpstan-return AutoloadRules
     */
    public function getAutoload(): array;

    /**
     * Returns an associative array of dev autoloading rules
     *
     * {"<type>": {"<namespace": "<directory>"}}
     *
     * Type is either "psr-4", "psr-0", "classmap" or "files". Namespaces are mapped to
     * directories for autoloading using the type specified.
     *
     * @return array Mapping of dev autoloading rules
     * @phpstan-return DevAutoloadRules
     */
    public function getDevAutoload(): array;

    /**
     * Returns a list of directories which should get added to PHP's
     * include path.
     *
     * @return string[]
     */
    public function getIncludePaths(): array;

    /**
     * Returns the settings for php extension packages
     *
     * @return array|null
     *
     * @phpstan-return PhpExtConfig|null
     */
    public function getPhpExt(): ?array;

    /**
     * Stores a reference to the repository that owns the package
     */
    public function setRepository(RepositoryInterface $repository): void;

    /**
     * Returns a reference to the repository that owns the package
     *
     * @return ?RepositoryInterface
     */
    public function getRepository(): ?RepositoryInterface;

    /**
     * Returns the package binaries
     *
     * @return string[]
     */
    public function getBinaries(): array;

    /**
     * Returns package unique name, constructed from name and version.
     */
    public function getUniqueName(): string;

    /**
     * Returns the package notification url
     *
     * @return ?string
     */
    public function getNotificationUrl(): ?string;

    /**
     * Converts the package into a readable and unique string
     */
    public function __toString(): string;

    /**
     * Converts the package into a pretty readable string
     */
    public function getPrettyString(): string;

    public function isDefaultBranch(): bool;

    /**
     * Returns a list of options to download package dist files
     *
     * @return mixed[]
     */
    public function getTransportOptions(): array;

    /**
     * Configures the list of options to download package dist files
     *
     * @param mixed[] $options
     */
    public function setTransportOptions(array $options): void;

    public function setSourceReference(?string $reference): void;

    public function setDistUrl(?string $url): void;

    public function setDistType(?string $type): void;

    public function setDistReference(?string $reference): void;

    /**
     * Set dist and source references and update dist URL for ones that contain a reference
     */
    public function setSourceDistReferences(string $reference): void;
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Version;

use Composer\Package\PackageInterface;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Dumper\ArrayDumper;
use Composer\Pcre\Preg;
use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Intervals;
use Composer\Util\Platform;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @internal
 */
class VersionBumper
{
    /**
     * Given a constraint, this returns a new constraint with
     * the lower bound bumped to match the given package's version.
     *
     * For example:
     *  * ^1.0 + 1.2.1            -> ^1.2.1
     *  * ^1.2 + 1.2.0            -> ^1.2
     *  * ^1.2.0 + 1.3.0          -> ^1.3.0
     *  * ^1.2 || ^2.3 + 1.3.0    -> ^1.3 || ^2.3
     *  * ^1.2 || ^2.3 + 2.4.0    -> ^1.2 || ^2.4
     *  * ^3@dev + 3.2.99999-dev  -> ^3.2@dev
     *  * ~2 + 2.0-beta.1         -> ~2
     *  * ~2.0.0 + 2.0.3          -> ~2.0.3
     *  * ~2.0 + 2.0.3            -> ^2.0.3
     *  * dev-master + dev-master -> dev-master
     *  * * + 1.2.3               -> >=1.2.3
     */
    public function bumpRequirement(ConstraintInterface $constraint, PackageInterface $package): string
    {
        $parser = new VersionParser();
        $prettyConstraint = $constraint->getPrettyString();
        if (str_starts_with($constraint->getPrettyString(), 'dev-')) {
            return $prettyConstraint;
        }

        $version = $package->getVersion();
        if (str_starts_with($package->getVersion(), 'dev-')) {
            $loader = new ArrayLoader($parser);
            $dumper = new ArrayDumper();
            $extra = $loader->getBranchAlias($dumper->dump($package));

            // dev packages without branch alias cannot be processed
            if (null === $extra || $extra === VersionParser::DEFAULT_BRANCH_ALIAS) {
                return $prettyConstraint;
            }

            $version = $extra;
        }

        $intervals = Intervals::get($constraint);

        // complex constraints with branch names are not bumped
        if (\count($intervals['branches']['names']) > 0) {
            return $prettyConstraint;
        }

        $major = Preg::replace('{^([1-9]+|0\.\d+).*}', '$1', $version);
        $versionWithoutSuffix = Preg::replace('{(?:\.(?:0|9999999))+(-dev)?$}', '', $version);
        $newPrettyConstraint = '^'.$versionWithoutSuffix;

        // not a simple stable version, abort
        if (!Preg::isMatch('{^\^\d+(\.\d+)*$}', $newPrettyConstraint)) {
            return $prettyConstraint;
        }

        $pattern = '{
            (?<=,|\ |\||^) # leading separator
            (?P<constraint>
                \^v?'.$major.'(?:\.\d+)* # e.g. ^2.anything
                | ~v?'.$major.'(?:\.\d+){1,3} # e.g. ~2.2 or ~2.2.2 or ~2.2.2.2
                | v?'.$major.'(?:\.[*x])+ # e.g. 2.* or 2.*.* or 2.x.x.x etc
                | >=v?\d(?:\.\d+)* # e.g. >=2 or >=1.2 etc
                | \* # full wildcard
            )
            (?=,|$|\ |\||@) # trailing separator
        }x';
        if (Preg::isMatchAllWithOffsets($pattern, $prettyConstraint, $matches)) {
            $modified = $prettyConstraint;
            foreach (array_reverse($matches['constraint']) as $match) {
                assert(is_string($match[0]));
                $suffix = '';
                if (substr_count($match[0], '.') === 2 && substr_count($versionWithoutSuffix, '.') === 1) {
                    $suffix = '.0';
                }
                if (str_starts_with($match[0], '~') && substr_count($match[0], '.') !== 1) {
                    // take as many version bits from the current version as we have in the constraint to bump it without making it more specific
                    $versionBits = explode('.', $versionWithoutSuffix);
                    $versionBits = array_pad($versionBits, substr_count($match[0], '.') + 1, '0');
                    $replacement = '~'.implode('.', array_slice($versionBits, 0, substr_count($match[0], '.') + 1));
                } elseif ($match[0] === '*' || str_starts_with($match[0], '>=')) {
                    $replacement = '>='.$versionWithoutSuffix.$suffix;
                } else {
                    $replacement = $newPrettyConstraint.$suffix;
                }
                $modified = substr_replace($modified, $replacement, $match[1], Platform::strlen($match[0]));
            }

            // if it is strictly equal to the previous one then no need to change anything
            $newConstraint = $parser->parseConstraints($modified);
            if (Intervals::isSubsetOf($newConstraint, $constraint) && Intervals::isSubsetOf($constraint, $newConstraint)) {
                return $prettyConstraint;
            }

            return $modified;
        }

        return $prettyConstraint;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Version;

use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Pcre\Preg;
use Composer\Repository\Vcs\HgDriver;
use Composer\IO\NullIO;
use Composer\Semver\VersionParser as SemverVersionParser;
use Composer\Util\Git as GitUtil;
use Composer\Util\HttpDownloader;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Util\Svn as SvnUtil;
use React\Promise\CancellablePromiseInterface;
use Symfony\Component\Process\Process;

/**
 * Try to guess the current version number based on different VCS configuration.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Samuel Roze <samuel.roze@gmail.com>
 *
 * @phpstan-type Version array{version: string, commit: string|null, pretty_version: string|null}|array{version: string, commit: string|null, pretty_version: string|null, feature_version: string|null, feature_pretty_version: string|null}
 */
class VersionGuesser
{
    /**
     * @var Config
     */
    private $config;

    /**
     * @var ProcessExecutor
     */
    private $process;

    /**
     * @var SemverVersionParser
     */
    private $versionParser;

    /**
     * @var IOInterface|null
     */
    private $io;

    public function __construct(Config $config, ProcessExecutor $process, SemverVersionParser $versionParser, ?IOInterface $io = null)
    {
        $this->config = $config;
        $this->process = $process;
        $this->versionParser = $versionParser;
        $this->io = $io;
    }

    /**
     * @param array<string, mixed> $packageConfig
     * @param string               $path Path to guess into
     *
     * @phpstan-return Version|null
     */
    public function guessVersion(array $packageConfig, string $path): ?array
    {
        if (!function_exists('proc_open')) {
            return null;
        }

        // bypass version guessing in bash completions as it takes time to create
        // new processes and the root version is usually not that important
        if (Platform::isInputCompletionProcess()) {
            return null;
        }

        $versionData = $this->guessGitVersion($packageConfig, $path);
        if (null !== $versionData['version']) {
            return $this->postprocess($versionData);
        }

        $versionData = $this->guessHgVersion($packageConfig, $path);
        if (null !== $versionData && null !== $versionData['version']) {
            return $this->postprocess($versionData);
        }

        $versionData = $this->guessFossilVersion($path);
        if (null !== $versionData['version']) {
            return $this->postprocess($versionData);
        }

        $versionData = $this->guessSvnVersion($packageConfig, $path);
        if (null !== $versionData && null !== $versionData['version']) {
            return $this->postprocess($versionData);
        }

        return null;
    }

    /**
     * @phpstan-param Version $versionData
     *
     * @phpstan-return Version
     */
    private function postprocess(array $versionData): array
    {
        if (!empty($versionData['feature_version']) && $versionData['feature_version'] === $versionData['version'] && $versionData['feature_pretty_version'] === $versionData['pretty_version']) {
            unset($versionData['feature_version'], $versionData['feature_pretty_version']);
        }

        if ('-dev' === substr($versionData['version'], -4) && Preg::isMatch('{\.9{7}}', $versionData['version'])) {
            $versionData['pretty_version'] = Preg::replace('{(\.9{7})+}', '.x', $versionData['version']);
        }

        if (!empty($versionData['feature_version']) && '-dev' === substr($versionData['feature_version'], -4) && Preg::isMatch('{\.9{7}}', $versionData['feature_version'])) {
            $versionData['feature_pretty_version'] = Preg::replace('{(\.9{7})+}', '.x', $versionData['feature_version']);
        }

        return $versionData;
    }

    /**
     * @param array<string, mixed> $packageConfig
     *
     * @return array{version: string|null, commit: string|null, pretty_version: string|null, feature_version?: string|null, feature_pretty_version?: string|null}
     */
    private function guessGitVersion(array $packageConfig, string $path): array
    {
        GitUtil::cleanEnv();
        $commit = null;
        $version = null;
        $prettyVersion = null;
        $featureVersion = null;
        $featurePrettyVersion = null;
        $isDetached = false;

        // try to fetch current version from git branch
        if (0 === $this->process->execute(['git', 'branch', '-a', '--no-color', '--no-abbrev', '-v'], $output, $path)) {
            $branches = [];
            $isFeatureBranch = false;

            // find current branch and collect all branch names
            foreach ($this->process->splitLines($output) as $branch) {
                if ($branch && Preg::isMatchStrictGroups('{^(?:\* ) *(\(no branch\)|\(detached from \S+\)|\(HEAD detached at \S+\)|\S+) *([a-f0-9]+) .*$}', $branch, $match)) {
                    if (
                        $match[1] === '(no branch)'
                        || strpos($match[1], '(detached ') === 0
                        || strpos($match[1], '(HEAD detached at') === 0
                    ) {
                        $version = 'dev-' . $match[2];
                        $prettyVersion = $version;
                        $isFeatureBranch = true;
                        $isDetached = true;
                    } else {
                        $version = $this->versionParser->normalizeBranch($match[1]);
                        $prettyVersion = 'dev-' . $match[1];
                        $isFeatureBranch = $this->isFeatureBranch($packageConfig, $match[1]);
                    }

                    $commit = $match[2];
                }

                if ($branch && !Preg::isMatchStrictGroups('{^ *.+/HEAD }', $branch)) {
                    if (Preg::isMatchStrictGroups('{^(?:\* )? *((?:remotes/(?:origin|upstream)/)?[^\s/]+) *([a-f0-9]+) .*$}', $branch, $match)) {
                        $branches[] = $match[1];
                    }
                }
            }

            if ($isFeatureBranch) {
                $featureVersion = $version;
                $featurePrettyVersion = $prettyVersion;

                // try to find the best (nearest) version branch to assume this feature's version
                $result = $this->guessFeatureVersion($packageConfig, $version, $branches, ['git', 'rev-list', '%candidate%..%branch%'], $path);
                $version = $result['version'];
                $prettyVersion = $result['pretty_version'];
            }
        }
        GitUtil::checkForRepoOwnershipError($this->process->getErrorOutput(), $path, $this->io);

        if (!$version || $isDetached) {
            $result = $this->versionFromGitTags($path);
            if ($result) {
                $version = $result['version'];
                $prettyVersion = $result['pretty_version'];
                $featureVersion = null;
                $featurePrettyVersion = null;
            }
        }

        if (null === $commit) {
            $command = array_merge(['git', 'log', '--pretty=%H', '-n1', 'HEAD'], GitUtil::getNoShowSignatureFlags($this->process));
            if (0 === $this->process->execute($command, $output, $path)) {
                $commit = trim($output) ?: null;
            }
        }

        if ($featureVersion) {
            return ['version' => $version, 'commit' => $commit, 'pretty_version' => $prettyVersion, 'feature_version' => $featureVersion, 'feature_pretty_version' => $featurePrettyVersion];
        }

        return ['version' => $version, 'commit' => $commit, 'pretty_version' => $prettyVersion];
    }

    /**
     * @return array{version: string, pretty_version: string}|null
     */
    private function versionFromGitTags(string $path): ?array
    {
        // try to fetch current version from git tags
        if (0 === $this->process->execute(['git', 'describe', '--exact-match', '--tags'], $output, $path)) {
            try {
                $version = $this->versionParser->normalize(trim($output));

                return ['version' => $version, 'pretty_version' => trim($output)];
            } catch (\Exception $e) {
            }
        }

        return null;
    }

    /**
     * @param array<string, mixed> $packageConfig
     *
     * @return array{version: string|null, commit: ''|null, pretty_version: string|null, feature_version?: string|null, feature_pretty_version?: string|null}|null
     */
    private function guessHgVersion(array $packageConfig, string $path): ?array
    {
        // try to fetch current version from hg branch
        if (0 === $this->process->execute(['hg', 'branch'], $output, $path)) {
            $branch = trim($output);
            $version = $this->versionParser->normalizeBranch($branch);
            $isFeatureBranch = 0 === strpos($version, 'dev-');

            if (VersionParser::DEFAULT_BRANCH_ALIAS === $version) {
                return ['version' => $version, 'commit' => null, 'pretty_version' => 'dev-'.$branch];
            }

            if (!$isFeatureBranch) {
                return ['version' => $version, 'commit' => null, 'pretty_version' => $version];
            }

            // re-use the HgDriver to fetch branches (this properly includes bookmarks)
            $io = new NullIO();
            $driver = new HgDriver(['url' => $path], $io, $this->config, new HttpDownloader($io, $this->config), $this->process);
            $branches = array_map('strval', array_keys($driver->getBranches()));

            // try to find the best (nearest) version branch to assume this feature's version
            $result = $this->guessFeatureVersion($packageConfig, $version, $branches, ['hg', 'log', '-r', 'not ancestors(\'%candidate%\') and ancestors(\'%branch%\')', '--template', '"{node}\\n"'], $path);
            $result['commit'] = '';
            $result['feature_version'] = $version;
            $result['feature_pretty_version'] = $version;

            return $result;
        }

        return null;
    }

    /**
     * @param array<string, mixed>     $packageConfig
     * @param list<string>             $branches
     * @param list<string>             $scmCmdline
     *
     * @return array{version: string|null, pretty_version: string|null}
     */
    private function guessFeatureVersion(array $packageConfig, ?string $version, array $branches, array $scmCmdline, string $path): array
    {
        $prettyVersion = $version;

        // ignore feature branches if they have no branch-alias or self.version is used
        // and find the branch they came from to use as a version instead
        if (!isset($packageConfig['extra']['branch-alias'][$version])
            || strpos(json_encode($packageConfig), '"self.version"')
        ) {
            $branch = Preg::replace('{^dev-}', '', $version);
            $length = PHP_INT_MAX;

            // return directly, if branch is configured to be non-feature branch
            if (!$this->isFeatureBranch($packageConfig, $branch)) {
                return ['version' => $version, 'pretty_version' => $prettyVersion];
            }

            // sort local branches first then remote ones
            // and sort numeric branches below named ones, to make sure if the branch has the same distance from main and 1.10 and 1.9 for example, 1.9 is picked
            // and sort using natural sort so that 1.10 will appear before 1.9
            usort($branches, static function ($a, $b): int {
                $aRemote = 0 === strpos($a, 'remotes/');
                $bRemote = 0 === strpos($b, 'remotes/');

                if ($aRemote !== $bRemote) {
                    return $aRemote ? 1 : -1;
                }

                return strnatcasecmp($b, $a);
            });

            $promises = [];
            $this->process->setMaxJobs(30);
            try {
                $lastIndex = -1;
                foreach ($branches as $index => $candidate) {
                    $candidateVersion = Preg::replace('{^remotes/\S+/}', '', $candidate);

                    // do not compare against itself or other feature branches
                    if ($candidate === $branch || $this->isFeatureBranch($packageConfig, $candidateVersion)) {
                        continue;
                    }

                    $cmdLine = array_map(static function (string $component) use ($candidate, $branch) {
                        return str_replace(['%candidate%', '%branch%'], [$candidate, $branch], $component);
                    }, $scmCmdline);
                    $promises[] = $this->process->executeAsync($cmdLine, $path)->then(function (Process $process) use (&$lastIndex, $index, &$length, &$version, &$prettyVersion, $candidateVersion, &$promises): void {
                        if (!$process->isSuccessful()) {
                            return;
                        }

                        $output = $process->getOutput();
                        // overwrite existing if we have a shorter diff, or we have an equal diff and an index that comes later in the array (i.e. older version)
                        // as newer versions typically have more commits, if the feature branch is based on a newer branch it should have a longer diff to the old version
                        // but if it doesn't and they have equal diffs, then it probably is based on the old version
                        if (strlen($output) < $length || (strlen($output) === $length && $lastIndex < $index)) {
                            $lastIndex = $index;
                            $length = strlen($output);
                            $version = $this->versionParser->normalizeBranch($candidateVersion);
                            $prettyVersion = 'dev-' . $candidateVersion;
                            if ($length === 0) {
                                foreach ($promises as $promise) {
                                    // to support react/promise 2.x we wrap the promise in a resolve() call for safety
                                    \React\Promise\resolve($promise)->cancel();
                                }
                            }
                        }
                    });
                }

                $this->process->wait();
            } finally {
                $this->process->resetMaxJobs();
            }
        }

        return ['version' => $version, 'pretty_version' => $prettyVersion];
    }

    /**
     * @param array<string, mixed> $packageConfig
     */
    private function isFeatureBranch(array $packageConfig, ?string $branchName): bool
    {
        $nonFeatureBranches = '';
        if (!empty($packageConfig['non-feature-branches'])) {
            $nonFeatureBranches = implode('|', $packageConfig['non-feature-branches']);
        }

        return !Preg::isMatch('{^(' . $nonFeatureBranches . '|master|main|latest|next|current|support|tip|trunk|default|develop|\d+\..+)$}', $branchName, $match);
    }

    /**
     * @return array{version: string|null, commit: '', pretty_version: string|null}
     */
    private function guessFossilVersion(string $path): array
    {
        $version = null;
        $prettyVersion = null;

        // try to fetch current version from fossil
        if (0 === $this->process->execute(['fossil', 'branch', 'list'], $output, $path)) {
            $branch = trim($output);
            $version = $this->versionParser->normalizeBranch($branch);
            $prettyVersion = 'dev-' . $branch;
        }

        // try to fetch current version from fossil tags
        if (0 === $this->process->execute(['fossil', 'tag', 'list'], $output, $path)) {
            try {
                $version = $this->versionParser->normalize(trim($output));
                $prettyVersion = trim($output);
            } catch (\Exception $e) {
            }
        }

        return ['version' => $version, 'commit' => '', 'pretty_version' => $prettyVersion];
    }

    /**
     * @param array<string, mixed> $packageConfig
     *
     * @return array{version: string, commit: '', pretty_version: string}|null
     */
    private function guessSvnVersion(array $packageConfig, string $path): ?array
    {
        SvnUtil::cleanEnv();

        // try to fetch current version from svn
        if (0 === $this->process->execute(['svn', 'info', '--xml'], $output, $path)) {
            $trunkPath = isset($packageConfig['trunk-path']) ? preg_quote($packageConfig['trunk-path'], '#') : 'trunk';
            $branchesPath = isset($packageConfig['branches-path']) ? preg_quote($packageConfig['branches-path'], '#') : 'branches';
            $tagsPath = isset($packageConfig['tags-path']) ? preg_quote($packageConfig['tags-path'], '#') : 'tags';

            $urlPattern = '#<url>.*/(' . $trunkPath . '|(' . $branchesPath . '|' . $tagsPath . ')/(.*))</url>#';

            if (Preg::isMatch($urlPattern, $output, $matches)) {
                if (isset($matches[2], $matches[3]) && ($branchesPath === $matches[2] || $tagsPath === $matches[2])) {
                    // we are in a branches path
                    $version = $this->versionParser->normalizeBranch($matches[3]);
                    $prettyVersion = 'dev-' . $matches[3];

                    return ['version' => $version, 'commit' => '', 'pretty_version' => $prettyVersion];
                }

                assert(is_string($matches[1]));
                $prettyVersion = trim($matches[1]);
                if ($prettyVersion === 'trunk') {
                    $version = 'dev-trunk';
                } else {
                    $version = $this->versionParser->normalize($prettyVersion);
                }

                return ['version' => $version, 'commit' => '', 'pretty_version' => $prettyVersion];
            }
        }

        return null;
    }

    public function getRootVersionFromEnv(): string
    {
        $version = Platform::getEnv('COMPOSER_ROOT_VERSION');
        if (!is_string($version) || $version === '') {
            throw new \RuntimeException('COMPOSER_ROOT_VERSION not set or empty');
        }
        if (Preg::isMatch('{^(\d+(?:\.\d+)*)-dev$}i', $version, $match)) {
            $version = $match[1].'.x-dev';
        }

        return $version;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Version;

use Composer\Pcre\Preg;
use Composer\Repository\PlatformRepository;
use Composer\Semver\VersionParser as SemverVersionParser;
use Composer\Semver\Semver;
use Composer\Semver\Constraint\ConstraintInterface;

class VersionParser extends SemverVersionParser
{
    public const DEFAULT_BRANCH_ALIAS = '9999999-dev';

    /** @var array<string, ConstraintInterface> Constraint parsing cache */
    private static $constraints = [];

    /**
     * @inheritDoc
     */
    public function parseConstraints($constraints): ConstraintInterface
    {
        if (!isset(self::$constraints[$constraints])) {
            self::$constraints[$constraints] = parent::parseConstraints($constraints);
        }

        return self::$constraints[$constraints];
    }

    /**
     * Parses an array of strings representing package/version pairs.
     *
     * The parsing results in an array of arrays, each of which
     * contain a 'name' key with value and optionally a 'version' key with value.
     *
     * @param string[] $pairs a set of package/version pairs separated by ":", "=" or " "
     *
     * @return list<array{name: string, version?: string}>
     */
    public function parseNameVersionPairs(array $pairs): array
    {
        $pairs = array_values($pairs);
        $result = [];

        for ($i = 0, $count = count($pairs); $i < $count; $i++) {
            $pair = Preg::replace('{^([^=: ]+)[=: ](.*)$}', '$1 $2', trim($pairs[$i]));
            if (false === strpos($pair, ' ') && isset($pairs[$i + 1]) && false === strpos($pairs[$i + 1], '/') && !Preg::isMatch('{(?<=[a-z0-9_/-])\*|\*(?=[a-z0-9_/-])}i', $pairs[$i + 1]) && !PlatformRepository::isPlatformPackage($pairs[$i + 1])) {
                $pair .= ' '.$pairs[$i + 1];
                $i++;
            }

            if (strpos($pair, ' ')) {
                [$name, $version] = explode(' ', $pair, 2);
                $result[] = ['name' => $name, 'version' => $version];
            } else {
                $result[] = ['name' => $pair];
            }
        }

        return $result;
    }

    public static function isUpgrade(string $normalizedFrom, string $normalizedTo): bool
    {
        if ($normalizedFrom === $normalizedTo) {
            return true;
        }

        if (in_array($normalizedFrom, ['dev-master', 'dev-trunk', 'dev-default'], true)) {
            $normalizedFrom = VersionParser::DEFAULT_BRANCH_ALIAS;
        }
        if (in_array($normalizedTo, ['dev-master', 'dev-trunk', 'dev-default'], true)) {
            $normalizedTo = VersionParser::DEFAULT_BRANCH_ALIAS;
        }

        if (strpos($normalizedFrom, 'dev-') === 0 || strpos($normalizedTo, 'dev-') === 0) {
            return true;
        }

        $sorted = Semver::sort([$normalizedTo, $normalizedFrom]);

        return $sorted[0] === $normalizedFrom;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Version;

use Composer\Package\BasePackage;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class StabilityFilter
{
    /**
     * Checks if any of the provided package names in the given stability match the configured acceptable stability and flags
     *
     * @param int[] $acceptableStabilities array of stability => BasePackage::STABILITY_* value
     * @phpstan-param array<key-of<BasePackage::STABILITIES>, BasePackage::STABILITY_*> $acceptableStabilities
     * @param int[] $stabilityFlags an array of package name => BasePackage::STABILITY_* value
     * @phpstan-param array<string, BasePackage::STABILITY_*> $stabilityFlags
     * @param  string[] $names     The package name(s) to check for stability flags
     * @param  key-of<BasePackage::STABILITIES> $stability one of 'stable', 'RC', 'beta', 'alpha' or 'dev'
     * @return bool     true if any package name is acceptable
     */
    public static function isPackageAcceptable(array $acceptableStabilities, array $stabilityFlags, array $names, string $stability): bool
    {
        foreach ($names as $name) {
            // allow if package matches the package-specific stability flag
            if (isset($stabilityFlags[$name])) {
                if (BasePackage::STABILITIES[$stability] <= $stabilityFlags[$name]) {
                    return true;
                }
            } elseif (isset($acceptableStabilities[$stability])) {
                // allow if package matches the global stability requirement and has no exception
                return true;
            }
        }

        return false;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Version;

use Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter;
use Composer\Filter\PlatformRequirementFilter\IgnoreListPlatformRequirementFilter;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
use Composer\IO\IOInterface;
use Composer\Package\BasePackage;
use Composer\Package\AliasPackage;
use Composer\Package\PackageInterface;
use Composer\Composer;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Dumper\ArrayDumper;
use Composer\Pcre\Preg;
use Composer\Repository\RepositorySet;
use Composer\Repository\PlatformRepository;
use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\ConstraintInterface;

/**
 * Selects the best possible version for a package
 *
 * @author Ryan Weaver <ryan@knpuniversity.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class VersionSelector
{
    /** @var RepositorySet */
    private $repositorySet;

    /** @var array<string, ConstraintInterface[]> */
    private $platformConstraints = [];

    /** @var VersionParser */
    private $parser;

    /**
     * @param PlatformRepository $platformRepo If passed in, the versions found will be filtered against their requirements to eliminate any not matching the current platform packages
     */
    public function __construct(RepositorySet $repositorySet, ?PlatformRepository $platformRepo = null)
    {
        $this->repositorySet = $repositorySet;
        if ($platformRepo) {
            foreach ($platformRepo->getPackages() as $package) {
                $this->platformConstraints[$package->getName()][] = new Constraint('==', $package->getVersion());
            }
        }
    }

    /**
     * Given a package name and optional version, returns the latest PackageInterface
     * that matches.
     *
     * @param string                                           $targetPackageVersion
     * @param PlatformRequirementFilterInterface|bool|string[] $platformRequirementFilter
     * @param IOInterface|null                                 $io                        If passed, warnings will be output there in case versions cannot be selected due to platform requirements
     * @param callable(PackageInterface):bool|bool             $showWarnings
     * @return PackageInterface|false
     */
    public function findBestCandidate(string $packageName, ?string $targetPackageVersion = null, string $preferredStability = 'stable', $platformRequirementFilter = null, int $repoSetFlags = 0, ?IOInterface $io = null, $showWarnings = true)
    {
        if (!isset(BasePackage::STABILITIES[$preferredStability])) {
            // If you get this, maybe you are still relying on the Composer 1.x signature where the 3rd arg was the php version
            throw new \UnexpectedValueException('Expected a valid stability name as 3rd argument, got '.$preferredStability);
        }

        if (null === $platformRequirementFilter) {
            $platformRequirementFilter = PlatformRequirementFilterFactory::ignoreNothing();
        } elseif (!($platformRequirementFilter instanceof PlatformRequirementFilterInterface)) {
            trigger_error('VersionSelector::findBestCandidate with ignored platform reqs as bool|array is deprecated since Composer 2.2, use an instance of PlatformRequirementFilterInterface instead.', E_USER_DEPRECATED);
            $platformRequirementFilter = PlatformRequirementFilterFactory::fromBoolOrList($platformRequirementFilter);
        }

        $constraint = $targetPackageVersion ? $this->getParser()->parseConstraints($targetPackageVersion) : null;
        $candidates = $this->repositorySet->findPackages(strtolower($packageName), $constraint, $repoSetFlags);

        $minPriority = BasePackage::STABILITIES[$preferredStability];
        usort($candidates, static function (PackageInterface $a, PackageInterface $b) use ($minPriority) {
            $aPriority = $a->getStabilityPriority();
            $bPriority = $b->getStabilityPriority();

            // A is less stable than our preferred stability,
            // and B is more stable than A, select B
            if ($minPriority < $aPriority && $bPriority < $aPriority) {
                return 1;
            }

            // A is less stable than our preferred stability,
            // and B is less stable than A, select A
            if ($minPriority < $aPriority && $aPriority < $bPriority) {
                return -1;
            }

            // A is more stable than our preferred stability,
            // and B is less stable than preferred stability, select A
            if ($minPriority >= $aPriority && $minPriority < $bPriority) {
                return -1;
            }

            // select highest version of the two
            return version_compare($b->getVersion(), $a->getVersion());
        });

        if (count($this->platformConstraints) > 0 && !($platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter)) {
            /** @var array<string, true> $alreadyWarnedNames */
            $alreadyWarnedNames = [];
            /** @var array<string, true> $alreadySeenNames */
            $alreadySeenNames = [];

            foreach ($candidates as $pkg) {
                $reqs = $pkg->getRequires();
                $skip = false;
                foreach ($reqs as $name => $link) {
                    if (!PlatformRepository::isPlatformPackage($name) || $platformRequirementFilter->isIgnored($name)) {
                        continue;
                    }
                    if (isset($this->platformConstraints[$name])) {
                        foreach ($this->platformConstraints[$name] as $providedConstraint) {
                            if ($link->getConstraint()->matches($providedConstraint)) {
                                // constraint satisfied, go to next require
                                continue 2;
                            }
                            if ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter && $platformRequirementFilter->isUpperBoundIgnored($name)) {
                                $filteredConstraint = $platformRequirementFilter->filterConstraint($name, $link->getConstraint());
                                if ($filteredConstraint->matches($providedConstraint)) {
                                    // constraint satisfied with the upper bound ignored, go to next require
                                    continue 2;
                                }
                            }
                        }

                        // constraint not satisfied
                        $reason = 'is not satisfied by your platform';
                    } else {
                        // Package requires a platform package that is unknown on current platform.
                        // It means that current platform cannot validate this constraint and so package is not installable.
                        $reason = 'is missing from your platform';
                    }

                    $isLatestVersion = !isset($alreadySeenNames[$pkg->getName()]);
                    $alreadySeenNames[$pkg->getName()] = true;
                    if ($io !== null && ($showWarnings === true || (is_callable($showWarnings) && $showWarnings($pkg)))) {
                        $isFirstWarning = !isset($alreadyWarnedNames[$pkg->getName().'/'.$link->getTarget()]);
                        $alreadyWarnedNames[$pkg->getName().'/'.$link->getTarget()] = true;
                        $latest = $isLatestVersion ? "'s latest version" : '';
                        $io->writeError(
                            '<warning>Cannot use '.$pkg->getPrettyName().$latest.' '.$pkg->getPrettyVersion().' as it '.$link->getDescription().' '.$link->getTarget().' '.$link->getPrettyConstraint().' which '.$reason.'.</>',
                            true,
                            $isFirstWarning ? IOInterface::NORMAL : IOInterface::VERBOSE
                        );
                    }

                    // skip candidate
                    $skip = true;
                }

                if ($skip) {
                    continue;
                }

                $package = $pkg;
                break;
            }
        } else {
            $package = count($candidates) > 0 ? $candidates[0] : null;
        }

        if (!isset($package)) {
            return false;
        }

        // if we end up with 9999999-dev as selected package, make sure we use the original version instead of the alias
        if ($package instanceof AliasPackage && $package->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) {
            $package = $package->getAliasOf();
        }

        return $package;
    }

    /**
     * Given a concrete version, this returns a ^ constraint (when possible)
     * that should be used, for example, in composer.json.
     *
     * For example:
     *  * 1.2.1         -> ^1.2
     *  * 1.2.1.2       -> ^1.2
     *  * 1.2           -> ^1.2
     *  * v3.2.1        -> ^3.2
     *  * 2.0-beta.1    -> ^2.0@beta
     *  * dev-master    -> ^2.1@dev      (dev version with alias)
     *  * dev-master    -> dev-master    (dev versions are untouched)
     */
    public function findRecommendedRequireVersion(PackageInterface $package): string
    {
        // Extensions which are versioned in sync with PHP should rather be required as "*" to simplify
        // the requires and have only one required version to change when bumping the php requirement
        if (0 === strpos($package->getName(), 'ext-')) {
            $phpVersion = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION;
            $extVersion = implode('.', array_slice(explode('.', $package->getVersion()), 0, 3));
            if ($phpVersion === $extVersion) {
                return '*';
            }
        }

        $version = $package->getVersion();
        if (!$package->isDev()) {
            return $this->transformVersion($version, $package->getPrettyVersion(), $package->getStability());
        }

        $loader = new ArrayLoader($this->getParser());
        $dumper = new ArrayDumper();
        $extra = $loader->getBranchAlias($dumper->dump($package));
        if ($extra && $extra !== VersionParser::DEFAULT_BRANCH_ALIAS) {
            $extra = Preg::replace('{^(\d+\.\d+\.\d+)(\.9999999)-dev$}', '$1.0', $extra, -1, $count);
            if ($count > 0) {
                $extra = str_replace('.9999999', '.0', $extra);

                return $this->transformVersion($extra, $extra, 'dev');
            }
        }

        return $package->getPrettyVersion();
    }

    private function transformVersion(string $version, string $prettyVersion, string $stability): string
    {
        // attempt to transform 2.1.1 to 2.1
        // this allows you to upgrade through minor versions
        $semanticVersionParts = explode('.', $version);

        // check to see if we have a semver-looking version
        if (count($semanticVersionParts) === 4 && Preg::isMatch('{^\d+\D?}', $semanticVersionParts[3])) {
            // remove the last parts (i.e. the patch version number and any extra)
            if ($semanticVersionParts[0] === '0') {
                unset($semanticVersionParts[3]);
            } else {
                unset($semanticVersionParts[2], $semanticVersionParts[3]);
            }
            $version = implode('.', $semanticVersionParts);
        } else {
            return $prettyVersion;
        }

        // append stability flag if not default
        if ($stability !== 'stable') {
            $version .= '@'.$stability;
        }

        // 2.1 -> ^2.1
        return '^' . $version;
    }

    private function getParser(): VersionParser
    {
        if ($this->parser === null) {
            $this->parser = new VersionParser();
        }

        return $this->parser;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package;

use Composer\Json\JsonFile;
use Composer\Installer\InstallationManager;
use Composer\Pcre\Preg;
use Composer\Repository\InstalledRepository;
use Composer\Repository\LockArrayRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RootPackageRepository;
use Composer\Util\ProcessExecutor;
use Composer\Package\Dumper\ArrayDumper;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Version\VersionParser;
use Composer\Plugin\PluginInterface;
use Composer\Util\Git as GitUtil;
use Composer\IO\IOInterface;
use Seld\JsonLint\ParsingException;

/**
 * Reads/writes project lockfile (composer.lock).
 *
 * @author Konstantin Kudryashiv <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Locker
{
    /** @var JsonFile */
    private $lockFile;
    /** @var InstallationManager */
    private $installationManager;
    /** @var string */
    private $hash;
    /** @var string */
    private $contentHash;
    /** @var ArrayLoader */
    private $loader;
    /** @var ArrayDumper */
    private $dumper;
    /** @var ProcessExecutor */
    private $process;
    /** @var mixed[]|null */
    private $lockDataCache = null;
    /** @var bool */
    private $virtualFileWritten = false;

    /**
     * Initializes packages locker.
     *
     * @param JsonFile            $lockFile             lockfile loader
     * @param InstallationManager $installationManager  installation manager instance
     * @param string              $composerFileContents The contents of the composer file
     */
    public function __construct(IOInterface $io, JsonFile $lockFile, InstallationManager $installationManager, string $composerFileContents, ?ProcessExecutor $process = null)
    {
        $this->lockFile = $lockFile;
        $this->installationManager = $installationManager;
        $this->hash = hash('md5', $composerFileContents);
        $this->contentHash = self::getContentHash($composerFileContents);
        $this->loader = new ArrayLoader(null, true);
        $this->dumper = new ArrayDumper();
        $this->process = $process ?? new ProcessExecutor($io);
    }

    /**
     * @internal
     */
    public function getJsonFile(): JsonFile
    {
        return $this->lockFile;
    }

    /**
     * Returns the md5 hash of the sorted content of the composer file.
     *
     * @param string $composerFileContents The contents of the composer file.
     */
    public static function getContentHash(string $composerFileContents): string
    {
        $content = JsonFile::parseJson($composerFileContents, 'composer.json');

        $relevantKeys = [
            'name',
            'version',
            'require',
            'require-dev',
            'conflict',
            'replace',
            'provide',
            'minimum-stability',
            'prefer-stable',
            'repositories',
            'extra',
        ];

        $relevantContent = [];

        foreach (array_intersect($relevantKeys, array_keys($content)) as $key) {
            $relevantContent[$key] = $content[$key];
        }
        if (isset($content['config']['platform'])) {
            $relevantContent['config']['platform'] = $content['config']['platform'];
        }

        ksort($relevantContent);

        return hash('md5', JsonFile::encode($relevantContent, 0));
    }

    /**
     * Checks whether locker has been locked (lockfile found).
     */
    public function isLocked(): bool
    {
        if (!$this->virtualFileWritten && !$this->lockFile->exists()) {
            return false;
        }

        $data = $this->getLockData();

        return isset($data['packages']);
    }

    /**
     * Checks whether the lock file is still up to date with the current hash
     */
    public function isFresh(): bool
    {
        $lock = $this->lockFile->read();

        if (!empty($lock['content-hash'])) {
            // There is a content hash key, use that instead of the file hash
            return $this->contentHash === $lock['content-hash'];
        }

        // BC support for old lock files without content-hash
        if (!empty($lock['hash'])) {
            return $this->hash === $lock['hash'];
        }

        // should not be reached unless the lock file is corrupted, so assume it's out of date
        return false;
    }

    /**
     * Searches and returns an array of locked packages, retrieved from registered repositories.
     *
     * @param  bool                                     $withDevReqs true to retrieve the locked dev packages
     * @throws \RuntimeException
     */
    public function getLockedRepository(bool $withDevReqs = false): LockArrayRepository
    {
        $lockData = $this->getLockData();
        $packages = new LockArrayRepository();

        $lockedPackages = $lockData['packages'];
        if ($withDevReqs) {
            if (isset($lockData['packages-dev'])) {
                $lockedPackages = array_merge($lockedPackages, $lockData['packages-dev']);
            } else {
                throw new \RuntimeException('The lock file does not contain require-dev information, run install with the --no-dev option or delete it and run composer update to generate a new lock file.');
            }
        }

        if (empty($lockedPackages)) {
            return $packages;
        }

        if (isset($lockedPackages[0]['name'])) {
            $packageByName = [];
            foreach ($lockedPackages as $info) {
                $package = $this->loader->load($info);
                $packages->addPackage($package);
                $packageByName[$package->getName()] = $package;

                if ($package instanceof AliasPackage) {
                    $packageByName[$package->getAliasOf()->getName()] = $package->getAliasOf();
                }
            }

            if (isset($lockData['aliases'])) {
                foreach ($lockData['aliases'] as $alias) {
                    if (isset($packageByName[$alias['package']])) {
                        $aliasPkg = new CompleteAliasPackage($packageByName[$alias['package']], $alias['alias_normalized'], $alias['alias']);
                        $aliasPkg->setRootPackageAlias(true);
                        $packages->addPackage($aliasPkg);
                    }
                }
            }

            return $packages;
        }

        throw new \RuntimeException('Your composer.lock is invalid. Run "composer update" to generate a new one.');
    }

    /**
     * @return string[] Names of dependencies installed through require-dev
     */
    public function getDevPackageNames(): array
    {
        $names = [];
        $lockData = $this->getLockData();
        if (isset($lockData['packages-dev'])) {
            foreach ($lockData['packages-dev'] as $package) {
                $names[] = strtolower($package['name']);
            }
        }

        return $names;
    }

    /**
     * Returns the platform requirements stored in the lock file
     *
     * @param  bool                     $withDevReqs if true, the platform requirements from the require-dev block are also returned
     * @return \Composer\Package\Link[]
     */
    public function getPlatformRequirements(bool $withDevReqs = false): array
    {
        $lockData = $this->getLockData();
        $requirements = [];

        if (!empty($lockData['platform'])) {
            $requirements = $this->loader->parseLinks(
                '__root__',
                '1.0.0',
                Link::TYPE_REQUIRE,
                $lockData['platform'] ?? []
            );
        }

        if ($withDevReqs && !empty($lockData['platform-dev'])) {
            $devRequirements = $this->loader->parseLinks(
                '__root__',
                '1.0.0',
                Link::TYPE_REQUIRE,
                $lockData['platform-dev'] ?? []
            );

            $requirements = array_merge($requirements, $devRequirements);
        }

        return $requirements;
    }

    /**
     * @return key-of<BasePackage::STABILITIES>
     */
    public function getMinimumStability(): string
    {
        $lockData = $this->getLockData();

        return $lockData['minimum-stability'] ?? 'stable';
    }

    /**
     * @return array<string, string>
     */
    public function getStabilityFlags(): array
    {
        $lockData = $this->getLockData();

        return $lockData['stability-flags'] ?? [];
    }

    public function getPreferStable(): ?bool
    {
        $lockData = $this->getLockData();

        // return null if not set to allow caller logic to choose the
        // right behavior since old lock files have no prefer-stable
        return $lockData['prefer-stable'] ?? null;
    }

    public function getPreferLowest(): ?bool
    {
        $lockData = $this->getLockData();

        // return null if not set to allow caller logic to choose the
        // right behavior since old lock files have no prefer-lowest
        return $lockData['prefer-lowest'] ?? null;
    }

    /**
     * @return array<string, string>
     */
    public function getPlatformOverrides(): array
    {
        $lockData = $this->getLockData();

        return $lockData['platform-overrides'] ?? [];
    }

    /**
     * @return string[][]
     *
     * @phpstan-return list<array{package: string, version: string, alias: string, alias_normalized: string}>
     */
    public function getAliases(): array
    {
        $lockData = $this->getLockData();

        return $lockData['aliases'] ?? [];
    }

    /**
     * @return string
     */
    public function getPluginApi()
    {
        $lockData = $this->getLockData();

        return $lockData['plugin-api-version'] ?? '1.1.0';
    }

    /**
     * @return array<string, mixed>
     */
    public function getLockData(): array
    {
        if (null !== $this->lockDataCache) {
            return $this->lockDataCache;
        }

        if (!$this->lockFile->exists()) {
            throw new \LogicException('No lockfile found. Unable to read locked packages');
        }

        return $this->lockDataCache = $this->lockFile->read();
    }

    /**
     * Locks provided data into lockfile.
     *
     * @param PackageInterface[]          $packages          array of packages
     * @param PackageInterface[]|null     $devPackages       array of dev packages or null if installed without --dev
     * @param array<string, string>       $platformReqs      array of package name => constraint for required platform packages
     * @param array<string, string>       $platformDevReqs   array of package name => constraint for dev-required platform packages
     * @param string[][]                  $aliases           array of aliases
     * @param array<string, int>          $stabilityFlags
     * @param array<string, string|false> $platformOverrides
     * @param bool                        $write             Whether to actually write data to disk, useful in tests and for --dry-run
     *
     * @phpstan-param list<array{package: string, version: string, alias: string, alias_normalized: string}> $aliases
     */
    public function setLockData(array $packages, ?array $devPackages, array $platformReqs, array $platformDevReqs, array $aliases, string $minimumStability, array $stabilityFlags, bool $preferStable, bool $preferLowest, array $platformOverrides, bool $write = true): bool
    {
        // keep old default branch names normalized to DEFAULT_BRANCH_ALIAS for BC as that is how Composer 1 outputs the lock file
        // when loading the lock file the version is anyway ignored in Composer 2, so it has no adverse effect
        $aliases = array_map(static function ($alias): array {
            if (in_array($alias['version'], ['dev-master', 'dev-trunk', 'dev-default'], true)) {
                $alias['version'] = VersionParser::DEFAULT_BRANCH_ALIAS;
            }

            return $alias;
        }, $aliases);

        $lock = [
            '_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 @gener'.'ated automatically', ],
            'content-hash' => $this->contentHash,
            'packages' => $this->lockPackages($packages),
            'packages-dev' => null,
            'aliases' => $aliases,
            'minimum-stability' => $minimumStability,
            'stability-flags' => $stabilityFlags,
            'prefer-stable' => $preferStable,
            'prefer-lowest' => $preferLowest,
        ];

        if (null !== $devPackages) {
            $lock['packages-dev'] = $this->lockPackages($devPackages);
        }

        $lock['platform'] = $platformReqs;
        $lock['platform-dev'] = $platformDevReqs;
        if (\count($platformOverrides) > 0) {
            $lock['platform-overrides'] = $platformOverrides;
        }
        $lock['plugin-api-version'] = PluginInterface::PLUGIN_API_VERSION;

        $lock = $this->fixupJsonDataType($lock);

        try {
            $isLocked = $this->isLocked();
        } catch (ParsingException $e) {
            $isLocked = false;
        }
        if (!$isLocked || $lock !== $this->getLockData()) {
            if ($write) {
                $this->lockFile->write($lock);
                $this->lockDataCache = null;
                $this->virtualFileWritten = false;
            } else {
                $this->virtualFileWritten = true;
                $this->lockDataCache = JsonFile::parseJson(JsonFile::encode($lock));
            }

            return true;
        }

        return false;
    }

    /**
     * Updates the lock file's hash in-place from a given composer.json's JsonFile
     *
     * This does not reload or require any packages, and retains the filemtime of the lock file.
     *
     * Use this only to update the lock file hash after updating a composer.json in ways that are guaranteed NOT to impact the dependency resolution.
     *
     * This is a risky method, use carefully.
     *
     * @param (callable(array<string, mixed>): array<string, mixed>)|null $dataProcessor Receives the lock data and can process it before it gets written to disk
     */
    public function updateHash(JsonFile $composerJson, ?callable $dataProcessor = null): void
    {
        $contents = file_get_contents($composerJson->getPath());
        if (false === $contents) {
            throw new \RuntimeException('Unable to read '.$composerJson->getPath().' contents to update the lock file hash.');
        }

        $lockMtime = filemtime($this->lockFile->getPath());
        $lockData = $this->lockFile->read();
        $lockData['content-hash'] = Locker::getContentHash($contents);
        if ($dataProcessor !== null) {
            $lockData = $dataProcessor($lockData);
        }

        $this->lockFile->write($this->fixupJsonDataType($lockData));
        $this->lockDataCache = null;
        $this->virtualFileWritten = false;
        if (is_int($lockMtime)) {
            @touch($this->lockFile->getPath(), $lockMtime);
        }
    }

    /**
     * Ensures correct data types and ordering for the JSON lock format
     *
     * @param array<mixed> $lockData
     * @return array<mixed>
     */
    private function fixupJsonDataType(array $lockData): array
    {
        foreach (['stability-flags', 'platform', 'platform-dev'] as $key) {
            if (isset($lockData[$key]) && is_array($lockData[$key]) && \count($lockData[$key]) === 0) {
                $lockData[$key] = new \stdClass();
            }
        }

        if (is_array($lockData['stability-flags'])) {
            ksort($lockData['stability-flags']);
        }

        return $lockData;
    }

    /**
     * @param PackageInterface[] $packages
     *
     * @return mixed[][]
     *
     * @phpstan-return list<array<string, mixed>>
     */
    private function lockPackages(array $packages): array
    {
        $locked = [];

        foreach ($packages as $package) {
            if ($package instanceof AliasPackage) {
                continue;
            }

            $name = $package->getPrettyName();
            $version = $package->getPrettyVersion();

            if (!$name || !$version) {
                throw new \LogicException(sprintf(
                    'Package "%s" has no version or name and can not be locked',
                    $package
                ));
            }

            $spec = $this->dumper->dump($package);
            unset($spec['version_normalized']);

            // always move time to the end of the package definition
            $time = $spec['time'] ?? null;
            unset($spec['time']);
            if ($package->isDev() && $package->getInstallationSource() === 'source') {
                // use the exact commit time of the current reference if it's a dev package
                $time = $this->getPackageTime($package) ?: $time;
            }
            if (null !== $time) {
                $spec['time'] = $time;
            }

            unset($spec['installation-source']);

            $locked[] = $spec;
        }

        usort($locked, static function ($a, $b) {
            $comparison = strcmp($a['name'], $b['name']);

            if (0 !== $comparison) {
                return $comparison;
            }

            // If it is the same package, compare the versions to make the order deterministic
            return strcmp($a['version'], $b['version']);
        });

        return $locked;
    }

    /**
     * Returns the packages's datetime for its source reference.
     *
     * @param  PackageInterface $package The package to scan.
     * @return string|null      The formatted datetime or null if none was found.
     */
    private function getPackageTime(PackageInterface $package): ?string
    {
        if (!function_exists('proc_open')) {
            return null;
        }

        $path = $this->installationManager->getInstallPath($package);
        if ($path === null) {
            return null;
        }
        $path = realpath($path);
        $sourceType = $package->getSourceType();
        $datetime = null;

        if ($path && in_array($sourceType, ['git', 'hg'])) {
            $sourceRef = $package->getSourceReference() ?: $package->getDistReference();
            switch ($sourceType) {
                case 'git':
                    GitUtil::cleanEnv();

                    $command = array_merge(['git', 'log', '-n1', '--pretty=%ct', (string) $sourceRef], GitUtil::getNoShowSignatureFlags($this->process));
                    if (0 === $this->process->execute($command, $output, $path) && Preg::isMatch('{^\s*\d+\s*$}', $output)) {
                        $datetime = new \DateTime('@'.trim($output), new \DateTimeZone('UTC'));
                    }
                    break;

                case 'hg':
                    if (0 === $this->process->execute(['hg', 'log', '--template', '{date|hgdate}', '-r', (string) $sourceRef], $output, $path) && Preg::isMatch('{^\s*(\d+)\s*}', $output, $match)) {
                        $datetime = new \DateTime('@'.$match[1], new \DateTimeZone('UTC'));
                    }
                    break;
            }
        }

        return $datetime ? $datetime->format(DATE_RFC3339) : null;
    }

    /**
     * @return array<string>
     */
    public function getMissingRequirementInfo(RootPackageInterface $package, bool $includeDev): array
    {
        $missingRequirementInfo = [];
        $missingRequirements = false;
        $sets = [['repo' => $this->getLockedRepository(false), 'method' => 'getRequires', 'description' => 'Required']];
        if ($includeDev === true) {
            $sets[] = ['repo' => $this->getLockedRepository(true), 'method' => 'getDevRequires', 'description' => 'Required (in require-dev)'];
        }
        $rootRepo = new RootPackageRepository(clone $package);

        foreach ($sets as $set) {
            $installedRepo = new InstalledRepository([$set['repo'], $rootRepo]);

            foreach (call_user_func([$package, $set['method']]) as $link) {
                if (PlatformRepository::isPlatformPackage($link->getTarget())) {
                    continue;
                }
                if ($link->getPrettyConstraint() === 'self.version') {
                    continue;
                }
                if ($installedRepo->findPackagesWithReplacersAndProviders($link->getTarget(), $link->getConstraint()) === []) {
                    $results = $installedRepo->findPackagesWithReplacersAndProviders($link->getTarget());

                    if ($results !== []) {
                        $provider = reset($results);
                        $description = $provider->getPrettyVersion();
                        if ($provider->getName() !== $link->getTarget()) {
                            foreach (['getReplaces' => 'replaced as %s by %s', 'getProvides' => 'provided as %s by %s'] as $method => $text) {
                                foreach (call_user_func([$provider, $method]) as $providerLink) {
                                    if ($providerLink->getTarget() === $link->getTarget()) {
                                        $description = sprintf($text, $providerLink->getPrettyConstraint(), $provider->getPrettyName().' '.$provider->getPrettyVersion());
                                        break 2;
                                    }
                                }
                            }
                        }
                        $missingRequirementInfo[] = '- ' . $set['description'].' package "' . $link->getTarget() . '" is in the lock file as "'.$description.'" but that does not satisfy your constraint "'.$link->getPrettyConstraint().'".';
                    } else {
                        $missingRequirementInfo[] = '- ' . $set['description'].' package "' . $link->getTarget() . '" is not present in the lock file.';
                    }
                    $missingRequirements = true;
                }
            }
        }

        if ($missingRequirements) {
            $missingRequirementInfo[] = 'This usually happens when composer files are incorrectly merged or the composer.json file is manually edited.';
            $missingRequirementInfo[] = 'Read more about correctly resolving merge conflicts https://getcomposer.org/doc/articles/resolving-merge-conflicts.md';
            $missingRequirementInfo[] = 'and prefer using the "require" command over editing the composer.json file directly https://getcomposer.org/doc/03-cli.md#require-r';
        }

        return $missingRequirementInfo;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package;

use Composer\Semver\Constraint\Constraint;
use Composer\Package\Version\VersionParser;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class AliasPackage extends BasePackage
{
    /** @var string */
    protected $version;
    /** @var string */
    protected $prettyVersion;
    /** @var bool */
    protected $dev;
    /** @var bool */
    protected $rootPackageAlias = false;
    /**
     * @var string
     * @phpstan-var 'stable'|'RC'|'beta'|'alpha'|'dev'
     */
    protected $stability;
    /** @var bool */
    protected $hasSelfVersionRequires = false;

    /** @var BasePackage */
    protected $aliasOf;
    /** @var Link[] */
    protected $requires;
    /** @var Link[] */
    protected $devRequires;
    /** @var Link[] */
    protected $conflicts;
    /** @var Link[] */
    protected $provides;
    /** @var Link[] */
    protected $replaces;

    /**
     * All descendants' constructors should call this parent constructor
     *
     * @param BasePackage $aliasOf       The package this package is an alias of
     * @param string      $version       The version the alias must report
     * @param string      $prettyVersion The alias's non-normalized version
     */
    public function __construct(BasePackage $aliasOf, string $version, string $prettyVersion)
    {
        parent::__construct($aliasOf->getName());

        $this->version = $version;
        $this->prettyVersion = $prettyVersion;
        $this->aliasOf = $aliasOf;
        $this->stability = VersionParser::parseStability($version);
        $this->dev = $this->stability === 'dev';

        foreach (Link::$TYPES as $type) {
            $links = $aliasOf->{'get' . ucfirst($type)}();
            $this->{$type} = $this->replaceSelfVersionDependencies($links, $type);
        }
    }

    /**
     * @return BasePackage
     */
    public function getAliasOf()
    {
        return $this->aliasOf;
    }

    /**
     * @inheritDoc
     */
    public function getVersion(): string
    {
        return $this->version;
    }

    /**
     * @inheritDoc
     */
    public function getStability(): string
    {
        return $this->stability;
    }

    /**
     * @inheritDoc
     */
    public function getPrettyVersion(): string
    {
        return $this->prettyVersion;
    }

    /**
     * @inheritDoc
     */
    public function isDev(): bool
    {
        return $this->dev;
    }

    /**
     * @inheritDoc
     */
    public function getRequires(): array
    {
        return $this->requires;
    }

    /**
     * @inheritDoc
     * @return array<string|int, Link>
     */
    public function getConflicts(): array
    {
        return $this->conflicts;
    }

    /**
     * @inheritDoc
     * @return array<string|int, Link>
     */
    public function getProvides(): array
    {
        return $this->provides;
    }

    /**
     * @inheritDoc
     * @return array<string|int, Link>
     */
    public function getReplaces(): array
    {
        return $this->replaces;
    }

    /**
     * @inheritDoc
     */
    public function getDevRequires(): array
    {
        return $this->devRequires;
    }

    /**
     * Stores whether this is an alias created by an aliasing in the requirements of the root package or not
     *
     * Use by the policy for sorting manually aliased packages first, see #576
     */
    public function setRootPackageAlias(bool $value): void
    {
        $this->rootPackageAlias = $value;
    }

    /**
     * @see setRootPackageAlias
     */
    public function isRootPackageAlias(): bool
    {
        return $this->rootPackageAlias;
    }

    /**
     * @param Link[]       $links
     * @param Link::TYPE_* $linkType
     *
     * @return Link[]
     */
    protected function replaceSelfVersionDependencies(array $links, $linkType): array
    {
        // for self.version requirements, we use the original package's branch name instead, to avoid leaking the magic dev-master-alias to users
        $prettyVersion = $this->prettyVersion;
        if ($prettyVersion === VersionParser::DEFAULT_BRANCH_ALIAS) {
            $prettyVersion = $this->aliasOf->getPrettyVersion();
        }

        if (\in_array($linkType, [Link::TYPE_CONFLICT, Link::TYPE_PROVIDE, Link::TYPE_REPLACE], true)) {
            $newLinks = [];
            foreach ($links as $link) {
                // link is self.version, but must be replacing also the replaced version
                if ('self.version' === $link->getPrettyConstraint()) {
                    $newLinks[] = new Link($link->getSource(), $link->getTarget(), $constraint = new Constraint('=', $this->version), $linkType, $prettyVersion);
                    $constraint->setPrettyString($prettyVersion);
                }
            }
            $links = array_merge($links, $newLinks);
        } else {
            foreach ($links as $index => $link) {
                if ('self.version' === $link->getPrettyConstraint()) {
                    if ($linkType === Link::TYPE_REQUIRE) {
                        $this->hasSelfVersionRequires = true;
                    }
                    $links[$index] = new Link($link->getSource(), $link->getTarget(), $constraint = new Constraint('=', $this->version), $linkType, $prettyVersion);
                    $constraint->setPrettyString($prettyVersion);
                }
            }
        }

        return $links;
    }

    public function hasSelfVersionRequires(): bool
    {
        return $this->hasSelfVersionRequires;
    }

    public function __toString(): string
    {
        return parent::__toString().' ('.($this->rootPackageAlias ? 'root ' : ''). 'alias of '.$this->aliasOf->getVersion().')';
    }

    /***************************************
     * Wrappers around the aliased package *
     ***************************************/

    public function getType(): string
    {
        return $this->aliasOf->getType();
    }

    public function getTargetDir(): ?string
    {
        return $this->aliasOf->getTargetDir();
    }

    public function getExtra(): array
    {
        return $this->aliasOf->getExtra();
    }

    public function setInstallationSource(?string $type): void
    {
        $this->aliasOf->setInstallationSource($type);
    }

    public function getInstallationSource(): ?string
    {
        return $this->aliasOf->getInstallationSource();
    }

    public function getSourceType(): ?string
    {
        return $this->aliasOf->getSourceType();
    }

    public function getSourceUrl(): ?string
    {
        return $this->aliasOf->getSourceUrl();
    }

    public function getSourceUrls(): array
    {
        return $this->aliasOf->getSourceUrls();
    }

    public function getSourceReference(): ?string
    {
        return $this->aliasOf->getSourceReference();
    }

    public function setSourceReference(?string $reference): void
    {
        $this->aliasOf->setSourceReference($reference);
    }

    public function setSourceMirrors(?array $mirrors): void
    {
        $this->aliasOf->setSourceMirrors($mirrors);
    }

    public function getSourceMirrors(): ?array
    {
        return $this->aliasOf->getSourceMirrors();
    }

    public function getDistType(): ?string
    {
        return $this->aliasOf->getDistType();
    }

    public function getDistUrl(): ?string
    {
        return $this->aliasOf->getDistUrl();
    }

    public function getDistUrls(): array
    {
        return $this->aliasOf->getDistUrls();
    }

    public function getDistReference(): ?string
    {
        return $this->aliasOf->getDistReference();
    }

    public function setDistReference(?string $reference): void
    {
        $this->aliasOf->setDistReference($reference);
    }

    public function getDistSha1Checksum(): ?string
    {
        return $this->aliasOf->getDistSha1Checksum();
    }

    public function setTransportOptions(array $options): void
    {
        $this->aliasOf->setTransportOptions($options);
    }

    public function getTransportOptions(): array
    {
        return $this->aliasOf->getTransportOptions();
    }

    public function setDistMirrors(?array $mirrors): void
    {
        $this->aliasOf->setDistMirrors($mirrors);
    }

    public function getDistMirrors(): ?array
    {
        return $this->aliasOf->getDistMirrors();
    }

    public function getAutoload(): array
    {
        return $this->aliasOf->getAutoload();
    }

    public function getDevAutoload(): array
    {
        return $this->aliasOf->getDevAutoload();
    }

    public function getIncludePaths(): array
    {
        return $this->aliasOf->getIncludePaths();
    }

    public function getPhpExt(): ?array
    {
        return $this->aliasOf->getPhpExt();
    }

    public function getReleaseDate(): ?\DateTimeInterface
    {
        return $this->aliasOf->getReleaseDate();
    }

    public function getBinaries(): array
    {
        return $this->aliasOf->getBinaries();
    }

    public function getSuggests(): array
    {
        return $this->aliasOf->getSuggests();
    }

    public function getNotificationUrl(): ?string
    {
        return $this->aliasOf->getNotificationUrl();
    }

    public function isDefaultBranch(): bool
    {
        return $this->aliasOf->isDefaultBranch();
    }

    public function setDistUrl(?string $url): void
    {
        $this->aliasOf->setDistUrl($url);
    }

    public function setDistType(?string $type): void
    {
        $this->aliasOf->setDistType($type);
    }

    public function setSourceDistReferences(string $reference): void
    {
        $this->aliasOf->setSourceDistReferences($reference);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Dumper;

use Composer\Package\BasePackage;
use Composer\Package\PackageInterface;
use Composer\Package\CompletePackageInterface;
use Composer\Package\RootPackageInterface;

/**
 * @author Konstantin Kudryashiv <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ArrayDumper
{
    /**
     * @return array<string, mixed>
     */
    public function dump(PackageInterface $package): array
    {
        $keys = [
            'binaries' => 'bin',
            'type',
            'extra',
            'installationSource' => 'installation-source',
            'autoload',
            'devAutoload' => 'autoload-dev',
            'notificationUrl' => 'notification-url',
            'includePaths' => 'include-path',
            'phpExt' => 'php-ext',
        ];

        $data = [];
        $data['name'] = $package->getPrettyName();
        $data['version'] = $package->getPrettyVersion();
        $data['version_normalized'] = $package->getVersion();

        if ($package->getTargetDir() !== null) {
            $data['target-dir'] = $package->getTargetDir();
        }

        if ($package->getSourceType() !== null) {
            $data['source']['type'] = $package->getSourceType();
            $data['source']['url'] = $package->getSourceUrl();
            if (null !== ($value = $package->getSourceReference())) {
                $data['source']['reference'] = $value;
            }
            if ($mirrors = $package->getSourceMirrors()) {
                $data['source']['mirrors'] = $mirrors;
            }
        }

        if ($package->getDistType() !== null) {
            $data['dist']['type'] = $package->getDistType();
            $data['dist']['url'] = $package->getDistUrl();
            if (null !== ($value = $package->getDistReference())) {
                $data['dist']['reference'] = $value;
            }
            if (null !== ($value = $package->getDistSha1Checksum())) {
                $data['dist']['shasum'] = $value;
            }
            if ($mirrors = $package->getDistMirrors()) {
                $data['dist']['mirrors'] = $mirrors;
            }
        }

        foreach (BasePackage::$supportedLinkTypes as $type => $opts) {
            $links = $package->{'get'.ucfirst($opts['method'])}();
            if (\count($links) === 0) {
                continue;
            }
            foreach ($links as $link) {
                $data[$type][$link->getTarget()] = $link->getPrettyConstraint();
            }
            ksort($data[$type]);
        }

        $packages = $package->getSuggests();
        if (\count($packages) > 0) {
            ksort($packages);
            $data['suggest'] = $packages;
        }

        if ($package->getReleaseDate() instanceof \DateTimeInterface) {
            $data['time'] = $package->getReleaseDate()->format(DATE_RFC3339);
        }

        if ($package->isDefaultBranch()) {
            $data['default-branch'] = true;
        }

        $data = $this->dumpValues($package, $keys, $data);

        if ($package instanceof CompletePackageInterface) {
            if ($package->getArchiveName()) {
                $data['archive']['name'] = $package->getArchiveName();
            }
            if ($package->getArchiveExcludes()) {
                $data['archive']['exclude'] = $package->getArchiveExcludes();
            }

            $keys = [
                'scripts',
                'license',
                'authors',
                'description',
                'homepage',
                'keywords',
                'repositories',
                'support',
                'funding',
            ];

            $data = $this->dumpValues($package, $keys, $data);

            if (isset($data['keywords']) && \is_array($data['keywords'])) {
                sort($data['keywords']);
            }

            if ($package->isAbandoned()) {
                $data['abandoned'] = $package->getReplacementPackage() ?: true;
            }
        }

        if ($package instanceof RootPackageInterface) {
            $minimumStability = $package->getMinimumStability();
            if ($minimumStability !== '') {
                $data['minimum-stability'] = $minimumStability;
            }
        }

        if (\count($package->getTransportOptions()) > 0) {
            $data['transport-options'] = $package->getTransportOptions();
        }

        return $data;
    }

    /**
     * @param array<int|string, string> $keys
     * @param array<string, mixed>      $data
     *
     * @return array<string, mixed>
     */
    private function dumpValues(PackageInterface $package, array $keys, array $data): array
    {
        foreach ($keys as $method => $key) {
            if (is_numeric($method)) {
                $method = $key;
            }

            $getter = 'get'.ucfirst($method);
            $value = $package->{$getter}();

            if (null !== $value && !(\is_array($value) && 0 === \count($value))) {
                $data[$key] = $value;
            }
        }

        return $data;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class RootAliasPackage extends CompleteAliasPackage implements RootPackageInterface
{
    /** @var RootPackage */
    protected $aliasOf;

    /**
     * All descendants' constructors should call this parent constructor
     *
     * @param RootPackage $aliasOf       The package this package is an alias of
     * @param string      $version       The version the alias must report
     * @param string      $prettyVersion The alias's non-normalized version
     */
    public function __construct(RootPackage $aliasOf, string $version, string $prettyVersion)
    {
        parent::__construct($aliasOf, $version, $prettyVersion);
    }

    /**
     * @return RootPackage
     */
    public function getAliasOf()
    {
        return $this->aliasOf;
    }

    /**
     * @inheritDoc
     */
    public function getAliases(): array
    {
        return $this->aliasOf->getAliases();
    }

    /**
     * @inheritDoc
     */
    public function getMinimumStability(): string
    {
        return $this->aliasOf->getMinimumStability();
    }

    /**
     * @inheritDoc
     */
    public function getStabilityFlags(): array
    {
        return $this->aliasOf->getStabilityFlags();
    }

    /**
     * @inheritDoc
     */
    public function getReferences(): array
    {
        return $this->aliasOf->getReferences();
    }

    /**
     * @inheritDoc
     */
    public function getPreferStable(): bool
    {
        return $this->aliasOf->getPreferStable();
    }

    /**
     * @inheritDoc
     */
    public function getConfig(): array
    {
        return $this->aliasOf->getConfig();
    }

    /**
     * @inheritDoc
     */
    public function setRequires(array $requires): void
    {
        $this->requires = $this->replaceSelfVersionDependencies($requires, Link::TYPE_REQUIRE);

        $this->aliasOf->setRequires($requires);
    }

    /**
     * @inheritDoc
     */
    public function setDevRequires(array $devRequires): void
    {
        $this->devRequires = $this->replaceSelfVersionDependencies($devRequires, Link::TYPE_DEV_REQUIRE);

        $this->aliasOf->setDevRequires($devRequires);
    }

    /**
     * @inheritDoc
     */
    public function setConflicts(array $conflicts): void
    {
        $this->conflicts = $this->replaceSelfVersionDependencies($conflicts, Link::TYPE_CONFLICT);
        $this->aliasOf->setConflicts($conflicts);
    }

    /**
     * @inheritDoc
     */
    public function setProvides(array $provides): void
    {
        $this->provides = $this->replaceSelfVersionDependencies($provides, Link::TYPE_PROVIDE);
        $this->aliasOf->setProvides($provides);
    }

    /**
     * @inheritDoc
     */
    public function setReplaces(array $replaces): void
    {
        $this->replaces = $this->replaceSelfVersionDependencies($replaces, Link::TYPE_REPLACE);
        $this->aliasOf->setReplaces($replaces);
    }

    /**
     * @inheritDoc
     */
    public function setAutoload(array $autoload): void
    {
        $this->aliasOf->setAutoload($autoload);
    }

    /**
     * @inheritDoc
     */
    public function setDevAutoload(array $devAutoload): void
    {
        $this->aliasOf->setDevAutoload($devAutoload);
    }

    /**
     * @inheritDoc
     */
    public function setStabilityFlags(array $stabilityFlags): void
    {
        $this->aliasOf->setStabilityFlags($stabilityFlags);
    }

    /**
     * @inheritDoc
     */
    public function setMinimumStability(string $minimumStability): void
    {
        $this->aliasOf->setMinimumStability($minimumStability);
    }

    /**
     * @inheritDoc
     */
    public function setPreferStable(bool $preferStable): void
    {
        $this->aliasOf->setPreferStable($preferStable);
    }

    /**
     * @inheritDoc
     */
    public function setConfig(array $config): void
    {
        $this->aliasOf->setConfig($config);
    }

    /**
     * @inheritDoc
     */
    public function setReferences(array $references): void
    {
        $this->aliasOf->setReferences($references);
    }

    /**
     * @inheritDoc
     */
    public function setAliases(array $aliases): void
    {
        $this->aliasOf->setAliases($aliases);
    }

    /**
     * @inheritDoc
     */
    public function setSuggests(array $suggests): void
    {
        $this->aliasOf->setSuggests($suggests);
    }

    /**
     * @inheritDoc
     */
    public function setExtra(array $extra): void
    {
        $this->aliasOf->setExtra($extra);
    }

    public function __clone()
    {
        parent::__clone();
        $this->aliasOf = clone $this->aliasOf;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class CompleteAliasPackage extends AliasPackage implements CompletePackageInterface
{
    /** @var CompletePackage */
    protected $aliasOf;

    /**
     * All descendants' constructors should call this parent constructor
     *
     * @param CompletePackage $aliasOf       The package this package is an alias of
     * @param string          $version       The version the alias must report
     * @param string          $prettyVersion The alias's non-normalized version
     */
    public function __construct(CompletePackage $aliasOf, string $version, string $prettyVersion)
    {
        parent::__construct($aliasOf, $version, $prettyVersion);
    }

    /**
     * @return CompletePackage
     */
    public function getAliasOf()
    {
        return $this->aliasOf;
    }

    public function getScripts(): array
    {
        return $this->aliasOf->getScripts();
    }

    public function setScripts(array $scripts): void
    {
        $this->aliasOf->setScripts($scripts);
    }

    public function getRepositories(): array
    {
        return $this->aliasOf->getRepositories();
    }

    public function setRepositories(array $repositories): void
    {
        $this->aliasOf->setRepositories($repositories);
    }

    public function getLicense(): array
    {
        return $this->aliasOf->getLicense();
    }

    public function setLicense(array $license): void
    {
        $this->aliasOf->setLicense($license);
    }

    public function getKeywords(): array
    {
        return $this->aliasOf->getKeywords();
    }

    public function setKeywords(array $keywords): void
    {
        $this->aliasOf->setKeywords($keywords);
    }

    public function getDescription(): ?string
    {
        return $this->aliasOf->getDescription();
    }

    public function setDescription(?string $description): void
    {
        $this->aliasOf->setDescription($description);
    }

    public function getHomepage(): ?string
    {
        return $this->aliasOf->getHomepage();
    }

    public function setHomepage(?string $homepage): void
    {
        $this->aliasOf->setHomepage($homepage);
    }

    public function getAuthors(): array
    {
        return $this->aliasOf->getAuthors();
    }

    public function setAuthors(array $authors): void
    {
        $this->aliasOf->setAuthors($authors);
    }

    public function getSupport(): array
    {
        return $this->aliasOf->getSupport();
    }

    public function setSupport(array $support): void
    {
        $this->aliasOf->setSupport($support);
    }

    public function getFunding(): array
    {
        return $this->aliasOf->getFunding();
    }

    public function setFunding(array $funding): void
    {
        $this->aliasOf->setFunding($funding);
    }

    public function isAbandoned(): bool
    {
        return $this->aliasOf->isAbandoned();
    }

    public function getReplacementPackage(): ?string
    {
        return $this->aliasOf->getReplacementPackage();
    }

    public function setAbandoned($abandoned): void
    {
        $this->aliasOf->setAbandoned($abandoned);
    }

    public function getArchiveName(): ?string
    {
        return $this->aliasOf->getArchiveName();
    }

    public function setArchiveName(?string $name): void
    {
        $this->aliasOf->setArchiveName($name);
    }

    public function getArchiveExcludes(): array
    {
        return $this->aliasOf->getArchiveExcludes();
    }

    public function setArchiveExcludes(array $excludes): void
    {
        $this->aliasOf->setArchiveExcludes($excludes);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package;

use Composer\Semver\Constraint\ConstraintInterface;

/**
 * Represents a link between two packages, represented by their names
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class Link
{
    public const TYPE_REQUIRE = 'requires';
    public const TYPE_DEV_REQUIRE = 'devRequires';
    public const TYPE_PROVIDE = 'provides';
    public const TYPE_CONFLICT = 'conflicts';
    public const TYPE_REPLACE = 'replaces';

    /**
     * Special type
     * @internal
     */
    public const TYPE_DOES_NOT_REQUIRE = 'does not require';

    private const TYPE_UNKNOWN = 'relates to';

    /**
     * Will be converted into a constant once the min PHP version allows this
     *
     * @internal
     * @var string[]
     * @phpstan-var array<self::TYPE_REQUIRE|self::TYPE_DEV_REQUIRE|self::TYPE_PROVIDE|self::TYPE_CONFLICT|self::TYPE_REPLACE>
     */
    public static $TYPES = [
        self::TYPE_REQUIRE,
        self::TYPE_DEV_REQUIRE,
        self::TYPE_PROVIDE,
        self::TYPE_CONFLICT,
        self::TYPE_REPLACE,
    ];

    /**
     * @var string
     */
    protected $source;

    /**
     * @var string
     */
    protected $target;

    /**
     * @var ConstraintInterface
     */
    protected $constraint;

    /**
     * @var string
     * @phpstan-var string $description
     */
    protected $description;

    /**
     * @var ?string
     */
    protected $prettyConstraint;

    /**
     * Creates a new package link.
     *
     * @param ConstraintInterface $constraint       Constraint applying to the target of this link
     * @param self::TYPE_*        $description      Used to create a descriptive string representation
     */
    public function __construct(
        string $source,
        string $target,
        ConstraintInterface $constraint,
        $description = self::TYPE_UNKNOWN,
        ?string $prettyConstraint = null
    ) {
        $this->source = strtolower($source);
        $this->target = strtolower($target);
        $this->constraint = $constraint;
        $this->description = self::TYPE_DEV_REQUIRE === $description ? 'requires (for development)' : $description;
        $this->prettyConstraint = $prettyConstraint;
    }

    public function getDescription(): string
    {
        return $this->description;
    }

    public function getSource(): string
    {
        return $this->source;
    }

    public function getTarget(): string
    {
        return $this->target;
    }

    public function getConstraint(): ConstraintInterface
    {
        return $this->constraint;
    }

    /**
     * @throws \UnexpectedValueException If no pretty constraint was provided
     */
    public function getPrettyConstraint(): string
    {
        if (null === $this->prettyConstraint) {
            throw new \UnexpectedValueException(sprintf('Link %s has been misconfigured and had no prettyConstraint given.', $this));
        }

        return $this->prettyConstraint;
    }

    public function __toString(): string
    {
        return $this->source.' '.$this->description.' '.$this->target.' ('.$this->constraint.')';
    }

    public function getPrettyString(PackageInterface $sourcePackage): string
    {
        return $sourcePackage->getPrettyString().' '.$this->description.' '.$this->target.' '.$this->constraint->getPrettyString();
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package;

/**
 * The root package represents the project's composer.json and contains additional metadata
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class RootPackage extends CompletePackage implements RootPackageInterface
{
    public const DEFAULT_PRETTY_VERSION = '1.0.0+no-version-set';

    /** @var key-of<BasePackage::STABILITIES> */
    protected $minimumStability = 'stable';
    /** @var bool */
    protected $preferStable = false;
    /** @var array<string, BasePackage::STABILITY_*> Map of package name to stability constant */
    protected $stabilityFlags = [];
    /** @var mixed[] */
    protected $config = [];
    /** @var array<string, string> Map of package name to reference/commit hash */
    protected $references = [];
    /** @var list<array{package: string, version: string, alias: string, alias_normalized: string}> */
    protected $aliases = [];

    /**
     * @inheritDoc
     */
    public function setMinimumStability(string $minimumStability): void
    {
        $this->minimumStability = $minimumStability;
    }

    /**
     * @inheritDoc
     */
    public function getMinimumStability(): string
    {
        return $this->minimumStability;
    }

    /**
     * @inheritDoc
     */
    public function setStabilityFlags(array $stabilityFlags): void
    {
        $this->stabilityFlags = $stabilityFlags;
    }

    /**
     * @inheritDoc
     */
    public function getStabilityFlags(): array
    {
        return $this->stabilityFlags;
    }

    /**
     * @inheritDoc
     */
    public function setPreferStable(bool $preferStable): void
    {
        $this->preferStable = $preferStable;
    }

    /**
     * @inheritDoc
     */
    public function getPreferStable(): bool
    {
        return $this->preferStable;
    }

    /**
     * @inheritDoc
     */
    public function setConfig(array $config): void
    {
        $this->config = $config;
    }

    /**
     * @inheritDoc
     */
    public function getConfig(): array
    {
        return $this->config;
    }

    /**
     * @inheritDoc
     */
    public function setReferences(array $references): void
    {
        $this->references = $references;
    }

    /**
     * @inheritDoc
     */
    public function getReferences(): array
    {
        return $this->references;
    }

    /**
     * @inheritDoc
     */
    public function setAliases(array $aliases): void
    {
        $this->aliases = $aliases;
    }

    /**
     * @inheritDoc
     */
    public function getAliases(): array
    {
        return $this->aliases;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package;

/**
 * Package containing additional metadata that is not used by the solver
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class CompletePackage extends Package implements CompletePackageInterface
{
    /** @var mixed[] */
    protected $repositories = [];
    /** @var string[] */
    protected $license = [];
    /** @var string[] */
    protected $keywords = [];
    /** @var array<array{name?: string, homepage?: string, email?: string, role?: string}> */
    protected $authors = [];
    /** @var ?string */
    protected $description = null;
    /** @var ?string */
    protected $homepage = null;
    /** @var array<string, string[]> Map of script name to array of handlers */
    protected $scripts = [];
    /** @var array{issues?: string, forum?: string, wiki?: string, source?: string, email?: string, irc?: string, docs?: string, rss?: string, chat?: string, security?: string} */
    protected $support = [];
    /** @var array<array{url?: string, type?: string}> */
    protected $funding = [];
    /** @var bool|string */
    protected $abandoned = false;
    /** @var ?string */
    protected $archiveName = null;
    /** @var string[] */
    protected $archiveExcludes = [];

    /**
     * @inheritDoc
     */
    public function setScripts(array $scripts): void
    {
        $this->scripts = $scripts;
    }

    /**
     * @inheritDoc
     */
    public function getScripts(): array
    {
        return $this->scripts;
    }

    /**
     * @inheritDoc
     */
    public function setRepositories(array $repositories): void
    {
        $this->repositories = $repositories;
    }

    /**
     * @inheritDoc
     */
    public function getRepositories(): array
    {
        return $this->repositories;
    }

    /**
     * @inheritDoc
     */
    public function setLicense(array $license): void
    {
        $this->license = $license;
    }

    /**
     * @inheritDoc
     */
    public function getLicense(): array
    {
        return $this->license;
    }

    /**
     * @inheritDoc
     */
    public function setKeywords(array $keywords): void
    {
        $this->keywords = $keywords;
    }

    /**
     * @inheritDoc
     */
    public function getKeywords(): array
    {
        return $this->keywords;
    }

    /**
     * @inheritDoc
     */
    public function setAuthors(array $authors): void
    {
        $this->authors = $authors;
    }

    /**
     * @inheritDoc
     */
    public function getAuthors(): array
    {
        return $this->authors;
    }

    /**
     * @inheritDoc
     */
    public function setDescription(?string $description): void
    {
        $this->description = $description;
    }

    /**
     * @inheritDoc
     */
    public function getDescription(): ?string
    {
        return $this->description;
    }

    /**
     * @inheritDoc
     */
    public function setHomepage(?string $homepage): void
    {
        $this->homepage = $homepage;
    }

    /**
     * @inheritDoc
     */
    public function getHomepage(): ?string
    {
        return $this->homepage;
    }

    /**
     * @inheritDoc
     */
    public function setSupport(array $support): void
    {
        $this->support = $support;
    }

    /**
     * @inheritDoc
     */
    public function getSupport(): array
    {
        return $this->support;
    }

    /**
     * @inheritDoc
     */
    public function setFunding(array $funding): void
    {
        $this->funding = $funding;
    }

    /**
     * @inheritDoc
     */
    public function getFunding(): array
    {
        return $this->funding;
    }

    /**
     * @inheritDoc
     */
    public function isAbandoned(): bool
    {
        return (bool) $this->abandoned;
    }

    /**
     * @inheritDoc
     */
    public function setAbandoned($abandoned): void
    {
        $this->abandoned = $abandoned;
    }

    /**
     * @inheritDoc
     */
    public function getReplacementPackage(): ?string
    {
        return \is_string($this->abandoned) ? $this->abandoned : null;
    }

    /**
     * @inheritDoc
     */
    public function setArchiveName(?string $name): void
    {
        $this->archiveName = $name;
    }

    /**
     * @inheritDoc
     */
    public function getArchiveName(): ?string
    {
        return $this->archiveName;
    }

    /**
     * @inheritDoc
     */
    public function setArchiveExcludes(array $excludes): void
    {
        $this->archiveExcludes = $excludes;
    }

    /**
     * @inheritDoc
     */
    public function getArchiveExcludes(): array
    {
        return $this->archiveExcludes;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package;

/**
 * Defines additional fields that are only needed for the root package
 *
 * PackageInterface & derivatives are considered internal, you may use them in type hints but extending/implementing them is not recommended and not supported. Things may change without notice.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 *
 * @phpstan-import-type AutoloadRules from PackageInterface
 * @phpstan-import-type DevAutoloadRules from PackageInterface
 */
interface RootPackageInterface extends CompletePackageInterface
{
    /**
     * Returns a set of package names and their aliases
     *
     * @return list<array{package: string, version: string, alias: string, alias_normalized: string}>
     */
    public function getAliases(): array;

    /**
     * Returns the minimum stability of the package
     *
     * @return key-of<BasePackage::STABILITIES>
     */
    public function getMinimumStability(): string;

    /**
     * Returns the stability flags to apply to dependencies
     *
     * array('foo/bar' => 'dev')
     *
     * @return array<string, BasePackage::STABILITY_*>
     */
    public function getStabilityFlags(): array;

    /**
     * Returns a set of package names and source references that must be enforced on them
     *
     * array('foo/bar' => 'abcd1234')
     *
     * @return array<string, string>
     */
    public function getReferences(): array;

    /**
     * Returns true if the root package prefers picking stable packages over unstable ones
     */
    public function getPreferStable(): bool;

    /**
     * Returns the root package's configuration
     *
     * @return mixed[]
     */
    public function getConfig(): array;

    /**
     * Set the required packages
     *
     * @param Link[] $requires A set of package links
     */
    public function setRequires(array $requires): void;

    /**
     * Set the recommended packages
     *
     * @param Link[] $devRequires A set of package links
     */
    public function setDevRequires(array $devRequires): void;

    /**
     * Set the conflicting packages
     *
     * @param Link[] $conflicts A set of package links
     */
    public function setConflicts(array $conflicts): void;

    /**
     * Set the provided virtual packages
     *
     * @param Link[] $provides A set of package links
     */
    public function setProvides(array $provides): void;

    /**
     * Set the packages this one replaces
     *
     * @param Link[] $replaces A set of package links
     */
    public function setReplaces(array $replaces): void;

    /**
     * Set the autoload mapping
     *
     * @param array $autoload Mapping of autoloading rules
     * @phpstan-param AutoloadRules $autoload
     */
    public function setAutoload(array $autoload): void;

    /**
     * Set the dev autoload mapping
     *
     * @param array $devAutoload Mapping of dev autoloading rules
     * @phpstan-param DevAutoloadRules $devAutoload
     */
    public function setDevAutoload(array $devAutoload): void;

    /**
     * Set the stabilityFlags
     *
     * @phpstan-param array<string, BasePackage::STABILITY_*> $stabilityFlags
     */
    public function setStabilityFlags(array $stabilityFlags): void;

    /**
     * Set the minimumStability
     *
     * @phpstan-param key-of<BasePackage::STABILITIES> $minimumStability
     */
    public function setMinimumStability(string $minimumStability): void;

    /**
     * Set the preferStable
     */
    public function setPreferStable(bool $preferStable): void;

    /**
     * Set the config
     *
     * @param mixed[] $config
     */
    public function setConfig(array $config): void;

    /**
     * Set the references
     *
     * @param array<string, string> $references
     */
    public function setReferences(array $references): void;

    /**
     * Set the aliases
     *
     * @param list<array{package: string, version: string, alias: string, alias_normalized: string}> $aliases
     */
    public function setAliases(array $aliases): void;

    /**
     * Set the suggested packages
     *
     * @param array<string, string> $suggests A set of package names/comments
     */
    public function setSuggests(array $suggests): void;

    /**
     * @param mixed[] $extra
     */
    public function setExtra(array $extra): void;
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package;

/**
 * Defines package metadata that is not necessarily needed for solving and installing packages
 *
 * PackageInterface & derivatives are considered internal, you may use them in type hints but extending/implementing them is not recommended and not supported. Things may change without notice.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
interface CompletePackageInterface extends PackageInterface
{
    /**
     * Returns the scripts of this package
     *
     * @return array<string, string[]> Map of script name to array of handlers
     */
    public function getScripts(): array;

    /**
     * @param  array<string, string[]> $scripts
     */
    public function setScripts(array $scripts): void;

    /**
     * Returns an array of repositories
     *
     * @return mixed[] Repositories
     */
    public function getRepositories(): array;

    /**
     * Set the repositories
     *
     * @param  mixed[] $repositories
     */
    public function setRepositories(array $repositories): void;

    /**
     * Returns the package license, e.g. MIT, BSD, GPL
     *
     * @return string[] The package licenses
     */
    public function getLicense(): array;

    /**
     * Set the license
     *
     * @param  string[] $license
     */
    public function setLicense(array $license): void;

    /**
     * Returns an array of keywords relating to the package
     *
     * @return string[]
     */
    public function getKeywords(): array;

    /**
     * Set the keywords
     *
     * @param  string[] $keywords
     */
    public function setKeywords(array $keywords): void;

    /**
     * Returns the package description
     *
     * @return ?string
     */
    public function getDescription(): ?string;

    /**
     * Set the description
     */
    public function setDescription(string $description): void;

    /**
     * Returns the package homepage
     *
     * @return ?string
     */
    public function getHomepage(): ?string;

    /**
     * Set the homepage
     */
    public function setHomepage(string $homepage): void;

    /**
     * Returns an array of authors of the package
     *
     * Each item can contain name/homepage/email keys
     *
     * @return array<array{name?: string, homepage?: string, email?: string, role?: string}>
     */
    public function getAuthors(): array;

    /**
     * Set the authors
     *
     * @param  array<array{name?: string, homepage?: string, email?: string, role?: string}> $authors
     */
    public function setAuthors(array $authors): void;

    /**
     * Returns the support information
     *
     * @return array{issues?: string, forum?: string, wiki?: string, source?: string, email?: string, irc?: string, docs?: string, rss?: string, chat?: string, security?: string}
     */
    public function getSupport(): array;

    /**
     * Set the support information
     *
     * @param  array{issues?: string, forum?: string, wiki?: string, source?: string, email?: string, irc?: string, docs?: string, rss?: string, chat?: string, security?: string} $support
     */
    public function setSupport(array $support): void;

    /**
     * Returns an array of funding options for the package
     *
     * Each item will contain type and url keys
     *
     * @return array<array{type?: string, url?: string}>
     */
    public function getFunding(): array;

    /**
     * Set the funding
     *
     * @param  array<array{type?: string, url?: string}> $funding
     */
    public function setFunding(array $funding): void;

    /**
     * Returns if the package is abandoned or not
     */
    public function isAbandoned(): bool;

    /**
     * If the package is abandoned and has a suggested replacement, this method returns it
     */
    public function getReplacementPackage(): ?string;

    /**
     * @param  bool|string $abandoned
     */
    public function setAbandoned($abandoned): void;

    /**
     * Returns default base filename for archive
     *
     * @return ?string
     */
    public function getArchiveName(): ?string;

    /**
     * Sets default base filename for archive
     */
    public function setArchiveName(string $name): void;

    /**
     * Returns a list of patterns to exclude from package archives
     *
     * @return string[]
     */
    public function getArchiveExcludes(): array;

    /**
     * Sets a list of patterns to be excluded from archives
     *
     * @param  string[] $excludes
     */
    public function setArchiveExcludes(array $excludes): void;
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package;

use Composer\Package\Version\VersionParser;
use Composer\Pcre\Preg;
use Composer\Util\ComposerMirror;

/**
 * Core package definitions that are needed to resolve dependencies and install packages
 *
 * @author Nils Adermann <naderman@naderman.de>
 *
 * @phpstan-import-type AutoloadRules from PackageInterface
 * @phpstan-import-type DevAutoloadRules from PackageInterface
 * @phpstan-import-type PhpExtConfig from PackageInterface
 */
class Package extends BasePackage
{
    /** @var string */
    protected $type;
    /** @var ?string */
    protected $targetDir;
    /** @var 'source'|'dist'|null */
    protected $installationSource;
    /** @var ?string */
    protected $sourceType;
    /** @var ?string */
    protected $sourceUrl;
    /** @var ?string */
    protected $sourceReference;
    /** @var ?list<array{url: non-empty-string, preferred: bool}> */
    protected $sourceMirrors;
    /** @var ?non-empty-string */
    protected $distType;
    /** @var ?non-empty-string */
    protected $distUrl;
    /** @var ?string */
    protected $distReference;
    /** @var ?string */
    protected $distSha1Checksum;
    /** @var ?list<array{url: non-empty-string, preferred: bool}> */
    protected $distMirrors;
    /** @var string */
    protected $version;
    /** @var string */
    protected $prettyVersion;
    /** @var ?\DateTimeInterface */
    protected $releaseDate;
    /** @var mixed[] */
    protected $extra = [];
    /** @var string[] */
    protected $binaries = [];
    /** @var bool */
    protected $dev;
    /**
     * @var string
     * @phpstan-var 'stable'|'RC'|'beta'|'alpha'|'dev'
     */
    protected $stability;
    /** @var ?string */
    protected $notificationUrl;

    /** @var array<string, Link> */
    protected $requires = [];
    /** @var array<string, Link> */
    protected $conflicts = [];
    /** @var array<string, Link> */
    protected $provides = [];
    /** @var array<string, Link> */
    protected $replaces = [];
    /** @var array<string, Link> */
    protected $devRequires = [];
    /** @var array<string, string> */
    protected $suggests = [];
    /**
     * @var array
     * @phpstan-var AutoloadRules
     */
    protected $autoload = [];
    /**
     * @var array
     * @phpstan-var DevAutoloadRules
     */
    protected $devAutoload = [];
    /** @var string[] */
    protected $includePaths = [];
    /** @var bool */
    protected $isDefaultBranch = false;
    /** @var mixed[] */
    protected $transportOptions = [];
    /**
     * @var array|null
     * @phpstan-var PhpExtConfig|null
     */
    protected $phpExt = null;

    /**
     * Creates a new in memory package.
     *
     * @param string $name          The package's name
     * @param string $version       The package's version
     * @param string $prettyVersion The package's non-normalized version
     */
    public function __construct(string $name, string $version, string $prettyVersion)
    {
        parent::__construct($name);

        $this->version = $version;
        $this->prettyVersion = $prettyVersion;

        $this->stability = VersionParser::parseStability($version);
        $this->dev = $this->stability === 'dev';
    }

    /**
     * @inheritDoc
     */
    public function isDev(): bool
    {
        return $this->dev;
    }

    public function setType(string $type): void
    {
        $this->type = $type;
    }

    /**
     * @inheritDoc
     */
    public function getType(): string
    {
        return $this->type ?: 'library';
    }

    /**
     * @inheritDoc
     */
    public function getStability(): string
    {
        return $this->stability;
    }

    public function setTargetDir(?string $targetDir): void
    {
        $this->targetDir = $targetDir;
    }

    /**
     * @inheritDoc
     */
    public function getTargetDir(): ?string
    {
        if (null === $this->targetDir) {
            return null;
        }

        return ltrim(Preg::replace('{ (?:^|[\\\\/]+) \.\.? (?:[\\\\/]+|$) (?:\.\.? (?:[\\\\/]+|$) )*}x', '/', $this->targetDir), '/');
    }

    /**
     * @param mixed[] $extra
     */
    public function setExtra(array $extra): void
    {
        $this->extra = $extra;
    }

    /**
     * @inheritDoc
     */
    public function getExtra(): array
    {
        return $this->extra;
    }

    /**
     * @param string[] $binaries
     */
    public function setBinaries(array $binaries): void
    {
        $this->binaries = $binaries;
    }

    /**
     * @inheritDoc
     */
    public function getBinaries(): array
    {
        return $this->binaries;
    }

    /**
     * @inheritDoc
     */
    public function setInstallationSource(?string $type): void
    {
        $this->installationSource = $type;
    }

    /**
     * @inheritDoc
     */
    public function getInstallationSource(): ?string
    {
        return $this->installationSource;
    }

    public function setSourceType(?string $type): void
    {
        $this->sourceType = $type;
    }

    /**
     * @inheritDoc
     */
    public function getSourceType(): ?string
    {
        return $this->sourceType;
    }

    public function setSourceUrl(?string $url): void
    {
        $this->sourceUrl = $url;
    }

    /**
     * @inheritDoc
     */
    public function getSourceUrl(): ?string
    {
        return $this->sourceUrl;
    }

    public function setSourceReference(?string $reference): void
    {
        $this->sourceReference = $reference;
    }

    /**
     * @inheritDoc
     */
    public function getSourceReference(): ?string
    {
        return $this->sourceReference;
    }

    public function setSourceMirrors(?array $mirrors): void
    {
        $this->sourceMirrors = $mirrors;
    }

    /**
     * @inheritDoc
     */
    public function getSourceMirrors(): ?array
    {
        return $this->sourceMirrors;
    }

    /**
     * @inheritDoc
     */
    public function getSourceUrls(): array
    {
        return $this->getUrls($this->sourceUrl, $this->sourceMirrors, $this->sourceReference, $this->sourceType, 'source');
    }

    /**
     * @param string $type
     */
    public function setDistType(?string $type): void
    {
        $this->distType = $type === '' ? null : $type;
    }

    /**
     * @inheritDoc
     */
    public function getDistType(): ?string
    {
        return $this->distType;
    }

    /**
     * @param string|null $url
     */
    public function setDistUrl(?string $url): void
    {
        $this->distUrl = $url === '' ? null : $url;
    }

    /**
     * @inheritDoc
     */
    public function getDistUrl(): ?string
    {
        return $this->distUrl;
    }

    /**
     * @param string $reference
     */
    public function setDistReference(?string $reference): void
    {
        $this->distReference = $reference;
    }

    /**
     * @inheritDoc
     */
    public function getDistReference(): ?string
    {
        return $this->distReference;
    }

    /**
     * @param string $sha1checksum
     */
    public function setDistSha1Checksum(?string $sha1checksum): void
    {
        $this->distSha1Checksum = $sha1checksum;
    }

    /**
     * @inheritDoc
     */
    public function getDistSha1Checksum(): ?string
    {
        return $this->distSha1Checksum;
    }

    public function setDistMirrors(?array $mirrors): void
    {
        $this->distMirrors = $mirrors;
    }

    /**
     * @inheritDoc
     */
    public function getDistMirrors(): ?array
    {
        return $this->distMirrors;
    }

    /**
     * @inheritDoc
     */
    public function getDistUrls(): array
    {
        return $this->getUrls($this->distUrl, $this->distMirrors, $this->distReference, $this->distType, 'dist');
    }

    /**
     * @inheritDoc
     */
    public function getTransportOptions(): array
    {
        return $this->transportOptions;
    }

    /**
     * @inheritDoc
     */
    public function setTransportOptions(array $options): void
    {
        $this->transportOptions = $options;
    }

    /**
     * @inheritDoc
     */
    public function getVersion(): string
    {
        return $this->version;
    }

    /**
     * @inheritDoc
     */
    public function getPrettyVersion(): string
    {
        return $this->prettyVersion;
    }

    public function setReleaseDate(?\DateTimeInterface $releaseDate): void
    {
        $this->releaseDate = $releaseDate;
    }

    /**
     * @inheritDoc
     */
    public function getReleaseDate(): ?\DateTimeInterface
    {
        return $this->releaseDate;
    }

    /**
     * Set the required packages
     *
     * @param array<string, Link> $requires A set of package links
     */
    public function setRequires(array $requires): void
    {
        if (isset($requires[0])) { // @phpstan-ignore-line
            $requires = $this->convertLinksToMap($requires, 'setRequires');
        }

        $this->requires = $requires;
    }

    /**
     * @inheritDoc
     */
    public function getRequires(): array
    {
        return $this->requires;
    }

    /**
     * Set the conflicting packages
     *
     * @param array<string, Link> $conflicts A set of package links
     */
    public function setConflicts(array $conflicts): void
    {
        if (isset($conflicts[0])) { // @phpstan-ignore-line
            $conflicts = $this->convertLinksToMap($conflicts, 'setConflicts');
        }

        $this->conflicts = $conflicts;
    }

    /**
     * @inheritDoc
     * @return array<string, Link>
     */
    public function getConflicts(): array
    {
        return $this->conflicts;
    }

    /**
     * Set the provided virtual packages
     *
     * @param array<string, Link> $provides A set of package links
     */
    public function setProvides(array $provides): void
    {
        if (isset($provides[0])) { // @phpstan-ignore-line
            $provides = $this->convertLinksToMap($provides, 'setProvides');
        }

        $this->provides = $provides;
    }

    /**
     * @inheritDoc
     * @return array<string, Link>
     */
    public function getProvides(): array
    {
        return $this->provides;
    }

    /**
     * Set the packages this one replaces
     *
     * @param array<string, Link> $replaces A set of package links
     */
    public function setReplaces(array $replaces): void
    {
        if (isset($replaces[0])) { // @phpstan-ignore-line
            $replaces = $this->convertLinksToMap($replaces, 'setReplaces');
        }

        $this->replaces = $replaces;
    }

    /**
     * @inheritDoc
     * @return array<string, Link>
     */
    public function getReplaces(): array
    {
        return $this->replaces;
    }

    /**
     * Set the recommended packages
     *
     * @param array<string, Link> $devRequires A set of package links
     */
    public function setDevRequires(array $devRequires): void
    {
        if (isset($devRequires[0])) { // @phpstan-ignore-line
            $devRequires = $this->convertLinksToMap($devRequires, 'setDevRequires');
        }

        $this->devRequires = $devRequires;
    }

    /**
     * @inheritDoc
     */
    public function getDevRequires(): array
    {
        return $this->devRequires;
    }

    /**
     * Set the suggested packages
     *
     * @param array<string, string> $suggests A set of package names/comments
     */
    public function setSuggests(array $suggests): void
    {
        $this->suggests = $suggests;
    }

    /**
     * @inheritDoc
     */
    public function getSuggests(): array
    {
        return $this->suggests;
    }

    /**
     * Set the autoload mapping
     *
     * @param array $autoload Mapping of autoloading rules
     *
     * @phpstan-param AutoloadRules $autoload
     */
    public function setAutoload(array $autoload): void
    {
        $this->autoload = $autoload;
    }

    /**
     * @inheritDoc
     */
    public function getAutoload(): array
    {
        return $this->autoload;
    }

    /**
     * Set the dev autoload mapping
     *
     * @param array $devAutoload Mapping of dev autoloading rules
     *
     * @phpstan-param DevAutoloadRules $devAutoload
     */
    public function setDevAutoload(array $devAutoload): void
    {
        $this->devAutoload = $devAutoload;
    }

    /**
     * @inheritDoc
     */
    public function getDevAutoload(): array
    {
        return $this->devAutoload;
    }

    /**
     * Sets the list of paths added to PHP's include path.
     *
     * @param string[] $includePaths List of directories.
     */
    public function setIncludePaths(array $includePaths): void
    {
        $this->includePaths = $includePaths;
    }

    /**
     * @inheritDoc
     */
    public function getIncludePaths(): array
    {
        return $this->includePaths;
    }

    /**
     * Sets the settings for php extension packages
     *
     * @param array|null $phpExt
     *
     * @phpstan-param PhpExtConfig|null $phpExt
     */
    public function setPhpExt(?array $phpExt): void
    {
        $this->phpExt = $phpExt;
    }

    /**
     * @inheritDoc
     */
    public function getPhpExt(): ?array
    {
        return $this->phpExt;
    }

    /**
     * Sets the notification URL
     */
    public function setNotificationUrl(string $notificationUrl): void
    {
        $this->notificationUrl = $notificationUrl;
    }

    /**
     * @inheritDoc
     */
    public function getNotificationUrl(): ?string
    {
        return $this->notificationUrl;
    }

    public function setIsDefaultBranch(bool $defaultBranch): void
    {
        $this->isDefaultBranch = $defaultBranch;
    }

    /**
     * @inheritDoc
     */
    public function isDefaultBranch(): bool
    {
        return $this->isDefaultBranch;
    }

    /**
     * @inheritDoc
     */
    public function setSourceDistReferences(string $reference): void
    {
        $this->setSourceReference($reference);

        // only bitbucket, github and gitlab have auto generated dist URLs that easily allow replacing the reference in the dist URL
        // TODO generalize this a bit for self-managed/on-prem versions? Some kind of replace token in dist urls which allow this?
        if (
            $this->getDistUrl() !== null
            && Preg::isMatch('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com|(?:www\.)?gitlab\.com)/}i', $this->getDistUrl())
        ) {
            $this->setDistReference($reference);
            $this->setDistUrl(Preg::replace('{(?<=/|sha=)[a-f0-9]{40}(?=/|$)}i', $reference, $this->getDistUrl()));
        } elseif ($this->getDistReference()) { // update the dist reference if there was one, but if none was provided ignore it
            $this->setDistReference($reference);
        }
    }

    /**
     * Replaces current version and pretty version with passed values.
     * It also sets stability.
     *
     * @param string $version       The package's normalized version
     * @param string $prettyVersion The package's non-normalized version
     */
    public function replaceVersion(string $version, string $prettyVersion): void
    {
        $this->version = $version;
        $this->prettyVersion = $prettyVersion;

        $this->stability = VersionParser::parseStability($version);
        $this->dev = $this->stability === 'dev';
    }

    /**
     * @param mixed[]|null $mirrors
     *
     * @return list<non-empty-string>
     *
     * @phpstan-param list<array{url: non-empty-string, preferred: bool}>|null $mirrors
     */
    protected function getUrls(?string $url, ?array $mirrors, ?string $ref, ?string $type, string $urlType): array
    {
        if (!$url) {
            return [];
        }

        if ($urlType === 'dist' && false !== strpos($url, '%')) {
            $url = ComposerMirror::processUrl($url, $this->name, $this->version, $ref, $type, $this->prettyVersion);
        }

        $urls = [$url];
        if ($mirrors) {
            foreach ($mirrors as $mirror) {
                if ($urlType === 'dist') {
                    $mirrorUrl = ComposerMirror::processUrl($mirror['url'], $this->name, $this->version, $ref, $type, $this->prettyVersion);
                } elseif ($urlType === 'source' && $type === 'git') {
                    $mirrorUrl = ComposerMirror::processGitUrl($mirror['url'], $this->name, $url, $type);
                } elseif ($urlType === 'source' && $type === 'hg') {
                    $mirrorUrl = ComposerMirror::processHgUrl($mirror['url'], $this->name, $url, $type);
                } else {
                    continue;
                }
                if (!\in_array($mirrorUrl, $urls)) {
                    $func = $mirror['preferred'] ? 'array_unshift' : 'array_push';
                    $func($urls, $mirrorUrl);
                }
            }
        }

        return $urls;
    }

    /**
     * @param  array<int, Link> $links
     * @return array<string, Link>
     */
    private function convertLinksToMap(array $links, string $source): array
    {
        trigger_error('Package::'.$source.' must be called with a map of lowercased package name => Link object, got a indexed array, this is deprecated and you should fix your usage.');
        $newLinks = [];
        foreach ($links as $link) {
            $newLinks[$link->getTarget()] = $link;
        }

        return $newLinks;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Loader;

use Composer\Package\BasePackage;
use Composer\Pcre\Preg;
use Composer\Semver\Constraint\Constraint;
use Composer\Package\Version\VersionParser;
use Composer\Repository\PlatformRepository;
use Composer\Semver\Constraint\MatchNoneConstraint;
use Composer\Semver\Intervals;
use Composer\Spdx\SpdxLicenses;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ValidatingArrayLoader implements LoaderInterface
{
    public const CHECK_ALL = 3;
    public const CHECK_UNBOUND_CONSTRAINTS = 1;
    public const CHECK_STRICT_CONSTRAINTS = 2;

    /** @var LoaderInterface */
    private $loader;
    /** @var VersionParser */
    private $versionParser;
    /** @var list<string> */
    private $errors;
    /** @var list<string> */
    private $warnings;
    /** @var mixed[] */
    private $config;
    /** @var int One or more of self::CHECK_* constants */
    private $flags;

    /**
     * @param true $strictName
     */
    public function __construct(LoaderInterface $loader, bool $strictName = true, ?VersionParser $parser = null, int $flags = 0)
    {
        $this->loader = $loader;
        $this->versionParser = $parser ?? new VersionParser();
        $this->flags = $flags;

        if ($strictName !== true) { // @phpstan-ignore-line
            trigger_error('$strictName must be set to true in ValidatingArrayLoader\'s constructor as of 2.2, and it will be removed in 3.0', E_USER_DEPRECATED);
        }
    }

    /**
     * @inheritDoc
     */
    public function load(array $config, string $class = 'Composer\Package\CompletePackage'): BasePackage
    {
        $this->errors = [];
        $this->warnings = [];
        $this->config = $config;

        $this->validateString('name', true);
        if (isset($config['name']) && null !== ($err = self::hasPackageNamingError($config['name']))) {
            $this->errors[] = 'name : '.$err;
        }

        if (isset($this->config['version'])) {
            if (!is_scalar($this->config['version'])) {
                $this->validateString('version');
            } else {
                if (!is_string($this->config['version'])) {
                    $this->config['version'] = (string) $this->config['version'];
                }
                try {
                    $this->versionParser->normalize($this->config['version']);
                } catch (\Exception $e) {
                    $this->errors[] = 'version : invalid value ('.$this->config['version'].'): '.$e->getMessage();
                    unset($this->config['version']);
                }
            }
        }

        if (isset($this->config['config']['platform'])) {
            foreach ((array) $this->config['config']['platform'] as $key => $platform) {
                if (false === $platform) {
                    continue;
                }
                if (!is_string($platform)) {
                    $this->errors[] = 'config.platform.' . $key . ' : invalid value ('.gettype($platform).' '.var_export($platform, true).'): expected string or false';
                    continue;
                }
                try {
                    $this->versionParser->normalize($platform);
                } catch (\Exception $e) {
                    $this->errors[] = 'config.platform.' . $key . ' : invalid value ('.$platform.'): '.$e->getMessage();
                }
            }
        }

        $this->validateRegex('type', '[A-Za-z0-9-]+');
        $this->validateString('target-dir');
        $this->validateArray('extra');

        if (isset($this->config['bin'])) {
            if (is_string($this->config['bin'])) {
                $this->validateString('bin');
            } else {
                $this->validateFlatArray('bin');
            }
        }

        $this->validateArray('scripts'); // TODO validate event names & listener syntax
        $this->validateString('description');
        $this->validateUrl('homepage');
        $this->validateFlatArray('keywords', '[\p{N}\p{L} ._-]+');

        $releaseDate = null;
        $this->validateString('time');
        if (isset($this->config['time'])) {
            try {
                $releaseDate = new \DateTime($this->config['time'], new \DateTimeZone('UTC'));
            } catch (\Exception $e) {
                $this->errors[] = 'time : invalid value ('.$this->config['time'].'): '.$e->getMessage();
                unset($this->config['time']);
            }
        }

        if (isset($this->config['license'])) {
            // validate main data types
            if (is_array($this->config['license']) || is_string($this->config['license'])) {
                $licenses = (array) $this->config['license'];

                foreach ($licenses as $index => $license) {
                    if (!is_string($license)) {
                        $this->warnings[] = sprintf(
                            'License %s should be a string.',
                            json_encode($license)
                        );
                        unset($licenses[$index]);
                    }
                }

                // check for license validity on newly updated branches/tags
                if (null === $releaseDate || $releaseDate->getTimestamp() >= strtotime('-8days')) {
                    $licenseValidator = new SpdxLicenses();
                    foreach ($licenses as $license) {
                        // replace proprietary by MIT for validation purposes since it's not a valid SPDX identifier, but is accepted by composer
                        if ('proprietary' === $license) {
                            continue;
                        }
                        $licenseToValidate = str_replace('proprietary', 'MIT', $license);
                        if (!$licenseValidator->validate($licenseToValidate)) {
                            if ($licenseValidator->validate(trim($licenseToValidate))) {
                                $this->warnings[] = sprintf(
                                    'License %s must not contain extra spaces, make sure to trim it.',
                                    json_encode($license)
                                );
                            } else {
                                $this->warnings[] = sprintf(
                                    'License %s is not a valid SPDX license identifier, see https://spdx.org/licenses/ if you use an open license.' . PHP_EOL .
                                    'If the software is closed-source, you may use "proprietary" as license.',
                                    json_encode($license)
                                );
                            }
                        }
                    }
                }

                $this->config['license'] = array_values($licenses);
            } else {
                $this->warnings[] = sprintf(
                    'License must be a string or array of strings, got %s.',
                    json_encode($this->config['license'])
                );
                unset($this->config['license']);
            }
        }

        if ($this->validateArray('authors')) {
            foreach ($this->config['authors'] as $key => $author) {
                if (!is_array($author)) {
                    $this->errors[] = 'authors.'.$key.' : should be an array, '.gettype($author).' given';
                    unset($this->config['authors'][$key]);
                    continue;
                }
                foreach (['homepage', 'email', 'name', 'role'] as $authorData) {
                    if (isset($author[$authorData]) && !is_string($author[$authorData])) {
                        $this->errors[] = 'authors.'.$key.'.'.$authorData.' : invalid value, must be a string';
                        unset($this->config['authors'][$key][$authorData]);
                    }
                }
                if (isset($author['homepage']) && !$this->filterUrl($author['homepage'])) {
                    $this->warnings[] = 'authors.'.$key.'.homepage : invalid value ('.$author['homepage'].'), must be an http/https URL';
                    unset($this->config['authors'][$key]['homepage']);
                }
                if (isset($author['email']) && false === filter_var($author['email'], FILTER_VALIDATE_EMAIL)) {
                    $this->warnings[] = 'authors.'.$key.'.email : invalid value ('.$author['email'].'), must be a valid email address';
                    unset($this->config['authors'][$key]['email']);
                }
                if (\count($this->config['authors'][$key]) === 0) {
                    unset($this->config['authors'][$key]);
                }
            }
            if (\count($this->config['authors']) === 0) {
                unset($this->config['authors']);
            }
        }

        if ($this->validateArray('support') && !empty($this->config['support'])) {
            foreach (['issues', 'forum', 'wiki', 'source', 'email', 'irc', 'docs', 'rss', 'chat', 'security'] as $key) {
                if (isset($this->config['support'][$key]) && !is_string($this->config['support'][$key])) {
                    $this->errors[] = 'support.'.$key.' : invalid value, must be a string';
                    unset($this->config['support'][$key]);
                }
            }

            if (isset($this->config['support']['email']) && !filter_var($this->config['support']['email'], FILTER_VALIDATE_EMAIL)) {
                $this->warnings[] = 'support.email : invalid value ('.$this->config['support']['email'].'), must be a valid email address';
                unset($this->config['support']['email']);
            }

            if (isset($this->config['support']['irc']) && !$this->filterUrl($this->config['support']['irc'], ['irc', 'ircs'])) {
                $this->warnings[] = 'support.irc : invalid value ('.$this->config['support']['irc'].'), must be a irc://<server>/<channel> or ircs:// URL';
                unset($this->config['support']['irc']);
            }

            foreach (['issues', 'forum', 'wiki', 'source', 'docs', 'chat', 'security'] as $key) {
                if (isset($this->config['support'][$key]) && !$this->filterUrl($this->config['support'][$key])) {
                    $this->warnings[] = 'support.'.$key.' : invalid value ('.$this->config['support'][$key].'), must be an http/https URL';
                    unset($this->config['support'][$key]);
                }
            }
            if (empty($this->config['support'])) {
                unset($this->config['support']);
            }
        }

        if ($this->validateArray('funding') && !empty($this->config['funding'])) {
            foreach ($this->config['funding'] as $key => $fundingOption) {
                if (!is_array($fundingOption)) {
                    $this->errors[] = 'funding.'.$key.' : should be an array, '.gettype($fundingOption).' given';
                    unset($this->config['funding'][$key]);
                    continue;
                }
                foreach (['type', 'url'] as $fundingData) {
                    if (isset($fundingOption[$fundingData]) && !is_string($fundingOption[$fundingData])) {
                        $this->errors[] = 'funding.'.$key.'.'.$fundingData.' : invalid value, must be a string';
                        unset($this->config['funding'][$key][$fundingData]);
                    }
                }
                if (isset($fundingOption['url']) && !$this->filterUrl($fundingOption['url'])) {
                    $this->warnings[] = 'funding.'.$key.'.url : invalid value ('.$fundingOption['url'].'), must be an http/https URL';
                    unset($this->config['funding'][$key]['url']);
                }
                if (empty($this->config['funding'][$key])) {
                    unset($this->config['funding'][$key]);
                }
            }
            if (empty($this->config['funding'])) {
                unset($this->config['funding']);
            }
        }

        $this->validateArray('php-ext');
        if (isset($this->config['php-ext']) && !in_array($this->config['type'] ?? '', ['php-ext', 'php-ext-zend'], true)) {
            $this->errors[] = 'php-ext can only be set by packages of type "php-ext" or "php-ext-zend" which must be C extensions';
            unset($this->config['php-ext']);
        }

        $unboundConstraint = new Constraint('=', '10000000-dev');

        foreach (array_keys(BasePackage::$supportedLinkTypes) as $linkType) {
            if ($this->validateArray($linkType) && isset($this->config[$linkType])) {
                foreach ($this->config[$linkType] as $package => $constraint) {
                    $package = (string) $package;
                    if (isset($this->config['name']) && 0 === strcasecmp($package, $this->config['name'])) {
                        $this->errors[] = $linkType.'.'.$package.' : a package cannot set a '.$linkType.' on itself';
                        unset($this->config[$linkType][$package]);
                        continue;
                    }
                    if ($err = self::hasPackageNamingError($package, true)) {
                        $this->warnings[] = $linkType.'.'.$err;
                    } elseif (!Preg::isMatch('{^[A-Za-z0-9_./-]+$}', $package)) {
                        $this->errors[] = $linkType.'.'.$package.' : invalid key, package names must be strings containing only [A-Za-z0-9_./-]';
                    }
                    if (!is_string($constraint)) {
                        $this->errors[] = $linkType.'.'.$package.' : invalid value, must be a string containing a version constraint';
                        unset($this->config[$linkType][$package]);
                    } elseif ('self.version' !== $constraint) {
                        try {
                            $linkConstraint = $this->versionParser->parseConstraints($constraint);
                        } catch (\Exception $e) {
                            $this->errors[] = $linkType.'.'.$package.' : invalid version constraint ('.$e->getMessage().')';
                            unset($this->config[$linkType][$package]);
                            continue;
                        }

                        // check requires for unbound constraints on non-platform packages
                        if (
                            ($this->flags & self::CHECK_UNBOUND_CONSTRAINTS)
                            && 'require' === $linkType
                            && $linkConstraint->matches($unboundConstraint)
                            && !PlatformRepository::isPlatformPackage($package)
                        ) {
                            $this->warnings[] = $linkType.'.'.$package.' : unbound version constraints ('.$constraint.') should be avoided';
                        } elseif (
                            // check requires for exact constraints
                            ($this->flags & self::CHECK_STRICT_CONSTRAINTS)
                            && 'require' === $linkType
                            && $linkConstraint instanceof Constraint && in_array($linkConstraint->getOperator(), ['==', '='], true)
                            && (new Constraint('>=', '1.0.0.0-dev'))->matches($linkConstraint)
                        ) {
                            $this->warnings[] = $linkType.'.'.$package.' : exact version constraints ('.$constraint.') should be avoided if the package follows semantic versioning';
                        }

                        $compacted = Intervals::compactConstraint($linkConstraint);
                        if ($compacted instanceof MatchNoneConstraint) {
                            $this->warnings[] = $linkType.'.'.$package.' : this version constraint cannot possibly match anything ('.$constraint.')';
                        }
                    }

                    if ($linkType === 'conflict' && isset($this->config['replace']) && $keys = array_intersect_key($this->config['replace'], $this->config['conflict'])) {
                        $this->errors[] = $linkType.'.'.$package.' : you cannot conflict with a package that is also replaced, as replace already creates an implicit conflict rule';
                        unset($this->config[$linkType][$package]);
                    }
                }
            }
        }

        if ($this->validateArray('suggest') && isset($this->config['suggest'])) {
            foreach ($this->config['suggest'] as $package => $description) {
                if (!is_string($description)) {
                    $this->errors[] = 'suggest.'.$package.' : invalid value, must be a string describing why the package is suggested';
                    unset($this->config['suggest'][$package]);
                }
            }
        }

        if ($this->validateString('minimum-stability') && isset($this->config['minimum-stability'])) {
            if (!isset(BasePackage::STABILITIES[strtolower($this->config['minimum-stability'])]) && $this->config['minimum-stability'] !== 'RC') {
                $this->errors[] = 'minimum-stability : invalid value ('.$this->config['minimum-stability'].'), must be one of '.implode(', ', array_keys(BasePackage::STABILITIES));
                unset($this->config['minimum-stability']);
            }
        }

        if ($this->validateArray('autoload') && isset($this->config['autoload'])) {
            $types = ['psr-0', 'psr-4', 'classmap', 'files', 'exclude-from-classmap'];
            foreach ($this->config['autoload'] as $type => $typeConfig) {
                if (!in_array($type, $types)) {
                    $this->errors[] = 'autoload : invalid value ('.$type.'), must be one of '.implode(', ', $types);
                    unset($this->config['autoload'][$type]);
                }
                if ($type === 'psr-4') {
                    foreach ($typeConfig as $namespace => $dirs) {
                        if ($namespace !== '' && '\\' !== substr((string) $namespace, -1)) {
                            $this->errors[] = 'autoload.psr-4 : invalid value ('.$namespace.'), namespaces must end with a namespace separator, should be '.$namespace.'\\\\';
                        }
                    }
                }
            }
        }

        if (isset($this->config['autoload']['psr-4']) && isset($this->config['target-dir'])) {
            $this->errors[] = 'target-dir : this can not be used together with the autoload.psr-4 setting, remove target-dir to upgrade to psr-4';
            // Unset the psr-4 setting, since unsetting target-dir might
            // interfere with other settings.
            unset($this->config['autoload']['psr-4']);
        }

        foreach (['source', 'dist'] as $srcType) {
            if ($this->validateArray($srcType) && !empty($this->config[$srcType])) {
                if (!isset($this->config[$srcType]['type'])) {
                    $this->errors[] = $srcType . '.type : must be present';
                }
                if (!isset($this->config[$srcType]['url'])) {
                    $this->errors[] = $srcType . '.url : must be present';
                }
                if ($srcType === 'source' && !isset($this->config[$srcType]['reference'])) {
                    $this->errors[] = $srcType . '.reference : must be present';
                }
                if (isset($this->config[$srcType]['type']) && !is_string($this->config[$srcType]['type'])) {
                    $this->errors[] = $srcType . '.type : should be a string, '.gettype($this->config[$srcType]['type']).' given';
                }
                if (isset($this->config[$srcType]['url']) && !is_string($this->config[$srcType]['url'])) {
                    $this->errors[] = $srcType . '.url : should be a string, '.gettype($this->config[$srcType]['url']).' given';
                }
                if (isset($this->config[$srcType]['reference']) && !is_string($this->config[$srcType]['reference']) && !is_int($this->config[$srcType]['reference'])) {
                    $this->errors[] = $srcType . '.reference : should be a string or int, '.gettype($this->config[$srcType]['reference']).' given';
                }
                if (isset($this->config[$srcType]['reference']) && Preg::isMatch('{^\s*-}', (string) $this->config[$srcType]['reference'])) {
                    $this->errors[] = $srcType . '.reference : must not start with a "-", "'.$this->config[$srcType]['reference'].'" given';
                }
                if (isset($this->config[$srcType]['url']) && Preg::isMatch('{^\s*-}', (string) $this->config[$srcType]['url'])) {
                    $this->errors[] = $srcType . '.url : must not start with a "-", "'.$this->config[$srcType]['url'].'" given';
                }
            }
        }

        // TODO validate repositories
        // TODO validate package repositories' packages using this recursively

        $this->validateFlatArray('include-path');
        $this->validateArray('transport-options');

        // branch alias validation
        if (isset($this->config['extra']['branch-alias'])) {
            if (!is_array($this->config['extra']['branch-alias'])) {
                $this->errors[] = 'extra.branch-alias : must be an array of versions => aliases';
            } else {
                foreach ($this->config['extra']['branch-alias'] as $sourceBranch => $targetBranch) {
                    if (!is_string($targetBranch)) {
                        $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.json_encode($targetBranch).') must be a string, "'.gettype($targetBranch).'" received.';
                        unset($this->config['extra']['branch-alias'][$sourceBranch]);

                        continue;
                    }

                    // ensure it is an alias to a -dev package
                    if ('-dev' !== substr($targetBranch, -4)) {
                        $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') must end in -dev';
                        unset($this->config['extra']['branch-alias'][$sourceBranch]);

                        continue;
                    }

                    // normalize without -dev and ensure it's a numeric branch that is parseable
                    $validatedTargetBranch = $this->versionParser->normalizeBranch(substr($targetBranch, 0, -4));
                    if ('-dev' !== substr($validatedTargetBranch, -4)) {
                        $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') must be a parseable number like 2.0-dev';
                        unset($this->config['extra']['branch-alias'][$sourceBranch]);

                        continue;
                    }

                    // If using numeric aliases ensure the alias is a valid subversion
                    if (($sourcePrefix = $this->versionParser->parseNumericAliasPrefix($sourceBranch))
                        && ($targetPrefix = $this->versionParser->parseNumericAliasPrefix($targetBranch))
                        && (stripos($targetPrefix, $sourcePrefix) !== 0)
                    ) {
                        $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') is not a valid numeric alias for this version';
                        unset($this->config['extra']['branch-alias'][$sourceBranch]);
                    }
                }
            }
        }

        if ($this->errors) {
            throw new InvalidPackageException($this->errors, $this->warnings, $config);
        }

        $package = $this->loader->load($this->config, $class);
        $this->config = [];

        return $package;
    }

    /**
     * @return list<string>
     */
    public function getWarnings(): array
    {
        return $this->warnings;
    }

    /**
     * @return list<string>
     */
    public function getErrors(): array
    {
        return $this->errors;
    }

    public static function hasPackageNamingError(string $name, bool $isLink = false): ?string
    {
        if (PlatformRepository::isPlatformPackage($name)) {
            return null;
        }

        if (!Preg::isMatch('{^[a-z0-9](?:[_.-]?[a-z0-9]++)*+/[a-z0-9](?:(?:[_.]|-{1,2})?[a-z0-9]++)*+$}iD', $name)) {
            return $name.' is invalid, it should have a vendor name, a forward slash, and a package name. The vendor and package name can be words separated by -, . or _. The complete name should match "^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9](([_.]?|-{0,2})[a-z0-9]+)*$".';
        }

        $reservedNames = ['nul', 'con', 'prn', 'aux', 'com1', 'com2', 'com3', 'com4', 'com5', 'com6', 'com7', 'com8', 'com9', 'lpt1', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 'lpt6', 'lpt7', 'lpt8', 'lpt9'];
        $bits = explode('/', strtolower($name));
        if (in_array($bits[0], $reservedNames, true) || in_array($bits[1], $reservedNames, true)) {
            return $name.' is reserved, package and vendor names can not match any of: '.implode(', ', $reservedNames).'.';
        }

        if (Preg::isMatch('{\.json$}', $name)) {
            return $name.' is invalid, package names can not end in .json, consider renaming it or perhaps using a -json suffix instead.';
        }

        if (Preg::isMatch('{[A-Z]}', $name)) {
            if ($isLink) {
                return $name.' is invalid, it should not contain uppercase characters. Please use '.strtolower($name).' instead.';
            }

            $suggestName = Preg::replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}', '\\1\\3-\\2\\4', $name);
            $suggestName = strtolower($suggestName);

            return $name.' is invalid, it should not contain uppercase characters. We suggest using '.$suggestName.' instead.';
        }

        return null;
    }

    /**
     * @phpstan-param non-empty-string $property
     * @phpstan-param non-empty-string $regex
     */
    private function validateRegex(string $property, string $regex, bool $mandatory = false): bool
    {
        if (!$this->validateString($property, $mandatory)) {
            return false;
        }

        if (!Preg::isMatch('{^'.$regex.'$}u', $this->config[$property])) {
            $message = $property.' : invalid value ('.$this->config[$property].'), must match '.$regex;
            if ($mandatory) {
                $this->errors[] = $message;
            } else {
                $this->warnings[] = $message;
            }
            unset($this->config[$property]);

            return false;
        }

        return true;
    }

    /**
     * @phpstan-param non-empty-string $property
     */
    private function validateString(string $property, bool $mandatory = false): bool
    {
        if (isset($this->config[$property]) && !is_string($this->config[$property])) {
            $this->errors[] = $property.' : should be a string, '.gettype($this->config[$property]).' given';
            unset($this->config[$property]);

            return false;
        }

        if (!isset($this->config[$property]) || trim($this->config[$property]) === '') {
            if ($mandatory) {
                $this->errors[] = $property.' : must be present';
            }
            unset($this->config[$property]);

            return false;
        }

        return true;
    }

    /**
     * @phpstan-param non-empty-string $property
     */
    private function validateArray(string $property, bool $mandatory = false): bool
    {
        if (isset($this->config[$property]) && !is_array($this->config[$property])) {
            $this->errors[] = $property.' : should be an array, '.gettype($this->config[$property]).' given';
            unset($this->config[$property]);

            return false;
        }

        if (!isset($this->config[$property]) || !count($this->config[$property])) {
            if ($mandatory) {
                $this->errors[] = $property.' : must be present and contain at least one element';
            }
            unset($this->config[$property]);

            return false;
        }

        return true;
    }

    /**
     * @phpstan-param non-empty-string      $property
     * @phpstan-param non-empty-string|null $regex
     */
    private function validateFlatArray(string $property, ?string $regex = null, bool $mandatory = false): bool
    {
        if (!$this->validateArray($property, $mandatory)) {
            return false;
        }

        $pass = true;
        foreach ($this->config[$property] as $key => $value) {
            if (!is_string($value) && !is_numeric($value)) {
                $this->errors[] = $property.'.'.$key.' : must be a string or int, '.gettype($value).' given';
                unset($this->config[$property][$key]);
                $pass = false;

                continue;
            }

            if ($regex && !Preg::isMatch('{^'.$regex.'$}u', (string) $value)) {
                $this->warnings[] = $property.'.'.$key.' : invalid value ('.$value.'), must match '.$regex;
                unset($this->config[$property][$key]);
                $pass = false;
            }
        }

        return $pass;
    }

    /**
     * @phpstan-param non-empty-string $property
     */
    private function validateUrl(string $property, bool $mandatory = false): bool
    {
        if (!$this->validateString($property, $mandatory)) {
            return false;
        }

        if (!$this->filterUrl($this->config[$property])) {
            $this->warnings[] = $property.' : invalid value ('.$this->config[$property].'), must be an http/https URL';
            unset($this->config[$property]);

            return false;
        }

        return true;
    }

    /**
     * @param mixed    $value
     * @param string[] $schemes
     */
    private function filterUrl($value, array $schemes = ['http', 'https']): bool
    {
        if ($value === '') {
            return true;
        }

        $bits = parse_url($value);
        if (empty($bits['scheme']) || empty($bits['host'])) {
            return false;
        }

        if (!in_array($bits['scheme'], $schemes, true)) {
            return false;
        }

        return true;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Loader;

use Composer\Package\BasePackage;
use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Package\RootAliasPackage;
use Composer\Pcre\Preg;
use Composer\Repository\RepositoryFactory;
use Composer\Package\Version\VersionGuesser;
use Composer\Package\Version\VersionParser;
use Composer\Package\RootPackage;
use Composer\Repository\RepositoryManager;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;

/**
 * ArrayLoader built for the sole purpose of loading the root package
 *
 * Sets additional defaults and loads repositories
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class RootPackageLoader extends ArrayLoader
{
    /**
     * @var RepositoryManager
     */
    private $manager;

    /**
     * @var Config
     */
    private $config;

    /**
     * @var VersionGuesser
     */
    private $versionGuesser;

    /**
     * @var IOInterface|null
     */
    private $io;

    public function __construct(RepositoryManager $manager, Config $config, ?VersionParser $parser = null, ?VersionGuesser $versionGuesser = null, ?IOInterface $io = null)
    {
        parent::__construct($parser);

        $this->manager = $manager;
        $this->config = $config;
        if (null === $versionGuesser) {
            $processExecutor = new ProcessExecutor($io);
            $processExecutor->enableAsync();
            $versionGuesser = new VersionGuesser($config, $processExecutor, $this->versionParser);
        }
        $this->versionGuesser = $versionGuesser;
        $this->io = $io;
    }

    /**
     * @inheritDoc
     *
     * @return RootPackage|RootAliasPackage
     *
     * @phpstan-param class-string<RootPackage> $class
     */
    public function load(array $config, string $class = 'Composer\Package\RootPackage', ?string $cwd = null): BasePackage
    {
        if ($class !== 'Composer\Package\RootPackage') {
            trigger_error('The $class arg is deprecated, please reach out to Composer maintainers ASAP if you still need this.', E_USER_DEPRECATED);
        }

        if (!isset($config['name'])) {
            $config['name'] = '__root__';
        } elseif ($err = ValidatingArrayLoader::hasPackageNamingError($config['name'])) {
            throw new \RuntimeException('Your package name '.$err);
        }
        $autoVersioned = false;
        if (!isset($config['version'])) {
            $commit = null;

            // override with env var if available
            if (Platform::getEnv('COMPOSER_ROOT_VERSION')) {
                $config['version'] = $this->versionGuesser->getRootVersionFromEnv();
            } else {
                $versionData = $this->versionGuesser->guessVersion($config, $cwd ?? Platform::getCwd(true));
                if ($versionData) {
                    $config['version'] = $versionData['pretty_version'];
                    $config['version_normalized'] = $versionData['version'];
                    $commit = $versionData['commit'];
                }
            }

            if (!isset($config['version'])) {
                if ($this->io !== null && $config['name'] !== '__root__' && 'project' !== ($config['type'] ?? '')) {
                    $this->io->warning(
                        sprintf(
                            "Composer could not detect the root package (%s) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version",
                            $config['name']
                        )
                    );
                }
                $config['version'] = '1.0.0';
                $autoVersioned = true;
            }

            if ($commit) {
                $config['source'] = [
                    'type' => '',
                    'url' => '',
                    'reference' => $commit,
                ];
                $config['dist'] = [
                    'type' => '',
                    'url' => '',
                    'reference' => $commit,
                ];
            }
        }

        /** @var RootPackage|RootAliasPackage $package */
        $package = parent::load($config, $class);
        if ($package instanceof RootAliasPackage) {
            $realPackage = $package->getAliasOf();
        } else {
            $realPackage = $package;
        }

        if (!$realPackage instanceof RootPackage) {
            throw new \LogicException('Expecting a Composer\Package\RootPackage at this point');
        }

        if ($autoVersioned) {
            $realPackage->replaceVersion($realPackage->getVersion(), RootPackage::DEFAULT_PRETTY_VERSION);
        }

        if (isset($config['minimum-stability'])) {
            $realPackage->setMinimumStability(VersionParser::normalizeStability($config['minimum-stability']));
        }

        $aliases = [];
        $stabilityFlags = [];
        $references = [];
        foreach (['require', 'require-dev'] as $linkType) {
            if (isset($config[$linkType])) {
                $linkInfo = BasePackage::$supportedLinkTypes[$linkType];
                $method = 'get'.ucfirst($linkInfo['method']);
                $links = [];
                foreach ($realPackage->{$method}() as $link) {
                    $links[$link->getTarget()] = $link->getConstraint()->getPrettyString();
                }
                $aliases = $this->extractAliases($links, $aliases);
                $stabilityFlags = self::extractStabilityFlags($links, $realPackage->getMinimumStability(), $stabilityFlags);
                $references = self::extractReferences($links, $references);

                if (isset($links[$config['name']])) {
                    throw new \RuntimeException(sprintf('Root package \'%s\' cannot require itself in its composer.json' . PHP_EOL .
                                'Did you accidentally name your root package after an external package?', $config['name']));
                }
            }
        }

        foreach (array_keys(BasePackage::$supportedLinkTypes) as $linkType) {
            if (isset($config[$linkType])) {
                foreach ($config[$linkType] as $linkName => $constraint) {
                    if ($err = ValidatingArrayLoader::hasPackageNamingError($linkName, true)) {
                        throw new \RuntimeException($linkType.'.'.$err);
                    }
                }
            }
        }

        $realPackage->setAliases($aliases);
        $realPackage->setStabilityFlags($stabilityFlags);
        $realPackage->setReferences($references);

        if (isset($config['prefer-stable'])) {
            $realPackage->setPreferStable((bool) $config['prefer-stable']);
        }

        if (isset($config['config'])) {
            $realPackage->setConfig($config['config']);
        }

        $repos = RepositoryFactory::defaultRepos(null, $this->config, $this->manager);
        foreach ($repos as $repo) {
            $this->manager->addRepository($repo);
        }
        $realPackage->setRepositories($this->config->getRepositories());

        return $package;
    }

    /**
     * @param array<string, string>                                                                  $requires
     * @param list<array{package: string, version: string, alias: string, alias_normalized: string}> $aliases
     *
     * @return list<array{package: string, version: string, alias: string, alias_normalized: string}>
     */
    private function extractAliases(array $requires, array $aliases): array
    {
        foreach ($requires as $reqName => $reqVersion) {
            if (Preg::isMatchStrictGroups('{(?:^|\| *|, *)([^,\s#|]+)(?:#[^ ]+)? +as +([^,\s|]+)(?:$| *\|| *,)}', $reqVersion, $match)) {
                $aliases[] = [
                    'package' => strtolower($reqName),
                    'version' => $this->versionParser->normalize($match[1], $reqVersion),
                    'alias' => $match[2],
                    'alias_normalized' => $this->versionParser->normalize($match[2], $reqVersion),
                ];
            } elseif (strpos($reqVersion, ' as ') !== false) {
                throw new \UnexpectedValueException('Invalid alias definition in "'.$reqName.'": "'.$reqVersion.'". Aliases should be in the form "exact-version as other-exact-version".');
            }
        }

        return $aliases;
    }

    /**
     * @internal
     *
     * @param array<string, string> $requires
     * @param array<string, int>    $stabilityFlags
     * @param key-of<BasePackage::STABILITIES> $minimumStability
     *
     * @return array<string, int>
     *
     * @phpstan-param array<string, BasePackage::STABILITY_*> $stabilityFlags
     * @phpstan-return array<string, BasePackage::STABILITY_*>
     */
    public static function extractStabilityFlags(array $requires, string $minimumStability, array $stabilityFlags): array
    {
        $stabilities = BasePackage::STABILITIES;
        $minimumStability = $stabilities[$minimumStability];
        foreach ($requires as $reqName => $reqVersion) {
            $constraints = [];

            // extract all sub-constraints in case it is an OR/AND multi-constraint
            $orSplit = Preg::split('{\s*\|\|?\s*}', trim($reqVersion));
            foreach ($orSplit as $orConstraint) {
                $andSplit = Preg::split('{(?<!^|as|[=>< ,]) *(?<!-)[, ](?!-) *(?!,|as|$)}', $orConstraint);
                foreach ($andSplit as $andConstraint) {
                    $constraints[] = $andConstraint;
                }
            }

            // parse explicit stability flags to the most unstable
            $matched = false;
            foreach ($constraints as $constraint) {
                if (Preg::isMatchStrictGroups('{^[^@]*?@('.implode('|', array_keys($stabilities)).')$}i', $constraint, $match)) {
                    $name = strtolower($reqName);
                    $stability = $stabilities[VersionParser::normalizeStability($match[1])];

                    if (isset($stabilityFlags[$name]) && $stabilityFlags[$name] > $stability) {
                        continue;
                    }
                    $stabilityFlags[$name] = $stability;
                    $matched = true;
                }
            }

            if ($matched) {
                continue;
            }

            foreach ($constraints as $constraint) {
                // infer flags for requirements that have an explicit -dev or -beta version specified but only
                // for those that are more unstable than the minimumStability or existing flags
                $reqVersion = Preg::replace('{^([^,\s@]+) as .+$}', '$1', $constraint);
                if (Preg::isMatch('{^[^,\s@]+$}', $reqVersion) && 'stable' !== ($stabilityName = VersionParser::parseStability($reqVersion))) {
                    $name = strtolower($reqName);
                    $stability = $stabilities[$stabilityName];
                    if ((isset($stabilityFlags[$name]) && $stabilityFlags[$name] > $stability) || ($minimumStability > $stability)) {
                        continue;
                    }
                    $stabilityFlags[$name] = $stability;
                }
            }
        }

        return $stabilityFlags;
    }

    /**
     * @internal
     *
     * @param array<string, string> $requires
     * @param array<string, string> $references
     *
     * @return array<string, string>
     */
    public static function extractReferences(array $requires, array $references): array
    {
        foreach ($requires as $reqName => $reqVersion) {
            $reqVersion = Preg::replace('{^([^,\s@]+) as .+$}', '$1', $reqVersion);
            if (Preg::isMatchStrictGroups('{^[^,\s@]+?#([a-f0-9]+)$}', $reqVersion, $match) && 'dev' === VersionParser::parseStability($reqVersion)) {
                $name = strtolower($reqName);
                $references[$name] = $match[1];
            }
        }

        return $references;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Loader;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class InvalidPackageException extends \Exception
{
    /** @var list<string> */
    private $errors;
    /** @var list<string> */
    private $warnings;
    /** @var mixed[] package config */
    private $data;

    /**
     * @param list<string> $errors
     * @param list<string> $warnings
     * @param mixed[]  $data
     */
    public function __construct(array $errors, array $warnings, array $data)
    {
        $this->errors = $errors;
        $this->warnings = $warnings;
        $this->data = $data;
        parent::__construct("Invalid package information: \n".implode("\n", array_merge($errors, $warnings)));
    }

    /**
     * @return mixed[]
     */
    public function getData(): array
    {
        return $this->data;
    }

    /**
     * @return list<string>
     */
    public function getErrors(): array
    {
        return $this->errors;
    }

    /**
     * @return list<string>
     */
    public function getWarnings(): array
    {
        return $this->warnings;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Loader;

use Composer\Package\BasePackage;
use Composer\Package\CompleteAliasPackage;
use Composer\Package\CompletePackage;
use Composer\Package\RootPackage;
use Composer\Package\PackageInterface;
use Composer\Package\CompletePackageInterface;
use Composer\Package\Link;
use Composer\Package\RootAliasPackage;
use Composer\Package\Version\VersionParser;
use Composer\Pcre\Preg;

/**
 * @author Konstantin Kudryashiv <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ArrayLoader implements LoaderInterface
{
    /** @var VersionParser */
    protected $versionParser;
    /** @var bool */
    protected $loadOptions;

    public function __construct(?VersionParser $parser = null, bool $loadOptions = false)
    {
        if (!$parser) {
            $parser = new VersionParser;
        }
        $this->versionParser = $parser;
        $this->loadOptions = $loadOptions;
    }

    /**
     * @inheritDoc
     */
    public function load(array $config, string $class = 'Composer\Package\CompletePackage'): BasePackage
    {
        if ($class !== 'Composer\Package\CompletePackage' && $class !== 'Composer\Package\RootPackage') {
            trigger_error('The $class arg is deprecated, please reach out to Composer maintainers ASAP if you still need this.', E_USER_DEPRECATED);
        }

        $package = $this->createObject($config, $class);

        foreach (BasePackage::$supportedLinkTypes as $type => $opts) {
            if (!isset($config[$type]) || !is_array($config[$type])) {
                continue;
            }
            $method = 'set'.ucfirst($opts['method']);
            $package->{$method}(
                $this->parseLinks(
                    $package->getName(),
                    $package->getPrettyVersion(),
                    $opts['method'],
                    $config[$type]
                )
            );
        }

        $package = $this->configureObject($package, $config);

        return $package;
    }

    /**
     * @param array<array<mixed>> $versions
     *
     * @return list<CompletePackage|CompleteAliasPackage>
     */
    public function loadPackages(array $versions): array
    {
        $packages = [];
        $linkCache = [];

        foreach ($versions as $version) {
            $package = $this->createObject($version, 'Composer\Package\CompletePackage');

            $this->configureCachedLinks($linkCache, $package, $version);
            $package = $this->configureObject($package, $version);

            $packages[] = $package;
        }

        return $packages;
    }

    /**
     * @template PackageClass of CompletePackage
     *
     * @param mixed[] $config package data
     * @param string  $class  FQCN to be instantiated
     *
     * @return CompletePackage|RootPackage
     *
     * @phpstan-param class-string<PackageClass> $class
     */
    private function createObject(array $config, string $class): CompletePackage
    {
        if (!isset($config['name'])) {
            throw new \UnexpectedValueException('Unknown package has no name defined ('.json_encode($config).').');
        }
        if (!isset($config['version']) || !is_scalar($config['version'])) {
            throw new \UnexpectedValueException('Package '.$config['name'].' has no version defined.');
        }
        if (!is_string($config['version'])) {
            $config['version'] = (string) $config['version'];
        }

        // handle already normalized versions
        if (isset($config['version_normalized']) && is_string($config['version_normalized'])) {
            $version = $config['version_normalized'];

            // handling of existing repos which need to remain composer v1 compatible, in case the version_normalized contained VersionParser::DEFAULT_BRANCH_ALIAS, we renormalize it
            if ($version === VersionParser::DEFAULT_BRANCH_ALIAS) {
                $version = $this->versionParser->normalize($config['version']);
            }
        } else {
            $version = $this->versionParser->normalize($config['version']);
        }

        return new $class($config['name'], $version, $config['version']);
    }

    /**
     * @param CompletePackage $package
     * @param mixed[]         $config package data
     *
     * @return RootPackage|RootAliasPackage|CompletePackage|CompleteAliasPackage
     */
    private function configureObject(PackageInterface $package, array $config): BasePackage
    {
        if (!$package instanceof CompletePackage) {
            throw new \LogicException('ArrayLoader expects instances of the Composer\Package\CompletePackage class to function correctly');
        }

        $package->setType(isset($config['type']) ? strtolower($config['type']) : 'library');

        if (isset($config['target-dir'])) {
            $package->setTargetDir($config['target-dir']);
        }

        if (isset($config['extra']) && \is_array($config['extra'])) {
            $package->setExtra($config['extra']);
        }

        if (isset($config['bin'])) {
            if (!\is_array($config['bin'])) {
                $config['bin'] = [$config['bin']];
            }
            foreach ($config['bin'] as $key => $bin) {
                $config['bin'][$key] = ltrim($bin, '/');
            }
            $package->setBinaries($config['bin']);
        }

        if (isset($config['installation-source'])) {
            $package->setInstallationSource($config['installation-source']);
        }

        if (isset($config['default-branch']) && $config['default-branch'] === true) {
            $package->setIsDefaultBranch(true);
        }

        if (isset($config['source'])) {
            if (!isset($config['source']['type'], $config['source']['url'], $config['source']['reference'])) {
                throw new \UnexpectedValueException(sprintf(
                    "Package %s's source key should be specified as {\"type\": ..., \"url\": ..., \"reference\": ...},\n%s given.",
                    $config['name'],
                    json_encode($config['source'])
                ));
            }
            $package->setSourceType($config['source']['type']);
            $package->setSourceUrl($config['source']['url']);
            $package->setSourceReference(isset($config['source']['reference']) ? (string) $config['source']['reference'] : null);
            if (isset($config['source']['mirrors'])) {
                $package->setSourceMirrors($config['source']['mirrors']);
            }
        }

        if (isset($config['dist'])) {
            if (!isset($config['dist']['type'], $config['dist']['url'])) {
                throw new \UnexpectedValueException(sprintf(
                    "Package %s's dist key should be specified as ".
                    "{\"type\": ..., \"url\": ..., \"reference\": ..., \"shasum\": ...},\n%s given.",
                    $config['name'],
                    json_encode($config['dist'])
                ));
            }
            $package->setDistType($config['dist']['type']);
            $package->setDistUrl($config['dist']['url']);
            $package->setDistReference(isset($config['dist']['reference']) ? (string) $config['dist']['reference'] : null);
            $package->setDistSha1Checksum($config['dist']['shasum'] ?? null);
            if (isset($config['dist']['mirrors'])) {
                $package->setDistMirrors($config['dist']['mirrors']);
            }
        }

        if (isset($config['suggest']) && \is_array($config['suggest'])) {
            foreach ($config['suggest'] as $target => $reason) {
                if ('self.version' === trim($reason)) {
                    $config['suggest'][$target] = $package->getPrettyVersion();
                }
            }
            $package->setSuggests($config['suggest']);
        }

        if (isset($config['autoload'])) {
            $package->setAutoload($config['autoload']);
        }

        if (isset($config['autoload-dev'])) {
            $package->setDevAutoload($config['autoload-dev']);
        }

        if (isset($config['include-path'])) {
            $package->setIncludePaths($config['include-path']);
        }

        if (isset($config['php-ext'])) {
            $package->setPhpExt($config['php-ext']);
        }

        if (!empty($config['time'])) {
            $time = Preg::isMatch('/^\d++$/D', $config['time']) ? '@'.$config['time'] : $config['time'];

            try {
                $date = new \DateTime($time, new \DateTimeZone('UTC'));
                $package->setReleaseDate($date);
            } catch (\Exception $e) {
            }
        }

        if (!empty($config['notification-url'])) {
            $package->setNotificationUrl($config['notification-url']);
        }

        if ($package instanceof CompletePackageInterface) {
            if (!empty($config['archive']['name'])) {
                $package->setArchiveName($config['archive']['name']);
            }
            if (!empty($config['archive']['exclude'])) {
                $package->setArchiveExcludes($config['archive']['exclude']);
            }

            if (isset($config['scripts']) && \is_array($config['scripts'])) {
                foreach ($config['scripts'] as $event => $listeners) {
                    $config['scripts'][$event] = (array) $listeners;
                }
                foreach (['composer', 'php', 'putenv'] as $reserved) {
                    if (isset($config['scripts'][$reserved])) {
                        trigger_error('The `'.$reserved.'` script name is reserved for internal use, please avoid defining it', E_USER_DEPRECATED);
                    }
                }
                $package->setScripts($config['scripts']);
            }

            if (!empty($config['description']) && \is_string($config['description'])) {
                $package->setDescription($config['description']);
            }

            if (!empty($config['homepage']) && \is_string($config['homepage'])) {
                $package->setHomepage($config['homepage']);
            }

            if (!empty($config['keywords']) && \is_array($config['keywords'])) {
                $package->setKeywords(array_map('strval', $config['keywords']));
            }

            if (!empty($config['license'])) {
                $package->setLicense(\is_array($config['license']) ? $config['license'] : [$config['license']]);
            }

            if (!empty($config['authors']) && \is_array($config['authors'])) {
                $package->setAuthors($config['authors']);
            }

            if (isset($config['support']) && \is_array($config['support'])) {
                $package->setSupport($config['support']);
            }

            if (!empty($config['funding']) && \is_array($config['funding'])) {
                $package->setFunding($config['funding']);
            }

            if (isset($config['abandoned'])) {
                $package->setAbandoned($config['abandoned']);
            }
        }

        if ($this->loadOptions && isset($config['transport-options'])) {
            $package->setTransportOptions($config['transport-options']);
        }

        if ($aliasNormalized = $this->getBranchAlias($config)) {
            $prettyAlias = Preg::replace('{(\.9{7})+}', '.x', $aliasNormalized);

            if ($package instanceof RootPackage) {
                return new RootAliasPackage($package, $aliasNormalized, $prettyAlias);
            }

            return new CompleteAliasPackage($package, $aliasNormalized, $prettyAlias);
        }

        return $package;
    }

    /**
     * @param array<string, array<string, array<int|string, array<int|string, array{string, Link}>>>> $linkCache
     * @param mixed[]                                                                             $config
     */
    private function configureCachedLinks(array &$linkCache, PackageInterface $package, array $config): void
    {
        $name = $package->getName();
        $prettyVersion = $package->getPrettyVersion();

        foreach (BasePackage::$supportedLinkTypes as $type => $opts) {
            if (isset($config[$type])) {
                $method = 'set'.ucfirst($opts['method']);

                $links = [];
                foreach ($config[$type] as $prettyTarget => $constraint) {
                    $target = strtolower($prettyTarget);

                    // recursive links are not supported
                    if ($target === $name) {
                        continue;
                    }

                    if ($constraint === 'self.version') {
                        $links[$target] = $this->createLink($name, $prettyVersion, $opts['method'], $target, $constraint);
                    } else {
                        if (!isset($linkCache[$name][$type][$target][$constraint])) {
                            $linkCache[$name][$type][$target][$constraint] = [$target, $this->createLink($name, $prettyVersion, $opts['method'], $target, $constraint)];
                        }

                        [$target, $link] = $linkCache[$name][$type][$target][$constraint];
                        $links[$target] = $link;
                    }
                }

                $package->{$method}($links);
            }
        }
    }

    /**
     * @param  string                    $source        source package name
     * @param  string                    $sourceVersion source package version (pretty version ideally)
     * @param  string                    $description   link description (e.g. requires, replaces, ..)
     * @param  array<string|int, string> $links         array of package name => constraint mappings
     *
     * @return Link[]
     *
     * @phpstan-param Link::TYPE_* $description
     */
    public function parseLinks(string $source, string $sourceVersion, string $description, array $links): array
    {
        $res = [];
        foreach ($links as $target => $constraint) {
            if (!is_string($constraint)) {
                continue;
            }
            $target = strtolower((string) $target);
            $res[$target] = $this->createLink($source, $sourceVersion, $description, $target, $constraint);
        }

        return $res;
    }

    /**
     * @param  string       $source           source package name
     * @param  string       $sourceVersion    source package version (pretty version ideally)
     * @param  Link::TYPE_* $description      link description (e.g. requires, replaces, ..)
     * @param  string       $target           target package name
     * @param  string       $prettyConstraint constraint string
     */
    private function createLink(string $source, string $sourceVersion, string $description, string $target, string $prettyConstraint): Link
    {
        if (!\is_string($prettyConstraint)) {
            throw new \UnexpectedValueException('Link constraint in '.$source.' '.$description.' > '.$target.' should be a string, got '.\gettype($prettyConstraint) . ' (' . var_export($prettyConstraint, true) . ')');
        }
        if ('self.version' === $prettyConstraint) {
            $parsedConstraint = $this->versionParser->parseConstraints($sourceVersion);
        } else {
            $parsedConstraint = $this->versionParser->parseConstraints($prettyConstraint);
        }

        return new Link($source, $target, $parsedConstraint, $description, $prettyConstraint);
    }

    /**
     * Retrieves a branch alias (dev-master => 1.0.x-dev for example) if it exists
     *
     * @param mixed[] $config the entire package config
     *
     * @return string|null normalized version of the branch alias or null if there is none
     */
    public function getBranchAlias(array $config): ?string
    {
        if (!isset($config['version']) || !is_scalar($config['version'])) {
            throw new \UnexpectedValueException('no/invalid version defined');
        }
        if (!is_string($config['version'])) {
            $config['version'] = (string) $config['version'];
        }

        if (strpos($config['version'], 'dev-') !== 0 && '-dev' !== substr($config['version'], -4)) {
            return null;
        }

        if (isset($config['extra']['branch-alias']) && \is_array($config['extra']['branch-alias'])) {
            foreach ($config['extra']['branch-alias'] as $sourceBranch => $targetBranch) {
                $sourceBranch = (string) $sourceBranch;

                // ensure it is an alias to a -dev package
                if ('-dev' !== substr($targetBranch, -4)) {
                    continue;
                }

                // normalize without -dev and ensure it's a numeric branch that is parseable
                if ($targetBranch === VersionParser::DEFAULT_BRANCH_ALIAS) {
                    $validatedTargetBranch = VersionParser::DEFAULT_BRANCH_ALIAS;
                } else {
                    $validatedTargetBranch = $this->versionParser->normalizeBranch(substr($targetBranch, 0, -4));
                }
                if ('-dev' !== substr($validatedTargetBranch, -4)) {
                    continue;
                }

                // ensure that it is the current branch aliasing itself
                if (strtolower($config['version']) !== strtolower($sourceBranch)) {
                    continue;
                }

                // If using numeric aliases ensure the alias is a valid subversion
                if (($sourcePrefix = $this->versionParser->parseNumericAliasPrefix($sourceBranch))
                    && ($targetPrefix = $this->versionParser->parseNumericAliasPrefix($targetBranch))
                    && (stripos($targetPrefix, $sourcePrefix) !== 0)
                ) {
                    continue;
                }

                return $validatedTargetBranch;
            }
        }

        if (
            isset($config['default-branch'])
            && $config['default-branch'] === true
            && false === $this->versionParser->parseNumericAliasPrefix(Preg::replace('{^v}', '', $config['version']))
        ) {
            return VersionParser::DEFAULT_BRANCH_ALIAS;
        }

        return null;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Loader;

use Composer\Json\JsonFile;
use Composer\Package\BasePackage;
use Composer\Package\CompletePackage;
use Composer\Package\CompleteAliasPackage;
use Composer\Package\RootPackage;
use Composer\Package\RootAliasPackage;

/**
 * @author Konstantin Kudryashiv <ever.zet@gmail.com>
 */
class JsonLoader
{
    /** @var LoaderInterface */
    private $loader;

    public function __construct(LoaderInterface $loader)
    {
        $this->loader = $loader;
    }

    /**
     * @param  string|JsonFile                      $json A filename, json string or JsonFile instance to load the package from
     * @return CompletePackage|CompleteAliasPackage|RootPackage|RootAliasPackage
     */
    public function load($json): BasePackage
    {
        if ($json instanceof JsonFile) {
            $config = $json->read();
        } elseif (file_exists($json)) {
            $config = JsonFile::parseJson(file_get_contents($json), $json);
        } elseif (is_string($json)) {
            $config = JsonFile::parseJson($json);
        } else {
            throw new \InvalidArgumentException(sprintf(
                "JsonLoader: Unknown \$json parameter %s. Please report at https://github.com/composer/composer/issues/new.",
                gettype($json)
            ));
        }

        return $this->loader->load($config);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Loader;

use Composer\Package\CompletePackage;
use Composer\Package\CompleteAliasPackage;
use Composer\Package\RootAliasPackage;
use Composer\Package\RootPackage;
use Composer\Package\BasePackage;

/**
 * Defines a loader that takes an array to create package instances
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface LoaderInterface
{
    /**
     * Converts a package from an array to a real instance
     *
     * @param  mixed[] $config package data
     * @param  string  $class  FQCN to be instantiated
     *
     * @return CompletePackage|CompleteAliasPackage|RootPackage|RootAliasPackage
     *
     * @phpstan-param class-string<CompletePackage|RootPackage> $class
     */
    public function load(array $config, string $class = 'Composer\Package\CompletePackage'): BasePackage;
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Comparer;

use Composer\Util\Platform;

/**
 * class Comparer
 *
 * @author Hector Prats <hectorpratsortega@gmail.com>
 */
class Comparer
{
    /** @var string Source directory */
    private $source;
    /** @var string Target directory */
    private $update;
    /** @var array{changed?: string[], removed?: string[], added?: string[]} */
    private $changed;

    public function setSource(string $source): void
    {
        $this->source = $source;
    }

    public function setUpdate(string $update): void
    {
        $this->update = $update;
    }

    /**
     * @return array{changed?: string[], removed?: string[], added?: string[]}|false false if no change
     */
    public function getChanged(bool $explicated = false)
    {
        $changed = $this->changed;
        if (!count($changed)) {
            return false;
        }
        if ($explicated) {
            foreach ($changed as $sectionKey => $itemSection) {
                foreach ($itemSection as $itemKey => $item) {
                    $changed[$sectionKey][$itemKey] = $item.' ('.$sectionKey.')';
                }
            }
        }

        return $changed;
    }

    /**
     * @return string empty string if no changes
     */
    public function getChangedAsString(bool $toString = false, bool $explicated = false): string
    {
        $changed = $this->getChanged($explicated);
        if (false === $changed) {
            return '';
        }

        $strings = [];
        foreach ($changed as $sectionKey => $itemSection) {
            foreach ($itemSection as $itemKey => $item) {
                $strings[] = $item."\r\n";
            }
        }

        return trim(implode("\r\n", $strings));
    }

    public function doCompare(): void
    {
        $source = [];
        $destination = [];
        $this->changed = [];
        $currentDirectory = Platform::getCwd();
        chdir($this->source);
        $source = $this->doTree('.', $source);
        if (!is_array($source)) {
            return;
        }
        chdir($currentDirectory);
        chdir($this->update);
        $destination = $this->doTree('.', $destination);
        if (!is_array($destination)) {
            exit;
        }
        chdir($currentDirectory);
        foreach ($source as $dir => $value) {
            foreach ($value as $file => $hash) {
                if (isset($destination[$dir][$file])) {
                    if ($hash !== $destination[$dir][$file]) {
                        $this->changed['changed'][] = $dir.'/'.$file;
                    }
                } else {
                    $this->changed['removed'][] = $dir.'/'.$file;
                }
            }
        }
        foreach ($destination as $dir => $value) {
            foreach ($value as $file => $hash) {
                if (!isset($source[$dir][$file])) {
                    $this->changed['added'][] = $dir.'/'.$file;
                }
            }
        }
    }

    /**
     * @param mixed[] $array
     *
     * @return array<string, array<string, string|false>>|false
     */
    private function doTree(string $dir, array &$array)
    {
        if ($dh = opendir($dir)) {
            while ($file = readdir($dh)) {
                if ($file !== '.' && $file !== '..') {
                    if (is_link($dir.'/'.$file)) {
                        $array[$dir][$file] = readlink($dir.'/'.$file);
                    } elseif (is_dir($dir.'/'.$file)) {
                        if (!count($array)) {
                            $array[0] = 'Temp';
                        }
                        if (!$this->doTree($dir.'/'.$file, $array)) {
                            return false;
                        }
                    } elseif (is_file($dir.'/'.$file) && filesize($dir.'/'.$file)) {
                        $array[$dir][$file] = hash_file(\PHP_VERSION_ID > 80100 ? 'xxh3' : 'sha1', $dir.'/'.$file);
                    }
                }
            }
            if (count($array) > 1 && isset($array['0'])) {
                unset($array['0']);
            }

            return $array;
        }

        return false;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package;

use Composer\Repository\RepositoryInterface;
use Composer\Repository\PlatformRepository;

/**
 * Base class for packages providing name storage and default match implementation
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
abstract class BasePackage implements PackageInterface
{
    /**
     * @phpstan-var array<non-empty-string, array{description: string, method: Link::TYPE_*}>
     * @internal
     */
    public static $supportedLinkTypes = [
        'require' => ['description' => 'requires', 'method' => Link::TYPE_REQUIRE],
        'conflict' => ['description' => 'conflicts', 'method' => Link::TYPE_CONFLICT],
        'provide' => ['description' => 'provides', 'method' => Link::TYPE_PROVIDE],
        'replace' => ['description' => 'replaces', 'method' => Link::TYPE_REPLACE],
        'require-dev' => ['description' => 'requires (for development)', 'method' => Link::TYPE_DEV_REQUIRE],
    ];

    public const STABILITY_STABLE = 0;
    public const STABILITY_RC = 5;
    public const STABILITY_BETA = 10;
    public const STABILITY_ALPHA = 15;
    public const STABILITY_DEV = 20;

    public const STABILITIES = [
        'stable' => self::STABILITY_STABLE,
        'RC' => self::STABILITY_RC,
        'beta' => self::STABILITY_BETA,
        'alpha' => self::STABILITY_ALPHA,
        'dev' => self::STABILITY_DEV,
    ];

    /**
     * @deprecated
     * @readonly
     * @var array<key-of<BasePackage::STABILITIES>, self::STABILITY_*>
     * @phpstan-ignore property.readOnlyByPhpDocDefaultValue
     */
    public static $stabilities = self::STABILITIES;

    /**
     * READ-ONLY: The package id, public for fast access in dependency solver
     * @var int
     * @internal
     */
    public $id;
    /** @var string */
    protected $name;
    /** @var string */
    protected $prettyName;
    /** @var ?RepositoryInterface */
    protected $repository = null;

    /**
     * All descendants' constructors should call this parent constructor
     *
     * @param string $name The package's name
     */
    public function __construct(string $name)
    {
        $this->prettyName = $name;
        $this->name = strtolower($name);
        $this->id = -1;
    }

    /**
     * @inheritDoc
     */
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * @inheritDoc
     */
    public function getPrettyName(): string
    {
        return $this->prettyName;
    }

    /**
     * @inheritDoc
     */
    public function getNames($provides = true): array
    {
        $names = [
            $this->getName() => true,
        ];

        if ($provides) {
            foreach ($this->getProvides() as $link) {
                $names[$link->getTarget()] = true;
            }
        }

        foreach ($this->getReplaces() as $link) {
            $names[$link->getTarget()] = true;
        }

        return array_keys($names);
    }

    /**
     * @inheritDoc
     */
    public function setId(int $id): void
    {
        $this->id = $id;
    }

    /**
     * @inheritDoc
     */
    public function getId(): int
    {
        return $this->id;
    }

    /**
     * @inheritDoc
     */
    public function setRepository(RepositoryInterface $repository): void
    {
        if ($this->repository && $repository !== $this->repository) {
            throw new \LogicException(sprintf(
                'Package "%s" cannot be added to repository "%s" as it is already in repository "%s".',
                $this->getPrettyName(),
                $repository->getRepoName(),
                $this->repository->getRepoName()
            ));
        }
        $this->repository = $repository;
    }

    /**
     * @inheritDoc
     */
    public function getRepository(): ?RepositoryInterface
    {
        return $this->repository;
    }

    /**
     * checks if this package is a platform package
     */
    public function isPlatform(): bool
    {
        return $this->getRepository() instanceof PlatformRepository;
    }

    /**
     * Returns package unique name, constructed from name, version and release type.
     */
    public function getUniqueName(): string
    {
        return $this->getName().'-'.$this->getVersion();
    }

    public function equals(PackageInterface $package): bool
    {
        $self = $this;
        if ($this instanceof AliasPackage) {
            $self = $this->getAliasOf();
        }
        if ($package instanceof AliasPackage) {
            $package = $package->getAliasOf();
        }

        return $package === $self;
    }

    /**
     * Converts the package into a readable and unique string
     */
    public function __toString(): string
    {
        return $this->getUniqueName();
    }

    public function getPrettyString(): string
    {
        return $this->getPrettyName().' '.$this->getPrettyVersion();
    }

    /**
     * @inheritDoc
     */
    public function getFullPrettyVersion(bool $truncate = true, int $displayMode = PackageInterface::DISPLAY_SOURCE_REF_IF_DEV): string
    {
        if ($displayMode === PackageInterface::DISPLAY_SOURCE_REF_IF_DEV &&
            (!$this->isDev() || !\in_array($this->getSourceType(), ['hg', 'git']))
        ) {
            return $this->getPrettyVersion();
        }

        switch ($displayMode) {
            case PackageInterface::DISPLAY_SOURCE_REF_IF_DEV:
            case PackageInterface::DISPLAY_SOURCE_REF:
                $reference = $this->getSourceReference();
                break;
            case PackageInterface::DISPLAY_DIST_REF:
                $reference = $this->getDistReference();
                break;
            default:
                throw new \UnexpectedValueException('Display mode '.$displayMode.' is not supported');
        }

        if (null === $reference) {
            return $this->getPrettyVersion();
        }

        // if source reference is a sha1 hash -- truncate
        if ($truncate && \strlen($reference) === 40 && $this->getSourceType() !== 'svn') {
            return $this->getPrettyVersion() . ' ' . substr($reference, 0, 7);
        }

        return $this->getPrettyVersion() . ' ' . $reference;
    }

    /**
     * @phpstan-return self::STABILITY_*
     */
    public function getStabilityPriority(): int
    {
        return self::STABILITIES[$this->getStability()];
    }

    public function __clone()
    {
        $this->repository = null;
        $this->id = -1;
    }

    /**
     * Build a regexp from a package name, expanding * globs as required
     *
     * @param  non-empty-string $wrap         Wrap the cleaned string by the given string
     * @return non-empty-string
     */
    public static function packageNameToRegexp(string $allowPattern, string $wrap = '{^%s$}i'): string
    {
        $cleanedAllowPattern = str_replace('\\*', '.*', preg_quote($allowPattern));

        return sprintf($wrap, $cleanedAllowPattern);
    }

    /**
     * Build a regexp from package names, expanding * globs as required
     *
     * @param string[] $packageNames
     * @param non-empty-string $wrap
     * @return non-empty-string
     */
    public static function packageNamesToRegexp(array $packageNames, string $wrap = '{^(?:%s)$}iD'): string
    {
        $packageNames = array_map(
            static function ($packageName): string {
                return BasePackage::packageNameToRegexp($packageName, '%s');
            },
            $packageNames
        );

        return sprintf($wrap, implode('|', $packageNames));
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Archiver;

use Composer\Util\Filesystem;
use Composer\Util\Platform;
use ZipArchive;

/**
 * @author Jan Prieser <jan@prieser.net>
 */
class ZipArchiver implements ArchiverInterface
{
    /** @var array<string, bool> */
    protected static $formats = [
        'zip' => true,
    ];

    /**
     * @inheritDoc
     */
    public function archive(string $sources, string $target, string $format, array $excludes = [], bool $ignoreFilters = false): string
    {
        $fs = new Filesystem();
        $sourcesRealpath = realpath($sources);
        if (false !== $sourcesRealpath) {
            $sources = $sourcesRealpath;
        }
        unset($sourcesRealpath);
        $sources = $fs->normalizePath($sources);

        $zip = new ZipArchive();
        $res = $zip->open($target, ZipArchive::CREATE);
        if ($res === true) {
            $files = new ArchivableFilesFinder($sources, $excludes, $ignoreFilters);
            foreach ($files as $file) {
                /** @var \Symfony\Component\Finder\SplFileInfo $file */
                $filepath = $file->getPathname();
                $relativePath = $file->getRelativePathname();

                if (Platform::isWindows()) {
                    $relativePath = strtr($relativePath, '\\', '/');
                }

                if ($file->isDir()) {
                    $zip->addEmptyDir($relativePath);
                } else {
                    $zip->addFile($filepath, $relativePath);
                }

                /**
                 * setExternalAttributesName() is only available with libzip 0.11.2 or above
                 */
                if (method_exists($zip, 'setExternalAttributesName')) {
                    $perms = fileperms($filepath);

                    /**
                     * Ensure to preserve the permission umasks for the filepath in the archive.
                     */
                    $zip->setExternalAttributesName($relativePath, ZipArchive::OPSYS_UNIX, $perms << 16);
                }
            }
            if ($zip->close()) {
                if (!file_exists($target)) {
                    // create minimal valid ZIP file (Empty Central Directory + End of Central Directory record)
                    $eocd = pack(
                        'VvvvvVVv',
                        0x06054b50,  // End of central directory signature
                        0,           // Number of this disk
                        0,           // Disk where central directory starts
                        0,           // Number of central directory records on this disk
                        0,           // Total number of central directory records
                        0,           // Size of central directory (bytes)
                        0,           // Offset of start of central directory
                        0            // Comment length
                    );
                    file_put_contents($target, $eocd);
                }

                return $target;
            }
        }
        $message = sprintf(
            "Could not create archive '%s' from '%s': %s",
            $target,
            $sources,
            $zip->getStatusString()
        );
        throw new \RuntimeException($message);
    }

    /**
     * @inheritDoc
     */
    public function supports(string $format, ?string $sourceType): bool
    {
        return isset(static::$formats[$format]) && $this->compressionAvailable();
    }

    private function compressionAvailable(): bool
    {
        return class_exists('ZipArchive');
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Archiver;

use FilterIterator;
use Iterator;
use PharData;
use SplFileInfo;

/**
 * @phpstan-extends FilterIterator<string, SplFileInfo, Iterator<string, SplFileInfo>>
 */
class ArchivableFilesFilter extends FilterIterator
{
    /** @var string[] */
    private $dirs = [];

    /**
     * @return bool true if the current element is acceptable, otherwise false.
     */
    public function accept(): bool
    {
        $file = $this->getInnerIterator()->current();
        if ($file->isDir()) {
            $this->dirs[] = (string) $file;

            return false;
        }

        return true;
    }

    public function addEmptyDir(PharData $phar, string $sources): void
    {
        foreach ($this->dirs as $filepath) {
            $localname = str_replace($sources . "/", '', $filepath);
            $phar->addEmptyDir($localname);
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Archiver;

use Composer\Pcre\Preg;
use Composer\Util\Filesystem;
use FilesystemIterator;
use FilterIterator;
use Iterator;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;

/**
 * A Symfony Finder wrapper which locates files that should go into archives
 *
 * Handles .gitignore, .gitattributes and .hgignore files as well as composer's
 * own exclude rules from composer.json
 *
 * @author Nils Adermann <naderman@naderman.de>
 * @phpstan-extends FilterIterator<string, SplFileInfo, Iterator<string, SplFileInfo>>
 */
class ArchivableFilesFinder extends FilterIterator
{
    /**
     * @var Finder
     */
    protected $finder;

    /**
     * Initializes the internal Symfony Finder with appropriate filters
     *
     * @param string $sources Path to source files to be archived
     * @param string[] $excludes Composer's own exclude rules from composer.json
     * @param bool $ignoreFilters Ignore filters when looking for files
     */
    public function __construct(string $sources, array $excludes, bool $ignoreFilters = false)
    {
        $fs = new Filesystem();

        $sourcesRealPath = realpath($sources);
        if ($sourcesRealPath === false) {
            throw new \RuntimeException('Could not realpath() the source directory "'.$sources.'"');
        }
        $sources = $fs->normalizePath($sourcesRealPath);

        if ($ignoreFilters) {
            $filters = [];
        } else {
            $filters = [
                new GitExcludeFilter($sources),
                new ComposerExcludeFilter($sources, $excludes),
            ];
        }

        $this->finder = new Finder();

        $filter = static function (\SplFileInfo $file) use ($sources, $filters, $fs): bool {
            $realpath = $file->getRealPath();
            if ($realpath === false) {
                return false;
            }
            if ($file->isLink() && strpos($realpath, $sources) !== 0) {
                return false;
            }

            $relativePath = Preg::replace(
                '#^'.preg_quote($sources, '#').'#',
                '',
                $fs->normalizePath($realpath)
            );

            $exclude = false;
            foreach ($filters as $filter) {
                $exclude = $filter->filter($relativePath, $exclude);
            }

            return !$exclude;
        };

        $this->finder
            ->in($sources)
            ->filter($filter)
            ->ignoreVCS(true)
            ->ignoreDotFiles(false)
            ->sortByName();

        parent::__construct($this->finder->getIterator());
    }

    public function accept(): bool
    {
        /** @var SplFileInfo $current */
        $current = $this->getInnerIterator()->current();

        if (!$current->isDir()) {
            return true;
        }

        $iterator = new FilesystemIterator((string) $current, FilesystemIterator::SKIP_DOTS);

        return !$iterator->valid();
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Archiver;

/**
 * An exclude filter which processes composer's own exclude rules
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class ComposerExcludeFilter extends BaseExcludeFilter
{
    /**
     * @param string $sourcePath Directory containing sources to be filtered
     * @param string[] $excludeRules An array of exclude rules from composer.json
     */
    public function __construct(string $sourcePath, array $excludeRules)
    {
        parent::__construct($sourcePath);
        $this->excludePatterns = $this->generatePatterns($excludeRules);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Archiver;

use Composer\Pcre\Preg;

/**
 * An exclude filter that processes gitattributes
 *
 * It respects export-ignore git attributes
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class GitExcludeFilter extends BaseExcludeFilter
{
    /**
     * Parses .gitattributes if it exists
     */
    public function __construct(string $sourcePath)
    {
        parent::__construct($sourcePath);

        if (file_exists($sourcePath.'/.gitattributes')) {
            $this->excludePatterns = array_merge(
                $this->excludePatterns,
                $this->parseLines(
                    file($sourcePath.'/.gitattributes'),
                    [$this, 'parseGitAttributesLine']
                )
            );
        }
    }

    /**
     * Callback parser which finds export-ignore rules in git attribute lines
     *
     * @param string $line A line from .gitattributes
     *
     * @return array{0: string, 1: bool, 2: bool}|null An exclude pattern for filter()
     */
    public function parseGitAttributesLine(string $line): ?array
    {
        $parts = Preg::split('#\s+#', $line);

        if (count($parts) === 2 && $parts[1] === 'export-ignore') {
            return $this->generatePattern($parts[0]);
        }

        if (count($parts) === 2 && $parts[1] === '-export-ignore') {
            return $this->generatePattern('!'.$parts[0]);
        }

        return null;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Archiver;

use PharData;

/**
 * @author Till Klampaeckel <till@php.net>
 * @author Nils Adermann <naderman@naderman.de>
 * @author Matthieu Moquet <matthieu@moquet.net>
 */
class PharArchiver implements ArchiverInterface
{
    /** @var array<string, int> */
    protected static $formats = [
        'zip' => \Phar::ZIP,
        'tar' => \Phar::TAR,
        'tar.gz' => \Phar::TAR,
        'tar.bz2' => \Phar::TAR,
    ];

    /** @var array<string, int> */
    protected static $compressFormats = [
        'tar.gz' => \Phar::GZ,
        'tar.bz2' => \Phar::BZ2,
    ];

    /**
     * @inheritDoc
     */
    public function archive(string $sources, string $target, string $format, array $excludes = [], bool $ignoreFilters = false): string
    {
        $sources = realpath($sources);

        // Phar would otherwise load the file which we don't want
        if (file_exists($target)) {
            unlink($target);
        }

        try {
            $filename = substr($target, 0, strrpos($target, $format) - 1);

            // Check if compress format
            if (isset(static::$compressFormats[$format])) {
                // Current compress format supported base on tar
                $target = $filename . '.tar';
            }

            $phar = new \PharData(
                $target,
                \FilesystemIterator::KEY_AS_PATHNAME | \FilesystemIterator::CURRENT_AS_FILEINFO,
                '',
                static::$formats[$format]
            );
            $files = new ArchivableFilesFinder($sources, $excludes, $ignoreFilters);
            $filesOnly = new ArchivableFilesFilter($files);
            $phar->buildFromIterator($filesOnly, $sources);
            $filesOnly->addEmptyDir($phar, $sources);

            if (!file_exists($target)) {
                $target = $filename . '.' . $format;
                unset($phar);

                if ($format === 'tar') {
                    // create an empty tar file (=10240 null bytes) if the tar file is empty and PharData thus did not write it to disk
                    file_put_contents($target, str_repeat("\0", 10240));
                } elseif ($format === 'zip') {
                    // create minimal valid ZIP file (Empty Central Directory + End of Central Directory record)
                    $eocd = pack(
                        'VvvvvVVv',
                        0x06054b50,  // End of central directory signature
                        0,           // Number of this disk
                        0,           // Disk where central directory starts
                        0,           // Number of central directory records on this disk
                        0,           // Total number of central directory records
                        0,           // Size of central directory (bytes)
                        0,           // Offset of start of central directory
                        0            // Comment length
                    );

                    file_put_contents($target, $eocd);
                } elseif ($format === 'tar.gz' || $format === 'tar.bz2') {
                    if (!PharData::canCompress(static::$compressFormats[$format])) {
                        throw new \RuntimeException(sprintf('Can not compress to %s format', $format));
                    }
                    if ($format === 'tar.gz' && function_exists('gzcompress')) {
                        file_put_contents($target, gzcompress(str_repeat("\0", 10240)));
                    } elseif ($format === 'tar.bz2' && function_exists('bzcompress')) {
                        file_put_contents($target, bzcompress(str_repeat("\0", 10240)));
                    }
                }

                return $target;
            }

            if (isset(static::$compressFormats[$format])) {
                // Check can be compressed?
                if (!PharData::canCompress(static::$compressFormats[$format])) {
                    throw new \RuntimeException(sprintf('Can not compress to %s format', $format));
                }

                // Delete old tar
                unlink($target);

                // Compress the new tar
                $phar->compress(static::$compressFormats[$format]);

                // Make the correct filename
                $target = $filename . '.' . $format;
            }

            return $target;
        } catch (\UnexpectedValueException $e) {
            $message = sprintf(
                "Could not create archive '%s' from '%s': %s",
                $target,
                $sources,
                $e->getMessage()
            );

            throw new \RuntimeException($message, $e->getCode(), $e);
        }
    }

    /**
     * @inheritDoc
     */
    public function supports(string $format, ?string $sourceType): bool
    {
        return isset(static::$formats[$format]);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Archiver;

use Composer\Pcre\Preg;
use Symfony\Component\Finder;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
abstract class BaseExcludeFilter
{
    /**
     * @var string
     */
    protected $sourcePath;

    /**
     * @var array<array{0: non-empty-string, 1: bool, 2: bool}> array of [$pattern, $negate, $stripLeadingSlash] arrays
     */
    protected $excludePatterns;

    /**
     * @param string $sourcePath Directory containing sources to be filtered
     */
    public function __construct(string $sourcePath)
    {
        $this->sourcePath = $sourcePath;
        $this->excludePatterns = [];
    }

    /**
     * Checks the given path against all exclude patterns in this filter
     *
     * Negated patterns overwrite exclude decisions of previous filters.
     *
     * @param string $relativePath The file's path relative to the sourcePath
     * @param bool $exclude Whether a previous filter wants to exclude this file
     *
     * @return bool Whether the file should be excluded
     */
    public function filter(string $relativePath, bool $exclude): bool
    {
        foreach ($this->excludePatterns as $patternData) {
            [$pattern, $negate, $stripLeadingSlash] = $patternData;

            if ($stripLeadingSlash) {
                $path = substr($relativePath, 1);
            } else {
                $path = $relativePath;
            }

            try {
                if (Preg::isMatch($pattern, $path)) {
                    $exclude = !$negate;
                }
            } catch (\RuntimeException $e) {
                // suppressed
            }
        }

        return $exclude;
    }

    /**
     * Processes a file containing exclude rules of different formats per line
     *
     * @param string[] $lines A set of lines to be parsed
     * @param callable $lineParser The parser to be used on each line
     *
     * @return array<array{0: non-empty-string, 1: bool, 2: bool}> Exclude patterns to be used in filter()
     */
    protected function parseLines(array $lines, callable $lineParser): array
    {
        return array_filter(
            array_map(
                static function ($line) use ($lineParser) {
                    $line = trim($line);

                    if (!$line || 0 === strpos($line, '#')) {
                        return null;
                    }

                    return $lineParser($line);
                },
                $lines
            ),
            static function ($pattern): bool {
                return $pattern !== null;
            }
        );
    }

    /**
     * Generates a set of exclude patterns for filter() from gitignore rules
     *
     * @param string[] $rules A list of exclude rules in gitignore syntax
     *
     * @return array<int, array{0: non-empty-string, 1: bool, 2: bool}> Exclude patterns
     */
    protected function generatePatterns(array $rules): array
    {
        $patterns = [];
        foreach ($rules as $rule) {
            $patterns[] = $this->generatePattern($rule);
        }

        return $patterns;
    }

    /**
     * Generates an exclude pattern for filter() from a gitignore rule
     *
     * @param string $rule An exclude rule in gitignore syntax
     *
     * @return array{0: non-empty-string, 1: bool, 2: bool} An exclude pattern
     */
    protected function generatePattern(string $rule): array
    {
        $negate = false;
        $pattern = '';

        if ($rule !== '' && $rule[0] === '!') {
            $negate = true;
            $rule = ltrim($rule, '!');
        }

        $firstSlashPosition = strpos($rule, '/');
        if (0 === $firstSlashPosition) {
            $pattern = '^/';
        } elseif (false === $firstSlashPosition || strlen($rule) - 1 === $firstSlashPosition) {
            $pattern = '/';
        }

        $rule = trim($rule, '/');

        // remove delimiters as well as caret (^) and dollar sign ($) from the regex
        $rule = substr(Finder\Glob::toRegex($rule), 2, -2);

        return ['{'.$pattern.$rule.'(?=$|/)}', $negate, false];
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Archiver;

/**
 * @author Till Klampaeckel <till@php.net>
 * @author Matthieu Moquet <matthieu@moquet.net>
 * @author Nils Adermann <naderman@naderman.de>
 */
interface ArchiverInterface
{
    /**
     * Create an archive from the sources.
     *
     * @param string   $sources       The sources directory
     * @param string   $target        The target file
     * @param string   $format        The format used for archive
     * @param string[] $excludes      A list of patterns for files to exclude
     * @param bool     $ignoreFilters Whether to ignore filters when looking for files
     *
     * @return string The path to the written archive file
     */
    public function archive(string $sources, string $target, string $format, array $excludes = [], bool $ignoreFilters = false): string;

    /**
     * Format supported by the archiver.
     *
     * @param string $format     The archive format
     * @param ?string $sourceType The source type (git, svn, hg, etc.)
     *
     * @return bool true if the format is supported by the archiver
     */
    public function supports(string $format, ?string $sourceType): bool;
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Archiver;

use Composer\Downloader\DownloadManager;
use Composer\Package\RootPackageInterface;
use Composer\Pcre\Preg;
use Composer\Util\Filesystem;
use Composer\Util\Loop;
use Composer\Util\SyncHelper;
use Composer\Json\JsonFile;
use Composer\Package\CompletePackageInterface;

/**
 * @author Matthieu Moquet <matthieu@moquet.net>
 * @author Till Klampaeckel <till@php.net>
 */
class ArchiveManager
{
    /** @var DownloadManager */
    protected $downloadManager;
    /** @var Loop */
    protected $loop;

    /**
     * @var ArchiverInterface[]
     */
    protected $archivers = [];

    /**
     * @var bool
     */
    protected $overwriteFiles = true;

    /**
     * @param DownloadManager $downloadManager A manager used to download package sources
     */
    public function __construct(DownloadManager $downloadManager, Loop $loop)
    {
        $this->downloadManager = $downloadManager;
        $this->loop = $loop;
    }

    public function addArchiver(ArchiverInterface $archiver): void
    {
        $this->archivers[] = $archiver;
    }

    /**
     * Set whether existing archives should be overwritten
     *
     * @param bool $overwriteFiles New setting
     *
     * @return $this
     */
    public function setOverwriteFiles(bool $overwriteFiles): self
    {
        $this->overwriteFiles = $overwriteFiles;

        return $this;
    }

    /**
     * @return array<string, string>
     * @internal
     */
    public function getPackageFilenameParts(CompletePackageInterface $package): array
    {
        $baseName = $package->getArchiveName();
        if (null === $baseName) {
            $baseName = Preg::replace('#[^a-z0-9-_]#i', '-', $package->getName());
        }

        $parts = [
            'base' => $baseName,
        ];

        $distReference = $package->getDistReference();
        if (null !== $distReference && Preg::isMatch('{^[a-f0-9]{40}$}', $distReference)) {
            $parts['dist_reference'] = $distReference;
            $parts['dist_type'] = $package->getDistType();
        } else {
            $parts['version'] = $package->getPrettyVersion();
            $parts['dist_reference'] = $distReference;
        }

        $sourceReference = $package->getSourceReference();
        if (null !== $sourceReference) {
            $parts['source_reference'] = substr(hash('sha1', $sourceReference), 0, 6);
        }

        $parts = array_filter($parts, function (?string $part) {
            return $part !== null;
        });
        foreach ($parts as $key => $part) {
            $parts[$key] = str_replace('/', '-', $part);
        }

        return $parts;
    }

    /**
     * @param array<string, string> $parts
     *
     * @return string
     * @internal
     */
    public function getPackageFilenameFromParts(array $parts): string
    {
        return implode('-', $parts);
    }

    /**
     * Generate a distinct filename for a particular version of a package.
     *
     * @param CompletePackageInterface $package The package to get a name for
     *
     * @return string A filename without an extension
     */
    public function getPackageFilename(CompletePackageInterface $package): string
    {
        return $this->getPackageFilenameFromParts($this->getPackageFilenameParts($package));
    }

    /**
     * Create an archive of the specified package.
     *
     * @param  CompletePackageInterface  $package       The package to archive
     * @param  string                    $format        The format of the archive (zip, tar, ...)
     * @param  string                    $targetDir     The directory where to build the archive
     * @param  string|null               $fileName      The relative file name to use for the archive, or null to generate
     *                                                  the package name. Note that the format will be appended to this name
     * @param  bool                      $ignoreFilters Ignore filters when looking for files in the package
     * @throws \InvalidArgumentException
     * @throws \RuntimeException
     * @return string                    The path of the created archive
     */
    public function archive(CompletePackageInterface $package, string $format, string $targetDir, ?string $fileName = null, bool $ignoreFilters = false): string
    {
        if (empty($format)) {
            throw new \InvalidArgumentException('Format must be specified');
        }

        // Search for the most appropriate archiver
        $usableArchiver = null;
        foreach ($this->archivers as $archiver) {
            if ($archiver->supports($format, $package->getSourceType())) {
                $usableArchiver = $archiver;
                break;
            }
        }

        // Checks the format/source type are supported before downloading the package
        if (null === $usableArchiver) {
            throw new \RuntimeException(sprintf('No archiver found to support %s format', $format));
        }

        $filesystem = new Filesystem();

        if ($package instanceof RootPackageInterface) {
            $sourcePath = realpath('.');
        } else {
            // Directory used to download the sources
            $sourcePath = sys_get_temp_dir().'/composer_archive'.bin2hex(random_bytes(5));
            $filesystem->ensureDirectoryExists($sourcePath);

            try {
                // Download sources
                $promise = $this->downloadManager->download($package, $sourcePath);
                SyncHelper::await($this->loop, $promise);
                $promise = $this->downloadManager->install($package, $sourcePath);
                SyncHelper::await($this->loop, $promise);
            } catch (\Exception $e) {
                $filesystem->removeDirectory($sourcePath);
                throw  $e;
            }

            // Check exclude from downloaded composer.json
            if (file_exists($composerJsonPath = $sourcePath.'/composer.json')) {
                $jsonFile = new JsonFile($composerJsonPath);
                $jsonData = $jsonFile->read();
                if (!empty($jsonData['archive']['name'])) {
                    $package->setArchiveName($jsonData['archive']['name']);
                }
                if (!empty($jsonData['archive']['exclude'])) {
                    $package->setArchiveExcludes($jsonData['archive']['exclude']);
                }
            }
        }

        $supportedFormats = $this->getSupportedFormats();
        $packageNameParts = null === $fileName ?
            $this->getPackageFilenameParts($package)
            : ['base' => $fileName];

        $packageName = $this->getPackageFilenameFromParts($packageNameParts);
        $excludePatterns = $this->buildExcludePatterns($packageNameParts, $supportedFormats);

        // Archive filename
        $filesystem->ensureDirectoryExists($targetDir);
        $target = realpath($targetDir).'/'.$packageName.'.'.$format;
        $filesystem->ensureDirectoryExists(dirname($target));

        if (!$this->overwriteFiles && file_exists($target)) {
            return $target;
        }

        // Create the archive
        $tempTarget = sys_get_temp_dir().'/composer_archive'.bin2hex(random_bytes(5)).'.'.$format;
        $filesystem->ensureDirectoryExists(dirname($tempTarget));

        $archivePath = $usableArchiver->archive(
            $sourcePath,
            $tempTarget,
            $format,
            array_merge($excludePatterns, $package->getArchiveExcludes()),
            $ignoreFilters
        );
        $filesystem->rename($archivePath, $target);

        // cleanup temporary download
        if (!$package instanceof RootPackageInterface) {
            $filesystem->removeDirectory($sourcePath);
        }
        $filesystem->remove($tempTarget);

        return $target;
    }

    /**
     * @param string[] $parts
     * @param string[] $formats
     *
     * @return string[]
     */
    private function buildExcludePatterns(array $parts, array $formats): array
    {
        $base = $parts['base'];
        if (count($parts) > 1) {
            $base .= '-*';
        }

        $patterns = [];
        foreach ($formats as $format) {
            $patterns[] = "$base.$format";
        }

        return $patterns;
    }

    /**
     * @return string[]
     */
    private function getSupportedFormats(): array
    {
        // The problem is that the \Composer\Package\Archiver\ArchiverInterface
        // doesn't provide method to get the supported formats.
        // Supported formats are also hard-coded into the description of the
        // --format option.
        // See \Composer\Command\ArchiveCommand::configure().
        $formats = [];
        foreach ($this->archivers as $archiver) {
            $items = [];
            switch (get_class($archiver)) {
                case ZipArchiver::class:
                    $items = ['zip'];
                    break;

                case PharArchiver::class:
                    $items = ['zip', 'tar', 'tar.gz', 'tar.bz2'];
                    break;
            }

            $formats = array_merge($formats, $items);
        }

        return array_unique($formats);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\PartialComposer;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Package\PackageInterface;
use Composer\Plugin\PluginManager;
use Composer\Util\Filesystem;
use Composer\Util\Platform;
use React\Promise\PromiseInterface;

/**
 * Installer for plugin packages
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Nils Adermann <naderman@naderman.de>
 */
class PluginInstaller extends LibraryInstaller
{
    public function __construct(IOInterface $io, PartialComposer $composer, ?Filesystem $fs = null, ?BinaryInstaller $binaryInstaller = null)
    {
        parent::__construct($io, $composer, 'composer-plugin', $fs, $binaryInstaller);
    }

    /**
     * @inheritDoc
     */
    public function supports(string $packageType)
    {
        return $packageType === 'composer-plugin' || $packageType === 'composer-installer';
    }

    public function disablePlugins(): void
    {
        $this->getPluginManager()->disablePlugins();
    }

    /**
     * @inheritDoc
     */
    public function prepare($type, PackageInterface $package, ?PackageInterface $prevPackage = null)
    {
        // fail install process early if it is going to fail due to a plugin not being allowed
        if (($type === 'install' || $type === 'update') && !$this->getPluginManager()->arePluginsDisabled('local')) {
            $this->getPluginManager()->isPluginAllowed($package->getName(), false, true === ($package->getExtra()['plugin-optional'] ?? false));
        }

        return parent::prepare($type, $package, $prevPackage);
    }

    /**
     * @inheritDoc
     */
    public function download(PackageInterface $package, ?PackageInterface $prevPackage = null)
    {
        $extra = $package->getExtra();
        if (empty($extra['class'])) {
            throw new \UnexpectedValueException('Error while installing '.$package->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.');
        }

        return parent::download($package, $prevPackage);
    }

    /**
     * @inheritDoc
     */
    public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        $promise = parent::install($repo, $package);
        if (!$promise instanceof PromiseInterface) {
            $promise = \React\Promise\resolve(null);
        }

        return $promise->then(function () use ($package, $repo): void {
            try {
                Platform::workaroundFilesystemIssues();
                $this->getPluginManager()->registerPackage($package, true);
            } catch (\Exception $e) {
                $this->rollbackInstall($e, $repo, $package);
            }
        });
    }

    /**
     * @inheritDoc
     */
    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
    {
        $promise = parent::update($repo, $initial, $target);
        if (!$promise instanceof PromiseInterface) {
            $promise = \React\Promise\resolve(null);
        }

        return $promise->then(function () use ($initial, $target, $repo): void {
            try {
                Platform::workaroundFilesystemIssues();
                $this->getPluginManager()->deactivatePackage($initial);
                $this->getPluginManager()->registerPackage($target, true);
            } catch (\Exception $e) {
                $this->rollbackInstall($e, $repo, $target);
            }
        });
    }

    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        $this->getPluginManager()->uninstallPackage($package);

        return parent::uninstall($repo, $package);
    }

    private function rollbackInstall(\Exception $e, InstalledRepositoryInterface $repo, PackageInterface $package): void
    {
        $this->io->writeError('Plugin initialization failed ('.$e->getMessage().'), uninstalling plugin');
        parent::uninstall($repo, $package);
        throw $e;
    }

    protected function getPluginManager(): PluginManager
    {
        assert($this->composer instanceof Composer, new \LogicException(self::class.' should be initialized with a fully loaded Composer instance.'));
        $pluginManager = $this->composer->getPluginManager();

        return $pluginManager;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\PartialComposer;
use Composer\Pcre\Preg;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Package\PackageInterface;
use Composer\Util\Filesystem;
use Composer\Util\Silencer;
use Composer\Util\Platform;
use React\Promise\PromiseInterface;
use Composer\Downloader\DownloadManager;

/**
 * Package installation manager.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface
{
    /** @var PartialComposer */
    protected $composer;
    /** @var string */
    protected $vendorDir;
    /** @var DownloadManager|null */
    protected $downloadManager;
    /** @var IOInterface */
    protected $io;
    /** @var string */
    protected $type;
    /** @var Filesystem */
    protected $filesystem;
    /** @var BinaryInstaller */
    protected $binaryInstaller;

    /**
     * Initializes library installer.
     *
     * @param Filesystem      $filesystem
     * @param BinaryInstaller $binaryInstaller
     */
    public function __construct(IOInterface $io, PartialComposer $composer, ?string $type = 'library', ?Filesystem $filesystem = null, ?BinaryInstaller $binaryInstaller = null)
    {
        $this->composer = $composer;
        $this->downloadManager = $composer instanceof Composer ? $composer->getDownloadManager() : null;
        $this->io = $io;
        $this->type = $type;

        $this->filesystem = $filesystem ?: new Filesystem();
        $this->vendorDir = rtrim($composer->getConfig()->get('vendor-dir'), '/');
        $this->binaryInstaller = $binaryInstaller ?: new BinaryInstaller($this->io, rtrim($composer->getConfig()->get('bin-dir'), '/'), $composer->getConfig()->get('bin-compat'), $this->filesystem, $this->vendorDir);
    }

    /**
     * @inheritDoc
     */
    public function supports(string $packageType)
    {
        return $packageType === $this->type || null === $this->type;
    }

    /**
     * @inheritDoc
     */
    public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        if (!$repo->hasPackage($package)) {
            return false;
        }

        $installPath = $this->getInstallPath($package);

        if (Filesystem::isReadable($installPath)) {
            return true;
        }

        if (Platform::isWindows() && $this->filesystem->isJunction($installPath)) {
            return true;
        }

        if (is_link($installPath)) {
            if (realpath($installPath) === false) {
                return false;
            }

            return true;
        }

        return false;
    }

    /**
     * @inheritDoc
     */
    public function download(PackageInterface $package, ?PackageInterface $prevPackage = null)
    {
        $this->initializeVendorDir();
        $downloadPath = $this->getInstallPath($package);

        return $this->getDownloadManager()->download($package, $downloadPath, $prevPackage);
    }

    /**
     * @inheritDoc
     */
    public function prepare($type, PackageInterface $package, ?PackageInterface $prevPackage = null)
    {
        $this->initializeVendorDir();
        $downloadPath = $this->getInstallPath($package);

        return $this->getDownloadManager()->prepare($type, $package, $downloadPath, $prevPackage);
    }

    /**
     * @inheritDoc
     */
    public function cleanup($type, PackageInterface $package, ?PackageInterface $prevPackage = null)
    {
        $this->initializeVendorDir();
        $downloadPath = $this->getInstallPath($package);

        return $this->getDownloadManager()->cleanup($type, $package, $downloadPath, $prevPackage);
    }

    /**
     * @inheritDoc
     */
    public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        $this->initializeVendorDir();
        $downloadPath = $this->getInstallPath($package);

        // remove the binaries if it appears the package files are missing
        if (!Filesystem::isReadable($downloadPath) && $repo->hasPackage($package)) {
            $this->binaryInstaller->removeBinaries($package);
        }

        $promise = $this->installCode($package);
        if (!$promise instanceof PromiseInterface) {
            $promise = \React\Promise\resolve(null);
        }

        $binaryInstaller = $this->binaryInstaller;
        $installPath = $this->getInstallPath($package);

        return $promise->then(static function () use ($binaryInstaller, $installPath, $package, $repo): void {
            $binaryInstaller->installBinaries($package, $installPath);
            if (!$repo->hasPackage($package)) {
                $repo->addPackage(clone $package);
            }
        });
    }

    /**
     * @inheritDoc
     */
    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
    {
        if (!$repo->hasPackage($initial)) {
            throw new \InvalidArgumentException('Package is not installed: '.$initial);
        }

        $this->initializeVendorDir();

        $this->binaryInstaller->removeBinaries($initial);
        $promise = $this->updateCode($initial, $target);
        if (!$promise instanceof PromiseInterface) {
            $promise = \React\Promise\resolve(null);
        }

        $binaryInstaller = $this->binaryInstaller;
        $installPath = $this->getInstallPath($target);

        return $promise->then(static function () use ($binaryInstaller, $installPath, $target, $initial, $repo): void {
            $binaryInstaller->installBinaries($target, $installPath);
            $repo->removePackage($initial);
            if (!$repo->hasPackage($target)) {
                $repo->addPackage(clone $target);
            }
        });
    }

    /**
     * @inheritDoc
     */
    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        if (!$repo->hasPackage($package)) {
            throw new \InvalidArgumentException('Package is not installed: '.$package);
        }

        $promise = $this->removeCode($package);
        if (!$promise instanceof PromiseInterface) {
            $promise = \React\Promise\resolve(null);
        }

        $binaryInstaller = $this->binaryInstaller;
        $downloadPath = $this->getPackageBasePath($package);
        $filesystem = $this->filesystem;

        return $promise->then(static function () use ($binaryInstaller, $filesystem, $downloadPath, $package, $repo): void {
            $binaryInstaller->removeBinaries($package);
            $repo->removePackage($package);

            if (strpos($package->getName(), '/')) {
                $packageVendorDir = dirname($downloadPath);
                if (is_dir($packageVendorDir) && $filesystem->isDirEmpty($packageVendorDir)) {
                    Silencer::call('rmdir', $packageVendorDir);
                }
            }
        });
    }

    /**
     * @inheritDoc
     *
     * @return string
     */
    public function getInstallPath(PackageInterface $package)
    {
        $this->initializeVendorDir();

        $basePath = ($this->vendorDir ? $this->vendorDir.'/' : '') . $package->getPrettyName();
        $targetDir = $package->getTargetDir();

        return $basePath . ($targetDir ? '/'.$targetDir : '');
    }

    /**
     * Make sure binaries are installed for a given package.
     *
     * @param PackageInterface $package Package instance
     */
    public function ensureBinariesPresence(PackageInterface $package)
    {
        $this->binaryInstaller->installBinaries($package, $this->getInstallPath($package), false);
    }

    /**
     * Returns the base path of the package without target-dir path
     *
     * It is used for BC as getInstallPath tends to be overridden by
     * installer plugins but not getPackageBasePath
     *
     * @return string
     */
    protected function getPackageBasePath(PackageInterface $package)
    {
        $installPath = $this->getInstallPath($package);
        $targetDir = $package->getTargetDir();

        if ($targetDir) {
            return Preg::replace('{/*'.str_replace('/', '/+', preg_quote($targetDir)).'/?$}', '', $installPath);
        }

        return $installPath;
    }

    /**
     * @return PromiseInterface|null
     * @phpstan-return PromiseInterface<void|null>|null
     */
    protected function installCode(PackageInterface $package)
    {
        $downloadPath = $this->getInstallPath($package);

        return $this->getDownloadManager()->install($package, $downloadPath);
    }

    /**
     * @return PromiseInterface|null
     * @phpstan-return PromiseInterface<void|null>|null
     */
    protected function updateCode(PackageInterface $initial, PackageInterface $target)
    {
        $initialDownloadPath = $this->getInstallPath($initial);
        $targetDownloadPath = $this->getInstallPath($target);
        if ($targetDownloadPath !== $initialDownloadPath) {
            // if the target and initial dirs intersect, we force a remove + install
            // to avoid the rename wiping the target dir as part of the initial dir cleanup
            if (strpos($initialDownloadPath, $targetDownloadPath) === 0
                || strpos($targetDownloadPath, $initialDownloadPath) === 0
            ) {
                $promise = $this->removeCode($initial);
                if (!$promise instanceof PromiseInterface) {
                    $promise = \React\Promise\resolve(null);
                }

                return $promise->then(function () use ($target): PromiseInterface {
                    $promise = $this->installCode($target);
                    if ($promise instanceof PromiseInterface) {
                        return $promise;
                    }

                    return \React\Promise\resolve(null);
                });
            }

            $this->filesystem->rename($initialDownloadPath, $targetDownloadPath);
        }

        return $this->getDownloadManager()->update($initial, $target, $targetDownloadPath);
    }

    /**
     * @return PromiseInterface|null
     * @phpstan-return PromiseInterface<void|null>|null
     */
    protected function removeCode(PackageInterface $package)
    {
        $downloadPath = $this->getPackageBasePath($package);

        return $this->getDownloadManager()->remove($package, $downloadPath);
    }

    /**
     * @return void
     */
    protected function initializeVendorDir()
    {
        $this->filesystem->ensureDirectoryExists($this->vendorDir);
        $this->vendorDir = realpath($this->vendorDir);
    }

    protected function getDownloadManager(): DownloadManager
    {
        assert($this->downloadManager instanceof DownloadManager, new \LogicException(self::class.' should be initialized with a fully loaded Composer instance to be able to install/... packages'));

        return $this->downloadManager;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

use Composer\IO\IOInterface;
use Composer\IO\ConsoleIO;
use Composer\Package\PackageInterface;
use Composer\Package\AliasPackage;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\DependencyResolver\Operation\UninstallOperation;
use Composer\DependencyResolver\Operation\MarkAliasInstalledOperation;
use Composer\DependencyResolver\Operation\MarkAliasUninstalledOperation;
use Composer\Downloader\FileDownloader;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Util\Loop;
use Composer\Util\Platform;
use React\Promise\PromiseInterface;
use Seld\Signal\SignalHandler;

/**
 * Package operation manager.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Nils Adermann <naderman@naderman.de>
 */
class InstallationManager
{
    /** @var list<InstallerInterface> */
    private $installers = [];
    /** @var array<string, InstallerInterface> */
    private $cache = [];
    /** @var array<string, array<PackageInterface>> */
    private $notifiablePackages = [];
    /** @var Loop */
    private $loop;
    /** @var IOInterface */
    private $io;
    /** @var ?EventDispatcher */
    private $eventDispatcher;
    /** @var bool */
    private $outputProgress;

    public function __construct(Loop $loop, IOInterface $io, ?EventDispatcher $eventDispatcher = null)
    {
        $this->loop = $loop;
        $this->io = $io;
        $this->eventDispatcher = $eventDispatcher;
    }

    public function reset(): void
    {
        $this->notifiablePackages = [];
        FileDownloader::$downloadMetadata = [];
    }

    /**
     * Adds installer
     *
     * @param InstallerInterface $installer installer instance
     */
    public function addInstaller(InstallerInterface $installer): void
    {
        array_unshift($this->installers, $installer);
        $this->cache = [];
    }

    /**
     * Removes installer
     *
     * @param InstallerInterface $installer installer instance
     */
    public function removeInstaller(InstallerInterface $installer): void
    {
        if (false !== ($key = array_search($installer, $this->installers, true))) {
            array_splice($this->installers, $key, 1);
            $this->cache = [];
        }
    }

    /**
     * Disables plugins.
     *
     * We prevent any plugins from being instantiated by
     * disabling the PluginManager. This ensures that no third-party
     * code is ever executed.
     */
    public function disablePlugins(): void
    {
        foreach ($this->installers as $i => $installer) {
            if (!$installer instanceof PluginInstaller) {
                continue;
            }

            $installer->disablePlugins();
        }
    }

    /**
     * Returns installer for a specific package type.
     *
     * @param string $type package type
     *
     * @throws \InvalidArgumentException if installer for provided type is not registered
     */
    public function getInstaller(string $type): InstallerInterface
    {
        $type = strtolower($type);

        if (isset($this->cache[$type])) {
            return $this->cache[$type];
        }

        foreach ($this->installers as $installer) {
            if ($installer->supports($type)) {
                return $this->cache[$type] = $installer;
            }
        }

        throw new \InvalidArgumentException('Unknown installer type: '.$type);
    }

    /**
     * Checks whether provided package is installed in one of the registered installers.
     *
     * @param InstalledRepositoryInterface $repo    repository in which to check
     * @param PackageInterface             $package package instance
     */
    public function isPackageInstalled(InstalledRepositoryInterface $repo, PackageInterface $package): bool
    {
        if ($package instanceof AliasPackage) {
            return $repo->hasPackage($package) && $this->isPackageInstalled($repo, $package->getAliasOf());
        }

        return $this->getInstaller($package->getType())->isInstalled($repo, $package);
    }

    /**
     * Install binary for the given package.
     * If the installer associated to this package doesn't handle that function, it'll do nothing.
     *
     * @param PackageInterface $package Package instance
     */
    public function ensureBinariesPresence(PackageInterface $package): void
    {
        try {
            $installer = $this->getInstaller($package->getType());
        } catch (\InvalidArgumentException $e) {
            // no installer found for the current package type (@see `getInstaller()`)
            return;
        }

        // if the given installer support installing binaries
        if ($installer instanceof BinaryPresenceInterface) {
            $installer->ensureBinariesPresence($package);
        }
    }

    /**
     * Executes solver operation.
     *
     * @param InstalledRepositoryInterface $repo         repository in which to add/remove/update packages
     * @param OperationInterface[]         $operations   operations to execute
     * @param bool                         $devMode      whether the install is being run in dev mode
     * @param bool                         $runScripts   whether to dispatch script events
     * @param bool                         $downloadOnly whether to only download packages
     */
    public function execute(InstalledRepositoryInterface $repo, array $operations, bool $devMode = true, bool $runScripts = true, bool $downloadOnly = false): void
    {
        /** @var array<callable(): ?PromiseInterface<void|null>> $cleanupPromises */
        $cleanupPromises = [];

        $signalHandler = SignalHandler::create([SignalHandler::SIGINT, SignalHandler::SIGTERM, SignalHandler::SIGHUP], function (string $signal, SignalHandler $handler) use (&$cleanupPromises) {
            $this->io->writeError('Received '.$signal.', aborting', true, IOInterface::DEBUG);
            $this->runCleanup($cleanupPromises);
            $handler->exitWithLastSignal();
        });

        try {
            // execute operations in batches to make sure download-modifying-plugins are installed
            // before the other packages get downloaded
            $batches = [];
            $batch = [];
            foreach ($operations as $index => $operation) {
                if ($operation instanceof UpdateOperation || $operation instanceof InstallOperation) {
                    $package = $operation instanceof UpdateOperation ? $operation->getTargetPackage() : $operation->getPackage();
                    if ($package->getType() === 'composer-plugin') {
                        $extra = $package->getExtra();
                        if (isset($extra['plugin-modifies-downloads']) && $extra['plugin-modifies-downloads'] === true) {
                            if (count($batch) > 0) {
                                $batches[] = $batch;
                            }
                            $batches[] = [$index => $operation];
                            $batch = [];

                            continue;
                        }
                    }
                }
                $batch[$index] = $operation;
            }

            if (count($batch) > 0) {
                $batches[] = $batch;
            }

            foreach ($batches as $batchToExecute) {
                $this->downloadAndExecuteBatch($repo, $batchToExecute, $cleanupPromises, $devMode, $runScripts, $downloadOnly, $operations);
            }
        } catch (\Exception $e) {
            $this->runCleanup($cleanupPromises);

            throw $e;
        } finally {
            $signalHandler->unregister();
        }

        if ($downloadOnly) {
            return;
        }

        // do a last write so that we write the repository even if nothing changed
        // as that can trigger an update of some files like InstalledVersions.php if
        // running a new composer version
        $repo->write($devMode, $this);
    }

    /**
     * @param OperationInterface[] $operations    List of operations to execute in this batch
     * @param OperationInterface[] $allOperations Complete list of operations to be executed in the install job, used for event listeners
     * @phpstan-param array<callable(): ?PromiseInterface<void|null>> $cleanupPromises
     */
    private function downloadAndExecuteBatch(InstalledRepositoryInterface $repo, array $operations, array &$cleanupPromises, bool $devMode, bool $runScripts, bool $downloadOnly, array $allOperations): void
    {
        $promises = [];

        foreach ($operations as $index => $operation) {
            $opType = $operation->getOperationType();

            // ignoring alias ops as they don't need to execute anything at this stage
            if (!in_array($opType, ['update', 'install', 'uninstall'], true)) {
                continue;
            }

            if ($opType === 'update') {
                /** @var UpdateOperation $operation */
                $package = $operation->getTargetPackage();
                $initialPackage = $operation->getInitialPackage();
            } else {
                /** @var InstallOperation|MarkAliasInstalledOperation|MarkAliasUninstalledOperation|UninstallOperation $operation */
                $package = $operation->getPackage();
                $initialPackage = null;
            }
            $installer = $this->getInstaller($package->getType());

            $cleanupPromises[$index] = static function () use ($opType, $installer, $package, $initialPackage): ?PromiseInterface {
                // avoid calling cleanup if the download was not even initialized for a package
                // as without installation source configured nothing will work
                if (null === $package->getInstallationSource()) {
                    return \React\Promise\resolve(null);
                }

                return $installer->cleanup($opType, $package, $initialPackage);
            };

            if ($opType !== 'uninstall') {
                $promise = $installer->download($package, $initialPackage);
                if (null !== $promise) {
                    $promises[] = $promise;
                }
            }
        }

        // execute all downloads first
        if (count($promises) > 0) {
            $this->waitOnPromises($promises);
        }

        if ($downloadOnly) {
            $this->runCleanup($cleanupPromises);
            return;
        }

        // execute operations in batches to make sure every plugin is installed in the
        // right order and activated before the packages depending on it are installed
        $batches = [];
        $batch = [];
        foreach ($operations as $index => $operation) {
            if ($operation instanceof InstallOperation || $operation instanceof UpdateOperation) {
                $package = $operation instanceof UpdateOperation ? $operation->getTargetPackage() : $operation->getPackage();
                if ($package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer') {
                    if (count($batch) > 0) {
                        $batches[] = $batch;
                    }
                    $batches[] = [$index => $operation];
                    $batch = [];

                    continue;
                }
            }
            $batch[$index] = $operation;
        }

        if (count($batch) > 0) {
            $batches[] = $batch;
        }

        foreach ($batches as $batchToExecute) {
            $this->executeBatch($repo, $batchToExecute, $cleanupPromises, $devMode, $runScripts, $allOperations);
        }
    }

    /**
     * @param OperationInterface[] $operations    List of operations to execute in this batch
     * @param OperationInterface[] $allOperations Complete list of operations to be executed in the install job, used for event listeners
     * @phpstan-param array<callable(): ?PromiseInterface<void|null>> $cleanupPromises
     */
    private function executeBatch(InstalledRepositoryInterface $repo, array $operations, array $cleanupPromises, bool $devMode, bool $runScripts, array $allOperations): void
    {
        $promises = [];
        $postExecCallbacks = [];

        foreach ($operations as $index => $operation) {
            $opType = $operation->getOperationType();

            // ignoring alias ops as they don't need to execute anything
            if (!in_array($opType, ['update', 'install', 'uninstall'], true)) {
                // output alias ops in debug verbosity as they have no output otherwise
                if ($this->io->isDebug()) {
                    $this->io->writeError('  - ' . $operation->show(false));
                }
                $this->{$opType}($repo, $operation);

                continue;
            }

            if ($opType === 'update') {
                /** @var UpdateOperation $operation */
                $package = $operation->getTargetPackage();
                $initialPackage = $operation->getInitialPackage();
            } else {
                /** @var InstallOperation|MarkAliasInstalledOperation|MarkAliasUninstalledOperation|UninstallOperation $operation */
                $package = $operation->getPackage();
                $initialPackage = null;
            }

            $installer = $this->getInstaller($package->getType());

            $eventName = [
                'install' => PackageEvents::PRE_PACKAGE_INSTALL,
                'update' => PackageEvents::PRE_PACKAGE_UPDATE,
                'uninstall' => PackageEvents::PRE_PACKAGE_UNINSTALL,
            ][$opType];

            if ($runScripts && $this->eventDispatcher !== null) {
                $this->eventDispatcher->dispatchPackageEvent($eventName, $devMode, $repo, $allOperations, $operation);
            }

            $dispatcher = $this->eventDispatcher;
            $io = $this->io;

            $promise = $installer->prepare($opType, $package, $initialPackage);
            if (!$promise instanceof PromiseInterface) {
                $promise = \React\Promise\resolve(null);
            }

            $promise = $promise->then(function () use ($opType, $repo, $operation) {
                return $this->{$opType}($repo, $operation);
            })->then($cleanupPromises[$index])
            ->then(function () use ($devMode, $repo): void {
                $repo->write($devMode, $this);
            }, static function ($e) use ($opType, $package, $io): void {
                $io->writeError('    <error>' . ucfirst($opType) .' of '.$package->getPrettyName().' failed</error>');

                throw $e;
            });

            $eventName = [
                'install' => PackageEvents::POST_PACKAGE_INSTALL,
                'update' => PackageEvents::POST_PACKAGE_UPDATE,
                'uninstall' => PackageEvents::POST_PACKAGE_UNINSTALL,
            ][$opType];

            if ($runScripts && $dispatcher !== null) {
                $postExecCallbacks[] = static function () use ($dispatcher, $eventName, $devMode, $repo, $allOperations, $operation): void {
                    $dispatcher->dispatchPackageEvent($eventName, $devMode, $repo, $allOperations, $operation);
                };
            }

            $promises[] = $promise;
        }

        // execute all prepare => installs/updates/removes => cleanup steps
        if (count($promises) > 0) {
            $this->waitOnPromises($promises);
        }

        Platform::workaroundFilesystemIssues();

        foreach ($postExecCallbacks as $cb) {
            $cb();
        }
    }

    /**
     * @param array<PromiseInterface<void|null>> $promises
     */
    private function waitOnPromises(array $promises): void
    {
        $progress = null;
        if (
            $this->outputProgress
            && $this->io instanceof ConsoleIO
            && !((bool) Platform::getEnv('CI'))
            && !$this->io->isDebug()
            && count($promises) > 1
        ) {
            $progress = $this->io->getProgressBar();
        }
        $this->loop->wait($promises, $progress);
        if ($progress !== null) {
            $progress->clear();
            // ProgressBar in non-decorated output does not output a final line-break and clear() does nothing
            if (!$this->io->isDecorated()) {
                $this->io->writeError('');
            }
        }
    }

    /**
     * Executes download operation.
     *
     * @phpstan-return PromiseInterface<void|null>|null
     */
    public function download(PackageInterface $package): ?PromiseInterface
    {
        $installer = $this->getInstaller($package->getType());
        $promise = $installer->cleanup("install", $package);

        return $promise;
    }

    /**
     * Executes install operation.
     *
     * @param InstalledRepositoryInterface $repo      repository in which to check
     * @param InstallOperation             $operation operation instance
     * @phpstan-return PromiseInterface<void|null>|null
     */
    public function install(InstalledRepositoryInterface $repo, InstallOperation $operation): ?PromiseInterface
    {
        $package = $operation->getPackage();
        $installer = $this->getInstaller($package->getType());
        $promise = $installer->install($repo, $package);
        $this->markForNotification($package);

        return $promise;
    }

    /**
     * Executes update operation.
     *
     * @param InstalledRepositoryInterface $repo      repository in which to check
     * @param UpdateOperation              $operation operation instance
     * @phpstan-return PromiseInterface<void|null>|null
     */
    public function update(InstalledRepositoryInterface $repo, UpdateOperation $operation): ?PromiseInterface
    {
        $initial = $operation->getInitialPackage();
        $target = $operation->getTargetPackage();

        $initialType = $initial->getType();
        $targetType = $target->getType();

        if ($initialType === $targetType) {
            $installer = $this->getInstaller($initialType);
            $promise = $installer->update($repo, $initial, $target);
            $this->markForNotification($target);
        } else {
            $promise = $this->getInstaller($initialType)->uninstall($repo, $initial);
            if (!$promise instanceof PromiseInterface) {
                $promise = \React\Promise\resolve(null);
            }

            $installer = $this->getInstaller($targetType);
            $promise = $promise->then(static function () use ($installer, $repo, $target): PromiseInterface {
                $promise = $installer->install($repo, $target);
                if ($promise instanceof PromiseInterface) {
                    return $promise;
                }

                return \React\Promise\resolve(null);
            });
        }

        return $promise;
    }

    /**
     * Uninstalls package.
     *
     * @param InstalledRepositoryInterface $repo      repository in which to check
     * @param UninstallOperation           $operation operation instance
     * @phpstan-return PromiseInterface<void|null>|null
     */
    public function uninstall(InstalledRepositoryInterface $repo, UninstallOperation $operation): ?PromiseInterface
    {
        $package = $operation->getPackage();
        $installer = $this->getInstaller($package->getType());

        return $installer->uninstall($repo, $package);
    }

    /**
     * Executes markAliasInstalled operation.
     *
     * @param InstalledRepositoryInterface $repo      repository in which to check
     * @param MarkAliasInstalledOperation  $operation operation instance
     */
    public function markAliasInstalled(InstalledRepositoryInterface $repo, MarkAliasInstalledOperation $operation): void
    {
        $package = $operation->getPackage();

        if (!$repo->hasPackage($package)) {
            $repo->addPackage(clone $package);
        }
    }

    /**
     * Executes markAlias operation.
     *
     * @param InstalledRepositoryInterface  $repo      repository in which to check
     * @param MarkAliasUninstalledOperation $operation operation instance
     */
    public function markAliasUninstalled(InstalledRepositoryInterface $repo, MarkAliasUninstalledOperation $operation): void
    {
        $package = $operation->getPackage();

        $repo->removePackage($package);
    }

    /**
     * Returns the installation path of a package
     *
     * @return string|null absolute path to install to, which does not end with a slash, or null if the package does not have anything installed on disk
     */
    public function getInstallPath(PackageInterface $package): ?string
    {
        $installer = $this->getInstaller($package->getType());

        return $installer->getInstallPath($package);
    }

    public function setOutputProgress(bool $outputProgress): void
    {
        $this->outputProgress = $outputProgress;
    }

    public function notifyInstalls(IOInterface $io): void
    {
        $promises = [];

        try {
            foreach ($this->notifiablePackages as $repoUrl => $packages) {
                // non-batch API, deprecated
                if (str_contains($repoUrl, '%package%')) {
                    foreach ($packages as $package) {
                        $url = str_replace('%package%', $package->getPrettyName(), $repoUrl);

                        $params = [
                            'version' => $package->getPrettyVersion(),
                            'version_normalized' => $package->getVersion(),
                        ];
                        $opts = [
                            'retry-auth-failure' => false,
                            'http' => [
                                'method' => 'POST',
                                'header' => ['Content-type: application/x-www-form-urlencoded'],
                                'content' => http_build_query($params, '', '&'),
                                'timeout' => 3,
                            ],
                        ];

                        $promises[] = $this->loop->getHttpDownloader()->add($url, $opts);
                    }

                    continue;
                }

                $postData = ['downloads' => []];
                foreach ($packages as $package) {
                    $packageNotification = [
                        'name' => $package->getPrettyName(),
                        'version' => $package->getVersion(),
                    ];
                    if (strpos($repoUrl, 'packagist.org/') !== false) {
                        if (isset(FileDownloader::$downloadMetadata[$package->getName()])) {
                            $packageNotification['downloaded'] = FileDownloader::$downloadMetadata[$package->getName()];
                        } else {
                            $packageNotification['downloaded'] = false;
                        }
                    }
                    $postData['downloads'][] = $packageNotification;
                }

                $opts = [
                    'retry-auth-failure' => false,
                    'http' => [
                        'method' => 'POST',
                        'header' => ['Content-Type: application/json'],
                        'content' => json_encode($postData),
                        'timeout' => 6,
                    ],
                ];

                $promises[] = $this->loop->getHttpDownloader()->add($repoUrl, $opts);
            }

            $this->loop->wait($promises);
        } catch (\Exception $e) {
        }

        $this->reset();
    }

    private function markForNotification(PackageInterface $package): void
    {
        if ($package->getNotificationUrl() !== null) {
            $this->notifiablePackages[$package->getNotificationUrl()][$package->getName()] = $package;
        }
    }

    /**
     * @return void
     * @phpstan-param array<callable(): ?PromiseInterface<void|null>> $cleanupPromises
     */
    private function runCleanup(array $cleanupPromises): void
    {
        $promises = [];

        $this->loop->abortJobs();

        foreach ($cleanupPromises as $cleanup) {
            $promises[] = new \React\Promise\Promise(static function ($resolve) use ($cleanup): void {
                $promise = $cleanup();
                if (!$promise instanceof PromiseInterface) {
                    $resolve(null);
                } else {
                    $promise->then(static function () use ($resolve): void {
                        $resolve(null);
                    });
                }
            });
        }

        if (count($promises) > 0) {
            $this->loop->wait($promises);
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

use Composer\Package\PackageInterface;
use Composer\Repository\InstalledRepositoryInterface;
use InvalidArgumentException;
use React\Promise\PromiseInterface;

/**
 * Interface for the package installation manager.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface InstallerInterface
{
    /**
     * Decides if the installer supports the given type
     *
     * @return bool
     */
    public function supports(string $packageType);

    /**
     * Checks that provided package is installed.
     *
     * @param InstalledRepositoryInterface $repo    repository in which to check
     * @param PackageInterface             $package package instance
     *
     * @return bool
     */
    public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package);

    /**
     * Downloads the files needed to later install the given package.
     *
     * @param  PackageInterface      $package     package instance
     * @param  PackageInterface      $prevPackage previous package instance in case of an update
     * @return PromiseInterface|null
     * @phpstan-return PromiseInterface<void|null>|null
     */
    public function download(PackageInterface $package, ?PackageInterface $prevPackage = null);

    /**
     * Do anything that needs to be done between all downloads have been completed and the actual operation is executed
     *
     * All packages get first downloaded, then all together prepared, then all together installed/updated/uninstalled. Therefore
     * for error recovery it is important to avoid failing during install/update/uninstall as much as possible, and risky things or
     * user prompts should happen in the prepare step rather. In case of failure, cleanup() will be called so that changes can
     * be undone as much as possible.
     *
     * @param  string                $type        one of install/update/uninstall
     * @param  PackageInterface      $package     package instance
     * @param  PackageInterface      $prevPackage previous package instance in case of an update
     * @return PromiseInterface|null
     * @phpstan-return PromiseInterface<void|null>|null
     */
    public function prepare(string $type, PackageInterface $package, ?PackageInterface $prevPackage = null);

    /**
     * Installs specific package.
     *
     * @param  InstalledRepositoryInterface $repo    repository in which to check
     * @param  PackageInterface             $package package instance
     * @return PromiseInterface|null
     * @phpstan-return PromiseInterface<void|null>|null
     */
    public function install(InstalledRepositoryInterface $repo, PackageInterface $package);

    /**
     * Updates specific package.
     *
     * @param  InstalledRepositoryInterface $repo    repository in which to check
     * @param  PackageInterface             $initial already installed package version
     * @param  PackageInterface             $target  updated version
     * @throws InvalidArgumentException     if $initial package is not installed
     * @return PromiseInterface|null
     * @phpstan-return PromiseInterface<void|null>|null
     */
    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target);

    /**
     * Uninstalls specific package.
     *
     * @param  InstalledRepositoryInterface $repo    repository in which to check
     * @param  PackageInterface             $package package instance
     * @return PromiseInterface|null
     * @phpstan-return PromiseInterface<void|null>|null
     */
    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package);

    /**
     * Do anything to cleanup changes applied in the prepare or install/update/uninstall steps
     *
     * Note that cleanup will be called for all packages regardless if they failed an operation or not, to give
     * all installers a change to cleanup things they did previously, so you need to keep track of changes
     * applied in the installer/downloader themselves.
     *
     * @param  string                $type        one of install/update/uninstall
     * @param  PackageInterface      $package     package instance
     * @param  PackageInterface      $prevPackage previous package instance in case of an update
     * @return PromiseInterface|null
     * @phpstan-return PromiseInterface<void|null>|null
     */
    public function cleanup(string $type, PackageInterface $package, ?PackageInterface $prevPackage = null);

    /**
     * Returns the absolute installation path of a package.
     *
     * @return string|null absolute path to install to, which MUST not end with a slash, or null if the package does not have anything installed on disk
     */
    public function getInstallPath(PackageInterface $package);
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

/**
 * Package Events.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PackageEvents
{
    /**
     * The PRE_PACKAGE_INSTALL event occurs before a package is installed.
     *
     * The event listener method receives a Composer\Installer\PackageEvent instance.
     *
     * @var string
     */
    public const PRE_PACKAGE_INSTALL = 'pre-package-install';

    /**
     * The POST_PACKAGE_INSTALL event occurs after a package is installed.
     *
     * The event listener method receives a Composer\Installer\PackageEvent instance.
     *
     * @var string
     */
    public const POST_PACKAGE_INSTALL = 'post-package-install';

    /**
     * The PRE_PACKAGE_UPDATE event occurs before a package is updated.
     *
     * The event listener method receives a Composer\Installer\PackageEvent instance.
     *
     * @var string
     */
    public const PRE_PACKAGE_UPDATE = 'pre-package-update';

    /**
     * The POST_PACKAGE_UPDATE event occurs after a package is updated.
     *
     * The event listener method receives a Composer\Installer\PackageEvent instance.
     *
     * @var string
     */
    public const POST_PACKAGE_UPDATE = 'post-package-update';

    /**
     * The PRE_PACKAGE_UNINSTALL event occurs before a package has been uninstalled.
     *
     * The event listener method receives a Composer\Installer\PackageEvent instance.
     *
     * @var string
     */
    public const PRE_PACKAGE_UNINSTALL = 'pre-package-uninstall';

    /**
     * The POST_PACKAGE_UNINSTALL event occurs after a package has been uninstalled.
     *
     * The event listener method receives a Composer\Installer\PackageEvent instance.
     *
     * @var string
     */
    public const POST_PACKAGE_UNINSTALL = 'post-package-uninstall';
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

use Composer\Composer;
use Composer\DependencyResolver\Transaction;
use Composer\EventDispatcher\Event;
use Composer\IO\IOInterface;

class InstallerEvent extends Event
{
    /**
     * @var Composer
     */
    private $composer;

    /**
     * @var IOInterface
     */
    private $io;

    /**
     * @var bool
     */
    private $devMode;

    /**
     * @var bool
     */
    private $executeOperations;

    /**
     * @var Transaction
     */
    private $transaction;

    /**
     * Constructor.
     */
    public function __construct(string $eventName, Composer $composer, IOInterface $io, bool $devMode, bool $executeOperations, Transaction $transaction)
    {
        parent::__construct($eventName);

        $this->composer = $composer;
        $this->io = $io;
        $this->devMode = $devMode;
        $this->executeOperations = $executeOperations;
        $this->transaction = $transaction;
    }

    public function getComposer(): Composer
    {
        return $this->composer;
    }

    public function getIO(): IOInterface
    {
        return $this->io;
    }

    public function isDevMode(): bool
    {
        return $this->devMode;
    }

    public function isExecutingOperations(): bool
    {
        return $this->executeOperations;
    }

    public function getTransaction(): ?Transaction
    {
        return $this->transaction;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

use Composer\Repository\InstalledRepositoryInterface;
use Composer\Package\PackageInterface;

/**
 * Does not install anything but marks packages installed in the repo
 *
 * Useful for dry runs
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class NoopInstaller implements InstallerInterface
{
    /**
     * @inheritDoc
     */
    public function supports(string $packageType)
    {
        return true;
    }

    /**
     * @inheritDoc
     */
    public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        return $repo->hasPackage($package);
    }

    /**
     * @inheritDoc
     */
    public function download(PackageInterface $package, ?PackageInterface $prevPackage = null)
    {
        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function prepare($type, PackageInterface $package, ?PackageInterface $prevPackage = null)
    {
        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function cleanup($type, PackageInterface $package, ?PackageInterface $prevPackage = null)
    {
        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        if (!$repo->hasPackage($package)) {
            $repo->addPackage(clone $package);
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
    {
        if (!$repo->hasPackage($initial)) {
            throw new \InvalidArgumentException('Package is not installed: '.$initial);
        }

        $repo->removePackage($initial);
        if (!$repo->hasPackage($target)) {
            $repo->addPackage(clone $target);
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        if (!$repo->hasPackage($package)) {
            throw new \InvalidArgumentException('Package is not installed: '.$package);
        }
        $repo->removePackage($package);

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function getInstallPath(PackageInterface $package)
    {
        $targetDir = $package->getTargetDir();

        return $package->getPrettyName() . ($targetDir ? '/'.$targetDir : '');
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Pcre\Preg;
use Composer\Util\Filesystem;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Util\Silencer;

/**
 * Utility to handle installation of package "bin"/binaries
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Helmut Hummel <info@helhum.io>
 */
class BinaryInstaller
{
    /** @var string */
    protected $binDir;
    /** @var string */
    protected $binCompat;
    /** @var IOInterface */
    protected $io;
    /** @var Filesystem */
    protected $filesystem;
    /** @var string|null */
    private $vendorDir;

    /**
     * @param Filesystem  $filesystem
     */
    public function __construct(IOInterface $io, string $binDir, string $binCompat, ?Filesystem $filesystem = null, ?string $vendorDir = null)
    {
        $this->binDir = $binDir;
        $this->binCompat = $binCompat;
        $this->io = $io;
        $this->filesystem = $filesystem ?: new Filesystem();
        $this->vendorDir = $vendorDir;
    }

    public function installBinaries(PackageInterface $package, string $installPath, bool $warnOnOverwrite = true): void
    {
        $binaries = $this->getBinaries($package);
        if (!$binaries) {
            return;
        }

        Platform::workaroundFilesystemIssues();

        foreach ($binaries as $bin) {
            $binPath = $installPath.'/'.$bin;
            if (!file_exists($binPath)) {
                $this->io->writeError('    <warning>Skipped installation of bin '.$bin.' for package '.$package->getName().': file not found in package</warning>');
                continue;
            }
            if (is_dir($binPath)) {
                $this->io->writeError('    <warning>Skipped installation of bin '.$bin.' for package '.$package->getName().': found a directory at that path</warning>');
                continue;
            }
            if (!$this->filesystem->isAbsolutePath($binPath)) {
                // in case a custom installer returned a relative path for the
                // $package, we can now safely turn it into a absolute path (as we
                // already checked the binary's existence). The following helpers
                // will require absolute paths to work properly.
                $binPath = realpath($binPath);
            }
            $this->initializeBinDir();
            $link = $this->binDir.'/'.basename($bin);
            if (file_exists($link)) {
                if (!is_link($link)) {
                    if ($warnOnOverwrite) {
                        $this->io->writeError('    Skipped installation of bin '.$bin.' for package '.$package->getName().': name conflicts with an existing file');
                    }
                    continue;
                }
                if (realpath($link) === realpath($binPath)) {
                    // It is a linked binary from a previous installation, which can be replaced with a proxy file
                    $this->filesystem->unlink($link);
                }
            }

            $binCompat = $this->binCompat;
            if ($binCompat === "auto" && (Platform::isWindows() || Platform::isWindowsSubsystemForLinux())) {
                $binCompat = 'full';
            }

            if ($binCompat === "full") {
                $this->installFullBinaries($binPath, $link, $bin, $package);
            } else {
                $this->installUnixyProxyBinaries($binPath, $link);
            }
            Silencer::call('chmod', $binPath, 0777 & ~umask());
        }
    }

    public function removeBinaries(PackageInterface $package): void
    {
        $this->initializeBinDir();

        $binaries = $this->getBinaries($package);
        if (!$binaries) {
            return;
        }
        foreach ($binaries as $bin) {
            $link = $this->binDir.'/'.basename($bin);
            if (is_link($link) || file_exists($link)) { // still checking for symlinks here for legacy support
                $this->filesystem->unlink($link);
            }
            if (is_file($link.'.bat')) {
                $this->filesystem->unlink($link.'.bat');
            }
        }

        // attempt removing the bin dir in case it is left empty
        if (is_dir($this->binDir) && $this->filesystem->isDirEmpty($this->binDir)) {
            Silencer::call('rmdir', $this->binDir);
        }
    }

    public static function determineBinaryCaller(string $bin): string
    {
        if ('.bat' === substr($bin, -4) || '.exe' === substr($bin, -4)) {
            return 'call';
        }

        $handle = fopen($bin, 'r');
        $line = fgets($handle);
        fclose($handle);
        if (Preg::isMatchStrictGroups('{^#!/(?:usr/bin/env )?(?:[^/]+/)*(.+)$}m', (string) $line, $match)) {
            return trim($match[1]);
        }

        return 'php';
    }

    /**
     * @return string[]
     */
    protected function getBinaries(PackageInterface $package): array
    {
        return $package->getBinaries();
    }

    protected function installFullBinaries(string $binPath, string $link, string $bin, PackageInterface $package): void
    {
        // add unixy support for cygwin and similar environments
        if ('.bat' !== substr($binPath, -4)) {
            $this->installUnixyProxyBinaries($binPath, $link);
            $link .= '.bat';
            if (file_exists($link)) {
                $this->io->writeError('    Skipped installation of bin '.$bin.'.bat proxy for package '.$package->getName().': a .bat proxy was already installed');
            }
        }
        if (!file_exists($link)) {
            file_put_contents($link, $this->generateWindowsProxyCode($binPath, $link));
            Silencer::call('chmod', $link, 0777 & ~umask());
        }
    }

    protected function installUnixyProxyBinaries(string $binPath, string $link): void
    {
        file_put_contents($link, $this->generateUnixyProxyCode($binPath, $link));
        Silencer::call('chmod', $link, 0777 & ~umask());
    }

    protected function initializeBinDir(): void
    {
        $this->filesystem->ensureDirectoryExists($this->binDir);
        $this->binDir = realpath($this->binDir);
    }

    protected function generateWindowsProxyCode(string $bin, string $link): string
    {
        $binPath = $this->filesystem->findShortestPath($link, $bin);
        $caller = self::determineBinaryCaller($bin);

        // if the target is a php file, we run the unixy proxy file
        // to ensure that _composer_autoload_path gets defined, instead
        // of running the binary directly
        if ($caller === 'php') {
            return "@ECHO OFF\r\n".
                "setlocal DISABLEDELAYEDEXPANSION\r\n".
                "SET BIN_TARGET=%~dp0/".trim(ProcessExecutor::escape(basename($link, '.bat')), '"\'')."\r\n".
                "SET COMPOSER_RUNTIME_BIN_DIR=%~dp0\r\n".
                "{$caller} \"%BIN_TARGET%\" %*\r\n";
        }

        return "@ECHO OFF\r\n".
            "setlocal DISABLEDELAYEDEXPANSION\r\n".
            "SET BIN_TARGET=%~dp0/".trim(ProcessExecutor::escape($binPath), '"\'')."\r\n".
            "SET COMPOSER_RUNTIME_BIN_DIR=%~dp0\r\n".
            "{$caller} \"%BIN_TARGET%\" %*\r\n";
    }

    protected function generateUnixyProxyCode(string $bin, string $link): string
    {
        $binPath = $this->filesystem->findShortestPath($link, $bin);

        $binDir = ProcessExecutor::escape(dirname($binPath));
        $binFile = basename($binPath);

        $binContents = (string) file_get_contents($bin, false, null, 0, 500);
        // For php files, we generate a PHP proxy instead of a shell one,
        // which allows calling the proxy with a custom php process
        if (Preg::isMatch('{^(#!.*\r?\n)?[\r\n\t ]*<\?php}', $binContents, $match)) {
            // carry over the existing shebang if present, otherwise add our own
            $proxyCode = $match[1] === null ? '#!/usr/bin/env php' : trim($match[1]);
            $binPathExported = $this->filesystem->findShortestPathCode($link, $bin, false, true);
            $streamProxyCode = $streamHint = '';
            $globalsCode = '$GLOBALS[\'_composer_bin_dir\'] = __DIR__;'."\n";
            $phpunitHack1 = $phpunitHack2 = '';
            // Don't expose autoload path when vendor dir was not set in custom installers
            if ($this->vendorDir !== null) {
                // ensure comparisons work accurately if the CWD is a symlink, as $link is realpath'd already
                $vendorDirReal = realpath($this->vendorDir);
                if ($vendorDirReal === false) {
                    $vendorDirReal = $this->vendorDir;
                }
                $globalsCode .= '$GLOBALS[\'_composer_autoload_path\'] = ' . $this->filesystem->findShortestPathCode($link, $vendorDirReal . '/autoload.php', false, true).";\n";
            }
            // Add workaround for PHPUnit process isolation
            if ($this->filesystem->normalizePath($bin) === $this->filesystem->normalizePath($this->vendorDir.'/phpunit/phpunit/phpunit')) {
                // workaround issue on PHPUnit 6.5+ running on PHP 8+
                $globalsCode .= '$GLOBALS[\'__PHPUNIT_ISOLATION_EXCLUDE_LIST\'] = $GLOBALS[\'__PHPUNIT_ISOLATION_BLACKLIST\'] = array(realpath('.$binPathExported.'));'."\n";
                // workaround issue on all PHPUnit versions running on PHP <8
                $phpunitHack1 = "'phpvfscomposer://'.";
                $phpunitHack2 = '
                $data = str_replace(\'__DIR__\', var_export(dirname($this->realpath), true), $data);
                $data = str_replace(\'__FILE__\', var_export($this->realpath, true), $data);';
            }
            if (trim($match[0]) !== '<?php') {
                $streamHint = ' using a stream wrapper to prevent the shebang from being output on PHP<8'."\n *";
                $streamProxyCode = <<<STREAMPROXY
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 = $phpunitHack1\$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);
                }$phpunitHack2

                \$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'))
    ) {
        return include("phpvfscomposer://" . $binPathExported);
    }
}

STREAMPROXY;
            }

            return $proxyCode . "\n" . <<<PROXY
<?php

/**
 * Proxy PHP file generated by Composer
 *
 * This file includes the referenced bin path ($binPath)
 *$streamHint
 * @generated
 */

namespace Composer;

$globalsCode
$streamProxyCode
return include $binPathExported;

PROXY;
        }

        return <<<PROXY
#!/usr/bin/env sh

# Support bash to support `source` with fallback on $0 if this does not run with bash
# https://stackoverflow.com/a/35006505/6512
selfArg="\$BASH_SOURCE"
if [ -z "\$selfArg" ]; then
    selfArg="\$0"
fi

self=\$(realpath \$selfArg 2> /dev/null)
if [ -z "\$self" ]; then
    self="\$selfArg"
fi

dir=\$(cd "\${self%[/\\\\]*}" > /dev/null; cd $binDir && pwd)

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

export COMPOSER_RUNTIME_BIN_DIR="\$(cd "\${self%[/\\\\]*}" > /dev/null; pwd)"

# If bash is sourcing this file, we have to source the target as well
bashSource="\$BASH_SOURCE"
if [ -n "\$bashSource" ]; then
    if [ "\$bashSource" != "\$0" ]; then
        source "\${dir}/$binFile" "\$@"
        return
    fi
fi

exec "\${dir}/$binFile" "\$@"

PROXY;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\Repository\RepositoryInterface;
use Composer\EventDispatcher\Event;

/**
 * The Package Event.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PackageEvent extends Event
{
    /**
     * @var Composer
     */
    private $composer;

    /**
     * @var IOInterface
     */
    private $io;

    /**
     * @var bool
     */
    private $devMode;

    /**
     * @var RepositoryInterface
     */
    private $localRepo;

    /**
     * @var OperationInterface[]
     */
    private $operations;

    /**
     * @var OperationInterface The operation instance which is being executed
     */
    private $operation;

    /**
     * Constructor.
     *
     * @param OperationInterface[] $operations
     */
    public function __construct(string $eventName, Composer $composer, IOInterface $io, bool $devMode, RepositoryInterface $localRepo, array $operations, OperationInterface $operation)
    {
        parent::__construct($eventName);

        $this->composer = $composer;
        $this->io = $io;
        $this->devMode = $devMode;
        $this->localRepo = $localRepo;
        $this->operations = $operations;
        $this->operation = $operation;
    }

    public function getComposer(): Composer
    {
        return $this->composer;
    }

    public function getIO(): IOInterface
    {
        return $this->io;
    }

    public function isDevMode(): bool
    {
        return $this->devMode;
    }

    public function getLocalRepo(): RepositoryInterface
    {
        return $this->localRepo;
    }

    /**
     * @return OperationInterface[]
     */
    public function getOperations(): array
    {
        return $this->operations;
    }

    /**
     * Returns the package instance.
     */
    public function getOperation(): OperationInterface
    {
        return $this->operation;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

use Composer\Repository\InstalledRepositoryInterface;
use Composer\Package\PackageInterface;
use Composer\IO\IOInterface;
use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\UninstallOperation;

/**
 * Metapackage installation manager.
 *
 * @author Martin Hasoň <martin.hason@gmail.com>
 */
class MetapackageInstaller implements InstallerInterface
{
    /** @var IOInterface */
    private $io;

    public function __construct(IOInterface $io)
    {
        $this->io = $io;
    }

    /**
     * @inheritDoc
     */
    public function supports(string $packageType)
    {
        return $packageType === 'metapackage';
    }

    /**
     * @inheritDoc
     */
    public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        return $repo->hasPackage($package);
    }

    /**
     * @inheritDoc
     */
    public function download(PackageInterface $package, ?PackageInterface $prevPackage = null)
    {
        // noop
        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function prepare($type, PackageInterface $package, ?PackageInterface $prevPackage = null)
    {
        // noop
        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function cleanup($type, PackageInterface $package, ?PackageInterface $prevPackage = null)
    {
        // noop
        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        $this->io->writeError("  - " . InstallOperation::format($package));

        $repo->addPackage(clone $package);

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
    {
        if (!$repo->hasPackage($initial)) {
            throw new \InvalidArgumentException('Package is not installed: '.$initial);
        }

        $this->io->writeError("  - " . UpdateOperation::format($initial, $target));

        $repo->removePackage($initial);
        $repo->addPackage(clone $target);

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        if (!$repo->hasPackage($package)) {
            throw new \InvalidArgumentException('Package is not installed: '.$package);
        }

        $this->io->writeError("  - " . UninstallOperation::format($package));

        $repo->removePackage($package);

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     *
     * @return null
     */
    public function getInstallPath(PackageInterface $package)
    {
        return null;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

class InstallerEvents
{
    /**
     * The PRE_OPERATIONS_EXEC event occurs before the lock file gets
     * installed and operations are executed.
     *
     * The event listener method receives an Composer\Installer\InstallerEvent instance.
     *
     * @var string
     */
    public const PRE_OPERATIONS_EXEC = 'pre-operations-exec';
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

use React\Promise\PromiseInterface;
use Composer\Package\PackageInterface;
use Composer\Downloader\DownloadManager;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Util\Filesystem;

/**
 * Project Installer is used to install a single package into a directory as
 * root project.
 *
 * @author Benjamin Eberlei <kontakt@beberlei.de>
 */
class ProjectInstaller implements InstallerInterface
{
    /** @var string */
    private $installPath;
    /** @var DownloadManager */
    private $downloadManager;
    /** @var Filesystem */
    private $filesystem;

    public function __construct(string $installPath, DownloadManager $dm, Filesystem $fs)
    {
        $this->installPath = rtrim(strtr($installPath, '\\', '/'), '/').'/';
        $this->downloadManager = $dm;
        $this->filesystem = $fs;
    }

    /**
     * Decides if the installer supports the given type
     */
    public function supports(string $packageType): bool
    {
        return true;
    }

    /**
     * @inheritDoc
     */
    public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package): bool
    {
        return false;
    }

    /**
     * @inheritDoc
     */
    public function download(PackageInterface $package, ?PackageInterface $prevPackage = null): ?PromiseInterface
    {
        $installPath = $this->installPath;
        if (file_exists($installPath) && !$this->filesystem->isDirEmpty($installPath)) {
            throw new \InvalidArgumentException("Project directory $installPath is not empty.");
        }
        if (!is_dir($installPath)) {
            mkdir($installPath, 0777, true);
        }

        return $this->downloadManager->download($package, $installPath, $prevPackage);
    }

    /**
     * @inheritDoc
     */
    public function prepare($type, PackageInterface $package, ?PackageInterface $prevPackage = null): ?PromiseInterface
    {
        return $this->downloadManager->prepare($type, $package, $this->installPath, $prevPackage);
    }

    /**
     * @inheritDoc
     */
    public function cleanup($type, PackageInterface $package, ?PackageInterface $prevPackage = null): ?PromiseInterface
    {
        return $this->downloadManager->cleanup($type, $package, $this->installPath, $prevPackage);
    }

    /**
     * @inheritDoc
     */
    public function install(InstalledRepositoryInterface $repo, PackageInterface $package): ?PromiseInterface
    {
        return $this->downloadManager->install($package, $this->installPath);
    }

    /**
     * @inheritDoc
     */
    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target): ?PromiseInterface
    {
        throw new \InvalidArgumentException("not supported");
    }

    /**
     * @inheritDoc
     */
    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package): ?PromiseInterface
    {
        throw new \InvalidArgumentException("not supported");
    }

    /**
     * Returns the installation path of a package
     *
     * @return string configured install path
     */
    public function getInstallPath(PackageInterface $package): string
    {
        return $this->installPath;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Pcre\Preg;
use Composer\Repository\InstalledRepository;
use Symfony\Component\Console\Formatter\OutputFormatter;

/**
 * Add suggested packages from different places to output them in the end.
 *
 * @author Haralan Dobrev <hkdobrev@gmail.com>
 */
class SuggestedPackagesReporter
{
    public const MODE_LIST = 1;
    public const MODE_BY_PACKAGE = 2;
    public const MODE_BY_SUGGESTION = 4;

    /**
     * @var array<array{source: string, target: string, reason: string}>
     */
    protected $suggestedPackages = [];

    /**
     * @var IOInterface
     */
    private $io;

    public function __construct(IOInterface $io)
    {
        $this->io = $io;
    }

    /**
     * @return array<array{source: string, target: string, reason: string}> Suggested packages with source, target and reason keys.
     */
    public function getPackages(): array
    {
        return $this->suggestedPackages;
    }

    /**
     * Add suggested packages to be listed after install
     *
     * Could be used to add suggested packages both from the installer
     * or from CreateProjectCommand.
     *
     * @param  string                    $source Source package which made the suggestion
     * @param  string                    $target Target package to be suggested
     * @param  string                    $reason Reason the target package to be suggested
     */
    public function addPackage(string $source, string $target, string $reason): SuggestedPackagesReporter
    {
        $this->suggestedPackages[] = [
            'source' => $source,
            'target' => $target,
            'reason' => $reason,
        ];

        return $this;
    }

    /**
     * Add all suggestions from a package.
     */
    public function addSuggestionsFromPackage(PackageInterface $package): SuggestedPackagesReporter
    {
        $source = $package->getPrettyName();
        foreach ($package->getSuggests() as $target => $reason) {
            $this->addPackage(
                $source,
                $target,
                $reason
            );
        }

        return $this;
    }

    /**
     * Output suggested packages.
     *
     * Do not list the ones already installed if installed repository provided.
     *
     * @param  int                      $mode             One of the MODE_* constants from this class
     * @param  InstalledRepository|null $installedRepo    If passed in, suggested packages which are installed already will be skipped
     * @param  PackageInterface|null    $onlyDependentsOf If passed in, only the suggestions from direct dependents of that package, or from the package itself, will be shown
     */
    public function output(int $mode, ?InstalledRepository $installedRepo = null, ?PackageInterface $onlyDependentsOf = null): void
    {
        $suggestedPackages = $this->getFilteredSuggestions($installedRepo, $onlyDependentsOf);

        $suggesters = [];
        $suggested = [];
        foreach ($suggestedPackages as $suggestion) {
            $suggesters[$suggestion['source']][$suggestion['target']] = $suggestion['reason'];
            $suggested[$suggestion['target']][$suggestion['source']] = $suggestion['reason'];
        }
        ksort($suggesters);
        ksort($suggested);

        // Simple mode
        if ($mode & self::MODE_LIST) {
            foreach (array_keys($suggested) as $name) {
                $this->io->write(sprintf('<info>%s</info>', $name));
            }

            return;
        }

        // Grouped by package
        if ($mode & self::MODE_BY_PACKAGE) {
            foreach ($suggesters as $suggester => $suggestions) {
                $this->io->write(sprintf('<comment>%s</comment> suggests:', $suggester));

                foreach ($suggestions as $suggestion => $reason) {
                    $this->io->write(sprintf(' - <info>%s</info>' . ($reason ? ': %s' : ''), $suggestion, $this->escapeOutput($reason)));
                }
                $this->io->write('');
            }
        }

        // Grouped by suggestion
        if ($mode & self::MODE_BY_SUGGESTION) {
            // Improve readability in full mode
            if ($mode & self::MODE_BY_PACKAGE) {
                $this->io->write(str_repeat('-', 78));
            }
            foreach ($suggested as $suggestion => $suggesters) {
                $this->io->write(sprintf('<comment>%s</comment> is suggested by:', $suggestion));

                foreach ($suggesters as $suggester => $reason) {
                    $this->io->write(sprintf(' - <info>%s</info>' . ($reason ? ': %s' : ''), $suggester, $this->escapeOutput($reason)));
                }
                $this->io->write('');
            }
        }

        if ($onlyDependentsOf) {
            $allSuggestedPackages = $this->getFilteredSuggestions($installedRepo);
            $diff = count($allSuggestedPackages) - count($suggestedPackages);
            if ($diff) {
                $this->io->write('<info>'.$diff.' additional suggestions</info> by transitive dependencies can be shown with <info>--all</info>');
            }
        }
    }

    /**
     * Output number of new suggested packages and a hint to use suggest command.
     *
     * @param  InstalledRepository|null $installedRepo    If passed in, suggested packages which are installed already will be skipped
     * @param  PackageInterface|null    $onlyDependentsOf If passed in, only the suggestions from direct dependents of that package, or from the package itself, will be shown
     */
    public function outputMinimalistic(?InstalledRepository $installedRepo = null, ?PackageInterface $onlyDependentsOf = null): void
    {
        $suggestedPackages = $this->getFilteredSuggestions($installedRepo, $onlyDependentsOf);
        if ($suggestedPackages) {
            $this->io->writeError('<info>'.count($suggestedPackages).' package suggestions were added by new dependencies, use `composer suggest` to see details.</info>');
        }
    }

    /**
     * @param  InstalledRepository|null $installedRepo    If passed in, suggested packages which are installed already will be skipped
     * @param  PackageInterface|null    $onlyDependentsOf If passed in, only the suggestions from direct dependents of that package, or from the package itself, will be shown
     * @return mixed[]
     */
    private function getFilteredSuggestions(?InstalledRepository $installedRepo = null, ?PackageInterface $onlyDependentsOf = null): array
    {
        $suggestedPackages = $this->getPackages();
        $installedNames = [];
        if (null !== $installedRepo && !empty($suggestedPackages)) {
            foreach ($installedRepo->getPackages() as $package) {
                $installedNames = array_merge(
                    $installedNames,
                    $package->getNames()
                );
            }
        }

        $sourceFilter = [];
        if ($onlyDependentsOf) {
            $sourceFilter = array_map(static function ($link): string {
                return $link->getTarget();
            }, array_merge($onlyDependentsOf->getRequires(), $onlyDependentsOf->getDevRequires()));
            $sourceFilter[] = $onlyDependentsOf->getName();
        }

        $suggestions = [];
        foreach ($suggestedPackages as $suggestion) {
            if (in_array($suggestion['target'], $installedNames) || (\count($sourceFilter) > 0 && !in_array($suggestion['source'], $sourceFilter))) {
                continue;
            }

            $suggestions[] = $suggestion;
        }

        return $suggestions;
    }

    private function escapeOutput(string $string): string
    {
        return OutputFormatter::escape(
            $this->removeControlCharacters($string)
        );
    }

    private function removeControlCharacters(string $string): string
    {
        return Preg::replace(
            '/[[:cntrl:]]/',
            '',
            str_replace("\n", ' ', $string)
        );
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

use Composer\Package\PackageInterface;

/**
 * Interface for the package installation manager that handle binary installation.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface BinaryPresenceInterface
{
    /**
     * Make sure binaries are installed for a given package.
     *
     * @param PackageInterface $package package instance
     *
     * @return void
     */
    public function ensureBinariesPresence(PackageInterface $package);
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Json;

use Composer\Pcre\Preg;
use Composer\Util\Filesystem;
use JsonSchema\Validator;
use Seld\JsonLint\JsonParser;
use Seld\JsonLint\ParsingException;
use Composer\Util\HttpDownloader;
use Composer\IO\IOInterface;
use Composer\Downloader\TransportException;

/**
 * Reads/writes json files.
 *
 * @author Konstantin Kudryashiv <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class JsonFile
{
    public const LAX_SCHEMA = 1;
    public const STRICT_SCHEMA = 2;
    public const AUTH_SCHEMA = 3;
    public const LOCK_SCHEMA = 4;

    /** @deprecated Use \JSON_UNESCAPED_SLASHES */
    public const JSON_UNESCAPED_SLASHES = 64;
    /** @deprecated Use \JSON_PRETTY_PRINT */
    public const JSON_PRETTY_PRINT = 128;
    /** @deprecated Use \JSON_UNESCAPED_UNICODE */
    public const JSON_UNESCAPED_UNICODE = 256;

    public const COMPOSER_SCHEMA_PATH = __DIR__ . '/../../../res/composer-schema.json';
    public const LOCK_SCHEMA_PATH = __DIR__ . '/../../../res/composer-lock-schema.json';

    public const INDENT_DEFAULT = '    ';

    /** @var string */
    private $path;
    /** @var ?HttpDownloader */
    private $httpDownloader;
    /** @var ?IOInterface */
    private $io;
    /** @var string */
    private $indent = self::INDENT_DEFAULT;

    /**
     * Initializes json file reader/parser.
     *
     * @param  string                    $path           path to a lockfile
     * @param  ?HttpDownloader           $httpDownloader required for loading http/https json files
     * @param  ?IOInterface              $io
     * @throws \InvalidArgumentException
     */
    public function __construct(string $path, ?HttpDownloader $httpDownloader = null, ?IOInterface $io = null)
    {
        $this->path = $path;

        if (null === $httpDownloader && Preg::isMatch('{^https?://}i', $path)) {
            throw new \InvalidArgumentException('http urls require a HttpDownloader instance to be passed');
        }
        $this->httpDownloader = $httpDownloader;
        $this->io = $io;
    }

    public function getPath(): string
    {
        return $this->path;
    }

    /**
     * Checks whether json file exists.
     */
    public function exists(): bool
    {
        return is_file($this->path);
    }

    /**
     * Reads json file.
     *
     * @throws ParsingException
     * @throws \RuntimeException
     * @return mixed
     */
    public function read()
    {
        try {
            if ($this->httpDownloader) {
                $json = $this->httpDownloader->get($this->path)->getBody();
            } else {
                if (!Filesystem::isReadable($this->path)) {
                    throw new \RuntimeException('The file "'.$this->path.'" is not readable.');
                }
                if ($this->io && $this->io->isDebug()) {
                    $realpathInfo = '';
                    $realpath = realpath($this->path);
                    if (false !== $realpath && $realpath !== $this->path) {
                        $realpathInfo = ' (' . $realpath . ')';
                    }
                    $this->io->writeError('Reading ' . $this->path . $realpathInfo);
                }
                $json = file_get_contents($this->path);
            }
        } catch (TransportException $e) {
            throw new \RuntimeException($e->getMessage(), 0, $e);
        } catch (\Exception $e) {
            throw new \RuntimeException('Could not read '.$this->path."\n\n".$e->getMessage());
        }

        if ($json === false) {
            throw new \RuntimeException('Could not read '.$this->path);
        }

        $this->indent = self::detectIndenting($json);

        return static::parseJson($json, $this->path);
    }

    /**
     * Writes json file.
     *
     * @param  mixed[]                              $hash    writes hash into json file
     * @param  int                                  $options json_encode options
     * @throws \UnexpectedValueException|\Exception
     * @return void
     */
    public function write(array $hash, int $options = JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
    {
        if ($this->path === 'php://memory') {
            file_put_contents($this->path, static::encode($hash, $options, $this->indent));

            return;
        }

        $dir = dirname($this->path);
        if (!is_dir($dir)) {
            if (file_exists($dir)) {
                throw new \UnexpectedValueException(
                    realpath($dir).' exists and is not a directory.'
                );
            }
            if (!@mkdir($dir, 0777, true)) {
                throw new \UnexpectedValueException(
                    $dir.' does not exist and could not be created.'
                );
            }
        }

        $retries = 3;
        while ($retries--) {
            try {
                $this->filePutContentsIfModified($this->path, static::encode($hash, $options, $this->indent). ($options & JSON_PRETTY_PRINT ? "\n" : ''));
                break;
            } catch (\Exception $e) {
                if ($retries > 0) {
                    usleep(500000);
                    continue;
                }

                throw $e;
            }
        }
    }

    /**
     * Modify file properties only if content modified
     *
     * @return int|false
     */
    private function filePutContentsIfModified(string $path, string $content)
    {
        $currentContent = @file_get_contents($path);
        if (false === $currentContent || $currentContent !== $content) {
            return file_put_contents($path, $content);
        }

        return 0;
    }

    /**
     * Validates the schema of the current json file according to composer-schema.json rules
     *
     * @param  int                     $schema     a JsonFile::*_SCHEMA constant
     * @param  string|null             $schemaFile a path to the schema file
     * @throws JsonValidationException
     * @throws ParsingException
     * @return true                    true on success
     *
     * @phpstan-param self::*_SCHEMA $schema
     */
    public function validateSchema(int $schema = self::STRICT_SCHEMA, ?string $schemaFile = null): bool
    {
        if (!Filesystem::isReadable($this->path)) {
            throw new \RuntimeException('The file "'.$this->path.'" is not readable.');
        }
        $content = file_get_contents($this->path);
        $data = json_decode($content);

        if (null === $data && 'null' !== $content) {
            self::validateSyntax($content, $this->path);
        }

        return self::validateJsonSchema($this->path, $data, $schema, $schemaFile);
    }

    /**
     * Validates the schema of the current json file according to composer-schema.json rules
     *
     * @param  mixed                   $data       Decoded JSON data to validate
     * @param  int                     $schema     a JsonFile::*_SCHEMA constant
     * @param  string|null             $schemaFile a path to the schema file
     * @throws JsonValidationException
     * @return true                    true on success
     *
     * @phpstan-param self::*_SCHEMA $schema
     */
    public static function validateJsonSchema(string $source, $data, int $schema, ?string $schemaFile = null): bool
    {
        $isComposerSchemaFile = false;
        if (null === $schemaFile) {
            if ($schema === self::LOCK_SCHEMA) {
                $schemaFile = self::LOCK_SCHEMA_PATH;
            } else {
                $isComposerSchemaFile = true;
                $schemaFile = self::COMPOSER_SCHEMA_PATH;
            }
        }

        // Prepend with file:// only when not using a special schema already (e.g. in the phar)
        if (false === strpos($schemaFile, '://')) {
            $schemaFile = 'file://' . $schemaFile;
        }

        $schemaData = (object) ['$ref' => $schemaFile, '$schema' => "https://json-schema.org/draft-04/schema#"];

        if ($schema === self::STRICT_SCHEMA && $isComposerSchemaFile) {
            $schemaData = json_decode((string) file_get_contents($schemaFile));
            $schemaData->additionalProperties = false;
            $schemaData->required = ['name', 'description'];
        } elseif ($schema === self::AUTH_SCHEMA && $isComposerSchemaFile) {
            $schemaData = (object) ['$ref' => $schemaFile.'#/properties/config', '$schema' => "https://json-schema.org/draft-04/schema#"];
        }

        $validator = new Validator();
        // convert assoc arrays to objects
        $data = json_decode((string) json_encode($data));
        $validator->validate($data, $schemaData);

        if (!$validator->isValid()) {
            $errors = [];
            foreach ($validator->getErrors() as $error) {
                $errors[] = ($error['property'] ? $error['property'].' : ' : '').$error['message'];
            }
            throw new JsonValidationException('"'.$source.'" does not match the expected JSON schema', $errors);
        }

        return true;
    }

    /**
     * Encodes an array into (optionally pretty-printed) JSON
     *
     * @param  mixed  $data    Data to encode into a formatted JSON string
     * @param  int    $options json_encode options (defaults to JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
     * @param  string $indent  Indentation string
     * @return string Encoded json
     */
    public static function encode($data, int $options = 448, string $indent = self::INDENT_DEFAULT): string
    {
        $json = json_encode($data, $options);

        if (false === $json) {
            self::throwEncodeError(json_last_error());
        }

        if (($options & JSON_PRETTY_PRINT) > 0 && $indent !== self::INDENT_DEFAULT ) {
            // Pretty printing and not using default indentation
            return Preg::replaceCallback(
                '#^ {4,}#m',
                static function ($match) use ($indent): string {
                    return str_repeat($indent, (int)(strlen($match[0]) / 4));
                },
                $json
            );
        }

        return $json;
    }

    /**
     * Throws an exception according to a given code with a customized message
     *
     * @param  int               $code return code of json_last_error function
     * @throws \RuntimeException
     * @return never
     */
    private static function throwEncodeError(int $code): void
    {
        switch ($code) {
            case JSON_ERROR_DEPTH:
                $msg = 'Maximum stack depth exceeded';
                break;
            case JSON_ERROR_STATE_MISMATCH:
                $msg = 'Underflow or the modes mismatch';
                break;
            case JSON_ERROR_CTRL_CHAR:
                $msg = 'Unexpected control character found';
                break;
            case JSON_ERROR_UTF8:
                $msg = 'Malformed UTF-8 characters, possibly incorrectly encoded';
                break;
            default:
                $msg = 'Unknown error';
        }

        throw new \RuntimeException('JSON encoding failed: '.$msg);
    }

    /**
     * Parses json string and returns hash.
     *
     * @param null|string $json json string
     * @param string $file the json file
     *
     * @throws ParsingException
     * @return mixed
     */
    public static function parseJson(?string $json, ?string $file = null)
    {
        if (null === $json) {
            return null;
        }
        $data = json_decode($json, true);
        if (null === $data && JSON_ERROR_NONE !== json_last_error()) {
            self::validateSyntax($json, $file);
        }

        return $data;
    }

    /**
     * Validates the syntax of a JSON string
     *
     * @param  string                    $file
     * @throws \UnexpectedValueException
     * @throws ParsingException
     * @return bool                      true on success
     */
    protected static function validateSyntax(string $json, ?string $file = null): bool
    {
        $parser = new JsonParser();
        $result = $parser->lint($json);
        if (null === $result) {
            if (defined('JSON_ERROR_UTF8') && JSON_ERROR_UTF8 === json_last_error()) {
                if ($file === null) {
                    throw new \UnexpectedValueException('The input is not UTF-8, could not parse as JSON');
                } else {
                    throw new \UnexpectedValueException('"' . $file . '" is not UTF-8, could not parse as JSON');
                }
            }

            return true;
        }

        if ($file === null) {
            throw new ParsingException('The input does not contain valid JSON' . "\n" . $result->getMessage(),
                $result->getDetails());
        } else {
            throw new ParsingException('"' . $file . '" does not contain valid JSON' . "\n" . $result->getMessage(),
                $result->getDetails());
        }
    }

    public static function detectIndenting(?string $json): string
    {
        if (Preg::isMatchStrictGroups('#^([ \t]+)"#m', $json ?? '', $match)) {
            return $match[1];
        }
        return self::INDENT_DEFAULT;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Json;

use Composer\Pcre\Preg;

/**
 * Formats json strings used for php < 5.4 because the json_encode doesn't
 * supports the flags JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE
 * in these versions
 *
 * @author Konstantin Kudryashiv <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 *
 * @deprecated Use json_encode or JsonFile::encode() with modern JSON_* flags to configure formatting - this class will be removed in 3.0
 */
class JsonFormatter
{
    /**
     * This code is based on the function found at:
     *  http://recursive-design.com/blog/2008/03/11/format-json-with-php/
     *
     * Originally licensed under MIT by Dave Perrett <mail@recursive-design.com>
     *
     * @param  bool   $unescapeUnicode Un escape unicode
     * @param  bool   $unescapeSlashes Un escape slashes
     */
    public static function format(string $json, bool $unescapeUnicode, bool $unescapeSlashes): string
    {
        $result = '';
        $pos = 0;
        $strLen = strlen($json);
        $indentStr = '    ';
        $newLine = "\n";
        $outOfQuotes = true;
        $buffer = '';
        $noescape = true;

        for ($i = 0; $i < $strLen; $i++) {
            // Grab the next character in the string
            $char = substr($json, $i, 1);

            // Are we inside a quoted string?
            if ('"' === $char && $noescape) {
                $outOfQuotes = !$outOfQuotes;
            }

            if (!$outOfQuotes) {
                $buffer .= $char;
                $noescape = '\\' === $char ? !$noescape : true;
                continue;
            }
            if ('' !== $buffer) {
                if ($unescapeSlashes) {
                    $buffer = str_replace('\\/', '/', $buffer);
                }

                if ($unescapeUnicode && function_exists('mb_convert_encoding')) {
                    // https://stackoverflow.com/questions/2934563/how-to-decode-unicode-escape-sequences-like-u00ed-to-proper-utf-8-encoded-cha
                    $buffer = Preg::replaceCallback('/(\\\\+)u([0-9a-f]{4})/i', static function ($match): string {
                        $l = strlen($match[1]);

                        if ($l % 2) {
                            $code = hexdec($match[2]);
                            // 0xD800..0xDFFF denotes UTF-16 surrogate pair which won't be unescaped
                            // see https://github.com/composer/composer/issues/7510
                            if (0xD800 <= $code && 0xDFFF >= $code) {
                                return $match[0];
                            }

                            return str_repeat('\\', $l - 1) . mb_convert_encoding(
                                pack('H*', $match[2]),
                                'UTF-8',
                                'UCS-2BE'
                            );
                        }

                        return $match[0];
                    }, $buffer);
                }

                $result .= $buffer.$char;
                $buffer = '';
                continue;
            }

            if (':' === $char) {
                // Add a space after the : character
                $char .= ' ';
            } elseif ('}' === $char || ']' === $char) {
                $pos--;
                $prevChar = substr($json, $i - 1, 1);

                if ('{' !== $prevChar && '[' !== $prevChar) {
                    // If this character is the end of an element,
                    // output a new line and indent the next line
                    $result .= $newLine;
                    $result .= str_repeat($indentStr, $pos);
                } else {
                    // Collapse empty {} and []
                    $result = rtrim($result);
                }
            }

            $result .= $char;

            // If the last character was the beginning of an element,
            // output a new line and indent the next line
            if (',' === $char || '{' === $char || '[' === $char) {
                $result .= $newLine;

                if ('{' === $char || '[' === $char) {
                    $pos++;
                }

                $result .= str_repeat($indentStr, $pos);
            }
        }

        return $result;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Json;

use Exception;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class JsonValidationException extends Exception
{
    /**
     * @var string[]
     */
    protected $errors;

    /**
     * @param string[] $errors
     */
    public function __construct(string $message, array $errors = [], ?Exception $previous = null)
    {
        $this->errors = $errors;
        parent::__construct((string) $message, 0, $previous);
    }

    /**
     * @return string[]
     */
    public function getErrors(): array
    {
        return $this->errors;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Json;

use Composer\Pcre\Preg;
use Composer\Repository\PlatformRepository;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class JsonManipulator
{
    /** @var string */
    private const DEFINES = '(?(DEFINE)
       (?<number>    -? (?= [1-9]|0(?!\d) ) \d++ (?:\.\d++)? (?:[eE] [+-]?+ \d++)? )
       (?<boolean>   true | false | null )
       (?<string>    " (?:[^"\\\\]*+ | \\\\ ["\\\\bfnrt\/] | \\\\ u [0-9A-Fa-f]{4} )* " )
       (?<array>     \[  (?:  (?&json) \s*+ (?: , (?&json) \s*+ )*+  )?+  \s*+ \] )
       (?<pair>      \s*+ (?&string) \s*+ : (?&json) \s*+ )
       (?<object>    \{  (?:  (?&pair)  (?: , (?&pair)  )*+  )?+  \s*+ \} )
       (?<json>      \s*+ (?: (?&number) | (?&boolean) | (?&string) | (?&array) | (?&object) ) )
    )';

    /** @var string */
    private $contents;
    /** @var string */
    private $newline;
    /** @var string */
    private $indent;

    public function __construct(string $contents)
    {
        $contents = trim($contents);
        if ($contents === '') {
            $contents = '{}';
        }
        if (!Preg::isMatch('#^\{(.*)\}$#s', $contents)) {
            throw new \InvalidArgumentException('The json file must be an object ({})');
        }
        $this->newline = false !== strpos($contents, "\r\n") ? "\r\n" : "\n";
        $this->contents = $contents === '{}' ? '{' . $this->newline . '}' : $contents;
        $this->detectIndenting();
    }

    public function getContents(): string
    {
        return $this->contents . $this->newline;
    }

    public function addLink(string $type, string $package, string $constraint, bool $sortPackages = false): bool
    {
        $decoded = JsonFile::parseJson($this->contents);

        // no link of that type yet
        if (!isset($decoded[$type])) {
            return $this->addMainKey($type, [$package => $constraint]);
        }

        $regex = '{'.self::DEFINES.'^(?P<start>\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?)'.
            '(?P<property>'.preg_quote(JsonFile::encode($type)).'\s*:\s*)(?P<value>(?&json))(?P<end>.*)}sx';
        if (!Preg::isMatch($regex, $this->contents, $matches)) {
            return false;
        }
        assert(is_string($matches['start']));
        assert(is_string($matches['value']));
        assert(is_string($matches['end']));

        $links = $matches['value'];

        // try to find existing link
        $packageRegex = str_replace('/', '\\\\?/', preg_quote($package));
        $regex = '{'.self::DEFINES.'"(?P<package>'.$packageRegex.')"(\s*:\s*)(?&string)}ix';
        if (Preg::isMatch($regex, $links, $packageMatches)) {
            assert(is_string($packageMatches['package']));
            // update existing link
            $existingPackage = $packageMatches['package'];
            $packageRegex = str_replace('/', '\\\\?/', preg_quote($existingPackage));
            $links = Preg::replaceCallback('{'.self::DEFINES.'"'.$packageRegex.'"(?P<separator>\s*:\s*)(?&string)}ix', static function ($m) use ($existingPackage, $constraint): string {
                return JsonFile::encode(str_replace('\\/', '/', $existingPackage)) . $m['separator'] . '"' . $constraint . '"';
            }, $links);
        } else {
            if (Preg::isMatchStrictGroups('#^\s*\{\s*\S+.*?(\s*\}\s*)$#s', $links, $match)) {
                // link missing but non empty links
                $links = Preg::replace(
                    '{'.preg_quote($match[1]).'$}',
                    // addcslashes is used to double up backslashes/$ since preg_replace resolves them as back references otherwise, see #1588
                    addcslashes(',' . $this->newline . $this->indent . $this->indent . JsonFile::encode($package).': '.JsonFile::encode($constraint) . $match[1], '\\$'),
                    $links
                );
            } else {
                // links empty
                $links = '{' . $this->newline .
                    $this->indent . $this->indent . JsonFile::encode($package).': '.JsonFile::encode($constraint) . $this->newline .
                    $this->indent . '}';
            }
        }

        if (true === $sortPackages) {
            $requirements = json_decode($links, true);
            $this->sortPackages($requirements);
            $links = $this->format($requirements);
        }

        $this->contents = $matches['start'] . $matches['property'] . $links . $matches['end'];

        return true;
    }

    /**
     * Sorts packages by importance (platform packages first, then PHP dependencies) and alphabetically.
     *
     * @link https://getcomposer.org/doc/02-libraries.md#platform-packages
     *
     * @param array<string> $packages
     */
    private function sortPackages(array &$packages = []): void
    {
        $prefix = static function ($requirement): string {
            if (PlatformRepository::isPlatformPackage($requirement)) {
                return Preg::replace(
                    [
                        '/^php/',
                        '/^hhvm/',
                        '/^ext/',
                        '/^lib/',
                        '/^\D/',
                    ],
                    [
                        '0-$0',
                        '1-$0',
                        '2-$0',
                        '3-$0',
                        '4-$0',
                    ],
                    $requirement
                );
            }

            return '5-'.$requirement;
        };

        uksort($packages, static function ($a, $b) use ($prefix): int {
            return strnatcmp($prefix($a), $prefix($b));
        });
    }

    /**
     * @param array<string, mixed>|false $config
     */
    public function addRepository(string $name, $config, bool $append = true): bool
    {
        return $this->addSubNode('repositories', $name, $config, $append);
    }

    public function removeRepository(string $name): bool
    {
        return $this->removeSubNode('repositories', $name);
    }

    /**
     * @param mixed  $value
     */
    public function addConfigSetting(string $name, $value): bool
    {
        return $this->addSubNode('config', $name, $value);
    }

    public function removeConfigSetting(string $name): bool
    {
        return $this->removeSubNode('config', $name);
    }

    /**
     * @param mixed $value
     */
    public function addProperty(string $name, $value): bool
    {
        if (strpos($name, 'suggest.') === 0) {
            return $this->addSubNode('suggest', substr($name, 8), $value);
        }

        if (strpos($name, 'extra.') === 0) {
            return $this->addSubNode('extra', substr($name, 6), $value);
        }

        if (strpos($name, 'scripts.') === 0) {
            return $this->addSubNode('scripts', substr($name, 8), $value);
        }

        return $this->addMainKey($name, $value);
    }

    public function removeProperty(string $name): bool
    {
        if (strpos($name, 'suggest.') === 0) {
            return $this->removeSubNode('suggest', substr($name, 8));
        }

        if (strpos($name, 'extra.') === 0) {
            return $this->removeSubNode('extra', substr($name, 6));
        }

        if (strpos($name, 'scripts.') === 0) {
            return $this->removeSubNode('scripts', substr($name, 8));
        }

        if (strpos($name, 'autoload.') === 0) {
            return $this->removeSubNode('autoload', substr($name, 9));
        }

        if (strpos($name, 'autoload-dev.') === 0) {
            return $this->removeSubNode('autoload-dev', substr($name, 13));
        }

        return $this->removeMainKey($name);
    }

    /**
     * @param mixed  $value
     */
    public function addSubNode(string $mainNode, string $name, $value, bool $append = true): bool
    {
        $decoded = JsonFile::parseJson($this->contents);

        $subName = null;
        if (in_array($mainNode, ['config', 'extra', 'scripts']) && false !== strpos($name, '.')) {
            [$name, $subName] = explode('.', $name, 2);
        }

        // no main node yet
        if (!isset($decoded[$mainNode])) {
            if ($subName !== null) {
                $this->addMainKey($mainNode, [$name => [$subName => $value]]);
            } else {
                $this->addMainKey($mainNode, [$name => $value]);
            }

            return true;
        }

        // main node content not match-able
        $nodeRegex = '{'.self::DEFINES.'^(?P<start> \s* \{ \s* (?: (?&string) \s* : (?&json) \s* , \s* )*?'.
            preg_quote(JsonFile::encode($mainNode)).'\s*:\s*)(?P<content>(?&object))(?P<end>.*)}sx';

        try {
            if (!Preg::isMatch($nodeRegex, $this->contents, $match)) {
                return false;
            }
        } catch (\RuntimeException $e) {
            if ($e->getCode() === PREG_BACKTRACK_LIMIT_ERROR) {
                return false;
            }
            throw $e;
        }

        assert(is_string($match['start']));
        assert(is_string($match['content']));
        assert(is_string($match['end']));

        $children = $match['content'];
        // invalid match due to un-regexable content, abort
        if (!@json_decode($children)) {
            return false;
        }

        // child exists
        $childRegex = '{'.self::DEFINES.'(?P<start>"'.preg_quote($name).'"\s*:\s*)(?P<content>(?&json))(?P<end>,?)}x';
        if (Preg::isMatch($childRegex, $children, $matches)) {
            $children = Preg::replaceCallback($childRegex, function ($matches) use ($subName, $value): string {
                if ($subName !== null && is_string($matches['content'])) {
                    $curVal = json_decode($matches['content'], true);
                    if (!is_array($curVal)) {
                        $curVal = [];
                    }
                    $curVal[$subName] = $value;
                    $value = $curVal;
                }

                return $matches['start'] . $this->format($value, 1) . $matches['end'];
            }, $children);
        } elseif (Preg::isMatch('#^\{(?P<leadingspace>\s*?)(?P<content>\S+.*?)?(?P<trailingspace>\s*)\}$#s', $children, $match)) {
            $whitespace = $match['trailingspace'];
            if (null !== $match['content']) {
                if ($subName !== null) {
                    $value = [$subName => $value];
                }

                // child missing but non empty children
                if ($append) {
                    $children = Preg::replace(
                        '#'.$whitespace.'}$#',
                        addcslashes(',' . $this->newline . $this->indent . $this->indent . JsonFile::encode($name).': '.$this->format($value, 1) . $whitespace . '}', '\\$'),
                        $children
                    );
                } else {
                    $whitespace = $match['leadingspace'];
                    $children = Preg::replace(
                        '#^{'.$whitespace.'#',
                        addcslashes('{' . $whitespace . JsonFile::encode($name).': '.$this->format($value, 1) . ',' . $this->newline . $this->indent . $this->indent, '\\$'),
                        $children
                    );
                }
            } else {
                if ($subName !== null) {
                    $value = [$subName => $value];
                }

                // children present but empty
                $children = '{' . $this->newline . $this->indent . $this->indent . JsonFile::encode($name).': '.$this->format($value, 1) . $whitespace . '}';
            }
        } else {
            throw new \LogicException('Nothing matched above for: '.$children);
        }

        $this->contents = Preg::replaceCallback($nodeRegex, static function ($m) use ($children): string {
            return $m['start'] . $children . $m['end'];
        }, $this->contents);

        return true;
    }

    public function removeSubNode(string $mainNode, string $name): bool
    {
        $decoded = JsonFile::parseJson($this->contents);

        // no node or empty node
        if (empty($decoded[$mainNode])) {
            return true;
        }

        // no node content match-able
        $nodeRegex = '{'.self::DEFINES.'^(?P<start> \s* \{ \s* (?: (?&string) \s* : (?&json) \s* , \s* )*?'.
            preg_quote(JsonFile::encode($mainNode)).'\s*:\s*)(?P<content>(?&object))(?P<end>.*)}sx';
        try {
            if (!Preg::isMatch($nodeRegex, $this->contents, $match)) {
                return false;
            }
        } catch (\RuntimeException $e) {
            if ($e->getCode() === PREG_BACKTRACK_LIMIT_ERROR) {
                return false;
            }
            throw $e;
        }

        assert(is_string($match['start']));
        assert(is_string($match['content']));
        assert(is_string($match['end']));

        $children = $match['content'];

        // invalid match due to un-regexable content, abort
        if (!@json_decode($children, true)) {
            return false;
        }

        $subName = null;
        if (in_array($mainNode, ['config', 'extra', 'scripts']) && false !== strpos($name, '.')) {
            [$name, $subName] = explode('.', $name, 2);
        }

        // no node to remove
        if (!isset($decoded[$mainNode][$name]) || ($subName && !isset($decoded[$mainNode][$name][$subName]))) {
            return true;
        }

        // try and find a match for the subkey
        $keyRegex = str_replace('/', '\\\\?/', preg_quote($name));
        if (Preg::isMatch('{"'.$keyRegex.'"\s*:}i', $children)) {
            // find best match for the value of "name"
            if (Preg::isMatchAll('{'.self::DEFINES.'"'.$keyRegex.'"\s*:\s*(?:(?&json))}x', $children, $matches)) {
                $bestMatch = '';
                foreach ($matches[0] as $match) {
                    assert(is_string($match));
                    if (strlen($bestMatch) < strlen($match)) {
                        $bestMatch = $match;
                    }
                }
                $childrenClean = Preg::replace('{,\s*'.preg_quote($bestMatch).'}i', '', $children, -1, $count);
                if (1 !== $count) {
                    $childrenClean = Preg::replace('{'.preg_quote($bestMatch).'\s*,?\s*}i', '', $childrenClean, -1, $count);
                    if (1 !== $count) {
                        return false;
                    }
                }
            }
        } else {
            $childrenClean = $children;
        }

        if (!isset($childrenClean)) {
            throw new \InvalidArgumentException("JsonManipulator: \$childrenClean is not defined. Please report at https://github.com/composer/composer/issues/new.");
        }

        // no child data left, $name was the only key in
        unset($match);
        if (Preg::isMatch('#^\{\s*?(?P<content>\S+.*?)?(?P<trailingspace>\s*)\}$#s', $childrenClean, $match)) {
            if (null === $match['content']) {
                $newline = $this->newline;
                $indent = $this->indent;

                $this->contents = Preg::replaceCallback($nodeRegex, static function ($matches) use ($indent, $newline): string {
                    return $matches['start'] . '{' . $newline . $indent . '}' . $matches['end'];
                }, $this->contents);

                // we have a subname, so we restore the rest of $name
                if ($subName !== null) {
                    $curVal = json_decode($children, true);
                    unset($curVal[$name][$subName]);
                    if ($curVal[$name] === []) {
                        $curVal[$name] = new \ArrayObject();
                    }
                    $this->addSubNode($mainNode, $name, $curVal[$name]);
                }

                return true;
            }
        }

        $this->contents = Preg::replaceCallback($nodeRegex, function ($matches) use ($name, $subName, $childrenClean): string {
            assert(is_string($matches['content']));
            if ($subName !== null) {
                $curVal = json_decode($matches['content'], true);
                unset($curVal[$name][$subName]);
                if ($curVal[$name] === []) {
                    $curVal[$name] = new \ArrayObject();
                }
                $childrenClean = $this->format($curVal, 0, true);
            }

            return $matches['start'] . $childrenClean . $matches['end'];
        }, $this->contents);

        return true;
    }

    /**
     * @param mixed  $content
     */
    public function addMainKey(string $key, $content): bool
    {
        $decoded = JsonFile::parseJson($this->contents);
        $content = $this->format($content);

        // key exists already
        $regex = '{'.self::DEFINES.'^(?P<start>\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?)'.
            '(?P<key>'.preg_quote(JsonFile::encode($key)).'\s*:\s*(?&json))(?P<end>.*)}sx';
        if (isset($decoded[$key]) && Preg::isMatch($regex, $this->contents, $matches)) {
            // invalid match due to un-regexable content, abort
            if (!@json_decode('{'.$matches['key'].'}')) {
                return false;
            }

            $this->contents = $matches['start'] . JsonFile::encode($key).': '.$content . $matches['end'];

            return true;
        }

        // append at the end of the file and keep whitespace
        if (Preg::isMatch('#[^{\s](\s*)\}$#', $this->contents, $match)) {
            $this->contents = Preg::replace(
                '#'.$match[1].'\}$#',
                addcslashes(',' . $this->newline . $this->indent . JsonFile::encode($key). ': '. $content . $this->newline . '}', '\\$'),
                $this->contents
            );

            return true;
        }

        // append at the end of the file
        $this->contents = Preg::replace(
            '#\}$#',
            addcslashes($this->indent . JsonFile::encode($key). ': '.$content . $this->newline . '}', '\\$'),
            $this->contents
        );

        return true;
    }

    public function removeMainKey(string $key): bool
    {
        $decoded = JsonFile::parseJson($this->contents);

        if (!array_key_exists($key, $decoded)) {
            return true;
        }

        // key exists already
        $regex = '{'.self::DEFINES.'^(?P<start>\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?)'.
            '(?P<removal>'.preg_quote(JsonFile::encode($key)).'\s*:\s*(?&json))\s*,?\s*(?P<end>.*)}sx';
        if (Preg::isMatch($regex, $this->contents, $matches)) {
            assert(is_string($matches['start']));
            assert(is_string($matches['removal']));
            assert(is_string($matches['end']));

            // invalid match due to un-regexable content, abort
            if (!@json_decode('{'.$matches['removal'].'}')) {
                return false;
            }

            // check that we are not leaving a dangling comma on the previous line if the last line was removed
            if (Preg::isMatchStrictGroups('#,\s*$#', $matches['start']) && Preg::isMatch('#^\}$#', $matches['end'])) {
                $matches['start'] = rtrim(Preg::replace('#,(\s*)$#', '$1', $matches['start']), $this->indent);
            }

            $this->contents = $matches['start'] . $matches['end'];
            if (Preg::isMatch('#^\{\s*\}\s*$#', $this->contents)) {
                $this->contents = "{\n}";
            }

            return true;
        }

        return false;
    }

    public function removeMainKeyIfEmpty(string $key): bool
    {
        $decoded = JsonFile::parseJson($this->contents);

        if (!array_key_exists($key, $decoded)) {
            return true;
        }

        if (is_array($decoded[$key]) && count($decoded[$key]) === 0) {
            return $this->removeMainKey($key);
        }

        return true;
    }

    /**
     * @param mixed $data
     */
    public function format($data, int $depth = 0, bool $wasObject = false): string
    {
        if ($data instanceof \stdClass || $data instanceof \ArrayObject) {
            $data = (array) $data;
            $wasObject = true;
        }

        if (is_array($data)) {
            if (\count($data) === 0) {
                return $wasObject ? '{' . $this->newline . str_repeat($this->indent, $depth + 1) . '}' : '[]';
            }

            if (array_is_list($data)) {
                foreach ($data as $key => $val) {
                    $data[$key] = $this->format($val, $depth + 1);
                }

                return '['.implode(', ', $data).']';
            }

            $out = '{' . $this->newline;
            $elems = [];
            foreach ($data as $key => $val) {
                $elems[] = str_repeat($this->indent, $depth + 2) . JsonFile::encode((string) $key). ': '.$this->format($val, $depth + 1);
            }

            return $out . implode(','.$this->newline, $elems) . $this->newline . str_repeat($this->indent, $depth + 1) . '}';
        }

        return JsonFile::encode($data);
    }

    protected function detectIndenting(): void
    {
        $this->indent = JsonFile::detectIndenting($this->contents);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Repository\RepositorySet;

/**
 * @author Nils Adermann <naderman@naderman.de>
 * @implements \IteratorAggregate<Rule>
 * @internal
 * @final
 */
class RuleSet implements \IteratorAggregate, \Countable
{
    // highest priority => lowest number
    public const TYPE_PACKAGE = 0;
    public const TYPE_REQUEST = 1;
    public const TYPE_LEARNED = 4;

    /**
     * READ-ONLY: Lookup table for rule id to rule object
     *
     * @var array<int, Rule>
     */
    public $ruleById = [];

    const TYPES = [
        self::TYPE_PACKAGE => 'PACKAGE',
        self::TYPE_REQUEST => 'REQUEST',
        self::TYPE_LEARNED => 'LEARNED',
    ];

    /** @var array<self::TYPE_*, Rule[]> */
    protected $rules;

    /** @var 0|positive-int */
    protected $nextRuleId = 0;

    /** @var array<int|string, Rule|Rule[]> */
    protected $rulesByHash = [];

    public function __construct()
    {
        foreach ($this->getTypes() as $type) {
            $this->rules[$type] = [];
        }
    }

    /**
     * @param self::TYPE_* $type
     */
    public function add(Rule $rule, $type): void
    {
        if (!isset(self::TYPES[$type])) {
            throw new \OutOfBoundsException('Unknown rule type: ' . $type);
        }

        $hash = $rule->getHash();

        // Do not add if rule already exists
        if (isset($this->rulesByHash[$hash])) {
            $potentialDuplicates = $this->rulesByHash[$hash];
            if (\is_array($potentialDuplicates)) {
                foreach ($potentialDuplicates as $potentialDuplicate) {
                    if ($rule->equals($potentialDuplicate)) {
                        return;
                    }
                }
            } else {
                if ($rule->equals($potentialDuplicates)) {
                    return;
                }
            }
        }

        if (!isset($this->rules[$type])) {
            $this->rules[$type] = [];
        }

        $this->rules[$type][] = $rule;
        $this->ruleById[$this->nextRuleId] = $rule;
        $rule->setType($type);

        $this->nextRuleId++;

        if (!isset($this->rulesByHash[$hash])) {
            $this->rulesByHash[$hash] = $rule;
        } elseif (\is_array($this->rulesByHash[$hash])) {
            $this->rulesByHash[$hash][] = $rule;
        } else {
            $originalRule = $this->rulesByHash[$hash];
            $this->rulesByHash[$hash] = [$originalRule, $rule];
        }
    }

    public function count(): int
    {
        return $this->nextRuleId;
    }

    public function ruleById(int $id): Rule
    {
        return $this->ruleById[$id];
    }

    /** @return array<self::TYPE_*, Rule[]> */
    public function getRules(): array
    {
        return $this->rules;
    }

    public function getIterator(): RuleSetIterator
    {
        return new RuleSetIterator($this->getRules());
    }

    /**
     * @param  self::TYPE_*|array<self::TYPE_*> $types
     */
    public function getIteratorFor($types): RuleSetIterator
    {
        if (!\is_array($types)) {
            $types = [$types];
        }

        $allRules = $this->getRules();

        /** @var array<self::TYPE_*, Rule[]> $rules */
        $rules = [];

        foreach ($types as $type) {
            $rules[$type] = $allRules[$type];
        }

        return new RuleSetIterator($rules);
    }

    /**
     * @param array<self::TYPE_*>|self::TYPE_* $types
     */
    public function getIteratorWithout($types): RuleSetIterator
    {
        if (!\is_array($types)) {
            $types = [$types];
        }

        $rules = $this->getRules();

        foreach ($types as $type) {
            unset($rules[$type]);
        }

        return new RuleSetIterator($rules);
    }

    /**
     * @return array{self::TYPE_PACKAGE, self::TYPE_REQUEST, self::TYPE_LEARNED}
     */
    public function getTypes(): array
    {
        $types = self::TYPES;

        return array_keys($types);
    }

    public function getPrettyString(?RepositorySet $repositorySet = null, ?Request $request = null, ?Pool $pool = null, bool $isVerbose = false): string
    {
        $string = "\n";
        foreach ($this->rules as $type => $rules) {
            $string .= str_pad(self::TYPES[$type], 8, ' ') . ": ";
            foreach ($rules as $rule) {
                $string .= ($repositorySet !== null && $request !== null && $pool !== null ? $rule->getPrettyString($repositorySet, $request, $pool, $isVerbose) : $rule)."\n";
            }
            $string .= "\n\n";
        }

        return $string;
    }

    public function __toString(): string
    {
        return $this->getPrettyString();
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Package\CompletePackageInterface;
use Composer\Package\AliasPackage;
use Composer\Package\BasePackage;
use Composer\Package\Link;
use Composer\Package\PackageInterface;
use Composer\Package\RootPackageInterface;
use Composer\Pcre\Preg;
use Composer\Repository\RepositorySet;
use Composer\Repository\LockArrayRepository;
use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Package\Version\VersionParser;
use Composer\Repository\PlatformRepository;
use Composer\Semver\Constraint\MultiConstraint;

/**
 * Represents a problem detected while solving dependencies
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class Problem
{
    /**
     * A map containing the id of each rule part of this problem as a key
     * @var array<string, true>
     */
    protected $reasonSeen;

    /**
     * A set of reasons for the problem, each is a rule or a root require and a rule
     * @var array<int, array<int, Rule>>
     */
    protected $reasons = [];

    /** @var int */
    protected $section = 0;

    /**
     * Add a rule as a reason
     *
     * @param Rule $rule A rule which is a reason for this problem
     */
    public function addRule(Rule $rule): void
    {
        $this->addReason(spl_object_hash($rule), $rule);
    }

    /**
     * Retrieve all reasons for this problem
     *
     * @return array<int, array<int, Rule>> The problem's reasons
     */
    public function getReasons(): array
    {
        return $this->reasons;
    }

    /**
     * A human readable textual representation of the problem's reasons
     *
     * @param array<int|string, BasePackage> $installedMap A map of all present packages
     * @param array<Rule[]> $learnedPool
     */
    public function getPrettyString(RepositorySet $repositorySet, Request $request, Pool $pool, bool $isVerbose, array $installedMap = [], array $learnedPool = []): string
    {
        // TODO doesn't this entirely defeat the purpose of the problem sections? what's the point of sections?
        $reasons = array_merge(...array_reverse($this->reasons));

        if (\count($reasons) === 1) {
            reset($reasons);
            $rule = current($reasons);

            if ($rule->getReason() !== Rule::RULE_ROOT_REQUIRE) {
                throw new \LogicException("Single reason problems must contain a root require rule.");
            }

            $reasonData = $rule->getReasonData();
            $packageName = $reasonData['packageName'];
            $constraint = $reasonData['constraint'];

            $packages = $pool->whatProvides($packageName, $constraint);
            if (\count($packages) === 0) {
                return "\n    ".implode(self::getMissingPackageReason($repositorySet, $request, $pool, $isVerbose, $packageName, $constraint));
            }
        }

        usort($reasons, function (Rule $rule1, Rule $rule2) use ($pool) {
            $rule1Prio = $this->getRulePriority($rule1);
            $rule2Prio = $this->getRulePriority($rule2);
            if ($rule1Prio !== $rule2Prio) {
                return $rule2Prio - $rule1Prio;
            }

            return $this->getSortableString($pool, $rule1) <=> $this->getSortableString($pool, $rule2);
        });

        return self::formatDeduplicatedRules($reasons, '    ', $repositorySet, $request, $pool, $isVerbose, $installedMap, $learnedPool);
    }

    private function getSortableString(Pool $pool, Rule $rule): string
    {
        switch ($rule->getReason()) {
            case Rule::RULE_ROOT_REQUIRE:
                return $rule->getReasonData()['packageName'];
            case Rule::RULE_FIXED:
                return (string) $rule->getReasonData()['package'];
            case Rule::RULE_PACKAGE_CONFLICT:
            case Rule::RULE_PACKAGE_REQUIRES:
                return $rule->getSourcePackage($pool) . '//' . $rule->getReasonData()->getPrettyString($rule->getSourcePackage($pool));
            case Rule::RULE_PACKAGE_SAME_NAME:
            case Rule::RULE_PACKAGE_ALIAS:
            case Rule::RULE_PACKAGE_INVERSE_ALIAS:
                return (string) $rule->getReasonData();
            case Rule::RULE_LEARNED:
                return implode('-', $rule->getLiterals());
        }

        // @phpstan-ignore deadCode.unreachable
        throw new \LogicException('Unknown rule type: '.$rule->getReason());
    }

    private function getRulePriority(Rule $rule): int
    {
        switch ($rule->getReason()) {
            case Rule::RULE_FIXED:
                return 3;
            case Rule::RULE_ROOT_REQUIRE:
                return 2;
            case Rule::RULE_PACKAGE_CONFLICT:
            case Rule::RULE_PACKAGE_REQUIRES:
                return 1;
            case Rule::RULE_PACKAGE_SAME_NAME:
            case Rule::RULE_LEARNED:
            case Rule::RULE_PACKAGE_ALIAS:
            case Rule::RULE_PACKAGE_INVERSE_ALIAS:
                return 0;
        }

        // @phpstan-ignore deadCode.unreachable
        throw new \LogicException('Unknown rule type: '.$rule->getReason());
    }

    /**
     * @param Rule[] $rules
     * @param array<int|string, BasePackage> $installedMap A map of all present packages
     * @param array<Rule[]> $learnedPool
     * @internal
     */
    public static function formatDeduplicatedRules(array $rules, string $indent, RepositorySet $repositorySet, Request $request, Pool $pool, bool $isVerbose, array $installedMap = [], array $learnedPool = []): string
    {
        $messages = [];
        $templates = [];
        $parser = new VersionParser;
        $deduplicatableRuleTypes = [Rule::RULE_PACKAGE_REQUIRES, Rule::RULE_PACKAGE_CONFLICT];
        foreach ($rules as $rule) {
            $message = $rule->getPrettyString($repositorySet, $request, $pool, $isVerbose, $installedMap, $learnedPool);
            if (in_array($rule->getReason(), $deduplicatableRuleTypes, true) && Preg::isMatchStrictGroups('{^(?P<package>\S+) (?P<version>\S+) (?P<type>requires|conflicts)}', $message, $m)) {
                $message = str_replace('%', '%%', $message);
                $template = Preg::replace('{^\S+ \S+ }', '%s%s ', $message);
                $messages[] = $template;
                $templates[$template][$m[1]][$parser->normalize($m[2])] = $m[2];
                $sourcePackage = $rule->getSourcePackage($pool);
                foreach ($pool->getRemovedVersionsByPackage(spl_object_hash($sourcePackage)) as $version => $prettyVersion) {
                    $templates[$template][$m[1]][$version] = $prettyVersion;
                }
            } elseif ($message !== '') {
                $messages[] = $message;
            }
        }

        $result = [];
        foreach (array_unique($messages) as $message) {
            if (isset($templates[$message])) {
                foreach ($templates[$message] as $package => $versions) {
                    uksort($versions, 'version_compare');
                    if (!$isVerbose) {
                        $versions = self::condenseVersionList($versions, 1);
                    }
                    if (\count($versions) > 1) {
                        // remove the s from requires/conflicts to correct grammar
                        $message = Preg::replace('{^(%s%s (?:require|conflict))s}', '$1', $message);
                        $result[] = sprintf($message, $package, '['.implode(', ', $versions).']');
                    } else {
                        $result[] = sprintf($message, $package, ' '.reset($versions));
                    }
                }
            } else {
                $result[] = $message;
            }
        }

        return "\n$indent- ".implode("\n$indent- ", $result);
    }

    public function isCausedByLock(RepositorySet $repositorySet, Request $request, Pool $pool): bool
    {
        foreach ($this->reasons as $sectionRules) {
            foreach ($sectionRules as $rule) {
                if ($rule->isCausedByLock($repositorySet, $request, $pool)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Store a reason descriptor but ignore duplicates
     *
     * @param string $id     A canonical identifier for the reason
     * @param Rule   $reason The reason descriptor
     */
    protected function addReason(string $id, Rule $reason): void
    {
        // TODO: if a rule is part of a problem description in two sections, isn't this going to remove a message
        // that is important to understand the issue?

        if (!isset($this->reasonSeen[$id])) {
            $this->reasonSeen[$id] = true;
            $this->reasons[$this->section][] = $reason;
        }
    }

    public function nextSection(): void
    {
        $this->section++;
    }

    /**
     * @internal
     * @return array{0: string, 1: string}
     */
    public static function getMissingPackageReason(RepositorySet $repositorySet, Request $request, Pool $pool, bool $isVerbose, string $packageName, ?ConstraintInterface $constraint = null): array
    {
        if (PlatformRepository::isPlatformPackage($packageName)) {
            // handle php/php-*/hhvm
            if (0 === stripos($packageName, 'php') || $packageName === 'hhvm') {
                $version = self::getPlatformPackageVersion($pool, $packageName, phpversion());

                $msg = "- Root composer.json requires ".$packageName.self::constraintToText($constraint).' but ';

                if (defined('HHVM_VERSION') || ($packageName === 'hhvm' && count($pool->whatProvides($packageName)) > 0)) {
                    return [$msg, 'your HHVM version does not satisfy that requirement.'];
                }

                if ($packageName === 'hhvm') {
                    return [$msg, 'HHVM was not detected on this machine, make sure it is in your PATH.'];
                }

                if (null === $version) {
                    return [$msg, 'the '.$packageName.' package is disabled by your platform config. Enable it again with "composer config platform.'.$packageName.' --unset".'];
                }

                return [$msg, 'your '.$packageName.' version ('. $version .') does not satisfy that requirement.'];
            }

            // handle php extensions
            if (0 === stripos($packageName, 'ext-')) {
                if (false !== strpos($packageName, ' ')) {
                    return ['- ', "PHP extension ".$packageName.' should be required as '.str_replace(' ', '-', $packageName).'.'];
                }

                $ext = substr($packageName, 4);
                $msg = "- Root composer.json requires PHP extension ".$packageName.self::constraintToText($constraint).' but ';

                $version = self::getPlatformPackageVersion($pool, $packageName, phpversion($ext) === false ? '0' : phpversion($ext));
                if (null === $version) {
                    $providersStr = self::getProvidersList($repositorySet, $packageName, 5);
                    if ($providersStr !== null) {
                        $providersStr = "\n\n      Alternatively you can require one of these packages that provide the extension (or parts of it):\n".
                            "      <warning>Keep in mind that the suggestions are automated and may not be valid or safe to use</warning>\n$providersStr";
                    }

                    if (extension_loaded($ext)) {
                        return [
                            $msg,
                            'the '.$packageName.' package is disabled by your platform config. Enable it again with "composer config platform.'.$packageName.' --unset".' . $providersStr,
                        ];
                    }

                    return [$msg, 'it is missing from your system. Install or enable PHP\'s '.$ext.' extension.' . $providersStr];
                }

                return [$msg, 'it has the wrong version installed ('.$version.').'];
            }

            // handle linked libs
            if (0 === stripos($packageName, 'lib-')) {
                if (strtolower($packageName) === 'lib-icu') {
                    $error = extension_loaded('intl') ? 'it has the wrong version installed, try upgrading the intl extension.' : 'it is missing from your system, make sure the intl extension is loaded.';

                    return ["- Root composer.json requires linked library ".$packageName.self::constraintToText($constraint).' but ', $error];
                }

                $providersStr = self::getProvidersList($repositorySet, $packageName, 5);
                if ($providersStr !== null) {
                    $providersStr = "\n\n      Alternatively you can require one of these packages that provide the library (or parts of it):\n".
                    "      <warning>Keep in mind that the suggestions are automated and may not be valid or safe to use</warning>\n$providersStr";
                }

                return ["- Root composer.json requires linked library ".$packageName.self::constraintToText($constraint).' but ', 'it has the wrong version installed or is missing from your system, make sure to load the extension providing it.'.$providersStr];
            }
        }

        $lockedPackage = null;
        foreach ($request->getLockedPackages() as $package) {
            if ($package->getName() === $packageName) {
                $lockedPackage = $package;
                if ($pool->isUnacceptableFixedOrLockedPackage($package)) {
                    return ["- ", $package->getPrettyName().' is fixed to '.$package->getPrettyVersion().' (lock file version) by a partial update but that version is rejected by your minimum-stability. Make sure you list it as an argument for the update command.'];
                }
                break;
            }
        }

        if ($constraint instanceof Constraint && $constraint->getOperator() === Constraint::STR_OP_EQ && Preg::isMatch('{^dev-.*#.*}', $constraint->getPrettyString())) {
            $newConstraint = Preg::replace('{ +as +([^,\s|]+)$}', '', $constraint->getPrettyString());
            $packages = $repositorySet->findPackages($packageName, new MultiConstraint([
                new Constraint(Constraint::STR_OP_EQ, $newConstraint),
                new Constraint(Constraint::STR_OP_EQ, str_replace('#', '+', $newConstraint))
            ], false));
            if (\count($packages) > 0) {
                return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).'. The # character in branch names is replaced by a + character. Make sure to require it as "'.str_replace('#', '+', $constraint->getPrettyString()).'".'];
            }
        }

        // first check if the actual requested package is found in normal conditions
        // if so it must mean it is rejected by another constraint than the one given here
        $packages = $repositorySet->findPackages($packageName, $constraint);
        if (\count($packages) > 0) {
            $rootReqs = $repositorySet->getRootRequires();
            if (isset($rootReqs[$packageName])) {
                $filtered = array_filter($packages, static function ($p) use ($rootReqs, $packageName): bool {
                    return $rootReqs[$packageName]->matches(new Constraint('==', $p->getVersion()));
                });
                if (0 === count($filtered)) {
                    return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but '.(self::hasMultipleNames($packages) ? 'these conflict' : 'it conflicts').' with your root composer.json require ('.$rootReqs[$packageName]->getPrettyString().').'];
                }
            }

            $tempReqs = $repositorySet->getTemporaryConstraints();
            foreach (reset($packages)->getNames() as $name) {
                if (isset($tempReqs[$name])) {
                    $filtered = array_filter($packages, static function ($p) use ($tempReqs, $name): bool {
                        return $tempReqs[$name]->matches(new Constraint('==', $p->getVersion()));
                    });
                    if (0 === count($filtered)) {
                        return ["- Root composer.json requires $name".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but '.(self::hasMultipleNames($packages) ? 'these conflict' : 'it conflicts').' with your temporary update constraint ('.$name.':'.$tempReqs[$name]->getPrettyString().').'];
                    }
                }
            }

            if ($lockedPackage !== null) {
                $fixedConstraint = new Constraint('==', $lockedPackage->getVersion());
                $filtered = array_filter($packages, static function ($p) use ($fixedConstraint): bool {
                    return $fixedConstraint->matches(new Constraint('==', $p->getVersion()));
                });
                if (0 === count($filtered)) {
                    return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but the package is fixed to '.$lockedPackage->getPrettyVersion().' (lock file version) by a partial update and that version does not match. Make sure you list it as an argument for the update command.'];
                }
            }

            $nonLockedPackages = array_filter($packages, static function ($p): bool {
                return !$p->getRepository() instanceof LockArrayRepository;
            });

            if (0 === \count($nonLockedPackages)) {
                return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' in the lock file but not in remote repositories, make sure you avoid updating this package to keep the one from the lock file.'];
            }

            return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but these were not loaded, likely because '.(self::hasMultipleNames($packages) ? 'they conflict' : 'it conflicts').' with another require.'];
        }

        // check if the package is found when bypassing stability checks
        $packages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES);
        if (\count($packages) > 0) {
            // we must first verify if a valid package would be found in a lower priority repository
            $allReposPackages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_SHADOWED_REPOSITORIES);
            if (\count($allReposPackages) > 0) {
                return self::computeCheckForLowerPrioRepo($pool, $isVerbose, $packageName, $packages, $allReposPackages, 'minimum-stability', $constraint);
            }

            return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but '.(self::hasMultipleNames($packages) ? 'these do' : 'it does').' not match your minimum-stability.'];
        }

        // check if the package is found when bypassing the constraint and stability checks
        $packages = $repositorySet->findPackages($packageName, null, RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES);
        if (\count($packages) > 0) {
            // we must first verify if a valid package would be found in a lower priority repository
            $allReposPackages = $repositorySet->findPackages($packageName, $constraint, RepositorySet::ALLOW_SHADOWED_REPOSITORIES);
            if (\count($allReposPackages) > 0) {
                return self::computeCheckForLowerPrioRepo($pool, $isVerbose, $packageName, $packages, $allReposPackages, 'constraint', $constraint);
            }

            $suffix = '';
            if ($constraint instanceof Constraint && $constraint->getVersion() === 'dev-master') {
                foreach ($packages as $candidate) {
                    if (in_array($candidate->getVersion(), ['dev-default', 'dev-main'], true)) {
                        $suffix = ' Perhaps dev-master was renamed to '.$candidate->getPrettyVersion().'?';
                        break;
                    }
                }
            }

            // check if the root package is a name match and hint the dependencies on root troubleshooting article
            $allReposPackages = $packages;
            $topPackage = reset($allReposPackages);
            if ($topPackage instanceof RootPackageInterface) {
                $suffix = ' See https://getcomposer.org/dep-on-root for details and assistance.';
            }

            return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ', 'found '.self::getPackageList($packages, $isVerbose, $pool, $constraint).' but '.(self::hasMultipleNames($packages) ? 'these do' : 'it does').' not match the constraint.' . $suffix];
        }

        if (!Preg::isMatch('{^[A-Za-z0-9_./-]+$}', $packageName)) {
            $illegalChars = Preg::replace('{[A-Za-z0-9_./-]+}', '', $packageName);

            return ["- Root composer.json requires $packageName, it ", 'could not be found, it looks like its name is invalid, "'.$illegalChars.'" is not allowed in package names.'];
        }

        $providersStr = self::getProvidersList($repositorySet, $packageName, 15);
        if ($providersStr !== null) {
            return ["- Root composer.json requires $packageName".self::constraintToText($constraint).", it ", "could not be found in any version, but the following packages provide it:\n".$providersStr."      Consider requiring one of these to satisfy the $packageName requirement."];
        }

        return ["- Root composer.json requires $packageName, it ", "could not be found in any version, there may be a typo in the package name."];
    }

    /**
     * @internal
     * @param PackageInterface[] $packages
     */
    public static function getPackageList(array $packages, bool $isVerbose, ?Pool $pool = null, ?ConstraintInterface $constraint = null, bool $useRemovedVersionGroup = false): string
    {
        $prepared = [];
        $hasDefaultBranch = [];
        foreach ($packages as $package) {
            $prepared[$package->getName()]['name'] = $package->getPrettyName();
            $prepared[$package->getName()]['versions'][$package->getVersion()] = $package->getPrettyVersion().($package instanceof AliasPackage ? ' (alias of '.$package->getAliasOf()->getPrettyVersion().')' : '');
            if ($pool !== null && $constraint !== null) {
                foreach ($pool->getRemovedVersions($package->getName(), $constraint) as $version => $prettyVersion) {
                    $prepared[$package->getName()]['versions'][$version] = $prettyVersion;
                }
            }
            if ($pool !== null && $useRemovedVersionGroup) {
                foreach ($pool->getRemovedVersionsByPackage(spl_object_hash($package)) as $version => $prettyVersion) {
                    $prepared[$package->getName()]['versions'][$version] = $prettyVersion;
                }
            }
            if ($package->isDefaultBranch()) {
                $hasDefaultBranch[$package->getName()] = true;
            }
        }

        $preparedStrings = [];
        foreach ($prepared as $name => $package) {
            // remove the implicit default branch alias to avoid cruft in the display
            if (isset($package['versions'][VersionParser::DEFAULT_BRANCH_ALIAS], $hasDefaultBranch[$name])) {
                unset($package['versions'][VersionParser::DEFAULT_BRANCH_ALIAS]);
            }

            uksort($package['versions'], 'version_compare');

            if (!$isVerbose) {
                $package['versions'] = self::condenseVersionList($package['versions'], 4);
            }
            $preparedStrings[] = $package['name'].'['.implode(', ', $package['versions']).']';
        }

        return implode(', ', $preparedStrings);
    }

    /**
     * @param  string $version the effective runtime version of the platform package
     * @return ?string a version string or null if it appears the package was artificially disabled
     */
    private static function getPlatformPackageVersion(Pool $pool, string $packageName, string $version): ?string
    {
        $available = $pool->whatProvides($packageName);

        if (\count($available) > 0) {
            $selected = null;
            foreach ($available as $pkg) {
                if ($pkg->getRepository() instanceof PlatformRepository) {
                    $selected = $pkg;
                    break;
                }
            }
            if ($selected === null) {
                $selected = reset($available);
            }

            // must be a package providing/replacing and not a real platform package
            if ($selected->getName() !== $packageName) {
                /** @var Link $link */
                foreach (array_merge(array_values($selected->getProvides()), array_values($selected->getReplaces())) as $link) {
                    if ($link->getTarget() === $packageName) {
                        return $link->getPrettyConstraint().' '.substr($link->getDescription(), 0, -1).'d by '.$selected->getPrettyString();
                    }
                }
            }

            $version = $selected->getPrettyVersion();
            $extra = $selected->getExtra();
            if ($selected instanceof CompletePackageInterface && isset($extra['config.platform']) && $extra['config.platform'] === true) {
                $version .= '; ' . str_replace('Package ', '', (string) $selected->getDescription());
            }
        } else {
            return null;
        }

        return $version;
    }

    /**
     * @param array<string|int, string> $versions an array of pretty versions, with normalized versions as keys
     * @return list<string> a list of pretty versions and '...' where versions were removed
     */
    private static function condenseVersionList(array $versions, int $max, int $maxDev = 16): array
    {
        if (count($versions) <= $max) {
            return array_values($versions);
        }

        $filtered = [];
        $byMajor = [];
        foreach ($versions as $version => $pretty) {
            if (0 === stripos((string) $version, 'dev-')) {
                $byMajor['dev'][] = $pretty;
            } else {
                $byMajor[Preg::replace('{^(\d+)\..*}', '$1', (string) $version)][] = $pretty;
            }
        }
        foreach ($byMajor as $majorVersion => $versionsForMajor) {
            $maxVersions = $majorVersion === 'dev' ? $maxDev : $max;
            if (count($versionsForMajor) > $maxVersions) {
                // output only 1st and last versions
                $filtered[] = $versionsForMajor[0];
                $filtered[] = '...';
                $filtered[] = $versionsForMajor[count($versionsForMajor) - 1];
            } else {
                $filtered = array_merge($filtered, $versionsForMajor);
            }
        }

        return $filtered;
    }

    /**
     * @param PackageInterface[] $packages
     */
    private static function hasMultipleNames(array $packages): bool
    {
        $name = null;
        foreach ($packages as $package) {
            if ($name === null || $name === $package->getName()) {
                $name = $package->getName();
            } else {
                return true;
            }
        }

        return false;
    }

    /**
     * @param non-empty-array<PackageInterface> $higherRepoPackages
     * @param non-empty-array<PackageInterface> $allReposPackages
     * @return array{0: string, 1: string}
     */
    private static function computeCheckForLowerPrioRepo(Pool $pool, bool $isVerbose, string $packageName, array $higherRepoPackages, array $allReposPackages, string $reason, ?ConstraintInterface $constraint = null): array
    {
        $nextRepoPackages = [];
        $nextRepo = null;

        foreach ($allReposPackages as $package) {
            if ($nextRepo === null || $nextRepo === $package->getRepository()) {
                $nextRepoPackages[] = $package;
                $nextRepo = $package->getRepository();
            } else {
                break;
            }
        }

        assert(null !== $nextRepo);

        if (\count($higherRepoPackages) > 0) {
            $topPackage = reset($higherRepoPackages);
            if ($topPackage instanceof RootPackageInterface) {
                return [
                    "- Root composer.json requires $packageName".self::constraintToText($constraint).', it is ',
                    'satisfiable by '.self::getPackageList($nextRepoPackages, $isVerbose, $pool, $constraint).' from '.$nextRepo->getRepoName().' but '.$topPackage->getPrettyName().' '.$topPackage->getPrettyVersion().' is the root package and cannot be modified. See https://getcomposer.org/dep-on-root for details and assistance.',
                ];
            }
        }

        if ($nextRepo instanceof LockArrayRepository) {
            $singular = count($higherRepoPackages) === 1;

            $suggestion = 'Make sure you either fix the '.$reason.' or avoid updating this package to keep the one present in the lock file ('.self::getPackageList($nextRepoPackages, $isVerbose, $pool, $constraint).').';
            // symlinked path repos cannot be locked so do not suggest keeping it locked
            if ($nextRepoPackages[0]->getDistType() === 'path') {
                $transportOptions = $nextRepoPackages[0]->getTransportOptions();
                if (!isset($transportOptions['symlink']) || $transportOptions['symlink'] !== false) {
                    $suggestion = 'Make sure you fix the '.$reason.' as packages installed from symlinked path repos are updated even in partial updates and the one from the lock file can thus not be used.';
                }
            }

            return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', ',
                'found ' . self::getPackageList($higherRepoPackages, $isVerbose, $pool, $constraint).' but ' . ($singular ? 'it does' : 'these do') . ' not match your '.$reason.' and ' . ($singular ? 'is' : 'are') . ' therefore not installable. '.$suggestion,
            ];
        }

        return ["- Root composer.json requires $packageName".self::constraintToText($constraint) . ', it is ', 'satisfiable by '.self::getPackageList($nextRepoPackages, $isVerbose, $pool, $constraint).' from '.$nextRepo->getRepoName().' but '.self::getPackageList($higherRepoPackages, $isVerbose, $pool, $constraint).' from '.reset($higherRepoPackages)->getRepository()->getRepoName().' has higher repository priority. The packages from the higher priority repository do not match your '.$reason.' and are therefore not installable. That repository is canonical so the lower priority repo\'s packages are not installable. See https://getcomposer.org/repoprio for details and assistance.'];
    }

    /**
     * Turns a constraint into text usable in a sentence describing a request
     */
    protected static function constraintToText(?ConstraintInterface $constraint = null): string
    {
        if ($constraint instanceof Constraint && $constraint->getOperator() === Constraint::STR_OP_EQ && !str_starts_with($constraint->getVersion(), 'dev-')) {
            if (!Preg::isMatch('{^\d+(?:\.\d+)*$}', $constraint->getPrettyString())) {
                return ' '.$constraint->getPrettyString() .' (exact version match)';
            }

            $versions = [$constraint->getPrettyString()];
            for ($i = 3 - substr_count($versions[0], '.'); $i > 0; $i--) {
                $versions[] = end($versions) . '.0';
            }

            return ' ' . $constraint->getPrettyString() . ' (exact version match: ' . (count($versions) > 1 ? implode(', ', array_slice($versions, 0, -1)) . ' or ' . end($versions) : $versions[0]) . ')';
        }

        return $constraint !== null ? ' '.$constraint->getPrettyString() : '';
    }

    private static function getProvidersList(RepositorySet $repositorySet, string $packageName, int $maxProviders): ?string
    {
        $providers = $repositorySet->getProviders($packageName);
        if (\count($providers) > 0) {
            $providersStr = implode(array_map(static function ($p): string {
                $description = $p['description'] !== '' && $p['description'] !== null ? ' '.substr($p['description'], 0, 100) : '';

                return '      - '.$p['name'].$description."\n";
            }, count($providers) > $maxProviders + 1 ? array_slice($providers, 0, $maxProviders) : $providers));
            if (count($providers) > $maxProviders + 1) {
                $providersStr .= '      ... and '.(count($providers) - $maxProviders).' more.'."\n";
            }

            return $providersStr;
        }

        return null;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
class SolverBugException extends \RuntimeException
{
    public function __construct(string $message)
    {
        parent::__construct(
            $message."\nThis exception was most likely caused by a bug in Composer.\n".
            "Please report the command you ran, the exact error you received, and your composer.json on https://github.com/composer/composer/issues - thank you!\n"
        );
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Package\BasePackage;
use Composer\Package\PackageInterface;
use Composer\Repository\LockArrayRepository;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\MatchAllConstraint;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
class Request
{
    /**
     * Identifies a partial update for listed packages only, all dependencies will remain at locked versions
     */
    public const UPDATE_ONLY_LISTED = 0;

    /**
     * Identifies a partial update for listed packages and recursively all their dependencies, however dependencies
     * also directly required by the root composer.json and their dependencies will remain at the locked version.
     */
    public const UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRE = 1;

    /**
     * Identifies a partial update for listed packages and recursively all their dependencies, even dependencies
     * also directly required by the root composer.json will be updated.
     */
    public const UPDATE_LISTED_WITH_TRANSITIVE_DEPS = 2;

    /** @var ?LockArrayRepository */
    protected $lockedRepository;
    /** @var array<string, ConstraintInterface> */
    protected $requires = [];
    /** @var array<string, BasePackage> */
    protected $fixedPackages = [];
    /** @var array<string, BasePackage> */
    protected $lockedPackages = [];
    /** @var array<string, BasePackage> */
    protected $fixedLockedPackages = [];
    /** @var array<string> */
    protected $updateAllowList = [];
    /** @var false|self::UPDATE_* */
    protected $updateAllowTransitiveDependencies = false;
    /** @var non-empty-list<string>|null */
    private $restrictedPackages = null;

    public function __construct(?LockArrayRepository $lockedRepository = null)
    {
        $this->lockedRepository = $lockedRepository;
    }

    public function requireName(string $packageName, ?ConstraintInterface $constraint = null): void
    {
        $packageName = strtolower($packageName);

        if ($constraint === null) {
            $constraint = new MatchAllConstraint();
        }
        if (isset($this->requires[$packageName])) {
            throw new \LogicException('Overwriting requires seems like a bug ('.$packageName.' '.$this->requires[$packageName]->getPrettyString().' => '.$constraint->getPrettyString().', check why it is happening, might be a root alias');
        }
        $this->requires[$packageName] = $constraint;
    }

    /**
     * Mark a package as currently present and having to remain installed
     *
     * This is used for platform packages which cannot be modified by Composer. A rule enforcing their installation is
     * generated for dependency resolution. Partial updates with dependencies cannot in any way modify these packages.
     */
    public function fixPackage(BasePackage $package): void
    {
        $this->fixedPackages[spl_object_hash($package)] = $package;
    }

    /**
     * Mark a package as locked to a specific version but removable
     *
     * This is used for lock file packages which need to be treated similar to fixed packages by the pool builder in
     * that by default they should really only have the currently present version loaded and no remote alternatives.
     *
     * However unlike fixed packages there will not be a special rule enforcing their installation for the solver, so
     * if nothing requires these packages they will be removed. Additionally in a partial update these packages can be
     * unlocked, meaning other versions can be installed if explicitly requested as part of the update.
     */
    public function lockPackage(BasePackage $package): void
    {
        $this->lockedPackages[spl_object_hash($package)] = $package;
    }

    /**
     * Marks a locked package fixed. So it's treated irremovable like a platform package.
     *
     * This is necessary for the composer install step which verifies the lock file integrity and should not allow
     * removal of any packages. At the same time lock packages there cannot simply be marked fixed, as error reporting
     * would then report them as platform packages, so this still marks them as locked packages at the same time.
     */
    public function fixLockedPackage(BasePackage $package): void
    {
        $this->fixedPackages[spl_object_hash($package)] = $package;
        $this->fixedLockedPackages[spl_object_hash($package)] = $package;
    }

    public function unlockPackage(BasePackage $package): void
    {
        unset($this->lockedPackages[spl_object_hash($package)]);
    }

    /**
     * @param array<string> $updateAllowList
     * @param false|self::UPDATE_* $updateAllowTransitiveDependencies
     */
    public function setUpdateAllowList(array $updateAllowList, $updateAllowTransitiveDependencies): void
    {
        $this->updateAllowList = $updateAllowList;
        $this->updateAllowTransitiveDependencies = $updateAllowTransitiveDependencies;
    }

    /**
     * @return array<string>
     */
    public function getUpdateAllowList(): array
    {
        return $this->updateAllowList;
    }

    public function getUpdateAllowTransitiveDependencies(): bool
    {
        return $this->updateAllowTransitiveDependencies !== self::UPDATE_ONLY_LISTED;
    }

    public function getUpdateAllowTransitiveRootDependencies(): bool
    {
        return $this->updateAllowTransitiveDependencies === self::UPDATE_LISTED_WITH_TRANSITIVE_DEPS;
    }

    /**
     * @return array<string, ConstraintInterface>
     */
    public function getRequires(): array
    {
        return $this->requires;
    }

    /**
     * @return array<string, BasePackage>
     */
    public function getFixedPackages(): array
    {
        return $this->fixedPackages;
    }

    public function isFixedPackage(BasePackage $package): bool
    {
        return isset($this->fixedPackages[spl_object_hash($package)]);
    }

    /**
     * @return array<string, BasePackage>
     */
    public function getLockedPackages(): array
    {
        return $this->lockedPackages;
    }

    public function isLockedPackage(PackageInterface $package): bool
    {
        return isset($this->lockedPackages[spl_object_hash($package)]) || isset($this->fixedLockedPackages[spl_object_hash($package)]);
    }

    /**
     * @return array<string, BasePackage>
     */
    public function getFixedOrLockedPackages(): array
    {
        return array_merge($this->fixedPackages, $this->lockedPackages);
    }

    /**
     * @return ($packageIds is true ? array<int, BasePackage> : array<string, BasePackage>)
     *
     * @TODO look into removing the packageIds option, the only place true is used
     *       is for the installed map in the solver problems.
     *       Some locked packages may not be in the pool,
     *       so they have a package->id of -1
     */
    public function getPresentMap(bool $packageIds = false): array
    {
        $presentMap = [];

        if ($this->lockedRepository !== null) {
            foreach ($this->lockedRepository->getPackages() as $package) {
                $presentMap[$packageIds ? $package->getId() : spl_object_hash($package)] = $package;
            }
        }

        foreach ($this->fixedPackages as $package) {
            $presentMap[$packageIds ? $package->getId() : spl_object_hash($package)] = $package;
        }

        return $presentMap;
    }

    /**
     * @return array<int, BasePackage>
     */
    public function getFixedPackagesMap(): array
    {
        $fixedPackagesMap = [];

        foreach ($this->fixedPackages as $package) {
            $fixedPackagesMap[$package->getId()] = $package;
        }

        return $fixedPackagesMap;
    }

    /**
     * @return ?LockArrayRepository
     */
    public function getLockedRepository(): ?LockArrayRepository
    {
        return $this->lockedRepository;
    }

    /**
     * Restricts the pool builder from loading other packages than those listed here
     *
     * @param non-empty-list<string> $names
     */
    public function restrictPackages(array $names): void
    {
        $this->restrictedPackages = $names;
    }

    /**
     * @return list<string>
     */
    public function getRestrictedPackages(): ?array
    {
        return $this->restrictedPackages;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Package\BasePackage;
use Composer\Package\Version\VersionParser;
use Composer\Semver\CompilingMatcher;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\Constraint;

/**
 * A package pool contains all packages for dependency resolution
 *
 * @author Nils Adermann <naderman@naderman.de>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Pool implements \Countable
{
    /** @var BasePackage[] */
    protected $packages = [];
    /** @var array<string, BasePackage[]> */
    protected $packageByName = [];
    /** @var VersionParser */
    protected $versionParser;
    /** @var array<string, array<string, BasePackage[]>> */
    protected $providerCache = [];
    /** @var BasePackage[] */
    protected $unacceptableFixedOrLockedPackages;
    /** @var array<string, array<string, string>> Map of package name => normalized version => pretty version */
    protected $removedVersions = [];
    /** @var array<string, array<string, string>> Map of package object hash => removed normalized versions => removed pretty version */
    protected $removedVersionsByPackage = [];

    /**
     * @param BasePackage[] $packages
     * @param BasePackage[] $unacceptableFixedOrLockedPackages
     * @param array<string, array<string, string>> $removedVersions
     * @param array<string, array<string, string>> $removedVersionsByPackage
     */
    public function __construct(array $packages = [], array $unacceptableFixedOrLockedPackages = [], array $removedVersions = [], array $removedVersionsByPackage = [])
    {
        $this->versionParser = new VersionParser;
        $this->setPackages($packages);
        $this->unacceptableFixedOrLockedPackages = $unacceptableFixedOrLockedPackages;
        $this->removedVersions = $removedVersions;
        $this->removedVersionsByPackage = $removedVersionsByPackage;
    }

    /**
     * @return array<string, string>
     */
    public function getRemovedVersions(string $name, ConstraintInterface $constraint): array
    {
        if (!isset($this->removedVersions[$name])) {
            return [];
        }

        $result = [];
        foreach ($this->removedVersions[$name] as $version => $prettyVersion) {
            if ($constraint->matches(new Constraint('==', $version))) {
                $result[$version] = $prettyVersion;
            }
        }

        return $result;
    }

    /**
     * @return array<string, string>
     */
    public function getRemovedVersionsByPackage(string $objectHash): array
    {
        if (!isset($this->removedVersionsByPackage[$objectHash])) {
            return [];
        }

        return $this->removedVersionsByPackage[$objectHash];
    }

    /**
     * @param BasePackage[] $packages
     */
    private function setPackages(array $packages): void
    {
        $id = 1;

        foreach ($packages as $package) {
            $this->packages[] = $package;

            $package->id = $id++;

            foreach ($package->getNames() as $provided) {
                $this->packageByName[$provided][] = $package;
            }
        }
    }

    /**
     * @return BasePackage[]
     */
    public function getPackages(): array
    {
        return $this->packages;
    }

    /**
     * Retrieves the package object for a given package id.
     */
    public function packageById(int $id): BasePackage
    {
        return $this->packages[$id - 1];
    }

    /**
     * Returns how many packages have been loaded into the pool
     */
    public function count(): int
    {
        return \count($this->packages);
    }

    /**
     * Searches all packages providing the given package name and match the constraint
     *
     * @param string $name The package name to be searched for
     * @param ?ConstraintInterface $constraint A constraint that all returned
     *                                         packages must match or null to return all
     * @return BasePackage[] A set of packages
     */
    public function whatProvides(string $name, ?ConstraintInterface $constraint = null): array
    {
        $key = (string) $constraint;
        if (isset($this->providerCache[$name][$key])) {
            return $this->providerCache[$name][$key];
        }

        return $this->providerCache[$name][$key] = $this->computeWhatProvides($name, $constraint);
    }

    /**
     * @param  string               $name       The package name to be searched for
     * @param  ?ConstraintInterface $constraint A constraint that all returned
     *                                          packages must match or null to return all
     * @return BasePackage[]
     */
    private function computeWhatProvides(string $name, ?ConstraintInterface $constraint = null): array
    {
        if (!isset($this->packageByName[$name])) {
            return [];
        }

        $matches = [];

        foreach ($this->packageByName[$name] as $candidate) {
            if ($this->match($candidate, $name, $constraint)) {
                $matches[] = $candidate;
            }
        }

        return $matches;
    }

    public function literalToPackage(int $literal): BasePackage
    {
        $packageId = abs($literal);

        return $this->packageById($packageId);
    }

    /**
     * @param array<int, BasePackage> $installedMap
     */
    public function literalToPrettyString(int $literal, array $installedMap): string
    {
        $package = $this->literalToPackage($literal);

        if (isset($installedMap[$package->id])) {
            $prefix = ($literal > 0 ? 'keep' : 'remove');
        } else {
            $prefix = ($literal > 0 ? 'install' : 'don\'t install');
        }

        return $prefix.' '.$package->getPrettyString();
    }

    /**
     * Checks if the package matches the given constraint directly or through
     * provided or replaced packages
     *
     * @param  string              $name       Name of the package to be matched
     */
    public function match(BasePackage $candidate, string $name, ?ConstraintInterface $constraint = null): bool
    {
        $candidateName = $candidate->getName();
        $candidateVersion = $candidate->getVersion();

        if ($candidateName === $name) {
            return $constraint === null || CompilingMatcher::match($constraint, Constraint::OP_EQ, $candidateVersion);
        }

        $provides = $candidate->getProvides();
        $replaces = $candidate->getReplaces();

        // aliases create multiple replaces/provides for one target so they can not use the shortcut below
        if (isset($replaces[0]) || isset($provides[0])) {
            foreach ($provides as $link) {
                if ($link->getTarget() === $name && ($constraint === null || $constraint->matches($link->getConstraint()))) {
                    return true;
                }
            }

            foreach ($replaces as $link) {
                if ($link->getTarget() === $name && ($constraint === null || $constraint->matches($link->getConstraint()))) {
                    return true;
                }
            }

            return false;
        }

        if (isset($provides[$name]) && ($constraint === null || $constraint->matches($provides[$name]->getConstraint()))) {
            return true;
        }

        if (isset($replaces[$name]) && ($constraint === null || $constraint->matches($replaces[$name]->getConstraint()))) {
            return true;
        }

        return false;
    }

    public function isUnacceptableFixedOrLockedPackage(BasePackage $package): bool
    {
        return \in_array($package, $this->unacceptableFixedOrLockedPackages, true);
    }

    /**
     * @return BasePackage[]
     */
    public function getUnacceptableFixedOrLockedPackages(): array
    {
        return $this->unacceptableFixedOrLockedPackages;
    }

    public function __toString(): string
    {
        $str = "Pool:\n";

        foreach ($this->packages as $package) {
            $str .= '- '.str_pad((string) $package->id, 6, ' ', STR_PAD_LEFT).': '.$package->getName()."\n";
        }

        return $str;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

/**
 * @author Nils Adermann <naderman@naderman.de>
 * @phpstan-import-type ReasonData from Rule
 */
class Rule2Literals extends Rule
{
    /** @var int */
    protected $literal1;
    /** @var int */
    protected $literal2;

    /**
     * @param Rule::RULE_* $reason A RULE_* constant
     * @param mixed $reasonData
     *
     * @phpstan-param ReasonData $reasonData
     */
    public function __construct(int $literal1, int $literal2, $reason, $reasonData)
    {
        parent::__construct($reason, $reasonData);

        if ($literal1 < $literal2) {
            $this->literal1 = $literal1;
            $this->literal2 = $literal2;
        } else {
            $this->literal1 = $literal2;
            $this->literal2 = $literal1;
        }
    }

    /**
     * @return non-empty-list<int>
     */
    public function getLiterals(): array
    {
        return [$this->literal1, $this->literal2];
    }

    /**
     * @inheritDoc
     */
    public function getHash()
    {
        return $this->literal1.','.$this->literal2;
    }

    /**
     * Checks if this rule is equal to another one
     *
     * Ignores whether either of the rules is disabled.
     *
     * @param  Rule $rule The rule to check against
     * @return bool Whether the rules are equal
     */
    public function equals(Rule $rule): bool
    {
        // specialized fast-case
        if ($rule instanceof self) {
            if ($this->literal1 !== $rule->literal1) {
                return false;
            }

            if ($this->literal2 !== $rule->literal2) {
                return false;
            }

            return true;
        }

        $literals = $rule->getLiterals();
        if (2 !== \count($literals)) {
            return false;
        }

        if ($this->literal1 !== $literals[0]) {
            return false;
        }

        if ($this->literal2 !== $literals[1]) {
            return false;
        }

        return true;
    }

    /** @return false */
    public function isAssertion(): bool
    {
        return false;
    }

    /**
     * Formats a rule as a string of the format (Literal1|Literal2|...)
     */
    public function __toString(): string
    {
        $result = $this->isDisabled() ? 'disabled(' : '(';

        $result .= $this->literal1 . '|' . $this->literal2 . ')';

        return $result;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Repository\InstalledRepositoryInterface;
use Composer\Repository\RepositoryInterface;

/**
 * @author Nils Adermann <naderman@naderman.de>
 * @internal
 */
class LocalRepoTransaction extends Transaction
{
    public function __construct(RepositoryInterface $lockedRepository, InstalledRepositoryInterface $localRepository)
    {
        parent::__construct(
            $localRepository->getPackages(),
            $lockedRepository->getPackages()
        );
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Package\AliasPackage;
use Composer\Package\BasePackage;
use Composer\Package\Link;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositorySet;
use Composer\Package\Version\VersionParser;
use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\ConstraintInterface;

/**
 * @author Nils Adermann <naderman@naderman.de>
 * @author Ruben Gonzalez <rubenrua@gmail.com>
 * @phpstan-type ReasonData Link|BasePackage|string|int|array{packageName: string, constraint: ConstraintInterface}|array{package: BasePackage}
 */
abstract class Rule
{
    // reason constants and // their reason data contents
    public const RULE_ROOT_REQUIRE = 2; // array{packageName: string, constraint: ConstraintInterface}
    public const RULE_FIXED = 3; // array{package: BasePackage}
    public const RULE_PACKAGE_CONFLICT = 6; // Link
    public const RULE_PACKAGE_REQUIRES = 7; // Link
    public const RULE_PACKAGE_SAME_NAME = 10; // string (package name)
    public const RULE_LEARNED = 12; // int (rule id)
    public const RULE_PACKAGE_ALIAS = 13; // BasePackage
    public const RULE_PACKAGE_INVERSE_ALIAS = 14; // BasePackage

    // bitfield defs
    private const BITFIELD_TYPE = 0;
    private const BITFIELD_REASON = 8;
    private const BITFIELD_DISABLED = 16;

    /** @var int */
    protected $bitfield;
    /** @var Request */
    protected $request;
    /**
     * @var Link|BasePackage|ConstraintInterface|string
     * @phpstan-var ReasonData
     */
    protected $reasonData;

    /**
     * @param self::RULE_* $reason     A RULE_* constant describing the reason for generating this rule
     * @param mixed        $reasonData
     *
     * @phpstan-param ReasonData $reasonData
     */
    public function __construct($reason, $reasonData)
    {
        $this->reasonData = $reasonData;

        $this->bitfield = (0 << self::BITFIELD_DISABLED) |
            ($reason << self::BITFIELD_REASON) |
            (255 << self::BITFIELD_TYPE);
    }

    /**
     * @return list<int>
     */
    abstract public function getLiterals(): array;

    /**
     * @return int|string
     */
    abstract public function getHash();

    abstract public function __toString(): string;

    abstract public function equals(Rule $rule): bool;

    /**
     * @return self::RULE_*
     */
    public function getReason(): int
    {
        return ($this->bitfield & (255 << self::BITFIELD_REASON)) >> self::BITFIELD_REASON;
    }

    /**
     * @phpstan-return ReasonData
     */
    public function getReasonData()
    {
        return $this->reasonData;
    }

    public function getRequiredPackage(): ?string
    {
        switch ($this->getReason()) {
            case self::RULE_ROOT_REQUIRE:
                return $this->getReasonData()['packageName'];
            case self::RULE_FIXED:
                return $this->getReasonData()['package']->getName();
            case self::RULE_PACKAGE_REQUIRES:
                return $this->getReasonData()->getTarget();
        }

        return null;
    }

    /**
     * @param RuleSet::TYPE_* $type
     */
    public function setType($type): void
    {
        $this->bitfield = ($this->bitfield & ~(255 << self::BITFIELD_TYPE)) | ((255 & $type) << self::BITFIELD_TYPE);
    }

    public function getType(): int
    {
        return ($this->bitfield & (255 << self::BITFIELD_TYPE)) >> self::BITFIELD_TYPE;
    }

    public function disable(): void
    {
        $this->bitfield = ($this->bitfield & ~(255 << self::BITFIELD_DISABLED)) | (1 << self::BITFIELD_DISABLED);
    }

    public function enable(): void
    {
        $this->bitfield &= ~(255 << self::BITFIELD_DISABLED);
    }

    public function isDisabled(): bool
    {
        return 0 !== (($this->bitfield & (255 << self::BITFIELD_DISABLED)) >> self::BITFIELD_DISABLED);
    }

    public function isEnabled(): bool
    {
        return 0 === (($this->bitfield & (255 << self::BITFIELD_DISABLED)) >> self::BITFIELD_DISABLED);
    }

    abstract public function isAssertion(): bool;

    public function isCausedByLock(RepositorySet $repositorySet, Request $request, Pool $pool): bool
    {
        if ($this->getReason() === self::RULE_PACKAGE_REQUIRES) {
            if (PlatformRepository::isPlatformPackage($this->getReasonData()->getTarget())) {
                return false;
            }
            if ($request->getLockedRepository() !== null) {
                foreach ($request->getLockedRepository()->getPackages() as $package) {
                    if ($package->getName() === $this->getReasonData()->getTarget()) {
                        if ($pool->isUnacceptableFixedOrLockedPackage($package)) {
                            return true;
                        }
                        if (!$this->getReasonData()->getConstraint()->matches(new Constraint('=', $package->getVersion()))) {
                            return true;
                        }
                        // required package was locked but has been unlocked and still matches
                        if (!$request->isLockedPackage($package)) {
                            return true;
                        }
                        break;
                    }
                }
            }
        }

        if ($this->getReason() === self::RULE_ROOT_REQUIRE) {
            if (PlatformRepository::isPlatformPackage($this->getReasonData()['packageName'])) {
                return false;
            }
            if ($request->getLockedRepository() !== null) {
                foreach ($request->getLockedRepository()->getPackages() as $package) {
                    if ($package->getName() === $this->getReasonData()['packageName']) {
                        if ($pool->isUnacceptableFixedOrLockedPackage($package)) {
                            return true;
                        }
                        if (!$this->getReasonData()['constraint']->matches(new Constraint('=', $package->getVersion()))) {
                            return true;
                        }
                        break;
                    }
                }
            }
        }

        return false;
    }

    /**
     * @internal
     */
    public function getSourcePackage(Pool $pool): BasePackage
    {
        $literals = $this->getLiterals();

        switch ($this->getReason()) {
            case self::RULE_PACKAGE_CONFLICT:
                $package1 = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[0]));
                $package2 = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[1]));

                $reasonData = $this->getReasonData();
                // swap literals if they are not in the right order with package2 being the conflicter
                if ($reasonData->getSource() === $package1->getName()) {
                    [$package2, $package1] = [$package1, $package2];
                }

                return $package2;

            case self::RULE_PACKAGE_REQUIRES:
                $sourceLiteral = $literals[0];
                $sourcePackage = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($sourceLiteral));

                return $sourcePackage;

            default:
                throw new \LogicException('Not implemented');
        }
    }

    /**
     * @param BasePackage[] $installedMap
     * @param array<Rule[]> $learnedPool
     */
    public function getPrettyString(RepositorySet $repositorySet, Request $request, Pool $pool, bool $isVerbose, array $installedMap = [], array $learnedPool = []): string
    {
        $literals = $this->getLiterals();

        switch ($this->getReason()) {
            case self::RULE_ROOT_REQUIRE:
                $reasonData = $this->getReasonData();
                $packageName = $reasonData['packageName'];
                $constraint = $reasonData['constraint'];

                $packages = $pool->whatProvides($packageName, $constraint);
                if (0 === \count($packages)) {
                    return 'No package found to satisfy root composer.json require '.$packageName.' '.$constraint->getPrettyString();
                }

                $packagesNonAlias = array_values(array_filter($packages, static function ($p): bool {
                    return !($p instanceof AliasPackage);
                }));
                if (\count($packagesNonAlias) === 1) {
                    $package = $packagesNonAlias[0];
                    if ($request->isLockedPackage($package)) {
                        return $package->getPrettyName().' is locked to version '.$package->getPrettyVersion()." and an update of this package was not requested.";
                    }
                }

                return 'Root composer.json requires '.$packageName.' '.$constraint->getPrettyString().' -> satisfiable by '.$this->formatPackagesUnique($pool, $packages, $isVerbose, $constraint).'.';

            case self::RULE_FIXED:
                $package = $this->deduplicateDefaultBranchAlias($this->getReasonData()['package']);

                if ($request->isLockedPackage($package)) {
                    return $package->getPrettyName().' is locked to version '.$package->getPrettyVersion().' and an update of this package was not requested.';
                }

                return $package->getPrettyName().' is present at version '.$package->getPrettyVersion() . ' and cannot be modified by Composer';

            case self::RULE_PACKAGE_CONFLICT:
                $package1 = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[0]));
                $package2 = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[1]));

                $conflictTarget = $package1->getPrettyString();
                $reasonData = $this->getReasonData();

                // swap literals if they are not in the right order with package2 being the conflicter
                if ($reasonData->getSource() === $package1->getName()) {
                    [$package2, $package1] = [$package1, $package2];
                    $conflictTarget = $package1->getPrettyName().' '.$reasonData->getPrettyConstraint();
                }

                // if the conflict is not directly against the package but something it provides/replaces,
                // we try to find that link to display a better message
                if ($reasonData->getTarget() !== $package1->getName()) {
                    $provideType = null;
                    $provided = null;
                    foreach ($package1->getProvides() as $provide) {
                        if ($provide->getTarget() === $reasonData->getTarget()) {
                            $provideType = 'provides';
                            $provided = $provide->getPrettyConstraint();
                            break;
                        }
                    }
                    foreach ($package1->getReplaces() as $replace) {
                        if ($replace->getTarget() === $reasonData->getTarget()) {
                            $provideType = 'replaces';
                            $provided = $replace->getPrettyConstraint();
                            break;
                        }
                    }
                    if (null !== $provideType) {
                        $conflictTarget = $reasonData->getTarget().' '.$reasonData->getPrettyConstraint().' ('.$package1->getPrettyString().' '.$provideType.' '.$reasonData->getTarget().' '.$provided.')';
                    }
                }

                return $package2->getPrettyString().' conflicts with '.$conflictTarget.'.';

            case self::RULE_PACKAGE_REQUIRES:
                assert(\count($literals) > 0);
                $sourceLiteral = array_shift($literals);
                $sourcePackage = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($sourceLiteral));
                $reasonData = $this->getReasonData();

                $requires = [];
                foreach ($literals as $literal) {
                    $requires[] = $pool->literalToPackage($literal);
                }

                $text = $reasonData->getPrettyString($sourcePackage);
                if (\count($requires) > 0) {
                    $text .= ' -> satisfiable by ' . $this->formatPackagesUnique($pool, $requires, $isVerbose, $reasonData->getConstraint()) . '.';
                } else {
                    $targetName = $reasonData->getTarget();

                    $reason = Problem::getMissingPackageReason($repositorySet, $request, $pool, $isVerbose, $targetName, $reasonData->getConstraint());

                    return $text . ' -> ' . $reason[1];
                }

                return $text;

            case self::RULE_PACKAGE_SAME_NAME:
                $packageNames = [];
                foreach ($literals as $literal) {
                    $package = $pool->literalToPackage($literal);
                    $packageNames[$package->getName()] = true;
                }
                unset($literal);
                $replacedName = $this->getReasonData();

                if (\count($packageNames) > 1) {
                    if (!isset($packageNames[$replacedName])) {
                        $reason = 'They '.(\count($literals) === 2 ? 'both' : 'all').' replace '.$replacedName.' and thus cannot coexist.';
                    } else {
                        $replacerNames = $packageNames;
                        unset($replacerNames[$replacedName]);
                        $replacerNames = array_keys($replacerNames);

                        if (\count($replacerNames) === 1) {
                            $reason = $replacerNames[0] . ' replaces ';
                        } else {
                            $reason = '['.implode(', ', $replacerNames).'] replace ';
                        }
                        $reason .= $replacedName.' and thus cannot coexist with it.';
                    }

                    $installedPackages = [];
                    $removablePackages = [];
                    foreach ($literals as $literal) {
                        if (isset($installedMap[abs($literal)])) {
                            $installedPackages[] = $pool->literalToPackage($literal);
                        } else {
                            $removablePackages[] = $pool->literalToPackage($literal);
                        }
                    }

                    if (\count($installedPackages) > 0 && \count($removablePackages) > 0) {
                        return $this->formatPackagesUnique($pool, $removablePackages, $isVerbose, null, true).' cannot be installed as that would require removing '.$this->formatPackagesUnique($pool, $installedPackages, $isVerbose, null, true).'. '.$reason;
                    }

                    return 'Only one of these can be installed: '.$this->formatPackagesUnique($pool, $literals, $isVerbose, null, true).'. '.$reason;
                }

                return 'You can only install one version of a package, so only one of these can be installed: ' . $this->formatPackagesUnique($pool, $literals, $isVerbose, null, true) . '.';
            case self::RULE_LEARNED:
                /** @TODO currently still generates way too much output to be helpful, and in some cases can even lead to endless recursion */
                // if (isset($learnedPool[$this->getReasonData()])) {
                //     echo $this->getReasonData()."\n";
                //     $learnedString = ', learned rules:' . Problem::formatDeduplicatedRules($learnedPool[$this->getReasonData()], '        ', $repositorySet, $request, $pool, $isVerbose, $installedMap, $learnedPool);
                // } else {
                //     $learnedString = ' (reasoning unavailable)';
                // }
                $learnedString = ' (conflict analysis result)';

                if (\count($literals) === 1) {
                    $ruleText = $pool->literalToPrettyString($literals[0], $installedMap);
                } else {
                    $groups = [];
                    foreach ($literals as $literal) {
                        $package = $pool->literalToPackage($literal);
                        if (isset($installedMap[$package->id])) {
                            $group = $literal > 0 ? 'keep' : 'remove';
                        } else {
                            $group = $literal > 0 ? 'install' : 'don\'t install';
                        }

                        $groups[$group][] = $this->deduplicateDefaultBranchAlias($package);
                    }
                    $ruleTexts = [];
                    foreach ($groups as $group => $packages) {
                        $ruleTexts[] = $group . (\count($packages) > 1 ? ' one of' : '').' ' . $this->formatPackagesUnique($pool, $packages, $isVerbose);
                    }

                    $ruleText = implode(' | ', $ruleTexts);
                }

                return 'Conclusion: '.$ruleText.$learnedString;
            case self::RULE_PACKAGE_ALIAS:
                $aliasPackage = $pool->literalToPackage($literals[0]);

                // avoid returning content like "9999999-dev is an alias of dev-master" as it is useless
                if ($aliasPackage->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) {
                    return '';
                }
                $package = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[1]));

                return $aliasPackage->getPrettyString() .' is an alias of '.$package->getPrettyString().' and thus requires it to be installed too.';
            case self::RULE_PACKAGE_INVERSE_ALIAS:
                // inverse alias rules work the other way around than above
                $aliasPackage = $pool->literalToPackage($literals[1]);

                // avoid returning content like "9999999-dev is an alias of dev-master" as it is useless
                if ($aliasPackage->getVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) {
                    return '';
                }
                $package = $this->deduplicateDefaultBranchAlias($pool->literalToPackage($literals[0]));

                return $aliasPackage->getPrettyString() .' is an alias of '.$package->getPrettyString().' and must be installed with it.';
            default:
                $ruleText = '';
                foreach ($literals as $i => $literal) {
                    if ($i !== 0) {
                        $ruleText .= '|';
                    }
                    $ruleText .= $pool->literalToPrettyString($literal, $installedMap);
                }

                return '('.$ruleText.')';
        }
    }

    /**
     * @param array<int|BasePackage> $literalsOrPackages An array containing packages or literals
     */
    protected function formatPackagesUnique(Pool $pool, array $literalsOrPackages, bool $isVerbose, ?ConstraintInterface $constraint = null, bool $useRemovedVersionGroup = false): string
    {
        $packages = [];
        foreach ($literalsOrPackages as $package) {
            $packages[] = \is_object($package) ? $package : $pool->literalToPackage($package);
        }

        return Problem::getPackageList($packages, $isVerbose, $pool, $constraint, $useRemovedVersionGroup);
    }

    private function deduplicateDefaultBranchAlias(BasePackage $package): BasePackage
    {
        if ($package instanceof AliasPackage && $package->getPrettyVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) {
            $package = $package->getAliasOf();
        }

        return $package;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver\Operation;

use Composer\Package\AliasPackage;

/**
 * Solver install operation.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class MarkAliasInstalledOperation extends SolverOperation implements OperationInterface
{
    protected const TYPE = 'markAliasInstalled';

    /**
     * @var AliasPackage
     */
    protected $package;

    public function __construct(AliasPackage $package)
    {
        $this->package = $package;
    }

    /**
     * Returns package instance.
     */
    public function getPackage(): AliasPackage
    {
        return $this->package;
    }

    /**
     * @inheritDoc
     */
    public function show($lock): string
    {
        return 'Marking <info>'.$this->package->getPrettyName().'</info> (<comment>'.$this->package->getFullPrettyVersion().'</comment>) as installed, alias of <info>'.$this->package->getAliasOf()->getPrettyName().'</info> (<comment>'.$this->package->getAliasOf()->getFullPrettyVersion().'</comment>)';
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver\Operation;

use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionParser;

/**
 * Solver update operation.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class UpdateOperation extends SolverOperation implements OperationInterface
{
    protected const TYPE = 'update';

    /**
     * @var PackageInterface
     */
    protected $initialPackage;

    /**
     * @var PackageInterface
     */
    protected $targetPackage;

    /**
     * @param PackageInterface $initial initial package
     * @param PackageInterface $target  target package (updated)
     */
    public function __construct(PackageInterface $initial, PackageInterface $target)
    {
        $this->initialPackage = $initial;
        $this->targetPackage = $target;
    }

    /**
     * Returns initial package.
     */
    public function getInitialPackage(): PackageInterface
    {
        return $this->initialPackage;
    }

    /**
     * Returns target package.
     */
    public function getTargetPackage(): PackageInterface
    {
        return $this->targetPackage;
    }

    /**
     * @inheritDoc
     */
    public function show($lock): string
    {
        return self::format($this->initialPackage, $this->targetPackage, $lock);
    }

    public static function format(PackageInterface $initialPackage, PackageInterface $targetPackage, bool $lock = false): string
    {
        $fromVersion = $initialPackage->getFullPrettyVersion();
        $toVersion = $targetPackage->getFullPrettyVersion();

        if ($fromVersion === $toVersion && $initialPackage->getSourceReference() !== $targetPackage->getSourceReference()) {
            $fromVersion = $initialPackage->getFullPrettyVersion(true, PackageInterface::DISPLAY_SOURCE_REF);
            $toVersion = $targetPackage->getFullPrettyVersion(true, PackageInterface::DISPLAY_SOURCE_REF);
        } elseif ($fromVersion === $toVersion && $initialPackage->getDistReference() !== $targetPackage->getDistReference()) {
            $fromVersion = $initialPackage->getFullPrettyVersion(true, PackageInterface::DISPLAY_DIST_REF);
            $toVersion = $targetPackage->getFullPrettyVersion(true, PackageInterface::DISPLAY_DIST_REF);
        }

        $actionName = VersionParser::isUpgrade($initialPackage->getVersion(), $targetPackage->getVersion()) ? 'Upgrading' : 'Downgrading';

        return $actionName.' <info>'.$initialPackage->getPrettyName().'</info> (<comment>'.$fromVersion.'</comment> => <comment>'.$toVersion.'</comment>)';
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver\Operation;

/**
 * Abstract operation class.
 *
 * @author Aleksandr Bezpiatov <aleksandr.bezpiatov@spryker.com>
 */
abstract class SolverOperation implements OperationInterface
{
    /**
     * @abstract must be redefined by extending classes
     */
    protected const TYPE = '';

    /**
     * Returns operation type.
     */
    public function getOperationType(): string
    {
        return static::TYPE;
    }

    /**
     * @inheritDoc
     */
    public function __toString()
    {
        return $this->show(false);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver\Operation;

use Composer\Package\PackageInterface;

/**
 * Solver install operation.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class InstallOperation extends SolverOperation implements OperationInterface
{
    protected const TYPE = 'install';

    /**
     * @var PackageInterface
     */
    protected $package;

    public function __construct(PackageInterface $package)
    {
        $this->package = $package;
    }

    /**
     * Returns package instance.
     */
    public function getPackage(): PackageInterface
    {
        return $this->package;
    }

    /**
     * @inheritDoc
     */
    public function show($lock): string
    {
        return self::format($this->package, $lock);
    }

    public static function format(PackageInterface $package, bool $lock = false): string
    {
        return ($lock ? 'Locking ' : 'Installing ').'<info>'.$package->getPrettyName().'</info> (<comment>'.$package->getFullPrettyVersion().'</comment>)';
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver\Operation;

use Composer\Package\AliasPackage;

/**
 * Solver install operation.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class MarkAliasUninstalledOperation extends SolverOperation implements OperationInterface
{
    protected const TYPE = 'markAliasUninstalled';

    /**
     * @var AliasPackage
     */
    protected $package;

    public function __construct(AliasPackage $package)
    {
        $this->package = $package;
    }

    /**
     * Returns package instance.
     */
    public function getPackage(): AliasPackage
    {
        return $this->package;
    }

    /**
     * @inheritDoc
     */
    public function show($lock): string
    {
        return 'Marking <info>'.$this->package->getPrettyName().'</info> (<comment>'.$this->package->getFullPrettyVersion().'</comment>) as uninstalled, alias of <info>'.$this->package->getAliasOf()->getPrettyName().'</info> (<comment>'.$this->package->getAliasOf()->getFullPrettyVersion().'</comment>)';
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver\Operation;

use Composer\Package\PackageInterface;

/**
 * Solver uninstall operation.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class UninstallOperation extends SolverOperation implements OperationInterface
{
    protected const TYPE = 'uninstall';

    /**
     * @var PackageInterface
     */
    protected $package;

    public function __construct(PackageInterface $package)
    {
        $this->package = $package;
    }

    /**
     * Returns package instance.
     */
    public function getPackage(): PackageInterface
    {
        return $this->package;
    }

    /**
     * @inheritDoc
     */
    public function show($lock): string
    {
        return self::format($this->package, $lock);
    }

    public static function format(PackageInterface $package, bool $lock = false): string
    {
        return 'Removing <info>'.$package->getPrettyName().'</info> (<comment>'.$package->getFullPrettyVersion().'</comment>)';
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver\Operation;

/**
 * Solver operation interface.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
interface OperationInterface
{
    /**
     * Returns operation type.
     *
     * @return string
     */
    public function getOperationType();

    /**
     * Serializes the operation in a human readable format
     *
     * @param  bool   $lock Whether this is an operation on the lock file
     * @return string
     */
    public function show(bool $lock);

    /**
     * Serializes the operation in a human readable format
     *
     * @return string
     */
    public function __toString();
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

/**
 * Wrapper around a Rule which keeps track of the two literals it watches
 *
 * Used by RuleWatchGraph to store rules in two RuleWatchChains.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class RuleWatchNode
{
    /** @var int */
    public $watch1;
    /** @var int */
    public $watch2;

    /** @var Rule */
    protected $rule;

    /**
     * Creates a new node watching the first and second literals of the rule.
     *
     * @param Rule $rule The rule to wrap
     */
    public function __construct(Rule $rule)
    {
        $this->rule = $rule;

        $literals = $rule->getLiterals();

        $literalCount = \count($literals);
        $this->watch1 = $literalCount > 0 ? $literals[0] : 0;
        $this->watch2 = $literalCount > 1 ? $literals[1] : 0;
    }

    /**
     * Places the second watch on the rule's literal, decided at the highest level
     *
     * Useful for learned rules where the literal for the highest rule is most
     * likely to quickly lead to further decisions.
     *
     * @param Decisions $decisions The decisions made so far by the solver
     */
    public function watch2OnHighest(Decisions $decisions): void
    {
        $literals = $this->rule->getLiterals();

        // if there are only 2 elements, both are being watched anyway
        if (\count($literals) < 3 || $this->rule instanceof MultiConflictRule) {
            return;
        }

        $watchLevel = 0;

        foreach ($literals as $literal) {
            $level = $decisions->decisionLevel($literal);

            if ($level > $watchLevel) {
                $this->watch2 = $literal;
                $watchLevel = $level;
            }
        }
    }

    /**
     * Returns the rule this node wraps
     */
    public function getRule(): Rule
    {
        return $this->rule;
    }

    /**
     * Given one watched literal, this method returns the other watched literal
     *
     * @param  int $literal The watched literal that should not be returned
     * @return int A literal
     */
    public function getOtherWatch(int $literal): int
    {
        if ($this->watch1 === $literal) {
            return $this->watch2;
        }

        return $this->watch1;
    }

    /**
     * Moves a watch from one literal to another
     *
     * @param int $from The previously watched literal
     * @param int $to   The literal to be watched now
     */
    public function moveWatch(int $from, int $to): void
    {
        if ($this->watch1 === $from) {
            $this->watch1 = $to;
        } else {
            $this->watch2 = $to;
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Package\AliasPackage;
use Composer\Package\BasePackage;
use Composer\Package\Version\VersionParser;
use Composer\Semver\CompilingMatcher;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\MultiConstraint;
use Composer\Semver\Intervals;

/**
 * Optimizes a given pool
 *
 * @author Yanick Witschi <yanick.witschi@terminal42.ch>
 */
class PoolOptimizer
{
    /**
     * @var PolicyInterface
     */
    private $policy;

    /**
     * @var array<int, true>
     */
    private $irremovablePackages = [];

    /**
     * @var array<string, array<string, ConstraintInterface>>
     */
    private $requireConstraintsPerPackage = [];

    /**
     * @var array<string, array<string, ConstraintInterface>>
     */
    private $conflictConstraintsPerPackage = [];

    /**
     * @var array<int, true>
     */
    private $packagesToRemove = [];

    /**
     * @var array<int, BasePackage[]>
     */
    private $aliasesPerPackage = [];

    /**
     * @var array<string, array<string, string>>
     */
    private $removedVersionsByPackage = [];

    public function __construct(PolicyInterface $policy)
    {
        $this->policy = $policy;
    }

    public function optimize(Request $request, Pool $pool): Pool
    {
        $this->prepare($request, $pool);

        $this->optimizeByIdenticalDependencies($request, $pool);

        $this->optimizeImpossiblePackagesAway($request, $pool);

        $optimizedPool = $this->applyRemovalsToPool($pool);

        // No need to run this recursively at the moment
        // because the current optimizations cannot provide
        // even more gains when ran again. Might change
        // in the future with additional optimizations.

        $this->irremovablePackages = [];
        $this->requireConstraintsPerPackage = [];
        $this->conflictConstraintsPerPackage = [];
        $this->packagesToRemove = [];
        $this->aliasesPerPackage = [];
        $this->removedVersionsByPackage = [];

        return $optimizedPool;
    }

    private function prepare(Request $request, Pool $pool): void
    {
        $irremovablePackageConstraintGroups = [];

        // Mark fixed or locked packages as irremovable
        foreach ($request->getFixedOrLockedPackages() as $package) {
            $irremovablePackageConstraintGroups[$package->getName()][] = new Constraint('==', $package->getVersion());
        }

        // Extract requested package requirements
        foreach ($request->getRequires() as $require => $constraint) {
            $this->extractRequireConstraintsPerPackage($require, $constraint);
        }

        // First pass over all packages to extract information and mark package constraints irremovable
        foreach ($pool->getPackages() as $package) {
            // Extract package requirements
            foreach ($package->getRequires() as $link) {
                $this->extractRequireConstraintsPerPackage($link->getTarget(), $link->getConstraint());
            }
            // Extract package conflicts
            foreach ($package->getConflicts() as $link) {
                $this->extractConflictConstraintsPerPackage($link->getTarget(), $link->getConstraint());
            }

            // Keep track of alias packages for every package so if either the alias or aliased is kept
            // we keep the others as they are a unit of packages really
            if ($package instanceof AliasPackage) {
                $this->aliasesPerPackage[$package->getAliasOf()->id][] = $package;
            }
        }

        $irremovablePackageConstraints = [];
        foreach ($irremovablePackageConstraintGroups as $packageName => $constraints) {
            $irremovablePackageConstraints[$packageName] = 1 === \count($constraints) ? $constraints[0] : new MultiConstraint($constraints, false);
        }
        unset($irremovablePackageConstraintGroups);

        // Mark the packages as irremovable based on the constraints
        foreach ($pool->getPackages() as $package) {
            if (!isset($irremovablePackageConstraints[$package->getName()])) {
                continue;
            }

            if (CompilingMatcher::match($irremovablePackageConstraints[$package->getName()], Constraint::OP_EQ, $package->getVersion())) {
                $this->markPackageIrremovable($package);
            }
        }
    }

    private function markPackageIrremovable(BasePackage $package): void
    {
        $this->irremovablePackages[$package->id] = true;
        if ($package instanceof AliasPackage) {
            // recursing here so aliasesPerPackage for the aliasOf can be checked
            // and all its aliases marked as irremovable as well
            $this->markPackageIrremovable($package->getAliasOf());
        }
        if (isset($this->aliasesPerPackage[$package->id])) {
            foreach ($this->aliasesPerPackage[$package->id] as $aliasPackage) {
                $this->irremovablePackages[$aliasPackage->id] = true;
            }
        }
    }

    /**
     * @return Pool Optimized pool
     */
    private function applyRemovalsToPool(Pool $pool): Pool
    {
        $packages = [];
        $removedVersions = [];
        foreach ($pool->getPackages() as $package) {
            if (!isset($this->packagesToRemove[$package->id])) {
                $packages[] = $package;
            } else {
                $removedVersions[$package->getName()][$package->getVersion()] = $package->getPrettyVersion();
            }
        }

        $optimizedPool = new Pool($packages, $pool->getUnacceptableFixedOrLockedPackages(), $removedVersions, $this->removedVersionsByPackage);

        return $optimizedPool;
    }

    private function optimizeByIdenticalDependencies(Request $request, Pool $pool): void
    {
        $identicalDefinitionsPerPackage = [];
        $packageIdenticalDefinitionLookup = [];

        foreach ($pool->getPackages() as $package) {

            // If that package was already marked irremovable, we can skip
            // the entire process for it
            if (isset($this->irremovablePackages[$package->id])) {
                continue;
            }

            $this->markPackageForRemoval($package->id);

            $dependencyHash = $this->calculateDependencyHash($package);

            foreach ($package->getNames(false) as $packageName) {
                if (!isset($this->requireConstraintsPerPackage[$packageName])) {
                    continue;
                }

                foreach ($this->requireConstraintsPerPackage[$packageName] as $requireConstraint) {
                    $groupHashParts = [];

                    if (CompilingMatcher::match($requireConstraint, Constraint::OP_EQ, $package->getVersion())) {
                        $groupHashParts[] = 'require:' . (string) $requireConstraint;
                    }

                    if (\count($package->getReplaces()) > 0) {
                        foreach ($package->getReplaces() as $link) {
                            if (CompilingMatcher::match($link->getConstraint(), Constraint::OP_EQ, $package->getVersion())) {
                                // Use the same hash part as the regular require hash because that's what the replacement does
                                $groupHashParts[] = 'require:' . (string) $link->getConstraint();
                            }
                        }
                    }

                    if (isset($this->conflictConstraintsPerPackage[$packageName])) {
                        foreach ($this->conflictConstraintsPerPackage[$packageName] as $conflictConstraint) {
                            if (CompilingMatcher::match($conflictConstraint, Constraint::OP_EQ, $package->getVersion())) {
                                $groupHashParts[] = 'conflict:' . (string) $conflictConstraint;
                            }
                        }
                    }

                    if (0 === \count($groupHashParts)) {
                        continue;
                    }

                    $groupHash = implode('', $groupHashParts);
                    $identicalDefinitionsPerPackage[$packageName][$groupHash][$dependencyHash][] = $package;
                    $packageIdenticalDefinitionLookup[$package->id][$packageName] = ['groupHash' => $groupHash, 'dependencyHash' => $dependencyHash];
                }
            }
        }

        foreach ($identicalDefinitionsPerPackage as $constraintGroups) {
            foreach ($constraintGroups as $constraintGroup) {
                foreach ($constraintGroup as $packages) {
                    // Only one package in this constraint group has the same requirements, we're not allowed to remove that package
                    if (1 === \count($packages)) {
                        $this->keepPackage($packages[0], $identicalDefinitionsPerPackage, $packageIdenticalDefinitionLookup);
                        continue;
                    }

                    // Otherwise we find out which one is the preferred package in this constraint group which is
                    // then not allowed to be removed either
                    $literals = [];

                    foreach ($packages as $package) {
                        $literals[] = $package->id;
                    }

                    foreach ($this->policy->selectPreferredPackages($pool, $literals) as $preferredLiteral) {
                        $this->keepPackage($pool->literalToPackage($preferredLiteral), $identicalDefinitionsPerPackage, $packageIdenticalDefinitionLookup);
                    }
                }
            }
        }
    }

    private function calculateDependencyHash(BasePackage $package): string
    {
        $hash = '';

        $hashRelevantLinks = [
            'requires' => $package->getRequires(),
            'conflicts' => $package->getConflicts(),
            'replaces' => $package->getReplaces(),
            'provides' => $package->getProvides(),
        ];

        foreach ($hashRelevantLinks as $key => $links) {
            if (0 === \count($links)) {
                continue;
            }

            // start new hash section
            $hash .= $key . ':';

            $subhash = [];

            foreach ($links as $link) {
                // To get the best dependency hash matches we should use Intervals::compactConstraint() here.
                // However, the majority of projects are going to specify their constraints already pretty
                // much in the best variant possible. In other words, we'd be wasting time here and it would actually hurt
                // performance more than the additional few packages that could be filtered out would benefit the process.
                $subhash[$link->getTarget()] = (string) $link->getConstraint();
            }

            // Sort for best result
            ksort($subhash);

            foreach ($subhash as $target => $constraint) {
                $hash .= $target . '@' . $constraint;
            }
        }

        return $hash;
    }

    private function markPackageForRemoval(int $id): void
    {
        // We are not allowed to remove packages if they have been marked as irremovable
        if (isset($this->irremovablePackages[$id])) {
            throw new \LogicException('Attempted removing a package which was previously marked irremovable');
        }

        $this->packagesToRemove[$id] = true;
    }

    /**
     * @param array<string, array<string, array<string, list<BasePackage>>>> $identicalDefinitionsPerPackage
     * @param array<int, array<string, array{groupHash: string, dependencyHash: string}>> $packageIdenticalDefinitionLookup
     */
    private function keepPackage(BasePackage $package, array $identicalDefinitionsPerPackage, array $packageIdenticalDefinitionLookup): void
    {
        // Already marked to keep
        if (!isset($this->packagesToRemove[$package->id])) {
            return;
        }

        unset($this->packagesToRemove[$package->id]);

        if ($package instanceof AliasPackage) {
            // recursing here so aliasesPerPackage for the aliasOf can be checked
            // and all its aliases marked to be kept as well
            $this->keepPackage($package->getAliasOf(), $identicalDefinitionsPerPackage, $packageIdenticalDefinitionLookup);
        }

        // record all the versions of the package group so we can list them later in Problem output
        foreach ($package->getNames(false) as $name) {
            if (isset($packageIdenticalDefinitionLookup[$package->id][$name])) {
                $packageGroupPointers = $packageIdenticalDefinitionLookup[$package->id][$name];
                $packageGroup = $identicalDefinitionsPerPackage[$name][$packageGroupPointers['groupHash']][$packageGroupPointers['dependencyHash']];
                foreach ($packageGroup as $pkg) {
                    if ($pkg instanceof AliasPackage && $pkg->getPrettyVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) {
                        $pkg = $pkg->getAliasOf();
                    }
                    $this->removedVersionsByPackage[spl_object_hash($package)][$pkg->getVersion()] = $pkg->getPrettyVersion();
                }
            }
        }

        if (isset($this->aliasesPerPackage[$package->id])) {
            foreach ($this->aliasesPerPackage[$package->id] as $aliasPackage) {
                unset($this->packagesToRemove[$aliasPackage->id]);

                // record all the versions of the package group so we can list them later in Problem output
                foreach ($aliasPackage->getNames(false) as $name) {
                    if (isset($packageIdenticalDefinitionLookup[$aliasPackage->id][$name])) {
                        $packageGroupPointers = $packageIdenticalDefinitionLookup[$aliasPackage->id][$name];
                        $packageGroup = $identicalDefinitionsPerPackage[$name][$packageGroupPointers['groupHash']][$packageGroupPointers['dependencyHash']];
                        foreach ($packageGroup as $pkg) {
                            if ($pkg instanceof AliasPackage && $pkg->getPrettyVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) {
                                $pkg = $pkg->getAliasOf();
                            }
                            $this->removedVersionsByPackage[spl_object_hash($aliasPackage)][$pkg->getVersion()] = $pkg->getPrettyVersion();
                        }
                    }
                }
            }
        }
    }

    /**
     * Use the list of locked packages to constrain the loaded packages
     * This will reduce packages with significant numbers of historical versions to a smaller number
     * and reduce the resulting rule set that is generated
     */
    private function optimizeImpossiblePackagesAway(Request $request, Pool $pool): void
    {
        if (\count($request->getLockedPackages()) === 0) {
            return;
        }

        $packageIndex = [];

        foreach ($pool->getPackages() as $package) {
            $id = $package->id;

            // Do not remove irremovable packages
            if (isset($this->irremovablePackages[$id])) {
                continue;
            }
            // Do not remove a package aliased by another package, nor aliases
            if (isset($this->aliasesPerPackage[$id]) || $package instanceof AliasPackage) {
                continue;
            }
            // Do not remove locked packages
            if ($request->isFixedPackage($package) || $request->isLockedPackage($package)) {
                continue;
            }

            $packageIndex[$package->getName()][$package->id] = $package;
        }

        foreach ($request->getLockedPackages() as $package) {
            // If this locked package is no longer required by root or anything in the pool, it may get uninstalled so do not apply its requirements
            // In a case where a requirement WERE to appear in the pool by a package that would not be used, it would've been unlocked and so not filtered still
            $isUnusedPackage = true;
            foreach ($package->getNames(false) as $packageName) {
                if (isset($this->requireConstraintsPerPackage[$packageName])) {
                    $isUnusedPackage = false;
                    break;
                }
            }

            if ($isUnusedPackage) {
                continue;
            }

            foreach ($package->getRequires() as $link) {
                $require = $link->getTarget();
                if (!isset($packageIndex[$require])) {
                    continue;
                }

                $linkConstraint = $link->getConstraint();
                foreach ($packageIndex[$require] as $id => $requiredPkg) {
                    if (false === CompilingMatcher::match($linkConstraint, Constraint::OP_EQ, $requiredPkg->getVersion())) {
                        $this->markPackageForRemoval($id);
                        unset($packageIndex[$require][$id]);
                    }
                }
            }
        }
    }

    /**
     * Disjunctive require constraints need to be considered in their own group. E.g. "^2.14 || ^3.3" needs to generate
     * two require constraint groups in order for us to keep the best matching package for "^2.14" AND "^3.3" as otherwise, we'd
     * only keep either one which can cause trouble (e.g. when using --prefer-lowest).
     *
     * @return void
     */
    private function extractRequireConstraintsPerPackage(string $package, ConstraintInterface $constraint)
    {
        foreach ($this->expandDisjunctiveMultiConstraints($constraint) as $expanded) {
            $this->requireConstraintsPerPackage[$package][(string) $expanded] = $expanded;
        }
    }

    /**
     * Disjunctive conflict constraints need to be considered in their own group. E.g. "^2.14 || ^3.3" needs to generate
     * two conflict constraint groups in order for us to keep the best matching package for "^2.14" AND "^3.3" as otherwise, we'd
     * only keep either one which can cause trouble (e.g. when using --prefer-lowest).
     *
     * @return void
     */
    private function extractConflictConstraintsPerPackage(string $package, ConstraintInterface $constraint)
    {
        foreach ($this->expandDisjunctiveMultiConstraints($constraint) as $expanded) {
            $this->conflictConstraintsPerPackage[$package][(string) $expanded] = $expanded;
        }
    }

    /**
     * @return ConstraintInterface[]
     */
    private function expandDisjunctiveMultiConstraints(ConstraintInterface $constraint)
    {
        $constraint = Intervals::compactConstraint($constraint);

        if ($constraint instanceof MultiConstraint && $constraint->isDisjunctive()) {
            // No need to call ourselves recursively here because Intervals::compactConstraint() ensures that there
            // are no nested disjunctive MultiConstraint instances possible
            return $constraint->getConstraints();
        }

        // Regular constraints and conjunctive MultiConstraints
        return [$constraint];
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Util\IniHelper;
use Composer\Repository\RepositorySet;

/**
 * @author Nils Adermann <naderman@naderman.de>
 *
 * @method self::ERROR_DEPENDENCY_RESOLUTION_FAILED getCode()
 */
class SolverProblemsException extends \RuntimeException
{
    public const ERROR_DEPENDENCY_RESOLUTION_FAILED = 2;

    /** @var Problem[] */
    protected $problems;
    /** @var array<Rule[]> */
    protected $learnedPool;

    /**
     * @param Problem[] $problems
     * @param array<Rule[]> $learnedPool
     */
    public function __construct(array $problems, array $learnedPool)
    {
        $this->problems = $problems;
        $this->learnedPool = $learnedPool;

        parent::__construct('Failed resolving dependencies with '.\count($problems).' problems, call getPrettyString to get formatted details', self::ERROR_DEPENDENCY_RESOLUTION_FAILED);
    }

    public function getPrettyString(RepositorySet $repositorySet, Request $request, Pool $pool, bool $isVerbose, bool $isDevExtraction = false): string
    {
        $installedMap = $request->getPresentMap(true);
        $missingExtensions = [];
        $isCausedByLock = false;

        $problems = [];
        foreach ($this->problems as $problem) {
            $problems[] = $problem->getPrettyString($repositorySet, $request, $pool, $isVerbose, $installedMap, $this->learnedPool)."\n";

            $missingExtensions = array_merge($missingExtensions, $this->getExtensionProblems($problem->getReasons()));

            $isCausedByLock = $isCausedByLock || $problem->isCausedByLock($repositorySet, $request, $pool);
        }

        $i = 1;
        $text = "\n";
        foreach (array_unique($problems) as $problem) {
            $text .= "  Problem ".($i++).$problem;
        }

        $hints = [];
        if (!$isDevExtraction && (str_contains($text, 'could not be found') || str_contains($text, 'no matching package found'))) {
            $hints[] = "Potential causes:\n - A typo in the package name\n - The package is not available in a stable-enough version according to your minimum-stability setting\n   see <https://getcomposer.org/doc/04-schema.md#minimum-stability> for more details.\n - It's a private package and you forgot to add a custom repository to find it\n\nRead <https://getcomposer.org/doc/articles/troubleshooting.md> for further common problems.";
        }

        if (\count($missingExtensions) > 0) {
            $hints[] = $this->createExtensionHint($missingExtensions);
        }

        if ($isCausedByLock && !$isDevExtraction && !$request->getUpdateAllowTransitiveRootDependencies()) {
            $hints[] = "Use the option --with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions.";
        }

        if (str_contains($text, 'found composer-plugin-api[2.0.0] but it does not match') && str_contains($text, '- ocramius/package-versions')) {
            $hints[] = "<warning>ocramius/package-versions only provides support for Composer 2 in 1.8+, which requires PHP 7.4.</warning>\nIf you can not upgrade PHP you can require <info>composer/package-versions-deprecated</info> to resolve this with PHP 7.0+.";
        }

        if (!class_exists('PHPUnit\Framework\TestCase', false)) {
            if (str_contains($text, 'found composer-plugin-api[2.0.0] but it does not match')) {
                $hints[] = "You are using Composer 2, which some of your plugins seem to be incompatible with. Make sure you update your plugins or report a plugin-issue to ask them to support Composer 2.";
            }
        }

        if (\count($hints) > 0) {
            $text .= "\n" . implode("\n\n", $hints);
        }

        return $text;
    }

    /**
     * @return Problem[]
     */
    public function getProblems(): array
    {
        return $this->problems;
    }

    /**
     * @param string[] $missingExtensions
     */
    private function createExtensionHint(array $missingExtensions): string
    {
        $paths = IniHelper::getAll();

        if ('' === $paths[0]) {
            if (count($paths) === 1) {
                return '';
            }

            array_shift($paths);
        }

        $ignoreExtensionsArguments = implode(" ", array_map(static function ($extension) {
            return "--ignore-platform-req=$extension";
        }, array_unique($missingExtensions)));

        $text = "To enable extensions, verify that they are enabled in your .ini files:\n    - ";
        $text .= implode("\n    - ", $paths);
        $text .= "\nYou can also run `php --ini` in a terminal to see which files are used by PHP in CLI mode.";
        $text .= "\nAlternatively, you can run Composer with `$ignoreExtensionsArguments` to temporarily ignore these required extensions.";

        return $text;
    }

    /**
     * @param Rule[][] $reasonSets
     * @return string[]
     */
    private function getExtensionProblems(array $reasonSets): array
    {
        $missingExtensions = [];
        foreach ($reasonSets as $reasonSet) {
            foreach ($reasonSet as $rule) {
                $required = $rule->getRequiredPackage();
                if (null !== $required && 0 === strpos($required, 'ext-')) {
                    $missingExtensions[$required] = 1;
                }
            }
        }

        return array_keys($missingExtensions);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

/**
 * The RuleWatchGraph efficiently propagates decisions to other rules
 *
 * All rules generated for solving a SAT problem should be inserted into the
 * graph. When a decision on a literal is made, the graph can be used to
 * propagate the decision to all other rules involving the literal, leading to
 * other trivial decisions resulting from unit clauses.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class RuleWatchGraph
{
    /** @var array<int, RuleWatchChain> */
    protected $watchChains = [];

    /**
     * Inserts a rule node into the appropriate chains within the graph
     *
     * The node is prepended to the watch chains for each of the two literals it
     * watches.
     *
     * Assertions are skipped because they only depend on a single package and
     * have no alternative literal that could be true, so there is no need to
     * watch changes in any literals.
     *
     * @param RuleWatchNode $node The rule node to be inserted into the graph
     */
    public function insert(RuleWatchNode $node): void
    {
        if ($node->getRule()->isAssertion()) {
            return;
        }

        if (!$node->getRule() instanceof MultiConflictRule) {
            foreach ([$node->watch1, $node->watch2] as $literal) {
                if (!isset($this->watchChains[$literal])) {
                    $this->watchChains[$literal] = new RuleWatchChain;
                }

                $this->watchChains[$literal]->unshift($node);
            }
        } else {
            foreach ($node->getRule()->getLiterals() as $literal) {
                if (!isset($this->watchChains[$literal])) {
                    $this->watchChains[$literal] = new RuleWatchChain;
                }

                $this->watchChains[$literal]->unshift($node);
            }
        }
    }

    /**
     * Propagates a decision on a literal to all rules watching the literal
     *
     * If a decision, e.g. +A has been made, then all rules containing -A, e.g.
     * (-A|+B|+C) now need to satisfy at least one of the other literals, so
     * that the rule as a whole becomes true, since with +A applied the rule
     * is now (false|+B|+C) so essentially (+B|+C).
     *
     * This means that all rules watching the literal -A need to be updated to
     * watch 2 other literals which can still be satisfied instead. So literals
     * that conflict with previously made decisions are not an option.
     *
     * Alternatively it can occur that a unit clause results: e.g. if in the
     * above example the rule was (-A|+B), then A turning true means that
     * B must now be decided true as well.
     *
     * @param  int       $decidedLiteral The literal which was decided (A in our example)
     * @param  int       $level          The level at which the decision took place and at which
     *                                   all resulting decisions should be made.
     * @param  Decisions $decisions      Used to check previous decisions and to
     *                                   register decisions resulting from propagation
     * @return Rule|null If a conflict is found the conflicting rule is returned
     */
    public function propagateLiteral(int $decidedLiteral, int $level, Decisions $decisions): ?Rule
    {
        // we invert the decided literal here, example:
        // A was decided => (-A|B) now requires B to be true, so we look for
        // rules which are fulfilled by -A, rather than A.
        $literal = -$decidedLiteral;

        if (!isset($this->watchChains[$literal])) {
            return null;
        }

        $chain = $this->watchChains[$literal];

        $chain->rewind();
        while ($chain->valid()) {
            $node = $chain->current();
            if (!$node->getRule() instanceof MultiConflictRule) {
                $otherWatch = $node->getOtherWatch($literal);

                if (!$node->getRule()->isDisabled() && !$decisions->satisfy($otherWatch)) {
                    $ruleLiterals = $node->getRule()->getLiterals();

                    $alternativeLiterals = array_filter($ruleLiterals, static function ($ruleLiteral) use ($literal, $otherWatch, $decisions): bool {
                        return $literal !== $ruleLiteral &&
                            $otherWatch !== $ruleLiteral &&
                            !$decisions->conflict($ruleLiteral);
                    });

                    if (\count($alternativeLiterals) > 0) {
                        reset($alternativeLiterals);
                        $this->moveWatch($literal, current($alternativeLiterals), $node);
                        continue;
                    }

                    if ($decisions->conflict($otherWatch)) {
                        return $node->getRule();
                    }

                    $decisions->decide($otherWatch, $level, $node->getRule());
                }
            } else {
                foreach ($node->getRule()->getLiterals() as $otherLiteral) {
                    if ($literal !== $otherLiteral && !$decisions->satisfy($otherLiteral)) {
                        if ($decisions->conflict($otherLiteral)) {
                            return $node->getRule();
                        }

                        $decisions->decide($otherLiteral, $level, $node->getRule());
                    }
                }
            }

            $chain->next();
        }

        return null;
    }

    /**
     * Moves a rule node from one watch chain to another
     *
     * The rule node's watched literals are updated accordingly.
     *
     * @param int           $fromLiteral A literal the node used to watch
     * @param int           $toLiteral   A literal the node should watch now
     * @param RuleWatchNode $node        The rule node to be moved
     */
    protected function moveWatch(int $fromLiteral, int $toLiteral, RuleWatchNode $node): void
    {
        if (!isset($this->watchChains[$toLiteral])) {
            $this->watchChains[$toLiteral] = new RuleWatchChain;
        }

        $node->moveWatch($fromLiteral, $toLiteral);
        $this->watchChains[$fromLiteral]->remove();
        $this->watchChains[$toLiteral]->unshift($node);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

/**
 * An extension of SplDoublyLinkedList with seek and removal of current element
 *
 * SplDoublyLinkedList only allows deleting a particular offset and has no
 * method to set the internal iterator to a particular offset.
 *
 * @author Nils Adermann <naderman@naderman.de>
 * @extends \SplDoublyLinkedList<RuleWatchNode>
 */
class RuleWatchChain extends \SplDoublyLinkedList
{
    /**
     * Moves the internal iterator to the specified offset
     *
     * @param int $offset The offset to seek to.
     */
    public function seek(int $offset): void
    {
        $this->rewind();
        for ($i = 0; $i < $offset; $i++, $this->next());
    }

    /**
     * Removes the current element from the list
     *
     * As SplDoublyLinkedList only allows deleting a particular offset and
     * incorrectly sets the internal iterator if you delete the current value
     * this method sets the internal iterator back to the following element
     * using the seek method.
     */
    public function remove(): void
    {
        $offset = $this->key();
        $this->offsetUnset($offset);
        $this->seek($offset);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Filter\PlatformRequirementFilter\IgnoreListPlatformRequirementFilter;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
use Composer\Package\BasePackage;
use Composer\Package\AliasPackage;

/**
 * @author Nils Adermann <naderman@naderman.de>
 * @phpstan-import-type ReasonData from Rule
 */
class RuleSetGenerator
{
    /** @var PolicyInterface */
    protected $policy;
    /** @var Pool */
    protected $pool;
    /** @var RuleSet */
    protected $rules;
    /** @var array<int, BasePackage> */
    protected $addedMap = [];
    /** @var array<string, BasePackage[]> */
    protected $addedPackagesByNames = [];

    public function __construct(PolicyInterface $policy, Pool $pool)
    {
        $this->policy = $policy;
        $this->pool = $pool;
        $this->rules = new RuleSet;
    }

    /**
     * Creates a new rule for the requirements of a package
     *
     * This rule is of the form (-A|B|C), where B and C are the providers of
     * one requirement of the package A.
     *
     * @param  BasePackage $package The package with a requirement
     * @param  BasePackage[] $providers The providers of the requirement
     * @param  Rule::RULE_* $reason A RULE_* constant describing the reason for generating this rule
     * @param  mixed $reasonData Any data, e.g. the requirement name, that goes with the reason
     * @return Rule|null The generated rule or null if tautological
     *
     * @phpstan-param ReasonData $reasonData
     */
    protected function createRequireRule(BasePackage $package, array $providers, $reason, $reasonData): ?Rule
    {
        $literals = [-$package->id];

        foreach ($providers as $provider) {
            // self fulfilling rule?
            if ($provider === $package) {
                return null;
            }
            $literals[] = $provider->id;
        }

        return new GenericRule($literals, $reason, $reasonData);
    }

    /**
     * Creates a rule to install at least one of a set of packages
     *
     * The rule is (A|B|C) with A, B and C different packages. If the given
     * set of packages is empty an impossible rule is generated.
     *
     * @param  non-empty-array<BasePackage> $packages   The set of packages to choose from
     * @param  Rule::RULE_*  $reason     A RULE_* constant describing the reason for
     *                                   generating this rule
     * @param  mixed         $reasonData Additional data like the root require or fix request info
     * @return Rule          The generated rule
     *
     * @phpstan-param ReasonData $reasonData
     */
    protected function createInstallOneOfRule(array $packages, $reason, $reasonData): Rule
    {
        $literals = [];
        foreach ($packages as $package) {
            $literals[] = $package->id;
        }

        return new GenericRule($literals, $reason, $reasonData);
    }

    /**
     * Creates a rule for two conflicting packages
     *
     * The rule for conflicting packages A and B is (-A|-B). A is called the issuer
     * and B the provider.
     *
     * @param BasePackage $issuer The package declaring the conflict
     * @param BasePackage $provider The package causing the conflict
     * @param Rule::RULE_* $reason A RULE_* constant describing the reason for generating this rule
     * @param mixed $reasonData Any data, e.g. the package name, that goes with the reason
     * @return ?Rule The generated rule
     *
     * @phpstan-param ReasonData $reasonData
     */
    protected function createRule2Literals(BasePackage $issuer, BasePackage $provider, $reason, $reasonData): ?Rule
    {
        // ignore self conflict
        if ($issuer === $provider) {
            return null;
        }

        return new Rule2Literals(-$issuer->id, -$provider->id, $reason, $reasonData);
    }

    /**
     * @param non-empty-array<BasePackage> $packages
     * @param Rule::RULE_* $reason A RULE_* constant
     * @param mixed $reasonData
     *
     * @phpstan-param ReasonData $reasonData
     */
    protected function createMultiConflictRule(array $packages, $reason, $reasonData): Rule
    {
        $literals = [];
        foreach ($packages as $package) {
            $literals[] = -$package->id;
        }

        if (\count($literals) === 2) {
            return new Rule2Literals($literals[0], $literals[1], $reason, $reasonData);
        }

        return new MultiConflictRule($literals, $reason, $reasonData);
    }

    /**
     * Adds a rule unless it duplicates an existing one of any type
     *
     * To be able to directly pass in the result of one of the rule creation
     * methods null is allowed which will not insert a rule.
     *
     * @param RuleSet::TYPE_* $type A TYPE_* constant defining the rule type
     * @param Rule $newRule The rule about to be added
     */
    private function addRule($type, ?Rule $newRule = null): void
    {
        if (null === $newRule) {
            return;
        }

        $this->rules->add($newRule, $type);
    }

    protected function addRulesForPackage(BasePackage $package, PlatformRequirementFilterInterface $platformRequirementFilter): void
    {
        /** @var \SplQueue<BasePackage> */
        $workQueue = new \SplQueue;
        $workQueue->enqueue($package);

        while (!$workQueue->isEmpty()) {
            $package = $workQueue->dequeue();
            if (isset($this->addedMap[$package->id])) {
                continue;
            }

            $this->addedMap[$package->id] = $package;

            if (!$package instanceof AliasPackage) {
                foreach ($package->getNames(false) as $name) {
                    $this->addedPackagesByNames[$name][] = $package;
                }
            } else {
                $workQueue->enqueue($package->getAliasOf());
                $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package, [$package->getAliasOf()], Rule::RULE_PACKAGE_ALIAS, $package));

                // aliases must be installed with their main package, so create a rule the other way around as well
                $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package->getAliasOf(), [$package], Rule::RULE_PACKAGE_INVERSE_ALIAS, $package->getAliasOf()));

                // if alias package has no self.version requires, its requirements do not
                // need to be added as the aliased package processing will take care of it
                if (!$package->hasSelfVersionRequires()) {
                    continue;
                }
            }

            foreach ($package->getRequires() as $link) {
                $constraint = $link->getConstraint();
                if ($platformRequirementFilter->isIgnored($link->getTarget())) {
                    continue;
                } elseif ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) {
                    $constraint = $platformRequirementFilter->filterConstraint($link->getTarget(), $constraint);
                }

                $possibleRequires = $this->pool->whatProvides($link->getTarget(), $constraint);

                $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package, $possibleRequires, Rule::RULE_PACKAGE_REQUIRES, $link));

                foreach ($possibleRequires as $require) {
                    $workQueue->enqueue($require);
                }
            }
        }
    }

    protected function addConflictRules(PlatformRequirementFilterInterface $platformRequirementFilter): void
    {
        /** @var BasePackage $package */
        foreach ($this->addedMap as $package) {
            foreach ($package->getConflicts() as $link) {
                // even if conflict ends up being with an alias, there would be at least one actual package by this name
                if (!isset($this->addedPackagesByNames[$link->getTarget()])) {
                    continue;
                }

                $constraint = $link->getConstraint();
                if ($platformRequirementFilter->isIgnored($link->getTarget())) {
                    continue;
                } elseif ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) {
                    $constraint = $platformRequirementFilter->filterConstraint($link->getTarget(), $constraint, false);
                }

                $conflicts = $this->pool->whatProvides($link->getTarget(), $constraint);

                foreach ($conflicts as $conflict) {
                    // define the conflict rule for regular packages, for alias packages it's only needed if the name
                    // matches the conflict exactly, otherwise the name match is by provide/replace which means the
                    // package which this is an alias of will conflict anyway, so no need to create additional rules
                    if (!$conflict instanceof AliasPackage || $conflict->getName() === $link->getTarget()) {
                        $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $conflict, Rule::RULE_PACKAGE_CONFLICT, $link));
                    }
                }
            }
        }

        foreach ($this->addedPackagesByNames as $name => $packages) {
            if (\count($packages) > 1) {
                $reason = Rule::RULE_PACKAGE_SAME_NAME;
                $this->addRule(RuleSet::TYPE_PACKAGE, $this->createMultiConflictRule($packages, $reason, $name));
            }
        }
    }

    protected function addRulesForRequest(Request $request, PlatformRequirementFilterInterface $platformRequirementFilter): void
    {
        foreach ($request->getFixedPackages() as $package) {
            if ($package->id === -1) {
                // fixed package was not added to the pool as it did not pass the stability requirements, this is fine
                if ($this->pool->isUnacceptableFixedOrLockedPackage($package)) {
                    continue;
                }

                // otherwise, looks like a bug
                throw new \LogicException("Fixed package ".$package->getPrettyString()." was not added to solver pool.");
            }

            $this->addRulesForPackage($package, $platformRequirementFilter);

            $rule = $this->createInstallOneOfRule([$package], Rule::RULE_FIXED, [
                'package' => $package,
            ]);
            $this->addRule(RuleSet::TYPE_REQUEST, $rule);
        }

        foreach ($request->getRequires() as $packageName => $constraint) {
            if ($platformRequirementFilter->isIgnored($packageName)) {
                continue;
            } elseif ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) {
                $constraint = $platformRequirementFilter->filterConstraint($packageName, $constraint);
            }

            $packages = $this->pool->whatProvides($packageName, $constraint);
            if (\count($packages) > 0) {
                foreach ($packages as $package) {
                    $this->addRulesForPackage($package, $platformRequirementFilter);
                }

                $rule = $this->createInstallOneOfRule($packages, Rule::RULE_ROOT_REQUIRE, [
                    'packageName' => $packageName,
                    'constraint' => $constraint,
                ]);
                $this->addRule(RuleSet::TYPE_REQUEST, $rule);
            }
        }
    }

    protected function addRulesForRootAliases(PlatformRequirementFilterInterface $platformRequirementFilter): void
    {
        foreach ($this->pool->getPackages() as $package) {
            // ensure that rules for root alias packages and aliases of packages which were loaded are also loaded
            // even if the alias itself isn't required, otherwise a package could be installed without its alias which
            // leads to unexpected behavior
            if (!isset($this->addedMap[$package->id]) &&
                $package instanceof AliasPackage &&
                ($package->isRootPackageAlias() || isset($this->addedMap[$package->getAliasOf()->id]))
            ) {
                $this->addRulesForPackage($package, $platformRequirementFilter);
            }
        }
    }

    public function getRulesFor(Request $request, ?PlatformRequirementFilterInterface $platformRequirementFilter = null): RuleSet
    {
        $platformRequirementFilter = $platformRequirementFilter ?? PlatformRequirementFilterFactory::ignoreNothing();

        $this->addRulesForRequest($request, $platformRequirementFilter);

        $this->addRulesForRootAliases($platformRequirementFilter);

        $this->addConflictRules($platformRequirementFilter);

        // Remove references to packages
        $this->addedMap = $this->addedPackagesByNames = [];

        $rules = $this->rules;

        $this->rules = new RuleSet;

        return $rules;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

/**
 * @author Nils Adermann <naderman@naderman.de>
 * @implements \Iterator<RuleSet::TYPE_*|-1, Rule>
 */
class RuleSetIterator implements \Iterator
{
    /** @var array<RuleSet::TYPE_*, Rule[]> */
    protected $rules;
    /** @var array<RuleSet::TYPE_*> */
    protected $types;

    /** @var int */
    protected $currentOffset;
    /** @var RuleSet::TYPE_*|-1 */
    protected $currentType;
    /** @var int */
    protected $currentTypeOffset;

    /**
     * @param array<RuleSet::TYPE_*, Rule[]> $rules
     */
    public function __construct(array $rules)
    {
        $this->rules = $rules;
        $this->types = array_keys($rules);
        sort($this->types);

        $this->rewind();
    }

    public function current(): Rule
    {
        return $this->rules[$this->currentType][$this->currentOffset];
    }

    /**
     * @return RuleSet::TYPE_*|-1
     */
    public function key(): int
    {
        return $this->currentType;
    }

    public function next(): void
    {
        $this->currentOffset++;

        if (!isset($this->rules[$this->currentType])) {
            return;
        }

        if ($this->currentOffset >= \count($this->rules[$this->currentType])) {
            $this->currentOffset = 0;

            do {
                $this->currentTypeOffset++;

                if (!isset($this->types[$this->currentTypeOffset])) {
                    $this->currentType = -1;
                    break;
                }

                $this->currentType = $this->types[$this->currentTypeOffset];
            } while (0 === \count($this->rules[$this->currentType]));
        }
    }

    public function rewind(): void
    {
        $this->currentOffset = 0;

        $this->currentTypeOffset = -1;
        $this->currentType = -1;

        do {
            $this->currentTypeOffset++;

            if (!isset($this->types[$this->currentTypeOffset])) {
                $this->currentType = -1;
                break;
            }

            $this->currentType = $this->types[$this->currentTypeOffset];
        } while (0 === \count($this->rules[$this->currentType]));
    }

    public function valid(): bool
    {
        return isset($this->rules[$this->currentType], $this->rules[$this->currentType][$this->currentOffset]);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Filter\PlatformRequirementFilter\IgnoreListPlatformRequirementFilter;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
use Composer\IO\IOInterface;
use Composer\Package\BasePackage;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
class Solver
{
    private const BRANCH_LITERALS = 0;
    private const BRANCH_LEVEL = 1;

    /** @var PolicyInterface */
    protected $policy;
    /** @var Pool */
    protected $pool;

    /** @var RuleSet */
    protected $rules;

    /** @var RuleWatchGraph */
    protected $watchGraph;
    /** @var Decisions */
    protected $decisions;
    /** @var BasePackage[] */
    protected $fixedMap;

    /** @var int */
    protected $propagateIndex;
    /** @var array<int, array{array<int, int>, int}> */
    protected $branches = [];
    /** @var Problem[] */
    protected $problems = [];
    /** @var array<Rule[]> */
    protected $learnedPool = [];
    /** @var array<string, int> */
    protected $learnedWhy = [];

    /** @var bool */
    public $testFlagLearnedPositiveLiteral = false;

    /** @var IOInterface */
    protected $io;

    public function __construct(PolicyInterface $policy, Pool $pool, IOInterface $io)
    {
        $this->io = $io;
        $this->policy = $policy;
        $this->pool = $pool;
    }

    public function getRuleSetSize(): int
    {
        return \count($this->rules);
    }

    public function getPool(): Pool
    {
        return $this->pool;
    }

    // aka solver_makeruledecisions

    private function makeAssertionRuleDecisions(): void
    {
        $decisionStart = \count($this->decisions) - 1;

        $rulesCount = \count($this->rules);
        for ($ruleIndex = 0; $ruleIndex < $rulesCount; $ruleIndex++) {
            $rule = $this->rules->ruleById[$ruleIndex];

            if (!$rule->isAssertion() || $rule->isDisabled()) {
                continue;
            }

            $literals = $rule->getLiterals();
            $literal = $literals[0];

            if (!$this->decisions->decided($literal)) {
                $this->decisions->decide($literal, 1, $rule);
                continue;
            }

            if ($this->decisions->satisfy($literal)) {
                continue;
            }

            // found a conflict
            if (RuleSet::TYPE_LEARNED === $rule->getType()) {
                $rule->disable();
                continue;
            }

            $conflict = $this->decisions->decisionRule($literal);

            if (RuleSet::TYPE_PACKAGE === $conflict->getType()) {
                $problem = new Problem();

                $problem->addRule($rule);
                $problem->addRule($conflict);
                $rule->disable();
                $this->problems[] = $problem;
                continue;
            }

            // conflict with another root require/fixed package
            $problem = new Problem();
            $problem->addRule($rule);
            $problem->addRule($conflict);

            // push all of our rules (can only be root require/fixed package rules)
            // asserting this literal on the problem stack
            foreach ($this->rules->getIteratorFor(RuleSet::TYPE_REQUEST) as $assertRule) {
                if ($assertRule->isDisabled() || !$assertRule->isAssertion()) {
                    continue;
                }

                $assertRuleLiterals = $assertRule->getLiterals();
                $assertRuleLiteral = $assertRuleLiterals[0];

                if (abs($literal) !== abs($assertRuleLiteral)) {
                    continue;
                }
                $problem->addRule($assertRule);
                $assertRule->disable();
            }
            $this->problems[] = $problem;

            $this->decisions->resetToOffset($decisionStart);
            $ruleIndex = -1;
        }
    }

    protected function setupFixedMap(Request $request): void
    {
        $this->fixedMap = [];
        foreach ($request->getFixedPackages() as $package) {
            $this->fixedMap[$package->id] = $package;
        }
    }

    protected function checkForRootRequireProblems(Request $request, PlatformRequirementFilterInterface $platformRequirementFilter): void
    {
        foreach ($request->getRequires() as $packageName => $constraint) {
            if ($platformRequirementFilter->isIgnored($packageName)) {
                continue;
            } elseif ($platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) {
                $constraint = $platformRequirementFilter->filterConstraint($packageName, $constraint);
            }

            if (0 === \count($this->pool->whatProvides($packageName, $constraint))) {
                $problem = new Problem();
                $problem->addRule(new GenericRule([], Rule::RULE_ROOT_REQUIRE, ['packageName' => $packageName, 'constraint' => $constraint]));
                $this->problems[] = $problem;
            }
        }
    }

    public function solve(Request $request, ?PlatformRequirementFilterInterface $platformRequirementFilter = null): LockTransaction
    {
        $platformRequirementFilter = $platformRequirementFilter ?? PlatformRequirementFilterFactory::ignoreNothing();

        $this->setupFixedMap($request);

        $this->io->writeError('Generating rules', true, IOInterface::DEBUG);
        $ruleSetGenerator = new RuleSetGenerator($this->policy, $this->pool);
        $this->rules = $ruleSetGenerator->getRulesFor($request, $platformRequirementFilter);
        unset($ruleSetGenerator);
        $this->checkForRootRequireProblems($request, $platformRequirementFilter);
        $this->decisions = new Decisions($this->pool);
        $this->watchGraph = new RuleWatchGraph;

        foreach ($this->rules as $rule) {
            $this->watchGraph->insert(new RuleWatchNode($rule));
        }

        /* make decisions based on root require/fix assertions */
        $this->makeAssertionRuleDecisions();

        $this->io->writeError('Resolving dependencies through SAT', true, IOInterface::DEBUG);
        $before = microtime(true);
        $this->runSat();
        $this->io->writeError('', true, IOInterface::DEBUG);
        $this->io->writeError(sprintf('Dependency resolution completed in %.3f seconds', microtime(true) - $before), true, IOInterface::VERBOSE);

        if (\count($this->problems) > 0) {
            throw new SolverProblemsException($this->problems, $this->learnedPool);
        }

        return new LockTransaction($this->pool, $request->getPresentMap(), $request->getFixedPackagesMap(), $this->decisions);
    }

    /**
     * Makes a decision and propagates it to all rules.
     *
     * Evaluates each term affected by the decision (linked through watches)
     * If we find unit rules we make new decisions based on them
     *
     * @return Rule|null A rule on conflict, otherwise null.
     */
    protected function propagate(int $level): ?Rule
    {
        while ($this->decisions->validOffset($this->propagateIndex)) {
            $decision = $this->decisions->atOffset($this->propagateIndex);

            $conflict = $this->watchGraph->propagateLiteral(
                $decision[Decisions::DECISION_LITERAL],
                $level,
                $this->decisions
            );

            $this->propagateIndex++;

            if ($conflict !== null) {
                return $conflict;
            }
        }

        return null;
    }

    /**
     * Reverts a decision at the given level.
     */
    private function revert(int $level): void
    {
        while (!$this->decisions->isEmpty()) {
            $literal = $this->decisions->lastLiteral();

            if ($this->decisions->undecided($literal)) {
                break;
            }

            $decisionLevel = $this->decisions->decisionLevel($literal);

            if ($decisionLevel <= $level) {
                break;
            }

            $this->decisions->revertLast();
            $this->propagateIndex = \count($this->decisions);
        }

        while (\count($this->branches) > 0 && $this->branches[\count($this->branches) - 1][self::BRANCH_LEVEL] >= $level) {
            array_pop($this->branches);
        }
    }

    /**
     * setpropagatelearn
     *
     * add free decision (a positive literal) to decision queue
     * increase level and propagate decision
     * return if no conflict.
     *
     * in conflict case, analyze conflict rule, add resulting
     * rule to learnt rule set, make decision from learnt
     * rule (always unit) and re-propagate.
     *
     * returns the new solver level or 0 if unsolvable
     */
    private function setPropagateLearn(int $level, int $literal, Rule $rule): int
    {
        $level++;

        $this->decisions->decide($literal, $level, $rule);

        while (true) {
            $rule = $this->propagate($level);

            if (null === $rule) {
                break;
            }

            if ($level === 1) {
                $this->analyzeUnsolvable($rule);

                return 0;
            }

            // conflict
            [$learnLiteral, $newLevel, $newRule, $why] = $this->analyze($level, $rule);

            if ($newLevel <= 0 || $newLevel >= $level) {
                throw new SolverBugException(
                    "Trying to revert to invalid level ".$newLevel." from level ".$level."."
                );
            }

            $level = $newLevel;

            $this->revert($level);

            $this->rules->add($newRule, RuleSet::TYPE_LEARNED);

            $this->learnedWhy[spl_object_hash($newRule)] = $why;

            $ruleNode = new RuleWatchNode($newRule);
            $ruleNode->watch2OnHighest($this->decisions);
            $this->watchGraph->insert($ruleNode);

            $this->decisions->decide($learnLiteral, $level, $newRule);
        }

        return $level;
    }

    /**
     * @param non-empty-list<int> $decisionQueue
     */
    private function selectAndInstall(int $level, array $decisionQueue, Rule $rule): int
    {
        // choose best package to install from decisionQueue
        $literals = $this->policy->selectPreferredPackages($this->pool, $decisionQueue, $rule->getRequiredPackage());

        $selectedLiteral = array_shift($literals);

        // if there are multiple candidates, then branch
        if (\count($literals) > 0) {
            $this->branches[] = [$literals, $level];
        }

        return $this->setPropagateLearn($level, $selectedLiteral, $rule);
    }

    /**
     * @return array{int, int, GenericRule, int}
     */
    protected function analyze(int $level, Rule $rule): array
    {
        $analyzedRule = $rule;
        $ruleLevel = 1;
        $num = 0;
        $l1num = 0;
        $seen = [];
        $learnedLiteral = null;
        $otherLearnedLiterals = [];

        $decisionId = \count($this->decisions);

        $this->learnedPool[] = [];

        while (true) {
            $this->learnedPool[\count($this->learnedPool) - 1][] = $rule;

            foreach ($rule->getLiterals() as $literal) {
                // multiconflictrule is really a bunch of rules in one, so some may not have finished propagating yet
                if ($rule instanceof MultiConflictRule && !$this->decisions->decided($literal)) {
                    continue;
                }

                // skip the one true literal
                if ($this->decisions->satisfy($literal)) {
                    continue;
                }

                if (isset($seen[abs($literal)])) {
                    continue;
                }
                $seen[abs($literal)] = true;

                $l = $this->decisions->decisionLevel($literal);

                if (1 === $l) {
                    $l1num++;
                } elseif ($level === $l) {
                    $num++;
                } else {
                    // not level1 or conflict level, add to new rule
                    $otherLearnedLiterals[] = $literal;

                    if ($l > $ruleLevel) {
                        $ruleLevel = $l;
                    }
                }
            }
            unset($literal);

            $l1retry = true;
            while ($l1retry) {
                $l1retry = false;

                if (0 === $num && 0 === --$l1num) {
                    // all level 1 literals done
                    break 2;
                }

                while (true) {
                    if ($decisionId <= 0) {
                        throw new SolverBugException(
                            "Reached invalid decision id $decisionId while looking through $rule for a literal present in the analyzed rule $analyzedRule."
                        );
                    }

                    $decisionId--;

                    $decision = $this->decisions->atOffset($decisionId);
                    $literal = $decision[Decisions::DECISION_LITERAL];

                    if (isset($seen[abs($literal)])) {
                        break;
                    }
                }

                unset($seen[abs($literal)]);

                if (0 !== $num && 0 === --$num) {
                    if ($literal < 0) {
                        $this->testFlagLearnedPositiveLiteral = true;
                    }
                    $learnedLiteral = -$literal;

                    if (0 === $l1num) {
                        break 2;
                    }

                    foreach ($otherLearnedLiterals as $otherLiteral) {
                        unset($seen[abs($otherLiteral)]);
                    }
                    // only level 1 marks left
                    $l1num++;
                    $l1retry = true;
                } else {
                    $decision = $this->decisions->atOffset($decisionId);
                    $rule = $decision[Decisions::DECISION_REASON];

                    if ($rule instanceof MultiConflictRule) {
                        // there is only ever exactly one positive decision in a MultiConflictRule
                        foreach ($rule->getLiterals() as $ruleLiteral) {
                            if (!isset($seen[abs($ruleLiteral)]) && $this->decisions->satisfy(-$ruleLiteral)) {
                                $this->learnedPool[\count($this->learnedPool) - 1][] = $rule;
                                $l = $this->decisions->decisionLevel($ruleLiteral);
                                if (1 === $l) {
                                    $l1num++;
                                } elseif ($level === $l) {
                                    $num++;
                                } else {
                                    // not level1 or conflict level, add to new rule
                                    $otherLearnedLiterals[] = $ruleLiteral;

                                    if ($l > $ruleLevel) {
                                        $ruleLevel = $l;
                                    }
                                }
                                $seen[abs($ruleLiteral)] = true;
                                break;
                            }
                        }

                        $l1retry = true;
                    }
                }
            }

            $decision = $this->decisions->atOffset($decisionId);
            $rule = $decision[Decisions::DECISION_REASON];
        }

        $why = \count($this->learnedPool) - 1;

        if (null === $learnedLiteral) {
            throw new SolverBugException(
                "Did not find a learnable literal in analyzed rule $analyzedRule."
            );
        }

        array_unshift($otherLearnedLiterals, $learnedLiteral);
        $newRule = new GenericRule($otherLearnedLiterals, Rule::RULE_LEARNED, $why);

        return [$learnedLiteral, $ruleLevel, $newRule, $why];
    }

    /**
     * @param array<string, true> $ruleSeen
     */
    private function analyzeUnsolvableRule(Problem $problem, Rule $conflictRule, array &$ruleSeen): void
    {
        $why = spl_object_hash($conflictRule);
        $ruleSeen[$why] = true;

        if ($conflictRule->getType() === RuleSet::TYPE_LEARNED) {
            $learnedWhy = $this->learnedWhy[$why];
            $problemRules = $this->learnedPool[$learnedWhy];

            foreach ($problemRules as $problemRule) {
                if (!isset($ruleSeen[spl_object_hash($problemRule)])) {
                    $this->analyzeUnsolvableRule($problem, $problemRule, $ruleSeen);
                }
            }

            return;
        }

        if ($conflictRule->getType() === RuleSet::TYPE_PACKAGE) {
            // package rules cannot be part of a problem
            return;
        }

        $problem->nextSection();
        $problem->addRule($conflictRule);
    }

    private function analyzeUnsolvable(Rule $conflictRule): void
    {
        $problem = new Problem();
        $problem->addRule($conflictRule);

        $ruleSeen = [];

        $this->analyzeUnsolvableRule($problem, $conflictRule, $ruleSeen);

        $this->problems[] = $problem;

        $seen = [];
        $literals = $conflictRule->getLiterals();

        foreach ($literals as $literal) {
            // skip the one true literal
            if ($this->decisions->satisfy($literal)) {
                continue;
            }
            $seen[abs($literal)] = true;
        }

        foreach ($this->decisions as $decision) {
            $decisionLiteral = $decision[Decisions::DECISION_LITERAL];

            // skip literals that are not in this rule
            if (!isset($seen[abs($decisionLiteral)])) {
                continue;
            }

            $why = $decision[Decisions::DECISION_REASON];

            $problem->addRule($why);
            $this->analyzeUnsolvableRule($problem, $why, $ruleSeen);

            $literals = $why->getLiterals();
            foreach ($literals as $literal) {
                // skip the one true literal
                if ($this->decisions->satisfy($literal)) {
                    continue;
                }
                $seen[abs($literal)] = true;
            }
        }
    }

    private function runSat(): void
    {
        $this->propagateIndex = 0;

        /*
         * here's the main loop:
         * 1) propagate new decisions (only needed once)
         * 2) fulfill root requires/fixed packages
         * 3) fulfill all unresolved rules
         * 4) minimalize solution if we had choices
         * if we encounter a problem, we rewind to a safe level and restart
         * with step 1
         */

        $level = 1;
        $systemLevel = $level + 1;

        while (true) {
            if (1 === $level) {
                $conflictRule = $this->propagate($level);
                if (null !== $conflictRule) {
                    $this->analyzeUnsolvable($conflictRule);

                    return;
                }
            }

            // handle root require/fixed package rules
            if ($level < $systemLevel) {
                $iterator = $this->rules->getIteratorFor(RuleSet::TYPE_REQUEST);
                foreach ($iterator as $rule) {
                    if ($rule->isEnabled()) {
                        $decisionQueue = [];
                        $noneSatisfied = true;

                        foreach ($rule->getLiterals() as $literal) {
                            if ($this->decisions->satisfy($literal)) {
                                $noneSatisfied = false;
                                break;
                            }
                            if ($literal > 0 && $this->decisions->undecided($literal)) {
                                $decisionQueue[] = $literal;
                            }
                        }

                        if ($noneSatisfied && \count($decisionQueue) > 0) {
                            // if any of the options in the decision queue are fixed, only use those
                            $prunedQueue = [];
                            foreach ($decisionQueue as $literal) {
                                if (isset($this->fixedMap[abs($literal)])) {
                                    $prunedQueue[] = $literal;
                                }
                            }
                            if (\count($prunedQueue) > 0) {
                                $decisionQueue = $prunedQueue;
                            }
                        }

                        if ($noneSatisfied && \count($decisionQueue) > 0) {
                            $oLevel = $level;
                            $level = $this->selectAndInstall($level, $decisionQueue, $rule);

                            if (0 === $level) {
                                return;
                            }
                            if ($level <= $oLevel) {
                                break;
                            }
                        }
                    }
                }

                $systemLevel = $level + 1;

                // root requires/fixed packages left
                $iterator->next();
                if ($iterator->valid()) {
                    continue;
                }
            }

            if ($level < $systemLevel) {
                $systemLevel = $level;
            }

            $rulesCount = \count($this->rules);
            $pass = 1;

            $this->io->writeError('Looking at all rules.', true, IOInterface::DEBUG);
            for ($i = 0, $n = 0; $n < $rulesCount; $i++, $n++) {
                if ($i === $rulesCount) {
                    if (1 === $pass) {
                        $this->io->writeError("Something's changed, looking at all rules again (pass #$pass)", false, IOInterface::DEBUG);
                    } else {
                        $this->io->overwriteError("Something's changed, looking at all rules again (pass #$pass)", false, null, IOInterface::DEBUG);
                    }

                    $i = 0;
                    $pass++;
                }

                $rule = $this->rules->ruleById[$i];
                $literals = $rule->getLiterals();

                if ($rule->isDisabled()) {
                    continue;
                }

                $decisionQueue = [];

                // make sure that
                // * all negative literals are installed
                // * no positive literal is installed
                // i.e. the rule is not fulfilled and we
                // just need to decide on the positive literals
                //
                foreach ($literals as $literal) {
                    if ($literal <= 0) {
                        if (!$this->decisions->decidedInstall($literal)) {
                            continue 2; // next rule
                        }
                    } else {
                        if ($this->decisions->decidedInstall($literal)) {
                            continue 2; // next rule
                        }
                        if ($this->decisions->undecided($literal)) {
                            $decisionQueue[] = $literal;
                        }
                    }
                }

                // need to have at least 2 item to pick from
                if (\count($decisionQueue) < 2) {
                    continue;
                }

                $level = $this->selectAndInstall($level, $decisionQueue, $rule);

                if (0 === $level) {
                    return;
                }

                // something changed, so look at all rules again
                $rulesCount = \count($this->rules);
                $n = -1;
            }

            if ($level < $systemLevel) {
                continue;
            }

            // minimization step
            if (\count($this->branches) > 0) {
                $lastLiteral = null;
                $lastLevel = null;
                $lastBranchIndex = 0;
                $lastBranchOffset = 0;

                for ($i = \count($this->branches) - 1; $i >= 0; $i--) {
                    [$literals, $l] = $this->branches[$i];

                    foreach ($literals as $offset => $literal) {
                        if ($literal > 0 && $this->decisions->decisionLevel($literal) > $l + 1) {
                            $lastLiteral = $literal;
                            $lastBranchIndex = $i;
                            $lastBranchOffset = $offset;
                            $lastLevel = $l;
                        }
                    }
                }

                if ($lastLiteral !== null) {
                    assert($lastLevel !== null);
                    unset($this->branches[$lastBranchIndex][self::BRANCH_LITERALS][$lastBranchOffset]);

                    $level = $lastLevel;
                    $this->revert($level);

                    $why = $this->decisions->lastReason();

                    $level = $this->setPropagateLearn($level, $lastLiteral, $why);

                    if ($level === 0) {
                        return;
                    }

                    continue;
                }
            }

            break;
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Package\AliasPackage;
use Composer\Package\BasePackage;
use Composer\Package\Package;
use Composer\Pcre\Preg;

/**
 * @author Nils Adermann <naderman@naderman.de>
 * @internal
 */
class LockTransaction extends Transaction
{
    /**
     * packages in current lock file, platform repo or otherwise present
     *
     * Indexed by spl_object_hash
     *
     * @var array<string, BasePackage>
     */
    protected $presentMap;

    /**
     * Packages which cannot be mapped, platform repo, root package, other fixed repos
     *
     * Indexed by package id
     *
     * @var array<int, BasePackage>
     */
    protected $unlockableMap;

    /**
     * @var array{dev: BasePackage[], non-dev: BasePackage[], all: BasePackage[]}
     */
    protected $resultPackages;

    /**
     * @param array<string, BasePackage> $presentMap
     * @param array<int, BasePackage> $unlockableMap
     */
    public function __construct(Pool $pool, array $presentMap, array $unlockableMap, Decisions $decisions)
    {
        $this->presentMap = $presentMap;
        $this->unlockableMap = $unlockableMap;

        $this->setResultPackages($pool, $decisions);
        parent::__construct($this->presentMap, $this->resultPackages['all']);
    }

    // TODO make this a bit prettier instead of the two text indexes?

    public function setResultPackages(Pool $pool, Decisions $decisions): void
    {
        $this->resultPackages = ['all' => [], 'non-dev' => [], 'dev' => []];
        foreach ($decisions as $i => $decision) {
            $literal = $decision[Decisions::DECISION_LITERAL];

            if ($literal > 0) {
                $package = $pool->literalToPackage($literal);

                $this->resultPackages['all'][] = $package;
                if (!isset($this->unlockableMap[$package->id])) {
                    $this->resultPackages['non-dev'][] = $package;
                }
            }
        }
    }

    public function setNonDevPackages(LockTransaction $extractionResult): void
    {
        $packages = $extractionResult->getNewLockPackages(false);

        $this->resultPackages['dev'] = $this->resultPackages['non-dev'];
        $this->resultPackages['non-dev'] = [];

        foreach ($packages as $package) {
            foreach ($this->resultPackages['dev'] as $i => $resultPackage) {
                // TODO this comparison is probably insufficient, aliases, what about modified versions? I guess they aren't possible?
                if ($package->getName() === $resultPackage->getName()) {
                    $this->resultPackages['non-dev'][] = $resultPackage;
                    unset($this->resultPackages['dev'][$i]);
                }
            }
        }
    }

    // TODO additionalFixedRepository needs to be looked at here as well?
    /**
     * @return BasePackage[]
     */
    public function getNewLockPackages(bool $devMode, bool $updateMirrors = false): array
    {
        $packages = [];
        foreach ($this->resultPackages[$devMode ? 'dev' : 'non-dev'] as $package) {
            if ($package instanceof AliasPackage) {
                continue;
            }

            // if we're just updating mirrors we need to reset everything to the same as currently "present" packages' references to keep the lock file as-is
            if ($updateMirrors === true && !array_key_exists(spl_object_hash($package), $this->presentMap)) {
                $package = $this->updateMirrorAndUrls($package);
            }

            $packages[] = $package;
        }

        return $packages;
    }

    /**
     * Try to return the original package from presentMap with updated URLs/mirrors
     *
     * If the type of source/dist changed, then we do not update those and keep them as they were
     */
    private function updateMirrorAndUrls(BasePackage $package): BasePackage
    {
        foreach ($this->presentMap as $presentPackage) {
            if ($package->getName() !== $presentPackage->getName()) {
                continue;
            }

            if ($package->getVersion() !== $presentPackage->getVersion()) {
                continue;
            }

            if ($presentPackage->getSourceReference() === null) {
                continue;
            }

            if ($presentPackage->getSourceType() !== $package->getSourceType()) {
                continue;
            }

            if ($presentPackage instanceof Package) {
                $presentPackage->setSourceUrl($package->getSourceUrl());
                $presentPackage->setSourceMirrors($package->getSourceMirrors());
            }

            // if the dist type changed, we only update the source url/mirrors
            if ($presentPackage->getDistType() !== $package->getDistType()) {
                return $presentPackage;
            }

            // update dist url if it is in a known format
            if (
                $package->getDistUrl() !== null
                && $presentPackage->getDistReference() !== null
                && Preg::isMatch('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com|(?:www\.)?gitlab\.com)/}i', $package->getDistUrl())
            ) {
                $presentPackage->setDistUrl(Preg::replace('{(?<=/|sha=)[a-f0-9]{40}(?=/|$)}i', $presentPackage->getDistReference(), $package->getDistUrl()));
            }
            $presentPackage->setDistMirrors($package->getDistMirrors());

            return $presentPackage;
        }

        return $package;
    }

    /**
     * Checks which of the given aliases from composer.json are actually in use for the lock file
     * @param list<array{package: string, version: string, alias: string, alias_normalized: string}> $aliases
     * @return list<array{package: string, version: string, alias: string, alias_normalized: string}>
     */
    public function getAliases(array $aliases): array
    {
        $usedAliases = [];

        foreach ($this->resultPackages['all'] as $package) {
            if ($package instanceof AliasPackage) {
                foreach ($aliases as $index => $alias) {
                    if ($alias['package'] === $package->getName()) {
                        $usedAliases[] = $alias;
                        unset($aliases[$index]);
                    }
                }
            }
        }

        usort($usedAliases, static function ($a, $b): int {
            return strcmp($a['package'], $b['package']);
        });

        return $usedAliases;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Package\PackageInterface;
use Composer\Semver\Constraint\Constraint;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
interface PolicyInterface
{
    /**
     * @phpstan-param Constraint::STR_OP_* $operator
     */
    public function versionCompare(PackageInterface $a, PackageInterface $b, string $operator): bool;

    /**
     * @param  non-empty-list<int>   $literals
     * @return non-empty-list<int>
     */
    public function selectPreferredPackages(Pool $pool, array $literals, ?string $requiredPackage = null): array;
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

/**
 * Stores decisions on installing, removing or keeping packages
 *
 * @author Nils Adermann <naderman@naderman.de>
 * @implements \Iterator<array{0: int, 1: Rule}>
 */
class Decisions implements \Iterator, \Countable
{
    public const DECISION_LITERAL = 0;
    public const DECISION_REASON = 1;

    /** @var Pool */
    protected $pool;
    /** @var array<int, int> */
    protected $decisionMap;
    /**
     * @var array<int, array{0: int, 1: Rule}>
     */
    protected $decisionQueue = [];

    public function __construct(Pool $pool)
    {
        $this->pool = $pool;
        $this->decisionMap = [];
    }

    public function decide(int $literal, int $level, Rule $why): void
    {
        $this->addDecision($literal, $level);
        $this->decisionQueue[] = [
            self::DECISION_LITERAL => $literal,
            self::DECISION_REASON => $why,
        ];
    }

    public function satisfy(int $literal): bool
    {
        $packageId = abs($literal);

        return (
            $literal > 0 && isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0 ||
            $literal < 0 && isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] < 0
        );
    }

    public function conflict(int $literal): bool
    {
        $packageId = abs($literal);

        return (
            (isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0 && $literal < 0) ||
            (isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] < 0 && $literal > 0)
        );
    }

    public function decided(int $literalOrPackageId): bool
    {
        return ($this->decisionMap[abs($literalOrPackageId)] ?? 0) !== 0;
    }

    public function undecided(int $literalOrPackageId): bool
    {
        return ($this->decisionMap[abs($literalOrPackageId)] ?? 0) === 0;
    }

    public function decidedInstall(int $literalOrPackageId): bool
    {
        $packageId = abs($literalOrPackageId);

        return isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0;
    }

    public function decisionLevel(int $literalOrPackageId): int
    {
        $packageId = abs($literalOrPackageId);
        if (isset($this->decisionMap[$packageId])) {
            return abs($this->decisionMap[$packageId]);
        }

        return 0;
    }

    public function decisionRule(int $literalOrPackageId): Rule
    {
        $packageId = abs($literalOrPackageId);

        foreach ($this->decisionQueue as $decision) {
            if ($packageId === abs($decision[self::DECISION_LITERAL])) {
                return $decision[self::DECISION_REASON];
            }
        }

        throw new \LogicException('Did not find a decision rule using '.$literalOrPackageId);
    }

    /**
     * @return array{0: int, 1: Rule} a literal and decision reason
     */
    public function atOffset(int $queueOffset): array
    {
        return $this->decisionQueue[$queueOffset];
    }

    public function validOffset(int $queueOffset): bool
    {
        return $queueOffset >= 0 && $queueOffset < \count($this->decisionQueue);
    }

    public function lastReason(): Rule
    {
        return $this->decisionQueue[\count($this->decisionQueue) - 1][self::DECISION_REASON];
    }

    public function lastLiteral(): int
    {
        return $this->decisionQueue[\count($this->decisionQueue) - 1][self::DECISION_LITERAL];
    }

    public function reset(): void
    {
        while ($decision = array_pop($this->decisionQueue)) {
            $this->decisionMap[abs($decision[self::DECISION_LITERAL])] = 0;
        }
    }

    /**
     * @param int<-1, max> $offset
     */
    public function resetToOffset(int $offset): void
    {
        while (\count($this->decisionQueue) > $offset + 1) {
            $decision = array_pop($this->decisionQueue);
            $this->decisionMap[abs($decision[self::DECISION_LITERAL])] = 0;
        }
    }

    public function revertLast(): void
    {
        $this->decisionMap[abs($this->lastLiteral())] = 0;
        array_pop($this->decisionQueue);
    }

    public function count(): int
    {
        return \count($this->decisionQueue);
    }

    public function rewind(): void
    {
        end($this->decisionQueue);
    }

    /**
     * @return array{0: int, 1: Rule}|false
     */
    #[\ReturnTypeWillChange]
    public function current()
    {
        return current($this->decisionQueue);
    }

    public function key(): ?int
    {
        return key($this->decisionQueue);
    }

    public function next(): void
    {
        prev($this->decisionQueue);
    }

    public function valid(): bool
    {
        return false !== current($this->decisionQueue);
    }

    public function isEmpty(): bool
    {
        return \count($this->decisionQueue) === 0;
    }

    protected function addDecision(int $literal, int $level): void
    {
        $packageId = abs($literal);

        $previousDecision = $this->decisionMap[$packageId] ?? 0;
        if ($previousDecision !== 0) {
            $literalString = $this->pool->literalToPrettyString($literal, []);
            $package = $this->pool->literalToPackage($literal);
            throw new SolverBugException(
                "Trying to decide $literalString on level $level, even though $package was previously decided as ".$previousDecision."."
            );
        }

        if ($literal > 0) {
            $this->decisionMap[$packageId] = $level;
        } else {
            $this->decisionMap[$packageId] = -$level;
        }
    }

    public function toString(?Pool $pool = null): string
    {
        $decisionMap = $this->decisionMap;
        ksort($decisionMap);
        $str = '[';
        foreach ($decisionMap as $packageId => $level) {
            $str .= ($pool !== null ? $pool->literalToPackage($packageId) : $packageId).':'.$level.',';
        }
        $str .= ']';

        return $str;
    }

    public function __toString(): string
    {
        return $this->toString();
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\EventDispatcher\EventDispatcher;
use Composer\IO\IOInterface;
use Composer\Package\AliasPackage;
use Composer\Package\BasePackage;
use Composer\Package\CompleteAliasPackage;
use Composer\Package\CompletePackage;
use Composer\Package\PackageInterface;
use Composer\Package\Version\StabilityFilter;
use Composer\Pcre\Preg;
use Composer\Plugin\PluginEvents;
use Composer\Plugin\PrePoolCreateEvent;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\RootPackageRepository;
use Composer\Semver\CompilingMatcher;
use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\MatchAllConstraint;
use Composer\Semver\Constraint\MultiConstraint;
use Composer\Semver\Intervals;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
class PoolBuilder
{
    /**
     * @var int[]
     * @phpstan-var array<key-of<BasePackage::STABILITIES>, BasePackage::STABILITY_*>
     */
    private $acceptableStabilities;
    /**
     * @var int[]
     * @phpstan-var array<string, BasePackage::STABILITY_*>
     */
    private $stabilityFlags;
    /**
     * @var array[]
     * @phpstan-var array<string, array<string, array{alias: string, alias_normalized: string}>>
     */
    private $rootAliases;
    /**
     * @var string[]
     * @phpstan-var array<string, string>
     */
    private $rootReferences;
    /**
     * @var array<string, ConstraintInterface>
     */
    private $temporaryConstraints;
    /**
     * @var ?EventDispatcher
     */
    private $eventDispatcher;
    /**
     * @var PoolOptimizer|null
     */
    private $poolOptimizer;
    /**
     * @var IOInterface
     */
    private $io;
    /**
     * @var array[]
     * @phpstan-var array<string, AliasPackage[]>
     */
    private $aliasMap = [];
    /**
     * @var ConstraintInterface[]
     * @phpstan-var array<string, ConstraintInterface>
     */
    private $packagesToLoad = [];
    /**
     * @var ConstraintInterface[]
     * @phpstan-var array<string, ConstraintInterface>
     */
    private $loadedPackages = [];
    /**
     * @var array[]
     * @phpstan-var array<int, array<string, array<string, PackageInterface>>>
     */
    private $loadedPerRepo = [];
    /**
     * @var array<int, BasePackage>
     */
    private $packages = [];
    /**
     * @var BasePackage[]
     */
    private $unacceptableFixedOrLockedPackages = [];
    /** @var array<string> */
    private $updateAllowList = [];
    /** @var array<string, array<PackageInterface>> */
    private $skippedLoad = [];
    /** @var list<string> */
    private $ignoredTypes = [];
    /** @var list<string>|null */
    private $allowedTypes = null;

    /**
     * If provided, only these package names are loaded
     *
     * This is a special-use functionality of the Request class to optimize the pool creation process
     * when only a minimal subset of packages is needed and we do not need their dependencies.
     *
     * @var array<string, int>|null
     */
    private $restrictedPackagesList = null;

    /**
     * Keeps a list of dependencies which are locked but were auto-unlocked as they are path repositories
     *
     * This half-unlocked state means the package itself will update but the UPDATE_LISTED_WITH_TRANSITIVE_DEPS*
     * flags will not apply until the package really gets unlocked in some other way than being a path repo
     *
     * @var array<string, true>
     */
    private $pathRepoUnlocked = [];

    /**
     * Keeps a list of dependencies which are root requirements, and as such
     * have already their maximum required range loaded and can not be
     * extended by markPackageNameForLoading
     *
     * Packages get cleared from this list if they get unlocked as in that case
     * we need to actually load them
     *
     * @var array<string, true>
     */
    private $maxExtendedReqs = [];
    /**
     * @var array
     * @phpstan-var array<string, bool>
     */
    private $updateAllowWarned = [];

    /** @var int */
    private $indexCounter = 0;

    /**
     * @param int[] $acceptableStabilities array of stability => BasePackage::STABILITY_* value
     * @phpstan-param array<key-of<BasePackage::STABILITIES>, BasePackage::STABILITY_*> $acceptableStabilities
     * @param int[] $stabilityFlags an array of package name => BasePackage::STABILITY_* value
     * @phpstan-param array<string, BasePackage::STABILITY_*> $stabilityFlags
     * @param array[] $rootAliases
     * @phpstan-param array<string, array<string, array{alias: string, alias_normalized: string}>> $rootAliases
     * @param string[] $rootReferences an array of package name => source reference
     * @phpstan-param array<string, string> $rootReferences
     * @param array<string, ConstraintInterface> $temporaryConstraints Runtime temporary constraints that will be used to filter packages
     */
    public function __construct(array $acceptableStabilities, array $stabilityFlags, array $rootAliases, array $rootReferences, IOInterface $io, ?EventDispatcher $eventDispatcher = null, ?PoolOptimizer $poolOptimizer = null, array $temporaryConstraints = [])
    {
        $this->acceptableStabilities = $acceptableStabilities;
        $this->stabilityFlags = $stabilityFlags;
        $this->rootAliases = $rootAliases;
        $this->rootReferences = $rootReferences;
        $this->eventDispatcher = $eventDispatcher;
        $this->poolOptimizer = $poolOptimizer;
        $this->io = $io;
        $this->temporaryConstraints = $temporaryConstraints;
    }

    /**
     * Packages of those types are ignored
     *
     * @param list<string> $types
     */
    public function setIgnoredTypes(array $types): void
    {
        $this->ignoredTypes = $types;
    }

    /**
     * Only packages of those types are allowed if set to non-null
     *
     * @param list<string>|null $types
     */
    public function setAllowedTypes(?array $types): void
    {
        $this->allowedTypes = $types;
    }

    /**
     * @param RepositoryInterface[] $repositories
     */
    public function buildPool(array $repositories, Request $request): Pool
    {
        $this->restrictedPackagesList = $request->getRestrictedPackages() !== null ? array_flip($request->getRestrictedPackages()) : null;

        if (\count($request->getUpdateAllowList()) > 0) {
            $this->updateAllowList = $request->getUpdateAllowList();
            $this->warnAboutNonMatchingUpdateAllowList($request);

            if (null === $request->getLockedRepository()) {
                throw new \LogicException('No lock repo present and yet a partial update was requested.');
            }

            foreach ($request->getLockedRepository()->getPackages() as $lockedPackage) {
                if (!$this->isUpdateAllowed($lockedPackage)) {
                    // remember which packages we skipped loading remote content for in this partial update
                    $this->skippedLoad[$lockedPackage->getName()][] = $lockedPackage;
                    foreach ($lockedPackage->getReplaces() as $link) {
                        $this->skippedLoad[$link->getTarget()][] = $lockedPackage;
                    }

                    // Path repo packages are never loaded from lock, to force them to always remain in sync
                    // unless symlinking is disabled in which case we probably should rather treat them like
                    // regular packages. We mark them specially so they can be reloaded fully including update propagation
                    // if they do get unlocked, but by default they are unlocked without update propagation.
                    if ($lockedPackage->getDistType() === 'path') {
                        $transportOptions = $lockedPackage->getTransportOptions();
                        if (!isset($transportOptions['symlink']) || $transportOptions['symlink'] !== false) {
                            $this->pathRepoUnlocked[$lockedPackage->getName()] = true;
                            continue;
                        }
                    }

                    $request->lockPackage($lockedPackage);
                }
            }
        }

        foreach ($request->getFixedOrLockedPackages() as $package) {
            // using MatchAllConstraint here because fixed packages do not need to retrigger
            // loading any packages
            $this->loadedPackages[$package->getName()] = new MatchAllConstraint();

            // replace means conflict, so if a fixed package replaces a name, no need to load that one, packages would conflict anyways
            foreach ($package->getReplaces() as $link) {
                $this->loadedPackages[$link->getTarget()] = new MatchAllConstraint();
            }

            // TODO in how far can we do the above for conflicts? It's more tricky cause conflicts can be limited to
            // specific versions while replace is a conflict with all versions of the name

            if (
                $package->getRepository() instanceof RootPackageRepository
                || $package->getRepository() instanceof PlatformRepository
                || StabilityFilter::isPackageAcceptable($this->acceptableStabilities, $this->stabilityFlags, $package->getNames(), $package->getStability())
            ) {
                $this->loadPackage($request, $repositories, $package, false);
            } else {
                $this->unacceptableFixedOrLockedPackages[] = $package;
            }
        }

        foreach ($request->getRequires() as $packageName => $constraint) {
            // fixed and locked packages have already been added, so if a root require needs one of them, no need to do anything
            if (isset($this->loadedPackages[$packageName])) {
                continue;
            }

            $this->packagesToLoad[$packageName] = $constraint;
            $this->maxExtendedReqs[$packageName] = true;
        }

        // clean up packagesToLoad for anything we manually marked loaded above
        foreach ($this->packagesToLoad as $name => $constraint) {
            if (isset($this->loadedPackages[$name])) {
                unset($this->packagesToLoad[$name]);
            }
        }

        while (\count($this->packagesToLoad) > 0) {
            $this->loadPackagesMarkedForLoading($request, $repositories);
        }

        if (\count($this->temporaryConstraints) > 0) {
            foreach ($this->packages as $i => $package) {
                // we check all alias related packages at once, so no need to check individual aliases
                if ($package instanceof AliasPackage) {
                    continue;
                }

                foreach ($package->getNames() as $packageName) {
                    if (!isset($this->temporaryConstraints[$packageName])) {
                        continue;
                    }

                    $constraint = $this->temporaryConstraints[$packageName];
                    $packageAndAliases = [$i => $package];
                    if (isset($this->aliasMap[spl_object_hash($package)])) {
                        $packageAndAliases += $this->aliasMap[spl_object_hash($package)];
                    }

                    $found = false;
                    foreach ($packageAndAliases as $packageOrAlias) {
                        if (CompilingMatcher::match($constraint, Constraint::OP_EQ, $packageOrAlias->getVersion())) {
                            $found = true;
                        }
                    }

                    if (!$found) {
                        foreach ($packageAndAliases as $index => $packageOrAlias) {
                            unset($this->packages[$index]);
                        }
                    }
                }
            }
        }

        if ($this->eventDispatcher !== null) {
            $prePoolCreateEvent = new PrePoolCreateEvent(
                PluginEvents::PRE_POOL_CREATE,
                $repositories,
                $request,
                $this->acceptableStabilities,
                $this->stabilityFlags,
                $this->rootAliases,
                $this->rootReferences,
                $this->packages,
                $this->unacceptableFixedOrLockedPackages
            );
            $this->eventDispatcher->dispatch($prePoolCreateEvent->getName(), $prePoolCreateEvent);
            $this->packages = $prePoolCreateEvent->getPackages();
            $this->unacceptableFixedOrLockedPackages = $prePoolCreateEvent->getUnacceptableFixedPackages();
        }

        $pool = new Pool($this->packages, $this->unacceptableFixedOrLockedPackages);

        $this->aliasMap = [];
        $this->packagesToLoad = [];
        $this->loadedPackages = [];
        $this->loadedPerRepo = [];
        $this->packages = [];
        $this->unacceptableFixedOrLockedPackages = [];
        $this->maxExtendedReqs = [];
        $this->skippedLoad = [];
        $this->indexCounter = 0;

        $this->io->debug('Built pool.');

        $pool = $this->runOptimizer($request, $pool);

        Intervals::clear();

        return $pool;
    }

    private function markPackageNameForLoading(Request $request, string $name, ConstraintInterface $constraint): void
    {
        // Skip platform requires at this stage
        if (PlatformRepository::isPlatformPackage($name)) {
            return;
        }

        // Root require (which was not unlocked) already loaded the maximum range so no
        // need to check anything here
        if (isset($this->maxExtendedReqs[$name])) {
            return;
        }

        // Root requires can not be overruled by dependencies so there is no point in
        // extending the loaded constraint for those.
        // This is triggered when loading a root require which was locked but got unlocked, then
        // we make sure that we load at most the intervals covered by the root constraint.
        $rootRequires = $request->getRequires();
        if (isset($rootRequires[$name]) && !Intervals::isSubsetOf($constraint, $rootRequires[$name])) {
            $constraint = $rootRequires[$name];
        }

        // Not yet loaded or already marked for a reload, set the constraint to be loaded
        if (!isset($this->loadedPackages[$name])) {
            // Maybe it was already marked before but not loaded yet. In that case
            // we have to extend the constraint (we don't check if they are identical because
            // MultiConstraint::create() will optimize anyway)
            if (isset($this->packagesToLoad[$name])) {
                // Already marked for loading and this does not expand the constraint to be loaded, nothing to do
                if (Intervals::isSubsetOf($constraint, $this->packagesToLoad[$name])) {
                    return;
                }

                // extend the constraint to be loaded
                $constraint = Intervals::compactConstraint(MultiConstraint::create([$this->packagesToLoad[$name], $constraint], false));
            }

            $this->packagesToLoad[$name] = $constraint;

            return;
        }

        // No need to load this package with this constraint because it is
        // a subset of the constraint with which we have already loaded packages
        if (Intervals::isSubsetOf($constraint, $this->loadedPackages[$name])) {
            return;
        }

        // We have already loaded that package but not in the constraint that's
        // required. We extend the constraint and mark that package as not being loaded
        // yet so we get the required package versions
        $this->packagesToLoad[$name] = Intervals::compactConstraint(MultiConstraint::create([$this->loadedPackages[$name], $constraint], false));
        unset($this->loadedPackages[$name]);
    }

    /**
     * @param RepositoryInterface[] $repositories
     */
    private function loadPackagesMarkedForLoading(Request $request, array $repositories): void
    {
        foreach ($this->packagesToLoad as $name => $constraint) {
            if ($this->restrictedPackagesList !== null && !isset($this->restrictedPackagesList[$name])) {
                unset($this->packagesToLoad[$name]);
                continue;
            }
            $this->loadedPackages[$name] = $constraint;
        }

        $packageBatch = $this->packagesToLoad;
        $this->packagesToLoad = [];

        foreach ($repositories as $repoIndex => $repository) {
            if (0 === \count($packageBatch)) {
                break;
            }

            // these repos have their packages fixed or locked if they need to be loaded so we
            // never need to load anything else from them
            if ($repository instanceof PlatformRepository || $repository === $request->getLockedRepository()) {
                continue;
            }
            $result = $repository->loadPackages($packageBatch, $this->acceptableStabilities, $this->stabilityFlags, $this->loadedPerRepo[$repoIndex] ?? []);

            foreach ($result['namesFound'] as $name) {
                // avoid loading the same package again from other repositories once it has been found
                unset($packageBatch[$name]);
            }
            foreach ($result['packages'] as $package) {
                $this->loadedPerRepo[$repoIndex][$package->getName()][$package->getVersion()] = $package;

                if (in_array($package->getType(), $this->ignoredTypes, true) || ($this->allowedTypes !== null && !in_array($package->getType(), $this->allowedTypes, true))) {
                    continue;
                }
                $this->loadPackage($request, $repositories, $package, !isset($this->pathRepoUnlocked[$package->getName()]));
            }
        }
    }

    /**
     * @param RepositoryInterface[] $repositories
     */
    private function loadPackage(Request $request, array $repositories, BasePackage $package, bool $propagateUpdate): void
    {
        $index = $this->indexCounter++;
        $this->packages[$index] = $package;

        if ($package instanceof AliasPackage) {
            $this->aliasMap[spl_object_hash($package->getAliasOf())][$index] = $package;
        }

        $name = $package->getName();

        // we're simply setting the root references on all versions for a name here and rely on the solver to pick the
        // right version. It'd be more work to figure out which versions and which aliases of those versions this may
        // apply to
        if (isset($this->rootReferences[$name])) {
            // do not modify the references on already locked or fixed packages
            if (!$request->isLockedPackage($package) && !$request->isFixedPackage($package)) {
                $package->setSourceDistReferences($this->rootReferences[$name]);
            }
        }

        // if propagateUpdate is false we are loading a fixed or locked package, root aliases do not apply as they are
        // manually loaded as separate packages in this case
        //
        // packages in pathRepoUnlocked however need to also load root aliases, they have propagateUpdate set to
        // false because their deps should not be unlocked, but that is irrelevant for root aliases
        if (($propagateUpdate || isset($this->pathRepoUnlocked[$package->getName()])) && isset($this->rootAliases[$name][$package->getVersion()])) {
            $alias = $this->rootAliases[$name][$package->getVersion()];
            if ($package instanceof AliasPackage) {
                $basePackage = $package->getAliasOf();
            } else {
                $basePackage = $package;
            }
            if ($basePackage instanceof CompletePackage) {
                $aliasPackage = new CompleteAliasPackage($basePackage, $alias['alias_normalized'], $alias['alias']);
            } else {
                $aliasPackage = new AliasPackage($basePackage, $alias['alias_normalized'], $alias['alias']);
            }
            $aliasPackage->setRootPackageAlias(true);

            $newIndex = $this->indexCounter++;
            $this->packages[$newIndex] = $aliasPackage;
            $this->aliasMap[spl_object_hash($aliasPackage->getAliasOf())][$newIndex] = $aliasPackage;
        }

        foreach ($package->getRequires() as $link) {
            $require = $link->getTarget();
            $linkConstraint = $link->getConstraint();

            // if the required package is loaded as a locked package only and hasn't had its deps analyzed
            if (isset($this->skippedLoad[$require])) {
                // if we're doing a full update or this is a partial update with transitive deps and we're currently
                // looking at a package which needs to be updated we need to unlock the package we now know is a
                // dependency of another package which we are trying to update, and then attempt to load it again
                if ($propagateUpdate && $request->getUpdateAllowTransitiveDependencies()) {
                    $skippedRootRequires = $this->getSkippedRootRequires($request, $require);

                    if ($request->getUpdateAllowTransitiveRootDependencies() || 0 === \count($skippedRootRequires)) {
                        $this->unlockPackage($request, $repositories, $require);
                        $this->markPackageNameForLoading($request, $require, $linkConstraint);
                    } else {
                        foreach ($skippedRootRequires as $rootRequire) {
                            if (!isset($this->updateAllowWarned[$rootRequire])) {
                                $this->updateAllowWarned[$rootRequire] = true;
                                $this->io->writeError('<warning>Dependency '.$rootRequire.' is also a root requirement. Package has not been listed as an update argument, so keeping locked at old version. Use --with-all-dependencies (-W) to include root dependencies.</warning>');
                            }
                        }
                    }
                } elseif (isset($this->pathRepoUnlocked[$require]) && !isset($this->loadedPackages[$require])) {
                    // if doing a partial update and a package depends on a path-repo-unlocked package which is not referenced by the root, we need to ensure it gets loaded as it was not loaded by the request's root requirements
                    // and would not be loaded above if update propagation is not allowed (which happens if the requirer is itself a path-repo-unlocked package) or if transitive deps are not allowed to be unlocked
                    $this->markPackageNameForLoading($request, $require, $linkConstraint);
                }
            } else {
                $this->markPackageNameForLoading($request, $require, $linkConstraint);
            }
        }

        // if we're doing a partial update with deps we also need to unlock packages which are being replaced in case
        // they are currently locked and thus prevent this updateable package from being installable/updateable
        if ($propagateUpdate && $request->getUpdateAllowTransitiveDependencies()) {
            foreach ($package->getReplaces() as $link) {
                $replace = $link->getTarget();
                if (isset($this->loadedPackages[$replace], $this->skippedLoad[$replace])) {
                    $skippedRootRequires = $this->getSkippedRootRequires($request, $replace);

                    if ($request->getUpdateAllowTransitiveRootDependencies() || 0 === \count($skippedRootRequires)) {
                        $this->unlockPackage($request, $repositories, $replace);
                        // the replaced package only needs to be loaded if something else requires it
                        $this->markPackageNameForLoadingIfRequired($request, $replace);
                    } else {
                        foreach ($skippedRootRequires as $rootRequire) {
                            if (!isset($this->updateAllowWarned[$rootRequire])) {
                                $this->updateAllowWarned[$rootRequire] = true;
                                $this->io->writeError('<warning>Dependency '.$rootRequire.' is also a root requirement. Package has not been listed as an update argument, so keeping locked at old version. Use --with-all-dependencies (-W) to include root dependencies.</warning>');
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * Checks if a particular name is required directly in the request
     *
     * @param string $name packageName
     */
    private function isRootRequire(Request $request, string $name): bool
    {
        $rootRequires = $request->getRequires();

        return isset($rootRequires[$name]);
    }

    /**
     * @return string[]
     */
    private function getSkippedRootRequires(Request $request, string $name): array
    {
        if (!isset($this->skippedLoad[$name])) {
            return [];
        }

        $rootRequires = $request->getRequires();
        $matches = [];

        if (isset($rootRequires[$name])) {
            return array_map(static function (PackageInterface $package) use ($name): string {
                if ($name !== $package->getName()) {
                    return $package->getName() .' (via replace of '.$name.')';
                }

                return $package->getName();
            }, $this->skippedLoad[$name]);
        }

        foreach ($this->skippedLoad[$name] as $packageOrReplacer) {
            if (isset($rootRequires[$packageOrReplacer->getName()])) {
                $matches[] = $packageOrReplacer->getName();
            }
            foreach ($packageOrReplacer->getReplaces() as $link) {
                if (isset($rootRequires[$link->getTarget()])) {
                    if ($name !== $packageOrReplacer->getName()) {
                        $matches[] = $packageOrReplacer->getName() .' (via replace of '.$name.')';
                    } else {
                        $matches[] = $packageOrReplacer->getName();
                    }
                    break;
                }
            }
        }

        return $matches;
    }

    /**
     * Checks whether the update allow list allows this package in the lock file to be updated
     */
    private function isUpdateAllowed(BasePackage $package): bool
    {
        foreach ($this->updateAllowList as $pattern) {
            $patternRegexp = BasePackage::packageNameToRegexp($pattern);
            if (Preg::isMatch($patternRegexp, $package->getName())) {
                return true;
            }
        }

        return false;
    }

    private function warnAboutNonMatchingUpdateAllowList(Request $request): void
    {
        if (null === $request->getLockedRepository()) {
            throw new \LogicException('No lock repo present and yet a partial update was requested.');
        }

        foreach ($this->updateAllowList as $pattern) {
            $matchedPlatformPackage = false;

            $patternRegexp = BasePackage::packageNameToRegexp($pattern);
            // update pattern matches a locked package? => all good
            foreach ($request->getLockedRepository()->getPackages() as $package) {
                if (Preg::isMatch($patternRegexp, $package->getName())) {
                    continue 2;
                }
            }
            // update pattern matches a root require? => all good, probably a new package
            foreach ($request->getRequires() as $packageName => $constraint) {
                if (Preg::isMatch($patternRegexp, $packageName)) {
                    if (PlatformRepository::isPlatformPackage($packageName)) {
                        $matchedPlatformPackage = true;
                        continue;
                    }
                    continue 2;
                }
            }
            if ($matchedPlatformPackage) {
                $this->io->writeError('<warning>Pattern "' . $pattern . '" listed for update matches platform packages, but these cannot be updated by Composer.</warning>');
            } elseif (strpos($pattern, '*') !== false) {
                $this->io->writeError('<warning>Pattern "' . $pattern . '" listed for update does not match any locked packages.</warning>');
            } else {
                $this->io->writeError('<warning>Package "' . $pattern . '" listed for update is not locked.</warning>');
            }
        }
    }

    /**
     * Reverts the decision to use a locked package if a partial update with transitive dependencies
     * found that this package actually needs to be updated
     *
     * @param RepositoryInterface[] $repositories
     */
    private function unlockPackage(Request $request, array $repositories, string $name): void
    {
        foreach ($this->skippedLoad[$name] as $packageOrReplacer) {
            // if we unfixed a replaced package name, we also need to unfix the replacer itself
            // as long as it was not unfixed yet
            if ($packageOrReplacer->getName() !== $name && isset($this->skippedLoad[$packageOrReplacer->getName()])) {
                $replacerName = $packageOrReplacer->getName();
                if ($request->getUpdateAllowTransitiveRootDependencies() || (!$this->isRootRequire($request, $name) && !$this->isRootRequire($request, $replacerName))) {
                    $this->unlockPackage($request, $repositories, $replacerName);

                    if ($this->isRootRequire($request, $replacerName)) {
                        $this->markPackageNameForLoading($request, $replacerName, new MatchAllConstraint);
                    } else {
                        foreach ($this->packages as $loadedPackage) {
                            $requires = $loadedPackage->getRequires();
                            if (isset($requires[$replacerName])) {
                                $this->markPackageNameForLoading($request, $replacerName, $requires[$replacerName]->getConstraint());
                            }
                        }
                    }
                }
            }
        }

        if (isset($this->pathRepoUnlocked[$name])) {
            foreach ($this->packages as $index => $package) {
                if ($package->getName() === $name) {
                    $this->removeLoadedPackage($request, $repositories, $package, $index);
                }
            }
        }

        unset($this->skippedLoad[$name], $this->loadedPackages[$name], $this->maxExtendedReqs[$name], $this->pathRepoUnlocked[$name]);

        // remove locked package by this name which was already initialized
        foreach ($request->getLockedPackages() as $lockedPackage) {
            if (!($lockedPackage instanceof AliasPackage) && $lockedPackage->getName() === $name) {
                if (false !== $index = array_search($lockedPackage, $this->packages, true)) {
                    $request->unlockPackage($lockedPackage);
                    $this->removeLoadedPackage($request, $repositories, $lockedPackage, $index);

                    // make sure that any requirements for this package by other locked or fixed packages are now
                    // also loaded, as they were previously ignored because the locked (now unlocked) package already
                    // satisfied their requirements
                    // and if this package is replacing another that is required by a locked or fixed package, ensure
                    // that we load that replaced package in case an update to this package removes the replacement
                    foreach ($request->getFixedOrLockedPackages() as $fixedOrLockedPackage) {
                        if ($fixedOrLockedPackage === $lockedPackage) {
                            continue;
                        }

                        if (isset($this->skippedLoad[$fixedOrLockedPackage->getName()])) {
                            $requires = $fixedOrLockedPackage->getRequires();
                            if (isset($requires[$lockedPackage->getName()])) {
                                $this->markPackageNameForLoading($request, $lockedPackage->getName(), $requires[$lockedPackage->getName()]->getConstraint());
                            }

                            foreach ($lockedPackage->getReplaces() as $replace) {
                                if (isset($requires[$replace->getTarget()], $this->skippedLoad[$replace->getTarget()])) {
                                    $this->unlockPackage($request, $repositories, $replace->getTarget());
                                    // this package is in $requires so no need to call markPackageNameForLoadingIfRequired
                                    $this->markPackageNameForLoading($request, $replace->getTarget(), $replace->getConstraint());
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    private function markPackageNameForLoadingIfRequired(Request $request, string $name): void
    {
        if ($this->isRootRequire($request, $name)) {
            $this->markPackageNameForLoading($request, $name, $request->getRequires()[$name]);
        }

        foreach ($this->packages as $package) {
            foreach ($package->getRequires() as $link) {
                if ($name === $link->getTarget()) {
                    $this->markPackageNameForLoading($request, $link->getTarget(), $link->getConstraint());
                }
            }
        }
    }

    /**
     * @param RepositoryInterface[] $repositories
     */
    private function removeLoadedPackage(Request $request, array $repositories, BasePackage $package, int $index): void
    {
        $repoIndex = array_search($package->getRepository(), $repositories, true);

        unset($this->loadedPerRepo[$repoIndex][$package->getName()][$package->getVersion()]);
        unset($this->packages[$index]);
        if (isset($this->aliasMap[spl_object_hash($package)])) {
            foreach ($this->aliasMap[spl_object_hash($package)] as $aliasIndex => $aliasPackage) {
                unset($this->loadedPerRepo[$repoIndex][$aliasPackage->getName()][$aliasPackage->getVersion()]);
                unset($this->packages[$aliasIndex]);
            }
            unset($this->aliasMap[spl_object_hash($package)]);
        }
    }

    private function runOptimizer(Request $request, Pool $pool): Pool
    {
        if (null === $this->poolOptimizer) {
            return $pool;
        }

        $this->io->debug('Running pool optimizer.');

        $before = microtime(true);
        $total = \count($pool->getPackages());

        $pool = $this->poolOptimizer->optimize($request, $pool);

        $filtered = $total - \count($pool->getPackages());

        if (0 === $filtered) {
            return $pool;
        }

        $this->io->write(sprintf('Pool optimizer completed in %.3f seconds', microtime(true) - $before), true, IOInterface::VERY_VERBOSE);
        $this->io->write(sprintf(
            '<info>Found %s package versions referenced in your dependency graph. %s (%d%%) were optimized away.</info>',
            number_format($total),
            number_format($filtered),
            round(100 / $total * $filtered)
        ), true, IOInterface::VERY_VERBOSE);

        return $pool;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
class GenericRule extends Rule
{
    /** @var list<int> */
    protected $literals;

    /**
     * @param list<int> $literals
     */
    public function __construct(array $literals, $reason, $reasonData)
    {
        parent::__construct($reason, $reasonData);

        // sort all packages ascending by id
        sort($literals);

        $this->literals = $literals;
    }

    /**
     * @return list<int>
     */
    public function getLiterals(): array
    {
        return $this->literals;
    }

    /**
     * @inheritDoc
     */
    public function getHash()
    {
        $data = unpack('ihash', (string) hash(\PHP_VERSION_ID > 80100 ? 'xxh3' : 'sha1', implode(',', $this->literals), true));
        if (false === $data) {
            throw new \RuntimeException('Failed unpacking: '.implode(', ', $this->literals));
        }

        return $data['hash'];
    }

    /**
     * Checks if this rule is equal to another one
     *
     * Ignores whether either of the rules is disabled.
     *
     * @param  Rule $rule The rule to check against
     * @return bool Whether the rules are equal
     */
    public function equals(Rule $rule): bool
    {
        return $this->literals === $rule->getLiterals();
    }

    public function isAssertion(): bool
    {
        return 1 === \count($this->literals);
    }

    /**
     * Formats a rule as a string of the format (Literal1|Literal2|...)
     */
    public function __toString(): string
    {
        $result = $this->isDisabled() ? 'disabled(' : '(';

        foreach ($this->literals as $i => $literal) {
            if ($i !== 0) {
                $result .= '|';
            }
            $result .= $literal;
        }

        $result .= ')';

        return $result;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

/**
 * @author Nils Adermann <naderman@naderman.de>
 *
 * MultiConflictRule([A, B, C]) acts as Rule([-A, -B]), Rule([-A, -C]), Rule([-B, -C])
 */
class MultiConflictRule extends Rule
{
    /** @var non-empty-list<int> */
    protected $literals;

    /**
     * @param non-empty-list<int> $literals
     */
    public function __construct(array $literals, $reason, $reasonData)
    {
        parent::__construct($reason, $reasonData);

        if (\count($literals) < 3) {
            throw new \RuntimeException("multi conflict rule requires at least 3 literals");
        }

        // sort all packages ascending by id
        sort($literals);

        $this->literals = $literals;
    }

    /**
     * @return non-empty-list<int>
     */
    public function getLiterals(): array
    {
        return $this->literals;
    }

    /**
     * @inheritDoc
     */
    public function getHash()
    {
        $data = unpack('ihash', (string) hash(\PHP_VERSION_ID > 80100 ? 'xxh3' : 'sha1', 'c:'.implode(',', $this->literals), true));
        if (false === $data) {
            throw new \RuntimeException('Failed unpacking: '.implode(', ', $this->literals));
        }

        return $data['hash'];
    }

    /**
     * Checks if this rule is equal to another one
     *
     * Ignores whether either of the rules is disabled.
     *
     * @param  Rule $rule The rule to check against
     * @return bool Whether the rules are equal
     */
    public function equals(Rule $rule): bool
    {
        if ($rule instanceof MultiConflictRule) {
            return $this->literals === $rule->getLiterals();
        }

        return false;
    }

    public function isAssertion(): bool
    {
        return false;
    }

    /**
     * @return never
     * @throws \RuntimeException
     */
    public function disable(): void
    {
        throw new \RuntimeException("Disabling multi conflict rules is not possible. Please contact composer at https://github.com/composer/composer to let us debug what lead to this situation.");
    }

    /**
     * Formats a rule as a string of the format (Literal1|Literal2|...)
     */
    public function __toString(): string
    {
        // TODO multi conflict?
        $result = $this->isDisabled() ? 'disabled(multi(' : '(multi(';

        foreach ($this->literals as $i => $literal) {
            if ($i !== 0) {
                $result .= '|';
            }
            $result .= $literal;
        }

        $result .= '))';

        return $result;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Package\AliasPackage;
use Composer\Package\Link;
use Composer\Package\PackageInterface;
use Composer\Repository\PlatformRepository;
use Composer\DependencyResolver\Operation\OperationInterface;

/**
 * @author Nils Adermann <naderman@naderman.de>
 * @internal
 */
class Transaction
{
    /**
     * @var OperationInterface[]
     */
    protected $operations;

    /**
     * Packages present at the beginning of the transaction
     * @var PackageInterface[]
     */
    protected $presentPackages;

    /**
     * Package set resulting from this transaction
     * @var array<string, PackageInterface>
     */
    protected $resultPackageMap;

    /**
     * @var array<string, PackageInterface[]>
     */
    protected $resultPackagesByName = [];

    /**
     * @param PackageInterface[] $presentPackages
     * @param PackageInterface[] $resultPackages
     */
    public function __construct(array $presentPackages, array $resultPackages)
    {
        $this->presentPackages = $presentPackages;
        $this->setResultPackageMaps($resultPackages);
        $this->operations = $this->calculateOperations();
    }

    /**
     * @return OperationInterface[]
     */
    public function getOperations(): array
    {
        return $this->operations;
    }

    /**
     * @param PackageInterface[] $resultPackages
     */
    private function setResultPackageMaps(array $resultPackages): void
    {
        $packageSort = static function (PackageInterface $a, PackageInterface $b): int {
            // sort alias packages by the same name behind their non alias version
            if ($a->getName() === $b->getName()) {
                if ($a instanceof AliasPackage !== $b instanceof AliasPackage) {
                    return $a instanceof AliasPackage ? -1 : 1;
                }
                // if names are the same, compare version, e.g. to sort aliases reliably, actual order does not matter
                return strcmp($b->getVersion(), $a->getVersion());
            }

            return strcmp($b->getName(), $a->getName());
        };

        $this->resultPackageMap = [];
        foreach ($resultPackages as $package) {
            $this->resultPackageMap[spl_object_hash($package)] = $package;
            foreach ($package->getNames() as $name) {
                $this->resultPackagesByName[$name][] = $package;
            }
        }

        uasort($this->resultPackageMap, $packageSort);
        foreach ($this->resultPackagesByName as $name => $packages) {
            uasort($this->resultPackagesByName[$name], $packageSort);
        }
    }

    /**
     * @return OperationInterface[]
     */
    protected function calculateOperations(): array
    {
        $operations = [];

        $presentPackageMap = [];
        $removeMap = [];
        $presentAliasMap = [];
        $removeAliasMap = [];
        foreach ($this->presentPackages as $package) {
            if ($package instanceof AliasPackage) {
                $presentAliasMap[$package->getName().'::'.$package->getVersion()] = $package;
                $removeAliasMap[$package->getName().'::'.$package->getVersion()] = $package;
            } else {
                $presentPackageMap[$package->getName()] = $package;
                $removeMap[$package->getName()] = $package;
            }
        }

        $stack = $this->getRootPackages();

        $visited = [];
        $processed = [];

        while (\count($stack) > 0) {
            $package = array_pop($stack);

            if (isset($processed[spl_object_hash($package)])) {
                continue;
            }

            if (!isset($visited[spl_object_hash($package)])) {
                $visited[spl_object_hash($package)] = true;

                $stack[] = $package;
                if ($package instanceof AliasPackage) {
                    $stack[] = $package->getAliasOf();
                } else {
                    foreach ($package->getRequires() as $link) {
                        $possibleRequires = $this->getProvidersInResult($link);

                        foreach ($possibleRequires as $require) {
                            $stack[] = $require;
                        }
                    }
                }
            } elseif (!isset($processed[spl_object_hash($package)])) {
                $processed[spl_object_hash($package)] = true;

                if ($package instanceof AliasPackage) {
                    $aliasKey = $package->getName().'::'.$package->getVersion();
                    if (isset($presentAliasMap[$aliasKey])) {
                        unset($removeAliasMap[$aliasKey]);
                    } else {
                        $operations[] = new Operation\MarkAliasInstalledOperation($package);
                    }
                } else {
                    if (isset($presentPackageMap[$package->getName()])) {
                        $source = $presentPackageMap[$package->getName()];

                        // do we need to update?
                        // TODO different for lock?
                        if ($package->getVersion() !== $presentPackageMap[$package->getName()]->getVersion() ||
                            $package->getDistReference() !== $presentPackageMap[$package->getName()]->getDistReference() ||
                            $package->getSourceReference() !== $presentPackageMap[$package->getName()]->getSourceReference()
                        ) {
                            $operations[] = new Operation\UpdateOperation($source, $package);
                        }
                        unset($removeMap[$package->getName()]);
                    } else {
                        $operations[] = new Operation\InstallOperation($package);
                        unset($removeMap[$package->getName()]);
                    }
                }
            }
        }

        foreach ($removeMap as $name => $package) {
            array_unshift($operations, new Operation\UninstallOperation($package));
        }
        foreach ($removeAliasMap as $nameVersion => $package) {
            $operations[] = new Operation\MarkAliasUninstalledOperation($package);
        }

        $operations = $this->movePluginsToFront($operations);
        // TODO fix this:
        // we have to do this again here even though the above stack code did it because moving plugins moves them before uninstalls
        $operations = $this->moveUninstallsToFront($operations);

        // TODO skip updates which don't update? is this needed? we shouldn't schedule this update in the first place?
        /*
        if ('update' === $opType) {
            $targetPackage = $operation->getTargetPackage();
            if ($targetPackage->isDev()) {
                $initialPackage = $operation->getInitialPackage();
                if ($targetPackage->getVersion() === $initialPackage->getVersion()
                    && (!$targetPackage->getSourceReference() || $targetPackage->getSourceReference() === $initialPackage->getSourceReference())
                    && (!$targetPackage->getDistReference() || $targetPackage->getDistReference() === $initialPackage->getDistReference())
                ) {
                    $this->io->writeError('  - Skipping update of ' . $targetPackage->getPrettyName() . ' to the same reference-locked version', true, IOInterface::DEBUG);
                    $this->io->writeError('', true, IOInterface::DEBUG);

                    continue;
                }
            }
        }*/

        return $this->operations = $operations;
    }

    /**
     * Determine which packages in the result are not required by any other packages in it.
     *
     * These serve as a starting point to enumerate packages in a topological order despite potential cycles.
     * If there are packages with a cycle on the top level the package with the lowest name gets picked
     *
     * @return array<string, PackageInterface>
     */
    protected function getRootPackages(): array
    {
        $roots = $this->resultPackageMap;

        foreach ($this->resultPackageMap as $packageHash => $package) {
            if (!isset($roots[$packageHash])) {
                continue;
            }

            foreach ($package->getRequires() as $link) {
                $possibleRequires = $this->getProvidersInResult($link);

                foreach ($possibleRequires as $require) {
                    if ($require !== $package) {
                        unset($roots[spl_object_hash($require)]);
                    }
                }
            }
        }

        return $roots;
    }

    /**
     * @return PackageInterface[]
     */
    protected function getProvidersInResult(Link $link): array
    {
        if (!isset($this->resultPackagesByName[$link->getTarget()])) {
            return [];
        }

        return $this->resultPackagesByName[$link->getTarget()];
    }

    /**
     * Workaround: if your packages depend on plugins, we must be sure
     * that those are installed / updated first; else it would lead to packages
     * being installed multiple times in different folders, when running Composer
     * twice.
     *
     * While this does not fix the root-causes of https://github.com/composer/composer/issues/1147,
     * it at least fixes the symptoms and makes usage of composer possible (again)
     * in such scenarios.
     *
     * @param  OperationInterface[] $operations
     * @return OperationInterface[] reordered operation list
     */
    private function movePluginsToFront(array $operations): array
    {
        $dlModifyingPluginsNoDeps = [];
        $dlModifyingPluginsWithDeps = [];
        $dlModifyingPluginRequires = [];
        $pluginsNoDeps = [];
        $pluginsWithDeps = [];
        $pluginRequires = [];

        foreach (array_reverse($operations, true) as $idx => $op) {
            if ($op instanceof Operation\InstallOperation) {
                $package = $op->getPackage();
            } elseif ($op instanceof Operation\UpdateOperation) {
                $package = $op->getTargetPackage();
            } else {
                continue;
            }

            $extra = $package->getExtra();
            $isDownloadsModifyingPlugin = $package->getType() === 'composer-plugin' && isset($extra['plugin-modifies-downloads']) && $extra['plugin-modifies-downloads'] === true;

            // is this a downloads modifying plugin or a dependency of one?
            if ($isDownloadsModifyingPlugin || \count(array_intersect($package->getNames(), $dlModifyingPluginRequires)) > 0) {
                // get the package's requires, but filter out any platform requirements
                $requires = array_filter(array_keys($package->getRequires()), static function ($req): bool {
                    return !PlatformRepository::isPlatformPackage($req);
                });

                // is this a plugin with no meaningful dependencies?
                if ($isDownloadsModifyingPlugin && 0 === \count($requires)) {
                    // plugins with no dependencies go to the very front
                    array_unshift($dlModifyingPluginsNoDeps, $op);
                } else {
                    // capture the requirements for this package so those packages will be moved up as well
                    $dlModifyingPluginRequires = array_merge($dlModifyingPluginRequires, $requires);
                    // move the operation to the front
                    array_unshift($dlModifyingPluginsWithDeps, $op);
                }

                unset($operations[$idx]);
                continue;
            }

            // is this package a plugin?
            $isPlugin = $package->getType() === 'composer-plugin' || $package->getType() === 'composer-installer';

            // is this a plugin or a dependency of a plugin?
            if ($isPlugin || \count(array_intersect($package->getNames(), $pluginRequires)) > 0) {
                // get the package's requires, but filter out any platform requirements
                $requires = array_filter(array_keys($package->getRequires()), static function ($req): bool {
                    return !PlatformRepository::isPlatformPackage($req);
                });

                // is this a plugin with no meaningful dependencies?
                if ($isPlugin && 0 === \count($requires)) {
                    // plugins with no dependencies go to the very front
                    array_unshift($pluginsNoDeps, $op);
                } else {
                    // capture the requirements for this package so those packages will be moved up as well
                    $pluginRequires = array_merge($pluginRequires, $requires);
                    // move the operation to the front
                    array_unshift($pluginsWithDeps, $op);
                }

                unset($operations[$idx]);
            }
        }

        return array_merge($dlModifyingPluginsNoDeps, $dlModifyingPluginsWithDeps, $pluginsNoDeps, $pluginsWithDeps, $operations);
    }

    /**
     * Removals of packages should be executed before installations in
     * case two packages resolve to the same path (due to custom installers)
     *
     * @param  OperationInterface[] $operations
     * @return OperationInterface[] reordered operation list
     */
    private function moveUninstallsToFront(array $operations): array
    {
        $uninstOps = [];
        foreach ($operations as $idx => $op) {
            if ($op instanceof Operation\UninstallOperation || $op instanceof Operation\MarkAliasUninstalledOperation) {
                $uninstOps[] = $op;
                unset($operations[$idx]);
            }
        }

        return array_merge($uninstOps, $operations);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Package\AliasPackage;
use Composer\Package\BasePackage;
use Composer\Package\PackageInterface;
use Composer\Semver\CompilingMatcher;
use Composer\Semver\Constraint\Constraint;

/**
 * @author Nils Adermann <naderman@naderman.de>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class DefaultPolicy implements PolicyInterface
{
    /** @var bool */
    private $preferStable;
    /** @var bool */
    private $preferLowest;
    /** @var array<string, string>|null */
    private $preferredVersions;
    /** @var array<int, array<string, non-empty-list<int>>> */
    private $preferredPackageResultCachePerPool;
    /** @var array<int, array<string, int>> */
    private $sortingCachePerPool;

    /**
     * @param array<string, string>|null $preferredVersions Must be an array of package name => normalized version
     */
    public function __construct(bool $preferStable = false, bool $preferLowest = false, ?array $preferredVersions = null)
    {
        $this->preferStable = $preferStable;
        $this->preferLowest = $preferLowest;
        $this->preferredVersions = $preferredVersions;
    }

    /**
     * @param string $operator One of Constraint::STR_OP_*
     *
     * @phpstan-param Constraint::STR_OP_* $operator
     */
    public function versionCompare(PackageInterface $a, PackageInterface $b, string $operator): bool
    {
        if ($this->preferStable && ($stabA = $a->getStability()) !== ($stabB = $b->getStability())) {
            return BasePackage::STABILITIES[$stabA] < BasePackage::STABILITIES[$stabB];
        }

        // dev versions need to be compared as branches via matchSpecific's special treatment, the rest can be optimized with compiling matcher
        if (($a->isDev() && str_starts_with($a->getVersion(), 'dev-')) || ($b->isDev() && str_starts_with($b->getVersion(), 'dev-'))) {
            $constraint = new Constraint($operator, $b->getVersion());
            $version = new Constraint('==', $a->getVersion());

            return $constraint->matchSpecific($version, true);
        }

        return CompilingMatcher::match(new Constraint($operator, $b->getVersion()), Constraint::OP_EQ, $a->getVersion());
    }

    /**
     * @param  non-empty-list<int>  $literals
     * @return non-empty-list<int>
     */
    public function selectPreferredPackages(Pool $pool, array $literals, ?string $requiredPackage = null): array
    {
        sort($literals);
        $resultCacheKey = implode(',', $literals).$requiredPackage;
        $poolId = spl_object_id($pool);

        if (isset($this->preferredPackageResultCachePerPool[$poolId][$resultCacheKey])) {
            return $this->preferredPackageResultCachePerPool[$poolId][$resultCacheKey];
        }

        $packages = $this->groupLiteralsByName($pool, $literals);

        foreach ($packages as &$nameLiterals) {
            usort($nameLiterals, function ($a, $b) use ($pool, $requiredPackage, $poolId): int {
                $cacheKey = 'i'.$a.'.'.$b.$requiredPackage; // i prefix -> ignoreReplace = true

                if (isset($this->sortingCachePerPool[$poolId][$cacheKey])) {
                    return $this->sortingCachePerPool[$poolId][$cacheKey];
                }

                return $this->sortingCachePerPool[$poolId][$cacheKey] = $this->compareByPriority($pool, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage, true);
            });
        }

        foreach ($packages as &$sortedLiterals) {
            $sortedLiterals = $this->pruneToBestVersion($pool, $sortedLiterals);
            $sortedLiterals = $this->pruneRemoteAliases($pool, $sortedLiterals);
        }

        $selected = array_merge(...array_values($packages));

        // now sort the result across all packages to respect replaces across packages
        usort($selected, function ($a, $b) use ($pool, $requiredPackage, $poolId): int {
            $cacheKey = $a.'.'.$b.$requiredPackage; // no i prefix -> ignoreReplace = false

            if (isset($this->sortingCachePerPool[$poolId][$cacheKey])) {
                return $this->sortingCachePerPool[$poolId][$cacheKey];
            }

            return $this->sortingCachePerPool[$poolId][$cacheKey] = $this->compareByPriority($pool, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage);
        });

        return $this->preferredPackageResultCachePerPool[$poolId][$resultCacheKey] = $selected;
    }

    /**
     * @param  non-empty-list<int> $literals
     * @return non-empty-array<string, non-empty-list<int>>
     */
    protected function groupLiteralsByName(Pool $pool, array $literals): array
    {
        $packages = [];
        foreach ($literals as $literal) {
            $packageName = $pool->literalToPackage($literal)->getName();

            if (!isset($packages[$packageName])) {
                $packages[$packageName] = [];
            }
            $packages[$packageName][] = $literal;
        }

        return $packages;
    }

    /**
     * @protected
     */
    public function compareByPriority(Pool $pool, BasePackage $a, BasePackage $b, ?string $requiredPackage = null, bool $ignoreReplace = false): int
    {
        // prefer aliases to the original package
        if ($a->getName() === $b->getName()) {
            $aAliased = $a instanceof AliasPackage;
            $bAliased = $b instanceof AliasPackage;
            if ($aAliased && !$bAliased) {
                return -1; // use a
            }
            if (!$aAliased && $bAliased) {
                return 1; // use b
            }
        }

        if (!$ignoreReplace) {
            // return original, not replaced
            if ($this->replaces($a, $b)) {
                return 1; // use b
            }
            if ($this->replaces($b, $a)) {
                return -1; // use a
            }

            // for replacers not replacing each other, put a higher prio on replacing
            // packages with the same vendor as the required package
            if ($requiredPackage !== null && false !== ($pos = strpos($requiredPackage, '/'))) {
                $requiredVendor = substr($requiredPackage, 0, $pos);

                $aIsSameVendor = strpos($a->getName(), $requiredVendor) === 0;
                $bIsSameVendor = strpos($b->getName(), $requiredVendor) === 0;

                if ($bIsSameVendor !== $aIsSameVendor) {
                    return $aIsSameVendor ? -1 : 1;
                }
            }
        }

        // priority equal, sort by package id to make reproducible
        if ($a->id === $b->id) {
            return 0;
        }

        return ($a->id < $b->id) ? -1 : 1;
    }

    /**
     * Checks if source replaces a package with the same name as target.
     *
     * Replace constraints are ignored. This method should only be used for
     * prioritisation, not for actual constraint verification.
     */
    protected function replaces(BasePackage $source, BasePackage $target): bool
    {
        foreach ($source->getReplaces() as $link) {
            if ($link->getTarget() === $target->getName()
//                && (null === $link->getConstraint() ||
//                $link->getConstraint()->matches(new Constraint('==', $target->getVersion())))) {
            ) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param  list<int> $literals
     * @return list<int>
     */
    protected function pruneToBestVersion(Pool $pool, array $literals): array
    {
        if ($this->preferredVersions !== null) {
            $name = $pool->literalToPackage($literals[0])->getName();
            if (isset($this->preferredVersions[$name])) {
                $preferredVersion = $this->preferredVersions[$name];
                $bestLiterals = [];
                foreach ($literals as $literal) {
                    if ($pool->literalToPackage($literal)->getVersion() === $preferredVersion) {
                        $bestLiterals[] = $literal;
                    }
                }
                if (\count($bestLiterals) > 0) {
                    return $bestLiterals;
                }
            }
        }

        $operator = $this->preferLowest ? '<' : '>';
        $bestLiterals = [$literals[0]];
        $bestPackage = $pool->literalToPackage($literals[0]);
        foreach ($literals as $i => $literal) {
            if (0 === $i) {
                continue;
            }

            $package = $pool->literalToPackage($literal);

            if ($this->versionCompare($package, $bestPackage, $operator)) {
                $bestPackage = $package;
                $bestLiterals = [$literal];
            } elseif ($this->versionCompare($package, $bestPackage, '==')) {
                $bestLiterals[] = $literal;
            }
        }

        return $bestLiterals;
    }

    /**
     * Assumes that locally aliased (in root package requires) packages take priority over branch-alias ones
     *
     * If no package is a local alias, nothing happens
     *
     * @param  list<int> $literals
     * @return list<int>
     */
    protected function pruneRemoteAliases(Pool $pool, array $literals): array
    {
        $hasLocalAlias = false;

        foreach ($literals as $literal) {
            $package = $pool->literalToPackage($literal);

            if ($package instanceof AliasPackage && $package->isRootPackageAlias()) {
                $hasLocalAlias = true;
                break;
            }
        }

        if (!$hasLocalAlias) {
            return $literals;
        }

        $selected = [];
        foreach ($literals as $literal) {
            $package = $pool->literalToPackage($literal);

            if ($package instanceof AliasPackage && $package->isRootPackageAlias()) {
                $selected[] = $literal;
            }
        }

        return $selected;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\PHPStan;

use Composer\Config;
use Composer\Json\JsonFile;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\ArrayType;
use PHPStan\Type\BooleanType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\IntegerRangeType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\MixedType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeUtils;
use PHPStan\Type\UnionType;

final class ConfigReturnTypeExtension implements DynamicMethodReturnTypeExtension
{
    /** @var array<string, \PHPStan\Type\Type> */
    private $properties = [];

    public function __construct()
    {
        $schema = JsonFile::parseJson((string) file_get_contents(JsonFile::COMPOSER_SCHEMA_PATH));
        /**
         * @var string $prop
         */
        foreach ($schema['properties']['config']['properties'] as $prop => $conf) {
            $type = $this->parseType($conf, $prop);

            $this->properties[$prop] = $type;
        }
    }

    public function getClass(): string
    {
        return Config::class;
    }

    public function isMethodSupported(MethodReflection $methodReflection): bool
    {
        return strtolower($methodReflection->getName()) === 'get';
    }

    public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type
    {
        $args = $methodCall->getArgs();

        if (count($args) < 1) {
            return null;
        }

        $keyType = $scope->getType($args[0]->value);
        if (method_exists($keyType, 'getConstantStrings')) { // @phpstan-ignore function.alreadyNarrowedType (- depending on PHPStan version, this method will always exist, or not.)
            $strings = $keyType->getConstantStrings();
        } else {
            // for compat with old phpstan versions, we use a deprecated phpstan method.
            $strings = TypeUtils::getConstantStrings($keyType); // @phpstan-ignore staticMethod.deprecated (ignore deprecation)
        }
        if ($strings !== []) {
            $types = [];
            foreach($strings as $string) {
                if (!isset($this->properties[$string->getValue()])) {
                    return null;
                }
                $types[] = $this->properties[$string->getValue()];
            }

            return TypeCombinator::union(...$types);
        }

        return null;
    }

    /**
     * @param array<mixed> $def
     */
    private function parseType(array $def, string $path): Type
    {
        if (isset($def['type'])) {
            $types = [];
            foreach ((array) $def['type'] as $type) {
                switch ($type) {
                    case 'integer':
                        if (in_array($path, ['process-timeout', 'cache-ttl', 'cache-files-ttl', 'cache-files-maxsize'], true)) {
                            $types[] = IntegerRangeType::createAllGreaterThanOrEqualTo(0);
                        } else {
                            $types[] = new IntegerType();
                        }
                        break;

                    case 'string':
                        if ($path === 'cache-files-maxsize') {
                            // passthru, skip as it is always converted to int
                        } elseif ($path === 'discard-changes') {
                            $types[] = new ConstantStringType('stash');
                        } elseif ($path === 'use-parent-dir') {
                            $types[] = new ConstantStringType('prompt');
                        } elseif ($path === 'store-auths') {
                            $types[] = new ConstantStringType('prompt');
                        } elseif ($path === 'platform-check') {
                            $types[] = new ConstantStringType('php-only');
                        } elseif ($path === 'github-protocols') {
                            $types[] = new UnionType([new ConstantStringType('git'), new ConstantStringType('https'), new ConstantStringType('ssh'), new ConstantStringType('http')]);
                        } elseif (str_starts_with($path, 'preferred-install')) {
                            $types[] = new UnionType([new ConstantStringType('source'), new ConstantStringType('dist'), new ConstantStringType('auto')]);
                        } else {
                            $types[] = new StringType();
                        }
                        break;

                    case 'boolean':
                        if ($path === 'platform.additionalProperties') {
                            $types[] = new ConstantBooleanType(false);
                        } else {
                            $types[] = new BooleanType();
                        }
                        break;

                    case 'object':
                        $addlPropType = null;
                        if (isset($def['additionalProperties'])) {
                            $addlPropType = $this->parseType($def['additionalProperties'], $path.'.additionalProperties');
                        }

                        if (isset($def['properties'])) {
                            $keyNames = [];
                            $valTypes = [];
                            $optionalKeys = [];
                            $propIndex = 0;
                            foreach ($def['properties'] as $propName => $propdef) {
                                $keyNames[] = new ConstantStringType($propName);
                                $valType = $this->parseType($propdef, $path.'.'.$propName);
                                if (!isset($def['required']) || !in_array($propName, $def['required'], true)) {
                                    $valType = TypeCombinator::addNull($valType);
                                    $optionalKeys[] = $propIndex;
                                }
                                $valTypes[] = $valType;
                                $propIndex++;
                            }

                            if ($addlPropType !== null) {
                                $types[] = new ArrayType(TypeCombinator::union(new StringType(), ...$keyNames), TypeCombinator::union($addlPropType, ...$valTypes));
                            } else {
                                $types[] = new ConstantArrayType($keyNames, $valTypes, [0], $optionalKeys);
                            }
                        } else {
                            $types[] = new ArrayType(new StringType(), $addlPropType ?? new MixedType());
                        }
                        break;

                    case 'array':
                        if (isset($def['items'])) {
                            $valType = $this->parseType($def['items'], $path.'.items');
                        } else {
                            $valType = new MixedType();
                        }

                        $types[] = new ArrayType(new IntegerType(), $valType);
                        break;

                    default:
                        $types[] = new MixedType();
                }
            }

            $type = TypeCombinator::union(...$types);
        } elseif (isset($def['enum'])) {
            $type = TypeCombinator::union(...array_map(static function (string $value): ConstantStringType {
                return new ConstantStringType($value);
            }, $def['enum']));
        } else {
            $type = new MixedType();
        }

        // allow-plugins defaults to null until July 1st 2022 for some BC hackery, but after that it is not nullable anymore
        if ($path === 'allow-plugins' && time() < strtotime('2022-07-01')) {
            $type = TypeCombinator::addNull($type);
        }

        // default null props
        if (in_array($path, ['autoloader-suffix', 'gitlab-protocol'], true)) {
            $type = TypeCombinator::addNull($type);
        }

        return $type;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\PHPStan;

use Composer\DependencyResolver\Rule;
use Composer\Package\BasePackage;
use Composer\Package\Link;
use Composer\Semver\Constraint\ConstraintInterface;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\Accessory\AccessoryNonEmptyStringType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantStringType;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\IntegerType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\ObjectType;
use PHPStan\Type\TypeCombinator;
use PhpParser\Node\Identifier;

final class RuleReasonDataReturnTypeExtension implements DynamicMethodReturnTypeExtension
{
    public function getClass(): string
    {
        return Rule::class;
    }

    public function isMethodSupported(MethodReflection $methodReflection): bool
    {
        return strtolower($methodReflection->getName()) === 'getreasondata';
    }

    public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
    {
        $reasonType = $scope->getType(new MethodCall($methodCall->var, new Identifier('getReason')));

        $types = [
            Rule::RULE_ROOT_REQUIRE => new ConstantArrayType([new ConstantStringType('packageName'), new ConstantStringType('constraint')], [new StringType, new ObjectType(ConstraintInterface::class)]),
            Rule::RULE_FIXED => new ConstantArrayType([new ConstantStringType('package')], [new ObjectType(BasePackage::class)]),
            Rule::RULE_PACKAGE_CONFLICT => new ObjectType(Link::class),
            Rule::RULE_PACKAGE_REQUIRES => new ObjectType(Link::class),
            Rule::RULE_PACKAGE_SAME_NAME => TypeCombinator::intersect(new StringType, new AccessoryNonEmptyStringType()),
            Rule::RULE_LEARNED => new IntegerType(),
            Rule::RULE_PACKAGE_ALIAS => new ObjectType(BasePackage::class),
            Rule::RULE_PACKAGE_INVERSE_ALIAS => new ObjectType(BasePackage::class),
        ];

        foreach ($types as $const => $type) {
            if ((new ConstantIntegerType($const))->isSuperTypeOf($reasonType)->yes()) {
                return $type;
            }
        }

        return TypeCombinator::union(...$types);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Autoload;

use Composer\ClassMapGenerator\ClassMap;
use Composer\ClassMapGenerator\ClassMapGenerator;
use Composer\Config;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Filter\PlatformRequirementFilter\IgnoreAllPlatformRequirementFilter;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
use Composer\Installer\InstallationManager;
use Composer\IO\IOInterface;
use Composer\IO\NullIO;
use Composer\Package\AliasPackage;
use Composer\Package\PackageInterface;
use Composer\Package\RootPackageInterface;
use Composer\Pcre\Preg;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Semver\Constraint\Bound;
use Composer\Util\Filesystem;
use Composer\Util\Platform;
use Composer\Script\ScriptEvents;
use Composer\Util\PackageSorter;
use Composer\Json\JsonFile;
use Composer\Package\Locker;
use Symfony\Component\Console\Formatter\OutputFormatter;

/**
 * @author Igor Wiedler <igor@wiedler.ch>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class AutoloadGenerator
{
    /**
     * @var EventDispatcher
     */
    private $eventDispatcher;

    /**
     * @var IOInterface
     */
    private $io;

    /**
     * @var ?bool
     */
    private $devMode = null;

    /**
     * @var bool
     */
    private $classMapAuthoritative = false;

    /**
     * @var bool
     */
    private $apcu = false;

    /**
     * @var string|null
     */
    private $apcuPrefix;

    /**
     * @var bool
     */
    private $dryRun = false;

    /**
     * @var bool
     */
    private $runScripts = false;

    /**
     * @var PlatformRequirementFilterInterface
     */
    private $platformRequirementFilter;

    public function __construct(EventDispatcher $eventDispatcher, ?IOInterface $io = null)
    {
        $this->eventDispatcher = $eventDispatcher;
        $this->io = $io ?? new NullIO();

        $this->platformRequirementFilter = PlatformRequirementFilterFactory::ignoreNothing();
    }

    /**
     * @return void
     */
    public function setDevMode(bool $devMode = true)
    {
        $this->devMode = $devMode;
    }

    /**
     * Whether generated autoloader considers the class map authoritative.
     *
     * @return void
     */
    public function setClassMapAuthoritative(bool $classMapAuthoritative)
    {
        $this->classMapAuthoritative = $classMapAuthoritative;
    }

    /**
     * Whether generated autoloader considers APCu caching.
     *
     * @return void
     */
    public function setApcu(bool $apcu, ?string $apcuPrefix = null)
    {
        $this->apcu = $apcu;
        $this->apcuPrefix = $apcuPrefix;
    }

    /**
     * Whether to run scripts or not
     *
     * @return void
     */
    public function setRunScripts(bool $runScripts = true)
    {
        $this->runScripts = $runScripts;
    }

    /**
     * Whether to run in drymode or not
     */
    public function setDryRun(bool $dryRun = true): void
    {
        $this->dryRun = $dryRun;
    }

    /**
     * Whether platform requirements should be ignored.
     *
     * If this is set to true, the platform check file will not be generated
     * If this is set to false, the platform check file will be generated with all requirements
     * If this is set to string[], those packages will be ignored from the platform check file
     *
     * @param bool|string[] $ignorePlatformReqs
     * @return void
     *
     * @deprecated use setPlatformRequirementFilter instead
     */
    public function setIgnorePlatformRequirements($ignorePlatformReqs)
    {
        trigger_error('AutoloadGenerator::setIgnorePlatformRequirements is deprecated since Composer 2.2, use setPlatformRequirementFilter instead.', E_USER_DEPRECATED);

        $this->setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs));
    }

    /**
     * @return void
     */
    public function setPlatformRequirementFilter(PlatformRequirementFilterInterface $platformRequirementFilter)
    {
        $this->platformRequirementFilter = $platformRequirementFilter;
    }

    /**
     * @return ClassMap
     * @throws \Seld\JsonLint\ParsingException
     * @throws \RuntimeException
     */
    public function dump(Config $config, InstalledRepositoryInterface $localRepo, RootPackageInterface $rootPackage, InstallationManager $installationManager, string $targetDir, bool $scanPsrPackages = false, ?string $suffix = null, ?Locker $locker = null, bool $strictAmbiguous = false)
    {
        if ($this->classMapAuthoritative) {
            // Force scanPsrPackages when classmap is authoritative
            $scanPsrPackages = true;
        }

        // auto-set devMode based on whether dev dependencies are installed or not
        if (null === $this->devMode) {
            // we assume no-dev mode if no vendor dir is present or it is too old to contain dev information
            $this->devMode = false;

            $installedJson = new JsonFile($config->get('vendor-dir').'/composer/installed.json');
            if ($installedJson->exists()) {
                $installedJson = $installedJson->read();
                if (isset($installedJson['dev'])) {
                    $this->devMode = $installedJson['dev'];
                }
            }
        }

        if ($this->runScripts) {
            // set COMPOSER_DEV_MODE in case not set yet so it is available in the dump-autoload event listeners
            if (!isset($_SERVER['COMPOSER_DEV_MODE'])) {
                Platform::putEnv('COMPOSER_DEV_MODE', $this->devMode ? '1' : '0');
            }

            $this->eventDispatcher->dispatchScript(ScriptEvents::PRE_AUTOLOAD_DUMP, $this->devMode, [], [
                'optimize' => $scanPsrPackages,
            ]);
        }

        $classMapGenerator = new ClassMapGenerator(['php', 'inc', 'hh']);
        $classMapGenerator->avoidDuplicateScans();

        $filesystem = new Filesystem();
        $filesystem->ensureDirectoryExists($config->get('vendor-dir'));
        // Do not remove double realpath() calls.
        // Fixes failing Windows realpath() implementation.
        // See https://bugs.php.net/bug.php?id=72738
        $basePath = $filesystem->normalizePath(realpath(realpath(Platform::getCwd())));
        $vendorPath = $filesystem->normalizePath(realpath(realpath($config->get('vendor-dir'))));
        $useGlobalIncludePath = $config->get('use-include-path');
        $prependAutoloader = $config->get('prepend-autoloader') === false ? 'false' : 'true';
        $targetDir = $vendorPath.'/'.$targetDir;
        $filesystem->ensureDirectoryExists($targetDir);

        $vendorPathCode = $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true);
        $vendorPathToTargetDirCode = $filesystem->findShortestPathCode($vendorPath, realpath($targetDir), true);

        $appBaseDirCode = $filesystem->findShortestPathCode($vendorPath, $basePath, true);
        $appBaseDirCode = str_replace('__DIR__', '$vendorDir', $appBaseDirCode);

        $namespacesFile = <<<EOF
<?php

// autoload_namespaces.php @generated by Composer

\$vendorDir = $vendorPathCode;
\$baseDir = $appBaseDirCode;

return array(

EOF;

        $psr4File = <<<EOF
<?php

// autoload_psr4.php @generated by Composer

\$vendorDir = $vendorPathCode;
\$baseDir = $appBaseDirCode;

return array(

EOF;

        // Collect information from all packages.
        $devPackageNames = $localRepo->getDevPackageNames();
        $packageMap = $this->buildPackageMap($installationManager, $rootPackage, $localRepo->getCanonicalPackages());
        if ($this->devMode) {
            // if dev mode is enabled, then we do not filter any dev packages out so disable this entirely
            $filteredDevPackages = false;
        } else {
            // if the list of dev package names is available we use that straight, otherwise pass true which means use legacy algo to figure them out
            $filteredDevPackages = $devPackageNames ?: true;
        }
        $autoloads = $this->parseAutoloads($packageMap, $rootPackage, $filteredDevPackages);

        // Process the 'psr-0' base directories.
        foreach ($autoloads['psr-0'] as $namespace => $paths) {
            $exportedPaths = [];
            foreach ($paths as $path) {
                $exportedPaths[] = $this->getPathCode($filesystem, $basePath, $vendorPath, $path);
            }
            $exportedPrefix = var_export($namespace, true);
            $namespacesFile .= "    $exportedPrefix => ";
            $namespacesFile .= "array(".implode(', ', $exportedPaths)."),\n";
        }
        $namespacesFile .= ");\n";

        // Process the 'psr-4' base directories.
        foreach ($autoloads['psr-4'] as $namespace => $paths) {
            $exportedPaths = [];
            foreach ($paths as $path) {
                $exportedPaths[] = $this->getPathCode($filesystem, $basePath, $vendorPath, $path);
            }
            $exportedPrefix = var_export($namespace, true);
            $psr4File .= "    $exportedPrefix => ";
            $psr4File .= "array(".implode(', ', $exportedPaths)."),\n";
        }
        $psr4File .= ");\n";

        // add custom psr-0 autoloading if the root package has a target dir
        $targetDirLoader = null;
        $mainAutoload = $rootPackage->getAutoload();
        if ($rootPackage->getTargetDir() && !empty($mainAutoload['psr-0'])) {
            $levels = substr_count($filesystem->normalizePath($rootPackage->getTargetDir()), '/') + 1;
            $prefixes = implode(', ', array_map(static function ($prefix): string {
                return var_export($prefix, true);
            }, array_keys($mainAutoload['psr-0'])));
            $baseDirFromTargetDirCode = $filesystem->findShortestPathCode($targetDir, $basePath, true);

            $targetDirLoader = <<<EOF

    public static function autoload(\$class)
    {
        \$dir = $baseDirFromTargetDirCode . '/';
        \$prefixes = array($prefixes);
        foreach (\$prefixes as \$prefix) {
            if (0 !== strpos(\$class, \$prefix)) {
                continue;
            }
            \$path = \$dir . implode('/', array_slice(explode('\\\\', \$class), $levels)).'.php';
            if (!\$path = stream_resolve_include_path(\$path)) {
                return false;
            }
            require \$path;

            return true;
        }
    }

EOF;
        }

        $excluded = [];
        if (!empty($autoloads['exclude-from-classmap'])) {
            $excluded = $autoloads['exclude-from-classmap'];
        }

        foreach ($autoloads['classmap'] as $dir) {
            $classMapGenerator->scanPaths($dir, $this->buildExclusionRegex($dir, $excluded));
        }

        if ($scanPsrPackages) {
            $namespacesToScan = [];

            // Scan the PSR-0/4 directories for class files, and add them to the class map
            foreach (['psr-4', 'psr-0'] as $psrType) {
                foreach ($autoloads[$psrType] as $namespace => $paths) {
                    $namespacesToScan[$namespace][] = ['paths' => $paths, 'type' => $psrType];
                }
            }

            krsort($namespacesToScan);

            foreach ($namespacesToScan as $namespace => $groups) {
                foreach ($groups as $group) {
                    foreach ($group['paths'] as $dir) {
                        $dir = $filesystem->normalizePath($filesystem->isAbsolutePath($dir) ? $dir : $basePath.'/'.$dir);
                        if (!is_dir($dir)) {
                            continue;
                        }

                        // if the vendor dir is contained within a psr-0/psr-4 dir being scanned we exclude it
                        if (str_contains($vendorPath, $dir.'/')) {
                            $exclusionRegex = $this->buildExclusionRegex($dir, array_merge($excluded, [$vendorPath.'/']));
                        } else {
                            $exclusionRegex = $this->buildExclusionRegex($dir, $excluded);
                        }

                        $classMapGenerator->scanPaths($dir, $exclusionRegex, $group['type'], $namespace);
                    }
                }
            }
        }

        $classMap = $classMapGenerator->getClassMap();
        if ($strictAmbiguous) {
            $ambiguousClasses = $classMap->getAmbiguousClasses(false);
        } else {
            $ambiguousClasses = $classMap->getAmbiguousClasses();
        }
        foreach ($ambiguousClasses as $className => $ambiguousPaths) {
            if (count($ambiguousPaths) > 1) {
                $this->io->writeError(
                    '<warning>Warning: Ambiguous class resolution, "'.$className.'"'.
                    ' was found '. (count($ambiguousPaths) + 1) .'x: in "'.$classMap->getClassPath($className).'" and "'. implode('", "', $ambiguousPaths) .'", the first will be used.</warning>'
                );
            } else {
                $this->io->writeError(
                    '<warning>Warning: Ambiguous class resolution, "'.$className.'"'.
                    ' was found in both "'.$classMap->getClassPath($className).'" and "'. implode('", "', $ambiguousPaths) .'", the first will be used.</warning>'
                );
            }
        }
        if (\count($ambiguousClasses) > 0) {
            $this->io->writeError('<info>To resolve ambiguity in classes not under your control you can ignore them by path using <href='.OutputFormatter::escape('https://getcomposer.org/doc/04-schema.md#exclude-files-from-classmaps').'>exclude-from-classmap</>');
        }

        // output PSR violations which are not coming from the vendor dir
        $classMap->clearPsrViolationsByPath($vendorPath);
        foreach ($classMap->getPsrViolations() as $msg) {
            $this->io->writeError("<warning>$msg</warning>");
        }

        $classMap->addClass('Composer\InstalledVersions', $vendorPath . '/composer/InstalledVersions.php');
        $classMap->sort();

        $classmapFile = <<<EOF
<?php

// autoload_classmap.php @generated by Composer

\$vendorDir = $vendorPathCode;
\$baseDir = $appBaseDirCode;

return array(

EOF;
        foreach ($classMap->getMap() as $className => $path) {
            $pathCode = $this->getPathCode($filesystem, $basePath, $vendorPath, $path).",\n";
            $classmapFile .= '    '.var_export($className, true).' => '.$pathCode;
        }
        $classmapFile .= ");\n";

        if ('' === $suffix) {
            $suffix = null;
        }
        if (null === $suffix) {
            $suffix = $config->get('autoloader-suffix');

            // carry over existing autoload.php's suffix if possible and none is configured
            if (null === $suffix && Filesystem::isReadable($vendorPath.'/autoload.php')) {
                $content = (string) file_get_contents($vendorPath.'/autoload.php');
                if (Preg::isMatch('{ComposerAutoloaderInit([^:\s]+)::}', $content, $match)) {
                    $suffix = $match[1];
                }
            }

            if (null === $suffix) {
                $suffix = $locker !== null && $locker->isLocked() ? $locker->getLockData()['content-hash'] : bin2hex(random_bytes(16));
            }
        }

        if ($this->dryRun) {
            return $classMap;
        }

        $filesystem->filePutContentsIfModified($targetDir.'/autoload_namespaces.php', $namespacesFile);
        $filesystem->filePutContentsIfModified($targetDir.'/autoload_psr4.php', $psr4File);
        $filesystem->filePutContentsIfModified($targetDir.'/autoload_classmap.php', $classmapFile);
        $includePathFilePath = $targetDir.'/include_paths.php';
        if ($includePathFileContents = $this->getIncludePathsFile($packageMap, $filesystem, $basePath, $vendorPath, $vendorPathCode, $appBaseDirCode)) {
            $filesystem->filePutContentsIfModified($includePathFilePath, $includePathFileContents);
        } elseif (file_exists($includePathFilePath)) {
            unlink($includePathFilePath);
        }
        $includeFilesFilePath = $targetDir.'/autoload_files.php';
        if ($includeFilesFileContents = $this->getIncludeFilesFile($autoloads['files'], $filesystem, $basePath, $vendorPath, $vendorPathCode, $appBaseDirCode)) {
            $filesystem->filePutContentsIfModified($includeFilesFilePath, $includeFilesFileContents);
        } elseif (file_exists($includeFilesFilePath)) {
            unlink($includeFilesFilePath);
        }
        $filesystem->filePutContentsIfModified($targetDir.'/autoload_static.php', $this->getStaticFile($suffix, $targetDir, $vendorPath, $basePath));
        $checkPlatform = $config->get('platform-check') !== false && !($this->platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter);
        $platformCheckContent = null;
        if ($checkPlatform) {
            $platformCheckContent = $this->getPlatformCheck($packageMap, $config->get('platform-check'), $devPackageNames);
            if (null === $platformCheckContent) {
                $checkPlatform = false;
            }
        }
        if ($checkPlatform) {
            $filesystem->filePutContentsIfModified($targetDir.'/platform_check.php', $platformCheckContent);
        } elseif (file_exists($targetDir.'/platform_check.php')) {
            unlink($targetDir.'/platform_check.php');
        }
        $filesystem->filePutContentsIfModified($vendorPath.'/autoload.php', $this->getAutoloadFile($vendorPathToTargetDirCode, $suffix));
        $filesystem->filePutContentsIfModified($targetDir.'/autoload_real.php', $this->getAutoloadRealFile(true, (bool) $includePathFileContents, $targetDirLoader, (bool) $includeFilesFileContents, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader, $checkPlatform));

        $filesystem->safeCopy(__DIR__.'/ClassLoader.php', $targetDir.'/ClassLoader.php');
        $filesystem->safeCopy(__DIR__.'/../../../LICENSE', $targetDir.'/LICENSE');

        if ($this->runScripts) {
            $this->eventDispatcher->dispatchScript(ScriptEvents::POST_AUTOLOAD_DUMP, $this->devMode, [], [
                'optimize' => $scanPsrPackages,
            ]);
        }

        return $classMap;
    }

    /**
     * @param array<string> $excluded
     * @return non-empty-string|null
     */
    private function buildExclusionRegex(string $dir, array $excluded): ?string
    {
        if ([] === $excluded) {
            return null;
        }

        // filter excluded patterns here to only use those matching $dir
        // exclude-from-classmap patterns are all realpath'd so we can only filter them if $dir exists so that realpath($dir) will work
        // if $dir does not exist, it should anyway not find anything there so no trouble
        if (file_exists($dir)) {
            // transform $dir in the same way that exclude-from-classmap patterns are transformed so we can match them against each other
            $dirMatch = preg_quote(strtr(realpath($dir), '\\', '/'));
            // also match against the non-realpath version for symlinks
            $fs = new Filesystem();
            $absDir = $fs->isAbsolutePath($dir) ? $dir : realpath(Platform::getCwd()).'/'.$dir;
            $dirMatchNormalized = preg_quote(strtr($fs->normalizePath($absDir), '\\', '/'));
            $isSymlink = $dirMatch !== $dirMatchNormalized;

            foreach ($excluded as $index => $pattern) {
                // extract the constant string prefix of the pattern here, until we reach a non-escaped regex special character
                $pattern = Preg::replace('{^(([^.+*?\[^\]$(){}=!<>|:\\\\#-]+|\\\\[.+*?\[^\]$(){}=!<>|:#-])*).*}', '$1', $pattern);
                // if the pattern is not a subset or superset of $dir, it is unrelated and we skip it
                if (
                    (!str_starts_with($pattern, $dirMatch) && !str_starts_with($dirMatch, $pattern))
                    && (
                        // only check dirMatchNormalized if it is actually different from dirMatch otherwise there is no point
                        !$isSymlink
                        || (!str_starts_with($pattern, $dirMatchNormalized) && !str_starts_with($dirMatchNormalized, $pattern))
                    )
                ) {
                    unset($excluded[$index]);
                }
            }
        }

        return \count($excluded) > 0 ? '{(' . implode('|', $excluded) . ')}' : null;
    }

    /**
     * @param PackageInterface[] $packages
     * @return non-empty-array<int, array{0: PackageInterface, 1: string|null}>
     */
    public function buildPackageMap(InstallationManager $installationManager, PackageInterface $rootPackage, array $packages)
    {
        // build package => install path map
        $packageMap = [[$rootPackage, '']];

        foreach ($packages as $package) {
            if ($package instanceof AliasPackage) {
                continue;
            }
            $this->validatePackage($package);
            $packageMap[] = [
                $package,
                $installationManager->getInstallPath($package),
            ];
        }

        return $packageMap;
    }

    /**
     * @return void
     * @throws \InvalidArgumentException Throws an exception, if the package has illegal settings.
     */
    protected function validatePackage(PackageInterface $package)
    {
        $autoload = $package->getAutoload();
        if (!empty($autoload['psr-4']) && null !== $package->getTargetDir()) {
            $name = $package->getName();
            $package->getTargetDir();
            throw new \InvalidArgumentException("PSR-4 autoloading is incompatible with the target-dir property, remove the target-dir in package '$name'.");
        }
        if (!empty($autoload['psr-4'])) {
            foreach ($autoload['psr-4'] as $namespace => $dirs) {
                if ($namespace !== '' && '\\' !== substr($namespace, -1)) {
                    throw new \InvalidArgumentException("psr-4 namespaces must end with a namespace separator, '$namespace' does not, use '$namespace\\'.");
                }
            }
        }
    }

    /**
     * Compiles an ordered list of namespace => path mappings
     *
     * @param non-empty-array<int, array{0: PackageInterface, 1: string|null}> $packageMap array of array(package, installDir-relative-to-composer.json or null for metapackages)
     * @param RootPackageInterface $rootPackage root package instance
     * @param bool|string[] $filteredDevPackages If an array, the list of packages that must be removed. If bool, whether to filter out require-dev packages
     * @return array
     * @phpstan-return array{
     *     'psr-0': array<string, array<string>>,
     *     'psr-4': array<string, array<string>>,
     *     'classmap': array<int, string>,
     *     'files': array<string, string>,
     *     'exclude-from-classmap': array<int, string>,
     * }
     */
    public function parseAutoloads(array $packageMap, PackageInterface $rootPackage, $filteredDevPackages = false)
    {
        $rootPackageMap = array_shift($packageMap);
        if (is_array($filteredDevPackages)) {
            $packageMap = array_filter($packageMap, static function ($item) use ($filteredDevPackages): bool {
                return !in_array($item[0]->getName(), $filteredDevPackages, true);
            });
        } elseif ($filteredDevPackages) {
            $packageMap = $this->filterPackageMap($packageMap, $rootPackage);
        }
        $sortedPackageMap = $this->sortPackageMap($packageMap);
        $sortedPackageMap[] = $rootPackageMap;
        $reverseSortedMap = array_reverse($sortedPackageMap);

        // reverse-sorted means root first, then dependents, then their dependents, etc.
        // which makes sense to allow root to override classmap or psr-0/4 entries with higher precedence rules
        $psr0 = $this->parseAutoloadsType($reverseSortedMap, 'psr-0', $rootPackage);
        $psr4 = $this->parseAutoloadsType($reverseSortedMap, 'psr-4', $rootPackage);
        $classmap = $this->parseAutoloadsType($reverseSortedMap, 'classmap', $rootPackage);

        // sorted (i.e. dependents first) for files to ensure that dependencies are loaded/available once a file is included
        $files = $this->parseAutoloadsType($sortedPackageMap, 'files', $rootPackage);
        // using sorted here but it does not really matter as all are excluded equally
        $exclude = $this->parseAutoloadsType($sortedPackageMap, 'exclude-from-classmap', $rootPackage);

        krsort($psr0);
        krsort($psr4);

        return [
            'psr-0' => $psr0,
            'psr-4' => $psr4,
            'classmap' => $classmap,
            'files' => $files,
            'exclude-from-classmap' => $exclude,
        ];
    }

    /**
     * Registers an autoloader based on an autoload-map returned by parseAutoloads
     *
     * @param array<string, mixed[]> $autoloads see parseAutoloads return value
     * @return ClassLoader
     */
    public function createLoader(array $autoloads, ?string $vendorDir = null)
    {
        $loader = new ClassLoader($vendorDir);

        if (isset($autoloads['psr-0'])) {
            foreach ($autoloads['psr-0'] as $namespace => $path) {
                $loader->add($namespace, $path);
            }
        }

        if (isset($autoloads['psr-4'])) {
            foreach ($autoloads['psr-4'] as $namespace => $path) {
                $loader->addPsr4($namespace, $path);
            }
        }

        if (isset($autoloads['classmap'])) {
            $excluded = [];
            if (!empty($autoloads['exclude-from-classmap'])) {
                $excluded = $autoloads['exclude-from-classmap'];
            }

            $classMapGenerator = new ClassMapGenerator(['php', 'inc', 'hh']);
            $classMapGenerator->avoidDuplicateScans();

            foreach ($autoloads['classmap'] as $dir) {
                try {
                    $classMapGenerator->scanPaths($dir, $this->buildExclusionRegex($dir, $excluded));
                } catch (\RuntimeException $e) {
                    $this->io->writeError('<warning>'.$e->getMessage().'</warning>');
                }
            }

            $loader->addClassMap($classMapGenerator->getClassMap()->getMap());
        }

        return $loader;
    }

    /**
     * @param array<int, array{0: PackageInterface, 1: string|null}> $packageMap
     * @return ?string
     */
    protected function getIncludePathsFile(array $packageMap, Filesystem $filesystem, string $basePath, string $vendorPath, string $vendorPathCode, string $appBaseDirCode)
    {
        $includePaths = [];

        foreach ($packageMap as $item) {
            [$package, $installPath] = $item;

            // packages that are not installed cannot autoload anything
            if (null === $installPath) {
                continue;
            }

            if (null !== $package->getTargetDir() && strlen($package->getTargetDir()) > 0) {
                $installPath = substr($installPath, 0, -strlen('/'.$package->getTargetDir()));
            }

            foreach ($package->getIncludePaths() as $includePath) {
                $includePath = trim($includePath, '/');
                $includePaths[] = $installPath === '' ? $includePath : $installPath.'/'.$includePath;
            }
        }

        if (\count($includePaths) === 0) {
            return null;
        }

        $includePathsCode = '';
        foreach ($includePaths as $path) {
            $includePathsCode .= "    " . $this->getPathCode($filesystem, $basePath, $vendorPath, $path) . ",\n";
        }

        return <<<EOF
<?php

// include_paths.php @generated by Composer

\$vendorDir = $vendorPathCode;
\$baseDir = $appBaseDirCode;

return array(
$includePathsCode);

EOF;
    }

    /**
     * @param array<string, string> $files
     * @return ?string
     */
    protected function getIncludeFilesFile(array $files, Filesystem $filesystem, string $basePath, string $vendorPath, string $vendorPathCode, string $appBaseDirCode)
    {
        // Get the path to each file, and make sure these paths are unique.
        $files = array_map(
            function (string $functionFile) use ($filesystem, $basePath, $vendorPath): string {
                return $this->getPathCode($filesystem, $basePath, $vendorPath, $functionFile);
            },
            $files
        );
        $uniqueFiles = array_unique($files);
        if (count($uniqueFiles) < count($files)) {
            $this->io->writeError('<warning>The following "files" autoload rules are included multiple times, this may cause issues and should be resolved:</warning>');
            foreach (array_unique(array_diff_assoc($files, $uniqueFiles)) as $duplicateFile) {
                $this->io->writeError('<warning> - '.$duplicateFile.'</warning>');
            }
        }
        unset($uniqueFiles);

        $filesCode = '';

        foreach ($files as $fileIdentifier => $functionFile) {
            $filesCode .= '    ' . var_export($fileIdentifier, true) . ' => ' . $functionFile . ",\n";
        }

        if (!$filesCode) {
            return null;
        }

        return <<<EOF
<?php

// autoload_files.php @generated by Composer

\$vendorDir = $vendorPathCode;
\$baseDir = $appBaseDirCode;

return array(
$filesCode);

EOF;
    }

    /**
     * @return string
     */
    protected function getPathCode(Filesystem $filesystem, string $basePath, string $vendorPath, string $path)
    {
        if (!$filesystem->isAbsolutePath($path)) {
            $path = $basePath . '/' . $path;
        }
        $path = $filesystem->normalizePath($path);

        $baseDir = '';
        if (strpos($path.'/', $vendorPath.'/') === 0) {
            $path = (string) substr($path, strlen($vendorPath));
            $baseDir = '$vendorDir . ';
        } else {
            $path = $filesystem->normalizePath($filesystem->findShortestPath($basePath, $path, true));
            if (!$filesystem->isAbsolutePath($path)) {
                $baseDir = '$baseDir . ';
                $path = '/' . $path;
            }
        }

        if (Preg::isMatch('{\.phar([\\\\/]|$)}', $path)) {
            $baseDir = "'phar://' . " . $baseDir;
        }

        return $baseDir . var_export($path, true);
    }

    /**
     * @param array<int, array{0: PackageInterface, 1: string|null}> $packageMap
     * @param bool|'php-only' $checkPlatform
     * @param string[] $devPackageNames
     * @return ?string
     */
    protected function getPlatformCheck(array $packageMap, $checkPlatform, array $devPackageNames)
    {
        $lowestPhpVersion = Bound::zero();
        $requiredPhp64bit = false;
        $requiredExtensions = [];
        $extensionProviders = [];

        foreach ($packageMap as $item) {
            $package = $item[0];
            foreach (array_merge($package->getReplaces(), $package->getProvides()) as $link) {
                if (Preg::isMatch('{^ext-(.+)$}iD', $link->getTarget(), $match)) {
                    $extensionProviders[$match[1]][] = $link->getConstraint();
                }
            }
        }

        foreach ($packageMap as $item) {
            $package = $item[0];
            // skip dev dependencies platform requirements as platform-check really should only be a production safeguard
            if (in_array($package->getName(), $devPackageNames, true)) {
                continue;
            }

            foreach ($package->getRequires() as $link) {
                if ($this->platformRequirementFilter->isIgnored($link->getTarget())) {
                    continue;
                }

                if (in_array($link->getTarget(), ['php', 'php-64bit'], true)) {
                    $constraint = $link->getConstraint();
                    if ($constraint->getLowerBound()->compareTo($lowestPhpVersion, '>')) {
                        $lowestPhpVersion = $constraint->getLowerBound();
                    }
                }

                if ('php-64bit' === $link->getTarget()) {
                    $requiredPhp64bit = true;
                }

                if ($checkPlatform === true && Preg::isMatch('{^ext-(.+)$}iD', $link->getTarget(), $match)) {
                    // skip extension checks if they have a valid provider/replacer
                    if (isset($extensionProviders[$match[1]])) {
                        foreach ($extensionProviders[$match[1]] as $provided) {
                            if ($provided->matches($link->getConstraint())) {
                                continue 2;
                            }
                        }
                    }

                    if ($match[1] === 'zend-opcache') {
                        $match[1] = 'zend opcache';
                    }

                    $extension = var_export($match[1], true);
                    if ($match[1] === 'pcntl' || $match[1] === 'readline') {
                        $requiredExtensions[$extension] = "PHP_SAPI !== 'cli' || extension_loaded($extension) || \$missingExtensions[] = $extension;\n";
                    } else {
                        $requiredExtensions[$extension] = "extension_loaded($extension) || \$missingExtensions[] = $extension;\n";
                    }
                }
            }
        }

        ksort($requiredExtensions);

        $formatToPhpVersionId = static function (Bound $bound): int {
            if ($bound->isZero()) {
                return 0;
            }

            if ($bound->isPositiveInfinity()) {
                return 99999;
            }

            $version = str_replace('-', '.', $bound->getVersion());
            $chunks = array_map('intval', explode('.', $version));

            return $chunks[0] * 10000 + $chunks[1] * 100 + $chunks[2];
        };

        $formatToHumanReadable = static function (Bound $bound) {
            if ($bound->isZero()) {
                return 0;
            }

            if ($bound->isPositiveInfinity()) {
                return 99999;
            }

            $version = str_replace('-', '.', $bound->getVersion());
            $chunks = explode('.', $version);
            $chunks = array_slice($chunks, 0, 3);

            return implode('.', $chunks);
        };

        $requiredPhp = '';
        $requiredPhpError = '';
        if (!$lowestPhpVersion->isZero()) {
            $operator = $lowestPhpVersion->isInclusive() ? '>=' : '>';
            $requiredPhp = 'PHP_VERSION_ID '.$operator.' '.$formatToPhpVersionId($lowestPhpVersion);
            $requiredPhpError = '"'.$operator.' '.$formatToHumanReadable($lowestPhpVersion).'"';
        }

        if ($requiredPhp) {
            $requiredPhp = <<<PHP_CHECK

if (!($requiredPhp)) {
    \$issues[] = 'Your Composer dependencies require a PHP version $requiredPhpError. You are running ' . PHP_VERSION . '.';
}

PHP_CHECK;
        }

        if ($requiredPhp64bit) {
            $requiredPhp .= <<<PHP_CHECK

if (PHP_INT_SIZE !== 8) {
    \$issues[] = 'Your Composer dependencies require a 64-bit build of PHP.';
}

PHP_CHECK;
        }

        $requiredExtensions = implode('', $requiredExtensions);
        if ('' !== $requiredExtensions) {
            $requiredExtensions = <<<EXT_CHECKS

\$missingExtensions = array();
$requiredExtensions
if (\$missingExtensions) {
    \$issues[] = 'Your Composer dependencies require the following PHP extensions to be installed: ' . implode(', ', \$missingExtensions) . '.';
}

EXT_CHECKS;
        }

        if (!$requiredPhp && !$requiredExtensions) {
            return null;
        }

        return <<<PLATFORM_CHECK
<?php

// platform_check.php @generated by Composer

\$issues = array();
{$requiredPhp}{$requiredExtensions}
if (\$issues) {
    if (!headers_sent()) {
        header('HTTP/1.1 500 Internal Server Error');
    }
    if (!ini_get('display_errors')) {
        if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
            fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, \$issues) . PHP_EOL.PHP_EOL);
        } elseif (!headers_sent()) {
            echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, \$issues)) . PHP_EOL.PHP_EOL;
        }
    }
    throw new \RuntimeException(
        'Composer detected issues in your platform: ' . implode(' ', \$issues)
    );
}

PLATFORM_CHECK;
    }

    /**
     * @return string
     */
    protected function getAutoloadFile(string $vendorPathToTargetDirCode, string $suffix)
    {
        $lastChar = $vendorPathToTargetDirCode[strlen($vendorPathToTargetDirCode) - 1];
        if ("'" === $lastChar || '"' === $lastChar) {
            $vendorPathToTargetDirCode = substr($vendorPathToTargetDirCode, 0, -1).'/autoload_real.php'.$lastChar;
        } else {
            $vendorPathToTargetDirCode .= " . '/autoload_real.php'";
        }

        return <<<AUTOLOAD
<?php

// autoload.php @generated by Composer

if (PHP_VERSION_ID < 50600) {
    if (!headers_sent()) {
        header('HTTP/1.1 500 Internal Server Error');
    }
    \$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
    if (!ini_get('display_errors')) {
        if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
            fwrite(STDERR, \$err);
        } elseif (!headers_sent()) {
            echo \$err;
        }
    }
    throw new RuntimeException(\$err);
}

require_once $vendorPathToTargetDirCode;

return ComposerAutoloaderInit$suffix::getLoader();

AUTOLOAD;
    }

    /**
     * @param string $vendorPathCode unused in this method
     * @param string $appBaseDirCode unused in this method
     * @param string $prependAutoloader 'true'|'false'
     * @return string
     */
    protected function getAutoloadRealFile(bool $useClassMap, bool $useIncludePath, ?string $targetDirLoader, bool $useIncludeFiles, string $vendorPathCode, string $appBaseDirCode, string $suffix, bool $useGlobalIncludePath, string $prependAutoloader, bool $checkPlatform)
    {
        $file = <<<HEADER
<?php

// autoload_real.php @generated by Composer

class ComposerAutoloaderInit$suffix
{
    private static \$loader;

    public static function loadClassLoader(\$class)
    {
        if ('Composer\\Autoload\\ClassLoader' === \$class) {
            require __DIR__ . '/ClassLoader.php';
        }
    }

    /**
     * @return \Composer\Autoload\ClassLoader
     */
    public static function getLoader()
    {
        if (null !== self::\$loader) {
            return self::\$loader;
        }


HEADER;

        if ($checkPlatform) {
            $file .= <<<'PLATFORM_CHECK'
        require __DIR__ . '/platform_check.php';


PLATFORM_CHECK;
        }

        $file .= <<<CLASSLOADER_INIT
        spl_autoload_register(array('ComposerAutoloaderInit$suffix', 'loadClassLoader'), true, $prependAutoloader);
        self::\$loader = \$loader = new \\Composer\\Autoload\\ClassLoader(\\dirname(__DIR__));
        spl_autoload_unregister(array('ComposerAutoloaderInit$suffix', 'loadClassLoader'));


CLASSLOADER_INIT;

        if ($useIncludePath) {
            $file .= <<<'INCLUDE_PATH'
        $includePaths = require __DIR__ . '/include_paths.php';
        $includePaths[] = get_include_path();
        set_include_path(implode(PATH_SEPARATOR, $includePaths));


INCLUDE_PATH;
        }

        // keeping PHP 5.6+ compatibility for the autoloader here by using call_user_func vs getInitializer()()
        $file .= <<<STATIC_INIT
        require __DIR__ . '/autoload_static.php';
        call_user_func(\Composer\Autoload\ComposerStaticInit$suffix::getInitializer(\$loader));


STATIC_INIT;

        if ($this->classMapAuthoritative) {
            $file .= <<<'CLASSMAPAUTHORITATIVE'
        $loader->setClassMapAuthoritative(true);

CLASSMAPAUTHORITATIVE;
        }

        if ($this->apcu) {
            $apcuPrefix = var_export(($this->apcuPrefix !== null ? $this->apcuPrefix : bin2hex(random_bytes(10))), true);
            $file .= <<<APCU
        \$loader->setApcuPrefix($apcuPrefix);

APCU;
        }

        if ($useGlobalIncludePath) {
            $file .= <<<'INCLUDEPATH'
        $loader->setUseIncludePath(true);

INCLUDEPATH;
        }

        if ($targetDirLoader) {
            $file .= <<<REGISTER_TARGET_DIR_AUTOLOAD
        spl_autoload_register(array('ComposerAutoloaderInit$suffix', 'autoload'), true, true);


REGISTER_TARGET_DIR_AUTOLOAD;
        }

        $file .= <<<REGISTER_LOADER
        \$loader->register($prependAutoloader);


REGISTER_LOADER;

        if ($useIncludeFiles) {
            $file .= <<<INCLUDE_FILES
        \$filesToLoad = \Composer\Autoload\ComposerStaticInit$suffix::\$files;
        \$requireFile = \Closure::bind(static function (\$fileIdentifier, \$file) {
            if (empty(\$GLOBALS['__composer_autoload_files'][\$fileIdentifier])) {
                \$GLOBALS['__composer_autoload_files'][\$fileIdentifier] = true;

                require \$file;
            }
        }, null, null);
        foreach (\$filesToLoad as \$fileIdentifier => \$file) {
            \$requireFile(\$fileIdentifier, \$file);
        }


INCLUDE_FILES;
        }

        $file .= <<<METHOD_FOOTER
        return \$loader;
    }

METHOD_FOOTER;

        $file .= $targetDirLoader;

        return $file . <<<FOOTER
}

FOOTER;
    }

    /**
     * @param string $vendorPath input for findShortestPathCode
     * @param string $basePath input for findShortestPathCode
     * @return string
     */
    protected function getStaticFile(string $suffix, string $targetDir, string $vendorPath, string $basePath)
    {
        $file = <<<HEADER
<?php

// autoload_static.php @generated by Composer

namespace Composer\Autoload;

class ComposerStaticInit$suffix
{

HEADER;

        $loader = new ClassLoader();

        $map = require $targetDir . '/autoload_namespaces.php';
        foreach ($map as $namespace => $path) {
            $loader->set($namespace, $path);
        }

        $map = require $targetDir . '/autoload_psr4.php';
        foreach ($map as $namespace => $path) {
            $loader->setPsr4($namespace, $path);
        }

        /**
         * @var string $vendorDir
         * @var string $baseDir
         */
        $classMap = require $targetDir . '/autoload_classmap.php';
        if ($classMap) {
            $loader->addClassMap($classMap);
        }

        $filesystem = new Filesystem();

        $vendorPathCode = ' => ' . $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true, true) . " . '/";
        $vendorPharPathCode = ' => \'phar://\' . ' . $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true, true) . " . '/";
        $appBaseDirCode = ' => ' . $filesystem->findShortestPathCode(realpath($targetDir), $basePath, true, true) . " . '/";
        $appBaseDirPharCode = ' => \'phar://\' . ' . $filesystem->findShortestPathCode(realpath($targetDir), $basePath, true, true) . " . '/";

        $absoluteVendorPathCode = ' => ' . substr(var_export(rtrim($vendorDir, '\\/') . '/', true), 0, -1);
        $absoluteVendorPharPathCode = ' => ' . substr(var_export(rtrim('phar://' . $vendorDir, '\\/') . '/', true), 0, -1);
        $absoluteAppBaseDirCode = ' => ' . substr(var_export(rtrim($baseDir, '\\/') . '/', true), 0, -1);
        $absoluteAppBaseDirPharCode = ' => ' . substr(var_export(rtrim('phar://' . $baseDir, '\\/') . '/', true), 0, -1);

        $initializer = '';
        $prefix = "\0Composer\Autoload\ClassLoader\0";
        $prefixLen = strlen($prefix);
        if (file_exists($targetDir . '/autoload_files.php')) {
            $maps = ['files' => require $targetDir . '/autoload_files.php'];
        } else {
            $maps = [];
        }

        foreach ((array) $loader as $prop => $value) {
            if (!is_array($value) || \count($value) === 0 || !str_starts_with($prop, $prefix)) {
                continue;
            }
            $maps[substr($prop, $prefixLen)] = $value;
        }

        foreach ($maps as $prop => $value) {
            $value = strtr(
                var_export($value, true),
                [
                    $absoluteVendorPathCode => $vendorPathCode,
                    $absoluteVendorPharPathCode => $vendorPharPathCode,
                    $absoluteAppBaseDirCode => $appBaseDirCode,
                    $absoluteAppBaseDirPharCode => $appBaseDirPharCode,
                ]
            );
            $value = ltrim(Preg::replace('/^ */m', '    $0$0', $value));

            $file .= sprintf("    public static $%s = %s;\n\n", $prop, $value);
            if ('files' !== $prop) {
                $initializer .= "            \$loader->$prop = ComposerStaticInit$suffix::\$$prop;\n";
            }
        }

        return $file . <<<INITIALIZER
    public static function getInitializer(ClassLoader \$loader)
    {
        return \Closure::bind(function () use (\$loader) {
$initializer
        }, null, ClassLoader::class);
    }
}

INITIALIZER;
    }

    /**
     * @param array<int, array{0: PackageInterface, 1: string|null}> $packageMap
     * @param string $type one of: 'psr-0'|'psr-4'|'classmap'|'files'
     * @return array<int, string>|array<string, array<string>>|array<string, string>
     */
    protected function parseAutoloadsType(array $packageMap, string $type, RootPackageInterface $rootPackage)
    {
        $autoloads = [];

        foreach ($packageMap as $item) {
            [$package, $installPath] = $item;

            // packages that are not installed cannot autoload anything
            if (null === $installPath) {
                continue;
            }

            $autoload = $package->getAutoload();
            if ($this->devMode && $package === $rootPackage) {
                $autoload = array_merge_recursive($autoload, $package->getDevAutoload());
            }

            // skip misconfigured packages
            if (!isset($autoload[$type]) || !is_array($autoload[$type])) {
                continue;
            }
            if (null !== $package->getTargetDir() && $package !== $rootPackage) {
                $installPath = substr($installPath, 0, -strlen('/'.$package->getTargetDir()));
            }

            foreach ($autoload[$type] as $namespace => $paths) {
                if (in_array($type, ['psr-4', 'psr-0'], true)) {
                    // normalize namespaces to ensure "\" becomes "" and others do not have leading separators as they are not needed
                    $namespace = ltrim($namespace, '\\');
                }
                foreach ((array) $paths as $path) {
                    if (($type === 'files' || $type === 'classmap' || $type === 'exclude-from-classmap') && $package->getTargetDir() && !Filesystem::isReadable($installPath.'/'.$path)) {
                        // remove target-dir from file paths of the root package
                        if ($package === $rootPackage) {
                            $targetDir = str_replace('\\<dirsep\\>', '[\\\\/]', preg_quote(str_replace(['/', '\\'], '<dirsep>', $package->getTargetDir())));
                            $path = ltrim(Preg::replace('{^'.$targetDir.'}', '', ltrim($path, '\\/')), '\\/');
                        } else {
                            // add target-dir from file paths that don't have it
                            $path = $package->getTargetDir() . '/' . $path;
                        }
                    }

                    if ($type === 'exclude-from-classmap') {
                        // first escape user input
                        $path = Preg::replace('{/+}', '/', preg_quote(trim(strtr($path, '\\', '/'), '/')));

                        // add support for wildcards * and **
                        $path = strtr($path, ['\\*\\*' => '.+?', '\\*' => '[^/]+?']);

                        // add support for up-level relative paths
                        $updir = null;
                        $path = Preg::replaceCallback(
                            '{^((?:(?:\\\\\\.){1,2}+/)+)}',
                            static function ($matches) use (&$updir): string {
                                // undo preg_quote for the matched string
                                $updir = str_replace('\\.', '.', $matches[1]);

                                return '';
                            },
                            $path
                        );
                        if (empty($installPath)) {
                            $installPath = strtr(Platform::getCwd(), '\\', '/');
                        }

                        $resolvedPath = realpath($installPath . '/' . $updir);
                        if (false === $resolvedPath) {
                            continue;
                        }
                        $autoloads[] = preg_quote(strtr($resolvedPath, '\\', '/')) . '/' . $path . '($|/)';
                        continue;
                    }

                    $relativePath = empty($installPath) ? (empty($path) ? '.' : $path) : $installPath.'/'.$path;

                    if ($type === 'files') {
                        $autoloads[$this->getFileIdentifier($package, $path)] = $relativePath;
                        continue;
                    }
                    if ($type === 'classmap') {
                        $autoloads[] = $relativePath;
                        continue;
                    }

                    $autoloads[$namespace][] = $relativePath;
                }
            }
        }

        return $autoloads;
    }

    /**
     * @return string
     */
    protected function getFileIdentifier(PackageInterface $package, string $path)
    {
        // TODO composer v3 change this to sha1 or xxh3? Possibly not worth the potential breakage though
        return hash('md5', $package->getName() . ':' . $path);
    }

    /**
     * Filters out dev-dependencies
     *
     * @param array<int, array{0: PackageInterface, 1: string|null}> $packageMap
     * @return array<int, array{0: PackageInterface, 1: string|null}>
     */
    protected function filterPackageMap(array $packageMap, RootPackageInterface $rootPackage)
    {
        $packages = [];
        $include = [];
        $replacedBy = [];

        foreach ($packageMap as $item) {
            $package = $item[0];
            $name = $package->getName();
            $packages[$name] = $package;
            foreach ($package->getReplaces() as $replace) {
                $replacedBy[$replace->getTarget()] = $name;
            }
        }

        $add = static function (PackageInterface $package) use (&$add, $packages, &$include, $replacedBy): void {
            foreach ($package->getRequires() as $link) {
                $target = $link->getTarget();
                if (isset($replacedBy[$target])) {
                    $target = $replacedBy[$target];
                }
                if (!isset($include[$target])) {
                    $include[$target] = true;
                    if (isset($packages[$target])) {
                        $add($packages[$target]);
                    }
                }
            }
        };
        $add($rootPackage);

        return array_filter(
            $packageMap,
            static function ($item) use ($include): bool {
                $package = $item[0];
                foreach ($package->getNames() as $name) {
                    if (isset($include[$name])) {
                        return true;
                    }
                }

                return false;
            }
        );
    }

    /**
     * Sorts packages by dependency weight
     *
     * Packages of equal weight are sorted alphabetically
     *
     * @param array<int, array{0: PackageInterface, 1: string|null}> $packageMap
     * @return array<int, array{0: PackageInterface, 1: string|null}>
     */
    protected function sortPackageMap(array $packageMap)
    {
        $packages = [];
        $paths = [];

        foreach ($packageMap as $item) {
            [$package, $path] = $item;
            $name = $package->getName();
            $packages[$name] = $package;
            $paths[$name] = $path;
        }

        $sortedPackages = PackageSorter::sortPackages($packages);

        $sortedPackageMap = [];

        foreach ($sortedPackages as $package) {
            $name = $package->getName();
            $sortedPackageMap[] = [$packages[$name], $paths[$name]];
        }

        return $sortedPackageMap;
    }
}

function composerRequire(string $fileIdentifier, string $file): void
{
    if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
        $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;

        require $file;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/*
 * This file is copied from the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 */

namespace Composer\Autoload;

use Composer\ClassMapGenerator\FileList;
use Composer\IO\IOInterface;

/**
 * ClassMapGenerator
 *
 * @author Gyula Sallai <salla016@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 *
 * @deprecated Since Composer 2.4.0 use the composer/class-map-generator package instead
 */
class ClassMapGenerator
{
    /**
     * Generate a class map file
     *
     * @param \Traversable<string>|array<string> $dirs Directories or a single path to search in
     * @param string                             $file The name of the class map file
     */
    public static function dump(iterable $dirs, string $file): void
    {
        $maps = [];

        foreach ($dirs as $dir) {
            $maps = array_merge($maps, static::createMap($dir));
        }

        file_put_contents($file, sprintf('<?php return %s;', var_export($maps, true)));
    }

    /**
     * Iterate over all files in the given directory searching for classes
     *
     * @param \Traversable<\SplFileInfo>|string|array<\SplFileInfo> $path The path to search in or an iterator
     * @param non-empty-string|null                                 $excluded     Regex that matches file paths to be excluded from the classmap
     * @param ?IOInterface                                          $io           IO object
     * @param null|string                                           $namespace    Optional namespace prefix to filter by
     * @param null|'psr-0'|'psr-4'|'classmap'                       $autoloadType psr-0|psr-4 Optional autoload standard to use mapping rules
     * @param array<non-empty-string, true>                         $scannedFiles
     * @return array<class-string, non-empty-string> A class map array
     * @throws \RuntimeException When the path is neither an existing file nor directory
     */
    public static function createMap($path, ?string $excluded = null, ?IOInterface $io = null, ?string $namespace = null, ?string $autoloadType = null, array &$scannedFiles = []): array
    {
        $generator = new \Composer\ClassMapGenerator\ClassMapGenerator(['php', 'inc', 'hh']);
        $fileList = new FileList();
        $fileList->files = $scannedFiles;
        $generator->avoidDuplicateScans($fileList);

        $generator->scanPaths($path, $excluded, $autoloadType ?? 'classmap', $namespace);

        $classMap = $generator->getClassMap();

        $scannedFiles = $fileList->files;

        if ($io !== null) {
            foreach ($classMap->getPsrViolations() as $msg) {
                $io->writeError("<warning>$msg</warning>");
            }

            foreach ($classMap->getAmbiguousClasses() as $class => $paths) {
                if (count($paths) > 1) {
                    $io->writeError(
                        '<warning>Warning: Ambiguous class resolution, "'.$class.'"'.
                        ' was found '. (count($paths) + 1) .'x: in "'.$classMap->getClassPath($class).'" and "'. implode('", "', $paths) .'", the first will be used.</warning>'
                    );
                } else {
                    $io->writeError(
                        '<warning>Warning: Ambiguous class resolution, "'.$class.'"'.
                        ' was found in both "'.$classMap->getClassPath($class).'" and "'. implode('", "', $paths) .'", the first will be used.</warning>'
                    );
                }
            }
        }

        return $classMap->getMap();
    }
}
<?php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Autoload;

/**
 * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
 *
 *     $loader = new \Composer\Autoload\ClassLoader();
 *
 *     // register classes with namespaces
 *     $loader->add('Symfony\Component', __DIR__.'/component');
 *     $loader->add('Symfony',           __DIR__.'/framework');
 *
 *     // activate the autoloader
 *     $loader->register();
 *
 *     // to enable searching the include path (eg. for PEAR packages)
 *     $loader->setUseIncludePath(true);
 *
 * In this example, if you try to use a class in the Symfony\Component
 * namespace or one of its children (Symfony\Component\Console for instance),
 * the autoloader will first look for the class under the component/
 * directory, and it will then fallback to the framework/ directory if not
 * found before giving up.
 *
 * This class is loosely based on the Symfony UniversalClassLoader.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @see    https://www.php-fig.org/psr/psr-0/
 * @see    https://www.php-fig.org/psr/psr-4/
 */
class ClassLoader
{
    /** @var \Closure(string):void */
    private static $includeFile;

    /** @var string|null */
    private $vendorDir;

    // PSR-4
    /**
     * @var array<string, array<string, int>>
     */
    private $prefixLengthsPsr4 = array();
    /**
     * @var array<string, list<string>>
     */
    private $prefixDirsPsr4 = array();
    /**
     * @var list<string>
     */
    private $fallbackDirsPsr4 = array();

    // PSR-0
    /**
     * List of PSR-0 prefixes
     *
     * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
     *
     * @var array<string, array<string, list<string>>>
     */
    private $prefixesPsr0 = array();
    /**
     * @var list<string>
     */
    private $fallbackDirsPsr0 = array();

    /** @var bool */
    private $useIncludePath = false;

    /**
     * @var array<string, string>
     */
    private $classMap = array();

    /** @var bool */
    private $classMapAuthoritative = false;

    /**
     * @var array<string, bool>
     */
    private $missingClasses = array();

    /** @var string|null */
    private $apcuPrefix;

    /**
     * @var array<string, self>
     */
    private static $registeredLoaders = array();

    /**
     * @param string|null $vendorDir
     */
    public function __construct($vendorDir = null)
    {
        $this->vendorDir = $vendorDir;
        self::initializeIncludeClosure();
    }

    /**
     * @return array<string, list<string>>
     */
    public function getPrefixes()
    {
        if (!empty($this->prefixesPsr0)) {
            return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
        }

        return array();
    }

    /**
     * @return array<string, list<string>>
     */
    public function getPrefixesPsr4()
    {
        return $this->prefixDirsPsr4;
    }

    /**
     * @return list<string>
     */
    public function getFallbackDirs()
    {
        return $this->fallbackDirsPsr0;
    }

    /**
     * @return list<string>
     */
    public function getFallbackDirsPsr4()
    {
        return $this->fallbackDirsPsr4;
    }

    /**
     * @return array<string, string> Array of classname => path
     */
    public function getClassMap()
    {
        return $this->classMap;
    }

    /**
     * @param array<string, string> $classMap Class to filename map
     *
     * @return void
     */
    public function addClassMap(array $classMap)
    {
        if ($this->classMap) {
            $this->classMap = array_merge($this->classMap, $classMap);
        } else {
            $this->classMap = $classMap;
        }
    }

    /**
     * Registers a set of PSR-0 directories for a given prefix, either
     * appending or prepending to the ones previously set for this prefix.
     *
     * @param string              $prefix  The prefix
     * @param list<string>|string $paths   The PSR-0 root directories
     * @param bool                $prepend Whether to prepend the directories
     *
     * @return void
     */
    public function add($prefix, $paths, $prepend = false)
    {
        $paths = (array) $paths;
        if (!$prefix) {
            if ($prepend) {
                $this->fallbackDirsPsr0 = array_merge(
                    $paths,
                    $this->fallbackDirsPsr0
                );
            } else {
                $this->fallbackDirsPsr0 = array_merge(
                    $this->fallbackDirsPsr0,
                    $paths
                );
            }

            return;
        }

        $first = $prefix[0];
        if (!isset($this->prefixesPsr0[$first][$prefix])) {
            $this->prefixesPsr0[$first][$prefix] = $paths;

            return;
        }
        if ($prepend) {
            $this->prefixesPsr0[$first][$prefix] = array_merge(
                $paths,
                $this->prefixesPsr0[$first][$prefix]
            );
        } else {
            $this->prefixesPsr0[$first][$prefix] = array_merge(
                $this->prefixesPsr0[$first][$prefix],
                $paths
            );
        }
    }

    /**
     * Registers a set of PSR-4 directories for a given namespace, either
     * appending or prepending to the ones previously set for this namespace.
     *
     * @param string              $prefix  The prefix/namespace, with trailing '\\'
     * @param list<string>|string $paths   The PSR-4 base directories
     * @param bool                $prepend Whether to prepend the directories
     *
     * @throws \InvalidArgumentException
     *
     * @return void
     */
    public function addPsr4($prefix, $paths, $prepend = false)
    {
        $paths = (array) $paths;
        if (!$prefix) {
            // Register directories for the root namespace.
            if ($prepend) {
                $this->fallbackDirsPsr4 = array_merge(
                    $paths,
                    $this->fallbackDirsPsr4
                );
            } else {
                $this->fallbackDirsPsr4 = array_merge(
                    $this->fallbackDirsPsr4,
                    $paths
                );
            }
        } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
            // Register directories for a new namespace.
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            $this->prefixDirsPsr4[$prefix] = $paths;
        } elseif ($prepend) {
            // Prepend directories for an already registered namespace.
            $this->prefixDirsPsr4[$prefix] = array_merge(
                $paths,
                $this->prefixDirsPsr4[$prefix]
            );
        } else {
            // Append directories for an already registered namespace.
            $this->prefixDirsPsr4[$prefix] = array_merge(
                $this->prefixDirsPsr4[$prefix],
                $paths
            );
        }
    }

    /**
     * Registers a set of PSR-0 directories for a given prefix,
     * replacing any others previously set for this prefix.
     *
     * @param string              $prefix The prefix
     * @param list<string>|string $paths  The PSR-0 base directories
     *
     * @return void
     */
    public function set($prefix, $paths)
    {
        if (!$prefix) {
            $this->fallbackDirsPsr0 = (array) $paths;
        } else {
            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
        }
    }

    /**
     * Registers a set of PSR-4 directories for a given namespace,
     * replacing any others previously set for this namespace.
     *
     * @param string              $prefix The prefix/namespace, with trailing '\\'
     * @param list<string>|string $paths  The PSR-4 base directories
     *
     * @throws \InvalidArgumentException
     *
     * @return void
     */
    public function setPsr4($prefix, $paths)
    {
        if (!$prefix) {
            $this->fallbackDirsPsr4 = (array) $paths;
        } else {
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            $this->prefixDirsPsr4[$prefix] = (array) $paths;
        }
    }

    /**
     * Turns on searching the include path for class files.
     *
     * @param bool $useIncludePath
     *
     * @return void
     */
    public function setUseIncludePath($useIncludePath)
    {
        $this->useIncludePath = $useIncludePath;
    }

    /**
     * Can be used to check if the autoloader uses the include path to check
     * for classes.
     *
     * @return bool
     */
    public function getUseIncludePath()
    {
        return $this->useIncludePath;
    }

    /**
     * Turns off searching the prefix and fallback directories for classes
     * that have not been registered with the class map.
     *
     * @param bool $classMapAuthoritative
     *
     * @return void
     */
    public function setClassMapAuthoritative($classMapAuthoritative)
    {
        $this->classMapAuthoritative = $classMapAuthoritative;
    }

    /**
     * Should class lookup fail if not found in the current class map?
     *
     * @return bool
     */
    public function isClassMapAuthoritative()
    {
        return $this->classMapAuthoritative;
    }

    /**
     * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
     *
     * @param string|null $apcuPrefix
     *
     * @return void
     */
    public function setApcuPrefix($apcuPrefix)
    {
        $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
    }

    /**
     * The APCu prefix in use, or null if APCu caching is not enabled.
     *
     * @return string|null
     */
    public function getApcuPrefix()
    {
        return $this->apcuPrefix;
    }

    /**
     * Registers this instance as an autoloader.
     *
     * @param bool $prepend Whether to prepend the autoloader or not
     *
     * @return void
     */
    public function register($prepend = false)
    {
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);

        if (null === $this->vendorDir) {
            return;
        }

        if ($prepend) {
            self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
        } else {
            unset(self::$registeredLoaders[$this->vendorDir]);
            self::$registeredLoaders[$this->vendorDir] = $this;
        }
    }

    /**
     * Unregisters this instance as an autoloader.
     *
     * @return void
     */
    public function unregister()
    {
        spl_autoload_unregister(array($this, 'loadClass'));

        if (null !== $this->vendorDir) {
            unset(self::$registeredLoaders[$this->vendorDir]);
        }
    }

    /**
     * Loads the given class or interface.
     *
     * @param  string    $class The name of the class
     * @return true|null True if loaded, null otherwise
     */
    public function loadClass($class)
    {
        if ($file = $this->findFile($class)) {
            $includeFile = self::$includeFile;
            $includeFile($file);

            return true;
        }

        return null;
    }

    /**
     * Finds the path to the file where the class is defined.
     *
     * @param string $class The name of the class
     *
     * @return string|false The path if found, false otherwise
     */
    public function findFile($class)
    {
        // class map lookup
        if (isset($this->classMap[$class])) {
            return $this->classMap[$class];
        }
        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
            return false;
        }
        if (null !== $this->apcuPrefix) {
            $file = apcu_fetch($this->apcuPrefix.$class, $hit);
            if ($hit) {
                return $file;
            }
        }

        $file = $this->findFileWithExtension($class, '.php');

        // Search for Hack files if we are running on HHVM
        if (false === $file && defined('HHVM_VERSION')) {
            $file = $this->findFileWithExtension($class, '.hh');
        }

        if (null !== $this->apcuPrefix) {
            apcu_add($this->apcuPrefix.$class, $file);
        }

        if (false === $file) {
            // Remember that this class does not exist.
            $this->missingClasses[$class] = true;
        }

        return $file;
    }

    /**
     * Returns the currently registered loaders keyed by their corresponding vendor directories.
     *
     * @return array<string, self>
     */
    public static function getRegisteredLoaders()
    {
        return self::$registeredLoaders;
    }

    /**
     * @param  string       $class
     * @param  string       $ext
     * @return string|false
     */
    private function findFileWithExtension($class, $ext)
    {
        // PSR-4 lookup
        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

        $first = $class[0];
        if (isset($this->prefixLengthsPsr4[$first])) {
            $subPath = $class;
            while (false !== $lastPos = strrpos($subPath, '\\')) {
                $subPath = substr($subPath, 0, $lastPos);
                $search = $subPath . '\\';
                if (isset($this->prefixDirsPsr4[$search])) {
                    $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
                    foreach ($this->prefixDirsPsr4[$search] as $dir) {
                        if (file_exists($file = $dir . $pathEnd)) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-4 fallback dirs
        foreach ($this->fallbackDirsPsr4 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
                return $file;
            }
        }

        // PSR-0 lookup
        if (false !== $pos = strrpos($class, '\\')) {
            // namespaced class name
            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
        } else {
            // PEAR-like class name
            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
        }

        if (isset($this->prefixesPsr0[$first])) {
            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
                if (0 === strpos($class, $prefix)) {
                    foreach ($dirs as $dir) {
                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-0 fallback dirs
        foreach ($this->fallbackDirsPsr0 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                return $file;
            }
        }

        // PSR-0 include paths.
        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
            return $file;
        }

        return false;
    }

    /**
     * @return void
     */
    private static function initializeIncludeClosure()
    {
        if (self::$includeFile !== null) {
            return;
        }

        /**
         * Scope isolated include.
         *
         * Prevents access to $this/self from included files.
         *
         * @param  string $file
         * @return void
         */
        self::$includeFile = \Closure::bind(static function($file) {
            include $file;
        }, null, null);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer;

use Composer\Advisory\Auditor;
use Composer\Config\ConfigSourceInterface;
use Composer\Downloader\TransportException;
use Composer\IO\IOInterface;
use Composer\Pcre\Preg;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Config
{
    public const SOURCE_DEFAULT = 'default';
    public const SOURCE_COMMAND = 'command';
    public const SOURCE_UNKNOWN = 'unknown';

    public const RELATIVE_PATHS = 1;

    /** @var array<string, mixed> */
    public static $defaultConfig = [
        'process-timeout' => 300,
        'use-include-path' => false,
        'allow-plugins' => [],
        'use-parent-dir' => 'prompt',
        'preferred-install' => 'dist',
        'audit' => ['ignore' => [], 'abandoned' => Auditor::ABANDONED_FAIL],
        'notify-on-install' => true,
        'github-protocols' => ['https', 'ssh', 'git'],
        'gitlab-protocol' => null,
        'vendor-dir' => 'vendor',
        'bin-dir' => '{$vendor-dir}/bin',
        'cache-dir' => '{$home}/cache',
        'data-dir' => '{$home}',
        'cache-files-dir' => '{$cache-dir}/files',
        'cache-repo-dir' => '{$cache-dir}/repo',
        'cache-vcs-dir' => '{$cache-dir}/vcs',
        'cache-ttl' => 15552000, // 6 months
        'cache-files-ttl' => null, // fallback to cache-ttl
        'cache-files-maxsize' => '300MiB',
        'cache-read-only' => false,
        'bin-compat' => 'auto',
        'discard-changes' => false,
        'autoloader-suffix' => null,
        'sort-packages' => false,
        'optimize-autoloader' => false,
        'classmap-authoritative' => false,
        'apcu-autoloader' => false,
        'prepend-autoloader' => true,
        'github-domains' => ['github.com'],
        'bitbucket-expose-hostname' => true,
        'disable-tls' => false,
        'secure-http' => true,
        'secure-svn-domains' => [],
        'cafile' => null,
        'capath' => null,
        'github-expose-hostname' => true,
        'gitlab-domains' => ['gitlab.com'],
        'store-auths' => 'prompt',
        'platform' => [],
        'archive-format' => 'tar',
        'archive-dir' => '.',
        'htaccess-protect' => true,
        'use-github-api' => true,
        'lock' => true,
        'platform-check' => 'php-only',
        'bitbucket-oauth' => [],
        'github-oauth' => [],
        'gitlab-oauth' => [],
        'gitlab-token' => [],
        'http-basic' => [],
        'bearer' => [],
        'bump-after-update' => false,
        'allow-missing-requirements' => false,
    ];

    /** @var array<string, mixed> */
    public static $defaultRepositories = [
        'packagist.org' => [
            'type' => 'composer',
            'url' => 'https://repo.packagist.org',
        ],
    ];

    /** @var array<string, mixed> */
    private $config;
    /** @var ?non-empty-string */
    private $baseDir;
    /** @var array<int|string, mixed> */
    private $repositories;
    /** @var ConfigSourceInterface */
    private $configSource;
    /** @var ConfigSourceInterface */
    private $authConfigSource;
    /** @var ConfigSourceInterface|null */
    private $localAuthConfigSource = null;
    /** @var bool */
    private $useEnvironment;
    /** @var array<string, true> */
    private $warnedHosts = [];
    /** @var array<string, true> */
    private $sslVerifyWarnedHosts = [];
    /** @var array<string, string> */
    private $sourceOfConfigValue = [];

    /**
     * @param bool    $useEnvironment Use COMPOSER_ environment variables to replace config settings
     * @param ?string $baseDir        Optional base directory of the config
     */
    public function __construct(bool $useEnvironment = true, ?string $baseDir = null)
    {
        // load defaults
        $this->config = static::$defaultConfig;

        $this->repositories = static::$defaultRepositories;
        $this->useEnvironment = $useEnvironment;
        $this->baseDir = is_string($baseDir) && '' !== $baseDir ? $baseDir : null;

        foreach ($this->config as $configKey => $configValue) {
            $this->setSourceOfConfigValue($configValue, $configKey, self::SOURCE_DEFAULT);
        }

        foreach ($this->repositories as $configKey => $configValue) {
            $this->setSourceOfConfigValue($configValue, 'repositories.' . $configKey, self::SOURCE_DEFAULT);
        }
    }

    /**
     * Changing this can break path resolution for relative config paths so do not call this without knowing what you are doing
     *
     * The $baseDir should be an absolute path and without trailing slash
     *
     * @param non-empty-string|null $baseDir
     */
    public function setBaseDir(?string $baseDir): void
    {
        $this->baseDir = $baseDir;
    }

    public function setConfigSource(ConfigSourceInterface $source): void
    {
        $this->configSource = $source;
    }

    public function getConfigSource(): ConfigSourceInterface
    {
        return $this->configSource;
    }

    public function setAuthConfigSource(ConfigSourceInterface $source): void
    {
        $this->authConfigSource = $source;
    }

    public function getAuthConfigSource(): ConfigSourceInterface
    {
        return $this->authConfigSource;
    }

    public function setLocalAuthConfigSource(ConfigSourceInterface $source): void
    {
        $this->localAuthConfigSource = $source;
    }

    public function getLocalAuthConfigSource(): ?ConfigSourceInterface
    {
        return $this->localAuthConfigSource;
    }

    /**
     * Merges new config values with the existing ones (overriding)
     *
     * @param array{config?: array<string, mixed>, repositories?: array<mixed>} $config
     */
    public function merge(array $config, string $source = self::SOURCE_UNKNOWN): void
    {
        // override defaults with given config
        if (!empty($config['config']) && is_array($config['config'])) {
            foreach ($config['config'] as $key => $val) {
                if (in_array($key, ['bitbucket-oauth', 'github-oauth', 'gitlab-oauth', 'gitlab-token', 'http-basic', 'bearer'], true) && isset($this->config[$key])) {
                    $this->config[$key] = array_merge($this->config[$key], $val);
                    $this->setSourceOfConfigValue($val, $key, $source);
                } elseif (in_array($key, ['allow-plugins'], true) && isset($this->config[$key]) && is_array($this->config[$key]) && is_array($val)) {
                    // merging $val first to get the local config on top of the global one, then appending the global config,
                    // then merging local one again to make sure the values from local win over global ones for keys present in both
                    $this->config[$key] = array_merge($val, $this->config[$key], $val);
                    $this->setSourceOfConfigValue($val, $key, $source);
                } elseif (in_array($key, ['gitlab-domains', 'github-domains'], true) && isset($this->config[$key])) {
                    $this->config[$key] = array_unique(array_merge($this->config[$key], $val));
                    $this->setSourceOfConfigValue($val, $key, $source);
                } elseif ('preferred-install' === $key && isset($this->config[$key])) {
                    if (is_array($val) || is_array($this->config[$key])) {
                        if (is_string($val)) {
                            $val = ['*' => $val];
                        }
                        if (is_string($this->config[$key])) {
                            $this->config[$key] = ['*' => $this->config[$key]];
                            $this->sourceOfConfigValue[$key . '*'] = $source;
                        }
                        $this->config[$key] = array_merge($this->config[$key], $val);
                        $this->setSourceOfConfigValue($val, $key, $source);
                        // the full match pattern needs to be last
                        if (isset($this->config[$key]['*'])) {
                            $wildcard = $this->config[$key]['*'];
                            unset($this->config[$key]['*']);
                            $this->config[$key]['*'] = $wildcard;
                        }
                    } else {
                        $this->config[$key] = $val;
                        $this->setSourceOfConfigValue($val, $key, $source);
                    }
                } elseif ('audit' === $key) {
                    $currentIgnores = $this->config['audit']['ignore'];
                    $this->config[$key] = array_merge($this->config['audit'], $val);
                    $this->setSourceOfConfigValue($val, $key, $source);
                    $this->config['audit']['ignore'] = array_merge($currentIgnores, $val['ignore'] ?? []);
                } else {
                    $this->config[$key] = $val;
                    $this->setSourceOfConfigValue($val, $key, $source);
                }
            }
        }

        if (!empty($config['repositories']) && is_array($config['repositories'])) {
            $this->repositories = array_reverse($this->repositories, true);
            $newRepos = array_reverse($config['repositories'], true);
            foreach ($newRepos as $name => $repository) {
                // disable a repository by name
                if (false === $repository) {
                    $this->disableRepoByName((string) $name);
                    continue;
                }

                // disable a repository with an anonymous {"name": false} repo
                if (is_array($repository) && 1 === count($repository) && false === current($repository)) {
                    $this->disableRepoByName((string) key($repository));
                    continue;
                }

                // auto-deactivate the default packagist.org repo if it gets redefined
                if (isset($repository['type'], $repository['url']) && $repository['type'] === 'composer' && Preg::isMatch('{^https?://(?:[a-z0-9-.]+\.)?packagist.org(/|$)}', $repository['url'])) {
                    $this->disableRepoByName('packagist.org');
                }

                // store repo
                if (is_int($name)) {
                    $this->repositories[] = $repository;
                    $this->setSourceOfConfigValue($repository, 'repositories.' . array_search($repository, $this->repositories, true), $source);
                } else {
                    if ($name === 'packagist') { // BC support for default "packagist" named repo
                        $this->repositories[$name . '.org'] = $repository;
                        $this->setSourceOfConfigValue($repository, 'repositories.' . $name . '.org', $source);
                    } else {
                        $this->repositories[$name] = $repository;
                        $this->setSourceOfConfigValue($repository, 'repositories.' . $name, $source);
                    }
                }
            }
            $this->repositories = array_reverse($this->repositories, true);
        }
    }

    /**
     * @return array<int|string, mixed>
     */
    public function getRepositories(): array
    {
        return $this->repositories;
    }

    /**
     * Returns a setting
     *
     * @param  int               $flags Options (see class constants)
     * @throws \RuntimeException
     *
     * @return mixed
     */
    public function get(string $key, int $flags = 0)
    {
        switch ($key) {
            // strings/paths with env var and {$refs} support
            case 'vendor-dir':
            case 'bin-dir':
            case 'process-timeout':
            case 'data-dir':
            case 'cache-dir':
            case 'cache-files-dir':
            case 'cache-repo-dir':
            case 'cache-vcs-dir':
            case 'cafile':
            case 'capath':
                // convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config
                $env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_'));

                $val = $this->getComposerEnv($env);
                if ($val !== false) {
                    $this->setSourceOfConfigValue($val, $key, $env);
                }

                if ($key === 'process-timeout') {
                    return max(0, false !== $val ? (int) $val : $this->config[$key]);
                }

                $val = rtrim((string) $this->process(false !== $val ? $val : $this->config[$key], $flags), '/\\');
                $val = Platform::expandPath($val);

                if (substr($key, -4) !== '-dir') {
                    return $val;
                }

                return (($flags & self::RELATIVE_PATHS) === self::RELATIVE_PATHS) ? $val : $this->realpath($val);

            // booleans with env var support
            case 'cache-read-only':
            case 'htaccess-protect':
                // convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config
                $env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_'));

                $val = $this->getComposerEnv($env);
                if (false === $val) {
                    $val = $this->config[$key];
                } else {
                    $this->setSourceOfConfigValue($val, $key, $env);
                }

                return $val !== 'false' && (bool) $val;

            // booleans without env var support
            case 'disable-tls':
            case 'secure-http':
            case 'use-github-api':
            case 'lock':
                // special case for secure-http
                if ($key === 'secure-http' && $this->get('disable-tls') === true) {
                    return false;
                }

                return $this->config[$key] !== 'false' && (bool) $this->config[$key];

            // ints without env var support
            case 'cache-ttl':
                return max(0, (int) $this->config[$key]);

            // numbers with kb/mb/gb support, without env var support
            case 'cache-files-maxsize':
                if (!Preg::isMatch('/^\s*([0-9.]+)\s*(?:([kmg])(?:i?b)?)?\s*$/i', (string) $this->config[$key], $matches)) {
                    throw new \RuntimeException(
                        "Could not parse the value of '$key': {$this->config[$key]}"
                    );
                }
                $size = (float) $matches[1];
                if (isset($matches[2])) {
                    switch (strtolower($matches[2])) {
                        case 'g':
                            $size *= 1024;
                            // intentional fallthrough
                            // no break
                        case 'm':
                            $size *= 1024;
                            // intentional fallthrough
                            // no break
                        case 'k':
                            $size *= 1024;
                            break;
                    }
                }

                return max(0, (int) $size);

            // special cases below
            case 'cache-files-ttl':
                if (isset($this->config[$key])) {
                    return max(0, (int) $this->config[$key]);
                }

                return $this->get('cache-ttl');

            case 'home':
                return rtrim($this->process(Platform::expandPath($this->config[$key]), $flags), '/\\');

            case 'bin-compat':
                $value = $this->getComposerEnv('COMPOSER_BIN_COMPAT') ?: $this->config[$key];

                if (!in_array($value, ['auto', 'full', 'proxy', 'symlink'])) {
                    throw new \RuntimeException(
                        "Invalid value for 'bin-compat': {$value}. Expected auto, full or proxy"
                    );
                }

                if ($value === 'symlink') {
                    trigger_error('config.bin-compat "symlink" is deprecated since Composer 2.2, use auto, full (for Windows compatibility) or proxy instead.', E_USER_DEPRECATED);
                }

                return $value;

            case 'discard-changes':
                $env = $this->getComposerEnv('COMPOSER_DISCARD_CHANGES');
                if ($env !== false) {
                    if (!in_array($env, ['stash', 'true', 'false', '1', '0'], true)) {
                        throw new \RuntimeException(
                            "Invalid value for COMPOSER_DISCARD_CHANGES: {$env}. Expected 1, 0, true, false or stash"
                        );
                    }
                    if ('stash' === $env) {
                        return 'stash';
                    }

                    // convert string value to bool
                    return $env !== 'false' && (bool) $env;
                }

                if (!in_array($this->config[$key], [true, false, 'stash'], true)) {
                    throw new \RuntimeException(
                        "Invalid value for 'discard-changes': {$this->config[$key]}. Expected true, false or stash"
                    );
                }

                return $this->config[$key];

            case 'github-protocols':
                $protos = $this->config['github-protocols'];
                if ($this->config['secure-http'] && false !== ($index = array_search('git', $protos))) {
                    unset($protos[$index]);
                }
                if (reset($protos) === 'http') {
                    throw new \RuntimeException('The http protocol for github is not available anymore, update your config\'s github-protocols to use "https", "git" or "ssh"');
                }

                return $protos;

            case 'autoloader-suffix':
                if ($this->config[$key] === '') { // we need to guarantee null or non-empty-string
                    return null;
                }

                return $this->process($this->config[$key], $flags);

            case 'audit':
                $result = $this->config[$key];
                $abandonedEnv = $this->getComposerEnv('COMPOSER_AUDIT_ABANDONED');
                if (false !== $abandonedEnv) {
                    if (!in_array($abandonedEnv, $validChoices = Auditor::ABANDONEDS, true)) {
                        throw new \RuntimeException(
                            "Invalid value for COMPOSER_AUDIT_ABANDONED: {$abandonedEnv}. Expected one of ".implode(', ', Auditor::ABANDONEDS)."."
                        );
                    }
                    $result['abandoned'] = $abandonedEnv;
                }

                return $result;

            default:
                if (!isset($this->config[$key])) {
                    return null;
                }

                return $this->process($this->config[$key], $flags);
        }
    }

    /**
     * @return array<string, mixed[]>
     */
    public function all(int $flags = 0): array
    {
        $all = [
            'repositories' => $this->getRepositories(),
        ];
        foreach (array_keys($this->config) as $key) {
            $all['config'][$key] = $this->get($key, $flags);
        }

        return $all;
    }

    public function getSourceOfValue(string $key): string
    {
        $this->get($key);

        return $this->sourceOfConfigValue[$key] ?? self::SOURCE_UNKNOWN;
    }

    /**
     * @param mixed  $configValue
     */
    private function setSourceOfConfigValue($configValue, string $path, string $source): void
    {
        $this->sourceOfConfigValue[$path] = $source;

        if (is_array($configValue)) {
            foreach ($configValue as $key => $value) {
                $this->setSourceOfConfigValue($value, $path . '.' . $key, $source);
            }
        }
    }

    /**
     * @return array<string, mixed[]>
     */
    public function raw(): array
    {
        return [
            'repositories' => $this->getRepositories(),
            'config' => $this->config,
        ];
    }

    /**
     * Checks whether a setting exists
     */
    public function has(string $key): bool
    {
        return array_key_exists($key, $this->config);
    }

    /**
     * Replaces {$refs} inside a config string
     *
     * @param  string|mixed $value a config string that can contain {$refs-to-other-config}
     * @param  int          $flags Options (see class constants)
     *
     * @return string|mixed
     */
    private function process($value, int $flags)
    {
        if (!is_string($value)) {
            return $value;
        }

        return Preg::replaceCallback('#\{\$(.+)\}#', function ($match) use ($flags) {
            return $this->get($match[1], $flags);
        }, $value);
    }

    /**
     * Turns relative paths in absolute paths without realpath()
     *
     * Since the dirs might not exist yet we can not call realpath or it will fail.
     */
    private function realpath(string $path): string
    {
        if (Preg::isMatch('{^(?:/|[a-z]:|[a-z0-9.]+://|\\\\\\\\)}i', $path)) {
            return $path;
        }

        return $this->baseDir !== null ? $this->baseDir . '/' . $path : $path;
    }

    /**
     * Reads the value of a Composer environment variable
     *
     * This should be used to read COMPOSER_ environment variables
     * that overload config values.
     *
     * @param non-empty-string $var
     *
     * @return string|false
     */
    private function getComposerEnv(string $var)
    {
        if ($this->useEnvironment) {
            return Platform::getEnv($var);
        }

        return false;
    }

    private function disableRepoByName(string $name): void
    {
        if (isset($this->repositories[$name])) {
            unset($this->repositories[$name]);
        } elseif ($name === 'packagist') { // BC support for default "packagist" named repo
            unset($this->repositories['packagist.org']);
        }
    }

    /**
     * Validates that the passed URL is allowed to be used by current config, or throws an exception.
     *
     * @param IOInterface $io
     * @param mixed[]     $repoOptions
     */
    public function prohibitUrlByConfig(string $url, ?IOInterface $io = null, array $repoOptions = []): void
    {
        // Return right away if the URL is malformed or custom (see issue #5173), but only for non-HTTP(S) URLs
        if (false === filter_var($url, FILTER_VALIDATE_URL) && !Preg::isMatch('{^https?://}', $url)) {
            return;
        }

        // Extract scheme and throw exception on known insecure protocols
        $scheme = parse_url($url, PHP_URL_SCHEME);
        $hostname = parse_url($url, PHP_URL_HOST);
        if (in_array($scheme, ['http', 'git', 'ftp', 'svn'])) {
            if ($this->get('secure-http')) {
                if ($scheme === 'svn') {
                    if (in_array($hostname, $this->get('secure-svn-domains'), true)) {
                        return;
                    }

                    throw new TransportException("Your configuration does not allow connections to $url. See https://getcomposer.org/doc/06-config.md#secure-svn-domains for details.");
                }

                throw new TransportException("Your configuration does not allow connections to $url. See https://getcomposer.org/doc/06-config.md#secure-http for details.");
            }
            if ($io !== null) {
                if (is_string($hostname)) {
                    if (!isset($this->warnedHosts[$hostname])) {
                        $io->writeError("<warning>Warning: Accessing $hostname over $scheme which is an insecure protocol.</warning>");
                    }
                    $this->warnedHosts[$hostname] = true;
                }
            }
        }

        if ($io !== null && is_string($hostname) && !isset($this->sslVerifyWarnedHosts[$hostname])) {
            $warning = null;
            if (isset($repoOptions['ssl']['verify_peer']) && !(bool) $repoOptions['ssl']['verify_peer']) {
                $warning = 'verify_peer';
            }

            if (isset($repoOptions['ssl']['verify_peer_name']) && !(bool) $repoOptions['ssl']['verify_peer_name']) {
                $warning = $warning === null ? 'verify_peer_name' : $warning . ' and verify_peer_name';
            }

            if ($warning !== null) {
                $io->writeError("<warning>Warning: Accessing $hostname with $warning disabled.</warning>");
                $this->sslVerifyWarnedHosts[$hostname] = true;
            }
        }
    }

    /**
     * Used by long-running custom scripts in composer.json
     *
     * "scripts": {
     *   "watch": [
     *     "Composer\\Config::disableProcessTimeout",
     *     "vendor/bin/long-running-script --watch"
     *   ]
     * }
     */
    public static function disableProcessTimeout(): void
    {
        // Override global timeout set earlier by environment or config
        ProcessExecutor::setTimeout(0);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use React\Promise\PromiseInterface;
use Composer\Package\PackageInterface;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;

/**
 * GZip archive downloader.
 *
 * @author Pavel Puchkin <i@neoascetic.me>
 */
class GzipDownloader extends ArchiveDownloader
{
    protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface
    {
        $filename = pathinfo(parse_url(strtr((string) $package->getDistUrl(), '\\', '/'), PHP_URL_PATH), PATHINFO_FILENAME);
        $targetFilepath = $path . DIRECTORY_SEPARATOR . $filename;

        // Try to use gunzip on *nix
        if (!Platform::isWindows()) {
            $command = ['sh', '-c', 'gzip -cd -- "$0" > "$1"', $file, $targetFilepath];

            if (0 === $this->process->execute($command, $ignoredOutput)) {
                return \React\Promise\resolve(null);
            }

            if (extension_loaded('zlib')) {
                // Fallback to using the PHP extension.
                $this->extractUsingExt($file, $targetFilepath);

                return \React\Promise\resolve(null);
            }

            $processError = 'Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput();
            throw new \RuntimeException($processError);
        }

        // Windows version of PHP has built-in support of gzip functions
        $this->extractUsingExt($file, $targetFilepath);

        return \React\Promise\resolve(null);
    }

    private function extractUsingExt(string $file, string $targetFilepath): void
    {
        $archiveFile = gzopen($file, 'rb');
        $targetFile = fopen($targetFilepath, 'wb');
        while ($string = gzread($archiveFile, 4096)) {
            fwrite($targetFile, $string, Platform::strlen($string));
        }
        gzclose($archiveFile);
        fclose($targetFile);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use React\Promise\PromiseInterface;
use Composer\Package\PackageInterface;
use Composer\Repository\VcsRepository;
use Composer\Util\Perforce;

/**
 * @author Matt Whittom <Matt.Whittom@veteransunited.com>
 */
class PerforceDownloader extends VcsDownloader
{
    /** @var Perforce|null */
    protected $perforce;

    /**
     * @inheritDoc
     */
    protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null): PromiseInterface
    {
        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function doInstall(PackageInterface $package, string $path, string $url): PromiseInterface
    {
        $ref = $package->getSourceReference();
        $label = $this->getLabelFromSourceReference((string) $ref);

        $this->io->writeError('Cloning ' . $ref);
        $this->initPerforce($package, $path, $url);
        $this->perforce->setStream($ref);
        $this->perforce->p4Login();
        $this->perforce->writeP4ClientSpec();
        $this->perforce->connectClient();
        $this->perforce->syncCodeBase($label);
        $this->perforce->cleanupClientSpec();

        return \React\Promise\resolve(null);
    }

    private function getLabelFromSourceReference(string $ref): ?string
    {
        $pos = strpos($ref, '@');
        if (false !== $pos) {
            return substr($ref, $pos + 1);
        }

        return null;
    }

    public function initPerforce(PackageInterface $package, string $path, string $url): void
    {
        if (!empty($this->perforce)) {
            $this->perforce->initializePath($path);

            return;
        }

        $repository = $package->getRepository();
        $repoConfig = null;
        if ($repository instanceof VcsRepository) {
            $repoConfig = $this->getRepoConfig($repository);
        }
        $this->perforce = Perforce::create($repoConfig, $url, $path, $this->process, $this->io);
    }

    /**
     * @return array<string, mixed>
     */
    private function getRepoConfig(VcsRepository $repository): array
    {
        return $repository->getRepoConfig();
    }

    /**
     * @inheritDoc
     */
    protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface
    {
        return $this->doInstall($target, $path, $url);
    }

    /**
     * @inheritDoc
     */
    public function getLocalChanges(PackageInterface $package, string $path): ?string
    {
        $this->io->writeError('Perforce driver does not check for local changes before overriding');

        return null;
    }

    /**
     * @inheritDoc
     */
    protected function getCommitLogs(string $fromReference, string $toReference, string $path): string
    {
        return $this->perforce->getCommitLogs($fromReference, $toReference);
    }

    public function setPerforce(Perforce $perforce): void
    {
        $this->perforce = $perforce;
    }

    /**
     * @inheritDoc
     */
    protected function hasMetadataRepository(string $path): bool
    {
        return true;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use React\Promise\PromiseInterface;
use Composer\Package\Archiver\ArchivableFilesFinder;
use Composer\Package\Dumper\ArrayDumper;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionGuesser;
use Composer\Package\Version\VersionParser;
use Composer\Util\Platform;
use Composer\Util\Filesystem;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\UninstallOperation;

/**
 * Download a package from a local path.
 *
 * @author Samuel Roze <samuel.roze@gmail.com>
 * @author Johann Reinke <johann.reinke@gmail.com>
 */
class PathDownloader extends FileDownloader implements VcsCapableDownloaderInterface
{
    private const STRATEGY_SYMLINK = 10;
    private const STRATEGY_MIRROR = 20;

    /**
     * @inheritDoc
     */
    public function download(PackageInterface $package, string $path, ?PackageInterface $prevPackage = null, bool $output = true): PromiseInterface
    {
        $path = Filesystem::trimTrailingSlash($path);
        $url = $package->getDistUrl();
        if (null === $url) {
            throw new \RuntimeException('The package '.$package->getPrettyName().' has no dist url configured, cannot download.');
        }
        $realUrl = realpath($url);
        if (false === $realUrl || !file_exists($realUrl) || !is_dir($realUrl)) {
            throw new \RuntimeException(sprintf(
                'Source path "%s" is not found for package %s',
                $url,
                $package->getName()
            ));
        }

        if (realpath($path) === $realUrl) {
            return \React\Promise\resolve(null);
        }

        if (strpos(realpath($path) . DIRECTORY_SEPARATOR, $realUrl . DIRECTORY_SEPARATOR) === 0) {
            // IMPORTANT NOTICE: If you wish to change this, don't. You are wasting your time and ours.
            //
            // Please see https://github.com/composer/composer/pull/5974 and https://github.com/composer/composer/pull/6174
            // for previous attempts that were shut down because they did not work well enough or introduced too many risks.
            throw new \RuntimeException(sprintf(
                'Package %s cannot install to "%s" inside its source at "%s"',
                $package->getName(),
                realpath($path),
                $realUrl
            ));
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function install(PackageInterface $package, string $path, bool $output = true): PromiseInterface
    {
        $path = Filesystem::trimTrailingSlash($path);
        $url = $package->getDistUrl();
        if (null === $url) {
            throw new \RuntimeException('The package '.$package->getPrettyName().' has no dist url configured, cannot install.');
        }
        $realUrl = realpath($url);
        if (false === $realUrl) {
            throw new \RuntimeException('Failed to realpath '.$url);
        }

        if (realpath($path) === $realUrl) {
            if ($output) {
                $this->io->writeError("  - " . InstallOperation::format($package) . $this->getInstallOperationAppendix($package, $path));
            }

            return \React\Promise\resolve(null);
        }

        // Get the transport options with default values
        $transportOptions = $package->getTransportOptions() + ['relative' => true];

        [$currentStrategy, $allowedStrategies] = $this->computeAllowedStrategies($transportOptions);

        $symfonyFilesystem = new SymfonyFilesystem();
        $this->filesystem->removeDirectory($path);

        if ($output) {
            $this->io->writeError("  - " . InstallOperation::format($package).': ', false);
        }

        $isFallback = false;
        if (self::STRATEGY_SYMLINK === $currentStrategy) {
            try {
                if (Platform::isWindows()) {
                    // Implement symlinks as NTFS junctions on Windows
                    if ($output) {
                        $this->io->writeError(sprintf('Junctioning from %s', $url), false);
                    }
                    $this->filesystem->junction($realUrl, $path);
                } else {
                    $path = rtrim($path, "/");
                    if ($output) {
                        $this->io->writeError(sprintf('Symlinking from %s', $url), false);
                    }
                    if ($transportOptions['relative'] === true) {
                        $absolutePath = $path;
                        if (!$this->filesystem->isAbsolutePath($absolutePath)) {
                            $absolutePath = Platform::getCwd() . DIRECTORY_SEPARATOR . $path;
                        }
                        $shortestPath = $this->filesystem->findShortestPath($absolutePath, $realUrl, false, true);
                        $symfonyFilesystem->symlink($shortestPath.'/', $path);
                    } else {
                        $symfonyFilesystem->symlink($realUrl.'/', $path);
                    }
                }
            } catch (IOException $e) {
                if (in_array(self::STRATEGY_MIRROR, $allowedStrategies, true)) {
                    if ($output) {
                        $this->io->writeError('');
                        $this->io->writeError('    <error>Symlink failed, fallback to use mirroring!</error>');
                    }
                    $currentStrategy = self::STRATEGY_MIRROR;
                    $isFallback = true;
                } else {
                    throw new \RuntimeException(sprintf('Symlink from "%s" to "%s" failed!', $realUrl, $path));
                }
            }
        }

        // Fallback if symlink failed or if symlink is not allowed for the package
        if (self::STRATEGY_MIRROR === $currentStrategy) {
            $realUrl = $this->filesystem->normalizePath($realUrl);

            if ($output) {
                $this->io->writeError(sprintf('%sMirroring from %s', $isFallback ? '    ' : '', $url), false);
            }
            $iterator = new ArchivableFilesFinder($realUrl, []);
            $symfonyFilesystem->mirror($realUrl, $path, $iterator);
        }

        if ($output) {
            $this->io->writeError('');
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function remove(PackageInterface $package, string $path, bool $output = true): PromiseInterface
    {
        $path = Filesystem::trimTrailingSlash($path);
        /**
         * realpath() may resolve Windows junctions to the source path, so we'll check for a junction first
         * to prevent a false positive when checking if the dist and install paths are the same.
         * See https://bugs.php.net/bug.php?id=77639
         *
         * For junctions don't blindly rely on Filesystem::removeDirectory as it may be overzealous. If a process
         * inadvertently locks the file the removal will fail, but it would fall back to recursive delete which
         * is disastrous within a junction. So in that case we have no other real choice but to fail hard.
         */
        if (Platform::isWindows() && $this->filesystem->isJunction($path)) {
            if ($output) {
                $this->io->writeError("  - " . UninstallOperation::format($package).", source is still present in $path");
            }
            if (!$this->filesystem->removeJunction($path)) {
                $this->io->writeError("    <warning>Could not remove junction at " . $path . " - is another process locking it?</warning>");
                throw new \RuntimeException('Could not reliably remove junction for package ' . $package->getName());
            }

            return \React\Promise\resolve(null);
        }

        $url = $package->getDistUrl();
        if (null === $url) {
            throw new \RuntimeException('The package '.$package->getPrettyName().' has no dist url configured, cannot remove.');
        }

        // ensure that the source path (dist url) is not the same as the install path, which
        // can happen when using custom installers, see https://github.com/composer/composer/pull/9116
        // not using realpath here as we do not want to resolve the symlink to the original dist url
        // it points to
        $fs = new Filesystem;
        $absPath = $fs->isAbsolutePath($path) ? $path : Platform::getCwd() . '/' . $path;
        $absDistUrl = $fs->isAbsolutePath($url) ? $url : Platform::getCwd() . '/' . $url;
        if ($fs->normalizePath($absPath) === $fs->normalizePath($absDistUrl)) {
            if ($output) {
                $this->io->writeError("  - " . UninstallOperation::format($package).", source is still present in $path");
            }

            return \React\Promise\resolve(null);
        }

        return parent::remove($package, $path, $output);
    }

    /**
     * @inheritDoc
     */
    public function getVcsReference(PackageInterface $package, string $path): ?string
    {
        $path = Filesystem::trimTrailingSlash($path);
        $parser = new VersionParser;
        $guesser = new VersionGuesser($this->config, $this->process, $parser, $this->io);
        $dumper = new ArrayDumper;

        $packageConfig = $dumper->dump($package);
        $packageVersion = $guesser->guessVersion($packageConfig, $path);
        if ($packageVersion !== null) {
            return $packageVersion['commit'];
        }

        return null;
    }

    /**
     * @inheritDoc
     */
    protected function getInstallOperationAppendix(PackageInterface $package, string $path): string
    {
        $url = $package->getDistUrl();
        if (null === $url) {
            throw new \RuntimeException('The package '.$package->getPrettyName().' has no dist url configured, cannot install.');
        }
        $realUrl = realpath($url);
        if (false === $realUrl) {
            throw new \RuntimeException('Failed to realpath '.$url);
        }

        if (realpath($path) === $realUrl) {
            return ': Source already present';
        }

        [$currentStrategy] = $this->computeAllowedStrategies($package->getTransportOptions());

        if ($currentStrategy === self::STRATEGY_SYMLINK) {
            if (Platform::isWindows()) {
                return ': Junctioning from '.$package->getDistUrl();
            }

            return ': Symlinking from '.$package->getDistUrl();
        }

        return ': Mirroring from '.$package->getDistUrl();
    }

    /**
     * @param mixed[] $transportOptions
     *
     * @phpstan-return array{self::STRATEGY_*, non-empty-list<self::STRATEGY_*>}
     */
    private function computeAllowedStrategies(array $transportOptions): array
    {
        // When symlink transport option is null, both symlink and mirror are allowed
        $currentStrategy = self::STRATEGY_SYMLINK;
        $allowedStrategies = [self::STRATEGY_SYMLINK, self::STRATEGY_MIRROR];

        $mirrorPathRepos = Platform::getEnv('COMPOSER_MIRROR_PATH_REPOS');
        if ((bool) $mirrorPathRepos) {
            $currentStrategy = self::STRATEGY_MIRROR;
        }

        $symlinkOption = $transportOptions['symlink'] ?? null;

        if (true === $symlinkOption) {
            $currentStrategy = self::STRATEGY_SYMLINK;
            $allowedStrategies = [self::STRATEGY_SYMLINK];
        } elseif (false === $symlinkOption) {
            $currentStrategy = self::STRATEGY_MIRROR;
            $allowedStrategies = [self::STRATEGY_MIRROR];
        }

        // Check we can use junctions safely if we are on Windows
        if (Platform::isWindows() && self::STRATEGY_SYMLINK === $currentStrategy && !$this->safeJunctions()) {
            if (!in_array(self::STRATEGY_MIRROR, $allowedStrategies, true)) {
                throw new \RuntimeException('You are on an old Windows / old PHP combo which does not allow Composer to use junctions/symlinks and this path repository has symlink:true in its options so copying is not allowed');
            }
            $currentStrategy = self::STRATEGY_MIRROR;
            $allowedStrategies = [self::STRATEGY_MIRROR];
        }

        // Check we can use symlink() otherwise
        if (!Platform::isWindows() && self::STRATEGY_SYMLINK === $currentStrategy && !function_exists('symlink')) {
            if (!in_array(self::STRATEGY_MIRROR, $allowedStrategies, true)) {
                throw new \RuntimeException('Your PHP has the symlink() function disabled which does not allow Composer to use symlinks and this path repository has symlink:true in its options so copying is not allowed');
            }
            $currentStrategy = self::STRATEGY_MIRROR;
            $allowedStrategies = [self::STRATEGY_MIRROR];
        }

        return [$currentStrategy, $allowedStrategies];
    }

    /**
     * Returns true if junctions can be created and safely used on Windows
     *
     * A PHP bug makes junction detection fragile, leading to possible data loss
     * when removing a package. See https://bugs.php.net/bug.php?id=77552
     *
     * For safety we require a minimum version of Windows 7, so we can call the
     * system rmdir which will preserve target content if given a junction.
     *
     * The PHP bug was fixed in 7.2.16 and 7.3.3 (requires at least Windows 7).
     */
    private function safeJunctions(): bool
    {
        // We need to call mklink, and rmdir on Windows 7 (version 6.1)
        return function_exists('proc_open') &&
            (PHP_WINDOWS_VERSION_MAJOR > 6 ||
            (PHP_WINDOWS_VERSION_MAJOR === 6 && PHP_WINDOWS_VERSION_MINOR >= 1));
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use Composer\Package\PackageInterface;
use React\Promise\PromiseInterface;

/**
 * Downloader interface.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface DownloaderInterface
{
    /**
     * Returns installation source (either source or dist).
     *
     * @return string "source" or "dist"
     */
    public function getInstallationSource(): string;

    /**
     * This should do any network-related tasks to prepare for an upcoming install/update
     *
     * @param  string $path download path
     * @phpstan-return PromiseInterface<void|null|string>
     */
    public function download(PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface;

    /**
     * Do anything that needs to be done between all downloads have been completed and the actual operation is executed
     *
     * All packages get first downloaded, then all together prepared, then all together installed/updated/uninstalled. Therefore
     * for error recovery it is important to avoid failing during install/update/uninstall as much as possible, and risky things or
     * user prompts should happen in the prepare step rather. In case of failure, cleanup() will be called so that changes can
     * be undone as much as possible.
     *
     * @param  string                $type        one of install/update/uninstall
     * @param  PackageInterface      $package     package instance
     * @param  string                $path        download path
     * @param  PackageInterface      $prevPackage previous package instance in case of an update
     * @phpstan-return PromiseInterface<void|null>
     */
    public function prepare(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface;

    /**
     * Installs specific package into specific folder.
     *
     * @param  PackageInterface      $package package instance
     * @param  string                $path    download path
     * @phpstan-return PromiseInterface<void|null>
     */
    public function install(PackageInterface $package, string $path): PromiseInterface;

    /**
     * Updates specific package in specific folder from initial to target version.
     *
     * @param  PackageInterface      $initial initial package
     * @param  PackageInterface      $target  updated package
     * @param  string                $path    download path
     * @phpstan-return PromiseInterface<void|null>
     */
    public function update(PackageInterface $initial, PackageInterface $target, string $path): PromiseInterface;

    /**
     * Removes specific package from specific folder.
     *
     * @param  PackageInterface      $package package instance
     * @param  string                $path    download path
     * @phpstan-return PromiseInterface<void|null>
     */
    public function remove(PackageInterface $package, string $path): PromiseInterface;

    /**
     * Do anything to cleanup changes applied in the prepare or install/update/uninstall steps
     *
     * Note that cleanup will be called for all packages, either after install/update/uninstall is complete,
     * or if any package failed any operation. This is to give all installers a change to cleanup things
     * they did previously, so you need to keep track of changes applied in the installer/downloader themselves.
     *
     * @param  string                $type        one of install/update/uninstall
     * @param  PackageInterface      $package     package instance
     * @param  string                $path        download path
     * @param  PackageInterface      $prevPackage previous package instance in case of an update
     * @phpstan-return PromiseInterface<void|null>
     */
    public function cleanup(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface;
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use React\Promise\PromiseInterface;
use Composer\Util\IniHelper;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Package\PackageInterface;
use RarArchive;

/**
 * RAR archive downloader.
 *
 * Based on previous work by Jordi Boggiano ({@see ZipDownloader}).
 *
 * @author Derrick Nelson <drrcknlsn@gmail.com>
 */
class RarDownloader extends ArchiveDownloader
{
    protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface
    {
        $processError = null;

        // Try to use unrar on *nix
        if (!Platform::isWindows()) {
            $command = ['sh', '-c', 'unrar x -- "$0" "$1" >/dev/null && chmod -R u+w "$1"', $file, $path];

            if (0 === $this->process->execute($command, $ignoredOutput)) {
                return \React\Promise\resolve(null);
            }

            $processError = 'Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput();
        }

        if (!class_exists('RarArchive')) {
            // php.ini path is added to the error message to help users find the correct file
            $iniMessage = IniHelper::getMessage();

            $error = "Could not decompress the archive, enable the PHP rar extension or install unrar.\n"
                . $iniMessage . "\n" . $processError;

            if (!Platform::isWindows()) {
                $error = "Could not decompress the archive, enable the PHP rar extension.\n" . $iniMessage;
            }

            throw new \RuntimeException($error);
        }

        $rarArchive = RarArchive::open($file);

        if (false === $rarArchive) {
            throw new \UnexpectedValueException('Could not open RAR archive: ' . $file);
        }

        $entries = $rarArchive->getEntries();

        if (false === $entries) {
            throw new \RuntimeException('Could not retrieve RAR archive entries');
        }

        foreach ($entries as $entry) {
            if (false === $entry->extract($path)) {
                throw new \RuntimeException('Could not extract entry');
            }
        }

        $rarArchive->close();

        return \React\Promise\resolve(null);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use Composer\Package\PackageInterface;

/**
 * VCS Capable Downloader interface.
 *
 * @author Steve Buzonas <steve@fancyguy.com>
 */
interface VcsCapableDownloaderInterface
{
    /**
     * Gets the VCS Reference for the package at path
     *
     * @param  PackageInterface $package package instance
     * @param  string           $path    package directory
     * @return string|null      reference or null
     */
    public function getVcsReference(PackageInterface $package, string $path): ?string;
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use Composer\Package\PackageInterface;
use Composer\Pcre\Preg;
use Composer\Util\IniHelper;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Symfony\Component\Process\ExecutableFinder;
use Symfony\Component\Process\Process;
use React\Promise\PromiseInterface;
use ZipArchive;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ZipDownloader extends ArchiveDownloader
{
    /** @var array<int, non-empty-list<string>> */
    private static $unzipCommands;
    /** @var bool */
    private static $hasZipArchive;
    /** @var bool */
    private static $isWindows;

    /** @var ZipArchive|null */
    private $zipArchiveObject; // @phpstan-ignore property.onlyRead (helper property that is set via reflection for testing purposes)

    /**
     * @inheritDoc
     */
    public function download(PackageInterface $package, string $path, ?PackageInterface $prevPackage = null, bool $output = true): PromiseInterface
    {
        if (null === self::$unzipCommands) {
            self::$unzipCommands = [];
            $finder = new ExecutableFinder;
            if (Platform::isWindows() && ($cmd = $finder->find('7z', null, ['C:\Program Files\7-Zip']))) {
                self::$unzipCommands[] = ['7z', $cmd, 'x', '-bb0', '-y', '%file%', '-o%path%'];
            }
            if ($cmd = $finder->find('unzip')) {
                self::$unzipCommands[] = ['unzip', $cmd, '-qq', '%file%', '-d', '%path%'];
            }
            if (!Platform::isWindows() && ($cmd = $finder->find('7z'))) { // 7z linux/macOS support is only used if unzip is not present
                self::$unzipCommands[] = ['7z', $cmd, 'x', '-bb0', '-y', '%file%', '-o%path%'];
            }
            if (!Platform::isWindows() && ($cmd = $finder->find('7zz'))) { // 7zz linux/macOS support is only used if unzip is not present
                self::$unzipCommands[] = ['7zz', $cmd, 'x', '-bb0', '-y', '%file%', '-o%path%'];
            }
        }

        $procOpenMissing = false;
        if (!function_exists('proc_open')) {
            self::$unzipCommands = [];
            $procOpenMissing = true;
        }

        if (null === self::$hasZipArchive) {
            self::$hasZipArchive = class_exists('ZipArchive');
        }

        if (!self::$hasZipArchive && !self::$unzipCommands) {
            // php.ini path is added to the error message to help users find the correct file
            $iniMessage = IniHelper::getMessage();
            if ($procOpenMissing) {
                $error = "The zip extension is missing and unzip/7z commands cannot be called as proc_open is disabled, skipping.\n" . $iniMessage;
            } else {
                $error = "The zip extension and unzip/7z commands are both missing, skipping.\n" . $iniMessage;
            }

            throw new \RuntimeException($error);
        }

        if (null === self::$isWindows) {
            self::$isWindows = Platform::isWindows();

            if (!self::$isWindows && !self::$unzipCommands) {
                if ($procOpenMissing) {
                    $this->io->writeError("<warning>proc_open is disabled so 'unzip' and '7z' commands cannot be used, zip files are being unpacked using the PHP zip extension.</warning>");
                    $this->io->writeError("<warning>This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost.</warning>");
                    $this->io->writeError("<warning>Enabling proc_open and installing 'unzip' or '7z' (21.01+) may remediate them.</warning>");
                } else {
                    $this->io->writeError("<warning>As there is no 'unzip' nor '7z' command installed zip files are being unpacked using the PHP zip extension.</warning>");
                    $this->io->writeError("<warning>This may cause invalid reports of corrupted archives. Besides, any UNIX permissions (e.g. executable) defined in the archives will be lost.</warning>");
                    $this->io->writeError("<warning>Installing 'unzip' or '7z' (21.01+) may remediate them.</warning>");
                }
            }
        }

        return parent::download($package, $path, $prevPackage, $output);
    }

    /**
     * extract $file to $path with "unzip" command
     *
     * @param  string           $file File to extract
     * @param  string           $path Path where to extract file
     * @phpstan-return PromiseInterface<void|null>
     */
    private function extractWithSystemUnzip(PackageInterface $package, string $file, string $path): PromiseInterface
    {
        static $warned7ZipLinux = false;

        // Force Exception throwing if the other alternative extraction method is not available
        $isLastChance = !self::$hasZipArchive;

        if (0 === \count(self::$unzipCommands)) {
            // This was call as the favorite extract way, but is not available
            // We switch to the alternative
            return $this->extractWithZipArchive($package, $file, $path);
        }

        $commandSpec = reset(self::$unzipCommands);
        $executable = $commandSpec[0];
        $command = array_slice($commandSpec, 1);
        $map = [
            // normalize separators to backslashes to avoid problems with 7-zip on windows
            // see https://github.com/composer/composer/issues/10058
            '%file%' => strtr($file, '/', DIRECTORY_SEPARATOR),
            '%path%' => strtr($path, '/', DIRECTORY_SEPARATOR),
        ];
        $command = array_map(static function ($value) use ($map) {
            return strtr($value, $map);
        }, $command);

        if (!$warned7ZipLinux && !Platform::isWindows() && in_array($executable, ['7z', '7zz'], true)) {
            $warned7ZipLinux = true;
            if (0 === $this->process->execute([$commandSpec[1]], $output)) {
                if (Preg::isMatchStrictGroups('{^\s*7-Zip(?: \[64\])? ([0-9.]+)}', $output, $match) && version_compare($match[1], '21.01', '<')) {
                    $this->io->writeError('    <warning>Unzipping using '.$executable.' '.$match[1].' may result in incorrect file permissions. Install '.$executable.' 21.01+ or unzip to ensure you get correct permissions.</warning>');
                }
            }
        }

        $io = $this->io;
        $tryFallback = function (\Throwable $processError) use ($isLastChance, $io, $file, $path, $package, $executable): \React\Promise\PromiseInterface {
            if ($isLastChance) {
                throw $processError;
            }

            if (str_contains($processError->getMessage(), 'zip bomb')) {
                throw $processError;
            }

            if (!is_file($file)) {
                $io->writeError('    <warning>'.$processError->getMessage().'</warning>');
                $io->writeError('    <warning>This most likely is due to a custom installer plugin not handling the returned Promise from the downloader</warning>');
                $io->writeError('    <warning>See https://github.com/composer/installers/commit/5006d0c28730ade233a8f42ec31ac68fb1c5c9bb for an example fix</warning>');
            } else {
                $io->writeError('    <warning>'.$processError->getMessage().'</warning>');
                $io->writeError('    The archive may contain identical file names with different capitalization (which fails on case insensitive filesystems)');
                $io->writeError('    Unzip with '.$executable.' command failed, falling back to ZipArchive class');

                // additional debug data to try to figure out GH actions issues https://github.com/composer/composer/issues/11148
                if (Platform::getEnv('GITHUB_ACTIONS') !== false && Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING') === false) {
                    $io->writeError('    <warning>Additional debug info, please report to https://github.com/composer/composer/issues/11148 if you see this:</warning>');
                    $io->writeError('File size: '.@filesize($file));
                    $io->writeError('File SHA1: '.hash_file('sha1', $file));
                    $io->writeError('First 100 bytes (hex): '.bin2hex(substr((string) file_get_contents($file), 0, 100)));
                    $io->writeError('Last 100 bytes (hex): '.bin2hex(substr((string) file_get_contents($file), -100)));
                    if (strlen((string) $package->getDistUrl()) > 0) {
                        $io->writeError('Origin URL: '.$this->processUrl($package, (string) $package->getDistUrl()));
                        $io->writeError('Response Headers: '.json_encode(FileDownloader::$responseHeaders[$package->getName()] ?? []));
                    }
                }
            }

            return $this->extractWithZipArchive($package, $file, $path);
        };

        try {
            $promise = $this->process->executeAsync($command);

            return $promise->then(function (Process $process) use ($tryFallback, $command, $package, $file) {
                if (!$process->isSuccessful()) {
                    if (isset($this->cleanupExecuted[$package->getName()])) {
                        throw new \RuntimeException('Failed to extract '.$package->getName().' as the installation was aborted by another package operation.');
                    }

                    $output = $process->getErrorOutput();
                    $output = str_replace(', '.$file.'.zip or '.$file.'.ZIP', '', $output);

                    return $tryFallback(new \RuntimeException('Failed to extract '.$package->getName().': ('.$process->getExitCode().') '.implode(' ', $command)."\n\n".$output));
                }
            });
        } catch (\Throwable $e) {
            return $tryFallback($e);
        }
    }

    /**
     * extract $file to $path with ZipArchive
     *
     * @param  string           $file File to extract
     * @param  string           $path Path where to extract file
     * @phpstan-return PromiseInterface<void|null>
     */
    private function extractWithZipArchive(PackageInterface $package, string $file, string $path): PromiseInterface
    {
        $processError = null;
        $zipArchive = $this->zipArchiveObject ?: new ZipArchive();

        try {
            if (!file_exists($file) || ($filesize = filesize($file)) === false || $filesize === 0) {
                $retval = -1;
            } else {
                $retval = $zipArchive->open($file);
            }

            if (true === $retval) {
                $totalSize = 0;
                $archiveSize = filesize($file);
                $totalFiles = $zipArchive->count();
                if ($totalFiles > 0) {
                    $inspectAll = false;
                    $filesToInspect = min($totalFiles, 5);
                    for ($i = 0; $i < $filesToInspect; $i++) {
                        $stat = $zipArchive->statIndex($inspectAll ? $i : random_int(0, $totalFiles - 1));
                        if ($stat === false) {
                            continue;
                        }
                        $totalSize += $stat['size'];
                        if (!$inspectAll && $stat['size'] > $stat['comp_size'] * 200) {
                            $totalSize = 0;
                            $inspectAll = true;
                            $i = -1;
                            $filesToInspect = $totalFiles;
                        }
                    }
                    if ($archiveSize !== false && $totalSize > $archiveSize * 100 && $totalSize > 50*1024*1024) {
                        throw new \RuntimeException('Invalid zip file for "'.$package->getName().'" with compression ratio >99% (possible zip bomb)');
                    }
                }

                $extractResult = $zipArchive->extractTo($path);

                if (true === $extractResult) {
                    $zipArchive->close();

                    return \React\Promise\resolve(null);
                }

                $processError = new \RuntimeException(rtrim("There was an error extracting the ZIP file for \"{$package->getName()}\", it is either corrupted or using an invalid format.\n"));
            } else {
                $processError = new \UnexpectedValueException(rtrim($this->getErrorMessage($retval, $file)."\n"), $retval);
            }
        } catch (\ErrorException $e) {
            $processError = new \RuntimeException('The archive for "'.$package->getName().'" may contain identical file names with different capitalization (which fails on case insensitive filesystems): '.$e->getMessage(), 0, $e);
        } catch (\Throwable $e) {
            $processError = $e;
        }

        throw $processError;
    }

    /**
     * extract $file to $path
     *
     * @param  string                $file File to extract
     * @param  string                $path Path where to extract file
     */
    protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface
    {
        return $this->extractWithSystemUnzip($package, $file, $path);
    }

    /**
     * Give a meaningful error message to the user.
     */
    protected function getErrorMessage(int $retval, string $file): string
    {
        switch ($retval) {
            case ZipArchive::ER_EXISTS:
                return sprintf("File '%s' already exists.", $file);
            case ZipArchive::ER_INCONS:
                return sprintf("Zip archive '%s' is inconsistent.", $file);
            case ZipArchive::ER_INVAL:
                return sprintf("Invalid argument (%s)", $file);
            case ZipArchive::ER_MEMORY:
                return sprintf("Malloc failure (%s)", $file);
            case ZipArchive::ER_NOENT:
                return sprintf("No such zip file: '%s'", $file);
            case ZipArchive::ER_NOZIP:
                return sprintf("'%s' is not a zip archive.", $file);
            case ZipArchive::ER_OPEN:
                return sprintf("Can't open zip file: %s", $file);
            case ZipArchive::ER_READ:
                return sprintf("Zip read error (%s)", $file);
            case ZipArchive::ER_SEEK:
                return sprintf("Zip seek error (%s)", $file);
            case -1:
                return sprintf("'%s' is a corrupted zip archive (0 bytes), try again.", $file);
            default:
                return sprintf("'%s' is not a valid zip archive, got error code: %s", $file, $retval);
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use Composer\Config;
use Composer\Cache;
use Composer\IO\IOInterface;
use Composer\IO\NullIO;
use Composer\Exception\IrrecoverableDownloadException;
use Composer\Package\Comparer\Comparer;
use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\UninstallOperation;
use Composer\Package\PackageInterface;
use Composer\Plugin\PluginEvents;
use Composer\Plugin\PostFileDownloadEvent;
use Composer\Plugin\PreFileDownloadEvent;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Util\Filesystem;
use Composer\Util\Http\Response;
use Composer\Util\Platform;
use Composer\Util\Silencer;
use Composer\Util\HttpDownloader;
use Composer\Util\Url as UrlUtil;
use Composer\Util\ProcessExecutor;
use React\Promise\PromiseInterface;

/**
 * Base downloader for files
 *
 * @author Kirill chEbba Chebunin <iam@chebba.org>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 * @author Nils Adermann <naderman@naderman.de>
 */
class FileDownloader implements DownloaderInterface, ChangeReportInterface
{
    /** @var IOInterface */
    protected $io;
    /** @var Config */
    protected $config;
    /** @var HttpDownloader */
    protected $httpDownloader;
    /** @var Filesystem */
    protected $filesystem;
    /** @var ?Cache */
    protected $cache;
    /** @var ?EventDispatcher */
    protected $eventDispatcher;
    /** @var ProcessExecutor */
    protected $process;
    /**
     * @var array<string, int|string>
     * @private
     * @internal
     */
    public static $downloadMetadata = [];
    /**
     * Collects response headers when running on GH Actions
     *
     * @see https://github.com/composer/composer/issues/11148
     * @var array<string, array<string>>
     * @private
     * @internal
     */
    public static $responseHeaders = [];

    /**
     * @var array<string, string> Map of package name to cache key
     */
    private $lastCacheWrites = [];
    /** @var array<string, string[]> Map of package name to list of paths */
    private $additionalCleanupPaths = [];

    /**
     * Constructor.
     *
     * @param IOInterface     $io              The IO instance
     * @param Config          $config          The config
     * @param HttpDownloader  $httpDownloader  The remote filesystem
     * @param EventDispatcher $eventDispatcher The event dispatcher
     * @param Cache           $cache           Cache instance
     * @param Filesystem      $filesystem      The filesystem
     */
    public function __construct(IOInterface $io, Config $config, HttpDownloader $httpDownloader, ?EventDispatcher $eventDispatcher = null, ?Cache $cache = null, ?Filesystem $filesystem = null, ?ProcessExecutor $process = null)
    {
        $this->io = $io;
        $this->config = $config;
        $this->eventDispatcher = $eventDispatcher;
        $this->httpDownloader = $httpDownloader;
        $this->cache = $cache;
        $this->process = $process ?? new ProcessExecutor($io);
        $this->filesystem = $filesystem ?? new Filesystem($this->process);

        if ($this->cache !== null && $this->cache->gcIsNecessary()) {
            $this->io->writeError('Running cache garbage collection', true, IOInterface::VERY_VERBOSE);
            $this->cache->gc($config->get('cache-files-ttl'), $config->get('cache-files-maxsize'));
        }
    }

    /**
     * @inheritDoc
     */
    public function getInstallationSource(): string
    {
        return 'dist';
    }

    /**
     * @inheritDoc
     */
    public function download(PackageInterface $package, string $path, ?PackageInterface $prevPackage = null, bool $output = true): PromiseInterface
    {
        if (null === $package->getDistUrl()) {
            throw new \InvalidArgumentException('The given package is missing url information');
        }

        $cacheKeyGenerator = static function (PackageInterface $package, $key): string {
            $cacheKey = hash('sha1', $key);

            return $package->getName().'/'.$cacheKey.'.'.$package->getDistType();
        };

        $retries = 3;
        $distUrls = $package->getDistUrls();
        /** @var array<array{base: non-empty-string, processed: non-empty-string, cacheKey: string}> $urls */
        $urls = [];
        foreach ($distUrls as $index => $url) {
            $processedUrl = $this->processUrl($package, $url);
            $urls[$index] = [
                'base' => $url,
                'processed' => $processedUrl,
                // we use the complete download url here to avoid conflicting entries
                // from different packages, which would potentially allow a given package
                // in a third party repo to pre-populate the cache for the same package in
                // packagist for example.
                'cacheKey' => $cacheKeyGenerator($package, $processedUrl),
            ];
        }
        assert(count($urls) > 0);

        $fileName = $this->getFileName($package, $path);
        $this->filesystem->ensureDirectoryExists($path);
        $this->filesystem->ensureDirectoryExists(dirname($fileName));

        $accept = null;
        $reject = null;
        $download = function () use ($output, $cacheKeyGenerator, $package, $fileName, &$urls, &$accept, &$reject) {
            $url = reset($urls);
            $index = key($urls);

            if ($this->eventDispatcher !== null) {
                $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $url['processed'], 'package', $package);
                $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent);
                if ($preFileDownloadEvent->getCustomCacheKey() !== null) {
                    $url['cacheKey'] = $cacheKeyGenerator($package, $preFileDownloadEvent->getCustomCacheKey());
                } elseif ($preFileDownloadEvent->getProcessedUrl() !== $url['processed']) {
                    $url['cacheKey'] = $cacheKeyGenerator($package, $preFileDownloadEvent->getProcessedUrl());
                }
                $url['processed'] = $preFileDownloadEvent->getProcessedUrl();
            }

            $urls[$index] = $url;

            $checksum = $package->getDistSha1Checksum();
            $cacheKey = $url['cacheKey'];

            // use from cache if it is present and has a valid checksum or we have no checksum to check against
            if ($this->cache !== null && ($checksum === null || $checksum === '' || $checksum === $this->cache->sha1($cacheKey)) && $this->cache->copyTo($cacheKey, $fileName)) {
                if ($output) {
                    $this->io->writeError("  - Loading <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>) from cache", true, IOInterface::VERY_VERBOSE);
                }
                // mark the file as having been written in cache even though it is only read from cache, so that if
                // the cache is corrupt the archive will be deleted and the next attempt will re-download it
                // see https://github.com/composer/composer/issues/10028
                if (!$this->cache->isReadOnly()) {
                    $this->lastCacheWrites[$package->getName()] = $cacheKey;
                }
                $result = \React\Promise\resolve($fileName);
            } else {
                if ($output) {
                    $this->io->writeError("  - Downloading <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");
                }

                $result = $this->httpDownloader->addCopy($url['processed'], $fileName, $package->getTransportOptions())
                    ->then($accept, $reject);
            }

            return $result->then(function ($result) use ($fileName, $checksum, $url, $package): string {
                // in case of retry, the first call's Promise chain finally calls this twice at the end,
                // once with $result being the returned $fileName from $accept, and then once for every
                // failed request with a null result, which can be skipped.
                if (null === $result) {
                    return $fileName;
                }

                if (!file_exists($fileName)) {
                    throw new \UnexpectedValueException($url['base'].' could not be saved to '.$fileName.', make sure the'
                        .' directory is writable and you have internet connectivity');
                }

                if ($checksum !== null && $checksum !== '' && hash_file('sha1', $fileName) !== $checksum) {
                    throw new \UnexpectedValueException('The checksum verification of the file failed (downloaded from '.$url['base'].')');
                }

                if ($this->eventDispatcher !== null) {
                    $postFileDownloadEvent = new PostFileDownloadEvent(PluginEvents::POST_FILE_DOWNLOAD, $fileName, $checksum, $url['processed'], 'package', $package);
                    $this->eventDispatcher->dispatch($postFileDownloadEvent->getName(), $postFileDownloadEvent);
                }

                return $fileName;
            });
        };

        $accept = function (Response $response) use ($package, $fileName, &$urls): string {
            $url = reset($urls);
            $cacheKey = $url['cacheKey'];
            $fileSize = @filesize($fileName);
            if (false === $fileSize) {
                $fileSize = $response->getHeader('Content-Length') ?? '?';
            }
            FileDownloader::$downloadMetadata[$package->getName()] = $fileSize;

            if (Platform::getEnv('GITHUB_ACTIONS') !== false && Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING') === false) {
                FileDownloader::$responseHeaders[$package->getName()] = $response->getHeaders();
            }

            if ($this->cache !== null && !$this->cache->isReadOnly()) {
                $this->lastCacheWrites[$package->getName()] = $cacheKey;
                $this->cache->copyFrom($cacheKey, $fileName);
            }

            $response->collect();

            return $fileName;
        };

        $reject = function ($e) use (&$urls, $download, $fileName, $package, &$retries) {
            // clean up
            if (file_exists($fileName)) {
                $this->filesystem->unlink($fileName);
            }
            $this->clearLastCacheWrite($package);

            if ($e instanceof IrrecoverableDownloadException) {
                throw $e;
            }

            if ($e instanceof MaxFileSizeExceededException) {
                throw $e;
            }

            if ($e instanceof TransportException) {
                // if we got an http response with a proper code, then requesting again will probably not help, abort
                if (0 !== $e->getCode() && !in_array($e->getCode(), [500, 502, 503, 504], true)) {
                    $retries = 0;
                }
            }

            // special error code returned when network is being artificially disabled
            if ($e instanceof TransportException && $e->getStatusCode() === 499) {
                $retries = 0;
                $urls = [];
            }

            if ($retries > 0) {
                usleep(500000);
                $retries--;

                return $download();
            }

            array_shift($urls);
            if (\count($urls) > 0) {
                if ($this->io->isDebug()) {
                    $this->io->writeError('    Failed downloading '.$package->getName().': ['.get_class($e).'] '.$e->getCode().': '.$e->getMessage());
                    $this->io->writeError('    Trying the next URL for '.$package->getName());
                } else {
                    $this->io->writeError('    Failed downloading '.$package->getName().', trying the next URL ('.$e->getCode().': '.$e->getMessage().')');
                }

                $retries = 3;
                usleep(100000);

                return $download();
            }

            throw $e;
        };

        return $download();
    }

    /**
     * @inheritDoc
     */
    public function prepare(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface
    {
        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function cleanup(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface
    {
        $fileName = $this->getFileName($package, $path);
        if (file_exists($fileName)) {
            $this->filesystem->unlink($fileName);
        }

        $dirsToCleanUp = [
            $path,
            $this->config->get('vendor-dir').'/'.explode('/', $package->getPrettyName())[0],
            $this->config->get('vendor-dir').'/composer/',
            $this->config->get('vendor-dir'),
        ];

        if (isset($this->additionalCleanupPaths[$package->getName()])) {
            foreach ($this->additionalCleanupPaths[$package->getName()] as $pathToClean) {
                $this->filesystem->remove($pathToClean);
            }
        }

        foreach ($dirsToCleanUp as $dir) {
            if (is_dir($dir) && $this->filesystem->isDirEmpty($dir) && realpath($dir) !== Platform::getCwd()) {
                $this->filesystem->removeDirectoryPhp($dir);
            }
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function install(PackageInterface $package, string $path, bool $output = true): PromiseInterface
    {
        if ($output) {
            $this->io->writeError("  - " . InstallOperation::format($package));
        }

        $vendorDir = $this->config->get('vendor-dir');

        // clean up the target directory, unless it contains the vendor dir, as the vendor dir contains
        // the file to be installed. This is the case when installing with create-project in the current directory
        // but in that case we ensure the directory is empty already in ProjectInstaller so no need to empty it here.
        if (false === strpos($this->filesystem->normalizePath($vendorDir), $this->filesystem->normalizePath($path.DIRECTORY_SEPARATOR))) {
            $this->filesystem->emptyDirectory($path);
        }
        $this->filesystem->ensureDirectoryExists($path);
        $this->filesystem->rename($this->getFileName($package, $path), $path . '/' . $this->getDistPath($package, PATHINFO_BASENAME));

        // Single files can not have a mode set like files in archives
        // so we make sure if the file is a binary that it is executable
        foreach ($package->getBinaries() as $bin) {
            if (file_exists($path . '/' . $bin) && !is_executable($path . '/' . $bin)) {
                Silencer::call('chmod', $path . '/' . $bin, 0777 & ~umask());
            }
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @param PATHINFO_EXTENSION|PATHINFO_BASENAME $component
     */
    protected function getDistPath(PackageInterface $package, int $component): string
    {
        return pathinfo((string) parse_url(strtr((string) $package->getDistUrl(), '\\', '/'), PHP_URL_PATH), $component);
    }

    protected function clearLastCacheWrite(PackageInterface $package): void
    {
        if ($this->cache !== null && isset($this->lastCacheWrites[$package->getName()])) {
            $this->cache->remove($this->lastCacheWrites[$package->getName()]);
            unset($this->lastCacheWrites[$package->getName()]);
        }
    }

    protected function addCleanupPath(PackageInterface $package, string $path): void
    {
        $this->additionalCleanupPaths[$package->getName()][] = $path;
    }

    protected function removeCleanupPath(PackageInterface $package, string $path): void
    {
        if (isset($this->additionalCleanupPaths[$package->getName()])) {
            $idx = array_search($path, $this->additionalCleanupPaths[$package->getName()], true);
            if (false !== $idx) {
                unset($this->additionalCleanupPaths[$package->getName()][$idx]);
            }
        }
    }

    /**
     * @inheritDoc
     */
    public function update(PackageInterface $initial, PackageInterface $target, string $path): PromiseInterface
    {
        $this->io->writeError("  - " . UpdateOperation::format($initial, $target) . $this->getInstallOperationAppendix($target, $path));

        $promise = $this->remove($initial, $path, false);

        return $promise->then(function () use ($target, $path): PromiseInterface {
            return $this->install($target, $path, false);
        });
    }

    /**
     * @inheritDoc
     */
    public function remove(PackageInterface $package, string $path, bool $output = true): PromiseInterface
    {
        if ($output) {
            $this->io->writeError("  - " . UninstallOperation::format($package));
        }
        $promise = $this->filesystem->removeDirectoryAsync($path);

        return $promise->then(static function ($result) use ($path): void {
            if (!$result) {
                throw new \RuntimeException('Could not completely delete '.$path.', aborting.');
            }
        });
    }

    /**
     * Gets file name for specific package
     *
     * @param  PackageInterface $package package instance
     * @param  string           $path    download path
     * @return string           file name
     */
    protected function getFileName(PackageInterface $package, string $path): string
    {
        $extension = $this->getDistPath($package, PATHINFO_EXTENSION);
        if ($extension === '') {
            $extension = $package->getDistType();
        }

        return rtrim($this->config->get('vendor-dir') . '/composer/tmp-' . hash('md5', $package . spl_object_hash($package)) . '.' . $extension, '.');
    }

    /**
     * Gets appendix message to add to the "- Upgrading x" string being output on update
     *
     * @param  PackageInterface $package package instance
     * @param  string           $path    download path
     */
    protected function getInstallOperationAppendix(PackageInterface $package, string $path): string
    {
        return '';
    }

    /**
     * Process the download url
     *
     * @param  PackageInterface  $package package instance
     * @param  non-empty-string  $url     download url
     * @throws \RuntimeException If any problem with the url
     * @return non-empty-string  url
     */
    protected function processUrl(PackageInterface $package, string $url): string
    {
        if (!extension_loaded('openssl') && 0 === strpos($url, 'https:')) {
            throw new \RuntimeException('You must enable the openssl extension to download files via https');
        }

        if ($package->getDistReference() !== null) {
            $url = UrlUtil::updateDistReference($this->config, $url, $package->getDistReference());
        }

        return $url;
    }

    /**
     * @inheritDoc
     * @throws \RuntimeException
     */
    public function getLocalChanges(PackageInterface $package, string $path): ?string
    {
        $prevIO = $this->io;

        $this->io = new NullIO;
        $this->io->loadConfiguration($this->config);
        $e = null;
        $output = '';

        $targetDir = Filesystem::trimTrailingSlash($path);
        try {
            if (is_dir($targetDir.'_compare')) {
                $this->filesystem->removeDirectory($targetDir.'_compare');
            }

            $promise = $this->download($package, $targetDir.'_compare', null, false);
            $promise->then(null, function ($ex) use (&$e) {
                $e = $ex;
            });
            $this->httpDownloader->wait();
            if ($e !== null) {
                throw $e;
            }
            $promise = $this->install($package, $targetDir.'_compare', false);
            $promise->then(null, function ($ex) use (&$e) {
                $e = $ex;
            });
            $this->process->wait();
            if ($e !== null) {
                throw $e;
            }

            $comparer = new Comparer();
            $comparer->setSource($targetDir.'_compare');
            $comparer->setUpdate($targetDir);
            $comparer->doCompare();
            $output = $comparer->getChangedAsString(true);
            $this->filesystem->removeDirectory($targetDir.'_compare');
        } catch (\Exception $e) {
        }

        $this->io = $prevIO;

        if ($e !== null) {
            if ($this->io->isDebug()) {
                throw $e;
            }

            return 'Failed to detect changes: ['.get_class($e).'] '.$e->getMessage();
        }

        $output = trim($output);

        return strlen($output) > 0 ? $output : null;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use React\Promise\PromiseInterface;
use Composer\Package\PackageInterface;
use Composer\Util\ProcessExecutor;

/**
 * Xz archive downloader.
 *
 * @author Pavel Puchkin <i@neoascetic.me>
 * @author Pierre Rudloff <contact@rudloff.pro>
 */
class XzDownloader extends ArchiveDownloader
{
    protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface
    {
        $command = ['tar', '-xJf', $file, '-C', $path];

        if (0 === $this->process->execute($command, $ignoredOutput)) {
            return \React\Promise\resolve(null);
        }

        $processError = 'Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput();

        throw new \RuntimeException($processError);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use Composer\Package\PackageInterface;
use React\Promise\PromiseInterface;

/**
 * Downloader for tar files: tar, tar.gz or tar.bz2
 *
 * @author Kirill chEbba Chebunin <iam@chebba.org>
 */
class TarDownloader extends ArchiveDownloader
{
    /**
     * @inheritDoc
     */
    protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface
    {
        // Can throw an UnexpectedValueException
        $archive = new \PharData($file);
        $archive->extractTo($path, null, true);

        return \React\Promise\resolve(null);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use Composer\Package\PackageInterface;
use Composer\Util\Platform;
use Symfony\Component\Finder\Finder;
use React\Promise\PromiseInterface;
use Composer\DependencyResolver\Operation\InstallOperation;

/**
 * Base downloader for archives
 *
 * @author Kirill chEbba Chebunin <iam@chebba.org>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 */
abstract class ArchiveDownloader extends FileDownloader
{
    /**
     * @var array<string, true>
     */
    protected $cleanupExecuted = [];

    public function prepare(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface
    {
        unset($this->cleanupExecuted[$package->getName()]);

        return parent::prepare($type, $package, $path, $prevPackage);
    }

    public function cleanup(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface
    {
        $this->cleanupExecuted[$package->getName()] = true;

        return parent::cleanup($type, $package, $path, $prevPackage);
    }

    /**
     * @inheritDoc
     *
     * @throws \RuntimeException
     * @throws \UnexpectedValueException
     */
    public function install(PackageInterface $package, string $path, bool $output = true): PromiseInterface
    {
        if ($output) {
            $this->io->writeError("  - " . InstallOperation::format($package) . $this->getInstallOperationAppendix($package, $path));
        }

        $vendorDir = $this->config->get('vendor-dir');

        // clean up the target directory, unless it contains the vendor dir, as the vendor dir contains
        // the archive to be extracted. This is the case when installing with create-project in the current directory
        // but in that case we ensure the directory is empty already in ProjectInstaller so no need to empty it here.
        if (false === strpos($this->filesystem->normalizePath($vendorDir), $this->filesystem->normalizePath($path.DIRECTORY_SEPARATOR))) {
            $this->filesystem->emptyDirectory($path);
        }

        do {
            $temporaryDir = $vendorDir.'/composer/'.bin2hex(random_bytes(4));
        } while (is_dir($temporaryDir));

        $this->addCleanupPath($package, $temporaryDir);
        // avoid cleaning up $path if installing in "." for eg create-project as we can not
        // delete the directory we are currently in on windows
        if (!is_dir($path) || realpath($path) !== Platform::getCwd()) {
            $this->addCleanupPath($package, $path);
        }

        $this->filesystem->ensureDirectoryExists($temporaryDir);
        $fileName = $this->getFileName($package, $path);

        $filesystem = $this->filesystem;

        $cleanup = function () use ($path, $filesystem, $temporaryDir, $package) {
            // remove cache if the file was corrupted
            $this->clearLastCacheWrite($package);

            // clean up
            $filesystem->removeDirectory($temporaryDir);
            if (is_dir($path) && realpath($path) !== Platform::getCwd()) {
                $filesystem->removeDirectory($path);
            }
            $this->removeCleanupPath($package, $temporaryDir);
            $realpath = realpath($path);
            if ($realpath !== false) {
                $this->removeCleanupPath($package, $realpath);
            }
        };

        try {
            $promise = $this->extract($package, $fileName, $temporaryDir);
        } catch (\Exception $e) {
            $cleanup();
            throw $e;
        }

        return $promise->then(function () use ($package, $filesystem, $fileName, $temporaryDir, $path): \React\Promise\PromiseInterface {
            if (file_exists($fileName)) {
                $filesystem->unlink($fileName);
            }

            /**
             * Returns the folder content, excluding .DS_Store
             *
             * @param  string         $dir Directory
             * @return \SplFileInfo[]
             */
            $getFolderContent = static function ($dir): array {
                $finder = Finder::create()
                    ->ignoreVCS(false)
                    ->ignoreDotFiles(false)
                    ->notName('.DS_Store')
                    ->depth(0)
                    ->in($dir);

                return iterator_to_array($finder);
            };
            $renameRecursively = null;
            /**
             * Renames (and recursively merges if needed) a folder into another one
             *
             * For custom installers, where packages may share paths, and given Composer 2's parallelism, we need to make sure
             * that the source directory gets merged into the target one if the target exists. Otherwise rename() by default would
             * put the source into the target e.g. src/ => target/src/ (assuming target exists) instead of src/ => target/
             *
             * @param  string $from Directory
             * @param  string $to   Directory
             * @return void
             */
            $renameRecursively = static function ($from, $to) use ($filesystem, $getFolderContent, $package, &$renameRecursively) {
                $contentDir = $getFolderContent($from);

                // move files back out of the temp dir
                foreach ($contentDir as $file) {
                    $file = (string) $file;
                    if (is_dir($to . '/' . basename($file))) {
                        if (!is_dir($file)) {
                            throw new \RuntimeException('Installing '.$package.' would lead to overwriting the '.$to.'/'.basename($file).' directory with a file from the package, invalid operation.');
                        }
                        $renameRecursively($file, $to . '/' . basename($file));
                    } else {
                        $filesystem->rename($file, $to . '/' . basename($file));
                    }
                }
            };

            $renameAsOne = false;
            if (!file_exists($path)) {
                $renameAsOne = true;
            } elseif ($filesystem->isDirEmpty($path)) {
                try {
                    if ($filesystem->removeDirectoryPhp($path)) {
                        $renameAsOne = true;
                    }
                } catch (\RuntimeException $e) {
                    // ignore error, and simply do not renameAsOne
                }
            }

            $contentDir = $getFolderContent($temporaryDir);
            $singleDirAtTopLevel = 1 === count($contentDir) && is_dir((string) reset($contentDir));

            if ($renameAsOne) {
                // if the target $path is clear, we can rename the whole package in one go instead of looping over the contents
                if ($singleDirAtTopLevel) {
                    $extractedDir = (string) reset($contentDir);
                } else {
                    $extractedDir = $temporaryDir;
                }
                $filesystem->rename($extractedDir, $path);
            } else {
                // only one dir in the archive, extract its contents out of it
                $from = $temporaryDir;
                if ($singleDirAtTopLevel) {
                    $from = (string) reset($contentDir);
                }

                $renameRecursively($from, $path);
            }

            $promise = $filesystem->removeDirectoryAsync($temporaryDir);

            return $promise->then(function () use ($package, $path, $temporaryDir) {
                $this->removeCleanupPath($package, $temporaryDir);
                $this->removeCleanupPath($package, $path);
            });
        }, static function ($e) use ($cleanup) {
            $cleanup();

            throw $e;
        });
    }

    /**
     * @inheritDoc
     */
    protected function getInstallOperationAppendix(PackageInterface $package, string $path): string
    {
        return ': Extracting archive';
    }

    /**
     * Extract file to directory
     *
     * @param string $file Extracted file
     * @param string $path Directory
     * @phpstan-return PromiseInterface<void|null>
     *
     * @throws \UnexpectedValueException If can not extract downloaded file to path
     */
    abstract protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface;
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use Composer\Util\Platform;
use React\Promise\PromiseInterface;
use Composer\Package\PackageInterface;
use Composer\Pcre\Preg;
use Composer\Util\ProcessExecutor;
use RuntimeException;

/**
 * @author BohwaZ <http://bohwaz.net/>
 */
class FossilDownloader extends VcsDownloader
{
    /**
     * @inheritDoc
     */
    protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null): PromiseInterface
    {
        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    protected function doInstall(PackageInterface $package, string $path, string $url): PromiseInterface
    {
        // Ensure we are allowed to use this URL by config
        $this->config->prohibitUrlByConfig($url, $this->io);

        $repoFile = $path . '.fossil';
        $realPath = Platform::realpath($path);

        $this->io->writeError("Cloning ".$package->getSourceReference());
        $this->execute(['fossil', 'clone', '--', $url, $repoFile]);
        $this->execute(['fossil', 'open', '--nested', '--', $repoFile], $realPath);
        $this->execute(['fossil', 'update', '--', (string) $package->getSourceReference()], $realPath);

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface
    {
        // Ensure we are allowed to use this URL by config
        $this->config->prohibitUrlByConfig($url, $this->io);

        $this->io->writeError(" Updating to ".$target->getSourceReference());

        if (!$this->hasMetadataRepository($path)) {
            throw new \RuntimeException('The .fslckout file is missing from '.$path.', see https://getcomposer.org/commit-deps for more information');
        }

        $realPath = Platform::realpath($path);
        $this->execute(['fossil', 'pull'], $realPath);
        $this->execute(['fossil', 'up', (string) $target->getSourceReference()], $realPath);

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function getLocalChanges(PackageInterface $package, string $path): ?string
    {
        if (!$this->hasMetadataRepository($path)) {
            return null;
        }

        $this->process->execute(['fossil', 'changes'], $output, Platform::realpath($path));

        $output = trim($output);

        return strlen($output) > 0 ? $output : null;
    }

    /**
     * @inheritDoc
     */
    protected function getCommitLogs(string $fromReference, string $toReference, string $path): string
    {
        $this->execute(['fossil', 'timeline', '-t', 'ci', '-W', '0', '-n', '0', 'before', $toReference], Platform::realpath($path), $output);

        $log = '';
        $match = '/\d\d:\d\d:\d\d\s+\[' . $toReference . '\]/';

        foreach ($this->process->splitLines($output) as $line) {
            if (Preg::isMatch($match, $line)) {
                break;
            }
            $log .= $line;
        }

        return $log;
    }

    /**
     * @param non-empty-list<string> $command
     * @throws \RuntimeException
     */
    private function execute(array $command, ?string $cwd = null, ?string &$output = null): void
    {
        if (0 !== $this->process->execute($command, $output, $cwd)) {
            throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput());
        }
    }

    /**
     * @inheritDoc
     */
    protected function hasMetadataRepository(string $path): bool
    {
        return is_file($path . '/.fslckout') || is_file($path . '/_FOSSIL_');
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use Composer\Config;
use Composer\Package\Dumper\ArrayDumper;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionGuesser;
use Composer\Package\Version\VersionParser;
use Composer\Util\ProcessExecutor;
use Composer\IO\IOInterface;
use Composer\Util\Filesystem;
use React\Promise\PromiseInterface;
use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\UninstallOperation;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterface, VcsCapableDownloaderInterface
{
    /** @var IOInterface */
    protected $io;
    /** @var Config */
    protected $config;
    /** @var ProcessExecutor */
    protected $process;
    /** @var Filesystem */
    protected $filesystem;
    /** @var array<string, true> */
    protected $hasCleanedChanges = [];

    public function __construct(IOInterface $io, Config $config, ?ProcessExecutor $process = null, ?Filesystem $fs = null)
    {
        $this->io = $io;
        $this->config = $config;
        $this->process = $process ?? new ProcessExecutor($io);
        $this->filesystem = $fs ?? new Filesystem($this->process);
    }

    /**
     * @inheritDoc
     */
    public function getInstallationSource(): string
    {
        return 'source';
    }

    /**
     * @inheritDoc
     */
    public function download(PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface
    {
        if (!$package->getSourceReference()) {
            throw new \InvalidArgumentException('Package '.$package->getPrettyName().' is missing reference information');
        }

        $urls = $this->prepareUrls($package->getSourceUrls());

        while ($url = array_shift($urls)) {
            try {
                return $this->doDownload($package, $path, $url, $prevPackage);
            } catch (\Exception $e) {
                // rethrow phpunit exceptions to avoid hard to debug bug failures
                if ($e instanceof \PHPUnit\Framework\Exception) {
                    throw $e;
                }
                if ($this->io->isDebug()) {
                    $this->io->writeError('Failed: ['.get_class($e).'] '.$e->getMessage());
                } elseif (count($urls)) {
                    $this->io->writeError('    Failed, trying the next URL');
                }
                if (!count($urls)) {
                    throw $e;
                }
            }
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function prepare(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface
    {
        if ($type === 'update') {
            $this->cleanChanges($prevPackage, $path, true);
            $this->hasCleanedChanges[$prevPackage->getUniqueName()] = true;
        } elseif ($type === 'install') {
            $this->filesystem->emptyDirectory($path);
        } elseif ($type === 'uninstall') {
            $this->cleanChanges($package, $path, false);
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function cleanup(string $type, PackageInterface $package, string $path, ?PackageInterface $prevPackage = null): PromiseInterface
    {
        if ($type === 'update' && isset($this->hasCleanedChanges[$prevPackage->getUniqueName()])) {
            $this->reapplyChanges($path);
            unset($this->hasCleanedChanges[$prevPackage->getUniqueName()]);
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function install(PackageInterface $package, string $path): PromiseInterface
    {
        if (!$package->getSourceReference()) {
            throw new \InvalidArgumentException('Package '.$package->getPrettyName().' is missing reference information');
        }

        $this->io->writeError("  - " . InstallOperation::format($package).': ', false);

        $urls = $this->prepareUrls($package->getSourceUrls());
        while ($url = array_shift($urls)) {
            try {
                $this->doInstall($package, $path, $url);
                break;
            } catch (\Exception $e) {
                // rethrow phpunit exceptions to avoid hard to debug bug failures
                if ($e instanceof \PHPUnit\Framework\Exception) {
                    throw $e;
                }
                if ($this->io->isDebug()) {
                    $this->io->writeError('Failed: ['.get_class($e).'] '.$e->getMessage());
                } elseif (count($urls)) {
                    $this->io->writeError('    Failed, trying the next URL');
                }
                if (!count($urls)) {
                    throw $e;
                }
            }
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function update(PackageInterface $initial, PackageInterface $target, string $path): PromiseInterface
    {
        if (!$target->getSourceReference()) {
            throw new \InvalidArgumentException('Package '.$target->getPrettyName().' is missing reference information');
        }

        $this->io->writeError("  - " . UpdateOperation::format($initial, $target).': ', false);

        $urls = $this->prepareUrls($target->getSourceUrls());

        $exception = null;
        while ($url = array_shift($urls)) {
            try {
                $this->doUpdate($initial, $target, $path, $url);

                $exception = null;
                break;
            } catch (\Exception $exception) {
                // rethrow phpunit exceptions to avoid hard to debug bug failures
                if ($exception instanceof \PHPUnit\Framework\Exception) {
                    throw $exception;
                }
                if ($this->io->isDebug()) {
                    $this->io->writeError('Failed: ['.get_class($exception).'] '.$exception->getMessage());
                } elseif (count($urls)) {
                    $this->io->writeError('    Failed, trying the next URL');
                }
            }
        }

        // print the commit logs if in verbose mode and VCS metadata is present
        // because in case of missing metadata code would trigger another exception
        if (!$exception && $this->io->isVerbose() && $this->hasMetadataRepository($path)) {
            $message = 'Pulling in changes:';
            $logs = $this->getCommitLogs($initial->getSourceReference(), $target->getSourceReference(), $path);

            if ('' === trim($logs)) {
                $message = 'Rolling back changes:';
                $logs = $this->getCommitLogs($target->getSourceReference(), $initial->getSourceReference(), $path);
            }

            if ('' !== trim($logs)) {
                $logs = implode("\n", array_map(static function ($line): string {
                    return '      ' . $line;
                }, explode("\n", $logs)));

                // escape angle brackets for proper output in the console
                $logs = str_replace('<', '\<', $logs);

                $this->io->writeError('    '.$message);
                $this->io->writeError($logs);
            }
        }

        if (!$urls && $exception) {
            throw $exception;
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function remove(PackageInterface $package, string $path): PromiseInterface
    {
        $this->io->writeError("  - " . UninstallOperation::format($package));

        $promise = $this->filesystem->removeDirectoryAsync($path);

        return $promise->then(static function (bool $result) use ($path) {
            if (!$result) {
                throw new \RuntimeException('Could not completely delete '.$path.', aborting.');
            }
        });
    }

    /**
     * @inheritDoc
     */
    public function getVcsReference(PackageInterface $package, string $path): ?string
    {
        $parser = new VersionParser;
        $guesser = new VersionGuesser($this->config, $this->process, $parser, $this->io);
        $dumper = new ArrayDumper;

        $packageConfig = $dumper->dump($package);
        if ($packageVersion = $guesser->guessVersion($packageConfig, $path)) {
            return $packageVersion['commit'];
        }

        return null;
    }

    /**
     * Prompt the user to check if changes should be stashed/removed or the operation aborted
     *
     * @param  bool              $update  if true (update) the changes can be stashed and reapplied after an update,
     *                                    if false (remove) the changes should be assumed to be lost if the operation is not aborted
     *
     * @throws \RuntimeException in case the operation must be aborted
     * @phpstan-return PromiseInterface<void|null>
     */
    protected function cleanChanges(PackageInterface $package, string $path, bool $update): PromiseInterface
    {
        // the default implementation just fails if there are any changes, override in child classes to provide stash-ability
        if (null !== $this->getLocalChanges($package, $path)) {
            throw new \RuntimeException('Source directory ' . $path . ' has uncommitted changes.');
        }

        return \React\Promise\resolve(null);
    }

    /**
     * Reapply previously stashes changes if applicable, only called after an update (regardless if successful or not)
     *
     * @throws \RuntimeException in case the operation must be aborted or the patch does not apply cleanly
     */
    protected function reapplyChanges(string $path): void
    {
    }

    /**
     * Downloads data needed to run an install/update later
     *
     * @param PackageInterface      $package     package instance
     * @param string                $path        download path
     * @param string                $url         package url
     * @param PackageInterface|null $prevPackage previous package (in case of an update)
     * @phpstan-return PromiseInterface<void|null>
     */
    abstract protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null): PromiseInterface;

    /**
     * Downloads specific package into specific folder.
     *
     * @param PackageInterface $package package instance
     * @param string           $path    download path
     * @param string           $url     package url
     * @phpstan-return PromiseInterface<void|null>
     */
    abstract protected function doInstall(PackageInterface $package, string $path, string $url): PromiseInterface;

    /**
     * Updates specific package in specific folder from initial to target version.
     *
     * @param PackageInterface $initial initial package
     * @param PackageInterface $target  updated package
     * @param string           $path    download path
     * @param string           $url     package url
     * @phpstan-return PromiseInterface<void|null>
     */
    abstract protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface;

    /**
     * Fetches the commit logs between two commits
     *
     * @param  string $fromReference the source reference
     * @param  string $toReference   the target reference
     * @param  string $path          the package path
     */
    abstract protected function getCommitLogs(string $fromReference, string $toReference, string $path): string;

    /**
     * Checks if VCS metadata repository has been initialized
     * repository example: .git|.svn|.hg
     */
    abstract protected function hasMetadataRepository(string $path): bool;

    /**
     * @param string[] $urls
     *
     * @return string[]
     */
    private function prepareUrls(array $urls): array
    {
        foreach ($urls as $index => $url) {
            if (Filesystem::isLocalPath($url)) {
                // realpath() below will not understand
                // url that starts with "file://"
                $fileProtocol = 'file://';
                $isFileProtocol = false;
                if (0 === strpos($url, $fileProtocol)) {
                    $url = substr($url, strlen($fileProtocol));
                    $isFileProtocol = true;
                }

                // realpath() below will not understand %20 spaces etc.
                if (false !== strpos($url, '%')) {
                    $url = rawurldecode($url);
                }

                $urls[$index] = realpath($url);

                if ($isFileProtocol) {
                    $urls[$index] = $fileProtocol . $urls[$index];
                }
            }
        }

        return $urls;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use React\Promise\PromiseInterface;
use Composer\Package\PackageInterface;

/**
 * Downloader for phar files
 *
 * @author Kirill chEbba Chebunin <iam@chebba.org>
 */
class PharDownloader extends ArchiveDownloader
{
    /**
     * @inheritDoc
     */
    protected function extract(PackageInterface $package, string $file, string $path): PromiseInterface
    {
        // Can throw an UnexpectedValueException
        $archive = new \Phar($file);
        $archive->extractTo($path, null, true);
        /* TODO: handle openssl signed phars
         * https://github.com/composer/composer/pull/33#issuecomment-2250768
         * https://github.com/koto/phar-util
         * http://blog.kotowicz.net/2010/08/hardening-php-how-to-securely-include.html
         */

        return \React\Promise\resolve(null);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

class MaxFileSizeExceededException extends TransportException
{
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

/**
 * Exception thrown when issues exist on local filesystem
 *
 * @author Javier Spagnoletti <jspagnoletti@javierspagnoletti.com.ar>
 */
class FilesystemException extends \Exception
{
    public function __construct(string $message = '', int $code = 0, ?\Exception $previous = null)
    {
        parent::__construct("Filesystem exception: \n".$message, $code, $previous);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use Composer\Package\PackageInterface;

/**
 * ChangeReport interface.
 *
 * @author Sascha Egerer <sascha.egerer@dkd.de>
 */
interface ChangeReportInterface
{
    /**
     * Checks for changes to the local copy
     *
     * @param  PackageInterface $package package instance
     * @param  string           $path    package directory
     * @return string|null      changes or null
     */
    public function getLocalChanges(PackageInterface $package, string $path): ?string;
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Pcre\Preg;
use Composer\Util\Filesystem;
use Composer\Util\Git as GitUtil;
use Composer\Util\Url;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Cache;
use React\Promise\PromiseInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
{
    /**
     * @var bool[]
     * @phpstan-var array<string, bool>
     */
    private $hasStashedChanges = [];
    /**
     * @var bool[]
     * @phpstan-var array<string, bool>
     */
    private $hasDiscardedChanges = [];
    /**
     * @var GitUtil
     */
    private $gitUtil;
    /**
     * @var array
     * @phpstan-var array<int, array<string, bool>>
     */
    private $cachedPackages = [];

    public function __construct(IOInterface $io, Config $config, ?ProcessExecutor $process = null, ?Filesystem $fs = null)
    {
        parent::__construct($io, $config, $process, $fs);
        $this->gitUtil = new GitUtil($this->io, $this->config, $this->process, $this->filesystem);
    }

    /**
     * @inheritDoc
     */
    protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null): PromiseInterface
    {
        // Do not create an extra local cache when repository is already local
        if (Filesystem::isLocalPath($url)) {
            return \React\Promise\resolve(null);
        }

        GitUtil::cleanEnv();

        $cachePath = $this->config->get('cache-vcs-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($url)).'/';
        $gitVersion = GitUtil::getVersion($this->process);

        // --dissociate option is only available since git 2.3.0-rc0
        if ($gitVersion && version_compare($gitVersion, '2.3.0-rc0', '>=') && Cache::isUsable($cachePath)) {
            $this->io->writeError("  - Syncing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>) into cache");
            $this->io->writeError(sprintf('    Cloning to cache at %s', $cachePath), true, IOInterface::DEBUG);
            $ref = $package->getSourceReference();
            if ($this->gitUtil->fetchRefOrSyncMirror($url, $cachePath, $ref, $package->getPrettyVersion()) && is_dir($cachePath)) {
                $this->cachedPackages[$package->getId()][$ref] = true;
            }
        } elseif (null === $gitVersion) {
            throw new \RuntimeException('git was not found in your PATH, skipping source download');
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    protected function doInstall(PackageInterface $package, string $path, string $url): PromiseInterface
    {
        GitUtil::cleanEnv();
        $path = $this->normalizePath($path);
        $cachePath = $this->config->get('cache-vcs-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($url)).'/';
        $ref = $package->getSourceReference();

        if (!empty($this->cachedPackages[$package->getId()][$ref])) {
            $msg = "Cloning ".$this->getShortHash($ref).' from cache';

            $cloneFlags = ['--dissociate', '--reference', $cachePath];
            $transportOptions = $package->getTransportOptions();
            if (isset($transportOptions['git']['single_use_clone']) && $transportOptions['git']['single_use_clone']) {
                $cloneFlags = [];
            }

            $commands = [
                array_merge(['git', 'clone', '--no-checkout', $cachePath, $path], $cloneFlags),
                ['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%'],
                ['git', 'remote', 'add', 'composer', '--', '%sanitizedUrl%'],
            ];
        } else {
            $msg = "Cloning ".$this->getShortHash($ref);
            $commands = [
                array_merge(['git', 'clone', '--no-checkout', '--', '%url%', $path]),
                ['git', 'remote', 'add', 'composer', '--', '%url%'],
                ['git', 'fetch', 'composer'],
                ['git', 'remote', 'set-url', 'origin', '--', '%sanitizedUrl%'],
                ['git', 'remote', 'set-url', 'composer', '--', '%sanitizedUrl%'],
            ];
            if (Platform::getEnv('COMPOSER_DISABLE_NETWORK')) {
                throw new \RuntimeException('The required git reference for '.$package->getName().' is not in cache and network is disabled, aborting');
            }
        }

        $this->io->writeError($msg);

        $this->gitUtil->runCommands($commands, $url, $path, true);

        $sourceUrl = $package->getSourceUrl();
        if ($url !== $sourceUrl && $sourceUrl !== null) {
            $this->updateOriginUrl($path, $sourceUrl);
        } else {
            $this->setPushUrl($path, $url);
        }

        if ($newRef = $this->updateToCommit($package, $path, (string) $ref, $package->getPrettyVersion())) {
            if ($package->getDistReference() === $package->getSourceReference()) {
                $package->setDistReference($newRef);
            }
            $package->setSourceReference($newRef);
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface
    {
        GitUtil::cleanEnv();
        $path = $this->normalizePath($path);
        if (!$this->hasMetadataRepository($path)) {
            throw new \RuntimeException('The .git directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information');
        }

        $cachePath = $this->config->get('cache-vcs-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($url)).'/';
        $ref = $target->getSourceReference();

        if (!empty($this->cachedPackages[$target->getId()][$ref])) {
            $msg = "Checking out ".$this->getShortHash($ref).' from cache';
            $remoteUrl = $cachePath;
        } else {
            $msg = "Checking out ".$this->getShortHash($ref);
            $remoteUrl = '%url%';
            if (Platform::getEnv('COMPOSER_DISABLE_NETWORK')) {
                throw new \RuntimeException('The required git reference for '.$target->getName().' is not in cache and network is disabled, aborting');
            }
        }

        $this->io->writeError($msg);

        if (0 !== $this->process->execute(['git', 'rev-parse', '--quiet', '--verify', $ref.'^{commit}'], $output, $path)) {
            $commands = [
                ['git', 'remote', 'set-url', 'composer', '--', $remoteUrl],
                ['git', 'fetch', 'composer'],
                ['git', 'fetch', '--tags', 'composer'],
            ];

            $this->gitUtil->runCommands($commands, $url, $path);
        }

        $command = ['git', 'remote', 'set-url', 'composer', '--', '%sanitizedUrl%'];
        $this->gitUtil->runCommands([$command], $url, $path);

        if ($newRef = $this->updateToCommit($target, $path, (string) $ref, $target->getPrettyVersion())) {
            if ($target->getDistReference() === $target->getSourceReference()) {
                $target->setDistReference($newRef);
            }
            $target->setSourceReference($newRef);
        }

        $updateOriginUrl = false;
        if (
            0 === $this->process->execute(['git', 'remote', '-v'], $output, $path)
            && Preg::isMatch('{^origin\s+(?P<url>\S+)}m', $output, $originMatch)
            && Preg::isMatch('{^composer\s+(?P<url>\S+)}m', $output, $composerMatch)
        ) {
            if ($originMatch['url'] === $composerMatch['url'] && $composerMatch['url'] !== $target->getSourceUrl()) {
                $updateOriginUrl = true;
            }
        }
        if ($updateOriginUrl && $target->getSourceUrl() !== null) {
            $this->updateOriginUrl($path, $target->getSourceUrl());
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function getLocalChanges(PackageInterface $package, string $path): ?string
    {
        GitUtil::cleanEnv();
        if (!$this->hasMetadataRepository($path)) {
            return null;
        }

        $command = ['git', 'status', '--porcelain', '--untracked-files=no'];
        if (0 !== $this->process->execute($command, $output, $path)) {
            throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput());
        }

        $output = trim($output);

        return strlen($output) > 0 ? $output : null;
    }

    public function getUnpushedChanges(PackageInterface $package, string $path): ?string
    {
        GitUtil::cleanEnv();
        $path = $this->normalizePath($path);
        if (!$this->hasMetadataRepository($path)) {
            return null;
        }

        $command = ['git', 'show-ref', '--head', '-d'];
        if (0 !== $this->process->execute($command, $output, $path)) {
            throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput());
        }

        $refs = trim($output);
        if (!Preg::isMatchStrictGroups('{^([a-f0-9]+) HEAD$}mi', $refs, $match)) {
            // could not match the HEAD for some reason
            return null;
        }

        $headRef = $match[1];
        if (!Preg::isMatchAllStrictGroups('{^'.preg_quote($headRef).' refs/heads/(.+)$}mi', $refs, $matches)) {
            // not on a branch, we are either on a not-modified tag or some sort of detached head, so skip this
            return null;
        }

        $candidateBranches = $matches[1];
        // use the first match as branch name for now
        $branch = $candidateBranches[0];
        $unpushedChanges = null;
        $branchNotFoundError = false;

        // do two passes, as if we find anything we want to fetch and then re-try
        for ($i = 0; $i <= 1; $i++) {
            $remoteBranches = [];

            // try to find matching branch names in remote repos
            foreach ($candidateBranches as $candidate) {
                if (Preg::isMatchAllStrictGroups('{^[a-f0-9]+ refs/remotes/((?:[^/]+)/'.preg_quote($candidate).')$}mi', $refs, $matches)) {
                    foreach ($matches[1] as $match) {
                        $branch = $candidate;
                        $remoteBranches[] = $match;
                    }
                    break;
                }
            }

            // if it doesn't exist, then we assume it is an unpushed branch
            // this is bad as we have no reference point to do a diff so we just bail listing
            // the branch as being unpushed
            if (count($remoteBranches) === 0) {
                $unpushedChanges = 'Branch ' . $branch . ' could not be found on any remote and appears to be unpushed';
                $branchNotFoundError = true;
            } else {
                // if first iteration found no remote branch but it has now found some, reset $unpushedChanges
                // so we get the real diff output no matter its length
                if ($branchNotFoundError) {
                    $unpushedChanges = null;
                }
                foreach ($remoteBranches as $remoteBranch) {
                    $command = ['git', 'diff', '--name-status', $remoteBranch.'...'.$branch, '--'];
                    if (0 !== $this->process->execute($command, $output, $path)) {
                        throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput());
                    }

                    $output = trim($output);
                    // keep the shortest diff from all remote branches we compare against
                    if ($unpushedChanges === null || strlen($output) < strlen($unpushedChanges)) {
                        $unpushedChanges = $output;
                    }
                }
            }

            // first pass and we found unpushed changes, fetch from all remotes to make sure we have up to date
            // remotes and then try again as outdated remotes can sometimes cause false-positives
            if ($unpushedChanges && $i === 0) {
                $this->process->execute(['git', 'fetch', '--all'], $output, $path);

                // update list of refs after fetching
                $command = ['git', 'show-ref', '--head', '-d'];
                if (0 !== $this->process->execute($command, $output, $path)) {
                    throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput());
                }
                $refs = trim($output);
            }

            // abort after first pass if we didn't find anything
            if (!$unpushedChanges) {
                break;
            }
        }

        return $unpushedChanges;
    }

    /**
     * @inheritDoc
     */
    protected function cleanChanges(PackageInterface $package, string $path, bool $update): PromiseInterface
    {
        GitUtil::cleanEnv();
        $path = $this->normalizePath($path);

        $unpushed = $this->getUnpushedChanges($package, $path);
        if ($unpushed && ($this->io->isInteractive() || $this->config->get('discard-changes') !== true)) {
            throw new \RuntimeException('Source directory ' . $path . ' has unpushed changes on the current branch: '."\n".$unpushed);
        }

        if (null === ($changes = $this->getLocalChanges($package, $path))) {
            return \React\Promise\resolve(null);
        }

        if (!$this->io->isInteractive()) {
            $discardChanges = $this->config->get('discard-changes');
            if (true === $discardChanges) {
                return $this->discardChanges($path);
            }
            if ('stash' === $discardChanges) {
                if (!$update) {
                    return parent::cleanChanges($package, $path, $update);
                }

                return $this->stashChanges($path);
            }

            return parent::cleanChanges($package, $path, $update);
        }

        $changes = array_map(static function ($elem): string {
            return '    '.$elem;
        }, Preg::split('{\s*\r?\n\s*}', $changes));
        $this->io->writeError('    <error>'.$package->getPrettyName().' has modified files:</error>');
        $this->io->writeError(array_slice($changes, 0, 10));
        if (count($changes) > 10) {
            $this->io->writeError('    <info>' . (count($changes) - 10) . ' more files modified, choose "v" to view the full list</info>');
        }

        while (true) {
            switch ($this->io->ask('    <info>Discard changes [y,n,v,d,'.($update ? 's,' : '').'?]?</info> ', '?')) {
                case 'y':
                    $this->discardChanges($path);
                    break 2;

                case 's':
                    if (!$update) {
                        goto help;
                    }

                    $this->stashChanges($path);
                    break 2;

                case 'n':
                    throw new \RuntimeException('Update aborted');

                case 'v':
                    $this->io->writeError($changes);
                    break;

                case 'd':
                    $this->viewDiff($path);
                    break;

                case '?':
                default:
                    help :
                    $this->io->writeError([
                        '    y - discard changes and apply the '.($update ? 'update' : 'uninstall'),
                        '    n - abort the '.($update ? 'update' : 'uninstall').' and let you manually clean things up',
                        '    v - view modified files',
                        '    d - view local modifications (diff)',
                    ]);
                    if ($update) {
                        $this->io->writeError('    s - stash changes and try to reapply them after the update');
                    }
                    $this->io->writeError('    ? - print help');
                    break;
            }
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    protected function reapplyChanges(string $path): void
    {
        $path = $this->normalizePath($path);
        if (!empty($this->hasStashedChanges[$path])) {
            unset($this->hasStashedChanges[$path]);
            $this->io->writeError('    <info>Re-applying stashed changes</info>');
            if (0 !== $this->process->execute(['git', 'stash', 'pop'], $output, $path)) {
                throw new \RuntimeException("Failed to apply stashed changes:\n\n".$this->process->getErrorOutput());
            }
        }

        unset($this->hasDiscardedChanges[$path]);
    }

    /**
     * Updates the given path to the given commit ref
     *
     * @throws \RuntimeException
     * @return null|string       if a string is returned, it is the commit reference that was checked out if the original could not be found
     */
    protected function updateToCommit(PackageInterface $package, string $path, string $reference, string $prettyVersion): ?string
    {
        $force = !empty($this->hasDiscardedChanges[$path]) || !empty($this->hasStashedChanges[$path]) ? ['-f'] : [];

        // This uses the "--" sequence to separate branch from file parameters.
        //
        // Otherwise git tries the branch name as well as file name.
        // If the non-existent branch is actually the name of a file, the file
        // is checked out.

        $branch = Preg::replace('{(?:^dev-|(?:\.x)?-dev$)}i', '', $prettyVersion);

        /**
         * @var \Closure(non-empty-list<string>): bool $execute
         * @phpstan-ignore varTag.nativeType
         */
        $execute = function (array $command) use (&$output, $path) {
            /** @var non-empty-list<string> $command */
            $output = '';

            return 0 === $this->process->execute($command, $output, $path);
        };

        $branches = null;
        if ($execute(['git', 'branch', '-r'])) {
            $branches = $output;
        }

        // check whether non-commitish are branches or tags, and fetch branches with the remote name
        $gitRef = $reference;
        if (!Preg::isMatch('{^[a-f0-9]{40}$}', $reference)
            && null !== $branches
            && Preg::isMatch('{^\s+composer/'.preg_quote($reference).'$}m', $branches)
        ) {
            $command1 = array_merge(['git', 'checkout'], $force, ['-B', $branch, 'composer/'.$reference, '--']);
            $command2 = ['git', 'reset', '--hard', 'composer/'.$reference, '--'];

            if ($execute($command1) && $execute($command2)) {
                return null;
            }
        }

        // try to checkout branch by name and then reset it so it's on the proper branch name
        if (Preg::isMatch('{^[a-f0-9]{40}$}', $reference)) {
            // add 'v' in front of the branch if it was stripped when generating the pretty name
            if (null !== $branches && !Preg::isMatch('{^\s+composer/'.preg_quote($branch).'$}m', $branches) && Preg::isMatch('{^\s+composer/v'.preg_quote($branch).'$}m', $branches)) {
                $branch = 'v' . $branch;
            }

            $command = ['git', 'checkout', $branch, '--'];
            $fallbackCommand = array_merge(['git', 'checkout'], $force, ['-B', $branch, 'composer/'.$branch, '--']);
            $resetCommand = ['git', 'reset', '--hard', $reference, '--'];

            if (($execute($command) || $execute($fallbackCommand)) && $execute($resetCommand)) {
                return null;
            }
        }

        $command1 = array_merge(['git', 'checkout'], $force, [$gitRef, '--']);
        $command2 = ['git', 'reset', '--hard', $gitRef, '--'];
        if ($execute($command1) && $execute($command2)) {
            return null;
        }

        $exceptionExtra = '';

        // reference was not found (prints "fatal: reference is not a tree: $ref")
        if (false !== strpos($this->process->getErrorOutput(), $reference)) {
            $this->io->writeError('    <warning>'.$reference.' is gone (history was rewritten?)</warning>');
            $exceptionExtra = "\nIt looks like the commit hash is not available in the repository, maybe ".($package->isDev() ? 'the commit was removed from the branch' : 'the tag was recreated').'? Run "composer update '.$package->getPrettyName().'" to resolve this.';
        }

        $command = implode(' ', $command1). ' && '.implode(' ', $command2);

        throw new \RuntimeException(Url::sanitize('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput() . $exceptionExtra));
    }

    protected function updateOriginUrl(string $path, string $url): void
    {
        $this->process->execute(['git', 'remote', 'set-url', 'origin', '--', $url], $output, $path);
        $this->setPushUrl($path, $url);
    }

    protected function setPushUrl(string $path, string $url): void
    {
        // set push url for github projects
        if (Preg::isMatch('{^(?:https?|git)://'.GitUtil::getGitHubDomainsRegex($this->config).'/([^/]+)/([^/]+?)(?:\.git)?$}', $url, $match)) {
            $protocols = $this->config->get('github-protocols');
            $pushUrl = 'git@'.$match[1].':'.$match[2].'/'.$match[3].'.git';
            if (!in_array('ssh', $protocols, true)) {
                $pushUrl = 'https://' . $match[1] . '/'.$match[2].'/'.$match[3].'.git';
            }
            $cmd = ['git', 'remote', 'set-url', '--push', 'origin', '--', $pushUrl];
            $this->process->execute($cmd, $ignoredOutput, $path);
        }
    }

    /**
     * @inheritDoc
     */
    protected function getCommitLogs(string $fromReference, string $toReference, string $path): string
    {
        $path = $this->normalizePath($path);
        $command = array_merge(['git', 'log', $fromReference.'..'.$toReference, '--pretty=format:%h - %an: %s'], GitUtil::getNoShowSignatureFlags($this->process));

        if (0 !== $this->process->execute($command, $output, $path)) {
            throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput());
        }

        return $output;
    }

    /**
     * @phpstan-return PromiseInterface<void|null>
     * @throws \RuntimeException
     */
    protected function discardChanges(string $path): PromiseInterface
    {
        $path = $this->normalizePath($path);
        if (0 !== $this->process->execute(['git', 'clean', '-df'], $output, $path)) {
            throw new \RuntimeException("Could not reset changes\n\n:".$output);
        }
        if (0 !== $this->process->execute(['git', 'reset', '--hard'], $output, $path)) {
            throw new \RuntimeException("Could not reset changes\n\n:".$output);
        }

        $this->hasDiscardedChanges[$path] = true;

        return \React\Promise\resolve(null);
    }

    /**
     * @phpstan-return PromiseInterface<void|null>
     * @throws \RuntimeException
     */
    protected function stashChanges(string $path): PromiseInterface
    {
        $path = $this->normalizePath($path);
        if (0 !== $this->process->execute(['git', 'stash', '--include-untracked'], $output, $path)) {
            throw new \RuntimeException("Could not stash changes\n\n:".$output);
        }

        $this->hasStashedChanges[$path] = true;

        return \React\Promise\resolve(null);
    }

    /**
     * @throws \RuntimeException
     */
    protected function viewDiff(string $path): void
    {
        $path = $this->normalizePath($path);
        if (0 !== $this->process->execute(['git', 'diff', 'HEAD'], $output, $path)) {
            throw new \RuntimeException("Could not view diff\n\n:".$output);
        }

        $this->io->writeError($output);
    }

    protected function normalizePath(string $path): string
    {
        if (Platform::isWindows() && strlen($path) > 0) {
            $basePath = $path;
            $removed = [];

            while (!is_dir($basePath) && $basePath !== '\\') {
                array_unshift($removed, basename($basePath));
                $basePath = dirname($basePath);
            }

            if ($basePath === '\\') {
                return $path;
            }

            $path = rtrim(realpath($basePath) . '/' . implode('/', $removed), '/');
        }

        return $path;
    }

    /**
     * @inheritDoc
     */
    protected function hasMetadataRepository(string $path): bool
    {
        $path = $this->normalizePath($path);

        return is_dir($path.'/.git');
    }

    protected function getShortHash(string $reference): string
    {
        if (!$this->io->isVerbose() && Preg::isMatch('{^[0-9a-f]{40}$}', $reference)) {
            return substr($reference, 0, 10);
        }

        return $reference;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use Composer\Package\PackageInterface;
use Composer\Pcre\Preg;
use Composer\Util\Svn as SvnUtil;
use Composer\Repository\VcsRepository;
use Composer\Util\ProcessExecutor;
use React\Promise\PromiseInterface;

/**
 * @author Ben Bieker <mail@ben-bieker.de>
 * @author Till Klampaeckel <till@php.net>
 */
class SvnDownloader extends VcsDownloader
{
    /** @var bool */
    protected $cacheCredentials = true;

    /**
     * @inheritDoc
     */
    protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null): PromiseInterface
    {
        SvnUtil::cleanEnv();
        $util = new SvnUtil($url, $this->io, $this->config, $this->process);
        if (null === $util->binaryVersion()) {
            throw new \RuntimeException('svn was not found in your PATH, skipping source download');
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    protected function doInstall(PackageInterface $package, string $path, string $url): PromiseInterface
    {
        SvnUtil::cleanEnv();
        $ref = $package->getSourceReference();

        $repo = $package->getRepository();
        if ($repo instanceof VcsRepository) {
            $repoConfig = $repo->getRepoConfig();
            if (array_key_exists('svn-cache-credentials', $repoConfig)) {
                $this->cacheCredentials = (bool) $repoConfig['svn-cache-credentials'];
            }
        }

        $this->io->writeError(" Checking out ".$package->getSourceReference());
        $this->execute($package, $url, ['svn', 'co'], sprintf("%s/%s", $url, $ref), null, $path);

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface
    {
        SvnUtil::cleanEnv();
        $ref = $target->getSourceReference();

        if (!$this->hasMetadataRepository($path)) {
            throw new \RuntimeException('The .svn directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information');
        }

        $util = new SvnUtil($url, $this->io, $this->config, $this->process);
        $flags = [];
        if (version_compare($util->binaryVersion(), '1.7.0', '>=')) {
            $flags[] = '--ignore-ancestry';
        }

        $this->io->writeError(" Checking out " . $ref);
        $this->execute($target, $url, array_merge(['svn', 'switch'], $flags), sprintf("%s/%s", $url, $ref), $path);

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function getLocalChanges(PackageInterface $package, string $path): ?string
    {
        if (!$this->hasMetadataRepository($path)) {
            return null;
        }

        $this->process->execute(['svn', 'status', '--ignore-externals'], $output, $path);

        return Preg::isMatch('{^ *[^X ] +}m', $output) ? $output : null;
    }

    /**
     * Execute an SVN command and try to fix up the process with credentials
     * if necessary.
     *
     * @param  string            $baseUrl Base URL of the repository
     * @param  non-empty-list<string> $command SVN command to run
     * @param  string            $url     SVN url
     * @param  string            $cwd     Working directory
     * @param  string            $path    Target for a checkout
     * @throws \RuntimeException
     */
    protected function execute(PackageInterface $package, string $baseUrl, array $command, string $url, ?string $cwd = null, ?string $path = null): string
    {
        $util = new SvnUtil($baseUrl, $this->io, $this->config, $this->process);
        $util->setCacheCredentials($this->cacheCredentials);
        try {
            return $util->execute($command, $url, $cwd, $path, $this->io->isVerbose());
        } catch (\RuntimeException $e) {
            throw new \RuntimeException(
                $package->getPrettyName().' could not be downloaded, '.$e->getMessage()
            );
        }
    }

    /**
     * @inheritDoc
     */
    protected function cleanChanges(PackageInterface $package, string $path, bool $update): PromiseInterface
    {
        if (null === ($changes = $this->getLocalChanges($package, $path))) {
            return \React\Promise\resolve(null);
        }

        if (!$this->io->isInteractive()) {
            if (true === $this->config->get('discard-changes')) {
                return $this->discardChanges($path);
            }

            return parent::cleanChanges($package, $path, $update);
        }

        $changes = array_map(static function ($elem): string {
            return '    '.$elem;
        }, Preg::split('{\s*\r?\n\s*}', $changes));
        $countChanges = count($changes);
        $this->io->writeError(sprintf('    <error>'.$package->getPrettyName().' has modified file%s:</error>', $countChanges === 1 ? '' : 's'));
        $this->io->writeError(array_slice($changes, 0, 10));
        if ($countChanges > 10) {
            $remainingChanges = $countChanges - 10;
            $this->io->writeError(
                sprintf(
                    '    <info>'.$remainingChanges.' more file%s modified, choose "v" to view the full list</info>',
                    $remainingChanges === 1 ? '' : 's'
                )
            );
        }

        while (true) {
            switch ($this->io->ask('    <info>Discard changes [y,n,v,?]?</info> ', '?')) {
                case 'y':
                    $this->discardChanges($path);
                    break 2;

                case 'n':
                    throw new \RuntimeException('Update aborted');

                case 'v':
                    $this->io->writeError($changes);
                    break;

                case '?':
                default:
                    $this->io->writeError([
                        '    y - discard changes and apply the '.($update ? 'update' : 'uninstall'),
                        '    n - abort the '.($update ? 'update' : 'uninstall').' and let you manually clean things up',
                        '    v - view modified files',
                        '    ? - print help',
                    ]);
                    break;
            }
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    protected function getCommitLogs(string $fromReference, string $toReference, string $path): string
    {
        if (Preg::isMatch('{@(\d+)$}', $fromReference) && Preg::isMatch('{@(\d+)$}', $toReference)) {
            // retrieve the svn base url from the checkout folder
            $command = ['svn', 'info', '--non-interactive', '--xml', '--', $path];
            if (0 !== $this->process->execute($command, $output, $path)) {
                throw new \RuntimeException(
                    'Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput()
                );
            }

            $urlPattern = '#<url>(.*)</url>#';
            if (Preg::isMatchStrictGroups($urlPattern, $output, $matches)) {
                $baseUrl = $matches[1];
            } else {
                throw new \RuntimeException(
                    'Unable to determine svn url for path '. $path
                );
            }

            // strip paths from references and only keep the actual revision
            $fromRevision = Preg::replace('{.*@(\d+)$}', '$1', $fromReference);
            $toRevision = Preg::replace('{.*@(\d+)$}', '$1', $toReference);

            $command = ['svn', 'log', '-r', $fromRevision.':'.$toRevision, '--incremental'];

            $util = new SvnUtil($baseUrl, $this->io, $this->config, $this->process);
            $util->setCacheCredentials($this->cacheCredentials);
            try {
                return $util->executeLocal($command, $path, null, $this->io->isVerbose());
            } catch (\RuntimeException $e) {
                throw new \RuntimeException(
                    'Failed to execute ' . implode(' ', $command) . "\n\n".$e->getMessage()
                );
            }
        }

        return "Could not retrieve changes between $fromReference and $toReference due to missing revision information";
    }

    /**
     * @phpstan-return PromiseInterface<void|null>
     */
    protected function discardChanges(string $path): PromiseInterface
    {
        if (0 !== $this->process->execute(['svn', 'revert', '-R', '.'], $output, $path)) {
            throw new \RuntimeException("Could not reset changes\n\n:".$this->process->getErrorOutput());
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    protected function hasMetadataRepository(string $path): bool
    {
        return is_dir($path.'/.svn');
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use React\Promise\PromiseInterface;
use Composer\Package\PackageInterface;
use Composer\Util\ProcessExecutor;
use Composer\Util\Hg as HgUtils;

/**
 * @author Per Bernhardt <plb@webfactory.de>
 */
class HgDownloader extends VcsDownloader
{
    /**
     * @inheritDoc
     */
    protected function doDownload(PackageInterface $package, string $path, string $url, ?PackageInterface $prevPackage = null): PromiseInterface
    {
        if (null === HgUtils::getVersion($this->process)) {
            throw new \RuntimeException('hg was not found in your PATH, skipping source download');
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    protected function doInstall(PackageInterface $package, string $path, string $url): PromiseInterface
    {
        $hgUtils = new HgUtils($this->io, $this->config, $this->process);

        $cloneCommand = static function (string $url) use ($path): array {
            return ['hg', 'clone', '--', $url, $path];
        };

        $hgUtils->runCommand($cloneCommand, $url, $path);

        $command = ['hg', 'up', '--', (string) $package->getSourceReference()];
        if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) {
            throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput());
        }

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    protected function doUpdate(PackageInterface $initial, PackageInterface $target, string $path, string $url): PromiseInterface
    {
        $hgUtils = new HgUtils($this->io, $this->config, $this->process);

        $ref = $target->getSourceReference();
        $this->io->writeError(" Updating to ".$target->getSourceReference());

        if (!$this->hasMetadataRepository($path)) {
            throw new \RuntimeException('The .hg directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information');
        }

        $command = static function ($url): array {
            return ['hg', 'pull', '--', $url];
        };
        $hgUtils->runCommand($command, $url, $path);

        $command = static function () use ($ref): array {
            return ['hg', 'up', '--', $ref];
        };
        $hgUtils->runCommand($command, $url, $path);

        return \React\Promise\resolve(null);
    }

    /**
     * @inheritDoc
     */
    public function getLocalChanges(PackageInterface $package, string $path): ?string
    {
        if (!is_dir($path.'/.hg')) {
            return null;
        }

        $this->process->execute(['hg', 'st'], $output, realpath($path));

        $output = trim($output);

        return strlen($output) > 0 ? $output : null;
    }

    /**
     * @inheritDoc
     */
    protected function getCommitLogs(string $fromReference, string $toReference, string $path): string
    {
        $command = ['hg', 'log', '-r', $fromReference.':'.$toReference, '--style', 'compact'];

        if (0 !== $this->process->execute($command, $output, realpath($path))) {
            throw new \RuntimeException('Failed to execute ' . implode(' ', $command) . "\n\n" . $this->process->getErrorOutput());
        }

        return $output;
    }

    /**
     * @inheritDoc
     */
    protected function hasMetadataRepository(string $path): bool
    {
        return is_dir($path . '/.hg');
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class TransportException extends \RuntimeException
{
    /** @var ?array<string> */
    protected $headers;
    /** @var ?string */
    protected $response;
    /** @var ?int */
    protected $statusCode;
    /** @var array<mixed> */
    protected $responseInfo = [];

    public function __construct(string $message = "", int $code = 400, ?\Throwable $previous = null)
    {
        parent::__construct($message, $code, $previous);
    }

    /**
     * @param array<string> $headers
     */
    public function setHeaders(array $headers): void
    {
        $this->headers = $headers;
    }

    /**
     * @return ?array<string>
     */
    public function getHeaders(): ?array
    {
        return $this->headers;
    }

    public function setResponse(?string $response): void
    {
        $this->response = $response;
    }

    /**
     * @return ?string
     */
    public function getResponse(): ?string
    {
        return $this->response;
    }

    /**
     * @param ?int $statusCode
     */
    public function setStatusCode($statusCode): void
    {
        $this->statusCode = $statusCode;
    }

    /**
     * @return ?int
     */
    public function getStatusCode(): ?int
    {
        return $this->statusCode;
    }

    /**
     * @return array<mixed>
     */
    public function getResponseInfo(): array
    {
        return $this->responseInfo;
    }

    /**
     * @param array<mixed> $responseInfo
     */
    public function setResponseInfo(array $responseInfo): void
    {
        $this->responseInfo = $responseInfo;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use Composer\Package\PackageInterface;
use Composer\IO\IOInterface;
use Composer\Pcre\Preg;
use Composer\Util\Filesystem;
use Composer\Exception\IrrecoverableDownloadException;
use React\Promise\PromiseInterface;

/**
 * Downloaders manager.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class DownloadManager
{
    /** @var IOInterface */
    private $io;
    /** @var bool */
    private $preferDist = false;
    /** @var bool */
    private $preferSource;
    /** @var array<string, string> */
    private $packagePreferences = [];
    /** @var Filesystem */
    private $filesystem;
    /** @var array<string, DownloaderInterface> */
    private $downloaders = [];

    /**
     * Initializes download manager.
     *
     * @param IOInterface     $io           The Input Output Interface
     * @param bool            $preferSource prefer downloading from source
     * @param Filesystem|null $filesystem   custom Filesystem object
     */
    public function __construct(IOInterface $io, bool $preferSource = false, ?Filesystem $filesystem = null)
    {
        $this->io = $io;
        $this->preferSource = $preferSource;
        $this->filesystem = $filesystem ?: new Filesystem();
    }

    /**
     * Makes downloader prefer source installation over the dist.
     *
     * @param  bool            $preferSource prefer downloading from source
     * @return DownloadManager
     */
    public function setPreferSource(bool $preferSource): self
    {
        $this->preferSource = $preferSource;

        return $this;
    }

    /**
     * Makes downloader prefer dist installation over the source.
     *
     * @param  bool            $preferDist prefer downloading from dist
     * @return DownloadManager
     */
    public function setPreferDist(bool $preferDist): self
    {
        $this->preferDist = $preferDist;

        return $this;
    }

    /**
     * Sets fine tuned preference settings for package level source/dist selection.
     *
     * @param array<string, string> $preferences array of preferences by package patterns
     *
     * @return DownloadManager
     */
    public function setPreferences(array $preferences): self
    {
        $this->packagePreferences = $preferences;

        return $this;
    }

    /**
     * Sets installer downloader for a specific installation type.
     *
     * @param  string              $type       installation type
     * @param  DownloaderInterface $downloader downloader instance
     * @return DownloadManager
     */
    public function setDownloader(string $type, DownloaderInterface $downloader): self
    {
        $type = strtolower($type);
        $this->downloaders[$type] = $downloader;

        return $this;
    }

    /**
     * Returns downloader for a specific installation type.
     *
     * @param  string                    $type installation type
     * @throws \InvalidArgumentException if downloader for provided type is not registered
     */
    public function getDownloader(string $type): DownloaderInterface
    {
        $type = strtolower($type);
        if (!isset($this->downloaders[$type])) {
            throw new \InvalidArgumentException(sprintf('Unknown downloader type: %s. Available types: %s.', $type, implode(', ', array_keys($this->downloaders))));
        }

        return $this->downloaders[$type];
    }

    /**
     * Returns downloader for already installed package.
     *
     * @param  PackageInterface          $package package instance
     * @throws \InvalidArgumentException if package has no installation source specified
     * @throws \LogicException           if specific downloader used to load package with
     *                                           wrong type
     */
    public function getDownloaderForPackage(PackageInterface $package): ?DownloaderInterface
    {
        $installationSource = $package->getInstallationSource();

        if ('metapackage' === $package->getType()) {
            return null;
        }

        if ('dist' === $installationSource) {
            $downloader = $this->getDownloader($package->getDistType());
        } elseif ('source' === $installationSource) {
            $downloader = $this->getDownloader($package->getSourceType());
        } else {
            throw new \InvalidArgumentException(
                'Package '.$package.' does not have an installation source set'
            );
        }

        if ($installationSource !== $downloader->getInstallationSource()) {
            throw new \LogicException(sprintf(
                'Downloader "%s" is a %s type downloader and can not be used to download %s for package %s',
                get_class($downloader),
                $downloader->getInstallationSource(),
                $installationSource,
                $package
            ));
        }

        return $downloader;
    }

    public function getDownloaderType(DownloaderInterface $downloader): string
    {
        return array_search($downloader, $this->downloaders);
    }

    /**
     * Downloads package into target dir.
     *
     * @param PackageInterface      $package     package instance
     * @param string                $targetDir   target dir
     * @param PackageInterface|null $prevPackage previous package instance in case of updates
     * @phpstan-return PromiseInterface<void|null>
     *
     * @throws \InvalidArgumentException if package have no urls to download from
     * @throws \RuntimeException
     */
    public function download(PackageInterface $package, string $targetDir, ?PackageInterface $prevPackage = null): PromiseInterface
    {
        $targetDir = $this->normalizeTargetDir($targetDir);
        $this->filesystem->ensureDirectoryExists(dirname($targetDir));

        $sources = $this->getAvailableSources($package, $prevPackage);

        $io = $this->io;

        $download = function ($retry = false) use (&$sources, $io, $package, $targetDir, &$download, $prevPackage) {
            $source = array_shift($sources);
            if ($retry) {
                $io->writeError('    <warning>Now trying to download from ' . $source . '</warning>');
            }
            $package->setInstallationSource($source);

            $downloader = $this->getDownloaderForPackage($package);
            if (!$downloader) {
                return \React\Promise\resolve(null);
            }

            $handleError = static function ($e) use ($sources, $source, $package, $io, $download) {
                if ($e instanceof \RuntimeException && !$e instanceof IrrecoverableDownloadException) {
                    if (!$sources) {
                        throw $e;
                    }

                    $io->writeError(
                        '    <warning>Failed to download '.
                        $package->getPrettyName().
                        ' from ' . $source . ': '.
                        $e->getMessage().'</warning>'
                    );

                    return $download(true);
                }

                throw $e;
            };

            try {
                $result = $downloader->download($package, $targetDir, $prevPackage);
            } catch (\Exception $e) {
                return $handleError($e);
            }

            $res = $result->then(static function ($res) {
                return $res;
            }, $handleError);

            return $res;
        };

        return $download();
    }

    /**
     * Prepares an operation execution
     *
     * @param string                $type        one of install/update/uninstall
     * @param PackageInterface      $package     package instance
     * @param string                $targetDir   target dir
     * @param PackageInterface|null $prevPackage previous package instance in case of updates
     * @phpstan-return PromiseInterface<void|null>
     */
    public function prepare(string $type, PackageInterface $package, string $targetDir, ?PackageInterface $prevPackage = null): PromiseInterface
    {
        $targetDir = $this->normalizeTargetDir($targetDir);
        $downloader = $this->getDownloaderForPackage($package);
        if ($downloader) {
            return $downloader->prepare($type, $package, $targetDir, $prevPackage);
        }

        return \React\Promise\resolve(null);
    }

    /**
     * Installs package into target dir.
     *
     * @param PackageInterface $package   package instance
     * @param string           $targetDir target dir
     * @phpstan-return PromiseInterface<void|null>
     *
     * @throws \InvalidArgumentException if package have no urls to download from
     * @throws \RuntimeException
     */
    public function install(PackageInterface $package, string $targetDir): PromiseInterface
    {
        $targetDir = $this->normalizeTargetDir($targetDir);
        $downloader = $this->getDownloaderForPackage($package);
        if ($downloader) {
            return $downloader->install($package, $targetDir);
        }

        return \React\Promise\resolve(null);
    }

    /**
     * Updates package from initial to target version.
     *
     * @param PackageInterface $initial   initial package version
     * @param PackageInterface $target    target package version
     * @param string           $targetDir target dir
     * @phpstan-return PromiseInterface<void|null>
     *
     * @throws \InvalidArgumentException if initial package is not installed
     */
    public function update(PackageInterface $initial, PackageInterface $target, string $targetDir): PromiseInterface
    {
        $targetDir = $this->normalizeTargetDir($targetDir);
        $downloader = $this->getDownloaderForPackage($target);
        $initialDownloader = $this->getDownloaderForPackage($initial);

        // no downloaders present means update from metapackage to metapackage, nothing to do
        if (!$initialDownloader && !$downloader) {
            return \React\Promise\resolve(null);
        }

        // if we have a downloader present before, but not after, the package became a metapackage and its files should be removed
        if (!$downloader) {
            return $initialDownloader->remove($initial, $targetDir);
        }

        $initialType = $this->getDownloaderType($initialDownloader);
        $targetType = $this->getDownloaderType($downloader);
        if ($initialType === $targetType) {
            try {
                return $downloader->update($initial, $target, $targetDir);
            } catch (\RuntimeException $e) {
                if (!$this->io->isInteractive()) {
                    throw $e;
                }
                $this->io->writeError('<error>    Update failed ('.$e->getMessage().')</error>');
                if (!$this->io->askConfirmation('    Would you like to try reinstalling the package instead [<comment>yes</comment>]? ')) {
                    throw $e;
                }
            }
        }

        // if downloader type changed, or update failed and user asks for reinstall,
        // we wipe the dir and do a new install instead of updating it
        $promise = $initialDownloader->remove($initial, $targetDir);

        return $promise->then(function ($res) use ($target, $targetDir): PromiseInterface {
            return $this->install($target, $targetDir);
        });
    }

    /**
     * Removes package from target dir.
     *
     * @param PackageInterface $package   package instance
     * @param string           $targetDir target dir
     * @phpstan-return PromiseInterface<void|null>
     */
    public function remove(PackageInterface $package, string $targetDir): PromiseInterface
    {
        $targetDir = $this->normalizeTargetDir($targetDir);
        $downloader = $this->getDownloaderForPackage($package);
        if ($downloader) {
            return $downloader->remove($package, $targetDir);
        }

        return \React\Promise\resolve(null);
    }

    /**
     * Cleans up a failed operation
     *
     * @param string                $type        one of install/update/uninstall
     * @param PackageInterface      $package     package instance
     * @param string                $targetDir   target dir
     * @param PackageInterface|null $prevPackage previous package instance in case of updates
     * @phpstan-return PromiseInterface<void|null>
     */
    public function cleanup(string $type, PackageInterface $package, string $targetDir, ?PackageInterface $prevPackage = null): PromiseInterface
    {
        $targetDir = $this->normalizeTargetDir($targetDir);
        $downloader = $this->getDownloaderForPackage($package);
        if ($downloader) {
            return $downloader->cleanup($type, $package, $targetDir, $prevPackage);
        }

        return \React\Promise\resolve(null);
    }

    /**
     * Determines the install preference of a package
     *
     * @param PackageInterface $package package instance
     */
    protected function resolvePackageInstallPreference(PackageInterface $package): string
    {
        foreach ($this->packagePreferences as $pattern => $preference) {
            $pattern = '{^'.str_replace('\\*', '.*', preg_quote($pattern)).'$}i';
            if (Preg::isMatch($pattern, $package->getName())) {
                if ('dist' === $preference || (!$package->isDev() && 'auto' === $preference)) {
                    return 'dist';
                }

                return 'source';
            }
        }

        return $package->isDev() ? 'source' : 'dist';
    }

    /**
     * @return string[]
     * @phpstan-return array<'dist'|'source'>&non-empty-array
     */
    private function getAvailableSources(PackageInterface $package, ?PackageInterface $prevPackage = null): array
    {
        $sourceType = $package->getSourceType();
        $distType = $package->getDistType();

        // add source before dist by default
        $sources = [];
        if ($sourceType) {
            $sources[] = 'source';
        }
        if ($distType) {
            $sources[] = 'dist';
        }

        if (empty($sources)) {
            throw new \InvalidArgumentException('Package '.$package.' must have a source or dist specified');
        }

        if (
            $prevPackage
            // if we are updating, we want to keep the same source as the previously installed package (if available in the new one)
            && in_array($prevPackage->getInstallationSource(), $sources, true)
            // unless the previous package was stable dist (by default) and the new package is dev, then we allow the new default to take over
            && !(!$prevPackage->isDev() && $prevPackage->getInstallationSource() === 'dist' && $package->isDev())
        ) {
            $prevSource = $prevPackage->getInstallationSource();
            usort($sources, static function ($a, $b) use ($prevSource): int {
                return $a === $prevSource ? -1 : 1;
            });

            return $sources;
        }

        // reverse sources in case dist is the preferred source for this package
        if (!$this->preferSource && ($this->preferDist || 'dist' === $this->resolvePackageInstallPreference($package))) {
            $sources = array_reverse($sources);
        }

        return $sources;
    }

    /**
     * Downloaders expect a /path/to/dir without trailing slash
     *
     * If any Installer provides a path with a trailing slash, this can cause bugs so make sure we remove them
     */
    private function normalizeTargetDir(string $dir): string
    {
        if ($dir === '\\' || $dir === '/') {
            return $dir;
        }

        return rtrim($dir, '\\/');
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use Composer\Package\PackageInterface;

/**
 * DVCS Downloader interface.
 *
 * @author James Titcumb <james@asgrim.com>
 */
interface DvcsDownloaderInterface
{
    /**
     * Checks for unpushed changes to a current branch
     *
     * @param  PackageInterface $package package instance
     * @param  string           $path    package directory
     * @return string|null      changes or null
     */
    public function getUnpushedChanges(PackageInterface $package, string $path): ?string;
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Question;

use Composer\Pcre\Preg;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Question\Question;

/**
 * Represents a yes/no question
 * Enforces strict responses rather than non-standard answers counting as default
 * Based on Symfony\Component\Console\Question\ConfirmationQuestion
 *
 * @author Theo Tonge <theo@theotonge.co.uk>
 */
class StrictConfirmationQuestion extends Question
{
    /** @var non-empty-string */
    private $trueAnswerRegex;
    /** @var non-empty-string */
    private $falseAnswerRegex;

    /**
     * Constructor.s
     *
     * @param string $question         The question to ask to the user
     * @param bool   $default          The default answer to return, true or false
     * @param non-empty-string $trueAnswerRegex  A regex to match the "yes" answer
     * @param non-empty-string $falseAnswerRegex A regex to match the "no" answer
     */
    public function __construct(string $question, bool $default = true, string $trueAnswerRegex = '/^y(?:es)?$/i', string $falseAnswerRegex = '/^no?$/i')
    {
        parent::__construct($question, $default);

        $this->trueAnswerRegex = $trueAnswerRegex;
        $this->falseAnswerRegex = $falseAnswerRegex;
        $this->setNormalizer($this->getDefaultNormalizer());
        $this->setValidator($this->getDefaultValidator());
    }

    /**
     * Returns the default answer normalizer.
     */
    private function getDefaultNormalizer(): callable
    {
        $default = $this->getDefault();
        $trueRegex = $this->trueAnswerRegex;
        $falseRegex = $this->falseAnswerRegex;

        return static function ($answer) use ($default, $trueRegex, $falseRegex) {
            if (is_bool($answer)) {
                return $answer;
            }
            if (empty($answer) && !empty($default)) {
                return $default;
            }

            if (Preg::isMatch($trueRegex, $answer)) {
                return true;
            }

            if (Preg::isMatch($falseRegex, $answer)) {
                return false;
            }

            return null;
        };
    }

    /**
     * Returns the default answer validator.
     */
    private function getDefaultValidator(): callable
    {
        return static function ($answer): bool {
            if (!is_bool($answer)) {
                throw new InvalidArgumentException('Please answer yes, y, no, or n.');
            }

            return $answer;
        };
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer;

use Composer\Package\Locker;
use Composer\Pcre\Preg;
use Composer\Plugin\PluginManager;
use Composer\Downloader\DownloadManager;
use Composer\Autoload\AutoloadGenerator;
use Composer\Package\Archiver\ArchiveManager;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Konstantin Kudryashiv <ever.zet@gmail.com>
 * @author Nils Adermann <naderman@naderman.de>
 */
class Composer extends PartialComposer
{
    /*
     * Examples of the following constants in the various configurations they can be in
     *
     * You are probably better off using Composer::getVersion() though as that will always return something usable
     *
     * releases (phar):
     * const VERSION = '1.8.2';
     * const BRANCH_ALIAS_VERSION = '';
     * const RELEASE_DATE = '2019-01-29 15:00:53';
     * const SOURCE_VERSION = '';
     *
     * snapshot builds (phar):
     * const VERSION = 'd3873a05650e168251067d9648845c220c50e2d7';
     * const BRANCH_ALIAS_VERSION = '1.9-dev';
     * const RELEASE_DATE = '2019-02-20 07:43:56';
     * const SOURCE_VERSION = '';
     *
     * source (git clone):
     * const VERSION = '@package_version@';
     * const BRANCH_ALIAS_VERSION = '@package_branch_alias_version@';
     * const RELEASE_DATE = '@release_date@';
     * const SOURCE_VERSION = '1.8-dev+source';
     *
     * @see getVersion()
     */
    public const VERSION = '2.8.11';
    public const BRANCH_ALIAS_VERSION = '';
    public const RELEASE_DATE = '2025-08-21 11:29:39';
    public const SOURCE_VERSION = '';

    /**
     * Version number of the internal composer-runtime-api package
     *
     * This is used to version features available to projects at runtime
     * like the platform-check file, the Composer\InstalledVersions class
     * and possibly others in the future.
     *
     * @var string
     */
    public const RUNTIME_API_VERSION = '2.2.2';

    public static function getVersion(): string
    {
        // no replacement done, this must be a source checkout
        if (self::VERSION === '@package_version'.'@') {
            return self::SOURCE_VERSION;
        }

        // we have a branch alias and version is a commit id, this must be a snapshot build
        if (self::BRANCH_ALIAS_VERSION !== '' && Preg::isMatch('{^[a-f0-9]{40}$}', self::VERSION)) {
            return self::BRANCH_ALIAS_VERSION.'+'.self::VERSION;
        }

        return self::VERSION;
    }

    /**
     * @var Locker
     */
    private $locker;

    /**
     * @var Downloader\DownloadManager
     */
    private $downloadManager;

    /**
     * @var Plugin\PluginManager
     */
    private $pluginManager;

    /**
     * @var Autoload\AutoloadGenerator
     */
    private $autoloadGenerator;

    /**
     * @var ArchiveManager
     */
    private $archiveManager;

    public function setLocker(Locker $locker): void
    {
        $this->locker = $locker;
    }

    public function getLocker(): Locker
    {
        return $this->locker;
    }

    public function setDownloadManager(DownloadManager $manager): void
    {
        $this->downloadManager = $manager;
    }

    public function getDownloadManager(): DownloadManager
    {
        return $this->downloadManager;
    }

    public function setArchiveManager(ArchiveManager $manager): void
    {
        $this->archiveManager = $manager;
    }

    public function getArchiveManager(): ArchiveManager
    {
        return $this->archiveManager;
    }

    public function setPluginManager(PluginManager $manager): void
    {
        $this->pluginManager = $manager;
    }

    public function getPluginManager(): PluginManager
    {
        return $this->pluginManager;
    }

    public function setAutoloadGenerator(AutoloadGenerator $autoloadGenerator): void
    {
        $this->autoloadGenerator = $autoloadGenerator;
    }

    public function getAutoloadGenerator(): AutoloadGenerator
    {
        return $this->autoloadGenerator;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Console\Input;

use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Completion\Suggestion;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Input\InputArgument as BaseInputArgument;

/**
 * Backport suggested values definition from symfony/console 6.1+
 *
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 *
 * @internal
 *
 * TODO symfony/console:6.1 drop when PHP 8.1 / symfony 6.1+ can be required
 */
class InputArgument extends BaseInputArgument
{
    /**
     * @var list<string>|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion>
     */
    private $suggestedValues;

    /**
     * @param string                              $name        The argument name
     * @param int|null                            $mode        The argument mode: self::REQUIRED or self::OPTIONAL
     * @param string                              $description A description text
     * @param string|bool|int|float|string[]|null $default     The default value (for self::OPTIONAL mode only)
     * @param list<string>|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion> $suggestedValues The values used for input completion
     *
     * @throws InvalidArgumentException When argument mode is not valid
     */
    public function __construct(string $name, ?int $mode = null, string $description = '', $default = null, $suggestedValues = [])
    {
        parent::__construct($name, $mode, $description, $default);

        $this->suggestedValues = $suggestedValues;
    }

    /**
     * Adds suggestions to $suggestions for the current completion input.
     *
     * @see Command::complete()
     */
    public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
    {
        $values = $this->suggestedValues;
        if ($values instanceof \Closure && !\is_array($values = $values($input, $suggestions))) { // @phpstan-ignore function.impossibleType
            throw new LogicException(sprintf('Closure for option "%s" must return an array. Got "%s".', $this->getName(), get_debug_type($values)));
        }
        if ([] !== $values) {
            $suggestions->suggestValues($values);
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Console\Input;

use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Completion\Suggestion;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Input\InputOption as BaseInputOption;

/**
 * Backport suggested values definition from symfony/console 6.1+
 *
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 *
 * @internal
 *
 * TODO symfony/console:6.1 drop when PHP 8.1 / symfony 6.1+ can be required
 */
class InputOption extends BaseInputOption
{
    /**
     * @var list<string>|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion>
     */
    private $suggestedValues;

    /**
     * @param string|string[]|null                $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
     * @param int|null                            $mode     The option mode: One of the VALUE_* constants
     * @param string|bool|int|float|string[]|null $default  The default value (must be null for self::VALUE_NONE)
     * @param list<string>|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion> $suggestedValues The values used for input completionnull for self::VALUE_NONE)
     *
     * @throws InvalidArgumentException If option mode is invalid or incompatible
     */
    public function __construct(string $name, $shortcut = null, ?int $mode = null, string $description = '', $default = null, $suggestedValues = [])
    {
        parent::__construct($name, $shortcut, $mode, $description, $default);

        $this->suggestedValues = $suggestedValues;

        if ([] !== $suggestedValues && !$this->acceptValue()) {
            throw new LogicException('Cannot set suggested values if the option does not accept a value.');
        }
    }

    /**
     * Adds suggestions to $suggestions for the current completion input.
     *
     * @see Command::complete()
     */
    public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
    {
        $values = $this->suggestedValues;
        if ($values instanceof \Closure && !\is_array($values = $values($input, $suggestions))) { // @phpstan-ignore function.impossibleType
            throw new LogicException(sprintf('Closure for argument "%s" must return an array. Got "%s".', $this->getName(), get_debug_type($values)));
        }
        if ([] !== $values) {
            $suggestions->suggestValues($values);
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Console;

use Composer\Installer;
use Composer\IO\NullIO;
use Composer\Util\Filesystem;
use Composer\Util\Platform;
use Composer\Util\Silencer;
use LogicException;
use RuntimeException;
use Symfony\Component\Console\Application as BaseApplication;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputDefinition;
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 Seld\JsonLint\ParsingException;
use Composer\Command;
use Composer\Composer;
use Composer\Factory;
use Composer\Downloader\TransportException;
use Composer\IO\IOInterface;
use Composer\IO\ConsoleIO;
use Composer\Json\JsonValidationException;
use Composer\Util\ErrorHandler;
use Composer\Util\HttpDownloader;
use Composer\EventDispatcher\ScriptExecutionException;
use Composer\Exception\NoSslException;
use Composer\XdebugHandler\XdebugHandler;
use Symfony\Component\Process\Exception\ProcessTimedOutException;

/**
 * The console application that handles the commands
 *
 * @author Ryan Weaver <ryan@knplabs.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 */
class Application extends BaseApplication
{
    /**
     * @var ?Composer
     */
    protected $composer;

    /**
     * @var IOInterface
     */
    protected $io;

    /** @var string */
    private static $logo = '   ______
  / ____/___  ____ ___  ____  ____  ________  _____
 / /   / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__  )  __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
                    /_/
';

    /** @var bool */
    private $hasPluginCommands = false;
    /** @var bool */
    private $disablePluginsByDefault = false;
    /** @var bool */
    private $disableScriptsByDefault = false;

    /**
     * @var string|false Store the initial working directory at startup time
     */
    private $initialWorkingDirectory;

    public function __construct(string $name = 'Composer', string $version = '')
    {
        if (method_exists($this, 'setCatchErrors')) {
            $this->setCatchErrors(true);
        }

        static $shutdownRegistered = false;
        if ($version === '') {
            $version = Composer::getVersion();
        }
        if (function_exists('ini_set') && extension_loaded('xdebug')) {
            ini_set('xdebug.show_exception_trace', '0');
            ini_set('xdebug.scream', '0');
        }

        if (function_exists('date_default_timezone_set') && function_exists('date_default_timezone_get')) {
            date_default_timezone_set(Silencer::call('date_default_timezone_get'));
        }

        $this->io = new NullIO();

        if (!$shutdownRegistered) {
            $shutdownRegistered = true;

            register_shutdown_function(static function (): void {
                $lastError = error_get_last();

                if ($lastError && $lastError['message'] &&
                   (strpos($lastError['message'], 'Allowed memory') !== false /*Zend PHP out of memory error*/ ||
                    strpos($lastError['message'], 'exceeded memory') !== false /*HHVM out of memory errors*/)) {
                    echo "\n". 'Check https://getcomposer.org/doc/articles/troubleshooting.md#memory-limit-errors for more info on how to handle out of memory errors.';
                }
            });
        }

        $this->initialWorkingDirectory = getcwd();

        parent::__construct($name, $version);
    }

    public function __destruct()
    {
    }

    public function run(?InputInterface $input = null, ?OutputInterface $output = null): int
    {
        if (null === $output) {
            $output = Factory::createOutput();
        }

        return parent::run($input, $output);
    }

    public function doRun(InputInterface $input, OutputInterface $output): int
    {
        $this->disablePluginsByDefault = $input->hasParameterOption('--no-plugins');
        $this->disableScriptsByDefault = $input->hasParameterOption('--no-scripts');

        static $stdin = null;
        if (null === $stdin) {
            $stdin = defined('STDIN') ? STDIN : fopen('php://stdin', 'r');
        }
        if (Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING') !== '1' && (Platform::getEnv('COMPOSER_NO_INTERACTION') || $stdin === false || !Platform::isTty($stdin))) {
            $input->setInteractive(false);
        }

        $io = $this->io = new ConsoleIO($input, $output, new HelperSet([
            new QuestionHelper(),
        ]));

        // Register error handler again to pass it the IO instance
        ErrorHandler::register($io);

        if ($input->hasParameterOption('--no-cache')) {
            $io->writeError('Disabling cache usage', true, IOInterface::DEBUG);
            Platform::putEnv('COMPOSER_CACHE_DIR', Platform::isWindows() ? 'nul' : '/dev/null');
        }

        // switch working dir
        $newWorkDir = $this->getNewWorkingDir($input);
        if (null !== $newWorkDir) {
            $oldWorkingDir = Platform::getCwd(true);
            chdir($newWorkDir);
            $this->initialWorkingDirectory = $newWorkDir;
            $cwd = Platform::getCwd(true);
            $io->writeError('Changed CWD to ' . ($cwd !== '' ? $cwd : $newWorkDir), true, IOInterface::DEBUG);
        }

        // determine command name to be executed without including plugin commands
        $commandName = '';
        if ($name = $this->getCommandNameBeforeBinding($input)) {
            try {
                $commandName = $this->find($name)->getName();
            } catch (CommandNotFoundException $e) {
                // we'll check command validity again later after plugins are loaded
                $commandName = false;
            } catch (\InvalidArgumentException $e) {
            }
        }

        // prompt user for dir change if no composer.json is present in current dir
        if (
            null === $newWorkDir
            // do not prompt for commands that can function without composer.json
            && !in_array($commandName, ['', 'list', 'init', 'about', 'help', 'diagnose', 'self-update', 'global', 'create-project', 'outdated'], true)
            && !file_exists(Factory::getComposerFile())
            // if use-parent-dir is disabled we should not prompt
            && ($useParentDirIfNoJsonAvailable = $this->getUseParentDirConfigValue()) !== false
            // config --file ... should not prompt
            && ($commandName !== 'config' || ($input->hasParameterOption('--file', true) === false && $input->hasParameterOption('-f', true) === false))
            // calling a command's help should not prompt
            && $input->hasParameterOption('--help', true) === false
            && $input->hasParameterOption('-h', true) === false
        ) {
            $dir = dirname(Platform::getCwd(true));
            $home = realpath(Platform::getEnv('HOME') ?: Platform::getEnv('USERPROFILE') ?: '/');

            // abort when we reach the home dir or top of the filesystem
            while (dirname($dir) !== $dir && $dir !== $home) {
                if (file_exists($dir.'/'.Factory::getComposerFile())) {
                    if ($useParentDirIfNoJsonAvailable !== true && !$io->isInteractive()) {
                        $io->writeError('<info>No composer.json in current directory, to use the one at '.$dir.' run interactively or set config.use-parent-dir to true</info>');
                        break;
                    }
                    if ($useParentDirIfNoJsonAvailable === true || $io->askConfirmation('<info>No composer.json in current directory, do you want to use the one at '.$dir.'?</info> [<comment>Y,n</comment>]? ')) {
                        if ($useParentDirIfNoJsonAvailable === true) {
                            $io->writeError('<info>No composer.json in current directory, changing working directory to '.$dir.'</info>');
                        } else {
                            $io->writeError('<info>Always want to use the parent dir? Use "composer config --global use-parent-dir true" to change the default.</info>');
                        }
                        $oldWorkingDir = Platform::getCwd(true);
                        chdir($dir);
                    }
                    break;
                }
                $dir = dirname($dir);
            }
            unset($dir, $home);
        }

        $needsSudoCheck = !Platform::isWindows()
            && function_exists('exec')
            && !Platform::getEnv('COMPOSER_ALLOW_SUPERUSER')
            && !Platform::isDocker();
        $isNonAllowedRoot = false;

        // Clobber sudo credentials if COMPOSER_ALLOW_SUPERUSER is not set before loading plugins
        if ($needsSudoCheck) {
            $isNonAllowedRoot = $this->isRunningAsRoot();

            if ($isNonAllowedRoot) {
                if ($uid = (int) Platform::getEnv('SUDO_UID')) {
                    // Silently clobber any sudo credentials on the invoking user to avoid privilege escalations later on
                    // ref. https://github.com/composer/composer/issues/5119
                    Silencer::call('exec', "sudo -u \\#{$uid} sudo -K > /dev/null 2>&1");
                }
            }

            // Silently clobber any remaining sudo leases on the current user as well to avoid privilege escalations
            Silencer::call('exec', 'sudo -K > /dev/null 2>&1');
        }

        // avoid loading plugins/initializing the Composer instance earlier than necessary if no plugin command is needed
        // if showing the version, we never need plugin commands
        $mayNeedPluginCommand = false === $input->hasParameterOption(['--version', '-V'])
            && (
                // not a composer command, so try loading plugin ones
                false === $commandName
                // list command requires plugin commands to show them
                || in_array($commandName, ['', 'list', 'help'], true)
                // autocompletion requires plugin commands but if we are running as root without COMPOSER_ALLOW_SUPERUSER
                // we'd rather not autocomplete plugins than abort autocompletion entirely, so we avoid loading plugins in this case
                || ($commandName === '_complete' && !$isNonAllowedRoot)
            );

        if ($mayNeedPluginCommand && !$this->disablePluginsByDefault && !$this->hasPluginCommands) {
            // at this point plugins are needed, so if we are running as root and it is not allowed we need to prompt
            // if interactive, and abort otherwise
            if ($isNonAllowedRoot) {
                $io->writeError('<warning>Do not run Composer as root/super user! See https://getcomposer.org/root for details</warning>');

                if ($io->isInteractive() && $io->askConfirmation('<info>Continue as root/super user</info> [<comment>yes</comment>]? ')) {
                    // avoid a second prompt later
                    $isNonAllowedRoot = false;
                } else {
                    $io->writeError('<warning>Aborting as no plugin should be loaded if running as super user is not explicitly allowed</warning>');

                    return 1;
                }
            }

            try {
                foreach ($this->getPluginCommands() as $command) {
                    if ($this->has($command->getName())) {
                        $io->writeError('<warning>Plugin command '.$command->getName().' ('.get_class($command).') would override a Composer command and has been skipped</warning>');
                    } else {
                        // Compatibility layer for symfony/console <7.4
                        method_exists($this, 'addCommand') ? $this->addCommand($command) : $this->add($command);
                    }
                }
            } catch (NoSslException $e) {
                // suppress these as they are not relevant at this point
            } catch (ParsingException $e) {
                $details = $e->getDetails();

                $file = realpath(Factory::getComposerFile());

                $line = null;
                if ($details && isset($details['line'])) {
                    $line = $details['line'];
                }

                $ghe = new GithubActionError($this->io);
                $ghe->emit($e->getMessage(), $file, $line);

                throw $e;
            }

            $this->hasPluginCommands = true;
        }

        if (!$this->disablePluginsByDefault && $isNonAllowedRoot && !$io->isInteractive()) {
            $io->writeError('<error>Composer plugins have been disabled for safety in this non-interactive session.</error>');
            $io->writeError('<error>Set COMPOSER_ALLOW_SUPERUSER=1 if you want to allow plugins to run as root/super user.</error>');
            $this->disablePluginsByDefault = true;
        }

        // determine command name to be executed incl plugin commands, and check if it's a proxy command
        $isProxyCommand = false;
        if ($name = $this->getCommandNameBeforeBinding($input)) {
            try {
                $command = $this->find($name);
                $commandName = $command->getName();
                $isProxyCommand = ($command instanceof Command\BaseCommand && $command->isProxyCommand());
            } catch (\InvalidArgumentException $e) {
            }
        }

        if (!$isProxyCommand) {
            $io->writeError(sprintf(
                'Running %s (%s) with %s on %s',
                Composer::getVersion(),
                Composer::RELEASE_DATE,
                defined('HHVM_VERSION') ? 'HHVM '.HHVM_VERSION : 'PHP '.PHP_VERSION,
                function_exists('php_uname') ? php_uname('s') . ' / ' . php_uname('r') : 'Unknown OS'
            ), true, IOInterface::DEBUG);

            if (\PHP_VERSION_ID < 70205) {
                $io->writeError('<warning>Composer supports PHP 7.2.5 and above, you will most likely encounter problems with your PHP '.PHP_VERSION.'. Upgrading is strongly recommended but you can use Composer 2.2.x LTS as a fallback.</warning>');
            }

            if (XdebugHandler::isXdebugActive() && !Platform::getEnv('COMPOSER_DISABLE_XDEBUG_WARN')) {
                $io->writeError('<warning>Composer is operating slower than normal because you have Xdebug enabled. See https://getcomposer.org/xdebug</warning>');
            }

            if (defined('COMPOSER_DEV_WARNING_TIME') && $commandName !== 'self-update' && $commandName !== 'selfupdate' && time() > COMPOSER_DEV_WARNING_TIME) {
                $io->writeError(sprintf('<warning>Warning: This development build of Composer is over 60 days old. It is recommended to update it by running "%s self-update" to get the latest version.</warning>', $_SERVER['PHP_SELF']));
            }

            if ($isNonAllowedRoot) {
                if ($commandName !== 'self-update' && $commandName !== 'selfupdate' && $commandName !== '_complete') {
                    $io->writeError('<warning>Do not run Composer as root/super user! See https://getcomposer.org/root for details</warning>');

                    if ($io->isInteractive()) {
                        if (!$io->askConfirmation('<info>Continue as root/super user</info> [<comment>yes</comment>]? ')) {
                            return 1;
                        }
                    }
                }
            }

            // Check system temp folder for usability as it can cause weird runtime issues otherwise
            Silencer::call(static function () use ($io): void {
                $pid = function_exists('getmypid') ? getmypid() . '-' : '';
                $tempfile = sys_get_temp_dir() . '/temp-' . $pid . bin2hex(random_bytes(5));
                if (!(file_put_contents($tempfile, __FILE__) && (file_get_contents($tempfile) === __FILE__) && unlink($tempfile) && !file_exists($tempfile))) {
                    $io->writeError(sprintf('<error>PHP temp directory (%s) does not exist or is not writable to Composer. Set sys_temp_dir in your php.ini</error>', sys_get_temp_dir()));
                }
            });

            // add non-standard scripts as own commands
            $file = Factory::getComposerFile();
            if (is_file($file) && Filesystem::isReadable($file) && is_array($composer = json_decode(file_get_contents($file), true))) {
                if (isset($composer['scripts']) && is_array($composer['scripts'])) {
                    foreach ($composer['scripts'] as $script => $dummy) {
                        if (!defined('Composer\Script\ScriptEvents::'.str_replace('-', '_', strtoupper($script)))) {
                            if ($this->has($script)) {
                                $io->writeError('<warning>A script named '.$script.' would override a Composer command and has been skipped</warning>');
                            } else {
                                $description = null;

                                if (isset($composer['scripts-descriptions'][$script])) {
                                    $description = $composer['scripts-descriptions'][$script];
                                }

                                $aliases = $composer['scripts-aliases'][$script] ?? [];

                                $scriptAlias = new Command\ScriptAliasCommand($script, $description, $aliases);
                                // Compatibility layer for symfony/console <7.4
                                method_exists($this, 'addCommand') ? $this->addCommand($scriptAlias) : $this->add($scriptAlias);
                            }
                        }
                    }
                }
            }
        }

        try {
            if ($input->hasParameterOption('--profile')) {
                $startTime = microtime(true);
                $this->io->enableDebugging($startTime);
            }

            $result = parent::doRun($input, $output);

            if (true === $input->hasParameterOption(['--version', '-V'], true)) {
                $io->writeError(sprintf('<info>PHP</info> version <comment>%s</comment> (%s)', \PHP_VERSION, \PHP_BINARY));
                $io->writeError('Run the "diagnose" command to get more detailed diagnostics output.');
            }

            // chdir back to $oldWorkingDir if set
            if (isset($oldWorkingDir) && '' !== $oldWorkingDir) {
                Silencer::call('chdir', $oldWorkingDir);
            }

            if (isset($startTime)) {
                $io->writeError('<info>Memory usage: '.round(memory_get_usage() / 1024 / 1024, 2).'MiB (peak: '.round(memory_get_peak_usage() / 1024 / 1024, 2).'MiB), time: '.round(microtime(true) - $startTime, 2).'s</info>');
            }

            return $result;
        } catch (ScriptExecutionException $e) {
            if ($this->getDisablePluginsByDefault() && $this->isRunningAsRoot() && !$this->io->isInteractive()) {
                $io->writeError('<error>Plugins have been disabled automatically as you are running as root, this may be the cause of the script failure.</error>', true, IOInterface::QUIET);
                $io->writeError('<error>See also https://getcomposer.org/root</error>', true, IOInterface::QUIET);
            }

            return $e->getCode();
        } catch (\Throwable $e) {
            $ghe = new GithubActionError($this->io);
            $ghe->emit($e->getMessage());

            $this->hintCommonErrors($e, $output);

            // symfony/console <6.4 does not handle \Error subtypes so we have to renderThrowable ourselves
            // instead of rethrowing those for consumption by the parent class
            // can be removed when Composer supports PHP 8.1+
            if (!method_exists($this, 'setCatchErrors') && !$e instanceof \Exception) {
                if ($output instanceof ConsoleOutputInterface) {
                    $this->renderThrowable($e, $output->getErrorOutput());
                } else {
                    $this->renderThrowable($e, $output);
                }

                return max(1, $e->getCode());
            }

            // override TransportException's code for the purpose of parent::run() using it as process exit code
            // as http error codes are all beyond the 255 range of permitted exit codes
            if ($e instanceof TransportException) {
                $reflProp = new \ReflectionProperty($e, 'code');
                (\PHP_VERSION_ID < 80100) and $reflProp->setAccessible(true);
                $reflProp->setValue($e, Installer::ERROR_TRANSPORT_EXCEPTION);
            }

            throw $e;
        } finally {
            restore_error_handler();
        }
    }

    /**
     * @throws \RuntimeException
     * @return ?string
     */
    private function getNewWorkingDir(InputInterface $input): ?string
    {
        /** @var string|null $workingDir */
        $workingDir = $input->getParameterOption(['--working-dir', '-d'], null, true);
        if (null !== $workingDir && !is_dir($workingDir)) {
            throw new \RuntimeException('Invalid working directory specified, '.$workingDir.' does not exist.');
        }

        return $workingDir;
    }

    private function hintCommonErrors(\Throwable $exception, OutputInterface $output): void
    {
        $io = $this->getIO();

        if ((get_class($exception) === LogicException::class || $exception instanceof \Error) && $output->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) {
            $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
        }

        Silencer::suppress();
        try {
            $composer = $this->getComposer(false, true);
            if (null !== $composer && function_exists('disk_free_space')) {
                $config = $composer->getConfig();

                $minSpaceFree = 100 * 1024 * 1024;
                if ((($df = disk_free_space($dir = $config->get('home'))) !== false && $df < $minSpaceFree)
                    || (($df = disk_free_space($dir = $config->get('vendor-dir'))) !== false && $df < $minSpaceFree)
                    || (($df = disk_free_space($dir = sys_get_temp_dir())) !== false && $df < $minSpaceFree)
                ) {
                    $io->writeError('<error>The disk hosting '.$dir.' has less than 100MiB of free space, this may be the cause of the following exception</error>', true, IOInterface::QUIET);
                }
            }
        } catch (\Exception $e) {
        }
        Silencer::restore();

        if ($exception instanceof TransportException && str_contains($exception->getMessage(), 'Unable to use a proxy')) {
            $io->writeError('<error>The following exception indicates your proxy is misconfigured</error>', true, IOInterface::QUIET);
            $io->writeError('<error>Check https://getcomposer.org/doc/faqs/how-to-use-composer-behind-a-proxy.md for details</error>', true, IOInterface::QUIET);
        }

        if (Platform::isWindows() && $exception instanceof TransportException && str_contains($exception->getMessage(), 'unable to get local issuer certificate')) {
            $avastDetect = glob('C:\Program Files\Avast*');
            if (is_array($avastDetect) && count($avastDetect) !== 0) {
                $io->writeError('<error>The following exception indicates a possible issue with the Avast Firewall</error>', true, IOInterface::QUIET);
                $io->writeError('<error>Check https://getcomposer.org/local-issuer for details</error>', true, IOInterface::QUIET);
            } else {
                $io->writeError('<error>The following exception indicates a possible issue with a Firewall/Antivirus</error>', true, IOInterface::QUIET);
                $io->writeError('<error>Check https://getcomposer.org/local-issuer for details</error>', true, IOInterface::QUIET);
            }
        }

        if (Platform::isWindows() && false !== strpos($exception->getMessage(), 'The system cannot find the path specified')) {
            $io->writeError('<error>The following exception may be caused by a stale entry in your cmd.exe AutoRun</error>', true, IOInterface::QUIET);
            $io->writeError('<error>Check https://getcomposer.org/doc/articles/troubleshooting.md#-the-system-cannot-find-the-path-specified-windows- for details</error>', true, IOInterface::QUIET);
        }

        if (false !== strpos($exception->getMessage(), 'fork failed - Cannot allocate memory')) {
            $io->writeError('<error>The following exception is caused by a lack of memory or swap, or not having swap configured</error>', true, IOInterface::QUIET);
            $io->writeError('<error>Check https://getcomposer.org/doc/articles/troubleshooting.md#proc-open-fork-failed-errors for details</error>', true, IOInterface::QUIET);
        }

        if ($exception instanceof ProcessTimedOutException) {
            $io->writeError('<error>The following exception is caused by a process timeout</error>', true, IOInterface::QUIET);
            $io->writeError('<error>Check https://getcomposer.org/doc/06-config.md#process-timeout for details</error>', true, IOInterface::QUIET);
        }

        if ($this->getDisablePluginsByDefault() && $this->isRunningAsRoot() && !$this->io->isInteractive()) {
            $io->writeError('<error>Plugins have been disabled automatically as you are running as root, this may be the cause of the following exception. See also https://getcomposer.org/root</error>', true, IOInterface::QUIET);
        } elseif ($exception instanceof CommandNotFoundException && $this->getDisablePluginsByDefault()) {
            $io->writeError('<error>Plugins have been disabled, which may be why some commands are missing, unless you made a typo</error>', true, IOInterface::QUIET);
        }

        $hints = HttpDownloader::getExceptionHints($exception);
        if (null !== $hints && count($hints) > 0) {
            foreach ($hints as $hint) {
                $io->writeError($hint, true, IOInterface::QUIET);
            }
        }
    }

    /**
     * @throws JsonValidationException
     * @throws \InvalidArgumentException
     * @return ?Composer If $required is true then the return value is guaranteed
     */
    public function getComposer(bool $required = true, ?bool $disablePlugins = null, ?bool $disableScripts = null): ?Composer
    {
        if (null === $disablePlugins) {
            $disablePlugins = $this->disablePluginsByDefault;
        }
        if (null === $disableScripts) {
            $disableScripts = $this->disableScriptsByDefault;
        }

        if (null === $this->composer) {
            try {
                $this->composer = Factory::create(Platform::isInputCompletionProcess() ? new NullIO() : $this->io, null, $disablePlugins, $disableScripts);
            } catch (\InvalidArgumentException $e) {
                if ($required) {
                    $this->io->writeError($e->getMessage());
                    if ($this->areExceptionsCaught()) {
                        exit(1);
                    }
                    throw $e;
                }
            } catch (JsonValidationException $e) {
                if ($required) {
                    throw $e;
                }
            } catch (RuntimeException $e) {
                if ($required) {
                    throw $e;
                }
            }
        }

        return $this->composer;
    }

    /**
     * Removes the cached composer instance
     */
    public function resetComposer(): void
    {
        $this->composer = null;
        if (method_exists($this->getIO(), 'resetAuthentications')) {
            $this->getIO()->resetAuthentications();
        }
    }

    public function getIO(): IOInterface
    {
        return $this->io;
    }

    public function getHelp(): string
    {
        return self::$logo . parent::getHelp();
    }

    /**
     * Initializes all the composer commands.
     * @return \Symfony\Component\Console\Command\Command[]
     */
    protected function getDefaultCommands(): array
    {
        $commands = array_merge(parent::getDefaultCommands(), [
            new Command\AboutCommand(),
            new Command\ConfigCommand(),
            new Command\DependsCommand(),
            new Command\ProhibitsCommand(),
            new Command\InitCommand(),
            new Command\InstallCommand(),
            new Command\CreateProjectCommand(),
            new Command\UpdateCommand(),
            new Command\SearchCommand(),
            new Command\ValidateCommand(),
            new Command\AuditCommand(),
            new Command\ShowCommand(),
            new Command\SuggestsCommand(),
            new Command\RequireCommand(),
            new Command\DumpAutoloadCommand(),
            new Command\StatusCommand(),
            new Command\ArchiveCommand(),
            new Command\DiagnoseCommand(),
            new Command\RunScriptCommand(),
            new Command\LicensesCommand(),
            new Command\GlobalCommand(),
            new Command\ClearCacheCommand(),
            new Command\RemoveCommand(),
            new Command\HomeCommand(),
            new Command\ExecCommand(),
            new Command\OutdatedCommand(),
            new Command\CheckPlatformReqsCommand(),
            new Command\FundCommand(),
            new Command\ReinstallCommand(),
            new Command\BumpCommand(),
        ]);

        if (strpos(__FILE__, 'phar:') === 0 || '1' === Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING')) {
            $commands[] = new Command\SelfUpdateCommand();
        }

        return $commands;
    }

    /**
     * This ensures we can find the correct command name even if a global input option is present before it
     *
     * e.g. "composer -d foo bar" should detect bar as the command name, and not foo
     */
    private function getCommandNameBeforeBinding(InputInterface $input): ?string
    {
        $input = clone $input;
        try {
            // Makes ArgvInput::getFirstArgument() able to distinguish an option from an argument.
            $input->bind($this->getDefinition());
        } catch (ExceptionInterface $e) {
            // Errors must be ignored, full binding/validation happens later when the command is known.
        }

        return $input->getFirstArgument();
    }

    public function getLongVersion(): string
    {
        $branchAliasString = '';
        if (Composer::BRANCH_ALIAS_VERSION && Composer::BRANCH_ALIAS_VERSION !== '@package_branch_alias_version'.'@') {
            $branchAliasString = sprintf(' (%s)', Composer::BRANCH_ALIAS_VERSION);
        }

        return sprintf(
            '<info>%s</info> version <comment>%s%s</comment> %s',
            $this->getName(),
            $this->getVersion(),
            $branchAliasString,
            Composer::RELEASE_DATE
        );
    }

    protected function getDefaultInputDefinition(): InputDefinition
    {
        $definition = parent::getDefaultInputDefinition();
        $definition->addOption(new InputOption('--profile', null, InputOption::VALUE_NONE, 'Display timing and memory usage information'));
        $definition->addOption(new InputOption('--no-plugins', null, InputOption::VALUE_NONE, 'Whether to disable plugins.'));
        $definition->addOption(new InputOption('--no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'));
        $definition->addOption(new InputOption('--working-dir', '-d', InputOption::VALUE_REQUIRED, 'If specified, use the given directory as working directory.'));
        $definition->addOption(new InputOption('--no-cache', null, InputOption::VALUE_NONE, 'Prevent use of the cache'));

        return $definition;
    }

    /**
     * @return Command\BaseCommand[]
     */
    private function getPluginCommands(): array
    {
        $commands = [];

        $composer = $this->getComposer(false, false);
        if (null === $composer) {
            $composer = Factory::createGlobal($this->io, $this->disablePluginsByDefault, $this->disableScriptsByDefault);
        }

        if (null !== $composer) {
            $pm = $composer->getPluginManager();
            foreach ($pm->getPluginCapabilities('Composer\Plugin\Capability\CommandProvider', ['composer' => $composer, 'io' => $this->io]) as $capability) {
                $newCommands = $capability->getCommands();
                if (!is_array($newCommands)) {
                    throw new \UnexpectedValueException('Plugin capability '.get_class($capability).' failed to return an array from getCommands');
                }
                foreach ($newCommands as $command) {
                    if (!$command instanceof Command\BaseCommand) {
                        throw new \UnexpectedValueException('Plugin capability '.get_class($capability).' returned an invalid value, we expected an array of Composer\Command\BaseCommand objects');
                    }
                }
                $commands = array_merge($commands, $newCommands);
            }
        }

        return $commands;
    }

    /**
     * Get the working directory at startup time
     *
     * @return string|false
     */
    public function getInitialWorkingDirectory()
    {
        return $this->initialWorkingDirectory;
    }

    public function getDisablePluginsByDefault(): bool
    {
        return $this->disablePluginsByDefault;
    }

    public function getDisableScriptsByDefault(): bool
    {
        return $this->disableScriptsByDefault;
    }

    /**
     * @return 'prompt'|bool
     */
    private function getUseParentDirConfigValue()
    {
        $config = Factory::createConfig($this->io);

        return $config->get('use-parent-dir');
    }

    private function isRunningAsRoot(): bool
    {
        return function_exists('posix_getuid') && posix_getuid() === 0;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Console;

use Closure;
use Composer\Pcre\Preg;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class HtmlOutputFormatter extends OutputFormatter
{
    /** @var array<int, string> */
    private static $availableForegroundColors = [
        30 => 'black',
        31 => 'red',
        32 => 'green',
        33 => 'yellow',
        34 => 'blue',
        35 => 'magenta',
        36 => 'cyan',
        37 => 'white',
    ];
    /** @var array<int, string> */
    private static $availableBackgroundColors = [
        40 => 'black',
        41 => 'red',
        42 => 'green',
        43 => 'yellow',
        44 => 'blue',
        45 => 'magenta',
        46 => 'cyan',
        47 => 'white',
    ];
    /** @var array<int, string> */
    private static $availableOptions = [
        1 => 'bold',
        4 => 'underscore',
        //5 => 'blink',
        //7 => 'reverse',
        //8 => 'conceal'
    ];

    /**
     * @param array<string, OutputFormatterStyle> $styles Array of "name => FormatterStyle" instances
     */
    public function __construct(array $styles = [])
    {
        parent::__construct(true, $styles);
    }

    public function format(?string $message): ?string
    {
        $formatted = parent::format($message);

        if ($formatted === null) {
            return null;
        }

        $clearEscapeCodes = '(?:39|49|0|22|24|25|27|28)';

        return Preg::replaceCallback("{\033\[([0-9;]+)m(.*?)\033\[(?:".$clearEscapeCodes.";)*?".$clearEscapeCodes."m}s", Closure::fromCallable([$this, 'formatHtml']), $formatted);
    }

    /**
     * @param array<string|null> $matches
     */
    private function formatHtml(array $matches): string
    {
        assert(is_string($matches[1]));
        $out = '<span style="';
        foreach (explode(';', $matches[1]) as $code) {
            if (isset(self::$availableForegroundColors[(int) $code])) {
                $out .= 'color:'.self::$availableForegroundColors[(int) $code].';';
            } elseif (isset(self::$availableBackgroundColors[(int) $code])) {
                $out .= 'background-color:'.self::$availableBackgroundColors[(int) $code].';';
            } elseif (isset(self::$availableOptions[(int) $code])) {
                switch (self::$availableOptions[(int) $code]) {
                    case 'bold':
                        $out .= 'font-weight:bold;';
                        break;

                    case 'underscore':
                        $out .= 'text-decoration:underline;';
                        break;
                }
            }
        }

        return $out.'">'.$matches[2].'</span>';
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Console;

use Composer\IO\IOInterface;
use Composer\Util\Platform;

final class GithubActionError
{
    /**
     * @var IOInterface
     */
    protected $io;

    public function __construct(IOInterface  $io)
    {
        $this->io = $io;
    }

    public function emit(string $message, ?string $file = null, ?int $line = null): void
    {
        if (Platform::getEnv('GITHUB_ACTIONS') && !Platform::getEnv('COMPOSER_TESTS_ARE_RUNNING')) {
            $message = $this->escapeData($message);

            if ($file && $line) {
                $file = $this->escapeProperty($file);
                $this->io->write("::error file=". $file .",line=". $line ."::". $message);
            } elseif ($file) {
                $file = $this->escapeProperty($file);
                $this->io->write("::error file=". $file ."::". $message);
            } else {
                $this->io->write("::error ::". $message);
            }
        }
    }

    private function escapeData(string $data): string
    {
        // see https://github.com/actions/toolkit/blob/4f7fb6513a355689f69f0849edeb369a4dc81729/packages/core/src/command.ts#L80-L85
        $data = str_replace("%", '%25', $data);
        $data = str_replace("\r", '%0D', $data);
        $data = str_replace("\n", '%0A', $data);

        return $data;
    }

    private function escapeProperty(string $property): string
    {
        // see https://github.com/actions/toolkit/blob/4f7fb6513a355689f69f0849edeb369a4dc81729/packages/core/src/command.ts#L87-L94
        $property = str_replace("%", '%25', $property);
        $property = str_replace("\r", '%0D', $property);
        $property = str_replace("\n", '%0A', $property);
        $property = str_replace(":", '%3A', $property);
        $property = str_replace(",", '%2C', $property);

        return $property;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\EventDispatcher;

use Composer\DependencyResolver\Transaction;
use Composer\Installer\InstallerEvent;
use Composer\IO\BufferIO;
use Composer\IO\ConsoleIO;
use Composer\IO\IOInterface;
use Composer\Composer;
use Composer\PartialComposer;
use Composer\Pcre\Preg;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PreCommandRunEvent;
use Composer\Util\Platform;
use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\Repository\RepositoryInterface;
use Composer\Script;
use Composer\Installer\PackageEvent;
use Composer\Installer\BinaryInstaller;
use Composer\Util\ProcessExecutor;
use Composer\Script\Event as ScriptEvent;
use Composer\Autoload\ClassLoader;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Process\PhpExecutableFinder;
use Symfony\Component\Process\ExecutableFinder;

/**
 * The Event Dispatcher.
 *
 * Example in command:
 *     $dispatcher = new EventDispatcher($this->requireComposer(), $this->getApplication()->getIO());
 *     // ...
 *     $dispatcher->dispatch(ScriptEvents::POST_INSTALL_CMD);
 *     // ...
 *
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Nils Adermann <naderman@naderman.de>
 */
class EventDispatcher
{
    /** @var PartialComposer */
    protected $composer;
    /** @var IOInterface */
    protected $io;
    /** @var ?ClassLoader */
    protected $loader;
    /** @var ProcessExecutor */
    protected $process;
    /** @var array<string, array<int, array<callable|string>>> */
    protected $listeners = [];
    /** @var bool */
    protected $runScripts = true;
    /** @var list<string> */
    private $eventStack;
    /** @var list<string> */
    private $skipScripts;

    /**
     * Constructor.
     *
     * @param PartialComposer $composer The composer instance
     * @param IOInterface     $io       The IOInterface instance
     * @param ProcessExecutor $process
     */
    public function __construct(PartialComposer $composer, IOInterface $io, ?ProcessExecutor $process = null)
    {
        $this->composer = $composer;
        $this->io = $io;
        $this->process = $process ?? new ProcessExecutor($io);
        $this->eventStack = [];
        $this->skipScripts = array_values(array_filter(
            array_map('trim', explode(',', (string) Platform::getEnv('COMPOSER_SKIP_SCRIPTS'))),
            function ($val) {
                return $val !== '';
            }
        ));
    }

    /**
     * Set whether script handlers are active or not
     *
     * @return $this
     */
    public function setRunScripts(bool $runScripts = true): self
    {
        $this->runScripts = $runScripts;

        return $this;
    }

    /**
     * Dispatch an event
     *
     * @param  string|null $eventName The event name, required if no $event is provided
     * @param  Event       $event An event instance, required if no $eventName is provided
     * @return int         return code of the executed script if any, for php scripts a false return
     *                          value is changed to 1, anything else to 0
     */
    public function dispatch(?string $eventName, ?Event $event = null): int
    {
        if (null === $event) {
            if (null === $eventName) {
                throw new \InvalidArgumentException('If no $event is passed in to '.__METHOD__.' you have to pass in an $eventName, got null.');
            }
            $event = new Event($eventName);
        }

        return $this->doDispatch($event);
    }

    /**
     * Dispatch a script event.
     *
     * @param  string               $eventName      The constant in ScriptEvents
     * @param  array<int, mixed>    $additionalArgs Arguments passed by the user
     * @param  array<string, mixed> $flags          Optional flags to pass data not as argument
     * @return int                                  return code of the executed script if any, for php scripts a false return
     *                                              value is changed to 1, anything else to 0
     */
    public function dispatchScript(string $eventName, bool $devMode = false, array $additionalArgs = [], array $flags = []): int
    {
        assert($this->composer instanceof Composer, new \LogicException('This should only be reached with a fully loaded Composer'));

        return $this->doDispatch(new Script\Event($eventName, $this->composer, $this->io, $devMode, $additionalArgs, $flags));
    }

    /**
     * Dispatch a package event.
     *
     * @param string               $eventName  The constant in PackageEvents
     * @param bool                 $devMode    Whether or not we are in dev mode
     * @param RepositoryInterface  $localRepo  The installed repository
     * @param OperationInterface[] $operations The list of operations
     * @param OperationInterface   $operation  The package being installed/updated/removed
     *
     * @return int return code of the executed script if any, for php scripts a false return
     *             value is changed to 1, anything else to 0
     */
    public function dispatchPackageEvent(string $eventName, bool $devMode, RepositoryInterface $localRepo, array $operations, OperationInterface $operation): int
    {
        assert($this->composer instanceof Composer, new \LogicException('This should only be reached with a fully loaded Composer'));

        return $this->doDispatch(new PackageEvent($eventName, $this->composer, $this->io, $devMode, $localRepo, $operations, $operation));
    }

    /**
     * Dispatch a installer event.
     *
     * @param string      $eventName         The constant in InstallerEvents
     * @param bool        $devMode           Whether or not we are in dev mode
     * @param bool        $executeOperations True if operations will be executed, false in --dry-run
     * @param Transaction $transaction       The transaction contains the list of operations
     *
     * @return int return code of the executed script if any, for php scripts a false return
     *             value is changed to 1, anything else to 0
     */
    public function dispatchInstallerEvent(string $eventName, bool $devMode, bool $executeOperations, Transaction $transaction): int
    {
        assert($this->composer instanceof Composer, new \LogicException('This should only be reached with a fully loaded Composer'));

        return $this->doDispatch(new InstallerEvent($eventName, $this->composer, $this->io, $devMode, $executeOperations, $transaction));
    }

    /**
     * Triggers the listeners of an event.
     *
     * @param  Event                        $event The event object to pass to the event handlers/listeners.
     * @throws \RuntimeException|\Exception
     * @return int                          return code of the executed script if any, for php scripts a false return
     *                                            value is changed to 1, anything else to 0
     */
    protected function doDispatch(Event $event)
    {
        if (Platform::getEnv('COMPOSER_DEBUG_EVENTS')) {
            $details = null;
            if ($event instanceof PackageEvent) {
                $details = (string) $event->getOperation();
            } elseif ($event instanceof CommandEvent) {
                $details = $event->getCommandName();
            } elseif ($event instanceof PreCommandRunEvent) {
                $details = $event->getCommand();
            }
            $this->io->writeError('Dispatching <info>'.$event->getName().'</info>'.($details ? ' ('.$details.')' : '').' event');
        }

        $listeners = $this->getListeners($event);

        $this->pushEvent($event);

        $autoloadersBefore = spl_autoload_functions();

        try {
            $returnMax = 0;
            foreach ($listeners as $callable) {
                $return = 0;
                $this->ensureBinDirIsInPath();

                $additionalArgs = $event->getArguments();
                if (is_string($callable) && str_contains($callable, '@no_additional_args')) {
                    $callable = Preg::replace('{ ?@no_additional_args}', '', $callable);
                    $additionalArgs = [];
                }
                $formattedEventNameWithArgs = $event->getName() . ($additionalArgs !== [] ? ' (' . implode(', ', $additionalArgs) . ')' : '');
                if (!is_string($callable)) {
                    if (!is_callable($callable)) {
                        $className = is_object($callable[0]) ? get_class($callable[0]) : $callable[0];

                        throw new \RuntimeException('Subscriber '.$className.'::'.$callable[1].' for event '.$event->getName().' is not callable, make sure the function is defined and public');
                    }
                    if (is_array($callable) && (is_string($callable[0]) || is_object($callable[0])) && is_string($callable[1])) {
                        $this->io->writeError(sprintf('> %s: %s', $formattedEventNameWithArgs, (is_object($callable[0]) ? get_class($callable[0]) : $callable[0]).'->'.$callable[1]), true, IOInterface::VERBOSE);
                    }
                    $return = false === $callable($event) ? 1 : 0;
                } elseif ($this->isComposerScript($callable)) {
                    $this->io->writeError(sprintf('> %s: %s', $formattedEventNameWithArgs, $callable), true, IOInterface::VERBOSE);

                    $script = explode(' ', substr($callable, 1));
                    $scriptName = $script[0];
                    unset($script[0]);

                    $index = array_search('@additional_args', $script, true);
                    if ($index !== false) {
                        $args = array_splice($script, $index, 0, $additionalArgs);
                    } else {
                        $args = array_merge($script, $additionalArgs);
                    }
                    $flags = $event->getFlags();
                    if (isset($flags['script-alias-input'])) {
                        $argsString = implode(' ', array_map(static function ($arg) { return ProcessExecutor::escape($arg); }, $script));
                        $flags['script-alias-input'] = $argsString . ' ' . $flags['script-alias-input'];
                        unset($argsString);
                    }
                    if (strpos($callable, '@composer ') === 0) {
                        $exec = $this->getPhpExecCommand() . ' ' . ProcessExecutor::escape(Platform::getEnv('COMPOSER_BINARY')) . ' ' . implode(' ', $args);
                        if (0 !== ($exitCode = $this->executeTty($exec))) {
                            $this->io->writeError(sprintf('<error>Script %s handling the %s event returned with error code '.$exitCode.'</error>', $callable, $event->getName()), true, IOInterface::QUIET);

                            throw new ScriptExecutionException('Error Output: '.$this->process->getErrorOutput(), $exitCode);
                        }
                    } else {
                        if (!$this->getListeners(new Event($scriptName))) {
                            $this->io->writeError(sprintf('<warning>You made a reference to a non-existent script %s</warning>', $callable), true, IOInterface::QUIET);
                        }

                        try {
                            /** @var InstallerEvent $event */
                            $scriptEvent = new Script\Event($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags);
                            $scriptEvent->setOriginatingEvent($event);
                            $return = $this->dispatch($scriptName, $scriptEvent);
                        } catch (ScriptExecutionException $e) {
                            $this->io->writeError(sprintf('<error>Script %s was called via %s</error>', $callable, $event->getName()), true, IOInterface::QUIET);
                            throw $e;
                        }
                    }
                } elseif ($this->isPhpScript($callable)) {
                    $className = substr($callable, 0, strpos($callable, '::'));
                    $methodName = substr($callable, strpos($callable, '::') + 2);

                    if (!class_exists($className)) {
                        $this->io->writeError('<warning>Class '.$className.' is not autoloadable, can not call '.$event->getName().' script</warning>', true, IOInterface::QUIET);
                        continue;
                    }
                    if (!is_callable($callable)) {
                        $this->io->writeError('<warning>Method '.$callable.' is not callable, can not call '.$event->getName().' script</warning>', true, IOInterface::QUIET);
                        continue;
                    }

                    try {
                        $return = false === $this->executeEventPhpScript($className, $methodName, $event) ? 1 : 0;
                    } catch (\Exception $e) {
                        $message = "Script %s handling the %s event terminated with an exception";
                        $this->io->writeError('<error>'.sprintf($message, $callable, $event->getName()).'</error>', true, IOInterface::QUIET);
                        throw $e;
                    }
                } elseif ($this->isCommandClass($callable)) {
                    $className = $callable;
                    if (!class_exists($className)) {
                        $this->io->writeError('<warning>Class '.$className.' is not autoloadable, can not call '.$event->getName().' script</warning>', true, IOInterface::QUIET);
                        continue;
                    }
                    if (!is_a($className, Command::class, true)) {
                        $this->io->writeError('<warning>Class '.$className.' does not extend '.Command::class.', can not call '.$event->getName().' script</warning>', true, IOInterface::QUIET);
                        continue;
                    }
                    if (defined('Composer\Script\ScriptEvents::'.str_replace('-', '_', strtoupper($event->getName())))) {
                        $this->io->writeError('<warning>You cannot bind '.$event->getName().' to a Command class, use a non-reserved name</warning>', true, IOInterface::QUIET);
                        continue;
                    }

                    $app = new Application();
                    $app->setCatchExceptions(false);
                    if (method_exists($app, 'setCatchErrors')) {
                        $app->setCatchErrors(false);
                    }
                    $app->setAutoExit(false);
                    $cmd = new $className($event->getName());
                    if (method_exists($app, 'addCommand')) {
                        $app->addCommand($cmd);
                    } else {
                        // Compatibility layer for symfony/console <7.4
                        $app->add($cmd);
                    }
                    $app->setDefaultCommand((string) $cmd->getName(), true);
                    try {
                        $args = implode(' ', array_map(static function ($arg) { return ProcessExecutor::escape($arg); }, $additionalArgs));
                        // reusing the output from $this->io is mostly needed for tests, but generally speaking
                        // it does not hurt to keep the same stream as the current Application
                        if ($this->io instanceof ConsoleIO) {
                            $reflProp = new \ReflectionProperty($this->io, 'output');
                            if (\PHP_VERSION_ID < 80100) {
                                $reflProp->setAccessible(true);
                            }
                            $output = $reflProp->getValue($this->io);
                        } else {
                            $output = new ConsoleOutput();
                        }
                        $return = $app->run(new StringInput($event->getFlags()['script-alias-input'] ?? $args), $output);
                    } catch (\Exception $e) {
                        $message = "Script %s handling the %s event terminated with an exception";
                        $this->io->writeError('<error>'.sprintf($message, $callable, $event->getName()).'</error>', true, IOInterface::QUIET);
                        throw $e;
                    }
                } else {
                    $args = implode(' ', array_map(['Composer\Util\ProcessExecutor', 'escape'], $additionalArgs));

                    // @putenv does not receive arguments
                    if (strpos($callable, '@putenv ') === 0) {
                        $exec = $callable;
                    } else {
                        if (str_contains($callable, '@additional_args')) {
                            $exec = str_replace('@additional_args', $args, $callable);
                        } else {
                            $exec = $callable . ($args === '' ? '' : ' '.$args);
                        }
                    }

                    if ($this->io->isVerbose()) {
                        $this->io->writeError(sprintf('> %s: %s', $event->getName(), $exec));
                    } elseif (
                        // do not output the command being run when using `composer exec` as it is fairly obvious the user is running it
                        $event->getName() !== '__exec_command'
                        // do not output the command being run when using `composer <script-name>` as it is also fairly obvious the user is running it
                        && ($event->getFlags()['script-alias-input'] ?? null) === null
                    ) {
                        $this->io->writeError(sprintf('> %s', $exec));
                    }

                    $possibleLocalBinaries = $this->composer->getPackage()->getBinaries();
                    if (count($possibleLocalBinaries) > 0) {
                        foreach ($possibleLocalBinaries as $localExec) {
                            if (Preg::isMatch('{\b'.preg_quote($callable).'$}', $localExec)) {
                                $caller = BinaryInstaller::determineBinaryCaller($localExec);
                                $exec = Preg::replace('{^'.preg_quote($callable).'}', $caller . ' ' . $localExec, $exec);
                                break;
                            }
                        }
                    }

                    if (strpos($exec, '@putenv ') === 0) {
                        if (false === strpos($exec, '=')) {
                            Platform::clearEnv(substr($exec, 8));
                        } else {
                            [$var, $value] = explode('=', substr($exec, 8), 2);
                            Platform::putEnv($var, $value);
                        }

                        continue;
                    }
                    if (strpos($exec, '@php ') === 0) {
                        $pathAndArgs = substr($exec, 5);
                        if (Platform::isWindows()) {
                            $pathAndArgs = Preg::replaceCallback('{^\S+}', static function ($path) {
                                return str_replace('/', '\\', $path[0]);
                            }, $pathAndArgs);
                        }
                        // match somename (not in quote, and not a qualified path) and if it is not a valid path from CWD then try to find it
                        // in $PATH. This allows support for `@php foo` where foo is a binary name found in PATH but not an actual relative path
                        $matched = Preg::isMatchStrictGroups('{^[^\'"\s/\\\\]+}', $pathAndArgs, $match);
                        if ($matched && !file_exists($match[0])) {
                            $finder = new ExecutableFinder;
                            if ($pathToExec = $finder->find($match[0])) {
                                if (Platform::isWindows()) {
                                    $execWithoutExt = Preg::replace('{\.(exe|bat|cmd|com)$}i', '', $pathToExec);
                                    // prefer non-extension file if it exists when executing with PHP
                                    if (file_exists($execWithoutExt)) {
                                        $pathToExec = $execWithoutExt;
                                    }
                                    unset($execWithoutExt);
                                }
                                $pathAndArgs = $pathToExec . substr($pathAndArgs, strlen($match[0]));
                            }
                        }
                        $exec = $this->getPhpExecCommand() . ' ' . $pathAndArgs;
                    } else {
                        $finder = new PhpExecutableFinder();
                        $phpPath = $finder->find(false);
                        if ($phpPath) {
                            Platform::putEnv('PHP_BINARY', $phpPath);
                        }

                        if (Platform::isWindows()) {
                            $exec = Preg::replaceCallback('{^\S+}', static function ($path) {
                                return str_replace('/', '\\', $path[0]);
                            }, $exec);
                        }
                    }

                    // if composer is being executed, make sure it runs the expected composer from current path
                    // resolution, even if bin-dir contains composer too because the project requires composer/composer
                    // see https://github.com/composer/composer/issues/8748
                    if (strpos($exec, 'composer ') === 0) {
                        $exec = $this->getPhpExecCommand() . ' ' . ProcessExecutor::escape(Platform::getEnv('COMPOSER_BINARY')) . substr($exec, 8);
                    }

                    if (0 !== ($exitCode = $this->executeTty($exec))) {
                        $this->io->writeError(sprintf('<error>Script %s handling the %s event returned with error code '.$exitCode.'</error>', $callable, $event->getName()), true, IOInterface::QUIET);

                        throw new ScriptExecutionException('Error Output: '.$this->process->getErrorOutput(), $exitCode);
                    }
                }

                $returnMax = max($returnMax, $return);

                if ($event->isPropagationStopped()) {
                    break;
                }
            }
        } finally {
            $this->popEvent();

            $knownIdentifiers = [];
            foreach ($autoloadersBefore as $key => $cb) {
                $knownIdentifiers[$this->getCallbackIdentifier($cb)] = ['key' => $key, 'callback' => $cb];
            }
            foreach (spl_autoload_functions() as $cb) {
                // once we get to the first known autoloader, we can leave any appended autoloader without problems
                if (isset($knownIdentifiers[$this->getCallbackIdentifier($cb)]) && $knownIdentifiers[$this->getCallbackIdentifier($cb)]['key'] === 0) {
                    break;
                }

                // other newly appeared prepended autoloaders should be appended instead to ensure Composer loads its classes first
                if ($cb instanceof ClassLoader) {
                    $cb->unregister();
                    $cb->register(false);
                } else {
                    spl_autoload_unregister($cb);
                    spl_autoload_register($cb);
                }
            }
        }

        return $returnMax;
    }

    protected function executeTty(string $exec): int
    {
        if ($this->io->isInteractive()) {
            return $this->process->executeTty($exec);
        }

        return $this->process->execute($exec);
    }

    protected function getPhpExecCommand(): string
    {
        $finder = new PhpExecutableFinder();
        $phpPath = $finder->find(false);
        if (!$phpPath) {
            throw new \RuntimeException('Failed to locate PHP binary to execute '.$phpPath);
        }
        $phpArgs = $finder->findArguments();
        $phpArgs = \count($phpArgs) > 0 ? ' ' . implode(' ', $phpArgs) : '';
        $allowUrlFOpenFlag = ' -d allow_url_fopen=' . ProcessExecutor::escape(ini_get('allow_url_fopen'));
        $disableFunctionsFlag = ' -d disable_functions=' . ProcessExecutor::escape(ini_get('disable_functions'));
        $memoryLimitFlag = ' -d memory_limit=' . ProcessExecutor::escape(ini_get('memory_limit'));

        return ProcessExecutor::escape($phpPath) . $phpArgs . $allowUrlFOpenFlag . $disableFunctionsFlag . $memoryLimitFlag;
    }

    /**
     * @param Event  $event      Event invoking the PHP callable
     *
     * @return mixed
     */
    protected function executeEventPhpScript(string $className, string $methodName, Event $event)
    {
        if ($this->io->isVerbose()) {
            $this->io->writeError(sprintf('> %s: %s::%s', $event->getName(), $className, $methodName));
        } else {
            $this->io->writeError(sprintf('> %s::%s', $className, $methodName));
        }

        return $className::$methodName($event);
    }

    /**
     * Add a listener for a particular event
     *
     * @param string          $eventName The event name - typically a constant
     * @param callable|string $listener  A callable expecting an event argument, or a command string to be executed (same as a composer.json "scripts" entry)
     * @param int             $priority  A higher value represents a higher priority
     */
    public function addListener(string $eventName, $listener, int $priority = 0): void
    {
        $this->listeners[$eventName][$priority][] = $listener;
    }

    /**
     * @param callable|object $listener A callable or an object instance for which all listeners should be removed
     */
    public function removeListener($listener): void
    {
        foreach ($this->listeners as $eventName => $priorities) {
            foreach ($priorities as $priority => $listeners) {
                foreach ($listeners as $index => $candidate) {
                    if ($listener === $candidate || (is_array($candidate) && is_object($listener) && $candidate[0] === $listener)) {
                        unset($this->listeners[$eventName][$priority][$index]);
                    }
                }
            }
        }
    }

    /**
     * Adds object methods as listeners for the events in getSubscribedEvents
     *
     * @see EventSubscriberInterface
     */
    public function addSubscriber(EventSubscriberInterface $subscriber): void
    {
        foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
            if (is_string($params)) {
                $this->addListener($eventName, [$subscriber, $params]);
            } elseif (is_string($params[0])) {
                $this->addListener($eventName, [$subscriber, $params[0]], $params[1] ?? 0);
            } else {
                foreach ($params as $listener) {
                    $this->addListener($eventName, [$subscriber, $listener[0]], $listener[1] ?? 0);
                }
            }
        }
    }

    /**
     * Retrieves all listeners for a given event
     *
     * @return array<callable|string> All listeners: callables and scripts
     */
    protected function getListeners(Event $event): array
    {
        $scriptListeners = $this->runScripts ? $this->getScriptListeners($event) : [];

        if (!isset($this->listeners[$event->getName()][0])) {
            $this->listeners[$event->getName()][0] = [];
        }
        krsort($this->listeners[$event->getName()]);

        $listeners = $this->listeners;
        $listeners[$event->getName()][0] = array_merge($listeners[$event->getName()][0], $scriptListeners);

        return array_merge(...$listeners[$event->getName()]);
    }

    /**
     * Checks if an event has listeners registered
     */
    public function hasEventListeners(Event $event): bool
    {
        $listeners = $this->getListeners($event);

        return count($listeners) > 0;
    }

    /**
     * Finds all listeners defined as scripts in the package
     *
     * @param  Event $event Event object
     * @return string[] Listeners
     */
    protected function getScriptListeners(Event $event): array
    {
        $package = $this->composer->getPackage();
        $scripts = $package->getScripts();

        if (empty($scripts[$event->getName()])) {
            return [];
        }

        if (in_array($event->getName(), $this->skipScripts, true)) {
            $this->io->writeError('Skipped script listeners for <info>'.$event->getName().'</info> because of COMPOSER_SKIP_SCRIPTS', true, IOInterface::VERBOSE);

            return [];
        }

        assert($this->composer instanceof Composer, new \LogicException('This should only be reached with a fully loaded Composer'));

        if ($this->loader) {
            $this->loader->unregister();
        }

        $generator = $this->composer->getAutoloadGenerator();
        if ($event instanceof ScriptEvent) {
            $generator->setDevMode($event->isDevMode());
        }

        $packages = $this->composer->getRepositoryManager()->getLocalRepository()->getCanonicalPackages();
        $packageMap = $generator->buildPackageMap($this->composer->getInstallationManager(), $package, $packages);
        $map = $generator->parseAutoloads($packageMap, $package);
        $this->loader = $generator->createLoader($map, $this->composer->getConfig()->get('vendor-dir'));
        $this->loader->register(false);

        return $scripts[$event->getName()];
    }

    /**
     * Checks if string given references a class path and method
     */
    protected function isPhpScript(string $callable): bool
    {
        return false === strpos($callable, ' ') && false !== strpos($callable, '::');
    }

    /**
     * Checks if string given references a command class
     */
    protected function isCommandClass(string $callable): bool
    {
        return str_contains($callable, '\\') && !str_contains($callable, ' ') && str_ends_with($callable, 'Command');
    }

    /**
     * Checks if string given references a composer run-script
     */
    protected function isComposerScript(string $callable): bool
    {
        return strpos($callable, '@') === 0 && strpos($callable, '@php ') !== 0 && strpos($callable, '@putenv ') !== 0;
    }

    /**
     * Push an event to the stack of active event
     *
     * @throws \RuntimeException
     */
    protected function pushEvent(Event $event): int
    {
        $eventName = $event->getName();
        if (in_array($eventName, $this->eventStack)) {
            throw new \RuntimeException(sprintf("Circular call to script handler '%s' detected", $eventName));
        }

        return array_push($this->eventStack, $eventName);
    }

    /**
     * Pops the active event from the stack
     */
    protected function popEvent(): ?string
    {
        return array_pop($this->eventStack);
    }

    private function ensureBinDirIsInPath(): void
    {
        $pathEnv = 'PATH';

        // checking if only Path and not PATH is set then we probably need to update the Path env
        // on Windows getenv is case-insensitive so we cannot check it via Platform::getEnv and
        // we need to check in $_SERVER directly
        if (!isset($_SERVER[$pathEnv]) && isset($_SERVER['Path'])) {
            $pathEnv = 'Path';
        }

        // add the bin dir to the PATH to make local binaries of deps usable in scripts
        $binDir = $this->composer->getConfig()->get('bin-dir');
        if (is_dir($binDir)) {
            $binDir = realpath($binDir);
            $pathValue = (string) Platform::getEnv($pathEnv);
            if (!Preg::isMatch('{(^|'.PATH_SEPARATOR.')'.preg_quote($binDir).'($|'.PATH_SEPARATOR.')}', $pathValue)) {
                Platform::putEnv($pathEnv, $binDir.PATH_SEPARATOR.$pathValue);
            }
        }
    }

    /**
     * @param callable $cb DO NOT MOVE TO TYPE HINT as private autoload callbacks are not technically callable
     */
    private function getCallbackIdentifier($cb): string
    {
        if (is_string($cb)) {
            return 'fn:'.$cb;
        }
        if (is_object($cb)) {
            return 'obj:'.spl_object_hash($cb);
        }
        if (is_array($cb)) {
            return 'array:'.(is_string($cb[0]) ? $cb[0] : get_class($cb[0]) .'#'.spl_object_hash($cb[0])).'::'.$cb[1];
        }

        // not great but also do not want to break everything here
        return 'unsupported';
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\EventDispatcher;

/**
 * An EventSubscriber knows which events it is interested in.
 *
 * If an EventSubscriber is added to an EventDispatcher, the manager invokes
 * {@link getSubscribedEvents} and registers the subscriber as a listener for all
 * returned events.
 *
 * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
 * @author Jonathan Wage <jonwage@gmail.com>
 * @author Roman Borschel <roman@code-factory.org>
 * @author Bernhard Schussek <bschussek@gmail.com>
 */
interface EventSubscriberInterface
{
    /**
     * Returns an array of event names this subscriber wants to listen to.
     *
     * The array keys are event names and the value can be:
     *
     * * The method name to call (priority defaults to 0)
     * * An array composed of the method name to call and the priority
     * * An array of arrays composed of the method names to call and respective
     *   priorities, or 0 if unset
     *
     * For instance:
     *
     * * array('eventName' => 'methodName')
     * * array('eventName' => array('methodName', $priority))
     * * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
     *
     * @return array<string, string|array{0: string, 1?: int}|array<array{0: string, 1?: int}>> The event names to listen to
     */
    public static function getSubscribedEvents();
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\EventDispatcher;

/**
 * Thrown when a script running an external process exits with a non-0 status code
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ScriptExecutionException extends \RuntimeException
{
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\EventDispatcher;

/**
 * The base event class
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class Event
{
    /**
     * @var string This event's name
     */
    protected $name;

    /**
     * @var string[] Arguments passed by the user, these will be forwarded to CLI script handlers
     */
    protected $args;

    /**
     * @var mixed[] Flags usable in PHP script handlers
     */
    protected $flags;

    /**
     * @var bool Whether the event should not be passed to more listeners
     */
    private $propagationStopped = false;

    /**
     * Constructor.
     *
     * @param string   $name  The event name
     * @param string[] $args  Arguments passed by the user
     * @param mixed[]  $flags Optional flags to pass data not as argument
     */
    public function __construct(string $name, array $args = [], array $flags = [])
    {
        $this->name = $name;
        $this->args = $args;
        $this->flags = $flags;
    }

    /**
     * Returns the event's name.
     *
     * @return string The event name
     */
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * Returns the event's arguments.
     *
     * @return string[] The event arguments
     */
    public function getArguments(): array
    {
        return $this->args;
    }

    /**
     * Returns the event's flags.
     *
     * @return mixed[] The event flags
     */
    public function getFlags(): array
    {
        return $this->flags;
    }

    /**
     * Checks if stopPropagation has been called
     *
     * @return bool Whether propagation has been stopped
     */
    public function isPropagationStopped(): bool
    {
        return $this->propagationStopped;
    }

    /**
     * Prevents the event from being passed to further listeners
     */
    public function stopPropagation(): void
    {
        $this->propagationStopped = true;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Plugin\Capability;

/**
 * Commands Provider Interface
 *
 * This capability will receive an array with 'composer' and 'io' keys as
 * constructor argument. Those contain Composer\Composer and Composer\IO\IOInterface
 * instances. It also contains a 'plugin' key containing the plugin instance that
 * created the capability.
 *
 * @author Jérémy Derussé <jeremy@derusse.com>
 */
interface CommandProvider extends Capability
{
    /**
     * Retrieves an array of commands
     *
     * @return \Composer\Command\BaseCommand[]
     */
    public function getCommands();
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Plugin\Capability;

/**
 * Marker interface for Plugin capabilities.
 * Every new Capability which is added to the Plugin API must implement this interface.
 *
 * @api
 */
interface Capability
{
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Plugin;

use Composer\Composer;
use Composer\IO\IOInterface;

/**
 * Plugin interface
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
interface PluginInterface
{
    /**
     * Version number of the internal composer-plugin-api package
     *
     * This is used to denote the API version of Plugin specific
     * features, but is also bumped to a new major if Composer
     * includes a major break in internal APIs which are susceptible
     * to be used by plugins.
     *
     * @var string
     */
    public const PLUGIN_API_VERSION = '2.6.0';

    /**
     * Apply plugin modifications to Composer
     *
     * @return void
     */
    public function activate(Composer $composer, IOInterface $io);

    /**
     * Remove any hooks from Composer
     *
     * This will be called when a plugin is deactivated before being
     * uninstalled, but also before it gets upgraded to a new version
     * so the old one can be deactivated and the new one activated.
     *
     * @return void
     */
    public function deactivate(Composer $composer, IOInterface $io);

    /**
     * Prepare the plugin to be uninstalled
     *
     * This will be called after deactivate.
     *
     * @return void
     */
    public function uninstall(Composer $composer, IOInterface $io);
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Plugin;

use Composer\EventDispatcher\Event;
use Composer\Package\PackageInterface;

/**
 * The post file download event.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class PostFileDownloadEvent extends Event
{
    /**
     * @var string
     */
    private $fileName;

    /**
     * @var string|null
     */
    private $checksum;

    /**
     * @var string
     */
    private $url;

    /**
     * @var mixed
     */
    private $context;

    /**
     * @var string
     */
    private $type;

    /**
     * Constructor.
     *
     * @param string      $name     The event name
     * @param string|null $fileName The file name
     * @param string|null $checksum The checksum
     * @param string      $url      The processed url
     * @param string      $type     The type (package or metadata).
     * @param mixed       $context  Additional context for the download.
     */
    public function __construct(string $name, ?string $fileName, ?string $checksum, string $url, string $type, $context = null)
    {
        /** @phpstan-ignore instanceof.alwaysFalse, booleanAnd.alwaysFalse */
        if ($context === null && $type instanceof PackageInterface) {
            $context = $type;
            $type = 'package';
            trigger_error('PostFileDownloadEvent::__construct should receive a $type=package and the package object in $context since Composer 2.1.', E_USER_DEPRECATED);
        }

        parent::__construct($name);
        $this->fileName = $fileName;
        $this->checksum = $checksum;
        $this->url = $url;
        $this->context = $context;
        $this->type = $type;
    }

    /**
     * Retrieves the target file name location.
     *
     * If this download is of type metadata, null is returned.
     */
    public function getFileName(): ?string
    {
        return $this->fileName;
    }

    /**
     * Gets the checksum.
     */
    public function getChecksum(): ?string
    {
        return $this->checksum;
    }

    /**
     * Gets the processed URL.
     */
    public function getUrl(): string
    {
        return $this->url;
    }

    /**
     * Returns the context of this download, if any.
     *
     * If this download is of type package, the package object is returned. If
     * this download is of type metadata, an array{response: Response, repository: RepositoryInterface} is returned.
     *
     * @return mixed
     */
    public function getContext()
    {
        return $this->context;
    }

    /**
     * Get the package.
     *
     * If this download is of type metadata, null is returned.
     *
     * @return \Composer\Package\PackageInterface|null The package.
     * @deprecated Use getContext instead
     */
    public function getPackage(): ?PackageInterface
    {
        trigger_error('PostFileDownloadEvent::getPackage is deprecated since Composer 2.1, use getContext instead.', E_USER_DEPRECATED);
        $context = $this->getContext();

        return $context instanceof PackageInterface ? $context : null;
    }

    /**
     * Returns the type of this download (package, metadata).
     */
    public function getType(): string
    {
        return $this->type;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Plugin;

/**
 * Plugins which need to expose various implementations
 * of the Composer Plugin Capabilities must have their
 * declared Plugin class implementing this interface.
 *
 * @api
 */
interface Capable
{
    /**
     * Method by which a Plugin announces its API implementations, through an array
     * with a special structure.
     *
     * The key must be a string, representing a fully qualified class/interface name
     * which Composer Plugin API exposes.
     * The value must be a string as well, representing the fully qualified class name
     * of the implementing class.
     *
     * @tutorial
     *
     * return array(
     *     'Composer\Plugin\Capability\CommandProvider' => 'My\CommandProvider',
     *     'Composer\Plugin\Capability\Validator'       => 'My\Validator',
     * );
     *
     * @return string[]
     */
    public function getCapabilities();
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Plugin;

/**
 * The Plugin Events.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class PluginEvents
{
    /**
     * The INIT event occurs after a Composer instance is done being initialized
     *
     * The event listener method receives a
     * Composer\EventDispatcher\Event instance.
     *
     * @var string
     */
    public const INIT = 'init';

    /**
     * The COMMAND event occurs as a command begins
     *
     * The event listener method receives a
     * Composer\Plugin\CommandEvent instance.
     *
     * @var string
     */
    public const COMMAND = 'command';

    /**
     * The PRE_FILE_DOWNLOAD event occurs before downloading a file
     *
     * The event listener method receives a
     * Composer\Plugin\PreFileDownloadEvent instance.
     *
     * @var string
     */
    public const PRE_FILE_DOWNLOAD = 'pre-file-download';

    /**
     * The POST_FILE_DOWNLOAD event occurs after downloading a package dist file
     *
     * The event listener method receives a
     * Composer\Plugin\PostFileDownloadEvent instance.
     *
     * @var string
     */
    public const POST_FILE_DOWNLOAD = 'post-file-download';

    /**
     * The PRE_COMMAND_RUN event occurs before a command is executed and lets you modify the input arguments/options
     *
     * The event listener method receives a
     * Composer\Plugin\PreCommandRunEvent instance.
     *
     * @var string
     */
    public const PRE_COMMAND_RUN = 'pre-command-run';

    /**
     * The PRE_POOL_CREATE event occurs before the Pool of packages is created, and lets
     * you filter the list of packages which is going to enter the Solver
     *
     * The event listener method receives a
     * Composer\Plugin\PrePoolCreateEvent instance.
     *
     * @var string
     */
    public const PRE_POOL_CREATE = 'pre-pool-create';
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Plugin;

use Composer\EventDispatcher\Event;
use Symfony\Component\Console\Input\InputInterface;

/**
 * The pre command run event.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PreCommandRunEvent extends Event
{
    /**
     * @var InputInterface
     */
    private $input;

    /**
     * @var string
     */
    private $command;

    /**
     * Constructor.
     *
     * @param string         $name    The event name
     * @param string         $command The command about to be executed
     */
    public function __construct(string $name, InputInterface $input, string $command)
    {
        parent::__construct($name);
        $this->input = $input;
        $this->command = $command;
    }

    /**
     * Returns the console input
     */
    public function getInput(): InputInterface
    {
        return $this->input;
    }

    /**
     * Returns the command about to be executed
     */
    public function getCommand(): string
    {
        return $this->command;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Plugin;

use Composer\EventDispatcher\Event;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * An event for all commands.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class CommandEvent extends Event
{
    /**
     * @var string
     */
    private $commandName;

    /**
     * @var InputInterface
     */
    private $input;

    /**
     * @var OutputInterface
     */
    private $output;

    /**
     * Constructor.
     *
     * @param string          $name        The event name
     * @param string          $commandName The command name
     * @param mixed[]         $args        Arguments passed by the user
     * @param mixed[]         $flags       Optional flags to pass data not as argument
     */
    public function __construct(string $name, string $commandName, InputInterface $input, OutputInterface $output, array $args = [], array $flags = [])
    {
        parent::__construct($name, $args, $flags);
        $this->commandName = $commandName;
        $this->input = $input;
        $this->output = $output;
    }

    /**
     * Returns the command input interface
     */
    public function getInput(): InputInterface
    {
        return $this->input;
    }

    /**
     * Retrieves the command output interface
     */
    public function getOutput(): OutputInterface
    {
        return $this->output;
    }

    /**
     * Retrieves the name of the command being run
     */
    public function getCommandName(): string
    {
        return $this->commandName;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Plugin;

use Composer\EventDispatcher\Event;
use Composer\Repository\RepositoryInterface;
use Composer\DependencyResolver\Request;
use Composer\Package\BasePackage;

/**
 * The pre command run event.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PrePoolCreateEvent extends Event
{
    /**
     * @var RepositoryInterface[]
     */
    private $repositories;
    /**
     * @var Request
     */
    private $request;
    /**
     * @var int[] array of stability => BasePackage::STABILITY_* value
     * @phpstan-var array<string, BasePackage::STABILITY_*>
     */
    private $acceptableStabilities;
    /**
     * @var int[] array of package name => BasePackage::STABILITY_* value
     * @phpstan-var array<string, BasePackage::STABILITY_*>
     */
    private $stabilityFlags;
    /**
     * @var array[] of package => version => [alias, alias_normalized]
     * @phpstan-var array<string, array<string, array{alias: string, alias_normalized: string}>>
     */
    private $rootAliases;
    /**
     * @var string[]
     * @phpstan-var array<string, string>
     */
    private $rootReferences;
    /**
     * @var BasePackage[]
     */
    private $packages;
    /**
     * @var BasePackage[]
     */
    private $unacceptableFixedPackages;

    /**
     * @param string                $name                   The event name
     * @param RepositoryInterface[] $repositories
     * @param int[]                 $acceptableStabilities  array of stability => BasePackage::STABILITY_* value
     * @param int[]                 $stabilityFlags         array of package name => BasePackage::STABILITY_* value
     * @param array[]               $rootAliases            array of package => version => [alias, alias_normalized]
     * @param string[]              $rootReferences
     * @param BasePackage[]         $packages
     * @param BasePackage[]         $unacceptableFixedPackages
     *
     * @phpstan-param array<string, BasePackage::STABILITY_*> $acceptableStabilities
     * @phpstan-param array<string, BasePackage::STABILITY_*> $stabilityFlags
     * @phpstan-param array<string, array<string, array{alias: string, alias_normalized: string}>> $rootAliases
     * @phpstan-param array<string, string> $rootReferences
     */
    public function __construct(string $name, array $repositories, Request $request, array $acceptableStabilities, array $stabilityFlags, array $rootAliases, array $rootReferences, array $packages, array $unacceptableFixedPackages)
    {
        parent::__construct($name);

        $this->repositories = $repositories;
        $this->request = $request;
        $this->acceptableStabilities = $acceptableStabilities;
        $this->stabilityFlags = $stabilityFlags;
        $this->rootAliases = $rootAliases;
        $this->rootReferences = $rootReferences;
        $this->packages = $packages;
        $this->unacceptableFixedPackages = $unacceptableFixedPackages;
    }

    /**
     * @return RepositoryInterface[]
     */
    public function getRepositories(): array
    {
        return $this->repositories;
    }

    public function getRequest(): Request
    {
        return $this->request;
    }

    /**
     * @return int[] array of stability => BasePackage::STABILITY_* value
     * @phpstan-return array<string, BasePackage::STABILITY_*>
     */
    public function getAcceptableStabilities(): array
    {
        return $this->acceptableStabilities;
    }

    /**
     * @return int[] array of package name => BasePackage::STABILITY_* value
     * @phpstan-return array<string, BasePackage::STABILITY_*>
     */
    public function getStabilityFlags(): array
    {
        return $this->stabilityFlags;
    }

    /**
     * @return array[] of package => version => [alias, alias_normalized]
     * @phpstan-return array<string, array<string, array{alias: string, alias_normalized: string}>>
     */
    public function getRootAliases(): array
    {
        return $this->rootAliases;
    }

    /**
     * @return string[]
     * @phpstan-return array<string, string>
     */
    public function getRootReferences(): array
    {
        return $this->rootReferences;
    }

    /**
     * @return BasePackage[]
     */
    public function getPackages(): array
    {
        return $this->packages;
    }

    /**
     * @return BasePackage[]
     */
    public function getUnacceptableFixedPackages(): array
    {
        return $this->unacceptableFixedPackages;
    }

    /**
     * @param BasePackage[] $packages
     */
    public function setPackages(array $packages): void
    {
        $this->packages = $packages;
    }

    /**
     * @param BasePackage[] $packages
     */
    public function setUnacceptableFixedPackages(array $packages): void
    {
        $this->unacceptableFixedPackages = $packages;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Plugin;

use Composer\Composer;
use Composer\EventDispatcher\EventSubscriberInterface;
use Composer\Installer\InstallerInterface;
use Composer\IO\IOInterface;
use Composer\Package\BasePackage;
use Composer\Package\CompletePackage;
use Composer\Package\Locker;
use Composer\Package\Package;
use Composer\Package\RootPackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\PartialComposer;
use Composer\Pcre\Preg;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\InstalledRepository;
use Composer\Repository\RepositoryUtils;
use Composer\Repository\RootPackageRepository;
use Composer\Package\PackageInterface;
use Composer\Package\Link;
use Composer\Semver\Constraint\Constraint;
use Composer\Plugin\Capability\Capability;
use Composer\Util\PackageSorter;

/**
 * Plugin manager
 *
 * @author Nils Adermann <naderman@naderman.de>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PluginManager
{
    /** @var Composer */
    protected $composer;
    /** @var IOInterface */
    protected $io;
    /** @var PartialComposer|null */
    protected $globalComposer;
    /** @var VersionParser */
    protected $versionParser;
    /** @var bool|'local'|'global' */
    protected $disablePlugins = false;

    /** @var array<PluginInterface> */
    protected $plugins = [];
    /** @var array<string, array<PluginInterface|InstallerInterface>> */
    protected $registeredPlugins = [];

    /**
     * @var array<non-empty-string, bool>|null
     */
    private $allowPluginRules;

    /**
     * @var array<non-empty-string, bool>|null
     */
    private $allowGlobalPluginRules;

    /** @var bool */
    private $runningInGlobalDir = false;

    /** @var int */
    private static $classCounter = 0;

    /**
     * @param bool|'local'|'global' $disablePlugins Whether plugins should not be loaded, can be set to local or global to only disable local/global plugins
     */
    public function __construct(IOInterface $io, Composer $composer, ?PartialComposer $globalComposer = null, $disablePlugins = false)
    {
        $this->io = $io;
        $this->composer = $composer;
        $this->globalComposer = $globalComposer;
        $this->versionParser = new VersionParser();
        $this->disablePlugins = $disablePlugins;
        $this->allowPluginRules = $this->parseAllowedPlugins($composer->getConfig()->get('allow-plugins'), $composer->getLocker());
        $this->allowGlobalPluginRules = $this->parseAllowedPlugins($globalComposer !== null ? $globalComposer->getConfig()->get('allow-plugins') : false);
    }

    public function setRunningInGlobalDir(bool $runningInGlobalDir): void
    {
        $this->runningInGlobalDir = $runningInGlobalDir;
    }

    /**
     * Loads all plugins from currently installed plugin packages
     */
    public function loadInstalledPlugins(): void
    {
        if (!$this->arePluginsDisabled('local')) {
            $repo = $this->composer->getRepositoryManager()->getLocalRepository();
            $this->loadRepository($repo, false, $this->composer->getPackage());
        }

        if ($this->globalComposer !== null && !$this->arePluginsDisabled('global')) {
            $this->loadRepository($this->globalComposer->getRepositoryManager()->getLocalRepository(), true);
        }
    }

    /**
     * Deactivate all plugins from currently installed plugin packages
     */
    public function deactivateInstalledPlugins(): void
    {
        if (!$this->arePluginsDisabled('local')) {
            $repo = $this->composer->getRepositoryManager()->getLocalRepository();
            $this->deactivateRepository($repo, false);
        }

        if ($this->globalComposer !== null && !$this->arePluginsDisabled('global')) {
            $this->deactivateRepository($this->globalComposer->getRepositoryManager()->getLocalRepository(), true);
        }
    }

    /**
     * Gets all currently active plugin instances
     *
     * @return array<PluginInterface> plugins
     */
    public function getPlugins(): array
    {
        return $this->plugins;
    }

    /**
     * Gets global composer or null when main composer is not fully loaded
     */
    public function getGlobalComposer(): ?PartialComposer
    {
        return $this->globalComposer;
    }

    /**
     * Register a plugin package, activate it etc.
     *
     * If it's of type composer-installer it is registered as an installer
     * instead for BC
     *
     * @param bool             $failOnMissingClasses By default this silently skips plugins that can not be found, but if set to true it fails with an exception
     * @param bool             $isGlobalPlugin       Set to true to denote plugins which are installed in the global Composer directory
     *
     * @throws \UnexpectedValueException
     */
    public function registerPackage(PackageInterface $package, bool $failOnMissingClasses = false, bool $isGlobalPlugin = false): void
    {
        if ($this->arePluginsDisabled($isGlobalPlugin ? 'global' : 'local')) {
            $this->io->writeError('<warning>The "'.$package->getName().'" plugin was not loaded as plugins are disabled.</warning>');
            return;
        }

        if ($package->getType() === 'composer-plugin') {
            $requiresComposer = null;
            foreach ($package->getRequires() as $link) { /** @var Link $link */
                if ('composer-plugin-api' === $link->getTarget()) {
                    $requiresComposer = $link->getConstraint();
                    break;
                }
            }

            if (!$requiresComposer) {
                throw new \RuntimeException("Plugin ".$package->getName()." is missing a require statement for a version of the composer-plugin-api package.");
            }

            $currentPluginApiVersion = $this->getPluginApiVersion();
            $currentPluginApiConstraint = new Constraint('==', $this->versionParser->normalize($currentPluginApiVersion));

            if ($requiresComposer->getPrettyString() === $this->getPluginApiVersion()) {
                $this->io->writeError('<warning>The "' . $package->getName() . '" plugin requires composer-plugin-api '.$this->getPluginApiVersion().', this *WILL* break in the future and it should be fixed ASAP (require ^'.$this->getPluginApiVersion().' instead for example).</warning>');
            } elseif (!$requiresComposer->matches($currentPluginApiConstraint)) {
                $this->io->writeError('<warning>The "' . $package->getName() . '" plugin '.($isGlobalPlugin || $this->runningInGlobalDir ? '(installed globally) ' : '').'was skipped because it requires a Plugin API version ("' . $requiresComposer->getPrettyString() . '") that does not match your Composer installation ("' . $currentPluginApiVersion . '"). You may need to run composer update with the "--no-plugins" option.</warning>');

                return;
            }

            if ($package->getName() === 'symfony/flex' && Preg::isMatch('{^[0-9.]+$}', $package->getVersion()) && version_compare($package->getVersion(), '1.9.8', '<')) {
                $this->io->writeError('<warning>The "' . $package->getName() . '" plugin '.($isGlobalPlugin || $this->runningInGlobalDir ? '(installed globally) ' : '').'was skipped because it is not compatible with Composer 2+. Make sure to update it to version 1.9.8 or greater.</warning>');

                return;
            }
        }

        if (!$this->isPluginAllowed($package->getName(), $isGlobalPlugin, true === ($package->getExtra()['plugin-optional'] ?? false))) {
            $this->io->writeError('Skipped loading "'.$package->getName() . '" '.($isGlobalPlugin || $this->runningInGlobalDir ? '(installed globally) ' : '').'as it is not in config.allow-plugins', true, IOInterface::DEBUG);

            return;
        }

        $oldInstallerPlugin = ($package->getType() === 'composer-installer');

        if (isset($this->registeredPlugins[$package->getName()])) {
            return;
        }

        $extra = $package->getExtra();
        if (empty($extra['class'])) {
            throw new \UnexpectedValueException('Error while installing '.$package->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.');
        }
        $classes = is_array($extra['class']) ? $extra['class'] : [$extra['class']];

        $localRepo = $this->composer->getRepositoryManager()->getLocalRepository();
        $globalRepo = $this->globalComposer !== null ? $this->globalComposer->getRepositoryManager()->getLocalRepository() : null;

        $rootPackage = clone $this->composer->getPackage();

        // clear files autoload rules from the root package as the root dependencies are not
        // necessarily all present yet when booting this runtime autoloader
        $rootPackageAutoloads = $rootPackage->getAutoload();
        $rootPackageAutoloads['files'] = [];
        $rootPackage->setAutoload($rootPackageAutoloads);
        $rootPackageAutoloads = $rootPackage->getDevAutoload();
        $rootPackageAutoloads['files'] = [];
        $rootPackage->setDevAutoload($rootPackageAutoloads);
        unset($rootPackageAutoloads);

        $rootPackageRepo = new RootPackageRepository($rootPackage);
        $installedRepo = new InstalledRepository([$localRepo, $rootPackageRepo]);
        if ($globalRepo) {
            $installedRepo->addRepository($globalRepo);
        }

        $autoloadPackages = [$package->getName() => $package];
        $autoloadPackages = $this->collectDependencies($installedRepo, $autoloadPackages, $package);

        $generator = $this->composer->getAutoloadGenerator();
        $autoloads = [[$rootPackage, '']];
        foreach ($autoloadPackages as $autoloadPackage) {
            if ($autoloadPackage === $rootPackage) {
                continue;
            }

            $installPath = $this->getInstallPath($autoloadPackage, $globalRepo && $globalRepo->hasPackage($autoloadPackage));
            if ($installPath === null) {
                continue;
            }
            $autoloads[] = [$autoloadPackage, $installPath];
        }

        $map = $generator->parseAutoloads($autoloads, $rootPackage);
        $classLoader = $generator->createLoader($map, $this->composer->getConfig()->get('vendor-dir'));
        $classLoader->register(false);

        foreach ($map['files'] as $fileIdentifier => $file) {
            // exclude laminas/laminas-zendframework-bridge:src/autoload.php as it breaks Composer in some conditions
            // see https://github.com/composer/composer/issues/10349 and https://github.com/composer/composer/issues/10401
            // this hack can be removed once this deprecated package stop being installed
            if ($fileIdentifier === '7e9bd612cc444b3eed788ebbe46263a0') {
                continue;
            }
            \Composer\Autoload\composerRequire($fileIdentifier, $file);
        }

        foreach ($classes as $class) {
            if (class_exists($class, false)) {
                $class = trim($class, '\\');
                $path = $classLoader->findFile($class);
                $code = file_get_contents($path);
                $separatorPos = strrpos($class, '\\');
                $className = $class;
                if ($separatorPos) {
                    $className = substr($class, $separatorPos + 1);
                }
                $code = Preg::replace('{^((?:(?:final|readonly)\s+)*(?:\s*))class\s+('.preg_quote($className).')}mi', '$1class $2_composer_tmp'.self::$classCounter, $code, 1);
                $code = strtr($code, [
                    '__FILE__' => var_export($path, true),
                    '__DIR__' => var_export(dirname($path), true),
                    '__CLASS__' => var_export($class, true),
                ]);
                $code = Preg::replace('/^\s*<\?(php)?/i', '', $code, 1);
                eval($code);
                $class .= '_composer_tmp'.self::$classCounter;
                self::$classCounter++;
            }

            if ($oldInstallerPlugin) {
                if (!is_a($class, 'Composer\Installer\InstallerInterface', true)) {
                    throw new \RuntimeException('Could not activate plugin "'.$package->getName().'" as "'.$class.'" does not implement Composer\Installer\InstallerInterface');
                }
                $this->io->writeError('<warning>Loading "'.$package->getName() . '" '.($isGlobalPlugin || $this->runningInGlobalDir ? '(installed globally) ' : '').'which is a legacy composer-installer built for Composer 1.x, it is likely to cause issues as you are running Composer 2.x.</warning>');
                $installer = new $class($this->io, $this->composer);
                $this->composer->getInstallationManager()->addInstaller($installer);
                $this->registeredPlugins[$package->getName()][] = $installer;
            } elseif (class_exists($class)) {
                if (!is_a($class, 'Composer\Plugin\PluginInterface', true)) {
                    throw new \RuntimeException('Could not activate plugin "'.$package->getName().'" as "'.$class.'" does not implement Composer\Plugin\PluginInterface');
                }
                $plugin = new $class();
                $this->addPlugin($plugin, $isGlobalPlugin, $package);
                $this->registeredPlugins[$package->getName()][] = $plugin;
            } elseif ($failOnMissingClasses) {
                throw new \UnexpectedValueException('Plugin '.$package->getName().' could not be initialized, class not found: '.$class);
            }
        }
    }

    /**
     * Deactivates a plugin package
     *
     * If it's of type composer-installer it is unregistered from the installers
     * instead for BC
     *
     * @throws \UnexpectedValueException
     */
    public function deactivatePackage(PackageInterface $package): void
    {
        if (!isset($this->registeredPlugins[$package->getName()])) {
            return;
        }

        $plugins = $this->registeredPlugins[$package->getName()];
        foreach ($plugins as $plugin) {
            if ($plugin instanceof InstallerInterface) {
                $this->composer->getInstallationManager()->removeInstaller($plugin);
            } else {
                $this->removePlugin($plugin);
            }
        }
        unset($this->registeredPlugins[$package->getName()]);
    }

    /**
     * Uninstall a plugin package
     *
     * If it's of type composer-installer it is unregistered from the installers
     * instead for BC
     *
     * @throws \UnexpectedValueException
     */
    public function uninstallPackage(PackageInterface $package): void
    {
        if (!isset($this->registeredPlugins[$package->getName()])) {
            return;
        }

        $plugins = $this->registeredPlugins[$package->getName()];
        foreach ($plugins as $plugin) {
            if ($plugin instanceof InstallerInterface) {
                $this->composer->getInstallationManager()->removeInstaller($plugin);
            } else {
                $this->removePlugin($plugin);
                $this->uninstallPlugin($plugin);
            }
        }
        unset($this->registeredPlugins[$package->getName()]);
    }

    /**
     * Returns the version of the internal composer-plugin-api package.
     */
    protected function getPluginApiVersion(): string
    {
        return PluginInterface::PLUGIN_API_VERSION;
    }

    /**
     * Adds a plugin, activates it and registers it with the event dispatcher
     *
     * Ideally plugin packages should be registered via registerPackage, but if you use Composer
     * programmatically and want to register a plugin class directly this is a valid way
     * to do it.
     *
     * @param PluginInterface   $plugin        plugin instance
     * @param ?PackageInterface $sourcePackage Package from which the plugin comes from
     */
    public function addPlugin(PluginInterface $plugin, bool $isGlobalPlugin = false, ?PackageInterface $sourcePackage = null): void
    {
        if ($this->arePluginsDisabled($isGlobalPlugin ? 'global' : 'local')) {
            return;
        }

        if ($sourcePackage === null) {
            trigger_error('Calling PluginManager::addPlugin without $sourcePackage is deprecated, if you are using this please get in touch with us to explain the use case', E_USER_DEPRECATED);
        } elseif (!$this->isPluginAllowed($sourcePackage->getName(), $isGlobalPlugin, true === ($sourcePackage->getExtra()['plugin-optional'] ?? false))) {
            $this->io->writeError('Skipped loading "'.get_class($plugin).' from '.$sourcePackage->getName() . '" '.($isGlobalPlugin || $this->runningInGlobalDir ? '(installed globally) ' : '').' as it is not in config.allow-plugins', true, IOInterface::DEBUG);

            return;
        }

        $details = [];
        if ($sourcePackage) {
            $details[] = 'from '.$sourcePackage->getName();
        }
        if ($isGlobalPlugin || $this->runningInGlobalDir) {
            $details[] = 'installed globally';
        }
        $this->io->writeError('Loading plugin '.get_class($plugin).($details ? ' ('.implode(', ', $details).')' : ''), true, IOInterface::DEBUG);
        $this->plugins[] = $plugin;
        $plugin->activate($this->composer, $this->io);

        if ($plugin instanceof EventSubscriberInterface) {
            $this->composer->getEventDispatcher()->addSubscriber($plugin);
        }
    }

    /**
     * Removes a plugin, deactivates it and removes any listener the plugin has set on the plugin instance
     *
     * Ideally plugin packages should be deactivated via deactivatePackage, but if you use Composer
     * programmatically and want to deregister a plugin class directly this is a valid way
     * to do it.
     *
     * @param PluginInterface $plugin plugin instance
     */
    public function removePlugin(PluginInterface $plugin): void
    {
        $index = array_search($plugin, $this->plugins, true);
        if ($index === false) {
            return;
        }

        $this->io->writeError('Unloading plugin '.get_class($plugin), true, IOInterface::DEBUG);
        unset($this->plugins[$index]);
        $plugin->deactivate($this->composer, $this->io);

        $this->composer->getEventDispatcher()->removeListener($plugin);
    }

    /**
     * Notifies a plugin it is being uninstalled and should clean up
     *
     * Ideally plugin packages should be uninstalled via uninstallPackage, but if you use Composer
     * programmatically and want to deregister a plugin class directly this is a valid way
     * to do it.
     *
     * @param PluginInterface $plugin plugin instance
     */
    public function uninstallPlugin(PluginInterface $plugin): void
    {
        $this->io->writeError('Uninstalling plugin '.get_class($plugin), true, IOInterface::DEBUG);
        $plugin->uninstall($this->composer, $this->io);
    }

    /**
     * Load all plugins and installers from a repository
     *
     * If a plugin requires another plugin, the required one will be loaded first
     *
     * Note that plugins in the specified repository that rely on events that
     * have fired prior to loading will be missed. This means you likely want to
     * call this method as early as possible.
     *
     * @param RepositoryInterface $repo Repository to scan for plugins to install
     *
     * @phpstan-param ($isGlobalRepo is true ? null : RootPackageInterface) $rootPackage
     *
     * @throws \RuntimeException
     */
    private function loadRepository(RepositoryInterface $repo, bool $isGlobalRepo, ?RootPackageInterface $rootPackage = null): void
    {
        $packages = $repo->getPackages();

        $weights = [];
        foreach ($packages as $package) {
            if ($package->getType() === 'composer-plugin') {
                $extra = $package->getExtra();
                if ($package->getName() === 'composer/installers' || true === ($extra['plugin-modifies-install-path'] ?? false)) {
                    $weights[$package->getName()] = -10000;
                }
            }
        }

        $sortedPackages = PackageSorter::sortPackages($packages, $weights);
        if (!$isGlobalRepo) {
            $requiredPackages = RepositoryUtils::filterRequiredPackages($packages, $rootPackage, true);
        }

        foreach ($sortedPackages as $package) {
            if (!($package instanceof CompletePackage)) {
                continue;
            }

            if (!in_array($package->getType(), ['composer-plugin', 'composer-installer'], true)) {
                continue;
            }

            if (
                !$isGlobalRepo
                && !in_array($package, $requiredPackages, true)
                && !$this->isPluginAllowed($package->getName(), false, true, false)
            ) {
                $this->io->writeError('<warning>The "'.$package->getName().'" plugin was not loaded as it is not listed in allow-plugins and is not required by the root package anymore.</warning>');
                continue;
            }

            if ('composer-plugin' === $package->getType()) {
                $this->registerPackage($package, false, $isGlobalRepo);
            // Backward compatibility
            } elseif ('composer-installer' === $package->getType()) {
                $this->registerPackage($package, false, $isGlobalRepo);
            }
        }
    }

    /**
     * Deactivate all plugins and installers from a repository
     *
     * If a plugin requires another plugin, the required one will be deactivated last
     *
     * @param RepositoryInterface $repo Repository to scan for plugins to install
     */
    private function deactivateRepository(RepositoryInterface $repo, bool $isGlobalRepo): void
    {
        $packages = $repo->getPackages();
        $sortedPackages = array_reverse(PackageSorter::sortPackages($packages));

        foreach ($sortedPackages as $package) {
            if (!($package instanceof CompletePackage)) {
                continue;
            }
            if ('composer-plugin' === $package->getType()) {
                $this->deactivatePackage($package);
            // Backward compatibility
            } elseif ('composer-installer' === $package->getType()) {
                $this->deactivatePackage($package);
            }
        }
    }

    /**
     * Recursively generates a map of package names to packages for all deps
     *
     * @param InstalledRepository             $installedRepo Set of local repos
     * @param array<string, PackageInterface> $collected     Current state of the map for recursion
     * @param PackageInterface                $package       The package to analyze
     *
     * @return array<string, PackageInterface> Map of package names to packages
     */
    private function collectDependencies(InstalledRepository $installedRepo, array $collected, PackageInterface $package): array
    {
        foreach ($package->getRequires() as $requireLink) {
            foreach ($installedRepo->findPackagesWithReplacersAndProviders($requireLink->getTarget()) as $requiredPackage) {
                if (!isset($collected[$requiredPackage->getName()])) {
                    $collected[$requiredPackage->getName()] = $requiredPackage;
                    $collected = $this->collectDependencies($installedRepo, $collected, $requiredPackage);
                }
            }
        }

        return $collected;
    }

    /**
     * Retrieves the path a package is installed to.
     *
     * @param bool             $global  Whether this is a global package
     *
     * @return string|null Install path
     */
    private function getInstallPath(PackageInterface $package, bool $global = false): ?string
    {
        if (!$global) {
            return $this->composer->getInstallationManager()->getInstallPath($package);
        }

        assert(null !== $this->globalComposer);

        return $this->globalComposer->getInstallationManager()->getInstallPath($package);
    }

    /**
     * @throws \RuntimeException On empty or non-string implementation class name value
     * @return null|string       The fully qualified class of the implementation or null if Plugin is not of Capable type or does not provide it
     */
    protected function getCapabilityImplementationClassName(PluginInterface $plugin, string $capability): ?string
    {
        if (!($plugin instanceof Capable)) {
            return null;
        }

        $capabilities = (array) $plugin->getCapabilities();

        if (!empty($capabilities[$capability]) && is_string($capabilities[$capability]) && trim($capabilities[$capability])) {
            return trim($capabilities[$capability]);
        }

        if (
            array_key_exists($capability, $capabilities)
            && (empty($capabilities[$capability]) || !is_string($capabilities[$capability]) || !trim($capabilities[$capability]))
        ) {
            throw new \UnexpectedValueException('Plugin '.get_class($plugin).' provided invalid capability class name(s), got '.var_export($capabilities[$capability], true));
        }

        return null;
    }

    /**
     * @template CapabilityClass of Capability
     * @param  class-string<CapabilityClass> $capabilityClassName The fully qualified name of the API interface which the plugin may provide
     *                                                            an implementation of.
     * @param  array<mixed>                  $ctorArgs            Arguments passed to Capability's constructor.
     *                                                            Keeping it an array will allow future values to be passed w\o changing the signature.
     * @phpstan-param class-string<CapabilityClass> $capabilityClassName
     * @phpstan-return null|CapabilityClass
     */
    public function getPluginCapability(PluginInterface $plugin, $capabilityClassName, array $ctorArgs = []): ?Capability
    {
        if ($capabilityClass = $this->getCapabilityImplementationClassName($plugin, $capabilityClassName)) {
            if (!class_exists($capabilityClass)) {
                throw new \RuntimeException("Cannot instantiate Capability, as class $capabilityClass from plugin ".get_class($plugin)." does not exist.");
            }

            $ctorArgs['plugin'] = $plugin;
            $capabilityObj = new $capabilityClass($ctorArgs);

            // FIXME these could use is_a and do the check *before* instantiating once drop support for php<5.3.9
            if (!$capabilityObj instanceof Capability || !$capabilityObj instanceof $capabilityClassName) {
                throw new \RuntimeException(
                    'Class ' . $capabilityClass . ' must implement both Composer\Plugin\Capability\Capability and '. $capabilityClassName . '.'
                );
            }

            return $capabilityObj;
        }

        return null;
    }

    /**
     * @template CapabilityClass of Capability
     * @param  class-string<CapabilityClass> $capabilityClassName The fully qualified name of the API interface which the plugin may provide
     *                                                            an implementation of.
     * @param  array<mixed>                  $ctorArgs            Arguments passed to Capability's constructor.
     *                                                            Keeping it an array will allow future values to be passed w\o changing the signature.
     * @return CapabilityClass[]
     */
    public function getPluginCapabilities($capabilityClassName, array $ctorArgs = []): array
    {
        $capabilities = [];
        foreach ($this->getPlugins() as $plugin) {
            $capability = $this->getPluginCapability($plugin, $capabilityClassName, $ctorArgs);
            if (null !== $capability) {
                $capabilities[] = $capability;
            }
        }

        return $capabilities;
    }

    /**
     * @param  array<string, bool>|bool $allowPluginsConfig
     * @return array<non-empty-string, bool>|null
     */
    private function parseAllowedPlugins($allowPluginsConfig, ?Locker $locker = null): ?array
    {
        if ([] === $allowPluginsConfig && $locker !== null && $locker->isLocked() && version_compare($locker->getPluginApi(), '2.2.0', '<')) {
            return null;
        }

        if (true === $allowPluginsConfig) {
            return ['{}' => true];
        }

        if (false === $allowPluginsConfig) {
            return ['{}' => false];
        }

        $rules = [];
        foreach ($allowPluginsConfig as $pattern => $allow) {
            $rules[BasePackage::packageNameToRegexp($pattern)] = $allow;
        }

        return $rules;
    }

    /**
     * @internal
     *
     * @param 'local'|'global' $type
     * @return bool
     */
    public function arePluginsDisabled($type)
    {
        return $this->disablePlugins === true || $this->disablePlugins === $type;
    }

    /**
     * @internal
     */
    public function disablePlugins(): void
    {
        $this->disablePlugins = true;
    }

    /**
     * @internal
     */
    public function isPluginAllowed(string $package, bool $isGlobalPlugin, bool $optional = false, bool $prompt = true): bool
    {
        if ($isGlobalPlugin) {
            $rules = &$this->allowGlobalPluginRules;
        } else {
            $rules = &$this->allowPluginRules;
        }

        // This is a BC mode for lock files created pre-Composer-2.2 where the expectation of
        // an allow-plugins config being present cannot be made.
        if ($rules === null) {
            if (!$this->io->isInteractive()) {
                $this->io->writeError('<warning>For additional security you should declare the allow-plugins config with a list of packages names that are allowed to run code. See https://getcomposer.org/allow-plugins</warning>');
                $this->io->writeError('<warning>This warning will become an exception once you run composer update!</warning>');

                $rules = ['{}' => true];

                // if no config is defined we allow all plugins for BC
                return true;
            }

            // keep going and prompt the user
            $rules = [];
        }

        foreach ($rules as $pattern => $allow) {
            if (Preg::isMatch($pattern, $package)) {
                return $allow === true;
            }
        }

        if ($package === 'composer/package-versions-deprecated') {
            return false;
        }

        if ($this->io->isInteractive() && $prompt) {
            $composer = $isGlobalPlugin && $this->globalComposer !== null ? $this->globalComposer : $this->composer;

            $this->io->writeError('<warning>'.$package.($isGlobalPlugin || $this->runningInGlobalDir ? ' (installed globally)' : '').' contains a Composer plugin which is currently not in your allow-plugins config. See https://getcomposer.org/allow-plugins</warning>');
            $attempts = 0;
            while (true) {
                // do not allow more than 5 prints of the help message, at some point assume the
                // input is not interactive and bail defaulting to a disabled plugin
                $default = '?';
                if ($attempts > 5) {
                    $this->io->writeError('Too many failed prompts, aborting.');
                    break;
                }

                switch ($answer = $this->io->ask('Do you trust "<fg=green;options=bold>'.$package.'</>" to execute code and wish to enable it now? (writes "allow-plugins" to composer.json) [<comment>y,n,d,?</comment>] ', $default)) {
                    case 'y':
                    case 'n':
                    case 'd':
                        $allow = $answer === 'y';

                        // persist answer in current rules to avoid prompting again if the package gets reloaded
                        $rules[BasePackage::packageNameToRegexp($package)] = $allow;

                        // persist answer in composer.json if it wasn't simply discarded
                        if ($answer === 'y' || $answer === 'n') {
                            $allowPlugins = $composer->getConfig()->get('allow-plugins');
                            if (is_array($allowPlugins)) {
                                $allowPlugins[$package] = $allow;
                                if ($composer->getConfig()->get('sort-packages')) {
                                    ksort($allowPlugins);
                                }
                                $composer->getConfig()->getConfigSource()->addConfigSetting('allow-plugins', $allowPlugins);
                                $composer->getConfig()->merge(['config' => ['allow-plugins' => $allowPlugins]]);
                            }
                        }

                        return $allow;

                    case '?':
                    default:
                        $attempts++;
                        $this->io->writeError([
                            'y - add package to allow-plugins in composer.json and let it run immediately',
                            'n - add package (as disallowed) to allow-plugins in composer.json to suppress further prompts',
                            'd - discard this, do not change composer.json and do not allow the plugin to run',
                            '? - print help',
                        ]);
                        break;
                }
            }
        } elseif ($optional) {
            return false;
        }

        throw new PluginBlockedException(
            $package.($isGlobalPlugin || $this->runningInGlobalDir ? ' (installed globally)' : '').' contains a Composer plugin which is blocked by your allow-plugins config. You may add it to the list if you consider it safe.'.PHP_EOL.
            'You can run "composer '.($isGlobalPlugin || $this->runningInGlobalDir ? 'global ' : '').'config --no-plugins allow-plugins.'.$package.' [true|false]" to enable it (true) or disable it explicitly and suppress this exception (false)'.PHP_EOL.
            'See https://getcomposer.org/allow-plugins'
        );
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Plugin;

use UnexpectedValueException;

class PluginBlockedException extends UnexpectedValueException
{
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Plugin;

use Composer\EventDispatcher\Event;
use Composer\Util\HttpDownloader;

/**
 * The pre file download event.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class PreFileDownloadEvent extends Event
{
    /**
     * @var HttpDownloader
     */
    private $httpDownloader;

    /**
     * @var non-empty-string
     */
    private $processedUrl;

    /**
     * @var string|null
     */
    private $customCacheKey;

    /**
     * @var string
     */
    private $type;

    /**
     * @var mixed
     */
    private $context;

    /**
     * @var mixed[]
     */
    private $transportOptions = [];

    /**
     * Constructor.
     *
     * @param string           $name           The event name
     * @param mixed            $context
     * @param non-empty-string $processedUrl
     */
    public function __construct(string $name, HttpDownloader $httpDownloader, string $processedUrl, string $type, $context = null)
    {
        parent::__construct($name);
        $this->httpDownloader = $httpDownloader;
        $this->processedUrl = $processedUrl;
        $this->type = $type;
        $this->context = $context;
    }

    public function getHttpDownloader(): HttpDownloader
    {
        return $this->httpDownloader;
    }

    /**
     * Retrieves the processed URL that will be downloaded.
     *
     * @return non-empty-string
     */
    public function getProcessedUrl(): string
    {
        return $this->processedUrl;
    }

    /**
     * Sets the processed URL that will be downloaded.
     *
     * @param non-empty-string $processedUrl New processed URL
     */
    public function setProcessedUrl(string $processedUrl): void
    {
        $this->processedUrl = $processedUrl;
    }

    /**
     * Retrieves a custom package cache key for this download.
     */
    public function getCustomCacheKey(): ?string
    {
        return $this->customCacheKey;
    }

    /**
     * Sets a custom package cache key for this download.
     *
     * @param string|null $customCacheKey New cache key
     */
    public function setCustomCacheKey(?string $customCacheKey): void
    {
        $this->customCacheKey = $customCacheKey;
    }

    /**
     * Returns the type of this download (package, metadata).
     */
    public function getType(): string
    {
        return $this->type;
    }

    /**
     * Returns the context of this download, if any.
     *
     * If this download is of type package, the package object is returned.
     * If the type is metadata, an array{repository: RepositoryInterface} is returned.
     *
     * @return mixed
     */
    public function getContext()
    {
        return $this->context;
    }

    /**
     * Returns transport options for the download.
     *
     * Only available for events with type metadata, for packages set the transport options on the package itself.
     *
     * @return mixed[]
     */
    public function getTransportOptions(): array
    {
        return $this->transportOptions;
    }

    /**
     * Sets transport options for the download.
     *
     * Only available for events with type metadata, for packages set the transport options on the package itself.
     *
     * @param mixed[] $options
     */
    public function setTransportOptions(array $options): void
    {
        $this->transportOptions = $options;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer;

use Composer\Package\RootPackageInterface;
use Composer\Util\Loop;
use Composer\Repository\RepositoryManager;
use Composer\Installer\InstallationManager;
use Composer\EventDispatcher\EventDispatcher;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PartialComposer
{
    /**
     * @var bool
     */
    private $global = false;

    /**
     * @var RootPackageInterface
     */
    private $package;

    /**
     * @var Loop
     */
    private $loop;

    /**
     * @var Repository\RepositoryManager
     */
    private $repositoryManager;

    /**
     * @var Installer\InstallationManager
     */
    private $installationManager;

    /**
     * @var Config
     */
    private $config;

    /**
     * @var EventDispatcher
     */
    private $eventDispatcher;

    public function setPackage(RootPackageInterface $package): void
    {
        $this->package = $package;
    }

    public function getPackage(): RootPackageInterface
    {
        return $this->package;
    }

    public function setConfig(Config $config): void
    {
        $this->config = $config;
    }

    public function getConfig(): Config
    {
        return $this->config;
    }

    public function setLoop(Loop $loop): void
    {
        $this->loop = $loop;
    }

    public function getLoop(): Loop
    {
        return $this->loop;
    }

    public function setRepositoryManager(RepositoryManager $manager): void
    {
        $this->repositoryManager = $manager;
    }

    public function getRepositoryManager(): RepositoryManager
    {
        return $this->repositoryManager;
    }

    public function setInstallationManager(InstallationManager $manager): void
    {
        $this->installationManager = $manager;
    }

    public function getInstallationManager(): InstallationManager
    {
        return $this->installationManager;
    }

    public function setEventDispatcher(EventDispatcher $eventDispatcher): void
    {
        $this->eventDispatcher = $eventDispatcher;
    }

    public function getEventDispatcher(): EventDispatcher
    {
        return $this->eventDispatcher;
    }

    public function isGlobal(): bool
    {
        return $this->global;
    }

    public function setGlobal(): void
    {
        $this->global = true;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Exception;

/**
 * Specific exception for Composer\Util\HttpDownloader creation.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class NoSslException extends \RuntimeException
{
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Exception;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class IrrecoverableDownloadException extends \RuntimeException
{
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Advisory;

use Composer\Pcre\Preg;
use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\VersionParser;
use JsonSerializable;

class PartialSecurityAdvisory implements JsonSerializable
{
    /**
     * @var string
     * @readonly
     */
    public $advisoryId;

    /**
     * @var string
     * @readonly
     */
    public $packageName;

    /**
     * @var ConstraintInterface
     * @readonly
     */
    public $affectedVersions;

    /**
     * @param array<mixed> $data
     * @return SecurityAdvisory|PartialSecurityAdvisory
     */
    public static function create(string $packageName, array $data, VersionParser $parser): self
    {
        try {
            $constraint = $parser->parseConstraints($data['affectedVersions']);
        } catch (\UnexpectedValueException $e) {
            // try to keep only the essential part of the constraint to turn invalid ones like <=3.20-test2 into <=3.20 which is better than nothing
            try {
                $affectedVersion = Preg::replace('{(^[>=<^~]*[\d.]+).*}', '$1', $data['affectedVersions']);
                $constraint = $parser->parseConstraints($affectedVersion);;
            } catch (\UnexpectedValueException $e) {
                $constraint = new Constraint('==', '0.0.0-invalid-version');
            }
        }

        if (isset($data['title'], $data['sources'], $data['reportedAt'])) {
            return new SecurityAdvisory($packageName, $data['advisoryId'], $constraint, $data['title'], $data['sources'], new \DateTimeImmutable($data['reportedAt'], new \DateTimeZone('UTC')), $data['cve'] ?? null, $data['link'] ?? null, $data['severity'] ?? null);
        }

        return new self($packageName, $data['advisoryId'], $constraint);
    }

    public function __construct(string $packageName, string $advisoryId, ConstraintInterface $affectedVersions)
    {
        $this->advisoryId = $advisoryId;
        $this->packageName = $packageName;
        $this->affectedVersions = $affectedVersions;
    }

    /**
     * @return mixed
     */
    #[\ReturnTypeWillChange]
    public function jsonSerialize()
    {
        $data = (array) $this;
        $data['affectedVersions'] = $data['affectedVersions']->getPrettyString();

        return $data;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Advisory;

use Composer\Semver\Constraint\ConstraintInterface;
use DateTimeImmutable;

class SecurityAdvisory extends PartialSecurityAdvisory
{
    /**
     * @var string
     * @readonly
     */
    public $title;

    /**
     * @var string|null
     * @readonly
     */
    public $cve;

    /**
     * @var string|null
     * @readonly
     */
    public $link;

    /**
     * @var DateTimeImmutable
     * @readonly
     */
    public $reportedAt;

    /**
     * @var non-empty-array<array{name: string, remoteId: string}>
     * @readonly
     */
    public $sources;

    /**
     * @var string|null
     * @readonly
     */
    public $severity;

    /**
     * @param non-empty-array<array{name: string, remoteId: string}> $sources
     */
    public function __construct(string $packageName, string $advisoryId, ConstraintInterface $affectedVersions, string $title, array $sources, DateTimeImmutable $reportedAt, ?string $cve = null, ?string $link = null, ?string $severity = null)
    {
        parent::__construct($packageName, $advisoryId, $affectedVersions);

        $this->title = $title;
        $this->sources = $sources;
        $this->reportedAt = $reportedAt;
        $this->cve = $cve;
        $this->link = $link;
        $this->severity = $severity;
    }

    /**
     * @internal
     */
    public function toIgnoredAdvisory(?string $ignoreReason): IgnoredSecurityAdvisory
    {
        return new IgnoredSecurityAdvisory(
            $this->packageName,
            $this->advisoryId,
            $this->affectedVersions,
            $this->title,
            $this->sources,
            $this->reportedAt,
            $this->cve,
            $this->link,
            $ignoreReason,
            $this->severity
        );
    }

    /**
     * @return mixed
     */
    #[\ReturnTypeWillChange]
    public function jsonSerialize()
    {
        $data = parent::jsonSerialize();
        $data['reportedAt'] = $data['reportedAt']->format(DATE_RFC3339);

        return $data;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Advisory;

use Composer\IO\ConsoleIO;
use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
use Composer\Package\CompletePackageInterface;
use Composer\Package\PackageInterface;
use Composer\Repository\RepositorySet;
use Composer\Util\PackageInfo;
use InvalidArgumentException;
use Symfony\Component\Console\Formatter\OutputFormatter;

/**
 * @internal
 */
class Auditor
{
    public const FORMAT_TABLE = 'table';

    public const FORMAT_PLAIN = 'plain';

    public const FORMAT_JSON = 'json';

    public const FORMAT_SUMMARY = 'summary';

    public const FORMATS = [
        self::FORMAT_TABLE,
        self::FORMAT_PLAIN,
        self::FORMAT_JSON,
        self::FORMAT_SUMMARY,
    ];

    public const ABANDONED_IGNORE = 'ignore';
    public const ABANDONED_REPORT = 'report';
    public const ABANDONED_FAIL = 'fail';

    /** @internal */
    public const ABANDONEDS = [
        self::ABANDONED_IGNORE,
        self::ABANDONED_REPORT,
        self::ABANDONED_FAIL,
    ];

    /** Values to determine the audit result. */
    public const STATUS_OK = 0;
    public const STATUS_VULNERABLE = 1;
    public const STATUS_ABANDONED = 2;

    /**
     * @param PackageInterface[] $packages
     * @param self::FORMAT_* $format The format that will be used to output audit results.
     * @param bool $warningOnly If true, outputs a warning. If false, outputs an error.
     * @param string[] $ignoreList List of advisory IDs, remote IDs or CVE IDs that reported but not listed as vulnerabilities.
     * @param self::ABANDONED_* $abandoned
     * @param array<string> $ignoredSeverities List of ignored severity levels
     *
     * @return int-mask<self::STATUS_*> A bitmask of STATUS_* constants or 0 on success
     * @throws InvalidArgumentException If no packages are passed in
     */
    public function audit(IOInterface $io, RepositorySet $repoSet, array $packages, string $format, bool $warningOnly = true, array $ignoreList = [], string $abandoned = self::ABANDONED_FAIL, array $ignoredSeverities = []): int
    {
        $allAdvisories = $repoSet->getMatchingSecurityAdvisories($packages, $format === self::FORMAT_SUMMARY);
        // we need the CVE & remote IDs set to filter ignores correctly so if we have any matches using the optimized codepath above
        // and ignores are set then we need to query again the full data to make sure it can be filtered
        if (count($allAdvisories) > 0 && $ignoreList !== [] && $format === self::FORMAT_SUMMARY) {
            $allAdvisories = $repoSet->getMatchingSecurityAdvisories($packages, false);
        }
        ['advisories' => $advisories, 'ignoredAdvisories' => $ignoredAdvisories] = $this->processAdvisories($allAdvisories, $ignoreList, $ignoredSeverities);

        $abandonedCount = 0;
        $affectedPackagesCount = count($advisories);
        if ($abandoned === self::ABANDONED_IGNORE) {
            $abandonedPackages = [];
        } else {
            $abandonedPackages = $this->filterAbandonedPackages($packages);
            if ($abandoned === self::ABANDONED_FAIL) {
                $abandonedCount = count($abandonedPackages);
            }
        }

        $auditBitmask = $this->calculateBitmask(0 < $affectedPackagesCount, 0 < $abandonedCount);

        if (self::FORMAT_JSON === $format) {
            $json = ['advisories' => $advisories];
            if ($ignoredAdvisories !== []) {
                $json['ignored-advisories'] = $ignoredAdvisories;
            }
            $json['abandoned'] = array_reduce($abandonedPackages, static function (array $carry, CompletePackageInterface $package): array {
                $carry[$package->getPrettyName()] = $package->getReplacementPackage();

                return $carry;
            }, []);

            $io->write(JsonFile::encode($json));

            return $auditBitmask;
        }

        $errorOrWarn = $warningOnly ? 'warning' : 'error';
        if ($affectedPackagesCount > 0 || count($ignoredAdvisories) > 0) {
            $passes = [
                [$ignoredAdvisories, "<info>Found %d ignored security vulnerability advisor%s affecting %d package%s%s</info>"],
                [$advisories, "<$errorOrWarn>Found %d security vulnerability advisor%s affecting %d package%s%s</$errorOrWarn>"],
            ];
            foreach ($passes as [$advisoriesToOutput, $message]) {
                [$pkgCount, $totalAdvisoryCount] = $this->countAdvisories($advisoriesToOutput);
                if ($pkgCount > 0) {
                    $plurality = $totalAdvisoryCount === 1 ? 'y' : 'ies';
                    $pkgPlurality = $pkgCount === 1 ? '' : 's';
                    $punctuation = $format === 'summary' ? '.' : ':';
                    $io->writeError(sprintf($message, $totalAdvisoryCount, $plurality, $pkgCount, $pkgPlurality, $punctuation));
                    $this->outputAdvisories($io, $advisoriesToOutput, $format);
                }
            }

            if ($format === self::FORMAT_SUMMARY) {
                $io->writeError('Run "composer audit" for a full list of advisories.');
            }
        } else {
            $io->writeError('<info>No security vulnerability advisories found.</info>');
        }

        if (count($abandonedPackages) > 0 && $format !== self::FORMAT_SUMMARY) {
            $this->outputAbandonedPackages($io, $abandonedPackages, $format);
        }

        return $auditBitmask;
    }

    /**
     * @param array<PackageInterface> $packages
     * @return array<CompletePackageInterface>
     */
    private function filterAbandonedPackages(array $packages): array
    {
        return array_filter($packages, static function (PackageInterface $pkg): bool {
            return $pkg instanceof CompletePackageInterface && $pkg->isAbandoned();
        });
    }

    /**
     * @phpstan-param array<string, array<PartialSecurityAdvisory|SecurityAdvisory>> $allAdvisories
     * @param array<string>|array<string,string> $ignoreList List of advisory IDs, remote IDs or CVE IDs that reported but not listed as vulnerabilities.
     * @param array<string> $ignoredSeverities List of ignored severity levels
     * @phpstan-return array{advisories: array<string, array<PartialSecurityAdvisory|SecurityAdvisory>>, ignoredAdvisories: array<string, array<PartialSecurityAdvisory|SecurityAdvisory>>}
     */
    private function processAdvisories(array $allAdvisories, array $ignoreList, array $ignoredSeverities): array
    {
        if ($ignoreList === [] && $ignoredSeverities === []) {
            return ['advisories' => $allAdvisories, 'ignoredAdvisories' => []];
        }

        if (\count($ignoreList) > 0 && !\array_is_list($ignoreList)) {
            $ignoredIds = array_keys($ignoreList);
        } else {
            $ignoredIds = $ignoreList;
        }

        $advisories = [];
        $ignored = [];
        $ignoreReason = null;

        foreach ($allAdvisories as $package => $pkgAdvisories) {
            foreach ($pkgAdvisories as $advisory) {
                $isActive = true;

                if (in_array($advisory->advisoryId, $ignoredIds, true)) {
                    $isActive = false;
                    $ignoreReason = $ignoreList[$advisory->advisoryId] ?? null;
                }

                if ($advisory instanceof SecurityAdvisory) {
                    if (in_array($advisory->severity, $ignoredSeverities, true)) {
                        $isActive = false;
                        $ignoreReason = "Ignored via --ignore-severity={$advisory->severity}";
                    }

                    if (in_array($advisory->cve, $ignoredIds, true)) {
                        $isActive = false;
                        $ignoreReason = $ignoreList[$advisory->cve] ?? null;
                    }

                    foreach ($advisory->sources as $source) {
                        if (in_array($source['remoteId'], $ignoredIds, true)) {
                            $isActive = false;
                            $ignoreReason = $ignoreList[$source['remoteId']] ?? null;
                            break;
                        }
                    }
                }

                if ($isActive) {
                    $advisories[$package][] = $advisory;
                    continue;
                }

                // Partial security advisories only used in summary mode
                // and in that case we do not need to cast the object.
                if ($advisory instanceof SecurityAdvisory) {
                    $advisory = $advisory->toIgnoredAdvisory($ignoreReason);
                }

                $ignored[$package][] = $advisory;
            }
        }

        return ['advisories' => $advisories, 'ignoredAdvisories' => $ignored];
    }

    /**
     * @param array<string, array<PartialSecurityAdvisory>> $advisories
     * @return array{int, int} Count of affected packages and total count of advisories
     */
    private function countAdvisories(array $advisories): array
    {
        $count = 0;
        foreach ($advisories as $packageAdvisories) {
            $count += count($packageAdvisories);
        }

        return [count($advisories), $count];
    }

    /**
     * @param array<string, array<SecurityAdvisory>> $advisories
     * @param self::FORMAT_* $format The format that will be used to output audit results.
     */
    private function outputAdvisories(IOInterface $io, array $advisories, string $format): void
    {
        switch ($format) {
            case self::FORMAT_TABLE:
                if (!($io instanceof ConsoleIO)) {
                    throw new InvalidArgumentException('Cannot use table format with ' . get_class($io));
                }
                $this->outputAdvisoriesTable($io, $advisories);

                return;
            case self::FORMAT_PLAIN:
                $this->outputAdvisoriesPlain($io, $advisories);

                return;
            case self::FORMAT_SUMMARY:

                return;
            default:
                throw new InvalidArgumentException('Invalid format "'.$format.'".');
        }
    }

    /**
     * @param array<string, array<SecurityAdvisory>> $advisories
     */
    private function outputAdvisoriesTable(ConsoleIO $io, array $advisories): void
    {
        foreach ($advisories as $packageAdvisories) {
            foreach ($packageAdvisories as $advisory) {
                $headers = [
                    'Package',
                    'Severity',
                    'CVE',
                    'Title',
                    'URL',
                    'Affected versions',
                    'Reported at',
                ];
                $row = [
                    $advisory->packageName,
                    $this->getSeverity($advisory),
                    $this->getCVE($advisory),
                    $advisory->title,
                    $this->getURL($advisory),
                    $advisory->affectedVersions->getPrettyString(),
                    $advisory->reportedAt->format(DATE_ATOM),
                ];
                if ($advisory->cve === null) {
                    $headers[] = 'Advisory ID';
                    $row[] = $advisory->advisoryId;
                }
                if ($advisory instanceof IgnoredSecurityAdvisory) {
                    $headers[] = 'Ignore reason';
                    $row[] = $advisory->ignoreReason ?? 'None specified';
                }
                $io->getTable()
                    ->setHorizontal()
                    ->setHeaders($headers)
                    ->addRow($row)
                    ->setColumnWidth(1, 80)
                    ->setColumnMaxWidth(1, 80)
                    ->render();
            }
        }
    }

    /**
     * @param array<string, array<SecurityAdvisory>> $advisories
     */
    private function outputAdvisoriesPlain(IOInterface $io, array $advisories): void
    {
        $error = [];
        $firstAdvisory = true;
        foreach ($advisories as $packageAdvisories) {
            foreach ($packageAdvisories as $advisory) {
                if (!$firstAdvisory) {
                    $error[] = '--------';
                }
                $error[] = "Package: ".$advisory->packageName;
                $error[] = "Severity: ".$this->getSeverity($advisory);
                $error[] = "CVE: ".$this->getCVE($advisory);
                if ($advisory->cve === null) {
                    $error[] = "Advisory ID: ".$advisory->advisoryId;
                }
                $error[] = "Title: ".OutputFormatter::escape($advisory->title);
                $error[] = "URL: ".$this->getURL($advisory);
                $error[] = "Affected versions: ".OutputFormatter::escape($advisory->affectedVersions->getPrettyString());
                $error[] = "Reported at: ".$advisory->reportedAt->format(DATE_ATOM);
                if ($advisory instanceof IgnoredSecurityAdvisory) {
                    $error[] = "Ignore reason: ".($advisory->ignoreReason ?? 'None specified');
                }
                $firstAdvisory = false;
            }
        }
        $io->writeError($error);
    }

    /**
     * @param array<CompletePackageInterface> $packages
     * @param self::FORMAT_PLAIN|self::FORMAT_TABLE $format
     */
    private function outputAbandonedPackages(IOInterface $io, array $packages, string $format): void
    {
        $io->writeError(sprintf('<error>Found %d abandoned package%s:</error>', count($packages), count($packages) > 1 ? 's' : ''));

        if ($format === self::FORMAT_PLAIN) {
            foreach ($packages as $pkg) {
                $replacement = $pkg->getReplacementPackage() !== null
                    ? 'Use '.$pkg->getReplacementPackage().' instead'
                    : 'No replacement was suggested';
                $io->writeError(sprintf(
                    '%s is abandoned. %s.',
                    $this->getPackageNameWithLink($pkg),
                    $replacement
                ));
            }

            return;
        }

        if (!($io instanceof ConsoleIO)) {
            throw new InvalidArgumentException('Cannot use table format with ' . get_class($io));
        }

        $table = $io->getTable()
            ->setHeaders(['Abandoned Package', 'Suggested Replacement'])
            ->setColumnWidth(1, 80)
            ->setColumnMaxWidth(1, 80);

        foreach ($packages as $pkg) {
            $replacement = $pkg->getReplacementPackage() !== null ? $pkg->getReplacementPackage() : 'none';
            $table->addRow([$this->getPackageNameWithLink($pkg), $replacement]);
        }

        $table->render();
    }

    private function getPackageNameWithLink(PackageInterface $package): string
    {
        $packageUrl = PackageInfo::getViewSourceOrHomepageUrl($package);

        return $packageUrl !== null ? '<href=' . OutputFormatter::escape($packageUrl) . '>' . $package->getPrettyName() . '</>' : $package->getPrettyName();
    }

    private function getSeverity(SecurityAdvisory $advisory): string
    {
        if ($advisory->severity === null) {
            return '';
        }

        return $advisory->severity;
    }

    private function getCVE(SecurityAdvisory $advisory): string
    {
        if ($advisory->cve === null) {
            return 'NO CVE';
        }

        return '<href=https://cve.mitre.org/cgi-bin/cvename.cgi?name='.$advisory->cve.'>'.$advisory->cve.'</>';
    }

    private function getURL(SecurityAdvisory $advisory): string
    {
        if ($advisory->link === null) {
            return '';
        }

        return '<href='.OutputFormatter::escape($advisory->link).'>'.OutputFormatter::escape($advisory->link).'</>';
    }

    /**
     * @return int-mask<self::STATUS_*>
     */
    private function calculateBitmask(bool $hasVulnerablePackages, bool $hasAbandonedPackages): int
    {
        $bitmask = self::STATUS_OK;

        if ($hasVulnerablePackages) {
            $bitmask |= self::STATUS_VULNERABLE;
        }

        if ($hasAbandonedPackages) {
            $bitmask |= self::STATUS_ABANDONED;
        }

        return $bitmask;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Advisory;

use Composer\Semver\Constraint\ConstraintInterface;
use DateTimeImmutable;

class IgnoredSecurityAdvisory extends SecurityAdvisory
{
    /**
     * @var string|null
     * @readonly
     */
    public $ignoreReason;

    /**
     * @param non-empty-array<array{name: string, remoteId: string}> $sources
     */
    public function __construct(string $packageName, string $advisoryId, ConstraintInterface $affectedVersions, string $title, array $sources, DateTimeImmutable $reportedAt, ?string $cve = null, ?string $link = null, ?string $ignoreReason = null, ?string $severity = null)
    {
        parent::__construct($packageName, $advisoryId, $affectedVersions, $title, $sources, $reportedAt, $cve, $link, $severity);

        $this->ignoreReason = $ignoreReason;
    }

    /**
     * @return mixed
     */
    #[\ReturnTypeWillChange]
    public function jsonSerialize()
    {
        $data = parent::jsonSerialize();
        if ($this->ignoreReason === NULL) {
            unset($data['ignoreReason']);
        }

        return $data;
    }

}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\SelfUpdate;

use Composer\IO\IOInterface;
use Composer\Pcre\Preg;
use Composer\Util\HttpDownloader;
use Composer\Config;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Versions
{
    /**
     * @var string[]
     * @deprecated use Versions::CHANNELS
     */
    public static $channels = self::CHANNELS;

    public const CHANNELS = ['stable', 'preview', 'snapshot', '1', '2', '2.2'];

    /** @var HttpDownloader */
    private $httpDownloader;
    /** @var Config */
    private $config;
    /** @var string */
    private $channel;
    /** @var array<string, array<int, array{path: string, version: string, min-php: int, eol?: true}>>|null */
    private $versionsData = null;

    public function __construct(Config $config, HttpDownloader $httpDownloader)
    {
        $this->httpDownloader = $httpDownloader;
        $this->config = $config;
    }

    public function getChannel(): string
    {
        if ($this->channel) {
            return $this->channel;
        }

        $channelFile = $this->config->get('home').'/update-channel';
        if (file_exists($channelFile)) {
            $channel = trim(file_get_contents($channelFile));
            if (in_array($channel, ['stable', 'preview', 'snapshot', '2.2'], true)) {
                return $this->channel = $channel;
            }
        }

        return $this->channel = 'stable';
    }

    public function setChannel(string $channel, ?IOInterface $io = null): void
    {
        if (!in_array($channel, self::CHANNELS, true)) {
            throw new \InvalidArgumentException('Invalid channel '.$channel.', must be one of: ' . implode(', ', self::CHANNELS));
        }

        $channelFile = $this->config->get('home').'/update-channel';
        $this->channel = $channel;

        // rewrite '2' and '1' channels to stable for future self-updates, but LTS ones like '2.2' remain pinned
        $storedChannel = Preg::isMatch('{^\d+$}D', $channel) ? 'stable' : $channel;
        $previouslyStored = file_exists($channelFile) ? trim((string) file_get_contents($channelFile)) : null;
        file_put_contents($channelFile, $storedChannel.PHP_EOL);

        if ($io !== null && $previouslyStored !== $storedChannel) {
            $io->writeError('Storing "<info>'.$storedChannel.'</info>" as default update channel for the next self-update run.');
        }
    }

    /**
     * @return array{path: string, version: string, min-php: int, eol?: true}
     */
    public function getLatest(?string $channel = null): array
    {
        $versions = $this->getVersionsData();

        foreach ($versions[$channel ?: $this->getChannel()] as $version) {
            if ($version['min-php'] <= \PHP_VERSION_ID) {
                return $version;
            }
        }

        throw new \UnexpectedValueException('There is no version of Composer available for your PHP version ('.PHP_VERSION.')');
    }

    /**
     * @return array<string, array<int, array{path: string, version: string, min-php: int, eol?: true}>>
     */
    private function getVersionsData(): array
    {
        if (null === $this->versionsData) {
            if ($this->config->get('disable-tls') === true) {
                $protocol = 'http';
            } else {
                $protocol = 'https';
            }

            $this->versionsData = $this->httpDownloader->get($protocol . '://getcomposer.org/versions')->decodeJson();
        }

        return $this->versionsData;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\SelfUpdate;

use Composer\Pcre\Preg;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Keys
{
    public static function fingerprint(string $path): string
    {
        $hash = strtoupper(hash('sha256', Preg::replace('{\s}', '', file_get_contents($path))));

        return implode(' ', [
            substr($hash, 0, 8),
            substr($hash, 8, 8),
            substr($hash, 16, 8),
            substr($hash, 24, 8),
            '', // Extra space
            substr($hash, 32, 8),
            substr($hash, 40, 8),
            substr($hash, 48, 8),
            substr($hash, 56, 8),
        ]);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Config;

/**
 * Configuration Source Interface
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Beau Simensen <beau@dflydev.com>
 */
interface ConfigSourceInterface
{
    /**
     * Add a repository
     *
     * @param string        $name   Name
     * @param mixed[]|false $config Configuration
     * @param bool          $append Whether the repo should be appended (true) or prepended (false)
     */
    public function addRepository(string $name, $config, bool $append = true): void;

    /**
     * Remove a repository
     */
    public function removeRepository(string $name): void;

    /**
     * Add a config setting
     *
     * @param string $name  Name
     * @param mixed  $value Value
     */
    public function addConfigSetting(string $name, $value): void;

    /**
     * Remove a config setting
     */
    public function removeConfigSetting(string $name): void;

    /**
     * Add a property
     *
     * @param string $name  Name
     * @param string|string[] $value Value
     */
    public function addProperty(string $name, $value): void;

    /**
     * Remove a property
     */
    public function removeProperty(string $name): void;

    /**
     * Add a package link
     *
     * @param string $type  Type (require, require-dev, provide, suggest, replace, conflict)
     * @param string $name  Name
     * @param string $value Value
     */
    public function addLink(string $type, string $name, string $value): void;

    /**
     * Remove a package link
     *
     * @param string $type Type (require, require-dev, provide, suggest, replace, conflict)
     * @param string $name Name
     */
    public function removeLink(string $type, string $name): void;

    /**
     * Gives a user-friendly name to this source (file path or so)
     */
    public function getName(): string;
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Config;

use Composer\Json\JsonFile;
use Composer\Json\JsonManipulator;
use Composer\Json\JsonValidationException;
use Composer\Pcre\Preg;
use Composer\Util\Filesystem;
use Composer\Util\Silencer;

/**
 * JSON Configuration Source
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Beau Simensen <beau@dflydev.com>
 */
class JsonConfigSource implements ConfigSourceInterface
{
    /**
     * @var JsonFile
     */
    private $file;

    /**
     * @var bool
     */
    private $authConfig;

    /**
     * Constructor
     */
    public function __construct(JsonFile $file, bool $authConfig = false)
    {
        $this->file = $file;
        $this->authConfig = $authConfig;
    }

    /**
     * @inheritDoc
     */
    public function getName(): string
    {
        return $this->file->getPath();
    }

    /**
     * @inheritDoc
     */
    public function addRepository(string $name, $config, bool $append = true): void
    {
        $this->manipulateJson('addRepository', static function (&$config, $repo, $repoConfig) use ($append): void {
            // if converting from an array format to hashmap format, and there is a {"packagist.org":false} repo, we have
            // to convert it to "packagist.org": false key on the hashmap otherwise it fails schema validation
            if (isset($config['repositories'])) {
                foreach ($config['repositories'] as $index => $val) {
                    if ($index === $repo) {
                        continue;
                    }
                    if (is_numeric($index) && ($val === ['packagist' => false] || $val === ['packagist.org' => false])) {
                        unset($config['repositories'][$index]);
                        $config['repositories']['packagist.org'] = false;
                        break;
                    }
                }
            }

            if ($append) {
                $config['repositories'][$repo] = $repoConfig;
            } else {
                $config['repositories'] = [$repo => $repoConfig] + $config['repositories'];
            }
        }, $name, $config, $append);
    }

    /**
     * @inheritDoc
     */
    public function removeRepository(string $name): void
    {
        $this->manipulateJson('removeRepository', static function (&$config, $repo): void {
            unset($config['repositories'][$repo]);
        }, $name);
    }

    /**
     * @inheritDoc
     */
    public function addConfigSetting(string $name, $value): void
    {
        $authConfig = $this->authConfig;
        $this->manipulateJson('addConfigSetting', static function (&$config, $key, $val) use ($authConfig): void {
            if (Preg::isMatch('{^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|bearer|http-basic|platform)\.}', $key)) {
                [$key, $host] = explode('.', $key, 2);
                if ($authConfig) {
                    $config[$key][$host] = $val;
                } else {
                    $config['config'][$key][$host] = $val;
                }
            } else {
                $config['config'][$key] = $val;
            }
        }, $name, $value);
    }

    /**
     * @inheritDoc
     */
    public function removeConfigSetting(string $name): void
    {
        $authConfig = $this->authConfig;
        $this->manipulateJson('removeConfigSetting', static function (&$config, $key) use ($authConfig): void {
            if (Preg::isMatch('{^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|bearer|http-basic|platform)\.}', $key)) {
                [$key, $host] = explode('.', $key, 2);
                if ($authConfig) {
                    unset($config[$key][$host]);
                } else {
                    unset($config['config'][$key][$host]);
                }
            } else {
                unset($config['config'][$key]);
            }
        }, $name);
    }

    /**
     * @inheritDoc
     */
    public function addProperty(string $name, $value): void
    {
        $this->manipulateJson('addProperty', static function (&$config, $key, $val): void {
            if (strpos($key, 'extra.') === 0 || strpos($key, 'scripts.') === 0) {
                $bits = explode('.', $key);
                $last = array_pop($bits);
                $arr = &$config[reset($bits)];
                foreach ($bits as $bit) {
                    if (!isset($arr[$bit])) {
                        $arr[$bit] = [];
                    }
                    $arr = &$arr[$bit];
                }
                $arr[$last] = $val;
            } else {
                $config[$key] = $val;
            }
        }, $name, $value);
    }

    /**
     * @inheritDoc
     */
    public function removeProperty(string $name): void
    {
        $this->manipulateJson('removeProperty', static function (&$config, $key): void {
            if (strpos($key, 'extra.') === 0 || strpos($key, 'scripts.') === 0 || stripos($key, 'autoload.') === 0 || stripos($key, 'autoload-dev.') === 0) {
                $bits = explode('.', $key);
                $last = array_pop($bits);
                $arr = &$config[reset($bits)];
                foreach ($bits as $bit) {
                    if (!isset($arr[$bit])) {
                        return;
                    }
                    $arr = &$arr[$bit];
                }
                unset($arr[$last]);
            } else {
                unset($config[$key]);
            }
        }, $name);
    }

    /**
     * @inheritDoc
     */
    public function addLink(string $type, string $name, string $value): void
    {
        $this->manipulateJson('addLink', static function (&$config, $type, $name, $value): void {
            $config[$type][$name] = $value;
        }, $type, $name, $value);
    }

    /**
     * @inheritDoc
     */
    public function removeLink(string $type, string $name): void
    {
        $this->manipulateJson('removeSubNode', static function (&$config, $type, $name): void {
            unset($config[$type][$name]);
        }, $type, $name);
        $this->manipulateJson('removeMainKeyIfEmpty', static function (&$config, $type): void {
            if (0 === count($config[$type])) {
                unset($config[$type]);
            }
        }, $type);
    }

    /**
     * @param mixed ...$args
     */
    private function manipulateJson(string $method, callable $fallback, ...$args): void
    {
        if ($this->file->exists()) {
            if (!is_writable($this->file->getPath())) {
                throw new \RuntimeException(sprintf('The file "%s" is not writable.', $this->file->getPath()));
            }

            if (!Filesystem::isReadable($this->file->getPath())) {
                throw new \RuntimeException(sprintf('The file "%s" is not readable.', $this->file->getPath()));
            }

            $contents = file_get_contents($this->file->getPath());
        } elseif ($this->authConfig) {
            $contents = "{\n}\n";
        } else {
            $contents = "{\n    \"config\": {\n    }\n}\n";
        }

        $manipulator = new JsonManipulator($contents);

        $newFile = !$this->file->exists();

        // override manipulator method for auth config files
        if ($this->authConfig && $method === 'addConfigSetting') {
            $method = 'addSubNode';
            [$mainNode, $name] = explode('.', $args[0], 2);
            $args = [$mainNode, $name, $args[1]];
        } elseif ($this->authConfig && $method === 'removeConfigSetting') {
            $method = 'removeSubNode';
            [$mainNode, $name] = explode('.', $args[0], 2);
            $args = [$mainNode, $name];
        }

        // try to update cleanly
        if (call_user_func_array([$manipulator, $method], $args)) {
            file_put_contents($this->file->getPath(), $manipulator->getContents());
        } else {
            // on failed clean update, call the fallback and rewrite the whole file
            $config = $this->file->read();
            $this->arrayUnshiftRef($args, $config);
            $fallback(...$args);
            // avoid ending up with arrays for keys that should be objects
            foreach (['require', 'require-dev', 'conflict', 'provide', 'replace', 'suggest', 'config', 'autoload', 'autoload-dev', 'scripts', 'scripts-descriptions', 'scripts-aliases', 'support'] as $prop) {
                if (isset($config[$prop]) && $config[$prop] === []) {
                    $config[$prop] = new \stdClass;
                }
            }
            foreach (['psr-0', 'psr-4'] as $prop) {
                if (isset($config['autoload'][$prop]) && $config['autoload'][$prop] === []) {
                    $config['autoload'][$prop] = new \stdClass;
                }
                if (isset($config['autoload-dev'][$prop]) && $config['autoload-dev'][$prop] === []) {
                    $config['autoload-dev'][$prop] = new \stdClass;
                }
            }
            foreach (['platform', 'http-basic', 'bearer', 'gitlab-token', 'gitlab-oauth', 'github-oauth', 'preferred-install'] as $prop) {
                if (isset($config['config'][$prop]) && $config['config'][$prop] === []) {
                    $config['config'][$prop] = new \stdClass;
                }
            }
            $this->file->write($config);
        }

        try {
            $this->file->validateSchema(JsonFile::LAX_SCHEMA);
        } catch (JsonValidationException $e) {
            // restore contents to the original state
            file_put_contents($this->file->getPath(), $contents);
            throw new \RuntimeException('Failed to update composer.json with a valid format, reverting to the original content. Please report an issue to us with details (command you run and a copy of your composer.json). '.PHP_EOL.implode(PHP_EOL, $e->getErrors()), 0, $e);
        }

        if ($newFile) {
            Silencer::call('chmod', $this->file->getPath(), 0600);
        }
    }

    /**
     * Prepend a reference to an element to the beginning of an array.
     *
     * @param  mixed[] $array
     * @param  mixed $value
     */
    private function arrayUnshiftRef(array &$array, &$value): int
    {
        $return = array_unshift($array, '');
        $array[0] = &$value;

        return $return;
    }
}
<?php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer;

use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;

/**
 * This class is copied in every Composer installed project and available to all
 *
 * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
 *
 * To require its presence, you can require `composer-runtime-api ^2.0`
 *
 * @final
 */
class InstalledVersions
{
    /**
     * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
     * @internal
     */
    private static $selfDir = null;

    /**
     * @var mixed[]|null
     * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
     */
    private static $installed;

    /**
     * @var bool
     */
    private static $installedIsLocalDir;

    /**
     * @var bool|null
     */
    private static $canGetVendors;

    /**
     * @var array[]
     * @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
     */
    private static $installedByVendor = array();

    /**
     * Returns a list of all package names which are present, either by being installed, replaced or provided
     *
     * @return string[]
     * @psalm-return list<string>
     */
    public static function getInstalledPackages()
    {
        $packages = array();
        foreach (self::getInstalled() as $installed) {
            $packages[] = array_keys($installed['versions']);
        }

        if (1 === \count($packages)) {
            return $packages[0];
        }

        return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
    }

    /**
     * Returns a list of all package names with a specific type e.g. 'library'
     *
     * @param  string   $type
     * @return string[]
     * @psalm-return list<string>
     */
    public static function getInstalledPackagesByType($type)
    {
        $packagesByType = array();

        foreach (self::getInstalled() as $installed) {
            foreach ($installed['versions'] as $name => $package) {
                if (isset($package['type']) && $package['type'] === $type) {
                    $packagesByType[] = $name;
                }
            }
        }

        return $packagesByType;
    }

    /**
     * Checks whether the given package is installed
     *
     * This also returns true if the package name is provided or replaced by another package
     *
     * @param  string $packageName
     * @param  bool   $includeDevRequirements
     * @return bool
     */
    public static function isInstalled($packageName, $includeDevRequirements = true)
    {
        foreach (self::getInstalled() as $installed) {
            if (isset($installed['versions'][$packageName])) {
                return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
            }
        }

        return false;
    }

    /**
     * Checks whether the given package satisfies a version constraint
     *
     * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
     *
     *   Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
     *
     * @param  VersionParser $parser      Install composer/semver to have access to this class and functionality
     * @param  string        $packageName
     * @param  string|null   $constraint  A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
     * @return bool
     */
    public static function satisfies(VersionParser $parser, $packageName, $constraint)
    {
        $constraint = $parser->parseConstraints((string) $constraint);
        $provided = $parser->parseConstraints(self::getVersionRanges($packageName));

        return $provided->matches($constraint);
    }

    /**
     * Returns a version constraint representing all the range(s) which are installed for a given package
     *
     * It is easier to use this via isInstalled() with the $constraint argument if you need to check
     * whether a given version of a package is installed, and not just whether it exists
     *
     * @param  string $packageName
     * @return string Version constraint usable with composer/semver
     */
    public static function getVersionRanges($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            $ranges = array();
            if (isset($installed['versions'][$packageName]['pretty_version'])) {
                $ranges[] = $installed['versions'][$packageName]['pretty_version'];
            }
            if (array_key_exists('aliases', $installed['versions'][$packageName])) {
                $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
            }
            if (array_key_exists('replaced', $installed['versions'][$packageName])) {
                $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
            }
            if (array_key_exists('provided', $installed['versions'][$packageName])) {
                $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
            }

            return implode(' || ', $ranges);
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

    /**
     * @param  string      $packageName
     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
     */
    public static function getVersion($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            if (!isset($installed['versions'][$packageName]['version'])) {
                return null;
            }

            return $installed['versions'][$packageName]['version'];
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

    /**
     * @param  string      $packageName
     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
     */
    public static function getPrettyVersion($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            if (!isset($installed['versions'][$packageName]['pretty_version'])) {
                return null;
            }

            return $installed['versions'][$packageName]['pretty_version'];
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

    /**
     * @param  string      $packageName
     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
     */
    public static function getReference($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            if (!isset($installed['versions'][$packageName]['reference'])) {
                return null;
            }

            return $installed['versions'][$packageName]['reference'];
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

    /**
     * @param  string      $packageName
     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
     */
    public static function getInstallPath($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

    /**
     * @return array
     * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
     */
    public static function getRootPackage()
    {
        $installed = self::getInstalled();

        return $installed[0]['root'];
    }

    /**
     * Returns the raw installed.php data for custom implementations
     *
     * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
     * @return array[]
     * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
     */
    public static function getRawData()
    {
        @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);

        if (null === self::$installed) {
            // only require the installed.php file if this file is loaded from its dumped location,
            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
            if (substr(__DIR__, -8, 1) !== 'C') {
                self::$installed = include __DIR__ . '/installed.php';
            } else {
                self::$installed = array();
            }
        }

        return self::$installed;
    }

    /**
     * Returns the raw data of all installed.php which are currently loaded for custom implementations
     *
     * @return array[]
     * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
     */
    public static function getAllRawData()
    {
        return self::getInstalled();
    }

    /**
     * Lets you reload the static array from another file
     *
     * This is only useful for complex integrations in which a project needs to use
     * this class but then also needs to execute another project's autoloader in process,
     * and wants to ensure both projects have access to their version of installed.php.
     *
     * A typical case would be PHPUnit, where it would need to make sure it reads all
     * the data it needs from this class, then call reload() with
     * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
     * the project in which it runs can then also use this class safely, without
     * interference between PHPUnit's dependencies and the project's dependencies.
     *
     * @param  array[] $data A vendor/composer/installed.php data set
     * @return void
     *
     * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
     */
    public static function reload($data)
    {
        self::$installed = $data;
        self::$installedByVendor = array();

        // when using reload, we disable the duplicate protection to ensure that self::$installed data is
        // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
        // so we have to assume it does not, and that may result in duplicate data being returned when listing
        // all installed packages for example
        self::$installedIsLocalDir = false;
    }

    /**
     * @return string
     */
    private static function getSelfDir()
    {
        if (self::$selfDir === null) {
            self::$selfDir = strtr(__DIR__, '\\', '/');
        }

        return self::$selfDir;
    }

    /**
     * @return array[]
     * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
     */
    private static function getInstalled()
    {
        if (null === self::$canGetVendors) {
            self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
        }

        $installed = array();
        $copiedLocalDir = false;

        if (self::$canGetVendors) {
            $selfDir = self::getSelfDir();
            foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
                $vendorDir = strtr($vendorDir, '\\', '/');
                if (isset(self::$installedByVendor[$vendorDir])) {
                    $installed[] = self::$installedByVendor[$vendorDir];
                } elseif (is_file($vendorDir.'/composer/installed.php')) {
                    /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
                    $required = require $vendorDir.'/composer/installed.php';
                    self::$installedByVendor[$vendorDir] = $required;
                    $installed[] = $required;
                    if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
                        self::$installed = $required;
                        self::$installedIsLocalDir = true;
                    }
                }
                if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
                    $copiedLocalDir = true;
                }
            }
        }

        if (null === self::$installed) {
            // only require the installed.php file if this file is loaded from its dumped location,
            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
            if (substr(__DIR__, -8, 1) !== 'C') {
                /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
                $required = require __DIR__ . '/installed.php';
                self::$installed = $required;
            } else {
                self::$installed = array();
            }
        }

        if (self::$installed !== array() && !$copiedLocalDir) {
            $installed[] = self::$installed;
        }

        return $installed;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Platform;

use Composer\Pcre\Preg;

/**
 * @author Lars Strojny <lars@strojny.net>
 */
class Version
{
    /**
     * @param bool $isFips Set by the method
     *
     * @param-out bool $isFips
     */
    public static function parseOpenssl(string $opensslVersion, ?bool &$isFips): ?string
    {
        $isFips = false;

        if (!Preg::isMatchStrictGroups('/^(?<version>[0-9.]+)(?<patch>[a-z]{0,2})(?<suffix>(?:-?(?:dev|pre|alpha|beta|rc|fips)[\d]*)*)(?:-\w+)?(?: \(.+?\))?$/', $opensslVersion, $matches)) {
            return null;
        }

        // OpenSSL 1 used 1.2.3a style versioning, 3+ uses semver
        $patch = '';
        if (version_compare($matches['version'], '3.0.0', '<')) {
            $patch = '.'.self::convertAlphaVersionToIntVersion($matches['patch']);
        }

        $isFips = strpos($matches['suffix'], 'fips') !== false;
        $suffix = strtr('-'.ltrim($matches['suffix'], '-'), ['-fips' => '', '-pre' => '-alpha']);

        return rtrim($matches['version'].$patch.$suffix, '-');
    }

    public static function parseLibjpeg(string $libjpegVersion): ?string
    {
        if (!Preg::isMatchStrictGroups('/^(?<major>\d+)(?<minor>[a-z]*)$/', $libjpegVersion, $matches)) {
            return null;
        }

        return $matches['major'].'.'.self::convertAlphaVersionToIntVersion($matches['minor']);
    }

    public static function parseZoneinfoVersion(string $zoneinfoVersion): ?string
    {
        if (!Preg::isMatchStrictGroups('/^(?<year>\d{4})(?<revision>[a-z]*)$/', $zoneinfoVersion, $matches)) {
            return null;
        }

        return $matches['year'].'.'.self::convertAlphaVersionToIntVersion($matches['revision']);
    }

    /**
     * "" => 0, "a" => 1, "zg" => 33
     */
    private static function convertAlphaVersionToIntVersion(string $alpha): int
    {
        return strlen($alpha) * (-ord('a') + 1) + array_sum(array_map('ord', str_split($alpha)));
    }

    public static function convertLibxpmVersionId(int $versionId): string
    {
        return self::convertVersionId($versionId, 100);
    }

    public static function convertOpenldapVersionId(int $versionId): string
    {
        return self::convertVersionId($versionId, 100);
    }

    private static function convertVersionId(int $versionId, int $base): string
    {
        return sprintf(
            '%d.%d.%d',
            $versionId / ($base * $base),
            (int) ($versionId / $base) % $base,
            $versionId % $base
        );
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Platform;

use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Symfony\Component\Process\ExecutableFinder;

class HhvmDetector
{
    /** @var string|false|null */
    private static $hhvmVersion = null;
    /** @var ?ExecutableFinder */
    private $executableFinder;
    /** @var ?ProcessExecutor */
    private $processExecutor;

    public function __construct(?ExecutableFinder $executableFinder = null, ?ProcessExecutor $processExecutor = null)
    {
        $this->executableFinder = $executableFinder;
        $this->processExecutor = $processExecutor;
    }

    public function reset(): void
    {
        self::$hhvmVersion = null;
    }

    public function getVersion(): ?string
    {
        if (null !== self::$hhvmVersion) {
            return self::$hhvmVersion ?: null;
        }

        self::$hhvmVersion = defined('HHVM_VERSION') ? HHVM_VERSION : null;
        if (self::$hhvmVersion === null && !Platform::isWindows()) {
            self::$hhvmVersion = false;
            $this->executableFinder = $this->executableFinder ?: new ExecutableFinder();
            $hhvmPath = $this->executableFinder->find('hhvm');
            if ($hhvmPath !== null) {
                $this->processExecutor = $this->processExecutor ?? new ProcessExecutor();
                $exitCode = $this->processExecutor->execute([$hhvmPath, '--php', '-d', 'hhvm.jit=0', '-r', 'echo HHVM_VERSION;'], self::$hhvmVersion);
                if ($exitCode !== 0) {
                    self::$hhvmVersion = false;
                }
            }
        }

        return self::$hhvmVersion ?: null;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Platform;

class Runtime
{
    /**
     * @param class-string $class
     */
    public function hasConstant(string $constant, ?string $class = null): bool
    {
        return defined(ltrim($class.'::'.$constant, ':'));
    }

    /**
     * @param class-string $class
     *
     * @return mixed
     */
    public function getConstant(string $constant, ?string $class = null)
    {
        return constant(ltrim($class.'::'.$constant, ':'));
    }

    public function hasFunction(string $fn): bool
    {
        return function_exists($fn);
    }

    /**
     * @param mixed[] $arguments
     *
     * @return mixed
     */
    public function invoke(callable $callable, array $arguments = [])
    {
        return $callable(...$arguments);
    }

    /**
     * @param class-string $class
     */
    public function hasClass(string $class): bool
    {
        return class_exists($class, false);
    }

    /**
     * @template T of object
     * @param mixed[] $arguments
     *
     * @phpstan-param class-string<T> $class
     * @phpstan-return T
     *
     * @throws \ReflectionException
     */
    public function construct(string $class, array $arguments = []): object
    {
        if (empty($arguments)) {
            return new $class;
        }

        $refl = new \ReflectionClass($class);

        return $refl->newInstanceArgs($arguments);
    }

    /** @return string[] */
    public function getExtensions(): array
    {
        return get_loaded_extensions();
    }

    public function getExtensionVersion(string $extension): string
    {
        $version = phpversion($extension);
        if ($version === false) {
            $version = '0';
        }

        return $version;
    }

    /**
     * @throws \ReflectionException
     */
    public function getExtensionInfo(string $extension): string
    {
        $reflector = new \ReflectionExtension($extension);

        ob_start();
        $reflector->info();

        return ob_get_clean();
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

/**
 * Thrown when a security problem, like a broken or missing signature
 *
 * @author Eric Daspet <edaspet@survol.fr>
 */
class RepositorySecurityException extends \Exception
{
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Advisory\PartialSecurityAdvisory;
use Composer\Advisory\SecurityAdvisory;

/**
 * Repositories that allow fetching security advisory data
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @internal
 */
interface AdvisoryProviderInterface
{
    public function hasSecurityAdvisories(): bool;

    /**
     * @param array<string, ConstraintInterface> $packageConstraintMap Map of package name to constraint (can be MatchAllConstraint to fetch all advisories)
     * @return ($allowPartialAdvisories is true ? array{namesFound: string[], advisories: array<string, array<PartialSecurityAdvisory|SecurityAdvisory>>} : array{namesFound: string[], advisories: array<string, array<SecurityAdvisory>>})
     */
    public function getSecurityAdvisories(array $packageConstraintMap, bool $allowPartialAdvisories = false): array;
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

/**
 * Lock array repository.
 *
 * Regular array repository, only uses a different type to identify the lock file as the source of info
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class LockArrayRepository extends ArrayRepository
{
    use CanonicalPackagesTrait;

    public function getRepoName(): string
    {
        return 'lock repo';
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

/**
 * Installed filesystem repository.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class InstalledFilesystemRepository extends FilesystemRepository implements InstalledRepositoryInterface
{
    public function getRepoName()
    {
        return 'installed '.parent::getRepoName();
    }

    /**
     * @inheritDoc
     */
    public function isFresh()
    {
        return !$this->file->exists();
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Downloader\TransportException;
use Composer\Pcre\Preg;
use Composer\Repository\Vcs\VcsDriverInterface;
use Composer\Package\Version\VersionParser;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Loader\ValidatingArrayLoader;
use Composer\Package\Loader\InvalidPackageException;
use Composer\Package\Loader\LoaderInterface;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Util\HttpDownloader;
use Composer\Util\Url;
use Composer\Semver\Constraint\Constraint;
use Composer\IO\IOInterface;
use Composer\Config;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInterface
{
    /** @var string */
    protected $url;
    /** @var ?string */
    protected $packageName;
    /** @var bool */
    protected $isVerbose;
    /** @var bool */
    protected $isVeryVerbose;
    /** @var IOInterface */
    protected $io;
    /** @var Config */
    protected $config;
    /** @var VersionParser */
    protected $versionParser;
    /** @var string */
    protected $type;
    /** @var ?LoaderInterface */
    protected $loader;
    /** @var array<string, mixed> */
    protected $repoConfig;
    /** @var HttpDownloader */
    protected $httpDownloader;
    /** @var ProcessExecutor */
    protected $processExecutor;
    /** @var bool */
    protected $branchErrorOccurred = false;
    /** @var array<string, class-string<VcsDriverInterface>> */
    private $drivers;
    /** @var ?VcsDriverInterface */
    private $driver;
    /** @var ?VersionCacheInterface */
    private $versionCache;
    /** @var list<string> */
    private $emptyReferences = [];
    /** @var array<'tags'|'branches', array<string, TransportException>> */
    private $versionTransportExceptions = [];

    /**
     * @param array{url: string, type?: string}&array<string, mixed> $repoConfig
     * @param array<string, class-string<VcsDriverInterface>>|null $drivers
     */
    public function __construct(array $repoConfig, IOInterface $io, Config $config, HttpDownloader $httpDownloader, ?EventDispatcher $dispatcher = null, ?ProcessExecutor $process = null, ?array $drivers = null, ?VersionCacheInterface $versionCache = null)
    {
        parent::__construct();
        $this->drivers = $drivers ?: [
            'github' => 'Composer\Repository\Vcs\GitHubDriver',
            'gitlab' => 'Composer\Repository\Vcs\GitLabDriver',
            'bitbucket' => 'Composer\Repository\Vcs\GitBitbucketDriver',
            'git-bitbucket' => 'Composer\Repository\Vcs\GitBitbucketDriver',
            'git' => 'Composer\Repository\Vcs\GitDriver',
            'hg' => 'Composer\Repository\Vcs\HgDriver',
            'perforce' => 'Composer\Repository\Vcs\PerforceDriver',
            'fossil' => 'Composer\Repository\Vcs\FossilDriver',
            // svn must be last because identifying a subversion server for sure is practically impossible
            'svn' => 'Composer\Repository\Vcs\SvnDriver',
        ];

        $this->url = $repoConfig['url'] = Platform::expandPath($repoConfig['url']);
        $this->io = $io;
        $this->type = $repoConfig['type'] ?? 'vcs';
        $this->isVerbose = $io->isVerbose();
        $this->isVeryVerbose = $io->isVeryVerbose();
        $this->config = $config;
        $this->repoConfig = $repoConfig;
        $this->versionCache = $versionCache;
        $this->httpDownloader = $httpDownloader;
        $this->processExecutor = $process ?? new ProcessExecutor($io);
    }

    public function getRepoName()
    {
        $driverClass = get_class($this->getDriver());
        $driverType = array_search($driverClass, $this->drivers);
        if (!$driverType) {
            $driverType = $driverClass;
        }

        return 'vcs repo ('.$driverType.' '.Url::sanitize($this->url).')';
    }

    public function getRepoConfig()
    {
        return $this->repoConfig;
    }

    public function setLoader(LoaderInterface $loader): void
    {
        $this->loader = $loader;
    }

    public function getDriver(): ?VcsDriverInterface
    {
        if ($this->driver) {
            return $this->driver;
        }

        if (isset($this->drivers[$this->type])) {
            $class = $this->drivers[$this->type];
            $this->driver = new $class($this->repoConfig, $this->io, $this->config, $this->httpDownloader, $this->processExecutor);
            $this->driver->initialize();

            return $this->driver;
        }

        foreach ($this->drivers as $driver) {
            if ($driver::supports($this->io, $this->config, $this->url)) {
                $this->driver = new $driver($this->repoConfig, $this->io, $this->config, $this->httpDownloader, $this->processExecutor);
                $this->driver->initialize();

                return $this->driver;
            }
        }

        foreach ($this->drivers as $driver) {
            if ($driver::supports($this->io, $this->config, $this->url, true)) {
                $this->driver = new $driver($this->repoConfig, $this->io, $this->config, $this->httpDownloader, $this->processExecutor);
                $this->driver->initialize();

                return $this->driver;
            }
        }

        return null;
    }

    public function hadInvalidBranches(): bool
    {
        return $this->branchErrorOccurred;
    }

    /**
     * @return list<string>
     */
    public function getEmptyReferences(): array
    {
        return $this->emptyReferences;
    }

    /**
     * @return array<'tags'|'branches', array<string, TransportException>>
     */
    public function getVersionTransportExceptions(): array
    {
        return $this->versionTransportExceptions;
    }

    protected function initialize()
    {
        parent::initialize();

        $isVerbose = $this->isVerbose;
        $isVeryVerbose = $this->isVeryVerbose;

        $driver = $this->getDriver();
        if (!$driver) {
            throw new \InvalidArgumentException('No driver found to handle VCS repository '.$this->url);
        }

        $this->versionParser = new VersionParser;
        if (!$this->loader) {
            $this->loader = new ArrayLoader($this->versionParser);
        }

        $hasRootIdentifierComposerJson = false;
        try {
            $hasRootIdentifierComposerJson = $driver->hasComposerFile($driver->getRootIdentifier());
            if ($hasRootIdentifierComposerJson) {
                $data = $driver->getComposerInformation($driver->getRootIdentifier());
                $this->packageName = !empty($data['name']) ? $data['name'] : null;
            }
        } catch (\Exception $e) {
            if ($e instanceof TransportException && $this->shouldRethrowTransportException($e)) {
                throw $e;
            }

            if ($isVeryVerbose) {
                $this->io->writeError('<error>Skipped parsing '.$driver->getRootIdentifier().', '.$e->getMessage().'</error>');
            }
        }

        foreach ($driver->getTags() as $tag => $identifier) {
            $tag = (string) $tag;
            $msg = 'Reading composer.json of <info>' . ($this->packageName ?: $this->url) . '</info> (<comment>' . $tag . '</comment>)';

            // strip the release- prefix from tags if present
            $tag = str_replace('release-', '', $tag);

            $cachedPackage = $this->getCachedPackageVersion($tag, $identifier, $isVerbose, $isVeryVerbose);
            if ($cachedPackage) {
                $this->addPackage($cachedPackage);

                continue;
            }
            if ($cachedPackage === false) {
                $this->emptyReferences[] = $identifier;

                continue;
            }

            if (!$parsedTag = $this->validateTag($tag)) {
                if ($isVeryVerbose) {
                    $this->io->writeError('<warning>Skipped tag '.$tag.', invalid tag name</warning>');
                }
                continue;
            }

            if ($isVeryVerbose) {
                $this->io->writeError($msg);
            } elseif ($isVerbose) {
                $this->io->overwriteError($msg, false);
            }

            try {
                $data = $driver->getComposerInformation($identifier);
                if (null === $data) {
                    if ($isVeryVerbose) {
                        $this->io->writeError('<warning>Skipped tag '.$tag.', no composer file</warning>');
                    }
                    $this->emptyReferences[] = $identifier;
                    continue;
                }

                // manually versioned package
                if (isset($data['version'])) {
                    $data['version_normalized'] = $this->versionParser->normalize($data['version']);
                } else {
                    // auto-versioned package, read value from tag
                    $data['version'] = $tag;
                    $data['version_normalized'] = $parsedTag;
                }

                // make sure tag packages have no -dev flag
                $data['version'] = Preg::replace('{[.-]?dev$}i', '', $data['version']);
                $data['version_normalized'] = Preg::replace('{(^dev-|[.-]?dev$)}i', '', $data['version_normalized']);

                // make sure tag do not contain the default-branch marker
                unset($data['default-branch']);

                // broken package, version doesn't match tag
                if ($data['version_normalized'] !== $parsedTag) {
                    if ($isVeryVerbose) {
                        if (Preg::isMatch('{(^dev-|[.-]?dev$)}i', $parsedTag)) {
                            $this->io->writeError('<warning>Skipped tag '.$tag.', invalid tag name, tags can not use dev prefixes or suffixes</warning>');
                        } else {
                            $this->io->writeError('<warning>Skipped tag '.$tag.', tag ('.$parsedTag.') does not match version ('.$data['version_normalized'].') in composer.json</warning>');
                        }
                    }
                    continue;
                }

                $tagPackageName = $this->packageName ?: ($data['name'] ?? '');
                if ($existingPackage = $this->findPackage($tagPackageName, $data['version_normalized'])) {
                    if ($isVeryVerbose) {
                        $this->io->writeError('<warning>Skipped tag '.$tag.', it conflicts with an another tag ('.$existingPackage->getPrettyVersion().') as both resolve to '.$data['version_normalized'].' internally</warning>');
                    }
                    continue;
                }

                if ($isVeryVerbose) {
                    $this->io->writeError('Importing tag '.$tag.' ('.$data['version_normalized'].')');
                }

                $this->addPackage($this->loader->load($this->preProcess($driver, $data, $identifier)));
            } catch (\Exception $e) {
                if ($e instanceof TransportException) {
                    $this->versionTransportExceptions['tags'][$tag] = $e;
                    if ($e->getCode() === 404) {
                        $this->emptyReferences[] = $identifier;
                    }
                    if ($this->shouldRethrowTransportException($e)) {
                        throw $e;
                    }
                }
                if ($isVeryVerbose) {
                    $this->io->writeError('<warning>Skipped tag '.$tag.', '.($e instanceof TransportException ? 'no composer file was found (' . $e->getCode() . ' HTTP status code)' : $e->getMessage()).'</warning>');
                }
                continue;
            }
        }

        if (!$isVeryVerbose) {
            $this->io->overwriteError('', false);
        }

        $branches = $driver->getBranches();
        // make sure the root identifier branch gets loaded first
        if ($hasRootIdentifierComposerJson && isset($branches[$driver->getRootIdentifier()])) {
            $branches = [$driver->getRootIdentifier() => $branches[$driver->getRootIdentifier()]] + $branches;
        }

        foreach ($branches as $branch => $identifier) {
            $branch = (string) $branch;
            $msg = 'Reading composer.json of <info>' . ($this->packageName ?: $this->url) . '</info> (<comment>' . $branch . '</comment>)';
            if ($isVeryVerbose) {
                $this->io->writeError($msg);
            } elseif ($isVerbose) {
                $this->io->overwriteError($msg, false);
            }

            if (!$parsedBranch = $this->validateBranch($branch)) {
                if ($isVeryVerbose) {
                    $this->io->writeError('<warning>Skipped branch '.$branch.', invalid name</warning>');
                }
                continue;
            }

            // make sure branch packages have a dev flag
            if (strpos($parsedBranch, 'dev-') === 0 || VersionParser::DEFAULT_BRANCH_ALIAS === $parsedBranch) {
                $version = 'dev-' . str_replace('#', '+', $branch);
                $parsedBranch = str_replace('#', '+', $parsedBranch);
            } else {
                $prefix = strpos($branch, 'v') === 0 ? 'v' : '';
                $version = $prefix . Preg::replace('{(\.9{7})+}', '.x', $parsedBranch);
            }

            $cachedPackage = $this->getCachedPackageVersion($version, $identifier, $isVerbose, $isVeryVerbose, $driver->getRootIdentifier() === $branch);
            if ($cachedPackage) {
                $this->addPackage($cachedPackage);

                continue;
            }
            if ($cachedPackage === false) {
                $this->emptyReferences[] = $identifier;

                continue;
            }

            try {
                $data = $driver->getComposerInformation($identifier);
                if (null === $data) {
                    if ($isVeryVerbose) {
                        $this->io->writeError('<warning>Skipped branch '.$branch.', no composer file</warning>');
                    }
                    $this->emptyReferences[] = $identifier;
                    continue;
                }

                // branches are always auto-versioned, read value from branch name
                $data['version'] = $version;
                $data['version_normalized'] = $parsedBranch;

                unset($data['default-branch']);
                if ($driver->getRootIdentifier() === $branch) {
                    $data['default-branch'] = true;
                }

                if ($isVeryVerbose) {
                    $this->io->writeError('Importing branch '.$branch.' ('.$data['version'].')');
                }

                $packageData = $this->preProcess($driver, $data, $identifier);
                $package = $this->loader->load($packageData);
                if ($this->loader instanceof ValidatingArrayLoader && \count($this->loader->getWarnings()) > 0) {
                    throw new InvalidPackageException($this->loader->getErrors(), $this->loader->getWarnings(), $packageData);
                }
                $this->addPackage($package);
            } catch (TransportException $e) {
                $this->versionTransportExceptions['branches'][$branch] = $e;
                if ($e->getCode() === 404) {
                    $this->emptyReferences[] = $identifier;
                }
                if ($this->shouldRethrowTransportException($e)) {
                    throw $e;
                }
                if ($isVeryVerbose) {
                    $this->io->writeError('<warning>Skipped branch '.$branch.', no composer file was found (' . $e->getCode() . ' HTTP status code)</warning>');
                }
                continue;
            } catch (\Exception $e) {
                if (!$isVeryVerbose) {
                    $this->io->writeError('');
                }
                $this->branchErrorOccurred = true;
                $this->io->writeError('<error>Skipped branch '.$branch.', '.$e->getMessage().'</error>');
                $this->io->writeError('');
                continue;
            }
        }
        $driver->cleanup();

        if (!$isVeryVerbose) {
            $this->io->overwriteError('', false);
        }

        if (!$this->getPackages()) {
            throw new InvalidRepositoryException('No valid composer.json was found in any branch or tag of '.$this->url.', could not load a package from it.');
        }
    }

    /**
     * @param array{name?: string, dist?: array{type: string, url: string, reference: string, shasum: string}, source?: array{type: string, url: string, reference: string}} $data
     *
     * @return array{name: string|null, dist: array{type: string, url: string, reference: string, shasum: string}|null, source: array{type: string, url: string, reference: string}}
     */
    protected function preProcess(VcsDriverInterface $driver, array $data, string $identifier): array
    {
        // keep the name of the main identifier for all packages
        // this ensures that a package can be renamed in one place and that all old tags
        // will still be installable using that new name without requiring re-tagging
        $dataPackageName = $data['name'] ?? null;
        $data['name'] = $this->packageName ?: $dataPackageName;

        if (!isset($data['dist'])) {
            $data['dist'] = $driver->getDist($identifier);
        }
        if (!isset($data['source'])) {
            $data['source'] = $driver->getSource($identifier);
        }

        // if custom dist info is provided but does not provide a reference, copy the source reference to it
        if (is_array($data['dist']) && !isset($data['dist']['reference']) && isset($data['source']['reference'])) {
            $data['dist']['reference'] = $data['source']['reference'];
        }

        return $data;
    }

    /**
     * @return string|false
     */
    private function validateBranch(string $branch)
    {
        try {
            $normalizedBranch = $this->versionParser->normalizeBranch($branch);

            // validate that the branch name has no weird characters conflicting with constraints
            $this->versionParser->parseConstraints($normalizedBranch);

            return $normalizedBranch;
        } catch (\Exception $e) {
        }

        return false;
    }

    /**
     * @return string|false
     */
    private function validateTag(string $version)
    {
        try {
            return $this->versionParser->normalize($version);
        } catch (\Exception $e) {
        }

        return false;
    }

    /**
     * @return \Composer\Package\CompletePackage|\Composer\Package\CompleteAliasPackage|null|false null if no cache present, false if the absence of a version was cached
     */
    private function getCachedPackageVersion(string $version, string $identifier, bool $isVerbose, bool $isVeryVerbose, bool $isDefaultBranch = false)
    {
        if (!$this->versionCache) {
            return null;
        }

        $cachedPackage = $this->versionCache->getVersionPackage($version, $identifier);
        if ($cachedPackage === false) {
            if ($isVeryVerbose) {
                $this->io->writeError('<warning>Skipped '.$version.', no composer file (cached from ref '.$identifier.')</warning>');
            }

            return false;
        }

        if ($cachedPackage) {
            $msg = 'Found cached composer.json of <info>' . ($this->packageName ?: $this->url) . '</info> (<comment>' . $version . '</comment>)';
            if ($isVeryVerbose) {
                $this->io->writeError($msg);
            } elseif ($isVerbose) {
                $this->io->overwriteError($msg, false);
            }

            unset($cachedPackage['default-branch']);
            if ($isDefaultBranch) {
                $cachedPackage['default-branch'] = true;
            }

            if ($existingPackage = $this->findPackage($cachedPackage['name'], new Constraint('=', $cachedPackage['version_normalized']))) {
                if ($isVeryVerbose) {
                    $this->io->writeError('<warning>Skipped cached version '.$version.', it conflicts with an another tag ('.$existingPackage->getPrettyVersion().') as both resolve to '.$cachedPackage['version_normalized'].' internally</warning>');
                }
                $cachedPackage = null;
            }
        }

        if ($cachedPackage) {
            return $this->loader->load($cachedPackage);
        }

        return null;
    }

    private function shouldRethrowTransportException(TransportException $e): bool
    {
        return in_array($e->getCode(), [401, 403, 429], true) || $e->getCode() >= 500;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Config;
use Composer\EventDispatcher\EventDispatcher;
use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Version\VersionGuesser;
use Composer\Package\Version\VersionParser;
use Composer\Pcre\Preg;
use Composer\Util\HttpDownloader;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Util\Filesystem;
use Composer\Util\Url;
use Composer\Util\Git as GitUtil;

/**
 * This repository allows installing local packages that are not necessarily under their own VCS.
 *
 * The local packages will be symlinked when possible, else they will be copied.
 *
 * @code
 * "require": {
 *     "<vendor>/<local-package>": "*"
 * },
 * "repositories": [
 *     {
 *         "type": "path",
 *         "url": "../../relative/path/to/package/"
 *     },
 *     {
 *         "type": "path",
 *         "url": "/absolute/path/to/package/"
 *     },
 *     {
 *         "type": "path",
 *         "url": "/absolute/path/to/several/packages/*"
 *     },
 *     {
 *         "type": "path",
 *         "url": "../../relative/path/to/package/",
 *         "options": {
 *             "symlink": false
 *         }
 *     },
 *     {
 *         "type": "path",
 *         "url": "../../relative/path/to/package/",
 *         "options": {
 *             "reference": "none"
 *         }
 *     },
 * ]
 * @endcode
 *
 * @author Samuel Roze <samuel.roze@gmail.com>
 * @author Johann Reinke <johann.reinke@gmail.com>
 */
class PathRepository extends ArrayRepository implements ConfigurableRepositoryInterface
{
    /**
     * @var ArrayLoader
     */
    private $loader;

    /**
     * @var VersionGuesser
     */
    private $versionGuesser;

    /**
     * @var string
     */
    private $url;

    /**
     * @var mixed[]
     * @phpstan-var array{url: string, options?: array{symlink?: bool, reference?: string, relative?: bool, versions?: array<string, string>}}
     */
    private $repoConfig;

    /**
     * @var ProcessExecutor
     */
    private $process;

    /**
     * @var array{symlink?: bool, reference?: string, relative?: bool, versions?: array<string, string>}
     */
    private $options;

    /**
     * Initializes path repository.
     *
     * @param array{url?: string, options?: array{symlink?: bool, reference?: string, relative?: bool, versions?: array<string, string>}} $repoConfig
     */
    public function __construct(array $repoConfig, IOInterface $io, Config $config, ?HttpDownloader $httpDownloader = null, ?EventDispatcher $dispatcher = null, ?ProcessExecutor $process = null)
    {
        if (!isset($repoConfig['url'])) {
            throw new \RuntimeException('You must specify the `url` configuration for the path repository');
        }

        $this->loader = new ArrayLoader(null, true);
        $this->url = Platform::expandPath($repoConfig['url']);
        $this->process = $process ?? new ProcessExecutor($io);
        $this->versionGuesser = new VersionGuesser($config, $this->process, new VersionParser(), $io);
        $this->repoConfig = $repoConfig;
        $this->options = $repoConfig['options'] ?? [];
        if (!isset($this->options['relative'])) {
            $filesystem = new Filesystem();
            $this->options['relative'] = !$filesystem->isAbsolutePath($this->url);
        }

        parent::__construct();
    }

    public function getRepoName(): string
    {
        return 'path repo ('.Url::sanitize($this->repoConfig['url']).')';
    }

    public function getRepoConfig(): array
    {
        return $this->repoConfig;
    }

    /**
     * Initializes path repository.
     *
     * This method will basically read the folder and add the found package.
     */
    protected function initialize(): void
    {
        parent::initialize();

        $urlMatches = $this->getUrlMatches();

        if (empty($urlMatches)) {
            if (Preg::isMatch('{[*{}]}', $this->url)) {
                $url = $this->url;
                while (Preg::isMatch('{[*{}]}', $url)) {
                    $url = dirname($url);
                }
                // the parent directory before any wildcard exists, so we assume it is correctly configured but simply empty
                if (is_dir($url)) {
                    return;
                }
            }

            throw new \RuntimeException('The `url` supplied for the path (' . $this->url . ') repository does not exist');
        }

        foreach ($urlMatches as $url) {
            $path = realpath($url) . DIRECTORY_SEPARATOR;
            $composerFilePath = $path.'composer.json';

            if (!file_exists($composerFilePath)) {
                continue;
            }

            $json = file_get_contents($composerFilePath);
            $package = JsonFile::parseJson($json, $composerFilePath);
            $package['dist'] = [
                'type' => 'path',
                'url' => $url,
            ];
            $reference = $this->options['reference'] ?? 'auto';
            if ('none' === $reference) {
                $package['dist']['reference'] = null;
            } elseif ('config' === $reference || 'auto' === $reference) {
                $package['dist']['reference'] = hash('sha1', $json . serialize($this->options));
            }

            // copy symlink/relative options to transport options
            $package['transport-options'] = array_intersect_key($this->options, ['symlink' => true, 'relative' => true]);
            // use the version provided as option if available
            if (isset($package['name'], $this->options['versions'][$package['name']])) {
                $package['version'] = $this->options['versions'][$package['name']];
            }

            // carry over the root package version if this path repo is in the same git repository as root package
            if (!isset($package['version']) && ($rootVersion = Platform::getEnv('COMPOSER_ROOT_VERSION'))) {
                if (
                    0 === $this->process->execute(['git', 'rev-parse', 'HEAD'], $ref1, $path)
                    && 0 === $this->process->execute(['git', 'rev-parse', 'HEAD'], $ref2)
                    && $ref1 === $ref2
                ) {
                    $package['version'] = $this->versionGuesser->getRootVersionFromEnv();
                }
            }

            $output = '';
            if ('auto' === $reference && is_dir($path . DIRECTORY_SEPARATOR . '.git') && 0 === $this->process->execute(array_merge(['git', 'log', '-n1', '--pretty=%H'], GitUtil::getNoShowSignatureFlags($this->process)), $output, $path)) {
                $package['dist']['reference'] = trim($output);
            }

            if (!isset($package['version'])) {
                $versionData = $this->versionGuesser->guessVersion($package, $path);
                if (is_array($versionData) && $versionData['pretty_version']) {
                    // if there is a feature branch detected, we add a second packages with the feature branch version
                    if (!empty($versionData['feature_pretty_version'])) {
                        $package['version'] = $versionData['feature_pretty_version'];
                        $this->addPackage($this->loader->load($package));
                    }

                    $package['version'] = $versionData['pretty_version'];
                } else {
                    $package['version'] = 'dev-main';
                }
            }

            try {
                $this->addPackage($this->loader->load($package));
            } catch (\Exception $e) {
                throw new \RuntimeException('Failed loading the package in '.$composerFilePath, 0, $e);
            }
        }
    }

    /**
     * Get a list of all (possibly relative) path names matching given url (supports globbing).
     *
     * @return string[]
     */
    private function getUrlMatches(): array
    {
        $flags = GLOB_MARK | GLOB_ONLYDIR;

        if (defined('GLOB_BRACE')) {
            $flags |= GLOB_BRACE;
        } elseif (strpos($this->url, '{') !== false || strpos($this->url, '}') !== false) {
            throw new \RuntimeException('The operating system does not support GLOB_BRACE which is required for the url '. $this->url);
        }

        // Ensure environment-specific path separators are normalized to URL separators
        return array_map(static function ($val): string {
            return rtrim(str_replace(DIRECTORY_SEPARATOR, '/', $val), '/');
        }, glob($this->url, $flags));
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Package\PackageInterface;
use Composer\Installer\InstallationManager;

/**
 * Writable repository interface.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
interface WritableRepositoryInterface extends RepositoryInterface
{
    /**
     * Writes repository (f.e. to the disc).
     *
     * @param bool $devMode Whether dev requirements were included or not in this installation
     * @return void
     */
    public function write(bool $devMode, InstallationManager $installationManager);

    /**
     * Adds package to the repository.
     *
     * @param PackageInterface $package package instance
     * @return void
     */
    public function addPackage(PackageInterface $package);

    /**
     * Removes package from the repository.
     *
     * @param PackageInterface $package package instance
     * @return void
     */
    public function removePackage(PackageInterface $package);

    /**
     * Get unique packages (at most one package of each name), with aliases resolved and removed.
     *
     * @return PackageInterface[]
     */
    public function getCanonicalPackages();

    /**
     * Forces a reload of all packages.
     *
     * @return void
     */
    public function reload();

    /**
     * @param string[] $devPackageNames
     * @return void
     */
    public function setDevPackageNames(array $devPackageNames);

    /**
     * @return string[] Names of dependencies installed through require-dev
     */
    public function getDevPackageNames();
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

/**
 * Exception thrown when a package repository is utterly broken
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class InvalidRepositoryException extends \Exception
{
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Composer;
use Composer\Package\CompletePackage;
use Composer\Package\CompletePackageInterface;
use Composer\Package\Link;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\Pcre\Preg;
use Composer\Platform\HhvmDetector;
use Composer\Platform\Runtime;
use Composer\Platform\Version;
use Composer\Plugin\PluginInterface;
use Composer\Semver\Constraint\Constraint;
use Composer\Util\Silencer;
use Composer\XdebugHandler\XdebugHandler;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PlatformRepository extends ArrayRepository
{
    /**
     * @deprecated use PlatformRepository::isPlatformPackage(string $name) instead
     * @private
     */
    public const PLATFORM_PACKAGE_REGEX = '{^(?:php(?:-64bit|-ipv6|-zts|-debug)?|hhvm|(?:ext|lib)-[a-z0-9](?:[_.-]?[a-z0-9]+)*|composer(?:-(?:plugin|runtime)-api)?)$}iD';

    /**
     * @var ?string
     */
    private static $lastSeenPlatformPhp = null;

    /**
     * @var VersionParser
     */
    private $versionParser;

    /**
     * Defines overrides so that the platform can be mocked
     *
     * Keyed by package name (lowercased)
     *
     * @var array<string, array{name: string, version: string|false}>
     */
    private $overrides = [];

    /**
     * Stores which packages have been disabled and their actual version
     *
     * @var array<string, CompletePackageInterface>
     */
    private $disabledPackages = [];

    /** @var Runtime */
    private $runtime;
    /** @var HhvmDetector */
    private $hhvmDetector;

    /**
     * @param array<string, string|false> $overrides
     */
    public function __construct(array $packages = [], array $overrides = [], ?Runtime $runtime = null, ?HhvmDetector $hhvmDetector = null)
    {
        $this->runtime = $runtime ?: new Runtime();
        $this->hhvmDetector = $hhvmDetector ?: new HhvmDetector();
        foreach ($overrides as $name => $version) {
            if (!is_string($version) && false !== $version) { // @phpstan-ignore-line
                throw new \UnexpectedValueException('config.platform.'.$name.' should be a string or false, but got '.gettype($version).' '.var_export($version, true));
            }
            if ($name === 'php' && $version === false) {
                throw new \UnexpectedValueException('config.platform.'.$name.' cannot be set to false as you cannot disable php entirely.');
            }
            $this->overrides[strtolower($name)] = ['name' => $name, 'version' => $version];
        }
        parent::__construct($packages);
    }

    public function getRepoName(): string
    {
        return 'platform repo';
    }

    public function isPlatformPackageDisabled(string $name): bool
    {
        return isset($this->disabledPackages[$name]);
    }

    /**
     * @return array<string, CompletePackageInterface>
     */
    public function getDisabledPackages(): array
    {
        return $this->disabledPackages;
    }

    protected function initialize(): void
    {
        parent::initialize();

        $libraries = [];

        $this->versionParser = new VersionParser();

        // Add each of the override versions as options.
        // Later we might even replace the extensions instead.
        foreach ($this->overrides as $override) {
            // Check that it's a platform package.
            if (!self::isPlatformPackage($override['name'])) {
                throw new \InvalidArgumentException('Invalid platform package name in config.platform: '.$override['name']);
            }

            if ($override['version'] !== false) {
                $this->addOverriddenPackage($override);
            }
        }

        $prettyVersion = Composer::getVersion();
        $version = $this->versionParser->normalize($prettyVersion);
        $composer = new CompletePackage('composer', $version, $prettyVersion);
        $composer->setDescription('Composer package');
        $this->addPackage($composer);

        $prettyVersion = PluginInterface::PLUGIN_API_VERSION;
        $version = $this->versionParser->normalize($prettyVersion);
        $composerPluginApi = new CompletePackage('composer-plugin-api', $version, $prettyVersion);
        $composerPluginApi->setDescription('The Composer Plugin API');
        $this->addPackage($composerPluginApi);

        $prettyVersion = Composer::RUNTIME_API_VERSION;
        $version = $this->versionParser->normalize($prettyVersion);
        $composerRuntimeApi = new CompletePackage('composer-runtime-api', $version, $prettyVersion);
        $composerRuntimeApi->setDescription('The Composer Runtime API');
        $this->addPackage($composerRuntimeApi);

        try {
            $prettyVersion = $this->runtime->getConstant('PHP_VERSION');
            $version = $this->versionParser->normalize($prettyVersion);
        } catch (\UnexpectedValueException $e) {
            $prettyVersion = Preg::replace('#^([^~+-]+).*$#', '$1', $this->runtime->getConstant('PHP_VERSION'));
            $version = $this->versionParser->normalize($prettyVersion);
        }

        $php = new CompletePackage('php', $version, $prettyVersion);
        $php->setDescription('The PHP interpreter');
        $this->addPackage($php);

        if ($this->runtime->getConstant('PHP_DEBUG')) {
            $phpdebug = new CompletePackage('php-debug', $version, $prettyVersion);
            $phpdebug->setDescription('The PHP interpreter, with debugging symbols');
            $this->addPackage($phpdebug);
        }

        if ($this->runtime->hasConstant('PHP_ZTS') && $this->runtime->getConstant('PHP_ZTS')) {
            $phpzts = new CompletePackage('php-zts', $version, $prettyVersion);
            $phpzts->setDescription('The PHP interpreter, with Zend Thread Safety');
            $this->addPackage($phpzts);
        }

        if ($this->runtime->getConstant('PHP_INT_SIZE') === 8) {
            $php64 = new CompletePackage('php-64bit', $version, $prettyVersion);
            $php64->setDescription('The PHP interpreter, 64bit');
            $this->addPackage($php64);
        }

        // The AF_INET6 constant is only defined if ext-sockets is available but
        // IPv6 support might still be available.
        if ($this->runtime->hasConstant('AF_INET6') || Silencer::call([$this->runtime, 'invoke'], 'inet_pton', ['::']) !== false) {
            $phpIpv6 = new CompletePackage('php-ipv6', $version, $prettyVersion);
            $phpIpv6->setDescription('The PHP interpreter, with IPv6 support');
            $this->addPackage($phpIpv6);
        }

        $loadedExtensions = $this->runtime->getExtensions();

        // Extensions scanning
        foreach ($loadedExtensions as $name) {
            if (in_array($name, ['standard', 'Core'])) {
                continue;
            }

            $this->addExtension($name, $this->runtime->getExtensionVersion($name));
        }

        // Check for Xdebug in a restarted process
        if (!in_array('xdebug', $loadedExtensions, true) && ($prettyVersion = XdebugHandler::getSkippedVersion())) {
            $this->addExtension('xdebug', $prettyVersion);
        }

        // Another quick loop, just for possible libraries
        // Doing it this way to know that functions or constants exist before
        // relying on them.
        foreach ($loadedExtensions as $name) {
            switch ($name) {
                case 'amqp':
                    $info = $this->runtime->getExtensionInfo($name);

                    // librabbitmq version => 0.9.0
                    if (Preg::isMatch('/^librabbitmq version => (?<version>.+)$/im', $info, $librabbitmqMatches)) {
                        $this->addLibrary($libraries, $name.'-librabbitmq', $librabbitmqMatches['version'], 'AMQP librabbitmq version');
                    }

                    // AMQP protocol version => 0-9-1
                    if (Preg::isMatchStrictGroups('/^AMQP protocol version => (?<version>.+)$/im', $info, $protocolMatches)) {
                        $this->addLibrary($libraries, $name.'-protocol', str_replace('-', '.', $protocolMatches['version']), 'AMQP protocol version');
                    }
                    break;

                case 'bz2':
                    $info = $this->runtime->getExtensionInfo($name);

                    // BZip2 Version => 1.0.6, 6-Sept-2010
                    if (Preg::isMatch('/^BZip2 Version => (?<version>.*),/im', $info, $matches)) {
                        $this->addLibrary($libraries, $name, $matches['version']);
                    }
                    break;

                case 'curl':
                    $curlVersion = $this->runtime->invoke('curl_version');
                    $this->addLibrary($libraries, $name, $curlVersion['version']);

                    $info = $this->runtime->getExtensionInfo($name);

                    // SSL Version => OpenSSL/1.0.1t
                    if (Preg::isMatchStrictGroups('{^SSL Version => (?<library>[^/]+)/(?<version>.+)$}im', $info, $sslMatches)) {
                        $library = strtolower($sslMatches['library']);
                        if ($library === 'openssl') {
                            $parsedVersion = Version::parseOpenssl($sslMatches['version'], $isFips);
                            $this->addLibrary($libraries, $name.'-openssl'.($isFips ? '-fips' : ''), $parsedVersion, 'curl OpenSSL version ('.$parsedVersion.')', [], $isFips ? ['curl-openssl'] : []);
                        } else {
                            if ($library === '(securetransport) openssl') {
                                $shortlib = 'securetransport';
                            } else {
                                $shortlib = $library;
                            }
                            $this->addLibrary($libraries, $name.'-'.$shortlib, $sslMatches['version'], 'curl '.$library.' version ('.$sslMatches['version'].')', ['curl-openssl']);
                        }
                    }

                    // libSSH Version => libssh2/1.4.3
                    if (Preg::isMatchStrictGroups('{^libSSH Version => (?<library>[^/]+)/(?<version>.+?)(?:/.*)?$}im', $info, $sshMatches)) {
                        $this->addLibrary($libraries, $name.'-'.strtolower($sshMatches['library']), $sshMatches['version'], 'curl '.$sshMatches['library'].' version');
                    }

                    // ZLib Version => 1.2.8
                    if (Preg::isMatchStrictGroups('{^ZLib Version => (?<version>.+)$}im', $info, $zlibMatches)) {
                        $this->addLibrary($libraries, $name.'-zlib', $zlibMatches['version'], 'curl zlib version');
                    }
                    break;

                case 'date':
                    $info = $this->runtime->getExtensionInfo($name);

                    // timelib version => 2018.03
                    if (Preg::isMatchStrictGroups('/^timelib version => (?<version>.+)$/im', $info, $timelibMatches)) {
                        $this->addLibrary($libraries, $name.'-timelib', $timelibMatches['version'], 'date timelib version');
                    }

                    // Timezone Database => internal
                    if (Preg::isMatchStrictGroups('/^Timezone Database => (?<source>internal|external)$/im', $info, $zoneinfoSourceMatches)) {
                        $external = $zoneinfoSourceMatches['source'] === 'external';
                        if (Preg::isMatchStrictGroups('/^"Olson" Timezone Database Version => (?<version>.+?)(?:\.system)?$/im', $info, $zoneinfoMatches)) {
                            // If the timezonedb is provided by ext/timezonedb, register that version as a replacement
                            if ($external && in_array('timezonedb', $loadedExtensions, true)) {
                                $this->addLibrary($libraries, 'timezonedb-zoneinfo', $zoneinfoMatches['version'], 'zoneinfo ("Olson") database for date (replaced by timezonedb)', [$name.'-zoneinfo']);
                            } else {
                                $this->addLibrary($libraries, $name.'-zoneinfo', $zoneinfoMatches['version'], 'zoneinfo ("Olson") database for date');
                            }
                        }
                    }
                    break;

                case 'fileinfo':
                    $info = $this->runtime->getExtensionInfo($name);

                    // libmagic => 537
                    if (Preg::isMatch('/^libmagic => (?<version>.+)$/im', $info, $magicMatches)) {
                        $this->addLibrary($libraries, $name.'-libmagic', $magicMatches['version'], 'fileinfo libmagic version');
                    }
                    break;

                case 'gd':
                    $this->addLibrary($libraries, $name, $this->runtime->getConstant('GD_VERSION'));

                    $info = $this->runtime->getExtensionInfo($name);

                    if (Preg::isMatchStrictGroups('/^libJPEG Version => (?<version>.+?)(?: compatible)?$/im', $info, $libjpegMatches)) {
                        $this->addLibrary($libraries, $name.'-libjpeg', Version::parseLibjpeg($libjpegMatches['version']), 'libjpeg version for gd');
                    }

                    if (Preg::isMatchStrictGroups('/^libPNG Version => (?<version>.+)$/im', $info, $libpngMatches)) {
                        $this->addLibrary($libraries, $name.'-libpng', $libpngMatches['version'], 'libpng version for gd');
                    }

                    if (Preg::isMatchStrictGroups('/^FreeType Version => (?<version>.+)$/im', $info, $freetypeMatches)) {
                        $this->addLibrary($libraries, $name.'-freetype', $freetypeMatches['version'], 'freetype version for gd');
                    }

                    if (Preg::isMatchStrictGroups('/^libXpm Version => (?<versionId>\d+)$/im', $info, $libxpmMatches)) {
                        $this->addLibrary($libraries, $name.'-libxpm', Version::convertLibxpmVersionId((int) $libxpmMatches['versionId']), 'libxpm version for gd');
                    }

                    break;

                case 'gmp':
                    $this->addLibrary($libraries, $name, $this->runtime->getConstant('GMP_VERSION'));
                    break;

                case 'iconv':
                    $this->addLibrary($libraries, $name, $this->runtime->getConstant('ICONV_VERSION'));
                    break;

                case 'intl':
                    $info = $this->runtime->getExtensionInfo($name);

                    $description = 'The ICU unicode and globalization support library';
                    // Truthy check is for testing only so we can make the condition fail
                    if ($this->runtime->hasConstant('INTL_ICU_VERSION')) {
                        $this->addLibrary($libraries, 'icu', $this->runtime->getConstant('INTL_ICU_VERSION'), $description);
                    } elseif (Preg::isMatch('/^ICU version => (?<version>.+)$/im', $info, $matches)) {
                        $this->addLibrary($libraries, 'icu', $matches['version'], $description);
                    }

                    // ICU TZData version => 2019c
                    if (Preg::isMatchStrictGroups('/^ICU TZData version => (?<version>.*)$/im', $info, $zoneinfoMatches) && null !== ($version = Version::parseZoneinfoVersion($zoneinfoMatches['version']))) {
                        $this->addLibrary($libraries, 'icu-zoneinfo', $version, 'zoneinfo ("Olson") database for icu');
                    }

                    // Add a separate version for the CLDR library version
                    if ($this->runtime->hasClass('ResourceBundle')) {
                        $resourceBundle = $this->runtime->invoke(['ResourceBundle', 'create'], ['root', 'ICUDATA', false]);
                        if ($resourceBundle !== null) {
                            $this->addLibrary($libraries, 'icu-cldr', $resourceBundle->get('Version'), 'ICU CLDR project version');
                        }
                    }

                    if ($this->runtime->hasClass('IntlChar')) {
                        $this->addLibrary($libraries, 'icu-unicode', implode('.', array_slice($this->runtime->invoke(['IntlChar', 'getUnicodeVersion']), 0, 3)), 'ICU unicode version');
                    }
                    break;

                case 'imagick':
                    // @phpstan-ignore staticMethod.dynamicCall (called like this for mockability)
                    $imageMagickVersion = $this->runtime->construct('Imagick')->getVersion();
                    // 6.x: ImageMagick 6.2.9 08/24/06 Q16 http://www.imagemagick.org
                    // 7.x: ImageMagick 7.0.8-34 Q16 x86_64 2019-03-23 https://imagemagick.org
                    if (Preg::isMatch('/^ImageMagick (?<version>[\d.]+)(?:-(?<patch>\d+))?/', $imageMagickVersion['versionString'], $matches)) {
                        $version = $matches['version'];
                        if (isset($matches['patch'])) {
                            $version .= '.'.$matches['patch'];
                        }

                        $this->addLibrary($libraries, $name.'-imagemagick', $version, null, ['imagick']);
                    }
                    break;

                case 'ldap':
                    $info = $this->runtime->getExtensionInfo($name);

                    if (Preg::isMatchStrictGroups('/^Vendor Version => (?<versionId>\d+)$/im', $info, $matches) && Preg::isMatchStrictGroups('/^Vendor Name => (?<vendor>.+)$/im', $info, $vendorMatches)) {
                        $this->addLibrary($libraries, $name.'-'.strtolower($vendorMatches['vendor']), Version::convertOpenldapVersionId((int) $matches['versionId']), $vendorMatches['vendor'].' version of ldap');
                    }
                    break;

                case 'libxml':
                    // ext/dom, ext/simplexml, ext/xmlreader and ext/xmlwriter use the same libxml as the ext/libxml
                    $libxmlProvides = array_map(static function ($extension): string {
                        return $extension . '-libxml';
                    }, array_intersect($loadedExtensions, ['dom', 'simplexml', 'xml', 'xmlreader', 'xmlwriter']));
                    $this->addLibrary($libraries, $name, $this->runtime->getConstant('LIBXML_DOTTED_VERSION'), 'libxml library version', [], $libxmlProvides);

                    break;

                case 'mbstring':
                    $info = $this->runtime->getExtensionInfo($name);

                    // libmbfl version => 1.3.2
                    if (Preg::isMatch('/^libmbfl version => (?<version>.+)$/im', $info, $libmbflMatches)) {
                        $this->addLibrary($libraries, $name.'-libmbfl', $libmbflMatches['version'], 'mbstring libmbfl version');
                    }

                    if ($this->runtime->hasConstant('MB_ONIGURUMA_VERSION')) {
                        $this->addLibrary($libraries, $name.'-oniguruma', $this->runtime->getConstant('MB_ONIGURUMA_VERSION'), 'mbstring oniguruma version');

                    // Multibyte regex (oniguruma) version => 5.9.5
                    // oniguruma version => 6.9.0
                    } elseif (Preg::isMatch('/^(?:oniguruma|Multibyte regex \(oniguruma\)) version => (?<version>.+)$/im', $info, $onigurumaMatches)) {
                        $this->addLibrary($libraries, $name.'-oniguruma', $onigurumaMatches['version'], 'mbstring oniguruma version');
                    }

                    break;

                case 'memcached':
                    $info = $this->runtime->getExtensionInfo($name);

                    // libmemcached version => 1.0.18
                    if (Preg::isMatch('/^libmemcached version => (?<version>.+)$/im', $info, $matches)) {
                        $this->addLibrary($libraries, $name.'-libmemcached', $matches['version'], 'libmemcached version');
                    }
                    break;

                case 'openssl':
                    // OpenSSL 1.1.1g  21 Apr 2020
                    if (Preg::isMatchStrictGroups('{^(?:OpenSSL|LibreSSL)?\s*(?<version>\S+)}i', $this->runtime->getConstant('OPENSSL_VERSION_TEXT'), $matches)) {
                        $parsedVersion = Version::parseOpenssl($matches['version'], $isFips);
                        $this->addLibrary($libraries, $name.($isFips ? '-fips' : ''), $parsedVersion, $this->runtime->getConstant('OPENSSL_VERSION_TEXT'), [], $isFips ? [$name] : []);
                    }
                    break;

                case 'pcre':
                    $this->addLibrary($libraries, $name, Preg::replace('{^(\S+).*}', '$1', $this->runtime->getConstant('PCRE_VERSION')));

                    $info = $this->runtime->getExtensionInfo($name);

                    // PCRE Unicode Version => 12.1.0
                    if (Preg::isMatchStrictGroups('/^PCRE Unicode Version => (?<version>.+)$/im', $info, $pcreUnicodeMatches)) {
                        $this->addLibrary($libraries, $name.'-unicode', $pcreUnicodeMatches['version'], 'PCRE Unicode version support');
                    }

                    break;

                case 'mysqlnd':
                case 'pdo_mysql':
                    $info = $this->runtime->getExtensionInfo($name);

                    if (Preg::isMatchStrictGroups('/^(?:Client API version|Version) => mysqlnd (?<version>.+?) /mi', $info, $matches)) {
                        $this->addLibrary($libraries, $name.'-mysqlnd', $matches['version'], 'mysqlnd library version for '.$name);
                    }
                    break;

                case 'mongodb':
                    $info = $this->runtime->getExtensionInfo($name);

                    if (Preg::isMatchStrictGroups('/^libmongoc bundled version => (?<version>.+)$/im', $info, $libmongocMatches)) {
                        $this->addLibrary($libraries, $name.'-libmongoc', $libmongocMatches['version'], 'libmongoc version of mongodb');
                    }

                    if (Preg::isMatchStrictGroups('/^libbson bundled version => (?<version>.+)$/im', $info, $libbsonMatches)) {
                        $this->addLibrary($libraries, $name.'-libbson', $libbsonMatches['version'], 'libbson version of mongodb');
                    }
                    break;

                case 'pgsql':
                    if ($this->runtime->hasConstant('PGSQL_LIBPQ_VERSION')) {
                        $this->addLibrary($libraries, 'pgsql-libpq', $this->runtime->getConstant('PGSQL_LIBPQ_VERSION'), 'libpq for pgsql');
                        break;
                    }
                // intentional fall-through to next case...

                case 'pdo_pgsql':
                    $info = $this->runtime->getExtensionInfo($name);

                    if (Preg::isMatch('/^PostgreSQL\(libpq\) Version => (?<version>.*)$/im', $info, $matches)) {
                        $this->addLibrary($libraries, $name.'-libpq', $matches['version'], 'libpq for '.$name);
                    }
                    break;

                case 'pq':
                    $info = $this->runtime->getExtensionInfo($name);

                    // Used Library => Compiled => Linked
                    // libpq => 14.3 (Ubuntu 14.3-1.pgdg22.04+1) => 15.0.2
                    if (Preg::isMatch('/^libpq => (?<compiled>.+) => (?<linked>.+)$/im', $info, $matches)) {
                        $this->addLibrary($libraries, $name.'-libpq', $matches['linked'], 'libpq for '.$name);
                    }
                    break;

                case 'rdkafka':
                    if ($this->runtime->hasConstant('RD_KAFKA_VERSION')) {
                        /**
                         * Interpreted as hex \c MM.mm.rr.xx:
                         *  - MM = Major
                         *  - mm = minor
                         *  - rr = revision
                         *  - xx = pre-release id (0xff is the final release)
                         *
                         * pre-release ID in practice is always 0xff even for RCs etc, so we ignore it
                         */
                        $libRdKafkaVersionInt = $this->runtime->getConstant('RD_KAFKA_VERSION');
                        $this->addLibrary($libraries, $name.'-librdkafka', sprintf('%d.%d.%d', ($libRdKafkaVersionInt & 0x7F000000) >> 24, ($libRdKafkaVersionInt & 0x00FF0000) >> 16, ($libRdKafkaVersionInt & 0x0000FF00) >> 8), 'librdkafka for '.$name);
                    }
                    break;

                case 'libsodium':
                case 'sodium':
                    if ($this->runtime->hasConstant('SODIUM_LIBRARY_VERSION')) {
                        $this->addLibrary($libraries, 'libsodium', $this->runtime->getConstant('SODIUM_LIBRARY_VERSION'));
                        $this->addLibrary($libraries, 'libsodium', $this->runtime->getConstant('SODIUM_LIBRARY_VERSION'));
                    }
                    break;

                case 'sqlite3':
                case 'pdo_sqlite':
                    $info = $this->runtime->getExtensionInfo($name);

                    if (Preg::isMatch('/^SQLite Library => (?<version>.+)$/im', $info, $matches)) {
                        $this->addLibrary($libraries, $name.'-sqlite', $matches['version']);
                    }
                    break;

                case 'ssh2':
                    $info = $this->runtime->getExtensionInfo($name);

                    if (Preg::isMatch('/^libssh2 version => (?<version>.+)$/im', $info, $matches)) {
                        $this->addLibrary($libraries, $name.'-libssh2', $matches['version']);
                    }
                    break;

                case 'xsl':
                    $this->addLibrary($libraries, 'libxslt', $this->runtime->getConstant('LIBXSLT_DOTTED_VERSION'), null, ['xsl']);

                    $info = $this->runtime->getExtensionInfo('xsl');
                    if (Preg::isMatch('/^libxslt compiled against libxml Version => (?<version>.+)$/im', $info, $matches)) {
                        $this->addLibrary($libraries, 'libxslt-libxml', $matches['version'], 'libxml version libxslt is compiled against');
                    }
                    break;

                case 'yaml':
                    $info = $this->runtime->getExtensionInfo('yaml');

                    if (Preg::isMatch('/^LibYAML Version => (?<version>.+)$/im', $info, $matches)) {
                        $this->addLibrary($libraries, $name.'-libyaml', $matches['version'], 'libyaml version of yaml');
                    }
                    break;

                case 'zip':
                    if ($this->runtime->hasConstant('LIBZIP_VERSION', 'ZipArchive')) {
                        $this->addLibrary($libraries, $name.'-libzip', $this->runtime->getConstant('LIBZIP_VERSION', 'ZipArchive'), null, ['zip']);
                    }
                    break;

                case 'zlib':
                    if ($this->runtime->hasConstant('ZLIB_VERSION')) {
                        $this->addLibrary($libraries, $name, $this->runtime->getConstant('ZLIB_VERSION'));

                    // Linked Version => 1.2.8
                    } elseif (Preg::isMatch('/^Linked Version => (?<version>.+)$/im', $this->runtime->getExtensionInfo($name), $matches)) {
                        $this->addLibrary($libraries, $name, $matches['version']);
                    }
                    break;

                default:
                    break;
            }
        }

        $hhvmVersion = $this->hhvmDetector->getVersion();
        if ($hhvmVersion) {
            try {
                $prettyVersion = $hhvmVersion;
                $version = $this->versionParser->normalize($prettyVersion);
            } catch (\UnexpectedValueException $e) {
                $prettyVersion = Preg::replace('#^([^~+-]+).*$#', '$1', $hhvmVersion);
                $version = $this->versionParser->normalize($prettyVersion);
            }

            $hhvm = new CompletePackage('hhvm', $version, $prettyVersion);
            $hhvm->setDescription('The HHVM Runtime (64bit)');
            $this->addPackage($hhvm);
        }
    }

    /**
     * @inheritDoc
     */
    public function addPackage(PackageInterface $package): void
    {
        if (!$package instanceof CompletePackage) {
            throw new \UnexpectedValueException('Expected CompletePackage but got '.get_class($package));
        }

        // Skip if overridden
        if (isset($this->overrides[$package->getName()])) {
            if ($this->overrides[$package->getName()]['version'] === false) {
                $this->addDisabledPackage($package);

                return;
            }

            $overrider = $this->findPackage($package->getName(), '*');
            if ($package->getVersion() === $overrider->getVersion()) {
                $actualText = 'same as actual';
            } else {
                $actualText = 'actual: '.$package->getPrettyVersion();
            }
            if ($overrider instanceof CompletePackageInterface) {
                $overrider->setDescription($overrider->getDescription().', '.$actualText);
            }

            return;
        }

        // Skip if PHP is overridden and we are adding a php-* package
        if (isset($this->overrides['php']) && 0 === strpos($package->getName(), 'php-')) {
            $overrider = $this->addOverriddenPackage($this->overrides['php'], $package->getPrettyName());
            if ($package->getVersion() === $overrider->getVersion()) {
                $actualText = 'same as actual';
            } else {
                $actualText = 'actual: '.$package->getPrettyVersion();
            }
            $overrider->setDescription($overrider->getDescription().', '.$actualText);

            return;
        }

        parent::addPackage($package);
    }

    /**
     * @param array{version: string, name: string} $override
     */
    private function addOverriddenPackage(array $override, ?string $name = null): CompletePackage
    {
        $version = $this->versionParser->normalize($override['version']);
        $package = new CompletePackage($name ?: $override['name'], $version, $override['version']);
        $package->setDescription('Package overridden via config.platform');
        $package->setExtra(['config.platform' => true]);
        parent::addPackage($package);

        if ($package->getName() === 'php') {
            self::$lastSeenPlatformPhp = implode('.', array_slice(explode('.', $package->getVersion()), 0, 3));
        }

        return $package;
    }

    private function addDisabledPackage(CompletePackage $package): void
    {
        $package->setDescription($package->getDescription().'. <warning>Package disabled via config.platform</warning>');
        $package->setExtra(['config.platform' => true]);

        $this->disabledPackages[$package->getName()] = $package;
    }

    /**
     * Parses the version and adds a new package to the repository
     */
    private function addExtension(string $name, string $prettyVersion): void
    {
        $extraDescription = null;

        try {
            $version = $this->versionParser->normalize($prettyVersion);
        } catch (\UnexpectedValueException $e) {
            $extraDescription = ' (actual version: '.$prettyVersion.')';
            if (Preg::isMatchStrictGroups('{^(\d+\.\d+\.\d+(?:\.\d+)?)}', $prettyVersion, $match)) {
                $prettyVersion = $match[1];
            } else {
                $prettyVersion = '0';
            }
            $version = $this->versionParser->normalize($prettyVersion);
        }

        $packageName = $this->buildPackageName($name);
        $ext = new CompletePackage($packageName, $version, $prettyVersion);
        $ext->setDescription('The '.$name.' PHP extension'.$extraDescription);
        $ext->setType('php-ext');

        if ($name === 'uuid') {
            $ext->setReplaces([
                'lib-uuid' => new Link('ext-uuid', 'lib-uuid', new Constraint('=', $version), Link::TYPE_REPLACE, $ext->getPrettyVersion()),
            ]);
        }

        $this->addPackage($ext);
    }

    private function buildPackageName(string $name): string
    {
        return 'ext-' . str_replace(' ', '-', strtolower($name));
    }

    /**
     * @param array<string, bool> $libraries
     * @param array<string> $replaces
     * @param array<string> $provides
     */
    private function addLibrary(array &$libraries, string $name, ?string $prettyVersion, ?string $description = null, array $replaces = [], array $provides = []): void
    {
        if (null === $prettyVersion) {
            return;
        }
        try {
            $version = $this->versionParser->normalize($prettyVersion);
        } catch (\UnexpectedValueException $e) {
            return;
        }

        // avoid adding the same lib twice even if two conflicting extensions provide the same lib
        // see https://github.com/composer/composer/issues/12082
        if (isset($libraries['lib-'.$name])) {
            return;
        }
        $libraries['lib-'.$name] = true;

        if ($description === null) {
            $description = 'The '.$name.' library';
        }

        $lib = new CompletePackage('lib-'.$name, $version, $prettyVersion);
        $lib->setDescription($description);

        $replaceLinks = [];
        foreach ($replaces as $replace) {
            $replace = strtolower($replace);
            $replaceLinks[$replace] = new Link('lib-'.$name, 'lib-'.$replace, new Constraint('=', $version), Link::TYPE_REPLACE, $lib->getPrettyVersion());
        }
        $provideLinks = [];
        foreach ($provides as $provide) {
            $provide = strtolower($provide);
            $provideLinks[$provide] = new Link('lib-'.$name, 'lib-'.$provide, new Constraint('=', $version), Link::TYPE_PROVIDE, $lib->getPrettyVersion());
        }
        $lib->setReplaces($replaceLinks);
        $lib->setProvides($provideLinks);

        $this->addPackage($lib);
    }

    /**
     * Check if a package name is a platform package.
     */
    public static function isPlatformPackage(string $name): bool
    {
        static $cache = [];

        if (isset($cache[$name])) {
            return $cache[$name];
        }

        return $cache[$name] = Preg::isMatch(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name);
    }

    /**
     * Returns the last seen config.platform.php version if defined
     *
     * This is a best effort attempt for internal purposes, retrieve the real
     * packages from a PlatformRepository instance if you need a version guaranteed to
     * be correct.
     *
     * @internal
     */
    public static function getPlatformPhpVersion(): ?string
    {
        return self::$lastSeenPlatformPhp;
    }

    public function search(string $query, int $mode = 0, ?string $type = null): array
    {
        // suppress vendor search as there are no vendors to match in platform packages
        if ($mode === self::SEARCH_VENDOR) {
            return [];
        }

        return parent::search($query, $mode, $type);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Package\AliasPackage;
use Composer\Package\BasePackage;
use Composer\Package\CompleteAliasPackage;
use Composer\Package\CompletePackage;
use Composer\Package\PackageInterface;
use Composer\Package\CompletePackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\Package\Version\StabilityFilter;
use Composer\Pcre\Preg;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\Constraint;

/**
 * A repository implementation that simply stores packages in an array
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class ArrayRepository implements RepositoryInterface
{
    /** @var ?array<BasePackage> */
    protected $packages = null;

    /**
     * @var ?array<BasePackage> indexed by package unique name and used to cache hasPackage calls
     */
    protected $packageMap = null;

    /**
     * @param array<PackageInterface> $packages
     */
    public function __construct(array $packages = [])
    {
        foreach ($packages as $package) {
            $this->addPackage($package);
        }
    }

    public function getRepoName()
    {
        return 'array repo (defining '.$this->count().' package'.($this->count() > 1 ? 's' : '').')';
    }

    /**
     * @inheritDoc
     */
    public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = [])
    {
        $packages = $this->getPackages();

        $result = [];
        $namesFound = [];
        foreach ($packages as $package) {
            if (array_key_exists($package->getName(), $packageNameMap)) {
                if (
                    (!$packageNameMap[$package->getName()] || $packageNameMap[$package->getName()]->matches(new Constraint('==', $package->getVersion())))
                    && StabilityFilter::isPackageAcceptable($acceptableStabilities, $stabilityFlags, $package->getNames(), $package->getStability())
                    && !isset($alreadyLoaded[$package->getName()][$package->getVersion()])
                ) {
                    // add selected packages which match stability requirements
                    $result[spl_object_hash($package)] = $package;
                    // add the aliased package for packages where the alias matches
                    if ($package instanceof AliasPackage && !isset($result[spl_object_hash($package->getAliasOf())])) {
                        $result[spl_object_hash($package->getAliasOf())] = $package->getAliasOf();
                    }
                }

                $namesFound[$package->getName()] = true;
            }
        }

        // add aliases of packages that were selected, even if the aliases did not match
        foreach ($packages as $package) {
            if ($package instanceof AliasPackage) {
                if (isset($result[spl_object_hash($package->getAliasOf())])) {
                    $result[spl_object_hash($package)] = $package;
                }
            }
        }

        return ['namesFound' => array_keys($namesFound), 'packages' => $result];
    }

    /**
     * @inheritDoc
     */
    public function findPackage(string $name, $constraint)
    {
        $name = strtolower($name);

        if (!$constraint instanceof ConstraintInterface) {
            $versionParser = new VersionParser();
            $constraint = $versionParser->parseConstraints($constraint);
        }

        foreach ($this->getPackages() as $package) {
            if ($name === $package->getName()) {
                $pkgConstraint = new Constraint('==', $package->getVersion());
                if ($constraint->matches($pkgConstraint)) {
                    return $package;
                }
            }
        }

        return null;
    }

    /**
     * @inheritDoc
     */
    public function findPackages(string $name, $constraint = null)
    {
        // normalize name
        $name = strtolower($name);
        $packages = [];

        if (null !== $constraint && !$constraint instanceof ConstraintInterface) {
            $versionParser = new VersionParser();
            $constraint = $versionParser->parseConstraints($constraint);
        }

        foreach ($this->getPackages() as $package) {
            if ($name === $package->getName()) {
                if (null === $constraint || $constraint->matches(new Constraint('==', $package->getVersion()))) {
                    $packages[] = $package;
                }
            }
        }

        return $packages;
    }

    /**
     * @inheritDoc
     */
    public function search(string $query, int $mode = 0, ?string $type = null)
    {
        if ($mode === self::SEARCH_FULLTEXT) {
            $regex = '{(?:'.implode('|', Preg::split('{\s+}', preg_quote($query))).')}i';
        } else {
            // vendor/name searches expect the caller to have preg_quoted the query
            $regex = '{(?:'.implode('|', Preg::split('{\s+}', $query)).')}i';
        }

        $matches = [];
        foreach ($this->getPackages() as $package) {
            $name = $package->getName();
            if ($mode === self::SEARCH_VENDOR) {
                [$name] = explode('/', $name);
            }
            if (isset($matches[$name])) {
                continue;
            }
            if (null !== $type && $package->getType() !== $type) {
                continue;
            }

            if (Preg::isMatch($regex, $name)
                || ($mode === self::SEARCH_FULLTEXT && $package instanceof CompletePackageInterface && Preg::isMatch($regex, implode(' ', (array) $package->getKeywords()) . ' ' . $package->getDescription()))
            ) {
                if ($mode === self::SEARCH_VENDOR) {
                    $matches[$name] = [
                        'name' => $name,
                        'description' => null,
                    ];
                } else {
                    $matches[$name] = [
                        'name' => $package->getPrettyName(),
                        'description' => $package instanceof CompletePackageInterface ? $package->getDescription() : null,
                    ];

                    if ($package instanceof CompletePackageInterface && $package->isAbandoned()) {
                        $matches[$name]['abandoned'] = $package->getReplacementPackage() ?: true;
                    }
                }
            }
        }

        return array_values($matches);
    }

    /**
     * @inheritDoc
     */
    public function hasPackage(PackageInterface $package)
    {
        if ($this->packageMap === null) {
            $this->packageMap = [];
            foreach ($this->getPackages() as $repoPackage) {
                $this->packageMap[$repoPackage->getUniqueName()] = $repoPackage;
            }
        }

        return isset($this->packageMap[$package->getUniqueName()]);
    }

    /**
     * Adds a new package to the repository
     *
     * @return void
     */
    public function addPackage(PackageInterface $package)
    {
        if (!$package instanceof BasePackage) {
            throw new \InvalidArgumentException('Only subclasses of BasePackage are supported');
        }
        if (null === $this->packages) {
            $this->initialize();
        }
        $package->setRepository($this);
        $this->packages[] = $package;

        if ($package instanceof AliasPackage) {
            $aliasedPackage = $package->getAliasOf();
            if (null === $aliasedPackage->getRepository()) {
                $this->addPackage($aliasedPackage);
            }
        }

        // invalidate package map cache
        $this->packageMap = null;
    }

    /**
     * @inheritDoc
     */
    public function getProviders(string $packageName)
    {
        $result = [];

        foreach ($this->getPackages() as $candidate) {
            if (isset($result[$candidate->getName()])) {
                continue;
            }
            foreach ($candidate->getProvides() as $link) {
                if ($packageName === $link->getTarget()) {
                    $result[$candidate->getName()] = [
                        'name' => $candidate->getName(),
                        'description' => $candidate instanceof CompletePackageInterface ? $candidate->getDescription() : null,
                        'type' => $candidate->getType(),
                    ];
                    continue 2;
                }
            }
        }

        return $result;
    }

    /**
     * @return AliasPackage|CompleteAliasPackage
     */
    protected function createAliasPackage(BasePackage $package, string $alias, string $prettyAlias)
    {
        while ($package instanceof AliasPackage) {
            $package = $package->getAliasOf();
        }

        if ($package instanceof CompletePackage) {
            return new CompleteAliasPackage($package, $alias, $prettyAlias);
        }

        return new AliasPackage($package, $alias, $prettyAlias);
    }

    /**
     * Removes package from repository.
     *
     * @param PackageInterface $package package instance
     *
     * @return void
     */
    public function removePackage(PackageInterface $package)
    {
        $packageId = $package->getUniqueName();

        foreach ($this->getPackages() as $key => $repoPackage) {
            if ($packageId === $repoPackage->getUniqueName()) {
                array_splice($this->packages, $key, 1);

                // invalidate package map cache
                $this->packageMap = null;

                return;
            }
        }
    }

    /**
     * @inheritDoc
     */
    public function getPackages()
    {
        if (null === $this->packages) {
            $this->initialize();
        }

        if (null === $this->packages) {
            throw new \LogicException('initialize failed to initialize the packages array');
        }

        return $this->packages;
    }

    /**
     * Returns the number of packages in this repository
     *
     * @return 0|positive-int Number of packages
     */
    public function count(): int
    {
        if (null === $this->packages) {
            $this->initialize();
        }

        return count($this->packages);
    }

    /**
     * Initializes the packages array. Mostly meant as an extension point.
     *
     * @return void
     */
    protected function initialize()
    {
        $this->packages = [];
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

/**
 * Configurable repository interface.
 *
 * @author Lukas Homza <lukashomz@gmail.com>
 */
interface ConfigurableRepositoryInterface
{
    /**
     * @return mixed[]
     */
    public function getRepoConfig();
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\DependencyResolver\PoolOptimizer;
use Composer\DependencyResolver\Pool;
use Composer\DependencyResolver\PoolBuilder;
use Composer\DependencyResolver\Request;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Advisory\SecurityAdvisory;
use Composer\Advisory\PartialSecurityAdvisory;
use Composer\IO\IOInterface;
use Composer\IO\NullIO;
use Composer\Package\BasePackage;
use Composer\Package\AliasPackage;
use Composer\Package\CompleteAliasPackage;
use Composer\Package\CompletePackage;
use Composer\Package\PackageInterface;
use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Package\Version\StabilityFilter;
use Composer\Semver\Constraint\MatchAllConstraint;
use Composer\Semver\Constraint\MultiConstraint;

/**
 * @author Nils Adermann <naderman@naderman.de>
 *
 * @see RepositoryUtils for ways to work with single repos
 */
class RepositorySet
{
    /**
     * Packages are returned even though their stability does not match the required stability
     */
    public const ALLOW_UNACCEPTABLE_STABILITIES = 1;
    /**
     * Packages will be looked up in all repositories, even after they have been found in a higher prio one
     */
    public const ALLOW_SHADOWED_REPOSITORIES = 2;

    /**
     * @var array[]
     * @phpstan-var array<string, array<string, array{alias: string, alias_normalized: string}>>
     */
    private $rootAliases;

    /**
     * @var string[]
     * @phpstan-var array<string, string>
     */
    private $rootReferences;

    /** @var RepositoryInterface[] */
    private $repositories = [];

    /**
     * @var int[] array of stability => BasePackage::STABILITY_* value
     * @phpstan-var array<key-of<BasePackage::STABILITIES>, BasePackage::STABILITY_*>
     */
    private $acceptableStabilities;

    /**
     * @var int[] array of package name => BasePackage::STABILITY_* value
     * @phpstan-var array<string, BasePackage::STABILITY_*>
     */
    private $stabilityFlags;

    /**
     * @var ConstraintInterface[]
     * @phpstan-var array<string, ConstraintInterface>
     */
    private $rootRequires;

    /**
     * @var array<string, ConstraintInterface>
     */
    private $temporaryConstraints;

    /** @var bool */
    private $locked = false;
    /** @var bool */
    private $allowInstalledRepositories = false;

    /**
     * In most cases if you are looking to use this class as a way to find packages from repositories
     * passing minimumStability is all you need to worry about. The rest is for advanced pool creation including
     * aliases, pinned references and other special cases.
     *
     * @param key-of<BasePackage::STABILITIES> $minimumStability
     * @param int[]  $stabilityFlags   an array of package name => BasePackage::STABILITY_* value
     * @phpstan-param array<string, BasePackage::STABILITY_*> $stabilityFlags
     * @param array[] $rootAliases
     * @phpstan-param list<array{package: string, version: string, alias: string, alias_normalized: string}> $rootAliases
     * @param string[] $rootReferences an array of package name => source reference
     * @phpstan-param array<string, string> $rootReferences
     * @param ConstraintInterface[] $rootRequires an array of package name => constraint from the root package
     * @phpstan-param array<string, ConstraintInterface> $rootRequires
     * @param array<string, ConstraintInterface> $temporaryConstraints Runtime temporary constraints that will be used to filter packages
     */
    public function __construct(string $minimumStability = 'stable', array $stabilityFlags = [], array $rootAliases = [], array $rootReferences = [], array $rootRequires = [], array $temporaryConstraints = [])
    {
        $this->rootAliases = self::getRootAliasesPerPackage($rootAliases);
        $this->rootReferences = $rootReferences;

        $this->acceptableStabilities = [];
        foreach (BasePackage::STABILITIES as $stability => $value) {
            if ($value <= BasePackage::STABILITIES[$minimumStability]) {
                $this->acceptableStabilities[$stability] = $value;
            }
        }
        $this->stabilityFlags = $stabilityFlags;
        $this->rootRequires = $rootRequires;
        foreach ($rootRequires as $name => $constraint) {
            if (PlatformRepository::isPlatformPackage($name)) {
                unset($this->rootRequires[$name]);
            }
        }

        $this->temporaryConstraints = $temporaryConstraints;
    }

    public function allowInstalledRepositories(bool $allow = true): void
    {
        $this->allowInstalledRepositories = $allow;
    }

    /**
     * @return ConstraintInterface[] an array of package name => constraint from the root package, platform requirements excluded
     * @phpstan-return array<string, ConstraintInterface>
     */
    public function getRootRequires(): array
    {
        return $this->rootRequires;
    }

    /**
     * @return array<string, ConstraintInterface> Runtime temporary constraints that will be used to filter packages
     */
    public function getTemporaryConstraints(): array
    {
        return $this->temporaryConstraints;
    }

    /**
     * Adds a repository to this repository set
     *
     * The first repos added have a higher priority. As soon as a package is found in any
     * repository the search for that package ends, and following repos will not be consulted.
     *
     * @param RepositoryInterface $repo A package repository
     */
    public function addRepository(RepositoryInterface $repo): void
    {
        if ($this->locked) {
            throw new \RuntimeException("Pool has already been created from this repository set, it cannot be modified anymore.");
        }

        if ($repo instanceof CompositeRepository) {
            $repos = $repo->getRepositories();
        } else {
            $repos = [$repo];
        }

        foreach ($repos as $repo) {
            $this->repositories[] = $repo;
        }
    }

    /**
     * Find packages providing or matching a name and optionally meeting a constraint in all repositories
     *
     * Returned in the order of repositories, matching priority
     *
     * @param  int                      $flags      any of the ALLOW_* constants from this class to tweak what is returned
     * @return BasePackage[]
     */
    public function findPackages(string $name, ?ConstraintInterface $constraint = null, int $flags = 0): array
    {
        $ignoreStability = ($flags & self::ALLOW_UNACCEPTABLE_STABILITIES) !== 0;
        $loadFromAllRepos = ($flags & self::ALLOW_SHADOWED_REPOSITORIES) !== 0;

        $packages = [];
        if ($loadFromAllRepos) {
            foreach ($this->repositories as $repository) {
                $packages[] = $repository->findPackages($name, $constraint) ?: [];
            }
        } else {
            foreach ($this->repositories as $repository) {
                $result = $repository->loadPackages([$name => $constraint], $ignoreStability ? BasePackage::STABILITIES : $this->acceptableStabilities, $ignoreStability ? [] : $this->stabilityFlags);

                $packages[] = $result['packages'];
                foreach ($result['namesFound'] as $nameFound) {
                    // avoid loading the same package again from other repositories once it has been found
                    if ($name === $nameFound) {
                        break 2;
                    }
                }
            }
        }

        $candidates = $packages ? array_merge(...$packages) : [];

        // when using loadPackages above (!$loadFromAllRepos) the repos already filter for stability so no need to do it again
        if ($ignoreStability || !$loadFromAllRepos) {
            return $candidates;
        }

        $result = [];
        foreach ($candidates as $candidate) {
            if ($this->isPackageAcceptable($candidate->getNames(), $candidate->getStability())) {
                $result[] = $candidate;
            }
        }

        return $result;
    }

    /**
     * @param string[] $packageNames
     * @return ($allowPartialAdvisories is true ? array<string, array<PartialSecurityAdvisory|SecurityAdvisory>> : array<string, array<SecurityAdvisory>>)
     */
    public function getSecurityAdvisories(array $packageNames, bool $allowPartialAdvisories = false): array
    {
        $map = [];
        foreach ($packageNames as $name) {
            $map[$name] = new MatchAllConstraint();
        }

        return $this->getSecurityAdvisoriesForConstraints($map, $allowPartialAdvisories);
    }

    /**
     * @param PackageInterface[] $packages
     * @return ($allowPartialAdvisories is true ? array<string, array<PartialSecurityAdvisory|SecurityAdvisory>> : array<string, array<SecurityAdvisory>>)
     */
    public function getMatchingSecurityAdvisories(array $packages, bool $allowPartialAdvisories = false): array
    {
        $map = [];
        foreach ($packages as $package) {
            // ignore root alias versions as they are not actual package versions and should not matter when it comes to vulnerabilities
            if ($package instanceof AliasPackage && $package->isRootPackageAlias()) {
                continue;
            }
            if (isset($map[$package->getName()])) {
                $map[$package->getName()] = new MultiConstraint([new Constraint('=', $package->getVersion()), $map[$package->getName()]], false);
            } else {
                $map[$package->getName()] = new Constraint('=', $package->getVersion());
            }
        }

        return $this->getSecurityAdvisoriesForConstraints($map, $allowPartialAdvisories);
    }

    /**
     * @param array<string, ConstraintInterface> $packageConstraintMap
     * @return ($allowPartialAdvisories is true ? array<string, array<PartialSecurityAdvisory|SecurityAdvisory>> : array<string, array<SecurityAdvisory>>)
     */
    private function getSecurityAdvisoriesForConstraints(array $packageConstraintMap, bool $allowPartialAdvisories): array
    {
        $repoAdvisories = [];
        foreach ($this->repositories as $repository) {
            if (!$repository instanceof AdvisoryProviderInterface || !$repository->hasSecurityAdvisories()) {
                continue;
            }

            $repoAdvisories[] = $repository->getSecurityAdvisories($packageConstraintMap, $allowPartialAdvisories)['advisories'];
        }

        $advisories = array_merge_recursive([], ...$repoAdvisories);
        ksort($advisories);

        return $advisories;
    }

    /**
     * @return array[] an array with the provider name as key and value of array('name' => '...', 'description' => '...', 'type' => '...')
     * @phpstan-return array<string, array{name: string, description: string|null, type: string}>
     */
    public function getProviders(string $packageName): array
    {
        $providers = [];
        foreach ($this->repositories as $repository) {
            if ($repoProviders = $repository->getProviders($packageName)) {
                $providers = array_merge($providers, $repoProviders);
            }
        }

        return $providers;
    }

    /**
     * Check for each given package name whether it would be accepted by this RepositorySet in the given $stability
     *
     * @param string[] $names
     * @param key-of<BasePackage::STABILITIES> $stability one of 'stable', 'RC', 'beta', 'alpha' or 'dev'
     */
    public function isPackageAcceptable(array $names, string $stability): bool
    {
        return StabilityFilter::isPackageAcceptable($this->acceptableStabilities, $this->stabilityFlags, $names, $stability);
    }

    /**
     * Create a pool for dependency resolution from the packages in this repository set.
     *
     * @param list<string>      $ignoredTypes Packages of those types are ignored
     * @param list<string>|null $allowedTypes Only packages of those types are allowed if set to non-null
     */
    public function createPool(Request $request, IOInterface $io, ?EventDispatcher $eventDispatcher = null, ?PoolOptimizer $poolOptimizer = null, array $ignoredTypes = [], ?array $allowedTypes = null): Pool
    {
        $poolBuilder = new PoolBuilder($this->acceptableStabilities, $this->stabilityFlags, $this->rootAliases, $this->rootReferences, $io, $eventDispatcher, $poolOptimizer, $this->temporaryConstraints);
        $poolBuilder->setIgnoredTypes($ignoredTypes);
        $poolBuilder->setAllowedTypes($allowedTypes);

        foreach ($this->repositories as $repo) {
            if (($repo instanceof InstalledRepositoryInterface || $repo instanceof InstalledRepository) && !$this->allowInstalledRepositories) {
                throw new \LogicException('The pool can not accept packages from an installed repository');
            }
        }

        $this->locked = true;

        return $poolBuilder->buildPool($this->repositories, $request);
    }

    /**
     * Create a pool for dependency resolution from the packages in this repository set.
     */
    public function createPoolWithAllPackages(): Pool
    {
        foreach ($this->repositories as $repo) {
            if (($repo instanceof InstalledRepositoryInterface || $repo instanceof InstalledRepository) && !$this->allowInstalledRepositories) {
                throw new \LogicException('The pool can not accept packages from an installed repository');
            }
        }

        $this->locked = true;

        $packages = [];
        foreach ($this->repositories as $repository) {
            foreach ($repository->getPackages() as $package) {
                $packages[] = $package;

                if (isset($this->rootAliases[$package->getName()][$package->getVersion()])) {
                    $alias = $this->rootAliases[$package->getName()][$package->getVersion()];
                    while ($package instanceof AliasPackage) {
                        $package = $package->getAliasOf();
                    }
                    if ($package instanceof CompletePackage) {
                        $aliasPackage = new CompleteAliasPackage($package, $alias['alias_normalized'], $alias['alias']);
                    } else {
                        $aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias']);
                    }
                    $aliasPackage->setRootPackageAlias(true);
                    $packages[] = $aliasPackage;
                }
            }
        }

        return new Pool($packages);
    }

    public function createPoolForPackage(string $packageName, ?LockArrayRepository $lockedRepo = null): Pool
    {
        // TODO unify this with above in some simpler version without "request"?
        return $this->createPoolForPackages([$packageName], $lockedRepo);
    }

    /**
     * @param string[] $packageNames
     */
    public function createPoolForPackages(array $packageNames, ?LockArrayRepository $lockedRepo = null): Pool
    {
        $request = new Request($lockedRepo);

        $allowedPackages = [];
        foreach ($packageNames as $packageName) {
            if (PlatformRepository::isPlatformPackage($packageName)) {
                throw new \LogicException('createPoolForPackage(s) can not be used for platform packages, as they are never loaded by the PoolBuilder which expects them to be fixed. Use createPoolWithAllPackages or pass in a proper request with the platform packages you need fixed in it.');
            }

            $request->requireName($packageName);
            $allowedPackages[] = strtolower($packageName);
        }

        if (count($allowedPackages) > 0) {
            $request->restrictPackages($allowedPackages);
        }

        return $this->createPool($request, new NullIO());
    }

    /**
     * @param array[] $aliases
     * @phpstan-param list<array{package: string, version: string, alias: string, alias_normalized: string}> $aliases
     *
     * @return array<string, array<string, array{alias: string, alias_normalized: string}>>
     */
    private static function getRootAliasesPerPackage(array $aliases): array
    {
        $normalizedAliases = [];

        foreach ($aliases as $alias) {
            $normalizedAliases[$alias['package']][$alias['version']] = [
                'alias' => $alias['alias'],
                'alias_normalized' => $alias['alias_normalized'],
            ];
        }

        return $normalizedAliases;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Json\JsonFile;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\PackageInterface;
use Composer\Package\RootAliasPackage;
use Composer\Package\RootPackageInterface;
use Composer\Package\AliasPackage;
use Composer\Package\Dumper\ArrayDumper;
use Composer\Installer\InstallationManager;
use Composer\Pcre\Preg;
use Composer\Util\Filesystem;
use Composer\Util\Platform;

/**
 * Filesystem repository.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class FilesystemRepository extends WritableArrayRepository
{
    /** @var JsonFile */
    protected $file;
    /** @var bool */
    private $dumpVersions;
    /** @var ?RootPackageInterface */
    private $rootPackage;
    /** @var Filesystem */
    private $filesystem;
    /** @var bool|null */
    private $devMode = null;

    /**
     * Initializes filesystem repository.
     *
     * @param JsonFile              $repositoryFile repository json file
     * @param ?RootPackageInterface $rootPackage    Must be provided if $dumpVersions is true
     */
    public function __construct(JsonFile $repositoryFile, bool $dumpVersions = false, ?RootPackageInterface $rootPackage = null, ?Filesystem $filesystem = null)
    {
        parent::__construct();
        $this->file = $repositoryFile;
        $this->dumpVersions = $dumpVersions;
        $this->rootPackage = $rootPackage;
        $this->filesystem = $filesystem ?: new Filesystem;
        if ($dumpVersions && !$rootPackage) {
            throw new \InvalidArgumentException('Expected a root package instance if $dumpVersions is true');
        }
    }

    /**
     * @return bool|null true if dev requirements were installed, false if --no-dev was used, null if yet unknown
     */
    public function getDevMode()
    {
        return $this->devMode;
    }

    /**
     * Initializes repository (reads file, or remote address).
     */
    protected function initialize()
    {
        parent::initialize();

        if (!$this->file->exists()) {
            return;
        }

        try {
            $data = $this->file->read();
            if (isset($data['packages'])) {
                $packages = $data['packages'];
            } else {
                $packages = $data;
            }

            if (isset($data['dev-package-names'])) {
                $this->setDevPackageNames($data['dev-package-names']);
            }
            if (isset($data['dev'])) {
                $this->devMode = $data['dev'];
            }

            if (!is_array($packages)) {
                throw new \UnexpectedValueException('Could not parse package list from the repository');
            }
        } catch (\Exception $e) {
            throw new InvalidRepositoryException('Invalid repository data in '.$this->file->getPath().', packages could not be loaded: ['.get_class($e).'] '.$e->getMessage());
        }

        $loader = new ArrayLoader(null, true);
        foreach ($packages as $packageData) {
            $package = $loader->load($packageData);
            $this->addPackage($package);
        }
    }

    public function reload()
    {
        $this->packages = null;
        $this->initialize();
    }

    /**
     * Writes writable repository.
     */
    public function write(bool $devMode, InstallationManager $installationManager)
    {
        $data = ['packages' => [], 'dev' => $devMode, 'dev-package-names' => []];
        $dumper = new ArrayDumper();

        // make sure the directory is created so we can realpath it
        // as realpath() does some additional normalizations with network paths that normalizePath does not
        // and we need to find shortest path correctly
        $repoDir = dirname($this->file->getPath());
        $this->filesystem->ensureDirectoryExists($repoDir);

        $repoDir = $this->filesystem->normalizePath(realpath($repoDir));
        $installPaths = [];

        foreach ($this->getCanonicalPackages() as $package) {
            $pkgArray = $dumper->dump($package);
            $path = $installationManager->getInstallPath($package);
            $installPath = null;
            if ('' !== $path && null !== $path) {
                $normalizedPath = $this->filesystem->normalizePath($this->filesystem->isAbsolutePath($path) ? $path : Platform::getCwd() . '/' . $path);
                $installPath = $this->filesystem->findShortestPath($repoDir, $normalizedPath, true);
            }
            $installPaths[$package->getName()] = $installPath;

            $pkgArray['install-path'] = $installPath;
            $data['packages'][] = $pkgArray;

            // only write to the files the names which are really installed, as we receive the full list
            // of dev package names before they get installed during composer install
            if (in_array($package->getName(), $this->devPackageNames, true)) {
                $data['dev-package-names'][] = $package->getName();
            }
        }

        sort($data['dev-package-names']);
        usort($data['packages'], static function ($a, $b): int {
            return strcmp($a['name'], $b['name']);
        });

        $this->file->write($data);

        if ($this->dumpVersions) {
            $versions = $this->generateInstalledVersions($installationManager, $installPaths, $devMode, $repoDir);

            $this->filesystem->filePutContentsIfModified($repoDir.'/installed.php', '<?php return ' . $this->dumpToPhpCode($versions) . ';'."\n");
            $installedVersionsClass = file_get_contents(__DIR__.'/../InstalledVersions.php');

            // this normally should not happen but during upgrades of Composer when it is installed in the project it is a possibility
            if ($installedVersionsClass !== false) {
                $this->filesystem->filePutContentsIfModified($repoDir.'/InstalledVersions.php', $installedVersionsClass);

                // make sure the in memory state is up to date with on disk
                \Composer\InstalledVersions::reload($versions);

                // make sure the selfDir matches the expected data at runtime if the class was loaded from the vendor dir, as it may have been
                // loaded from the Composer sources, causing packages to appear twice in that case if the installed.php is loaded in addition to the
                // in memory loaded data from above
                try {
                    $reflProp = new \ReflectionProperty(\Composer\InstalledVersions::class, 'selfDir');
                    (\PHP_VERSION_ID < 80100) and $reflProp->setAccessible(true);
                    $reflProp->setValue(null, strtr($repoDir, '\\', '/'));

                    $reflProp = new \ReflectionProperty(\Composer\InstalledVersions::class, 'installedIsLocalDir');
                    (\PHP_VERSION_ID < 80100) and $reflProp->setAccessible(true);
                    $reflProp->setValue(null, true);
                } catch (\ReflectionException $e) {
                    if (!Preg::isMatch('{Property .*? does not exist}i', $e->getMessage())) {
                        throw $e;
                    }
                    // noop, if outdated class is loaded we do not want to cause trouble
                }
            }
        }
    }

    /**
     * As we load the file from vendor dir during bootstrap, we need to make sure it contains only expected code before executing it
     *
     * @internal
     */
    public static function safelyLoadInstalledVersions(string $path): bool
    {
        $installedVersionsData = @file_get_contents($path);
        $pattern = <<<'REGEX'
{(?(DEFINE)
   (?<number>  -? \s*+ \d++ (?:\.\d++)? )
   (?<boolean> true | false | null )
   (?<strings> (?&string) (?: \s*+ \. \s*+ (?&string))*+ )
   (?<string>  (?: " (?:[^"\\$]*+ | \\ ["\\0] )* " | ' (?:[^'\\]*+ | \\ ['\\] )* ' ) )
   (?<array>   array\( \s*+ (?: (?:(?&number)|(?&strings)) \s*+ => \s*+ (?: (?:__DIR__ \s*+ \. \s*+)? (?&strings) | (?&value) ) \s*+, \s*+ )*+  \s*+ \) )
   (?<value>   (?: (?&number) | (?&boolean) | (?&strings) | (?&array) ) )
)
^<\?php\s++return\s++(?&array)\s*+;$}ix
REGEX;
        if (is_string($installedVersionsData) && Preg::isMatch($pattern, trim($installedVersionsData))) {
            \Composer\InstalledVersions::reload(eval('?>'.Preg::replace('{=>\s*+__DIR__\s*+\.\s*+([\'"])}', '=> '.var_export(dirname($path), true).' . $1', $installedVersionsData)));

            return true;
        }

        return false;
    }

    /**
     * @param array<mixed> $array
     */
    private function dumpToPhpCode(array $array = [], int $level = 0): string
    {
        $lines = "array(\n";
        $level++;

        foreach ($array as $key => $value) {
            $lines .= str_repeat('    ', $level);
            $lines .= is_int($key) ? $key . ' => ' : var_export($key, true) . ' => ';

            if (is_array($value)) {
                if (!empty($value)) {
                    $lines .= $this->dumpToPhpCode($value, $level);
                } else {
                    $lines .= "array(),\n";
                }
            } elseif ($key === 'install_path' && is_string($value)) {
                if ($this->filesystem->isAbsolutePath($value)) {
                    $lines .= var_export($value, true) . ",\n";
                } else {
                    $lines .= "__DIR__ . " . var_export('/' . $value, true) . ",\n";
                }
            } elseif (is_string($value)) {
                $lines .= var_export($value, true) . ",\n";
            } elseif (is_bool($value)) {
                $lines .= ($value ? 'true' : 'false') . ",\n";
            } elseif (is_null($value)) {
                $lines .= "null,\n";
            } else {
                throw new \UnexpectedValueException('Unexpected type '.gettype($value));
            }
        }

        $lines .= str_repeat('    ', $level - 1) . ')' . ($level - 1 === 0 ? '' : ",\n");

        return $lines;
    }

    /**
     * @param array<string, string> $installPaths
     *
     * @return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
     */
    private function generateInstalledVersions(InstallationManager $installationManager, array $installPaths, bool $devMode, string $repoDir): array
    {
        $devPackages = array_flip($this->devPackageNames);
        $packages = $this->getPackages();
        if (null === $this->rootPackage) {
            throw new \LogicException('It should not be possible to dump packages if no root package is given');
        }
        $packages[] = $rootPackage = $this->rootPackage;

        while ($rootPackage instanceof RootAliasPackage) {
            $rootPackage = $rootPackage->getAliasOf();
            $packages[] = $rootPackage;
        }
        $versions = [
            'root' => $this->dumpRootPackage($rootPackage, $installPaths, $devMode, $repoDir, $devPackages),
            'versions' => [],
        ];

        // add real installed packages
        foreach ($packages as $package) {
            if ($package instanceof AliasPackage) {
                continue;
            }

            $versions['versions'][$package->getName()] = $this->dumpInstalledPackage($package, $installPaths, $repoDir, $devPackages);
        }

        // add provided/replaced packages
        foreach ($packages as $package) {
            $isDevPackage = isset($devPackages[$package->getName()]);
            foreach ($package->getReplaces() as $replace) {
                // exclude platform replaces as when they are really there we can not check for their presence
                if (PlatformRepository::isPlatformPackage($replace->getTarget())) {
                    continue;
                }
                if (!isset($versions['versions'][$replace->getTarget()]['dev_requirement'])) {
                    $versions['versions'][$replace->getTarget()]['dev_requirement'] = $isDevPackage;
                } elseif (!$isDevPackage) {
                    $versions['versions'][$replace->getTarget()]['dev_requirement'] = false;
                }
                $replaced = $replace->getPrettyConstraint();
                if ($replaced === 'self.version') {
                    $replaced = $package->getPrettyVersion();
                }
                if (!isset($versions['versions'][$replace->getTarget()]['replaced']) || !in_array($replaced, $versions['versions'][$replace->getTarget()]['replaced'], true)) {
                    $versions['versions'][$replace->getTarget()]['replaced'][] = $replaced;
                }
            }
            foreach ($package->getProvides() as $provide) {
                // exclude platform provides as when they are really there we can not check for their presence
                if (PlatformRepository::isPlatformPackage($provide->getTarget())) {
                    continue;
                }
                if (!isset($versions['versions'][$provide->getTarget()]['dev_requirement'])) {
                    $versions['versions'][$provide->getTarget()]['dev_requirement'] = $isDevPackage;
                } elseif (!$isDevPackage) {
                    $versions['versions'][$provide->getTarget()]['dev_requirement'] = false;
                }
                $provided = $provide->getPrettyConstraint();
                if ($provided === 'self.version') {
                    $provided = $package->getPrettyVersion();
                }
                if (!isset($versions['versions'][$provide->getTarget()]['provided']) || !in_array($provided, $versions['versions'][$provide->getTarget()]['provided'], true)) {
                    $versions['versions'][$provide->getTarget()]['provided'][] = $provided;
                }
            }
        }

        // add aliases
        foreach ($packages as $package) {
            if (!$package instanceof AliasPackage) {
                continue;
            }
            $versions['versions'][$package->getName()]['aliases'][] = $package->getPrettyVersion();
            if ($package instanceof RootPackageInterface) {
                $versions['root']['aliases'][] = $package->getPrettyVersion();
            }
        }

        ksort($versions['versions']);
        ksort($versions);

        foreach ($versions['versions'] as $name => $version) {
            foreach (['aliases', 'replaced', 'provided'] as $key) {
                if (isset($versions['versions'][$name][$key])) {
                    sort($versions['versions'][$name][$key], SORT_NATURAL);
                }
            }
        }

        return $versions;
    }

    /**
     * @param array<string, string> $installPaths
     * @param array<string, int> $devPackages
     * @return array{pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev_requirement: bool}
     */
    private function dumpInstalledPackage(PackageInterface $package, array $installPaths, string $repoDir, array $devPackages): array
    {
        $reference = null;
        if ($package->getInstallationSource()) {
            $reference = $package->getInstallationSource() === 'source' ? $package->getSourceReference() : $package->getDistReference();
        }
        if (null === $reference) {
            $reference = ($package->getSourceReference() ?: $package->getDistReference()) ?: null;
        }

        if ($package instanceof RootPackageInterface) {
            $to = $this->filesystem->normalizePath(realpath(Platform::getCwd()));
            $installPath = $this->filesystem->findShortestPath($repoDir, $to, true);
        } else {
            $installPath = $installPaths[$package->getName()];
        }

        $data = [
            'pretty_version' => $package->getPrettyVersion(),
            'version' => $package->getVersion(),
            'reference' => $reference,
            'type' => $package->getType(),
            'install_path' => $installPath,
            'aliases' => [],
            'dev_requirement' => isset($devPackages[$package->getName()]),
        ];

        return $data;
    }

    /**
     * @param array<string, string> $installPaths
     * @param array<string, int> $devPackages
     * @return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
     */
    private function dumpRootPackage(RootPackageInterface $package, array $installPaths, bool $devMode, string $repoDir, array $devPackages)
    {
        $data = $this->dumpInstalledPackage($package, $installPaths, $repoDir, $devPackages);

        return [
            'name' => $package->getName(),
            'pretty_version' => $data['pretty_version'],
            'version' => $data['version'],
            'reference' => $data['reference'],
            'type' => $data['type'],
            'install_path' => $data['install_path'],
            'aliases' => $data['aliases'],
            'dev' => $devMode,
        ];
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Package\PackageInterface;
use Composer\Package\BasePackage;
use Composer\Pcre\Preg;

/**
 * Filters which packages are seen as canonical on this repo by loadPackages
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class FilterRepository implements RepositoryInterface, AdvisoryProviderInterface
{
    /** @var ?string */
    private $only = null;
    /** @var ?non-empty-string */
    private $exclude = null;
    /** @var bool */
    private $canonical = true;
    /** @var RepositoryInterface */
    private $repo;

    /**
     * @param array{only?: array<string>, exclude?: array<string>, canonical?: bool} $options
     */
    public function __construct(RepositoryInterface $repo, array $options)
    {
        if (isset($options['only'])) {
            if (!is_array($options['only'])) {
                throw new \InvalidArgumentException('"only" key for repository '.$repo->getRepoName().' should be an array');
            }
            $this->only = BasePackage::packageNamesToRegexp($options['only']);
        }
        if (isset($options['exclude'])) {
            if (!is_array($options['exclude'])) {
                throw new \InvalidArgumentException('"exclude" key for repository '.$repo->getRepoName().' should be an array');
            }
            $this->exclude = BasePackage::packageNamesToRegexp($options['exclude']);
        }
        if ($this->exclude && $this->only) {
            throw new \InvalidArgumentException('Only one of "only" and "exclude" can be specified for repository '.$repo->getRepoName());
        }
        if (isset($options['canonical'])) {
            if (!is_bool($options['canonical'])) {
                throw new \InvalidArgumentException('"canonical" key for repository '.$repo->getRepoName().' should be a boolean');
            }
            $this->canonical = $options['canonical'];
        }

        $this->repo = $repo;
    }

    public function getRepoName(): string
    {
        return $this->repo->getRepoName();
    }

    /**
     * Returns the wrapped repositories
     */
    public function getRepository(): RepositoryInterface
    {
        return $this->repo;
    }

    /**
     * @inheritDoc
     */
    public function hasPackage(PackageInterface $package): bool
    {
        return $this->repo->hasPackage($package);
    }

    /**
     * @inheritDoc
     */
    public function findPackage($name, $constraint): ?BasePackage
    {
        if (!$this->isAllowed($name)) {
            return null;
        }

        return $this->repo->findPackage($name, $constraint);
    }

    /**
     * @inheritDoc
     */
    public function findPackages($name, $constraint = null): array
    {
        if (!$this->isAllowed($name)) {
            return [];
        }

        return $this->repo->findPackages($name, $constraint);
    }

    /**
     * @inheritDoc
     */
    public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = []): array
    {
        foreach ($packageNameMap as $name => $constraint) {
            if (!$this->isAllowed($name)) {
                unset($packageNameMap[$name]);
            }
        }

        if (!$packageNameMap) {
            return ['namesFound' => [], 'packages' => []];
        }

        $result = $this->repo->loadPackages($packageNameMap, $acceptableStabilities, $stabilityFlags, $alreadyLoaded);
        if (!$this->canonical) {
            $result['namesFound'] = [];
        }

        return $result;
    }

    /**
     * @inheritDoc
     */
    public function search(string $query, int $mode = 0, ?string $type = null): array
    {
        $result = [];

        foreach ($this->repo->search($query, $mode, $type) as $package) {
            if ($this->isAllowed($package['name'])) {
                $result[] = $package;
            }
        }

        return $result;
    }

    /**
     * @inheritDoc
     */
    public function getPackages(): array
    {
        $result = [];
        foreach ($this->repo->getPackages() as $package) {
            if ($this->isAllowed($package->getName())) {
                $result[] = $package;
            }
        }

        return $result;
    }

    /**
     * @inheritDoc
     */
    public function getProviders($packageName): array
    {
        $result = [];
        foreach ($this->repo->getProviders($packageName) as $name => $provider) {
            if ($this->isAllowed($provider['name'])) {
                $result[$name] = $provider;
            }
        }

        return $result;
    }

    /**
     * @inheritDoc
     */
    public function count(): int
    {
        if ($this->repo->count() > 0) {
            return count($this->getPackages());
        }

        return 0;
    }

    public function hasSecurityAdvisories(): bool
    {
        if (!$this->repo instanceof AdvisoryProviderInterface) {
            return false;
        }

        return $this->repo->hasSecurityAdvisories();
    }

    /**
     * @inheritDoc
     */
    public function getSecurityAdvisories(array $packageConstraintMap, bool $allowPartialAdvisories = false): array
    {
        if (!$this->repo instanceof AdvisoryProviderInterface) {
            return ['namesFound' => [], 'advisories' => []];
        }

        foreach ($packageConstraintMap as $name => $constraint) {
            if (!$this->isAllowed($name)) {
                unset($packageConstraintMap[$name]);
            }
        }

        return $this->repo->getSecurityAdvisories($packageConstraintMap, $allowPartialAdvisories);
    }

    private function isAllowed(string $name): bool
    {
        if (!$this->only && !$this->exclude) {
            return true;
        }

        if ($this->only) {
            return Preg::isMatch($this->only, $name);
        }

        if ($this->exclude === null) {
            return true;
        }

        return !Preg::isMatch($this->exclude, $name);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
use Composer\Package\BasePackage;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Loader\LoaderInterface;
use Composer\Util\Platform;
use Composer\Util\Tar;
use Composer\Util\Zip;

/**
 * @author Serge Smertin <serg.smertin@gmail.com>
 */
class ArtifactRepository extends ArrayRepository implements ConfigurableRepositoryInterface
{
    /** @var LoaderInterface */
    protected $loader;

    /** @var string */
    protected $lookup;
    /** @var array{url: string} */
    protected $repoConfig;
    /** @var IOInterface */
    private $io;

    /**
     * @param array{url: string} $repoConfig
     */
    public function __construct(array $repoConfig, IOInterface $io)
    {
        parent::__construct();
        if (!extension_loaded('zip')) {
            throw new \RuntimeException('The artifact repository requires PHP\'s zip extension');
        }

        $this->loader = new ArrayLoader();
        $this->lookup = Platform::expandPath($repoConfig['url']);
        $this->io = $io;
        $this->repoConfig = $repoConfig;
    }

    public function getRepoName()
    {
        return 'artifact repo ('.$this->lookup.')';
    }

    public function getRepoConfig()
    {
        return $this->repoConfig;
    }

    protected function initialize()
    {
        parent::initialize();

        $this->scanDirectory($this->lookup);
    }

    private function scanDirectory(string $path): void
    {
        $io = $this->io;

        $directory = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS);
        $iterator = new \RecursiveIteratorIterator($directory);
        $regex = new \RegexIterator($iterator, '/^.+\.(zip|tar|gz|tgz)$/i');
        foreach ($regex as $file) {
            /* @var $file \SplFileInfo */
            if (!$file->isFile()) {
                continue;
            }

            $package = $this->getComposerInformation($file);
            if (!$package) {
                $io->writeError("File <comment>{$file->getBasename()}</comment> doesn't seem to hold a package", true, IOInterface::VERBOSE);
                continue;
            }

            $template = 'Found package <info>%s</info> (<comment>%s</comment>) in file <info>%s</info>';
            $io->writeError(sprintf($template, $package->getName(), $package->getPrettyVersion(), $file->getBasename()), true, IOInterface::VERBOSE);

            $this->addPackage($package);
        }
    }

    /**
     * @return ?BasePackage
     */
    private function getComposerInformation(\SplFileInfo $file): ?BasePackage
    {
        $json = null;
        $fileType = null;
        $fileExtension = pathinfo($file->getPathname(), PATHINFO_EXTENSION);
        if (in_array($fileExtension, ['gz', 'tar', 'tgz'], true)) {
            $fileType = 'tar';
        } elseif ($fileExtension === 'zip') {
            $fileType = 'zip';
        } else {
            throw new \RuntimeException('Files with "'.$fileExtension.'" extensions aren\'t supported. Only ZIP and TAR/TAR.GZ/TGZ archives are supported.');
        }

        try {
            if ($fileType === 'tar') {
                $json = Tar::getComposerJson($file->getPathname());
            } else {
                $json = Zip::getComposerJson($file->getPathname());
            }
        } catch (\Exception $exception) {
            $this->io->write('Failed loading package '.$file->getPathname().': '.$exception->getMessage(), false, IOInterface::VERBOSE);
        }

        if (null === $json) {
            return null;
        }

        $package = JsonFile::parseJson($json, $file->getPathname().'#composer.json');
        $package['dist'] = [
            'type' => $fileType,
            'url' => strtr($file->getPathname(), '\\', '/'),
            'shasum' => hash_file('sha1', $file->getRealPath()),
        ];

        try {
            $package = $this->loader->load($package);
        } catch (\UnexpectedValueException $e) {
            throw new \UnexpectedValueException('Failed loading package in '.$file.': '.$e->getMessage(), 0, $e);
        }

        return $package;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Installer\InstallationManager;

/**
 * Writable array repository.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class WritableArrayRepository extends ArrayRepository implements WritableRepositoryInterface
{
    use CanonicalPackagesTrait;

    /**
     * @var string[]
     */
    protected $devPackageNames = [];

    /** @var bool|null */
    private $devMode = null;

    /**
     * @return bool|null true if dev requirements were installed, false if --no-dev was used, null if yet unknown
     */
    public function getDevMode()
    {
        return $this->devMode;
    }

    /**
     * @inheritDoc
     */
    public function setDevPackageNames(array $devPackageNames)
    {
        $this->devPackageNames = $devPackageNames;
    }

    /**
     * @inheritDoc
     */
    public function getDevPackageNames()
    {
        return $this->devPackageNames;
    }

    /**
     * @inheritDoc
     */
    public function write(bool $devMode, InstallationManager $installationManager)
    {
        $this->devMode = $devMode;
    }

    /**
     * @inheritDoc
     */
    public function reload()
    {
        $this->devMode = null;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\IO\IOInterface;
use Composer\Config;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Package\PackageInterface;
use Composer\Util\HttpDownloader;
use Composer\Util\ProcessExecutor;

/**
 * Repositories manager.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 */
class RepositoryManager
{
    /** @var InstalledRepositoryInterface */
    private $localRepository;
    /** @var list<RepositoryInterface> */
    private $repositories = [];
    /** @var array<string, class-string<RepositoryInterface>> */
    private $repositoryClasses = [];
    /** @var IOInterface */
    private $io;
    /** @var Config */
    private $config;
    /** @var HttpDownloader */
    private $httpDownloader;
    /** @var ?EventDispatcher */
    private $eventDispatcher;
    /** @var ProcessExecutor */
    private $process;

    public function __construct(IOInterface $io, Config $config, HttpDownloader $httpDownloader, ?EventDispatcher $eventDispatcher = null, ?ProcessExecutor $process = null)
    {
        $this->io = $io;
        $this->config = $config;
        $this->httpDownloader = $httpDownloader;
        $this->eventDispatcher = $eventDispatcher;
        $this->process = $process ?? new ProcessExecutor($io);
    }

    /**
     * Searches for a package by its name and version in managed repositories.
     *
     * @param string                                                 $name       package name
     * @param string|\Composer\Semver\Constraint\ConstraintInterface $constraint package version or version constraint to match against
     */
    public function findPackage(string $name, $constraint): ?PackageInterface
    {
        foreach ($this->repositories as $repository) {
            /** @var RepositoryInterface $repository */
            if ($package = $repository->findPackage($name, $constraint)) {
                return $package;
            }
        }

        return null;
    }

    /**
     * Searches for all packages matching a name and optionally a version in managed repositories.
     *
     * @param string                                                 $name       package name
     * @param string|\Composer\Semver\Constraint\ConstraintInterface $constraint package version or version constraint to match against
     *
     * @return PackageInterface[]
     */
    public function findPackages(string $name, $constraint): array
    {
        $packages = [];

        foreach ($this->getRepositories() as $repository) {
            $packages = array_merge($packages, $repository->findPackages($name, $constraint));
        }

        return $packages;
    }

    /**
     * Adds repository
     *
     * @param RepositoryInterface $repository repository instance
     */
    public function addRepository(RepositoryInterface $repository): void
    {
        $this->repositories[] = $repository;
    }

    /**
     * Adds a repository to the beginning of the chain
     *
     * This is useful when injecting additional repositories that should trump Packagist, e.g. from a plugin.
     *
     * @param RepositoryInterface $repository repository instance
     */
    public function prependRepository(RepositoryInterface $repository): void
    {
        array_unshift($this->repositories, $repository);
    }

    /**
     * Returns a new repository for a specific installation type.
     *
     * @param  string                    $type   repository type
     * @param  array<string, mixed>      $config repository configuration
     * @param  string                    $name   repository name
     * @throws \InvalidArgumentException if repository for provided type is not registered
     */
    public function createRepository(string $type, array $config, ?string $name = null): RepositoryInterface
    {
        if (!isset($this->repositoryClasses[$type])) {
            throw new \InvalidArgumentException('Repository type is not registered: '.$type);
        }

        if (isset($config['packagist']) && false === $config['packagist']) {
            $this->io->writeError('<warning>Repository "'.$name.'" ('.json_encode($config).') has a packagist key which should be in its own repository definition</warning>');
        }

        $class = $this->repositoryClasses[$type];

        if (isset($config['only']) || isset($config['exclude']) || isset($config['canonical'])) {
            $filterConfig = $config;
            unset($config['only'], $config['exclude'], $config['canonical']);
        }

        $repository = new $class($config, $this->io, $this->config, $this->httpDownloader, $this->eventDispatcher, $this->process);

        if (isset($filterConfig)) {
            $repository = new FilterRepository($repository, $filterConfig);
        }

        return $repository;
    }

    /**
     * Stores repository class for a specific installation type.
     *
     * @param string $type  installation type
     * @param class-string<RepositoryInterface> $class class name of the repo implementation
     */
    public function setRepositoryClass(string $type, $class): void
    {
        $this->repositoryClasses[$type] = $class;
    }

    /**
     * Returns all repositories, except local one.
     *
     * @return RepositoryInterface[]
     */
    public function getRepositories(): array
    {
        return $this->repositories;
    }

    /**
     * Sets local repository for the project.
     *
     * @param InstalledRepositoryInterface $repository repository instance
     */
    public function setLocalRepository(InstalledRepositoryInterface $repository): void
    {
        $this->localRepository = $repository;
    }

    /**
     * Returns local repository for the project.
     */
    public function getLocalRepository(): InstalledRepositoryInterface
    {
        return $this->localRepository;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Package\AliasPackage;
use Composer\Package\PackageInterface;

/**
 * Provides getCanonicalPackages() to various repository implementations
 *
 * @internal
 */
trait CanonicalPackagesTrait
{
    /**
     * Get unique packages (at most one package of each name), with aliases resolved and removed.
     *
     * @return PackageInterface[]
     */
    public function getCanonicalPackages()
    {
        $packages = $this->getPackages();

        // get at most one package of each name, preferring non-aliased ones
        $packagesByName = [];
        foreach ($packages as $package) {
            if (!isset($packagesByName[$package->getName()]) || $packagesByName[$package->getName()] instanceof AliasPackage) {
                $packagesByName[$package->getName()] = $package;
            }
        }

        $canonicalPackages = [];

        // unfold aliased packages
        foreach ($packagesByName as $package) {
            while ($package instanceof AliasPackage) {
                $package = $package->getAliasOf();
            }

            $canonicalPackages[] = $package;
        }

        return $canonicalPackages;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Package\BasePackage;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\MatchAllConstraint;
use Composer\Package\RootPackageInterface;
use Composer\Package\Link;

/**
 * Installed repository is a composite of all installed repo types.
 *
 * The main use case is tagging a repo as an "installed" repository, and offering a way to get providers/replacers easily.
 *
 * Installed repos are LockArrayRepository, InstalledRepositoryInterface, RootPackageRepository and PlatformRepository
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class InstalledRepository extends CompositeRepository
{
    /**
     * @param ConstraintInterface|string|null $constraint
     *
     * @return BasePackage[]
     */
    public function findPackagesWithReplacersAndProviders(string $name, $constraint = null): array
    {
        $name = strtolower($name);

        if (null !== $constraint && !$constraint instanceof ConstraintInterface) {
            $versionParser = new VersionParser();
            $constraint = $versionParser->parseConstraints($constraint);
        }

        $matches = [];
        foreach ($this->getRepositories() as $repo) {
            foreach ($repo->getPackages() as $candidate) {
                if ($name === $candidate->getName()) {
                    if (null === $constraint || $constraint->matches(new Constraint('==', $candidate->getVersion()))) {
                        $matches[] = $candidate;
                    }
                    continue;
                }

                foreach (array_merge($candidate->getProvides(), $candidate->getReplaces()) as $link) {
                    if (
                        $name === $link->getTarget()
                        && ($constraint === null || $constraint->matches($link->getConstraint()))
                    ) {
                        $matches[] = $candidate;
                        continue 2;
                    }
                }
            }
        }

        return $matches;
    }

    /**
     * Returns a list of links causing the requested needle packages to be installed, as an associative array with the
     * dependent's name as key, and an array containing in order the PackageInterface and Link describing the relationship
     * as values. If recursive lookup was requested a third value is returned containing an identically formed array up
     * to the root package. That third value will be false in case a circular recursion was detected.
     *
     * @param  string|string[]          $needle        The package name(s) to inspect.
     * @param  ConstraintInterface|null $constraint    Optional constraint to filter by.
     * @param  bool                     $invert        Whether to invert matches to discover reasons for the package *NOT* to be installed.
     * @param  bool                     $recurse       Whether to recursively expand the requirement tree up to the root package.
     * @param  string[]                 $packagesFound Used internally when recurring
     *
     * @return array[] An associative array of arrays as described above.
     * @phpstan-return array<array{0: PackageInterface, 1: Link, 2: array<mixed>|false}>
     */
    public function getDependents($needle, ?ConstraintInterface $constraint = null, bool $invert = false, bool $recurse = true, ?array $packagesFound = null): array
    {
        $needles = array_map('strtolower', (array) $needle);
        $results = [];

        // initialize the array with the needles before any recursion occurs
        if (null === $packagesFound) {
            $packagesFound = $needles;
        }

        // locate root package for use below
        $rootPackage = null;
        foreach ($this->getPackages() as $package) {
            if ($package instanceof RootPackageInterface) {
                $rootPackage = $package;
                break;
            }
        }

        // Loop over all currently installed packages.
        foreach ($this->getPackages() as $package) {
            $links = $package->getRequires();

            // each loop needs its own "tree" as we want to show the complete dependent set of every needle
            // without warning all the time about finding circular deps
            $packagesInTree = $packagesFound;

            // Replacements are considered valid reasons for a package to be installed during forward resolution
            if (!$invert) {
                $links += $package->getReplaces();

                // On forward search, check if any replaced package was required and add the replaced
                // packages to the list of needles. Contrary to the cross-reference link check below,
                // replaced packages are the target of links.
                foreach ($package->getReplaces() as $link) {
                    foreach ($needles as $needle) {
                        if ($link->getSource() === $needle) {
                            if ($constraint === null || ($link->getConstraint()->matches($constraint) === true)) {
                                // already displayed this node's dependencies, cutting short
                                if (in_array($link->getTarget(), $packagesInTree)) {
                                    $results[] = [$package, $link, false];
                                    continue;
                                }
                                $packagesInTree[] = $link->getTarget();
                                $dependents = $recurse ? $this->getDependents($link->getTarget(), null, false, true, $packagesInTree) : [];
                                $results[] = [$package, $link, $dependents];
                                $needles[] = $link->getTarget();
                            }
                        }
                    }
                }
                unset($needle);
            }

            // Require-dev is only relevant for the root package
            if ($package instanceof RootPackageInterface) {
                $links += $package->getDevRequires();
            }

            // Cross-reference all discovered links to the needles
            foreach ($links as $link) {
                foreach ($needles as $needle) {
                    if ($link->getTarget() === $needle) {
                        if ($constraint === null || ($link->getConstraint()->matches($constraint) === !$invert)) {
                            // already displayed this node's dependencies, cutting short
                            if (in_array($link->getSource(), $packagesInTree)) {
                                $results[] = [$package, $link, false];
                                continue;
                            }
                            $packagesInTree[] = $link->getSource();
                            $dependents = $recurse ? $this->getDependents($link->getSource(), null, false, true, $packagesInTree) : [];
                            $results[] = [$package, $link, $dependents];
                        }
                    }
                }
            }

            // When inverting, we need to check for conflicts of the needles against installed packages
            if ($invert && in_array($package->getName(), $needles, true)) {
                foreach ($package->getConflicts() as $link) {
                    foreach ($this->findPackages($link->getTarget()) as $pkg) {
                        $version = new Constraint('=', $pkg->getVersion());
                        if ($link->getConstraint()->matches($version) === $invert) {
                            $results[] = [$package, $link, false];
                        }
                    }
                }
            }

            // List conflicts against X as they may explain why the current version was selected, or explain why it is rejected if the conflict matched when inverting
            foreach ($package->getConflicts() as $link) {
                if (in_array($link->getTarget(), $needles, true)) {
                    foreach ($this->findPackages($link->getTarget()) as $pkg) {
                        $version = new Constraint('=', $pkg->getVersion());
                        if ($link->getConstraint()->matches($version) === $invert) {
                            $results[] = [$package, $link, false];
                        }
                    }
                }
            }

            // When inverting, we need to check for conflicts of the needles' requirements against installed packages
            if ($invert && $constraint && in_array($package->getName(), $needles, true) && $constraint->matches(new Constraint('=', $package->getVersion()))) {
                foreach ($package->getRequires() as $link) {
                    if (PlatformRepository::isPlatformPackage($link->getTarget())) {
                        if ($this->findPackage($link->getTarget(), $link->getConstraint())) {
                            continue;
                        }

                        $platformPkg = $this->findPackage($link->getTarget(), '*');
                        $description = $platformPkg ? 'but '.$platformPkg->getPrettyVersion().' is installed' : 'but it is missing';
                        $results[] = [$package, new Link($package->getName(), $link->getTarget(), new MatchAllConstraint, Link::TYPE_REQUIRE, $link->getPrettyConstraint().' '.$description), false];

                        continue;
                    }

                    foreach ($this->getPackages() as $pkg) {
                        if (!in_array($link->getTarget(), $pkg->getNames())) {
                            continue;
                        }

                        $version = new Constraint('=', $pkg->getVersion());

                        if ($link->getTarget() !== $pkg->getName()) {
                            foreach (array_merge($pkg->getReplaces(), $pkg->getProvides()) as $prov) {
                                if ($link->getTarget() === $prov->getTarget()) {
                                    $version = $prov->getConstraint();
                                    break;
                                }
                            }
                        }

                        if (!$link->getConstraint()->matches($version)) {
                            // if we have a root package (we should but can not guarantee..) we show
                            // the root requires as well to perhaps allow to find an issue there
                            if ($rootPackage) {
                                foreach (array_merge($rootPackage->getRequires(), $rootPackage->getDevRequires()) as $rootReq) {
                                    if (in_array($rootReq->getTarget(), $pkg->getNames()) && !$rootReq->getConstraint()->matches($link->getConstraint())) {
                                        $results[] = [$package, $link, false];
                                        $results[] = [$rootPackage, $rootReq, false];
                                        continue 3;
                                    }
                                }

                                $results[] = [$package, $link, false];
                                $results[] = [$rootPackage, new Link($rootPackage->getName(), $link->getTarget(), new MatchAllConstraint, Link::TYPE_DOES_NOT_REQUIRE, 'but ' . $pkg->getPrettyVersion() . ' is installed'), false];
                            } else {
                                // no root so let's just print whatever we found
                                $results[] = [$package, $link, false];
                            }
                        }

                        continue 2;
                    }
                }
            }
        }

        ksort($results);

        return $results;
    }

    public function getRepoName(): string
    {
        return 'installed repo ('.implode(', ', array_map(static function ($repo): string {
            return $repo->getRepoName();
        }, $this->getRepositories())).')';
    }

    /**
     * @inheritDoc
     */
    public function addRepository(RepositoryInterface $repository): void
    {
        if (
            $repository instanceof LockArrayRepository
            || $repository instanceof InstalledRepositoryInterface
            || $repository instanceof RootPackageRepository
            || $repository instanceof PlatformRepository
        ) {
            parent::addRepository($repository);

            return;
        }

        throw new \LogicException('An InstalledRepository can not contain a repository of type '.get_class($repository).' ('.$repository->getRepoName().')');
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

/**
 * Installable repository interface.
 *
 * Just used to tag installed repositories so the base classes can act differently on Alias packages
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface InstalledRepositoryInterface extends WritableRepositoryInterface
{
    /**
     * @return bool|null true if dev requirements were installed, false if --no-dev was used, null if yet unknown
     */
    public function getDevMode();

    /**
     * @return bool true if packages were never installed in this repository
     */
    public function isFresh();
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Advisory\PartialSecurityAdvisory;
use Composer\Advisory\SecurityAdvisory;
use Composer\Package\BasePackage;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\PackageInterface;
use Composer\Package\AliasPackage;
use Composer\Package\CompletePackage;
use Composer\Package\CompleteAliasPackage;
use Composer\Package\Version\VersionParser;
use Composer\Package\Version\StabilityFilter;
use Composer\Json\JsonFile;
use Composer\Cache;
use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Pcre\Preg;
use Composer\Plugin\PostFileDownloadEvent;
use Composer\Semver\CompilingMatcher;
use Composer\Util\HttpDownloader;
use Composer\Util\Loop;
use Composer\Plugin\PluginEvents;
use Composer\Plugin\PreFileDownloadEvent;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Downloader\TransportException;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\MatchAllConstraint;
use Composer\Util\Http\Response;
use Composer\MetadataMinifier\MetadataMinifier;
use Composer\Util\Url;
use React\Promise\PromiseInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ComposerRepository extends ArrayRepository implements ConfigurableRepositoryInterface, AdvisoryProviderInterface
{
    /**
     * @var mixed[]
     * @phpstan-var array{url: string, options?: mixed[], type?: 'composer', allow_ssl_downgrade?: bool}
     */
    private $repoConfig;
    /** @var mixed[] */
    private $options;
    /** @var non-empty-string */
    private $url;
    /** @var non-empty-string */
    private $baseUrl;
    /** @var IOInterface */
    private $io;
    /** @var HttpDownloader */
    private $httpDownloader;
    /** @var Loop */
    private $loop;
    /** @var Cache */
    protected $cache;
    /** @var ?non-empty-string */
    protected $notifyUrl = null;
    /** @var ?non-empty-string */
    protected $searchUrl = null;
    /** @var ?non-empty-string a URL containing %package% which can be queried to get providers of a given name */
    protected $providersApiUrl = null;
    /** @var bool */
    protected $hasProviders = false;
    /** @var ?non-empty-string */
    protected $providersUrl = null;
    /** @var ?non-empty-string */
    protected $listUrl = null;
    /** @var bool Indicates whether a comprehensive list of packages this repository might provide is expressed in the repository root. **/
    protected $hasAvailablePackageList = false;
    /** @var ?array<string> */
    protected $availablePackages = null;
    /** @var ?array<non-empty-string> */
    protected $availablePackagePatterns = null;
    /** @var ?non-empty-string */
    protected $lazyProvidersUrl = null;
    /** @var ?array<string, array{sha256: string}> */
    protected $providerListing;
    /** @var ArrayLoader */
    protected $loader;
    /** @var bool */
    private $allowSslDowngrade = false;
    /** @var ?EventDispatcher */
    private $eventDispatcher;
    /** @var ?array<string, list<array{url: non-empty-string, preferred: bool}>> */
    private $sourceMirrors;
    /** @var ?list<array{url: non-empty-string, preferred: bool}> */
    private $distMirrors;
    /** @var bool */
    private $degradedMode = false;
    /** @var mixed[]|true */
    private $rootData;
    /** @var bool */
    private $hasPartialPackages = false;
    /** @var ?array<string, mixed[]> */
    private $partialPackagesByName = null;
    /** @var bool */
    private $displayedWarningAboutNonMatchingPackageIndex = false;
    /** @var array{metadata: bool, api-url: string|null}|null */
    private $securityAdvisoryConfig = null;

    /**
     * @var array list of package names which are fresh and can be loaded from the cache directly in case loadPackage is called several times
     *            useful for v2 metadata repositories with lazy providers
     * @phpstan-var array<string, true>
     */
    private $freshMetadataUrls = [];

    /**
     * @var array list of package names which returned a 404 and should not be re-fetched in case loadPackage is called several times
     *            useful for v2 metadata repositories with lazy providers
     * @phpstan-var array<string, true>
     */
    private $packagesNotFoundCache = [];

    /**
     * @var VersionParser
     */
    private $versionParser;

    /**
     * @param array<string, mixed> $repoConfig
     * @phpstan-param array{url: non-empty-string, options?: mixed[], type?: 'composer', allow_ssl_downgrade?: bool} $repoConfig
     */
    public function __construct(array $repoConfig, IOInterface $io, Config $config, HttpDownloader $httpDownloader, ?EventDispatcher $eventDispatcher = null)
    {
        parent::__construct();
        if (!Preg::isMatch('{^[\w.]+\??://}', $repoConfig['url'])) {
            if (($localFilePath = realpath($repoConfig['url'])) !== false) {
                // it is a local path, add file scheme
                $repoConfig['url'] = 'file://'.$localFilePath;
            } else {
                // otherwise, assume http as the default protocol
                $repoConfig['url'] = 'http://'.$repoConfig['url'];
            }
        }
        $repoConfig['url'] = rtrim($repoConfig['url'], '/');
        if ($repoConfig['url'] === '') {
            throw new \InvalidArgumentException('The repository url must not be an empty string');
        }

        if (str_starts_with($repoConfig['url'], 'https?')) {
            $repoConfig['url'] = (extension_loaded('openssl') ? 'https' : 'http') . substr($repoConfig['url'], 6);
        }

        $urlBits = parse_url(strtr($repoConfig['url'], '\\', '/'));
        if ($urlBits === false || empty($urlBits['scheme'])) {
            throw new \UnexpectedValueException('Invalid url given for Composer repository: '.$repoConfig['url']);
        }

        if (!isset($repoConfig['options'])) {
            $repoConfig['options'] = [];
        }
        if (isset($repoConfig['allow_ssl_downgrade']) && true === $repoConfig['allow_ssl_downgrade']) {
            $this->allowSslDowngrade = true;
        }

        $this->options = $repoConfig['options'];
        $this->url = $repoConfig['url'];

        // force url for packagist.org to repo.packagist.org
        if (Preg::isMatch('{^(?P<proto>https?)://packagist\.org/?$}i', $this->url, $match)) {
            $this->url = $match['proto'].'://repo.packagist.org';
        }

        $baseUrl = rtrim(Preg::replace('{(?:/[^/\\\\]+\.json)?(?:[?#].*)?$}', '', $this->url), '/');
        assert($baseUrl !== '');
        $this->baseUrl = $baseUrl;
        $this->io = $io;
        $this->cache = new Cache($io, $config->get('cache-repo-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($this->url)), 'a-z0-9.$~_');
        $this->cache->setReadOnly($config->get('cache-read-only'));
        $this->versionParser = new VersionParser();
        $this->loader = new ArrayLoader($this->versionParser);
        $this->httpDownloader = $httpDownloader;
        $this->eventDispatcher = $eventDispatcher;
        $this->repoConfig = $repoConfig;
        $this->loop = new Loop($this->httpDownloader);
    }

    public function getRepoName()
    {
        return 'composer repo ('.Url::sanitize($this->url).')';
    }

    public function getRepoConfig()
    {
        return $this->repoConfig;
    }

    /**
     * @inheritDoc
     */
    public function findPackage(string $name, $constraint)
    {
        // this call initializes loadRootServerFile which is needed for the rest below to work
        $hasProviders = $this->hasProviders();

        $name = strtolower($name);
        if (!$constraint instanceof ConstraintInterface) {
            $constraint = $this->versionParser->parseConstraints($constraint);
        }

        if ($this->lazyProvidersUrl) {
            if ($this->hasPartialPackages() && isset($this->partialPackagesByName[$name])) {
                return $this->filterPackages($this->whatProvides($name), $constraint, true);
            }

            if ($this->hasAvailablePackageList && !$this->lazyProvidersRepoContains($name)) {
                return null;
            }

            $packages = $this->loadAsyncPackages([$name => $constraint]);

            if (count($packages['packages']) > 0) {
                return reset($packages['packages']);
            }

            return null;
        }

        if ($hasProviders) {
            foreach ($this->getProviderNames() as $providerName) {
                if ($name === $providerName) {
                    return $this->filterPackages($this->whatProvides($providerName), $constraint, true);
                }
            }

            return null;
        }

        return parent::findPackage($name, $constraint);
    }

    /**
     * @inheritDoc
     */
    public function findPackages(string $name, $constraint = null)
    {
        // this call initializes loadRootServerFile which is needed for the rest below to work
        $hasProviders = $this->hasProviders();

        $name = strtolower($name);
        if (null !== $constraint && !$constraint instanceof ConstraintInterface) {
            $constraint = $this->versionParser->parseConstraints($constraint);
        }

        if ($this->lazyProvidersUrl) {
            if ($this->hasPartialPackages() && isset($this->partialPackagesByName[$name])) {
                return $this->filterPackages($this->whatProvides($name), $constraint);
            }

            if ($this->hasAvailablePackageList && !$this->lazyProvidersRepoContains($name)) {
                return [];
            }

            $result = $this->loadAsyncPackages([$name => $constraint]);

            return $result['packages'];
        }

        if ($hasProviders) {
            foreach ($this->getProviderNames() as $providerName) {
                if ($name === $providerName) {
                    return $this->filterPackages($this->whatProvides($providerName), $constraint);
                }
            }

            return [];
        }

        return parent::findPackages($name, $constraint);
    }

    /**
     * @param array<BasePackage> $packages
     *
     * @return BasePackage|array<BasePackage>|null
     */
    private function filterPackages(array $packages, ?ConstraintInterface $constraint = null, bool $returnFirstMatch = false)
    {
        if (null === $constraint) {
            if ($returnFirstMatch) {
                return reset($packages);
            }

            return $packages;
        }

        $filteredPackages = [];

        foreach ($packages as $package) {
            $pkgConstraint = new Constraint('==', $package->getVersion());

            if ($constraint->matches($pkgConstraint)) {
                if ($returnFirstMatch) {
                    return $package;
                }

                $filteredPackages[] = $package;
            }
        }

        if ($returnFirstMatch) {
            return null;
        }

        return $filteredPackages;
    }

    public function getPackages()
    {
        $hasProviders = $this->hasProviders();

        if ($this->lazyProvidersUrl) {
            if (is_array($this->availablePackages) && !$this->availablePackagePatterns) {
                $packageMap = [];
                foreach ($this->availablePackages as $name) {
                    $packageMap[$name] = new MatchAllConstraint();
                }

                $result = $this->loadAsyncPackages($packageMap);

                return array_values($result['packages']);
            }

            if ($this->hasPartialPackages()) {
                if (!is_array($this->partialPackagesByName)) {
                    throw new \LogicException('hasPartialPackages failed to initialize $this->partialPackagesByName');
                }

                return $this->createPackages($this->partialPackagesByName, 'packages.json inline packages');
            }

            throw new \LogicException('Composer repositories that have lazy providers and no available-packages list can not load the complete list of packages, use getPackageNames instead.');
        }

        if ($hasProviders) {
            throw new \LogicException('Composer repositories that have providers can not load the complete list of packages, use getPackageNames instead.');
        }

        return parent::getPackages();
    }

    /**
     * @param string|null $packageFilter Package pattern filter which can include "*" as a wildcard
     *
     * @return string[]
     */
    public function getPackageNames(?string $packageFilter = null)
    {
        $hasProviders = $this->hasProviders();

        $filterResults =
            /**
             * @param list<string> $results
             * @return list<string>
             */
            static function (array $results): array {
                return $results;
            }
        ;
        if (null !== $packageFilter && '' !== $packageFilter) {
            $packageFilterRegex = BasePackage::packageNameToRegexp($packageFilter);
            $filterResults =
                /**
                 * @param list<string> $results
                 * @return list<string>
                 */
                static function (array $results) use ($packageFilterRegex): array {
                    /** @var list<string> $results */
                    return Preg::grep($packageFilterRegex, $results);
                }
            ;
        }

        if ($this->lazyProvidersUrl) {
            if (is_array($this->availablePackages)) {
                return $filterResults(array_keys($this->availablePackages));
            }

            if ($this->listUrl) {
                // no need to call $filterResults here as the $packageFilter is applied in the function itself
                return $this->loadPackageList($packageFilter);
            }

            if ($this->hasPartialPackages() && $this->partialPackagesByName !== null) {
                return $filterResults(array_keys($this->partialPackagesByName));
            }

            return [];
        }

        if ($hasProviders) {
            return $filterResults($this->getProviderNames());
        }

        $names = [];
        foreach ($this->getPackages() as $package) {
            $names[] = $package->getPrettyName();
        }

        return $filterResults($names);
    }

    /**
     * @return list<string>
     */
    private function getVendorNames(): array
    {
        $cacheKey = 'vendor-list.txt';
        $cacheAge = $this->cache->getAge($cacheKey);
        if (false !== $cacheAge && $cacheAge < 600 && ($cachedData = $this->cache->read($cacheKey)) !== false) {
            $cachedData = explode("\n", $cachedData);

            return $cachedData;
        }

        $names = $this->getPackageNames();

        $uniques = [];
        foreach ($names as $name) {
            $uniques[explode('/', $name, 2)[0]] = true;
        }

        $vendors = array_keys($uniques);

        if (!$this->cache->isReadOnly()) {
            $this->cache->write($cacheKey, implode("\n", $vendors));
        }

        return $vendors;
    }

    /**
     * @return list<string>
     */
    private function loadPackageList(?string $packageFilter = null): array
    {
        if (null === $this->listUrl) {
            throw new \LogicException('Make sure to call loadRootServerFile before loadPackageList');
        }

        $url = $this->listUrl;
        if (is_string($packageFilter) && $packageFilter !== '') {
            $url .= '?filter='.urlencode($packageFilter);
            $result = $this->httpDownloader->get($url, $this->options)->decodeJson();

            return $result['packageNames'];
        }

        $cacheKey = 'package-list.txt';
        $cacheAge = $this->cache->getAge($cacheKey);
        if (false !== $cacheAge && $cacheAge < 600 && ($cachedData = $this->cache->read($cacheKey)) !== false) {
            $cachedData = explode("\n", $cachedData);

            return $cachedData;
        }

        $result = $this->httpDownloader->get($url, $this->options)->decodeJson();
        if (!$this->cache->isReadOnly()) {
            $this->cache->write($cacheKey, implode("\n", $result['packageNames']));
        }

        return $result['packageNames'];
    }

    public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = [])
    {
        // this call initializes loadRootServerFile which is needed for the rest below to work
        $hasProviders = $this->hasProviders();

        if (!$hasProviders && !$this->hasPartialPackages() && null === $this->lazyProvidersUrl) {
            return parent::loadPackages($packageNameMap, $acceptableStabilities, $stabilityFlags, $alreadyLoaded);
        }

        $packages = [];
        $namesFound = [];

        if ($hasProviders || $this->hasPartialPackages()) {
            foreach ($packageNameMap as $name => $constraint) {
                $matches = [];

                // if a repo has no providers but only partial packages and the partial packages are missing
                // then we don't want to call whatProvides as it would try to load from the providers and fail
                if (!$hasProviders && !isset($this->partialPackagesByName[$name])) {
                    continue;
                }

                $candidates = $this->whatProvides($name, $acceptableStabilities, $stabilityFlags, $alreadyLoaded);
                foreach ($candidates as $candidate) {
                    if ($candidate->getName() !== $name) {
                        throw new \LogicException('whatProvides should never return a package with a different name than the requested one');
                    }
                    $namesFound[$name] = true;

                    if (!$constraint || $constraint->matches(new Constraint('==', $candidate->getVersion()))) {
                        $matches[spl_object_hash($candidate)] = $candidate;
                        if ($candidate instanceof AliasPackage && !isset($matches[spl_object_hash($candidate->getAliasOf())])) {
                            $matches[spl_object_hash($candidate->getAliasOf())] = $candidate->getAliasOf();
                        }
                    }
                }

                // add aliases of matched packages even if they did not match the constraint
                foreach ($candidates as $candidate) {
                    if ($candidate instanceof AliasPackage) {
                        if (isset($matches[spl_object_hash($candidate->getAliasOf())])) {
                            $matches[spl_object_hash($candidate)] = $candidate;
                        }
                    }
                }
                $packages = array_merge($packages, $matches);

                unset($packageNameMap[$name]);
            }
        }

        if ($this->lazyProvidersUrl && count($packageNameMap)) {
            if ($this->hasAvailablePackageList) {
                foreach ($packageNameMap as $name => $constraint) {
                    if (!$this->lazyProvidersRepoContains(strtolower($name))) {
                        unset($packageNameMap[$name]);
                    }
                }
            }

            $result = $this->loadAsyncPackages($packageNameMap, $acceptableStabilities, $stabilityFlags, $alreadyLoaded);
            $packages = array_merge($packages, $result['packages']);
            $namesFound = array_merge($namesFound, $result['namesFound']);
        }

        return ['namesFound' => array_keys($namesFound), 'packages' => $packages];
    }

    /**
     * @inheritDoc
     */
    public function search(string $query, int $mode = 0, ?string $type = null)
    {
        $this->loadRootServerFile(600);

        if ($this->searchUrl && $mode === self::SEARCH_FULLTEXT) {
            $url = str_replace(['%query%', '%type%'], [urlencode($query), $type], $this->searchUrl);

            $search = $this->httpDownloader->get($url, $this->options)->decodeJson();

            if (empty($search['results'])) {
                return [];
            }

            $results = [];
            foreach ($search['results'] as $result) {
                // do not show virtual packages in results as they are not directly useful from a composer perspective
                if (!empty($result['virtual'])) {
                    continue;
                }

                $results[] = $result;
            }

            return $results;
        }

        if ($mode === self::SEARCH_VENDOR) {
            $results = [];
            $regex = '{(?:'.implode('|', Preg::split('{\s+}', $query)).')}i';

            $vendorNames = $this->getVendorNames();
            foreach (Preg::grep($regex, $vendorNames) as $name) {
                $results[] = ['name' => $name, 'description' => ''];
            }

            return $results;
        }

        if ($this->hasProviders() || $this->lazyProvidersUrl) {
            // optimize search for "^foo/bar" where at least "^foo/" is present by loading this directly from the listUrl if present
            if (Preg::isMatchStrictGroups('{^\^(?P<query>(?P<vendor>[a-z0-9_.-]+)/[a-z0-9_.-]*)\*?$}i', $query, $match) && $this->listUrl !== null) {
                $url = $this->listUrl . '?vendor='.urlencode($match['vendor']).'&filter='.urlencode($match['query'].'*');
                $result = $this->httpDownloader->get($url, $this->options)->decodeJson();

                $results = [];
                foreach ($result['packageNames'] as $name) {
                    $results[] = ['name' => $name, 'description' => ''];
                }

                return $results;
            }

            $results = [];
            $regex = '{(?:'.implode('|', Preg::split('{\s+}', $query)).')}i';

            $packageNames = $this->getPackageNames();
            foreach (Preg::grep($regex, $packageNames) as $name) {
                $results[] = ['name' => $name, 'description' => ''];
            }

            return $results;
        }

        return parent::search($query, $mode);
    }

    public function hasSecurityAdvisories(): bool
    {
        $this->loadRootServerFile(600);

        return $this->securityAdvisoryConfig !== null && ($this->securityAdvisoryConfig['metadata'] || $this->securityAdvisoryConfig['api-url'] !== null);
    }

    /**
     * @inheritDoc
     */
    public function getSecurityAdvisories(array $packageConstraintMap, bool $allowPartialAdvisories = false): array
    {
        $this->loadRootServerFile(600);
        if (null === $this->securityAdvisoryConfig) {
            return ['namesFound' => [], 'advisories' => []];
        }

        $advisories = [];
        $namesFound = [];

        $apiUrl = $this->securityAdvisoryConfig['api-url'];

        // respect available-package-patterns / available-packages directives from the repo
        if ($this->hasAvailablePackageList) {
            foreach ($packageConstraintMap as $name => $constraint) {
                if (!$this->lazyProvidersRepoContains(strtolower($name))) {
                    unset($packageConstraintMap[$name]);
                }
            }
        }

        $parser = new VersionParser();
        /**
         * @param array<mixed> $data
         * @param string $name
         * @return ($allowPartialAdvisories is false ? SecurityAdvisory|null : PartialSecurityAdvisory|SecurityAdvisory|null)
         */
        $create = function (array $data, string $name) use ($parser, $allowPartialAdvisories, &$packageConstraintMap): ?PartialSecurityAdvisory {
            $advisory = PartialSecurityAdvisory::create($name, $data, $parser);
            if (!$allowPartialAdvisories && !$advisory instanceof SecurityAdvisory) {
                throw new \RuntimeException('Advisory for '.$name.' could not be loaded as a full advisory from '.$this->getRepoName() . PHP_EOL . var_export($data, true));
            }
            if (!$advisory->affectedVersions->matches($packageConstraintMap[$name])) {
                return null;
            }

            return $advisory;
        };

        if ($this->securityAdvisoryConfig['metadata'] && ($allowPartialAdvisories || $apiUrl === null)) {
            $promises = [];
            foreach ($packageConstraintMap as $name => $constraint) {
                $name = strtolower($name);

                // skip platform packages, root package and composer-plugin-api
                if (PlatformRepository::isPlatformPackage($name) || '__root__' === $name) {
                    continue;
                }

                $promises[] = $this->startCachedAsyncDownload($name, $name)
                    ->then(static function (array $spec) use (&$advisories, &$namesFound, &$packageConstraintMap, $name, $create): void {
                        [$response, ] = $spec;

                        if (!isset($response['security-advisories']) || !is_array($response['security-advisories'])) {
                            return;
                        }

                        $namesFound[$name] = true;
                        if (count($response['security-advisories']) > 0) {
                            $advisories[$name] = array_filter(array_map(
                                static function ($data) use ($name, $create) {
                                    return $create($data, $name);
                                },
                                $response['security-advisories']
                            ));
                        }
                        unset($packageConstraintMap[$name]);
                    });
            }

            $this->loop->wait($promises);
        }

        if ($apiUrl !== null && count($packageConstraintMap) > 0) {
            $options = $this->options;
            $options['http']['method'] = 'POST';
            if (isset($options['http']['header'])) {
                $options['http']['header'] = (array) $options['http']['header'];
            }
            $options['http']['header'][] = 'Content-type: application/x-www-form-urlencoded';
            $options['http']['timeout'] = 10;
            $options['http']['content'] = http_build_query(['packages' => array_keys($packageConstraintMap)]);

            $response = $this->httpDownloader->get($apiUrl, $options);
            $warned = false;
            /** @var string $name */
            foreach ($response->decodeJson()['advisories'] as $name => $list) {
                if (!isset($packageConstraintMap[$name])) {
                    if (!$warned) {
                        $this->io->writeError('<warning>'.$this->getRepoName().' returned names which were not requested in response to the security-advisories API. '.$name.' was not requested but is present in the response. Requested names were: '.implode(', ', array_keys($packageConstraintMap)).'</warning>');
                        $warned = true;
                    }
                    continue;
                }
                if (count($list) > 0) {
                    $advisories[$name] = array_filter(array_map(
                        static function ($data) use ($name, $create) {
                            return $create($data, $name);
                        },
                        $list
                    ));
                }
                $namesFound[$name] = true;
            }
        }

        return ['namesFound' => array_keys($namesFound), 'advisories' => array_filter($advisories)];
    }

    public function getProviders(string $packageName)
    {
        $this->loadRootServerFile();
        $result = [];

        if ($this->providersApiUrl) {
            try {
                $apiResult = $this->httpDownloader->get(str_replace('%package%', $packageName, $this->providersApiUrl), $this->options)->decodeJson();
            } catch (TransportException $e) {
                if ($e->getStatusCode() === 404) {
                    return $result;
                }
                throw $e;
            }

            foreach ($apiResult['providers'] as $provider) {
                $result[$provider['name']] = $provider;
            }

            return $result;
        }

        if ($this->hasPartialPackages()) {
            if (!is_array($this->partialPackagesByName)) {
                throw new \LogicException('hasPartialPackages failed to initialize $this->partialPackagesByName');
            }
            foreach ($this->partialPackagesByName as $versions) {
                foreach ($versions as $candidate) {
                    if (isset($result[$candidate['name']]) || !isset($candidate['provide'][$packageName])) {
                        continue;
                    }
                    $result[$candidate['name']] = [
                        'name' => $candidate['name'],
                        'description' => $candidate['description'] ?? '',
                        'type' => $candidate['type'] ?? '',
                    ];
                }
            }
        }

        if ($this->packages) {
            $result = array_merge($result, parent::getProviders($packageName));
        }

        return $result;
    }

    /**
     * @return string[]
     */
    private function getProviderNames(): array
    {
        $this->loadRootServerFile();

        if (null === $this->providerListing) {
            $data = $this->loadRootServerFile();
            if (is_array($data)) {
                $this->loadProviderListings($data);
            }
        }

        if ($this->lazyProvidersUrl) {
            // Can not determine list of provided packages for lazy repositories
            return [];
        }

        if (null !== $this->providersUrl && null !== $this->providerListing) {
            return array_keys($this->providerListing);
        }

        return [];
    }

    protected function configurePackageTransportOptions(PackageInterface $package): void
    {
        foreach ($package->getDistUrls() as $url) {
            if (strpos($url, $this->baseUrl) === 0) {
                $package->setTransportOptions($this->options);

                return;
            }
        }
    }

    private function hasProviders(): bool
    {
        $this->loadRootServerFile();

        return $this->hasProviders;
    }

    /**
     * @param  string      $name package name
     * @param array<string, int>|null $acceptableStabilities
     * @phpstan-param array<key-of<BasePackage::STABILITIES>, BasePackage::STABILITY_*>|null $acceptableStabilities
     * @param array<string, int>|null $stabilityFlags an array of package name => BasePackage::STABILITY_* value
     * @phpstan-param array<string, BasePackage::STABILITY_*>|null $stabilityFlags
     * @param array<string, array<string, PackageInterface>> $alreadyLoaded
     *
     * @return array<string, BasePackage>
     */
    private function whatProvides(string $name, ?array $acceptableStabilities = null, ?array $stabilityFlags = null, array $alreadyLoaded = []): array
    {
        $packagesSource = null;
        if (!$this->hasPartialPackages() || !isset($this->partialPackagesByName[$name])) {
            // skip platform packages, root package and composer-plugin-api
            if (PlatformRepository::isPlatformPackage($name) || '__root__' === $name) {
                return [];
            }

            if (null === $this->providerListing) {
                $data = $this->loadRootServerFile();
                if (is_array($data)) {
                    $this->loadProviderListings($data);
                }
            }

            $useLastModifiedCheck = false;
            if ($this->lazyProvidersUrl && !isset($this->providerListing[$name])) {
                $hash = null;
                $url = str_replace('%package%', $name, $this->lazyProvidersUrl);
                $cacheKey = 'provider-'.strtr($name, '/', '$').'.json';
                $useLastModifiedCheck = true;
            } elseif ($this->providersUrl) {
                // package does not exist in this repo
                if (!isset($this->providerListing[$name])) {
                    return [];
                }

                $hash = $this->providerListing[$name]['sha256'];
                $url = str_replace(['%package%', '%hash%'], [$name, $hash], $this->providersUrl);
                $cacheKey = 'provider-'.strtr($name, '/', '$').'.json';
            } else {
                return [];
            }

            $packages = null;
            if (!$useLastModifiedCheck && $hash && $this->cache->sha256($cacheKey) === $hash) {
                $packages = json_decode($this->cache->read($cacheKey), true);
                $packagesSource = 'cached file ('.$cacheKey.' originating from '.Url::sanitize($url).')';
            } elseif ($useLastModifiedCheck) {
                if ($contents = $this->cache->read($cacheKey)) {
                    $contents = json_decode($contents, true);
                    // we already loaded some packages from this file, so assume it is fresh and avoid fetching it again
                    if (isset($alreadyLoaded[$name])) {
                        $packages = $contents;
                        $packagesSource = 'cached file ('.$cacheKey.' originating from '.Url::sanitize($url).')';
                    } elseif (isset($contents['last-modified'])) {
                        $response = $this->fetchFileIfLastModified($url, $cacheKey, $contents['last-modified']);
                        $packages = true === $response ? $contents : $response;
                        $packagesSource = true === $response ? 'cached file ('.$cacheKey.' originating from '.Url::sanitize($url).')' : 'downloaded file ('.Url::sanitize($url).')';
                    }
                }
            }

            if (!$packages) {
                try {
                    $packages = $this->fetchFile($url, $cacheKey, $hash, $useLastModifiedCheck);
                    $packagesSource = 'downloaded file ('.Url::sanitize($url).')';
                } catch (TransportException $e) {
                    // 404s are acceptable for lazy provider repos
                    if ($this->lazyProvidersUrl && in_array($e->getStatusCode(), [404, 499], true)) {
                        $packages = ['packages' => []];
                        $packagesSource = 'not-found file ('.Url::sanitize($url).')';
                        if ($e->getStatusCode() === 499) {
                            $this->io->error('<warning>' . $e->getMessage() . '</warning>');
                        }
                    } else {
                        throw $e;
                    }
                }
            }

            $loadingPartialPackage = false;
        } else {
            $packages = ['packages' => ['versions' => $this->partialPackagesByName[$name]]];
            $packagesSource = 'root file ('.Url::sanitize($this->getPackagesJsonUrl()).')';
            $loadingPartialPackage = true;
        }

        $result = [];
        $versionsToLoad = [];
        foreach ($packages['packages'] as $versions) {
            foreach ($versions as $version) {
                $normalizedName = strtolower($version['name']);

                // only load the actual named package, not other packages that might find themselves in the same file
                if ($normalizedName !== $name) {
                    continue;
                }

                if (!$loadingPartialPackage && $this->hasPartialPackages() && isset($this->partialPackagesByName[$normalizedName])) {
                    continue;
                }

                if (!isset($versionsToLoad[$version['uid']])) {
                    if (!isset($version['version_normalized'])) {
                        $version['version_normalized'] = $this->versionParser->normalize($version['version']);
                    } elseif ($version['version_normalized'] === VersionParser::DEFAULT_BRANCH_ALIAS) {
                        // handling of existing repos which need to remain composer v1 compatible, in case the version_normalized contained VersionParser::DEFAULT_BRANCH_ALIAS, we renormalize it
                        $version['version_normalized'] = $this->versionParser->normalize($version['version']);
                    }

                    // avoid loading packages which have already been loaded
                    if (isset($alreadyLoaded[$name][$version['version_normalized']])) {
                        continue;
                    }

                    if ($this->isVersionAcceptable(null, $normalizedName, $version, $acceptableStabilities, $stabilityFlags)) {
                        $versionsToLoad[$version['uid']] = $version;
                    }
                }
            }
        }

        // load acceptable packages in the providers
        $loadedPackages = $this->createPackages($versionsToLoad, $packagesSource);
        $uids = array_keys($versionsToLoad);

        foreach ($loadedPackages as $index => $package) {
            $package->setRepository($this);
            $uid = $uids[$index];

            if ($package instanceof AliasPackage) {
                $aliased = $package->getAliasOf();
                $aliased->setRepository($this);

                $result[$uid] = $aliased;
                $result[$uid.'-alias'] = $package;
            } else {
                $result[$uid] = $package;
            }
        }

        return $result;
    }

    /**
     * @inheritDoc
     */
    protected function initialize()
    {
        parent::initialize();

        $repoData = $this->loadDataFromServer();

        foreach ($this->createPackages($repoData, 'root file ('.Url::sanitize($this->getPackagesJsonUrl()).')') as $package) {
            $this->addPackage($package);
        }
    }

    /**
     * Adds a new package to the repository
     */
    public function addPackage(PackageInterface $package)
    {
        parent::addPackage($package);
        $this->configurePackageTransportOptions($package);
    }

    /**
     * @param array<string, ConstraintInterface|null> $packageNames array of package name => ConstraintInterface|null - if a constraint is provided, only
     *                                                packages matching it will be loaded
     * @param array<string, int>|null $acceptableStabilities
     * @phpstan-param array<key-of<BasePackage::STABILITIES>, BasePackage::STABILITY_*>|null $acceptableStabilities
     * @param array<string, int>|null $stabilityFlags an array of package name => BasePackage::STABILITY_* value
     * @phpstan-param array<string, BasePackage::STABILITY_*>|null $stabilityFlags
     * @param array<string, array<string, PackageInterface>> $alreadyLoaded
     *
     * @return array{namesFound: array<string, true>, packages: array<string, BasePackage>}
     */
    private function loadAsyncPackages(array $packageNames, ?array $acceptableStabilities = null, ?array $stabilityFlags = null, array $alreadyLoaded = []): array
    {
        $this->loadRootServerFile();

        $packages = [];
        $namesFound = [];
        $promises = [];

        if (null === $this->lazyProvidersUrl) {
            throw new \LogicException('loadAsyncPackages only supports v2 protocol composer repos with a metadata-url');
        }

        // load ~dev versions of the packages as well if needed
        foreach ($packageNames as $name => $constraint) {
            if ($acceptableStabilities === null || $stabilityFlags === null || StabilityFilter::isPackageAcceptable($acceptableStabilities, $stabilityFlags, [$name], 'dev')) {
                $packageNames[$name.'~dev'] = $constraint;
            }
            // if only dev stability is requested, we skip loading the non dev file
            if (isset($acceptableStabilities['dev']) && count($acceptableStabilities) === 1 && count($stabilityFlags) === 0) {
                unset($packageNames[$name]);
            }
        }

        foreach ($packageNames as $name => $constraint) {
            $name = strtolower($name);

            $realName = Preg::replace('{~dev$}', '', $name);
            // skip platform packages, root package and composer-plugin-api
            if (PlatformRepository::isPlatformPackage($realName) || '__root__' === $realName) {
                continue;
            }

            $promises[] = $this->startCachedAsyncDownload($name, $realName)
                ->then(function (array $spec) use (&$packages, &$namesFound, $realName, $constraint, $acceptableStabilities, $stabilityFlags, $alreadyLoaded): void {
                    [$response, $packagesSource] = $spec;
                    if (null === $response || !isset($response['packages'][$realName])) {
                        return;
                    }

                    $versions = $response['packages'][$realName];

                    if (isset($response['minified']) && $response['minified'] === 'composer/2.0') {
                        $versions = MetadataMinifier::expand($versions);
                    }

                    $namesFound[$realName] = true;
                    $versionsToLoad = [];
                    foreach ($versions as $version) {
                        if (!isset($version['version_normalized'])) {
                            $version['version_normalized'] = $this->versionParser->normalize($version['version']);
                        } elseif ($version['version_normalized'] === VersionParser::DEFAULT_BRANCH_ALIAS) {
                            // handling of existing repos which need to remain composer v1 compatible, in case the version_normalized contained VersionParser::DEFAULT_BRANCH_ALIAS, we renormalize it
                            $version['version_normalized'] = $this->versionParser->normalize($version['version']);
                        }

                        // avoid loading packages which have already been loaded
                        if (isset($alreadyLoaded[$realName][$version['version_normalized']])) {
                            continue;
                        }

                        if ($this->isVersionAcceptable($constraint, $realName, $version, $acceptableStabilities, $stabilityFlags)) {
                            $versionsToLoad[] = $version;
                        }
                    }

                    $loadedPackages = $this->createPackages($versionsToLoad, $packagesSource);
                    foreach ($loadedPackages as $package) {
                        $package->setRepository($this);
                        $packages[spl_object_hash($package)] = $package;

                        if ($package instanceof AliasPackage && !isset($packages[spl_object_hash($package->getAliasOf())])) {
                            $package->getAliasOf()->setRepository($this);
                            $packages[spl_object_hash($package->getAliasOf())] = $package->getAliasOf();
                        }
                    }
                });
        }

        $this->loop->wait($promises);

        return ['namesFound' => $namesFound, 'packages' => $packages];
    }

    /**
     * @phpstan-return PromiseInterface<array{mixed, string}>
     */
    private function startCachedAsyncDownload(string $fileName, ?string $packageName = null): PromiseInterface
    {
        if (null === $this->lazyProvidersUrl) {
            throw new \LogicException('startCachedAsyncDownload only supports v2 protocol composer repos with a metadata-url');
        }

        $name = strtolower($fileName);
        $packageName = $packageName ?? $name;

        $url = str_replace('%package%', $name, $this->lazyProvidersUrl);
        $cacheKey = 'provider-'.strtr($name, '/', '~').'.json';

        $lastModified = null;
        if ($contents = $this->cache->read($cacheKey)) {
            $contents = json_decode($contents, true);
            $lastModified = $contents['last-modified'] ?? null;
        }

        return $this->asyncFetchFile($url, $cacheKey, $lastModified)
            ->then(static function ($response) use ($url, $cacheKey, $contents, $packageName): array {
                $packagesSource = 'downloaded file ('.Url::sanitize($url).')';

                if (true === $response) {
                    $packagesSource = 'cached file ('.$cacheKey.' originating from '.Url::sanitize($url).')';
                    $response = $contents;
                }

                if (!isset($response['packages'][$packageName]) && !isset($response['security-advisories'])) {
                    return [null, $packagesSource];
                }

                return [$response, $packagesSource];
            });
    }

    /**
     * @param string $name package name (must be lowercased already)
     * @param array<string, mixed> $versionData
     * @param array<string, int>|null $acceptableStabilities
     * @phpstan-param array<key-of<BasePackage::STABILITIES>, BasePackage::STABILITY_*>|null $acceptableStabilities
     * @param array<string, int>|null $stabilityFlags an array of package name => BasePackage::STABILITY_* value
     * @phpstan-param array<string, BasePackage::STABILITY_*>|null $stabilityFlags
     */
    private function isVersionAcceptable(?ConstraintInterface $constraint, string $name, array $versionData, ?array $acceptableStabilities = null, ?array $stabilityFlags = null): bool
    {
        $versions = [$versionData['version_normalized']];

        if ($alias = $this->loader->getBranchAlias($versionData)) {
            $versions[] = $alias;
        }

        foreach ($versions as $version) {
            if (null !== $acceptableStabilities && null !== $stabilityFlags && !StabilityFilter::isPackageAcceptable($acceptableStabilities, $stabilityFlags, [$name], VersionParser::parseStability($version))) {
                continue;
            }

            if ($constraint && !CompilingMatcher::match($constraint, Constraint::OP_EQ, $version)) {
                continue;
            }

            return true;
        }

        return false;
    }

    private function getPackagesJsonUrl(): string
    {
        $jsonUrlParts = parse_url(strtr($this->url, '\\', '/'));

        if (isset($jsonUrlParts['path']) && false !== strpos($jsonUrlParts['path'], '.json')) {
            return $this->url;
        }

        return $this->url . '/packages.json';
    }

    /**
     * @return array<'providers'|'provider-includes'|'packages'|'providers-url'|'notify-batch'|'search'|'mirrors'|'providers-lazy-url'|'metadata-url'|'available-packages'|'available-package-patterns', mixed>|true
     */
    protected function loadRootServerFile(?int $rootMaxAge = null)
    {
        if (null !== $this->rootData) {
            return $this->rootData;
        }

        if (!extension_loaded('openssl') && strpos($this->url, 'https') === 0) {
            throw new \RuntimeException('You must enable the openssl extension in your php.ini to load information from '.$this->url);
        }

        if ($cachedData = $this->cache->read('packages.json')) {
            $cachedData = json_decode($cachedData, true);
            if ($rootMaxAge !== null && ($age = $this->cache->getAge('packages.json')) !== false && $age <= $rootMaxAge) {
                $data = $cachedData;
            } elseif (isset($cachedData['last-modified'])) {
                $response = $this->fetchFileIfLastModified($this->getPackagesJsonUrl(), 'packages.json', $cachedData['last-modified']);
                $data = true === $response ? $cachedData : $response;
            }
        }

        if (!isset($data)) {
            $data = $this->fetchFile($this->getPackagesJsonUrl(), 'packages.json', null, true);
        }

        if (!empty($data['notify-batch'])) {
            $this->notifyUrl = $this->canonicalizeUrl($data['notify-batch']);
        } elseif (!empty($data['notify'])) {
            $this->notifyUrl = $this->canonicalizeUrl($data['notify']);
        }

        if (!empty($data['search'])) {
            $this->searchUrl = $this->canonicalizeUrl($data['search']);
        }

        if (!empty($data['mirrors'])) {
            foreach ($data['mirrors'] as $mirror) {
                if (!empty($mirror['git-url'])) {
                    $this->sourceMirrors['git'][] = ['url' => $mirror['git-url'], 'preferred' => !empty($mirror['preferred'])];
                }
                if (!empty($mirror['hg-url'])) {
                    $this->sourceMirrors['hg'][] = ['url' => $mirror['hg-url'], 'preferred' => !empty($mirror['preferred'])];
                }
                if (!empty($mirror['dist-url'])) {
                    $this->distMirrors[] = [
                        'url' => $this->canonicalizeUrl($mirror['dist-url']),
                        'preferred' => !empty($mirror['preferred']),
                    ];
                }
            }
        }

        if (!empty($data['providers-lazy-url'])) {
            $this->lazyProvidersUrl = $this->canonicalizeUrl($data['providers-lazy-url']);
            $this->hasProviders = true;

            $this->hasPartialPackages = !empty($data['packages']) && is_array($data['packages']);
        }

        // metadata-url indicates V2 repo protocol so it takes over from all the V1 types
        // V2 only has lazyProviders and possibly partial packages, but no ability to process anything else,
        // V2 also supports async loading
        if (!empty($data['metadata-url'])) {
            $this->lazyProvidersUrl = $this->canonicalizeUrl($data['metadata-url']);
            $this->providersUrl = null;
            $this->hasProviders = false;
            $this->hasPartialPackages = !empty($data['packages']) && is_array($data['packages']);
            $this->allowSslDowngrade = false;

            // provides a list of package names that are available in this repo
            // this disables lazy-provider behavior in the sense that if a list is available we assume it is finite and won't search for other packages in that repo
            // while if no list is there lazyProvidersUrl is used when looking for any package name to see if the repo knows it
            if (!empty($data['available-packages'])) {
                $availPackages = array_map('strtolower', $data['available-packages']);
                $this->availablePackages = array_combine($availPackages, $availPackages);
                $this->hasAvailablePackageList = true;
            }

            // Provides a list of package name patterns (using * wildcards to match any substring, e.g. "vendor/*") that are available in this repo
            // Disables lazy-provider behavior as with available-packages, but may allow much more compact expression of packages covered by this repository.
            // Over-specifying covered packages is safe, but may result in increased traffic to your repository.
            if (!empty($data['available-package-patterns'])) {
                $this->availablePackagePatterns = array_map(static function ($pattern): string {
                    return BasePackage::packageNameToRegexp($pattern);
                }, $data['available-package-patterns']);
                $this->hasAvailablePackageList = true;
            }

            // Remove legacy keys as most repos need to be compatible with Composer v1
            // as well but we are not interested in the old format anymore at this point
            unset($data['providers-url'], $data['providers'], $data['providers-includes']);

            if (isset($data['security-advisories']) && is_array($data['security-advisories'])) {
                $this->securityAdvisoryConfig = [
                    'metadata' => $data['security-advisories']['metadata'] ?? false,
                    'api-url' => isset($data['security-advisories']['api-url']) && is_string($data['security-advisories']['api-url']) ? $this->canonicalizeUrl($data['security-advisories']['api-url']) : null,
                ];
                if ($this->securityAdvisoryConfig['api-url'] === null && !$this->hasAvailablePackageList) {
                    throw new \UnexpectedValueException('Invalid security advisory configuration on '.$this->getRepoName().': If the repository does not provide a security-advisories.api-url then available-packages or available-package-patterns are required to be provided for performance reason.');
                }
            }
        }

        if ($this->allowSslDowngrade) {
            $this->url = str_replace('https://', 'http://', $this->url);
            $this->baseUrl = str_replace('https://', 'http://', $this->baseUrl);
        }

        if (!empty($data['providers-url'])) {
            $this->providersUrl = $this->canonicalizeUrl($data['providers-url']);
            $this->hasProviders = true;
        }

        if (!empty($data['list'])) {
            $this->listUrl = $this->canonicalizeUrl($data['list']);
        }

        if (!empty($data['providers']) || !empty($data['providers-includes'])) {
            $this->hasProviders = true;
        }

        if (!empty($data['providers-api'])) {
            $this->providersApiUrl = $this->canonicalizeUrl($data['providers-api']);
        }

        return $this->rootData = $data;
    }

    /**
     * @param string $url
     * @return non-empty-string
     */
    private function canonicalizeUrl(string $url): string
    {
        if (strlen($url) === 0) {
            throw new \InvalidArgumentException('Expected a string with a value and not an empty string');
        }

        if (str_starts_with($url, '/')) {
            if (Preg::isMatch('{^[^:]++://[^/]*+}', $this->url, $matches)) {
                return $matches[0] . $url;
            }

            return $this->url;
        }

        return $url;
    }

    /**
     * @return mixed[]
     */
    private function loadDataFromServer(): array
    {
        $data = $this->loadRootServerFile();
        if (true === $data) {
            throw new \LogicException('loadRootServerFile should not return true during initialization');
        }

        return $this->loadIncludes($data);
    }

    private function hasPartialPackages(): bool
    {
        if ($this->hasPartialPackages && null === $this->partialPackagesByName) {
            $this->initializePartialPackages();
        }

        return $this->hasPartialPackages;
    }

    /**
     * @param array{providers?: mixed[], provider-includes?: mixed[]} $data
     */
    private function loadProviderListings($data): void
    {
        if (isset($data['providers'])) {
            if (!is_array($this->providerListing)) {
                $this->providerListing = [];
            }
            $this->providerListing = array_merge($this->providerListing, $data['providers']);
        }

        if ($this->providersUrl && isset($data['provider-includes'])) {
            $includes = $data['provider-includes'];
            foreach ($includes as $include => $metadata) {
                $url = $this->baseUrl . '/' . str_replace('%hash%', $metadata['sha256'], $include);
                $cacheKey = str_replace(['%hash%','$'], '', $include);
                if ($this->cache->sha256($cacheKey) === $metadata['sha256']) {
                    $includedData = json_decode($this->cache->read($cacheKey), true);
                } else {
                    $includedData = $this->fetchFile($url, $cacheKey, $metadata['sha256']);
                }

                $this->loadProviderListings($includedData);
            }
        }
    }

    /**
     * @param mixed[] $data
     *
     * @return mixed[]
     */
    private function loadIncludes(array $data): array
    {
        $packages = [];

        // legacy repo handling
        if (!isset($data['packages']) && !isset($data['includes'])) {
            foreach ($data as $pkg) {
                if (isset($pkg['versions']) && is_array($pkg['versions'])) {
                    foreach ($pkg['versions'] as $metadata) {
                        $packages[] = $metadata;
                    }
                }
            }

            return $packages;
        }

        if (isset($data['packages'])) {
            foreach ($data['packages'] as $package => $versions) {
                $packageName = strtolower((string) $package);
                foreach ($versions as $version => $metadata) {
                    $packages[] = $metadata;
                    if (!$this->displayedWarningAboutNonMatchingPackageIndex && $packageName !== strtolower((string) ($metadata['name'] ?? ''))) {
                        $this->displayedWarningAboutNonMatchingPackageIndex = true;
                        $this->io->writeError(sprintf("<warning>Warning: the packages key '%s' doesn't match the name defined in the package metadata '%s' in repository %s</warning>", $package, $metadata['name'] ?? '', $this->baseUrl));
                    }
                }
            }
        }

        if (isset($data['includes'])) {
            foreach ($data['includes'] as $include => $metadata) {
                if (isset($metadata['sha1']) && $this->cache->sha1((string) $include) === $metadata['sha1']) {
                    $includedData = json_decode($this->cache->read((string) $include), true);
                } else {
                    $includedData = $this->fetchFile($include);
                }
                $packages = array_merge($packages, $this->loadIncludes($includedData));
            }
        }

        return $packages;
    }

    /**
     * @param mixed[] $packages
     *
     * @return list<CompletePackage|CompleteAliasPackage>
     */
    private function createPackages(array $packages, ?string $source = null): array
    {
        if (!$packages) {
            return [];
        }

        try {
            foreach ($packages as &$data) {
                if (!isset($data['notification-url'])) {
                    $data['notification-url'] = $this->notifyUrl;
                }
            }

            $packageInstances = $this->loader->loadPackages($packages);

            foreach ($packageInstances as $package) {
                if (isset($this->sourceMirrors[$package->getSourceType()])) {
                    $package->setSourceMirrors($this->sourceMirrors[$package->getSourceType()]);
                }
                $package->setDistMirrors($this->distMirrors);
                $this->configurePackageTransportOptions($package);
            }

            return $packageInstances;
        } catch (\Exception $e) {
            throw new \RuntimeException('Could not load packages in '.$this->getRepoName().($source ? ' from '.$source : '').': ['.get_class($e).'] '.$e->getMessage(), 0, $e);
        }
    }

    /**
     * @return array<mixed>
     */
    protected function fetchFile(string $filename, ?string $cacheKey = null, ?string $sha256 = null, bool $storeLastModifiedTime = false)
    {
        if ('' === $filename) {
            throw new \InvalidArgumentException('$filename should not be an empty string');
        }

        if (null === $cacheKey) {
            $cacheKey = $filename;
            $filename = $this->baseUrl.'/'.$filename;
        }

        // url-encode $ signs in URLs as bad proxies choke on them
        if (($pos = strpos($filename, '$')) && Preg::isMatch('{^https?://}i', $filename)) {
            $filename = substr($filename, 0, $pos) . '%24' . substr($filename, $pos + 1);
        }

        $retries = 3;
        while ($retries--) {
            try {
                $options = $this->options;
                if ($this->eventDispatcher) {
                    $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $filename, 'metadata', ['repository' => $this]);
                    $preFileDownloadEvent->setTransportOptions($this->options);
                    $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent);
                    $filename = $preFileDownloadEvent->getProcessedUrl();
                    $options = $preFileDownloadEvent->getTransportOptions();
                }

                $response = $this->httpDownloader->get($filename, $options);
                $json = (string) $response->getBody();
                if ($sha256 && $sha256 !== hash('sha256', $json)) {
                    // undo downgrade before trying again if http seems to be hijacked or modifying content somehow
                    if ($this->allowSslDowngrade) {
                        $this->url = str_replace('http://', 'https://', $this->url);
                        $this->baseUrl = str_replace('http://', 'https://', $this->baseUrl);
                        $filename = str_replace('http://', 'https://', $filename);
                    }

                    if ($retries > 0) {
                        usleep(100000);

                        continue;
                    }

                    // TODO use scarier wording once we know for sure it doesn't do false positives anymore
                    throw new RepositorySecurityException('The contents of '.$filename.' do not match its signature. This could indicate a man-in-the-middle attack or e.g. antivirus software corrupting files. Try running composer again and report this if you think it is a mistake.');
                }

                if ($this->eventDispatcher) {
                    $postFileDownloadEvent = new PostFileDownloadEvent(PluginEvents::POST_FILE_DOWNLOAD, null, $sha256, $filename, 'metadata', ['response' => $response, 'repository' => $this]);
                    $this->eventDispatcher->dispatch($postFileDownloadEvent->getName(), $postFileDownloadEvent);
                }

                $data = $response->decodeJson();
                HttpDownloader::outputWarnings($this->io, $this->url, $data);

                if ($cacheKey && !$this->cache->isReadOnly()) {
                    if ($storeLastModifiedTime) {
                        $lastModifiedDate = $response->getHeader('last-modified');
                        if ($lastModifiedDate) {
                            $data['last-modified'] = $lastModifiedDate;
                            $json = JsonFile::encode($data, 0);
                        }
                    }
                    $this->cache->write($cacheKey, $json);
                }

                $response->collect();

                break;
            } catch (\Exception $e) {
                if ($e instanceof \LogicException) {
                    throw $e;
                }

                if ($e instanceof TransportException && $e->getStatusCode() === 404) {
                    throw $e;
                }

                if ($e instanceof RepositorySecurityException) {
                    throw $e;
                }

                if ($cacheKey && ($contents = $this->cache->read($cacheKey))) {
                    if (!$this->degradedMode) {
                        $this->io->writeError('<warning>'.$this->url.' could not be fully loaded ('.$e->getMessage().'), package information was loaded from the local cache and may be out of date</warning>');
                    }
                    $this->degradedMode = true;
                    $data = JsonFile::parseJson($contents, $this->cache->getRoot().$cacheKey);

                    break;
                }

                throw $e;
            }
        }

        if (!isset($data)) {
            throw new \LogicException("ComposerRepository: Undefined \$data. Please report at https://github.com/composer/composer/issues/new.");
        }

        return $data;
    }

    /**
     * @return array<mixed>|true
     */
    private function fetchFileIfLastModified(string $filename, string $cacheKey, string $lastModifiedTime)
    {
        if ('' === $filename) {
            throw new \InvalidArgumentException('$filename should not be an empty string');
        }

        try {
            $options = $this->options;
            if ($this->eventDispatcher) {
                $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $filename, 'metadata', ['repository' => $this]);
                $preFileDownloadEvent->setTransportOptions($this->options);
                $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent);
                $filename = $preFileDownloadEvent->getProcessedUrl();
                $options = $preFileDownloadEvent->getTransportOptions();
            }

            if (isset($options['http']['header'])) {
                $options['http']['header'] = (array) $options['http']['header'];
            }
            $options['http']['header'][] = 'If-Modified-Since: '.$lastModifiedTime;
            $response = $this->httpDownloader->get($filename, $options);
            $json = (string) $response->getBody();
            if ($json === '' && $response->getStatusCode() === 304) {
                return true;
            }

            if ($this->eventDispatcher) {
                $postFileDownloadEvent = new PostFileDownloadEvent(PluginEvents::POST_FILE_DOWNLOAD, null, null, $filename, 'metadata', ['response' => $response, 'repository' => $this]);
                $this->eventDispatcher->dispatch($postFileDownloadEvent->getName(), $postFileDownloadEvent);
            }

            $data = $response->decodeJson();
            HttpDownloader::outputWarnings($this->io, $this->url, $data);

            $lastModifiedDate = $response->getHeader('last-modified');
            $response->collect();
            if ($lastModifiedDate) {
                $data['last-modified'] = $lastModifiedDate;
                $json = JsonFile::encode($data, 0);
            }
            if (!$this->cache->isReadOnly()) {
                $this->cache->write($cacheKey, $json);
            }

            return $data;
        } catch (\Exception $e) {
            if ($e instanceof \LogicException) {
                throw $e;
            }

            if ($e instanceof TransportException && $e->getStatusCode() === 404) {
                throw $e;
            }

            if (!$this->degradedMode) {
                $this->io->writeError('<warning>'.$this->url.' could not be fully loaded ('.$e->getMessage().'), package information was loaded from the local cache and may be out of date</warning>');
            }
            $this->degradedMode = true;

            return true;
        }
    }

    /**
     * @phpstan-return PromiseInterface<array<mixed>|true> true if the response was a 304 and the cache is fresh, otherwise it returns the decoded json
     */
    private function asyncFetchFile(string $filename, string $cacheKey, ?string $lastModifiedTime = null): PromiseInterface
    {
        if ('' === $filename) {
            throw new \InvalidArgumentException('$filename should not be an empty string');
        }

        if (isset($this->packagesNotFoundCache[$filename])) {
            return \React\Promise\resolve(['packages' => []]);
        }

        if (isset($this->freshMetadataUrls[$filename]) && $lastModifiedTime) {
            // make it look like we got a 304 response
            /** @var PromiseInterface<true> $promise */
            $promise = \React\Promise\resolve(true);

            return $promise;
        }

        $httpDownloader = $this->httpDownloader;
        $options = $this->options;
        if ($this->eventDispatcher) {
            $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->httpDownloader, $filename, 'metadata', ['repository' => $this]);
            $preFileDownloadEvent->setTransportOptions($this->options);
            $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent);
            $filename = $preFileDownloadEvent->getProcessedUrl();
            $options = $preFileDownloadEvent->getTransportOptions();
        }

        if ($lastModifiedTime) {
            if (isset($options['http']['header'])) {
                $options['http']['header'] = (array) $options['http']['header'];
            }
            $options['http']['header'][] = 'If-Modified-Since: '.$lastModifiedTime;
        }

        $io = $this->io;
        $url = $this->url;
        $cache = $this->cache;
        $degradedMode = &$this->degradedMode;
        $eventDispatcher = $this->eventDispatcher;

        /**
         * @return array<mixed>|true true if the response was a 304 and the cache is fresh
         */
        $accept = function ($response) use ($io, $url, $filename, $cache, $cacheKey, $eventDispatcher) {
            // package not found is acceptable for a v2 protocol repository
            if ($response->getStatusCode() === 404) {
                $this->packagesNotFoundCache[$filename] = true;

                return ['packages' => []];
            }

            $json = (string) $response->getBody();
            if ($json === '' && $response->getStatusCode() === 304) {
                $this->freshMetadataUrls[$filename] = true;

                return true;
            }

            if ($eventDispatcher) {
                $postFileDownloadEvent = new PostFileDownloadEvent(PluginEvents::POST_FILE_DOWNLOAD, null, null, $filename, 'metadata', ['response' => $response, 'repository' => $this]);
                $eventDispatcher->dispatch($postFileDownloadEvent->getName(), $postFileDownloadEvent);
            }

            $data = $response->decodeJson();
            HttpDownloader::outputWarnings($io, $url, $data);

            $lastModifiedDate = $response->getHeader('last-modified');
            $response->collect();
            if ($lastModifiedDate) {
                $data['last-modified'] = $lastModifiedDate;
                $json = JsonFile::encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
            }
            if (!$cache->isReadOnly()) {
                $cache->write($cacheKey, $json);
            }
            $this->freshMetadataUrls[$filename] = true;

            return $data;
        };

        $reject = function ($e) use ($filename, $accept, $io, $url, &$degradedMode, $lastModifiedTime) {
            if ($e instanceof TransportException && $e->getStatusCode() === 404) {
                $this->packagesNotFoundCache[$filename] = true;

                return false;
            }

            if (!$degradedMode) {
                $io->writeError('<warning>'.$url.' could not be fully loaded ('.$e->getMessage().'), package information was loaded from the local cache and may be out of date</warning>');
            }
            $degradedMode = true;

            // if the file is in the cache, we fake a 304 Not Modified to allow the process to continue
            if ($lastModifiedTime) {
                return $accept(new Response(['url' => $url], 304, [], ''));
            }

            // special error code returned when network is being artificially disabled
            if ($e instanceof TransportException && $e->getStatusCode() === 499) {
                return $accept(new Response(['url' => $url], 404, [], ''));
            }

            throw $e;
        };

        return $httpDownloader->add($filename, $options)->then($accept, $reject);
    }

    /**
     * This initializes the packages key of a partial packages.json that contain some packages inlined + a providers-lazy-url
     *
     * This should only be called once
     */
    private function initializePartialPackages(): void
    {
        $rootData = $this->loadRootServerFile();
        if ($rootData === true) {
            return;
        }

        $this->partialPackagesByName = [];
        foreach ($rootData['packages'] as $package => $versions) {
            foreach ($versions as $version) {
                $versionPackageName = strtolower((string) ($version['name'] ?? ''));
                $this->partialPackagesByName[$versionPackageName][] = $version;
                if (!$this->displayedWarningAboutNonMatchingPackageIndex && $versionPackageName !== strtolower($package)) {
                    $this->io->writeError(sprintf("<warning>Warning: the packages key '%s' doesn't match the name defined in the package metadata '%s' in repository %s</warning>", $package, $version['name'] ?? '', $this->baseUrl));
                    $this->displayedWarningAboutNonMatchingPackageIndex = true;
                }
            }
        }

        // wipe rootData as it is fully consumed at this point and this saves some memory
        $this->rootData = true;
    }

    /**
     * Checks if the package name is present in this lazy providers repo
     *
     * @return bool   true if the package name is present in availablePackages or matched by availablePackagePatterns
     */
    protected function lazyProvidersRepoContains(string $name)
    {
        if (!$this->hasAvailablePackageList) {
            throw new \LogicException('lazyProvidersRepoContains should not be called unless hasAvailablePackageList is true');
        }

        if (is_array($this->availablePackages) && isset($this->availablePackages[$name])) {
            return true;
        }

        if (is_array($this->availablePackagePatterns)) {
            foreach ($this->availablePackagePatterns as $providerRegex) {
                if (Preg::isMatch($providerRegex, $name)) {
                    return true;
                }
            }
        }

        return false;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Package\PackageInterface;
use Composer\Package\BasePackage;
use Composer\Semver\Constraint\ConstraintInterface;

/**
 * Repository interface.
 *
 * @author Nils Adermann <naderman@naderman.de>
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface RepositoryInterface extends \Countable
{
    public const SEARCH_FULLTEXT = 0;
    public const SEARCH_NAME = 1;
    public const SEARCH_VENDOR = 2;

    /**
     * Checks if specified package registered (installed).
     *
     * @param PackageInterface $package package instance
     *
     * @return bool
     */
    public function hasPackage(PackageInterface $package);

    /**
     * Searches for the first match of a package by name and version.
     *
     * @param string                     $name       package name
     * @param string|ConstraintInterface $constraint package version or version constraint to match against
     *
     * @return BasePackage|null
     */
    public function findPackage(string $name, $constraint);

    /**
     * Searches for all packages matching a name and optionally a version.
     *
     * @param string                     $name       package name
     * @param string|ConstraintInterface $constraint package version or version constraint to match against
     *
     * @return BasePackage[]
     */
    public function findPackages(string $name, $constraint = null);

    /**
     * Returns list of registered packages.
     *
     * @return BasePackage[]
     */
    public function getPackages();

    /**
     * Returns list of registered packages with the supplied name
     *
     * - The packages returned are the packages found which match the constraints, acceptable stability and stability flags provided
     * - The namesFound returned are names which should be considered as canonically found in this repository, that should not be looked up in any further lower priority repositories
     *
     * @param ConstraintInterface[]                          $packageNameMap        package names pointing to constraints
     * @param array<string, int>        $acceptableStabilities array of stability => BasePackage::STABILITY_* value
     * @param array<string, BasePackage::STABILITY_*>        $stabilityFlags        an array of package name => BasePackage::STABILITY_* value
     * @param array<string, array<string, PackageInterface>> $alreadyLoaded         an array of package name => package version => package
     *
     * @return array
     *
     * @phpstan-param  array<key-of<BasePackage::STABILITIES>, BasePackage::STABILITY_*> $acceptableStabilities
     * @phpstan-param  array<string, ConstraintInterface|null> $packageNameMap
     * @phpstan-return array{namesFound: array<string>, packages: array<BasePackage>}
     */
    public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = []);

    /**
     * Searches the repository for packages containing the query
     *
     * @param string  $query search query, for SEARCH_NAME and SEARCH_VENDOR regular expressions metacharacters are supported by implementations, and user input should be escaped through preg_quote by callers
     * @param int     $mode  a set of SEARCH_* constants to search on, implementations should do a best effort only, default is SEARCH_FULLTEXT
     * @param ?string $type  The type of package to search for. Defaults to all types of packages
     *
     * @return array[] an array of array('name' => '...', 'description' => '...'|null, 'abandoned' => 'string'|true|unset) For SEARCH_VENDOR the name will be in "vendor" form
     * @phpstan-return list<array{name: string, description: ?string, abandoned?: string|true, url?: string}>
     */
    public function search(string $query, int $mode = 0, ?string $type = null);

    /**
     * Returns a list of packages providing a given package name
     *
     * Packages which have the same name as $packageName should not be returned, only those that have a "provide" on it.
     *
     * @param string $packageName package name which must be provided
     *
     * @return array[] an array with the provider name as key and value of array('name' => '...', 'description' => '...', 'type' => '...')
     * @phpstan-return array<string, array{name: string, description: string|null, type: string}>
     */
    public function getProviders(string $packageName);

    /**
     * Returns a name representing this repository to the user
     *
     * This is best effort and definitely can not always be very precise
     *
     * @return string
     */
    public function getRepoName();
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Package\BasePackage;
use Composer\Package\PackageInterface;

/**
 * Composite repository.
 *
 * @author Beau Simensen <beau@dflydev.com>
 */
class CompositeRepository implements RepositoryInterface
{
    /**
     * List of repositories
     * @var RepositoryInterface[]
     */
    private $repositories;

    /**
     * Constructor
     * @param RepositoryInterface[] $repositories
     */
    public function __construct(array $repositories)
    {
        $this->repositories = [];
        foreach ($repositories as $repo) {
            $this->addRepository($repo);
        }
    }

    public function getRepoName(): string
    {
        return 'composite repo ('.implode(', ', array_map(static function ($repo): string {
            return $repo->getRepoName();
        }, $this->repositories)).')';
    }

    /**
     * Returns all the wrapped repositories
     *
     * @return RepositoryInterface[]
     */
    public function getRepositories(): array
    {
        return $this->repositories;
    }

    /**
     * @inheritDoc
     */
    public function hasPackage(PackageInterface $package): bool
    {
        foreach ($this->repositories as $repository) {
            /* @var $repository RepositoryInterface */
            if ($repository->hasPackage($package)) {
                return true;
            }
        }

        return false;
    }

    /**
     * @inheritDoc
     */
    public function findPackage($name, $constraint): ?BasePackage
    {
        foreach ($this->repositories as $repository) {
            /* @var $repository RepositoryInterface */
            $package = $repository->findPackage($name, $constraint);
            if (null !== $package) {
                return $package;
            }
        }

        return null;
    }

    /**
     * @inheritDoc
     */
    public function findPackages($name, $constraint = null): array
    {
        $packages = [];
        foreach ($this->repositories as $repository) {
            /* @var $repository RepositoryInterface */
            $packages[] = $repository->findPackages($name, $constraint);
        }

        return $packages ? array_merge(...$packages) : [];
    }

    /**
     * @inheritDoc
     */
    public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = []): array
    {
        $packages = [];
        $namesFound = [];
        foreach ($this->repositories as $repository) {
            /* @var $repository RepositoryInterface */
            $result = $repository->loadPackages($packageNameMap, $acceptableStabilities, $stabilityFlags, $alreadyLoaded);
            $packages[] = $result['packages'];
            $namesFound[] = $result['namesFound'];
        }

        return [
            'packages' => $packages ? array_merge(...$packages) : [],
            'namesFound' => $namesFound ? array_unique(array_merge(...$namesFound)) : [],
        ];
    }

    /**
     * @inheritDoc
     */
    public function search(string $query, int $mode = 0, ?string $type = null): array
    {
        $matches = [];
        foreach ($this->repositories as $repository) {
            /* @var $repository RepositoryInterface */
            $matches[] = $repository->search($query, $mode, $type);
        }

        return \count($matches) > 0 ? array_merge(...$matches) : [];
    }

    /**
     * @inheritDoc
     */
    public function getPackages(): array
    {
        $packages = [];
        foreach ($this->repositories as $repository) {
            /* @var $repository RepositoryInterface */
            $packages[] = $repository->getPackages();
        }

        return $packages ? array_merge(...$packages) : [];
    }

    /**
     * @inheritDoc
     */
    public function getProviders($packageName): array
    {
        $results = [];
        foreach ($this->repositories as $repository) {
            /* @var $repository RepositoryInterface */
            $results[] = $repository->getProviders($packageName);
        }

        return $results ? array_merge(...$results) : [];
    }

    public function removePackage(PackageInterface $package): void
    {
        foreach ($this->repositories as $repository) {
            if ($repository instanceof WritableRepositoryInterface) {
                $repository->removePackage($package);
            }
        }
    }

    /**
     * @inheritDoc
     */
    public function count(): int
    {
        $total = 0;
        foreach ($this->repositories as $repository) {
            /* @var $repository RepositoryInterface */
            $total += $repository->count();
        }

        return $total;
    }

    /**
     * Add a repository.
     */
    public function addRepository(RepositoryInterface $repository): void
    {
        if ($repository instanceof self) {
            foreach ($repository->getRepositories() as $repo) {
                $this->addRepository($repo);
            }
        } else {
            $this->repositories[] = $repository;
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Package\RootPackageInterface;

/**
 * Root package repository.
 *
 * This is used for serving the RootPackage inside an in-memory InstalledRepository
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class RootPackageRepository extends ArrayRepository
{
    public function __construct(RootPackageInterface $package)
    {
        parent::__construct([$package]);
    }

    public function getRepoName(): string
    {
        return 'root package repo';
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository\Vcs;

use Composer\Cache;
use Composer\Config;
use Composer\Json\JsonFile;
use Composer\Pcre\Preg;
use Composer\Util\ProcessExecutor;
use Composer\Util\Filesystem;
use Composer\Util\Url;
use Composer\Util\Svn as SvnUtil;
use Composer\IO\IOInterface;
use Composer\Downloader\TransportException;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Till Klampaeckel <till@php.net>
 */
class SvnDriver extends VcsDriver
{
    /** @var string */
    protected $baseUrl;
    /** @var array<int|string, string> Map of tag name to identifier */
    protected $tags;
    /** @var array<int|string, string> Map of branch name to identifier */
    protected $branches;
    /** @var ?string */
    protected $rootIdentifier;

    /** @var string|false */
    protected $trunkPath = 'trunk';
    /** @var string */
    protected $branchesPath = 'branches';
    /** @var string */
    protected $tagsPath = 'tags';
    /** @var string */
    protected $packagePath = '';
    /** @var bool */
    protected $cacheCredentials = true;

    /**
     * @var \Composer\Util\Svn
     */
    private $util;

    /**
     * @inheritDoc
     */
    public function initialize(): void
    {
        $this->url = $this->baseUrl = rtrim(self::normalizeUrl($this->url), '/');

        SvnUtil::cleanEnv();

        if (isset($this->repoConfig['trunk-path'])) {
            $this->trunkPath = $this->repoConfig['trunk-path'];
        }
        if (isset($this->repoConfig['branches-path'])) {
            $this->branchesPath = $this->repoConfig['branches-path'];
        }
        if (isset($this->repoConfig['tags-path'])) {
            $this->tagsPath = $this->repoConfig['tags-path'];
        }
        if (array_key_exists('svn-cache-credentials', $this->repoConfig)) {
            $this->cacheCredentials = (bool) $this->repoConfig['svn-cache-credentials'];
        }
        if (isset($this->repoConfig['package-path'])) {
            $this->packagePath = '/' . trim($this->repoConfig['package-path'], '/');
        }

        if (false !== ($pos = strrpos($this->url, '/' . $this->trunkPath))) {
            $this->baseUrl = substr($this->url, 0, $pos);
        }

        $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($this->baseUrl)));
        $this->cache->setReadOnly($this->config->get('cache-read-only'));

        $this->getBranches();
        $this->getTags();
    }

    /**
     * @inheritDoc
     */
    public function getRootIdentifier(): string
    {
        return $this->rootIdentifier ?: $this->trunkPath;
    }

    /**
     * @inheritDoc
     */
    public function getUrl(): string
    {
        return $this->url;
    }

    /**
     * @inheritDoc
     */
    public function getSource(string $identifier): array
    {
        return ['type' => 'svn', 'url' => $this->baseUrl, 'reference' => $identifier];
    }

    /**
     * @inheritDoc
     */
    public function getDist(string $identifier): ?array
    {
        return null;
    }

    /**
     * @inheritDoc
     */
    protected function shouldCache(string $identifier): bool
    {
        return $this->cache && Preg::isMatch('{@\d+$}', $identifier);
    }

    /**
     * @inheritDoc
     */
    public function getComposerInformation(string $identifier): ?array
    {
        if (!isset($this->infoCache[$identifier])) {
            if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier.'.json')) {
                // old cache files had '' stored instead of null due to af3783b5f40bae32a23e353eaf0a00c9b8ce82e2, so we make sure here that we always return null or array
                // and fix outdated invalid cache files
                if ($res === '""') {
                    $res = 'null';
                    $this->cache->write($identifier.'.json', json_encode(null));
                }

                return $this->infoCache[$identifier] = JsonFile::parseJson($res);
            }

            try {
                $composer = $this->getBaseComposerInformation($identifier);
            } catch (TransportException $e) {
                $message = $e->getMessage();
                if (stripos($message, 'path not found') === false && stripos($message, 'svn: warning: W160013') === false) {
                    throw $e;
                }
                // remember a not-existent composer.json
                $composer = null;
            }

            if ($this->shouldCache($identifier)) {
                $this->cache->write($identifier.'.json', json_encode($composer));
            }

            $this->infoCache[$identifier] = $composer;
        }

        // old cache files had '' stored instead of null due to af3783b5f40bae32a23e353eaf0a00c9b8ce82e2, so we make sure here that we always return null or array
        if (!is_array($this->infoCache[$identifier])) {
            return null;
        }

        return $this->infoCache[$identifier];
    }

    public function getFileContent(string $file, string $identifier): ?string
    {
        $identifier = '/' . trim($identifier, '/') . '/';

        if (Preg::isMatch('{^(.+?)(@\d+)?/$}', $identifier, $match) && $match[2] !== null) {
            $path = $match[1];
            $rev = $match[2];
        } else {
            $path = $identifier;
            $rev = '';
        }

        try {
            $resource = $path.$file;
            $output = $this->execute(['svn', 'cat'], $this->baseUrl . $resource . $rev);
            if ('' === trim($output)) {
                return null;
            }
        } catch (\RuntimeException $e) {
            throw new TransportException($e->getMessage());
        }

        return $output;
    }

    /**
     * @inheritDoc
     */
    public function getChangeDate(string $identifier): ?\DateTimeImmutable
    {
        $identifier = '/' . trim($identifier, '/') . '/';

        if (Preg::isMatch('{^(.+?)(@\d+)?/$}', $identifier, $match) && null !== $match[2]) {
            $path = $match[1];
            $rev = $match[2];
        } else {
            $path = $identifier;
            $rev = '';
        }

        $output = $this->execute(['svn', 'info'], $this->baseUrl . $path . $rev);
        foreach ($this->process->splitLines($output) as $line) {
            if ($line !== '' && Preg::isMatchStrictGroups('{^Last Changed Date: ([^(]+)}', $line, $match)) {
                return new \DateTimeImmutable($match[1], new \DateTimeZone('UTC'));
            }
        }

        return null;
    }

    /**
     * @inheritDoc
     */
    public function getTags(): array
    {
        if (null === $this->tags) {
            $tags = [];

            if ($this->tagsPath !== false) {
                $output = $this->execute(['svn', 'ls', '--verbose'], $this->baseUrl . '/' . $this->tagsPath);
                if ($output !== '') {
                    $lastRev = 0;
                    foreach ($this->process->splitLines($output) as $line) {
                        $line = trim($line);
                        if ($line !== '' && Preg::isMatch('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) {
                            if ($match[2] === './') {
                                $lastRev = (int) $match[1];
                            } else {
                                $tags[rtrim($match[2], '/')] = $this->buildIdentifier(
                                    '/' . $this->tagsPath . '/' . $match[2],
                                    max($lastRev, (int) $match[1])
                                );
                            }
                        }
                    }
                }
            }

            $this->tags = $tags;
        }

        return $this->tags;
    }

    /**
     * @inheritDoc
     */
    public function getBranches(): array
    {
        if (null === $this->branches) {
            $branches = [];

            if (false === $this->trunkPath) {
                $trunkParent = $this->baseUrl . '/';
            } else {
                $trunkParent = $this->baseUrl . '/' . $this->trunkPath;
            }

            $output = $this->execute(['svn', 'ls', '--verbose'], $trunkParent);
            if ($output !== '') {
                foreach ($this->process->splitLines($output) as $line) {
                    $line = trim($line);
                    if ($line !== '' && Preg::isMatch('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) {
                        if ($match[2] === './') {
                            $branches['trunk'] = $this->buildIdentifier(
                                '/' . $this->trunkPath,
                                (int) $match[1]
                            );
                            $this->rootIdentifier = $branches['trunk'];
                            break;
                        }
                    }
                }
            }
            unset($output);

            if ($this->branchesPath !== false) {
                $output = $this->execute(['svn', 'ls', '--verbose'], $this->baseUrl . '/' . $this->branchesPath);
                if ($output !== '') {
                    $lastRev = 0;
                    foreach ($this->process->splitLines(trim($output)) as $line) {
                        $line = trim($line);
                        if ($line !== '' && Preg::isMatch('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) {
                            if ($match[2] === './') {
                                $lastRev = (int) $match[1];
                            } else {
                                $branches[rtrim($match[2], '/')] = $this->buildIdentifier(
                                    '/' . $this->branchesPath . '/' . $match[2],
                                    max($lastRev, (int) $match[1])
                                );
                            }
                        }
                    }
                }
            }

            $this->branches = $branches;
        }

        return $this->branches;
    }

    /**
     * @inheritDoc
     */
    public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool
    {
        $url = self::normalizeUrl($url);
        if (Preg::isMatch('#(^svn://|^svn\+ssh://|svn\.)#i', $url)) {
            return true;
        }

        // proceed with deep check for local urls since they are fast to process
        if (!$deep && !Filesystem::isLocalPath($url)) {
            return false;
        }

        $process = new ProcessExecutor($io);
        $exit = $process->execute(['svn', 'info', '--non-interactive', '--', $url], $ignoredOutput);

        if ($exit === 0) {
            // This is definitely a Subversion repository.
            return true;
        }

        // Subversion client 1.7 and older
        if (false !== stripos($process->getErrorOutput(), 'authorization failed:')) {
            // This is likely a remote Subversion repository that requires
            // authentication. We will handle actual authentication later.
            return true;
        }

        // Subversion client 1.8 and newer
        if (false !== stripos($process->getErrorOutput(), 'Authentication failed')) {
            // This is likely a remote Subversion or newer repository that requires
            // authentication. We will handle actual authentication later.
            return true;
        }

        return false;
    }

    /**
     * An absolute path (leading '/') is converted to a file:// url.
     */
    protected static function normalizeUrl(string $url): string
    {
        $fs = new Filesystem();
        if ($fs->isAbsolutePath($url)) {
            return 'file://' . strtr($url, '\\', '/');
        }

        return $url;
    }

    /**
     * Execute an SVN command and try to fix up the process with credentials
     * if necessary.
     *
     * @param  non-empty-list<string> $command The svn command to run.
     * @param  string            $url     The SVN URL.
     * @throws \RuntimeException
     */
    protected function execute(array $command, string $url): string
    {
        if (null === $this->util) {
            $this->util = new SvnUtil($this->baseUrl, $this->io, $this->config, $this->process);
            $this->util->setCacheCredentials($this->cacheCredentials);
        }

        try {
            return $this->util->execute($command, $url);
        } catch (\RuntimeException $e) {
            if (null === $this->util->binaryVersion()) {
                throw new \RuntimeException('Failed to load '.$this->url.', svn was not found, check that it is installed and in your PATH env.' . "\n\n" . $this->process->getErrorOutput());
            }

            throw new \RuntimeException(
                'Repository '.$this->url.' could not be processed, '.$e->getMessage()
            );
        }
    }

    /**
     * Build the identifier respecting "package-path" config option
     *
     * @param string $baseDir  The path to trunk/branch/tag
     * @param int $revision The revision mark to add to identifier
     */
    protected function buildIdentifier(string $baseDir, int $revision): string
    {
        return rtrim($baseDir, '/') . $this->packagePath . '/@' . $revision;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository\Vcs;

use Composer\Config;
use Composer\IO\IOInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @internal
 */
interface VcsDriverInterface
{
    /**
     * Initializes the driver (git clone, svn checkout, fetch info etc)
     */
    public function initialize(): void;

    /**
     * Return the composer.json file information
     *
     * @param  string  $identifier Any identifier to a specific branch/tag/commit
     * @return mixed[]|null Array containing all infos from the composer.json file, or null to denote that no file was present
     */
    public function getComposerInformation(string $identifier): ?array;

    /**
     * Return the content of $file or null if the file does not exist.
     */
    public function getFileContent(string $file, string $identifier): ?string;

    /**
     * Get the changedate for $identifier.
     */
    public function getChangeDate(string $identifier): ?\DateTimeImmutable;

    /**
     * Return the root identifier (trunk, master, default/tip ..)
     *
     * @return string Identifier
     */
    public function getRootIdentifier(): string;

    /**
     * Return list of branches in the repository
     *
     * @return array<int|string, string> Branch names as keys, identifiers as values
     */
    public function getBranches(): array;

    /**
     * Return list of tags in the repository
     *
     * @return array<int|string, string> Tag names as keys, identifiers as values
     */
    public function getTags(): array;

    /**
     * @param string $identifier Any identifier to a specific branch/tag/commit
     *
     * @return array{type: string, url: string, reference: string, shasum: string}|null
     */
    public function getDist(string $identifier): ?array;

    /**
     * @param string $identifier Any identifier to a specific branch/tag/commit
     *
     * @return array{type: string, url: string, reference: string}
     */
    public function getSource(string $identifier): array;

    /**
     * Return the URL of the repository
     */
    public function getUrl(): string;

    /**
     * Return true if the repository has a composer file for a given identifier,
     * false otherwise.
     *
     * @param  string $identifier Any identifier to a specific branch/tag/commit
     * @return bool   Whether the repository has a composer file for a given identifier.
     */
    public function hasComposerFile(string $identifier): bool;

    /**
     * Performs any cleanup necessary as the driver is not longer needed
     */
    public function cleanup(): void;

    /**
     * Checks if this driver can handle a given url
     *
     * @param  IOInterface $io     IO instance
     * @param  Config      $config current $config
     * @param  string      $url    URL to validate/check
     * @param  bool        $deep   unless true, only shallow checks (url matching typically) should be done
     */
    public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool;
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository\Vcs;

use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Cache;
use Composer\Downloader\TransportException;
use Composer\Json\JsonFile;
use Composer\Pcre\Preg;
use Composer\Util\Bitbucket;
use Composer\Util\Http\Response;

/**
 * @author Per Bernhardt <plb@webfactory.de>
 */
class GitBitbucketDriver extends VcsDriver
{
    /** @var string */
    protected $owner;
    /** @var string */
    protected $repository;
    /** @var bool */
    private $hasIssues = false;
    /** @var ?string */
    private $rootIdentifier;
    /** @var array<int|string, string> Map of tag name to identifier */
    private $tags;
    /** @var array<int|string, string> Map of branch name to identifier */
    private $branches;
    /** @var string */
    private $branchesUrl = '';
    /** @var string */
    private $tagsUrl = '';
    /** @var string */
    private $homeUrl = '';
    /** @var string */
    private $website = '';
    /** @var string */
    private $cloneHttpsUrl = '';
    /** @var array<string, mixed> */
    private $repoData;

    /**
     * @var ?VcsDriver
     */
    protected $fallbackDriver = null;
    /** @var string|null if set either git or hg */
    private $vcsType;

    /**
     * @inheritDoc
     */
    public function initialize(): void
    {
        if (!Preg::isMatchStrictGroups('#^https?://bitbucket\.org/([^/]+)/([^/]+?)(?:\.git|/?)?$#i', $this->url, $match)) {
            throw new \InvalidArgumentException(sprintf('The Bitbucket repository URL %s is invalid. It must be the HTTPS URL of a Bitbucket repository.', $this->url));
        }

        $this->owner = $match[1];
        $this->repository = $match[2];
        $this->originUrl = 'bitbucket.org';
        $this->cache = new Cache(
            $this->io,
            implode('/', [
                $this->config->get('cache-repo-dir'),
                $this->originUrl,
                $this->owner,
                $this->repository,
            ])
        );
        $this->cache->setReadOnly($this->config->get('cache-read-only'));
    }

    /**
     * @inheritDoc
     */
    public function getUrl(): string
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getUrl();
        }

        return $this->cloneHttpsUrl;
    }

    /**
     * Attempts to fetch the repository data via the BitBucket API and
     * sets some parameters which are used in other methods
     *
     * @phpstan-impure
     */
    protected function getRepoData(): bool
    {
        $resource = sprintf(
            'https://api.bitbucket.org/2.0/repositories/%s/%s?%s',
            $this->owner,
            $this->repository,
            http_build_query(
                ['fields' => '-project,-owner'],
                '',
                '&'
            )
        );

        $repoData = $this->fetchWithOAuthCredentials($resource, true)->decodeJson();
        if ($this->fallbackDriver) {
            return false;
        }
        $this->parseCloneUrls($repoData['links']['clone']);

        $this->hasIssues = !empty($repoData['has_issues']);
        $this->branchesUrl = $repoData['links']['branches']['href'];
        $this->tagsUrl = $repoData['links']['tags']['href'];
        $this->homeUrl = $repoData['links']['html']['href'];
        $this->website = $repoData['website'];
        $this->vcsType = $repoData['scm'];

        $this->repoData = $repoData;

        return true;
    }

    /**
     * @inheritDoc
     */
    public function getComposerInformation(string $identifier): ?array
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getComposerInformation($identifier);
        }

        if (!isset($this->infoCache[$identifier])) {
            if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) {
                $composer = JsonFile::parseJson($res);
            } else {
                $composer = $this->getBaseComposerInformation($identifier);

                if ($this->shouldCache($identifier)) {
                    $this->cache->write($identifier, json_encode($composer));
                }
            }

            if ($composer !== null) {
                // specials for bitbucket
                if (isset($composer['support']) && !is_array($composer['support'])) {
                    $composer['support'] = [];
                }
                if (!isset($composer['support']['source'])) {
                    $label = array_search(
                        $identifier,
                        $this->getTags()
                    ) ?: array_search(
                        $identifier,
                        $this->getBranches()
                    ) ?: $identifier;

                    if (array_key_exists($label, $tags = $this->getTags())) {
                        $hash = $tags[$label];
                    } elseif (array_key_exists($label, $branches = $this->getBranches())) {
                        $hash = $branches[$label];
                    }

                    if (!isset($hash)) {
                        $composer['support']['source'] = sprintf(
                            'https://%s/%s/%s/src',
                            $this->originUrl,
                            $this->owner,
                            $this->repository
                        );
                    } else {
                        $composer['support']['source'] = sprintf(
                            'https://%s/%s/%s/src/%s/?at=%s',
                            $this->originUrl,
                            $this->owner,
                            $this->repository,
                            $hash,
                            $label
                        );
                    }
                }
                if (!isset($composer['support']['issues']) && $this->hasIssues) {
                    $composer['support']['issues'] = sprintf(
                        'https://%s/%s/%s/issues',
                        $this->originUrl,
                        $this->owner,
                        $this->repository
                    );
                }
                if (!isset($composer['homepage'])) {
                    $composer['homepage'] = empty($this->website) ? $this->homeUrl : $this->website;
                }
            }

            $this->infoCache[$identifier] = $composer;
        }

        return $this->infoCache[$identifier];
    }

    /**
     * @inheritDoc
     */
    public function getFileContent(string $file, string $identifier): ?string
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getFileContent($file, $identifier);
        }

        if (strpos($identifier, '/') !== false) {
            $branches = $this->getBranches();
            if (isset($branches[$identifier])) {
                $identifier = $branches[$identifier];
            }
        }

        $resource = sprintf(
            'https://api.bitbucket.org/2.0/repositories/%s/%s/src/%s/%s',
            $this->owner,
            $this->repository,
            $identifier,
            $file
        );

        return $this->fetchWithOAuthCredentials($resource)->getBody();
    }

    /**
     * @inheritDoc
     */
    public function getChangeDate(string $identifier): ?\DateTimeImmutable
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getChangeDate($identifier);
        }

        if (strpos($identifier, '/') !== false) {
            $branches = $this->getBranches();
            if (isset($branches[$identifier])) {
                $identifier = $branches[$identifier];
            }
        }

        $resource = sprintf(
            'https://api.bitbucket.org/2.0/repositories/%s/%s/commit/%s?fields=date',
            $this->owner,
            $this->repository,
            $identifier
        );
        $commit = $this->fetchWithOAuthCredentials($resource)->decodeJson();

        return new \DateTimeImmutable($commit['date']);
    }

    /**
     * @inheritDoc
     */
    public function getSource(string $identifier): array
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getSource($identifier);
        }

        return ['type' => $this->vcsType, 'url' => $this->getUrl(), 'reference' => $identifier];
    }

    /**
     * @inheritDoc
     */
    public function getDist(string $identifier): ?array
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getDist($identifier);
        }

        $url = sprintf(
            'https://bitbucket.org/%s/%s/get/%s.zip',
            $this->owner,
            $this->repository,
            $identifier
        );

        return ['type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => ''];
    }

    /**
     * @inheritDoc
     */
    public function getTags(): array
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getTags();
        }

        if (null === $this->tags) {
            $tags = [];
            $resource = sprintf(
                '%s?%s',
                $this->tagsUrl,
                http_build_query(
                    [
                        'pagelen' => 100,
                        'fields' => 'values.name,values.target.hash,next',
                        'sort' => '-target.date',
                    ],
                    '',
                    '&'
                )
            );
            $hasNext = true;
            while ($hasNext) {
                $tagsData = $this->fetchWithOAuthCredentials($resource)->decodeJson();
                foreach ($tagsData['values'] as $data) {
                    $tags[$data['name']] = $data['target']['hash'];
                }
                if (empty($tagsData['next'])) {
                    $hasNext = false;
                } else {
                    $resource = $tagsData['next'];
                }
            }

            $this->tags = $tags;
        }

        return $this->tags;
    }

    /**
     * @inheritDoc
     */
    public function getBranches(): array
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getBranches();
        }

        if (null === $this->branches) {
            $branches = [];
            $resource = sprintf(
                '%s?%s',
                $this->branchesUrl,
                http_build_query(
                    [
                        'pagelen' => 100,
                        'fields' => 'values.name,values.target.hash,values.heads,next',
                        'sort' => '-target.date',
                    ],
                    '',
                    '&'
                )
            );
            $hasNext = true;
            while ($hasNext) {
                $branchData = $this->fetchWithOAuthCredentials($resource)->decodeJson();
                foreach ($branchData['values'] as $data) {
                    $branches[$data['name']] = $data['target']['hash'];
                }
                if (empty($branchData['next'])) {
                    $hasNext = false;
                } else {
                    $resource = $branchData['next'];
                }
            }

            $this->branches = $branches;
        }

        return $this->branches;
    }

    /**
     * Get the remote content.
     *
     * @param string $url              The URL of content
     *
     * @return Response The result
     *
     * @phpstan-impure
     */
    protected function fetchWithOAuthCredentials(string $url, bool $fetchingRepoData = false): Response
    {
        try {
            return parent::getContents($url);
        } catch (TransportException $e) {
            $bitbucketUtil = new Bitbucket($this->io, $this->config, $this->process, $this->httpDownloader);

            if (in_array($e->getCode(), [403, 404], true) || (401 === $e->getCode() && strpos($e->getMessage(), 'Could not authenticate against') === 0)) {
                if (!$this->io->hasAuthentication($this->originUrl)
                    && $bitbucketUtil->authorizeOAuth($this->originUrl)
                ) {
                    return parent::getContents($url);
                }

                if (!$this->io->isInteractive() && $fetchingRepoData) {
                    $this->attemptCloneFallback();

                    return new Response(['url' => 'dummy'], 200, [], 'null');
                }
            }

            throw $e;
        }
    }

    /**
     * Generate an SSH URL
     */
    protected function generateSshUrl(): string
    {
        return 'git@' . $this->originUrl . ':' . $this->owner.'/'.$this->repository.'.git';
    }

    /**
     * @phpstan-impure
     *
     * @return true
     * @throws \RuntimeException
     */
    protected function attemptCloneFallback(): bool
    {
        try {
            $this->setupFallbackDriver($this->generateSshUrl());

            return true;
        } catch (\RuntimeException $e) {
            $this->fallbackDriver = null;

            $this->io->writeError(
                '<error>Failed to clone the ' . $this->generateSshUrl() . ' repository, try running in interactive mode'
                    . ' so that you can enter your Bitbucket OAuth consumer credentials</error>'
            );
            throw $e;
        }
    }

    protected function setupFallbackDriver(string $url): void
    {
        $this->fallbackDriver = new GitDriver(
            ['url' => $url],
            $this->io,
            $this->config,
            $this->httpDownloader,
            $this->process
        );
        $this->fallbackDriver->initialize();
    }

    /**
     * @param  array<array{name: string, href: string}> $cloneLinks
     */
    protected function parseCloneUrls(array $cloneLinks): void
    {
        foreach ($cloneLinks as $cloneLink) {
            if ($cloneLink['name'] === 'https') {
                // Format: https://(user@)bitbucket.org/{user}/{repo}
                // Strip username from URL (only present in clone URL's for private repositories)
                $this->cloneHttpsUrl = Preg::replace('/https:\/\/([^@]+@)?/', 'https://', $cloneLink['href']);
            }
        }
    }

    /**
     * @inheritDoc
     */
    public function getRootIdentifier(): string
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getRootIdentifier();
        }

        if (null === $this->rootIdentifier) {
            if (!$this->getRepoData()) {
                if (!$this->fallbackDriver) {
                    throw new \LogicException('A fallback driver should be setup if getRepoData returns false');
                }

                return $this->fallbackDriver->getRootIdentifier();
            }

            if ($this->vcsType !== 'git') {
                throw new \RuntimeException(
                    $this->url.' does not appear to be a git repository, use '.
                    $this->cloneHttpsUrl.' but remember that Bitbucket no longer supports the mercurial repositories. '.
                    'https://bitbucket.org/blog/sunsetting-mercurial-support-in-bitbucket'
                );
            }

            $this->rootIdentifier = $this->repoData['mainbranch']['name'] ?? 'master';
        }

        return $this->rootIdentifier;
    }

    /**
     * @inheritDoc
     */
    public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool
    {
        if (!Preg::isMatch('#^https?://bitbucket\.org/([^/]+)/([^/]+?)(\.git|/?)?$#i', $url)) {
            return false;
        }

        if (!extension_loaded('openssl')) {
            $io->writeError('Skipping Bitbucket git driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE);

            return false;
        }

        return true;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository\Vcs;

use Composer\Config;
use Composer\Downloader\TransportException;
use Composer\Json\JsonFile;
use Composer\Cache;
use Composer\IO\IOInterface;
use Composer\Pcre\Preg;
use Composer\Util\GitHub;
use Composer\Util\Http\Response;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class GitHubDriver extends VcsDriver
{
    /** @var string */
    protected $owner;
    /** @var string */
    protected $repository;
    /** @var array<int|string, string> Map of tag name to identifier */
    protected $tags;
    /** @var array<int|string, string> Map of branch name to identifier */
    protected $branches;
    /** @var string */
    protected $rootIdentifier;
    /** @var mixed[] */
    protected $repoData;
    /** @var bool */
    protected $hasIssues = false;
    /** @var bool */
    protected $isPrivate = false;
    /** @var bool */
    private $isArchived = false;
    /** @var array<int, array{type: string, url: string}>|false|null */
    private $fundingInfo;

    /**
     * Git Driver
     *
     * @var ?GitDriver
     */
    protected $gitDriver = null;

    /**
     * @inheritDoc
     */
    public function initialize(): void
    {
        if (!Preg::isMatch('#^(?:(?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/([^/]+?)(?:\.git|/)?$#', $this->url, $match)) {
            throw new \InvalidArgumentException(sprintf('The GitHub repository URL %s is invalid.', $this->url));
        }

        $this->owner = $match[3];
        $this->repository = $match[4];
        $this->originUrl = strtolower($match[1] ?? (string) $match[2]);
        if ($this->originUrl === 'www.github.com') {
            $this->originUrl = 'github.com';
        }
        $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->owner.'/'.$this->repository);
        $this->cache->setReadOnly($this->config->get('cache-read-only'));

        if ($this->config->get('use-github-api') === false || (isset($this->repoConfig['no-api']) && $this->repoConfig['no-api'])) {
            $this->setupGitDriver($this->url);

            return;
        }

        $this->fetchRootIdentifier();
    }

    public function getRepositoryUrl(): string
    {
        return 'https://'.$this->originUrl.'/'.$this->owner.'/'.$this->repository;
    }

    /**
     * @inheritDoc
     */
    public function getRootIdentifier(): string
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getRootIdentifier();
        }

        return $this->rootIdentifier;
    }

    /**
     * @inheritDoc
     */
    public function getUrl(): string
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getUrl();
        }

        return 'https://' . $this->originUrl . '/'.$this->owner.'/'.$this->repository.'.git';
    }

    protected function getApiUrl(): string
    {
        if ('github.com' === $this->originUrl) {
            $apiUrl = 'api.github.com';
        } else {
            $apiUrl = $this->originUrl . '/api/v3';
        }

        return 'https://' . $apiUrl;
    }

    /**
     * @inheritDoc
     */
    public function getSource(string $identifier): array
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getSource($identifier);
        }
        if ($this->isPrivate) {
            // Private GitHub repositories should be accessed using the
            // SSH version of the URL.
            $url = $this->generateSshUrl();
        } else {
            $url = $this->getUrl();
        }

        return ['type' => 'git', 'url' => $url, 'reference' => $identifier];
    }

    /**
     * @inheritDoc
     */
    public function getDist(string $identifier): ?array
    {
        $url = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/zipball/'.$identifier;

        return ['type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => ''];
    }

    /**
     * @inheritDoc
     */
    public function getComposerInformation(string $identifier): ?array
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getComposerInformation($identifier);
        }

        if (!isset($this->infoCache[$identifier])) {
            if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) {
                $composer = JsonFile::parseJson($res);
            } else {
                $composer = $this->getBaseComposerInformation($identifier);

                if ($this->shouldCache($identifier)) {
                    $this->cache->write($identifier, json_encode($composer));
                }
            }

            if ($composer !== null) {
                // specials for github
                if (isset($composer['support']) && !is_array($composer['support'])) {
                    $composer['support'] = [];
                }
                if (!isset($composer['support']['source'])) {
                    $label = array_search($identifier, $this->getTags()) ?: array_search($identifier, $this->getBranches()) ?: $identifier;
                    $composer['support']['source'] = sprintf('https://%s/%s/%s/tree/%s', $this->originUrl, $this->owner, $this->repository, $label);
                }
                if (!isset($composer['support']['issues']) && $this->hasIssues) {
                    $composer['support']['issues'] = sprintf('https://%s/%s/%s/issues', $this->originUrl, $this->owner, $this->repository);
                }
                if (!isset($composer['abandoned']) && $this->isArchived) {
                    $composer['abandoned'] = true;
                }
                if (!isset($composer['funding']) && $funding = $this->getFundingInfo()) {
                    $composer['funding'] = $funding;
                }
            }

            $this->infoCache[$identifier] = $composer;
        }

        return $this->infoCache[$identifier];
    }

    /**
     * @return array<int, array{type: string, url: string}>|false
     */
    private function getFundingInfo()
    {
        if (null !== $this->fundingInfo) {
            return $this->fundingInfo;
        }

        if ($this->originUrl !== 'github.com') {
            return $this->fundingInfo = false;
        }

        foreach ([$this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/contents/.github/FUNDING.yml', $this->getApiUrl() . '/repos/'.$this->owner.'/.github/contents/FUNDING.yml'] as $file) {
            try {
                $response = $this->httpDownloader->get($file, [
                    'retry-auth-failure' => false,
                ])->decodeJson();
            } catch (TransportException $e) {
                continue;
            }
            if (empty($response['content']) || $response['encoding'] !== 'base64' || !($funding = base64_decode($response['content']))) {
                continue;
            }
            break;
        }
        if (empty($funding)) {
            return $this->fundingInfo = false;
        }

        $result = [];
        $key = null;
        foreach (Preg::split('{\r?\n}', $funding) as $line) {
            $line = trim($line);
            if (Preg::isMatchStrictGroups('{^(\w+)\s*:\s*(.+)$}', $line, $match)) {
                if ($match[2] === '[') {
                    $key = $match[1];
                    continue;
                }
                if (Preg::isMatchStrictGroups('{^\[(.*?)\](?:\s*#.*)?$}', $match[2], $match2)) {
                    foreach (array_map('trim', Preg::split('{[\'"]?\s*,\s*[\'"]?}', $match2[1])) as $item) {
                        $result[] = ['type' => $match[1], 'url' => trim($item, '"\' ')];
                    }
                } elseif (Preg::isMatchStrictGroups('{^([^#].*?)(?:\s+#.*)?$}', $match[2], $match2)) {
                    $result[] = ['type' => $match[1], 'url' => trim($match2[1], '"\' ')];
                }
                $key = null;
            } elseif (Preg::isMatchStrictGroups('{^(\w+)\s*:\s*#\s*$}', $line, $match)) {
                $key = $match[1];
            } elseif ($key !== null && (
                Preg::isMatchStrictGroups('{^-\s*(.+)(?:\s+#.*)?$}', $line, $match)
                || Preg::isMatchStrictGroups('{^(.+),(?:\s*#.*)?$}', $line, $match)
            )) {
                $result[] = ['type' => $key, 'url' => trim($match[1], '"\' ')];
            } elseif ($key !== null && $line === ']') {
                $key = null;
            }
        }

        foreach ($result as $key => $item) {
            switch ($item['type']) {
                case 'community_bridge':
                    $result[$key]['url'] = 'https://funding.communitybridge.org/projects/' . basename($item['url']);
                    break;
                case 'github':
                    $result[$key]['url'] = 'https://github.com/' . basename($item['url']);
                    break;
                case 'issuehunt':
                    $result[$key]['url'] = 'https://issuehunt.io/r/' . $item['url'];
                    break;
                case 'ko_fi':
                    $result[$key]['url'] = 'https://ko-fi.com/' . basename($item['url']);
                    break;
                case 'liberapay':
                    $result[$key]['url'] = 'https://liberapay.com/' . basename($item['url']);
                    break;
                case 'open_collective':
                    $result[$key]['url'] = 'https://opencollective.com/' . basename($item['url']);
                    break;
                case 'patreon':
                    $result[$key]['url'] = 'https://www.patreon.com/' . basename($item['url']);
                    break;
                case 'tidelift':
                    $result[$key]['url'] = 'https://tidelift.com/funding/github/' . $item['url'];
                    break;
                case 'polar':
                    $result[$key]['url'] = 'https://polar.sh/' . basename($item['url']);
                    break;
                case 'buy_me_a_coffee':
                    $result[$key]['url'] = 'https://www.buymeacoffee.com/' . basename($item['url']);
                    break;
                case 'thanks_dev':
                    $result[$key]['url'] = 'https://thanks.dev/' . $item['url'];
                    break;
                case 'otechie':
                    $result[$key]['url'] = 'https://otechie.com/' . basename($item['url']);
                    break;
                case 'custom':
                    $bits = parse_url($item['url']);
                    if ($bits === false) {
                        unset($result[$key]);
                        break;
                    }

                    if (!array_key_exists('scheme', $bits) && !array_key_exists('host', $bits)) {
                        if (Preg::isMatch('{^[a-z0-9-]++\.[a-z]{2,3}$}', $item['url'])) {
                            $result[$key]['url'] = 'https://'.$item['url'];
                            break;
                        }

                        $this->io->writeError('<warning>Funding URL '.$item['url'].' not in a supported format.</warning>');
                        unset($result[$key]);
                        break;
                    }
                    break;
            }
        }

        return $this->fundingInfo = $result;
    }

    /**
     * @inheritDoc
     */
    public function getFileContent(string $file, string $identifier): ?string
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getFileContent($file, $identifier);
        }

        $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/contents/' . $file . '?ref='.urlencode($identifier);
        $resource = $this->getContents($resource)->decodeJson();

        // The GitHub contents API only returns files up to 1MB as base64 encoded files
        // larger files either need be fetched with a raw accept header or by using the git blob endpoint
        if ((!isset($resource['content']) || $resource['content'] === '') && $resource['encoding'] === 'none' && isset($resource['git_url'])) {
            $resource = $this->getContents($resource['git_url'])->decodeJson();
        }

        if (!isset($resource['content']) || $resource['encoding'] !== 'base64' || false === ($content = base64_decode($resource['content']))) {
            throw new \RuntimeException('Could not retrieve ' . $file . ' for '.$identifier);
        }

        return $content;
    }

    /**
     * @inheritDoc
     */
    public function getChangeDate(string $identifier): ?\DateTimeImmutable
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getChangeDate($identifier);
        }

        $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/commits/'.urlencode($identifier);
        $commit = $this->getContents($resource)->decodeJson();

        return new \DateTimeImmutable($commit['commit']['committer']['date']);
    }

    /**
     * @inheritDoc
     */
    public function getTags(): array
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getTags();
        }
        if (null === $this->tags) {
            $tags = [];
            $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/tags?per_page=100';

            do {
                $response = $this->getContents($resource);
                $tagsData = $response->decodeJson();
                foreach ($tagsData as $tag) {
                    $tags[$tag['name']] = $tag['commit']['sha'];
                }

                $resource = $this->getNextPage($response);
            } while ($resource);

            $this->tags = $tags;
        }

        return $this->tags;
    }

    /**
     * @inheritDoc
     */
    public function getBranches(): array
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getBranches();
        }
        if (null === $this->branches) {
            $branches = [];
            $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/git/refs/heads?per_page=100';

            do {
                $response = $this->getContents($resource);
                $branchData = $response->decodeJson();
                foreach ($branchData as $branch) {
                    $name = substr($branch['ref'], 11);
                    if ($name !== 'gh-pages') {
                        $branches[$name] = $branch['object']['sha'];
                    }
                }

                $resource = $this->getNextPage($response);
            } while ($resource);

            $this->branches = $branches;
        }

        return $this->branches;
    }

    /**
     * @inheritDoc
     */
    public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool
    {
        if (!Preg::isMatch('#^((?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/([^/]+?)(?:\.git|/)?$#', $url, $matches)) {
            return false;
        }

        $originUrl = $matches[2] ?? (string) $matches[3];
        if (!in_array(strtolower(Preg::replace('{^www\.}i', '', $originUrl)), $config->get('github-domains'))) {
            return false;
        }

        if (!extension_loaded('openssl')) {
            $io->writeError('Skipping GitHub driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE);

            return false;
        }

        return true;
    }

    /**
     * Gives back the loaded <github-api>/repos/<owner>/<repo> result
     *
     * @return mixed[]|null
     */
    public function getRepoData(): ?array
    {
        $this->fetchRootIdentifier();

        return $this->repoData;
    }

    /**
     * Generate an SSH URL
     */
    protected function generateSshUrl(): string
    {
        if (false !== strpos($this->originUrl, ':')) {
            return 'ssh://git@' . $this->originUrl . '/'.$this->owner.'/'.$this->repository.'.git';
        }

        return 'git@' . $this->originUrl . ':'.$this->owner.'/'.$this->repository.'.git';
    }

    /**
     * @inheritDoc
     */
    protected function getContents(string $url, bool $fetchingRepoData = false): Response
    {
        try {
            return parent::getContents($url);
        } catch (TransportException $e) {
            $gitHubUtil = new GitHub($this->io, $this->config, $this->process, $this->httpDownloader);

            switch ($e->getCode()) {
                case 401:
                case 404:
                    // try to authorize only if we are fetching the main /repos/foo/bar data, otherwise it must be a real 404
                    if (!$fetchingRepoData) {
                        throw $e;
                    }

                    if ($gitHubUtil->authorizeOAuth($this->originUrl)) {
                        return parent::getContents($url);
                    }

                    if (!$this->io->isInteractive()) {
                        $this->attemptCloneFallback();

                        return new Response(['url' => 'dummy'], 200, [], 'null');
                    }

                    $scopesIssued = [];
                    $scopesNeeded = [];
                    if ($headers = $e->getHeaders()) {
                        if ($scopes = Response::findHeaderValue($headers, 'X-OAuth-Scopes')) {
                            $scopesIssued = explode(' ', $scopes);
                        }
                        if ($scopes = Response::findHeaderValue($headers, 'X-Accepted-OAuth-Scopes')) {
                            $scopesNeeded = explode(' ', $scopes);
                        }
                    }
                    $scopesFailed = array_diff($scopesNeeded, $scopesIssued);
                    // non-authenticated requests get no scopesNeeded, so ask for credentials
                    // authenticated requests which failed some scopes should ask for new credentials too
                    if (!$headers || !count($scopesNeeded) || count($scopesFailed)) {
                        $gitHubUtil->authorizeOAuthInteractively($this->originUrl, 'Your GitHub credentials are required to fetch private repository metadata (<info>'.$this->url.'</info>)');
                    }

                    return parent::getContents($url);

                case 403:
                    if (!$this->io->hasAuthentication($this->originUrl) && $gitHubUtil->authorizeOAuth($this->originUrl)) {
                        return parent::getContents($url);
                    }

                    if (!$this->io->isInteractive() && $fetchingRepoData) {
                        $this->attemptCloneFallback();

                        return new Response(['url' => 'dummy'], 200, [], 'null');
                    }

                    $rateLimited = $gitHubUtil->isRateLimited((array) $e->getHeaders());

                    if (!$this->io->hasAuthentication($this->originUrl)) {
                        if (!$this->io->isInteractive()) {
                            $this->io->writeError('<error>GitHub API limit exhausted. Failed to get metadata for the '.$this->url.' repository, try running in interactive mode so that you can enter your GitHub credentials to increase the API limit</error>');
                            throw $e;
                        }

                        $gitHubUtil->authorizeOAuthInteractively($this->originUrl, 'API limit exhausted. Enter your GitHub credentials to get a larger API limit (<info>'.$this->url.'</info>)');

                        return parent::getContents($url);
                    }

                    if ($rateLimited) {
                        $rateLimit = $gitHubUtil->getRateLimit($e->getHeaders());
                        $this->io->writeError(sprintf(
                            '<error>GitHub API limit (%d calls/hr) is exhausted. You are already authorized so you have to wait until %s before doing more requests</error>',
                            $rateLimit['limit'],
                            $rateLimit['reset']
                        ));
                    }

                    throw $e;

                default:
                    throw $e;
            }
        }
    }

    /**
     * Fetch root identifier from GitHub
     *
     * @throws TransportException
     */
    protected function fetchRootIdentifier(): void
    {
        if ($this->repoData) {
            return;
        }

        $repoDataUrl = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository;

        try {
            $this->repoData = $this->getContents($repoDataUrl, true)->decodeJson();
        } catch (TransportException $e) {
            if ($e->getCode() === 499) {
                $this->attemptCloneFallback();
            } else {
                throw $e;
            }
        }
        if (null === $this->repoData && null !== $this->gitDriver) {
            return;
        }

        $this->owner = $this->repoData['owner']['login'];
        $this->repository = $this->repoData['name'];

        $this->isPrivate = !empty($this->repoData['private']);
        if (isset($this->repoData['default_branch'])) {
            $this->rootIdentifier = $this->repoData['default_branch'];
        } elseif (isset($this->repoData['master_branch'])) {
            $this->rootIdentifier = $this->repoData['master_branch'];
        } else {
            $this->rootIdentifier = 'master';
        }
        $this->hasIssues = !empty($this->repoData['has_issues']);
        $this->isArchived = !empty($this->repoData['archived']);
    }

    /**
     * @phpstan-impure
     *
     * @return true
     * @throws \RuntimeException
     */
    protected function attemptCloneFallback(): bool
    {
        $this->isPrivate = true;

        try {
            // If this repository may be private (hard to say for sure,
            // GitHub returns 404 for private repositories) and we
            // cannot ask for authentication credentials (because we
            // are not interactive) then we fallback to GitDriver.
            $this->setupGitDriver($this->generateSshUrl());

            return true;
        } catch (\RuntimeException $e) {
            $this->gitDriver = null;

            $this->io->writeError('<error>Failed to clone the '.$this->generateSshUrl().' repository, try running in interactive mode so that you can enter your GitHub credentials</error>');
            throw $e;
        }
    }

    protected function setupGitDriver(string $url): void
    {
        $this->gitDriver = new GitDriver(
            ['url' => $url],
            $this->io,
            $this->config,
            $this->httpDownloader,
            $this->process
        );
        $this->gitDriver->initialize();
    }

    protected function getNextPage(Response $response): ?string
    {
        $header = $response->getHeader('link');
        if (!$header) {
            return null;
        }

        $links = explode(',', $header);
        foreach ($links as $link) {
            if (Preg::isMatch('{<(.+?)>; *rel="next"}', $link, $match)) {
                return $match[1];
            }
        }

        return null;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository\Vcs;

use Composer\Config;
use Composer\Cache;
use Composer\Pcre\Preg;
use Composer\Util\Hg as HgUtils;
use Composer\Util\ProcessExecutor;
use Composer\Util\Filesystem;
use Composer\IO\IOInterface;
use Composer\Util\Url;

/**
 * @author Per Bernhardt <plb@webfactory.de>
 */
class HgDriver extends VcsDriver
{
    /** @var array<int|string, string> Map of tag name to identifier */
    protected $tags;
    /** @var array<int|string, string> Map of branch name to identifier */
    protected $branches;
    /** @var string */
    protected $rootIdentifier;
    /** @var string */
    protected $repoDir;

    /**
     * @inheritDoc
     */
    public function initialize(): void
    {
        if (Filesystem::isLocalPath($this->url)) {
            $this->repoDir = $this->url;
        } else {
            if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) {
                throw new \RuntimeException('HgDriver requires a usable cache directory, and it looks like you set it to be disabled');
            }

            $cacheDir = $this->config->get('cache-vcs-dir');
            $this->repoDir = $cacheDir . '/' . Preg::replace('{[^a-z0-9]}i', '-', Url::sanitize($this->url)) . '/';

            $fs = new Filesystem();
            $fs->ensureDirectoryExists($cacheDir);

            if (!is_writable(dirname($this->repoDir))) {
                throw new \RuntimeException('Can not clone '.$this->url.' to access package information. The "'.$cacheDir.'" directory is not writable by the current user.');
            }

            // Ensure we are allowed to use this URL by config
            $this->config->prohibitUrlByConfig($this->url, $this->io);

            $hgUtils = new HgUtils($this->io, $this->config, $this->process);

            // update the repo if it is a valid hg repository
            if (is_dir($this->repoDir) && 0 === $this->process->execute(['hg', 'summary'], $output, $this->repoDir)) {
                if (0 !== $this->process->execute(['hg', 'pull'], $output, $this->repoDir)) {
                    $this->io->writeError('<error>Failed to update '.$this->url.', package information from this repository may be outdated ('.$this->process->getErrorOutput().')</error>');
                }
            } else {
                // clean up directory and do a fresh clone into it
                $fs->removeDirectory($this->repoDir);

                $repoDir = $this->repoDir;
                $command = static function ($url) use ($repoDir): array {
                    return ['hg', 'clone', '--noupdate', '--', $url, $repoDir];
                };

                $hgUtils->runCommand($command, $this->url, null);
            }
        }

        $this->getTags();
        $this->getBranches();
    }

    /**
     * @inheritDoc
     */
    public function getRootIdentifier(): string
    {
        if (null === $this->rootIdentifier) {
            $this->process->execute(['hg', 'tip', '--template', '{node}'], $output, $this->repoDir);
            $output = $this->process->splitLines($output);
            $this->rootIdentifier = $output[0];
        }

        return $this->rootIdentifier;
    }

    /**
     * @inheritDoc
     */
    public function getUrl(): string
    {
        return $this->url;
    }

    /**
     * @inheritDoc
     */
    public function getSource(string $identifier): array
    {
        return ['type' => 'hg', 'url' => $this->getUrl(), 'reference' => $identifier];
    }

    /**
     * @inheritDoc
     */
    public function getDist(string $identifier): ?array
    {
        return null;
    }

    /**
     * @inheritDoc
     */
    public function getFileContent(string $file, string $identifier): ?string
    {
        if (isset($identifier[0]) && $identifier[0] === '-') {
            throw new \RuntimeException('Invalid hg identifier detected. Identifier must not start with a -, given: ' . $identifier);
        }

        $resource = ['hg', 'cat', '-r', $identifier, '--', $file];
        $this->process->execute($resource, $content, $this->repoDir);

        if (!trim($content)) {
            return null;
        }

        return $content;
    }

    /**
     * @inheritDoc
     */
    public function getChangeDate(string $identifier): ?\DateTimeImmutable
    {
        $this->process->execute(
            ['hg', 'log', '--template', '{date|rfc3339date}', '-r', $identifier],
            $output,
            $this->repoDir
        );

        return new \DateTimeImmutable(trim($output), new \DateTimeZone('UTC'));
    }

    /**
     * @inheritDoc
     */
    public function getTags(): array
    {
        if (null === $this->tags) {
            $tags = [];

            $this->process->execute(['hg', 'tags'], $output, $this->repoDir);
            foreach ($this->process->splitLines($output) as $tag) {
                if ($tag && Preg::isMatchStrictGroups('(^([^\s]+)\s+\d+:(.*)$)', $tag, $match)) {
                    $tags[$match[1]] = $match[2];
                }
            }
            unset($tags['tip']);

            $this->tags = $tags;
        }

        return $this->tags;
    }

    /**
     * @inheritDoc
     */
    public function getBranches(): array
    {
        if (null === $this->branches) {
            $branches = [];
            $bookmarks = [];

            $this->process->execute(['hg', 'branches'], $output, $this->repoDir);
            foreach ($this->process->splitLines($output) as $branch) {
                if ($branch && Preg::isMatchStrictGroups('(^([^\s]+)\s+\d+:([a-f0-9]+))', $branch, $match) && $match[1][0] !== '-') {
                    $branches[$match[1]] = $match[2];
                }
            }

            $this->process->execute(['hg', 'bookmarks'], $output, $this->repoDir);
            foreach ($this->process->splitLines($output) as $branch) {
                if ($branch && Preg::isMatchStrictGroups('(^(?:[\s*]*)([^\s]+)\s+\d+:(.*)$)', $branch, $match) && $match[1][0] !== '-') {
                    $bookmarks[$match[1]] = $match[2];
                }
            }

            // Branches will have preference over bookmarks
            $this->branches = array_merge($bookmarks, $branches);
        }

        return $this->branches;
    }

    /**
     * @inheritDoc
     */
    public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool
    {
        if (Preg::isMatch('#(^(?:https?|ssh)://(?:[^@]+@)?bitbucket.org|https://(?:.*?)\.kilnhg.com)#i', $url)) {
            return true;
        }

        // local filesystem
        if (Filesystem::isLocalPath($url)) {
            $url = Filesystem::getPlatformPath($url);
            if (!is_dir($url)) {
                return false;
            }

            $process = new ProcessExecutor($io);
            // check whether there is a hg repo in that path
            if ($process->execute(['hg', 'summary'], $output, $url) === 0) {
                return true;
            }
        }

        if (!$deep) {
            return false;
        }

        $process = new ProcessExecutor($io);
        $exit = $process->execute(['hg', 'identify', '--', $url], $ignored);

        return $exit === 0;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository\Vcs;

use Composer\Config;
use Composer\Cache;
use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
use Composer\Downloader\TransportException;
use Composer\Pcre\Preg;
use Composer\Util\HttpDownloader;
use Composer\Util\GitLab;
use Composer\Util\Http\Response;

/**
 * Driver for GitLab API, use the Git driver for local checkouts.
 *
 * @author Henrik Bjørnskov <henrik@bjrnskov.dk>
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class GitLabDriver extends VcsDriver
{
    /**
     * @var string
     * @phpstan-var 'https'|'http'
     */
    private $scheme;
    /** @var string */
    private $namespace;
    /** @var string */
    private $repository;

    /**
     * @var mixed[] Project data returned by GitLab API
     */
    private $project = null;

    /**
     * @var array<string|int, mixed[]> Keeps commits returned by GitLab API as commit id => info
     */
    private $commits = [];

    /** @var array<int|string, string> Map of tag name to identifier */
    private $tags;

    /** @var array<int|string, string> Map of branch name to identifier */
    private $branches;

    /**
     * Git Driver
     *
     * @var ?GitDriver
     */
    protected $gitDriver = null;

    /**
     * Protocol to force use of for repository URLs.
     *
     * @var string One of ssh, http
     */
    protected $protocol;

    /**
     * Defaults to true unless we can make sure it is public
     *
     * @var bool defines whether the repo is private or not
     */
    private $isPrivate = true;

    /**
     * @var bool true if the origin has a port number or a path component in it
     */
    private $hasNonstandardOrigin = false;

    public const URL_REGEX = '#^(?:(?P<scheme>https?)://(?P<domain>.+?)(?::(?P<port>[0-9]+))?/|git@(?P<domain2>[^:]+):)(?P<parts>.+)/(?P<repo>[^/]+?)(?:\.git|/)?$#';

    /**
     * Extracts information from the repository url.
     *
     * SSH urls use https by default. Set "secure-http": false on the repository config to use http instead.
     *
     * @inheritDoc
     */
    public function initialize(): void
    {
        if (!Preg::isMatch(self::URL_REGEX, $this->url, $match)) {
            throw new \InvalidArgumentException(sprintf('The GitLab repository URL %s is invalid. It must be the HTTP URL of a GitLab project.', $this->url));
        }

        $guessedDomain = $match['domain'] ?? (string) $match['domain2'];
        $configuredDomains = $this->config->get('gitlab-domains');
        $urlParts = explode('/', $match['parts']);

        $this->scheme = in_array($match['scheme'], ['https', 'http'], true)
            ? $match['scheme']
            : (isset($this->repoConfig['secure-http']) && $this->repoConfig['secure-http'] === false ? 'http' : 'https')
        ;
        $origin = self::determineOrigin($configuredDomains, $guessedDomain, $urlParts, $match['port']);
        if (false === $origin) {
            throw new \LogicException('It should not be possible to create a gitlab driver with an unparsable origin URL ('.$this->url.')');
        }
        $this->originUrl = $origin;

        if (is_string($protocol = $this->config->get('gitlab-protocol'))) {
            // https treated as a synonym for http.
            if (!in_array($protocol, ['git', 'http', 'https'], true)) {
                throw new \RuntimeException('gitlab-protocol must be one of git, http.');
            }
            $this->protocol = $protocol === 'git' ? 'ssh' : 'http';
        }

        if (false !== strpos($this->originUrl, ':') || false !== strpos($this->originUrl, '/')) {
            $this->hasNonstandardOrigin = true;
        }

        $this->namespace = implode('/', $urlParts);
        $this->repository = Preg::replace('#(\.git)$#', '', $match['repo']);

        $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.$this->originUrl.'/'.$this->namespace.'/'.$this->repository);
        $this->cache->setReadOnly($this->config->get('cache-read-only'));

        $this->fetchProject();
    }

    /**
     * Updates the HttpDownloader instance.
     * Mainly useful for tests.
     *
     * @internal
     */
    public function setHttpDownloader(HttpDownloader $httpDownloader): void
    {
        $this->httpDownloader = $httpDownloader;
    }

    /**
     * @inheritDoc
     */
    public function getComposerInformation(string $identifier): ?array
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getComposerInformation($identifier);
        }

        if (!isset($this->infoCache[$identifier])) {
            if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) {
                $composer = JsonFile::parseJson($res);
            } else {
                $composer = $this->getBaseComposerInformation($identifier);

                if ($this->shouldCache($identifier)) {
                    $this->cache->write($identifier, json_encode($composer));
                }
            }

            if (null !== $composer) {
                // specials for gitlab (this data is only available if authentication is provided)
                if (isset($composer['support']) && !is_array($composer['support'])) {
                    $composer['support'] = [];
                }
                if (!isset($composer['support']['source']) && isset($this->project['web_url'])) {
                    $label = array_search($identifier, $this->getTags(), true) ?: array_search($identifier, $this->getBranches(), true) ?: $identifier;
                    $composer['support']['source'] = sprintf('%s/-/tree/%s', $this->project['web_url'], $label);
                }
                if (!isset($composer['support']['issues']) && !empty($this->project['issues_enabled']) && isset($this->project['web_url'])) {
                    $composer['support']['issues'] = sprintf('%s/-/issues', $this->project['web_url']);
                }
                if (!isset($composer['abandoned']) && !empty($this->project['archived'])) {
                    $composer['abandoned'] = true;
                }
            }

            $this->infoCache[$identifier] = $composer;
        }

        return $this->infoCache[$identifier];
    }

    /**
     * @inheritDoc
     */
    public function getFileContent(string $file, string $identifier): ?string
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getFileContent($file, $identifier);
        }

        // Convert the root identifier to a cacheable commit id
        if (!Preg::isMatch('{[a-f0-9]{40}}i', $identifier)) {
            $branches = $this->getBranches();
            if (isset($branches[$identifier])) {
                $identifier = $branches[$identifier];
            }
        }

        $resource = $this->getApiUrl().'/repository/files/'.$this->urlEncodeAll($file).'/raw?ref='.$identifier;

        try {
            $content = $this->getContents($resource)->getBody();
        } catch (TransportException $e) {
            if ($e->getCode() !== 404) {
                throw $e;
            }

            return null;
        }

        return $content;
    }

    /**
     * @inheritDoc
     */
    public function getChangeDate(string $identifier): ?\DateTimeImmutable
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getChangeDate($identifier);
        }

        if (isset($this->commits[$identifier])) {
            return new \DateTimeImmutable($this->commits[$identifier]['committed_date']);
        }

        return null;
    }

    public function getRepositoryUrl(): string
    {
        if ($this->protocol) {
            return $this->project["{$this->protocol}_url_to_repo"];
        }

        return $this->isPrivate ? $this->project['ssh_url_to_repo'] : $this->project['http_url_to_repo'];
    }

    /**
     * @inheritDoc
     */
    public function getUrl(): string
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getUrl();
        }

        return $this->project['web_url'];
    }

    /**
     * @inheritDoc
     */
    public function getDist(string $identifier): ?array
    {
        $url = $this->getApiUrl().'/repository/archive.zip?sha='.$identifier;

        return ['type' => 'zip', 'url' => $url, 'reference' => $identifier, 'shasum' => ''];
    }

    /**
     * @inheritDoc
     */
    public function getSource(string $identifier): array
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getSource($identifier);
        }

        return ['type' => 'git', 'url' => $this->getRepositoryUrl(), 'reference' => $identifier];
    }

    /**
     * @inheritDoc
     */
    public function getRootIdentifier(): string
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getRootIdentifier();
        }

        return $this->project['default_branch'];
    }

    /**
     * @inheritDoc
     */
    public function getBranches(): array
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getBranches();
        }

        if (null === $this->branches) {
            $this->branches = $this->getReferences('branches');
        }

        return $this->branches;
    }

    /**
     * @inheritDoc
     */
    public function getTags(): array
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getTags();
        }

        if (null === $this->tags) {
            $this->tags = $this->getReferences('tags');
        }

        return $this->tags;
    }

    /**
     * @return string Base URL for GitLab API v3
     */
    public function getApiUrl(): string
    {
        return $this->scheme.'://'.$this->originUrl.'/api/v4/projects/'.$this->urlEncodeAll($this->namespace).'%2F'.$this->urlEncodeAll($this->repository);
    }

    /**
     * Urlencode all non alphanumeric characters. rawurlencode() can not be used as it does not encode `.`
     */
    private function urlEncodeAll(string $string): string
    {
        $encoded = '';
        for ($i = 0; isset($string[$i]); $i++) {
            $character = $string[$i];
            if (!ctype_alnum($character) && !in_array($character, ['-', '_'], true)) {
                $character = '%' . sprintf('%02X', ord($character));
            }
            $encoded .= $character;
        }

        return $encoded;
    }

    /**
     * @return string[] where keys are named references like tags or branches and the value a sha
     */
    protected function getReferences(string $type): array
    {
        $perPage = 100;
        $resource = $this->getApiUrl().'/repository/'.$type.'?per_page='.$perPage;

        $references = [];
        do {
            $response = $this->getContents($resource);
            $data = $response->decodeJson();

            foreach ($data as $datum) {
                $references[$datum['name']] = $datum['commit']['id'];

                // Keep the last commit date of a reference to avoid
                // unnecessary API call when retrieving the composer file.
                $this->commits[$datum['commit']['id']] = $datum['commit'];
            }

            if (count($data) >= $perPage) {
                $resource = $this->getNextPage($response);
            } else {
                $resource = false;
            }
        } while ($resource);

        return $references;
    }

    protected function fetchProject(): void
    {
        if (!is_null($this->project)) {
            return;
        }

        // we need to fetch the default branch from the api
        $resource = $this->getApiUrl();
        $this->project = $this->getContents($resource, true)->decodeJson();
        if (isset($this->project['visibility'])) {
            $this->isPrivate = $this->project['visibility'] !== 'public';
        } else {
            // client is not authenticated, therefore repository has to be public
            $this->isPrivate = false;
        }
    }

    /**
     * @phpstan-impure
     *
     * @return true
     * @throws \RuntimeException
     */
    protected function attemptCloneFallback(): bool
    {
        if ($this->isPrivate === false) {
            $url = $this->generatePublicUrl();
        } else {
            $url = $this->generateSshUrl();
        }

        try {
            // If this repository may be private and we
            // cannot ask for authentication credentials (because we
            // are not interactive) then we fallback to GitDriver.
            $this->setupGitDriver($url);

            return true;
        } catch (\RuntimeException $e) {
            $this->gitDriver = null;

            $this->io->writeError('<error>Failed to clone the '.$url.' repository, try running in interactive mode so that you can enter your credentials</error>');
            throw $e;
        }
    }

    /**
     * Generate an SSH URL
     */
    protected function generateSshUrl(): string
    {
        if ($this->hasNonstandardOrigin) {
            return 'ssh://git@'.$this->originUrl.'/'.$this->namespace.'/'.$this->repository.'.git';
        }

        return 'git@' . $this->originUrl . ':'.$this->namespace.'/'.$this->repository.'.git';
    }

    protected function generatePublicUrl(): string
    {
        return $this->scheme . '://' . $this->originUrl . '/'.$this->namespace.'/'.$this->repository.'.git';
    }

    protected function setupGitDriver(string $url): void
    {
        $this->gitDriver = new GitDriver(
            ['url' => $url],
            $this->io,
            $this->config,
            $this->httpDownloader,
            $this->process
        );
        $this->gitDriver->initialize();
    }

    /**
     * @inheritDoc
     */
    protected function getContents(string $url, bool $fetchingRepoData = false): Response
    {
        try {
            $response = parent::getContents($url);

            if ($fetchingRepoData) {
                $json = $response->decodeJson();

                // Accessing the API with a token with Guest (10) or Planner (15) access will return
                // more data than unauthenticated access but no default_branch data
                // accessing files via the API will then also fail
                if (!isset($json['default_branch']) && isset($json['permissions'])) {
                    $this->isPrivate = $json['visibility'] !== 'public';

                    $moreThanGuestAccess = false;
                    // Check both access levels (e.g. project, group)
                    // - value will be null if no access is set
                    // - value will be array with key access_level if set
                    foreach ($json['permissions'] as $permission) {
                        if ($permission && $permission['access_level'] >= 20) {
                            $moreThanGuestAccess = true;
                        }
                    }

                    if (!$moreThanGuestAccess) {
                        $this->io->writeError('<warning>GitLab token with Guest or Planner only access detected</warning>');

                        $this->attemptCloneFallback();

                        return new Response(['url' => 'dummy'], 200, [], 'null');
                    }
                }

                // force auth as the unauthenticated version of the API is broken
                if (!isset($json['default_branch'])) {
                    // GitLab allows you to disable the repository inside a project to use a project only for issues and wiki
                    if (isset($json['repository_access_level']) && $json['repository_access_level'] === 'disabled') {
                        throw new TransportException('The GitLab repository is disabled in the project', 400);
                    }

                    if (!empty($json['id'])) {
                        $this->isPrivate = false;
                    }

                    throw new TransportException('GitLab API seems to not be authenticated as it did not return a default_branch', 401);
                }
            }

            return $response;
        } catch (TransportException $e) {
            $gitLabUtil = new GitLab($this->io, $this->config, $this->process, $this->httpDownloader);

            switch ($e->getCode()) {
                case 401:
                case 404:
                    // try to authorize only if we are fetching the main /repos/foo/bar data, otherwise it must be a real 404
                    if (!$fetchingRepoData) {
                        throw $e;
                    }

                    if ($gitLabUtil->authorizeOAuth($this->originUrl)) {
                        return parent::getContents($url);
                    }

                    if ($gitLabUtil->isOAuthExpired($this->originUrl) && $gitLabUtil->authorizeOAuthRefresh($this->scheme, $this->originUrl)) {
                        return parent::getContents($url);
                    }

                    if (!$this->io->isInteractive()) {
                        $this->attemptCloneFallback();

                        return new Response(['url' => 'dummy'], 200, [], 'null');
                    }
                    $this->io->writeError('<warning>Failed to download ' . $this->namespace . '/' . $this->repository . ':' . $e->getMessage() . '</warning>');
                    $gitLabUtil->authorizeOAuthInteractively($this->scheme, $this->originUrl, 'Your credentials are required to fetch private repository metadata (<info>'.$this->url.'</info>)');

                    return parent::getContents($url);

                case 403:
                    if (!$this->io->hasAuthentication($this->originUrl) && $gitLabUtil->authorizeOAuth($this->originUrl)) {
                        return parent::getContents($url);
                    }

                    if (!$this->io->isInteractive() && $fetchingRepoData) {
                        $this->attemptCloneFallback();

                        return new Response(['url' => 'dummy'], 200, [], 'null');
                    }

                    throw $e;

                default:
                    throw $e;
            }
        }
    }

    /**
     * Uses the config `gitlab-domains` to see if the driver supports the url for the
     * repository given.
     *
     * @inheritDoc
     */
    public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool
    {
        if (!Preg::isMatch(self::URL_REGEX, $url, $match)) {
            return false;
        }

        $scheme = $match['scheme'];
        $guessedDomain = $match['domain'] ?? (string) $match['domain2'];
        $urlParts = explode('/', $match['parts']);

        if (false === self::determineOrigin($config->get('gitlab-domains'), $guessedDomain, $urlParts, $match['port'])) {
            return false;
        }

        if ('https' === $scheme && !extension_loaded('openssl')) {
            $io->writeError('Skipping GitLab driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE);

            return false;
        }

        return true;
    }

    /**
     * Gives back the loaded <gitlab-api>/projects/<owner>/<repo> result
     *
     * @return mixed[]|null
     */
    public function getRepoData(): ?array
    {
        $this->fetchProject();

        return $this->project;
    }

    protected function getNextPage(Response $response): ?string
    {
        $header = $response->getHeader('link');

        $links = explode(',', $header);
        foreach ($links as $link) {
            if (Preg::isMatchStrictGroups('{<(.+?)>; *rel="next"}', $link, $match)) {
                return $match[1];
            }
        }

        return null;
    }

    /**
     * @param  array<string> $configuredDomains
     * @param  array<string> $urlParts
     * @param string         $portNumber
     *
     * @return string|false
     */
    private static function determineOrigin(array $configuredDomains, string $guessedDomain, array &$urlParts, ?string $portNumber)
    {
        $guessedDomain = strtolower($guessedDomain);

        if (in_array($guessedDomain, $configuredDomains) || (null !== $portNumber && in_array($guessedDomain.':'.$portNumber, $configuredDomains))) {
            if (null !== $portNumber) {
                return $guessedDomain.':'.$portNumber;
            }

            return $guessedDomain;
        }

        if (null !== $portNumber) {
            $guessedDomain .= ':'.$portNumber;
        }

        while (null !== ($part = array_shift($urlParts))) {
            $guessedDomain .= '/' . $part;

            if (in_array($guessedDomain, $configuredDomains) || (null !== $portNumber && in_array(Preg::replace('{:\d+}', '', $guessedDomain), $configuredDomains))) {
                return $guessedDomain;
            }
        }

        return false;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository\Vcs;

use Composer\Cache;
use Composer\Config;
use Composer\Pcre\Preg;
use Composer\Util\ProcessExecutor;
use Composer\Util\Filesystem;
use Composer\IO\IOInterface;

/**
 * @author BohwaZ <http://bohwaz.net/>
 */
class FossilDriver extends VcsDriver
{
    /** @var array<int|string, string> Map of tag name to identifier */
    protected $tags;
    /** @var array<int|string, string> Map of branch name to identifier */
    protected $branches;
    /** @var ?string */
    protected $rootIdentifier = null;
    /** @var ?string */
    protected $repoFile = null;
    /** @var string */
    protected $checkoutDir;

    /**
     * @inheritDoc
     */
    public function initialize(): void
    {
        // Make sure fossil is installed and reachable.
        $this->checkFossil();

        // Ensure we are allowed to use this URL by config.
        $this->config->prohibitUrlByConfig($this->url, $this->io);

        // Only if url points to a locally accessible directory, assume it's the checkout directory.
        // Otherwise, it should be something fossil can clone from.
        if (Filesystem::isLocalPath($this->url) && is_dir($this->url)) {
            $this->checkoutDir = $this->url;
        } else {
            if (!Cache::isUsable($this->config->get('cache-repo-dir')) || !Cache::isUsable($this->config->get('cache-vcs-dir'))) {
                throw new \RuntimeException('FossilDriver requires a usable cache directory, and it looks like you set it to be disabled');
            }

            $localName = Preg::replace('{[^a-z0-9]}i', '-', $this->url);
            $this->repoFile = $this->config->get('cache-repo-dir') . '/' . $localName . '.fossil';
            $this->checkoutDir = $this->config->get('cache-vcs-dir') . '/' . $localName . '/';

            $this->updateLocalRepo();
        }

        $this->getTags();
        $this->getBranches();
    }

    /**
     * Check that fossil can be invoked via command line.
     */
    protected function checkFossil(): void
    {
        if (0 !== $this->process->execute(['fossil', 'version'], $ignoredOutput)) {
            throw new \RuntimeException("fossil was not found, check that it is installed and in your PATH env.\n\n" . $this->process->getErrorOutput());
        }
    }

    /**
     * Clone or update existing local fossil repository.
     */
    protected function updateLocalRepo(): void
    {
        assert($this->repoFile !== null);

        $fs = new Filesystem();
        $fs->ensureDirectoryExists($this->checkoutDir);

        if (!is_writable(dirname($this->checkoutDir))) {
            throw new \RuntimeException('Can not clone '.$this->url.' to access package information. The "'.$this->checkoutDir.'" directory is not writable by the current user.');
        }

        // update the repo if it is a valid fossil repository
        if (is_file($this->repoFile) && is_dir($this->checkoutDir) && 0 === $this->process->execute(['fossil', 'info'], $output, $this->checkoutDir)) {
            if (0 !== $this->process->execute(['fossil', 'pull'], $output, $this->checkoutDir)) {
                $this->io->writeError('<error>Failed to update '.$this->url.', package information from this repository may be outdated ('.$this->process->getErrorOutput().')</error>');
            }
        } else {
            // clean up directory and do a fresh clone into it
            $fs->removeDirectory($this->checkoutDir);
            $fs->remove($this->repoFile);

            $fs->ensureDirectoryExists($this->checkoutDir);

            if (0 !== $this->process->execute(['fossil', 'clone', '--', $this->url, $this->repoFile], $output)) {
                $output = $this->process->getErrorOutput();

                throw new \RuntimeException('Failed to clone '.$this->url.' to repository ' . $this->repoFile . "\n\n" .$output);
            }

            if (0 !== $this->process->execute(['fossil', 'open', '--nested', '--', $this->repoFile], $output, $this->checkoutDir)) {
                $output = $this->process->getErrorOutput();

                throw new \RuntimeException('Failed to open repository '.$this->repoFile.' in ' . $this->checkoutDir . "\n\n" .$output);
            }
        }
    }

    /**
     * @inheritDoc
     */
    public function getRootIdentifier(): string
    {
        if (null === $this->rootIdentifier) {
            $this->rootIdentifier = 'trunk';
        }

        return $this->rootIdentifier;
    }

    /**
     * @inheritDoc
     */
    public function getUrl(): string
    {
        return $this->url;
    }

    /**
     * @inheritDoc
     */
    public function getSource(string $identifier): array
    {
        return ['type' => 'fossil', 'url' => $this->getUrl(), 'reference' => $identifier];
    }

    /**
     * @inheritDoc
     */
    public function getDist(string $identifier): ?array
    {
        return null;
    }

    /**
     * @inheritDoc
     */
    public function getFileContent(string $file, string $identifier): ?string
    {
        $this->process->execute(['fossil', 'cat', '-r', $identifier, '--', $file], $content, $this->checkoutDir);

        if ('' === trim($content)) {
            return null;
        }

        return $content;
    }

    /**
     * @inheritDoc
     */
    public function getChangeDate(string $identifier): ?\DateTimeImmutable
    {
        $this->process->execute(['fossil', 'finfo', '-b', '-n', '1', 'composer.json'], $output, $this->checkoutDir);
        [, $date] = explode(' ', trim($output), 3);

        return new \DateTimeImmutable($date, new \DateTimeZone('UTC'));
    }

    /**
     * @inheritDoc
     */
    public function getTags(): array
    {
        if (null === $this->tags) {
            $tags = [];

            $this->process->execute(['fossil', 'tag', 'list'], $output, $this->checkoutDir);
            foreach ($this->process->splitLines($output) as $tag) {
                $tags[$tag] = $tag;
            }

            $this->tags = $tags;
        }

        return $this->tags;
    }

    /**
     * @inheritDoc
     */
    public function getBranches(): array
    {
        if (null === $this->branches) {
            $branches = [];

            $this->process->execute(['fossil', 'branch', 'list'], $output, $this->checkoutDir);
            foreach ($this->process->splitLines($output) as $branch) {
                $branch = trim(Preg::replace('/^\*/', '', trim($branch)));
                $branches[$branch] = $branch;
            }

            $this->branches = $branches;
        }

        return $this->branches;
    }

    /**
     * @inheritDoc
     */
    public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool
    {
        if (Preg::isMatch('#(^(?:https?|ssh)://(?:[^@]@)?(?:chiselapp\.com|fossil\.))#i', $url)) {
            return true;
        }

        if (Preg::isMatch('!/fossil/|\.fossil!', $url)) {
            return true;
        }

        // local filesystem
        if (Filesystem::isLocalPath($url)) {
            $url = Filesystem::getPlatformPath($url);
            if (!is_dir($url)) {
                return false;
            }

            $process = new ProcessExecutor($io);
            // check whether there is a fossil repo in that path
            if ($process->execute(['fossil', 'info'], $output, $url) === 0) {
                return true;
            }
        }

        return false;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository\Vcs;

use Composer\Cache;
use Composer\Downloader\TransportException;
use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
use Composer\Pcre\Preg;
use Composer\Util\ProcessExecutor;
use Composer\Util\HttpDownloader;
use Composer\Util\Filesystem;
use Composer\Util\Http\Response;

/**
 * A driver implementation for driver with authentication interaction.
 *
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 */
abstract class VcsDriver implements VcsDriverInterface
{
    /** @var string */
    protected $url;
    /** @var string */
    protected $originUrl;
    /** @var array<string, mixed> */
    protected $repoConfig;
    /** @var IOInterface */
    protected $io;
    /** @var Config */
    protected $config;
    /** @var ProcessExecutor */
    protected $process;
    /** @var HttpDownloader */
    protected $httpDownloader;
    /** @var array<int|string, mixed> */
    protected $infoCache = [];
    /** @var ?Cache */
    protected $cache;

    /**
     * Constructor.
     *
     * @param array{url: string}&array<string, mixed>           $repoConfig     The repository configuration
     * @param IOInterface     $io             The IO instance
     * @param Config          $config         The composer configuration
     * @param HttpDownloader  $httpDownloader Remote Filesystem, injectable for mocking
     * @param ProcessExecutor $process        Process instance, injectable for mocking
     */
    final public function __construct(array $repoConfig, IOInterface $io, Config $config, HttpDownloader $httpDownloader, ProcessExecutor $process)
    {
        if (Filesystem::isLocalPath($repoConfig['url'])) {
            $repoConfig['url'] = Filesystem::getPlatformPath($repoConfig['url']);
        }

        $this->url = $repoConfig['url'];
        $this->originUrl = $repoConfig['url'];
        $this->repoConfig = $repoConfig;
        $this->io = $io;
        $this->config = $config;
        $this->httpDownloader = $httpDownloader;
        $this->process = $process;
    }

    /**
     * Returns whether or not the given $identifier should be cached or not.
     */
    protected function shouldCache(string $identifier): bool
    {
        return $this->cache && Preg::isMatch('{^[a-f0-9]{40}$}iD', $identifier);
    }

    /**
     * @inheritDoc
     */
    public function getComposerInformation(string $identifier): ?array
    {
        if (!isset($this->infoCache[$identifier])) {
            if ($this->shouldCache($identifier) && $res = $this->cache->read($identifier)) {
                return $this->infoCache[$identifier] = JsonFile::parseJson($res);
            }

            $composer = $this->getBaseComposerInformation($identifier);

            if ($this->shouldCache($identifier)) {
                $this->cache->write($identifier, JsonFile::encode($composer, 0));
            }

            $this->infoCache[$identifier] = $composer;
        }

        return $this->infoCache[$identifier];
    }

    /**
     * @return array<mixed>|null
     */
    protected function getBaseComposerInformation(string $identifier): ?array
    {
        $composerFileContent = $this->getFileContent('composer.json', $identifier);

        if (!$composerFileContent) {
            return null;
        }

        $composer = JsonFile::parseJson($composerFileContent, $identifier . ':composer.json');

        if ([] === $composer || !is_array($composer)) {
            return null;
        }

        if (empty($composer['time']) && null !== ($changeDate = $this->getChangeDate($identifier))) {
            $composer['time'] = $changeDate->format(DATE_RFC3339);
        }

        return $composer;
    }

    /**
     * @inheritDoc
     */
    public function hasComposerFile(string $identifier): bool
    {
        try {
            return null !== $this->getComposerInformation($identifier);
        } catch (TransportException $e) {
        }

        return false;
    }

    /**
     * Get the https or http protocol depending on SSL support.
     *
     * Call this only if you know that the server supports both.
     *
     * @return string The correct type of protocol
     */
    protected function getScheme(): string
    {
        if (extension_loaded('openssl')) {
            return 'https';
        }

        return 'http';
    }

    /**
     * Get the remote content.
     *
     * @param string $url The URL of content
     *
     * @throws TransportException
     */
    protected function getContents(string $url): Response
    {
        $options = $this->repoConfig['options'] ?? [];

        return $this->httpDownloader->get($url, $options);
    }

    /**
     * @inheritDoc
     */
    public function cleanup(): void
    {
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository\Vcs;

use Composer\Pcre\Preg;
use Composer\Util\ProcessExecutor;
use Composer\Util\Filesystem;
use Composer\Util\Url;
use Composer\Util\Git as GitUtil;
use Composer\IO\IOInterface;
use Composer\Cache;
use Composer\Config;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class GitDriver extends VcsDriver
{
    /** @var array<int|string, string> Map of tag name (can be turned to an int by php if it is a numeric name) to identifier */
    protected $tags;
    /** @var array<int|string, string> Map of branch name (can be turned to an int by php if it is a numeric name) to identifier */
    protected $branches;
    /** @var string */
    protected $rootIdentifier;
    /** @var string */
    protected $repoDir;

    /**
     * @inheritDoc
     */
    public function initialize(): void
    {
        if (Filesystem::isLocalPath($this->url)) {
            $this->url = Preg::replace('{[\\/]\.git/?$}', '', $this->url);
            if (!is_dir($this->url)) {
                throw new \RuntimeException('Failed to read package information from '.$this->url.' as the path does not exist');
            }
            $this->repoDir = $this->url;
            $cacheUrl = realpath($this->url);
        } else {
            if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) {
                throw new \RuntimeException('GitDriver requires a usable cache directory, and it looks like you set it to be disabled');
            }

            $this->repoDir = $this->config->get('cache-vcs-dir') . '/' . Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($this->url)) . '/';

            GitUtil::cleanEnv();

            $fs = new Filesystem();
            $fs->ensureDirectoryExists(dirname($this->repoDir));

            if (!is_writable(dirname($this->repoDir))) {
                throw new \RuntimeException('Can not clone '.$this->url.' to access package information. The "'.dirname($this->repoDir).'" directory is not writable by the current user.');
            }

            if (Preg::isMatch('{^ssh://[^@]+@[^:]+:[^0-9]+}', $this->url)) {
                throw new \InvalidArgumentException('The source URL '.$this->url.' is invalid, ssh URLs should have a port number after ":".'."\n".'Use ssh://git@example.com:22/path or just git@example.com:path if you do not want to provide a password or custom port.');
            }

            $gitUtil = new GitUtil($this->io, $this->config, $this->process, $fs);
            if (!$gitUtil->syncMirror($this->url, $this->repoDir)) {
                if (!is_dir($this->repoDir)) {
                    throw new \RuntimeException('Failed to clone '.$this->url.' to read package information from it');
                }
                $this->io->writeError('<error>Failed to update '.$this->url.', package information from this repository may be outdated</error>');
            }

            $cacheUrl = $this->url;
        }

        $this->getTags();
        $this->getBranches();

        $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.Preg::replace('{[^a-z0-9.]}i', '-', Url::sanitize($cacheUrl)));
        $this->cache->setReadOnly($this->config->get('cache-read-only'));
    }

    /**
     * @inheritDoc
     */
    public function getRootIdentifier(): string
    {
        if (null === $this->rootIdentifier) {
            $this->rootIdentifier = 'master';

            $gitUtil = new GitUtil($this->io, $this->config, $this->process, new Filesystem());
            if (!Filesystem::isLocalPath($this->url)) {
                $defaultBranch = $gitUtil->getMirrorDefaultBranch($this->url, $this->repoDir, false);
                if ($defaultBranch !== null) {
                    return $this->rootIdentifier = $defaultBranch;
                }
            }

            // select currently checked out branch as default branch
            $this->process->execute(['git', 'branch', '--no-color'], $output, $this->repoDir);
            $branches = $this->process->splitLines($output);
            if (!in_array('* master', $branches)) {
                foreach ($branches as $branch) {
                    if ($branch && Preg::isMatchStrictGroups('{^\* +(\S+)}', $branch, $match)) {
                        $this->rootIdentifier = $match[1];
                        break;
                    }
                }
            }
        }

        return $this->rootIdentifier;
    }

    /**
     * @inheritDoc
     */
    public function getUrl(): string
    {
        return $this->url;
    }

    /**
     * @inheritDoc
     */
    public function getSource(string $identifier): array
    {
        return ['type' => 'git', 'url' => $this->getUrl(), 'reference' => $identifier];
    }

    /**
     * @inheritDoc
     */
    public function getDist(string $identifier): ?array
    {
        return null;
    }

    /**
     * @inheritDoc
     */
    public function getFileContent(string $file, string $identifier): ?string
    {
        if (isset($identifier[0]) && $identifier[0] === '-') {
            throw new \RuntimeException('Invalid git identifier detected. Identifier must not start with a -, given: ' . $identifier);
        }

        $this->process->execute(['git', 'show', $identifier.':'.$file], $content, $this->repoDir);

        if (trim($content) === '') {
            return null;
        }

        return $content;
    }

    /**
     * @inheritDoc
     */
    public function getChangeDate(string $identifier): ?\DateTimeImmutable
    {
        $this->process->execute(['git', '-c', 'log.showSignature=false', 'log', '-1', '--format=%at', $identifier], $output, $this->repoDir);

        return new \DateTimeImmutable('@'.trim($output), new \DateTimeZone('UTC'));
    }

    /**
     * @inheritDoc
     */
    public function getTags(): array
    {
        if (null === $this->tags) {
            $this->tags = [];

            $this->process->execute(['git', 'show-ref', '--tags', '--dereference'], $output, $this->repoDir);
            foreach ($this->process->splitLines($output) as $tag) {
                if ($tag !== '' && Preg::isMatch('{^([a-f0-9]{40}) refs/tags/(\S+?)(\^\{\})?$}', $tag, $match)) {
                    $this->tags[$match[2]] = $match[1];
                }
            }
        }

        return $this->tags;
    }

    /**
     * @inheritDoc
     */
    public function getBranches(): array
    {
        if (null === $this->branches) {
            $branches = [];

            $this->process->execute(['git', 'branch', '--no-color', '--no-abbrev', '-v'], $output, $this->repoDir);
            foreach ($this->process->splitLines($output) as $branch) {
                if ($branch !== '' && !Preg::isMatch('{^ *[^/]+/HEAD }', $branch)) {
                    if (Preg::isMatchStrictGroups('{^(?:\* )? *(\S+) *([a-f0-9]+)(?: .*)?$}', $branch, $match) && $match[1][0] !== '-') {
                        $branches[$match[1]] = $match[2];
                    }
                }
            }

            $this->branches = $branches;
        }

        return $this->branches;
    }

    /**
     * @inheritDoc
     */
    public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool
    {
        if (Preg::isMatch('#(^git://|\.git/?$|git(?:olite)?@|//git\.|//github.com/)#i', $url)) {
            return true;
        }

        // local filesystem
        if (Filesystem::isLocalPath($url)) {
            $url = Filesystem::getPlatformPath($url);
            if (!is_dir($url)) {
                return false;
            }

            $process = new ProcessExecutor($io);
            // check whether there is a git repo in that path
            if ($process->execute(['git', 'tag'], $output, $url) === 0) {
                return true;
            }
            GitUtil::checkForRepoOwnershipError($process->getErrorOutput(), $url);
        }

        if (!$deep) {
            return false;
        }

        $gitUtil = new GitUtil($io, $config, new ProcessExecutor($io), new Filesystem());
        GitUtil::cleanEnv();

        try {
            $gitUtil->runCommands([['git', 'ls-remote', '--heads', '--', '%url%']], $url, sys_get_temp_dir());
        } catch (\RuntimeException $e) {
            return false;
        }

        return true;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository\Vcs;

use Composer\Config;
use Composer\Cache;
use Composer\IO\IOInterface;
use Composer\Pcre\Preg;
use Composer\Util\ProcessExecutor;
use Composer\Util\Perforce;
use Composer\Util\Http\Response;

/**
 * @author Matt Whittom <Matt.Whittom@veteransunited.com>
 */
class PerforceDriver extends VcsDriver
{
    /** @var string */
    protected $depot;
    /** @var string */
    protected $branch;
    /** @var ?Perforce */
    protected $perforce = null;

    /**
     * @inheritDoc
     */
    public function initialize(): void
    {
        $this->depot = $this->repoConfig['depot'];
        $this->branch = '';
        if (!empty($this->repoConfig['branch'])) {
            $this->branch = $this->repoConfig['branch'];
        }

        $this->initPerforce($this->repoConfig);
        $this->perforce->p4Login();
        $this->perforce->checkStream();

        $this->perforce->writeP4ClientSpec();
        $this->perforce->connectClient();
    }

    /**
     * @param array<string, mixed> $repoConfig
     */
    private function initPerforce(array $repoConfig): void
    {
        if (!empty($this->perforce)) {
            return;
        }

        if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) {
            throw new \RuntimeException('PerforceDriver requires a usable cache directory, and it looks like you set it to be disabled');
        }

        $repoDir = $this->config->get('cache-vcs-dir') . '/' . $this->depot;
        $this->perforce = Perforce::create($repoConfig, $this->getUrl(), $repoDir, $this->process, $this->io);
    }

    /**
     * @inheritDoc
     */
    public function getFileContent(string $file, string $identifier): ?string
    {
        return $this->perforce->getFileContent($file, $identifier);
    }

    /**
     * @inheritDoc
     */
    public function getChangeDate(string $identifier): ?\DateTimeImmutable
    {
        return null;
    }

    /**
     * @inheritDoc
     */
    public function getRootIdentifier(): string
    {
        return $this->branch;
    }

    /**
     * @inheritDoc
     */
    public function getBranches(): array
    {
        return $this->perforce->getBranches();
    }

    /**
     * @inheritDoc
     */
    public function getTags(): array
    {
        return $this->perforce->getTags();
    }

    /**
     * @inheritDoc
     */
    public function getDist(string $identifier): ?array
    {
        return null;
    }

    /**
     * @inheritDoc
     */
    public function getSource(string $identifier): array
    {
        return [
            'type' => 'perforce',
            'url' => $this->repoConfig['url'],
            'reference' => $identifier,
            'p4user' => $this->perforce->getUser(),
        ];
    }

    /**
     * @inheritDoc
     */
    public function getUrl(): string
    {
        return $this->url;
    }

    /**
     * @inheritDoc
     */
    public function hasComposerFile(string $identifier): bool
    {
        $composerInfo = $this->perforce->getComposerInformation('//' . $this->depot . '/' . $identifier);

        return !empty($composerInfo);
    }

    /**
     * @inheritDoc
     */
    public function getContents(string $url): Response
    {
        throw new \BadMethodCallException('Not implemented/used in PerforceDriver');
    }

    /**
     * @inheritDoc
     */
    public static function supports(IOInterface $io, Config $config, string $url, bool $deep = false): bool
    {
        if ($deep || Preg::isMatch('#\b(perforce|p4)\b#i', $url)) {
            return Perforce::checkServerExists($url, new ProcessExecutor($io));
        }

        return false;
    }

    /**
     * @inheritDoc
     */
    public function cleanup(): void
    {
        $this->perforce->cleanupClientSpec();
        $this->perforce = null;
    }

    public function getDepot(): string
    {
        return $this->depot;
    }

    public function getBranch(): string
    {
        return $this->branch;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

/**
 * Builds list of package from PEAR channel.
 *
 * Packages read from channel are named as 'pear-{channelName}/{packageName}'
 * and has aliased as 'pear-{channelAlias}/{packageName}'
 *
 * @author Benjamin Eberlei <kontakt@beberlei.de>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @deprecated
 * @private
 */
class PearRepository extends ArrayRepository
{
    public function __construct()
    {
        throw new \InvalidArgumentException('The PEAR repository has been removed from Composer 2.x');
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Loader\ValidatingArrayLoader;
use Composer\Pcre\Preg;

/**
 * Package repository.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PackageRepository extends ArrayRepository
{
    /** @var mixed[] */
    private $config;

    /**
     * Initializes filesystem repository.
     *
     * @param array{package: mixed[]} $config package definition
     */
    public function __construct(array $config)
    {
        parent::__construct();
        $this->config = $config['package'];

        // make sure we have an array of package definitions
        if (!is_numeric(key($this->config))) {
            $this->config = [$this->config];
        }
    }

    /**
     * Initializes repository (reads file, or remote address).
     */
    protected function initialize(): void
    {
        parent::initialize();

        $loader = new ValidatingArrayLoader(new ArrayLoader(null, true), true);
        foreach ($this->config as $package) {
            try {
                $package = $loader->load($package);
            } catch (\Exception $e) {
                throw new InvalidRepositoryException('A repository of type "package" contains an invalid package definition: '.$e->getMessage()."\n\nInvalid package definition:\n".json_encode($package));
            }

            $this->addPackage($package);
        }
    }

    public function getRepoName(): string
    {
        return Preg::replace('{^array }', 'package ', parent::getRepoName());
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

interface VersionCacheInterface
{
    /**
     * @return mixed[]|null|false Package version data if found, false to indicate the identifier is known but has no package, null for an unknown identifier
     */
    public function getVersionPackage(string $version, string $identifier);
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Package\PackageInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 *
 * @see RepositorySet for ways to work with sets of repos
 */
class RepositoryUtils
{
    /**
     * Find all of $packages which are required by $requirer, either directly or transitively
     *
     * Require-dev is ignored by default, you can enable the require-dev of the initial $requirer
     * packages by passing $includeRequireDev=true, but require-dev of transitive dependencies
     * are always ignored.
     *
     * @template T of PackageInterface
     * @param  array<T> $packages
     * @param  list<T> $bucket Do not pass this in, only used to avoid recursion with circular deps
     * @return list<T>
     */
    public static function filterRequiredPackages(array $packages, PackageInterface $requirer, bool $includeRequireDev = false, array $bucket = []): array
    {
        $requires = $requirer->getRequires();
        if ($includeRequireDev) {
            $requires = array_merge($requires, $requirer->getDevRequires());
        }

        foreach ($packages as $candidate) {
            foreach ($candidate->getNames() as $name) {
                if (isset($requires[$name])) {
                    if (!in_array($candidate, $bucket, true)) {
                        $bucket[] = $candidate;
                        $bucket = self::filterRequiredPackages($packages, $candidate, false, $bucket);
                    }
                    break;
                }
            }
        }

        return $bucket;
    }

    /**
     * Unwraps CompositeRepository, InstalledRepository and optionally FilterRepository to get a flat array of pure repository instances
     *
     * @return RepositoryInterface[]
     */
    public static function flattenRepositories(RepositoryInterface $repo, bool $unwrapFilterRepos = true): array
    {
        // unwrap filter repos
        if ($unwrapFilterRepos && $repo instanceof FilterRepository) {
            $repo = $repo->getRepository();
        }

        if (!$repo instanceof CompositeRepository) {
            return [$repo];
        }

        $repos = [];
        foreach ($repo->getRepositories() as $r) {
            foreach (self::flattenRepositories($r, $unwrapFilterRepos) as $r2) {
                $repos[] = $r2;
            }
        }

        return $repos;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

/**
 * Installed array repository.
 *
 * This is used as an in-memory InstalledRepository mostly for testing purposes
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class InstalledArrayRepository extends WritableArrayRepository implements InstalledRepositoryInterface
{
    public function getRepoName(): string
    {
        return 'installed '.parent::getRepoName();
    }

    /**
     * @inheritDoc
     */
    public function isFresh(): bool
    {
        // this is not a completely correct implementation but there is no way to
        // distinguish an empty repo and a newly created one given this is all in-memory
        return $this->count() === 0;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\Config;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Pcre\Preg;
use Composer\Util\HttpDownloader;
use Composer\Util\ProcessExecutor;
use Composer\Json\JsonFile;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class RepositoryFactory
{
    /**
     * @return array|mixed
     */
    public static function configFromString(IOInterface $io, Config $config, string $repository, bool $allowFilesystem = false)
    {
        if (0 === strpos($repository, 'http')) {
            $repoConfig = ['type' => 'composer', 'url' => $repository];
        } elseif ("json" === pathinfo($repository, PATHINFO_EXTENSION)) {
            $json = new JsonFile($repository, Factory::createHttpDownloader($io, $config));
            $data = $json->read();
            if (!empty($data['packages']) || !empty($data['includes']) || !empty($data['provider-includes'])) {
                $repoConfig = ['type' => 'composer', 'url' => 'file://' . strtr(realpath($repository), '\\', '/')];
            } elseif ($allowFilesystem) {
                $repoConfig = ['type' => 'filesystem', 'json' => $json];
            } else {
                throw new \InvalidArgumentException("Invalid repository URL ($repository) given. This file does not contain a valid composer repository.");
            }
        } elseif (strpos($repository, '{') === 0) {
            // assume it is a json object that makes a repo config
            $repoConfig = JsonFile::parseJson($repository);
        } else {
            throw new \InvalidArgumentException("Invalid repository url ($repository) given. Has to be a .json file, an http url or a JSON object.");
        }

        return $repoConfig;
    }

    public static function fromString(IOInterface $io, Config $config, string $repository, bool $allowFilesystem = false, ?RepositoryManager $rm = null): RepositoryInterface
    {
        $repoConfig = static::configFromString($io, $config, $repository, $allowFilesystem);

        return static::createRepo($io, $config, $repoConfig, $rm);
    }

    /**
     * @param  array<string, mixed> $repoConfig
     */
    public static function createRepo(IOInterface $io, Config $config, array $repoConfig, ?RepositoryManager $rm = null): RepositoryInterface
    {
        if (!$rm) {
            @trigger_error('Not passing a repository manager when calling createRepo is deprecated since Composer 2.3.6', E_USER_DEPRECATED);
            $rm = static::manager($io, $config);
        }
        $repos = self::createRepos($rm, [$repoConfig]);

        return reset($repos);
    }

    /**
     * @return RepositoryInterface[]
     */
    public static function defaultRepos(?IOInterface $io = null, ?Config $config = null, ?RepositoryManager $rm = null): array
    {
        if (null === $rm) {
            @trigger_error('Not passing a repository manager when calling defaultRepos is deprecated since Composer 2.3.6, use defaultReposWithDefaultManager() instead if you cannot get a manager.', E_USER_DEPRECATED);
        }

        if (null === $config) {
            $config = Factory::createConfig($io);
        }
        if (null !== $io) {
            $io->loadConfiguration($config);
        }
        if (null === $rm) {
            if (null === $io) {
                throw new \InvalidArgumentException('This function requires either an IOInterface or a RepositoryManager');
            }
            $rm = static::manager($io, $config, Factory::createHttpDownloader($io, $config));
        }

        return self::createRepos($rm, $config->getRepositories());
    }

    /**
     * @param  EventDispatcher   $eventDispatcher
     * @param  HttpDownloader    $httpDownloader
     */
    public static function manager(IOInterface $io, Config $config, ?HttpDownloader $httpDownloader = null, ?EventDispatcher $eventDispatcher = null, ?ProcessExecutor $process = null): RepositoryManager
    {
        if ($httpDownloader === null) {
            $httpDownloader = Factory::createHttpDownloader($io, $config);
        }
        if ($process === null) {
            $process = new ProcessExecutor($io);
            $process->enableAsync();
        }

        $rm = new RepositoryManager($io, $config, $httpDownloader, $eventDispatcher, $process);
        $rm->setRepositoryClass('composer', 'Composer\Repository\ComposerRepository');
        $rm->setRepositoryClass('vcs', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('package', 'Composer\Repository\PackageRepository');
        $rm->setRepositoryClass('pear', 'Composer\Repository\PearRepository');
        $rm->setRepositoryClass('git', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('bitbucket', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('git-bitbucket', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('github', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('gitlab', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('svn', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('fossil', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('perforce', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('hg', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('artifact', 'Composer\Repository\ArtifactRepository');
        $rm->setRepositoryClass('path', 'Composer\Repository\PathRepository');

        return $rm;
    }

    /**
     * @return RepositoryInterface[]
     */
    public static function defaultReposWithDefaultManager(IOInterface $io): array
    {
        $manager = RepositoryFactory::manager($io, $config = Factory::createConfig($io));
        $io->loadConfiguration($config);

        return RepositoryFactory::defaultRepos($io, $config, $manager);
    }

    /**
     * @param array<int|string, mixed> $repoConfigs
     *
     * @return RepositoryInterface[]
     */
    private static function createRepos(RepositoryManager $rm, array $repoConfigs): array
    {
        $repos = [];

        foreach ($repoConfigs as $index => $repo) {
            if (is_string($repo)) {
                throw new \UnexpectedValueException('"repositories" should be an array of repository definitions, only a single repository was given');
            }
            if (!is_array($repo)) {
                throw new \UnexpectedValueException('Repository "'.$index.'" ('.json_encode($repo).') should be an array, '.gettype($repo).' given');
            }
            if (!isset($repo['type'])) {
                throw new \UnexpectedValueException('Repository "'.$index.'" ('.json_encode($repo).') must have a type defined');
            }

            $name = self::generateRepositoryName($index, $repo, $repos);
            if ($repo['type'] === 'filesystem') {
                $repos[$name] = new FilesystemRepository($repo['json']);
            } else {
                $repos[$name] = $rm->createRepository($repo['type'], $repo, (string) $index);
            }
        }

        return $repos;
    }

    /**
     * @param int|string $index
     * @param array{url?: string} $repo
     * @param array<int|string, mixed> $existingRepos
     */
    public static function generateRepositoryName($index, array $repo, array $existingRepos): string
    {
        $name = is_int($index) && isset($repo['url']) ? Preg::replace('{^https?://}i', '', $repo['url']) : (string) $index;
        while (isset($existingRepos[$name])) {
            $name .= '2';
        }

        return $name;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Filter\PlatformRequirementFilter;

interface PlatformRequirementFilterInterface
{
    public function isIgnored(string $req): bool;

    public function isUpperBoundIgnored(string $req): bool;
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Filter\PlatformRequirementFilter;

use Composer\Package\BasePackage;
use Composer\Pcre\Preg;
use Composer\Repository\PlatformRepository;
use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\MatchAllConstraint;
use Composer\Semver\Constraint\MultiConstraint;
use Composer\Semver\Interval;
use Composer\Semver\Intervals;

final class IgnoreListPlatformRequirementFilter implements PlatformRequirementFilterInterface
{
    /**
     * @var non-empty-string
     */
    private $ignoreRegex;

    /**
     * @var non-empty-string
     */
    private $ignoreUpperBoundRegex;

    /**
     * @param string[] $reqList
     */
    public function __construct(array $reqList)
    {
        $ignoreAll = $ignoreUpperBound = [];
        foreach ($reqList as $req) {
            if (substr($req, -1) === '+') {
                $ignoreUpperBound[] = substr($req, 0, -1);
            } else {
                $ignoreAll[] = $req;
            }
        }
        $this->ignoreRegex = BasePackage::packageNamesToRegexp($ignoreAll);
        $this->ignoreUpperBoundRegex = BasePackage::packageNamesToRegexp($ignoreUpperBound);
    }

    public function isIgnored(string $req): bool
    {
        if (!PlatformRepository::isPlatformPackage($req)) {
            return false;
        }

        return Preg::isMatch($this->ignoreRegex, $req);
    }

    public function isUpperBoundIgnored(string $req): bool
    {
        if (!PlatformRepository::isPlatformPackage($req)) {
            return false;
        }

        return $this->isIgnored($req) || Preg::isMatch($this->ignoreUpperBoundRegex, $req);
    }

    /**
     * @param bool $allowUpperBoundOverride For conflicts we do not want the upper bound to be skipped
     */
    public function filterConstraint(string $req, ConstraintInterface $constraint, bool $allowUpperBoundOverride = true): ConstraintInterface
    {
        if (!PlatformRepository::isPlatformPackage($req)) {
            return $constraint;
        }

        if (!$allowUpperBoundOverride || !Preg::isMatch($this->ignoreUpperBoundRegex, $req)) {
            return $constraint;
        }

        if (Preg::isMatch($this->ignoreRegex, $req)) {
            return new MatchAllConstraint;
        }

        $intervals = Intervals::get($constraint);
        $last = end($intervals['numeric']);
        if ($last !== false && (string) $last->getEnd() !== (string) Interval::untilPositiveInfinity()) {
            $constraint = new MultiConstraint([$constraint, new Constraint('>=', $last->getEnd()->getVersion())], false);
        }

        return $constraint;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Filter\PlatformRequirementFilter;

final class IgnoreNothingPlatformRequirementFilter implements PlatformRequirementFilterInterface
{
    /**
     * @return false
     */
    public function isIgnored(string $req): bool
    {
        return false;
    }

    /**
     * @return false
     */
    public function isUpperBoundIgnored(string $req): bool
    {
        return false;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Filter\PlatformRequirementFilter;

final class PlatformRequirementFilterFactory
{
    /**
     * @param mixed $boolOrList
     */
    public static function fromBoolOrList($boolOrList): PlatformRequirementFilterInterface
    {
        if (is_bool($boolOrList)) {
            return $boolOrList ? self::ignoreAll() : self::ignoreNothing();
        }

        if (is_array($boolOrList)) {
            return new IgnoreListPlatformRequirementFilter($boolOrList);
        }

        throw new \InvalidArgumentException(
            sprintf(
                'PlatformRequirementFilter: Unknown $boolOrList parameter %s. Please report at https://github.com/composer/composer/issues/new.',
                gettype($boolOrList)
            )
        );
    }

    public static function ignoreAll(): PlatformRequirementFilterInterface
    {
        return new IgnoreAllPlatformRequirementFilter();
    }

    public static function ignoreNothing(): PlatformRequirementFilterInterface
    {
        return new IgnoreNothingPlatformRequirementFilter();
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Filter\PlatformRequirementFilter;

use Composer\Repository\PlatformRepository;

final class IgnoreAllPlatformRequirementFilter implements PlatformRequirementFilterInterface
{
    public function isIgnored(string $req): bool
    {
        return PlatformRepository::isPlatformPackage($req);
    }

    public function isUpperBoundIgnored(string $req): bool
    {
        return $this->isIgnored($req);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer;

use Composer\Config\JsonConfigSource;
use Composer\Json\JsonFile;
use Composer\IO\IOInterface;
use Composer\Package\Archiver;
use Composer\Package\Version\VersionGuesser;
use Composer\Package\RootPackageInterface;
use Composer\Repository\FilesystemRepository;
use Composer\Repository\RepositoryManager;
use Composer\Repository\RepositoryFactory;
use Composer\Util\Filesystem;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Util\HttpDownloader;
use Composer\Util\Loop;
use Composer\Util\Silencer;
use Composer\Plugin\PluginEvents;
use Composer\EventDispatcher\Event;
use Phar;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Output\ConsoleOutput;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Autoload\AutoloadGenerator;
use Composer\Package\Version\VersionParser;
use Composer\Downloader\TransportException;
use Composer\Json\JsonValidationException;
use Composer\Repository\InstalledRepositoryInterface;
use UnexpectedValueException;
use ZipArchive;

/**
 * Creates a configured instance of composer.
 *
 * @author Ryan Weaver <ryan@knplabs.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Igor Wiedler <igor@wiedler.ch>
 * @author Nils Adermann <naderman@naderman.de>
 */
class Factory
{
    /**
     * @throws \RuntimeException
     */
    protected static function getHomeDir(): string
    {
        $home = Platform::getEnv('COMPOSER_HOME');
        if ($home) {
            return $home;
        }

        if (Platform::isWindows()) {
            if (!Platform::getEnv('APPDATA')) {
                throw new \RuntimeException('The APPDATA or COMPOSER_HOME environment variable must be set for composer to run correctly');
            }

            return rtrim(strtr(Platform::getEnv('APPDATA'), '\\', '/'), '/') . '/Composer';
        }

        $userDir = self::getUserDir();
        $dirs = [];

        if (self::useXdg()) {
            // XDG Base Directory Specifications
            $xdgConfig = Platform::getEnv('XDG_CONFIG_HOME');
            if (!$xdgConfig) {
                $xdgConfig = $userDir . '/.config';
            }

            $dirs[] = $xdgConfig . '/composer';
        }

        $dirs[] = $userDir . '/.composer';

        // select first dir which exists of: $XDG_CONFIG_HOME/composer or ~/.composer
        foreach ($dirs as $dir) {
            if (Silencer::call('is_dir', $dir)) {
                return $dir;
            }
        }

        // if none exists, we default to first defined one (XDG one if system uses it, or ~/.composer otherwise)
        return $dirs[0];
    }

    protected static function getCacheDir(string $home): string
    {
        $cacheDir = Platform::getEnv('COMPOSER_CACHE_DIR');
        if ($cacheDir) {
            return $cacheDir;
        }

        $homeEnv = Platform::getEnv('COMPOSER_HOME');
        if ($homeEnv) {
            return $homeEnv . '/cache';
        }

        if (Platform::isWindows()) {
            if ($cacheDir = Platform::getEnv('LOCALAPPDATA')) {
                $cacheDir .= '/Composer';
            } else {
                $cacheDir = $home . '/cache';
            }

            return rtrim(strtr($cacheDir, '\\', '/'), '/');
        }

        $userDir = self::getUserDir();
        if (PHP_OS === 'Darwin') {
            // Migrate existing cache dir in old location if present
            if (is_dir($home . '/cache') && !is_dir($userDir . '/Library/Caches/composer')) {
                Silencer::call('rename', $home . '/cache', $userDir . '/Library/Caches/composer');
            }

            return $userDir . '/Library/Caches/composer';
        }

        if ($home === $userDir . '/.composer' && is_dir($home . '/cache')) {
            return $home . '/cache';
        }

        if (self::useXdg()) {
            $xdgCache = Platform::getEnv('XDG_CACHE_HOME') ?: $userDir . '/.cache';

            return $xdgCache . '/composer';
        }

        return $home . '/cache';
    }

    protected static function getDataDir(string $home): string
    {
        $homeEnv = Platform::getEnv('COMPOSER_HOME');
        if ($homeEnv) {
            return $homeEnv;
        }

        if (Platform::isWindows()) {
            return strtr($home, '\\', '/');
        }

        $userDir = self::getUserDir();
        if ($home !== $userDir . '/.composer' && self::useXdg()) {
            $xdgData = Platform::getEnv('XDG_DATA_HOME') ?: $userDir . '/.local/share';

            return $xdgData . '/composer';
        }

        return $home;
    }

    public static function createConfig(?IOInterface $io = null, ?string $cwd = null): Config
    {
        $cwd = $cwd ?? Platform::getCwd(true);

        $config = new Config(true, $cwd);

        // determine and add main dirs to the config
        $home = self::getHomeDir();
        $config->merge([
            'config' => [
                'home' => $home,
                'cache-dir' => self::getCacheDir($home),
                'data-dir' => self::getDataDir($home),
            ],
        ], Config::SOURCE_DEFAULT);

        // load global config
        $file = new JsonFile($config->get('home').'/config.json');
        if ($file->exists()) {
            if ($io instanceof IOInterface) {
                $io->writeError('Loading config file ' . $file->getPath(), true, IOInterface::DEBUG);
            }
            self::validateJsonSchema($io, $file);
            $config->merge($file->read(), $file->getPath());
        }
        $config->setConfigSource(new JsonConfigSource($file));

        $htaccessProtect = $config->get('htaccess-protect');
        if ($htaccessProtect) {
            // Protect directory against web access. Since HOME could be
            // the www-data's user home and be web-accessible it is a
            // potential security risk
            $dirs = [$config->get('home'), $config->get('cache-dir'), $config->get('data-dir')];
            foreach ($dirs as $dir) {
                if (!file_exists($dir . '/.htaccess')) {
                    if (!is_dir($dir)) {
                        Silencer::call('mkdir', $dir, 0777, true);
                    }
                    Silencer::call('file_put_contents', $dir . '/.htaccess', 'Deny from all');
                }
            }
        }

        // load global auth file
        $file = new JsonFile($config->get('home').'/auth.json');
        if ($file->exists()) {
            if ($io instanceof IOInterface) {
                $io->writeError('Loading config file ' . $file->getPath(), true, IOInterface::DEBUG);
            }
            self::validateJsonSchema($io, $file, JsonFile::AUTH_SCHEMA);
            $config->merge(['config' => $file->read()], $file->getPath());
        }
        $config->setAuthConfigSource(new JsonConfigSource($file, true));

        self::loadComposerAuthEnv($config, $io);

        return $config;
    }

    public static function getComposerFile(): string
    {
        $env = Platform::getEnv('COMPOSER');
        if (is_string($env)) {
            $env = trim($env);
            if ('' !== $env) {
                if (is_dir($env)) {
                    throw new \RuntimeException('The COMPOSER environment variable is set to '.$env.' which is a directory, this variable should point to a composer.json or be left unset.');
                }

                return $env;
            }
        }

        return './composer.json';
    }

    public static function getLockFile(string $composerFile): string
    {
        return "json" === pathinfo($composerFile, PATHINFO_EXTENSION)
                ? substr($composerFile, 0, -4).'lock'
                : $composerFile . '.lock';
    }

    /**
     * @return array{highlight: OutputFormatterStyle, warning: OutputFormatterStyle}
     */
    public static function createAdditionalStyles(): array
    {
        return [
            'highlight' => new OutputFormatterStyle('red'),
            'warning' => new OutputFormatterStyle('black', 'yellow'),
        ];
    }

    public static function createOutput(): ConsoleOutput
    {
        $styles = self::createAdditionalStyles();
        $formatter = new OutputFormatter(false, $styles);

        return new ConsoleOutput(ConsoleOutput::VERBOSITY_NORMAL, null, $formatter);
    }

    /**
     * Creates a Composer instance
     *
     * @param  IOInterface                       $io             IO instance
     * @param  array<string, mixed>|string|null  $localConfig    either a configuration array or a filename to read from, if null it will
     *                                                           read from the default filename
     * @param  bool|'local'|'global'             $disablePlugins Whether plugins should not be loaded, can be set to local or global to only disable local/global plugins
     * @param  bool                              $disableScripts Whether scripts should not be run
     * @param  bool                              $fullLoad       Whether to initialize everything or only main project stuff (used when loading the global composer)
     * @throws \InvalidArgumentException
     * @throws \UnexpectedValueException
     * @return Composer|PartialComposer Composer if $fullLoad is true, otherwise PartialComposer
     * @phpstan-return ($fullLoad is true ? Composer : PartialComposer)
     */
    public function createComposer(IOInterface $io, $localConfig = null, $disablePlugins = false, ?string $cwd = null, bool $fullLoad = true, bool $disableScripts = false)
    {
        // if a custom composer.json path is given, we change the default cwd to be that file's directory
        if (is_string($localConfig) && is_file($localConfig) && null === $cwd) {
            $cwd = dirname($localConfig);
        }

        $cwd = $cwd ?? Platform::getCwd(true);

        // load Composer configuration
        if (null === $localConfig) {
            $localConfig = static::getComposerFile();
        }

        $localConfigSource = Config::SOURCE_UNKNOWN;
        if (is_string($localConfig)) {
            $composerFile = $localConfig;

            $file = new JsonFile($localConfig, null, $io);

            if (!$file->exists()) {
                if ($localConfig === './composer.json' || $localConfig === 'composer.json') {
                    $message = 'Composer could not find a composer.json file in '.$cwd;
                } else {
                    $message = 'Composer could not find the config file: '.$localConfig;
                }
                $instructions = $fullLoad ? 'To initialize a project, please create a composer.json file. See https://getcomposer.org/basic-usage' : '';
                throw new \InvalidArgumentException($message.PHP_EOL.$instructions);
            }

            if (!Platform::isInputCompletionProcess()) {
                try {
                    $file->validateSchema(JsonFile::LAX_SCHEMA);
                } catch (JsonValidationException $e) {
                    $errors = ' - ' . implode(PHP_EOL . ' - ', $e->getErrors());
                    $message = $e->getMessage() . ':' . PHP_EOL . $errors;
                    throw new JsonValidationException($message);
                }
            }

            $localConfig = $file->read();
            $localConfigSource = $file->getPath();
        }

        // Load config and override with local config/auth config
        $config = static::createConfig($io, $cwd);
        $isGlobal = $localConfigSource !== Config::SOURCE_UNKNOWN && realpath($config->get('home')) === realpath(dirname($localConfigSource));
        $config->merge($localConfig, $localConfigSource);

        if (isset($composerFile)) {
            $io->writeError('Loading config file ' . $composerFile .' ('.realpath($composerFile).')', true, IOInterface::DEBUG);
            $config->setConfigSource(new JsonConfigSource(new JsonFile(realpath($composerFile), null, $io)));

            $localAuthFile = new JsonFile(dirname(realpath($composerFile)) . '/auth.json', null, $io);
            if ($localAuthFile->exists()) {
                $io->writeError('Loading config file ' . $localAuthFile->getPath(), true, IOInterface::DEBUG);
                self::validateJsonSchema($io, $localAuthFile, JsonFile::AUTH_SCHEMA);
                $config->merge(['config' => $localAuthFile->read()], $localAuthFile->getPath());
                $config->setLocalAuthConfigSource(new JsonConfigSource($localAuthFile, true));
            }
        }

        // make sure we load the auth env again over the local auth.json + composer.json config
        self::loadComposerAuthEnv($config, $io);

        $vendorDir = $config->get('vendor-dir');

        // initialize composer
        $composer = $fullLoad ? new Composer() : new PartialComposer();
        $composer->setConfig($config);
        if ($isGlobal) {
            $composer->setGlobal();
        }

        if ($fullLoad) {
            // load auth configs into the IO instance
            $io->loadConfiguration($config);

            // load existing Composer\InstalledVersions instance if available and scripts/plugins are allowed, as they might need it
            // we only load if the InstalledVersions class wasn't defined yet so that this is only loaded once
            if (false === $disablePlugins && false === $disableScripts && !class_exists('Composer\InstalledVersions', false) && file_exists($installedVersionsPath = $config->get('vendor-dir').'/composer/installed.php')) {
                // force loading the class at this point so it is loaded from the composer phar and not from the vendor dir
                // as we cannot guarantee integrity of that file
                if (class_exists('Composer\InstalledVersions')) {
                    FilesystemRepository::safelyLoadInstalledVersions($installedVersionsPath);
                }
            }
        }

        $httpDownloader = self::createHttpDownloader($io, $config);
        $process = new ProcessExecutor($io);
        $loop = new Loop($httpDownloader, $process);
        $composer->setLoop($loop);

        // initialize event dispatcher
        $dispatcher = new EventDispatcher($composer, $io, $process);
        $dispatcher->setRunScripts(!$disableScripts);
        $composer->setEventDispatcher($dispatcher);

        // initialize repository manager
        $rm = RepositoryFactory::manager($io, $config, $httpDownloader, $dispatcher, $process);
        $composer->setRepositoryManager($rm);

        // force-set the version of the global package if not defined as
        // guessing it adds no value and only takes time
        if (!$fullLoad && !isset($localConfig['version'])) {
            $localConfig['version'] = '1.0.0';
        }

        // load package
        $parser = new VersionParser;
        $guesser = new VersionGuesser($config, $process, $parser, $io);
        $loader = $this->loadRootPackage($rm, $config, $parser, $guesser, $io);
        $package = $loader->load($localConfig, 'Composer\Package\RootPackage', $cwd);
        $composer->setPackage($package);

        // load local repository
        $this->addLocalRepository($io, $rm, $vendorDir, $package, $process);

        // initialize installation manager
        $im = $this->createInstallationManager($loop, $io, $dispatcher);
        $composer->setInstallationManager($im);

        if ($composer instanceof Composer) {
            // initialize download manager
            $dm = $this->createDownloadManager($io, $config, $httpDownloader, $process, $dispatcher);
            $composer->setDownloadManager($dm);

            // initialize autoload generator
            $generator = new AutoloadGenerator($dispatcher, $io);
            $composer->setAutoloadGenerator($generator);

            // initialize archive manager
            $am = $this->createArchiveManager($config, $dm, $loop);
            $composer->setArchiveManager($am);
        }

        // add installers to the manager (must happen after download manager is created since they read it out of $composer)
        $this->createDefaultInstallers($im, $composer, $io, $process);

        // init locker if possible
        if ($composer instanceof Composer && isset($composerFile)) {
            $lockFile = self::getLockFile($composerFile);
            if (!$config->get('lock') && file_exists($lockFile)) {
                $io->writeError('<warning>'.$lockFile.' is present but ignored as the "lock" config option is disabled.</warning>');
            }

            $locker = new Package\Locker($io, new JsonFile($config->get('lock') ? $lockFile : Platform::getDevNull(), null, $io), $im, file_get_contents($composerFile), $process);
            $composer->setLocker($locker);
        } elseif ($composer instanceof Composer) {
            $locker = new Package\Locker($io, new JsonFile(Platform::getDevNull(), null, $io), $im, JsonFile::encode($localConfig), $process);
            $composer->setLocker($locker);
        }

        if ($composer instanceof Composer) {
            $globalComposer = null;
            if (!$composer->isGlobal()) {
                $globalComposer = $this->createGlobalComposer($io, $config, $disablePlugins, $disableScripts);
            }

            $pm = $this->createPluginManager($io, $composer, $globalComposer, $disablePlugins);
            $composer->setPluginManager($pm);

            if ($composer->isGlobal()) {
                $pm->setRunningInGlobalDir(true);
            }

            $pm->loadInstalledPlugins();
        }

        if ($fullLoad) {
            $initEvent = new Event(PluginEvents::INIT);
            $composer->getEventDispatcher()->dispatch($initEvent->getName(), $initEvent);

            // once everything is initialized we can
            // purge packages from local repos if they have been deleted on the filesystem
            $this->purgePackages($rm->getLocalRepository(), $im);
        }

        return $composer;
    }

    /**
     * @param  bool          $disablePlugins Whether plugins should not be loaded
     * @param  bool          $disableScripts Whether scripts should not be executed
     */
    public static function createGlobal(IOInterface $io, bool $disablePlugins = false, bool $disableScripts = false): ?Composer
    {
        $factory = new static();

        return $factory->createGlobalComposer($io, static::createConfig($io), $disablePlugins, $disableScripts, true);
    }

    /**
     * @param Repository\RepositoryManager $rm
     */
    protected function addLocalRepository(IOInterface $io, RepositoryManager $rm, string $vendorDir, RootPackageInterface $rootPackage, ?ProcessExecutor $process = null): void
    {
        $fs = null;
        if ($process) {
            $fs = new Filesystem($process);
        }

        $rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/composer/installed.json', null, $io), true, $rootPackage, $fs));
    }

    /**
     * @param bool|'local'|'global' $disablePlugins Whether plugins should not be loaded, can be set to local or global to only disable local/global plugins
     * @return PartialComposer|Composer|null By default PartialComposer, but Composer if $fullLoad is set to true
     * @phpstan-return ($fullLoad is true ? Composer|null : PartialComposer|null)
     */
    protected function createGlobalComposer(IOInterface $io, Config $config, $disablePlugins, bool $disableScripts, bool $fullLoad = false): ?PartialComposer
    {
        // make sure if disable plugins was 'local' it is now turned off
        $disablePlugins = $disablePlugins === 'global' || $disablePlugins === true;

        $composer = null;
        try {
            $composer = $this->createComposer($io, $config->get('home') . '/composer.json', $disablePlugins, $config->get('home'), $fullLoad, $disableScripts);
        } catch (\Exception $e) {
            $io->writeError('Failed to initialize global composer: '.$e->getMessage(), true, IOInterface::DEBUG);
        }

        return $composer;
    }

    /**
     * @param  IO\IOInterface             $io
     * @param  EventDispatcher            $eventDispatcher
     */
    public function createDownloadManager(IOInterface $io, Config $config, HttpDownloader $httpDownloader, ProcessExecutor $process, ?EventDispatcher $eventDispatcher = null): Downloader\DownloadManager
    {
        $cache = null;
        if ($config->get('cache-files-ttl') > 0) {
            $cache = new Cache($io, $config->get('cache-files-dir'), 'a-z0-9_./');
            $cache->setReadOnly($config->get('cache-read-only'));
        }

        $fs = new Filesystem($process);

        $dm = new Downloader\DownloadManager($io, false, $fs);
        switch ($preferred = $config->get('preferred-install')) {
            case 'dist':
                $dm->setPreferDist(true);
                break;
            case 'source':
                $dm->setPreferSource(true);
                break;
            case 'auto':
            default:
                // noop
                break;
        }

        if (is_array($preferred)) {
            $dm->setPreferences($preferred);
        }

        $dm->setDownloader('git', new Downloader\GitDownloader($io, $config, $process, $fs));
        $dm->setDownloader('svn', new Downloader\SvnDownloader($io, $config, $process, $fs));
        $dm->setDownloader('fossil', new Downloader\FossilDownloader($io, $config, $process, $fs));
        $dm->setDownloader('hg', new Downloader\HgDownloader($io, $config, $process, $fs));
        $dm->setDownloader('perforce', new Downloader\PerforceDownloader($io, $config, $process, $fs));
        $dm->setDownloader('zip', new Downloader\ZipDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process));
        $dm->setDownloader('rar', new Downloader\RarDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process));
        $dm->setDownloader('tar', new Downloader\TarDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process));
        $dm->setDownloader('gzip', new Downloader\GzipDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process));
        $dm->setDownloader('xz', new Downloader\XzDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process));
        $dm->setDownloader('phar', new Downloader\PharDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process));
        $dm->setDownloader('file', new Downloader\FileDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process));
        $dm->setDownloader('path', new Downloader\PathDownloader($io, $config, $httpDownloader, $eventDispatcher, $cache, $fs, $process));

        return $dm;
    }

    /**
     * @param  Config                     $config The configuration
     * @param  Downloader\DownloadManager $dm     Manager use to download sources
     * @return Archiver\ArchiveManager
     */
    public function createArchiveManager(Config $config, Downloader\DownloadManager $dm, Loop $loop)
    {
        $am = new Archiver\ArchiveManager($dm, $loop);
        if (class_exists(ZipArchive::class)) {
            $am->addArchiver(new Archiver\ZipArchiver);
        }
        if (class_exists(Phar::class)) {
            $am->addArchiver(new Archiver\PharArchiver);
        }

        return $am;
    }

    /**
     * @param  bool|'local'|'global' $disablePlugins Whether plugins should not be loaded, can be set to local or global to only disable local/global plugins
     */
    protected function createPluginManager(IOInterface $io, Composer $composer, ?PartialComposer $globalComposer = null, $disablePlugins = false): Plugin\PluginManager
    {
        return new Plugin\PluginManager($io, $composer, $globalComposer, $disablePlugins);
    }

    public function createInstallationManager(Loop $loop, IOInterface $io, ?EventDispatcher $eventDispatcher = null): Installer\InstallationManager
    {
        return new Installer\InstallationManager($loop, $io, $eventDispatcher);
    }

    protected function createDefaultInstallers(Installer\InstallationManager $im, PartialComposer $composer, IOInterface $io, ?ProcessExecutor $process = null): void
    {
        $fs = new Filesystem($process);
        $binaryInstaller = new Installer\BinaryInstaller($io, rtrim($composer->getConfig()->get('bin-dir'), '/'), $composer->getConfig()->get('bin-compat'), $fs, rtrim($composer->getConfig()->get('vendor-dir'), '/'));

        $im->addInstaller(new Installer\LibraryInstaller($io, $composer, null, $fs, $binaryInstaller));
        $im->addInstaller(new Installer\PluginInstaller($io, $composer, $fs, $binaryInstaller));
        $im->addInstaller(new Installer\MetapackageInstaller($io));
    }

    /**
     * @param InstalledRepositoryInterface   $repo repository to purge packages from
     * @param Installer\InstallationManager  $im   manager to check whether packages are still installed
     */
    protected function purgePackages(InstalledRepositoryInterface $repo, Installer\InstallationManager $im): void
    {
        foreach ($repo->getPackages() as $package) {
            if (!$im->isPackageInstalled($repo, $package)) {
                $repo->removePackage($package);
            }
        }
    }

    protected function loadRootPackage(RepositoryManager $rm, Config $config, VersionParser $parser, VersionGuesser $guesser, IOInterface $io): Package\Loader\RootPackageLoader
    {
        return new Package\Loader\RootPackageLoader($rm, $config, $parser, $guesser, $io);
    }

    /**
     * @param  IOInterface $io             IO instance
     * @param  mixed       $config         either a configuration array or a filename to read from, if null it will read from
     *                                     the default filename
     * @param  bool|'local'|'global' $disablePlugins Whether plugins should not be loaded, can be set to local or global to only disable local/global plugins
     * @param  bool        $disableScripts Whether scripts should not be run
     */
    public static function create(IOInterface $io, $config = null, $disablePlugins = false, bool $disableScripts = false): Composer
    {
        $factory = new static();

        // for BC reasons, if a config is passed in either as array or a path that is not the default composer.json path
        // we disable local plugins as they really should not be loaded from CWD
        // If you want to avoid this behavior, you should be calling createComposer directly with a $cwd arg set correctly
        // to the path where the composer.json being loaded resides
        if ($config !== null && $config !== self::getComposerFile() && $disablePlugins === false) {
            $disablePlugins = 'local';
        }

        return $factory->createComposer($io, $config, $disablePlugins, null, true, $disableScripts);
    }

    /**
     * If you are calling this in a plugin, you probably should instead use $composer->getLoop()->getHttpDownloader()
     *
     * @param  IOInterface    $io      IO instance
     * @param  Config         $config  Config instance
     * @param  mixed[]        $options Array of options passed directly to HttpDownloader constructor
     */
    public static function createHttpDownloader(IOInterface $io, Config $config, array $options = []): HttpDownloader
    {
        static $warned = false;
        $disableTls = false;
        // allow running the config command if disable-tls is in the arg list, even if openssl is missing, to allow disabling it via the config command
        if (isset($_SERVER['argv']) && in_array('disable-tls', $_SERVER['argv']) && (in_array('conf', $_SERVER['argv']) || in_array('config', $_SERVER['argv']))) {
            $warned = true;
            $disableTls = !extension_loaded('openssl');
        } elseif ($config->get('disable-tls') === true) {
            if (!$warned) {
                $io->writeError('<warning>You are running Composer with SSL/TLS protection disabled.</warning>');
            }
            $warned = true;
            $disableTls = true;
        } elseif (!extension_loaded('openssl')) {
            throw new Exception\NoSslException('The openssl extension is required for SSL/TLS protection but is not available. '
                . 'If you can not enable the openssl extension, you can disable this error, at your own risk, by setting the \'disable-tls\' option to true.');
        }
        $httpDownloaderOptions = [];
        if ($disableTls === false) {
            if ('' !== $config->get('cafile')) {
                $httpDownloaderOptions['ssl']['cafile'] = $config->get('cafile');
            }
            if ('' !== $config->get('capath')) {
                $httpDownloaderOptions['ssl']['capath'] = $config->get('capath');
            }
            $httpDownloaderOptions = array_replace_recursive($httpDownloaderOptions, $options);
        }
        try {
            $httpDownloader = new HttpDownloader($io, $config, $httpDownloaderOptions, $disableTls);
        } catch (TransportException $e) {
            if (false !== strpos($e->getMessage(), 'cafile')) {
                $io->write('<error>Unable to locate a valid CA certificate file. You must set a valid \'cafile\' option.</error>');
                $io->write('<error>A valid CA certificate file is required for SSL/TLS protection.</error>');
                $io->write('<error>You can disable this error, at your own risk, by setting the \'disable-tls\' option to true.</error>');
            }
            throw $e;
        }

        return $httpDownloader;
    }

    private static function loadComposerAuthEnv(Config $config, ?IOInterface $io): void
    {
        $composerAuthEnv = Platform::getEnv('COMPOSER_AUTH');
        if (false === $composerAuthEnv || '' === $composerAuthEnv) {
            return;
        }

        $authData = json_decode($composerAuthEnv);
        if (null === $authData) {
            throw new \UnexpectedValueException('COMPOSER_AUTH environment variable is malformed, should be a valid JSON object');
        }

        if ($io instanceof IOInterface) {
            $io->writeError('Loading auth config from COMPOSER_AUTH', true, IOInterface::DEBUG);
        }
        self::validateJsonSchema($io, $authData, JsonFile::AUTH_SCHEMA, 'COMPOSER_AUTH');
        $authData = json_decode($composerAuthEnv, true);
        if (null !== $authData) {
            $config->merge(['config' => $authData], 'COMPOSER_AUTH');
        }
    }

    private static function useXdg(): bool
    {
        foreach (array_keys($_SERVER) as $key) {
            if (strpos((string) $key, 'XDG_') === 0) {
                return true;
            }
        }

        if (Silencer::call('is_dir', '/etc/xdg')) {
            return true;
        }

        return false;
    }

    /**
     * @throws \RuntimeException
     */
    private static function getUserDir(): string
    {
        $home = Platform::getEnv('HOME');
        if (!$home) {
            throw new \RuntimeException('The HOME or COMPOSER_HOME environment variable must be set for composer to run correctly');
        }

        return rtrim(strtr($home, '\\', '/'), '/');
    }

    /**
     * @param mixed $fileOrData
     * @param JsonFile::*_SCHEMA $schema
     */
    private static function validateJsonSchema(?IOInterface $io, $fileOrData, int $schema = JsonFile::LAX_SCHEMA, ?string $source = null): void
    {
        if (Platform::isInputCompletionProcess()) {
            return;
        }

        try {
            if ($fileOrData instanceof JsonFile) {
                $fileOrData->validateSchema($schema);
            } else {
                if (null === $source) {
                    throw new \InvalidArgumentException('$source is required to be provided if $fileOrData is arbitrary data');
                }
                JsonFile::validateJsonSchema($source, $fileOrData, $schema);
            }
        } catch (JsonValidationException $e) {
            $msg = $e->getMessage().', this may result in errors and should be resolved:'.PHP_EOL.' - '.implode(PHP_EOL.' - ', $e->getErrors());
            if ($io instanceof IOInterface) {
                $io->writeError('<warning>'.$msg.'</>');
            } else {
                throw new UnexpectedValueException($msg);
            }
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\IO;

use Composer\Config;
use Psr\Log\LoggerInterface;

/**
 * The Input/Output helper interface.
 *
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 */
interface IOInterface extends LoggerInterface
{
    public const QUIET = 1;
    public const NORMAL = 2;
    public const VERBOSE = 4;
    public const VERY_VERBOSE = 8;
    public const DEBUG = 16;

    /**
     * Is this input means interactive?
     *
     * @return bool
     */
    public function isInteractive();

    /**
     * Is this output verbose?
     *
     * @return bool
     */
    public function isVerbose();

    /**
     * Is the output very verbose?
     *
     * @return bool
     */
    public function isVeryVerbose();

    /**
     * Is the output in debug verbosity?
     *
     * @return bool
     */
    public function isDebug();

    /**
     * Is this output decorated?
     *
     * @return bool
     */
    public function isDecorated();

    /**
     * Writes a message to the output.
     *
     * @param string|string[] $messages  The message as an array of lines or a single string
     * @param bool            $newline   Whether to add a newline or not
     * @param int             $verbosity Verbosity level from the VERBOSITY_* constants
     *
     * @return void
     */
    public function write($messages, bool $newline = true, int $verbosity = self::NORMAL);

    /**
     * Writes a message to the error output.
     *
     * @param string|string[] $messages  The message as an array of lines or a single string
     * @param bool            $newline   Whether to add a newline or not
     * @param int             $verbosity Verbosity level from the VERBOSITY_* constants
     *
     * @return void
     */
    public function writeError($messages, bool $newline = true, int $verbosity = self::NORMAL);

    /**
     * Writes a message to the output, without formatting it.
     *
     * @param string|string[] $messages  The message as an array of lines or a single string
     * @param bool            $newline   Whether to add a newline or not
     * @param int             $verbosity Verbosity level from the VERBOSITY_* constants
     *
     * @return void
     */
    public function writeRaw($messages, bool $newline = true, int $verbosity = self::NORMAL);

    /**
     * Writes a message to the error output, without formatting it.
     *
     * @param string|string[] $messages  The message as an array of lines or a single string
     * @param bool            $newline   Whether to add a newline or not
     * @param int             $verbosity Verbosity level from the VERBOSITY_* constants
     *
     * @return void
     */
    public function writeErrorRaw($messages, bool $newline = true, int $verbosity = self::NORMAL);

    /**
     * Overwrites a previous message to the output.
     *
     * @param string|string[] $messages  The message as an array of lines or a single string
     * @param bool            $newline   Whether to add a newline or not
     * @param int             $size      The size of line
     * @param int             $verbosity Verbosity level from the VERBOSITY_* constants
     *
     * @return void
     */
    public function overwrite($messages, bool $newline = true, ?int $size = null, int $verbosity = self::NORMAL);

    /**
     * Overwrites a previous message to the error output.
     *
     * @param string|string[] $messages  The message as an array of lines or a single string
     * @param bool            $newline   Whether to add a newline or not
     * @param int             $size      The size of line
     * @param int             $verbosity Verbosity level from the VERBOSITY_* constants
     *
     * @return void
     */
    public function overwriteError($messages, bool $newline = true, ?int $size = null, int $verbosity = self::NORMAL);

    /**
     * Asks a question to the user.
     *
     * @param string $question The question to ask
     * @param string|bool|int|float|null $default  The default answer if none is given by the user
     *
     * @throws \RuntimeException If there is no data to read in the input stream
     * @return mixed       The user answer
     */
    public function ask(string $question, $default = null);

    /**
     * Asks a confirmation to the user.
     *
     * The question will be asked until the user answers by nothing, yes, or no.
     *
     * @param string $question The question to ask
     * @param bool   $default  The default answer if the user enters nothing
     *
     * @return bool true if the user has confirmed, false otherwise
     */
    public function askConfirmation(string $question, bool $default = true);

    /**
     * Asks for a value and validates the response.
     *
     * The validator receives the data to validate. It must return the
     * validated data when the data is valid and throw an exception
     * otherwise.
     *
     * @param string   $question  The question to ask
     * @param callable $validator A PHP callback
     * @param null|int $attempts  Max number of times to ask before giving up (default of null means infinite)
     * @param mixed    $default   The default answer if none is given by the user
     *
     * @throws \Exception When any of the validators return an error
     * @return mixed
     */
    public function askAndValidate(string $question, callable $validator, ?int $attempts = null, $default = null);

    /**
     * Asks a question to the user and hide the answer.
     *
     * @param string $question The question to ask
     *
     * @return string|null The answer
     */
    public function askAndHideAnswer(string $question);

    /**
     * Asks the user to select a value.
     *
     * @param string      $question     The question to ask
     * @param string[]    $choices      List of choices to pick from
     * @param bool|string $default      The default answer if the user enters nothing
     * @param bool|int    $attempts     Max number of times to ask before giving up (false by default, which means infinite)
     * @param string      $errorMessage Message which will be shown if invalid value from choice list would be picked
     * @param bool        $multiselect  Select more than one value separated by comma
     *
     * @throws \InvalidArgumentException
     *
     * @return int|string|list<string>|bool     The selected value or values (the key of the choices array)
     * @phpstan-return ($multiselect is true ? list<string> : string|int|bool)
     */
    public function select(string $question, array $choices, $default, $attempts = false, string $errorMessage = 'Value "%s" is invalid', bool $multiselect = false);

    /**
     * Get all authentication information entered.
     *
     * @return array<string, array{username: string|null, password: string|null}> The map of authentication data
     */
    public function getAuthentications();

    /**
     * Verify if the repository has a authentication information.
     *
     * @param string $repositoryName The unique name of repository
     *
     * @return bool
     */
    public function hasAuthentication(string $repositoryName);

    /**
     * Get the username and password of repository.
     *
     * @param string $repositoryName The unique name of repository
     *
     * @return array{username: string|null, password: string|null}
     */
    public function getAuthentication(string $repositoryName);

    /**
     * Set the authentication information for the repository.
     *
     * @param string      $repositoryName The unique name of repository
     * @param string      $username       The username
     * @param null|string $password       The password
     *
     * @return void
     */
    public function setAuthentication(string $repositoryName, string $username, ?string $password = null);

    /**
     * Loads authentications from a config instance
     *
     * @return void
     */
    public function loadConfiguration(Config $config);
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\IO;

/**
 * IOInterface that is not interactive and never writes the output
 *
 * @author Christophe Coevoet <stof@notk.org>
 */
class NullIO extends BaseIO
{
    /**
     * @inheritDoc
     */
    public function isInteractive(): bool
    {
        return false;
    }

    /**
     * @inheritDoc
     */
    public function isVerbose(): bool
    {
        return false;
    }

    /**
     * @inheritDoc
     */
    public function isVeryVerbose(): bool
    {
        return false;
    }

    /**
     * @inheritDoc
     */
    public function isDebug(): bool
    {
        return false;
    }

    /**
     * @inheritDoc
     */
    public function isDecorated(): bool
    {
        return false;
    }

    /**
     * @inheritDoc
     */
    public function write($messages, bool $newline = true, int $verbosity = self::NORMAL): void
    {
    }

    /**
     * @inheritDoc
     */
    public function writeError($messages, bool $newline = true, int $verbosity = self::NORMAL): void
    {
    }

    /**
     * @inheritDoc
     */
    public function overwrite($messages, bool $newline = true, ?int $size = null, int $verbosity = self::NORMAL): void
    {
    }

    /**
     * @inheritDoc
     */
    public function overwriteError($messages, bool $newline = true, ?int $size = null, int $verbosity = self::NORMAL): void
    {
    }

    /**
     * @inheritDoc
     */
    public function ask($question, $default = null)
    {
        return $default;
    }

    /**
     * @inheritDoc
     */
    public function askConfirmation($question, $default = true): bool
    {
        return $default;
    }

    /**
     * @inheritDoc
     */
    public function askAndValidate($question, $validator, $attempts = null, $default = null)
    {
        return $default;
    }

    /**
     * @inheritDoc
     */
    public function askAndHideAnswer($question): ?string
    {
        return null;
    }

    /**
     * @inheritDoc
     */
    public function select($question, $choices, $default, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false)
    {
        return $default;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\IO;

use Composer\Question\StrictConfirmationQuestion;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\Question;

/**
 * The Input/Output helper.
 *
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ConsoleIO extends BaseIO
{
    /** @var InputInterface */
    protected $input;
    /** @var OutputInterface */
    protected $output;
    /** @var HelperSet */
    protected $helperSet;
    /** @var string */
    protected $lastMessage = '';
    /** @var string */
    protected $lastMessageErr = '';

    /** @var float */
    private $startTime;
    /** @var array<IOInterface::*, OutputInterface::VERBOSITY_*> */
    private $verbosityMap;

    /**
     * Constructor.
     *
     * @param InputInterface  $input     The input instance
     * @param OutputInterface $output    The output instance
     * @param HelperSet       $helperSet The helperSet instance
     */
    public function __construct(InputInterface $input, OutputInterface $output, HelperSet $helperSet)
    {
        $this->input = $input;
        $this->output = $output;
        $this->helperSet = $helperSet;
        $this->verbosityMap = [
            self::QUIET => OutputInterface::VERBOSITY_QUIET,
            self::NORMAL => OutputInterface::VERBOSITY_NORMAL,
            self::VERBOSE => OutputInterface::VERBOSITY_VERBOSE,
            self::VERY_VERBOSE => OutputInterface::VERBOSITY_VERY_VERBOSE,
            self::DEBUG => OutputInterface::VERBOSITY_DEBUG,
        ];
    }

    /**
     * @return void
     */
    public function enableDebugging(float $startTime)
    {
        $this->startTime = $startTime;
    }

    /**
     * @inheritDoc
     */
    public function isInteractive()
    {
        return $this->input->isInteractive();
    }

    /**
     * @inheritDoc
     */
    public function isDecorated()
    {
        return $this->output->isDecorated();
    }

    /**
     * @inheritDoc
     */
    public function isVerbose()
    {
        return $this->output->isVerbose();
    }

    /**
     * @inheritDoc
     */
    public function isVeryVerbose()
    {
        return $this->output->isVeryVerbose();
    }

    /**
     * @inheritDoc
     */
    public function isDebug()
    {
        return $this->output->isDebug();
    }

    /**
     * @inheritDoc
     */
    public function write($messages, bool $newline = true, int $verbosity = self::NORMAL)
    {
        $this->doWrite($messages, $newline, false, $verbosity);
    }

    /**
     * @inheritDoc
     */
    public function writeError($messages, bool $newline = true, int $verbosity = self::NORMAL)
    {
        $this->doWrite($messages, $newline, true, $verbosity);
    }

    /**
     * @inheritDoc
     */
    public function writeRaw($messages, bool $newline = true, int $verbosity = self::NORMAL)
    {
        $this->doWrite($messages, $newline, false, $verbosity, true);
    }

    /**
     * @inheritDoc
     */
    public function writeErrorRaw($messages, bool $newline = true, int $verbosity = self::NORMAL)
    {
        $this->doWrite($messages, $newline, true, $verbosity, true);
    }

    /**
     * @param string[]|string $messages
     */
    private function doWrite($messages, bool $newline, bool $stderr, int $verbosity, bool $raw = false): void
    {
        $sfVerbosity = $this->verbosityMap[$verbosity];
        if ($sfVerbosity > $this->output->getVerbosity()) {
            return;
        }

        if ($raw) {
            $sfVerbosity |= OutputInterface::OUTPUT_RAW;
        }

        if (null !== $this->startTime) {
            $memoryUsage = memory_get_usage() / 1024 / 1024;
            $timeSpent = microtime(true) - $this->startTime;
            $messages = array_map(static function ($message) use ($memoryUsage, $timeSpent): string {
                return sprintf('[%.1fMiB/%.2fs] %s', $memoryUsage, $timeSpent, $message);
            }, (array) $messages);
        }

        if (true === $stderr && $this->output instanceof ConsoleOutputInterface) {
            $this->output->getErrorOutput()->write($messages, $newline, $sfVerbosity);
            $this->lastMessageErr = implode($newline ? "\n" : '', (array) $messages);

            return;
        }

        $this->output->write($messages, $newline, $sfVerbosity);
        $this->lastMessage = implode($newline ? "\n" : '', (array) $messages);
    }

    /**
     * @inheritDoc
     */
    public function overwrite($messages, bool $newline = true, ?int $size = null, int $verbosity = self::NORMAL)
    {
        $this->doOverwrite($messages, $newline, $size, false, $verbosity);
    }

    /**
     * @inheritDoc
     */
    public function overwriteError($messages, bool $newline = true, ?int $size = null, int $verbosity = self::NORMAL)
    {
        $this->doOverwrite($messages, $newline, $size, true, $verbosity);
    }

    /**
     * @param string[]|string $messages
     */
    private function doOverwrite($messages, bool $newline, ?int $size, bool $stderr, int $verbosity): void
    {
        // messages can be an array, let's convert it to string anyway
        $messages = implode($newline ? "\n" : '', (array) $messages);

        // since overwrite is supposed to overwrite last message...
        if (!isset($size)) {
            // removing possible formatting of lastMessage with strip_tags
            $size = strlen(strip_tags($stderr ? $this->lastMessageErr : $this->lastMessage));
        }
        // ...let's fill its length with backspaces
        $this->doWrite(str_repeat("\x08", $size), false, $stderr, $verbosity);

        // write the new message
        $this->doWrite($messages, false, $stderr, $verbosity);

        // In cmd.exe on Win8.1 (possibly 10?), the line can not be cleared, so we need to
        // track the length of previous output and fill it with spaces to make sure the line is cleared.
        // See https://github.com/composer/composer/pull/5836 for more details
        $fill = $size - strlen(strip_tags($messages));
        if ($fill > 0) {
            // whitespace whatever has left
            $this->doWrite(str_repeat(' ', $fill), false, $stderr, $verbosity);
            // move the cursor back
            $this->doWrite(str_repeat("\x08", $fill), false, $stderr, $verbosity);
        }

        if ($newline) {
            $this->doWrite('', true, $stderr, $verbosity);
        }

        if ($stderr) {
            $this->lastMessageErr = $messages;
        } else {
            $this->lastMessage = $messages;
        }
    }

    /**
     * @return ProgressBar
     */
    public function getProgressBar(int $max = 0)
    {
        return new ProgressBar($this->getErrorOutput(), $max);
    }

    /**
     * @inheritDoc
     */
    public function ask($question, $default = null)
    {
        /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */
        $helper = $this->helperSet->get('question');
        $question = new Question($question, $default);

        return $helper->ask($this->input, $this->getErrorOutput(), $question);
    }

    /**
     * @inheritDoc
     */
    public function askConfirmation($question, $default = true)
    {
        /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */
        $helper = $this->helperSet->get('question');
        $question = new StrictConfirmationQuestion($question, $default);

        return $helper->ask($this->input, $this->getErrorOutput(), $question);
    }

    /**
     * @inheritDoc
     */
    public function askAndValidate($question, $validator, $attempts = null, $default = null)
    {
        /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */
        $helper = $this->helperSet->get('question');
        $question = new Question($question, $default);
        $question->setValidator($validator);
        $question->setMaxAttempts($attempts);

        return $helper->ask($this->input, $this->getErrorOutput(), $question);
    }

    /**
     * @inheritDoc
     */
    public function askAndHideAnswer($question)
    {
        /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */
        $helper = $this->helperSet->get('question');
        $question = new Question($question);
        $question->setHidden(true);

        return $helper->ask($this->input, $this->getErrorOutput(), $question);
    }

    /**
     * @inheritDoc
     */
    public function select($question, $choices, $default, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false)
    {
        /** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */
        $helper = $this->helperSet->get('question');
        $question = new ChoiceQuestion($question, $choices, $default);
        $question->setMaxAttempts($attempts ?: null); // IOInterface requires false, and Question requires null or int
        $question->setErrorMessage($errorMessage);
        $question->setMultiselect($multiselect);

        $result = $helper->ask($this->input, $this->getErrorOutput(), $question);

        $isAssoc = (bool) \count(array_filter(array_keys($choices), 'is_string'));
        if ($isAssoc) {
            return $result;
        }

        if (!is_array($result)) {
            return (string) array_search($result, $choices, true);
        }

        $results = [];
        foreach ($choices as $index => $choice) {
            if (in_array($choice, $result, true)) {
                $results[] = (string) $index;
            }
        }

        return $results;
    }

    public function getTable(): Table
    {
        return new Table($this->output);
    }

    private function getErrorOutput(): OutputInterface
    {
        if ($this->output instanceof ConsoleOutputInterface) {
            return $this->output->getErrorOutput();
        }

        return $this->output;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\IO;

use Composer\Config;
use Composer\Pcre\Preg;
use Composer\Util\ProcessExecutor;
use Composer\Util\Silencer;
use Psr\Log\LogLevel;

abstract class BaseIO implements IOInterface
{
    /** @var array<string, array{username: string|null, password: string|null}> */
    protected $authentications = [];

    /**
     * @inheritDoc
     */
    public function getAuthentications()
    {
        return $this->authentications;
    }

    /**
     * @return void
     */
    public function resetAuthentications()
    {
        $this->authentications = [];
    }

    /**
     * @inheritDoc
     */
    public function hasAuthentication($repositoryName)
    {
        return isset($this->authentications[$repositoryName]);
    }

    /**
     * @inheritDoc
     */
    public function getAuthentication($repositoryName)
    {
        if (isset($this->authentications[$repositoryName])) {
            return $this->authentications[$repositoryName];
        }

        return ['username' => null, 'password' => null];
    }

    /**
     * @inheritDoc
     */
    public function setAuthentication($repositoryName, $username, $password = null)
    {
        $this->authentications[$repositoryName] = ['username' => $username, 'password' => $password];
    }

    /**
     * @inheritDoc
     */
    public function writeRaw($messages, bool $newline = true, int $verbosity = self::NORMAL)
    {
        $this->write($messages, $newline, $verbosity);
    }

    /**
     * @inheritDoc
     */
    public function writeErrorRaw($messages, bool $newline = true, int $verbosity = self::NORMAL)
    {
        $this->writeError($messages, $newline, $verbosity);
    }

    /**
     * Check for overwrite and set the authentication information for the repository.
     *
     * @param string $repositoryName The unique name of repository
     * @param string $username       The username
     * @param string $password       The password
     *
     * @return void
     */
    protected function checkAndSetAuthentication(string $repositoryName, string $username, ?string $password = null)
    {
        if ($this->hasAuthentication($repositoryName)) {
            $auth = $this->getAuthentication($repositoryName);
            if ($auth['username'] === $username && $auth['password'] === $password) {
                return;
            }

            $this->writeError(
                sprintf(
                    "<warning>Warning: You should avoid overwriting already defined auth settings for %s.</warning>",
                    $repositoryName
                )
            );
        }
        $this->setAuthentication($repositoryName, $username, $password);
    }

    /**
     * @inheritDoc
     */
    public function loadConfiguration(Config $config)
    {
        $bitbucketOauth = $config->get('bitbucket-oauth');
        $githubOauth = $config->get('github-oauth');
        $gitlabOauth = $config->get('gitlab-oauth');
        $gitlabToken = $config->get('gitlab-token');
        $httpBasic = $config->get('http-basic');
        $bearerToken = $config->get('bearer');

        // reload oauth tokens from config if available

        foreach ($bitbucketOauth as $domain => $cred) {
            $this->checkAndSetAuthentication($domain, $cred['consumer-key'], $cred['consumer-secret']);
        }

        foreach ($githubOauth as $domain => $token) {
            if ($domain !== 'github.com' && !in_array($domain, $config->get('github-domains'), true)) {
                $this->debug($domain.' is not in the configured github-domains, adding it implicitly as authentication is configured for this domain');
                $config->merge(['config' => ['github-domains' => array_merge($config->get('github-domains'), [$domain])]], 'implicit-due-to-auth');
            }

            // allowed chars for GH tokens are from https://github.blog/changelog/2021-03-04-authentication-token-format-updates/
            // plus dots which were at some point used for GH app integration tokens
            if (!Preg::isMatch('{^[.A-Za-z0-9_]+$}', $token)) {
                throw new \UnexpectedValueException('Your github oauth token for '.$domain.' contains invalid characters: "'.$token.'"');
            }
            $this->checkAndSetAuthentication($domain, $token, 'x-oauth-basic');
        }

        foreach ($gitlabOauth as $domain => $token) {
            if ($domain !== 'gitlab.com' && !in_array($domain, $config->get('gitlab-domains'), true)) {
                $this->debug($domain.' is not in the configured gitlab-domains, adding it implicitly as authentication is configured for this domain');
                $config->merge(['config' => ['gitlab-domains' => array_merge($config->get('gitlab-domains'), [$domain])]], 'implicit-due-to-auth');
            }

            $token = is_array($token) ? $token["token"] : $token;
            $this->checkAndSetAuthentication($domain, $token, 'oauth2');
        }

        foreach ($gitlabToken as $domain => $token) {
            if ($domain !== 'gitlab.com' && !in_array($domain, $config->get('gitlab-domains'), true)) {
                $this->debug($domain.' is not in the configured gitlab-domains, adding it implicitly as authentication is configured for this domain');
                $config->merge(['config' => ['gitlab-domains' => array_merge($config->get('gitlab-domains'), [$domain])]], 'implicit-due-to-auth');
            }

            $username = is_array($token) ? $token["username"] : $token;
            $password = is_array($token) ? $token["token"] : 'private-token';
            $this->checkAndSetAuthentication($domain, $username, $password);
        }

        // reload http basic credentials from config if available
        foreach ($httpBasic as $domain => $cred) {
            $this->checkAndSetAuthentication($domain, $cred['username'], $cred['password']);
        }

        foreach ($bearerToken as $domain => $token) {
            $this->checkAndSetAuthentication($domain, $token, 'bearer');
        }

        // setup process timeout
        ProcessExecutor::setTimeout($config->get('process-timeout'));
    }

    /**
     * @param string|\Stringable $message
     */
    public function emergency($message, array $context = []): void
    {
        $this->log(LogLevel::EMERGENCY, $message, $context);
    }

    /**
     * @param string|\Stringable $message
     */
    public function alert($message, array $context = []): void
    {
        $this->log(LogLevel::ALERT, $message, $context);
    }

    /**
     * @param string|\Stringable $message
     */
    public function critical($message, array $context = []): void
    {
        $this->log(LogLevel::CRITICAL, $message, $context);
    }

    /**
     * @param string|\Stringable $message
     */
    public function error($message, array $context = []): void
    {
        $this->log(LogLevel::ERROR, $message, $context);
    }

    /**
     * @param string|\Stringable $message
     */
    public function warning($message, array $context = []): void
    {
        $this->log(LogLevel::WARNING, $message, $context);
    }

    /**
     * @param string|\Stringable $message
     */
    public function notice($message, array $context = []): void
    {
        $this->log(LogLevel::NOTICE, $message, $context);
    }

    /**
     * @param string|\Stringable $message
     */
    public function info($message, array $context = []): void
    {
        $this->log(LogLevel::INFO, $message, $context);
    }

    /**
     * @param string|\Stringable $message
     */
    public function debug($message, array $context = []): void
    {
        $this->log(LogLevel::DEBUG, $message, $context);
    }

    /**
     * @param mixed|LogLevel::* $level
     * @param string|\Stringable $message
     */
    public function log($level, $message, array $context = []): void
    {
        $message = (string) $message;

        if ($context !== []) {
            $json = Silencer::call('json_encode', $context, JSON_INVALID_UTF8_IGNORE|JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE);
            if ($json !== false) {
                $message .= ' ' . $json;
            }
        }

        if (in_array($level, [LogLevel::EMERGENCY, LogLevel::ALERT, LogLevel::CRITICAL, LogLevel::ERROR])) {
            $this->writeError('<error>'.$message.'</error>');
        } elseif ($level === LogLevel::WARNING) {
            $this->writeError('<warning>'.$message.'</warning>');
        } elseif ($level === LogLevel::NOTICE) {
            $this->writeError('<info>'.$message.'</info>', true, self::VERBOSE);
        } elseif ($level === LogLevel::INFO) {
            $this->writeError('<info>'.$message.'</info>', true, self::VERY_VERBOSE);
        } else {
            $this->writeError($message, true, self::DEBUG);
        }
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\IO;

use Composer\Pcre\Preg;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\StreamOutput;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Input\StreamableInputInterface;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Helper\HelperSet;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class BufferIO extends ConsoleIO
{
    public function __construct(string $input = '', int $verbosity = StreamOutput::VERBOSITY_NORMAL, ?OutputFormatterInterface $formatter = null)
    {
        $input = new StringInput($input);
        $input->setInteractive(false);

        $stream = fopen('php://memory', 'rw');
        if ($stream === false) {
            throw new \RuntimeException('Unable to open memory output stream');
        }
        $output = new StreamOutput($stream, $verbosity, $formatter !== null ? $formatter->isDecorated() : false, $formatter);

        parent::__construct($input, $output, new HelperSet([
            new QuestionHelper(),
        ]));
    }

    /**
     * @return string output
     */
    public function getOutput(): string
    {
        assert($this->output instanceof StreamOutput);
        fseek($this->output->getStream(), 0);

        $output = (string) stream_get_contents($this->output->getStream());

        $output = Preg::replaceCallback("{(?<=^|\n|\x08)(.+?)(\x08+)}", static function ($matches): string {
            $pre = strip_tags($matches[1]);

            if (strlen($pre) === strlen($matches[2])) {
                return '';
            }

            // TODO reverse parse the string, skipping span tags and \033\[([0-9;]+)m(.*?)\033\[0m style blobs
            return rtrim($matches[1])."\n";
        }, $output);

        return $output;
    }

    /**
     * @param string[] $inputs
     *
     * @see createStream
     */
    public function setUserInputs(array $inputs): void
    {
        if (!$this->input instanceof StreamableInputInterface) {
            throw new \RuntimeException('Setting the user inputs requires at least the version 3.2 of the symfony/console component.');
        }

        $this->input->setStream($this->createStream($inputs));
        $this->input->setInteractive(true);
    }

    /**
     * @param string[] $inputs
     *
     * @return resource stream
     */
    private function createStream(array $inputs)
    {
        $stream = fopen('php://memory', 'r+');
        if ($stream === false) {
            throw new \RuntimeException('Unable to open memory output stream');
        }

        foreach ($inputs as $input) {
            fwrite($stream, $input.PHP_EOL);
        }

        rewind($stream);

        return $stream;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer;

use Composer\Json\JsonFile;
use Composer\CaBundle\CaBundle;
use Composer\Pcre\Preg;
use Composer\Util\ProcessExecutor;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Process\Process;
use Seld\PharUtils\Timestamps;
use Seld\PharUtils\Linter;

/**
 * The Compiler class compiles composer into a phar
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Compiler
{
    /** @var string */
    private $version;
    /** @var string */
    private $branchAliasVersion = '';
    /** @var \DateTime */
    private $versionDate;

    /**
     * Compiles composer into a single phar file
     *
     * @param string $pharFile The full path to the file to create
     *
     * @throws \RuntimeException
     */
    public function compile(string $pharFile = 'composer.phar'): void
    {
        if (file_exists($pharFile)) {
            unlink($pharFile);
        }

        $process = new ProcessExecutor();

        if (0 !== $process->execute(['git', 'log', '--pretty=%H', '-n1', 'HEAD'], $output, dirname(dirname(__DIR__)))) {
            throw new \RuntimeException('Can\'t run git log. You must ensure to run compile from composer git repository clone and that git binary is available.');
        }
        $this->version = trim($output);

        if (0 !== $process->execute(['git', 'log', '-n1', '--pretty=%ci', 'HEAD'], $output, dirname(dirname(__DIR__)))) {
            throw new \RuntimeException('Can\'t run git log. You must ensure to run compile from composer git repository clone and that git binary is available.');
        }

        $this->versionDate = new \DateTime(trim($output));
        $this->versionDate->setTimezone(new \DateTimeZone('UTC'));

        if (0 === $process->execute(['git', 'describe', '--tags', '--exact-match', 'HEAD'], $output, dirname(dirname(__DIR__)))) {
            $this->version = trim($output);
        } else {
            // get branch-alias defined in composer.json for dev-main (if any)
            $localConfig = __DIR__.'/../../composer.json';
            $file = new JsonFile($localConfig);
            $localConfig = $file->read();
            if (isset($localConfig['extra']['branch-alias']['dev-main'])) {
                $this->branchAliasVersion = $localConfig['extra']['branch-alias']['dev-main'];
            }
        }

        if ('' === $this->version) {
            throw new \UnexpectedValueException('Version detection failed');
        }

        $phar = new \Phar($pharFile, 0, 'composer.phar');
        $phar->setSignatureAlgorithm(\Phar::SHA512);

        $phar->startBuffering();

        $finderSort = static function ($a, $b): int {
            return strcmp(strtr($a->getRealPath(), '\\', '/'), strtr($b->getRealPath(), '\\', '/'));
        };

        // Add Composer sources
        $finder = new Finder();
        $finder->files()
            ->ignoreVCS(true)
            ->name('*.php')
            ->notName('Compiler.php')
            ->notName('ClassLoader.php')
            ->notName('InstalledVersions.php')
            ->in(__DIR__.'/..')
            ->sort($finderSort)
        ;
        foreach ($finder as $file) {
            $this->addFile($phar, $file);
        }
        // Add runtime utilities separately to make sure they retains the docblocks as these will get copied into projects
        $this->addFile($phar, new \SplFileInfo(__DIR__ . '/Autoload/ClassLoader.php'), false);
        $this->addFile($phar, new \SplFileInfo(__DIR__ . '/InstalledVersions.php'), false);

        // Add Composer resources
        $finder = new Finder();
        $finder->files()
            ->in(__DIR__.'/../../res')
            ->sort($finderSort)
        ;
        foreach ($finder as $file) {
            $this->addFile($phar, $file, false);
        }

        // Add vendor files
        $finder = new Finder();
        $finder->files()
            ->ignoreVCS(true)
            ->notPath('/\/(composer\.(?:json|lock)|[A-Z]+\.md(?:own)?|\.gitignore|appveyor.yml|phpunit\.xml\.dist|phpstan\.neon\.dist|phpstan-config\.neon|phpstan-baseline\.neon|UPGRADE.*\.(?:md|txt))$/')
            ->notPath('/bin\/(jsonlint|validate-json|simple-phpunit|phpstan|phpstan\.phar)(\.bat)?$/')
            ->notPath('justinrainbow/json-schema/demo/')
            ->notPath('justinrainbow/json-schema/dist/')
            ->notPath('composer/pcre/extension.neon')
            ->notPath('composer/LICENSE')
            ->exclude('Tests')
            ->exclude('tests')
            ->exclude('docs')
            ->in(__DIR__.'/../../vendor/')
            ->sort($finderSort)
        ;

        $extraFiles = [];
        foreach ([
            __DIR__ . '/../../vendor/composer/installed.json',
            __DIR__ . '/../../vendor/composer/spdx-licenses/res/spdx-exceptions.json',
            __DIR__ . '/../../vendor/composer/spdx-licenses/res/spdx-licenses.json',
            CaBundle::getBundledCaBundlePath(),
            __DIR__ . '/../../vendor/symfony/console/Resources/bin/hiddeninput.exe',
            __DIR__ . '/../../vendor/symfony/console/Resources/completion.bash',
        ] as $file) {
            $extraFiles[$file] = realpath($file);
            if (!file_exists($file)) {
                throw new \RuntimeException('Extra file listed is missing from the filesystem: '.$file);
            }
        }
        $unexpectedFiles = [];

        foreach ($finder as $file) {
            if (false !== ($index = array_search($file->getRealPath(), $extraFiles, true))) {
                unset($extraFiles[$index]);
            } elseif (!Preg::isMatch('{(^LICENSE(?:\.txt)?$|\.php$)}', $file->getFilename())) {
                $unexpectedFiles[] = (string) $file;
            }

            if (Preg::isMatch('{\.php[\d.]*$}', $file->getFilename())) {
                $this->addFile($phar, $file);
            } else {
                $this->addFile($phar, $file, false);
            }
        }

        if (count($extraFiles) > 0) {
            throw new \RuntimeException('These files were expected but not added to the phar, they might be excluded or gone from the source package:'.PHP_EOL.var_export($extraFiles, true));
        }
        if (count($unexpectedFiles) > 0) {
            throw new \RuntimeException('These files were unexpectedly added to the phar, make sure they are excluded or listed in $extraFiles:'.PHP_EOL.var_export($unexpectedFiles, true));
        }

        // Add bin/composer
        $this->addComposerBin($phar);

        // Stubs
        $phar->setStub($this->getStub());

        $phar->stopBuffering();

        // disabled for interoperability with systems without gzip ext
        // $phar->compressFiles(\Phar::GZ);

        $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../LICENSE'), false);

        unset($phar);

        // re-sign the phar with reproducible timestamp / signature
        $util = new Timestamps($pharFile);
        $util->updateTimestamps($this->versionDate);
        $util->save($pharFile, \Phar::SHA512);

        Linter::lint($pharFile, [
            'vendor/symfony/console/Attribute/AsCommand.php',
            'vendor/symfony/polyfill-intl-grapheme/bootstrap80.php',
            'vendor/symfony/polyfill-intl-normalizer/bootstrap80.php',
            'vendor/symfony/polyfill-mbstring/bootstrap80.php',
            'vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php',
            'vendor/symfony/service-contracts/Attribute/SubscribedService.php',
        ]);
    }

    private function getRelativeFilePath(\SplFileInfo $file): string
    {
        $realPath = $file->getRealPath();
        $pathPrefix = dirname(__DIR__, 2).DIRECTORY_SEPARATOR;

        $pos = strpos($realPath, $pathPrefix);
        $relativePath = ($pos !== false) ? substr_replace($realPath, '', $pos, strlen($pathPrefix)) : $realPath;

        return strtr($relativePath, '\\', '/');
    }

    private function addFile(\Phar $phar, \SplFileInfo $file, bool $strip = true): void
    {
        $path = $this->getRelativeFilePath($file);
        $content = file_get_contents((string) $file);
        if ($strip) {
            $content = $this->stripWhitespace($content);
        } elseif ('LICENSE' === $file->getFilename()) {
            $content = "\n".$content."\n";
        }

        if ($path === 'src/Composer/Composer.php') {
            $content = strtr(
                $content,
                [
                    '@package_version@' => $this->version,
                    '@package_branch_alias_version@' => $this->branchAliasVersion,
                    '@release_date@' => $this->versionDate->format('Y-m-d H:i:s'),
                ]
            );
            $content = Preg::replace('{SOURCE_VERSION = \'[^\']+\';}', 'SOURCE_VERSION = \'\';', $content);
        }

        $phar->addFromString($path, $content);
    }

    private function addComposerBin(\Phar $phar): void
    {
        $content = file_get_contents(__DIR__.'/../../bin/composer');
        $content = Preg::replace('{^#!/usr/bin/env php\s*}', '', $content);
        $phar->addFromString('bin/composer', $content);
    }

    /**
     * Removes whitespace from a PHP source string while preserving line numbers.
     *
     * @param  string $source A PHP string
     * @return string The PHP string with the whitespace removed
     */
    private function stripWhitespace(string $source): string
    {
        if (!function_exists('token_get_all')) {
            return $source;
        }

        $output = '';
        foreach (token_get_all($source) as $token) {
            if (is_string($token)) {
                $output .= $token;
            } elseif (in_array($token[0], [T_COMMENT, T_DOC_COMMENT])) {
                $output .= str_repeat("\n", substr_count($token[1], "\n"));
            } elseif (T_WHITESPACE === $token[0]) {
                // reduce wide spaces
                $whitespace = Preg::replace('{[ \t]+}', ' ', $token[1]);
                // normalize newlines to \n
                $whitespace = Preg::replace('{(?:\r\n|\r|\n)}', "\n", $whitespace);
                // trim leading spaces
                $whitespace = Preg::replace('{\n +}', "\n", $whitespace);
                $output .= $whitespace;
            } else {
                $output .= $token[1];
            }
        }

        return $output;
    }

    private function getStub(): string
    {
        $stub = <<<'EOF'
#!/usr/bin/env php
<?php
/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view
 * the license that is located at the bottom of this file.
 */

// Avoid APC causing random fatal errors per https://github.com/composer/composer/issues/264
if (extension_loaded('apc') && filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN) && filter_var(ini_get('apc.cache_by_default'), FILTER_VALIDATE_BOOLEAN)) {
    if (version_compare(phpversion('apc'), '3.0.12', '>=')) {
        ini_set('apc.cache_by_default', 0);
    } else {
        fwrite(STDERR, 'Warning: APC <= 3.0.12 may cause fatal errors when running composer commands.'.PHP_EOL);
        fwrite(STDERR, 'Update APC, or set apc.enable_cli or apc.cache_by_default to 0 in your php.ini.'.PHP_EOL);
    }
}

if (!class_exists('Phar')) {
    echo 'PHP\'s phar extension is missing. Composer requires it to run. Enable the extension or recompile php without --disable-phar then try again.' . PHP_EOL;
    exit(1);
}

Phar::mapPhar('composer.phar');

EOF;

        // add warning once the phar is older than 60 days
        if (Preg::isMatch('{^[a-f0-9]+$}', $this->version)) {
            $warningTime = ((int) $this->versionDate->format('U')) + 60 * 86400;
            $stub .= "define('COMPOSER_DEV_WARNING_TIME', $warningTime);\n";
        }

        return $stub . <<<'EOF'
require 'phar://composer.phar/bin/composer';

__HALT_COMPILER();
EOF;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use Composer\Autoload\ClassLoader;

function includeIfExists(string $file): ?ClassLoader
{
    return file_exists($file) ? include $file : null;
}

if ((!$loader = includeIfExists(__DIR__.'/../vendor/autoload.php')) && (!$loader = includeIfExists(__DIR__.'/../../../autoload.php'))) {
    echo 'You must set up the project dependencies using `composer install`'.PHP_EOL.
        'See https://getcomposer.org/download/ for instructions on installing Composer'.PHP_EOL;
    exit(1);
}

return $loader;
#!/usr/bin/env php
<?php

if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') {
    if (0 === strpos(__FILE__, 'phar:') && ini_get('register_argc_argv')) {
        echo 'Composer cannot be run safely on non-CLI SAPIs with register_argc_argv=On. Aborting.'.PHP_EOL;
        exit(1);
    }

    echo 'Warning: Composer should be invoked via the CLI version of PHP, not the '.PHP_SAPI.' SAPI'.PHP_EOL;
}

if (PHP_VERSION_ID < 70205) {
    echo 'Composer 2.3.0 dropped support for PHP <7.2.5 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
    exit(1);
}

setlocale(LC_ALL, 'C');
require __DIR__.'/../src/bootstrap.php';

use Composer\Console\Application;
use Composer\XdebugHandler\XdebugHandler;
use Composer\Util\Platform;
use Composer\Util\ErrorHandler;

error_reporting(-1);

// Restart without Xdebug
$xdebug = new XdebugHandler('Composer');
$xdebug->check();
unset($xdebug);

if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '4.0', '>=')) {
    echo 'HHVM 4.0 has dropped support for Composer, please use PHP instead. Aborting.'.PHP_EOL;
    exit(1);
}
if (!extension_loaded('iconv') && !extension_loaded('mbstring')) {
    echo 'The iconv OR mbstring extension is required and both are missing.'
        .PHP_EOL.'Install either of them or recompile php without --disable-iconv.'
        .PHP_EOL.'Aborting.'.PHP_EOL;
    exit(1);
}

if (function_exists('ini_set')) {
    // check if error logging is on, but to an empty destination - for the CLI SAPI, that means stderr
    $logsToSapiDefault = ('' === ini_get('error_log') && (bool) ini_get('log_errors'));
    // on the CLI SAPI, ensure errors are displayed on stderr, either via display_errors or via error_log
    if (PHP_SAPI === 'cli') {
        @ini_set('display_errors', $logsToSapiDefault ? '0' : 'stderr');
    }

    // Set user defined memory limit
    if ($memoryLimit = getenv('COMPOSER_MEMORY_LIMIT')) {
        @ini_set('memory_limit', $memoryLimit);
    } else {
        $memoryInBytes = function ($value) {
            $unit = strtolower(substr($value, -1, 1));
            $value = (int) $value;
            switch($unit) {
                case 'g':
                    $value *= 1024;
                    // no break (cumulative multiplier)
                case 'm':
                    $value *= 1024;
                    // no break (cumulative multiplier)
                case 'k':
                    $value *= 1024;
            }

            return $value;
        };

        $memoryLimit = trim(ini_get('memory_limit'));
        // Increase memory_limit if it is lower than 1.5GB
        if ($memoryLimit != -1 && $memoryInBytes($memoryLimit) < 1024 * 1024 * 1536) {
            @ini_set('memory_limit', '1536M');
        }
        unset($memoryInBytes);
    }
    unset($memoryLimit);
}

// Workaround PHP bug on Windows where env vars containing Unicode chars are mangled in $_SERVER
// see https://github.com/php/php-src/issues/7896
if (PHP_VERSION_ID >= 70113 && (PHP_VERSION_ID < 80016 || (PHP_VERSION_ID >= 80100 && PHP_VERSION_ID < 80103)) && Platform::isWindows()) {
    foreach ($_SERVER as $serverVar => $serverVal) {
        if (($serverVal = getenv($serverVar)) !== false) {
            $_SERVER[$serverVar] = $serverVal;
        }
    }
}

Platform::putEnv('COMPOSER_BINARY', realpath($_SERVER['argv'][0]));

ErrorHandler::register();

// run the command application
$application = new Application();
$application->run();
#!/usr/bin/env php
<?php

$cwd = getcwd();
assert(is_string($cwd));
chdir(__DIR__.'/../');
$ts = rtrim(exec('git log -n1 --pretty=%ct HEAD'));
if (!is_numeric($ts)) {
    echo 'Could not detect date using "git log -n1 --pretty=%ct HEAD"'.PHP_EOL;
    exit(1);
}
// Install with the current version to force it having the right ClassLoader version
// Install without dev packages to clean up the included classmap from phpunit classes
exec('php bin/composer config autoloader-suffix ComposerPhar' . $ts, $output, $result);
if (0 !== $result) {
    echo 'Could not set the autoloader suffix, make sure exec is allowed and php can be found in your PATH';
    exit(1);
}
exec('php bin/composer install -q --no-dev', $output, $result);
if (0 !== $result) {
    echo 'Could not remove dev deps, make sure exec is allowed and php can be found in your PATH';
    exit(1);
}
exec('php bin/composer config autoloader-suffix --unset', $output, $result);
if (0 !== $result) {
    echo 'Could not remove the autoloader suffix, make sure exec is allowed and php can be found in your PATH';
    exit(1);
}
chdir($cwd);

require __DIR__.'/../src/bootstrap.php';

use Composer\Compiler;

error_reporting(-1);
ini_set('display_errors', '1');

try {
    $compiler = new Compiler();
    $compiler->compile();
} catch (\Exception $e) {
    echo 'Failed to compile phar: ['.get_class($e).'] '.$e->getMessage().' at '.$e->getFile().':'.$e->getLine().PHP_EOL;
    exit(1);
}
# Composer-specific PHPStan extensions
#
# These can be reused by third party packages by including 'vendor/composer/composer/phpstan/rules.neon'
# in your phpstan config

services:
    -
        class: Composer\PHPStan\ConfigReturnTypeExtension
        tags:
            - phpstan.broker.dynamicMethodReturnTypeExtension
    -
        class: Composer\PHPStan\RuleReasonDataReturnTypeExtension
        tags:
            - phpstan.broker.dynamicMethodReturnTypeExtension
{
    "$schema": "https://json-schema.org/draft-04/schema#",
    "title": "Composer Package",
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "description": "Package name, including 'vendor-name/' prefix.",
            "pattern": "^[a-z0-9]([_.-]?[a-z0-9]+)*\/[a-z0-9](([_.]|-{1,2})?[a-z0-9]+)*$"
        },
        "description": {
            "type": "string",
            "description": "Short package description."
        },
        "license": {
            "type": ["string", "array"],
            "description": "License name. Or an array of license names."
        },
        "type": {
            "description": "Package type, either 'library' for common packages, 'composer-plugin' for plugins, 'metapackage' for empty packages, or a custom type ([a-z0-9-]+) defined by whatever project this package applies to.",
            "type": "string",
            "pattern": "^[a-z0-9-]+$"
        },
        "abandoned": {
            "type": ["boolean", "string"],
            "description": "Indicates whether this package has been abandoned, it can be boolean or a package name/URL pointing to a recommended alternative. Defaults to false."
        },
        "version": {
            "type": "string",
            "description": "Package version, see https://getcomposer.org/doc/04-schema.md#version for more info on valid schemes.",
            "pattern": "^[vV]?\\d+(?:[.-]\\d+){0,3}[._-]?(?:(?:[sS][tT][aA][bB][lL][eE]|[bB][eE][tT][aA]|[bB]|[rR][cC]|[aA][lL][pP][hH][aA]|[aA]|[pP][aA][tT][cC][hH]|[pP][lL]|[pP])(?:(?:[.-]?\\d+)*)?)?(?:[.-]?[dD][eE][vV]|\\.x-dev)?(?:\\+.*)?$|^dev-.*$"
        },
        "default-branch": {
            "type": ["boolean"],
            "description": "Internal use only, do not specify this in composer.json. Indicates whether this version is the default branch of the linked VCS repository. Defaults to false."
        },
        "non-feature-branches": {
            "type": ["array"],
            "description": "A set of string or regex patterns for non-numeric branch names that will not be handled as feature branches.",
            "items": {
                "type": "string"
            }
        },
        "keywords": {
            "type": "array",
            "items": {
                "type": "string",
                "description": "A tag/keyword that this package relates to."
            }
        },
        "readme": {
            "type": "string",
            "description": "Relative path to the readme document."
        },
        "time": {
            "type": "string",
            "description": "Package release date, in 'YYYY-MM-DD', 'YYYY-MM-DD HH:MM:SS' or 'YYYY-MM-DDTHH:MM:SSZ' format."
        },
        "authors": {
            "$ref": "#/definitions/authors"
        },
        "homepage": {
            "type": "string",
            "description": "Homepage URL for the project.",
            "format": "uri"
        },
        "support": {
            "type": "object",
            "properties": {
                "email": {
                    "type": "string",
                    "description": "Email address for support.",
                    "format": "email"
                },
                "issues": {
                    "type": "string",
                    "description": "URL to the issue tracker.",
                    "format": "uri"
                },
                "forum": {
                    "type": "string",
                    "description": "URL to the forum.",
                    "format": "uri"
                },
                "wiki": {
                    "type": "string",
                    "description": "URL to the wiki.",
                    "format": "uri"
                },
                "irc": {
                    "type": "string",
                    "description": "IRC channel for support, as irc://server/channel.",
                    "format": "uri"
                },
                "chat": {
                    "type": "string",
                    "description": "URL to the support chat.",
                    "format": "uri"
                },
                "source": {
                    "type": "string",
                    "description": "URL to browse or download the sources.",
                    "format": "uri"
                },
                "docs": {
                    "type": "string",
                    "description": "URL to the documentation.",
                    "format": "uri"
                },
                "rss": {
                    "type": "string",
                    "description": "URL to the RSS feed.",
                    "format": "uri"
                },
                "security": {
                    "type": "string",
                    "description": "URL to the vulnerability disclosure policy (VDP).",
                    "format": "uri"
                }
            }
        },
        "funding": {
            "type": "array",
            "description": "A list of options to fund the development and maintenance of the package.",
            "items": {
                "type": "object",
                "properties": {
                    "type": {
                        "type": "string",
                        "description": "Type of funding or platform through which funding is possible."
                    },
                    "url": {
                        "type": "string",
                        "description": "URL to a website with details on funding and a way to fund the package.",
                        "format": "uri"
                    }
                }
            }
        },
        "source": {
            "$ref": "#/definitions/source"
        },
        "dist": {
            "$ref": "#/definitions/dist"
        },
        "_comment": {
            "type": ["array", "string"],
            "description": "A key to store comments in"
        },
        "require": {
            "type": "object",
            "description": "This is an object of package name (keys) and version constraints (values) that are required to run this package.",
            "additionalProperties": {
                "type": "string"
            }
        },
        "require-dev": {
            "type": "object",
            "description": "This is an object of package name (keys) and version constraints (values) that this package requires for developing it (testing tools and such).",
            "additionalProperties": {
                "type": "string"
            }
        },
        "replace": {
            "type": "object",
            "description": "This is an object of package name (keys) and version constraints (values) that can be replaced by this package.",
            "additionalProperties": {
                "type": "string"
            }
        },
        "conflict": {
            "type": "object",
            "description": "This is an object of package name (keys) and version constraints (values) that conflict with this package.",
            "additionalProperties": {
                "type": "string"
            }
        },
        "provide": {
            "type": "object",
            "description": "This is an object of package name (keys) and version constraints (values) that this package provides in addition to this package's name.",
            "additionalProperties": {
                "type": "string"
            }
        },
        "suggest": {
            "type": "object",
            "description": "This is an object of package name (keys) and descriptions (values) that this package suggests work well with it (this will be suggested to the user during installation).",
            "additionalProperties": {
                "type": "string"
            }
        },
        "repositories": {
            "type": ["object", "array"],
            "description": "A set of additional repositories where packages can be found.",
            "additionalProperties": {
                "anyOf": [
                    { "$ref": "#/definitions/repository" },
                    { "type": "boolean", "enum": [false] }
                ]
            },
            "items": {
                "anyOf": [
                    { "$ref": "#/definitions/repository" },
                    {
                        "type": "object",
                        "additionalProperties": { "type": "boolean", "enum": [false] },
                        "minProperties": 1,
                        "maxProperties": 1
                    }
                ]
            }
        },
        "minimum-stability": {
            "type": ["string"],
            "description": "The minimum stability the packages must have to be install-able. Possible values are: dev, alpha, beta, RC, stable.",
            "enum": ["dev", "alpha", "beta", "rc", "RC", "stable"]
        },
        "prefer-stable": {
            "type": ["boolean"],
            "description": "If set to true, stable packages will be preferred to dev packages when possible, even if the minimum-stability allows unstable packages."
        },
        "autoload": {
            "$ref": "#/definitions/autoload"
        },
        "autoload-dev": {
            "type": "object",
            "description": "Description of additional autoload rules for development purpose (eg. a test suite).",
            "properties": {
                "psr-0": {
                    "type": "object",
                    "description": "This is an object of namespaces (keys) and the directories they can be found into (values, can be arrays of paths) by the autoloader.",
                    "additionalProperties": {
                        "type": ["string", "array"],
                        "items": {
                            "type": "string"
                        }
                    }
                },
                "psr-4": {
                    "type": "object",
                    "description": "This is an object of namespaces (keys) and the PSR-4 directories they can map to (values, can be arrays of paths) by the autoloader.",
                    "additionalProperties": {
                        "type": ["string", "array"],
                        "items": {
                            "type": "string"
                        }
                    }
                },
                "classmap": {
                    "type": "array",
                    "description": "This is an array of paths that contain classes to be included in the class-map generation process."
                },
                "files": {
                    "type": "array",
                    "description": "This is an array of files that are always required on every request."
                }
            }
        },
        "target-dir": {
            "description": "DEPRECATED: Forces the package to be installed into the given subdirectory path. This is used for autoloading PSR-0 packages that do not contain their full path. Use forward slashes for cross-platform compatibility.",
            "type": "string"
        },
        "include-path": {
            "type": ["array"],
            "description": "DEPRECATED: A list of directories which should get added to PHP's include path. This is only present to support legacy projects, and all new code should preferably use autoloading.",
            "items": {
                "type": "string"
            }
        },
        "bin": {
            "type": ["string", "array"],
            "description": "A set of files, or a single file, that should be treated as binaries and symlinked into bin-dir (from config).",
            "items": {
                "type": "string"
            }
        },
        "archive": {
            "type": ["object"],
            "description": "Options for creating package archives for distribution.",
            "properties": {
                "name": {
                    "type": "string",
                    "description": "A base name for archive."
                },
                "exclude": {
                    "type": "array",
                    "description": "A list of patterns for paths to exclude or include if prefixed with an exclamation mark."
                }
            }
        },
        "php-ext": {
            "type": "object",
            "description": "Settings for PHP extension packages.",
            "properties": {
                "extension-name": {
                    "type": "string",
                    "description": "If specified, this will be used as the name of the extension, where needed by tooling. If this is not specified, the extension name will be derived from the Composer package name (e.g. `vendor/name` would become `ext-name`). The extension name may be specified with or without the `ext-` prefix, and tools that use this must normalise this appropriately.",
                    "example": "ext-xdebug"
                },
                "priority": {
                    "type": "integer",
                    "description": "This is used to add a prefix to the INI file, e.g. `90-xdebug.ini` which affects the loading order. The priority is a number in the range 10-99 inclusive, with 10 being the highest priority (i.e. will be processed first), and 99 being the lowest priority (i.e. will be processed last). There are two digits so that the files sort correctly on any platform, whether the sorting is natural or not.",
                    "minimum": 10,
                    "maximum": 99,
                    "example": 80,
                    "default": 80
                },
                "support-zts": {
                    "type": "boolean",
                    "description": "Does this package support Zend Thread Safety",
                    "example": false,
                    "default": true
                },
                "support-nts": {
                    "type": "boolean",
                    "description": "Does this package support non-Thread Safe mode",
                    "example": false,
                    "default": true
                },
                "build-path": {
                    "type": ["string", "null"],
                    "description": "If specified, this is the subdirectory that will be used to build the extension instead of the root of the project.",
                    "example": "my-extension-source",
                    "default": null
                },
                "download-url-method": {
                    "type": "string",
                    "description": "If specified, this technique will be used to override the URL that PIE uses to download the asset. The default, if not specified, is composer-default.",
                    "enum": ["composer-default", "pre-packaged-source"],
                    "example": "composer-default"
                },
                "os-families": {
                    "type": "array",
                    "minItems": 1,
                    "description": "An array of OS families to mark as compatible with the extension. Specifying this property will mean this package is not installable with PIE on any OS family not listed here. Must not be specified alongside os-families-exclude.",
                    "items": {
                        "type": "string",
                        "enum": ["windows", "bsd", "darwin", "solaris", "linux", "unknown"],
                        "description": "The name of the OS family to mark as compatible."
                    }
                },
                "os-families-exclude": {
                    "type": "array",
                    "minItems": 1,
                    "description": "An array of OS families to mark as incompatible with the extension. Specifying this property will mean this package is installable on any OS family except those listed here. Must not be specified alongside os-families.",
                    "items": {
                        "type": "string",
                        "enum": ["windows", "bsd", "darwin", "solaris", "linux", "unknown"],
                        "description": "The name of the OS family to exclude."
                    }
                },
                "configure-options": {
                    "type": "array",
                    "description": "These configure options make up the flags that can be passed to ./configure when installing the extension.",
                    "items": {
                        "type": "object",
                        "required": ["name"],
                        "properties": {
                            "name": {
                                "type": "string",
                                "description": "The name of the flag, this would typically be prefixed with `--`, for example, the value 'the-flag' would be passed as `./configure --the-flag`.",
                                "example": "without-xdebug-compression",
                                "pattern": "^[a-zA-Z0-9][a-zA-Z0-9-_]*$"
                            },
                            "needs-value": {
                                "type": "boolean",
                                "description": "If this is set to true, the flag needs a value (e.g. --with-somelib=<path>), otherwise it is a flag without a value (e.g. --enable-some-feature).",
                                "example": false,
                                "default": false
                            },
                            "description": {
                                "type": "string",
                                "description": "The description of what the flag does or means.",
                                "example": "Disable compression through zlib"
                            }
                        }
                    }
                }
            },
            "allOf": [
                {
                    "not": {
                        "required": ["os-families", "os-families-exclude"]
                    }
                }
            ]
        },
        "config": {
            "type": "object",
            "description": "Composer options.",
            "properties": {
                "platform": {
                    "type": "object",
                    "description": "This is an object of package name (keys) and version (values) that will be used to mock the platform packages on this machine, the version can be set to false to make it appear like the package is not present.",
                    "additionalProperties": {
                        "type": ["string", "boolean"]
                    }
                },
                "allow-plugins": {
                    "type": ["object", "boolean"],
                    "description": "This is an object of {\"pattern\": true|false} with packages which are allowed to be loaded as plugins, or true to allow all, false to allow none. Defaults to {} which prompts when an unknown plugin is added.",
                    "additionalProperties": {
                        "type": ["boolean"]
                    }
                },
                "process-timeout": {
                    "type": "integer",
                    "description": "The timeout in seconds for process executions, defaults to 300 (5mins)."
                },
                "use-include-path": {
                    "type": "boolean",
                    "description": "If true, the Composer autoloader will also look for classes in the PHP include path."
                },
                "use-parent-dir": {
                    "type": ["string", "boolean"],
                    "description": "When running Composer in a directory where there is no composer.json, if there is one present in a directory above Composer will by default ask you whether you want to use that directory's composer.json instead. One of: true (always use parent if needed), false (never ask or use it) or \"prompt\" (ask every time), defaults to prompt."
                },
                "preferred-install": {
                    "type": ["string", "object"],
                    "description": "The install method Composer will prefer to use, defaults to auto and can be any of source, dist, auto, or an object of {\"pattern\": \"preference\"}.",
                    "additionalProperties": {
                        "type": ["string"]
                    }
                },
                "audit": {
                    "type": "object",
                    "description": "Security audit configuration options",
                    "properties": {
                        "ignore": {
                            "anyOf": [
                                {
                                    "type": "object",
                                    "description": "A list of advisory ids, remote ids or CVE ids (keys) and the explanations (values) for why they're being ignored. The listed items are reported but let the audit command pass.",
                                    "additionalProperties": {
                                        "type": ["string", "string"]
                                    }
                                },
                                {
                                    "type": "array",
                                    "description": "A set of advisory ids, remote ids or CVE ids that are reported but let the audit command pass.",
                                    "items": {
                                        "type": "string"
                                    }
                                }
                            ]
                        },
                        "abandoned": {
                            "enum": ["ignore", "report", "fail"],
                            "description": "Whether abandoned packages should be ignored, reported as problems or cause an audit failure."
                        }
                    }
                },
                "notify-on-install": {
                    "type": "boolean",
                    "description": "Composer allows repositories to define a notification URL, so that they get notified whenever a package from that repository is installed. This option allows you to disable that behaviour, defaults to true."
                },
                "github-protocols": {
                    "type": "array",
                    "description": "A list of protocols to use for github.com clones, in priority order, defaults to [\"https\", \"ssh\", \"git\"].",
                    "items": {
                        "type": "string"
                    }
                },
                "github-oauth": {
                    "type": "object",
                    "description": "An object of domain name => github API oauth tokens, typically {\"github.com\":\"<token>\"}.",
                    "additionalProperties": {
                        "type": "string"
                    }
                },
                "gitlab-oauth": {
                    "type": "object",
                    "description": "An object of domain name => gitlab API oauth tokens, typically {\"gitlab.com\":{\"expires-at\":\"<expiration date>\", \"refresh-token\":\"<refresh token>\", \"token\":\"<token>\"}}.",
                    "additionalProperties": {
                        "type": ["string", "object"],
                        "required": [ "token"],
                        "properties": {
                            "expires-at": {
                                "type": "integer",
                                "description": "The expiration date for this GitLab token"
                            },
                            "refresh-token": {
                                "type": "string",
                                "description": "The refresh token used for GitLab authentication"
                            },
                            "token": {
                                "type": "string",
                                "description": "The token used for GitLab authentication"
                            }
                        }
                    }
                },
                "gitlab-token": {
                    "type": "object",
                    "description": "An object of domain name => gitlab private tokens, typically {\"gitlab.com\":\"<token>\"}, or an object with username and token keys.",
                    "additionalProperties": {
                        "type": ["string", "object"],
                        "required": ["username", "token"],
                        "properties": {
                            "username": {
                                "type": "string",
                                "description": "The username used for GitLab authentication"
                            },
                            "token": {
                                "type": "string",
                                "description": "The token used for GitLab authentication"
                            }
                        }
                    }
                },
                "gitlab-protocol": {
                    "enum": ["git", "http", "https"],
                    "description": "A protocol to force use of when creating a repository URL for the `source` value of the package metadata. One of `git` or `http`. By default, Composer will generate a git URL for private repositories and http one for public repos."
                },
                "bearer": {
                    "type": "object",
                    "description": "An object of domain name => bearer authentication token, for example {\"example.com\":\"<token>\"}.",
                    "additionalProperties": {
                        "type": "string"
                    }
                },
                "disable-tls": {
                    "type": "boolean",
                    "description": "Defaults to `false`. If set to true all HTTPS URLs will be tried with HTTP instead and no network level encryption is performed. Enabling this is a security risk and is NOT recommended. The better way is to enable the php_openssl extension in php.ini."
                },
                "secure-http": {
                    "type": "boolean",
                    "description": "Defaults to `true`. If set to true only HTTPS URLs are allowed to be downloaded via Composer. If you really absolutely need HTTP access to something then you can disable it, but using \"Let's Encrypt\" to get a free SSL certificate is generally a better alternative."
                },
                "secure-svn-domains": {
                    "type": "array",
                    "description": "A list of domains which should be trusted/marked as using a secure Subversion/SVN transport. By default svn:// protocol is seen as insecure and will throw. This is a better/safer alternative to disabling `secure-http` altogether.",
                    "items": {
                        "type": "string"
                    }
                },
                "cafile": {
                    "type": "string",
                    "description": "A way to set the path to the openssl CA file. In PHP 5.6+ you should rather set this via openssl.cafile in php.ini, although PHP 5.6+ should be able to detect your system CA file automatically."
                },
                "capath": {
                    "type": "string",
                    "description": "If cafile is not specified or if the certificate is not found there, the directory pointed to by capath is searched for a suitable certificate. capath must be a correctly hashed certificate directory."
                },
                "http-basic": {
                    "type": "object",
                    "description": "An object of domain name => {\"username\": \"...\", \"password\": \"...\"}.",
                    "additionalProperties": {
                        "type": "object",
                        "required": ["username", "password"],
                        "properties": {
                            "username": {
                                "type": "string",
                                "description": "The username used for HTTP Basic authentication"
                            },
                            "password": {
                                "type": "string",
                                "description": "The password used for HTTP Basic authentication"
                            }
                        }
                    }
                },
                "store-auths": {
                    "type": ["string", "boolean"],
                    "description": "What to do after prompting for authentication, one of: true (store), false (do not store) or \"prompt\" (ask every time), defaults to prompt."
                },
                "vendor-dir": {
                    "type": "string",
                    "description": "The location where all packages are installed, defaults to \"vendor\"."
                },
                "bin-dir": {
                    "type": "string",
                    "description": "The location where all binaries are linked, defaults to \"vendor/bin\"."
                },
                "data-dir": {
                    "type": "string",
                    "description": "The location where old phar files are stored, defaults to \"$home\" except on XDG Base Directory compliant unixes."
                },
                "cache-dir": {
                    "type": "string",
                    "description": "The location where all caches are located, defaults to \"~/.composer/cache\" on *nix and \"%LOCALAPPDATA%\\Composer\" on windows."
                },
                "cache-files-dir": {
                    "type": "string",
                    "description": "The location where files (zip downloads) are cached, defaults to \"{$cache-dir}/files\"."
                },
                "cache-repo-dir": {
                    "type": "string",
                    "description": "The location where repo (git/hg repo clones) are cached, defaults to \"{$cache-dir}/repo\"."
                },
                "cache-vcs-dir": {
                    "type": "string",
                    "description": "The location where vcs infos (git clones, github api calls, etc. when reading vcs repos) are cached, defaults to \"{$cache-dir}/vcs\"."
                },
                "cache-ttl": {
                    "type": "integer",
                    "description": "The default cache time-to-live, defaults to 15552000 (6 months)."
                },
                "cache-files-ttl": {
                    "type": "integer",
                    "description": "The cache time-to-live for files, defaults to the value of cache-ttl."
                },
                "cache-files-maxsize": {
                    "type": ["string", "integer"],
                    "description": "The cache max size for the files cache, defaults to \"300MiB\"."
                },
                "cache-read-only": {
                    "type": ["boolean"],
                    "description": "Whether to use the Composer cache in read-only mode."
                },
                "bin-compat": {
                    "enum": ["auto", "full", "proxy", "symlink"],
                    "description": "The compatibility of the binaries, defaults to \"auto\" (automatically guessed), can be \"full\" (compatible with both Windows and Unix-based systems) and \"proxy\" (only bash-style proxy)."
                },
                "discard-changes": {
                    "type": ["string", "boolean"],
                    "description": "The default style of handling dirty updates, defaults to false and can be any of true, false or \"stash\"."
                },
                "autoloader-suffix": {
                    "type": "string",
                    "description": "Optional string to be used as a suffix for the generated Composer autoloader. When null a random one will be generated."
                },
                "optimize-autoloader": {
                    "type": "boolean",
                    "description": "Always optimize when dumping the autoloader."
                },
                "prepend-autoloader": {
                    "type": "boolean",
                    "description": "If false, the composer autoloader will not be prepended to existing autoloaders, defaults to true."
                },
                "classmap-authoritative": {
                    "type": "boolean",
                    "description": "If true, the composer autoloader will not scan the filesystem for classes that are not found in the class map, defaults to false."
                },
                "apcu-autoloader": {
                    "type": "boolean",
                    "description": "If true, the Composer autoloader will check for APCu and use it to cache found/not-found classes when the extension is enabled, defaults to false."
                },
                "github-domains": {
                    "type": "array",
                    "description": "A list of domains to use in github mode. This is used for GitHub Enterprise setups, defaults to [\"github.com\"].",
                    "items": {
                        "type": "string"
                    }
                },
                "github-expose-hostname": {
                    "type": "boolean",
                    "description": "Defaults to true. If set to false, the OAuth tokens created to access the github API will have a date instead of the machine hostname."
                },
                "gitlab-domains": {
                    "type": "array",
                    "description": "A list of domains to use in gitlab mode. This is used for custom GitLab setups, defaults to [\"gitlab.com\"].",
                    "items": {
                        "type": "string"
                    }
                },
                "bitbucket-oauth": {
                    "type": "object",
                    "description": "An object of domain name => {\"consumer-key\": \"...\", \"consumer-secret\": \"...\"}.",
                    "additionalProperties": {
                        "type": "object",
                        "required": ["consumer-key", "consumer-secret"],
                        "properties": {
                            "consumer-key": {
                                "type": "string",
                                "description": "The consumer-key used for OAuth authentication"
                            },
                            "consumer-secret": {
                                "type": "string",
                                "description": "The consumer-secret used for OAuth authentication"
                            },
                            "access-token": {
                                "type": "string",
                                "description": "The OAuth token retrieved from Bitbucket's API, this is written by Composer and you should not set it nor modify it."
                            },
                            "access-token-expiration": {
                                "type": "integer",
                                "description": "The generated token's expiration timestamp, this is written by Composer and you should not set it nor modify it."
                            }
                        }
                    }
                },
                "use-github-api": {
                    "type": "boolean",
                    "description": "Defaults to true.  If set to false, globally disables the use of the GitHub API for all GitHub repositories and clones the repository as it would for any other repository."
                },
                "archive-format": {
                    "type": "string",
                    "description": "The default archiving format when not provided on cli, defaults to \"tar\"."
                },
                "archive-dir": {
                    "type": "string",
                    "description": "The default archive path when not provided on cli, defaults to \".\"."
                },
                "htaccess-protect": {
                    "type": "boolean",
                    "description": "Defaults to true. If set to false, Composer will not create .htaccess files in the composer home, cache, and data directories."
                },
                "sort-packages": {
                    "type": "boolean",
                    "description": "Defaults to false. If set to true, Composer will sort packages when adding/updating a new dependency."
                },
                "lock": {
                    "type": "boolean",
                    "description": "Defaults to true. If set to false, Composer will not create a composer.lock file."
                },
                "platform-check": {
                    "type": ["boolean", "string"],
                    "description": "Defaults to \"php-only\" which checks only the PHP version. Setting to true will also check the presence of required PHP extensions. If set to false, Composer will not create and require a platform_check.php file as part of the autoloader bootstrap."
                },
                "bump-after-update": {
                    "type": ["string", "boolean"],
                    "description": "Defaults to false and can be any of true, false, \"dev\"` or \"no-dev\"`. If set to true, Composer will run the bump command after running the update command. If set to \"dev\" or \"no-dev\" then only the corresponding dependencies will be bumped."
                },
                "allow-missing-requirements": {
                    "type": ["boolean"],
                    "description": "Defaults to false. If set to true, Composer will allow install when lock file is not up to date with the latest changes in composer.json."
                }
            }
        },
        "extra": {
            "type": ["object", "array"],
            "description": "Arbitrary extra data that can be used by plugins, for example, package of type composer-plugin may have a 'class' key defining an installer class name.",
            "additionalProperties": true
        },
        "scripts": {
            "type": ["object"],
            "description": "Script listeners that will be executed before/after some events.",
            "properties": {
                "pre-install-cmd": {
                    "type": ["array", "string"],
                    "description": "Occurs before the install command is executed, contains one or more Class::method callables or shell commands."
                },
                "post-install-cmd": {
                    "type": ["array", "string"],
                    "description": "Occurs after the install command is executed, contains one or more Class::method callables or shell commands."
                },
                "pre-update-cmd": {
                    "type": ["array", "string"],
                    "description": "Occurs before the update command is executed, contains one or more Class::method callables or shell commands."
                },
                "post-update-cmd": {
                    "type": ["array", "string"],
                    "description": "Occurs after the update command is executed, contains one or more Class::method callables or shell commands."
                },
                "pre-status-cmd": {
                    "type": ["array", "string"],
                    "description": "Occurs before the status command is executed, contains one or more Class::method callables or shell commands."
                },
                "post-status-cmd": {
                    "type": ["array", "string"],
                    "description": "Occurs after the status command is executed, contains one or more Class::method callables or shell commands."
                },
                "pre-package-install": {
                    "type": ["array", "string"],
                    "description": "Occurs before a package is installed, contains one or more Class::method callables or shell commands."
                },
                "post-package-install": {
                    "type": ["array", "string"],
                    "description": "Occurs after a package is installed, contains one or more Class::method callables or shell commands."
                },
                "pre-package-update": {
                    "type": ["array", "string"],
                    "description": "Occurs before a package is updated, contains one or more Class::method callables or shell commands."
                },
                "post-package-update": {
                    "type": ["array", "string"],
                    "description": "Occurs after a package is updated, contains one or more Class::method callables or shell commands."
                },
                "pre-package-uninstall": {
                    "type": ["array", "string"],
                    "description": "Occurs before a package has been uninstalled, contains one or more Class::method callables or shell commands."
                },
                "post-package-uninstall": {
                    "type": ["array", "string"],
                    "description": "Occurs after a package has been uninstalled, contains one or more Class::method callables or shell commands."
                },
                "pre-autoload-dump": {
                    "type": ["array", "string"],
                    "description": "Occurs before the autoloader is dumped, contains one or more Class::method callables or shell commands."
                },
                "post-autoload-dump": {
                    "type": ["array", "string"],
                    "description": "Occurs after the autoloader is dumped, contains one or more Class::method callables or shell commands."
                },
                "post-root-package-install": {
                    "type": ["array", "string"],
                    "description": "Occurs after the root-package is installed, contains one or more Class::method callables or shell commands."
                },
                "post-create-project-cmd": {
                    "type": ["array", "string"],
                    "description": "Occurs after the create-project command is executed, contains one or more Class::method callables or shell commands."
                }
            }
        },
        "scripts-descriptions": {
            "type": ["object"],
            "description": "Descriptions for custom commands, shown in console help.",
            "additionalProperties": {
                "type": "string"
            }
        },
        "scripts-aliases": {
            "type": ["object"],
            "description": "Aliases for custom commands.",
            "additionalProperties": {
                "type": "array"
            }
        }
    },
    "definitions": {
        "authors": {
            "type": "array",
            "description": "List of authors that contributed to the package. This is typically the main maintainers, not the full list.",
            "items": {
                "type": "object",
                "additionalProperties": false,
                "required": [ "name"],
                "properties": {
                    "name": {
                        "type": "string",
                        "description": "Full name of the author."
                    },
                    "email": {
                        "type": "string",
                        "description": "Email address of the author.",
                        "format": "email"
                    },
                    "homepage": {
                        "type": "string",
                        "description": "Homepage URL for the author.",
                        "format": "uri"
                    },
                    "role": {
                        "type": "string",
                        "description": "Author's role in the project."
                    }
                }
            }
        },
        "autoload": {
            "type": "object",
            "description": "Description of how the package can be autoloaded.",
            "properties": {
                "psr-0": {
                    "type": "object",
                    "description": "This is an object of namespaces (keys) and the directories they can be found in (values, can be arrays of paths) by the autoloader.",
                    "additionalProperties": {
                        "type": ["string", "array"],
                        "items": {
                            "type": "string"
                        }
                    }
                },
                "psr-4": {
                    "type": "object",
                    "description": "This is an object of namespaces (keys) and the PSR-4 directories they can map to (values, can be arrays of paths) by the autoloader.",
                    "additionalProperties": {
                        "type": ["string", "array"],
                        "items": {
                            "type": "string"
                        }
                    }
                },
                "classmap": {
                    "type": "array",
                    "description": "This is an array of paths that contain classes to be included in the class-map generation process."
                },
                "files": {
                    "type": "array",
                    "description": "This is an array of files that are always required on every request."
                },
                "exclude-from-classmap": {
                    "type": "array",
                    "description": "This is an array of patterns to exclude from autoload classmap generation. (e.g. \"exclude-from-classmap\": [\"/test/\", \"/tests/\", \"/Tests/\"]"
                }
            }
        },
        "repository": {
            "type": "object",
            "anyOf": [
                { "$ref": "#/definitions/composer-repository" },
                { "$ref": "#/definitions/vcs-repository" },
                { "$ref": "#/definitions/path-repository" },
                { "$ref": "#/definitions/artifact-repository" },
                { "$ref": "#/definitions/pear-repository" },
                { "$ref": "#/definitions/package-repository" }
            ]
        },
        "composer-repository": {
            "type": "object",
            "required": ["type", "url"],
            "properties": {
                "type": { "type": "string", "enum": ["composer"] },
                "url": { "type": "string" },
                "canonical": { "type": "boolean" },
                "only": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                },
                "exclude": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                },
                "options": {
                    "type": "object",
                    "additionalProperties": true
                },
                "allow_ssl_downgrade": { "type": "boolean" },
                "force-lazy-providers": { "type": "boolean" }
            }
        },
        "vcs-repository": {
            "type": "object",
            "required": ["type", "url"],
            "properties": {
                "type": { "type": "string", "enum": ["vcs", "github", "git", "gitlab", "bitbucket", "git-bitbucket", "hg", "fossil", "perforce", "svn"] },
                "url": { "type": "string" },
                "canonical": { "type": "boolean" },
                "only": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                },
                "exclude": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                },
                "no-api": { "type": "boolean" },
                "secure-http": { "type": "boolean" },
                "svn-cache-credentials": { "type": "boolean" },
                "trunk-path": { "type": ["string", "boolean"] },
                "branches-path": { "type": ["string", "boolean"] },
                "tags-path": { "type": ["string", "boolean"] },
                "package-path": { "type": "string" },
                "depot": { "type": "string" },
                "branch": { "type": "string" },
                "unique_perforce_client_name": { "type": "string" },
                "p4user": { "type": "string" },
                "p4password": { "type": "string" }
            }
        },
        "path-repository": {
            "type": "object",
            "required": ["type", "url"],
            "properties": {
                "type": { "type": "string", "enum": ["path"] },
                "url": { "type": "string" },
                "canonical": { "type": "boolean" },
                "only": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                },
                "exclude": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                },
                "options": {
                    "type": "object",
                    "properties": {
                        "reference": { "type": ["string"], "enum": ["none", "config", "auto"] },
                        "symlink": { "type": ["boolean", "null"] },
                        "relative": { "type": ["boolean"] },
                        "versions": { "type": "object", "additionalProperties": { "type": "string" } }
                    },
                    "additionalProperties": true
                }
            }
        },
        "artifact-repository": {
            "type": "object",
            "required": ["type", "url"],
            "properties": {
                "type": { "type": "string", "enum": ["artifact"] },
                "url": { "type": "string" },
                "canonical": { "type": "boolean" },
                "only": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                },
                "exclude": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                }
            }
        },
        "pear-repository": {
            "type": "object",
            "required": ["type", "url"],
            "properties": {
                "type": { "type": "string", "enum": ["pear"] },
                "url": { "type": "string" },
                "canonical": { "type": "boolean" },
                "only": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                },
                "exclude": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                },
                "vendor-alias": { "type": "string" }
            }
        },
        "package-repository": {
            "type": "object",
            "required": ["type", "package"],
            "properties": {
                "type": { "type": "string", "enum": ["package"] },
                "canonical": { "type": "boolean" },
                "only": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                },
                "exclude": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                },
                "package": {
                    "oneOf": [
                        { "$ref": "#/definitions/inline-package" },
                        {
                            "type": "array",
                            "items": { "$ref": "#/definitions/inline-package" }
                        }
                    ]
                }
            }
        },
        "inline-package": {
            "type": "object",
            "required": ["name", "version"],
            "properties": {
                "name": {
                    "type": "string",
                    "description": "Package name, including 'vendor-name/' prefix."
                },
                "type": {
                    "type": "string"
                },
                "target-dir": {
                    "description": "DEPRECATED: Forces the package to be installed into the given subdirectory path. This is used for autoloading PSR-0 packages that do not contain their full path. Use forward slashes for cross-platform compatibility.",
                    "type": "string"
                },
                "description": {
                    "type": "string"
                },
                "keywords": {
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                },
                "homepage": {
                    "type": "string",
                    "format": "uri"
                },
                "version": {
                    "type": "string"
                },
                "time": {
                    "type": "string"
                },
                "license": {
                    "type": [
                        "string",
                        "array"
                    ]
                },
                "authors": {
                    "$ref": "#/definitions/authors"
                },
                "require": {
                    "type": "object",
                    "additionalProperties": {
                        "type": "string"
                    }
                },
                "replace": {
                    "type": "object",
                    "additionalProperties": {
                        "type": "string"
                    }
                },
                "conflict": {
                    "type": "object",
                    "additionalProperties": {
                        "type": "string"
                    }
                },
                "provide": {
                    "type": "object",
                    "additionalProperties": {
                        "type": "string"
                    }
                },
                "require-dev": {
                    "type": "object",
                    "additionalProperties": {
                        "type": "string"
                    }
                },
                "suggest": {
                    "type": "object",
                    "additionalProperties": {
                        "type": "string"
                    }
                },
                "extra": {
                    "type": ["object", "array"],
                    "additionalProperties": true
                },
                "autoload": {
                    "$ref": "#/definitions/autoload"
                },
                "archive": {
                    "type": ["object"],
                    "properties": {
                        "exclude": {
                            "type": "array"
                        }
                    }
                },
                "bin": {
                    "type": ["string", "array"],
                    "description": "A set of files, or a single file, that should be treated as binaries and symlinked into bin-dir (from config).",
                    "items": {
                        "type": "string"
                    }
                },
                "include-path": {
                    "type": ["array"],
                    "description": "DEPRECATED: A list of directories which should get added to PHP's include path. This is only present to support legacy projects, and all new code should preferably use autoloading.",
                    "items": {
                        "type": "string"
                    }
                },
                "source": {
                    "$ref": "#/definitions/source"
                },
                "dist": {
                    "$ref": "#/definitions/dist"
                }
            },
            "additionalProperties": true
        },
        "source": {
            "type": "object",
            "required": ["type", "url", "reference"],
            "properties": {
                "type": {
                    "type": "string"
                },
                "url": {
                    "type": "string"
                },
                "reference": {
                    "type": "string"
                },
                "mirrors": {
                    "type": "array"
                }
            }
        },
        "dist": {
            "type": "object",
            "required": ["type", "url"],
            "properties": {
                "type": {
                    "type": "string"
                },
                "url": {
                    "type": "string"
                },
                "reference": {
                    "type": "string"
                },
                "shasum": {
                    "type": "string"
                },
                "mirrors": {
                    "type": "array"
                }
            }
        }
    }
}
{
    "$schema": "https://json-schema.org/draft-04/schema#",
    "title": "Composer Package Repository",
    "type": "object",
    "oneOf": [
        { "required": [ "packages" ] },
        { "required": [ "providers" ] },
        { "required": [ "provider-includes", "providers-url" ] },
        { "required": [ "metadata-url" ] }
    ],
    "properties": {
        "packages": {
            "type": ["object", "array"],
            "description": "A hashmap of package names in the form of <vendor>/<name>.",
            "additionalProperties": { "$ref": "#/definitions/versions" }
        },
        "metadata-url": {
            "type": "string",
            "description": "Endpoint to retrieve package metadata data from, in Composer v2 format, e.g. '/p2/%package%.json'."
        },
        "available-packages": {
            "type": "array",
            "items": {
                "type": "string"
            },
            "description": "If your repository only has a small number of packages, and you want to avoid serving many 404s, specify all the package names that your repository contains here."
        },
        "available-package-patterns": {
            "type": "array",
            "items": {
                "type": "string"
            },
            "description": "If your repository only has a small number of packages, and you want to avoid serving many 404s, specify package name patterns containing wildcards (*) that your repository contains here."
        },
        "security-advisories": {
            "type": "array",
            "items": {
                "type": "object",
                "required": ["metadata", "api-url"],
                "properties": {
                    "metadata": {
                        "type": "boolean",
                        "description": "Whether metadata files contain security advisory data or whether it should always be queried using the API URL."
                    },
                    "api-url": {
                        "type": "string",
                        "description": "Endpoint to call to retrieve security advisories data."
                    }
                }
            }
        },
        "metadata-changes-url": {
            "type": "string",
            "description": "Endpoint to retrieve package metadata updates from. This should receive a timestamp since last call to be able to return new changes. e.g. '/metadata/changes.json'."
        },
        "providers-api": {
            "type": "string",
            "description": "Endpoint to retrieve package names providing a given name from, e.g. '/providers/%package%.json'."
        },
        "notify-batch": {
            "type": "string",
            "description": "Endpoint to call after multiple packages have been installed, e.g. '/downloads/'."
        },
        "search": {
            "type": "string",
            "description": "Endpoint that provides search capabilities, e.g. '/search.json?q=%query%&type=%type%'."
        },
        "list": {
            "type": "string",
            "description": "Endpoint that provides a full list of packages present in the repository. It should accept an optional `?filter=xx` query param, which can contain `*` as wildcards matching any substring. e.g. '/list.json'."
        },
        "warnings": {
            "type": "array",
            "items": {
                "type": "object",
                "required": ["message", "versions"],
                "properties": {
                    "message": {
                        "type": "string",
                        "description": "A message that will be output by Composer as a warning when this source is consulted."
                    },
                    "versions": {
                        "type": "string",
                        "description": "A version constraint to limit to which Composer versions the warning should be shown."
                    }
                }
            }
        },
        "infos": {
            "type": "array",
            "items": {
                "type": "object",
                "required": ["message", "versions"],
                "properties": {
                    "message": {
                        "type": "string",
                        "description": "A message that will be output by Composer as info when this source is consulted."
                    },
                    "versions": {
                        "type": "string",
                        "description": "A version constraint to limit to which Composer versions the info should be shown."
                    }
                }
            }
        },
        "providers-url": {
            "type": "string",
            "description": "DEPRECATED: Endpoint to retrieve provider data from, e.g. '/p/%package%$%hash%.json'."
        },
        "provider-includes": {
            "type": "object",
            "description": "DEPRECATED: A hashmap of provider listings.",
            "additionalProperties": { "$ref": "#/definitions/provider" }
        },
        "providers": {
            "type": "object",
            "description": "DEPRECATED: A hashmap of package names in the form of <vendor>/<name>.",
            "additionalProperties": { "$ref": "#/definitions/provider" }
        },
        "warning": {
            "type": "string",
            "description": "DEPRECATED: A message that will be output by Composer as a warning when this source is consulted."
        },
        "warning-versions": {
            "type": "string",
            "description": "DEPRECATED: A version constraint to limit to which Composer versions the warning should be shown."
        },
        "info": {
            "type": "string",
            "description": "DEPRECATED: A message that will be output by Composer as a info when this source is consulted."
        },
        "info-versions": {
            "type": "string",
            "description": "DEPRECATED: A version constraint to limit to which Composer versions the info should be shown."
        }
    },
    "definitions": {
        "versions": {
            "type": "object",
            "description": "A hashmap of versions and their metadata.",
            "additionalProperties": { "$ref": "#/definitions/version" }
        },
        "version": {
            "type": "object",
            "oneOf": [
                { "$ref": "#/definitions/package" },
                { "$ref": "#/definitions/metapackage" }
            ]
        },
        "package-base": {
            "properties": {
                "name": { "type": "string" },
                "type": { "type": "string" },
                "version": { "type": "string" },
                "version_normalized": {
                    "type": "string",
                    "description": "Normalized version, optional but can save computational time on client side."
                },
                "autoload": { "type": "object" },
                "require": { "type": "object" },
                "replace": { "type": "object" },
                "conflict": { "type": "object" },
                "provide": { "type": "object" },
                "time": { "type": "string" }
            },
            "additionalProperties": true
        },
        "package": {
            "allOf": [
                { "$ref": "#/definitions/package-base" },
                {
                    "properties": {
                        "dist": { "type": "object" },
                        "source": { "type": "object" }
                    }
                },
                { "oneOf": [
                    { "required": [ "name", "version", "source" ] },
                    { "required": [ "name", "version", "dist" ] }
                ] }
            ]
        },
        "metapackage": {
            "allOf": [
                { "$ref": "#/definitions/package-base" },
                {
                    "properties": {
                        "type": { "type": "string", "enum": [ "metapackage" ] }
                    },
                    "required": [ "name", "version", "type" ]
                }
            ]
        },
        "provider": {
            "type": "object",
            "properties": {
                "sha256": {
                    "type": "string",
                    "description": "Hash value that can be used to validate the resource."
                }
            }
        }
    }
}
{
    "$schema": "https://json-schema.org/draft-04/schema#",
    "title": "Composer Lock File",
    "type": "object",
    "required": [ "content-hash", "packages", "packages-dev" ],
    "additionalProperties": true,
    "properties": {
        "_readme": {
            "type": "array",
            "items": {
                "type": "string"
            },
            "description": "Informational text for humans reading the file"
        },
        "content-hash": {
            "type": "string",
            "description": "Hash of all relevant properties of the composer.json that was used to create this lock file."
        },
        "packages": {
            "type": "array",
            "description": "An array of packages that are required.",
            "items": {
                "$ref": "./composer-schema.json",
                "required": ["name", "version"]
            }
        },
        "packages-dev": {
            "type": "array",
            "description": "An array of packages that are required in require-dev.",
            "items": {
                "$ref": "./composer-schema.json"
            }
        },
        "aliases": {
            "type": "array",
            "description": "Inline aliases defined in the root package.",
            "items": {
                "type": "object",
                "required": [ "package", "version", "alias", "alias_normalized" ],
                "properties": {
                    "package": {
                        "type": "string"
                    },
                    "version": {
                        "type": "string"
                    },
                    "alias": {
                        "type": "string"
                    },
                    "alias_normalized": {
                        "type": "string"
                    }
                }
            }
        },
        "minimum-stability": {
            "type": "string",
            "description": "The minimum-stability used to generate this lock file."
        },
        "stability-flags": {
            "type": "object",
            "description": "Root package stability flags changing the minimum-stability for specific packages.",
            "additionalProperties": {
                "type": "integer"
            }
        },
        "prefer-stable": {
            "type": "boolean",
            "description": "Whether the --prefer-stable flag was used when building this lock file."
        },
        "prefer-lowest": {
            "type": "boolean",
            "description": "Whether the --prefer-lowest flag was used when building this lock file."
        },
        "platform": {
            "type": "object",
            "description": "Platform requirements of the root package.",
            "additionalProperties": {
                "type": "string"
            }
        },
        "platform-dev": {
            "type": "object",
            "description": "Platform dev-requirements of the root package.",
            "additionalProperties": {
                "type": "string"
            }
        },
        "platform-overrides": {
            "type": "object",
            "description": "Platform config overrides of the root package.",
            "additionalProperties": {
                "type": "string"
            }
        },
        "plugin-api-version": {
            "type": "string",
            "description": "The composer-plugin-api version that was used to generate this lock file."
        }
    }
}

Copyright (c) Nils Adermann, Jordi Boggiano

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.

<?php return array(
    'root' => array(
        'name' => 'php/pie',
        'pretty_version' => '1.2.2',
        'version' => '1.2.2.0',
        'reference' => '5315702a8e134ce9f80ea0fb298547c5ea7f752f',
        'type' => 'project',
        'install_path' => __DIR__ . '/../../',
        'aliases' => array(),
        'dev' => true,
    ),
    'versions' => array(
        'amphp/amp' => array(
            'pretty_version' => 'v3.1.1',
            'version' => '3.1.1.0',
            'reference' => 'fa0ab33a6f47a82929c38d03ca47ebb71086a93f',
            'type' => 'library',
            'install_path' => __DIR__ . '/../amphp/amp',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'amphp/byte-stream' => array(
            'pretty_version' => 'v2.1.2',
            'version' => '2.1.2.0',
            'reference' => '55a6bd071aec26fa2a3e002618c20c35e3df1b46',
            'type' => 'library',
            'install_path' => __DIR__ . '/../amphp/byte-stream',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'amphp/cache' => array(
            'pretty_version' => 'v2.0.1',
            'version' => '2.0.1.0',
            'reference' => '46912e387e6aa94933b61ea1ead9cf7540b7797c',
            'type' => 'library',
            'install_path' => __DIR__ . '/../amphp/cache',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'amphp/dns' => array(
            'pretty_version' => 'v2.4.0',
            'version' => '2.4.0.0',
            'reference' => '78eb3db5fc69bf2fc0cb503c4fcba667bc223c71',
            'type' => 'library',
            'install_path' => __DIR__ . '/../amphp/dns',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'amphp/parallel' => array(
            'pretty_version' => 'v2.3.2',
            'version' => '2.3.2.0',
            'reference' => '321b45ae771d9c33a068186b24117e3cd1c48dce',
            'type' => 'library',
            'install_path' => __DIR__ . '/../amphp/parallel',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'amphp/parser' => array(
            'pretty_version' => 'v1.1.1',
            'version' => '1.1.1.0',
            'reference' => '3cf1f8b32a0171d4b1bed93d25617637a77cded7',
            'type' => 'library',
            'install_path' => __DIR__ . '/../amphp/parser',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'amphp/pipeline' => array(
            'pretty_version' => 'v1.2.3',
            'version' => '1.2.3.0',
            'reference' => '7b52598c2e9105ebcddf247fc523161581930367',
            'type' => 'library',
            'install_path' => __DIR__ . '/../amphp/pipeline',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'amphp/process' => array(
            'pretty_version' => 'v2.0.3',
            'version' => '2.0.3.0',
            'reference' => '52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d',
            'type' => 'library',
            'install_path' => __DIR__ . '/../amphp/process',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'amphp/serialization' => array(
            'pretty_version' => 'v1.0.0',
            'version' => '1.0.0.0',
            'reference' => '693e77b2fb0b266c3c7d622317f881de44ae94a1',
            'type' => 'library',
            'install_path' => __DIR__ . '/../amphp/serialization',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'amphp/socket' => array(
            'pretty_version' => 'v2.3.1',
            'version' => '2.3.1.0',
            'reference' => '58e0422221825b79681b72c50c47a930be7bf1e1',
            'type' => 'library',
            'install_path' => __DIR__ . '/../amphp/socket',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'amphp/sync' => array(
            'pretty_version' => 'v2.3.0',
            'version' => '2.3.0.0',
            'reference' => '217097b785130d77cfcc58ff583cf26cd1770bf1',
            'type' => 'library',
            'install_path' => __DIR__ . '/../amphp/sync',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'behat/behat' => array(
            'pretty_version' => 'v3.23.0',
            'version' => '3.23.0.0',
            'reference' => 'c465af8756adaaa6d962c3176a0a6c594361809b',
            'type' => 'library',
            'install_path' => __DIR__ . '/../behat/behat',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'behat/gherkin' => array(
            'pretty_version' => 'v4.14.0',
            'version' => '4.14.0.0',
            'reference' => '34c9b59c59355a7b4c53b9f041c8dbd1c8acc3b4',
            'type' => 'library',
            'install_path' => __DIR__ . '/../behat/gherkin',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'composer/ca-bundle' => array(
            'pretty_version' => '1.5.8',
            'version' => '1.5.8.0',
            'reference' => '719026bb30813accb68271fee7e39552a58e9f65',
            'type' => 'library',
            'install_path' => __DIR__ . '/./ca-bundle',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'composer/class-map-generator' => array(
            'pretty_version' => '1.6.2',
            'version' => '1.6.2.0',
            'reference' => 'ba9f089655d4cdd64e762a6044f411ccdaec0076',
            'type' => 'library',
            'install_path' => __DIR__ . '/./class-map-generator',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'composer/composer' => array(
            'pretty_version' => '2.8.11',
            'version' => '2.8.11.0',
            'reference' => '00e1a3396eea67033775c4a49c772376f45acd73',
            'type' => 'library',
            'install_path' => __DIR__ . '/./composer',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'composer/metadata-minifier' => array(
            'pretty_version' => '1.0.0',
            'version' => '1.0.0.0',
            'reference' => 'c549d23829536f0d0e984aaabbf02af91f443207',
            'type' => 'library',
            'install_path' => __DIR__ . '/./metadata-minifier',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'composer/pcre' => array(
            'pretty_version' => '3.3.2',
            'version' => '3.3.2.0',
            'reference' => 'b2bed4734f0cc156ee1fe9c0da2550420d99a21e',
            'type' => 'library',
            'install_path' => __DIR__ . '/./pcre',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'composer/semver' => array(
            'pretty_version' => '3.4.4',
            'version' => '3.4.4.0',
            'reference' => '198166618906cb2de69b95d7d47e5fa8aa1b2b95',
            'type' => 'library',
            'install_path' => __DIR__ . '/./semver',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'composer/spdx-licenses' => array(
            'pretty_version' => '1.5.9',
            'version' => '1.5.9.0',
            'reference' => 'edf364cefe8c43501e21e88110aac10b284c3c9f',
            'type' => 'library',
            'install_path' => __DIR__ . '/./spdx-licenses',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'composer/xdebug-handler' => array(
            'pretty_version' => '3.0.5',
            'version' => '3.0.5.0',
            'reference' => '6c1925561632e83d60a44492e0b344cf48ab85ef',
            'type' => 'library',
            'install_path' => __DIR__ . '/./xdebug-handler',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'danog/advanced-json-rpc' => array(
            'pretty_version' => 'v3.2.2',
            'version' => '3.2.2.0',
            'reference' => 'aadb1c4068a88c3d0530cfe324b067920661efcb',
            'type' => 'library',
            'install_path' => __DIR__ . '/../danog/advanced-json-rpc',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'daverandom/libdns' => array(
            'pretty_version' => 'v2.1.0',
            'version' => '2.1.0.0',
            'reference' => 'b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a',
            'type' => 'library',
            'install_path' => __DIR__ . '/../daverandom/libdns',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'dealerdirect/phpcodesniffer-composer-installer' => array(
            'pretty_version' => 'v1.1.2',
            'version' => '1.1.2.0',
            'reference' => 'e9cf5e4bbf7eeaf9ef5db34938942602838fc2b1',
            'type' => 'composer-plugin',
            'install_path' => __DIR__ . '/../dealerdirect/phpcodesniffer-composer-installer',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'dnoegel/php-xdg-base-dir' => array(
            'pretty_version' => 'v0.1.1',
            'version' => '0.1.1.0',
            'reference' => '8f8a6e48c5ecb0f991c2fdcf5f154a47d85f9ffd',
            'type' => 'library',
            'install_path' => __DIR__ . '/../dnoegel/php-xdg-base-dir',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'doctrine/coding-standard' => array(
            'pretty_version' => '13.0.1',
            'version' => '13.0.1.0',
            'reference' => '0affd62169186f32de725ca612e6129e81186a21',
            'type' => 'phpcodesniffer-standard',
            'install_path' => __DIR__ . '/../doctrine/coding-standard',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'doctrine/deprecations' => array(
            'pretty_version' => '1.1.5',
            'version' => '1.1.5.0',
            'reference' => '459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38',
            'type' => 'library',
            'install_path' => __DIR__ . '/../doctrine/deprecations',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'felixfbecker/language-server-protocol' => array(
            'pretty_version' => 'v1.5.3',
            'version' => '1.5.3.0',
            'reference' => 'a9e113dbc7d849e35b8776da39edaf4313b7b6c9',
            'type' => 'library',
            'install_path' => __DIR__ . '/../felixfbecker/language-server-protocol',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'felixfbecker/php-advanced-json-rpc' => array(
            'dev_requirement' => true,
            'replaced' => array(
                0 => '^3',
            ),
        ),
        'fidry/cpu-core-counter' => array(
            'pretty_version' => '1.3.0',
            'version' => '1.3.0.0',
            'reference' => 'db9508f7b1474469d9d3c53b86f817e344732678',
            'type' => 'library',
            'install_path' => __DIR__ . '/../fidry/cpu-core-counter',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'illuminate/container' => array(
            'pretty_version' => 'v10.48.28',
            'version' => '10.48.28.0',
            'reference' => 'ed6253f7dd3a67d468b2cc7a69a657e1f14c7ba3',
            'type' => 'library',
            'install_path' => __DIR__ . '/../illuminate/container',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'illuminate/contracts' => array(
            'pretty_version' => 'v10.48.28',
            'version' => '10.48.28.0',
            'reference' => 'f90663a69f926105a70b78060a31f3c64e2d1c74',
            'type' => 'library',
            'install_path' => __DIR__ . '/../illuminate/contracts',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'justinrainbow/json-schema' => array(
            'pretty_version' => '6.5.1',
            'version' => '6.5.1.0',
            'reference' => 'b5ab21e431594897e5bb86343c01f140ba862c26',
            'type' => 'library',
            'install_path' => __DIR__ . '/../justinrainbow/json-schema',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'kelunik/certificate' => array(
            'pretty_version' => 'v1.1.3',
            'version' => '1.1.3.0',
            'reference' => '7e00d498c264d5eb4f78c69f41c8bd6719c0199e',
            'type' => 'library',
            'install_path' => __DIR__ . '/../kelunik/certificate',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'league/uri' => array(
            'pretty_version' => '7.5.1',
            'version' => '7.5.1.0',
            'reference' => '81fb5145d2644324614cc532b28efd0215bda430',
            'type' => 'library',
            'install_path' => __DIR__ . '/../league/uri',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'league/uri-interfaces' => array(
            'pretty_version' => '7.5.0',
            'version' => '7.5.0.0',
            'reference' => '08cfc6c4f3d811584fb09c37e2849e6a7f9b0742',
            'type' => 'library',
            'install_path' => __DIR__ . '/../league/uri-interfaces',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'marc-mabe/php-enum' => array(
            'pretty_version' => 'v4.7.1',
            'version' => '4.7.1.0',
            'reference' => '7159809e5cfa041dca28e61f7f7ae58063aae8ed',
            'type' => 'library',
            'install_path' => __DIR__ . '/../marc-mabe/php-enum',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'myclabs/deep-copy' => array(
            'pretty_version' => '1.13.4',
            'version' => '1.13.4.0',
            'reference' => '07d290f0c47959fd5eed98c95ee5602db07e0b6a',
            'type' => 'library',
            'install_path' => __DIR__ . '/../myclabs/deep-copy',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'netresearch/jsonmapper' => array(
            'pretty_version' => 'v5.0.0',
            'version' => '5.0.0.0',
            'reference' => '8c64d8d444a5d764c641ebe97e0e3bc72b25bf6c',
            'type' => 'library',
            'install_path' => __DIR__ . '/../netresearch/jsonmapper',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'nikic/php-parser' => array(
            'pretty_version' => 'v5.6.1',
            'version' => '5.6.1.0',
            'reference' => 'f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2',
            'type' => 'library',
            'install_path' => __DIR__ . '/../nikic/php-parser',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'phar-io/manifest' => array(
            'pretty_version' => '2.0.4',
            'version' => '2.0.4.0',
            'reference' => '54750ef60c58e43759730615a392c31c80e23176',
            'type' => 'library',
            'install_path' => __DIR__ . '/../phar-io/manifest',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'phar-io/version' => array(
            'pretty_version' => '3.2.1',
            'version' => '3.2.1.0',
            'reference' => '4f7fd7836c6f332bb2933569e566a0d6c4cbed74',
            'type' => 'library',
            'install_path' => __DIR__ . '/../phar-io/version',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'php/pie' => array(
            'pretty_version' => '1.2.2',
            'version' => '1.2.2.0',
            'reference' => '5315702a8e134ce9f80ea0fb298547c5ea7f752f',
            'type' => 'project',
            'install_path' => __DIR__ . '/../../',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'phpdocumentor/reflection-common' => array(
            'pretty_version' => '2.2.0',
            'version' => '2.2.0.0',
            'reference' => '1d01c49d4ed62f25aa84a747ad35d5a16924662b',
            'type' => 'library',
            'install_path' => __DIR__ . '/../phpdocumentor/reflection-common',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'phpdocumentor/reflection-docblock' => array(
            'pretty_version' => '5.6.3',
            'version' => '5.6.3.0',
            'reference' => '94f8051919d1b0369a6bcc7931d679a511c03fe9',
            'type' => 'library',
            'install_path' => __DIR__ . '/../phpdocumentor/reflection-docblock',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'phpdocumentor/type-resolver' => array(
            'pretty_version' => '1.10.0',
            'version' => '1.10.0.0',
            'reference' => '679e3ce485b99e84c775d28e2e96fade9a7fb50a',
            'type' => 'library',
            'install_path' => __DIR__ . '/../phpdocumentor/type-resolver',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'phpstan/phpdoc-parser' => array(
            'pretty_version' => '2.3.0',
            'version' => '2.3.0.0',
            'reference' => '1e0cd5370df5dd2e556a36b9c62f62e555870495',
            'type' => 'library',
            'install_path' => __DIR__ . '/../phpstan/phpdoc-parser',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'phpunit/php-code-coverage' => array(
            'pretty_version' => '10.1.16',
            'version' => '10.1.16.0',
            'reference' => '7e308268858ed6baedc8704a304727d20bc07c77',
            'type' => 'library',
            'install_path' => __DIR__ . '/../phpunit/php-code-coverage',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'phpunit/php-file-iterator' => array(
            'pretty_version' => '4.1.0',
            'version' => '4.1.0.0',
            'reference' => 'a95037b6d9e608ba092da1b23931e537cadc3c3c',
            'type' => 'library',
            'install_path' => __DIR__ . '/../phpunit/php-file-iterator',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'phpunit/php-invoker' => array(
            'pretty_version' => '4.0.0',
            'version' => '4.0.0.0',
            'reference' => 'f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7',
            'type' => 'library',
            'install_path' => __DIR__ . '/../phpunit/php-invoker',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'phpunit/php-text-template' => array(
            'pretty_version' => '3.0.1',
            'version' => '3.0.1.0',
            'reference' => '0c7b06ff49e3d5072f057eb1fa59258bf287a748',
            'type' => 'library',
            'install_path' => __DIR__ . '/../phpunit/php-text-template',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'phpunit/php-timer' => array(
            'pretty_version' => '6.0.0',
            'version' => '6.0.0.0',
            'reference' => 'e2a2d67966e740530f4a3343fe2e030ffdc1161d',
            'type' => 'library',
            'install_path' => __DIR__ . '/../phpunit/php-timer',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'phpunit/phpunit' => array(
            'pretty_version' => '10.5.48',
            'version' => '10.5.48.0',
            'reference' => '6e0a2bc39f6fae7617989d690d76c48e6d2eb541',
            'type' => 'library',
            'install_path' => __DIR__ . '/../phpunit/phpunit',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'psalm/plugin-phpunit' => array(
            'pretty_version' => '0.19.5',
            'version' => '0.19.5.0',
            'reference' => '143f9d5e049fffcdbc0da3fbb99f6149f9d3e2dc',
            'type' => 'psalm-plugin',
            'install_path' => __DIR__ . '/../psalm/plugin-phpunit',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'psalm/psalm' => array(
            'dev_requirement' => true,
            'provided' => array(
                0 => '6.13.1',
            ),
        ),
        'psr/container' => array(
            'pretty_version' => '2.0.2',
            'version' => '2.0.2.0',
            'reference' => 'c71ecc56dfe541dbd90c5360474fbc405f8d5963',
            'type' => 'library',
            'install_path' => __DIR__ . '/../psr/container',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'psr/container-implementation' => array(
            'dev_requirement' => false,
            'provided' => array(
                0 => '1.1|2.0',
            ),
        ),
        'psr/event-dispatcher' => array(
            'pretty_version' => '1.0.0',
            'version' => '1.0.0.0',
            'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0',
            'type' => 'library',
            'install_path' => __DIR__ . '/../psr/event-dispatcher',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'psr/event-dispatcher-implementation' => array(
            'dev_requirement' => false,
            'provided' => array(
                0 => '1.0',
            ),
        ),
        'psr/http-factory' => array(
            'pretty_version' => '1.1.0',
            'version' => '1.1.0.0',
            'reference' => '2b4765fddfe3b508ac62f829e852b1501d3f6e8a',
            'type' => 'library',
            'install_path' => __DIR__ . '/../psr/http-factory',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'psr/http-message' => array(
            'pretty_version' => '2.0',
            'version' => '2.0.0.0',
            'reference' => '402d35bcb92c70c026d1a6a9883f06b2ead23d71',
            'type' => 'library',
            'install_path' => __DIR__ . '/../psr/http-message',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'psr/log' => array(
            'pretty_version' => '3.0.2',
            'version' => '3.0.2.0',
            'reference' => 'f16e1d5863e37f8d8c2a01719f5b34baa2b714d3',
            'type' => 'library',
            'install_path' => __DIR__ . '/../psr/log',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'psr/log-implementation' => array(
            'dev_requirement' => false,
            'provided' => array(
                0 => '1.0|2.0|3.0',
            ),
        ),
        'psr/simple-cache' => array(
            'pretty_version' => '3.0.0',
            'version' => '3.0.0.0',
            'reference' => '764e0b3939f5ca87cb904f570ef9be2d78a07865',
            'type' => 'library',
            'install_path' => __DIR__ . '/../psr/simple-cache',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'react/promise' => array(
            'pretty_version' => 'v3.3.0',
            'version' => '3.3.0.0',
            'reference' => '23444f53a813a3296c1368bb104793ce8d88f04a',
            'type' => 'library',
            'install_path' => __DIR__ . '/../react/promise',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'revolt/event-loop' => array(
            'pretty_version' => 'v1.0.7',
            'version' => '1.0.7.0',
            'reference' => '09bf1bf7f7f574453efe43044b06fafe12216eb3',
            'type' => 'library',
            'install_path' => __DIR__ . '/../revolt/event-loop',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'sebastian/cli-parser' => array(
            'pretty_version' => '2.0.1',
            'version' => '2.0.1.0',
            'reference' => 'c34583b87e7b7a8055bf6c450c2c77ce32a24084',
            'type' => 'library',
            'install_path' => __DIR__ . '/../sebastian/cli-parser',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'sebastian/code-unit' => array(
            'pretty_version' => '2.0.0',
            'version' => '2.0.0.0',
            'reference' => 'a81fee9eef0b7a76af11d121767abc44c104e503',
            'type' => 'library',
            'install_path' => __DIR__ . '/../sebastian/code-unit',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'sebastian/code-unit-reverse-lookup' => array(
            'pretty_version' => '3.0.0',
            'version' => '3.0.0.0',
            'reference' => '5e3a687f7d8ae33fb362c5c0743794bbb2420a1d',
            'type' => 'library',
            'install_path' => __DIR__ . '/../sebastian/code-unit-reverse-lookup',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'sebastian/comparator' => array(
            'pretty_version' => '5.0.3',
            'version' => '5.0.3.0',
            'reference' => 'a18251eb0b7a2dcd2f7aa3d6078b18545ef0558e',
            'type' => 'library',
            'install_path' => __DIR__ . '/../sebastian/comparator',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'sebastian/complexity' => array(
            'pretty_version' => '3.2.0',
            'version' => '3.2.0.0',
            'reference' => '68ff824baeae169ec9f2137158ee529584553799',
            'type' => 'library',
            'install_path' => __DIR__ . '/../sebastian/complexity',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'sebastian/diff' => array(
            'pretty_version' => '5.1.1',
            'version' => '5.1.1.0',
            'reference' => 'c41e007b4b62af48218231d6c2275e4c9b975b2e',
            'type' => 'library',
            'install_path' => __DIR__ . '/../sebastian/diff',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'sebastian/environment' => array(
            'pretty_version' => '6.1.0',
            'version' => '6.1.0.0',
            'reference' => '8074dbcd93529b357029f5cc5058fd3e43666984',
            'type' => 'library',
            'install_path' => __DIR__ . '/../sebastian/environment',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'sebastian/exporter' => array(
            'pretty_version' => '5.1.2',
            'version' => '5.1.2.0',
            'reference' => '955288482d97c19a372d3f31006ab3f37da47adf',
            'type' => 'library',
            'install_path' => __DIR__ . '/../sebastian/exporter',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'sebastian/global-state' => array(
            'pretty_version' => '6.0.2',
            'version' => '6.0.2.0',
            'reference' => '987bafff24ecc4c9ac418cab1145b96dd6e9cbd9',
            'type' => 'library',
            'install_path' => __DIR__ . '/../sebastian/global-state',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'sebastian/lines-of-code' => array(
            'pretty_version' => '2.0.2',
            'version' => '2.0.2.0',
            'reference' => '856e7f6a75a84e339195d48c556f23be2ebf75d0',
            'type' => 'library',
            'install_path' => __DIR__ . '/../sebastian/lines-of-code',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'sebastian/object-enumerator' => array(
            'pretty_version' => '5.0.0',
            'version' => '5.0.0.0',
            'reference' => '202d0e344a580d7f7d04b3fafce6933e59dae906',
            'type' => 'library',
            'install_path' => __DIR__ . '/../sebastian/object-enumerator',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'sebastian/object-reflector' => array(
            'pretty_version' => '3.0.0',
            'version' => '3.0.0.0',
            'reference' => '24ed13d98130f0e7122df55d06c5c4942a577957',
            'type' => 'library',
            'install_path' => __DIR__ . '/../sebastian/object-reflector',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'sebastian/recursion-context' => array(
            'pretty_version' => '5.0.0',
            'version' => '5.0.0.0',
            'reference' => '05909fb5bc7df4c52992396d0116aed689f93712',
            'type' => 'library',
            'install_path' => __DIR__ . '/../sebastian/recursion-context',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'sebastian/type' => array(
            'pretty_version' => '4.0.0',
            'version' => '4.0.0.0',
            'reference' => '462699a16464c3944eefc02ebdd77882bd3925bf',
            'type' => 'library',
            'install_path' => __DIR__ . '/../sebastian/type',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'sebastian/version' => array(
            'pretty_version' => '4.0.1',
            'version' => '4.0.1.0',
            'reference' => 'c51fa83a5d8f43f1402e3f32a005e6262244ef17',
            'type' => 'library',
            'install_path' => __DIR__ . '/../sebastian/version',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'seld/jsonlint' => array(
            'pretty_version' => '1.11.0',
            'version' => '1.11.0.0',
            'reference' => '1748aaf847fc731cfad7725aec413ee46f0cc3a2',
            'type' => 'library',
            'install_path' => __DIR__ . '/../seld/jsonlint',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'seld/phar-utils' => array(
            'pretty_version' => '1.2.1',
            'version' => '1.2.1.0',
            'reference' => 'ea2f4014f163c1be4c601b9b7bd6af81ba8d701c',
            'type' => 'library',
            'install_path' => __DIR__ . '/../seld/phar-utils',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'seld/signal-handler' => array(
            'pretty_version' => '2.0.2',
            'version' => '2.0.2.0',
            'reference' => '04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98',
            'type' => 'library',
            'install_path' => __DIR__ . '/../seld/signal-handler',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'slevomat/coding-standard' => array(
            'pretty_version' => '8.20.0',
            'version' => '8.20.0.0',
            'reference' => 'b4f9f02edd4e6a586777f0cabe8d05574323f3eb',
            'type' => 'phpcodesniffer-standard',
            'install_path' => __DIR__ . '/../slevomat/coding-standard',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'spatie/array-to-xml' => array(
            'pretty_version' => '3.4.0',
            'version' => '3.4.0.0',
            'reference' => '7dcfc67d60b0272926dabad1ec01f6b8a5fb5e67',
            'type' => 'library',
            'install_path' => __DIR__ . '/../spatie/array-to-xml',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'squizlabs/php_codesniffer' => array(
            'pretty_version' => '3.13.2',
            'version' => '3.13.2.0',
            'reference' => '5b5e3821314f947dd040c70f7992a64eac89025c',
            'type' => 'library',
            'install_path' => __DIR__ . '/../squizlabs/php_codesniffer',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'symfony/config' => array(
            'pretty_version' => 'v6.4.24',
            'version' => '6.4.24.0',
            'reference' => '80e2cf005cf17138c97193be0434cdcfd1b2212e',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/config',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'symfony/console' => array(
            'pretty_version' => 'v6.4.25',
            'version' => '6.4.25.0',
            'reference' => '273fd29ff30ba0a88ca5fb83f7cf1ab69306adae',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/console',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/dependency-injection' => array(
            'pretty_version' => 'v6.4.24',
            'version' => '6.4.24.0',
            'reference' => '929ab73b93247a15166ee79e807ccee4f930322d',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/dependency-injection',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'symfony/deprecation-contracts' => array(
            'pretty_version' => 'v3.6.0',
            'version' => '3.6.0.0',
            'reference' => '63afe740e99a13ba87ec199bb07bbdee937a5b62',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/deprecation-contracts',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/event-dispatcher' => array(
            'pretty_version' => 'v6.4.24',
            'version' => '6.4.24.0',
            'reference' => '307a09d8d7228d14a05e5e05b95fffdacab032b2',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/event-dispatcher',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/event-dispatcher-contracts' => array(
            'pretty_version' => 'v3.6.0',
            'version' => '3.6.0.0',
            'reference' => '59eb412e93815df44f05f342958efa9f46b1e586',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/event-dispatcher-contracts',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/event-dispatcher-implementation' => array(
            'dev_requirement' => false,
            'provided' => array(
                0 => '2.0|3.0',
            ),
        ),
        'symfony/filesystem' => array(
            'pretty_version' => 'v6.4.24',
            'version' => '6.4.24.0',
            'reference' => '75ae2edb7cdcc0c53766c30b0a2512b8df574bd8',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/filesystem',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/finder' => array(
            'pretty_version' => 'v6.4.24',
            'version' => '6.4.24.0',
            'reference' => '73089124388c8510efb8d2d1689285d285937b08',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/finder',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/polyfill-ctype' => array(
            'pretty_version' => 'v1.33.0',
            'version' => '1.33.0.0',
            'reference' => 'a3cc8b044a6ea513310cbd48ef7333b384945638',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/polyfill-ctype',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/polyfill-intl-grapheme' => array(
            'pretty_version' => 'v1.33.0',
            'version' => '1.33.0.0',
            'reference' => '380872130d3a5dd3ace2f4010d95125fde5d5c70',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/polyfill-intl-grapheme',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/polyfill-intl-normalizer' => array(
            'pretty_version' => 'v1.33.0',
            'version' => '1.33.0.0',
            'reference' => '3833d7255cc303546435cb650316bff708a1c75c',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/polyfill-intl-normalizer',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/polyfill-mbstring' => array(
            'pretty_version' => 'v1.33.0',
            'version' => '1.33.0.0',
            'reference' => '6d857f4d76bd4b343eac26d6b539585d2bc56493',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/polyfill-mbstring',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/polyfill-php73' => array(
            'dev_requirement' => false,
            'replaced' => array(
                0 => '*',
            ),
        ),
        'symfony/polyfill-php80' => array(
            'dev_requirement' => false,
            'replaced' => array(
                0 => '*',
            ),
        ),
        'symfony/polyfill-php81' => array(
            'dev_requirement' => false,
            'replaced' => array(
                0 => '*',
            ),
        ),
        'symfony/polyfill-php84' => array(
            'pretty_version' => 'v1.33.0',
            'version' => '1.33.0.0',
            'reference' => 'd8ced4d875142b6a7426000426b8abc631d6b191',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/polyfill-php84',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'symfony/process' => array(
            'pretty_version' => 'v6.4.25',
            'version' => '6.4.25.0',
            'reference' => '6be2f0c9ab3428587c07bed03aa9e3d1b823c6c8',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/process',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/service-contracts' => array(
            'pretty_version' => 'v3.6.0',
            'version' => '3.6.0.0',
            'reference' => 'f021b05a130d35510bd6b25fe9053c2a8a15d5d4',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/service-contracts',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/service-implementation' => array(
            'dev_requirement' => true,
            'provided' => array(
                0 => '1.1|2.0|3.0',
            ),
        ),
        'symfony/string' => array(
            'pretty_version' => 'v6.4.25',
            'version' => '6.4.25.0',
            'reference' => '7cdec7edfaf2cdd9c18901e35bcf9653d6209ff1',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/string',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
        'symfony/translation' => array(
            'pretty_version' => 'v6.4.24',
            'version' => '6.4.24.0',
            'reference' => '300b72643e89de0734d99a9e3f8494a3ef6936e1',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/translation',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'symfony/translation-contracts' => array(
            'pretty_version' => 'v3.6.0',
            'version' => '3.6.0.0',
            'reference' => 'df210c7a2573f1913b2d17cc95f90f53a73d8f7d',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/translation-contracts',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'symfony/translation-implementation' => array(
            'dev_requirement' => true,
            'provided' => array(
                0 => '2.3|3.0',
            ),
        ),
        'symfony/var-exporter' => array(
            'pretty_version' => 'v6.4.24',
            'version' => '6.4.24.0',
            'reference' => '1e742d559fe5b19d0cdc281b1bf0b1fcc243bd35',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/var-exporter',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'symfony/yaml' => array(
            'pretty_version' => 'v6.4.24',
            'version' => '6.4.24.0',
            'reference' => '742a8efc94027624b36b10ba58e23d402f961f51',
            'type' => 'library',
            'install_path' => __DIR__ . '/../symfony/yaml',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'theseer/tokenizer' => array(
            'pretty_version' => '1.2.3',
            'version' => '1.2.3.0',
            'reference' => '737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2',
            'type' => 'library',
            'install_path' => __DIR__ . '/../theseer/tokenizer',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'vimeo/psalm' => array(
            'pretty_version' => '6.13.1',
            'version' => '6.13.1.0',
            'reference' => '1e3b7f0a8ab32b23197b91107adc0a7ed8a05b51',
            'type' => 'project',
            'install_path' => __DIR__ . '/../vimeo/psalm',
            'aliases' => array(),
            'dev_requirement' => true,
        ),
        'webmozart/assert' => array(
            'pretty_version' => '1.11.0',
            'version' => '1.11.0.0',
            'reference' => '11cb2199493b2f8a3b53e7f19068fc6aac760991',
            'type' => 'library',
            'install_path' => __DIR__ . '/../webmozart/assert',
            'aliases' => array(),
            'dev_requirement' => false,
        ),
    ),
);
Copyright (C) 2015 Composer

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.
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver;

use Composer\Semver\Constraint\Constraint;

class Comparator
{
    /**
     * Evaluates the expression: $version1 > $version2.
     *
     * @param string $version1
     * @param string $version2
     *
     * @return bool
     */
    public static function greaterThan($version1, $version2)
    {
        return self::compare($version1, '>', $version2);
    }

    /**
     * Evaluates the expression: $version1 >= $version2.
     *
     * @param string $version1
     * @param string $version2
     *
     * @return bool
     */
    public static function greaterThanOrEqualTo($version1, $version2)
    {
        return self::compare($version1, '>=', $version2);
    }

    /**
     * Evaluates the expression: $version1 < $version2.
     *
     * @param string $version1
     * @param string $version2
     *
     * @return bool
     */
    public static function lessThan($version1, $version2)
    {
        return self::compare($version1, '<', $version2);
    }

    /**
     * Evaluates the expression: $version1 <= $version2.
     *
     * @param string $version1
     * @param string $version2
     *
     * @return bool
     */
    public static function lessThanOrEqualTo($version1, $version2)
    {
        return self::compare($version1, '<=', $version2);
    }

    /**
     * Evaluates the expression: $version1 == $version2.
     *
     * @param string $version1
     * @param string $version2
     *
     * @return bool
     */
    public static function equalTo($version1, $version2)
    {
        return self::compare($version1, '==', $version2);
    }

    /**
     * Evaluates the expression: $version1 != $version2.
     *
     * @param string $version1
     * @param string $version2
     *
     * @return bool
     */
    public static function notEqualTo($version1, $version2)
    {
        return self::compare($version1, '!=', $version2);
    }

    /**
     * Evaluates the expression: $version1 $operator $version2.
     *
     * @param string $version1
     * @param string $operator
     * @param string $version2
     *
     * @return bool
     *
     * @phpstan-param Constraint::STR_OP_*  $operator
     */
    public static function compare($version1, $operator, $version2)
    {
        $constraint = new Constraint($operator, $version2);

        return $constraint->matchSpecific(new Constraint('==', $version1), true);
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver;

use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\MatchAllConstraint;
use Composer\Semver\Constraint\MultiConstraint;
use Composer\Semver\Constraint\Constraint;

/**
 * Version parser.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class VersionParser
{
    /**
     * Regex to match pre-release data (sort of).
     *
     * Due to backwards compatibility:
     *   - Instead of enforcing hyphen, an underscore, dot or nothing at all are also accepted.
     *   - Only stabilities as recognized by Composer are allowed to precede a numerical identifier.
     *   - Numerical-only pre-release identifiers are not supported, see tests.
     *
     *                        |--------------|
     * [major].[minor].[patch] -[pre-release] +[build-metadata]
     *
     * @var string
     */
    private static $modifierRegex = '[._-]?(?:(stable|beta|b|RC|alpha|a|patch|pl|p)((?:[.-]?\d+)*+)?)?([.-]?dev)?';

    /** @var string */
    private static $stabilitiesRegex = 'stable|RC|beta|alpha|dev';

    /**
     * Returns the stability of a version.
     *
     * @param string $version
     *
     * @return string
     * @phpstan-return 'stable'|'RC'|'beta'|'alpha'|'dev'
     */
    public static function parseStability($version)
    {
        $version = (string) preg_replace('{#.+$}', '', (string) $version);

        if (strpos($version, 'dev-') === 0 || '-dev' === substr($version, -4)) {
            return 'dev';
        }

        preg_match('{' . self::$modifierRegex . '(?:\+.*)?$}i', strtolower($version), $match);

        if (!empty($match[3])) {
            return 'dev';
        }

        if (!empty($match[1])) {
            if ('beta' === $match[1] || 'b' === $match[1]) {
                return 'beta';
            }
            if ('alpha' === $match[1] || 'a' === $match[1]) {
                return 'alpha';
            }
            if ('rc' === $match[1]) {
                return 'RC';
            }
        }

        return 'stable';
    }

    /**
     * @param string $stability
     *
     * @return string
     * @phpstan-return 'stable'|'RC'|'beta'|'alpha'|'dev'
     */
    public static function normalizeStability($stability)
    {
        $stability = strtolower((string) $stability);

        if (!in_array($stability, array('stable', 'rc', 'beta', 'alpha', 'dev'), true)) {
            throw new \InvalidArgumentException('Invalid stability string "'.$stability.'", expected one of stable, RC, beta, alpha or dev');
        }

        return $stability === 'rc' ? 'RC' : $stability;
    }

    /**
     * Normalizes a version string to be able to perform comparisons on it.
     *
     * @param string $version
     * @param ?string $fullVersion optional complete version string to give more context
     *
     * @throws \UnexpectedValueException
     *
     * @return string
     */
    public function normalize($version, $fullVersion = null)
    {
        $version = trim((string) $version);
        $origVersion = $version;
        if (null === $fullVersion) {
            $fullVersion = $version;
        }

        // strip off aliasing
        if (preg_match('{^([^,\s]++) ++as ++([^,\s]++)$}', $version, $match)) {
            $version = $match[1];
        }

        // strip off stability flag
        if (preg_match('{@(?:' . self::$stabilitiesRegex . ')$}i', $version, $match)) {
            $version = substr($version, 0, strlen($version) - strlen($match[0]));
        }

        // normalize master/trunk/default branches to dev-name for BC with 1.x as these used to be valid constraints
        if (\in_array($version, array('master', 'trunk', 'default'), true)) {
            $version = 'dev-' . $version;
        }

        // if requirement is branch-like, use full name
        if (stripos($version, 'dev-') === 0) {
            return 'dev-' . substr($version, 4);
        }

        // strip off build metadata
        if (preg_match('{^([^,\s+]++)\+[^\s]++$}', $version, $match)) {
            $version = $match[1];
        }

        // match classical versioning
        if (preg_match('{^v?(\d{1,5}+)(\.\d++)?(\.\d++)?(\.\d++)?' . self::$modifierRegex . '$}i', $version, $matches)) {
            $version = $matches[1]
                . (!empty($matches[2]) ? $matches[2] : '.0')
                . (!empty($matches[3]) ? $matches[3] : '.0')
                . (!empty($matches[4]) ? $matches[4] : '.0');
            $index = 5;
        // match date(time) based versioning
        } elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3}){0,2})' . self::$modifierRegex . '$}i', $version, $matches)) {
            $version = (string) preg_replace('{\D}', '.', $matches[1]);
            $index = 2;
        }

        // add version modifiers if a version was matched
        if (isset($index)) {
            if (!empty($matches[$index])) {
                if ('stable' === $matches[$index]) {
                    return $version;
                }
                $version .= '-' . $this->expandStability($matches[$index]) . (isset($matches[$index + 1]) && '' !== $matches[$index + 1] ? ltrim($matches[$index + 1], '.-') : '');
            }

            if (!empty($matches[$index + 2])) {
                $version .= '-dev';
            }

            return $version;
        }

        // match dev branches
        if (preg_match('{(.*?)[.-]?dev$}i', $version, $match)) {
            try {
                $normalized = $this->normalizeBranch($match[1]);
                // a branch ending with -dev is only valid if it is numeric
                // if it gets prefixed with dev- it means the branch name should
                // have had a dev- prefix already when passed to normalize
                if (strpos($normalized, 'dev-') === false) {
                    return $normalized;
                }
            } catch (\Exception $e) {
            }
        }

        $extraMessage = '';
        if (preg_match('{ +as +' . preg_quote($version) . '(?:@(?:'.self::$stabilitiesRegex.'))?$}', $fullVersion)) {
            $extraMessage = ' in "' . $fullVersion . '", the alias must be an exact version';
        } elseif (preg_match('{^' . preg_quote($version) . '(?:@(?:'.self::$stabilitiesRegex.'))? +as +}', $fullVersion)) {
            $extraMessage = ' in "' . $fullVersion . '", the alias source must be an exact version, if it is a branch name you should prefix it with dev-';
        }

        throw new \UnexpectedValueException('Invalid version string "' . $origVersion . '"' . $extraMessage);
    }

    /**
     * Extract numeric prefix from alias, if it is in numeric format, suitable for version comparison.
     *
     * @param string $branch Branch name (e.g. 2.1.x-dev)
     *
     * @return string|false Numeric prefix if present (e.g. 2.1.) or false
     */
    public function parseNumericAliasPrefix($branch)
    {
        if (preg_match('{^(?P<version>(\d++\\.)*\d++)(?:\.x)?-dev$}i', (string) $branch, $matches)) {
            return $matches['version'] . '.';
        }

        return false;
    }

    /**
     * Normalizes a branch name to be able to perform comparisons on it.
     *
     * @param string $name
     *
     * @return string
     */
    public function normalizeBranch($name)
    {
        $name = trim((string) $name);

        if (preg_match('{^v?(\d++)(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?$}i', $name, $matches)) {
            $version = '';
            for ($i = 1; $i < 5; ++$i) {
                $version .= isset($matches[$i]) ? str_replace(array('*', 'X'), 'x', $matches[$i]) : '.x';
            }

            return str_replace('x', '9999999', $version) . '-dev';
        }

        return 'dev-' . $name;
    }

    /**
     * Normalizes a default branch name (i.e. master on git) to 9999999-dev.
     *
     * @param string $name
     *
     * @return string
     *
     * @deprecated No need to use this anymore in theory, Composer 2 does not normalize any branch names to 9999999-dev anymore
     */
    public function normalizeDefaultBranch($name)
    {
        if ($name === 'dev-master' || $name === 'dev-default' || $name === 'dev-trunk') {
            return '9999999-dev';
        }

        return (string) $name;
    }

    /**
     * Parses a constraint string into MultiConstraint and/or Constraint objects.
     *
     * @param string $constraints
     *
     * @return ConstraintInterface
     */
    public function parseConstraints($constraints)
    {
        $prettyConstraint = (string) $constraints;

        $orConstraints = preg_split('{\s*\|\|?\s*}', trim((string) $constraints));
        if (false === $orConstraints) {
            throw new \RuntimeException('Failed to preg_split string: '.$constraints);
        }
        $orGroups = array();

        foreach ($orConstraints as $orConstraint) {
            $andConstraints = preg_split('{(?<!^|as|[=>< ,]) *(?<!-)[, ](?!-) *(?!,|as|$)}', $orConstraint);
            if (false === $andConstraints) {
                throw new \RuntimeException('Failed to preg_split string: '.$orConstraint);
            }
            if (\count($andConstraints) > 1) {
                $constraintObjects = array();
                foreach ($andConstraints as $andConstraint) {
                    foreach ($this->parseConstraint($andConstraint) as $parsedAndConstraint) {
                        $constraintObjects[] = $parsedAndConstraint;
                    }
                }
            } else {
                $constraintObjects = $this->parseConstraint($andConstraints[0]);
            }

            if (1 === \count($constraintObjects)) {
                $constraint = $constraintObjects[0];
            } else {
                $constraint = new MultiConstraint($constraintObjects);
            }

            $orGroups[] = $constraint;
        }

        $parsedConstraint = MultiConstraint::create($orGroups, false);

        $parsedConstraint->setPrettyString($prettyConstraint);

        return $parsedConstraint;
    }

    /**
     * @param string $constraint
     *
     * @throws \UnexpectedValueException
     *
     * @return array
     *
     * @phpstan-return non-empty-array<ConstraintInterface>
     */
    private function parseConstraint($constraint)
    {
        // strip off aliasing
        if (preg_match('{^([^,\s]++) ++as ++([^,\s]++)$}', $constraint, $match)) {
            $constraint = $match[1];
        }

        // strip @stability flags, and keep it for later use
        if (preg_match('{^([^,\s]*?)@(' . self::$stabilitiesRegex . ')$}i', $constraint, $match)) {
            $constraint = '' !== $match[1] ? $match[1] : '*';
            if ($match[2] !== 'stable') {
                $stabilityModifier = $match[2];
            }
        }

        // get rid of #refs as those are used by composer only
        if (preg_match('{^(dev-[^,\s@]+?|[^,\s@]+?\.x-dev)#.+$}i', $constraint, $match)) {
            $constraint = $match[1];
        }

        if (preg_match('{^(v)?[xX*](\.[xX*])*$}i', $constraint, $match)) {
            if (!empty($match[1]) || !empty($match[2])) {
                return array(new Constraint('>=', '0.0.0.0-dev'));
            }

            return array(new MatchAllConstraint());
        }

        $versionRegex = 'v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.(\d++))?(?:' . self::$modifierRegex . '|\.([xX*][.-]?dev))(?:\+[^\s]+)?';

        // Tilde Range
        //
        // Like wildcard constraints, unsuffixed tilde constraints say that they must be greater than the previous
        // version, to ensure that unstable instances of the current version are allowed. However, if a stability
        // suffix is added to the constraint, then a >= match on the current version is used instead.
        if (preg_match('{^~>?' . $versionRegex . '$}i', $constraint, $matches)) {
            if (strpos($constraint, '~>') === 0) {
                throw new \UnexpectedValueException(
                    'Could not parse version constraint ' . $constraint . ': ' .
                    'Invalid operator "~>", you probably meant to use the "~" operator'
                );
            }

            // Work out which position in the version we are operating at
            if (isset($matches[4]) && '' !== $matches[4] && null !== $matches[4]) {
                $position = 4;
            } elseif (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) {
                $position = 3;
            } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) {
                $position = 2;
            } else {
                $position = 1;
            }

            // when matching 2.x-dev or 3.0.x-dev we have to shift the second or third number, despite no second/third number matching above
            if (!empty($matches[8])) {
                $position++;
            }

            // Calculate the stability suffix
            $stabilitySuffix = '';
            if (empty($matches[5]) && empty($matches[7]) && empty($matches[8])) {
                $stabilitySuffix .= '-dev';
            }

            $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1));
            $lowerBound = new Constraint('>=', $lowVersion);

            // For upper bound, we increment the position of one more significance,
            // but highPosition = 0 would be illegal
            $highPosition = max(1, $position - 1);
            $highVersion = $this->manipulateVersionString($matches, $highPosition, 1) . '-dev';
            $upperBound = new Constraint('<', $highVersion);

            return array(
                $lowerBound,
                $upperBound,
            );
        }

        // Caret Range
        //
        // Allows changes that do not modify the left-most non-zero digit in the [major, minor, patch] tuple.
        // In other words, this allows patch and minor updates for versions 1.0.0 and above, patch updates for
        // versions 0.X >=0.1.0, and no updates for versions 0.0.X
        if (preg_match('{^\^' . $versionRegex . '($)}i', $constraint, $matches)) {
            // Work out which position in the version we are operating at
            if ('0' !== $matches[1] || '' === $matches[2] || null === $matches[2]) {
                $position = 1;
            } elseif ('0' !== $matches[2] || '' === $matches[3] || null === $matches[3]) {
                $position = 2;
            } else {
                $position = 3;
            }

            // Calculate the stability suffix
            $stabilitySuffix = '';
            if (empty($matches[5]) && empty($matches[7]) && empty($matches[8])) {
                $stabilitySuffix .= '-dev';
            }

            $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1));
            $lowerBound = new Constraint('>=', $lowVersion);

            // For upper bound, we increment the position of one more significance,
            // but highPosition = 0 would be illegal
            $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev';
            $upperBound = new Constraint('<', $highVersion);

            return array(
                $lowerBound,
                $upperBound,
            );
        }

        // X Range
        //
        // Any of X, x, or * may be used to "stand in" for one of the numeric values in the [major, minor, patch] tuple.
        // A partial version range is treated as an X-Range, so the special character is in fact optional.
        if (preg_match('{^v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.[xX*])++$}', $constraint, $matches)) {
            if (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) {
                $position = 3;
            } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) {
                $position = 2;
            } else {
                $position = 1;
            }

            $lowVersion = $this->manipulateVersionString($matches, $position) . '-dev';
            $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev';

            if ($lowVersion === '0.0.0.0-dev') {
                return array(new Constraint('<', $highVersion));
            }

            return array(
                new Constraint('>=', $lowVersion),
                new Constraint('<', $highVersion),
            );
        }

        // Hyphen Range
        //
        // Specifies an inclusive set. If a partial version is provided as the first version in the inclusive range,
        // then the missing pieces are replaced with zeroes. If a partial version is provided as the second version in
        // the inclusive range, then all versions that start with the supplied parts of the tuple are accepted, but
        // nothing that would be greater than the provided tuple parts.
        if (preg_match('{^(?P<from>' . $versionRegex . ') +- +(?P<to>' . $versionRegex . ')($)}i', $constraint, $matches)) {
            // Calculate the stability suffix
            $lowStabilitySuffix = '';
            if (empty($matches[6]) && empty($matches[8]) && empty($matches[9])) {
                $lowStabilitySuffix = '-dev';
            }

            $lowVersion = $this->normalize($matches['from']);
            $lowerBound = new Constraint('>=', $lowVersion . $lowStabilitySuffix);

            $empty = function ($x) {
                return ($x === 0 || $x === '0') ? false : empty($x);
            };

            if ((!$empty($matches[12]) && !$empty($matches[13])) || !empty($matches[15]) || !empty($matches[17]) || !empty($matches[18])) {
                $highVersion = $this->normalize($matches['to']);
                $upperBound = new Constraint('<=', $highVersion);
            } else {
                $highMatch = array('', $matches[11], $matches[12], $matches[13], $matches[14]);

                // validate to version
                $this->normalize($matches['to']);

                $highVersion = $this->manipulateVersionString($highMatch, $empty($matches[12]) ? 1 : 2, 1) . '-dev';
                $upperBound = new Constraint('<', $highVersion);
            }

            return array(
                $lowerBound,
                $upperBound,
            );
        }

        // Basic Comparators
        if (preg_match('{^(<>|!=|>=?|<=?|==?)?\s*(.*)}', $constraint, $matches)) {
            try {
                try {
                    $version = $this->normalize($matches[2]);
                } catch (\UnexpectedValueException $e) {
                    // recover from an invalid constraint like foobar-dev which should be dev-foobar
                    // except if the constraint uses a known operator, in which case it must be a parse error
                    if (substr($matches[2], -4) === '-dev' && preg_match('{^[0-9a-zA-Z-./]+$}', $matches[2])) {
                        $version = $this->normalize('dev-'.substr($matches[2], 0, -4));
                    } else {
                        throw $e;
                    }
                }

                $op = $matches[1] ?: '=';

                if ($op !== '==' && $op !== '=' && !empty($stabilityModifier) && self::parseStability($version) === 'stable') {
                    $version .= '-' . $stabilityModifier;
                } elseif ('<' === $op || '>=' === $op) {
                    if (!preg_match('/-' . self::$modifierRegex . '$/', strtolower($matches[2]))) {
                        if (strpos($matches[2], 'dev-') !== 0) {
                            $version .= '-dev';
                        }
                    }
                }

                return array(new Constraint($matches[1] ?: '=', $version));
            } catch (\Exception $e) {
            }
        }

        $message = 'Could not parse version constraint ' . $constraint;
        if (isset($e)) {
            $message .= ': ' . $e->getMessage();
        }

        throw new \UnexpectedValueException($message);
    }

    /**
     * Increment, decrement, or simply pad a version number.
     *
     * Support function for {@link parseConstraint()}
     *
     * @param array  $matches   Array with version parts in array indexes 1,2,3,4
     * @param int    $position  1,2,3,4 - which segment of the version to increment/decrement
     * @param int    $increment
     * @param string $pad       The string to pad version parts after $position
     *
     * @return string|null The new version
     *
     * @phpstan-param string[] $matches
     */
    private function manipulateVersionString(array $matches, $position, $increment = 0, $pad = '0')
    {
        for ($i = 4; $i > 0; --$i) {
            if ($i > $position) {
                $matches[$i] = $pad;
            } elseif ($i === $position && $increment) {
                $matches[$i] += $increment;
                // If $matches[$i] was 0, carry the decrement
                if ($matches[$i] < 0) {
                    $matches[$i] = $pad;
                    --$position;

                    // Return null on a carry overflow
                    if ($i === 1) {
                        return null;
                    }
                }
            }
        }

        return $matches[1] . '.' . $matches[2] . '.' . $matches[3] . '.' . $matches[4];
    }

    /**
     * Expand shorthand stability string to long version.
     *
     * @param string $stability
     *
     * @return string
     */
    private function expandStability($stability)
    {
        $stability = strtolower($stability);

        switch ($stability) {
            case 'a':
                return 'alpha';
            case 'b':
                return 'beta';
            case 'p':
            case 'pl':
                return 'patch';
            case 'rc':
                return 'RC';
            default:
                return $stability;
        }
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver;

use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\ConstraintInterface;

/**
 * Helper class to evaluate constraint by compiling and reusing the code to evaluate
 */
class CompilingMatcher
{
    /**
     * @var array
     * @phpstan-var array<string, callable>
     */
    private static $compiledCheckerCache = array();
    /**
     * @var array
     * @phpstan-var array<string, bool>
     */
    private static $resultCache = array();

    /** @var bool */
    private static $enabled;

    /**
     * @phpstan-var array<Constraint::OP_*, Constraint::STR_OP_*>
     */
    private static $transOpInt = array(
        Constraint::OP_EQ => Constraint::STR_OP_EQ,
        Constraint::OP_LT => Constraint::STR_OP_LT,
        Constraint::OP_LE => Constraint::STR_OP_LE,
        Constraint::OP_GT => Constraint::STR_OP_GT,
        Constraint::OP_GE => Constraint::STR_OP_GE,
        Constraint::OP_NE => Constraint::STR_OP_NE,
    );

    /**
     * Clears the memoization cache once you are done
     *
     * @return void
     */
    public static function clear()
    {
        self::$resultCache = array();
        self::$compiledCheckerCache = array();
    }

    /**
     * Evaluates the expression: $constraint match $operator $version
     *
     * @param ConstraintInterface $constraint
     * @param int                 $operator
     * @phpstan-param Constraint::OP_*  $operator
     * @param string              $version
     *
     * @return bool
     */
    public static function match(ConstraintInterface $constraint, $operator, $version)
    {
        $resultCacheKey = $operator.$constraint.';'.$version;

        if (isset(self::$resultCache[$resultCacheKey])) {
            return self::$resultCache[$resultCacheKey];
        }

        if (self::$enabled === null) {
            self::$enabled = !\in_array('eval', explode(',', (string) ini_get('disable_functions')), true);
        }
        if (!self::$enabled) {
            return self::$resultCache[$resultCacheKey] = $constraint->matches(new Constraint(self::$transOpInt[$operator], $version));
        }

        $cacheKey = $operator.$constraint;
        if (!isset(self::$compiledCheckerCache[$cacheKey])) {
            $code = $constraint->compile($operator);
            self::$compiledCheckerCache[$cacheKey] = $function = eval('return function($v, $b){return '.$code.';};');
        } else {
            $function = self::$compiledCheckerCache[$cacheKey];
        }

        return self::$resultCache[$resultCacheKey] = $function($version, strpos($version, 'dev-') === 0);
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver;

use Composer\Semver\Constraint\Constraint;

class Semver
{
    const SORT_ASC = 1;
    const SORT_DESC = -1;

    /** @var VersionParser */
    private static $versionParser;

    /**
     * Determine if given version satisfies given constraints.
     *
     * @param string $version
     * @param string $constraints
     *
     * @return bool
     */
    public static function satisfies($version, $constraints)
    {
        if (null === self::$versionParser) {
            self::$versionParser = new VersionParser();
        }

        $versionParser = self::$versionParser;
        $provider = new Constraint('==', $versionParser->normalize($version));
        $parsedConstraints = $versionParser->parseConstraints($constraints);

        return $parsedConstraints->matches($provider);
    }

    /**
     * Return all versions that satisfy given constraints.
     *
     * @param string[] $versions
     * @param string   $constraints
     *
     * @return list<string>
     */
    public static function satisfiedBy(array $versions, $constraints)
    {
        $versions = array_filter($versions, function ($version) use ($constraints) {
            return Semver::satisfies($version, $constraints);
        });

        return array_values($versions);
    }

    /**
     * Sort given array of versions.
     *
     * @param string[] $versions
     *
     * @return list<string>
     */
    public static function sort(array $versions)
    {
        return self::usort($versions, self::SORT_ASC);
    }

    /**
     * Sort given array of versions in reverse.
     *
     * @param string[] $versions
     *
     * @return list<string>
     */
    public static function rsort(array $versions)
    {
        return self::usort($versions, self::SORT_DESC);
    }

    /**
     * @param string[] $versions
     * @param int      $direction
     *
     * @return list<string>
     */
    private static function usort(array $versions, $direction)
    {
        if (null === self::$versionParser) {
            self::$versionParser = new VersionParser();
        }

        $versionParser = self::$versionParser;
        $normalized = array();

        // Normalize outside of usort() scope for minor performance increase.
        // Creates an array of arrays: [[normalized, key], ...]
        foreach ($versions as $key => $version) {
            $normalizedVersion = $versionParser->normalize($version);
            $normalizedVersion = $versionParser->normalizeDefaultBranch($normalizedVersion);
            $normalized[] = array($normalizedVersion, $key);
        }

        usort($normalized, function (array $left, array $right) use ($direction) {
            if ($left[0] === $right[0]) {
                return 0;
            }

            if (Comparator::lessThan($left[0], $right[0])) {
                return -$direction;
            }

            return $direction;
        });

        // Recreate input array, using the original indexes which are now in sorted order.
        $sorted = array();
        foreach ($normalized as $item) {
            $sorted[] = $versions[$item[1]];
        }

        return $sorted;
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver;

use Composer\Semver\Constraint\Constraint;

class Interval
{
    /** @var Constraint */
    private $start;
    /** @var Constraint */
    private $end;

    public function __construct(Constraint $start, Constraint $end)
    {
        $this->start = $start;
        $this->end = $end;
    }

    /**
     * @return Constraint
     */
    public function getStart()
    {
        return $this->start;
    }

    /**
     * @return Constraint
     */
    public function getEnd()
    {
        return $this->end;
    }

    /**
     * @return Constraint
     */
    public static function fromZero()
    {
        static $zero;

        if (null === $zero) {
            $zero = new Constraint('>=', '0.0.0.0-dev');
        }

        return $zero;
    }

    /**
     * @return Constraint
     */
    public static function untilPositiveInfinity()
    {
        static $positiveInfinity;

        if (null === $positiveInfinity) {
            $positiveInfinity = new Constraint('<', PHP_INT_MAX.'.0.0.0');
        }

        return $positiveInfinity;
    }

    /**
     * @return self
     */
    public static function any()
    {
        return new self(self::fromZero(), self::untilPositiveInfinity());
    }

    /**
     * @return array{'names': string[], 'exclude': bool}
     */
    public static function anyDev()
    {
        // any == exclude nothing
        return array('names' => array(), 'exclude' => true);
    }

    /**
     * @return array{'names': string[], 'exclude': bool}
     */
    public static function noDev()
    {
        // nothing == no names included
        return array('names' => array(), 'exclude' => false);
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver;

use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\MatchAllConstraint;
use Composer\Semver\Constraint\MatchNoneConstraint;
use Composer\Semver\Constraint\MultiConstraint;

/**
 * Helper class generating intervals from constraints
 *
 * This contains utilities for:
 *
 *  - compacting an existing constraint which can be used to combine several into one
 * by creating a MultiConstraint out of the many constraints you have.
 *
 *  - checking whether one subset is a subset of another.
 *
 * Note: You should call clear to free memoization memory  usage when you are done using this class
 */
class Intervals
{
    /**
     * @phpstan-var array<string, array{'numeric': Interval[], 'branches': array{'names': string[], 'exclude': bool}}>
     */
    private static $intervalsCache = array();

    /**
     * @phpstan-var array<string, int>
     */
    private static $opSortOrder = array(
        '>=' => -3,
        '<' => -2,
        '>' => 2,
        '<=' => 3,
    );

    /**
     * Clears the memoization cache once you are done
     *
     * @return void
     */
    public static function clear()
    {
        self::$intervalsCache = array();
    }

    /**
     * Checks whether $candidate is a subset of $constraint
     *
     * @return bool
     */
    public static function isSubsetOf(ConstraintInterface $candidate, ConstraintInterface $constraint)
    {
        if ($constraint instanceof MatchAllConstraint) {
            return true;
        }

        if ($candidate instanceof MatchNoneConstraint || $constraint instanceof MatchNoneConstraint) {
            return false;
        }

        $intersectionIntervals = self::get(new MultiConstraint(array($candidate, $constraint), true));
        $candidateIntervals = self::get($candidate);
        if (\count($intersectionIntervals['numeric']) !== \count($candidateIntervals['numeric'])) {
            return false;
        }

        foreach ($intersectionIntervals['numeric'] as $index => $interval) {
            if (!isset($candidateIntervals['numeric'][$index])) {
                return false;
            }

            if ((string) $candidateIntervals['numeric'][$index]->getStart() !== (string) $interval->getStart()) {
                return false;
            }

            if ((string) $candidateIntervals['numeric'][$index]->getEnd() !== (string) $interval->getEnd()) {
                return false;
            }
        }

        if ($intersectionIntervals['branches']['exclude'] !== $candidateIntervals['branches']['exclude']) {
            return false;
        }
        if (\count($intersectionIntervals['branches']['names']) !== \count($candidateIntervals['branches']['names'])) {
            return false;
        }
        foreach ($intersectionIntervals['branches']['names'] as $index => $name) {
            if ($name !== $candidateIntervals['branches']['names'][$index]) {
                return false;
            }
        }

        return true;
    }

    /**
     * Checks whether $a and $b have any intersection, equivalent to $a->matches($b)
     *
     * @return bool
     */
    public static function haveIntersections(ConstraintInterface $a, ConstraintInterface $b)
    {
        if ($a instanceof MatchAllConstraint || $b instanceof MatchAllConstraint) {
            return true;
        }

        if ($a instanceof MatchNoneConstraint || $b instanceof MatchNoneConstraint) {
            return false;
        }

        $intersectionIntervals = self::generateIntervals(new MultiConstraint(array($a, $b), true), true);

        return \count($intersectionIntervals['numeric']) > 0 || $intersectionIntervals['branches']['exclude'] || \count($intersectionIntervals['branches']['names']) > 0;
    }

    /**
     * Attempts to optimize a MultiConstraint
     *
     * When merging MultiConstraints together they can get very large, this will
     * compact it by looking at the real intervals covered by all the constraints
     * and then creates a new constraint containing only the smallest amount of rules
     * to match the same intervals.
     *
     * @return ConstraintInterface
     */
    public static function compactConstraint(ConstraintInterface $constraint)
    {
        if (!$constraint instanceof MultiConstraint) {
            return $constraint;
        }

        $intervals = self::generateIntervals($constraint);
        $constraints = array();
        $hasNumericMatchAll = false;

        if (\count($intervals['numeric']) === 1 && (string) $intervals['numeric'][0]->getStart() === (string) Interval::fromZero() && (string) $intervals['numeric'][0]->getEnd() === (string) Interval::untilPositiveInfinity()) {
            $constraints[] = $intervals['numeric'][0]->getStart();
            $hasNumericMatchAll = true;
        } else {
            $unEqualConstraints = array();
            for ($i = 0, $count = \count($intervals['numeric']); $i < $count; $i++) {
                $interval = $intervals['numeric'][$i];

                // if current interval ends with < N and next interval begins with > N we can swap this out for != N
                // but this needs to happen as a conjunctive expression together with the start of the current interval
                // and end of next interval, so [>=M, <N] || [>N, <P] => [>=M, !=N, <P] but M/P can be skipped if
                // they are zero/+inf
                if ($interval->getEnd()->getOperator() === '<' && $i+1 < $count) {
                    $nextInterval = $intervals['numeric'][$i+1];
                    if ($interval->getEnd()->getVersion() === $nextInterval->getStart()->getVersion() && $nextInterval->getStart()->getOperator() === '>') {
                        // only add a start if we didn't already do so, can be skipped if we're looking at second
                        // interval in [>=M, <N] || [>N, <P] || [>P, <Q] where unEqualConstraints currently contains
                        // [>=M, !=N] already and we only want to add !=P right now
                        if (\count($unEqualConstraints) === 0 && (string) $interval->getStart() !== (string) Interval::fromZero()) {
                            $unEqualConstraints[] = $interval->getStart();
                        }
                        $unEqualConstraints[] = new Constraint('!=', $interval->getEnd()->getVersion());
                        continue;
                    }
                }

                if (\count($unEqualConstraints) > 0) {
                    // this is where the end of the following interval of a != constraint is added as explained above
                    if ((string) $interval->getEnd() !== (string) Interval::untilPositiveInfinity()) {
                        $unEqualConstraints[] = $interval->getEnd();
                    }

                    // count is 1 if entire constraint is just one != expression
                    if (\count($unEqualConstraints) > 1) {
                        $constraints[] = new MultiConstraint($unEqualConstraints, true);
                    } else {
                        $constraints[] = $unEqualConstraints[0];
                    }

                    $unEqualConstraints = array();
                    continue;
                }

                // convert back >= x - <= x intervals to == x
                if ($interval->getStart()->getVersion() === $interval->getEnd()->getVersion() && $interval->getStart()->getOperator() === '>=' && $interval->getEnd()->getOperator() === '<=') {
                    $constraints[] = new Constraint('==', $interval->getStart()->getVersion());
                    continue;
                }

                if ((string) $interval->getStart() === (string) Interval::fromZero()) {
                    $constraints[] = $interval->getEnd();
                } elseif ((string) $interval->getEnd() === (string) Interval::untilPositiveInfinity()) {
                    $constraints[] = $interval->getStart();
                } else {
                    $constraints[] = new MultiConstraint(array($interval->getStart(), $interval->getEnd()), true);
                }
            }
        }

        $devConstraints = array();

        if (0 === \count($intervals['branches']['names'])) {
            if ($intervals['branches']['exclude']) {
                if ($hasNumericMatchAll) {
                    return new MatchAllConstraint;
                }
                // otherwise constraint should contain a != operator and already cover this
            }
        } else {
            foreach ($intervals['branches']['names'] as $branchName) {
                if ($intervals['branches']['exclude']) {
                    $devConstraints[] = new Constraint('!=', $branchName);
                } else {
                    $devConstraints[] = new Constraint('==', $branchName);
                }
            }

            // excluded branches, e.g. != dev-foo are conjunctive with the interval, so
            // > 2.0 != dev-foo must return a conjunctive constraint
            if ($intervals['branches']['exclude']) {
                if (\count($constraints) > 1) {
                    return new MultiConstraint(array_merge(
                        array(new MultiConstraint($constraints, false)),
                        $devConstraints
                    ), true);
                }

                if (\count($constraints) === 1 && (string)$constraints[0] === (string)Interval::fromZero()) {
                    if (\count($devConstraints) > 1) {
                        return new MultiConstraint($devConstraints, true);
                    }
                    return $devConstraints[0];
                }

                return new MultiConstraint(array_merge($constraints, $devConstraints), true);
            }

            // otherwise devConstraints contains a list of == operators for branches which are disjunctive with the
            // rest of the constraint
            $constraints = array_merge($constraints, $devConstraints);
        }

        if (\count($constraints) > 1) {
            return new MultiConstraint($constraints, false);
        }

        if (\count($constraints) === 1) {
            return $constraints[0];
        }

        return new MatchNoneConstraint;
    }

    /**
     * Creates an array of numeric intervals and branch constraints representing a given constraint
     *
     * if the returned numeric array is empty it means the constraint matches nothing in the numeric range (0 - +inf)
     * if the returned branches array is empty it means no dev-* versions are matched
     * if a constraint matches all possible dev-* versions, branches will contain Interval::anyDev()
     *
     * @return array
     * @phpstan-return array{'numeric': Interval[], 'branches': array{'names': string[], 'exclude': bool}}
     */
    public static function get(ConstraintInterface $constraint)
    {
        $key = (string) $constraint;

        if (!isset(self::$intervalsCache[$key])) {
            self::$intervalsCache[$key] = self::generateIntervals($constraint);
        }

        return self::$intervalsCache[$key];
    }

    /**
     * @param bool $stopOnFirstValidInterval
     *
     * @phpstan-return array{'numeric': Interval[], 'branches': array{'names': string[], 'exclude': bool}}
     */
    private static function generateIntervals(ConstraintInterface $constraint, $stopOnFirstValidInterval = false)
    {
        if ($constraint instanceof MatchAllConstraint) {
            return array('numeric' => array(new Interval(Interval::fromZero(), Interval::untilPositiveInfinity())), 'branches' => Interval::anyDev());
        }

        if ($constraint instanceof MatchNoneConstraint) {
            return array('numeric' => array(), 'branches' => array('names' => array(), 'exclude' => false));
        }

        if ($constraint instanceof Constraint) {
            return self::generateSingleConstraintIntervals($constraint);
        }

        if (!$constraint instanceof MultiConstraint) {
            throw new \UnexpectedValueException('The constraint passed in should be an MatchAllConstraint, Constraint or MultiConstraint instance, got '.\get_class($constraint).'.');
        }

        $constraints = $constraint->getConstraints();

        $numericGroups = array();
        $constraintBranches = array();
        foreach ($constraints as $c) {
            $res = self::get($c);
            $numericGroups[] = $res['numeric'];
            $constraintBranches[] = $res['branches'];
        }

        if ($constraint->isDisjunctive()) {
            $branches = Interval::noDev();
            foreach ($constraintBranches as $b) {
                if ($b['exclude']) {
                    if ($branches['exclude']) {
                        // disjunctive constraint, so only exclude what's excluded in all constraints
                        // !=a,!=b || !=b,!=c => !=b
                        $branches['names'] = array_intersect($branches['names'], $b['names']);
                    } else {
                        // disjunctive constraint so exclude all names which are not explicitly included in the alternative
                        // (==b || ==c) || !=a,!=b => !=a
                        $branches['exclude'] = true;
                        $branches['names'] = array_diff($b['names'], $branches['names']);
                    }
                } else {
                    if ($branches['exclude']) {
                        // disjunctive constraint so exclude all names which are not explicitly included in the alternative
                        // !=a,!=b || (==b || ==c) => !=a
                        $branches['names'] = array_diff($branches['names'], $b['names']);
                    } else {
                        // disjunctive constraint, so just add all the other branches
                        // (==a || ==b) || ==c => ==a || ==b || ==c
                        $branches['names'] = array_merge($branches['names'], $b['names']);
                    }
                }
            }
        } else {
            $branches = Interval::anyDev();
            foreach ($constraintBranches as $b) {
                if ($b['exclude']) {
                    if ($branches['exclude']) {
                        // conjunctive, so just add all branch names to be excluded
                        // !=a && !=b => !=a,!=b
                        $branches['names'] = array_merge($branches['names'], $b['names']);
                    } else {
                        // conjunctive, so only keep included names which are not excluded
                        // (==a||==c) && !=a,!=b => ==c
                        $branches['names'] = array_diff($branches['names'], $b['names']);
                    }
                } else {
                    if ($branches['exclude']) {
                        // conjunctive, so only keep included names which are not excluded
                        // !=a,!=b && (==a||==c) => ==c
                        $branches['names'] = array_diff($b['names'], $branches['names']);
                        $branches['exclude'] = false;
                    } else {
                        // conjunctive, so only keep names that are included in both
                        // (==a||==b) && (==a||==c) => ==a
                        $branches['names'] = array_intersect($branches['names'], $b['names']);
                    }
                }
            }
        }

        $branches['names'] = array_unique($branches['names']);

        if (\count($numericGroups) === 1) {
            return array('numeric' => $numericGroups[0], 'branches' => $branches);
        }

        $borders = array();
        foreach ($numericGroups as $group) {
            foreach ($group as $interval) {
                $borders[] = array('version' => $interval->getStart()->getVersion(), 'operator' => $interval->getStart()->getOperator(), 'side' => 'start');
                $borders[] = array('version' => $interval->getEnd()->getVersion(), 'operator' => $interval->getEnd()->getOperator(), 'side' => 'end');
            }
        }

        $opSortOrder = self::$opSortOrder;
        usort($borders, function ($a, $b) use ($opSortOrder) {
            $order = version_compare($a['version'], $b['version']);
            if ($order === 0) {
                return $opSortOrder[$a['operator']] - $opSortOrder[$b['operator']];
            }

            return $order;
        });

        $activeIntervals = 0;
        $intervals = array();
        $index = 0;
        $activationThreshold = $constraint->isConjunctive() ? \count($numericGroups) : 1;
        $start = null;
        foreach ($borders as $border) {
            if ($border['side'] === 'start') {
                $activeIntervals++;
            } else {
                $activeIntervals--;
            }
            if (!$start && $activeIntervals >= $activationThreshold) {
                $start = new Constraint($border['operator'], $border['version']);
            } elseif ($start && $activeIntervals < $activationThreshold) {
                // filter out invalid intervals like > x - <= x, or >= x - < x
                if (
                    version_compare($start->getVersion(), $border['version'], '=')
                    && (
                        ($start->getOperator() === '>' && $border['operator'] === '<=')
                        || ($start->getOperator() === '>=' && $border['operator'] === '<')
                    )
                ) {
                    unset($intervals[$index]);
                } else {
                    $intervals[$index] = new Interval($start, new Constraint($border['operator'], $border['version']));
                    $index++;

                    if ($stopOnFirstValidInterval) {
                        break;
                    }
                }

                $start = null;
            }
        }

        return array('numeric' => $intervals, 'branches' => $branches);
    }

    /**
     * @phpstan-return array{'numeric': Interval[], 'branches': array{'names': string[], 'exclude': bool}}
     */
    private static function generateSingleConstraintIntervals(Constraint $constraint)
    {
        $op = $constraint->getOperator();

        // handle branch constraints first
        if (strpos($constraint->getVersion(), 'dev-') === 0) {
            $intervals = array();
            $branches = array('names' => array(), 'exclude' => false);

            // != dev-foo means any numeric version may match, we treat >/< like != they are not really defined for branches
            if ($op === '!=') {
                $intervals[] = new Interval(Interval::fromZero(), Interval::untilPositiveInfinity());
                $branches = array('names' => array($constraint->getVersion()), 'exclude' => true);
            } elseif ($op === '==') {
                $branches['names'][] = $constraint->getVersion();
            }

            return array(
                'numeric' => $intervals,
                'branches' => $branches,
            );
        }

        if ($op[0] === '>') { // > & >=
            return array('numeric' => array(new Interval($constraint, Interval::untilPositiveInfinity())), 'branches' => Interval::noDev());
        }
        if ($op[0] === '<') { // < & <=
            return array('numeric' => array(new Interval(Interval::fromZero(), $constraint)), 'branches' => Interval::noDev());
        }
        if ($op === '!=') {
            // convert !=x to intervals of 0 - <x && >x - +inf + dev*
            return array('numeric' => array(
                new Interval(Interval::fromZero(), new Constraint('<', $constraint->getVersion())),
                new Interval(new Constraint('>', $constraint->getVersion()), Interval::untilPositiveInfinity()),
            ), 'branches' => Interval::anyDev());
        }

        // convert ==x to an interval of >=x - <=x
        return array('numeric' => array(
            new Interval(new Constraint('>=', $constraint->getVersion()), new Constraint('<=', $constraint->getVersion())),
        ), 'branches' => Interval::noDev());
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver\Constraint;

/**
 * DO NOT IMPLEMENT this interface. It is only meant for usage as a type hint
 * in libraries relying on composer/semver but creating your own constraint class
 * that implements this interface is not a supported use case and will cause the
 * composer/semver components to return unexpected results.
 */
interface ConstraintInterface
{
    /**
     * Checks whether the given constraint intersects in any way with this constraint
     *
     * @param ConstraintInterface $provider
     *
     * @return bool
     */
    public function matches(ConstraintInterface $provider);

    /**
     * Provides a compiled version of the constraint for the given operator
     * The compiled version must be a PHP expression.
     * Executor of compile version must provide 2 variables:
     * - $v = the string version to compare with
     * - $b = whether or not the version is a non-comparable branch (starts with "dev-")
     *
     * @see Constraint::OP_* for the list of available operators.
     * @example return '!$b && version_compare($v, '1.0', '>')';
     *
     * @param int $otherOperator one Constraint::OP_*
     *
     * @return string
     *
     * @phpstan-param Constraint::OP_* $otherOperator
     */
    public function compile($otherOperator);

    /**
     * @return Bound
     */
    public function getUpperBound();

    /**
     * @return Bound
     */
    public function getLowerBound();

    /**
     * @return string
     */
    public function getPrettyString();

    /**
     * @param string|null $prettyString
     *
     * @return void
     */
    public function setPrettyString($prettyString);

    /**
     * @return string
     */
    public function __toString();
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver\Constraint;

/**
 * Defines a constraint.
 */
class Constraint implements ConstraintInterface
{
    /* operator integer values */
    const OP_EQ = 0;
    const OP_LT = 1;
    const OP_LE = 2;
    const OP_GT = 3;
    const OP_GE = 4;
    const OP_NE = 5;

    /* operator string values */
    const STR_OP_EQ = '==';
    const STR_OP_EQ_ALT = '=';
    const STR_OP_LT = '<';
    const STR_OP_LE = '<=';
    const STR_OP_GT = '>';
    const STR_OP_GE = '>=';
    const STR_OP_NE = '!=';
    const STR_OP_NE_ALT = '<>';

    /**
     * Operator to integer translation table.
     *
     * @var array
     * @phpstan-var array<self::STR_OP_*, self::OP_*>
     */
    private static $transOpStr = array(
        '=' => self::OP_EQ,
        '==' => self::OP_EQ,
        '<' => self::OP_LT,
        '<=' => self::OP_LE,
        '>' => self::OP_GT,
        '>=' => self::OP_GE,
        '<>' => self::OP_NE,
        '!=' => self::OP_NE,
    );

    /**
     * Integer to operator translation table.
     *
     * @var array
     * @phpstan-var array<self::OP_*, self::STR_OP_*>
     */
    private static $transOpInt = array(
        self::OP_EQ => '==',
        self::OP_LT => '<',
        self::OP_LE => '<=',
        self::OP_GT => '>',
        self::OP_GE => '>=',
        self::OP_NE => '!=',
    );

    /**
     * @var int
     * @phpstan-var self::OP_*
     */
    protected $operator;

    /** @var string */
    protected $version;

    /** @var string|null */
    protected $prettyString;

    /** @var Bound */
    protected $lowerBound;

    /** @var Bound */
    protected $upperBound;

    /**
     * Sets operator and version to compare with.
     *
     * @param string $operator
     * @param string $version
     *
     * @throws \InvalidArgumentException if invalid operator is given.
     *
     * @phpstan-param self::STR_OP_* $operator
     */
    public function __construct($operator, $version)
    {
        if (!isset(self::$transOpStr[$operator])) {
            throw new \InvalidArgumentException(sprintf(
                'Invalid operator "%s" given, expected one of: %s',
                $operator,
                implode(', ', self::getSupportedOperators())
            ));
        }

        $this->operator = self::$transOpStr[$operator];
        $this->version = $version;
    }

    /**
     * @return string
     */
    public function getVersion()
    {
        return $this->version;
    }

    /**
     * @return string
     *
     * @phpstan-return self::STR_OP_*
     */
    public function getOperator()
    {
        return self::$transOpInt[$this->operator];
    }

    /**
     * @param ConstraintInterface $provider
     *
     * @return bool
     */
    public function matches(ConstraintInterface $provider)
    {
        if ($provider instanceof self) {
            return $this->matchSpecific($provider);
        }

        // turn matching around to find a match
        return $provider->matches($this);
    }

    /**
     * {@inheritDoc}
     */
    public function setPrettyString($prettyString)
    {
        $this->prettyString = $prettyString;
    }

    /**
     * {@inheritDoc}
     */
    public function getPrettyString()
    {
        if ($this->prettyString) {
            return $this->prettyString;
        }

        return $this->__toString();
    }

    /**
     * Get all supported comparison operators.
     *
     * @return array
     *
     * @phpstan-return list<self::STR_OP_*>
     */
    public static function getSupportedOperators()
    {
        return array_keys(self::$transOpStr);
    }

    /**
     * @param  string $operator
     * @return int
     *
     * @phpstan-param  self::STR_OP_* $operator
     * @phpstan-return self::OP_*
     */
    public static function getOperatorConstant($operator)
    {
        return self::$transOpStr[$operator];
    }

    /**
     * @param string $a
     * @param string $b
     * @param string $operator
     * @param bool   $compareBranches
     *
     * @throws \InvalidArgumentException if invalid operator is given.
     *
     * @return bool
     *
     * @phpstan-param self::STR_OP_* $operator
     */
    public function versionCompare($a, $b, $operator, $compareBranches = false)
    {
        if (!isset(self::$transOpStr[$operator])) {
            throw new \InvalidArgumentException(sprintf(
                'Invalid operator "%s" given, expected one of: %s',
                $operator,
                implode(', ', self::getSupportedOperators())
            ));
        }

        $aIsBranch = strpos($a, 'dev-') === 0;
        $bIsBranch = strpos($b, 'dev-') === 0;

        if ($operator === '!=' && ($aIsBranch || $bIsBranch)) {
            return $a !== $b;
        }

        if ($aIsBranch && $bIsBranch) {
            return $operator === '==' && $a === $b;
        }

        // when branches are not comparable, we make sure dev branches never match anything
        if (!$compareBranches && ($aIsBranch || $bIsBranch)) {
            return false;
        }

        return \version_compare($a, $b, $operator);
    }

    /**
     * {@inheritDoc}
     */
    public function compile($otherOperator)
    {
        if (strpos($this->version, 'dev-') === 0) {
            if (self::OP_EQ === $this->operator) {
                if (self::OP_EQ === $otherOperator) {
                    return sprintf('$b && $v === %s', \var_export($this->version, true));
                }
                if (self::OP_NE === $otherOperator) {
                    return sprintf('!$b || $v !== %s', \var_export($this->version, true));
                }
                return 'false';
            }

            if (self::OP_NE === $this->operator) {
                if (self::OP_EQ === $otherOperator) {
                    return sprintf('!$b || $v !== %s', \var_export($this->version, true));
                }
                if (self::OP_NE === $otherOperator) {
                    return 'true';
                }
                return '!$b';
            }

            return 'false';
        }

        if (self::OP_EQ === $this->operator) {
            if (self::OP_EQ === $otherOperator) {
                return sprintf('\version_compare($v, %s, \'==\')', \var_export($this->version, true));
            }
            if (self::OP_NE === $otherOperator) {
                return sprintf('$b || \version_compare($v, %s, \'!=\')', \var_export($this->version, true));
            }

            return sprintf('!$b && \version_compare(%s, $v, \'%s\')', \var_export($this->version, true), self::$transOpInt[$otherOperator]);
        }

        if (self::OP_NE === $this->operator) {
            if (self::OP_EQ === $otherOperator) {
                return sprintf('$b || (!$b && \version_compare($v, %s, \'!=\'))', \var_export($this->version, true));
            }

            if (self::OP_NE === $otherOperator) {
                return 'true';
            }
            return '!$b';
        }

        if (self::OP_LT === $this->operator || self::OP_LE === $this->operator) {
            if (self::OP_LT === $otherOperator || self::OP_LE === $otherOperator) {
                return '!$b';
            }
        } else { // $this->operator must be self::OP_GT || self::OP_GE here
            if (self::OP_GT === $otherOperator || self::OP_GE === $otherOperator) {
                return '!$b';
            }
        }

        if (self::OP_NE === $otherOperator) {
            return 'true';
        }

        $codeComparison = sprintf('\version_compare($v, %s, \'%s\')', \var_export($this->version, true), self::$transOpInt[$this->operator]);
        if ($this->operator === self::OP_LE) {
            if ($otherOperator === self::OP_GT) {
                return sprintf('!$b && \version_compare($v, %s, \'!=\') && ', \var_export($this->version, true)) . $codeComparison;
            }
        } elseif ($this->operator === self::OP_GE) {
            if ($otherOperator === self::OP_LT) {
                return sprintf('!$b && \version_compare($v, %s, \'!=\') && ', \var_export($this->version, true)) . $codeComparison;
            }
        }

        return sprintf('!$b && %s', $codeComparison);
    }

    /**
     * @param Constraint $provider
     * @param bool       $compareBranches
     *
     * @return bool
     */
    public function matchSpecific(Constraint $provider, $compareBranches = false)
    {
        $noEqualOp = str_replace('=', '', self::$transOpInt[$this->operator]);
        $providerNoEqualOp = str_replace('=', '', self::$transOpInt[$provider->operator]);

        $isEqualOp = self::OP_EQ === $this->operator;
        $isNonEqualOp = self::OP_NE === $this->operator;
        $isProviderEqualOp = self::OP_EQ === $provider->operator;
        $isProviderNonEqualOp = self::OP_NE === $provider->operator;

        // '!=' operator is match when other operator is not '==' operator or version is not match
        // these kinds of comparisons always have a solution
        if ($isNonEqualOp || $isProviderNonEqualOp) {
            if ($isNonEqualOp && !$isProviderNonEqualOp && !$isProviderEqualOp && strpos($provider->version, 'dev-') === 0) {
                return false;
            }

            if ($isProviderNonEqualOp && !$isNonEqualOp && !$isEqualOp && strpos($this->version, 'dev-') === 0) {
                return false;
            }

            if (!$isEqualOp && !$isProviderEqualOp) {
                return true;
            }
            return $this->versionCompare($provider->version, $this->version, '!=', $compareBranches);
        }

        // an example for the condition is <= 2.0 & < 1.0
        // these kinds of comparisons always have a solution
        if ($this->operator !== self::OP_EQ && $noEqualOp === $providerNoEqualOp) {
            return !(strpos($this->version, 'dev-') === 0 || strpos($provider->version, 'dev-') === 0);
        }

        $version1 = $isEqualOp ? $this->version : $provider->version;
        $version2 = $isEqualOp ? $provider->version : $this->version;
        $operator = $isEqualOp ? $provider->operator : $this->operator;

        if ($this->versionCompare($version1, $version2, self::$transOpInt[$operator], $compareBranches)) {
            // special case, e.g. require >= 1.0 and provide < 1.0
            // 1.0 >= 1.0 but 1.0 is outside of the provided interval

            return !(self::$transOpInt[$provider->operator] === $providerNoEqualOp
                && self::$transOpInt[$this->operator] !== $noEqualOp
                && \version_compare($provider->version, $this->version, '=='));
        }

        return false;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return self::$transOpInt[$this->operator] . ' ' . $this->version;
    }

    /**
     * {@inheritDoc}
     */
    public function getLowerBound()
    {
        $this->extractBounds();

        return $this->lowerBound;
    }

    /**
     * {@inheritDoc}
     */
    public function getUpperBound()
    {
        $this->extractBounds();

        return $this->upperBound;
    }

    /**
     * @return void
     */
    private function extractBounds()
    {
        if (null !== $this->lowerBound) {
            return;
        }

        // Branches
        if (strpos($this->version, 'dev-') === 0) {
            $this->lowerBound = Bound::zero();
            $this->upperBound = Bound::positiveInfinity();

            return;
        }

        switch ($this->operator) {
            case self::OP_EQ:
                $this->lowerBound = new Bound($this->version, true);
                $this->upperBound = new Bound($this->version, true);
                break;
            case self::OP_LT:
                $this->lowerBound = Bound::zero();
                $this->upperBound = new Bound($this->version, false);
                break;
            case self::OP_LE:
                $this->lowerBound = Bound::zero();
                $this->upperBound = new Bound($this->version, true);
                break;
            case self::OP_GT:
                $this->lowerBound = new Bound($this->version, false);
                $this->upperBound = Bound::positiveInfinity();
                break;
            case self::OP_GE:
                $this->lowerBound = new Bound($this->version, true);
                $this->upperBound = Bound::positiveInfinity();
                break;
            case self::OP_NE:
                $this->lowerBound = Bound::zero();
                $this->upperBound = Bound::positiveInfinity();
                break;
        }
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver\Constraint;

class Bound
{
    /**
     * @var string
     */
    private $version;

    /**
     * @var bool
     */
    private $isInclusive;

    /**
     * @param string $version
     * @param bool   $isInclusive
     */
    public function __construct($version, $isInclusive)
    {
        $this->version = $version;
        $this->isInclusive = $isInclusive;
    }

    /**
     * @return string
     */
    public function getVersion()
    {
        return $this->version;
    }

    /**
     * @return bool
     */
    public function isInclusive()
    {
        return $this->isInclusive;
    }

    /**
     * @return bool
     */
    public function isZero()
    {
        return $this->getVersion() === '0.0.0.0-dev' && $this->isInclusive();
    }

    /**
     * @return bool
     */
    public function isPositiveInfinity()
    {
        return $this->getVersion() === PHP_INT_MAX.'.0.0.0' && !$this->isInclusive();
    }

    /**
     * Compares a bound to another with a given operator.
     *
     * @param Bound  $other
     * @param string $operator
     *
     * @return bool
     */
    public function compareTo(Bound $other, $operator)
    {
        if (!\in_array($operator, array('<', '>'), true)) {
            throw new \InvalidArgumentException('Does not support any other operator other than > or <.');
        }

        // If they are the same it doesn't matter
        if ($this == $other) {
            return false;
        }

        $compareResult = version_compare($this->getVersion(), $other->getVersion());

        // Not the same version means we don't need to check if the bounds are inclusive or not
        if (0 !== $compareResult) {
            return (('>' === $operator) ? 1 : -1) === $compareResult;
        }

        // Question we're answering here is "am I higher than $other?"
        return '>' === $operator ? $other->isInclusive() : !$other->isInclusive();
    }

    public function __toString()
    {
        return sprintf(
            '%s [%s]',
            $this->getVersion(),
            $this->isInclusive() ? 'inclusive' : 'exclusive'
        );
    }

    /**
     * @return self
     */
    public static function zero()
    {
        return new Bound('0.0.0.0-dev', true);
    }

    /**
     * @return self
     */
    public static function positiveInfinity()
    {
        return new Bound(PHP_INT_MAX.'.0.0.0', false);
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver\Constraint;

/**
 * Defines a conjunctive or disjunctive set of constraints.
 */
class MultiConstraint implements ConstraintInterface
{
    /**
     * @var ConstraintInterface[]
     * @phpstan-var non-empty-array<ConstraintInterface>
     */
    protected $constraints;

    /** @var string|null */
    protected $prettyString;

    /** @var string|null */
    protected $string;

    /** @var bool */
    protected $conjunctive;

    /** @var Bound|null */
    protected $lowerBound;

    /** @var Bound|null */
    protected $upperBound;

    /**
     * @param ConstraintInterface[] $constraints A set of constraints
     * @param bool                  $conjunctive Whether the constraints should be treated as conjunctive or disjunctive
     *
     * @throws \InvalidArgumentException If less than 2 constraints are passed
     */
    public function __construct(array $constraints, $conjunctive = true)
    {
        if (\count($constraints) < 2) {
            throw new \InvalidArgumentException(
                'Must provide at least two constraints for a MultiConstraint. Use '.
                'the regular Constraint class for one constraint only or MatchAllConstraint for none. You may use '.
                'MultiConstraint::create() which optimizes and handles those cases automatically.'
            );
        }

        $this->constraints = $constraints;
        $this->conjunctive = $conjunctive;
    }

    /**
     * @return ConstraintInterface[]
     */
    public function getConstraints()
    {
        return $this->constraints;
    }

    /**
     * @return bool
     */
    public function isConjunctive()
    {
        return $this->conjunctive;
    }

    /**
     * @return bool
     */
    public function isDisjunctive()
    {
        return !$this->conjunctive;
    }

    /**
     * {@inheritDoc}
     */
    public function compile($otherOperator)
    {
        $parts = array();
        foreach ($this->constraints as $constraint) {
            $code = $constraint->compile($otherOperator);
            if ($code === 'true') {
                if (!$this->conjunctive) {
                    return 'true';
                }
            } elseif ($code === 'false') {
                if ($this->conjunctive) {
                    return 'false';
                }
            } else {
                $parts[] = '('.$code.')';
            }
        }

        if (!$parts) {
            return $this->conjunctive ? 'true' : 'false';
        }

        return $this->conjunctive ? implode('&&', $parts) : implode('||', $parts);
    }

    /**
     * @param ConstraintInterface $provider
     *
     * @return bool
     */
    public function matches(ConstraintInterface $provider)
    {
        if (false === $this->conjunctive) {
            foreach ($this->constraints as $constraint) {
                if ($provider->matches($constraint)) {
                    return true;
                }
            }

            return false;
        }

        // when matching a conjunctive and a disjunctive multi constraint we have to iterate over the disjunctive one
        // otherwise we'd return true if different parts of the disjunctive constraint match the conjunctive one
        // which would lead to incorrect results, e.g. [>1 and <2] would match [<1 or >2] although they do not intersect
        if ($provider instanceof MultiConstraint && $provider->isDisjunctive()) {
            return $provider->matches($this);
        }

        foreach ($this->constraints as $constraint) {
            if (!$provider->matches($constraint)) {
                return false;
            }
        }

        return true;
    }

    /**
     * {@inheritDoc}
     */
    public function setPrettyString($prettyString)
    {
        $this->prettyString = $prettyString;
    }

    /**
     * {@inheritDoc}
     */
    public function getPrettyString()
    {
        if ($this->prettyString) {
            return $this->prettyString;
        }

        return (string) $this;
    }

    /**
     * {@inheritDoc}
     */
    public function __toString()
    {
        if ($this->string !== null) {
            return $this->string;
        }

        $constraints = array();
        foreach ($this->constraints as $constraint) {
            $constraints[] = (string) $constraint;
        }

        return $this->string = '[' . implode($this->conjunctive ? ' ' : ' || ', $constraints) . ']';
    }

    /**
     * {@inheritDoc}
     */
    public function getLowerBound()
    {
        $this->extractBounds();

        if (null === $this->lowerBound) {
            throw new \LogicException('extractBounds should have populated the lowerBound property');
        }

        return $this->lowerBound;
    }

    /**
     * {@inheritDoc}
     */
    public function getUpperBound()
    {
        $this->extractBounds();

        if (null === $this->upperBound) {
            throw new \LogicException('extractBounds should have populated the upperBound property');
        }

        return $this->upperBound;
    }

    /**
     * Tries to optimize the constraints as much as possible, meaning
     * reducing/collapsing congruent constraints etc.
     * Does not necessarily return a MultiConstraint instance if
     * things can be reduced to a simple constraint
     *
     * @param ConstraintInterface[] $constraints A set of constraints
     * @param bool                  $conjunctive Whether the constraints should be treated as conjunctive or disjunctive
     *
     * @return ConstraintInterface
     */
    public static function create(array $constraints, $conjunctive = true)
    {
        if (0 === \count($constraints)) {
            return new MatchAllConstraint();
        }

        if (1 === \count($constraints)) {
            return $constraints[0];
        }

        $optimized = self::optimizeConstraints($constraints, $conjunctive);
        if ($optimized !== null) {
            list($constraints, $conjunctive) = $optimized;
            if (\count($constraints) === 1) {
                return $constraints[0];
            }
        }

        return new self($constraints, $conjunctive);
    }

    /**
     * @param  ConstraintInterface[] $constraints
     * @param  bool                  $conjunctive
     * @return ?array
     *
     * @phpstan-return array{0: list<ConstraintInterface>, 1: bool}|null
     */
    private static function optimizeConstraints(array $constraints, $conjunctive)
    {
        // parse the two OR groups and if they are contiguous we collapse
        // them into one constraint
        // [>= 1 < 2] || [>= 2 < 3] || [>= 3 < 4] => [>= 1 < 4]
        if (!$conjunctive) {
            $left = $constraints[0];
            $mergedConstraints = array();
            $optimized = false;
            for ($i = 1, $l = \count($constraints); $i < $l; $i++) {
                $right = $constraints[$i];
                if (
                    $left instanceof self
                    && $left->conjunctive
                    && $right instanceof self
                    && $right->conjunctive
                    && \count($left->constraints) === 2
                    && \count($right->constraints) === 2
                    && ($left0 = (string) $left->constraints[0])
                    && $left0[0] === '>' && $left0[1] === '='
                    && ($left1 = (string) $left->constraints[1])
                    && $left1[0] === '<'
                    && ($right0 = (string) $right->constraints[0])
                    && $right0[0] === '>' && $right0[1] === '='
                    && ($right1 = (string) $right->constraints[1])
                    && $right1[0] === '<'
                    && substr($left1, 2) === substr($right0, 3)
                ) {
                    $optimized = true;
                    $left = new MultiConstraint(
                        array(
                            $left->constraints[0],
                            $right->constraints[1],
                        ),
                        true);
                } else {
                    $mergedConstraints[] = $left;
                    $left = $right;
                }
            }
            if ($optimized) {
                $mergedConstraints[] = $left;
                return array($mergedConstraints, false);
            }
        }

        // TODO: Here's the place to put more optimizations

        return null;
    }

    /**
     * @return void
     */
    private function extractBounds()
    {
        if (null !== $this->lowerBound) {
            return;
        }

        foreach ($this->constraints as $constraint) {
            if (null === $this->lowerBound || null === $this->upperBound) {
                $this->lowerBound = $constraint->getLowerBound();
                $this->upperBound = $constraint->getUpperBound();
                continue;
            }

            if ($constraint->getLowerBound()->compareTo($this->lowerBound, $this->isConjunctive() ? '>' : '<')) {
                $this->lowerBound = $constraint->getLowerBound();
            }

            if ($constraint->getUpperBound()->compareTo($this->upperBound, $this->isConjunctive() ? '<' : '>')) {
                $this->upperBound = $constraint->getUpperBound();
            }
        }
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver\Constraint;

/**
 * Blackhole of constraints, nothing escapes it
 */
class MatchNoneConstraint implements ConstraintInterface
{
    /** @var string|null */
    protected $prettyString;

    /**
     * @param ConstraintInterface $provider
     *
     * @return bool
     */
    public function matches(ConstraintInterface $provider)
    {
        return false;
    }

    /**
     * {@inheritDoc}
     */
    public function compile($otherOperator)
    {
        return 'false';
    }

    /**
     * {@inheritDoc}
     */
    public function setPrettyString($prettyString)
    {
        $this->prettyString = $prettyString;
    }

    /**
     * {@inheritDoc}
     */
    public function getPrettyString()
    {
        if ($this->prettyString) {
            return $this->prettyString;
        }

        return (string) $this;
    }

    /**
     * {@inheritDoc}
     */
    public function __toString()
    {
        return '[]';
    }

    /**
     * {@inheritDoc}
     */
    public function getUpperBound()
    {
        return new Bound('0.0.0.0-dev', false);
    }

    /**
     * {@inheritDoc}
     */
    public function getLowerBound()
    {
        return new Bound('0.0.0.0-dev', false);
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver\Constraint;

/**
 * Defines the absence of a constraint.
 *
 * This constraint matches everything.
 */
class MatchAllConstraint implements ConstraintInterface
{
    /** @var string|null */
    protected $prettyString;

    /**
     * @param ConstraintInterface $provider
     *
     * @return bool
     */
    public function matches(ConstraintInterface $provider)
    {
        return true;
    }

    /**
     * {@inheritDoc}
     */
    public function compile($otherOperator)
    {
        return 'true';
    }

    /**
     * {@inheritDoc}
     */
    public function setPrettyString($prettyString)
    {
        $this->prettyString = $prettyString;
    }

    /**
     * {@inheritDoc}
     */
    public function getPrettyString()
    {
        if ($this->prettyString) {
            return $this->prettyString;
        }

        return (string) $this;
    }

    /**
     * {@inheritDoc}
     */
    public function __toString()
    {
        return '*';
    }

    /**
     * {@inheritDoc}
     */
    public function getUpperBound()
    {
        return Bound::positiveInfinity();
    }

    /**
     * {@inheritDoc}
     */
    public function getLowerBound()
    {
        return Bound::zero();
    }
}
Copyright (C) 2022 Composer

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.
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\ClassMapGenerator;

/**
 * Contains a list of files which were scanned to generate a classmap
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class FileList
{
    /**
     * @var array<non-empty-string, true>
     */
    public $files = [];

    /**
     * @param non-empty-string $path
     */
    public function add(string $path): void
    {
        $this->files[$path] = true;
    }

    /**
     * @param non-empty-string $path
     */
    public function contains(string $path): bool
    {
        return isset($this->files[$path]);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/*
 * This file was initially based on a version from the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 */

namespace Composer\ClassMapGenerator;

use Composer\Pcre\Preg;
use Symfony\Component\Finder\Finder;
use Composer\IO\IOInterface;

/**
 * ClassMapGenerator
 *
 * @author Gyula Sallai <salla016@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ClassMapGenerator
{
    /**
     * @var list<string>
     */
    private $extensions;

    /**
     * @var FileList|null
     */
    private $scannedFiles = null;

    /**
     * @var ClassMap
     */
    private $classMap;

    /**
     * @var non-empty-string
     */
    private $streamWrappersRegex;

    /**
     * @param list<string> $extensions File extensions to scan for classes in the given paths
     */
    public function __construct(array $extensions = ['php', 'inc'])
    {
        $this->extensions = $extensions;
        $this->classMap = new ClassMap;
        $this->streamWrappersRegex = sprintf('{^(?:%s)://}', implode('|', array_map('preg_quote', stream_get_wrappers())));
    }

    /**
     * When calling scanPaths repeatedly with paths that may overlap, calling this will ensure that the same class is never scanned twice
     *
     * You can provide your own FileList instance or use the default one if you pass no argument
     *
     * @return $this
     */
    public function avoidDuplicateScans(?FileList $scannedFiles = null): self
    {
        $this->scannedFiles = $scannedFiles ?? new FileList;

        return $this;
    }

    /**
     * Iterate over all files in the given directory searching for classes
     *
     * @param string|\Traversable<\SplFileInfo>|array<\SplFileInfo> $path The path to search in or an array/traversable of SplFileInfo (e.g. symfony/finder instance)
     * @return array<class-string, non-empty-string> A class map array
     *
     * @throws \RuntimeException When the path is neither an existing file nor directory
     */
    public static function createMap($path): array
    {
        $generator = new self();

        $generator->scanPaths($path);

        return $generator->getClassMap()->getMap();
    }

    public function getClassMap(): ClassMap
    {
        return $this->classMap;
    }

    /**
     * Iterate over all files in the given directory searching for classes
     *
     * @param string|\Traversable<\SplFileInfo>|array<\SplFileInfo> $path         The path to search in or an array/traversable of SplFileInfo (e.g. symfony/finder instance)
     * @param non-empty-string|null                                 $excluded     Regex that matches file paths to be excluded from the classmap
     * @param 'classmap'|'psr-0'|'psr-4'                            $autoloadType Optional autoload standard to use mapping rules with the namespace instead of purely doing a classmap
     * @param string|null                                           $namespace    Optional namespace prefix to filter by, only for psr-0/psr-4 autoloading
     * @param array<string>                                         $excludedDirs Optional dirs to exclude from search relative to $path
     *
     * @throws \RuntimeException When the path is neither an existing file nor directory
     */
    public function scanPaths($path, ?string $excluded = null, string $autoloadType = 'classmap', ?string $namespace = null, array $excludedDirs = []): void
    {
        if (!in_array($autoloadType, ['psr-0', 'psr-4', 'classmap'], true)) {
            throw new \InvalidArgumentException('$autoloadType must be one of: "psr-0", "psr-4" or "classmap"');
        }

        if ('classmap' !== $autoloadType) {
            if (!is_string($path)) {
                throw new \InvalidArgumentException('$path must be a string when specifying a psr-0 or psr-4 autoload type');
            }
            if (!is_string($namespace)) {
                throw new \InvalidArgumentException('$namespace must be given (even if it is an empty string if you do not want to filter) when specifying a psr-0 or psr-4 autoload type');
            }
            $basePath = $path;
        }

        if (is_string($path)) {
            if (is_file($path)) {
                $path = [new \SplFileInfo($path)];
            } elseif (is_dir($path) || strpos($path, '*') !== false) {
                $path = Finder::create()
                    ->files()
                    ->followLinks()
                    ->name('/\.(?:'.implode('|', array_map('preg_quote', $this->extensions)).')$/')
                    ->in($path)
                    ->exclude($excludedDirs);
            } else {
                throw new \RuntimeException(
                    'Could not scan for classes inside "'.$path.'" which does not appear to be a file nor a folder'
                );
            }
        }

        $cwd = realpath(self::getCwd());

        foreach ($path as $file) {
            $filePath = $file->getPathname();
            if (!in_array(pathinfo($filePath, PATHINFO_EXTENSION), $this->extensions, true)) {
                continue;
            }

            $isStreamWrapperPath = Preg::isMatch($this->streamWrappersRegex, $filePath);
            if (!self::isAbsolutePath($filePath) && !$isStreamWrapperPath) {
                $filePath = $cwd . '/' . $filePath;
                $filePath = self::normalizePath($filePath);
            } else {
                $filePath = Preg::replace('{(?<!:)[\\\\/]{2,}}', '/', $filePath);
            }

            if ('' === $filePath) {
                throw new \LogicException('Got an empty $filePath for '.$file->getPathname());
            }

            $realPath = $isStreamWrapperPath
                ? $filePath
                : realpath($filePath);

            // fallback just in case but this really should not happen
            if (false === $realPath) {
                throw new \RuntimeException('realpath of '.$filePath.' failed to resolve, got false');
            }

            // if a list of scanned files is given, avoid scanning twice the same file to save cycles and avoid generating warnings
            // in case a PSR-0/4 declaration follows another more specific one, or a classmap declaration, which covered this file already
            if ($this->scannedFiles !== null && $this->scannedFiles->contains($realPath)) {
                continue;
            }

            // check the realpath of the file against the excluded paths as the path might be a symlink and the excluded path is realpath'd so symlink are resolved
            if (null !== $excluded && Preg::isMatch($excluded, strtr($realPath, '\\', '/'))) {
                continue;
            }
            // check non-realpath of file for directories symlink in project dir
            if (null !== $excluded && Preg::isMatch($excluded, strtr($filePath, '\\', '/'))) {
                continue;
            }

            $classes = PhpFileParser::findClasses($filePath);
            if ('classmap' !== $autoloadType && isset($namespace)) {
                $classes = $this->filterByNamespace($classes, $filePath, $namespace, $autoloadType, $basePath);

                // if no valid class was found in the file then we do not mark it as scanned as it might still be matched by another rule later
                if (\count($classes) > 0 && $this->scannedFiles !== null) {
                    $this->scannedFiles->add($realPath);
                }
            } elseif ($this->scannedFiles !== null) {
                // classmap autoload rules always collect all classes so for these we definitely do not want to scan again
                $this->scannedFiles->add($realPath);
            }

            foreach ($classes as $class) {
                if (!$this->classMap->hasClass($class)) {
                    $this->classMap->addClass($class, $filePath);
                } elseif ($filePath !== $this->classMap->getClassPath($class)) {
                    $this->classMap->addAmbiguousClass($class, $filePath);
                }
            }
        }
    }

    /**
     * Remove classes which could not have been loaded by namespace autoloaders
     *
     * @param  array<int, class-string> $classes       found classes in given file
     * @param  string                   $filePath      current file
     * @param  string                   $baseNamespace prefix of given autoload mapping
     * @param  'psr-0'|'psr-4'          $namespaceType
     * @param  string                   $basePath      root directory of given autoload mapping
     * @return array<int, class-string> valid classes
     *
     * @throws \InvalidArgumentException When namespaceType is neither psr-0 nor psr-4
     */
    private function filterByNamespace(array $classes, string $filePath, string $baseNamespace, string $namespaceType, string $basePath): array
    {
        $validClasses = [];
        $rejectedClasses = [];

        $realSubPath = substr($filePath, strlen($basePath) + 1);
        $dotPosition = strrpos($realSubPath, '.');
        $realSubPath = substr($realSubPath, 0, $dotPosition === false ? PHP_INT_MAX : $dotPosition);

        foreach ($classes as $class) {
            // transform class name to file path and validate
            if ('psr-0' === $namespaceType) {
                $namespaceLength = strrpos($class, '\\');
                if (false !== $namespaceLength) {
                    $namespace = substr($class, 0, $namespaceLength + 1);
                    $className = substr($class, $namespaceLength + 1);
                    $subPath = str_replace('\\', DIRECTORY_SEPARATOR, $namespace)
                        . str_replace('_', DIRECTORY_SEPARATOR, $className);
                } else {
                    $subPath = str_replace('_', DIRECTORY_SEPARATOR, $class);
                }
            } elseif ('psr-4' === $namespaceType) {
                $subNamespace = ('' !== $baseNamespace) ? substr($class, strlen($baseNamespace)) : $class;
                $subPath = str_replace('\\', DIRECTORY_SEPARATOR, $subNamespace);
            } else {
                throw new \InvalidArgumentException('$namespaceType must be "psr-0" or "psr-4"');
            }
            if ($subPath === $realSubPath) {
                $validClasses[] = $class;
            } else {
                $rejectedClasses[] = $class;
            }
        }
        // warn only if no valid classes, else silently skip invalid
        if (\count($validClasses) === 0) {
            $cwd = realpath(self::getCwd());
            if ($cwd === false) {
                $cwd = self::getCwd();
            }
            $cwd = self::normalizePath($cwd);
            $shortPath = Preg::replace('{^'.preg_quote($cwd).'}', '.', self::normalizePath($filePath), 1);
            $shortBasePath = Preg::replace('{^'.preg_quote($cwd).'}', '.', self::normalizePath($basePath), 1);

            foreach ($rejectedClasses as $class) {
                $this->classMap->addPsrViolation("Class $class located in $shortPath does not comply with $namespaceType autoloading standard (rule: $baseNamespace => $shortBasePath). Skipping.", $class, $filePath);
            }

            return [];
        }

        return $validClasses;
    }

    /**
     * Checks if the given path is absolute
     *
     * @see Composer\Util\Filesystem::isAbsolutePath
     *
     * @param  string $path
     * @return bool
     */
    private static function isAbsolutePath(string $path): bool
    {
        return strpos($path, '/') === 0 || substr($path, 1, 1) === ':' || strpos($path, '\\\\') === 0;
    }

    /**
     * Normalize a path. This replaces backslashes with slashes, removes ending
     * slash and collapses redundant separators and up-level references.
     *
     * @see Composer\Util\Filesystem::normalizePath
     *
     * @param  string $path Path to the file or directory
     * @return string
     */
    private static function normalizePath(string $path): string
    {
        $parts = [];
        $path = strtr($path, '\\', '/');
        $prefix = '';
        $absolute = '';

        // extract windows UNC paths e.g. \\foo\bar
        if (strpos($path, '//') === 0 && \strlen($path) > 2) {
            $absolute = '//';
            $path = substr($path, 2);
        }

        // extract a prefix being a protocol://, protocol:, protocol://drive: or simply drive:
        if (Preg::isMatchStrictGroups('{^( [0-9a-z]{2,}+: (?: // (?: [a-z]: )? )? | [a-z]: )}ix', $path, $match)) {
            $prefix = $match[1];
            $path = substr($path, \strlen($prefix));
        }

        if (strpos($path, '/') === 0) {
            $absolute = '/';
            $path = substr($path, 1);
        }

        $up = false;
        foreach (explode('/', $path) as $chunk) {
            if ('..' === $chunk && (\strlen($absolute) > 0 || $up)) {
                array_pop($parts);
                $up = !(\count($parts) === 0 || '..' === end($parts));
            } elseif ('.' !== $chunk && '' !== $chunk) {
                $parts[] = $chunk;
                $up = '..' !== $chunk;
            }
        }

        // ensure c: is normalized to C:
        $prefix = Preg::replaceCallback('{(?:^|://)[a-z]:$}i', function (array $m) { return strtoupper((string) $m[0]); }, $prefix);

        return $prefix.$absolute.implode('/', $parts);
    }

    /**
     * @see Composer\Util\Platform::getCwd
     */
    private static function getCwd(): string
    {
        $cwd = getcwd();

        if (false === $cwd) {
            throw new \RuntimeException('Could not determine the current working directory');
        }

        return $cwd;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\ClassMapGenerator;

use Composer\Pcre\Preg;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @internal
 */
class PhpFileCleaner
{
    /** @var array<array{name: string, length: int, pattern: non-empty-string}> */
    private static $typeConfig;

    /** @var non-empty-string */
    private static $restPattern;

    /**
     * @readonly
     * @var string
     */
    private $contents;

    /**
     * @readonly
     * @var int
     */
    private $len;

    /**
     * @readonly
     * @var int
     */
    private $maxMatches;

    /** @var int */
    private $index = 0;

    /**
     * @param string[] $types
     */
    public static function setTypeConfig(array $types): void
    {
        foreach ($types as $type) {
            self::$typeConfig[$type[0]] = [
                'name' => $type,
                'length' => \strlen($type),
                'pattern' => '{.\b(?<![\$:>])'.$type.'\s++[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+}Ais',
            ];
        }

        self::$restPattern = '{[^?"\'</'.implode('', array_keys(self::$typeConfig)).']+}A';
    }

    public function __construct(string $contents, int $maxMatches)
    {
        $this->contents = $contents;
        $this->len = \strlen($this->contents);
        $this->maxMatches = $maxMatches;
    }

    public function clean(): string
    {
        $clean = '';

        while ($this->index < $this->len) {
            $this->skipToPhp();
            $clean .= '<?';

            while ($this->index < $this->len) {
                $char = $this->contents[$this->index];
                if ($char === '?' && $this->peek('>')) {
                    $clean .= '?>';
                    $this->index += 2;
                    continue 2;
                }

                if ($char === '"') {
                    $this->skipString('"');
                    $clean .= 'null';
                    continue;
                }

                if ($char === "'") {
                    $this->skipString("'");
                    $clean .= 'null';
                    continue;
                }

                if ($char === "<" && $this->peek('<') && $this->match('{<<<[ \t]*+([\'"]?)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*+)\\1(?:\r\n|\n|\r)}A', $match)) {
                    $this->index += \strlen($match[0]);
                    $this->skipHeredoc($match[2]);
                    $clean .= 'null';
                    continue;
                }

                if ($char === '/') {
                    if ($this->peek('/')) {
                        $this->skipToNewline();
                        continue;
                    }

                    if ($this->peek('*')) {
                        $this->skipComment();
                        continue;
                    }
                }

                if ($this->maxMatches === 1 && isset(self::$typeConfig[$char])) {
                    $type = self::$typeConfig[$char];
                    if (
                        \substr($this->contents, $this->index, $type['length']) === $type['name']
                        && Preg::isMatch($type['pattern'], $this->contents, $match, 0, $this->index - 1)
                    ) {
                        return $clean . $match[0];
                    }
                }

                $this->index += 1;
                if ($this->match(self::$restPattern, $match)) {
                    $clean .= $char . $match[0];
                    $this->index += \strlen($match[0]);
                } else {
                    $clean .= $char;
                }
            }
        }

        return $clean;
    }

    private function skipToPhp(): void
    {
        while ($this->index < $this->len) {
            if ($this->contents[$this->index] === '<' && $this->peek('?')) {
                $this->index += 2;
                break;
            }

            $this->index += 1;
        }
    }

    private function skipString(string $delimiter): void
    {
        $this->index += 1;
        while ($this->index < $this->len) {
            if ($this->contents[$this->index] === '\\' && ($this->peek('\\') || $this->peek($delimiter))) {
                $this->index += 2;
                continue;
            }

            if ($this->contents[$this->index] === $delimiter) {
                $this->index += 1;
                break;
            }

            $this->index += 1;
        }
    }

    private function skipComment(): void
    {
        $this->index += 2;
        while ($this->index < $this->len) {
            if ($this->contents[$this->index] === '*' && $this->peek('/')) {
                $this->index += 2;
                break;
            }

            $this->index += 1;
        }
    }

    private function skipToNewline(): void
    {
        while ($this->index < $this->len) {
            if ($this->contents[$this->index] === "\r" || $this->contents[$this->index] === "\n") {
                return;
            }

            $this->index += 1;
        }
    }

    private function skipHeredoc(string $delimiter): void
    {
        $firstDelimiterChar = $delimiter[0];
        $delimiterLength = \strlen($delimiter);
        $delimiterPattern = '{'.preg_quote($delimiter).'(?![a-zA-Z0-9_\x80-\xff])}A';

        while ($this->index < $this->len) {
            // check if we find the delimiter after some spaces/tabs
            switch ($this->contents[$this->index]) {
                case "\t":
                case " ":
                    $this->index += 1;
                    continue 2;
                case $firstDelimiterChar:
                    if (
                        \substr($this->contents, $this->index, $delimiterLength) === $delimiter
                        && $this->match($delimiterPattern)
                    ) {
                        $this->index += $delimiterLength;

                        return;
                    }

                    break;
            }

            // skip the rest of the line
            while ($this->index < $this->len) {
                $this->skipToNewline();

                // skip newlines
                while ($this->index < $this->len && ($this->contents[$this->index] === "\r" || $this->contents[$this->index] === "\n")) {
                    $this->index += 1;
                }

                break;
            }
        }
    }

    private function peek(string $char): bool
    {
        return $this->index + 1 < $this->len && $this->contents[$this->index + 1] === $char;
    }

    /**
     * @param non-empty-string $regex
     * @param null|array<mixed> $match
     * @param-out array<int|string, string> $match
     */
    private function match(string $regex, ?array &$match = null): bool
    {
        return Preg::isMatchStrictGroups($regex, $this->contents, $match, 0, $this->index);
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\ClassMapGenerator;

use Composer\Pcre\Preg;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ClassMap implements \Countable
{
    /**
     * @var array<class-string, non-empty-string>
     */
    public $map = [];

    /**
     * @var array<class-string, array<non-empty-string>>
     */
    private $ambiguousClasses = [];

    /**
     * @var array<string, array<array{warning: string, className: string}>>
     */
    private $psrViolations = [];

    /**
     * Returns the class map, which is a list of paths indexed by class name
     *
     * @return array<class-string, non-empty-string>
     */
    public function getMap(): array
    {
        return $this->map;
    }

    /**
     * Returns warning strings containing details about PSR-0/4 violations that were detected
     *
     * Violations are for ex a class which is in the wrong file/directory and thus should not be
     * found using psr-0/psr-4 autoloading but was found by the ClassMapGenerator as it scans all files.
     *
     * This is only happening when scanning paths using psr-0/psr-4 autoload type. Classmap type
     * always accepts every class as it finds it.
     *
     * @return string[]
     */
    public function getPsrViolations(): array
    {
        if (\count($this->psrViolations) === 0) {
            return [];
        }

        return array_map(static function (array $violation): string {
            return $violation['warning'];
        }, array_merge(...array_values($this->psrViolations)));
    }

    /**
     * A map of class names to their list of ambiguous paths
     *
     * This occurs when the same class can be found in several files
     *
     * To get the path the class is being mapped to, call getClassPath
     *
     * By default, paths that contain test(s), fixture(s), example(s) or stub(s) are ignored
     * as those are typically not problematic when they're dummy classes in the tests folder.
     * If you want to get these back as well you can pass false to $duplicatesFilter. Or
     * you can pass your own pattern to exclude if you need to change the default.
     *
     * @param non-empty-string|false $duplicatesFilter
     *
     * @return array<class-string, array<non-empty-string>>
     */
    public function getAmbiguousClasses($duplicatesFilter = '{/(test|fixture|example|stub)s?/}i'): array
    {
        if (false === $duplicatesFilter) {
            return $this->ambiguousClasses;
        }

        if (true === $duplicatesFilter) {
            throw new \InvalidArgumentException('$duplicatesFilter should be false or a string with a valid regex, got true.');
        }

        $ambiguousClasses = [];
        foreach ($this->ambiguousClasses as $class => $paths) {
            $paths = array_filter($paths, function ($path) use ($duplicatesFilter): bool {
                return !Preg::isMatch($duplicatesFilter, strtr($path, '\\', '/'));
            });
            if (\count($paths) > 0) {
                $ambiguousClasses[$class] = array_values($paths);
            }
        }

        return $ambiguousClasses;
    }

    /**
     * Sorts the class map alphabetically by class names
     */
    public function sort(): void
    {
        ksort($this->map);
    }

    /**
     * @param class-string $className
     * @param non-empty-string $path
     */
    public function addClass(string $className, string $path): void
    {
        unset($this->psrViolations[strtr($path, '\\', '/')]);

        $this->map[$className] = $path;
    }

    /**
     * @param class-string $className
     * @return non-empty-string
     */
    public function getClassPath(string $className): string
    {
        if (!isset($this->map[$className])) {
            throw new \OutOfBoundsException('Class '.$className.' is not present in the map');
        }

        return $this->map[$className];
    }

    /**
     * @param class-string $className
     */
    public function hasClass(string $className): bool
    {
        return isset($this->map[$className]);
    }

    public function addPsrViolation(string $warning, string $className, string $path): void
    {
        $path = rtrim(strtr($path, '\\', '/'), '/');

        $this->psrViolations[$path][] = ['warning' => $warning, 'className' => $className];
    }

    public function clearPsrViolationsByPath(string $pathPrefix): void
    {
        $pathPrefix = rtrim(strtr($pathPrefix, '\\', '/'), '/');

        foreach ($this->psrViolations as $path => $violations) {
            if ($path === $pathPrefix || 0 === \strpos($path, $pathPrefix.'/')) {
                unset($this->psrViolations[$path]);
            }
        }
    }

    /**
     * @param class-string $className
     * @param non-empty-string $path
     */
    public function addAmbiguousClass(string $className, string $path): void
    {
        $this->ambiguousClasses[$className][] = $path;
    }

    public function count(): int
    {
        return \count($this->map);
    }

    /**
     * Get the raw psr violations
     *
     * This is a map of filepath to an associative array of the warning string
     * and the offending class name.
     * @return array<string, array<array{warning: string, className: string}>>
     */
    public function getRawPsrViolations(): array
    {
        return $this->psrViolations;
    }
}
<?php declare(strict_types=1);

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\ClassMapGenerator;

use RuntimeException;
use Composer\Pcre\Preg;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PhpFileParser
{
    /**
     * Extract the classes in the given file
     *
     * @param  string            $path The file to check
     * @throws RuntimeException
     * @return list<class-string> The found classes
     */
    public static function findClasses(string $path): array
    {
        $extraTypes = self::getExtraTypes();

        if (!function_exists('php_strip_whitespace')) {
            throw new RuntimeException('Classmap generation relies on the php_strip_whitespace function, but it has been disabled by the disable_functions directive.');
        }

        // Use @ here instead of Silencer to actively suppress 'unhelpful' output
        // @link https://github.com/composer/composer/pull/4886
        $contents = @php_strip_whitespace($path);
        if ('' === $contents) {
            if (!file_exists($path)) {
                $message = 'File at "%s" does not exist, check your classmap definitions';
            } elseif (!self::isReadable($path)) {
                $message = 'File at "%s" is not readable, check its permissions';
            } elseif ('' === trim((string) file_get_contents($path))) {
                // The input file was really empty and thus contains no classes
                return [];
            } else {
                $message = 'File at "%s" could not be parsed as PHP, it may be binary or corrupted';
            }

            $error = error_get_last();
            if (isset($error['message'])) {
                $message .= PHP_EOL . 'The following message may be helpful:' . PHP_EOL . $error['message'];
            }

            throw new RuntimeException(sprintf($message, $path));
        }

        // return early if there is no chance of matching anything in this file
        Preg::matchAllStrictGroups('{\b(?:class|interface|trait'.$extraTypes.')\s}i', $contents, $matches);
        if ([] === $matches) {
            return [];
        }

        $p = new PhpFileCleaner($contents, count($matches[0]));
        $contents = $p->clean();
        unset($p);

        Preg::matchAll('{
            (?:
                 \b(?<![\\\\$:>])(?P<type>class|interface|trait'.$extraTypes.') \s++ (?P<name>[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+)
               | \b(?<![\\\\$:>])(?P<ns>namespace) (?P<nsname>\s++[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\s*+\\\\\s*+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+)? \s*+ [\{;]
            )
        }ix', $contents, $matches);

        $classes = [];
        $namespace = '';

        for ($i = 0, $len = count($matches['type']); $i < $len; ++$i) {
            if (isset($matches['ns'][$i]) && $matches['ns'][$i] !== '') {
                $namespace = str_replace([' ', "\t", "\r", "\n"], '', (string) $matches['nsname'][$i]) . '\\';
            } else {
                $name = $matches['name'][$i];
                assert(is_string($name));
                // skip anon classes extending/implementing
                if ($name === 'extends') {
                    continue;
                }
                if ($name === 'implements') {
                    continue;
                }

                if ($name[0] === ':') {
                    // This is an XHP class, https://github.com/facebook/xhp
                    $name = 'xhp'.substr(str_replace(['-', ':'], ['_', '__'], $name), 1);
                } elseif (strtolower((string) $matches['type'][$i]) === 'enum') {
                    // something like:
                    //   enum Foo: int { HERP = '123'; }
                    // The regex above captures the colon, which isn't part of
                    // the class name.
                    // or:
                    //   enum Foo:int { HERP = '123'; }
                    // The regex above captures the colon and type, which isn't part of
                    // the class name.
                    $colonPos = strrpos($name, ':');
                    if (false !== $colonPos) {
                        $name = substr($name, 0, $colonPos);
                    }
                }

                /** @var class-string */
                $className = ltrim($namespace . $name, '\\');
                $classes[] = $className;
            }
        }

        return $classes;
    }

    private static function getExtraTypes(): string
    {
        static $extraTypes = null;

        if (null === $extraTypes) {
            $extraTypes = '';
            $extraTypesArray = [];
            if (PHP_VERSION_ID >= 80100 || (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.3', '>='))) {
                $extraTypes .= '|enum';
                $extraTypesArray = ['enum'];
            }

            PhpFileCleaner::setTypeConfig(array_merge(['class', 'interface', 'trait'], $extraTypesArray));
        }

        return $extraTypes;
    }

    /**
     * Cross-platform safe version of is_readable()
     *
     * This will also check for readability by reading the file as is_readable can not be trusted on network-mounts
     * and \\wsl$ paths. See https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926
     *
     * @see Composer\Util\Filesystem::isReadable
     *
     * @return bool
     */
    private static function isReadable(string $path)
    {
        if (is_readable($path)) {
            return true;
        }

        if (is_file($path)) {
            return false !== @file_get_contents($path, false, null, 0, 1);
        }

        // assume false otherwise
        return false;
    }
}
Copyright (C) 2016 Composer

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.
<?php

/*
 * This file is part of composer/ca-bundle.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\CaBundle;

use Psr\Log\LoggerInterface;
use Symfony\Component\Process\PhpProcess;

/**
 * @author Chris Smith <chris@cs278.org>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class CaBundle
{
    /** @var string|null */
    private static $caPath;
    /** @var array<string, bool> */
    private static $caFileValidity = array();

    /**
     * Returns the system CA bundle path, or a path to the bundled one
     *
     * This method was adapted from Sslurp.
     * https://github.com/EvanDotPro/Sslurp
     *
     * (c) Evan Coury <me@evancoury.com>
     *
     * For the full copyright and license information, please see below:
     *
     * Copyright (c) 2013, Evan Coury
     * 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.
     *
     * @param  LoggerInterface $logger optional logger for information about which CA files were loaded
     * @return string          path to a CA bundle file or directory
     */
    public static function getSystemCaRootBundlePath(?LoggerInterface $logger = null)
    {
        if (self::$caPath !== null) {
            return self::$caPath;
        }
        $caBundlePaths = array();

        // If SSL_CERT_FILE env variable points to a valid certificate/bundle, use that.
        // This mimics how OpenSSL uses the SSL_CERT_FILE env variable.
        $caBundlePaths[] = self::getEnvVariable('SSL_CERT_FILE');

        // If SSL_CERT_DIR env variable points to a valid certificate/bundle, use that.
        // This mimics how OpenSSL uses the SSL_CERT_FILE env variable.
        $caBundlePaths[] = self::getEnvVariable('SSL_CERT_DIR');

        $caBundlePaths[] = ini_get('openssl.cafile');
        $caBundlePaths[] = ini_get('openssl.capath');

        $otherLocations = array(
            '/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem', // Fedora, RHEL, CentOS (ca-certificates package) - NEW
            '/etc/pki/tls/certs/ca-bundle.crt', // Fedora, RHEL, CentOS (ca-certificates package) - Deprecated
            '/etc/ssl/certs/ca-certificates.crt', // Debian, Ubuntu, Gentoo, Arch Linux (ca-certificates package)
            '/etc/ssl/ca-bundle.pem', // SUSE, openSUSE (ca-certificates package)
            '/usr/ssl/certs/ca-bundle.crt', // Cygwin
            '/opt/local/share/curl/curl-ca-bundle.crt', // OS X macports, curl-ca-bundle package
            '/usr/local/share/curl/curl-ca-bundle.crt', // Default cURL CA bunde path (without --with-ca-bundle option)
            '/usr/share/ssl/certs/ca-bundle.crt', // Really old RedHat?
            '/etc/ssl/cert.pem', // OpenBSD
            '/usr/local/etc/openssl/cert.pem', // OS X homebrew, openssl package
            '/usr/local/etc/openssl@1.1/cert.pem', // OS X homebrew, openssl@1.1 package
            '/opt/homebrew/etc/openssl@3/cert.pem', // macOS silicon homebrew, openssl@3 package
            '/opt/homebrew/etc/openssl@1.1/cert.pem', // macOS silicon homebrew, openssl@1.1 package
            '/etc/pki/tls/certs',
            '/etc/ssl/certs', // FreeBSD
        );

        $caBundlePaths = array_merge($caBundlePaths, $otherLocations);

        foreach ($caBundlePaths as $caBundle) {
            if ($caBundle && self::caFileUsable($caBundle, $logger)) {
                return self::$caPath = $caBundle;
            }

            if ($caBundle && self::caDirUsable($caBundle, $logger)) {
                return self::$caPath = $caBundle;
            }
        }

        return self::$caPath = static::getBundledCaBundlePath(); // Bundled CA file, last resort
    }

    /**
     * Returns the path to the bundled CA file
     *
     * In case you don't want to trust the user or the system, you can use this directly
     *
     * @return string path to a CA bundle file
     */
    public static function getBundledCaBundlePath()
    {
        $caBundleFile = __DIR__.'/../res/cacert.pem';

        // cURL does not understand 'phar://' paths
        // see https://github.com/composer/ca-bundle/issues/10
        if (0 === strpos($caBundleFile, 'phar://')) {
            $tempCaBundleFile = tempnam(sys_get_temp_dir(), 'openssl-ca-bundle-');
            if (false === $tempCaBundleFile) {
                throw new \RuntimeException('Could not create a temporary file to store the bundled CA file');
            }

            file_put_contents(
                $tempCaBundleFile,
                file_get_contents($caBundleFile)
            );

            register_shutdown_function(function() use ($tempCaBundleFile) {
                @unlink($tempCaBundleFile);
            });

            $caBundleFile = $tempCaBundleFile;
        }

        return $caBundleFile;
    }

    /**
     * Validates a CA file using opensl_x509_parse only if it is safe to use
     *
     * @param string          $filename
     * @param LoggerInterface $logger   optional logger for information about which CA files were loaded
     *
     * @return bool
     */
    public static function validateCaFile($filename, ?LoggerInterface $logger = null)
    {
        static $warned = false;

        if (isset(self::$caFileValidity[$filename])) {
            return self::$caFileValidity[$filename];
        }

        $contents = file_get_contents($filename);

        if (is_string($contents) && strlen($contents) > 0) {
            $contents = preg_replace("/^(\\-+(?:BEGIN|END))\\s+TRUSTED\\s+(CERTIFICATE\\-+)\$/m", '$1 $2', $contents);
            if (null === $contents) {
                // regex extraction failed
                $isValid = false;
            } else {
                $isValid = (bool) openssl_x509_parse($contents);
            }
        } else {
            $isValid = false;
        }

        if ($logger) {
            $logger->debug('Checked CA file '.realpath($filename).': '.($isValid ? 'valid' : 'invalid'));
        }

        return self::$caFileValidity[$filename] = $isValid;
    }

    /**
     * Test if it is safe to use the PHP function openssl_x509_parse().
     *
     * This checks if OpenSSL extensions is vulnerable to remote code execution
     * via the exploit documented as CVE-2013-6420.
     *
     * @return bool
     */
    public static function isOpensslParseSafe()
    {
        return true;
    }

    /**
     * Resets the static caches
     * @return void
     */
    public static function reset()
    {
        self::$caFileValidity = array();
        self::$caPath = null;
    }

    /**
     * @param  string $name
     * @return string|false
     */
    private static function getEnvVariable($name)
    {
        if (isset($_SERVER[$name])) {
            return (string) $_SERVER[$name];
        }

        if (PHP_SAPI === 'cli' && ($value = getenv($name)) !== false && $value !== null) {
            return (string) $value;
        }

        return false;
    }

    /**
     * @param  string|false $certFile
     * @param  LoggerInterface|null $logger
     * @return bool
     */
    private static function caFileUsable($certFile, ?LoggerInterface $logger = null)
    {
        return $certFile
            && self::isFile($certFile, $logger)
            && self::isReadable($certFile, $logger)
            && self::validateCaFile($certFile, $logger);
    }

    /**
     * @param  string|false $certDir
     * @param  LoggerInterface|null $logger
     * @return bool
     */
    private static function caDirUsable($certDir, ?LoggerInterface $logger = null)
    {
        return $certDir
            && self::isDir($certDir, $logger)
            && self::isReadable($certDir, $logger)
            && self::glob($certDir . '/*', $logger);
    }

    /**
     * @param  string $certFile
     * @param  LoggerInterface|null $logger
     * @return bool
     */
    private static function isFile($certFile, ?LoggerInterface $logger = null)
    {
        $isFile = @is_file($certFile);
        if (!$isFile && $logger) {
            $logger->debug(sprintf('Checked CA file %s does not exist or it is not a file.', $certFile));
        }

        return $isFile;
    }

    /**
     * @param  string $certDir
     * @param  LoggerInterface|null $logger
     * @return bool
     */
    private static function isDir($certDir, ?LoggerInterface $logger = null)
    {
        $isDir = @is_dir($certDir);
        if (!$isDir && $logger) {
            $logger->debug(sprintf('Checked directory %s does not exist or it is not a directory.', $certDir));
        }

        return $isDir;
    }

    /**
     * @param  string $certFileOrDir
     * @param  LoggerInterface|null $logger
     * @return bool
     */
    private static function isReadable($certFileOrDir, ?LoggerInterface $logger = null)
    {
        $isReadable = @is_readable($certFileOrDir);
        if (!$isReadable && $logger) {
            $logger->debug(sprintf('Checked file or directory %s is not readable.', $certFileOrDir));
        }

        return $isReadable;
    }

    /**
     * @param  string $pattern
     * @param  LoggerInterface|null $logger
     * @return bool
     */
    private static function glob($pattern, ?LoggerInterface $logger = null)
    {
        $certs = glob($pattern);
        if ($certs === false) {
            if ($logger) {
                $logger->debug(sprintf("An error occurred while trying to find certificates for pattern: %s", $pattern));
            }
            return false;
        }

        if (count($certs) === 0) {
            if ($logger) {
                $logger->debug(sprintf("No CA files found for pattern: %s", $pattern));
            }
            return false;
        }

        return true;
    }
}
##
## Bundle of CA Root Certificates
##
## Certificate data from Mozilla as of: Tue Aug 12 03:12:01 2025 GMT
##
## Find updated versions here: https://curl.se/docs/caextract.html
##
## This is a bundle of X.509 certificates of public Certificate Authorities
## (CA). These were automatically extracted from Mozilla's root certificates
## file (certdata.txt).  This file can be found in the mozilla source tree:
## https://raw.githubusercontent.com/mozilla-firefox/firefox/refs/heads/release/security/nss/lib/ckfw/builtins/certdata.txt
##
## It contains the certificates in PEM format and therefore
## can be directly used with curl / libcurl / php_curl, or with
## an Apache+mod_ssl webserver for SSL client authentication.
## Just configure this file as the SSLCACertificateFile.
##
## Conversion done with mk-ca-bundle.pl version 1.29.
## SHA256: c185b859c19b05f104c50e1b0b2a6c775149a1d9bb731d414d73b1722892a66c
##


Entrust Root Certification Authority
====================================
-----BEGIN CERTIFICATE-----
MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMCVVMxFjAUBgNV
BAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0Lm5ldC9DUFMgaXMgaW5jb3Jw
b3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMWKGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsG
A1UEAxMkRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0
MloXDTI2MTEyNzIwNTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMu
MTkwNwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSByZWZlcmVu
Y2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNVBAMTJEVudHJ1c3QgUm9v
dCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
ALaVtkNC+sZtKm9I35RMOVcF7sN5EUFoNu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYsz
A9u3g3s+IIRe7bJWKKf44LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOww
Cj0Yzfv9KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGIrb68
j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi94DkZfs0Nw4pgHBN
rziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOBsDCBrTAOBgNVHQ8BAf8EBAMCAQYw
DwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAigA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1
MzQyWjAfBgNVHSMEGDAWgBRokORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DH
hmak8fdLQ/uEvW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA
A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9tO1KzKtvn1ISM
Y/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6ZuaAGAT/3B+XxFNSRuzFVJ7yVTa
v52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTS
W3iDVuycNsMm4hH2Z0kdkquM++v/eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0
tHuu2guQOHXvgR1m0vdXcDazv/wor3ElhVsT/h5/WrQ8
-----END CERTIFICATE-----

QuoVadis Root CA 2
==================
-----BEGIN CERTIFICATE-----
MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT
EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMjAeFw0wNjExMjQx
ODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4IC
DwAwggIKAoICAQCaGMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6
XJxgFyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55JWpzmM+Yk
lvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bBrrcCaoF6qUWD4gXmuVbB
lDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp+ARz8un+XJiM9XOva7R+zdRcAitMOeGy
lZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt
66/3FsvbzSUr5R/7mp/iUcw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1Jdxn
wQ5hYIizPtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og/zOh
D7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UHoycR7hYQe7xFSkyy
BNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuIyV77zGHcizN300QyNQliBJIWENie
J0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1Ud
DgQWBBQahGK8SEwzJQTU7tD2A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGU
a6FJpEcwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT
ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2fBluornFdLwUv
Z+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzng/iN/Ae42l9NLmeyhP3ZRPx3
UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2BlfF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodm
VjB3pjd4M1IQWK4/YY7yarHvGH5KWWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK
+JDSV6IZUaUtl0HaB0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrW
IozchLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPRTUIZ3Ph1
WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWDmbA4CD/pXvk1B+TJYm5X
f6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0ZohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II
4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8
VCLAAVBpQ570su9t+Oza8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u
-----END CERTIFICATE-----

QuoVadis Root CA 3
==================
-----BEGIN CERTIFICATE-----
MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0xGTAXBgNVBAoT
EFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJvb3QgQ0EgMzAeFw0wNjExMjQx
OTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
aW1pdGVkMRswGQYDVQQDExJRdW9WYWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4IC
DwAwggIKAoICAQDMV0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNgg
DhoB4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUrH556VOij
KTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd8lyyBTNvijbO0BNO/79K
DDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9CabwvvWhDFlaJKjdhkf2mrk7AyxRllDdLkgbv
BNDInIjbC3uBr7E9KsRlOni27tyAsdLTmZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwp
p5ijJUMv7/FfJuGITfhebtfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8
nT8KKdjcT5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDtWAEX
MJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZc6tsgLjoC2SToJyM
Gf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A4iLItLRkT9a6fUg+qGkM17uGcclz
uD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYDVR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHT
BgkrBgEEAb5YAAMwgcUwgZMGCCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmlj
YXRlIGNvbnN0aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0
aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVudC4wLQYIKwYB
BQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2NwczALBgNVHQ8EBAMCAQYwHQYD
VR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4GA1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4
ywLQoUmkRzBFMQswCQYDVQQGEwJCTTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UE
AxMSUXVvVmFkaXMgUm9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZV
qyM07ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSemd1o417+s
hvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd+LJ2w/w4E6oM3kJpK27z
POuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2
Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadNt54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp
8kokUvd0/bpO5qgdAm6xDYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBC
bjPsMZ57k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6szHXu
g/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0jWy10QJLZYxkNc91p
vGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeTmJlglFwjz1onl14LBQaTNx47aTbr
qZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK4SVhM7JZG+Ju1zdXtg2pEto=
-----END CERTIFICATE-----

DigiCert Assured ID Root CA
===========================
-----BEGIN CERTIFICATE-----
MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw
IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzEx
MTEwMDAwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL
ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0Ew
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7cJpSIqvTO
9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYPmDI2dsze3Tyoou9q+yHy
UmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW
/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpy
oeb6pNnVFzF1roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf
GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRF
66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzANBgkq
hkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2Bc
EkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38Fn
SbNd67IJKusm7Xi+fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i
8b5QZ7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe
+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g==
-----END CERTIFICATE-----

DigiCert Global Root CA
=======================
-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBhMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw
HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBDQTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAw
MDAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsBCSDMAZOn
TjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97nh6Vfe63SKMI2tavegw5
BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt43C/dxC//AH2hdmoRBBYMql1GNXRor5H
4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7PT19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y
7vrTC0LUq7dBMtoM1O/4gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQAB
o2MwYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbRTLtm
8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUwDQYJKoZIhvcNAQEF
BQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/EsrhMAtudXH/vTBH1jLuG2cenTnmCmr
EbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIt
tep3Sp+dWOIrWcBAI+0tKIJFPnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886
UAb3LujEV0lsYSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
-----END CERTIFICATE-----

DigiCert High Assurance EV Root CA
==================================
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw
KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw
MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ
MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu
Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t
Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS
OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3
MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ
NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe
h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB
Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY
JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ
V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp
myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK
mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K
-----END CERTIFICATE-----

SwissSign Gold CA - G2
======================
-----BEGIN CERTIFICATE-----
MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkNIMRUw
EwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2lnbiBHb2xkIENBIC0gRzIwHhcN
MDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBFMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dp
c3NTaWduIEFHMR8wHQYDVQQDExZTd2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0B
AQEFAAOCAg8AMIICCgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUq
t2/876LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+bbqBHH5C
jCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c6bM8K8vzARO/Ws/BtQpg
vd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqEemA8atufK+ze3gE/bk3lUIbLtK/tREDF
ylqM2tIrfKjuvqblCqoOpd8FUrdVxyJdMmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvR
AiTysybUa9oEVeXBCsdtMDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuend
jIj3o02yMszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69yFGkO
peUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPiaG59je883WX0XaxR
7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxMgI93e2CaHt+28kgeDrpOVG2Y4OGi
GqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw
AwEB/zAdBgNVHQ4EFgQUWyV7lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64
OfPAeGZe6Drn8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov
L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe645R88a7A3hfm
5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczOUYrHUDFu4Up+GC9pWbY9ZIEr
44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOf
Mke6UiI0HTJ6CVanfCU2qT1L2sCCbwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6m
Gu6uLftIdxf+u+yvGPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxp
mo/a77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCChdiDyyJk
vC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid392qgQmwLOM7XdVAyksLf
KzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEppLd6leNcG2mqeSz53OiATIgHQv2ieY2Br
NU0LbbqhPcCT4H8js1WtciVORvnSFu+wZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6Lqj
viOvrv1vA+ACOzB2+httQc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ
-----END CERTIFICATE-----

SecureTrust CA
==============
-----BEGIN CERTIFICATE-----
MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBIMQswCQYDVQQG
EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xFzAVBgNVBAMTDlNlY3VyZVRy
dXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIzMTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAe
BgNVBAoTF1NlY3VyZVRydXN0IENvcnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQX
OZEzZum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO0gMdA+9t
DWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIaowW8xQmxSPmjL8xk037uH
GFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b
01k/unK8RCSc43Oz969XL0Imnal0ugBS8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmH
ursCAwEAAaOBnTCBmjATBgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/
BAUwAwEB/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCegJYYj
aHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ
KoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt36Z3q059c4EVlew3KW+JwULKUBRSu
SceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHf
mbx8IVQr5Fiiu1cprp6poxkmD5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZ
nMUFdAvnZyPSCPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR
3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE=
-----END CERTIFICATE-----

Secure Global CA
================
-----BEGIN CERTIFICATE-----
MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQG
EwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBH
bG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkxMjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEg
MB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwg
Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jx
YDiJiQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa/FHtaMbQ
bqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJjnIFHovdRIWCQtBJwB1g
8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnIHmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYV
HDGA76oYa8J719rO+TMg1fW9ajMtgQT7sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi
0XPnj3pDAgMBAAGjgZ0wgZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud
EwEB/wQFMAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCswKaAn
oCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsGAQQBgjcVAQQDAgEA
MA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0LURYD7xh8yOOvaliTFGCRsoTciE6+
OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXOH0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cn
CDpOGR86p1hcF895P4vkp9MmI50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/5
3CYNv6ZHdAbYiNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc
f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW
-----END CERTIFICATE-----

COMODO Certification Authority
==============================
-----BEGIN CERTIFICATE-----
MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCBgTELMAkGA1UE
BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG
A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNVBAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1
dGhvcml0eTAeFw0wNjEyMDEwMDAwMDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEb
MBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFD
T01PRE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0aG9yaXR5
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3UcEbVASY06m/weaKXTuH
+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI2GqGd0S7WWaXUF601CxwRM/aN5VCaTww
xHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV
4EajcNxo2f8ESIl33rXp+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA
1KGzqSX+DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5OnKVI
rLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW/zAOBgNVHQ8BAf8E
BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3JsLmNvbW9k
b2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOC
AQEAPpiem/Yb6dc5t3iuHXIYSdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CP
OGEIqB6BCsAvIC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/
RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4zJVSk/BwJVmc
IGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5ddBA6+C4OmF4O5MBKgxTMVBbkN
+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IBZQ==
-----END CERTIFICATE-----

COMODO ECC Certification Authority
==================================
-----BEGIN CERTIFICATE-----
MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTELMAkGA1UEBhMC
R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE
ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBB
dXRob3JpdHkwHhcNMDgwMzA2MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0Ix
GzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRo
b3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSRFtSrYpn1PlILBs5BAH+X
4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0JcfRK9ChQtP6IHG4/bC8vCVlbpVsLM5ni
wz2J+Wos77LTBumjQjBAMB0GA1UdDgQWBBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8E
BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VG
FAkK+qDmfQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdvGDeA
U/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY=
-----END CERTIFICATE-----

Certigna
========
-----BEGIN CERTIFICATE-----
MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNVBAYTAkZSMRIw
EAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4XDTA3MDYyOTE1MTMwNVoXDTI3
MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwI
Q2VydGlnbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7q
XOEm7RFHYeGifBZ4QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyH
GxnygQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbwzBfsV1/p
ogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q130yGLMLLGq/jj8UEYkg
DncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKf
Irjxwo1p3Po6WAbfAgMBAAGjgbwwgbkwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQ
tCRZvgHyUtVF9lo53BEwZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJ
BgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzjAQ/J
SP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG9w0BAQUFAAOCAQEA
hQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8hbV6lUmPOEvjvKtpv6zf+EwLHyzs+
ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFncfca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1klu
PBS1xp81HlDQwY9qcEQCYsuuHWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY
1gkIl2PlwS6wt0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw
WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg==
-----END CERTIFICATE-----

ePKI Root Certification Authority
=================================
-----BEGIN CERTIFICATE-----
MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBeMQswCQYDVQQG
EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xKjAoBgNVBAsMIWVQS0kg
Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMx
MjdaMF4xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEq
MCgGA1UECwwhZVBLSSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0B
AQEFAAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAHSyZbCUNs
IZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAhijHyl3SJCRImHJ7K2RKi
lTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3XDZoTM1PRYfl61dd4s5oz9wCGzh1NlDiv
qOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX
12ruOzjjK9SXDrkb5wdJfzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0O
WQqraffAsgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uUWH1+
ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLSnT0IFaUQAS2zMnao
lQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pHdmX2Os+PYhcZewoozRrSgx4hxyy/
vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJipNiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXi
Zo1jDiVN1Rmy5nk3pyKdVDECAwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/Qkqi
MAwGA1UdEwQFMAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH
ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGBuvl2ICO1J2B0
1GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6YlPwZpVnPDimZI+ymBV3QGypzq
KOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkPJXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdV
xrsStZf0X4OFunHB2WyBEXYKCrC/gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEP
NXubrjlpC2JgQCA2j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+r
GNm65ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUBo2M3IUxE
xJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS/jQ6fbjpKdx2qcgw+BRx
gMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2zGp1iro2C6pSe3VkQw63d4k3jMdXH7Ojy
sP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTEW9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmOD
BCEIZ43ygknQW/2xzQ+DhNQ+IIX3Sj0rnP0qCglN6oH4EZw=
-----END CERTIFICATE-----

certSIGN ROOT CA
================
-----BEGIN CERTIFICATE-----
MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYTAlJPMREwDwYD
VQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTAeFw0wNjA3MDQxNzIwMDRa
Fw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UE
CxMQY2VydFNJR04gUk9PVCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7I
JUqOtdu0KBuqV5Do0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHH
rfAQUySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5dRdY4zTW2
ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQOA7+j0xbm0bqQfWwCHTD
0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwvJoIQ4uNllAoEwF73XVv4EOLQunpL+943
AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B
Af8EBAMCAcYwHQYDVR0OBBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IB
AQA+0hyJLjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecYMnQ8
SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ44gx+FkagQnIl6Z0
x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6IJd1hJyMctTEHBDa0GpC9oHRxUIlt
vBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNwi/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7Nz
TogVZ96edhBiIL5VaZVDADlN9u6wWk5JRFRYX0KD
-----END CERTIFICATE-----

NetLock Arany (Class Gold) Főtanúsítvány
========================================
-----BEGIN CERTIFICATE-----
MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTERMA8G
A1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610
dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBB
cmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgx
MjA2MTUwODIxWjCBpzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxO
ZXRMb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlmaWNhdGlv
biBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNzIEdvbGQpIEbFkXRhbsO6
c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu
0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrTlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw
/HpYzY6b7cNGbIRwXdrzAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAk
H3B5r9s5VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRGILdw
fzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2BJtr+UBdADTHLpl1
neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIB
BjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2MU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwW
qZw8UQCgwBEIBaeZ5m8BiFRhbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTta
YtOUZcTh5m2C+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC
bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2FuLjbvrW5Kfna
NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu
dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=
-----END CERTIFICATE-----

Microsec e-Szigno Root CA 2009
==============================
-----BEGIN CERTIFICATE-----
MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYDVQQGEwJIVTER
MA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jv
c2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o
dTAeFw0wOTA2MTYxMTMwMThaFw0yOTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UE
BwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUt
U3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvPkd6mJviZpWNwrZuuyjNA
fW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tccbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG
0IMZfcChEhyVbUr02MelTTMuhTlAdX4UfIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKA
pxn1ntxVUwOXewdI/5n7N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm
1HxdrtbCxkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1+rUC
AwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTLD8bf
QkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAbBgNVHREE
FDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqGSIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0o
lZMEyL/azXm4Q5DwpL7v8u8hmLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfX
I/OMn74dseGkddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775
tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c2Pm2G2JwCz02
yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5tHMN1Rq41Bab2XD0h7lbwyYIi
LXpUq3DDfSJlgnCW
-----END CERTIFICATE-----

GlobalSign Root CA - R3
=======================
-----BEGIN CERTIFICATE-----
MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4GA1UECxMXR2xv
YmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh
bFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT
aWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln
bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWt
iHL8RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsTgHeMCOFJ
0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmmKPZpO/bLyCiR5Z2KYVc3
rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zdQQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjl
OCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZXriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2
xmmFghcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
FI/wS3+oLkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZURUm7
lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMpjjM5RcOO5LlXbKr8
EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK6fBdRoyV3XpYKBovHd7NADdBj+1E
bddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQXmcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18
YIvDQVETI53O9zJrlAGomecsMx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7r
kpeDMdmztcpHWD9f
-----END CERTIFICATE-----

Izenpe.com
==========
-----BEGIN CERTIFICATE-----
MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4MQswCQYDVQQG
EwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wHhcNMDcxMjEz
MTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMu
QS4xEzARBgNVBAMMCkl6ZW5wZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ
03rKDx6sp4boFmVqscIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAK
ClaOxdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6HLmYRY2xU
+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFXuaOKmMPsOzTFlUFpfnXC
PCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQDyCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxT
OTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbK
F7jJeodWLBoBHmy+E60QrLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK
0GqfvEyNBjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8Lhij+
0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIBQFqNeb+Lz0vPqhbB
leStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+HMh3/1uaD7euBUbl8agW7EekFwID
AQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2luZm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+
SVpFTlBFIFMuQS4gLSBDSUYgQTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBG
NjIgUzgxQzBBBgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx
MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O
BBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUAA4ICAQB4pgwWSp9MiDrAyw6l
Fn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWblaQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbga
kEyrkgPH7UIBzg/YsfqikuFgba56awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8q
hT/AQKM6WfxZSzwoJNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Cs
g1lwLDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCTVyvehQP5
aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGkLhObNA5me0mrZJfQRsN5
nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJbUjWumDqtujWTI6cfSN01RpiyEGjkpTHC
ClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZo
Q0iy2+tzJOeRf1SktoA+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1Z
WrOZyGlsQyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw==
-----END CERTIFICATE-----

Go Daddy Root Certificate Authority - G2
========================================
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT
B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoTEUdvRGFkZHkuY29tLCBJbmMu
MTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5
MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6
b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8G
A1UEAxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKDE6bFIEMBO4Tx5oVJnyfq
9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD
+qK+ihVqf94Lw7YZFAXK6sOoBJQ7RnwyDfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutd
fMh8+7ArU6SSYmlRJQVhGkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMl
NAJWJwGRtDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEAAaNC
MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFDqahQcQZyi27/a9
BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmXWWcDYfF+OwYxdS2hII5PZYe096ac
vNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r
5N9ss4UXnT3ZJE95kTXWXwTrgIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYV
N8Gb5DKj7Tjo2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO
LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI4uJEvlz36hz1
-----END CERTIFICATE-----

Starfield Root Certificate Authority - G2
=========================================
-----BEGIN CERTIFICATE-----
MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMxEDAOBgNVBAgT
B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s
b2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVsZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0
eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAw
DgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQg
VGVjaG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZpY2F0ZSBB
dXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL3twQP89o/8ArFv
W59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMgnLRJdzIpVv257IzdIvpy3Cdhl+72WoTs
bhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNk
N3mSwOxGXn/hbVNMYq/NHwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7Nf
ZTD4p7dNdloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0HZbU
JtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
AQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0GCSqGSIb3DQEBCwUAA4IBAQARWfol
TwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjUsHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx
4mcujJUDJi5DnUox9g61DLu34jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUw
F5okxBDgBPfg8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K
pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1mMpYjn0q7pBZ
c2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0
-----END CERTIFICATE-----

Starfield Services Root Certificate Authority - G2
==================================================
-----BEGIN CERTIFICATE-----
MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT
B0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNobm9s
b2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVsZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRl
IEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNV
BAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxT
dGFyZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2VydmljZXMg
Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20pOsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2
h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm28xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4Pa
hHQUw2eeBGg6345AWh1KTs9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLP
LJGmpufehRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk6mFB
rMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAwDwYDVR0TAQH/BAUw
AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+qAdcwKziIorhtSpzyEZGDMA0GCSqG
SIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMIbw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPP
E95Dz+I0swSdHynVv/heyNXBve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTy
xQGjhdByPq1zqwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd
iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn0q23KXB56jza
YyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCNsSi6
-----END CERTIFICATE-----

AffirmTrust Commercial
======================
-----BEGIN CERTIFICATE-----
MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCVVMxFDAS
BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMB4XDTEw
MDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly
bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6Eqdb
DuKPHx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yrba0F8PrV
C8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPALMeIrJmqbTFeurCA+ukV6
BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1yHp52UKqK39c/s4mT6NmgTWvRLpUHhww
MmWd5jyTXlBOeuM61G7MGvv50jeuJCqrVwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNV
HQ4EFgQUnZPGU4teyq8/nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
AQYwDQYJKoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYGXUPG
hi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNjvbz4YYCanrHOQnDi
qX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivtZ8SOyUOyXGsViQK8YvxO8rUzqrJv
0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9gN53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0kh
sUlHRUe072o0EclNmsxZt9YCnlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8=
-----END CERTIFICATE-----

AffirmTrust Networking
======================
-----BEGIN CERTIFICATE-----
MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UEBhMCVVMxFDAS
BgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMB4XDTEw
MDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmly
bVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SE
Hi3yYJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbuakCNrmreI
dIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRLQESxG9fhwoXA3hA/Pe24
/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gb
h+0t+nvujArjqWaJGctB+d1ENmHP4ndGyH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNV
HQ4EFgQUBx/S55zawm6iQLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
AQYwDQYJKoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfOtDIu
UFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzuQY0x2+c06lkh1QF6
12S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZLgo/bNjR9eUJtGxUAArgFU2HdW23
WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4uolu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9
/ZFvgrG+CJPbFEfxojfHRZ48x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s=
-----END CERTIFICATE-----

AffirmTrust Premium
===================
-----BEGIN CERTIFICATE-----
MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UEBhMCVVMxFDAS
BgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMB4XDTEwMDEy
OTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRy
dXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
MIICCgKCAgEAxBLfqV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtn
BKAQJG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ+jjeRFcV
5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrSs8PhaJyJ+HoAVt70VZVs
+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmd
GPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d770O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5R
p9EixAqnOEhss/n/fauGV+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NI
S+LI+H+SqHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S5u04
6uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4IaC1nEWTJ3s7xgaVY5
/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TXOwF0lkLgAOIua+rF7nKsu7/+6qqo
+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYEFJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB
/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByv
MiPIs0laUZx2KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg
Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B8OWycvpEgjNC
6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQMKSOyARiqcTtNd56l+0OOF6S
L5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK
+4w1IX2COPKpVJEZNZOUbWo6xbLQu4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmV
BtWVyuEklut89pMFu+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFg
IxpHYoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8GKa1qF60
g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaORtGdFNrHF+QFlozEJLUb
zxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6eKeC2uAloGRwYQw==
-----END CERTIFICATE-----

AffirmTrust Premium ECC
=======================
-----BEGIN CERTIFICATE-----
MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMCVVMxFDASBgNV
BAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQcmVtaXVtIEVDQzAeFw0xMDAx
MjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJBgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1U
cnVzdDEgMB4GA1UEAwwXQWZmaXJtVHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQA
IgNiAAQNMF4bFZ0D0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQ
N8O9ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0GA1UdDgQW
BBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAK
BggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/VsaobgxCd05DhT1wV/GzTjxi+zygk8N53X
57hG8f2h4nECMEJZh0PUUd+60wkyWs6Iflc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKM
eQ==
-----END CERTIFICATE-----

Certum Trusted Network CA
=========================
-----BEGIN CERTIFICATE-----
MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBMMSIwIAYDVQQK
ExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBUcnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIy
MTIwNzM3WhcNMjkxMjMxMTIwNzM3WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBU
ZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5
MSIwIAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rHUV+rpDKmYYe2bg+G0jAC
l/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LMTXPb865Px1bVWqeWifrzq2jUI4ZZJ88J
J7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVUBBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4
fOQtf/WsX+sWn7Et0brMkUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0
cvW0QM8xAcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNVHRMB
Af8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNVHQ8BAf8EBAMCAQYw
DQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15ysHhE49wcrwn9I0j6vSrEuVUEtRCj
jSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfLI9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1
mS1FhIrlQgnXdAIv94nYmem8J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5aj
Zt3hrvJBW8qYVoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI
03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw=
-----END CERTIFICATE-----

TWCA Root Certification Authority
=================================
-----BEGIN CERTIFICATE-----
MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJ
VEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMzWhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQG
EwJUVzESMBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NB
IFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFEAcK0HMMx
QhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HHK3XLfJ+utdGdIzdjp9xC
oi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeXRfwZVzsrb+RH9JlF/h3x+JejiB03HFyP
4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/zrX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1r
y+UPizgN7gr8/g+YnzAx3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIB
BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkqhkiG
9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeCMErJk/9q56YAf4lC
mtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdlsXebQ79NqZp4VKIV66IIArB6nCWlW
QtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62Dlhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVY
T0bf+215WfKEIlKuD8z7fDvnaspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocny
Yh0igzyXxfkZYiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw==
-----END CERTIFICATE-----

Security Communication RootCA2
==============================
-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc
U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMeU2VjdXJpdHkgQ29tbXVuaWNh
dGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoXDTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMC
SlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3Vy
aXR5IENvbW11bmljYXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
ANAVOVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGrzbl+dp++
+T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVMVAX3NuRFg3sUZdbcDE3R
3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQhNBqyjoGADdH5H5XTz+L62e4iKrFvlNV
spHEfbmwhRkGeC7bYRr6hfVKkaHnFtWOojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1K
EOtOghY6rCcMU/Gt1SSwawNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8
QIH4D5csOPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEB
CwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpFcoJxDjrSzG+ntKEj
u/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXcokgfGT+Ok+vx+hfuzU7jBBJV1uXk
3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6q
tnRGEmyR7jTV7JqR50S+kDFy1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29
mvVXIwAHIRc/SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03
-----END CERTIFICATE-----

Actalis Authentication Root CA
==============================
-----BEGIN CERTIFICATE-----
MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UEBhMCSVQxDjAM
BgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UE
AwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDky
MjExMjIwMlowazELMAkGA1UEBhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlz
IFMucC5BLi8wMzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290
IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNvUTufClrJ
wkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX4ay8IMKx4INRimlNAJZa
by/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9KK3giq0itFZljoZUj5NDKd45RnijMCO6
zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1f
YVEiVRvjRuPjPdA1YprbrxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2
oxgkg4YQ51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2Fbe8l
EfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxeKF+w6D9Fz8+vm2/7
hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4Fv6MGn8i1zeQf1xcGDXqVdFUNaBr8
EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbnfpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5
jF66CyCU3nuDuP/jVo23Eek7jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLY
iDrIn3hm7YnzezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt
ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQALe3KHwGCmSUyI
WOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70jsNjLiNmsGe+b7bAEzlgqqI0
JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDzWochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKx
K3JCaKygvU5a2hi/a5iB0P2avl4VSM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+
Xlff1ANATIGk0k9jpwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC
4yyXX04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+OkfcvHlXHo
2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7RK4X9p2jIugErsWx0Hbhz
lefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btUZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXem
OR/qnuOf0GZvBeyqdn6/axag67XH/JJULysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9
vwGYT7JZVEc+NHt4bVaTLnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg==
-----END CERTIFICATE-----

Buypass Class 2 Root CA
=======================
-----BEGIN CERTIFICATE-----
MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU
QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMiBSb290IENBMB4X
DTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1owTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1
eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIw
DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1
g1Lr6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPVL4O2fuPn
9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC911K2GScuVr1QGbNgGE41b
/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHxMlAQTn/0hpPshNOOvEu/XAFOBz3cFIqU
CqTqc/sLUegTBxj6DvEr0VQVfTzh97QZQmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeff
awrbD02TTqigzXsu8lkBarcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgI
zRFo1clrUs3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLiFRhn
Bkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRSP/TizPJhk9H9Z2vX
Uq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN9SG9dKpN6nIDSdvHXx1iY8f93ZHs
M+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxPAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD
VR0OBBYEFMmAd+BikoL1RpzzuvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF
AAOCAgEAU18h9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s
A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3tOluwlN5E40EI
osHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo+fsicdl9sz1Gv7SEr5AcD48S
aq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYd
DnkM/crqJIByw5c/8nerQyIKx+u2DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWD
LfJ6v9r9jv6ly0UsH8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0
oyLQI+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK75t98biGC
wWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h3PFaTWwyI0PurKju7koS
CTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPzY11aWOIv4x3kqdbQCtCev9eBCfHJxyYN
rJgWVqA=
-----END CERTIFICATE-----

Buypass Class 3 Root CA
=======================
-----BEGIN CERTIFICATE-----
MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU
QnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3MgQ2xhc3MgMyBSb290IENBMB4X
DTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFowTjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1
eXBhc3MgQVMtOTgzMTYzMzI3MSAwHgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIw
DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRH
sJ8YZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3EN3coTRiR
5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9tznDDgFHmV0ST9tD+leh
7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX0DJq1l1sDPGzbjniazEuOQAnFN44wOwZ
ZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH
2xc519woe2v1n/MuwU8XKhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV
/afmiSTYzIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvSO1UQ
RwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D34xFMFbG02SrZvPA
Xpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgPK9Dx2hzLabjKSWJtyNBjYt1gD1iq
j6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD
VR0OBBYEFEe4zf/lb+74suwvTg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsF
AAOCAgEAACAjQTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV
cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXSIGrs/CIBKM+G
uIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2HJLw5QY33KbmkJs4j1xrG0aG
Q0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsaO5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8
ZORK15FTAaggiG6cX0S5y2CBNOxv033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2
KSb12tjE8nVhz36udmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz
6MkEkbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg413OEMXbug
UZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvDu79leNKGef9JOxqDDPDe
eOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq4/g7u9xN12TyUb7mqqta6THuBrxzvxNi
Cp/HuZc=
-----END CERTIFICATE-----

T-TeleSec GlobalRoot Class 3
============================
-----BEGIN CERTIFICATE-----
MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM
IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU
cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgx
MDAxMTAyOTU2WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz
dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD
ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN8ELg63iIVl6bmlQdTQyK
9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/RLyTPWGrTs0NvvAgJ1gORH8EGoel15YU
NpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZF
iP0Zf3WHHx+xGwpzJFu5ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W
0eDrXltMEnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGjQjBA
MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1A/d2O2GCahKqGFPr
AyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOyWL6ukK2YJ5f+AbGwUgC4TeQbIXQb
fsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzT
ucpH9sry9uetuUg/vBa3wW306gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7h
P0HHRwA11fXT91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml
e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4pTpPDpFQUWw==
-----END CERTIFICATE-----

D-TRUST Root Class 3 CA 2 2009
==============================
-----BEGIN CERTIFICATE-----
MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQK
DAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTAe
Fw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NThaME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxE
LVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOAD
ER03UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42tSHKXzlA
BF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9RySPocq60vFYJfxLLHLGv
KZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsMlFqVlNpQmvH/pStmMaTJOKDfHR+4CS7z
p+hnUquVH+BGPtikw8paxTGA6Eian5Rp/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUC
AwEAAaOCARowggEWMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ
4PGEMA4GA1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVjdG9y
eS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUyMENBJTIwMiUyMDIw
MDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRlcmV2b2NhdGlvbmxpc3QwQ6BBoD+G
PWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAw
OS5jcmwwDQYJKoZIhvcNAQELBQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm
2H6NMLVwMeniacfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0
o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4KzCUqNQT4YJEV
dT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8PIWmawomDeCTmGCufsYkl4ph
X5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3YJohw1+qRzT65ysCQblrGXnRl11z+o+I=
-----END CERTIFICATE-----

D-TRUST Root Class 3 CA 2 EV 2009
=================================
-----BEGIN CERTIFICATE-----
MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK
DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw
OTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUwNDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQK
DAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAw
OTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfS
egpnljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM03TP1YtHh
zRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6ZqQTMFexgaDbtCHu39b+T
7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lRp75mpoo6Kr3HGrHhFPC+Oh25z1uxav60
sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure35
11H3a6UCAwEAAaOCASQwggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyv
cop9NteaHNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFwOi8v
ZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xhc3MlMjAzJTIwQ0El
MjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1ERT9jZXJ0aWZpY2F0ZXJldm9jYXRp
b25saXN0MEagRKBChkBodHRwOi8vd3d3LmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xh
c3NfM19jYV8yX2V2XzIwMDkuY3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+
PPoeUSbrh/Yp3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05
nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNFCSuGdXzfX2lX
ANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7naxpeG0ILD5EJt/rDiZE4OJudA
NCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqXKVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVv
w9y4AyHqnxbxLFS1
-----END CERTIFICATE-----

CA Disig Root R2
================
-----BEGIN CERTIFICATE-----
MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNVBAYTAlNLMRMw
EQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMuMRkwFwYDVQQDExBDQSBEaXNp
ZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQyMDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sx
EzARBgNVBAcTCkJyYXRpc2xhdmExEzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERp
c2lnIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbC
w3OeNcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNHPWSb6Wia
xswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3Ix2ymrdMxp7zo5eFm1tL7
A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbeQTg06ov80egEFGEtQX6sx3dOy1FU+16S
GBsEWmjGycT6txOgmLcRK7fWV8x8nhfRyyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqV
g8NTEQxzHQuyRpDRQjrOQG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa
5Beny912H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJQfYE
koopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUDi/ZnWejBBhG93c+A
Ak9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORsnLMOPReisjQS1n6yqEm70XooQL6i
Fh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNV
HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5u
Qu0wDQYJKoZIhvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM
tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqfGopTpti72TVV
sRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkblvdhuDvEK7Z4bLQjb/D907Je
dR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W8
1k/BfDxujRNt+3vrMNDcTa/F1balTFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjx
mHHEt38OFdAlab0inSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01
utI3gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18DrG5gPcFw0
sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3OszMOl6W8KjptlwlCFtaOg
UxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8xL4ysEr3vQCj8KWefshNPZiTEUxnpHikV
7+ZtsH8tZ/3zbBt1RqPlShfppNcL
-----END CERTIFICATE-----

ACCVRAIZ1
=========
-----BEGIN CERTIFICATE-----
MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UEAwwJQUNDVlJB
SVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQswCQYDVQQGEwJFUzAeFw0xMTA1
MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQBgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwH
UEtJQUNDVjENMAsGA1UECgwEQUNDVjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4IC
DwAwggIKAoICAQCbqau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gM
jmoYHtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWoG2ioPej0
RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpAlHPrzg5XPAOBOp0KoVdD
aaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhrIA8wKFSVf+DuzgpmndFALW4ir50awQUZ
0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDG
WuzndN9wrqODJerWx5eHk6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs7
8yM2x/474KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMOm3WR
5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpacXpkatcnYGMN285J
9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPluUsXQA+xtrn13k/c4LOsOxFwYIRK
Q26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYIKwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRw
Oi8vd3d3LmFjY3YuZXMvZmlsZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEu
Y3J0MB8GCCsGAQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2
VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeTVfZW6oHlNsyM
Hj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIGCCsGAQUFBwICMIIBFB6CARAA
QQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUAcgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBh
AO0AegAgAGQAZQAgAGwAYQAgAEEAQwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUA
YwBuAG8AbABvAGcA7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBj
AHQAcgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAAQwBQAFMA
IABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUAczAwBggrBgEFBQcCARYk
aHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2MuaHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0
dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRtaW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2
MV9kZXIuY3JsMA4GA1UdDwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZI
hvcNAQEFBQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdpD70E
R9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gUJyCpZET/LtZ1qmxN
YEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+mAM/EKXMRNt6GGT6d7hmKG9Ww7Y49
nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepDvV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJ
TS+xJlsndQAJxGJ3KQhfnlmstn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3
sCPdK6jT2iWH7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h
I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szAh1xA2syVP1Xg
Nce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xFd3+YJ5oyXSrjhO7FmGYvliAd
3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2HpPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3p
EfbRD0tVNEYqi4Y7
-----END CERTIFICATE-----

TWCA Global Root CA
===================
-----BEGIN CERTIFICATE-----
MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcxEjAQBgNVBAoT
CVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMTVFdDQSBHbG9iYWwgUm9vdCBD
QTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQK
EwlUQUlXQU4tQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3Qg
Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2C
nJfF10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz0ALfUPZV
r2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfChMBwqoJimFb3u/Rk28OKR
Q4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbHzIh1HrtsBv+baz4X7GGqcXzGHaL3SekV
tTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1W
KKD+u4ZqyPpcC1jcxkt2yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99
sy2sbZCilaLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYPoA/p
yJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQABDzfuBSO6N+pjWxn
kjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcEqYSjMq+u7msXi7Kx/mzhkIyIqJdI
zshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMC
AQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6g
cFGn90xHNcgL1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn
LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WFH6vPNOw/KP4M
8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNoRI2T9GRwoD2dKAXDOXC4Ynsg
/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlg
lPx4mI88k1HtQJAH32RjJMtOcQWh15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryP
A9gK8kxkRr05YuWW6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3m
i4TWnsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5jwa19hAM8
EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWzaGHQRiapIVJpLesux+t3
zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmyKwbQBM0=
-----END CERTIFICATE-----

TeliaSonera Root CA v1
======================
-----BEGIN CERTIFICATE-----
MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAwNzEUMBIGA1UE
CgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJvb3QgQ0EgdjEwHhcNMDcxMDE4
MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYDVQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwW
VGVsaWFTb25lcmEgUm9vdCBDQSB2MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+
6yfwIaPzaSZVfp3FVRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA
3GV17CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+XZ75Ljo1k
B1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+/jXh7VB7qTCNGdMJjmhn
Xb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxH
oLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkmdtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3
F0fUTPHSiXk+TT2YqGHeOh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJ
oWjiUIMusDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4pgd7
gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fsslESl1MpWtTwEhDc
TwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQarMCpgKIv7NHfirZ1fpoeDVNAgMB
AAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qW
DNXr+nuqF+gTEjANBgkqhkiG9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNm
zqjMDfz1mgbldxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx
0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1TjTQpgcmLNkQfW
pb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBedY2gea+zDTYa4EzAvXUYNR0PV
G6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpc
c41teyWRyu5FrgZLAMzTsVlQ2jqIOylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOT
JsjrDNYmiLbAJM+7vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2
qReWt88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcnHL/EVlP6
Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVxSK236thZiNSQvxaz2ems
WWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY=
-----END CERTIFICATE-----

T-TeleSec GlobalRoot Class 2
============================
-----BEGIN CERTIFICATE-----
MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoM
IlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBU
cnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgx
MDAxMTA0MDE0WhcNMzMxMDAxMjM1OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lz
dGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBD
ZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUdAqSzm1nzHoqvNK38DcLZ
SBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiCFoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/F
vudocP05l03Sx5iRUKrERLMjfTlH6VJi1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx970
2cu+fjOlbpSD8DT6IavqjnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGV
WOHAD3bZwI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGjQjBA
MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/WSA2AHmgoCJrjNXy
YdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhyNsZt+U2e+iKo4YFWz827n+qrkRk4
r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPACuvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNf
vNoBYimipidx5joifsFvHZVwIEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR
3p1m0IvVVGb6g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN
9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlPBSeOE6Fuwg==
-----END CERTIFICATE-----

Atos TrustedRoot 2011
=====================
-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UEAwwVQXRvcyBU
cnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0xMTA3MDcxNDU4
MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMMFUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsG
A1UECgwEQXRvczELMAkGA1UEBhMCREUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCV
hTuXbyo7LjvPpvMpNb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr
54rMVD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+SZFhyBH+
DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ4J7sVaE3IqKHBAUsR320
HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0Lcp2AMBYHlT8oDv3FdU9T1nSatCQujgKR
z3bFmx5VdJx4IbHwLfELn8LVlhgf8FQieowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7R
l+lwrrw7GWzbITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZ
bNshMBgGA1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB
CwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8jvZfza1zv7v1Apt+h
k6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kPDpFrdRbhIfzYJsdHt6bPWHJxfrrh
TZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pcmaHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a9
61qn8FYiqTxlVMYVqL2Gns2Dlmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G
3mB/ufNPRJLvKrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed
-----END CERTIFICATE-----

QuoVadis Root CA 1 G3
=====================
-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQELBQAwSDELMAkG
A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv
b3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJN
MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEg
RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakE
PBtVwedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWerNrwU8lm
PNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF34168Xfuw6cwI2H44g4hWf6
Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh4Pw5qlPafX7PGglTvF0FBM+hSo+LdoIN
ofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXpUhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/l
g6AnhF4EwfWQvTA9xO+oabw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV
7qJZjqlc3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/GKubX
9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSthfbZxbGL0eUQMk1f
iyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KOTk0k+17kBL5yG6YnLUlamXrXXAkg
t3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOtzCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZI
hvcNAQELBQADggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC
MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2cDMT/uFPpiN3
GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUNqXsCHKnQO18LwIE6PWThv6ct
Tr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP
+V04ikkwj+3x6xn0dxoxGE1nVGwvb2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh
3jRJjehZrJ3ydlo28hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fa
wx/kNSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNjZgKAvQU6
O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhpq1467HxpvMc7hU6eFbm0
FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFtnh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOV
hMJKzRwuJIczYOXD
-----END CERTIFICATE-----

QuoVadis Root CA 2 G3
=====================
-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQELBQAwSDELMAkG
A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv
b3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJN
MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIg
RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFh
ZiFfqq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMWn4rjyduY
NM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ymc5GQYaYDFCDy54ejiK2t
oIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+O7q414AB+6XrW7PFXmAqMaCvN+ggOp+o
MiwMzAkd056OXbxMmO7FGmh77FOm6RQ1o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+l
V0POKa2Mq1W/xPtbAd0jIaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZo
L1NesNKqIcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz8eQQ
sSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43ehvNURG3YBZwjgQQvD
6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l7ZizlWNof/k19N+IxWA1ksB8aRxh
lRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALGcC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZI
hvcNAQELBQADggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66
AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RCroijQ1h5fq7K
pVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0GaW/ZZGYjeVYg3UQt4XAoeo0L9
x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4nlv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgz
dWqTHBLmYF5vHX/JHyPLhGGfHoJE+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6X
U/IyAgkwo1jwDQHVcsaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+Nw
mNtddbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNgKCLjsZWD
zYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeMHVOyToV7BjjHLPj4sHKN
JeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4WSr2Rz0ZiC3oheGe7IUIarFsNMkd7Egr
O3jtZsSOeWmD3n+M
-----END CERTIFICATE-----

QuoVadis Root CA 3 G3
=====================
-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQELBQAwSDELMAkG
A1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAcBgNVBAMTFVF1b1ZhZGlzIFJv
b3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJN
MRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMg
RzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286
IxSR/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNuFoM7pmRL
Mon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXRU7Ox7sWTaYI+FrUoRqHe
6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+cra1AdHkrAj80//ogaX3T7mH1urPnMNA3
I4ZyYUUpSFlob3emLoG+B01vr87ERRORFHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3U
VDmrJqMz6nWB2i3ND0/kA9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f7
5li59wzweyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634RylsSqi
Md5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBpVzgeAVuNVejH38DM
dyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0QA4XN8f+MFrXBsj6IbGB/kE+V9/Yt
rQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZI
hvcNAQELBQADggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px
KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnIFUBhynLWcKzS
t/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5WvvoxXqA/4Ti2Tk08HS6IT7SdEQ
TXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFgu/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9Du
DcpmvJRPpq3t/O5jrFc/ZSXPsoaP0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGib
Ih6BJpsQBJFxwAYf3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmD
hPbl8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+DhcI00iX
0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HNPlopNLk9hM6xZdRZkZFW
dSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ywaZWWDYWGWVjUTR939+J399roD1B0y2
PpxxVJkES/1Y+Zj0
-----END CERTIFICATE-----

DigiCert Assured ID Root G2
===========================
-----BEGIN CERTIFICATE-----
MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQw
IgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgw
MTE1MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQL
ExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIw
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSAn61UQbVH
35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4HteccbiJVMWWXvdMX0h5i89vq
bFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9HpEgjAALAcKxHad3A2m67OeYfcgnDmCXRw
VWmvo2ifv922ebPynXApVfSr/5Vh88lAbx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OP
YLfykqGxvYmJHzDNw6YuYjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+Rn
lTGNAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTO
w0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPIQW5pJ6d1Ee88hjZv
0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I0jJmwYrA8y8678Dj1JGG0VDjA9tz
d29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4GnilmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAW
hsI6yLETcDbYz+70CjTVW0z9B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0M
jomZmWzwPDCvON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo
IhNzbM8m9Yop5w==
-----END CERTIFICATE-----

DigiCert Assured ID Root G3
===========================
-----BEGIN CERTIFICATE-----
MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV
UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYD
VQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1
MTIwMDAwWjBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQ
BgcqhkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJfZn4f5dwb
RXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17QRSAPWXYQ1qAk8C3eNvJs
KTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgF
UaFNN6KDec6NHSrkhDAKBggqhkjOPQQDAwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5Fy
YZ5eEJJZVrmDxxDnOOlYJjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy
1vUhZscv6pZjamVFkpUBtA==
-----END CERTIFICATE-----

DigiCert Global Root G2
=======================
-----BEGIN CERTIFICATE-----
MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAw
HgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUx
MjAwMDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3
dy5kaWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI2/Ou8jqJ
kTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx1x7e/dfgy5SDN67sH0NO
3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQq2EGnI/yuum06ZIya7XzV+hdG82MHauV
BJVJ8zUtluNJbd134/tJS7SsVQepj5WztCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyM
UNGPHgm+F6HmIcr9g+UQvIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQAB
o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV5uNu
5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY1Yl9PMWLSn/pvtsr
F9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4NeF22d+mQrvHRAiGfzZ0JFrabA0U
WTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NGFdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBH
QRFXGU7Aj64GxJUTFy8bJZ918rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/
iyK5S9kJRaTepLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
MrY=
-----END CERTIFICATE-----

DigiCert Global Root G3
=======================
-----BEGIN CERTIFICATE-----
MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQswCQYDVQQGEwJV
UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSAwHgYD
VQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAw
MDBaMGExCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5k
aWdpY2VydC5jb20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0C
AQYFK4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FGfp4tn+6O
YwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPOZ9wj/wMco+I+o0IwQDAP
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNp
Yim8S8YwCgYIKoZIzj0EAwMDaAAwZQIxAK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y
3maTD/HMsQmP3Wyr+mt/oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34
VOKa5Vt8sycX
-----END CERTIFICATE-----

DigiCert Trusted Root G4
========================
-----BEGIN CERTIFICATE-----
MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEw
HwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1
MTIwMDAwWjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0G
CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3yithZwuEp
pz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9o
k3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7Fsa
vOvJz82sNEBfsXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGY
QJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6
MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCiEhtm
mnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7
f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFH
dL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8
oR7FwI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud
DwEB/wQEAwIBhjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD
ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2SV1EY+CtnJYY
ZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd+SeuMIW59mdNOj6PWTkiU0Tr
yF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWcfFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy
7zBZLq7gcfJW5GqXb5JQbZaNaHqasjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iah
ixTXTBmyUEFxPT9NcCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN
5r5N0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie4u1Ki7wb
/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mIr/OSmbaz5mEP0oUA51Aa
5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tK
G48BtieVU+i2iW1bvGjUI+iLUaJW+fCmgKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP
82Z+
-----END CERTIFICATE-----

COMODO RSA Certification Authority
==================================
-----BEGIN CERTIFICATE-----
MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCBhTELMAkGA1UE
BhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgG
A1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkwHhcNMTAwMTE5MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMC
R0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UE
ChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBB
dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR6FSS0gpWsawNJN3Fz0Rn
dJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8Xpz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZ
FGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+
5eNu/Nio5JIk2kNrYrhV/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pG
x8cgoLEfZd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z+pUX
2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7wqP/0uK3pN/u6uPQL
OvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZahSL0896+1DSJMwBGB7FY79tOi4lu3
sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVICu9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+C
GCe01a60y1Dma/RMhnEw6abfFobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5
WdYgGq/yapiqcrxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E
FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w
DQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvlwFTPoCWOAvn9sKIN9SCYPBMt
rFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+
nq6PK7o9mfjYcwlYRm6mnPTXJ9OV2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSg
tZx8jb8uk2IntznaFxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwW
sRqZCuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiKboHGhfKp
pC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmckejkk9u+UJueBPSZI9FoJA
zMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yLS0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHq
ZJx64SIDqZxubw5lT2yHh17zbqD5daWbQOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk52
7RH89elWsn2/x20Kk4yl0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7I
LaZRfyHBNVOFBkpdn627G190
-----END CERTIFICATE-----

USERTrust RSA Certification Authority
=====================================
-----BEGIN CERTIFICATE-----
MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCBiDELMAkGA1UE
BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK
ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UE
BhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQK
ExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCAEmUXNg7D2wiz
0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2j
Y0K2dvKpOyuR+OJv0OwWIJAJPuLodMkYtJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFn
RghRy4YUVD+8M/5+bJz/Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O
+T23LLb2VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT79uq
/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6c0Plfg6lZrEpfDKE
Y1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmTYo61Zs8liM2EuLE/pDkP2QKe6xJM
lXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97lc6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8
yexDJtC/QV9AqURE9JnnV4eeUB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+
eLf8ZxXhyVeEHg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd
BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF
MAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPFUp/L+M+ZBn8b2kMVn54CVVeW
FPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KOVWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ
7l8wXEskEVX/JJpuXior7gtNn3/3ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQ
Eg9zKC7F4iRO/Fjs8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM
8WcRiQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYzeSf7dNXGi
FSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZXHlKYC6SQK5MNyosycdi
yA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9c
J2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRBVXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGw
sAvgnEzDHNb842m1R0aBL6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gx
Q+6IHdfGjjxDah2nGN59PRbxYvnKkKj9
-----END CERTIFICATE-----

USERTrust ECC Certification Authority
=====================================
-----BEGIN CERTIFICATE-----
MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDELMAkGA1UEBhMC
VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU
aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkwHhcNMTAwMjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMC
VVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU
aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqfloI+d61SRvU8Za2EurxtW2
0eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinngo4N+LZfQYcTxmdwlkWOrfzCjtHDix6Ez
nPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0GA1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNV
HQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBB
HU6+4WMBzzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbWRNZu
9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=
-----END CERTIFICATE-----

GlobalSign ECC Root CA - R5
===========================
-----BEGIN CERTIFICATE-----
MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEkMCIGA1UECxMb
R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD
EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb
R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD
EwpHbG9iYWxTaWduMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6
SFkc8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8kehOvRnkmS
h5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd
BgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYIKoZIzj0EAwMDaAAwZQIxAOVpEslu28Yx
uglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7
yFz9SO8NdCKoCOJuxUnOxwy8p2Fp8fc74SrL+SvzZpA3
-----END CERTIFICATE-----

IdenTrust Commercial Root CA 1
==============================
-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBKMQswCQYDVQQG
EwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBS
b290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQwMTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzES
MBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENB
IDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ld
hNlT3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU+ehcCuz/
mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gpS0l4PJNgiCL8mdo2yMKi
1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1bVoE/c40yiTcdCMbXTMTEl3EASX2MN0C
XZ/g1Ue9tOsbobtJSdifWwLziuQkkORiT0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl
3ZBWzvurpWCdxJ35UrCLvYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzy
NeVJSQjKVsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZKdHzV
WYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHTc+XvvqDtMwt0viAg
xGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hvl7yTmvmcEpB4eoCHFddydJxVdHix
uuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5NiGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMC
AQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZI
hvcNAQELBQADggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH
6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwtLRvM7Kqas6pg
ghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93nAbowacYXVKV7cndJZ5t+qnt
ozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmV
YjzlVYA211QC//G5Xc7UI2/YRYRKW2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUX
feu+h1sXIFRRk0pTAwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/ro
kTLql1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG4iZZRHUe
2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZmUlO+KWA2yUPHGNiiskz
Z2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7R
cGzM7vRX+Bi6hG6H
-----END CERTIFICATE-----

IdenTrust Public Sector Root CA 1
=================================
-----BEGIN CERTIFICATE-----
MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBNMQswCQYDVQQG
EwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3Rv
ciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcNMzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJV
UzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBS
b290IENBIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTy
P4o7ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGyRBb06tD6
Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlSbdsHyo+1W/CD80/HLaXI
rcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF/YTLNiCBWS2ab21ISGHKTN9T0a9SvESf
qy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoS
mJxZZoY+rfGwyj4GD3vwEUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFn
ol57plzy9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9VGxyh
LrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ2fjXctscvG29ZV/v
iDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsVWaFHVCkugyhfHMKiq3IXAAaOReyL
4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gDW/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8B
Af8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMw
DQYJKoZIhvcNAQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj
t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHVDRDtfULAj+7A
mgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9TaDKQGXSc3z1i9kKlT/YPyNt
GtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8GlwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFt
m6/n6J91eEyrRjuazr8FGF1NFTwWmhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMx
NRF4eKLg6TCMf4DfWN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4
Mhn5+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJtshquDDI
ajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhAGaQdp/lLQzfcaFpPz+vC
ZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ
3Wl9af0AVqW3rLatt8o+Ae+c
-----END CERTIFICATE-----

Entrust Root Certification Authority - G2
=========================================
-----BEGIN CERTIFICATE-----
MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMCVVMxFjAUBgNV
BAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVy
bXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ug
b25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIw
HhcNMDkwNzA3MTcyNTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoT
DUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMx
OTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25s
eTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP
/vaCeb9zYQYKpSfYs1/TRU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXz
HHfV1IWNcCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hWwcKU
s/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1U1+cPvQXLOZprE4y
TGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0jaWvYkxN4FisZDQSA/i2jZRjJKRx
AgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ6
0B7vfec7aVHUbI2fkBJmqzANBgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5Z
iXMRrEPR9RP/jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ
Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v1fN2D807iDgi
nWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4RnAuknZoh8/CbCzB428Hch0P+
vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmHVHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xO
e4pIb4tF9g==
-----END CERTIFICATE-----

Entrust Root Certification Authority - EC1
==========================================
-----BEGIN CERTIFICATE-----
MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkGA1UEBhMCVVMx
FjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVn
YWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXpl
ZCB1c2Ugb25seTEzMDEGA1UEAxMqRW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5
IC0gRUMxMB4XDTEyMTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYw
FAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2Fs
LXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQg
dXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt
IEVDMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHy
AsWfoPZb1YsGGYZPUxBtByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef
9eNi1KlHBz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
FLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVCR98crlOZF7ZvHH3h
vxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nXhTcGtXsI/esni0qU+eH6p44mCOh8
kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G
-----END CERTIFICATE-----

CFCA EV ROOT
============
-----BEGIN CERTIFICATE-----
MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJDTjEwMC4GA1UE
CgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNB
IEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkxMjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEw
MC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQD
DAxDRkNBIEVWIFJPT1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnV
BU03sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpLTIpTUnrD
7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5/ZOkVIBMUtRSqy5J35DN
uF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp7hZZLDRJGqgG16iI0gNyejLi6mhNbiyW
ZXvKWfry4t3uMCz7zEasxGPrb382KzRzEpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7
xzbh72fROdOXW3NiGUgthxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9f
py25IGvPa931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqotaK8K
gWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNgTnYGmE69g60dWIol
hdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfVPKPtl8MeNPo4+QgO48BdK4PRVmrJ
tqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hvcWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAf
BgNVHSMEGDAWgBTj/i39KNALtbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB
/wQEAwIBBjAdBgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB
ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObTej/tUxPQ4i9q
ecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdLjOztUmCypAbqTuv0axn96/Ua
4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBSESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sG
E5uPhnEFtC+NiWYzKXZUmhH4J/qyP5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfX
BDrDMlI1Dlb4pd19xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjn
aH9dCi77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN5mydLIhy
PDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe/v5WOaHIz16eGWRGENoX
kbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+ZAAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3C
ekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su
-----END CERTIFICATE-----

OISTE WISeKey Global Root GB CA
===============================
-----BEGIN CERTIFICATE-----
MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQG
EwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl
ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAw
MzJaFw0zOTEyMDExNTEwMzFaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYD
VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEds
b2JhbCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3HEokKtaX
scriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGxWuR51jIjK+FTzJlFXHtP
rby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk
9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNku7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4o
Qnc/nSMbsrY9gBQHTC5P99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvg
GUpuuy9rM2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB
/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZI
hvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrghcViXfa43FK8+5/ea4n32cZiZBKpD
dHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0
VQreUGdNZtGn//3ZwLWoo4rOZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEui
HZeeevJuQHHfaPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic
Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM=
-----END CERTIFICATE-----

SZAFIR ROOT CA2
===============
-----BEGIN CERTIFICATE-----
MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQELBQAwUTELMAkG
A1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6ZW5pb3dhIFMuQS4xGDAWBgNV
BAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkwNzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJ
BgNVBAYTAlBMMSgwJgYDVQQKDB9LcmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYD
VQQDDA9TWkFGSVIgUk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5Q
qEvNQLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT3PSQ1hNK
DJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw3gAeqDRHu5rr/gsUvTaE
2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr63fE9biCloBK0TXC5ztdyO4mTp4CEHCdJ
ckm1/zuVnsHMyAHs6A6KCpbns6aH5db5BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwi
ieDhZNRnvDF5YTy7ykHNXGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P
AQH/BAQDAgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsFAAOC
AQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw8PRBEew/R40/cof5
O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOGnXkZ7/e7DDWQw4rtTw/1zBLZpD67
oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCPoky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul
4+vJhaAlIDf7js4MNIThPIGyd05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6
+/NNIxuZMzSgLvWpCz/UXeHPhJ/iGcJfitYgHuNztw==
-----END CERTIFICATE-----

Certum Trusted Network CA 2
===========================
-----BEGIN CERTIFICATE-----
MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCBgDELMAkGA1UE
BhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAlBgNVBAsTHkNlcnR1
bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29y
ayBDQSAyMCIYDzIwMTExMDA2MDgzOTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQ
TDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENl
cnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENB
IDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWADGSdhhuWZGc/IjoedQF9
7/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+o
CgCXhVqqndwpyeI1B+twTUrWwbNWuKFBOJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40b
Rr5HMNUuctHFY9rnY3lEfktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2p
uTRZCr+ESv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1mo130
GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02isx7QBlrd9pPPV3WZ
9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOWOZV7bIBaTxNyxtd9KXpEulKkKtVB
Rgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgezTv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pye
hizKV/Ma5ciSixqClnrDvFASadgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vM
BhBgu4M1t15n3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZI
hvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQF/xlhMcQSZDe28cmk4gmb3DW
Al45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTfCVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuA
L55MYIR4PSFk1vtBHxgP58l1cb29XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMo
clm2q8KMZiYcdywmdjWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tM
pkT/WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jbAoJnwTnb
w3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksqP/ujmv5zMnHCnsZy4Ypo
J/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Kob7a6bINDd82Kkhehnlt4Fj1F4jNy3eFm
ypnTycUm/Q1oBEauttmbjL4ZvrHG8hnjXALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLX
is7VmFxWlgPF7ncGNf/P5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7
zAYspsbiDrW5viSP
-----END CERTIFICATE-----

Hellenic Academic and Research Institutions RootCA 2015
=======================================================
-----BEGIN CERTIFICATE-----
MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcT
BkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0
aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl
YXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAx
MTIxWjCBpjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMg
QWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNV
BAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9vdENBIDIw
MTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDC+Kk/G4n8PDwEXT2QNrCROnk8Zlrv
bTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+eh
iGsxr/CL0BgzuNtFajT0AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+
6PAQZe104S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06CojXd
FPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV9Cz82XBST3i4vTwr
i5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrDgfgXy5I2XdGj2HUb4Ysn6npIQf1F
GQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2
fu/Z8VFRfS0myGlZYeCsargqNhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9mu
iNX6hME6wGkoLfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc
Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVdctA4GGqd83EkVAswDQYJKoZI
hvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0IXtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+
D1hYc2Ryx+hFjtyp8iY/xnmMsVMIM4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrM
d/K4kPFox/la/vot9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+y
d+2VZ5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/eaj8GsGsVn
82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnhX9izjFk0WaSrT2y7Hxjb
davYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQl033DlZdwJVqwjbDG2jJ9SrcR5q+ss7F
Jej6A7na+RZukYT1HCjI/CbM1xyQVqdfbzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVt
J94Cj8rDtSvK6evIIVM4pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGa
JI7ZjnHKe7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0vm9q
p/UsQu0yrbYhnr68
-----END CERTIFICATE-----

Hellenic Academic and Research Institutions ECC RootCA 2015
===========================================================
-----BEGIN CERTIFICATE-----
MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzANBgNVBAcTBkF0
aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u
cyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj
aCBJbnN0aXR1dGlvbnMgRUNDIFJvb3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEw
MzcxMlowgaoxCzAJBgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmlj
IEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUQwQgYD
VQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIEVDQyBSb290
Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKgQehLgoRc4vgxEZmGZE4JJS+dQS8KrjVP
dJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJajq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoK
Vlp8aQuqgAkkbH7BRqNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0O
BBYEFLQiC4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaeplSTA
GiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7SofTUwJCA3sS61kFyjn
dc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR
-----END CERTIFICATE-----

ISRG Root X1
============
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAwTzELMAkGA1UE
BhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2VhcmNoIEdyb3VwMRUwEwYDVQQD
EwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQG
EwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMT
DElTUkcgUm9vdCBYMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54r
Vygch77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+0TM8ukj1
3Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6UA5/TR5d8mUgjU+g4rk8K
b4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sWT8KOEUt+zwvo/7V3LvSye0rgTBIlDHCN
Aymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyHB5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ
4Q7e2RCOFvu396j3x+UCB5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf
1b0SHzUvKBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWnOlFu
hjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTnjh8BCNAw1FtxNrQH
usEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbwqHyGO0aoSCqI3Haadr8faqU9GY/r
OPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CIrU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4G
A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY
9umbbjANBgkqhkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ3BebYhtF8GaV
0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KKNFtY2PwByVS5uCbMiogziUwt
hDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJw
TdwJx4nLCgdNbOhdjsnvzqvHu7UrTkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nx
e5AW0wdeRlN8NwdCjNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZA
JzVcoyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq4RgqsahD
YVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPAmRGunUHBcnWEvgJBQl9n
JEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57demyPxgcYxn/eR44/KJ4EBs+lVDR3veyJ
m+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----

AC RAIZ FNMT-RCM
================
-----BEGIN CERTIFICATE-----
MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsxCzAJBgNVBAYT
AkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTAeFw0wODEw
MjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJD
TTEZMBcGA1UECwwQQUMgUkFJWiBGTk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
ggIBALpxgHpMhm5/yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcf
qQgfBBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAzWHFctPVr
btQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxFtBDXaEAUwED653cXeuYL
j2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z374jNUUeAlz+taibmSXaXvMiwzn15Cou
08YfxGyqxRxqAQVKL9LFwag0Jl1mpdICIfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mw
WsXmo8RZZUc1g16p6DULmbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnT
tOmlcYF7wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peSMKGJ
47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2ZSysV4999AeU14EC
ll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMetUqIJ5G+GR4of6ygnXYMgrwTJbFaa
i0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE
FPd9xf3E6Jobd2Sn9R2gzL+HYJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1o
dHRwOi8vd3d3LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD
nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1RXxlDPiyN8+s
D8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYMLVN0V2Ue1bLdI4E7pWYjJ2cJ
j+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrT
Qfv6MooqtyuGC2mDOL7Nii4LcK2NJpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW
+YJF1DngoABd15jmfZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7
Ixjp6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp1txyM/1d
8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B9kiABdcPUXmsEKvU7ANm
5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wokRqEIr9baRRmW1FMdW4R58MD3R++Lj8UG
rp1MYp3/RgT408m2ECVAdf4WqslKYIYvuu8wd+RU4riEmViAqhOLUTpPSPaLtrM=
-----END CERTIFICATE-----

Amazon Root CA 1
================
-----BEGIN CERTIFICATE-----
MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsFADA5MQswCQYD
VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAxMB4XDTE1
MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv
bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBALJ4gHHKeNXjca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgH
FzZM9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qwIFAGbHrQ
gLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6VOujw5H5SNz/0egwLX0t
dHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L93FcXmn/6pUCyziKrlA4b9v7LWIbxcce
VOF34GfID5yHI9Y/QCB/IIDEgEw+OyQmjgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB
/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3
DQEBCwUAA4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDIU5PM
CCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUsN+gDS63pYaACbvXy
8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vvo/ufQJVtMVT8QtPHRh8jrdkPSHCa
2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2
xJNDd2ZhwLnoQdeXeGADbkpyrqXRfboQnoZsG4q5WTP468SQvvG5
-----END CERTIFICATE-----

Amazon Root CA 2
================
-----BEGIN CERTIFICATE-----
MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwFADA5MQswCQYD
VQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAyMB4XDTE1
MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpv
bjEZMBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
ggIBAK2Wny2cSkxKgXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4
kHbZW0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg1dKmSYXp
N+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K8nu+NQWpEjTj82R0Yiw9
AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvd
fLC6HM783k81ds8P+HgfajZRRidhW+mez/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAEx
kv8LV/SasrlX6avvDXbR8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSS
btqDT6ZjmUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz7Mt0
Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6+XUyo05f7O0oYtlN
c/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI0u1ufm8/0i2BWSlmy5A5lREedCf+
3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSw
DPBMMPQFWAJI/TPlUq9LhONmUjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oA
A7CXDpO8Wqj2LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY
+gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kSk5Nrp+gvU5LE
YFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl7uxMMne0nxrpS10gxdr9HIcW
xkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygmbtmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQ
gj9sAq+uEjonljYE1x2igGOpm/HlurR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbW
aQbLU8uz/mtBzUF+fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoV
Yh63n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE76KlXIx3
KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H9jVlpNMKVv/1F2Rs76gi
JUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT4PsJYGw=
-----END CERTIFICATE-----

Amazon Root CA 3
================
-----BEGIN CERTIFICATE-----
MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5MQswCQYDVQQG
EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSAzMB4XDTE1MDUy
NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ
MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZB
f8ANm+gBG1bG8lKlui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjr
Zt6jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSrttvXBp43
rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkrBqWTrBqYaGFy+uGh0Psc
eGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteMYyRIHN8wfdVoOw==
-----END CERTIFICATE-----

Amazon Root CA 4
================
-----BEGIN CERTIFICATE-----
MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5MQswCQYDVQQG
EwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24gUm9vdCBDQSA0MB4XDTE1MDUy
NjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZ
MBcGA1UEAxMQQW1hem9uIFJvb3QgQ0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN
/sGKe0uoe0ZLY7Bi9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri
83BkM6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
HQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WBMAoGCCqGSM49BAMDA2gA
MGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlwCkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1
AE47xDqUEpHJWEadIRNyp4iciuRMStuW1KyLa2tJElMzrdfkviT8tQp21KW8EA==
-----END CERTIFICATE-----

TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1
=============================================
-----BEGIN CERTIFICATE-----
MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIxGDAWBgNVBAcT
D0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxpbXNlbCB2ZSBUZWtub2xvamlr
IEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0wKwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24g
TWVya2V6aSAtIEthbXUgU00xNjA0BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRp
ZmlrYXNpIC0gU3VydW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYD
VQQGEwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXllIEJpbGlt
c2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklUQUsxLTArBgNVBAsTJEth
bXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBTTTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11
IFNNIFNTTCBLb2sgU2VydGlmaWthc2kgLSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAr3UwM6q7a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y8
6Ij5iySrLqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INrN3wc
wv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2XYacQuFWQfw4tJzh0
3+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/iSIzL+aFCr2lqBs23tPcLG07xxO9
WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4fAJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQU
ZT/HiobGPN08VFw1+DrtUgxHV8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJ
KoZIhvcNAQELBQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh
AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPfIPP54+M638yc
lNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4lzwDGrpDxpa5RXI4s6ehlj2R
e37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0j
q5Rm+K37DwhuJi1/FwcJsoz7UMCflo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM=
-----END CERTIFICATE-----

GDCA TrustAUTH R5 ROOT
======================
-----BEGIN CERTIFICATE-----
MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMCQ04xMjAw
BgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8wHQYDVQQD
DBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVow
YjELMAkGA1UEBhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ
IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0B
AQEFAAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJjDp6L3TQs
AlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBjTnnEt1u9ol2x8kECK62p
OqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+uKU49tm7srsHwJ5uu4/Ts765/94Y9cnrr
pftZTqfrlYwiOXnhLQiPzLyRuEH3FMEjqcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ
9Cy5WmYqsBebnh52nUpmMUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQ
xXABZG12ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloPzgsM
R6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3GkL30SgLdTMEZeS1SZ
D2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeCjGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4
oR24qoAATILnsn8JuLwwoC8N9VKejveSswoAHQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx
9hoh49pwBiFYFIeFd3mqgnkCAwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlR
MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg
p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZmDRd9FBUb1Ov9
H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5COmSdI31R9KrO9b7eGZONn35
6ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ryL3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd
+PwyvzeG5LuOmCd+uh8W4XAR8gPfJWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQ
HtZa37dG/OaG+svgIHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBD
F8Io2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV09tL7ECQ
8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQXR4EzzffHqhmsYzmIGrv
/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrqT8p+ck0LcIymSLumoRT2+1hEmRSuqguT
aaApJUqlyyvdimYHFngVV3Eb7PVHhPOeMTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g==
-----END CERTIFICATE-----

SSL.com Root Certification Authority RSA
========================================
-----BEGIN CERTIFICATE-----
MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxDjAM
BgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24x
MTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYw
MjEyMTczOTM5WhcNNDEwMjEyMTczOTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx
EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NM
LmNvbSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcNAQEBBQAD
ggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2RxFdHaxh3a3by/ZPkPQ/C
Fp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aXqhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8
P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcCC52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/ge
oeOy3ZExqysdBP+lSgQ36YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkp
k8zruFvh/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrFYD3Z
fBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93EJNyAKoFBbZQ+yODJ
gUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVcUS4cK38acijnALXRdMbX5J+tB5O2
UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi8
1xtZPCvM8hnIk2snYxnP/Okm+Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4s
bE6x/c+cCbqiM+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV
HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4GA1UdDwEB/wQE
AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGVcpNxJK1ok1iOMq8bs3AD/CUr
dIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBcHadm47GUBwwyOabqG7B52B2ccETjit3E+ZUf
ijhDPwGFpUenPUayvOUiaPd7nNgsPgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAsl
u1OJD7OAUN5F7kR/q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjq
erQ0cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jra6x+3uxj
MxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90IH37hVZkLId6Tngr75qNJ
vTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/YK9f1JmzJBjSWFupwWRoyeXkLtoh/D1JI
Pb9s2KJELtFOt3JY04kTlf5Eq/jXixtunLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406y
wKBjYZC6VWg3dGq2ktufoYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NI
WuuA8ShYIc2wBlX7Jz9TkHCpBB5XJ7k=
-----END CERTIFICATE-----

SSL.com Root Certification Authority ECC
========================================
-----BEGIN CERTIFICATE-----
MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMCVVMxDjAMBgNV
BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xMTAv
BgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEy
MTgxNDAzWhcNNDEwMjEyMTgxNDAzWjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAO
BgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv
bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuBBAAiA2IA
BEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI7Z4INcgn64mMU1jrYor+
8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPgCemB+vNH06NjMGEwHQYDVR0OBBYEFILR
hXMw5zUE044CkvvlpNHEIejNMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTT
jgKS++Wk0cQh6M0wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCW
e+0F+S8Tkdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+gA0z
5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl
-----END CERTIFICATE-----

SSL.com EV Root Certification Authority RSA R2
==============================================
-----BEGIN CERTIFICATE-----
MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNVBAYTAlVTMQ4w
DAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9u
MTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy
MB4XDTE3MDUzMTE4MTQzN1oXDTQyMDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQI
DAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYD
VQQDDC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMIICIjAN
BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvqM0fNTPl9fb69LT3w23jh
hqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssufOePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7w
cXHswxzpY6IXFJ3vG2fThVUCAtZJycxa4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTO
Zw+oz12WGQvE43LrrdF9HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+
B6KjBSYRaZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcAb9Zh
CBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQGp8hLH94t2S42Oim
9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQVPWKchjgGAGYS5Fl2WlPAApiiECto
RHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMOpgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+Slm
JuwgUHfbSguPvuUCYHBBXtSuUDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48
+qvWBkofZ6aYMBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV
HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa49QaAJadz20Zp
qJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBWs47LCp1Jjr+kxJG7ZhcFUZh1
++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nx
Y/hoLVUE0fKNsKTPvDxeH3jnpaAgcLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2G
guDKBAdRUNf/ktUM79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDz
OFSz/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXtll9ldDz7
CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEmKf7GUmG6sXP/wwyc5Wxq
lD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKKQbNmC1r7fSOl8hqw/96bg5Qu0T/fkreR
rwU7ZcegbLHNYhLDkBvjJc40vG93drEQw/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1
hlMYegouCRw2n5H9gooiS9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX
9hwJ1C07mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w==
-----END CERTIFICATE-----

SSL.com EV Root Certification Authority ECC
===========================================
-----BEGIN CERTIFICATE-----
MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMCVVMxDjAMBgNV
BAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9TU0wgQ29ycG9yYXRpb24xNDAy
BgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYw
MjEyMTgxNTIzWhcNNDEwMjEyMTgxNTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx
EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NM
LmNvbSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB
BAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMAVIbc/R/fALhBYlzccBYy
3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1KthkuWnBaBu2+8KGwytAJKaNjMGEwHQYDVR0O
BBYEFFvKXuXe0oGqzagtZFG22XKbl+ZPMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe
5d7SgarNqC1kUbbZcpuX5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJ
N+vp1RPZytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZgh5Mm
m7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg==
-----END CERTIFICATE-----

GlobalSign Root CA - R6
=======================
-----BEGIN CERTIFICATE-----
MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEgMB4GA1UECxMX
R2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds
b2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQxMjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9i
YWxTaWduIFJvb3QgQ0EgLSBSNjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFs
U2lnbjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQss
grRIxutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1kZguSgMpE
3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxDaNc9PIrFsmbVkJq3MQbF
vuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJwLnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqM
PKq0pPbzlUoSB239jLKJz9CgYXfIWHSw1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+
azayOeSsJDa38O+2HBNXk7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05O
WgtH8wY2SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/hbguy
CLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4nWUx2OVvq+aWh2IMP
0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpYrZxCRXluDocZXFSxZba/jJvcE+kN
b7gu3GduyYsRtYQUigAZcIN5kZeR1BonvzceMgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQE
AwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNV
HSMEGDAWgBSubAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN
nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGtIxg93eFyRJa0
lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr6155wsTLxDKZmOMNOsIeDjHfrY
BzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLjvUYAGm0CuiVdjaExUd1URhxN25mW7xocBFym
Fe944Hn+Xds+qkxV/ZoVqW/hpvvfcDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr
3TsTjxKM4kEaSHpzoHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB1
0jZpnOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfspA9MRf/T
uTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+vJJUEeKgDu+6B5dpffItK
oZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+t
JDfLRVpOoERIyNiwmcUVhAn21klJwGW45hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA=
-----END CERTIFICATE-----

OISTE WISeKey Global Root GC CA
===============================
-----BEGIN CERTIFICATE-----
MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQswCQYDVQQGEwJD
SDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEo
MCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRa
Fw00MjA1MDkwOTU4MzNaMG0xCzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQL
ExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh
bCBSb290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4nieUqjFqdr
VCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4Wp2OQ0jnUsYd4XxiWD1Ab
NTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAd
BgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7TrYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0E
AwMDaAAwZQIwJsdpW9zV57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtk
AjEA2zQgMgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9
-----END CERTIFICATE-----

UCA Global G2 Root
==================
-----BEGIN CERTIFICATE-----
MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9MQswCQYDVQQG
EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBHbG9iYWwgRzIgUm9vdDAeFw0x
NjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0xCzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlU
cnVzdDEbMBkGA1UEAwwSVUNBIEdsb2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
MIICCgKCAgEAxeYrb3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmT
oni9kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzmVHqUwCoV
8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/RVogvGjqNO7uCEeBHANBS
h6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDcC/Vkw85DvG1xudLeJ1uK6NjGruFZfc8o
LTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIjtm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/
R+zvWr9LesGtOxdQXGLYD0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBe
KW4bHAyvj5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6DlNaBa
4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6iIis7nCs+dwp4wwc
OxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznPO6Q0ibd5Ei9Hxeepl2n8pndntd97
8XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O
BBYEFIHEjMz15DD/pQwIX4wVZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo
5sOASD0Ee/ojL3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5
1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl1qnN3e92mI0A
Ds0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oUb3n09tDh05S60FdRvScFDcH9
yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LVPtateJLbXDzz2K36uGt/xDYotgIVilQsnLAX
c47QN6MUPJiVAAwpBVueSUmxX8fjy88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHo
jhJi6IjMtX9Gl8CbEGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZk
bxqgDMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI+Vg7RE+x
ygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGyYiGqhkCyLmTTX8jjfhFn
RR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bXUB+K+wb1whnw0A==
-----END CERTIFICATE-----

UCA Extended Validation Root
============================
-----BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBHMQswCQYDVQQG
EwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9u
IFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMxMDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8G
A1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrs
iWogD4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvSsPGP2KxF
Rv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aopO2z6+I9tTcg1367r3CTu
eUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dksHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR
59mzLC52LqGj3n5qiAno8geK+LLNEOfic0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH
0mK1lTnj8/FtDw5lhIpjVMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KR
el7sFsLzKuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/TuDv
B0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41Gsx2VYVdWf6/wFlth
WG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs1+lvK9JKBZP8nm9rZ/+I8U6laUpS
NwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQDfwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS
3H5aBZ8eNJr34RQwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQEL
BQADggIBADaNl8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR
ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQVBcZEhrxH9cM
aVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5c6sq1WnIeJEmMX3ixzDx/BR4
dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb
+7lsq+KePRXBOy5nAliRn+/4Qh8st2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOW
F3sGPjLtx7dCvHaj2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwi
GpWOvpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2CxR9GUeOc
GMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmxcmtpzyKEC2IPrNkZAJSi
djzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbMfjKaiJUINlK73nZfdklJrX+9ZSCyycEr
dhh2n1ax
-----END CERTIFICATE-----

Certigna Root CA
================
-----BEGIN CERTIFICATE-----
MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UE
BhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAwMiA0ODE0NjMwODEwMDAzNjEZ
MBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0xMzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjda
MFoxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYz
MDgxMDAwMzYxGTAXBgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4IC
DwAwggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sOty3tRQgX
stmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9MCiBtnyN6tMbaLOQdLNyz
KNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPuI9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8
JXrJhFwLrN1CTivngqIkicuQstDuI7pmTLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16
XdG+RCYyKfHx9WzMfgIhC59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq
4NYKpkDfePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3YzIoej
wpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWTCo/1VTp2lc5ZmIoJ
lXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1kJWumIWmbat10TWuXekG9qxf5kBdI
jzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp/
/TBt2dzhauH8XwIDAQABo4IBGjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw
HQYDVR0OBBYEFBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of
1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczovL3d3d3cuY2Vy
dGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilodHRwOi8vY3JsLmNlcnRpZ25h
LmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYraHR0cDovL2NybC5kaGlteW90aXMuY29tL2Nl
cnRpZ25hcm9vdGNhLmNybDANBgkqhkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOIt
OoldaDgvUSILSo3L6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxP
TGRGHVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH60BGM+RFq
7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncBlA2c5uk5jR+mUYyZDDl3
4bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdio2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd
8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS
6Cvu5zHbugRqh5jnxV/vfaci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaY
tlu3zM63Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayhjWZS
aX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw3kAP+HwV96LOPNde
E4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0=
-----END CERTIFICATE-----

emSign Root CA - G1
===================
-----BEGIN CERTIFICATE-----
MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYDVQQGEwJJTjET
MBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRl
ZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBHMTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgx
ODMwMDBaMGcxCzAJBgNVBAYTAklOMRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVk
aHJhIFRlY2hub2xvZ2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQzf2N4aLTN
LnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO8oG0x5ZOrRkVUkr+PHB1
cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aqd7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHW
DV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhMtTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ
6DqS0hdW5TUaQBw+jSztOd9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrH
hQIDAQABo0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQDAgEG
MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31xPaOfG1vR2vjTnGs2
vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjMwiI/aTvFthUvozXGaCocV685743Q
NcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6dGNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q
+Mri/Tm3R7nrft8EI6/6nAYH6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeih
U80Bv2noWgbyRQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx
iN66zB+Afko=
-----END CERTIFICATE-----

emSign ECC Root CA - G3
=======================
-----BEGIN CERTIFICATE-----
MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQGEwJJTjETMBEG
A1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEg
MB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4
MTgzMDAwWjBrMQswCQYDVQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11
ZGhyYSBUZWNobm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g
RzMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0WXTsuwYc
58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xySfvalY8L1X44uT6EYGQIr
MgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuBzhccLikenEhjQjAOBgNVHQ8BAf8EBAMC
AQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+D
CBeQyh+KTOgNG3qxrdWBCUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7
jHvrZQnD+JbNR6iC8hZVdyR+EhCVBCyj
-----END CERTIFICATE-----

emSign Root CA - C1
===================
-----BEGIN CERTIFICATE-----
MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkGA1UEBhMCVVMx
EzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNp
Z24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UE
BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQD
ExNlbVNpZ24gUm9vdCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+up
ufGZBczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZHdPIWoU/
Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH3DspVpNqs8FqOp099cGX
OFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvHGPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4V
I5b2P/AgNBbeCsbEBEV5f6f9vtKppa+cxSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleooms
lMuoaJuvimUnzYnu3Yy1aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+
XJGFehiqTbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQAD
ggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87/kOXSTKZEhVb3xEp
/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4kqNPEjE2NuLe/gDEo2APJ62gsIq1
NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrGYQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9
wC68AivTxEDkigcxHpvOJpkT+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQ
BmIMMMAVSKeoWXzhriKi4gp6D/piq1JM4fHfyr6DDUI=
-----END CERTIFICATE-----

emSign ECC Root CA - C3
=======================
-----BEGIN CERTIFICATE-----
MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQGEwJVUzETMBEG
A1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMxIDAeBgNVBAMTF2VtU2lnbiBF
Q0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAwMFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UE
BhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQD
ExdlbVNpZ24gRUNDIFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd
6bciMK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4OjavtisIGJAnB9
SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0OBBYEFPtaSNCAIEDyqOkA
B2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gA
MGUCMQC02C8Cif22TGK6Q04ThHK1rt0c3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwU
ZOR8loMRnLDRWmFLpg9J0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ==
-----END CERTIFICATE-----

Hongkong Post Root CA 3
=======================
-----BEGIN CERTIFICATE-----
MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQELBQAwbzELMAkG
A1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJSG9uZyBLb25nMRYwFAYDVQQK
Ew1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25na29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2
MDMwMjI5NDZaFw00MjA2MDMwMjI5NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtv
bmcxEjAQBgNVBAcTCUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMX
SG9uZ2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz
iNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFOdem1p+/l6TWZ5Mwc50tf
jTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mIVoBc+L0sPOFMV4i707mV78vH9toxdCim
5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOe
sL4jpNrcyCse2m5FHomY2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj
0mRiikKYvLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+TtbNe/
JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZbx39ri1UbSsUgYT2u
y1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+l2oBlKN8W4UdKjk60FSh0Tlxnf0h
+bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YKTE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsG
xVd7GYYKecsAyVKvQv83j+GjHno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwID
AQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e
i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEwDQYJKoZIhvcN
AQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG7BJ8dNVI0lkUmcDrudHr9Egw
W62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCkMpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWld
y8joRTnU+kLBEUx3XZL7av9YROXrgZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov
+BS5gLNdTaqX4fnkGMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDc
eqFS3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJmOzj/2ZQw
9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+l6mc1X5VTMbeRRAc6uk7
nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6cJfTzPV4e0hz5sy229zdcxsshTrD3mUcY
hcErulWuBurQB7Lcq9CClnXO0lD+mefPL5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB
60PZ2Pierc+xYw5F9KBaLJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fq
dBb9HxEGmpv0
-----END CERTIFICATE-----

Microsoft ECC Root Certificate Authority 2017
=============================================
-----BEGIN CERTIFICATE-----
MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQswCQYDVQQGEwJV
UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQgRUND
IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4
MjMxNjA0WjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw
NAYDVQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQ
BgcqhkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZRogPZnZH6
thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYbhGBKia/teQ87zvH2RPUB
eMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTIy5lycFIM
+Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlf
Xu5gKcs68tvWMoQZP3zVL8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaR
eNtUjGUBiudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M=
-----END CERTIFICATE-----

Microsoft RSA Root Certificate Authority 2017
=============================================
-----BEGIN CERTIFICATE-----
MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBlMQswCQYDVQQG
EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNyb3NvZnQg
UlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIw
NzE4MjMwMDIzWjBlMQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u
MTYwNAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcw
ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZNt9GkMml
7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0ZdDMbRnMlfl7rEqUrQ7e
S0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw7
1VdyvD/IybLeS2v4I2wDwAW9lcfNcztmgGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+
dkC0zVJhUXAoP8XFWvLJjEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49F
yGcohJUcaDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaGYaRS
MLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6W6IYZVcSn2i51BVr
lMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4KUGsTuqwPN1q3ErWQgR5WrlcihtnJ
0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH+FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJ
ClTUFLkqqNfs+avNJVgyeY+QW5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYw
DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC
NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZCLgLNFgVZJ8og
6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OCgMNPOsduET/m4xaRhPtthH80
dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk
+ONVFT24bcMKpBLBaYVu32TxU5nhSnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex
/2kskZGT4d9Mozd2TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDy
AmH3pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGRxpl/j8nW
ZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiAppGWSZI1b7rCoucL5mxAyE
7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKT
c0QWbej09+CVgI+WXTik9KveCjCHk9hNAHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D
5KbvtwEwXlGjefVwaaZBRA+GsCyRxj3qrg+E
-----END CERTIFICATE-----

e-Szigno Root CA 2017
=====================
-----BEGIN CERTIFICATE-----
MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNVBAYTAkhVMREw
DwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUt
MjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJvb3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZa
Fw00MjA4MjIxMjA3MDZaMHExCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UE
CgwNTWljcm9zZWMgTHRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3pp
Z25vIFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtvxie+RJCx
s1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+HWyx7xf58etqjYzBhMA8G
A1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSHERUI0arBeAyxr87GyZDv
vzAEwDAfBgNVHSMEGDAWgBSHERUI0arBeAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEA
tVfd14pVCzbhhkT61NlojbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxO
svxyqltZ+efcMQ==
-----END CERTIFICATE-----

certSIGN Root CA G2
===================
-----BEGIN CERTIFICATE-----
MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNVBAYTAlJPMRQw
EgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjAeFw0xNzAy
MDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJBgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lH
TiBTQTEcMBoGA1UECxMTY2VydFNJR04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP
ADCCAgoCggIBAMDFdRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05
N0IwvlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZuIt4Imfk
abBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhpn+Sc8CnTXPnGFiWeI8Mg
wT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKscpc/I1mbySKEwQdPzH/iV8oScLumZfNp
dWO9lfsbl83kqK/20U6o2YpxJM02PbyWxPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91Qqh
ngLjYl/rNUssuHLoPj1PrCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732
jcZZroiFDsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fxDTvf
95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgyLcsUDFDYg2WD7rlc
z8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6CeWRgKRM+o/1Pcmqr4tTluCRVLERL
iohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1Ud
DgQWBBSCIS1mxteg4BXrzkwJd8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOB
ywaK8SJJ6ejqkX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC
b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQlqiCA2ClV9+BB
/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0OJD7uNGzcgbJceaBxXntC6Z5
8hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+cNywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5
BiKDUyUM/FHE5r7iOZULJK2v0ZXkltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklW
atKcsWMy5WHgUyIOpwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tU
Sxfj03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZkPuXaTH4M
NMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE1LlSVHJ7liXMvGnjSG4N
0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MXQRBdJ3NghVdJIgc=
-----END CERTIFICATE-----

Trustwave Global Certification Authority
========================================
-----BEGIN CERTIFICATE-----
MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJV
UzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2
ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9u
IEF1dGhvcml0eTAeFw0xNzA4MjMxOTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJV
UzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2
ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9u
IEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALldUShLPDeS0YLOvR29
zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0XznswuvCAAJWX/NKSqIk4cXGIDtiLK0thAf
LdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4Bq
stTnoApTAbqOl5F2brz81Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9o
WN0EACyW80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotPJqX+
OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1lRtzuzWniTY+HKE40
Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfwhI0Vcnyh78zyiGG69Gm7DIwLdVcE
uE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm
+9jaJXLE9gCxInm943xZYkqcBW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqj
ifLJS3tBEW1ntwiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud
EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1UdDwEB/wQEAwIB
BjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W0OhUKDtkLSGm+J1WE2pIPU/H
PinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfeuyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0H
ZJDmHvUqoai7PF35owgLEQzxPy0QlG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla
4gt5kNdXElE1GYhBaCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5R
vbbEsLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPTMaCm/zjd
zyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qequ5AvzSxnI9O4fKSTx+O
856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxhVicGaeVyQYHTtgGJoC86cnn+OjC/QezH
Yj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu
3R3y4G5OBVixwJAWKqQ9EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP
29FpHOTKyeC2nOnOcXHebD8WpHk=
-----END CERTIFICATE-----

Trustwave Global ECC P256 Certification Authority
=================================================
-----BEGIN CERTIFICATE-----
MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYDVQQGEwJVUzER
MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI
b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZp
Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYD
VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy
dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDI1
NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABH77bOYj
43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoNFWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqm
P62jQzBBMA8GA1UdEwEB/wQFMAMBAf8wDwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt
0UrrdaVKEJmzsaGLSvcwCgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjz
RM4q3wghDDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7
-----END CERTIFICATE-----

Trustwave Global ECC P384 Certification Authority
=================================================
-----BEGIN CERTIFICATE-----
MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYDVQQGEwJVUzER
MA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0d2F2ZSBI
b2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZp
Y2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYD
VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRy
dXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBFQ0MgUDM4
NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuBBAAiA2IABGvaDXU1CDFH
Ba5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJj9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr
/TklZvFe/oyujUF5nQlgziip04pt89ZF1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNV
HQ8BAf8EBQMDBwYAMB0GA1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNn
ADBkAjA3AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsCMGcl
CrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVuSw==
-----END CERTIFICATE-----

NAVER Global Root Certification Authority
=========================================
-----BEGIN CERTIFICATE-----
MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEMBQAwaTELMAkG
A1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRGT1JNIENvcnAuMTIwMAYDVQQD
DClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4
NDJaFw0zNzA4MTgyMzU5NTlaMGkxCzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVT
UyBQTEFURk9STSBDb3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVAiQqrDZBb
UGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH38dq6SZeWYp34+hInDEW
+j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lEHoSTGEq0n+USZGnQJoViAbbJAh2+g1G7
XNr4rRVqmfeSVPc0W+m/6imBEtRTkZazkVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2
aacp+yPOiNgSnABIqKYPszuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4
Yb8ObtoqvC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHfnZ3z
VHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaGYQ5fG8Ir4ozVu53B
A0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo0es+nPxdGoMuK8u180SdOqcXYZai
cdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3aCJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejy
YhbLgGvtPe31HzClrkvJE+2KAQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNV
HQ4EFgQU0p+I36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB
Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoNqo0hV4/GPnrK
21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatjcu3cvuzHV+YwIHHW1xDBE1UB
jCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm+LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bx
hYTeodoS76TiEJd6eN4MUZeoIUCLhr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTg
E34h5prCy8VCZLQelHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTH
D8z7p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8piKCk5XQ
A76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLRLBT/DShycpWbXgnbiUSY
qqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oG
I/hGoiLtk/bdmuYqh7GYVPEi92tF4+KOdh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmg
kpzNNIaRkPpkUZ3+/uul9XXeifdy
-----END CERTIFICATE-----

AC RAIZ FNMT-RCM SERVIDORES SEGUROS
===================================
-----BEGIN CERTIFICATE-----
MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQswCQYDVQQGEwJF
UzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgwFgYDVQRhDA9WQVRFUy1RMjgy
NjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1SQ00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4
MTIyMDA5MzczM1oXDTQzMTIyMDA5MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQt
UkNNMQ4wDAYDVQQLDAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNB
QyBSQUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuBBAAiA2IA
BPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LHsbI6GA60XYyzZl2hNPk2
LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oKUm8BA06Oi6NCMEAwDwYDVR0TAQH/BAUw
AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqG
SM49BAMDA2kAMGYCMQCuSuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoD
zBOQn5ICMQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJyv+c=
-----END CERTIFICATE-----

GlobalSign Root R46
===================
-----BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUAMEYxCzAJBgNV
BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJv
b3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAX
BgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08Es
CVeJOaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQGvGIFAha/
r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud316HCkD7rRlr+/fKYIje
2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo0q3v84RLHIf8E6M6cqJaESvWJ3En7YEt
bWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSEy132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvj
K8Cd+RTyG/FWaha/LIWFzXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD4
12lPFzYE+cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCNI/on
ccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzsx2sZy/N78CsHpdls
eVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqaByFrgY/bxFn63iLABJzjqls2k+g9
vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYD
VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEM
BQADggIBAHx47PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg
JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti2kM3S+LGteWy
gxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIkpnnpHs6i58FZFZ8d4kuaPp92
CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRFFRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZm
OUdkLG5NrmJ7v2B0GbhWrJKsFjLtrWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qq
JZ4d16GLuc1CLgSkZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwye
qiv5u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP4vkYxboz
nxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6N3ec592kD3ZDZopD8p/7
DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3vouXsXgxT7PntgMTzlSdriVZzH81Xwj3
QEUxeCp6
-----END CERTIFICATE-----

GlobalSign Root E46
===================
-----BEGIN CERTIFICATE-----
MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYxCzAJBgNVBAYT
AkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQDExNHbG9iYWxTaWduIFJvb3Qg
RTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNV
BAoTEEdsb2JhbFNpZ24gbnYtc2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcq
hkjOPQIBBgUrgQQAIgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkB
jtjqR+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGddyXqBPCCj
QjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQxCpCPtsad0kRL
gLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZk
vLtoURMMA/cVi4RguYv/Uo7njLwcAjA8+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+
CAezNIm8BZ/3Hobui3A=
-----END CERTIFICATE-----

GLOBALTRUST 2020
================
-----BEGIN CERTIFICATE-----
MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkGA1UEBhMCQVQx
IzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVT
VCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYxMDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAh
BgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAy
MDIwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWi
D59bRatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9ZYybNpyrO
VPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3QWPKzv9pj2gOlTblzLmM
CcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPwyJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCm
fecqQjuCgGOlYx8ZzHyyZqjC0203b+J+BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKA
A1GqtH6qRNdDYfOiaxaJSaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9OR
JitHHmkHr96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj04KlG
DfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9MedKZssCz3AwyIDMvU
clOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIwq7ejMZdnrY8XD2zHc+0klGvIg5rQ
mjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUw
AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1Ud
IwQYMBaAFNwuH9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA
VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJCXtzoRlgHNQIw
4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd6IwPS3BD0IL/qMy/pJTAvoe9
iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf+I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS
8cE54+X1+NZK3TTN+2/BT+MAi1bikvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2
HcqtbepBEX4tdJP7wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxS
vTOBTI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6CMUO+1918
oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn4rnvyOL2NSl6dPrFf4IF
YqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+IaFvowdlxfv1k7/9nR4hYJS8+hge9+6jl
gqispdNpQ80xiEmEU5LAsTkbOYMBMMTyqfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg==
-----END CERTIFICATE-----

ANF Secure Server Root CA
=========================
-----BEGIN CERTIFICATE-----
MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNVBAUTCUc2MzI4
NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lv
bjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNVBAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3Qg
Q0EwHhcNMTkwOTA0MTAwMDM4WhcNMzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEw
MQswCQYDVQQGEwJFUzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQw
EgYDVQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9vdCBDQTCC
AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCjcqQZAZ2cC4Ffc0m6p6zz
BE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9qyGFOtibBTI3/TO80sh9l2Ll49a2pcbnv
T1gdpd50IJeh7WhM3pIXS7yr/2WanvtH2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcv
B2VSAKduyK9o7PQUlrZXH1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXse
zx76W0OLzc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyRp1RM
VwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQzW7i1o0TJrH93PB0j
7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/SiOL9V8BY9KHcyi1Swr1+KuCLH5z
JTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJnLNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe
8TZBAQIvfXOn3kLMTOmJDVb3n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVO
Hj1tyRRM4y5Bu8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj
o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAOBgNVHQ8BAf8E
BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEATh65isagmD9uw2nAalxJ
UqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzx
j6ptBZNscsdW699QIyjlRRA96Gejrw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDt
dD+4E5UGUcjohybKpFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM
5gf0vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjqOknkJjCb
5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ/zo1PqVUSlJZS2Db7v54
EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ92zg/LFis6ELhDtjTO0wugumDLmsx2d1H
hk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI+PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGy
g77FGr8H6lnco4g175x2MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3
r5+qPeoott7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw=
-----END CERTIFICATE-----

Certum EC-384 CA
================
-----BEGIN CERTIFICATE-----
MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQswCQYDVQQGEwJQ
TDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2Vy
dGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2
MDcyNDU0WhcNNDMwMzI2MDcyNDU0WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERh
dGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx
GTAXBgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATEKI6rGFtq
vm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7TmFy8as10CW4kjPMIRBSqn
iBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68KjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD
VR0OBBYEFI0GZnQkdjrzife81r1HfS+8EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNo
ADBlAjADVS2m5hjEfO/JUG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0
QoSZ/6vnnvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k=
-----END CERTIFICATE-----

Certum Trusted Root CA
======================
-----BEGIN CERTIFICATE-----
MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6MQswCQYDVQQG
EwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0g
Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0Ew
HhcNMTgwMzE2MTIxMDEzWhcNNDMwMzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMY
QXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBB
dXRob3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZn0EGze2jusDbCSzBfN8p
fktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/qp1x4EaTByIVcJdPTsuclzxFUl6s1wB52
HO8AU5853BSlLCIls3Jy/I2z5T4IHhQqNwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2
fJmItdUDmj0VDT06qKhF8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGt
g/BKEiJ3HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGamqi4
NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi7VdNIuJGmj8PkTQk
fVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSFytKAQd8FqKPVhJBPC/PgP5sZ0jeJ
P/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0PqafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSY
njYJdmZm/Bo/6khUHL4wvYBQv3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHK
HRzQ+8S1h9E6Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1
vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQADggIBAEii1QAL
LtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4WxmB82M+w85bj/UvXgF2Ez8s
ALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvozMrnadyHncI013nR03e4qllY/p0m+jiGPp2K
h2RX5Rc64vmNueMzeMGQ2Ljdt4NR5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8
CYyqOhNf6DR5UMEQGfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA
4kZf5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq0Uc9Nneo
WWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7DP78v3DSk+yshzWePS/Tj
6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTMqJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmT
OPQD8rv7gmsHINFSH5pkAnuYZttcTVoP0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZck
bxJF0WddCajJFdr60qZfE2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb
-----END CERTIFICATE-----

TunTrust Root CA
================
-----BEGIN CERTIFICATE-----
MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQELBQAwYTELMAkG
A1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUgQ2VydGlmaWNhdGlvbiBFbGVj
dHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJvb3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQw
NDI2MDg1NzU2WjBhMQswCQYDVQQGEwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBD
ZXJ0aWZpY2F0aW9uIEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIw
DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZn56eY+hz
2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd2JQDoOw05TDENX37Jk0b
bjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgFVwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7
NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZGoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAd
gjH8KcwAWJeRTIAAHDOFli/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViW
VSHbhlnUr8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2eY8f
Tpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIbMlEsPvLfe/ZdeikZ
juXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISgjwBUFfyRbVinljvrS5YnzWuioYas
DXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwS
VXAkPcvCFDVDXSdOvsC9qnyW5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI
04Y+oXNZtPdEITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0
90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+zxiD2BkewhpMl
0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYuQEkHDVneixCwSQXi/5E/S7fd
Ao74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRY
YdZ2vyJ/0Adqp2RT8JeNnYA/u8EH22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJp
adbGNjHh/PqAulxPxOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65x
xBzndFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5Xc0yGYuP
jCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7bnV2UqL1g52KAdoGDDIzM
MEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQCvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9z
ZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZHu/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3r
AZ3r2OvEhJn7wAzMMujjd9qDRIueVSjAi1jTkD5OGwDxFa2DK5o=
-----END CERTIFICATE-----

HARICA TLS RSA Root CA 2021
===========================
-----BEGIN CERTIFICATE-----
MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQG
EwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9u
cyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0EgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUz
OFoXDTQ1MDIxMzEwNTUzN1owbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRl
bWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNB
IFJvb3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569lmwVnlskN
JLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE4VGC/6zStGndLuwRo0Xu
a2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uva9of08WRiFukiZLRgeaMOVig1mlDqa2Y
Ulhu2wr7a89o+uOkXjpFc5gH6l8Cct4MpbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K
5FrZx40d/JiZ+yykgmvwKh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEv
dmn8kN3bLW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcYAuUR
0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqBAGMUuTNe3QvboEUH
GjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYqE613TBoYm5EPWNgGVMWX+Ko/IIqm
haZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHrW2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQ
CPxrvrNQKlr9qEgYRtaQQJKQCoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8G
A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE
AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAUX15QvWiWkKQU
EapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3f5Z2EMVGpdAgS1D0NTsY9FVq
QRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxajaH6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxD
QpSbIPDRzbLrLFPCU3hKTwSUQZqPJzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcR
j88YxeMn/ibvBZ3PzzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5
vZStjBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0/L5H9MG0
qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pTBGIBnfHAT+7hOtSLIBD6
Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79aPib8qXPMThcFarmlwDB31qlpzmq6YR/
PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YWxw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnn
kf3/W9b3raYvAwtt41dU63ZTGI0RmLo=
-----END CERTIFICATE-----

HARICA TLS ECC Root CA 2021
===========================
-----BEGIN CERTIFICATE-----
MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQswCQYDVQQGEwJH
UjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBD
QTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9vdCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoX
DTQ1MDIxMzExMDEwOVowbDELMAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWlj
IGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJv
b3QgQ0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7KKrxcm1l
AEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9YSTHMmE5gEYd103KUkE+b
ECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW
0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAi
rcJRQO9gcS3ujwLEXQNwSaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/Qw
CZ61IygNnxS2PFOiTAZpffpskcYqSUXm7LcT4Tps
-----END CERTIFICATE-----

Autoridad de Certificacion Firmaprofesional CIF A62634068
=========================================================
-----BEGIN CERTIFICATE-----
MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCRVMxQjBA
BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2
MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIw
QAYDVQQDDDlBdXRvcmlkYWQgZGUgQ2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBB
NjI2MzQwNjgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDD
Utd9thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQMcas9UX4P
B99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefGL9ItWY16Ck6WaVICqjaY
7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15iNA9wBj4gGFrO93IbJWyTdBSTo3OxDqqH
ECNZXyAFGUftaI6SEspd/NYrspI8IM/hX68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyI
plD9amML9ZMWGxmPsu2bm8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctX
MbScyJCyZ/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirjaEbsX
LZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/TKI8xWVvTyQKmtFLK
bpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF6NkBiDkal4ZkQdU7hwxu+g/GvUgU
vzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVhOSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1Ud
DgQWBBRlzeurNR4APn7VdMActHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4w
gZswgZgGBFUdIAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j
b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABCAG8AbgBhAG4A
bwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAwADEANzAOBgNVHQ8BAf8EBAMC
AQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9miWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL
4QjbEwj4KKE1soCzC1HA01aajTNFSa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDb
LIpgD7dvlAceHabJhfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1il
I45PVf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZEEAEeiGaP
cjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV1aUsIC+nmCjuRfzxuIgA
LI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2tCsvMo2ebKHTEm9caPARYpoKdrcd7b/+A
lun4jWq9GJAd/0kakFI3ky88Al2CdgtR5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH
9IBk9W6VULgRfhVwOEqwf9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpf
NIbnYrX9ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNKGbqE
ZycPvEJdvSRUDewdcAZfpLz6IHxV
-----END CERTIFICATE-----

vTrus ECC Root CA
=================
-----BEGIN CERTIFICATE-----
MIICDzCCAZWgAwIBAgIUbmq8WapTvpg5Z6LSa6Q75m0c1towCgYIKoZIzj0EAwMwRzELMAkGA1UE
BhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBS
b290IENBMB4XDTE4MDczMTA3MjY0NFoXDTQzMDczMTA3MjY0NFowRzELMAkGA1UEBhMCQ04xHDAa
BgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBSb290IENBMHYw
EAYHKoZIzj0CAQYFK4EEACIDYgAEZVBKrox5lkqqHAjDo6LN/llWQXf9JpRCux3NCNtzslt188+c
ToL0v/hhJoVs1oVbcnDS/dtitN9Ti72xRFhiQgnH+n9bEOf+QP3A2MMrMudwpremIFUde4BdS49n
TPEQo0IwQDAdBgNVHQ4EFgQUmDnNvtiyjPeyq+GtJK97fKHbH88wDwYDVR0TAQH/BAUwAwEB/zAO
BgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaAAwZQIwV53dVvHH4+m4SVBrm2nDb+zDfSXkV5UT
QJtS0zvzQBm8JsctBp61ezaf9SXUY2sAAjEA6dPGnlaaKsyh2j/IZivTWJwghfqrkYpwcBE4YGQL
YgmRWAD5Tfs0aNoJrSEGGJTO
-----END CERTIFICATE-----

vTrus Root CA
=============
-----BEGIN CERTIFICATE-----
MIIFVjCCAz6gAwIBAgIUQ+NxE9izWRRdt86M/TX9b7wFjUUwDQYJKoZIhvcNAQELBQAwQzELMAkG
A1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xFjAUBgNVBAMTDXZUcnVzIFJv
b3QgQ0EwHhcNMTgwNzMxMDcyNDA1WhcNNDMwNzMxMDcyNDA1WjBDMQswCQYDVQQGEwJDTjEcMBoG
A1UEChMTaVRydXNDaGluYSBDby4sTHRkLjEWMBQGA1UEAxMNdlRydXMgUm9vdCBDQTCCAiIwDQYJ
KoZIhvcNAQEBBQADggIPADCCAgoCggIBAL1VfGHTuB0EYgWgrmy3cLRB6ksDXhA/kFocizuwZots
SKYcIrrVQJLuM7IjWcmOvFjai57QGfIvWcaMY1q6n6MLsLOaXLoRuBLpDLvPbmyAhykUAyyNJJrI
ZIO1aqwTLDPxn9wsYTwaP3BVm60AUn/PBLn+NvqcwBauYv6WTEN+VRS+GrPSbcKvdmaVayqwlHeF
XgQPYh1jdfdr58tbmnDsPmcF8P4HCIDPKNsFxhQnL4Z98Cfe/+Z+M0jnCx5Y0ScrUw5XSmXX+6KA
YPxMvDVTAWqXcoKv8R1w6Jz1717CbMdHflqUhSZNO7rrTOiwCcJlwp2dCZtOtZcFrPUGoPc2BX70
kLJrxLT5ZOrpGgrIDajtJ8nU57O5q4IikCc9Kuh8kO+8T/3iCiSn3mUkpF3qwHYw03dQ+A0Em5Q2
AXPKBlim0zvc+gRGE1WKyURHuFE5Gi7oNOJ5y1lKCn+8pu8fA2dqWSslYpPZUxlmPCdiKYZNpGvu
/9ROutW04o5IWgAZCfEF2c6Rsffr6TlP9m8EQ5pV9T4FFL2/s1m02I4zhKOQUqqzApVg+QxMaPnu
1RcN+HFXtSXkKe5lXa/R7jwXC1pDxaWG6iSe4gUH3DRCEpHWOXSuTEGC2/KmSNGzm/MzqvOmwMVO
9fSddmPmAsYiS8GVP1BkLFTltvA8Kc9XAgMBAAGjQjBAMB0GA1UdDgQWBBRUYnBj8XWEQ1iO0RYg
scasGrz2iTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOC
AgEAKbqSSaet8PFww+SX8J+pJdVrnjT+5hpk9jprUrIQeBqfTNqK2uwcN1LgQkv7bHbKJAs5EhWd
nxEt/Hlk3ODg9d3gV8mlsnZwUKT+twpw1aA08XXXTUm6EdGz2OyC/+sOxL9kLX1jbhd47F18iMjr
jld22VkE+rxSH0Ws8HqA7Oxvdq6R2xCOBNyS36D25q5J08FsEhvMKar5CKXiNxTKsbhm7xqC5PD4
8acWabfbqWE8n/Uxy+QARsIvdLGx14HuqCaVvIivTDUHKgLKeBRtRytAVunLKmChZwOgzoy8sHJn
xDHO2zTlJQNgJXtxmOTAGytfdELSS8VZCAeHvsXDf+eW2eHcKJfWjwXj9ZtOyh1QRwVTsMo554Wg
icEFOwE30z9J4nfrI8iIZjs9OXYhRvHsXyO466JmdXTBQPfYaJqT4i2pLr0cox7IdMakLXogqzu4
sEb9b91fUlV1YvCXoHzXOP0l382gmxDPi7g4Xl7FtKYCNqEeXxzP4padKar9mK5S4fNBUvupLnKW
nyfjqnN9+BojZns7q2WwMgFLFT49ok8MKzWixtlnEjUwzXYuFrOZnk1PTi07NEPhmg4NpGaXutIc
SkwsKouLgU9xGqndXHt7CMUADTdA43x7VF8vhV929vensBxXVsFy6K2ir40zSbofitzmdHxghm+H
l3s=
-----END CERTIFICATE-----

ISRG Root X2
============
-----BEGIN CERTIFICATE-----
MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQswCQYDVQQGEwJV
UzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElT
UkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVT
MSkwJwYDVQQKEyBJbnRlcm5ldCBTZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNS
RyBSb290IFgyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0H
ttwW+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9ItgKbppb
d9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
HQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZIzj0EAwMDaAAwZQIwe3lORlCEwkSHRhtF
cP9Ymd70/aTSVaYgLXTWNLxBo1BfASdWtL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5
U6VR5CmD1/iQMVtCnwr1/q4AaOeMSQ+2b1tbFfLn
-----END CERTIFICATE-----

HiPKI Root CA - G1
==================
-----BEGIN CERTIFICATE-----
MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBPMQswCQYDVQQG
EwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0ZC4xGzAZBgNVBAMMEkhpUEtJ
IFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRaFw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYT
AlRXMSMwIQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kg
Um9vdCBDQSAtIEcxMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0
o9QwqNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twvVcg3Px+k
wJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6lZgRZq2XNdZ1AYDgr/SE
YYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnzQs7ZngyzsHeXZJzA9KMuH5UHsBffMNsA
GJZMoYFL3QRtU6M9/Aes1MU3guvklQgZKILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfd
hSi8MEyr48KxRURHH+CKFgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj
1jOXTyFjHluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDry+K4
9a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ/W3c1pzAtH2lsN0/
Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgMa/aOEmem8rJY5AIJEzypuxC00jBF
8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYD
VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQD
AgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi
7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqcSE5XCV0vrPSl
tJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6FzaZsT0pPBWGTMpWmWSBUdGSquE
wx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9TcXzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07Q
JNBAsNB1CI69aO4I1258EHBGG3zgiLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv
5wiZqAxeJoBF1PhoL5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+Gpz
jLrFNe85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wrkkVbbiVg
hUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+vhV4nYWBSipX3tUZQ9rb
yltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQUYDksswBVLuT1sw5XxJFBAJw/6KXf6vb/
yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ==
-----END CERTIFICATE-----

GlobalSign ECC Root CA - R4
===========================
-----BEGIN CERTIFICATE-----
MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYDVQQLExtHbG9i
YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds
b2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgwMTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9i
YWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkds
b2JhbFNpZ24wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkW
ymOxuYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNVHQ8BAf8E
BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/+wpu+74zyTyjhNUwCgYI
KoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147bmF0774BxL4YSFlhgjICICadVGNA3jdg
UM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm
-----END CERTIFICATE-----

GTS Root R1
===========
-----BEGIN CERTIFICATE-----
MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJV
UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg
UjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE
ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0G
CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM
f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7wCl7raKb0
xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjwTcLCeoiKu7rPWRnWr4+w
B7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0PfyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXW
nOunVmSPlk9orj2XwoSPwLxAwAtcvfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk
9+aCEI3oncKKiPo4Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zq
kUspzBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92wO1A
K/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70paDPvOmbsB4om3xPX
V2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDW
cfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T
AQH/BAUwAwEB/zAdBgNVHQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQAD
ggIBAJ+qQibbC5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe
QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuyh6f88/qBVRRi
ClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM47HLwEXWdyzRSjeZ2axfG34ar
J45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8JZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYci
NuaCp+0KueIHoI17eko8cdLiA6EfMgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5me
LMFrUKTX5hgUvYU/Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJF
fbdT6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ0E6yove+
7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm2tIMPNuzjsmhDYAPexZ3
FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bbbP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3
gm3c
-----END CERTIFICATE-----

GTS Root R2
===========
-----BEGIN CERTIFICATE-----
MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQGEwJV
UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg
UjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE
ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0G
CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3Lv
CvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY6Dlo7JUl
e3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAuMC6C/Pq8tBcKSOWIm8Wb
a96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7kRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS
+LFjKBC4swm4VndAoiaYecb+3yXuPuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7M
kogwTZq9TwtImoS1mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJG
r61K8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RWIr9q
S34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKaG73VululycslaVNV
J1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCqgc7dGtxRcw1PcOnlthYhGXmy5okL
dWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0T
AQH/BAUwAwEB/zAdBgNVHQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQAD
ggIBAB/Kzt3HvqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8
0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyCB19m3H0Q/gxh
swWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2uNmSRXbBoGOqKYcl3qJfEycel
/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMgyALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVn
jWQye+mew4K6Ki3pHrTgSAai/GevHyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y5
9PYjJbigapordwj6xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M
7YNRTOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924SgJPFI/2R8
0L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV7LXTWtiBmelDGDfrs7vR
WGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjW
HYbL
-----END CERTIFICATE-----

GTS Root R3
===========
-----BEGIN CERTIFICATE-----
MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi
MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMw
HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ
R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjO
PQIBBgUrgQQAIgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout
736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL24CejQjBA
MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTB8Sa6oC2uhYHP0/Eq
Er24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azT
L818+FsuVbu/3ZL3pAzcMeGiAjEA/JdmZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV
11RZt+cRLInUue4X
-----END CERTIFICATE-----

GTS Root R4
===========
-----BEGIN CERTIFICATE-----
MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJVUzEi
MCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQw
HhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZ
R29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjO
PQIBBgUrgQQAIgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu
hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvRHYqjQjBA
MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSATNbrdP9JNqPV2Py1
PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/C
r8deVl5c1RxYIigL9zC2L7F8AjEA8GE8p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh
4rsUecrNIdSUtUlD
-----END CERTIFICATE-----

Telia Root CA v2
================
-----BEGIN CERTIFICATE-----
MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQxCzAJBgNVBAYT
AkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2
MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQK
DBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZI
hvcNAQEBBQADggIPADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ7
6zBqAMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9vVYiQJ3q
9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9lRdU2HhE8Qx3FZLgmEKn
pNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTODn3WhUidhOPFZPY5Q4L15POdslv5e2QJl
tI5c0BE0312/UqeBAMN/mUWZFdUXyApT7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW
5olWK8jjfN7j/4nlNW4o6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNr
RBH0pUPCTEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6WT0E
BXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63RDolUK5X6wK0dmBR4
M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZIpEYslOqodmJHixBTB0hXbOKSTbau
BcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGjYzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7W
xy+G2CQ5MB0GA1UdDgQWBBRyrOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYD
VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ
8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi0f6X+J8wfBj5
tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMMA8iZGok1GTzTyVR8qPAs5m4H
eW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBSSRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+C
y748fdHif64W1lZYudogsYMVoe+KTTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygC
QMez2P2ccGrGKMOF6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15
h2Er3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMtTy3EHD70
sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pTVmBds9hCG1xLEooc6+t9
xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAWysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQ
raVplI/owd8k+BsHMYeB2F326CjYSlKArBPuUBQemMc=
-----END CERTIFICATE-----

D-TRUST BR Root CA 1 2020
=========================
-----BEGIN CERTIFICATE-----
MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE
RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEJSIFJvb3QgQ0EgMSAy
MDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNV
BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAG
ByqGSM49AgEGBSuBBAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7
dPYSzuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0QVK5buXu
QqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/VbNafAkl1bK6CKBrqx9t
MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu
bmV0L2NybC9kLXRydXN0X2JyX3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj
dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP
PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD
AwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFWwKrY7RjEsK70Pvom
AjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHVdWNbFJWcHwHP2NVypw87
-----END CERTIFICATE-----

D-TRUST EV Root CA 1 2020
=========================
-----BEGIN CERTIFICATE-----
MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQswCQYDVQQGEwJE
RTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEVWIFJvb3QgQ0EgMSAy
MDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNV
BAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAG
ByqGSM49AgEGBSuBBAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8
ZRCC/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rDwpdhQntJ
raOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3OqQo5FD4pPfsazK2/umL
MA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6gPKA6hjhodHRwOi8vY3JsLmQtdHJ1c3Qu
bmV0L2NybC9kLXRydXN0X2V2X3Jvb3RfY2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVj
dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxP
PUQtVHJ1c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjOPQQD
AwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CAy/m0sRtW9XLS/BnR
AjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJbgfM0agPnIjhQW+0ZT0MW
-----END CERTIFICATE-----

DigiCert TLS ECC P384 Root G5
=============================
-----BEGIN CERTIFICATE-----
MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQswCQYDVQQGEwJV
UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURpZ2lDZXJ0IFRMUyBFQ0MgUDM4
NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMx
FzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQg
Um9vdCBHNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1Tzvd
lHJS7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp0zVozptj
n4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICISB4CIfBFqMA4GA1UdDwEB
/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQCJao1H5+z8blUD2Wds
Jk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQLgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIx
AJSdYsiJvRmEFOml+wG4DXZDjC5Ty3zfDBeWUA==
-----END CERTIFICATE-----

DigiCert TLS RSA4096 Root G5
============================
-----BEGIN CERTIFICATE-----
MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBNMQswCQYDVQQG
EwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0
MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcNNDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJV
UzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2
IFJvb3QgRzUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS8
7IE+ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG02C+JFvuU
AT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgpwgscONyfMXdcvyej/Ces
tyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZMpG2T6T867jp8nVid9E6P/DsjyG244gXa
zOvswzH016cpVIDPRFtMbzCe88zdH5RDnU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnV
DdXifBBiqmvwPXbzP6PosMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9q
TXeXAaDxZre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cdLvvy
z6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvXKyY//SovcfXWJL5/
MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNeXoVPzthwiHvOAbWWl9fNff2C+MIk
wcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPLtgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4E
FgQUUTMc7TZArxfTJc1paPKvTiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8w
DQYJKoZIhvcNAQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw
GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7HPNtQOa27PShN
lnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLFO4uJ+DQtpBflF+aZfTCIITfN
MBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQREtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/
u4cnYiWB39yhL/btp/96j1EuMPikAdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9G
OUrYU9DzLjtxpdRv/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh
47a+p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilwMUc/dNAU
FvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WFqUITVuwhd4GTWgzqltlJ
yqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCKovfepEWFJqgejF0pW8hL2JpqA15w8oVP
bEtoL8pU9ozaMv7Da4M/OMZ+
-----END CERTIFICATE-----

Certainly Root R1
=================
-----BEGIN CERTIFICATE-----
MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAwPTELMAkGA1UE
BhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2VydGFpbmx5IFJvb3QgUjEwHhcN
MjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2Vy
dGFpbmx5MRowGAYDVQQDExFDZXJ0YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIP
ADCCAgoCggIBANA21B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O
5MQTvqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbedaFySpvXl
8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b01C7jcvk2xusVtyWMOvwl
DbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGI
XsXwClTNSaa/ApzSRKft43jvRl5tcdF5cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkN
KPl6I7ENPT2a/Z2B7yyQwHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQ
AjeZjOVJ6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA2Cnb
rlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyHWyf5QBGenDPBt+U1
VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMReiFPCyEQtkA6qyI6BJyLm4SGcprS
p6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
DgQWBBTgqj8ljZ9EXME66C6ud0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAsz
HQNTVfSVcOQrPbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d
8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi1wrykXprOQ4v
MMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrdrRT90+7iIgXr0PK3aBLXWopB
GsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9ditaY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+
gjwN/KUD+nsa2UUeYNrEjvn8K8l7lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgH
JBu6haEaBQmAupVjyTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7
fpYnKx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLyyCwzk5Iw
x06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5nwXARPbv0+Em34yaXOp/S
X3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6OV+KmalBWQewLK8=
-----END CERTIFICATE-----

Certainly Root E1
=================
-----BEGIN CERTIFICATE-----
MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQswCQYDVQQGEwJV
UzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlubHkgUm9vdCBFMTAeFw0yMTA0
MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJBgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlu
bHkxGjAYBgNVBAMTEUNlcnRhaW5seSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4
fxzf7flHh4axpMCK+IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9
YBk2QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8EBAMCAQYw
DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4hevIIgcwCgYIKoZIzj0E
AwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozmut6Dacpps6kFtZaSF4fC0urQe87YQVt8
rgIwRt7qy12a7DLCZRawTDBcMPPaTnOGBtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR
-----END CERTIFICATE-----

Security Communication ECC RootCA1
==================================
-----BEGIN CERTIFICATE-----
MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYTAkpQMSUwIwYD
VQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYDVQQDEyJTZWN1cml0eSBDb21t
dW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYxNjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTEL
MAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNV
BAMTIlNlY3VyaXR5IENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQA
IgNiAASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+CnnfdldB9sELLo
5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpKULGjQjBAMB0GA1UdDgQW
BBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAK
BggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3L
snNdo4gIxwwCMQDAqy0Obe0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70e
N9k=
-----END CERTIFICATE-----

BJCA Global Root CA1
====================
-----BEGIN CERTIFICATE-----
MIIFdDCCA1ygAwIBAgIQVW9l47TZkGobCdFsPsBsIDANBgkqhkiG9w0BAQsFADBUMQswCQYDVQQG
EwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJVFkxHTAbBgNVBAMMFEJK
Q0EgR2xvYmFsIFJvb3QgQ0ExMB4XDTE5MTIxOTAzMTYxN1oXDTQ0MTIxMjAzMTYxN1owVDELMAkG
A1UEBhMCQ04xJjAkBgNVBAoMHUJFSUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQD
DBRCSkNBIEdsb2JhbCBSb290IENBMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPFm
CL3ZxRVhy4QEQaVpN3cdwbB7+sN3SJATcmTRuHyQNZ0YeYjjlwE8R4HyDqKYDZ4/N+AZspDyRhyS
sTphzvq3Rp4Dhtczbu33RYx2N95ulpH3134rhxfVizXuhJFyV9xgw8O558dnJCNPYwpj9mZ9S1Wn
P3hkSWkSl+BMDdMJoDIwOvqfwPKcxRIqLhy1BDPapDgRat7GGPZHOiJBhyL8xIkoVNiMpTAK+BcW
yqw3/XmnkRd4OJmtWO2y3syJfQOcs4ll5+M7sSKGjwZteAf9kRJ/sGsciQ35uMt0WwfCyPQ10WRj
eulumijWML3mG90Vr4TqnMfK9Q7q8l0ph49pczm+LiRvRSGsxdRpJQaDrXpIhRMsDQa4bHlW/KNn
MoH1V6XKV0Jp6VwkYe/iMBhORJhVb3rCk9gZtt58R4oRTklH2yiUAguUSiz5EtBP6DF+bHq/pj+b
OT0CFqMYs2esWz8sgytnOYFcuX6U1WTdno9uruh8W7TXakdI136z1C2OVnZOz2nxbkRs1CTqjSSh
GL+9V/6pmTW12xB3uD1IutbB5/EjPtffhZ0nPNRAvQoMvfXnjSXWgXSHRtQpdaJCbPdzied9v3pK
H9MiyRVVz99vfFXQpIsHETdfg6YmV6YBW37+WGgHqel62bno/1Afq8K0wM7o6v0PvY1NuLxxAgMB
AAGjQjBAMB0GA1UdDgQWBBTF7+3M2I0hxkjk49cULqcWk+WYATAPBgNVHRMBAf8EBTADAQH/MA4G
A1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAUoKsITQfI/Ki2Pm4rzc2IInRNwPWaZ+4
YRC6ojGYWUfo0Q0lHhVBDOAqVdVXUsv45Mdpox1NcQJeXyFFYEhcCY5JEMEE3KliawLwQ8hOnThJ
dMkycFRtwUf8jrQ2ntScvd0g1lPJGKm1Vrl2i5VnZu69mP6u775u+2D2/VnGKhs/I0qUJDAnyIm8
60Qkmss9vk/Ves6OF8tiwdneHg56/0OGNFK8YT88X7vZdrRTvJez/opMEi4r89fO4aL/3Xtw+zuh
TaRjAv04l5U/BXCga99igUOLtFkNSoxUnMW7gZ/NfaXvCyUeOiDbHPwfmGcCCtRzRBPbUYQaVQNW
4AB+dAb/OMRyHdOoP2gxXdMJxy6MW2Pg6Nwe0uxhHvLe5e/2mXZgLR6UcnHGCyoyx5JO1UbXHfmp
GQrI+pXObSOYqgs4rZpWDW+N8TEAiMEXnM0ZNjX+VVOg4DwzX5Ze4jLp3zO7Bkqp2IRzznfSxqxx
4VyjHQy7Ct9f4qNx2No3WqB4K/TUfet27fJhcKVlmtOJNBir+3I+17Q9eVzYH6Eze9mCUAyTF6ps
3MKCuwJXNq+YJyo5UOGwifUll35HaBC07HPKs5fRJNz2YqAo07WjuGS3iGJCz51TzZm+ZGiPTx4S
SPfSKcOYKMryMguTjClPPGAyzQWWYezyr/6zcCwupvI=
-----END CERTIFICATE-----

BJCA Global Root CA2
====================
-----BEGIN CERTIFICATE-----
MIICJTCCAaugAwIBAgIQLBcIfWQqwP6FGFkGz7RK6zAKBggqhkjOPQQDAzBUMQswCQYDVQQGEwJD
TjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJVFkxHTAbBgNVBAMMFEJKQ0Eg
R2xvYmFsIFJvb3QgQ0EyMB4XDTE5MTIxOTAzMTgyMVoXDTQ0MTIxMjAzMTgyMVowVDELMAkGA1UE
BhMCQ04xJjAkBgNVBAoMHUJFSUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRC
SkNBIEdsb2JhbCBSb290IENBMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABJ3LgJGNU2e1uVCxA/jl
SR9BIgmwUVJY1is0j8USRhTFiy8shP8sbqjV8QnjAyEUxEM9fMEsxEtqSs3ph+B99iK++kpRuDCK
/eHeGBIK9ke35xe/J4rUQUyWPGCWwf0VHKNCMEAwHQYDVR0OBBYEFNJKsVF/BvDRgh9Obl+rg/xI
1LCRMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMBq8
W9f+qdJUDkpd0m2xQNz0Q9XSSpkZElaA94M04TVOSG0ED1cxMDAtsaqdAzjbBgIxAMvMh1PLet8g
UXOQwKhbYdDFUDn9hf7B43j4ptZLvZuHjw/l1lOWqzzIQNph91Oj9w==
-----END CERTIFICATE-----

Sectigo Public Server Authentication Root E46
=============================================
-----BEGIN CERTIFICATE-----
MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQswCQYDVQQGEwJH
QjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBTZXJ2
ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5
WjBfMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0
aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUr
gQQAIgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccCWvkEN/U0
NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+6xnOQ6OjQjBAMB0GA1Ud
DgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB
/zAKBggqhkjOPQQDAwNnADBkAjAn7qRaqCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RH
lAFWovgzJQxC36oCMB3q4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21U
SAGKcw==
-----END CERTIFICATE-----

Sectigo Public Server Authentication Root R46
=============================================
-----BEGIN CERTIFICATE-----
MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBfMQswCQYDVQQG
EwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT
ZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwHhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1
OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T
ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3
DQEBAQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDaef0rty2k
1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnzSDBh+oF8HqcIStw+Kxwf
GExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xfiOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMP
FF1bFOdLvt30yNoDN9HWOaEhUTCDsG3XME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vu
ZDCQOc2TZYEhMbUjUDM3IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5Qaz
Yw6A3OASVYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgESJ/A
wSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu+Zd4KKTIRJLpfSYF
plhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt8uaZFURww3y8nDnAtOFr94MlI1fZ
EoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+LHaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW
6aWWrL3DkJiy4Pmi1KZHQ3xtzwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWI
IUkwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c
mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQYKlJfp/imTYp
E0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52gDY9hAaLMyZlbcp+nv4fjFg4
exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZAFv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M
0ejf5lG5Nkc/kLnHvALcWxxPDkjBJYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI
84HxZmduTILA7rpXDhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9m
pFuiTdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5dHn5Hrwd
Vw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65LvKRRFHQV80MNNVIIb/b
E/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmm
J1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAYQqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL
-----END CERTIFICATE-----

SSL.com TLS RSA Root CA 2022
============================
-----BEGIN CERTIFICATE-----
MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQG
EwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxTU0wuY29tIFRMUyBSU0Eg
Um9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloXDTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMC
VVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJv
b3QgQ0EgMjAyMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u
9nTPL3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OYt6/wNr/y
7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0insS657Lb85/bRi3pZ7Qcac
oOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3PnxEX4MN8/HdIGkWCVDi1FW24IBydm5M
R7d1VVm0U3TZlMZBrViKMWYPHqIbKUBOL9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDG
D6C1vBdOSHtRwvzpXGk3R2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEW
TO6Af77wdr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS+YCk
8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYSd66UNHsef8JmAOSq
g+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoGAtUjHBPW6dvbxrB6y3snm/vg1UYk
7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2fgTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1Ud
EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsu
N+7jhHonLs0ZNbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt
hEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsMQtfhWsSWTVTN
j8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvfR4iyrT7gJ4eLSYwfqUdYe5by
iB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJDPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjU
o3KUQyxi4U5cMj29TH0ZR6LDSeeWP4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqo
ENjwuSfr98t67wVylrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7Egkaib
MOlqbLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2wAgDHbICi
vRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3qr5nsLFR+jM4uElZI7xc7
P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sjiMho6/4UIyYOf8kpIEFR3N+2ivEC+5BB0
9+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA=
-----END CERTIFICATE-----

SSL.com TLS ECC Root CA 2022
============================
-----BEGIN CERTIFICATE-----
MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQswCQYDVQQGEwJV
UzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxTU0wuY29tIFRMUyBFQ0MgUm9v
dCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2MDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMx
GDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3Qg
Q0EgMjAyMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWy
JGYmacCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFNSeR7T5v1
5wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSJjy+j6CugFFR7
81a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NWuCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGG
MAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w
7deedWo1dlJF4AIxAMeNb0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5
Zn6g6g==
-----END CERTIFICATE-----

Atos TrustedRoot Root CA ECC TLS 2021
=====================================
-----BEGIN CERTIFICATE-----
MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4wLAYDVQQDDCVB
dG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0wCwYDVQQKDARBdG9zMQswCQYD
VQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0MTcwOTI2MjJaMEwxLjAsBgNVBAMMJUF0b3Mg
VHJ1c3RlZFJvb3QgUm9vdCBDQSBFQ0MgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYT
AkRFMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEloZYKDcKZ9Cg3iQZGeHkBQcfl+3oZIK59sRxUM6K
DP/XtXa7oWyTbIOiaG6l2b4siJVBzV3dscqDY4PMwL502eCdpO5KTlbgmClBk1IQ1SQ4AjJn8ZQS
b+/Xxd4u/RmAo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR2KCXWfeBmmnoJsmo7jjPX
NtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIwW5kp85wxtolrbNa9d+F851F+
uDrNozZffPc8dz7kUK2o59JZDCaOMDtuCCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGY
a3cpetskz2VAv9LcjBHo9H1/IISpQuQo
-----END CERTIFICATE-----

Atos TrustedRoot Root CA RSA TLS 2021
=====================================
-----BEGIN CERTIFICATE-----
MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBMMS4wLAYDVQQD
DCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIxMQ0wCwYDVQQKDARBdG9zMQsw
CQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00MTA0MTcwOTIxMDlaMEwxLjAsBgNVBAMMJUF0
b3MgVHJ1c3RlZFJvb3QgUm9vdCBDQSBSU0EgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNV
BAYTAkRFMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtoAOxHm9BYx9sKOdTSJNy/BB
l01Z4NH+VoyX8te9j2y3I49f1cTYQcvyAh5x5en2XssIKl4w8i1mx4QbZFc4nXUtVsYvYe+W/CBG
vevUez8/fEc4BKkbqlLfEzfTFRVOvV98r61jx3ncCHvVoOX3W3WsgFWZkmGbzSoXfduP9LVq6hdK
ZChmFSlsAvFr1bqjM9xaZ6cF4r9lthawEO3NUDPJcFDsGY6wx/J0W2tExn2WuZgIWWbeKQGb9Cpt
0xU6kGpn8bRrZtkh68rZYnxGEFzedUlnnkL5/nWpo63/dgpnQOPF943HhZpZnmKaau1Fh5hnstVK
PNe0OwANwI8f4UDErmwh3El+fsqyjW22v5MvoVw+j8rtgI5Y4dtXz4U2OLJxpAmMkokIiEjxQGMY
sluMWuPD0xeqqxmjLBvk1cbiZnrXghmmOxYsL3GHX0WelXOTwkKBIROW1527k2gV+p2kHYzygeBY
Br3JtuP2iV2J+axEoctr+hbxx1A9JNr3w+SH1VbxT5Aw+kUJWdo0zuATHAR8ANSbhqRAvNncTFd+
rrcztl524WWLZt+NyteYr842mIycg5kDcPOvdO3GDjbnvezBc6eUWsuSZIKmAMFwoW4sKeFYV+xa
fJlrJaSQOoD0IJ2azsct+bJLKZWD6TWNp0lIpw9MGZHQ9b8Q4HECAwEAAaNCMEAwDwYDVR0TAQH/
BAUwAwEB/zAdBgNVHQ4EFgQUdEmZ0f+0emhFdcN+tNzMzjkz2ggwDgYDVR0PAQH/BAQDAgGGMA0G
CSqGSIb3DQEBDAUAA4ICAQAjQ1MkYlxt/T7Cz1UAbMVWiLkO3TriJQ2VSpfKgInuKs1l+NsW4AmS
4BjHeJi78+xCUvuppILXTdiK/ORO/auQxDh1MoSf/7OwKwIzNsAQkG8dnK/haZPso0UvFJ/1TCpl
Q3IM98P4lYsU84UgYt1UU90s3BiVaU+DR3BAM1h3Egyi61IxHkzJqM7F78PRreBrAwA0JrRUITWX
AdxfG/F851X6LWh3e9NpzNMOa7pNdkTWwhWaJuywxfW70Xp0wmzNxbVe9kzmWy2B27O3Opee7c9G
slA9hGCZcbUztVdF5kJHdWoOsAgMrr3e97sPWD2PAzHoPYJQyi9eDF20l74gNAf0xBLh7tew2Vkt
afcxBPTy+av5EzH4AXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9q
TFsR0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuYo7Ey7Nmj
1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5dDTedk+SKlOxJTnbPP/l
PqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcEoji2jbDwN/zIIX8/syQbPYtuzE2wFg2W
HYMfRsCbvUOZ58SWLs5fyQ==
-----END CERTIFICATE-----

TrustAsia Global Root CA G3
===========================
-----BEGIN CERTIFICATE-----
MIIFpTCCA42gAwIBAgIUZPYOZXdhaqs7tOqFhLuxibhxkw8wDQYJKoZIhvcNAQEMBQAwWjELMAkG
A1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xJDAiBgNVBAMM
G1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHMzAeFw0yMTA1MjAwMjEwMTlaFw00NjA1MTkwMjEw
MTlaMFoxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMu
MSQwIgYDVQQDDBtUcnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzMwggIiMA0GCSqGSIb3DQEBAQUA
A4ICDwAwggIKAoICAQDAMYJhkuSUGwoqZdC+BqmHO1ES6nBBruL7dOoKjbmzTNyPtxNST1QY4Sxz
lZHFZjtqz6xjbYdT8PfxObegQ2OwxANdV6nnRM7EoYNl9lA+sX4WuDqKAtCWHwDNBSHvBm3dIZwZ
Q0WhxeiAysKtQGIXBsaqvPPW5vxQfmZCHzyLpnl5hkA1nyDvP+uLRx+PjsXUjrYsyUQE49RDdT/V
P68czH5GX6zfZBCK70bwkPAPLfSIC7Epqq+FqklYqL9joDiR5rPmd2jE+SoZhLsO4fWvieylL1Ag
dB4SQXMeJNnKziyhWTXAyB1GJ2Faj/lN03J5Zh6fFZAhLf3ti1ZwA0pJPn9pMRJpxx5cynoTi+jm
9WAPzJMshH/x/Gr8m0ed262IPfN2dTPXS6TIi/n1Q1hPy8gDVI+lhXgEGvNz8teHHUGf59gXzhqc
D0r83ERoVGjiQTz+LISGNzzNPy+i2+f3VANfWdP3kXjHi3dqFuVJhZBFcnAvkV34PmVACxmZySYg
WmjBNb9Pp1Hx2BErW+Canig7CjoKH8GB5S7wprlppYiU5msTf9FkPz2ccEblooV7WIQn3MSAPmea
mseaMQ4w7OYXQJXZRe0Blqq/DPNL0WP3E1jAuPP6Z92bfW1K/zJMtSU7/xxnD4UiWQWRkUF3gdCF
TIcQcf+eQxuulXUtgQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEDk5PIj
7zjKsK5Xf/IhMBY027ySMB0GA1UdDgQWBBRA5OTyI+84yrCuV3/yITAWNNu8kjAOBgNVHQ8BAf8E
BAMCAQYwDQYJKoZIhvcNAQEMBQADggIBACY7UeFNOPMyGLS0XuFlXsSUT9SnYaP4wM8zAQLpw6o1
D/GUE3d3NZ4tVlFEbuHGLige/9rsR82XRBf34EzC4Xx8MnpmyFq2XFNFV1pF1AWZLy4jVe5jaN/T
G3inEpQGAHUNcoTpLrxaatXeL1nHo+zSh2bbt1S1JKv0Q3jbSwTEb93mPmY+KfJLaHEih6D4sTNj
duMNhXJEIlU/HHzp/LgV6FL6qj6jITk1dImmasI5+njPtqzn59ZW/yOSLlALqbUHM/Q4X6RJpstl
cHboCoWASzY9M/eVVHUl2qzEc4Jl6VL1XP04lQJqaTDFHApXB64ipCz5xUG3uOyfT0gA+QEEVcys
+TIxxHWVBqB/0Y0n3bOppHKH/lmLmnp0Ft0WpWIp6zqW3IunaFnT63eROfjXy9mPX1onAX1daBli
2MjN9LdyR75bl87yraKZk62Uy5P2EgmVtqvXO9A/EcswFi55gORngS1d7XB4tmBZrOFdRWOPyN9y
aFvqHbgB8X7754qz41SgOAngPN5C8sLtLpvzHzW2NtjjgKGLzZlkD8Kqq7HK9W+eQ42EVJmzbsAS
ZthwEPEGNTNDqJwuuhQxzhB/HIbjj9LV+Hfsm6vxL2PZQl/gZ4FkkfGXL/xuJvYz+NO1+MRiqzFR
JQJ6+N1rZdVtTTDIZbpoFGWsJwt0ivKH
-----END CERTIFICATE-----

TrustAsia Global Root CA G4
===========================
-----BEGIN CERTIFICATE-----
MIICVTCCAdygAwIBAgIUTyNkuI6XY57GU4HBdk7LKnQV1tcwCgYIKoZIzj0EAwMwWjELMAkGA1UE
BhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xJDAiBgNVBAMMG1Ry
dXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHNDAeFw0yMTA1MjAwMjEwMjJaFw00NjA1MTkwMjEwMjJa
MFoxCzAJBgNVBAYTAkNOMSUwIwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQw
IgYDVQQDDBtUcnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi
AATxs8045CVD5d4ZCbuBeaIVXxVjAd7Cq92zphtnS4CDr5nLrBfbK5bKfFJV4hrhPVbwLxYI+hW8
m7tH5j/uqOFMjPXTNvk4XatwmkcN4oFBButJ+bAp3TPsUKV/eSm4IJijYzBhMA8GA1UdEwEB/wQF
MAMBAf8wHwYDVR0jBBgwFoAUpbtKl86zK3+kMd6Xg1mDpm9xy94wHQYDVR0OBBYEFKW7SpfOsyt/
pDHel4NZg6ZvccveMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjBe8usGzEkxn0AA
bbd+NvBNEU/zy4k6LHiRUKNbwMp1JvK/kF0LgoxgKJ/GcJpo5PECMFxYDlZ2z1jD1xCMuo6u47xk
dUfFVZDj/bpV6wfEU6s3qe4hsiFbYI89MvHVI5TWWA==
-----END CERTIFICATE-----

CommScope Public Trust ECC Root-01
==================================
-----BEGIN CERTIFICATE-----
MIICHTCCAaOgAwIBAgIUQ3CCd89NXTTxyq4yLzf39H91oJ4wCgYIKoZIzj0EAwMwTjELMAkGA1UE
BhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBUcnVz
dCBFQ0MgUm9vdC0wMTAeFw0yMTA0MjgxNzM1NDNaFw00NjA0MjgxNzM1NDJaME4xCzAJBgNVBAYT
AlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3Qg
RUNDIFJvb3QtMDEwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARLNumuV16ocNfQj3Rid8NeeqrltqLx
eP0CflfdkXmcbLlSiFS8LwS+uM32ENEp7LXQoMPwiXAZu1FlxUOcw5tjnSCDPgYLpkJEhRGnSjot
6dZoL0hOUysHP029uax3OVejQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G
A1UdDgQWBBSOB2LAUN3GGQYARnQE9/OufXVNMDAKBggqhkjOPQQDAwNoADBlAjEAnDPfQeMjqEI2
Jpc1XHvr20v4qotzVRVcrHgpD7oh2MSg2NED3W3ROT3Ek2DS43KyAjB8xX6I01D1HiXo+k515liW
pDVfG2XqYZpwI7UNo5uSUm9poIyNStDuiw7LR47QjRE=
-----END CERTIFICATE-----

CommScope Public Trust ECC Root-02
==================================
-----BEGIN CERTIFICATE-----
MIICHDCCAaOgAwIBAgIUKP2ZYEFHpgE6yhR7H+/5aAiDXX0wCgYIKoZIzj0EAwMwTjELMAkGA1UE
BhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBUcnVz
dCBFQ0MgUm9vdC0wMjAeFw0yMTA0MjgxNzQ0NTRaFw00NjA0MjgxNzQ0NTNaME4xCzAJBgNVBAYT
AlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3Qg
RUNDIFJvb3QtMDIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAR4MIHoYx7l63FRD/cHB8o5mXxO1Q/M
MDALj2aTPs+9xYa9+bG3tD60B8jzljHz7aRP+KNOjSkVWLjVb3/ubCK1sK9IRQq9qEmUv4RDsNuE
SgMjGWdqb8FuvAY5N9GIIvejQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G
A1UdDgQWBBTmGHX/72DehKT1RsfeSlXjMjZ59TAKBggqhkjOPQQDAwNnADBkAjAmc0l6tqvmSfR9
Uj/UQQSugEODZXW5hYA4O9Zv5JOGq4/nich/m35rChJVYaoR4HkCMHfoMXGsPHED1oQmHhS48zs7
3u1Z/GtMMH9ZzkXpc2AVmkzw5l4lIhVtwodZ0LKOag==
-----END CERTIFICATE-----

CommScope Public Trust RSA Root-01
==================================
-----BEGIN CERTIFICATE-----
MIIFbDCCA1SgAwIBAgIUPgNJgXUWdDGOTKvVxZAplsU5EN0wDQYJKoZIhvcNAQELBQAwTjELMAkG
A1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBU
cnVzdCBSU0EgUm9vdC0wMTAeFw0yMTA0MjgxNjQ1NTRaFw00NjA0MjgxNjQ1NTNaME4xCzAJBgNV
BAYTAlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1
c3QgUlNBIFJvb3QtMDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwSGWjDR1C45Ft
nYSkYZYSwu3D2iM0GXb26v1VWvZVAVMP8syMl0+5UMuzAURWlv2bKOx7dAvnQmtVzslhsuitQDy6
uUEKBU8bJoWPQ7VAtYXR1HHcg0Hz9kXHgKKEUJdGzqAMxGBWBB0HW0alDrJLpA6lfO741GIDuZNq
ihS4cPgugkY4Iw50x2tBt9Apo52AsH53k2NC+zSDO3OjWiE260f6GBfZumbCk6SP/F2krfxQapWs
vCQz0b2If4b19bJzKo98rwjyGpg/qYFlP8GMicWWMJoKz/TUyDTtnS+8jTiGU+6Xn6myY5QXjQ/c
Zip8UlF1y5mO6D1cv547KI2DAg+pn3LiLCuz3GaXAEDQpFSOm117RTYm1nJD68/A6g3czhLmfTif
BSeolz7pUcZsBSjBAg/pGG3svZwG1KdJ9FQFa2ww8esD1eo9anbCyxooSU1/ZOD6K9pzg4H/kQO9
lLvkuI6cMmPNn7togbGEW682v3fuHX/3SZtS7NJ3Wn2RnU3COS3kuoL4b/JOHg9O5j9ZpSPcPYeo
KFgo0fEbNttPxP/hjFtyjMcmAyejOQoBqsCyMWCDIqFPEgkBEa801M/XrmLTBQe0MXXgDW1XT2mH
+VepuhX2yFJtocucH+X8eKg1mp9BFM6ltM6UCBwJrVbl2rZJmkrqYxhTnCwuwwIDAQABo0IwQDAP
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUN12mmnQywsL5x6YVEFm4
5P3luG0wDQYJKoZIhvcNAQELBQADggIBAK+nz97/4L1CjU3lIpbfaOp9TSp90K09FlxD533Ahuh6
NWPxzIHIxgvoLlI1pKZJkGNRrDSsBTtXAOnTYtPZKdVUvhwQkZyybf5Z/Xn36lbQnmhUQo8mUuJM
3y+Xpi/SB5io82BdS5pYV4jvguX6r2yBS5KPQJqTRlnLX3gWsWc+QgvfKNmwrZggvkN80V4aCRck
jXtdlemrwWCrWxhkgPut4AZ9HcpZuPN4KWfGVh2vtrV0KnahP/t1MJ+UXjulYPPLXAziDslg+Mkf
Foom3ecnf+slpoq9uC02EJqxWE2aaE9gVOX2RhOOiKy8IUISrcZKiX2bwdgt6ZYD9KJ0DLwAHb/W
NyVntHKLr4W96ioDj8z7PEQkguIBpQtZtjSNMgsSDesnwv1B10A8ckYpwIzqug/xBpMu95yo9GA+
o/E4Xo4TwbM6l4c/ksp4qRyv0LAbJh6+cOx69TOY6lz/KwsETkPdY34Op054A5U+1C0wlREQKC6/
oAI+/15Z0wUOlV9TRe9rh9VIzRamloPh37MG88EU26fsHItdkJANclHnYfkUyq+Dj7+vsQpZXdxc
1+SWrVtgHdqul7I52Qb1dgAT+GhMIbA1xNxVssnBQVocicCMb3SgazNNtQEo/a2tiRc7ppqEvOuM
6sRxJKi6KfkIsidWNTJf6jn7MZrVGczw
-----END CERTIFICATE-----

CommScope Public Trust RSA Root-02
==================================
-----BEGIN CERTIFICATE-----
MIIFbDCCA1SgAwIBAgIUVBa/O345lXGN0aoApYYNK496BU4wDQYJKoZIhvcNAQELBQAwTjELMAkG
A1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29tbVNjb3BlIFB1YmxpYyBU
cnVzdCBSU0EgUm9vdC0wMjAeFw0yMTA0MjgxNzE2NDNaFw00NjA0MjgxNzE2NDJaME4xCzAJBgNV
BAYTAlVTMRIwEAYDVQQKDAlDb21tU2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1
c3QgUlNBIFJvb3QtMDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDh+g77aAASyE3V
rCLENQE7xVTlWXZjpX/rwcRqmL0yjReA61260WI9JSMZNRTpf4mnG2I81lDnNJUDMrG0kyI9p+Kx
7eZ7Ti6Hmw0zdQreqjXnfuU2mKKuJZ6VszKWpCtYHu8//mI0SFHRtI1CrWDaSWqVcN3SAOLMV2MC
e5bdSZdbkk6V0/nLKR8YSvgBKtJjCW4k6YnS5cciTNxzhkcAqg2Ijq6FfUrpuzNPDlJwnZXjfG2W
Wy09X6GDRl224yW4fKcZgBzqZUPckXk2LHR88mcGyYnJ27/aaL8j7dxrrSiDeS/sOKUNNwFnJ5rp
M9kzXzehxfCrPfp4sOcsn/Y+n2Dg70jpkEUeBVF4GiwSLFworA2iI540jwXmojPOEXcT1A6kHkIf
hs1w/tkuFT0du7jyU1fbzMZ0KZwYszZ1OC4PVKH4kh+Jlk+71O6d6Ts2QrUKOyrUZHk2EOH5kQMr
eyBUzQ0ZGshBMjTRsJnhkB4BQDa1t/qp5Xd1pCKBXbCL5CcSD1SIxtuFdOa3wNemKfrb3vOTlycE
VS8KbzfFPROvCgCpLIscgSjX74Yxqa7ybrjKaixUR9gqiC6vwQcQeKwRoi9C8DfF8rhW3Q5iLc4t
Vn5V8qdE9isy9COoR+jUKgF4z2rDN6ieZdIs5fq6M8EGRPbmz6UNp2YINIos8wIDAQABo0IwQDAP
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUR9DnsSL/nSz12Vdgs7Gx
cJXvYXowDQYJKoZIhvcNAQELBQADggIBAIZpsU0v6Z9PIpNojuQhmaPORVMbc0RTAIFhzTHjCLqB
KCh6krm2qMhDnscTJk3C2OVVnJJdUNjCK9v+5qiXz1I6JMNlZFxHMaNlNRPDk7n3+VGXu6TwYofF
1gbTl4MgqX67tiHCpQ2EAOHyJxCDut0DgdXdaMNmEMjRdrSzbymeAPnCKfWxkxlSaRosTKCL4BWa
MS/TiJVZbuXEs1DIFAhKm4sTg7GkcrI7djNB3NyqpgdvHSQSn8h2vS/ZjvQs7rfSOBAkNlEv41xd
gSGn2rtO/+YHqP65DSdsu3BaVXoT6fEqSWnHX4dXTEN5bTpl6TBcQe7rd6VzEojov32u5cSoHw2O
HG1QAk8mGEPej1WFsQs3BWDJVTkSBKEqz3EWnzZRSb9wO55nnPt7eck5HHisd5FUmrh1CoFSl+Nm
YWvtPjgelmFV4ZFUjO2MJB+ByRCac5krFk5yAD9UG/iNuovnFNa2RU9g7Jauwy8CTl2dlklyALKr
dVwPaFsdZcJfMw8eD/A7hvWwTruc9+olBdytoptLFwG+Qt81IR2tq670v64fG9PiO/yzcnMcmyiQ
iRM9HcEARwmWmjgb3bHPDcK0RPOWlc4yOo80nOAXx17Org3bhzjlP1v9mxnhMUF6cKojawHhRUzN
lM47ni3niAIi9G7oyOzWPPO5std3eqx7
-----END CERTIFICATE-----

Telekom Security TLS ECC Root 2020
==================================
-----BEGIN CERTIFICATE-----
MIICQjCCAcmgAwIBAgIQNjqWjMlcsljN0AFdxeVXADAKBggqhkjOPQQDAzBjMQswCQYDVQQGEwJE
RTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBHbWJIMSswKQYDVQQDDCJUZWxl
a29tIFNlY3VyaXR5IFRMUyBFQ0MgUm9vdCAyMDIwMB4XDTIwMDgyNTA3NDgyMFoXDTQ1MDgyNTIz
NTk1OVowYzELMAkGA1UEBhMCREUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkg
R21iSDErMCkGA1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgRUNDIFJvb3QgMjAyMDB2MBAGByqG
SM49AgEGBSuBBAAiA2IABM6//leov9Wq9xCazbzREaK9Z0LMkOsVGJDZos0MKiXrPk/OtdKPD/M1
2kOLAoC+b1EkHQ9rK8qfwm9QMuU3ILYg/4gND21Ju9sGpIeQkpT0CdDPf8iAC8GXs7s1J8nCG6NC
MEAwHQYDVR0OBBYEFONyzG6VmUex5rNhTNHLq+O6zd6fMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P
AQH/BAQDAgEGMAoGCCqGSM49BAMDA2cAMGQCMHVSi7ekEE+uShCLsoRbQuHmKjYC2qBuGT8lv9pZ
Mo7k+5Dck2TOrbRBR2Diz6fLHgIwN0GMZt9Ba9aDAEH9L1r3ULRn0SyocddDypwnJJGDSA3PzfdU
ga/sf+Rn27iQ7t0l
-----END CERTIFICATE-----

Telekom Security TLS RSA Root 2023
==================================
-----BEGIN CERTIFICATE-----
MIIFszCCA5ugAwIBAgIQIZxULej27HF3+k7ow3BXlzANBgkqhkiG9w0BAQwFADBjMQswCQYDVQQG
EwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBHbWJIMSswKQYDVQQDDCJU
ZWxla29tIFNlY3VyaXR5IFRMUyBSU0EgUm9vdCAyMDIzMB4XDTIzMDMyODEyMTY0NVoXDTQ4MDMy
NzIzNTk1OVowYzELMAkGA1UEBhMCREUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJp
dHkgR21iSDErMCkGA1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgUlNBIFJvb3QgMjAyMzCCAiIw
DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAO01oYGA88tKaVvC+1GDrib94W7zgRJ9cUD/h3VC
KSHtgVIs3xLBGYSJwb3FKNXVS2xE1kzbB5ZKVXrKNoIENqil/Cf2SfHVcp6R+SPWcHu79ZvB7JPP
GeplfohwoHP89v+1VmLhc2o0mD6CuKyVU/QBoCcHcqMAU6DksquDOFczJZSfvkgdmOGjup5czQRx
UX11eKvzWarE4GC+j4NSuHUaQTXtvPM6Y+mpFEXX5lLRbtLevOP1Czvm4MS9Q2QTps70mDdsipWo
l8hHD/BeEIvnHRz+sTugBTNoBUGCwQMrAcjnj02r6LX2zWtEtefdi+zqJbQAIldNsLGyMcEWzv/9
FIS3R/qy8XDe24tsNlikfLMR0cN3f1+2JeANxdKz+bi4d9s3cXFH42AYTyS2dTd4uaNir73Jco4v
zLuu2+QVUhkHM/tqty1LkCiCc/4YizWN26cEar7qwU02OxY2kTLvtkCJkUPg8qKrBC7m8kwOFjQg
rIfBLX7JZkcXFBGk8/ehJImr2BrIoVyxo/eMbcgByU/J7MT8rFEz0ciD0cmfHdRHNCk+y7AO+oML
KFjlKdw/fKifybYKu6boRhYPluV75Gp6SG12mAWl3G0eQh5C2hrgUve1g8Aae3g1LDj1H/1Joy7S
WWO/gLCMk3PLNaaZlSJhZQNg+y+TS/qanIA7AgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAdBgNV
HQ4EFgQUtqeXgj10hZv3PJ+TmpV5dVKMbUcwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS2
p5eCPXSFm/c8n5OalXl1UoxtRzANBgkqhkiG9w0BAQwFAAOCAgEAqMxhpr51nhVQpGv7qHBFfLp+
sVr8WyP6Cnf4mHGCDG3gXkaqk/QeoMPhk9tLrbKmXauw1GLLXrtm9S3ul0A8Yute1hTWjOKWi0Fp
kzXmuZlrYrShF2Y0pmtjxrlO8iLpWA1WQdH6DErwM807u20hOq6OcrXDSvvpfeWxm4bu4uB9tPcy
/SKE8YXJN3nptT+/XOR0so8RYgDdGGah2XsjX/GO1WfoVNpbOms2b/mBsTNHM3dA+VKq3dSDz4V4
mZqTuXNnQkYRIer+CqkbGmVps4+uFrb2S1ayLfmlyOw7YqPta9BO1UAJpB+Y1zqlklkg5LB9zVtz
aL1txKITDmcZuI1CfmwMmm6gJC3VRRvcxAIU/oVbZZfKTpBQCHpCNfnqwmbU+AGuHrS+w6jv/naa
oqYfRvaE7fzbzsQCzndILIyy7MMAo+wsVRjBfhnu4S/yrYObnqsZ38aKL4x35bcF7DvB7L6Gs4a8
wPfc5+pbrrLMtTWGS9DiP7bY+A4A7l3j941Y/8+LN+ljX273CXE2whJdV/LItM3z7gLfEdxquVeE
HVlNjM7IDiPCtyaaEBRx/pOyiriA8A4QntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0
o82bNSQ3+pCTE4FCxpgmdTdmQRCsu/WU48IxK63nI1bMNSWSs1A=
-----END CERTIFICATE-----

FIRMAPROFESIONAL CA ROOT-A WEB
==============================
-----BEGIN CERTIFICATE-----
MIICejCCAgCgAwIBAgIQMZch7a+JQn81QYehZ1ZMbTAKBggqhkjOPQQDAzBuMQswCQYDVQQGEwJF
UzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UEYQwPVkFURVMtQTYyNjM0MDY4
MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENBIFJPT1QtQSBXRUIwHhcNMjIwNDA2MDkwMTM2
WhcNNDcwMzMxMDkwMTM2WjBuMQswCQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25h
bCBTQTEYMBYGA1UEYQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFM
IENBIFJPT1QtQSBXRUIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARHU+osEaR3xyrq89Zfe9MEkVz6
iMYiuYMQYneEMy3pA4jU4DP37XcsSmDq5G+tbbT4TIqk5B/K6k84Si6CcyvHZpsKjECcfIr28jlg
st7L7Ljkb+qbXbdTkBgyVcUgt5SjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUk+FD
Y1w8ndYn81LsF7Kpryz3dvgwHQYDVR0OBBYEFJPhQ2NcPJ3WJ/NS7Beyqa8s93b4MA4GA1UdDwEB
/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjAdfKR7w4l1M+E7qUW/Runpod3JIha3RxEL2Jq68cgL
cFBTApFwhVmpHqTm6iMxoAACMQD94vizrxa5HnPEluPBMBnYfubDl94cT7iJLzPrSA8Z94dGXSaQ
pYXFuXqUPoeovQA=
-----END CERTIFICATE-----

TWCA CYBER Root CA
==================
-----BEGIN CERTIFICATE-----
MIIFjTCCA3WgAwIBAgIQQAE0jMIAAAAAAAAAATzyxjANBgkqhkiG9w0BAQwFADBQMQswCQYDVQQG
EwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NB
IENZQkVSIFJvb3QgQ0EwHhcNMjIxMTIyMDY1NDI5WhcNNDcxMTIyMTU1OTU5WjBQMQswCQYDVQQG
EwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NB
IENZQkVSIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDG+Moe2Qkgfh1s
Ts6P40czRJzHyWmqOlt47nDSkvgEs1JSHWdyKKHfi12VCv7qze33Kc7wb3+szT3vsxxFavcokPFh
V8UMxKNQXd7UtcsZyoC5dc4pztKFIuwCY8xEMCDa6pFbVuYdHNWdZsc/34bKS1PE2Y2yHer43CdT
o0fhYcx9tbD47nORxc5zb87uEB8aBs/pJ2DFTxnk684iJkXXYJndzk834H/nY62wuFm40AZoNWDT
Nq5xQwTxaWV4fPMf88oon1oglWa0zbfuj3ikRRjpJi+NmykosaS3Om251Bw4ckVYsV7r8Cibt4LK
/c/WMw+f+5eesRycnupfXtuq3VTpMCEobY5583WSjCb+3MX2w7DfRFlDo7YDKPYIMKoNM+HvnKkH
IuNZW0CP2oi3aQiotyMuRAlZN1vH4xfyIutuOVLF3lSnmMlLIJXcRolftBL5hSmO68gnFSDAS9TM
fAxsNAwmmyYxpjyn9tnQS6Jk/zuZQXLB4HCX8SS7K8R0IrGsayIyJNN4KsDAoS/xUgXJP+92ZuJF
2A09rZXIx4kmyA+upwMu+8Ff+iDhcK2wZSA3M2Cw1a/XDBzCkHDXShi8fgGwsOsVHkQGzaRP6AzR
wyAQ4VRlnrZR0Bp2a0JaWHY06rc3Ga4udfmW5cFZ95RXKSWNOkyrTZpB0F8mAwIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSdhWEUfMFib5do5E83
QOGt4A1WNzAdBgNVHQ4EFgQUnYVhFHzBYm+XaORPN0DhreANVjcwDQYJKoZIhvcNAQEMBQADggIB
AGSPesRiDrWIzLjHhg6hShbNcAu3p4ULs3a2D6f/CIsLJc+o1IN1KriWiLb73y0ttGlTITVX1olN
c79pj3CjYcya2x6a4CD4bLubIp1dhDGaLIrdaqHXKGnK/nZVekZn68xDiBaiA9a5F/gZbG0jAn/x
X9AKKSM70aoK7akXJlQKTcKlTfjF/biBzysseKNnTKkHmvPfXvt89YnNdJdhEGoHK4Fa0o635yDR
IG4kqIQnoVesqlVYL9zZyvpoBJ7tRCT5dEA7IzOrg1oYJkK2bVS1FmAwbLGg+LhBoF1JSdJlBTrq
/p1hvIbZv97Tujqxf36SNI7JAG7cmL3c7IAFrQI932XtCwP39xaEBDG6k5TY8hL4iuO/Qq+n1M0R
FxbIQh0UqEL20kCGoE8jypZFVmAGzbdVAaYBlGX+bgUJurSkquLvWL69J1bY73NxW0Qz8ppy6rBe
Pm6pUlvscG21h483XjyMnM7k8M4MZ0HMzvaAq07MTFb1wWFZk7Q+ptq4NxKfKjLji7gh7MMrZQzv
It6IKTtM1/r+t+FHvpw+PoP7UV31aPcuIYXcv/Fa4nzXxeSDwWrruoBa3lwtcHb4yOWHh8qgnaHl
IhInD0Q9HWzq1MKLL295q39QpsQZp6F6t5b5wR9iWqJDB0BeJsas7a5wFsWqynKKTbDPAYsDP27X
-----END CERTIFICATE-----

SecureSign Root CA12
====================
-----BEGIN CERTIFICATE-----
MIIDcjCCAlqgAwIBAgIUZvnHwa/swlG07VOX5uaCwysckBYwDQYJKoZIhvcNAQELBQAwUTELMAkG
A1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMR0wGwYDVQQDExRT
ZWN1cmVTaWduIFJvb3QgQ0ExMjAeFw0yMDA0MDgwNTM2NDZaFw00MDA0MDgwNTM2NDZaMFExCzAJ
BgNVBAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMU
U2VjdXJlU2lnbiBSb290IENBMTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6OcE3
emhFKxS06+QT61d1I02PJC0W6K6OyX2kVzsqdiUzg2zqMoqUm048luT9Ub+ZyZN+v/mtp7JIKwcc
J/VMvHASd6SFVLX9kHrko+RRWAPNEHl57muTH2SOa2SroxPjcf59q5zdJ1M3s6oYwlkm7Fsf0uZl
fO+TvdhYXAvA42VvPMfKWeP+bl+sg779XSVOKik71gurFzJ4pOE+lEa+Ym6b3kaosRbnhW70CEBF
EaCeVESE99g2zvVQR9wsMJvuwPWW0v4JhscGWa5Pro4RmHvzC1KqYiaqId+OJTN5lxZJjfU+1Uef
NzFJM3IFTQy2VYzxV4+Kh9GtxRESOaCtAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P
AQH/BAQDAgEGMB0GA1UdDgQWBBRXNPN0zwRL1SXm8UC2LEzZLemgrTANBgkqhkiG9w0BAQsFAAOC
AQEAPrvbFxbS8hQBICw4g0utvsqFepq2m2um4fylOqyttCg6r9cBg0krY6LdmmQOmFxv3Y67ilQi
LUoT865AQ9tPkbeGGuwAtEGBpE/6aouIs3YIcipJQMPTw4WJmBClnW8Zt7vPemVV2zfrPIpyMpce
mik+rY3moxtt9XUa5rBouVui7mlHJzWhhpmA8zNL4WukJsPvdFlseqJkth5Ew1DgDzk9qTPxpfPS
vWKErI4cqc1avTc7bgoitPQV55FYxTpE05Uo2cBl6XLK0A+9H7MV2anjpEcJnuDLN/v9vZfVvhga
aaI5gdka9at/yOPiZwud9AzqVN/Ssq+xIvEg37xEHA==
-----END CERTIFICATE-----

SecureSign Root CA14
====================
-----BEGIN CERTIFICATE-----
MIIFcjCCA1qgAwIBAgIUZNtaDCBO6Ncpd8hQJ6JaJ90t8sswDQYJKoZIhvcNAQEMBQAwUTELMAkG
A1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMR0wGwYDVQQDExRT
ZWN1cmVTaWduIFJvb3QgQ0ExNDAeFw0yMDA0MDgwNzA2MTlaFw00NTA0MDgwNzA2MTlaMFExCzAJ
BgNVBAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMU
U2VjdXJlU2lnbiBSb290IENBMTQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDF0nqh
1oq/FjHQmNE6lPxauG4iwWL3pwon71D2LrGeaBLwbCRjOfHw3xDG3rdSINVSW0KZnvOgvlIfX8xn
bacuUKLBl422+JX1sLrcneC+y9/3OPJH9aaakpUqYllQC6KxNedlsmGy6pJxaeQp8E+BgQQ8sqVb
1MWoWWd7VRxJq3qdwudzTe/NCcLEVxLbAQ4jeQkHO6Lo/IrPj8BGJJw4J+CDnRugv3gVEOuGTgpa
/d/aLIJ+7sr2KeH6caH3iGicnPCNvg9JkdjqOvn90Ghx2+m1K06Ckm9mH+Dw3EzsytHqunQG+bOE
kJTRX45zGRBdAuVwpcAQ0BB8b8VYSbSwbprafZX1zNoCr7gsfXmPvkPx+SgojQlD+Ajda8iLLCSx
jVIHvXiby8posqTdDEx5YMaZ0ZPxMBoH064iwurO8YQJzOAUbn8/ftKChazcqRZOhaBgy/ac18iz
ju3Gm5h1DVXoX+WViwKkrkMpKBGk5hIwAUt1ax5mnXkvpXYvHUC0bcl9eQjs0Wq2XSqypWa9a4X0
dFbD9ed1Uigspf9mR6XU/v6eVL9lfgHWMI+lNpyiUBzuOIABSMbHdPTGrMNASRZhdCyvjG817XsY
AFs2PJxQDcqSMxDxJklt33UkN4Ii1+iW/RVLApY+B3KVfqs9TC7XyvDf4Fg/LS8EmjijAQIDAQAB
o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUBpOjCl4oaTeq
YR3r6/wtbyPk86AwDQYJKoZIhvcNAQEMBQADggIBAJaAcgkGfpzMkwQWu6A6jZJOtxEaCnFxEM0E
rX+lRVAQZk5KQaID2RFPeje5S+LGjzJmdSX7684/AykmjbgWHfYfM25I5uj4V7Ibed87hwriZLoA
ymzvftAj63iP/2SbNDefNWWipAA9EiOWWF3KY4fGoweITedpdopTzfFP7ELyk+OZpDc8h7hi2/Ds
Hzc/N19DzFGdtfCXwreFamgLRB7lUe6TzktuhsHSDCRZNhqfLJGP4xjblJUK7ZGqDpncllPjYYPG
FrojutzdfhrGe0K22VoF3Jpf1d+42kd92jjbrDnVHmtsKheMYc2xbXIBw8MgAGJoFjHVdqqGuw6q
nsb58Nn4DSEC5MUoFlkRudlpcyqSeLiSV5sI8jrlL5WwWLdrIBRtFO8KvH7YVdiI2i/6GaX7i+B/
OfVyK4XELKzvGUWSTLNhB9xNH27SgRNcmvMSZ4PPmz+Ln52kuaiWA3rF7iDeM9ovnhp6dB7h7sxa
OgTdsxoEqBRjrLdHEoOabPXm6RUVkRqEGQ6UROcSjiVbgGcZ3GOTEAtlLor6CZpO2oYofaphNdgO
pygau1LgePhsumywbrmHXumZNTfxPWQrqaA0k89jL9WB365jJ6UeTo3cKXhZ+PmhIIynJkBugnLN
eLLIjzwec+fBH7/PzqUqm9tEZDKgu39cJRNItX+S
-----END CERTIFICATE-----

SecureSign Root CA15
====================
-----BEGIN CERTIFICATE-----
MIICIzCCAamgAwIBAgIUFhXHw9hJp75pDIqI7fBw+d23PocwCgYIKoZIzj0EAwMwUTELMAkGA1UE
BhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBMdGQuMR0wGwYDVQQDExRTZWN1
cmVTaWduIFJvb3QgQ0ExNTAeFw0yMDA0MDgwODMyNTZaFw00NTA0MDgwODMyNTZaMFExCzAJBgNV
BAYTAkpQMSMwIQYDVQQKExpDeWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2Vj
dXJlU2lnbiBSb290IENBMTUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQLUHSNZDKZmbPSYAi4Io5G
dCx4wCtELW1fHcmuS1Iggz24FG1Th2CeX2yF2wYUleDHKP+dX+Sq8bOLbe1PL0vJSpSRZHX+AezB
2Ot6lHhWGENfa4HL9rzatAy2KZMIaY+jQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD
AgEGMB0GA1UdDgQWBBTrQciu/NWeUUj1vYv0hyCTQSvT9DAKBggqhkjOPQQDAwNoADBlAjEA2S6J
fl5OpBEHvVnCB96rMjhTKkZEBhd6zlHp4P9mLQlO4E/0BdGF9jVg3PVys0Z9AjBEmEYagoUeYWmJ
SwdLZrWeqrqgHkHZAXQ6bkU6iYAZezKYVWOr62Nuk22rGwlgMU4=
-----END CERTIFICATE-----

D-TRUST BR Root CA 2 2023
=========================
-----BEGIN CERTIFICATE-----
MIIFqTCCA5GgAwIBAgIQczswBEhb2U14LnNLyaHcZjANBgkqhkiG9w0BAQ0FADBIMQswCQYDVQQG
EwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEJSIFJvb3QgQ0Eg
MiAyMDIzMB4XDTIzMDUwOTA4NTYzMVoXDTM4MDUwOTA4NTYzMFowSDELMAkGA1UEBhMCREUxFTAT
BgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDIgMjAyMzCC
AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK7/CVmRgApKaOYkP7in5Mg6CjoWzckjYaCT
cfKri3OPoGdlYNJUa2NRb0kz4HIHE304zQaSBylSa053bATTlfrdTIzZXcFhfUvnKLNEgXtRr90z
sWh81k5M/itoucpmacTsXld/9w3HnDY25QdgrMBM6ghs7wZ8T1soegj8k12b9py0i4a6Ibn08OhZ
WiihNIQaJZG2tY/vsvmA+vk9PBFy2OMvhnbFeSzBqZCTRphny4NqoFAjpzv2gTng7fC5v2Xx2Mt6
++9zA84A9H3X4F07ZrjcjrqDy4d2A/wl2ecjbwb9Z/Pg/4S8R7+1FhhGaRTMBffb00msa8yr5LUL
QyReS2tNZ9/WtT5PeB+UcSTq3nD88ZP+npNa5JRal1QMNXtfbO4AHyTsA7oC9Xb0n9Sa7YUsOCIv
x9gvdhFP/Wxc6PWOJ4d/GUohR5AdeY0cW/jPSoXk7bNbjb7EZChdQcRurDhaTyN0dKkSw/bSuREV
MweR2Ds3OmMwBtHFIjYoYiMQ4EbMl6zWK11kJNXuHA7e+whadSr2Y23OC0K+0bpwHJwh5Q8xaRfX
/Aq03u2AnMuStIv13lmiWAmlY0cL4UEyNEHZmrHZqLAbWt4NDfTisl01gLmB1IRpkQLLddCNxbU9
CZEJjxShFHR5PtbJFR2kWVki3PaKRT08EtY+XTIvAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB
/zAdBgNVHQ4EFgQUZ5Dw1t61GNVGKX5cq/ieCLxklRAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRC
MEAwPqA8oDqGOGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfYnJfcm9vdF9jYV8y
XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQA097N3U9swFrktpSHxQCF16+tIFoE9c+CeJyrr
d6kTpGoKWloUMz1oH4Guaf2Mn2VsNELZLdB/eBaxOqwjMa1ef67nriv6uvw8l5VAk1/DLQOj7aRv
U9f6QA4w9QAgLABMjDu0ox+2v5Eyq6+SmNMW5tTRVFxDWy6u71cqqLRvpO8NVhTaIasgdp4D/Ca4
nj8+AybmTNudX0KEPUUDAxxZiMrcLmEkWqTqJwtzEr5SswrPMhfiHocaFpVIbVrg0M8JkiZmkdij
YQ6qgYF/6FKC0ULn4B0Y+qSFNueG4A3rvNTJ1jxD8V1Jbn6Bm2m1iWKPiFLY1/4nwSPFyysCu7Ff
/vtDhQNGvl3GyiEm/9cCnnRK3PgTFbGBVzbLZVzRHTF36SXDw7IyN9XxmAnkbWOACKsGkoHU6XCP
pz+y7YaMgmo1yEJagtFSGkUPFaUA8JR7ZSdXOUPPfH/mvTWze/EZTN46ls/pdu4D58JDUjxqgejB
WoC9EV2Ta/vH5mQ/u2kc6d0li690yVRAysuTEwrt+2aSEcr1wPrYg1UDfNPFIkZ1cGt5SAYqgpq/
5usWDiJFAbzdNpQ0qTUmiteXue4Icr80knCDgKs4qllo3UCkGJCy89UDyibK79XH4I9TjvAA46jt
n/mtd+ArY0+ew+43u3gJhJ65bvspmZDogNOfJA==
-----END CERTIFICATE-----

TrustAsia TLS ECC Root CA
=========================
-----BEGIN CERTIFICATE-----
MIICMTCCAbegAwIBAgIUNnThTXxlE8msg1UloD5Sfi9QaMcwCgYIKoZIzj0EAwMwWDELMAkGA1UE
BhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xIjAgBgNVBAMTGVRy
dXN0QXNpYSBUTFMgRUNDIFJvb3QgQ0EwHhcNMjQwNTE1MDU0MTU2WhcNNDQwNTE1MDU0MTU1WjBY
MQswCQYDVQQGEwJDTjElMCMGA1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEiMCAG
A1UEAxMZVHJ1c3RBc2lhIFRMUyBFQ0MgUm9vdCBDQTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLh/
pVs/AT598IhtrimY4ZtcU5nb9wj/1WrgjstEpvDBjL1P1M7UiFPoXlfXTr4sP/MSpwDpguMqWzJ8
S5sUKZ74LYO1644xST0mYekdcouJtgq7nDM1D9rs3qlKH8kzsaNCMEAwDwYDVR0TAQH/BAUwAwEB
/zAdBgNVHQ4EFgQULIVTu7FDzTLqnqOH/qKYqKaT6RAwDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49
BAMDA2gAMGUCMFRH18MtYYZI9HlaVQ01L18N9mdsd0AaRuf4aFtOJx24mH1/k78ITcTaRTChD15K
eAIxAKORh/IRM4PDwYqROkwrULG9IpRdNYlzg8WbGf60oenUoWa2AaU2+dhoYSi3dOGiMQ==
-----END CERTIFICATE-----

TrustAsia TLS RSA Root CA
=========================
-----BEGIN CERTIFICATE-----
MIIFgDCCA2igAwIBAgIUHBjYz+VTPyI1RlNUJDxsR9FcSpwwDQYJKoZIhvcNAQEMBQAwWDELMAkG
A1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dpZXMsIEluYy4xIjAgBgNVBAMT
GVRydXN0QXNpYSBUTFMgUlNBIFJvb3QgQ0EwHhcNMjQwNTE1MDU0MTU3WhcNNDQwNTE1MDU0MTU2
WjBYMQswCQYDVQQGEwJDTjElMCMGA1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEi
MCAGA1UEAxMZVHJ1c3RBc2lhIFRMUyBSU0EgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIP
ADCCAgoCggIBAMMWuBtqpERz5dZO9LnPWwvB0ZqB9WOwj0PBuwhaGnrhB3YmH49pVr7+NmDQDIPN
lOrnxS1cLwUWAp4KqC/lYCZUlviYQB2srp10Zy9U+5RjmOMmSoPGlbYJQ1DNDX3eRA5gEk9bNb2/
mThtfWza4mhzH/kxpRkQcwUqwzIZheo0qt1CHjCNP561HmHVb70AcnKtEj+qpklz8oYVlQwQX1Fk
zv93uMltrOXVmPGZLmzjyUT5tUMnCE32ft5EebuyjBza00tsLtbDeLdM1aTk2tyKjg7/D8OmYCYo
zza/+lcK7Fs/6TAWe8TbxNRkoDD75f0dcZLdKY9BWN4ArTr9PXwaqLEX8E40eFgl1oUh63kd0Nyr
z2I8sMeXi9bQn9P+PN7F4/w6g3CEIR0JwqH8uyghZVNgepBtljhb//HXeltt08lwSUq6HTrQUNoy
IBnkiz/r1RYmNzz7dZ6wB3C4FGB33PYPXFIKvF1tjVEK2sUYyJtt3LCDs3+jTnhMmCWr8n4uIF6C
FabW2I+s5c0yhsj55NqJ4js+k8UTav/H9xj8Z7XvGCxUq0DTbE3txci3OE9kxJRMT6DNrqXGJyV1
J23G2pyOsAWZ1SgRxSHUuPzHlqtKZFlhaxP8S8ySpg+kUb8OWJDZgoM5pl+z+m6Ss80zDoWo8SnT
q1mt1tve1CuBAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLgHkXlcBvRG/XtZ
ylomkadFK/hTMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQwFAAOCAgEAIZtqBSBdGBanEqT3
Rz/NyjuujsCCztxIJXgXbODgcMTWltnZ9r96nBO7U5WS/8+S4PPFJzVXqDuiGev4iqME3mmL5Dw8
veWv0BIb5Ylrc5tvJQJLkIKvQMKtuppgJFqBTQUYo+IzeXoLH5Pt7DlK9RME7I10nYEKqG/odv6L
TytpEoYKNDbdgptvT+Bz3Ul/KD7JO6NXBNiT2Twp2xIQaOHEibgGIOcberyxk2GaGUARtWqFVwHx
tlotJnMnlvm5P1vQiJ3koP26TpUJg3933FEFlJ0gcXax7PqJtZwuhfG5WyRasQmr2soaB82G39tp
27RIGAAtvKLEiUUjpQ7hRGU+isFqMB3iYPg6qocJQrmBktwliJiJ8Xw18WLK7nn4GS/+X/jbh87q
qA8MpugLoDzga5SYnH+tBuYc6kIQX+ImFTw3OffXvO645e8D7r0i+yiGNFjEWn9hongPXvPKnbwb
PKfILfanIhHKA9jnZwqKDss1jjQ52MjqjZ9k4DewbNfFj8GQYSbbJIweSsCI3zWQzj8C9GRh3sfI
B5XeMhg6j6JCQCTl1jNdfK7vsU1P1FeQNWrcrgSXSYk0ly4wBOeY99sLAZDBHwo/+ML+TvrbmnNz
FrwFuHnYWa8G5z9nODmxfKuU4CkUpijy323imttUQ/hHWKNddBWcwauwxzQ=
-----END CERTIFICATE-----

D-TRUST EV Root CA 2 2023
=========================
-----BEGIN CERTIFICATE-----
MIIFqTCCA5GgAwIBAgIQaSYJfoBLTKCnjHhiU19abzANBgkqhkiG9w0BAQ0FADBIMQswCQYDVQQG
EwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRSVVNUIEVWIFJvb3QgQ0Eg
MiAyMDIzMB4XDTIzMDUwOTA5MTAzM1oXDTM4MDUwOTA5MTAzMlowSDELMAkGA1UEBhMCREUxFTAT
BgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDIgMjAyMzCC
AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANiOo4mAC7JXUtypU0w3uX9jFxPvp1sjW2l1
sJkKF8GLxNuo4MwxusLyzV3pt/gdr2rElYfXR8mV2IIEUD2BCP/kPbOx1sWy/YgJ25yE7CUXFId/
MHibaljJtnMoPDT3mfd/06b4HEV8rSyMlD/YZxBTfiLNTiVR8CUkNRFeEMbsh2aJgWi6zCudR3Mf
vc2RpHJqnKIbGKBv7FD0fUDCqDDPvXPIEysQEx6Lmqg6lHPTGGkKSv/BAQP/eX+1SH977ugpbzZM
lWGG2Pmic4ruri+W7mjNPU0oQvlFKzIbRlUWaqZLKfm7lVa/Rh3sHZMdwGWyH6FDrlaeoLGPaxK3
YG14C8qKXO0elg6DpkiVjTujIcSuWMYAsoS0I6SWhjW42J7YrDRJmGOVxcttSEfi8i4YHtAxq910
7PncjLgcjmgjutDzUNzPZY9zOjLHfP7KgiJPvo5iR2blzYfi6NUPGJ/lBHJLRjwQ8kTCZFZxTnXo
nMkmdMV9WdEKWw9t/p51HBjGGjp82A0EzM23RWV6sY+4roRIPrN6TagD4uJ+ARZZaBhDM7DS3LAa
QzXupdqpRlyuhoFBAUp0JuyfBr/CBTdkdXgpaP3F9ev+R/nkhbDhezGdpn9yo7nELC7MmVcOIQxF
AZRl62UJxmMiCzNJkkg8/M3OsD6Onov4/knFNXJHAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB
/zAdBgNVHQ4EFgQUqvyREBuHkV8Wub9PS5FeAByxMoAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRC
MEAwPqA8oDqGOGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfZXZfcm9vdF9jYV8y
XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQCTy6UfmRHsmg1fLBWTxj++EI14QvBukEdHjqOS
Mo1wj/Zbjb6JzkcBahsgIIlbyIIQbODnmaprxiqgYzWRaoUlrRc4pZt+UPJ26oUFKidBK7GB0aL2
QHWpDsvxVUjY7NHss+jOFKE17MJeNRqrphYBBo7q3C+jisosketSjl8MmxfPy3MHGcRqwnNU73xD
UmPBEcrCRbH0O1P1aa4846XerOhUt7KR/aypH/KH5BfGSah82ApB9PI+53c0BFLd6IHyTS9URZ0V
4U/M5d40VxDJI3IXcI1QcB9WbMy5/zpaT2N6w25lBx2Eof+pDGOJbbJAiDnXH3dotfyc1dZnaVuo
dNv8ifYbMvekJKZ2t0dT741Jj6m2g1qllpBFYfXeA08mD6iL8AOWsKwV0HFaanuU5nCT2vFp4LJi
TZ6P/4mdm13NRemUAiKN4DV/6PEEeXFsVIP4M7kFMhtYVRFP0OUnR3Hs7dpn1mKmS00PaaLJvOwi
S5THaJQXfuKOKD62xur1NGyfN4gHONuGcfrNlUhDbqNPgofXNJhuS5N5YHVpD/Aa1VP6IQzCP+k/
HxiMkl14p3ZnGbuy6n/pcAlWVqOwDAstNl7F6cTVg8uGF5csbBNvh1qvSaYd2804BC5f4ko1Di1L
+KIkBI3Y4WNeApI02phhXBxvWHZks/wCuPWdCg==
-----END CERTIFICATE-----

SwissSign RSA TLS Root CA 2022 - 1
==================================
-----BEGIN CERTIFICATE-----
MIIFkzCCA3ugAwIBAgIUQ/oMX04bgBhE79G0TzUfRPSA7cswDQYJKoZIhvcNAQELBQAwUTELMAkG
A1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzErMCkGA1UEAxMiU3dpc3NTaWduIFJTQSBU
TFMgUm9vdCBDQSAyMDIyIC0gMTAeFw0yMjA2MDgxMTA4MjJaFw00NzA2MDgxMTA4MjJaMFExCzAJ
BgNVBAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxKzApBgNVBAMTIlN3aXNzU2lnbiBSU0Eg
VExTIFJvb3QgQ0EgMjAyMiAtIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDLKmji
C8NXvDVjvHClO/OMPE5Xlm7DTjak9gLKHqquuN6orx122ro10JFwB9+zBvKK8i5VUXu7LCTLf5Im
gKO0lPaCoaTo+nUdWfMHamFk4saMla+ju45vVs9xzF6BYQ1t8qsCLqSX5XH8irCRIFucdFJtrhUn
WXjyCcplDn/L9Ovn3KlMd/YrFgSVrpxxpT8q2kFC5zyEEPThPYxr4iuRR1VPuFa+Rd4iUU1OKNlf
GUEGjw5NBuBwQCMBauTLE5tzrE0USJIt/m2n+IdreXXhvhCxqohAWVTXz8TQm0SzOGlkjIHRI36q
OTw7D59Ke4LKa2/KIj4x0LDQKhySio/YGZxH5D4MucLNvkEM+KRHBdvBFzA4OmnczcNpI/2aDwLO
EGrOyvi5KaM2iYauC8BPY7kGWUleDsFpswrzd34unYyzJ5jSmY0lpx+Gs6ZUcDj8fV3oT4MM0ZPl
EuRU2j7yrTrePjxF8CgPBrnh25d7mUWe3f6VWQQvdT/TromZhqwUtKiE+shdOxtYk8EXlFXIC+OC
eYSf8wCENO7cMdWP8vpPlkwGqnj73mSiI80fPsWMvDdUDrtaclXvyFu1cvh43zcgTFeRc5JzrBh3
Q4IgaezprClG5QtO+DdziZaKHG29777YtvTKwP1H8K4LWCDFyB02rpeNUIMmJCn3nTsPBQIDAQAB
o2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBRvjmKLk0Ow
4UD2p8P98Q+4DxU4pTAdBgNVHQ4EFgQUb45ii5NDsOFA9qfD/fEPuA8VOKUwDQYJKoZIhvcNAQEL
BQADggIBAKwsKUF9+lz1GpUYvyypiqkkVHX1uECry6gkUSsYP2OprphWKwVDIqO310aewCoSPY6W
lkDfDDOLazeROpW7OSltwAJsipQLBwJNGD77+3v1dj2b9l4wBlgzHqp41eZUBDqyggmNzhYzWUUo
8aWjlw5DI/0LIICQ/+Mmz7hkkeUFjxOgdg3XNwwQiJb0Pr6VvfHDffCjw3lHC1ySFWPtUnWK50Zp
y1FVCypM9fJkT6lc/2cyjlUtMoIcgC9qkfjLvH4YoiaoLqNTKIftV+Vlek4ASltOU8liNr3Cjlvr
zG4ngRhZi0Rjn9UMZfQpZX+RLOV/fuiJz48gy20HQhFRJjKKLjpHE7iNvUcNCfAWpO2Whi4Z2L6M
OuhFLhG6rlrnub+xzI/goP+4s9GFe3lmozm1O2bYQL7Pt2eLSMkZJVX8vY3PXtpOpvJpzv1/THfQ
wUY1mFwjmwJFQ5Ra3bxHrSL+ul4vkSkphnsh3m5kt8sNjzdbowhq6/TdAo9QAwKxuDdollDruF/U
KIqlIgyKhPBZLtU30WHlQnNYKoH3dtvi4k0NX/a3vgW0rk4N3hY9A4GzJl5LuEsAz/+MF7psYC0n
hzck5npgL7XTgwSqT0N1osGDsieYK7EOgLrAhV5Cud+xYJHT6xh+cHiudoO+cVrQkOPKwRYlZ0rw
tnu64ZzZ
-----END CERTIFICATE-----
<?php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer;

use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;

/**
 * This class is copied in every Composer installed project and available to all
 *
 * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
 *
 * To require its presence, you can require `composer-runtime-api ^2.0`
 *
 * @final
 */
class InstalledVersions
{
    /**
     * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
     * @internal
     */
    private static $selfDir = null;

    /**
     * @var mixed[]|null
     * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
     */
    private static $installed;

    /**
     * @var bool
     */
    private static $installedIsLocalDir;

    /**
     * @var bool|null
     */
    private static $canGetVendors;

    /**
     * @var array[]
     * @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
     */
    private static $installedByVendor = array();

    /**
     * Returns a list of all package names which are present, either by being installed, replaced or provided
     *
     * @return string[]
     * @psalm-return list<string>
     */
    public static function getInstalledPackages()
    {
        $packages = array();
        foreach (self::getInstalled() as $installed) {
            $packages[] = array_keys($installed['versions']);
        }

        if (1 === \count($packages)) {
            return $packages[0];
        }

        return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
    }

    /**
     * Returns a list of all package names with a specific type e.g. 'library'
     *
     * @param  string   $type
     * @return string[]
     * @psalm-return list<string>
     */
    public static function getInstalledPackagesByType($type)
    {
        $packagesByType = array();

        foreach (self::getInstalled() as $installed) {
            foreach ($installed['versions'] as $name => $package) {
                if (isset($package['type']) && $package['type'] === $type) {
                    $packagesByType[] = $name;
                }
            }
        }

        return $packagesByType;
    }

    /**
     * Checks whether the given package is installed
     *
     * This also returns true if the package name is provided or replaced by another package
     *
     * @param  string $packageName
     * @param  bool   $includeDevRequirements
     * @return bool
     */
    public static function isInstalled($packageName, $includeDevRequirements = true)
    {
        foreach (self::getInstalled() as $installed) {
            if (isset($installed['versions'][$packageName])) {
                return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
            }
        }

        return false;
    }

    /**
     * Checks whether the given package satisfies a version constraint
     *
     * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
     *
     *   Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
     *
     * @param  VersionParser $parser      Install composer/semver to have access to this class and functionality
     * @param  string        $packageName
     * @param  string|null   $constraint  A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
     * @return bool
     */
    public static function satisfies(VersionParser $parser, $packageName, $constraint)
    {
        $constraint = $parser->parseConstraints((string) $constraint);
        $provided = $parser->parseConstraints(self::getVersionRanges($packageName));

        return $provided->matches($constraint);
    }

    /**
     * Returns a version constraint representing all the range(s) which are installed for a given package
     *
     * It is easier to use this via isInstalled() with the $constraint argument if you need to check
     * whether a given version of a package is installed, and not just whether it exists
     *
     * @param  string $packageName
     * @return string Version constraint usable with composer/semver
     */
    public static function getVersionRanges($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            $ranges = array();
            if (isset($installed['versions'][$packageName]['pretty_version'])) {
                $ranges[] = $installed['versions'][$packageName]['pretty_version'];
            }
            if (array_key_exists('aliases', $installed['versions'][$packageName])) {
                $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
            }
            if (array_key_exists('replaced', $installed['versions'][$packageName])) {
                $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
            }
            if (array_key_exists('provided', $installed['versions'][$packageName])) {
                $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
            }

            return implode(' || ', $ranges);
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

    /**
     * @param  string      $packageName
     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
     */
    public static function getVersion($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            if (!isset($installed['versions'][$packageName]['version'])) {
                return null;
            }

            return $installed['versions'][$packageName]['version'];
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

    /**
     * @param  string      $packageName
     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
     */
    public static function getPrettyVersion($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            if (!isset($installed['versions'][$packageName]['pretty_version'])) {
                return null;
            }

            return $installed['versions'][$packageName]['pretty_version'];
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

    /**
     * @param  string      $packageName
     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
     */
    public static function getReference($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            if (!isset($installed['versions'][$packageName]['reference'])) {
                return null;
            }

            return $installed['versions'][$packageName]['reference'];
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

    /**
     * @param  string      $packageName
     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
     */
    public static function getInstallPath($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

    /**
     * @return array
     * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
     */
    public static function getRootPackage()
    {
        $installed = self::getInstalled();

        return $installed[0]['root'];
    }

    /**
     * Returns the raw installed.php data for custom implementations
     *
     * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
     * @return array[]
     * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
     */
    public static function getRawData()
    {
        @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);

        if (null === self::$installed) {
            // only require the installed.php file if this file is loaded from its dumped location,
            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
            if (substr(__DIR__, -8, 1) !== 'C') {
                self::$installed = include __DIR__ . '/installed.php';
            } else {
                self::$installed = array();
            }
        }

        return self::$installed;
    }

    /**
     * Returns the raw data of all installed.php which are currently loaded for custom implementations
     *
     * @return array[]
     * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
     */
    public static function getAllRawData()
    {
        return self::getInstalled();
    }

    /**
     * Lets you reload the static array from another file
     *
     * This is only useful for complex integrations in which a project needs to use
     * this class but then also needs to execute another project's autoloader in process,
     * and wants to ensure both projects have access to their version of installed.php.
     *
     * A typical case would be PHPUnit, where it would need to make sure it reads all
     * the data it needs from this class, then call reload() with
     * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
     * the project in which it runs can then also use this class safely, without
     * interference between PHPUnit's dependencies and the project's dependencies.
     *
     * @param  array[] $data A vendor/composer/installed.php data set
     * @return void
     *
     * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
     */
    public static function reload($data)
    {
        self::$installed = $data;
        self::$installedByVendor = array();

        // when using reload, we disable the duplicate protection to ensure that self::$installed data is
        // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
        // so we have to assume it does not, and that may result in duplicate data being returned when listing
        // all installed packages for example
        self::$installedIsLocalDir = false;
    }

    /**
     * @return string
     */
    private static function getSelfDir()
    {
        if (self::$selfDir === null) {
            self::$selfDir = strtr(__DIR__, '\\', '/');
        }

        return self::$selfDir;
    }

    /**
     * @return array[]
     * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
     */
    private static function getInstalled()
    {
        if (null === self::$canGetVendors) {
            self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
        }

        $installed = array();
        $copiedLocalDir = false;

        if (self::$canGetVendors) {
            $selfDir = self::getSelfDir();
            foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
                $vendorDir = strtr($vendorDir, '\\', '/');
                if (isset(self::$installedByVendor[$vendorDir])) {
                    $installed[] = self::$installedByVendor[$vendorDir];
                } elseif (is_file($vendorDir.'/composer/installed.php')) {
                    /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
                    $required = require $vendorDir.'/composer/installed.php';
                    self::$installedByVendor[$vendorDir] = $required;
                    $installed[] = $required;
                    if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
                        self::$installed = $required;
                        self::$installedIsLocalDir = true;
                    }
                }
                if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
                    $copiedLocalDir = true;
                }
            }
        }

        if (null === self::$installed) {
            // only require the installed.php file if this file is loaded from its dumped location,
            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
            if (substr(__DIR__, -8, 1) !== 'C') {
                /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
                $required = require __DIR__ . '/installed.php';
                self::$installed = $required;
            } else {
                self::$installed = array();
            }
        }

        if (self::$installed !== array() && !$copiedLocalDir) {
            $installed[] = self::$installed;
        }

        return $installed;
    }
}
<?php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Autoload;

/**
 * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
 *
 *     $loader = new \Composer\Autoload\ClassLoader();
 *
 *     // register classes with namespaces
 *     $loader->add('Symfony\Component', __DIR__.'/component');
 *     $loader->add('Symfony',           __DIR__.'/framework');
 *
 *     // activate the autoloader
 *     $loader->register();
 *
 *     // to enable searching the include path (eg. for PEAR packages)
 *     $loader->setUseIncludePath(true);
 *
 * In this example, if you try to use a class in the Symfony\Component
 * namespace or one of its children (Symfony\Component\Console for instance),
 * the autoloader will first look for the class under the component/
 * directory, and it will then fallback to the framework/ directory if not
 * found before giving up.
 *
 * This class is loosely based on the Symfony UniversalClassLoader.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @see    https://www.php-fig.org/psr/psr-0/
 * @see    https://www.php-fig.org/psr/psr-4/
 */
class ClassLoader
{
    /** @var \Closure(string):void */
    private static $includeFile;

    /** @var string|null */
    private $vendorDir;

    // PSR-4
    /**
     * @var array<string, array<string, int>>
     */
    private $prefixLengthsPsr4 = array();
    /**
     * @var array<string, list<string>>
     */
    private $prefixDirsPsr4 = array();
    /**
     * @var list<string>
     */
    private $fallbackDirsPsr4 = array();

    // PSR-0
    /**
     * List of PSR-0 prefixes
     *
     * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
     *
     * @var array<string, array<string, list<string>>>
     */
    private $prefixesPsr0 = array();
    /**
     * @var list<string>
     */
    private $fallbackDirsPsr0 = array();

    /** @var bool */
    private $useIncludePath = false;

    /**
     * @var array<string, string>
     */
    private $classMap = array();

    /** @var bool */
    private $classMapAuthoritative = false;

    /**
     * @var array<string, bool>
     */
    private $missingClasses = array();

    /** @var string|null */
    private $apcuPrefix;

    /**
     * @var array<string, self>
     */
    private static $registeredLoaders = array();

    /**
     * @param string|null $vendorDir
     */
    public function __construct($vendorDir = null)
    {
        $this->vendorDir = $vendorDir;
        self::initializeIncludeClosure();
    }

    /**
     * @return array<string, list<string>>
     */
    public function getPrefixes()
    {
        if (!empty($this->prefixesPsr0)) {
            return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
        }

        return array();
    }

    /**
     * @return array<string, list<string>>
     */
    public function getPrefixesPsr4()
    {
        return $this->prefixDirsPsr4;
    }

    /**
     * @return list<string>
     */
    public function getFallbackDirs()
    {
        return $this->fallbackDirsPsr0;
    }

    /**
     * @return list<string>
     */
    public function getFallbackDirsPsr4()
    {
        return $this->fallbackDirsPsr4;
    }

    /**
     * @return array<string, string> Array of classname => path
     */
    public function getClassMap()
    {
        return $this->classMap;
    }

    /**
     * @param array<string, string> $classMap Class to filename map
     *
     * @return void
     */
    public function addClassMap(array $classMap)
    {
        if ($this->classMap) {
            $this->classMap = array_merge($this->classMap, $classMap);
        } else {
            $this->classMap = $classMap;
        }
    }

    /**
     * Registers a set of PSR-0 directories for a given prefix, either
     * appending or prepending to the ones previously set for this prefix.
     *
     * @param string              $prefix  The prefix
     * @param list<string>|string $paths   The PSR-0 root directories
     * @param bool                $prepend Whether to prepend the directories
     *
     * @return void
     */
    public function add($prefix, $paths, $prepend = false)
    {
        $paths = (array) $paths;
        if (!$prefix) {
            if ($prepend) {
                $this->fallbackDirsPsr0 = array_merge(
                    $paths,
                    $this->fallbackDirsPsr0
                );
            } else {
                $this->fallbackDirsPsr0 = array_merge(
                    $this->fallbackDirsPsr0,
                    $paths
                );
            }

            return;
        }

        $first = $prefix[0];
        if (!isset($this->prefixesPsr0[$first][$prefix])) {
            $this->prefixesPsr0[$first][$prefix] = $paths;

            return;
        }
        if ($prepend) {
            $this->prefixesPsr0[$first][$prefix] = array_merge(
                $paths,
                $this->prefixesPsr0[$first][$prefix]
            );
        } else {
            $this->prefixesPsr0[$first][$prefix] = array_merge(
                $this->prefixesPsr0[$first][$prefix],
                $paths
            );
        }
    }

    /**
     * Registers a set of PSR-4 directories for a given namespace, either
     * appending or prepending to the ones previously set for this namespace.
     *
     * @param string              $prefix  The prefix/namespace, with trailing '\\'
     * @param list<string>|string $paths   The PSR-4 base directories
     * @param bool                $prepend Whether to prepend the directories
     *
     * @throws \InvalidArgumentException
     *
     * @return void
     */
    public function addPsr4($prefix, $paths, $prepend = false)
    {
        $paths = (array) $paths;
        if (!$prefix) {
            // Register directories for the root namespace.
            if ($prepend) {
                $this->fallbackDirsPsr4 = array_merge(
                    $paths,
                    $this->fallbackDirsPsr4
                );
            } else {
                $this->fallbackDirsPsr4 = array_merge(
                    $this->fallbackDirsPsr4,
                    $paths
                );
            }
        } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
            // Register directories for a new namespace.
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            $this->prefixDirsPsr4[$prefix] = $paths;
        } elseif ($prepend) {
            // Prepend directories for an already registered namespace.
            $this->prefixDirsPsr4[$prefix] = array_merge(
                $paths,
                $this->prefixDirsPsr4[$prefix]
            );
        } else {
            // Append directories for an already registered namespace.
            $this->prefixDirsPsr4[$prefix] = array_merge(
                $this->prefixDirsPsr4[$prefix],
                $paths
            );
        }
    }

    /**
     * Registers a set of PSR-0 directories for a given prefix,
     * replacing any others previously set for this prefix.
     *
     * @param string              $prefix The prefix
     * @param list<string>|string $paths  The PSR-0 base directories
     *
     * @return void
     */
    public function set($prefix, $paths)
    {
        if (!$prefix) {
            $this->fallbackDirsPsr0 = (array) $paths;
        } else {
            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
        }
    }

    /**
     * Registers a set of PSR-4 directories for a given namespace,
     * replacing any others previously set for this namespace.
     *
     * @param string              $prefix The prefix/namespace, with trailing '\\'
     * @param list<string>|string $paths  The PSR-4 base directories
     *
     * @throws \InvalidArgumentException
     *
     * @return void
     */
    public function setPsr4($prefix, $paths)
    {
        if (!$prefix) {
            $this->fallbackDirsPsr4 = (array) $paths;
        } else {
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            $this->prefixDirsPsr4[$prefix] = (array) $paths;
        }
    }

    /**
     * Turns on searching the include path for class files.
     *
     * @param bool $useIncludePath
     *
     * @return void
     */
    public function setUseIncludePath($useIncludePath)
    {
        $this->useIncludePath = $useIncludePath;
    }

    /**
     * Can be used to check if the autoloader uses the include path to check
     * for classes.
     *
     * @return bool
     */
    public function getUseIncludePath()
    {
        return $this->useIncludePath;
    }

    /**
     * Turns off searching the prefix and fallback directories for classes
     * that have not been registered with the class map.
     *
     * @param bool $classMapAuthoritative
     *
     * @return void
     */
    public function setClassMapAuthoritative($classMapAuthoritative)
    {
        $this->classMapAuthoritative = $classMapAuthoritative;
    }

    /**
     * Should class lookup fail if not found in the current class map?
     *
     * @return bool
     */
    public function isClassMapAuthoritative()
    {
        return $this->classMapAuthoritative;
    }

    /**
     * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
     *
     * @param string|null $apcuPrefix
     *
     * @return void
     */
    public function setApcuPrefix($apcuPrefix)
    {
        $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
    }

    /**
     * The APCu prefix in use, or null if APCu caching is not enabled.
     *
     * @return string|null
     */
    public function getApcuPrefix()
    {
        return $this->apcuPrefix;
    }

    /**
     * Registers this instance as an autoloader.
     *
     * @param bool $prepend Whether to prepend the autoloader or not
     *
     * @return void
     */
    public function register($prepend = false)
    {
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);

        if (null === $this->vendorDir) {
            return;
        }

        if ($prepend) {
            self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
        } else {
            unset(self::$registeredLoaders[$this->vendorDir]);
            self::$registeredLoaders[$this->vendorDir] = $this;
        }
    }

    /**
     * Unregisters this instance as an autoloader.
     *
     * @return void
     */
    public function unregister()
    {
        spl_autoload_unregister(array($this, 'loadClass'));

        if (null !== $this->vendorDir) {
            unset(self::$registeredLoaders[$this->vendorDir]);
        }
    }

    /**
     * Loads the given class or interface.
     *
     * @param  string    $class The name of the class
     * @return true|null True if loaded, null otherwise
     */
    public function loadClass($class)
    {
        if ($file = $this->findFile($class)) {
            $includeFile = self::$includeFile;
            $includeFile($file);

            return true;
        }

        return null;
    }

    /**
     * Finds the path to the file where the class is defined.
     *
     * @param string $class The name of the class
     *
     * @return string|false The path if found, false otherwise
     */
    public function findFile($class)
    {
        // class map lookup
        if (isset($this->classMap[$class])) {
            return $this->classMap[$class];
        }
        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
            return false;
        }
        if (null !== $this->apcuPrefix) {
            $file = apcu_fetch($this->apcuPrefix.$class, $hit);
            if ($hit) {
                return $file;
            }
        }

        $file = $this->findFileWithExtension($class, '.php');

        // Search for Hack files if we are running on HHVM
        if (false === $file && defined('HHVM_VERSION')) {
            $file = $this->findFileWithExtension($class, '.hh');
        }

        if (null !== $this->apcuPrefix) {
            apcu_add($this->apcuPrefix.$class, $file);
        }

        if (false === $file) {
            // Remember that this class does not exist.
            $this->missingClasses[$class] = true;
        }

        return $file;
    }

    /**
     * Returns the currently registered loaders keyed by their corresponding vendor directories.
     *
     * @return array<string, self>
     */
    public static function getRegisteredLoaders()
    {
        return self::$registeredLoaders;
    }

    /**
     * @param  string       $class
     * @param  string       $ext
     * @return string|false
     */
    private function findFileWithExtension($class, $ext)
    {
        // PSR-4 lookup
        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

        $first = $class[0];
        if (isset($this->prefixLengthsPsr4[$first])) {
            $subPath = $class;
            while (false !== $lastPos = strrpos($subPath, '\\')) {
                $subPath = substr($subPath, 0, $lastPos);
                $search = $subPath . '\\';
                if (isset($this->prefixDirsPsr4[$search])) {
                    $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
                    foreach ($this->prefixDirsPsr4[$search] as $dir) {
                        if (file_exists($file = $dir . $pathEnd)) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-4 fallback dirs
        foreach ($this->fallbackDirsPsr4 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
                return $file;
            }
        }

        // PSR-0 lookup
        if (false !== $pos = strrpos($class, '\\')) {
            // namespaced class name
            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
        } else {
            // PEAR-like class name
            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
        }

        if (isset($this->prefixesPsr0[$first])) {
            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
                if (0 === strpos($class, $prefix)) {
                    foreach ($dirs as $dir) {
                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-0 fallback dirs
        foreach ($this->fallbackDirsPsr0 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                return $file;
            }
        }

        // PSR-0 include paths.
        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
            return $file;
        }

        return false;
    }

    /**
     * @return void
     */
    private static function initializeIncludeClosure()
    {
        if (self::$includeFile !== null) {
            return;
        }

        /**
         * Scope isolated include.
         *
         * Prevents access to $this/self from included files.
         *
         * @param  string $file
         * @return void
         */
        self::$includeFile = \Closure::bind(static function($file) {
            include $file;
        }, null, null);
    }
}
<?php

// autoload_psr4.php @generated by Composer

$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);

return array(
    'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'),
    'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
    'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'),
    'Symfony\\Polyfill\\Intl\\Grapheme\\' => array($vendorDir . '/symfony/polyfill-intl-grapheme'),
    'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
    'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'),
    'Symfony\\Contracts\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher-contracts'),
    'Symfony\\Component\\String\\' => array($vendorDir . '/symfony/string'),
    'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'),
    'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'),
    'Symfony\\Component\\Filesystem\\' => array($vendorDir . '/symfony/filesystem'),
    'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'),
    'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'),
    'Seld\\Signal\\' => array($vendorDir . '/seld/signal-handler/src'),
    'Seld\\PharUtils\\' => array($vendorDir . '/seld/phar-utils/src'),
    'Seld\\JsonLint\\' => array($vendorDir . '/seld/jsonlint/src/Seld/JsonLint'),
    'React\\Promise\\' => array($vendorDir . '/react/promise/src'),
    'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
    'Psr\\Log\\' => array($vendorDir . '/psr/log/src'),
    'Psr\\EventDispatcher\\' => array($vendorDir . '/psr/event-dispatcher/src'),
    'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
    'Php\\Pie\\' => array($baseDir . '/src'),
    'MabeEnum\\' => array($vendorDir . '/marc-mabe/php-enum/src'),
    'JsonSchema\\' => array($vendorDir . '/justinrainbow/json-schema/src/JsonSchema'),
    'Illuminate\\Contracts\\' => array($vendorDir . '/illuminate/contracts'),
    'Illuminate\\Container\\' => array($vendorDir . '/illuminate/container'),
    'Fidry\\CpuCoreCounter\\' => array($vendorDir . '/fidry/cpu-core-counter/src'),
    'Composer\\XdebugHandler\\' => array($vendorDir . '/composer/xdebug-handler/src'),
    'Composer\\Spdx\\' => array($vendorDir . '/composer/spdx-licenses/src'),
    'Composer\\Semver\\' => array($vendorDir . '/composer/semver/src'),
    'Composer\\Pcre\\' => array($vendorDir . '/composer/pcre/src'),
    'Composer\\MetadataMinifier\\' => array($vendorDir . '/composer/metadata-minifier/src'),
    'Composer\\ClassMapGenerator\\' => array($vendorDir . '/composer/class-map-generator/src'),
    'Composer\\CaBundle\\' => array($vendorDir . '/composer/ca-bundle/src'),
    'Composer\\' => array($vendorDir . '/composer/composer/src/Composer'),
);
<?php

// platform_check.php @generated by Composer

$issues = array();

if (!(PHP_VERSION_ID >= 80100)) {
    $issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.';
}

if ($issues) {
    if (!headers_sent()) {
        header('HTTP/1.1 500 Internal Server Error');
    }
    if (!ini_get('display_errors')) {
        if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
            fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
        } elseif (!headers_sent()) {
            echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
        }
    }
    throw new \RuntimeException(
        'Composer detected issues in your platform: ' . implode(' ', $issues)
    );
}
MIT License

Copyright (c) 2017 Composer

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.
<?php

/*
 * This file is part of composer/xdebug-handler.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Composer\XdebugHandler;

use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;

/**
 * @author John Stevenson <john-stevenson@blueyonder.co.uk>
 * @internal
 */
class Status
{
    const ENV_RESTART = 'XDEBUG_HANDLER_RESTART';
    const CHECK = 'Check';
    const ERROR = 'Error';
    const INFO = 'Info';
    const NORESTART = 'NoRestart';
    const RESTART = 'Restart';
    const RESTARTING = 'Restarting';
    const RESTARTED = 'Restarted';

    /** @var bool */
    private $debug;

    /** @var string */
    private $envAllowXdebug;

    /** @var string|null */
    private $loaded;

    /** @var LoggerInterface|null */
    private $logger;

    /** @var bool */
    private $modeOff;

     /** @var float */
    private $time;

    /**
     * @param string $envAllowXdebug Prefixed _ALLOW_XDEBUG name
     * @param bool $debug Whether debug output is required
     */
    public function __construct(string $envAllowXdebug, bool $debug)
    {
        $start = getenv(self::ENV_RESTART);
        Process::setEnv(self::ENV_RESTART);
        $this->time = is_numeric($start) ? round((microtime(true) - $start) * 1000) : 0;

        $this->envAllowXdebug = $envAllowXdebug;
        $this->debug = $debug && defined('STDERR');
        $this->modeOff = false;
    }

    /**
     * Activates status message output to a PSR3 logger
     *
     * @return void
     */
    public function setLogger(LoggerInterface $logger): void
    {
        $this->logger = $logger;
    }

    /**
     * Calls a handler method to report a message
     *
     * @throws \InvalidArgumentException If $op is not known
     */
    public function report(string $op, ?string $data): void
    {
        if ($this->logger !== null || $this->debug) {
            $param = (string) $data;

            switch($op) {
                case self::CHECK:
                    $this->reportCheck($param);
                    break;
                case self::ERROR:
                    $this->reportError($param);
                    break;
                case self::INFO:
                    $this->reportInfo($param);
                    break;
                case self::NORESTART:
                    $this->reportNoRestart();
                    break;
                case self::RESTART:
                    $this->reportRestart();
                    break;
                case self::RESTARTED:
                    $this->reportRestarted();
                    break;
                case self::RESTARTING:
                    $this->reportRestarting($param);
                    break;
                default:
                    throw new \InvalidArgumentException('Unknown op handler: '.$op);
            }
        }
    }

    /**
     * Outputs a status message
     */
    private function output(string $text, ?string $level = null): void
    {
        if ($this->logger !== null) {
            $this->logger->log($level !== null ? $level: LogLevel::DEBUG, $text);
        }

        if ($this->debug) {
            fwrite(STDERR, sprintf('xdebug-handler[%d] %s', getmypid(), $text.PHP_EOL));
        }
    }

    /**
     * Checking status message
     */
    private function reportCheck(string $loaded): void
    {
        list($version, $mode) = explode('|', $loaded);

        if ($version !== '') {
            $this->loaded = '('.$version.')'.($mode !== '' ? ' xdebug.mode='.$mode : '');
        }
        $this->modeOff = $mode === 'off';
        $this->output('Checking '.$this->envAllowXdebug);
    }

    /**
     * Error status message
     */
    private function reportError(string $error): void
    {
        $this->output(sprintf('No restart (%s)', $error), LogLevel::WARNING);
    }

    /**
     * Info status message
     */
    private function reportInfo(string $info): void
    {
        $this->output($info);
    }

    /**
     * No restart status message
     */
    private function reportNoRestart(): void
    {
        $this->output($this->getLoadedMessage());

        if ($this->loaded !== null) {
            $text = sprintf('No restart (%s)', $this->getEnvAllow());
            if (!((bool) getenv($this->envAllowXdebug))) {
                $text .= ' Allowed by '.($this->modeOff ? 'xdebug.mode' : 'application');
            }
            $this->output($text);
        }
    }

    /**
     * Restart status message
     */
    private function reportRestart(): void
    {
        $this->output($this->getLoadedMessage());
        Process::setEnv(self::ENV_RESTART, (string) microtime(true));
    }

    /**
     * Restarted status message
     */
    private function reportRestarted(): void
    {
        $loaded = $this->getLoadedMessage();
        $text = sprintf('Restarted (%d ms). %s', $this->time, $loaded);
        $level = $this->loaded !== null ? LogLevel::WARNING : null;
        $this->output($text, $level);
    }

    /**
     * Restarting status message
     */
    private function reportRestarting(string $command): void
    {
        $text = sprintf('Process restarting (%s)', $this->getEnvAllow());
        $this->output($text);
        $text = 'Running: '.$command;
        $this->output($text);
    }

    /**
     * Returns the _ALLOW_XDEBUG environment variable as name=value
     */
    private function getEnvAllow(): string
    {
        return $this->envAllowXdebug.'='.getenv($this->envAllowXdebug);
    }

    /**
     * Returns the Xdebug status and version
     */
    private function getLoadedMessage(): string
    {
        $loaded = $this->loaded !== null ? sprintf('loaded %s', $this->loaded) : 'not loaded';
        return 'The Xdebug extension is '.$loaded;
    }
}
<?php

/*
 * This file is part of composer/xdebug-handler.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Composer\XdebugHandler;

use Composer\Pcre\Preg;

/**
 * Process utility functions
 *
 * @author John Stevenson <john-stevenson@blueyonder.co.uk>
 */
class Process
{
    /**
     * Escapes a string to be used as a shell argument.
     *
     * From https://github.com/johnstevenson/winbox-args
     * MIT Licensed (c) John Stevenson <john-stevenson@blueyonder.co.uk>
     *
     * @param string $arg  The argument to be escaped
     * @param bool $meta Additionally escape cmd.exe meta characters
     * @param bool $module The argument is the module to invoke
     */
    public static function escape(string $arg, bool $meta = true, bool $module = false): string
    {
        if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
            return "'".str_replace("'", "'\\''", $arg)."'";
        }

        $quote = strpbrk($arg, " \t") !== false || $arg === '';

        $arg = Preg::replace('/(\\\\*)"/', '$1$1\\"', $arg, -1, $dquotes);
        $dquotes = (bool) $dquotes;

        if ($meta) {
            $meta = $dquotes || Preg::isMatch('/%[^%]+%/', $arg);

            if (!$meta) {
                $quote = $quote || strpbrk($arg, '^&|<>()') !== false;
            } elseif ($module && !$dquotes && $quote) {
                $meta = false;
            }
        }

        if ($quote) {
            $arg = '"'.(Preg::replace('/(\\\\*)$/', '$1$1', $arg)).'"';
        }

        if ($meta) {
            $arg = Preg::replace('/(["^&|<>()%])/', '^$1', $arg);
        }

        return $arg;
    }

    /**
     * Escapes an array of arguments that make up a shell command
     *
     * @param string[] $args Argument list, with the module name first
     */
    public static function escapeShellCommand(array $args): string
    {
        $command = '';
        $module = array_shift($args);

        if ($module !== null) {
            $command = self::escape($module, true, true);

            foreach ($args as $arg) {
                $command .= ' '.self::escape($arg);
            }
        }

        return $command;
    }

    /**
     * Makes putenv environment changes available in $_SERVER and $_ENV
     *
     * @param string $name
     * @param ?string $value A null value unsets the variable
      */
    public static function setEnv(string $name, ?string $value = null): bool
    {
        $unset = null === $value;

        if (!putenv($unset ? $name : $name.'='.$value)) {
            return false;
        }

        if ($unset) {
            unset($_SERVER[$name]);
        } else {
            $_SERVER[$name] = $value;
        }

        // Update $_ENV if it is being used
        if (false !== stripos((string) ini_get('variables_order'), 'E')) {
            if ($unset) {
                unset($_ENV[$name]);
            } else {
                $_ENV[$name] = $value;
            }
        }

        return true;
    }
}
<?php

/*
 * This file is part of composer/xdebug-handler.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Composer\XdebugHandler;

use Composer\Pcre\Preg;
use Psr\Log\LoggerInterface;

/**
 * @author John Stevenson <john-stevenson@blueyonder.co.uk>
 *
 * @phpstan-import-type restartData from PhpConfig
 */
class XdebugHandler
{
    const SUFFIX_ALLOW = '_ALLOW_XDEBUG';
    const SUFFIX_INIS = '_ORIGINAL_INIS';
    const RESTART_ID = 'internal';
    const RESTART_SETTINGS = 'XDEBUG_HANDLER_SETTINGS';
    const DEBUG = 'XDEBUG_HANDLER_DEBUG';

    /** @var string|null */
    protected $tmpIni;

    /** @var bool */
    private static $inRestart;

    /** @var string */
    private static $name;

    /** @var string|null */
    private static $skipped;

    /** @var bool */
    private static $xdebugActive;

    /** @var string|null */
    private static $xdebugMode;

    /** @var string|null */
    private static $xdebugVersion;

    /** @var bool */
    private $cli;

    /** @var string|null */
    private $debug;

    /** @var string */
    private $envAllowXdebug;

    /** @var string */
    private $envOriginalInis;

    /** @var bool */
    private $persistent;

    /** @var string|null */
    private $script;

    /** @var Status */
    private $statusWriter;

    /**
     * Constructor
     *
     * The $envPrefix is used to create distinct environment variables. It is
     * uppercased and prepended to the default base values. For example 'myapp'
     * would result in MYAPP_ALLOW_XDEBUG and MYAPP_ORIGINAL_INIS.
     *
     * @param string $envPrefix Value used in environment variables
     * @throws \RuntimeException If the parameter is invalid
     */
    public function __construct(string $envPrefix)
    {
        if ($envPrefix === '') {
            throw new \RuntimeException('Invalid constructor parameter');
        }

        self::$name = strtoupper($envPrefix);
        $this->envAllowXdebug = self::$name.self::SUFFIX_ALLOW;
        $this->envOriginalInis = self::$name.self::SUFFIX_INIS;

        self::setXdebugDetails();
        self::$inRestart = false;

        if ($this->cli = PHP_SAPI === 'cli') {
            $this->debug = (string) getenv(self::DEBUG);
        }

        $this->statusWriter = new Status($this->envAllowXdebug, (bool) $this->debug);
    }

    /**
     * Activates status message output to a PSR3 logger
     */
    public function setLogger(LoggerInterface $logger): self
    {
        $this->statusWriter->setLogger($logger);
        return $this;
    }

    /**
     * Sets the main script location if it cannot be called from argv
     */
    public function setMainScript(string $script): self
    {
        $this->script = $script;
        return $this;
    }

    /**
     * Persist the settings to keep Xdebug out of sub-processes
     */
    public function setPersistent(): self
    {
        $this->persistent = true;
        return $this;
    }

    /**
     * Checks if Xdebug is loaded and the process needs to be restarted
     *
     * This behaviour can be disabled by setting the MYAPP_ALLOW_XDEBUG
     * environment variable to 1. This variable is used internally so that
     * the restarted process is created only once.
     */
    public function check(): void
    {
        $this->notify(Status::CHECK, self::$xdebugVersion.'|'.self::$xdebugMode);
        $envArgs = explode('|', (string) getenv($this->envAllowXdebug));

        if (!((bool) $envArgs[0]) && $this->requiresRestart(self::$xdebugActive)) {
            // Restart required
            $this->notify(Status::RESTART);
            $command = $this->prepareRestart();

            if ($command !== null) {
                $this->restart($command);
            }
            return;
        }

        if (self::RESTART_ID === $envArgs[0] && count($envArgs) === 5) {
            // Restarted, so unset environment variable and use saved values
            $this->notify(Status::RESTARTED);

            Process::setEnv($this->envAllowXdebug);
            self::$inRestart = true;

            if (self::$xdebugVersion === null) {
                // Skipped version is only set if Xdebug is not loaded
                self::$skipped = $envArgs[1];
            }

            $this->tryEnableSignals();

            // Put restart settings in the environment
            $this->setEnvRestartSettings($envArgs);
            return;
        }

        $this->notify(Status::NORESTART);
        $settings = self::getRestartSettings();

        if ($settings !== null) {
            // Called with existing settings, so sync our settings
            $this->syncSettings($settings);
        }
    }

    /**
     * Returns an array of php.ini locations with at least one entry
     *
     * The equivalent of calling php_ini_loaded_file then php_ini_scanned_files.
     * The loaded ini location is the first entry and may be an empty string.
     *
     * @return non-empty-list<string>
     */
    public static function getAllIniFiles(): array
    {
        if (self::$name !== null) {
            $env = getenv(self::$name.self::SUFFIX_INIS);

            if (false !== $env) {
                return explode(PATH_SEPARATOR, $env);
            }
        }

        $paths = [(string) php_ini_loaded_file()];
        $scanned = php_ini_scanned_files();

        if ($scanned !== false) {
            $paths = array_merge($paths, array_map('trim', explode(',', $scanned)));
        }

        return $paths;
    }

    /**
     * Returns an array of restart settings or null
     *
     * Settings will be available if the current process was restarted, or
     * called with the settings from an existing restart.
     *
     * @phpstan-return restartData|null
     */
    public static function getRestartSettings(): ?array
    {
        $envArgs = explode('|', (string) getenv(self::RESTART_SETTINGS));

        if (count($envArgs) !== 6
            || (!self::$inRestart && php_ini_loaded_file() !== $envArgs[0])) {
            return null;
        }

        return [
            'tmpIni' => $envArgs[0],
            'scannedInis' => (bool) $envArgs[1],
            'scanDir' => '*' === $envArgs[2] ? false : $envArgs[2],
            'phprc' => '*' === $envArgs[3] ? false : $envArgs[3],
            'inis' => explode(PATH_SEPARATOR, $envArgs[4]),
            'skipped' => $envArgs[5],
        ];
    }

    /**
     * Returns the Xdebug version that triggered a successful restart
     */
    public static function getSkippedVersion(): string
    {
        return (string) self::$skipped;
    }

    /**
     * Returns whether Xdebug is loaded and active
     *
     * true: if Xdebug is loaded and is running in an active mode.
     * false: if Xdebug is not loaded, or it is running with xdebug.mode=off.
     */
    public static function isXdebugActive(): bool
    {
        self::setXdebugDetails();
        return self::$xdebugActive;
    }

    /**
     * Allows an extending class to decide if there should be a restart
     *
     * The default is to restart if Xdebug is loaded and its mode is not "off".
     */
    protected function requiresRestart(bool $default): bool
    {
        return $default;
    }

    /**
     * Allows an extending class to access the tmpIni
     *
     * @param non-empty-list<string> $command
     */
    protected function restart(array $command): void
    {
        $this->doRestart($command);
    }

    /**
     * Executes the restarted command then deletes the tmp ini
     *
     * @param non-empty-list<string> $command
     * @phpstan-return never
     */
    private function doRestart(array $command): void
    {
        if (PHP_VERSION_ID >= 70400) {
            $cmd = $command;
            $displayCmd = sprintf('[%s]', implode(', ', $cmd));
        } else {
            $cmd = Process::escapeShellCommand($command);
            if (defined('PHP_WINDOWS_VERSION_BUILD')) {
                // Outer quotes required on cmd string below PHP 8
                $cmd = '"'.$cmd.'"';
            }
            $displayCmd = $cmd;
        }

        $this->tryEnableSignals();
        $this->notify(Status::RESTARTING, $displayCmd);

        $process = proc_open($cmd, [], $pipes);
        if (is_resource($process)) {
            $exitCode = proc_close($process);
        }

        if (!isset($exitCode)) {
            // Unlikely that php or the default shell cannot be invoked
            $this->notify(Status::ERROR, 'Unable to restart process');
            $exitCode = -1;
        } else {
            $this->notify(Status::INFO, 'Restarted process exited '.$exitCode);
        }

        if ($this->debug === '2') {
            $this->notify(Status::INFO, 'Temp ini saved: '.$this->tmpIni);
        } else {
            @unlink((string) $this->tmpIni);
        }

        exit($exitCode);
    }

    /**
     * Returns the command line array if everything was written for the restart
     *
     * If any of the following fails (however unlikely) we must return false to
     * stop potential recursion:
     *   - tmp ini file creation
     *   - environment variable creation
     *
     * @return non-empty-list<string>|null
     */
    private function prepareRestart(): ?array
    {
        if (!$this->cli) {
            $this->notify(Status::ERROR, 'Unsupported SAPI: '.PHP_SAPI);
            return null;
        }

        if (($argv = $this->checkServerArgv()) === null) {
            $this->notify(Status::ERROR, '$_SERVER[argv] is not as expected');
            return null;
        }

        if (!$this->checkConfiguration($info)) {
            $this->notify(Status::ERROR, $info);
            return null;
        }

        $mainScript = (string) $this->script;
        if (!$this->checkMainScript($mainScript, $argv)) {
            $this->notify(Status::ERROR, 'Unable to access main script: '.$mainScript);
            return null;
        }

        $tmpDir = sys_get_temp_dir();
        $iniError = 'Unable to create temp ini file at: '.$tmpDir;

        if (($tmpfile = @tempnam($tmpDir, '')) === false) {
            $this->notify(Status::ERROR, $iniError);
            return null;
        }

        $error = null;
        $iniFiles = self::getAllIniFiles();
        $scannedInis = count($iniFiles) > 1;

        if (!$this->writeTmpIni($tmpfile, $iniFiles, $error)) {
            $this->notify(Status::ERROR, $error ?? $iniError);
            @unlink($tmpfile);
            return null;
        }

        if (!$this->setEnvironment($scannedInis, $iniFiles, $tmpfile)) {
            $this->notify(Status::ERROR, 'Unable to set environment variables');
            @unlink($tmpfile);
            return null;
        }

        $this->tmpIni = $tmpfile;

        return $this->getCommand($argv, $tmpfile, $mainScript);
    }

    /**
     * Returns true if the tmp ini file was written
     *
     * @param non-empty-list<string> $iniFiles All ini files used in the current process
     */
    private function writeTmpIni(string $tmpFile, array $iniFiles, ?string &$error): bool
    {
        // $iniFiles has at least one item and it may be empty
        if ($iniFiles[0] === '') {
            array_shift($iniFiles);
        }

        $content = '';
        $sectionRegex = '/^\s*\[(?:PATH|HOST)\s*=/mi';
        $xdebugRegex = '/^\s*(zend_extension\s*=.*xdebug.*)$/mi';

        foreach ($iniFiles as $file) {
            // Check for inaccessible ini files
            if (($data = @file_get_contents($file)) === false) {
                $error = 'Unable to read ini: '.$file;
                return false;
            }
            // Check and remove directives after HOST and PATH sections
            if (Preg::isMatchWithOffsets($sectionRegex, $data, $matches)) {
                $data = substr($data, 0, $matches[0][1]);
            }
            $content .= Preg::replace($xdebugRegex, ';$1', $data).PHP_EOL;
        }

        // Merge loaded settings into our ini content, if it is valid
        $config = parse_ini_string($content);
        $loaded = ini_get_all(null, false);

        if (false === $config || false === $loaded) {
            $error = 'Unable to parse ini data';
            return false;
        }

        $content .= $this->mergeLoadedConfig($loaded, $config);

        // Work-around for https://bugs.php.net/bug.php?id=75932
        $content .= 'opcache.enable_cli=0'.PHP_EOL;

        return (bool) @file_put_contents($tmpFile, $content);
    }

    /**
     * Returns the command line arguments for the restart
     *
     * @param non-empty-list<string> $argv
     * @return non-empty-list<string>
     */
    private function getCommand(array $argv, string $tmpIni, string $mainScript): array
    {
        $php = [PHP_BINARY];
        $args = array_slice($argv, 1);

        if (!$this->persistent) {
            // Use command-line options
            array_push($php, '-n', '-c', $tmpIni);
        }

        return array_merge($php, [$mainScript], $args);
    }

    /**
     * Returns true if the restart environment variables were set
     *
     * No need to update $_SERVER since this is set in the restarted process.
     *
     * @param non-empty-list<string> $iniFiles All ini files used in the current process
     */
    private function setEnvironment(bool $scannedInis, array $iniFiles, string $tmpIni): bool
    {
        $scanDir = getenv('PHP_INI_SCAN_DIR');
        $phprc = getenv('PHPRC');

        // Make original inis available to restarted process
        if (!putenv($this->envOriginalInis.'='.implode(PATH_SEPARATOR, $iniFiles))) {
            return false;
        }

        if ($this->persistent) {
            // Use the environment to persist the settings
            if (!putenv('PHP_INI_SCAN_DIR=') || !putenv('PHPRC='.$tmpIni)) {
                return false;
            }
        }

        // Flag restarted process and save values for it to use
        $envArgs = [
            self::RESTART_ID,
            self::$xdebugVersion,
            (int) $scannedInis,
            false === $scanDir ? '*' : $scanDir,
            false === $phprc ? '*' : $phprc,
        ];

        return putenv($this->envAllowXdebug.'='.implode('|', $envArgs));
    }

    /**
     * Logs status messages
     */
    private function notify(string $op, ?string $data = null): void
    {
        $this->statusWriter->report($op, $data);
    }

    /**
     * Returns default, changed and command-line ini settings
     *
     * @param mixed[] $loadedConfig All current ini settings
     * @param mixed[] $iniConfig Settings from user ini files
     *
     */
    private function mergeLoadedConfig(array $loadedConfig, array $iniConfig): string
    {
        $content = '';

        foreach ($loadedConfig as $name => $value) {
            // Value will either be null, string or array (HHVM only)
            if (!is_string($value)
                || strpos($name, 'xdebug') === 0
                || $name === 'apc.mmap_file_mask') {
                continue;
            }

            if (!isset($iniConfig[$name]) || $iniConfig[$name] !== $value) {
                // Double-quote escape each value
                $content .= $name.'="'.addcslashes($value, '\\"').'"'.PHP_EOL;
            }
        }

        return $content;
    }

    /**
     * Returns true if the script name can be used
     *
     * @param non-empty-list<string> $argv
     */
    private function checkMainScript(string &$mainScript, array $argv): bool
    {
        if ($mainScript !== '') {
            // Allow an application to set -- for standard input
            return file_exists($mainScript) || '--' === $mainScript;
        }

        if (file_exists($mainScript = $argv[0])) {
            return true;
        }

        // Use a backtrace to resolve Phar and chdir issues.
        $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
        $main = end($trace);

        if ($main !== false && isset($main['file'])) {
            return file_exists($mainScript = $main['file']);
        }

        return false;
    }

    /**
     * Adds restart settings to the environment
     *
     * @param non-empty-list<string> $envArgs
     */
    private function setEnvRestartSettings(array $envArgs): void
    {
        $settings = [
            php_ini_loaded_file(),
            $envArgs[2],
            $envArgs[3],
            $envArgs[4],
            getenv($this->envOriginalInis),
            self::$skipped,
        ];

        Process::setEnv(self::RESTART_SETTINGS, implode('|', $settings));
    }

    /**
     * Syncs settings and the environment if called with existing settings
     *
     * @phpstan-param restartData $settings
     */
    private function syncSettings(array $settings): void
    {
        if (false === getenv($this->envOriginalInis)) {
            // Called by another app, so make original inis available
            Process::setEnv($this->envOriginalInis, implode(PATH_SEPARATOR, $settings['inis']));
        }

        self::$skipped = $settings['skipped'];
        $this->notify(Status::INFO, 'Process called with existing restart settings');
    }

    /**
     * Returns true if there are no known configuration issues
     */
    private function checkConfiguration(?string &$info): bool
    {
        if (!function_exists('proc_open')) {
            $info = 'proc_open function is disabled';
            return false;
        }

        if (!file_exists(PHP_BINARY)) {
            $info = 'PHP_BINARY is not available';
            return false;
        }

        if (extension_loaded('uopz') && !((bool) ini_get('uopz.disable'))) {
            // uopz works at opcode level and disables exit calls
            if (function_exists('uopz_allow_exit')) {
                @uopz_allow_exit(true);
            } else {
                $info = 'uopz extension is not compatible';
                return false;
            }
        }

        // Check UNC paths when using cmd.exe
        if (defined('PHP_WINDOWS_VERSION_BUILD') && PHP_VERSION_ID < 70400) {
            $workingDir = getcwd();

            if ($workingDir === false) {
                $info = 'unable to determine working directory';
                return false;
            }

            if (0 === strpos($workingDir, '\\\\')) {
                $info = 'cmd.exe does not support UNC paths: '.$workingDir;
                return false;
            }
        }

        return true;
    }

    /**
     * Enables async signals and control interrupts in the restarted process
     *
     * Available on Unix PHP 7.1+ with the pcntl extension and Windows PHP 7.4+.
     */
    private function tryEnableSignals(): void
    {
        if (function_exists('pcntl_async_signals') && function_exists('pcntl_signal')) {
            pcntl_async_signals(true);
            $message = 'Async signals enabled';

            if (!self::$inRestart) {
                // Restarting, so ignore SIGINT in parent
                pcntl_signal(SIGINT, SIG_IGN);
            } elseif (is_int(pcntl_signal_get_handler(SIGINT))) {
                // Restarted, no handler set so force default action
                pcntl_signal(SIGINT, SIG_DFL);
            }
        }

        if (!self::$inRestart && function_exists('sapi_windows_set_ctrl_handler')) {
            // Restarting, so set a handler to ignore CTRL events in the parent.
            // This ensures that CTRL+C events will be available in the child
            // process without having to enable them there, which is unreliable.
            sapi_windows_set_ctrl_handler(function ($evt) {});
        }
    }

    /**
     * Returns $_SERVER['argv'] if it is as expected
     *
     * @return non-empty-list<string>|null
     */
    private function checkServerArgv(): ?array
    {
        $result = [];

        if (isset($_SERVER['argv']) && is_array($_SERVER['argv'])) {
            foreach ($_SERVER['argv'] as $value) {
                if (!is_string($value)) {
                    return null;
                }

                $result[] = $value;
            }
        }

        return count($result) > 0 ? $result : null;
    }

    /**
     * Sets static properties $xdebugActive, $xdebugVersion and $xdebugMode
     */
    private static function setXdebugDetails(): void
    {
        if (self::$xdebugActive !== null) {
            return;
        }

        self::$xdebugActive = false;
        if (!extension_loaded('xdebug')) {
            return;
        }

        $version = phpversion('xdebug');
        self::$xdebugVersion = $version !== false ? $version : 'unknown';

        if (version_compare(self::$xdebugVersion, '3.1', '>=')) {
            $modes = xdebug_info('mode');
            self::$xdebugMode = count($modes) === 0 ? 'off' : implode(',', $modes);
            self::$xdebugActive = self::$xdebugMode !== 'off';
            return;
        }

        // See if xdebug.mode is supported in this version
        $iniMode = ini_get('xdebug.mode');
        if ($iniMode === false) {
            self::$xdebugActive = true;
            return;
        }

        // Environment value wins but cannot be empty
        $envMode = (string) getenv('XDEBUG_MODE');
        if ($envMode !== '') {
            self::$xdebugMode = $envMode;
        } else {
            self::$xdebugMode = $iniMode !== '' ? $iniMode : 'off';
        }

        // An empty comma-separated list is treated as mode 'off'
        if (Preg::isMatch('/^,+$/', str_replace(' ', '', self::$xdebugMode))) {
            self::$xdebugMode = 'off';
        }

        self::$xdebugActive = self::$xdebugMode !== 'off';
    }
}
<?php

declare(strict_types=1);

/*
 * This file is part of composer/xdebug-handler.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\XdebugHandler;

/**
 * @author John Stevenson <john-stevenson@blueyonder.co.uk>
 *
 * @phpstan-type restartData array{tmpIni: string, scannedInis: bool, scanDir: false|string, phprc: false|string, inis: string[], skipped: string}
 */
class PhpConfig
{
    /**
     * Use the original PHP configuration
     *
     * @return string[] Empty array of PHP cli options
     */
    public function useOriginal(): array
    {
        $this->getDataAndReset();
        return [];
    }

    /**
     * Use standard restart settings
     *
     * @return string[] PHP cli options
     */
    public function useStandard(): array
    {
        $data = $this->getDataAndReset();
        if ($data !== null) {
            return ['-n', '-c', $data['tmpIni']];
        }

        return [];
    }

    /**
     * Use environment variables to persist settings
     *
     * @return string[] Empty array of PHP cli options
     */
    public function usePersistent(): array
    {
        $data = $this->getDataAndReset();
        if ($data !== null) {
            $this->updateEnv('PHPRC', $data['tmpIni']);
            $this->updateEnv('PHP_INI_SCAN_DIR', '');
        }

        return [];
    }

    /**
     * Returns restart data if available and resets the environment
     *
     * @phpstan-return restartData|null
     */
    private function getDataAndReset(): ?array
    {
        $data = XdebugHandler::getRestartSettings();
        if ($data !== null) {
            $this->updateEnv('PHPRC', $data['phprc']);
            $this->updateEnv('PHP_INI_SCAN_DIR', $data['scanDir']);
        }

        return $data;
    }

    /**
     * Updates a restart settings value in the environment
     *
     * @param string $name
     * @param string|false $value
     */
    private function updateEnv(string $name, $value): void
    {
        Process::setEnv($name, false !== $value ? $value : null);
    }
}
# composer/pcre PHPStan extensions
#
# These can be reused by third party packages by including 'vendor/composer/pcre/extension.neon'
# in your phpstan config

services:
    -
        class: Composer\Pcre\PHPStan\PregMatchParameterOutTypeExtension
        tags:
            - phpstan.staticMethodParameterOutTypeExtension
    -
        class: Composer\Pcre\PHPStan\PregMatchTypeSpecifyingExtension
        tags:
            - phpstan.typeSpecifier.staticMethodTypeSpecifyingExtension
    -
        class: Composer\Pcre\PHPStan\PregReplaceCallbackClosureTypeExtension
        tags:
            - phpstan.staticMethodParameterClosureTypeExtension

rules:
    - Composer\Pcre\PHPStan\UnsafeStrictGroupsCallRule
    - Composer\Pcre\PHPStan\InvalidRegexPatternRule
Copyright (C) 2021 Composer

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.
<?php

/*
 * This file is part of composer/pcre.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Pcre;

final class MatchWithOffsetsResult
{
    /**
     * An array of match group => pair of string matched + offset in bytes (or -1 if no match)
     *
     * @readonly
     * @var array<int|string, array{string|null, int}>
     * @phpstan-var array<int|string, array{string|null, int<-1, max>}>
     */
    public $matches;

    /**
     * @readonly
     * @var bool
     */
    public $matched;

    /**
     * @param 0|positive-int $count
     * @param array<array{string|null, int}> $matches
     * @phpstan-param array<int|string, array{string|null, int<-1, max>}> $matches
     */
    public function __construct(int $count, array $matches)
    {
        $this->matches = $matches;
        $this->matched = (bool) $count;
    }
}
<?php declare(strict_types = 1);

namespace Composer\Pcre\PHPStan;

use Composer\Pcre\Preg;
use Composer\Pcre\Regex;
use Composer\Pcre\PcreException;
use Nette\Utils\RegexpException;
use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Name\FullyQualified;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use function in_array;
use function sprintf;

/**
 * Copy of PHPStan's RegularExpressionPatternRule
 *
 * @implements Rule<StaticCall>
 */
class InvalidRegexPatternRule implements Rule
{
    public function getNodeType(): string
    {
        return StaticCall::class;
    }

    public function processNode(Node $node, Scope $scope): array
    {
        $patterns = $this->extractPatterns($node, $scope);

        $errors = [];
        foreach ($patterns as $pattern) {
            $errorMessage = $this->validatePattern($pattern);
            if ($errorMessage === null) {
                continue;
            }

            $errors[] = RuleErrorBuilder::message(sprintf('Regex pattern is invalid: %s', $errorMessage))->identifier('regexp.pattern')->build();
        }

        return $errors;
    }

    /**
     * @return string[]
     */
    private function extractPatterns(StaticCall $node, Scope $scope): array
    {
        if (!$node->class instanceof FullyQualified) {
            return [];
        }
        $isRegex = $node->class->toString() === Regex::class;
        $isPreg = $node->class->toString() === Preg::class;
        if (!$isRegex && !$isPreg) {
            return [];
        }
        if (!$node->name instanceof Node\Identifier || !Preg::isMatch('{^(match|isMatch|grep|replace|split)}', $node->name->name)) {
            return [];
        }

        $functionName = $node->name->name;
        if (!isset($node->getArgs()[0])) {
            return [];
        }

        $patternNode = $node->getArgs()[0]->value;
        $patternType = $scope->getType($patternNode);

        $patternStrings = [];

        foreach ($patternType->getConstantStrings() as $constantStringType) {
            if ($functionName === 'replaceCallbackArray') {
                continue;
            }

            $patternStrings[] = $constantStringType->getValue();
        }

        foreach ($patternType->getConstantArrays() as $constantArrayType) {
            if (
                in_array($functionName, [
                    'replace',
                    'replaceCallback',
                ], true)
            ) {
                foreach ($constantArrayType->getValueTypes() as $arrayKeyType) {
                    foreach ($arrayKeyType->getConstantStrings() as $constantString) {
                        $patternStrings[] = $constantString->getValue();
                    }
                }
            }

            if ($functionName !== 'replaceCallbackArray') {
                continue;
            }

            foreach ($constantArrayType->getKeyTypes() as $arrayKeyType) {
                foreach ($arrayKeyType->getConstantStrings() as $constantString) {
                    $patternStrings[] = $constantString->getValue();
                }
            }
        }

        return $patternStrings;
    }

    private function validatePattern(string $pattern): ?string
    {
        try {
            $msg = null;
            $prev = set_error_handler(function (int $severity, string $message, string $file) use (&$msg): bool {
                $msg = preg_replace("#^preg_match(_all)?\\(.*?\\): #", '', $message);

                return true;
            });

            if ($pattern === '') {
                return 'Empty string is not a valid regular expression';
            }

            Preg::match($pattern, '');
            if ($msg !== null) {
                return $msg;
            }
        } catch (PcreException $e) {
            if ($e->getCode() === PREG_INTERNAL_ERROR && $msg !== null) {
                return $msg;
            }

            return preg_replace('{.*? failed executing ".*": }', '', $e->getMessage());
        } finally {
            restore_error_handler();
        }

        return null;
    }

}
<?php declare(strict_types=1);

namespace Composer\Pcre\PHPStan;

use Composer\Pcre\Preg;
use Composer\Pcre\Regex;
use PhpParser\Node;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Name\FullyQualified;
use PHPStan\Analyser\Scope;
use PHPStan\Analyser\SpecifiedTypes;
use PHPStan\Rules\Rule;
use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\TrinaryLogic;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\Php\RegexArrayShapeMatcher;
use function sprintf;

/**
 * @implements Rule<StaticCall>
 */
final class UnsafeStrictGroupsCallRule implements Rule
{
    /**
     * @var RegexArrayShapeMatcher
     */
    private $regexShapeMatcher;

    public function __construct(RegexArrayShapeMatcher $regexShapeMatcher)
    {
        $this->regexShapeMatcher = $regexShapeMatcher;
    }

    public function getNodeType(): string
    {
        return StaticCall::class;
    }

    public function processNode(Node $node, Scope $scope): array
    {
        if (!$node->class instanceof FullyQualified) {
            return [];
        }
        $isRegex = $node->class->toString() === Regex::class;
        $isPreg = $node->class->toString() === Preg::class;
        if (!$isRegex && !$isPreg) {
            return [];
        }
        if (!$node->name instanceof Node\Identifier || !in_array($node->name->name, ['matchStrictGroups', 'isMatchStrictGroups', 'matchAllStrictGroups', 'isMatchAllStrictGroups'], true)) {
            return [];
        }

        $args = $node->getArgs();
        if (!isset($args[0])) {
            return [];
        }

        $patternArg = $args[0] ?? null;
        if ($isPreg) {
            if (!isset($args[2])) { // no matches set, skip as the matches won't be used anyway
                return [];
            }
            $flagsArg = $args[3] ?? null;
        } else {
            $flagsArg = $args[2] ?? null;
        }

        if ($patternArg === null) {
            return [];
        }

        $flagsType = PregMatchFlags::getType($flagsArg, $scope);
        if ($flagsType === null) {
            return [];
        }

        $matchedType = $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createYes(), $scope);
        if ($matchedType === null) {
            return [
                RuleErrorBuilder::message(sprintf('The %s call is potentially unsafe as $matches\' type could not be inferred.', $node->name->name))
                    ->identifier('composerPcre.maybeUnsafeStrictGroups')
                    ->build(),
            ];
        }

        if (count($matchedType->getConstantArrays()) === 1) {
            $matchedType = $matchedType->getConstantArrays()[0];
            $nullableGroups = [];
            foreach ($matchedType->getValueTypes() as $index => $type) {
                if (TypeCombinator::containsNull($type)) {
                    $nullableGroups[] = $matchedType->getKeyTypes()[$index]->getValue();
                }
            }

            if (\count($nullableGroups) > 0) {
                return [
                    RuleErrorBuilder::message(sprintf(
                        'The %s call is unsafe as match group%s "%s" %s optional and may be null.',
                        $node->name->name,
                        \count($nullableGroups) > 1 ? 's' : '',
                        implode('", "', $nullableGroups),
                        \count($nullableGroups) > 1 ? 'are' : 'is'
                    ))->identifier('composerPcre.unsafeStrictGroups')->build(),
                ];
            }
        }

        return [];
    }
}
<?php declare(strict_types=1);

namespace Composer\Pcre\PHPStan;

use Composer\Pcre\Preg;
use PhpParser\Node\Expr\StaticCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParameterReflection;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Php\RegexArrayShapeMatcher;
use PHPStan\Type\StaticMethodParameterOutTypeExtension;
use PHPStan\Type\Type;

final class PregMatchParameterOutTypeExtension implements StaticMethodParameterOutTypeExtension
{
    /**
     * @var RegexArrayShapeMatcher
     */
    private $regexShapeMatcher;

    public function __construct(
        RegexArrayShapeMatcher $regexShapeMatcher
    )
    {
        $this->regexShapeMatcher = $regexShapeMatcher;
    }

    public function isStaticMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool
    {
        return
            $methodReflection->getDeclaringClass()->getName() === Preg::class
            && in_array($methodReflection->getName(), [
                'match', 'isMatch', 'matchStrictGroups', 'isMatchStrictGroups',
                'matchAll', 'isMatchAll', 'matchAllStrictGroups', 'isMatchAllStrictGroups'
            ], true)
            && $parameter->getName() === 'matches';
    }

    public function getParameterOutTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, ParameterReflection $parameter, Scope $scope): ?Type
    {
        $args = $methodCall->getArgs();
        $patternArg = $args[0] ?? null;
        $matchesArg = $args[2] ?? null;
        $flagsArg = $args[3] ?? null;

        if (
            $patternArg === null || $matchesArg === null
        ) {
            return null;
        }

        $flagsType = PregMatchFlags::getType($flagsArg, $scope);
        if ($flagsType === null) {
            return null;
        }

        if (stripos($methodReflection->getName(), 'matchAll') !== false) {
            return $this->regexShapeMatcher->matchAllExpr($patternArg->value, $flagsType, TrinaryLogic::createMaybe(), $scope);
        }

        return $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createMaybe(), $scope);
    }

}
<?php declare(strict_types=1);

namespace Composer\Pcre\PHPStan;

use Composer\Pcre\Preg;
use PhpParser\Node\Expr\StaticCall;
use PHPStan\Analyser\Scope;
use PHPStan\Analyser\SpecifiedTypes;
use PHPStan\Analyser\TypeSpecifier;
use PHPStan\Analyser\TypeSpecifierAwareExtension;
use PHPStan\Analyser\TypeSpecifierContext;
use PHPStan\Reflection\MethodReflection;
use PHPStan\TrinaryLogic;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Php\RegexArrayShapeMatcher;
use PHPStan\Type\StaticMethodTypeSpecifyingExtension;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\Type;

final class PregMatchTypeSpecifyingExtension implements StaticMethodTypeSpecifyingExtension, TypeSpecifierAwareExtension
{
    /**
     * @var TypeSpecifier
     */
    private $typeSpecifier;

    /**
     * @var RegexArrayShapeMatcher
     */
    private $regexShapeMatcher;

    public function __construct(RegexArrayShapeMatcher $regexShapeMatcher)
    {
        $this->regexShapeMatcher = $regexShapeMatcher;
    }

    public function setTypeSpecifier(TypeSpecifier $typeSpecifier): void
    {
        $this->typeSpecifier = $typeSpecifier;
    }

    public function getClass(): string
    {
        return Preg::class;
    }

    public function isStaticMethodSupported(MethodReflection $methodReflection, StaticCall $node, TypeSpecifierContext $context): bool
    {
        return in_array($methodReflection->getName(), [
                'match', 'isMatch', 'matchStrictGroups', 'isMatchStrictGroups',
                'matchAll', 'isMatchAll', 'matchAllStrictGroups', 'isMatchAllStrictGroups'
            ], true)
            && !$context->null();
    }

    public function specifyTypes(MethodReflection $methodReflection, StaticCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
    {
        $args = $node->getArgs();
        $patternArg = $args[0] ?? null;
        $matchesArg = $args[2] ?? null;
        $flagsArg = $args[3] ?? null;

        if (
            $patternArg === null || $matchesArg === null
        ) {
            return new SpecifiedTypes();
        }

        $flagsType = PregMatchFlags::getType($flagsArg, $scope);
        if ($flagsType === null) {
            return new SpecifiedTypes();
        }

        if (stripos($methodReflection->getName(), 'matchAll') !== false) {
            $matchedType = $this->regexShapeMatcher->matchAllExpr($patternArg->value, $flagsType, TrinaryLogic::createFromBoolean($context->true()), $scope);
        } else {
            $matchedType = $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createFromBoolean($context->true()), $scope);
        }

        if ($matchedType === null) {
            return new SpecifiedTypes();
        }

        if (
            in_array($methodReflection->getName(), ['matchStrictGroups', 'isMatchStrictGroups', 'matchAllStrictGroups', 'isMatchAllStrictGroups'], true)
        ) {
            $matchedType = PregMatchFlags::removeNullFromMatches($matchedType);
        }

        $overwrite = false;
        if ($context->false()) {
            $overwrite = true;
            $context = $context->negate();
        }

        // @phpstan-ignore function.alreadyNarrowedType
        if (method_exists('PHPStan\Analyser\SpecifiedTypes', 'setRootExpr')) {
            $typeSpecifier = $this->typeSpecifier->create(
                $matchesArg->value,
                $matchedType,
                $context,
                $scope
            )->setRootExpr($node);

            return $overwrite ? $typeSpecifier->setAlwaysOverwriteTypes() : $typeSpecifier;
        }

        // @phpstan-ignore arguments.count
        return $this->typeSpecifier->create(
            $matchesArg->value,
            $matchedType,
            $context,
            // @phpstan-ignore argument.type
            $overwrite,
            $scope,
            $node
        );
    }
}
<?php declare(strict_types=1);

namespace Composer\Pcre\PHPStan;

use PHPStan\Analyser\Scope;
use PHPStan\Type\ArrayType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Constant\ConstantIntegerType;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\Type;
use PhpParser\Node\Arg;
use PHPStan\Type\Php\RegexArrayShapeMatcher;
use PHPStan\Type\TypeTraverser;
use PHPStan\Type\UnionType;

final class PregMatchFlags
{
    static public function getType(?Arg $flagsArg, Scope $scope): ?Type
    {
        if ($flagsArg === null) {
            return new ConstantIntegerType(PREG_UNMATCHED_AS_NULL);
        }

        $flagsType = $scope->getType($flagsArg->value);

        $constantScalars = $flagsType->getConstantScalarValues();
        if ($constantScalars === []) {
            return null;
        }

        $internalFlagsTypes = [];
        foreach ($flagsType->getConstantScalarValues() as $constantScalarValue) {
            if (!is_int($constantScalarValue)) {
                return null;
            }

            $internalFlagsTypes[] = new ConstantIntegerType($constantScalarValue | PREG_UNMATCHED_AS_NULL);
        }
        return TypeCombinator::union(...$internalFlagsTypes);
    }

    static public function removeNullFromMatches(Type $matchesType): Type
    {
        return TypeTraverser::map($matchesType, static function (Type $type, callable $traverse): Type {
            if ($type instanceof UnionType || $type instanceof IntersectionType) {
                return $traverse($type);
            }

            if ($type instanceof ConstantArrayType) {
                return new ConstantArrayType(
                    $type->getKeyTypes(),
                    array_map(static function (Type $valueType) use ($traverse): Type {
                        return $traverse($valueType);
                    }, $type->getValueTypes()),
                    $type->getNextAutoIndexes(),
                    [],
                    $type->isList()
                );
            }

            if ($type instanceof ArrayType) {
                return new ArrayType($type->getKeyType(), $traverse($type->getItemType()));
            }

            return TypeCombinator::removeNull($type);
        });
    }

}
<?php declare(strict_types=1);

namespace Composer\Pcre\PHPStan;

use Composer\Pcre\Preg;
use Composer\Pcre\Regex;
use PhpParser\Node\Expr\StaticCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\Native\NativeParameterReflection;
use PHPStan\Reflection\ParameterReflection;
use PHPStan\TrinaryLogic;
use PHPStan\Type\ClosureType;
use PHPStan\Type\Constant\ConstantArrayType;
use PHPStan\Type\Php\RegexArrayShapeMatcher;
use PHPStan\Type\StaticMethodParameterClosureTypeExtension;
use PHPStan\Type\StringType;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\Type;

final class PregReplaceCallbackClosureTypeExtension implements StaticMethodParameterClosureTypeExtension
{
    /**
     * @var RegexArrayShapeMatcher
     */
    private $regexShapeMatcher;

    public function __construct(RegexArrayShapeMatcher $regexShapeMatcher)
    {
        $this->regexShapeMatcher = $regexShapeMatcher;
    }

    public function isStaticMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool
    {
        return in_array($methodReflection->getDeclaringClass()->getName(), [Preg::class, Regex::class], true)
            && in_array($methodReflection->getName(), ['replaceCallback', 'replaceCallbackStrictGroups'], true)
            && $parameter->getName() === 'replacement';
    }

    public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, ParameterReflection $parameter, Scope $scope): ?Type
    {
        $args = $methodCall->getArgs();
        $patternArg = $args[0] ?? null;
        $flagsArg = $args[5] ?? null;

        if (
            $patternArg === null
        ) {
            return null;
        }

        $flagsType = PregMatchFlags::getType($flagsArg, $scope);

        $matchesType = $this->regexShapeMatcher->matchExpr($patternArg->value, $flagsType, TrinaryLogic::createYes(), $scope);
        if ($matchesType === null) {
            return null;
        }

        if ($methodReflection->getName() === 'replaceCallbackStrictGroups' && count($matchesType->getConstantArrays()) === 1) {
            $matchesType = $matchesType->getConstantArrays()[0];
            $matchesType = new ConstantArrayType(
                $matchesType->getKeyTypes(),
                array_map(static function (Type $valueType): Type {
                    if (count($valueType->getConstantArrays()) === 1) {
                        $valueTypeArray = $valueType->getConstantArrays()[0];
                        return new ConstantArrayType(
                            $valueTypeArray->getKeyTypes(),
                            array_map(static function (Type $valueType): Type {
                                return TypeCombinator::removeNull($valueType);
                            }, $valueTypeArray->getValueTypes()),
                            $valueTypeArray->getNextAutoIndexes(),
                            [],
                            $valueTypeArray->isList()
                        );
                    }
                    return TypeCombinator::removeNull($valueType);
                }, $matchesType->getValueTypes()),
                $matchesType->getNextAutoIndexes(),
                [],
                $matchesType->isList()
            );
        }

        return new ClosureType(
            [
                new NativeParameterReflection($parameter->getName(), $parameter->isOptional(), $matchesType, $parameter->passedByReference(), $parameter->isVariadic(), $parameter->getDefaultValue()),
            ],
            new StringType()
        );
    }
}
<?php

/*
 * This file is part of composer/pcre.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Pcre;

final class MatchAllWithOffsetsResult
{
    /**
     * An array of match group => list of matches, every match being a pair of string matched + offset in bytes (or -1 if no match)
     *
     * @readonly
     * @var array<int|string, list<array{string|null, int}>>
     * @phpstan-var array<int|string, list<array{string|null, int<-1, max>}>>
     */
    public $matches;

    /**
     * @readonly
     * @var 0|positive-int
     */
    public $count;

    /**
     * @readonly
     * @var bool
     */
    public $matched;

    /**
     * @param 0|positive-int $count
     * @param array<int|string, list<array{string|null, int}>> $matches
     * @phpstan-param array<int|string, list<array{string|null, int<-1, max>}>> $matches
     */
    public function __construct(int $count, array $matches)
    {
        $this->matches = $matches;
        $this->matched = (bool) $count;
        $this->count = $count;
    }
}
<?php

/*
 * This file is part of composer/pcre.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Pcre;

class UnexpectedNullMatchException extends PcreException
{
    public static function fromFunction($function, $pattern)
    {
        throw new \LogicException('fromFunction should not be called on '.self::class.', use '.PcreException::class);
    }
}
<?php

/*
 * This file is part of composer/pcre.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Pcre;

class Preg
{
    /** @internal */
    public const ARRAY_MSG = '$subject as an array is not supported. You can use \'foreach\' instead.';
    /** @internal */
    public const INVALID_TYPE_MSG = '$subject must be a string, %s given.';

    /**
     * @param non-empty-string   $pattern
     * @param array<mixed> $matches Set by method
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
     * @return 0|1
     *
     * @param-out array<int|string, string|null> $matches
     */
    public static function match(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int
    {
        self::checkOffsetCapture($flags, 'matchWithOffsets');

        $result = preg_match($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL, $offset);
        if ($result === false) {
            throw PcreException::fromFunction('preg_match', $pattern);
        }

        return $result;
    }

    /**
     * Variant of `match()` which outputs non-null matches (or throws)
     *
     * @param non-empty-string $pattern
     * @param array<mixed> $matches Set by method
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
     * @return 0|1
     * @throws UnexpectedNullMatchException
     *
     * @param-out array<int|string, string> $matches
     */
    public static function matchStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int
    {
        $result = self::match($pattern, $subject, $matchesInternal, $flags, $offset);
        $matches = self::enforceNonNullMatches($pattern, $matchesInternal, 'match');

        return $result;
    }

    /**
     * Runs preg_match with PREG_OFFSET_CAPTURE
     *
     * @param non-empty-string   $pattern
     * @param array<mixed> $matches Set by method
     * @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_UNMATCHED_AS_NULL and PREG_OFFSET_CAPTURE are always set, no other flags are supported
     * @return 0|1
     *
     * @param-out array<int|string, array{string|null, int<-1, max>}> $matches
     */
    public static function matchWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): int
    {
        $result = preg_match($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL | PREG_OFFSET_CAPTURE, $offset);
        if ($result === false) {
            throw PcreException::fromFunction('preg_match', $pattern);
        }

        return $result;
    }

    /**
     * @param non-empty-string   $pattern
     * @param array<mixed> $matches Set by method
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
     * @return 0|positive-int
     *
     * @param-out array<int|string, list<string|null>> $matches
     */
    public static function matchAll(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int
    {
        self::checkOffsetCapture($flags, 'matchAllWithOffsets');
        self::checkSetOrder($flags);

        $result = preg_match_all($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL, $offset);
        if (!is_int($result)) { // PHP < 8 may return null, 8+ returns int|false
            throw PcreException::fromFunction('preg_match_all', $pattern);
        }

        return $result;
    }

    /**
     * Variant of `match()` which outputs non-null matches (or throws)
     *
     * @param non-empty-string   $pattern
     * @param array<mixed> $matches Set by method
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
     * @return 0|positive-int
     * @throws UnexpectedNullMatchException
     *
     * @param-out array<int|string, list<string>> $matches
     */
    public static function matchAllStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): int
    {
        $result = self::matchAll($pattern, $subject, $matchesInternal, $flags, $offset);
        $matches = self::enforceNonNullMatchAll($pattern, $matchesInternal, 'matchAll');

        return $result;
    }

    /**
     * Runs preg_match_all with PREG_OFFSET_CAPTURE
     *
     * @param non-empty-string   $pattern
     * @param array<mixed> $matches Set by method
     * @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_UNMATCHED_AS_NULL and PREG_MATCH_OFFSET are always set, no other flags are supported
     * @return 0|positive-int
     *
     * @param-out array<int|string, list<array{string|null, int<-1, max>}>> $matches
     */
    public static function matchAllWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): int
    {
        self::checkSetOrder($flags);

        $result = preg_match_all($pattern, $subject, $matches, $flags | PREG_UNMATCHED_AS_NULL | PREG_OFFSET_CAPTURE, $offset);
        if (!is_int($result)) { // PHP < 8 may return null, 8+ returns int|false
            throw PcreException::fromFunction('preg_match_all', $pattern);
        }

        return $result;
    }

    /**
     * @param string|string[] $pattern
     * @param string|string[] $replacement
     * @param string $subject
     * @param int             $count Set by method
     *
     * @param-out int<0, max> $count
     */
    public static function replace($pattern, $replacement, $subject, int $limit = -1, ?int &$count = null): string
    {
        if (!is_scalar($subject)) {
            if (is_array($subject)) {
                throw new \InvalidArgumentException(static::ARRAY_MSG);
            }

            throw new \TypeError(sprintf(static::INVALID_TYPE_MSG, gettype($subject)));
        }

        $result = preg_replace($pattern, $replacement, $subject, $limit, $count);
        if ($result === null) {
            throw PcreException::fromFunction('preg_replace', $pattern);
        }

        return $result;
    }

    /**
     * @param string|string[] $pattern
     * @param ($flags is PREG_OFFSET_CAPTURE ? (callable(array<int|string, array{string|null, int<-1, max>}>): string) : callable(array<int|string, string|null>): string) $replacement
     * @param string $subject
     * @param int             $count Set by method
     * @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set
     *
     * @param-out int<0, max> $count
     */
    public static function replaceCallback($pattern, callable $replacement, $subject, int $limit = -1, ?int &$count = null, int $flags = 0): string
    {
        if (!is_scalar($subject)) {
            if (is_array($subject)) {
                throw new \InvalidArgumentException(static::ARRAY_MSG);
            }

            throw new \TypeError(sprintf(static::INVALID_TYPE_MSG, gettype($subject)));
        }

        $result = preg_replace_callback($pattern, $replacement, $subject, $limit, $count, $flags | PREG_UNMATCHED_AS_NULL);
        if ($result === null) {
            throw PcreException::fromFunction('preg_replace_callback', $pattern);
        }

        return $result;
    }

    /**
     * Variant of `replaceCallback()` which outputs non-null matches (or throws)
     *
     * @param string $pattern
     * @param ($flags is PREG_OFFSET_CAPTURE ? (callable(array<int|string, array{string, int<0, max>}>): string) : callable(array<int|string, string>): string) $replacement
     * @param string $subject
     * @param int $count Set by method
     * @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set
     *
     * @param-out int<0, max> $count
     */
    public static function replaceCallbackStrictGroups(string $pattern, callable $replacement, $subject, int $limit = -1, ?int &$count = null, int $flags = 0): string
    {
        return self::replaceCallback($pattern, function (array $matches) use ($pattern, $replacement) {
            return $replacement(self::enforceNonNullMatches($pattern, $matches, 'replaceCallback'));
        }, $subject, $limit, $count, $flags);
    }

    /**
     * @param ($flags is PREG_OFFSET_CAPTURE ? (array<string, callable(array<int|string, array{string|null, int<-1, max>}>): string>) : array<string, callable(array<int|string, string|null>): string>) $pattern
     * @param string $subject
     * @param int    $count Set by method
     * @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set
     *
     * @param-out int<0, max> $count
     */
    public static function replaceCallbackArray(array $pattern, $subject, int $limit = -1, ?int &$count = null, int $flags = 0): string
    {
        if (!is_scalar($subject)) {
            if (is_array($subject)) {
                throw new \InvalidArgumentException(static::ARRAY_MSG);
            }

            throw new \TypeError(sprintf(static::INVALID_TYPE_MSG, gettype($subject)));
        }

        $result = preg_replace_callback_array($pattern, $subject, $limit, $count, $flags | PREG_UNMATCHED_AS_NULL);
        if ($result === null) {
            $pattern = array_keys($pattern);
            throw PcreException::fromFunction('preg_replace_callback_array', $pattern);
        }

        return $result;
    }

    /**
     * @param int-mask<PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_OFFSET_CAPTURE> $flags PREG_SPLIT_NO_EMPTY or PREG_SPLIT_DELIM_CAPTURE
     * @return list<string>
     */
    public static function split(string $pattern, string $subject, int $limit = -1, int $flags = 0): array
    {
        if (($flags & PREG_SPLIT_OFFSET_CAPTURE) !== 0) {
            throw new \InvalidArgumentException('PREG_SPLIT_OFFSET_CAPTURE is not supported as it changes the type of $matches, use splitWithOffsets() instead');
        }

        $result = preg_split($pattern, $subject, $limit, $flags);
        if ($result === false) {
            throw PcreException::fromFunction('preg_split', $pattern);
        }

        return $result;
    }

    /**
     * @param int-mask<PREG_SPLIT_NO_EMPTY|PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_OFFSET_CAPTURE> $flags PREG_SPLIT_NO_EMPTY or PREG_SPLIT_DELIM_CAPTURE, PREG_SPLIT_OFFSET_CAPTURE is always set
     * @return list<array{string, int}>
     * @phpstan-return list<array{string, int<0, max>}>
     */
    public static function splitWithOffsets(string $pattern, string $subject, int $limit = -1, int $flags = 0): array
    {
        $result = preg_split($pattern, $subject, $limit, $flags | PREG_SPLIT_OFFSET_CAPTURE);
        if ($result === false) {
            throw PcreException::fromFunction('preg_split', $pattern);
        }

        return $result;
    }

    /**
     * @template T of string|\Stringable
     * @param string   $pattern
     * @param array<T> $array
     * @param int-mask<PREG_GREP_INVERT> $flags PREG_GREP_INVERT
     * @return array<T>
     */
    public static function grep(string $pattern, array $array, int $flags = 0): array
    {
        $result = preg_grep($pattern, $array, $flags);
        if ($result === false) {
            throw PcreException::fromFunction('preg_grep', $pattern);
        }

        return $result;
    }

    /**
     * Variant of match() which returns a bool instead of int
     *
     * @param non-empty-string   $pattern
     * @param array<mixed> $matches Set by method
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
     *
     * @param-out array<int|string, string|null> $matches
     */
    public static function isMatch(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool
    {
        return (bool) static::match($pattern, $subject, $matches, $flags, $offset);
    }

    /**
     * Variant of `isMatch()` which outputs non-null matches (or throws)
     *
     * @param non-empty-string $pattern
     * @param array<mixed> $matches Set by method
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
     * @throws UnexpectedNullMatchException
     *
     * @param-out array<int|string, string> $matches
     */
    public static function isMatchStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool
    {
        return (bool) self::matchStrictGroups($pattern, $subject, $matches, $flags, $offset);
    }

    /**
     * Variant of matchAll() which returns a bool instead of int
     *
     * @param non-empty-string   $pattern
     * @param array<mixed> $matches Set by method
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
     *
     * @param-out array<int|string, list<string|null>> $matches
     */
    public static function isMatchAll(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool
    {
        return (bool) static::matchAll($pattern, $subject, $matches, $flags, $offset);
    }

    /**
     * Variant of `isMatchAll()` which outputs non-null matches (or throws)
     *
     * @param non-empty-string $pattern
     * @param array<mixed> $matches Set by method
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
     *
     * @param-out array<int|string, list<string>> $matches
     */
    public static function isMatchAllStrictGroups(string $pattern, string $subject, ?array &$matches = null, int $flags = 0, int $offset = 0): bool
    {
        return (bool) self::matchAllStrictGroups($pattern, $subject, $matches, $flags, $offset);
    }

    /**
     * Variant of matchWithOffsets() which returns a bool instead of int
     *
     * Runs preg_match with PREG_OFFSET_CAPTURE
     *
     * @param non-empty-string   $pattern
     * @param array<mixed> $matches Set by method
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
     *
     * @param-out array<int|string, array{string|null, int<-1, max>}> $matches
     */
    public static function isMatchWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): bool
    {
        return (bool) static::matchWithOffsets($pattern, $subject, $matches, $flags, $offset);
    }

    /**
     * Variant of matchAllWithOffsets() which returns a bool instead of int
     *
     * Runs preg_match_all with PREG_OFFSET_CAPTURE
     *
     * @param non-empty-string   $pattern
     * @param array<mixed> $matches Set by method
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
     *
     * @param-out array<int|string, list<array{string|null, int<-1, max>}>> $matches
     */
    public static function isMatchAllWithOffsets(string $pattern, string $subject, ?array &$matches, int $flags = 0, int $offset = 0): bool
    {
        return (bool) static::matchAllWithOffsets($pattern, $subject, $matches, $flags, $offset);
    }

    private static function checkOffsetCapture(int $flags, string $useFunctionName): void
    {
        if (($flags & PREG_OFFSET_CAPTURE) !== 0) {
            throw new \InvalidArgumentException('PREG_OFFSET_CAPTURE is not supported as it changes the type of $matches, use ' . $useFunctionName . '() instead');
        }
    }

    private static function checkSetOrder(int $flags): void
    {
        if (($flags & PREG_SET_ORDER) !== 0) {
            throw new \InvalidArgumentException('PREG_SET_ORDER is not supported as it changes the type of $matches');
        }
    }

    /**
     * @param array<int|string, string|null|array{string|null, int}> $matches
     * @return array<int|string, string>
     * @throws UnexpectedNullMatchException
     */
    private static function enforceNonNullMatches(string $pattern, array $matches, string $variantMethod)
    {
        foreach ($matches as $group => $match) {
            if (is_string($match) || (is_array($match) && is_string($match[0]))) {
                continue;
            }

            throw new UnexpectedNullMatchException('Pattern "'.$pattern.'" had an unexpected unmatched group "'.$group.'", make sure the pattern always matches or use '.$variantMethod.'() instead.');
        }

        /** @var array<string> */
        return $matches;
    }

    /**
     * @param array<int|string, list<string|null>> $matches
     * @return array<int|string, list<string>>
     * @throws UnexpectedNullMatchException
     */
    private static function enforceNonNullMatchAll(string $pattern, array $matches, string $variantMethod)
    {
        foreach ($matches as $group => $groupMatches) {
            foreach ($groupMatches as $match) {
                if (null === $match) {
                    throw new UnexpectedNullMatchException('Pattern "'.$pattern.'" had an unexpected unmatched group "'.$group.'", make sure the pattern always matches or use '.$variantMethod.'() instead.');
                }
            }
        }

        /** @var array<int|string, list<string>> */
        return $matches;
    }
}
<?php

/*
 * This file is part of composer/pcre.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Pcre;

final class MatchResult
{
    /**
     * An array of match group => string matched
     *
     * @readonly
     * @var array<int|string, string|null>
     */
    public $matches;

    /**
     * @readonly
     * @var bool
     */
    public $matched;

    /**
     * @param 0|positive-int $count
     * @param array<string|null> $matches
     */
    public function __construct(int $count, array $matches)
    {
        $this->matches = $matches;
        $this->matched = (bool) $count;
    }
}
<?php

/*
 * This file is part of composer/pcre.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Pcre;

final class MatchAllStrictGroupsResult
{
    /**
     * An array of match group => list of matched strings
     *
     * @readonly
     * @var array<int|string, list<string>>
     */
    public $matches;

    /**
     * @readonly
     * @var 0|positive-int
     */
    public $count;

    /**
     * @readonly
     * @var bool
     */
    public $matched;

    /**
     * @param 0|positive-int $count
     * @param array<list<string>> $matches
     */
    public function __construct(int $count, array $matches)
    {
        $this->matches = $matches;
        $this->matched = (bool) $count;
        $this->count = $count;
    }
}
<?php

/*
 * This file is part of composer/pcre.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Pcre;

class PcreException extends \RuntimeException
{
    /**
     * @param  string $function
     * @param  string|string[] $pattern
     * @return self
     */
    public static function fromFunction($function, $pattern)
    {
        $code = preg_last_error();

        if (is_array($pattern)) {
            $pattern = implode(', ', $pattern);
        }

        return new PcreException($function.'(): failed executing "'.$pattern.'": '.self::pcreLastErrorMessage($code), $code);
    }

    /**
     * @param  int $code
     * @return string
     */
    private static function pcreLastErrorMessage($code)
    {
        if (function_exists('preg_last_error_msg')) {
            return preg_last_error_msg();
        }

        $constants = get_defined_constants(true);
        if (!isset($constants['pcre']) || !is_array($constants['pcre'])) {
            return 'UNDEFINED_ERROR';
        }

        foreach ($constants['pcre'] as $const => $val) {
            if ($val === $code && substr($const, -6) === '_ERROR') {
                return $const;
            }
        }

        return 'UNDEFINED_ERROR';
    }
}
<?php

/*
 * This file is part of composer/pcre.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Pcre;

final class MatchAllResult
{
    /**
     * An array of match group => list of matched strings
     *
     * @readonly
     * @var array<int|string, list<string|null>>
     */
    public $matches;

    /**
     * @readonly
     * @var 0|positive-int
     */
    public $count;

    /**
     * @readonly
     * @var bool
     */
    public $matched;

    /**
     * @param 0|positive-int $count
     * @param array<int|string, list<string|null>> $matches
     */
    public function __construct(int $count, array $matches)
    {
        $this->matches = $matches;
        $this->matched = (bool) $count;
        $this->count = $count;
    }
}
<?php

/*
 * This file is part of composer/pcre.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Pcre;

final class ReplaceResult
{
    /**
     * @readonly
     * @var string
     */
    public $result;

    /**
     * @readonly
     * @var 0|positive-int
     */
    public $count;

    /**
     * @readonly
     * @var bool
     */
    public $matched;

    /**
     * @param 0|positive-int $count
     */
    public function __construct(int $count, string $result)
    {
        $this->count = $count;
        $this->matched = (bool) $count;
        $this->result = $result;
    }
}
<?php

/*
 * This file is part of composer/pcre.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Pcre;

final class MatchStrictGroupsResult
{
    /**
     * An array of match group => string matched
     *
     * @readonly
     * @var array<int|string, string>
     */
    public $matches;

    /**
     * @readonly
     * @var bool
     */
    public $matched;

    /**
     * @param 0|positive-int $count
     * @param array<string> $matches
     */
    public function __construct(int $count, array $matches)
    {
        $this->matches = $matches;
        $this->matched = (bool) $count;
    }
}
<?php

/*
 * This file is part of composer/pcre.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Pcre;

class Regex
{
    /**
     * @param non-empty-string $pattern
     */
    public static function isMatch(string $pattern, string $subject, int $offset = 0): bool
    {
        return (bool) Preg::match($pattern, $subject, $matches, 0, $offset);
    }

    /**
     * @param non-empty-string $pattern
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
     */
    public static function match(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchResult
    {
        self::checkOffsetCapture($flags, 'matchWithOffsets');

        $count = Preg::match($pattern, $subject, $matches, $flags, $offset);

        return new MatchResult($count, $matches);
    }

    /**
     * Variant of `match()` which returns non-null matches (or throws)
     *
     * @param non-empty-string $pattern
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
     * @throws UnexpectedNullMatchException
     */
    public static function matchStrictGroups(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchStrictGroupsResult
    {
        // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups
        $count = Preg::matchStrictGroups($pattern, $subject, $matches, $flags, $offset);

        return new MatchStrictGroupsResult($count, $matches);
    }

    /**
     * Runs preg_match with PREG_OFFSET_CAPTURE
     *
     * @param non-empty-string $pattern
     * @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_UNMATCHED_AS_NULL and PREG_MATCH_OFFSET are always set, no other flags are supported
     */
    public static function matchWithOffsets(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchWithOffsetsResult
    {
        $count = Preg::matchWithOffsets($pattern, $subject, $matches, $flags, $offset);

        return new MatchWithOffsetsResult($count, $matches);
    }

    /**
     * @param non-empty-string $pattern
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
     */
    public static function matchAll(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchAllResult
    {
        self::checkOffsetCapture($flags, 'matchAllWithOffsets');
        self::checkSetOrder($flags);

        $count = Preg::matchAll($pattern, $subject, $matches, $flags, $offset);

        return new MatchAllResult($count, $matches);
    }

    /**
     * Variant of `matchAll()` which returns non-null matches (or throws)
     *
     * @param non-empty-string $pattern
     * @param int-mask<PREG_UNMATCHED_AS_NULL> $flags PREG_UNMATCHED_AS_NULL is always set, no other flags are supported
     * @throws UnexpectedNullMatchException
     */
    public static function matchAllStrictGroups(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchAllStrictGroupsResult
    {
        self::checkOffsetCapture($flags, 'matchAllWithOffsets');
        self::checkSetOrder($flags);

        // @phpstan-ignore composerPcre.maybeUnsafeStrictGroups
        $count = Preg::matchAllStrictGroups($pattern, $subject, $matches, $flags, $offset);

        return new MatchAllStrictGroupsResult($count, $matches);
    }

    /**
     * Runs preg_match_all with PREG_OFFSET_CAPTURE
     *
     * @param non-empty-string $pattern
     * @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_UNMATCHED_AS_NULL and PREG_MATCH_OFFSET are always set, no other flags are supported
     */
    public static function matchAllWithOffsets(string $pattern, string $subject, int $flags = 0, int $offset = 0): MatchAllWithOffsetsResult
    {
        self::checkSetOrder($flags);

        $count = Preg::matchAllWithOffsets($pattern, $subject, $matches, $flags, $offset);

        return new MatchAllWithOffsetsResult($count, $matches);
    }
    /**
     * @param string|string[] $pattern
     * @param string|string[] $replacement
     * @param string          $subject
     */
    public static function replace($pattern, $replacement, $subject, int $limit = -1): ReplaceResult
    {
        $result = Preg::replace($pattern, $replacement, $subject, $limit, $count);

        return new ReplaceResult($count, $result);
    }

    /**
     * @param string|string[] $pattern
     * @param ($flags is PREG_OFFSET_CAPTURE ? (callable(array<int|string, array{string|null, int<-1, max>}>): string) : callable(array<int|string, string|null>): string) $replacement
     * @param string          $subject
     * @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set
     */
    public static function replaceCallback($pattern, callable $replacement, $subject, int $limit = -1, int $flags = 0): ReplaceResult
    {
        $result = Preg::replaceCallback($pattern, $replacement, $subject, $limit, $count, $flags);

        return new ReplaceResult($count, $result);
    }

    /**
     * Variant of `replaceCallback()` which outputs non-null matches (or throws)
     *
     * @param string $pattern
     * @param ($flags is PREG_OFFSET_CAPTURE ? (callable(array<int|string, array{string, int<0, max>}>): string) : callable(array<int|string, string>): string) $replacement
     * @param string          $subject
     * @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set
     */
    public static function replaceCallbackStrictGroups($pattern, callable $replacement, $subject, int $limit = -1, int $flags = 0): ReplaceResult
    {
        $result = Preg::replaceCallbackStrictGroups($pattern, $replacement, $subject, $limit, $count, $flags);

        return new ReplaceResult($count, $result);
    }

    /**
     * @param ($flags is PREG_OFFSET_CAPTURE ? (array<string, callable(array<int|string, array{string|null, int<-1, max>}>): string>) : array<string, callable(array<int|string, string|null>): string>) $pattern
     * @param string $subject
     * @param int-mask<PREG_UNMATCHED_AS_NULL|PREG_OFFSET_CAPTURE> $flags PREG_OFFSET_CAPTURE is supported, PREG_UNMATCHED_AS_NULL is always set
     */
    public static function replaceCallbackArray(array $pattern, $subject, int $limit = -1, int $flags = 0): ReplaceResult
    {
        $result = Preg::replaceCallbackArray($pattern, $subject, $limit, $count, $flags);

        return new ReplaceResult($count, $result);
    }

    private static function checkOffsetCapture(int $flags, string $useFunctionName): void
    {
        if (($flags & PREG_OFFSET_CAPTURE) !== 0) {
            throw new \InvalidArgumentException('PREG_OFFSET_CAPTURE is not supported as it changes the return type, use '.$useFunctionName.'() instead');
        }
    }

    private static function checkSetOrder(int $flags): void
    {
        if (($flags & PREG_SET_ORDER) !== 0) {
            throw new \InvalidArgumentException('PREG_SET_ORDER is not supported as it changes the return type');
        }
    }
}
<?php

// autoload_classmap.php @generated by Composer

$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);

return array(
    'Composer\\Advisory\\Auditor' => $vendorDir . '/composer/composer/src/Composer/Advisory/Auditor.php',
    'Composer\\Advisory\\IgnoredSecurityAdvisory' => $vendorDir . '/composer/composer/src/Composer/Advisory/IgnoredSecurityAdvisory.php',
    'Composer\\Advisory\\PartialSecurityAdvisory' => $vendorDir . '/composer/composer/src/Composer/Advisory/PartialSecurityAdvisory.php',
    'Composer\\Advisory\\SecurityAdvisory' => $vendorDir . '/composer/composer/src/Composer/Advisory/SecurityAdvisory.php',
    'Composer\\Autoload\\AutoloadGenerator' => $vendorDir . '/composer/composer/src/Composer/Autoload/AutoloadGenerator.php',
    'Composer\\Autoload\\ClassLoader' => $vendorDir . '/composer/composer/src/Composer/Autoload/ClassLoader.php',
    'Composer\\Autoload\\ClassMapGenerator' => $vendorDir . '/composer/composer/src/Composer/Autoload/ClassMapGenerator.php',
    'Composer\\CaBundle\\CaBundle' => $vendorDir . '/composer/ca-bundle/src/CaBundle.php',
    'Composer\\Cache' => $vendorDir . '/composer/composer/src/Composer/Cache.php',
    'Composer\\ClassMapGenerator\\ClassMap' => $vendorDir . '/composer/class-map-generator/src/ClassMap.php',
    'Composer\\ClassMapGenerator\\ClassMapGenerator' => $vendorDir . '/composer/class-map-generator/src/ClassMapGenerator.php',
    'Composer\\ClassMapGenerator\\FileList' => $vendorDir . '/composer/class-map-generator/src/FileList.php',
    'Composer\\ClassMapGenerator\\PhpFileCleaner' => $vendorDir . '/composer/class-map-generator/src/PhpFileCleaner.php',
    'Composer\\ClassMapGenerator\\PhpFileParser' => $vendorDir . '/composer/class-map-generator/src/PhpFileParser.php',
    'Composer\\Command\\AboutCommand' => $vendorDir . '/composer/composer/src/Composer/Command/AboutCommand.php',
    'Composer\\Command\\ArchiveCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ArchiveCommand.php',
    'Composer\\Command\\AuditCommand' => $vendorDir . '/composer/composer/src/Composer/Command/AuditCommand.php',
    'Composer\\Command\\BaseCommand' => $vendorDir . '/composer/composer/src/Composer/Command/BaseCommand.php',
    'Composer\\Command\\BaseDependencyCommand' => $vendorDir . '/composer/composer/src/Composer/Command/BaseDependencyCommand.php',
    'Composer\\Command\\BumpCommand' => $vendorDir . '/composer/composer/src/Composer/Command/BumpCommand.php',
    'Composer\\Command\\CheckPlatformReqsCommand' => $vendorDir . '/composer/composer/src/Composer/Command/CheckPlatformReqsCommand.php',
    'Composer\\Command\\ClearCacheCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ClearCacheCommand.php',
    'Composer\\Command\\CompletionTrait' => $vendorDir . '/composer/composer/src/Composer/Command/CompletionTrait.php',
    'Composer\\Command\\ConfigCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ConfigCommand.php',
    'Composer\\Command\\CreateProjectCommand' => $vendorDir . '/composer/composer/src/Composer/Command/CreateProjectCommand.php',
    'Composer\\Command\\DependsCommand' => $vendorDir . '/composer/composer/src/Composer/Command/DependsCommand.php',
    'Composer\\Command\\DiagnoseCommand' => $vendorDir . '/composer/composer/src/Composer/Command/DiagnoseCommand.php',
    'Composer\\Command\\DumpAutoloadCommand' => $vendorDir . '/composer/composer/src/Composer/Command/DumpAutoloadCommand.php',
    'Composer\\Command\\ExecCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ExecCommand.php',
    'Composer\\Command\\FundCommand' => $vendorDir . '/composer/composer/src/Composer/Command/FundCommand.php',
    'Composer\\Command\\GlobalCommand' => $vendorDir . '/composer/composer/src/Composer/Command/GlobalCommand.php',
    'Composer\\Command\\HomeCommand' => $vendorDir . '/composer/composer/src/Composer/Command/HomeCommand.php',
    'Composer\\Command\\InitCommand' => $vendorDir . '/composer/composer/src/Composer/Command/InitCommand.php',
    'Composer\\Command\\InstallCommand' => $vendorDir . '/composer/composer/src/Composer/Command/InstallCommand.php',
    'Composer\\Command\\LicensesCommand' => $vendorDir . '/composer/composer/src/Composer/Command/LicensesCommand.php',
    'Composer\\Command\\OutdatedCommand' => $vendorDir . '/composer/composer/src/Composer/Command/OutdatedCommand.php',
    'Composer\\Command\\PackageDiscoveryTrait' => $vendorDir . '/composer/composer/src/Composer/Command/PackageDiscoveryTrait.php',
    'Composer\\Command\\ProhibitsCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ProhibitsCommand.php',
    'Composer\\Command\\ReinstallCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ReinstallCommand.php',
    'Composer\\Command\\RemoveCommand' => $vendorDir . '/composer/composer/src/Composer/Command/RemoveCommand.php',
    'Composer\\Command\\RequireCommand' => $vendorDir . '/composer/composer/src/Composer/Command/RequireCommand.php',
    'Composer\\Command\\RunScriptCommand' => $vendorDir . '/composer/composer/src/Composer/Command/RunScriptCommand.php',
    'Composer\\Command\\ScriptAliasCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ScriptAliasCommand.php',
    'Composer\\Command\\SearchCommand' => $vendorDir . '/composer/composer/src/Composer/Command/SearchCommand.php',
    'Composer\\Command\\SelfUpdateCommand' => $vendorDir . '/composer/composer/src/Composer/Command/SelfUpdateCommand.php',
    'Composer\\Command\\ShowCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ShowCommand.php',
    'Composer\\Command\\StatusCommand' => $vendorDir . '/composer/composer/src/Composer/Command/StatusCommand.php',
    'Composer\\Command\\SuggestsCommand' => $vendorDir . '/composer/composer/src/Composer/Command/SuggestsCommand.php',
    'Composer\\Command\\UpdateCommand' => $vendorDir . '/composer/composer/src/Composer/Command/UpdateCommand.php',
    'Composer\\Command\\ValidateCommand' => $vendorDir . '/composer/composer/src/Composer/Command/ValidateCommand.php',
    'Composer\\Compiler' => $vendorDir . '/composer/composer/src/Composer/Compiler.php',
    'Composer\\Composer' => $vendorDir . '/composer/composer/src/Composer/Composer.php',
    'Composer\\Config' => $vendorDir . '/composer/composer/src/Composer/Config.php',
    'Composer\\Config\\ConfigSourceInterface' => $vendorDir . '/composer/composer/src/Composer/Config/ConfigSourceInterface.php',
    'Composer\\Config\\JsonConfigSource' => $vendorDir . '/composer/composer/src/Composer/Config/JsonConfigSource.php',
    'Composer\\Console\\Application' => $vendorDir . '/composer/composer/src/Composer/Console/Application.php',
    'Composer\\Console\\GithubActionError' => $vendorDir . '/composer/composer/src/Composer/Console/GithubActionError.php',
    'Composer\\Console\\HtmlOutputFormatter' => $vendorDir . '/composer/composer/src/Composer/Console/HtmlOutputFormatter.php',
    'Composer\\Console\\Input\\InputArgument' => $vendorDir . '/composer/composer/src/Composer/Console/Input/InputArgument.php',
    'Composer\\Console\\Input\\InputOption' => $vendorDir . '/composer/composer/src/Composer/Console/Input/InputOption.php',
    'Composer\\DependencyResolver\\Decisions' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Decisions.php',
    'Composer\\DependencyResolver\\DefaultPolicy' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/DefaultPolicy.php',
    'Composer\\DependencyResolver\\GenericRule' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/GenericRule.php',
    'Composer\\DependencyResolver\\LocalRepoTransaction' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/LocalRepoTransaction.php',
    'Composer\\DependencyResolver\\LockTransaction' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/LockTransaction.php',
    'Composer\\DependencyResolver\\MultiConflictRule' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/MultiConflictRule.php',
    'Composer\\DependencyResolver\\Operation\\InstallOperation' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/InstallOperation.php',
    'Composer\\DependencyResolver\\Operation\\MarkAliasInstalledOperation' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php',
    'Composer\\DependencyResolver\\Operation\\MarkAliasUninstalledOperation' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php',
    'Composer\\DependencyResolver\\Operation\\OperationInterface' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/OperationInterface.php',
    'Composer\\DependencyResolver\\Operation\\SolverOperation' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/SolverOperation.php',
    'Composer\\DependencyResolver\\Operation\\UninstallOperation' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/UninstallOperation.php',
    'Composer\\DependencyResolver\\Operation\\UpdateOperation' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Operation/UpdateOperation.php',
    'Composer\\DependencyResolver\\PolicyInterface' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/PolicyInterface.php',
    'Composer\\DependencyResolver\\Pool' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Pool.php',
    'Composer\\DependencyResolver\\PoolBuilder' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/PoolBuilder.php',
    'Composer\\DependencyResolver\\PoolOptimizer' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/PoolOptimizer.php',
    'Composer\\DependencyResolver\\Problem' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Problem.php',
    'Composer\\DependencyResolver\\Request' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Request.php',
    'Composer\\DependencyResolver\\Rule' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Rule.php',
    'Composer\\DependencyResolver\\Rule2Literals' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Rule2Literals.php',
    'Composer\\DependencyResolver\\RuleSet' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/RuleSet.php',
    'Composer\\DependencyResolver\\RuleSetGenerator' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/RuleSetGenerator.php',
    'Composer\\DependencyResolver\\RuleSetIterator' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/RuleSetIterator.php',
    'Composer\\DependencyResolver\\RuleWatchChain' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/RuleWatchChain.php',
    'Composer\\DependencyResolver\\RuleWatchGraph' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/RuleWatchGraph.php',
    'Composer\\DependencyResolver\\RuleWatchNode' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/RuleWatchNode.php',
    'Composer\\DependencyResolver\\Solver' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Solver.php',
    'Composer\\DependencyResolver\\SolverBugException' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/SolverBugException.php',
    'Composer\\DependencyResolver\\SolverProblemsException' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/SolverProblemsException.php',
    'Composer\\DependencyResolver\\Transaction' => $vendorDir . '/composer/composer/src/Composer/DependencyResolver/Transaction.php',
    'Composer\\Downloader\\ArchiveDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/ArchiveDownloader.php',
    'Composer\\Downloader\\ChangeReportInterface' => $vendorDir . '/composer/composer/src/Composer/Downloader/ChangeReportInterface.php',
    'Composer\\Downloader\\DownloadManager' => $vendorDir . '/composer/composer/src/Composer/Downloader/DownloadManager.php',
    'Composer\\Downloader\\DownloaderInterface' => $vendorDir . '/composer/composer/src/Composer/Downloader/DownloaderInterface.php',
    'Composer\\Downloader\\DvcsDownloaderInterface' => $vendorDir . '/composer/composer/src/Composer/Downloader/DvcsDownloaderInterface.php',
    'Composer\\Downloader\\FileDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/FileDownloader.php',
    'Composer\\Downloader\\FilesystemException' => $vendorDir . '/composer/composer/src/Composer/Downloader/FilesystemException.php',
    'Composer\\Downloader\\FossilDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/FossilDownloader.php',
    'Composer\\Downloader\\GitDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/GitDownloader.php',
    'Composer\\Downloader\\GzipDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/GzipDownloader.php',
    'Composer\\Downloader\\HgDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/HgDownloader.php',
    'Composer\\Downloader\\MaxFileSizeExceededException' => $vendorDir . '/composer/composer/src/Composer/Downloader/MaxFileSizeExceededException.php',
    'Composer\\Downloader\\PathDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/PathDownloader.php',
    'Composer\\Downloader\\PerforceDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/PerforceDownloader.php',
    'Composer\\Downloader\\PharDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/PharDownloader.php',
    'Composer\\Downloader\\RarDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/RarDownloader.php',
    'Composer\\Downloader\\SvnDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/SvnDownloader.php',
    'Composer\\Downloader\\TarDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/TarDownloader.php',
    'Composer\\Downloader\\TransportException' => $vendorDir . '/composer/composer/src/Composer/Downloader/TransportException.php',
    'Composer\\Downloader\\VcsCapableDownloaderInterface' => $vendorDir . '/composer/composer/src/Composer/Downloader/VcsCapableDownloaderInterface.php',
    'Composer\\Downloader\\VcsDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/VcsDownloader.php',
    'Composer\\Downloader\\XzDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/XzDownloader.php',
    'Composer\\Downloader\\ZipDownloader' => $vendorDir . '/composer/composer/src/Composer/Downloader/ZipDownloader.php',
    'Composer\\EventDispatcher\\Event' => $vendorDir . '/composer/composer/src/Composer/EventDispatcher/Event.php',
    'Composer\\EventDispatcher\\EventDispatcher' => $vendorDir . '/composer/composer/src/Composer/EventDispatcher/EventDispatcher.php',
    'Composer\\EventDispatcher\\EventSubscriberInterface' => $vendorDir . '/composer/composer/src/Composer/EventDispatcher/EventSubscriberInterface.php',
    'Composer\\EventDispatcher\\ScriptExecutionException' => $vendorDir . '/composer/composer/src/Composer/EventDispatcher/ScriptExecutionException.php',
    'Composer\\Exception\\IrrecoverableDownloadException' => $vendorDir . '/composer/composer/src/Composer/Exception/IrrecoverableDownloadException.php',
    'Composer\\Exception\\NoSslException' => $vendorDir . '/composer/composer/src/Composer/Exception/NoSslException.php',
    'Composer\\Factory' => $vendorDir . '/composer/composer/src/Composer/Factory.php',
    'Composer\\Filter\\PlatformRequirementFilter\\IgnoreAllPlatformRequirementFilter' => $vendorDir . '/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreAllPlatformRequirementFilter.php',
    'Composer\\Filter\\PlatformRequirementFilter\\IgnoreListPlatformRequirementFilter' => $vendorDir . '/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilter.php',
    'Composer\\Filter\\PlatformRequirementFilter\\IgnoreNothingPlatformRequirementFilter' => $vendorDir . '/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilter.php',
    'Composer\\Filter\\PlatformRequirementFilter\\PlatformRequirementFilterFactory' => $vendorDir . '/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterFactory.php',
    'Composer\\Filter\\PlatformRequirementFilter\\PlatformRequirementFilterInterface' => $vendorDir . '/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterInterface.php',
    'Composer\\IO\\BaseIO' => $vendorDir . '/composer/composer/src/Composer/IO/BaseIO.php',
    'Composer\\IO\\BufferIO' => $vendorDir . '/composer/composer/src/Composer/IO/BufferIO.php',
    'Composer\\IO\\ConsoleIO' => $vendorDir . '/composer/composer/src/Composer/IO/ConsoleIO.php',
    'Composer\\IO\\IOInterface' => $vendorDir . '/composer/composer/src/Composer/IO/IOInterface.php',
    'Composer\\IO\\NullIO' => $vendorDir . '/composer/composer/src/Composer/IO/NullIO.php',
    'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
    'Composer\\Installer' => $vendorDir . '/composer/composer/src/Composer/Installer.php',
    'Composer\\Installer\\BinaryInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/BinaryInstaller.php',
    'Composer\\Installer\\BinaryPresenceInterface' => $vendorDir . '/composer/composer/src/Composer/Installer/BinaryPresenceInterface.php',
    'Composer\\Installer\\InstallationManager' => $vendorDir . '/composer/composer/src/Composer/Installer/InstallationManager.php',
    'Composer\\Installer\\InstallerEvent' => $vendorDir . '/composer/composer/src/Composer/Installer/InstallerEvent.php',
    'Composer\\Installer\\InstallerEvents' => $vendorDir . '/composer/composer/src/Composer/Installer/InstallerEvents.php',
    'Composer\\Installer\\InstallerInterface' => $vendorDir . '/composer/composer/src/Composer/Installer/InstallerInterface.php',
    'Composer\\Installer\\LibraryInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/LibraryInstaller.php',
    'Composer\\Installer\\MetapackageInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/MetapackageInstaller.php',
    'Composer\\Installer\\NoopInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/NoopInstaller.php',
    'Composer\\Installer\\PackageEvent' => $vendorDir . '/composer/composer/src/Composer/Installer/PackageEvent.php',
    'Composer\\Installer\\PackageEvents' => $vendorDir . '/composer/composer/src/Composer/Installer/PackageEvents.php',
    'Composer\\Installer\\PluginInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/PluginInstaller.php',
    'Composer\\Installer\\ProjectInstaller' => $vendorDir . '/composer/composer/src/Composer/Installer/ProjectInstaller.php',
    'Composer\\Installer\\SuggestedPackagesReporter' => $vendorDir . '/composer/composer/src/Composer/Installer/SuggestedPackagesReporter.php',
    'Composer\\Json\\JsonFile' => $vendorDir . '/composer/composer/src/Composer/Json/JsonFile.php',
    'Composer\\Json\\JsonFormatter' => $vendorDir . '/composer/composer/src/Composer/Json/JsonFormatter.php',
    'Composer\\Json\\JsonManipulator' => $vendorDir . '/composer/composer/src/Composer/Json/JsonManipulator.php',
    'Composer\\Json\\JsonValidationException' => $vendorDir . '/composer/composer/src/Composer/Json/JsonValidationException.php',
    'Composer\\MetadataMinifier\\MetadataMinifier' => $vendorDir . '/composer/metadata-minifier/src/MetadataMinifier.php',
    'Composer\\PHPStan\\ConfigReturnTypeExtension' => $vendorDir . '/composer/composer/src/Composer/PHPStan/ConfigReturnTypeExtension.php',
    'Composer\\PHPStan\\RuleReasonDataReturnTypeExtension' => $vendorDir . '/composer/composer/src/Composer/PHPStan/RuleReasonDataReturnTypeExtension.php',
    'Composer\\Package\\AliasPackage' => $vendorDir . '/composer/composer/src/Composer/Package/AliasPackage.php',
    'Composer\\Package\\Archiver\\ArchivableFilesFilter' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFilter.php',
    'Composer\\Package\\Archiver\\ArchivableFilesFinder' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFinder.php',
    'Composer\\Package\\Archiver\\ArchiveManager' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/ArchiveManager.php',
    'Composer\\Package\\Archiver\\ArchiverInterface' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/ArchiverInterface.php',
    'Composer\\Package\\Archiver\\BaseExcludeFilter' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/BaseExcludeFilter.php',
    'Composer\\Package\\Archiver\\ComposerExcludeFilter' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/ComposerExcludeFilter.php',
    'Composer\\Package\\Archiver\\GitExcludeFilter' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/GitExcludeFilter.php',
    'Composer\\Package\\Archiver\\PharArchiver' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/PharArchiver.php',
    'Composer\\Package\\Archiver\\ZipArchiver' => $vendorDir . '/composer/composer/src/Composer/Package/Archiver/ZipArchiver.php',
    'Composer\\Package\\BasePackage' => $vendorDir . '/composer/composer/src/Composer/Package/BasePackage.php',
    'Composer\\Package\\Comparer\\Comparer' => $vendorDir . '/composer/composer/src/Composer/Package/Comparer/Comparer.php',
    'Composer\\Package\\CompleteAliasPackage' => $vendorDir . '/composer/composer/src/Composer/Package/CompleteAliasPackage.php',
    'Composer\\Package\\CompletePackage' => $vendorDir . '/composer/composer/src/Composer/Package/CompletePackage.php',
    'Composer\\Package\\CompletePackageInterface' => $vendorDir . '/composer/composer/src/Composer/Package/CompletePackageInterface.php',
    'Composer\\Package\\Dumper\\ArrayDumper' => $vendorDir . '/composer/composer/src/Composer/Package/Dumper/ArrayDumper.php',
    'Composer\\Package\\Link' => $vendorDir . '/composer/composer/src/Composer/Package/Link.php',
    'Composer\\Package\\Loader\\ArrayLoader' => $vendorDir . '/composer/composer/src/Composer/Package/Loader/ArrayLoader.php',
    'Composer\\Package\\Loader\\InvalidPackageException' => $vendorDir . '/composer/composer/src/Composer/Package/Loader/InvalidPackageException.php',
    'Composer\\Package\\Loader\\JsonLoader' => $vendorDir . '/composer/composer/src/Composer/Package/Loader/JsonLoader.php',
    'Composer\\Package\\Loader\\LoaderInterface' => $vendorDir . '/composer/composer/src/Composer/Package/Loader/LoaderInterface.php',
    'Composer\\Package\\Loader\\RootPackageLoader' => $vendorDir . '/composer/composer/src/Composer/Package/Loader/RootPackageLoader.php',
    'Composer\\Package\\Loader\\ValidatingArrayLoader' => $vendorDir . '/composer/composer/src/Composer/Package/Loader/ValidatingArrayLoader.php',
    'Composer\\Package\\Locker' => $vendorDir . '/composer/composer/src/Composer/Package/Locker.php',
    'Composer\\Package\\Package' => $vendorDir . '/composer/composer/src/Composer/Package/Package.php',
    'Composer\\Package\\PackageInterface' => $vendorDir . '/composer/composer/src/Composer/Package/PackageInterface.php',
    'Composer\\Package\\RootAliasPackage' => $vendorDir . '/composer/composer/src/Composer/Package/RootAliasPackage.php',
    'Composer\\Package\\RootPackage' => $vendorDir . '/composer/composer/src/Composer/Package/RootPackage.php',
    'Composer\\Package\\RootPackageInterface' => $vendorDir . '/composer/composer/src/Composer/Package/RootPackageInterface.php',
    'Composer\\Package\\Version\\StabilityFilter' => $vendorDir . '/composer/composer/src/Composer/Package/Version/StabilityFilter.php',
    'Composer\\Package\\Version\\VersionBumper' => $vendorDir . '/composer/composer/src/Composer/Package/Version/VersionBumper.php',
    'Composer\\Package\\Version\\VersionGuesser' => $vendorDir . '/composer/composer/src/Composer/Package/Version/VersionGuesser.php',
    'Composer\\Package\\Version\\VersionParser' => $vendorDir . '/composer/composer/src/Composer/Package/Version/VersionParser.php',
    'Composer\\Package\\Version\\VersionSelector' => $vendorDir . '/composer/composer/src/Composer/Package/Version/VersionSelector.php',
    'Composer\\PartialComposer' => $vendorDir . '/composer/composer/src/Composer/PartialComposer.php',
    'Composer\\Pcre\\MatchAllResult' => $vendorDir . '/composer/pcre/src/MatchAllResult.php',
    'Composer\\Pcre\\MatchAllStrictGroupsResult' => $vendorDir . '/composer/pcre/src/MatchAllStrictGroupsResult.php',
    'Composer\\Pcre\\MatchAllWithOffsetsResult' => $vendorDir . '/composer/pcre/src/MatchAllWithOffsetsResult.php',
    'Composer\\Pcre\\MatchResult' => $vendorDir . '/composer/pcre/src/MatchResult.php',
    'Composer\\Pcre\\MatchStrictGroupsResult' => $vendorDir . '/composer/pcre/src/MatchStrictGroupsResult.php',
    'Composer\\Pcre\\MatchWithOffsetsResult' => $vendorDir . '/composer/pcre/src/MatchWithOffsetsResult.php',
    'Composer\\Pcre\\PHPStan\\InvalidRegexPatternRule' => $vendorDir . '/composer/pcre/src/PHPStan/InvalidRegexPatternRule.php',
    'Composer\\Pcre\\PHPStan\\PregMatchFlags' => $vendorDir . '/composer/pcre/src/PHPStan/PregMatchFlags.php',
    'Composer\\Pcre\\PHPStan\\PregMatchParameterOutTypeExtension' => $vendorDir . '/composer/pcre/src/PHPStan/PregMatchParameterOutTypeExtension.php',
    'Composer\\Pcre\\PHPStan\\PregMatchTypeSpecifyingExtension' => $vendorDir . '/composer/pcre/src/PHPStan/PregMatchTypeSpecifyingExtension.php',
    'Composer\\Pcre\\PHPStan\\PregReplaceCallbackClosureTypeExtension' => $vendorDir . '/composer/pcre/src/PHPStan/PregReplaceCallbackClosureTypeExtension.php',
    'Composer\\Pcre\\PHPStan\\UnsafeStrictGroupsCallRule' => $vendorDir . '/composer/pcre/src/PHPStan/UnsafeStrictGroupsCallRule.php',
    'Composer\\Pcre\\PcreException' => $vendorDir . '/composer/pcre/src/PcreException.php',
    'Composer\\Pcre\\Preg' => $vendorDir . '/composer/pcre/src/Preg.php',
    'Composer\\Pcre\\Regex' => $vendorDir . '/composer/pcre/src/Regex.php',
    'Composer\\Pcre\\ReplaceResult' => $vendorDir . '/composer/pcre/src/ReplaceResult.php',
    'Composer\\Pcre\\UnexpectedNullMatchException' => $vendorDir . '/composer/pcre/src/UnexpectedNullMatchException.php',
    'Composer\\Platform\\HhvmDetector' => $vendorDir . '/composer/composer/src/Composer/Platform/HhvmDetector.php',
    'Composer\\Platform\\Runtime' => $vendorDir . '/composer/composer/src/Composer/Platform/Runtime.php',
    'Composer\\Platform\\Version' => $vendorDir . '/composer/composer/src/Composer/Platform/Version.php',
    'Composer\\Plugin\\Capability\\Capability' => $vendorDir . '/composer/composer/src/Composer/Plugin/Capability/Capability.php',
    'Composer\\Plugin\\Capability\\CommandProvider' => $vendorDir . '/composer/composer/src/Composer/Plugin/Capability/CommandProvider.php',
    'Composer\\Plugin\\Capable' => $vendorDir . '/composer/composer/src/Composer/Plugin/Capable.php',
    'Composer\\Plugin\\CommandEvent' => $vendorDir . '/composer/composer/src/Composer/Plugin/CommandEvent.php',
    'Composer\\Plugin\\PluginBlockedException' => $vendorDir . '/composer/composer/src/Composer/Plugin/PluginBlockedException.php',
    'Composer\\Plugin\\PluginEvents' => $vendorDir . '/composer/composer/src/Composer/Plugin/PluginEvents.php',
    'Composer\\Plugin\\PluginInterface' => $vendorDir . '/composer/composer/src/Composer/Plugin/PluginInterface.php',
    'Composer\\Plugin\\PluginManager' => $vendorDir . '/composer/composer/src/Composer/Plugin/PluginManager.php',
    'Composer\\Plugin\\PostFileDownloadEvent' => $vendorDir . '/composer/composer/src/Composer/Plugin/PostFileDownloadEvent.php',
    'Composer\\Plugin\\PreCommandRunEvent' => $vendorDir . '/composer/composer/src/Composer/Plugin/PreCommandRunEvent.php',
    'Composer\\Plugin\\PreFileDownloadEvent' => $vendorDir . '/composer/composer/src/Composer/Plugin/PreFileDownloadEvent.php',
    'Composer\\Plugin\\PrePoolCreateEvent' => $vendorDir . '/composer/composer/src/Composer/Plugin/PrePoolCreateEvent.php',
    'Composer\\Question\\StrictConfirmationQuestion' => $vendorDir . '/composer/composer/src/Composer/Question/StrictConfirmationQuestion.php',
    'Composer\\Repository\\AdvisoryProviderInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/AdvisoryProviderInterface.php',
    'Composer\\Repository\\ArrayRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/ArrayRepository.php',
    'Composer\\Repository\\ArtifactRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/ArtifactRepository.php',
    'Composer\\Repository\\CanonicalPackagesTrait' => $vendorDir . '/composer/composer/src/Composer/Repository/CanonicalPackagesTrait.php',
    'Composer\\Repository\\ComposerRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/ComposerRepository.php',
    'Composer\\Repository\\CompositeRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/CompositeRepository.php',
    'Composer\\Repository\\ConfigurableRepositoryInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/ConfigurableRepositoryInterface.php',
    'Composer\\Repository\\FilesystemRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/FilesystemRepository.php',
    'Composer\\Repository\\FilterRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/FilterRepository.php',
    'Composer\\Repository\\InstalledArrayRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/InstalledArrayRepository.php',
    'Composer\\Repository\\InstalledFilesystemRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/InstalledFilesystemRepository.php',
    'Composer\\Repository\\InstalledRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/InstalledRepository.php',
    'Composer\\Repository\\InstalledRepositoryInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/InstalledRepositoryInterface.php',
    'Composer\\Repository\\InvalidRepositoryException' => $vendorDir . '/composer/composer/src/Composer/Repository/InvalidRepositoryException.php',
    'Composer\\Repository\\LockArrayRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/LockArrayRepository.php',
    'Composer\\Repository\\PackageRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/PackageRepository.php',
    'Composer\\Repository\\PathRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/PathRepository.php',
    'Composer\\Repository\\PearRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/PearRepository.php',
    'Composer\\Repository\\PlatformRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/PlatformRepository.php',
    'Composer\\Repository\\RepositoryFactory' => $vendorDir . '/composer/composer/src/Composer/Repository/RepositoryFactory.php',
    'Composer\\Repository\\RepositoryInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/RepositoryInterface.php',
    'Composer\\Repository\\RepositoryManager' => $vendorDir . '/composer/composer/src/Composer/Repository/RepositoryManager.php',
    'Composer\\Repository\\RepositorySecurityException' => $vendorDir . '/composer/composer/src/Composer/Repository/RepositorySecurityException.php',
    'Composer\\Repository\\RepositorySet' => $vendorDir . '/composer/composer/src/Composer/Repository/RepositorySet.php',
    'Composer\\Repository\\RepositoryUtils' => $vendorDir . '/composer/composer/src/Composer/Repository/RepositoryUtils.php',
    'Composer\\Repository\\RootPackageRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/RootPackageRepository.php',
    'Composer\\Repository\\VcsRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/VcsRepository.php',
    'Composer\\Repository\\Vcs\\FossilDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/FossilDriver.php',
    'Composer\\Repository\\Vcs\\GitBitbucketDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/GitBitbucketDriver.php',
    'Composer\\Repository\\Vcs\\GitDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/GitDriver.php',
    'Composer\\Repository\\Vcs\\GitHubDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/GitHubDriver.php',
    'Composer\\Repository\\Vcs\\GitLabDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/GitLabDriver.php',
    'Composer\\Repository\\Vcs\\HgDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/HgDriver.php',
    'Composer\\Repository\\Vcs\\PerforceDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/PerforceDriver.php',
    'Composer\\Repository\\Vcs\\SvnDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/SvnDriver.php',
    'Composer\\Repository\\Vcs\\VcsDriver' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/VcsDriver.php',
    'Composer\\Repository\\Vcs\\VcsDriverInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/Vcs/VcsDriverInterface.php',
    'Composer\\Repository\\VersionCacheInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/VersionCacheInterface.php',
    'Composer\\Repository\\WritableArrayRepository' => $vendorDir . '/composer/composer/src/Composer/Repository/WritableArrayRepository.php',
    'Composer\\Repository\\WritableRepositoryInterface' => $vendorDir . '/composer/composer/src/Composer/Repository/WritableRepositoryInterface.php',
    'Composer\\Script\\Event' => $vendorDir . '/composer/composer/src/Composer/Script/Event.php',
    'Composer\\Script\\ScriptEvents' => $vendorDir . '/composer/composer/src/Composer/Script/ScriptEvents.php',
    'Composer\\SelfUpdate\\Keys' => $vendorDir . '/composer/composer/src/Composer/SelfUpdate/Keys.php',
    'Composer\\SelfUpdate\\Versions' => $vendorDir . '/composer/composer/src/Composer/SelfUpdate/Versions.php',
    'Composer\\Semver\\Comparator' => $vendorDir . '/composer/semver/src/Comparator.php',
    'Composer\\Semver\\CompilingMatcher' => $vendorDir . '/composer/semver/src/CompilingMatcher.php',
    'Composer\\Semver\\Constraint\\Bound' => $vendorDir . '/composer/semver/src/Constraint/Bound.php',
    'Composer\\Semver\\Constraint\\Constraint' => $vendorDir . '/composer/semver/src/Constraint/Constraint.php',
    'Composer\\Semver\\Constraint\\ConstraintInterface' => $vendorDir . '/composer/semver/src/Constraint/ConstraintInterface.php',
    'Composer\\Semver\\Constraint\\MatchAllConstraint' => $vendorDir . '/composer/semver/src/Constraint/MatchAllConstraint.php',
    'Composer\\Semver\\Constraint\\MatchNoneConstraint' => $vendorDir . '/composer/semver/src/Constraint/MatchNoneConstraint.php',
    'Composer\\Semver\\Constraint\\MultiConstraint' => $vendorDir . '/composer/semver/src/Constraint/MultiConstraint.php',
    'Composer\\Semver\\Interval' => $vendorDir . '/composer/semver/src/Interval.php',
    'Composer\\Semver\\Intervals' => $vendorDir . '/composer/semver/src/Intervals.php',
    'Composer\\Semver\\Semver' => $vendorDir . '/composer/semver/src/Semver.php',
    'Composer\\Semver\\VersionParser' => $vendorDir . '/composer/semver/src/VersionParser.php',
    'Composer\\Spdx\\SpdxLicenses' => $vendorDir . '/composer/spdx-licenses/src/SpdxLicenses.php',
    'Composer\\Util\\AuthHelper' => $vendorDir . '/composer/composer/src/Composer/Util/AuthHelper.php',
    'Composer\\Util\\Bitbucket' => $vendorDir . '/composer/composer/src/Composer/Util/Bitbucket.php',
    'Composer\\Util\\ComposerMirror' => $vendorDir . '/composer/composer/src/Composer/Util/ComposerMirror.php',
    'Composer\\Util\\ConfigValidator' => $vendorDir . '/composer/composer/src/Composer/Util/ConfigValidator.php',
    'Composer\\Util\\ErrorHandler' => $vendorDir . '/composer/composer/src/Composer/Util/ErrorHandler.php',
    'Composer\\Util\\Filesystem' => $vendorDir . '/composer/composer/src/Composer/Util/Filesystem.php',
    'Composer\\Util\\Git' => $vendorDir . '/composer/composer/src/Composer/Util/Git.php',
    'Composer\\Util\\GitHub' => $vendorDir . '/composer/composer/src/Composer/Util/GitHub.php',
    'Composer\\Util\\GitLab' => $vendorDir . '/composer/composer/src/Composer/Util/GitLab.php',
    'Composer\\Util\\Hg' => $vendorDir . '/composer/composer/src/Composer/Util/Hg.php',
    'Composer\\Util\\HttpDownloader' => $vendorDir . '/composer/composer/src/Composer/Util/HttpDownloader.php',
    'Composer\\Util\\Http\\CurlDownloader' => $vendorDir . '/composer/composer/src/Composer/Util/Http/CurlDownloader.php',
    'Composer\\Util\\Http\\CurlResponse' => $vendorDir . '/composer/composer/src/Composer/Util/Http/CurlResponse.php',
    'Composer\\Util\\Http\\ProxyItem' => $vendorDir . '/composer/composer/src/Composer/Util/Http/ProxyItem.php',
    'Composer\\Util\\Http\\ProxyManager' => $vendorDir . '/composer/composer/src/Composer/Util/Http/ProxyManager.php',
    'Composer\\Util\\Http\\RequestProxy' => $vendorDir . '/composer/composer/src/Composer/Util/Http/RequestProxy.php',
    'Composer\\Util\\Http\\Response' => $vendorDir . '/composer/composer/src/Composer/Util/Http/Response.php',
    'Composer\\Util\\IniHelper' => $vendorDir . '/composer/composer/src/Composer/Util/IniHelper.php',
    'Composer\\Util\\Loop' => $vendorDir . '/composer/composer/src/Composer/Util/Loop.php',
    'Composer\\Util\\MetadataMinifier' => $vendorDir . '/composer/composer/src/Composer/Util/MetadataMinifier.php',
    'Composer\\Util\\NoProxyPattern' => $vendorDir . '/composer/composer/src/Composer/Util/NoProxyPattern.php',
    'Composer\\Util\\PackageInfo' => $vendorDir . '/composer/composer/src/Composer/Util/PackageInfo.php',
    'Composer\\Util\\PackageSorter' => $vendorDir . '/composer/composer/src/Composer/Util/PackageSorter.php',
    'Composer\\Util\\Perforce' => $vendorDir . '/composer/composer/src/Composer/Util/Perforce.php',
    'Composer\\Util\\Platform' => $vendorDir . '/composer/composer/src/Composer/Util/Platform.php',
    'Composer\\Util\\ProcessExecutor' => $vendorDir . '/composer/composer/src/Composer/Util/ProcessExecutor.php',
    'Composer\\Util\\RemoteFilesystem' => $vendorDir . '/composer/composer/src/Composer/Util/RemoteFilesystem.php',
    'Composer\\Util\\Silencer' => $vendorDir . '/composer/composer/src/Composer/Util/Silencer.php',
    'Composer\\Util\\StreamContextFactory' => $vendorDir . '/composer/composer/src/Composer/Util/StreamContextFactory.php',
    'Composer\\Util\\Svn' => $vendorDir . '/composer/composer/src/Composer/Util/Svn.php',
    'Composer\\Util\\SyncHelper' => $vendorDir . '/composer/composer/src/Composer/Util/SyncHelper.php',
    'Composer\\Util\\Tar' => $vendorDir . '/composer/composer/src/Composer/Util/Tar.php',
    'Composer\\Util\\TlsHelper' => $vendorDir . '/composer/composer/src/Composer/Util/TlsHelper.php',
    'Composer\\Util\\Url' => $vendorDir . '/composer/composer/src/Composer/Util/Url.php',
    'Composer\\Util\\Zip' => $vendorDir . '/composer/composer/src/Composer/Util/Zip.php',
    'Composer\\XdebugHandler\\PhpConfig' => $vendorDir . '/composer/xdebug-handler/src/PhpConfig.php',
    'Composer\\XdebugHandler\\Process' => $vendorDir . '/composer/xdebug-handler/src/Process.php',
    'Composer\\XdebugHandler\\Status' => $vendorDir . '/composer/xdebug-handler/src/Status.php',
    'Composer\\XdebugHandler\\XdebugHandler' => $vendorDir . '/composer/xdebug-handler/src/XdebugHandler.php',
    'Fidry\\CpuCoreCounter\\CpuCoreCounter' => $vendorDir . '/fidry/cpu-core-counter/src/CpuCoreCounter.php',
    'Fidry\\CpuCoreCounter\\Diagnoser' => $vendorDir . '/fidry/cpu-core-counter/src/Diagnoser.php',
    'Fidry\\CpuCoreCounter\\Executor\\ProcOpenExecutor' => $vendorDir . '/fidry/cpu-core-counter/src/Executor/ProcOpenExecutor.php',
    'Fidry\\CpuCoreCounter\\Executor\\ProcessExecutor' => $vendorDir . '/fidry/cpu-core-counter/src/Executor/ProcessExecutor.php',
    'Fidry\\CpuCoreCounter\\Finder\\CmiCmdletLogicalFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/CmiCmdletLogicalFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\CmiCmdletPhysicalFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/CmiCmdletPhysicalFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\CpuCoreFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/CpuCoreFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\CpuInfoFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/CpuInfoFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\DummyCpuCoreFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/DummyCpuCoreFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\EnvVariableFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/EnvVariableFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\FinderRegistry' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/FinderRegistry.php',
    'Fidry\\CpuCoreCounter\\Finder\\HwLogicalFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/HwLogicalFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\HwPhysicalFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/HwPhysicalFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\LscpuLogicalFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/LscpuLogicalFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\LscpuPhysicalFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/LscpuPhysicalFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\NProcFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/NProcFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\NProcessorFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/NProcessorFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\NullCpuCoreFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/NullCpuCoreFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\OnlyInPowerShellFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/OnlyInPowerShellFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\OnlyOnOSFamilyFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/OnlyOnOSFamilyFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\ProcOpenBasedFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/ProcOpenBasedFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\SkipOnOSFamilyFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/SkipOnOSFamilyFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\WindowsRegistryLogicalFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/WindowsRegistryLogicalFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\WmicLogicalFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/WmicLogicalFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\WmicPhysicalFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/WmicPhysicalFinder.php',
    'Fidry\\CpuCoreCounter\\Finder\\_NProcessorFinder' => $vendorDir . '/fidry/cpu-core-counter/src/Finder/_NProcessorFinder.php',
    'Fidry\\CpuCoreCounter\\NumberOfCpuCoreNotFound' => $vendorDir . '/fidry/cpu-core-counter/src/NumberOfCpuCoreNotFound.php',
    'Fidry\\CpuCoreCounter\\ParallelisationResult' => $vendorDir . '/fidry/cpu-core-counter/src/ParallelisationResult.php',
    'Illuminate\\Container\\BoundMethod' => $vendorDir . '/illuminate/container/BoundMethod.php',
    'Illuminate\\Container\\Container' => $vendorDir . '/illuminate/container/Container.php',
    'Illuminate\\Container\\ContextualBindingBuilder' => $vendorDir . '/illuminate/container/ContextualBindingBuilder.php',
    'Illuminate\\Container\\EntryNotFoundException' => $vendorDir . '/illuminate/container/EntryNotFoundException.php',
    'Illuminate\\Container\\RewindableGenerator' => $vendorDir . '/illuminate/container/RewindableGenerator.php',
    'Illuminate\\Container\\Util' => $vendorDir . '/illuminate/container/Util.php',
    'Illuminate\\Contracts\\Auth\\Access\\Authorizable' => $vendorDir . '/illuminate/contracts/Auth/Access/Authorizable.php',
    'Illuminate\\Contracts\\Auth\\Access\\Gate' => $vendorDir . '/illuminate/contracts/Auth/Access/Gate.php',
    'Illuminate\\Contracts\\Auth\\Authenticatable' => $vendorDir . '/illuminate/contracts/Auth/Authenticatable.php',
    'Illuminate\\Contracts\\Auth\\CanResetPassword' => $vendorDir . '/illuminate/contracts/Auth/CanResetPassword.php',
    'Illuminate\\Contracts\\Auth\\Factory' => $vendorDir . '/illuminate/contracts/Auth/Factory.php',
    'Illuminate\\Contracts\\Auth\\Guard' => $vendorDir . '/illuminate/contracts/Auth/Guard.php',
    'Illuminate\\Contracts\\Auth\\Middleware\\AuthenticatesRequests' => $vendorDir . '/illuminate/contracts/Auth/Middleware/AuthenticatesRequests.php',
    'Illuminate\\Contracts\\Auth\\MustVerifyEmail' => $vendorDir . '/illuminate/contracts/Auth/MustVerifyEmail.php',
    'Illuminate\\Contracts\\Auth\\PasswordBroker' => $vendorDir . '/illuminate/contracts/Auth/PasswordBroker.php',
    'Illuminate\\Contracts\\Auth\\PasswordBrokerFactory' => $vendorDir . '/illuminate/contracts/Auth/PasswordBrokerFactory.php',
    'Illuminate\\Contracts\\Auth\\StatefulGuard' => $vendorDir . '/illuminate/contracts/Auth/StatefulGuard.php',
    'Illuminate\\Contracts\\Auth\\SupportsBasicAuth' => $vendorDir . '/illuminate/contracts/Auth/SupportsBasicAuth.php',
    'Illuminate\\Contracts\\Auth\\UserProvider' => $vendorDir . '/illuminate/contracts/Auth/UserProvider.php',
    'Illuminate\\Contracts\\Broadcasting\\Broadcaster' => $vendorDir . '/illuminate/contracts/Broadcasting/Broadcaster.php',
    'Illuminate\\Contracts\\Broadcasting\\Factory' => $vendorDir . '/illuminate/contracts/Broadcasting/Factory.php',
    'Illuminate\\Contracts\\Broadcasting\\HasBroadcastChannel' => $vendorDir . '/illuminate/contracts/Broadcasting/HasBroadcastChannel.php',
    'Illuminate\\Contracts\\Broadcasting\\ShouldBeUnique' => $vendorDir . '/illuminate/contracts/Broadcasting/ShouldBeUnique.php',
    'Illuminate\\Contracts\\Broadcasting\\ShouldBroadcast' => $vendorDir . '/illuminate/contracts/Broadcasting/ShouldBroadcast.php',
    'Illuminate\\Contracts\\Broadcasting\\ShouldBroadcastNow' => $vendorDir . '/illuminate/contracts/Broadcasting/ShouldBroadcastNow.php',
    'Illuminate\\Contracts\\Bus\\Dispatcher' => $vendorDir . '/illuminate/contracts/Bus/Dispatcher.php',
    'Illuminate\\Contracts\\Bus\\QueueingDispatcher' => $vendorDir . '/illuminate/contracts/Bus/QueueingDispatcher.php',
    'Illuminate\\Contracts\\Cache\\Factory' => $vendorDir . '/illuminate/contracts/Cache/Factory.php',
    'Illuminate\\Contracts\\Cache\\Lock' => $vendorDir . '/illuminate/contracts/Cache/Lock.php',
    'Illuminate\\Contracts\\Cache\\LockProvider' => $vendorDir . '/illuminate/contracts/Cache/LockProvider.php',
    'Illuminate\\Contracts\\Cache\\LockTimeoutException' => $vendorDir . '/illuminate/contracts/Cache/LockTimeoutException.php',
    'Illuminate\\Contracts\\Cache\\Repository' => $vendorDir . '/illuminate/contracts/Cache/Repository.php',
    'Illuminate\\Contracts\\Cache\\Store' => $vendorDir . '/illuminate/contracts/Cache/Store.php',
    'Illuminate\\Contracts\\Config\\Repository' => $vendorDir . '/illuminate/contracts/Config/Repository.php',
    'Illuminate\\Contracts\\Console\\Application' => $vendorDir . '/illuminate/contracts/Console/Application.php',
    'Illuminate\\Contracts\\Console\\Isolatable' => $vendorDir . '/illuminate/contracts/Console/Isolatable.php',
    'Illuminate\\Contracts\\Console\\Kernel' => $vendorDir . '/illuminate/contracts/Console/Kernel.php',
    'Illuminate\\Contracts\\Console\\PromptsForMissingInput' => $vendorDir . '/illuminate/contracts/Console/PromptsForMissingInput.php',
    'Illuminate\\Contracts\\Container\\BindingResolutionException' => $vendorDir . '/illuminate/contracts/Container/BindingResolutionException.php',
    'Illuminate\\Contracts\\Container\\CircularDependencyException' => $vendorDir . '/illuminate/contracts/Container/CircularDependencyException.php',
    'Illuminate\\Contracts\\Container\\Container' => $vendorDir . '/illuminate/contracts/Container/Container.php',
    'Illuminate\\Contracts\\Container\\ContextualBindingBuilder' => $vendorDir . '/illuminate/contracts/Container/ContextualBindingBuilder.php',
    'Illuminate\\Contracts\\Cookie\\Factory' => $vendorDir . '/illuminate/contracts/Cookie/Factory.php',
    'Illuminate\\Contracts\\Cookie\\QueueingFactory' => $vendorDir . '/illuminate/contracts/Cookie/QueueingFactory.php',
    'Illuminate\\Contracts\\Database\\Eloquent\\Builder' => $vendorDir . '/illuminate/contracts/Database/Eloquent/Builder.php',
    'Illuminate\\Contracts\\Database\\Eloquent\\Castable' => $vendorDir . '/illuminate/contracts/Database/Eloquent/Castable.php',
    'Illuminate\\Contracts\\Database\\Eloquent\\CastsAttributes' => $vendorDir . '/illuminate/contracts/Database/Eloquent/CastsAttributes.php',
    'Illuminate\\Contracts\\Database\\Eloquent\\CastsInboundAttributes' => $vendorDir . '/illuminate/contracts/Database/Eloquent/CastsInboundAttributes.php',
    'Illuminate\\Contracts\\Database\\Eloquent\\DeviatesCastableAttributes' => $vendorDir . '/illuminate/contracts/Database/Eloquent/DeviatesCastableAttributes.php',
    'Illuminate\\Contracts\\Database\\Eloquent\\SerializesCastableAttributes' => $vendorDir . '/illuminate/contracts/Database/Eloquent/SerializesCastableAttributes.php',
    'Illuminate\\Contracts\\Database\\Eloquent\\SupportsPartialRelations' => $vendorDir . '/illuminate/contracts/Database/Eloquent/SupportsPartialRelations.php',
    'Illuminate\\Contracts\\Database\\Events\\MigrationEvent' => $vendorDir . '/illuminate/contracts/Database/Events/MigrationEvent.php',
    'Illuminate\\Contracts\\Database\\ModelIdentifier' => $vendorDir . '/illuminate/contracts/Database/ModelIdentifier.php',
    'Illuminate\\Contracts\\Database\\Query\\Builder' => $vendorDir . '/illuminate/contracts/Database/Query/Builder.php',
    'Illuminate\\Contracts\\Database\\Query\\ConditionExpression' => $vendorDir . '/illuminate/contracts/Database/Query/ConditionExpression.php',
    'Illuminate\\Contracts\\Database\\Query\\Expression' => $vendorDir . '/illuminate/contracts/Database/Query/Expression.php',
    'Illuminate\\Contracts\\Debug\\ExceptionHandler' => $vendorDir . '/illuminate/contracts/Debug/ExceptionHandler.php',
    'Illuminate\\Contracts\\Encryption\\DecryptException' => $vendorDir . '/illuminate/contracts/Encryption/DecryptException.php',
    'Illuminate\\Contracts\\Encryption\\EncryptException' => $vendorDir . '/illuminate/contracts/Encryption/EncryptException.php',
    'Illuminate\\Contracts\\Encryption\\Encrypter' => $vendorDir . '/illuminate/contracts/Encryption/Encrypter.php',
    'Illuminate\\Contracts\\Encryption\\StringEncrypter' => $vendorDir . '/illuminate/contracts/Encryption/StringEncrypter.php',
    'Illuminate\\Contracts\\Events\\Dispatcher' => $vendorDir . '/illuminate/contracts/Events/Dispatcher.php',
    'Illuminate\\Contracts\\Events\\ShouldDispatchAfterCommit' => $vendorDir . '/illuminate/contracts/Events/ShouldDispatchAfterCommit.php',
    'Illuminate\\Contracts\\Events\\ShouldHandleEventsAfterCommit' => $vendorDir . '/illuminate/contracts/Events/ShouldHandleEventsAfterCommit.php',
    'Illuminate\\Contracts\\Filesystem\\Cloud' => $vendorDir . '/illuminate/contracts/Filesystem/Cloud.php',
    'Illuminate\\Contracts\\Filesystem\\Factory' => $vendorDir . '/illuminate/contracts/Filesystem/Factory.php',
    'Illuminate\\Contracts\\Filesystem\\FileNotFoundException' => $vendorDir . '/illuminate/contracts/Filesystem/FileNotFoundException.php',
    'Illuminate\\Contracts\\Filesystem\\Filesystem' => $vendorDir . '/illuminate/contracts/Filesystem/Filesystem.php',
    'Illuminate\\Contracts\\Filesystem\\LockTimeoutException' => $vendorDir . '/illuminate/contracts/Filesystem/LockTimeoutException.php',
    'Illuminate\\Contracts\\Foundation\\Application' => $vendorDir . '/illuminate/contracts/Foundation/Application.php',
    'Illuminate\\Contracts\\Foundation\\CachesConfiguration' => $vendorDir . '/illuminate/contracts/Foundation/CachesConfiguration.php',
    'Illuminate\\Contracts\\Foundation\\CachesRoutes' => $vendorDir . '/illuminate/contracts/Foundation/CachesRoutes.php',
    'Illuminate\\Contracts\\Foundation\\ExceptionRenderer' => $vendorDir . '/illuminate/contracts/Foundation/ExceptionRenderer.php',
    'Illuminate\\Contracts\\Foundation\\MaintenanceMode' => $vendorDir . '/illuminate/contracts/Foundation/MaintenanceMode.php',
    'Illuminate\\Contracts\\Hashing\\Hasher' => $vendorDir . '/illuminate/contracts/Hashing/Hasher.php',
    'Illuminate\\Contracts\\Http\\Kernel' => $vendorDir . '/illuminate/contracts/Http/Kernel.php',
    'Illuminate\\Contracts\\Mail\\Attachable' => $vendorDir . '/illuminate/contracts/Mail/Attachable.php',
    'Illuminate\\Contracts\\Mail\\Factory' => $vendorDir . '/illuminate/contracts/Mail/Factory.php',
    'Illuminate\\Contracts\\Mail\\MailQueue' => $vendorDir . '/illuminate/contracts/Mail/MailQueue.php',
    'Illuminate\\Contracts\\Mail\\Mailable' => $vendorDir . '/illuminate/contracts/Mail/Mailable.php',
    'Illuminate\\Contracts\\Mail\\Mailer' => $vendorDir . '/illuminate/contracts/Mail/Mailer.php',
    'Illuminate\\Contracts\\Notifications\\Dispatcher' => $vendorDir . '/illuminate/contracts/Notifications/Dispatcher.php',
    'Illuminate\\Contracts\\Notifications\\Factory' => $vendorDir . '/illuminate/contracts/Notifications/Factory.php',
    'Illuminate\\Contracts\\Pagination\\CursorPaginator' => $vendorDir . '/illuminate/contracts/Pagination/CursorPaginator.php',
    'Illuminate\\Contracts\\Pagination\\LengthAwarePaginator' => $vendorDir . '/illuminate/contracts/Pagination/LengthAwarePaginator.php',
    'Illuminate\\Contracts\\Pagination\\Paginator' => $vendorDir . '/illuminate/contracts/Pagination/Paginator.php',
    'Illuminate\\Contracts\\Pipeline\\Hub' => $vendorDir . '/illuminate/contracts/Pipeline/Hub.php',
    'Illuminate\\Contracts\\Pipeline\\Pipeline' => $vendorDir . '/illuminate/contracts/Pipeline/Pipeline.php',
    'Illuminate\\Contracts\\Process\\InvokedProcess' => $vendorDir . '/illuminate/contracts/Process/InvokedProcess.php',
    'Illuminate\\Contracts\\Process\\ProcessResult' => $vendorDir . '/illuminate/contracts/Process/ProcessResult.php',
    'Illuminate\\Contracts\\Queue\\ClearableQueue' => $vendorDir . '/illuminate/contracts/Queue/ClearableQueue.php',
    'Illuminate\\Contracts\\Queue\\EntityNotFoundException' => $vendorDir . '/illuminate/contracts/Queue/EntityNotFoundException.php',
    'Illuminate\\Contracts\\Queue\\EntityResolver' => $vendorDir . '/illuminate/contracts/Queue/EntityResolver.php',
    'Illuminate\\Contracts\\Queue\\Factory' => $vendorDir . '/illuminate/contracts/Queue/Factory.php',
    'Illuminate\\Contracts\\Queue\\Job' => $vendorDir . '/illuminate/contracts/Queue/Job.php',
    'Illuminate\\Contracts\\Queue\\Monitor' => $vendorDir . '/illuminate/contracts/Queue/Monitor.php',
    'Illuminate\\Contracts\\Queue\\Queue' => $vendorDir . '/illuminate/contracts/Queue/Queue.php',
    'Illuminate\\Contracts\\Queue\\QueueableCollection' => $vendorDir . '/illuminate/contracts/Queue/QueueableCollection.php',
    'Illuminate\\Contracts\\Queue\\QueueableEntity' => $vendorDir . '/illuminate/contracts/Queue/QueueableEntity.php',
    'Illuminate\\Contracts\\Queue\\ShouldBeEncrypted' => $vendorDir . '/illuminate/contracts/Queue/ShouldBeEncrypted.php',
    'Illuminate\\Contracts\\Queue\\ShouldBeUnique' => $vendorDir . '/illuminate/contracts/Queue/ShouldBeUnique.php',
    'Illuminate\\Contracts\\Queue\\ShouldBeUniqueUntilProcessing' => $vendorDir . '/illuminate/contracts/Queue/ShouldBeUniqueUntilProcessing.php',
    'Illuminate\\Contracts\\Queue\\ShouldQueue' => $vendorDir . '/illuminate/contracts/Queue/ShouldQueue.php',
    'Illuminate\\Contracts\\Queue\\ShouldQueueAfterCommit' => $vendorDir . '/illuminate/contracts/Queue/ShouldQueueAfterCommit.php',
    'Illuminate\\Contracts\\Redis\\Connection' => $vendorDir . '/illuminate/contracts/Redis/Connection.php',
    'Illuminate\\Contracts\\Redis\\Connector' => $vendorDir . '/illuminate/contracts/Redis/Connector.php',
    'Illuminate\\Contracts\\Redis\\Factory' => $vendorDir . '/illuminate/contracts/Redis/Factory.php',
    'Illuminate\\Contracts\\Redis\\LimiterTimeoutException' => $vendorDir . '/illuminate/contracts/Redis/LimiterTimeoutException.php',
    'Illuminate\\Contracts\\Routing\\BindingRegistrar' => $vendorDir . '/illuminate/contracts/Routing/BindingRegistrar.php',
    'Illuminate\\Contracts\\Routing\\Registrar' => $vendorDir . '/illuminate/contracts/Routing/Registrar.php',
    'Illuminate\\Contracts\\Routing\\ResponseFactory' => $vendorDir . '/illuminate/contracts/Routing/ResponseFactory.php',
    'Illuminate\\Contracts\\Routing\\UrlGenerator' => $vendorDir . '/illuminate/contracts/Routing/UrlGenerator.php',
    'Illuminate\\Contracts\\Routing\\UrlRoutable' => $vendorDir . '/illuminate/contracts/Routing/UrlRoutable.php',
    'Illuminate\\Contracts\\Session\\Middleware\\AuthenticatesSessions' => $vendorDir . '/illuminate/contracts/Session/Middleware/AuthenticatesSessions.php',
    'Illuminate\\Contracts\\Session\\Session' => $vendorDir . '/illuminate/contracts/Session/Session.php',
    'Illuminate\\Contracts\\Support\\Arrayable' => $vendorDir . '/illuminate/contracts/Support/Arrayable.php',
    'Illuminate\\Contracts\\Support\\CanBeEscapedWhenCastToString' => $vendorDir . '/illuminate/contracts/Support/CanBeEscapedWhenCastToString.php',
    'Illuminate\\Contracts\\Support\\DeferrableProvider' => $vendorDir . '/illuminate/contracts/Support/DeferrableProvider.php',
    'Illuminate\\Contracts\\Support\\DeferringDisplayableValue' => $vendorDir . '/illuminate/contracts/Support/DeferringDisplayableValue.php',
    'Illuminate\\Contracts\\Support\\Htmlable' => $vendorDir . '/illuminate/contracts/Support/Htmlable.php',
    'Illuminate\\Contracts\\Support\\Jsonable' => $vendorDir . '/illuminate/contracts/Support/Jsonable.php',
    'Illuminate\\Contracts\\Support\\MessageBag' => $vendorDir . '/illuminate/contracts/Support/MessageBag.php',
    'Illuminate\\Contracts\\Support\\MessageProvider' => $vendorDir . '/illuminate/contracts/Support/MessageProvider.php',
    'Illuminate\\Contracts\\Support\\Renderable' => $vendorDir . '/illuminate/contracts/Support/Renderable.php',
    'Illuminate\\Contracts\\Support\\Responsable' => $vendorDir . '/illuminate/contracts/Support/Responsable.php',
    'Illuminate\\Contracts\\Support\\ValidatedData' => $vendorDir . '/illuminate/contracts/Support/ValidatedData.php',
    'Illuminate\\Contracts\\Translation\\HasLocalePreference' => $vendorDir . '/illuminate/contracts/Translation/HasLocalePreference.php',
    'Illuminate\\Contracts\\Translation\\Loader' => $vendorDir . '/illuminate/contracts/Translation/Loader.php',
    'Illuminate\\Contracts\\Translation\\Translator' => $vendorDir . '/illuminate/contracts/Translation/Translator.php',
    'Illuminate\\Contracts\\Validation\\DataAwareRule' => $vendorDir . '/illuminate/contracts/Validation/DataAwareRule.php',
    'Illuminate\\Contracts\\Validation\\Factory' => $vendorDir . '/illuminate/contracts/Validation/Factory.php',
    'Illuminate\\Contracts\\Validation\\ImplicitRule' => $vendorDir . '/illuminate/contracts/Validation/ImplicitRule.php',
    'Illuminate\\Contracts\\Validation\\InvokableRule' => $vendorDir . '/illuminate/contracts/Validation/InvokableRule.php',
    'Illuminate\\Contracts\\Validation\\Rule' => $vendorDir . '/illuminate/contracts/Validation/Rule.php',
    'Illuminate\\Contracts\\Validation\\UncompromisedVerifier' => $vendorDir . '/illuminate/contracts/Validation/UncompromisedVerifier.php',
    'Illuminate\\Contracts\\Validation\\ValidatesWhenResolved' => $vendorDir . '/illuminate/contracts/Validation/ValidatesWhenResolved.php',
    'Illuminate\\Contracts\\Validation\\ValidationRule' => $vendorDir . '/illuminate/contracts/Validation/ValidationRule.php',
    'Illuminate\\Contracts\\Validation\\Validator' => $vendorDir . '/illuminate/contracts/Validation/Validator.php',
    'Illuminate\\Contracts\\Validation\\ValidatorAwareRule' => $vendorDir . '/illuminate/contracts/Validation/ValidatorAwareRule.php',
    'Illuminate\\Contracts\\View\\Engine' => $vendorDir . '/illuminate/contracts/View/Engine.php',
    'Illuminate\\Contracts\\View\\Factory' => $vendorDir . '/illuminate/contracts/View/Factory.php',
    'Illuminate\\Contracts\\View\\View' => $vendorDir . '/illuminate/contracts/View/View.php',
    'Illuminate\\Contracts\\View\\ViewCompilationException' => $vendorDir . '/illuminate/contracts/View/ViewCompilationException.php',
    'JsonSchema\\ConstraintError' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/ConstraintError.php',
    'JsonSchema\\Constraints\\BaseConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/BaseConstraint.php',
    'JsonSchema\\Constraints\\CollectionConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/CollectionConstraint.php',
    'JsonSchema\\Constraints\\ConstConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstConstraint.php',
    'JsonSchema\\Constraints\\Constraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Constraint.php',
    'JsonSchema\\Constraints\\ConstraintInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstraintInterface.php',
    'JsonSchema\\Constraints\\EnumConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/EnumConstraint.php',
    'JsonSchema\\Constraints\\Factory' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.php',
    'JsonSchema\\Constraints\\FormatConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php',
    'JsonSchema\\Constraints\\NumberConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/NumberConstraint.php',
    'JsonSchema\\Constraints\\ObjectConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/ObjectConstraint.php',
    'JsonSchema\\Constraints\\SchemaConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/SchemaConstraint.php',
    'JsonSchema\\Constraints\\StringConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/StringConstraint.php',
    'JsonSchema\\Constraints\\TypeCheck\\LooseTypeCheck' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/LooseTypeCheck.php',
    'JsonSchema\\Constraints\\TypeCheck\\StrictTypeCheck' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/StrictTypeCheck.php',
    'JsonSchema\\Constraints\\TypeCheck\\TypeCheckInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/TypeCheckInterface.php',
    'JsonSchema\\Constraints\\TypeConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeConstraint.php',
    'JsonSchema\\Constraints\\UndefinedConstraint' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Constraints/UndefinedConstraint.php',
    'JsonSchema\\Entity\\JsonPointer' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Entity/JsonPointer.php',
    'JsonSchema\\Enum' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Enum.php',
    'JsonSchema\\Exception\\ExceptionInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/ExceptionInterface.php',
    'JsonSchema\\Exception\\InvalidArgumentException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidArgumentException.php',
    'JsonSchema\\Exception\\InvalidConfigException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidConfigException.php',
    'JsonSchema\\Exception\\InvalidSchemaException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaException.php',
    'JsonSchema\\Exception\\InvalidSchemaMediaTypeException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaMediaTypeException.php',
    'JsonSchema\\Exception\\InvalidSourceUriException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSourceUriException.php',
    'JsonSchema\\Exception\\JsonDecodingException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/JsonDecodingException.php',
    'JsonSchema\\Exception\\ResourceNotFoundException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/ResourceNotFoundException.php',
    'JsonSchema\\Exception\\RuntimeException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/RuntimeException.php',
    'JsonSchema\\Exception\\UnresolvableJsonPointerException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/UnresolvableJsonPointerException.php',
    'JsonSchema\\Exception\\UriResolverException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/UriResolverException.php',
    'JsonSchema\\Exception\\ValidationException' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Exception/ValidationException.php',
    'JsonSchema\\Iterator\\ObjectIterator' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Iterator/ObjectIterator.php',
    'JsonSchema\\Rfc3339' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Rfc3339.php',
    'JsonSchema\\SchemaStorage' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/SchemaStorage.php',
    'JsonSchema\\SchemaStorageInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/SchemaStorageInterface.php',
    'JsonSchema\\Tool\\DeepComparer' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Tool/DeepComparer.php',
    'JsonSchema\\Tool\\DeepCopy' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Tool/DeepCopy.php',
    'JsonSchema\\Tool\\Validator\\RelativeReferenceValidator' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Tool/Validator/RelativeReferenceValidator.php',
    'JsonSchema\\Tool\\Validator\\UriValidator' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Tool/Validator/UriValidator.php',
    'JsonSchema\\UriResolverInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/UriResolverInterface.php',
    'JsonSchema\\UriRetrieverInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/UriRetrieverInterface.php',
    'JsonSchema\\Uri\\Retrievers\\AbstractRetriever' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/AbstractRetriever.php',
    'JsonSchema\\Uri\\Retrievers\\Curl' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/Curl.php',
    'JsonSchema\\Uri\\Retrievers\\FileGetContents' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/FileGetContents.php',
    'JsonSchema\\Uri\\Retrievers\\PredefinedArray' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/PredefinedArray.php',
    'JsonSchema\\Uri\\Retrievers\\UriRetrieverInterface' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/UriRetrieverInterface.php',
    'JsonSchema\\Uri\\UriResolver' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/UriResolver.php',
    'JsonSchema\\Uri\\UriRetriever' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php',
    'JsonSchema\\Validator' => $vendorDir . '/justinrainbow/json-schema/src/JsonSchema/Validator.php',
    'MabeEnum\\Enum' => $vendorDir . '/marc-mabe/php-enum/src/Enum.php',
    'MabeEnum\\EnumMap' => $vendorDir . '/marc-mabe/php-enum/src/EnumMap.php',
    'MabeEnum\\EnumSerializableTrait' => $vendorDir . '/marc-mabe/php-enum/src/EnumSerializableTrait.php',
    'MabeEnum\\EnumSet' => $vendorDir . '/marc-mabe/php-enum/src/EnumSet.php',
    'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
    'Php\\Pie\\Building\\Build' => $baseDir . '/src/Building/Build.php',
    'Php\\Pie\\Building\\ExtensionBinaryNotFound' => $baseDir . '/src/Building/ExtensionBinaryNotFound.php',
    'Php\\Pie\\Building\\UnixBuild' => $baseDir . '/src/Building/UnixBuild.php',
    'Php\\Pie\\Building\\WindowsBuild' => $baseDir . '/src/Building/WindowsBuild.php',
    'Php\\Pie\\Command\\BuildCommand' => $baseDir . '/src/Command/BuildCommand.php',
    'Php\\Pie\\Command\\CommandHelper' => $baseDir . '/src/Command/CommandHelper.php',
    'Php\\Pie\\Command\\DownloadCommand' => $baseDir . '/src/Command/DownloadCommand.php',
    'Php\\Pie\\Command\\InfoCommand' => $baseDir . '/src/Command/InfoCommand.php',
    'Php\\Pie\\Command\\InstallCommand' => $baseDir . '/src/Command/InstallCommand.php',
    'Php\\Pie\\Command\\InstallExtensionsForProjectCommand' => $baseDir . '/src/Command/InstallExtensionsForProjectCommand.php',
    'Php\\Pie\\Command\\InvokeSubCommand' => $baseDir . '/src/Command/InvokeSubCommand.php',
    'Php\\Pie\\Command\\RepositoryAddCommand' => $baseDir . '/src/Command/RepositoryAddCommand.php',
    'Php\\Pie\\Command\\RepositoryListCommand' => $baseDir . '/src/Command/RepositoryListCommand.php',
    'Php\\Pie\\Command\\RepositoryRemoveCommand' => $baseDir . '/src/Command/RepositoryRemoveCommand.php',
    'Php\\Pie\\Command\\SelfUpdateCommand' => $baseDir . '/src/Command/SelfUpdateCommand.php',
    'Php\\Pie\\Command\\SelfVerifyCommand' => $baseDir . '/src/Command/SelfVerifyCommand.php',
    'Php\\Pie\\Command\\ShowCommand' => $baseDir . '/src/Command/ShowCommand.php',
    'Php\\Pie\\Command\\UninstallCommand' => $baseDir . '/src/Command/UninstallCommand.php',
    'Php\\Pie\\ComposerIntegration\\BundledPhpExtensionsRepository' => $baseDir . '/src/ComposerIntegration/BundledPhpExtensionsRepository.php',
    'Php\\Pie\\ComposerIntegration\\ComposerIntegrationHandler' => $baseDir . '/src/ComposerIntegration/ComposerIntegrationHandler.php',
    'Php\\Pie\\ComposerIntegration\\ComposerRunFailed' => $baseDir . '/src/ComposerIntegration/ComposerRunFailed.php',
    'Php\\Pie\\ComposerIntegration\\InstallAndBuildProcess' => $baseDir . '/src/ComposerIntegration/InstallAndBuildProcess.php',
    'Php\\Pie\\ComposerIntegration\\InstalledJsonMetadata' => $baseDir . '/src/ComposerIntegration/InstalledJsonMetadata.php',
    'Php\\Pie\\ComposerIntegration\\Listeners\\OverrideDownloadUrlInstallListener' => $baseDir . '/src/ComposerIntegration/Listeners/OverrideDownloadUrlInstallListener.php',
    'Php\\Pie\\ComposerIntegration\\Listeners\\RemoveUnrelatedInstallOperations' => $baseDir . '/src/ComposerIntegration/Listeners/RemoveUnrelatedInstallOperations.php',
    'Php\\Pie\\ComposerIntegration\\MinimalHelperSet' => $baseDir . '/src/ComposerIntegration/MinimalHelperSet.php',
    'Php\\Pie\\ComposerIntegration\\PhpBinaryPathBasedPlatformRepository' => $baseDir . '/src/ComposerIntegration/PhpBinaryPathBasedPlatformRepository.php',
    'Php\\Pie\\ComposerIntegration\\PieComposerFactory' => $baseDir . '/src/ComposerIntegration/PieComposerFactory.php',
    'Php\\Pie\\ComposerIntegration\\PieComposerInstaller' => $baseDir . '/src/ComposerIntegration/PieComposerInstaller.php',
    'Php\\Pie\\ComposerIntegration\\PieComposerRequest' => $baseDir . '/src/ComposerIntegration/PieComposerRequest.php',
    'Php\\Pie\\ComposerIntegration\\PieInstalledJsonMetadataKeys' => $baseDir . '/src/ComposerIntegration/PieInstalledJsonMetadataKeys.php',
    'Php\\Pie\\ComposerIntegration\\PieJsonEditor' => $baseDir . '/src/ComposerIntegration/PieJsonEditor.php',
    'Php\\Pie\\ComposerIntegration\\PieOperation' => $baseDir . '/src/ComposerIntegration/PieOperation.php',
    'Php\\Pie\\ComposerIntegration\\PiePackageInstaller' => $baseDir . '/src/ComposerIntegration/PiePackageInstaller.php',
    'Php\\Pie\\ComposerIntegration\\QuieterConsoleIO' => $baseDir . '/src/ComposerIntegration/QuieterConsoleIO.php',
    'Php\\Pie\\ComposerIntegration\\UninstallProcess' => $baseDir . '/src/ComposerIntegration/UninstallProcess.php',
    'Php\\Pie\\ComposerIntegration\\VendorCleanup' => $baseDir . '/src/ComposerIntegration/VendorCleanup.php',
    'Php\\Pie\\ComposerIntegration\\VersionSelectorFactory' => $baseDir . '/src/ComposerIntegration/VersionSelectorFactory.php',
    'Php\\Pie\\ConfigureOption' => $baseDir . '/src/ConfigureOption.php',
    'Php\\Pie\\Container' => $baseDir . '/src/Container.php',
    'Php\\Pie\\DependencyResolver\\BundledPhpExtensionRefusal' => $baseDir . '/src/DependencyResolver/BundledPhpExtensionRefusal.php',
    'Php\\Pie\\DependencyResolver\\DependencyResolver' => $baseDir . '/src/DependencyResolver/DependencyResolver.php',
    'Php\\Pie\\DependencyResolver\\DetermineMinimumStability' => $baseDir . '/src/DependencyResolver/DetermineMinimumStability.php',
    'Php\\Pie\\DependencyResolver\\IncompatibleOperatingSystemFamily' => $baseDir . '/src/DependencyResolver/IncompatibleOperatingSystemFamily.php',
    'Php\\Pie\\DependencyResolver\\IncompatibleThreadSafetyMode' => $baseDir . '/src/DependencyResolver/IncompatibleThreadSafetyMode.php',
    'Php\\Pie\\DependencyResolver\\InvalidPackageName' => $baseDir . '/src/DependencyResolver/InvalidPackageName.php',
    'Php\\Pie\\DependencyResolver\\Package' => $baseDir . '/src/DependencyResolver/Package.php',
    'Php\\Pie\\DependencyResolver\\RequestedPackageAndVersion' => $baseDir . '/src/DependencyResolver/RequestedPackageAndVersion.php',
    'Php\\Pie\\DependencyResolver\\ResolveDependencyWithComposer' => $baseDir . '/src/DependencyResolver/ResolveDependencyWithComposer.php',
    'Php\\Pie\\DependencyResolver\\UnableToResolveRequirement' => $baseDir . '/src/DependencyResolver/UnableToResolveRequirement.php',
    'Php\\Pie\\Downloading\\DownloadUrlMethod' => $baseDir . '/src/Downloading/DownloadUrlMethod.php',
    'Php\\Pie\\Downloading\\DownloadedPackage' => $baseDir . '/src/Downloading/DownloadedPackage.php',
    'Php\\Pie\\Downloading\\Exception\\CouldNotFindReleaseAsset' => $baseDir . '/src/Downloading/Exception/CouldNotFindReleaseAsset.php',
    'Php\\Pie\\Downloading\\GithubPackageReleaseAssets' => $baseDir . '/src/Downloading/GithubPackageReleaseAssets.php',
    'Php\\Pie\\Downloading\\PackageReleaseAssets' => $baseDir . '/src/Downloading/PackageReleaseAssets.php',
    'Php\\Pie\\ExtensionName' => $baseDir . '/src/ExtensionName.php',
    'Php\\Pie\\ExtensionType' => $baseDir . '/src/ExtensionType.php',
    'Php\\Pie\\File\\BinaryFile' => $baseDir . '/src/File/BinaryFile.php',
    'Php\\Pie\\File\\BinaryFileFailedVerification' => $baseDir . '/src/File/BinaryFileFailedVerification.php',
    'Php\\Pie\\File\\FailedToCreateFile' => $baseDir . '/src/File/FailedToCreateFile.php',
    'Php\\Pie\\File\\FailedToUnlinkFile' => $baseDir . '/src/File/FailedToUnlinkFile.php',
    'Php\\Pie\\File\\FailedToWriteFile' => $baseDir . '/src/File/FailedToWriteFile.php',
    'Php\\Pie\\File\\FullPathToSelf' => $baseDir . '/src/File/FullPathToSelf.php',
    'Php\\Pie\\File\\Sudo' => $baseDir . '/src/File/Sudo.php',
    'Php\\Pie\\File\\SudoCreate' => $baseDir . '/src/File/SudoCreate.php',
    'Php\\Pie\\File\\SudoFilePut' => $baseDir . '/src/File/SudoFilePut.php',
    'Php\\Pie\\File\\SudoNotFoundOnSystem' => $baseDir . '/src/File/SudoNotFoundOnSystem.php',
    'Php\\Pie\\File\\SudoRequiresInteractiveTerminal' => $baseDir . '/src/File/SudoRequiresInteractiveTerminal.php',
    'Php\\Pie\\File\\SudoUnlink' => $baseDir . '/src/File/SudoUnlink.php',
    'Php\\Pie\\File\\WindowsDelete' => $baseDir . '/src/File/WindowsDelete.php',
    'Php\\Pie\\Installing\\FailedToRemoveExtension' => $baseDir . '/src/Installing/FailedToRemoveExtension.php',
    'Php\\Pie\\Installing\\Ini\\AddExtensionToTheIniFile' => $baseDir . '/src/Installing/Ini/AddExtensionToTheIniFile.php',
    'Php\\Pie\\Installing\\Ini\\CheckAndAddExtensionToIniIfNeeded' => $baseDir . '/src/Installing/Ini/CheckAndAddExtensionToIniIfNeeded.php',
    'Php\\Pie\\Installing\\Ini\\DockerPhpExtEnable' => $baseDir . '/src/Installing/Ini/DockerPhpExtEnable.php',
    'Php\\Pie\\Installing\\Ini\\IsExtensionAlreadyInTheIniFile' => $baseDir . '/src/Installing/Ini/IsExtensionAlreadyInTheIniFile.php',
    'Php\\Pie\\Installing\\Ini\\OndrejPhpenmod' => $baseDir . '/src/Installing/Ini/OndrejPhpenmod.php',
    'Php\\Pie\\Installing\\Ini\\PickBestSetupIniApproach' => $baseDir . '/src/Installing/Ini/PickBestSetupIniApproach.php',
    'Php\\Pie\\Installing\\Ini\\PreCheckExtensionAlreadyLoaded' => $baseDir . '/src/Installing/Ini/PreCheckExtensionAlreadyLoaded.php',
    'Php\\Pie\\Installing\\Ini\\RemoveIniEntry' => $baseDir . '/src/Installing/Ini/RemoveIniEntry.php',
    'Php\\Pie\\Installing\\Ini\\RemoveIniEntryWithFileGetContents' => $baseDir . '/src/Installing/Ini/RemoveIniEntryWithFileGetContents.php',
    'Php\\Pie\\Installing\\Ini\\SetupIniApproach' => $baseDir . '/src/Installing/Ini/SetupIniApproach.php',
    'Php\\Pie\\Installing\\Ini\\StandardAdditionalPhpIniDirectory' => $baseDir . '/src/Installing/Ini/StandardAdditionalPhpIniDirectory.php',
    'Php\\Pie\\Installing\\Ini\\StandardSinglePhpIni' => $baseDir . '/src/Installing/Ini/StandardSinglePhpIni.php',
    'Php\\Pie\\Installing\\Install' => $baseDir . '/src/Installing/Install.php',
    'Php\\Pie\\Installing\\InstallForPhpProject\\ComposerFactoryForProject' => $baseDir . '/src/Installing/InstallForPhpProject/ComposerFactoryForProject.php',
    'Php\\Pie\\Installing\\InstallForPhpProject\\DetermineExtensionsRequired' => $baseDir . '/src/Installing/InstallForPhpProject/DetermineExtensionsRequired.php',
    'Php\\Pie\\Installing\\InstallForPhpProject\\FindMatchingPackages' => $baseDir . '/src/Installing/InstallForPhpProject/FindMatchingPackages.php',
    'Php\\Pie\\Installing\\InstallForPhpProject\\InstallPiePackageFromPath' => $baseDir . '/src/Installing/InstallForPhpProject/InstallPiePackageFromPath.php',
    'Php\\Pie\\Installing\\InstallForPhpProject\\InstallSelectedPackage' => $baseDir . '/src/Installing/InstallForPhpProject/InstallSelectedPackage.php',
    'Php\\Pie\\Installing\\PackageMetadataMissing' => $baseDir . '/src/Installing/PackageMetadataMissing.php',
    'Php\\Pie\\Installing\\SetupIniFile' => $baseDir . '/src/Installing/SetupIniFile.php',
    'Php\\Pie\\Installing\\Uninstall' => $baseDir . '/src/Installing/Uninstall.php',
    'Php\\Pie\\Installing\\UninstallUsingUnlink' => $baseDir . '/src/Installing/UninstallUsingUnlink.php',
    'Php\\Pie\\Installing\\UnixInstall' => $baseDir . '/src/Installing/UnixInstall.php',
    'Php\\Pie\\Installing\\WindowsInstall' => $baseDir . '/src/Installing/WindowsInstall.php',
    'Php\\Pie\\Platform' => $baseDir . '/src/Platform.php',
    'Php\\Pie\\Platform\\Architecture' => $baseDir . '/src/Platform/Architecture.php',
    'Php\\Pie\\Platform\\InstalledPiePackages' => $baseDir . '/src/Platform/InstalledPiePackages.php',
    'Php\\Pie\\Platform\\OperatingSystem' => $baseDir . '/src/Platform/OperatingSystem.php',
    'Php\\Pie\\Platform\\OperatingSystemFamily' => $baseDir . '/src/Platform/OperatingSystemFamily.php',
    'Php\\Pie\\Platform\\PrePackagedSourceAssetName' => $baseDir . '/src/Platform/PrePackagedSourceAssetName.php',
    'Php\\Pie\\Platform\\TargetPhp\\Exception\\ExtensionIsNotLoaded' => $baseDir . '/src/Platform/TargetPhp/Exception/ExtensionIsNotLoaded.php',
    'Php\\Pie\\Platform\\TargetPhp\\Exception\\InvalidPhpBinaryPath' => $baseDir . '/src/Platform/TargetPhp/Exception/InvalidPhpBinaryPath.php',
    'Php\\Pie\\Platform\\TargetPhp\\PhpBinaryPath' => $baseDir . '/src/Platform/TargetPhp/PhpBinaryPath.php',
    'Php\\Pie\\Platform\\TargetPhp\\PhpizePath' => $baseDir . '/src/Platform/TargetPhp/PhpizePath.php',
    'Php\\Pie\\Platform\\TargetPlatform' => $baseDir . '/src/Platform/TargetPlatform.php',
    'Php\\Pie\\Platform\\ThreadSafetyMode' => $baseDir . '/src/Platform/ThreadSafetyMode.php',
    'Php\\Pie\\Platform\\WindowsCompiler' => $baseDir . '/src/Platform/WindowsCompiler.php',
    'Php\\Pie\\Platform\\WindowsExtensionAssetName' => $baseDir . '/src/Platform/WindowsExtensionAssetName.php',
    'Php\\Pie\\SelfManage\\Update\\FetchPieRelease' => $baseDir . '/src/SelfManage/Update/FetchPieRelease.php',
    'Php\\Pie\\SelfManage\\Update\\FetchPieReleaseFromGitHub' => $baseDir . '/src/SelfManage/Update/FetchPieReleaseFromGitHub.php',
    'Php\\Pie\\SelfManage\\Update\\ReleaseMetadata' => $baseDir . '/src/SelfManage/Update/ReleaseMetadata.php',
    'Php\\Pie\\SelfManage\\Verify\\Attestation' => $baseDir . '/src/SelfManage/Verify/Attestation.php',
    'Php\\Pie\\SelfManage\\Verify\\FailedToVerifyRelease' => $baseDir . '/src/SelfManage/Verify/FailedToVerifyRelease.php',
    'Php\\Pie\\SelfManage\\Verify\\FallbackVerificationUsingOpenSsl' => $baseDir . '/src/SelfManage/Verify/FallbackVerificationUsingOpenSsl.php',
    'Php\\Pie\\SelfManage\\Verify\\GithubCliAttestationVerification' => $baseDir . '/src/SelfManage/Verify/GithubCliAttestationVerification.php',
    'Php\\Pie\\SelfManage\\Verify\\GithubCliNotAvailable' => $baseDir . '/src/SelfManage/Verify/GithubCliNotAvailable.php',
    'Php\\Pie\\SelfManage\\Verify\\VerifyPiePhar' => $baseDir . '/src/SelfManage/Verify/VerifyPiePhar.php',
    'Php\\Pie\\SelfManage\\Verify\\VerifyPieReleaseUsingAttestation' => $baseDir . '/src/SelfManage/Verify/VerifyPieReleaseUsingAttestation.php',
    'Php\\Pie\\Util\\CaptureErrors' => $baseDir . '/src/Util/CaptureErrors.php',
    'Php\\Pie\\Util\\Emoji' => $baseDir . '/src/Util/Emoji.php',
    'Php\\Pie\\Util\\FileNotFound' => $baseDir . '/src/Util/FileNotFound.php',
    'Php\\Pie\\Util\\PieVersion' => $baseDir . '/src/Util/PieVersion.php',
    'Php\\Pie\\Util\\Process' => $baseDir . '/src/Util/Process.php',
    '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\\Log\\AbstractLogger' => $vendorDir . '/psr/log/src/AbstractLogger.php',
    'Psr\\Log\\InvalidArgumentException' => $vendorDir . '/psr/log/src/InvalidArgumentException.php',
    'Psr\\Log\\LogLevel' => $vendorDir . '/psr/log/src/LogLevel.php',
    'Psr\\Log\\LoggerAwareInterface' => $vendorDir . '/psr/log/src/LoggerAwareInterface.php',
    'Psr\\Log\\LoggerAwareTrait' => $vendorDir . '/psr/log/src/LoggerAwareTrait.php',
    'Psr\\Log\\LoggerInterface' => $vendorDir . '/psr/log/src/LoggerInterface.php',
    'Psr\\Log\\LoggerTrait' => $vendorDir . '/psr/log/src/LoggerTrait.php',
    'Psr\\Log\\NullLogger' => $vendorDir . '/psr/log/src/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',
    'React\\Promise\\Deferred' => $vendorDir . '/react/promise/src/Deferred.php',
    'React\\Promise\\Exception\\CompositeException' => $vendorDir . '/react/promise/src/Exception/CompositeException.php',
    'React\\Promise\\Exception\\LengthException' => $vendorDir . '/react/promise/src/Exception/LengthException.php',
    'React\\Promise\\Internal\\CancellationQueue' => $vendorDir . '/react/promise/src/Internal/CancellationQueue.php',
    'React\\Promise\\Internal\\FulfilledPromise' => $vendorDir . '/react/promise/src/Internal/FulfilledPromise.php',
    'React\\Promise\\Internal\\RejectedPromise' => $vendorDir . '/react/promise/src/Internal/RejectedPromise.php',
    'React\\Promise\\Promise' => $vendorDir . '/react/promise/src/Promise.php',
    'React\\Promise\\PromiseInterface' => $vendorDir . '/react/promise/src/PromiseInterface.php',
    'Seld\\JsonLint\\DuplicateKeyException' => $vendorDir . '/seld/jsonlint/src/Seld/JsonLint/DuplicateKeyException.php',
    'Seld\\JsonLint\\JsonParser' => $vendorDir . '/seld/jsonlint/src/Seld/JsonLint/JsonParser.php',
    'Seld\\JsonLint\\Lexer' => $vendorDir . '/seld/jsonlint/src/Seld/JsonLint/Lexer.php',
    'Seld\\JsonLint\\ParsingException' => $vendorDir . '/seld/jsonlint/src/Seld/JsonLint/ParsingException.php',
    'Seld\\JsonLint\\Undefined' => $vendorDir . '/seld/jsonlint/src/Seld/JsonLint/Undefined.php',
    'Seld\\PharUtils\\Linter' => $vendorDir . '/seld/phar-utils/src/Linter.php',
    'Seld\\PharUtils\\Timestamps' => $vendorDir . '/seld/phar-utils/src/Timestamps.php',
    'Seld\\Signal\\SignalHandler' => $vendorDir . '/seld/signal-handler/src/SignalHandler.php',
    'Stringable' => $vendorDir . '/marc-mabe/php-enum/stubs/Stringable.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\\Command\\TraceableCommand' => $vendorDir . '/symfony/console/Command/TraceableCommand.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\\Output\\FishCompletionOutput' => $vendorDir . '/symfony/console/Completion/Output/FishCompletionOutput.php',
    'Symfony\\Component\\Console\\Completion\\Output\\ZshCompletionOutput' => $vendorDir . '/symfony/console/Completion/Output/ZshCompletionOutput.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\\DataCollector\\CommandDataCollector' => $vendorDir . '/symfony/console/DataCollector/CommandDataCollector.php',
    'Symfony\\Component\\Console\\Debug\\CliRequest' => $vendorDir . '/symfony/console/Debug/CliRequest.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',
    'Symfony\\Component\\Console\\Descriptor\\DescriptorInterface' => $vendorDir . '/symfony/console/Descriptor/DescriptorInterface.php',
    'Symfony\\Component\\Console\\Descriptor\\JsonDescriptor' => $vendorDir . '/symfony/console/Descriptor/JsonDescriptor.php',
    'Symfony\\Component\\Console\\Descriptor\\MarkdownDescriptor' => $vendorDir . '/symfony/console/Descriptor/MarkdownDescriptor.php',
    'Symfony\\Component\\Console\\Descriptor\\ReStructuredTextDescriptor' => $vendorDir . '/symfony/console/Descriptor/ReStructuredTextDescriptor.php',
    'Symfony\\Component\\Console\\Descriptor\\TextDescriptor' => $vendorDir . '/symfony/console/Descriptor/TextDescriptor.php',
    'Symfony\\Component\\Console\\Descriptor\\XmlDescriptor' => $vendorDir . '/symfony/console/Descriptor/XmlDescriptor.php',
    'Symfony\\Component\\Console\\EventListener\\ErrorListener' => $vendorDir . '/symfony/console/EventListener/ErrorListener.php',
    '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\\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\\RunCommandFailedException' => $vendorDir . '/symfony/console/Exception/RunCommandFailedException.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',
    'Symfony\\Component\\Console\\Helper\\HelperSet' => $vendorDir . '/symfony/console/Helper/HelperSet.php',
    'Symfony\\Component\\Console\\Helper\\InputAwareHelper' => $vendorDir . '/symfony/console/Helper/InputAwareHelper.php',
    'Symfony\\Component\\Console\\Helper\\OutputWrapper' => $vendorDir . '/symfony/console/Helper/OutputWrapper.php',
    'Symfony\\Component\\Console\\Helper\\ProcessHelper' => $vendorDir . '/symfony/console/Helper/ProcessHelper.php',
    'Symfony\\Component\\Console\\Helper\\ProgressBar' => $vendorDir . '/symfony/console/Helper/ProgressBar.php',
    'Symfony\\Component\\Console\\Helper\\ProgressIndicator' => $vendorDir . '/symfony/console/Helper/ProgressIndicator.php',
    'Symfony\\Component\\Console\\Helper\\QuestionHelper' => $vendorDir . '/symfony/console/Helper/QuestionHelper.php',
    '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',
    'Symfony\\Component\\Console\\Input\\ArrayInput' => $vendorDir . '/symfony/console/Input/ArrayInput.php',
    'Symfony\\Component\\Console\\Input\\Input' => $vendorDir . '/symfony/console/Input/Input.php',
    'Symfony\\Component\\Console\\Input\\InputArgument' => $vendorDir . '/symfony/console/Input/InputArgument.php',
    'Symfony\\Component\\Console\\Input\\InputAwareInterface' => $vendorDir . '/symfony/console/Input/InputAwareInterface.php',
    'Symfony\\Component\\Console\\Input\\InputDefinition' => $vendorDir . '/symfony/console/Input/InputDefinition.php',
    'Symfony\\Component\\Console\\Input\\InputInterface' => $vendorDir . '/symfony/console/Input/InputInterface.php',
    'Symfony\\Component\\Console\\Input\\InputOption' => $vendorDir . '/symfony/console/Input/InputOption.php',
    'Symfony\\Component\\Console\\Input\\StreamableInputInterface' => $vendorDir . '/symfony/console/Input/StreamableInputInterface.php',
    'Symfony\\Component\\Console\\Input\\StringInput' => $vendorDir . '/symfony/console/Input/StringInput.php',
    'Symfony\\Component\\Console\\Logger\\ConsoleLogger' => $vendorDir . '/symfony/console/Logger/ConsoleLogger.php',
    'Symfony\\Component\\Console\\Messenger\\RunCommandContext' => $vendorDir . '/symfony/console/Messenger/RunCommandContext.php',
    'Symfony\\Component\\Console\\Messenger\\RunCommandMessage' => $vendorDir . '/symfony/console/Messenger/RunCommandMessage.php',
    'Symfony\\Component\\Console\\Messenger\\RunCommandMessageHandler' => $vendorDir . '/symfony/console/Messenger/RunCommandMessageHandler.php',
    'Symfony\\Component\\Console\\Output\\AnsiColorMode' => $vendorDir . '/symfony/console/Output/AnsiColorMode.php',
    '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\\SignalMap' => $vendorDir . '/symfony/console/SignalRegistry/SignalMap.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\\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\\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\\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\\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\\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\\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',
    'Symfony\\Component\\Finder\\Iterator\\DepthRangeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/DepthRangeFilterIterator.php',
    'Symfony\\Component\\Finder\\Iterator\\ExcludeDirectoryFilterIterator' => $vendorDir . '/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php',
    '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\\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\\Process\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/process/Exception/ExceptionInterface.php',
    'Symfony\\Component\\Process\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/process/Exception/InvalidArgumentException.php',
    'Symfony\\Component\\Process\\Exception\\LogicException' => $vendorDir . '/symfony/process/Exception/LogicException.php',
    'Symfony\\Component\\Process\\Exception\\ProcessFailedException' => $vendorDir . '/symfony/process/Exception/ProcessFailedException.php',
    'Symfony\\Component\\Process\\Exception\\ProcessSignaledException' => $vendorDir . '/symfony/process/Exception/ProcessSignaledException.php',
    'Symfony\\Component\\Process\\Exception\\ProcessTimedOutException' => $vendorDir . '/symfony/process/Exception/ProcessTimedOutException.php',
    'Symfony\\Component\\Process\\Exception\\RunProcessFailedException' => $vendorDir . '/symfony/process/Exception/RunProcessFailedException.php',
    'Symfony\\Component\\Process\\Exception\\RuntimeException' => $vendorDir . '/symfony/process/Exception/RuntimeException.php',
    'Symfony\\Component\\Process\\ExecutableFinder' => $vendorDir . '/symfony/process/ExecutableFinder.php',
    'Symfony\\Component\\Process\\InputStream' => $vendorDir . '/symfony/process/InputStream.php',
    'Symfony\\Component\\Process\\Messenger\\RunProcessContext' => $vendorDir . '/symfony/process/Messenger/RunProcessContext.php',
    'Symfony\\Component\\Process\\Messenger\\RunProcessMessage' => $vendorDir . '/symfony/process/Messenger/RunProcessMessage.php',
    'Symfony\\Component\\Process\\Messenger\\RunProcessMessageHandler' => $vendorDir . '/symfony/process/Messenger/RunProcessMessageHandler.php',
    'Symfony\\Component\\Process\\PhpExecutableFinder' => $vendorDir . '/symfony/process/PhpExecutableFinder.php',
    'Symfony\\Component\\Process\\PhpProcess' => $vendorDir . '/symfony/process/PhpProcess.php',
    'Symfony\\Component\\Process\\PhpSubprocess' => $vendorDir . '/symfony/process/PhpSubprocess.php',
    'Symfony\\Component\\Process\\Pipes\\AbstractPipes' => $vendorDir . '/symfony/process/Pipes/AbstractPipes.php',
    'Symfony\\Component\\Process\\Pipes\\PipesInterface' => $vendorDir . '/symfony/process/Pipes/PipesInterface.php',
    'Symfony\\Component\\Process\\Pipes\\UnixPipes' => $vendorDir . '/symfony/process/Pipes/UnixPipes.php',
    'Symfony\\Component\\Process\\Pipes\\WindowsPipes' => $vendorDir . '/symfony/process/Pipes/WindowsPipes.php',
    'Symfony\\Component\\Process\\Process' => $vendorDir . '/symfony/process/Process.php',
    'Symfony\\Component\\Process\\ProcessUtils' => $vendorDir . '/symfony/process/ProcessUtils.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\\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\\ServiceCollectionInterface' => $vendorDir . '/symfony/service-contracts/ServiceCollectionInterface.php',
    'Symfony\\Contracts\\Service\\ServiceLocatorTrait' => $vendorDir . '/symfony/service-contracts/ServiceLocatorTrait.php',
    'Symfony\\Contracts\\Service\\ServiceMethodsSubscriberTrait' => $vendorDir . '/symfony/service-contracts/ServiceMethodsSubscriberTrait.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\\Polyfill\\Ctype\\Ctype' => $vendorDir . '/symfony/polyfill-ctype/Ctype.php',
    'Symfony\\Polyfill\\Intl\\Grapheme\\Grapheme' => $vendorDir . '/symfony/polyfill-intl-grapheme/Grapheme.php',
    'Symfony\\Polyfill\\Intl\\Normalizer\\Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Normalizer.php',
    'Symfony\\Polyfill\\Mbstring\\Mbstring' => $vendorDir . '/symfony/polyfill-mbstring/Mbstring.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',
);
<?php

// autoload_static.php @generated by Composer

namespace Composer\Autoload;

class ComposerStaticInitf542bd5a549fa468020c82327ba6fd88
{
    public static $files = array (
        '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
        '6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
        '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
        '8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php',
        'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php',
        'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php',
        'ad155f8f1cf0d418fe49e248db8c661b' => __DIR__ . '/..' . '/react/promise/src/functions_include.php',
    );

    public static $prefixLengthsPsr4 = array (
        'W' => 
        array (
            'Webmozart\\Assert\\' => 17,
        ),
        'S' => 
        array (
            'Symfony\\Polyfill\\Mbstring\\' => 26,
            'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33,
            'Symfony\\Polyfill\\Intl\\Grapheme\\' => 31,
            'Symfony\\Polyfill\\Ctype\\' => 23,
            'Symfony\\Contracts\\Service\\' => 26,
            'Symfony\\Contracts\\EventDispatcher\\' => 34,
            'Symfony\\Component\\String\\' => 25,
            'Symfony\\Component\\Process\\' => 26,
            'Symfony\\Component\\Finder\\' => 25,
            'Symfony\\Component\\Filesystem\\' => 29,
            'Symfony\\Component\\EventDispatcher\\' => 34,
            'Symfony\\Component\\Console\\' => 26,
            'Seld\\Signal\\' => 12,
            'Seld\\PharUtils\\' => 15,
            'Seld\\JsonLint\\' => 14,
        ),
        'R' => 
        array (
            'React\\Promise\\' => 14,
        ),
        'P' => 
        array (
            'Psr\\SimpleCache\\' => 16,
            'Psr\\Log\\' => 8,
            'Psr\\EventDispatcher\\' => 20,
            'Psr\\Container\\' => 14,
            'Php\\Pie\\' => 8,
        ),
        'M' => 
        array (
            'MabeEnum\\' => 9,
        ),
        'J' => 
        array (
            'JsonSchema\\' => 11,
        ),
        'I' => 
        array (
            'Illuminate\\Contracts\\' => 21,
            'Illuminate\\Container\\' => 21,
        ),
        'F' => 
        array (
            'Fidry\\CpuCoreCounter\\' => 21,
        ),
        'C' => 
        array (
            'Composer\\XdebugHandler\\' => 23,
            'Composer\\Spdx\\' => 14,
            'Composer\\Semver\\' => 16,
            'Composer\\Pcre\\' => 14,
            'Composer\\MetadataMinifier\\' => 26,
            'Composer\\ClassMapGenerator\\' => 27,
            'Composer\\CaBundle\\' => 18,
            'Composer\\' => 9,
        ),
    );

    public static $prefixDirsPsr4 = array (
        'Webmozart\\Assert\\' => 
        array (
            0 => __DIR__ . '/..' . '/webmozart/assert/src',
        ),
        'Symfony\\Polyfill\\Mbstring\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
        ),
        'Symfony\\Polyfill\\Intl\\Normalizer\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer',
        ),
        'Symfony\\Polyfill\\Intl\\Grapheme\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme',
        ),
        'Symfony\\Polyfill\\Ctype\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/polyfill-ctype',
        ),
        'Symfony\\Contracts\\Service\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/service-contracts',
        ),
        'Symfony\\Contracts\\EventDispatcher\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/event-dispatcher-contracts',
        ),
        'Symfony\\Component\\String\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/string',
        ),
        'Symfony\\Component\\Process\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/process',
        ),
        'Symfony\\Component\\Finder\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/finder',
        ),
        'Symfony\\Component\\Filesystem\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/filesystem',
        ),
        'Symfony\\Component\\EventDispatcher\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/event-dispatcher',
        ),
        'Symfony\\Component\\Console\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/console',
        ),
        'Seld\\Signal\\' => 
        array (
            0 => __DIR__ . '/..' . '/seld/signal-handler/src',
        ),
        'Seld\\PharUtils\\' => 
        array (
            0 => __DIR__ . '/..' . '/seld/phar-utils/src',
        ),
        'Seld\\JsonLint\\' => 
        array (
            0 => __DIR__ . '/..' . '/seld/jsonlint/src/Seld/JsonLint',
        ),
        'React\\Promise\\' => 
        array (
            0 => __DIR__ . '/..' . '/react/promise/src',
        ),
        'Psr\\SimpleCache\\' => 
        array (
            0 => __DIR__ . '/..' . '/psr/simple-cache/src',
        ),
        'Psr\\Log\\' => 
        array (
            0 => __DIR__ . '/..' . '/psr/log/src',
        ),
        'Psr\\EventDispatcher\\' => 
        array (
            0 => __DIR__ . '/..' . '/psr/event-dispatcher/src',
        ),
        'Psr\\Container\\' => 
        array (
            0 => __DIR__ . '/..' . '/psr/container/src',
        ),
        'Php\\Pie\\' => 
        array (
            0 => __DIR__ . '/../..' . '/src',
        ),
        'MabeEnum\\' => 
        array (
            0 => __DIR__ . '/..' . '/marc-mabe/php-enum/src',
        ),
        'JsonSchema\\' => 
        array (
            0 => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema',
        ),
        'Illuminate\\Contracts\\' => 
        array (
            0 => __DIR__ . '/..' . '/illuminate/contracts',
        ),
        'Illuminate\\Container\\' => 
        array (
            0 => __DIR__ . '/..' . '/illuminate/container',
        ),
        'Fidry\\CpuCoreCounter\\' => 
        array (
            0 => __DIR__ . '/..' . '/fidry/cpu-core-counter/src',
        ),
        'Composer\\XdebugHandler\\' => 
        array (
            0 => __DIR__ . '/..' . '/composer/xdebug-handler/src',
        ),
        'Composer\\Spdx\\' => 
        array (
            0 => __DIR__ . '/..' . '/composer/spdx-licenses/src',
        ),
        'Composer\\Semver\\' => 
        array (
            0 => __DIR__ . '/..' . '/composer/semver/src',
        ),
        'Composer\\Pcre\\' => 
        array (
            0 => __DIR__ . '/..' . '/composer/pcre/src',
        ),
        'Composer\\MetadataMinifier\\' => 
        array (
            0 => __DIR__ . '/..' . '/composer/metadata-minifier/src',
        ),
        'Composer\\ClassMapGenerator\\' => 
        array (
            0 => __DIR__ . '/..' . '/composer/class-map-generator/src',
        ),
        'Composer\\CaBundle\\' => 
        array (
            0 => __DIR__ . '/..' . '/composer/ca-bundle/src',
        ),
        'Composer\\' => 
        array (
            0 => __DIR__ . '/..' . '/composer/composer/src/Composer',
        ),
    );

    public static $classMap = array (
        'Composer\\Advisory\\Auditor' => __DIR__ . '/..' . '/composer/composer/src/Composer/Advisory/Auditor.php',
        'Composer\\Advisory\\IgnoredSecurityAdvisory' => __DIR__ . '/..' . '/composer/composer/src/Composer/Advisory/IgnoredSecurityAdvisory.php',
        'Composer\\Advisory\\PartialSecurityAdvisory' => __DIR__ . '/..' . '/composer/composer/src/Composer/Advisory/PartialSecurityAdvisory.php',
        'Composer\\Advisory\\SecurityAdvisory' => __DIR__ . '/..' . '/composer/composer/src/Composer/Advisory/SecurityAdvisory.php',
        'Composer\\Autoload\\AutoloadGenerator' => __DIR__ . '/..' . '/composer/composer/src/Composer/Autoload/AutoloadGenerator.php',
        'Composer\\Autoload\\ClassLoader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Autoload/ClassLoader.php',
        'Composer\\Autoload\\ClassMapGenerator' => __DIR__ . '/..' . '/composer/composer/src/Composer/Autoload/ClassMapGenerator.php',
        'Composer\\CaBundle\\CaBundle' => __DIR__ . '/..' . '/composer/ca-bundle/src/CaBundle.php',
        'Composer\\Cache' => __DIR__ . '/..' . '/composer/composer/src/Composer/Cache.php',
        'Composer\\ClassMapGenerator\\ClassMap' => __DIR__ . '/..' . '/composer/class-map-generator/src/ClassMap.php',
        'Composer\\ClassMapGenerator\\ClassMapGenerator' => __DIR__ . '/..' . '/composer/class-map-generator/src/ClassMapGenerator.php',
        'Composer\\ClassMapGenerator\\FileList' => __DIR__ . '/..' . '/composer/class-map-generator/src/FileList.php',
        'Composer\\ClassMapGenerator\\PhpFileCleaner' => __DIR__ . '/..' . '/composer/class-map-generator/src/PhpFileCleaner.php',
        'Composer\\ClassMapGenerator\\PhpFileParser' => __DIR__ . '/..' . '/composer/class-map-generator/src/PhpFileParser.php',
        'Composer\\Command\\AboutCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/AboutCommand.php',
        'Composer\\Command\\ArchiveCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ArchiveCommand.php',
        'Composer\\Command\\AuditCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/AuditCommand.php',
        'Composer\\Command\\BaseCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/BaseCommand.php',
        'Composer\\Command\\BaseDependencyCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/BaseDependencyCommand.php',
        'Composer\\Command\\BumpCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/BumpCommand.php',
        'Composer\\Command\\CheckPlatformReqsCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/CheckPlatformReqsCommand.php',
        'Composer\\Command\\ClearCacheCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ClearCacheCommand.php',
        'Composer\\Command\\CompletionTrait' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/CompletionTrait.php',
        'Composer\\Command\\ConfigCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ConfigCommand.php',
        'Composer\\Command\\CreateProjectCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/CreateProjectCommand.php',
        'Composer\\Command\\DependsCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/DependsCommand.php',
        'Composer\\Command\\DiagnoseCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/DiagnoseCommand.php',
        'Composer\\Command\\DumpAutoloadCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/DumpAutoloadCommand.php',
        'Composer\\Command\\ExecCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ExecCommand.php',
        'Composer\\Command\\FundCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/FundCommand.php',
        'Composer\\Command\\GlobalCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/GlobalCommand.php',
        'Composer\\Command\\HomeCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/HomeCommand.php',
        'Composer\\Command\\InitCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/InitCommand.php',
        'Composer\\Command\\InstallCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/InstallCommand.php',
        'Composer\\Command\\LicensesCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/LicensesCommand.php',
        'Composer\\Command\\OutdatedCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/OutdatedCommand.php',
        'Composer\\Command\\PackageDiscoveryTrait' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/PackageDiscoveryTrait.php',
        'Composer\\Command\\ProhibitsCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ProhibitsCommand.php',
        'Composer\\Command\\ReinstallCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ReinstallCommand.php',
        'Composer\\Command\\RemoveCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/RemoveCommand.php',
        'Composer\\Command\\RequireCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/RequireCommand.php',
        'Composer\\Command\\RunScriptCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/RunScriptCommand.php',
        'Composer\\Command\\ScriptAliasCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ScriptAliasCommand.php',
        'Composer\\Command\\SearchCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/SearchCommand.php',
        'Composer\\Command\\SelfUpdateCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/SelfUpdateCommand.php',
        'Composer\\Command\\ShowCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ShowCommand.php',
        'Composer\\Command\\StatusCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/StatusCommand.php',
        'Composer\\Command\\SuggestsCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/SuggestsCommand.php',
        'Composer\\Command\\UpdateCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/UpdateCommand.php',
        'Composer\\Command\\ValidateCommand' => __DIR__ . '/..' . '/composer/composer/src/Composer/Command/ValidateCommand.php',
        'Composer\\Compiler' => __DIR__ . '/..' . '/composer/composer/src/Composer/Compiler.php',
        'Composer\\Composer' => __DIR__ . '/..' . '/composer/composer/src/Composer/Composer.php',
        'Composer\\Config' => __DIR__ . '/..' . '/composer/composer/src/Composer/Config.php',
        'Composer\\Config\\ConfigSourceInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Config/ConfigSourceInterface.php',
        'Composer\\Config\\JsonConfigSource' => __DIR__ . '/..' . '/composer/composer/src/Composer/Config/JsonConfigSource.php',
        'Composer\\Console\\Application' => __DIR__ . '/..' . '/composer/composer/src/Composer/Console/Application.php',
        'Composer\\Console\\GithubActionError' => __DIR__ . '/..' . '/composer/composer/src/Composer/Console/GithubActionError.php',
        'Composer\\Console\\HtmlOutputFormatter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Console/HtmlOutputFormatter.php',
        'Composer\\Console\\Input\\InputArgument' => __DIR__ . '/..' . '/composer/composer/src/Composer/Console/Input/InputArgument.php',
        'Composer\\Console\\Input\\InputOption' => __DIR__ . '/..' . '/composer/composer/src/Composer/Console/Input/InputOption.php',
        'Composer\\DependencyResolver\\Decisions' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Decisions.php',
        'Composer\\DependencyResolver\\DefaultPolicy' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/DefaultPolicy.php',
        'Composer\\DependencyResolver\\GenericRule' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/GenericRule.php',
        'Composer\\DependencyResolver\\LocalRepoTransaction' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/LocalRepoTransaction.php',
        'Composer\\DependencyResolver\\LockTransaction' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/LockTransaction.php',
        'Composer\\DependencyResolver\\MultiConflictRule' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/MultiConflictRule.php',
        'Composer\\DependencyResolver\\Operation\\InstallOperation' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Operation/InstallOperation.php',
        'Composer\\DependencyResolver\\Operation\\MarkAliasInstalledOperation' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php',
        'Composer\\DependencyResolver\\Operation\\MarkAliasUninstalledOperation' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php',
        'Composer\\DependencyResolver\\Operation\\OperationInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Operation/OperationInterface.php',
        'Composer\\DependencyResolver\\Operation\\SolverOperation' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Operation/SolverOperation.php',
        'Composer\\DependencyResolver\\Operation\\UninstallOperation' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Operation/UninstallOperation.php',
        'Composer\\DependencyResolver\\Operation\\UpdateOperation' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Operation/UpdateOperation.php',
        'Composer\\DependencyResolver\\PolicyInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/PolicyInterface.php',
        'Composer\\DependencyResolver\\Pool' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Pool.php',
        'Composer\\DependencyResolver\\PoolBuilder' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/PoolBuilder.php',
        'Composer\\DependencyResolver\\PoolOptimizer' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/PoolOptimizer.php',
        'Composer\\DependencyResolver\\Problem' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Problem.php',
        'Composer\\DependencyResolver\\Request' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Request.php',
        'Composer\\DependencyResolver\\Rule' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Rule.php',
        'Composer\\DependencyResolver\\Rule2Literals' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Rule2Literals.php',
        'Composer\\DependencyResolver\\RuleSet' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/RuleSet.php',
        'Composer\\DependencyResolver\\RuleSetGenerator' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/RuleSetGenerator.php',
        'Composer\\DependencyResolver\\RuleSetIterator' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/RuleSetIterator.php',
        'Composer\\DependencyResolver\\RuleWatchChain' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/RuleWatchChain.php',
        'Composer\\DependencyResolver\\RuleWatchGraph' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/RuleWatchGraph.php',
        'Composer\\DependencyResolver\\RuleWatchNode' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/RuleWatchNode.php',
        'Composer\\DependencyResolver\\Solver' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Solver.php',
        'Composer\\DependencyResolver\\SolverBugException' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/SolverBugException.php',
        'Composer\\DependencyResolver\\SolverProblemsException' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/SolverProblemsException.php',
        'Composer\\DependencyResolver\\Transaction' => __DIR__ . '/..' . '/composer/composer/src/Composer/DependencyResolver/Transaction.php',
        'Composer\\Downloader\\ArchiveDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/ArchiveDownloader.php',
        'Composer\\Downloader\\ChangeReportInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/ChangeReportInterface.php',
        'Composer\\Downloader\\DownloadManager' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/DownloadManager.php',
        'Composer\\Downloader\\DownloaderInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/DownloaderInterface.php',
        'Composer\\Downloader\\DvcsDownloaderInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/DvcsDownloaderInterface.php',
        'Composer\\Downloader\\FileDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/FileDownloader.php',
        'Composer\\Downloader\\FilesystemException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/FilesystemException.php',
        'Composer\\Downloader\\FossilDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/FossilDownloader.php',
        'Composer\\Downloader\\GitDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/GitDownloader.php',
        'Composer\\Downloader\\GzipDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/GzipDownloader.php',
        'Composer\\Downloader\\HgDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/HgDownloader.php',
        'Composer\\Downloader\\MaxFileSizeExceededException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/MaxFileSizeExceededException.php',
        'Composer\\Downloader\\PathDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/PathDownloader.php',
        'Composer\\Downloader\\PerforceDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/PerforceDownloader.php',
        'Composer\\Downloader\\PharDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/PharDownloader.php',
        'Composer\\Downloader\\RarDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/RarDownloader.php',
        'Composer\\Downloader\\SvnDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/SvnDownloader.php',
        'Composer\\Downloader\\TarDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/TarDownloader.php',
        'Composer\\Downloader\\TransportException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/TransportException.php',
        'Composer\\Downloader\\VcsCapableDownloaderInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/VcsCapableDownloaderInterface.php',
        'Composer\\Downloader\\VcsDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/VcsDownloader.php',
        'Composer\\Downloader\\XzDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/XzDownloader.php',
        'Composer\\Downloader\\ZipDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Downloader/ZipDownloader.php',
        'Composer\\EventDispatcher\\Event' => __DIR__ . '/..' . '/composer/composer/src/Composer/EventDispatcher/Event.php',
        'Composer\\EventDispatcher\\EventDispatcher' => __DIR__ . '/..' . '/composer/composer/src/Composer/EventDispatcher/EventDispatcher.php',
        'Composer\\EventDispatcher\\EventSubscriberInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/EventDispatcher/EventSubscriberInterface.php',
        'Composer\\EventDispatcher\\ScriptExecutionException' => __DIR__ . '/..' . '/composer/composer/src/Composer/EventDispatcher/ScriptExecutionException.php',
        'Composer\\Exception\\IrrecoverableDownloadException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Exception/IrrecoverableDownloadException.php',
        'Composer\\Exception\\NoSslException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Exception/NoSslException.php',
        'Composer\\Factory' => __DIR__ . '/..' . '/composer/composer/src/Composer/Factory.php',
        'Composer\\Filter\\PlatformRequirementFilter\\IgnoreAllPlatformRequirementFilter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreAllPlatformRequirementFilter.php',
        'Composer\\Filter\\PlatformRequirementFilter\\IgnoreListPlatformRequirementFilter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilter.php',
        'Composer\\Filter\\PlatformRequirementFilter\\IgnoreNothingPlatformRequirementFilter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilter.php',
        'Composer\\Filter\\PlatformRequirementFilter\\PlatformRequirementFilterFactory' => __DIR__ . '/..' . '/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterFactory.php',
        'Composer\\Filter\\PlatformRequirementFilter\\PlatformRequirementFilterInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterInterface.php',
        'Composer\\IO\\BaseIO' => __DIR__ . '/..' . '/composer/composer/src/Composer/IO/BaseIO.php',
        'Composer\\IO\\BufferIO' => __DIR__ . '/..' . '/composer/composer/src/Composer/IO/BufferIO.php',
        'Composer\\IO\\ConsoleIO' => __DIR__ . '/..' . '/composer/composer/src/Composer/IO/ConsoleIO.php',
        'Composer\\IO\\IOInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/IO/IOInterface.php',
        'Composer\\IO\\NullIO' => __DIR__ . '/..' . '/composer/composer/src/Composer/IO/NullIO.php',
        'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
        'Composer\\Installer' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer.php',
        'Composer\\Installer\\BinaryInstaller' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/BinaryInstaller.php',
        'Composer\\Installer\\BinaryPresenceInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/BinaryPresenceInterface.php',
        'Composer\\Installer\\InstallationManager' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/InstallationManager.php',
        'Composer\\Installer\\InstallerEvent' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/InstallerEvent.php',
        'Composer\\Installer\\InstallerEvents' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/InstallerEvents.php',
        'Composer\\Installer\\InstallerInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/InstallerInterface.php',
        'Composer\\Installer\\LibraryInstaller' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/LibraryInstaller.php',
        'Composer\\Installer\\MetapackageInstaller' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/MetapackageInstaller.php',
        'Composer\\Installer\\NoopInstaller' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/NoopInstaller.php',
        'Composer\\Installer\\PackageEvent' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/PackageEvent.php',
        'Composer\\Installer\\PackageEvents' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/PackageEvents.php',
        'Composer\\Installer\\PluginInstaller' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/PluginInstaller.php',
        'Composer\\Installer\\ProjectInstaller' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/ProjectInstaller.php',
        'Composer\\Installer\\SuggestedPackagesReporter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Installer/SuggestedPackagesReporter.php',
        'Composer\\Json\\JsonFile' => __DIR__ . '/..' . '/composer/composer/src/Composer/Json/JsonFile.php',
        'Composer\\Json\\JsonFormatter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Json/JsonFormatter.php',
        'Composer\\Json\\JsonManipulator' => __DIR__ . '/..' . '/composer/composer/src/Composer/Json/JsonManipulator.php',
        'Composer\\Json\\JsonValidationException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Json/JsonValidationException.php',
        'Composer\\MetadataMinifier\\MetadataMinifier' => __DIR__ . '/..' . '/composer/metadata-minifier/src/MetadataMinifier.php',
        'Composer\\PHPStan\\ConfigReturnTypeExtension' => __DIR__ . '/..' . '/composer/composer/src/Composer/PHPStan/ConfigReturnTypeExtension.php',
        'Composer\\PHPStan\\RuleReasonDataReturnTypeExtension' => __DIR__ . '/..' . '/composer/composer/src/Composer/PHPStan/RuleReasonDataReturnTypeExtension.php',
        'Composer\\Package\\AliasPackage' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/AliasPackage.php',
        'Composer\\Package\\Archiver\\ArchivableFilesFilter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFilter.php',
        'Composer\\Package\\Archiver\\ArchivableFilesFinder' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFinder.php',
        'Composer\\Package\\Archiver\\ArchiveManager' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/ArchiveManager.php',
        'Composer\\Package\\Archiver\\ArchiverInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/ArchiverInterface.php',
        'Composer\\Package\\Archiver\\BaseExcludeFilter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/BaseExcludeFilter.php',
        'Composer\\Package\\Archiver\\ComposerExcludeFilter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/ComposerExcludeFilter.php',
        'Composer\\Package\\Archiver\\GitExcludeFilter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/GitExcludeFilter.php',
        'Composer\\Package\\Archiver\\PharArchiver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/PharArchiver.php',
        'Composer\\Package\\Archiver\\ZipArchiver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Archiver/ZipArchiver.php',
        'Composer\\Package\\BasePackage' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/BasePackage.php',
        'Composer\\Package\\Comparer\\Comparer' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Comparer/Comparer.php',
        'Composer\\Package\\CompleteAliasPackage' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/CompleteAliasPackage.php',
        'Composer\\Package\\CompletePackage' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/CompletePackage.php',
        'Composer\\Package\\CompletePackageInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/CompletePackageInterface.php',
        'Composer\\Package\\Dumper\\ArrayDumper' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Dumper/ArrayDumper.php',
        'Composer\\Package\\Link' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Link.php',
        'Composer\\Package\\Loader\\ArrayLoader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Loader/ArrayLoader.php',
        'Composer\\Package\\Loader\\InvalidPackageException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Loader/InvalidPackageException.php',
        'Composer\\Package\\Loader\\JsonLoader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Loader/JsonLoader.php',
        'Composer\\Package\\Loader\\LoaderInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Loader/LoaderInterface.php',
        'Composer\\Package\\Loader\\RootPackageLoader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Loader/RootPackageLoader.php',
        'Composer\\Package\\Loader\\ValidatingArrayLoader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Loader/ValidatingArrayLoader.php',
        'Composer\\Package\\Locker' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Locker.php',
        'Composer\\Package\\Package' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Package.php',
        'Composer\\Package\\PackageInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/PackageInterface.php',
        'Composer\\Package\\RootAliasPackage' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/RootAliasPackage.php',
        'Composer\\Package\\RootPackage' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/RootPackage.php',
        'Composer\\Package\\RootPackageInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/RootPackageInterface.php',
        'Composer\\Package\\Version\\StabilityFilter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Version/StabilityFilter.php',
        'Composer\\Package\\Version\\VersionBumper' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Version/VersionBumper.php',
        'Composer\\Package\\Version\\VersionGuesser' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Version/VersionGuesser.php',
        'Composer\\Package\\Version\\VersionParser' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Version/VersionParser.php',
        'Composer\\Package\\Version\\VersionSelector' => __DIR__ . '/..' . '/composer/composer/src/Composer/Package/Version/VersionSelector.php',
        'Composer\\PartialComposer' => __DIR__ . '/..' . '/composer/composer/src/Composer/PartialComposer.php',
        'Composer\\Pcre\\MatchAllResult' => __DIR__ . '/..' . '/composer/pcre/src/MatchAllResult.php',
        'Composer\\Pcre\\MatchAllStrictGroupsResult' => __DIR__ . '/..' . '/composer/pcre/src/MatchAllStrictGroupsResult.php',
        'Composer\\Pcre\\MatchAllWithOffsetsResult' => __DIR__ . '/..' . '/composer/pcre/src/MatchAllWithOffsetsResult.php',
        'Composer\\Pcre\\MatchResult' => __DIR__ . '/..' . '/composer/pcre/src/MatchResult.php',
        'Composer\\Pcre\\MatchStrictGroupsResult' => __DIR__ . '/..' . '/composer/pcre/src/MatchStrictGroupsResult.php',
        'Composer\\Pcre\\MatchWithOffsetsResult' => __DIR__ . '/..' . '/composer/pcre/src/MatchWithOffsetsResult.php',
        'Composer\\Pcre\\PHPStan\\InvalidRegexPatternRule' => __DIR__ . '/..' . '/composer/pcre/src/PHPStan/InvalidRegexPatternRule.php',
        'Composer\\Pcre\\PHPStan\\PregMatchFlags' => __DIR__ . '/..' . '/composer/pcre/src/PHPStan/PregMatchFlags.php',
        'Composer\\Pcre\\PHPStan\\PregMatchParameterOutTypeExtension' => __DIR__ . '/..' . '/composer/pcre/src/PHPStan/PregMatchParameterOutTypeExtension.php',
        'Composer\\Pcre\\PHPStan\\PregMatchTypeSpecifyingExtension' => __DIR__ . '/..' . '/composer/pcre/src/PHPStan/PregMatchTypeSpecifyingExtension.php',
        'Composer\\Pcre\\PHPStan\\PregReplaceCallbackClosureTypeExtension' => __DIR__ . '/..' . '/composer/pcre/src/PHPStan/PregReplaceCallbackClosureTypeExtension.php',
        'Composer\\Pcre\\PHPStan\\UnsafeStrictGroupsCallRule' => __DIR__ . '/..' . '/composer/pcre/src/PHPStan/UnsafeStrictGroupsCallRule.php',
        'Composer\\Pcre\\PcreException' => __DIR__ . '/..' . '/composer/pcre/src/PcreException.php',
        'Composer\\Pcre\\Preg' => __DIR__ . '/..' . '/composer/pcre/src/Preg.php',
        'Composer\\Pcre\\Regex' => __DIR__ . '/..' . '/composer/pcre/src/Regex.php',
        'Composer\\Pcre\\ReplaceResult' => __DIR__ . '/..' . '/composer/pcre/src/ReplaceResult.php',
        'Composer\\Pcre\\UnexpectedNullMatchException' => __DIR__ . '/..' . '/composer/pcre/src/UnexpectedNullMatchException.php',
        'Composer\\Platform\\HhvmDetector' => __DIR__ . '/..' . '/composer/composer/src/Composer/Platform/HhvmDetector.php',
        'Composer\\Platform\\Runtime' => __DIR__ . '/..' . '/composer/composer/src/Composer/Platform/Runtime.php',
        'Composer\\Platform\\Version' => __DIR__ . '/..' . '/composer/composer/src/Composer/Platform/Version.php',
        'Composer\\Plugin\\Capability\\Capability' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/Capability/Capability.php',
        'Composer\\Plugin\\Capability\\CommandProvider' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/Capability/CommandProvider.php',
        'Composer\\Plugin\\Capable' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/Capable.php',
        'Composer\\Plugin\\CommandEvent' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/CommandEvent.php',
        'Composer\\Plugin\\PluginBlockedException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/PluginBlockedException.php',
        'Composer\\Plugin\\PluginEvents' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/PluginEvents.php',
        'Composer\\Plugin\\PluginInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/PluginInterface.php',
        'Composer\\Plugin\\PluginManager' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/PluginManager.php',
        'Composer\\Plugin\\PostFileDownloadEvent' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/PostFileDownloadEvent.php',
        'Composer\\Plugin\\PreCommandRunEvent' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/PreCommandRunEvent.php',
        'Composer\\Plugin\\PreFileDownloadEvent' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/PreFileDownloadEvent.php',
        'Composer\\Plugin\\PrePoolCreateEvent' => __DIR__ . '/..' . '/composer/composer/src/Composer/Plugin/PrePoolCreateEvent.php',
        'Composer\\Question\\StrictConfirmationQuestion' => __DIR__ . '/..' . '/composer/composer/src/Composer/Question/StrictConfirmationQuestion.php',
        'Composer\\Repository\\AdvisoryProviderInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/AdvisoryProviderInterface.php',
        'Composer\\Repository\\ArrayRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/ArrayRepository.php',
        'Composer\\Repository\\ArtifactRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/ArtifactRepository.php',
        'Composer\\Repository\\CanonicalPackagesTrait' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/CanonicalPackagesTrait.php',
        'Composer\\Repository\\ComposerRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/ComposerRepository.php',
        'Composer\\Repository\\CompositeRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/CompositeRepository.php',
        'Composer\\Repository\\ConfigurableRepositoryInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/ConfigurableRepositoryInterface.php',
        'Composer\\Repository\\FilesystemRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/FilesystemRepository.php',
        'Composer\\Repository\\FilterRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/FilterRepository.php',
        'Composer\\Repository\\InstalledArrayRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/InstalledArrayRepository.php',
        'Composer\\Repository\\InstalledFilesystemRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/InstalledFilesystemRepository.php',
        'Composer\\Repository\\InstalledRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/InstalledRepository.php',
        'Composer\\Repository\\InstalledRepositoryInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/InstalledRepositoryInterface.php',
        'Composer\\Repository\\InvalidRepositoryException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/InvalidRepositoryException.php',
        'Composer\\Repository\\LockArrayRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/LockArrayRepository.php',
        'Composer\\Repository\\PackageRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/PackageRepository.php',
        'Composer\\Repository\\PathRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/PathRepository.php',
        'Composer\\Repository\\PearRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/PearRepository.php',
        'Composer\\Repository\\PlatformRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/PlatformRepository.php',
        'Composer\\Repository\\RepositoryFactory' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/RepositoryFactory.php',
        'Composer\\Repository\\RepositoryInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/RepositoryInterface.php',
        'Composer\\Repository\\RepositoryManager' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/RepositoryManager.php',
        'Composer\\Repository\\RepositorySecurityException' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/RepositorySecurityException.php',
        'Composer\\Repository\\RepositorySet' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/RepositorySet.php',
        'Composer\\Repository\\RepositoryUtils' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/RepositoryUtils.php',
        'Composer\\Repository\\RootPackageRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/RootPackageRepository.php',
        'Composer\\Repository\\VcsRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/VcsRepository.php',
        'Composer\\Repository\\Vcs\\FossilDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/FossilDriver.php',
        'Composer\\Repository\\Vcs\\GitBitbucketDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/GitBitbucketDriver.php',
        'Composer\\Repository\\Vcs\\GitDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/GitDriver.php',
        'Composer\\Repository\\Vcs\\GitHubDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/GitHubDriver.php',
        'Composer\\Repository\\Vcs\\GitLabDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/GitLabDriver.php',
        'Composer\\Repository\\Vcs\\HgDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/HgDriver.php',
        'Composer\\Repository\\Vcs\\PerforceDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/PerforceDriver.php',
        'Composer\\Repository\\Vcs\\SvnDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/SvnDriver.php',
        'Composer\\Repository\\Vcs\\VcsDriver' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/VcsDriver.php',
        'Composer\\Repository\\Vcs\\VcsDriverInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/Vcs/VcsDriverInterface.php',
        'Composer\\Repository\\VersionCacheInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/VersionCacheInterface.php',
        'Composer\\Repository\\WritableArrayRepository' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/WritableArrayRepository.php',
        'Composer\\Repository\\WritableRepositoryInterface' => __DIR__ . '/..' . '/composer/composer/src/Composer/Repository/WritableRepositoryInterface.php',
        'Composer\\Script\\Event' => __DIR__ . '/..' . '/composer/composer/src/Composer/Script/Event.php',
        'Composer\\Script\\ScriptEvents' => __DIR__ . '/..' . '/composer/composer/src/Composer/Script/ScriptEvents.php',
        'Composer\\SelfUpdate\\Keys' => __DIR__ . '/..' . '/composer/composer/src/Composer/SelfUpdate/Keys.php',
        'Composer\\SelfUpdate\\Versions' => __DIR__ . '/..' . '/composer/composer/src/Composer/SelfUpdate/Versions.php',
        'Composer\\Semver\\Comparator' => __DIR__ . '/..' . '/composer/semver/src/Comparator.php',
        'Composer\\Semver\\CompilingMatcher' => __DIR__ . '/..' . '/composer/semver/src/CompilingMatcher.php',
        'Composer\\Semver\\Constraint\\Bound' => __DIR__ . '/..' . '/composer/semver/src/Constraint/Bound.php',
        'Composer\\Semver\\Constraint\\Constraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/Constraint.php',
        'Composer\\Semver\\Constraint\\ConstraintInterface' => __DIR__ . '/..' . '/composer/semver/src/Constraint/ConstraintInterface.php',
        'Composer\\Semver\\Constraint\\MatchAllConstraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/MatchAllConstraint.php',
        'Composer\\Semver\\Constraint\\MatchNoneConstraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/MatchNoneConstraint.php',
        'Composer\\Semver\\Constraint\\MultiConstraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/MultiConstraint.php',
        'Composer\\Semver\\Interval' => __DIR__ . '/..' . '/composer/semver/src/Interval.php',
        'Composer\\Semver\\Intervals' => __DIR__ . '/..' . '/composer/semver/src/Intervals.php',
        'Composer\\Semver\\Semver' => __DIR__ . '/..' . '/composer/semver/src/Semver.php',
        'Composer\\Semver\\VersionParser' => __DIR__ . '/..' . '/composer/semver/src/VersionParser.php',
        'Composer\\Spdx\\SpdxLicenses' => __DIR__ . '/..' . '/composer/spdx-licenses/src/SpdxLicenses.php',
        'Composer\\Util\\AuthHelper' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/AuthHelper.php',
        'Composer\\Util\\Bitbucket' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Bitbucket.php',
        'Composer\\Util\\ComposerMirror' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/ComposerMirror.php',
        'Composer\\Util\\ConfigValidator' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/ConfigValidator.php',
        'Composer\\Util\\ErrorHandler' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/ErrorHandler.php',
        'Composer\\Util\\Filesystem' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Filesystem.php',
        'Composer\\Util\\Git' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Git.php',
        'Composer\\Util\\GitHub' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/GitHub.php',
        'Composer\\Util\\GitLab' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/GitLab.php',
        'Composer\\Util\\Hg' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Hg.php',
        'Composer\\Util\\HttpDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/HttpDownloader.php',
        'Composer\\Util\\Http\\CurlDownloader' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Http/CurlDownloader.php',
        'Composer\\Util\\Http\\CurlResponse' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Http/CurlResponse.php',
        'Composer\\Util\\Http\\ProxyItem' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Http/ProxyItem.php',
        'Composer\\Util\\Http\\ProxyManager' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Http/ProxyManager.php',
        'Composer\\Util\\Http\\RequestProxy' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Http/RequestProxy.php',
        'Composer\\Util\\Http\\Response' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Http/Response.php',
        'Composer\\Util\\IniHelper' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/IniHelper.php',
        'Composer\\Util\\Loop' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Loop.php',
        'Composer\\Util\\MetadataMinifier' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/MetadataMinifier.php',
        'Composer\\Util\\NoProxyPattern' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/NoProxyPattern.php',
        'Composer\\Util\\PackageInfo' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/PackageInfo.php',
        'Composer\\Util\\PackageSorter' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/PackageSorter.php',
        'Composer\\Util\\Perforce' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Perforce.php',
        'Composer\\Util\\Platform' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Platform.php',
        'Composer\\Util\\ProcessExecutor' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/ProcessExecutor.php',
        'Composer\\Util\\RemoteFilesystem' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/RemoteFilesystem.php',
        'Composer\\Util\\Silencer' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Silencer.php',
        'Composer\\Util\\StreamContextFactory' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/StreamContextFactory.php',
        'Composer\\Util\\Svn' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Svn.php',
        'Composer\\Util\\SyncHelper' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/SyncHelper.php',
        'Composer\\Util\\Tar' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Tar.php',
        'Composer\\Util\\TlsHelper' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/TlsHelper.php',
        'Composer\\Util\\Url' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Url.php',
        'Composer\\Util\\Zip' => __DIR__ . '/..' . '/composer/composer/src/Composer/Util/Zip.php',
        'Composer\\XdebugHandler\\PhpConfig' => __DIR__ . '/..' . '/composer/xdebug-handler/src/PhpConfig.php',
        'Composer\\XdebugHandler\\Process' => __DIR__ . '/..' . '/composer/xdebug-handler/src/Process.php',
        'Composer\\XdebugHandler\\Status' => __DIR__ . '/..' . '/composer/xdebug-handler/src/Status.php',
        'Composer\\XdebugHandler\\XdebugHandler' => __DIR__ . '/..' . '/composer/xdebug-handler/src/XdebugHandler.php',
        'Fidry\\CpuCoreCounter\\CpuCoreCounter' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/CpuCoreCounter.php',
        'Fidry\\CpuCoreCounter\\Diagnoser' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Diagnoser.php',
        'Fidry\\CpuCoreCounter\\Executor\\ProcOpenExecutor' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Executor/ProcOpenExecutor.php',
        'Fidry\\CpuCoreCounter\\Executor\\ProcessExecutor' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Executor/ProcessExecutor.php',
        'Fidry\\CpuCoreCounter\\Finder\\CmiCmdletLogicalFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/CmiCmdletLogicalFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\CmiCmdletPhysicalFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/CmiCmdletPhysicalFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\CpuCoreFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/CpuCoreFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\CpuInfoFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/CpuInfoFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\DummyCpuCoreFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/DummyCpuCoreFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\EnvVariableFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/EnvVariableFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\FinderRegistry' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/FinderRegistry.php',
        'Fidry\\CpuCoreCounter\\Finder\\HwLogicalFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/HwLogicalFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\HwPhysicalFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/HwPhysicalFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\LscpuLogicalFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/LscpuLogicalFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\LscpuPhysicalFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/LscpuPhysicalFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\NProcFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/NProcFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\NProcessorFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/NProcessorFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\NullCpuCoreFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/NullCpuCoreFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\OnlyInPowerShellFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/OnlyInPowerShellFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\OnlyOnOSFamilyFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/OnlyOnOSFamilyFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\ProcOpenBasedFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/ProcOpenBasedFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\SkipOnOSFamilyFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/SkipOnOSFamilyFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\WindowsRegistryLogicalFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/WindowsRegistryLogicalFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\WmicLogicalFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/WmicLogicalFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\WmicPhysicalFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/WmicPhysicalFinder.php',
        'Fidry\\CpuCoreCounter\\Finder\\_NProcessorFinder' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/Finder/_NProcessorFinder.php',
        'Fidry\\CpuCoreCounter\\NumberOfCpuCoreNotFound' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/NumberOfCpuCoreNotFound.php',
        'Fidry\\CpuCoreCounter\\ParallelisationResult' => __DIR__ . '/..' . '/fidry/cpu-core-counter/src/ParallelisationResult.php',
        'Illuminate\\Container\\BoundMethod' => __DIR__ . '/..' . '/illuminate/container/BoundMethod.php',
        'Illuminate\\Container\\Container' => __DIR__ . '/..' . '/illuminate/container/Container.php',
        'Illuminate\\Container\\ContextualBindingBuilder' => __DIR__ . '/..' . '/illuminate/container/ContextualBindingBuilder.php',
        'Illuminate\\Container\\EntryNotFoundException' => __DIR__ . '/..' . '/illuminate/container/EntryNotFoundException.php',
        'Illuminate\\Container\\RewindableGenerator' => __DIR__ . '/..' . '/illuminate/container/RewindableGenerator.php',
        'Illuminate\\Container\\Util' => __DIR__ . '/..' . '/illuminate/container/Util.php',
        'Illuminate\\Contracts\\Auth\\Access\\Authorizable' => __DIR__ . '/..' . '/illuminate/contracts/Auth/Access/Authorizable.php',
        'Illuminate\\Contracts\\Auth\\Access\\Gate' => __DIR__ . '/..' . '/illuminate/contracts/Auth/Access/Gate.php',
        'Illuminate\\Contracts\\Auth\\Authenticatable' => __DIR__ . '/..' . '/illuminate/contracts/Auth/Authenticatable.php',
        'Illuminate\\Contracts\\Auth\\CanResetPassword' => __DIR__ . '/..' . '/illuminate/contracts/Auth/CanResetPassword.php',
        'Illuminate\\Contracts\\Auth\\Factory' => __DIR__ . '/..' . '/illuminate/contracts/Auth/Factory.php',
        'Illuminate\\Contracts\\Auth\\Guard' => __DIR__ . '/..' . '/illuminate/contracts/Auth/Guard.php',
        'Illuminate\\Contracts\\Auth\\Middleware\\AuthenticatesRequests' => __DIR__ . '/..' . '/illuminate/contracts/Auth/Middleware/AuthenticatesRequests.php',
        'Illuminate\\Contracts\\Auth\\MustVerifyEmail' => __DIR__ . '/..' . '/illuminate/contracts/Auth/MustVerifyEmail.php',
        'Illuminate\\Contracts\\Auth\\PasswordBroker' => __DIR__ . '/..' . '/illuminate/contracts/Auth/PasswordBroker.php',
        'Illuminate\\Contracts\\Auth\\PasswordBrokerFactory' => __DIR__ . '/..' . '/illuminate/contracts/Auth/PasswordBrokerFactory.php',
        'Illuminate\\Contracts\\Auth\\StatefulGuard' => __DIR__ . '/..' . '/illuminate/contracts/Auth/StatefulGuard.php',
        'Illuminate\\Contracts\\Auth\\SupportsBasicAuth' => __DIR__ . '/..' . '/illuminate/contracts/Auth/SupportsBasicAuth.php',
        'Illuminate\\Contracts\\Auth\\UserProvider' => __DIR__ . '/..' . '/illuminate/contracts/Auth/UserProvider.php',
        'Illuminate\\Contracts\\Broadcasting\\Broadcaster' => __DIR__ . '/..' . '/illuminate/contracts/Broadcasting/Broadcaster.php',
        'Illuminate\\Contracts\\Broadcasting\\Factory' => __DIR__ . '/..' . '/illuminate/contracts/Broadcasting/Factory.php',
        'Illuminate\\Contracts\\Broadcasting\\HasBroadcastChannel' => __DIR__ . '/..' . '/illuminate/contracts/Broadcasting/HasBroadcastChannel.php',
        'Illuminate\\Contracts\\Broadcasting\\ShouldBeUnique' => __DIR__ . '/..' . '/illuminate/contracts/Broadcasting/ShouldBeUnique.php',
        'Illuminate\\Contracts\\Broadcasting\\ShouldBroadcast' => __DIR__ . '/..' . '/illuminate/contracts/Broadcasting/ShouldBroadcast.php',
        'Illuminate\\Contracts\\Broadcasting\\ShouldBroadcastNow' => __DIR__ . '/..' . '/illuminate/contracts/Broadcasting/ShouldBroadcastNow.php',
        'Illuminate\\Contracts\\Bus\\Dispatcher' => __DIR__ . '/..' . '/illuminate/contracts/Bus/Dispatcher.php',
        'Illuminate\\Contracts\\Bus\\QueueingDispatcher' => __DIR__ . '/..' . '/illuminate/contracts/Bus/QueueingDispatcher.php',
        'Illuminate\\Contracts\\Cache\\Factory' => __DIR__ . '/..' . '/illuminate/contracts/Cache/Factory.php',
        'Illuminate\\Contracts\\Cache\\Lock' => __DIR__ . '/..' . '/illuminate/contracts/Cache/Lock.php',
        'Illuminate\\Contracts\\Cache\\LockProvider' => __DIR__ . '/..' . '/illuminate/contracts/Cache/LockProvider.php',
        'Illuminate\\Contracts\\Cache\\LockTimeoutException' => __DIR__ . '/..' . '/illuminate/contracts/Cache/LockTimeoutException.php',
        'Illuminate\\Contracts\\Cache\\Repository' => __DIR__ . '/..' . '/illuminate/contracts/Cache/Repository.php',
        'Illuminate\\Contracts\\Cache\\Store' => __DIR__ . '/..' . '/illuminate/contracts/Cache/Store.php',
        'Illuminate\\Contracts\\Config\\Repository' => __DIR__ . '/..' . '/illuminate/contracts/Config/Repository.php',
        'Illuminate\\Contracts\\Console\\Application' => __DIR__ . '/..' . '/illuminate/contracts/Console/Application.php',
        'Illuminate\\Contracts\\Console\\Isolatable' => __DIR__ . '/..' . '/illuminate/contracts/Console/Isolatable.php',
        'Illuminate\\Contracts\\Console\\Kernel' => __DIR__ . '/..' . '/illuminate/contracts/Console/Kernel.php',
        'Illuminate\\Contracts\\Console\\PromptsForMissingInput' => __DIR__ . '/..' . '/illuminate/contracts/Console/PromptsForMissingInput.php',
        'Illuminate\\Contracts\\Container\\BindingResolutionException' => __DIR__ . '/..' . '/illuminate/contracts/Container/BindingResolutionException.php',
        'Illuminate\\Contracts\\Container\\CircularDependencyException' => __DIR__ . '/..' . '/illuminate/contracts/Container/CircularDependencyException.php',
        'Illuminate\\Contracts\\Container\\Container' => __DIR__ . '/..' . '/illuminate/contracts/Container/Container.php',
        'Illuminate\\Contracts\\Container\\ContextualBindingBuilder' => __DIR__ . '/..' . '/illuminate/contracts/Container/ContextualBindingBuilder.php',
        'Illuminate\\Contracts\\Cookie\\Factory' => __DIR__ . '/..' . '/illuminate/contracts/Cookie/Factory.php',
        'Illuminate\\Contracts\\Cookie\\QueueingFactory' => __DIR__ . '/..' . '/illuminate/contracts/Cookie/QueueingFactory.php',
        'Illuminate\\Contracts\\Database\\Eloquent\\Builder' => __DIR__ . '/..' . '/illuminate/contracts/Database/Eloquent/Builder.php',
        'Illuminate\\Contracts\\Database\\Eloquent\\Castable' => __DIR__ . '/..' . '/illuminate/contracts/Database/Eloquent/Castable.php',
        'Illuminate\\Contracts\\Database\\Eloquent\\CastsAttributes' => __DIR__ . '/..' . '/illuminate/contracts/Database/Eloquent/CastsAttributes.php',
        'Illuminate\\Contracts\\Database\\Eloquent\\CastsInboundAttributes' => __DIR__ . '/..' . '/illuminate/contracts/Database/Eloquent/CastsInboundAttributes.php',
        'Illuminate\\Contracts\\Database\\Eloquent\\DeviatesCastableAttributes' => __DIR__ . '/..' . '/illuminate/contracts/Database/Eloquent/DeviatesCastableAttributes.php',
        'Illuminate\\Contracts\\Database\\Eloquent\\SerializesCastableAttributes' => __DIR__ . '/..' . '/illuminate/contracts/Database/Eloquent/SerializesCastableAttributes.php',
        'Illuminate\\Contracts\\Database\\Eloquent\\SupportsPartialRelations' => __DIR__ . '/..' . '/illuminate/contracts/Database/Eloquent/SupportsPartialRelations.php',
        'Illuminate\\Contracts\\Database\\Events\\MigrationEvent' => __DIR__ . '/..' . '/illuminate/contracts/Database/Events/MigrationEvent.php',
        'Illuminate\\Contracts\\Database\\ModelIdentifier' => __DIR__ . '/..' . '/illuminate/contracts/Database/ModelIdentifier.php',
        'Illuminate\\Contracts\\Database\\Query\\Builder' => __DIR__ . '/..' . '/illuminate/contracts/Database/Query/Builder.php',
        'Illuminate\\Contracts\\Database\\Query\\ConditionExpression' => __DIR__ . '/..' . '/illuminate/contracts/Database/Query/ConditionExpression.php',
        'Illuminate\\Contracts\\Database\\Query\\Expression' => __DIR__ . '/..' . '/illuminate/contracts/Database/Query/Expression.php',
        'Illuminate\\Contracts\\Debug\\ExceptionHandler' => __DIR__ . '/..' . '/illuminate/contracts/Debug/ExceptionHandler.php',
        'Illuminate\\Contracts\\Encryption\\DecryptException' => __DIR__ . '/..' . '/illuminate/contracts/Encryption/DecryptException.php',
        'Illuminate\\Contracts\\Encryption\\EncryptException' => __DIR__ . '/..' . '/illuminate/contracts/Encryption/EncryptException.php',
        'Illuminate\\Contracts\\Encryption\\Encrypter' => __DIR__ . '/..' . '/illuminate/contracts/Encryption/Encrypter.php',
        'Illuminate\\Contracts\\Encryption\\StringEncrypter' => __DIR__ . '/..' . '/illuminate/contracts/Encryption/StringEncrypter.php',
        'Illuminate\\Contracts\\Events\\Dispatcher' => __DIR__ . '/..' . '/illuminate/contracts/Events/Dispatcher.php',
        'Illuminate\\Contracts\\Events\\ShouldDispatchAfterCommit' => __DIR__ . '/..' . '/illuminate/contracts/Events/ShouldDispatchAfterCommit.php',
        'Illuminate\\Contracts\\Events\\ShouldHandleEventsAfterCommit' => __DIR__ . '/..' . '/illuminate/contracts/Events/ShouldHandleEventsAfterCommit.php',
        'Illuminate\\Contracts\\Filesystem\\Cloud' => __DIR__ . '/..' . '/illuminate/contracts/Filesystem/Cloud.php',
        'Illuminate\\Contracts\\Filesystem\\Factory' => __DIR__ . '/..' . '/illuminate/contracts/Filesystem/Factory.php',
        'Illuminate\\Contracts\\Filesystem\\FileNotFoundException' => __DIR__ . '/..' . '/illuminate/contracts/Filesystem/FileNotFoundException.php',
        'Illuminate\\Contracts\\Filesystem\\Filesystem' => __DIR__ . '/..' . '/illuminate/contracts/Filesystem/Filesystem.php',
        'Illuminate\\Contracts\\Filesystem\\LockTimeoutException' => __DIR__ . '/..' . '/illuminate/contracts/Filesystem/LockTimeoutException.php',
        'Illuminate\\Contracts\\Foundation\\Application' => __DIR__ . '/..' . '/illuminate/contracts/Foundation/Application.php',
        'Illuminate\\Contracts\\Foundation\\CachesConfiguration' => __DIR__ . '/..' . '/illuminate/contracts/Foundation/CachesConfiguration.php',
        'Illuminate\\Contracts\\Foundation\\CachesRoutes' => __DIR__ . '/..' . '/illuminate/contracts/Foundation/CachesRoutes.php',
        'Illuminate\\Contracts\\Foundation\\ExceptionRenderer' => __DIR__ . '/..' . '/illuminate/contracts/Foundation/ExceptionRenderer.php',
        'Illuminate\\Contracts\\Foundation\\MaintenanceMode' => __DIR__ . '/..' . '/illuminate/contracts/Foundation/MaintenanceMode.php',
        'Illuminate\\Contracts\\Hashing\\Hasher' => __DIR__ . '/..' . '/illuminate/contracts/Hashing/Hasher.php',
        'Illuminate\\Contracts\\Http\\Kernel' => __DIR__ . '/..' . '/illuminate/contracts/Http/Kernel.php',
        'Illuminate\\Contracts\\Mail\\Attachable' => __DIR__ . '/..' . '/illuminate/contracts/Mail/Attachable.php',
        'Illuminate\\Contracts\\Mail\\Factory' => __DIR__ . '/..' . '/illuminate/contracts/Mail/Factory.php',
        'Illuminate\\Contracts\\Mail\\MailQueue' => __DIR__ . '/..' . '/illuminate/contracts/Mail/MailQueue.php',
        'Illuminate\\Contracts\\Mail\\Mailable' => __DIR__ . '/..' . '/illuminate/contracts/Mail/Mailable.php',
        'Illuminate\\Contracts\\Mail\\Mailer' => __DIR__ . '/..' . '/illuminate/contracts/Mail/Mailer.php',
        'Illuminate\\Contracts\\Notifications\\Dispatcher' => __DIR__ . '/..' . '/illuminate/contracts/Notifications/Dispatcher.php',
        'Illuminate\\Contracts\\Notifications\\Factory' => __DIR__ . '/..' . '/illuminate/contracts/Notifications/Factory.php',
        'Illuminate\\Contracts\\Pagination\\CursorPaginator' => __DIR__ . '/..' . '/illuminate/contracts/Pagination/CursorPaginator.php',
        'Illuminate\\Contracts\\Pagination\\LengthAwarePaginator' => __DIR__ . '/..' . '/illuminate/contracts/Pagination/LengthAwarePaginator.php',
        'Illuminate\\Contracts\\Pagination\\Paginator' => __DIR__ . '/..' . '/illuminate/contracts/Pagination/Paginator.php',
        'Illuminate\\Contracts\\Pipeline\\Hub' => __DIR__ . '/..' . '/illuminate/contracts/Pipeline/Hub.php',
        'Illuminate\\Contracts\\Pipeline\\Pipeline' => __DIR__ . '/..' . '/illuminate/contracts/Pipeline/Pipeline.php',
        'Illuminate\\Contracts\\Process\\InvokedProcess' => __DIR__ . '/..' . '/illuminate/contracts/Process/InvokedProcess.php',
        'Illuminate\\Contracts\\Process\\ProcessResult' => __DIR__ . '/..' . '/illuminate/contracts/Process/ProcessResult.php',
        'Illuminate\\Contracts\\Queue\\ClearableQueue' => __DIR__ . '/..' . '/illuminate/contracts/Queue/ClearableQueue.php',
        'Illuminate\\Contracts\\Queue\\EntityNotFoundException' => __DIR__ . '/..' . '/illuminate/contracts/Queue/EntityNotFoundException.php',
        'Illuminate\\Contracts\\Queue\\EntityResolver' => __DIR__ . '/..' . '/illuminate/contracts/Queue/EntityResolver.php',
        'Illuminate\\Contracts\\Queue\\Factory' => __DIR__ . '/..' . '/illuminate/contracts/Queue/Factory.php',
        'Illuminate\\Contracts\\Queue\\Job' => __DIR__ . '/..' . '/illuminate/contracts/Queue/Job.php',
        'Illuminate\\Contracts\\Queue\\Monitor' => __DIR__ . '/..' . '/illuminate/contracts/Queue/Monitor.php',
        'Illuminate\\Contracts\\Queue\\Queue' => __DIR__ . '/..' . '/illuminate/contracts/Queue/Queue.php',
        'Illuminate\\Contracts\\Queue\\QueueableCollection' => __DIR__ . '/..' . '/illuminate/contracts/Queue/QueueableCollection.php',
        'Illuminate\\Contracts\\Queue\\QueueableEntity' => __DIR__ . '/..' . '/illuminate/contracts/Queue/QueueableEntity.php',
        'Illuminate\\Contracts\\Queue\\ShouldBeEncrypted' => __DIR__ . '/..' . '/illuminate/contracts/Queue/ShouldBeEncrypted.php',
        'Illuminate\\Contracts\\Queue\\ShouldBeUnique' => __DIR__ . '/..' . '/illuminate/contracts/Queue/ShouldBeUnique.php',
        'Illuminate\\Contracts\\Queue\\ShouldBeUniqueUntilProcessing' => __DIR__ . '/..' . '/illuminate/contracts/Queue/ShouldBeUniqueUntilProcessing.php',
        'Illuminate\\Contracts\\Queue\\ShouldQueue' => __DIR__ . '/..' . '/illuminate/contracts/Queue/ShouldQueue.php',
        'Illuminate\\Contracts\\Queue\\ShouldQueueAfterCommit' => __DIR__ . '/..' . '/illuminate/contracts/Queue/ShouldQueueAfterCommit.php',
        'Illuminate\\Contracts\\Redis\\Connection' => __DIR__ . '/..' . '/illuminate/contracts/Redis/Connection.php',
        'Illuminate\\Contracts\\Redis\\Connector' => __DIR__ . '/..' . '/illuminate/contracts/Redis/Connector.php',
        'Illuminate\\Contracts\\Redis\\Factory' => __DIR__ . '/..' . '/illuminate/contracts/Redis/Factory.php',
        'Illuminate\\Contracts\\Redis\\LimiterTimeoutException' => __DIR__ . '/..' . '/illuminate/contracts/Redis/LimiterTimeoutException.php',
        'Illuminate\\Contracts\\Routing\\BindingRegistrar' => __DIR__ . '/..' . '/illuminate/contracts/Routing/BindingRegistrar.php',
        'Illuminate\\Contracts\\Routing\\Registrar' => __DIR__ . '/..' . '/illuminate/contracts/Routing/Registrar.php',
        'Illuminate\\Contracts\\Routing\\ResponseFactory' => __DIR__ . '/..' . '/illuminate/contracts/Routing/ResponseFactory.php',
        'Illuminate\\Contracts\\Routing\\UrlGenerator' => __DIR__ . '/..' . '/illuminate/contracts/Routing/UrlGenerator.php',
        'Illuminate\\Contracts\\Routing\\UrlRoutable' => __DIR__ . '/..' . '/illuminate/contracts/Routing/UrlRoutable.php',
        'Illuminate\\Contracts\\Session\\Middleware\\AuthenticatesSessions' => __DIR__ . '/..' . '/illuminate/contracts/Session/Middleware/AuthenticatesSessions.php',
        'Illuminate\\Contracts\\Session\\Session' => __DIR__ . '/..' . '/illuminate/contracts/Session/Session.php',
        'Illuminate\\Contracts\\Support\\Arrayable' => __DIR__ . '/..' . '/illuminate/contracts/Support/Arrayable.php',
        'Illuminate\\Contracts\\Support\\CanBeEscapedWhenCastToString' => __DIR__ . '/..' . '/illuminate/contracts/Support/CanBeEscapedWhenCastToString.php',
        'Illuminate\\Contracts\\Support\\DeferrableProvider' => __DIR__ . '/..' . '/illuminate/contracts/Support/DeferrableProvider.php',
        'Illuminate\\Contracts\\Support\\DeferringDisplayableValue' => __DIR__ . '/..' . '/illuminate/contracts/Support/DeferringDisplayableValue.php',
        'Illuminate\\Contracts\\Support\\Htmlable' => __DIR__ . '/..' . '/illuminate/contracts/Support/Htmlable.php',
        'Illuminate\\Contracts\\Support\\Jsonable' => __DIR__ . '/..' . '/illuminate/contracts/Support/Jsonable.php',
        'Illuminate\\Contracts\\Support\\MessageBag' => __DIR__ . '/..' . '/illuminate/contracts/Support/MessageBag.php',
        'Illuminate\\Contracts\\Support\\MessageProvider' => __DIR__ . '/..' . '/illuminate/contracts/Support/MessageProvider.php',
        'Illuminate\\Contracts\\Support\\Renderable' => __DIR__ . '/..' . '/illuminate/contracts/Support/Renderable.php',
        'Illuminate\\Contracts\\Support\\Responsable' => __DIR__ . '/..' . '/illuminate/contracts/Support/Responsable.php',
        'Illuminate\\Contracts\\Support\\ValidatedData' => __DIR__ . '/..' . '/illuminate/contracts/Support/ValidatedData.php',
        'Illuminate\\Contracts\\Translation\\HasLocalePreference' => __DIR__ . '/..' . '/illuminate/contracts/Translation/HasLocalePreference.php',
        'Illuminate\\Contracts\\Translation\\Loader' => __DIR__ . '/..' . '/illuminate/contracts/Translation/Loader.php',
        'Illuminate\\Contracts\\Translation\\Translator' => __DIR__ . '/..' . '/illuminate/contracts/Translation/Translator.php',
        'Illuminate\\Contracts\\Validation\\DataAwareRule' => __DIR__ . '/..' . '/illuminate/contracts/Validation/DataAwareRule.php',
        'Illuminate\\Contracts\\Validation\\Factory' => __DIR__ . '/..' . '/illuminate/contracts/Validation/Factory.php',
        'Illuminate\\Contracts\\Validation\\ImplicitRule' => __DIR__ . '/..' . '/illuminate/contracts/Validation/ImplicitRule.php',
        'Illuminate\\Contracts\\Validation\\InvokableRule' => __DIR__ . '/..' . '/illuminate/contracts/Validation/InvokableRule.php',
        'Illuminate\\Contracts\\Validation\\Rule' => __DIR__ . '/..' . '/illuminate/contracts/Validation/Rule.php',
        'Illuminate\\Contracts\\Validation\\UncompromisedVerifier' => __DIR__ . '/..' . '/illuminate/contracts/Validation/UncompromisedVerifier.php',
        'Illuminate\\Contracts\\Validation\\ValidatesWhenResolved' => __DIR__ . '/..' . '/illuminate/contracts/Validation/ValidatesWhenResolved.php',
        'Illuminate\\Contracts\\Validation\\ValidationRule' => __DIR__ . '/..' . '/illuminate/contracts/Validation/ValidationRule.php',
        'Illuminate\\Contracts\\Validation\\Validator' => __DIR__ . '/..' . '/illuminate/contracts/Validation/Validator.php',
        'Illuminate\\Contracts\\Validation\\ValidatorAwareRule' => __DIR__ . '/..' . '/illuminate/contracts/Validation/ValidatorAwareRule.php',
        'Illuminate\\Contracts\\View\\Engine' => __DIR__ . '/..' . '/illuminate/contracts/View/Engine.php',
        'Illuminate\\Contracts\\View\\Factory' => __DIR__ . '/..' . '/illuminate/contracts/View/Factory.php',
        'Illuminate\\Contracts\\View\\View' => __DIR__ . '/..' . '/illuminate/contracts/View/View.php',
        'Illuminate\\Contracts\\View\\ViewCompilationException' => __DIR__ . '/..' . '/illuminate/contracts/View/ViewCompilationException.php',
        'JsonSchema\\ConstraintError' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/ConstraintError.php',
        'JsonSchema\\Constraints\\BaseConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/BaseConstraint.php',
        'JsonSchema\\Constraints\\CollectionConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/CollectionConstraint.php',
        'JsonSchema\\Constraints\\ConstConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstConstraint.php',
        'JsonSchema\\Constraints\\Constraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Constraint.php',
        'JsonSchema\\Constraints\\ConstraintInterface' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstraintInterface.php',
        'JsonSchema\\Constraints\\EnumConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/EnumConstraint.php',
        'JsonSchema\\Constraints\\Factory' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.php',
        'JsonSchema\\Constraints\\FormatConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php',
        'JsonSchema\\Constraints\\NumberConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/NumberConstraint.php',
        'JsonSchema\\Constraints\\ObjectConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/ObjectConstraint.php',
        'JsonSchema\\Constraints\\SchemaConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/SchemaConstraint.php',
        'JsonSchema\\Constraints\\StringConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/StringConstraint.php',
        'JsonSchema\\Constraints\\TypeCheck\\LooseTypeCheck' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/LooseTypeCheck.php',
        'JsonSchema\\Constraints\\TypeCheck\\StrictTypeCheck' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/StrictTypeCheck.php',
        'JsonSchema\\Constraints\\TypeCheck\\TypeCheckInterface' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/TypeCheckInterface.php',
        'JsonSchema\\Constraints\\TypeConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeConstraint.php',
        'JsonSchema\\Constraints\\UndefinedConstraint' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Constraints/UndefinedConstraint.php',
        'JsonSchema\\Entity\\JsonPointer' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Entity/JsonPointer.php',
        'JsonSchema\\Enum' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Enum.php',
        'JsonSchema\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/ExceptionInterface.php',
        'JsonSchema\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidArgumentException.php',
        'JsonSchema\\Exception\\InvalidConfigException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidConfigException.php',
        'JsonSchema\\Exception\\InvalidSchemaException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaException.php',
        'JsonSchema\\Exception\\InvalidSchemaMediaTypeException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaMediaTypeException.php',
        'JsonSchema\\Exception\\InvalidSourceUriException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSourceUriException.php',
        'JsonSchema\\Exception\\JsonDecodingException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/JsonDecodingException.php',
        'JsonSchema\\Exception\\ResourceNotFoundException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/ResourceNotFoundException.php',
        'JsonSchema\\Exception\\RuntimeException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/RuntimeException.php',
        'JsonSchema\\Exception\\UnresolvableJsonPointerException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/UnresolvableJsonPointerException.php',
        'JsonSchema\\Exception\\UriResolverException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/UriResolverException.php',
        'JsonSchema\\Exception\\ValidationException' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Exception/ValidationException.php',
        'JsonSchema\\Iterator\\ObjectIterator' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Iterator/ObjectIterator.php',
        'JsonSchema\\Rfc3339' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Rfc3339.php',
        'JsonSchema\\SchemaStorage' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/SchemaStorage.php',
        'JsonSchema\\SchemaStorageInterface' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/SchemaStorageInterface.php',
        'JsonSchema\\Tool\\DeepComparer' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Tool/DeepComparer.php',
        'JsonSchema\\Tool\\DeepCopy' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Tool/DeepCopy.php',
        'JsonSchema\\Tool\\Validator\\RelativeReferenceValidator' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Tool/Validator/RelativeReferenceValidator.php',
        'JsonSchema\\Tool\\Validator\\UriValidator' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Tool/Validator/UriValidator.php',
        'JsonSchema\\UriResolverInterface' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/UriResolverInterface.php',
        'JsonSchema\\UriRetrieverInterface' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/UriRetrieverInterface.php',
        'JsonSchema\\Uri\\Retrievers\\AbstractRetriever' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/AbstractRetriever.php',
        'JsonSchema\\Uri\\Retrievers\\Curl' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/Curl.php',
        'JsonSchema\\Uri\\Retrievers\\FileGetContents' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/FileGetContents.php',
        'JsonSchema\\Uri\\Retrievers\\PredefinedArray' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/PredefinedArray.php',
        'JsonSchema\\Uri\\Retrievers\\UriRetrieverInterface' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/UriRetrieverInterface.php',
        'JsonSchema\\Uri\\UriResolver' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/UriResolver.php',
        'JsonSchema\\Uri\\UriRetriever' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php',
        'JsonSchema\\Validator' => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema/Validator.php',
        'MabeEnum\\Enum' => __DIR__ . '/..' . '/marc-mabe/php-enum/src/Enum.php',
        'MabeEnum\\EnumMap' => __DIR__ . '/..' . '/marc-mabe/php-enum/src/EnumMap.php',
        'MabeEnum\\EnumSerializableTrait' => __DIR__ . '/..' . '/marc-mabe/php-enum/src/EnumSerializableTrait.php',
        'MabeEnum\\EnumSet' => __DIR__ . '/..' . '/marc-mabe/php-enum/src/EnumSet.php',
        'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
        'Php\\Pie\\Building\\Build' => __DIR__ . '/../..' . '/src/Building/Build.php',
        'Php\\Pie\\Building\\ExtensionBinaryNotFound' => __DIR__ . '/../..' . '/src/Building/ExtensionBinaryNotFound.php',
        'Php\\Pie\\Building\\UnixBuild' => __DIR__ . '/../..' . '/src/Building/UnixBuild.php',
        'Php\\Pie\\Building\\WindowsBuild' => __DIR__ . '/../..' . '/src/Building/WindowsBuild.php',
        'Php\\Pie\\Command\\BuildCommand' => __DIR__ . '/../..' . '/src/Command/BuildCommand.php',
        'Php\\Pie\\Command\\CommandHelper' => __DIR__ . '/../..' . '/src/Command/CommandHelper.php',
        'Php\\Pie\\Command\\DownloadCommand' => __DIR__ . '/../..' . '/src/Command/DownloadCommand.php',
        'Php\\Pie\\Command\\InfoCommand' => __DIR__ . '/../..' . '/src/Command/InfoCommand.php',
        'Php\\Pie\\Command\\InstallCommand' => __DIR__ . '/../..' . '/src/Command/InstallCommand.php',
        'Php\\Pie\\Command\\InstallExtensionsForProjectCommand' => __DIR__ . '/../..' . '/src/Command/InstallExtensionsForProjectCommand.php',
        'Php\\Pie\\Command\\InvokeSubCommand' => __DIR__ . '/../..' . '/src/Command/InvokeSubCommand.php',
        'Php\\Pie\\Command\\RepositoryAddCommand' => __DIR__ . '/../..' . '/src/Command/RepositoryAddCommand.php',
        'Php\\Pie\\Command\\RepositoryListCommand' => __DIR__ . '/../..' . '/src/Command/RepositoryListCommand.php',
        'Php\\Pie\\Command\\RepositoryRemoveCommand' => __DIR__ . '/../..' . '/src/Command/RepositoryRemoveCommand.php',
        'Php\\Pie\\Command\\SelfUpdateCommand' => __DIR__ . '/../..' . '/src/Command/SelfUpdateCommand.php',
        'Php\\Pie\\Command\\SelfVerifyCommand' => __DIR__ . '/../..' . '/src/Command/SelfVerifyCommand.php',
        'Php\\Pie\\Command\\ShowCommand' => __DIR__ . '/../..' . '/src/Command/ShowCommand.php',
        'Php\\Pie\\Command\\UninstallCommand' => __DIR__ . '/../..' . '/src/Command/UninstallCommand.php',
        'Php\\Pie\\ComposerIntegration\\BundledPhpExtensionsRepository' => __DIR__ . '/../..' . '/src/ComposerIntegration/BundledPhpExtensionsRepository.php',
        'Php\\Pie\\ComposerIntegration\\ComposerIntegrationHandler' => __DIR__ . '/../..' . '/src/ComposerIntegration/ComposerIntegrationHandler.php',
        'Php\\Pie\\ComposerIntegration\\ComposerRunFailed' => __DIR__ . '/../..' . '/src/ComposerIntegration/ComposerRunFailed.php',
        'Php\\Pie\\ComposerIntegration\\InstallAndBuildProcess' => __DIR__ . '/../..' . '/src/ComposerIntegration/InstallAndBuildProcess.php',
        'Php\\Pie\\ComposerIntegration\\InstalledJsonMetadata' => __DIR__ . '/../..' . '/src/ComposerIntegration/InstalledJsonMetadata.php',
        'Php\\Pie\\ComposerIntegration\\Listeners\\OverrideDownloadUrlInstallListener' => __DIR__ . '/../..' . '/src/ComposerIntegration/Listeners/OverrideDownloadUrlInstallListener.php',
        'Php\\Pie\\ComposerIntegration\\Listeners\\RemoveUnrelatedInstallOperations' => __DIR__ . '/../..' . '/src/ComposerIntegration/Listeners/RemoveUnrelatedInstallOperations.php',
        'Php\\Pie\\ComposerIntegration\\MinimalHelperSet' => __DIR__ . '/../..' . '/src/ComposerIntegration/MinimalHelperSet.php',
        'Php\\Pie\\ComposerIntegration\\PhpBinaryPathBasedPlatformRepository' => __DIR__ . '/../..' . '/src/ComposerIntegration/PhpBinaryPathBasedPlatformRepository.php',
        'Php\\Pie\\ComposerIntegration\\PieComposerFactory' => __DIR__ . '/../..' . '/src/ComposerIntegration/PieComposerFactory.php',
        'Php\\Pie\\ComposerIntegration\\PieComposerInstaller' => __DIR__ . '/../..' . '/src/ComposerIntegration/PieComposerInstaller.php',
        'Php\\Pie\\ComposerIntegration\\PieComposerRequest' => __DIR__ . '/../..' . '/src/ComposerIntegration/PieComposerRequest.php',
        'Php\\Pie\\ComposerIntegration\\PieInstalledJsonMetadataKeys' => __DIR__ . '/../..' . '/src/ComposerIntegration/PieInstalledJsonMetadataKeys.php',
        'Php\\Pie\\ComposerIntegration\\PieJsonEditor' => __DIR__ . '/../..' . '/src/ComposerIntegration/PieJsonEditor.php',
        'Php\\Pie\\ComposerIntegration\\PieOperation' => __DIR__ . '/../..' . '/src/ComposerIntegration/PieOperation.php',
        'Php\\Pie\\ComposerIntegration\\PiePackageInstaller' => __DIR__ . '/../..' . '/src/ComposerIntegration/PiePackageInstaller.php',
        'Php\\Pie\\ComposerIntegration\\QuieterConsoleIO' => __DIR__ . '/../..' . '/src/ComposerIntegration/QuieterConsoleIO.php',
        'Php\\Pie\\ComposerIntegration\\UninstallProcess' => __DIR__ . '/../..' . '/src/ComposerIntegration/UninstallProcess.php',
        'Php\\Pie\\ComposerIntegration\\VendorCleanup' => __DIR__ . '/../..' . '/src/ComposerIntegration/VendorCleanup.php',
        'Php\\Pie\\ComposerIntegration\\VersionSelectorFactory' => __DIR__ . '/../..' . '/src/ComposerIntegration/VersionSelectorFactory.php',
        'Php\\Pie\\ConfigureOption' => __DIR__ . '/../..' . '/src/ConfigureOption.php',
        'Php\\Pie\\Container' => __DIR__ . '/../..' . '/src/Container.php',
        'Php\\Pie\\DependencyResolver\\BundledPhpExtensionRefusal' => __DIR__ . '/../..' . '/src/DependencyResolver/BundledPhpExtensionRefusal.php',
        'Php\\Pie\\DependencyResolver\\DependencyResolver' => __DIR__ . '/../..' . '/src/DependencyResolver/DependencyResolver.php',
        'Php\\Pie\\DependencyResolver\\DetermineMinimumStability' => __DIR__ . '/../..' . '/src/DependencyResolver/DetermineMinimumStability.php',
        'Php\\Pie\\DependencyResolver\\IncompatibleOperatingSystemFamily' => __DIR__ . '/../..' . '/src/DependencyResolver/IncompatibleOperatingSystemFamily.php',
        'Php\\Pie\\DependencyResolver\\IncompatibleThreadSafetyMode' => __DIR__ . '/../..' . '/src/DependencyResolver/IncompatibleThreadSafetyMode.php',
        'Php\\Pie\\DependencyResolver\\InvalidPackageName' => __DIR__ . '/../..' . '/src/DependencyResolver/InvalidPackageName.php',
        'Php\\Pie\\DependencyResolver\\Package' => __DIR__ . '/../..' . '/src/DependencyResolver/Package.php',
        'Php\\Pie\\DependencyResolver\\RequestedPackageAndVersion' => __DIR__ . '/../..' . '/src/DependencyResolver/RequestedPackageAndVersion.php',
        'Php\\Pie\\DependencyResolver\\ResolveDependencyWithComposer' => __DIR__ . '/../..' . '/src/DependencyResolver/ResolveDependencyWithComposer.php',
        'Php\\Pie\\DependencyResolver\\UnableToResolveRequirement' => __DIR__ . '/../..' . '/src/DependencyResolver/UnableToResolveRequirement.php',
        'Php\\Pie\\Downloading\\DownloadUrlMethod' => __DIR__ . '/../..' . '/src/Downloading/DownloadUrlMethod.php',
        'Php\\Pie\\Downloading\\DownloadedPackage' => __DIR__ . '/../..' . '/src/Downloading/DownloadedPackage.php',
        'Php\\Pie\\Downloading\\Exception\\CouldNotFindReleaseAsset' => __DIR__ . '/../..' . '/src/Downloading/Exception/CouldNotFindReleaseAsset.php',
        'Php\\Pie\\Downloading\\GithubPackageReleaseAssets' => __DIR__ . '/../..' . '/src/Downloading/GithubPackageReleaseAssets.php',
        'Php\\Pie\\Downloading\\PackageReleaseAssets' => __DIR__ . '/../..' . '/src/Downloading/PackageReleaseAssets.php',
        'Php\\Pie\\ExtensionName' => __DIR__ . '/../..' . '/src/ExtensionName.php',
        'Php\\Pie\\ExtensionType' => __DIR__ . '/../..' . '/src/ExtensionType.php',
        'Php\\Pie\\File\\BinaryFile' => __DIR__ . '/../..' . '/src/File/BinaryFile.php',
        'Php\\Pie\\File\\BinaryFileFailedVerification' => __DIR__ . '/../..' . '/src/File/BinaryFileFailedVerification.php',
        'Php\\Pie\\File\\FailedToCreateFile' => __DIR__ . '/../..' . '/src/File/FailedToCreateFile.php',
        'Php\\Pie\\File\\FailedToUnlinkFile' => __DIR__ . '/../..' . '/src/File/FailedToUnlinkFile.php',
        'Php\\Pie\\File\\FailedToWriteFile' => __DIR__ . '/../..' . '/src/File/FailedToWriteFile.php',
        'Php\\Pie\\File\\FullPathToSelf' => __DIR__ . '/../..' . '/src/File/FullPathToSelf.php',
        'Php\\Pie\\File\\Sudo' => __DIR__ . '/../..' . '/src/File/Sudo.php',
        'Php\\Pie\\File\\SudoCreate' => __DIR__ . '/../..' . '/src/File/SudoCreate.php',
        'Php\\Pie\\File\\SudoFilePut' => __DIR__ . '/../..' . '/src/File/SudoFilePut.php',
        'Php\\Pie\\File\\SudoNotFoundOnSystem' => __DIR__ . '/../..' . '/src/File/SudoNotFoundOnSystem.php',
        'Php\\Pie\\File\\SudoRequiresInteractiveTerminal' => __DIR__ . '/../..' . '/src/File/SudoRequiresInteractiveTerminal.php',
        'Php\\Pie\\File\\SudoUnlink' => __DIR__ . '/../..' . '/src/File/SudoUnlink.php',
        'Php\\Pie\\File\\WindowsDelete' => __DIR__ . '/../..' . '/src/File/WindowsDelete.php',
        'Php\\Pie\\Installing\\FailedToRemoveExtension' => __DIR__ . '/../..' . '/src/Installing/FailedToRemoveExtension.php',
        'Php\\Pie\\Installing\\Ini\\AddExtensionToTheIniFile' => __DIR__ . '/../..' . '/src/Installing/Ini/AddExtensionToTheIniFile.php',
        'Php\\Pie\\Installing\\Ini\\CheckAndAddExtensionToIniIfNeeded' => __DIR__ . '/../..' . '/src/Installing/Ini/CheckAndAddExtensionToIniIfNeeded.php',
        'Php\\Pie\\Installing\\Ini\\DockerPhpExtEnable' => __DIR__ . '/../..' . '/src/Installing/Ini/DockerPhpExtEnable.php',
        'Php\\Pie\\Installing\\Ini\\IsExtensionAlreadyInTheIniFile' => __DIR__ . '/../..' . '/src/Installing/Ini/IsExtensionAlreadyInTheIniFile.php',
        'Php\\Pie\\Installing\\Ini\\OndrejPhpenmod' => __DIR__ . '/../..' . '/src/Installing/Ini/OndrejPhpenmod.php',
        'Php\\Pie\\Installing\\Ini\\PickBestSetupIniApproach' => __DIR__ . '/../..' . '/src/Installing/Ini/PickBestSetupIniApproach.php',
        'Php\\Pie\\Installing\\Ini\\PreCheckExtensionAlreadyLoaded' => __DIR__ . '/../..' . '/src/Installing/Ini/PreCheckExtensionAlreadyLoaded.php',
        'Php\\Pie\\Installing\\Ini\\RemoveIniEntry' => __DIR__ . '/../..' . '/src/Installing/Ini/RemoveIniEntry.php',
        'Php\\Pie\\Installing\\Ini\\RemoveIniEntryWithFileGetContents' => __DIR__ . '/../..' . '/src/Installing/Ini/RemoveIniEntryWithFileGetContents.php',
        'Php\\Pie\\Installing\\Ini\\SetupIniApproach' => __DIR__ . '/../..' . '/src/Installing/Ini/SetupIniApproach.php',
        'Php\\Pie\\Installing\\Ini\\StandardAdditionalPhpIniDirectory' => __DIR__ . '/../..' . '/src/Installing/Ini/StandardAdditionalPhpIniDirectory.php',
        'Php\\Pie\\Installing\\Ini\\StandardSinglePhpIni' => __DIR__ . '/../..' . '/src/Installing/Ini/StandardSinglePhpIni.php',
        'Php\\Pie\\Installing\\Install' => __DIR__ . '/../..' . '/src/Installing/Install.php',
        'Php\\Pie\\Installing\\InstallForPhpProject\\ComposerFactoryForProject' => __DIR__ . '/../..' . '/src/Installing/InstallForPhpProject/ComposerFactoryForProject.php',
        'Php\\Pie\\Installing\\InstallForPhpProject\\DetermineExtensionsRequired' => __DIR__ . '/../..' . '/src/Installing/InstallForPhpProject/DetermineExtensionsRequired.php',
        'Php\\Pie\\Installing\\InstallForPhpProject\\FindMatchingPackages' => __DIR__ . '/../..' . '/src/Installing/InstallForPhpProject/FindMatchingPackages.php',
        'Php\\Pie\\Installing\\InstallForPhpProject\\InstallPiePackageFromPath' => __DIR__ . '/../..' . '/src/Installing/InstallForPhpProject/InstallPiePackageFromPath.php',
        'Php\\Pie\\Installing\\InstallForPhpProject\\InstallSelectedPackage' => __DIR__ . '/../..' . '/src/Installing/InstallForPhpProject/InstallSelectedPackage.php',
        'Php\\Pie\\Installing\\PackageMetadataMissing' => __DIR__ . '/../..' . '/src/Installing/PackageMetadataMissing.php',
        'Php\\Pie\\Installing\\SetupIniFile' => __DIR__ . '/../..' . '/src/Installing/SetupIniFile.php',
        'Php\\Pie\\Installing\\Uninstall' => __DIR__ . '/../..' . '/src/Installing/Uninstall.php',
        'Php\\Pie\\Installing\\UninstallUsingUnlink' => __DIR__ . '/../..' . '/src/Installing/UninstallUsingUnlink.php',
        'Php\\Pie\\Installing\\UnixInstall' => __DIR__ . '/../..' . '/src/Installing/UnixInstall.php',
        'Php\\Pie\\Installing\\WindowsInstall' => __DIR__ . '/../..' . '/src/Installing/WindowsInstall.php',
        'Php\\Pie\\Platform' => __DIR__ . '/../..' . '/src/Platform.php',
        'Php\\Pie\\Platform\\Architecture' => __DIR__ . '/../..' . '/src/Platform/Architecture.php',
        'Php\\Pie\\Platform\\InstalledPiePackages' => __DIR__ . '/../..' . '/src/Platform/InstalledPiePackages.php',
        'Php\\Pie\\Platform\\OperatingSystem' => __DIR__ . '/../..' . '/src/Platform/OperatingSystem.php',
        'Php\\Pie\\Platform\\OperatingSystemFamily' => __DIR__ . '/../..' . '/src/Platform/OperatingSystemFamily.php',
        'Php\\Pie\\Platform\\PrePackagedSourceAssetName' => __DIR__ . '/../..' . '/src/Platform/PrePackagedSourceAssetName.php',
        'Php\\Pie\\Platform\\TargetPhp\\Exception\\ExtensionIsNotLoaded' => __DIR__ . '/../..' . '/src/Platform/TargetPhp/Exception/ExtensionIsNotLoaded.php',
        'Php\\Pie\\Platform\\TargetPhp\\Exception\\InvalidPhpBinaryPath' => __DIR__ . '/../..' . '/src/Platform/TargetPhp/Exception/InvalidPhpBinaryPath.php',
        'Php\\Pie\\Platform\\TargetPhp\\PhpBinaryPath' => __DIR__ . '/../..' . '/src/Platform/TargetPhp/PhpBinaryPath.php',
        'Php\\Pie\\Platform\\TargetPhp\\PhpizePath' => __DIR__ . '/../..' . '/src/Platform/TargetPhp/PhpizePath.php',
        'Php\\Pie\\Platform\\TargetPlatform' => __DIR__ . '/../..' . '/src/Platform/TargetPlatform.php',
        'Php\\Pie\\Platform\\ThreadSafetyMode' => __DIR__ . '/../..' . '/src/Platform/ThreadSafetyMode.php',
        'Php\\Pie\\Platform\\WindowsCompiler' => __DIR__ . '/../..' . '/src/Platform/WindowsCompiler.php',
        'Php\\Pie\\Platform\\WindowsExtensionAssetName' => __DIR__ . '/../..' . '/src/Platform/WindowsExtensionAssetName.php',
        'Php\\Pie\\SelfManage\\Update\\FetchPieRelease' => __DIR__ . '/../..' . '/src/SelfManage/Update/FetchPieRelease.php',
        'Php\\Pie\\SelfManage\\Update\\FetchPieReleaseFromGitHub' => __DIR__ . '/../..' . '/src/SelfManage/Update/FetchPieReleaseFromGitHub.php',
        'Php\\Pie\\SelfManage\\Update\\ReleaseMetadata' => __DIR__ . '/../..' . '/src/SelfManage/Update/ReleaseMetadata.php',
        'Php\\Pie\\SelfManage\\Verify\\Attestation' => __DIR__ . '/../..' . '/src/SelfManage/Verify/Attestation.php',
        'Php\\Pie\\SelfManage\\Verify\\FailedToVerifyRelease' => __DIR__ . '/../..' . '/src/SelfManage/Verify/FailedToVerifyRelease.php',
        'Php\\Pie\\SelfManage\\Verify\\FallbackVerificationUsingOpenSsl' => __DIR__ . '/../..' . '/src/SelfManage/Verify/FallbackVerificationUsingOpenSsl.php',
        'Php\\Pie\\SelfManage\\Verify\\GithubCliAttestationVerification' => __DIR__ . '/../..' . '/src/SelfManage/Verify/GithubCliAttestationVerification.php',
        'Php\\Pie\\SelfManage\\Verify\\GithubCliNotAvailable' => __DIR__ . '/../..' . '/src/SelfManage/Verify/GithubCliNotAvailable.php',
        'Php\\Pie\\SelfManage\\Verify\\VerifyPiePhar' => __DIR__ . '/../..' . '/src/SelfManage/Verify/VerifyPiePhar.php',
        'Php\\Pie\\SelfManage\\Verify\\VerifyPieReleaseUsingAttestation' => __DIR__ . '/../..' . '/src/SelfManage/Verify/VerifyPieReleaseUsingAttestation.php',
        'Php\\Pie\\Util\\CaptureErrors' => __DIR__ . '/../..' . '/src/Util/CaptureErrors.php',
        'Php\\Pie\\Util\\Emoji' => __DIR__ . '/../..' . '/src/Util/Emoji.php',
        'Php\\Pie\\Util\\FileNotFound' => __DIR__ . '/../..' . '/src/Util/FileNotFound.php',
        'Php\\Pie\\Util\\PieVersion' => __DIR__ . '/../..' . '/src/Util/PieVersion.php',
        'Php\\Pie\\Util\\Process' => __DIR__ . '/../..' . '/src/Util/Process.php',
        '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\\Log\\AbstractLogger' => __DIR__ . '/..' . '/psr/log/src/AbstractLogger.php',
        'Psr\\Log\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/log/src/InvalidArgumentException.php',
        'Psr\\Log\\LogLevel' => __DIR__ . '/..' . '/psr/log/src/LogLevel.php',
        'Psr\\Log\\LoggerAwareInterface' => __DIR__ . '/..' . '/psr/log/src/LoggerAwareInterface.php',
        'Psr\\Log\\LoggerAwareTrait' => __DIR__ . '/..' . '/psr/log/src/LoggerAwareTrait.php',
        'Psr\\Log\\LoggerInterface' => __DIR__ . '/..' . '/psr/log/src/LoggerInterface.php',
        'Psr\\Log\\LoggerTrait' => __DIR__ . '/..' . '/psr/log/src/LoggerTrait.php',
        'Psr\\Log\\NullLogger' => __DIR__ . '/..' . '/psr/log/src/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',
        'React\\Promise\\Deferred' => __DIR__ . '/..' . '/react/promise/src/Deferred.php',
        'React\\Promise\\Exception\\CompositeException' => __DIR__ . '/..' . '/react/promise/src/Exception/CompositeException.php',
        'React\\Promise\\Exception\\LengthException' => __DIR__ . '/..' . '/react/promise/src/Exception/LengthException.php',
        'React\\Promise\\Internal\\CancellationQueue' => __DIR__ . '/..' . '/react/promise/src/Internal/CancellationQueue.php',
        'React\\Promise\\Internal\\FulfilledPromise' => __DIR__ . '/..' . '/react/promise/src/Internal/FulfilledPromise.php',
        'React\\Promise\\Internal\\RejectedPromise' => __DIR__ . '/..' . '/react/promise/src/Internal/RejectedPromise.php',
        'React\\Promise\\Promise' => __DIR__ . '/..' . '/react/promise/src/Promise.php',
        'React\\Promise\\PromiseInterface' => __DIR__ . '/..' . '/react/promise/src/PromiseInterface.php',
        'Seld\\JsonLint\\DuplicateKeyException' => __DIR__ . '/..' . '/seld/jsonlint/src/Seld/JsonLint/DuplicateKeyException.php',
        'Seld\\JsonLint\\JsonParser' => __DIR__ . '/..' . '/seld/jsonlint/src/Seld/JsonLint/JsonParser.php',
        'Seld\\JsonLint\\Lexer' => __DIR__ . '/..' . '/seld/jsonlint/src/Seld/JsonLint/Lexer.php',
        'Seld\\JsonLint\\ParsingException' => __DIR__ . '/..' . '/seld/jsonlint/src/Seld/JsonLint/ParsingException.php',
        'Seld\\JsonLint\\Undefined' => __DIR__ . '/..' . '/seld/jsonlint/src/Seld/JsonLint/Undefined.php',
        'Seld\\PharUtils\\Linter' => __DIR__ . '/..' . '/seld/phar-utils/src/Linter.php',
        'Seld\\PharUtils\\Timestamps' => __DIR__ . '/..' . '/seld/phar-utils/src/Timestamps.php',
        'Seld\\Signal\\SignalHandler' => __DIR__ . '/..' . '/seld/signal-handler/src/SignalHandler.php',
        'Stringable' => __DIR__ . '/..' . '/marc-mabe/php-enum/stubs/Stringable.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\\Command\\TraceableCommand' => __DIR__ . '/..' . '/symfony/console/Command/TraceableCommand.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\\Output\\FishCompletionOutput' => __DIR__ . '/..' . '/symfony/console/Completion/Output/FishCompletionOutput.php',
        'Symfony\\Component\\Console\\Completion\\Output\\ZshCompletionOutput' => __DIR__ . '/..' . '/symfony/console/Completion/Output/ZshCompletionOutput.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\\DataCollector\\CommandDataCollector' => __DIR__ . '/..' . '/symfony/console/DataCollector/CommandDataCollector.php',
        'Symfony\\Component\\Console\\Debug\\CliRequest' => __DIR__ . '/..' . '/symfony/console/Debug/CliRequest.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',
        'Symfony\\Component\\Console\\Descriptor\\DescriptorInterface' => __DIR__ . '/..' . '/symfony/console/Descriptor/DescriptorInterface.php',
        'Symfony\\Component\\Console\\Descriptor\\JsonDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/JsonDescriptor.php',
        'Symfony\\Component\\Console\\Descriptor\\MarkdownDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/MarkdownDescriptor.php',
        'Symfony\\Component\\Console\\Descriptor\\ReStructuredTextDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/ReStructuredTextDescriptor.php',
        'Symfony\\Component\\Console\\Descriptor\\TextDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/TextDescriptor.php',
        'Symfony\\Component\\Console\\Descriptor\\XmlDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/XmlDescriptor.php',
        'Symfony\\Component\\Console\\EventListener\\ErrorListener' => __DIR__ . '/..' . '/symfony/console/EventListener/ErrorListener.php',
        '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\\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\\RunCommandFailedException' => __DIR__ . '/..' . '/symfony/console/Exception/RunCommandFailedException.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',
        'Symfony\\Component\\Console\\Helper\\HelperSet' => __DIR__ . '/..' . '/symfony/console/Helper/HelperSet.php',
        'Symfony\\Component\\Console\\Helper\\InputAwareHelper' => __DIR__ . '/..' . '/symfony/console/Helper/InputAwareHelper.php',
        'Symfony\\Component\\Console\\Helper\\OutputWrapper' => __DIR__ . '/..' . '/symfony/console/Helper/OutputWrapper.php',
        'Symfony\\Component\\Console\\Helper\\ProcessHelper' => __DIR__ . '/..' . '/symfony/console/Helper/ProcessHelper.php',
        'Symfony\\Component\\Console\\Helper\\ProgressBar' => __DIR__ . '/..' . '/symfony/console/Helper/ProgressBar.php',
        'Symfony\\Component\\Console\\Helper\\ProgressIndicator' => __DIR__ . '/..' . '/symfony/console/Helper/ProgressIndicator.php',
        'Symfony\\Component\\Console\\Helper\\QuestionHelper' => __DIR__ . '/..' . '/symfony/console/Helper/QuestionHelper.php',
        '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',
        'Symfony\\Component\\Console\\Input\\ArrayInput' => __DIR__ . '/..' . '/symfony/console/Input/ArrayInput.php',
        'Symfony\\Component\\Console\\Input\\Input' => __DIR__ . '/..' . '/symfony/console/Input/Input.php',
        'Symfony\\Component\\Console\\Input\\InputArgument' => __DIR__ . '/..' . '/symfony/console/Input/InputArgument.php',
        'Symfony\\Component\\Console\\Input\\InputAwareInterface' => __DIR__ . '/..' . '/symfony/console/Input/InputAwareInterface.php',
        'Symfony\\Component\\Console\\Input\\InputDefinition' => __DIR__ . '/..' . '/symfony/console/Input/InputDefinition.php',
        'Symfony\\Component\\Console\\Input\\InputInterface' => __DIR__ . '/..' . '/symfony/console/Input/InputInterface.php',
        'Symfony\\Component\\Console\\Input\\InputOption' => __DIR__ . '/..' . '/symfony/console/Input/InputOption.php',
        'Symfony\\Component\\Console\\Input\\StreamableInputInterface' => __DIR__ . '/..' . '/symfony/console/Input/StreamableInputInterface.php',
        'Symfony\\Component\\Console\\Input\\StringInput' => __DIR__ . '/..' . '/symfony/console/Input/StringInput.php',
        'Symfony\\Component\\Console\\Logger\\ConsoleLogger' => __DIR__ . '/..' . '/symfony/console/Logger/ConsoleLogger.php',
        'Symfony\\Component\\Console\\Messenger\\RunCommandContext' => __DIR__ . '/..' . '/symfony/console/Messenger/RunCommandContext.php',
        'Symfony\\Component\\Console\\Messenger\\RunCommandMessage' => __DIR__ . '/..' . '/symfony/console/Messenger/RunCommandMessage.php',
        'Symfony\\Component\\Console\\Messenger\\RunCommandMessageHandler' => __DIR__ . '/..' . '/symfony/console/Messenger/RunCommandMessageHandler.php',
        'Symfony\\Component\\Console\\Output\\AnsiColorMode' => __DIR__ . '/..' . '/symfony/console/Output/AnsiColorMode.php',
        '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\\SignalMap' => __DIR__ . '/..' . '/symfony/console/SignalRegistry/SignalMap.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\\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\\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\\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\\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\\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\\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',
        'Symfony\\Component\\Finder\\Iterator\\DepthRangeFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/DepthRangeFilterIterator.php',
        'Symfony\\Component\\Finder\\Iterator\\ExcludeDirectoryFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php',
        '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\\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\\Process\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/process/Exception/ExceptionInterface.php',
        'Symfony\\Component\\Process\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/process/Exception/InvalidArgumentException.php',
        'Symfony\\Component\\Process\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/process/Exception/LogicException.php',
        'Symfony\\Component\\Process\\Exception\\ProcessFailedException' => __DIR__ . '/..' . '/symfony/process/Exception/ProcessFailedException.php',
        'Symfony\\Component\\Process\\Exception\\ProcessSignaledException' => __DIR__ . '/..' . '/symfony/process/Exception/ProcessSignaledException.php',
        'Symfony\\Component\\Process\\Exception\\ProcessTimedOutException' => __DIR__ . '/..' . '/symfony/process/Exception/ProcessTimedOutException.php',
        'Symfony\\Component\\Process\\Exception\\RunProcessFailedException' => __DIR__ . '/..' . '/symfony/process/Exception/RunProcessFailedException.php',
        'Symfony\\Component\\Process\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/process/Exception/RuntimeException.php',
        'Symfony\\Component\\Process\\ExecutableFinder' => __DIR__ . '/..' . '/symfony/process/ExecutableFinder.php',
        'Symfony\\Component\\Process\\InputStream' => __DIR__ . '/..' . '/symfony/process/InputStream.php',
        'Symfony\\Component\\Process\\Messenger\\RunProcessContext' => __DIR__ . '/..' . '/symfony/process/Messenger/RunProcessContext.php',
        'Symfony\\Component\\Process\\Messenger\\RunProcessMessage' => __DIR__ . '/..' . '/symfony/process/Messenger/RunProcessMessage.php',
        'Symfony\\Component\\Process\\Messenger\\RunProcessMessageHandler' => __DIR__ . '/..' . '/symfony/process/Messenger/RunProcessMessageHandler.php',
        'Symfony\\Component\\Process\\PhpExecutableFinder' => __DIR__ . '/..' . '/symfony/process/PhpExecutableFinder.php',
        'Symfony\\Component\\Process\\PhpProcess' => __DIR__ . '/..' . '/symfony/process/PhpProcess.php',
        'Symfony\\Component\\Process\\PhpSubprocess' => __DIR__ . '/..' . '/symfony/process/PhpSubprocess.php',
        'Symfony\\Component\\Process\\Pipes\\AbstractPipes' => __DIR__ . '/..' . '/symfony/process/Pipes/AbstractPipes.php',
        'Symfony\\Component\\Process\\Pipes\\PipesInterface' => __DIR__ . '/..' . '/symfony/process/Pipes/PipesInterface.php',
        'Symfony\\Component\\Process\\Pipes\\UnixPipes' => __DIR__ . '/..' . '/symfony/process/Pipes/UnixPipes.php',
        'Symfony\\Component\\Process\\Pipes\\WindowsPipes' => __DIR__ . '/..' . '/symfony/process/Pipes/WindowsPipes.php',
        'Symfony\\Component\\Process\\Process' => __DIR__ . '/..' . '/symfony/process/Process.php',
        'Symfony\\Component\\Process\\ProcessUtils' => __DIR__ . '/..' . '/symfony/process/ProcessUtils.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\\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\\ServiceCollectionInterface' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceCollectionInterface.php',
        'Symfony\\Contracts\\Service\\ServiceLocatorTrait' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceLocatorTrait.php',
        'Symfony\\Contracts\\Service\\ServiceMethodsSubscriberTrait' => __DIR__ . '/..' . '/symfony/service-contracts/ServiceMethodsSubscriberTrait.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\\Polyfill\\Ctype\\Ctype' => __DIR__ . '/..' . '/symfony/polyfill-ctype/Ctype.php',
        'Symfony\\Polyfill\\Intl\\Grapheme\\Grapheme' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/Grapheme.php',
        'Symfony\\Polyfill\\Intl\\Normalizer\\Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Normalizer.php',
        'Symfony\\Polyfill\\Mbstring\\Mbstring' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/Mbstring.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',
    );

    public static function getInitializer(ClassLoader $loader)
    {
        return \Closure::bind(function () use ($loader) {
            $loader->prefixLengthsPsr4 = ComposerStaticInitf542bd5a549fa468020c82327ba6fd88::$prefixLengthsPsr4;
            $loader->prefixDirsPsr4 = ComposerStaticInitf542bd5a549fa468020c82327ba6fd88::$prefixDirsPsr4;
            $loader->classMap = ComposerStaticInitf542bd5a549fa468020c82327ba6fd88::$classMap;

        }, null, ClassLoader::class);
    }
}
Copyright (C) 2021 Composer

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.
<?php

/*
 * This file is part of composer/metadata-minifier.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\MetadataMinifier;

class MetadataMinifier
{
    /**
     * Expands an array of minified versions back to their original format
     *
     * @param array[] $versions A list of minified version arrays
     * @return array[] A list of version arrays
     */
    public static function expand(array $versions)
    {
        $expanded = array();
        $expandedVersion = null;
        foreach ($versions as $versionData) {
            if (!$expandedVersion) {
                $expandedVersion = $versionData;
                $expanded[] = $expandedVersion;
                continue;
            }

            // add any changes from the previous version to the expanded one
            foreach ($versionData as $key => $val) {
                if ($val === '__unset') {
                    unset($expandedVersion[$key]);
                } else {
                    $expandedVersion[$key] = $val;
                }
            }

            $expanded[] = $expandedVersion;
        }

        return $expanded;
    }

    /**
     * Minifies an array of versions into a set of version diffs
     *
     * @param array[] $versions A list of version arrays
     * @return array[] A list of versions minified with each array only containing the differences to the previous one
     */
    public static function minify(array $versions)
    {
        $minifiedVersions = array();

        $lastKnownVersionData = null;
        foreach ($versions as $version) {
            if (!$lastKnownVersionData) {
                $lastKnownVersionData = $version;
                $minifiedVersions[] = $version;
                continue;
            }

            $minifiedVersion = array();

            // add any changes from the previous version
            foreach ($version as $key => $val) {
                if (!isset($lastKnownVersionData[$key]) || $lastKnownVersionData[$key] !== $val) {
                    $minifiedVersion[$key] = $val;
                    $lastKnownVersionData[$key] = $val;
                }
            }

            // store any deletions from the previous version for keys missing in current one
            foreach ($lastKnownVersionData as $key => $val) {
                if (!isset($version[$key])) {
                    $minifiedVersion[$key] = "__unset";
                    unset($lastKnownVersionData[$key]);
                }
            }

            $minifiedVersions[] = $minifiedVersion;
        }

        return $minifiedVersions;
    }
}
<?php

// autoload_real.php @generated by Composer

class ComposerAutoloaderInitf542bd5a549fa468020c82327ba6fd88
{
    private static $loader;

    public static function loadClassLoader($class)
    {
        if ('Composer\Autoload\ClassLoader' === $class) {
            require __DIR__ . '/ClassLoader.php';
        }
    }

    /**
     * @return \Composer\Autoload\ClassLoader
     */
    public static function getLoader()
    {
        if (null !== self::$loader) {
            return self::$loader;
        }

        require __DIR__ . '/platform_check.php';

        spl_autoload_register(array('ComposerAutoloaderInitf542bd5a549fa468020c82327ba6fd88', 'loadClassLoader'), true, true);
        self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
        spl_autoload_unregister(array('ComposerAutoloaderInitf542bd5a549fa468020c82327ba6fd88', 'loadClassLoader'));

        require __DIR__ . '/autoload_static.php';
        call_user_func(\Composer\Autoload\ComposerStaticInitf542bd5a549fa468020c82327ba6fd88::getInitializer($loader));

        $loader->setClassMapAuthoritative(true);
        $loader->register(true);

        $filesToLoad = \Composer\Autoload\ComposerStaticInitf542bd5a549fa468020c82327ba6fd88::$files;
        $requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
            if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
                $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;

                require $file;
            }
        }, null, null);
        foreach ($filesToLoad as $fileIdentifier => $file) {
            $requireFile($fileIdentifier, $file);
        }

        return $loader;
    }
}
Copyright (C) 2015 Composer

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.
<?php

/*
 * This file is part of composer/spdx-licenses.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Spdx;

class SpdxLicenses
{
    /** @var string */
    const LICENSES_FILE = 'spdx-licenses.json';

    /** @var string */
    const EXCEPTIONS_FILE = 'spdx-exceptions.json';

    /**
     * Contains all the licenses.
     *
     * The array is indexed by license identifiers, which contain
     * a numerically indexed array with license details.
     *
     *  [ lowercased license identifier =>
     *      [ 0 => identifier (string), 1 => full name (string), 2 => osi certified (bool), 3 => deprecated (bool) ]
     *    , ...
     *  ]
     *
     * @var array<string, array{0: string, 1: string, 2: bool, 3: bool}>
     */
    private $licenses;

    /**
     * @var string
     */
    private $licensesExpression;

    /**
     * Contains all the license exceptions.
     *
     * The array is indexed by license exception identifiers, which contain
     * a numerically indexed array with license exception details.
     *
     *  [ lowercased exception identifier =>
     *      [ 0 => exception identifier (string), 1 => full name (string) ]
     *    , ...
     *  ]
     *
     * @var array<string, array{0: string, 1: string}>
     */
    private $exceptions;

    /**
     * @var string
     */
    private $exceptionsExpression;

    public function __construct()
    {
        $this->loadLicenses();
        $this->loadExceptions();
    }

    /**
     * Returns license metadata by license identifier.
     *
     * This function adds a link to the full license text to the license metadata.
     * The array returned is in the form of:
     *
     *  [ 0 => full name (string), 1 => osi certified, 2 => link to license text (string), 3 => deprecation status (bool) ]
     *
     * @param string $identifier
     *
     * @return array{0: string, 1: bool, 2: string, 3: bool}|null
     */
    public function getLicenseByIdentifier($identifier)
    {
        $key = strtolower($identifier);

        if (!isset($this->licenses[$key])) {
            return null;
        }

        list($identifier, $name, $isOsiApproved, $isDeprecatedLicenseId) = $this->licenses[$key];

        return array(
            $name,
            $isOsiApproved,
            'https://spdx.org/licenses/' . $identifier . '.html#licenseText',
            $isDeprecatedLicenseId,
        );
    }

    /**
     * Returns all licenses information, keyed by the lowercased license identifier.
     *
     * @return array{0: string, 1: string, 2: bool, 3: bool}[] Each item is [ 0 => identifier (string), 1 => full name (string), 2 => osi certified (bool), 3 => deprecated (bool) ]
     */
    public function getLicenses()
    {
        return $this->licenses;
    }

    /**
     * Returns license exception metadata by license exception identifier.
     *
     * This function adds a link to the full license exception text to the license exception metadata.
     * The array returned is in the form of:
     *
     *  [ 0 => full name (string), 1 => link to license text (string) ]
     *
     * @param string $identifier
     *
     * @return array{0: string, 1: string}|null
     */
    public function getExceptionByIdentifier($identifier)
    {
        $key = strtolower($identifier);

        if (!isset($this->exceptions[$key])) {
            return null;
        }

        list($identifier, $name) = $this->exceptions[$key];

        return array(
            $name,
            'https://spdx.org/licenses/' . $identifier . '.html#licenseExceptionText',
        );
    }

    /**
     * Returns the short identifier of a license (or license exception) by full name.
     *
     * @param string $name
     *
     * @return string|null
     */
    public function getIdentifierByName($name)
    {
        foreach ($this->licenses as $licenseData) {
            if ($licenseData[1] === $name) {
                return $licenseData[0];
            }
        }

        foreach ($this->exceptions as $licenseData) {
            if ($licenseData[1] === $name) {
                return $licenseData[0];
            }
        }

        return null;
    }

    /**
     * Returns the OSI Approved status for a license by identifier.
     *
     * @param string $identifier
     *
     * @return bool
     */
    public function isOsiApprovedByIdentifier($identifier)
    {
        return $this->licenses[strtolower($identifier)][2];
    }

    /**
     * Returns the deprecation status for a license by identifier.
     *
     * @param string $identifier
     *
     * @return bool
     */
    public function isDeprecatedByIdentifier($identifier)
    {
        return $this->licenses[strtolower($identifier)][3];
    }

    /**
     * @param string[]|string $license
     *
     * @throws \InvalidArgumentException
     *
     * @return bool
     */
    public function validate($license)
    {
        if (is_array($license)) {
            $count = count($license);
            if ($count !== count(array_filter($license, 'is_string'))) {
                throw new \InvalidArgumentException('Array of strings expected.');
            }
            $license = $count > 1  ? '(' . implode(' OR ', $license) . ')' : (string) reset($license);
        }

        if (!is_string($license)) {
            throw new \InvalidArgumentException(sprintf(
                'Array or String expected, %s given.',
                gettype($license)
            ));
        }

        return $this->isValidLicenseString($license);
    }

    /**
     * @return string
     */
    public static function getResourcesDir()
    {
        return dirname(__DIR__) . '/res';
    }

    /**
     * @return void
     */
    private function loadLicenses()
    {
        if (null !== $this->licenses) {
            return;
        }

        $json = file_get_contents(self::getResourcesDir() . '/' . self::LICENSES_FILE);
        if (false === $json) {
            throw new \RuntimeException('Missing license file in ' . self::getResourcesDir() . '/' . self::LICENSES_FILE);
        }
        $this->licenses = array();

        foreach (json_decode($json, true) as $identifier => $license) {
            $this->licenses[strtolower($identifier)] = array($identifier, $license[0], $license[1], $license[2]);
        }
    }

    /**
     * @return void
     */
    private function loadExceptions()
    {
        if (null !== $this->exceptions) {
            return;
        }

        $json = file_get_contents(self::getResourcesDir() . '/' . self::EXCEPTIONS_FILE);
        if (false === $json) {
            throw new \RuntimeException('Missing exceptions file in ' . self::getResourcesDir() . '/' . self::EXCEPTIONS_FILE);
        }
        $this->exceptions = array();

        foreach (json_decode($json, true) as $identifier => $exception) {
            $this->exceptions[strtolower($identifier)] = array($identifier, $exception[0]);
        }
    }

    /**
     * @return string
     */
    private function getLicensesExpression()
    {
        if (null === $this->licensesExpression) {
            $licenses = array_map('preg_quote', array_keys($this->licenses));
            rsort($licenses);
            $licenses = implode('|', $licenses);
            $this->licensesExpression = $licenses;
        }

        return $this->licensesExpression;
    }

    /**
     * @return string
     */
    private function getExceptionsExpression()
    {
        if (null === $this->exceptionsExpression) {
            $exceptions = array_map('preg_quote', array_keys($this->exceptions));
            rsort($exceptions);
            $exceptions = implode('|', $exceptions);
            $this->exceptionsExpression = $exceptions;
        }

        return $this->exceptionsExpression;
    }

    /**
     * @param string $license
     *
     * @throws \RuntimeException
     *
     * @return bool
     */
    private function isValidLicenseString($license)
    {
        if (isset($this->licenses[strtolower($license)])) {
            return true;
        }

        $licenses = $this->getLicensesExpression();
        $exceptions = $this->getExceptionsExpression();

        $regex = <<<REGEX
{
(?(DEFINE)
    # idstring: 1*( ALPHA / DIGIT / - / . )
    (?<idstring>[\pL\pN.-]{1,})

    # license-id: taken from list
    (?<licenseid>{$licenses})

    # license-exception-id: taken from list
    (?<licenseexceptionid>{$exceptions})

    # license-ref: [DocumentRef-1*(idstring):]LicenseRef-1*(idstring)
    (?<licenseref>(?:DocumentRef-(?&idstring):)?LicenseRef-(?&idstring))

    # simple-expresssion: license-id / license-id+ / license-ref
    (?<simple_expression>(?&licenseid)\+? | (?&licenseid) | (?&licenseref))

    # compound-expression: 1*(
    #   simple-expression /
    #   simple-expression WITH license-exception-id /
    #   compound-expression AND compound-expression /
    #   compound-expression OR compound-expression
    # ) / ( compound-expression ) )
    (?<compound_head>
        (?&simple_expression) ( \s+ WITH \s+ (?&licenseexceptionid))?
            | \( \s* (?&compound_expression) \s* \)
    )
    (?<compound_expression>
        (?&compound_head) (?: \s+ (?:AND|OR) \s+ (?&compound_expression))?
    )

    # license-expression: 1*1(simple-expression / compound-expression)
    (?<license_expression>(?&compound_expression) | (?&simple_expression))
) # end of define

^(NONE | NOASSERTION | (?&license_expression))$
}xi
REGEX;

        $match = preg_match($regex, $license);

        if (0 === $match) {
            return false;
        }

        if (false === $match) {
            throw new \RuntimeException('Regex failed to compile/run.');
        }

        return true;
    }
}
{
    "389-exception": [
        "389 Directory Server Exception"
    ],
    "Asterisk-exception": [
        "Asterisk exception"
    ],
    "Asterisk-linking-protocols-exception": [
        "Asterisk linking protocols exception"
    ],
    "Autoconf-exception-2.0": [
        "Autoconf exception 2.0"
    ],
    "Autoconf-exception-3.0": [
        "Autoconf exception 3.0"
    ],
    "Autoconf-exception-generic": [
        "Autoconf generic exception"
    ],
    "Autoconf-exception-generic-3.0": [
        "Autoconf generic exception for GPL-3.0"
    ],
    "Autoconf-exception-macro": [
        "Autoconf macro exception"
    ],
    "Bison-exception-1.24": [
        "Bison exception 1.24"
    ],
    "Bison-exception-2.2": [
        "Bison exception 2.2"
    ],
    "Bootloader-exception": [
        "Bootloader Distribution Exception"
    ],
    "CGAL-linking-exception": [
        "CGAL Linking Exception"
    ],
    "Classpath-exception-2.0": [
        "Classpath exception 2.0"
    ],
    "CLISP-exception-2.0": [
        "CLISP exception 2.0"
    ],
    "cryptsetup-OpenSSL-exception": [
        "cryptsetup OpenSSL exception"
    ],
    "Digia-Qt-LGPL-exception-1.1": [
        "Digia Qt LGPL Exception version 1.1"
    ],
    "DigiRule-FOSS-exception": [
        "DigiRule FOSS License Exception"
    ],
    "eCos-exception-2.0": [
        "eCos exception 2.0"
    ],
    "erlang-otp-linking-exception": [
        "Erlang/OTP Linking Exception"
    ],
    "Fawkes-Runtime-exception": [
        "Fawkes Runtime Exception"
    ],
    "FLTK-exception": [
        "FLTK exception"
    ],
    "fmt-exception": [
        "fmt exception"
    ],
    "Font-exception-2.0": [
        "Font exception 2.0"
    ],
    "freertos-exception-2.0": [
        "FreeRTOS Exception 2.0"
    ],
    "GCC-exception-2.0": [
        "GCC Runtime Library exception 2.0"
    ],
    "GCC-exception-2.0-note": [
        "GCC    Runtime Library exception 2.0 - note variant"
    ],
    "GCC-exception-3.1": [
        "GCC Runtime Library exception 3.1"
    ],
    "Gmsh-exception": [
        "Gmsh exception"
    ],
    "GNAT-exception": [
        "GNAT exception"
    ],
    "GNOME-examples-exception": [
        "GNOME examples exception"
    ],
    "GNU-compiler-exception": [
        "GNU Compiler Exception"
    ],
    "gnu-javamail-exception": [
        "GNU JavaMail exception"
    ],
    "GPL-3.0-389-ds-base-exception": [
        "GPL-3.0 389 DS Base Exception"
    ],
    "GPL-3.0-interface-exception": [
        "GPL-3.0 Interface Exception"
    ],
    "GPL-3.0-linking-exception": [
        "GPL-3.0 Linking Exception"
    ],
    "GPL-3.0-linking-source-exception": [
        "GPL-3.0 Linking Exception (with Corresponding Source)"
    ],
    "GPL-CC-1.0": [
        "GPL Cooperation Commitment 1.0"
    ],
    "GStreamer-exception-2005": [
        "GStreamer Exception (2005)"
    ],
    "GStreamer-exception-2008": [
        "GStreamer Exception (2008)"
    ],
    "harbour-exception": [
        "harbour exception"
    ],
    "i2p-gpl-java-exception": [
        "i2p GPL+Java Exception"
    ],
    "Independent-modules-exception": [
        "Independent Module Linking exception"
    ],
    "KiCad-libraries-exception": [
        "KiCad Libraries Exception"
    ],
    "LGPL-3.0-linking-exception": [
        "LGPL-3.0 Linking Exception"
    ],
    "libpri-OpenH323-exception": [
        "libpri OpenH323 exception"
    ],
    "Libtool-exception": [
        "Libtool Exception"
    ],
    "Linux-syscall-note": [
        "Linux Syscall Note"
    ],
    "LLGPL": [
        "LLGPL Preamble"
    ],
    "LLVM-exception": [
        "LLVM Exception"
    ],
    "LZMA-exception": [
        "LZMA exception"
    ],
    "mif-exception": [
        "Macros and Inline Functions Exception"
    ],
    "mxml-exception": [
        "mxml Exception"
    ],
    "Nokia-Qt-exception-1.1": [
        "Nokia Qt LGPL exception 1.1"
    ],
    "OCaml-LGPL-linking-exception": [
        "OCaml LGPL Linking Exception"
    ],
    "OCCT-exception-1.0": [
        "Open CASCADE Exception 1.0"
    ],
    "OpenJDK-assembly-exception-1.0": [
        "OpenJDK Assembly exception 1.0"
    ],
    "openvpn-openssl-exception": [
        "OpenVPN OpenSSL Exception"
    ],
    "PCRE2-exception": [
        "PCRE2 exception"
    ],
    "polyparse-exception": [
        "Polyparse Exception"
    ],
    "PS-or-PDF-font-exception-20170817": [
        "PS/PDF font exception (2017-08-17)"
    ],
    "QPL-1.0-INRIA-2004-exception": [
        "INRIA QPL 1.0 2004 variant exception"
    ],
    "Qt-GPL-exception-1.0": [
        "Qt GPL exception 1.0"
    ],
    "Qt-LGPL-exception-1.1": [
        "Qt LGPL exception 1.1"
    ],
    "Qwt-exception-1.0": [
        "Qwt exception 1.0"
    ],
    "romic-exception": [
        "Romic Exception"
    ],
    "RRDtool-FLOSS-exception-2.0": [
        "RRDtool FLOSS exception 2.0"
    ],
    "SANE-exception": [
        "SANE Exception"
    ],
    "SHL-2.0": [
        "Solderpad Hardware License v2.0"
    ],
    "SHL-2.1": [
        "Solderpad Hardware License v2.1"
    ],
    "stunnel-exception": [
        "stunnel Exception"
    ],
    "SWI-exception": [
        "SWI exception"
    ],
    "Swift-exception": [
        "Swift Exception"
    ],
    "Texinfo-exception": [
        "Texinfo exception"
    ],
    "u-boot-exception-2.0": [
        "U-Boot exception 2.0"
    ],
    "UBDL-exception": [
        "Unmodified Binary Distribution exception"
    ],
    "Universal-FOSS-exception-1.0": [
        "Universal FOSS Exception, Version 1.0"
    ],
    "vsftpd-openssl-exception": [
        "vsftpd OpenSSL exception"
    ],
    "WxWindows-exception-3.1": [
        "WxWindows Library Exception 3.1"
    ],
    "x11vnc-openssl-exception": [
        "x11vnc OpenSSL Exception"
    ]
}{
    "0BSD": [
        "BSD Zero Clause License",
        true,
        false
    ],
    "3D-Slicer-1.0": [
        "3D Slicer License v1.0",
        false,
        false
    ],
    "AAL": [
        "Attribution Assurance License",
        true,
        false
    ],
    "Abstyles": [
        "Abstyles License",
        false,
        false
    ],
    "AdaCore-doc": [
        "AdaCore Doc License",
        false,
        false
    ],
    "Adobe-2006": [
        "Adobe Systems Incorporated Source Code License Agreement",
        false,
        false
    ],
    "Adobe-Display-PostScript": [
        "Adobe Display PostScript License",
        false,
        false
    ],
    "Adobe-Glyph": [
        "Adobe Glyph List License",
        false,
        false
    ],
    "Adobe-Utopia": [
        "Adobe Utopia Font License",
        false,
        false
    ],
    "ADSL": [
        "Amazon Digital Services License",
        false,
        false
    ],
    "AFL-1.1": [
        "Academic Free License v1.1",
        true,
        false
    ],
    "AFL-1.2": [
        "Academic Free License v1.2",
        true,
        false
    ],
    "AFL-2.0": [
        "Academic Free License v2.0",
        true,
        false
    ],
    "AFL-2.1": [
        "Academic Free License v2.1",
        true,
        false
    ],
    "AFL-3.0": [
        "Academic Free License v3.0",
        true,
        false
    ],
    "Afmparse": [
        "Afmparse License",
        false,
        false
    ],
    "AGPL-1.0": [
        "Affero General Public License v1.0",
        false,
        true
    ],
    "AGPL-1.0-only": [
        "Affero General Public License v1.0 only",
        false,
        false
    ],
    "AGPL-1.0-or-later": [
        "Affero General Public License v1.0 or later",
        false,
        false
    ],
    "AGPL-3.0": [
        "GNU Affero General Public License v3.0",
        true,
        true
    ],
    "AGPL-3.0-only": [
        "GNU Affero General Public License v3.0 only",
        true,
        false
    ],
    "AGPL-3.0-or-later": [
        "GNU Affero General Public License v3.0 or later",
        true,
        false
    ],
    "Aladdin": [
        "Aladdin Free Public License",
        false,
        false
    ],
    "AMD-newlib": [
        "AMD newlib License",
        false,
        false
    ],
    "AMDPLPA": [
        "AMD's plpa_map.c License",
        false,
        false
    ],
    "AML": [
        "Apple MIT License",
        false,
        false
    ],
    "AML-glslang": [
        "AML glslang variant License",
        false,
        false
    ],
    "AMPAS": [
        "Academy of Motion Picture Arts and Sciences BSD",
        false,
        false
    ],
    "ANTLR-PD": [
        "ANTLR Software Rights Notice",
        false,
        false
    ],
    "ANTLR-PD-fallback": [
        "ANTLR Software Rights Notice with license fallback",
        false,
        false
    ],
    "any-OSI": [
        "Any OSI License",
        false,
        false
    ],
    "any-OSI-perl-modules": [
        "Any OSI License - Perl Modules",
        false,
        false
    ],
    "Apache-1.0": [
        "Apache License 1.0",
        false,
        false
    ],
    "Apache-1.1": [
        "Apache License 1.1",
        true,
        false
    ],
    "Apache-2.0": [
        "Apache License 2.0",
        true,
        false
    ],
    "APAFML": [
        "Adobe Postscript AFM License",
        false,
        false
    ],
    "APL-1.0": [
        "Adaptive Public License 1.0",
        true,
        false
    ],
    "App-s2p": [
        "App::s2p License",
        false,
        false
    ],
    "APSL-1.0": [
        "Apple Public Source License 1.0",
        true,
        false
    ],
    "APSL-1.1": [
        "Apple Public Source License 1.1",
        true,
        false
    ],
    "APSL-1.2": [
        "Apple Public Source License 1.2",
        true,
        false
    ],
    "APSL-2.0": [
        "Apple Public Source License 2.0",
        true,
        false
    ],
    "Arphic-1999": [
        "Arphic Public License",
        false,
        false
    ],
    "Artistic-1.0": [
        "Artistic License 1.0",
        true,
        false
    ],
    "Artistic-1.0-cl8": [
        "Artistic License 1.0 w/clause 8",
        true,
        false
    ],
    "Artistic-1.0-Perl": [
        "Artistic License 1.0 (Perl)",
        true,
        false
    ],
    "Artistic-2.0": [
        "Artistic License 2.0",
        true,
        false
    ],
    "Artistic-dist": [
        "Artistic License 1.0 (dist)",
        false,
        false
    ],
    "ASWF-Digital-Assets-1.0": [
        "ASWF Digital Assets License version 1.0",
        false,
        false
    ],
    "ASWF-Digital-Assets-1.1": [
        "ASWF Digital Assets License 1.1",
        false,
        false
    ],
    "Baekmuk": [
        "Baekmuk License",
        false,
        false
    ],
    "Bahyph": [
        "Bahyph License",
        false,
        false
    ],
    "Barr": [
        "Barr License",
        false,
        false
    ],
    "bcrypt-Solar-Designer": [
        "bcrypt Solar Designer License",
        false,
        false
    ],
    "Beerware": [
        "Beerware License",
        false,
        false
    ],
    "Bitstream-Charter": [
        "Bitstream Charter Font License",
        false,
        false
    ],
    "Bitstream-Vera": [
        "Bitstream Vera Font License",
        false,
        false
    ],
    "BitTorrent-1.0": [
        "BitTorrent Open Source License v1.0",
        false,
        false
    ],
    "BitTorrent-1.1": [
        "BitTorrent Open Source License v1.1",
        false,
        false
    ],
    "blessing": [
        "SQLite Blessing",
        false,
        false
    ],
    "BlueOak-1.0.0": [
        "Blue Oak Model License 1.0.0",
        true,
        false
    ],
    "Boehm-GC": [
        "Boehm-Demers-Weiser GC License",
        false,
        false
    ],
    "Boehm-GC-without-fee": [
        "Boehm-Demers-Weiser GC License (without fee)",
        false,
        false
    ],
    "Borceux": [
        "Borceux license",
        false,
        false
    ],
    "Brian-Gladman-2-Clause": [
        "Brian Gladman 2-Clause License",
        false,
        false
    ],
    "Brian-Gladman-3-Clause": [
        "Brian Gladman 3-Clause License",
        false,
        false
    ],
    "BSD-1-Clause": [
        "BSD 1-Clause License",
        true,
        false
    ],
    "BSD-2-Clause": [
        "BSD 2-Clause \"Simplified\" License",
        true,
        false
    ],
    "BSD-2-Clause-Darwin": [
        "BSD 2-Clause - Ian Darwin variant",
        false,
        false
    ],
    "BSD-2-Clause-first-lines": [
        "BSD 2-Clause - first lines requirement",
        false,
        false
    ],
    "BSD-2-Clause-FreeBSD": [
        "BSD 2-Clause FreeBSD License",
        false,
        true
    ],
    "BSD-2-Clause-NetBSD": [
        "BSD 2-Clause NetBSD License",
        false,
        true
    ],
    "BSD-2-Clause-Patent": [
        "BSD-2-Clause Plus Patent License",
        true,
        false
    ],
    "BSD-2-Clause-pkgconf-disclaimer": [
        "BSD 2-Clause pkgconf disclaimer variant",
        false,
        false
    ],
    "BSD-2-Clause-Views": [
        "BSD 2-Clause with views sentence",
        false,
        false
    ],
    "BSD-3-Clause": [
        "BSD 3-Clause \"New\" or \"Revised\" License",
        true,
        false
    ],
    "BSD-3-Clause-acpica": [
        "BSD 3-Clause acpica variant",
        false,
        false
    ],
    "BSD-3-Clause-Attribution": [
        "BSD with attribution",
        false,
        false
    ],
    "BSD-3-Clause-Clear": [
        "BSD 3-Clause Clear License",
        false,
        false
    ],
    "BSD-3-Clause-flex": [
        "BSD 3-Clause Flex variant",
        false,
        false
    ],
    "BSD-3-Clause-HP": [
        "Hewlett-Packard BSD variant license",
        false,
        false
    ],
    "BSD-3-Clause-LBNL": [
        "Lawrence Berkeley National Labs BSD variant license",
        true,
        false
    ],
    "BSD-3-Clause-Modification": [
        "BSD 3-Clause Modification",
        false,
        false
    ],
    "BSD-3-Clause-No-Military-License": [
        "BSD 3-Clause No Military License",
        false,
        false
    ],
    "BSD-3-Clause-No-Nuclear-License": [
        "BSD 3-Clause No Nuclear License",
        false,
        false
    ],
    "BSD-3-Clause-No-Nuclear-License-2014": [
        "BSD 3-Clause No Nuclear License 2014",
        false,
        false
    ],
    "BSD-3-Clause-No-Nuclear-Warranty": [
        "BSD 3-Clause No Nuclear Warranty",
        false,
        false
    ],
    "BSD-3-Clause-Open-MPI": [
        "BSD 3-Clause Open MPI variant",
        false,
        false
    ],
    "BSD-3-Clause-Sun": [
        "BSD 3-Clause Sun Microsystems",
        false,
        false
    ],
    "BSD-4-Clause": [
        "BSD 4-Clause \"Original\" or \"Old\" License",
        false,
        false
    ],
    "BSD-4-Clause-Shortened": [
        "BSD 4 Clause Shortened",
        false,
        false
    ],
    "BSD-4-Clause-UC": [
        "BSD-4-Clause (University of California-Specific)",
        false,
        false
    ],
    "BSD-4.3RENO": [
        "BSD 4.3 RENO License",
        false,
        false
    ],
    "BSD-4.3TAHOE": [
        "BSD 4.3 TAHOE License",
        false,
        false
    ],
    "BSD-Advertising-Acknowledgement": [
        "BSD Advertising Acknowledgement License",
        false,
        false
    ],
    "BSD-Attribution-HPND-disclaimer": [
        "BSD with Attribution and HPND disclaimer",
        false,
        false
    ],
    "BSD-Inferno-Nettverk": [
        "BSD-Inferno-Nettverk",
        false,
        false
    ],
    "BSD-Protection": [
        "BSD Protection License",
        false,
        false
    ],
    "BSD-Source-beginning-file": [
        "BSD Source Code Attribution - beginning of file variant",
        false,
        false
    ],
    "BSD-Source-Code": [
        "BSD Source Code Attribution",
        false,
        false
    ],
    "BSD-Systemics": [
        "Systemics BSD variant license",
        false,
        false
    ],
    "BSD-Systemics-W3Works": [
        "Systemics W3Works BSD variant license",
        false,
        false
    ],
    "BSL-1.0": [
        "Boost Software License 1.0",
        true,
        false
    ],
    "BUSL-1.1": [
        "Business Source License 1.1",
        false,
        false
    ],
    "bzip2-1.0.5": [
        "bzip2 and libbzip2 License v1.0.5",
        false,
        true
    ],
    "bzip2-1.0.6": [
        "bzip2 and libbzip2 License v1.0.6",
        false,
        false
    ],
    "C-UDA-1.0": [
        "Computational Use of Data Agreement v1.0",
        false,
        false
    ],
    "CAL-1.0": [
        "Cryptographic Autonomy License 1.0",
        true,
        false
    ],
    "CAL-1.0-Combined-Work-Exception": [
        "Cryptographic Autonomy License 1.0 (Combined Work Exception)",
        true,
        false
    ],
    "Caldera": [
        "Caldera License",
        false,
        false
    ],
    "Caldera-no-preamble": [
        "Caldera License (without preamble)",
        false,
        false
    ],
    "Catharon": [
        "Catharon License",
        false,
        false
    ],
    "CATOSL-1.1": [
        "Computer Associates Trusted Open Source License 1.1",
        true,
        false
    ],
    "CC-BY-1.0": [
        "Creative Commons Attribution 1.0 Generic",
        false,
        false
    ],
    "CC-BY-2.0": [
        "Creative Commons Attribution 2.0 Generic",
        false,
        false
    ],
    "CC-BY-2.5": [
        "Creative Commons Attribution 2.5 Generic",
        false,
        false
    ],
    "CC-BY-2.5-AU": [
        "Creative Commons Attribution 2.5 Australia",
        false,
        false
    ],
    "CC-BY-3.0": [
        "Creative Commons Attribution 3.0 Unported",
        false,
        false
    ],
    "CC-BY-3.0-AT": [
        "Creative Commons Attribution 3.0 Austria",
        false,
        false
    ],
    "CC-BY-3.0-AU": [
        "Creative Commons Attribution 3.0 Australia",
        false,
        false
    ],
    "CC-BY-3.0-DE": [
        "Creative Commons Attribution 3.0 Germany",
        false,
        false
    ],
    "CC-BY-3.0-IGO": [
        "Creative Commons Attribution 3.0 IGO",
        false,
        false
    ],
    "CC-BY-3.0-NL": [
        "Creative Commons Attribution 3.0 Netherlands",
        false,
        false
    ],
    "CC-BY-3.0-US": [
        "Creative Commons Attribution 3.0 United States",
        false,
        false
    ],
    "CC-BY-4.0": [
        "Creative Commons Attribution 4.0 International",
        false,
        false
    ],
    "CC-BY-NC-1.0": [
        "Creative Commons Attribution Non Commercial 1.0 Generic",
        false,
        false
    ],
    "CC-BY-NC-2.0": [
        "Creative Commons Attribution Non Commercial 2.0 Generic",
        false,
        false
    ],
    "CC-BY-NC-2.5": [
        "Creative Commons Attribution Non Commercial 2.5 Generic",
        false,
        false
    ],
    "CC-BY-NC-3.0": [
        "Creative Commons Attribution Non Commercial 3.0 Unported",
        false,
        false
    ],
    "CC-BY-NC-3.0-DE": [
        "Creative Commons Attribution Non Commercial 3.0 Germany",
        false,
        false
    ],
    "CC-BY-NC-4.0": [
        "Creative Commons Attribution Non Commercial 4.0 International",
        false,
        false
    ],
    "CC-BY-NC-ND-1.0": [
        "Creative Commons Attribution Non Commercial No Derivatives 1.0 Generic",
        false,
        false
    ],
    "CC-BY-NC-ND-2.0": [
        "Creative Commons Attribution Non Commercial No Derivatives 2.0 Generic",
        false,
        false
    ],
    "CC-BY-NC-ND-2.5": [
        "Creative Commons Attribution Non Commercial No Derivatives 2.5 Generic",
        false,
        false
    ],
    "CC-BY-NC-ND-3.0": [
        "Creative Commons Attribution Non Commercial No Derivatives 3.0 Unported",
        false,
        false
    ],
    "CC-BY-NC-ND-3.0-DE": [
        "Creative Commons Attribution Non Commercial No Derivatives 3.0 Germany",
        false,
        false
    ],
    "CC-BY-NC-ND-3.0-IGO": [
        "Creative Commons Attribution Non Commercial No Derivatives 3.0 IGO",
        false,
        false
    ],
    "CC-BY-NC-ND-4.0": [
        "Creative Commons Attribution Non Commercial No Derivatives 4.0 International",
        false,
        false
    ],
    "CC-BY-NC-SA-1.0": [
        "Creative Commons Attribution Non Commercial Share Alike 1.0 Generic",
        false,
        false
    ],
    "CC-BY-NC-SA-2.0": [
        "Creative Commons Attribution Non Commercial Share Alike 2.0 Generic",
        false,
        false
    ],
    "CC-BY-NC-SA-2.0-DE": [
        "Creative Commons Attribution Non Commercial Share Alike 2.0 Germany",
        false,
        false
    ],
    "CC-BY-NC-SA-2.0-FR": [
        "Creative Commons Attribution-NonCommercial-ShareAlike 2.0 France",
        false,
        false
    ],
    "CC-BY-NC-SA-2.0-UK": [
        "Creative Commons Attribution Non Commercial Share Alike 2.0 England and Wales",
        false,
        false
    ],
    "CC-BY-NC-SA-2.5": [
        "Creative Commons Attribution Non Commercial Share Alike 2.5 Generic",
        false,
        false
    ],
    "CC-BY-NC-SA-3.0": [
        "Creative Commons Attribution Non Commercial Share Alike 3.0 Unported",
        false,
        false
    ],
    "CC-BY-NC-SA-3.0-DE": [
        "Creative Commons Attribution Non Commercial Share Alike 3.0 Germany",
        false,
        false
    ],
    "CC-BY-NC-SA-3.0-IGO": [
        "Creative Commons Attribution Non Commercial Share Alike 3.0 IGO",
        false,
        false
    ],
    "CC-BY-NC-SA-4.0": [
        "Creative Commons Attribution Non Commercial Share Alike 4.0 International",
        false,
        false
    ],
    "CC-BY-ND-1.0": [
        "Creative Commons Attribution No Derivatives 1.0 Generic",
        false,
        false
    ],
    "CC-BY-ND-2.0": [
        "Creative Commons Attribution No Derivatives 2.0 Generic",
        false,
        false
    ],
    "CC-BY-ND-2.5": [
        "Creative Commons Attribution No Derivatives 2.5 Generic",
        false,
        false
    ],
    "CC-BY-ND-3.0": [
        "Creative Commons Attribution No Derivatives 3.0 Unported",
        false,
        false
    ],
    "CC-BY-ND-3.0-DE": [
        "Creative Commons Attribution No Derivatives 3.0 Germany",
        false,
        false
    ],
    "CC-BY-ND-4.0": [
        "Creative Commons Attribution No Derivatives 4.0 International",
        false,
        false
    ],
    "CC-BY-SA-1.0": [
        "Creative Commons Attribution Share Alike 1.0 Generic",
        false,
        false
    ],
    "CC-BY-SA-2.0": [
        "Creative Commons Attribution Share Alike 2.0 Generic",
        false,
        false
    ],
    "CC-BY-SA-2.0-UK": [
        "Creative Commons Attribution Share Alike 2.0 England and Wales",
        false,
        false
    ],
    "CC-BY-SA-2.1-JP": [
        "Creative Commons Attribution Share Alike 2.1 Japan",
        false,
        false
    ],
    "CC-BY-SA-2.5": [
        "Creative Commons Attribution Share Alike 2.5 Generic",
        false,
        false
    ],
    "CC-BY-SA-3.0": [
        "Creative Commons Attribution Share Alike 3.0 Unported",
        false,
        false
    ],
    "CC-BY-SA-3.0-AT": [
        "Creative Commons Attribution Share Alike 3.0 Austria",
        false,
        false
    ],
    "CC-BY-SA-3.0-DE": [
        "Creative Commons Attribution Share Alike 3.0 Germany",
        false,
        false
    ],
    "CC-BY-SA-3.0-IGO": [
        "Creative Commons Attribution-ShareAlike 3.0 IGO",
        false,
        false
    ],
    "CC-BY-SA-4.0": [
        "Creative Commons Attribution Share Alike 4.0 International",
        false,
        false
    ],
    "CC-PDDC": [
        "Creative Commons Public Domain Dedication and Certification",
        false,
        false
    ],
    "CC-PDM-1.0": [
        "Creative    Commons Public Domain Mark 1.0 Universal",
        false,
        false
    ],
    "CC-SA-1.0": [
        "Creative Commons Share Alike 1.0 Generic",
        false,
        false
    ],
    "CC0-1.0": [
        "Creative Commons Zero v1.0 Universal",
        false,
        false
    ],
    "CDDL-1.0": [
        "Common Development and Distribution License 1.0",
        true,
        false
    ],
    "CDDL-1.1": [
        "Common Development and Distribution License 1.1",
        false,
        false
    ],
    "CDL-1.0": [
        "Common Documentation License 1.0",
        false,
        false
    ],
    "CDLA-Permissive-1.0": [
        "Community Data License Agreement Permissive 1.0",
        false,
        false
    ],
    "CDLA-Permissive-2.0": [
        "Community Data License Agreement Permissive 2.0",
        false,
        false
    ],
    "CDLA-Sharing-1.0": [
        "Community Data License Agreement Sharing 1.0",
        false,
        false
    ],
    "CECILL-1.0": [
        "CeCILL Free Software License Agreement v1.0",
        false,
        false
    ],
    "CECILL-1.1": [
        "CeCILL Free Software License Agreement v1.1",
        false,
        false
    ],
    "CECILL-2.0": [
        "CeCILL Free Software License Agreement v2.0",
        false,
        false
    ],
    "CECILL-2.1": [
        "CeCILL Free Software License Agreement v2.1",
        true,
        false
    ],
    "CECILL-B": [
        "CeCILL-B Free Software License Agreement",
        false,
        false
    ],
    "CECILL-C": [
        "CeCILL-C Free Software License Agreement",
        false,
        false
    ],
    "CERN-OHL-1.1": [
        "CERN Open Hardware Licence v1.1",
        false,
        false
    ],
    "CERN-OHL-1.2": [
        "CERN Open Hardware Licence v1.2",
        false,
        false
    ],
    "CERN-OHL-P-2.0": [
        "CERN Open Hardware Licence Version 2 - Permissive",
        true,
        false
    ],
    "CERN-OHL-S-2.0": [
        "CERN Open Hardware Licence Version 2 - Strongly Reciprocal",
        true,
        false
    ],
    "CERN-OHL-W-2.0": [
        "CERN Open Hardware Licence Version 2 - Weakly Reciprocal",
        true,
        false
    ],
    "CFITSIO": [
        "CFITSIO License",
        false,
        false
    ],
    "check-cvs": [
        "check-cvs License",
        false,
        false
    ],
    "checkmk": [
        "Checkmk License",
        false,
        false
    ],
    "ClArtistic": [
        "Clarified Artistic License",
        false,
        false
    ],
    "Clips": [
        "Clips License",
        false,
        false
    ],
    "CMU-Mach": [
        "CMU Mach License",
        false,
        false
    ],
    "CMU-Mach-nodoc": [
        "CMU    Mach - no notices-in-documentation variant",
        false,
        false
    ],
    "CNRI-Jython": [
        "CNRI Jython License",
        false,
        false
    ],
    "CNRI-Python": [
        "CNRI Python License",
        true,
        false
    ],
    "CNRI-Python-GPL-Compatible": [
        "CNRI Python Open Source GPL Compatible License Agreement",
        false,
        false
    ],
    "COIL-1.0": [
        "Copyfree Open Innovation License",
        false,
        false
    ],
    "Community-Spec-1.0": [
        "Community Specification License 1.0",
        false,
        false
    ],
    "Condor-1.1": [
        "Condor Public License v1.1",
        false,
        false
    ],
    "copyleft-next-0.3.0": [
        "copyleft-next 0.3.0",
        false,
        false
    ],
    "copyleft-next-0.3.1": [
        "copyleft-next 0.3.1",
        false,
        false
    ],
    "Cornell-Lossless-JPEG": [
        "Cornell Lossless JPEG License",
        false,
        false
    ],
    "CPAL-1.0": [
        "Common Public Attribution License 1.0",
        true,
        false
    ],
    "CPL-1.0": [
        "Common Public License 1.0",
        true,
        false
    ],
    "CPOL-1.02": [
        "Code Project Open License 1.02",
        false,
        false
    ],
    "Cronyx": [
        "Cronyx License",
        false,
        false
    ],
    "Crossword": [
        "Crossword License",
        false,
        false
    ],
    "CryptoSwift": [
        "CryptoSwift License",
        false,
        false
    ],
    "CrystalStacker": [
        "CrystalStacker License",
        false,
        false
    ],
    "CUA-OPL-1.0": [
        "CUA Office Public License v1.0",
        true,
        false
    ],
    "Cube": [
        "Cube License",
        false,
        false
    ],
    "curl": [
        "curl License",
        false,
        false
    ],
    "cve-tou": [
        "Common Vulnerability Enumeration ToU License",
        false,
        false
    ],
    "D-FSL-1.0": [
        "Deutsche Freie Software Lizenz",
        false,
        false
    ],
    "DEC-3-Clause": [
        "DEC 3-Clause License",
        false,
        false
    ],
    "diffmark": [
        "diffmark license",
        false,
        false
    ],
    "DL-DE-BY-2.0": [
        "Data licence Germany \u2013 attribution \u2013 version 2.0",
        false,
        false
    ],
    "DL-DE-ZERO-2.0": [
        "Data licence Germany \u2013 zero \u2013 version 2.0",
        false,
        false
    ],
    "DOC": [
        "DOC License",
        false,
        false
    ],
    "DocBook-DTD": [
        "DocBook DTD License",
        false,
        false
    ],
    "DocBook-Schema": [
        "DocBook Schema License",
        false,
        false
    ],
    "DocBook-Stylesheet": [
        "DocBook Stylesheet License",
        false,
        false
    ],
    "DocBook-XML": [
        "DocBook XML License",
        false,
        false
    ],
    "Dotseqn": [
        "Dotseqn License",
        false,
        false
    ],
    "DRL-1.0": [
        "Detection Rule License 1.0",
        false,
        false
    ],
    "DRL-1.1": [
        "Detection Rule License 1.1",
        false,
        false
    ],
    "DSDP": [
        "DSDP License",
        false,
        false
    ],
    "dtoa": [
        "David M. Gay dtoa License",
        false,
        false
    ],
    "dvipdfm": [
        "dvipdfm License",
        false,
        false
    ],
    "ECL-1.0": [
        "Educational Community License v1.0",
        true,
        false
    ],
    "ECL-2.0": [
        "Educational Community License v2.0",
        true,
        false
    ],
    "eCos-2.0": [
        "eCos license version 2.0",
        false,
        true
    ],
    "EFL-1.0": [
        "Eiffel Forum License v1.0",
        true,
        false
    ],
    "EFL-2.0": [
        "Eiffel Forum License v2.0",
        true,
        false
    ],
    "eGenix": [
        "eGenix.com Public License 1.1.0",
        false,
        false
    ],
    "Elastic-2.0": [
        "Elastic License 2.0",
        false,
        false
    ],
    "Entessa": [
        "Entessa Public License v1.0",
        true,
        false
    ],
    "EPICS": [
        "EPICS Open License",
        false,
        false
    ],
    "EPL-1.0": [
        "Eclipse Public License 1.0",
        true,
        false
    ],
    "EPL-2.0": [
        "Eclipse Public License 2.0",
        true,
        false
    ],
    "ErlPL-1.1": [
        "Erlang Public License v1.1",
        false,
        false
    ],
    "etalab-2.0": [
        "Etalab Open License 2.0",
        false,
        false
    ],
    "EUDatagrid": [
        "EU DataGrid Software License",
        true,
        false
    ],
    "EUPL-1.0": [
        "European Union Public License 1.0",
        false,
        false
    ],
    "EUPL-1.1": [
        "European Union Public License 1.1",
        true,
        false
    ],
    "EUPL-1.2": [
        "European Union Public License 1.2",
        true,
        false
    ],
    "Eurosym": [
        "Eurosym License",
        false,
        false
    ],
    "Fair": [
        "Fair License",
        true,
        false
    ],
    "FBM": [
        "Fuzzy Bitmap License",
        false,
        false
    ],
    "FDK-AAC": [
        "Fraunhofer FDK AAC Codec Library",
        false,
        false
    ],
    "Ferguson-Twofish": [
        "Ferguson Twofish License",
        false,
        false
    ],
    "Frameworx-1.0": [
        "Frameworx Open License 1.0",
        true,
        false
    ],
    "FreeBSD-DOC": [
        "FreeBSD Documentation License",
        false,
        false
    ],
    "FreeImage": [
        "FreeImage Public License v1.0",
        false,
        false
    ],
    "FSFAP": [
        "FSF All Permissive License",
        false,
        false
    ],
    "FSFAP-no-warranty-disclaimer": [
        "FSF All Permissive License (without Warranty)",
        false,
        false
    ],
    "FSFUL": [
        "FSF Unlimited License",
        false,
        false
    ],
    "FSFULLR": [
        "FSF Unlimited License (with License Retention)",
        false,
        false
    ],
    "FSFULLRWD": [
        "FSF Unlimited License (With License Retention and Warranty Disclaimer)",
        false,
        false
    ],
    "FSL-1.1-ALv2": [
        "Functional Source License, Version 1.1, ALv2 Future License",
        false,
        false
    ],
    "FSL-1.1-MIT": [
        "Functional Source License, Version 1.1, MIT Future License",
        false,
        false
    ],
    "FTL": [
        "Freetype Project License",
        false,
        false
    ],
    "Furuseth": [
        "Furuseth License",
        false,
        false
    ],
    "fwlw": [
        "fwlw License",
        false,
        false
    ],
    "Game-Programming-Gems": [
        "Game Programming Gems License",
        false,
        false
    ],
    "GCR-docs": [
        "Gnome GCR Documentation License",
        false,
        false
    ],
    "GD": [
        "GD License",
        false,
        false
    ],
    "generic-xts": [
        "Generic XTS License",
        false,
        false
    ],
    "GFDL-1.1": [
        "GNU Free Documentation License v1.1",
        false,
        true
    ],
    "GFDL-1.1-invariants-only": [
        "GNU Free Documentation License v1.1 only - invariants",
        false,
        false
    ],
    "GFDL-1.1-invariants-or-later": [
        "GNU Free Documentation License v1.1 or later - invariants",
        false,
        false
    ],
    "GFDL-1.1-no-invariants-only": [
        "GNU Free Documentation License v1.1 only - no invariants",
        false,
        false
    ],
    "GFDL-1.1-no-invariants-or-later": [
        "GNU Free Documentation License v1.1 or later - no invariants",
        false,
        false
    ],
    "GFDL-1.1-only": [
        "GNU Free Documentation License v1.1 only",
        false,
        false
    ],
    "GFDL-1.1-or-later": [
        "GNU Free Documentation License v1.1 or later",
        false,
        false
    ],
    "GFDL-1.2": [
        "GNU Free Documentation License v1.2",
        false,
        true
    ],
    "GFDL-1.2-invariants-only": [
        "GNU Free Documentation License v1.2 only - invariants",
        false,
        false
    ],
    "GFDL-1.2-invariants-or-later": [
        "GNU Free Documentation License v1.2 or later - invariants",
        false,
        false
    ],
    "GFDL-1.2-no-invariants-only": [
        "GNU Free Documentation License v1.2 only - no invariants",
        false,
        false
    ],
    "GFDL-1.2-no-invariants-or-later": [
        "GNU Free Documentation License v1.2 or later - no invariants",
        false,
        false
    ],
    "GFDL-1.2-only": [
        "GNU Free Documentation License v1.2 only",
        false,
        false
    ],
    "GFDL-1.2-or-later": [
        "GNU Free Documentation License v1.2 or later",
        false,
        false
    ],
    "GFDL-1.3": [
        "GNU Free Documentation License v1.3",
        false,
        true
    ],
    "GFDL-1.3-invariants-only": [
        "GNU Free Documentation License v1.3 only - invariants",
        false,
        false
    ],
    "GFDL-1.3-invariants-or-later": [
        "GNU Free Documentation License v1.3 or later - invariants",
        false,
        false
    ],
    "GFDL-1.3-no-invariants-only": [
        "GNU Free Documentation License v1.3 only - no invariants",
        false,
        false
    ],
    "GFDL-1.3-no-invariants-or-later": [
        "GNU Free Documentation License v1.3 or later - no invariants",
        false,
        false
    ],
    "GFDL-1.3-only": [
        "GNU Free Documentation License v1.3 only",
        false,
        false
    ],
    "GFDL-1.3-or-later": [
        "GNU Free Documentation License v1.3 or later",
        false,
        false
    ],
    "Giftware": [
        "Giftware License",
        false,
        false
    ],
    "GL2PS": [
        "GL2PS License",
        false,
        false
    ],
    "Glide": [
        "3dfx Glide License",
        false,
        false
    ],
    "Glulxe": [
        "Glulxe License",
        false,
        false
    ],
    "GLWTPL": [
        "Good Luck With That Public License",
        false,
        false
    ],
    "gnuplot": [
        "gnuplot License",
        false,
        false
    ],
    "GPL-1.0": [
        "GNU General Public License v1.0 only",
        false,
        true
    ],
    "GPL-1.0+": [
        "GNU General Public License v1.0 or later",
        false,
        true
    ],
    "GPL-1.0-only": [
        "GNU General Public License v1.0 only",
        false,
        false
    ],
    "GPL-1.0-or-later": [
        "GNU General Public License v1.0 or later",
        false,
        false
    ],
    "GPL-2.0": [
        "GNU General Public License v2.0 only",
        true,
        true
    ],
    "GPL-2.0+": [
        "GNU General Public License v2.0 or later",
        true,
        true
    ],
    "GPL-2.0-only": [
        "GNU General Public License v2.0 only",
        true,
        false
    ],
    "GPL-2.0-or-later": [
        "GNU General Public License v2.0 or later",
        true,
        false
    ],
    "GPL-2.0-with-autoconf-exception": [
        "GNU General Public License v2.0 w/Autoconf exception",
        false,
        true
    ],
    "GPL-2.0-with-bison-exception": [
        "GNU General Public License v2.0 w/Bison exception",
        false,
        true
    ],
    "GPL-2.0-with-classpath-exception": [
        "GNU General Public License v2.0 w/Classpath exception",
        false,
        true
    ],
    "GPL-2.0-with-font-exception": [
        "GNU General Public License v2.0 w/Font exception",
        false,
        true
    ],
    "GPL-2.0-with-GCC-exception": [
        "GNU General Public License v2.0 w/GCC Runtime Library exception",
        false,
        true
    ],
    "GPL-3.0": [
        "GNU General Public License v3.0 only",
        true,
        true
    ],
    "GPL-3.0+": [
        "GNU General Public License v3.0 or later",
        true,
        true
    ],
    "GPL-3.0-only": [
        "GNU General Public License v3.0 only",
        true,
        false
    ],
    "GPL-3.0-or-later": [
        "GNU General Public License v3.0 or later",
        true,
        false
    ],
    "GPL-3.0-with-autoconf-exception": [
        "GNU General Public License v3.0 w/Autoconf exception",
        false,
        true
    ],
    "GPL-3.0-with-GCC-exception": [
        "GNU General Public License v3.0 w/GCC Runtime Library exception",
        true,
        true
    ],
    "Graphics-Gems": [
        "Graphics Gems License",
        false,
        false
    ],
    "gSOAP-1.3b": [
        "gSOAP Public License v1.3b",
        false,
        false
    ],
    "gtkbook": [
        "gtkbook License",
        false,
        false
    ],
    "Gutmann": [
        "Gutmann License",
        false,
        false
    ],
    "HaskellReport": [
        "Haskell Language Report License",
        false,
        false
    ],
    "hdparm": [
        "hdparm License",
        false,
        false
    ],
    "HIDAPI": [
        "HIDAPI License",
        false,
        false
    ],
    "Hippocratic-2.1": [
        "Hippocratic License 2.1",
        false,
        false
    ],
    "HP-1986": [
        "Hewlett-Packard 1986 License",
        false,
        false
    ],
    "HP-1989": [
        "Hewlett-Packard 1989 License",
        false,
        false
    ],
    "HPND": [
        "Historical Permission Notice and Disclaimer",
        true,
        false
    ],
    "HPND-DEC": [
        "Historical Permission Notice and Disclaimer - DEC variant",
        false,
        false
    ],
    "HPND-doc": [
        "Historical Permission Notice and Disclaimer - documentation variant",
        false,
        false
    ],
    "HPND-doc-sell": [
        "Historical Permission Notice and Disclaimer - documentation sell variant",
        false,
        false
    ],
    "HPND-export-US": [
        "HPND with US Government export control warning",
        false,
        false
    ],
    "HPND-export-US-acknowledgement": [
        "HPND with US Government export control warning and acknowledgment",
        false,
        false
    ],
    "HPND-export-US-modify": [
        "HPND with US Government export control warning and modification rqmt",
        false,
        false
    ],
    "HPND-export2-US": [
        "HPND with US Government export control and 2 disclaimers",
        false,
        false
    ],
    "HPND-Fenneberg-Livingston": [
        "Historical Permission Notice and Disclaimer - Fenneberg-Livingston variant",
        false,
        false
    ],
    "HPND-INRIA-IMAG": [
        "Historical Permission Notice and Disclaimer    - INRIA-IMAG variant",
        false,
        false
    ],
    "HPND-Intel": [
        "Historical Permission Notice and Disclaimer - Intel variant",
        false,
        false
    ],
    "HPND-Kevlin-Henney": [
        "Historical Permission Notice and Disclaimer - Kevlin Henney variant",
        false,
        false
    ],
    "HPND-Markus-Kuhn": [
        "Historical Permission Notice and Disclaimer - Markus Kuhn variant",
        false,
        false
    ],
    "HPND-merchantability-variant": [
        "Historical Permission Notice and Disclaimer - merchantability variant",
        false,
        false
    ],
    "HPND-MIT-disclaimer": [
        "Historical Permission Notice and Disclaimer with MIT disclaimer",
        false,
        false
    ],
    "HPND-Netrek": [
        "Historical Permission Notice and Disclaimer - Netrek variant",
        false,
        false
    ],
    "HPND-Pbmplus": [
        "Historical Permission Notice and Disclaimer - Pbmplus variant",
        false,
        false
    ],
    "HPND-sell-MIT-disclaimer-xserver": [
        "Historical Permission Notice and Disclaimer - sell xserver variant with MIT disclaimer",
        false,
        false
    ],
    "HPND-sell-regexpr": [
        "Historical Permission Notice and Disclaimer - sell regexpr variant",
        false,
        false
    ],
    "HPND-sell-variant": [
        "Historical Permission Notice and Disclaimer - sell variant",
        false,
        false
    ],
    "HPND-sell-variant-MIT-disclaimer": [
        "HPND sell variant with MIT disclaimer",
        false,
        false
    ],
    "HPND-sell-variant-MIT-disclaimer-rev": [
        "HPND sell variant with MIT disclaimer - reverse",
        false,
        false
    ],
    "HPND-UC": [
        "Historical Permission Notice and Disclaimer - University of California variant",
        false,
        false
    ],
    "HPND-UC-export-US": [
        "Historical Permission Notice and Disclaimer - University of California, US export warning",
        false,
        false
    ],
    "HTMLTIDY": [
        "HTML Tidy License",
        false,
        false
    ],
    "IBM-pibs": [
        "IBM PowerPC Initialization and Boot Software",
        false,
        false
    ],
    "ICU": [
        "ICU License",
        true,
        false
    ],
    "IEC-Code-Components-EULA": [
        "IEC    Code Components End-user licence agreement",
        false,
        false
    ],
    "IJG": [
        "Independent JPEG Group License",
        false,
        false
    ],
    "IJG-short": [
        "Independent JPEG Group License - short",
        false,
        false
    ],
    "ImageMagick": [
        "ImageMagick License",
        false,
        false
    ],
    "iMatix": [
        "iMatix Standard Function Library Agreement",
        false,
        false
    ],
    "Imlib2": [
        "Imlib2 License",
        false,
        false
    ],
    "Info-ZIP": [
        "Info-ZIP License",
        false,
        false
    ],
    "Inner-Net-2.0": [
        "Inner Net License v2.0",
        false,
        false
    ],
    "InnoSetup": [
        "Inno Setup License",
        false,
        false
    ],
    "Intel": [
        "Intel Open Source License",
        true,
        false
    ],
    "Intel-ACPI": [
        "Intel ACPI Software License Agreement",
        false,
        false
    ],
    "Interbase-1.0": [
        "Interbase Public License v1.0",
        false,
        false
    ],
    "IPA": [
        "IPA Font License",
        true,
        false
    ],
    "IPL-1.0": [
        "IBM Public License v1.0",
        true,
        false
    ],
    "ISC": [
        "ISC License",
        true,
        false
    ],
    "ISC-Veillard": [
        "ISC Veillard variant",
        false,
        false
    ],
    "Jam": [
        "Jam License",
        true,
        false
    ],
    "JasPer-2.0": [
        "JasPer License",
        false,
        false
    ],
    "jove": [
        "Jove License",
        false,
        false
    ],
    "JPL-image": [
        "JPL Image Use Policy",
        false,
        false
    ],
    "JPNIC": [
        "Japan Network Information Center License",
        false,
        false
    ],
    "JSON": [
        "JSON License",
        false,
        false
    ],
    "Kastrup": [
        "Kastrup License",
        false,
        false
    ],
    "Kazlib": [
        "Kazlib License",
        false,
        false
    ],
    "Knuth-CTAN": [
        "Knuth CTAN License",
        false,
        false
    ],
    "LAL-1.2": [
        "Licence Art Libre 1.2",
        false,
        false
    ],
    "LAL-1.3": [
        "Licence Art Libre 1.3",
        false,
        false
    ],
    "Latex2e": [
        "Latex2e License",
        false,
        false
    ],
    "Latex2e-translated-notice": [
        "Latex2e with translated notice permission",
        false,
        false
    ],
    "Leptonica": [
        "Leptonica License",
        false,
        false
    ],
    "LGPL-2.0": [
        "GNU Library General Public License v2 only",
        true,
        true
    ],
    "LGPL-2.0+": [
        "GNU Library General Public License v2 or later",
        true,
        true
    ],
    "LGPL-2.0-only": [
        "GNU Library General Public License v2 only",
        true,
        false
    ],
    "LGPL-2.0-or-later": [
        "GNU Library General Public License v2 or later",
        true,
        false
    ],
    "LGPL-2.1": [
        "GNU Lesser General Public License v2.1 only",
        true,
        true
    ],
    "LGPL-2.1+": [
        "GNU Lesser General Public License v2.1 or later",
        true,
        true
    ],
    "LGPL-2.1-only": [
        "GNU Lesser General Public License v2.1 only",
        true,
        false
    ],
    "LGPL-2.1-or-later": [
        "GNU Lesser General Public License v2.1 or later",
        true,
        false
    ],
    "LGPL-3.0": [
        "GNU Lesser General Public License v3.0 only",
        true,
        true
    ],
    "LGPL-3.0+": [
        "GNU Lesser General Public License v3.0 or later",
        true,
        true
    ],
    "LGPL-3.0-only": [
        "GNU Lesser General Public License v3.0 only",
        true,
        false
    ],
    "LGPL-3.0-or-later": [
        "GNU Lesser General Public License v3.0 or later",
        true,
        false
    ],
    "LGPLLR": [
        "Lesser General Public License For Linguistic Resources",
        false,
        false
    ],
    "Libpng": [
        "libpng License",
        false,
        false
    ],
    "libpng-2.0": [
        "PNG Reference Library version 2",
        false,
        false
    ],
    "libselinux-1.0": [
        "libselinux public domain notice",
        false,
        false
    ],
    "libtiff": [
        "libtiff License",
        false,
        false
    ],
    "libutil-David-Nugent": [
        "libutil David Nugent License",
        false,
        false
    ],
    "LiLiQ-P-1.1": [
        "Licence Libre du Qu\u00e9bec \u2013 Permissive version 1.1",
        true,
        false
    ],
    "LiLiQ-R-1.1": [
        "Licence Libre du Qu\u00e9bec \u2013 R\u00e9ciprocit\u00e9 version 1.1",
        true,
        false
    ],
    "LiLiQ-Rplus-1.1": [
        "Licence Libre du Qu\u00e9bec \u2013 R\u00e9ciprocit\u00e9 forte version 1.1",
        true,
        false
    ],
    "Linux-man-pages-1-para": [
        "Linux man-pages - 1 paragraph",
        false,
        false
    ],
    "Linux-man-pages-copyleft": [
        "Linux man-pages Copyleft",
        false,
        false
    ],
    "Linux-man-pages-copyleft-2-para": [
        "Linux man-pages Copyleft - 2 paragraphs",
        false,
        false
    ],
    "Linux-man-pages-copyleft-var": [
        "Linux man-pages Copyleft Variant",
        false,
        false
    ],
    "Linux-OpenIB": [
        "Linux Kernel Variant of OpenIB.org license",
        false,
        false
    ],
    "LOOP": [
        "Common Lisp LOOP License",
        false,
        false
    ],
    "LPD-document": [
        "LPD Documentation License",
        false,
        false
    ],
    "LPL-1.0": [
        "Lucent Public License Version 1.0",
        true,
        false
    ],
    "LPL-1.02": [
        "Lucent Public License v1.02",
        true,
        false
    ],
    "LPPL-1.0": [
        "LaTeX Project Public License v1.0",
        false,
        false
    ],
    "LPPL-1.1": [
        "LaTeX Project Public License v1.1",
        false,
        false
    ],
    "LPPL-1.2": [
        "LaTeX Project Public License v1.2",
        false,
        false
    ],
    "LPPL-1.3a": [
        "LaTeX Project Public License v1.3a",
        false,
        false
    ],
    "LPPL-1.3c": [
        "LaTeX Project Public License v1.3c",
        true,
        false
    ],
    "lsof": [
        "lsof License",
        false,
        false
    ],
    "Lucida-Bitmap-Fonts": [
        "Lucida Bitmap Fonts License",
        false,
        false
    ],
    "LZMA-SDK-9.11-to-9.20": [
        "LZMA SDK License (versions 9.11 to 9.20)",
        false,
        false
    ],
    "LZMA-SDK-9.22": [
        "LZMA SDK License (versions 9.22 and beyond)",
        false,
        false
    ],
    "Mackerras-3-Clause": [
        "Mackerras 3-Clause License",
        false,
        false
    ],
    "Mackerras-3-Clause-acknowledgment": [
        "Mackerras 3-Clause - acknowledgment variant",
        false,
        false
    ],
    "magaz": [
        "magaz License",
        false,
        false
    ],
    "mailprio": [
        "mailprio License",
        false,
        false
    ],
    "MakeIndex": [
        "MakeIndex License",
        false,
        false
    ],
    "man2html": [
        "man2html License",
        false,
        false
    ],
    "Martin-Birgmeier": [
        "Martin Birgmeier License",
        false,
        false
    ],
    "McPhee-slideshow": [
        "McPhee Slideshow License",
        false,
        false
    ],
    "metamail": [
        "metamail License",
        false,
        false
    ],
    "Minpack": [
        "Minpack License",
        false,
        false
    ],
    "MIPS": [
        "MIPS License",
        false,
        false
    ],
    "MirOS": [
        "The MirOS Licence",
        true,
        false
    ],
    "MIT": [
        "MIT License",
        true,
        false
    ],
    "MIT-0": [
        "MIT No Attribution",
        true,
        false
    ],
    "MIT-advertising": [
        "Enlightenment License (e16)",
        false,
        false
    ],
    "MIT-Click": [
        "MIT Click License",
        false,
        false
    ],
    "MIT-CMU": [
        "CMU License",
        false,
        false
    ],
    "MIT-enna": [
        "enna License",
        false,
        false
    ],
    "MIT-feh": [
        "feh License",
        false,
        false
    ],
    "MIT-Festival": [
        "MIT Festival Variant",
        false,
        false
    ],
    "MIT-Khronos-old": [
        "MIT Khronos - old variant",
        false,
        false
    ],
    "MIT-Modern-Variant": [
        "MIT License Modern Variant",
        true,
        false
    ],
    "MIT-open-group": [
        "MIT Open Group variant",
        false,
        false
    ],
    "MIT-testregex": [
        "MIT testregex Variant",
        false,
        false
    ],
    "MIT-Wu": [
        "MIT Tom Wu Variant",
        false,
        false
    ],
    "MITNFA": [
        "MIT +no-false-attribs license",
        false,
        false
    ],
    "MMIXware": [
        "MMIXware License",
        false,
        false
    ],
    "Motosoto": [
        "Motosoto License",
        true,
        false
    ],
    "MPEG-SSG": [
        "MPEG Software Simulation",
        false,
        false
    ],
    "mpi-permissive": [
        "mpi Permissive License",
        false,
        false
    ],
    "mpich2": [
        "mpich2 License",
        false,
        false
    ],
    "MPL-1.0": [
        "Mozilla Public License 1.0",
        true,
        false
    ],
    "MPL-1.1": [
        "Mozilla Public License 1.1",
        true,
        false
    ],
    "MPL-2.0": [
        "Mozilla Public License 2.0",
        true,
        false
    ],
    "MPL-2.0-no-copyleft-exception": [
        "Mozilla Public License 2.0 (no copyleft exception)",
        true,
        false
    ],
    "mplus": [
        "mplus Font License",
        false,
        false
    ],
    "MS-LPL": [
        "Microsoft Limited Public License",
        false,
        false
    ],
    "MS-PL": [
        "Microsoft Public License",
        true,
        false
    ],
    "MS-RL": [
        "Microsoft Reciprocal License",
        true,
        false
    ],
    "MTLL": [
        "Matrix Template Library License",
        false,
        false
    ],
    "MulanPSL-1.0": [
        "Mulan Permissive Software License, Version 1",
        false,
        false
    ],
    "MulanPSL-2.0": [
        "Mulan Permissive Software License, Version 2",
        true,
        false
    ],
    "Multics": [
        "Multics License",
        true,
        false
    ],
    "Mup": [
        "Mup License",
        false,
        false
    ],
    "NAIST-2003": [
        "Nara Institute of Science and Technology License (2003)",
        false,
        false
    ],
    "NASA-1.3": [
        "NASA Open Source Agreement 1.3",
        true,
        false
    ],
    "Naumen": [
        "Naumen Public License",
        true,
        false
    ],
    "NBPL-1.0": [
        "Net Boolean Public License v1",
        false,
        false
    ],
    "NCBI-PD": [
        "NCBI Public Domain Notice",
        false,
        false
    ],
    "NCGL-UK-2.0": [
        "Non-Commercial Government Licence",
        false,
        false
    ],
    "NCL": [
        "NCL Source Code License",
        false,
        false
    ],
    "NCSA": [
        "University of Illinois/NCSA Open Source License",
        true,
        false
    ],
    "Net-SNMP": [
        "Net-SNMP License",
        false,
        true
    ],
    "NetCDF": [
        "NetCDF license",
        false,
        false
    ],
    "Newsletr": [
        "Newsletr License",
        false,
        false
    ],
    "NGPL": [
        "Nethack General Public License",
        true,
        false
    ],
    "NICTA-1.0": [
        "NICTA Public Software License, Version 1.0",
        false,
        false
    ],
    "NIST-PD": [
        "NIST Public Domain Notice",
        false,
        false
    ],
    "NIST-PD-fallback": [
        "NIST Public Domain Notice with license fallback",
        false,
        false
    ],
    "NIST-Software": [
        "NIST Software License",
        false,
        false
    ],
    "NLOD-1.0": [
        "Norwegian Licence for Open Government Data (NLOD) 1.0",
        false,
        false
    ],
    "NLOD-2.0": [
        "Norwegian Licence for Open Government Data (NLOD) 2.0",
        false,
        false
    ],
    "NLPL": [
        "No Limit Public License",
        false,
        false
    ],
    "Nokia": [
        "Nokia Open Source License",
        true,
        false
    ],
    "NOSL": [
        "Netizen Open Source License",
        false,
        false
    ],
    "Noweb": [
        "Noweb License",
        false,
        false
    ],
    "NPL-1.0": [
        "Netscape Public License v1.0",
        false,
        false
    ],
    "NPL-1.1": [
        "Netscape Public License v1.1",
        false,
        false
    ],
    "NPOSL-3.0": [
        "Non-Profit Open Software License 3.0",
        true,
        false
    ],
    "NRL": [
        "NRL License",
        false,
        false
    ],
    "NTIA-PD": [
        "NTIA Public Domain Notice",
        false,
        false
    ],
    "NTP": [
        "NTP License",
        true,
        false
    ],
    "NTP-0": [
        "NTP No Attribution",
        false,
        false
    ],
    "Nunit": [
        "Nunit License",
        false,
        true
    ],
    "O-UDA-1.0": [
        "Open Use of Data Agreement v1.0",
        false,
        false
    ],
    "OAR": [
        "OAR License",
        false,
        false
    ],
    "OCCT-PL": [
        "Open CASCADE Technology Public License",
        false,
        false
    ],
    "OCLC-2.0": [
        "OCLC Research Public License 2.0",
        true,
        false
    ],
    "ODbL-1.0": [
        "Open Data Commons Open Database License v1.0",
        false,
        false
    ],
    "ODC-By-1.0": [
        "Open Data Commons Attribution License v1.0",
        false,
        false
    ],
    "OFFIS": [
        "OFFIS License",
        false,
        false
    ],
    "OFL-1.0": [
        "SIL Open Font License 1.0",
        false,
        false
    ],
    "OFL-1.0-no-RFN": [
        "SIL Open Font License 1.0 with no Reserved Font Name",
        false,
        false
    ],
    "OFL-1.0-RFN": [
        "SIL Open Font License 1.0 with Reserved Font Name",
        false,
        false
    ],
    "OFL-1.1": [
        "SIL Open Font License 1.1",
        true,
        false
    ],
    "OFL-1.1-no-RFN": [
        "SIL Open Font License 1.1 with no Reserved Font Name",
        true,
        false
    ],
    "OFL-1.1-RFN": [
        "SIL Open Font License 1.1 with Reserved Font Name",
        true,
        false
    ],
    "OGC-1.0": [
        "OGC Software License, Version 1.0",
        false,
        false
    ],
    "OGDL-Taiwan-1.0": [
        "Taiwan Open Government Data License, version 1.0",
        false,
        false
    ],
    "OGL-Canada-2.0": [
        "Open Government Licence - Canada",
        false,
        false
    ],
    "OGL-UK-1.0": [
        "Open Government Licence v1.0",
        false,
        false
    ],
    "OGL-UK-2.0": [
        "Open Government Licence v2.0",
        false,
        false
    ],
    "OGL-UK-3.0": [
        "Open Government Licence v3.0",
        false,
        false
    ],
    "OGTSL": [
        "Open Group Test Suite License",
        true,
        false
    ],
    "OLDAP-1.1": [
        "Open LDAP Public License v1.1",
        false,
        false
    ],
    "OLDAP-1.2": [
        "Open LDAP Public License v1.2",
        false,
        false
    ],
    "OLDAP-1.3": [
        "Open LDAP Public License v1.3",
        false,
        false
    ],
    "OLDAP-1.4": [
        "Open LDAP Public License v1.4",
        false,
        false
    ],
    "OLDAP-2.0": [
        "Open LDAP Public License v2.0 (or possibly 2.0A and 2.0B)",
        false,
        false
    ],
    "OLDAP-2.0.1": [
        "Open LDAP Public License v2.0.1",
        false,
        false
    ],
    "OLDAP-2.1": [
        "Open LDAP Public License v2.1",
        false,
        false
    ],
    "OLDAP-2.2": [
        "Open LDAP Public License v2.2",
        false,
        false
    ],
    "OLDAP-2.2.1": [
        "Open LDAP Public License v2.2.1",
        false,
        false
    ],
    "OLDAP-2.2.2": [
        "Open LDAP Public License 2.2.2",
        false,
        false
    ],
    "OLDAP-2.3": [
        "Open LDAP Public License v2.3",
        false,
        false
    ],
    "OLDAP-2.4": [
        "Open LDAP Public License v2.4",
        false,
        false
    ],
    "OLDAP-2.5": [
        "Open LDAP Public License v2.5",
        false,
        false
    ],
    "OLDAP-2.6": [
        "Open LDAP Public License v2.6",
        false,
        false
    ],
    "OLDAP-2.7": [
        "Open LDAP Public License v2.7",
        false,
        false
    ],
    "OLDAP-2.8": [
        "Open LDAP Public License v2.8",
        true,
        false
    ],
    "OLFL-1.3": [
        "Open Logistics Foundation License Version 1.3",
        true,
        false
    ],
    "OML": [
        "Open Market License",
        false,
        false
    ],
    "OpenPBS-2.3": [
        "OpenPBS v2.3 Software License",
        false,
        false
    ],
    "OpenSSL": [
        "OpenSSL License",
        false,
        false
    ],
    "OpenSSL-standalone": [
        "OpenSSL License - standalone",
        false,
        false
    ],
    "OpenVision": [
        "OpenVision License",
        false,
        false
    ],
    "OPL-1.0": [
        "Open Public License v1.0",
        false,
        false
    ],
    "OPL-UK-3.0": [
        "United    Kingdom Open Parliament Licence v3.0",
        false,
        false
    ],
    "OPUBL-1.0": [
        "Open Publication License v1.0",
        false,
        false
    ],
    "OSET-PL-2.1": [
        "OSET Public License version 2.1",
        true,
        false
    ],
    "OSL-1.0": [
        "Open Software License 1.0",
        true,
        false
    ],
    "OSL-1.1": [
        "Open Software License 1.1",
        false,
        false
    ],
    "OSL-2.0": [
        "Open Software License 2.0",
        true,
        false
    ],
    "OSL-2.1": [
        "Open Software License 2.1",
        true,
        false
    ],
    "OSL-3.0": [
        "Open Software License 3.0",
        true,
        false
    ],
    "PADL": [
        "PADL License",
        false,
        false
    ],
    "Parity-6.0.0": [
        "The Parity Public License 6.0.0",
        false,
        false
    ],
    "Parity-7.0.0": [
        "The Parity Public License 7.0.0",
        false,
        false
    ],
    "PDDL-1.0": [
        "Open Data Commons Public Domain Dedication & License 1.0",
        false,
        false
    ],
    "PHP-3.0": [
        "PHP License v3.0",
        true,
        false
    ],
    "PHP-3.01": [
        "PHP License v3.01",
        true,
        false
    ],
    "Pixar": [
        "Pixar License",
        false,
        false
    ],
    "pkgconf": [
        "pkgconf License",
        false,
        false
    ],
    "Plexus": [
        "Plexus Classworlds License",
        false,
        false
    ],
    "pnmstitch": [
        "pnmstitch License",
        false,
        false
    ],
    "PolyForm-Noncommercial-1.0.0": [
        "PolyForm Noncommercial License 1.0.0",
        false,
        false
    ],
    "PolyForm-Small-Business-1.0.0": [
        "PolyForm Small Business License 1.0.0",
        false,
        false
    ],
    "PostgreSQL": [
        "PostgreSQL License",
        true,
        false
    ],
    "PPL": [
        "Peer Production License",
        false,
        false
    ],
    "PSF-2.0": [
        "Python Software Foundation License 2.0",
        false,
        false
    ],
    "psfrag": [
        "psfrag License",
        false,
        false
    ],
    "psutils": [
        "psutils License",
        false,
        false
    ],
    "Python-2.0": [
        "Python License 2.0",
        true,
        false
    ],
    "Python-2.0.1": [
        "Python License 2.0.1",
        false,
        false
    ],
    "python-ldap": [
        "Python ldap License",
        false,
        false
    ],
    "Qhull": [
        "Qhull License",
        false,
        false
    ],
    "QPL-1.0": [
        "Q Public License 1.0",
        true,
        false
    ],
    "QPL-1.0-INRIA-2004": [
        "Q Public License 1.0 - INRIA 2004 variant",
        false,
        false
    ],
    "radvd": [
        "radvd License",
        false,
        false
    ],
    "Rdisc": [
        "Rdisc License",
        false,
        false
    ],
    "RHeCos-1.1": [
        "Red Hat eCos Public License v1.1",
        false,
        false
    ],
    "RPL-1.1": [
        "Reciprocal Public License 1.1",
        true,
        false
    ],
    "RPL-1.5": [
        "Reciprocal Public License 1.5",
        true,
        false
    ],
    "RPSL-1.0": [
        "RealNetworks Public Source License v1.0",
        true,
        false
    ],
    "RSA-MD": [
        "RSA Message-Digest License",
        false,
        false
    ],
    "RSCPL": [
        "Ricoh Source Code Public License",
        true,
        false
    ],
    "Ruby": [
        "Ruby License",
        false,
        false
    ],
    "Ruby-pty": [
        "Ruby pty extension license",
        false,
        false
    ],
    "SAX-PD": [
        "Sax Public Domain Notice",
        false,
        false
    ],
    "SAX-PD-2.0": [
        "Sax Public Domain Notice 2.0",
        false,
        false
    ],
    "Saxpath": [
        "Saxpath License",
        false,
        false
    ],
    "SCEA": [
        "SCEA Shared Source License",
        false,
        false
    ],
    "SchemeReport": [
        "Scheme Language Report License",
        false,
        false
    ],
    "Sendmail": [
        "Sendmail License",
        false,
        false
    ],
    "Sendmail-8.23": [
        "Sendmail License 8.23",
        false,
        false
    ],
    "Sendmail-Open-Source-1.1": [
        "Sendmail Open Source License v1.1",
        false,
        false
    ],
    "SGI-B-1.0": [
        "SGI Free Software License B v1.0",
        false,
        false
    ],
    "SGI-B-1.1": [
        "SGI Free Software License B v1.1",
        false,
        false
    ],
    "SGI-B-2.0": [
        "SGI Free Software License B v2.0",
        false,
        false
    ],
    "SGI-OpenGL": [
        "SGI OpenGL License",
        false,
        false
    ],
    "SGP4": [
        "SGP4 Permission Notice",
        false,
        false
    ],
    "SHL-0.5": [
        "Solderpad Hardware License v0.5",
        false,
        false
    ],
    "SHL-0.51": [
        "Solderpad Hardware License, Version 0.51",
        false,
        false
    ],
    "SimPL-2.0": [
        "Simple Public License 2.0",
        true,
        false
    ],
    "SISSL": [
        "Sun Industry Standards Source License v1.1",
        true,
        false
    ],
    "SISSL-1.2": [
        "Sun Industry Standards Source License v1.2",
        false,
        false
    ],
    "SL": [
        "SL License",
        false,
        false
    ],
    "Sleepycat": [
        "Sleepycat License",
        true,
        false
    ],
    "SMAIL-GPL": [
        "SMAIL General Public License",
        false,
        false
    ],
    "SMLNJ": [
        "Standard ML of New Jersey License",
        false,
        false
    ],
    "SMPPL": [
        "Secure Messaging Protocol Public License",
        false,
        false
    ],
    "SNIA": [
        "SNIA Public License 1.1",
        false,
        false
    ],
    "snprintf": [
        "snprintf License",
        false,
        false
    ],
    "SOFA": [
        "SOFA Software License",
        false,
        false
    ],
    "softSurfer": [
        "softSurfer License",
        false,
        false
    ],
    "Soundex": [
        "Soundex License",
        false,
        false
    ],
    "Spencer-86": [
        "Spencer License 86",
        false,
        false
    ],
    "Spencer-94": [
        "Spencer License 94",
        false,
        false
    ],
    "Spencer-99": [
        "Spencer License 99",
        false,
        false
    ],
    "SPL-1.0": [
        "Sun Public License v1.0",
        true,
        false
    ],
    "ssh-keyscan": [
        "ssh-keyscan License",
        false,
        false
    ],
    "SSH-OpenSSH": [
        "SSH OpenSSH license",
        false,
        false
    ],
    "SSH-short": [
        "SSH short notice",
        false,
        false
    ],
    "SSLeay-standalone": [
        "SSLeay License - standalone",
        false,
        false
    ],
    "SSPL-1.0": [
        "Server Side Public License, v 1",
        false,
        false
    ],
    "StandardML-NJ": [
        "Standard ML of New Jersey License",
        false,
        true
    ],
    "SugarCRM-1.1.3": [
        "SugarCRM Public License v1.1.3",
        false,
        false
    ],
    "Sun-PPP": [
        "Sun PPP License",
        false,
        false
    ],
    "Sun-PPP-2000": [
        "Sun PPP License (2000)",
        false,
        false
    ],
    "SunPro": [
        "SunPro License",
        false,
        false
    ],
    "SWL": [
        "Scheme Widget Library (SWL) Software License Agreement",
        false,
        false
    ],
    "swrule": [
        "swrule License",
        false,
        false
    ],
    "Symlinks": [
        "Symlinks License",
        false,
        false
    ],
    "TAPR-OHL-1.0": [
        "TAPR Open Hardware License v1.0",
        false,
        false
    ],
    "TCL": [
        "TCL/TK License",
        false,
        false
    ],
    "TCP-wrappers": [
        "TCP Wrappers License",
        false,
        false
    ],
    "TermReadKey": [
        "TermReadKey License",
        false,
        false
    ],
    "TGPPL-1.0": [
        "Transitive Grace Period Public Licence 1.0",
        false,
        false
    ],
    "ThirdEye": [
        "ThirdEye License",
        false,
        false
    ],
    "threeparttable": [
        "threeparttable License",
        false,
        false
    ],
    "TMate": [
        "TMate Open Source License",
        false,
        false
    ],
    "TORQUE-1.1": [
        "TORQUE v2.5+ Software License v1.1",
        false,
        false
    ],
    "TOSL": [
        "Trusster Open Source License",
        false,
        false
    ],
    "TPDL": [
        "Time::ParseDate License",
        false,
        false
    ],
    "TPL-1.0": [
        "THOR Public License 1.0",
        false,
        false
    ],
    "TrustedQSL": [
        "TrustedQSL License",
        false,
        false
    ],
    "TTWL": [
        "Text-Tabs+Wrap License",
        false,
        false
    ],
    "TTYP0": [
        "TTYP0 License",
        false,
        false
    ],
    "TU-Berlin-1.0": [
        "Technische Universitaet Berlin License 1.0",
        false,
        false
    ],
    "TU-Berlin-2.0": [
        "Technische Universitaet Berlin License 2.0",
        false,
        false
    ],
    "Ubuntu-font-1.0": [
        "Ubuntu Font Licence v1.0",
        false,
        false
    ],
    "UCAR": [
        "UCAR License",
        false,
        false
    ],
    "UCL-1.0": [
        "Upstream Compatibility License v1.0",
        true,
        false
    ],
    "ulem": [
        "ulem License",
        false,
        false
    ],
    "UMich-Merit": [
        "Michigan/Merit Networks License",
        false,
        false
    ],
    "Unicode-3.0": [
        "Unicode License v3",
        true,
        false
    ],
    "Unicode-DFS-2015": [
        "Unicode License Agreement - Data Files and Software (2015)",
        false,
        false
    ],
    "Unicode-DFS-2016": [
        "Unicode License Agreement - Data Files and Software (2016)",
        true,
        false
    ],
    "Unicode-TOU": [
        "Unicode Terms of Use",
        false,
        false
    ],
    "UnixCrypt": [
        "UnixCrypt License",
        false,
        false
    ],
    "Unlicense": [
        "The Unlicense",
        true,
        false
    ],
    "Unlicense-libtelnet": [
        "Unlicense - libtelnet variant",
        false,
        false
    ],
    "Unlicense-libwhirlpool": [
        "Unlicense - libwhirlpool variant",
        false,
        false
    ],
    "UPL-1.0": [
        "Universal Permissive License v1.0",
        true,
        false
    ],
    "URT-RLE": [
        "Utah Raster Toolkit Run Length Encoded License",
        false,
        false
    ],
    "Vim": [
        "Vim License",
        false,
        false
    ],
    "VOSTROM": [
        "VOSTROM Public License for Open Source",
        false,
        false
    ],
    "VSL-1.0": [
        "Vovida Software License v1.0",
        true,
        false
    ],
    "W3C": [
        "W3C Software Notice and License (2002-12-31)",
        true,
        false
    ],
    "W3C-19980720": [
        "W3C Software Notice and License (1998-07-20)",
        false,
        false
    ],
    "W3C-20150513": [
        "W3C Software Notice and Document License (2015-05-13)",
        true,
        false
    ],
    "w3m": [
        "w3m License",
        false,
        false
    ],
    "Watcom-1.0": [
        "Sybase Open Watcom Public License 1.0",
        true,
        false
    ],
    "Widget-Workshop": [
        "Widget Workshop License",
        false,
        false
    ],
    "Wsuipa": [
        "Wsuipa License",
        false,
        false
    ],
    "WTFPL": [
        "Do What The F*ck You Want To Public License",
        false,
        false
    ],
    "wwl": [
        "WWL License",
        false,
        false
    ],
    "wxWindows": [
        "wxWindows Library License",
        true,
        true
    ],
    "X11": [
        "X11 License",
        false,
        false
    ],
    "X11-distribute-modifications-variant": [
        "X11 License Distribution Modification Variant",
        false,
        false
    ],
    "X11-swapped": [
        "X11 swapped final paragraphs",
        false,
        false
    ],
    "Xdebug-1.03": [
        "Xdebug License v 1.03",
        false,
        false
    ],
    "Xerox": [
        "Xerox License",
        false,
        false
    ],
    "Xfig": [
        "Xfig License",
        false,
        false
    ],
    "XFree86-1.1": [
        "XFree86 License 1.1",
        false,
        false
    ],
    "xinetd": [
        "xinetd License",
        false,
        false
    ],
    "xkeyboard-config-Zinoviev": [
        "xkeyboard-config Zinoviev License",
        false,
        false
    ],
    "xlock": [
        "xlock License",
        false,
        false
    ],
    "Xnet": [
        "X.Net License",
        true,
        false
    ],
    "xpp": [
        "XPP License",
        false,
        false
    ],
    "XSkat": [
        "XSkat License",
        false,
        false
    ],
    "xzoom": [
        "xzoom License",
        false,
        false
    ],
    "YPL-1.0": [
        "Yahoo! Public License v1.0",
        false,
        false
    ],
    "YPL-1.1": [
        "Yahoo! Public License v1.1",
        false,
        false
    ],
    "Zed": [
        "Zed License",
        false,
        false
    ],
    "Zeeff": [
        "Zeeff License",
        false,
        false
    ],
    "Zend-2.0": [
        "Zend License v2.0",
        false,
        false
    ],
    "Zimbra-1.3": [
        "Zimbra Public License v1.3",
        false,
        false
    ],
    "Zimbra-1.4": [
        "Zimbra Public License v1.4",
        false,
        false
    ],
    "Zlib": [
        "zlib License",
        true,
        false
    ],
    "zlib-acknowledgement": [
        "zlib/libpng License with Acknowledgement",
        false,
        false
    ],
    "ZPL-1.1": [
        "Zope Public License 1.1",
        false,
        false
    ],
    "ZPL-2.0": [
        "Zope Public License 2.0",
        true,
        false
    ],
    "ZPL-2.1": [
        "Zope Public License 2.1",
        true,
        false
    ]
}<?php

// autoload_namespaces.php @generated by Composer

$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);

return array(
);
Copyright (c) 2015 Jordi Boggiano

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.
<?php

/*
 * This file is part of signal-handler.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Seld\Signal;

use Psr\Log\LoggerInterface;
use Closure;
use WeakReference;

/**
 * SignalHandler and factory
 */
final class SignalHandler
{
    /**
     * The SIGHUP signal is sent to a process when its controlling terminal is closed. It was originally designed to
     * notify the process of a serial line drop (a hangup). In modern systems, this signal usually means that the
     * controlling pseudo or virtual terminal has been closed. Many daemons will reload their configuration files and
     * reopen their logfiles instead of exiting when receiving this signal. nohup is a command to make a command ignore
     * the signal.
     */
    public const SIGHUP = 'SIGHUP';

    /**
     * The SIGINT signal is sent to a process by its controlling terminal when a user wishes to interrupt the process.
     * This is typically initiated by pressing Ctrl-C, but on some systems, the "delete" character or "break" key can be
     * used.
     *
     * On Windows this is used to denote a PHP_WINDOWS_EVENT_CTRL_C
     */
    public const SIGINT = 'SIGINT';

    /**
     * The SIGQUIT signal is sent to a process by its controlling terminal when the user requests that the process quit
     * and perform a core dump.
     */
    public const SIGQUIT = 'SIGQUIT';

    /**
     * The SIGILL signal is sent to a process when it attempts to execute an illegal, malformed, unknown, or privileged
     * instruction.
     */
    public const SIGILL = 'SIGILL';

    /**
     * The SIGTRAP signal is sent to a process when an exception (or trap) occurs: a condition that a debugger has
     * requested to be informed of — for example, when a particular function is executed, or when a particular variable
     * changes value.
     */
    public const SIGTRAP = 'SIGTRAP';

    /**
     * The SIGABRT signal is sent to a process to tell it to abort, i.e. to terminate. The signal is usually initiated
     * by the process itself when it calls abort function of the C Standard Library, but it can be sent to the process
     * from outside like any other signal.
     */
    public const SIGABRT = 'SIGABRT';

    public const SIGIOT = 'SIGIOT';

    /**
     * The SIGBUS signal is sent to a process when it causes a bus error. The conditions that lead to the signal being
     * sent are, for example, incorrect memory access alignment or non-existent physical address.
     */
    public const SIGBUS = 'SIGBUS';

    public const SIGFPE = 'SIGFPE';

    /**
     * The SIGKILL signal is sent to a process to cause it to terminate immediately (kill). In contrast to SIGTERM and
     * SIGINT, this signal cannot be caught or ignored, and the receiving process cannot perform any clean-up upon
     * receiving this signal.
     */
    public const SIGKILL = 'SIGKILL';

    /**
     * The SIGUSR1 signal is sent to a process to indicate user-defined conditions.
     */
    public const SIGUSR1 = 'SIGUSR1';

    /**
     * The SIGUSR1 signa2 is sent to a process to indicate user-defined conditions.
     */
    public const SIGUSR2 = 'SIGUSR2';

    /**
     * The SIGSEGV signal is sent to a process when it makes an invalid virtual memory reference, or segmentation fault,
     * i.e. when it performs a segmentation violation.
     */
    public const SIGSEGV = 'SIGSEGV';

    /**
     * The SIGPIPE signal is sent to a process when it attempts to write to a pipe without a process connected to the
     * other end.
     */
    public const SIGPIPE = 'SIGPIPE';

    /**
     * The SIGALRM, SIGVTALRM and SIGPROF signal is sent to a process when the time limit specified in a call to a
     * preceding alarm setting function (such as setitimer) elapses. SIGALRM is sent when real or clock time elapses.
     * SIGVTALRM is sent when CPU time used by the process elapses. SIGPROF is sent when CPU time used by the process
     * and by the system on behalf of the process elapses.
     */
    public const SIGALRM = 'SIGALRM';

    /**
     * The SIGTERM signal is sent to a process to request its termination. Unlike the SIGKILL signal, it can be caught
     * and interpreted or ignored by the process. This allows the process to perform nice termination releasing
     * resources and saving state if appropriate. SIGINT is nearly identical to SIGTERM.
     */
    public const SIGTERM = 'SIGTERM';

    public const SIGSTKFLT = 'SIGSTKFLT';
    public const SIGCLD = 'SIGCLD';

    /**
     * The SIGCHLD signal is sent to a process when a child process terminates, is interrupted, or resumes after being
     * interrupted. One common usage of the signal is to instruct the operating system to clean up the resources used by
     * a child process after its termination without an explicit call to the wait system call.
     */
    public const SIGCHLD = 'SIGCHLD';

    /**
     * The SIGCONT signal instructs the operating system to continue (restart) a process previously paused by the
     * SIGSTOP or SIGTSTP signal. One important use of this signal is in job control in the Unix shell.
     */
    public const SIGCONT = 'SIGCONT';

    /**
     * The SIGSTOP signal instructs the operating system to stop a process for later resumption.
     */
    public const SIGSTOP = 'SIGSTOP';

    /**
     * The SIGTSTP signal is sent to a process by its controlling terminal to request it to stop (terminal stop). It is
     * commonly initiated by the user pressing Ctrl+Z. Unlike SIGSTOP, the process can register a signal handler for or
     * ignore the signal.
     */
    public const SIGTSTP = 'SIGTSTP';

    /**
     * The SIGTTIN signal is sent to a process when it attempts to read in from the tty while in the background.
     * Typically, this signal is received only by processes under job control; daemons do not have controlling
     */
    public const SIGTTIN = 'SIGTTIN';

    /**
     * The SIGTTOU signal is sent to a process when it attempts to write out from the tty while in the background.
     * Typically, this signal is received only by processes under job control; daemons do not have controlling
     */
    public const SIGTTOU = 'SIGTTOU';

    /**
     * The SIGURG signal is sent to a process when a socket has urgent or out-of-band data available to read.
     */
    public const SIGURG = 'SIGURG';

    /**
     * The SIGXCPU signal is sent to a process when it has used up the CPU for a duration that exceeds a certain
     * predetermined user-settable value. The arrival of a SIGXCPU signal provides the receiving process a chance to
     * quickly save any intermediate results and to exit gracefully, before it is terminated by the operating system
     * using the SIGKILL signal.
     */
    public const SIGXCPU = 'SIGXCPU';

    /**
     * The SIGXFSZ signal is sent to a process when it grows a file larger than the maximum allowed size
     */
    public const SIGXFSZ = 'SIGXFSZ';

    /**
     * The SIGVTALRM signal is sent to a process when the time limit specified in a call to a preceding alarm setting
     * function (such as setitimer) elapses. SIGVTALRM is sent when CPU time used by the process elapses.
     */
    public const SIGVTALRM = 'SIGVTALRM';

    /**
     * The SIGPROF signal is sent to a process when the time limit specified in a call to a preceding alarm setting
     * function (such as setitimer) elapses. SIGPROF is sent when CPU time used by the process and by the system on
     * behalf of the process elapses.
     */
    public const SIGPROF = 'SIGPROF';

    /**
     * The SIGWINCH signal is sent to a process when its controlling terminal changes its size (a window change).
     */
    public const SIGWINCH = 'SIGWINCH';

    /**
     * The SIGPOLL signal is sent when an event occurred on an explicitly watched file descriptor.Using it effectively
     * leads to making asynchronous I/O requests since the kernel will poll the descriptor in place of the caller. It
     * provides an alternative to active polling.
     */
    public const SIGPOLL = 'SIGPOLL';

    public const SIGIO = 'SIGIO';

    /**
     * The SIGPWR signal is sent to a process when the system experiences a power failure.
     */
    public const SIGPWR = 'SIGPWR';

    /**
     * The SIGSYS signal is sent to a process when it passes a bad argument to a system call. In practice, this kind of
     * signal is rarely encountered since applications rely on libraries (e.g. libc) to make the call for them.
     */
    public const SIGSYS = 'SIGSYS';

    public const SIGBABY = 'SIGBABY';

    /**
     * CTRL+Break support, available on Windows only for PHP_WINDOWS_EVENT_CTRL_BREAK
     */
    public const SIGBREAK = 'SIGBREAK';

    private const ALL_SIGNALS = [
        self::SIGHUP, self::SIGINT, self::SIGQUIT, self::SIGILL, self::SIGTRAP, self::SIGABRT, self::SIGIOT, self::SIGBUS,
        self::SIGFPE, self::SIGKILL, self::SIGUSR1, self::SIGUSR2, self::SIGSEGV, self::SIGPIPE, self::SIGALRM, self::SIGTERM,
        self::SIGSTKFLT, self::SIGCLD, self::SIGCHLD, self::SIGCONT, self::SIGSTOP, self::SIGTSTP, self::SIGTTIN, self::SIGTTOU,
        self::SIGURG, self::SIGXCPU, self::SIGXFSZ, self::SIGVTALRM, self::SIGPROF, self::SIGWINCH, self::SIGPOLL, self::SIGIO,
        self::SIGPWR, self::SIGSYS, self::SIGBABY, self::SIGBREAK
    ];

    /**
     * @var self::SIG*|null
     */
    private $triggered = null;

    /**
     * @var list<self::SIG*>
     * @readonly
     */
    private $signals;

    /**
     * @var LoggerInterface|(callable(self::SIG* $name, SignalHandler $self): void)|null
     * @readonly
     */
    private $loggerOrCallback;

    /**
     * @var array<int, self|WeakReference<self>>
     */
    private static $handlers = [];

    /** @var Closure|null */
    private static $windowsHandler = null;

    /**
     * @param array<self::SIG*> $signals
     * @param LoggerInterface|(callable(self::SIG* $name, SignalHandler $self): void)|null $loggerOrCallback
     */
    private function __construct(array $signals, $loggerOrCallback)
    {
        if (!is_callable($loggerOrCallback) && !$loggerOrCallback instanceof LoggerInterface && $loggerOrCallback !== null) {
            throw new \InvalidArgumentException('$loggerOrCallback must be a '.LoggerInterface::class.' instance, a callable, or null, '.(is_object($loggerOrCallback) ? get_class($loggerOrCallback) : gettype($loggerOrCallback)).' received.');
        }

        $this->signals = $signals;
        $this->loggerOrCallback = $loggerOrCallback;
    }

    /**
     * @param self::SIG* $signalName
     */
    private function trigger(string $signalName): void
    {
        $this->triggered = $signalName;

        if ($this->loggerOrCallback instanceof LoggerInterface) {
            $this->loggerOrCallback->info('Received '.$signalName);
        } elseif ($this->loggerOrCallback !== null) {
            ($this->loggerOrCallback)($signalName, $this);
        }
    }

    /**
     * Fetches the triggered state of the handler
     *
     * @phpstan-impure
     */
    public function isTriggered(): bool
    {
        return $this->triggered !== null;
    }

    /**
     * Exits the process while communicating that the handled signal was what killed the process
     *
     * This is different from doing exit(SIGINT), and is also different to a successful exit(0).
     *
     * This should only be used when you received a signal and then handled it to gracefully shutdown and are now ready to shutdown.
     *
     * ```
     * $signal = SignalHandler::create([SignalHandler::SIGINT], function (string $signal, SignalHandler $handler) {
     *     // do cleanup here..
     *
     *     $handler->exitWithLastSignal();
     * });
     *
     * // or...
     *
     * $signal = SignalHandler::create([SignalHandler::SIGINT]);
     *
     * while ($doingThings) {
     *     if ($signal->isTriggered()) {
     *         $signal->exitWithLastSignal();
     *     }
     *
     *     // do more things
     * }
     * ```
     *
     * @see https://www.cons.org/cracauer/sigint.html
     * @return never
     */
    public function exitWithLastSignal(): void
    {
        $signal = $this->triggered ?? 'SIGINT';
        $signal = defined($signal) ? constant($signal) : 2;

        if (function_exists('posix_kill') && function_exists('posix_getpid')) {
            pcntl_signal($signal, SIG_DFL);
            posix_kill(posix_getpid(), $signal);
        }

        // just in case posix_kill above could not run
        // not strictly correct but it's the best we can do here
        exit(128 + $signal);
    }

    /**
     * Resets the state to let a handler accept a signal again
     */
    public function reset(): void
    {
        $this->triggered = null;
    }

    public function __destruct()
    {
        $this->unregister();
    }

    /**
     * @param (string|int)[] $signals array of signal names (more portable, see SignalHandler::SIG*) or constants - defaults to [SIGINT, SIGTERM]
     * @param LoggerInterface|callable $loggerOrCallback A PSR-3 Logger or a callback($signal, $signalName)
     * @return self A handler on which you can call isTriggered to know if the signal was received, and reset() to forget
     *
     * @phpstan-param list<self::SIG*|int> $signals
     * @phpstan-param LoggerInterface|(callable(self::SIG* $name, SignalHandler $self): void) $loggerOrCallback
     */
    public static function create(?array $signals = null, $loggerOrCallback = null): self
    {
        if ($signals === null) {
            $signals = [self::SIGINT, self::SIGTERM];
        }
        $signals = array_map(function ($signal) {
            if (is_int($signal)) {
                return self::getSignalName($signal);
            } elseif (!in_array($signal, self::ALL_SIGNALS, true)) {
                throw new \InvalidArgumentException('$signals must be an array of SIG* constants or self::SIG* constants, got '.var_export($signal, true));
            }
            return $signal;
        }, (array) $signals);

        $handler = new self($signals, $loggerOrCallback);

        if (PHP_VERSION_ID >= 80000) {
            array_unshift(self::$handlers, WeakReference::create($handler));
        } else {
            array_unshift(self::$handlers, $handler);
        }

        if (function_exists('sapi_windows_set_ctrl_handler') && PHP_SAPI === 'cli' && (in_array(self::SIGINT, $signals, true) || in_array(self::SIGBREAK, $signals, true))) {
            if (null === self::$windowsHandler) {
                self::$windowsHandler = Closure::fromCallable([self::class, 'handleWindowsSignal']);
                sapi_windows_set_ctrl_handler(self::$windowsHandler);
            }
        }

        if (function_exists('pcntl_signal') && function_exists('pcntl_async_signals')) {
            pcntl_async_signals(true);

            self::registerPcntlHandler($signals);
        }

        return $handler;
    }

    /**
     * Clears the signal handler
     *
     * On PHP 8+ this is not necessary and it will happen automatically on __destruct, but PHP 7 does not
     * support weak references and thus there you need to manually do this.
     *
     * If another handler was registered previously to this one, it becomes active again
     */
    public function unregister(): void
    {
        $signals = $this->signals;

        $index = false;
        foreach (self::$handlers as $key => $handler) {
            if (($handler instanceof WeakReference && $handler->get() === $this) || $handler === $this) {
                $index = $key;
                break;
            }
        }
        if ($index === false) {
            // guard against double-unregistration when __destruct happens
            return;
        }

        unset(self::$handlers[$index]);

        if (self::$windowsHandler !== null && (in_array(self::SIGINT, $signals, true) || in_array(self::SIGBREAK, $signals, true))) {
            if (self::getHandlerFor(self::SIGINT) === null && self::getHandlerFor(self::SIGBREAK) === null) {
                sapi_windows_set_ctrl_handler(self::$windowsHandler, false);
                self::$windowsHandler = null;
            }
        }

        if (function_exists('pcntl_signal')) {
            foreach ($signals as $signal) {
                // skip missing signals, for example OSX does not have all signals
                if (!defined($signal)) {
                    continue;
                }

                // keep listening to signals where we have a handler registered
                if (self::getHandlerFor($signal) !== null) {
                    continue;
                }

                pcntl_signal(constant($signal), SIG_DFL);
            }
        }
    }

    /**
     * Clears all signal handlers
     *
     * On PHP 8+ this should not be necessary as it will happen automatically on __destruct, but PHP 7 does not
     * support weak references and thus there you need to manually do this.
     *
     * This can be done to reset the global state, but ideally you should always call ->unregister() in a try/finally block to ensure it happens.
     */
    public static function unregisterAll(): void
    {
        if (self::$windowsHandler !== null) {
            sapi_windows_set_ctrl_handler(self::$windowsHandler, false);
            self::$windowsHandler = null;
        }

        foreach (self::$handlers as $key => $handler) {
            if ($handler instanceof WeakReference) {
                $handler = $handler->get();
                if ($handler === null) {
                    unset(self::$handlers[$key]);
                    continue;
                }
            }
            $handler->unregister();
        }
    }

    /**
     * @param list<self::SIG*> $signals
     */
    private static function registerPcntlHandler(array $signals): void
    {
        static $callable;
        if ($callable === null) {
            $callable = Closure::fromCallable([self::class, 'handlePcntlSignal']);
        }
        foreach ($signals as $signal) {
            // skip missing signals, for example OSX does not have all signals
            if (!defined($signal)) {
                continue;
            }

            pcntl_signal(constant($signal), $callable);
        }
    }

    private static function handleWindowsSignal(int $event): void
    {
        if (PHP_WINDOWS_EVENT_CTRL_C === $event) {
            self::callHandlerFor(self::SIGINT);
        } elseif (PHP_WINDOWS_EVENT_CTRL_BREAK === $event) {
            self::callHandlerFor(self::SIGBREAK);
        }
    }

    private static function handlePcntlSignal(int $signal): void
    {
        self::callHandlerFor(self::getSignalName($signal));
    }

    /**
     * Calls the first handler from the top of the stack that can handle a given signal
     *
     * @param self::SIG* $signal
     */
    private static function callHandlerFor(string $signal): void
    {
        $handler = self::getHandlerFor($signal);
        if ($handler !== null) {
            $handler->trigger($signal);
        }
    }

    /**
     * Returns the first handler from the top of the stack that can handle a given signal
     *
     * @param self::SIG* $signal
     * @return self|null
     */
    private static function getHandlerFor(string $signal): ?self
    {
        foreach (self::$handlers as $key => $handler) {
            if ($handler instanceof WeakReference) {
                $handler = $handler->get();
                if ($handler === null) {
                    unset(self::$handlers[$key]);
                    continue;
                }
            }
            if (in_array($signal, $handler->signals, true)) {
                return $handler;
            }
        }

        return null;
    }

    /**
     * @return self::SIG*
     */
    private static function getSignalName(int $signo): string
    {
        static $signals = null;
        if ($signals === null) {
            $signals = [];
            foreach (self::ALL_SIGNALS as $value) {
                if (defined($value)) {
                    $signals[constant($value)] = $value;
                }
            }
        }

        if (isset($signals[$signo])) {
            return $signals[$signo];
        }

        throw new \InvalidArgumentException('Unknown signal #'.$signo);
    }
}
Copyright (c) 2011 Jordi Boggiano

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.
<?php

/*
 * This file is part of the JSON Lint package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Seld\JsonLint;

class DuplicateKeyException extends ParsingException
{
    /**
     * @var array{key: string, line: int}
     */
    protected $details;

    /**
     * @param string $message
     * @param string $key
     * @phpstan-param array{line: int} $details
     */
    public function __construct($message, $key, array $details)
    {
        $details['key'] = $key;
        parent::__construct($message, $details);
    }

    /**
     * @return string
     */
    public function getKey()
    {
        return $this->details['key'];
    }

    /**
     * @phpstan-return array{key: string, line: int}
     */
    public function getDetails()
    {
        return $this->details;
    }
}
<?php

/*
 * This file is part of the JSON Lint package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Seld\JsonLint;

class Undefined
{
}
<?php

/*
 * This file is part of the JSON Lint package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Seld\JsonLint;

/**
 * Lexer class
 *
 * Ported from https://github.com/zaach/jsonlint
 */
class Lexer
{
    /** @internal */
    const EOF = 1;
    /** @internal */
    const T_INVALID = -1;
    const T_SKIP_WHITESPACE = 0;
    const T_ERROR = 2;
    /** @internal */
    const T_BREAK_LINE = 3;
    /** @internal */
    const T_COMMENT = 30;
    /** @internal */
    const T_OPEN_COMMENT = 31;
    /** @internal */
    const T_CLOSE_COMMENT = 32;

    /**
     * @phpstan-var array<int<0,17>, string>
     * @const
     */
    private $rules = array(
        0 => '/\G\s*\n\r?/',
        1 => '/\G\s+/',
        2 => '/\G-?([0-9]|[1-9][0-9]+)(\.[0-9]+)?([eE][+-]?[0-9]+)?\b/',
        3 => '{\G"(?>\\\\["bfnrt/\\\\]|\\\\u[a-fA-F0-9]{4}|[^\0-\x1f\\\\"]++)*+"}',
        4 => '/\G\{/',
        5 => '/\G\}/',
        6 => '/\G\[/',
        7 => '/\G\]/',
        8 => '/\G,/',
        9 => '/\G:/',
        10 => '/\Gtrue\b/',
        11 => '/\Gfalse\b/',
        12 => '/\Gnull\b/',
        13 => '/\G$/',
        14 => '/\G\/\//',
        15 => '/\G\/\*/',
        16 => '/\G\*\//',
        17 => '/\G./',
    );

    /** @var string */
    private $input;
    /** @var bool */
    private $more;
    /** @var bool */
    private $done;
    /** @var 0|positive-int */
    private $offset;
    /** @var int */
    private $flags;

    /** @var string */
    public $match;
    /** @var 0|positive-int */
    public $yylineno;
    /** @var 0|positive-int */
    public $yyleng;
    /** @var string */
    public $yytext;
    /** @var array{first_line: 0|positive-int, first_column: 0|positive-int, last_line: 0|positive-int, last_column: 0|positive-int} */
    public $yylloc;

    /**
     * @param int $flags
     */
    public function __construct($flags = 0)
    {
        $this->flags = $flags;
    }

    /**
     * @return 0|1|4|6|8|10|11|14|17|18|21|22|23|24|30|-1
     */
    public function lex()
    {
        while (true) {
            $symbol = $this->next();
            switch ($symbol) {
                case self::T_SKIP_WHITESPACE:
                case self::T_BREAK_LINE:
                    break;
                case self::T_COMMENT:
                case self::T_OPEN_COMMENT:
                    if (!($this->flags & JsonParser::ALLOW_COMMENTS)) {
                        $this->parseError('Lexical error on line ' . ($this->yylineno+1) . ". Comments are not allowed.\n" . $this->showPosition());
                    }
                    $this->skipUntil($symbol === self::T_COMMENT ? self::T_BREAK_LINE : self::T_CLOSE_COMMENT);
                    if ($this->done) {
                        // last symbol '/\G$/' before EOF
                        return 14;
                    }
                    break;
                case self::T_CLOSE_COMMENT:
                    $this->parseError('Lexical error on line ' . ($this->yylineno+1) . ". Unexpected token.\n" . $this->showPosition());
                default:
                    return $symbol;
            }
        }
    }

    /**
     * @param string $input
     * @return $this
     */
    public function setInput($input)
    {
        $this->input = $input;
        $this->more = false;
        $this->done = false;
        $this->offset = 0;
        $this->yylineno = $this->yyleng = 0;
        $this->yytext = $this->match = '';
        $this->yylloc = array('first_line' => 1, 'first_column' => 0, 'last_line' => 1, 'last_column' => 0);

        return $this;
    }

    /**
     * @return string
     */
    public function showPosition()
    {
        if ($this->yylineno === 0 && $this->offset === 1 && $this->match !== '{') {
            return $this->match.'...' . "\n^";
        }

        $pre = str_replace("\n", '', $this->getPastInput());
        $c = str_repeat('-', max(0, \strlen($pre) - 1)); // new Array(pre.length + 1).join("-");

        return $pre . str_replace("\n", '', $this->getUpcomingInput()) . "\n" . $c . "^";
    }

    /**
     * @return string
     */
    public function getPastInput()
    {
        $pastLength = $this->offset - \strlen($this->match);

        return ($pastLength > 20 ? '...' : '') . substr($this->input, max(0, $pastLength - 20), min(20, $pastLength));
    }

    /**
     * @return string
     */
    public function getUpcomingInput()
    {
        $next = $this->match;
        if (\strlen($next) < 20) {
            $next .= substr($this->input, $this->offset, 20 - \strlen($next));
        }

        return substr($next, 0, 20) . (\strlen($next) > 20 ? '...' : '');
    }

    /**
     * @return string
     */
    public function getFullUpcomingInput()
    {
        $next = $this->match;
        if (substr($next, 0, 1) === '"' && substr_count($next, '"') === 1) {
            $len = \strlen($this->input);
            if ($len === $this->offset) {
                $strEnd = $len;
            } else {
                $strEnd = min(strpos($this->input, '"', $this->offset + 1) ?: $len, strpos($this->input, "\n", $this->offset + 1) ?: $len);
            }
            $next .= substr($this->input, $this->offset, $strEnd - $this->offset);
        } elseif (\strlen($next) < 20) {
            $next .= substr($this->input, $this->offset, 20 - \strlen($next));
        }

        return $next;
    }

    /**
     * @param string $str
     * @return never
     */
    protected function parseError($str)
    {
        throw new ParsingException($str);
    }

    /**
     * @param int $token
     * @return void
     */
    private function skipUntil($token)
    {
        $symbol = $this->next();
        while ($symbol !== $token && false === $this->done) {
            $symbol = $this->next();
        }
    }

    /**
     * @return 0|1|3|4|6|8|10|11|14|17|18|21|22|23|24|30|31|32|-1
     */
    private function next()
    {
        if ($this->done) {
            return self::EOF;
        }
        if ($this->offset === \strlen($this->input)) {
            $this->done = true;
        }

        $token = null;
        $match = null;
        $col = null;
        $lines = null;

        if (!$this->more) {
            $this->yytext = '';
            $this->match = '';
        }

        $rulesLen = count($this->rules);

        for ($i=0; $i < $rulesLen; $i++) {
            if (preg_match($this->rules[$i], $this->input, $match, 0, $this->offset)) {
                $lines = explode("\n", $match[0]);
                array_shift($lines);
                $lineCount = \count($lines);
                $this->yylineno += $lineCount;
                $this->yylloc = array(
                    'first_line' => $this->yylloc['last_line'],
                    'last_line' => $this->yylineno+1,
                    'first_column' => $this->yylloc['last_column'],
                    'last_column' => $lineCount > 0 ? \strlen($lines[$lineCount - 1]) : $this->yylloc['last_column'] + \strlen($match[0]),
                );
                $this->yytext .= $match[0];
                $this->match .= $match[0];
                $this->yyleng = \strlen($this->yytext);
                $this->more = false;
                $this->offset += \strlen($match[0]);
                return $this->performAction($i);
            }
        }

        if ($this->offset === \strlen($this->input)) {
            return self::EOF;
        }

        $this->parseError(
            'Lexical error on line ' . ($this->yylineno+1) . ". Unrecognized text.\n" . $this->showPosition()
        );
    }

    /**
     * @param  int $rule
     * @return 0|3|4|6|8|10|11|14|17|18|21|22|23|24|30|31|32|-1
     */
    private function performAction($rule)
    {
        switch ($rule) {
        case 0:/* skip break line */
            return self::T_BREAK_LINE;
        case 1:/* skip whitespace */
            return self::T_SKIP_WHITESPACE;
        case 2:
            return 6;
        case 3:
            $this->yytext = substr($this->yytext, 1, $this->yyleng-2);
            return 4;
        case 4:
            return 17;
        case 5:
            return 18;
        case 6:
            return 23;
        case 7:
            return 24;
        case 8:
            return 22;
        case 9:
            return 21;
        case 10:
            return 10;
        case 11:
            return 11;
        case 12:
            return 8;
        case 13:
            return 14;
        case 14:
            return self::T_COMMENT;
        case 15:
            return self::T_OPEN_COMMENT;
        case 16:
            return self::T_CLOSE_COMMENT;
        case 17:
            return self::T_INVALID;
        default:
            throw new \LogicException('Unsupported rule '.$rule);
        }
    }
}
<?php

/*
 * This file is part of the JSON Lint package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Seld\JsonLint;

class ParsingException extends \Exception
{
    /**
     * @var array{text?: string, token?: string|int, line?: int, loc?: array{first_line: int, first_column: int, last_line: int, last_column: int}, expected?: string[]}
     */
    protected $details;

    /**
     * @param string $message
     * @phpstan-param array{text?: string, token?: string|int, line?: int, loc?: array{first_line: int, first_column: int, last_line: int, last_column: int}, expected?: string[]} $details
     */
    public function __construct($message, $details = array())
    {
        $this->details = $details;
        parent::__construct($message);
    }

    /**
     * @phpstan-return array{text?: string, token?: string|int, line?: int, loc?: array{first_line: int, first_column: int, last_line: int, last_column: int}, expected?: string[]}
     */
    public function getDetails()
    {
        return $this->details;
    }
}
<?php

/*
 * This file is part of the JSON Lint package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Seld\JsonLint;
use stdClass;

/**
 * Parser class
 *
 * Example:
 *
 * $parser = new JsonParser();
 * // returns null if it's valid json, or an error object
 * $parser->lint($json);
 * // returns parsed json, like json_decode does, but slower, throws exceptions on failure.
 * $parser->parse($json);
 *
 * Ported from https://github.com/zaach/jsonlint
 */
class JsonParser
{
    const DETECT_KEY_CONFLICTS = 1;
    const ALLOW_DUPLICATE_KEYS = 2;
    const PARSE_TO_ASSOC = 4;
    const ALLOW_COMMENTS = 8;
    const ALLOW_DUPLICATE_KEYS_TO_ARRAY = 16;

    /** @var Lexer */
    private $lexer;

    /**
     * @var int
     * @phpstan-var int-mask-of<self::*>
     */
    private $flags;
    /** @var list<int> */
    private $stack;
    /** @var list<stdClass|array<mixed>|int|bool|float|string|null> */
    private $vstack; // semantic value stack
    /** @var list<array{first_line: int, first_column: int, last_line: int, last_column: int}> */
    private $lstack; // location stack

    /**
     * @phpstan-var array<string, int>
     */
    private $symbols = array(
        'error'                 => 2,
        'JSONString'            => 3,
        'STRING'                => 4,
        'JSONNumber'            => 5,
        'NUMBER'                => 6,
        'JSONNullLiteral'       => 7,
        'NULL'                  => 8,
        'JSONBooleanLiteral'    => 9,
        'TRUE'                  => 10,
        'FALSE'                 => 11,
        'JSONText'              => 12,
        'JSONValue'             => 13,
        'EOF'                   => 14,
        'JSONObject'            => 15,
        'JSONArray'             => 16,
        '{'                     => 17,
        '}'                     => 18,
        'JSONMemberList'        => 19,
        'JSONMember'            => 20,
        ':'                     => 21,
        ','                     => 22,
        '['                     => 23,
        ']'                     => 24,
        'JSONElementList'       => 25,
        '$accept'               => 0,
        '$end'                  => 1,
    );

    /**
     * @phpstan-var array<int, string>
     * @const
     */
    private $terminals_ = array(
        2   => "error",
        4   => "STRING",
        6   => "NUMBER",
        8   => "NULL",
        10  => "TRUE",
        11  => "FALSE",
        14  => "EOF",
        17  => "{",
        18  => "}",
        21  => ":",
        22  => ",",
        23  => "[",
        24  => "]",
    );

    /**
     * @phpstan-var array<int<1,21>, array{int, int}>
     * @const
     */
    private $productions_ = array(
        1 => array(3, 1),
        2 => array(5, 1),
        3 => array(7, 1),
        4 => array(9, 1),
        5 => array(9, 1),
        6 => array(12, 2),
        7 => array(13, 1),
        8 => array(13, 1),
        9 => array(13, 1),
        10 => array(13, 1),
        11 => array(13, 1),
        12 => array(13, 1),
        13 => array(15, 2),
        14 => array(15, 3),
        15 => array(20, 3),
        16 => array(19, 1),
        17 => array(19, 3),
        18 => array(16, 2),
        19 => array(16, 3),
        20 => array(25, 1),
        21 => array(25, 3)
    );

    /**
     * @var array<int<0, 31>, array<int, array<int>|int>> List of stateID=>symbolID=>actionIDs|actionID
     * @const
     */
    private $table = array(
        0 => array( 3 => 5, 4 => array(1,12), 5 => 6, 6 => array(1,13), 7 => 3, 8 => array(1,9), 9 => 4, 10 => array(1,10), 11 => array(1,11), 12 => 1, 13 => 2, 15 => 7, 16 => 8, 17 => array(1,14), 23 => array(1,15)),
        1 => array( 1 => array(3)),
        2 => array( 14 => array(1,16)),
        3 => array( 14 => array(2,7), 18 => array(2,7), 22 => array(2,7), 24 => array(2,7)),
        4 => array( 14 => array(2,8), 18 => array(2,8), 22 => array(2,8), 24 => array(2,8)),
        5 => array( 14 => array(2,9), 18 => array(2,9), 22 => array(2,9), 24 => array(2,9)),
        6 => array( 14 => array(2,10), 18 => array(2,10), 22 => array(2,10), 24 => array(2,10)),
        7 => array( 14 => array(2,11), 18 => array(2,11), 22 => array(2,11), 24 => array(2,11)),
        8 => array( 14 => array(2,12), 18 => array(2,12), 22 => array(2,12), 24 => array(2,12)),
        9 => array( 14 => array(2,3), 18 => array(2,3), 22 => array(2,3), 24 => array(2,3)),
        10 => array( 14 => array(2,4), 18 => array(2,4), 22 => array(2,4), 24 => array(2,4)),
        11 => array( 14 => array(2,5), 18 => array(2,5), 22 => array(2,5), 24 => array(2,5)),
        12 => array( 14 => array(2,1), 18 => array(2,1), 21 => array(2,1), 22 => array(2,1), 24 => array(2,1)),
        13 => array( 14 => array(2,2), 18 => array(2,2), 22 => array(2,2), 24 => array(2,2)),
        14 => array( 3 => 20, 4 => array(1,12), 18 => array(1,17), 19 => 18, 20 => 19 ),
        15 => array( 3 => 5, 4 => array(1,12), 5 => 6, 6 => array(1,13), 7 => 3, 8 => array(1,9), 9 => 4, 10 => array(1,10), 11 => array(1,11), 13 => 23, 15 => 7, 16 => 8, 17 => array(1,14), 23 => array(1,15), 24 => array(1,21), 25 => 22 ),
        16 => array( 1 => array(2,6)),
        17 => array( 14 => array(2,13), 18 => array(2,13), 22 => array(2,13), 24 => array(2,13)),
        18 => array( 18 => array(1,24), 22 => array(1,25)),
        19 => array( 18 => array(2,16), 22 => array(2,16)),
        20 => array( 21 => array(1,26)),
        21 => array( 14 => array(2,18), 18 => array(2,18), 22 => array(2,18), 24 => array(2,18)),
        22 => array( 22 => array(1,28), 24 => array(1,27)),
        23 => array( 22 => array(2,20), 24 => array(2,20)),
        24 => array( 14 => array(2,14), 18 => array(2,14), 22 => array(2,14), 24 => array(2,14)),
        25 => array( 3 => 20, 4 => array(1,12), 20 => 29 ),
        26 => array( 3 => 5, 4 => array(1,12), 5 => 6, 6 => array(1,13), 7 => 3, 8 => array(1,9), 9 => 4, 10 => array(1,10), 11 => array(1,11), 13 => 30, 15 => 7, 16 => 8, 17 => array(1,14), 23 => array(1,15)),
        27 => array( 14 => array(2,19), 18 => array(2,19), 22 => array(2,19), 24 => array(2,19)),
        28 => array( 3 => 5, 4 => array(1,12), 5 => 6, 6 => array(1,13), 7 => 3, 8 => array(1,9), 9 => 4, 10 => array(1,10), 11 => array(1,11), 13 => 31, 15 => 7, 16 => 8, 17 => array(1,14), 23 => array(1,15)),
        29 => array( 18 => array(2,17), 22 => array(2,17)),
        30 => array( 18 => array(2,15), 22 => array(2,15)),
        31 => array( 22 => array(2,21), 24 => array(2,21)),
    );

    /**
     * @var array{16: array{2, 6}}
     * @const
     */
    private $defaultActions = array(
        16 => array(2, 6)
    );

    /**
     * @param  string                $input JSON string
     * @param  int                   $flags Bitmask of parse/lint options (see constants of this class)
     * @return null|ParsingException null if no error is found, a ParsingException containing all details otherwise
     *
     * @phpstan-param int-mask-of<self::*> $flags
     */
    public function lint($input, $flags = 0)
    {
        try {
            $this->parse($input, $flags);
        } catch (ParsingException $e) {
            return $e;
        }
        return null;
    }

    /**
     * @param  string           $input JSON string
     * @param  int              $flags Bitmask of parse/lint options (see constants of this class)
     * @return mixed
     * @throws ParsingException
     *
     * @phpstan-param int-mask-of<self::*> $flags
     */
    public function parse($input, $flags = 0)
    {
        if (($flags & self::ALLOW_DUPLICATE_KEYS_TO_ARRAY) && ($flags & self::ALLOW_DUPLICATE_KEYS)) {
            throw new \InvalidArgumentException('Only one of ALLOW_DUPLICATE_KEYS and ALLOW_DUPLICATE_KEYS_TO_ARRAY can be used, you passed in both.');
        }

        $this->failOnBOM($input);

        $this->flags = $flags;

        $this->stack = array(0);
        $this->vstack = array(null);
        $this->lstack = array();

        $yytext = '';
        $yylineno = 0;
        $yyleng = 0;
        /** @var int<0,3> */
        $recovering = 0;

        $this->lexer = new Lexer($flags);
        $this->lexer->setInput($input);

        $yyloc = $this->lexer->yylloc;
        $this->lstack[] = $yyloc;

        $symbol = null;
        $preErrorSymbol = null;
        $action = null;
        $a = null;
        $r = null;
        $p = null;
        $len = null;
        $newState = null;
        $expected = null;
        /** @var string|null */
        $errStr = null;

        while (true) {
            // retrieve state number from top of stack
            $state = $this->stack[\count($this->stack)-1];

            // use default actions if available
            if (isset($this->defaultActions[$state])) {
                $action = $this->defaultActions[$state];
            } else {
                if ($symbol === null) {
                    $symbol = $this->lexer->lex();
                }
                // read action for current state and first input
                /** @var array<int, int>|false */
                $action = isset($this->table[$state][$symbol]) ? $this->table[$state][$symbol] : false;
            }

            // handle parse error
            if (!$action || !$action[0]) {
                assert(isset($symbol));
                if (!$recovering) {
                    // Report error
                    $expected = array();
                    foreach ($this->table[$state] as $p => $ignore) {
                        if (isset($this->terminals_[$p]) && $p > 2) {
                            $expected[] = "'" . $this->terminals_[$p] . "'";
                        }
                    }

                    $message = null;
                    if (\in_array("'STRING'", $expected) && \in_array(substr($this->lexer->match, 0, 1), array('"', "'"))) {
                        $message = "Invalid string";
                        if ("'" === substr($this->lexer->match, 0, 1)) {
                            $message .= ", it appears you used single quotes instead of double quotes";
                        } elseif (preg_match('{".+?(\\\\[^"bfnrt/\\\\u](...)?)}', $this->lexer->getFullUpcomingInput(), $match)) {
                            $message .= ", it appears you have an unescaped backslash at: ".$match[1];
                        } elseif (preg_match('{"(?:[^"]+|\\\\")*$}m', $this->lexer->getFullUpcomingInput())) {
                            $message .= ", it appears you forgot to terminate a string, or attempted to write a multiline string which is invalid";
                        }
                    }

                    $errStr = 'Parse error on line ' . ($yylineno+1) . ":\n";
                    $errStr .= $this->lexer->showPosition() . "\n";
                    if ($message) {
                        $errStr .= $message;
                    } else {
                        $errStr .= (\count($expected) > 1) ? "Expected one of: " : "Expected: ";
                        $errStr .= implode(', ', $expected);
                    }

                    if (',' === substr(trim($this->lexer->getPastInput()), -1)) {
                        $errStr .= " - It appears you have an extra trailing comma";
                    }

                    $this->parseError($errStr, array(
                        'text' => $this->lexer->match,
                        'token' => isset($this->terminals_[$symbol]) ? $this->terminals_[$symbol] : $symbol,
                        'line' => $this->lexer->yylineno,
                        'loc' => $yyloc,
                        'expected' => $expected,
                    ));
                }

                // just recovered from another error
                if ($recovering == 3) {
                    if ($symbol === Lexer::EOF) {
                        throw new ParsingException($errStr ?: 'Parsing halted.');
                    }

                    // discard current lookahead and grab another
                    $yyleng = $this->lexer->yyleng;
                    $yytext = $this->lexer->yytext;
                    $yylineno = $this->lexer->yylineno;
                    $yyloc = $this->lexer->yylloc;
                    $symbol = $this->lexer->lex();
                }

                // try to recover from error
                while (true) {
                    // check for error recovery rule in this state
                    if (\array_key_exists(Lexer::T_ERROR, $this->table[$state])) {
                        break;
                    }
                    if ($state == 0) {
                        throw new ParsingException($errStr ?: 'Parsing halted.');
                    }
                    $this->popStack(1);
                    $state = $this->stack[\count($this->stack)-1];
                }

                $preErrorSymbol = $symbol; // save the lookahead token
                $symbol = Lexer::T_ERROR;         // insert generic error symbol as new lookahead
                $state = $this->stack[\count($this->stack)-1];
                /** @var array<int, int>|false */
                $action = isset($this->table[$state][Lexer::T_ERROR]) ? $this->table[$state][Lexer::T_ERROR] : false;
                if ($action === false) {
                    throw new \LogicException('No table value found for '.$state.' => '.Lexer::T_ERROR);
                }
                $recovering = 3; // allow 3 real symbols to be shifted before reporting a new error
            }

            // this shouldn't happen, unless resolve defaults are off
            if (\is_array($action[0]) && \count($action) > 1) {
                throw new ParsingException('Parse Error: multiple actions possible at state: ' . $state . ', token: ' . $symbol);
            }

            switch ($action[0]) {
                case 1: // shift
                    assert(isset($symbol));
                    $this->stack[] = $symbol;
                    $this->vstack[] = $this->lexer->yytext;
                    $this->lstack[] = $this->lexer->yylloc;
                    $this->stack[] = $action[1]; // push state
                    $symbol = null;
                    if (!$preErrorSymbol) { // normal execution/no error
                        $yyleng = $this->lexer->yyleng;
                        $yytext = $this->lexer->yytext;
                        $yylineno = $this->lexer->yylineno;
                        $yyloc = $this->lexer->yylloc;
                        if ($recovering > 0) {
                            $recovering--;
                        }
                    } else { // error just occurred, resume old lookahead from before error
                        $symbol = $preErrorSymbol;
                        $preErrorSymbol = null;
                    }
                    break;

                case 2: // reduce
                    $len = $this->productions_[$action[1]][1];

                    // perform semantic action
                    $currentToken = $this->vstack[\count($this->vstack) - $len]; // default to $$ = $1
                    // default location, uses first token for firsts, last for lasts
                    $position = array( // _$ = store
                        'first_line' => $this->lstack[\count($this->lstack) - ($len ?: 1)]['first_line'],
                        'last_line' => $this->lstack[\count($this->lstack) - 1]['last_line'],
                        'first_column' => $this->lstack[\count($this->lstack) - ($len ?: 1)]['first_column'],
                        'last_column' => $this->lstack[\count($this->lstack) - 1]['last_column'],
                    );
                    list($newToken, $actionResult) = $this->performAction($currentToken, $yytext, $yyleng, $yylineno, $action[1]);

                    if (!$actionResult instanceof Undefined) {
                        return $actionResult;
                    }

                    if ($len) {
                        $this->popStack($len);
                    }

                    $this->stack[] = $this->productions_[$action[1]][0];    // push nonterminal (reduce)
                    $this->vstack[] = $newToken;
                    $this->lstack[] = $position;
                    /** @var int */
                    $newState = $this->table[$this->stack[\count($this->stack)-2]][$this->stack[\count($this->stack)-1]];
                    $this->stack[] = $newState;
                    break;

                case 3: // accept

                    return true;
            }
        }
    }

    /**
     * @param  string $str
     * @param  array{text: string, token: string|int, line: int, loc: array{first_line: int, first_column: int, last_line: int, last_column: int}, expected: string[]}|null $hash
     * @return never
     */
    protected function parseError($str, $hash = null)
    {
        throw new ParsingException($str, $hash ?: array());
    }

    /**
     * @param  stdClass|array<mixed>|int|bool|float|string|null $currentToken
     * @param  string   $yytext
     * @param  int      $yyleng
     * @param  int      $yylineno
     * @param  int      $yystate
     * @return array{stdClass|array<mixed>|int|bool|float|string|null, stdClass|array<mixed>|int|bool|float|string|null|Undefined}
     */
    private function performAction($currentToken, $yytext, $yyleng, $yylineno, $yystate)
    {
        $token = $currentToken;

        $len = \count($this->vstack) - 1;
        switch ($yystate) {
        case 1:
            $yytext = preg_replace_callback('{(?:\\\\["bfnrt/\\\\]|\\\\u[a-fA-F0-9]{4})}', array($this, 'stringInterpolation'), $yytext);
            $token = $yytext;
            break;
        case 2:
            if (strpos($yytext, 'e') !== false || strpos($yytext, 'E') !== false) {
                $token = \floatval($yytext);
            } else {
                $token = strpos($yytext, '.') === false ? \intval($yytext) : \floatval($yytext);
            }
            break;
        case 3:
            $token = null;
            break;
        case 4:
            $token = true;
            break;
        case 5:
            $token = false;
            break;
        case 6:
            $token = $this->vstack[$len-1];

            return array($token, $token);
        case 13:
            if ($this->flags & self::PARSE_TO_ASSOC) {
                $token = array();
            } else {
                $token = new stdClass;
            }
            break;
        case 14:
            $token = $this->vstack[$len-1];
            break;
        case 15:
            $token = array($this->vstack[$len-2], $this->vstack[$len]);
            break;
        case 16:
            assert(\is_array($this->vstack[$len]));
            if (PHP_VERSION_ID < 70100) {
                $property = $this->vstack[$len][0] === '' ? '_empty_' : $this->vstack[$len][0];
            } else {
                $property = $this->vstack[$len][0];
            }
            if ($this->flags & self::PARSE_TO_ASSOC) {
                $token = array();
                $token[$property] = $this->vstack[$len][1];
            } else {
                $token = new stdClass;
                $token->$property = $this->vstack[$len][1];
            }
            break;
        case 17:
            assert(\is_array($this->vstack[$len]));
            if ($this->flags & self::PARSE_TO_ASSOC) {
                assert(\is_array($this->vstack[$len-2]));
                $token =& $this->vstack[$len-2];
                $key = $this->vstack[$len][0];
                if (($this->flags & self::DETECT_KEY_CONFLICTS) && isset($this->vstack[$len-2][$key])) {
                    $errStr = 'Parse error on line ' . ($yylineno+1) . ":\n";
                    $errStr .= $this->lexer->showPosition() . "\n";
                    $errStr .= "Duplicate key: ".$this->vstack[$len][0];
                    throw new DuplicateKeyException($errStr, $this->vstack[$len][0], array('line' => $yylineno+1));
                }
                if (($this->flags & self::ALLOW_DUPLICATE_KEYS) && isset($this->vstack[$len-2][$key])) {
                    $duplicateCount = 1;
                    do {
                        $duplicateKey = $key . '.' . $duplicateCount++;
                    } while (isset($this->vstack[$len-2][$duplicateKey]));
                    $this->vstack[$len-2][$duplicateKey] = $this->vstack[$len][1];
                } elseif (($this->flags & self::ALLOW_DUPLICATE_KEYS_TO_ARRAY) && isset($this->vstack[$len-2][$key])) {
                    if (!isset($this->vstack[$len-2][$key]['__duplicates__']) || !is_array($this->vstack[$len-2][$key]['__duplicates__'])) {
                        $this->vstack[$len-2][$key] = array('__duplicates__' => array($this->vstack[$len-2][$key]));
                    }
                    $this->vstack[$len-2][$key]['__duplicates__'][] = $this->vstack[$len][1];
                } else {
                    $this->vstack[$len-2][$key] = $this->vstack[$len][1];
                }
            } else {
                assert($this->vstack[$len-2] instanceof stdClass);
                $token = $this->vstack[$len-2];
                if (PHP_VERSION_ID < 70100) {
                    $key = $this->vstack[$len][0] === '' ? '_empty_' : $this->vstack[$len][0];
                } else {
                    $key = $this->vstack[$len][0];
                }
                if (($this->flags & self::DETECT_KEY_CONFLICTS) && isset($this->vstack[$len-2]->$key)) {
                    $errStr = 'Parse error on line ' . ($yylineno+1) . ":\n";
                    $errStr .= $this->lexer->showPosition() . "\n";
                    $errStr .= "Duplicate key: ".$this->vstack[$len][0];
                    throw new DuplicateKeyException($errStr, $this->vstack[$len][0], array('line' => $yylineno+1));
                }
                if (($this->flags & self::ALLOW_DUPLICATE_KEYS) && isset($this->vstack[$len-2]->$key)) {
                    $duplicateCount = 1;
                    do {
                        $duplicateKey = $key . '.' . $duplicateCount++;
                    } while (isset($this->vstack[$len-2]->$duplicateKey));
                    $this->vstack[$len-2]->$duplicateKey = $this->vstack[$len][1];
                } elseif (($this->flags & self::ALLOW_DUPLICATE_KEYS_TO_ARRAY) && isset($this->vstack[$len-2]->$key)) {
                    if (!isset($this->vstack[$len-2]->$key->__duplicates__)) {
                        $this->vstack[$len-2]->$key = (object) array('__duplicates__' => array($this->vstack[$len-2]->$key));
                    }
                    $this->vstack[$len-2]->$key->__duplicates__[] = $this->vstack[$len][1];
                } else {
                    $this->vstack[$len-2]->$key = $this->vstack[$len][1];
                }
            }
            break;
        case 18:
            $token = array();
            break;
        case 19:
            $token = $this->vstack[$len-1];
            break;
        case 20:
            $token = array($this->vstack[$len]);
            break;
        case 21:
            assert(\is_array($this->vstack[$len-2]));
            $this->vstack[$len-2][] = $this->vstack[$len];
            $token = $this->vstack[$len-2];
            break;
        }

        return array($token, new Undefined());
    }

    /**
     * @param  string $match
     * @return string
     */
    private function stringInterpolation($match)
    {
        switch ($match[0]) {
        case '\\\\':
            return '\\';
        case '\"':
            return '"';
        case '\b':
            return \chr(8);
        case '\f':
            return \chr(12);
        case '\n':
            return "\n";
        case '\r':
            return "\r";
        case '\t':
            return "\t";
        case '\/':
            return "/";
        default:
            return html_entity_decode('&#x'.ltrim(substr($match[0], 2), '0').';', ENT_QUOTES, 'UTF-8');
        }
    }

    /**
     * @param  int $n
     * @return void
     */
    private function popStack($n)
    {
        $this->stack = \array_slice($this->stack, 0, - (2 * $n));
        $this->vstack = \array_slice($this->vstack, 0, - $n);
        $this->lstack = \array_slice($this->lstack, 0, - $n);
    }

    /**
     * @param  string $input
     * @return void
     */
    private function failOnBOM($input)
    {
        // UTF-8 ByteOrderMark sequence
        $bom = "\xEF\xBB\xBF";

        if (substr($input, 0, 3) === $bom) {
            $this->parseError("BOM detected, make sure your input does not include a Unicode Byte-Order-Mark");
        }
    }
}
#!/usr/bin/env php
<?php

/*
 * This file is part of the JSON Lint package.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

function includeIfExists($file)
{
    if (file_exists($file)) {
        return include $file;
    }
}

if (!includeIfExists(__DIR__.'/../vendor/autoload.php') && !includeIfExists(__DIR__.'/../../../autoload.php')) {
    $msg = 'You must set up the project dependencies, run the following commands:'.PHP_EOL.
           'curl -sS https://getcomposer.org/installer | php'.PHP_EOL.
           'php composer.phar install'.PHP_EOL;
    fwrite(STDERR, $msg);
    exit(1);
}

use Seld\JsonLint\JsonParser;

$files = array();
$quiet = false;

if (isset($_SERVER['argc']) && $_SERVER['argc'] > 1) {
    for ($i = 1; $i < $_SERVER['argc']; $i++) {
        $arg = $_SERVER['argv'][$i];
        if ($arg == '-q' || $arg == '--quiet') {
            $quiet = true;
        } else {
            if ($arg == '-h' || $arg == '--help') {
                showUsage($_SERVER['argv'][0]);
            } else {
                $files[] = $arg;
            }
        }
    }
}

if (!empty($files)) {
    // file linting
    $exitCode = 0;
    foreach ($files as $file) {
        $result = lintFile($file, $quiet);
        if ($result === false) {
            $exitCode = 1;
        }
    }
    exit($exitCode);
} else {
    //stdin linting
    if ($contents = file_get_contents('php://stdin')) {
        lint($contents, $quiet);
    } else {
        fwrite(STDERR, 'No file name or json input given' . PHP_EOL);
        exit(1);
    }
}

// stdin lint function
function lint($content, $quiet = false)
{
    $parser = new JsonParser();
    if ($err = $parser->lint($content)) {
        fwrite(STDERR, $err->getMessage() . ' (stdin)' . PHP_EOL);
        exit(1);
    }
    if (!$quiet) {
        echo 'Valid JSON (stdin)' . PHP_EOL;
        exit(0);
    }
}

// file lint function
function lintFile($file, $quiet = false)
{
    if (!preg_match('{^https?://}i', $file)) {
        if (!file_exists($file)) {
            fwrite(STDERR, 'File not found: ' . $file . PHP_EOL);
            return false;
        }
        if (!is_readable($file)) {
            fwrite(STDERR, 'File not readable: ' . $file . PHP_EOL);
            return false;
        }
    }

    $content = file_get_contents($file);
    $parser = new JsonParser();
    if ($err = $parser->lint($content)) {
        fwrite(STDERR, $file . ': ' . $err->getMessage() . PHP_EOL);
        return false;
    }
    if (!$quiet) {
        echo 'Valid JSON (' . $file . ')' . PHP_EOL;
    }
    return true;
}

// usage text function
function showUsage($programPath)
{
    echo 'Usage: '.$programPath.' file [options]'.PHP_EOL;
    echo PHP_EOL;
    echo 'Options:'.PHP_EOL;
    echo '  -q, --quiet     Cause jsonlint to be quiet when no errors are found'.PHP_EOL;
    echo '  -h, --help      Show this message'.PHP_EOL;
    exit(0);
}
Copyright (c) 2015 Jordi Boggiano

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.
<?php

/*
 * This file is part of PHAR Utils.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Seld\PharUtils;

class Timestamps
{
    private $contents;

    /**
     * @param string $file path to the phar file to use
     */
    public function __construct($file)
    {
        $this->contents = file_get_contents($file);
    }

    /**
     * Updates each file's unix timestamps in the PHAR
     *
     * The PHAR signature can then be produced in a reproducible manner.
     *
     * @param int|\DateTimeInterface|string $timestamp Date string or DateTime or unix timestamp to use
     */
    public function updateTimestamps($timestamp = null)
    {
        if ($timestamp instanceof \DateTime || $timestamp instanceof \DateTimeInterface) {
            $timestamp = $timestamp->getTimestamp();
        } elseif (is_string($timestamp)) {
            $timestamp = strtotime($timestamp);
        } elseif (!is_int($timestamp)) {
            $timestamp = strtotime('1984-12-24T00:00:00Z');
        }

        // detect manifest offset / end of stub
        if (!preg_match('{__HALT_COMPILER\(\);(?: +\?>)?\r?\n}', $this->contents, $match, PREG_OFFSET_CAPTURE)) {
            throw new \RuntimeException('Could not detect the stub\'s end in the phar');
        }

        // set starting position and skip past manifest length
        $pos = $match[0][1] + strlen($match[0][0]);
        $stubEnd = $pos + $this->readUint($pos, 4);
        $pos += 4;

        $numFiles = $this->readUint($pos, 4);
        $pos += 4;

        // skip API version (YOLO)
        $pos += 2;

        // skip PHAR flags
        $pos += 4;

        $aliasLength = $this->readUint($pos, 4);
        $pos += 4 + $aliasLength;

        $metadataLength = $this->readUint($pos, 4);
        $pos += 4 + $metadataLength;

        while ($pos < $stubEnd) {
            $filenameLength = $this->readUint($pos, 4);
            $pos += 4 + $filenameLength;

            // skip filesize
            $pos += 4;

            // update timestamp to a fixed value
            $timeStampBytes = pack('L', $timestamp);
            $this->contents[$pos + 0] = $timeStampBytes[0];
            $this->contents[$pos + 1] = $timeStampBytes[1];
            $this->contents[$pos + 2] = $timeStampBytes[2];
            $this->contents[$pos + 3] = $timeStampBytes[3];

            // skip timestamp, compressed file size, crc32 checksum and file flags
            $pos += 4*4;

            $metadataLength = $this->readUint($pos, 4);
            $pos += 4 + $metadataLength;

            $numFiles--;
        }

        if ($numFiles !== 0) {
            throw new \LogicException('All files were not processed, something must have gone wrong');
        }
    }

    /**
     * Saves the updated phar file, optionally with an updated signature.
     *
     * @param  string $path
     * @param  int $signatureAlgo One of Phar::MD5, Phar::SHA1, Phar::SHA256 or Phar::SHA512
     * @return bool
     */
    public function save($path, $signatureAlgo)
    {
        $pos = $this->determineSignatureBegin();

        $algos = array(
            \Phar::MD5 => 'md5',
            \Phar::SHA1 => 'sha1',
            \Phar::SHA256 => 'sha256',
            \Phar::SHA512 => 'sha512',
        );

        if (!isset($algos[$signatureAlgo])) {
            throw new \UnexpectedValueException('Invalid hash algorithm given: '.$signatureAlgo.' expected one of Phar::MD5, Phar::SHA1, Phar::SHA256 or Phar::SHA512');
        }
        $algo = $algos[$signatureAlgo];

        // re-sign phar
        //           signature
        $signature = hash($algo, substr($this->contents, 0, $pos), true)
            // sig type
            . pack('L', $signatureAlgo)
            // ohai Greg & Marcus
            . 'GBMB';

        $this->contents = substr($this->contents, 0, $pos) . $signature;

        return file_put_contents($path, $this->contents);
    }

    private function readUint($pos, $bytes)
    {
        $res = unpack('V', substr($this->contents, $pos, $bytes));

        return $res[1];
    }

    /**
     * Determine the beginning of the signature.
     *
     * @return int
     */
    private function determineSignatureBegin()
    {
        // detect signature position
        if (!preg_match('{__HALT_COMPILER\(\);(?: +\?>)?\r?\n}', $this->contents, $match, PREG_OFFSET_CAPTURE)) {
            throw new \RuntimeException('Could not detect the stub\'s end in the phar');
        }

        // set starting position and skip past manifest length
        $pos = $match[0][1] + strlen($match[0][0]);
        $manifestEnd = $pos + 4 + $this->readUint($pos, 4);

        $pos += 4;
        $numFiles = $this->readUint($pos, 4);

        $pos += 4;

        // skip API version (YOLO)
        $pos += 2;

        // skip PHAR flags
        $pos += 4;

        $aliasLength = $this->readUint($pos, 4);
        $pos += 4 + $aliasLength;

        $metadataLength = $this->readUint($pos, 4);
        $pos += 4 + $metadataLength;

        $compressedSizes = 0;
        while (($numFiles > 0) && ($pos < $manifestEnd - 24)) {
            $filenameLength = $this->readUint($pos, 4);
            $pos += 4 + $filenameLength;

            // skip filesize and timestamp
            $pos += 2*4;

            $compressedSizes += $this->readUint($pos, 4);
            // skip compressed file size, crc32 checksum and file flags
            $pos += 3*4;

            $metadataLength = $this->readUint($pos, 4);
            $pos += 4 + $metadataLength;

            $numFiles--;
        }

        if ($numFiles !== 0) {
            throw new \LogicException('All files were not processed, something must have gone wrong');
        }

        return $manifestEnd + $compressedSizes;
    }
}
<?php

/*
 * This file is part of PHAR Utils.
 *
 * (c) Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Seld\PharUtils;

class Linter
{
    /**
     * Lints all php files inside a given phar with the current PHP version
     *
     * @param string $path Phar file path
     * @param list<string> $excludedPaths Paths which should be skipped by the linter
     */
    public static function lint($path, array $excludedPaths = array())
    {
        $php = defined('PHP_BINARY') ? PHP_BINARY : 'php';

        if ($isWindows = defined('PHP_WINDOWS_VERSION_BUILD')) {
            $tmpFile = @tempnam(sys_get_temp_dir(), '');

            if (!$tmpFile || !is_writable($tmpFile)) {
                throw new \RuntimeException('Unable to create temp file');
            }

            $php = self::escapeWindowsPath($php);
            $tmpFile = self::escapeWindowsPath($tmpFile);

            // PHP 8 encloses the command in double-quotes
            if (PHP_VERSION_ID >= 80000) {
                $format = '%s -l %s';
            } else {
                $format = '"%s -l %s"';
            }

            $command = sprintf($format, $php, $tmpFile);
        } else {
            $command = "'".$php."' -l";
        }

        $descriptorspec = array(
            0 => array('pipe', 'r'),
            1 => array('pipe', 'w'),
            2 => array('pipe', 'w')
        );

        // path to phar + phar:// + trailing slash
        $baseLen = strlen(realpath($path)) + 7 + 1;
        foreach (new \RecursiveIteratorIterator(new \Phar($path)) as $file) {
            if ($file->isDir()) {
                continue;
            }
            if (substr($file, -4) === '.php') {
                $filename = (string) $file;
                if (in_array(substr($filename, $baseLen), $excludedPaths, true)) {
                    continue;
                }
                if ($isWindows) {
                    file_put_contents($tmpFile, file_get_contents($filename));
                }

                $process = proc_open($command, $descriptorspec, $pipes);
                if (is_resource($process)) {
                    if (!$isWindows) {
                        fwrite($pipes[0], file_get_contents($filename));
                    }
                    fclose($pipes[0]);

                    $stdout = stream_get_contents($pipes[1]);
                    fclose($pipes[1]);
                    $stderr = stream_get_contents($pipes[2]);
                    fclose($pipes[2]);

                    $exitCode = proc_close($process);

                    if ($exitCode !== 0) {
                        if ($isWindows) {
                            $stderr = str_replace($tmpFile, $filename, $stderr);
                        }
                        throw new \UnexpectedValueException('Failed linting '.$file.': '.$stderr);
                    }
                } else {
                    throw new \RuntimeException('Could not start linter process');
                }
            }
        }

        if ($isWindows) {
            @unlink($tmpFile);
        }
    }

    /**
     * Escapes a Windows file path
     *
     * @param string $path
     * @return string The escaped path
     */
    private static function escapeWindowsPath($path)
    {
        // Quote if path contains spaces or brackets
        if (strpbrk($path, " ()") !== false) {
            $path = '"'.$path.'"';
        }

        return $path;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Contracts\Service;

/**
 * A ServiceProviderInterface that is also countable and iterable.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 *
 * @template-covariant T of mixed
 *
 * @extends ServiceProviderInterface<T>
 * @extends \IteratorAggregate<string, T>
 */
interface ServiceCollectionInterface extends ServiceProviderInterface, \Countable, \IteratorAggregate
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <p@tchwork.com>
 * @author Mateusz Sip <mateusz.sip@gmail.com>
 *
 * @template-covariant T of mixed
 */
interface ServiceProviderInterface extends ContainerInterface
{
    /**
     * @return T
     */
    public function get(string $id): mixed;

    public function has(string $id): bool;

    /**
     * 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 array<string, string> The provided service types, keyed by service names
     */
    public function getProvidedServices(): array;
}
Copyright (c) 2018-present Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
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.
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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\Required;
use Symfony\Contracts\Service\Attribute\SubscribedService;

trigger_deprecation('symfony/contracts', 'v3.5', '"%s" is deprecated, use "ServiceMethodsSubscriberTrait" instead.', ServiceSubscriberTrait::class);

/**
 * Implementation of ServiceSubscriberInterface that determines subscribed services
 * from methods that have the #[SubscribedService] attribute.
 *
 * Service ids are available as "ClassName::methodName" so that the implementation
 * of subscriber methods can be just `return $this->container->get(__METHOD__);`.
 *
 * @property ContainerInterface $container
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 *
 * @deprecated since symfony/contracts v3.5, use ServiceMethodsSubscriberTrait instead
 */
trait ServiceSubscriberTrait
{
    public static function getSubscribedServices(): array
    {
        $services = method_exists(get_parent_class(self::class) ?: '', __FUNCTION__) ? parent::getSubscribedServices() : [];

        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));
            }

            /* @var SubscribedService $attribute */
            $attribute = $attribute->newInstance();
            $attribute->key ??= self::class.'::'.$method->name;
            $attribute->type ??= $returnType instanceof \ReflectionNamedType ? $returnType->getName() : (string) $returnType;
            $attribute->nullable = $attribute->nullable ?: $returnType->allowsNull();

            if ($attribute->attributes) {
                $services[] = $attribute;
            } else {
                $services[$attribute->key] = ($attribute->nullable ? '?' : '').$attribute->type;
            }
        }

        return $services;
    }

    #[Required]
    public function setContainer(ContainerInterface $container): ?ContainerInterface
    {
        $ret = null;
        if (method_exists(get_parent_class(self::class) ?: '', __FUNCTION__)) {
            $ret = parent::setContainer($container);
        }

        $this->container = $container;

        return $ret;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Contracts\Service;

use Symfony\Contracts\Service\Attribute\SubscribedService;

/**
 * A ServiceSubscriber exposes its dependencies via the static {@link getSubscribedServices} method.
 *
 * The getSubscribedServices method returns an array of service types required by such instances,
 * optionally keyed by the service names used internally. Service types that start with an interrogation
 * mark "?" are optional, while the other ones are mandatory service dependencies.
 *
 * The injected service locators SHOULD NOT allow access to any other services not specified by the method.
 *
 * It is expected that ServiceSubscriber instances consume PSR-11-based service locators internally.
 * This interface does not dictate any injection method for these service locators, although constructor
 * injection is recommended.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
interface ServiceSubscriberInterface
{
    /**
     * Returns an array of service types (or {@see SubscribedService} objects) required
     * by such instances, optionally keyed by the service names used internally.
     *
     * For mandatory dependencies:
     *
     *  * ['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']
     *
     * additionally, an array of {@see SubscribedService}'s can be returned:
     *
     *  * [new SubscribedService('logger', Psr\Log\LoggerInterface::class)]
     *  * [new SubscribedService(type: Psr\Log\LoggerInterface::class, nullable: true)]
     *  * [new SubscribedService('http_client', HttpClientInterface::class, attributes: new Target('githubApi'))]
     *
     * @return string[]|SubscribedService[] The required service types, optionally keyed by service names
     */
    public static function getSubscribedServices(): array;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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
{
    /**
     * @return void
     */
    public function reset();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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\Required;
use Symfony\Contracts\Service\Attribute\SubscribedService;

/**
 * Implementation of ServiceSubscriberInterface that determines subscribed services
 * from methods that have the #[SubscribedService] attribute.
 *
 * Service ids are available as "ClassName::methodName" so that the implementation
 * of subscriber methods can be just `return $this->container->get(__METHOD__);`.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
trait ServiceMethodsSubscriberTrait
{
    protected ContainerInterface $container;

    public static function getSubscribedServices(): array
    {
        $services = method_exists(get_parent_class(self::class) ?: '', __FUNCTION__) ? parent::getSubscribedServices() : [];

        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));
            }

            /* @var SubscribedService $attribute */
            $attribute = $attribute->newInstance();
            $attribute->key ??= self::class.'::'.$method->name;
            $attribute->type ??= $returnType instanceof \ReflectionNamedType ? $returnType->getName() : (string) $returnType;
            $attribute->nullable = $attribute->nullable ?: $returnType->allowsNull();

            if ($attribute->attributes) {
                $services[] = $attribute;
            } else {
                $services[$attribute->key] = ($attribute->nullable ? '?' : '').$attribute->type;
            }
        }

        return $services;
    }

    #[Required]
    public function setContainer(ContainerInterface $container): ?ContainerInterface
    {
        $ret = null;
        if (method_exists(get_parent_class(self::class) ?: '', __FUNCTION__)) {
            $ret = parent::setContainer($container);
        }

        $this->container = $container;

        return $ret;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <robin.chalas@gmail.com>
 * @author Nicolas Grekas <p@tchwork.com>
 */
trait ServiceLocatorTrait
{
    private array $loading = [];
    private array $providedTypes;

    /**
     * @param array<string, callable> $factories
     */
    public function __construct(
        private array $factories,
    ) {
    }

    public function has(string $id): bool
    {
        return isset($this->factories[$id]);
    }

    public function get(string $id): mixed
    {
        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]);
        }
    }

    public function getProvidedServices(): array
    {
        if (!isset($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 {
        };
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <me@derrabus.de>
 */
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)]
final class Required
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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\ServiceMethodsSubscriberTrait;
use Symfony\Contracts\Service\ServiceSubscriberInterface;

/**
 * For use as the return value for {@see ServiceSubscriberInterface}.
 *
 * @example new SubscribedService('http_client', HttpClientInterface::class, false, new Target('githubApi'))
 *
 * Use with {@see ServiceMethodsSubscriberTrait} to mark a method's return type
 * as a subscribed service.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
#[\Attribute(\Attribute::TARGET_METHOD)]
final class SubscribedService
{
    /** @var object[] */
    public array $attributes;

    /**
     * @param string|null       $key        The key to use for the service
     * @param class-string|null $type       The service class
     * @param bool              $nullable   Whether the service is optional
     * @param object|object[]   $attributes One or more dependency injection attributes to use
     */
    public function __construct(
        public ?string $key = null,
        public ?string $type = null,
        public bool $nullable = false,
        array|object $attributes = [],
    ) {
        $this->attributes = \is_array($attributes) ? $attributes : [$attributes];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Contracts\Service\Test;

class_alias(ServiceLocatorTestCase::class, ServiceLocatorTest::class);

if (false) {
    /**
     * @deprecated since PHPUnit 9.6
     */
    class ServiceLocatorTest
    {
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Contracts\Service\Test;

use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use Symfony\Contracts\Service\ServiceLocatorTrait;

abstract class ServiceLocatorTestCase extends TestCase
{
    /**
     * @param array<string, callable> $factories
     */
    protected function getServiceLocator(array $factories): ContainerInterface
    {
        return new class($factories) implements ContainerInterface {
            use ServiceLocatorTrait;
        };
    }

    public function testHas()
    {
        $locator = $this->getServiceLocator([
            'foo' => fn () => 'bar',
            'bar' => fn () => 'baz',
            fn () => 'dummy',
        ]);

        $this->assertTrue($locator->has('foo'));
        $this->assertTrue($locator->has('bar'));
        $this->assertFalse($locator->has('dummy'));
    }

    public function testGet()
    {
        $locator = $this->getServiceLocator([
            'foo' => fn () => 'bar',
            'bar' => fn () => 'baz',
        ]);

        $this->assertSame('bar', $locator->get('foo'));
        $this->assertSame('baz', $locator->get('bar'));
    }

    public function testGetDoesNotMemoize()
    {
        $i = 0;
        $locator = $this->getServiceLocator([
            'foo' => function () use (&$i) {
                ++$i;

                return 'bar';
            },
        ]);

        $this->assertSame('bar', $locator->get('foo'));
        $this->assertSame('bar', $locator->get('foo'));
        $this->assertSame(2, $i);
    }

    public function testThrowsOnUndefinedInternalService()
    {
        $locator = $this->getServiceLocator([
            'foo' => function () use (&$locator) { return $locator->get('bar'); },
        ]);

        $this->expectException(NotFoundExceptionInterface::class);
        $this->expectExceptionMessage('The service "foo" has a dependency on a non-existent service "bar". This locator only knows about the "foo" service.');

        $locator->get('foo');
    }

    public function testThrowsOnCircularReference()
    {
        $locator = $this->getServiceLocator([
            'foo' => function () use (&$locator) { return $locator->get('bar'); },
            'bar' => function () use (&$locator) { return $locator->get('baz'); },
            'baz' => function () use (&$locator) { return $locator->get('bar'); },
        ]);

        $this->expectException(ContainerExceptionInterface::class);
        $this->expectExceptionMessage('Circular reference detected for service "bar", path: "bar -> baz -> bar".');

        $locator->get('foo');
    }
}
Copyright (c) 2015-present Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 (!function_exists('grapheme_str_split')) {
    function grapheme_str_split(string $string, int $length = 1): array|false { return p\Grapheme::grapheme_str_split($string, $length); }
}

if (extension_loaded('intl')) {
    return;
}

if (!defined('GRAPHEME_EXTR_COUNT')) {
    define('GRAPHEME_EXTR_COUNT', 0);
}
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); }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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
 * - grapheme_str_split - Splits a string into an array of individual or chunks of graphemes
 *
 * @author Nicolas Grekas <p@tchwork.com>
 *
 * @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');
    }

    public static function grapheme_str_split($s, $len = 1)
    {
        if (0 > $len || 1073741823 < $len) {
            if (80000 > \PHP_VERSION_ID) {
                return false;
            }

            throw new \ValueError('grapheme_str_split(): Argument #2 ($length) must be greater than 0 and less than or equal to 1073741823.');
        }

        if ('' === $s) {
            return [];
        }

        if (!preg_match_all('/('.SYMFONY_GRAPHEME_CLUSTER_RX.')/u', $s, $matches)) {
            return false;
        }

        if (1 === $len) {
            return $matches[0];
        }

        $chunks = array_chunk($matches[0], $len);

        foreach ($chunks as &$chunk) {
            $chunk = implode('', $chunk);
        }

        return $chunks;
    }

    private static function grapheme_position($s, $needle, $offset, $mode)
    {
        $needle = (string) $needle;
        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;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 (\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); }
}
if (!function_exists('grapheme_str_split')) {
    function grapheme_str_split($string, $length = 1) { return p\Grapheme::grapheme_str_split($string, $length); }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process;

/**
 * An executable finder specifically designed for the PHP executable.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class PhpExecutableFinder
{
    private ExecutableFinder $executableFinder;

    public function __construct()
    {
        $this->executableFinder = new ExecutableFinder();
    }

    /**
     * Finds The PHP executable.
     */
    public function find(bool $includeArgs = true): string|false
    {
        if ($php = getenv('PHP_BINARY')) {
            if (!is_executable($php) && !$php = $this->executableFinder->find($php)) {
                return false;
            }

            if (@is_dir($php)) {
                return false;
            }

            return $php;
        }

        $args = $this->findArguments();
        $args = $includeArgs && $args ? ' '.implode(' ', $args) : '';

        // PHP_BINARY return the current sapi executable
        if (\PHP_BINARY && \in_array(\PHP_SAPI, ['cli', 'cli-server', 'phpdbg'], true)) {
            return \PHP_BINARY.$args;
        }

        if ($php = getenv('PHP_PATH')) {
            if (!@is_executable($php) || @is_dir($php)) {
                return false;
            }

            return $php;
        }

        if ($php = getenv('PHP_PEAR_PHP_BIN')) {
            if (@is_executable($php) && !@is_dir($php)) {
                return $php;
            }
        }

        if (@is_executable($php = \PHP_BINDIR.('\\' === \DIRECTORY_SEPARATOR ? '\\php.exe' : '/php')) && !@is_dir($php)) {
            return $php;
        }

        $dirs = [\PHP_BINDIR];
        if ('\\' === \DIRECTORY_SEPARATOR) {
            $dirs[] = 'C:\xampp\php\\';
        }

        return $this->executableFinder->find('php', false, $dirs);
    }

    /**
     * Finds the PHP executable arguments.
     */
    public function findArguments(): array
    {
        $arguments = [];
        if ('phpdbg' === \PHP_SAPI) {
            $arguments[] = '-qrr';
        }

        return $arguments;
    }
}
Copyright (c) 2004-present Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process;

use Symfony\Component\Process\Exception\RuntimeException;

/**
 * Provides a way to continuously write to the input of a Process until the InputStream is closed.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 *
 * @implements \IteratorAggregate<int, string>
 */
class InputStream implements \IteratorAggregate
{
    private ?\Closure $onEmpty = null;
    private array $input = [];
    private bool $open = true;

    /**
     * Sets a callback that is called when the write buffer becomes empty.
     *
     * @return void
     */
    public function onEmpty(?callable $onEmpty = null)
    {
        $this->onEmpty = null !== $onEmpty ? $onEmpty(...) : null;
    }

    /**
     * Appends an input to the write buffer.
     *
     * @param resource|string|int|float|bool|\Traversable|null $input The input to append as scalar,
     *                                                                stream resource or \Traversable
     *
     * @return void
     */
    public function write(mixed $input)
    {
        if (null === $input) {
            return;
        }
        if ($this->isClosed()) {
            throw new RuntimeException(\sprintf('"%s" is closed.', static::class));
        }
        $this->input[] = ProcessUtils::validateInput(__METHOD__, $input);
    }

    /**
     * Closes the write buffer.
     *
     * @return void
     */
    public function close()
    {
        $this->open = false;
    }

    /**
     * Tells whether the write buffer is closed or not.
     *
     * @return bool
     */
    public function isClosed()
    {
        return !$this->open;
    }

    public function getIterator(): \Traversable
    {
        $this->open = true;

        while ($this->open || $this->input) {
            if (!$this->input) {
                yield '';
                continue;
            }
            $current = array_shift($this->input);

            if ($current instanceof \Iterator) {
                yield from $current;
            } else {
                yield $current;
            }
            if (!$this->input && $this->open && null !== $onEmpty = $this->onEmpty) {
                $this->write($onEmpty($this));
            }
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Exception;

/**
 * InvalidArgumentException for the Process Component.
 *
 * @author Romain Neutron <imprec@gmail.com>
 */
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Exception;

use Symfony\Component\Process\Process;

/**
 * Exception that is thrown when a process times out.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class ProcessTimedOutException extends RuntimeException
{
    public const TYPE_GENERAL = 1;
    public const TYPE_IDLE = 2;

    private Process $process;
    private int $timeoutType;

    public function __construct(Process $process, int $timeoutType)
    {
        $this->process = $process;
        $this->timeoutType = $timeoutType;

        parent::__construct(\sprintf(
            'The process "%s" exceeded the timeout of %s seconds.',
            $process->getCommandLine(),
            $this->getExceededTimeout()
        ));
    }

    /**
     * @return Process
     */
    public function getProcess()
    {
        return $this->process;
    }

    /**
     * @return bool
     */
    public function isGeneralTimeout()
    {
        return self::TYPE_GENERAL === $this->timeoutType;
    }

    /**
     * @return bool
     */
    public function isIdleTimeout()
    {
        return self::TYPE_IDLE === $this->timeoutType;
    }

    public function getExceededTimeout(): ?float
    {
        return match ($this->timeoutType) {
            self::TYPE_GENERAL => $this->process->getTimeout(),
            self::TYPE_IDLE => $this->process->getIdleTimeout(),
            default => throw new \LogicException(\sprintf('Unknown timeout type "%d".', $this->timeoutType)),
        };
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Exception;

/**
 * LogicException for the Process Component.
 *
 * @author Romain Neutron <imprec@gmail.com>
 */
class LogicException extends \LogicException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Exception;

/**
 * RuntimeException for the Process Component.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Exception;

use Symfony\Component\Process\Process;

/**
 * Exception for failed processes.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class ProcessFailedException extends RuntimeException
{
    private Process $process;

    public function __construct(Process $process)
    {
        if ($process->isSuccessful()) {
            throw new InvalidArgumentException('Expected a failed process, but the given process was successful.');
        }

        $error = \sprintf('The command "%s" failed.'."\n\nExit Code: %s(%s)\n\nWorking directory: %s",
            $process->getCommandLine(),
            $process->getExitCode(),
            $process->getExitCodeText(),
            $process->getWorkingDirectory()
        );

        if (!$process->isOutputDisabled()) {
            $error .= \sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s",
                $process->getOutput(),
                $process->getErrorOutput()
            );
        }

        parent::__construct($error);

        $this->process = $process;
    }

    /**
     * @return Process
     */
    public function getProcess()
    {
        return $this->process;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Exception;

/**
 * Marker Interface for the Process Component.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
interface ExceptionInterface extends \Throwable
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Exception;

use Symfony\Component\Process\Process;

/**
 * Exception that is thrown when a process has been signaled.
 *
 * @author Sullivan Senechal <soullivaneuh@gmail.com>
 */
final class ProcessSignaledException extends RuntimeException
{
    private Process $process;

    public function __construct(Process $process)
    {
        $this->process = $process;

        parent::__construct(\sprintf('The process has been signaled with signal "%s".', $process->getTermSignal()));
    }

    public function getProcess(): Process
    {
        return $this->process;
    }

    public function getSignal(): int
    {
        return $this->getProcess()->getTermSignal();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Exception;

use Symfony\Component\Process\Messenger\RunProcessContext;

/**
 * @author Kevin Bond <kevinbond@gmail.com>
 */
final class RunProcessFailedException extends RuntimeException
{
    public function __construct(ProcessFailedException $exception, public readonly RunProcessContext $context)
    {
        parent::__construct($exception->getMessage(), $exception->getCode());
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process;

/**
 * Generic executable finder.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class ExecutableFinder
{
    private const CMD_BUILTINS = [
        'assoc', 'break', 'call', 'cd', 'chdir', 'cls', 'color', 'copy', 'date',
        'del', 'dir', 'echo', 'endlocal', 'erase', 'exit', 'for', 'ftype', 'goto',
        'help', 'if', 'label', 'md', 'mkdir', 'mklink', 'move', 'path', 'pause',
        'popd', 'prompt', 'pushd', 'rd', 'rem', 'ren', 'rename', 'rmdir', 'set',
        'setlocal', 'shift', 'start', 'time', 'title', 'type', 'ver', 'vol',
    ];

    private array $suffixes = [];

    /**
     * Replaces default suffixes of executable.
     *
     * @return void
     */
    public function setSuffixes(array $suffixes)
    {
        $this->suffixes = $suffixes;
    }

    /**
     * Adds new possible suffix to check for executable.
     *
     * @return void
     */
    public function addSuffix(string $suffix)
    {
        $this->suffixes[] = $suffix;
    }

    /**
     * Finds an executable by name.
     *
     * @param string      $name      The executable name (without the extension)
     * @param string|null $default   The default to return if no executable is found
     * @param array       $extraDirs Additional dirs to check into
     */
    public function find(string $name, ?string $default = null, array $extraDirs = []): ?string
    {
        // windows built-in commands that are present in cmd.exe should not be resolved using PATH as they do not exist as exes
        if ('\\' === \DIRECTORY_SEPARATOR && \in_array(strtolower($name), self::CMD_BUILTINS, true)) {
            return $name;
        }

        $dirs = array_merge(
            explode(\PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')),
            $extraDirs
        );

        $suffixes = [];
        if ('\\' === \DIRECTORY_SEPARATOR) {
            $pathExt = getenv('PATHEXT');
            $suffixes = $this->suffixes;
            $suffixes = array_merge($suffixes, $pathExt ? explode(\PATH_SEPARATOR, $pathExt) : ['.exe', '.bat', '.cmd', '.com']);
        }
        $suffixes = '' !== pathinfo($name, \PATHINFO_EXTENSION) ? array_merge([''], $suffixes) : array_merge($suffixes, ['']);
        foreach ($suffixes as $suffix) {
            foreach ($dirs as $dir) {
                if ('' === $dir) {
                    $dir = '.';
                }
                if (@is_file($file = $dir.\DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === \DIRECTORY_SEPARATOR || @is_executable($file))) {
                    return $file;
                }

                if (!@is_dir($dir) && basename($dir) === $name.$suffix && @is_executable($dir)) {
                    return $dir;
                }
            }
        }

        if ('\\' === \DIRECTORY_SEPARATOR || !\function_exists('exec') || \strlen($name) !== strcspn($name, '/'.\DIRECTORY_SEPARATOR)) {
            return $default;
        }

        $execResult = exec('command -v -- '.escapeshellarg($name));

        if (($executablePath = substr($execResult, 0, strpos($execResult, \PHP_EOL) ?: null)) && @is_executable($executablePath)) {
            return $executablePath;
        }

        return $default;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Pipes;

use Symfony\Component\Process\Process;

/**
 * UnixPipes implementation uses unix pipes as handles.
 *
 * @author Romain Neutron <imprec@gmail.com>
 *
 * @internal
 */
class UnixPipes extends AbstractPipes
{
    private ?bool $ttyMode;
    private bool $ptyMode;
    private bool $haveReadSupport;

    public function __construct(?bool $ttyMode, bool $ptyMode, mixed $input, bool $haveReadSupport)
    {
        $this->ttyMode = $ttyMode;
        $this->ptyMode = $ptyMode;
        $this->haveReadSupport = $haveReadSupport;

        parent::__construct($input);
    }

    public function __sleep(): array
    {
        throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
    }

    public function __wakeup(): void
    {
        throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
    }

    public function __destruct()
    {
        $this->close();
    }

    public function getDescriptors(): array
    {
        if (!$this->haveReadSupport) {
            $nullstream = fopen('/dev/null', 'c');

            return [
                ['pipe', 'r'],
                $nullstream,
                $nullstream,
            ];
        }

        if ($this->ttyMode) {
            return [
                ['file', '/dev/tty', 'r'],
                ['file', '/dev/tty', 'w'],
                ['file', '/dev/tty', 'w'],
            ];
        }

        if ($this->ptyMode && Process::isPtySupported()) {
            return [
                ['pty'],
                ['pty'],
                ['pipe', 'w'], // stderr needs to be in a pipe to correctly split error and output, since PHP will use the same stream for both
            ];
        }

        return [
            ['pipe', 'r'],
            ['pipe', 'w'], // stdout
            ['pipe', 'w'], // stderr
        ];
    }

    public function getFiles(): array
    {
        return [];
    }

    public function readAndWrite(bool $blocking, bool $close = false): array
    {
        $this->unblock();
        $w = $this->write();

        $read = $e = [];
        $r = $this->pipes;
        unset($r[0]);

        // let's have a look if something changed in streams
        set_error_handler($this->handleError(...));
        if (($r || $w) && false === stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
            restore_error_handler();
            // if a system call has been interrupted, forget about it, let's try again
            // otherwise, an error occurred, let's reset pipes
            if (!$this->hasSystemCallBeenInterrupted()) {
                $this->pipes = [];
            }

            return $read;
        }
        restore_error_handler();

        foreach ($r as $pipe) {
            // prior PHP 5.4 the array passed to stream_select is modified and
            // lose key association, we have to find back the key
            $read[$type = array_search($pipe, $this->pipes, true)] = '';

            do {
                $data = @fread($pipe, self::CHUNK_SIZE);
                $read[$type] .= $data;
            } while (isset($data[0]) && ($close || isset($data[self::CHUNK_SIZE - 1])));

            if (!isset($read[$type][0])) {
                unset($read[$type]);
            }

            if ($close && feof($pipe)) {
                fclose($pipe);
                unset($this->pipes[$type]);
            }
        }

        return $read;
    }

    public function haveReadSupport(): bool
    {
        return $this->haveReadSupport;
    }

    public function areOpen(): bool
    {
        return (bool) $this->pipes;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Pipes;

/**
 * PipesInterface manages descriptors and pipes for the use of proc_open.
 *
 * @author Romain Neutron <imprec@gmail.com>
 *
 * @internal
 */
interface PipesInterface
{
    public const CHUNK_SIZE = 16384;

    /**
     * Returns an array of descriptors for the use of proc_open.
     */
    public function getDescriptors(): array;

    /**
     * Returns an array of filenames indexed by their related stream in case these pipes use temporary files.
     *
     * @return string[]
     */
    public function getFiles(): array;

    /**
     * Reads data in file handles and pipes.
     *
     * @param bool $blocking Whether to use blocking calls or not
     * @param bool $close    Whether to close pipes if they've reached EOF
     *
     * @return string[] An array of read data indexed by their fd
     */
    public function readAndWrite(bool $blocking, bool $close = false): array;

    /**
     * Returns if the current state has open file handles or pipes.
     */
    public function areOpen(): bool;

    /**
     * Returns if pipes are able to read output.
     */
    public function haveReadSupport(): bool;

    /**
     * Closes file handles and pipes.
     */
    public function close(): void;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Pipes;

use Symfony\Component\Process\Exception\InvalidArgumentException;

/**
 * @author Romain Neutron <imprec@gmail.com>
 *
 * @internal
 */
abstract class AbstractPipes implements PipesInterface
{
    public array $pipes = [];

    private string $inputBuffer = '';
    /** @var resource|string|\Iterator */
    private $input;
    private bool $blocked = true;
    private ?string $lastError = null;

    /**
     * @param resource|string|\Iterator $input
     */
    public function __construct($input)
    {
        if (\is_resource($input) || $input instanceof \Iterator) {
            $this->input = $input;
        } else {
            $this->inputBuffer = (string) $input;
        }
    }

    public function close(): void
    {
        foreach ($this->pipes as $pipe) {
            if (\is_resource($pipe)) {
                fclose($pipe);
            }
        }
        $this->pipes = [];
    }

    /**
     * Returns true if a system call has been interrupted.
     *
     * stream_select() returns false when the `select` system call is interrupted by an incoming signal.
     */
    protected function hasSystemCallBeenInterrupted(): bool
    {
        $lastError = $this->lastError;
        $this->lastError = null;

        if (null === $lastError) {
            return false;
        }

        if (false !== stripos($lastError, 'interrupted system call')) {
            return true;
        }

        // on applications with a different locale than english, the message above is not found because
        // it's translated. So we also check for the SOCKET_EINTR constant which is defined under
        // Windows and UNIX-like platforms (if available on the platform).
        return \defined('SOCKET_EINTR') && str_starts_with($lastError, 'stream_select(): Unable to select ['.\SOCKET_EINTR.']');
    }

    /**
     * Unblocks streams.
     */
    protected function unblock(): void
    {
        if (!$this->blocked) {
            return;
        }

        foreach ($this->pipes as $pipe) {
            stream_set_blocking($pipe, false);
        }
        if (\is_resource($this->input)) {
            stream_set_blocking($this->input, false);
        }

        $this->blocked = false;
    }

    /**
     * Writes input to stdin.
     *
     * @throws InvalidArgumentException When an input iterator yields a non supported value
     */
    protected function write(): ?array
    {
        if (!isset($this->pipes[0])) {
            return null;
        }
        $input = $this->input;

        if ($input instanceof \Iterator) {
            if (!$input->valid()) {
                $input = null;
            } elseif (\is_resource($input = $input->current())) {
                stream_set_blocking($input, false);
            } elseif (!isset($this->inputBuffer[0])) {
                if (!\is_string($input)) {
                    if (!\is_scalar($input)) {
                        throw new InvalidArgumentException(\sprintf('"%s" yielded a value of type "%s", but only scalars and stream resources are supported.', get_debug_type($this->input), get_debug_type($input)));
                    }
                    $input = (string) $input;
                }
                $this->inputBuffer = $input;
                $this->input->next();
                $input = null;
            } else {
                $input = null;
            }
        }

        $r = $e = [];
        $w = [$this->pipes[0]];

        // let's have a look if something changed in streams
        if (false === @stream_select($r, $w, $e, 0, 0)) {
            return null;
        }

        foreach ($w as $stdin) {
            if (isset($this->inputBuffer[0])) {
                $written = fwrite($stdin, $this->inputBuffer);
                $this->inputBuffer = substr($this->inputBuffer, $written);
                if (isset($this->inputBuffer[0])) {
                    return [$this->pipes[0]];
                }
            }

            if ($input) {
                while (true) {
                    $data = fread($input, self::CHUNK_SIZE);
                    if (!isset($data[0])) {
                        break;
                    }
                    $written = fwrite($stdin, $data);
                    $data = substr($data, $written);
                    if (isset($data[0])) {
                        $this->inputBuffer = $data;

                        return [$this->pipes[0]];
                    }
                }
                if (feof($input)) {
                    if ($this->input instanceof \Iterator) {
                        $this->input->next();
                    } else {
                        $this->input = null;
                    }
                }
            }
        }

        // no input to read on resource, buffer is empty
        if (!isset($this->inputBuffer[0]) && !($this->input instanceof \Iterator ? $this->input->valid() : $this->input)) {
            $this->input = null;
            fclose($this->pipes[0]);
            unset($this->pipes[0]);
        } elseif (!$w) {
            return [$this->pipes[0]];
        }

        return null;
    }

    /**
     * @internal
     */
    public function handleError(int $type, string $msg): void
    {
        $this->lastError = $msg;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Pipes;

use Symfony\Component\Process\Exception\RuntimeException;
use Symfony\Component\Process\Process;

/**
 * WindowsPipes implementation uses temporary files as handles.
 *
 * @see https://bugs.php.net/51800
 * @see https://bugs.php.net/65650
 *
 * @author Romain Neutron <imprec@gmail.com>
 *
 * @internal
 */
class WindowsPipes extends AbstractPipes
{
    private array $files = [];
    private array $fileHandles = [];
    private array $lockHandles = [];
    private array $readBytes = [
        Process::STDOUT => 0,
        Process::STDERR => 0,
    ];
    private bool $haveReadSupport;

    public function __construct(mixed $input, bool $haveReadSupport)
    {
        $this->haveReadSupport = $haveReadSupport;

        if ($this->haveReadSupport) {
            // Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.
            // Workaround for this problem is to use temporary files instead of pipes on Windows platform.
            //
            // @see https://bugs.php.net/51800
            $pipes = [
                Process::STDOUT => Process::OUT,
                Process::STDERR => Process::ERR,
            ];
            $tmpDir = sys_get_temp_dir();
            $lastError = 'unknown reason';
            set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; });
            for ($i = 0;; ++$i) {
                foreach ($pipes as $pipe => $name) {
                    $file = \sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name);

                    if (!$h = fopen($file.'.lock', 'w')) {
                        if (file_exists($file.'.lock')) {
                            continue 2;
                        }
                        restore_error_handler();
                        throw new RuntimeException('A temporary file could not be opened to write the process output: '.$lastError);
                    }
                    if (!flock($h, \LOCK_EX | \LOCK_NB)) {
                        continue 2;
                    }
                    if (isset($this->lockHandles[$pipe])) {
                        flock($this->lockHandles[$pipe], \LOCK_UN);
                        fclose($this->lockHandles[$pipe]);
                    }
                    $this->lockHandles[$pipe] = $h;

                    if (!($h = fopen($file, 'w')) || !fclose($h) || !$h = fopen($file, 'r')) {
                        flock($this->lockHandles[$pipe], \LOCK_UN);
                        fclose($this->lockHandles[$pipe]);
                        unset($this->lockHandles[$pipe]);
                        continue 2;
                    }
                    $this->fileHandles[$pipe] = $h;
                    $this->files[$pipe] = $file;
                }
                break;
            }
            restore_error_handler();
        }

        parent::__construct($input);
    }

    public function __sleep(): array
    {
        throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
    }

    public function __wakeup(): void
    {
        throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
    }

    public function __destruct()
    {
        $this->close();
    }

    public function getDescriptors(): array
    {
        if (!$this->haveReadSupport) {
            $nullstream = fopen('NUL', 'c');

            return [
                ['pipe', 'r'],
                $nullstream,
                $nullstream,
            ];
        }

        // We're not using pipe on Windows platform as it hangs (https://bugs.php.net/51800)
        // We're not using file handles as it can produce corrupted output https://bugs.php.net/65650
        // So we redirect output within the commandline and pass the nul device to the process
        return [
            ['pipe', 'r'],
            ['file', 'NUL', 'w'],
            ['file', 'NUL', 'w'],
        ];
    }

    public function getFiles(): array
    {
        return $this->files;
    }

    public function readAndWrite(bool $blocking, bool $close = false): array
    {
        $this->unblock();
        $w = $this->write();
        $read = $r = $e = [];

        if ($blocking) {
            if ($w) {
                @stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6);
            } elseif ($this->fileHandles) {
                usleep((int) (Process::TIMEOUT_PRECISION * 1E6));
            }
        }
        foreach ($this->fileHandles as $type => $fileHandle) {
            $data = stream_get_contents($fileHandle, -1, $this->readBytes[$type]);

            if (isset($data[0])) {
                $this->readBytes[$type] += \strlen($data);
                $read[$type] = $data;
            }
            if ($close) {
                ftruncate($fileHandle, 0);
                fclose($fileHandle);
                flock($this->lockHandles[$type], \LOCK_UN);
                fclose($this->lockHandles[$type]);
                unset($this->fileHandles[$type], $this->lockHandles[$type]);
            }
        }

        return $read;
    }

    public function haveReadSupport(): bool
    {
        return $this->haveReadSupport;
    }

    public function areOpen(): bool
    {
        return $this->pipes && $this->fileHandles;
    }

    public function close(): void
    {
        parent::close();
        foreach ($this->fileHandles as $type => $handle) {
            ftruncate($handle, 0);
            fclose($handle);
            flock($this->lockHandles[$type], \LOCK_UN);
            fclose($this->lockHandles[$type]);
        }
        $this->fileHandles = $this->lockHandles = [];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Messenger;

use Symfony\Component\Process\Process;

/**
 * @author Kevin Bond <kevinbond@gmail.com>
 */
final class RunProcessContext
{
    public readonly ?int $exitCode;
    public readonly ?string $output;
    public readonly ?string $errorOutput;

    public function __construct(
        public readonly RunProcessMessage $message,
        Process $process,
    ) {
        $this->exitCode = $process->getExitCode();
        $this->output = $process->isOutputDisabled() ? null : $process->getOutput();
        $this->errorOutput = $process->isOutputDisabled() ? null : $process->getErrorOutput();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Messenger;

/**
 * @author Kevin Bond <kevinbond@gmail.com>
 */
class RunProcessMessage implements \Stringable
{
    public function __construct(
        public readonly array $command,
        public readonly ?string $cwd = null,
        public readonly ?array $env = null,
        public readonly mixed $input = null,
        public readonly ?float $timeout = 60.0,
    ) {
    }

    public function __toString(): string
    {
        return implode(' ', $this->command);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Messenger;

use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Exception\RunProcessFailedException;
use Symfony\Component\Process\Process;

/**
 * @author Kevin Bond <kevinbond@gmail.com>
 */
final class RunProcessMessageHandler
{
    public function __invoke(RunProcessMessage $message): RunProcessContext
    {
        $process = new Process($message->command, $message->cwd, $message->env, $message->input, $message->timeout);

        try {
            return new RunProcessContext($message, $process->mustRun());
        } catch (ProcessFailedException $e) {
            throw new RunProcessFailedException($e, new RunProcessContext($message, $e->getProcess()));
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process;

use Symfony\Component\Process\Exception\InvalidArgumentException;

/**
 * ProcessUtils is a bunch of utility methods.
 *
 * This class contains static methods only and is not meant to be instantiated.
 *
 * @author Martin Hasoň <martin.hason@gmail.com>
 */
class ProcessUtils
{
    /**
     * This class should not be instantiated.
     */
    private function __construct()
    {
    }

    /**
     * Validates and normalizes a Process input.
     *
     * @param string $caller The name of method call that validates the input
     * @param mixed  $input  The input to validate
     *
     * @throws InvalidArgumentException In case the input is not valid
     */
    public static function validateInput(string $caller, mixed $input): mixed
    {
        if (null !== $input) {
            if (\is_resource($input)) {
                return $input;
            }
            if (\is_scalar($input)) {
                return (string) $input;
            }
            if ($input instanceof Process) {
                return $input->getIterator($input::ITER_SKIP_ERR);
            }
            if ($input instanceof \Iterator) {
                return $input;
            }
            if ($input instanceof \Traversable) {
                return new \IteratorIterator($input);
            }

            throw new InvalidArgumentException(\sprintf('"%s" only accepts strings, Traversable objects or stream resources.', $caller));
        }

        return $input;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process;

use Symfony\Component\Process\Exception\LogicException;
use Symfony\Component\Process\Exception\RuntimeException;

/**
 * PhpProcess runs a PHP script in an independent process.
 *
 *     $p = new PhpProcess('<?php echo "foo"; ?>');
 *     $p->run();
 *     print $p->getOutput()."\n";
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class PhpProcess extends Process
{
    /**
     * @param string      $script  The PHP script to run (as a string)
     * @param string|null $cwd     The working directory or null to use the working dir of the current PHP process
     * @param array|null  $env     The environment variables or null to use the same environment as the current PHP process
     * @param int         $timeout The timeout in seconds
     * @param array|null  $php     Path to the PHP binary to use with any additional arguments
     */
    public function __construct(string $script, ?string $cwd = null, ?array $env = null, int $timeout = 60, ?array $php = null)
    {
        if (null === $php) {
            $executableFinder = new PhpExecutableFinder();
            $php = $executableFinder->find(false);
            $php = false === $php ? null : array_merge([$php], $executableFinder->findArguments());
        }
        if ('phpdbg' === \PHP_SAPI) {
            $file = tempnam(sys_get_temp_dir(), 'dbg');
            file_put_contents($file, $script);
            register_shutdown_function('unlink', $file);
            $php[] = $file;
            $script = null;
        }

        parent::__construct($php, $cwd, $env, $script, $timeout);
    }

    public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, mixed $input = null, ?float $timeout = 60): static
    {
        throw new LogicException(\sprintf('The "%s()" method cannot be called when using "%s".', __METHOD__, self::class));
    }

    /**
     * @return void
     */
    public function start(?callable $callback = null, array $env = [])
    {
        if (null === $this->getCommandLine()) {
            throw new RuntimeException('Unable to find the PHP executable.');
        }

        parent::start($callback, $env);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process;

use Symfony\Component\Process\Exception\InvalidArgumentException;
use Symfony\Component\Process\Exception\LogicException;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Exception\ProcessSignaledException;
use Symfony\Component\Process\Exception\ProcessTimedOutException;
use Symfony\Component\Process\Exception\RuntimeException;
use Symfony\Component\Process\Pipes\UnixPipes;
use Symfony\Component\Process\Pipes\WindowsPipes;

/**
 * Process is a thin wrapper around proc_* functions to easily
 * start independent PHP processes.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Romain Neutron <imprec@gmail.com>
 *
 * @implements \IteratorAggregate<string, string>
 */
class Process implements \IteratorAggregate
{
    public const ERR = 'err';
    public const OUT = 'out';

    public const STATUS_READY = 'ready';
    public const STATUS_STARTED = 'started';
    public const STATUS_TERMINATED = 'terminated';

    public const STDIN = 0;
    public const STDOUT = 1;
    public const STDERR = 2;

    // Timeout Precision in seconds.
    public const TIMEOUT_PRECISION = 0.2;

    public const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking
    public const ITER_KEEP_OUTPUT = 2;  // By default, outputs are cleared while iterating, use this flag to keep them in memory
    public const ITER_SKIP_OUT = 4;     // Use this flag to skip STDOUT while iterating
    public const ITER_SKIP_ERR = 8;     // Use this flag to skip STDERR while iterating

    private ?\Closure $callback = null;
    private array|string $commandline;
    private ?string $cwd;
    private array $env = [];
    /** @var resource|string|\Iterator|null */
    private $input;
    private ?float $starttime = null;
    private ?float $lastOutputTime = null;
    private ?float $timeout = null;
    private ?float $idleTimeout = null;
    private ?int $exitcode = null;
    private array $fallbackStatus = [];
    private array $processInformation;
    private bool $outputDisabled = false;
    /** @var resource */
    private $stdout;
    /** @var resource */
    private $stderr;
    /** @var resource|null */
    private $process;
    private string $status = self::STATUS_READY;
    private int $incrementalOutputOffset = 0;
    private int $incrementalErrorOutputOffset = 0;
    private bool $tty = false;
    private bool $pty;
    private array $options = ['suppress_errors' => true, 'bypass_shell' => true];

    private WindowsPipes|UnixPipes $processPipes;

    private ?int $latestSignal = null;

    private static ?bool $sigchild = null;

    /**
     * Exit codes translation table.
     *
     * User-defined errors must use exit codes in the 64-113 range.
     */
    public static $exitCodes = [
        0 => 'OK',
        1 => 'General error',
        2 => 'Misuse of shell builtins',

        126 => 'Invoked command cannot execute',
        127 => 'Command not found',
        128 => 'Invalid exit argument',

        // signals
        129 => 'Hangup',
        130 => 'Interrupt',
        131 => 'Quit and dump core',
        132 => 'Illegal instruction',
        133 => 'Trace/breakpoint trap',
        134 => 'Process aborted',
        135 => 'Bus error: "access to undefined portion of memory object"',
        136 => 'Floating point exception: "erroneous arithmetic operation"',
        137 => 'Kill (terminate immediately)',
        138 => 'User-defined 1',
        139 => 'Segmentation violation',
        140 => 'User-defined 2',
        141 => 'Write to pipe with no one reading',
        142 => 'Signal raised by alarm',
        143 => 'Termination (request to terminate)',
        // 144 - not defined
        145 => 'Child process terminated, stopped (or continued*)',
        146 => 'Continue if stopped',
        147 => 'Stop executing temporarily',
        148 => 'Terminal stop signal',
        149 => 'Background process attempting to read from tty ("in")',
        150 => 'Background process attempting to write to tty ("out")',
        151 => 'Urgent data available on socket',
        152 => 'CPU time limit exceeded',
        153 => 'File size limit exceeded',
        154 => 'Signal raised by timer counting virtual time: "virtual timer expired"',
        155 => 'Profiling timer expired',
        // 156 - not defined
        157 => 'Pollable event',
        // 158 - not defined
        159 => 'Bad syscall',
    ];

    /**
     * @param array          $command The command to run and its arguments listed as separate entries
     * @param string|null    $cwd     The working directory or null to use the working dir of the current PHP process
     * @param array|null     $env     The environment variables or null to use the same environment as the current PHP process
     * @param mixed          $input   The input as stream resource, scalar or \Traversable, or null for no input
     * @param int|float|null $timeout The timeout in seconds or null to disable
     *
     * @throws LogicException When proc_open is not installed
     */
    public function __construct(array $command, ?string $cwd = null, ?array $env = null, mixed $input = null, ?float $timeout = 60)
    {
        if (!\function_exists('proc_open')) {
            throw new LogicException('The Process class relies on proc_open, which is not available on your PHP installation.');
        }

        $this->commandline = $command;
        $this->cwd = $cwd;

        // on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started
        // on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected
        // @see : https://bugs.php.net/51800
        // @see : https://bugs.php.net/50524
        if (null === $this->cwd && (\defined('ZEND_THREAD_SAFE') || '\\' === \DIRECTORY_SEPARATOR)) {
            $this->cwd = getcwd();
        }
        if (null !== $env) {
            $this->setEnv($env);
        }

        $this->setInput($input);
        $this->setTimeout($timeout);
        $this->pty = false;
    }

    /**
     * Creates a Process instance as a command-line to be run in a shell wrapper.
     *
     * Command-lines are parsed by the shell of your OS (/bin/sh on Unix-like, cmd.exe on Windows.)
     * This allows using e.g. pipes or conditional execution. In this mode, signals are sent to the
     * shell wrapper and not to your commands.
     *
     * In order to inject dynamic values into command-lines, we strongly recommend using placeholders.
     * This will save escaping values, which is not portable nor secure anyway:
     *
     *   $process = Process::fromShellCommandline('my_command "${:MY_VAR}"');
     *   $process->run(null, ['MY_VAR' => $theValue]);
     *
     * @param string         $command The command line to pass to the shell of the OS
     * @param string|null    $cwd     The working directory or null to use the working dir of the current PHP process
     * @param array|null     $env     The environment variables or null to use the same environment as the current PHP process
     * @param mixed          $input   The input as stream resource, scalar or \Traversable, or null for no input
     * @param int|float|null $timeout The timeout in seconds or null to disable
     *
     * @throws LogicException When proc_open is not installed
     */
    public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, mixed $input = null, ?float $timeout = 60): static
    {
        $process = new static([], $cwd, $env, $input, $timeout);
        $process->commandline = $command;

        return $process;
    }

    public function __sleep(): array
    {
        throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
    }

    /**
     * @return void
     */
    public function __wakeup()
    {
        throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
    }

    public function __destruct()
    {
        if ($this->options['create_new_console'] ?? false) {
            $this->processPipes->close();
        } else {
            $this->stop(0);
        }
    }

    public function __clone()
    {
        $this->resetProcessData();
    }

    /**
     * Runs the process.
     *
     * The callback receives the type of output (out or err) and
     * some bytes from the output in real-time. It allows to have feedback
     * from the independent process during execution.
     *
     * The STDOUT and STDERR are also available after the process is finished
     * via the getOutput() and getErrorOutput() methods.
     *
     * @param callable|null $callback A PHP callback to run whenever there is some
     *                                output available on STDOUT or STDERR
     *
     * @return int The exit status code
     *
     * @throws RuntimeException         When process can't be launched
     * @throws RuntimeException         When process is already running
     * @throws ProcessTimedOutException When process timed out
     * @throws ProcessSignaledException When process stopped after receiving signal
     * @throws LogicException           In case a callback is provided and output has been disabled
     *
     * @final
     */
    public function run(?callable $callback = null, array $env = []): int
    {
        $this->start($callback, $env);

        return $this->wait();
    }

    /**
     * Runs the process.
     *
     * This is identical to run() except that an exception is thrown if the process
     * exits with a non-zero exit code.
     *
     * @return $this
     *
     * @throws ProcessFailedException if the process didn't terminate successfully
     *
     * @final
     */
    public function mustRun(?callable $callback = null, array $env = []): static
    {
        if (0 !== $this->run($callback, $env)) {
            throw new ProcessFailedException($this);
        }

        return $this;
    }

    /**
     * Starts the process and returns after writing the input to STDIN.
     *
     * This method blocks until all STDIN data is sent to the process then it
     * returns while the process runs in the background.
     *
     * The termination of the process can be awaited with wait().
     *
     * The callback receives the type of output (out or err) and some bytes from
     * the output in real-time while writing the standard input to the process.
     * It allows to have feedback from the independent process during execution.
     *
     * @param callable|null $callback A PHP callback to run whenever there is some
     *                                output available on STDOUT or STDERR
     *
     * @return void
     *
     * @throws RuntimeException When process can't be launched
     * @throws RuntimeException When process is already running
     * @throws LogicException   In case a callback is provided and output has been disabled
     */
    public function start(?callable $callback = null, array $env = [])
    {
        if ($this->isRunning()) {
            throw new RuntimeException('Process is already running.');
        }

        $this->resetProcessData();
        $this->starttime = $this->lastOutputTime = microtime(true);
        $this->callback = $this->buildCallback($callback);
        $descriptors = $this->getDescriptors(null !== $callback);

        if ($this->env) {
            $env += '\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($this->env, $env, 'strcasecmp') : $this->env;
        }

        $env += '\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($this->getDefaultEnv(), $env, 'strcasecmp') : $this->getDefaultEnv();

        if (\is_array($commandline = $this->commandline)) {
            $commandline = implode(' ', array_map($this->escapeArgument(...), $commandline));

            if ('\\' !== \DIRECTORY_SEPARATOR) {
                // exec is mandatory to deal with sending a signal to the process
                $commandline = 'exec '.$commandline;
            }
        } else {
            $commandline = $this->replacePlaceholders($commandline, $env);
        }

        if ('\\' === \DIRECTORY_SEPARATOR) {
            $commandline = $this->prepareWindowsCommandLine($commandline, $env);
        } elseif ($this->isSigchildEnabled()) {
            // last exit code is output on the fourth pipe and caught to work around --enable-sigchild
            $descriptors[3] = ['pipe', 'w'];

            // See https://unix.stackexchange.com/questions/71205/background-process-pipe-input
            $commandline = '{ ('.$commandline.') <&3 3<&- 3>/dev/null & } 3<&0;';
            $commandline .= 'pid=$!; echo $pid >&3; wait $pid 2>/dev/null; code=$?; echo $code >&3; exit $code';
        }

        $envPairs = [];
        foreach ($env as $k => $v) {
            if (false !== $v && false === \in_array($k, ['argc', 'argv', 'ARGC', 'ARGV'], true)) {
                $envPairs[] = $k.'='.$v;
            }
        }

        if (!is_dir($this->cwd)) {
            throw new RuntimeException(\sprintf('The provided cwd "%s" does not exist.', $this->cwd));
        }

        $process = @proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $this->options);

        if (!$process) {
            throw new RuntimeException('Unable to launch a new process.');
        }
        $this->process = $process;
        $this->status = self::STATUS_STARTED;

        if (isset($descriptors[3])) {
            $this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]);
        }

        if ($this->tty) {
            return;
        }

        $this->updateStatus(false);
        $this->checkTimeout();
    }

    /**
     * Restarts the process.
     *
     * Be warned that the process is cloned before being started.
     *
     * @param callable|null $callback A PHP callback to run whenever there is some
     *                                output available on STDOUT or STDERR
     *
     * @throws RuntimeException When process can't be launched
     * @throws RuntimeException When process is already running
     *
     * @see start()
     *
     * @final
     */
    public function restart(?callable $callback = null, array $env = []): static
    {
        if ($this->isRunning()) {
            throw new RuntimeException('Process is already running.');
        }

        $process = clone $this;
        $process->start($callback, $env);

        return $process;
    }

    /**
     * Waits for the process to terminate.
     *
     * The callback receives the type of output (out or err) and some bytes
     * from the output in real-time while writing the standard input to the process.
     * It allows to have feedback from the independent process during execution.
     *
     * @param callable|null $callback A valid PHP callback
     *
     * @return int The exitcode of the process
     *
     * @throws ProcessTimedOutException When process timed out
     * @throws ProcessSignaledException When process stopped after receiving signal
     * @throws LogicException           When process is not yet started
     */
    public function wait(?callable $callback = null): int
    {
        $this->requireProcessIsStarted(__FUNCTION__);

        $this->updateStatus(false);

        if (null !== $callback) {
            if (!$this->processPipes->haveReadSupport()) {
                $this->stop(0);
                throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::wait".');
            }
            $this->callback = $this->buildCallback($callback);
        }

        do {
            $this->checkTimeout();
            $running = $this->isRunning() && ('\\' === \DIRECTORY_SEPARATOR || $this->processPipes->areOpen());
            $this->readPipes($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
        } while ($running);

        while ($this->isRunning()) {
            $this->checkTimeout();
            usleep(1000);
        }

        if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) {
            throw new ProcessSignaledException($this);
        }

        return $this->exitcode;
    }

    /**
     * Waits until the callback returns true.
     *
     * The callback receives the type of output (out or err) and some bytes
     * from the output in real-time while writing the standard input to the process.
     * It allows to have feedback from the independent process during execution.
     *
     * @throws RuntimeException         When process timed out
     * @throws LogicException           When process is not yet started
     * @throws ProcessTimedOutException In case the timeout was reached
     */
    public function waitUntil(callable $callback): bool
    {
        $this->requireProcessIsStarted(__FUNCTION__);
        $this->updateStatus(false);

        if (!$this->processPipes->haveReadSupport()) {
            $this->stop(0);
            throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::waitUntil".');
        }
        $callback = $this->buildCallback($callback);

        $ready = false;
        while (true) {
            $this->checkTimeout();
            $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
            $output = $this->processPipes->readAndWrite($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);

            foreach ($output as $type => $data) {
                if (3 !== $type) {
                    $ready = $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data) || $ready;
                } elseif (!isset($this->fallbackStatus['signaled'])) {
                    $this->fallbackStatus['exitcode'] = (int) $data;
                }
            }
            if ($ready) {
                return true;
            }
            if (!$running) {
                return false;
            }

            usleep(1000);
        }
    }

    /**
     * Returns the Pid (process identifier), if applicable.
     *
     * @return int|null The process id if running, null otherwise
     */
    public function getPid(): ?int
    {
        return $this->isRunning() ? $this->processInformation['pid'] : null;
    }

    /**
     * Sends a POSIX signal to the process.
     *
     * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants)
     *
     * @return $this
     *
     * @throws LogicException   In case the process is not running
     * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
     * @throws RuntimeException In case of failure
     */
    public function signal(int $signal): static
    {
        $this->doSignal($signal, true);

        return $this;
    }

    /**
     * Disables fetching output and error output from the underlying process.
     *
     * @return $this
     *
     * @throws RuntimeException In case the process is already running
     * @throws LogicException   if an idle timeout is set
     */
    public function disableOutput(): static
    {
        if ($this->isRunning()) {
            throw new RuntimeException('Disabling output while the process is running is not possible.');
        }
        if (null !== $this->idleTimeout) {
            throw new LogicException('Output cannot be disabled while an idle timeout is set.');
        }

        $this->outputDisabled = true;

        return $this;
    }

    /**
     * Enables fetching output and error output from the underlying process.
     *
     * @return $this
     *
     * @throws RuntimeException In case the process is already running
     */
    public function enableOutput(): static
    {
        if ($this->isRunning()) {
            throw new RuntimeException('Enabling output while the process is running is not possible.');
        }

        $this->outputDisabled = false;

        return $this;
    }

    /**
     * Returns true in case the output is disabled, false otherwise.
     */
    public function isOutputDisabled(): bool
    {
        return $this->outputDisabled;
    }

    /**
     * Returns the current output of the process (STDOUT).
     *
     * @throws LogicException in case the output has been disabled
     * @throws LogicException In case the process is not started
     */
    public function getOutput(): string
    {
        $this->readPipesForOutput(__FUNCTION__);

        if (false === $ret = stream_get_contents($this->stdout, -1, 0)) {
            return '';
        }

        return $ret;
    }

    /**
     * Returns the output incrementally.
     *
     * In comparison with the getOutput method which always return the whole
     * output, this one returns the new output since the last call.
     *
     * @throws LogicException in case the output has been disabled
     * @throws LogicException In case the process is not started
     */
    public function getIncrementalOutput(): string
    {
        $this->readPipesForOutput(__FUNCTION__);

        $latest = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
        $this->incrementalOutputOffset = ftell($this->stdout);

        if (false === $latest) {
            return '';
        }

        return $latest;
    }

    /**
     * Returns an iterator to the output of the process, with the output type as keys (Process::OUT/ERR).
     *
     * @param int $flags A bit field of Process::ITER_* flags
     *
     * @return \Generator<string, string>
     *
     * @throws LogicException in case the output has been disabled
     * @throws LogicException In case the process is not started
     */
    public function getIterator(int $flags = 0): \Generator
    {
        $this->readPipesForOutput(__FUNCTION__, false);

        $clearOutput = !(self::ITER_KEEP_OUTPUT & $flags);
        $blocking = !(self::ITER_NON_BLOCKING & $flags);
        $yieldOut = !(self::ITER_SKIP_OUT & $flags);
        $yieldErr = !(self::ITER_SKIP_ERR & $flags);

        while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) {
            if ($yieldOut) {
                $out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);

                if (isset($out[0])) {
                    if ($clearOutput) {
                        $this->clearOutput();
                    } else {
                        $this->incrementalOutputOffset = ftell($this->stdout);
                    }

                    yield self::OUT => $out;
                }
            }

            if ($yieldErr) {
                $err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);

                if (isset($err[0])) {
                    if ($clearOutput) {
                        $this->clearErrorOutput();
                    } else {
                        $this->incrementalErrorOutputOffset = ftell($this->stderr);
                    }

                    yield self::ERR => $err;
                }
            }

            if (!$blocking && !isset($out[0]) && !isset($err[0])) {
                yield self::OUT => '';
            }

            $this->checkTimeout();
            $this->readPipesForOutput(__FUNCTION__, $blocking);
        }
    }

    /**
     * Clears the process output.
     *
     * @return $this
     */
    public function clearOutput(): static
    {
        ftruncate($this->stdout, 0);
        fseek($this->stdout, 0);
        $this->incrementalOutputOffset = 0;

        return $this;
    }

    /**
     * Returns the current error output of the process (STDERR).
     *
     * @throws LogicException in case the output has been disabled
     * @throws LogicException In case the process is not started
     */
    public function getErrorOutput(): string
    {
        $this->readPipesForOutput(__FUNCTION__);

        if (false === $ret = stream_get_contents($this->stderr, -1, 0)) {
            return '';
        }

        return $ret;
    }

    /**
     * Returns the errorOutput incrementally.
     *
     * In comparison with the getErrorOutput method which always return the
     * whole error output, this one returns the new error output since the last
     * call.
     *
     * @throws LogicException in case the output has been disabled
     * @throws LogicException In case the process is not started
     */
    public function getIncrementalErrorOutput(): string
    {
        $this->readPipesForOutput(__FUNCTION__);

        $latest = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
        $this->incrementalErrorOutputOffset = ftell($this->stderr);

        if (false === $latest) {
            return '';
        }

        return $latest;
    }

    /**
     * Clears the process output.
     *
     * @return $this
     */
    public function clearErrorOutput(): static
    {
        ftruncate($this->stderr, 0);
        fseek($this->stderr, 0);
        $this->incrementalErrorOutputOffset = 0;

        return $this;
    }

    /**
     * Returns the exit code returned by the process.
     *
     * @return int|null The exit status code, null if the Process is not terminated
     */
    public function getExitCode(): ?int
    {
        $this->updateStatus(false);

        return $this->exitcode;
    }

    /**
     * Returns a string representation for the exit code returned by the process.
     *
     * This method relies on the Unix exit code status standardization
     * and might not be relevant for other operating systems.
     *
     * @return string|null A string representation for the exit status code, null if the Process is not terminated
     *
     * @see http://tldp.org/LDP/abs/html/exitcodes.html
     * @see http://en.wikipedia.org/wiki/Unix_signal
     */
    public function getExitCodeText(): ?string
    {
        if (null === $exitcode = $this->getExitCode()) {
            return null;
        }

        return self::$exitCodes[$exitcode] ?? 'Unknown error';
    }

    /**
     * Checks if the process ended successfully.
     */
    public function isSuccessful(): bool
    {
        return 0 === $this->getExitCode();
    }

    /**
     * Returns true if the child process has been terminated by an uncaught signal.
     *
     * It always returns false on Windows.
     *
     * @throws LogicException In case the process is not terminated
     */
    public function hasBeenSignaled(): bool
    {
        $this->requireProcessIsTerminated(__FUNCTION__);

        return $this->processInformation['signaled'];
    }

    /**
     * Returns the number of the signal that caused the child process to terminate its execution.
     *
     * It is only meaningful if hasBeenSignaled() returns true.
     *
     * @throws RuntimeException In case --enable-sigchild is activated
     * @throws LogicException   In case the process is not terminated
     */
    public function getTermSignal(): int
    {
        $this->requireProcessIsTerminated(__FUNCTION__);

        if ($this->isSigchildEnabled() && -1 === $this->processInformation['termsig']) {
            throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal cannot be retrieved.');
        }

        return $this->processInformation['termsig'];
    }

    /**
     * Returns true if the child process has been stopped by a signal.
     *
     * It always returns false on Windows.
     *
     * @throws LogicException In case the process is not terminated
     */
    public function hasBeenStopped(): bool
    {
        $this->requireProcessIsTerminated(__FUNCTION__);

        return $this->processInformation['stopped'];
    }

    /**
     * Returns the number of the signal that caused the child process to stop its execution.
     *
     * It is only meaningful if hasBeenStopped() returns true.
     *
     * @throws LogicException In case the process is not terminated
     */
    public function getStopSignal(): int
    {
        $this->requireProcessIsTerminated(__FUNCTION__);

        return $this->processInformation['stopsig'];
    }

    /**
     * Checks if the process is currently running.
     */
    public function isRunning(): bool
    {
        if (self::STATUS_STARTED !== $this->status) {
            return false;
        }

        $this->updateStatus(false);

        return $this->processInformation['running'];
    }

    /**
     * Checks if the process has been started with no regard to the current state.
     */
    public function isStarted(): bool
    {
        return self::STATUS_READY != $this->status;
    }

    /**
     * Checks if the process is terminated.
     */
    public function isTerminated(): bool
    {
        $this->updateStatus(false);

        return self::STATUS_TERMINATED == $this->status;
    }

    /**
     * Gets the process status.
     *
     * The status is one of: ready, started, terminated.
     */
    public function getStatus(): string
    {
        $this->updateStatus(false);

        return $this->status;
    }

    /**
     * Stops the process.
     *
     * @param int|float $timeout The timeout in seconds
     * @param int|null  $signal  A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9)
     *
     * @return int|null The exit-code of the process or null if it's not running
     */
    public function stop(float $timeout = 10, ?int $signal = null): ?int
    {
        $timeoutMicro = microtime(true) + $timeout;
        if ($this->isRunning()) {
            // given SIGTERM may not be defined and that "proc_terminate" uses the constant value and not the constant itself, we use the same here
            $this->doSignal(15, false);
            do {
                usleep(1000);
            } while ($this->isRunning() && microtime(true) < $timeoutMicro);

            if ($this->isRunning()) {
                // Avoid exception here: process is supposed to be running, but it might have stopped just
                // after this line. In any case, let's silently discard the error, we cannot do anything.
                $this->doSignal($signal ?: 9, false);
            }
        }

        if ($this->isRunning()) {
            if (isset($this->fallbackStatus['pid'])) {
                unset($this->fallbackStatus['pid']);

                return $this->stop(0, $signal);
            }
            $this->close();
        }

        return $this->exitcode;
    }

    /**
     * Adds a line to the STDOUT stream.
     *
     * @internal
     */
    public function addOutput(string $line): void
    {
        $this->lastOutputTime = microtime(true);

        fseek($this->stdout, 0, \SEEK_END);
        fwrite($this->stdout, $line);
        fseek($this->stdout, $this->incrementalOutputOffset);
    }

    /**
     * Adds a line to the STDERR stream.
     *
     * @internal
     */
    public function addErrorOutput(string $line): void
    {
        $this->lastOutputTime = microtime(true);

        fseek($this->stderr, 0, \SEEK_END);
        fwrite($this->stderr, $line);
        fseek($this->stderr, $this->incrementalErrorOutputOffset);
    }

    /**
     * Gets the last output time in seconds.
     */
    public function getLastOutputTime(): ?float
    {
        return $this->lastOutputTime;
    }

    /**
     * Gets the command line to be executed.
     */
    public function getCommandLine(): string
    {
        return \is_array($this->commandline) ? implode(' ', array_map($this->escapeArgument(...), $this->commandline)) : $this->commandline;
    }

    /**
     * Gets the process timeout in seconds (max. runtime).
     */
    public function getTimeout(): ?float
    {
        return $this->timeout;
    }

    /**
     * Gets the process idle timeout in seconds (max. time since last output).
     */
    public function getIdleTimeout(): ?float
    {
        return $this->idleTimeout;
    }

    /**
     * Sets the process timeout (max. runtime) in seconds.
     *
     * To disable the timeout, set this value to null.
     *
     * @return $this
     *
     * @throws InvalidArgumentException if the timeout is negative
     */
    public function setTimeout(?float $timeout): static
    {
        $this->timeout = $this->validateTimeout($timeout);

        return $this;
    }

    /**
     * Sets the process idle timeout (max. time since last output) in seconds.
     *
     * To disable the timeout, set this value to null.
     *
     * @return $this
     *
     * @throws LogicException           if the output is disabled
     * @throws InvalidArgumentException if the timeout is negative
     */
    public function setIdleTimeout(?float $timeout): static
    {
        if (null !== $timeout && $this->outputDisabled) {
            throw new LogicException('Idle timeout cannot be set while the output is disabled.');
        }

        $this->idleTimeout = $this->validateTimeout($timeout);

        return $this;
    }

    /**
     * Enables or disables the TTY mode.
     *
     * @return $this
     *
     * @throws RuntimeException In case the TTY mode is not supported
     */
    public function setTty(bool $tty): static
    {
        if ('\\' === \DIRECTORY_SEPARATOR && $tty) {
            throw new RuntimeException('TTY mode is not supported on Windows platform.');
        }

        if ($tty && !self::isTtySupported()) {
            throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.');
        }

        $this->tty = $tty;

        return $this;
    }

    /**
     * Checks if the TTY mode is enabled.
     */
    public function isTty(): bool
    {
        return $this->tty;
    }

    /**
     * Sets PTY mode.
     *
     * @return $this
     */
    public function setPty(bool $bool): static
    {
        $this->pty = $bool;

        return $this;
    }

    /**
     * Returns PTY state.
     */
    public function isPty(): bool
    {
        return $this->pty;
    }

    /**
     * Gets the working directory.
     */
    public function getWorkingDirectory(): ?string
    {
        if (null === $this->cwd) {
            // getcwd() will return false if any one of the parent directories does not have
            // the readable or search mode set, even if the current directory does
            return getcwd() ?: null;
        }

        return $this->cwd;
    }

    /**
     * Sets the current working directory.
     *
     * @return $this
     */
    public function setWorkingDirectory(string $cwd): static
    {
        $this->cwd = $cwd;

        return $this;
    }

    /**
     * Gets the environment variables.
     */
    public function getEnv(): array
    {
        return $this->env;
    }

    /**
     * Sets the environment variables.
     *
     * @param array<string|\Stringable> $env The new environment variables
     *
     * @return $this
     */
    public function setEnv(array $env): static
    {
        $this->env = $env;

        return $this;
    }

    /**
     * Gets the Process input.
     *
     * @return resource|string|\Iterator|null
     */
    public function getInput()
    {
        return $this->input;
    }

    /**
     * Sets the input.
     *
     * This content will be passed to the underlying process standard input.
     *
     * @param string|resource|\Traversable|self|null $input The content
     *
     * @return $this
     *
     * @throws LogicException In case the process is running
     */
    public function setInput(mixed $input): static
    {
        if ($this->isRunning()) {
            throw new LogicException('Input cannot be set while the process is running.');
        }

        $this->input = ProcessUtils::validateInput(__METHOD__, $input);

        return $this;
    }

    /**
     * Performs a check between the timeout definition and the time the process started.
     *
     * In case you run a background process (with the start method), you should
     * trigger this method regularly to ensure the process timeout
     *
     * @return void
     *
     * @throws ProcessTimedOutException In case the timeout was reached
     */
    public function checkTimeout()
    {
        if (self::STATUS_STARTED !== $this->status) {
            return;
        }

        if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) {
            $this->stop(0);

            throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL);
        }

        if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) {
            $this->stop(0);

            throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE);
        }
    }

    /**
     * @throws LogicException in case process is not started
     */
    public function getStartTime(): float
    {
        if (!$this->isStarted()) {
            throw new LogicException('Start time is only available after process start.');
        }

        return $this->starttime;
    }

    /**
     * Defines options to pass to the underlying proc_open().
     *
     * @see https://php.net/proc_open for the options supported by PHP.
     *
     * Enabling the "create_new_console" option allows a subprocess to continue
     * to run after the main process exited, on both Windows and *nix
     *
     * @return void
     */
    public function setOptions(array $options)
    {
        if ($this->isRunning()) {
            throw new RuntimeException('Setting options while the process is running is not possible.');
        }

        $defaultOptions = $this->options;
        $existingOptions = ['blocking_pipes', 'create_process_group', 'create_new_console'];

        foreach ($options as $key => $value) {
            if (!\in_array($key, $existingOptions)) {
                $this->options = $defaultOptions;
                throw new LogicException(\sprintf('Invalid option "%s" passed to "%s()". Supported options are "%s".', $key, __METHOD__, implode('", "', $existingOptions)));
            }
            $this->options[$key] = $value;
        }
    }

    /**
     * Returns whether TTY is supported on the current operating system.
     */
    public static function isTtySupported(): bool
    {
        static $isTtySupported;

        return $isTtySupported ??= ('/' === \DIRECTORY_SEPARATOR && stream_isatty(\STDOUT) && @is_writable('/dev/tty'));
    }

    /**
     * Returns whether PTY is supported on the current operating system.
     */
    public static function isPtySupported(): bool
    {
        static $result;

        if (null !== $result) {
            return $result;
        }

        if ('\\' === \DIRECTORY_SEPARATOR) {
            return $result = false;
        }

        return $result = (bool) @proc_open('echo 1 >/dev/null', [['pty'], ['pty'], ['pty']], $pipes);
    }

    /**
     * Creates the descriptors needed by the proc_open.
     */
    private function getDescriptors(bool $hasCallback): array
    {
        if ($this->input instanceof \Iterator) {
            $this->input->rewind();
        }
        if ('\\' === \DIRECTORY_SEPARATOR) {
            $this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $hasCallback);
        } else {
            $this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $hasCallback);
        }

        return $this->processPipes->getDescriptors();
    }

    /**
     * Builds up the callback used by wait().
     *
     * The callbacks adds all occurred output to the specific buffer and calls
     * the user callback (if present) with the received output.
     *
     * @param callable|null $callback The user defined PHP callback
     */
    protected function buildCallback(?callable $callback = null): \Closure
    {
        if ($this->outputDisabled) {
            return fn ($type, $data): bool => null !== $callback && $callback($type, $data);
        }

        $out = self::OUT;

        return function ($type, $data) use ($callback, $out): bool {
            if ($out == $type) {
                $this->addOutput($data);
            } else {
                $this->addErrorOutput($data);
            }

            return null !== $callback && $callback($type, $data);
        };
    }

    /**
     * Updates the status of the process, reads pipes.
     *
     * @param bool $blocking Whether to use a blocking read call
     *
     * @return void
     */
    protected function updateStatus(bool $blocking)
    {
        if (self::STATUS_STARTED !== $this->status) {
            return;
        }

        if ($this->processInformation['running'] ?? true) {
            $this->processInformation = proc_get_status($this->process);
        }
        $running = $this->processInformation['running'];

        $this->readPipes($running && $blocking, '\\' !== \DIRECTORY_SEPARATOR || !$running);

        if ($this->fallbackStatus && $this->isSigchildEnabled()) {
            $this->processInformation = $this->fallbackStatus + $this->processInformation;
        }

        if (!$running) {
            $this->close();
        }
    }

    /**
     * Returns whether PHP has been compiled with the '--enable-sigchild' option or not.
     */
    protected function isSigchildEnabled(): bool
    {
        if (null !== self::$sigchild) {
            return self::$sigchild;
        }

        if (!\function_exists('phpinfo')) {
            return self::$sigchild = false;
        }

        ob_start();
        phpinfo(\INFO_GENERAL);

        return self::$sigchild = str_contains(ob_get_clean(), '--enable-sigchild');
    }

    /**
     * Reads pipes for the freshest output.
     *
     * @param string $caller   The name of the method that needs fresh outputs
     * @param bool   $blocking Whether to use blocking calls or not
     *
     * @throws LogicException in case output has been disabled or process is not started
     */
    private function readPipesForOutput(string $caller, bool $blocking = false): void
    {
        if ($this->outputDisabled) {
            throw new LogicException('Output has been disabled.');
        }

        $this->requireProcessIsStarted($caller);

        $this->updateStatus($blocking);
    }

    /**
     * Validates and returns the filtered timeout.
     *
     * @throws InvalidArgumentException if the given timeout is a negative number
     */
    private function validateTimeout(?float $timeout): ?float
    {
        $timeout = (float) $timeout;

        if (0.0 === $timeout) {
            $timeout = null;
        } elseif ($timeout < 0) {
            throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
        }

        return $timeout;
    }

    /**
     * Reads pipes, executes callback.
     *
     * @param bool $blocking Whether to use blocking calls or not
     * @param bool $close    Whether to close file handles or not
     */
    private function readPipes(bool $blocking, bool $close): void
    {
        $result = $this->processPipes->readAndWrite($blocking, $close);

        $callback = $this->callback;
        foreach ($result as $type => $data) {
            if (3 !== $type) {
                $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data);
            } elseif (!isset($this->fallbackStatus['signaled'])) {
                $this->fallbackStatus['exitcode'] = (int) $data;
            }
        }
    }

    /**
     * Closes process resource, closes file handles, sets the exitcode.
     *
     * @return int The exitcode
     */
    private function close(): int
    {
        $this->processPipes->close();
        if ($this->process) {
            proc_close($this->process);
            $this->process = null;
        }
        $this->exitcode = $this->processInformation['exitcode'];
        $this->status = self::STATUS_TERMINATED;

        if (-1 === $this->exitcode) {
            if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) {
                // if process has been signaled, no exitcode but a valid termsig, apply Unix convention
                $this->exitcode = 128 + $this->processInformation['termsig'];
            } elseif ($this->isSigchildEnabled()) {
                $this->processInformation['signaled'] = true;
                $this->processInformation['termsig'] = -1;
            }
        }

        // Free memory from self-reference callback created by buildCallback
        // Doing so in other contexts like __destruct or by garbage collector is ineffective
        // Now pipes are closed, so the callback is no longer necessary
        $this->callback = null;

        return $this->exitcode;
    }

    /**
     * Resets data related to the latest run of the process.
     */
    private function resetProcessData(): void
    {
        $this->starttime = null;
        $this->callback = null;
        $this->exitcode = null;
        $this->fallbackStatus = [];
        $this->processInformation = [];
        $this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+');
        $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+');
        $this->process = null;
        $this->latestSignal = null;
        $this->status = self::STATUS_READY;
        $this->incrementalOutputOffset = 0;
        $this->incrementalErrorOutputOffset = 0;
    }

    /**
     * Sends a POSIX signal to the process.
     *
     * @param int  $signal         A valid POSIX signal (see https://php.net/pcntl.constants)
     * @param bool $throwException Whether to throw exception in case signal failed
     *
     * @throws LogicException   In case the process is not running
     * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
     * @throws RuntimeException In case of failure
     */
    private function doSignal(int $signal, bool $throwException): bool
    {
        if (null === $pid = $this->getPid()) {
            if ($throwException) {
                throw new LogicException('Cannot send signal on a non running process.');
            }

            return false;
        }

        if ('\\' === \DIRECTORY_SEPARATOR) {
            exec(\sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode);
            if ($exitCode && $this->isRunning()) {
                if ($throwException) {
                    throw new RuntimeException(\sprintf('Unable to kill the process (%s).', implode(' ', $output)));
                }

                return false;
            }
        } else {
            if (!$this->isSigchildEnabled()) {
                $ok = @proc_terminate($this->process, $signal);
            } elseif (\function_exists('posix_kill')) {
                $ok = @posix_kill($pid, $signal);
            } elseif ($ok = proc_open(\sprintf('kill -%d %d', $signal, $pid), [2 => ['pipe', 'w']], $pipes)) {
                $ok = false === fgets($pipes[2]);
            }
            if (!$ok) {
                if ($throwException) {
                    throw new RuntimeException(\sprintf('Error while sending signal "%s".', $signal));
                }

                return false;
            }
        }

        $this->latestSignal = $signal;
        $this->fallbackStatus['signaled'] = true;
        $this->fallbackStatus['exitcode'] = -1;
        $this->fallbackStatus['termsig'] = $this->latestSignal;

        return true;
    }

    private function prepareWindowsCommandLine(string $cmd, array &$env): string
    {
        $uid = uniqid('', true);
        $cmd = preg_replace_callback(
            '/"(?:(
                [^"%!^]*+
                (?:
                    (?: !LF! | "(?:\^[%!^])?+" )
                    [^"%!^]*+
                )++
            ) | [^"]*+ )"/x',
            function ($m) use (&$env, $uid) {
                static $varCount = 0;
                static $varCache = [];
                if (!isset($m[1])) {
                    return $m[0];
                }
                if (isset($varCache[$m[0]])) {
                    return $varCache[$m[0]];
                }
                if (str_contains($value = $m[1], "\0")) {
                    $value = str_replace("\0", '?', $value);
                }
                if (false === strpbrk($value, "\"%!\n")) {
                    return '"'.$value.'"';
                }

                $value = str_replace(['!LF!', '"^!"', '"^%"', '"^^"', '""'], ["\n", '!', '%', '^', '"'], $value);
                $value = '"'.preg_replace('/(\\\\*)"/', '$1$1\\"', $value).'"';
                $var = $uid.++$varCount;

                $env[$var] = $value;

                return $varCache[$m[0]] = '!'.$var.'!';
            },
            $cmd
        );

        static $comSpec;

        if (!$comSpec && $comSpec = (new ExecutableFinder())->find('cmd.exe')) {
            // Escape according to CommandLineToArgvW rules
            $comSpec = '"'.preg_replace('{(\\\\*+)"}', '$1$1\"', $comSpec).'"';
        }

        $cmd = ($comSpec ?? 'cmd').' /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')';
        foreach ($this->processPipes->getFiles() as $offset => $filename) {
            $cmd .= ' '.$offset.'>"'.$filename.'"';
        }

        return $cmd;
    }

    /**
     * Ensures the process is running or terminated, throws a LogicException if the process has a not started.
     *
     * @throws LogicException if the process has not run
     */
    private function requireProcessIsStarted(string $functionName): void
    {
        if (!$this->isStarted()) {
            throw new LogicException(\sprintf('Process must be started before calling "%s()".', $functionName));
        }
    }

    /**
     * Ensures the process is terminated, throws a LogicException if the process has a status different than "terminated".
     *
     * @throws LogicException if the process is not yet terminated
     */
    private function requireProcessIsTerminated(string $functionName): void
    {
        if (!$this->isTerminated()) {
            throw new LogicException(\sprintf('Process must be terminated before calling "%s()".', $functionName));
        }
    }

    /**
     * Escapes a string to be used as a shell argument.
     */
    private function escapeArgument(?string $argument): string
    {
        if ('' === $argument || null === $argument) {
            return '""';
        }
        if ('\\' !== \DIRECTORY_SEPARATOR) {
            return "'".str_replace("'", "'\\''", $argument)."'";
        }
        if (str_contains($argument, "\0")) {
            $argument = str_replace("\0", '?', $argument);
        }
        if (!preg_match('/[()%!^"<>&|\s]/', $argument)) {
            return $argument;
        }
        $argument = preg_replace('/(\\\\+)$/', '$1$1', $argument);

        return '"'.str_replace(['"', '^', '%', '!', "\n"], ['""', '"^^"', '"^%"', '"^!"', '!LF!'], $argument).'"';
    }

    private function replacePlaceholders(string $commandline, array $env): string
    {
        return preg_replace_callback('/"\$\{:([_a-zA-Z]++[_a-zA-Z0-9]*+)\}"/', function ($matches) use ($commandline, $env) {
            if (!isset($env[$matches[1]]) || false === $env[$matches[1]]) {
                throw new InvalidArgumentException(\sprintf('Command line is missing a value for parameter "%s": ', $matches[1]).$commandline);
            }

            return $this->escapeArgument($env[$matches[1]]);
        }, $commandline);
    }

    private function getDefaultEnv(): array
    {
        $env = getenv();
        $env = ('\\' === \DIRECTORY_SEPARATOR ? array_intersect_ukey($env, $_SERVER, 'strcasecmp') : array_intersect_key($env, $_SERVER)) ?: $env;

        return $_ENV + ('\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($env, $_ENV, 'strcasecmp') : $env);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process;

use Symfony\Component\Process\Exception\LogicException;
use Symfony\Component\Process\Exception\RuntimeException;

/**
 * PhpSubprocess runs a PHP command as a subprocess while keeping the original php.ini settings.
 *
 * For this, it generates a temporary php.ini file taking over all the current settings and disables
 * loading additional .ini files. Basically, your command gets prefixed using "php -n -c /tmp/temp.ini".
 *
 * Given your php.ini contains "memory_limit=-1" and you have a "MemoryTest.php" with the following content:
 *
 *     <?php var_dump(ini_get('memory_limit'));
 *
 * These are the differences between the regular Process and PhpSubprocess classes:
 *
 *     $p = new Process(['php', '-d', 'memory_limit=256M', 'MemoryTest.php']);
 *     $p->run();
 *     print $p->getOutput()."\n";
 *
 * This will output "string(2) "-1", because the process is started with the default php.ini settings.
 *
 *     $p = new PhpSubprocess(['MemoryTest.php'], null, null, 60, ['php', '-d', 'memory_limit=256M']);
 *     $p->run();
 *     print $p->getOutput()."\n";
 *
 * This will output "string(4) "256M"", because the process is started with the temporarily created php.ini settings.
 *
 * @author Yanick Witschi <yanick.witschi@terminal42.ch>
 * @author Partially copied and heavily inspired from composer/xdebug-handler by John Stevenson <john-stevenson@blueyonder.co.uk>
 */
class PhpSubprocess extends Process
{
    /**
     * @param array       $command The command to run and its arguments listed as separate entries. They will automatically
     *                             get prefixed with the PHP binary
     * @param string|null $cwd     The working directory or null to use the working dir of the current PHP process
     * @param array|null  $env     The environment variables or null to use the same environment as the current PHP process
     * @param int         $timeout The timeout in seconds
     * @param array|null  $php     Path to the PHP binary to use with any additional arguments
     */
    public function __construct(array $command, ?string $cwd = null, ?array $env = null, int $timeout = 60, ?array $php = null)
    {
        if (null === $php) {
            $executableFinder = new PhpExecutableFinder();
            $php = $executableFinder->find(false);
            $php = false === $php ? null : array_merge([$php], $executableFinder->findArguments());
        }

        if (null === $php) {
            throw new RuntimeException('Unable to find PHP binary.');
        }

        $tmpIni = $this->writeTmpIni($this->getAllIniFiles(), sys_get_temp_dir());

        $php = array_merge($php, ['-n', '-c', $tmpIni]);
        register_shutdown_function('unlink', $tmpIni);

        $command = array_merge($php, $command);

        parent::__construct($command, $cwd, $env, null, $timeout);
    }

    public static function fromShellCommandline(string $command, ?string $cwd = null, ?array $env = null, mixed $input = null, ?float $timeout = 60): static
    {
        throw new LogicException(\sprintf('The "%s()" method cannot be called when using "%s".', __METHOD__, self::class));
    }

    public function start(?callable $callback = null, array $env = []): void
    {
        if (null === $this->getCommandLine()) {
            throw new RuntimeException('Unable to find the PHP executable.');
        }

        parent::start($callback, $env);
    }

    private function writeTmpIni(array $iniFiles, string $tmpDir): string
    {
        if (false === $tmpfile = @tempnam($tmpDir, '')) {
            throw new RuntimeException('Unable to create temporary ini file.');
        }

        // $iniFiles has at least one item and it may be empty
        if ('' === $iniFiles[0]) {
            array_shift($iniFiles);
        }

        $content = '';

        foreach ($iniFiles as $file) {
            // Check for inaccessible ini files
            if (($data = @file_get_contents($file)) === false) {
                throw new RuntimeException('Unable to read ini: '.$file);
            }
            // Check and remove directives after HOST and PATH sections
            if (preg_match('/^\s*\[(?:PATH|HOST)\s*=/mi', $data, $matches, \PREG_OFFSET_CAPTURE)) {
                $data = substr($data, 0, $matches[0][1]);
            }

            $content .= $data."\n";
        }

        // Merge loaded settings into our ini content, if it is valid
        $config = parse_ini_string($content);
        $loaded = ini_get_all(null, false);

        if (false === $config || false === $loaded) {
            throw new RuntimeException('Unable to parse ini data.');
        }

        $content .= $this->mergeLoadedConfig($loaded, $config);

        // Work-around for https://bugs.php.net/bug.php?id=75932
        $content .= "opcache.enable_cli=0\n";

        if (false === @file_put_contents($tmpfile, $content)) {
            throw new RuntimeException('Unable to write temporary ini file.');
        }

        return $tmpfile;
    }

    private function mergeLoadedConfig(array $loadedConfig, array $iniConfig): string
    {
        $content = '';

        foreach ($loadedConfig as $name => $value) {
            if (!\is_string($value)) {
                continue;
            }

            if (!isset($iniConfig[$name]) || $iniConfig[$name] !== $value) {
                // Double-quote escape each value
                $content .= $name.'="'.addcslashes($value, '\\"')."\"\n";
            }
        }

        return $content;
    }

    private function getAllIniFiles(): array
    {
        $paths = [(string) php_ini_loaded_file()];

        if (false !== $scanned = php_ini_scanned_files()) {
            $paths = array_merge($paths, array_map('trim', explode(',', $scanned)));
        }

        return $paths;
    }
}
<?php

return [
    'İ' => 'i̇',
    'µ' => 'μ',
    'ſ' => 's',
    'ͅ' => 'ι',
    'ς' => 'σ',
    'ϐ' => 'β',
    'ϑ' => 'θ',
    'ϕ' => 'φ',
    'ϖ' => 'π',
    'ϰ' => 'κ',
    'ϱ' => 'ρ',
    'ϵ' => 'ε',
    'ẛ' => 'ṡ',
    'ι' => 'ι',
    'ß' => 'ss',
    'ŉ' => 'ʼn',
    'ǰ' => 'ǰ',
    'ΐ' => 'ΐ',
    'ΰ' => 'ΰ',
    'և' => 'եւ',
    'ẖ' => 'ẖ',
    'ẗ' => 'ẗ',
    'ẘ' => 'ẘ',
    'ẙ' => 'ẙ',
    'ẚ' => 'aʾ',
    'ẞ' => 'ss',
    'ὐ' => 'ὐ',
    'ὒ' => 'ὒ',
    'ὔ' => 'ὔ',
    'ὖ' => 'ὖ',
    'ᾀ' => 'ἀι',
    'ᾁ' => 'ἁι',
    'ᾂ' => 'ἂι',
    'ᾃ' => 'ἃι',
    'ᾄ' => 'ἄι',
    'ᾅ' => 'ἅι',
    'ᾆ' => 'ἆι',
    'ᾇ' => 'ἇι',
    'ᾈ' => 'ἀι',
    'ᾉ' => 'ἁι',
    'ᾊ' => 'ἂι',
    'ᾋ' => 'ἃι',
    'ᾌ' => 'ἄι',
    'ᾍ' => 'ἅι',
    'ᾎ' => 'ἆι',
    'ᾏ' => 'ἇι',
    'ᾐ' => 'ἠι',
    'ᾑ' => 'ἡι',
    'ᾒ' => 'ἢι',
    'ᾓ' => 'ἣι',
    'ᾔ' => 'ἤι',
    'ᾕ' => 'ἥι',
    'ᾖ' => 'ἦι',
    'ᾗ' => 'ἧι',
    'ᾘ' => 'ἠι',
    'ᾙ' => 'ἡι',
    'ᾚ' => 'ἢι',
    'ᾛ' => 'ἣι',
    'ᾜ' => 'ἤι',
    'ᾝ' => 'ἥι',
    'ᾞ' => 'ἦι',
    'ᾟ' => 'ἧι',
    'ᾠ' => 'ὠι',
    'ᾡ' => 'ὡι',
    'ᾢ' => 'ὢι',
    'ᾣ' => 'ὣι',
    'ᾤ' => 'ὤι',
    'ᾥ' => 'ὥι',
    'ᾦ' => 'ὦι',
    'ᾧ' => 'ὧι',
    'ᾨ' => 'ὠι',
    'ᾩ' => 'ὡι',
    'ᾪ' => 'ὢι',
    'ᾫ' => 'ὣι',
    'ᾬ' => 'ὤι',
    'ᾭ' => 'ὥι',
    'ᾮ' => 'ὦι',
    'ᾯ' => 'ὧι',
    'ᾲ' => 'ὰι',
    'ᾳ' => 'αι',
    'ᾴ' => 'άι',
    'ᾶ' => 'ᾶ',
    'ᾷ' => 'ᾶι',
    'ᾼ' => 'αι',
    'ῂ' => 'ὴι',
    'ῃ' => 'ηι',
    'ῄ' => 'ήι',
    'ῆ' => 'ῆ',
    'ῇ' => 'ῆι',
    'ῌ' => 'ηι',
    'ῒ' => 'ῒ',
    'ῖ' => 'ῖ',
    'ῗ' => 'ῗ',
    'ῢ' => 'ῢ',
    'ῤ' => 'ῤ',
    'ῦ' => 'ῦ',
    'ῧ' => 'ῧ',
    'ῲ' => 'ὼι',
    'ῳ' => 'ωι',
    'ῴ' => 'ώι',
    'ῶ' => 'ῶ',
    'ῷ' => 'ῶι',
    'ῼ' => 'ωι',
    'ﬀ' => 'ff',
    'ﬁ' => 'fi',
    'ﬂ' => 'fl',
    'ﬃ' => 'ffi',
    'ﬄ' => 'ffl',
    'ﬅ' => 'st',
    'ﬆ' => 'st',
    'ﬓ' => 'մն',
    'ﬔ' => 'մե',
    'ﬕ' => 'մի',
    'ﬖ' => 'վն',
    'ﬗ' => 'մխ',
];
<?php

return array (
  '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',
  'À' => 'à',
  'Á' => 'á',
  'Â' => 'â',
  'Ã' => 'ã',
  'Ä' => 'ä',
  'Å' => 'å',
  'Æ' => 'æ',
  'Ç' => 'ç',
  'È' => 'è',
  'É' => 'é',
  'Ê' => 'ê',
  'Ë' => 'ë',
  'Ì' => 'ì',
  'Í' => 'í',
  'Î' => 'î',
  'Ï' => 'ï',
  'Ð' => 'ð',
  'Ñ' => 'ñ',
  'Ò' => 'ò',
  'Ó' => 'ó',
  'Ô' => 'ô',
  'Õ' => 'õ',
  'Ö' => 'ö',
  'Ø' => 'ø',
  'Ù' => 'ù',
  'Ú' => 'ú',
  'Û' => 'û',
  'Ü' => 'ü',
  'Ý' => 'ý',
  'Þ' => 'þ',
  'Ā' => 'ā',
  'Ă' => 'ă',
  'Ą' => 'ą',
  'Ć' => 'ć',
  'Ĉ' => 'ĉ',
  'Ċ' => 'ċ',
  'Č' => 'č',
  'Ď' => 'ď',
  'Đ' => 'đ',
  'Ē' => 'ē',
  'Ĕ' => 'ĕ',
  'Ė' => 'ė',
  'Ę' => 'ę',
  'Ě' => 'ě',
  'Ĝ' => 'ĝ',
  'Ğ' => 'ğ',
  'Ġ' => 'ġ',
  'Ģ' => 'ģ',
  'Ĥ' => 'ĥ',
  'Ħ' => 'ħ',
  'Ĩ' => 'ĩ',
  'Ī' => 'ī',
  'Ĭ' => 'ĭ',
  'Į' => 'į',
  'İ' => 'i̇',
  'Ĳ' => 'ĳ',
  'Ĵ' => 'ĵ',
  'Ķ' => 'ķ',
  'Ĺ' => 'ĺ',
  'Ļ' => 'ļ',
  'Ľ' => 'ľ',
  'Ŀ' => 'ŀ',
  'Ł' => 'ł',
  'Ń' => 'ń',
  'Ņ' => 'ņ',
  'Ň' => 'ň',
  'Ŋ' => 'ŋ',
  'Ō' => 'ō',
  'Ŏ' => 'ŏ',
  'Ő' => 'ő',
  'Œ' => 'œ',
  'Ŕ' => 'ŕ',
  'Ŗ' => 'ŗ',
  'Ř' => 'ř',
  'Ś' => 'ś',
  'Ŝ' => 'ŝ',
  'Ş' => 'ş',
  'Š' => 'š',
  'Ţ' => 'ţ',
  'Ť' => 'ť',
  'Ŧ' => 'ŧ',
  'Ũ' => 'ũ',
  'Ū' => 'ū',
  'Ŭ' => 'ŭ',
  'Ů' => 'ů',
  'Ű' => 'ű',
  'Ų' => 'ų',
  'Ŵ' => 'ŵ',
  'Ŷ' => 'ŷ',
  'Ÿ' => 'ÿ',
  'Ź' => 'ź',
  'Ż' => 'ż',
  'Ž' => 'ž',
  'Ɓ' => 'ɓ',
  'Ƃ' => 'ƃ',
  'Ƅ' => 'ƅ',
  'Ɔ' => 'ɔ',
  'Ƈ' => 'ƈ',
  'Ɖ' => 'ɖ',
  'Ɗ' => 'ɗ',
  'Ƌ' => 'ƌ',
  'Ǝ' => 'ǝ',
  'Ə' => 'ə',
  'Ɛ' => 'ɛ',
  'Ƒ' => 'ƒ',
  'Ɠ' => 'ɠ',
  'Ɣ' => 'ɣ',
  'Ɩ' => 'ɩ',
  'Ɨ' => 'ɨ',
  'Ƙ' => 'ƙ',
  'Ɯ' => 'ɯ',
  'Ɲ' => 'ɲ',
  'Ɵ' => 'ɵ',
  'Ơ' => 'ơ',
  'Ƣ' => 'ƣ',
  'Ƥ' => 'ƥ',
  'Ʀ' => 'ʀ',
  'Ƨ' => 'ƨ',
  'Ʃ' => 'ʃ',
  'Ƭ' => 'ƭ',
  'Ʈ' => 'ʈ',
  'Ư' => 'ư',
  'Ʊ' => 'ʊ',
  'Ʋ' => 'ʋ',
  'Ƴ' => 'ƴ',
  'Ƶ' => 'ƶ',
  'Ʒ' => 'ʒ',
  'Ƹ' => 'ƹ',
  'Ƽ' => 'ƽ',
  'Ǆ' => 'ǆ',
  'ǅ' => 'ǆ',
  'Ǉ' => 'ǉ',
  'ǈ' => 'ǉ',
  'Ǌ' => 'ǌ',
  'ǋ' => 'ǌ',
  'Ǎ' => 'ǎ',
  'Ǐ' => 'ǐ',
  'Ǒ' => 'ǒ',
  'Ǔ' => 'ǔ',
  'Ǖ' => 'ǖ',
  'Ǘ' => 'ǘ',
  'Ǚ' => 'ǚ',
  'Ǜ' => 'ǜ',
  'Ǟ' => 'ǟ',
  'Ǡ' => 'ǡ',
  'Ǣ' => 'ǣ',
  'Ǥ' => 'ǥ',
  'Ǧ' => 'ǧ',
  'Ǩ' => 'ǩ',
  'Ǫ' => 'ǫ',
  'Ǭ' => 'ǭ',
  'Ǯ' => 'ǯ',
  'Ǳ' => 'ǳ',
  'ǲ' => 'ǳ',
  'Ǵ' => 'ǵ',
  'Ƕ' => 'ƕ',
  'Ƿ' => 'ƿ',
  'Ǹ' => 'ǹ',
  'Ǻ' => 'ǻ',
  'Ǽ' => 'ǽ',
  'Ǿ' => 'ǿ',
  'Ȁ' => 'ȁ',
  'Ȃ' => 'ȃ',
  'Ȅ' => 'ȅ',
  'Ȇ' => 'ȇ',
  'Ȉ' => 'ȉ',
  'Ȋ' => 'ȋ',
  'Ȍ' => 'ȍ',
  'Ȏ' => 'ȏ',
  'Ȑ' => 'ȑ',
  'Ȓ' => 'ȓ',
  'Ȕ' => 'ȕ',
  'Ȗ' => 'ȗ',
  'Ș' => 'ș',
  'Ț' => 'ț',
  'Ȝ' => 'ȝ',
  'Ȟ' => 'ȟ',
  'Ƞ' => 'ƞ',
  'Ȣ' => 'ȣ',
  'Ȥ' => 'ȥ',
  'Ȧ' => 'ȧ',
  'Ȩ' => 'ȩ',
  'Ȫ' => 'ȫ',
  'Ȭ' => 'ȭ',
  'Ȯ' => 'ȯ',
  'Ȱ' => 'ȱ',
  'Ȳ' => 'ȳ',
  'Ⱥ' => 'ⱥ',
  'Ȼ' => 'ȼ',
  'Ƚ' => 'ƚ',
  'Ⱦ' => 'ⱦ',
  'Ɂ' => 'ɂ',
  'Ƀ' => 'ƀ',
  'Ʉ' => 'ʉ',
  'Ʌ' => 'ʌ',
  'Ɇ' => 'ɇ',
  'Ɉ' => 'ɉ',
  'Ɋ' => 'ɋ',
  'Ɍ' => 'ɍ',
  'Ɏ' => 'ɏ',
  'Ͱ' => 'ͱ',
  'Ͳ' => 'ͳ',
  'Ͷ' => 'ͷ',
  'Ϳ' => 'ϳ',
  'Ά' => 'ά',
  'Έ' => 'έ',
  'Ή' => 'ή',
  'Ί' => 'ί',
  'Ό' => 'ό',
  'Ύ' => 'ύ',
  'Ώ' => 'ώ',
  'Α' => 'α',
  'Β' => 'β',
  'Γ' => 'γ',
  'Δ' => 'δ',
  'Ε' => 'ε',
  'Ζ' => 'ζ',
  'Η' => 'η',
  'Θ' => 'θ',
  'Ι' => 'ι',
  'Κ' => 'κ',
  'Λ' => 'λ',
  'Μ' => 'μ',
  'Ν' => 'ν',
  'Ξ' => 'ξ',
  'Ο' => 'ο',
  'Π' => 'π',
  'Ρ' => 'ρ',
  'Σ' => 'σ',
  'Τ' => 'τ',
  'Υ' => 'υ',
  'Φ' => 'φ',
  'Χ' => 'χ',
  'Ψ' => 'ψ',
  'Ω' => 'ω',
  'Ϊ' => 'ϊ',
  'Ϋ' => 'ϋ',
  'Ϗ' => 'ϗ',
  'Ϙ' => 'ϙ',
  'Ϛ' => 'ϛ',
  'Ϝ' => 'ϝ',
  'Ϟ' => 'ϟ',
  'Ϡ' => 'ϡ',
  'Ϣ' => 'ϣ',
  'Ϥ' => 'ϥ',
  'Ϧ' => 'ϧ',
  'Ϩ' => 'ϩ',
  'Ϫ' => 'ϫ',
  'Ϭ' => 'ϭ',
  'Ϯ' => 'ϯ',
  'ϴ' => 'θ',
  'Ϸ' => 'ϸ',
  'Ϲ' => 'ϲ',
  'Ϻ' => 'ϻ',
  'Ͻ' => 'ͻ',
  'Ͼ' => 'ͼ',
  'Ͽ' => 'ͽ',
  'Ѐ' => 'ѐ',
  'Ё' => 'ё',
  'Ђ' => 'ђ',
  'Ѓ' => 'ѓ',
  'Є' => 'є',
  'Ѕ' => 'ѕ',
  'І' => 'і',
  'Ї' => 'ї',
  'Ј' => 'ј',
  'Љ' => 'љ',
  'Њ' => 'њ',
  'Ћ' => 'ћ',
  'Ќ' => 'ќ',
  'Ѝ' => 'ѝ',
  'Ў' => 'ў',
  'Џ' => 'џ',
  'А' => 'а',
  'Б' => 'б',
  'В' => 'в',
  'Г' => 'г',
  'Д' => 'д',
  'Е' => 'е',
  'Ж' => 'ж',
  'З' => 'з',
  'И' => 'и',
  'Й' => 'й',
  'К' => 'к',
  'Л' => 'л',
  'М' => 'м',
  'Н' => 'н',
  'О' => 'о',
  'П' => 'п',
  'Р' => 'р',
  'С' => 'с',
  'Т' => 'т',
  'У' => 'у',
  'Ф' => 'ф',
  'Х' => 'х',
  'Ц' => 'ц',
  'Ч' => 'ч',
  'Ш' => 'ш',
  'Щ' => 'щ',
  'Ъ' => 'ъ',
  'Ы' => 'ы',
  'Ь' => 'ь',
  'Э' => 'э',
  'Ю' => 'ю',
  'Я' => 'я',
  'Ѡ' => 'ѡ',
  'Ѣ' => 'ѣ',
  'Ѥ' => 'ѥ',
  'Ѧ' => 'ѧ',
  'Ѩ' => 'ѩ',
  'Ѫ' => 'ѫ',
  'Ѭ' => 'ѭ',
  'Ѯ' => 'ѯ',
  'Ѱ' => 'ѱ',
  'Ѳ' => 'ѳ',
  'Ѵ' => 'ѵ',
  'Ѷ' => 'ѷ',
  'Ѹ' => 'ѹ',
  'Ѻ' => 'ѻ',
  'Ѽ' => 'ѽ',
  'Ѿ' => 'ѿ',
  'Ҁ' => 'ҁ',
  'Ҋ' => 'ҋ',
  'Ҍ' => 'ҍ',
  'Ҏ' => 'ҏ',
  'Ґ' => 'ґ',
  'Ғ' => 'ғ',
  'Ҕ' => 'ҕ',
  'Җ' => 'җ',
  'Ҙ' => 'ҙ',
  'Қ' => 'қ',
  'Ҝ' => 'ҝ',
  'Ҟ' => 'ҟ',
  'Ҡ' => 'ҡ',
  'Ң' => 'ң',
  'Ҥ' => 'ҥ',
  'Ҧ' => 'ҧ',
  'Ҩ' => 'ҩ',
  'Ҫ' => 'ҫ',
  'Ҭ' => 'ҭ',
  'Ү' => 'ү',
  'Ұ' => 'ұ',
  'Ҳ' => 'ҳ',
  'Ҵ' => 'ҵ',
  'Ҷ' => 'ҷ',
  'Ҹ' => 'ҹ',
  'Һ' => 'һ',
  'Ҽ' => 'ҽ',
  'Ҿ' => 'ҿ',
  'Ӏ' => 'ӏ',
  'Ӂ' => 'ӂ',
  'Ӄ' => 'ӄ',
  'Ӆ' => 'ӆ',
  'Ӈ' => 'ӈ',
  'Ӊ' => 'ӊ',
  'Ӌ' => 'ӌ',
  'Ӎ' => 'ӎ',
  'Ӑ' => 'ӑ',
  'Ӓ' => 'ӓ',
  'Ӕ' => 'ӕ',
  'Ӗ' => 'ӗ',
  'Ә' => 'ә',
  'Ӛ' => 'ӛ',
  'Ӝ' => 'ӝ',
  'Ӟ' => 'ӟ',
  'Ӡ' => 'ӡ',
  'Ӣ' => 'ӣ',
  'Ӥ' => 'ӥ',
  'Ӧ' => 'ӧ',
  'Ө' => 'ө',
  'Ӫ' => 'ӫ',
  'Ӭ' => 'ӭ',
  'Ӯ' => 'ӯ',
  'Ӱ' => 'ӱ',
  'Ӳ' => 'ӳ',
  'Ӵ' => 'ӵ',
  'Ӷ' => 'ӷ',
  'Ӹ' => 'ӹ',
  'Ӻ' => 'ӻ',
  'Ӽ' => 'ӽ',
  'Ӿ' => 'ӿ',
  'Ԁ' => 'ԁ',
  'Ԃ' => 'ԃ',
  'Ԅ' => 'ԅ',
  'Ԇ' => 'ԇ',
  'Ԉ' => 'ԉ',
  'Ԋ' => 'ԋ',
  'Ԍ' => 'ԍ',
  'Ԏ' => 'ԏ',
  'Ԑ' => 'ԑ',
  'Ԓ' => 'ԓ',
  'Ԕ' => 'ԕ',
  'Ԗ' => 'ԗ',
  'Ԙ' => 'ԙ',
  'Ԛ' => 'ԛ',
  'Ԝ' => 'ԝ',
  'Ԟ' => 'ԟ',
  'Ԡ' => 'ԡ',
  'Ԣ' => 'ԣ',
  'Ԥ' => 'ԥ',
  'Ԧ' => 'ԧ',
  'Ԩ' => 'ԩ',
  'Ԫ' => 'ԫ',
  'Ԭ' => 'ԭ',
  'Ԯ' => 'ԯ',
  'Ա' => 'ա',
  'Բ' => 'բ',
  'Գ' => 'գ',
  'Դ' => 'դ',
  'Ե' => 'ե',
  'Զ' => 'զ',
  'Է' => 'է',
  'Ը' => 'ը',
  'Թ' => 'թ',
  'Ժ' => 'ժ',
  'Ի' => 'ի',
  'Լ' => 'լ',
  'Խ' => 'խ',
  'Ծ' => 'ծ',
  'Կ' => 'կ',
  'Հ' => 'հ',
  'Ձ' => 'ձ',
  'Ղ' => 'ղ',
  'Ճ' => 'ճ',
  'Մ' => 'մ',
  'Յ' => 'յ',
  'Ն' => 'ն',
  'Շ' => 'շ',
  'Ո' => 'ո',
  'Չ' => 'չ',
  'Պ' => 'պ',
  'Ջ' => 'ջ',
  'Ռ' => 'ռ',
  'Ս' => 'ս',
  'Վ' => 'վ',
  'Տ' => 'տ',
  'Ր' => 'ր',
  'Ց' => 'ց',
  'Ւ' => 'ւ',
  'Փ' => 'փ',
  'Ք' => 'ք',
  'Օ' => 'օ',
  'Ֆ' => 'ֆ',
  'Ⴀ' => 'ⴀ',
  'Ⴁ' => 'ⴁ',
  'Ⴂ' => 'ⴂ',
  'Ⴃ' => 'ⴃ',
  'Ⴄ' => 'ⴄ',
  'Ⴅ' => 'ⴅ',
  'Ⴆ' => 'ⴆ',
  'Ⴇ' => 'ⴇ',
  'Ⴈ' => 'ⴈ',
  'Ⴉ' => 'ⴉ',
  'Ⴊ' => 'ⴊ',
  'Ⴋ' => 'ⴋ',
  'Ⴌ' => 'ⴌ',
  'Ⴍ' => 'ⴍ',
  'Ⴎ' => 'ⴎ',
  'Ⴏ' => 'ⴏ',
  'Ⴐ' => 'ⴐ',
  'Ⴑ' => 'ⴑ',
  'Ⴒ' => 'ⴒ',
  'Ⴓ' => 'ⴓ',
  'Ⴔ' => 'ⴔ',
  'Ⴕ' => 'ⴕ',
  'Ⴖ' => 'ⴖ',
  'Ⴗ' => 'ⴗ',
  'Ⴘ' => 'ⴘ',
  'Ⴙ' => 'ⴙ',
  'Ⴚ' => 'ⴚ',
  'Ⴛ' => 'ⴛ',
  'Ⴜ' => 'ⴜ',
  'Ⴝ' => 'ⴝ',
  'Ⴞ' => 'ⴞ',
  'Ⴟ' => 'ⴟ',
  'Ⴠ' => 'ⴠ',
  'Ⴡ' => 'ⴡ',
  'Ⴢ' => 'ⴢ',
  'Ⴣ' => 'ⴣ',
  'Ⴤ' => 'ⴤ',
  'Ⴥ' => 'ⴥ',
  'Ⴧ' => 'ⴧ',
  'Ⴭ' => 'ⴭ',
  'Ꭰ' => 'ꭰ',
  'Ꭱ' => 'ꭱ',
  'Ꭲ' => 'ꭲ',
  'Ꭳ' => 'ꭳ',
  'Ꭴ' => 'ꭴ',
  'Ꭵ' => 'ꭵ',
  'Ꭶ' => 'ꭶ',
  'Ꭷ' => 'ꭷ',
  'Ꭸ' => 'ꭸ',
  'Ꭹ' => 'ꭹ',
  'Ꭺ' => 'ꭺ',
  'Ꭻ' => 'ꭻ',
  'Ꭼ' => 'ꭼ',
  'Ꭽ' => 'ꭽ',
  'Ꭾ' => 'ꭾ',
  'Ꭿ' => 'ꭿ',
  'Ꮀ' => 'ꮀ',
  'Ꮁ' => 'ꮁ',
  'Ꮂ' => 'ꮂ',
  'Ꮃ' => 'ꮃ',
  'Ꮄ' => 'ꮄ',
  'Ꮅ' => 'ꮅ',
  'Ꮆ' => 'ꮆ',
  'Ꮇ' => 'ꮇ',
  'Ꮈ' => 'ꮈ',
  'Ꮉ' => 'ꮉ',
  'Ꮊ' => 'ꮊ',
  'Ꮋ' => 'ꮋ',
  'Ꮌ' => 'ꮌ',
  'Ꮍ' => 'ꮍ',
  'Ꮎ' => 'ꮎ',
  'Ꮏ' => 'ꮏ',
  'Ꮐ' => 'ꮐ',
  'Ꮑ' => 'ꮑ',
  'Ꮒ' => 'ꮒ',
  'Ꮓ' => 'ꮓ',
  'Ꮔ' => 'ꮔ',
  'Ꮕ' => 'ꮕ',
  'Ꮖ' => 'ꮖ',
  'Ꮗ' => 'ꮗ',
  'Ꮘ' => 'ꮘ',
  'Ꮙ' => 'ꮙ',
  'Ꮚ' => 'ꮚ',
  'Ꮛ' => 'ꮛ',
  'Ꮜ' => 'ꮜ',
  'Ꮝ' => 'ꮝ',
  'Ꮞ' => 'ꮞ',
  'Ꮟ' => 'ꮟ',
  'Ꮠ' => 'ꮠ',
  'Ꮡ' => 'ꮡ',
  'Ꮢ' => 'ꮢ',
  'Ꮣ' => 'ꮣ',
  'Ꮤ' => 'ꮤ',
  'Ꮥ' => 'ꮥ',
  'Ꮦ' => 'ꮦ',
  'Ꮧ' => 'ꮧ',
  'Ꮨ' => 'ꮨ',
  'Ꮩ' => 'ꮩ',
  'Ꮪ' => 'ꮪ',
  'Ꮫ' => 'ꮫ',
  'Ꮬ' => 'ꮬ',
  'Ꮭ' => 'ꮭ',
  'Ꮮ' => 'ꮮ',
  'Ꮯ' => 'ꮯ',
  'Ꮰ' => 'ꮰ',
  'Ꮱ' => 'ꮱ',
  'Ꮲ' => 'ꮲ',
  'Ꮳ' => 'ꮳ',
  'Ꮴ' => 'ꮴ',
  'Ꮵ' => 'ꮵ',
  'Ꮶ' => 'ꮶ',
  'Ꮷ' => 'ꮷ',
  'Ꮸ' => 'ꮸ',
  'Ꮹ' => 'ꮹ',
  'Ꮺ' => 'ꮺ',
  'Ꮻ' => 'ꮻ',
  'Ꮼ' => 'ꮼ',
  'Ꮽ' => 'ꮽ',
  'Ꮾ' => 'ꮾ',
  'Ꮿ' => 'ꮿ',
  'Ᏸ' => 'ᏸ',
  'Ᏹ' => 'ᏹ',
  'Ᏺ' => 'ᏺ',
  'Ᏻ' => 'ᏻ',
  'Ᏼ' => 'ᏼ',
  'Ᏽ' => 'ᏽ',
  'Ა' => 'ა',
  'Ბ' => 'ბ',
  'Გ' => 'გ',
  'Დ' => 'დ',
  'Ე' => 'ე',
  'Ვ' => 'ვ',
  'Ზ' => 'ზ',
  'Თ' => 'თ',
  'Ი' => 'ი',
  'Კ' => 'კ',
  'Ლ' => 'ლ',
  'Მ' => 'მ',
  'Ნ' => 'ნ',
  'Ო' => 'ო',
  'Პ' => 'პ',
  'Ჟ' => 'ჟ',
  'Რ' => 'რ',
  'Ს' => 'ს',
  'Ტ' => 'ტ',
  'Უ' => 'უ',
  'Ფ' => 'ფ',
  'Ქ' => 'ქ',
  'Ღ' => 'ღ',
  'Ყ' => 'ყ',
  'Შ' => 'შ',
  'Ჩ' => 'ჩ',
  'Ც' => 'ც',
  'Ძ' => 'ძ',
  'Წ' => 'წ',
  'Ჭ' => 'ჭ',
  'Ხ' => 'ხ',
  'Ჯ' => 'ჯ',
  'Ჰ' => 'ჰ',
  'Ჱ' => 'ჱ',
  'Ჲ' => 'ჲ',
  'Ჳ' => 'ჳ',
  'Ჴ' => 'ჴ',
  'Ჵ' => 'ჵ',
  'Ჶ' => 'ჶ',
  'Ჷ' => 'ჷ',
  'Ჸ' => 'ჸ',
  'Ჹ' => 'ჹ',
  'Ჺ' => 'ჺ',
  'Ჽ' => 'ჽ',
  'Ჾ' => 'ჾ',
  'Ჿ' => 'ჿ',
  'Ḁ' => 'ḁ',
  'Ḃ' => 'ḃ',
  'Ḅ' => 'ḅ',
  'Ḇ' => 'ḇ',
  'Ḉ' => 'ḉ',
  'Ḋ' => 'ḋ',
  'Ḍ' => 'ḍ',
  'Ḏ' => 'ḏ',
  'Ḑ' => 'ḑ',
  'Ḓ' => 'ḓ',
  'Ḕ' => 'ḕ',
  'Ḗ' => 'ḗ',
  'Ḙ' => 'ḙ',
  'Ḛ' => 'ḛ',
  'Ḝ' => 'ḝ',
  'Ḟ' => 'ḟ',
  'Ḡ' => 'ḡ',
  'Ḣ' => 'ḣ',
  'Ḥ' => 'ḥ',
  'Ḧ' => 'ḧ',
  'Ḩ' => 'ḩ',
  'Ḫ' => 'ḫ',
  'Ḭ' => 'ḭ',
  'Ḯ' => 'ḯ',
  'Ḱ' => 'ḱ',
  'Ḳ' => 'ḳ',
  'Ḵ' => 'ḵ',
  'Ḷ' => 'ḷ',
  'Ḹ' => 'ḹ',
  'Ḻ' => 'ḻ',
  'Ḽ' => 'ḽ',
  'Ḿ' => 'ḿ',
  'Ṁ' => 'ṁ',
  'Ṃ' => 'ṃ',
  'Ṅ' => 'ṅ',
  'Ṇ' => 'ṇ',
  'Ṉ' => 'ṉ',
  'Ṋ' => 'ṋ',
  'Ṍ' => 'ṍ',
  'Ṏ' => 'ṏ',
  'Ṑ' => 'ṑ',
  'Ṓ' => 'ṓ',
  'Ṕ' => 'ṕ',
  'Ṗ' => 'ṗ',
  'Ṙ' => 'ṙ',
  'Ṛ' => 'ṛ',
  'Ṝ' => 'ṝ',
  'Ṟ' => 'ṟ',
  'Ṡ' => 'ṡ',
  'Ṣ' => 'ṣ',
  'Ṥ' => 'ṥ',
  'Ṧ' => 'ṧ',
  'Ṩ' => 'ṩ',
  'Ṫ' => 'ṫ',
  'Ṭ' => 'ṭ',
  'Ṯ' => 'ṯ',
  'Ṱ' => 'ṱ',
  'Ṳ' => 'ṳ',
  'Ṵ' => 'ṵ',
  'Ṷ' => 'ṷ',
  'Ṹ' => 'ṹ',
  'Ṻ' => 'ṻ',
  'Ṽ' => 'ṽ',
  'Ṿ' => 'ṿ',
  'Ẁ' => 'ẁ',
  'Ẃ' => 'ẃ',
  'Ẅ' => 'ẅ',
  'Ẇ' => 'ẇ',
  'Ẉ' => 'ẉ',
  'Ẋ' => 'ẋ',
  'Ẍ' => 'ẍ',
  'Ẏ' => 'ẏ',
  'Ẑ' => 'ẑ',
  'Ẓ' => 'ẓ',
  'Ẕ' => 'ẕ',
  'ẞ' => 'ß',
  'Ạ' => 'ạ',
  'Ả' => 'ả',
  'Ấ' => 'ấ',
  'Ầ' => 'ầ',
  'Ẩ' => 'ẩ',
  'Ẫ' => 'ẫ',
  'Ậ' => 'ậ',
  'Ắ' => 'ắ',
  'Ằ' => 'ằ',
  'Ẳ' => 'ẳ',
  'Ẵ' => 'ẵ',
  'Ặ' => 'ặ',
  'Ẹ' => 'ẹ',
  'Ẻ' => 'ẻ',
  'Ẽ' => 'ẽ',
  'Ế' => 'ế',
  'Ề' => 'ề',
  'Ể' => 'ể',
  'Ễ' => 'ễ',
  'Ệ' => 'ệ',
  'Ỉ' => 'ỉ',
  'Ị' => 'ị',
  'Ọ' => 'ọ',
  'Ỏ' => 'ỏ',
  'Ố' => 'ố',
  'Ồ' => 'ồ',
  'Ổ' => 'ổ',
  'Ỗ' => 'ỗ',
  'Ộ' => 'ộ',
  'Ớ' => 'ớ',
  'Ờ' => 'ờ',
  'Ở' => 'ở',
  'Ỡ' => 'ỡ',
  'Ợ' => 'ợ',
  'Ụ' => 'ụ',
  'Ủ' => 'ủ',
  'Ứ' => 'ứ',
  'Ừ' => 'ừ',
  'Ử' => 'ử',
  'Ữ' => 'ữ',
  'Ự' => 'ự',
  'Ỳ' => 'ỳ',
  'Ỵ' => 'ỵ',
  'Ỷ' => 'ỷ',
  'Ỹ' => 'ỹ',
  'Ỻ' => 'ỻ',
  'Ỽ' => 'ỽ',
  'Ỿ' => 'ỿ',
  'Ἀ' => 'ἀ',
  'Ἁ' => 'ἁ',
  'Ἂ' => 'ἂ',
  'Ἃ' => 'ἃ',
  'Ἄ' => 'ἄ',
  'Ἅ' => 'ἅ',
  'Ἆ' => 'ἆ',
  'Ἇ' => 'ἇ',
  'Ἐ' => 'ἐ',
  'Ἑ' => 'ἑ',
  'Ἒ' => 'ἒ',
  'Ἓ' => 'ἓ',
  'Ἔ' => 'ἔ',
  'Ἕ' => 'ἕ',
  'Ἠ' => 'ἠ',
  'Ἡ' => 'ἡ',
  'Ἢ' => 'ἢ',
  'Ἣ' => 'ἣ',
  'Ἤ' => 'ἤ',
  'Ἥ' => 'ἥ',
  'Ἦ' => 'ἦ',
  'Ἧ' => 'ἧ',
  'Ἰ' => 'ἰ',
  'Ἱ' => 'ἱ',
  'Ἲ' => 'ἲ',
  'Ἳ' => 'ἳ',
  'Ἴ' => 'ἴ',
  'Ἵ' => 'ἵ',
  'Ἶ' => 'ἶ',
  'Ἷ' => 'ἷ',
  'Ὀ' => 'ὀ',
  'Ὁ' => 'ὁ',
  'Ὂ' => 'ὂ',
  'Ὃ' => 'ὃ',
  'Ὄ' => 'ὄ',
  'Ὅ' => 'ὅ',
  'Ὑ' => 'ὑ',
  'Ὓ' => 'ὓ',
  'Ὕ' => 'ὕ',
  'Ὗ' => 'ὗ',
  'Ὠ' => 'ὠ',
  'Ὡ' => 'ὡ',
  'Ὢ' => 'ὢ',
  'Ὣ' => 'ὣ',
  'Ὤ' => 'ὤ',
  'Ὥ' => 'ὥ',
  'Ὦ' => 'ὦ',
  'Ὧ' => 'ὧ',
  'ᾈ' => 'ᾀ',
  'ᾉ' => 'ᾁ',
  'ᾊ' => 'ᾂ',
  'ᾋ' => 'ᾃ',
  'ᾌ' => 'ᾄ',
  'ᾍ' => 'ᾅ',
  'ᾎ' => 'ᾆ',
  'ᾏ' => 'ᾇ',
  'ᾘ' => 'ᾐ',
  'ᾙ' => 'ᾑ',
  'ᾚ' => 'ᾒ',
  'ᾛ' => 'ᾓ',
  'ᾜ' => 'ᾔ',
  'ᾝ' => 'ᾕ',
  'ᾞ' => 'ᾖ',
  'ᾟ' => 'ᾗ',
  'ᾨ' => 'ᾠ',
  'ᾩ' => 'ᾡ',
  'ᾪ' => 'ᾢ',
  'ᾫ' => 'ᾣ',
  'ᾬ' => 'ᾤ',
  'ᾭ' => 'ᾥ',
  'ᾮ' => 'ᾦ',
  'ᾯ' => 'ᾧ',
  'Ᾰ' => 'ᾰ',
  'Ᾱ' => 'ᾱ',
  'Ὰ' => 'ὰ',
  'Ά' => 'ά',
  'ᾼ' => 'ᾳ',
  'Ὲ' => 'ὲ',
  'Έ' => 'έ',
  'Ὴ' => 'ὴ',
  'Ή' => 'ή',
  'ῌ' => 'ῃ',
  'Ῐ' => 'ῐ',
  'Ῑ' => 'ῑ',
  'Ὶ' => 'ὶ',
  'Ί' => 'ί',
  'Ῠ' => 'ῠ',
  'Ῡ' => 'ῡ',
  'Ὺ' => 'ὺ',
  'Ύ' => 'ύ',
  'Ῥ' => 'ῥ',
  'Ὸ' => 'ὸ',
  'Ό' => 'ό',
  'Ὼ' => 'ὼ',
  'Ώ' => 'ώ',
  'ῼ' => 'ῳ',
  'Ω' => 'ω',
  'K' => 'k',
  'Å' => 'å',
  'Ⅎ' => 'ⅎ',
  'Ⅰ' => 'ⅰ',
  'Ⅱ' => 'ⅱ',
  'Ⅲ' => 'ⅲ',
  'Ⅳ' => 'ⅳ',
  'Ⅴ' => 'ⅴ',
  'Ⅵ' => 'ⅵ',
  'Ⅶ' => 'ⅶ',
  'Ⅷ' => 'ⅷ',
  'Ⅸ' => 'ⅸ',
  'Ⅹ' => 'ⅹ',
  'Ⅺ' => 'ⅺ',
  'Ⅻ' => 'ⅻ',
  'Ⅼ' => 'ⅼ',
  'Ⅽ' => 'ⅽ',
  'Ⅾ' => 'ⅾ',
  'Ⅿ' => 'ⅿ',
  'Ↄ' => 'ↄ',
  'Ⓐ' => 'ⓐ',
  'Ⓑ' => 'ⓑ',
  'Ⓒ' => 'ⓒ',
  'Ⓓ' => 'ⓓ',
  'Ⓔ' => 'ⓔ',
  'Ⓕ' => 'ⓕ',
  'Ⓖ' => 'ⓖ',
  'Ⓗ' => 'ⓗ',
  'Ⓘ' => 'ⓘ',
  'Ⓙ' => 'ⓙ',
  'Ⓚ' => 'ⓚ',
  'Ⓛ' => 'ⓛ',
  'Ⓜ' => 'ⓜ',
  'Ⓝ' => 'ⓝ',
  'Ⓞ' => 'ⓞ',
  'Ⓟ' => 'ⓟ',
  'Ⓠ' => 'ⓠ',
  'Ⓡ' => 'ⓡ',
  'Ⓢ' => 'ⓢ',
  'Ⓣ' => 'ⓣ',
  'Ⓤ' => 'ⓤ',
  'Ⓥ' => 'ⓥ',
  'Ⓦ' => 'ⓦ',
  'Ⓧ' => 'ⓧ',
  'Ⓨ' => 'ⓨ',
  'Ⓩ' => 'ⓩ',
  'Ⰰ' => 'ⰰ',
  'Ⰱ' => 'ⰱ',
  'Ⰲ' => 'ⰲ',
  'Ⰳ' => 'ⰳ',
  'Ⰴ' => 'ⰴ',
  'Ⰵ' => 'ⰵ',
  'Ⰶ' => 'ⰶ',
  'Ⰷ' => 'ⰷ',
  'Ⰸ' => 'ⰸ',
  'Ⰹ' => 'ⰹ',
  'Ⰺ' => 'ⰺ',
  'Ⰻ' => 'ⰻ',
  'Ⰼ' => 'ⰼ',
  'Ⰽ' => 'ⰽ',
  'Ⰾ' => 'ⰾ',
  'Ⰿ' => 'ⰿ',
  'Ⱀ' => 'ⱀ',
  'Ⱁ' => 'ⱁ',
  'Ⱂ' => 'ⱂ',
  'Ⱃ' => 'ⱃ',
  'Ⱄ' => 'ⱄ',
  'Ⱅ' => 'ⱅ',
  'Ⱆ' => 'ⱆ',
  'Ⱇ' => 'ⱇ',
  'Ⱈ' => 'ⱈ',
  'Ⱉ' => 'ⱉ',
  'Ⱊ' => 'ⱊ',
  'Ⱋ' => 'ⱋ',
  'Ⱌ' => 'ⱌ',
  'Ⱍ' => 'ⱍ',
  'Ⱎ' => 'ⱎ',
  'Ⱏ' => 'ⱏ',
  'Ⱐ' => 'ⱐ',
  'Ⱑ' => 'ⱑ',
  'Ⱒ' => 'ⱒ',
  'Ⱓ' => 'ⱓ',
  'Ⱔ' => 'ⱔ',
  'Ⱕ' => 'ⱕ',
  'Ⱖ' => 'ⱖ',
  'Ⱗ' => 'ⱗ',
  'Ⱘ' => 'ⱘ',
  'Ⱙ' => 'ⱙ',
  'Ⱚ' => 'ⱚ',
  'Ⱛ' => 'ⱛ',
  'Ⱜ' => 'ⱜ',
  'Ⱝ' => 'ⱝ',
  'Ⱞ' => 'ⱞ',
  'Ⱡ' => 'ⱡ',
  'Ɫ' => 'ɫ',
  'Ᵽ' => 'ᵽ',
  'Ɽ' => 'ɽ',
  'Ⱨ' => 'ⱨ',
  'Ⱪ' => 'ⱪ',
  'Ⱬ' => 'ⱬ',
  'Ɑ' => 'ɑ',
  'Ɱ' => 'ɱ',
  'Ɐ' => 'ɐ',
  'Ɒ' => 'ɒ',
  'Ⱳ' => 'ⱳ',
  'Ⱶ' => 'ⱶ',
  'Ȿ' => 'ȿ',
  'Ɀ' => 'ɀ',
  'Ⲁ' => 'ⲁ',
  'Ⲃ' => 'ⲃ',
  'Ⲅ' => 'ⲅ',
  'Ⲇ' => 'ⲇ',
  'Ⲉ' => 'ⲉ',
  'Ⲋ' => 'ⲋ',
  'Ⲍ' => 'ⲍ',
  'Ⲏ' => 'ⲏ',
  'Ⲑ' => 'ⲑ',
  'Ⲓ' => 'ⲓ',
  'Ⲕ' => 'ⲕ',
  'Ⲗ' => 'ⲗ',
  'Ⲙ' => 'ⲙ',
  'Ⲛ' => 'ⲛ',
  'Ⲝ' => 'ⲝ',
  'Ⲟ' => 'ⲟ',
  'Ⲡ' => 'ⲡ',
  'Ⲣ' => 'ⲣ',
  'Ⲥ' => 'ⲥ',
  'Ⲧ' => 'ⲧ',
  'Ⲩ' => 'ⲩ',
  'Ⲫ' => 'ⲫ',
  'Ⲭ' => 'ⲭ',
  'Ⲯ' => 'ⲯ',
  'Ⲱ' => 'ⲱ',
  'Ⲳ' => 'ⲳ',
  'Ⲵ' => 'ⲵ',
  'Ⲷ' => 'ⲷ',
  'Ⲹ' => 'ⲹ',
  'Ⲻ' => 'ⲻ',
  'Ⲽ' => 'ⲽ',
  'Ⲿ' => 'ⲿ',
  'Ⳁ' => 'ⳁ',
  'Ⳃ' => 'ⳃ',
  'Ⳅ' => 'ⳅ',
  'Ⳇ' => 'ⳇ',
  'Ⳉ' => 'ⳉ',
  'Ⳋ' => 'ⳋ',
  'Ⳍ' => 'ⳍ',
  'Ⳏ' => 'ⳏ',
  'Ⳑ' => 'ⳑ',
  'Ⳓ' => 'ⳓ',
  'Ⳕ' => 'ⳕ',
  'Ⳗ' => 'ⳗ',
  'Ⳙ' => 'ⳙ',
  'Ⳛ' => 'ⳛ',
  'Ⳝ' => 'ⳝ',
  'Ⳟ' => 'ⳟ',
  'Ⳡ' => 'ⳡ',
  'Ⳣ' => 'ⳣ',
  'Ⳬ' => 'ⳬ',
  'Ⳮ' => 'ⳮ',
  'Ⳳ' => 'ⳳ',
  'Ꙁ' => 'ꙁ',
  'Ꙃ' => 'ꙃ',
  'Ꙅ' => 'ꙅ',
  'Ꙇ' => 'ꙇ',
  'Ꙉ' => 'ꙉ',
  'Ꙋ' => 'ꙋ',
  'Ꙍ' => 'ꙍ',
  'Ꙏ' => 'ꙏ',
  'Ꙑ' => 'ꙑ',
  'Ꙓ' => 'ꙓ',
  'Ꙕ' => 'ꙕ',
  'Ꙗ' => 'ꙗ',
  'Ꙙ' => 'ꙙ',
  'Ꙛ' => 'ꙛ',
  'Ꙝ' => 'ꙝ',
  'Ꙟ' => 'ꙟ',
  'Ꙡ' => 'ꙡ',
  'Ꙣ' => 'ꙣ',
  'Ꙥ' => 'ꙥ',
  'Ꙧ' => 'ꙧ',
  'Ꙩ' => 'ꙩ',
  'Ꙫ' => 'ꙫ',
  'Ꙭ' => 'ꙭ',
  'Ꚁ' => 'ꚁ',
  'Ꚃ' => 'ꚃ',
  'Ꚅ' => 'ꚅ',
  'Ꚇ' => 'ꚇ',
  'Ꚉ' => 'ꚉ',
  'Ꚋ' => 'ꚋ',
  'Ꚍ' => 'ꚍ',
  'Ꚏ' => 'ꚏ',
  'Ꚑ' => 'ꚑ',
  'Ꚓ' => 'ꚓ',
  'Ꚕ' => 'ꚕ',
  'Ꚗ' => 'ꚗ',
  'Ꚙ' => 'ꚙ',
  'Ꚛ' => 'ꚛ',
  'Ꜣ' => 'ꜣ',
  'Ꜥ' => 'ꜥ',
  'Ꜧ' => 'ꜧ',
  'Ꜩ' => 'ꜩ',
  'Ꜫ' => 'ꜫ',
  'Ꜭ' => 'ꜭ',
  'Ꜯ' => 'ꜯ',
  'Ꜳ' => 'ꜳ',
  'Ꜵ' => 'ꜵ',
  'Ꜷ' => 'ꜷ',
  'Ꜹ' => 'ꜹ',
  'Ꜻ' => 'ꜻ',
  'Ꜽ' => 'ꜽ',
  'Ꜿ' => 'ꜿ',
  'Ꝁ' => 'ꝁ',
  'Ꝃ' => 'ꝃ',
  'Ꝅ' => 'ꝅ',
  'Ꝇ' => 'ꝇ',
  'Ꝉ' => 'ꝉ',
  'Ꝋ' => 'ꝋ',
  'Ꝍ' => 'ꝍ',
  'Ꝏ' => 'ꝏ',
  'Ꝑ' => 'ꝑ',
  'Ꝓ' => 'ꝓ',
  'Ꝕ' => 'ꝕ',
  'Ꝗ' => 'ꝗ',
  'Ꝙ' => 'ꝙ',
  'Ꝛ' => 'ꝛ',
  'Ꝝ' => 'ꝝ',
  'Ꝟ' => 'ꝟ',
  'Ꝡ' => 'ꝡ',
  'Ꝣ' => 'ꝣ',
  'Ꝥ' => 'ꝥ',
  'Ꝧ' => 'ꝧ',
  'Ꝩ' => 'ꝩ',
  'Ꝫ' => 'ꝫ',
  'Ꝭ' => 'ꝭ',
  'Ꝯ' => 'ꝯ',
  'Ꝺ' => 'ꝺ',
  'Ꝼ' => 'ꝼ',
  'Ᵹ' => 'ᵹ',
  'Ꝿ' => 'ꝿ',
  'Ꞁ' => 'ꞁ',
  'Ꞃ' => 'ꞃ',
  'Ꞅ' => 'ꞅ',
  'Ꞇ' => 'ꞇ',
  'Ꞌ' => 'ꞌ',
  'Ɥ' => 'ɥ',
  'Ꞑ' => 'ꞑ',
  'Ꞓ' => 'ꞓ',
  'Ꞗ' => 'ꞗ',
  'Ꞙ' => 'ꞙ',
  'Ꞛ' => 'ꞛ',
  'Ꞝ' => 'ꞝ',
  'Ꞟ' => 'ꞟ',
  'Ꞡ' => 'ꞡ',
  'Ꞣ' => 'ꞣ',
  'Ꞥ' => 'ꞥ',
  'Ꞧ' => 'ꞧ',
  'Ꞩ' => 'ꞩ',
  'Ɦ' => 'ɦ',
  'Ɜ' => 'ɜ',
  'Ɡ' => 'ɡ',
  'Ɬ' => 'ɬ',
  'Ɪ' => 'ɪ',
  'Ʞ' => 'ʞ',
  'Ʇ' => 'ʇ',
  'Ʝ' => 'ʝ',
  'Ꭓ' => 'ꭓ',
  'Ꞵ' => 'ꞵ',
  'Ꞷ' => 'ꞷ',
  'Ꞹ' => 'ꞹ',
  'Ꞻ' => 'ꞻ',
  'Ꞽ' => 'ꞽ',
  'Ꞿ' => 'ꞿ',
  'Ꟃ' => 'ꟃ',
  'Ꞔ' => 'ꞔ',
  'Ʂ' => 'ʂ',
  'Ᶎ' => 'ᶎ',
  'Ꟈ' => 'ꟈ',
  'Ꟊ' => 'ꟊ',
  'Ꟶ' => 'ꟶ',
  'Ａ' => 'ａ',
  'Ｂ' => 'ｂ',
  'Ｃ' => 'ｃ',
  'Ｄ' => 'ｄ',
  'Ｅ' => 'ｅ',
  'Ｆ' => 'ｆ',
  'Ｇ' => 'ｇ',
  'Ｈ' => 'ｈ',
  'Ｉ' => 'ｉ',
  'Ｊ' => 'ｊ',
  'Ｋ' => 'ｋ',
  'Ｌ' => 'ｌ',
  'Ｍ' => 'ｍ',
  'Ｎ' => 'ｎ',
  'Ｏ' => 'ｏ',
  'Ｐ' => 'ｐ',
  'Ｑ' => 'ｑ',
  'Ｒ' => 'ｒ',
  'Ｓ' => 'ｓ',
  'Ｔ' => 'ｔ',
  'Ｕ' => 'ｕ',
  'Ｖ' => 'ｖ',
  'Ｗ' => 'ｗ',
  'Ｘ' => 'ｘ',
  'Ｙ' => 'ｙ',
  'Ｚ' => 'ｚ',
  '𐐀' => '𐐨',
  '𐐁' => '𐐩',
  '𐐂' => '𐐪',
  '𐐃' => '𐐫',
  '𐐄' => '𐐬',
  '𐐅' => '𐐭',
  '𐐆' => '𐐮',
  '𐐇' => '𐐯',
  '𐐈' => '𐐰',
  '𐐉' => '𐐱',
  '𐐊' => '𐐲',
  '𐐋' => '𐐳',
  '𐐌' => '𐐴',
  '𐐍' => '𐐵',
  '𐐎' => '𐐶',
  '𐐏' => '𐐷',
  '𐐐' => '𐐸',
  '𐐑' => '𐐹',
  '𐐒' => '𐐺',
  '𐐓' => '𐐻',
  '𐐔' => '𐐼',
  '𐐕' => '𐐽',
  '𐐖' => '𐐾',
  '𐐗' => '𐐿',
  '𐐘' => '𐑀',
  '𐐙' => '𐑁',
  '𐐚' => '𐑂',
  '𐐛' => '𐑃',
  '𐐜' => '𐑄',
  '𐐝' => '𐑅',
  '𐐞' => '𐑆',
  '𐐟' => '𐑇',
  '𐐠' => '𐑈',
  '𐐡' => '𐑉',
  '𐐢' => '𐑊',
  '𐐣' => '𐑋',
  '𐐤' => '𐑌',
  '𐐥' => '𐑍',
  '𐐦' => '𐑎',
  '𐐧' => '𐑏',
  '𐒰' => '𐓘',
  '𐒱' => '𐓙',
  '𐒲' => '𐓚',
  '𐒳' => '𐓛',
  '𐒴' => '𐓜',
  '𐒵' => '𐓝',
  '𐒶' => '𐓞',
  '𐒷' => '𐓟',
  '𐒸' => '𐓠',
  '𐒹' => '𐓡',
  '𐒺' => '𐓢',
  '𐒻' => '𐓣',
  '𐒼' => '𐓤',
  '𐒽' => '𐓥',
  '𐒾' => '𐓦',
  '𐒿' => '𐓧',
  '𐓀' => '𐓨',
  '𐓁' => '𐓩',
  '𐓂' => '𐓪',
  '𐓃' => '𐓫',
  '𐓄' => '𐓬',
  '𐓅' => '𐓭',
  '𐓆' => '𐓮',
  '𐓇' => '𐓯',
  '𐓈' => '𐓰',
  '𐓉' => '𐓱',
  '𐓊' => '𐓲',
  '𐓋' => '𐓳',
  '𐓌' => '𐓴',
  '𐓍' => '𐓵',
  '𐓎' => '𐓶',
  '𐓏' => '𐓷',
  '𐓐' => '𐓸',
  '𐓑' => '𐓹',
  '𐓒' => '𐓺',
  '𐓓' => '𐓻',
  '𐲀' => '𐳀',
  '𐲁' => '𐳁',
  '𐲂' => '𐳂',
  '𐲃' => '𐳃',
  '𐲄' => '𐳄',
  '𐲅' => '𐳅',
  '𐲆' => '𐳆',
  '𐲇' => '𐳇',
  '𐲈' => '𐳈',
  '𐲉' => '𐳉',
  '𐲊' => '𐳊',
  '𐲋' => '𐳋',
  '𐲌' => '𐳌',
  '𐲍' => '𐳍',
  '𐲎' => '𐳎',
  '𐲏' => '𐳏',
  '𐲐' => '𐳐',
  '𐲑' => '𐳑',
  '𐲒' => '𐳒',
  '𐲓' => '𐳓',
  '𐲔' => '𐳔',
  '𐲕' => '𐳕',
  '𐲖' => '𐳖',
  '𐲗' => '𐳗',
  '𐲘' => '𐳘',
  '𐲙' => '𐳙',
  '𐲚' => '𐳚',
  '𐲛' => '𐳛',
  '𐲜' => '𐳜',
  '𐲝' => '𐳝',
  '𐲞' => '𐳞',
  '𐲟' => '𐳟',
  '𐲠' => '𐳠',
  '𐲡' => '𐳡',
  '𐲢' => '𐳢',
  '𐲣' => '𐳣',
  '𐲤' => '𐳤',
  '𐲥' => '𐳥',
  '𐲦' => '𐳦',
  '𐲧' => '𐳧',
  '𐲨' => '𐳨',
  '𐲩' => '𐳩',
  '𐲪' => '𐳪',
  '𐲫' => '𐳫',
  '𐲬' => '𐳬',
  '𐲭' => '𐳭',
  '𐲮' => '𐳮',
  '𐲯' => '𐳯',
  '𐲰' => '𐳰',
  '𐲱' => '𐳱',
  '𐲲' => '𐳲',
  '𑢠' => '𑣀',
  '𑢡' => '𑣁',
  '𑢢' => '𑣂',
  '𑢣' => '𑣃',
  '𑢤' => '𑣄',
  '𑢥' => '𑣅',
  '𑢦' => '𑣆',
  '𑢧' => '𑣇',
  '𑢨' => '𑣈',
  '𑢩' => '𑣉',
  '𑢪' => '𑣊',
  '𑢫' => '𑣋',
  '𑢬' => '𑣌',
  '𑢭' => '𑣍',
  '𑢮' => '𑣎',
  '𑢯' => '𑣏',
  '𑢰' => '𑣐',
  '𑢱' => '𑣑',
  '𑢲' => '𑣒',
  '𑢳' => '𑣓',
  '𑢴' => '𑣔',
  '𑢵' => '𑣕',
  '𑢶' => '𑣖',
  '𑢷' => '𑣗',
  '𑢸' => '𑣘',
  '𑢹' => '𑣙',
  '𑢺' => '𑣚',
  '𑢻' => '𑣛',
  '𑢼' => '𑣜',
  '𑢽' => '𑣝',
  '𑢾' => '𑣞',
  '𑢿' => '𑣟',
  '𖹀' => '𖹠',
  '𖹁' => '𖹡',
  '𖹂' => '𖹢',
  '𖹃' => '𖹣',
  '𖹄' => '𖹤',
  '𖹅' => '𖹥',
  '𖹆' => '𖹦',
  '𖹇' => '𖹧',
  '𖹈' => '𖹨',
  '𖹉' => '𖹩',
  '𖹊' => '𖹪',
  '𖹋' => '𖹫',
  '𖹌' => '𖹬',
  '𖹍' => '𖹭',
  '𖹎' => '𖹮',
  '𖹏' => '𖹯',
  '𖹐' => '𖹰',
  '𖹑' => '𖹱',
  '𖹒' => '𖹲',
  '𖹓' => '𖹳',
  '𖹔' => '𖹴',
  '𖹕' => '𖹵',
  '𖹖' => '𖹶',
  '𖹗' => '𖹷',
  '𖹘' => '𖹸',
  '𖹙' => '𖹹',
  '𖹚' => '𖹺',
  '𖹛' => '𖹻',
  '𖹜' => '𖹼',
  '𖹝' => '𖹽',
  '𖹞' => '𖹾',
  '𖹟' => '𖹿',
  '𞤀' => '𞤢',
  '𞤁' => '𞤣',
  '𞤂' => '𞤤',
  '𞤃' => '𞤥',
  '𞤄' => '𞤦',
  '𞤅' => '𞤧',
  '𞤆' => '𞤨',
  '𞤇' => '𞤩',
  '𞤈' => '𞤪',
  '𞤉' => '𞤫',
  '𞤊' => '𞤬',
  '𞤋' => '𞤭',
  '𞤌' => '𞤮',
  '𞤍' => '𞤯',
  '𞤎' => '𞤰',
  '𞤏' => '𞤱',
  '𞤐' => '𞤲',
  '𞤑' => '𞤳',
  '𞤒' => '𞤴',
  '𞤓' => '𞤵',
  '𞤔' => '𞤶',
  '𞤕' => '𞤷',
  '𞤖' => '𞤸',
  '𞤗' => '𞤹',
  '𞤘' => '𞤺',
  '𞤙' => '𞤻',
  '𞤚' => '𞤼',
  '𞤛' => '𞤽',
  '𞤜' => '𞤾',
  '𞤝' => '𞤿',
  '𞤞' => '𞥀',
  '𞤟' => '𞥁',
  '𞤠' => '𞥂',
  '𞤡' => '𞥃',
);
<?php

return array (
  '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',
  'µ' => 'Μ',
  'à' => 'À',
  'á' => 'Á',
  'â' => 'Â',
  'ã' => 'Ã',
  'ä' => 'Ä',
  'å' => 'Å',
  'æ' => 'Æ',
  'ç' => 'Ç',
  'è' => 'È',
  'é' => 'É',
  'ê' => 'Ê',
  'ë' => 'Ë',
  'ì' => 'Ì',
  'í' => 'Í',
  'î' => 'Î',
  'ï' => 'Ï',
  'ð' => 'Ð',
  'ñ' => 'Ñ',
  'ò' => 'Ò',
  'ó' => 'Ó',
  'ô' => 'Ô',
  'õ' => 'Õ',
  'ö' => 'Ö',
  'ø' => 'Ø',
  'ù' => 'Ù',
  'ú' => 'Ú',
  'û' => 'Û',
  'ü' => 'Ü',
  'ý' => 'Ý',
  'þ' => 'Þ',
  'ÿ' => 'Ÿ',
  'ā' => 'Ā',
  'ă' => 'Ă',
  'ą' => 'Ą',
  'ć' => 'Ć',
  'ĉ' => 'Ĉ',
  'ċ' => 'Ċ',
  'č' => 'Č',
  'ď' => 'Ď',
  'đ' => 'Đ',
  'ē' => 'Ē',
  'ĕ' => 'Ĕ',
  'ė' => 'Ė',
  'ę' => 'Ę',
  'ě' => 'Ě',
  'ĝ' => 'Ĝ',
  'ğ' => 'Ğ',
  'ġ' => 'Ġ',
  'ģ' => 'Ģ',
  'ĥ' => 'Ĥ',
  'ħ' => 'Ħ',
  'ĩ' => 'Ĩ',
  'ī' => 'Ī',
  'ĭ' => 'Ĭ',
  'į' => 'Į',
  'ı' => 'I',
  'ĳ' => 'Ĳ',
  'ĵ' => 'Ĵ',
  'ķ' => 'Ķ',
  'ĺ' => 'Ĺ',
  'ļ' => 'Ļ',
  'ľ' => 'Ľ',
  'ŀ' => 'Ŀ',
  'ł' => 'Ł',
  'ń' => 'Ń',
  'ņ' => 'Ņ',
  'ň' => 'Ň',
  'ŋ' => 'Ŋ',
  'ō' => 'Ō',
  'ŏ' => 'Ŏ',
  'ő' => 'Ő',
  'œ' => 'Œ',
  'ŕ' => 'Ŕ',
  'ŗ' => 'Ŗ',
  'ř' => 'Ř',
  'ś' => 'Ś',
  'ŝ' => 'Ŝ',
  'ş' => 'Ş',
  'š' => 'Š',
  'ţ' => 'Ţ',
  'ť' => 'Ť',
  'ŧ' => 'Ŧ',
  'ũ' => 'Ũ',
  'ū' => 'Ū',
  'ŭ' => 'Ŭ',
  'ů' => 'Ů',
  'ű' => 'Ű',
  'ų' => 'Ų',
  'ŵ' => 'Ŵ',
  'ŷ' => 'Ŷ',
  'ź' => 'Ź',
  'ż' => 'Ż',
  'ž' => 'Ž',
  'ſ' => 'S',
  'ƀ' => 'Ƀ',
  'ƃ' => 'Ƃ',
  'ƅ' => 'Ƅ',
  'ƈ' => 'Ƈ',
  'ƌ' => 'Ƌ',
  'ƒ' => 'Ƒ',
  'ƕ' => 'Ƕ',
  'ƙ' => 'Ƙ',
  'ƚ' => 'Ƚ',
  'ƞ' => 'Ƞ',
  'ơ' => 'Ơ',
  'ƣ' => 'Ƣ',
  'ƥ' => 'Ƥ',
  'ƨ' => 'Ƨ',
  'ƭ' => 'Ƭ',
  'ư' => 'Ư',
  'ƴ' => 'Ƴ',
  'ƶ' => 'Ƶ',
  'ƹ' => 'Ƹ',
  'ƽ' => 'Ƽ',
  'ƿ' => 'Ƿ',
  'ǅ' => 'Ǆ',
  'ǆ' => 'Ǆ',
  'ǈ' => 'Ǉ',
  'ǉ' => 'Ǉ',
  'ǋ' => 'Ǌ',
  'ǌ' => 'Ǌ',
  'ǎ' => 'Ǎ',
  'ǐ' => 'Ǐ',
  'ǒ' => 'Ǒ',
  'ǔ' => 'Ǔ',
  'ǖ' => 'Ǖ',
  'ǘ' => 'Ǘ',
  'ǚ' => 'Ǚ',
  'ǜ' => 'Ǜ',
  'ǝ' => 'Ǝ',
  'ǟ' => 'Ǟ',
  'ǡ' => 'Ǡ',
  'ǣ' => 'Ǣ',
  'ǥ' => 'Ǥ',
  'ǧ' => 'Ǧ',
  'ǩ' => 'Ǩ',
  'ǫ' => 'Ǫ',
  'ǭ' => 'Ǭ',
  'ǯ' => 'Ǯ',
  'ǲ' => 'Ǳ',
  'ǳ' => 'Ǳ',
  'ǵ' => 'Ǵ',
  'ǹ' => 'Ǹ',
  'ǻ' => 'Ǻ',
  'ǽ' => 'Ǽ',
  'ǿ' => 'Ǿ',
  'ȁ' => 'Ȁ',
  'ȃ' => 'Ȃ',
  'ȅ' => 'Ȅ',
  'ȇ' => 'Ȇ',
  'ȉ' => 'Ȉ',
  'ȋ' => 'Ȋ',
  'ȍ' => 'Ȍ',
  'ȏ' => 'Ȏ',
  'ȑ' => 'Ȑ',
  'ȓ' => 'Ȓ',
  'ȕ' => 'Ȕ',
  'ȗ' => 'Ȗ',
  'ș' => 'Ș',
  'ț' => 'Ț',
  'ȝ' => 'Ȝ',
  'ȟ' => 'Ȟ',
  'ȣ' => 'Ȣ',
  'ȥ' => 'Ȥ',
  'ȧ' => 'Ȧ',
  'ȩ' => 'Ȩ',
  'ȫ' => 'Ȫ',
  'ȭ' => 'Ȭ',
  'ȯ' => 'Ȯ',
  'ȱ' => 'Ȱ',
  'ȳ' => 'Ȳ',
  'ȼ' => 'Ȼ',
  'ȿ' => 'Ȿ',
  'ɀ' => 'Ɀ',
  'ɂ' => 'Ɂ',
  'ɇ' => 'Ɇ',
  'ɉ' => 'Ɉ',
  'ɋ' => 'Ɋ',
  'ɍ' => 'Ɍ',
  'ɏ' => 'Ɏ',
  'ɐ' => 'Ɐ',
  'ɑ' => 'Ɑ',
  'ɒ' => 'Ɒ',
  'ɓ' => 'Ɓ',
  'ɔ' => 'Ɔ',
  'ɖ' => 'Ɖ',
  'ɗ' => 'Ɗ',
  'ə' => 'Ə',
  'ɛ' => 'Ɛ',
  'ɜ' => 'Ɜ',
  'ɠ' => 'Ɠ',
  'ɡ' => 'Ɡ',
  'ɣ' => 'Ɣ',
  'ɥ' => 'Ɥ',
  'ɦ' => 'Ɦ',
  'ɨ' => 'Ɨ',
  'ɩ' => 'Ɩ',
  'ɪ' => 'Ɪ',
  'ɫ' => 'Ɫ',
  'ɬ' => 'Ɬ',
  'ɯ' => 'Ɯ',
  'ɱ' => 'Ɱ',
  'ɲ' => 'Ɲ',
  'ɵ' => 'Ɵ',
  'ɽ' => 'Ɽ',
  'ʀ' => 'Ʀ',
  'ʂ' => 'Ʂ',
  'ʃ' => 'Ʃ',
  'ʇ' => 'Ʇ',
  'ʈ' => 'Ʈ',
  'ʉ' => 'Ʉ',
  'ʊ' => 'Ʊ',
  'ʋ' => 'Ʋ',
  'ʌ' => 'Ʌ',
  'ʒ' => 'Ʒ',
  'ʝ' => 'Ʝ',
  'ʞ' => 'Ʞ',
  'ͅ' => 'Ι',
  'ͱ' => 'Ͱ',
  'ͳ' => 'Ͳ',
  'ͷ' => 'Ͷ',
  'ͻ' => 'Ͻ',
  'ͼ' => 'Ͼ',
  'ͽ' => 'Ͽ',
  'ά' => 'Ά',
  'έ' => 'Έ',
  'ή' => 'Ή',
  'ί' => 'Ί',
  'α' => 'Α',
  'β' => 'Β',
  'γ' => 'Γ',
  'δ' => 'Δ',
  'ε' => 'Ε',
  'ζ' => 'Ζ',
  'η' => 'Η',
  'θ' => 'Θ',
  'ι' => 'Ι',
  'κ' => 'Κ',
  'λ' => 'Λ',
  'μ' => 'Μ',
  'ν' => 'Ν',
  'ξ' => 'Ξ',
  'ο' => 'Ο',
  'π' => 'Π',
  'ρ' => 'Ρ',
  'ς' => 'Σ',
  'σ' => 'Σ',
  'τ' => 'Τ',
  'υ' => 'Υ',
  'φ' => 'Φ',
  'χ' => 'Χ',
  'ψ' => 'Ψ',
  'ω' => 'Ω',
  'ϊ' => 'Ϊ',
  'ϋ' => 'Ϋ',
  'ό' => 'Ό',
  'ύ' => 'Ύ',
  'ώ' => 'Ώ',
  'ϐ' => 'Β',
  'ϑ' => 'Θ',
  'ϕ' => 'Φ',
  'ϖ' => 'Π',
  'ϗ' => 'Ϗ',
  'ϙ' => 'Ϙ',
  'ϛ' => 'Ϛ',
  'ϝ' => 'Ϝ',
  'ϟ' => 'Ϟ',
  'ϡ' => 'Ϡ',
  'ϣ' => 'Ϣ',
  'ϥ' => 'Ϥ',
  'ϧ' => 'Ϧ',
  'ϩ' => 'Ϩ',
  'ϫ' => 'Ϫ',
  'ϭ' => 'Ϭ',
  'ϯ' => 'Ϯ',
  'ϰ' => 'Κ',
  'ϱ' => 'Ρ',
  'ϲ' => 'Ϲ',
  'ϳ' => 'Ϳ',
  'ϵ' => 'Ε',
  'ϸ' => 'Ϸ',
  'ϻ' => 'Ϻ',
  'а' => 'А',
  'б' => 'Б',
  'в' => 'В',
  'г' => 'Г',
  'д' => 'Д',
  'е' => 'Е',
  'ж' => 'Ж',
  'з' => 'З',
  'и' => 'И',
  'й' => 'Й',
  'к' => 'К',
  'л' => 'Л',
  'м' => 'М',
  'н' => 'Н',
  'о' => 'О',
  'п' => 'П',
  'р' => 'Р',
  'с' => 'С',
  'т' => 'Т',
  'у' => 'У',
  'ф' => 'Ф',
  'х' => 'Х',
  'ц' => 'Ц',
  'ч' => 'Ч',
  'ш' => 'Ш',
  'щ' => 'Щ',
  'ъ' => 'Ъ',
  'ы' => 'Ы',
  'ь' => 'Ь',
  'э' => 'Э',
  'ю' => 'Ю',
  'я' => 'Я',
  'ѐ' => 'Ѐ',
  'ё' => 'Ё',
  'ђ' => 'Ђ',
  'ѓ' => 'Ѓ',
  'є' => 'Є',
  'ѕ' => 'Ѕ',
  'і' => 'І',
  'ї' => 'Ї',
  'ј' => 'Ј',
  'љ' => 'Љ',
  'њ' => 'Њ',
  'ћ' => 'Ћ',
  'ќ' => 'Ќ',
  'ѝ' => 'Ѝ',
  'ў' => 'Ў',
  'џ' => 'Џ',
  'ѡ' => 'Ѡ',
  'ѣ' => 'Ѣ',
  'ѥ' => 'Ѥ',
  'ѧ' => 'Ѧ',
  'ѩ' => 'Ѩ',
  'ѫ' => 'Ѫ',
  'ѭ' => 'Ѭ',
  'ѯ' => 'Ѯ',
  'ѱ' => 'Ѱ',
  'ѳ' => 'Ѳ',
  'ѵ' => 'Ѵ',
  'ѷ' => 'Ѷ',
  'ѹ' => 'Ѹ',
  'ѻ' => 'Ѻ',
  'ѽ' => 'Ѽ',
  'ѿ' => 'Ѿ',
  'ҁ' => 'Ҁ',
  'ҋ' => 'Ҋ',
  'ҍ' => 'Ҍ',
  'ҏ' => 'Ҏ',
  'ґ' => 'Ґ',
  'ғ' => 'Ғ',
  'ҕ' => 'Ҕ',
  'җ' => 'Җ',
  'ҙ' => 'Ҙ',
  'қ' => 'Қ',
  'ҝ' => 'Ҝ',
  'ҟ' => 'Ҟ',
  'ҡ' => 'Ҡ',
  'ң' => 'Ң',
  'ҥ' => 'Ҥ',
  'ҧ' => 'Ҧ',
  'ҩ' => 'Ҩ',
  'ҫ' => 'Ҫ',
  'ҭ' => 'Ҭ',
  'ү' => 'Ү',
  'ұ' => 'Ұ',
  'ҳ' => 'Ҳ',
  'ҵ' => 'Ҵ',
  'ҷ' => 'Ҷ',
  'ҹ' => 'Ҹ',
  'һ' => 'Һ',
  'ҽ' => 'Ҽ',
  'ҿ' => 'Ҿ',
  'ӂ' => 'Ӂ',
  'ӄ' => 'Ӄ',
  'ӆ' => 'Ӆ',
  'ӈ' => 'Ӈ',
  'ӊ' => 'Ӊ',
  'ӌ' => 'Ӌ',
  'ӎ' => 'Ӎ',
  'ӏ' => 'Ӏ',
  'ӑ' => 'Ӑ',
  'ӓ' => 'Ӓ',
  'ӕ' => 'Ӕ',
  'ӗ' => 'Ӗ',
  'ә' => 'Ә',
  'ӛ' => 'Ӛ',
  'ӝ' => 'Ӝ',
  'ӟ' => 'Ӟ',
  'ӡ' => 'Ӡ',
  'ӣ' => 'Ӣ',
  'ӥ' => 'Ӥ',
  'ӧ' => 'Ӧ',
  'ө' => 'Ө',
  'ӫ' => 'Ӫ',
  'ӭ' => 'Ӭ',
  'ӯ' => 'Ӯ',
  'ӱ' => 'Ӱ',
  'ӳ' => 'Ӳ',
  'ӵ' => 'Ӵ',
  'ӷ' => 'Ӷ',
  'ӹ' => 'Ӹ',
  'ӻ' => 'Ӻ',
  'ӽ' => 'Ӽ',
  'ӿ' => 'Ӿ',
  'ԁ' => 'Ԁ',
  'ԃ' => 'Ԃ',
  'ԅ' => 'Ԅ',
  'ԇ' => 'Ԇ',
  'ԉ' => 'Ԉ',
  'ԋ' => 'Ԋ',
  'ԍ' => 'Ԍ',
  'ԏ' => 'Ԏ',
  'ԑ' => 'Ԑ',
  'ԓ' => 'Ԓ',
  'ԕ' => 'Ԕ',
  'ԗ' => 'Ԗ',
  'ԙ' => 'Ԙ',
  'ԛ' => 'Ԛ',
  'ԝ' => 'Ԝ',
  'ԟ' => 'Ԟ',
  'ԡ' => 'Ԡ',
  'ԣ' => 'Ԣ',
  'ԥ' => 'Ԥ',
  'ԧ' => 'Ԧ',
  'ԩ' => 'Ԩ',
  'ԫ' => 'Ԫ',
  'ԭ' => 'Ԭ',
  'ԯ' => 'Ԯ',
  'ա' => 'Ա',
  'բ' => 'Բ',
  'գ' => 'Գ',
  'դ' => 'Դ',
  'ե' => 'Ե',
  'զ' => 'Զ',
  'է' => 'Է',
  'ը' => 'Ը',
  'թ' => 'Թ',
  'ժ' => 'Ժ',
  'ի' => 'Ի',
  'լ' => 'Լ',
  'խ' => 'Խ',
  'ծ' => 'Ծ',
  'կ' => 'Կ',
  'հ' => 'Հ',
  'ձ' => 'Ձ',
  'ղ' => 'Ղ',
  'ճ' => 'Ճ',
  'մ' => 'Մ',
  'յ' => 'Յ',
  'ն' => 'Ն',
  'շ' => 'Շ',
  'ո' => 'Ո',
  'չ' => 'Չ',
  'պ' => 'Պ',
  'ջ' => 'Ջ',
  'ռ' => 'Ռ',
  'ս' => 'Ս',
  'վ' => 'Վ',
  'տ' => 'Տ',
  'ր' => 'Ր',
  'ց' => 'Ց',
  'ւ' => 'Ւ',
  'փ' => 'Փ',
  'ք' => 'Ք',
  'օ' => 'Օ',
  'ֆ' => 'Ֆ',
  'ა' => 'Ა',
  'ბ' => 'Ბ',
  'გ' => 'Გ',
  'დ' => 'Დ',
  'ე' => 'Ე',
  'ვ' => 'Ვ',
  'ზ' => 'Ზ',
  'თ' => 'Თ',
  'ი' => 'Ი',
  'კ' => 'Კ',
  'ლ' => 'Ლ',
  'მ' => 'Მ',
  'ნ' => 'Ნ',
  'ო' => 'Ო',
  'პ' => 'Პ',
  'ჟ' => 'Ჟ',
  'რ' => 'Რ',
  'ს' => 'Ს',
  'ტ' => 'Ტ',
  'უ' => 'Უ',
  'ფ' => 'Ფ',
  'ქ' => 'Ქ',
  'ღ' => 'Ღ',
  'ყ' => 'Ყ',
  'შ' => 'Შ',
  'ჩ' => 'Ჩ',
  'ც' => 'Ც',
  'ძ' => 'Ძ',
  'წ' => 'Წ',
  'ჭ' => 'Ჭ',
  'ხ' => 'Ხ',
  'ჯ' => 'Ჯ',
  'ჰ' => 'Ჰ',
  'ჱ' => 'Ჱ',
  'ჲ' => 'Ჲ',
  'ჳ' => 'Ჳ',
  'ჴ' => 'Ჴ',
  'ჵ' => 'Ჵ',
  'ჶ' => 'Ჶ',
  'ჷ' => 'Ჷ',
  'ჸ' => 'Ჸ',
  'ჹ' => 'Ჹ',
  'ჺ' => 'Ჺ',
  'ჽ' => 'Ჽ',
  'ჾ' => 'Ჾ',
  'ჿ' => 'Ჿ',
  'ᏸ' => 'Ᏸ',
  'ᏹ' => 'Ᏹ',
  'ᏺ' => 'Ᏺ',
  'ᏻ' => 'Ᏻ',
  'ᏼ' => 'Ᏼ',
  'ᏽ' => 'Ᏽ',
  'ᲀ' => 'В',
  'ᲁ' => 'Д',
  'ᲂ' => 'О',
  'ᲃ' => 'С',
  'ᲄ' => 'Т',
  'ᲅ' => 'Т',
  'ᲆ' => 'Ъ',
  'ᲇ' => 'Ѣ',
  'ᲈ' => 'Ꙋ',
  'ᵹ' => 'Ᵹ',
  'ᵽ' => 'Ᵽ',
  'ᶎ' => 'Ᶎ',
  'ḁ' => 'Ḁ',
  'ḃ' => 'Ḃ',
  'ḅ' => 'Ḅ',
  'ḇ' => 'Ḇ',
  'ḉ' => 'Ḉ',
  'ḋ' => 'Ḋ',
  'ḍ' => 'Ḍ',
  'ḏ' => 'Ḏ',
  'ḑ' => 'Ḑ',
  'ḓ' => 'Ḓ',
  'ḕ' => 'Ḕ',
  'ḗ' => 'Ḗ',
  'ḙ' => 'Ḙ',
  'ḛ' => 'Ḛ',
  'ḝ' => 'Ḝ',
  'ḟ' => 'Ḟ',
  'ḡ' => 'Ḡ',
  'ḣ' => 'Ḣ',
  'ḥ' => 'Ḥ',
  'ḧ' => 'Ḧ',
  'ḩ' => 'Ḩ',
  'ḫ' => 'Ḫ',
  'ḭ' => 'Ḭ',
  'ḯ' => 'Ḯ',
  'ḱ' => 'Ḱ',
  'ḳ' => 'Ḳ',
  'ḵ' => 'Ḵ',
  'ḷ' => 'Ḷ',
  'ḹ' => 'Ḹ',
  'ḻ' => 'Ḻ',
  'ḽ' => 'Ḽ',
  'ḿ' => 'Ḿ',
  'ṁ' => 'Ṁ',
  'ṃ' => 'Ṃ',
  'ṅ' => 'Ṅ',
  'ṇ' => 'Ṇ',
  'ṉ' => 'Ṉ',
  'ṋ' => 'Ṋ',
  'ṍ' => 'Ṍ',
  'ṏ' => 'Ṏ',
  'ṑ' => 'Ṑ',
  'ṓ' => 'Ṓ',
  'ṕ' => 'Ṕ',
  'ṗ' => 'Ṗ',
  'ṙ' => 'Ṙ',
  'ṛ' => 'Ṛ',
  'ṝ' => 'Ṝ',
  'ṟ' => 'Ṟ',
  'ṡ' => 'Ṡ',
  'ṣ' => 'Ṣ',
  'ṥ' => 'Ṥ',
  'ṧ' => 'Ṧ',
  'ṩ' => 'Ṩ',
  'ṫ' => 'Ṫ',
  'ṭ' => 'Ṭ',
  'ṯ' => 'Ṯ',
  'ṱ' => 'Ṱ',
  'ṳ' => 'Ṳ',
  'ṵ' => 'Ṵ',
  'ṷ' => 'Ṷ',
  'ṹ' => 'Ṹ',
  'ṻ' => 'Ṻ',
  'ṽ' => 'Ṽ',
  'ṿ' => 'Ṿ',
  'ẁ' => 'Ẁ',
  'ẃ' => 'Ẃ',
  'ẅ' => 'Ẅ',
  'ẇ' => 'Ẇ',
  'ẉ' => 'Ẉ',
  'ẋ' => 'Ẋ',
  'ẍ' => 'Ẍ',
  'ẏ' => 'Ẏ',
  'ẑ' => 'Ẑ',
  'ẓ' => 'Ẓ',
  'ẕ' => 'Ẕ',
  'ẛ' => 'Ṡ',
  'ạ' => 'Ạ',
  'ả' => 'Ả',
  'ấ' => 'Ấ',
  'ầ' => 'Ầ',
  'ẩ' => 'Ẩ',
  'ẫ' => 'Ẫ',
  'ậ' => 'Ậ',
  'ắ' => 'Ắ',
  'ằ' => 'Ằ',
  'ẳ' => 'Ẳ',
  'ẵ' => 'Ẵ',
  'ặ' => 'Ặ',
  'ẹ' => 'Ẹ',
  'ẻ' => 'Ẻ',
  'ẽ' => 'Ẽ',
  'ế' => 'Ế',
  'ề' => 'Ề',
  'ể' => 'Ể',
  'ễ' => 'Ễ',
  'ệ' => 'Ệ',
  'ỉ' => 'Ỉ',
  'ị' => 'Ị',
  'ọ' => 'Ọ',
  'ỏ' => 'Ỏ',
  'ố' => 'Ố',
  'ồ' => 'Ồ',
  'ổ' => 'Ổ',
  'ỗ' => 'Ỗ',
  'ộ' => 'Ộ',
  'ớ' => 'Ớ',
  'ờ' => 'Ờ',
  'ở' => 'Ở',
  'ỡ' => 'Ỡ',
  'ợ' => 'Ợ',
  'ụ' => 'Ụ',
  'ủ' => 'Ủ',
  'ứ' => 'Ứ',
  'ừ' => 'Ừ',
  'ử' => 'Ử',
  'ữ' => 'Ữ',
  'ự' => 'Ự',
  'ỳ' => 'Ỳ',
  'ỵ' => 'Ỵ',
  'ỷ' => 'Ỷ',
  'ỹ' => 'Ỹ',
  'ỻ' => 'Ỻ',
  'ỽ' => 'Ỽ',
  'ỿ' => 'Ỿ',
  'ἀ' => 'Ἀ',
  'ἁ' => 'Ἁ',
  'ἂ' => 'Ἂ',
  'ἃ' => 'Ἃ',
  'ἄ' => 'Ἄ',
  'ἅ' => 'Ἅ',
  'ἆ' => 'Ἆ',
  'ἇ' => 'Ἇ',
  'ἐ' => 'Ἐ',
  'ἑ' => 'Ἑ',
  'ἒ' => 'Ἒ',
  'ἓ' => 'Ἓ',
  'ἔ' => 'Ἔ',
  'ἕ' => 'Ἕ',
  'ἠ' => 'Ἠ',
  'ἡ' => 'Ἡ',
  'ἢ' => 'Ἢ',
  'ἣ' => 'Ἣ',
  'ἤ' => 'Ἤ',
  'ἥ' => 'Ἥ',
  'ἦ' => 'Ἦ',
  'ἧ' => 'Ἧ',
  'ἰ' => 'Ἰ',
  'ἱ' => 'Ἱ',
  'ἲ' => 'Ἲ',
  'ἳ' => 'Ἳ',
  'ἴ' => 'Ἴ',
  'ἵ' => 'Ἵ',
  'ἶ' => 'Ἶ',
  'ἷ' => 'Ἷ',
  'ὀ' => 'Ὀ',
  'ὁ' => 'Ὁ',
  'ὂ' => 'Ὂ',
  'ὃ' => 'Ὃ',
  'ὄ' => 'Ὄ',
  'ὅ' => 'Ὅ',
  'ὑ' => 'Ὑ',
  'ὓ' => 'Ὓ',
  'ὕ' => 'Ὕ',
  'ὗ' => 'Ὗ',
  'ὠ' => 'Ὠ',
  'ὡ' => 'Ὡ',
  'ὢ' => 'Ὢ',
  'ὣ' => 'Ὣ',
  'ὤ' => 'Ὤ',
  'ὥ' => 'Ὥ',
  'ὦ' => 'Ὦ',
  'ὧ' => 'Ὧ',
  'ὰ' => 'Ὰ',
  'ά' => 'Ά',
  'ὲ' => 'Ὲ',
  'έ' => 'Έ',
  'ὴ' => 'Ὴ',
  'ή' => 'Ή',
  'ὶ' => 'Ὶ',
  'ί' => 'Ί',
  'ὸ' => 'Ὸ',
  'ό' => 'Ό',
  'ὺ' => 'Ὺ',
  'ύ' => 'Ύ',
  'ὼ' => 'Ὼ',
  'ώ' => 'Ώ',
  'ᾀ' => 'ἈΙ',
  'ᾁ' => 'ἉΙ',
  'ᾂ' => 'ἊΙ',
  'ᾃ' => 'ἋΙ',
  'ᾄ' => 'ἌΙ',
  'ᾅ' => 'ἍΙ',
  'ᾆ' => 'ἎΙ',
  'ᾇ' => 'ἏΙ',
  'ᾐ' => 'ἨΙ',
  'ᾑ' => 'ἩΙ',
  'ᾒ' => 'ἪΙ',
  'ᾓ' => 'ἫΙ',
  'ᾔ' => 'ἬΙ',
  'ᾕ' => 'ἭΙ',
  'ᾖ' => 'ἮΙ',
  'ᾗ' => 'ἯΙ',
  'ᾠ' => 'ὨΙ',
  'ᾡ' => 'ὩΙ',
  'ᾢ' => 'ὪΙ',
  'ᾣ' => 'ὫΙ',
  'ᾤ' => 'ὬΙ',
  'ᾥ' => 'ὭΙ',
  'ᾦ' => 'ὮΙ',
  'ᾧ' => 'ὯΙ',
  'ᾰ' => 'Ᾰ',
  'ᾱ' => 'Ᾱ',
  'ᾳ' => 'ΑΙ',
  'ι' => 'Ι',
  'ῃ' => 'ΗΙ',
  'ῐ' => 'Ῐ',
  'ῑ' => 'Ῑ',
  'ῠ' => 'Ῠ',
  'ῡ' => 'Ῡ',
  'ῥ' => 'Ῥ',
  'ῳ' => 'ΩΙ',
  'ⅎ' => 'Ⅎ',
  'ⅰ' => 'Ⅰ',
  'ⅱ' => 'Ⅱ',
  'ⅲ' => 'Ⅲ',
  'ⅳ' => 'Ⅳ',
  'ⅴ' => 'Ⅴ',
  'ⅵ' => 'Ⅵ',
  'ⅶ' => 'Ⅶ',
  'ⅷ' => 'Ⅷ',
  'ⅸ' => 'Ⅸ',
  'ⅹ' => 'Ⅹ',
  'ⅺ' => 'Ⅺ',
  'ⅻ' => 'Ⅻ',
  'ⅼ' => 'Ⅼ',
  'ⅽ' => 'Ⅽ',
  'ⅾ' => 'Ⅾ',
  'ⅿ' => 'Ⅿ',
  'ↄ' => 'Ↄ',
  'ⓐ' => 'Ⓐ',
  'ⓑ' => 'Ⓑ',
  'ⓒ' => 'Ⓒ',
  'ⓓ' => 'Ⓓ',
  'ⓔ' => 'Ⓔ',
  'ⓕ' => 'Ⓕ',
  'ⓖ' => 'Ⓖ',
  'ⓗ' => 'Ⓗ',
  'ⓘ' => 'Ⓘ',
  'ⓙ' => 'Ⓙ',
  'ⓚ' => 'Ⓚ',
  'ⓛ' => 'Ⓛ',
  'ⓜ' => 'Ⓜ',
  'ⓝ' => 'Ⓝ',
  'ⓞ' => 'Ⓞ',
  'ⓟ' => 'Ⓟ',
  'ⓠ' => 'Ⓠ',
  'ⓡ' => 'Ⓡ',
  'ⓢ' => 'Ⓢ',
  'ⓣ' => 'Ⓣ',
  'ⓤ' => 'Ⓤ',
  'ⓥ' => 'Ⓥ',
  'ⓦ' => 'Ⓦ',
  'ⓧ' => 'Ⓧ',
  'ⓨ' => 'Ⓨ',
  'ⓩ' => 'Ⓩ',
  'ⰰ' => 'Ⰰ',
  'ⰱ' => 'Ⰱ',
  'ⰲ' => 'Ⰲ',
  'ⰳ' => 'Ⰳ',
  'ⰴ' => 'Ⰴ',
  'ⰵ' => 'Ⰵ',
  'ⰶ' => 'Ⰶ',
  'ⰷ' => 'Ⰷ',
  'ⰸ' => 'Ⰸ',
  'ⰹ' => 'Ⰹ',
  'ⰺ' => 'Ⰺ',
  'ⰻ' => 'Ⰻ',
  'ⰼ' => 'Ⰼ',
  'ⰽ' => 'Ⰽ',
  'ⰾ' => 'Ⰾ',
  'ⰿ' => 'Ⰿ',
  'ⱀ' => 'Ⱀ',
  'ⱁ' => 'Ⱁ',
  'ⱂ' => 'Ⱂ',
  'ⱃ' => 'Ⱃ',
  'ⱄ' => 'Ⱄ',
  'ⱅ' => 'Ⱅ',
  'ⱆ' => 'Ⱆ',
  'ⱇ' => 'Ⱇ',
  'ⱈ' => 'Ⱈ',
  'ⱉ' => 'Ⱉ',
  'ⱊ' => 'Ⱊ',
  'ⱋ' => 'Ⱋ',
  'ⱌ' => 'Ⱌ',
  'ⱍ' => 'Ⱍ',
  'ⱎ' => 'Ⱎ',
  'ⱏ' => 'Ⱏ',
  'ⱐ' => 'Ⱐ',
  'ⱑ' => 'Ⱑ',
  'ⱒ' => 'Ⱒ',
  'ⱓ' => 'Ⱓ',
  'ⱔ' => 'Ⱔ',
  'ⱕ' => 'Ⱕ',
  'ⱖ' => 'Ⱖ',
  'ⱗ' => 'Ⱗ',
  'ⱘ' => 'Ⱘ',
  'ⱙ' => 'Ⱙ',
  'ⱚ' => 'Ⱚ',
  'ⱛ' => 'Ⱛ',
  'ⱜ' => 'Ⱜ',
  'ⱝ' => 'Ⱝ',
  'ⱞ' => 'Ⱞ',
  'ⱡ' => 'Ⱡ',
  'ⱥ' => 'Ⱥ',
  'ⱦ' => 'Ⱦ',
  'ⱨ' => 'Ⱨ',
  'ⱪ' => 'Ⱪ',
  'ⱬ' => 'Ⱬ',
  'ⱳ' => 'Ⱳ',
  'ⱶ' => 'Ⱶ',
  'ⲁ' => 'Ⲁ',
  'ⲃ' => 'Ⲃ',
  'ⲅ' => 'Ⲅ',
  'ⲇ' => 'Ⲇ',
  'ⲉ' => 'Ⲉ',
  'ⲋ' => 'Ⲋ',
  'ⲍ' => 'Ⲍ',
  'ⲏ' => 'Ⲏ',
  'ⲑ' => 'Ⲑ',
  'ⲓ' => 'Ⲓ',
  'ⲕ' => 'Ⲕ',
  'ⲗ' => 'Ⲗ',
  'ⲙ' => 'Ⲙ',
  'ⲛ' => 'Ⲛ',
  'ⲝ' => 'Ⲝ',
  'ⲟ' => 'Ⲟ',
  'ⲡ' => 'Ⲡ',
  'ⲣ' => 'Ⲣ',
  'ⲥ' => 'Ⲥ',
  'ⲧ' => 'Ⲧ',
  'ⲩ' => 'Ⲩ',
  'ⲫ' => 'Ⲫ',
  'ⲭ' => 'Ⲭ',
  'ⲯ' => 'Ⲯ',
  'ⲱ' => 'Ⲱ',
  'ⲳ' => 'Ⲳ',
  'ⲵ' => 'Ⲵ',
  'ⲷ' => 'Ⲷ',
  'ⲹ' => 'Ⲹ',
  'ⲻ' => 'Ⲻ',
  'ⲽ' => 'Ⲽ',
  'ⲿ' => 'Ⲿ',
  'ⳁ' => 'Ⳁ',
  'ⳃ' => 'Ⳃ',
  'ⳅ' => 'Ⳅ',
  'ⳇ' => 'Ⳇ',
  'ⳉ' => 'Ⳉ',
  'ⳋ' => 'Ⳋ',
  'ⳍ' => 'Ⳍ',
  'ⳏ' => 'Ⳏ',
  'ⳑ' => 'Ⳑ',
  'ⳓ' => 'Ⳓ',
  'ⳕ' => 'Ⳕ',
  'ⳗ' => 'Ⳗ',
  'ⳙ' => 'Ⳙ',
  'ⳛ' => 'Ⳛ',
  'ⳝ' => 'Ⳝ',
  'ⳟ' => 'Ⳟ',
  'ⳡ' => 'Ⳡ',
  'ⳣ' => 'Ⳣ',
  'ⳬ' => 'Ⳬ',
  'ⳮ' => 'Ⳮ',
  'ⳳ' => 'Ⳳ',
  'ⴀ' => 'Ⴀ',
  'ⴁ' => 'Ⴁ',
  'ⴂ' => 'Ⴂ',
  'ⴃ' => 'Ⴃ',
  'ⴄ' => 'Ⴄ',
  'ⴅ' => 'Ⴅ',
  'ⴆ' => 'Ⴆ',
  'ⴇ' => 'Ⴇ',
  'ⴈ' => 'Ⴈ',
  'ⴉ' => 'Ⴉ',
  'ⴊ' => 'Ⴊ',
  'ⴋ' => 'Ⴋ',
  'ⴌ' => 'Ⴌ',
  'ⴍ' => 'Ⴍ',
  'ⴎ' => 'Ⴎ',
  'ⴏ' => 'Ⴏ',
  'ⴐ' => 'Ⴐ',
  'ⴑ' => 'Ⴑ',
  'ⴒ' => 'Ⴒ',
  'ⴓ' => 'Ⴓ',
  'ⴔ' => 'Ⴔ',
  'ⴕ' => 'Ⴕ',
  'ⴖ' => 'Ⴖ',
  'ⴗ' => 'Ⴗ',
  'ⴘ' => 'Ⴘ',
  'ⴙ' => 'Ⴙ',
  'ⴚ' => 'Ⴚ',
  'ⴛ' => 'Ⴛ',
  'ⴜ' => 'Ⴜ',
  'ⴝ' => 'Ⴝ',
  'ⴞ' => 'Ⴞ',
  'ⴟ' => 'Ⴟ',
  'ⴠ' => 'Ⴠ',
  'ⴡ' => 'Ⴡ',
  'ⴢ' => 'Ⴢ',
  'ⴣ' => 'Ⴣ',
  'ⴤ' => 'Ⴤ',
  'ⴥ' => 'Ⴥ',
  'ⴧ' => 'Ⴧ',
  'ⴭ' => 'Ⴭ',
  'ꙁ' => 'Ꙁ',
  'ꙃ' => 'Ꙃ',
  'ꙅ' => 'Ꙅ',
  'ꙇ' => 'Ꙇ',
  'ꙉ' => 'Ꙉ',
  'ꙋ' => 'Ꙋ',
  'ꙍ' => 'Ꙍ',
  'ꙏ' => 'Ꙏ',
  'ꙑ' => 'Ꙑ',
  'ꙓ' => 'Ꙓ',
  'ꙕ' => 'Ꙕ',
  'ꙗ' => 'Ꙗ',
  'ꙙ' => 'Ꙙ',
  'ꙛ' => 'Ꙛ',
  'ꙝ' => 'Ꙝ',
  'ꙟ' => 'Ꙟ',
  'ꙡ' => 'Ꙡ',
  'ꙣ' => 'Ꙣ',
  'ꙥ' => 'Ꙥ',
  'ꙧ' => 'Ꙧ',
  'ꙩ' => 'Ꙩ',
  'ꙫ' => 'Ꙫ',
  'ꙭ' => 'Ꙭ',
  'ꚁ' => 'Ꚁ',
  'ꚃ' => 'Ꚃ',
  'ꚅ' => 'Ꚅ',
  'ꚇ' => 'Ꚇ',
  'ꚉ' => 'Ꚉ',
  'ꚋ' => 'Ꚋ',
  'ꚍ' => 'Ꚍ',
  'ꚏ' => 'Ꚏ',
  'ꚑ' => 'Ꚑ',
  'ꚓ' => 'Ꚓ',
  'ꚕ' => 'Ꚕ',
  'ꚗ' => 'Ꚗ',
  'ꚙ' => 'Ꚙ',
  'ꚛ' => 'Ꚛ',
  'ꜣ' => 'Ꜣ',
  'ꜥ' => 'Ꜥ',
  'ꜧ' => 'Ꜧ',
  'ꜩ' => 'Ꜩ',
  'ꜫ' => 'Ꜫ',
  'ꜭ' => 'Ꜭ',
  'ꜯ' => 'Ꜯ',
  'ꜳ' => 'Ꜳ',
  'ꜵ' => 'Ꜵ',
  'ꜷ' => 'Ꜷ',
  'ꜹ' => 'Ꜹ',
  'ꜻ' => 'Ꜻ',
  'ꜽ' => 'Ꜽ',
  'ꜿ' => 'Ꜿ',
  'ꝁ' => 'Ꝁ',
  'ꝃ' => 'Ꝃ',
  'ꝅ' => 'Ꝅ',
  'ꝇ' => 'Ꝇ',
  'ꝉ' => 'Ꝉ',
  'ꝋ' => 'Ꝋ',
  'ꝍ' => 'Ꝍ',
  'ꝏ' => 'Ꝏ',
  'ꝑ' => 'Ꝑ',
  'ꝓ' => 'Ꝓ',
  'ꝕ' => 'Ꝕ',
  'ꝗ' => 'Ꝗ',
  'ꝙ' => 'Ꝙ',
  'ꝛ' => 'Ꝛ',
  'ꝝ' => 'Ꝝ',
  'ꝟ' => 'Ꝟ',
  'ꝡ' => 'Ꝡ',
  'ꝣ' => 'Ꝣ',
  'ꝥ' => 'Ꝥ',
  'ꝧ' => 'Ꝧ',
  'ꝩ' => 'Ꝩ',
  'ꝫ' => 'Ꝫ',
  'ꝭ' => 'Ꝭ',
  'ꝯ' => 'Ꝯ',
  'ꝺ' => 'Ꝺ',
  'ꝼ' => 'Ꝼ',
  'ꝿ' => 'Ꝿ',
  'ꞁ' => 'Ꞁ',
  'ꞃ' => 'Ꞃ',
  'ꞅ' => 'Ꞅ',
  'ꞇ' => 'Ꞇ',
  'ꞌ' => 'Ꞌ',
  'ꞑ' => 'Ꞑ',
  'ꞓ' => 'Ꞓ',
  'ꞔ' => 'Ꞔ',
  'ꞗ' => 'Ꞗ',
  'ꞙ' => 'Ꞙ',
  'ꞛ' => 'Ꞛ',
  'ꞝ' => 'Ꞝ',
  'ꞟ' => 'Ꞟ',
  'ꞡ' => 'Ꞡ',
  'ꞣ' => 'Ꞣ',
  'ꞥ' => 'Ꞥ',
  'ꞧ' => 'Ꞧ',
  'ꞩ' => 'Ꞩ',
  'ꞵ' => 'Ꞵ',
  'ꞷ' => 'Ꞷ',
  'ꞹ' => 'Ꞹ',
  'ꞻ' => 'Ꞻ',
  'ꞽ' => 'Ꞽ',
  'ꞿ' => 'Ꞿ',
  'ꟃ' => 'Ꟃ',
  'ꟈ' => 'Ꟈ',
  'ꟊ' => 'Ꟊ',
  'ꟶ' => 'Ꟶ',
  'ꭓ' => 'Ꭓ',
  'ꭰ' => 'Ꭰ',
  'ꭱ' => 'Ꭱ',
  'ꭲ' => 'Ꭲ',
  'ꭳ' => 'Ꭳ',
  'ꭴ' => 'Ꭴ',
  'ꭵ' => 'Ꭵ',
  'ꭶ' => 'Ꭶ',
  'ꭷ' => 'Ꭷ',
  'ꭸ' => 'Ꭸ',
  'ꭹ' => 'Ꭹ',
  'ꭺ' => 'Ꭺ',
  'ꭻ' => 'Ꭻ',
  'ꭼ' => 'Ꭼ',
  'ꭽ' => 'Ꭽ',
  'ꭾ' => 'Ꭾ',
  'ꭿ' => 'Ꭿ',
  'ꮀ' => 'Ꮀ',
  'ꮁ' => 'Ꮁ',
  'ꮂ' => 'Ꮂ',
  'ꮃ' => 'Ꮃ',
  'ꮄ' => 'Ꮄ',
  'ꮅ' => 'Ꮅ',
  'ꮆ' => 'Ꮆ',
  'ꮇ' => 'Ꮇ',
  'ꮈ' => 'Ꮈ',
  'ꮉ' => 'Ꮉ',
  'ꮊ' => 'Ꮊ',
  'ꮋ' => 'Ꮋ',
  'ꮌ' => 'Ꮌ',
  'ꮍ' => 'Ꮍ',
  'ꮎ' => 'Ꮎ',
  'ꮏ' => 'Ꮏ',
  'ꮐ' => 'Ꮐ',
  'ꮑ' => 'Ꮑ',
  'ꮒ' => 'Ꮒ',
  'ꮓ' => 'Ꮓ',
  'ꮔ' => 'Ꮔ',
  'ꮕ' => 'Ꮕ',
  'ꮖ' => 'Ꮖ',
  'ꮗ' => 'Ꮗ',
  'ꮘ' => 'Ꮘ',
  'ꮙ' => 'Ꮙ',
  'ꮚ' => 'Ꮚ',
  'ꮛ' => 'Ꮛ',
  'ꮜ' => 'Ꮜ',
  'ꮝ' => 'Ꮝ',
  'ꮞ' => 'Ꮞ',
  'ꮟ' => 'Ꮟ',
  'ꮠ' => 'Ꮠ',
  'ꮡ' => 'Ꮡ',
  'ꮢ' => 'Ꮢ',
  'ꮣ' => 'Ꮣ',
  'ꮤ' => 'Ꮤ',
  'ꮥ' => 'Ꮥ',
  'ꮦ' => 'Ꮦ',
  'ꮧ' => 'Ꮧ',
  'ꮨ' => 'Ꮨ',
  'ꮩ' => 'Ꮩ',
  'ꮪ' => 'Ꮪ',
  'ꮫ' => 'Ꮫ',
  'ꮬ' => 'Ꮬ',
  'ꮭ' => 'Ꮭ',
  'ꮮ' => 'Ꮮ',
  'ꮯ' => 'Ꮯ',
  'ꮰ' => 'Ꮰ',
  'ꮱ' => 'Ꮱ',
  'ꮲ' => 'Ꮲ',
  'ꮳ' => 'Ꮳ',
  'ꮴ' => 'Ꮴ',
  'ꮵ' => 'Ꮵ',
  'ꮶ' => 'Ꮶ',
  'ꮷ' => 'Ꮷ',
  'ꮸ' => 'Ꮸ',
  'ꮹ' => 'Ꮹ',
  'ꮺ' => 'Ꮺ',
  'ꮻ' => 'Ꮻ',
  'ꮼ' => 'Ꮼ',
  'ꮽ' => 'Ꮽ',
  'ꮾ' => 'Ꮾ',
  'ꮿ' => 'Ꮿ',
  'ａ' => 'Ａ',
  'ｂ' => 'Ｂ',
  'ｃ' => 'Ｃ',
  'ｄ' => 'Ｄ',
  'ｅ' => 'Ｅ',
  'ｆ' => 'Ｆ',
  'ｇ' => 'Ｇ',
  'ｈ' => 'Ｈ',
  'ｉ' => 'Ｉ',
  'ｊ' => 'Ｊ',
  'ｋ' => 'Ｋ',
  'ｌ' => 'Ｌ',
  'ｍ' => 'Ｍ',
  'ｎ' => 'Ｎ',
  'ｏ' => 'Ｏ',
  'ｐ' => 'Ｐ',
  'ｑ' => 'Ｑ',
  'ｒ' => 'Ｒ',
  'ｓ' => 'Ｓ',
  'ｔ' => 'Ｔ',
  'ｕ' => 'Ｕ',
  'ｖ' => 'Ｖ',
  'ｗ' => 'Ｗ',
  'ｘ' => 'Ｘ',
  'ｙ' => 'Ｙ',
  'ｚ' => 'Ｚ',
  '𐐨' => '𐐀',
  '𐐩' => '𐐁',
  '𐐪' => '𐐂',
  '𐐫' => '𐐃',
  '𐐬' => '𐐄',
  '𐐭' => '𐐅',
  '𐐮' => '𐐆',
  '𐐯' => '𐐇',
  '𐐰' => '𐐈',
  '𐐱' => '𐐉',
  '𐐲' => '𐐊',
  '𐐳' => '𐐋',
  '𐐴' => '𐐌',
  '𐐵' => '𐐍',
  '𐐶' => '𐐎',
  '𐐷' => '𐐏',
  '𐐸' => '𐐐',
  '𐐹' => '𐐑',
  '𐐺' => '𐐒',
  '𐐻' => '𐐓',
  '𐐼' => '𐐔',
  '𐐽' => '𐐕',
  '𐐾' => '𐐖',
  '𐐿' => '𐐗',
  '𐑀' => '𐐘',
  '𐑁' => '𐐙',
  '𐑂' => '𐐚',
  '𐑃' => '𐐛',
  '𐑄' => '𐐜',
  '𐑅' => '𐐝',
  '𐑆' => '𐐞',
  '𐑇' => '𐐟',
  '𐑈' => '𐐠',
  '𐑉' => '𐐡',
  '𐑊' => '𐐢',
  '𐑋' => '𐐣',
  '𐑌' => '𐐤',
  '𐑍' => '𐐥',
  '𐑎' => '𐐦',
  '𐑏' => '𐐧',
  '𐓘' => '𐒰',
  '𐓙' => '𐒱',
  '𐓚' => '𐒲',
  '𐓛' => '𐒳',
  '𐓜' => '𐒴',
  '𐓝' => '𐒵',
  '𐓞' => '𐒶',
  '𐓟' => '𐒷',
  '𐓠' => '𐒸',
  '𐓡' => '𐒹',
  '𐓢' => '𐒺',
  '𐓣' => '𐒻',
  '𐓤' => '𐒼',
  '𐓥' => '𐒽',
  '𐓦' => '𐒾',
  '𐓧' => '𐒿',
  '𐓨' => '𐓀',
  '𐓩' => '𐓁',
  '𐓪' => '𐓂',
  '𐓫' => '𐓃',
  '𐓬' => '𐓄',
  '𐓭' => '𐓅',
  '𐓮' => '𐓆',
  '𐓯' => '𐓇',
  '𐓰' => '𐓈',
  '𐓱' => '𐓉',
  '𐓲' => '𐓊',
  '𐓳' => '𐓋',
  '𐓴' => '𐓌',
  '𐓵' => '𐓍',
  '𐓶' => '𐓎',
  '𐓷' => '𐓏',
  '𐓸' => '𐓐',
  '𐓹' => '𐓑',
  '𐓺' => '𐓒',
  '𐓻' => '𐓓',
  '𐳀' => '𐲀',
  '𐳁' => '𐲁',
  '𐳂' => '𐲂',
  '𐳃' => '𐲃',
  '𐳄' => '𐲄',
  '𐳅' => '𐲅',
  '𐳆' => '𐲆',
  '𐳇' => '𐲇',
  '𐳈' => '𐲈',
  '𐳉' => '𐲉',
  '𐳊' => '𐲊',
  '𐳋' => '𐲋',
  '𐳌' => '𐲌',
  '𐳍' => '𐲍',
  '𐳎' => '𐲎',
  '𐳏' => '𐲏',
  '𐳐' => '𐲐',
  '𐳑' => '𐲑',
  '𐳒' => '𐲒',
  '𐳓' => '𐲓',
  '𐳔' => '𐲔',
  '𐳕' => '𐲕',
  '𐳖' => '𐲖',
  '𐳗' => '𐲗',
  '𐳘' => '𐲘',
  '𐳙' => '𐲙',
  '𐳚' => '𐲚',
  '𐳛' => '𐲛',
  '𐳜' => '𐲜',
  '𐳝' => '𐲝',
  '𐳞' => '𐲞',
  '𐳟' => '𐲟',
  '𐳠' => '𐲠',
  '𐳡' => '𐲡',
  '𐳢' => '𐲢',
  '𐳣' => '𐲣',
  '𐳤' => '𐲤',
  '𐳥' => '𐲥',
  '𐳦' => '𐲦',
  '𐳧' => '𐲧',
  '𐳨' => '𐲨',
  '𐳩' => '𐲩',
  '𐳪' => '𐲪',
  '𐳫' => '𐲫',
  '𐳬' => '𐲬',
  '𐳭' => '𐲭',
  '𐳮' => '𐲮',
  '𐳯' => '𐲯',
  '𐳰' => '𐲰',
  '𐳱' => '𐲱',
  '𐳲' => '𐲲',
  '𑣀' => '𑢠',
  '𑣁' => '𑢡',
  '𑣂' => '𑢢',
  '𑣃' => '𑢣',
  '𑣄' => '𑢤',
  '𑣅' => '𑢥',
  '𑣆' => '𑢦',
  '𑣇' => '𑢧',
  '𑣈' => '𑢨',
  '𑣉' => '𑢩',
  '𑣊' => '𑢪',
  '𑣋' => '𑢫',
  '𑣌' => '𑢬',
  '𑣍' => '𑢭',
  '𑣎' => '𑢮',
  '𑣏' => '𑢯',
  '𑣐' => '𑢰',
  '𑣑' => '𑢱',
  '𑣒' => '𑢲',
  '𑣓' => '𑢳',
  '𑣔' => '𑢴',
  '𑣕' => '𑢵',
  '𑣖' => '𑢶',
  '𑣗' => '𑢷',
  '𑣘' => '𑢸',
  '𑣙' => '𑢹',
  '𑣚' => '𑢺',
  '𑣛' => '𑢻',
  '𑣜' => '𑢼',
  '𑣝' => '𑢽',
  '𑣞' => '𑢾',
  '𑣟' => '𑢿',
  '𖹠' => '𖹀',
  '𖹡' => '𖹁',
  '𖹢' => '𖹂',
  '𖹣' => '𖹃',
  '𖹤' => '𖹄',
  '𖹥' => '𖹅',
  '𖹦' => '𖹆',
  '𖹧' => '𖹇',
  '𖹨' => '𖹈',
  '𖹩' => '𖹉',
  '𖹪' => '𖹊',
  '𖹫' => '𖹋',
  '𖹬' => '𖹌',
  '𖹭' => '𖹍',
  '𖹮' => '𖹎',
  '𖹯' => '𖹏',
  '𖹰' => '𖹐',
  '𖹱' => '𖹑',
  '𖹲' => '𖹒',
  '𖹳' => '𖹓',
  '𖹴' => '𖹔',
  '𖹵' => '𖹕',
  '𖹶' => '𖹖',
  '𖹷' => '𖹗',
  '𖹸' => '𖹘',
  '𖹹' => '𖹙',
  '𖹺' => '𖹚',
  '𖹻' => '𖹛',
  '𖹼' => '𖹜',
  '𖹽' => '𖹝',
  '𖹾' => '𖹞',
  '𖹿' => '𖹟',
  '𞤢' => '𞤀',
  '𞤣' => '𞤁',
  '𞤤' => '𞤂',
  '𞤥' => '𞤃',
  '𞤦' => '𞤄',
  '𞤧' => '𞤅',
  '𞤨' => '𞤆',
  '𞤩' => '𞤇',
  '𞤪' => '𞤈',
  '𞤫' => '𞤉',
  '𞤬' => '𞤊',
  '𞤭' => '𞤋',
  '𞤮' => '𞤌',
  '𞤯' => '𞤍',
  '𞤰' => '𞤎',
  '𞤱' => '𞤏',
  '𞤲' => '𞤐',
  '𞤳' => '𞤑',
  '𞤴' => '𞤒',
  '𞤵' => '𞤓',
  '𞤶' => '𞤔',
  '𞤷' => '𞤕',
  '𞤸' => '𞤖',
  '𞤹' => '𞤗',
  '𞤺' => '𞤘',
  '𞤻' => '𞤙',
  '𞤼' => '𞤚',
  '𞤽' => '𞤛',
  '𞤾' => '𞤜',
  '𞤿' => '𞤝',
  '𞥀' => '𞤞',
  '𞥁' => '𞤟',
  '𞥂' => '𞤠',
  '𞥃' => '𞤡',
  'ß' => 'SS',
  'ﬀ' => 'FF',
  'ﬁ' => 'FI',
  'ﬂ' => 'FL',
  'ﬃ' => 'FFI',
  'ﬄ' => 'FFL',
  'ﬅ' => 'ST',
  'ﬆ' => 'ST',
  'և' => 'ԵՒ',
  'ﬓ' => 'ՄՆ',
  'ﬔ' => 'ՄԵ',
  'ﬕ' => 'ՄԻ',
  'ﬖ' => 'ՎՆ',
  'ﬗ' => 'ՄԽ',
  'ŉ' => 'ʼN',
  'ΐ' => 'Ϊ́',
  'ΰ' => 'Ϋ́',
  'ǰ' => 'J̌',
  'ẖ' => 'H̱',
  'ẗ' => 'T̈',
  'ẘ' => 'W̊',
  'ẙ' => 'Y̊',
  'ẚ' => 'Aʾ',
  'ὐ' => 'Υ̓',
  'ὒ' => 'Υ̓̀',
  'ὔ' => 'Υ̓́',
  'ὖ' => 'Υ̓͂',
  'ᾶ' => 'Α͂',
  'ῆ' => 'Η͂',
  'ῒ' => 'Ϊ̀',
  'ΐ' => 'Ϊ́',
  'ῖ' => 'Ι͂',
  'ῗ' => 'Ϊ͂',
  'ῢ' => 'Ϋ̀',
  'ΰ' => 'Ϋ́',
  'ῤ' => 'Ρ̓',
  'ῦ' => 'Υ͂',
  'ῧ' => 'Ϋ͂',
  'ῶ' => 'Ω͂',
  'ᾈ' => 'ἈΙ',
  'ᾉ' => 'ἉΙ',
  'ᾊ' => 'ἊΙ',
  'ᾋ' => 'ἋΙ',
  'ᾌ' => 'ἌΙ',
  'ᾍ' => 'ἍΙ',
  'ᾎ' => 'ἎΙ',
  'ᾏ' => 'ἏΙ',
  'ᾘ' => 'ἨΙ',
  'ᾙ' => 'ἩΙ',
  'ᾚ' => 'ἪΙ',
  'ᾛ' => 'ἫΙ',
  'ᾜ' => 'ἬΙ',
  'ᾝ' => 'ἭΙ',
  'ᾞ' => 'ἮΙ',
  'ᾟ' => 'ἯΙ',
  'ᾨ' => 'ὨΙ',
  'ᾩ' => 'ὩΙ',
  'ᾪ' => 'ὪΙ',
  'ᾫ' => 'ὫΙ',
  'ᾬ' => 'ὬΙ',
  'ᾭ' => 'ὭΙ',
  'ᾮ' => 'ὮΙ',
  'ᾯ' => 'ὯΙ',
  'ᾼ' => 'ΑΙ',
  'ῌ' => 'ΗΙ',
  'ῼ' => 'ΩΙ',
  'ᾲ' => 'ᾺΙ',
  'ᾴ' => 'ΆΙ',
  'ῂ' => 'ῊΙ',
  'ῄ' => 'ΉΙ',
  'ῲ' => 'ῺΙ',
  'ῴ' => 'ΏΙ',
  'ᾷ' => 'Α͂Ι',
  'ῇ' => 'Η͂Ι',
  'ῷ' => 'Ω͂Ι',
);
<?php

// from Case_Ignorable in https://unicode.org/Public/UNIDATA/DerivedCoreProperties.txt

return '/(?<![\x{0027}\x{002E}\x{003A}\x{005E}\x{0060}\x{00A8}\x{00AD}\x{00AF}\x{00B4}\x{00B7}\x{00B8}\x{02B0}-\x{02C1}\x{02C2}-\x{02C5}\x{02C6}-\x{02D1}\x{02D2}-\x{02DF}\x{02E0}-\x{02E4}\x{02E5}-\x{02EB}\x{02EC}\x{02ED}\x{02EE}\x{02EF}-\x{02FF}\x{0300}-\x{036F}\x{0374}\x{0375}\x{037A}\x{0384}-\x{0385}\x{0387}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{0559}\x{0591}-\x{05BD}\x{05BF}\x{05C1}-\x{05C2}\x{05C4}-\x{05C5}\x{05C7}\x{05F4}\x{0600}-\x{0605}\x{0610}-\x{061A}\x{061C}\x{0640}\x{064B}-\x{065F}\x{0670}\x{06D6}-\x{06DC}\x{06DD}\x{06DF}-\x{06E4}\x{06E5}-\x{06E6}\x{06E7}-\x{06E8}\x{06EA}-\x{06ED}\x{070F}\x{0711}\x{0730}-\x{074A}\x{07A6}-\x{07B0}\x{07EB}-\x{07F3}\x{07F4}-\x{07F5}\x{07FA}\x{07FD}\x{0816}-\x{0819}\x{081A}\x{081B}-\x{0823}\x{0824}\x{0825}-\x{0827}\x{0828}\x{0829}-\x{082D}\x{0859}-\x{085B}\x{08D3}-\x{08E1}\x{08E2}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0971}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0C00}\x{0C04}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C81}\x{0CBC}\x{0CBF}\x{0CC6}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E46}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EB9}\x{0EBB}-\x{0EBC}\x{0EC6}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{10FC}\x{135D}-\x{135F}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17D7}\x{17DD}\x{180B}-\x{180D}\x{180E}\x{1843}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AA7}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1C78}-\x{1C7D}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1D2C}-\x{1D6A}\x{1D78}\x{1D9B}-\x{1DBF}\x{1DC0}-\x{1DF9}\x{1DFB}-\x{1DFF}\x{1FBD}\x{1FBF}-\x{1FC1}\x{1FCD}-\x{1FCF}\x{1FDD}-\x{1FDF}\x{1FED}-\x{1FEF}\x{1FFD}-\x{1FFE}\x{200B}-\x{200F}\x{2018}\x{2019}\x{2024}\x{2027}\x{202A}-\x{202E}\x{2060}-\x{2064}\x{2066}-\x{206F}\x{2071}\x{207F}\x{2090}-\x{209C}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2C7C}-\x{2C7D}\x{2CEF}-\x{2CF1}\x{2D6F}\x{2D7F}\x{2DE0}-\x{2DFF}\x{2E2F}\x{3005}\x{302A}-\x{302D}\x{3031}-\x{3035}\x{303B}\x{3099}-\x{309A}\x{309B}-\x{309C}\x{309D}-\x{309E}\x{30FC}-\x{30FE}\x{A015}\x{A4F8}-\x{A4FD}\x{A60C}\x{A66F}\x{A670}-\x{A672}\x{A674}-\x{A67D}\x{A67F}\x{A69C}-\x{A69D}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A700}-\x{A716}\x{A717}-\x{A71F}\x{A720}-\x{A721}\x{A770}\x{A788}\x{A789}-\x{A78A}\x{A7F8}-\x{A7F9}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}\x{A9CF}\x{A9E5}\x{A9E6}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA70}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AADD}\x{AAEC}-\x{AAED}\x{AAF3}-\x{AAF4}\x{AAF6}\x{AB5B}\x{AB5C}-\x{AB5F}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1E}\x{FBB2}-\x{FBC1}\x{FE00}-\x{FE0F}\x{FE13}\x{FE20}-\x{FE2F}\x{FE52}\x{FE55}\x{FEFF}\x{FF07}\x{FF0E}\x{FF1A}\x{FF3E}\x{FF40}\x{FF70}\x{FF9E}-\x{FF9F}\x{FFE3}\x{FFF9}-\x{FFFB}\x{101FD}\x{102E0}\x{10376}-\x{1037A}\x{10A01}-\x{10A03}\x{10A05}-\x{10A06}\x{10A0C}-\x{10A0F}\x{10A38}-\x{10A3A}\x{10A3F}\x{10AE5}-\x{10AE6}\x{10D24}-\x{10D27}\x{10F46}-\x{10F50}\x{11001}\x{11038}-\x{11046}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{110BD}\x{110CD}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{11A01}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C3F}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16B40}-\x{16B43}\x{16F8F}-\x{16F92}\x{16F93}-\x{16F9F}\x{16FE0}-\x{16FE1}\x{1BC9D}-\x{1BC9E}\x{1BCA0}-\x{1BCA3}\x{1D167}-\x{1D169}\x{1D173}-\x{1D17A}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D242}-\x{1D244}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E8D0}-\x{1E8D6}\x{1E944}-\x{1E94A}\x{1F3FB}-\x{1F3FF}\x{E0001}\x{E0020}-\x{E007F}\x{E0100}-\x{E01EF}])(\pL)(\pL*+)/u';
Copyright (c) 2015-present Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Polyfill\Mbstring;

/**
 * Partial mbstring implementation in PHP, iconv based, UTF-8 centric.
 *
 * Implemented:
 * - mb_chr                  - Returns a specific character from its Unicode code point
 * - mb_convert_encoding     - Convert character encoding
 * - mb_convert_variables    - Convert character code in variable(s)
 * - mb_decode_mimeheader    - Decode string in MIME header field
 * - mb_encode_mimeheader    - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED
 * - mb_decode_numericentity - Decode HTML numeric string reference to character
 * - mb_encode_numericentity - Encode character to HTML numeric string reference
 * - mb_convert_case         - Perform case folding on a string
 * - mb_detect_encoding      - Detect character encoding
 * - mb_get_info             - Get internal settings of mbstring
 * - mb_http_input           - Detect HTTP input character encoding
 * - mb_http_output          - Set/Get HTTP output character encoding
 * - mb_internal_encoding    - Set/Get internal character encoding
 * - mb_list_encodings       - Returns an array of all supported encodings
 * - mb_ord                  - Returns the Unicode code point of a character
 * - mb_output_handler       - Callback function converts character encoding in output buffer
 * - mb_scrub                - Replaces ill-formed byte sequences with substitute characters
 * - mb_strlen               - Get string length
 * - mb_strpos               - Find position of first occurrence of string in a string
 * - mb_strrpos              - Find position of last occurrence of a string in a string
 * - mb_str_split            - Convert a string to an array
 * - mb_strtolower           - Make a string lowercase
 * - mb_strtoupper           - Make a string uppercase
 * - mb_substitute_character - Set/Get substitution character
 * - mb_substr               - Get part of string
 * - mb_stripos              - Finds position of first occurrence of a string within another, case insensitive
 * - mb_stristr              - Finds first occurrence of a string within another, case insensitive
 * - mb_strrchr              - Finds the last occurrence of a character in a string within another
 * - mb_strrichr             - Finds the last occurrence of a character in a string within another, case insensitive
 * - mb_strripos             - Finds position of last occurrence of a string within another, case insensitive
 * - mb_strstr               - Finds first occurrence of a string within another
 * - mb_strwidth             - Return width of string
 * - mb_substr_count         - Count the number of substring occurrences
 * - mb_ucfirst              - Make a string's first character uppercase
 * - mb_lcfirst              - Make a string's first character lowercase
 * - mb_trim                 - Strip whitespace (or other characters) from the beginning and end of a string
 * - mb_ltrim                - Strip whitespace (or other characters) from the beginning of a string
 * - mb_rtrim                - Strip whitespace (or other characters) from the end of a string
 *
 * Not implemented:
 * - mb_convert_kana         - Convert "kana" one from another ("zen-kaku", "han-kaku" and more)
 * - mb_ereg_*               - Regular expression with multibyte support
 * - mb_parse_str            - Parse GET/POST/COOKIE data and set global variable
 * - mb_preferred_mime_name  - Get MIME charset string
 * - mb_regex_encoding       - Returns current encoding for multibyte regex as string
 * - mb_regex_set_options    - Set/Get the default options for mbregex functions
 * - mb_send_mail            - Send encoded mail
 * - mb_split                - Split multibyte string using regular expression
 * - mb_strcut               - Get part of string
 * - mb_strimwidth           - Get truncated string with specified width
 *
 * @author Nicolas Grekas <p@tchwork.com>
 *
 * @internal
 */
final class Mbstring
{
    public const MB_CASE_FOLD = \PHP_INT_MAX;

    private const SIMPLE_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';

    public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null)
    {
        if (\is_array($s)) {
            $r = [];
            foreach ($s as $str) {
                $r[] = self::mb_convert_encoding($str, $toEncoding, $fromEncoding);
            }

            return $r;
        }

        if (\is_array($fromEncoding) || (null !== $fromEncoding && false !== strpos($fromEncoding, ','))) {
            $fromEncoding = self::mb_detect_encoding($s, $fromEncoding);
        } else {
            $fromEncoding = self::getEncoding($fromEncoding);
        }

        $toEncoding = self::getEncoding($toEncoding);

        if ('BASE64' === $fromEncoding) {
            $s = base64_decode($s);
            $fromEncoding = $toEncoding;
        }

        if ('BASE64' === $toEncoding) {
            return base64_encode($s);
        }

        if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) {
            if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) {
                $fromEncoding = 'Windows-1252';
            }
            if ('UTF-8' !== $fromEncoding) {
                $s = iconv($fromEncoding, 'UTF-8//IGNORE', $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');
            $fromEncoding = 'UTF-8';
        }

        return iconv($fromEncoding, $toEncoding.'//IGNORE', $s);
    }

    public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars)
    {
        $ok = true;
        array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) {
            if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) {
                $ok = false;
            }
        });

        return $ok ? $fromEncoding : false;
    }

    public static function mb_decode_mimeheader($s)
    {
        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);
    }

    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);

            return null;
        }

        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);

            return '';  // Instead of null (cf. mb_encode_numericentity).
        }

        $s = (string) $s;
        if ('' === $s) {
            return '';
        }

        $encoding = self::getEncoding($encoding);

        if ('UTF-8' === $encoding) {
            $encoding = null;
            if (!preg_match('//u', $s)) {
                $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
            }
        } else {
            $s = iconv($encoding, 'UTF-8//IGNORE', $s);
        }

        $cnt = floor(\count($convmap) / 4) * 4;

        for ($i = 0; $i < $cnt; $i += 4) {
            // collector_decode_htmlnumericentity ignores $convmap[$i + 3]
            $convmap[$i] += $convmap[$i + 2];
            $convmap[$i + 1] += $convmap[$i + 2];
        }

        $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) {
            $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1];
            for ($i = 0; $i < $cnt; $i += 4) {
                if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) {
                    return self::mb_chr($c - $convmap[$i + 2]);
                }
            }

            return $m[0];
        }, $s);

        if (null === $encoding) {
            return $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);

            return null;
        }

        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);

            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);

            return null;
        }

        $s = (string) $s;
        if ('' === $s) {
            return '';
        }

        $encoding = self::getEncoding($encoding);

        if ('UTF-8' === $encoding) {
            $encoding = null;
            if (!preg_match('//u', $s)) {
                $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
            }
        } else {
            $s = iconv($encoding, 'UTF-8//IGNORE', $s);
        }

        static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4];

        $cnt = floor(\count($convmap) / 4) * 4;
        $i = 0;
        $len = \strlen($s);
        $result = '';

        while ($i < $len) {
            $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
            $uchr = substr($s, $i, $ulen);
            $i += $ulen;
            $c = self::mb_ord($uchr);

            for ($j = 0; $j < $cnt; $j += 4) {
                if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) {
                    $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3];
                    $result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';';
                    continue 2;
                }
            }
            $result .= $uchr;
        }

        if (null === $encoding) {
            return $result;
        }

        return iconv('UTF-8', $encoding.'//IGNORE', $result);
    }

    public static function mb_convert_case($s, $mode, $encoding = null)
    {
        $s = (string) $s;
        if ('' === $s) {
            return '';
        }

        $encoding = self::getEncoding($encoding);

        if ('UTF-8' === $encoding) {
            $encoding = null;
            if (!preg_match('//u', $s)) {
                $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
            }
        } else {
            $s = iconv($encoding, 'UTF-8//IGNORE', $s);
        }

        if (\MB_CASE_TITLE == $mode) {
            static $titleRegexp = null;
            if (null === $titleRegexp) {
                $titleRegexp = self::getData('titleCaseRegexp');
            }
            $s = preg_replace_callback($titleRegexp, [__CLASS__, 'title_case'], $s);
        } else {
            if (\MB_CASE_UPPER == $mode) {
                static $upper = null;
                if (null === $upper) {
                    $upper = self::getData('upperCase');
                }
                $map = $upper;
            } else {
                if (self::MB_CASE_FOLD === $mode) {
                    static $caseFolding = null;
                    if (null === $caseFolding) {
                        $caseFolding = self::getData('caseFolding');
                    }
                    $s = strtr($s, $caseFolding);
                }

                static $lower = null;
                if (null === $lower) {
                    $lower = self::getData('lowerCase');
                }
                $map = $lower;
            }

            static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4];

            $i = 0;
            $len = \strlen($s);

            while ($i < $len) {
                $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
                $uchr = substr($s, $i, $ulen);
                $i += $ulen;

                if (isset($map[$uchr])) {
                    $uchr = $map[$uchr];
                    $nlen = \strlen($uchr);

                    if ($nlen == $ulen) {
                        $nlen = $i;
                        do {
                            $s[--$nlen] = $uchr[--$ulen];
                        } while ($ulen);
                    } else {
                        $s = substr_replace($s, $uchr, $i - $ulen, $ulen);
                        $len += $nlen - $ulen;
                        $i += $nlen - $ulen;
                    }
                }
            }
        }

        if (null === $encoding) {
            return $s;
        }

        return iconv('UTF-8', $encoding.'//IGNORE', $s);
    }

    public static function mb_internal_encoding($encoding = null)
    {
        if (null === $encoding) {
            return self::$internalEncoding;
        }

        $normalizedEncoding = self::getEncoding($encoding);

        if ('UTF-8' === $normalizedEncoding || false !== @iconv($normalizedEncoding, $normalizedEncoding, ' ')) {
            self::$internalEncoding = $normalizedEncoding;

            return true;
        }

        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)
    {
        if (null === $lang) {
            return self::$language;
        }

        switch ($normalizedLang = strtolower($lang)) {
            case 'uni':
            case 'neutral':
                self::$language = $normalizedLang;

                return true;
        }

        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 ['UTF-8'];
    }

    public static function mb_encoding_aliases($encoding)
    {
        switch (strtoupper($encoding)) {
            case 'UTF8':
            case 'UTF-8':
                return ['utf8'];
        }

        return false;
    }

    public static function mb_check_encoding($var = null, $encoding = null)
    {
        if (null === $encoding) {
            if (null === $var) {
                return false;
            }
            $encoding = self::$internalEncoding;
        }

        if (!\is_array($var)) {
            return self::mb_detect_encoding($var, [$encoding]) || false !== @iconv($encoding, $encoding, $var);
        }

        foreach ($var as $key => $value) {
            if (!self::mb_check_encoding($key, $encoding)) {
                return false;
            }
            if (!self::mb_check_encoding($value, $encoding)) {
                return false;
            }
        }

        return true;
    }

    public static function mb_detect_encoding($str, $encodingList = null, $strict = false)
    {
        if (null === $encodingList) {
            $encodingList = self::$encodingList;
        } else {
            if (!\is_array($encodingList)) {
                $encodingList = array_map('trim', explode(',', $encodingList));
            }
            $encodingList = array_map('strtoupper', $encodingList);
        }

        foreach ($encodingList as $enc) {
            switch ($enc) {
                case 'ASCII':
                    if (!preg_match('/[\x80-\xFF]/', $str)) {
                        return $enc;
                    }
                    break;

                case 'UTF8':
                case 'UTF-8':
                    if (preg_match('//u', $str)) {
                        return 'UTF-8';
                    }
                    break;

                default:
                    if (0 === strncmp($enc, 'ISO-8859-', 9)) {
                        return $enc;
                    }
            }
        }

        return false;
    }

    public static function mb_detect_order($encodingList = null)
    {
        if (null === $encodingList) {
            return self::$encodingList;
        }

        if (!\is_array($encodingList)) {
            $encodingList = array_map('trim', explode(',', $encodingList));
        }
        $encodingList = array_map('strtoupper', $encodingList);

        foreach ($encodingList as $enc) {
            switch ($enc) {
                default:
                    if (strncmp($enc, 'ISO-8859-', 9)) {
                        return false;
                    }
                    // no break
                case 'ASCII':
                case 'UTF8':
                case 'UTF-8':
            }
        }

        self::$encodingList = $encodingList;

        return true;
    }

    public static function mb_strlen($s, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);
        if ('CP850' === $encoding || 'ASCII' === $encoding) {
            return \strlen($s);
        }

        return @iconv_strlen($s, $encoding);
    }

    public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);
        if ('CP850' === $encoding || 'ASCII' === $encoding) {
            return strpos($haystack, $needle, $offset);
        }

        $needle = (string) $needle;
        if ('' === $needle) {
            if (80000 > \PHP_VERSION_ID) {
                trigger_error(__METHOD__.': Empty delimiter', \E_USER_WARNING);

                return false;
            }

            return 0;
        }

        return iconv_strpos($haystack, $needle, $offset, $encoding);
    }

    public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);
        if ('CP850' === $encoding || 'ASCII' === $encoding) {
            return strrpos($haystack, $needle, $offset);
        }

        if ($offset != (int) $offset) {
            $offset = 0;
        } elseif ($offset = (int) $offset) {
            if ($offset < 0) {
                if (0 > $offset += self::mb_strlen($needle)) {
                    $haystack = self::mb_substr($haystack, 0, $offset, $encoding);
                }
                $offset = 0;
            } else {
                $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding);
            }
        }

        $pos = '' !== $needle || 80000 > \PHP_VERSION_ID
            ? iconv_strrpos($haystack, $needle, $encoding)
            : self::mb_strlen($haystack, $encoding);

        return false !== $pos ? $offset + $pos : false;
    }

    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);

            return null;
        }

        if (1 > $split_length = (int) $split_length) {
            if (80000 > \PHP_VERSION_ID) {
                trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING);

                return false;
            }

            throw new \ValueError('Argument #2 ($length) must be greater than 0');
        }

        if (null === $encoding) {
            $encoding = mb_internal_encoding();
        }

        if ('UTF-8' === $encoding = self::getEncoding($encoding)) {
            $rx = '/(';
            while (65535 < $split_length) {
                $rx .= '.{65535}';
                $split_length -= 65535;
            }
            $rx .= '.{'.$split_length.'})/us';

            return preg_split($rx, $string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY);
        }

        $result = [];
        $length = mb_strlen($string, $encoding);

        for ($i = 0; $i < $length; $i += $split_length) {
            $result[] = mb_substr($string, $i, $split_length, $encoding);
        }

        return $result;
    }

    public static function mb_strtolower($s, $encoding = null)
    {
        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);
    }

    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;
        }

        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)
    {
        $encoding = self::getEncoding($encoding);
        if ('CP850' === $encoding || 'ASCII' === $encoding) {
            return (string) substr($s, $start, null === $length ? 2147483647 : $length);
        }

        if ($start < 0) {
            $start = iconv_strlen($s, $encoding) + $start;
            if ($start < 0) {
                $start = 0;
            }
        }

        if (null === $length) {
            $length = 2147483647;
        } elseif ($length < 0) {
            $length = iconv_strlen($s, $encoding) + $length - $start;
            if ($length < 0) {
                return '';
            }
        }

        return (string) iconv_substr($s, $start, $length, $encoding);
    }

    public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null)
    {
        [$haystack, $needle] = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], [
            self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding),
            self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding),
        ]);

        return self::mb_strpos($haystack, $needle, $offset, $encoding);
    }

    public static function mb_stristr($haystack, $needle, $part = false, $encoding = null)
    {
        $pos = self::mb_stripos($haystack, $needle, 0, $encoding);

        return self::getSubpart($pos, $part, $haystack, $encoding);
    }

    public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);
        if ('CP850' === $encoding || 'ASCII' === $encoding) {
            $pos = strrpos($haystack, $needle);
        } else {
            $needle = self::mb_substr($needle, 0, 1, $encoding);
            $pos = iconv_strrpos($haystack, $needle, $encoding);
        }

        return self::getSubpart($pos, $part, $haystack, $encoding);
    }

    public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null)
    {
        $needle = self::mb_substr($needle, 0, 1, $encoding);
        $pos = self::mb_strripos($haystack, $needle, $encoding);

        return self::getSubpart($pos, $part, $haystack, $encoding);
    }

    public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null)
    {
        $haystack = self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding);
        $needle = self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding);

        $haystack = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $haystack);
        $needle = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $needle);

        return self::mb_strrpos($haystack, $needle, $offset, $encoding);
    }

    public static function mb_strstr($haystack, $needle, $part = false, $encoding = null)
    {
        $pos = strpos($haystack, $needle);
        if (false === $pos) {
            return false;
        }
        if ($part) {
            return substr($haystack, 0, $pos);
        }

        return substr($haystack, $pos);
    }

    public static function mb_get_info($type = 'all')
    {
        $info = [
            'internal_encoding' => self::$internalEncoding,
            'http_output' => 'pass',
            'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)',
            'func_overload' => 0,
            'func_overload_list' => 'no overload',
            'mail_charset' => 'UTF-8',
            'mail_header_encoding' => 'BASE64',
            'mail_body_encoding' => 'BASE64',
            'illegal_chars' => 0,
            'encoding_translation' => 'Off',
            'language' => self::$language,
            'detect_order' => self::$encodingList,
            'substitute_character' => 'none',
            'strict_detection' => 'Off',
        ];

        if ('all' === $type) {
            return $info;
        }
        if (isset($info[$type])) {
            return $info[$type];
        }

        return false;
    }

    public static function mb_http_input($type = '')
    {
        return false;
    }

    public static function mb_http_output($encoding = null)
    {
        return null !== $encoding ? 'pass' === $encoding : 'pass';
    }

    public static function mb_strwidth($s, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);

        if ('UTF-8' !== $encoding) {
            $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');
    }

    public static function mb_substr_count($haystack, $needle, $encoding = null)
    {
        return substr_count($haystack, $needle);
    }

    public static function mb_output_handler($contents, $status)
    {
        return $contents;
    }

    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 = self::getEncoding($encoding)) {
            $s = mb_convert_encoding($s, $encoding, 'UTF-8');
        }

        return $s;
    }

    public static function mb_ord($s, $encoding = null)
    {
        if ('UTF-8' !== $encoding = self::getEncoding($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;
    }

    public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, ?string $encoding = null): string
    {
        if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], true)) {
            throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH');
        }

        if (null === $encoding) {
            $encoding = self::mb_internal_encoding();
        } else {
            self::assertEncoding($encoding, 'mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given');
        }

        if (self::mb_strlen($pad_string, $encoding) <= 0) {
            throw new \ValueError('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string');
        }

        $paddingRequired = $length - self::mb_strlen($string, $encoding);

        if ($paddingRequired < 1) {
            return $string;
        }

        switch ($pad_type) {
            case \STR_PAD_LEFT:
                return self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding).$string;
            case \STR_PAD_RIGHT:
                return $string.self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding);
            default:
                $leftPaddingLength = floor($paddingRequired / 2);
                $rightPaddingLength = $paddingRequired - $leftPaddingLength;

                return self::mb_substr(str_repeat($pad_string, $leftPaddingLength), 0, $leftPaddingLength, $encoding).$string.self::mb_substr(str_repeat($pad_string, $rightPaddingLength), 0, $rightPaddingLength, $encoding);
        }
    }

    public static function mb_ucfirst(string $string, ?string $encoding = null): string
    {
        if (null === $encoding) {
            $encoding = self::mb_internal_encoding();
        } else {
            self::assertEncoding($encoding, 'mb_ucfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given');
        }

        $firstChar = mb_substr($string, 0, 1, $encoding);
        $firstChar = mb_convert_case($firstChar, \MB_CASE_TITLE, $encoding);

        return $firstChar.mb_substr($string, 1, null, $encoding);
    }

    public static function mb_lcfirst(string $string, ?string $encoding = null): string
    {
        if (null === $encoding) {
            $encoding = self::mb_internal_encoding();
        } else {
            self::assertEncoding($encoding, 'mb_lcfirst(): Argument #2 ($encoding) must be a valid encoding, "%s" given');
        }

        $firstChar = mb_substr($string, 0, 1, $encoding);
        $firstChar = mb_convert_case($firstChar, \MB_CASE_LOWER, $encoding);

        return $firstChar.mb_substr($string, 1, null, $encoding);
    }

    private static function getSubpart($pos, $part, $haystack, $encoding)
    {
        if (false === $pos) {
            return false;
        }
        if ($part) {
            return self::mb_substr($haystack, 0, $pos, $encoding);
        }

        return self::mb_substr($haystack, $pos, null, $encoding);
    }

    private static function html_encoding_callback(array $m)
    {
        $i = 1;
        $entities = '';
        $m = unpack('C*', htmlentities($m[0], \ENT_COMPAT, 'UTF-8'));

        while (isset($m[$i])) {
            if (0x80 > $m[$i]) {
                $entities .= \chr($m[$i++]);
                continue;
            }
            if (0xF0 <= $m[$i]) {
                $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
            } elseif (0xE0 <= $m[$i]) {
                $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
            } else {
                $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80;
            }

            $entities .= '&#'.$c.';';
        }

        return $entities;
    }

    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');
    }

    private static function getData($file)
    {
        if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) {
            return require $file;
        }

        return false;
    }

    private static function getEncoding($encoding)
    {
        if (null === $encoding) {
            return self::$internalEncoding;
        }

        if ('UTF-8' === $encoding) {
            return 'UTF-8';
        }

        $encoding = strtoupper($encoding);

        if ('8BIT' === $encoding || 'BINARY' === $encoding) {
            return 'CP850';
        }

        if ('UTF8' === $encoding) {
            return 'UTF-8';
        }

        return $encoding;
    }

    public static function mb_trim(string $string, ?string $characters = null, ?string $encoding = null): string
    {
        return self::mb_internal_trim('{^[%s]+|[%1$s]+$}Du', $string, $characters, $encoding, __FUNCTION__);
    }

    public static function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null): string
    {
        return self::mb_internal_trim('{^[%s]+}Du', $string, $characters, $encoding, __FUNCTION__);
    }

    public static function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null): string
    {
        return self::mb_internal_trim('{[%s]+$}Du', $string, $characters, $encoding, __FUNCTION__);
    }

    private static function mb_internal_trim(string $regex, string $string, ?string $characters, ?string $encoding, string $function): string
    {
        if (null === $encoding) {
            $encoding = self::mb_internal_encoding();
        } else {
            self::assertEncoding($encoding, $function.'(): Argument #3 ($encoding) must be a valid encoding, "%s" given');
        }

        if ('' === $characters) {
            return null === $encoding ? $string : self::mb_convert_encoding($string, $encoding);
        }

        if ('UTF-8' === $encoding) {
            $encoding = null;
            if (!preg_match('//u', $string)) {
                $string = @iconv('UTF-8', 'UTF-8//IGNORE', $string);
            }
            if (null !== $characters && !preg_match('//u', $characters)) {
                $characters = @iconv('UTF-8', 'UTF-8//IGNORE', $characters);
            }
        } else {
            $string = iconv($encoding, 'UTF-8//IGNORE', $string);

            if (null !== $characters) {
                $characters = iconv($encoding, 'UTF-8//IGNORE', $characters);
            }
        }

        if (null === $characters) {
            $characters = "\\0 \f\n\r\t\v\u{00A0}\u{1680}\u{2000}\u{2001}\u{2002}\u{2003}\u{2004}\u{2005}\u{2006}\u{2007}\u{2008}\u{2009}\u{200A}\u{2028}\u{2029}\u{202F}\u{205F}\u{3000}\u{0085}\u{180E}";
        } else {
            $characters = preg_quote($characters);
        }

        $string = preg_replace(sprintf($regex, $characters), '', $string);

        if (null === $encoding) {
            return $string;
        }

        return iconv('UTF-8', $encoding.'//IGNORE', $string);
    }

    private static function assertEncoding(string $encoding, string $errorFormat): void
    {
        try {
            $validEncoding = @self::mb_check_encoding('', $encoding);
        } catch (\ValueError $e) {
            throw new \ValueError(sprintf($errorFormat, $encoding));
        }

        // BC for PHP 7.3 and lower
        if (!$validEncoding) {
            throw new \ValueError(sprintf($errorFormat, $encoding));
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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|null { return p\Mbstring::mb_get_info((string) $type); }
}
if (!function_exists('mb_http_output')) {
    function mb_http_output(?string $encoding = null): string|bool { return p\Mbstring::mb_http_output($encoding); }
}
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 (!function_exists('mb_str_pad')) {
    function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); }
}

if (!function_exists('mb_ucfirst')) {
    function mb_ucfirst(string $string, ?string $encoding = null): string { return p\Mbstring::mb_ucfirst($string, $encoding); }
}

if (!function_exists('mb_lcfirst')) {
    function mb_lcfirst(string $string, ?string $encoding = null): string { return p\Mbstring::mb_lcfirst($string, $encoding); }
}

if (!function_exists('mb_trim')) {
    function mb_trim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_trim($string, $characters, $encoding); }
}

if (!function_exists('mb_ltrim')) {
    function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_ltrim($string, $characters, $encoding); }
}

if (!function_exists('mb_rtrim')) {
    function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_rtrim($string, $characters, $encoding); }
}

if (extension_loaded('mbstring')) {
    return;
}

if (!defined('MB_CASE_UPPER')) {
    define('MB_CASE_UPPER', 0);
}
if (!defined('MB_CASE_LOWER')) {
    define('MB_CASE_LOWER', 1);
}
if (!defined('MB_CASE_TITLE')) {
    define('MB_CASE_TITLE', 2);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 (\PHP_VERSION_ID >= 80000) {
    return require __DIR__.'/bootstrap80.php';
}

if (!function_exists('mb_convert_encoding')) {
    function mb_convert_encoding($string, $to_encoding, $from_encoding = null) { return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); }
}
if (!function_exists('mb_decode_mimeheader')) {
    function mb_decode_mimeheader($string) { return p\Mbstring::mb_decode_mimeheader($string); }
}
if (!function_exists('mb_encode_mimeheader')) {
    function mb_encode_mimeheader($string, $charset = null, $transfer_encoding = null, $newline = "\r\n", $indent = 0) { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); }
}
if (!function_exists('mb_decode_numericentity')) {
    function mb_decode_numericentity($string, $map, $encoding = null) { return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); }
}
if (!function_exists('mb_encode_numericentity')) {
    function mb_encode_numericentity($string, $map, $encoding = null, $hex = false) { return p\Mbstring::mb_encode_numericentity($string, $map, $encoding, $hex); }
}
if (!function_exists('mb_convert_case')) {
    function mb_convert_case($string, $mode, $encoding = null) { return p\Mbstring::mb_convert_case($string, $mode, $encoding); }
}
if (!function_exists('mb_internal_encoding')) {
    function mb_internal_encoding($encoding = null) { return p\Mbstring::mb_internal_encoding($encoding); }
}
if (!function_exists('mb_language')) {
    function mb_language($language = null) { return p\Mbstring::mb_language($language); }
}
if (!function_exists('mb_list_encodings')) {
    function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); }
}
if (!function_exists('mb_encoding_aliases')) {
    function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); }
}
if (!function_exists('mb_check_encoding')) {
    function mb_check_encoding($value = null, $encoding = null) { return p\Mbstring::mb_check_encoding($value, $encoding); }
}
if (!function_exists('mb_detect_encoding')) {
    function mb_detect_encoding($string, $encodings = null, $strict = false) { return p\Mbstring::mb_detect_encoding($string, $encodings, $strict); }
}
if (!function_exists('mb_detect_order')) {
    function mb_detect_order($encoding = null) { return p\Mbstring::mb_detect_order($encoding); }
}
if (!function_exists('mb_parse_str')) {
    function mb_parse_str($string, &$result = []) { parse_str($string, $result); return (bool) $result; }
}
if (!function_exists('mb_strlen')) {
    function mb_strlen($string, $encoding = null) { return p\Mbstring::mb_strlen($string, $encoding); }
}
if (!function_exists('mb_strpos')) {
    function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strpos($haystack, $needle, $offset, $encoding); }
}
if (!function_exists('mb_strtolower')) {
    function mb_strtolower($string, $encoding = null) { return p\Mbstring::mb_strtolower($string, $encoding); }
}
if (!function_exists('mb_strtoupper')) {
    function mb_strtoupper($string, $encoding = null) { return p\Mbstring::mb_strtoupper($string, $encoding); }
}
if (!function_exists('mb_substitute_character')) {
    function mb_substitute_character($substitute_character = null) { return p\Mbstring::mb_substitute_character($substitute_character); }
}
if (!function_exists('mb_substr')) {
    function mb_substr($string, $start, $length = 2147483647, $encoding = null) { return p\Mbstring::mb_substr($string, $start, $length, $encoding); }
}
if (!function_exists('mb_stripos')) {
    function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_stripos($haystack, $needle, $offset, $encoding); }
}
if (!function_exists('mb_stristr')) {
    function mb_stristr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_stristr($haystack, $needle, $before_needle, $encoding); }
}
if (!function_exists('mb_strrchr')) {
    function mb_strrchr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrchr($haystack, $needle, $before_needle, $encoding); }
}
if (!function_exists('mb_strrichr')) {
    function mb_strrichr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrichr($haystack, $needle, $before_needle, $encoding); }
}
if (!function_exists('mb_strripos')) {
    function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strripos($haystack, $needle, $offset, $encoding); }
}
if (!function_exists('mb_strrpos')) {
    function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strrpos($haystack, $needle, $offset, $encoding); }
}
if (!function_exists('mb_strstr')) {
    function mb_strstr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strstr($haystack, $needle, $before_needle, $encoding); }
}
if (!function_exists('mb_get_info')) {
    function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); }
}
if (!function_exists('mb_http_output')) {
    function mb_http_output($encoding = null) { return p\Mbstring::mb_http_output($encoding); }
}
if (!function_exists('mb_strwidth')) {
    function mb_strwidth($string, $encoding = null) { return p\Mbstring::mb_strwidth($string, $encoding); }
}
if (!function_exists('mb_substr_count')) {
    function mb_substr_count($haystack, $needle, $encoding = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $encoding); }
}
if (!function_exists('mb_output_handler')) {
    function mb_output_handler($string, $status) { return p\Mbstring::mb_output_handler($string, $status); }
}
if (!function_exists('mb_http_input')) {
    function mb_http_input($type = null) { return p\Mbstring::mb_http_input($type); }
}

if (!function_exists('mb_convert_variables')) {
    function mb_convert_variables($to_encoding, $from_encoding, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, ...$vars); }
}

if (!function_exists('mb_ord')) {
    function mb_ord($string, $encoding = null) { return p\Mbstring::mb_ord($string, $encoding); }
}
if (!function_exists('mb_chr')) {
    function mb_chr($codepoint, $encoding = null) { return p\Mbstring::mb_chr($codepoint, $encoding); }
}
if (!function_exists('mb_scrub')) {
    function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? mb_internal_encoding() : $encoding; return mb_convert_encoding($string, $encoding, $encoding); }
}
if (!function_exists('mb_str_split')) {
    function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $length, $encoding); }
}

if (!function_exists('mb_str_pad')) {
    function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); }
}

if (!function_exists('mb_ucfirst')) {
    function mb_ucfirst(string $string, ?string $encoding = null): string { return p\Mbstring::mb_ucfirst($string, $encoding); }
}

if (!function_exists('mb_lcfirst')) {
    function mb_lcfirst(string $string, ?string $encoding = null): string { return p\Mbstring::mb_lcfirst($string, $encoding); }
}

if (!function_exists('mb_trim')) {
    function mb_trim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_trim($string, $characters, $encoding); }
}

if (!function_exists('mb_ltrim')) {
    function mb_ltrim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_ltrim($string, $characters, $encoding); }
}

if (!function_exists('mb_rtrim')) {
    function mb_rtrim(string $string, ?string $characters = null, ?string $encoding = null): string { return p\Mbstring::mb_rtrim($string, $characters, $encoding); }
}


if (extension_loaded('mbstring')) {
    return;
}

if (!defined('MB_CASE_UPPER')) {
    define('MB_CASE_UPPER', 0);
}
if (!defined('MB_CASE_LOWER')) {
    define('MB_CASE_LOWER', 1);
}
if (!defined('MB_CASE_TITLE')) {
    define('MB_CASE_TITLE', 2);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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\Event;

/**
 * Event encapsulation class.
 *
 * Encapsulates events thus decoupling the observer from the subject they encapsulate.
 *
 * @author Drak <drak@zikula.org>
 *
 * @implements \ArrayAccess<string, mixed>
 * @implements \IteratorAggregate<string, mixed>
 */
class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
{
    protected $subject;
    protected $arguments;

    /**
     * Encapsulate an event with $subject and $arguments.
     *
     * @param mixed $subject   The subject of the event, usually an object or a callable
     * @param array $arguments Arguments to store in the event
     */
    public function __construct(mixed $subject = null, array $arguments = [])
    {
        $this->subject = $subject;
        $this->arguments = $arguments;
    }

    /**
     * Getter for subject property.
     */
    public function getSubject(): mixed
    {
        return $this->subject;
    }

    /**
     * Get argument by key.
     *
     * @throws \InvalidArgumentException if key is not found
     */
    public function getArgument(string $key): mixed
    {
        if ($this->hasArgument($key)) {
            return $this->arguments[$key];
        }

        throw new \InvalidArgumentException(\sprintf('Argument "%s" not found.', $key));
    }

    /**
     * Add argument to event.
     *
     * @return $this
     */
    public function setArgument(string $key, mixed $value): static
    {
        $this->arguments[$key] = $value;

        return $this;
    }

    /**
     * Getter for all arguments.
     */
    public function getArguments(): array
    {
        return $this->arguments;
    }

    /**
     * Set args property.
     *
     * @return $this
     */
    public function setArguments(array $args = []): static
    {
        $this->arguments = $args;

        return $this;
    }

    /**
     * Has argument.
     */
    public function hasArgument(string $key): bool
    {
        return \array_key_exists($key, $this->arguments);
    }

    /**
     * ArrayAccess for argument getter.
     *
     * @param string $key Array key
     *
     * @throws \InvalidArgumentException if key does not exist in $this->args
     */
    public function offsetGet(mixed $key): mixed
    {
        return $this->getArgument($key);
    }

    /**
     * ArrayAccess for argument setter.
     *
     * @param string $key Array key to set
     */
    public function offsetSet(mixed $key, mixed $value): void
    {
        $this->setArgument($key, $value);
    }

    /**
     * ArrayAccess for unset argument.
     *
     * @param string $key Array key
     */
    public function offsetUnset(mixed $key): void
    {
        if ($this->hasArgument($key)) {
            unset($this->arguments[$key]);
        }
    }

    /**
     * ArrayAccess has argument.
     *
     * @param string $key Array key
     */
    public function offsetExists(mixed $key): bool
    {
        return $this->hasArgument($key);
    }

    /**
     * IteratorAggregate for iterating over the object like an array.
     *
     * @return \ArrayIterator<string, mixed>
     */
    public function getIterator(): \ArrayIterator
    {
        return new \ArrayIterator($this->arguments);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 Psr\EventDispatcher\StoppableEventInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Component\VarDumper\Caster\ClassStub;

/**
 * @author Fabien Potencier <fabien@symfony.com>
 */
final class WrappedListener
{
    private string|array|object $listener;
    private ?\Closure $optimizedListener;
    private string $name;
    private bool $called = false;
    private bool $stoppedPropagation = false;
    private Stopwatch $stopwatch;
    private ?EventDispatcherInterface $dispatcher;
    private string $pretty;
    private string $callableRef;
    private ClassStub|string $stub;
    private ?int $priority = null;
    private static bool $hasClassStub;

    public function __construct(callable|array $listener, ?string $name, Stopwatch $stopwatch, ?EventDispatcherInterface $dispatcher = null, ?int $priority = null)
    {
        $this->listener = $listener;
        $this->optimizedListener = $listener instanceof \Closure ? $listener : (\is_callable($listener) ? $listener(...) : null);
        $this->stopwatch = $stopwatch;
        $this->dispatcher = $dispatcher;
        $this->priority = $priority;

        if (\is_array($listener)) {
            [$this->name, $this->callableRef] = $this->parseListener($listener);
            $this->pretty = $this->name.'::'.$listener[1];
            $this->callableRef .= '::'.$listener[1];
        } elseif ($listener instanceof \Closure) {
            $r = new \ReflectionFunction($listener);
            if (str_contains($r->name, '{closure')) {
                $this->pretty = $this->name = 'closure';
            } elseif ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) {
                $this->name = $class->name;
                $this->pretty = $this->name.'::'.$r->name;
            } else {
                $this->pretty = $this->name = $r->name;
            }
        } elseif (\is_string($listener)) {
            $this->pretty = $this->name = $listener;
        } else {
            $this->name = get_debug_type($listener);
            $this->pretty = $this->name.'::__invoke';
            $this->callableRef = $listener::class.'::__invoke';
        }

        if (null !== $name) {
            $this->name = $name;
        }

        self::$hasClassStub ??= class_exists(ClassStub::class);
    }

    public function getWrappedListener(): callable|array
    {
        return $this->listener;
    }

    public function wasCalled(): bool
    {
        return $this->called;
    }

    public function stoppedPropagation(): bool
    {
        return $this->stoppedPropagation;
    }

    public function getPretty(): string
    {
        return $this->pretty;
    }

    public function getInfo(string $eventName): array
    {
        $this->stub ??= self::$hasClassStub ? new ClassStub($this->pretty.'()', $this->callableRef ?? $this->listener) : $this->pretty.'()';

        return [
            'event' => $eventName,
            'priority' => $this->priority ??= $this->dispatcher?->getListenerPriority($eventName, $this->listener),
            'pretty' => $this->pretty,
            'stub' => $this->stub,
        ];
    }

    public function __invoke(object $event, string $eventName, EventDispatcherInterface $dispatcher): void
    {
        $dispatcher = $this->dispatcher ?: $dispatcher;

        $this->called = true;
        $this->priority ??= $dispatcher->getListenerPriority($eventName, $this->listener);

        $e = $this->stopwatch->start($this->name, 'event_listener');

        try {
            ($this->optimizedListener ?? $this->listener)($event, $eventName, $dispatcher);
        } finally {
            if ($e->isStarted()) {
                $e->stop();
            }
        }

        if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
            $this->stoppedPropagation = true;
        }
    }

    private function parseListener(array $listener): array
    {
        if ($listener[0] instanceof \Closure) {
            foreach ((new \ReflectionFunction($listener[0]))->getAttributes(\Closure::class) as $attribute) {
                if ($name = $attribute->getArguments()['name'] ?? false) {
                    return [$name, $attribute->getArguments()['class'] ?? $name];
                }
            }
        }

        if (\is_object($listener[0])) {
            return [get_debug_type($listener[0]), $listener[0]::class];
        }

        return [$listener[0], $listener[0]];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 Psr\EventDispatcher\StoppableEventInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
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.
 *
 * This event dispatcher delegates the dispatching to another one.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class TraceableEventDispatcher implements EventDispatcherInterface, ResetInterface
{
    protected $logger;
    protected $stopwatch;

    /**
     * @var \SplObjectStorage<WrappedListener, array{string, string}>|null
     */
    private ?\SplObjectStorage $callStack = null;
    private EventDispatcherInterface $dispatcher;
    private array $wrappedListeners = [];
    private array $orphanedEvents = [];
    private ?RequestStack $requestStack;
    private string $currentRequestHash = '';

    public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, ?LoggerInterface $logger = null, ?RequestStack $requestStack = null)
    {
        $this->dispatcher = $dispatcher;
        $this->stopwatch = $stopwatch;
        $this->logger = $logger;
        $this->requestStack = $requestStack;
    }

    /**
     * @return void
     */
    public function addListener(string $eventName, callable|array $listener, int $priority = 0)
    {
        $this->dispatcher->addListener($eventName, $listener, $priority);
    }

    /**
     * @return void
     */
    public function addSubscriber(EventSubscriberInterface $subscriber)
    {
        $this->dispatcher->addSubscriber($subscriber);
    }

    /**
     * @return void
     */
    public function removeListener(string $eventName, callable|array $listener)
    {
        if (isset($this->wrappedListeners[$eventName])) {
            foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) {
                if ($wrappedListener->getWrappedListener() === $listener || ($listener instanceof \Closure && $wrappedListener->getWrappedListener() == $listener)) {
                    $listener = $wrappedListener;
                    unset($this->wrappedListeners[$eventName][$index]);
                    break;
                }
            }
        }

        $this->dispatcher->removeListener($eventName, $listener);
    }

    /**
     * @return void
     */
    public function removeSubscriber(EventSubscriberInterface $subscriber)
    {
        $this->dispatcher->removeSubscriber($subscriber);
    }

    public function getListeners(?string $eventName = null): array
    {
        return $this->dispatcher->getListeners($eventName);
    }

    public function getListenerPriority(string $eventName, callable|array $listener): ?int
    {
        // 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 $wrappedListener) {
                if ($wrappedListener->getWrappedListener() === $listener || ($listener instanceof \Closure && $wrappedListener->getWrappedListener() == $listener)) {
                    return $this->dispatcher->getListenerPriority($eventName, $wrappedListener);
                }
            }
        }

        return $this->dispatcher->getListenerPriority($eventName, $listener);
    }

    public function hasListeners(?string $eventName = null): bool
    {
        return $this->dispatcher->hasListeners($eventName);
    }

    public function dispatch(object $event, ?string $eventName = null): object
    {
        $eventName ??= $event::class;

        $this->callStack ??= new \SplObjectStorage();

        $currentRequestHash = $this->currentRequestHash = $this->requestStack && ($request = $this->requestStack->getCurrentRequest()) ? spl_object_hash($request) : '';

        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->beforeDispatch($eventName, $event);
            try {
                $e = $this->stopwatch->start($eventName, 'section');
                try {
                    $this->dispatcher->dispatch($event, $eventName);
                } finally {
                    if ($e->isStarted()) {
                        $e->stop();
                    }
                }
            } finally {
                $this->afterDispatch($eventName, $event);
            }
        } finally {
            $this->currentRequestHash = $currentRequestHash;
            $this->postProcess($eventName);
        }

        return $event;
    }

    public function getCalledListeners(?Request $request = null): array
    {
        if (null === $this->callStack) {
            return [];
        }

        $hash = $request ? spl_object_hash($request) : null;
        $called = [];
        foreach ($this->callStack as $listener) {
            [$eventName, $requestHash] = $this->callStack->getInfo();
            if (null === $hash || $hash === $requestHash) {
                $called[] = $listener->getInfo($eventName);
            }
        }

        return $called;
    }

    public function getNotCalledListeners(?Request $request = null): array
    {
        try {
            $allListeners = $this->dispatcher instanceof EventDispatcher ? $this->getListenersWithPriority() : $this->getListenersWithoutPriority();
        } catch (\Exception $e) {
            $this->logger?->info('An exception was thrown while getting the uncalled listeners.', ['exception' => $e]);

            // unable to retrieve the uncalled listeners
            return [];
        }

        $hash = $request ? spl_object_hash($request) : null;
        $calledListeners = [];

        if (null !== $this->callStack) {
            foreach ($this->callStack as $calledListener) {
                [, $requestHash] = $this->callStack->getInfo();

                if (null === $hash || $hash === $requestHash) {
                    $calledListeners[] = $calledListener->getWrappedListener();
                }
            }
        }

        $notCalled = [];

        foreach ($allListeners as $eventName => $listeners) {
            foreach ($listeners as [$listener, $priority]) {
                if (!\in_array($listener, $calledListeners, true)) {
                    if (!$listener instanceof WrappedListener) {
                        $listener = new WrappedListener($listener, null, $this->stopwatch, $this, $priority);
                    }
                    $notCalled[] = $listener->getInfo($eventName);
                }
            }
        }

        uasort($notCalled, $this->sortNotCalledListeners(...));

        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));
    }

    /**
     * @return void
     */
    public function reset()
    {
        $this->callStack = null;
        $this->orphanedEvents = [];
        $this->currentRequestHash = '';
    }

    /**
     * Proxies all method calls to the original event dispatcher.
     *
     * @param string $method    The method name
     * @param array  $arguments The method arguments
     */
    public function __call(string $method, array $arguments): mixed
    {
        return $this->dispatcher->{$method}(...$arguments);
    }

    /**
     * Called before dispatching the event.
     *
     * @return void
     */
    protected function beforeDispatch(string $eventName, object $event)
    {
    }

    /**
     * Called after dispatching the event.
     *
     * @return void
     */
    protected function afterDispatch(string $eventName, object $event)
    {
    }

    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->currentRequestHash]);
        }
    }

    private function postProcess(string $eventName): void
    {
        unset($this->wrappedListeners[$eventName]);
        $skipped = false;
        foreach ($this->dispatcher->getListeners($eventName) as $listener) {
            if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch.
                continue;
            }
            // Unwrap listener
            $priority = $this->getListenerPriority($eventName, $listener);
            $this->dispatcher->removeListener($eventName, $listener);
            $this->dispatcher->addListener($eventName, $listener->getWrappedListener(), $priority);

            if (null !== $this->logger) {
                $context = ['event' => $eventName, 'listener' => $listener->getPretty()];
            }

            if ($listener->wasCalled()) {
                $this->logger?->debug('Notified event "{event}" to listener "{listener}".', $context);
            } else {
                $this->callStack->detach($listener);
            }

            if (null !== $this->logger && $skipped) {
                $this->logger->debug('Listener "{listener}" was not called for event "{event}".', $context);
            }

            if ($listener->stoppedPropagation()) {
                $this->logger?->debug('Listener "{listener}" stopped propagation of the event "{event}".', $context);

                $skipped = true;
            }
        }
    }

    private function sortNotCalledListeners(array $a, array $b): int
    {
        if (0 !== $cmp = strcmp($a['event'], $b['event'])) {
            return $cmp;
        }

        if (\is_int($a['priority']) && !\is_int($b['priority'])) {
            return 1;
        }

        if (!\is_int($a['priority']) && \is_int($b['priority'])) {
            return -1;
        }

        if ($a['priority'] === $b['priority']) {
            return 0;
        }

        if ($a['priority'] > $b['priority']) {
            return -1;
        }

        return 1;
    }

    private function getListenersWithPriority(): array
    {
        $result = [];

        $allListeners = new \ReflectionProperty(EventDispatcher::class, 'listeners');

        foreach ($allListeners->getValue($this->dispatcher) as $eventName => $listenersByPriority) {
            foreach ($listenersByPriority as $priority => $listeners) {
                foreach ($listeners as $listener) {
                    $result[$eventName][] = [$listener, $priority];
                }
            }
        }

        return $result;
    }

    private function getListenersWithoutPriority(): array
    {
        $result = [];

        foreach ($this->getListeners() as $eventName => $listeners) {
            foreach ($listeners as $listener) {
                $result[$eventName][] = [$listener, null];
            }
        }

        return $result;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\EventDispatcher;

/**
 * A read-only proxy for an event dispatcher.
 *
 * @author Bernhard Schussek <bschussek@gmail.com>
 */
class ImmutableEventDispatcher implements EventDispatcherInterface
{
    private EventDispatcherInterface $dispatcher;

    public function __construct(EventDispatcherInterface $dispatcher)
    {
        $this->dispatcher = $dispatcher;
    }

    public function dispatch(object $event, ?string $eventName = null): object
    {
        return $this->dispatcher->dispatch($event, $eventName);
    }

    /**
     * @return never
     */
    public function addListener(string $eventName, callable|array $listener, int $priority = 0)
    {
        throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
    }

    /**
     * @return never
     */
    public function addSubscriber(EventSubscriberInterface $subscriber)
    {
        throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
    }

    /**
     * @return never
     */
    public function removeListener(string $eventName, callable|array $listener)
    {
        throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
    }

    /**
     * @return never
     */
    public function removeSubscriber(EventSubscriberInterface $subscriber)
    {
        throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.');
    }

    public function getListeners(?string $eventName = null): array
    {
        return $this->dispatcher->getListeners($eventName);
    }

    public function getListenerPriority(string $eventName, callable|array $listener): ?int
    {
        return $this->dispatcher->getListenerPriority($eventName, $listener);
    }

    public function hasListeners(?string $eventName = null): bool
    {
        return $this->dispatcher->hasListeners($eventName);
    }
}
Copyright (c) 2004-present Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <me@derrabus.de>
 */
#[\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,
    ) {
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

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.
 *
 * Listeners are registered on the manager and events are dispatched through the
 * manager.
 *
 * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
 * @author Jonathan Wage <jonwage@gmail.com>
 * @author Roman Borschel <roman@code-factory.org>
 * @author Bernhard Schussek <bschussek@gmail.com>
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Jordan Alliot <jordan.alliot@gmail.com>
 * @author Nicolas Grekas <p@tchwork.com>
 */
class EventDispatcher implements EventDispatcherInterface
{
    private array $listeners = [];
    private array $sorted = [];
    private array $optimized;

    public function __construct()
    {
        if (__CLASS__ === static::class) {
            $this->optimized = [];
        }
    }

    public function dispatch(object $event, ?string $eventName = null): object
    {
        $eventName ??= $event::class;

        if (isset($this->optimized)) {
            $listeners = $this->optimized[$eventName] ?? (empty($this->listeners[$eventName]) ? [] : $this->optimizeListeners($eventName));
        } else {
            $listeners = $this->getListeners($eventName);
        }

        if ($listeners) {
            $this->callListeners($listeners, $eventName, $event);
        }

        return $event;
    }

    public function getListeners(?string $eventName = null): array
    {
        if (null !== $eventName) {
            if (empty($this->listeners[$eventName])) {
                return [];
            }

            if (!isset($this->sorted[$eventName])) {
                $this->sortListeners($eventName);
            }

            return $this->sorted[$eventName];
        }

        foreach ($this->listeners as $eventName => $eventListeners) {
            if (!isset($this->sorted[$eventName])) {
                $this->sortListeners($eventName);
            }
        }

        return array_filter($this->sorted);
    }

    public function getListenerPriority(string $eventName, callable|array $listener): ?int
    {
        if (empty($this->listeners[$eventName])) {
            return null;
        }

        if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
            $listener[0] = $listener[0]();
            $listener[1] ??= '__invoke';
        }

        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]();
                    $v[1] ??= '__invoke';
                }
                if ($v === $listener || ($listener instanceof \Closure && $v == $listener)) {
                    return $priority;
                }
            }
        }

        return null;
    }

    public function hasListeners(?string $eventName = null): bool
    {
        if (null !== $eventName) {
            return !empty($this->listeners[$eventName]);
        }

        foreach ($this->listeners as $eventListeners) {
            if ($eventListeners) {
                return true;
            }
        }

        return false;
    }

    /**
     * @return void
     */
    public function addListener(string $eventName, callable|array $listener, int $priority = 0)
    {
        $this->listeners[$eventName][$priority][] = $listener;
        unset($this->sorted[$eventName], $this->optimized[$eventName]);
    }

    /**
     * @return void
     */
    public function removeListener(string $eventName, callable|array $listener)
    {
        if (empty($this->listeners[$eventName])) {
            return;
        }

        if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
            $listener[0] = $listener[0]();
            $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 && 2 >= \count($v)) {
                    $v[0] = $v[0]();
                    $v[1] ??= '__invoke';
                }
                if ($v === $listener || ($listener instanceof \Closure && $v == $listener)) {
                    unset($listeners[$k], $this->sorted[$eventName], $this->optimized[$eventName]);
                }
            }

            if (!$listeners) {
                unset($this->listeners[$eventName][$priority]);
            }
        }
    }

    /**
     * @return void
     */
    public function addSubscriber(EventSubscriberInterface $subscriber)
    {
        foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
            if (\is_string($params)) {
                $this->addListener($eventName, [$subscriber, $params]);
            } elseif (\is_string($params[0])) {
                $this->addListener($eventName, [$subscriber, $params[0]], $params[1] ?? 0);
            } else {
                foreach ($params as $listener) {
                    $this->addListener($eventName, [$subscriber, $listener[0]], $listener[1] ?? 0);
                }
            }
        }
    }

    /**
     * @return void
     */
    public function removeSubscriber(EventSubscriberInterface $subscriber)
    {
        foreach ($subscriber->getSubscribedEvents() as $eventName => $params) {
            if (\is_array($params) && \is_array($params[0])) {
                foreach ($params as $listener) {
                    $this->removeListener($eventName, [$subscriber, $listener[0]]);
                }
            } else {
                $this->removeListener($eventName, [$subscriber, \is_string($params) ? $params : $params[0]]);
            }
        }
    }

    /**
     * Triggers the listeners of an event.
     *
     * This method can be overridden to add functionality that is executed
     * for each listener.
     *
     * @param callable[] $listeners The event listeners
     * @param string     $eventName The name of the event to dispatch
     * @param object     $event     The event object to pass to the event handlers/listeners
     *
     * @return void
     */
    protected function callListeners(iterable $listeners, string $eventName, object $event)
    {
        $stoppable = $event instanceof StoppableEventInterface;

        foreach ($listeners as $listener) {
            if ($stoppable && $event->isPropagationStopped()) {
                break;
            }
            $listener($event, $eventName, $this);
        }
    }

    /**
     * Sorts the internal list of listeners for the given event by priority.
     */
    private function sortListeners(string $eventName): void
    {
        krsort($this->listeners[$eventName]);
        $this->sorted[$eventName] = [];

        foreach ($this->listeners[$eventName] as &$listeners) {
            foreach ($listeners as &$listener) {
                if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure && 2 >= \count($listener)) {
                    $listener[0] = $listener[0]();
                    $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] ??= '__invoke';
                        }
                        ($closure = $listener(...))(...$args);
                    };
                } else {
                    $closure = $listener instanceof WrappedListener ? $listener : $listener(...);
                }
            }
        }

        return $this->optimized[$eventName];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <me@derrabus.de>
 */
class AddEventAliasesPass implements CompilerPassInterface
{
    private array $eventAliases;

    public function __construct(array $eventAliases)
    {
        $this->eventAliases = $eventAliases;
    }

    public function process(ContainerBuilder $container): void
    {
        $eventAliases = $container->hasParameter('event_dispatcher.event_aliases') ? $container->getParameter('event_dispatcher.event_aliases') : [];

        $container->setParameter(
            'event_dispatcher.event_aliases',
            array_merge($eventAliases, $this->eventAliases)
        );
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
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.
 */
class RegisterListenersPass implements CompilerPassInterface
{
    private array $hotPathEvents = [];
    private array $noPreloadEvents = [];

    /**
     * @return $this
     */
    public function setHotPathEvents(array $hotPathEvents): static
    {
        $this->hotPathEvents = array_flip($hotPathEvents);

        return $this;
    }

    /**
     * @return $this
     */
    public function setNoPreloadEvents(array $noPreloadEvents): static
    {
        $this->noPreloadEvents = array_flip($noPreloadEvents);

        return $this;
    }

    /**
     * @return void
     */
    public function process(ContainerBuilder $container)
    {
        if (!$container->hasDefinition('event_dispatcher') && !$container->hasAlias('event_dispatcher')) {
            return;
        }

        $aliases = [];

        if ($container->hasParameter('event_dispatcher.event_aliases')) {
            $aliases = $container->getParameter('event_dispatcher.event_aliases');
        }

        $globalDispatcherDefinition = $container->findDefinition('event_dispatcher');

        foreach ($container->findTaggedServiceIds('kernel.event_listener', true) as $id => $events) {
            $noPreload = 0;

            foreach ($events as $event) {
                $priority = $event['priority'] ?? 0;

                if (!isset($event['event'])) {
                    if ($container->getDefinition($id)->hasTag('kernel.event_subscriber')) {
                        continue;
                    }

                    $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',
                        '/[^a-z0-9]/i',
                    ], fn ($matches) => 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'])) {
                        if (!$r->hasMethod('__invoke')) {
                            throw new InvalidArgumentException(\sprintf('None of the "%s" or "__invoke" methods exist for the service "%s". Please define the "method" attribute on "kernel.event_listener" tags.', $event['method'], $id));
                        }

                        $event['method'] = '__invoke';
                    }
                }

                $dispatcherDefinition = $globalDispatcherDefinition;
                if (isset($event['dispatcher'])) {
                    $dispatcherDefinition = $container->findDefinition($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('container.hot_path');
                } elseif (isset($this->noPreloadEvents[$event['event']])) {
                    ++$noPreload;
                }
            }

            if ($noPreload && \count($events) === $noPreload) {
                $container->getDefinition($id)->addTag('container.no_preload');
            }
        }

        $extractingDispatcher = new ExtractingEventDispatcher();

        foreach ($container->findTaggedServiceIds('kernel.event_subscriber', 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
            $class = $def->getClass();

            if (!$r = $container->getReflectionClass($class)) {
                throw new InvalidArgumentException(\sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
            }
            if (!$r->isSubclassOf(EventSubscriberInterface::class)) {
                throw new InvalidArgumentException(\sprintf('Service "%s" must implement interface "%s".', $id, EventSubscriberInterface::class));
            }
            $class = $r->name;

            $dispatcherDefinitions = [];
            foreach ($tags as $attributes) {
                if (!isset($attributes['dispatcher']) || isset($dispatcherDefinitions[$attributes['dispatcher']])) {
                    continue;
                }

                $dispatcherDefinitions[$attributes['dispatcher']] = $container->findDefinition($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]];
                foreach ($dispatcherDefinitions as $dispatcherDefinition) {
                    $dispatcherDefinition->addMethodCall('addListener', $args);
                }

                if (isset($this->hotPathEvents[$args[0]])) {
                    $container->getDefinition($id)->addTag('container.hot_path');
                } elseif (isset($this->noPreloadEvents[$args[0]])) {
                    ++$noPreload;
                }
            }
            if ($noPreload && \count($extractingDispatcher->listeners) === $noPreload) {
                $container->getDefinition($id)->addTag('container.no_preload');
            }
            $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 "kernel.event_listener" tags.', $id));
        }

        return $name;
    }
}

/**
 * @internal
 */
class ExtractingEventDispatcher extends EventDispatcher implements EventSubscriberInterface
{
    public array $listeners = [];

    public static array $aliases = [];
    public static string $subscriber;

    public function addListener(string $eventName, callable|array $listener, int $priority = 0): void
    {
        $this->listeners[] = [$eventName, $listener[1], $priority];
    }

    public static function getSubscribedEvents(): array
    {
        $events = [];

        foreach ([self::$subscriber, 'getSubscribedEvents']() as $eventName => $params) {
            $events[self::$aliases[$eventName] ?? $eventName] = $params;
        }

        return $events;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\EventDispatcher;

/**
 * An EventSubscriber knows itself what events it is interested in.
 * If an EventSubscriber is added to an EventDispatcherInterface, the manager invokes
 * {@link getSubscribedEvents} and registers the subscriber as a listener for all
 * returned events.
 *
 * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
 * @author Jonathan Wage <jonwage@gmail.com>
 * @author Roman Borschel <roman@code-factory.org>
 * @author Bernhard Schussek <bschussek@gmail.com>
 */
interface EventSubscriberInterface
{
    /**
     * Returns an array of event names this subscriber wants to listen to.
     *
     * The array keys are event names and the value can be:
     *
     *  * The method name to call (priority defaults to 0)
     *  * An array composed of the method name to call and the priority
     *  * An array of arrays composed of the method names to call and respective
     *    priorities, or 0 if unset
     *
     * For instance:
     *
     *  * ['eventName' => 'methodName']
     *  * ['eventName' => ['methodName', $priority]]
     *  * ['eventName' => [['methodName1', $priority], ['methodName2']]]
     *
     * 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<string, string|array{0: string, 1: int}|list<array{0: string, 1?: int}>>
     */
    public static function getSubscribedEvents();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 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
 * manager.
 *
 * @author Bernhard Schussek <bschussek@gmail.com>
 */
interface EventDispatcherInterface extends ContractsEventDispatcherInterface
{
    /**
     * Adds an event listener that listens on the specified events.
     *
     * @param int $priority The higher this value, the earlier an event
     *                      listener will be triggered in the chain (defaults to 0)
     *
     * @return void
     */
    public function addListener(string $eventName, callable $listener, int $priority = 0);

    /**
     * Adds an event subscriber.
     *
     * The subscriber is asked for all the events it is
     * interested in and added as a listener for these events.
     *
     * @return void
     */
    public function addSubscriber(EventSubscriberInterface $subscriber);

    /**
     * Removes an event listener from the specified events.
     *
     * @return void
     */
    public function removeListener(string $eventName, callable $listener);

    /**
     * @return void
     */
    public function removeSubscriber(EventSubscriberInterface $subscriber);

    /**
     * Gets the listeners of a specific event or all listeners sorted by descending priority.
     *
     * @return array<callable[]|callable>
     */
    public function getListeners(?string $eventName = null): array;

    /**
     * Gets the listener priority for a specific event.
     *
     * Returns null if the event or the listener does not exist.
     */
    public function getListenerPriority(string $eventName, callable $listener): ?int;

    /**
     * Checks whether an event has any registered listeners.
     */
    public function hasListeners(?string $eventName = null): bool;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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\FileNotFoundException;
use Symfony\Component\Filesystem\Exception\InvalidArgumentException;
use Symfony\Component\Filesystem\Exception\IOException;

/**
 * Provides basic utility to manipulate the file system.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Filesystem
{
    private static ?string $lastError = null;

    /**
     * Copies a file.
     *
     * If the target file is older than the origin file, it's always overwritten.
     * If the target file is newer, it is overwritten only when the
     * $overwriteNewerFiles option is set to true.
     *
     * @return void
     *
     * @throws FileNotFoundException When originFile doesn't exist
     * @throws IOException           When copy fails
     */
    public function copy(string $originFile, string $targetFile, bool $overwriteNewerFiles = false)
    {
        $originIsLocal = stream_is_local($originFile) || 0 === stripos($originFile, 'file://');
        if ($originIsLocal && !is_file($originFile)) {
            throw new FileNotFoundException(\sprintf('Failed to copy "%s" because file does not exist.', $originFile), 0, null, $originFile);
        }

        $this->mkdir(\dirname($targetFile));

        $doCopy = true;
        if (!$overwriteNewerFiles && !parse_url($originFile, \PHP_URL_HOST) && is_file($targetFile)) {
            $doCopy = filemtime($originFile) > filemtime($targetFile);
        }

        if ($doCopy) {
            // https://bugs.php.net/64634
            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 (!$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);
            fclose($source);
            fclose($target);
            unset($source, $target);

            if (!is_file($targetFile)) {
                throw new IOException(\sprintf('Failed to copy "%s" to "%s".', $originFile, $targetFile), 0, null, $originFile);
            }

            if ($originIsLocal) {
                // Like `cp`, preserve executable permission bits
                self::box('chmod', $targetFile, fileperms($targetFile) | (fileperms($originFile) & 0111));

                // Like `cp`, preserve the file modification time
                self::box('touch', $targetFile, filemtime($originFile));

                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);
                }
            }
        }
    }

    /**
     * Creates a directory recursively.
     *
     * @return void
     *
     * @throws IOException On any directory creation failure
     */
    public function mkdir(string|iterable $dirs, int $mode = 0777)
    {
        foreach ($this->toIterable($dirs) as $dir) {
            if (is_dir($dir)) {
                continue;
            }

            if (!self::box('mkdir', $dir, $mode, true) && !is_dir($dir)) {
                throw new IOException(\sprintf('Failed to create "%s": ', $dir).self::$lastError, 0, null, $dir);
            }
        }
    }

    /**
     * Checks the existence of files or directories.
     */
    public function exists(string|iterable $files): bool
    {
        $maxPathLength = \PHP_MAXPATHLEN - 2;

        foreach ($this->toIterable($files) as $file) {
            if (\strlen($file) > $maxPathLength) {
                throw new IOException(\sprintf('Could not check if file exist because path length exceeds %d characters.', $maxPathLength), 0, null, $file);
            }

            if (!file_exists($file)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Sets access and modification time of file.
     *
     * @param int|null $time  The touch time as a Unix timestamp, if not supplied the current system time is used
     * @param int|null $atime The access time as a Unix timestamp, if not supplied the current system time is used
     *
     * @return void
     *
     * @throws IOException When touch fails
     */
    public function touch(string|iterable $files, ?int $time = null, ?int $atime = null)
    {
        foreach ($this->toIterable($files) as $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);
            }
        }
    }

    /**
     * Removes files or directories.
     *
     * @return void
     *
     * @throws IOException When removal fails
     */
    public function remove(string|iterable $files)
    {
        if ($files instanceof \Traversable) {
            $files = iterator_to_array($files, false);
        } 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)) {
                // See https://bugs.php.net/52176
                if (!(self::box('unlink', $file) || '\\' !== \DIRECTORY_SEPARATOR || self::box('rmdir', $file)) && file_exists($file)) {
                    throw new IOException(\sprintf('Failed to remove symlink "%s": ', $file).self::$lastError);
                }
            } elseif (is_dir($file)) {
                if (!$isRecursive) {
                    $tmpName = \dirname(realpath($file)).'/.!'.strrev(strtr(base64_encode(random_bytes(2)), '/=', '-!'));

                    if (file_exists($tmpName)) {
                        try {
                            self::doRemove([$tmpName], true);
                        } catch (IOException) {
                        }
                    }

                    if (!file_exists($tmpName) && self::box('rename', $file, $tmpName)) {
                        $origFile = $file;
                        $file = $tmpName;
                    } else {
                        $origFile = null;
                    }
                }

                $filesystemIterator = new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS);
                self::doRemove(iterator_to_array($filesystemIterator, 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) && ((self::$lastError && str_contains(self::$lastError, 'Permission denied')) || file_exists($file))) {
                throw new IOException(\sprintf('Failed to remove file "%s": ', $file).self::$lastError);
            }
        }
    }

    /**
     * Change mode for an array of files or directories.
     *
     * @param int  $mode      The new mode (octal)
     * @param int  $umask     The mode mask (octal)
     * @param bool $recursive Whether change the mod recursively or not
     *
     * @return void
     *
     * @throws IOException When the change fails
     */
    public function chmod(string|iterable $files, int $mode, int $umask = 0000, bool $recursive = false)
    {
        foreach ($this->toIterable($files) as $file) {
            if (!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);
            }
        }
    }

    /**
     * Change the owner of an array of files or directories.
     *
     * This method always throws on Windows, as the underlying PHP function is not supported.
     *
     * @see https://php.net/chown
     *
     * @param string|int $user      A user name or number
     * @param bool       $recursive Whether change the owner recursively or not
     *
     * @return void
     *
     * @throws IOException When the change fails
     */
    public function chown(string|iterable $files, string|int $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 (!self::box('lchown', $file, $user)) {
                    throw new IOException(\sprintf('Failed to chown file "%s": ', $file).self::$lastError, 0, null, $file);
                }
            } else {
                if (!self::box('chown', $file, $user)) {
                    throw new IOException(\sprintf('Failed to chown file "%s": ', $file).self::$lastError, 0, null, $file);
                }
            }
        }
    }

    /**
     * Change the group of an array of files or directories.
     *
     * This method always throws on Windows, as the underlying PHP function is not supported.
     *
     * @see https://php.net/chgrp
     *
     * @param string|int $group     A group name or number
     * @param bool       $recursive Whether change the group recursively or not
     *
     * @return void
     *
     * @throws IOException When the change fails
     */
    public function chgrp(string|iterable $files, string|int $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 (!self::box('lchgrp', $file, $group)) {
                    throw new IOException(\sprintf('Failed to chgrp file "%s": ', $file).self::$lastError, 0, null, $file);
                }
            } else {
                if (!self::box('chgrp', $file, $group)) {
                    throw new IOException(\sprintf('Failed to chgrp file "%s": ', $file).self::$lastError, 0, null, $file);
                }
            }
        }
    }

    /**
     * Renames a file or a directory.
     *
     * @return void
     *
     * @throws IOException When target file or directory already exists
     * @throws IOException When origin cannot be renamed
     */
    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 (!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]);
                $this->remove($origin);

                return;
            }
            throw new IOException(\sprintf('Cannot rename "%s" to "%s": ', $origin, $target).self::$lastError, 0, null, $target);
        }
    }

    /**
     * Tells whether a file exists and is readable.
     *
     * @throws IOException When windows path is longer than 258 characters
     */
    private function isReadable(string $filename): bool
    {
        $maxPathLength = \PHP_MAXPATHLEN - 2;

        if (\strlen($filename) > $maxPathLength) {
            throw new IOException(\sprintf('Could not check if file is readable because path length exceeds %d characters.', $maxPathLength), 0, null, $filename);
        }

        return is_readable($filename);
    }

    /**
     * Creates a symbolic link or copy a directory.
     *
     * @return void
     *
     * @throws IOException When symlink fails
     */
    public function symlink(string $originDir, string $targetDir, bool $copyOnWindows = false)
    {
        self::assertFunctionExists('symlink');

        if ('\\' === \DIRECTORY_SEPARATOR) {
            $originDir = strtr($originDir, '/', '\\');
            $targetDir = strtr($targetDir, '/', '\\');

            if ($copyOnWindows) {
                $this->mirror($originDir, $targetDir);

                return;
            }
        }

        $this->mkdir(\dirname($targetDir));

        if (is_link($targetDir)) {
            if (readlink($targetDir) === $originDir) {
                return;
            }
            $this->remove($targetDir);
        }

        if (!self::box('symlink', $originDir, $targetDir)) {
            $this->linkException($originDir, $targetDir, 'symbolic');
        }
    }

    /**
     * Creates a hard link, or several hard links to a file.
     *
     * @param string|string[] $targetFiles The target file(s)
     *
     * @return void
     *
     * @throws FileNotFoundException When original file is missing or not a file
     * @throws IOException           When link fails, including if link already exists
     */
    public function hardlink(string $originFile, string|iterable $targetFiles)
    {
        self::assertFunctionExists('link');

        if (!$this->exists($originFile)) {
            throw new FileNotFoundException(null, 0, null, $originFile);
        }

        if (!is_file($originFile)) {
            throw new FileNotFoundException(\sprintf('Origin file "%s" is not a file.', $originFile));
        }

        foreach ($this->toIterable($targetFiles) as $targetFile) {
            if (is_file($targetFile)) {
                if (fileinode($originFile) === fileinode($targetFile)) {
                    continue;
                }
                $this->remove($targetFile);
            }

            if (!self::box('link', $originFile, $targetFile)) {
                $this->linkException($originFile, $targetFile, 'hard');
            }
        }
    }

    /**
     * @param string $linkType Name of the link type, typically 'symbolic' or 'hard'
     */
    private function linkException(string $origin, string $target, string $linkType): never
    {
        if (self::$lastError) {
            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).self::$lastError, 0, null, $target);
    }

    /**
     * Resolves links in paths.
     *
     * With $canonicalize = false (default)
     *      - if $path does not exist or is not a link, returns null
     *      - if $path is a link, returns the next direct target of the link without considering the existence of the target
     *
     * With $canonicalize = true
     *      - if $path does not exist, returns null
     *      - if $path exists, returns its absolute fully resolved final version
     */
    public function readlink(string $path, bool $canonicalize = false): ?string
    {
        if (!$canonicalize && !is_link($path)) {
            return null;
        }

        if ($canonicalize) {
            if (!$this->exists($path)) {
                return null;
            }

            return realpath($path);
        }

        return readlink($path);
    }

    /**
     * Given an existing path, convert it to a path relative to a given starting path.
     */
    public function makePathRelative(string $endPath, string $startPath): string
    {
        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
        if ('\\' === \DIRECTORY_SEPARATOR) {
            $endPath = str_replace('\\', '/', $endPath);
            $startPath = str_replace('\\', '/', $startPath);
        }

        $splitDriveLetter = fn ($path) => (\strlen($path) > 2 && ':' === $path[1] && '/' === $path[2] && ctype_alpha($path[0]))
            ? [substr($path, 2), strtoupper($path[0])]
            : [$path, null];

        $splitPath = function ($path) {
            $result = [];

            foreach (explode('/', trim($path, '/')) as $segment) {
                if ('..' === $segment) {
                    array_pop($result);
                } elseif ('.' !== $segment && '' !== $segment) {
                    $result[] = $segment;
                }
            }

            return $result;
        };

        [$endPath, $endDriveLetter] = $splitDriveLetter($endPath);
        [$startPath, $startDriveLetter] = $splitDriveLetter($startPath);

        $startPathArr = $splitPath($startPath);
        $endPathArr = $splitPath($endPath);

        if ($endDriveLetter && $startDriveLetter && $endDriveLetter != $startDriveLetter) {
            // End path is on another drive, so no relative path exists
            return $endDriveLetter.':/'.($endPathArr ? implode('/', $endPathArr).'/' : '');
        }

        // Find for which directory the common path stops
        $index = 0;
        while (isset($startPathArr[$index]) && isset($endPathArr[$index]) && $startPathArr[$index] === $endPathArr[$index]) {
            ++$index;
        }

        // Determine how deep the start path is relative to the common path (ie, "web/bundles" = 2 levels)
        if (1 === \count($startPathArr) && '' === $startPathArr[0]) {
            $depth = 0;
        } else {
            $depth = \count($startPathArr) - $index;
        }

        // Repeated "../" for each level need to reach the common path
        $traverser = str_repeat('../', $depth);

        $endPathRemainder = implode('/', \array_slice($endPathArr, $index));

        // Construct $endPath from traversing to the common path, then to the remaining $endPath
        $relativePath = $traverser.('' !== $endPathRemainder ? $endPathRemainder.'/' : '');

        return '' === $relativePath ? './' : $relativePath;
    }

    /**
     * Mirrors a directory to another.
     *
     * Copies files and directories from the origin directory into the target directory. By default:
     *
     *  - 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 \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)
     *
     * @return void
     *
     * @throws IOException When file type is unknown
     */
    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;
            if (null === $deleteIterator) {
                $flags = \FilesystemIterator::SKIP_DOTS;
                $deleteIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($targetDir, $flags), \RecursiveIteratorIterator::CHILD_FIRST);
            }
            $targetDirLen = \strlen($targetDir);
            foreach ($deleteIterator as $file) {
                $origin = $originDir.substr($file->getPathname(), $targetDirLen);
                if (!$this->exists($origin)) {
                    $this->remove($file);
                }
            }
        }

        $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);
        }

        $this->mkdir($targetDir);
        $filesCreatedWhileMirroring = [];

        foreach ($iterator as $file) {
            if ($file->getPathname() === $targetDir || $file->getRealPath() === $targetDir || isset($filesCreatedWhileMirroring[$file->getRealPath()])) {
                continue;
            }

            $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 {
                throw new IOException(\sprintf('Unable to guess "%s" file type.', $file), 0, null, $file);
            }
        }
    }

    /**
     * Returns whether the file path is an absolute path.
     */
    public function isAbsolutePath(string $file): bool
    {
        return '' !== $file && (strspn($file, '/\\', 0, 1)
            || (\strlen($file) > 3 && ctype_alpha($file[0])
                && ':' === $file[1]
                && strspn($file, '/\\', 2, 1)
            )
            || null !== parse_url($file, \PHP_URL_SCHEME)
        );
    }

    /**
     * Creates a temporary file with support for custom stream wrappers.
     *
     * @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(string $dir, string $prefix, string $suffix = ''): string
    {
        [$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) && '' === $suffix) {
            // If tempnam failed or no scheme return the filename otherwise prepend the scheme
            if ($tmpFile = self::box('tempnam', $hierarchy, $prefix)) {
                if (null !== $scheme && 'gs' !== $scheme) {
                    return $scheme.'://'.$tmpFile;
                }

                return $tmpFile;
            }

            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).$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
            if (!$handle = self::box('fopen', $tmpFile, 'x+')) {
                continue;
            }

            // Close the file if it was successfully opened
            self::box('fclose', $handle);

            return $tmpFile;
        }

        throw new IOException('A temporary file could not be created: '.self::$lastError);
    }

    /**
     * Atomically dumps content into a file.
     *
     * @param string|resource $content The data to write into the file
     *
     * @return void
     *
     * @throws IOException if the file cannot be written to
     */
    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_link($filename) && $linkTarget = $this->readlink($filename)) {
            $this->dumpFile(Path::makeAbsolute($linkTarget, $dir), $content);

            return;
        }

        if (!is_dir($dir)) {
            $this->mkdir($dir);
        }

        // Will create a temp file with 0600 access rights
        // when the filesystem supports chmod.
        $tmpFile = $this->tempnam($dir, basename($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, self::box('fileperms', $filename) ?: 0666 & ~umask());

            $this->rename($tmpFile, $filename, true);
        } finally {
            if (file_exists($tmpFile)) {
                if ('\\' === \DIRECTORY_SEPARATOR && !is_writable($tmpFile)) {
                    self::box('chmod', $tmpFile, self::box('fileperms', $tmpFile) | 0200);
                }

                self::box('unlink', $tmpFile);
            }
        }
    }

    /**
     * Appends content to an existing file.
     *
     * @param string|resource $content The content to append
     * @param bool            $lock    Whether the file should be locked when writing to it
     *
     * @return void
     *
     * @throws IOException If the file is not writable
     */
    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);
        }

        $lock = \func_num_args() > 2 && func_get_arg(2);

        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);
        }
    }

    private function toIterable(string|iterable $files): iterable
    {
        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]).
     */
    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));
        }
    }

    private static function box(string $func, mixed ...$args): mixed
    {
        self::assertFunctionExists($func);

        self::$lastError = null;
        set_error_handler(self::handleError(...));
        try {
            return $func(...$args);
        } finally {
            restore_error_handler();
        }
    }

    /**
     * @internal
     */
    public static function handleError(int $type, string $msg): void
    {
        self::$lastError = $msg;
    }
}
Copyright (c) 2004-present Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Filesystem\Exception;

/**
 * Exception class thrown when a filesystem operation failure happens.
 *
 * @author Romain Neutron <imprec@gmail.com>
 * @author Christian Gärtner <christiangaertner.film@googlemail.com>
 * @author Fabien Potencier <fabien@symfony.com>
 */
class IOException extends \RuntimeException implements IOExceptionInterface
{
    private ?string $path;

    public function __construct(string $message, int $code = 0, ?\Throwable $previous = null, ?string $path = null)
    {
        $this->path = $path;

        parent::__construct($message, $code, $previous);
    }

    public function getPath(): ?string
    {
        return $this->path;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Filesystem\Exception;

/**
 * IOException interface for file and input/output stream related exceptions thrown by the component.
 *
 * @author Christian Gärtner <christiangaertner.film@googlemail.com>
 */
interface IOExceptionInterface extends ExceptionInterface
{
    /**
     * Returns the associated path for the exception.
     */
    public function getPath(): ?string;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <christian.flothmann@sensiolabs.de>
 */
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Filesystem\Exception;

/**
 * Exception class thrown when a file couldn't be found.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Christian Gärtner <christiangaertner.film@googlemail.com>
 */
class FileNotFoundException extends IOException
{
    public function __construct(?string $message = null, int $code = 0, ?\Throwable $previous = null, ?string $path = null)
    {
        if (null === $message) {
            if (null === $path) {
                $message = 'File could not be found.';
            } else {
                $message = \sprintf('File "%s" could not be found.', $path);
            }
        }

        parent::__construct($message, $code, $previous, $path);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <theo.fidry@gmail.com>
 */
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Filesystem\Exception;

/**
 * Exception interface for all exceptions thrown by the component.
 *
 * @author Romain Neutron <imprec@gmail.com>
 */
interface ExceptionInterface extends \Throwable
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <bschussek@gmail.com>
 * @author Thomas Schulz <mail@king2500.net>
 * @author Théo Fidry <theo.fidry@gmail.com>
 */
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<string, string>
     */
    private static array $buffer = [];

    private static int $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().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 = strpos($path, '://')) {
            $scheme = substr($path, 0, $schemeSeparatorPosition + 3);
            $path = 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.substr($path, 0, 3);
        }

        return $scheme.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 = \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 ('/' === substr($path, -1)) {
            return $path;
        }

        // No actual extension in path
        if (empty($actualExtension)) {
            return $path.('.' === substr($path, -1) ? '' : '.').$extension;
        }

        return substr($path, 0, -\strlen($actualExtension)).$extension;
    }

    public static function isAbsolute(string $path): bool
    {
        if ('' === $path) {
            return false;
        }

        // Strip scheme
        if (false !== ($schemeSeparatorPosition = strpos($path, '://')) && 1 !== $schemeSeparatorPosition) {
            $path = substr($path, $schemeSeparatorPosition + 3);
        }

        $firstCharacter = $path[0];

        // UNIX root "/" or "\" (Windows style)
        if ('/' === $firstCharacter || '\\' === $firstCharacter) {
            return true;
        }

        // Windows root
        if (\strlen($path) > 1 && ctype_alpha($firstCharacter) && ':' === $path[1]) {
            // Special case: "C:"
            if (2 === \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 = strpos($basePath, '://')) {
            $scheme = substr($basePath, 0, $schemeSeparatorPosition + 3);
            $basePath = 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 && !str_contains($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 (str_starts_with($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 = str_contains($path, '://');
                continue;
            }

            // Only add slash if previous part didn't end with '/' or '\'
            if (!\in_array(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 str_starts_with($ofPath.'/', rtrim($basePath, '/').'/');
    }

    /**
     * @return 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 = strpos($path, '://')) {
            $root = substr($path, 0, $schemeSeparatorPosition + 3);
            $path = substr($path, $schemeSeparatorPosition + 3);
        } else {
            $root = '';
        }

        $length = \strlen($path);

        // Remove and remember root directory
        if (str_starts_with($path, '/')) {
            $root .= '/';
            $path = $length > 1 ? 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 .= substr($path, 0, 3);
                $path = $length > 3 ? substr($path, 3) : '';
            }
        }

        return [$root, $path];
    }

    private static function toLower(string $string): string
    {
        if (false !== $encoding = mb_detect_encoding($string, null, true)) {
            return mb_strtolower($string, $encoding);
        }

        return strtolower($string);
    }

    private function __construct()
    {
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

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;

/**
 * Finder allows to build rules to find files and directories.
 *
 * It is a thin wrapper around several specialized iterator classes.
 *
 * All rules may be invoked several times.
 *
 * All methods return the current Finder object to allow chaining:
 *
 *     $finder = Finder::create()->files()->name('*.php')->in(__DIR__);
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @implements \IteratorAggregate<string, SplFileInfo>
 */
class Finder implements \IteratorAggregate, \Countable
{
    public const IGNORE_VCS_FILES = 1;
    public const IGNORE_DOT_FILES = 2;
    public const IGNORE_VCS_IGNORED_FILES = 4;

    private int $mode = 0;
    private array $names = [];
    private array $notNames = [];
    private array $exclude = [];
    private array $filters = [];
    private array $pruneFilters = [];
    private array $depths = [];
    private array $sizes = [];
    private bool $followLinks = false;
    private bool $reverseSorting = false;
    private \Closure|int|false $sort = false;
    private int $ignore = 0;
    private array $dirs = [];
    private array $dates = [];
    private array $iterators = [];
    private array $contains = [];
    private array $notContains = [];
    private array $paths = [];
    private array $notPaths = [];
    private bool $ignoreUnreadableDirs = false;

    private static array $vcsPatterns = ['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg'];

    public function __construct()
    {
        $this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES;
    }

    /**
     * Creates a new Finder.
     */
    public static function create(): static
    {
        return new static();
    }

    /**
     * Restricts the matching to directories only.
     *
     * @return $this
     */
    public function directories(): static
    {
        $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES;

        return $this;
    }

    /**
     * Restricts the matching to files only.
     *
     * @return $this
     */
    public function files(): static
    {
        $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES;

        return $this;
    }

    /**
     * Adds tests for the directory depth.
     *
     * Usage:
     *
     *     $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|string[]|int[] $levels The depth level expression or an array of depth levels
     *
     * @return $this
     *
     * @see DepthRangeFilterIterator
     * @see NumberComparator
     */
    public function depth(string|int|array $levels): static
    {
        foreach ((array) $levels as $level) {
            $this->depths[] = new NumberComparator($level);
        }

        return $this;
    }

    /**
     * Adds tests for file dates (last modified).
     *
     * The date must be something that strtotime() is able to parse:
     *
     *     $finder->date('since yesterday');
     *     $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|string[] $dates A date range string or an array of date ranges
     *
     * @return $this
     *
     * @see strtotime
     * @see DateRangeFilterIterator
     * @see DateComparator
     */
    public function date(string|array $dates): static
    {
        foreach ((array) $dates as $date) {
            $this->dates[] = new DateComparator($date);
        }

        return $this;
    }

    /**
     * Adds rules that files must match.
     *
     * You can use patterns (delimited with / sign), globs or simple strings.
     *
     *     $finder->name('/\.php$/')
     *     $finder->name('*.php') // same as above, without dot files
     *     $finder->name('test.php')
     *     $finder->name(['test.py', 'test.php'])
     *
     * @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(string|array $patterns): static
    {
        $this->names = array_merge($this->names, (array) $patterns);

        return $this;
    }

    /**
     * Adds rules that files must not match.
     *
     * @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(string|array $patterns): static
    {
        $this->notNames = array_merge($this->notNames, (array) $patterns);

        return $this;
    }

    /**
     * Adds tests that file contents must match.
     *
     * Strings or PCRE patterns can be used:
     *
     *     $finder->contains('Lorem ipsum')
     *     $finder->contains('/Lorem ipsum/i')
     *     $finder->contains(['dolor', '/ipsum/i'])
     *
     * @param string|string[] $patterns A pattern (string or regexp) or an array of patterns
     *
     * @return $this
     *
     * @see FilecontentFilterIterator
     */
    public function contains(string|array $patterns): static
    {
        $this->contains = array_merge($this->contains, (array) $patterns);

        return $this;
    }

    /**
     * Adds tests that file contents must not match.
     *
     * Strings or PCRE patterns can be used:
     *
     *     $finder->notContains('Lorem ipsum')
     *     $finder->notContains('/Lorem ipsum/i')
     *     $finder->notContains(['lorem', '/dolor/i'])
     *
     * @param string|string[] $patterns A pattern (string or regexp) or an array of patterns
     *
     * @return $this
     *
     * @see FilecontentFilterIterator
     */
    public function notContains(string|array $patterns): static
    {
        $this->notContains = array_merge($this->notContains, (array) $patterns);

        return $this;
    }

    /**
     * Adds rules that filenames must match.
     *
     * You can use patterns (delimited with / sign) or simple strings.
     *
     *     $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|string[] $patterns A pattern (a regexp or a string) or an array of patterns
     *
     * @return $this
     *
     * @see FilenameFilterIterator
     */
    public function path(string|array $patterns): static
    {
        $this->paths = array_merge($this->paths, (array) $patterns);

        return $this;
    }

    /**
     * Adds rules that filenames must not match.
     *
     * You can use patterns (delimited with / sign) or simple strings.
     *
     *     $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|string[] $patterns A pattern (a regexp or a string) or an array of patterns
     *
     * @return $this
     *
     * @see FilenameFilterIterator
     */
    public function notPath(string|array $patterns): static
    {
        $this->notPaths = array_merge($this->notPaths, (array) $patterns);

        return $this;
    }

    /**
     * Adds tests for file sizes.
     *
     *     $finder->size('> 10K');
     *     $finder->size('<= 1Ki');
     *     $finder->size(4);
     *     $finder->size(['> 10K', '< 20K'])
     *
     * @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(string|int|array $sizes): static
    {
        foreach ((array) $sizes as $size) {
            $this->sizes[] = new NumberComparator($size);
        }

        return $this;
    }

    /**
     * Excludes directories.
     *
     * Directories passed as argument must be relative to the ones defined with the `in()` method. For example:
     *
     *     $finder->in(__DIR__)->exclude('ruby');
     *
     * @param string|array $dirs A directory path or an array of directories
     *
     * @return $this
     *
     * @see ExcludeDirectoryFilterIterator
     */
    public function exclude(string|array $dirs): static
    {
        $this->exclude = array_merge($this->exclude, (array) $dirs);

        return $this;
    }

    /**
     * Excludes "hidden" directories and files (starting with a dot).
     *
     * This option is enabled by default.
     *
     * @return $this
     *
     * @see ExcludeDirectoryFilterIterator
     */
    public function ignoreDotFiles(bool $ignoreDotFiles): static
    {
        if ($ignoreDotFiles) {
            $this->ignore |= static::IGNORE_DOT_FILES;
        } else {
            $this->ignore &= ~static::IGNORE_DOT_FILES;
        }

        return $this;
    }

    /**
     * Forces the finder to ignore version control directories.
     *
     * This option is enabled by default.
     *
     * @return $this
     *
     * @see ExcludeDirectoryFilterIterator
     */
    public function ignoreVCS(bool $ignoreVCS): static
    {
        if ($ignoreVCS) {
            $this->ignore |= static::IGNORE_VCS_FILES;
        } else {
            $this->ignore &= ~static::IGNORE_VCS_FILES;
        }

        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): static
    {
        if ($ignoreVCSIgnored) {
            $this->ignore |= static::IGNORE_VCS_IGNORED_FILES;
        } else {
            $this->ignore &= ~static::IGNORE_VCS_IGNORED_FILES;
        }

        return $this;
    }

    /**
     * Adds VCS patterns.
     *
     * @see ignoreVCS()
     *
     * @param string|string[] $pattern VCS patterns to ignore
     *
     * @return void
     */
    public static function addVCSPattern(string|array $pattern)
    {
        foreach ((array) $pattern as $p) {
            self::$vcsPatterns[] = $p;
        }

        self::$vcsPatterns = array_unique(self::$vcsPatterns);
    }

    /**
     * Sorts files and directories by an anonymous function.
     *
     * The anonymous function receives two \SplFileInfo instances to compare.
     *
     * This can be slow as all the matching files and directories must be retrieved for comparison.
     *
     * @return $this
     *
     * @see SortableIterator
     */
    public function sort(\Closure $closure): static
    {
        $this->sort = $closure;

        return $this;
    }

    /**
     * Sorts files and directories by extension.
     *
     * This can be slow as all the matching files and directories must be retrieved for comparison.
     *
     * @return $this
     *
     * @see SortableIterator
     */
    public function sortByExtension(): static
    {
        $this->sort = SortableIterator::SORT_BY_EXTENSION;

        return $this;
    }

    /**
     * Sorts files and directories by name.
     *
     * This can be slow as all the matching files and directories must be retrieved for comparison.
     *
     * @return $this
     *
     * @see SortableIterator
     */
    public function sortByName(bool $useNaturalSort = false): static
    {
        $this->sort = $useNaturalSort ? SortableIterator::SORT_BY_NAME_NATURAL : SortableIterator::SORT_BY_NAME;

        return $this;
    }

    /**
     * Sorts files and directories by name case insensitive.
     *
     * This can be slow as all the matching files and directories must be retrieved for comparison.
     *
     * @return $this
     *
     * @see SortableIterator
     */
    public function sortByCaseInsensitiveName(bool $useNaturalSort = false): static
    {
        $this->sort = $useNaturalSort ? SortableIterator::SORT_BY_NAME_NATURAL_CASE_INSENSITIVE : SortableIterator::SORT_BY_NAME_CASE_INSENSITIVE;

        return $this;
    }

    /**
     * Sorts files and directories by size.
     *
     * This can be slow as all the matching files and directories must be retrieved for comparison.
     *
     * @return $this
     *
     * @see SortableIterator
     */
    public function sortBySize(): static
    {
        $this->sort = SortableIterator::SORT_BY_SIZE;

        return $this;
    }

    /**
     * Sorts files and directories by type (directories before files), then by name.
     *
     * This can be slow as all the matching files and directories must be retrieved for comparison.
     *
     * @return $this
     *
     * @see SortableIterator
     */
    public function sortByType(): static
    {
        $this->sort = SortableIterator::SORT_BY_TYPE;

        return $this;
    }

    /**
     * Sorts files and directories by the last accessed time.
     *
     * This is the time that the file was last accessed, read or written to.
     *
     * This can be slow as all the matching files and directories must be retrieved for comparison.
     *
     * @return $this
     *
     * @see SortableIterator
     */
    public function sortByAccessedTime(): static
    {
        $this->sort = SortableIterator::SORT_BY_ACCESSED_TIME;

        return $this;
    }

    /**
     * Reverses the sorting.
     *
     * @return $this
     */
    public function reverseSorting(): static
    {
        $this->reverseSorting = true;

        return $this;
    }

    /**
     * Sorts files and directories by the last inode changed time.
     *
     * This is the time that the inode information was last modified (permissions, owner, group or other metadata).
     *
     * On Windows, since inode is not available, changed time is actually the file creation time.
     *
     * This can be slow as all the matching files and directories must be retrieved for comparison.
     *
     * @return $this
     *
     * @see SortableIterator
     */
    public function sortByChangedTime(): static
    {
        $this->sort = SortableIterator::SORT_BY_CHANGED_TIME;

        return $this;
    }

    /**
     * Sorts files and directories by the last modified time.
     *
     * This is the last time the actual contents of the file were last modified.
     *
     * This can be slow as all the matching files and directories must be retrieved for comparison.
     *
     * @return $this
     *
     * @see SortableIterator
     */
    public function sortByModifiedTime(): static
    {
        $this->sort = SortableIterator::SORT_BY_MODIFIED_TIME;

        return $this;
    }

    /**
     * Filters the iterator with an anonymous function.
     *
     * The anonymous function receives a \SplFileInfo and must return false
     * to remove files.
     *
     * @param \Closure(SplFileInfo): bool $closure
     * @param bool                        $prune   Whether to skip traversing directories further
     *
     * @return $this
     *
     * @see CustomFilterIterator
     */
    public function filter(\Closure $closure /* , bool $prune = false */): static
    {
        $prune = 1 < \func_num_args() ? func_get_arg(1) : false;
        $this->filters[] = $closure;

        if ($prune) {
            $this->pruneFilters[] = $closure;
        }

        return $this;
    }

    /**
     * Forces the following of symlinks.
     *
     * @return $this
     */
    public function followLinks(): static
    {
        $this->followLinks = true;

        return $this;
    }

    /**
     * Tells finder to ignore unreadable directories.
     *
     * By default, scanning unreadable directories content throws an AccessDeniedException.
     *
     * @return $this
     */
    public function ignoreUnreadableDirs(bool $ignore = true): static
    {
        $this->ignoreUnreadableDirs = $ignore;

        return $this;
    }

    /**
     * Searches files and directories which match defined rules.
     *
     * @param string|string[] $dirs A directory path or an array of directories
     *
     * @return $this
     *
     * @throws DirectoryNotFoundException if one of the directories does not exist
     */
    public function in(string|array $dirs): static
    {
        $resolvedDirs = [];

        foreach ((array) $dirs as $dir) {
            if (is_dir($dir)) {
                $resolvedDirs[] = [$this->normalizeDir($dir)];
            } elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? \GLOB_BRACE : 0) | \GLOB_ONLYDIR | \GLOB_NOSORT)) {
                sort($glob);
                $resolvedDirs[] = array_map($this->normalizeDir(...), $glob);
            } else {
                throw new DirectoryNotFoundException(\sprintf('The "%s" directory does not exist.', $dir));
            }
        }

        $this->dirs = array_merge($this->dirs, ...$resolvedDirs);

        return $this;
    }

    /**
     * Returns an Iterator for the current Finder configuration.
     *
     * This method implements the IteratorAggregate interface.
     *
     * @return \Iterator<string, SplFileInfo>
     *
     * @throws \LogicException if the in() method has not been called
     */
    public function getIterator(): \Iterator
    {
        if (0 === \count($this->dirs) && 0 === \count($this->iterators)) {
            throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.');
        }

        if (1 === \count($this->dirs) && 0 === \count($this->iterators)) {
            $iterator = $this->searchInDirectory($this->dirs[0]);

            if ($this->sort || $this->reverseSorting) {
                $iterator = (new SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator();
            }

            return $iterator;
        }

        $iterator = new \AppendIterator();
        foreach ($this->dirs as $dir) {
            $iterator->append(new \IteratorIterator(new LazyIterator(fn () => $this->searchInDirectory($dir))));
        }

        foreach ($this->iterators as $it) {
            $iterator->append($it);
        }

        if ($this->sort || $this->reverseSorting) {
            $iterator = (new SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator();
        }

        return $iterator;
    }

    /**
     * Appends an existing set of files/directories to the finder.
     *
     * The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array.
     *
     * @return $this
     *
     * @throws \InvalidArgumentException when the given argument is not iterable
     */
    public function append(iterable $iterator): static
    {
        if ($iterator instanceof \IteratorAggregate) {
            $this->iterators[] = $iterator->getIterator();
        } elseif ($iterator instanceof \Iterator) {
            $this->iterators[] = $iterator;
        } elseif (is_iterable($iterator)) {
            $it = new \ArrayIterator();
            foreach ($iterator as $file) {
                $file = $file instanceof \SplFileInfo ? $file : new \SplFileInfo($file);
                $it[$file->getPathname()] = $file;
            }
            $this->iterators[] = $it;
        } else {
            throw new \InvalidArgumentException('Finder::append() method wrong argument type.');
        }

        return $this;
    }

    /**
     * Check if any results were found.
     */
    public function hasResults(): bool
    {
        foreach ($this->getIterator() as $_) {
            return true;
        }

        return false;
    }

    /**
     * Counts all the results collected by the iterators.
     */
    public function count(): int
    {
        return iterator_count($this->getIterator());
    }

    private function searchInDirectory(string $dir): \Iterator
    {
        $exclude = $this->exclude;
        $notPaths = $this->notPaths;

        if ($this->pruneFilters) {
            $exclude = array_merge($exclude, $this->pruneFilters);
        }

        if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) {
            $exclude = array_merge($exclude, self::$vcsPatterns);
        }

        if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) {
            $notPaths[] = '#(^|/)\..+(/|$)#';
        }

        $minDepth = 0;
        $maxDepth = \PHP_INT_MAX;

        foreach ($this->depths as $comparator) {
            switch ($comparator->getOperator()) {
                case '>':
                    $minDepth = $comparator->getTarget() + 1;
                    break;
                case '>=':
                    $minDepth = $comparator->getTarget();
                    break;
                case '<':
                    $maxDepth = $comparator->getTarget() - 1;
                    break;
                case '<=':
                    $maxDepth = $comparator->getTarget();
                    break;
                default:
                    $minDepth = $maxDepth = $comparator->getTarget();
            }
        }

        $flags = \RecursiveDirectoryIterator::SKIP_DOTS;

        if ($this->followLinks) {
            $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS;
        }

        $iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs);

        if ($exclude) {
            $iterator = new ExcludeDirectoryFilterIterator($iterator, $exclude);
        }

        $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST);

        if ($minDepth > 0 || $maxDepth < \PHP_INT_MAX) {
            $iterator = new DepthRangeFilterIterator($iterator, $minDepth, $maxDepth);
        }

        if ($this->mode) {
            $iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode);
        }

        if ($this->names || $this->notNames) {
            $iterator = new FilenameFilterIterator($iterator, $this->names, $this->notNames);
        }

        if ($this->contains || $this->notContains) {
            $iterator = new FilecontentFilterIterator($iterator, $this->contains, $this->notContains);
        }

        if ($this->sizes) {
            $iterator = new SizeRangeFilterIterator($iterator, $this->sizes);
        }

        if ($this->dates) {
            $iterator = new DateRangeFilterIterator($iterator, $this->dates);
        }

        if ($this->filters) {
            $iterator = new CustomFilterIterator($iterator, $this->filters);
        }

        if ($this->paths || $notPaths) {
            $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $notPaths);
        }

        if (static::IGNORE_VCS_IGNORED_FILES === (static::IGNORE_VCS_IGNORED_FILES & $this->ignore)) {
            $iterator = new Iterator\VcsIgnoredFilterIterator($iterator, $dir);
        }

        return $iterator;
    }

    /**
     * Normalizes given directory names by removing trailing slashes.
     *
     * Excluding: (s)ftp:// or ssh2.(s)ftp:// wrapper
     */
    private function normalizeDir(string $dir): string
    {
        if ('/' === $dir) {
            return $dir;
        }

        $dir = rtrim($dir, '/'.\DIRECTORY_SEPARATOR);

        if (preg_match('#^(ssh2\.)?s?ftp://#', $dir)) {
            $dir .= '/';
        }

        return $dir;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder;

/**
 * Extends \SplFileInfo to support relative paths.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class SplFileInfo extends \SplFileInfo
{
    private string $relativePath;
    private string $relativePathname;

    /**
     * @param string $file             The file name
     * @param string $relativePath     The relative path
     * @param string $relativePathname The relative path name
     */
    public function __construct(string $file, string $relativePath, string $relativePathname)
    {
        parent::__construct($file);
        $this->relativePath = $relativePath;
        $this->relativePathname = $relativePathname;
    }

    /**
     * Returns the relative path.
     *
     * This path does not contain the file name.
     */
    public function getRelativePath(): string
    {
        return $this->relativePath;
    }

    /**
     * Returns the relative path name.
     *
     * This path contains the file name.
     */
    public function getRelativePathname(): string
    {
        return $this->relativePathname;
    }

    public function getFilenameWithoutExtension(): string
    {
        $filename = $this->getFilename();

        return pathinfo($filename, \PATHINFO_FILENAME);
    }

    /**
     * Returns the contents of the file.
     *
     * @throws \RuntimeException
     */
    public function getContents(): string
    {
        set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
        try {
            $content = file_get_contents($this->getPathname());
        } finally {
            restore_error_handler();
        }
        if (false === $content) {
            throw new \RuntimeException($error);
        }

        return $content;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Comparator;

/**
 * NumberComparator compiles a simple comparison to an anonymous
 * subroutine, which you can call with a value to be tested again.
 *
 * Now this would be very pointless, if NumberCompare didn't understand
 * magnitudes.
 *
 * The target value may use magnitudes of kilobytes (k, ki),
 * megabytes (m, mi), or gigabytes (g, gi).  Those suffixed
 * with an i use the appropriate 2**n version in accordance with the
 * IEC standard: http://physics.nist.gov/cuu/Units/binary.html
 *
 * Based on the Perl Number::Compare module.
 *
 * @author    Fabien Potencier <fabien@symfony.com> PHP port
 * @author    Richard Clamp <richardc@unixbeard.net> Perl version
 * @copyright 2004-2005 Fabien Potencier <fabien@symfony.com>
 * @copyright 2002 Richard Clamp <richardc@unixbeard.net>
 *
 * @see http://physics.nist.gov/cuu/Units/binary.html
 */
class NumberComparator extends Comparator
{
    /**
     * @param string|null $test A comparison string or null
     *
     * @throws \InvalidArgumentException If the test is not understood
     */
    public function __construct(?string $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];
        if (!is_numeric($target)) {
            throw new \InvalidArgumentException(\sprintf('Invalid number "%s".', $target));
        }
        if (isset($matches[3])) {
            // magnitude
            switch (strtolower($matches[3])) {
                case 'k':
                    $target *= 1000;
                    break;
                case 'ki':
                    $target *= 1024;
                    break;
                case 'm':
                    $target *= 1000000;
                    break;
                case 'mi':
                    $target *= 1024 * 1024;
                    break;
                case 'g':
                    $target *= 1000000000;
                    break;
                case 'gi':
                    $target *= 1024 * 1024 * 1024;
                    break;
            }
        }

        parent::__construct($target, $matches[1] ?: '==');
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Comparator;

/**
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Comparator
{
    private string $target;
    private string $operator;

    public function __construct(string $target, string $operator = '==')
    {
        if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) {
            throw new \InvalidArgumentException(\sprintf('Invalid operator "%s".', $operator));
        }

        $this->target = $target;
        $this->operator = $operator;
    }

    /**
     * Gets the target value.
     */
    public function getTarget(): string
    {
        return $this->target;
    }

    /**
     * Gets the comparison operator.
     */
    public function getOperator(): string
    {
        return $this->operator;
    }

    /**
     * Tests against the target.
     */
    public function test(mixed $test): bool
    {
        return match ($this->operator) {
            '>' => $test > $this->target,
            '>=' => $test >= $this->target,
            '<' => $test < $this->target,
            '<=' => $test <= $this->target,
            '!=' => $test != $this->target,
            default => $test == $this->target,
        };
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Comparator;

/**
 * DateCompare compiles date comparisons.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class DateComparator extends Comparator
{
    /**
     * @param string $test A comparison string
     *
     * @throws \InvalidArgumentException If the test is not understood
     */
    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));
        }

        try {
            $date = new \DateTimeImmutable($matches[2]);
            $target = $date->format('U');
        } catch (\Exception) {
            throw new \InvalidArgumentException(\sprintf('"%s" is not a valid date.', $matches[2]));
        }

        $operator = $matches[1] ?: '==';
        if ('since' === $operator || 'after' === $operator) {
            $operator = '>';
        }

        if ('until' === $operator || 'before' === $operator) {
            $operator = '<';
        }

        parent::__construct($target, $operator);
    }
}
Copyright (c) 2004-present Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <vorismi3@fel.cvut.cz>
 * @author Ahmed Abdou <mail@ahmd.io>
 */
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('~(?<!\\\\)#[^\n\r]*~', '', $gitignoreFileContent);
        $gitignoreLines = preg_split('~\r\n?|\n~', $gitignoreFileContent);

        $res = self::lineToRegex('');
        foreach ($gitignoreLines as $line) {
            $line = preg_replace('~(?<!\\\\)[ \t]+$~', '', $line);

            if (str_starts_with($line, '!')) {
                $line = substr($line, 1);
                $isNegative = true;
            } else {
                $isNegative = false;
            }

            if ('' !== $line) {
                if ($isNegative xor $inverted) {
                    $res = '(?!'.self::lineToRegex($line).'$)'.$res;
                } else {
                    $res = '(?:'.$res.'|'.self::lineToRegex($line).')';
                }
            }
        }

        return '~^(?:'.$res.')~s';
    }

    private static function lineToRegex(string $gitignoreLine): string
    {
        if ('' === $gitignoreLine) {
            return '$f'; // always false
        }

        $slashPos = strpos($gitignoreLine, '/');
        if (false !== $slashPos && \strlen($gitignoreLine) - 1 !== $slashPos) {
            if (0 === $slashPos) {
                $gitignoreLine = substr($gitignoreLine, 1);
            }
            $isAbsolute = true;
        } else {
            $isAbsolute = false;
        }

        $regex = preg_quote(str_replace('\\', '', $gitignoreLine), '~');
        $regex = preg_replace_callback('~\\\\\[((?:\\\\!)?)([^\[\]]*)\\\\\]~', fn (array $matches): string => '['.('' !== $matches[1] ? '^' : '').str_replace('\\-', '-', $matches[2]).']', $regex);
        $regex = preg_replace('~(?:(?:\\\\\*){2,}(/?))+~', '(?:(?:(?!//).(?<!//))+$1)?', $regex);
        $regex = preg_replace('~\\\\\*~', '[^/]*', $regex);
        $regex = preg_replace('~\\\\\?~', '[^/]', $regex);

        return ($isAbsolute ? '' : '(?:[^/]+/)*')
            .$regex
            .(!str_ends_with($gitignoreLine, '/') ? '(?:$|/)' : '');
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <jeanfrancois.simon@sensiolabs.com>
 */
class AccessDeniedException extends \UnexpectedValueException
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <andreas.erhard@i-med.ac.at>
 */
class DirectoryNotFoundException extends \InvalidArgumentException
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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\Exception\AccessDeniedException;
use Symfony\Component\Finder\SplFileInfo;

/**
 * Extends the \RecursiveDirectoryIterator to support relative paths.
 *
 * @author Victor Berchet <victor@suumit.com>
 *
 * @extends \RecursiveDirectoryIterator<string, SplFileInfo>
 */
class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator
{
    private bool $ignoreUnreadableDirs;
    private bool $ignoreFirstRewind = true;

    // these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations
    private string $rootPath;
    private string $subPath;
    private string $directorySeparator = '/';

    /**
     * @throws \RuntimeException
     */
    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.');
        }

        parent::__construct($path, $flags);
        $this->ignoreUnreadableDirs = $ignoreUnreadableDirs;
        $this->rootPath = $path;
        if ('/' !== \DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) {
            $this->directorySeparator = \DIRECTORY_SEPARATOR;
        }
    }

    /**
     * Return an instance of SplFileInfo with support for relative paths.
     */
    public function current(): SplFileInfo
    {
        // the logic here avoids redoing the same work in all iterations

        if (!isset($this->subPath)) {
            $this->subPath = $this->getSubPath();
        }
        $subPathname = $this->subPath;
        if ('' !== $subPathname) {
            $subPathname .= $this->directorySeparator;
        }
        $subPathname .= $this->getFilename();
        $basePath = $this->rootPath;

        if ('/' !== $basePath && !str_ends_with($basePath, $this->directorySeparator) && !str_ends_with($basePath, '/')) {
            $basePath .= $this->directorySeparator;
        }

        return new SplFileInfo($basePath.$subPathname, $this->subPath, $subPathname);
    }

    public function hasChildren(bool $allowLinks = false): bool
    {
        $hasChildren = parent::hasChildren($allowLinks);

        if (!$hasChildren || !$this->ignoreUnreadableDirs) {
            return $hasChildren;
        }

        try {
            parent::getChildren();

            return true;
        } catch (\UnexpectedValueException) {
            // If directory is unreadable and finder is set to ignore it, skip children
            return false;
        }
    }

    /**
     * @throws AccessDeniedException
     */
    public function getChildren(): \RecursiveDirectoryIterator
    {
        try {
            $children = parent::getChildren();

            if ($children instanceof self) {
                // parent method will call the constructor with default arguments, so unreadable dirs won't be ignored anymore
                $children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs;

                // performance optimization to avoid redoing the same work in all children
                $children->rootPath = $this->rootPath;
            }

            return $children;
        } catch (\UnexpectedValueException $e) {
            throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e);
        }
    }

    public function next(): void
    {
        $this->ignoreFirstRewind = false;

        parent::next();
    }

    public function rewind(): void
    {
        // some streams like FTP are not rewindable, ignore the first rewind after creation,
        // as newly created DirectoryIterator does not need to be rewound
        if ($this->ignoreFirstRewind) {
            $this->ignoreFirstRewind = false;

            return;
        }

        parent::rewind();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * DepthRangeFilterIterator limits the directory depth.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @template-covariant TKey
 * @template-covariant TValue
 *
 * @extends \FilterIterator<TKey, TValue>
 */
class DepthRangeFilterIterator extends \FilterIterator
{
    private int $minDepth = 0;

    /**
     * @param \RecursiveIteratorIterator<\RecursiveIterator<TKey, TValue>> $iterator The Iterator to filter
     * @param int                                                          $minDepth The min depth
     * @param int                                                          $maxDepth The max depth
     */
    public function __construct(\RecursiveIteratorIterator $iterator, int $minDepth = 0, int $maxDepth = \PHP_INT_MAX)
    {
        $this->minDepth = $minDepth;
        $iterator->setMaxDepth(\PHP_INT_MAX === $maxDepth ? -1 : $maxDepth);

        parent::__construct($iterator);
    }

    /**
     * Filters the iterator values.
     */
    public function accept(): bool
    {
        return $this->getInnerIterator()->getDepth() >= $this->minDepth;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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\SplFileInfo;

/**
 * FilecontentFilterIterator filters files by their contents using patterns (regexps or strings).
 *
 * @author Fabien Potencier  <fabien@symfony.com>
 * @author Włodzimierz Gajda <gajdaw@gajdaw.pl>
 *
 * @extends MultiplePcreFilterIterator<string, SplFileInfo>
 */
class FilecontentFilterIterator extends MultiplePcreFilterIterator
{
    /**
     * Filters the iterator values.
     */
    public function accept(): bool
    {
        if (!$this->matchRegexps && !$this->noMatchRegexps) {
            return true;
        }

        $fileinfo = $this->current();

        if ($fileinfo->isDir() || !$fileinfo->isReadable()) {
            return false;
        }

        $content = $fileinfo->getContents();
        if (!$content) {
            return false;
        }

        return $this->isAccepted($content);
    }

    /**
     * Converts string to regexp if necessary.
     *
     * @param string $str Pattern: string or regexp
     */
    protected function toRegex(string $str): string
    {
        return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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;

/**
 * @extends \FilterIterator<string, \SplFileInfo>
 */
final class VcsIgnoredFilterIterator extends \FilterIterator
{
    private string $baseDir;

    /**
     * @var array<string, array{0: string, 1: string}|null>
     */
    private array $gitignoreFilesCache = [];

    /**
     * @var array<string, bool>
     */
    private array $ignoredPathsCache = [];

    /**
     * @param \Iterator<string, \SplFileInfo> $iterator
     */
    public function __construct(\Iterator $iterator, string $baseDir)
    {
        $this->baseDir = $this->normalizePath($baseDir);

        foreach ([$this->baseDir, ...$this->parentDirectoriesUpwards($this->baseDir)] as $directory) {
            if (@is_dir("{$directory}/.git")) {
                $this->baseDir = $directory;
                break;
            }
        }

        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->parentDirectoriesDownwards($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<string>
     */
    private function parentDirectoriesUpwards(string $from): array
    {
        $parentDirectories = [];

        $parentDirectory = $from;

        while (true) {
            $newParentDirectory = \dirname($parentDirectory);

            // dirname('/') = '/'
            if ($newParentDirectory === $parentDirectory) {
                break;
            }

            $parentDirectories[] = $parentDirectory = $newParentDirectory;
        }

        return $parentDirectories;
    }

    private function parentDirectoriesUpTo(string $from, string $upTo): array
    {
        return array_filter(
            $this->parentDirectoriesUpwards($from),
            static fn (string $directory): bool => str_starts_with($directory, $upTo)
        );
    }

    /**
     * @return list<string>
     */
    private function parentDirectoriesDownwards(string $fileRealPath): array
    {
        return array_reverse(
            $this->parentDirectoriesUpTo($fileRealPath, $this->baseDir)
        );
    }

    /**
     * @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;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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\Glob;

/**
 * FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string).
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @extends MultiplePcreFilterIterator<string, \SplFileInfo>
 */
class FilenameFilterIterator extends MultiplePcreFilterIterator
{
    /**
     * Filters the iterator values.
     */
    public function accept(): bool
    {
        return $this->isAccepted($this->current()->getFilename());
    }

    /**
     * Converts glob to regexp.
     *
     * PCRE patterns are left unchanged.
     * Glob strings are transformed with Glob::toRegex().
     *
     * @param string $str Pattern: glob or regexp
     */
    protected function toRegex(string $str): string
    {
        return $this->isRegex($str) ? $str : Glob::toRegex($str);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * SortableIterator applies a sort on a given Iterator.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @implements \IteratorAggregate<string, \SplFileInfo>
 */
class SortableIterator implements \IteratorAggregate
{
    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;
    public const SORT_BY_NAME_CASE_INSENSITIVE = 7;
    public const SORT_BY_NAME_NATURAL_CASE_INSENSITIVE = 8;
    public const SORT_BY_EXTENSION = 9;
    public const SORT_BY_SIZE = 10;

    /** @var \Traversable<string, \SplFileInfo> */
    private \Traversable $iterator;
    private \Closure|int $sort;

    /**
     * @param \Traversable<string, \SplFileInfo> $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, int|callable $sort, bool $reverseOrder = false)
    {
        $this->iterator = $iterator;
        $order = $reverseOrder ? -1 : 1;

        if (self::SORT_BY_NAME === $sort) {
            $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
        } elseif (self::SORT_BY_NAME_NATURAL === $sort) {
            $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
        } elseif (self::SORT_BY_NAME_CASE_INSENSITIVE === $sort) {
            $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strcasecmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
        } elseif (self::SORT_BY_NAME_NATURAL_CASE_INSENSITIVE === $sort) {
            $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strnatcasecmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
        } elseif (self::SORT_BY_TYPE === $sort) {
            $this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
                if ($a->isDir() && $b->isFile()) {
                    return -$order;
                } elseif ($a->isFile() && $b->isDir()) {
                    return $order;
                }

                return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
            };
        } elseif (self::SORT_BY_ACCESSED_TIME === $sort) {
            $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getATime() - $b->getATime());
        } elseif (self::SORT_BY_CHANGED_TIME === $sort) {
            $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getCTime() - $b->getCTime());
        } elseif (self::SORT_BY_MODIFIED_TIME === $sort) {
            $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getMTime() - $b->getMTime());
        } elseif (self::SORT_BY_EXTENSION === $sort) {
            $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * strnatcmp($a->getExtension(), $b->getExtension());
        } elseif (self::SORT_BY_SIZE === $sort) {
            $this->sort = static fn (\SplFileInfo $a, \SplFileInfo $b) => $order * ($a->getSize() - $b->getSize());
        } elseif (self::SORT_BY_NONE === $sort) {
            $this->sort = $order;
        } elseif (\is_callable($sort)) {
            $this->sort = $reverseOrder ? static fn (\SplFileInfo $a, \SplFileInfo $b) => -$sort($a, $b) : $sort(...);
        } else {
            throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.');
        }
    }

    public function getIterator(): \Traversable
    {
        if (1 === $this->sort) {
            return $this->iterator;
        }

        $array = iterator_to_array($this->iterator, true);

        if (-1 === $this->sort) {
            $array = array_reverse($array);
        } else {
            uasort($array, $this->sort);
        }

        return new \ArrayIterator($array);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * FileTypeFilterIterator only keeps files, directories, or both.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @extends \FilterIterator<string, \SplFileInfo>
 */
class FileTypeFilterIterator extends \FilterIterator
{
    public const ONLY_FILES = 1;
    public const ONLY_DIRECTORIES = 2;

    private int $mode;

    /**
     * @param \Iterator<string, \SplFileInfo> $iterator The Iterator to filter
     * @param int                             $mode     The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES)
     */
    public function __construct(\Iterator $iterator, int $mode)
    {
        $this->mode = $mode;

        parent::__construct($iterator);
    }

    /**
     * Filters the iterator values.
     */
    public function accept(): bool
    {
        $fileinfo = $this->current();
        if (self::ONLY_DIRECTORIES === (self::ONLY_DIRECTORIES & $this->mode) && $fileinfo->isFile()) {
            return false;
        } elseif (self::ONLY_FILES === (self::ONLY_FILES & $this->mode) && $fileinfo->isDir()) {
            return false;
        }

        return true;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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\Comparator\DateComparator;

/**
 * DateRangeFilterIterator filters out files that are not in the given date range (last modified dates).
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @extends \FilterIterator<string, \SplFileInfo>
 */
class DateRangeFilterIterator extends \FilterIterator
{
    private array $comparators = [];

    /**
     * @param \Iterator<string, \SplFileInfo> $iterator
     * @param DateComparator[]                $comparators
     */
    public function __construct(\Iterator $iterator, array $comparators)
    {
        $this->comparators = $comparators;

        parent::__construct($iterator);
    }

    /**
     * Filters the iterator values.
     */
    public function accept(): bool
    {
        $fileinfo = $this->current();

        if (!file_exists($fileinfo->getPathname())) {
            return false;
        }

        $filedate = $fileinfo->getMTime();
        foreach ($this->comparators as $compare) {
            if (!$compare->test($filedate)) {
                return false;
            }
        }

        return true;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings).
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @template-covariant TKey
 * @template-covariant TValue
 *
 * @extends \FilterIterator<TKey, TValue>
 */
abstract class MultiplePcreFilterIterator extends \FilterIterator
{
    protected $matchRegexps = [];
    protected $noMatchRegexps = [];

    /**
     * @param \Iterator<TKey, TValue> $iterator        The Iterator to filter
     * @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)
    {
        foreach ($matchPatterns as $pattern) {
            $this->matchRegexps[] = $this->toRegex($pattern);
        }

        foreach ($noMatchPatterns as $pattern) {
            $this->noMatchRegexps[] = $this->toRegex($pattern);
        }

        parent::__construct($iterator);
    }

    /**
     * Checks whether the string is accepted by the regex filters.
     *
     * If there is no regexps defined in the class, this method will accept the string.
     * Such case can be handled by child classes before calling the method if they want to
     * apply a different behavior.
     */
    protected function isAccepted(string $string): bool
    {
        // should at least not match one rule to exclude
        foreach ($this->noMatchRegexps as $regex) {
            if (preg_match($regex, $string)) {
                return false;
            }
        }

        // should at least match one rule
        if ($this->matchRegexps) {
            foreach ($this->matchRegexps as $regex) {
                if (preg_match($regex, $string)) {
                    return true;
                }
            }

            return false;
        }

        // If there is no match rules, the file is accepted
        return true;
    }

    /**
     * Checks whether the string is a regex.
     */
    protected function isRegex(string $str): bool
    {
        $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);

            if ($start === $end) {
                return !preg_match('/[*?[:alnum:] \\\\]/', $start);
            }

            foreach ([['{', '}'], ['(', ')'], ['[', ']'], ['<', '>']] as $delimiters) {
                if ($start === $delimiters[0] && $end === $delimiters[1]) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Converts string into regexp.
     */
    abstract protected function toRegex(string $str): string;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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\SplFileInfo;

/**
 * PathFilterIterator filters files by path patterns (e.g. some/special/dir).
 *
 * @author Fabien Potencier  <fabien@symfony.com>
 * @author Włodzimierz Gajda <gajdaw@gajdaw.pl>
 *
 * @extends MultiplePcreFilterIterator<string, SplFileInfo>
 */
class PathFilterIterator extends MultiplePcreFilterIterator
{
    /**
     * Filters the iterator values.
     */
    public function accept(): bool
    {
        $filename = $this->current()->getRelativePathname();

        if ('\\' === \DIRECTORY_SEPARATOR) {
            $filename = str_replace('\\', '/', $filename);
        }

        return $this->isAccepted($filename);
    }

    /**
     * Converts strings to regexp.
     *
     * PCRE patterns are left unchanged.
     *
     * Default conversion:
     *     'lorem/ipsum/dolor' ==>  'lorem\/ipsum\/dolor/'
     *
     * Use only / as directory separator (on Windows also).
     *
     * @param string $str Pattern: regexp or dirname
     */
    protected function toRegex(string $str): string
    {
        return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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\SplFileInfo;

/**
 * ExcludeDirectoryFilterIterator filters out directories.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @extends \FilterIterator<string, SplFileInfo>
 *
 * @implements \RecursiveIterator<string, SplFileInfo>
 */
class ExcludeDirectoryFilterIterator extends \FilterIterator implements \RecursiveIterator
{
    /** @var \Iterator<string, SplFileInfo> */
    private \Iterator $iterator;
    private bool $isRecursive;
    /** @var array<string, true> */
    private array $excludedDirs = [];
    private ?string $excludedPattern = null;
    /** @var list<callable(SplFileInfo):bool> */
    private array $pruneFilters = [];

    /**
     * @param \Iterator<string, SplFileInfo>          $iterator    The Iterator to filter
     * @param list<string|callable(SplFileInfo):bool> $directories An array of directories to exclude
     */
    public function __construct(\Iterator $iterator, array $directories)
    {
        $this->iterator = $iterator;
        $this->isRecursive = $iterator instanceof \RecursiveIterator;
        $patterns = [];
        foreach ($directories as $directory) {
            if (!\is_string($directory)) {
                if (!\is_callable($directory)) {
                    throw new \InvalidArgumentException('Invalid PHP callback.');
                }

                $this->pruneFilters[] = $directory;

                continue;
            }

            $directory = rtrim($directory, '/');
            if (!$this->isRecursive || str_contains($directory, '/')) {
                $patterns[] = preg_quote($directory, '#');
            } else {
                $this->excludedDirs[$directory] = true;
            }
        }
        if ($patterns) {
            $this->excludedPattern = '#(?:^|/)(?:'.implode('|', $patterns).')(?:/|$)#';
        }

        parent::__construct($iterator);
    }

    /**
     * Filters the iterator values.
     */
    public function accept(): bool
    {
        if ($this->isRecursive && isset($this->excludedDirs[$this->getFilename()]) && $this->isDir()) {
            return false;
        }

        if ($this->excludedPattern) {
            $path = $this->isDir() ? $this->current()->getRelativePathname() : $this->current()->getRelativePath();
            $path = str_replace('\\', '/', $path);

            return !preg_match($this->excludedPattern, $path);
        }

        if ($this->pruneFilters && $this->hasChildren()) {
            foreach ($this->pruneFilters as $pruneFilter) {
                if (!$pruneFilter($this->current())) {
                    return false;
                }
            }
        }

        return true;
    }

    public function hasChildren(): bool
    {
        return $this->isRecursive && $this->iterator->hasChildren();
    }

    public function getChildren(): self
    {
        $children = new self($this->iterator->getChildren(), []);
        $children->excludedDirs = $this->excludedDirs;
        $children->excludedPattern = $this->excludedPattern;

        return $children;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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\Comparator\NumberComparator;

/**
 * SizeRangeFilterIterator filters out files that are not in the given size range.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @extends \FilterIterator<string, \SplFileInfo>
 */
class SizeRangeFilterIterator extends \FilterIterator
{
    private array $comparators = [];

    /**
     * @param \Iterator<string, \SplFileInfo> $iterator
     * @param NumberComparator[]              $comparators
     */
    public function __construct(\Iterator $iterator, array $comparators)
    {
        $this->comparators = $comparators;

        parent::__construct($iterator);
    }

    /**
     * Filters the iterator values.
     */
    public function accept(): bool
    {
        $fileinfo = $this->current();
        if (!$fileinfo->isFile()) {
            return true;
        }

        $filesize = $fileinfo->getSize();
        foreach ($this->comparators as $compare) {
            if (!$compare->test($filesize)) {
                return false;
            }
        }

        return true;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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é <jeremy@derusse.com>
 *
 * @internal
 */
class LazyIterator implements \IteratorAggregate
{
    private \Closure $iteratorFactory;

    public function __construct(callable $iteratorFactory)
    {
        $this->iteratorFactory = $iteratorFactory(...);
    }

    public function getIterator(): \Traversable
    {
        yield from ($this->iteratorFactory)();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * CustomFilterIterator filters files by applying anonymous functions.
 *
 * The anonymous function receives a \SplFileInfo and must return false
 * to remove files.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @extends \FilterIterator<string, \SplFileInfo>
 */
class CustomFilterIterator extends \FilterIterator
{
    private array $filters = [];

    /**
     * @param \Iterator<string, \SplFileInfo> $iterator The Iterator to filter
     * @param callable[]                      $filters  An array of PHP callbacks
     *
     * @throws \InvalidArgumentException
     */
    public function __construct(\Iterator $iterator, array $filters)
    {
        foreach ($filters as $filter) {
            if (!\is_callable($filter)) {
                throw new \InvalidArgumentException('Invalid PHP callback.');
            }
        }
        $this->filters = $filters;

        parent::__construct($iterator);
    }

    /**
     * Filters the iterator values.
     */
    public function accept(): bool
    {
        $fileinfo = $this->current();

        foreach ($this->filters as $filter) {
            if (false === $filter($fileinfo)) {
                return false;
            }
        }

        return true;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder;

/**
 * Glob matches globbing patterns against text.
 *
 *     if match_glob("foo.*", "foo.bar") echo "matched\n";
 *
 *     // prints foo.bar and foo.baz
 *     $regex = glob_to_regex("foo.*");
 *     for (['foo.bar', 'foo.baz', 'foo', 'bar'] as $t)
 *     {
 *         if (/$regex/) echo "matched: $car\n";
 *     }
 *
 * Glob implements glob(3) style matching that can be used to match
 * against text, rather than fetching names from a filesystem.
 *
 * Based on the Perl Text::Glob module.
 *
 * @author Fabien Potencier <fabien@symfony.com> PHP port
 * @author     Richard Clamp <richardc@unixbeard.net> Perl version
 * @copyright  2004-2005 Fabien Potencier <fabien@symfony.com>
 * @copyright  2002 Richard Clamp <richardc@unixbeard.net>
 */
class Glob
{
    /**
     * Returns a regexp which is the equivalent of the glob pattern.
     */
    public static function toRegex(string $glob, bool $strictLeadingDot = true, bool $strictWildcardSlash = true, string $delimiter = '#'): string
    {
        $firstByte = true;
        $escaping = false;
        $inCurlies = 0;
        $regex = '';
        $sizeGlob = \strlen($glob);
        for ($i = 0; $i < $sizeGlob; ++$i) {
            $car = $glob[$i];
            if ($firstByte && $strictLeadingDot && '.' !== $car) {
                $regex .= '(?=[^\.])';
            }

            $firstByte = '/' === $car;

            if ($firstByte && $strictWildcardSlash && isset($glob[$i + 2]) && '**' === $glob[$i + 1].$glob[$i + 2] && (!isset($glob[$i + 3]) || '/' === $glob[$i + 3])) {
                $car = '[^/]++/';
                if (!isset($glob[$i + 3])) {
                    $car .= '?';
                }

                if ($strictLeadingDot) {
                    $car = '(?=[^\.])'.$car;
                }

                $car = '/(?:'.$car.')*';
                $i += 2 + isset($glob[$i + 3]);

                if ('/' === $delimiter) {
                    $car = str_replace('/', '\\/', $car);
                }
            }

            if ($delimiter === $car || '.' === $car || '(' === $car || ')' === $car || '|' === $car || '+' === $car || '^' === $car || '$' === $car) {
                $regex .= "\\$car";
            } elseif ('*' === $car) {
                $regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*');
            } elseif ('?' === $car) {
                $regex .= $escaping ? '\\?' : ($strictWildcardSlash ? '[^/]' : '.');
            } elseif ('{' === $car) {
                $regex .= $escaping ? '\\{' : '(';
                if (!$escaping) {
                    ++$inCurlies;
                }
            } elseif ('}' === $car && $inCurlies) {
                $regex .= $escaping ? '}' : ')';
                if (!$escaping) {
                    --$inCurlies;
                }
            } elseif (',' === $car && $inCurlies) {
                $regex .= $escaping ? ',' : '|';
            } elseif ('\\' === $car) {
                if ($escaping) {
                    $regex .= '\\\\';
                    $escaping = false;
                } else {
                    $escaping = true;
                }

                continue;
            } else {
                $regex .= $car;
            }
            $escaping = false;
        }

        return $delimiter.'^'.$regex.'$'.$delimiter;
    }
}
Copyright (c) 2020-present Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <p@tchwork.com>
     */
    function trigger_deprecation(string $package, string $version, string $message, mixed ...$args): void
    {
        @trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED);
    }
}
Copyright (c) 2018-present Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
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.
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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.
     *
     * @template T of object
     *
     * @param T           $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 T The passed $event MUST be returned
     */
    public function dispatch(object $event, ?string $eventName = null): object;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <guilhermeblanco@hotmail.com>
 * @author Jonathan Wage <jonwage@gmail.com>
 * @author Roman Borschel <roman@code-factory.org>
 * @author Bernhard Schussek <bschussek@gmail.com>
 * @author Nicolas Grekas <p@tchwork.com>
 */
class Event implements StoppableEventInterface
{
    private bool $propagationStopped = false;

    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;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Polyfill\Ctype;

/**
 * Ctype implementation through regex.
 *
 * @internal
 *
 * @author Gert de Pagter <BackEndTea@gmail.com>
 */
final class Ctype
{
    /**
     * Returns TRUE if every character in text is either a letter or a digit, FALSE otherwise.
     *
     * @see https://php.net/ctype-alnum
     *
     * @param mixed $text
     *
     * @return bool
     */
    public static function ctype_alnum($text)
    {
        $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);

        return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z0-9]/', $text);
    }

    /**
     * Returns TRUE if every character in text is a letter, FALSE otherwise.
     *
     * @see https://php.net/ctype-alpha
     *
     * @param mixed $text
     *
     * @return bool
     */
    public static function ctype_alpha($text)
    {
        $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);

        return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z]/', $text);
    }

    /**
     * Returns TRUE if every character in text is a control character from the current locale, FALSE otherwise.
     *
     * @see https://php.net/ctype-cntrl
     *
     * @param mixed $text
     *
     * @return bool
     */
    public static function ctype_cntrl($text)
    {
        $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);

        return \is_string($text) && '' !== $text && !preg_match('/[^\x00-\x1f\x7f]/', $text);
    }

    /**
     * Returns TRUE if every character in the string text is a decimal digit, FALSE otherwise.
     *
     * @see https://php.net/ctype-digit
     *
     * @param mixed $text
     *
     * @return bool
     */
    public static function ctype_digit($text)
    {
        $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);

        return \is_string($text) && '' !== $text && !preg_match('/[^0-9]/', $text);
    }

    /**
     * Returns TRUE if every character in text is printable and actually creates visible output (no white space), FALSE otherwise.
     *
     * @see https://php.net/ctype-graph
     *
     * @param mixed $text
     *
     * @return bool
     */
    public static function ctype_graph($text)
    {
        $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);

        return \is_string($text) && '' !== $text && !preg_match('/[^!-~]/', $text);
    }

    /**
     * Returns TRUE if every character in text is a lowercase letter.
     *
     * @see https://php.net/ctype-lower
     *
     * @param mixed $text
     *
     * @return bool
     */
    public static function ctype_lower($text)
    {
        $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);

        return \is_string($text) && '' !== $text && !preg_match('/[^a-z]/', $text);
    }

    /**
     * Returns TRUE if every character in text will actually create output (including blanks). Returns FALSE if text contains control characters or characters that do not have any output or control function at all.
     *
     * @see https://php.net/ctype-print
     *
     * @param mixed $text
     *
     * @return bool
     */
    public static function ctype_print($text)
    {
        $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);

        return \is_string($text) && '' !== $text && !preg_match('/[^ -~]/', $text);
    }

    /**
     * Returns TRUE if every character in text is printable, but neither letter, digit or blank, FALSE otherwise.
     *
     * @see https://php.net/ctype-punct
     *
     * @param mixed $text
     *
     * @return bool
     */
    public static function ctype_punct($text)
    {
        $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);

        return \is_string($text) && '' !== $text && !preg_match('/[^!-\/\:-@\[-`\{-~]/', $text);
    }

    /**
     * Returns TRUE if every character in text creates some sort of white space, FALSE otherwise. Besides the blank character this also includes tab, vertical tab, line feed, carriage return and form feed characters.
     *
     * @see https://php.net/ctype-space
     *
     * @param mixed $text
     *
     * @return bool
     */
    public static function ctype_space($text)
    {
        $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);

        return \is_string($text) && '' !== $text && !preg_match('/[^\s]/', $text);
    }

    /**
     * Returns TRUE if every character in text is an uppercase letter.
     *
     * @see https://php.net/ctype-upper
     *
     * @param mixed $text
     *
     * @return bool
     */
    public static function ctype_upper($text)
    {
        $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);

        return \is_string($text) && '' !== $text && !preg_match('/[^A-Z]/', $text);
    }

    /**
     * Returns TRUE if every character in text is a hexadecimal 'digit', that is a decimal digit or a character from [A-Fa-f] , FALSE otherwise.
     *
     * @see https://php.net/ctype-xdigit
     *
     * @param mixed $text
     *
     * @return bool
     */
    public static function ctype_xdigit($text)
    {
        $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);

        return \is_string($text) && '' !== $text && !preg_match('/[^A-Fa-f0-9]/', $text);
    }

    /**
     * Converts integers to their char versions according to normal ctype behaviour, if needed.
     *
     * If an integer between -128 and 255 inclusive is provided,
     * it is interpreted as the ASCII value of a single character
     * (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 mixed  $int
     * @param string $function
     *
     * @return mixed
     */
    private static function convert_int_to_char_for_ctype($int, $function)
    {
        if (!\is_int($int)) {
            return $int;
        }

        if ($int < -128 || $int > 255) {
            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;
        }

        return \chr($int);
    }
}
Copyright (c) 2018-present Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
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.
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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); }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 (\PHP_VERSION_ID >= 80000) {
    return require __DIR__.'/bootstrap80.php';
}

if (!function_exists('ctype_alnum')) {
    function ctype_alnum($text) { return p\Ctype::ctype_alnum($text); }
}
if (!function_exists('ctype_alpha')) {
    function ctype_alpha($text) { return p\Ctype::ctype_alpha($text); }
}
if (!function_exists('ctype_cntrl')) {
    function ctype_cntrl($text) { return p\Ctype::ctype_cntrl($text); }
}
if (!function_exists('ctype_digit')) {
    function ctype_digit($text) { return p\Ctype::ctype_digit($text); }
}
if (!function_exists('ctype_graph')) {
    function ctype_graph($text) { return p\Ctype::ctype_graph($text); }
}
if (!function_exists('ctype_lower')) {
    function ctype_lower($text) { return p\Ctype::ctype_lower($text); }
}
if (!function_exists('ctype_print')) {
    function ctype_print($text) { return p\Ctype::ctype_print($text); }
}
if (!function_exists('ctype_punct')) {
    function ctype_punct($text) { return p\Ctype::ctype_punct($text); }
}
if (!function_exists('ctype_space')) {
    function ctype_space($text) { return p\Ctype::ctype_space($text); }
}
if (!function_exists('ctype_upper')) {
    function ctype_upper($text) { return p\Ctype::ctype_upper($text); }
}
if (!function_exists('ctype_xdigit')) {
    function ctype_xdigit($text) { return p\Ctype::ctype_xdigit($text); }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <p@tchwork.com>
 *
 * @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;
    }
}
<?php

return array (
  'À' => 'À',
  'Á' => 'Á',
  'Â' => 'Â',
  'Ã' => 'Ã',
  'Ä' => 'Ä',
  'Å' => 'Å',
  'Ç' => 'Ç',
  'È' => 'È',
  'É' => 'É',
  'Ê' => 'Ê',
  'Ë' => 'Ë',
  'Ì' => 'Ì',
  'Í' => 'Í',
  'Î' => 'Î',
  'Ï' => 'Ï',
  'Ñ' => 'Ñ',
  'Ò' => 'Ò',
  'Ó' => 'Ó',
  'Ô' => 'Ô',
  'Õ' => 'Õ',
  'Ö' => 'Ö',
  'Ù' => 'Ù',
  'Ú' => 'Ú',
  'Û' => 'Û',
  'Ü' => 'Ü',
  'Ý' => 'Ý',
  'à' => 'à',
  'á' => 'á',
  'â' => 'â',
  'ã' => 'ã',
  'ä' => 'ä',
  'å' => 'å',
  'ç' => 'ç',
  'è' => 'è',
  'é' => 'é',
  'ê' => 'ê',
  'ë' => 'ë',
  'ì' => 'ì',
  'í' => 'í',
  'î' => 'î',
  'ï' => 'ï',
  'ñ' => 'ñ',
  'ò' => 'ò',
  'ó' => 'ó',
  'ô' => 'ô',
  'õ' => 'õ',
  'ö' => 'ö',
  'ù' => 'ù',
  'ú' => 'ú',
  'û' => 'û',
  'ü' => 'ü',
  'ý' => 'ý',
  'ÿ' => 'ÿ',
  'Ā' => 'Ā',
  'ā' => 'ā',
  'Ă' => 'Ă',
  'ă' => 'ă',
  'Ą' => 'Ą',
  'ą' => 'ą',
  'Ć' => 'Ć',
  'ć' => 'ć',
  'Ĉ' => 'Ĉ',
  'ĉ' => 'ĉ',
  'Ċ' => 'Ċ',
  'ċ' => 'ċ',
  'Č' => 'Č',
  'č' => 'č',
  'Ď' => 'Ď',
  'ď' => 'ď',
  'Ē' => 'Ē',
  'ē' => 'ē',
  'Ĕ' => 'Ĕ',
  'ĕ' => 'ĕ',
  'Ė' => 'Ė',
  'ė' => 'ė',
  'Ę' => 'Ę',
  'ę' => 'ę',
  'Ě' => 'Ě',
  'ě' => 'ě',
  'Ĝ' => 'Ĝ',
  'ĝ' => 'ĝ',
  'Ğ' => 'Ğ',
  'ğ' => 'ğ',
  'Ġ' => 'Ġ',
  'ġ' => 'ġ',
  'Ģ' => 'Ģ',
  'ģ' => 'ģ',
  'Ĥ' => 'Ĥ',
  'ĥ' => 'ĥ',
  'Ĩ' => 'Ĩ',
  'ĩ' => 'ĩ',
  'Ī' => 'Ī',
  'ī' => 'ī',
  'Ĭ' => 'Ĭ',
  'ĭ' => 'ĭ',
  'Į' => 'Į',
  'į' => 'į',
  'İ' => 'İ',
  'Ĵ' => 'Ĵ',
  'ĵ' => 'ĵ',
  'Ķ' => 'Ķ',
  'ķ' => 'ķ',
  'Ĺ' => 'Ĺ',
  'ĺ' => 'ĺ',
  'Ļ' => 'Ļ',
  'ļ' => 'ļ',
  'Ľ' => 'Ľ',
  'ľ' => 'ľ',
  'Ń' => 'Ń',
  'ń' => 'ń',
  'Ņ' => 'Ņ',
  'ņ' => 'ņ',
  'Ň' => 'Ň',
  'ň' => 'ň',
  'Ō' => 'Ō',
  'ō' => 'ō',
  'Ŏ' => 'Ŏ',
  'ŏ' => 'ŏ',
  'Ő' => 'Ő',
  'ő' => 'ő',
  'Ŕ' => 'Ŕ',
  'ŕ' => 'ŕ',
  'Ŗ' => 'Ŗ',
  'ŗ' => 'ŗ',
  'Ř' => 'Ř',
  'ř' => 'ř',
  'Ś' => 'Ś',
  'ś' => 'ś',
  'Ŝ' => 'Ŝ',
  'ŝ' => 'ŝ',
  'Ş' => 'Ş',
  'ş' => 'ş',
  'Š' => 'Š',
  'š' => 'š',
  'Ţ' => 'Ţ',
  'ţ' => 'ţ',
  'Ť' => 'Ť',
  'ť' => 'ť',
  'Ũ' => 'Ũ',
  'ũ' => 'ũ',
  'Ū' => 'Ū',
  'ū' => 'ū',
  'Ŭ' => 'Ŭ',
  'ŭ' => 'ŭ',
  'Ů' => 'Ů',
  'ů' => 'ů',
  'Ű' => 'Ű',
  'ű' => 'ű',
  'Ų' => 'Ų',
  'ų' => 'ų',
  'Ŵ' => 'Ŵ',
  'ŵ' => 'ŵ',
  'Ŷ' => 'Ŷ',
  'ŷ' => 'ŷ',
  'Ÿ' => 'Ÿ',
  'Ź' => 'Ź',
  'ź' => 'ź',
  'Ż' => 'Ż',
  'ż' => 'ż',
  'Ž' => 'Ž',
  'ž' => 'ž',
  'Ơ' => 'Ơ',
  'ơ' => 'ơ',
  'Ư' => 'Ư',
  'ư' => 'ư',
  'Ǎ' => 'Ǎ',
  'ǎ' => 'ǎ',
  'Ǐ' => 'Ǐ',
  'ǐ' => 'ǐ',
  'Ǒ' => 'Ǒ',
  'ǒ' => 'ǒ',
  'Ǔ' => 'Ǔ',
  'ǔ' => 'ǔ',
  'Ǖ' => 'Ǖ',
  'ǖ' => 'ǖ',
  'Ǘ' => 'Ǘ',
  'ǘ' => 'ǘ',
  'Ǚ' => 'Ǚ',
  'ǚ' => 'ǚ',
  'Ǜ' => 'Ǜ',
  'ǜ' => 'ǜ',
  'Ǟ' => 'Ǟ',
  'ǟ' => 'ǟ',
  'Ǡ' => 'Ǡ',
  'ǡ' => 'ǡ',
  'Ǣ' => 'Ǣ',
  'ǣ' => 'ǣ',
  'Ǧ' => 'Ǧ',
  'ǧ' => 'ǧ',
  'Ǩ' => 'Ǩ',
  'ǩ' => 'ǩ',
  'Ǫ' => 'Ǫ',
  'ǫ' => 'ǫ',
  'Ǭ' => 'Ǭ',
  'ǭ' => 'ǭ',
  'Ǯ' => 'Ǯ',
  'ǯ' => 'ǯ',
  'ǰ' => 'ǰ',
  'Ǵ' => 'Ǵ',
  'ǵ' => 'ǵ',
  'Ǹ' => 'Ǹ',
  'ǹ' => 'ǹ',
  'Ǻ' => 'Ǻ',
  'ǻ' => 'ǻ',
  'Ǽ' => 'Ǽ',
  'ǽ' => 'ǽ',
  'Ǿ' => 'Ǿ',
  'ǿ' => 'ǿ',
  'Ȁ' => 'Ȁ',
  'ȁ' => 'ȁ',
  'Ȃ' => 'Ȃ',
  'ȃ' => 'ȃ',
  'Ȅ' => 'Ȅ',
  'ȅ' => 'ȅ',
  'Ȇ' => 'Ȇ',
  'ȇ' => 'ȇ',
  'Ȉ' => 'Ȉ',
  'ȉ' => 'ȉ',
  'Ȋ' => 'Ȋ',
  'ȋ' => 'ȋ',
  'Ȍ' => 'Ȍ',
  'ȍ' => 'ȍ',
  'Ȏ' => 'Ȏ',
  'ȏ' => 'ȏ',
  'Ȑ' => 'Ȑ',
  'ȑ' => 'ȑ',
  'Ȓ' => 'Ȓ',
  'ȓ' => 'ȓ',
  'Ȕ' => 'Ȕ',
  'ȕ' => 'ȕ',
  'Ȗ' => 'Ȗ',
  'ȗ' => 'ȗ',
  'Ș' => 'Ș',
  'ș' => 'ș',
  'Ț' => 'Ț',
  'ț' => 'ț',
  'Ȟ' => 'Ȟ',
  'ȟ' => 'ȟ',
  'Ȧ' => 'Ȧ',
  'ȧ' => 'ȧ',
  'Ȩ' => 'Ȩ',
  'ȩ' => 'ȩ',
  'Ȫ' => 'Ȫ',
  'ȫ' => 'ȫ',
  'Ȭ' => 'Ȭ',
  'ȭ' => 'ȭ',
  'Ȯ' => 'Ȯ',
  'ȯ' => 'ȯ',
  'Ȱ' => 'Ȱ',
  'ȱ' => 'ȱ',
  'Ȳ' => 'Ȳ',
  'ȳ' => 'ȳ',
  '΅' => '΅',
  'Ά' => 'Ά',
  'Έ' => 'Έ',
  'Ή' => 'Ή',
  'Ί' => 'Ί',
  'Ό' => 'Ό',
  'Ύ' => 'Ύ',
  'Ώ' => 'Ώ',
  'ΐ' => 'ΐ',
  'Ϊ' => 'Ϊ',
  'Ϋ' => 'Ϋ',
  'ά' => 'ά',
  'έ' => 'έ',
  'ή' => 'ή',
  'ί' => 'ί',
  'ΰ' => 'ΰ',
  'ϊ' => 'ϊ',
  'ϋ' => 'ϋ',
  'ό' => 'ό',
  'ύ' => 'ύ',
  'ώ' => 'ώ',
  'ϓ' => 'ϓ',
  'ϔ' => 'ϔ',
  'Ѐ' => 'Ѐ',
  'Ё' => 'Ё',
  'Ѓ' => 'Ѓ',
  'Ї' => 'Ї',
  'Ќ' => 'Ќ',
  'Ѝ' => 'Ѝ',
  'Ў' => 'Ў',
  'Й' => 'Й',
  'й' => 'й',
  'ѐ' => 'ѐ',
  'ё' => 'ё',
  'ѓ' => 'ѓ',
  'ї' => 'ї',
  'ќ' => 'ќ',
  'ѝ' => 'ѝ',
  'ў' => 'ў',
  'Ѷ' => 'Ѷ',
  'ѷ' => 'ѷ',
  'Ӂ' => 'Ӂ',
  'ӂ' => 'ӂ',
  'Ӑ' => 'Ӑ',
  'ӑ' => 'ӑ',
  'Ӓ' => 'Ӓ',
  'ӓ' => 'ӓ',
  'Ӗ' => 'Ӗ',
  'ӗ' => 'ӗ',
  'Ӛ' => 'Ӛ',
  'ӛ' => 'ӛ',
  'Ӝ' => 'Ӝ',
  'ӝ' => 'ӝ',
  'Ӟ' => 'Ӟ',
  'ӟ' => 'ӟ',
  'Ӣ' => 'Ӣ',
  'ӣ' => 'ӣ',
  'Ӥ' => 'Ӥ',
  'ӥ' => 'ӥ',
  'Ӧ' => 'Ӧ',
  'ӧ' => 'ӧ',
  'Ӫ' => 'Ӫ',
  'ӫ' => 'ӫ',
  'Ӭ' => 'Ӭ',
  'ӭ' => 'ӭ',
  'Ӯ' => 'Ӯ',
  'ӯ' => 'ӯ',
  'Ӱ' => 'Ӱ',
  'ӱ' => 'ӱ',
  'Ӳ' => 'Ӳ',
  'ӳ' => 'ӳ',
  'Ӵ' => 'Ӵ',
  'ӵ' => 'ӵ',
  'Ӹ' => 'Ӹ',
  'ӹ' => 'ӹ',
  'آ' => 'آ',
  'أ' => 'أ',
  'ؤ' => 'ؤ',
  'إ' => 'إ',
  'ئ' => 'ئ',
  'ۀ' => 'ۀ',
  'ۂ' => 'ۂ',
  'ۓ' => 'ۓ',
  'ऩ' => 'ऩ',
  'ऱ' => 'ऱ',
  'ऴ' => 'ऴ',
  'ো' => 'ো',
  'ৌ' => 'ৌ',
  'ୈ' => 'ୈ',
  'ୋ' => 'ୋ',
  'ୌ' => 'ୌ',
  'ஔ' => 'ஔ',
  'ொ' => 'ொ',
  'ோ' => 'ோ',
  'ௌ' => 'ௌ',
  'ై' => 'ై',
  'ೀ' => 'ೀ',
  'ೇ' => 'ೇ',
  'ೈ' => 'ೈ',
  'ೊ' => 'ೊ',
  'ೋ' => 'ೋ',
  'ൊ' => 'ൊ',
  'ോ' => 'ോ',
  'ൌ' => 'ൌ',
  'ේ' => 'ේ',
  'ො' => 'ො',
  'ෝ' => 'ෝ',
  'ෞ' => 'ෞ',
  'ဦ' => 'ဦ',
  'ᬆ' => 'ᬆ',
  'ᬈ' => 'ᬈ',
  'ᬊ' => 'ᬊ',
  'ᬌ' => 'ᬌ',
  'ᬎ' => 'ᬎ',
  'ᬒ' => 'ᬒ',
  'ᬻ' => 'ᬻ',
  'ᬽ' => 'ᬽ',
  'ᭀ' => 'ᭀ',
  'ᭁ' => 'ᭁ',
  'ᭃ' => 'ᭃ',
  'Ḁ' => 'Ḁ',
  'ḁ' => 'ḁ',
  'Ḃ' => 'Ḃ',
  'ḃ' => 'ḃ',
  'Ḅ' => 'Ḅ',
  'ḅ' => 'ḅ',
  'Ḇ' => 'Ḇ',
  'ḇ' => 'ḇ',
  'Ḉ' => 'Ḉ',
  'ḉ' => 'ḉ',
  'Ḋ' => 'Ḋ',
  'ḋ' => 'ḋ',
  'Ḍ' => 'Ḍ',
  'ḍ' => 'ḍ',
  'Ḏ' => 'Ḏ',
  'ḏ' => 'ḏ',
  'Ḑ' => 'Ḑ',
  'ḑ' => 'ḑ',
  'Ḓ' => 'Ḓ',
  'ḓ' => 'ḓ',
  'Ḕ' => 'Ḕ',
  'ḕ' => 'ḕ',
  'Ḗ' => 'Ḗ',
  'ḗ' => 'ḗ',
  'Ḙ' => 'Ḙ',
  'ḙ' => 'ḙ',
  'Ḛ' => 'Ḛ',
  'ḛ' => 'ḛ',
  'Ḝ' => 'Ḝ',
  'ḝ' => 'ḝ',
  'Ḟ' => 'Ḟ',
  'ḟ' => 'ḟ',
  'Ḡ' => 'Ḡ',
  'ḡ' => 'ḡ',
  'Ḣ' => 'Ḣ',
  'ḣ' => 'ḣ',
  'Ḥ' => 'Ḥ',
  'ḥ' => 'ḥ',
  'Ḧ' => 'Ḧ',
  'ḧ' => 'ḧ',
  'Ḩ' => 'Ḩ',
  'ḩ' => 'ḩ',
  'Ḫ' => 'Ḫ',
  'ḫ' => 'ḫ',
  'Ḭ' => 'Ḭ',
  'ḭ' => 'ḭ',
  'Ḯ' => 'Ḯ',
  'ḯ' => 'ḯ',
  'Ḱ' => 'Ḱ',
  'ḱ' => 'ḱ',
  'Ḳ' => 'Ḳ',
  'ḳ' => 'ḳ',
  'Ḵ' => 'Ḵ',
  'ḵ' => 'ḵ',
  'Ḷ' => 'Ḷ',
  'ḷ' => 'ḷ',
  'Ḹ' => 'Ḹ',
  'ḹ' => 'ḹ',
  'Ḻ' => 'Ḻ',
  'ḻ' => 'ḻ',
  'Ḽ' => 'Ḽ',
  'ḽ' => 'ḽ',
  'Ḿ' => 'Ḿ',
  'ḿ' => 'ḿ',
  'Ṁ' => 'Ṁ',
  'ṁ' => 'ṁ',
  'Ṃ' => 'Ṃ',
  'ṃ' => 'ṃ',
  'Ṅ' => 'Ṅ',
  'ṅ' => 'ṅ',
  'Ṇ' => 'Ṇ',
  'ṇ' => 'ṇ',
  'Ṉ' => 'Ṉ',
  'ṉ' => 'ṉ',
  'Ṋ' => 'Ṋ',
  'ṋ' => 'ṋ',
  'Ṍ' => 'Ṍ',
  'ṍ' => 'ṍ',
  'Ṏ' => 'Ṏ',
  'ṏ' => 'ṏ',
  'Ṑ' => 'Ṑ',
  'ṑ' => 'ṑ',
  'Ṓ' => 'Ṓ',
  'ṓ' => 'ṓ',
  'Ṕ' => 'Ṕ',
  'ṕ' => 'ṕ',
  'Ṗ' => 'Ṗ',
  'ṗ' => 'ṗ',
  'Ṙ' => 'Ṙ',
  'ṙ' => 'ṙ',
  'Ṛ' => 'Ṛ',
  'ṛ' => 'ṛ',
  'Ṝ' => 'Ṝ',
  'ṝ' => 'ṝ',
  'Ṟ' => 'Ṟ',
  'ṟ' => 'ṟ',
  'Ṡ' => 'Ṡ',
  'ṡ' => 'ṡ',
  'Ṣ' => 'Ṣ',
  'ṣ' => 'ṣ',
  'Ṥ' => 'Ṥ',
  'ṥ' => 'ṥ',
  'Ṧ' => 'Ṧ',
  'ṧ' => 'ṧ',
  'Ṩ' => 'Ṩ',
  'ṩ' => 'ṩ',
  'Ṫ' => 'Ṫ',
  'ṫ' => 'ṫ',
  'Ṭ' => 'Ṭ',
  'ṭ' => 'ṭ',
  'Ṯ' => 'Ṯ',
  'ṯ' => 'ṯ',
  'Ṱ' => 'Ṱ',
  'ṱ' => 'ṱ',
  'Ṳ' => 'Ṳ',
  'ṳ' => 'ṳ',
  'Ṵ' => 'Ṵ',
  'ṵ' => 'ṵ',
  'Ṷ' => 'Ṷ',
  'ṷ' => 'ṷ',
  'Ṹ' => 'Ṹ',
  'ṹ' => 'ṹ',
  'Ṻ' => 'Ṻ',
  'ṻ' => 'ṻ',
  'Ṽ' => 'Ṽ',
  'ṽ' => 'ṽ',
  'Ṿ' => 'Ṿ',
  'ṿ' => 'ṿ',
  'Ẁ' => 'Ẁ',
  'ẁ' => 'ẁ',
  'Ẃ' => 'Ẃ',
  'ẃ' => 'ẃ',
  'Ẅ' => 'Ẅ',
  'ẅ' => 'ẅ',
  'Ẇ' => 'Ẇ',
  'ẇ' => 'ẇ',
  'Ẉ' => 'Ẉ',
  'ẉ' => 'ẉ',
  'Ẋ' => 'Ẋ',
  'ẋ' => 'ẋ',
  'Ẍ' => 'Ẍ',
  'ẍ' => 'ẍ',
  'Ẏ' => 'Ẏ',
  'ẏ' => 'ẏ',
  'Ẑ' => 'Ẑ',
  'ẑ' => 'ẑ',
  'Ẓ' => 'Ẓ',
  'ẓ' => 'ẓ',
  'Ẕ' => 'Ẕ',
  'ẕ' => 'ẕ',
  'ẖ' => 'ẖ',
  'ẗ' => 'ẗ',
  'ẘ' => 'ẘ',
  'ẙ' => 'ẙ',
  'ẛ' => 'ẛ',
  'Ạ' => 'Ạ',
  'ạ' => 'ạ',
  'Ả' => 'Ả',
  'ả' => 'ả',
  'Ấ' => 'Ấ',
  'ấ' => 'ấ',
  'Ầ' => 'Ầ',
  'ầ' => 'ầ',
  'Ẩ' => 'Ẩ',
  'ẩ' => 'ẩ',
  'Ẫ' => 'Ẫ',
  'ẫ' => 'ẫ',
  'Ậ' => 'Ậ',
  'ậ' => 'ậ',
  'Ắ' => 'Ắ',
  'ắ' => 'ắ',
  'Ằ' => 'Ằ',
  'ằ' => 'ằ',
  'Ẳ' => 'Ẳ',
  'ẳ' => 'ẳ',
  'Ẵ' => 'Ẵ',
  'ẵ' => 'ẵ',
  'Ặ' => 'Ặ',
  'ặ' => 'ặ',
  'Ẹ' => 'Ẹ',
  'ẹ' => 'ẹ',
  'Ẻ' => 'Ẻ',
  'ẻ' => 'ẻ',
  'Ẽ' => 'Ẽ',
  'ẽ' => 'ẽ',
  'Ế' => 'Ế',
  'ế' => 'ế',
  'Ề' => 'Ề',
  'ề' => 'ề',
  'Ể' => 'Ể',
  'ể' => 'ể',
  'Ễ' => 'Ễ',
  'ễ' => 'ễ',
  'Ệ' => 'Ệ',
  'ệ' => 'ệ',
  'Ỉ' => 'Ỉ',
  'ỉ' => 'ỉ',
  'Ị' => 'Ị',
  'ị' => 'ị',
  'Ọ' => 'Ọ',
  'ọ' => 'ọ',
  'Ỏ' => 'Ỏ',
  'ỏ' => 'ỏ',
  'Ố' => 'Ố',
  'ố' => 'ố',
  'Ồ' => 'Ồ',
  'ồ' => 'ồ',
  'Ổ' => 'Ổ',
  'ổ' => 'ổ',
  'Ỗ' => 'Ỗ',
  'ỗ' => 'ỗ',
  'Ộ' => 'Ộ',
  'ộ' => 'ộ',
  'Ớ' => 'Ớ',
  'ớ' => 'ớ',
  'Ờ' => 'Ờ',
  'ờ' => 'ờ',
  'Ở' => 'Ở',
  'ở' => 'ở',
  'Ỡ' => 'Ỡ',
  'ỡ' => 'ỡ',
  'Ợ' => 'Ợ',
  'ợ' => 'ợ',
  'Ụ' => 'Ụ',
  'ụ' => 'ụ',
  'Ủ' => 'Ủ',
  'ủ' => 'ủ',
  'Ứ' => 'Ứ',
  'ứ' => 'ứ',
  'Ừ' => 'Ừ',
  'ừ' => 'ừ',
  'Ử' => 'Ử',
  'ử' => 'ử',
  'Ữ' => 'Ữ',
  'ữ' => 'ữ',
  'Ự' => 'Ự',
  'ự' => 'ự',
  'Ỳ' => 'Ỳ',
  'ỳ' => 'ỳ',
  'Ỵ' => 'Ỵ',
  'ỵ' => 'ỵ',
  'Ỷ' => 'Ỷ',
  'ỷ' => 'ỷ',
  'Ỹ' => 'Ỹ',
  'ỹ' => 'ỹ',
  'ἀ' => 'ἀ',
  'ἁ' => 'ἁ',
  'ἂ' => 'ἂ',
  'ἃ' => 'ἃ',
  'ἄ' => 'ἄ',
  'ἅ' => 'ἅ',
  'ἆ' => 'ἆ',
  'ἇ' => 'ἇ',
  'Ἀ' => 'Ἀ',
  'Ἁ' => 'Ἁ',
  'Ἂ' => 'Ἂ',
  'Ἃ' => 'Ἃ',
  'Ἄ' => 'Ἄ',
  'Ἅ' => 'Ἅ',
  'Ἆ' => 'Ἆ',
  'Ἇ' => 'Ἇ',
  'ἐ' => 'ἐ',
  'ἑ' => 'ἑ',
  'ἒ' => 'ἒ',
  'ἓ' => 'ἓ',
  'ἔ' => 'ἔ',
  'ἕ' => 'ἕ',
  'Ἐ' => 'Ἐ',
  'Ἑ' => 'Ἑ',
  'Ἒ' => 'Ἒ',
  'Ἓ' => 'Ἓ',
  'Ἔ' => 'Ἔ',
  'Ἕ' => 'Ἕ',
  'ἠ' => 'ἠ',
  'ἡ' => 'ἡ',
  'ἢ' => 'ἢ',
  'ἣ' => 'ἣ',
  'ἤ' => 'ἤ',
  'ἥ' => 'ἥ',
  'ἦ' => 'ἦ',
  'ἧ' => 'ἧ',
  'Ἠ' => 'Ἠ',
  'Ἡ' => 'Ἡ',
  'Ἢ' => 'Ἢ',
  'Ἣ' => 'Ἣ',
  'Ἤ' => 'Ἤ',
  'Ἥ' => 'Ἥ',
  'Ἦ' => 'Ἦ',
  'Ἧ' => 'Ἧ',
  'ἰ' => 'ἰ',
  'ἱ' => 'ἱ',
  'ἲ' => 'ἲ',
  'ἳ' => 'ἳ',
  'ἴ' => 'ἴ',
  'ἵ' => 'ἵ',
  'ἶ' => 'ἶ',
  'ἷ' => 'ἷ',
  'Ἰ' => 'Ἰ',
  'Ἱ' => 'Ἱ',
  'Ἲ' => 'Ἲ',
  'Ἳ' => 'Ἳ',
  'Ἴ' => 'Ἴ',
  'Ἵ' => 'Ἵ',
  'Ἶ' => 'Ἶ',
  'Ἷ' => 'Ἷ',
  'ὀ' => 'ὀ',
  'ὁ' => 'ὁ',
  'ὂ' => 'ὂ',
  'ὃ' => 'ὃ',
  'ὄ' => 'ὄ',
  'ὅ' => 'ὅ',
  'Ὀ' => 'Ὀ',
  'Ὁ' => 'Ὁ',
  'Ὂ' => 'Ὂ',
  'Ὃ' => 'Ὃ',
  'Ὄ' => 'Ὄ',
  'Ὅ' => 'Ὅ',
  'ὐ' => 'ὐ',
  'ὑ' => 'ὑ',
  'ὒ' => 'ὒ',
  'ὓ' => 'ὓ',
  'ὔ' => 'ὔ',
  'ὕ' => 'ὕ',
  'ὖ' => 'ὖ',
  'ὗ' => 'ὗ',
  'Ὑ' => 'Ὑ',
  'Ὓ' => 'Ὓ',
  'Ὕ' => 'Ὕ',
  'Ὗ' => 'Ὗ',
  'ὠ' => 'ὠ',
  'ὡ' => 'ὡ',
  'ὢ' => 'ὢ',
  'ὣ' => 'ὣ',
  'ὤ' => 'ὤ',
  'ὥ' => 'ὥ',
  'ὦ' => 'ὦ',
  'ὧ' => 'ὧ',
  'Ὠ' => 'Ὠ',
  'Ὡ' => 'Ὡ',
  'Ὢ' => 'Ὢ',
  'Ὣ' => 'Ὣ',
  'Ὤ' => 'Ὤ',
  'Ὥ' => 'Ὥ',
  'Ὦ' => 'Ὦ',
  'Ὧ' => 'Ὧ',
  'ὰ' => 'ὰ',
  'ὲ' => 'ὲ',
  'ὴ' => 'ὴ',
  'ὶ' => 'ὶ',
  'ὸ' => 'ὸ',
  'ὺ' => 'ὺ',
  'ὼ' => 'ὼ',
  'ᾀ' => 'ᾀ',
  'ᾁ' => 'ᾁ',
  'ᾂ' => 'ᾂ',
  'ᾃ' => 'ᾃ',
  'ᾄ' => 'ᾄ',
  'ᾅ' => 'ᾅ',
  'ᾆ' => 'ᾆ',
  'ᾇ' => 'ᾇ',
  'ᾈ' => 'ᾈ',
  'ᾉ' => 'ᾉ',
  'ᾊ' => 'ᾊ',
  'ᾋ' => 'ᾋ',
  'ᾌ' => 'ᾌ',
  'ᾍ' => 'ᾍ',
  'ᾎ' => 'ᾎ',
  'ᾏ' => 'ᾏ',
  'ᾐ' => 'ᾐ',
  'ᾑ' => 'ᾑ',
  'ᾒ' => 'ᾒ',
  'ᾓ' => 'ᾓ',
  'ᾔ' => 'ᾔ',
  'ᾕ' => 'ᾕ',
  'ᾖ' => 'ᾖ',
  'ᾗ' => 'ᾗ',
  'ᾘ' => 'ᾘ',
  'ᾙ' => 'ᾙ',
  'ᾚ' => 'ᾚ',
  'ᾛ' => 'ᾛ',
  'ᾜ' => 'ᾜ',
  'ᾝ' => 'ᾝ',
  'ᾞ' => 'ᾞ',
  'ᾟ' => 'ᾟ',
  'ᾠ' => 'ᾠ',
  'ᾡ' => 'ᾡ',
  'ᾢ' => 'ᾢ',
  'ᾣ' => 'ᾣ',
  'ᾤ' => 'ᾤ',
  'ᾥ' => 'ᾥ',
  'ᾦ' => 'ᾦ',
  'ᾧ' => 'ᾧ',
  'ᾨ' => 'ᾨ',
  'ᾩ' => 'ᾩ',
  'ᾪ' => 'ᾪ',
  'ᾫ' => 'ᾫ',
  'ᾬ' => 'ᾬ',
  'ᾭ' => 'ᾭ',
  'ᾮ' => 'ᾮ',
  'ᾯ' => 'ᾯ',
  'ᾰ' => 'ᾰ',
  'ᾱ' => 'ᾱ',
  'ᾲ' => 'ᾲ',
  'ᾳ' => 'ᾳ',
  'ᾴ' => 'ᾴ',
  'ᾶ' => 'ᾶ',
  'ᾷ' => 'ᾷ',
  'Ᾰ' => 'Ᾰ',
  'Ᾱ' => 'Ᾱ',
  'Ὰ' => 'Ὰ',
  'ᾼ' => 'ᾼ',
  '῁' => '῁',
  'ῂ' => 'ῂ',
  'ῃ' => 'ῃ',
  'ῄ' => 'ῄ',
  'ῆ' => 'ῆ',
  'ῇ' => 'ῇ',
  'Ὲ' => 'Ὲ',
  'Ὴ' => 'Ὴ',
  'ῌ' => 'ῌ',
  '῍' => '῍',
  '῎' => '῎',
  '῏' => '῏',
  'ῐ' => 'ῐ',
  'ῑ' => 'ῑ',
  'ῒ' => 'ῒ',
  'ῖ' => 'ῖ',
  'ῗ' => 'ῗ',
  'Ῐ' => 'Ῐ',
  'Ῑ' => 'Ῑ',
  'Ὶ' => 'Ὶ',
  '῝' => '῝',
  '῞' => '῞',
  '῟' => '῟',
  'ῠ' => 'ῠ',
  'ῡ' => 'ῡ',
  'ῢ' => 'ῢ',
  'ῤ' => 'ῤ',
  'ῥ' => 'ῥ',
  'ῦ' => 'ῦ',
  'ῧ' => 'ῧ',
  'Ῠ' => 'Ῠ',
  'Ῡ' => 'Ῡ',
  'Ὺ' => 'Ὺ',
  'Ῥ' => 'Ῥ',
  '῭' => '῭',
  'ῲ' => 'ῲ',
  'ῳ' => 'ῳ',
  'ῴ' => 'ῴ',
  'ῶ' => 'ῶ',
  'ῷ' => 'ῷ',
  'Ὸ' => 'Ὸ',
  'Ὼ' => 'Ὼ',
  'ῼ' => 'ῼ',
  '↚' => '↚',
  '↛' => '↛',
  '↮' => '↮',
  '⇍' => '⇍',
  '⇎' => '⇎',
  '⇏' => '⇏',
  '∄' => '∄',
  '∉' => '∉',
  '∌' => '∌',
  '∤' => '∤',
  '∦' => '∦',
  '≁' => '≁',
  '≄' => '≄',
  '≇' => '≇',
  '≉' => '≉',
  '≠' => '≠',
  '≢' => '≢',
  '≭' => '≭',
  '≮' => '≮',
  '≯' => '≯',
  '≰' => '≰',
  '≱' => '≱',
  '≴' => '≴',
  '≵' => '≵',
  '≸' => '≸',
  '≹' => '≹',
  '⊀' => '⊀',
  '⊁' => '⊁',
  '⊄' => '⊄',
  '⊅' => '⊅',
  '⊈' => '⊈',
  '⊉' => '⊉',
  '⊬' => '⊬',
  '⊭' => '⊭',
  '⊮' => '⊮',
  '⊯' => '⊯',
  '⋠' => '⋠',
  '⋡' => '⋡',
  '⋢' => '⋢',
  '⋣' => '⋣',
  '⋪' => '⋪',
  '⋫' => '⋫',
  '⋬' => '⋬',
  '⋭' => '⋭',
  'が' => 'が',
  'ぎ' => 'ぎ',
  'ぐ' => 'ぐ',
  'げ' => 'げ',
  'ご' => 'ご',
  'ざ' => 'ざ',
  'じ' => 'じ',
  'ず' => 'ず',
  'ぜ' => 'ぜ',
  'ぞ' => 'ぞ',
  'だ' => 'だ',
  'ぢ' => 'ぢ',
  'づ' => 'づ',
  'で' => 'で',
  'ど' => 'ど',
  'ば' => 'ば',
  'ぱ' => 'ぱ',
  'び' => 'び',
  'ぴ' => 'ぴ',
  'ぶ' => 'ぶ',
  'ぷ' => 'ぷ',
  'べ' => 'べ',
  'ぺ' => 'ぺ',
  'ぼ' => 'ぼ',
  'ぽ' => 'ぽ',
  'ゔ' => 'ゔ',
  'ゞ' => 'ゞ',
  'ガ' => 'ガ',
  'ギ' => 'ギ',
  'グ' => 'グ',
  'ゲ' => 'ゲ',
  'ゴ' => 'ゴ',
  'ザ' => 'ザ',
  'ジ' => 'ジ',
  'ズ' => 'ズ',
  'ゼ' => 'ゼ',
  'ゾ' => 'ゾ',
  'ダ' => 'ダ',
  'ヂ' => 'ヂ',
  'ヅ' => 'ヅ',
  'デ' => 'デ',
  'ド' => 'ド',
  'バ' => 'バ',
  'パ' => 'パ',
  'ビ' => 'ビ',
  'ピ' => 'ピ',
  'ブ' => 'ブ',
  'プ' => 'プ',
  'ベ' => 'ベ',
  'ペ' => 'ペ',
  'ボ' => 'ボ',
  'ポ' => 'ポ',
  'ヴ' => 'ヴ',
  'ヷ' => 'ヷ',
  'ヸ' => 'ヸ',
  'ヹ' => 'ヹ',
  'ヺ' => 'ヺ',
  'ヾ' => 'ヾ',
  '𑂚' => '𑂚',
  '𑂜' => '𑂜',
  '𑂫' => '𑂫',
  '𑄮' => '𑄮',
  '𑄯' => '𑄯',
  '𑍋' => '𑍋',
  '𑍌' => '𑍌',
  '𑒻' => '𑒻',
  '𑒼' => '𑒼',
  '𑒾' => '𑒾',
  '𑖺' => '𑖺',
  '𑖻' => '𑖻',
  '𑤸' => '𑤸',
);
<?php

return array (
  'À' => 'À',
  'Á' => 'Á',
  'Â' => 'Â',
  'Ã' => 'Ã',
  'Ä' => 'Ä',
  'Å' => 'Å',
  'Ç' => 'Ç',
  'È' => 'È',
  'É' => 'É',
  'Ê' => 'Ê',
  'Ë' => 'Ë',
  'Ì' => 'Ì',
  'Í' => 'Í',
  'Î' => 'Î',
  'Ï' => 'Ï',
  'Ñ' => 'Ñ',
  'Ò' => 'Ò',
  'Ó' => 'Ó',
  'Ô' => 'Ô',
  'Õ' => 'Õ',
  'Ö' => 'Ö',
  'Ù' => 'Ù',
  'Ú' => 'Ú',
  'Û' => 'Û',
  'Ü' => 'Ü',
  'Ý' => 'Ý',
  'à' => 'à',
  'á' => 'á',
  'â' => 'â',
  'ã' => 'ã',
  'ä' => 'ä',
  'å' => 'å',
  'ç' => 'ç',
  'è' => 'è',
  'é' => 'é',
  'ê' => 'ê',
  'ë' => 'ë',
  'ì' => 'ì',
  'í' => 'í',
  'î' => 'î',
  'ï' => 'ï',
  'ñ' => 'ñ',
  'ò' => 'ò',
  'ó' => 'ó',
  'ô' => 'ô',
  'õ' => 'õ',
  'ö' => 'ö',
  'ù' => 'ù',
  'ú' => 'ú',
  'û' => 'û',
  'ü' => 'ü',
  'ý' => 'ý',
  'ÿ' => 'ÿ',
  'Ā' => 'Ā',
  'ā' => 'ā',
  'Ă' => 'Ă',
  'ă' => 'ă',
  'Ą' => 'Ą',
  'ą' => 'ą',
  'Ć' => 'Ć',
  'ć' => 'ć',
  'Ĉ' => 'Ĉ',
  'ĉ' => 'ĉ',
  'Ċ' => 'Ċ',
  'ċ' => 'ċ',
  'Č' => 'Č',
  'č' => 'č',
  'Ď' => 'Ď',
  'ď' => 'ď',
  'Ē' => 'Ē',
  'ē' => 'ē',
  'Ĕ' => 'Ĕ',
  'ĕ' => 'ĕ',
  'Ė' => 'Ė',
  'ė' => 'ė',
  'Ę' => 'Ę',
  'ę' => 'ę',
  'Ě' => 'Ě',
  'ě' => 'ě',
  'Ĝ' => 'Ĝ',
  'ĝ' => 'ĝ',
  'Ğ' => 'Ğ',
  'ğ' => 'ğ',
  'Ġ' => 'Ġ',
  'ġ' => 'ġ',
  'Ģ' => 'Ģ',
  'ģ' => 'ģ',
  'Ĥ' => 'Ĥ',
  'ĥ' => 'ĥ',
  'Ĩ' => 'Ĩ',
  'ĩ' => 'ĩ',
  'Ī' => 'Ī',
  'ī' => 'ī',
  'Ĭ' => 'Ĭ',
  'ĭ' => 'ĭ',
  'Į' => 'Į',
  'į' => 'į',
  'İ' => 'İ',
  'Ĵ' => 'Ĵ',
  'ĵ' => 'ĵ',
  'Ķ' => 'Ķ',
  'ķ' => 'ķ',
  'Ĺ' => 'Ĺ',
  'ĺ' => 'ĺ',
  'Ļ' => 'Ļ',
  'ļ' => 'ļ',
  'Ľ' => 'Ľ',
  'ľ' => 'ľ',
  'Ń' => 'Ń',
  'ń' => 'ń',
  'Ņ' => 'Ņ',
  'ņ' => 'ņ',
  'Ň' => 'Ň',
  'ň' => 'ň',
  'Ō' => 'Ō',
  'ō' => 'ō',
  'Ŏ' => 'Ŏ',
  'ŏ' => 'ŏ',
  'Ő' => 'Ő',
  'ő' => 'ő',
  'Ŕ' => 'Ŕ',
  'ŕ' => 'ŕ',
  'Ŗ' => 'Ŗ',
  'ŗ' => 'ŗ',
  'Ř' => 'Ř',
  'ř' => 'ř',
  'Ś' => 'Ś',
  'ś' => 'ś',
  'Ŝ' => 'Ŝ',
  'ŝ' => 'ŝ',
  'Ş' => 'Ş',
  'ş' => 'ş',
  'Š' => 'Š',
  'š' => 'š',
  'Ţ' => 'Ţ',
  'ţ' => 'ţ',
  'Ť' => 'Ť',
  'ť' => 'ť',
  'Ũ' => 'Ũ',
  'ũ' => 'ũ',
  'Ū' => 'Ū',
  'ū' => 'ū',
  'Ŭ' => 'Ŭ',
  'ŭ' => 'ŭ',
  'Ů' => 'Ů',
  'ů' => 'ů',
  'Ű' => 'Ű',
  'ű' => 'ű',
  'Ų' => 'Ų',
  'ų' => 'ų',
  'Ŵ' => 'Ŵ',
  'ŵ' => 'ŵ',
  'Ŷ' => 'Ŷ',
  'ŷ' => 'ŷ',
  'Ÿ' => 'Ÿ',
  'Ź' => 'Ź',
  'ź' => 'ź',
  'Ż' => 'Ż',
  'ż' => 'ż',
  'Ž' => 'Ž',
  'ž' => 'ž',
  'Ơ' => 'Ơ',
  'ơ' => 'ơ',
  'Ư' => 'Ư',
  'ư' => 'ư',
  'Ǎ' => 'Ǎ',
  'ǎ' => 'ǎ',
  'Ǐ' => 'Ǐ',
  'ǐ' => 'ǐ',
  'Ǒ' => 'Ǒ',
  'ǒ' => 'ǒ',
  'Ǔ' => 'Ǔ',
  'ǔ' => 'ǔ',
  'Ǖ' => 'Ǖ',
  'ǖ' => 'ǖ',
  'Ǘ' => 'Ǘ',
  'ǘ' => 'ǘ',
  'Ǚ' => 'Ǚ',
  'ǚ' => 'ǚ',
  'Ǜ' => 'Ǜ',
  'ǜ' => 'ǜ',
  'Ǟ' => 'Ǟ',
  'ǟ' => 'ǟ',
  'Ǡ' => 'Ǡ',
  'ǡ' => 'ǡ',
  'Ǣ' => 'Ǣ',
  'ǣ' => 'ǣ',
  'Ǧ' => 'Ǧ',
  'ǧ' => 'ǧ',
  'Ǩ' => 'Ǩ',
  'ǩ' => 'ǩ',
  'Ǫ' => 'Ǫ',
  'ǫ' => 'ǫ',
  'Ǭ' => 'Ǭ',
  'ǭ' => 'ǭ',
  'Ǯ' => 'Ǯ',
  'ǯ' => 'ǯ',
  'ǰ' => 'ǰ',
  'Ǵ' => 'Ǵ',
  'ǵ' => 'ǵ',
  'Ǹ' => 'Ǹ',
  'ǹ' => 'ǹ',
  'Ǻ' => 'Ǻ',
  'ǻ' => 'ǻ',
  'Ǽ' => 'Ǽ',
  'ǽ' => 'ǽ',
  'Ǿ' => 'Ǿ',
  'ǿ' => 'ǿ',
  'Ȁ' => 'Ȁ',
  'ȁ' => 'ȁ',
  'Ȃ' => 'Ȃ',
  'ȃ' => 'ȃ',
  'Ȅ' => 'Ȅ',
  'ȅ' => 'ȅ',
  'Ȇ' => 'Ȇ',
  'ȇ' => 'ȇ',
  'Ȉ' => 'Ȉ',
  'ȉ' => 'ȉ',
  'Ȋ' => 'Ȋ',
  'ȋ' => 'ȋ',
  'Ȍ' => 'Ȍ',
  'ȍ' => 'ȍ',
  'Ȏ' => 'Ȏ',
  'ȏ' => 'ȏ',
  'Ȑ' => 'Ȑ',
  'ȑ' => 'ȑ',
  'Ȓ' => 'Ȓ',
  'ȓ' => 'ȓ',
  'Ȕ' => 'Ȕ',
  'ȕ' => 'ȕ',
  'Ȗ' => 'Ȗ',
  'ȗ' => 'ȗ',
  'Ș' => 'Ș',
  'ș' => 'ș',
  'Ț' => 'Ț',
  'ț' => 'ț',
  'Ȟ' => 'Ȟ',
  'ȟ' => 'ȟ',
  'Ȧ' => 'Ȧ',
  'ȧ' => 'ȧ',
  'Ȩ' => 'Ȩ',
  'ȩ' => 'ȩ',
  'Ȫ' => 'Ȫ',
  'ȫ' => 'ȫ',
  'Ȭ' => 'Ȭ',
  'ȭ' => 'ȭ',
  'Ȯ' => 'Ȯ',
  'ȯ' => 'ȯ',
  'Ȱ' => 'Ȱ',
  'ȱ' => 'ȱ',
  'Ȳ' => 'Ȳ',
  'ȳ' => 'ȳ',
  '̀' => '̀',
  '́' => '́',
  '̓' => '̓',
  '̈́' => '̈́',
  'ʹ' => 'ʹ',
  ';' => ';',
  '΅' => '΅',
  'Ά' => 'Ά',
  '·' => '·',
  'Έ' => 'Έ',
  'Ή' => 'Ή',
  'Ί' => 'Ί',
  'Ό' => 'Ό',
  'Ύ' => 'Ύ',
  'Ώ' => 'Ώ',
  'ΐ' => 'ΐ',
  'Ϊ' => 'Ϊ',
  'Ϋ' => 'Ϋ',
  'ά' => 'ά',
  'έ' => 'έ',
  'ή' => 'ή',
  'ί' => 'ί',
  'ΰ' => 'ΰ',
  'ϊ' => 'ϊ',
  'ϋ' => 'ϋ',
  'ό' => 'ό',
  'ύ' => 'ύ',
  'ώ' => 'ώ',
  'ϓ' => 'ϓ',
  'ϔ' => 'ϔ',
  'Ѐ' => 'Ѐ',
  'Ё' => 'Ё',
  'Ѓ' => 'Ѓ',
  'Ї' => 'Ї',
  'Ќ' => 'Ќ',
  'Ѝ' => 'Ѝ',
  'Ў' => 'Ў',
  'Й' => 'Й',
  'й' => 'й',
  'ѐ' => 'ѐ',
  'ё' => 'ё',
  'ѓ' => 'ѓ',
  'ї' => 'ї',
  'ќ' => 'ќ',
  'ѝ' => 'ѝ',
  'ў' => 'ў',
  'Ѷ' => 'Ѷ',
  'ѷ' => 'ѷ',
  'Ӂ' => 'Ӂ',
  'ӂ' => 'ӂ',
  'Ӑ' => 'Ӑ',
  'ӑ' => 'ӑ',
  'Ӓ' => 'Ӓ',
  'ӓ' => 'ӓ',
  'Ӗ' => 'Ӗ',
  'ӗ' => 'ӗ',
  'Ӛ' => 'Ӛ',
  'ӛ' => 'ӛ',
  'Ӝ' => 'Ӝ',
  'ӝ' => 'ӝ',
  'Ӟ' => 'Ӟ',
  'ӟ' => 'ӟ',
  'Ӣ' => 'Ӣ',
  'ӣ' => 'ӣ',
  'Ӥ' => 'Ӥ',
  'ӥ' => 'ӥ',
  'Ӧ' => 'Ӧ',
  'ӧ' => 'ӧ',
  'Ӫ' => 'Ӫ',
  'ӫ' => 'ӫ',
  'Ӭ' => 'Ӭ',
  'ӭ' => 'ӭ',
  'Ӯ' => 'Ӯ',
  'ӯ' => 'ӯ',
  'Ӱ' => 'Ӱ',
  'ӱ' => 'ӱ',
  'Ӳ' => 'Ӳ',
  'ӳ' => 'ӳ',
  'Ӵ' => 'Ӵ',
  'ӵ' => 'ӵ',
  'Ӹ' => 'Ӹ',
  'ӹ' => 'ӹ',
  'آ' => 'آ',
  'أ' => 'أ',
  'ؤ' => 'ؤ',
  'إ' => 'إ',
  'ئ' => 'ئ',
  'ۀ' => 'ۀ',
  'ۂ' => 'ۂ',
  'ۓ' => 'ۓ',
  'ऩ' => 'ऩ',
  'ऱ' => 'ऱ',
  'ऴ' => 'ऴ',
  'क़' => 'क़',
  'ख़' => 'ख़',
  'ग़' => 'ग़',
  'ज़' => 'ज़',
  'ड़' => 'ड़',
  'ढ़' => 'ढ़',
  'फ़' => 'फ़',
  'य़' => 'य़',
  'ো' => 'ো',
  'ৌ' => 'ৌ',
  'ড়' => 'ড়',
  'ঢ়' => 'ঢ়',
  'য়' => 'য়',
  'ਲ਼' => 'ਲ਼',
  'ਸ਼' => 'ਸ਼',
  'ਖ਼' => 'ਖ਼',
  'ਗ਼' => 'ਗ਼',
  'ਜ਼' => 'ਜ਼',
  'ਫ਼' => 'ਫ਼',
  'ୈ' => 'ୈ',
  'ୋ' => 'ୋ',
  'ୌ' => 'ୌ',
  'ଡ଼' => 'ଡ଼',
  'ଢ଼' => 'ଢ଼',
  'ஔ' => 'ஔ',
  'ொ' => 'ொ',
  'ோ' => 'ோ',
  'ௌ' => 'ௌ',
  'ై' => 'ై',
  'ೀ' => 'ೀ',
  'ೇ' => 'ೇ',
  'ೈ' => 'ೈ',
  'ೊ' => 'ೊ',
  'ೋ' => 'ೋ',
  'ൊ' => 'ൊ',
  'ോ' => 'ോ',
  'ൌ' => 'ൌ',
  'ේ' => 'ේ',
  'ො' => 'ො',
  'ෝ' => 'ෝ',
  'ෞ' => 'ෞ',
  'གྷ' => 'གྷ',
  'ཌྷ' => 'ཌྷ',
  'དྷ' => 'དྷ',
  'བྷ' => 'བྷ',
  'ཛྷ' => 'ཛྷ',
  'ཀྵ' => 'ཀྵ',
  'ཱི' => 'ཱི',
  'ཱུ' => 'ཱུ',
  'ྲྀ' => 'ྲྀ',
  'ླྀ' => 'ླྀ',
  'ཱྀ' => 'ཱྀ',
  'ྒྷ' => 'ྒྷ',
  'ྜྷ' => 'ྜྷ',
  'ྡྷ' => 'ྡྷ',
  'ྦྷ' => 'ྦྷ',
  'ྫྷ' => 'ྫྷ',
  'ྐྵ' => 'ྐྵ',
  'ဦ' => 'ဦ',
  'ᬆ' => 'ᬆ',
  'ᬈ' => 'ᬈ',
  'ᬊ' => 'ᬊ',
  'ᬌ' => 'ᬌ',
  'ᬎ' => 'ᬎ',
  'ᬒ' => 'ᬒ',
  'ᬻ' => 'ᬻ',
  'ᬽ' => 'ᬽ',
  'ᭀ' => 'ᭀ',
  'ᭁ' => 'ᭁ',
  'ᭃ' => 'ᭃ',
  'Ḁ' => 'Ḁ',
  'ḁ' => 'ḁ',
  'Ḃ' => 'Ḃ',
  'ḃ' => 'ḃ',
  'Ḅ' => 'Ḅ',
  'ḅ' => 'ḅ',
  'Ḇ' => 'Ḇ',
  'ḇ' => 'ḇ',
  'Ḉ' => 'Ḉ',
  'ḉ' => 'ḉ',
  'Ḋ' => 'Ḋ',
  'ḋ' => 'ḋ',
  'Ḍ' => 'Ḍ',
  'ḍ' => 'ḍ',
  'Ḏ' => 'Ḏ',
  'ḏ' => 'ḏ',
  'Ḑ' => 'Ḑ',
  'ḑ' => 'ḑ',
  'Ḓ' => 'Ḓ',
  'ḓ' => 'ḓ',
  'Ḕ' => 'Ḕ',
  'ḕ' => 'ḕ',
  'Ḗ' => 'Ḗ',
  'ḗ' => 'ḗ',
  'Ḙ' => 'Ḙ',
  'ḙ' => 'ḙ',
  'Ḛ' => 'Ḛ',
  'ḛ' => 'ḛ',
  'Ḝ' => 'Ḝ',
  'ḝ' => 'ḝ',
  'Ḟ' => 'Ḟ',
  'ḟ' => 'ḟ',
  'Ḡ' => 'Ḡ',
  'ḡ' => 'ḡ',
  'Ḣ' => 'Ḣ',
  'ḣ' => 'ḣ',
  'Ḥ' => 'Ḥ',
  'ḥ' => 'ḥ',
  'Ḧ' => 'Ḧ',
  'ḧ' => 'ḧ',
  'Ḩ' => 'Ḩ',
  'ḩ' => 'ḩ',
  'Ḫ' => 'Ḫ',
  'ḫ' => 'ḫ',
  'Ḭ' => 'Ḭ',
  'ḭ' => 'ḭ',
  'Ḯ' => 'Ḯ',
  'ḯ' => 'ḯ',
  'Ḱ' => 'Ḱ',
  'ḱ' => 'ḱ',
  'Ḳ' => 'Ḳ',
  'ḳ' => 'ḳ',
  'Ḵ' => 'Ḵ',
  'ḵ' => 'ḵ',
  'Ḷ' => 'Ḷ',
  'ḷ' => 'ḷ',
  'Ḹ' => 'Ḹ',
  'ḹ' => 'ḹ',
  'Ḻ' => 'Ḻ',
  'ḻ' => 'ḻ',
  'Ḽ' => 'Ḽ',
  'ḽ' => 'ḽ',
  'Ḿ' => 'Ḿ',
  'ḿ' => 'ḿ',
  'Ṁ' => 'Ṁ',
  'ṁ' => 'ṁ',
  'Ṃ' => 'Ṃ',
  'ṃ' => 'ṃ',
  'Ṅ' => 'Ṅ',
  'ṅ' => 'ṅ',
  'Ṇ' => 'Ṇ',
  'ṇ' => 'ṇ',
  'Ṉ' => 'Ṉ',
  'ṉ' => 'ṉ',
  'Ṋ' => 'Ṋ',
  'ṋ' => 'ṋ',
  'Ṍ' => 'Ṍ',
  'ṍ' => 'ṍ',
  'Ṏ' => 'Ṏ',
  'ṏ' => 'ṏ',
  'Ṑ' => 'Ṑ',
  'ṑ' => 'ṑ',
  'Ṓ' => 'Ṓ',
  'ṓ' => 'ṓ',
  'Ṕ' => 'Ṕ',
  'ṕ' => 'ṕ',
  'Ṗ' => 'Ṗ',
  'ṗ' => 'ṗ',
  'Ṙ' => 'Ṙ',
  'ṙ' => 'ṙ',
  'Ṛ' => 'Ṛ',
  'ṛ' => 'ṛ',
  'Ṝ' => 'Ṝ',
  'ṝ' => 'ṝ',
  'Ṟ' => 'Ṟ',
  'ṟ' => 'ṟ',
  'Ṡ' => 'Ṡ',
  'ṡ' => 'ṡ',
  'Ṣ' => 'Ṣ',
  'ṣ' => 'ṣ',
  'Ṥ' => 'Ṥ',
  'ṥ' => 'ṥ',
  'Ṧ' => 'Ṧ',
  'ṧ' => 'ṧ',
  'Ṩ' => 'Ṩ',
  'ṩ' => 'ṩ',
  'Ṫ' => 'Ṫ',
  'ṫ' => 'ṫ',
  'Ṭ' => 'Ṭ',
  'ṭ' => 'ṭ',
  'Ṯ' => 'Ṯ',
  'ṯ' => 'ṯ',
  'Ṱ' => 'Ṱ',
  'ṱ' => 'ṱ',
  'Ṳ' => 'Ṳ',
  'ṳ' => 'ṳ',
  'Ṵ' => 'Ṵ',
  'ṵ' => 'ṵ',
  'Ṷ' => 'Ṷ',
  'ṷ' => 'ṷ',
  'Ṹ' => 'Ṹ',
  'ṹ' => 'ṹ',
  'Ṻ' => 'Ṻ',
  'ṻ' => 'ṻ',
  'Ṽ' => 'Ṽ',
  'ṽ' => 'ṽ',
  'Ṿ' => 'Ṿ',
  'ṿ' => 'ṿ',
  'Ẁ' => 'Ẁ',
  'ẁ' => 'ẁ',
  'Ẃ' => 'Ẃ',
  'ẃ' => 'ẃ',
  'Ẅ' => 'Ẅ',
  'ẅ' => 'ẅ',
  'Ẇ' => 'Ẇ',
  'ẇ' => 'ẇ',
  'Ẉ' => 'Ẉ',
  'ẉ' => 'ẉ',
  'Ẋ' => 'Ẋ',
  'ẋ' => 'ẋ',
  'Ẍ' => 'Ẍ',
  'ẍ' => 'ẍ',
  'Ẏ' => 'Ẏ',
  'ẏ' => 'ẏ',
  'Ẑ' => 'Ẑ',
  'ẑ' => 'ẑ',
  'Ẓ' => 'Ẓ',
  'ẓ' => 'ẓ',
  'Ẕ' => 'Ẕ',
  'ẕ' => 'ẕ',
  'ẖ' => 'ẖ',
  'ẗ' => 'ẗ',
  'ẘ' => 'ẘ',
  'ẙ' => 'ẙ',
  'ẛ' => 'ẛ',
  'Ạ' => 'Ạ',
  'ạ' => 'ạ',
  'Ả' => 'Ả',
  'ả' => 'ả',
  'Ấ' => 'Ấ',
  'ấ' => 'ấ',
  'Ầ' => 'Ầ',
  'ầ' => 'ầ',
  'Ẩ' => 'Ẩ',
  'ẩ' => 'ẩ',
  'Ẫ' => 'Ẫ',
  'ẫ' => 'ẫ',
  'Ậ' => 'Ậ',
  'ậ' => 'ậ',
  'Ắ' => 'Ắ',
  'ắ' => 'ắ',
  'Ằ' => 'Ằ',
  'ằ' => 'ằ',
  'Ẳ' => 'Ẳ',
  'ẳ' => 'ẳ',
  'Ẵ' => 'Ẵ',
  'ẵ' => 'ẵ',
  'Ặ' => 'Ặ',
  'ặ' => 'ặ',
  'Ẹ' => 'Ẹ',
  'ẹ' => 'ẹ',
  'Ẻ' => 'Ẻ',
  'ẻ' => 'ẻ',
  'Ẽ' => 'Ẽ',
  'ẽ' => 'ẽ',
  'Ế' => 'Ế',
  'ế' => 'ế',
  'Ề' => 'Ề',
  'ề' => 'ề',
  'Ể' => 'Ể',
  'ể' => 'ể',
  'Ễ' => 'Ễ',
  'ễ' => 'ễ',
  'Ệ' => 'Ệ',
  'ệ' => 'ệ',
  'Ỉ' => 'Ỉ',
  'ỉ' => 'ỉ',
  'Ị' => 'Ị',
  'ị' => 'ị',
  'Ọ' => 'Ọ',
  'ọ' => 'ọ',
  'Ỏ' => 'Ỏ',
  'ỏ' => 'ỏ',
  'Ố' => 'Ố',
  'ố' => 'ố',
  'Ồ' => 'Ồ',
  'ồ' => 'ồ',
  'Ổ' => 'Ổ',
  'ổ' => 'ổ',
  'Ỗ' => 'Ỗ',
  'ỗ' => 'ỗ',
  'Ộ' => 'Ộ',
  'ộ' => 'ộ',
  'Ớ' => 'Ớ',
  'ớ' => 'ớ',
  'Ờ' => 'Ờ',
  'ờ' => 'ờ',
  'Ở' => 'Ở',
  'ở' => 'ở',
  'Ỡ' => 'Ỡ',
  'ỡ' => 'ỡ',
  'Ợ' => 'Ợ',
  'ợ' => 'ợ',
  'Ụ' => 'Ụ',
  'ụ' => 'ụ',
  'Ủ' => 'Ủ',
  'ủ' => 'ủ',
  'Ứ' => 'Ứ',
  'ứ' => 'ứ',
  'Ừ' => 'Ừ',
  'ừ' => 'ừ',
  'Ử' => 'Ử',
  'ử' => 'ử',
  'Ữ' => 'Ữ',
  'ữ' => 'ữ',
  'Ự' => 'Ự',
  'ự' => 'ự',
  'Ỳ' => 'Ỳ',
  'ỳ' => 'ỳ',
  'Ỵ' => 'Ỵ',
  'ỵ' => 'ỵ',
  'Ỷ' => 'Ỷ',
  'ỷ' => 'ỷ',
  'Ỹ' => 'Ỹ',
  'ỹ' => 'ỹ',
  'ἀ' => 'ἀ',
  'ἁ' => 'ἁ',
  'ἂ' => 'ἂ',
  'ἃ' => 'ἃ',
  'ἄ' => 'ἄ',
  'ἅ' => 'ἅ',
  'ἆ' => 'ἆ',
  'ἇ' => 'ἇ',
  'Ἀ' => 'Ἀ',
  'Ἁ' => 'Ἁ',
  'Ἂ' => 'Ἂ',
  'Ἃ' => 'Ἃ',
  'Ἄ' => 'Ἄ',
  'Ἅ' => 'Ἅ',
  'Ἆ' => 'Ἆ',
  'Ἇ' => 'Ἇ',
  'ἐ' => 'ἐ',
  'ἑ' => 'ἑ',
  'ἒ' => 'ἒ',
  'ἓ' => 'ἓ',
  'ἔ' => 'ἔ',
  'ἕ' => 'ἕ',
  'Ἐ' => 'Ἐ',
  'Ἑ' => 'Ἑ',
  'Ἒ' => 'Ἒ',
  'Ἓ' => 'Ἓ',
  'Ἔ' => 'Ἔ',
  'Ἕ' => 'Ἕ',
  'ἠ' => 'ἠ',
  'ἡ' => 'ἡ',
  'ἢ' => 'ἢ',
  'ἣ' => 'ἣ',
  'ἤ' => 'ἤ',
  'ἥ' => 'ἥ',
  'ἦ' => 'ἦ',
  'ἧ' => 'ἧ',
  'Ἠ' => 'Ἠ',
  'Ἡ' => 'Ἡ',
  'Ἢ' => 'Ἢ',
  'Ἣ' => 'Ἣ',
  'Ἤ' => 'Ἤ',
  'Ἥ' => 'Ἥ',
  'Ἦ' => 'Ἦ',
  'Ἧ' => 'Ἧ',
  'ἰ' => 'ἰ',
  'ἱ' => 'ἱ',
  'ἲ' => 'ἲ',
  'ἳ' => 'ἳ',
  'ἴ' => 'ἴ',
  'ἵ' => 'ἵ',
  'ἶ' => 'ἶ',
  'ἷ' => 'ἷ',
  'Ἰ' => 'Ἰ',
  'Ἱ' => 'Ἱ',
  'Ἲ' => 'Ἲ',
  'Ἳ' => 'Ἳ',
  'Ἴ' => 'Ἴ',
  'Ἵ' => 'Ἵ',
  'Ἶ' => 'Ἶ',
  'Ἷ' => 'Ἷ',
  'ὀ' => 'ὀ',
  'ὁ' => 'ὁ',
  'ὂ' => 'ὂ',
  'ὃ' => 'ὃ',
  'ὄ' => 'ὄ',
  'ὅ' => 'ὅ',
  'Ὀ' => 'Ὀ',
  'Ὁ' => 'Ὁ',
  'Ὂ' => 'Ὂ',
  'Ὃ' => 'Ὃ',
  'Ὄ' => 'Ὄ',
  'Ὅ' => 'Ὅ',
  'ὐ' => 'ὐ',
  'ὑ' => 'ὑ',
  'ὒ' => 'ὒ',
  'ὓ' => 'ὓ',
  'ὔ' => 'ὔ',
  'ὕ' => 'ὕ',
  'ὖ' => 'ὖ',
  'ὗ' => 'ὗ',
  'Ὑ' => 'Ὑ',
  'Ὓ' => 'Ὓ',
  'Ὕ' => 'Ὕ',
  'Ὗ' => 'Ὗ',
  'ὠ' => 'ὠ',
  'ὡ' => 'ὡ',
  'ὢ' => 'ὢ',
  'ὣ' => 'ὣ',
  'ὤ' => 'ὤ',
  'ὥ' => 'ὥ',
  'ὦ' => 'ὦ',
  'ὧ' => 'ὧ',
  'Ὠ' => 'Ὠ',
  'Ὡ' => 'Ὡ',
  'Ὢ' => 'Ὢ',
  'Ὣ' => 'Ὣ',
  'Ὤ' => 'Ὤ',
  'Ὥ' => 'Ὥ',
  'Ὦ' => 'Ὦ',
  'Ὧ' => 'Ὧ',
  'ὰ' => 'ὰ',
  'ά' => 'ά',
  'ὲ' => 'ὲ',
  'έ' => 'έ',
  'ὴ' => 'ὴ',
  'ή' => 'ή',
  'ὶ' => 'ὶ',
  'ί' => 'ί',
  'ὸ' => 'ὸ',
  'ό' => 'ό',
  'ὺ' => 'ὺ',
  'ύ' => 'ύ',
  'ὼ' => 'ὼ',
  'ώ' => 'ώ',
  'ᾀ' => 'ᾀ',
  'ᾁ' => 'ᾁ',
  'ᾂ' => 'ᾂ',
  'ᾃ' => 'ᾃ',
  'ᾄ' => 'ᾄ',
  'ᾅ' => 'ᾅ',
  'ᾆ' => 'ᾆ',
  'ᾇ' => 'ᾇ',
  'ᾈ' => 'ᾈ',
  'ᾉ' => 'ᾉ',
  'ᾊ' => 'ᾊ',
  'ᾋ' => 'ᾋ',
  'ᾌ' => 'ᾌ',
  'ᾍ' => 'ᾍ',
  'ᾎ' => 'ᾎ',
  'ᾏ' => 'ᾏ',
  'ᾐ' => 'ᾐ',
  'ᾑ' => 'ᾑ',
  'ᾒ' => 'ᾒ',
  'ᾓ' => 'ᾓ',
  'ᾔ' => 'ᾔ',
  'ᾕ' => 'ᾕ',
  'ᾖ' => 'ᾖ',
  'ᾗ' => 'ᾗ',
  'ᾘ' => 'ᾘ',
  'ᾙ' => 'ᾙ',
  'ᾚ' => 'ᾚ',
  'ᾛ' => 'ᾛ',
  'ᾜ' => 'ᾜ',
  'ᾝ' => 'ᾝ',
  'ᾞ' => 'ᾞ',
  'ᾟ' => 'ᾟ',
  'ᾠ' => 'ᾠ',
  'ᾡ' => 'ᾡ',
  'ᾢ' => 'ᾢ',
  'ᾣ' => 'ᾣ',
  'ᾤ' => 'ᾤ',
  'ᾥ' => 'ᾥ',
  'ᾦ' => 'ᾦ',
  'ᾧ' => 'ᾧ',
  'ᾨ' => 'ᾨ',
  'ᾩ' => 'ᾩ',
  'ᾪ' => 'ᾪ',
  'ᾫ' => 'ᾫ',
  'ᾬ' => 'ᾬ',
  'ᾭ' => 'ᾭ',
  'ᾮ' => 'ᾮ',
  'ᾯ' => 'ᾯ',
  'ᾰ' => 'ᾰ',
  'ᾱ' => 'ᾱ',
  'ᾲ' => 'ᾲ',
  'ᾳ' => 'ᾳ',
  'ᾴ' => 'ᾴ',
  'ᾶ' => 'ᾶ',
  'ᾷ' => 'ᾷ',
  'Ᾰ' => 'Ᾰ',
  'Ᾱ' => 'Ᾱ',
  'Ὰ' => 'Ὰ',
  'Ά' => 'Ά',
  'ᾼ' => 'ᾼ',
  'ι' => 'ι',
  '῁' => '῁',
  'ῂ' => 'ῂ',
  'ῃ' => 'ῃ',
  'ῄ' => 'ῄ',
  'ῆ' => 'ῆ',
  'ῇ' => 'ῇ',
  'Ὲ' => 'Ὲ',
  'Έ' => 'Έ',
  'Ὴ' => 'Ὴ',
  'Ή' => 'Ή',
  'ῌ' => 'ῌ',
  '῍' => '῍',
  '῎' => '῎',
  '῏' => '῏',
  'ῐ' => 'ῐ',
  'ῑ' => 'ῑ',
  'ῒ' => 'ῒ',
  'ΐ' => 'ΐ',
  'ῖ' => 'ῖ',
  'ῗ' => 'ῗ',
  'Ῐ' => 'Ῐ',
  'Ῑ' => 'Ῑ',
  'Ὶ' => 'Ὶ',
  'Ί' => 'Ί',
  '῝' => '῝',
  '῞' => '῞',
  '῟' => '῟',
  'ῠ' => 'ῠ',
  'ῡ' => 'ῡ',
  'ῢ' => 'ῢ',
  'ΰ' => 'ΰ',
  'ῤ' => 'ῤ',
  'ῥ' => 'ῥ',
  'ῦ' => 'ῦ',
  'ῧ' => 'ῧ',
  'Ῠ' => 'Ῠ',
  'Ῡ' => 'Ῡ',
  'Ὺ' => 'Ὺ',
  'Ύ' => 'Ύ',
  'Ῥ' => 'Ῥ',
  '῭' => '῭',
  '΅' => '΅',
  '`' => '`',
  'ῲ' => 'ῲ',
  'ῳ' => 'ῳ',
  'ῴ' => 'ῴ',
  'ῶ' => 'ῶ',
  'ῷ' => 'ῷ',
  'Ὸ' => 'Ὸ',
  'Ό' => 'Ό',
  'Ὼ' => 'Ὼ',
  'Ώ' => 'Ώ',
  'ῼ' => 'ῼ',
  '´' => '´',
  ' ' => ' ',
  ' ' => ' ',
  'Ω' => 'Ω',
  'K' => 'K',
  'Å' => 'Å',
  '↚' => '↚',
  '↛' => '↛',
  '↮' => '↮',
  '⇍' => '⇍',
  '⇎' => '⇎',
  '⇏' => '⇏',
  '∄' => '∄',
  '∉' => '∉',
  '∌' => '∌',
  '∤' => '∤',
  '∦' => '∦',
  '≁' => '≁',
  '≄' => '≄',
  '≇' => '≇',
  '≉' => '≉',
  '≠' => '≠',
  '≢' => '≢',
  '≭' => '≭',
  '≮' => '≮',
  '≯' => '≯',
  '≰' => '≰',
  '≱' => '≱',
  '≴' => '≴',
  '≵' => '≵',
  '≸' => '≸',
  '≹' => '≹',
  '⊀' => '⊀',
  '⊁' => '⊁',
  '⊄' => '⊄',
  '⊅' => '⊅',
  '⊈' => '⊈',
  '⊉' => '⊉',
  '⊬' => '⊬',
  '⊭' => '⊭',
  '⊮' => '⊮',
  '⊯' => '⊯',
  '⋠' => '⋠',
  '⋡' => '⋡',
  '⋢' => '⋢',
  '⋣' => '⋣',
  '⋪' => '⋪',
  '⋫' => '⋫',
  '⋬' => '⋬',
  '⋭' => '⋭',
  '〈' => '〈',
  '〉' => '〉',
  '⫝̸' => '⫝̸',
  'が' => 'が',
  'ぎ' => 'ぎ',
  'ぐ' => 'ぐ',
  'げ' => 'げ',
  'ご' => 'ご',
  'ざ' => 'ざ',
  'じ' => 'じ',
  'ず' => 'ず',
  'ぜ' => 'ぜ',
  'ぞ' => 'ぞ',
  'だ' => 'だ',
  'ぢ' => 'ぢ',
  'づ' => 'づ',
  'で' => 'で',
  'ど' => 'ど',
  'ば' => 'ば',
  'ぱ' => 'ぱ',
  'び' => 'び',
  'ぴ' => 'ぴ',
  'ぶ' => 'ぶ',
  'ぷ' => 'ぷ',
  'べ' => 'べ',
  'ぺ' => 'ぺ',
  'ぼ' => 'ぼ',
  'ぽ' => 'ぽ',
  'ゔ' => 'ゔ',
  'ゞ' => 'ゞ',
  'ガ' => 'ガ',
  'ギ' => 'ギ',
  'グ' => 'グ',
  'ゲ' => 'ゲ',
  'ゴ' => 'ゴ',
  'ザ' => 'ザ',
  'ジ' => 'ジ',
  'ズ' => 'ズ',
  'ゼ' => 'ゼ',
  'ゾ' => 'ゾ',
  'ダ' => 'ダ',
  'ヂ' => 'ヂ',
  'ヅ' => 'ヅ',
  'デ' => 'デ',
  'ド' => 'ド',
  'バ' => 'バ',
  'パ' => 'パ',
  'ビ' => 'ビ',
  'ピ' => 'ピ',
  'ブ' => 'ブ',
  'プ' => 'プ',
  'ベ' => 'ベ',
  'ペ' => 'ペ',
  'ボ' => 'ボ',
  'ポ' => 'ポ',
  'ヴ' => 'ヴ',
  'ヷ' => 'ヷ',
  'ヸ' => 'ヸ',
  'ヹ' => 'ヹ',
  'ヺ' => 'ヺ',
  'ヾ' => 'ヾ',
  '豈' => '豈',
  '更' => '更',
  '車' => '車',
  '賈' => '賈',
  '滑' => '滑',
  '串' => '串',
  '句' => '句',
  '龜' => '龜',
  '龜' => '龜',
  '契' => '契',
  '金' => '金',
  '喇' => '喇',
  '奈' => '奈',
  '懶' => '懶',
  '癩' => '癩',
  '羅' => '羅',
  '蘿' => '蘿',
  '螺' => '螺',
  '裸' => '裸',
  '邏' => '邏',
  '樂' => '樂',
  '洛' => '洛',
  '烙' => '烙',
  '珞' => '珞',
  '落' => '落',
  '酪' => '酪',
  '駱' => '駱',
  '亂' => '亂',
  '卵' => '卵',
  '欄' => '欄',
  '爛' => '爛',
  '蘭' => '蘭',
  '鸞' => '鸞',
  '嵐' => '嵐',
  '濫' => '濫',
  '藍' => '藍',
  '襤' => '襤',
  '拉' => '拉',
  '臘' => '臘',
  '蠟' => '蠟',
  '廊' => '廊',
  '朗' => '朗',
  '浪' => '浪',
  '狼' => '狼',
  '郎' => '郎',
  '來' => '來',
  '冷' => '冷',
  '勞' => '勞',
  '擄' => '擄',
  '櫓' => '櫓',
  '爐' => '爐',
  '盧' => '盧',
  '老' => '老',
  '蘆' => '蘆',
  '虜' => '虜',
  '路' => '路',
  '露' => '露',
  '魯' => '魯',
  '鷺' => '鷺',
  '碌' => '碌',
  '祿' => '祿',
  '綠' => '綠',
  '菉' => '菉',
  '錄' => '錄',
  '鹿' => '鹿',
  '論' => '論',
  '壟' => '壟',
  '弄' => '弄',
  '籠' => '籠',
  '聾' => '聾',
  '牢' => '牢',
  '磊' => '磊',
  '賂' => '賂',
  '雷' => '雷',
  '壘' => '壘',
  '屢' => '屢',
  '樓' => '樓',
  '淚' => '淚',
  '漏' => '漏',
  '累' => '累',
  '縷' => '縷',
  '陋' => '陋',
  '勒' => '勒',
  '肋' => '肋',
  '凜' => '凜',
  '凌' => '凌',
  '稜' => '稜',
  '綾' => '綾',
  '菱' => '菱',
  '陵' => '陵',
  '讀' => '讀',
  '拏' => '拏',
  '樂' => '樂',
  '諾' => '諾',
  '丹' => '丹',
  '寧' => '寧',
  '怒' => '怒',
  '率' => '率',
  '異' => '異',
  '北' => '北',
  '磻' => '磻',
  '便' => '便',
  '復' => '復',
  '不' => '不',
  '泌' => '泌',
  '數' => '數',
  '索' => '索',
  '參' => '參',
  '塞' => '塞',
  '省' => '省',
  '葉' => '葉',
  '說' => '說',
  '殺' => '殺',
  '辰' => '辰',
  '沈' => '沈',
  '拾' => '拾',
  '若' => '若',
  '掠' => '掠',
  '略' => '略',
  '亮' => '亮',
  '兩' => '兩',
  '凉' => '凉',
  '梁' => '梁',
  '糧' => '糧',
  '良' => '良',
  '諒' => '諒',
  '量' => '量',
  '勵' => '勵',
  '呂' => '呂',
  '女' => '女',
  '廬' => '廬',
  '旅' => '旅',
  '濾' => '濾',
  '礪' => '礪',
  '閭' => '閭',
  '驪' => '驪',
  '麗' => '麗',
  '黎' => '黎',
  '力' => '力',
  '曆' => '曆',
  '歷' => '歷',
  '轢' => '轢',
  '年' => '年',
  '憐' => '憐',
  '戀' => '戀',
  '撚' => '撚',
  '漣' => '漣',
  '煉' => '煉',
  '璉' => '璉',
  '秊' => '秊',
  '練' => '練',
  '聯' => '聯',
  '輦' => '輦',
  '蓮' => '蓮',
  '連' => '連',
  '鍊' => '鍊',
  '列' => '列',
  '劣' => '劣',
  '咽' => '咽',
  '烈' => '烈',
  '裂' => '裂',
  '說' => '說',
  '廉' => '廉',
  '念' => '念',
  '捻' => '捻',
  '殮' => '殮',
  '簾' => '簾',
  '獵' => '獵',
  '令' => '令',
  '囹' => '囹',
  '寧' => '寧',
  '嶺' => '嶺',
  '怜' => '怜',
  '玲' => '玲',
  '瑩' => '瑩',
  '羚' => '羚',
  '聆' => '聆',
  '鈴' => '鈴',
  '零' => '零',
  '靈' => '靈',
  '領' => '領',
  '例' => '例',
  '禮' => '禮',
  '醴' => '醴',
  '隸' => '隸',
  '惡' => '惡',
  '了' => '了',
  '僚' => '僚',
  '寮' => '寮',
  '尿' => '尿',
  '料' => '料',
  '樂' => '樂',
  '燎' => '燎',
  '療' => '療',
  '蓼' => '蓼',
  '遼' => '遼',
  '龍' => '龍',
  '暈' => '暈',
  '阮' => '阮',
  '劉' => '劉',
  '杻' => '杻',
  '柳' => '柳',
  '流' => '流',
  '溜' => '溜',
  '琉' => '琉',
  '留' => '留',
  '硫' => '硫',
  '紐' => '紐',
  '類' => '類',
  '六' => '六',
  '戮' => '戮',
  '陸' => '陸',
  '倫' => '倫',
  '崙' => '崙',
  '淪' => '淪',
  '輪' => '輪',
  '律' => '律',
  '慄' => '慄',
  '栗' => '栗',
  '率' => '率',
  '隆' => '隆',
  '利' => '利',
  '吏' => '吏',
  '履' => '履',
  '易' => '易',
  '李' => '李',
  '梨' => '梨',
  '泥' => '泥',
  '理' => '理',
  '痢' => '痢',
  '罹' => '罹',
  '裏' => '裏',
  '裡' => '裡',
  '里' => '里',
  '離' => '離',
  '匿' => '匿',
  '溺' => '溺',
  '吝' => '吝',
  '燐' => '燐',
  '璘' => '璘',
  '藺' => '藺',
  '隣' => '隣',
  '鱗' => '鱗',
  '麟' => '麟',
  '林' => '林',
  '淋' => '淋',
  '臨' => '臨',
  '立' => '立',
  '笠' => '笠',
  '粒' => '粒',
  '狀' => '狀',
  '炙' => '炙',
  '識' => '識',
  '什' => '什',
  '茶' => '茶',
  '刺' => '刺',
  '切' => '切',
  '度' => '度',
  '拓' => '拓',
  '糖' => '糖',
  '宅' => '宅',
  '洞' => '洞',
  '暴' => '暴',
  '輻' => '輻',
  '行' => '行',
  '降' => '降',
  '見' => '見',
  '廓' => '廓',
  '兀' => '兀',
  '嗀' => '嗀',
  '塚' => '塚',
  '晴' => '晴',
  '凞' => '凞',
  '猪' => '猪',
  '益' => '益',
  '礼' => '礼',
  '神' => '神',
  '祥' => '祥',
  '福' => '福',
  '靖' => '靖',
  '精' => '精',
  '羽' => '羽',
  '蘒' => '蘒',
  '諸' => '諸',
  '逸' => '逸',
  '都' => '都',
  '飯' => '飯',
  '飼' => '飼',
  '館' => '館',
  '鶴' => '鶴',
  '郞' => '郞',
  '隷' => '隷',
  '侮' => '侮',
  '僧' => '僧',
  '免' => '免',
  '勉' => '勉',
  '勤' => '勤',
  '卑' => '卑',
  '喝' => '喝',
  '嘆' => '嘆',
  '器' => '器',
  '塀' => '塀',
  '墨' => '墨',
  '層' => '層',
  '屮' => '屮',
  '悔' => '悔',
  '慨' => '慨',
  '憎' => '憎',
  '懲' => '懲',
  '敏' => '敏',
  '既' => '既',
  '暑' => '暑',
  '梅' => '梅',
  '海' => '海',
  '渚' => '渚',
  '漢' => '漢',
  '煮' => '煮',
  '爫' => '爫',
  '琢' => '琢',
  '碑' => '碑',
  '社' => '社',
  '祉' => '祉',
  '祈' => '祈',
  '祐' => '祐',
  '祖' => '祖',
  '祝' => '祝',
  '禍' => '禍',
  '禎' => '禎',
  '穀' => '穀',
  '突' => '突',
  '節' => '節',
  '練' => '練',
  '縉' => '縉',
  '繁' => '繁',
  '署' => '署',
  '者' => '者',
  '臭' => '臭',
  '艹' => '艹',
  '艹' => '艹',
  '著' => '著',
  '褐' => '褐',
  '視' => '視',
  '謁' => '謁',
  '謹' => '謹',
  '賓' => '賓',
  '贈' => '贈',
  '辶' => '辶',
  '逸' => '逸',
  '難' => '難',
  '響' => '響',
  '頻' => '頻',
  '恵' => '恵',
  '𤋮' => '𤋮',
  '舘' => '舘',
  '並' => '並',
  '况' => '况',
  '全' => '全',
  '侀' => '侀',
  '充' => '充',
  '冀' => '冀',
  '勇' => '勇',
  '勺' => '勺',
  '喝' => '喝',
  '啕' => '啕',
  '喙' => '喙',
  '嗢' => '嗢',
  '塚' => '塚',
  '墳' => '墳',
  '奄' => '奄',
  '奔' => '奔',
  '婢' => '婢',
  '嬨' => '嬨',
  '廒' => '廒',
  '廙' => '廙',
  '彩' => '彩',
  '徭' => '徭',
  '惘' => '惘',
  '慎' => '慎',
  '愈' => '愈',
  '憎' => '憎',
  '慠' => '慠',
  '懲' => '懲',
  '戴' => '戴',
  '揄' => '揄',
  '搜' => '搜',
  '摒' => '摒',
  '敖' => '敖',
  '晴' => '晴',
  '朗' => '朗',
  '望' => '望',
  '杖' => '杖',
  '歹' => '歹',
  '殺' => '殺',
  '流' => '流',
  '滛' => '滛',
  '滋' => '滋',
  '漢' => '漢',
  '瀞' => '瀞',
  '煮' => '煮',
  '瞧' => '瞧',
  '爵' => '爵',
  '犯' => '犯',
  '猪' => '猪',
  '瑱' => '瑱',
  '甆' => '甆',
  '画' => '画',
  '瘝' => '瘝',
  '瘟' => '瘟',
  '益' => '益',
  '盛' => '盛',
  '直' => '直',
  '睊' => '睊',
  '着' => '着',
  '磌' => '磌',
  '窱' => '窱',
  '節' => '節',
  '类' => '类',
  '絛' => '絛',
  '練' => '練',
  '缾' => '缾',
  '者' => '者',
  '荒' => '荒',
  '華' => '華',
  '蝹' => '蝹',
  '襁' => '襁',
  '覆' => '覆',
  '視' => '視',
  '調' => '調',
  '諸' => '諸',
  '請' => '請',
  '謁' => '謁',
  '諾' => '諾',
  '諭' => '諭',
  '謹' => '謹',
  '變' => '變',
  '贈' => '贈',
  '輸' => '輸',
  '遲' => '遲',
  '醙' => '醙',
  '鉶' => '鉶',
  '陼' => '陼',
  '難' => '難',
  '靖' => '靖',
  '韛' => '韛',
  '響' => '響',
  '頋' => '頋',
  '頻' => '頻',
  '鬒' => '鬒',
  '龜' => '龜',
  '𢡊' => '𢡊',
  '𢡄' => '𢡄',
  '𣏕' => '𣏕',
  '㮝' => '㮝',
  '䀘' => '䀘',
  '䀹' => '䀹',
  '𥉉' => '𥉉',
  '𥳐' => '𥳐',
  '𧻓' => '𧻓',
  '齃' => '齃',
  '龎' => '龎',
  'יִ' => 'יִ',
  'ײַ' => 'ײַ',
  'שׁ' => 'שׁ',
  'שׂ' => 'שׂ',
  'שּׁ' => 'שּׁ',
  'שּׂ' => 'שּׂ',
  'אַ' => 'אַ',
  'אָ' => 'אָ',
  'אּ' => 'אּ',
  'בּ' => 'בּ',
  'גּ' => 'גּ',
  'דּ' => 'דּ',
  'הּ' => 'הּ',
  'וּ' => 'וּ',
  'זּ' => 'זּ',
  'טּ' => 'טּ',
  'יּ' => 'יּ',
  'ךּ' => 'ךּ',
  'כּ' => 'כּ',
  'לּ' => 'לּ',
  'מּ' => 'מּ',
  'נּ' => 'נּ',
  'סּ' => 'סּ',
  'ףּ' => 'ףּ',
  'פּ' => 'פּ',
  'צּ' => 'צּ',
  'קּ' => 'קּ',
  'רּ' => 'רּ',
  'שּ' => 'שּ',
  'תּ' => 'תּ',
  'וֹ' => 'וֹ',
  'בֿ' => 'בֿ',
  'כֿ' => 'כֿ',
  'פֿ' => 'פֿ',
  '𑂚' => '𑂚',
  '𑂜' => '𑂜',
  '𑂫' => '𑂫',
  '𑄮' => '𑄮',
  '𑄯' => '𑄯',
  '𑍋' => '𑍋',
  '𑍌' => '𑍌',
  '𑒻' => '𑒻',
  '𑒼' => '𑒼',
  '𑒾' => '𑒾',
  '𑖺' => '𑖺',
  '𑖻' => '𑖻',
  '𑤸' => '𑤸',
  '𝅗𝅥' => '𝅗𝅥',
  '𝅘𝅥' => '𝅘𝅥',
  '𝅘𝅥𝅮' => '𝅘𝅥𝅮',
  '𝅘𝅥𝅯' => '𝅘𝅥𝅯',
  '𝅘𝅥𝅰' => '𝅘𝅥𝅰',
  '𝅘𝅥𝅱' => '𝅘𝅥𝅱',
  '𝅘𝅥𝅲' => '𝅘𝅥𝅲',
  '𝆹𝅥' => '𝆹𝅥',
  '𝆺𝅥' => '𝆺𝅥',
  '𝆹𝅥𝅮' => '𝆹𝅥𝅮',
  '𝆺𝅥𝅮' => '𝆺𝅥𝅮',
  '𝆹𝅥𝅯' => '𝆹𝅥𝅯',
  '𝆺𝅥𝅯' => '𝆺𝅥𝅯',
  '丽' => '丽',
  '丸' => '丸',
  '乁' => '乁',
  '𠄢' => '𠄢',
  '你' => '你',
  '侮' => '侮',
  '侻' => '侻',
  '倂' => '倂',
  '偺' => '偺',
  '備' => '備',
  '僧' => '僧',
  '像' => '像',
  '㒞' => '㒞',
  '𠘺' => '𠘺',
  '免' => '免',
  '兔' => '兔',
  '兤' => '兤',
  '具' => '具',
  '𠔜' => '𠔜',
  '㒹' => '㒹',
  '內' => '內',
  '再' => '再',
  '𠕋' => '𠕋',
  '冗' => '冗',
  '冤' => '冤',
  '仌' => '仌',
  '冬' => '冬',
  '况' => '况',
  '𩇟' => '𩇟',
  '凵' => '凵',
  '刃' => '刃',
  '㓟' => '㓟',
  '刻' => '刻',
  '剆' => '剆',
  '割' => '割',
  '剷' => '剷',
  '㔕' => '㔕',
  '勇' => '勇',
  '勉' => '勉',
  '勤' => '勤',
  '勺' => '勺',
  '包' => '包',
  '匆' => '匆',
  '北' => '北',
  '卉' => '卉',
  '卑' => '卑',
  '博' => '博',
  '即' => '即',
  '卽' => '卽',
  '卿' => '卿',
  '卿' => '卿',
  '卿' => '卿',
  '𠨬' => '𠨬',
  '灰' => '灰',
  '及' => '及',
  '叟' => '叟',
  '𠭣' => '𠭣',
  '叫' => '叫',
  '叱' => '叱',
  '吆' => '吆',
  '咞' => '咞',
  '吸' => '吸',
  '呈' => '呈',
  '周' => '周',
  '咢' => '咢',
  '哶' => '哶',
  '唐' => '唐',
  '啓' => '啓',
  '啣' => '啣',
  '善' => '善',
  '善' => '善',
  '喙' => '喙',
  '喫' => '喫',
  '喳' => '喳',
  '嗂' => '嗂',
  '圖' => '圖',
  '嘆' => '嘆',
  '圗' => '圗',
  '噑' => '噑',
  '噴' => '噴',
  '切' => '切',
  '壮' => '壮',
  '城' => '城',
  '埴' => '埴',
  '堍' => '堍',
  '型' => '型',
  '堲' => '堲',
  '報' => '報',
  '墬' => '墬',
  '𡓤' => '𡓤',
  '売' => '売',
  '壷' => '壷',
  '夆' => '夆',
  '多' => '多',
  '夢' => '夢',
  '奢' => '奢',
  '𡚨' => '𡚨',
  '𡛪' => '𡛪',
  '姬' => '姬',
  '娛' => '娛',
  '娧' => '娧',
  '姘' => '姘',
  '婦' => '婦',
  '㛮' => '㛮',
  '㛼' => '㛼',
  '嬈' => '嬈',
  '嬾' => '嬾',
  '嬾' => '嬾',
  '𡧈' => '𡧈',
  '寃' => '寃',
  '寘' => '寘',
  '寧' => '寧',
  '寳' => '寳',
  '𡬘' => '𡬘',
  '寿' => '寿',
  '将' => '将',
  '当' => '当',
  '尢' => '尢',
  '㞁' => '㞁',
  '屠' => '屠',
  '屮' => '屮',
  '峀' => '峀',
  '岍' => '岍',
  '𡷤' => '𡷤',
  '嵃' => '嵃',
  '𡷦' => '𡷦',
  '嵮' => '嵮',
  '嵫' => '嵫',
  '嵼' => '嵼',
  '巡' => '巡',
  '巢' => '巢',
  '㠯' => '㠯',
  '巽' => '巽',
  '帨' => '帨',
  '帽' => '帽',
  '幩' => '幩',
  '㡢' => '㡢',
  '𢆃' => '𢆃',
  '㡼' => '㡼',
  '庰' => '庰',
  '庳' => '庳',
  '庶' => '庶',
  '廊' => '廊',
  '𪎒' => '𪎒',
  '廾' => '廾',
  '𢌱' => '𢌱',
  '𢌱' => '𢌱',
  '舁' => '舁',
  '弢' => '弢',
  '弢' => '弢',
  '㣇' => '㣇',
  '𣊸' => '𣊸',
  '𦇚' => '𦇚',
  '形' => '形',
  '彫' => '彫',
  '㣣' => '㣣',
  '徚' => '徚',
  '忍' => '忍',
  '志' => '志',
  '忹' => '忹',
  '悁' => '悁',
  '㤺' => '㤺',
  '㤜' => '㤜',
  '悔' => '悔',
  '𢛔' => '𢛔',
  '惇' => '惇',
  '慈' => '慈',
  '慌' => '慌',
  '慎' => '慎',
  '慌' => '慌',
  '慺' => '慺',
  '憎' => '憎',
  '憲' => '憲',
  '憤' => '憤',
  '憯' => '憯',
  '懞' => '懞',
  '懲' => '懲',
  '懶' => '懶',
  '成' => '成',
  '戛' => '戛',
  '扝' => '扝',
  '抱' => '抱',
  '拔' => '拔',
  '捐' => '捐',
  '𢬌' => '𢬌',
  '挽' => '挽',
  '拼' => '拼',
  '捨' => '捨',
  '掃' => '掃',
  '揤' => '揤',
  '𢯱' => '𢯱',
  '搢' => '搢',
  '揅' => '揅',
  '掩' => '掩',
  '㨮' => '㨮',
  '摩' => '摩',
  '摾' => '摾',
  '撝' => '撝',
  '摷' => '摷',
  '㩬' => '㩬',
  '敏' => '敏',
  '敬' => '敬',
  '𣀊' => '𣀊',
  '旣' => '旣',
  '書' => '書',
  '晉' => '晉',
  '㬙' => '㬙',
  '暑' => '暑',
  '㬈' => '㬈',
  '㫤' => '㫤',
  '冒' => '冒',
  '冕' => '冕',
  '最' => '最',
  '暜' => '暜',
  '肭' => '肭',
  '䏙' => '䏙',
  '朗' => '朗',
  '望' => '望',
  '朡' => '朡',
  '杞' => '杞',
  '杓' => '杓',
  '𣏃' => '𣏃',
  '㭉' => '㭉',
  '柺' => '柺',
  '枅' => '枅',
  '桒' => '桒',
  '梅' => '梅',
  '𣑭' => '𣑭',
  '梎' => '梎',
  '栟' => '栟',
  '椔' => '椔',
  '㮝' => '㮝',
  '楂' => '楂',
  '榣' => '榣',
  '槪' => '槪',
  '檨' => '檨',
  '𣚣' => '𣚣',
  '櫛' => '櫛',
  '㰘' => '㰘',
  '次' => '次',
  '𣢧' => '𣢧',
  '歔' => '歔',
  '㱎' => '㱎',
  '歲' => '歲',
  '殟' => '殟',
  '殺' => '殺',
  '殻' => '殻',
  '𣪍' => '𣪍',
  '𡴋' => '𡴋',
  '𣫺' => '𣫺',
  '汎' => '汎',
  '𣲼' => '𣲼',
  '沿' => '沿',
  '泍' => '泍',
  '汧' => '汧',
  '洖' => '洖',
  '派' => '派',
  '海' => '海',
  '流' => '流',
  '浩' => '浩',
  '浸' => '浸',
  '涅' => '涅',
  '𣴞' => '𣴞',
  '洴' => '洴',
  '港' => '港',
  '湮' => '湮',
  '㴳' => '㴳',
  '滋' => '滋',
  '滇' => '滇',
  '𣻑' => '𣻑',
  '淹' => '淹',
  '潮' => '潮',
  '𣽞' => '𣽞',
  '𣾎' => '𣾎',
  '濆' => '濆',
  '瀹' => '瀹',
  '瀞' => '瀞',
  '瀛' => '瀛',
  '㶖' => '㶖',
  '灊' => '灊',
  '災' => '災',
  '灷' => '灷',
  '炭' => '炭',
  '𠔥' => '𠔥',
  '煅' => '煅',
  '𤉣' => '𤉣',
  '熜' => '熜',
  '𤎫' => '𤎫',
  '爨' => '爨',
  '爵' => '爵',
  '牐' => '牐',
  '𤘈' => '𤘈',
  '犀' => '犀',
  '犕' => '犕',
  '𤜵' => '𤜵',
  '𤠔' => '𤠔',
  '獺' => '獺',
  '王' => '王',
  '㺬' => '㺬',
  '玥' => '玥',
  '㺸' => '㺸',
  '㺸' => '㺸',
  '瑇' => '瑇',
  '瑜' => '瑜',
  '瑱' => '瑱',
  '璅' => '璅',
  '瓊' => '瓊',
  '㼛' => '㼛',
  '甤' => '甤',
  '𤰶' => '𤰶',
  '甾' => '甾',
  '𤲒' => '𤲒',
  '異' => '異',
  '𢆟' => '𢆟',
  '瘐' => '瘐',
  '𤾡' => '𤾡',
  '𤾸' => '𤾸',
  '𥁄' => '𥁄',
  '㿼' => '㿼',
  '䀈' => '䀈',
  '直' => '直',
  '𥃳' => '𥃳',
  '𥃲' => '𥃲',
  '𥄙' => '𥄙',
  '𥄳' => '𥄳',
  '眞' => '眞',
  '真' => '真',
  '真' => '真',
  '睊' => '睊',
  '䀹' => '䀹',
  '瞋' => '瞋',
  '䁆' => '䁆',
  '䂖' => '䂖',
  '𥐝' => '𥐝',
  '硎' => '硎',
  '碌' => '碌',
  '磌' => '磌',
  '䃣' => '䃣',
  '𥘦' => '𥘦',
  '祖' => '祖',
  '𥚚' => '𥚚',
  '𥛅' => '𥛅',
  '福' => '福',
  '秫' => '秫',
  '䄯' => '䄯',
  '穀' => '穀',
  '穊' => '穊',
  '穏' => '穏',
  '𥥼' => '𥥼',
  '𥪧' => '𥪧',
  '𥪧' => '𥪧',
  '竮' => '竮',
  '䈂' => '䈂',
  '𥮫' => '𥮫',
  '篆' => '篆',
  '築' => '築',
  '䈧' => '䈧',
  '𥲀' => '𥲀',
  '糒' => '糒',
  '䊠' => '䊠',
  '糨' => '糨',
  '糣' => '糣',
  '紀' => '紀',
  '𥾆' => '𥾆',
  '絣' => '絣',
  '䌁' => '䌁',
  '緇' => '緇',
  '縂' => '縂',
  '繅' => '繅',
  '䌴' => '䌴',
  '𦈨' => '𦈨',
  '𦉇' => '𦉇',
  '䍙' => '䍙',
  '𦋙' => '𦋙',
  '罺' => '罺',
  '𦌾' => '𦌾',
  '羕' => '羕',
  '翺' => '翺',
  '者' => '者',
  '𦓚' => '𦓚',
  '𦔣' => '𦔣',
  '聠' => '聠',
  '𦖨' => '𦖨',
  '聰' => '聰',
  '𣍟' => '𣍟',
  '䏕' => '䏕',
  '育' => '育',
  '脃' => '脃',
  '䐋' => '䐋',
  '脾' => '脾',
  '媵' => '媵',
  '𦞧' => '𦞧',
  '𦞵' => '𦞵',
  '𣎓' => '𣎓',
  '𣎜' => '𣎜',
  '舁' => '舁',
  '舄' => '舄',
  '辞' => '辞',
  '䑫' => '䑫',
  '芑' => '芑',
  '芋' => '芋',
  '芝' => '芝',
  '劳' => '劳',
  '花' => '花',
  '芳' => '芳',
  '芽' => '芽',
  '苦' => '苦',
  '𦬼' => '𦬼',
  '若' => '若',
  '茝' => '茝',
  '荣' => '荣',
  '莭' => '莭',
  '茣' => '茣',
  '莽' => '莽',
  '菧' => '菧',
  '著' => '著',
  '荓' => '荓',
  '菊' => '菊',
  '菌' => '菌',
  '菜' => '菜',
  '𦰶' => '𦰶',
  '𦵫' => '𦵫',
  '𦳕' => '𦳕',
  '䔫' => '䔫',
  '蓱' => '蓱',
  '蓳' => '蓳',
  '蔖' => '蔖',
  '𧏊' => '𧏊',
  '蕤' => '蕤',
  '𦼬' => '𦼬',
  '䕝' => '䕝',
  '䕡' => '䕡',
  '𦾱' => '𦾱',
  '𧃒' => '𧃒',
  '䕫' => '䕫',
  '虐' => '虐',
  '虜' => '虜',
  '虧' => '虧',
  '虩' => '虩',
  '蚩' => '蚩',
  '蚈' => '蚈',
  '蜎' => '蜎',
  '蛢' => '蛢',
  '蝹' => '蝹',
  '蜨' => '蜨',
  '蝫' => '蝫',
  '螆' => '螆',
  '䗗' => '䗗',
  '蟡' => '蟡',
  '蠁' => '蠁',
  '䗹' => '䗹',
  '衠' => '衠',
  '衣' => '衣',
  '𧙧' => '𧙧',
  '裗' => '裗',
  '裞' => '裞',
  '䘵' => '䘵',
  '裺' => '裺',
  '㒻' => '㒻',
  '𧢮' => '𧢮',
  '𧥦' => '𧥦',
  '䚾' => '䚾',
  '䛇' => '䛇',
  '誠' => '誠',
  '諭' => '諭',
  '變' => '變',
  '豕' => '豕',
  '𧲨' => '𧲨',
  '貫' => '貫',
  '賁' => '賁',
  '贛' => '贛',
  '起' => '起',
  '𧼯' => '𧼯',
  '𠠄' => '𠠄',
  '跋' => '跋',
  '趼' => '趼',
  '跰' => '跰',
  '𠣞' => '𠣞',
  '軔' => '軔',
  '輸' => '輸',
  '𨗒' => '𨗒',
  '𨗭' => '𨗭',
  '邔' => '邔',
  '郱' => '郱',
  '鄑' => '鄑',
  '𨜮' => '𨜮',
  '鄛' => '鄛',
  '鈸' => '鈸',
  '鋗' => '鋗',
  '鋘' => '鋘',
  '鉼' => '鉼',
  '鏹' => '鏹',
  '鐕' => '鐕',
  '𨯺' => '𨯺',
  '開' => '開',
  '䦕' => '䦕',
  '閷' => '閷',
  '𨵷' => '𨵷',
  '䧦' => '䧦',
  '雃' => '雃',
  '嶲' => '嶲',
  '霣' => '霣',
  '𩅅' => '𩅅',
  '𩈚' => '𩈚',
  '䩮' => '䩮',
  '䩶' => '䩶',
  '韠' => '韠',
  '𩐊' => '𩐊',
  '䪲' => '䪲',
  '𩒖' => '𩒖',
  '頋' => '頋',
  '頋' => '頋',
  '頩' => '頩',
  '𩖶' => '𩖶',
  '飢' => '飢',
  '䬳' => '䬳',
  '餩' => '餩',
  '馧' => '馧',
  '駂' => '駂',
  '駾' => '駾',
  '䯎' => '䯎',
  '𩬰' => '𩬰',
  '鬒' => '鬒',
  '鱀' => '鱀',
  '鳽' => '鳽',
  '䳎' => '䳎',
  '䳭' => '䳭',
  '鵧' => '鵧',
  '𪃎' => '𪃎',
  '䳸' => '䳸',
  '𪄅' => '𪄅',
  '𪈎' => '𪈎',
  '𪊑' => '𪊑',
  '麻' => '麻',
  '䵖' => '䵖',
  '黹' => '黹',
  '黾' => '黾',
  '鼅' => '鼅',
  '鼏' => '鼏',
  '鼖' => '鼖',
  '鼻' => '鼻',
  '𪘀' => '𪘀',
);
<?php

return array (
  ' ' => ' ',
  '¨' => ' ̈',
  'ª' => 'a',
  '¯' => ' ̄',
  '²' => '2',
  '³' => '3',
  '´' => ' ́',
  'µ' => 'μ',
  '¸' => ' ̧',
  '¹' => '1',
  'º' => 'o',
  '¼' => '1⁄4',
  '½' => '1⁄2',
  '¾' => '3⁄4',
  'Ĳ' => 'IJ',
  'ĳ' => 'ij',
  'Ŀ' => 'L·',
  'ŀ' => 'l·',
  'ŉ' => 'ʼn',
  'ſ' => 's',
  'Ǆ' => 'DŽ',
  'ǅ' => 'Dž',
  'ǆ' => 'dž',
  'Ǉ' => 'LJ',
  'ǈ' => 'Lj',
  'ǉ' => 'lj',
  'Ǌ' => 'NJ',
  'ǋ' => 'Nj',
  'ǌ' => 'nj',
  'Ǳ' => '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ʾ',
  'ẛ' => 'ṡ',
  '᾽' => ' ̓',
  '᾿' => ' ̓',
  '῀' => ' ͂',
  '῁' => ' ̈͂',
  '῍' => ' ̓̀',
  '῎' => ' ̓́',
  '῏' => ' ̓͂',
  '῝' => ' ̔̀',
  '῞' => ' ̔́',
  '῟' => ' ̔͂',
  '῭' => ' ̈̀',
  '΅' => ' ̈́',
  '´' => ' ́',
  '῾' => ' ̔',
  ' ' => ' ',
  ' ' => ' ',
  ' ' => ' ',
  ' ' => ' ',
  ' ' => ' ',
  ' ' => ' ',
  ' ' => ' ',
  ' ' => ' ',
  ' ' => ' ',
  ' ' => ' ',
  ' ' => ' ',
  '‑' => '‐',
  '‗' => ' ̳',
  '․' => '.',
  '‥' => '..',
  '…' => '...',
  ' ' => ' ',
  '″' => '′′',
  '‴' => '′′′',
  '‶' => '‵‵',
  '‷' => '‵‵‵',
  '‼' => '!!',
  '‾' => ' ̅',
  '⁇' => '??',
  '⁈' => '?!',
  '⁉' => '!?',
  '⁗' => '′′′′',
  ' ' => ' ',
  '⁰' => '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',
  'ﬁ' => 'fi',
  'ﬂ' => 'fl',
  'ﬃ' => 'ffi',
  'ﬄ' => 'ffl',
  'ﬅ' => 'st',
  'ﬆ' => 'st',
  'ﬓ' => 'մն',
  'ﬔ' => 'մե',
  'ﬕ' => 'մի',
  'ﬖ' => 'վն',
  'ﬗ' => 'մխ',
  'ﬠ' => 'ע',
  'ﬡ' => 'א',
  'ﬢ' => 'ד',
  'ﬣ' => 'ה',
  'ﬤ' => 'כ',
  'ﬥ' => 'ל',
  'ﬦ' => 'ם',
  'ﬧ' => 'ר',
  'ﬨ' => 'ת',
  '﬩' => '+',
  'ﭏ' => 'אל',
  'ﭐ' => 'ٱ',
  'ﭑ' => 'ٱ',
  'ﭒ' => 'ٻ',
  'ﭓ' => 'ٻ',
  'ﭔ' => 'ٻ',
  'ﭕ' => 'ٻ',
  'ﭖ' => 'پ',
  'ﭗ' => 'پ',
  'ﭘ' => 'پ',
  'ﭙ' => 'پ',
  'ﭚ' => 'ڀ',
  'ﭛ' => 'ڀ',
  'ﭜ' => 'ڀ',
  'ﭝ' => 'ڀ',
  'ﭞ' => 'ٺ',
  'ﭟ' => 'ٺ',
  'ﭠ' => 'ٺ',
  'ﭡ' => 'ٺ',
  'ﭢ' => 'ٿ',
  'ﭣ' => 'ٿ',
  'ﭤ' => 'ٿ',
  'ﭥ' => 'ٿ',
  'ﭦ' => 'ٹ',
  'ﭧ' => 'ٹ',
  'ﭨ' => 'ٹ',
  'ﭩ' => 'ٹ',
  'ﭪ' => 'ڤ',
  'ﭫ' => 'ڤ',
  'ﭬ' => 'ڤ',
  'ﭭ' => 'ڤ',
  'ﭮ' => 'ڦ',
  'ﭯ' => 'ڦ',
  'ﭰ' => 'ڦ',
  'ﭱ' => 'ڦ',
  'ﭲ' => 'ڄ',
  'ﭳ' => 'ڄ',
  'ﭴ' => 'ڄ',
  'ﭵ' => 'ڄ',
  'ﭶ' => 'ڃ',
  'ﭷ' => 'ڃ',
  'ﭸ' => 'ڃ',
  'ﭹ' => 'ڃ',
  'ﭺ' => 'چ',
  'ﭻ' => 'چ',
  'ﭼ' => 'چ',
  'ﭽ' => 'چ',
  'ﭾ' => 'ڇ',
  'ﭿ' => 'ڇ',
  'ﮀ' => 'ڇ',
  'ﮁ' => 'ڇ',
  'ﮂ' => 'ڍ',
  'ﮃ' => 'ڍ',
  'ﮄ' => 'ڌ',
  'ﮅ' => 'ڌ',
  'ﮆ' => 'ڎ',
  'ﮇ' => 'ڎ',
  'ﮈ' => 'ڈ',
  'ﮉ' => 'ڈ',
  'ﮊ' => 'ژ',
  'ﮋ' => 'ژ',
  'ﮌ' => 'ڑ',
  'ﮍ' => 'ڑ',
  'ﮎ' => 'ک',
  'ﮏ' => 'ک',
  'ﮐ' => 'ک',
  'ﮑ' => 'ک',
  'ﮒ' => 'گ',
  'ﮓ' => 'گ',
  'ﮔ' => 'گ',
  'ﮕ' => 'گ',
  'ﮖ' => 'ڳ',
  'ﮗ' => 'ڳ',
  'ﮘ' => 'ڳ',
  'ﮙ' => 'ڳ',
  'ﮚ' => 'ڱ',
  'ﮛ' => 'ڱ',
  'ﮜ' => 'ڱ',
  'ﮝ' => 'ڱ',
  'ﮞ' => 'ں',
  'ﮟ' => 'ں',
  'ﮠ' => 'ڻ',
  'ﮡ' => 'ڻ',
  'ﮢ' => 'ڻ',
  'ﮣ' => 'ڻ',
  'ﮤ' => 'ۀ',
  'ﮥ' => 'ۀ',
  'ﮦ' => 'ہ',
  'ﮧ' => 'ہ',
  'ﮨ' => 'ہ',
  'ﮩ' => 'ہ',
  'ﮪ' => 'ھ',
  'ﮫ' => 'ھ',
  'ﮬ' => 'ھ',
  'ﮭ' => 'ھ',
  'ﮮ' => 'ے',
  'ﮯ' => 'ے',
  'ﮰ' => 'ۓ',
  'ﮱ' => 'ۓ',
  'ﯓ' => 'ڭ',
  'ﯔ' => 'ڭ',
  'ﯕ' => 'ڭ',
  'ﯖ' => 'ڭ',
  'ﯗ' => 'ۇ',
  'ﯘ' => 'ۇ',
  'ﯙ' => 'ۆ',
  'ﯚ' => 'ۆ',
  'ﯛ' => 'ۈ',
  'ﯜ' => 'ۈ',
  'ﯝ' => 'ۇٴ',
  'ﯞ' => 'ۋ',
  'ﯟ' => 'ۋ',
  'ﯠ' => 'ۅ',
  'ﯡ' => 'ۅ',
  'ﯢ' => 'ۉ',
  'ﯣ' => 'ۉ',
  'ﯤ' => 'ې',
  'ﯥ' => 'ې',
  'ﯦ' => 'ې',
  'ﯧ' => 'ې',
  'ﯨ' => 'ى',
  'ﯩ' => 'ى',
  'ﯪ' => 'ئا',
  'ﯫ' => 'ئا',
  'ﯬ' => 'ئە',
  'ﯭ' => 'ئە',
  'ﯮ' => 'ئو',
  'ﯯ' => 'ئو',
  'ﯰ' => 'ئۇ',
  'ﯱ' => 'ئۇ',
  'ﯲ' => 'ئۆ',
  'ﯳ' => 'ئۆ',
  'ﯴ' => 'ئۈ',
  'ﯵ' => 'ئۈ',
  'ﯶ' => 'ئې',
  'ﯷ' => 'ئې',
  'ﯸ' => 'ئې',
  'ﯹ' => 'ئى',
  'ﯺ' => 'ئى',
  'ﯻ' => 'ئى',
  'ﯼ' => 'ی',
  'ﯽ' => 'ی',
  'ﯾ' => 'ی',
  'ﯿ' => 'ی',
  'ﰀ' => 'ئج',
  'ﰁ' => 'ئح',
  'ﰂ' => 'ئم',
  'ﰃ' => 'ئى',
  'ﰄ' => 'ئي',
  'ﰅ' => 'بج',
  'ﰆ' => 'بح',
  'ﰇ' => 'بخ',
  'ﰈ' => 'بم',
  'ﰉ' => 'بى',
  'ﰊ' => 'بي',
  'ﰋ' => 'تج',
  'ﰌ' => 'تح',
  'ﰍ' => 'تخ',
  'ﰎ' => 'تم',
  'ﰏ' => 'تى',
  'ﰐ' => 'تي',
  'ﰑ' => 'ثج',
  'ﰒ' => 'ثم',
  'ﰓ' => 'ثى',
  'ﰔ' => 'ثي',
  'ﰕ' => 'جح',
  'ﰖ' => 'جم',
  'ﰗ' => 'حج',
  'ﰘ' => 'حم',
  'ﰙ' => 'خج',
  'ﰚ' => 'خح',
  'ﰛ' => 'خم',
  'ﰜ' => 'سج',
  'ﰝ' => 'سح',
  'ﰞ' => 'سخ',
  'ﰟ' => 'سم',
  'ﰠ' => 'صح',
  'ﰡ' => 'صم',
  'ﰢ' => 'ضج',
  'ﰣ' => 'ضح',
  'ﰤ' => 'ضخ',
  'ﰥ' => 'ضم',
  'ﰦ' => 'طح',
  'ﰧ' => 'طم',
  'ﰨ' => 'ظم',
  'ﰩ' => 'عج',
  'ﰪ' => 'عم',
  'ﰫ' => 'غج',
  'ﰬ' => 'غم',
  'ﰭ' => 'فج',
  'ﰮ' => 'فح',
  'ﰯ' => 'فخ',
  'ﰰ' => 'فم',
  'ﰱ' => 'فى',
  'ﰲ' => 'في',
  'ﰳ' => 'قح',
  'ﰴ' => 'قم',
  'ﰵ' => 'قى',
  'ﰶ' => 'قي',
  'ﰷ' => 'كا',
  'ﰸ' => 'كج',
  'ﰹ' => 'كح',
  'ﰺ' => 'كخ',
  'ﰻ' => 'كل',
  'ﰼ' => 'كم',
  'ﰽ' => 'كى',
  'ﰾ' => 'كي',
  'ﰿ' => 'لج',
  'ﱀ' => 'لح',
  'ﱁ' => 'لخ',
  'ﱂ' => 'لم',
  'ﱃ' => 'لى',
  'ﱄ' => 'لي',
  'ﱅ' => 'مج',
  'ﱆ' => 'مح',
  'ﱇ' => 'مخ',
  'ﱈ' => 'مم',
  'ﱉ' => 'مى',
  'ﱊ' => 'مي',
  'ﱋ' => 'نج',
  'ﱌ' => 'نح',
  'ﱍ' => 'نخ',
  'ﱎ' => 'نم',
  'ﱏ' => 'نى',
  'ﱐ' => 'ني',
  'ﱑ' => 'هج',
  'ﱒ' => 'هم',
  'ﱓ' => 'هى',
  'ﱔ' => 'هي',
  'ﱕ' => 'يج',
  'ﱖ' => 'يح',
  'ﱗ' => 'يخ',
  'ﱘ' => 'يم',
  'ﱙ' => 'يى',
  'ﱚ' => 'يي',
  'ﱛ' => 'ذٰ',
  'ﱜ' => 'رٰ',
  'ﱝ' => 'ىٰ',
  'ﱞ' => ' ٌّ',
  'ﱟ' => ' ٍّ',
  'ﱠ' => ' َّ',
  'ﱡ' => ' ُّ',
  'ﱢ' => ' ِّ',
  'ﱣ' => ' ّٰ',
  'ﱤ' => 'ئر',
  'ﱥ' => 'ئز',
  'ﱦ' => 'ئم',
  'ﱧ' => 'ئن',
  'ﱨ' => 'ئى',
  'ﱩ' => 'ئي',
  'ﱪ' => 'بر',
  'ﱫ' => 'بز',
  'ﱬ' => 'بم',
  'ﱭ' => 'بن',
  'ﱮ' => 'بى',
  'ﱯ' => 'بي',
  'ﱰ' => 'تر',
  'ﱱ' => 'تز',
  'ﱲ' => 'تم',
  'ﱳ' => 'تن',
  'ﱴ' => 'تى',
  'ﱵ' => 'تي',
  'ﱶ' => 'ثر',
  'ﱷ' => 'ثز',
  'ﱸ' => 'ثم',
  'ﱹ' => 'ثن',
  'ﱺ' => 'ثى',
  'ﱻ' => 'ثي',
  'ﱼ' => 'فى',
  'ﱽ' => 'في',
  'ﱾ' => 'قى',
  'ﱿ' => 'قي',
  'ﲀ' => 'كا',
  'ﲁ' => 'كل',
  'ﲂ' => 'كم',
  'ﲃ' => 'كى',
  'ﲄ' => 'كي',
  'ﲅ' => 'لم',
  'ﲆ' => 'لى',
  'ﲇ' => 'لي',
  'ﲈ' => 'ما',
  'ﲉ' => 'مم',
  'ﲊ' => 'نر',
  'ﲋ' => 'نز',
  'ﲌ' => 'نم',
  'ﲍ' => 'نن',
  'ﲎ' => 'نى',
  'ﲏ' => 'ني',
  'ﲐ' => 'ىٰ',
  'ﲑ' => 'ير',
  'ﲒ' => 'يز',
  'ﲓ' => 'يم',
  'ﲔ' => 'ين',
  'ﲕ' => 'يى',
  'ﲖ' => 'يي',
  'ﲗ' => 'ئج',
  'ﲘ' => 'ئح',
  'ﲙ' => 'ئخ',
  'ﲚ' => 'ئم',
  'ﲛ' => 'ئه',
  'ﲜ' => 'بج',
  'ﲝ' => 'بح',
  'ﲞ' => 'بخ',
  'ﲟ' => 'بم',
  'ﲠ' => 'به',
  'ﲡ' => 'تج',
  'ﲢ' => 'تح',
  'ﲣ' => 'تخ',
  'ﲤ' => 'تم',
  'ﲥ' => 'ته',
  'ﲦ' => 'ثم',
  'ﲧ' => 'جح',
  'ﲨ' => 'جم',
  'ﲩ' => 'حج',
  'ﲪ' => 'حم',
  'ﲫ' => 'خج',
  'ﲬ' => 'خم',
  'ﲭ' => 'سج',
  'ﲮ' => 'سح',
  'ﲯ' => 'سخ',
  'ﲰ' => 'سم',
  'ﲱ' => 'صح',
  'ﲲ' => 'صخ',
  'ﲳ' => 'صم',
  'ﲴ' => 'ضج',
  'ﲵ' => 'ضح',
  'ﲶ' => 'ضخ',
  'ﲷ' => 'ضم',
  'ﲸ' => 'طح',
  'ﲹ' => 'ظم',
  'ﲺ' => 'عج',
  'ﲻ' => 'عم',
  'ﲼ' => 'غج',
  'ﲽ' => 'غم',
  'ﲾ' => 'فج',
  'ﲿ' => 'فح',
  'ﳀ' => 'فخ',
  'ﳁ' => 'فم',
  'ﳂ' => 'قح',
  'ﳃ' => 'قم',
  'ﳄ' => 'كج',
  'ﳅ' => 'كح',
  'ﳆ' => 'كخ',
  'ﳇ' => 'كل',
  'ﳈ' => 'كم',
  'ﳉ' => 'لج',
  'ﳊ' => 'لح',
  'ﳋ' => 'لخ',
  'ﳌ' => 'لم',
  'ﳍ' => 'له',
  'ﳎ' => 'مج',
  'ﳏ' => 'مح',
  'ﳐ' => 'مخ',
  'ﳑ' => 'مم',
  'ﳒ' => 'نج',
  'ﳓ' => 'نح',
  'ﳔ' => 'نخ',
  'ﳕ' => 'نم',
  'ﳖ' => 'نه',
  'ﳗ' => 'هج',
  'ﳘ' => 'هم',
  'ﳙ' => 'هٰ',
  'ﳚ' => 'يج',
  'ﳛ' => 'يح',
  'ﳜ' => 'يخ',
  'ﳝ' => 'يم',
  'ﳞ' => 'يه',
  'ﳟ' => 'ئم',
  'ﳠ' => 'ئه',
  'ﳡ' => 'بم',
  'ﳢ' => 'به',
  'ﳣ' => 'تم',
  'ﳤ' => 'ته',
  'ﳥ' => 'ثم',
  'ﳦ' => 'ثه',
  'ﳧ' => 'سم',
  'ﳨ' => 'سه',
  'ﳩ' => 'شم',
  'ﳪ' => 'شه',
  'ﳫ' => 'كل',
  'ﳬ' => 'كم',
  'ﳭ' => 'لم',
  'ﳮ' => 'نم',
  'ﳯ' => 'نه',
  'ﳰ' => 'يم',
  'ﳱ' => 'يه',
  'ﳲ' => 'ـَّ',
  'ﳳ' => 'ـُّ',
  'ﳴ' => 'ـِّ',
  'ﳵ' => 'طى',
  'ﳶ' => 'طي',
  'ﳷ' => 'عى',
  'ﳸ' => 'عي',
  'ﳹ' => 'غى',
  'ﳺ' => 'غي',
  'ﳻ' => 'سى',
  'ﳼ' => 'سي',
  'ﳽ' => 'شى',
  'ﳾ' => 'شي',
  'ﳿ' => 'حى',
  'ﴀ' => 'حي',
  'ﴁ' => 'جى',
  'ﴂ' => 'جي',
  'ﴃ' => 'خى',
  'ﴄ' => 'خي',
  'ﴅ' => 'صى',
  'ﴆ' => 'صي',
  'ﴇ' => 'ضى',
  'ﴈ' => 'ضي',
  'ﴉ' => 'شج',
  'ﴊ' => 'شح',
  'ﴋ' => 'شخ',
  'ﴌ' => 'شم',
  'ﴍ' => 'شر',
  'ﴎ' => 'سر',
  'ﴏ' => 'صر',
  'ﴐ' => 'ضر',
  'ﴑ' => 'طى',
  'ﴒ' => 'طي',
  'ﴓ' => 'عى',
  'ﴔ' => 'عي',
  'ﴕ' => 'غى',
  'ﴖ' => 'غي',
  'ﴗ' => 'سى',
  'ﴘ' => 'سي',
  'ﴙ' => 'شى',
  'ﴚ' => 'شي',
  'ﴛ' => 'حى',
  'ﴜ' => 'حي',
  'ﴝ' => 'جى',
  'ﴞ' => 'جي',
  'ﴟ' => 'خى',
  'ﴠ' => 'خي',
  'ﴡ' => 'صى',
  'ﴢ' => 'صي',
  'ﴣ' => 'ضى',
  'ﴤ' => 'ضي',
  'ﴥ' => 'شج',
  'ﴦ' => 'شح',
  'ﴧ' => 'شخ',
  'ﴨ' => 'شم',
  'ﴩ' => 'شر',
  'ﴪ' => 'سر',
  'ﴫ' => 'صر',
  'ﴬ' => 'ضر',
  'ﴭ' => 'شج',
  'ﴮ' => 'شح',
  'ﴯ' => 'شخ',
  'ﴰ' => 'شم',
  'ﴱ' => 'سه',
  'ﴲ' => 'شه',
  'ﴳ' => 'طم',
  'ﴴ' => 'سج',
  'ﴵ' => 'سح',
  'ﴶ' => 'سخ',
  'ﴷ' => 'شج',
  'ﴸ' => 'شح',
  'ﴹ' => 'شخ',
  'ﴺ' => 'طم',
  'ﴻ' => 'ظم',
  'ﴼ' => 'اً',
  'ﴽ' => 'اً',
  'ﵐ' => 'تجم',
  'ﵑ' => 'تحج',
  'ﵒ' => 'تحج',
  'ﵓ' => 'تحم',
  'ﵔ' => 'تخم',
  'ﵕ' => 'تمج',
  'ﵖ' => 'تمح',
  'ﵗ' => 'تمخ',
  'ﵘ' => 'جمح',
  'ﵙ' => 'جمح',
  'ﵚ' => 'حمي',
  'ﵛ' => 'حمى',
  'ﵜ' => 'سحج',
  'ﵝ' => 'سجح',
  'ﵞ' => 'سجى',
  'ﵟ' => 'سمح',
  'ﵠ' => 'سمح',
  'ﵡ' => 'سمج',
  'ﵢ' => 'سمم',
  'ﵣ' => 'سمم',
  'ﵤ' => 'صحح',
  'ﵥ' => 'صحح',
  'ﵦ' => 'صمم',
  'ﵧ' => 'شحم',
  'ﵨ' => 'شحم',
  'ﵩ' => 'شجي',
  'ﵪ' => 'شمخ',
  'ﵫ' => 'شمخ',
  'ﵬ' => 'شمم',
  'ﵭ' => 'شمم',
  'ﵮ' => 'ضحى',
  'ﵯ' => 'ضخم',
  'ﵰ' => 'ضخم',
  'ﵱ' => 'طمح',
  'ﵲ' => 'طمح',
  'ﵳ' => 'طمم',
  'ﵴ' => 'طمي',
  'ﵵ' => 'عجم',
  'ﵶ' => 'عمم',
  'ﵷ' => 'عمم',
  'ﵸ' => 'عمى',
  'ﵹ' => 'غمم',
  'ﵺ' => 'غمي',
  'ﵻ' => 'غمى',
  'ﵼ' => 'فخم',
  'ﵽ' => 'فخم',
  'ﵾ' => 'قمح',
  'ﵿ' => 'قمم',
  'ﶀ' => 'لحم',
  'ﶁ' => 'لحي',
  'ﶂ' => 'لحى',
  'ﶃ' => 'لجج',
  'ﶄ' => 'لجج',
  'ﶅ' => 'لخم',
  'ﶆ' => 'لخم',
  'ﶇ' => 'لمح',
  'ﶈ' => 'لمح',
  'ﶉ' => 'محج',
  'ﶊ' => 'محم',
  'ﶋ' => 'محي',
  'ﶌ' => 'مجح',
  'ﶍ' => 'مجم',
  'ﶎ' => 'مخج',
  'ﶏ' => 'مخم',
  'ﶒ' => 'مجخ',
  'ﶓ' => 'همج',
  'ﶔ' => 'همم',
  'ﶕ' => 'نحم',
  'ﶖ' => 'نحى',
  'ﶗ' => 'نجم',
  'ﶘ' => 'نجم',
  'ﶙ' => 'نجى',
  'ﶚ' => 'نمي',
  'ﶛ' => 'نمى',
  'ﶜ' => 'يمم',
  'ﶝ' => 'يمم',
  'ﶞ' => 'بخي',
  'ﶟ' => 'تجي',
  'ﶠ' => 'تجى',
  'ﶡ' => 'تخي',
  'ﶢ' => 'تخى',
  'ﶣ' => 'تمي',
  'ﶤ' => 'تمى',
  'ﶥ' => 'جمي',
  'ﶦ' => 'جحى',
  'ﶧ' => 'جمى',
  'ﶨ' => 'سخى',
  'ﶩ' => 'صحي',
  'ﶪ' => 'شحي',
  'ﶫ' => 'ضحي',
  'ﶬ' => 'لجي',
  'ﶭ' => 'لمي',
  'ﶮ' => 'يحي',
  'ﶯ' => 'يجي',
  'ﶰ' => 'يمي',
  'ﶱ' => 'ممي',
  'ﶲ' => 'قمي',
  'ﶳ' => 'نحي',
  'ﶴ' => 'قمح',
  'ﶵ' => 'لحم',
  'ﶶ' => 'عمي',
  'ﶷ' => 'كمي',
  'ﶸ' => 'نجح',
  'ﶹ' => 'مخي',
  'ﶺ' => 'لجم',
  'ﶻ' => 'كمم',
  'ﶼ' => 'لجم',
  'ﶽ' => 'نجح',
  'ﶾ' => 'جحي',
  'ﶿ' => 'حجي',
  'ﷀ' => 'مجي',
  'ﷁ' => 'فمي',
  'ﷂ' => 'بحي',
  'ﷃ' => 'كمم',
  'ﷄ' => 'عجم',
  'ﷅ' => 'صمم',
  'ﷆ' => 'سخي',
  'ﷇ' => 'نجي',
  'ﷰ' => 'صلے',
  'ﷱ' => 'قلے',
  'ﷲ' => 'الله',
  'ﷳ' => 'اكبر',
  'ﷴ' => 'محمد',
  'ﷵ' => 'صلعم',
  'ﷶ' => 'رسول',
  'ﷷ' => 'عليه',
  'ﷸ' => 'وسلم',
  'ﷹ' => 'صلى',
  'ﷺ' => 'صلى الله عليه وسلم',
  'ﷻ' => 'جل جلاله',
  '﷼' => 'ریال',
  '︐' => ',',
  '︑' => '、',
  '︒' => '。',
  '︓' => ':',
  '︔' => ';',
  '︕' => '!',
  '︖' => '?',
  '︗' => '〖',
  '︘' => '〗',
  '︙' => '...',
  '︰' => '..',
  '︱' => '—',
  '︲' => '–',
  '︳' => '_',
  '︴' => '_',
  '︵' => '(',
  '︶' => ')',
  '︷' => '{',
  '︸' => '}',
  '︹' => '〔',
  '︺' => '〕',
  '︻' => '【',
  '︼' => '】',
  '︽' => '《',
  '︾' => '》',
  '︿' => '〈',
  '﹀' => '〉',
  '﹁' => '「',
  '﹂' => '」',
  '﹃' => '『',
  '﹄' => '』',
  '﹇' => '[',
  '﹈' => ']',
  '﹉' => ' ̅',
  '﹊' => ' ̅',
  '﹋' => ' ̅',
  '﹌' => ' ̅',
  '﹍' => '_',
  '﹎' => '_',
  '﹏' => '_',
  '﹐' => ',',
  '﹑' => '、',
  '﹒' => '.',
  '﹔' => ';',
  '﹕' => ':',
  '﹖' => '?',
  '﹗' => '!',
  '﹘' => '—',
  '﹙' => '(',
  '﹚' => ')',
  '﹛' => '{',
  '﹜' => '}',
  '﹝' => '〔',
  '﹞' => '〕',
  '﹟' => '#',
  '﹠' => '&',
  '﹡' => '*',
  '﹢' => '+',
  '﹣' => '-',
  '﹤' => '<',
  '﹥' => '>',
  '﹦' => '=',
  '﹨' => '\\',
  '﹩' => '$',
  '﹪' => '%',
  '﹫' => '@',
  'ﹰ' => ' ً',
  'ﹱ' => 'ـً',
  'ﹲ' => ' ٌ',
  'ﹴ' => ' ٍ',
  'ﹶ' => ' َ',
  'ﹷ' => 'ـَ',
  'ﹸ' => ' ُ',
  'ﹹ' => 'ـُ',
  'ﹺ' => ' ِ',
  'ﹻ' => 'ـِ',
  'ﹼ' => ' ّ',
  'ﹽ' => 'ـّ',
  'ﹾ' => ' ْ',
  'ﹿ' => 'ـْ',
  'ﺀ' => 'ء',
  'ﺁ' => 'آ',
  'ﺂ' => 'آ',
  'ﺃ' => 'أ',
  'ﺄ' => 'أ',
  'ﺅ' => 'ؤ',
  'ﺆ' => 'ؤ',
  'ﺇ' => 'إ',
  'ﺈ' => 'إ',
  'ﺉ' => 'ئ',
  'ﺊ' => 'ئ',
  'ﺋ' => 'ئ',
  'ﺌ' => 'ئ',
  'ﺍ' => 'ا',
  'ﺎ' => 'ا',
  'ﺏ' => 'ب',
  'ﺐ' => 'ب',
  'ﺑ' => 'ب',
  'ﺒ' => 'ب',
  'ﺓ' => 'ة',
  'ﺔ' => 'ة',
  'ﺕ' => 'ت',
  'ﺖ' => 'ت',
  'ﺗ' => 'ت',
  'ﺘ' => 'ت',
  'ﺙ' => 'ث',
  'ﺚ' => 'ث',
  'ﺛ' => 'ث',
  'ﺜ' => 'ث',
  'ﺝ' => 'ج',
  'ﺞ' => 'ج',
  'ﺟ' => 'ج',
  'ﺠ' => 'ج',
  'ﺡ' => 'ح',
  'ﺢ' => 'ح',
  'ﺣ' => 'ح',
  'ﺤ' => 'ح',
  'ﺥ' => 'خ',
  'ﺦ' => 'خ',
  'ﺧ' => 'خ',
  'ﺨ' => 'خ',
  'ﺩ' => 'د',
  'ﺪ' => 'د',
  'ﺫ' => 'ذ',
  'ﺬ' => 'ذ',
  'ﺭ' => 'ر',
  'ﺮ' => 'ر',
  'ﺯ' => 'ز',
  'ﺰ' => 'ز',
  'ﺱ' => 'س',
  'ﺲ' => 'س',
  'ﺳ' => 'س',
  'ﺴ' => 'س',
  'ﺵ' => 'ش',
  'ﺶ' => 'ش',
  'ﺷ' => 'ش',
  'ﺸ' => 'ش',
  'ﺹ' => 'ص',
  'ﺺ' => 'ص',
  'ﺻ' => 'ص',
  'ﺼ' => 'ص',
  'ﺽ' => 'ض',
  'ﺾ' => 'ض',
  'ﺿ' => 'ض',
  'ﻀ' => 'ض',
  'ﻁ' => 'ط',
  'ﻂ' => 'ط',
  'ﻃ' => 'ط',
  'ﻄ' => 'ط',
  'ﻅ' => 'ظ',
  'ﻆ' => 'ظ',
  'ﻇ' => 'ظ',
  'ﻈ' => 'ظ',
  'ﻉ' => 'ع',
  'ﻊ' => 'ع',
  'ﻋ' => 'ع',
  'ﻌ' => 'ع',
  'ﻍ' => 'غ',
  'ﻎ' => 'غ',
  'ﻏ' => 'غ',
  'ﻐ' => 'غ',
  'ﻑ' => 'ف',
  'ﻒ' => 'ف',
  'ﻓ' => 'ف',
  'ﻔ' => 'ف',
  'ﻕ' => 'ق',
  'ﻖ' => 'ق',
  'ﻗ' => 'ق',
  'ﻘ' => 'ق',
  'ﻙ' => 'ك',
  'ﻚ' => 'ك',
  'ﻛ' => 'ك',
  'ﻜ' => 'ك',
  'ﻝ' => 'ل',
  'ﻞ' => 'ل',
  'ﻟ' => 'ل',
  'ﻠ' => 'ل',
  'ﻡ' => 'م',
  'ﻢ' => 'م',
  'ﻣ' => 'م',
  'ﻤ' => 'م',
  'ﻥ' => 'ن',
  'ﻦ' => 'ن',
  'ﻧ' => 'ن',
  'ﻨ' => 'ن',
  'ﻩ' => 'ه',
  'ﻪ' => 'ه',
  'ﻫ' => 'ه',
  'ﻬ' => 'ه',
  'ﻭ' => 'و',
  'ﻮ' => 'و',
  'ﻯ' => 'ى',
  'ﻰ' => 'ى',
  'ﻱ' => 'ي',
  'ﻲ' => 'ي',
  'ﻳ' => 'ي',
  'ﻴ' => 'ي',
  'ﻵ' => 'لآ',
  'ﻶ' => 'لآ',
  'ﻷ' => 'لأ',
  'ﻸ' => 'لأ',
  'ﻹ' => 'لإ',
  'ﻺ' => 'لإ',
  'ﻻ' => 'لا',
  'ﻼ' => 'لا',
  '！' => '!',
  '＂' => '"',
  '＃' => '#',
  '＄' => '$',
  '％' => '%',
  '＆' => '&',
  '＇' => '\'',
  '（' => '(',
  '）' => ')',
  '＊' => '*',
  '＋' => '+',
  '，' => ',',
  '－' => '-',
  '．' => '.',
  '／' => '/',
  '０' => '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',
  '［' => '[',
  '＼' => '\\',
  '］' => ']',
  '＾' => '^',
  '＿' => '_',
  '｀' => '`',
  'ａ' => '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',
  '𝑖' => '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',
);
<?php

return array (
  '̀' => 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,
);
<?php

class Normalizer extends Symfony\Polyfill\Intl\Normalizer\Normalizer
{
    /**
     * @deprecated since ICU 56 and removed in PHP 8
     */
    public const NONE = 2;
    public const FORM_D = 4;
    public const FORM_KD = 8;
    public const FORM_C = 16;
    public const FORM_KC = 32;
    public const NFD = 4;
    public const NFKD = 8;
    public const NFC = 16;
    public const NFKC = 32;
}
Copyright (c) 2015-present Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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); }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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); }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <p@tchwork.com>
 *
 * @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", 'ß', 'ŉ', 'ǰ', 'ΐ', 'ΰ', 'և', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'ẚ', 'ẞ', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'ᾐ', 'ᾑ', 'ᾒ', 'ᾓ', 'ᾔ', 'ᾕ', 'ᾖ', 'ᾗ', 'ᾘ', 'ᾙ', 'ᾚ', 'ᾛ', 'ᾜ', 'ᾝ', 'ᾞ', 'ᾟ', 'ᾠ', 'ᾡ', 'ᾢ', 'ᾣ', 'ᾤ', 'ᾥ', 'ᾦ', 'ᾧ', 'ᾨ', 'ᾩ', 'ᾪ', 'ᾫ', 'ᾬ', 'ᾭ', 'ᾮ', 'ᾯ', 'ᾲ', 'ᾳ', 'ᾴ', 'ᾶ', 'ᾷ', 'ᾼ', 'ῂ', 'ῃ', 'ῄ', 'ῆ', 'ῇ', 'ῌ', 'ῒ', 'ῖ', 'ῗ', 'ῢ', 'ῤ', 'ῦ', 'ῧ', 'ῲ', 'ῳ', 'ῴ', 'ῶ', 'ῷ', 'ῼ', 'ﬀ', 'ﬁ', 'ﬂ', 'ﬃ', 'ﬄ', 'ﬅ', 'ﬆ', 'ﬓ', 'ﬔ', 'ﬕ', 'ﬖ', 'ﬗ'];
    private const FOLD_TO = ['i̇', 'μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', 'ṡ', 'ι', 'ss', 'ʼn', 'ǰ', 'ΐ', 'ΰ', 'եւ', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'aʾ', 'ss', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὰι', 'αι', 'άι', 'ᾶ', 'ᾶι', 'αι', 'ὴι', 'ηι', 'ήι', 'ῆ', 'ῆι', 'ηι', 'ῒ', 'ῖ', 'ῗ', 'ῢ', 'ῤ', 'ῦ', 'ῧ', 'ὼι', 'ωι', 'ώι', 'ῶ', 'ῶι', 'ωι', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'st', 'st', 'մն', 'մե', 'մի', 'վն', 'մխ'];

    // 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 = ['Æ', 'Ð', 'Ø', 'Þ', 'ß', 'æ', 'ð', 'ø', 'þ', 'Đ', 'đ', 'Ħ', 'ħ', 'ı', 'ĸ', 'Ŀ', 'ŀ', 'Ł', 'ł', 'ŉ', 'Ŋ', 'ŋ', 'Œ', 'œ', 'Ŧ', 'ŧ', 'ƀ', 'Ɓ', 'Ƃ', 'ƃ', 'Ƈ', 'ƈ', 'Ɖ', 'Ɗ', 'Ƌ', 'ƌ', 'Ɛ', 'Ƒ', 'ƒ', 'Ɠ', 'ƕ', 'Ɩ', 'Ɨ', 'Ƙ', 'ƙ', 'ƚ', 'Ɲ', 'ƞ', 'Ƣ', 'ƣ', 'Ƥ', 'ƥ', 'ƫ', 'Ƭ', 'ƭ', 'Ʈ', 'Ʋ', 'Ƴ', 'ƴ', 'Ƶ', 'ƶ', 'Ǆ', 'ǅ', 'ǆ', 'Ǥ', 'ǥ', 'ȡ', 'Ȥ', 'ȥ', 'ȴ', 'ȵ', 'ȶ', 'ȷ', 'ȸ', 'ȹ', 'Ⱥ', 'Ȼ', 'ȼ', 'Ƚ', 'Ⱦ', 'ȿ', 'ɀ', 'Ƀ', 'Ʉ', 'Ɇ', 'ɇ', 'Ɉ', 'ɉ', 'Ɍ', 'ɍ', 'Ɏ', 'ɏ', 'ɓ', 'ɕ', 'ɖ', 'ɗ', 'ɛ', 'ɟ', 'ɠ', 'ɡ', 'ɢ', 'ɦ', 'ɧ', 'ɨ', 'ɪ', 'ɫ', 'ɬ', 'ɭ', 'ɱ', 'ɲ', 'ɳ', 'ɴ', 'ɶ', 'ɼ', 'ɽ', 'ɾ', 'ʀ', 'ʂ', 'ʈ', 'ʉ', 'ʋ', 'ʏ', 'ʐ', 'ʑ', 'ʙ', 'ʛ', 'ʜ', 'ʝ', 'ʟ', 'ʠ', 'ʣ', 'ʥ', 'ʦ', 'ʪ', 'ʫ', 'ᴀ', 'ᴁ', 'ᴃ', 'ᴄ', 'ᴅ', 'ᴆ', 'ᴇ', 'ᴊ', 'ᴋ', 'ᴌ', 'ᴍ', 'ᴏ', 'ᴘ', 'ᴛ', 'ᴜ', 'ᴠ', 'ᴡ', 'ᴢ', 'ᵫ', 'ᵬ', 'ᵭ', 'ᵮ', 'ᵯ', 'ᵰ', 'ᵱ', 'ᵲ', 'ᵳ', 'ᵴ', 'ᵵ', 'ᵶ', 'ᵺ', 'ᵻ', 'ᵽ', 'ᵾ', 'ᶀ', 'ᶁ', 'ᶂ', 'ᶃ', 'ᶄ', 'ᶅ', 'ᶆ', 'ᶇ', 'ᶈ', 'ᶉ', 'ᶊ', 'ᶌ', 'ᶍ', 'ᶎ', 'ᶏ', 'ᶑ', 'ᶒ', 'ᶓ', 'ᶖ', 'ᶙ', 'ẚ', 'ẜ', 'ẝ', 'ẞ', 'Ỻ', 'ỻ', 'Ỽ', 'ỽ', 'Ỿ', 'ỿ', '©', '®', '₠', '₢', '₣', '₤', '₧', '₺', '₹', 'ℌ', '℞', '㎧', '㎮', '㏆', '㏗', '㏞', '㏟', '¼', '½', '¾', '⅓', '⅔', '⅕', '⅖', '⅗', '⅘', '⅙', '⅚', '⅛', '⅜', '⅝', '⅞', '⅟', '〇', '‘', '’', '‚', '‛', '“', '”', '„', '‟', '′', '″', '〝', '〞', '«', '»', '‹', '›', '‐', '‑', '‒', '–', '—', '―', '︱', '︲', '﹘', '‖', '⁄', '⁅', '⁆', '⁎', '、', '。', '〈', '〉', '《', '》', '〔', '〕', '〘', '〙', '〚', '〛', '︑', '︒', '︹', '︺', '︽', '︾', '︿', '﹀', '﹑', '﹝', '﹞', '｟', '｠', '｡', '､', '×', '÷', '−', '∕', '∖', '∣', '∥', '≪', '≫', '⦅', '⦆'];
    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 array $transliterators = [];
    private static array $tableZero;
    private static array $tableWide;

    public static function fromCodePoints(int ...$codes): static
    {
        $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] ??= \Transliterator::create($rule)) {
                        if ('any-latin/bgn' === $rule) {
                            $rule = 'any-latin';
                            $transliterator = 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 {
                $previousLocale = setlocale(\LC_CTYPE, 0);
                try {
                    setlocale(\LC_CTYPE, 'C');
                    $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);
                } finally {
                    setlocale(\LC_CTYPE, $previousLocale);
                }
            }
        }

        $str->string .= $s;

        return $str;
    }

    public function camel(): static
    {
        $str = clone $this;
        $str->string = str_replace(' ', '', preg_replace_callback('/\b.(?!\p{Lu})/u', static function ($m) {
            static $i = 0;

            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): static
    {
        $str = clone $this;

        if (!$compat || !\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, $str->string), 'UTF-8');
        } else {
            $str->string = normalizer_normalize($str->string, \Normalizer::NFKC_CF);
        }

        return $str;
    }

    public function join(array $strings, ?string $lastGlue = null): static
    {
        $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(): static
    {
        $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 fn ($t, $m) => throw new InvalidArgumentException($m));

        try {
            if (false === $match($regexp.'u', $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) {
                throw new RuntimeException('Matching failed with error: '.preg_last_error_msg());
            }
        } finally {
            restore_error_handler();
        }

        return $matches;
    }

    public function normalize(int $form = self::NFC): static
    {
        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 = ' '): static
    {
        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 = ' '): static
    {
        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 = ' '): static
    {
        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, string|callable $to): static
    {
        if ($this->ignoreCase) {
            $fromRegexp .= 'i';
        }

        if (\is_array($to) || $to instanceof \Closure) {
            $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 fn ($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 && str_ends_with($k, '_ERROR')) {
                        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(): static
    {
        $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(): static
    {
        $str = $this->camel();
        $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): static
    {
        $str = clone $this;

        $limit = $allWords ? -1 : 1;

        $str->string = preg_replace_callback('/\b./u', static fn (array $m): string => 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}"): static
    {
        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}"): static
    {
        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): static
    {
        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}"): static
    {
        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): static
    {
        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(): static
    {
        $str = clone $this;
        $str->string = mb_strtoupper($str->string, 'UTF-8');

        return $str;
    }

    public function width(bool $ignoreAnsiDecoration = true): int
    {
        $width = 0;
        $s = str_replace(["\x00", "\x05", "\x07"], '', $this->string);

        if (str_contains($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;
    }

    private function pad(int $len, self $pad, int $type): static
    {
        $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;
            }

            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;
                    }
                }
            }

            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;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <p@tchwork.com>
 * @author Hugo Hamon <hugohamon@neuf.fr>
 *
 * @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 ??= 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
     */
    public function after(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): static
    {
        $str = clone $this;
        $i = \PHP_INT_MAX;

        if (\is_string($needle)) {
            $needle = [$needle];
        }

        foreach ($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
     */
    public function afterLast(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): static
    {
        $str = clone $this;
        $i = null;

        if (\is_string($needle)) {
            $needle = [$needle];
        }

        foreach ($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);
    }

    abstract public function append(string ...$suffix): static;

    /**
     * @param string|string[] $needle
     */
    public function before(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): static
    {
        $str = clone $this;
        $i = \PHP_INT_MAX;

        if (\is_string($needle)) {
            $needle = [$needle];
        }

        foreach ($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
     */
    public function beforeLast(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): static
    {
        $str = clone $this;
        $i = null;

        if (\is_string($needle)) {
            $needle = [$needle];
        }

        foreach ($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));
    }

    abstract public function camel(): static;

    /**
     * @return static[]
     */
    abstract public function chunk(int $length = 1): array;

    public function collapseWhitespace(): static
    {
        $str = clone $this;
        $str->string = trim(preg_replace("/(?:[ \n\r\t\x0C]{2,}+|[\n\r\t\x0C])/", ' ', $str->string), " \n\r\t\x0C");

        return $str;
    }

    /**
     * @param string|string[] $needle
     */
    public function containsAny(string|iterable $needle): bool
    {
        return null !== $this->indexOf($needle);
    }

    /**
     * @param string|string[] $suffix
     */
    public function endsWith(string|iterable $suffix): bool
    {
        if (\is_string($suffix)) {
            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;
    }

    public function ensureEnd(string $suffix): static
    {
        if (!$this->endsWith($suffix)) {
            return $this->append($suffix);
        }

        $suffix = preg_quote($suffix);
        $regex = '{('.$suffix.')(?:'.$suffix.')++$}D';

        return $this->replaceMatches($regex.($this->ignoreCase ? 'i' : ''), '$1');
    }

    public function ensureStart(string $prefix): static
    {
        $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|iterable $string): bool
    {
        if (\is_string($string)) {
            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;
    }

    abstract public function folded(): static;

    public function ignoreCase(): static
    {
        $str = clone $this;
        $str->ignoreCase = true;

        return $str;
    }

    /**
     * @param string|string[] $needle
     */
    public function indexOf(string|iterable $needle, int $offset = 0): ?int
    {
        if (\is_string($needle)) {
            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(string|iterable $needle, int $offset = 0): ?int
    {
        if (\is_string($needle)) {
            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;
    }

    abstract public function join(array $strings, ?string $lastGlue = null): static;

    public function jsonSerialize(): string
    {
        return $this->string;
    }

    abstract public function length(): int;

    abstract public function lower(): static;

    /**
     * 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;

    abstract public function padBoth(int $length, string $padStr = ' '): static;

    abstract public function padEnd(int $length, string $padStr = ' '): static;

    abstract public function padStart(int $length, string $padStr = ' '): static;

    abstract public function prepend(string ...$prefix): static;

    public function repeat(int $multiplier): static
    {
        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;
    }

    abstract public function replace(string $from, string $to): static;

    abstract public function replaceMatches(string $fromRegexp, string|callable $to): static;

    abstract public function reverse(): static;

    abstract public function slice(int $start = 0, ?int $length = null): static;

    abstract public function snake(): static;

    abstract public function splice(string $replacement, int $start = 0, ?int $length = null): static;

    /**
     * @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 fn ($t, $m) => throw new InvalidArgumentException($m));

        try {
            if (false === $chunks = preg_split($delimiter, $this->string, $limit, $flags)) {
                throw new RuntimeException('Splitting failed with error: '.preg_last_error_msg());
            }
        } 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(string|iterable $prefix): bool
    {
        if (\is_string($prefix)) {
            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;
    }

    abstract public function title(bool $allWords = false): static;

    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;
        }

        try {
            $b->string = mb_convert_encoding($this->string, $toEncoding, 'UTF-8');
        } catch (\ValueError $e) {
            if (!\function_exists('iconv')) {
                throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
            }

            $b->string = iconv('UTF-8', $toEncoding, $this->string);
        }

        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);
    }

    abstract public function trim(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static;

    abstract public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static;

    /**
     * @param string|string[] $prefix
     */
    public function trimPrefix($prefix): static
    {
        if (\is_array($prefix) || $prefix instanceof \Traversable) { // don't use is_iterable(), it's slow
            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;
    }

    abstract public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static;

    /**
     * @param string|string[] $suffix
     */
    public function trimSuffix($suffix): static
    {
        if (\is_array($suffix) || $suffix instanceof \Traversable) { // don't use is_iterable(), it's slow
            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;
    }

    public function truncate(int $length, string $ellipsis = '', bool $cut = true): static
    {
        $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;
    }

    abstract public function upper(): static;

    /**
     * Returns the printable length on a terminal.
     */
    abstract public function width(bool $ignoreAnsiDecoration = true): int;

    public function wordwrap(int $width = 75, string $break = "\n", bool $cut = false): static
    {
        $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;
    }
}
<?php

/*
 * This file has been auto-generated by the Symfony String Component for internal use.
 *
 * Unicode version: 16.0.0
 * Date: 2024-09-11T08:21:22+00:00
 */

return [
    [
        768,
        879,
    ],
    [
        1155,
        1159,
    ],
    [
        1160,
        1161,
    ],
    [
        1425,
        1469,
    ],
    [
        1471,
        1471,
    ],
    [
        1473,
        1474,
    ],
    [
        1476,
        1477,
    ],
    [
        1479,
        1479,
    ],
    [
        1552,
        1562,
    ],
    [
        1611,
        1631,
    ],
    [
        1648,
        1648,
    ],
    [
        1750,
        1756,
    ],
    [
        1759,
        1764,
    ],
    [
        1767,
        1768,
    ],
    [
        1770,
        1773,
    ],
    [
        1809,
        1809,
    ],
    [
        1840,
        1866,
    ],
    [
        1958,
        1968,
    ],
    [
        2027,
        2035,
    ],
    [
        2045,
        2045,
    ],
    [
        2070,
        2073,
    ],
    [
        2075,
        2083,
    ],
    [
        2085,
        2087,
    ],
    [
        2089,
        2093,
    ],
    [
        2137,
        2139,
    ],
    [
        2199,
        2207,
    ],
    [
        2250,
        2273,
    ],
    [
        2275,
        2306,
    ],
    [
        2362,
        2362,
    ],
    [
        2364,
        2364,
    ],
    [
        2369,
        2376,
    ],
    [
        2381,
        2381,
    ],
    [
        2385,
        2391,
    ],
    [
        2402,
        2403,
    ],
    [
        2433,
        2433,
    ],
    [
        2492,
        2492,
    ],
    [
        2497,
        2500,
    ],
    [
        2509,
        2509,
    ],
    [
        2530,
        2531,
    ],
    [
        2558,
        2558,
    ],
    [
        2561,
        2562,
    ],
    [
        2620,
        2620,
    ],
    [
        2625,
        2626,
    ],
    [
        2631,
        2632,
    ],
    [
        2635,
        2637,
    ],
    [
        2641,
        2641,
    ],
    [
        2672,
        2673,
    ],
    [
        2677,
        2677,
    ],
    [
        2689,
        2690,
    ],
    [
        2748,
        2748,
    ],
    [
        2753,
        2757,
    ],
    [
        2759,
        2760,
    ],
    [
        2765,
        2765,
    ],
    [
        2786,
        2787,
    ],
    [
        2810,
        2815,
    ],
    [
        2817,
        2817,
    ],
    [
        2876,
        2876,
    ],
    [
        2879,
        2879,
    ],
    [
        2881,
        2884,
    ],
    [
        2893,
        2893,
    ],
    [
        2901,
        2902,
    ],
    [
        2914,
        2915,
    ],
    [
        2946,
        2946,
    ],
    [
        3008,
        3008,
    ],
    [
        3021,
        3021,
    ],
    [
        3072,
        3072,
    ],
    [
        3076,
        3076,
    ],
    [
        3132,
        3132,
    ],
    [
        3134,
        3136,
    ],
    [
        3142,
        3144,
    ],
    [
        3146,
        3149,
    ],
    [
        3157,
        3158,
    ],
    [
        3170,
        3171,
    ],
    [
        3201,
        3201,
    ],
    [
        3260,
        3260,
    ],
    [
        3263,
        3263,
    ],
    [
        3270,
        3270,
    ],
    [
        3276,
        3277,
    ],
    [
        3298,
        3299,
    ],
    [
        3328,
        3329,
    ],
    [
        3387,
        3388,
    ],
    [
        3393,
        3396,
    ],
    [
        3405,
        3405,
    ],
    [
        3426,
        3427,
    ],
    [
        3457,
        3457,
    ],
    [
        3530,
        3530,
    ],
    [
        3538,
        3540,
    ],
    [
        3542,
        3542,
    ],
    [
        3633,
        3633,
    ],
    [
        3636,
        3642,
    ],
    [
        3655,
        3662,
    ],
    [
        3761,
        3761,
    ],
    [
        3764,
        3772,
    ],
    [
        3784,
        3790,
    ],
    [
        3864,
        3865,
    ],
    [
        3893,
        3893,
    ],
    [
        3895,
        3895,
    ],
    [
        3897,
        3897,
    ],
    [
        3953,
        3966,
    ],
    [
        3968,
        3972,
    ],
    [
        3974,
        3975,
    ],
    [
        3981,
        3991,
    ],
    [
        3993,
        4028,
    ],
    [
        4038,
        4038,
    ],
    [
        4141,
        4144,
    ],
    [
        4146,
        4151,
    ],
    [
        4153,
        4154,
    ],
    [
        4157,
        4158,
    ],
    [
        4184,
        4185,
    ],
    [
        4190,
        4192,
    ],
    [
        4209,
        4212,
    ],
    [
        4226,
        4226,
    ],
    [
        4229,
        4230,
    ],
    [
        4237,
        4237,
    ],
    [
        4253,
        4253,
    ],
    [
        4957,
        4959,
    ],
    [
        5906,
        5908,
    ],
    [
        5938,
        5939,
    ],
    [
        5970,
        5971,
    ],
    [
        6002,
        6003,
    ],
    [
        6068,
        6069,
    ],
    [
        6071,
        6077,
    ],
    [
        6086,
        6086,
    ],
    [
        6089,
        6099,
    ],
    [
        6109,
        6109,
    ],
    [
        6155,
        6157,
    ],
    [
        6159,
        6159,
    ],
    [
        6277,
        6278,
    ],
    [
        6313,
        6313,
    ],
    [
        6432,
        6434,
    ],
    [
        6439,
        6440,
    ],
    [
        6450,
        6450,
    ],
    [
        6457,
        6459,
    ],
    [
        6679,
        6680,
    ],
    [
        6683,
        6683,
    ],
    [
        6742,
        6742,
    ],
    [
        6744,
        6750,
    ],
    [
        6752,
        6752,
    ],
    [
        6754,
        6754,
    ],
    [
        6757,
        6764,
    ],
    [
        6771,
        6780,
    ],
    [
        6783,
        6783,
    ],
    [
        6832,
        6845,
    ],
    [
        6846,
        6846,
    ],
    [
        6847,
        6862,
    ],
    [
        6912,
        6915,
    ],
    [
        6964,
        6964,
    ],
    [
        6966,
        6970,
    ],
    [
        6972,
        6972,
    ],
    [
        6978,
        6978,
    ],
    [
        7019,
        7027,
    ],
    [
        7040,
        7041,
    ],
    [
        7074,
        7077,
    ],
    [
        7080,
        7081,
    ],
    [
        7083,
        7085,
    ],
    [
        7142,
        7142,
    ],
    [
        7144,
        7145,
    ],
    [
        7149,
        7149,
    ],
    [
        7151,
        7153,
    ],
    [
        7212,
        7219,
    ],
    [
        7222,
        7223,
    ],
    [
        7376,
        7378,
    ],
    [
        7380,
        7392,
    ],
    [
        7394,
        7400,
    ],
    [
        7405,
        7405,
    ],
    [
        7412,
        7412,
    ],
    [
        7416,
        7417,
    ],
    [
        7616,
        7679,
    ],
    [
        8400,
        8412,
    ],
    [
        8413,
        8416,
    ],
    [
        8417,
        8417,
    ],
    [
        8418,
        8420,
    ],
    [
        8421,
        8432,
    ],
    [
        11503,
        11505,
    ],
    [
        11647,
        11647,
    ],
    [
        11744,
        11775,
    ],
    [
        12330,
        12333,
    ],
    [
        12441,
        12442,
    ],
    [
        42607,
        42607,
    ],
    [
        42608,
        42610,
    ],
    [
        42612,
        42621,
    ],
    [
        42654,
        42655,
    ],
    [
        42736,
        42737,
    ],
    [
        43010,
        43010,
    ],
    [
        43014,
        43014,
    ],
    [
        43019,
        43019,
    ],
    [
        43045,
        43046,
    ],
    [
        43052,
        43052,
    ],
    [
        43204,
        43205,
    ],
    [
        43232,
        43249,
    ],
    [
        43263,
        43263,
    ],
    [
        43302,
        43309,
    ],
    [
        43335,
        43345,
    ],
    [
        43392,
        43394,
    ],
    [
        43443,
        43443,
    ],
    [
        43446,
        43449,
    ],
    [
        43452,
        43453,
    ],
    [
        43493,
        43493,
    ],
    [
        43561,
        43566,
    ],
    [
        43569,
        43570,
    ],
    [
        43573,
        43574,
    ],
    [
        43587,
        43587,
    ],
    [
        43596,
        43596,
    ],
    [
        43644,
        43644,
    ],
    [
        43696,
        43696,
    ],
    [
        43698,
        43700,
    ],
    [
        43703,
        43704,
    ],
    [
        43710,
        43711,
    ],
    [
        43713,
        43713,
    ],
    [
        43756,
        43757,
    ],
    [
        43766,
        43766,
    ],
    [
        44005,
        44005,
    ],
    [
        44008,
        44008,
    ],
    [
        44013,
        44013,
    ],
    [
        64286,
        64286,
    ],
    [
        65024,
        65039,
    ],
    [
        65056,
        65071,
    ],
    [
        66045,
        66045,
    ],
    [
        66272,
        66272,
    ],
    [
        66422,
        66426,
    ],
    [
        68097,
        68099,
    ],
    [
        68101,
        68102,
    ],
    [
        68108,
        68111,
    ],
    [
        68152,
        68154,
    ],
    [
        68159,
        68159,
    ],
    [
        68325,
        68326,
    ],
    [
        68900,
        68903,
    ],
    [
        68969,
        68973,
    ],
    [
        69291,
        69292,
    ],
    [
        69372,
        69375,
    ],
    [
        69446,
        69456,
    ],
    [
        69506,
        69509,
    ],
    [
        69633,
        69633,
    ],
    [
        69688,
        69702,
    ],
    [
        69744,
        69744,
    ],
    [
        69747,
        69748,
    ],
    [
        69759,
        69761,
    ],
    [
        69811,
        69814,
    ],
    [
        69817,
        69818,
    ],
    [
        69826,
        69826,
    ],
    [
        69888,
        69890,
    ],
    [
        69927,
        69931,
    ],
    [
        69933,
        69940,
    ],
    [
        70003,
        70003,
    ],
    [
        70016,
        70017,
    ],
    [
        70070,
        70078,
    ],
    [
        70089,
        70092,
    ],
    [
        70095,
        70095,
    ],
    [
        70191,
        70193,
    ],
    [
        70196,
        70196,
    ],
    [
        70198,
        70199,
    ],
    [
        70206,
        70206,
    ],
    [
        70209,
        70209,
    ],
    [
        70367,
        70367,
    ],
    [
        70371,
        70378,
    ],
    [
        70400,
        70401,
    ],
    [
        70459,
        70460,
    ],
    [
        70464,
        70464,
    ],
    [
        70502,
        70508,
    ],
    [
        70512,
        70516,
    ],
    [
        70587,
        70592,
    ],
    [
        70606,
        70606,
    ],
    [
        70608,
        70608,
    ],
    [
        70610,
        70610,
    ],
    [
        70625,
        70626,
    ],
    [
        70712,
        70719,
    ],
    [
        70722,
        70724,
    ],
    [
        70726,
        70726,
    ],
    [
        70750,
        70750,
    ],
    [
        70835,
        70840,
    ],
    [
        70842,
        70842,
    ],
    [
        70847,
        70848,
    ],
    [
        70850,
        70851,
    ],
    [
        71090,
        71093,
    ],
    [
        71100,
        71101,
    ],
    [
        71103,
        71104,
    ],
    [
        71132,
        71133,
    ],
    [
        71219,
        71226,
    ],
    [
        71229,
        71229,
    ],
    [
        71231,
        71232,
    ],
    [
        71339,
        71339,
    ],
    [
        71341,
        71341,
    ],
    [
        71344,
        71349,
    ],
    [
        71351,
        71351,
    ],
    [
        71453,
        71453,
    ],
    [
        71455,
        71455,
    ],
    [
        71458,
        71461,
    ],
    [
        71463,
        71467,
    ],
    [
        71727,
        71735,
    ],
    [
        71737,
        71738,
    ],
    [
        71995,
        71996,
    ],
    [
        71998,
        71998,
    ],
    [
        72003,
        72003,
    ],
    [
        72148,
        72151,
    ],
    [
        72154,
        72155,
    ],
    [
        72160,
        72160,
    ],
    [
        72193,
        72202,
    ],
    [
        72243,
        72248,
    ],
    [
        72251,
        72254,
    ],
    [
        72263,
        72263,
    ],
    [
        72273,
        72278,
    ],
    [
        72281,
        72283,
    ],
    [
        72330,
        72342,
    ],
    [
        72344,
        72345,
    ],
    [
        72752,
        72758,
    ],
    [
        72760,
        72765,
    ],
    [
        72767,
        72767,
    ],
    [
        72850,
        72871,
    ],
    [
        72874,
        72880,
    ],
    [
        72882,
        72883,
    ],
    [
        72885,
        72886,
    ],
    [
        73009,
        73014,
    ],
    [
        73018,
        73018,
    ],
    [
        73020,
        73021,
    ],
    [
        73023,
        73029,
    ],
    [
        73031,
        73031,
    ],
    [
        73104,
        73105,
    ],
    [
        73109,
        73109,
    ],
    [
        73111,
        73111,
    ],
    [
        73459,
        73460,
    ],
    [
        73472,
        73473,
    ],
    [
        73526,
        73530,
    ],
    [
        73536,
        73536,
    ],
    [
        73538,
        73538,
    ],
    [
        73562,
        73562,
    ],
    [
        78912,
        78912,
    ],
    [
        78919,
        78933,
    ],
    [
        90398,
        90409,
    ],
    [
        90413,
        90415,
    ],
    [
        92912,
        92916,
    ],
    [
        92976,
        92982,
    ],
    [
        94031,
        94031,
    ],
    [
        94095,
        94098,
    ],
    [
        94180,
        94180,
    ],
    [
        113821,
        113822,
    ],
    [
        118528,
        118573,
    ],
    [
        118576,
        118598,
    ],
    [
        119143,
        119145,
    ],
    [
        119163,
        119170,
    ],
    [
        119173,
        119179,
    ],
    [
        119210,
        119213,
    ],
    [
        119362,
        119364,
    ],
    [
        121344,
        121398,
    ],
    [
        121403,
        121452,
    ],
    [
        121461,
        121461,
    ],
    [
        121476,
        121476,
    ],
    [
        121499,
        121503,
    ],
    [
        121505,
        121519,
    ],
    [
        122880,
        122886,
    ],
    [
        122888,
        122904,
    ],
    [
        122907,
        122913,
    ],
    [
        122915,
        122916,
    ],
    [
        122918,
        122922,
    ],
    [
        123023,
        123023,
    ],
    [
        123184,
        123190,
    ],
    [
        123566,
        123566,
    ],
    [
        123628,
        123631,
    ],
    [
        124140,
        124143,
    ],
    [
        124398,
        124399,
    ],
    [
        125136,
        125142,
    ],
    [
        125252,
        125258,
    ],
    [
        917760,
        917999,
    ],
];
<?php

/*
 * This file has been auto-generated by the Symfony String Component for internal use.
 *
 * Unicode version: 16.0.0
 * Date: 2024-09-11T08:21:22+00:00
 */

return [
    [
        4352,
        4447,
    ],
    [
        8986,
        8987,
    ],
    [
        9001,
        9001,
    ],
    [
        9002,
        9002,
    ],
    [
        9193,
        9196,
    ],
    [
        9200,
        9200,
    ],
    [
        9203,
        9203,
    ],
    [
        9725,
        9726,
    ],
    [
        9748,
        9749,
    ],
    [
        9776,
        9783,
    ],
    [
        9800,
        9811,
    ],
    [
        9855,
        9855,
    ],
    [
        9866,
        9871,
    ],
    [
        9875,
        9875,
    ],
    [
        9889,
        9889,
    ],
    [
        9898,
        9899,
    ],
    [
        9917,
        9918,
    ],
    [
        9924,
        9925,
    ],
    [
        9934,
        9934,
    ],
    [
        9940,
        9940,
    ],
    [
        9962,
        9962,
    ],
    [
        9970,
        9971,
    ],
    [
        9973,
        9973,
    ],
    [
        9978,
        9978,
    ],
    [
        9981,
        9981,
    ],
    [
        9989,
        9989,
    ],
    [
        9994,
        9995,
    ],
    [
        10024,
        10024,
    ],
    [
        10060,
        10060,
    ],
    [
        10062,
        10062,
    ],
    [
        10067,
        10069,
    ],
    [
        10071,
        10071,
    ],
    [
        10133,
        10135,
    ],
    [
        10160,
        10160,
    ],
    [
        10175,
        10175,
    ],
    [
        11035,
        11036,
    ],
    [
        11088,
        11088,
    ],
    [
        11093,
        11093,
    ],
    [
        11904,
        11929,
    ],
    [
        11931,
        12019,
    ],
    [
        12032,
        12245,
    ],
    [
        12272,
        12287,
    ],
    [
        12288,
        12288,
    ],
    [
        12289,
        12291,
    ],
    [
        12292,
        12292,
    ],
    [
        12293,
        12293,
    ],
    [
        12294,
        12294,
    ],
    [
        12295,
        12295,
    ],
    [
        12296,
        12296,
    ],
    [
        12297,
        12297,
    ],
    [
        12298,
        12298,
    ],
    [
        12299,
        12299,
    ],
    [
        12300,
        12300,
    ],
    [
        12301,
        12301,
    ],
    [
        12302,
        12302,
    ],
    [
        12303,
        12303,
    ],
    [
        12304,
        12304,
    ],
    [
        12305,
        12305,
    ],
    [
        12306,
        12307,
    ],
    [
        12308,
        12308,
    ],
    [
        12309,
        12309,
    ],
    [
        12310,
        12310,
    ],
    [
        12311,
        12311,
    ],
    [
        12312,
        12312,
    ],
    [
        12313,
        12313,
    ],
    [
        12314,
        12314,
    ],
    [
        12315,
        12315,
    ],
    [
        12316,
        12316,
    ],
    [
        12317,
        12317,
    ],
    [
        12318,
        12319,
    ],
    [
        12320,
        12320,
    ],
    [
        12321,
        12329,
    ],
    [
        12330,
        12333,
    ],
    [
        12334,
        12335,
    ],
    [
        12336,
        12336,
    ],
    [
        12337,
        12341,
    ],
    [
        12342,
        12343,
    ],
    [
        12344,
        12346,
    ],
    [
        12347,
        12347,
    ],
    [
        12348,
        12348,
    ],
    [
        12349,
        12349,
    ],
    [
        12350,
        12350,
    ],
    [
        12353,
        12438,
    ],
    [
        12441,
        12442,
    ],
    [
        12443,
        12444,
    ],
    [
        12445,
        12446,
    ],
    [
        12447,
        12447,
    ],
    [
        12448,
        12448,
    ],
    [
        12449,
        12538,
    ],
    [
        12539,
        12539,
    ],
    [
        12540,
        12542,
    ],
    [
        12543,
        12543,
    ],
    [
        12549,
        12591,
    ],
    [
        12593,
        12686,
    ],
    [
        12688,
        12689,
    ],
    [
        12690,
        12693,
    ],
    [
        12694,
        12703,
    ],
    [
        12704,
        12735,
    ],
    [
        12736,
        12773,
    ],
    [
        12783,
        12783,
    ],
    [
        12784,
        12799,
    ],
    [
        12800,
        12830,
    ],
    [
        12832,
        12841,
    ],
    [
        12842,
        12871,
    ],
    [
        12880,
        12880,
    ],
    [
        12881,
        12895,
    ],
    [
        12896,
        12927,
    ],
    [
        12928,
        12937,
    ],
    [
        12938,
        12976,
    ],
    [
        12977,
        12991,
    ],
    [
        12992,
        13055,
    ],
    [
        13056,
        13311,
    ],
    [
        13312,
        19903,
    ],
    [
        19904,
        19967,
    ],
    [
        19968,
        40959,
    ],
    [
        40960,
        40980,
    ],
    [
        40981,
        40981,
    ],
    [
        40982,
        42124,
    ],
    [
        42128,
        42182,
    ],
    [
        43360,
        43388,
    ],
    [
        44032,
        55203,
    ],
    [
        63744,
        64109,
    ],
    [
        64110,
        64111,
    ],
    [
        64112,
        64217,
    ],
    [
        64218,
        64255,
    ],
    [
        65040,
        65046,
    ],
    [
        65047,
        65047,
    ],
    [
        65048,
        65048,
    ],
    [
        65049,
        65049,
    ],
    [
        65072,
        65072,
    ],
    [
        65073,
        65074,
    ],
    [
        65075,
        65076,
    ],
    [
        65077,
        65077,
    ],
    [
        65078,
        65078,
    ],
    [
        65079,
        65079,
    ],
    [
        65080,
        65080,
    ],
    [
        65081,
        65081,
    ],
    [
        65082,
        65082,
    ],
    [
        65083,
        65083,
    ],
    [
        65084,
        65084,
    ],
    [
        65085,
        65085,
    ],
    [
        65086,
        65086,
    ],
    [
        65087,
        65087,
    ],
    [
        65088,
        65088,
    ],
    [
        65089,
        65089,
    ],
    [
        65090,
        65090,
    ],
    [
        65091,
        65091,
    ],
    [
        65092,
        65092,
    ],
    [
        65093,
        65094,
    ],
    [
        65095,
        65095,
    ],
    [
        65096,
        65096,
    ],
    [
        65097,
        65100,
    ],
    [
        65101,
        65103,
    ],
    [
        65104,
        65106,
    ],
    [
        65108,
        65111,
    ],
    [
        65112,
        65112,
    ],
    [
        65113,
        65113,
    ],
    [
        65114,
        65114,
    ],
    [
        65115,
        65115,
    ],
    [
        65116,
        65116,
    ],
    [
        65117,
        65117,
    ],
    [
        65118,
        65118,
    ],
    [
        65119,
        65121,
    ],
    [
        65122,
        65122,
    ],
    [
        65123,
        65123,
    ],
    [
        65124,
        65126,
    ],
    [
        65128,
        65128,
    ],
    [
        65129,
        65129,
    ],
    [
        65130,
        65131,
    ],
    [
        65281,
        65283,
    ],
    [
        65284,
        65284,
    ],
    [
        65285,
        65287,
    ],
    [
        65288,
        65288,
    ],
    [
        65289,
        65289,
    ],
    [
        65290,
        65290,
    ],
    [
        65291,
        65291,
    ],
    [
        65292,
        65292,
    ],
    [
        65293,
        65293,
    ],
    [
        65294,
        65295,
    ],
    [
        65296,
        65305,
    ],
    [
        65306,
        65307,
    ],
    [
        65308,
        65310,
    ],
    [
        65311,
        65312,
    ],
    [
        65313,
        65338,
    ],
    [
        65339,
        65339,
    ],
    [
        65340,
        65340,
    ],
    [
        65341,
        65341,
    ],
    [
        65342,
        65342,
    ],
    [
        65343,
        65343,
    ],
    [
        65344,
        65344,
    ],
    [
        65345,
        65370,
    ],
    [
        65371,
        65371,
    ],
    [
        65372,
        65372,
    ],
    [
        65373,
        65373,
    ],
    [
        65374,
        65374,
    ],
    [
        65375,
        65375,
    ],
    [
        65376,
        65376,
    ],
    [
        65504,
        65505,
    ],
    [
        65506,
        65506,
    ],
    [
        65507,
        65507,
    ],
    [
        65508,
        65508,
    ],
    [
        65509,
        65510,
    ],
    [
        94176,
        94177,
    ],
    [
        94178,
        94178,
    ],
    [
        94179,
        94179,
    ],
    [
        94180,
        94180,
    ],
    [
        94192,
        94193,
    ],
    [
        94208,
        100343,
    ],
    [
        100352,
        101119,
    ],
    [
        101120,
        101589,
    ],
    [
        101631,
        101631,
    ],
    [
        101632,
        101640,
    ],
    [
        110576,
        110579,
    ],
    [
        110581,
        110587,
    ],
    [
        110589,
        110590,
    ],
    [
        110592,
        110847,
    ],
    [
        110848,
        110882,
    ],
    [
        110898,
        110898,
    ],
    [
        110928,
        110930,
    ],
    [
        110933,
        110933,
    ],
    [
        110948,
        110951,
    ],
    [
        110960,
        111355,
    ],
    [
        119552,
        119638,
    ],
    [
        119648,
        119670,
    ],
    [
        126980,
        126980,
    ],
    [
        127183,
        127183,
    ],
    [
        127374,
        127374,
    ],
    [
        127377,
        127386,
    ],
    [
        127488,
        127490,
    ],
    [
        127504,
        127547,
    ],
    [
        127552,
        127560,
    ],
    [
        127568,
        127569,
    ],
    [
        127584,
        127589,
    ],
    [
        127744,
        127776,
    ],
    [
        127789,
        127797,
    ],
    [
        127799,
        127868,
    ],
    [
        127870,
        127891,
    ],
    [
        127904,
        127946,
    ],
    [
        127951,
        127955,
    ],
    [
        127968,
        127984,
    ],
    [
        127988,
        127988,
    ],
    [
        127992,
        127994,
    ],
    [
        127995,
        127999,
    ],
    [
        128000,
        128062,
    ],
    [
        128064,
        128064,
    ],
    [
        128066,
        128252,
    ],
    [
        128255,
        128317,
    ],
    [
        128331,
        128334,
    ],
    [
        128336,
        128359,
    ],
    [
        128378,
        128378,
    ],
    [
        128405,
        128406,
    ],
    [
        128420,
        128420,
    ],
    [
        128507,
        128511,
    ],
    [
        128512,
        128591,
    ],
    [
        128640,
        128709,
    ],
    [
        128716,
        128716,
    ],
    [
        128720,
        128722,
    ],
    [
        128725,
        128727,
    ],
    [
        128732,
        128735,
    ],
    [
        128747,
        128748,
    ],
    [
        128756,
        128764,
    ],
    [
        128992,
        129003,
    ],
    [
        129008,
        129008,
    ],
    [
        129292,
        129338,
    ],
    [
        129340,
        129349,
    ],
    [
        129351,
        129535,
    ],
    [
        129648,
        129660,
    ],
    [
        129664,
        129673,
    ],
    [
        129679,
        129734,
    ],
    [
        129742,
        129756,
    ],
    [
        129759,
        129769,
    ],
    [
        129776,
        129784,
    ],
    [
        131072,
        173791,
    ],
    [
        173792,
        173823,
    ],
    [
        173824,
        177977,
    ],
    [
        177978,
        177983,
    ],
    [
        177984,
        178205,
    ],
    [
        178206,
        178207,
    ],
    [
        178208,
        183969,
    ],
    [
        183970,
        183983,
    ],
    [
        183984,
        191456,
    ],
    [
        191457,
        191471,
    ],
    [
        191472,
        192093,
    ],
    [
        192094,
        194559,
    ],
    [
        194560,
        195101,
    ],
    [
        195102,
        195103,
    ],
    [
        195104,
        196605,
    ],
    [
        196608,
        201546,
    ],
    [
        201547,
        201551,
    ],
    [
        201552,
        205743,
    ],
    [
        205744,
        262141,
    ],
];
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 ??= '';

        return preg_match('//u', $string) ? new UnicodeString($string) : new ByteString($string);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <p@tchwork.com>
 * @author Hugo Hamon <hugohamon@neuf.fr>
 *
 * @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): static
    {
        $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(string|iterable|AbstractString $suffix): bool
    {
        if ($suffix instanceof AbstractString) {
            $suffix = $suffix->string;
        } elseif (!\is_string($suffix)) {
            return parent::endsWith($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|iterable|AbstractString $string): bool
    {
        if ($string instanceof AbstractString) {
            $string = $string->string;
        } elseif (!\is_string($string)) {
            return parent::equalsTo($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(string|iterable|AbstractString $needle, int $offset = 0): ?int
    {
        if ($needle instanceof AbstractString) {
            $needle = $needle->string;
        } elseif (!\is_string($needle)) {
            return parent::indexOf($needle, $offset);
        }

        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(string|iterable|AbstractString $needle, int $offset = 0): ?int
    {
        if ($needle instanceof AbstractString) {
            $needle = $needle->string;
        } elseif (!\is_string($needle)) {
            return parent::indexOfLast($needle, $offset);
        }

        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): static
    {
        $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): static
    {
        $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): static
    {
        $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): static
    {
        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 ??= \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(string|iterable|AbstractString $prefix): bool
    {
        if ($prefix instanceof AbstractString) {
            $prefix = $prefix->string;
        } elseif (!\is_string($prefix)) {
            return parent::startsWith($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));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <p@tchwork.com>
 * @author Hugo Hamon <hugohamon@neuf.fr>
 *
 * @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 ??= 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): static
    {
        $str = clone $this;
        $str->string .= 1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix);

        return $str;
    }

    public function camel(): static
    {
        $str = clone $this;

        $parts = explode(' ', trim(ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $this->string))));
        $parts[0] = 1 !== \strlen($parts[0]) && ctype_upper($parts[0]) ? $parts[0] : lcfirst($parts[0]);
        $str->string = implode('', $parts);

        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(string|iterable|AbstractString $suffix): bool
    {
        if ($suffix instanceof AbstractString) {
            $suffix = $suffix->string;
        } elseif (!\is_string($suffix)) {
            return parent::endsWith($suffix);
        }

        return '' !== $suffix && \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix), null, $this->ignoreCase);
    }

    public function equalsTo(string|iterable|AbstractString $string): bool
    {
        if ($string instanceof AbstractString) {
            $string = $string->string;
        } elseif (!\is_string($string)) {
            return parent::equalsTo($string);
        }

        if ('' !== $string && $this->ignoreCase) {
            return 0 === strcasecmp($string, $this->string);
        }

        return $string === $this->string;
    }

    public function folded(): static
    {
        $str = clone $this;
        $str->string = strtolower($str->string);

        return $str;
    }

    public function indexOf(string|iterable|AbstractString $needle, int $offset = 0): ?int
    {
        if ($needle instanceof AbstractString) {
            $needle = $needle->string;
        } elseif (!\is_string($needle)) {
            return parent::indexOf($needle, $offset);
        }

        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(string|iterable|AbstractString $needle, int $offset = 0): ?int
    {
        if ($needle instanceof AbstractString) {
            $needle = $needle->string;
        } elseif (!\is_string($needle)) {
            return parent::indexOfLast($needle, $offset);
        }

        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): static
    {
        $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(): static
    {
        $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 fn ($t, $m) => throw new InvalidArgumentException($m));

        try {
            if (false === $match($regexp, $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) {
                throw new RuntimeException('Matching failed with error: '.preg_last_error_msg());
            }
        } finally {
            restore_error_handler();
        }

        return $matches;
    }

    public function padBoth(int $length, string $padStr = ' '): static
    {
        $str = clone $this;
        $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_BOTH);

        return $str;
    }

    public function padEnd(int $length, string $padStr = ' '): static
    {
        $str = clone $this;
        $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_RIGHT);

        return $str;
    }

    public function padStart(int $length, string $padStr = ' '): static
    {
        $str = clone $this;
        $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_LEFT);

        return $str;
    }

    public function prepend(string ...$prefix): static
    {
        $str = clone $this;
        $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$str->string;

        return $str;
    }

    public function replace(string $from, string $to): static
    {
        $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, string|callable $to): static
    {
        if ($this->ignoreCase) {
            $fromRegexp .= 'i';
        }

        $replace = \is_array($to) || $to instanceof \Closure ? 'preg_replace_callback' : 'preg_replace';

        set_error_handler(static fn ($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 && str_ends_with($k, '_ERROR')) {
                        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(): static
    {
        $str = clone $this;
        $str->string = strrev($str->string);

        return $str;
    }

    public function slice(int $start = 0, ?int $length = null): static
    {
        $str = clone $this;
        $str->string = (string) substr($this->string, $start, $length ?? \PHP_INT_MAX);

        return $str;
    }

    public function snake(): static
    {
        $str = $this->camel();
        $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): static
    {
        $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 ??= \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(string|iterable|AbstractString $prefix): bool
    {
        if ($prefix instanceof AbstractString) {
            $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): static
    {
        $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 fn ($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"): static
    {
        $str = clone $this;
        $str->string = trim($str->string, $chars);

        return $str;
    }

    public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C"): static
    {
        $str = clone $this;
        $str->string = rtrim($str->string, $chars);

        return $str;
    }

    public function trimStart(string $chars = " \t\n\r\0\x0B\x0C"): static
    {
        $str = clone $this;
        $str->string = ltrim($str->string, $chars);

        return $str;
    }

    public function upper(): static
    {
        $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);
    }
}
Copyright (c) 2019-present Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 vowel
        // Fourth entry: Whether the suffix may succeed a consonant
        // Fifth entry: singular suffix, normal

        // nodes (node)
        ['sedon', 5, true, true, 'node'],

        // bacteria (bacterium)
        ['airetcab', 8, true, true, 'bacterium'],

        // issues (issue)
        ['seussi', 6, true, true, 'issue'],

        // corpora (corpus)
        ['aroproc', 7, true, true, 'corpus'],

        // criteria (criterion)
        ['airetirc', 8, true, true, 'criterion'],

        // curricula (curriculum)
        ['alucirruc', 9, true, true, 'curriculum'],

        // quora (quorum)
        ['arouq', 5, true, true, 'quorum'],

        // genera (genus)
        ['areneg', 6, true, true, 'genus'],

        // media (medium)
        ['aidem', 5, true, true, 'medium'],

        // memoranda (memorandum)
        ['adnaromem', 9, true, true, 'memorandum'],

        // phenomena (phenomenon)
        ['anemonehp', 9, true, true, 'phenomenon'],

        // strata (stratum)
        ['atarts', 6, true, true, 'stratum'],

        // 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']],

        // codes (code)
        ['sedoc', 5, false, true, 'code'],

        // selfies (selfie)
        ['seifles', 7, true, true, 'selfie'],

        // zombies (zombie)
        ['seibmoz', 7, true, true, 'zombie'],

        // movies (movie)
        ['seivom', 6, true, true, 'movie'],

        // names (name)
        ['seman', 5, true, false, 'name'],

        // 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'],

        // statuses (status)
        ['sesutats', 8, true, true, 'status'],

        // article (articles), ancle (ancles)
        ['sel', 3, true, true, 'le'],

        // 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'],

        // outages (outage) - specific fix to avoid 'outag'
        ['segatuo', 7, true, true, 'outage'],

        // roses (rose), garages (garage), cassettes (cassette),
        // waltzes (waltz), heroes (hero), bushes (bush), arches (arch),
        // shoes (shoe)
        ['se', 2, true, true, ['', 'e']],

        // status (status)
        ['sutats', 6, true, true, 'status'],

        // 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 vowel
        // Fourth entry: Whether the suffix may succeed a consonant
        // Fifth entry: plural suffix, normal

        // nodes (node)
        ['edon', 4, true, true, 'nodes'],

        // axes (axis)
        ['sixa', 4, false, false, 'axes'],

        // 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'],

        // albums (album)
        ['mubla', 5, true, true, 'albums'],

        // quorums (quorum)
        ['murouq', 6, true, true, ['quora', 'quorums']],

        // bacteria (bacterium), curricula (curriculum), media (medium), memoranda (memorandum), phenomena (phenomenon), strata (stratum)
        ['mu', 2, true, true, 'a'],

        // men (man), women (woman)
        ['nam', 3, true, true, 'men'],

        // people (person)
        ['nosrep', 6, true, true, ['persons', 'people']],

        // criteria (criterion)
        ['noiretirc', 9, true, true, 'criteria'],

        // phenomena (phenomenon)
        ['nonemonehp', 10, true, true, 'phenomena'],

        // echoes (echo)
        ['ohce', 4, true, true, 'echoes'],

        // heroes (hero)
        ['oreh', 4, true, true, 'heroes'],

        // atlases (atlas)
        ['salta', 5, true, true, 'atlases'],

        // aliases (alias)
        ['saila', 5, true, true, 'aliases'],

        // 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'],

        // hippocampi (hippocampus)
        ['supmacoppih', 11, false, false, 'hippocampi'],

        // campuses (campus)
        ['sup', 3, true, true, 'puses'],

        // status (status)
        ['sutats', 6, true, true, ['status', 'statuses']],

        // conspectuses (conspectus), prospectuses (prospectus)
        ['sutcep', 6, true, true, 'pectuses'],

        // nexuses (nexus)
        ['suxen', 5, false, false, 'nexuses'],

        // 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']],

        // fax (faxes, faxxes)
        ['xaf', 3, true, true, ['faxes', 'faxxes']],

        // boxes (box)
        ['xo', 2, false, true, 'oxes'],

        // indexes (index), matrixes (matrix), appendices (appendix)
        ['x', 1, true, false, ['ces', 'xes']],

        // 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',

        // equipment
        'tnempiuqe',

        // feedback
        'kcabdeef',

        // fish
        'hsif',

        // health
        'htlaeh',

        // history
        'yrotsih',

        // info
        'ofni',

        // information
        'noitamrofni',

        // money
        'yenom',

        // moose
        'esoom',

        // series
        'seires',

        // sheep
        'peehs',

        // species
        'seiceps',

        // traffic
        'ciffart',

        // aircraft
        'tfarcria',

        // hardware
        'erawdrah',
    ];

    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) {
                        $nextIsVowel = str_contains('aeiou', $lowerPluralRev[$j]);

                        if (!$map[2] && $nextIsVowel) {
                            // suffix may not succeed a vowel but next char is one
                            break;
                        }

                        if (!$map[3] && !$nextIsVowel) {
                            // 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];
    }

    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) {
                        $nextIsVowel = str_contains('aeiou', $lowerSingularRev[$j]);

                        if (!$map[2] && $nextIsVowel) {
                            // suffix may not succeed a vowel but next char is one
                            break;
                        }

                        if (!$map[3] && !$nextIsVowel) {
                            // 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'];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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';

    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];
    }

    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);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <p@tchwork.com>
 */
class LazyString implements \Stringable, \JsonSerializable
{
    private \Closure|string $value;

    /**
     * @param callable|array $callback A callable or a [Closure, method] lazy-callable
     */
    public static function fromCallable(callable|array $callback, mixed ...$arguments): static
    {
        if (\is_array($callback) && !\is_callable($callback) && !(($callback[0] ?? null) 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__, '['.implode(', ', array_map('get_debug_type', $callback)).']'));
        }

        $lazyString = new static();
        $lazyString->value = static function () use (&$callback, &$arguments): string {
            static $value;

            if (null !== $arguments) {
                if (!\is_callable($callback)) {
                    $callback[0] = $callback[0]();
                    $callback[1] ??= '__invoke';
                }
                $value = $callback(...$arguments);
                $callback = !\is_scalar($value) && !$value instanceof \Stringable ? self::getPrettyName($callback) : 'callable';
                $arguments = null;
            }

            return $value ?? '';
        };

        return $lazyString;
    }

    public static function fromStringable(string|int|float|bool|\Stringable $value): static
    {
        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(mixed $value): bool
    {
        return \is_string($value) || $value instanceof \Stringable || \is_scalar($value);
    }

    /**
     * Casts scalars and stringable objects to strings.
     *
     * @throws \TypeError When the provided value is not stringable
     */
    final public static function resolve(\Stringable|string|int|float|bool $value): string
    {
        return $value;
    }

    public function __toString(): string
    {
        if (\is_string($this->value)) {
            return $this->value;
        }

        try {
            return $this->value = ($this->value)();
        } catch (\Throwable $e) {
            if (\TypeError::class === $e::class && __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));
            }

            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 (str_contains($r->name, '{closure') || !$class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) {
                return $r->name;
            }

            $class = $class->name;
            $method = $r->name;
        } else {
            $class = get_debug_type($callback);
            $method = '__invoke';
        }

        return $class.'::'.$method;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <p@tchwork.com>
 * @author Hugo Hamon <hugohamon@neuf.fr>
 *
 * @throws ExceptionInterface
 */
class UnicodeString extends AbstractUnicodeString
{
    public function __construct(string $string = '')
    {
        if ('' === $string || normalizer_is_normalized($this->string = $string)) {
            return;
        }

        if (false === $string = normalizer_normalize($string)) {
            throw new InvalidArgumentException('Invalid UTF-8 string.');
        }

        $this->string = $string;
    }

    public function append(string ...$suffix): static
    {
        $str = clone $this;
        $str->string = $this->string.(1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix));

        if (normalizer_is_normalized($str->string)) {
            return $str;
        }

        if (false === $string = normalizer_normalize($str->string)) {
            throw new InvalidArgumentException('Invalid UTF-8 string.');
        }

        $str->string = $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(string|iterable|AbstractString $suffix): bool
    {
        if ($suffix instanceof AbstractString) {
            $suffix = $suffix->string;
        } elseif (!\is_string($suffix)) {
            return parent::endsWith($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|iterable|AbstractString $string): bool
    {
        if ($string instanceof AbstractString) {
            $string = $string->string;
        } elseif (!\is_string($string)) {
            return parent::equalsTo($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(string|iterable|AbstractString $needle, int $offset = 0): ?int
    {
        if ($needle instanceof AbstractString) {
            $needle = $needle->string;
        } elseif (!\is_string($needle)) {
            return parent::indexOf($needle, $offset);
        }

        $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) {
            return null;
        }

        return false === $i ? null : $i;
    }

    public function indexOfLast(string|iterable|AbstractString $needle, int $offset = 0): ?int
    {
        if ($needle instanceof AbstractString) {
            $needle = $needle->string;
        } elseif (!\is_string($needle)) {
            return parent::indexOfLast($needle, $offset);
        }

        $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): static
    {
        $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);
    }

    public function normalize(int $form = self::NFC): static
    {
        $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): static
    {
        $str = clone $this;
        $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$this->string;

        if (normalizer_is_normalized($str->string)) {
            return $str;
        }

        if (false === $string = normalizer_normalize($str->string)) {
            throw new InvalidArgumentException('Invalid UTF-8 string.');
        }

        $str->string = $string;

        return $str;
    }

    public function replace(string $from, string $to): static
    {
        $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;

            if (normalizer_is_normalized($str->string)) {
                return $str;
            }

            if (false === $string = normalizer_normalize($str->string)) {
                throw new InvalidArgumentException('Invalid UTF-8 string.');
            }

            $str->string = $string;
        }

        return $str;
    }

    public function replaceMatches(string $fromRegexp, string|callable $to): static
    {
        $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): static
    {
        $str = clone $this;

        $str->string = (string) grapheme_substr($this->string, $start, $length ?? 2147483647);

        return $str;
    }

    public function splice(string $replacement, int $start = 0, ?int $length = null): static
    {
        $str = clone $this;

        $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);

        if (normalizer_is_normalized($str->string)) {
            return $str;
        }

        if (false === $string = normalizer_normalize($str->string)) {
            throw new InvalidArgumentException('Invalid UTF-8 string.');
        }

        $str->string = $string;

        return $str;
    }

    public function split(string $delimiter, ?int $limit = null, ?int $flags = null): array
    {
        if (1 > $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(string|iterable|AbstractString $prefix): bool
    {
        if ($prefix instanceof AbstractString) {
            $prefix = $prefix->string;
        } elseif (!\is_string($prefix)) {
            return parent::startsWith($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);
    }

    /**
     * @return void
     */
    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;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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 <galopintitouan@gmail.com>
 */
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;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * 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\Intl\Transliterator\EmojiTransliterator;
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 <galopintitouan@gmail.com>
 */
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 ?string $defaultLocale;
    private \Closure|array $symbolsMap = [
        'en' => ['@' => 'at', '&' => 'and'],
    ];
    private bool|string $emoji = false;

    /**
     * Cache of transliterators per locale.
     *
     * @var \Transliterator[]
     */
    private array $transliterators = [];

    public function __construct(?string $defaultLocale = null, array|\Closure|null $symbolsMap = null)
    {
        $this->defaultLocale = $defaultLocale;
        $this->symbolsMap = $symbolsMap ?? $this->symbolsMap;
    }

    /**
     * @return void
     */
    public function setLocale(string $locale)
    {
        $this->defaultLocale = $locale;
    }

    public function getLocale(): string
    {
        return $this->defaultLocale;
    }

    /**
     * @param bool|string $emoji true will use the same locale,
     *                           false will disable emoji,
     *                           and a string to use a specific locale
     */
    public function withEmoji(bool|string $emoji = true): static
    {
        if (false !== $emoji && !class_exists(EmojiTransliterator::class)) {
            throw new \LogicException(\sprintf('You cannot use the "%s()" method as the "symfony/intl" package is not installed. Try running "composer require symfony/intl".', __METHOD__));
        }

        $new = clone $this;
        $new->emoji = $emoji;

        return $new;
    }

    public function slug(string $string, string $separator = '-', ?string $locale = null): AbstractUnicodeString
    {
        $locale ??= $this->defaultLocale;

        $transliterator = [];
        if ($locale && ('de' === $locale || str_starts_with($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 ($emojiTransliterator = $this->createEmojiTransliterator($locale)) {
            $transliterator[] = $emojiTransliterator;
        }

        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 fn ($s) => $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 function createEmojiTransliterator(?string $locale): ?EmojiTransliterator
    {
        if (\is_string($this->emoji)) {
            $locale = $this->emoji;
        } elseif (!$this->emoji) {
            return null;
        }

        while (null !== $locale) {
            try {
                return EmojiTransliterator::create("emoji-$locale");
            } catch (\IntlException) {
                $locale = self::getParentLocale($locale);
            }
        }

        return 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));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Debug;

use Symfony\Component\Console\Command\TraceableCommand;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

/**
 * @internal
 */
final class CliRequest extends Request
{
    public function __construct(
        public readonly TraceableCommand $command,
    ) {
        parent::__construct(
            attributes: ['_controller' => \get_class($command->command), '_virtual_type' => 'command'],
            server: $_SERVER,
        );
    }

    // Methods below allow to populate a profile, thus enable search and filtering
    public function getUri(): string
    {
        if ($this->server->has('SYMFONY_CLI_BINARY_NAME')) {
            $binary = $this->server->get('SYMFONY_CLI_BINARY_NAME').' console';
        } else {
            $binary = $this->server->get('argv')[0];
        }

        return $binary.' '.$this->command->input;
    }

    public function getMethod(): string
    {
        return $this->command->isInteractive ? 'INTERACTIVE' : 'BATCH';
    }

    public function getResponse(): Response
    {
        return new class($this->command->exitCode) extends Response {
            public function __construct(private readonly int $exitCode)
            {
                parent::__construct();
            }

            public function getStatusCode(): int
            {
                return $this->exitCode;
            }
        };
    }

    public function getClientIp(): string
    {
        $application = $this->command->getApplication();

        return $application->getName().' '.$application->getVersion();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Descriptor\ApplicationDescription;
use Symfony\Component\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * HelpCommand displays the help for a given command.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class HelpCommand extends Command
{
    private Command $command;

    /**
     * @return void
     */
    protected function configure()
    {
        $this->ignoreValidationErrors();

        $this
            ->setName('help')
            ->setDefinition([
                new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help', fn () => array_keys((new ApplicationDescription($this->getApplication()))->getCommands())),
                new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', fn () => (new DescriptorHelper())->getFormats()),
                new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'),
            ])
            ->setDescription('Display help for a command')
            ->setHelp(<<<'EOF'
The <info>%command.name%</info> command displays help for a given command:

  <info>%command.full_name% list</info>

You can also output the help in other formats by using the <comment>--format</comment> option:

  <info>%command.full_name% --format=xml list</info>

To display the list of available commands, please use the <info>list</info> command.
EOF
            )
        ;
    }

    /**
     * @return void
     */
    public function setCommand(Command $command)
    {
        $this->command = $command;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $this->command ??= $this->getApplication()->find($input->getArgument('command_name'));

        $helper = new DescriptorHelper();
        $helper->describe($output, $this->command, [
            'format' => $input->getOption('format'),
            'raw_text' => $input->getOption('raw'),
        ]);

        unset($this->command);

        return 0;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Completion\Suggestion;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Helper\HelperInterface;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Base class for all commands.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Command
{
    // see https://tldp.org/LDP/abs/html/exitcodes.html
    public const SUCCESS = 0;
    public const FAILURE = 1;
    public const INVALID = 2;

    /**
     * @var string|null The default command name
     *
     * @deprecated since Symfony 6.1, use the AsCommand attribute instead
     */
    protected static $defaultName;

    /**
     * @var string|null The default command description
     *
     * @deprecated since Symfony 6.1, use the AsCommand attribute instead
     */
    protected static $defaultDescription;

    private ?Application $application = null;
    private ?string $name = null;
    private ?string $processTitle = null;
    private array $aliases = [];
    private InputDefinition $definition;
    private bool $hidden = false;
    private string $help = '';
    private string $description = '';
    private ?InputDefinition $fullDefinition = null;
    private bool $ignoreValidationErrors = false;
    private ?\Closure $code = null;
    private array $synopsis = [];
    private array $usages = [];
    private ?HelperSet $helperSet = null;

    public static function getDefaultName(): ?string
    {
        $class = static::class;

        if ($attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class)) {
            return $attribute[0]->newInstance()->name;
        }

        $r = new \ReflectionProperty($class, 'defaultName');

        if ($class !== $r->class || null === static::$defaultName) {
            return null;
        }

        trigger_deprecation('symfony/console', '6.1', 'Relying on the static property "$defaultName" for setting a command name is deprecated. Add the "%s" attribute to the "%s" class instead.', AsCommand::class, static::class);

        return static::$defaultName;
    }

    public static function getDefaultDescription(): ?string
    {
        $class = static::class;

        if ($attribute = (new \ReflectionClass($class))->getAttributes(AsCommand::class)) {
            return $attribute[0]->newInstance()->description;
        }

        $r = new \ReflectionProperty($class, 'defaultDescription');

        if ($class !== $r->class || null === static::$defaultDescription) {
            return null;
        }

        trigger_deprecation('symfony/console', '6.1', 'Relying on the static property "$defaultDescription" for setting a command description is deprecated. Add the "%s" attribute to the "%s" class instead.', AsCommand::class, static::class);

        return static::$defaultDescription;
    }

    /**
     * @param string|null $name The name of the command; passing null means it must be set in configure()
     *
     * @throws LogicException When the command name is empty
     */
    public function __construct(?string $name = null)
    {
        $this->definition = new InputDefinition();

        if (null === $name && null !== $name = static::getDefaultName()) {
            $aliases = explode('|', $name);

            if ('' === $name = array_shift($aliases)) {
                $this->setHidden(true);
                $name = array_shift($aliases);
            }

            $this->setAliases($aliases);
        }

        if (null !== $name) {
            $this->setName($name);
        }

        if ('' === $this->description) {
            $this->setDescription(static::getDefaultDescription() ?? '');
        }

        $this->configure();
    }

    /**
     * Ignores validation errors.
     *
     * This is mainly useful for the help command.
     *
     * @return void
     */
    public function ignoreValidationErrors()
    {
        $this->ignoreValidationErrors = true;
    }

    /**
     * @return void
     */
    public function setApplication(?Application $application = null)
    {
        if (1 > \func_num_args()) {
            trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
        }
        $this->application = $application;
        if ($application) {
            $this->setHelperSet($application->getHelperSet());
        } else {
            $this->helperSet = null;
        }

        $this->fullDefinition = null;
    }

    /**
     * @return void
     */
    public function setHelperSet(HelperSet $helperSet)
    {
        $this->helperSet = $helperSet;
    }

    /**
     * Gets the helper set.
     */
    public function getHelperSet(): ?HelperSet
    {
        return $this->helperSet;
    }

    /**
     * Gets the application instance for this command.
     */
    public function getApplication(): ?Application
    {
        return $this->application;
    }

    /**
     * Checks whether the command is enabled or not in the current environment.
     *
     * Override this to check for x or y and return false if the command cannot
     * run properly under the current conditions.
     *
     * @return bool
     */
    public function isEnabled()
    {
        return true;
    }

    /**
     * Configures the current command.
     *
     * @return void
     */
    protected function configure()
    {
    }

    /**
     * Executes the current command.
     *
     * This method is not abstract because you can use this class
     * as a concrete class. In this case, instead of defining the
     * execute() method, you set the code to execute by passing
     * a Closure to the setCode() method.
     *
     * @return int 0 if everything went fine, or an exit code
     *
     * @throws LogicException When this abstract method is not implemented
     *
     * @see setCode()
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        throw new LogicException('You must override the execute() method in the concrete command class.');
    }

    /**
     * Interacts with the user.
     *
     * This method is executed before the InputDefinition is validated.
     * This means that this is the only place where the command can
     * interactively ask for values of missing required arguments.
     *
     * @return void
     */
    protected function interact(InputInterface $input, OutputInterface $output)
    {
    }

    /**
     * Initializes the command after the input has been bound and before the input
     * is validated.
     *
     * This is mainly useful when a lot of commands extends one main command
     * where some things need to be initialized based on the input arguments and options.
     *
     * @see InputInterface::bind()
     * @see InputInterface::validate()
     *
     * @return void
     */
    protected function initialize(InputInterface $input, OutputInterface $output)
    {
    }

    /**
     * Runs the command.
     *
     * The code to execute is either defined directly with the
     * setCode() method or by overriding the execute() method
     * in a sub-class.
     *
     * @return int The command exit code
     *
     * @throws ExceptionInterface When input binding fails. Bypass this by calling {@link ignoreValidationErrors()}.
     *
     * @see setCode()
     * @see execute()
     */
    public function run(InputInterface $input, OutputInterface $output): int
    {
        // add the application arguments and options
        $this->mergeApplicationDefinition();

        // bind the input against the command specific arguments/options
        try {
            $input->bind($this->getDefinition());
        } catch (ExceptionInterface $e) {
            if (!$this->ignoreValidationErrors) {
                throw $e;
            }
        }

        $this->initialize($input, $output);

        if (null !== $this->processTitle) {
            if (\function_exists('cli_set_process_title')) {
                if (!@cli_set_process_title($this->processTitle)) {
                    if ('Darwin' === \PHP_OS) {
                        $output->writeln('<comment>Running "cli_set_process_title" as an unprivileged user is not supported on MacOS.</comment>', OutputInterface::VERBOSITY_VERY_VERBOSE);
                    } else {
                        cli_set_process_title($this->processTitle);
                    }
                }
            } elseif (\function_exists('setproctitle')) {
                setproctitle($this->processTitle);
            } elseif (OutputInterface::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) {
                $output->writeln('<comment>Install the proctitle PECL to be able to change the process title.</comment>');
            }
        }

        if ($input->isInteractive()) {
            $this->interact($input, $output);
        }

        // The command name argument is often omitted when a command is executed directly with its run() method.
        // It would fail the validation if we didn't make sure the command argument is present,
        // since it's required by the application.
        if ($input->hasArgument('command') && null === $input->getArgument('command')) {
            $input->setArgument('command', $this->getName());
        }

        $input->validate();

        if ($this->code) {
            $statusCode = ($this->code)($input, $output);
        } else {
            $statusCode = $this->execute($input, $output);

            if (!\is_int($statusCode)) {
                throw new \TypeError(\sprintf('Return value of "%s::execute()" must be of the type int, "%s" returned.', static::class, get_debug_type($statusCode)));
            }
        }

        return is_numeric($statusCode) ? (int) $statusCode : 0;
    }

    /**
     * Adds suggestions to $suggestions for the current completion input (e.g. option or argument).
     */
    public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
    {
        $definition = $this->getDefinition();
        if (CompletionInput::TYPE_OPTION_VALUE === $input->getCompletionType() && $definition->hasOption($input->getCompletionName())) {
            $definition->getOption($input->getCompletionName())->complete($input, $suggestions);
        } elseif (CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType() && $definition->hasArgument($input->getCompletionName())) {
            $definition->getArgument($input->getCompletionName())->complete($input, $suggestions);
        }
    }

    /**
     * Sets the code to execute when running this command.
     *
     * If this method is used, it overrides the code defined
     * in the execute() method.
     *
     * @param callable $code A callable(InputInterface $input, OutputInterface $output)
     *
     * @return $this
     *
     * @throws InvalidArgumentException
     *
     * @see execute()
     */
    public function setCode(callable $code): static
    {
        if ($code instanceof \Closure) {
            $r = new \ReflectionFunction($code);
            if (null === $r->getClosureThis()) {
                set_error_handler(static function () {});
                try {
                    if ($c = \Closure::bind($code, $this)) {
                        $code = $c;
                    }
                } finally {
                    restore_error_handler();
                }
            }
        } else {
            $code = $code(...);
        }

        $this->code = $code;

        return $this;
    }

    /**
     * Merges the application definition with the command definition.
     *
     * This method is not part of public API and should not be used directly.
     *
     * @param bool $mergeArgs Whether to merge or not the Application definition arguments to Command definition arguments
     *
     * @internal
     */
    public function mergeApplicationDefinition(bool $mergeArgs = true): void
    {
        if (null === $this->application) {
            return;
        }

        $this->fullDefinition = new InputDefinition();
        $this->fullDefinition->setOptions($this->definition->getOptions());
        $this->fullDefinition->addOptions($this->application->getDefinition()->getOptions());

        if ($mergeArgs) {
            $this->fullDefinition->setArguments($this->application->getDefinition()->getArguments());
            $this->fullDefinition->addArguments($this->definition->getArguments());
        } else {
            $this->fullDefinition->setArguments($this->definition->getArguments());
        }
    }

    /**
     * Sets an array of argument and option instances.
     *
     * @return $this
     */
    public function setDefinition(array|InputDefinition $definition): static
    {
        if ($definition instanceof InputDefinition) {
            $this->definition = $definition;
        } else {
            $this->definition->setDefinition($definition);
        }

        $this->fullDefinition = null;

        return $this;
    }

    /**
     * Gets the InputDefinition attached to this Command.
     */
    public function getDefinition(): InputDefinition
    {
        return $this->fullDefinition ?? $this->getNativeDefinition();
    }

    /**
     * Gets the InputDefinition to be used to create representations of this Command.
     *
     * Can be overridden to provide the original command representation when it would otherwise
     * be changed by merging with the application InputDefinition.
     *
     * This method is not part of public API and should not be used directly.
     */
    public function getNativeDefinition(): InputDefinition
    {
        return $this->definition ?? throw new LogicException(\sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', static::class));
    }

    /**
     * Adds an argument.
     *
     * @param $mode    The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL
     * @param $default The default value (for InputArgument::OPTIONAL mode only)
     * @param array|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion> $suggestedValues The values used for input completion
     *
     * @return $this
     *
     * @throws InvalidArgumentException When argument mode is not valid
     */
    public function addArgument(string $name, ?int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = null */): static
    {
        $suggestedValues = 5 <= \func_num_args() ? func_get_arg(4) : [];
        if (!\is_array($suggestedValues) && !$suggestedValues instanceof \Closure) {
            throw new \TypeError(\sprintf('Argument 5 passed to "%s()" must be array or \Closure, "%s" given.', __METHOD__, get_debug_type($suggestedValues)));
        }
        $this->definition->addArgument(new InputArgument($name, $mode, $description, $default, $suggestedValues));
        $this->fullDefinition?->addArgument(new InputArgument($name, $mode, $description, $default, $suggestedValues));

        return $this;
    }

    /**
     * Adds an option.
     *
     * @param $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
     * @param $mode     The option mode: One of the InputOption::VALUE_* constants
     * @param $default  The default value (must be null for InputOption::VALUE_NONE)
     * @param array|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion> $suggestedValues The values used for input completion
     *
     * @return $this
     *
     * @throws InvalidArgumentException If option mode is invalid or incompatible
     */
    public function addOption(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = [] */): static
    {
        $suggestedValues = 6 <= \func_num_args() ? func_get_arg(5) : [];
        if (!\is_array($suggestedValues) && !$suggestedValues instanceof \Closure) {
            throw new \TypeError(\sprintf('Argument 5 passed to "%s()" must be array or \Closure, "%s" given.', __METHOD__, get_debug_type($suggestedValues)));
        }
        $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default, $suggestedValues));
        $this->fullDefinition?->addOption(new InputOption($name, $shortcut, $mode, $description, $default, $suggestedValues));

        return $this;
    }

    /**
     * Sets the name of the command.
     *
     * This method can set both the namespace and the name if
     * you separate them by a colon (:)
     *
     *     $command->setName('foo:bar');
     *
     * @return $this
     *
     * @throws InvalidArgumentException When the name is invalid
     */
    public function setName(string $name): static
    {
        $this->validateName($name);

        $this->name = $name;

        return $this;
    }

    /**
     * Sets the process title of the command.
     *
     * This feature should be used only when creating a long process command,
     * like a daemon.
     *
     * @return $this
     */
    public function setProcessTitle(string $title): static
    {
        $this->processTitle = $title;

        return $this;
    }

    /**
     * Returns the command name.
     */
    public function getName(): ?string
    {
        return $this->name;
    }

    /**
     * @param bool $hidden Whether or not the command should be hidden from the list of commands
     *
     * @return $this
     */
    public function setHidden(bool $hidden = true): static
    {
        $this->hidden = $hidden;

        return $this;
    }

    /**
     * @return bool whether the command should be publicly shown or not
     */
    public function isHidden(): bool
    {
        return $this->hidden;
    }

    /**
     * Sets the description for the command.
     *
     * @return $this
     */
    public function setDescription(string $description): static
    {
        $this->description = $description;

        return $this;
    }

    /**
     * Returns the description for the command.
     */
    public function getDescription(): string
    {
        return $this->description;
    }

    /**
     * Sets the help for the command.
     *
     * @return $this
     */
    public function setHelp(string $help): static
    {
        $this->help = $help;

        return $this;
    }

    /**
     * Returns the help for the command.
     */
    public function getHelp(): string
    {
        return $this->help;
    }

    /**
     * Returns the processed help for the command replacing the %command.name% and
     * %command.full_name% patterns with the real values dynamically.
     */
    public function getProcessedHelp(): string
    {
        $name = $this->name;
        $isSingleCommand = $this->application?->isSingleCommand();

        $placeholders = [
            '%command.name%',
            '%command.full_name%',
        ];
        $replacements = [
            $name,
            $isSingleCommand ? $_SERVER['PHP_SELF'] : $_SERVER['PHP_SELF'].' '.$name,
        ];

        return str_replace($placeholders, $replacements, $this->getHelp() ?: $this->getDescription());
    }

    /**
     * Sets the aliases for the command.
     *
     * @param string[] $aliases An array of aliases for the command
     *
     * @return $this
     *
     * @throws InvalidArgumentException When an alias is invalid
     */
    public function setAliases(iterable $aliases): static
    {
        $list = [];

        foreach ($aliases as $alias) {
            $this->validateName($alias);
            $list[] = $alias;
        }

        $this->aliases = \is_array($aliases) ? $aliases : $list;

        return $this;
    }

    /**
     * Returns the aliases for the command.
     */
    public function getAliases(): array
    {
        return $this->aliases;
    }

    /**
     * Returns the synopsis for the command.
     *
     * @param bool $short Whether to show the short version of the synopsis (with options folded) or not
     */
    public function getSynopsis(bool $short = false): string
    {
        $key = $short ? 'short' : 'long';

        if (!isset($this->synopsis[$key])) {
            $this->synopsis[$key] = trim(\sprintf('%s %s', $this->name, $this->definition->getSynopsis($short)));
        }

        return $this->synopsis[$key];
    }

    /**
     * Add a command usage example, it'll be prefixed with the command name.
     *
     * @return $this
     */
    public function addUsage(string $usage): static
    {
        if (!str_starts_with($usage, $this->name)) {
            $usage = \sprintf('%s %s', $this->name, $usage);
        }

        $this->usages[] = $usage;

        return $this;
    }

    /**
     * Returns alternative usages of the command.
     */
    public function getUsages(): array
    {
        return $this->usages;
    }

    /**
     * Gets a helper instance by name.
     *
     * @return HelperInterface
     *
     * @throws LogicException           if no HelperSet is defined
     * @throws InvalidArgumentException if the helper is not defined
     */
    public function getHelper(string $name): mixed
    {
        if (null === $this->helperSet) {
            throw new LogicException(\sprintf('Cannot retrieve helper "%s" because there is no HelperSet defined. Did you forget to add your command to the application or to set the application on the command using the setApplication() method? You can also set the HelperSet directly using the setHelperSet() method.', $name));
        }

        return $this->helperSet->get($name);
    }

    /**
     * Validates a command name.
     *
     * It must be non-empty and parts can optionally be separated by ":".
     *
     * @throws InvalidArgumentException When the name is invalid
     */
    private function validateName(string $name): void
    {
        if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) {
            throw new InvalidArgumentException(\sprintf('Command name "%s" is invalid.', $name));
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

/**
 * Interface for command reacting to signal.
 *
 * @author Grégoire Pineau <lyrixx@lyrix.info>
 */
interface SignalableCommandInterface
{
    /**
     * Returns the list of signals to subscribe.
     */
    public function getSubscribedSignals(): array;

    /**
     * The method will be called when the application is signaled.
     *
     * @param int|false $previousExitCode
     *
     * @return int|false The exit code to return or false to continue the normal execution
     */
    public function handleSignal(int $signal/* , int|false $previousExitCode = 0 */);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Attribute\AsCommand;
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\Process\Process;

/**
 * Dumps the completion script for the current shell.
 *
 * @author Wouter de Jong <wouter@wouterj.nl>
 */
#[AsCommand(name: 'completion', description: 'Dump the shell completion script')]
final class DumpCompletionCommand extends Command
{
    /**
     * @deprecated since Symfony 6.1
     */
    protected static $defaultName = 'completion';

    /**
     * @deprecated since Symfony 6.1
     */
    protected static $defaultDescription = 'Dump the shell completion script';

    private array $supportedShells;

    protected function configure(): void
    {
        $fullCommand = $_SERVER['PHP_SELF'];
        $commandName = basename($fullCommand);
        $fullCommand = @realpath($fullCommand) ?: $fullCommand;

        $shell = $this->guessShell();
        [$rcFile, $completionFile] = match ($shell) {
            'fish' => ['~/.config/fish/config.fish', "/etc/fish/completions/$commandName.fish"],
            'zsh' => ['~/.zshrc', '$fpath[1]/_'.$commandName],
            default => ['~/.bashrc', "/etc/bash_completion.d/$commandName"],
        };

        $supportedShells = implode(', ', $this->getSupportedShells());

        $this
            ->setHelp(<<<EOH
The <info>%command.name%</> command dumps the shell completion script required
to use shell autocompletion (currently, {$supportedShells} completion are supported).

<comment>Static installation
-------------------</>

Dump the script to a global completion file and restart your shell:

    <info>%command.full_name% {$shell} | sudo tee {$completionFile}</>

Or dump the script to a local file and source it:

    <info>%command.full_name% {$shell} > completion.sh</>

    <comment># source the file whenever you use the project</>
    <info>source completion.sh</>

    <comment># or add this line at the end of your "{$rcFile}" file:</>
    <info>source /path/to/completion.sh</>

<comment>Dynamic installation
--------------------</>

Add this to the end of your shell configuration file (e.g. <info>"{$rcFile}"</>):

    <info>eval "$({$fullCommand} completion {$shell})"</>
EOH
            )
            ->addArgument('shell', InputArgument::OPTIONAL, 'The shell type (e.g. "bash"), the value of the "$SHELL" env var will be used if this is not given', null, $this->getSupportedShells(...))
            ->addOption('debug', null, InputOption::VALUE_NONE, 'Tail the completion debug log')
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $commandName = basename($_SERVER['argv'][0]);

        if ($input->getOption('debug')) {
            $this->tailDebugLog($commandName, $output);

            return 0;
        }

        $shell = $input->getArgument('shell') ?? self::guessShell();
        $completionFile = __DIR__.'/../Resources/completion.'.$shell;
        if (!file_exists($completionFile)) {
            $supportedShells = $this->getSupportedShells();

            if ($output instanceof ConsoleOutputInterface) {
                $output = $output->getErrorOutput();
            }
            if ($shell) {
                $output->writeln(\sprintf('<error>Detected shell "%s", which is not supported by Symfony shell completion (supported shells: "%s").</>', $shell, implode('", "', $supportedShells)));
            } else {
                $output->writeln(\sprintf('<error>Shell not detected, Symfony shell completion only supports "%s").</>', implode('", "', $supportedShells)));
            }

            return 2;
        }

        $output->write(str_replace(['{{ COMMAND_NAME }}', '{{ VERSION }}'], [$commandName, CompleteCommand::COMPLETION_API_VERSION], file_get_contents($completionFile)));

        return 0;
    }

    private static function guessShell(): string
    {
        return basename($_SERVER['SHELL'] ?? '');
    }

    private function tailDebugLog(string $commandName, OutputInterface $output): void
    {
        $debugFile = sys_get_temp_dir().'/sf_'.$commandName.'.log';
        if (!file_exists($debugFile)) {
            touch($debugFile);
        }
        $process = new Process(['tail', '-f', $debugFile], null, null, null, 0);
        $process->run(function (string $type, string $line) use ($output): void {
            $output->write($line);
        });
    }

    /**
     * @return string[]
     */
    private function getSupportedShells(): array
    {
        if (isset($this->supportedShells)) {
            return $this->supportedShells;
        }

        $shells = [];

        foreach (new \DirectoryIterator(__DIR__.'/../Resources/') as $file) {
            if (str_starts_with($file->getBasename(), 'completion.') && $file->isFile()) {
                $shells[] = $file->getExtension();
            }
        }
        sort($shells);

        return $this->supportedShells = $shells;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\LockInterface;
use Symfony\Component\Lock\Store\FlockStore;
use Symfony\Component\Lock\Store\SemaphoreStore;

/**
 * Basic lock feature for commands.
 *
 * @author Geoffrey Brier <geoffrey.brier@gmail.com>
 */
trait LockableTrait
{
    private ?LockInterface $lock = null;

    /**
     * Locks a command.
     */
    private function lock(?string $name = null, bool $blocking = false): bool
    {
        if (!class_exists(SemaphoreStore::class)) {
            throw new LogicException('To enable the locking feature you must install the symfony/lock component. Try running "composer require symfony/lock".');
        }

        if (null !== $this->lock) {
            throw new LogicException('A lock is already in place.');
        }

        if (SemaphoreStore::isSupported()) {
            $store = new SemaphoreStore();
        } else {
            $store = new FlockStore();
        }

        $this->lock = (new LockFactory($store))->createLock($name ?: $this->getName());
        if (!$this->lock->acquire($blocking)) {
            $this->lock = null;

            return false;
        }

        return true;
    }

    /**
     * Releases the command lock if there is one.
     */
    private function release(): void
    {
        if ($this->lock) {
            $this->lock->release();
            $this->lock = null;
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Descriptor\ApplicationDescription;
use Symfony\Component\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * ListCommand displays the list of all available commands for the application.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ListCommand extends Command
{
    /**
     * @return void
     */
    protected function configure()
    {
        $this
            ->setName('list')
            ->setDefinition([
                new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name', null, fn () => array_keys((new ApplicationDescription($this->getApplication()))->getNamespaces())),
                new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'),
                new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt', fn () => (new DescriptorHelper())->getFormats()),
                new InputOption('short', null, InputOption::VALUE_NONE, 'To skip describing commands\' arguments'),
            ])
            ->setDescription('List commands')
            ->setHelp(<<<'EOF'
The <info>%command.name%</info> command lists all commands:

  <info>%command.full_name%</info>

You can also display the commands for a specific namespace:

  <info>%command.full_name% test</info>

You can also output the information in other formats by using the <comment>--format</comment> option:

  <info>%command.full_name% --format=xml</info>

It's also possible to get raw list of commands (useful for embedding command runner):

  <info>%command.full_name% --raw</info>
EOF
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $helper = new DescriptorHelper();
        $helper->describe($output, $this->getApplication(), [
            'format' => $input->getOption('format'),
            'raw_text' => $input->getOption('raw'),
            'namespace' => $input->getArgument('namespace'),
            'short' => $input->getOption('short'),
        ]);

        return 0;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Completion\Suggestion;
use Symfony\Component\Console\Helper\HelperInterface;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Nicolas Grekas <p@tchwork.com>
 */
final class LazyCommand extends Command
{
    private \Closure|Command $command;
    private ?bool $isEnabled;

    public function __construct(string $name, array $aliases, string $description, bool $isHidden, \Closure $commandFactory, ?bool $isEnabled = true)
    {
        $this->setName($name)
            ->setAliases($aliases)
            ->setHidden($isHidden)
            ->setDescription($description);

        $this->command = $commandFactory;
        $this->isEnabled = $isEnabled;
    }

    public function ignoreValidationErrors(): void
    {
        $this->getCommand()->ignoreValidationErrors();
    }

    public function setApplication(?Application $application = null): void
    {
        if (1 > \func_num_args()) {
            trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
        }
        if ($this->command instanceof parent) {
            $this->command->setApplication($application);
        }

        parent::setApplication($application);
    }

    public function setHelperSet(HelperSet $helperSet): void
    {
        if ($this->command instanceof parent) {
            $this->command->setHelperSet($helperSet);
        }

        parent::setHelperSet($helperSet);
    }

    public function isEnabled(): bool
    {
        return $this->isEnabled ?? $this->getCommand()->isEnabled();
    }

    public function run(InputInterface $input, OutputInterface $output): int
    {
        return $this->getCommand()->run($input, $output);
    }

    public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
    {
        $this->getCommand()->complete($input, $suggestions);
    }

    public function setCode(callable $code): static
    {
        $this->getCommand()->setCode($code);

        return $this;
    }

    /**
     * @internal
     */
    public function mergeApplicationDefinition(bool $mergeArgs = true): void
    {
        $this->getCommand()->mergeApplicationDefinition($mergeArgs);
    }

    public function setDefinition(array|InputDefinition $definition): static
    {
        $this->getCommand()->setDefinition($definition);

        return $this;
    }

    public function getDefinition(): InputDefinition
    {
        return $this->getCommand()->getDefinition();
    }

    public function getNativeDefinition(): InputDefinition
    {
        return $this->getCommand()->getNativeDefinition();
    }

    /**
     * @param array|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion> $suggestedValues The values used for input completion
     */
    public function addArgument(string $name, ?int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = [] */): static
    {
        $suggestedValues = 5 <= \func_num_args() ? func_get_arg(4) : [];
        $this->getCommand()->addArgument($name, $mode, $description, $default, $suggestedValues);

        return $this;
    }

    /**
     * @param array|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion> $suggestedValues The values used for input completion
     */
    public function addOption(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', mixed $default = null /* array|\Closure $suggestedValues = [] */): static
    {
        $suggestedValues = 6 <= \func_num_args() ? func_get_arg(5) : [];
        $this->getCommand()->addOption($name, $shortcut, $mode, $description, $default, $suggestedValues);

        return $this;
    }

    public function setProcessTitle(string $title): static
    {
        $this->getCommand()->setProcessTitle($title);

        return $this;
    }

    public function setHelp(string $help): static
    {
        $this->getCommand()->setHelp($help);

        return $this;
    }

    public function getHelp(): string
    {
        return $this->getCommand()->getHelp();
    }

    public function getProcessedHelp(): string
    {
        return $this->getCommand()->getProcessedHelp();
    }

    public function getSynopsis(bool $short = false): string
    {
        return $this->getCommand()->getSynopsis($short);
    }

    public function addUsage(string $usage): static
    {
        $this->getCommand()->addUsage($usage);

        return $this;
    }

    public function getUsages(): array
    {
        return $this->getCommand()->getUsages();
    }

    public function getHelper(string $name): HelperInterface
    {
        return $this->getCommand()->getHelper($name);
    }

    public function getCommand(): parent
    {
        if (!$this->command instanceof \Closure) {
            return $this->command;
        }

        $command = $this->command = ($this->command)();
        $command->setApplication($this->getApplication());

        if (null !== $this->getHelperSet()) {
            $command->setHelperSet($this->getHelperSet());
        }

        $command->setName($this->getName())
            ->setAliases($this->getAliases())
            ->setHidden($this->isHidden())
            ->setDescription($this->getDescription());

        // Will throw if the command is not correctly initialized.
        $command->getDefinition();

        return $command;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Completion\Output\BashCompletionOutput;
use Symfony\Component\Console\Completion\Output\CompletionOutputInterface;
use Symfony\Component\Console\Completion\Output\FishCompletionOutput;
use Symfony\Component\Console\Completion\Output\ZshCompletionOutput;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Responsible for providing the values to the shell completion.
 *
 * @author Wouter de Jong <wouter@wouterj.nl>
 */
#[AsCommand(name: '|_complete', description: 'Internal command to provide shell completion suggestions')]
final class CompleteCommand extends Command
{
    public const COMPLETION_API_VERSION = '1';

    /**
     * @deprecated since Symfony 6.1
     */
    protected static $defaultName = '|_complete';

    /**
     * @deprecated since Symfony 6.1
     */
    protected static $defaultDescription = 'Internal command to provide shell completion suggestions';

    private array $completionOutputs;

    private bool $isDebug = false;

    /**
     * @param array<string, class-string<CompletionOutputInterface>> $completionOutputs A list of additional completion outputs, with shell name as key and FQCN as value
     */
    public function __construct(array $completionOutputs = [])
    {
        // must be set before the parent constructor, as the property value is used in configure()
        $this->completionOutputs = $completionOutputs + [
            'bash' => BashCompletionOutput::class,
            'fish' => FishCompletionOutput::class,
            'zsh' => ZshCompletionOutput::class,
        ];

        parent::__construct();
    }

    protected function configure(): void
    {
        $this
            ->addOption('shell', 's', InputOption::VALUE_REQUIRED, 'The shell type ("'.implode('", "', array_keys($this->completionOutputs)).'")')
            ->addOption('input', 'i', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'An array of input tokens (e.g. COMP_WORDS or argv)')
            ->addOption('current', 'c', InputOption::VALUE_REQUIRED, 'The index of the "input" array that the cursor is in (e.g. COMP_CWORD)')
            ->addOption('api-version', 'a', InputOption::VALUE_REQUIRED, 'The API version of the completion script')
            ->addOption('symfony', 'S', InputOption::VALUE_REQUIRED, 'deprecated')
        ;
    }

    protected function initialize(InputInterface $input, OutputInterface $output): void
    {
        $this->isDebug = filter_var(getenv('SYMFONY_COMPLETION_DEBUG'), \FILTER_VALIDATE_BOOL);
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        try {
            // "symfony" must be kept for compat with the shell scripts generated by Symfony Console 5.4 - 6.1
            $version = $input->getOption('symfony') ? '1' : $input->getOption('api-version');
            if ($version && version_compare($version, self::COMPLETION_API_VERSION, '<')) {
                $message = \sprintf('Completion script version is not supported ("%s" given, ">=%s" required).', $version, self::COMPLETION_API_VERSION);
                $this->log($message);

                $output->writeln($message.' Install the Symfony completion script again by using the "completion" command.');

                return 126;
            }

            $shell = $input->getOption('shell');
            if (!$shell) {
                throw new \RuntimeException('The "--shell" option must be set.');
            }

            if (!$completionOutput = $this->completionOutputs[$shell] ?? false) {
                throw new \RuntimeException(\sprintf('Shell completion is not supported for your shell: "%s" (supported: "%s").', $shell, implode('", "', array_keys($this->completionOutputs))));
            }

            $completionInput = $this->createCompletionInput($input);
            $suggestions = new CompletionSuggestions();

            $this->log([
                '',
                '<comment>'.date('Y-m-d H:i:s').'</>',
                '<info>Input:</> <comment>("|" indicates the cursor position)</>',
                '  '.(string) $completionInput,
                '<info>Command:</>',
                '  '.(string) implode(' ', $_SERVER['argv']),
                '<info>Messages:</>',
            ]);

            $command = $this->findCommand($completionInput, $output);
            if (null === $command) {
                $this->log('  No command found, completing using the Application class.');

                $this->getApplication()->complete($completionInput, $suggestions);
            } elseif (
                $completionInput->mustSuggestArgumentValuesFor('command')
                && $command->getName() !== $completionInput->getCompletionValue()
                && !\in_array($completionInput->getCompletionValue(), $command->getAliases(), true)
            ) {
                $this->log('  No command found, completing using the Application class.');

                // expand shortcut names ("cache:cl<TAB>") into their full name ("cache:clear")
                $suggestions->suggestValues(array_filter(array_merge([$command->getName()], $command->getAliases())));
            } else {
                $command->mergeApplicationDefinition();
                $completionInput->bind($command->getDefinition());

                if (CompletionInput::TYPE_OPTION_NAME === $completionInput->getCompletionType()) {
                    $this->log('  Completing option names for the <comment>'.($command instanceof LazyCommand ? $command->getCommand() : $command)::class.'</> command.');

                    $suggestions->suggestOptions($command->getDefinition()->getOptions());
                } else {
                    $this->log([
                        '  Completing using the <comment>'.($command instanceof LazyCommand ? $command->getCommand() : $command)::class.'</> class.',
                        '  Completing <comment>'.$completionInput->getCompletionType().'</> for <comment>'.$completionInput->getCompletionName().'</>',
                    ]);
                    if (null !== $compval = $completionInput->getCompletionValue()) {
                        $this->log('  Current value: <comment>'.$compval.'</>');
                    }

                    $command->complete($completionInput, $suggestions);
                }
            }

            /** @var CompletionOutputInterface $completionOutput */
            $completionOutput = new $completionOutput();

            $this->log('<info>Suggestions:</>');
            if ($options = $suggestions->getOptionSuggestions()) {
                $this->log('  --'.implode(' --', array_map(fn ($o) => $o->getName(), $options)));
            } elseif ($values = $suggestions->getValueSuggestions()) {
                $this->log('  '.implode(' ', $values));
            } else {
                $this->log('  <comment>No suggestions were provided</>');
            }

            $completionOutput->write($suggestions, $output);
        } catch (\Throwable $e) {
            $this->log([
                '<error>Error!</error>',
                (string) $e,
            ]);

            if ($output->isDebug()) {
                throw $e;
            }

            return 2;
        }

        return 0;
    }

    private function createCompletionInput(InputInterface $input): CompletionInput
    {
        $currentIndex = $input->getOption('current');
        if (!$currentIndex || !ctype_digit($currentIndex)) {
            throw new \RuntimeException('The "--current" option must be set and it must be an integer.');
        }

        $completionInput = CompletionInput::fromTokens($input->getOption('input'), (int) $currentIndex);

        try {
            $completionInput->bind($this->getApplication()->getDefinition());
        } catch (ExceptionInterface) {
        }

        return $completionInput;
    }

    private function findCommand(CompletionInput $completionInput, OutputInterface $output): ?Command
    {
        try {
            $inputName = $completionInput->getFirstArgument();
            if (null === $inputName) {
                return null;
            }

            return $this->getApplication()->find($inputName);
        } catch (CommandNotFoundException) {
        }

        return null;
    }

    private function log($messages): void
    {
        if (!$this->isDebug) {
            return;
        }

        $commandName = basename($_SERVER['argv'][0]);
        file_put_contents(sys_get_temp_dir().'/sf_'.$commandName.'.log', implode(\PHP_EOL, (array) $messages).\PHP_EOL, \FILE_APPEND);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Helper\HelperInterface;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Stopwatch\Stopwatch;

/**
 * @internal
 *
 * @author Jules Pietri <jules@heahprod.com>
 */
final class TraceableCommand extends Command implements SignalableCommandInterface
{
    public readonly Command $command;
    public int $exitCode;
    public ?int $interruptedBySignal = null;
    public bool $ignoreValidation;
    public bool $isInteractive = false;
    public string $duration = 'n/a';
    public string $maxMemoryUsage = 'n/a';
    public InputInterface $input;
    public OutputInterface $output;
    /** @var array<string, mixed> */
    public array $arguments;
    /** @var array<string, mixed> */
    public array $options;
    /** @var array<string, mixed> */
    public array $interactiveInputs = [];
    public array $handledSignals = [];

    public function __construct(
        Command $command,
        private readonly Stopwatch $stopwatch,
    ) {
        if ($command instanceof LazyCommand) {
            $command = $command->getCommand();
        }

        $this->command = $command;

        // prevent call to self::getDefaultDescription()
        $this->setDescription($command->getDescription());

        parent::__construct($command->getName());

        // init below enables calling {@see parent::run()}
        [$code, $processTitle, $ignoreValidationErrors] = \Closure::bind(function () {
            return [$this->code, $this->processTitle, $this->ignoreValidationErrors];
        }, $command, Command::class)();

        if (\is_callable($code)) {
            $this->setCode($code);
        }

        if ($processTitle) {
            parent::setProcessTitle($processTitle);
        }

        if ($ignoreValidationErrors) {
            parent::ignoreValidationErrors();
        }

        $this->ignoreValidation = $ignoreValidationErrors;
    }

    public function __call(string $name, array $arguments): mixed
    {
        return $this->command->{$name}(...$arguments);
    }

    public function getSubscribedSignals(): array
    {
        return $this->command instanceof SignalableCommandInterface ? $this->command->getSubscribedSignals() : [];
    }

    public function handleSignal(int $signal, int|false $previousExitCode = 0): int|false
    {
        if (!$this->command instanceof SignalableCommandInterface) {
            return false;
        }

        $event = $this->stopwatch->start($this->getName().'.handle_signal');

        $exit = $this->command->handleSignal($signal, $previousExitCode);

        $event->stop();

        if (!isset($this->handledSignals[$signal])) {
            $this->handledSignals[$signal] = [
                'handled' => 0,
                'duration' => 0,
                'memory' => 0,
            ];
        }

        ++$this->handledSignals[$signal]['handled'];
        $this->handledSignals[$signal]['duration'] += $event->getDuration();
        $this->handledSignals[$signal]['memory'] = max(
            $this->handledSignals[$signal]['memory'],
            $event->getMemory() >> 20
        );

        return $exit;
    }

    /**
     * {@inheritdoc}
     *
     * Calling parent method is required to be used in {@see parent::run()}.
     */
    public function ignoreValidationErrors(): void
    {
        $this->ignoreValidation = true;
        $this->command->ignoreValidationErrors();

        parent::ignoreValidationErrors();
    }

    public function setApplication(?Application $application = null): void
    {
        $this->command->setApplication($application);
    }

    public function getApplication(): ?Application
    {
        return $this->command->getApplication();
    }

    public function setHelperSet(HelperSet $helperSet): void
    {
        $this->command->setHelperSet($helperSet);
    }

    public function getHelperSet(): ?HelperSet
    {
        return $this->command->getHelperSet();
    }

    public function isEnabled(): bool
    {
        return $this->command->isEnabled();
    }

    public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
    {
        $this->command->complete($input, $suggestions);
    }

    /**
     * {@inheritdoc}
     *
     * Calling parent method is required to be used in {@see parent::run()}.
     */
    public function setCode(callable $code): static
    {
        $this->command->setCode($code);

        return parent::setCode(function (InputInterface $input, OutputInterface $output) use ($code): int {
            $event = $this->stopwatch->start($this->getName().'.code');

            $this->exitCode = $code($input, $output);

            $event->stop();

            return $this->exitCode;
        });
    }

    /**
     * @internal
     */
    public function mergeApplicationDefinition(bool $mergeArgs = true): void
    {
        $this->command->mergeApplicationDefinition($mergeArgs);
    }

    public function setDefinition(array|InputDefinition $definition): static
    {
        $this->command->setDefinition($definition);

        return $this;
    }

    public function getDefinition(): InputDefinition
    {
        return $this->command->getDefinition();
    }

    public function getNativeDefinition(): InputDefinition
    {
        return $this->command->getNativeDefinition();
    }

    public function addArgument(string $name, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static
    {
        $this->command->addArgument($name, $mode, $description, $default, $suggestedValues);

        return $this;
    }

    public function addOption(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', mixed $default = null, array|\Closure $suggestedValues = []): static
    {
        $this->command->addOption($name, $shortcut, $mode, $description, $default, $suggestedValues);

        return $this;
    }

    /**
     * {@inheritdoc}
     *
     * Calling parent method is required to be used in {@see parent::run()}.
     */
    public function setProcessTitle(string $title): static
    {
        $this->command->setProcessTitle($title);

        return parent::setProcessTitle($title);
    }

    public function setHelp(string $help): static
    {
        $this->command->setHelp($help);

        return $this;
    }

    public function getHelp(): string
    {
        return $this->command->getHelp();
    }

    public function getProcessedHelp(): string
    {
        return $this->command->getProcessedHelp();
    }

    public function getSynopsis(bool $short = false): string
    {
        return $this->command->getSynopsis($short);
    }

    public function addUsage(string $usage): static
    {
        $this->command->addUsage($usage);

        return $this;
    }

    public function getUsages(): array
    {
        return $this->command->getUsages();
    }

    public function getHelper(string $name): HelperInterface
    {
        return $this->command->getHelper($name);
    }

    public function run(InputInterface $input, OutputInterface $output): int
    {
        $this->input = $input;
        $this->output = $output;
        $this->arguments = $input->getArguments();
        $this->options = $input->getOptions();
        $event = $this->stopwatch->start($this->getName(), 'command');

        try {
            $this->exitCode = $this->command->run($input, $output);
        } finally {
            $event->stop();

            if ($output instanceof ConsoleOutputInterface && $output->isDebug()) {
                $output->getErrorOutput()->writeln((string) $event);
            }

            $this->duration = $event->getDuration().' ms';
            $this->maxMemoryUsage = ($event->getMemory() >> 20).' MiB';

            if ($this->isInteractive) {
                $this->extractInteractiveInputs($input->getArguments(), $input->getOptions());
            }
        }

        return $this->exitCode;
    }

    protected function initialize(InputInterface $input, OutputInterface $output): void
    {
        $event = $this->stopwatch->start($this->getName().'.init', 'command');

        $this->command->initialize($input, $output);

        $event->stop();
    }

    protected function interact(InputInterface $input, OutputInterface $output): void
    {
        if (!$this->isInteractive = Command::class !== (new \ReflectionMethod($this->command, 'interact'))->getDeclaringClass()->getName()) {
            return;
        }

        $event = $this->stopwatch->start($this->getName().'.interact', 'command');

        $this->command->interact($input, $output);

        $event->stop();
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $event = $this->stopwatch->start($this->getName().'.execute', 'command');

        $exitCode = $this->command->execute($input, $output);

        $event->stop();

        return $exitCode;
    }

    private function extractInteractiveInputs(array $arguments, array $options): void
    {
        foreach ($arguments as $argName => $argValue) {
            if (\array_key_exists($argName, $this->arguments) && $this->arguments[$argName] === $argValue) {
                continue;
            }

            $this->interactiveInputs[$argName] = $argValue;
        }

        foreach ($options as $optName => $optValue) {
            if (\array_key_exists($optName, $this->options) && $this->options[$optName] === $optValue) {
                continue;
            }

            $this->interactiveInputs['--'.$optName] = $optValue;
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;

/**
 * Input is the base class for all concrete Input classes.
 *
 * Three concrete classes are provided by default:
 *
 *  * `ArgvInput`: The input comes from the CLI arguments (argv)
 *  * `StringInput`: The input is provided as a string
 *  * `ArrayInput`: The input is provided as an array
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
abstract class Input implements InputInterface, StreamableInputInterface
{
    protected $definition;
    /** @var resource */
    protected $stream;
    protected $options = [];
    protected $arguments = [];
    protected $interactive = true;

    public function __construct(?InputDefinition $definition = null)
    {
        if (null === $definition) {
            $this->definition = new InputDefinition();
        } else {
            $this->bind($definition);
            $this->validate();
        }
    }

    /**
     * @return void
     */
    public function bind(InputDefinition $definition)
    {
        $this->arguments = [];
        $this->options = [];
        $this->definition = $definition;

        $this->parse();
    }

    /**
     * Processes command line arguments.
     *
     * @return void
     */
    abstract protected function parse();

    /**
     * @return void
     */
    public function validate()
    {
        $definition = $this->definition;
        $givenArguments = $this->arguments;

        $missingArguments = array_filter(array_keys($definition->getArguments()), fn ($argument) => !\array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired());

        if (\count($missingArguments) > 0) {
            throw new RuntimeException(\sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArguments)));
        }
    }

    public function isInteractive(): bool
    {
        return $this->interactive;
    }

    /**
     * @return void
     */
    public function setInteractive(bool $interactive)
    {
        $this->interactive = $interactive;
    }

    public function getArguments(): array
    {
        return array_merge($this->definition->getArgumentDefaults(), $this->arguments);
    }

    public function getArgument(string $name): mixed
    {
        if (!$this->definition->hasArgument($name)) {
            throw new InvalidArgumentException(\sprintf('The "%s" argument does not exist.', $name));
        }

        return $this->arguments[$name] ?? $this->definition->getArgument($name)->getDefault();
    }

    /**
     * @return void
     */
    public function setArgument(string $name, mixed $value)
    {
        if (!$this->definition->hasArgument($name)) {
            throw new InvalidArgumentException(\sprintf('The "%s" argument does not exist.', $name));
        }

        $this->arguments[$name] = $value;
    }

    public function hasArgument(string $name): bool
    {
        return $this->definition->hasArgument($name);
    }

    public function getOptions(): array
    {
        return array_merge($this->definition->getOptionDefaults(), $this->options);
    }

    public function getOption(string $name): mixed
    {
        if ($this->definition->hasNegation($name)) {
            if (null === $value = $this->getOption($this->definition->negationToName($name))) {
                return $value;
            }

            return !$value;
        }

        if (!$this->definition->hasOption($name)) {
            throw new InvalidArgumentException(\sprintf('The "%s" option does not exist.', $name));
        }

        return \array_key_exists($name, $this->options) ? $this->options[$name] : $this->definition->getOption($name)->getDefault();
    }

    /**
     * @return void
     */
    public function setOption(string $name, mixed $value)
    {
        if ($this->definition->hasNegation($name)) {
            $this->options[$this->definition->negationToName($name)] = !$value;

            return;
        } elseif (!$this->definition->hasOption($name)) {
            throw new InvalidArgumentException(\sprintf('The "%s" option does not exist.', $name));
        }

        $this->options[$name] = $value;
    }

    public function hasOption(string $name): bool
    {
        return $this->definition->hasOption($name) || $this->definition->hasNegation($name);
    }

    /**
     * Escapes a token through escapeshellarg if it contains unsafe chars.
     */
    public function escapeToken(string $token): string
    {
        return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token);
    }

    /**
     * @param resource $stream
     *
     * @return void
     */
    public function setStream($stream)
    {
        $this->stream = $stream;
    }

    /**
     * @return resource
     */
    public function getStream()
    {
        return $this->stream;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Completion\Suggestion;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;

/**
 * Represents a command line argument.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class InputArgument
{
    public const REQUIRED = 1;
    public const OPTIONAL = 2;
    public const IS_ARRAY = 4;

    private string $name;
    private int $mode;
    private string|int|bool|array|float|null $default;
    private array|\Closure $suggestedValues;
    private string $description;

    /**
     * @param string                                                                        $name            The argument name
     * @param int|null                                                                      $mode            The argument mode: a bit mask of self::REQUIRED, self::OPTIONAL and self::IS_ARRAY
     * @param string                                                                        $description     A description text
     * @param string|bool|int|float|array|null                                              $default         The default value (for self::OPTIONAL mode only)
     * @param array|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion> $suggestedValues The values used for input completion
     *
     * @throws InvalidArgumentException When argument mode is not valid
     */
    public function __construct(string $name, ?int $mode = null, string $description = '', string|bool|int|float|array|null $default = null, \Closure|array $suggestedValues = [])
    {
        if (null === $mode) {
            $mode = self::OPTIONAL;
        } elseif ($mode > 7 || $mode < 1) {
            throw new InvalidArgumentException(\sprintf('Argument mode "%s" is not valid.', $mode));
        }

        $this->name = $name;
        $this->mode = $mode;
        $this->description = $description;
        $this->suggestedValues = $suggestedValues;

        $this->setDefault($default);
    }

    /**
     * Returns the argument name.
     */
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * Returns true if the argument is required.
     *
     * @return bool true if parameter mode is self::REQUIRED, false otherwise
     */
    public function isRequired(): bool
    {
        return self::REQUIRED === (self::REQUIRED & $this->mode);
    }

    /**
     * Returns true if the argument can take multiple values.
     *
     * @return bool true if mode is self::IS_ARRAY, false otherwise
     */
    public function isArray(): bool
    {
        return self::IS_ARRAY === (self::IS_ARRAY & $this->mode);
    }

    /**
     * Sets the default value.
     *
     * @return void
     *
     * @throws LogicException When incorrect default value is given
     */
    public function setDefault(string|bool|int|float|array|null $default = null)
    {
        if (1 > \func_num_args()) {
            trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
        }
        if ($this->isRequired() && null !== $default) {
            throw new LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.');
        }

        if ($this->isArray()) {
            if (null === $default) {
                $default = [];
            } elseif (!\is_array($default)) {
                throw new LogicException('A default value for an array argument must be an array.');
            }
        }

        $this->default = $default;
    }

    /**
     * Returns the default value.
     */
    public function getDefault(): string|bool|int|float|array|null
    {
        return $this->default;
    }

    public function hasCompletion(): bool
    {
        return [] !== $this->suggestedValues;
    }

    /**
     * Adds suggestions to $suggestions for the current completion input.
     *
     * @see Command::complete()
     */
    public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
    {
        $values = $this->suggestedValues;
        if ($values instanceof \Closure && !\is_array($values = $values($input))) {
            throw new LogicException(\sprintf('Closure for argument "%s" must return an array. Got "%s".', $this->name, get_debug_type($values)));
        }
        if ($values) {
            $suggestions->suggestValues($values);
        }
    }

    /**
     * Returns the description text.
     */
    public function getDescription(): string
    {
        return $this->description;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

/**
 * InputAwareInterface should be implemented by classes that depends on the
 * Console Input.
 *
 * @author Wouter J <waldio.webdesign@gmail.com>
 */
interface InputAwareInterface
{
    /**
     * Sets the Console Input.
     *
     * @return void
     */
    public function setInput(InputInterface $input);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

/**
 * StreamableInputInterface is the interface implemented by all input classes
 * that have an input stream.
 *
 * @author Robin Chalas <robin.chalas@gmail.com>
 */
interface StreamableInputInterface extends InputInterface
{
    /**
     * Sets the input stream to read from when interacting with the user.
     *
     * This is mainly useful for testing purpose.
     *
     * @param resource $stream The input stream
     *
     * @return void
     */
    public function setStream($stream);

    /**
     * Returns the input stream.
     *
     * @return resource|null
     */
    public function getStream();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;

/**
 * A InputDefinition represents a set of valid command line arguments and options.
 *
 * Usage:
 *
 *     $definition = new InputDefinition([
 *         new InputArgument('name', InputArgument::REQUIRED),
 *         new InputOption('foo', 'f', InputOption::VALUE_REQUIRED),
 *     ]);
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class InputDefinition
{
    private array $arguments = [];
    private int $requiredCount = 0;
    private ?InputArgument $lastArrayArgument = null;
    private ?InputArgument $lastOptionalArgument = null;
    private array $options = [];
    private array $negations = [];
    private array $shortcuts = [];

    /**
     * @param array $definition An array of InputArgument and InputOption instance
     */
    public function __construct(array $definition = [])
    {
        $this->setDefinition($definition);
    }

    /**
     * Sets the definition of the input.
     *
     * @return void
     */
    public function setDefinition(array $definition)
    {
        $arguments = [];
        $options = [];
        foreach ($definition as $item) {
            if ($item instanceof InputOption) {
                $options[] = $item;
            } else {
                $arguments[] = $item;
            }
        }

        $this->setArguments($arguments);
        $this->setOptions($options);
    }

    /**
     * Sets the InputArgument objects.
     *
     * @param InputArgument[] $arguments An array of InputArgument objects
     *
     * @return void
     */
    public function setArguments(array $arguments = [])
    {
        $this->arguments = [];
        $this->requiredCount = 0;
        $this->lastOptionalArgument = null;
        $this->lastArrayArgument = null;
        $this->addArguments($arguments);
    }

    /**
     * Adds an array of InputArgument objects.
     *
     * @param InputArgument[] $arguments An array of InputArgument objects
     *
     * @return void
     */
    public function addArguments(?array $arguments = [])
    {
        if (null !== $arguments) {
            foreach ($arguments as $argument) {
                $this->addArgument($argument);
            }
        }
    }

    /**
     * @return void
     *
     * @throws LogicException When incorrect argument is given
     */
    public function addArgument(InputArgument $argument)
    {
        if (isset($this->arguments[$argument->getName()])) {
            throw new LogicException(\sprintf('An argument with name "%s" already exists.', $argument->getName()));
        }

        if (null !== $this->lastArrayArgument) {
            throw new LogicException(\sprintf('Cannot add a required argument "%s" after an array argument "%s".', $argument->getName(), $this->lastArrayArgument->getName()));
        }

        if ($argument->isRequired() && null !== $this->lastOptionalArgument) {
            throw new LogicException(\sprintf('Cannot add a required argument "%s" after an optional one "%s".', $argument->getName(), $this->lastOptionalArgument->getName()));
        }

        if ($argument->isArray()) {
            $this->lastArrayArgument = $argument;
        }

        if ($argument->isRequired()) {
            ++$this->requiredCount;
        } else {
            $this->lastOptionalArgument = $argument;
        }

        $this->arguments[$argument->getName()] = $argument;
    }

    /**
     * Returns an InputArgument by name or by position.
     *
     * @throws InvalidArgumentException When argument given doesn't exist
     */
    public function getArgument(string|int $name): InputArgument
    {
        if (!$this->hasArgument($name)) {
            throw new InvalidArgumentException(\sprintf('The "%s" argument does not exist.', $name));
        }

        $arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments;

        return $arguments[$name];
    }

    /**
     * Returns true if an InputArgument object exists by name or position.
     */
    public function hasArgument(string|int $name): bool
    {
        $arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments;

        return isset($arguments[$name]);
    }

    /**
     * Gets the array of InputArgument objects.
     *
     * @return InputArgument[]
     */
    public function getArguments(): array
    {
        return $this->arguments;
    }

    /**
     * Returns the number of InputArguments.
     */
    public function getArgumentCount(): int
    {
        return null !== $this->lastArrayArgument ? \PHP_INT_MAX : \count($this->arguments);
    }

    /**
     * Returns the number of required InputArguments.
     */
    public function getArgumentRequiredCount(): int
    {
        return $this->requiredCount;
    }

    /**
     * @return array<string|bool|int|float|array|null>
     */
    public function getArgumentDefaults(): array
    {
        $values = [];
        foreach ($this->arguments as $argument) {
            $values[$argument->getName()] = $argument->getDefault();
        }

        return $values;
    }

    /**
     * Sets the InputOption objects.
     *
     * @param InputOption[] $options An array of InputOption objects
     *
     * @return void
     */
    public function setOptions(array $options = [])
    {
        $this->options = [];
        $this->shortcuts = [];
        $this->negations = [];
        $this->addOptions($options);
    }

    /**
     * Adds an array of InputOption objects.
     *
     * @param InputOption[] $options An array of InputOption objects
     *
     * @return void
     */
    public function addOptions(array $options = [])
    {
        foreach ($options as $option) {
            $this->addOption($option);
        }
    }

    /**
     * @return void
     *
     * @throws LogicException When option given already exist
     */
    public function addOption(InputOption $option)
    {
        if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) {
            throw new LogicException(\sprintf('An option named "%s" already exists.', $option->getName()));
        }
        if (isset($this->negations[$option->getName()])) {
            throw new LogicException(\sprintf('An option named "%s" already exists.', $option->getName()));
        }

        if ($option->getShortcut()) {
            foreach (explode('|', $option->getShortcut()) as $shortcut) {
                if (isset($this->shortcuts[$shortcut]) && !$option->equals($this->options[$this->shortcuts[$shortcut]])) {
                    throw new LogicException(\sprintf('An option with shortcut "%s" already exists.', $shortcut));
                }
            }
        }

        $this->options[$option->getName()] = $option;
        if ($option->getShortcut()) {
            foreach (explode('|', $option->getShortcut()) as $shortcut) {
                $this->shortcuts[$shortcut] = $option->getName();
            }
        }

        if ($option->isNegatable()) {
            $negatedName = 'no-'.$option->getName();
            if (isset($this->options[$negatedName])) {
                throw new LogicException(\sprintf('An option named "%s" already exists.', $negatedName));
            }
            $this->negations[$negatedName] = $option->getName();
        }
    }

    /**
     * Returns an InputOption by name.
     *
     * @throws InvalidArgumentException When option given doesn't exist
     */
    public function getOption(string $name): InputOption
    {
        if (!$this->hasOption($name)) {
            throw new InvalidArgumentException(\sprintf('The "--%s" option does not exist.', $name));
        }

        return $this->options[$name];
    }

    /**
     * Returns true if an InputOption object exists by name.
     *
     * This method can't be used to check if the user included the option when
     * executing the command (use getOption() instead).
     */
    public function hasOption(string $name): bool
    {
        return isset($this->options[$name]);
    }

    /**
     * Gets the array of InputOption objects.
     *
     * @return InputOption[]
     */
    public function getOptions(): array
    {
        return $this->options;
    }

    /**
     * Returns true if an InputOption object exists by shortcut.
     */
    public function hasShortcut(string $name): bool
    {
        return isset($this->shortcuts[$name]);
    }

    /**
     * Returns true if an InputOption object exists by negated name.
     */
    public function hasNegation(string $name): bool
    {
        return isset($this->negations[$name]);
    }

    /**
     * Gets an InputOption by shortcut.
     */
    public function getOptionForShortcut(string $shortcut): InputOption
    {
        return $this->getOption($this->shortcutToName($shortcut));
    }

    /**
     * @return array<string|bool|int|float|array|null>
     */
    public function getOptionDefaults(): array
    {
        $values = [];
        foreach ($this->options as $option) {
            $values[$option->getName()] = $option->getDefault();
        }

        return $values;
    }

    /**
     * Returns the InputOption name given a shortcut.
     *
     * @throws InvalidArgumentException When option given does not exist
     *
     * @internal
     */
    public function shortcutToName(string $shortcut): string
    {
        if (!isset($this->shortcuts[$shortcut])) {
            throw new InvalidArgumentException(\sprintf('The "-%s" option does not exist.', $shortcut));
        }

        return $this->shortcuts[$shortcut];
    }

    /**
     * Returns the InputOption name given a negation.
     *
     * @throws InvalidArgumentException When option given does not exist
     *
     * @internal
     */
    public function negationToName(string $negation): string
    {
        if (!isset($this->negations[$negation])) {
            throw new InvalidArgumentException(\sprintf('The "--%s" option does not exist.', $negation));
        }

        return $this->negations[$negation];
    }

    /**
     * Gets the synopsis.
     */
    public function getSynopsis(bool $short = false): string
    {
        $elements = [];

        if ($short && $this->getOptions()) {
            $elements[] = '[options]';
        } elseif (!$short) {
            foreach ($this->getOptions() as $option) {
                $value = '';
                if ($option->acceptValue()) {
                    $value = \sprintf(
                        ' %s%s%s',
                        $option->isValueOptional() ? '[' : '',
                        strtoupper($option->getName()),
                        $option->isValueOptional() ? ']' : ''
                    );
                }

                $shortcut = $option->getShortcut() ? \sprintf('-%s|', $option->getShortcut()) : '';
                $negation = $option->isNegatable() ? \sprintf('|--no-%s', $option->getName()) : '';
                $elements[] = \sprintf('[%s--%s%s%s]', $shortcut, $option->getName(), $value, $negation);
            }
        }

        if (\count($elements) && $this->getArguments()) {
            $elements[] = '[--]';
        }

        $tail = '';
        foreach ($this->getArguments() as $argument) {
            $element = '<'.$argument->getName().'>';
            if ($argument->isArray()) {
                $element .= '...';
            }

            if (!$argument->isRequired()) {
                $element = '['.$element;
                $tail .= ']';
            }

            $elements[] = $element;
        }

        return implode(' ', $elements).$tail;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;

/**
 * InputInterface is the interface implemented by all input classes.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @method string __toString() Returns a stringified representation of the args passed to the command.
 *                             InputArguments MUST be escaped as well as the InputOption values passed to the command.
 */
interface InputInterface
{
    /**
     * Returns the first argument from the raw parameters (not parsed).
     */
    public function getFirstArgument(): ?string;

    /**
     * Returns true if the raw parameters (not parsed) contain a value.
     *
     * This method is to be used to introspect the input parameters
     * before they have been validated. It must be used carefully.
     * Does not necessarily return the correct result for short options
     * when multiple flags are combined in the same option.
     *
     * @param string|array $values     The values to look for in the raw parameters (can be an array)
     * @param bool         $onlyParams Only check real parameters, skip those following an end of options (--) signal
     */
    public function hasParameterOption(string|array $values, bool $onlyParams = false): bool;

    /**
     * Returns the value of a raw option (not parsed).
     *
     * This method is to be used to introspect the input parameters
     * before they have been validated. It must be used carefully.
     * Does not necessarily return the correct result for short options
     * when multiple flags are combined in the same option.
     *
     * @param string|array                     $values     The value(s) to look for in the raw parameters (can be an array)
     * @param string|bool|int|float|array|null $default    The default value to return if no result is found
     * @param bool                             $onlyParams Only check real parameters, skip those following an end of options (--) signal
     *
     * @return mixed
     */
    public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false);

    /**
     * Binds the current Input instance with the given arguments and options.
     *
     * @return void
     *
     * @throws RuntimeException
     */
    public function bind(InputDefinition $definition);

    /**
     * Validates the input.
     *
     * @return void
     *
     * @throws RuntimeException When not enough arguments are given
     */
    public function validate();

    /**
     * Returns all the given arguments merged with the default values.
     *
     * @return array<string|bool|int|float|array|null>
     */
    public function getArguments(): array;

    /**
     * Returns the argument value for a given argument name.
     *
     * @return mixed
     *
     * @throws InvalidArgumentException When argument given doesn't exist
     */
    public function getArgument(string $name);

    /**
     * Sets an argument value by name.
     *
     * @return void
     *
     * @throws InvalidArgumentException When argument given doesn't exist
     */
    public function setArgument(string $name, mixed $value);

    /**
     * Returns true if an InputArgument object exists by name or position.
     */
    public function hasArgument(string $name): bool;

    /**
     * Returns all the given options merged with the default values.
     *
     * @return array<string|bool|int|float|array|null>
     */
    public function getOptions(): array;

    /**
     * Returns the option value for a given option name.
     *
     * @return mixed
     *
     * @throws InvalidArgumentException When option given doesn't exist
     */
    public function getOption(string $name);

    /**
     * Sets an option value by name.
     *
     * @return void
     *
     * @throws InvalidArgumentException When option given doesn't exist
     */
    public function setOption(string $name, mixed $value);

    /**
     * Returns true if an InputOption object exists by name.
     */
    public function hasOption(string $name): bool;

    /**
     * Is this input means interactive?
     */
    public function isInteractive(): bool;

    /**
     * Sets the input interactivity.
     *
     * @return void
     */
    public function setInteractive(bool $interactive);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\RuntimeException;

/**
 * ArgvInput represents an input coming from the CLI arguments.
 *
 * Usage:
 *
 *     $input = new ArgvInput();
 *
 * By default, the `$_SERVER['argv']` array is used for the input values.
 *
 * This can be overridden by explicitly passing the input values in the constructor:
 *
 *     $input = new ArgvInput($_SERVER['argv']);
 *
 * If you pass it yourself, don't forget that the first element of the array
 * is the name of the running application.
 *
 * When passing an argument to the constructor, be sure that it respects
 * the same rules as the argv one. It's almost always better to use the
 * `StringInput` when you want to provide your own input.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html
 * @see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02
 */
class ArgvInput extends Input
{
    private array $tokens;
    private array $parsed;

    public function __construct(?array $argv = null, ?InputDefinition $definition = null)
    {
        $argv ??= $_SERVER['argv'] ?? [];

        // strip the application name
        array_shift($argv);

        $this->tokens = $argv;

        parent::__construct($definition);
    }

    /**
     * @return void
     */
    protected function setTokens(array $tokens)
    {
        $this->tokens = $tokens;
    }

    /**
     * @return void
     */
    protected function parse()
    {
        $parseOptions = true;
        $this->parsed = $this->tokens;
        while (null !== $token = array_shift($this->parsed)) {
            $parseOptions = $this->parseToken($token, $parseOptions);
        }
    }

    protected function parseToken(string $token, bool $parseOptions): bool
    {
        if ($parseOptions && '' == $token) {
            $this->parseArgument($token);
        } elseif ($parseOptions && '--' == $token) {
            return false;
        } elseif ($parseOptions && str_starts_with($token, '--')) {
            $this->parseLongOption($token);
        } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) {
            $this->parseShortOption($token);
        } else {
            $this->parseArgument($token);
        }

        return $parseOptions;
    }

    /**
     * Parses a short option.
     */
    private function parseShortOption(string $token): void
    {
        $name = substr($token, 1);

        if (\strlen($name) > 1) {
            if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) {
                // an option with a value (with no space)
                $this->addShortOption($name[0], substr($name, 1));
            } else {
                $this->parseShortOptionSet($name);
            }
        } else {
            $this->addShortOption($name, null);
        }
    }

    /**
     * Parses a short option set.
     *
     * @throws RuntimeException When option given doesn't exist
     */
    private function parseShortOptionSet(string $name): void
    {
        $len = \strlen($name);
        for ($i = 0; $i < $len; ++$i) {
            if (!$this->definition->hasShortcut($name[$i])) {
                $encoding = mb_detect_encoding($name, null, true);
                throw new RuntimeException(\sprintf('The "-%s" option does not exist.', false === $encoding ? $name[$i] : mb_substr($name, $i, 1, $encoding)));
            }

            $option = $this->definition->getOptionForShortcut($name[$i]);
            if ($option->acceptValue()) {
                $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1));

                break;
            } else {
                $this->addLongOption($option->getName(), null);
            }
        }
    }

    /**
     * Parses a long option.
     */
    private function parseLongOption(string $token): void
    {
        $name = substr($token, 2);

        if (false !== $pos = strpos($name, '=')) {
            if ('' === $value = substr($name, $pos + 1)) {
                array_unshift($this->parsed, $value);
            }
            $this->addLongOption(substr($name, 0, $pos), $value);
        } else {
            $this->addLongOption($name, null);
        }
    }

    /**
     * Parses an argument.
     *
     * @throws RuntimeException When too many arguments are given
     */
    private function parseArgument(string $token): void
    {
        $c = \count($this->arguments);

        // if input is expecting another argument, add it
        if ($this->definition->hasArgument($c)) {
            $arg = $this->definition->getArgument($c);
            $this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token;

        // if last argument isArray(), append token to last argument
        } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) {
            $arg = $this->definition->getArgument($c - 1);
            $this->arguments[$arg->getName()][] = $token;

        // unexpected argument
        } else {
            $all = $this->definition->getArguments();
            $symfonyCommandName = null;
            if (($inputArgument = $all[$key = array_key_first($all)] ?? null) && 'command' === $inputArgument->getName()) {
                $symfonyCommandName = $this->arguments['command'] ?? null;
                unset($all[$key]);
            }

            if (\count($all)) {
                if ($symfonyCommandName) {
                    $message = \sprintf('Too many arguments to "%s" command, expected arguments "%s".', $symfonyCommandName, implode('" "', array_keys($all)));
                } else {
                    $message = \sprintf('Too many arguments, expected arguments "%s".', implode('" "', array_keys($all)));
                }
            } elseif ($symfonyCommandName) {
                $message = \sprintf('No arguments expected for "%s" command, got "%s".', $symfonyCommandName, $token);
            } else {
                $message = \sprintf('No arguments expected, got "%s".', $token);
            }

            throw new RuntimeException($message);
        }
    }

    /**
     * Adds a short option value.
     *
     * @throws RuntimeException When option given doesn't exist
     */
    private function addShortOption(string $shortcut, mixed $value): void
    {
        if (!$this->definition->hasShortcut($shortcut)) {
            throw new RuntimeException(\sprintf('The "-%s" option does not exist.', $shortcut));
        }

        $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value);
    }

    /**
     * Adds a long option value.
     *
     * @throws RuntimeException When option given doesn't exist
     */
    private function addLongOption(string $name, mixed $value): void
    {
        if (!$this->definition->hasOption($name)) {
            if (!$this->definition->hasNegation($name)) {
                throw new RuntimeException(\sprintf('The "--%s" option does not exist.', $name));
            }

            $optionName = $this->definition->negationToName($name);
            if (null !== $value) {
                throw new RuntimeException(\sprintf('The "--%s" option does not accept a value.', $name));
            }
            $this->options[$optionName] = false;

            return;
        }

        $option = $this->definition->getOption($name);

        if (null !== $value && !$option->acceptValue()) {
            throw new RuntimeException(\sprintf('The "--%s" option does not accept a value.', $name));
        }

        if (\in_array($value, ['', null], true) && $option->acceptValue() && \count($this->parsed)) {
            // if option accepts an optional or mandatory argument
            // let's see if there is one provided
            $next = array_shift($this->parsed);
            if ((isset($next[0]) && '-' !== $next[0]) || \in_array($next, ['', null], true)) {
                $value = $next;
            } else {
                array_unshift($this->parsed, $next);
            }
        }

        if (null === $value) {
            if ($option->isValueRequired()) {
                throw new RuntimeException(\sprintf('The "--%s" option requires a value.', $name));
            }

            if (!$option->isArray() && !$option->isValueOptional()) {
                $value = true;
            }
        }

        if ($option->isArray()) {
            $this->options[$name][] = $value;
        } else {
            $this->options[$name] = $value;
        }
    }

    public function getFirstArgument(): ?string
    {
        $isOption = false;
        foreach ($this->tokens as $i => $token) {
            if ($token && '-' === $token[0]) {
                if (str_contains($token, '=') || !isset($this->tokens[$i + 1])) {
                    continue;
                }

                // If it's a long option, consider that everything after "--" is the option name.
                // Otherwise, use the last char (if it's a short option set, only the last one can take a value with space separator)
                $name = '-' === $token[1] ? substr($token, 2) : substr($token, -1);
                if (!isset($this->options[$name]) && !$this->definition->hasShortcut($name)) {
                    // noop
                } elseif ((isset($this->options[$name]) || isset($this->options[$name = $this->definition->shortcutToName($name)])) && $this->tokens[$i + 1] === $this->options[$name]) {
                    $isOption = true;
                }

                continue;
            }

            if ($isOption) {
                $isOption = false;
                continue;
            }

            return $token;
        }

        return null;
    }

    public function hasParameterOption(string|array $values, bool $onlyParams = false): bool
    {
        $values = (array) $values;

        foreach ($this->tokens as $token) {
            if ($onlyParams && '--' === $token) {
                return false;
            }
            foreach ($values as $value) {
                // Options with values:
                //   For long options, test for '--option=' at beginning
                //   For short options, test for '-o' at beginning
                $leading = str_starts_with($value, '--') ? $value.'=' : $value;
                if ($token === $value || '' !== $leading && str_starts_with($token, $leading)) {
                    return true;
                }
            }
        }

        return false;
    }

    public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false): mixed
    {
        $values = (array) $values;
        $tokens = $this->tokens;

        while (0 < \count($tokens)) {
            $token = array_shift($tokens);
            if ($onlyParams && '--' === $token) {
                return $default;
            }

            foreach ($values as $value) {
                if ($token === $value) {
                    return array_shift($tokens);
                }
                // Options with values:
                //   For long options, test for '--option=' at beginning
                //   For short options, test for '-o' at beginning
                $leading = str_starts_with($value, '--') ? $value.'=' : $value;
                if ('' !== $leading && str_starts_with($token, $leading)) {
                    return substr($token, \strlen($leading));
                }
            }
        }

        return $default;
    }

    /**
     * Returns a stringified representation of the args passed to the command.
     */
    public function __toString(): string
    {
        $tokens = array_map(function ($token) {
            if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) {
                return $match[1].$this->escapeToken($match[2]);
            }

            if ($token && '-' !== $token[0]) {
                return $this->escapeToken($token);
            }

            return $token;
        }, $this->tokens);

        return implode(' ', $tokens);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\InvalidOptionException;

/**
 * ArrayInput represents an input provided as an array.
 *
 * Usage:
 *
 *     $input = new ArrayInput(['command' => 'foo:bar', 'foo' => 'bar', '--bar' => 'foobar']);
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ArrayInput extends Input
{
    private array $parameters;

    public function __construct(array $parameters, ?InputDefinition $definition = null)
    {
        $this->parameters = $parameters;

        parent::__construct($definition);
    }

    public function getFirstArgument(): ?string
    {
        foreach ($this->parameters as $param => $value) {
            if ($param && \is_string($param) && '-' === $param[0]) {
                continue;
            }

            return $value;
        }

        return null;
    }

    public function hasParameterOption(string|array $values, bool $onlyParams = false): bool
    {
        $values = (array) $values;

        foreach ($this->parameters as $k => $v) {
            if (!\is_int($k)) {
                $v = $k;
            }

            if ($onlyParams && '--' === $v) {
                return false;
            }

            if (\in_array($v, $values)) {
                return true;
            }
        }

        return false;
    }

    public function getParameterOption(string|array $values, string|bool|int|float|array|null $default = false, bool $onlyParams = false): mixed
    {
        $values = (array) $values;

        foreach ($this->parameters as $k => $v) {
            if ($onlyParams && ('--' === $k || (\is_int($k) && '--' === $v))) {
                return $default;
            }

            if (\is_int($k)) {
                if (\in_array($v, $values)) {
                    return true;
                }
            } elseif (\in_array($k, $values)) {
                return $v;
            }
        }

        return $default;
    }

    /**
     * Returns a stringified representation of the args passed to the command.
     */
    public function __toString(): string
    {
        $params = [];
        foreach ($this->parameters as $param => $val) {
            if ($param && \is_string($param) && '-' === $param[0]) {
                $glue = ('-' === $param[1]) ? '=' : ' ';
                if (\is_array($val)) {
                    foreach ($val as $v) {
                        $params[] = $param.('' != $v ? $glue.$this->escapeToken($v) : '');
                    }
                } else {
                    $params[] = $param.('' != $val ? $glue.$this->escapeToken($val) : '');
                }
            } else {
                $params[] = \is_array($val) ? implode(' ', array_map($this->escapeToken(...), $val)) : $this->escapeToken($val);
            }
        }

        return implode(' ', $params);
    }

    /**
     * @return void
     */
    protected function parse()
    {
        foreach ($this->parameters as $key => $value) {
            if ('--' === $key) {
                return;
            }
            if (str_starts_with($key, '--')) {
                $this->addLongOption(substr($key, 2), $value);
            } elseif (str_starts_with($key, '-')) {
                $this->addShortOption(substr($key, 1), $value);
            } else {
                $this->addArgument($key, $value);
            }
        }
    }

    /**
     * Adds a short option value.
     *
     * @throws InvalidOptionException When option given doesn't exist
     */
    private function addShortOption(string $shortcut, mixed $value): void
    {
        if (!$this->definition->hasShortcut($shortcut)) {
            throw new InvalidOptionException(\sprintf('The "-%s" option does not exist.', $shortcut));
        }

        $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value);
    }

    /**
     * Adds a long option value.
     *
     * @throws InvalidOptionException When option given doesn't exist
     * @throws InvalidOptionException When a required value is missing
     */
    private function addLongOption(string $name, mixed $value): void
    {
        if (!$this->definition->hasOption($name)) {
            if (!$this->definition->hasNegation($name)) {
                throw new InvalidOptionException(\sprintf('The "--%s" option does not exist.', $name));
            }

            $optionName = $this->definition->negationToName($name);
            $this->options[$optionName] = false;

            return;
        }

        $option = $this->definition->getOption($name);

        if (null === $value) {
            if ($option->isValueRequired()) {
                throw new InvalidOptionException(\sprintf('The "--%s" option requires a value.', $name));
            }

            if (!$option->isValueOptional()) {
                $value = true;
            }
        }

        $this->options[$name] = $value;
    }

    /**
     * Adds an argument value.
     *
     * @throws InvalidArgumentException When argument given doesn't exist
     */
    private function addArgument(string|int $name, mixed $value): void
    {
        if (!$this->definition->hasArgument($name)) {
            throw new InvalidArgumentException(\sprintf('The "%s" argument does not exist.', $name));
        }

        $this->arguments[$name] = $value;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Completion\Suggestion;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;

/**
 * Represents a command line option.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class InputOption
{
    /**
     * Do not accept input for the option (e.g. --yell). This is the default behavior of options.
     */
    public const VALUE_NONE = 1;

    /**
     * A value must be passed when the option is used (e.g. --iterations=5 or -i5).
     */
    public const VALUE_REQUIRED = 2;

    /**
     * The option may or may not have a value (e.g. --yell or --yell=loud).
     */
    public const VALUE_OPTIONAL = 4;

    /**
     * The option accepts multiple values (e.g. --dir=/foo --dir=/bar).
     */
    public const VALUE_IS_ARRAY = 8;

    /**
     * The option may have either positive or negative value (e.g. --ansi or --no-ansi).
     */
    public const VALUE_NEGATABLE = 16;

    private string $name;
    private string|array|null $shortcut;
    private int $mode;
    private string|int|bool|array|float|null $default;
    private array|\Closure $suggestedValues;
    private string $description;

    /**
     * @param string|array|null                                                             $shortcut        The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
     * @param int|null                                                                      $mode            The option mode: One of the VALUE_* constants
     * @param string|bool|int|float|array|null                                              $default         The default value (must be null for self::VALUE_NONE)
     * @param array|\Closure(CompletionInput,CompletionSuggestions):list<string|Suggestion> $suggestedValues The values used for input completion
     *
     * @throws InvalidArgumentException If option mode is invalid or incompatible
     */
    public function __construct(string $name, string|array|null $shortcut = null, ?int $mode = null, string $description = '', string|bool|int|float|array|null $default = null, array|\Closure $suggestedValues = [])
    {
        if (str_starts_with($name, '--')) {
            $name = substr($name, 2);
        }

        if (empty($name)) {
            throw new InvalidArgumentException('An option name cannot be empty.');
        }

        if ('' === $shortcut || [] === $shortcut || false === $shortcut) {
            $shortcut = null;
        }

        if (null !== $shortcut) {
            if (\is_array($shortcut)) {
                $shortcut = implode('|', $shortcut);
            }
            $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-'));
            $shortcuts = array_filter($shortcuts, 'strlen');
            $shortcut = implode('|', $shortcuts);

            if ('' === $shortcut) {
                throw new InvalidArgumentException('An option shortcut cannot be empty.');
            }
        }

        if (null === $mode) {
            $mode = self::VALUE_NONE;
        } elseif ($mode >= (self::VALUE_NEGATABLE << 1) || $mode < 1) {
            throw new InvalidArgumentException(\sprintf('Option mode "%s" is not valid.', $mode));
        }

        $this->name = $name;
        $this->shortcut = $shortcut;
        $this->mode = $mode;
        $this->description = $description;
        $this->suggestedValues = $suggestedValues;

        if ($suggestedValues && !$this->acceptValue()) {
            throw new LogicException('Cannot set suggested values if the option does not accept a value.');
        }
        if ($this->isArray() && !$this->acceptValue()) {
            throw new InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.');
        }
        if ($this->isNegatable() && $this->acceptValue()) {
            throw new InvalidArgumentException('Impossible to have an option mode VALUE_NEGATABLE if the option also accepts a value.');
        }

        $this->setDefault($default);
    }

    /**
     * Returns the option shortcut.
     */
    public function getShortcut(): ?string
    {
        return $this->shortcut;
    }

    /**
     * Returns the option name.
     */
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * Returns true if the option accepts a value.
     *
     * @return bool true if value mode is not self::VALUE_NONE, false otherwise
     */
    public function acceptValue(): bool
    {
        return $this->isValueRequired() || $this->isValueOptional();
    }

    /**
     * Returns true if the option requires a value.
     *
     * @return bool true if value mode is self::VALUE_REQUIRED, false otherwise
     */
    public function isValueRequired(): bool
    {
        return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode);
    }

    /**
     * Returns true if the option takes an optional value.
     *
     * @return bool true if value mode is self::VALUE_OPTIONAL, false otherwise
     */
    public function isValueOptional(): bool
    {
        return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode);
    }

    /**
     * Returns true if the option can take multiple values.
     *
     * @return bool true if mode is self::VALUE_IS_ARRAY, false otherwise
     */
    public function isArray(): bool
    {
        return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode);
    }

    public function isNegatable(): bool
    {
        return self::VALUE_NEGATABLE === (self::VALUE_NEGATABLE & $this->mode);
    }

    /**
     * @return void
     */
    public function setDefault(string|bool|int|float|array|null $default = null)
    {
        if (1 > \func_num_args()) {
            trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
        }
        if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) {
            throw new LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.');
        }

        if ($this->isArray()) {
            if (null === $default) {
                $default = [];
            } elseif (!\is_array($default)) {
                throw new LogicException('A default value for an array option must be an array.');
            }
        }

        $this->default = $this->acceptValue() || $this->isNegatable() ? $default : false;
    }

    /**
     * Returns the default value.
     */
    public function getDefault(): string|bool|int|float|array|null
    {
        return $this->default;
    }

    /**
     * Returns the description text.
     */
    public function getDescription(): string
    {
        return $this->description;
    }

    public function hasCompletion(): bool
    {
        return [] !== $this->suggestedValues;
    }

    /**
     * Adds suggestions to $suggestions for the current completion input.
     *
     * @see Command::complete()
     */
    public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
    {
        $values = $this->suggestedValues;
        if ($values instanceof \Closure && !\is_array($values = $values($input))) {
            throw new LogicException(\sprintf('Closure for option "%s" must return an array. Got "%s".', $this->name, get_debug_type($values)));
        }
        if ($values) {
            $suggestions->suggestValues($values);
        }
    }

    /**
     * Checks whether the given option equals this one.
     */
    public function equals(self $option): bool
    {
        return $option->getName() === $this->getName()
            && $option->getShortcut() === $this->getShortcut()
            && $option->getDefault() === $this->getDefault()
            && $option->isNegatable() === $this->isNegatable()
            && $option->isArray() === $this->isArray()
            && $option->isValueRequired() === $this->isValueRequired()
            && $option->isValueOptional() === $this->isValueOptional()
        ;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * StringInput represents an input provided as a string.
 *
 * Usage:
 *
 *     $input = new StringInput('foo --bar="foobar"');
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class StringInput extends ArgvInput
{
    /**
     * @deprecated since Symfony 6.1
     */
    public const REGEX_STRING = '([^\s]+?)(?:\s|(?<!\\\\)"|(?<!\\\\)\'|$)';
    public const REGEX_UNQUOTED_STRING = '([^\s\\\\]+?)';
    public const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')';

    /**
     * @param string $input A string representing the parameters from the CLI
     */
    public function __construct(string $input)
    {
        parent::__construct([]);

        $this->setTokens($this->tokenize($input));
    }

    /**
     * Tokenizes a string.
     *
     * @throws InvalidArgumentException When unable to parse input (should never happen)
     */
    private function tokenize(string $input): array
    {
        $tokens = [];
        $length = \strlen($input);
        $cursor = 0;
        $token = null;
        while ($cursor < $length) {
            if ('\\' === $input[$cursor]) {
                $token .= $input[++$cursor] ?? '';
                ++$cursor;
                continue;
            }

            if (preg_match('/\s+/A', $input, $match, 0, $cursor)) {
                if (null !== $token) {
                    $tokens[] = $token;
                    $token = null;
                }
            } elseif (preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, 0, $cursor)) {
                $token .= $match[1].$match[2].stripcslashes(str_replace(['"\'', '\'"', '\'\'', '""'], '', substr($match[3], 1, -1)));
            } elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, 0, $cursor)) {
                $token .= stripcslashes(substr($match[0], 1, -1));
            } elseif (preg_match('/'.self::REGEX_UNQUOTED_STRING.'/A', $input, $match, 0, $cursor)) {
                $token .= $match[1];
            } else {
                // should never happen
                throw new InvalidArgumentException(\sprintf('Unable to parse input near "... %s ...".', substr($input, $cursor, 10)));
            }

            $cursor += \strlen($match[0]);
        }

        if (null !== $token) {
            $tokens[] = $token;
        }

        return $tokens;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Tester;

use PHPUnit\Framework\Assert;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\StreamOutput;
use Symfony\Component\Console\Tester\Constraint\CommandIsSuccessful;

/**
 * @author Amrouche Hamza <hamza.simperfit@gmail.com>
 */
trait TesterTrait
{
    private StreamOutput $output;
    private array $inputs = [];
    private bool $captureStreamsIndependently = false;
    private InputInterface $input;
    private int $statusCode;

    /**
     * Gets the display returned by the last execution of the command or application.
     *
     * @throws \RuntimeException If it's called before the execute method
     */
    public function getDisplay(bool $normalize = false): string
    {
        if (!isset($this->output)) {
            throw new \RuntimeException('Output not initialized, did you execute the command before requesting the display?');
        }

        rewind($this->output->getStream());

        $display = stream_get_contents($this->output->getStream());

        if ($normalize) {
            $display = str_replace(\PHP_EOL, "\n", $display);
        }

        return $display;
    }

    /**
     * Gets the output written to STDERR by the application.
     *
     * @param bool $normalize Whether to normalize end of lines to \n or not
     */
    public function getErrorOutput(bool $normalize = false): string
    {
        if (!$this->captureStreamsIndependently) {
            throw new \LogicException('The error output is not available when the tester is run without "capture_stderr_separately" option set.');
        }

        rewind($this->output->getErrorOutput()->getStream());

        $display = stream_get_contents($this->output->getErrorOutput()->getStream());

        if ($normalize) {
            $display = str_replace(\PHP_EOL, "\n", $display);
        }

        return $display;
    }

    /**
     * Gets the input instance used by the last execution of the command or application.
     */
    public function getInput(): InputInterface
    {
        return $this->input;
    }

    /**
     * Gets the output instance used by the last execution of the command or application.
     */
    public function getOutput(): OutputInterface
    {
        return $this->output;
    }

    /**
     * Gets the status code returned by the last execution of the command or application.
     *
     * @throws \RuntimeException If it's called before the execute method
     */
    public function getStatusCode(): int
    {
        return $this->statusCode ?? throw new \RuntimeException('Status code not initialized, did you execute the command before requesting the status code?');
    }

    public function assertCommandIsSuccessful(string $message = ''): void
    {
        Assert::assertThat($this->statusCode, new CommandIsSuccessful(), $message);
    }

    /**
     * Sets the user inputs.
     *
     * @param array $inputs An array of strings representing each input
     *                      passed to the command input stream
     *
     * @return $this
     */
    public function setInputs(array $inputs): static
    {
        $this->inputs = $inputs;

        return $this;
    }

    /**
     * Initializes the output property.
     *
     * Available options:
     *
     *  * decorated:                 Sets the output decorated flag
     *  * verbosity:                 Sets the output verbosity flag
     *  * capture_stderr_separately: Make output of stdOut and stdErr separately available
     */
    private function initOutput(array $options): void
    {
        $this->captureStreamsIndependently = $options['capture_stderr_separately'] ?? false;
        if (!$this->captureStreamsIndependently) {
            $this->output = new StreamOutput(fopen('php://memory', 'w', false));
            if (isset($options['decorated'])) {
                $this->output->setDecorated($options['decorated']);
            }
            if (isset($options['verbosity'])) {
                $this->output->setVerbosity($options['verbosity']);
            }
        } else {
            $this->output = new ConsoleOutput(
                $options['verbosity'] ?? ConsoleOutput::VERBOSITY_NORMAL,
                $options['decorated'] ?? null
            );

            $errorOutput = new StreamOutput(fopen('php://memory', 'w', false));
            $errorOutput->setFormatter($this->output->getFormatter());
            $errorOutput->setVerbosity($this->output->getVerbosity());
            $errorOutput->setDecorated($this->output->isDecorated());

            $reflectedOutput = new \ReflectionObject($this->output);
            $strErrProperty = $reflectedOutput->getProperty('stderr');
            $strErrProperty->setValue($this->output, $errorOutput);

            $reflectedParent = $reflectedOutput->getParentClass();
            $streamProperty = $reflectedParent->getProperty('stream');
            $streamProperty->setValue($this->output, fopen('php://memory', 'w', false));
        }
    }

    /**
     * @return resource
     */
    private static function createStream(array $inputs)
    {
        $stream = fopen('php://memory', 'r+', false);

        foreach ($inputs as $input) {
            fwrite($stream, $input.\PHP_EOL);

            if (str_contains($input, \PHP_EOL)) {
                fwrite($stream, "\x4");
            }
        }

        rewind($stream);

        return $stream;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Tester;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\ArrayInput;

/**
 * Eases the testing of console applications.
 *
 * When testing an application, don't forget to disable the auto exit flag:
 *
 *     $application = new Application();
 *     $application->setAutoExit(false);
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ApplicationTester
{
    use TesterTrait;

    private Application $application;

    public function __construct(Application $application)
    {
        $this->application = $application;
    }

    /**
     * Executes the application.
     *
     * Available options:
     *
     *  * interactive:               Sets the input interactive flag
     *  * decorated:                 Sets the output decorated flag
     *  * verbosity:                 Sets the output verbosity flag
     *  * capture_stderr_separately: Make output of stdOut and stdErr separately available
     *
     * @return int The command exit code
     */
    public function run(array $input, array $options = []): int
    {
        $prevShellVerbosity = getenv('SHELL_VERBOSITY');

        try {
            $this->input = new ArrayInput($input);
            if (isset($options['interactive'])) {
                $this->input->setInteractive($options['interactive']);
            }

            if ($this->inputs) {
                $this->input->setStream(self::createStream($this->inputs));
            }

            $this->initOutput($options);

            return $this->statusCode = $this->application->run($this->input, $this->output);
        } finally {
            // SHELL_VERBOSITY is set by Application::configureIO so we need to unset/reset it
            // to its previous value to avoid one test's verbosity to spread to the following tests
            if (false === $prevShellVerbosity) {
                if (\function_exists('putenv')) {
                    @putenv('SHELL_VERBOSITY');
                }
                unset($_ENV['SHELL_VERBOSITY']);
                unset($_SERVER['SHELL_VERBOSITY']);
            } else {
                if (\function_exists('putenv')) {
                    @putenv('SHELL_VERBOSITY='.$prevShellVerbosity);
                }
                $_ENV['SHELL_VERBOSITY'] = $prevShellVerbosity;
                $_SERVER['SHELL_VERBOSITY'] = $prevShellVerbosity;
            }
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Tester;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;

/**
 * Eases the testing of console commands.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Robin Chalas <robin.chalas@gmail.com>
 */
class CommandTester
{
    use TesterTrait;

    private Command $command;

    public function __construct(Command $command)
    {
        $this->command = $command;
    }

    /**
     * Executes the command.
     *
     * Available execution options:
     *
     *  * interactive:               Sets the input interactive flag
     *  * decorated:                 Sets the output decorated flag
     *  * verbosity:                 Sets the output verbosity flag
     *  * capture_stderr_separately: Make output of stdOut and stdErr separately available
     *
     * @param array $input   An array of command arguments and options
     * @param array $options An array of execution options
     *
     * @return int The command exit code
     */
    public function execute(array $input, array $options = []): int
    {
        // set the command name automatically if the application requires
        // this argument and no command name was passed
        if (!isset($input['command'])
            && (null !== $application = $this->command->getApplication())
            && $application->getDefinition()->hasArgument('command')
        ) {
            $input = array_merge(['command' => $this->command->getName()], $input);
        }

        $this->input = new ArrayInput($input);
        // Use an in-memory input stream even if no inputs are set so that QuestionHelper::ask() does not rely on the blocking STDIN.
        $this->input->setStream(self::createStream($this->inputs));

        if (isset($options['interactive'])) {
            $this->input->setInteractive($options['interactive']);
        }

        if (!isset($options['decorated'])) {
            $options['decorated'] = false;
        }

        $this->initOutput($options);

        return $this->statusCode = $this->command->run($this->input, $this->output);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Tester\Constraint;

use PHPUnit\Framework\Constraint\Constraint;
use Symfony\Component\Console\Command\Command;

final class CommandIsSuccessful extends Constraint
{
    public function toString(): string
    {
        return 'is successful';
    }

    protected function matches($other): bool
    {
        return Command::SUCCESS === $other;
    }

    protected function failureDescription($other): string
    {
        return 'the command '.$this->toString();
    }

    protected function additionalFailureDescription($other): string
    {
        $mapping = [
            Command::FAILURE => 'Command failed.',
            Command::INVALID => 'Command was invalid.',
        ];

        return $mapping[$other] ?? \sprintf('Command returned exit status %d.', $other);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Tester;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;

/**
 * Eases the testing of command completion.
 *
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class CommandCompletionTester
{
    private Command $command;

    public function __construct(Command $command)
    {
        $this->command = $command;
    }

    /**
     * Create completion suggestions from input tokens.
     */
    public function complete(array $input): array
    {
        $currentIndex = \count($input);
        if ('' === end($input)) {
            array_pop($input);
        }
        array_unshift($input, $this->command->getName());

        $completionInput = CompletionInput::fromTokens($input, $currentIndex);
        $completionInput->bind($this->command->getDefinition());
        $suggestions = new CompletionSuggestions();

        $this->command->complete($completionInput, $suggestions);

        $options = [];
        foreach ($suggestions->getOptionSuggestions() as $option) {
            $options[] = '--'.$option->getName();
        }

        return array_map('strval', array_merge($options, $suggestions->getValueSuggestions()));
    }
}
# This file is part of the Symfony package.
#
# (c) Fabien Potencier <fabien@symfony.com>
#
# For the full copyright and license information, please view
# https://symfony.com/doc/current/contributing/code/license.html

_sf_{{ COMMAND_NAME }}() {

    # Use the default completion for shell redirect operators.
    for w in '>' '>>' '&>' '<'; do
        if [[ $w = "${COMP_WORDS[COMP_CWORD-1]}" ]]; then
            compopt -o filenames
            COMPREPLY=($(compgen -f -- "${COMP_WORDS[COMP_CWORD]}"))
            return 0
        fi
    done

    # Use newline as only separator to allow space in completion values
    local IFS=$'\n'
    local sf_cmd="${COMP_WORDS[0]}"

    # for an alias, get the real script behind it
    sf_cmd_type=$(type -t $sf_cmd)
    if [[ $sf_cmd_type == "alias" ]]; then
        sf_cmd=$(alias $sf_cmd | sed -E "s/alias $sf_cmd='(.*)'/\1/")
    elif [[ $sf_cmd_type == "file" ]]; then
        sf_cmd=$(type -p $sf_cmd)
    fi

    if [[ $sf_cmd_type != "function" && ! -x $sf_cmd ]]; then
        return 1
    fi

    local cur prev words cword
    _get_comp_words_by_ref -n := cur prev words cword

    local completecmd=("$sf_cmd" "_complete" "--no-interaction" "-sbash" "-c$cword" "-a{{ VERSION }}")
    for w in ${words[@]}; do
        w=$(printf -- '%b' "$w")
        # remove quotes from typed values
        quote="${w:0:1}"
        if [ "$quote" == \' ]; then
            w="${w%\'}"
            w="${w#\'}"
        elif [ "$quote" == \" ]; then
            w="${w%\"}"
            w="${w#\"}"
        fi
        # empty values are ignored
        if [ ! -z "$w" ]; then
            completecmd+=("-i$w")
        fi
    done

    local sfcomplete
    if sfcomplete=$(${completecmd[@]} 2>&1); then
        local quote suggestions
        quote=${cur:0:1}

        # Use single quotes by default if suggestions contains backslash (FQCN)
        if [ "$quote" == '' ] && [[ "$sfcomplete" =~ \\ ]]; then
            quote=\'
        fi

        if [ "$quote" == \' ]; then
            # single quotes: no additional escaping (does not accept ' in values)
            suggestions=$(for s in $sfcomplete; do printf $'%q%q%q\n' "$quote" "$s" "$quote"; done)
        elif [ "$quote" == \" ]; then
            # double quotes: double escaping for \ $ ` "
            suggestions=$(for s in $sfcomplete; do
                s=${s//\\/\\\\}
                s=${s//\$/\\\$}
                s=${s//\`/\\\`}
                s=${s//\"/\\\"}
                printf $'%q%q%q\n' "$quote" "$s" "$quote";
            done)
        else
            # no quotes: double escaping
            suggestions=$(for s in $sfcomplete; do printf $'%q\n' $(printf '%q' "$s"); done)
        fi
        COMPREPLY=($(IFS=$'\n' compgen -W "$suggestions" -- $(printf -- "%q" "$cur")))
        __ltrim_colon_completions "$cur"
    else
        if [[ "$sfcomplete" != *"Command \"_complete\" is not defined."* ]]; then
            >&2 echo
            >&2 echo $sfcomplete
        fi

        return 1
    fi
}

complete -F _sf_{{ COMMAND_NAME }} {{ COMMAND_NAME }}
# This file is part of the Symfony package.
#
# (c) Fabien Potencier <fabien@symfony.com>
#
# For the full copyright and license information, please view
# https://symfony.com/doc/current/contributing/code/license.html

function _sf_{{ COMMAND_NAME }}
    set sf_cmd (commandline -o)
    set c (count (commandline -oc))

    set completecmd "$sf_cmd[1]" "_complete" "--no-interaction" "-sfish" "-a{{ VERSION }}"

    for i in $sf_cmd
        if [ $i != "" ]
            set completecmd $completecmd "-i$i"
        end
    end

    set completecmd $completecmd "-c$c"

    set sfcomplete ($completecmd)

    for i in $sfcomplete
        echo $i
    end
end

complete -c '{{ COMMAND_NAME }}' -a '(_sf_{{ COMMAND_NAME }})' -f
#compdef {{ COMMAND_NAME }}

# This file is part of the Symfony package.
#
# (c) Fabien Potencier <fabien@symfony.com>
#
# For the full copyright and license information, please view
# https://symfony.com/doc/current/contributing/code/license.html

#
# zsh completions for {{ COMMAND_NAME }}
#
# References:
#   - https://github.com/spf13/cobra/blob/master/zsh_completions.go
#   - https://github.com/symfony/symfony/blob/5.4/src/Symfony/Component/Console/Resources/completion.bash
#
_sf_{{ COMMAND_NAME }}() {
    local lastParam flagPrefix requestComp out comp
    local -a completions

    # The user could have moved the cursor backwards on the command-line.
    # We need to trigger completion from the $CURRENT location, so we need
    # to truncate the command-line ($words) up to the $CURRENT location.
    # (We cannot use $CURSOR as its value does not work when a command is an alias.)
    words=("${=words[1,CURRENT]}") lastParam=${words[-1]}

    # For zsh, when completing a flag with an = (e.g., {{ COMMAND_NAME }} -n=<TAB>)
    # completions must be prefixed with the flag
    setopt local_options BASH_REMATCH
    if [[ "${lastParam}" =~ '-.*=' ]]; then
        # We are dealing with a flag with an =
        flagPrefix="-P ${BASH_REMATCH}"
    fi

    # Prepare the command to obtain completions
    requestComp="${words[0]} ${words[1]} _complete --no-interaction -szsh -a{{ VERSION }} -c$((CURRENT-1))" i=""
    for w in ${words[@]}; do
        w=$(printf -- '%b' "$w")
        # remove quotes from typed values
        quote="${w:0:1}"
        if [ "$quote" = \' ]; then
            w="${w%\'}"
            w="${w#\'}"
        elif [ "$quote" = \" ]; then
            w="${w%\"}"
            w="${w#\"}"
        fi
        # empty values are ignored
        if [ ! -z "$w" ]; then
            i="${i}-i${w} "
        fi
    done

    # Ensure at least 1 input
    if [ "${i}" = "" ]; then
        requestComp="${requestComp} -i\" \""
    else
        requestComp="${requestComp} ${i}"
    fi

    # Use eval to handle any environment variables and such
    out=$(eval ${requestComp} 2>/dev/null)

    while IFS='\n' read -r comp; do
        if [ -n "$comp" ]; then
            # If requested, completions are returned with a description.
            # The description is preceded by a TAB character.
            # For zsh's _describe, we need to use a : instead of a TAB.
            # We first need to escape any : as part of the completion itself.
            comp=${comp//:/\\:}
            local tab=$(printf '\t')
            comp=${comp//$tab/:}
            completions+=${comp}
        fi
    done < <(printf "%s\n" "${out[@]}")

    # Let inbuilt _describe handle completions
    eval _describe "completions" completions $flagPrefix
    return $?
}

compdef _sf_{{ COMMAND_NAME }} {{ COMMAND_NAME }}
MZ                @                                       	!L!This program cannot be run in DOS mode.
$       ,;B;B;B2מ:B2-B2ƞ9B2ў?Ba98B;CB2Ȟ:B2֞:B2Ӟ:BRich;B        PE  L MoO         	  
         8           @                      `     ?   @                           "  P    @                      P  p   !                             8!  @                                          .text   	      
                    `.rdata  	       
                 @  @.data      0                    @  .rsrc       @                    @  @.reloc     P      "              @  B                                                                                                                                                                                                                                                                                                                                                        j$@ x  j @ e EPV  @ EЃPV @ MX @ e EP5H @ L @ YY5\ @ EP5` @ D @ YYP @ MMT @ 3H  ; 0@ u  h@   l3@ $40@ 5h3@ 40@ h$0@ h(0@ h 0@  @ 00@ }j  Yjh"@   3ۉ]d   p]俀3@ SVW0 @ ;t;u3Fuh  4 @ 3F|3@ ;u
j\  Y;|3@ u,5|3@ h @ h @   YYtE      5<0@ |3@ ;uh @ h @ l  YY|3@    9]uSW8 @ 93@ th3@   Yt
SjS3@ $0@  @ 5$0@ 5(0@ 5 0@ 80@ 9,0@ u7P @ E	MPQ  YYËeE80@ 39,0@ uPh @ 9<0@ u @ E80@   øMZ  f9  @ t3M< @   @ 8PE  uH  t  uՃ   v39   xtv39   j,0@ p @ jl @ YY3@ 3@  @ t3@  @ p3@  @  x3@ V    =0@  uh@  @ Yg  =0@ u	j @ Y3{  U(  H1@ D1@ @1@ <1@ 581@ =41@ f`1@ fT1@ f01@ f,1@ f%(1@ f-$1@ X1@ E L1@ EP1@ E\1@ 0@   P1@ L0@ @0@ 	 D0@     0@ 0@  @ 0@ j?  Yj   @ h!@ $ @ =0@  uj  Yh	 ( @ P, @ ËUE 8csmu*xu$@= t=!t="t= @u  3] hH@   @ 3% @ jh("@ b  53@ 5 @ YEuu @ Ygj  Ye 53@ ։E53@ YYEEPEPu5l @ YPU  Eu֣3@ uփ3@ E	   E  j  YËUuNYH]ËV!@ !@ W;stЃ;r_^ËV"@ "@ W;stЃ;r_^% @ ̋UMMZ  f9t3]ËA<8PE  u3ҹ  f9H]̋UEH<ASVq3WDv}H;r	X;r
B(;r3_^[]̋UjhH"@ he@ d    PSVW 0@ 1E3PEd    eE    h  @ *tUE-  @ Ph  @ Pt;@$ЃEMd    Y_^[]ËE3=  ËeE3Md    Y_^[]% @ % @ he@ d5    D$l$l$+SVW 0@ 1E3PeuEEEEd    ËMd    Y__^[]QËUuuuuh@ h 0@    ]ËVh   h   3V   tVVVVV   ^3ËU 0@ e e SWN@  ;tt	У0@ `VEP< @ u3u @ 3 @ 3 @ 3EP @ E3E3;uO@u5 0@ ։50@ ^_[%t @ %x @ %| @ % @ % @ % @ % @ % @ % @ Pd5    D$+d$SVW( 0@ 3PEuEEd    ËMd    Y__^[]QËM3M%T @ T$BJ3J3l"@ s                                                                                                                                                                                                                                                     #  #  #  )  r)  b)  H)  4)  )  (  (  (  (  (  (  )      #  $  %  %  &  d&  &  $      ('  '  '  '  '  (  ((  6(  '  H(  Z(  t(  (  '  '   '  '  '  l'  ^'  R'  F'  >'  >(  0'  '  )          @         W@ @                     MoO       l   !    @0@ 0@ bad allocation      H                                                            0@ !@    RSDSьJ!LZ    c:\users\seld\documents\visual studio 2010\Projects\hiddeninp\Release\hiddeninp.pdb     e                            @ @                 :@             @ @ @ "   d"@                        "          #      $#          &  D   H#          (  h                       #  #  #  )  r)  b)  H)  4)  )  (  (  (  (  (  (  )      #  $  %  %  &  d&  &  $      ('  '  '  '  '  (  ((  6(  '  H(  Z(  t(  (  '  '   '  '  '  l'  ^'  R'  F'  >'  >(  0'  '  )      GetConsoleMode  SetConsoleMode  ;GetStdHandle  KERNEL32.dll   ??$?6DU?$char_traits@D@std@@V?$allocator@D@1@@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@0@AAV10@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@0@@Z ?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A  J?cin@std@@3V?$basic_istream@DU?$char_traits@D@std@@@1@A  ??$getline@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@YAAAV?$basic_istream@DU?$char_traits@D@std@@@0@AAV10@AAV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@0@@Z ??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@P6AAAV01@AAV01@@Z@Z  _??1?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAE@XZ  {??0?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAE@XZ  ?endl@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@1@AAV21@@Z  MSVCP90.dll _amsg_exit   __getmainargs ,_cexit  |_exit f _XcptFilter exit   __initenv _initterm _initterm_e <_configthreadlocale  __setusermatherr  _adjust_fdiv   __p__commode   __p__fmode  j_encode_pointer  __set_app_type  K_crt_debugger_hook  C ?terminate@@YAXXZ MSVCR90.dll _unlock  __dllonexit v_lock _onexit `_decode_pointer s_except_handler4_common _invoke_watson  ?_controlfp_s  InterlockedExchange !Sleep InterlockedCompareExchange  -TerminateProcess  GetCurrentProcess >UnhandledExceptionFilter  SetUnhandledExceptionFilter IsDebuggerPresent TQueryPerformanceCounter fGetTickCount  GetCurrentThreadId  GetCurrentProcessId OGetSystemTimeAsFileTime s __CxxFrameHandler3                                                    N@D   $!@                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            8                   P                   h                	                   	     @  (        C  V        (4   V S _ V E R S I O N _ I N F O                                                  S t r i n g F i l e I n f o   b   0 4 0 9 0 4 b 0    Q  F i l e D e s c r i p t i o n     R e a d s   f r o m   s t d i n   w i t h o u t   l e a k i n g   i n f o   t o   t h e   t e r m i n a l   a n d   o u t p u t s   b a c k   t o   s t d o u t     6   F i l e V e r s i o n     1 ,   0 ,   0 ,   0     8   I n t e r n a l N a m e   h i d d e n i n p u t   P   L e g a l C o p y r i g h t   J o r d i   B o g g i a n o   -   2 0 1 2   H   O r i g i n a l F i l e n a m e   h i d d e n i n p u t . e x e   :   P r o d u c t N a m e     H i d d e n   I n p u t     :   P r o d u c t V e r s i o n   1 ,   0 ,   0 ,   0     D    V a r F i l e I n f o     $    T r a n s l a t i o n     	<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
    <security>
      <requestedPrivileges>
        <requestedExecutionLevel level="asInvoker" uiAccess="false"></requestedExecutionLevel>
      </requestedPrivileges>
    </security>
  </trustInfo>
  <dependency>
    <dependentAssembly>
      <assemblyIdentity type="win32" name="Microsoft.VC90.CRT" version="9.0.21022.8" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>
    </dependentAssembly>
  </dependency>
</assembly>PAPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDING   @  00!0/080F0L0T0^0d0n0{000000000000001#1-1@1J1O1T1v1{1111111111111112"2*23292A2M2_2j2p222222222222333%303N3T3Z3`3f3l3s3z333333333333333334444%4;4B444444444445!5^5c5555H6M6_6}666 777*7w7|77777888=8E8P8V8\8b8h8n8t8z88889      $   0001 1t1x12 2@2\2`2h2t2 0     0                                                                                                                                                  <?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\EventListener;

use Psr\Log\LoggerInterface;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
 * @author James Halsall <james.t.halsall@googlemail.com>
 * @author Robin Chalas <robin.chalas@gmail.com>
 */
class ErrorListener implements EventSubscriberInterface
{
    private ?LoggerInterface $logger;

    public function __construct(?LoggerInterface $logger = null)
    {
        $this->logger = $logger;
    }

    /**
     * @return void
     */
    public function onConsoleError(ConsoleErrorEvent $event)
    {
        if (null === $this->logger) {
            return;
        }

        $error = $event->getError();

        if (!$inputString = $this->getInputString($event)) {
            $this->logger->critical('An error occurred while using the console. Message: "{message}"', ['exception' => $error, 'message' => $error->getMessage()]);

            return;
        }

        $this->logger->critical('Error thrown while running command "{command}". Message: "{message}"', ['exception' => $error, 'command' => $inputString, 'message' => $error->getMessage()]);
    }

    /**
     * @return void
     */
    public function onConsoleTerminate(ConsoleTerminateEvent $event)
    {
        if (null === $this->logger) {
            return;
        }

        $exitCode = $event->getExitCode();

        if (0 === $exitCode) {
            return;
        }

        if (!$inputString = $this->getInputString($event)) {
            $this->logger->debug('The console exited with code "{code}"', ['code' => $exitCode]);

            return;
        }

        $this->logger->debug('Command "{command}" exited with code "{code}"', ['command' => $inputString, 'code' => $exitCode]);
    }

    public static function getSubscribedEvents(): array
    {
        return [
            ConsoleEvents::ERROR => ['onConsoleError', -128],
            ConsoleEvents::TERMINATE => ['onConsoleTerminate', -128],
        ];
    }

    private static function getInputString(ConsoleEvent $event): ?string
    {
        $commandName = $event->getCommand()?->getName();
        $input = $event->getInput();

        if ($input instanceof \Stringable) {
            if ($commandName) {
                return str_replace(["'$commandName'", "\"$commandName\""], $commandName, (string) $input);
            }

            return (string) $input;
        }

        return $commandName;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console;

use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleSignalEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;

/**
 * Contains all events dispatched by an Application.
 *
 * @author Francesco Levorato <git@flevour.net>
 */
final class ConsoleEvents
{
    /**
     * The COMMAND event allows you to attach listeners before any command is
     * executed by the console. It also allows you to modify the command, input and output
     * before they are handed to the command.
     *
     * @Event("Symfony\Component\Console\Event\ConsoleCommandEvent")
     */
    public const COMMAND = 'console.command';

    /**
     * The SIGNAL event allows you to perform some actions
     * after the command execution was interrupted.
     *
     * @Event("Symfony\Component\Console\Event\ConsoleSignalEvent")
     */
    public const SIGNAL = 'console.signal';

    /**
     * The TERMINATE event allows you to attach listeners after a command is
     * executed by the console.
     *
     * @Event("Symfony\Component\Console\Event\ConsoleTerminateEvent")
     */
    public const TERMINATE = 'console.terminate';

    /**
     * The ERROR event occurs when an uncaught exception or error appears.
     *
     * This event allows you to deal with the exception/error or
     * to modify the thrown exception.
     *
     * @Event("Symfony\Component\Console\Event\ConsoleErrorEvent")
     */
    public const ERROR = 'console.error';

    /**
     * Event aliases.
     *
     * These aliases can be consumed by RegisterListenersPass.
     */
    public const ALIASES = [
        ConsoleCommandEvent::class => self::COMMAND,
        ConsoleErrorEvent::class => self::ERROR,
        ConsoleSignalEvent::class => self::SIGNAL,
        ConsoleTerminateEvent::class => self::TERMINATE,
    ];
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * HelperSet represents a set of helpers to be used with a command.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @implements \IteratorAggregate<string, HelperInterface>
 */
class HelperSet implements \IteratorAggregate
{
    /** @var array<string, HelperInterface> */
    private array $helpers = [];

    /**
     * @param HelperInterface[] $helpers
     */
    public function __construct(array $helpers = [])
    {
        foreach ($helpers as $alias => $helper) {
            $this->set($helper, \is_int($alias) ? null : $alias);
        }
    }

    /**
     * @return void
     */
    public function set(HelperInterface $helper, ?string $alias = null)
    {
        $this->helpers[$helper->getName()] = $helper;
        if (null !== $alias) {
            $this->helpers[$alias] = $helper;
        }

        $helper->setHelperSet($this);
    }

    /**
     * Returns true if the helper if defined.
     */
    public function has(string $name): bool
    {
        return isset($this->helpers[$name]);
    }

    /**
     * Gets a helper value.
     *
     * @throws InvalidArgumentException if the helper is not defined
     */
    public function get(string $name): HelperInterface
    {
        if (!$this->has($name)) {
            throw new InvalidArgumentException(\sprintf('The helper "%s" is not defined.', $name));
        }

        return $this->helpers[$name];
    }

    public function getIterator(): \Traversable
    {
        return new \ArrayIterator($this->helpers);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Input\InputAwareInterface;
use Symfony\Component\Console\Input\InputInterface;

/**
 * An implementation of InputAwareInterface for Helpers.
 *
 * @author Wouter J <waldio.webdesign@gmail.com>
 */
abstract class InputAwareHelper extends Helper implements InputAwareInterface
{
    protected $input;

    /**
     * @return void
     */
    public function setInput(InputInterface $input)
    {
        $this->input = $input;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\WrappableOutputFormatterInterface;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Provides helpers to display a table.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Саша Стаменковић <umpirsky@gmail.com>
 * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
 * @author Max Grigorian <maxakawizard@gmail.com>
 * @author Dany Maillard <danymaillard93b@gmail.com>
 */
class Table
{
    private const SEPARATOR_TOP = 0;
    private const SEPARATOR_TOP_BOTTOM = 1;
    private const SEPARATOR_MID = 2;
    private const SEPARATOR_BOTTOM = 3;
    private const BORDER_OUTSIDE = 0;
    private const BORDER_INSIDE = 1;
    private const DISPLAY_ORIENTATION_DEFAULT = 'default';
    private const DISPLAY_ORIENTATION_HORIZONTAL = 'horizontal';
    private const DISPLAY_ORIENTATION_VERTICAL = 'vertical';

    private ?string $headerTitle = null;
    private ?string $footerTitle = null;
    private array $headers = [];
    private array $rows = [];
    private array $effectiveColumnWidths = [];
    private int $numberOfColumns;
    private OutputInterface $output;
    private TableStyle $style;
    private array $columnStyles = [];
    private array $columnWidths = [];
    private array $columnMaxWidths = [];
    private bool $rendered = false;
    private string $displayOrientation = self::DISPLAY_ORIENTATION_DEFAULT;

    private static array $styles;

    public function __construct(OutputInterface $output)
    {
        $this->output = $output;

        self::$styles ??= self::initStyles();

        $this->setStyle('default');
    }

    /**
     * Sets a style definition.
     *
     * @return void
     */
    public static function setStyleDefinition(string $name, TableStyle $style)
    {
        self::$styles ??= self::initStyles();

        self::$styles[$name] = $style;
    }

    /**
     * Gets a style definition by name.
     */
    public static function getStyleDefinition(string $name): TableStyle
    {
        self::$styles ??= self::initStyles();

        return self::$styles[$name] ?? throw new InvalidArgumentException(\sprintf('Style "%s" is not defined.', $name));
    }

    /**
     * Sets table style.
     *
     * @return $this
     */
    public function setStyle(TableStyle|string $name): static
    {
        $this->style = $this->resolveStyle($name);

        return $this;
    }

    /**
     * Gets the current table style.
     */
    public function getStyle(): TableStyle
    {
        return $this->style;
    }

    /**
     * Sets table column style.
     *
     * @param TableStyle|string $name The style name or a TableStyle instance
     *
     * @return $this
     */
    public function setColumnStyle(int $columnIndex, TableStyle|string $name): static
    {
        $this->columnStyles[$columnIndex] = $this->resolveStyle($name);

        return $this;
    }

    /**
     * Gets the current style for a column.
     *
     * If style was not set, it returns the global table style.
     */
    public function getColumnStyle(int $columnIndex): TableStyle
    {
        return $this->columnStyles[$columnIndex] ?? $this->getStyle();
    }

    /**
     * Sets the minimum width of a column.
     *
     * @return $this
     */
    public function setColumnWidth(int $columnIndex, int $width): static
    {
        $this->columnWidths[$columnIndex] = $width;

        return $this;
    }

    /**
     * Sets the minimum width of all columns.
     *
     * @return $this
     */
    public function setColumnWidths(array $widths): static
    {
        $this->columnWidths = [];
        foreach ($widths as $index => $width) {
            $this->setColumnWidth($index, $width);
        }

        return $this;
    }

    /**
     * Sets the maximum width of a column.
     *
     * Any cell within this column which contents exceeds the specified width will be wrapped into multiple lines, while
     * formatted strings are preserved.
     *
     * @return $this
     */
    public function setColumnMaxWidth(int $columnIndex, int $width): static
    {
        if (!$this->output->getFormatter() instanceof WrappableOutputFormatterInterface) {
            throw new \LogicException(\sprintf('Setting a maximum column width is only supported when using a "%s" formatter, got "%s".', WrappableOutputFormatterInterface::class, get_debug_type($this->output->getFormatter())));
        }

        $this->columnMaxWidths[$columnIndex] = $width;

        return $this;
    }

    /**
     * @return $this
     */
    public function setHeaders(array $headers): static
    {
        $headers = array_values($headers);
        if ($headers && !\is_array($headers[0])) {
            $headers = [$headers];
        }

        $this->headers = $headers;

        return $this;
    }

    /**
     * @return $this
     */
    public function setRows(array $rows)
    {
        $this->rows = [];

        return $this->addRows($rows);
    }

    /**
     * @return $this
     */
    public function addRows(array $rows): static
    {
        foreach ($rows as $row) {
            $this->addRow($row);
        }

        return $this;
    }

    /**
     * @return $this
     */
    public function addRow(TableSeparator|array $row): static
    {
        if ($row instanceof TableSeparator) {
            $this->rows[] = $row;

            return $this;
        }

        $this->rows[] = array_values($row);

        return $this;
    }

    /**
     * Adds a row to the table, and re-renders the table.
     *
     * @return $this
     */
    public function appendRow(TableSeparator|array $row): static
    {
        if (!$this->output instanceof ConsoleSectionOutput) {
            throw new RuntimeException(\sprintf('Output should be an instance of "%s" when calling "%s".', ConsoleSectionOutput::class, __METHOD__));
        }

        if ($this->rendered) {
            $this->output->clear($this->calculateRowCount());
        }

        $this->addRow($row);
        $this->render();

        return $this;
    }

    /**
     * @return $this
     */
    public function setRow(int|string $column, array $row): static
    {
        $this->rows[$column] = $row;

        return $this;
    }

    /**
     * @return $this
     */
    public function setHeaderTitle(?string $title): static
    {
        $this->headerTitle = $title;

        return $this;
    }

    /**
     * @return $this
     */
    public function setFooterTitle(?string $title): static
    {
        $this->footerTitle = $title;

        return $this;
    }

    /**
     * @return $this
     */
    public function setHorizontal(bool $horizontal = true): static
    {
        $this->displayOrientation = $horizontal ? self::DISPLAY_ORIENTATION_HORIZONTAL : self::DISPLAY_ORIENTATION_DEFAULT;

        return $this;
    }

    /**
     * @return $this
     */
    public function setVertical(bool $vertical = true): static
    {
        $this->displayOrientation = $vertical ? self::DISPLAY_ORIENTATION_VERTICAL : self::DISPLAY_ORIENTATION_DEFAULT;

        return $this;
    }

    /**
     * Renders table to output.
     *
     * Example:
     *
     *     +---------------+-----------------------+------------------+
     *     | ISBN          | Title                 | Author           |
     *     +---------------+-----------------------+------------------+
     *     | 99921-58-10-7 | Divine Comedy         | Dante Alighieri  |
     *     | 9971-5-0210-0 | A Tale of Two Cities  | Charles Dickens  |
     *     | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
     *     +---------------+-----------------------+------------------+
     *
     * @return void
     */
    public function render()
    {
        $divider = new TableSeparator();
        $isCellWithColspan = static fn ($cell) => $cell instanceof TableCell && $cell->getColspan() >= 2;

        $horizontal = self::DISPLAY_ORIENTATION_HORIZONTAL === $this->displayOrientation;
        $vertical = self::DISPLAY_ORIENTATION_VERTICAL === $this->displayOrientation;

        $rows = [];
        if ($horizontal) {
            foreach ($this->headers[0] ?? [] as $i => $header) {
                $rows[$i] = [$header];
                foreach ($this->rows as $row) {
                    if ($row instanceof TableSeparator) {
                        continue;
                    }
                    if (isset($row[$i])) {
                        $rows[$i][] = $row[$i];
                    } elseif ($isCellWithColspan($rows[$i][0])) {
                        // Noop, there is a "title"
                    } else {
                        $rows[$i][] = null;
                    }
                }
            }
        } elseif ($vertical) {
            $formatter = $this->output->getFormatter();
            $maxHeaderLength = array_reduce($this->headers[0] ?? [], static fn ($max, $header) => max($max, Helper::width(Helper::removeDecoration($formatter, $header))), 0);

            foreach ($this->rows as $row) {
                if ($row instanceof TableSeparator) {
                    continue;
                }

                if ($rows) {
                    $rows[] = [$divider];
                }

                $containsColspan = false;
                foreach ($row as $cell) {
                    if ($containsColspan = $isCellWithColspan($cell)) {
                        break;
                    }
                }

                $headers = $this->headers[0] ?? [];
                $maxRows = max(\count($headers), \count($row));
                for ($i = 0; $i < $maxRows; ++$i) {
                    $cell = (string) ($row[$i] ?? '');

                    $eol = str_contains($cell, "\r\n") ? "\r\n" : "\n";
                    $parts = explode($eol, $cell);
                    foreach ($parts as $idx => $part) {
                        if ($headers && !$containsColspan) {
                            if (0 === $idx) {
                                $rows[] = [\sprintf(
                                    '<comment>%s%s</>: %s',
                                    str_repeat(' ', $maxHeaderLength - Helper::width(Helper::removeDecoration($formatter, $headers[$i] ?? ''))),
                                    $headers[$i] ?? '',
                                    $part
                                )];
                            } else {
                                $rows[] = [\sprintf(
                                    '%s  %s',
                                    str_pad('', $maxHeaderLength, ' ', \STR_PAD_LEFT),
                                    $part
                                )];
                            }
                        } elseif ('' !== $cell) {
                            $rows[] = [$part];
                        }
                    }
                }
            }
        } else {
            $rows = array_merge($this->headers, [$divider], $this->rows);
        }

        $this->calculateNumberOfColumns($rows);

        $rowGroups = $this->buildTableRows($rows);
        $this->calculateColumnsWidth($rowGroups);

        $isHeader = !$horizontal;
        $isFirstRow = $horizontal;
        $hasTitle = (bool) $this->headerTitle;

        foreach ($rowGroups as $rowGroup) {
            $isHeaderSeparatorRendered = false;

            foreach ($rowGroup as $row) {
                if ($divider === $row) {
                    $isHeader = false;
                    $isFirstRow = true;

                    continue;
                }

                if ($row instanceof TableSeparator) {
                    $this->renderRowSeparator();

                    continue;
                }

                if (!$row) {
                    continue;
                }

                if ($isHeader && !$isHeaderSeparatorRendered) {
                    $this->renderRowSeparator(
                        self::SEPARATOR_TOP,
                        $hasTitle ? $this->headerTitle : null,
                        $hasTitle ? $this->style->getHeaderTitleFormat() : null
                    );
                    $hasTitle = false;
                    $isHeaderSeparatorRendered = true;
                }

                if ($isFirstRow) {
                    $this->renderRowSeparator(
                        $horizontal ? self::SEPARATOR_TOP : self::SEPARATOR_TOP_BOTTOM,
                        $hasTitle ? $this->headerTitle : null,
                        $hasTitle ? $this->style->getHeaderTitleFormat() : null
                    );
                    $isFirstRow = false;
                    $hasTitle = false;
                }

                if ($vertical) {
                    $isHeader = false;
                    $isFirstRow = false;
                }

                if ($horizontal) {
                    $this->renderRow($row, $this->style->getCellRowFormat(), $this->style->getCellHeaderFormat());
                } else {
                    $this->renderRow($row, $isHeader ? $this->style->getCellHeaderFormat() : $this->style->getCellRowFormat());
                }
            }
        }
        $this->renderRowSeparator(self::SEPARATOR_BOTTOM, $this->footerTitle, $this->style->getFooterTitleFormat());

        $this->cleanup();
        $this->rendered = true;
    }

    /**
     * Renders horizontal header separator.
     *
     * Example:
     *
     *     +-----+-----------+-------+
     */
    private function renderRowSeparator(int $type = self::SEPARATOR_MID, ?string $title = null, ?string $titleFormat = null): void
    {
        if (!$count = $this->numberOfColumns) {
            return;
        }

        $borders = $this->style->getBorderChars();
        if (!$borders[0] && !$borders[2] && !$this->style->getCrossingChar()) {
            return;
        }

        $crossings = $this->style->getCrossingChars();
        if (self::SEPARATOR_MID === $type) {
            [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[2], $crossings[8], $crossings[0], $crossings[4]];
        } elseif (self::SEPARATOR_TOP === $type) {
            [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[1], $crossings[2], $crossings[3]];
        } elseif (self::SEPARATOR_TOP_BOTTOM === $type) {
            [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[9], $crossings[10], $crossings[11]];
        } else {
            [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[7], $crossings[6], $crossings[5]];
        }

        $markup = $leftChar;
        for ($column = 0; $column < $count; ++$column) {
            $markup .= str_repeat($horizontal, $this->effectiveColumnWidths[$column]);
            $markup .= $column === $count - 1 ? $rightChar : $midChar;
        }

        if (null !== $title) {
            $titleLength = Helper::width(Helper::removeDecoration($formatter = $this->output->getFormatter(), $formattedTitle = \sprintf($titleFormat, $title)));
            $markupLength = Helper::width($markup);
            if ($titleLength > $limit = $markupLength - 4) {
                $titleLength = $limit;
                $formatLength = Helper::width(Helper::removeDecoration($formatter, \sprintf($titleFormat, '')));
                $formattedTitle = \sprintf($titleFormat, Helper::substr($title, 0, $limit - $formatLength - 3).'...');
            }

            $titleStart = intdiv($markupLength - $titleLength, 2);
            if (false === mb_detect_encoding($markup, null, true)) {
                $markup = substr_replace($markup, $formattedTitle, $titleStart, $titleLength);
            } else {
                $markup = mb_substr($markup, 0, $titleStart).$formattedTitle.mb_substr($markup, $titleStart + $titleLength);
            }
        }

        $this->output->writeln(\sprintf($this->style->getBorderFormat(), $markup));
    }

    /**
     * Renders vertical column separator.
     */
    private function renderColumnSeparator(int $type = self::BORDER_OUTSIDE): string
    {
        $borders = $this->style->getBorderChars();

        return \sprintf($this->style->getBorderFormat(), self::BORDER_OUTSIDE === $type ? $borders[1] : $borders[3]);
    }

    /**
     * Renders table row.
     *
     * Example:
     *
     *     | 9971-5-0210-0 | A Tale of Two Cities  | Charles Dickens  |
     */
    private function renderRow(array $row, string $cellFormat, ?string $firstCellFormat = null): void
    {
        $rowContent = $this->renderColumnSeparator(self::BORDER_OUTSIDE);
        $columns = $this->getRowColumns($row);
        $last = \count($columns) - 1;
        foreach ($columns as $i => $column) {
            if ($firstCellFormat && 0 === $i) {
                $rowContent .= $this->renderCell($row, $column, $firstCellFormat);
            } else {
                $rowContent .= $this->renderCell($row, $column, $cellFormat);
            }
            $rowContent .= $this->renderColumnSeparator($last === $i ? self::BORDER_OUTSIDE : self::BORDER_INSIDE);
        }
        $this->output->writeln($rowContent);
    }

    /**
     * Renders table cell with padding.
     */
    private function renderCell(array $row, int $column, string $cellFormat): string
    {
        $cell = $row[$column] ?? '';
        $width = $this->effectiveColumnWidths[$column];
        if ($cell instanceof TableCell && $cell->getColspan() > 1) {
            // add the width of the following columns(numbers of colspan).
            foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) {
                $width += $this->getColumnSeparatorWidth() + $this->effectiveColumnWidths[$nextColumn];
            }
        }

        // str_pad won't work properly with multi-byte strings, we need to fix the padding
        $width += \strlen($cell) - Helper::width($cell) - substr_count($cell, "\0");
        $style = $this->getColumnStyle($column);

        if ($cell instanceof TableSeparator) {
            return \sprintf($style->getBorderFormat(), str_repeat($style->getBorderChars()[2], $width));
        }

        $width += Helper::length($cell) - Helper::length(Helper::removeDecoration($this->output->getFormatter(), $cell));
        $content = \sprintf($style->getCellRowContentFormat(), $cell);

        $padType = $style->getPadType();
        if ($cell instanceof TableCell && $cell->getStyle() instanceof TableCellStyle) {
            $isNotStyledByTag = !preg_match('/^<(\w+|(\w+=[\w,]+;?)*)>.+<\/(\w+|(\w+=\w+;?)*)?>$/', $cell);
            if ($isNotStyledByTag) {
                $cellFormat = $cell->getStyle()->getCellFormat();
                if (!\is_string($cellFormat)) {
                    $tag = http_build_query($cell->getStyle()->getTagOptions(), '', ';');
                    $cellFormat = '<'.$tag.'>%s</>';
                }

                if (str_contains($content, '</>')) {
                    $content = str_replace('</>', '', $content);
                    $width -= 3;
                }
                if (str_contains($content, '<fg=default;bg=default>')) {
                    $content = str_replace('<fg=default;bg=default>', '', $content);
                    $width -= \strlen('<fg=default;bg=default>');
                }
            }

            $padType = $cell->getStyle()->getPadByAlign();
        }

        return \sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $padType));
    }

    /**
     * Calculate number of columns for this table.
     */
    private function calculateNumberOfColumns(array $rows): void
    {
        $columns = [0];
        foreach ($rows as $row) {
            if ($row instanceof TableSeparator) {
                continue;
            }

            $columns[] = $this->getNumberOfColumns($row);
        }

        $this->numberOfColumns = max($columns);
    }

    private function buildTableRows(array $rows): TableRows
    {
        /** @var WrappableOutputFormatterInterface $formatter */
        $formatter = $this->output->getFormatter();
        $unmergedRows = [];
        for ($rowKey = 0; $rowKey < \count($rows); ++$rowKey) {
            $rows = $this->fillNextRows($rows, $rowKey);

            // Remove any new line breaks and replace it with a new line
            foreach ($rows[$rowKey] as $column => $cell) {
                $colspan = $cell instanceof TableCell ? $cell->getColspan() : 1;

                $minWrappedWidth = 0;
                $widthApplied = [];
                $lengthColumnBorder = $this->getColumnSeparatorWidth() + Helper::width($this->style->getCellRowContentFormat()) - 2;
                for ($i = $column; $i < ($column + $colspan); ++$i) {
                    if (isset($this->columnMaxWidths[$i])) {
                        $minWrappedWidth += $this->columnMaxWidths[$i];
                        $widthApplied[] = ['type' => 'max', 'column' => $i];
                    } elseif (($this->columnWidths[$i] ?? 0) > 0 && $colspan > 1) {
                        $minWrappedWidth += $this->columnWidths[$i];
                        $widthApplied[] = ['type' => 'min', 'column' => $i];
                    }
                }
                if (1 === \count($widthApplied)) {
                    if ($colspan > 1) {
                        $minWrappedWidth *= $colspan;  // previous logic
                    }
                } elseif (\count($widthApplied) > 1) {
                    $minWrappedWidth += (\count($widthApplied) - 1) * $lengthColumnBorder;
                }

                $cellWidth = Helper::width(Helper::removeDecoration($formatter, $cell));
                if ($minWrappedWidth && $cellWidth > $minWrappedWidth) {
                    $cell = $formatter->formatAndWrap($cell, $minWrappedWidth);
                }
                // update minimal columnWidths for spanned columns
                if ($colspan > 1 && $minWrappedWidth > 0) {
                    $columnsMinWidthProcessed = [];
                    $cellWidth = min($cellWidth, $minWrappedWidth);
                    foreach ($widthApplied as $item) {
                        if ('max' === $item['type'] && $cellWidth >= $this->columnMaxWidths[$item['column']]) {
                            $minWidthColumn = $this->columnMaxWidths[$item['column']];
                            $this->columnWidths[$item['column']] = $minWidthColumn;
                            $columnsMinWidthProcessed[$item['column']] = true;
                            $cellWidth -= $minWidthColumn + $lengthColumnBorder;
                        }
                    }
                    for ($i = $column; $i < ($column + $colspan); ++$i) {
                        if (isset($columnsMinWidthProcessed[$i])) {
                            continue;
                        }
                        $this->columnWidths[$i] = $cellWidth + $lengthColumnBorder;
                    }
                }
                if (!str_contains($cell ?? '', "\n")) {
                    continue;
                }
                $eol = str_contains($cell ?? '', "\r\n") ? "\r\n" : "\n";
                $escaped = implode($eol, array_map(OutputFormatter::escapeTrailingBackslash(...), explode($eol, $cell)));
                $cell = $cell instanceof TableCell ? new TableCell($escaped, ['colspan' => $cell->getColspan()]) : $escaped;
                $lines = explode($eol, str_replace($eol, '<fg=default;bg=default></>'.$eol, $cell));
                foreach ($lines as $lineKey => $line) {
                    if ($colspan > 1) {
                        $line = new TableCell($line, ['colspan' => $colspan]);
                    }
                    if (0 === $lineKey) {
                        $rows[$rowKey][$column] = $line;
                    } else {
                        if (!\array_key_exists($rowKey, $unmergedRows) || !\array_key_exists($lineKey, $unmergedRows[$rowKey])) {
                            $unmergedRows[$rowKey][$lineKey] = $this->copyRow($rows, $rowKey);
                        }
                        $unmergedRows[$rowKey][$lineKey][$column] = $line;
                    }
                }
            }
        }

        return new TableRows(function () use ($rows, $unmergedRows): \Traversable {
            foreach ($rows as $rowKey => $row) {
                $rowGroup = [$row instanceof TableSeparator ? $row : $this->fillCells($row)];

                if (isset($unmergedRows[$rowKey])) {
                    foreach ($unmergedRows[$rowKey] as $row) {
                        $rowGroup[] = $row instanceof TableSeparator ? $row : $this->fillCells($row);
                    }
                }
                yield $rowGroup;
            }
        });
    }

    private function calculateRowCount(): int
    {
        $numberOfRows = \count(iterator_to_array($this->buildTableRows(array_merge($this->headers, [new TableSeparator()], $this->rows))));

        if ($this->headers) {
            ++$numberOfRows; // Add row for header separator
        }

        if ($this->rows) {
            ++$numberOfRows; // Add row for footer separator
        }

        return $numberOfRows;
    }

    /**
     * fill rows that contains rowspan > 1.
     *
     * @throws InvalidArgumentException
     */
    private function fillNextRows(array $rows, int $line): array
    {
        $unmergedRows = [];
        foreach ($rows[$line] as $column => $cell) {
            if (null !== $cell && !$cell instanceof TableCell && !\is_scalar($cell) && !$cell instanceof \Stringable) {
                throw new InvalidArgumentException(\sprintf('A cell must be a TableCell, a scalar or an object implementing "__toString()", "%s" given.', get_debug_type($cell)));
            }
            if ($cell instanceof TableCell && $cell->getRowspan() > 1) {
                $nbLines = $cell->getRowspan() - 1;
                $lines = [$cell];
                if (str_contains($cell, "\n")) {
                    $eol = str_contains($cell, "\r\n") ? "\r\n" : "\n";
                    $lines = explode($eol, str_replace($eol, '<fg=default;bg=default>'.$eol.'</>', $cell));
                    $nbLines = \count($lines) > $nbLines ? substr_count($cell, $eol) : $nbLines;

                    $rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]);
                    unset($lines[0]);
                }

                // create a two dimensional array (rowspan x colspan)
                $unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, []), $unmergedRows);
                foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
                    $value = $lines[$unmergedRowKey - $line] ?? '';
                    $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]);
                    if ($nbLines === $unmergedRowKey - $line) {
                        break;
                    }
                }
            }
        }

        foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
            // we need to know if $unmergedRow will be merged or inserted into $rows
            if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) {
                foreach ($unmergedRow as $cellKey => $cell) {
                    // insert cell into row at cellKey position
                    array_splice($rows[$unmergedRowKey], $cellKey, 0, [$cell]);
                }
            } else {
                $row = $this->copyRow($rows, $unmergedRowKey - 1);
                foreach ($unmergedRow as $column => $cell) {
                    if (!empty($cell)) {
                        $row[$column] = $unmergedRow[$column];
                    }
                }
                array_splice($rows, $unmergedRowKey, 0, [$row]);
            }
        }

        return $rows;
    }

    /**
     * fill cells for a row that contains colspan > 1.
     */
    private function fillCells(iterable $row): iterable
    {
        $newRow = [];

        foreach ($row as $column => $cell) {
            $newRow[] = $cell;
            if ($cell instanceof TableCell && $cell->getColspan() > 1) {
                foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) {
                    // insert empty value at column position
                    $newRow[] = '';
                }
            }
        }

        return $newRow ?: $row;
    }

    private function copyRow(array $rows, int $line): array
    {
        $row = $rows[$line];
        foreach ($row as $cellKey => $cellValue) {
            $row[$cellKey] = '';
            if ($cellValue instanceof TableCell) {
                $row[$cellKey] = new TableCell('', ['colspan' => $cellValue->getColspan()]);
            }
        }

        return $row;
    }

    /**
     * Gets number of columns by row.
     */
    private function getNumberOfColumns(array $row): int
    {
        $columns = \count($row);
        foreach ($row as $column) {
            $columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0;
        }

        return $columns;
    }

    /**
     * Gets list of columns for the given row.
     */
    private function getRowColumns(array $row): array
    {
        $columns = range(0, $this->numberOfColumns - 1);
        foreach ($row as $cellKey => $cell) {
            if ($cell instanceof TableCell && $cell->getColspan() > 1) {
                // exclude grouped columns.
                $columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1));
            }
        }

        return $columns;
    }

    /**
     * Calculates columns widths.
     */
    private function calculateColumnsWidth(iterable $groups): void
    {
        for ($column = 0; $column < $this->numberOfColumns; ++$column) {
            $lengths = [];
            foreach ($groups as $group) {
                foreach ($group as $row) {
                    if ($row instanceof TableSeparator) {
                        continue;
                    }

                    foreach ($row as $i => $cell) {
                        if ($cell instanceof TableCell) {
                            $textContent = Helper::removeDecoration($this->output->getFormatter(), $cell);
                            $textLength = Helper::width($textContent);
                            if ($textLength > 0) {
                                $contentColumns = mb_str_split($textContent, ceil($textLength / $cell->getColspan()));
                                foreach ($contentColumns as $position => $content) {
                                    $row[$i + $position] = $content;
                                }
                            }
                        }
                    }

                    $lengths[] = $this->getCellWidth($row, $column);
                }
            }

            $this->effectiveColumnWidths[$column] = max($lengths) + Helper::width($this->style->getCellRowContentFormat()) - 2;
        }
    }

    private function getColumnSeparatorWidth(): int
    {
        return Helper::width(\sprintf($this->style->getBorderFormat(), $this->style->getBorderChars()[3]));
    }

    private function getCellWidth(array $row, int $column): int
    {
        $cellWidth = 0;

        if (isset($row[$column])) {
            $cell = $row[$column];
            $cellWidth = Helper::width(Helper::removeDecoration($this->output->getFormatter(), $cell));
        }

        $columnWidth = $this->columnWidths[$column] ?? 0;
        $cellWidth = max($cellWidth, $columnWidth);

        return isset($this->columnMaxWidths[$column]) ? min($this->columnMaxWidths[$column], $cellWidth) : $cellWidth;
    }

    /**
     * Called after rendering to cleanup cache data.
     */
    private function cleanup(): void
    {
        $this->effectiveColumnWidths = [];
        unset($this->numberOfColumns);
    }

    /**
     * @return array<string, TableStyle>
     */
    private static function initStyles(): array
    {
        $borderless = new TableStyle();
        $borderless
            ->setHorizontalBorderChars('=')
            ->setVerticalBorderChars(' ')
            ->setDefaultCrossingChar(' ')
        ;

        $compact = new TableStyle();
        $compact
            ->setHorizontalBorderChars('')
            ->setVerticalBorderChars('')
            ->setDefaultCrossingChar('')
            ->setCellRowContentFormat('%s ')
        ;

        $styleGuide = new TableStyle();
        $styleGuide
            ->setHorizontalBorderChars('-')
            ->setVerticalBorderChars(' ')
            ->setDefaultCrossingChar(' ')
            ->setCellHeaderFormat('%s')
        ;

        $box = (new TableStyle())
            ->setHorizontalBorderChars('─')
            ->setVerticalBorderChars('│')
            ->setCrossingChars('┼', '┌', '┬', '┐', '┤', '┘', '┴', '└', '├')
        ;

        $boxDouble = (new TableStyle())
            ->setHorizontalBorderChars('═', '─')
            ->setVerticalBorderChars('║', '│')
            ->setCrossingChars('┼', '╔', '╤', '╗', '╢', '╝', '╧', '╚', '╟', '╠', '╪', '╣')
        ;

        return [
            'default' => new TableStyle(),
            'borderless' => $borderless,
            'compact' => $compact,
            'symfony-style-guide' => $styleGuide,
            'box' => $box,
            'box-double' => $boxDouble,
        ];
    }

    private function resolveStyle(TableStyle|string $name): TableStyle
    {
        if ($name instanceof TableStyle) {
            return $name;
        }

        return self::$styles[$name] ?? throw new InvalidArgumentException(\sprintf('Style "%s" is not defined.', $name));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Style\SymfonyStyle;

/**
 * Symfony Style Guide compliant question helper.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
class SymfonyQuestionHelper extends QuestionHelper
{
    /**
     * @return void
     */
    protected function writePrompt(OutputInterface $output, Question $question)
    {
        $text = OutputFormatter::escapeTrailingBackslash($question->getQuestion());
        $default = $question->getDefault();

        if ($question->isMultiline()) {
            $text .= \sprintf(' (press %s to continue)', $this->getEofShortcut());
        }

        switch (true) {
            case null === $default:
                $text = \sprintf(' <info>%s</info>:', $text);

                break;

            case $question instanceof ConfirmationQuestion:
                $text = \sprintf(' <info>%s (yes/no)</info> [<comment>%s</comment>]:', $text, $default ? 'yes' : 'no');

                break;

            case $question instanceof ChoiceQuestion && $question->isMultiselect():
                $choices = $question->getChoices();
                $default = explode(',', $default);

                foreach ($default as $key => $value) {
                    $default[$key] = $choices[trim($value)];
                }

                $text = \sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape(implode(', ', $default)));

                break;

            case $question instanceof ChoiceQuestion:
                $choices = $question->getChoices();
                $text = \sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($choices[$default] ?? $default));

                break;

            default:
                $text = \sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($default));
        }

        $output->writeln($text);

        $prompt = ' > ';

        if ($question instanceof ChoiceQuestion) {
            $output->writeln($this->formatChoiceQuestionChoices($question, 'comment'));

            $prompt = $question->getPrompt();
        }

        $output->write($prompt);
    }

    /**
     * @return void
     */
    protected function writeError(OutputInterface $output, \Exception $error)
    {
        if ($output instanceof SymfonyStyle) {
            $output->newLine();
            $output->error($error->getMessage());

            return;
        }

        parent::writeError($output, $error);
    }

    private function getEofShortcut(): string
    {
        if ('Windows' === \PHP_OS_FAMILY) {
            return '<comment>Ctrl+Z</comment> then <comment>Enter</comment>';
        }

        return '<comment>Ctrl+D</comment>';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Formatter\OutputFormatter;

/**
 * The Formatter class provides helpers to format messages.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class FormatterHelper extends Helper
{
    /**
     * Formats a message within a section.
     */
    public function formatSection(string $section, string $message, string $style = 'info'): string
    {
        return \sprintf('<%s>[%s]</%s> %s', $style, $section, $style, $message);
    }

    /**
     * Formats a message as a block of text.
     */
    public function formatBlock(string|array $messages, string $style, bool $large = false): string
    {
        if (!\is_array($messages)) {
            $messages = [$messages];
        }

        $len = 0;
        $lines = [];
        foreach ($messages as $message) {
            $message = OutputFormatter::escape($message);
            $lines[] = \sprintf($large ? '  %s  ' : ' %s ', $message);
            $len = max(self::width($message) + ($large ? 4 : 2), $len);
        }

        $messages = $large ? [str_repeat(' ', $len)] : [];
        for ($i = 0; isset($lines[$i]); ++$i) {
            $messages[] = $lines[$i].str_repeat(' ', $len - self::width($lines[$i]));
        }
        if ($large) {
            $messages[] = str_repeat(' ', $len);
        }

        for ($i = 0; isset($messages[$i]); ++$i) {
            $messages[$i] = \sprintf('<%s>%s</%s>', $style, $messages[$i], $style);
        }

        return implode("\n", $messages);
    }

    /**
     * Truncates a message to the given length.
     */
    public function truncate(string $message, int $length, string $suffix = '...'): string
    {
        $computedLength = $length - self::width($suffix);

        if ($computedLength > self::width($message)) {
            return $message;
        }

        return self::substr($message, 0, $length).$suffix;
    }

    public function getName(): string
    {
        return 'formatter';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Descriptor\DescriptorInterface;
use Symfony\Component\Console\Descriptor\JsonDescriptor;
use Symfony\Component\Console\Descriptor\MarkdownDescriptor;
use Symfony\Component\Console\Descriptor\ReStructuredTextDescriptor;
use Symfony\Component\Console\Descriptor\TextDescriptor;
use Symfony\Component\Console\Descriptor\XmlDescriptor;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * This class adds helper method to describe objects in various formats.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class DescriptorHelper extends Helper
{
    /**
     * @var DescriptorInterface[]
     */
    private array $descriptors = [];

    public function __construct()
    {
        $this
            ->register('txt', new TextDescriptor())
            ->register('xml', new XmlDescriptor())
            ->register('json', new JsonDescriptor())
            ->register('md', new MarkdownDescriptor())
            ->register('rst', new ReStructuredTextDescriptor())
        ;
    }

    /**
     * Describes an object if supported.
     *
     * Available options are:
     * * format: string, the output format name
     * * raw_text: boolean, sets output type as raw
     *
     * @return void
     *
     * @throws InvalidArgumentException when the given format is not supported
     */
    public function describe(OutputInterface $output, ?object $object, array $options = [])
    {
        $options = array_merge([
            'raw_text' => false,
            'format' => 'txt',
        ], $options);

        if (!isset($this->descriptors[$options['format']])) {
            throw new InvalidArgumentException(\sprintf('Unsupported format "%s".', $options['format']));
        }

        $descriptor = $this->descriptors[$options['format']];
        $descriptor->describe($output, $object, $options);
    }

    /**
     * Registers a descriptor.
     *
     * @return $this
     */
    public function register(string $format, DescriptorInterface $descriptor): static
    {
        $this->descriptors[$format] = $descriptor;

        return $this;
    }

    public function getName(): string
    {
        return 'descriptor';
    }

    public function getFormats(): array
    {
        return array_keys($this->descriptors);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;

/**
 * Defines the styles for a Table.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Саша Стаменковић <umpirsky@gmail.com>
 * @author Dany Maillard <danymaillard93b@gmail.com>
 */
class TableStyle
{
    private string $paddingChar = ' ';
    private string $horizontalOutsideBorderChar = '-';
    private string $horizontalInsideBorderChar = '-';
    private string $verticalOutsideBorderChar = '|';
    private string $verticalInsideBorderChar = '|';
    private string $crossingChar = '+';
    private string $crossingTopRightChar = '+';
    private string $crossingTopMidChar = '+';
    private string $crossingTopLeftChar = '+';
    private string $crossingMidRightChar = '+';
    private string $crossingBottomRightChar = '+';
    private string $crossingBottomMidChar = '+';
    private string $crossingBottomLeftChar = '+';
    private string $crossingMidLeftChar = '+';
    private string $crossingTopLeftBottomChar = '+';
    private string $crossingTopMidBottomChar = '+';
    private string $crossingTopRightBottomChar = '+';
    private string $headerTitleFormat = '<fg=black;bg=white;options=bold> %s </>';
    private string $footerTitleFormat = '<fg=black;bg=white;options=bold> %s </>';
    private string $cellHeaderFormat = '<info>%s</info>';
    private string $cellRowFormat = '%s';
    private string $cellRowContentFormat = ' %s ';
    private string $borderFormat = '%s';
    private int $padType = \STR_PAD_RIGHT;

    /**
     * Sets padding character, used for cell padding.
     *
     * @return $this
     */
    public function setPaddingChar(string $paddingChar): static
    {
        if (!$paddingChar) {
            throw new LogicException('The padding char must not be empty.');
        }

        $this->paddingChar = $paddingChar;

        return $this;
    }

    /**
     * Gets padding character, used for cell padding.
     */
    public function getPaddingChar(): string
    {
        return $this->paddingChar;
    }

    /**
     * Sets horizontal border characters.
     *
     * <code>
     * ╔═══════════════╤══════════════════════════╤══════════════════╗
     * 1 ISBN          2 Title                    │ Author           ║
     * ╠═══════════════╪══════════════════════════╪══════════════════╣
     * ║ 99921-58-10-7 │ Divine Comedy            │ Dante Alighieri  ║
     * ║ 9971-5-0210-0 │ A Tale of Two Cities     │ Charles Dickens  ║
     * ║ 960-425-059-0 │ The Lord of the Rings    │ J. R. R. Tolkien ║
     * ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie  ║
     * ╚═══════════════╧══════════════════════════╧══════════════════╝
     * </code>
     *
     * @return $this
     */
    public function setHorizontalBorderChars(string $outside, ?string $inside = null): static
    {
        $this->horizontalOutsideBorderChar = $outside;
        $this->horizontalInsideBorderChar = $inside ?? $outside;

        return $this;
    }

    /**
     * Sets vertical border characters.
     *
     * <code>
     * ╔═══════════════╤══════════════════════════╤══════════════════╗
     * ║ ISBN          │ Title                    │ Author           ║
     * ╠═══════1═══════╪══════════════════════════╪══════════════════╣
     * ║ 99921-58-10-7 │ Divine Comedy            │ Dante Alighieri  ║
     * ║ 9971-5-0210-0 │ A Tale of Two Cities     │ Charles Dickens  ║
     * ╟───────2───────┼──────────────────────────┼──────────────────╢
     * ║ 960-425-059-0 │ The Lord of the Rings    │ J. R. R. Tolkien ║
     * ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie  ║
     * ╚═══════════════╧══════════════════════════╧══════════════════╝
     * </code>
     *
     * @return $this
     */
    public function setVerticalBorderChars(string $outside, ?string $inside = null): static
    {
        $this->verticalOutsideBorderChar = $outside;
        $this->verticalInsideBorderChar = $inside ?? $outside;

        return $this;
    }

    /**
     * Gets border characters.
     *
     * @internal
     */
    public function getBorderChars(): array
    {
        return [
            $this->horizontalOutsideBorderChar,
            $this->verticalOutsideBorderChar,
            $this->horizontalInsideBorderChar,
            $this->verticalInsideBorderChar,
        ];
    }

    /**
     * Sets crossing characters.
     *
     * Example:
     * <code>
     * 1═══════════════2══════════════════════════2══════════════════3
     * ║ ISBN          │ Title                    │ Author           ║
     * 8'══════════════0'═════════════════════════0'═════════════════4'
     * ║ 99921-58-10-7 │ Divine Comedy            │ Dante Alighieri  ║
     * ║ 9971-5-0210-0 │ A Tale of Two Cities     │ Charles Dickens  ║
     * 8───────────────0──────────────────────────0──────────────────4
     * ║ 960-425-059-0 │ The Lord of the Rings    │ J. R. R. Tolkien ║
     * ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie  ║
     * 7═══════════════6══════════════════════════6══════════════════5
     * </code>
     *
     * @param string      $cross          Crossing char (see #0 of example)
     * @param string      $topLeft        Top left char (see #1 of example)
     * @param string      $topMid         Top mid char (see #2 of example)
     * @param string      $topRight       Top right char (see #3 of example)
     * @param string      $midRight       Mid right char (see #4 of example)
     * @param string      $bottomRight    Bottom right char (see #5 of example)
     * @param string      $bottomMid      Bottom mid char (see #6 of example)
     * @param string      $bottomLeft     Bottom left char (see #7 of example)
     * @param string      $midLeft        Mid left char (see #8 of example)
     * @param string|null $topLeftBottom  Top left bottom char (see #8' of example), equals to $midLeft if null
     * @param string|null $topMidBottom   Top mid bottom char (see #0' of example), equals to $cross if null
     * @param string|null $topRightBottom Top right bottom char (see #4' of example), equals to $midRight if null
     *
     * @return $this
     */
    public function setCrossingChars(string $cross, string $topLeft, string $topMid, string $topRight, string $midRight, string $bottomRight, string $bottomMid, string $bottomLeft, string $midLeft, ?string $topLeftBottom = null, ?string $topMidBottom = null, ?string $topRightBottom = null): static
    {
        $this->crossingChar = $cross;
        $this->crossingTopLeftChar = $topLeft;
        $this->crossingTopMidChar = $topMid;
        $this->crossingTopRightChar = $topRight;
        $this->crossingMidRightChar = $midRight;
        $this->crossingBottomRightChar = $bottomRight;
        $this->crossingBottomMidChar = $bottomMid;
        $this->crossingBottomLeftChar = $bottomLeft;
        $this->crossingMidLeftChar = $midLeft;
        $this->crossingTopLeftBottomChar = $topLeftBottom ?? $midLeft;
        $this->crossingTopMidBottomChar = $topMidBottom ?? $cross;
        $this->crossingTopRightBottomChar = $topRightBottom ?? $midRight;

        return $this;
    }

    /**
     * Sets default crossing character used for each cross.
     *
     * @see {@link setCrossingChars()} for setting each crossing individually.
     */
    public function setDefaultCrossingChar(string $char): self
    {
        return $this->setCrossingChars($char, $char, $char, $char, $char, $char, $char, $char, $char);
    }

    /**
     * Gets crossing character.
     */
    public function getCrossingChar(): string
    {
        return $this->crossingChar;
    }

    /**
     * Gets crossing characters.
     *
     * @internal
     */
    public function getCrossingChars(): array
    {
        return [
            $this->crossingChar,
            $this->crossingTopLeftChar,
            $this->crossingTopMidChar,
            $this->crossingTopRightChar,
            $this->crossingMidRightChar,
            $this->crossingBottomRightChar,
            $this->crossingBottomMidChar,
            $this->crossingBottomLeftChar,
            $this->crossingMidLeftChar,
            $this->crossingTopLeftBottomChar,
            $this->crossingTopMidBottomChar,
            $this->crossingTopRightBottomChar,
        ];
    }

    /**
     * Sets header cell format.
     *
     * @return $this
     */
    public function setCellHeaderFormat(string $cellHeaderFormat): static
    {
        $this->cellHeaderFormat = $cellHeaderFormat;

        return $this;
    }

    /**
     * Gets header cell format.
     */
    public function getCellHeaderFormat(): string
    {
        return $this->cellHeaderFormat;
    }

    /**
     * Sets row cell format.
     *
     * @return $this
     */
    public function setCellRowFormat(string $cellRowFormat): static
    {
        $this->cellRowFormat = $cellRowFormat;

        return $this;
    }

    /**
     * Gets row cell format.
     */
    public function getCellRowFormat(): string
    {
        return $this->cellRowFormat;
    }

    /**
     * Sets row cell content format.
     *
     * @return $this
     */
    public function setCellRowContentFormat(string $cellRowContentFormat): static
    {
        $this->cellRowContentFormat = $cellRowContentFormat;

        return $this;
    }

    /**
     * Gets row cell content format.
     */
    public function getCellRowContentFormat(): string
    {
        return $this->cellRowContentFormat;
    }

    /**
     * Sets table border format.
     *
     * @return $this
     */
    public function setBorderFormat(string $borderFormat): static
    {
        $this->borderFormat = $borderFormat;

        return $this;
    }

    /**
     * Gets table border format.
     */
    public function getBorderFormat(): string
    {
        return $this->borderFormat;
    }

    /**
     * Sets cell padding type.
     *
     * @return $this
     */
    public function setPadType(int $padType): static
    {
        if (!\in_array($padType, [\STR_PAD_LEFT, \STR_PAD_RIGHT, \STR_PAD_BOTH], true)) {
            throw new InvalidArgumentException('Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH).');
        }

        $this->padType = $padType;

        return $this;
    }

    /**
     * Gets cell padding type.
     */
    public function getPadType(): int
    {
        return $this->padType;
    }

    public function getHeaderTitleFormat(): string
    {
        return $this->headerTitleFormat;
    }

    /**
     * @return $this
     */
    public function setHeaderTitleFormat(string $format): static
    {
        $this->headerTitleFormat = $format;

        return $this;
    }

    public function getFooterTitleFormat(): string
    {
        return $this->footerTitleFormat;
    }

    /**
     * @return $this
     */
    public function setFooterTitleFormat(string $format): static
    {
        $this->footerTitleFormat = $format;

        return $this;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * @author Yewhen Khoptynskyi <khoptynskyi@gmail.com>
 */
class TableCellStyle
{
    public const DEFAULT_ALIGN = 'left';

    private const TAG_OPTIONS = [
        'fg',
        'bg',
        'options',
    ];

    private const ALIGN_MAP = [
        'left' => \STR_PAD_RIGHT,
        'center' => \STR_PAD_BOTH,
        'right' => \STR_PAD_LEFT,
    ];

    private array $options = [
        'fg' => 'default',
        'bg' => 'default',
        'options' => null,
        'align' => self::DEFAULT_ALIGN,
        'cellFormat' => null,
    ];

    public function __construct(array $options = [])
    {
        if ($diff = array_diff(array_keys($options), array_keys($this->options))) {
            throw new InvalidArgumentException(\sprintf('The TableCellStyle does not support the following options: \'%s\'.', implode('\', \'', $diff)));
        }

        if (isset($options['align']) && !\array_key_exists($options['align'], self::ALIGN_MAP)) {
            throw new InvalidArgumentException(\sprintf('Wrong align value. Value must be following: \'%s\'.', implode('\', \'', array_keys(self::ALIGN_MAP))));
        }

        $this->options = array_merge($this->options, $options);
    }

    public function getOptions(): array
    {
        return $this->options;
    }

    /**
     * Gets options we need for tag for example fg, bg.
     *
     * @return string[]
     */
    public function getTagOptions(): array
    {
        return array_filter(
            $this->getOptions(),
            fn ($key) => \in_array($key, self::TAG_OPTIONS) && isset($this->options[$key]),
            \ARRAY_FILTER_USE_KEY
        );
    }

    public function getPadByAlign(): int
    {
        return self::ALIGN_MAP[$this->getOptions()['align']];
    }

    public function getCellFormat(): ?string
    {
        return $this->getOptions()['cellFormat'];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\VarDumper\Cloner\ClonerInterface;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;

/**
 * @author Roland Franssen <franssen.roland@gmail.com>
 */
final class Dumper
{
    private OutputInterface $output;
    private ?CliDumper $dumper;
    private ?ClonerInterface $cloner;
    private \Closure $handler;

    public function __construct(OutputInterface $output, ?CliDumper $dumper = null, ?ClonerInterface $cloner = null)
    {
        $this->output = $output;
        $this->dumper = $dumper;
        $this->cloner = $cloner;

        if (class_exists(CliDumper::class)) {
            $this->handler = function ($var): string {
                $dumper = $this->dumper ??= new CliDumper(null, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR);
                $dumper->setColors($this->output->isDecorated());

                return rtrim($dumper->dump(($this->cloner ??= new VarCloner())->cloneVar($var)->withRefHandles(false), true));
            };
        } else {
            $this->handler = fn ($var): string => match (true) {
                null === $var => 'null',
                true === $var => 'true',
                false === $var => 'false',
                \is_string($var) => '"'.$var.'"',
                default => rtrim(print_r($var, true)),
            };
        }
    }

    public function __invoke(mixed $var): string
    {
        return ($this->handler)($var);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

/**
 * HelperInterface is the interface all helpers must implement.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
interface HelperInterface
{
    /**
     * Sets the helper set associated with this helper.
     *
     * @return void
     */
    public function setHelperSet(?HelperSet $helperSet);

    /**
     * Gets the helper set associated with this helper.
     */
    public function getHelperSet(): ?HelperSet;

    /**
     * Returns the canonical name of this helper.
     *
     * @return string
     */
    public function getName();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

/**
 * Helps outputting debug information when running an external program from a command.
 *
 * An external program can be a Process, an HTTP request, or anything else.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class DebugFormatterHelper extends Helper
{
    private const COLORS = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default'];
    private array $started = [];
    private int $count = -1;

    /**
     * Starts a debug formatting session.
     */
    public function start(string $id, string $message, string $prefix = 'RUN'): string
    {
        $this->started[$id] = ['border' => ++$this->count % \count(self::COLORS)];

        return \sprintf("%s<bg=blue;fg=white> %s </> <fg=blue>%s</>\n", $this->getBorder($id), $prefix, $message);
    }

    /**
     * Adds progress to a formatting session.
     */
    public function progress(string $id, string $buffer, bool $error = false, string $prefix = 'OUT', string $errorPrefix = 'ERR'): string
    {
        $message = '';

        if ($error) {
            if (isset($this->started[$id]['out'])) {
                $message .= "\n";
                unset($this->started[$id]['out']);
            }
            if (!isset($this->started[$id]['err'])) {
                $message .= \sprintf('%s<bg=red;fg=white> %s </> ', $this->getBorder($id), $errorPrefix);
                $this->started[$id]['err'] = true;
            }

            $message .= str_replace("\n", \sprintf("\n%s<bg=red;fg=white> %s </> ", $this->getBorder($id), $errorPrefix), $buffer);
        } else {
            if (isset($this->started[$id]['err'])) {
                $message .= "\n";
                unset($this->started[$id]['err']);
            }
            if (!isset($this->started[$id]['out'])) {
                $message .= \sprintf('%s<bg=green;fg=white> %s </> ', $this->getBorder($id), $prefix);
                $this->started[$id]['out'] = true;
            }

            $message .= str_replace("\n", \sprintf("\n%s<bg=green;fg=white> %s </> ", $this->getBorder($id), $prefix), $buffer);
        }

        return $message;
    }

    /**
     * Stops a formatting session.
     */
    public function stop(string $id, string $message, bool $successful, string $prefix = 'RES'): string
    {
        $trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : '';

        if ($successful) {
            return \sprintf("%s%s<bg=green;fg=white> %s </> <fg=green>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
        }

        $message = \sprintf("%s%s<bg=red;fg=white> %s </> <fg=red>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);

        unset($this->started[$id]['out'], $this->started[$id]['err']);

        return $message;
    }

    private function getBorder(string $id): string
    {
        return \sprintf('<bg=%s> </>', self::COLORS[$this->started[$id]['border']]);
    }

    public function getName(): string
    {
        return 'debug_formatter';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Kevin Bond <kevinbond@gmail.com>
 */
class ProgressIndicator
{
    private const FORMATS = [
        'normal' => ' %indicator% %message%',
        'normal_no_ansi' => ' %message%',

        'verbose' => ' %indicator% %message% (%elapsed:6s%)',
        'verbose_no_ansi' => ' %message% (%elapsed:6s%)',

        'very_verbose' => ' %indicator% %message% (%elapsed:6s%, %memory:6s%)',
        'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)',
    ];

    private OutputInterface $output;
    private int $startTime;
    private ?string $format = null;
    private ?string $message = null;
    private array $indicatorValues;
    private int $indicatorCurrent;
    private int $indicatorChangeInterval;
    private float $indicatorUpdateTime;
    private bool $started = false;

    /**
     * @var array<string, callable>
     */
    private static array $formatters;

    /**
     * @param int        $indicatorChangeInterval Change interval in milliseconds
     * @param array|null $indicatorValues         Animated indicator characters
     */
    public function __construct(OutputInterface $output, ?string $format = null, int $indicatorChangeInterval = 100, ?array $indicatorValues = null)
    {
        $this->output = $output;

        $format ??= $this->determineBestFormat();
        $indicatorValues ??= ['-', '\\', '|', '/'];
        $indicatorValues = array_values($indicatorValues);

        if (2 > \count($indicatorValues)) {
            throw new InvalidArgumentException('Must have at least 2 indicator value characters.');
        }

        $this->format = self::getFormatDefinition($format);
        $this->indicatorChangeInterval = $indicatorChangeInterval;
        $this->indicatorValues = $indicatorValues;
        $this->startTime = time();
    }

    /**
     * Sets the current indicator message.
     *
     * @return void
     */
    public function setMessage(?string $message)
    {
        $this->message = $message;

        $this->display();
    }

    /**
     * Starts the indicator output.
     *
     * @return void
     */
    public function start(string $message)
    {
        if ($this->started) {
            throw new LogicException('Progress indicator already started.');
        }

        $this->message = $message;
        $this->started = true;
        $this->startTime = time();
        $this->indicatorUpdateTime = $this->getCurrentTimeInMilliseconds() + $this->indicatorChangeInterval;
        $this->indicatorCurrent = 0;

        $this->display();
    }

    /**
     * Advances the indicator.
     *
     * @return void
     */
    public function advance()
    {
        if (!$this->started) {
            throw new LogicException('Progress indicator has not yet been started.');
        }

        if (!$this->output->isDecorated()) {
            return;
        }

        $currentTime = $this->getCurrentTimeInMilliseconds();

        if ($currentTime < $this->indicatorUpdateTime) {
            return;
        }

        $this->indicatorUpdateTime = $currentTime + $this->indicatorChangeInterval;
        ++$this->indicatorCurrent;

        $this->display();
    }

    /**
     * Finish the indicator with message.
     *
     * @return void
     */
    public function finish(string $message)
    {
        if (!$this->started) {
            throw new LogicException('Progress indicator has not yet been started.');
        }

        $this->message = $message;
        $this->display();
        $this->output->writeln('');
        $this->started = false;
    }

    /**
     * Gets the format for a given name.
     */
    public static function getFormatDefinition(string $name): ?string
    {
        return self::FORMATS[$name] ?? null;
    }

    /**
     * Sets a placeholder formatter for a given name.
     *
     * This method also allow you to override an existing placeholder.
     *
     * @return void
     */
    public static function setPlaceholderFormatterDefinition(string $name, callable $callable)
    {
        self::$formatters ??= self::initPlaceholderFormatters();

        self::$formatters[$name] = $callable;
    }

    /**
     * Gets the placeholder formatter for a given name (including the delimiter char like %).
     */
    public static function getPlaceholderFormatterDefinition(string $name): ?callable
    {
        self::$formatters ??= self::initPlaceholderFormatters();

        return self::$formatters[$name] ?? null;
    }

    private function display(): void
    {
        if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
            return;
        }

        $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) {
            if ($formatter = self::getPlaceholderFormatterDefinition($matches[1])) {
                return $formatter($this);
            }

            return $matches[0];
        }, $this->format ?? ''));
    }

    private function determineBestFormat(): string
    {
        return match ($this->output->getVerbosity()) {
            // OutputInterface::VERBOSITY_QUIET: display is disabled anyway
            OutputInterface::VERBOSITY_VERBOSE => $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi',
            OutputInterface::VERBOSITY_VERY_VERBOSE,
            OutputInterface::VERBOSITY_DEBUG => $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi',
            default => $this->output->isDecorated() ? 'normal' : 'normal_no_ansi',
        };
    }

    /**
     * Overwrites a previous message to the output.
     */
    private function overwrite(string $message): void
    {
        if ($this->output->isDecorated()) {
            $this->output->write("\x0D\x1B[2K");
            $this->output->write($message);
        } else {
            $this->output->writeln($message);
        }
    }

    private function getCurrentTimeInMilliseconds(): float
    {
        return round(microtime(true) * 1000);
    }

    /**
     * @return array<string, \Closure>
     */
    private static function initPlaceholderFormatters(): array
    {
        return [
            'indicator' => fn (self $indicator) => $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)],
            'message' => fn (self $indicator) => $indicator->message,
            'elapsed' => fn (self $indicator) => Helper::formatTime(time() - $indicator->startTime, 2),
            'memory' => fn () => Helper::formatMemory(memory_get_usage(true)),
        ];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

/**
 * Marks a row as being a separator.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class TableSeparator extends TableCell
{
    public function __construct(array $options = [])
    {
        parent::__construct('', $options);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Cursor;
use Symfony\Component\Console\Exception\MissingInputException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\StreamableInputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Terminal;

use function Symfony\Component\String\s;

/**
 * The QuestionHelper class provides helpers to interact with the user.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class QuestionHelper extends Helper
{
    /**
     * @var resource|null
     */
    private $inputStream;

    private static bool $stty = true;
    private static bool $stdinIsInteractive;

    /**
     * Asks a question to the user.
     *
     * @return mixed The user answer
     *
     * @throws RuntimeException If there is no data to read in the input stream
     */
    public function ask(InputInterface $input, OutputInterface $output, Question $question): mixed
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        if (!$input->isInteractive()) {
            return $this->getDefaultAnswer($question);
        }

        if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) {
            $this->inputStream = $stream;
        }

        try {
            if (!$question->getValidator()) {
                return $this->doAsk($output, $question);
            }

            $interviewer = fn () => $this->doAsk($output, $question);

            return $this->validateAttempts($interviewer, $output, $question);
        } catch (MissingInputException $exception) {
            $input->setInteractive(false);

            if (null === $fallbackOutput = $this->getDefaultAnswer($question)) {
                throw $exception;
            }

            return $fallbackOutput;
        }
    }

    public function getName(): string
    {
        return 'question';
    }

    /**
     * Prevents usage of stty.
     *
     * @return void
     */
    public static function disableStty()
    {
        self::$stty = false;
    }

    /**
     * Asks the question to the user.
     *
     * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
     */
    private function doAsk(OutputInterface $output, Question $question): mixed
    {
        $this->writePrompt($output, $question);

        $inputStream = $this->inputStream ?: \STDIN;
        $autocomplete = $question->getAutocompleterCallback();

        if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) {
            $ret = false;
            if ($question->isHidden()) {
                try {
                    $hiddenResponse = $this->getHiddenResponse($output, $inputStream, $question->isTrimmable());
                    $ret = $question->isTrimmable() ? trim($hiddenResponse) : $hiddenResponse;
                } catch (RuntimeException $e) {
                    if (!$question->isHiddenFallback()) {
                        throw $e;
                    }
                }
            }

            if (false === $ret) {
                $isBlocked = stream_get_meta_data($inputStream)['blocked'] ?? true;

                if (!$isBlocked) {
                    stream_set_blocking($inputStream, true);
                }

                $ret = $this->readInput($inputStream, $question);

                if (!$isBlocked) {
                    stream_set_blocking($inputStream, false);
                }

                if (false === $ret) {
                    throw new MissingInputException('Aborted.');
                }
                if ($question->isTrimmable()) {
                    $ret = trim($ret);
                }
            }
        } else {
            $autocomplete = $this->autocomplete($output, $question, $inputStream, $autocomplete);
            $ret = $question->isTrimmable() ? trim($autocomplete) : $autocomplete;
        }

        if ($output instanceof ConsoleSectionOutput) {
            $output->addContent(''); // add EOL to the question
            $output->addContent($ret);
        }

        $ret = \strlen($ret) > 0 ? $ret : $question->getDefault();

        if ($normalizer = $question->getNormalizer()) {
            return $normalizer($ret);
        }

        return $ret;
    }

    private function getDefaultAnswer(Question $question): mixed
    {
        $default = $question->getDefault();

        if (null === $default) {
            return $default;
        }

        if ($validator = $question->getValidator()) {
            return \call_user_func($validator, $default);
        } elseif ($question instanceof ChoiceQuestion) {
            $choices = $question->getChoices();

            if (!$question->isMultiselect()) {
                return $choices[$default] ?? $default;
            }

            $default = explode(',', $default);
            foreach ($default as $k => $v) {
                $v = $question->isTrimmable() ? trim($v) : $v;
                $default[$k] = $choices[$v] ?? $v;
            }
        }

        return $default;
    }

    /**
     * Outputs the question prompt.
     *
     * @return void
     */
    protected function writePrompt(OutputInterface $output, Question $question)
    {
        $message = $question->getQuestion();

        if ($question instanceof ChoiceQuestion) {
            $output->writeln(array_merge([
                $question->getQuestion(),
            ], $this->formatChoiceQuestionChoices($question, 'info')));

            $message = $question->getPrompt();
        }

        $output->write($message);
    }

    /**
     * @return string[]
     */
    protected function formatChoiceQuestionChoices(ChoiceQuestion $question, string $tag): array
    {
        $messages = [];

        $maxWidth = max(array_map([__CLASS__, 'width'], array_keys($choices = $question->getChoices())));

        foreach ($choices as $key => $value) {
            $padding = str_repeat(' ', $maxWidth - self::width($key));

            $messages[] = \sprintf("  [<$tag>%s$padding</$tag>] %s", $key, $value);
        }

        return $messages;
    }

    /**
     * Outputs an error message.
     *
     * @return void
     */
    protected function writeError(OutputInterface $output, \Exception $error)
    {
        if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) {
            $message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error');
        } else {
            $message = '<error>'.$error->getMessage().'</error>';
        }

        $output->writeln($message);
    }

    /**
     * Autocompletes a question.
     *
     * @param resource $inputStream
     */
    private function autocomplete(OutputInterface $output, Question $question, $inputStream, callable $autocomplete): string
    {
        $cursor = new Cursor($output, $inputStream);

        $fullChoice = '';
        $ret = '';

        $i = 0;
        $ofs = -1;
        $matches = $autocomplete($ret);
        $numMatches = \count($matches);

        $sttyMode = shell_exec('stty -g');
        $isStdin = 'php://stdin' === (stream_get_meta_data($inputStream)['uri'] ?? null);
        $r = [$inputStream];
        $w = [];

        // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead)
        shell_exec('stty -icanon -echo');

        // Add highlighted text style
        $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white'));

        // Read a keypress
        while (!feof($inputStream)) {
            while ($isStdin && 0 === @stream_select($r, $w, $w, 0, 100)) {
                // Give signal handlers a chance to run
                $r = [$inputStream];
            }
            $c = fread($inputStream, 1);

            // as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false.
            if (false === $c || ('' === $ret && '' === $c && null === $question->getDefault())) {
                shell_exec('stty '.$sttyMode);
                throw new MissingInputException('Aborted.');
            } elseif ("\177" === $c) { // Backspace Character
                if (0 === $numMatches && 0 !== $i) {
                    --$i;
                    $cursor->moveLeft(s($fullChoice)->slice(-1)->width(false));

                    $fullChoice = self::substr($fullChoice, 0, $i);
                }

                if (0 === $i) {
                    $ofs = -1;
                    $matches = $autocomplete($ret);
                    $numMatches = \count($matches);
                } else {
                    $numMatches = 0;
                }

                // Pop the last character off the end of our string
                $ret = self::substr($ret, 0, $i);
            } elseif ("\033" === $c) {
                // Did we read an escape sequence?
                $c .= fread($inputStream, 2);

                // A = Up Arrow. B = Down Arrow
                if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) {
                    if ('A' === $c[2] && -1 === $ofs) {
                        $ofs = 0;
                    }

                    if (0 === $numMatches) {
                        continue;
                    }

                    $ofs += ('A' === $c[2]) ? -1 : 1;
                    $ofs = ($numMatches + $ofs) % $numMatches;
                }
            } elseif (\ord($c) < 32) {
                if ("\t" === $c || "\n" === $c) {
                    if ($numMatches > 0 && -1 !== $ofs) {
                        $ret = (string) $matches[$ofs];
                        // Echo out remaining chars for current match
                        $remainingCharacters = substr($ret, \strlen(trim($this->mostRecentlyEnteredValue($fullChoice))));
                        $output->write($remainingCharacters);
                        $fullChoice .= $remainingCharacters;
                        $i = (false === $encoding = mb_detect_encoding($fullChoice, null, true)) ? \strlen($fullChoice) : mb_strlen($fullChoice, $encoding);

                        $matches = array_filter(
                            $autocomplete($ret),
                            fn ($match) => '' === $ret || str_starts_with($match, $ret)
                        );
                        $numMatches = \count($matches);
                        $ofs = -1;
                    }

                    if ("\n" === $c) {
                        $output->write($c);
                        break;
                    }

                    $numMatches = 0;
                }

                continue;
            } else {
                if ("\x80" <= $c) {
                    $c .= fread($inputStream, ["\xC0" => 1, "\xD0" => 1, "\xE0" => 2, "\xF0" => 3][$c & "\xF0"]);
                }

                $output->write($c);
                $ret .= $c;
                $fullChoice .= $c;
                ++$i;

                $tempRet = $ret;

                if ($question instanceof ChoiceQuestion && $question->isMultiselect()) {
                    $tempRet = $this->mostRecentlyEnteredValue($fullChoice);
                }

                $numMatches = 0;
                $ofs = 0;

                foreach ($autocomplete($ret) as $value) {
                    // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
                    if (str_starts_with($value, $tempRet)) {
                        $matches[$numMatches++] = $value;
                    }
                }
            }

            $cursor->clearLineAfter();

            if ($numMatches > 0 && -1 !== $ofs) {
                $cursor->savePosition();
                // Write highlighted text, complete the partially entered response
                $charactersEntered = \strlen(trim($this->mostRecentlyEnteredValue($fullChoice)));
                $output->write('<hl>'.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $charactersEntered)).'</hl>');
                $cursor->restorePosition();
            }
        }

        // Reset stty so it behaves normally again
        shell_exec('stty '.$sttyMode);

        return $fullChoice;
    }

    private function mostRecentlyEnteredValue(string $entered): string
    {
        // Determine the most recent value that the user entered
        if (!str_contains($entered, ',')) {
            return $entered;
        }

        $choices = explode(',', $entered);
        if ('' !== $lastChoice = trim($choices[\count($choices) - 1])) {
            return $lastChoice;
        }

        return $entered;
    }

    /**
     * Gets a hidden response from user.
     *
     * @param resource $inputStream The handler resource
     * @param bool     $trimmable   Is the answer trimmable
     *
     * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
     */
    private function getHiddenResponse(OutputInterface $output, $inputStream, bool $trimmable = true): string
    {
        if ('\\' === \DIRECTORY_SEPARATOR) {
            $exe = __DIR__.'/../Resources/bin/hiddeninput.exe';

            // handle code running from a phar
            if (str_starts_with(__FILE__, 'phar:')) {
                $tmpExe = sys_get_temp_dir().'/hiddeninput.exe';
                copy($exe, $tmpExe);
                $exe = $tmpExe;
            }

            $sExec = shell_exec('"'.$exe.'"');
            $value = $trimmable ? rtrim($sExec) : $sExec;
            $output->writeln('');

            if (isset($tmpExe)) {
                unlink($tmpExe);
            }

            return $value;
        }

        if (self::$stty && Terminal::hasSttyAvailable()) {
            $sttyMode = shell_exec('stty -g');
            shell_exec('stty -echo');
        } elseif ($this->isInteractiveInput($inputStream)) {
            throw new RuntimeException('Unable to hide the response.');
        }

        $value = fgets($inputStream, 4096);

        if (4095 === \strlen($value)) {
            $errOutput = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output;
            $errOutput->warning('The value was possibly truncated by your shell or terminal emulator');
        }

        if (self::$stty && Terminal::hasSttyAvailable()) {
            shell_exec('stty '.$sttyMode);
        }

        if (false === $value) {
            throw new MissingInputException('Aborted.');
        }
        if ($trimmable) {
            $value = trim($value);
        }
        $output->writeln('');

        return $value;
    }

    /**
     * Validates an attempt.
     *
     * @param callable $interviewer A callable that will ask for a question and return the result
     *
     * @throws \Exception In case the max number of attempts has been reached and no valid response has been given
     */
    private function validateAttempts(callable $interviewer, OutputInterface $output, Question $question): mixed
    {
        $error = null;
        $attempts = $question->getMaxAttempts();

        while (null === $attempts || $attempts--) {
            if (null !== $error) {
                $this->writeError($output, $error);
            }

            try {
                return $question->getValidator()($interviewer());
            } catch (RuntimeException $e) {
                throw $e;
            } catch (\Exception $error) {
            }
        }

        throw $error;
    }

    private function isInteractiveInput($inputStream): bool
    {
        if ('php://stdin' !== (stream_get_meta_data($inputStream)['uri'] ?? null)) {
            return false;
        }

        if (isset(self::$stdinIsInteractive)) {
            return self::$stdinIsInteractive;
        }

        return self::$stdinIsInteractive = @stream_isatty(fopen('php://stdin', 'r'));
    }

    /**
     * Reads one or more lines of input and returns what is read.
     *
     * @param resource $inputStream The handler resource
     * @param Question $question    The question being asked
     */
    private function readInput($inputStream, Question $question): string|false
    {
        if (!$question->isMultiline()) {
            $cp = $this->setIOCodepage();
            $ret = fgets($inputStream, 4096);

            return $this->resetIOCodepage($cp, $ret);
        }

        $multiLineStreamReader = $this->cloneInputStream($inputStream);
        if (null === $multiLineStreamReader) {
            return false;
        }

        $ret = '';
        $cp = $this->setIOCodepage();
        while (false !== ($char = fgetc($multiLineStreamReader))) {
            if ("\x4" === $char || \PHP_EOL === "{$ret}{$char}") {
                break;
            }
            $ret .= $char;
        }

        if (stream_get_meta_data($inputStream)['seekable']) {
            fseek($inputStream, ftell($multiLineStreamReader));
        }

        return $this->resetIOCodepage($cp, $ret);
    }

    private function setIOCodepage(): int
    {
        if (\function_exists('sapi_windows_cp_set')) {
            $cp = sapi_windows_cp_get();
            sapi_windows_cp_set(sapi_windows_cp_get('oem'));

            return $cp;
        }

        return 0;
    }

    /**
     * Sets console I/O to the specified code page and converts the user input.
     */
    private function resetIOCodepage(int $cp, string|false $input): string|false
    {
        if (0 !== $cp) {
            sapi_windows_cp_set($cp);

            if (false !== $input && '' !== $input) {
                $input = sapi_windows_cp_conv(sapi_windows_cp_get('oem'), $cp, $input);
            }
        }

        return $input;
    }

    /**
     * Clones an input stream in order to act on one instance of the same
     * stream without affecting the other instance.
     *
     * @param resource $inputStream The handler resource
     *
     * @return resource|null The cloned resource, null in case it could not be cloned
     */
    private function cloneInputStream($inputStream)
    {
        $streamMetaData = stream_get_meta_data($inputStream);
        $seekable = $streamMetaData['seekable'] ?? false;
        $mode = $streamMetaData['mode'] ?? 'rb';
        $uri = $streamMetaData['uri'] ?? null;

        if (null === $uri) {
            return null;
        }

        $cloneStream = fopen($uri, $mode);

        // For seekable and writable streams, add all the same data to the
        // cloned stream and then seek to the same offset.
        if (true === $seekable && !\in_array($mode, ['r', 'rb', 'rt'])) {
            $offset = ftell($inputStream);
            rewind($inputStream);
            stream_copy_to_stream($inputStream, $cloneStream);
            fseek($inputStream, $offset);
            fseek($cloneStream, $offset);
        }

        return $cloneStream;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

/**
 * @internal
 */
class TableRows implements \IteratorAggregate
{
    private \Closure $generator;

    public function __construct(\Closure $generator)
    {
        $this->generator = $generator;
    }

    public function getIterator(): \Traversable
    {
        return ($this->generator)();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\String\UnicodeString;

/**
 * Helper is the base class for all helper classes.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
abstract class Helper implements HelperInterface
{
    protected $helperSet;

    /**
     * @return void
     */
    public function setHelperSet(?HelperSet $helperSet = null)
    {
        if (1 > \func_num_args()) {
            trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
        }
        $this->helperSet = $helperSet;
    }

    public function getHelperSet(): ?HelperSet
    {
        return $this->helperSet;
    }

    /**
     * Returns the width of a string, using mb_strwidth if it is available.
     * The width is how many characters positions the string will use.
     */
    public static function width(?string $string): int
    {
        $string ??= '';

        if (preg_match('//u', $string)) {
            $string = preg_replace('/[\p{Cc}\x7F]++/u', '', $string, -1, $count);

            return (new UnicodeString($string))->width(false) + $count;
        }

        if (false === $encoding = mb_detect_encoding($string, null, true)) {
            return \strlen($string);
        }

        return mb_strwidth($string, $encoding);
    }

    /**
     * Returns the length of a string, using mb_strlen if it is available.
     * The length is related to how many bytes the string will use.
     */
    public static function length(?string $string): int
    {
        $string ??= '';

        if (preg_match('//u', $string)) {
            return (new UnicodeString($string))->length();
        }

        if (false === $encoding = mb_detect_encoding($string, null, true)) {
            return \strlen($string);
        }

        return mb_strlen($string, $encoding);
    }

    /**
     * Returns the subset of a string, using mb_substr if it is available.
     */
    public static function substr(?string $string, int $from, ?int $length = null): string
    {
        $string ??= '';

        if (preg_match('//u', $string)) {
            return (new UnicodeString($string))->slice($from, $length);
        }

        if (false === $encoding = mb_detect_encoding($string, null, true)) {
            return substr($string, $from, $length);
        }

        return mb_substr($string, $from, $length, $encoding);
    }

    /**
     * @return string
     */
    public static function formatTime(int|float $secs, int $precision = 1)
    {
        $secs = (int) floor($secs);

        if (0 === $secs) {
            return '< 1 sec';
        }

        static $timeFormats = [
            [1, '1 sec', 'secs'],
            [60, '1 min', 'mins'],
            [3600, '1 hr', 'hrs'],
            [86400, '1 day', 'days'],
        ];

        $times = [];
        foreach ($timeFormats as $index => $format) {
            $seconds = isset($timeFormats[$index + 1]) ? $secs % $timeFormats[$index + 1][0] : $secs;

            if (isset($times[$index - $precision])) {
                unset($times[$index - $precision]);
            }

            if (0 === $seconds) {
                continue;
            }

            $unitCount = ($seconds / $format[0]);
            $times[$index] = 1 === $unitCount ? $format[1] : $unitCount.' '.$format[2];

            if ($secs === $seconds) {
                break;
            }

            $secs -= $seconds;
        }

        return implode(', ', array_reverse($times));
    }

    /**
     * @return string
     */
    public static function formatMemory(int $memory)
    {
        if ($memory >= 1024 * 1024 * 1024) {
            return \sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024);
        }

        if ($memory >= 1024 * 1024) {
            return \sprintf('%.1f MiB', $memory / 1024 / 1024);
        }

        if ($memory >= 1024) {
            return \sprintf('%d KiB', $memory / 1024);
        }

        return \sprintf('%d B', $memory);
    }

    /**
     * @return string
     */
    public static function removeDecoration(OutputFormatterInterface $formatter, ?string $string)
    {
        $isDecorated = $formatter->isDecorated();
        $formatter->setDecorated(false);
        // remove <...> formatting
        $string = $formatter->format($string ?? '');
        // remove already formatted characters
        $string = preg_replace("/\033\[[^m]*m/", '', $string ?? '');
        // remove terminal hyperlinks
        $string = preg_replace('/\\033]8;[^;]*;[^\\033]*\\033\\\\/', '', $string ?? '');
        $formatter->setDecorated($isDecorated);

        return $string;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Cursor;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Terminal;

/**
 * The ProgressBar provides helpers to display progress output.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Chris Jones <leeked@gmail.com>
 */
final class ProgressBar
{
    public const FORMAT_VERBOSE = 'verbose';
    public const FORMAT_VERY_VERBOSE = 'very_verbose';
    public const FORMAT_DEBUG = 'debug';
    public const FORMAT_NORMAL = 'normal';

    private const FORMAT_VERBOSE_NOMAX = 'verbose_nomax';
    private const FORMAT_VERY_VERBOSE_NOMAX = 'very_verbose_nomax';
    private const FORMAT_DEBUG_NOMAX = 'debug_nomax';
    private const FORMAT_NORMAL_NOMAX = 'normal_nomax';

    private int $barWidth = 28;
    private string $barChar;
    private string $emptyBarChar = '-';
    private string $progressChar = '>';
    private ?string $format = null;
    private ?string $internalFormat = null;
    private ?int $redrawFreq = 1;
    private int $writeCount = 0;
    private float $lastWriteTime = 0;
    private float $minSecondsBetweenRedraws = 0;
    private float $maxSecondsBetweenRedraws = 1;
    private OutputInterface $output;
    private int $step = 0;
    private int $startingStep = 0;
    private ?int $max = null;
    private int $startTime;
    private int $stepWidth;
    private float $percent = 0.0;
    private array $messages = [];
    private bool $overwrite = true;
    private Terminal $terminal;
    private ?string $previousMessage = null;
    private Cursor $cursor;
    private array $placeholders = [];

    private static array $formatters;
    private static array $formats;

    /**
     * @param int $max Maximum steps (0 if unknown)
     */
    public function __construct(OutputInterface $output, int $max = 0, float $minSecondsBetweenRedraws = 1 / 25)
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        $this->output = $output;
        $this->setMaxSteps($max);
        $this->terminal = new Terminal();

        if (0 < $minSecondsBetweenRedraws) {
            $this->redrawFreq = null;
            $this->minSecondsBetweenRedraws = $minSecondsBetweenRedraws;
        }

        if (!$this->output->isDecorated()) {
            // disable overwrite when output does not support ANSI codes.
            $this->overwrite = false;

            // set a reasonable redraw frequency so output isn't flooded
            $this->redrawFreq = null;
        }

        $this->startTime = time();
        $this->cursor = new Cursor($output);
    }

    /**
     * Sets a placeholder formatter for a given name, globally for all instances of ProgressBar.
     *
     * This method also allow you to override an existing placeholder.
     *
     * @param string                       $name     The placeholder name (including the delimiter char like %)
     * @param callable(ProgressBar):string $callable A PHP callable
     */
    public static function setPlaceholderFormatterDefinition(string $name, callable $callable): void
    {
        self::$formatters ??= self::initPlaceholderFormatters();

        self::$formatters[$name] = $callable;
    }

    /**
     * Gets the placeholder formatter for a given name.
     *
     * @param string $name The placeholder name (including the delimiter char like %)
     */
    public static function getPlaceholderFormatterDefinition(string $name): ?callable
    {
        self::$formatters ??= self::initPlaceholderFormatters();

        return self::$formatters[$name] ?? null;
    }

    /**
     * Sets a placeholder formatter for a given name, for this instance only.
     *
     * @param callable(ProgressBar):string $callable A PHP callable
     */
    public function setPlaceholderFormatter(string $name, callable $callable): void
    {
        $this->placeholders[$name] = $callable;
    }

    /**
     * Gets the placeholder formatter for a given name.
     *
     * @param string $name The placeholder name (including the delimiter char like %)
     */
    public function getPlaceholderFormatter(string $name): ?callable
    {
        return $this->placeholders[$name] ?? $this::getPlaceholderFormatterDefinition($name);
    }

    /**
     * Sets a format for a given name.
     *
     * This method also allow you to override an existing format.
     *
     * @param string $name   The format name
     * @param string $format A format string
     */
    public static function setFormatDefinition(string $name, string $format): void
    {
        self::$formats ??= self::initFormats();

        self::$formats[$name] = $format;
    }

    /**
     * Gets the format for a given name.
     *
     * @param string $name The format name
     */
    public static function getFormatDefinition(string $name): ?string
    {
        self::$formats ??= self::initFormats();

        return self::$formats[$name] ?? null;
    }

    /**
     * Associates a text with a named placeholder.
     *
     * The text is displayed when the progress bar is rendered but only
     * when the corresponding placeholder is part of the custom format line
     * (by wrapping the name with %).
     *
     * @param string $message The text to associate with the placeholder
     * @param string $name    The name of the placeholder
     */
    public function setMessage(string $message, string $name = 'message'): void
    {
        $this->messages[$name] = $message;
    }

    public function getMessage(string $name = 'message'): ?string
    {
        return $this->messages[$name] ?? null;
    }

    public function getStartTime(): int
    {
        return $this->startTime;
    }

    public function getMaxSteps(): int
    {
        return $this->max;
    }

    public function getProgress(): int
    {
        return $this->step;
    }

    private function getStepWidth(): int
    {
        return $this->stepWidth;
    }

    public function getProgressPercent(): float
    {
        return $this->percent;
    }

    public function getBarOffset(): float
    {
        return floor($this->max ? $this->percent * $this->barWidth : (null === $this->redrawFreq ? (int) (min(5, $this->barWidth / 15) * $this->writeCount) : $this->step) % $this->barWidth);
    }

    public function getEstimated(): float
    {
        if (0 === $this->step || $this->step === $this->startingStep) {
            return 0;
        }

        return round((time() - $this->startTime) / ($this->step - $this->startingStep) * $this->max);
    }

    public function getRemaining(): float
    {
        if (0 === $this->step || $this->step === $this->startingStep) {
            return 0;
        }

        return round((time() - $this->startTime) / ($this->step - $this->startingStep) * ($this->max - $this->step));
    }

    public function setBarWidth(int $size): void
    {
        $this->barWidth = max(1, $size);
    }

    public function getBarWidth(): int
    {
        return $this->barWidth;
    }

    public function setBarCharacter(string $char): void
    {
        $this->barChar = $char;
    }

    public function getBarCharacter(): string
    {
        return $this->barChar ?? ($this->max ? '=' : $this->emptyBarChar);
    }

    public function setEmptyBarCharacter(string $char): void
    {
        $this->emptyBarChar = $char;
    }

    public function getEmptyBarCharacter(): string
    {
        return $this->emptyBarChar;
    }

    public function setProgressCharacter(string $char): void
    {
        $this->progressChar = $char;
    }

    public function getProgressCharacter(): string
    {
        return $this->progressChar;
    }

    public function setFormat(string $format): void
    {
        $this->format = null;
        $this->internalFormat = $format;
    }

    /**
     * Sets the redraw frequency.
     *
     * @param int|null $freq The frequency in steps
     */
    public function setRedrawFrequency(?int $freq): void
    {
        $this->redrawFreq = null !== $freq ? max(1, $freq) : null;
    }

    public function minSecondsBetweenRedraws(float $seconds): void
    {
        $this->minSecondsBetweenRedraws = $seconds;
    }

    public function maxSecondsBetweenRedraws(float $seconds): void
    {
        $this->maxSecondsBetweenRedraws = $seconds;
    }

    /**
     * Returns an iterator that will automatically update the progress bar when iterated.
     *
     * @template TKey
     * @template TValue
     *
     * @param iterable<TKey, TValue> $iterable
     * @param int|null               $max      Number of steps to complete the bar (0 if indeterminate), if null it will be inferred from $iterable
     *
     * @return iterable<TKey, TValue>
     */
    public function iterate(iterable $iterable, ?int $max = null): iterable
    {
        $this->start($max ?? (is_countable($iterable) ? \count($iterable) : 0));

        foreach ($iterable as $key => $value) {
            yield $key => $value;

            $this->advance();
        }

        $this->finish();
    }

    /**
     * Starts the progress output.
     *
     * @param int|null $max     Number of steps to complete the bar (0 if indeterminate), null to leave unchanged
     * @param int      $startAt The starting point of the bar (useful e.g. when resuming a previously started bar)
     */
    public function start(?int $max = null, int $startAt = 0): void
    {
        $this->startTime = time();
        $this->step = $startAt;
        $this->startingStep = $startAt;

        $startAt > 0 ? $this->setProgress($startAt) : $this->percent = 0.0;

        if (null !== $max) {
            $this->setMaxSteps($max);
        }

        $this->display();
    }

    /**
     * Advances the progress output X steps.
     *
     * @param int $step Number of steps to advance
     */
    public function advance(int $step = 1): void
    {
        $this->setProgress($this->step + $step);
    }

    /**
     * Sets whether to overwrite the progressbar, false for new line.
     */
    public function setOverwrite(bool $overwrite): void
    {
        $this->overwrite = $overwrite;
    }

    public function setProgress(int $step): void
    {
        if ($this->max && $step > $this->max) {
            $this->max = $step;
        } elseif ($step < 0) {
            $step = 0;
        }

        $redrawFreq = $this->redrawFreq ?? (($this->max ?: 10) / 10);
        $prevPeriod = (int) ($this->step / $redrawFreq);
        $currPeriod = (int) ($step / $redrawFreq);
        $this->step = $step;
        $this->percent = $this->max ? (float) $this->step / $this->max : 0;
        $timeInterval = microtime(true) - $this->lastWriteTime;

        // Draw regardless of other limits
        if ($this->max === $step) {
            $this->display();

            return;
        }

        // Throttling
        if ($timeInterval < $this->minSecondsBetweenRedraws) {
            return;
        }

        // Draw each step period, but not too late
        if ($prevPeriod !== $currPeriod || $timeInterval >= $this->maxSecondsBetweenRedraws) {
            $this->display();
        }
    }

    public function setMaxSteps(int $max): void
    {
        $this->format = null;
        $this->max = max(0, $max);
        $this->stepWidth = $this->max ? Helper::width((string) $this->max) : 4;
    }

    /**
     * Finishes the progress output.
     */
    public function finish(): void
    {
        if (!$this->max) {
            $this->max = $this->step;
        }

        if ($this->step === $this->max && !$this->overwrite) {
            // prevent double 100% output
            return;
        }

        $this->setProgress($this->max);
    }

    /**
     * Outputs the current progress string.
     */
    public function display(): void
    {
        if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
            return;
        }

        if (null === $this->format) {
            $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
        }

        $this->overwrite($this->buildLine());
    }

    /**
     * Removes the progress bar from the current line.
     *
     * This is useful if you wish to write some output
     * while a progress bar is running.
     * Call display() to show the progress bar again.
     */
    public function clear(): void
    {
        if (!$this->overwrite) {
            return;
        }

        if (null === $this->format) {
            $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
        }

        $this->overwrite('');
    }

    private function setRealFormat(string $format): void
    {
        // try to use the _nomax variant if available
        if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) {
            $this->format = self::getFormatDefinition($format.'_nomax');
        } elseif (null !== self::getFormatDefinition($format)) {
            $this->format = self::getFormatDefinition($format);
        } else {
            $this->format = $format;
        }
    }

    /**
     * Overwrites a previous message to the output.
     */
    private function overwrite(string $message): void
    {
        if ($this->previousMessage === $message) {
            return;
        }

        $originalMessage = $message;

        if ($this->overwrite) {
            if (null !== $this->previousMessage) {
                if ($this->output instanceof ConsoleSectionOutput) {
                    $messageLines = explode("\n", $this->previousMessage);
                    $lineCount = \count($messageLines);

                    $lastLineWithoutDecoration = Helper::removeDecoration($this->output->getFormatter(), end($messageLines) ?? '');

                    // When the last previous line is empty (without formatting) it is already cleared by the section output, so we don't need to clear it again
                    if ('' === $lastLineWithoutDecoration) {
                        --$lineCount;
                    }

                    foreach ($messageLines as $messageLine) {
                        $messageLineLength = Helper::width(Helper::removeDecoration($this->output->getFormatter(), $messageLine));
                        if ($messageLineLength > $this->terminal->getWidth()) {
                            $lineCount += floor($messageLineLength / $this->terminal->getWidth());
                        }
                    }

                    $this->output->clear($lineCount);
                } else {
                    $lineCount = substr_count($this->previousMessage, "\n");
                    for ($i = 0; $i < $lineCount; ++$i) {
                        $this->cursor->moveToColumn(1);
                        $this->cursor->clearLine();
                        $this->cursor->moveUp();
                    }

                    $this->cursor->moveToColumn(1);
                    $this->cursor->clearLine();
                }
            }
        } elseif ($this->step > 0) {
            $message = \PHP_EOL.$message;
        }

        $this->previousMessage = $originalMessage;
        $this->lastWriteTime = microtime(true);

        $this->output->write($message);
        ++$this->writeCount;
    }

    private function determineBestFormat(): string
    {
        return match ($this->output->getVerbosity()) {
            // OutputInterface::VERBOSITY_QUIET: display is disabled anyway
            OutputInterface::VERBOSITY_VERBOSE => $this->max ? self::FORMAT_VERBOSE : self::FORMAT_VERBOSE_NOMAX,
            OutputInterface::VERBOSITY_VERY_VERBOSE => $this->max ? self::FORMAT_VERY_VERBOSE : self::FORMAT_VERY_VERBOSE_NOMAX,
            OutputInterface::VERBOSITY_DEBUG => $this->max ? self::FORMAT_DEBUG : self::FORMAT_DEBUG_NOMAX,
            default => $this->max ? self::FORMAT_NORMAL : self::FORMAT_NORMAL_NOMAX,
        };
    }

    private static function initPlaceholderFormatters(): array
    {
        return [
            'bar' => function (self $bar, OutputInterface $output) {
                $completeBars = $bar->getBarOffset();
                $display = str_repeat($bar->getBarCharacter(), $completeBars);
                if ($completeBars < $bar->getBarWidth()) {
                    $emptyBars = $bar->getBarWidth() - $completeBars - Helper::length(Helper::removeDecoration($output->getFormatter(), $bar->getProgressCharacter()));
                    $display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars);
                }

                return $display;
            },
            'elapsed' => fn (self $bar) => Helper::formatTime(time() - $bar->getStartTime(), 2),
            'remaining' => function (self $bar) {
                if (!$bar->getMaxSteps()) {
                    throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.');
                }

                return Helper::formatTime($bar->getRemaining(), 2);
            },
            'estimated' => function (self $bar) {
                if (!$bar->getMaxSteps()) {
                    throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.');
                }

                return Helper::formatTime($bar->getEstimated(), 2);
            },
            'memory' => fn (self $bar) => Helper::formatMemory(memory_get_usage(true)),
            'current' => fn (self $bar) => str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', \STR_PAD_LEFT),
            'max' => fn (self $bar) => $bar->getMaxSteps(),
            'percent' => fn (self $bar) => floor($bar->getProgressPercent() * 100),
        ];
    }

    private static function initFormats(): array
    {
        return [
            self::FORMAT_NORMAL => ' %current%/%max% [%bar%] %percent:3s%%',
            self::FORMAT_NORMAL_NOMAX => ' %current% [%bar%]',

            self::FORMAT_VERBOSE => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%',
            self::FORMAT_VERBOSE_NOMAX => ' %current% [%bar%] %elapsed:6s%',

            self::FORMAT_VERY_VERBOSE => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%',
            self::FORMAT_VERY_VERBOSE_NOMAX => ' %current% [%bar%] %elapsed:6s%',

            self::FORMAT_DEBUG => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%',
            self::FORMAT_DEBUG_NOMAX => ' %current% [%bar%] %elapsed:6s% %memory:6s%',
        ];
    }

    private function buildLine(): string
    {
        \assert(null !== $this->format);

        $regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i";
        $callback = function ($matches) {
            if ($formatter = $this->getPlaceholderFormatter($matches[1])) {
                $text = $formatter($this, $this->output);
            } elseif (isset($this->messages[$matches[1]])) {
                $text = $this->messages[$matches[1]];
            } else {
                return $matches[0];
            }

            if (isset($matches[2])) {
                $text = \sprintf('%'.$matches[2], $text);
            }

            return $text;
        };
        $line = preg_replace_callback($regex, $callback, $this->format);

        // gets string length for each sub line with multiline format
        $linesLength = array_map(fn ($subLine) => Helper::width(Helper::removeDecoration($this->output->getFormatter(), rtrim($subLine, "\r"))), explode("\n", $line));

        $linesWidth = max($linesLength);

        $terminalWidth = $this->terminal->getWidth();
        if ($linesWidth <= $terminalWidth) {
            return $line;
        }

        $this->setBarWidth($this->barWidth - $linesWidth + $terminalWidth);

        return preg_replace_callback($regex, $callback, $this->format);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;

/**
 * The ProcessHelper class provides helpers to run external processes.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @final
 */
class ProcessHelper extends Helper
{
    /**
     * Runs an external process.
     *
     * @param array|Process $cmd      An instance of Process or an array of the command and arguments
     * @param callable|null $callback A PHP callback to run whenever there is some
     *                                output available on STDOUT or STDERR
     */
    public function run(OutputInterface $output, array|Process $cmd, ?string $error = null, ?callable $callback = null, int $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE): Process
    {
        if (!class_exists(Process::class)) {
            throw new \LogicException('The ProcessHelper cannot be run as the Process component is not installed. Try running "compose require symfony/process".');
        }

        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        $formatter = $this->getHelperSet()->get('debug_formatter');

        if ($cmd instanceof Process) {
            $cmd = [$cmd];
        }

        if (\is_string($cmd[0] ?? null)) {
            $process = new Process($cmd);
            $cmd = [];
        } elseif (($cmd[0] ?? null) instanceof Process) {
            $process = $cmd[0];
            unset($cmd[0]);
        } else {
            throw new \InvalidArgumentException(\sprintf('Invalid command provided to "%s()": the command should be an array whose first element is either the path to the binary to run or a "Process" object.', __METHOD__));
        }

        if ($verbosity <= $output->getVerbosity()) {
            $output->write($formatter->start(spl_object_hash($process), $this->escapeString($process->getCommandLine())));
        }

        if ($output->isDebug()) {
            $callback = $this->wrapCallback($output, $process, $callback);
        }

        $process->run($callback, $cmd);

        if ($verbosity <= $output->getVerbosity()) {
            $message = $process->isSuccessful() ? 'Command ran successfully' : \sprintf('%s Command did not run successfully', $process->getExitCode());
            $output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful()));
        }

        if (!$process->isSuccessful() && null !== $error) {
            $output->writeln(\sprintf('<error>%s</error>', $this->escapeString($error)));
        }

        return $process;
    }

    /**
     * Runs the process.
     *
     * This is identical to run() except that an exception is thrown if the process
     * exits with a non-zero exit code.
     *
     * @param array|Process $cmd      An instance of Process or a command to run
     * @param callable|null $callback A PHP callback to run whenever there is some
     *                                output available on STDOUT or STDERR
     *
     * @throws ProcessFailedException
     *
     * @see run()
     */
    public function mustRun(OutputInterface $output, array|Process $cmd, ?string $error = null, ?callable $callback = null): Process
    {
        $process = $this->run($output, $cmd, $error, $callback);

        if (!$process->isSuccessful()) {
            throw new ProcessFailedException($process);
        }

        return $process;
    }

    /**
     * Wraps a Process callback to add debugging output.
     */
    public function wrapCallback(OutputInterface $output, Process $process, ?callable $callback = null): callable
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        $formatter = $this->getHelperSet()->get('debug_formatter');

        return function ($type, $buffer) use ($output, $process, $callback, $formatter) {
            $output->write($formatter->progress(spl_object_hash($process), $this->escapeString($buffer), Process::ERR === $type));

            if (null !== $callback) {
                $callback($type, $buffer);
            }
        };
    }

    private function escapeString(string $str): string
    {
        return str_replace('<', '\\<', $str);
    }

    public function getName(): string
    {
        return 'process';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

/**
 * Simple output wrapper for "tagged outputs" instead of wordwrap(). This solution is based on a StackOverflow
 * answer: https://stackoverflow.com/a/20434776/1476819 from user557597 (alias SLN).
 *
 *  (?:
 *       # -- Words/Characters
 *       (                       # (1 start)
 *            (?>                     # Atomic Group - Match words with valid breaks
 *                 .{1,16}                 #  1-N characters
 *                                         #  Followed by one of 4 prioritized, non-linebreak whitespace
 *                 (?:                     #  break types:
 *                      (?<= [^\S\r\n] )        # 1. - Behind a non-linebreak whitespace
 *                      [^\S\r\n]?              #      ( optionally accept an extra non-linebreak whitespace )
 *                   |  (?= \r? \n )            # 2. - Ahead a linebreak
 *                   |  $                       # 3. - EOS
 *                   |  [^\S\r\n]               # 4. - Accept an extra non-linebreak whitespace
 *                 )
 *            )                       # End atomic group
 *         |
 *            .{1,16}                 # No valid word breaks, just break on the N'th character
 *       )                       # (1 end)
 *       (?: \r? \n )?           # Optional linebreak after Words/Characters
 *    |
 *       # -- Or, Linebreak
 *       (?: \r? \n | $ )        # Stand alone linebreak or at EOS
 *  )
 *
 * @author Krisztián Ferenczi <ferenczi.krisztian@gmail.com>
 *
 * @see https://stackoverflow.com/a/20434776/1476819
 */
final class OutputWrapper
{
    private const TAG_OPEN_REGEX_SEGMENT = '[a-z](?:[^\\\\<>]*+ | \\\\.)*';
    private const TAG_CLOSE_REGEX_SEGMENT = '[a-z][^<>]*+';
    private const URL_PATTERN = 'https?://\S+';

    public function __construct(
        private bool $allowCutUrls = false,
    ) {
    }

    public function wrap(string $text, int $width, string $break = "\n"): string
    {
        if (!$width) {
            return $text;
        }

        $tagPattern = \sprintf('<(?:(?:%s)|/(?:%s)?)>', self::TAG_OPEN_REGEX_SEGMENT, self::TAG_CLOSE_REGEX_SEGMENT);
        $limitPattern = "{1,$width}";
        $patternBlocks = [$tagPattern];
        if (!$this->allowCutUrls) {
            $patternBlocks[] = self::URL_PATTERN;
        }
        $patternBlocks[] = '.';
        $blocks = implode('|', $patternBlocks);
        $rowPattern = "(?:$blocks)$limitPattern";
        $pattern = \sprintf('#(?:((?>(%1$s)((?<=[^\S\r\n])[^\S\r\n]?|(?=\r?\n)|$|[^\S\r\n]))|(%1$s))(?:\r?\n)?|(?:\r?\n|$))#imux', $rowPattern);
        $output = rtrim(preg_replace($pattern, '\\1'.$break, $text), $break);

        return str_replace(' '.$break, $break, $output);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
 */
class TableCell
{
    private string $value;
    private array $options = [
        'rowspan' => 1,
        'colspan' => 1,
        'style' => null,
    ];

    public function __construct(string $value = '', array $options = [])
    {
        $this->value = $value;

        // check option names
        if ($diff = array_diff(array_keys($options), array_keys($this->options))) {
            throw new InvalidArgumentException(\sprintf('The TableCell does not support the following options: \'%s\'.', implode('\', \'', $diff)));
        }

        if (isset($options['style']) && !$options['style'] instanceof TableCellStyle) {
            throw new InvalidArgumentException('The style option must be an instance of "TableCellStyle".');
        }

        $this->options = array_merge($this->options, $options);
    }

    /**
     * Returns the cell value.
     */
    public function __toString(): string
    {
        return $this->value;
    }

    /**
     * Gets number of colspan.
     */
    public function getColspan(): int
    {
        return (int) $this->options['colspan'];
    }

    /**
     * Gets number of rowspan.
     */
    public function getRowspan(): int
    {
        return (int) $this->options['rowspan'];
    }

    public function getStyle(): ?TableCellStyle
    {
        return $this->options['style'];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Event;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Allows to handle throwables thrown while running a command.
 *
 * @author Wouter de Jong <wouter@wouterj.nl>
 */
final class ConsoleErrorEvent extends ConsoleEvent
{
    private \Throwable $error;
    private int $exitCode;

    public function __construct(InputInterface $input, OutputInterface $output, \Throwable $error, ?Command $command = null)
    {
        parent::__construct($command, $input, $output);

        $this->error = $error;
    }

    public function getError(): \Throwable
    {
        return $this->error;
    }

    public function setError(\Throwable $error): void
    {
        $this->error = $error;
    }

    public function setExitCode(int $exitCode): void
    {
        $this->exitCode = $exitCode;

        $r = new \ReflectionProperty($this->error, 'code');
        $r->setValue($this->error, $this->exitCode);
    }

    public function getExitCode(): int
    {
        return $this->exitCode ?? (\is_int($this->error->getCode()) && 0 !== $this->error->getCode() ? $this->error->getCode() : 1);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Event;

/**
 * Allows to do things before the command is executed, like skipping the command or executing code before the command is
 * going to be executed.
 *
 * Changing the input arguments will have no effect.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
final class ConsoleCommandEvent extends ConsoleEvent
{
    /**
     * The return code for skipped commands, this will also be passed into the terminate event.
     */
    public const RETURN_CODE_DISABLED = 113;

    /**
     * Indicates if the command should be run or skipped.
     */
    private bool $commandShouldRun = true;

    /**
     * Disables the command, so it won't be run.
     */
    public function disableCommand(): bool
    {
        return $this->commandShouldRun = false;
    }

    public function enableCommand(): bool
    {
        return $this->commandShouldRun = true;
    }

    /**
     * Returns true if the command is runnable, false otherwise.
     */
    public function commandShouldRun(): bool
    {
        return $this->commandShouldRun;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Event;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Allows to manipulate the exit code of a command after its execution.
 *
 * @author Francesco Levorato <git@flevour.net>
 * @author Jules Pietri <jules@heahprod.com>
 */
final class ConsoleTerminateEvent extends ConsoleEvent
{
    public function __construct(
        Command $command,
        InputInterface $input,
        OutputInterface $output,
        private int $exitCode,
        private readonly ?int $interruptingSignal = null,
    ) {
        parent::__construct($command, $input, $output);
    }

    public function setExitCode(int $exitCode): void
    {
        $this->exitCode = $exitCode;
    }

    public function getExitCode(): int
    {
        return $this->exitCode;
    }

    public function getInterruptingSignal(): ?int
    {
        return $this->interruptingSignal;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Event;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Contracts\EventDispatcher\Event;

/**
 * Allows to inspect input and output of a command.
 *
 * @author Francesco Levorato <git@flevour.net>
 */
class ConsoleEvent extends Event
{
    protected $command;

    private InputInterface $input;
    private OutputInterface $output;

    public function __construct(?Command $command, InputInterface $input, OutputInterface $output)
    {
        $this->command = $command;
        $this->input = $input;
        $this->output = $output;
    }

    /**
     * Gets the command that is executed.
     */
    public function getCommand(): ?Command
    {
        return $this->command;
    }

    /**
     * Gets the input instance.
     */
    public function getInput(): InputInterface
    {
        return $this->input;
    }

    /**
     * Gets the output instance.
     */
    public function getOutput(): OutputInterface
    {
        return $this->output;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Event;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author marie <marie@users.noreply.github.com>
 */
final class ConsoleSignalEvent extends ConsoleEvent
{
    private int $handlingSignal;
    private int|false $exitCode;

    public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $handlingSignal, int|false $exitCode = 0)
    {
        parent::__construct($command, $input, $output);
        $this->handlingSignal = $handlingSignal;
        $this->exitCode = $exitCode;
    }

    public function getHandlingSignal(): int
    {
        return $this->handlingSignal;
    }

    public function setExitCode(int $exitCode): void
    {
        if ($exitCode < 0 || $exitCode > 255) {
            throw new \InvalidArgumentException('Exit code must be between 0 and 255.');
        }

        $this->exitCode = $exitCode;
    }

    public function abortExit(): void
    {
        $this->exitCode = false;
    }

    public function getExitCode(): int|false
    {
        return $this->exitCode;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Command\CompleteCommand;
use Symfony\Component\Console\Command\DumpCompletionCommand;
use Symfony\Component\Console\Command\HelpCommand;
use Symfony\Component\Console\Command\LazyCommand;
use Symfony\Component\Console\Command\ListCommand;
use Symfony\Component\Console\Command\SignalableCommandInterface;
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Completion\Suggestion;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleSignalEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Exception\NamespaceNotFoundException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\DebugFormatterHelper;
use Symfony\Component\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\ProcessHelper;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputAwareInterface;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\SignalRegistry\SignalRegistry;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\ErrorHandler\ErrorHandler;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\Service\ResetInterface;

/**
 * An Application is the container for a collection of commands.
 *
 * It is the main entry point of a Console application.
 *
 * This class is optimized for a standard CLI environment.
 *
 * Usage:
 *
 *     $app = new Application('myapp', '1.0 (stable)');
 *     $app->add(new SimpleCommand());
 *     $app->run();
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Application implements ResetInterface
{
    private array $commands = [];
    private bool $wantHelps = false;
    private ?Command $runningCommand = null;
    private string $name;
    private string $version;
    private ?CommandLoaderInterface $commandLoader = null;
    private bool $catchExceptions = true;
    private bool $catchErrors = false;
    private bool $autoExit = true;
    private InputDefinition $definition;
    private HelperSet $helperSet;
    private ?EventDispatcherInterface $dispatcher = null;
    private Terminal $terminal;
    private string $defaultCommand;
    private bool $singleCommand = false;
    private bool $initialized = false;
    private ?SignalRegistry $signalRegistry = null;
    private array $signalsToDispatchEvent = [];

    public function __construct(string $name = 'UNKNOWN', string $version = 'UNKNOWN')
    {
        $this->name = $name;
        $this->version = $version;
        $this->terminal = new Terminal();
        $this->defaultCommand = 'list';
        if (\defined('SIGINT') && SignalRegistry::isSupported()) {
            $this->signalRegistry = new SignalRegistry();
            $this->signalsToDispatchEvent = [\SIGINT, \SIGTERM, \SIGUSR1, \SIGUSR2];
        }
    }

    /**
     * @final
     */
    public function setDispatcher(EventDispatcherInterface $dispatcher): void
    {
        $this->dispatcher = $dispatcher;
    }

    /**
     * @return void
     */
    public function setCommandLoader(CommandLoaderInterface $commandLoader)
    {
        $this->commandLoader = $commandLoader;
    }

    public function getSignalRegistry(): SignalRegistry
    {
        if (!$this->signalRegistry) {
            throw new RuntimeException('Signals are not supported. Make sure that the "pcntl" extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.');
        }

        return $this->signalRegistry;
    }

    /**
     * @return void
     */
    public function setSignalsToDispatchEvent(int ...$signalsToDispatchEvent)
    {
        $this->signalsToDispatchEvent = $signalsToDispatchEvent;
    }

    /**
     * Runs the current application.
     *
     * @return int 0 if everything went fine, or an error code
     *
     * @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}.
     */
    public function run(?InputInterface $input = null, ?OutputInterface $output = null): int
    {
        if (\function_exists('putenv')) {
            @putenv('LINES='.$this->terminal->getHeight());
            @putenv('COLUMNS='.$this->terminal->getWidth());
        }

        $input ??= new ArgvInput();
        $output ??= new ConsoleOutput();

        $renderException = function (\Throwable $e) use ($output) {
            if ($output instanceof ConsoleOutputInterface) {
                $this->renderThrowable($e, $output->getErrorOutput());
            } else {
                $this->renderThrowable($e, $output);
            }
        };
        if ($phpHandler = set_exception_handler($renderException)) {
            restore_exception_handler();
            if (!\is_array($phpHandler) || !$phpHandler[0] instanceof ErrorHandler) {
                $errorHandler = true;
            } elseif ($errorHandler = $phpHandler[0]->setExceptionHandler($renderException)) {
                $phpHandler[0]->setExceptionHandler($errorHandler);
            }
        }

        try {
            $this->configureIO($input, $output);

            $exitCode = $this->doRun($input, $output);
        } catch (\Throwable $e) {
            if ($e instanceof \Exception && !$this->catchExceptions) {
                throw $e;
            }
            if (!$e instanceof \Exception && !$this->catchErrors) {
                throw $e;
            }

            $renderException($e);

            $exitCode = $e->getCode();
            if (is_numeric($exitCode)) {
                $exitCode = (int) $exitCode;
                if ($exitCode <= 0) {
                    $exitCode = 1;
                }
            } else {
                $exitCode = 1;
            }
        } finally {
            // if the exception handler changed, keep it
            // otherwise, unregister $renderException
            if (!$phpHandler) {
                if (set_exception_handler($renderException) === $renderException) {
                    restore_exception_handler();
                }
                restore_exception_handler();
            } elseif (!$errorHandler) {
                $finalHandler = $phpHandler[0]->setExceptionHandler(null);
                if ($finalHandler !== $renderException) {
                    $phpHandler[0]->setExceptionHandler($finalHandler);
                }
            }
        }

        if ($this->autoExit) {
            if ($exitCode > 255) {
                $exitCode = 255;
            }

            exit($exitCode);
        }

        return $exitCode;
    }

    /**
     * Runs the current application.
     *
     * @return int 0 if everything went fine, or an error code
     */
    public function doRun(InputInterface $input, OutputInterface $output)
    {
        if (true === $input->hasParameterOption(['--version', '-V'], true)) {
            $output->writeln($this->getLongVersion());

            return 0;
        }

        try {
            // Makes ArgvInput::getFirstArgument() able to distinguish an option from an argument.
            $input->bind($this->getDefinition());
        } catch (ExceptionInterface) {
            // Errors must be ignored, full binding/validation happens later when the command is known.
        }

        $name = $this->getCommandName($input);
        if (true === $input->hasParameterOption(['--help', '-h'], true)) {
            if (!$name) {
                $name = 'help';
                $input = new ArrayInput(['command_name' => $this->defaultCommand]);
            } else {
                $this->wantHelps = true;
            }
        }

        if (!$name) {
            $name = $this->defaultCommand;
            $definition = $this->getDefinition();
            $definition->setArguments(array_merge(
                $definition->getArguments(),
                [
                    'command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name),
                ]
            ));
        }

        try {
            $this->runningCommand = null;
            // the command name MUST be the first element of the input
            $command = $this->find($name);
        } catch (\Throwable $e) {
            if (($e instanceof CommandNotFoundException && !$e instanceof NamespaceNotFoundException) && 1 === \count($alternatives = $e->getAlternatives()) && $input->isInteractive()) {
                $alternative = $alternatives[0];

                $style = new SymfonyStyle($input, $output);
                $output->writeln('');
                $formattedBlock = (new FormatterHelper())->formatBlock(\sprintf('Command "%s" is not defined.', $name), 'error', true);
                $output->writeln($formattedBlock);
                if (!$style->confirm(\sprintf('Do you want to run "%s" instead? ', $alternative), false)) {
                    if (null !== $this->dispatcher) {
                        $event = new ConsoleErrorEvent($input, $output, $e);
                        $this->dispatcher->dispatch($event, ConsoleEvents::ERROR);

                        return $event->getExitCode();
                    }

                    return 1;
                }

                $command = $this->find($alternative);
            } else {
                if (null !== $this->dispatcher) {
                    $event = new ConsoleErrorEvent($input, $output, $e);
                    $this->dispatcher->dispatch($event, ConsoleEvents::ERROR);

                    if (0 === $event->getExitCode()) {
                        return 0;
                    }

                    $e = $event->getError();
                }

                try {
                    if ($e instanceof CommandNotFoundException && $namespace = $this->findNamespace($name)) {
                        $helper = new DescriptorHelper();
                        $helper->describe($output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output, $this, [
                            'format' => 'txt',
                            'raw_text' => false,
                            'namespace' => $namespace,
                            'short' => false,
                        ]);

                        return isset($event) ? $event->getExitCode() : 1;
                    }

                    throw $e;
                } catch (NamespaceNotFoundException) {
                    throw $e;
                }
            }
        }

        if ($command instanceof LazyCommand) {
            $command = $command->getCommand();
        }

        $this->runningCommand = $command;
        $exitCode = $this->doRunCommand($command, $input, $output);
        $this->runningCommand = null;

        return $exitCode;
    }

    /**
     * @return void
     */
    public function reset()
    {
    }

    /**
     * @return void
     */
    public function setHelperSet(HelperSet $helperSet)
    {
        $this->helperSet = $helperSet;
    }

    /**
     * Get the helper set associated with the command.
     */
    public function getHelperSet(): HelperSet
    {
        return $this->helperSet ??= $this->getDefaultHelperSet();
    }

    /**
     * @return void
     */
    public function setDefinition(InputDefinition $definition)
    {
        $this->definition = $definition;
    }

    /**
     * Gets the InputDefinition related to this Application.
     */
    public function getDefinition(): InputDefinition
    {
        $this->definition ??= $this->getDefaultInputDefinition();

        if ($this->singleCommand) {
            $inputDefinition = $this->definition;
            $inputDefinition->setArguments();

            return $inputDefinition;
        }

        return $this->definition;
    }

    /**
     * Adds suggestions to $suggestions for the current completion input (e.g. option or argument).
     */
    public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
    {
        if (
            CompletionInput::TYPE_ARGUMENT_VALUE === $input->getCompletionType()
            && 'command' === $input->getCompletionName()
        ) {
            foreach ($this->all() as $name => $command) {
                // skip hidden commands and aliased commands as they already get added below
                if ($command->isHidden() || $command->getName() !== $name) {
                    continue;
                }
                $suggestions->suggestValue(new Suggestion($command->getName(), $command->getDescription()));
                foreach ($command->getAliases() as $name) {
                    $suggestions->suggestValue(new Suggestion($name, $command->getDescription()));
                }
            }

            return;
        }

        if (CompletionInput::TYPE_OPTION_NAME === $input->getCompletionType()) {
            $suggestions->suggestOptions($this->getDefinition()->getOptions());

            return;
        }
    }

    /**
     * Gets the help message.
     */
    public function getHelp(): string
    {
        return $this->getLongVersion();
    }

    /**
     * Gets whether to catch exceptions or not during commands execution.
     */
    public function areExceptionsCaught(): bool
    {
        return $this->catchExceptions;
    }

    /**
     * Sets whether to catch exceptions or not during commands execution.
     *
     * @return void
     */
    public function setCatchExceptions(bool $boolean)
    {
        $this->catchExceptions = $boolean;
    }

    /**
     * Sets whether to catch errors or not during commands execution.
     */
    public function setCatchErrors(bool $catchErrors = true): void
    {
        $this->catchErrors = $catchErrors;
    }

    /**
     * Gets whether to automatically exit after a command execution or not.
     */
    public function isAutoExitEnabled(): bool
    {
        return $this->autoExit;
    }

    /**
     * Sets whether to automatically exit after a command execution or not.
     *
     * @return void
     */
    public function setAutoExit(bool $boolean)
    {
        $this->autoExit = $boolean;
    }

    /**
     * Gets the name of the application.
     */
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * Sets the application name.
     *
     * @return void
     */
    public function setName(string $name)
    {
        $this->name = $name;
    }

    /**
     * Gets the application version.
     */
    public function getVersion(): string
    {
        return $this->version;
    }

    /**
     * Sets the application version.
     *
     * @return void
     */
    public function setVersion(string $version)
    {
        $this->version = $version;
    }

    /**
     * Returns the long version of the application.
     *
     * @return string
     */
    public function getLongVersion()
    {
        if ('UNKNOWN' !== $this->getName()) {
            if ('UNKNOWN' !== $this->getVersion()) {
                return \sprintf('%s <info>%s</info>', $this->getName(), $this->getVersion());
            }

            return $this->getName();
        }

        return 'Console Tool';
    }

    /**
     * Registers a new command.
     */
    public function register(string $name): Command
    {
        return $this->add(new Command($name));
    }

    /**
     * Adds an array of command objects.
     *
     * If a Command is not enabled it will not be added.
     *
     * @param Command[] $commands An array of commands
     *
     * @return void
     */
    public function addCommands(array $commands)
    {
        foreach ($commands as $command) {
            $this->add($command);
        }
    }

    /**
     * Adds a command object.
     *
     * If a command with the same name already exists, it will be overridden.
     * If the command is not enabled it will not be added.
     *
     * @return Command|null
     */
    public function add(Command $command)
    {
        $this->init();

        $command->setApplication($this);

        if (!$command->isEnabled()) {
            $command->setApplication(null);

            return null;
        }

        if (!$command instanceof LazyCommand) {
            // Will throw if the command is not correctly initialized.
            $command->getDefinition();
        }

        if (!$command->getName()) {
            throw new LogicException(\sprintf('The command defined in "%s" cannot have an empty name.', get_debug_type($command)));
        }

        $this->commands[$command->getName()] = $command;

        foreach ($command->getAliases() as $alias) {
            $this->commands[$alias] = $command;
        }

        return $command;
    }

    /**
     * Returns a registered command by name or alias.
     *
     * @return Command
     *
     * @throws CommandNotFoundException When given command name does not exist
     */
    public function get(string $name)
    {
        $this->init();

        if (!$this->has($name)) {
            throw new CommandNotFoundException(\sprintf('The command "%s" does not exist.', $name));
        }

        // When the command has a different name than the one used at the command loader level
        if (!isset($this->commands[$name])) {
            throw new CommandNotFoundException(\sprintf('The "%s" command cannot be found because it is registered under multiple names. Make sure you don\'t set a different name via constructor or "setName()".', $name));
        }

        $command = $this->commands[$name];

        if ($this->wantHelps) {
            $this->wantHelps = false;

            $helpCommand = $this->get('help');
            $helpCommand->setCommand($command);

            return $helpCommand;
        }

        return $command;
    }

    /**
     * Returns true if the command exists, false otherwise.
     */
    public function has(string $name): bool
    {
        $this->init();

        return isset($this->commands[$name]) || ($this->commandLoader?->has($name) && $this->add($this->commandLoader->get($name)));
    }

    /**
     * Returns an array of all unique namespaces used by currently registered commands.
     *
     * It does not return the global namespace which always exists.
     *
     * @return string[]
     */
    public function getNamespaces(): array
    {
        $namespaces = [];
        foreach ($this->all() as $command) {
            if ($command->isHidden()) {
                continue;
            }

            $namespaces[] = $this->extractAllNamespaces($command->getName());

            foreach ($command->getAliases() as $alias) {
                $namespaces[] = $this->extractAllNamespaces($alias);
            }
        }

        return array_values(array_unique(array_filter(array_merge([], ...$namespaces))));
    }

    /**
     * Finds a registered namespace by a name or an abbreviation.
     *
     * @throws NamespaceNotFoundException When namespace is incorrect or ambiguous
     */
    public function findNamespace(string $namespace): string
    {
        $allNamespaces = $this->getNamespaces();
        $expr = implode('[^:]*:', array_map('preg_quote', explode(':', $namespace))).'[^:]*';
        $namespaces = preg_grep('{^'.$expr.'}', $allNamespaces);

        if (empty($namespaces)) {
            $message = \sprintf('There are no commands defined in the "%s" namespace.', $namespace);

            if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) {
                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);
            }

            throw new NamespaceNotFoundException($message, $alternatives);
        }

        $exact = \in_array($namespace, $namespaces, true);
        if (\count($namespaces) > 1 && !$exact) {
            throw new NamespaceNotFoundException(\sprintf("The namespace \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces));
        }

        return $exact ? $namespace : reset($namespaces);
    }

    /**
     * Finds a command by name or alias.
     *
     * Contrary to get, this command tries to find the best
     * match if you give it an abbreviation of a name or alias.
     *
     * @return Command
     *
     * @throws CommandNotFoundException When command name is incorrect or ambiguous
     */
    public function find(string $name)
    {
        $this->init();

        $aliases = [];

        foreach ($this->commands as $command) {
            foreach ($command->getAliases() as $alias) {
                if (!$this->has($alias)) {
                    $this->commands[$alias] = $command;
                }
            }
        }

        if ($this->has($name)) {
            return $this->get($name);
        }

        $allCommands = $this->commandLoader ? array_merge($this->commandLoader->getNames(), array_keys($this->commands)) : array_keys($this->commands);
        $expr = implode('[^:]*:', array_map('preg_quote', explode(':', $name))).'[^:]*';
        $commands = preg_grep('{^'.$expr.'}', $allCommands);

        if (empty($commands)) {
            $commands = preg_grep('{^'.$expr.'}i', $allCommands);
        }

        // if no commands matched or we just matched namespaces
        if (empty($commands) || \count(preg_grep('{^'.$expr.'$}i', $commands)) < 1) {
            if (false !== $pos = strrpos($name, ':')) {
                // check if a namespace exists and contains commands
                $this->findNamespace(substr($name, 0, $pos));
            }

            $message = \sprintf('Command "%s" is not defined.', $name);

            if ($alternatives = $this->findAlternatives($name, $allCommands)) {
                // remove hidden commands
                $alternatives = array_filter($alternatives, fn ($name) => !$this->get($name)->isHidden());

                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);
            }

            throw new CommandNotFoundException($message, array_values($alternatives));
        }

        // filter out aliases for commands which are already on the list
        if (\count($commands) > 1) {
            $commandList = $this->commandLoader ? array_merge(array_flip($this->commandLoader->getNames()), $this->commands) : $this->commands;
            $commands = array_unique(array_filter($commands, function ($nameOrAlias) use (&$commandList, $commands, &$aliases) {
                if (!$commandList[$nameOrAlias] instanceof Command) {
                    $commandList[$nameOrAlias] = $this->commandLoader->get($nameOrAlias);
                }

                $commandName = $commandList[$nameOrAlias]->getName();

                $aliases[$nameOrAlias] = $commandName;

                return $commandName === $nameOrAlias || !\in_array($commandName, $commands);
            }));
        }

        if (\count($commands) > 1) {
            $usableWidth = $this->terminal->getWidth() - 10;
            $abbrevs = array_values($commands);
            $maxLen = 0;
            foreach ($abbrevs as $abbrev) {
                $maxLen = max(Helper::width($abbrev), $maxLen);
            }
            $abbrevs = array_map(function ($cmd) use ($commandList, $usableWidth, $maxLen, &$commands) {
                if ($commandList[$cmd]->isHidden()) {
                    unset($commands[array_search($cmd, $commands)]);

                    return false;
                }

                $abbrev = str_pad($cmd, $maxLen, ' ').' '.$commandList[$cmd]->getDescription();

                return Helper::width($abbrev) > $usableWidth ? Helper::substr($abbrev, 0, $usableWidth - 3).'...' : $abbrev;
            }, array_values($commands));

            if (\count($commands) > 1) {
                $suggestions = $this->getAbbreviationSuggestions(array_filter($abbrevs));

                throw new CommandNotFoundException(\sprintf("Command \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $name, $suggestions), array_values($commands));
            }
        }

        $command = $this->get(reset($commands));

        if ($command->isHidden()) {
            throw new CommandNotFoundException(\sprintf('The command "%s" does not exist.', $name));
        }

        return $command;
    }

    /**
     * Gets the commands (registered in the given namespace if provided).
     *
     * The array keys are the full names and the values the command instances.
     *
     * @return Command[]
     */
    public function all(?string $namespace = null)
    {
        $this->init();

        if (null === $namespace) {
            if (!$this->commandLoader) {
                return $this->commands;
            }

            $commands = $this->commands;
            foreach ($this->commandLoader->getNames() as $name) {
                if (!isset($commands[$name]) && $this->has($name)) {
                    $commands[$name] = $this->get($name);
                }
            }

            return $commands;
        }

        $commands = [];
        foreach ($this->commands as $name => $command) {
            if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) {
                $commands[$name] = $command;
            }
        }

        if ($this->commandLoader) {
            foreach ($this->commandLoader->getNames() as $name) {
                if (!isset($commands[$name]) && $namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1) && $this->has($name)) {
                    $commands[$name] = $this->get($name);
                }
            }
        }

        return $commands;
    }

    /**
     * Returns an array of possible abbreviations given a set of names.
     *
     * @return string[][]
     */
    public static function getAbbreviations(array $names): array
    {
        $abbrevs = [];
        foreach ($names as $name) {
            for ($len = \strlen($name); $len > 0; --$len) {
                $abbrev = substr($name, 0, $len);
                $abbrevs[$abbrev][] = $name;
            }
        }

        return $abbrevs;
    }

    public function renderThrowable(\Throwable $e, OutputInterface $output): void
    {
        $output->writeln('', OutputInterface::VERBOSITY_QUIET);

        $this->doRenderThrowable($e, $output);

        if (null !== $this->runningCommand) {
            $output->writeln(\sprintf('<info>%s</info>', OutputFormatter::escape(\sprintf($this->runningCommand->getSynopsis(), $this->getName()))), OutputInterface::VERBOSITY_QUIET);
            $output->writeln('', OutputInterface::VERBOSITY_QUIET);
        }
    }

    protected function doRenderThrowable(\Throwable $e, OutputInterface $output): void
    {
        do {
            $message = trim($e->getMessage());
            if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
                $class = get_debug_type($e);
                $title = \sprintf('  [%s%s]  ', $class, 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : '');
                $len = Helper::width($title);
            } else {
                $len = 0;
            }

            if (str_contains($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]++/', fn ($m) => class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0], $message);
            }

            $width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : \PHP_INT_MAX;
            $lines = [];
            foreach ('' !== $message ? preg_split('/\r?\n/', $message) : [] as $line) {
                foreach ($this->splitStringByWidth($line, $width - 4) as $line) {
                    // pre-format lines to get the right string length
                    $lineLength = Helper::width($line) + 4;
                    $lines[] = [$line, $lineLength];

                    $len = max($lineLength, $len);
                }
            }

            $messages = [];
            if (!$e instanceof ExceptionInterface || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
                $messages[] = \sprintf('<comment>%s</comment>', OutputFormatter::escape(\sprintf('In %s line %s:', basename($e->getFile()) ?: 'n/a', $e->getLine() ?: 'n/a')));
            }
            $messages[] = $emptyLine = \sprintf('<error>%s</error>', str_repeat(' ', $len));
            if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
                $messages[] = \sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - Helper::width($title))));
            }
            foreach ($lines as $line) {
                $messages[] = \sprintf('<error>  %s  %s</error>', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1]));
            }
            $messages[] = $emptyLine;
            $messages[] = '';

            $output->writeln($messages, OutputInterface::VERBOSITY_QUIET);

            if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
                $output->writeln('<comment>Exception trace:</comment>', OutputInterface::VERBOSITY_QUIET);

                // exception related properties
                $trace = $e->getTrace();

                array_unshift($trace, [
                    'function' => '',
                    'file' => $e->getFile() ?: 'n/a',
                    'line' => $e->getLine() ?: 'n/a',
                    'args' => [],
                ]);

                for ($i = 0, $count = \count($trace); $i < $count; ++$i) {
                    $class = $trace[$i]['class'] ?? '';
                    $type = $trace[$i]['type'] ?? '';
                    $function = $trace[$i]['function'] ?? '';
                    $file = $trace[$i]['file'] ?? 'n/a';
                    $line = $trace[$i]['line'] ?? 'n/a';

                    $output->writeln(\sprintf(' %s%s at <info>%s:%s</info>', $class, $function ? $type.$function.'()' : '', $file, $line), OutputInterface::VERBOSITY_QUIET);
                }

                $output->writeln('', OutputInterface::VERBOSITY_QUIET);
            }
        } while ($e = $e->getPrevious());
    }

    /**
     * Configures the input and output instances based on the user arguments and options.
     *
     * @return void
     */
    protected function configureIO(InputInterface $input, OutputInterface $output)
    {
        if (true === $input->hasParameterOption(['--ansi'], true)) {
            $output->setDecorated(true);
        } elseif (true === $input->hasParameterOption(['--no-ansi'], true)) {
            $output->setDecorated(false);
        }

        if (true === $input->hasParameterOption(['--no-interaction', '-n'], true)) {
            $input->setInteractive(false);
        }

        switch ($shellVerbosity = (int) getenv('SHELL_VERBOSITY')) {
            case -1:
                $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
                break;
            case 1:
                $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
                break;
            case 2:
                $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
                break;
            case 3:
                $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
                break;
            default:
                $shellVerbosity = 0;
                break;
        }

        if (true === $input->hasParameterOption(['--quiet', '-q'], true)) {
            $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
            $shellVerbosity = -1;
        } else {
            if ($input->hasParameterOption('-vvv', true) || $input->hasParameterOption('--verbose=3', true) || 3 === $input->getParameterOption('--verbose', false, true)) {
                $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
                $shellVerbosity = 3;
            } elseif ($input->hasParameterOption('-vv', true) || $input->hasParameterOption('--verbose=2', true) || 2 === $input->getParameterOption('--verbose', false, true)) {
                $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
                $shellVerbosity = 2;
            } elseif ($input->hasParameterOption('-v', true) || $input->hasParameterOption('--verbose=1', true) || $input->hasParameterOption('--verbose', true) || $input->getParameterOption('--verbose', false, true)) {
                $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
                $shellVerbosity = 1;
            }
        }

        if (-1 === $shellVerbosity) {
            $input->setInteractive(false);
        }

        if (\function_exists('putenv')) {
            @putenv('SHELL_VERBOSITY='.$shellVerbosity);
        }
        $_ENV['SHELL_VERBOSITY'] = $shellVerbosity;
        $_SERVER['SHELL_VERBOSITY'] = $shellVerbosity;
    }

    /**
     * Runs the current command.
     *
     * If an event dispatcher has been attached to the application,
     * events are also dispatched during the life-cycle of the command.
     *
     * @return int 0 if everything went fine, or an error code
     */
    protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
    {
        foreach ($command->getHelperSet() as $helper) {
            if ($helper instanceof InputAwareInterface) {
                $helper->setInput($input);
            }
        }

        $commandSignals = $command instanceof SignalableCommandInterface ? $command->getSubscribedSignals() : [];
        if ($commandSignals || $this->dispatcher && $this->signalsToDispatchEvent) {
            if (!$this->signalRegistry) {
                throw new RuntimeException('Unable to subscribe to signal events. Make sure that the "pcntl" extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.');
            }

            if (Terminal::hasSttyAvailable()) {
                $sttyMode = shell_exec('stty -g');

                foreach ([\SIGINT, \SIGTERM] as $signal) {
                    $this->signalRegistry->register($signal, static fn () => shell_exec('stty '.$sttyMode));
                }
            }

            if ($this->dispatcher) {
                // We register application signals, so that we can dispatch the event
                foreach ($this->signalsToDispatchEvent as $signal) {
                    $event = new ConsoleSignalEvent($command, $input, $output, $signal);

                    $this->signalRegistry->register($signal, function ($signal) use ($event, $command, $commandSignals) {
                        $this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL);
                        $exitCode = $event->getExitCode();

                        // If the command is signalable, we call the handleSignal() method
                        if (\in_array($signal, $commandSignals, true)) {
                            $exitCode = $command->handleSignal($signal, $exitCode);
                            // BC layer for Symfony <= 5
                            if (null === $exitCode) {
                                trigger_deprecation('symfony/console', '6.3', 'Not returning an exit code from "%s::handleSignal()" is deprecated, return "false" to keep the command running or "0" to exit successfully.', get_debug_type($command));
                                $exitCode = 0;
                            }
                        }

                        if (false !== $exitCode) {
                            $event = new ConsoleTerminateEvent($command, $event->getInput(), $event->getOutput(), $exitCode, $signal);
                            $this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE);

                            exit($event->getExitCode());
                        }
                    });
                }

                // then we register command signals, but not if already handled after the dispatcher
                $commandSignals = array_diff($commandSignals, $this->signalsToDispatchEvent);
            }

            foreach ($commandSignals as $signal) {
                $this->signalRegistry->register($signal, function (int $signal) use ($command): void {
                    $exitCode = $command->handleSignal($signal);
                    // BC layer for Symfony <= 5
                    if (null === $exitCode) {
                        trigger_deprecation('symfony/console', '6.3', 'Not returning an exit code from "%s::handleSignal()" is deprecated, return "false" to keep the command running or "0" to exit successfully.', get_debug_type($command));
                        $exitCode = 0;
                    }

                    if (false !== $exitCode) {
                        exit($exitCode);
                    }
                });
            }
        }

        if (null === $this->dispatcher) {
            return $command->run($input, $output);
        }

        // bind before the console.command event, so the listeners have access to input options/arguments
        try {
            $command->mergeApplicationDefinition();
            $input->bind($command->getDefinition());
        } catch (ExceptionInterface) {
            // ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition
        }

        $event = new ConsoleCommandEvent($command, $input, $output);
        $e = null;

        try {
            $this->dispatcher->dispatch($event, ConsoleEvents::COMMAND);

            if ($event->commandShouldRun()) {
                $exitCode = $command->run($input, $output);
            } else {
                $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED;
            }
        } catch (\Throwable $e) {
            $event = new ConsoleErrorEvent($input, $output, $e, $command);
            $this->dispatcher->dispatch($event, ConsoleEvents::ERROR);
            $e = $event->getError();

            if (0 === $exitCode = $event->getExitCode()) {
                $e = null;
            }
        }

        $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode);
        $this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE);

        if (null !== $e) {
            throw $e;
        }

        return $event->getExitCode();
    }

    /**
     * Gets the name of the command based on input.
     */
    protected function getCommandName(InputInterface $input): ?string
    {
        return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument();
    }

    /**
     * Gets the default input definition.
     */
    protected function getDefaultInputDefinition(): InputDefinition
    {
        return new InputDefinition([
            new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
            new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display help for the given command. When no command is given display help for the <info>'.$this->defaultCommand.'</info> command'),
            new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'),
            new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'),
            new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'),
            new InputOption('--ansi', '', InputOption::VALUE_NEGATABLE, 'Force (or disable --no-ansi) ANSI output', null),
            new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'),
        ]);
    }

    /**
     * Gets the default commands that should always be available.
     *
     * @return Command[]
     */
    protected function getDefaultCommands(): array
    {
        return [new HelpCommand(), new ListCommand(), new CompleteCommand(), new DumpCompletionCommand()];
    }

    /**
     * Gets the default helper set with the helpers that should always be available.
     */
    protected function getDefaultHelperSet(): HelperSet
    {
        return new HelperSet([
            new FormatterHelper(),
            new DebugFormatterHelper(),
            new ProcessHelper(),
            new QuestionHelper(),
        ]);
    }

    /**
     * Returns abbreviated suggestions in string format.
     */
    private function getAbbreviationSuggestions(array $abbrevs): string
    {
        return '    '.implode("\n    ", $abbrevs);
    }

    /**
     * Returns the namespace part of the command name.
     *
     * This method is not part of public API and should not be used directly.
     */
    public function extractNamespace(string $name, ?int $limit = null): string
    {
        $parts = explode(':', $name, -1);

        return implode(':', null === $limit ? $parts : \array_slice($parts, 0, $limit));
    }

    /**
     * Finds alternative of $name among $collection,
     * if nothing is found in $collection, try in $abbrevs.
     *
     * @return string[]
     */
    private function findAlternatives(string $name, iterable $collection): array
    {
        $threshold = 1e3;
        $alternatives = [];

        $collectionParts = [];
        foreach ($collection as $item) {
            $collectionParts[$item] = explode(':', $item);
        }

        foreach (explode(':', $name) as $i => $subname) {
            foreach ($collectionParts as $collectionName => $parts) {
                $exists = isset($alternatives[$collectionName]);
                if (!isset($parts[$i]) && $exists) {
                    $alternatives[$collectionName] += $threshold;
                    continue;
                } elseif (!isset($parts[$i])) {
                    continue;
                }

                $lev = levenshtein($subname, $parts[$i]);
                if ($lev <= \strlen($subname) / 3 || '' !== $subname && str_contains($parts[$i], $subname)) {
                    $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev;
                } elseif ($exists) {
                    $alternatives[$collectionName] += $threshold;
                }
            }
        }

        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;
            }
        }

        $alternatives = array_filter($alternatives, fn ($lev) => $lev < 2 * $threshold);
        ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE);

        return array_keys($alternatives);
    }

    /**
     * Sets the default Command name.
     *
     * @return $this
     */
    public function setDefaultCommand(string $commandName, bool $isSingleCommand = false): static
    {
        $this->defaultCommand = explode('|', ltrim($commandName, '|'))[0];

        if ($isSingleCommand) {
            // Ensure the command exist
            $this->find($commandName);

            $this->singleCommand = true;
        }

        return $this;
    }

    /**
     * @internal
     */
    public function isSingleCommand(): bool
    {
        return $this->singleCommand;
    }

    private function splitStringByWidth(string $string, int $width): array
    {
        // str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly.
        // additionally, array_slice() is not enough as some character has doubled width.
        // we need a function to split string not by character count but by string width
        if (false === $encoding = mb_detect_encoding($string, null, true)) {
            return str_split($string, $width);
        }

        $utf8String = mb_convert_encoding($string, 'utf8', $encoding);
        $lines = [];
        $line = '';

        $offset = 0;
        while (preg_match('/.{1,10000}/u', $utf8String, $m, 0, $offset)) {
            $offset += \strlen($m[0]);

            foreach (preg_split('//u', $m[0]) as $char) {
                // test if $char could be appended to current line
                if (Helper::width($line.$char) <= $width) {
                    $line .= $char;
                    continue;
                }
                // if not, push current line to array and make new line
                $lines[] = str_pad($line, $width);
                $line = $char;
            }
        }

        $lines[] = \count($lines) ? str_pad($line, $width) : $line;

        mb_convert_variables($encoding, 'utf8', $lines);

        return $lines;
    }

    /**
     * Returns all namespaces of the command name.
     *
     * @return string[]
     */
    private function extractAllNamespaces(string $name): array
    {
        // -1 as third argument is needed to skip the command short name when exploding
        $parts = explode(':', $name, -1);
        $namespaces = [];

        foreach ($parts as $part) {
            if (\count($namespaces)) {
                $namespaces[] = end($namespaces).':'.$part;
            } else {
                $namespaces[] = $part;
            }
        }

        return $namespaces;
    }

    private function init(): void
    {
        if ($this->initialized) {
            return;
        }
        $this->initialized = true;

        foreach ($this->getDefaultCommands() as $command) {
            $this->add($command);
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\DataCollector;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Debug\CliRequest;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\SignalRegistry\SignalMap;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
use Symfony\Component\VarDumper\Cloner\Data;

/**
 * @internal
 *
 * @author Jules Pietri <jules@heahprod.com>
 */
final class CommandDataCollector extends DataCollector
{
    public function collect(Request $request, Response $response, ?\Throwable $exception = null): void
    {
        if (!$request instanceof CliRequest) {
            return;
        }

        $command = $request->command;
        $application = $command->getApplication();

        $this->data = [
            'command' => $this->cloneVar($command->command),
            'exit_code' => $command->exitCode,
            'interrupted_by_signal' => $command->interruptedBySignal,
            'duration' => $command->duration,
            'max_memory_usage' => $command->maxMemoryUsage,
            'verbosity_level' => match ($command->output->getVerbosity()) {
                OutputInterface::VERBOSITY_QUIET => 'quiet',
                OutputInterface::VERBOSITY_NORMAL => 'normal',
                OutputInterface::VERBOSITY_VERBOSE => 'verbose',
                OutputInterface::VERBOSITY_VERY_VERBOSE => 'very verbose',
                OutputInterface::VERBOSITY_DEBUG => 'debug',
            },
            'interactive' => $command->isInteractive,
            'validate_input' => !$command->ignoreValidation,
            'enabled' => $command->isEnabled(),
            'visible' => !$command->isHidden(),
            'input' => $this->cloneVar($command->input),
            'output' => $this->cloneVar($command->output),
            'interactive_inputs' => array_map($this->cloneVar(...), $command->interactiveInputs),
            'signalable' => $command->getSubscribedSignals(),
            'handled_signals' => $command->handledSignals,
            'helper_set' => array_map($this->cloneVar(...), iterator_to_array($command->getHelperSet())),
        ];

        $baseDefinition = $application->getDefinition();

        foreach ($command->arguments as $argName => $argValue) {
            if ($baseDefinition->hasArgument($argName)) {
                $this->data['application_inputs'][$argName] = $this->cloneVar($argValue);
            } else {
                $this->data['arguments'][$argName] = $this->cloneVar($argValue);
            }
        }

        foreach ($command->options as $optName => $optValue) {
            if ($baseDefinition->hasOption($optName)) {
                $this->data['application_inputs']['--'.$optName] = $this->cloneVar($optValue);
            } else {
                $this->data['options'][$optName] = $this->cloneVar($optValue);
            }
        }
    }

    public function getName(): string
    {
        return 'command';
    }

    /**
     * @return array{
     *     class?: class-string,
     *     executor?: string,
     *     file: string,
     *     line: int,
     * }
     */
    public function getCommand(): array
    {
        $class = $this->data['command']->getType();
        $r = new \ReflectionMethod($class, 'execute');

        if (Command::class !== $r->getDeclaringClass()) {
            return [
                'executor' => $class.'::'.$r->name,
                'file' => $r->getFileName(),
                'line' => $r->getStartLine(),
            ];
        }

        $r = new \ReflectionClass($class);

        return [
            'class' => $class,
            'file' => $r->getFileName(),
            'line' => $r->getStartLine(),
        ];
    }

    public function getInterruptedBySignal(): ?string
    {
        if (isset($this->data['interrupted_by_signal'])) {
            return \sprintf('%s (%d)', SignalMap::getSignalName($this->data['interrupted_by_signal']), $this->data['interrupted_by_signal']);
        }

        return null;
    }

    public function getDuration(): string
    {
        return $this->data['duration'];
    }

    public function getMaxMemoryUsage(): string
    {
        return $this->data['max_memory_usage'];
    }

    public function getVerbosityLevel(): string
    {
        return $this->data['verbosity_level'];
    }

    public function getInteractive(): bool
    {
        return $this->data['interactive'];
    }

    public function getValidateInput(): bool
    {
        return $this->data['validate_input'];
    }

    public function getEnabled(): bool
    {
        return $this->data['enabled'];
    }

    public function getVisible(): bool
    {
        return $this->data['visible'];
    }

    public function getInput(): Data
    {
        return $this->data['input'];
    }

    public function getOutput(): Data
    {
        return $this->data['output'];
    }

    /**
     * @return Data[]
     */
    public function getArguments(): array
    {
        return $this->data['arguments'] ?? [];
    }

    /**
     * @return Data[]
     */
    public function getOptions(): array
    {
        return $this->data['options'] ?? [];
    }

    /**
     * @return Data[]
     */
    public function getApplicationInputs(): array
    {
        return $this->data['application_inputs'] ?? [];
    }

    /**
     * @return Data[]
     */
    public function getInteractiveInputs(): array
    {
        return $this->data['interactive_inputs'] ?? [];
    }

    public function getSignalable(): array
    {
        return array_map(
            static fn (int $signal): string => \sprintf('%s (%d)', SignalMap::getSignalName($signal), $signal),
            $this->data['signalable']
        );
    }

    public function getHandledSignals(): array
    {
        $keys = array_map(
            static fn (int $signal): string => \sprintf('%s (%d)', SignalMap::getSignalName($signal), $signal),
            array_keys($this->data['handled_signals'])
        );

        return array_combine($keys, array_values($this->data['handled_signals']));
    }

    /**
     * @return Data[]
     */
    public function getHelperSet(): array
    {
        return $this->data['helper_set'] ?? [];
    }

    public function reset(): void
    {
        $this->data = [];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Completion;

/**
 * Represents a single suggested value.
 *
 * @author Wouter de Jong <wouter@wouterj.nl>
 */
class Suggestion implements \Stringable
{
    public function __construct(
        private readonly string $value,
        private readonly string $description = '',
    ) {
    }

    public function getValue(): string
    {
        return $this->value;
    }

    public function getDescription(): string
    {
        return $this->description;
    }

    public function __toString(): string
    {
        return $this->getValue();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Completion\Output;

use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jitendra A <adhocore@gmail.com>
 */
class ZshCompletionOutput implements CompletionOutputInterface
{
    public function write(CompletionSuggestions $suggestions, OutputInterface $output): void
    {
        $values = [];
        foreach ($suggestions->getValueSuggestions() as $value) {
            $values[] = $value->getValue().($value->getDescription() ? "\t".$value->getDescription() : '');
        }
        foreach ($suggestions->getOptionSuggestions() as $option) {
            $values[] = '--'.$option->getName().($option->getDescription() ? "\t".$option->getDescription() : '');
            if ($option->isNegatable()) {
                $values[] = '--no-'.$option->getName().($option->getDescription() ? "\t".$option->getDescription() : '');
            }
        }
        $output->write(implode("\n", $values)."\n");
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Completion\Output;

use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Transforms the {@see CompletionSuggestions} object into output readable by the shell completion.
 *
 * @author Wouter de Jong <wouter@wouterj.nl>
 */
interface CompletionOutputInterface
{
    public function write(CompletionSuggestions $suggestions, OutputInterface $output): void;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Completion\Output;

use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Wouter de Jong <wouter@wouterj.nl>
 */
class BashCompletionOutput implements CompletionOutputInterface
{
    public function write(CompletionSuggestions $suggestions, OutputInterface $output): void
    {
        $values = $suggestions->getValueSuggestions();
        foreach ($suggestions->getOptionSuggestions() as $option) {
            $values[] = '--'.$option->getName();
            if ($option->isNegatable()) {
                $values[] = '--no-'.$option->getName();
            }
        }
        $output->writeln(implode("\n", $values));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Completion\Output;

use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Guillaume Aveline <guillaume.aveline@pm.me>
 */
class FishCompletionOutput implements CompletionOutputInterface
{
    public function write(CompletionSuggestions $suggestions, OutputInterface $output): void
    {
        $values = $suggestions->getValueSuggestions();
        foreach ($suggestions->getOptionSuggestions() as $option) {
            $values[] = '--'.$option->getName();
            if ($option->isNegatable()) {
                $values[] = '--no-'.$option->getName();
            }
        }
        $output->write(implode("\n", $values));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Completion;

use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;

/**
 * An input specialized for shell completion.
 *
 * This input allows unfinished option names or values and exposes what kind of
 * completion is expected.
 *
 * @author Wouter de Jong <wouter@wouterj.nl>
 */
final class CompletionInput extends ArgvInput
{
    public const TYPE_ARGUMENT_VALUE = 'argument_value';
    public const TYPE_OPTION_VALUE = 'option_value';
    public const TYPE_OPTION_NAME = 'option_name';
    public const TYPE_NONE = 'none';

    private array $tokens;
    private int $currentIndex;
    private string $completionType;
    private ?string $completionName = null;
    private string $completionValue = '';

    /**
     * Converts a terminal string into tokens.
     *
     * This is required for shell completions without COMP_WORDS support.
     */
    public static function fromString(string $inputStr, int $currentIndex): self
    {
        preg_match_all('/(?<=^|\s)([\'"]?)(.+?)(?<!\\\\)\1(?=$|\s)/', $inputStr, $tokens);

        return self::fromTokens($tokens[0], $currentIndex);
    }

    /**
     * Create an input based on an COMP_WORDS token list.
     *
     * @param string[] $tokens       the set of split tokens (e.g. COMP_WORDS or argv)
     * @param int      $currentIndex the index of the cursor (e.g. COMP_CWORD)
     */
    public static function fromTokens(array $tokens, int $currentIndex): self
    {
        $input = new self($tokens);
        $input->tokens = $tokens;
        $input->currentIndex = $currentIndex;

        return $input;
    }

    public function bind(InputDefinition $definition): void
    {
        parent::bind($definition);

        $relevantToken = $this->getRelevantToken();
        if ('-' === $relevantToken[0]) {
            // the current token is an input option: complete either option name or option value
            [$optionToken, $optionValue] = explode('=', $relevantToken, 2) + ['', ''];

            $option = $this->getOptionFromToken($optionToken);
            if (null === $option && !$this->isCursorFree()) {
                $this->completionType = self::TYPE_OPTION_NAME;
                $this->completionValue = $relevantToken;

                return;
            }

            if ($option?->acceptValue()) {
                $this->completionType = self::TYPE_OPTION_VALUE;
                $this->completionName = $option->getName();
                $this->completionValue = $optionValue ?: (!str_starts_with($optionToken, '--') ? substr($optionToken, 2) : '');

                return;
            }
        }

        $previousToken = $this->tokens[$this->currentIndex - 1];
        if ('-' === $previousToken[0] && '' !== trim($previousToken, '-')) {
            // check if previous option accepted a value
            $previousOption = $this->getOptionFromToken($previousToken);
            if ($previousOption?->acceptValue()) {
                $this->completionType = self::TYPE_OPTION_VALUE;
                $this->completionName = $previousOption->getName();
                $this->completionValue = $relevantToken;

                return;
            }
        }

        // complete argument value
        $this->completionType = self::TYPE_ARGUMENT_VALUE;

        foreach ($this->definition->getArguments() as $argumentName => $argument) {
            if (!isset($this->arguments[$argumentName])) {
                break;
            }

            $argumentValue = $this->arguments[$argumentName];
            $this->completionName = $argumentName;
            if (\is_array($argumentValue)) {
                $this->completionValue = $argumentValue ? $argumentValue[array_key_last($argumentValue)] : null;
            } else {
                $this->completionValue = $argumentValue;
            }
        }

        if ($this->currentIndex >= \count($this->tokens)) {
            if (!isset($this->arguments[$argumentName]) || $this->definition->getArgument($argumentName)->isArray()) {
                $this->completionName = $argumentName;
                $this->completionValue = '';
            } else {
                // we've reached the end
                $this->completionType = self::TYPE_NONE;
                $this->completionName = null;
                $this->completionValue = '';
            }
        }
    }

    /**
     * Returns the type of completion required.
     *
     * TYPE_ARGUMENT_VALUE when completing the value of an input argument
     * TYPE_OPTION_VALUE   when completing the value of an input option
     * TYPE_OPTION_NAME    when completing the name of an input option
     * TYPE_NONE           when nothing should be completed
     *
     * TYPE_OPTION_NAME and TYPE_NONE are already implemented by the Console component.
     *
     * @return self::TYPE_*
     */
    public function getCompletionType(): string
    {
        return $this->completionType;
    }

    /**
     * The name of the input option or argument when completing a value.
     *
     * @return string|null returns null when completing an option name
     */
    public function getCompletionName(): ?string
    {
        return $this->completionName;
    }

    /**
     * The value already typed by the user (or empty string).
     */
    public function getCompletionValue(): string
    {
        return $this->completionValue;
    }

    public function mustSuggestOptionValuesFor(string $optionName): bool
    {
        return self::TYPE_OPTION_VALUE === $this->getCompletionType() && $optionName === $this->getCompletionName();
    }

    public function mustSuggestArgumentValuesFor(string $argumentName): bool
    {
        return self::TYPE_ARGUMENT_VALUE === $this->getCompletionType() && $argumentName === $this->getCompletionName();
    }

    protected function parseToken(string $token, bool $parseOptions): bool
    {
        try {
            return parent::parseToken($token, $parseOptions);
        } catch (RuntimeException) {
            // suppress errors, completed input is almost never valid
        }

        return $parseOptions;
    }

    private function getOptionFromToken(string $optionToken): ?InputOption
    {
        $optionName = ltrim($optionToken, '-');
        if (!$optionName) {
            return null;
        }

        if ('-' === ($optionToken[1] ?? ' ')) {
            // long option name
            return $this->definition->hasOption($optionName) ? $this->definition->getOption($optionName) : null;
        }

        // short option name
        return $this->definition->hasShortcut($optionName[0]) ? $this->definition->getOptionForShortcut($optionName[0]) : null;
    }

    /**
     * The token of the cursor, or the last token if the cursor is at the end of the input.
     */
    private function getRelevantToken(): string
    {
        return $this->tokens[$this->isCursorFree() ? $this->currentIndex - 1 : $this->currentIndex];
    }

    /**
     * Whether the cursor is "free" (i.e. at the end of the input preceded by a space).
     */
    private function isCursorFree(): bool
    {
        $nrOfTokens = \count($this->tokens);
        if ($this->currentIndex > $nrOfTokens) {
            throw new \LogicException('Current index is invalid, it must be the number of input tokens or one more.');
        }

        return $this->currentIndex >= $nrOfTokens;
    }

    public function __toString()
    {
        $str = '';
        foreach ($this->tokens as $i => $token) {
            $str .= $token;

            if ($this->currentIndex === $i) {
                $str .= '|';
            }

            $str .= ' ';
        }

        if ($this->currentIndex > $i) {
            $str .= '|';
        }

        return rtrim($str);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Completion;

use Symfony\Component\Console\Input\InputOption;

/**
 * Stores all completion suggestions for the current input.
 *
 * @author Wouter de Jong <wouter@wouterj.nl>
 */
final class CompletionSuggestions
{
    private array $valueSuggestions = [];
    private array $optionSuggestions = [];

    /**
     * Add a suggested value for an input option or argument.
     *
     * @return $this
     */
    public function suggestValue(string|Suggestion $value): static
    {
        $this->valueSuggestions[] = !$value instanceof Suggestion ? new Suggestion($value) : $value;

        return $this;
    }

    /**
     * Add multiple suggested values at once for an input option or argument.
     *
     * @param list<string|Suggestion> $values
     *
     * @return $this
     */
    public function suggestValues(array $values): static
    {
        foreach ($values as $value) {
            $this->suggestValue($value);
        }

        return $this;
    }

    /**
     * Add a suggestion for an input option name.
     *
     * @return $this
     */
    public function suggestOption(InputOption $option): static
    {
        $this->optionSuggestions[] = $option;

        return $this;
    }

    /**
     * Add multiple suggestions for input option names at once.
     *
     * @param InputOption[] $options
     *
     * @return $this
     */
    public function suggestOptions(array $options): static
    {
        foreach ($options as $option) {
            $this->suggestOption($option);
        }

        return $this;
    }

    /**
     * @return InputOption[]
     */
    public function getOptionSuggestions(): array
    {
        return $this->optionSuggestions;
    }

    /**
     * @return Suggestion[]
     */
    public function getValueSuggestions(): array
    {
        return $this->valueSuggestions;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
 *
 * @internal
 */
abstract class Descriptor implements DescriptorInterface
{
    protected OutputInterface $output;

    public function describe(OutputInterface $output, object $object, array $options = []): void
    {
        $this->output = $output;

        match (true) {
            $object instanceof InputArgument => $this->describeInputArgument($object, $options),
            $object instanceof InputOption => $this->describeInputOption($object, $options),
            $object instanceof InputDefinition => $this->describeInputDefinition($object, $options),
            $object instanceof Command => $this->describeCommand($object, $options),
            $object instanceof Application => $this->describeApplication($object, $options),
            default => throw new InvalidArgumentException(\sprintf('Object of type "%s" is not describable.', get_debug_type($object))),
        };
    }

    protected function write(string $content, bool $decorated = false): void
    {
        $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW);
    }

    /**
     * Describes an InputArgument instance.
     */
    abstract protected function describeInputArgument(InputArgument $argument, array $options = []): void;

    /**
     * Describes an InputOption instance.
     */
    abstract protected function describeInputOption(InputOption $option, array $options = []): void;

    /**
     * Describes an InputDefinition instance.
     */
    abstract protected function describeInputDefinition(InputDefinition $definition, array $options = []): void;

    /**
     * Describes a Command instance.
     */
    abstract protected function describeCommand(Command $command, array $options = []): void;

    /**
     * Describes an Application instance.
     */
    abstract protected function describeApplication(Application $application, array $options = []): void;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;

/**
 * Text descriptor.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @internal
 */
class TextDescriptor extends Descriptor
{
    protected function describeInputArgument(InputArgument $argument, array $options = []): void
    {
        if (null !== $argument->getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) {
            $default = \sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($argument->getDefault()));
        } else {
            $default = '';
        }

        $totalWidth = $options['total_width'] ?? Helper::width($argument->getName());
        $spacingWidth = $totalWidth - \strlen($argument->getName());

        $this->writeText(\sprintf('  <info>%s</info>  %s%s%s',
            $argument->getName(),
            str_repeat(' ', $spacingWidth),
            // + 4 = 2 spaces before <info>, 2 spaces after </info>
            preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $argument->getDescription()),
            $default
        ), $options);
    }

    protected function describeInputOption(InputOption $option, array $options = []): void
    {
        if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) {
            $default = \sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($option->getDefault()));
        } else {
            $default = '';
        }

        $value = '';
        if ($option->acceptValue()) {
            $value = '='.strtoupper($option->getName());

            if ($option->isValueOptional()) {
                $value = '['.$value.']';
            }
        }

        $totalWidth = $options['total_width'] ?? $this->calculateTotalWidthForOptions([$option]);
        $synopsis = \sprintf('%s%s',
            $option->getShortcut() ? \sprintf('-%s, ', $option->getShortcut()) : '    ',
            \sprintf($option->isNegatable() ? '--%1$s|--no-%1$s' : '--%1$s%2$s', $option->getName(), $value)
        );

        $spacingWidth = $totalWidth - Helper::width($synopsis);

        $this->writeText(\sprintf('  <info>%s</info>  %s%s%s%s',
            $synopsis,
            str_repeat(' ', $spacingWidth),
            // + 4 = 2 spaces before <info>, 2 spaces after </info>
            preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $option->getDescription()),
            $default,
            $option->isArray() ? '<comment> (multiple values allowed)</comment>' : ''
        ), $options);
    }

    protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
    {
        $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions());
        foreach ($definition->getArguments() as $argument) {
            $totalWidth = max($totalWidth, Helper::width($argument->getName()));
        }

        if ($definition->getArguments()) {
            $this->writeText('<comment>Arguments:</comment>', $options);
            $this->writeText("\n");
            foreach ($definition->getArguments() as $argument) {
                $this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth]));
                $this->writeText("\n");
            }
        }

        if ($definition->getArguments() && $definition->getOptions()) {
            $this->writeText("\n");
        }

        if ($definition->getOptions()) {
            $laterOptions = [];

            $this->writeText('<comment>Options:</comment>', $options);
            foreach ($definition->getOptions() as $option) {
                if (\strlen($option->getShortcut() ?? '') > 1) {
                    $laterOptions[] = $option;
                    continue;
                }
                $this->writeText("\n");
                $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
            }
            foreach ($laterOptions as $option) {
                $this->writeText("\n");
                $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
            }
        }
    }

    protected function describeCommand(Command $command, array $options = []): void
    {
        $command->mergeApplicationDefinition(false);

        if ($description = $command->getDescription()) {
            $this->writeText('<comment>Description:</comment>', $options);
            $this->writeText("\n");
            $this->writeText('  '.$description);
            $this->writeText("\n\n");
        }

        $this->writeText('<comment>Usage:</comment>', $options);
        foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) {
            $this->writeText("\n");
            $this->writeText('  '.OutputFormatter::escape($usage), $options);
        }
        $this->writeText("\n");

        $definition = $command->getDefinition();
        if ($definition->getOptions() || $definition->getArguments()) {
            $this->writeText("\n");
            $this->describeInputDefinition($definition, $options);
            $this->writeText("\n");
        }

        $help = $command->getProcessedHelp();
        if ($help && $help !== $description) {
            $this->writeText("\n");
            $this->writeText('<comment>Help:</comment>', $options);
            $this->writeText("\n");
            $this->writeText('  '.str_replace("\n", "\n  ", $help), $options);
            $this->writeText("\n");
        }
    }

    protected function describeApplication(Application $application, array $options = []): void
    {
        $describedNamespace = $options['namespace'] ?? null;
        $description = new ApplicationDescription($application, $describedNamespace);

        if (isset($options['raw_text']) && $options['raw_text']) {
            $width = $this->getColumnWidth($description->getCommands());

            foreach ($description->getCommands() as $command) {
                $this->writeText(\sprintf("%-{$width}s %s", $command->getName(), $command->getDescription()), $options);
                $this->writeText("\n");
            }
        } else {
            if ('' != $help = $application->getHelp()) {
                $this->writeText("$help\n\n", $options);
            }

            $this->writeText("<comment>Usage:</comment>\n", $options);
            $this->writeText("  command [options] [arguments]\n\n", $options);

            $this->describeInputDefinition(new InputDefinition($application->getDefinition()->getOptions()), $options);

            $this->writeText("\n");
            $this->writeText("\n");

            $commands = $description->getCommands();
            $namespaces = $description->getNamespaces();
            if ($describedNamespace && $namespaces) {
                // make sure all alias commands are included when describing a specific namespace
                $describedNamespaceInfo = reset($namespaces);
                foreach ($describedNamespaceInfo['commands'] as $name) {
                    $commands[$name] = $description->getCommand($name);
                }
            }

            // calculate max. width based on available commands per namespace
            $width = $this->getColumnWidth(array_merge(...array_values(array_map(fn ($namespace) => array_intersect($namespace['commands'], array_keys($commands)), array_values($namespaces)))));

            if ($describedNamespace) {
                $this->writeText(\sprintf('<comment>Available commands for the "%s" namespace:</comment>', $describedNamespace), $options);
            } else {
                $this->writeText('<comment>Available commands:</comment>', $options);
            }

            foreach ($namespaces as $namespace) {
                $namespace['commands'] = array_filter($namespace['commands'], fn ($name) => isset($commands[$name]));

                if (!$namespace['commands']) {
                    continue;
                }

                if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
                    $this->writeText("\n");
                    $this->writeText(' <comment>'.$namespace['id'].'</comment>', $options);
                }

                foreach ($namespace['commands'] as $name) {
                    $this->writeText("\n");
                    $spacingWidth = $width - Helper::width($name);
                    $command = $commands[$name];
                    $commandAliases = $name === $command->getName() ? $this->getCommandAliasesText($command) : '';
                    $this->writeText(\sprintf('  <info>%s</info>%s%s', $name, str_repeat(' ', $spacingWidth), $commandAliases.$command->getDescription()), $options);
                }
            }

            $this->writeText("\n");
        }
    }

    private function writeText(string $content, array $options = []): void
    {
        $this->write(
            isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content,
            isset($options['raw_output']) ? !$options['raw_output'] : true
        );
    }

    /**
     * Formats command aliases to show them in the command description.
     */
    private function getCommandAliasesText(Command $command): string
    {
        $text = '';
        $aliases = $command->getAliases();

        if ($aliases) {
            $text = '['.implode('|', $aliases).'] ';
        }

        return $text;
    }

    /**
     * Formats input option/argument default value.
     */
    private function formatDefaultValue(mixed $default): string
    {
        if (\INF === $default) {
            return 'INF';
        }

        if (\is_string($default)) {
            $default = OutputFormatter::escape($default);
        } elseif (\is_array($default)) {
            foreach ($default as $key => $value) {
                if (\is_string($value)) {
                    $default[$key] = OutputFormatter::escape($value);
                }
            }
        }

        return str_replace('\\\\', '\\', json_encode($default, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE));
    }

    /**
     * @param array<Command|string> $commands
     */
    private function getColumnWidth(array $commands): int
    {
        $widths = [];

        foreach ($commands as $command) {
            if ($command instanceof Command) {
                $widths[] = Helper::width($command->getName());
                foreach ($command->getAliases() as $alias) {
                    $widths[] = Helper::width($alias);
                }
            } else {
                $widths[] = Helper::width($command);
            }
        }

        return $widths ? max($widths) + 2 : 0;
    }

    /**
     * @param InputOption[] $options
     */
    private function calculateTotalWidthForOptions(array $options): int
    {
        $totalWidth = 0;
        foreach ($options as $option) {
            // "-" + shortcut + ", --" + name
            $nameLength = 1 + max(Helper::width($option->getShortcut()), 1) + 4 + Helper::width($option->getName());
            if ($option->isNegatable()) {
                $nameLength += 6 + Helper::width($option->getName()); // |--no- + name
            } elseif ($option->acceptValue()) {
                $valueLength = 1 + Helper::width($option->getName()); // = + value
                $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ]

                $nameLength += $valueLength;
            }
            $totalWidth = max($totalWidth, $nameLength);
        }

        return $totalWidth;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Output\OutputInterface;

/**
 * Descriptor interface.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
interface DescriptorInterface
{
    /**
     * @return void
     */
    public function describe(OutputInterface $output, object $object, array $options = []);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\String\UnicodeString;

class ReStructuredTextDescriptor extends Descriptor
{
    // <h1>
    private string $partChar = '=';
    // <h2>
    private string $chapterChar = '-';
    // <h3>
    private string $sectionChar = '~';
    // <h4>
    private string $subsectionChar = '.';
    // <h5>
    private string $subsubsectionChar = '^';
    // <h6>
    private string $paragraphsChar = '"';

    private array $visibleNamespaces = [];

    public function describe(OutputInterface $output, object $object, array $options = []): void
    {
        $decorated = $output->isDecorated();
        $output->setDecorated(false);

        parent::describe($output, $object, $options);

        $output->setDecorated($decorated);
    }

    /**
     * Override parent method to set $decorated = true.
     */
    protected function write(string $content, bool $decorated = true): void
    {
        parent::write($content, $decorated);
    }

    protected function describeInputArgument(InputArgument $argument, array $options = []): void
    {
        $this->write(
            $argument->getName() ?: '<none>'."\n".str_repeat($this->paragraphsChar, Helper::width($argument->getName()))."\n\n"
                .($argument->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $argument->getDescription())."\n\n" : '')
                .'- **Is required**: '.($argument->isRequired() ? 'yes' : 'no')."\n"
                .'- **Is array**: '.($argument->isArray() ? 'yes' : 'no')."\n"
                .'- **Default**: ``'.str_replace("\n", '', var_export($argument->getDefault(), true)).'``'
        );
    }

    protected function describeInputOption(InputOption $option, array $options = []): void
    {
        $name = '\-\-'.$option->getName();
        if ($option->isNegatable()) {
            $name .= '|\-\-no-'.$option->getName();
        }
        if ($option->getShortcut()) {
            $name .= '|-'.str_replace('|', '|-', $option->getShortcut());
        }

        $optionDescription = $option->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n\n", $option->getDescription())."\n\n" : '';
        $optionDescription = (new UnicodeString($optionDescription))->ascii();
        $this->write(
            $name."\n".str_repeat($this->paragraphsChar, Helper::width($name))."\n\n"
            .$optionDescription
            .'- **Accept value**: '.($option->acceptValue() ? 'yes' : 'no')."\n"
            .'- **Is value required**: '.($option->isValueRequired() ? 'yes' : 'no')."\n"
            .'- **Is multiple**: '.($option->isArray() ? 'yes' : 'no')."\n"
            .'- **Is negatable**: '.($option->isNegatable() ? 'yes' : 'no')."\n"
            .'- **Default**: ``'.str_replace("\n", '', var_export($option->getDefault(), true)).'``'."\n"
        );
    }

    protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
    {
        if ($showArguments = ((bool) $definition->getArguments())) {
            $this->write("Arguments\n".str_repeat($this->subsubsectionChar, 9))."\n\n";
            foreach ($definition->getArguments() as $argument) {
                $this->write("\n\n");
                $this->describeInputArgument($argument);
            }
        }

        if ($nonDefaultOptions = $this->getNonDefaultOptions($definition)) {
            if ($showArguments) {
                $this->write("\n\n");
            }

            $this->write("Options\n".str_repeat($this->subsubsectionChar, 7)."\n\n");
            foreach ($nonDefaultOptions as $option) {
                $this->describeInputOption($option);
                $this->write("\n");
            }
        }
    }

    protected function describeCommand(Command $command, array $options = []): void
    {
        if ($options['short'] ?? false) {
            $this->write(
                '``'.$command->getName()."``\n"
                .str_repeat($this->subsectionChar, Helper::width($command->getName()))."\n\n"
                .($command->getDescription() ? $command->getDescription()."\n\n" : '')
                ."Usage\n".str_repeat($this->paragraphsChar, 5)."\n\n"
                .array_reduce($command->getAliases(), static fn ($carry, $usage) => $carry.'- ``'.$usage.'``'."\n")
            );

            return;
        }

        $command->mergeApplicationDefinition(false);

        foreach ($command->getAliases() as $alias) {
            $this->write('.. _'.$alias.":\n\n");
        }
        $this->write(
            $command->getName()."\n"
            .str_repeat($this->subsectionChar, Helper::width($command->getName()))."\n\n"
            .($command->getDescription() ? $command->getDescription()."\n\n" : '')
            ."Usage\n".str_repeat($this->subsubsectionChar, 5)."\n\n"
            .array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), static fn ($carry, $usage) => $carry.'- ``'.$usage.'``'."\n")
        );

        if ($help = $command->getProcessedHelp()) {
            $this->write("\n");
            $this->write($help);
        }

        $definition = $command->getDefinition();
        if ($definition->getOptions() || $definition->getArguments()) {
            $this->write("\n\n");
            $this->describeInputDefinition($definition);
        }
    }

    protected function describeApplication(Application $application, array $options = []): void
    {
        $description = new ApplicationDescription($application, $options['namespace'] ?? null);
        $title = $this->getApplicationTitle($application);

        $this->write($title."\n".str_repeat($this->partChar, Helper::width($title)));
        $this->createTableOfContents($description, $application);
        $this->describeCommands($application, $options);
    }

    private function getApplicationTitle(Application $application): string
    {
        if ('UNKNOWN' === $application->getName()) {
            return 'Console Tool';
        }
        if ('UNKNOWN' !== $application->getVersion()) {
            return \sprintf('%s %s', $application->getName(), $application->getVersion());
        }

        return $application->getName();
    }

    private function describeCommands($application, array $options): void
    {
        $title = 'Commands';
        $this->write("\n\n$title\n".str_repeat($this->chapterChar, Helper::width($title))."\n\n");
        foreach ($this->visibleNamespaces as $namespace) {
            if ('_global' === $namespace) {
                $commands = $application->all('');
                $this->write('Global'."\n".str_repeat($this->sectionChar, Helper::width('Global'))."\n\n");
            } else {
                $commands = $application->all($namespace);
                $this->write($namespace."\n".str_repeat($this->sectionChar, Helper::width($namespace))."\n\n");
            }

            foreach ($this->removeAliasesAndHiddenCommands($commands) as $command) {
                $this->describeCommand($command, $options);
                $this->write("\n\n");
            }
        }
    }

    private function createTableOfContents(ApplicationDescription $description, Application $application): void
    {
        $this->setVisibleNamespaces($description);
        $chapterTitle = 'Table of Contents';
        $this->write("\n\n$chapterTitle\n".str_repeat($this->chapterChar, Helper::width($chapterTitle))."\n\n");
        foreach ($this->visibleNamespaces as $namespace) {
            if ('_global' === $namespace) {
                $commands = $application->all('');
            } else {
                $commands = $application->all($namespace);
                $this->write("\n\n");
                $this->write($namespace."\n".str_repeat($this->sectionChar, Helper::width($namespace))."\n\n");
            }
            $commands = $this->removeAliasesAndHiddenCommands($commands);

            $this->write("\n\n");
            $this->write(implode("\n", array_map(static fn ($commandName) => \sprintf('- `%s`_', $commandName), array_keys($commands))));
        }
    }

    private function getNonDefaultOptions(InputDefinition $definition): array
    {
        $globalOptions = [
            'help',
            'quiet',
            'verbose',
            'version',
            'ansi',
            'no-interaction',
        ];
        $nonDefaultOptions = [];
        foreach ($definition->getOptions() as $option) {
            // Skip global options.
            if (!\in_array($option->getName(), $globalOptions)) {
                $nonDefaultOptions[] = $option;
            }
        }

        return $nonDefaultOptions;
    }

    private function setVisibleNamespaces(ApplicationDescription $description): void
    {
        $commands = $description->getCommands();
        foreach ($description->getNamespaces() as $namespace) {
            try {
                $namespaceCommands = $namespace['commands'];
                foreach ($namespaceCommands as $key => $commandName) {
                    if (!\array_key_exists($commandName, $commands)) {
                        // If the array key does not exist, then this is an alias.
                        unset($namespaceCommands[$key]);
                    } elseif ($commands[$commandName]->isHidden()) {
                        unset($namespaceCommands[$key]);
                    }
                }
                if (!$namespaceCommands) {
                    // If the namespace contained only aliases or hidden commands, skip the namespace.
                    continue;
                }
            } catch (\Exception) {
            }
            $this->visibleNamespaces[] = $namespace['id'];
        }
    }

    private function removeAliasesAndHiddenCommands(array $commands): array
    {
        foreach ($commands as $key => $command) {
            if ($command->isHidden() || \in_array($key, $command->getAliases(), true)) {
                unset($commands[$key]);
            }
        }
        unset($commands['completion']);

        return $commands;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;

/**
 * XML descriptor.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @internal
 */
class XmlDescriptor extends Descriptor
{
    public function getInputDefinitionDocument(InputDefinition $definition): \DOMDocument
    {
        $dom = new \DOMDocument('1.0', 'UTF-8');
        $dom->appendChild($definitionXML = $dom->createElement('definition'));

        $definitionXML->appendChild($argumentsXML = $dom->createElement('arguments'));
        foreach ($definition->getArguments() as $argument) {
            $this->appendDocument($argumentsXML, $this->getInputArgumentDocument($argument));
        }

        $definitionXML->appendChild($optionsXML = $dom->createElement('options'));
        foreach ($definition->getOptions() as $option) {
            $this->appendDocument($optionsXML, $this->getInputOptionDocument($option));
        }

        return $dom;
    }

    public function getCommandDocument(Command $command, bool $short = false): \DOMDocument
    {
        $dom = new \DOMDocument('1.0', 'UTF-8');
        $dom->appendChild($commandXML = $dom->createElement('command'));

        $commandXML->setAttribute('id', $command->getName());
        $commandXML->setAttribute('name', $command->getName());
        $commandXML->setAttribute('hidden', $command->isHidden() ? 1 : 0);

        $commandXML->appendChild($usagesXML = $dom->createElement('usages'));

        $commandXML->appendChild($descriptionXML = $dom->createElement('description'));
        $descriptionXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getDescription())));

        if ($short) {
            foreach ($command->getAliases() as $usage) {
                $usagesXML->appendChild($dom->createElement('usage', $usage));
            }
        } else {
            $command->mergeApplicationDefinition(false);

            foreach (array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()) as $usage) {
                $usagesXML->appendChild($dom->createElement('usage', $usage));
            }

            $commandXML->appendChild($helpXML = $dom->createElement('help'));
            $helpXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getProcessedHelp())));

            $definitionXML = $this->getInputDefinitionDocument($command->getDefinition());
            $this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0));
        }

        return $dom;
    }

    public function getApplicationDocument(Application $application, ?string $namespace = null, bool $short = false): \DOMDocument
    {
        $dom = new \DOMDocument('1.0', 'UTF-8');
        $dom->appendChild($rootXml = $dom->createElement('symfony'));

        if ('UNKNOWN' !== $application->getName()) {
            $rootXml->setAttribute('name', $application->getName());
            if ('UNKNOWN' !== $application->getVersion()) {
                $rootXml->setAttribute('version', $application->getVersion());
            }
        }

        $rootXml->appendChild($commandsXML = $dom->createElement('commands'));

        $description = new ApplicationDescription($application, $namespace, true);

        if ($namespace) {
            $commandsXML->setAttribute('namespace', $namespace);
        }

        foreach ($description->getCommands() as $command) {
            $this->appendDocument($commandsXML, $this->getCommandDocument($command, $short));
        }

        if (!$namespace) {
            $rootXml->appendChild($namespacesXML = $dom->createElement('namespaces'));

            foreach ($description->getNamespaces() as $namespaceDescription) {
                $namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace'));
                $namespaceArrayXML->setAttribute('id', $namespaceDescription['id']);

                foreach ($namespaceDescription['commands'] as $name) {
                    $namespaceArrayXML->appendChild($commandXML = $dom->createElement('command'));
                    $commandXML->appendChild($dom->createTextNode($name));
                }
            }
        }

        return $dom;
    }

    protected function describeInputArgument(InputArgument $argument, array $options = []): void
    {
        $this->writeDocument($this->getInputArgumentDocument($argument));
    }

    protected function describeInputOption(InputOption $option, array $options = []): void
    {
        $this->writeDocument($this->getInputOptionDocument($option));
    }

    protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
    {
        $this->writeDocument($this->getInputDefinitionDocument($definition));
    }

    protected function describeCommand(Command $command, array $options = []): void
    {
        $this->writeDocument($this->getCommandDocument($command, $options['short'] ?? false));
    }

    protected function describeApplication(Application $application, array $options = []): void
    {
        $this->writeDocument($this->getApplicationDocument($application, $options['namespace'] ?? null, $options['short'] ?? false));
    }

    /**
     * Appends document children to parent node.
     */
    private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent): void
    {
        foreach ($importedParent->childNodes as $childNode) {
            $parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true));
        }
    }

    /**
     * Writes DOM document.
     */
    private function writeDocument(\DOMDocument $dom): void
    {
        $dom->formatOutput = true;
        $this->write($dom->saveXML());
    }

    private function getInputArgumentDocument(InputArgument $argument): \DOMDocument
    {
        $dom = new \DOMDocument('1.0', 'UTF-8');

        $dom->appendChild($objectXML = $dom->createElement('argument'));
        $objectXML->setAttribute('name', $argument->getName());
        $objectXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0);
        $objectXML->setAttribute('is_array', $argument->isArray() ? 1 : 0);
        $objectXML->appendChild($descriptionXML = $dom->createElement('description'));
        $descriptionXML->appendChild($dom->createTextNode($argument->getDescription()));

        $objectXML->appendChild($defaultsXML = $dom->createElement('defaults'));
        $defaults = \is_array($argument->getDefault()) ? $argument->getDefault() : (\is_bool($argument->getDefault()) ? [var_export($argument->getDefault(), true)] : ($argument->getDefault() ? [$argument->getDefault()] : []));
        foreach ($defaults as $default) {
            $defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
            $defaultXML->appendChild($dom->createTextNode($default));
        }

        return $dom;
    }

    private function getInputOptionDocument(InputOption $option): \DOMDocument
    {
        $dom = new \DOMDocument('1.0', 'UTF-8');

        $dom->appendChild($objectXML = $dom->createElement('option'));
        $objectXML->setAttribute('name', '--'.$option->getName());
        $pos = strpos($option->getShortcut() ?? '', '|');
        if (false !== $pos) {
            $objectXML->setAttribute('shortcut', '-'.substr($option->getShortcut(), 0, $pos));
            $objectXML->setAttribute('shortcuts', '-'.str_replace('|', '|-', $option->getShortcut()));
        } else {
            $objectXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : '');
        }
        $objectXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0);
        $objectXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0);
        $objectXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0);
        $objectXML->appendChild($descriptionXML = $dom->createElement('description'));
        $descriptionXML->appendChild($dom->createTextNode($option->getDescription()));

        if ($option->acceptValue()) {
            $defaults = \is_array($option->getDefault()) ? $option->getDefault() : (\is_bool($option->getDefault()) ? [var_export($option->getDefault(), true)] : ($option->getDefault() ? [$option->getDefault()] : []));
            $objectXML->appendChild($defaultsXML = $dom->createElement('defaults'));

            if (!empty($defaults)) {
                foreach ($defaults as $default) {
                    $defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
                    $defaultXML->appendChild($dom->createTextNode($default));
                }
            }
        }

        if ($option->isNegatable()) {
            $dom->appendChild($objectXML = $dom->createElement('option'));
            $objectXML->setAttribute('name', '--no-'.$option->getName());
            $objectXML->setAttribute('shortcut', '');
            $objectXML->setAttribute('accept_value', 0);
            $objectXML->setAttribute('is_value_required', 0);
            $objectXML->setAttribute('is_multiple', 0);
            $objectXML->appendChild($descriptionXML = $dom->createElement('description'));
            $descriptionXML->appendChild($dom->createTextNode('Negate the "--'.$option->getName().'" option'));
        }

        return $dom;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\CommandNotFoundException;

/**
 * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
 *
 * @internal
 */
class ApplicationDescription
{
    public const GLOBAL_NAMESPACE = '_global';

    private Application $application;
    private ?string $namespace;
    private bool $showHidden;
    private array $namespaces;

    /**
     * @var array<string, Command>
     */
    private array $commands;

    /**
     * @var array<string, Command>
     */
    private array $aliases = [];

    public function __construct(Application $application, ?string $namespace = null, bool $showHidden = false)
    {
        $this->application = $application;
        $this->namespace = $namespace;
        $this->showHidden = $showHidden;
    }

    public function getNamespaces(): array
    {
        if (!isset($this->namespaces)) {
            $this->inspectApplication();
        }

        return $this->namespaces;
    }

    /**
     * @return Command[]
     */
    public function getCommands(): array
    {
        if (!isset($this->commands)) {
            $this->inspectApplication();
        }

        return $this->commands;
    }

    /**
     * @throws CommandNotFoundException
     */
    public function getCommand(string $name): Command
    {
        if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) {
            throw new CommandNotFoundException(\sprintf('Command "%s" does not exist.', $name));
        }

        return $this->commands[$name] ?? $this->aliases[$name];
    }

    private function inspectApplication(): void
    {
        $this->commands = [];
        $this->namespaces = [];

        $all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null);
        foreach ($this->sortCommands($all) as $namespace => $commands) {
            $names = [];

            /** @var Command $command */
            foreach ($commands as $name => $command) {
                if (!$command->getName() || (!$this->showHidden && $command->isHidden())) {
                    continue;
                }

                if ($command->getName() === $name) {
                    $this->commands[$name] = $command;
                } else {
                    $this->aliases[$name] = $command;
                }

                $names[] = $name;
            }

            $this->namespaces[$namespace] = ['id' => $namespace, 'commands' => $names];
        }
    }

    private function sortCommands(array $commands): array
    {
        $namespacedCommands = [];
        $globalCommands = [];
        $sortedCommands = [];
        foreach ($commands as $name => $command) {
            $key = $this->application->extractNamespace($name, 1);
            if (\in_array($key, ['', self::GLOBAL_NAMESPACE], true)) {
                $globalCommands[$name] = $command;
            } else {
                $namespacedCommands[$key][$name] = $command;
            }
        }

        if ($globalCommands) {
            ksort($globalCommands);
            $sortedCommands[self::GLOBAL_NAMESPACE] = $globalCommands;
        }

        if ($namespacedCommands) {
            ksort($namespacedCommands, \SORT_STRING);
            foreach ($namespacedCommands as $key => $commandsSet) {
                ksort($commandsSet);
                $sortedCommands[$key] = $commandsSet;
            }
        }

        return $sortedCommands;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Markdown descriptor.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @internal
 */
class MarkdownDescriptor extends Descriptor
{
    public function describe(OutputInterface $output, object $object, array $options = []): void
    {
        $decorated = $output->isDecorated();
        $output->setDecorated(false);

        parent::describe($output, $object, $options);

        $output->setDecorated($decorated);
    }

    protected function write(string $content, bool $decorated = true): void
    {
        parent::write($content, $decorated);
    }

    protected function describeInputArgument(InputArgument $argument, array $options = []): void
    {
        $this->write(
            '#### `'.($argument->getName() ?: '<none>')."`\n\n"
            .($argument->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $argument->getDescription())."\n\n" : '')
            .'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n"
            .'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n"
            .'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`'
        );
    }

    protected function describeInputOption(InputOption $option, array $options = []): void
    {
        $name = '--'.$option->getName();
        if ($option->isNegatable()) {
            $name .= '|--no-'.$option->getName();
        }
        if ($option->getShortcut()) {
            $name .= '|-'.str_replace('|', '|-', $option->getShortcut()).'';
        }

        $this->write(
            '#### `'.$name.'`'."\n\n"
            .($option->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $option->getDescription())."\n\n" : '')
            .'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n"
            .'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n"
            .'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n"
            .'* Is negatable: '.($option->isNegatable() ? 'yes' : 'no')."\n"
            .'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`'
        );
    }

    protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
    {
        if ($showArguments = \count($definition->getArguments()) > 0) {
            $this->write('### Arguments');
            foreach ($definition->getArguments() as $argument) {
                $this->write("\n\n");
                $this->describeInputArgument($argument);
            }
        }

        if (\count($definition->getOptions()) > 0) {
            if ($showArguments) {
                $this->write("\n\n");
            }

            $this->write('### Options');
            foreach ($definition->getOptions() as $option) {
                $this->write("\n\n");
                $this->describeInputOption($option);
            }
        }
    }

    protected function describeCommand(Command $command, array $options = []): void
    {
        if ($options['short'] ?? false) {
            $this->write(
                '`'.$command->getName()."`\n"
                .str_repeat('-', Helper::width($command->getName()) + 2)."\n\n"
                .($command->getDescription() ? $command->getDescription()."\n\n" : '')
                .'### Usage'."\n\n"
                .array_reduce($command->getAliases(), fn ($carry, $usage) => $carry.'* `'.$usage.'`'."\n")
            );

            return;
        }

        $command->mergeApplicationDefinition(false);

        $this->write(
            '`'.$command->getName()."`\n"
            .str_repeat('-', Helper::width($command->getName()) + 2)."\n\n"
            .($command->getDescription() ? $command->getDescription()."\n\n" : '')
            .'### Usage'."\n\n"
            .array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), fn ($carry, $usage) => $carry.'* `'.$usage.'`'."\n")
        );

        if ($help = $command->getProcessedHelp()) {
            $this->write("\n");
            $this->write($help);
        }

        $definition = $command->getDefinition();
        if ($definition->getOptions() || $definition->getArguments()) {
            $this->write("\n\n");
            $this->describeInputDefinition($definition);
        }
    }

    protected function describeApplication(Application $application, array $options = []): void
    {
        $describedNamespace = $options['namespace'] ?? null;
        $description = new ApplicationDescription($application, $describedNamespace);
        $title = $this->getApplicationTitle($application);

        $this->write($title."\n".str_repeat('=', Helper::width($title)));

        foreach ($description->getNamespaces() as $namespace) {
            if (ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
                $this->write("\n\n");
                $this->write('**'.$namespace['id'].':**');
            }

            $this->write("\n\n");
            $this->write(implode("\n", array_map(fn ($commandName) => \sprintf('* [`%s`](#%s)', $commandName, str_replace(':', '', $description->getCommand($commandName)->getName())), $namespace['commands'])));
        }

        foreach ($description->getCommands() as $command) {
            $this->write("\n\n");
            $this->describeCommand($command, $options);
        }
    }

    private function getApplicationTitle(Application $application): string
    {
        if ('UNKNOWN' !== $application->getName()) {
            if ('UNKNOWN' !== $application->getVersion()) {
                return \sprintf('%s %s', $application->getName(), $application->getVersion());
            }

            return $application->getName();
        }

        return 'Console Tool';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;

/**
 * JSON descriptor.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @internal
 */
class JsonDescriptor extends Descriptor
{
    protected function describeInputArgument(InputArgument $argument, array $options = []): void
    {
        $this->writeData($this->getInputArgumentData($argument), $options);
    }

    protected function describeInputOption(InputOption $option, array $options = []): void
    {
        $this->writeData($this->getInputOptionData($option), $options);
        if ($option->isNegatable()) {
            $this->writeData($this->getInputOptionData($option, true), $options);
        }
    }

    protected function describeInputDefinition(InputDefinition $definition, array $options = []): void
    {
        $this->writeData($this->getInputDefinitionData($definition), $options);
    }

    protected function describeCommand(Command $command, array $options = []): void
    {
        $this->writeData($this->getCommandData($command, $options['short'] ?? false), $options);
    }

    protected function describeApplication(Application $application, array $options = []): void
    {
        $describedNamespace = $options['namespace'] ?? null;
        $description = new ApplicationDescription($application, $describedNamespace, true);
        $commands = [];

        foreach ($description->getCommands() as $command) {
            $commands[] = $this->getCommandData($command, $options['short'] ?? false);
        }

        $data = [];
        if ('UNKNOWN' !== $application->getName()) {
            $data['application']['name'] = $application->getName();
            if ('UNKNOWN' !== $application->getVersion()) {
                $data['application']['version'] = $application->getVersion();
            }
        }

        $data['commands'] = $commands;

        if ($describedNamespace) {
            $data['namespace'] = $describedNamespace;
        } else {
            $data['namespaces'] = array_values($description->getNamespaces());
        }

        $this->writeData($data, $options);
    }

    /**
     * Writes data as json.
     */
    private function writeData(array $data, array $options): void
    {
        $flags = $options['json_encoding'] ?? 0;

        $this->write(json_encode($data, $flags));
    }

    private function getInputArgumentData(InputArgument $argument): array
    {
        return [
            'name' => $argument->getName(),
            'is_required' => $argument->isRequired(),
            'is_array' => $argument->isArray(),
            'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $argument->getDescription()),
            'default' => \INF === $argument->getDefault() ? 'INF' : $argument->getDefault(),
        ];
    }

    private function getInputOptionData(InputOption $option, bool $negated = false): array
    {
        return $negated ? [
            'name' => '--no-'.$option->getName(),
            'shortcut' => '',
            'accept_value' => false,
            'is_value_required' => false,
            'is_multiple' => false,
            'description' => 'Negate the "--'.$option->getName().'" option',
            'default' => null === $option->getDefault() ? null : !$option->getDefault(),
        ] : [
            'name' => '--'.$option->getName(),
            'shortcut' => $option->getShortcut() ? '-'.str_replace('|', '|-', $option->getShortcut()) : '',
            'accept_value' => $option->acceptValue(),
            'is_value_required' => $option->isValueRequired(),
            'is_multiple' => $option->isArray(),
            'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $option->getDescription()),
            'default' => \INF === $option->getDefault() ? 'INF' : $option->getDefault(),
        ];
    }

    private function getInputDefinitionData(InputDefinition $definition): array
    {
        $inputArguments = [];
        foreach ($definition->getArguments() as $name => $argument) {
            $inputArguments[$name] = $this->getInputArgumentData($argument);
        }

        $inputOptions = [];
        foreach ($definition->getOptions() as $name => $option) {
            $inputOptions[$name] = $this->getInputOptionData($option);
            if ($option->isNegatable()) {
                $inputOptions['no-'.$name] = $this->getInputOptionData($option, true);
            }
        }

        return ['arguments' => $inputArguments, 'options' => $inputOptions];
    }

    private function getCommandData(Command $command, bool $short = false): array
    {
        $data = [
            'name' => $command->getName(),
            'description' => $command->getDescription(),
        ];

        if ($short) {
            $data += [
                'usage' => $command->getAliases(),
            ];
        } else {
            $command->mergeApplicationDefinition(false);

            $data += [
                'usage' => array_merge([$command->getSynopsis()], $command->getUsages(), $command->getAliases()),
                'help' => $command->getProcessedHelp(),
                'definition' => $this->getInputDefinitionData($command->getDefinition()),
            ];
        }

        $data['hidden'] = $command->isHidden();

        return $data;
    }
}
Copyright (c) 2004-present Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Julien Boudry <julien@condorcet.vote>
 */
enum AnsiColorMode
{
    /*
     * Classical 4-bit Ansi colors, including 8 classical colors and 8 bright color. Output syntax is "ESC[${foreGroundColorcode};${backGroundColorcode}m"
     * Must be compatible with all terminals and it's the minimal version supported.
     */
    case Ansi4;

    /*
     * 8-bit Ansi colors (240 different colors + 16 duplicate color codes, ensuring backward compatibility).
     * Output syntax is: "ESC[38;5;${foreGroundColorcode};48;5;${backGroundColorcode}m"
     * Should be compatible with most terminals.
     */
    case Ansi8;

    /*
     * 24-bit Ansi colors (RGB).
     * Output syntax is: "ESC[38;2;${foreGroundColorcodeRed};${foreGroundColorcodeGreen};${foreGroundColorcodeBlue};48;2;${backGroundColorcodeRed};${backGroundColorcodeGreen};${backGroundColorcodeBlue}m"
     * May be compatible with many modern terminals.
     */
    case Ansi24;

    /**
     * Converts an RGB hexadecimal color to the corresponding Ansi code.
     */
    public function convertFromHexToAnsiColorCode(string $hexColor): string
    {
        $hexColor = str_replace('#', '', $hexColor);

        if (3 === \strlen($hexColor)) {
            $hexColor = $hexColor[0].$hexColor[0].$hexColor[1].$hexColor[1].$hexColor[2].$hexColor[2];
        }

        if (6 !== \strlen($hexColor)) {
            throw new InvalidArgumentException(\sprintf('Invalid "#%s" color.', $hexColor));
        }

        $color = hexdec($hexColor);

        $r = ($color >> 16) & 255;
        $g = ($color >> 8) & 255;
        $b = $color & 255;

        return match ($this) {
            self::Ansi4 => (string) $this->convertFromRGB($r, $g, $b),
            self::Ansi8 => '8;5;'.((string) $this->convertFromRGB($r, $g, $b)),
            self::Ansi24 => \sprintf('8;2;%d;%d;%d', $r, $g, $b),
        };
    }

    private function convertFromRGB(int $r, int $g, int $b): int
    {
        return match ($this) {
            self::Ansi4 => $this->degradeHexColorToAnsi4($r, $g, $b),
            self::Ansi8 => $this->degradeHexColorToAnsi8($r, $g, $b),
            default => throw new InvalidArgumentException("RGB cannot be converted to {$this->name}."),
        };
    }

    private function degradeHexColorToAnsi4(int $r, int $g, int $b): int
    {
        return round($b / 255) << 2 | (round($g / 255) << 1) | round($r / 255);
    }

    /**
     * Inspired from https://github.com/ajalt/colormath/blob/e464e0da1b014976736cf97250063248fc77b8e7/colormath/src/commonMain/kotlin/com/github/ajalt/colormath/model/Ansi256.kt code (MIT license).
     */
    private function degradeHexColorToAnsi8(int $r, int $g, int $b): int
    {
        if ($r === $g && $g === $b) {
            if ($r < 8) {
                return 16;
            }

            if ($r > 248) {
                return 231;
            }

            return (int) round(($r - 8) / 247 * 24) + 232;
        } else {
            return 16 +
                    (36 * (int) round($r / 255 * 5)) +
                    (6 * (int) round($g / 255 * 5)) +
                    (int) round($b / 255 * 5);
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * OutputInterface is the interface implemented by all Output classes.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
interface OutputInterface
{
    public const VERBOSITY_QUIET = 16;
    public const VERBOSITY_NORMAL = 32;
    public const VERBOSITY_VERBOSE = 64;
    public const VERBOSITY_VERY_VERBOSE = 128;
    public const VERBOSITY_DEBUG = 256;

    public const OUTPUT_NORMAL = 1;
    public const OUTPUT_RAW = 2;
    public const OUTPUT_PLAIN = 4;

    /**
     * Writes a message to the output.
     *
     * @param bool $newline Whether to add a newline
     * @param int  $options A bitmask of options (one of the OUTPUT or VERBOSITY constants),
     *                      0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
     *
     * @return void
     */
    public function write(string|iterable $messages, bool $newline = false, int $options = 0);

    /**
     * Writes a message to the output and adds a newline at the end.
     *
     * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants),
     *                     0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
     *
     * @return void
     */
    public function writeln(string|iterable $messages, int $options = 0);

    /**
     * Sets the verbosity of the output.
     *
     * @param self::VERBOSITY_* $level
     *
     * @return void
     */
    public function setVerbosity(int $level);

    /**
     * Gets the current verbosity of the output.
     *
     * @return self::VERBOSITY_*
     */
    public function getVerbosity(): int;

    /**
     * Returns whether verbosity is quiet (-q).
     */
    public function isQuiet(): bool;

    /**
     * Returns whether verbosity is verbose (-v).
     */
    public function isVerbose(): bool;

    /**
     * Returns whether verbosity is very verbose (-vv).
     */
    public function isVeryVerbose(): bool;

    /**
     * Returns whether verbosity is debug (-vvv).
     */
    public function isDebug(): bool;

    /**
     * Sets the decorated flag.
     *
     * @return void
     */
    public function setDecorated(bool $decorated);

    /**
     * Gets the decorated flag.
     */
    public function isDecorated(): bool;

    /**
     * @return void
     */
    public function setFormatter(OutputFormatterInterface $formatter);

    /**
     * Returns current output formatter instance.
     */
    public function getFormatter(): OutputFormatterInterface;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * StreamOutput writes the output to a given stream.
 *
 * Usage:
 *
 *     $output = new StreamOutput(fopen('php://stdout', 'w'));
 *
 * As `StreamOutput` can use any stream, you can also use a file:
 *
 *     $output = new StreamOutput(fopen('/path/to/output.log', 'a', false));
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class StreamOutput extends Output
{
    /** @var resource */
    private $stream;

    /**
     * @param resource                      $stream    A stream resource
     * @param int                           $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
     * @param bool|null                     $decorated Whether to decorate messages (null for auto-guessing)
     * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
     *
     * @throws InvalidArgumentException When first argument is not a real stream
     */
    public function __construct($stream, int $verbosity = self::VERBOSITY_NORMAL, ?bool $decorated = null, ?OutputFormatterInterface $formatter = null)
    {
        if (!\is_resource($stream) || 'stream' !== get_resource_type($stream)) {
            throw new InvalidArgumentException('The StreamOutput class needs a stream as its first argument.');
        }

        $this->stream = $stream;

        $decorated ??= $this->hasColorSupport();

        parent::__construct($verbosity, $decorated, $formatter);
    }

    /**
     * Gets the stream attached to this StreamOutput instance.
     *
     * @return resource
     */
    public function getStream()
    {
        return $this->stream;
    }

    /**
     * @return void
     */
    protected function doWrite(string $message, bool $newline)
    {
        if ($newline) {
            $message .= \PHP_EOL;
        }

        @fwrite($this->stream, $message);

        fflush($this->stream);
    }

    /**
     * Returns true if the stream supports colorization.
     *
     * Colorization is disabled if not supported by the stream:
     *
     * This is tricky on Windows, because Cygwin, Msys2 etc emulate pseudo
     * terminals via named pipes, so we can only check the environment.
     *
     * Reference: Composer\XdebugHandler\Process::supportsColor
     * https://github.com/composer/xdebug-handler
     *
     * @return bool true if the stream supports colorization, false otherwise
     */
    protected function hasColorSupport(): bool
    {
        // Follow https://no-color.org/
        if ('' !== (($_SERVER['NO_COLOR'] ?? getenv('NO_COLOR'))[0] ?? '')) {
            return false;
        }

        // Detect msysgit/mingw and assume this is a tty because detection
        // does not work correctly, see https://github.com/composer/composer/issues/9690
        if (!@stream_isatty($this->stream) && !\in_array(strtoupper((string) getenv('MSYSTEM')), ['MINGW32', 'MINGW64'], true)) {
            return false;
        }

        if ('\\' === \DIRECTORY_SEPARATOR && @sapi_windows_vt100_support($this->stream)) {
            return true;
        }

        if ('Hyper' === getenv('TERM_PROGRAM')
            || false !== getenv('COLORTERM')
            || false !== getenv('ANSICON')
            || 'ON' === getenv('ConEmuANSI')
        ) {
            return true;
        }

        if ('dumb' === $term = (string) getenv('TERM')) {
            return false;
        }

        // See https://github.com/chalk/supports-color/blob/d4f413efaf8da045c5ab440ed418ef02dbb28bf1/index.js#L157
        return preg_match('/^((screen|xterm|vt100|vt220|putty|rxvt|ansi|cygwin|linux).*)|(.*-256(color)?(-bce)?)$/', $term);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * A BufferedOutput that keeps only the last N chars.
 *
 * @author Jérémy Derussé <jeremy@derusse.com>
 */
class TrimmedBufferOutput extends Output
{
    private int $maxLength;
    private string $buffer = '';

    public function __construct(int $maxLength, ?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, ?OutputFormatterInterface $formatter = null)
    {
        if ($maxLength <= 0) {
            throw new InvalidArgumentException(\sprintf('"%s()" expects a strictly positive maxLength. Got %d.', __METHOD__, $maxLength));
        }

        parent::__construct($verbosity, $decorated, $formatter);
        $this->maxLength = $maxLength;
    }

    /**
     * Empties buffer and returns its content.
     */
    public function fetch(): string
    {
        $content = $this->buffer;
        $this->buffer = '';

        return $content;
    }

    /**
     * @return void
     */
    protected function doWrite(string $message, bool $newline)
    {
        $this->buffer .= $message;

        if ($newline) {
            $this->buffer .= \PHP_EOL;
        }

        $this->buffer = substr($this->buffer, 0 - $this->maxLength);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * ConsoleOutput is the default class for all CLI output. It uses STDOUT and STDERR.
 *
 * This class is a convenient wrapper around `StreamOutput` for both STDOUT and STDERR.
 *
 *     $output = new ConsoleOutput();
 *
 * This is equivalent to:
 *
 *     $output = new StreamOutput(fopen('php://stdout', 'w'));
 *     $stdErr = new StreamOutput(fopen('php://stderr', 'w'));
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
{
    private OutputInterface $stderr;
    private array $consoleSectionOutputs = [];

    /**
     * @param int                           $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
     * @param bool|null                     $decorated Whether to decorate messages (null for auto-guessing)
     * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
     */
    public function __construct(int $verbosity = self::VERBOSITY_NORMAL, ?bool $decorated = null, ?OutputFormatterInterface $formatter = null)
    {
        parent::__construct($this->openOutputStream(), $verbosity, $decorated, $formatter);

        if (null === $formatter) {
            // for BC reasons, stdErr has it own Formatter only when user don't inject a specific formatter.
            $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated);

            return;
        }

        $actualDecorated = $this->isDecorated();
        $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated, $this->getFormatter());

        if (null === $decorated) {
            $this->setDecorated($actualDecorated && $this->stderr->isDecorated());
        }
    }

    /**
     * Creates a new output section.
     */
    public function section(): ConsoleSectionOutput
    {
        return new ConsoleSectionOutput($this->getStream(), $this->consoleSectionOutputs, $this->getVerbosity(), $this->isDecorated(), $this->getFormatter());
    }

    /**
     * @return void
     */
    public function setDecorated(bool $decorated)
    {
        parent::setDecorated($decorated);
        $this->stderr->setDecorated($decorated);
    }

    /**
     * @return void
     */
    public function setFormatter(OutputFormatterInterface $formatter)
    {
        parent::setFormatter($formatter);
        $this->stderr->setFormatter($formatter);
    }

    /**
     * @return void
     */
    public function setVerbosity(int $level)
    {
        parent::setVerbosity($level);
        $this->stderr->setVerbosity($level);
    }

    public function getErrorOutput(): OutputInterface
    {
        return $this->stderr;
    }

    /**
     * @return void
     */
    public function setErrorOutput(OutputInterface $error)
    {
        $this->stderr = $error;
    }

    /**
     * Returns true if current environment supports writing console output to
     * STDOUT.
     */
    protected function hasStdoutSupport(): bool
    {
        return false === $this->isRunningOS400();
    }

    /**
     * Returns true if current environment supports writing console output to
     * STDERR.
     */
    protected function hasStderrSupport(): bool
    {
        return false === $this->isRunningOS400();
    }

    /**
     * Checks if current executing environment is IBM iSeries (OS400), which
     * doesn't properly convert character-encodings between ASCII to EBCDIC.
     */
    private function isRunningOS400(): bool
    {
        $checks = [
            \function_exists('php_uname') ? php_uname('s') : '',
            getenv('OSTYPE'),
            \PHP_OS,
        ];

        return false !== stripos(implode(';', $checks), 'OS400');
    }

    /**
     * @return resource
     */
    private function openOutputStream()
    {
        if (!$this->hasStdoutSupport()) {
            return fopen('php://output', 'w');
        }

        // Use STDOUT when possible to prevent from opening too many file descriptors
        return \defined('STDOUT') ? \STDOUT : (@fopen('php://stdout', 'w') ?: fopen('php://output', 'w'));
    }

    /**
     * @return resource
     */
    private function openErrorStream()
    {
        if (!$this->hasStderrSupport()) {
            return fopen('php://output', 'w');
        }

        // Use STDERR when possible to prevent from opening too many file descriptors
        return \defined('STDERR') ? \STDERR : (@fopen('php://stderr', 'w') ?: fopen('php://output', 'w'));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Formatter\NullOutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * NullOutput suppresses all output.
 *
 *     $output = new NullOutput();
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Tobias Schultze <http://tobion.de>
 */
class NullOutput implements OutputInterface
{
    private NullOutputFormatter $formatter;

    /**
     * @return void
     */
    public function setFormatter(OutputFormatterInterface $formatter)
    {
        // do nothing
    }

    public function getFormatter(): OutputFormatterInterface
    {
        // to comply with the interface we must return a OutputFormatterInterface
        return $this->formatter ??= new NullOutputFormatter();
    }

    /**
     * @return void
     */
    public function setDecorated(bool $decorated)
    {
        // do nothing
    }

    public function isDecorated(): bool
    {
        return false;
    }

    /**
     * @return void
     */
    public function setVerbosity(int $level)
    {
        // do nothing
    }

    public function getVerbosity(): int
    {
        return self::VERBOSITY_QUIET;
    }

    public function isQuiet(): bool
    {
        return true;
    }

    public function isVerbose(): bool
    {
        return false;
    }

    public function isVeryVerbose(): bool
    {
        return false;
    }

    public function isDebug(): bool
    {
        return false;
    }

    /**
     * @return void
     */
    public function writeln(string|iterable $messages, int $options = self::OUTPUT_NORMAL)
    {
        // do nothing
    }

    /**
     * @return void
     */
    public function write(string|iterable $messages, bool $newline = false, int $options = self::OUTPUT_NORMAL)
    {
        // do nothing
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class BufferedOutput extends Output
{
    private string $buffer = '';

    /**
     * Empties buffer and returns its content.
     */
    public function fetch(): string
    {
        $content = $this->buffer;
        $this->buffer = '';

        return $content;
    }

    /**
     * @return void
     */
    protected function doWrite(string $message, bool $newline)
    {
        $this->buffer .= $message;

        if ($newline) {
            $this->buffer .= \PHP_EOL;
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

/**
 * ConsoleOutputInterface is the interface implemented by ConsoleOutput class.
 * This adds information about stderr and section output stream.
 *
 * @author Dariusz Górecki <darek.krk@gmail.com>
 */
interface ConsoleOutputInterface extends OutputInterface
{
    /**
     * Gets the OutputInterface for errors.
     */
    public function getErrorOutput(): OutputInterface;

    /**
     * @return void
     */
    public function setErrorOutput(OutputInterface $error);

    public function section(): ConsoleSectionOutput;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * Base class for output classes.
 *
 * There are five levels of verbosity:
 *
 *  * normal: no option passed (normal output)
 *  * verbose: -v (more output)
 *  * very verbose: -vv (highly extended output)
 *  * debug: -vvv (all debug output)
 *  * quiet: -q (no output)
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
abstract class Output implements OutputInterface
{
    private int $verbosity;
    private OutputFormatterInterface $formatter;

    /**
     * @param int|null                      $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
     * @param bool                          $decorated Whether to decorate messages
     * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
     */
    public function __construct(?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, ?OutputFormatterInterface $formatter = null)
    {
        $this->verbosity = $verbosity ?? self::VERBOSITY_NORMAL;
        $this->formatter = $formatter ?? new OutputFormatter();
        $this->formatter->setDecorated($decorated);
    }

    /**
     * @return void
     */
    public function setFormatter(OutputFormatterInterface $formatter)
    {
        $this->formatter = $formatter;
    }

    public function getFormatter(): OutputFormatterInterface
    {
        return $this->formatter;
    }

    /**
     * @return void
     */
    public function setDecorated(bool $decorated)
    {
        $this->formatter->setDecorated($decorated);
    }

    public function isDecorated(): bool
    {
        return $this->formatter->isDecorated();
    }

    /**
     * @return void
     */
    public function setVerbosity(int $level)
    {
        $this->verbosity = $level;
    }

    public function getVerbosity(): int
    {
        return $this->verbosity;
    }

    public function isQuiet(): bool
    {
        return self::VERBOSITY_QUIET === $this->verbosity;
    }

    public function isVerbose(): bool
    {
        return self::VERBOSITY_VERBOSE <= $this->verbosity;
    }

    public function isVeryVerbose(): bool
    {
        return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity;
    }

    public function isDebug(): bool
    {
        return self::VERBOSITY_DEBUG <= $this->verbosity;
    }

    /**
     * @return void
     */
    public function writeln(string|iterable $messages, int $options = self::OUTPUT_NORMAL)
    {
        $this->write($messages, true, $options);
    }

    /**
     * @return void
     */
    public function write(string|iterable $messages, bool $newline = false, int $options = self::OUTPUT_NORMAL)
    {
        if (!is_iterable($messages)) {
            $messages = [$messages];
        }

        $types = self::OUTPUT_NORMAL | self::OUTPUT_RAW | self::OUTPUT_PLAIN;
        $type = $types & $options ?: self::OUTPUT_NORMAL;

        $verbosities = self::VERBOSITY_QUIET | self::VERBOSITY_NORMAL | self::VERBOSITY_VERBOSE | self::VERBOSITY_VERY_VERBOSE | self::VERBOSITY_DEBUG;
        $verbosity = $verbosities & $options ?: self::VERBOSITY_NORMAL;

        if ($verbosity > $this->getVerbosity()) {
            return;
        }

        foreach ($messages as $message) {
            switch ($type) {
                case OutputInterface::OUTPUT_NORMAL:
                    $message = $this->formatter->format($message);
                    break;
                case OutputInterface::OUTPUT_RAW:
                    break;
                case OutputInterface::OUTPUT_PLAIN:
                    $message = strip_tags($this->formatter->format($message));
                    break;
            }

            $this->doWrite($message ?? '', $newline);
        }
    }

    /**
     * Writes a message to the output.
     *
     * @return void
     */
    abstract protected function doWrite(string $message, bool $newline);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Terminal;

/**
 * @author Pierre du Plessis <pdples@gmail.com>
 * @author Gabriel Ostrolucký <gabriel.ostrolucky@gmail.com>
 */
class ConsoleSectionOutput extends StreamOutput
{
    private array $content = [];
    private int $lines = 0;
    private array $sections;
    private Terminal $terminal;
    private int $maxHeight = 0;

    /**
     * @param resource               $stream
     * @param ConsoleSectionOutput[] $sections
     */
    public function __construct($stream, array &$sections, int $verbosity, bool $decorated, OutputFormatterInterface $formatter)
    {
        parent::__construct($stream, $verbosity, $decorated, $formatter);
        array_unshift($sections, $this);
        $this->sections = &$sections;
        $this->terminal = new Terminal();
    }

    /**
     * Defines a maximum number of lines for this section.
     *
     * When more lines are added, the section will automatically scroll to the
     * end (i.e. remove the first lines to comply with the max height).
     */
    public function setMaxHeight(int $maxHeight): void
    {
        // when changing max height, clear output of current section and redraw again with the new height
        $previousMaxHeight = $this->maxHeight;
        $this->maxHeight = $maxHeight;
        $existingContent = $this->popStreamContentUntilCurrentSection($previousMaxHeight ? min($previousMaxHeight, $this->lines) : $this->lines);

        parent::doWrite($this->getVisibleContent(), false);
        parent::doWrite($existingContent, false);
    }

    /**
     * Clears previous output for this section.
     *
     * @param int $lines Number of lines to clear. If null, then the entire output of this section is cleared
     *
     * @return void
     */
    public function clear(?int $lines = null)
    {
        if (empty($this->content) || !$this->isDecorated()) {
            return;
        }

        if ($lines) {
            array_splice($this->content, -$lines);
        } else {
            $lines = $this->lines;
            $this->content = [];
        }

        $this->lines -= $lines;

        parent::doWrite($this->popStreamContentUntilCurrentSection($this->maxHeight ? min($this->maxHeight, $lines) : $lines), false);
    }

    /**
     * Overwrites the previous output with a new message.
     *
     * @return void
     */
    public function overwrite(string|iterable $message)
    {
        $this->clear();
        $this->writeln($message);
    }

    public function getContent(): string
    {
        return implode('', $this->content);
    }

    public function getVisibleContent(): string
    {
        if (0 === $this->maxHeight) {
            return $this->getContent();
        }

        return implode('', \array_slice($this->content, -$this->maxHeight));
    }

    /**
     * @internal
     */
    public function addContent(string $input, bool $newline = true): int
    {
        $width = $this->terminal->getWidth();
        $lines = explode(\PHP_EOL, $input);
        $linesAdded = 0;
        $count = \count($lines) - 1;
        foreach ($lines as $i => $lineContent) {
            // re-add the line break (that has been removed in the above `explode()` for
            // - every line that is not the last line
            // - if $newline is required, also add it to the last line
            if ($i < $count || $newline) {
                $lineContent .= \PHP_EOL;
            }

            // skip line if there is no text (or newline for that matter)
            if ('' === $lineContent) {
                continue;
            }

            // For the first line, check if the previous line (last entry of `$this->content`)
            // needs to be continued (i.e. does not end with a line break).
            if (0 === $i
                && (false !== $lastLine = end($this->content))
                && !str_ends_with($lastLine, \PHP_EOL)
            ) {
                // deduct the line count of the previous line
                $this->lines -= (int) ceil($this->getDisplayLength($lastLine) / $width) ?: 1;
                // concatenate previous and new line
                $lineContent = $lastLine.$lineContent;
                // replace last entry of `$this->content` with the new expanded line
                array_splice($this->content, -1, 1, $lineContent);
            } else {
                // otherwise just add the new content
                $this->content[] = $lineContent;
            }

            $linesAdded += (int) ceil($this->getDisplayLength($lineContent) / $width) ?: 1;
        }

        $this->lines += $linesAdded;

        return $linesAdded;
    }

    /**
     * @internal
     */
    public function addNewLineOfInputSubmit(): void
    {
        $this->content[] = \PHP_EOL;
        ++$this->lines;
    }

    /**
     * @return void
     */
    protected function doWrite(string $message, bool $newline)
    {
        // Simulate newline behavior for consistent output formatting, avoiding extra logic
        if (!$newline && str_ends_with($message, \PHP_EOL)) {
            $message = substr($message, 0, -\strlen(\PHP_EOL));
            $newline = true;
        }

        if (!$this->isDecorated()) {
            parent::doWrite($message, $newline);

            return;
        }

        // Check if the previous line (last entry of `$this->content`) needs to be continued
        // (i.e. does not end with a line break). In which case, it needs to be erased first.
        $linesToClear = $deleteLastLine = ($lastLine = end($this->content) ?: '') && !str_ends_with($lastLine, \PHP_EOL) ? 1 : 0;

        $linesAdded = $this->addContent($message, $newline);

        if ($lineOverflow = $this->maxHeight > 0 && $this->lines > $this->maxHeight) {
            // on overflow, clear the whole section and redraw again (to remove the first lines)
            $linesToClear = $this->maxHeight;
        }

        $erasedContent = $this->popStreamContentUntilCurrentSection($linesToClear);

        if ($lineOverflow) {
            // redraw existing lines of the section
            $previousLinesOfSection = \array_slice($this->content, $this->lines - $this->maxHeight, $this->maxHeight - $linesAdded);
            parent::doWrite(implode('', $previousLinesOfSection), false);
        }

        // if the last line was removed, re-print its content together with the new content.
        // otherwise, just print the new content.
        parent::doWrite($deleteLastLine ? $lastLine.$message : $message, true);
        parent::doWrite($erasedContent, false);
    }

    /**
     * At initial stage, cursor is at the end of stream output. This method makes cursor crawl upwards until it hits
     * current section. Then it erases content it crawled through. Optionally, it erases part of current section too.
     */
    private function popStreamContentUntilCurrentSection(int $numberOfLinesToClearFromCurrentSection = 0): string
    {
        $numberOfLinesToClear = $numberOfLinesToClearFromCurrentSection;
        $erasedContent = [];

        foreach ($this->sections as $section) {
            if ($section === $this) {
                break;
            }

            $numberOfLinesToClear += $section->maxHeight ? min($section->lines, $section->maxHeight) : $section->lines;
            if ('' !== $sectionContent = $section->getVisibleContent()) {
                if (!str_ends_with($sectionContent, \PHP_EOL)) {
                    $sectionContent .= \PHP_EOL;
                }
                $erasedContent[] = $sectionContent;
            }
        }

        if ($numberOfLinesToClear > 0) {
            // move cursor up n lines
            parent::doWrite(\sprintf("\x1b[%dA", $numberOfLinesToClear), false);
            // erase to end of screen
            parent::doWrite("\x1b[0J", false);
        }

        return implode('', array_reverse($erasedContent));
    }

    private function getDisplayLength(string $text): int
    {
        return Helper::width(Helper::removeDecoration($this->getFormatter(), str_replace("\t", '        ', $text)));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Question;

/**
 * Represents a yes/no question.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ConfirmationQuestion extends Question
{
    private string $trueAnswerRegex;

    /**
     * @param string $question        The question to ask to the user
     * @param bool   $default         The default answer to return, true or false
     * @param string $trueAnswerRegex A regex to match the "yes" answer
     */
    public function __construct(string $question, bool $default = true, string $trueAnswerRegex = '/^y/i')
    {
        parent::__construct($question, $default);

        $this->trueAnswerRegex = $trueAnswerRegex;
        $this->setNormalizer($this->getDefaultNormalizer());
    }

    /**
     * Returns the default answer normalizer.
     */
    private function getDefaultNormalizer(): callable
    {
        $default = $this->getDefault();
        $regex = $this->trueAnswerRegex;

        return function ($answer) use ($default, $regex) {
            if (\is_bool($answer)) {
                return $answer;
            }

            $answerIsTrue = (bool) preg_match($regex, $answer);
            if (false === $default) {
                return $answer && $answerIsTrue;
            }

            return '' === $answer || $answerIsTrue;
        };
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Question;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * Represents a choice question.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ChoiceQuestion extends Question
{
    private array $choices;
    private bool $multiselect = false;
    private string $prompt = ' > ';
    private string $errorMessage = 'Value "%s" is invalid';

    /**
     * @param string                     $question The question to ask to the user
     * @param array                      $choices  The list of available choices
     * @param string|bool|int|float|null $default  The default answer to return
     */
    public function __construct(string $question, array $choices, string|bool|int|float|null $default = null)
    {
        if (!$choices) {
            throw new \LogicException('Choice question must have at least 1 choice available.');
        }

        parent::__construct($question, $default);

        $this->choices = $choices;
        $this->setValidator($this->getDefaultValidator());
        $this->setAutocompleterValues($choices);
    }

    /**
     * Returns available choices.
     */
    public function getChoices(): array
    {
        return $this->choices;
    }

    /**
     * Sets multiselect option.
     *
     * When multiselect is set to true, multiple choices can be answered.
     *
     * @return $this
     */
    public function setMultiselect(bool $multiselect): static
    {
        $this->multiselect = $multiselect;
        $this->setValidator($this->getDefaultValidator());

        return $this;
    }

    /**
     * Returns whether the choices are multiselect.
     */
    public function isMultiselect(): bool
    {
        return $this->multiselect;
    }

    /**
     * Gets the prompt for choices.
     */
    public function getPrompt(): string
    {
        return $this->prompt;
    }

    /**
     * Sets the prompt for choices.
     *
     * @return $this
     */
    public function setPrompt(string $prompt): static
    {
        $this->prompt = $prompt;

        return $this;
    }

    /**
     * Sets the error message for invalid values.
     *
     * The error message has a string placeholder (%s) for the invalid value.
     *
     * @return $this
     */
    public function setErrorMessage(string $errorMessage): static
    {
        $this->errorMessage = $errorMessage;
        $this->setValidator($this->getDefaultValidator());

        return $this;
    }

    private function getDefaultValidator(): callable
    {
        $choices = $this->choices;
        $errorMessage = $this->errorMessage;
        $multiselect = $this->multiselect;
        $isAssoc = $this->isAssoc($choices);

        return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) {
            if ($multiselect) {
                // Check for a separated comma values
                if (!preg_match('/^[^,]+(?:,[^,]+)*$/', (string) $selected, $matches)) {
                    throw new InvalidArgumentException(\sprintf($errorMessage, $selected));
                }

                $selectedChoices = explode(',', (string) $selected);
            } else {
                $selectedChoices = [$selected];
            }

            if ($this->isTrimmable()) {
                foreach ($selectedChoices as $k => $v) {
                    $selectedChoices[$k] = trim((string) $v);
                }
            }

            $multiselectChoices = [];
            foreach ($selectedChoices as $value) {
                $results = [];
                foreach ($choices as $key => $choice) {
                    if ($choice === $value) {
                        $results[] = $key;
                    }
                }

                if (\count($results) > 1) {
                    throw new InvalidArgumentException(\sprintf('The provided answer is ambiguous. Value should be one of "%s".', implode('" or "', $results)));
                }

                $result = array_search($value, $choices);

                if (!$isAssoc) {
                    if (false !== $result) {
                        $result = $choices[$result];
                    } elseif (isset($choices[$value])) {
                        $result = $choices[$value];
                    }
                } elseif (false === $result && isset($choices[$value])) {
                    $result = $value;
                }

                if (false === $result) {
                    throw new InvalidArgumentException(\sprintf($errorMessage, $value));
                }

                // For associative choices, consistently return the key as string:
                $multiselectChoices[] = $isAssoc ? (string) $result : $result;
            }

            if ($multiselect) {
                return $multiselectChoices;
            }

            return current($multiselectChoices);
        };
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Question;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;

/**
 * Represents a Question.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Question
{
    private string $question;
    private ?int $attempts = null;
    private bool $hidden = false;
    private bool $hiddenFallback = true;
    private ?\Closure $autocompleterCallback = null;
    private ?\Closure $validator = null;
    private string|int|bool|float|null $default;
    private ?\Closure $normalizer = null;
    private bool $trimmable = true;
    private bool $multiline = false;

    /**
     * @param string                     $question The question to ask to the user
     * @param string|bool|int|float|null $default  The default answer to return if the user enters nothing
     */
    public function __construct(string $question, string|bool|int|float|null $default = null)
    {
        $this->question = $question;
        $this->default = $default;
    }

    /**
     * Returns the question.
     */
    public function getQuestion(): string
    {
        return $this->question;
    }

    /**
     * Returns the default answer.
     */
    public function getDefault(): string|bool|int|float|null
    {
        return $this->default;
    }

    /**
     * Returns whether the user response accepts newline characters.
     */
    public function isMultiline(): bool
    {
        return $this->multiline;
    }

    /**
     * Sets whether the user response should accept newline characters.
     *
     * @return $this
     */
    public function setMultiline(bool $multiline): static
    {
        $this->multiline = $multiline;

        return $this;
    }

    /**
     * Returns whether the user response must be hidden.
     */
    public function isHidden(): bool
    {
        return $this->hidden;
    }

    /**
     * Sets whether the user response must be hidden or not.
     *
     * @return $this
     *
     * @throws LogicException In case the autocompleter is also used
     */
    public function setHidden(bool $hidden): static
    {
        if ($this->autocompleterCallback) {
            throw new LogicException('A hidden question cannot use the autocompleter.');
        }

        $this->hidden = $hidden;

        return $this;
    }

    /**
     * In case the response cannot be hidden, whether to fallback on non-hidden question or not.
     */
    public function isHiddenFallback(): bool
    {
        return $this->hiddenFallback;
    }

    /**
     * Sets whether to fallback on non-hidden question if the response cannot be hidden.
     *
     * @return $this
     */
    public function setHiddenFallback(bool $fallback): static
    {
        $this->hiddenFallback = $fallback;

        return $this;
    }

    /**
     * Gets values for the autocompleter.
     */
    public function getAutocompleterValues(): ?iterable
    {
        $callback = $this->getAutocompleterCallback();

        return $callback ? $callback('') : null;
    }

    /**
     * Sets values for the autocompleter.
     *
     * @return $this
     *
     * @throws LogicException
     */
    public function setAutocompleterValues(?iterable $values): static
    {
        if (\is_array($values)) {
            $values = $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values);

            $callback = static fn () => $values;
        } elseif ($values instanceof \Traversable) {
            $callback = static function () use ($values) {
                static $valueCache;

                return $valueCache ??= iterator_to_array($values, false);
            };
        } else {
            $callback = null;
        }

        return $this->setAutocompleterCallback($callback);
    }

    /**
     * Gets the callback function used for the autocompleter.
     */
    public function getAutocompleterCallback(): ?callable
    {
        return $this->autocompleterCallback;
    }

    /**
     * Sets the callback function used for the autocompleter.
     *
     * The callback is passed the user input as argument and should return an iterable of corresponding suggestions.
     *
     * @return $this
     */
    public function setAutocompleterCallback(?callable $callback = null): static
    {
        if (1 > \func_num_args()) {
            trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
        }
        if ($this->hidden && null !== $callback) {
            throw new LogicException('A hidden question cannot use the autocompleter.');
        }

        $this->autocompleterCallback = null === $callback ? null : $callback(...);

        return $this;
    }

    /**
     * Sets a validator for the question.
     *
     * @return $this
     */
    public function setValidator(?callable $validator = null): static
    {
        if (1 > \func_num_args()) {
            trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
        }
        $this->validator = null === $validator ? null : $validator(...);

        return $this;
    }

    /**
     * Gets the validator for the question.
     */
    public function getValidator(): ?callable
    {
        return $this->validator;
    }

    /**
     * Sets the maximum number of attempts.
     *
     * Null means an unlimited number of attempts.
     *
     * @return $this
     *
     * @throws InvalidArgumentException in case the number of attempts is invalid
     */
    public function setMaxAttempts(?int $attempts): static
    {
        if (null !== $attempts && $attempts < 1) {
            throw new InvalidArgumentException('Maximum number of attempts must be a positive value.');
        }

        $this->attempts = $attempts;

        return $this;
    }

    /**
     * Gets the maximum number of attempts.
     *
     * Null means an unlimited number of attempts.
     */
    public function getMaxAttempts(): ?int
    {
        return $this->attempts;
    }

    /**
     * Sets a normalizer for the response.
     *
     * The normalizer can be a callable (a string), a closure or a class implementing __invoke.
     *
     * @return $this
     */
    public function setNormalizer(callable $normalizer): static
    {
        $this->normalizer = $normalizer(...);

        return $this;
    }

    /**
     * Gets the normalizer for the response.
     *
     * The normalizer can ba a callable (a string), a closure or a class implementing __invoke.
     */
    public function getNormalizer(): ?callable
    {
        return $this->normalizer;
    }

    /**
     * @return bool
     */
    protected function isAssoc(array $array)
    {
        return (bool) \count(array_filter(array_keys($array), 'is_string'));
    }

    public function isTrimmable(): bool
    {
        return $this->trimmable;
    }

    /**
     * @return $this
     */
    public function setTrimmable(bool $trimmable): static
    {
        $this->trimmable = $trimmable;

        return $this;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console;

use Symfony\Component\Console\Output\AnsiColorMode;

class Terminal
{
    public const DEFAULT_COLOR_MODE = AnsiColorMode::Ansi4;

    private static ?AnsiColorMode $colorMode = null;
    private static ?int $width = null;
    private static ?int $height = null;
    private static ?bool $stty = null;

    /**
     * About Ansi color types: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
     * For more information about true color support with terminals https://github.com/termstandard/colors/.
     */
    public static function getColorMode(): AnsiColorMode
    {
        // Use Cache from previous run (or user forced mode)
        if (null !== self::$colorMode) {
            return self::$colorMode;
        }

        // Try with $COLORTERM first
        if (\is_string($colorterm = getenv('COLORTERM'))) {
            $colorterm = strtolower($colorterm);

            if (str_contains($colorterm, 'truecolor')) {
                self::setColorMode(AnsiColorMode::Ansi24);

                return self::$colorMode;
            }

            if (str_contains($colorterm, '256color')) {
                self::setColorMode(AnsiColorMode::Ansi8);

                return self::$colorMode;
            }
        }

        // Try with $TERM
        if (\is_string($term = getenv('TERM'))) {
            $term = strtolower($term);

            if (str_contains($term, 'truecolor')) {
                self::setColorMode(AnsiColorMode::Ansi24);

                return self::$colorMode;
            }

            if (str_contains($term, '256color')) {
                self::setColorMode(AnsiColorMode::Ansi8);

                return self::$colorMode;
            }
        }

        self::setColorMode(self::DEFAULT_COLOR_MODE);

        return self::$colorMode;
    }

    /**
     * Force a terminal color mode rendering.
     */
    public static function setColorMode(?AnsiColorMode $colorMode): void
    {
        self::$colorMode = $colorMode;
    }

    /**
     * Gets the terminal width.
     */
    public function getWidth(): int
    {
        $width = getenv('COLUMNS');
        if (false !== $width) {
            return (int) trim($width);
        }

        if (null === self::$width) {
            self::initDimensions();
        }

        return self::$width ?: 80;
    }

    /**
     * Gets the terminal height.
     */
    public function getHeight(): int
    {
        $height = getenv('LINES');
        if (false !== $height) {
            return (int) trim($height);
        }

        if (null === self::$height) {
            self::initDimensions();
        }

        return self::$height ?: 50;
    }

    /**
     * @internal
     */
    public static function hasSttyAvailable(): bool
    {
        if (null !== self::$stty) {
            return self::$stty;
        }

        // skip check if shell_exec function is disabled
        if (!\function_exists('shell_exec')) {
            return false;
        }

        return self::$stty = (bool) shell_exec('stty 2> '.('\\' === \DIRECTORY_SEPARATOR ? 'NUL' : '/dev/null'));
    }

    private static function initDimensions(): void
    {
        if ('\\' === \DIRECTORY_SEPARATOR) {
            $ansicon = getenv('ANSICON');
            if (false !== $ansicon && preg_match('/^(\d+)x(\d+)(?: \((\d+)x(\d+)\))?$/', trim($ansicon), $matches)) {
                // extract [w, H] from "wxh (WxH)"
                // or [w, h] from "wxh"
                self::$width = (int) $matches[1];
                self::$height = isset($matches[4]) ? (int) $matches[4] : (int) $matches[2];
            } elseif (!self::hasVt100Support() && self::hasSttyAvailable()) {
                // only use stty on Windows if the terminal does not support vt100 (e.g. Windows 7 + git-bash)
                // testing for stty in a Windows 10 vt100-enabled console will implicitly disable vt100 support on STDOUT
                self::initDimensionsUsingStty();
            } elseif (null !== $dimensions = self::getConsoleMode()) {
                // extract [w, h] from "wxh"
                self::$width = (int) $dimensions[0];
                self::$height = (int) $dimensions[1];
            }
        } else {
            self::initDimensionsUsingStty();
        }
    }

    /**
     * Returns whether STDOUT has vt100 support (some Windows 10+ configurations).
     */
    private static function hasVt100Support(): bool
    {
        return \function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support(fopen('php://stdout', 'w'));
    }

    /**
     * Initializes dimensions using the output of an stty columns line.
     */
    private static function initDimensionsUsingStty(): void
    {
        if ($sttyString = self::getSttyColumns()) {
            if (preg_match('/rows.(\d+);.columns.(\d+);/is', $sttyString, $matches)) {
                // extract [w, h] from "rows h; columns w;"
                self::$width = (int) $matches[2];
                self::$height = (int) $matches[1];
            } elseif (preg_match('/;.(\d+).rows;.(\d+).columns/is', $sttyString, $matches)) {
                // extract [w, h] from "; h rows; w columns"
                self::$width = (int) $matches[2];
                self::$height = (int) $matches[1];
            }
        }
    }

    /**
     * Runs and parses mode CON if it's available, suppressing any error output.
     *
     * @return int[]|null An array composed of the width and the height or null if it could not be parsed
     */
    private static function getConsoleMode(): ?array
    {
        $info = self::readFromProcess('mode CON');

        if (null === $info || !preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
            return null;
        }

        return [(int) $matches[2], (int) $matches[1]];
    }

    /**
     * Runs and parses stty -a if it's available, suppressing any error output.
     */
    private static function getSttyColumns(): ?string
    {
        return self::readFromProcess(['stty', '-a']);
    }

    private static function readFromProcess(string|array $command): ?string
    {
        if (!\function_exists('proc_open')) {
            return null;
        }

        $descriptorspec = [
            1 => ['pipe', 'w'],
            2 => ['pipe', 'w'],
        ];

        $cp = \function_exists('sapi_windows_cp_set') ? sapi_windows_cp_get() : 0;

        if (!$process = @proc_open($command, $descriptorspec, $pipes, null, null, ['suppress_errors' => true])) {
            return null;
        }

        $info = stream_get_contents($pipes[1]);
        fclose($pipes[1]);
        fclose($pipes[2]);
        proc_close($process);

        if ($cp) {
            sapi_windows_cp_set($cp);
        }

        return $info;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

use Symfony\Component\Console\Messenger\RunCommandContext;

/**
 * @author Kevin Bond <kevinbond@gmail.com>
 */
final class RunCommandFailedException extends RuntimeException
{
    public function __construct(\Throwable|string $exception, public readonly RunCommandContext $context)
    {
        parent::__construct(
            $exception instanceof \Throwable ? $exception->getMessage() : $exception,
            $exception instanceof \Throwable ? $exception->getCode() : 0,
            $exception instanceof \Throwable ? $exception : null,
        );
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * Represents an incorrect option name or value typed in the console.
 *
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class InvalidOptionException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class LogicException extends \LogicException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * Represents an incorrect namespace typed in the console.
 *
 * @author Pierre du Plessis <pdples@gmail.com>
 */
class NamespaceNotFoundException extends CommandNotFoundException
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * ExceptionInterface.
 *
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
interface ExceptionInterface extends \Throwable
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * Represents an incorrect command name typed in the console.
 *
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class CommandNotFoundException extends \InvalidArgumentException implements ExceptionInterface
{
    private array $alternatives;

    /**
     * @param string          $message      Exception message to throw
     * @param string[]        $alternatives List of similar defined names
     * @param int             $code         Exception code
     * @param \Throwable|null $previous     Previous exception used for the exception chaining
     */
    public function __construct(string $message, array $alternatives = [], int $code = 0, ?\Throwable $previous = null)
    {
        parent::__construct($message, $code, $previous);

        $this->alternatives = $alternatives;
    }

    /**
     * @return string[]
     */
    public function getAlternatives(): array
    {
        return $this->alternatives;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * Represents failure to read input from stdin.
 *
 * @author Gabriel Ostrolucký <gabriel.ostrolucky@gmail.com>
 */
class MissingInputException extends RuntimeException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Style;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Decorates output to add console style guide helpers.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
abstract class OutputStyle implements OutputInterface, StyleInterface
{
    private OutputInterface $output;

    public function __construct(OutputInterface $output)
    {
        $this->output = $output;
    }

    /**
     * @return void
     */
    public function newLine(int $count = 1)
    {
        $this->output->write(str_repeat(\PHP_EOL, $count));
    }

    public function createProgressBar(int $max = 0): ProgressBar
    {
        return new ProgressBar($this->output, $max);
    }

    /**
     * @return void
     */
    public function write(string|iterable $messages, bool $newline = false, int $type = self::OUTPUT_NORMAL)
    {
        $this->output->write($messages, $newline, $type);
    }

    /**
     * @return void
     */
    public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORMAL)
    {
        $this->output->writeln($messages, $type);
    }

    /**
     * @return void
     */
    public function setVerbosity(int $level)
    {
        $this->output->setVerbosity($level);
    }

    public function getVerbosity(): int
    {
        return $this->output->getVerbosity();
    }

    /**
     * @return void
     */
    public function setDecorated(bool $decorated)
    {
        $this->output->setDecorated($decorated);
    }

    public function isDecorated(): bool
    {
        return $this->output->isDecorated();
    }

    /**
     * @return void
     */
    public function setFormatter(OutputFormatterInterface $formatter)
    {
        $this->output->setFormatter($formatter);
    }

    public function getFormatter(): OutputFormatterInterface
    {
        return $this->output->getFormatter();
    }

    public function isQuiet(): bool
    {
        return $this->output->isQuiet();
    }

    public function isVerbose(): bool
    {
        return $this->output->isVerbose();
    }

    public function isVeryVerbose(): bool
    {
        return $this->output->isVeryVerbose();
    }

    public function isDebug(): bool
    {
        return $this->output->isDebug();
    }

    /**
     * @return OutputInterface
     */
    protected function getErrorOutput()
    {
        if (!$this->output instanceof ConsoleOutputInterface) {
            return $this->output;
        }

        return $this->output->getErrorOutput();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Style;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Helper\OutputWrapper;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Helper\SymfonyQuestionHelper;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableCell;
use Symfony\Component\Console\Helper\TableSeparator;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\TrimmedBufferOutput;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Terminal;

/**
 * Output decorator helpers for the Symfony Style Guide.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
class SymfonyStyle extends OutputStyle
{
    public const MAX_LINE_LENGTH = 120;

    private InputInterface $input;
    private OutputInterface $output;
    private SymfonyQuestionHelper $questionHelper;
    private ProgressBar $progressBar;
    private int $lineLength;
    private TrimmedBufferOutput $bufferedOutput;

    public function __construct(InputInterface $input, OutputInterface $output)
    {
        $this->input = $input;
        $this->bufferedOutput = new TrimmedBufferOutput(\DIRECTORY_SEPARATOR === '\\' ? 4 : 2, $output->getVerbosity(), false, clone $output->getFormatter());
        // Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not.
        $width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH;
        $this->lineLength = min($width - (int) (\DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH);

        parent::__construct($this->output = $output);
    }

    /**
     * Formats a message as a block of text.
     *
     * @return void
     */
    public function block(string|array $messages, ?string $type = null, ?string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = true)
    {
        $messages = \is_array($messages) ? array_values($messages) : [$messages];

        $this->autoPrependBlock();
        $this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding, $escape));
        $this->newLine();
    }

    /**
     * @return void
     */
    public function title(string $message)
    {
        $this->autoPrependBlock();
        $this->writeln([
            \sprintf('<comment>%s</>', OutputFormatter::escapeTrailingBackslash($message)),
            \sprintf('<comment>%s</>', str_repeat('=', Helper::width(Helper::removeDecoration($this->getFormatter(), $message)))),
        ]);
        $this->newLine();
    }

    /**
     * @return void
     */
    public function section(string $message)
    {
        $this->autoPrependBlock();
        $this->writeln([
            \sprintf('<comment>%s</>', OutputFormatter::escapeTrailingBackslash($message)),
            \sprintf('<comment>%s</>', str_repeat('-', Helper::width(Helper::removeDecoration($this->getFormatter(), $message)))),
        ]);
        $this->newLine();
    }

    /**
     * @return void
     */
    public function listing(array $elements)
    {
        $this->autoPrependText();
        $elements = array_map(fn ($element) => \sprintf(' * %s', $element), $elements);

        $this->writeln($elements);
        $this->newLine();
    }

    /**
     * @return void
     */
    public function text(string|array $message)
    {
        $this->autoPrependText();

        $messages = \is_array($message) ? array_values($message) : [$message];
        foreach ($messages as $message) {
            $this->writeln(\sprintf(' %s', $message));
        }
    }

    /**
     * Formats a command comment.
     *
     * @return void
     */
    public function comment(string|array $message)
    {
        $this->block($message, null, null, '<fg=default;bg=default> // </>', false, false);
    }

    /**
     * @return void
     */
    public function success(string|array $message)
    {
        $this->block($message, 'OK', 'fg=black;bg=green', ' ', true);
    }

    /**
     * @return void
     */
    public function error(string|array $message)
    {
        $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true);
    }

    /**
     * @return void
     */
    public function warning(string|array $message)
    {
        $this->block($message, 'WARNING', 'fg=black;bg=yellow', ' ', true);
    }

    /**
     * @return void
     */
    public function note(string|array $message)
    {
        $this->block($message, 'NOTE', 'fg=yellow', ' ! ');
    }

    /**
     * Formats an info message.
     *
     * @return void
     */
    public function info(string|array $message)
    {
        $this->block($message, 'INFO', 'fg=green', ' ', true);
    }

    /**
     * @return void
     */
    public function caution(string|array $message)
    {
        $this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true);
    }

    /**
     * @return void
     */
    public function table(array $headers, array $rows)
    {
        $this->createTable()
            ->setHeaders($headers)
            ->setRows($rows)
            ->render()
        ;

        $this->newLine();
    }

    /**
     * Formats a horizontal table.
     *
     * @return void
     */
    public function horizontalTable(array $headers, array $rows)
    {
        $this->createTable()
            ->setHorizontal(true)
            ->setHeaders($headers)
            ->setRows($rows)
            ->render()
        ;

        $this->newLine();
    }

    /**
     * Formats a list of key/value horizontally.
     *
     * Each row can be one of:
     * * 'A title'
     * * ['key' => 'value']
     * * new TableSeparator()
     *
     * @return void
     */
    public function definitionList(string|array|TableSeparator ...$list)
    {
        $headers = [];
        $row = [];
        foreach ($list as $value) {
            if ($value instanceof TableSeparator) {
                $headers[] = $value;
                $row[] = $value;
                continue;
            }
            if (\is_string($value)) {
                $headers[] = new TableCell($value, ['colspan' => 2]);
                $row[] = null;
                continue;
            }
            if (!\is_array($value)) {
                throw new InvalidArgumentException('Value should be an array, string, or an instance of TableSeparator.');
            }
            $headers[] = key($value);
            $row[] = current($value);
        }

        $this->horizontalTable($headers, [$row]);
    }

    public function ask(string $question, ?string $default = null, ?callable $validator = null): mixed
    {
        $question = new Question($question, $default);
        $question->setValidator($validator);

        return $this->askQuestion($question);
    }

    public function askHidden(string $question, ?callable $validator = null): mixed
    {
        $question = new Question($question);

        $question->setHidden(true);
        $question->setValidator($validator);

        return $this->askQuestion($question);
    }

    public function confirm(string $question, bool $default = true): bool
    {
        return $this->askQuestion(new ConfirmationQuestion($question, $default));
    }

    public function choice(string $question, array $choices, mixed $default = null, bool $multiSelect = false): mixed
    {
        if (null !== $default) {
            $values = array_flip($choices);
            $default = $values[$default] ?? $default;
        }

        $questionChoice = new ChoiceQuestion($question, $choices, $default);
        $questionChoice->setMultiselect($multiSelect);

        return $this->askQuestion($questionChoice);
    }

    /**
     * @return void
     */
    public function progressStart(int $max = 0)
    {
        $this->progressBar = $this->createProgressBar($max);
        $this->progressBar->start();
    }

    /**
     * @return void
     */
    public function progressAdvance(int $step = 1)
    {
        $this->getProgressBar()->advance($step);
    }

    /**
     * @return void
     */
    public function progressFinish()
    {
        $this->getProgressBar()->finish();
        $this->newLine(2);
        unset($this->progressBar);
    }

    public function createProgressBar(int $max = 0): ProgressBar
    {
        $progressBar = parent::createProgressBar($max);

        if ('\\' !== \DIRECTORY_SEPARATOR || 'Hyper' === getenv('TERM_PROGRAM')) {
            $progressBar->setEmptyBarCharacter('░'); // light shade character \u2591
            $progressBar->setProgressCharacter('');
            $progressBar->setBarCharacter('▓'); // dark shade character \u2593
        }

        return $progressBar;
    }

    /**
     * @see ProgressBar::iterate()
     *
     * @template TKey
     * @template TValue
     *
     * @param iterable<TKey, TValue> $iterable
     * @param int|null               $max      Number of steps to complete the bar (0 if indeterminate), if null it will be inferred from $iterable
     *
     * @return iterable<TKey, TValue>
     */
    public function progressIterate(iterable $iterable, ?int $max = null): iterable
    {
        yield from $this->createProgressBar()->iterate($iterable, $max);

        $this->newLine(2);
    }

    public function askQuestion(Question $question): mixed
    {
        if ($this->input->isInteractive()) {
            $this->autoPrependBlock();
        }

        $this->questionHelper ??= new SymfonyQuestionHelper();

        $answer = $this->questionHelper->ask($this->input, $this, $question);

        if ($this->input->isInteractive()) {
            if ($this->output instanceof ConsoleSectionOutput) {
                // add the new line of the `return` to submit the input to ConsoleSectionOutput, because ConsoleSectionOutput is holding all it's lines.
                // this is relevant when a `ConsoleSectionOutput::clear` is called.
                $this->output->addNewLineOfInputSubmit();
            }
            $this->newLine();
            $this->bufferedOutput->write("\n");
        }

        return $answer;
    }

    /**
     * @return void
     */
    public function writeln(string|iterable $messages, int $type = self::OUTPUT_NORMAL)
    {
        if (!is_iterable($messages)) {
            $messages = [$messages];
        }

        foreach ($messages as $message) {
            parent::writeln($message, $type);
            $this->writeBuffer($message, true, $type);
        }
    }

    /**
     * @return void
     */
    public function write(string|iterable $messages, bool $newline = false, int $type = self::OUTPUT_NORMAL)
    {
        if (!is_iterable($messages)) {
            $messages = [$messages];
        }

        foreach ($messages as $message) {
            parent::write($message, $newline, $type);
            $this->writeBuffer($message, $newline, $type);
        }
    }

    /**
     * @return void
     */
    public function newLine(int $count = 1)
    {
        parent::newLine($count);
        $this->bufferedOutput->write(str_repeat("\n", $count));
    }

    /**
     * Returns a new instance which makes use of stderr if available.
     */
    public function getErrorStyle(): self
    {
        return new self($this->input, $this->getErrorOutput());
    }

    public function createTable(): Table
    {
        $output = $this->output instanceof ConsoleOutputInterface ? $this->output->section() : $this->output;
        $style = clone Table::getStyleDefinition('symfony-style-guide');
        $style->setCellHeaderFormat('<info>%s</info>');

        return (new Table($output))->setStyle($style);
    }

    private function getProgressBar(): ProgressBar
    {
        return $this->progressBar
            ?? throw new RuntimeException('The ProgressBar is not started.');
    }

    private function autoPrependBlock(): void
    {
        $chars = substr(str_replace(\PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2);

        if (!isset($chars[0])) {
            $this->newLine(); // empty history, so we should start with a new line.

            return;
        }
        // Prepend new line for each non LF chars (This means no blank line was output before)
        $this->newLine(2 - substr_count($chars, "\n"));
    }

    private function autoPrependText(): void
    {
        $fetched = $this->bufferedOutput->fetch();
        // Prepend new line if last char isn't EOL:
        if ($fetched && !str_ends_with($fetched, "\n")) {
            $this->newLine();
        }
    }

    private function writeBuffer(string $message, bool $newLine, int $type): void
    {
        // We need to know if the last chars are PHP_EOL
        $this->bufferedOutput->write($message, $newLine, $type);
    }

    private function createBlock(iterable $messages, ?string $type = null, ?string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = false): array
    {
        $indentLength = 0;
        $prefixLength = Helper::width(Helper::removeDecoration($this->getFormatter(), $prefix));
        $lines = [];

        if (null !== $type) {
            $type = \sprintf('[%s] ', $type);
            $indentLength = Helper::width($type);
            $lineIndentation = str_repeat(' ', $indentLength);
        }

        // wrap and add newlines for each element
        $outputWrapper = new OutputWrapper();
        foreach ($messages as $key => $message) {
            if ($escape) {
                $message = OutputFormatter::escape($message);
            }

            $lines = array_merge(
                $lines,
                explode(\PHP_EOL, $outputWrapper->wrap(
                    $message,
                    $this->lineLength - $prefixLength - $indentLength,
                    \PHP_EOL
                ))
            );

            if (\count($messages) > 1 && $key < \count($messages) - 1) {
                $lines[] = '';
            }
        }

        $firstLineIndex = 0;
        if ($padding && $this->isDecorated()) {
            $firstLineIndex = 1;
            array_unshift($lines, '');
            $lines[] = '';
        }

        foreach ($lines as $i => &$line) {
            if (null !== $type) {
                $line = $firstLineIndex === $i ? $type.$line : $lineIndentation.$line;
            }

            $line = $prefix.$line;
            $line .= str_repeat(' ', max($this->lineLength - Helper::width(Helper::removeDecoration($this->getFormatter(), $line)), 0));

            if ($style) {
                $line = \sprintf('<%s>%s</>', $style, $line);
            }
        }

        return $lines;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Style;

/**
 * Output style helpers.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
interface StyleInterface
{
    /**
     * Formats a command title.
     *
     * @return void
     */
    public function title(string $message);

    /**
     * Formats a section title.
     *
     * @return void
     */
    public function section(string $message);

    /**
     * Formats a list.
     *
     * @return void
     */
    public function listing(array $elements);

    /**
     * Formats informational text.
     *
     * @return void
     */
    public function text(string|array $message);

    /**
     * Formats a success result bar.
     *
     * @return void
     */
    public function success(string|array $message);

    /**
     * Formats an error result bar.
     *
     * @return void
     */
    public function error(string|array $message);

    /**
     * Formats an warning result bar.
     *
     * @return void
     */
    public function warning(string|array $message);

    /**
     * Formats a note admonition.
     *
     * @return void
     */
    public function note(string|array $message);

    /**
     * Formats a caution admonition.
     *
     * @return void
     */
    public function caution(string|array $message);

    /**
     * Formats a table.
     *
     * @return void
     */
    public function table(array $headers, array $rows);

    /**
     * Asks a question.
     */
    public function ask(string $question, ?string $default = null, ?callable $validator = null): mixed;

    /**
     * Asks a question with the user input hidden.
     */
    public function askHidden(string $question, ?callable $validator = null): mixed;

    /**
     * Asks for confirmation.
     */
    public function confirm(string $question, bool $default = true): bool;

    /**
     * Asks a choice question.
     */
    public function choice(string $question, array $choices, mixed $default = null): mixed;

    /**
     * Add newline(s).
     *
     * @return void
     */
    public function newLine(int $count = 1);

    /**
     * Starts the progress output.
     *
     * @return void
     */
    public function progressStart(int $max = 0);

    /**
     * Advances the progress output X steps.
     *
     * @return void
     */
    public function progressAdvance(int $step = 1);

    /**
     * Finishes the progress output.
     *
     * @return void
     */
    public function progressFinish();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Attribute;

/**
 * Service tag to autoconfigure commands.
 */
#[\Attribute(\Attribute::TARGET_CLASS)]
class AsCommand
{
    public function __construct(
        public string $name,
        public ?string $description = null,
        array $aliases = [],
        bool $hidden = false,
    ) {
        if (!$hidden && !$aliases) {
            return;
        }

        $name = explode('|', $name);
        $name = array_merge($name, $aliases);

        if ($hidden && '' !== $name[0]) {
            array_unshift($name, '');
        }

        $this->name = implode('|', $name);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Messenger;

/**
 * @author Kevin Bond <kevinbond@gmail.com>
 */
final class RunCommandContext
{
    public function __construct(
        public readonly RunCommandMessage $message,
        public readonly int $exitCode,
        public readonly string $output,
    ) {
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Messenger;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\RunCommandFailedException;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Messenger\Exception\RecoverableExceptionInterface;
use Symfony\Component\Messenger\Exception\UnrecoverableExceptionInterface;

/**
 * @author Kevin Bond <kevinbond@gmail.com>
 */
final class RunCommandMessageHandler
{
    public function __construct(private readonly Application $application)
    {
    }

    public function __invoke(RunCommandMessage $message): RunCommandContext
    {
        $input = new StringInput($message->input);
        $output = new BufferedOutput();

        $this->application->setCatchExceptions($message->catchExceptions);

        try {
            $exitCode = $this->application->run($input, $output);
        } catch (UnrecoverableExceptionInterface|RecoverableExceptionInterface $e) {
            throw $e;
        } catch (\Throwable $e) {
            throw new RunCommandFailedException($e, new RunCommandContext($message, Command::FAILURE, $output->fetch()));
        }

        if ($message->throwOnFailure && Command::SUCCESS !== $exitCode) {
            throw new RunCommandFailedException(\sprintf('Command "%s" exited with code "%s".', $message->input, $exitCode), new RunCommandContext($message, $exitCode, $output->fetch()));
        }

        return new RunCommandContext($message, $exitCode, $output->fetch());
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Messenger;

use Symfony\Component\Console\Exception\RunCommandFailedException;

/**
 * @author Kevin Bond <kevinbond@gmail.com>
 */
class RunCommandMessage implements \Stringable
{
    /**
     * @param bool $throwOnFailure  If the command has a non-zero exit code, throw {@see RunCommandFailedException}
     * @param bool $catchExceptions @see Application::setCatchExceptions()
     */
    public function __construct(
        public readonly string $input,
        public readonly bool $throwOnFailure = true,
        public readonly bool $catchExceptions = false,
    ) {
    }

    public function __toString(): string
    {
        return $this->input;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\CI;

use Symfony\Component\Console\Output\OutputInterface;

/**
 * Utility class for Github actions.
 *
 * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
 */
class GithubActionReporter
{
    private OutputInterface $output;

    /**
     * @see https://github.com/actions/toolkit/blob/5e5e1b7aacba68a53836a34db4a288c3c1c1585b/packages/core/src/command.ts#L80-L85
     */
    private const ESCAPED_DATA = [
        '%' => '%25',
        "\r" => '%0D',
        "\n" => '%0A',
    ];

    /**
     * @see https://github.com/actions/toolkit/blob/5e5e1b7aacba68a53836a34db4a288c3c1c1585b/packages/core/src/command.ts#L87-L94
     */
    private const ESCAPED_PROPERTIES = [
        '%' => '%25',
        "\r" => '%0D',
        "\n" => '%0A',
        ':' => '%3A',
        ',' => '%2C',
    ];

    public function __construct(OutputInterface $output)
    {
        $this->output = $output;
    }

    public static function isGithubActionEnvironment(): bool
    {
        return false !== getenv('GITHUB_ACTIONS');
    }

    /**
     * Output an error using the Github annotations format.
     *
     * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-error-message
     */
    public function error(string $message, ?string $file = null, ?int $line = null, ?int $col = null): void
    {
        $this->log('error', $message, $file, $line, $col);
    }

    /**
     * Output a warning using the Github annotations format.
     *
     * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message
     */
    public function warning(string $message, ?string $file = null, ?int $line = null, ?int $col = null): void
    {
        $this->log('warning', $message, $file, $line, $col);
    }

    /**
     * Output a debug log using the Github annotations format.
     *
     * @see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-a-debug-message
     */
    public function debug(string $message, ?string $file = null, ?int $line = null, ?int $col = null): void
    {
        $this->log('debug', $message, $file, $line, $col);
    }

    private function log(string $type, string $message, ?string $file = null, ?int $line = null, ?int $col = null): void
    {
        // Some values must be encoded.
        $message = strtr($message, self::ESCAPED_DATA);

        if (!$file) {
            // No file provided, output the message solely:
            $this->output->writeln(\sprintf('::%s::%s', $type, $message));

            return;
        }

        $this->output->writeln(\sprintf('::%s file=%s,line=%s,col=%s::%s', $type, strtr($file, self::ESCAPED_PROPERTIES), strtr($line ?? 1, self::ESCAPED_PROPERTIES), strtr($col ?? 0, self::ESCAPED_PROPERTIES), $message));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\DependencyInjection;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Command\LazyCommand;
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\TypedReference;

/**
 * Registers console commands.
 *
 * @author Grégoire Pineau <lyrixx@lyrixx.info>
 */
class AddConsoleCommandPass implements CompilerPassInterface
{
    /**
     * @return void
     */
    public function process(ContainerBuilder $container)
    {
        $commandServices = $container->findTaggedServiceIds('console.command', true);
        $lazyCommandMap = [];
        $lazyCommandRefs = [];
        $serviceIds = [];

        foreach ($commandServices as $id => $tags) {
            $definition = $container->getDefinition($id);
            $definition->addTag('container.no_preload');
            $class = $container->getParameterBag()->resolveValue($definition->getClass());

            if (isset($tags[0]['command'])) {
                $aliases = $tags[0]['command'];
            } else {
                if (!$r = $container->getReflectionClass($class)) {
                    throw new InvalidArgumentException(\sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
                }
                if (!$r->isSubclassOf(Command::class)) {
                    throw new InvalidArgumentException(\sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, 'console.command', Command::class));
                }
                $aliases = str_replace('%', '%%', $class::getDefaultName() ?? '');
            }

            $aliases = explode('|', $aliases ?? '');
            $commandName = array_shift($aliases);

            if ($isHidden = '' === $commandName) {
                $commandName = array_shift($aliases);
            }

            if (null === $commandName) {
                if (!$definition->isPublic() || $definition->isPrivate() || $definition->hasTag('container.private')) {
                    $commandId = 'console.command.public_alias.'.$id;
                    $container->setAlias($commandId, $id)->setPublic(true);
                    $id = $commandId;
                }
                $serviceIds[] = $id;

                continue;
            }

            $description = $tags[0]['description'] ?? null;

            unset($tags[0]);
            $lazyCommandMap[$commandName] = $id;
            $lazyCommandRefs[$id] = new TypedReference($id, $class);

            foreach ($aliases as $alias) {
                $lazyCommandMap[$alias] = $id;
            }

            foreach ($tags as $tag) {
                if (isset($tag['command'])) {
                    $aliases[] = $tag['command'];
                    $lazyCommandMap[$tag['command']] = $id;
                }

                $description ??= $tag['description'] ?? null;
            }

            $definition->addMethodCall('setName', [$commandName]);

            if ($aliases) {
                $definition->addMethodCall('setAliases', [$aliases]);
            }

            if ($isHidden) {
                $definition->addMethodCall('setHidden', [true]);
            }

            if (!$description) {
                if (!$r = $container->getReflectionClass($class)) {
                    throw new InvalidArgumentException(\sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
                }
                if (!$r->isSubclassOf(Command::class)) {
                    throw new InvalidArgumentException(\sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, 'console.command', Command::class));
                }
                $description = str_replace('%', '%%', $class::getDefaultDescription() ?? '');
            }

            if ($description) {
                $definition->addMethodCall('setDescription', [$description]);

                $container->register('.'.$id.'.lazy', LazyCommand::class)
                    ->setArguments([$commandName, $aliases, $description, $isHidden, new ServiceClosureArgument($lazyCommandRefs[$id])]);

                $lazyCommandRefs[$id] = new Reference('.'.$id.'.lazy');
            }
        }

        $container
            ->register('console.command_loader', ContainerCommandLoader::class)
            ->setPublic(true)
            ->addTag('container.no_preload')
            ->setArguments([ServiceLocatorTagPass::register($container, $lazyCommandRefs), $lazyCommandMap]);

        $container->setParameter('console.command.ids', $serviceIds);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console;

use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Pierre du Plessis <pdples@gmail.com>
 */
final class Cursor
{
    private OutputInterface $output;
    /** @var resource */
    private $input;

    /**
     * @param resource|null $input
     */
    public function __construct(OutputInterface $output, $input = null)
    {
        $this->output = $output;
        $this->input = $input ?? (\defined('STDIN') ? \STDIN : fopen('php://input', 'r+'));
    }

    /**
     * @return $this
     */
    public function moveUp(int $lines = 1): static
    {
        $this->output->write(\sprintf("\x1b[%dA", $lines));

        return $this;
    }

    /**
     * @return $this
     */
    public function moveDown(int $lines = 1): static
    {
        $this->output->write(\sprintf("\x1b[%dB", $lines));

        return $this;
    }

    /**
     * @return $this
     */
    public function moveRight(int $columns = 1): static
    {
        $this->output->write(\sprintf("\x1b[%dC", $columns));

        return $this;
    }

    /**
     * @return $this
     */
    public function moveLeft(int $columns = 1): static
    {
        $this->output->write(\sprintf("\x1b[%dD", $columns));

        return $this;
    }

    /**
     * @return $this
     */
    public function moveToColumn(int $column): static
    {
        $this->output->write(\sprintf("\x1b[%dG", $column));

        return $this;
    }

    /**
     * @return $this
     */
    public function moveToPosition(int $column, int $row): static
    {
        $this->output->write(\sprintf("\x1b[%d;%dH", $row + 1, $column));

        return $this;
    }

    /**
     * @return $this
     */
    public function savePosition(): static
    {
        $this->output->write("\x1b7");

        return $this;
    }

    /**
     * @return $this
     */
    public function restorePosition(): static
    {
        $this->output->write("\x1b8");

        return $this;
    }

    /**
     * @return $this
     */
    public function hide(): static
    {
        $this->output->write("\x1b[?25l");

        return $this;
    }

    /**
     * @return $this
     */
    public function show(): static
    {
        $this->output->write("\x1b[?25h\x1b[?0c");

        return $this;
    }

    /**
     * Clears all the output from the current line.
     *
     * @return $this
     */
    public function clearLine(): static
    {
        $this->output->write("\x1b[2K");

        return $this;
    }

    /**
     * Clears all the output from the current line after the current position.
     */
    public function clearLineAfter(): self
    {
        $this->output->write("\x1b[K");

        return $this;
    }

    /**
     * Clears all the output from the cursors' current position to the end of the screen.
     *
     * @return $this
     */
    public function clearOutput(): static
    {
        $this->output->write("\x1b[0J");

        return $this;
    }

    /**
     * Clears the entire screen.
     *
     * @return $this
     */
    public function clearScreen(): static
    {
        $this->output->write("\x1b[2J");

        return $this;
    }

    /**
     * Returns the current cursor position as x,y coordinates.
     */
    public function getCurrentPosition(): array
    {
        static $isTtySupported;

        if (!$isTtySupported ??= '/' === \DIRECTORY_SEPARATOR && stream_isatty(\STDOUT)) {
            return [1, 1];
        }

        $sttyMode = shell_exec('stty -g');
        shell_exec('stty -icanon -echo');

        @fwrite($this->input, "\033[6n");

        $code = trim(fread($this->input, 1024));

        shell_exec(\sprintf('stty %s', $sttyMode));

        sscanf($code, "\033[%d;%dR", $row, $col);

        return [$col, $row];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\CommandLoader;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\CommandNotFoundException;

/**
 * @author Robin Chalas <robin.chalas@gmail.com>
 */
interface CommandLoaderInterface
{
    /**
     * Loads a command.
     *
     * @throws CommandNotFoundException
     */
    public function get(string $name): Command;

    /**
     * Checks if a command exists.
     */
    public function has(string $name): bool;

    /**
     * @return string[]
     */
    public function getNames(): array;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\CommandLoader;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\CommandNotFoundException;

/**
 * A simple command loader using factories to instantiate commands lazily.
 *
 * @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
 */
class FactoryCommandLoader implements CommandLoaderInterface
{
    private array $factories;

    /**
     * @param callable[] $factories Indexed by command names
     */
    public function __construct(array $factories)
    {
        $this->factories = $factories;
    }

    public function has(string $name): bool
    {
        return isset($this->factories[$name]);
    }

    public function get(string $name): Command
    {
        if (!isset($this->factories[$name])) {
            throw new CommandNotFoundException(\sprintf('Command "%s" does not exist.', $name));
        }

        $factory = $this->factories[$name];

        return $factory();
    }

    public function getNames(): array
    {
        return array_keys($this->factories);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\CommandLoader;

use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\CommandNotFoundException;

/**
 * Loads commands from a PSR-11 container.
 *
 * @author Robin Chalas <robin.chalas@gmail.com>
 */
class ContainerCommandLoader implements CommandLoaderInterface
{
    private ContainerInterface $container;
    private array $commandMap;

    /**
     * @param array $commandMap An array with command names as keys and service ids as values
     */
    public function __construct(ContainerInterface $container, array $commandMap)
    {
        $this->container = $container;
        $this->commandMap = $commandMap;
    }

    public function get(string $name): Command
    {
        if (!$this->has($name)) {
            throw new CommandNotFoundException(\sprintf('Command "%s" does not exist.', $name));
        }

        return $this->container->get($this->commandMap[$name]);
    }

    public function has(string $name): bool
    {
        return isset($this->commandMap[$name]) && $this->container->has($this->commandMap[$name]);
    }

    public function getNames(): array
    {
        return array_keys($this->commandMap);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\SignalRegistry;

/**
 * @author Grégoire Pineau <lyrixx@lyrixx.info>
 */
class SignalMap
{
    private static array $map;

    public static function getSignalName(int $signal): ?string
    {
        if (!\extension_loaded('pcntl')) {
            return null;
        }

        if (!isset(self::$map)) {
            $r = new \ReflectionExtension('pcntl');
            $c = $r->getConstants();
            $map = array_filter($c, fn ($k) => str_starts_with($k, 'SIG') && !str_starts_with($k, 'SIG_') && 'SIGBABY' !== $k, \ARRAY_FILTER_USE_KEY);
            self::$map = array_flip($map);
        }

        return self::$map[$signal] ?? null;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\SignalRegistry;

final class SignalRegistry
{
    private array $signalHandlers = [];

    public function __construct()
    {
        if (\function_exists('pcntl_async_signals')) {
            pcntl_async_signals(true);
        }
    }

    public function register(int $signal, callable $signalHandler): void
    {
        if (!isset($this->signalHandlers[$signal])) {
            $previousCallback = pcntl_signal_get_handler($signal);

            if (\is_callable($previousCallback)) {
                $this->signalHandlers[$signal][] = $previousCallback;
            }
        }

        $this->signalHandlers[$signal][] = $signalHandler;

        pcntl_signal($signal, $this->handle(...));
    }

    public static function isSupported(): bool
    {
        return \function_exists('pcntl_signal');
    }

    /**
     * @internal
     */
    public function handle(int $signal): void
    {
        $count = \count($this->signalHandlers[$signal]);

        foreach ($this->signalHandlers[$signal] as $i => $signalHandler) {
            $hasNext = $i !== $count - 1;
            $signalHandler($signal, $hasNext);
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

/**
 * Formatter interface for console output that supports word wrapping.
 *
 * @author Roland Franssen <franssen.roland@gmail.com>
 */
interface WrappableOutputFormatterInterface extends OutputFormatterInterface
{
    /**
     * Formats a message according to the given styles, wrapping at `$width` (0 means no wrapping).
     *
     * @return string
     */
    public function formatAndWrap(?string $message, int $width);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Helper\Helper;

use function Symfony\Component\String\b;

/**
 * Formatter class for console output.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Roland Franssen <franssen.roland@gmail.com>
 */
class OutputFormatter implements WrappableOutputFormatterInterface
{
    private bool $decorated;
    private array $styles = [];
    private OutputFormatterStyleStack $styleStack;

    public function __clone()
    {
        $this->styleStack = clone $this->styleStack;
        foreach ($this->styles as $key => $value) {
            $this->styles[$key] = clone $value;
        }
    }

    /**
     * Escapes "<" and ">" special chars in given text.
     */
    public static function escape(string $text): string
    {
        $text = preg_replace('/([^\\\\]|^)([<>])/', '$1\\\\$2', $text);

        return self::escapeTrailingBackslash($text);
    }

    /**
     * Escapes trailing "\" in given text.
     *
     * @internal
     */
    public static function escapeTrailingBackslash(string $text): string
    {
        if (str_ends_with($text, '\\')) {
            $len = \strlen($text);
            $text = rtrim($text, '\\');
            $text = str_replace("\0", '', $text);
            $text .= str_repeat("\0", $len - \strlen($text));
        }

        return $text;
    }

    /**
     * Initializes console output formatter.
     *
     * @param OutputFormatterStyleInterface[] $styles Array of "name => FormatterStyle" instances
     */
    public function __construct(bool $decorated = false, array $styles = [])
    {
        $this->decorated = $decorated;

        $this->setStyle('error', new OutputFormatterStyle('white', 'red'));
        $this->setStyle('info', new OutputFormatterStyle('green'));
        $this->setStyle('comment', new OutputFormatterStyle('yellow'));
        $this->setStyle('question', new OutputFormatterStyle('black', 'cyan'));

        foreach ($styles as $name => $style) {
            $this->setStyle($name, $style);
        }

        $this->styleStack = new OutputFormatterStyleStack();
    }

    /**
     * @return void
     */
    public function setDecorated(bool $decorated)
    {
        $this->decorated = $decorated;
    }

    public function isDecorated(): bool
    {
        return $this->decorated;
    }

    /**
     * @return void
     */
    public function setStyle(string $name, OutputFormatterStyleInterface $style)
    {
        $this->styles[strtolower($name)] = $style;
    }

    public function hasStyle(string $name): bool
    {
        return isset($this->styles[strtolower($name)]);
    }

    public function getStyle(string $name): OutputFormatterStyleInterface
    {
        if (!$this->hasStyle($name)) {
            throw new InvalidArgumentException(\sprintf('Undefined style: "%s".', $name));
        }

        return $this->styles[strtolower($name)];
    }

    public function format(?string $message): ?string
    {
        return $this->formatAndWrap($message, 0);
    }

    /**
     * @return string
     */
    public function formatAndWrap(?string $message, int $width)
    {
        if (null === $message) {
            return '';
        }

        $offset = 0;
        $output = '';
        $openTagRegex = '[a-z](?:[^\\\\<>]*+ | \\\\.)*';
        $closeTagRegex = '[a-z][^<>]*+';
        $currentLineLength = 0;
        preg_match_all("#<(($openTagRegex) | /($closeTagRegex)?)>#ix", $message, $matches, \PREG_OFFSET_CAPTURE);
        foreach ($matches[0] as $i => $match) {
            $pos = $match[1];
            $text = $match[0];

            if (0 != $pos && '\\' == $message[$pos - 1]) {
                continue;
            }

            // convert byte position to character position.
            $pos = Helper::length(substr($message, 0, $pos));
            // add the text up to the next tag
            $output .= $this->applyCurrentStyle(Helper::substr($message, $offset, $pos - $offset), $output, $width, $currentLineLength);
            $offset = $pos + Helper::length($text);

            // opening tag?
            if ($open = '/' !== $text[1]) {
                $tag = $matches[1][$i][0];
            } else {
                $tag = $matches[3][$i][0] ?? '';
            }

            if (!$open && !$tag) {
                // </>
                $this->styleStack->pop();
            } elseif (null === $style = $this->createStyleFromString($tag)) {
                $output .= $this->applyCurrentStyle($text, $output, $width, $currentLineLength);
            } elseif ($open) {
                $this->styleStack->push($style);
            } else {
                $this->styleStack->pop($style);
            }
        }

        $output .= $this->applyCurrentStyle(Helper::substr($message, $offset), $output, $width, $currentLineLength);

        return strtr($output, ["\0" => '\\', '\\<' => '<', '\\>' => '>']);
    }

    public function getStyleStack(): OutputFormatterStyleStack
    {
        return $this->styleStack;
    }

    /**
     * Tries to create new style instance from string.
     */
    private function createStyleFromString(string $string): ?OutputFormatterStyleInterface
    {
        if (isset($this->styles[$string])) {
            return $this->styles[$string];
        }

        if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', $string, $matches, \PREG_SET_ORDER)) {
            return null;
        }

        $style = new OutputFormatterStyle();
        foreach ($matches as $match) {
            array_shift($match);
            $match[0] = strtolower($match[0]);

            if ('fg' == $match[0]) {
                $style->setForeground(strtolower($match[1]));
            } elseif ('bg' == $match[0]) {
                $style->setBackground(strtolower($match[1]));
            } elseif ('href' === $match[0]) {
                $url = preg_replace('{\\\\([<>])}', '$1', $match[1]);
                $style->setHref($url);
            } elseif ('options' === $match[0]) {
                preg_match_all('([^,;]+)', strtolower($match[1]), $options);
                $options = array_shift($options);
                foreach ($options as $option) {
                    $style->setOption($option);
                }
            } else {
                return null;
            }
        }

        return $style;
    }

    /**
     * Applies current style from stack to text, if must be applied.
     */
    private function applyCurrentStyle(string $text, string $current, int $width, int &$currentLineLength): string
    {
        if ('' === $text) {
            return '';
        }

        if (!$width) {
            return $this->isDecorated() ? $this->styleStack->getCurrent()->apply($text) : $text;
        }

        if (!$currentLineLength && '' !== $current) {
            $text = ltrim($text);
        }

        if ($currentLineLength) {
            $lines = explode("\n", $text, 2);
            $prefix = Helper::substr($lines[0], 0, $i = $width - $currentLineLength)."\n";
            $text = Helper::substr($lines[0], $i);

            if (isset($lines[1])) {
                // $prefix may contain the full first line in which the \n is already a part of $prefix.
                if ('' !== $text) {
                    $text .= "\n";
                }

                $text .= $lines[1];
            }
        } else {
            $prefix = '';
        }

        preg_match('~(\\n)$~', $text, $matches);
        $text = $prefix.$this->addLineBreaks($text, $width);
        $text = rtrim($text, "\n").($matches[1] ?? '');

        if (!$currentLineLength && '' !== $current && !str_ends_with($current, "\n")) {
            $text = "\n".$text;
        }

        $lines = explode("\n", $text);

        foreach ($lines as $i => $line) {
            $currentLineLength = 0 === $i ? $currentLineLength + Helper::length($line) : Helper::length($line);
            if ($width <= $currentLineLength) {
                $currentLineLength = 0;
            }
        }

        if ($this->isDecorated()) {
            foreach ($lines as $i => $line) {
                $lines[$i] = $this->styleStack->getCurrent()->apply($line);
            }
        }

        return implode("\n", $lines);
    }

    private function addLineBreaks(string $text, int $width): string
    {
        $encoding = mb_detect_encoding($text, null, true) ?: 'UTF-8';

        return b($text)->toUnicodeString($encoding)->wordwrap($width, "\n", true)->toByteString($encoding);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

/**
 * @author Tien Xuan Vo <tien.xuan.vo@gmail.com>
 */
final class NullOutputFormatter implements OutputFormatterInterface
{
    private NullOutputFormatterStyle $style;

    public function format(?string $message): ?string
    {
        return null;
    }

    public function getStyle(string $name): OutputFormatterStyleInterface
    {
        // to comply with the interface we must return a OutputFormatterStyleInterface
        return $this->style ??= new NullOutputFormatterStyle();
    }

    public function hasStyle(string $name): bool
    {
        return false;
    }

    public function isDecorated(): bool
    {
        return false;
    }

    public function setDecorated(bool $decorated): void
    {
        // do nothing
    }

    public function setStyle(string $name, OutputFormatterStyleInterface $style): void
    {
        // do nothing
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

/**
 * Formatter interface for console output.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
interface OutputFormatterInterface
{
    /**
     * Sets the decorated flag.
     *
     * @return void
     */
    public function setDecorated(bool $decorated);

    /**
     * Whether the output will decorate messages.
     */
    public function isDecorated(): bool;

    /**
     * Sets a new style.
     *
     * @return void
     */
    public function setStyle(string $name, OutputFormatterStyleInterface $style);

    /**
     * Checks if output formatter has style with specified name.
     */
    public function hasStyle(string $name): bool;

    /**
     * Gets style options from style with specified name.
     *
     * @throws \InvalidArgumentException When style isn't defined
     */
    public function getStyle(string $name): OutputFormatterStyleInterface;

    /**
     * Formats a message according to the given styles.
     */
    public function format(?string $message): ?string;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

/**
 * Formatter style interface for defining styles.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
interface OutputFormatterStyleInterface
{
    /**
     * Sets style foreground color.
     *
     * @return void
     */
    public function setForeground(?string $color);

    /**
     * Sets style background color.
     *
     * @return void
     */
    public function setBackground(?string $color);

    /**
     * Sets some specific style option.
     *
     * @return void
     */
    public function setOption(string $option);

    /**
     * Unsets some specific style option.
     *
     * @return void
     */
    public function unsetOption(string $option);

    /**
     * Sets multiple style options at once.
     *
     * @return void
     */
    public function setOptions(array $options);

    /**
     * Applies the style to a given text.
     */
    public function apply(string $text): string;
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

/**
 * @author Tien Xuan Vo <tien.xuan.vo@gmail.com>
 */
final class NullOutputFormatterStyle implements OutputFormatterStyleInterface
{
    public function apply(string $text): string
    {
        return $text;
    }

    public function setBackground(?string $color = null): void
    {
        if (1 > \func_num_args()) {
            trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
        }
        // do nothing
    }

    public function setForeground(?string $color = null): void
    {
        if (1 > \func_num_args()) {
            trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
        }
        // do nothing
    }

    public function setOption(string $option): void
    {
        // do nothing
    }

    public function setOptions(array $options): void
    {
        // do nothing
    }

    public function unsetOption(string $option): void
    {
        // do nothing
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

use Symfony\Component\Console\Color;

/**
 * Formatter style class for defining styles.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class OutputFormatterStyle implements OutputFormatterStyleInterface
{
    private Color $color;
    private string $foreground;
    private string $background;
    private array $options;
    private ?string $href = null;
    private bool $handlesHrefGracefully;

    /**
     * Initializes output formatter style.
     *
     * @param string|null $foreground The style foreground color name
     * @param string|null $background The style background color name
     */
    public function __construct(?string $foreground = null, ?string $background = null, array $options = [])
    {
        $this->color = new Color($this->foreground = $foreground ?: '', $this->background = $background ?: '', $this->options = $options);
    }

    /**
     * @return void
     */
    public function setForeground(?string $color = null)
    {
        if (1 > \func_num_args()) {
            trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
        }
        $this->color = new Color($this->foreground = $color ?: '', $this->background, $this->options);
    }

    /**
     * @return void
     */
    public function setBackground(?string $color = null)
    {
        if (1 > \func_num_args()) {
            trigger_deprecation('symfony/console', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
        }
        $this->color = new Color($this->foreground, $this->background = $color ?: '', $this->options);
    }

    public function setHref(string $url): void
    {
        $this->href = $url;
    }

    /**
     * @return void
     */
    public function setOption(string $option)
    {
        $this->options[] = $option;
        $this->color = new Color($this->foreground, $this->background, $this->options);
    }

    /**
     * @return void
     */
    public function unsetOption(string $option)
    {
        $pos = array_search($option, $this->options);
        if (false !== $pos) {
            unset($this->options[$pos]);
        }

        $this->color = new Color($this->foreground, $this->background, $this->options);
    }

    /**
     * @return void
     */
    public function setOptions(array $options)
    {
        $this->color = new Color($this->foreground, $this->background, $this->options = $options);
    }

    public function apply(string $text): string
    {
        $this->handlesHrefGracefully ??= 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR')
            && (!getenv('KONSOLE_VERSION') || (int) getenv('KONSOLE_VERSION') > 201100)
            && !isset($_SERVER['IDEA_INITIAL_DIRECTORY']);

        if (null !== $this->href && $this->handlesHrefGracefully) {
            $text = "\033]8;;$this->href\033\\$text\033]8;;\033\\";
        }

        return $this->color->apply($text);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Contracts\Service\ResetInterface;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class OutputFormatterStyleStack implements ResetInterface
{
    /**
     * @var OutputFormatterStyleInterface[]
     */
    private array $styles = [];

    private OutputFormatterStyleInterface $emptyStyle;

    public function __construct(?OutputFormatterStyleInterface $emptyStyle = null)
    {
        $this->emptyStyle = $emptyStyle ?? new OutputFormatterStyle();
        $this->reset();
    }

    /**
     * Resets stack (ie. empty internal arrays).
     *
     * @return void
     */
    public function reset()
    {
        $this->styles = [];
    }

    /**
     * Pushes a style in the stack.
     *
     * @return void
     */
    public function push(OutputFormatterStyleInterface $style)
    {
        $this->styles[] = $style;
    }

    /**
     * Pops a style from the stack.
     *
     * @throws InvalidArgumentException When style tags incorrectly nested
     */
    public function pop(?OutputFormatterStyleInterface $style = null): OutputFormatterStyleInterface
    {
        if (!$this->styles) {
            return $this->emptyStyle;
        }

        if (null === $style) {
            return array_pop($this->styles);
        }

        foreach (array_reverse($this->styles, true) as $index => $stackedStyle) {
            if ($style->apply('') === $stackedStyle->apply('')) {
                $this->styles = \array_slice($this->styles, 0, $index);

                return $stackedStyle;
            }
        }

        throw new InvalidArgumentException('Incorrectly nested style tag found.');
    }

    /**
     * Computes current style with stacks top codes.
     */
    public function getCurrent(): OutputFormatterStyleInterface
    {
        if (!$this->styles) {
            return $this->emptyStyle;
        }

        return $this->styles[\count($this->styles) - 1];
    }

    /**
     * @return $this
     */
    public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle): static
    {
        $this->emptyStyle = $emptyStyle;

        return $this;
    }

    public function getEmptyStyle(): OutputFormatterStyleInterface
    {
        return $this->emptyStyle;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Logger;

use Psr\Log\AbstractLogger;
use Psr\Log\InvalidArgumentException;
use Psr\Log\LogLevel;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * PSR-3 compliant console logger.
 *
 * @author Kévin Dunglas <dunglas@gmail.com>
 *
 * @see https://www.php-fig.org/psr/psr-3/
 */
class ConsoleLogger extends AbstractLogger
{
    public const INFO = 'info';
    public const ERROR = 'error';

    private OutputInterface $output;
    private array $verbosityLevelMap = [
        LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL,
        LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL,
        LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL,
        LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL,
        LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL,
        LogLevel::NOTICE => OutputInterface::VERBOSITY_VERBOSE,
        LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE,
        LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG,
    ];
    private array $formatLevelMap = [
        LogLevel::EMERGENCY => self::ERROR,
        LogLevel::ALERT => self::ERROR,
        LogLevel::CRITICAL => self::ERROR,
        LogLevel::ERROR => self::ERROR,
        LogLevel::WARNING => self::INFO,
        LogLevel::NOTICE => self::INFO,
        LogLevel::INFO => self::INFO,
        LogLevel::DEBUG => self::INFO,
    ];
    private bool $errored = false;

    public function __construct(OutputInterface $output, array $verbosityLevelMap = [], array $formatLevelMap = [])
    {
        $this->output = $output;
        $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap;
        $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap;
    }

    public function log($level, $message, array $context = []): void
    {
        if (!isset($this->verbosityLevelMap[$level])) {
            throw new InvalidArgumentException(\sprintf('The log level "%s" does not exist.', $level));
        }

        $output = $this->output;

        // Write to the error output if necessary and available
        if (self::ERROR === $this->formatLevelMap[$level]) {
            if ($this->output instanceof ConsoleOutputInterface) {
                $output = $output->getErrorOutput();
            }
            $this->errored = true;
        }

        // the if condition check isn't necessary -- it's the same one that $output will do internally anyway.
        // We only do it for efficiency here as the message formatting is relatively expensive.
        if ($output->getVerbosity() >= $this->verbosityLevelMap[$level]) {
            $output->writeln(\sprintf('<%1$s>[%2$s] %3$s</%1$s>', $this->formatLevelMap[$level], $level, $this->interpolate($message, $context)), $this->verbosityLevelMap[$level]);
        }
    }

    /**
     * Returns true when any messages have been logged at error levels.
     */
    public function hasErrored(): bool
    {
        return $this->errored;
    }

    /**
     * Interpolates context values into the message placeholders.
     *
     * @author PHP Framework Interoperability Group
     */
    private function interpolate(string $message, array $context): string
    {
        if (!str_contains($message, '{')) {
            return $message;
        }

        $replacements = [];
        foreach ($context as $key => $val) {
            if (null === $val || \is_scalar($val) || $val instanceof \Stringable) {
                $replacements["{{$key}}"] = $val;
            } elseif ($val instanceof \DateTimeInterface) {
                $replacements["{{$key}}"] = $val->format(\DateTimeInterface::RFC3339);
            } elseif (\is_object($val)) {
                $replacements["{{$key}}"] = '[object '.$val::class.']';
            } else {
                $replacements["{{$key}}"] = '['.\gettype($val).']';
            }
        }

        return strtr($message, $replacements);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Grégoire Pineau <lyrixx@lyrixx.info>
 */
class SingleCommandApplication extends Command
{
    private string $version = 'UNKNOWN';
    private bool $autoExit = true;
    private bool $running = false;

    /**
     * @return $this
     */
    public function setVersion(string $version): static
    {
        $this->version = $version;

        return $this;
    }

    /**
     * @final
     *
     * @return $this
     */
    public function setAutoExit(bool $autoExit): static
    {
        $this->autoExit = $autoExit;

        return $this;
    }

    public function run(?InputInterface $input = null, ?OutputInterface $output = null): int
    {
        if ($this->running) {
            return parent::run($input, $output);
        }

        // We use the command name as the application name
        $application = new Application($this->getName() ?: 'UNKNOWN', $this->version);
        $application->setAutoExit($this->autoExit);
        // Fix the usage of the command displayed with "--help"
        $this->setName($_SERVER['argv'][0]);
        $application->add($this);
        $application->setDefaultCommand($this->getName(), true);

        $this->running = true;
        try {
            $ret = $application->run($input, $output);
        } finally {
            $this->running = false;
        }

        return $ret ?? 1;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * @author Fabien Potencier <fabien@symfony.com>
 */
final class Color
{
    private const COLORS = [
        'black' => 0,
        'red' => 1,
        'green' => 2,
        'yellow' => 3,
        'blue' => 4,
        'magenta' => 5,
        'cyan' => 6,
        'white' => 7,
        'default' => 9,
    ];

    private const BRIGHT_COLORS = [
        'gray' => 0,
        'bright-red' => 1,
        'bright-green' => 2,
        'bright-yellow' => 3,
        'bright-blue' => 4,
        'bright-magenta' => 5,
        'bright-cyan' => 6,
        'bright-white' => 7,
    ];

    private const AVAILABLE_OPTIONS = [
        'bold' => ['set' => 1, 'unset' => 22],
        'underscore' => ['set' => 4, 'unset' => 24],
        'blink' => ['set' => 5, 'unset' => 25],
        'reverse' => ['set' => 7, 'unset' => 27],
        'conceal' => ['set' => 8, 'unset' => 28],
    ];

    private string $foreground;
    private string $background;
    private array $options = [];

    public function __construct(string $foreground = '', string $background = '', array $options = [])
    {
        $this->foreground = $this->parseColor($foreground);
        $this->background = $this->parseColor($background, true);

        foreach ($options as $option) {
            if (!isset(self::AVAILABLE_OPTIONS[$option])) {
                throw new InvalidArgumentException(\sprintf('Invalid option specified: "%s". Expected one of (%s).', $option, implode(', ', array_keys(self::AVAILABLE_OPTIONS))));
            }

            $this->options[$option] = self::AVAILABLE_OPTIONS[$option];
        }
    }

    public function apply(string $text): string
    {
        return $this->set().$text.$this->unset();
    }

    public function set(): string
    {
        $setCodes = [];
        if ('' !== $this->foreground) {
            $setCodes[] = $this->foreground;
        }
        if ('' !== $this->background) {
            $setCodes[] = $this->background;
        }
        foreach ($this->options as $option) {
            $setCodes[] = $option['set'];
        }
        if (0 === \count($setCodes)) {
            return '';
        }

        return \sprintf("\033[%sm", implode(';', $setCodes));
    }

    public function unset(): string
    {
        $unsetCodes = [];
        if ('' !== $this->foreground) {
            $unsetCodes[] = 39;
        }
        if ('' !== $this->background) {
            $unsetCodes[] = 49;
        }
        foreach ($this->options as $option) {
            $unsetCodes[] = $option['unset'];
        }
        if (0 === \count($unsetCodes)) {
            return '';
        }

        return \sprintf("\033[%sm", implode(';', $unsetCodes));
    }

    private function parseColor(string $color, bool $background = false): string
    {
        if ('' === $color) {
            return '';
        }

        if ('#' === $color[0]) {
            return ($background ? '4' : '3').Terminal::getColorMode()->convertFromHexToAnsiColorCode($color);
        }

        if (isset(self::COLORS[$color])) {
            return ($background ? '4' : '3').self::COLORS[$color];
        }

        if (isset(self::BRIGHT_COLORS[$color])) {
            return ($background ? '10' : '9').self::BRIGHT_COLORS[$color];
        }

        throw new InvalidArgumentException(\sprintf('Invalid "%s" color; expected one of (%s).', $color, implode(', ', array_merge(array_keys(self::COLORS), array_keys(self::BRIGHT_COLORS)))));
    }
}
<?php

declare(strict_types=1);

namespace MabeEnum;

use ArrayAccess;
use Countable;
use InvalidArgumentException;
use Iterator;
use IteratorAggregate;
use UnexpectedValueException;

/**
 * A map of enumerators and data values (EnumMap<T of Enum, mixed>).
 *
 * @template T of Enum
 * @implements ArrayAccess<T, mixed>
 * @implements IteratorAggregate<T, mixed>
 *
 * @copyright 2020, Marc Bennewitz
 * @license http://github.com/marc-mabe/php-enum/blob/master/LICENSE.txt New BSD License
 * @link http://github.com/marc-mabe/php-enum for the canonical source repository
 */
class EnumMap implements ArrayAccess, Countable, IteratorAggregate
{
    /**
     * The classname of the enumeration type
     * @var class-string<T>
     */
    private $enumeration;

    /**
     * Internal map of ordinal number and data value
     * @var array<int, mixed>
     */
    private $map = [];

    /**
     * Constructor
     * @param class-string<T> $enumeration The classname of the enumeration type
     * @param null|iterable<T|null|bool|int|float|string|array<mixed>, mixed> $map Initialize map
     * @throws InvalidArgumentException
     */
    public function __construct(string $enumeration, ?iterable $map = null)
    {
        if (!\is_subclass_of($enumeration, Enum::class)) {
            throw new InvalidArgumentException(\sprintf(
                '%s can handle subclasses of %s only',
                 __CLASS__,
                Enum::class
            ));
        }
        $this->enumeration = $enumeration;

        if ($map) {
            $this->addIterable($map);
        }
    }

    /**
     * Add virtual private property "__pairs" with a list of key-value-pairs
     * to the result of var_dump.
     *
     * This helps debugging as internally the map is using the ordinal number.
     *
     * @return array<string, mixed>
     */
    public function __debugInfo(): array {
        $dbg = (array)$this;
        $dbg["\0" . self::class . "\0__pairs"] = array_map(function ($k, $v) {
            return [$k, $v];
        }, $this->getKeys(), $this->getValues());
        return $dbg;
    }

    /* write access (mutable) */

    /**
     * Adds the given enumerator (object or value) mapping to the specified data value.
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
     * @param mixed                                                 $value
     * @throws InvalidArgumentException On an invalid given enumerator
     * @see offsetSet()
     */
    public function add($enumerator, $value): void
    {
        $ord = ($this->enumeration)::get($enumerator)->getOrdinal();
        $this->map[$ord] = $value;
    }

    /**
     * Adds the given iterable, mapping enumerators (objects or values) to data values.
     * @param iterable<T|null|bool|int|float|string|array<mixed>, mixed> $map
     * @throws InvalidArgumentException On an invalid given enumerator
     */
    public function addIterable(iterable $map): void
    {
        $innerMap = $this->map;
        foreach ($map as $enumerator => $value) {
            $ord = ($this->enumeration)::get($enumerator)->getOrdinal();
            $innerMap[$ord] = $value;
        }
        $this->map = $innerMap;
    }

    /**
     * Removes the given enumerator (object or value) mapping.
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
     * @throws InvalidArgumentException On an invalid given enumerator
     * @see offsetUnset()
     */
    public function remove($enumerator): void
    {
        $ord = ($this->enumeration)::get($enumerator)->getOrdinal();
        unset($this->map[$ord]);
    }

    /**
     * Removes the given iterable enumerator (object or value) mappings.
     * @param iterable<T|null|bool|int|float|string|array<mixed>> $enumerators
     * @throws InvalidArgumentException On an invalid given enumerator
     */
    public function removeIterable(iterable $enumerators): void
    {
        $map = $this->map;
        foreach ($enumerators as $enumerator) {
            $ord = ($this->enumeration)::get($enumerator)->getOrdinal();
            unset($map[$ord]);
        }

        $this->map = $map;
    }

    /* write access (immutable) */

    /**
     * Creates a new map with the given enumerator (object or value) mapping to the specified data value added.
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
     * @param mixed                                                 $value
     * @return static
     * @throws InvalidArgumentException On an invalid given enumerator
     */
    public function with($enumerator, $value): self
    {
        $clone = clone $this;
        $clone->add($enumerator, $value);
        return $clone;
    }

    /**
     * Creates a new map with the given iterable mapping enumerators (objects or values) to data values added.
     * @param iterable<T|null|bool|int|float|string|array<mixed>, mixed> $map
     * @return static
     * @throws InvalidArgumentException On an invalid given enumerator
     */
    public function withIterable(iterable $map): self
    {
        $clone = clone $this;
        $clone->addIterable($map);
        return $clone;
    }

    /**
     * Create a new map with the given enumerator mapping removed.
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
     * @return static
     * @throws InvalidArgumentException On an invalid given enumerator
     */
    public function without($enumerator): self
    {
        $clone = clone $this;
        $clone->remove($enumerator);
        return $clone;
    }

    /**
     * Creates a new map with the given iterable enumerator (object or value) mappings removed.
     * @param iterable<T|null|bool|int|float|string|array<mixed>> $enumerators
     * @return static
     * @throws InvalidArgumentException On an invalid given enumerator
     */
    public function withoutIterable(iterable $enumerators): self
    {
        $clone = clone $this;
        $clone->removeIterable($enumerators);
        return $clone;
    }

    /* read access */

    /**
     * Get the classname of the enumeration type.
     * @return class-string<T>
     */
    public function getEnumeration(): string
    {
        return $this->enumeration;
    }

    /**
     * Get the mapped data value of the given enumerator (object or value).
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
     * @return mixed
     * @throws InvalidArgumentException On an invalid given enumerator
     * @throws UnexpectedValueException If the given enumerator does not exist in this map
     * @see offsetGet()
     */
    public function get($enumerator)
    {
        $enumerator = ($this->enumeration)::get($enumerator);
        $ord = $enumerator->getOrdinal();
        if (!\array_key_exists($ord, $this->map)) {
            throw new UnexpectedValueException(sprintf(
                'Enumerator %s could not be found',
                \var_export($enumerator->getValue(), true)
            ));
        }

        return $this->map[$ord];
    }

    /**
     * Get a list of enumerator keys.
     * @return T[]
     *
     * @phpstan-return array<int, T>
     * @psalm-return list<T>
     */
    public function getKeys(): array
    {
        /** @var callable $byOrdinalFn */
        $byOrdinalFn = [$this->enumeration, 'byOrdinal'];

        return \array_map($byOrdinalFn, \array_keys($this->map));
    }

    /**
     * Get a list of mapped data values.
     * @return mixed[]
     *
     * @phpstan-return array<int, mixed>
     * @psalm-return list<mixed>
     */
    public function getValues(): array
    {
        return \array_values($this->map);
    }

    /**
     * Search for the given data value.
     * @param mixed $value
     * @param bool $strict Use strict type comparison
     * @return T|null The enumerator object of the first matching data value or NULL
     */
    public function search($value, bool $strict = false)
    {
        /** @var false|int $ord */
        $ord = \array_search($value, $this->map, $strict);
        if ($ord !== false) {
            return ($this->enumeration)::byOrdinal($ord);
        }

        return null;
    }

    /**
     * Test if the given enumerator key (object or value) exists.
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
     * @return bool
     * @see offsetExists()
     */
    public function has($enumerator): bool
    {
        try {
            $ord = ($this->enumeration)::get($enumerator)->getOrdinal();
            return \array_key_exists($ord, $this->map);
        } catch (InvalidArgumentException $e) {
            // An invalid enumerator can't be contained in this map
            return false;
        }
    }

    /**
     * Test if the given enumerator key (object or value) exists.
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
     * @return bool
     * @see offsetExists()
     * @see has()
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
     */
    public function contains($enumerator): bool
    {
        return $this->has($enumerator);
    }

    /* ArrayAccess */

    /**
     * Test if the given enumerator key (object or value) exists and is not NULL
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
     * @return bool
     * @see contains()
     */
    public function offsetExists($enumerator): bool
    {
        try {
            return isset($this->map[($this->enumeration)::get($enumerator)->getOrdinal()]);
        } catch (InvalidArgumentException $e) {
            // An invalid enumerator can't be an offset of this map
            return false;
        }
    }

    /**
     * Get the mapped data value of the given enumerator (object or value).
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
     * @return mixed The mapped date value of the given enumerator or NULL
     * @throws InvalidArgumentException On an invalid given enumerator
     * @see get()
     */
    #[\ReturnTypeWillChange]
    public function offsetGet($enumerator)
    {
        try {
            return $this->get($enumerator);
        } catch (UnexpectedValueException $e) {
            return null;
        }
    }

    /**
     * Adds the given enumerator (object or value) mapping to the specified data value.
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
     * @param mixed                                     $value
     * @return void
     * @throws InvalidArgumentException On an invalid given enumerator
     * @see add()
     */
    public function offsetSet($enumerator, $value = null): void
    {
        $this->add($enumerator, $value);
    }

    /**
     * Removes the given enumerator (object or value) mapping.
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
     * @return void
     * @throws InvalidArgumentException On an invalid given enumerator
     * @see remove()
     */
    public function offsetUnset($enumerator): void
    {
        $this->remove($enumerator);
    }

    /* IteratorAggregate */

    /**
     * Get a new Iterator.
     *
     * @return Iterator<T, mixed> Iterator<K extends Enum, V>
     */
    public function getIterator(): Iterator
    {
        $map = $this->map;
        foreach ($map as $ordinal => $value) {
            yield ($this->enumeration)::byOrdinal($ordinal) => $value;
        }
    }

    /* Countable */

    /**
     * Count the number of elements
     *
     * @return int
     */
    public function count(): int
    {
        return \count($this->map);
    }

    /**
     * Tests if the map is empty
     *
     * @return bool
     */
    public function isEmpty(): bool
    {
        return empty($this->map);
    }
}
<?php

declare(strict_types=1);

namespace MabeEnum;

use Countable;
use InvalidArgumentException;
use Iterator;
use IteratorAggregate;

/**
 * A set of enumerators of the given enumeration (EnumSet<T of Enum>)
 * based on an integer or binary bitset depending of given enumeration size.
 *
 * @template T of Enum
 * @implements IteratorAggregate<int, T>
 *
 * @copyright 2020, Marc Bennewitz
 * @license http://github.com/marc-mabe/php-enum/blob/master/LICENSE.txt New BSD License
 * @link http://github.com/marc-mabe/php-enum for the canonical source repository
 */
class EnumSet implements IteratorAggregate, Countable
{
    /**
     * The classname of the Enumeration
     * @var class-string<T>
     */
    private $enumeration;

    /**
     * Number of enumerators defined in the enumeration
     * @var int
     */
    private $enumerationCount;

    /**
     * Integer or binary (little endian) bitset
     * @var int|string
     */
    private $bitset = 0;

    /**
     * Integer or binary (little endian) empty bitset
     *
     * @var int|string
     */
    private $emptyBitset = 0;

    /**#@+
     * Defines private method names to be called depended of how the bitset type was set too.
     * ... Integer or binary bitset.
     * ... *Int or *Bin method
     *
     * @var string
     */
    /** @var string */
    private $fnDoGetIterator       = 'doGetIteratorInt';

    /** @var string */
    private $fnDoCount             = 'doCountInt';

    /** @var string */
    private $fnDoGetOrdinals       = 'doGetOrdinalsInt';

    /** @var string */
    private $fnDoGetBit            = 'doGetBitInt';

    /** @var string */
    private $fnDoSetBit            = 'doSetBitInt';

    /** @var string */
    private $fnDoUnsetBit          = 'doUnsetBitInt';

    /** @var string */
    private $fnDoGetBinaryBitsetLe = 'doGetBinaryBitsetLeInt';

    /** @var string */
    private $fnDoSetBinaryBitsetLe = 'doSetBinaryBitsetLeInt';
    /**#@-*/

    /**
     * Constructor
     *
     * @param class-string<T> $enumeration The classname of the enumeration
     * @param iterable<T|null|bool|int|float|string|array<mixed>>|null $enumerators iterable list of enumerators initializing the set
     * @throws InvalidArgumentException
     */
    public function __construct(string $enumeration, ?iterable $enumerators = null)
    {
        if (!\is_subclass_of($enumeration, Enum::class)) {
            throw new InvalidArgumentException(\sprintf(
                '%s can handle subclasses of %s only',
                __METHOD__,
                Enum::class
            ));
        }

        $this->enumeration      = $enumeration;
        $this->enumerationCount = \count($enumeration::getConstants());

        // By default the bitset is initialized as integer bitset
        // in case the enumeration has more enumerators then integer bits
        // we will switch this into a binary bitset
        if ($this->enumerationCount > \PHP_INT_SIZE * 8) {
            // init binary bitset with zeros
            $this->bitset = $this->emptyBitset = \str_repeat("\0", (int)\ceil($this->enumerationCount / 8));

            // switch internal binary bitset functions
            $this->fnDoGetIterator       = 'doGetIteratorBin';
            $this->fnDoCount             = 'doCountBin';
            $this->fnDoGetOrdinals       = 'doGetOrdinalsBin';
            $this->fnDoGetBit            = 'doGetBitBin';
            $this->fnDoSetBit            = 'doSetBitBin';
            $this->fnDoUnsetBit          = 'doUnsetBitBin';
            $this->fnDoGetBinaryBitsetLe = 'doGetBinaryBitsetLeBin';
            $this->fnDoSetBinaryBitsetLe = 'doSetBinaryBitsetLeBin';
        }

        if ($enumerators !== null) {
            foreach ($enumerators as $enumerator) {
                $this->{$this->fnDoSetBit}($enumeration::get($enumerator)->getOrdinal());
            }
        }
    }

    /**
     * Add virtual private property "__enumerators" with a list of enumerator values set
     * to the result of var_dump.
     *
     * This helps debugging as internally the enumerators of this EnumSet gets stored
     * as either integer or binary bit-array.
     *
     * @return array<string, mixed>
     */
    public function __debugInfo() {
        $dbg = (array)$this;
        $dbg["\0" . self::class . "\0__enumerators"] = $this->getValues();
        return $dbg;
    }

    /**
     * Get the classname of the enumeration
     * @return class-string<T>
     */
    public function getEnumeration(): string
    {
        return $this->enumeration;
    }

    /* write access (mutable) */

    /**
     * Adds an enumerator object or value
     * @param T|null|bool|int|float|string|array<mixed> $enumerator Enumerator object or value
     * @return void
     * @throws InvalidArgumentException On an invalid given enumerator
     */
    public function add($enumerator): void
    {
        $this->{$this->fnDoSetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
    }

    /**
     * Adds all enumerator objects or values of the given iterable
     * @param iterable<T|null|bool|int|float|string|array<mixed>> $enumerators Iterable list of enumerator objects or values
     * @return void
     * @throws InvalidArgumentException On an invalid given enumerator
     */
    public function addIterable(iterable $enumerators): void
    {
        $bitset = $this->bitset;

        try {
            foreach ($enumerators as $enumerator) {
                $this->{$this->fnDoSetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
            }
        } catch (\Throwable $e) {
            // reset all changes until error happened
            $this->bitset = $bitset;
            throw $e;
        }
    }

    /**
     * Removes the given enumerator object or value
     * @param T|null|bool|int|float|string|array<mixed> $enumerator Enumerator object or value
     * @return void
     * @throws InvalidArgumentException On an invalid given enumerator
     */
    public function remove($enumerator): void
    {
        $this->{$this->fnDoUnsetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
    }

    /**
     * Adds an enumerator object or value
     * @param T|null|bool|int|float|string|array<mixed> $enumerator Enumerator object or value
     * @return void
     * @throws InvalidArgumentException On an invalid given enumerator
     * @see add()
     * @see with()
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
     */
    public function attach($enumerator): void
    {
        $this->add($enumerator);
    }

    /**
     * Removes the given enumerator object or value
     * @param T|null|bool|int|float|string|array<mixed> $enumerator Enumerator object or value
     * @return void
     * @throws InvalidArgumentException On an invalid given enumerator
     * @see remove()
     * @see without()
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
     */
    public function detach($enumerator): void
    {
        $this->remove($enumerator);
    }

    /**
     * Removes all enumerator objects or values of the given iterable
     * @param iterable<T|null|bool|int|float|string|array<mixed>> $enumerators Iterable list of enumerator objects or values
     * @return void
     * @throws InvalidArgumentException On an invalid given enumerator
     */
    public function removeIterable(iterable $enumerators): void
    {
        $bitset = $this->bitset;

        try {
            foreach ($enumerators as $enumerator) {
                $this->{$this->fnDoUnsetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
            }
        } catch (\Throwable $e) {
            // reset all changes until error happened
            $this->bitset = $bitset;
            throw $e;
        }
    }

    /**
     * Modify this set from both this and other (this | other)
     *
     * @param EnumSet<T> $other EnumSet of the same enumeration to produce the union
     * @return void
     * @throws InvalidArgumentException If $other doesn't match the enumeration
     */
    public function setUnion(EnumSet $other): void
    {
        if ($this->enumeration !== $other->enumeration) {
            throw new InvalidArgumentException(\sprintf(
                'Other should be of the same enumeration as this %s',
                $this->enumeration
            ));
        }

        $this->bitset = $this->bitset | $other->bitset;
    }

    /**
     * Modify this set with enumerators common to both this and other (this & other)
     *
     * @param EnumSet<T> $other EnumSet of the same enumeration to produce the intersect
     * @return void
     * @throws InvalidArgumentException If $other doesn't match the enumeration
     */
    public function setIntersect(EnumSet $other): void
    {
        if ($this->enumeration !== $other->enumeration) {
            throw new InvalidArgumentException(\sprintf(
                'Other should be of the same enumeration as this %s',
                $this->enumeration
            ));
        }

        $this->bitset = $this->bitset & $other->bitset;
    }

    /**
     * Modify this set with enumerators in this but not in other (this - other)
     *
     * @param EnumSet<T> $other EnumSet of the same enumeration to produce the diff
     * @return void
     * @throws InvalidArgumentException If $other doesn't match the enumeration
     */
    public function setDiff(EnumSet $other): void
    {
        if ($this->enumeration !== $other->enumeration) {
            throw new InvalidArgumentException(\sprintf(
                'Other should be of the same enumeration as this %s',
                $this->enumeration
            ));
        }

        $this->bitset = $this->bitset & ~$other->bitset;
    }

    /**
     * Modify this set with enumerators in either this and other but not in both (this ^ other)
     *
     * @param EnumSet<T> $other EnumSet of the same enumeration to produce the symmetric difference
     * @return void
     * @throws InvalidArgumentException If $other doesn't match the enumeration
     */
    public function setSymDiff(EnumSet $other): void
    {
        if ($this->enumeration !== $other->enumeration) {
            throw new InvalidArgumentException(\sprintf(
                'Other should be of the same enumeration as this %s',
                $this->enumeration
            ));
        }

        $this->bitset = $this->bitset ^ $other->bitset;
    }

    /**
     * Set the given binary bitset in little-endian order
     *
     * @param string $bitset
     * @return void
     * @throws InvalidArgumentException On out-of-range bits given as input bitset
     * @uses doSetBinaryBitsetLeBin()
     * @uses doSetBinaryBitsetLeInt()
     */
    public function setBinaryBitsetLe(string $bitset): void
    {
        $this->{$this->fnDoSetBinaryBitsetLe}($bitset);
    }

    /**
     * Set binary bitset in little-endian order
     *
     * @param string $bitset
     * @return void
     * @throws InvalidArgumentException On out-of-range bits given as input bitset
     * @see setBinaryBitsetLeBin()
     * @see doSetBinaryBitsetLeInt()
     */
    private function doSetBinaryBitsetLeBin($bitset): void
    {
        /** @var string $thisBitset */
        $thisBitset = $this->bitset;

        $size   = \strlen($thisBitset);
        $sizeIn = \strlen($bitset);

        if ($sizeIn < $size) {
            // add "\0" if the given bitset is not long enough
            $bitset .= \str_repeat("\0", $size - $sizeIn);
        } elseif ($sizeIn > $size) {
            if (\ltrim(\substr($bitset, $size), "\0") !== '') {
                throw new InvalidArgumentException('out-of-range bits detected');
            }
            $bitset = \substr($bitset, 0, $size);
        }

        // truncate out-of-range bits of last byte
        $lastByteMaxOrd = $this->enumerationCount % 8;
        if ($lastByteMaxOrd !== 0) {
            $lastByte         = $bitset[-1];
            $lastByteExpected = \chr((1 << $lastByteMaxOrd) - 1) & $lastByte;
            if ($lastByte !== $lastByteExpected) {
                throw new InvalidArgumentException('out-of-range bits detected');
            }

            $this->bitset = \substr($bitset, 0, -1) . $lastByteExpected;
        }

        $this->bitset = $bitset;
    }

    /**
     * Set binary bitset in little-endian order
     *
     * @param string $bitset
     * @return void
     * @throws InvalidArgumentException On out-of-range bits given as input bitset
     * @see setBinaryBitsetLeBin()
     * @see doSetBinaryBitsetLeBin()
     */
    private function doSetBinaryBitsetLeInt($bitset): void
    {
        $len = \strlen($bitset);
        $int = 0;
        for ($i = 0; $i < $len; ++$i) {
            $ord = \ord($bitset[$i]);

            if ($ord && $i > \PHP_INT_SIZE - 1) {
                throw new InvalidArgumentException('out-of-range bits detected');
            }

            $int |= $ord << (8 * $i);
        }

        if ($int & (~0 << $this->enumerationCount)) {
            throw new InvalidArgumentException('out-of-range bits detected');
        }

        $this->bitset = $int;
    }

    /**
     * Set the given binary bitset in big-endian order
     *
     * @param string $bitset
     * @return void
     * @throws InvalidArgumentException On out-of-range bits given as input bitset
     */
    public function setBinaryBitsetBe(string $bitset): void
    {
        $this->{$this->fnDoSetBinaryBitsetLe}(\strrev($bitset));
    }

    /**
     * Set a bit at the given ordinal number
     *
     * @param int $ordinal Ordinal number of bit to set
     * @param bool $bit    The bit to set
     * @return void
     * @throws InvalidArgumentException If the given ordinal number is out-of-range
     * @uses doSetBitBin()
     * @uses doSetBitInt()
     * @uses doUnsetBitBin()
     * @uses doUnsetBitInt()
     */
    public function setBit(int $ordinal, bool $bit): void
    {
        if ($ordinal < 0 || $ordinal > $this->enumerationCount) {
            throw new InvalidArgumentException("Ordinal number must be between 0 and {$this->enumerationCount}");
        }

        if ($bit) {
            $this->{$this->fnDoSetBit}($ordinal);
        } else {
            $this->{$this->fnDoUnsetBit}($ordinal);
        }
    }

    /**
     * Set a bit at the given ordinal number.
     *
     * This is the binary bitset implementation.
     *
     * @param int $ordinal Ordinal number of bit to set
     * @return void
     * @see setBit()
     * @see doSetBitInt()
     */
    private function doSetBitBin($ordinal): void
    {
        /** @var string $thisBitset */
        $thisBitset = $this->bitset;

        $byte = (int) ($ordinal / 8);
        $thisBitset[$byte] = $thisBitset[$byte] | \chr(1 << ($ordinal % 8));

        $this->bitset = $thisBitset;
    }

    /**
     * Set a bit at the given ordinal number.
     *
     * This is the binary bitset implementation.
     *
     * @param int $ordinal Ordinal number of bit to set
     * @return void
     * @see setBit()
     * @see doSetBitBin()
     */
    private function doSetBitInt($ordinal): void
    {
        /** @var int $thisBitset */
        $thisBitset = $this->bitset;

        $this->bitset = $thisBitset | (1 << $ordinal);
    }

    /**
     * Unset a bit at the given ordinal number.
     *
     * This is the binary bitset implementation.
     *
     * @param int $ordinal Ordinal number of bit to unset
     * @return void
     * @see setBit()
     * @see doUnsetBitInt()
     */
    private function doUnsetBitBin($ordinal): void
    {
        /** @var string $thisBitset */
        $thisBitset = $this->bitset;

        $byte = (int) ($ordinal / 8);
        $thisBitset[$byte] = $thisBitset[$byte] & \chr(~(1 << ($ordinal % 8)));

        $this->bitset = $thisBitset;
    }

    /**
     * Unset a bit at the given ordinal number.
     *
     * This is the integer bitset implementation.
     *
     * @param int $ordinal Ordinal number of bit to unset
     * @return void
     * @see setBit()
     * @see doUnsetBitBin()
     */
    private function doUnsetBitInt($ordinal): void
    {
        /** @var int $thisBitset */
        $thisBitset = $this->bitset;

        $this->bitset = $thisBitset & ~(1 << $ordinal);
    }

    /* write access (immutable) */

    /**
     * Creates a new set with the given enumerator object or value added
     * @param T|null|bool|int|float|string|array<mixed> $enumerator Enumerator object or value
     * @return static
     * @throws InvalidArgumentException On an invalid given enumerator
     */
    public function with($enumerator): self
    {
        $clone = clone $this;
        $clone->{$this->fnDoSetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
        return $clone;
    }

    /**
     * Creates a new set with the given enumeration objects or values added
     * @param iterable<T|null|bool|int|float|string|array<mixed>> $enumerators Iterable list of enumerator objects or values
     * @return static
     * @throws InvalidArgumentException On an invalid given enumerator
     */
    public function withIterable(iterable $enumerators): self
    {
        $clone = clone $this;
        foreach ($enumerators as $enumerator) {
            $clone->{$this->fnDoSetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
        }
        return $clone;
    }

    /**
     * Create a new set with the given enumerator object or value removed
     * @param T|null|bool|int|float|string|array<mixed> $enumerator Enumerator object or value
     * @return static
     * @throws InvalidArgumentException On an invalid given enumerator
     */
    public function without($enumerator): self
    {
        $clone = clone $this;
        $clone->{$this->fnDoUnsetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
        return $clone;
    }

    /**
     * Creates a new set with the given enumeration objects or values removed
     * @param iterable<T|null|bool|int|float|string|array<mixed>> $enumerators Iterable list of enumerator objects or values
     * @return static
     * @throws InvalidArgumentException On an invalid given enumerator
     */
    public function withoutIterable(iterable $enumerators): self
    {
        $clone = clone $this;
        foreach ($enumerators as $enumerator) {
            $clone->{$this->fnDoUnsetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
        }
        return $clone;
    }

    /**
     * Create a new set with enumerators from both this and other (this | other)
     *
     * @param EnumSet<T> $other EnumSet of the same enumeration to produce the union
     * @return static
     * @throws InvalidArgumentException If $other doesn't match the enumeration
     */
    public function withUnion(EnumSet $other): self
    {
        $clone = clone $this;
        $clone->setUnion($other);
        return $clone;
    }

    /**
     * Create a new set with enumerators from both this and other (this | other)
     *
     * @param EnumSet<T> $other EnumSet of the same enumeration to produce the union
     * @return static
     * @throws InvalidArgumentException If $other doesn't match the enumeration
     * @see withUnion()
     * @see setUnion()
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
     */
    public function union(EnumSet $other): self
    {
        return $this->withUnion($other);
    }

    /**
     * Create a new set with enumerators common to both this and other (this & other)
     *
     * @param EnumSet<T> $other EnumSet of the same enumeration to produce the intersect
     * @return static
     * @throws InvalidArgumentException If $other doesn't match the enumeration
     */
    public function withIntersect(EnumSet $other): self
    {
        $clone = clone $this;
        $clone->setIntersect($other);
        return $clone;
    }

    /**
     * Create a new set with enumerators common to both this and other (this & other)
     *
     * @param EnumSet<T> $other EnumSet of the same enumeration to produce the intersect
     * @return static
     * @throws InvalidArgumentException If $other doesn't match the enumeration
     * @see withIntersect()
     * @see setIntersect()
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
     */
    public function intersect(EnumSet $other): self
    {
        return $this->withIntersect($other);
    }

    /**
     * Create a new set with enumerators in this but not in other (this - other)
     *
     * @param EnumSet<T> $other EnumSet of the same enumeration to produce the diff
     * @return static
     * @throws InvalidArgumentException If $other doesn't match the enumeration
     */
    public function withDiff(EnumSet $other): self
    {
        $clone = clone $this;
        $clone->setDiff($other);
        return $clone;
    }

    /**
     * Create a new set with enumerators in this but not in other (this - other)
     *
     * @param EnumSet<T> $other EnumSet of the same enumeration to produce the diff
     * @return static
     * @throws InvalidArgumentException If $other doesn't match the enumeration
     * @see withDiff()
     * @see setDiff()
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
     */
    public function diff(EnumSet $other): self
    {
        return $this->withDiff($other);
    }

    /**
     * Create a new set with enumerators in either this and other but not in both (this ^ other)
     *
     * @param EnumSet<T> $other EnumSet of the same enumeration to produce the symmetric difference
     * @return static
     * @throws InvalidArgumentException If $other doesn't match the enumeration
     */
    public function withSymDiff(EnumSet $other): self
    {
        $clone = clone $this;
        $clone->setSymDiff($other);
        return $clone;
    }

    /**
     * Create a new set with enumerators in either this and other but not in both (this ^ other)
     *
     * @param EnumSet<T> $other EnumSet of the same enumeration to produce the symmetric difference
     * @return static
     * @throws InvalidArgumentException If $other doesn't match the enumeration
     * @see withSymDiff()
     * @see setSymDiff()
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
     */
    public function symDiff(EnumSet $other): self
    {
        return $this->withSymDiff($other);
    }

    /**
     * Create a new set with the given binary bitset in little-endian order
     *
     * @param string $bitset
     * @return static
     * @throws InvalidArgumentException On out-of-range bits given as input bitset
     * @uses doSetBinaryBitsetLeBin()
     * @uses doSetBinaryBitsetLeInt()
     */
    public function withBinaryBitsetLe(string $bitset): self
    {
        $clone = clone $this;
        $clone->{$this->fnDoSetBinaryBitsetLe}($bitset);
        return $clone;
    }

    /**
     * Create a new set with the given binary bitset in big-endian order
     *
     * @param string $bitset
     * @return static
     * @throws InvalidArgumentException On out-of-range bits given as input bitset
     */
    public function withBinaryBitsetBe(string $bitset): self
    {
        $clone = $this;
        $clone->{$this->fnDoSetBinaryBitsetLe}(\strrev($bitset));
        return $clone;
    }

    /**
     * Create a new set with the bit at the given ordinal number set
     *
     * @param int $ordinal Ordinal number of bit to set
     * @param bool $bit    The bit to set
     * @return static
     * @throws InvalidArgumentException If the given ordinal number is out-of-range
     * @uses doSetBitBin()
     * @uses doSetBitInt()
     * @uses doUnsetBitBin()
     * @uses doUnsetBitInt()
     */
    public function withBit(int $ordinal, bool $bit): self
    {
        $clone = clone $this;
        $clone->setBit($ordinal, $bit);
        return $clone;
    }

    /* read access */

    /**
     * Test if the given enumerator exists
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
     * @return bool
     */
    public function has($enumerator): bool
    {
        return $this->{$this->fnDoGetBit}(($this->enumeration)::get($enumerator)->getOrdinal());
    }

    /**
     * Test if the given enumerator exists
     * @param T|null|bool|int|float|string|array<mixed> $enumerator
     * @return bool
     * @see has()
     * @deprecated Will trigger deprecation warning in last 4.x and removed in 5.x
     */
    public function contains($enumerator): bool
    {
        return $this->has($enumerator);
    }

    /* IteratorAggregate */

    /**
     * Get a new iterator
     * @return Iterator<int, T>
     * @uses doGetIteratorInt()
     * @uses doGetIteratorBin()
     */
    public function getIterator(): Iterator
    {
        return $this->{$this->fnDoGetIterator}();
    }

    /**
     * Get a new Iterator.
     *
     * This is the binary bitset implementation.
     *
     * @return Iterator<int, T>
     * @see getIterator()
     * @see goGetIteratorInt()
     */
    private function doGetIteratorBin()
    {
        /** @var string $bitset */
        $bitset   = $this->bitset;
        $byteLen  = \strlen($bitset);
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
            if ($bitset[$bytePos] === "\0") {
                // fast skip null byte
                continue;
            }

            $ord = \ord($bitset[$bytePos]);
            for ($bitPos = 0; $bitPos < 8; ++$bitPos) {
                if ($ord & (1 << $bitPos)) {
                    $ordinal = $bytePos * 8 + $bitPos;
                    yield $ordinal => ($this->enumeration)::byOrdinal($ordinal);
                }
            }
        }
    }

    /**
     * Get a new Iterator.
     *
     * This is the integer bitset implementation.
     *
     * @return Iterator<int, T>
     * @see getIterator()
     * @see doGetIteratorBin()
     */
    private function doGetIteratorInt()
    {
        /** @var int $bitset */
        $bitset = $this->bitset;
        $count  = $this->enumerationCount;
        for ($ordinal = 0; $ordinal < $count; ++$ordinal) {
            if ($bitset & (1 << $ordinal)) {
                yield $ordinal => ($this->enumeration)::byOrdinal($ordinal);
            }
        }
    }

    /* Countable */

    /**
     * Count the number of elements
     *
     * @return int
     * @uses doCountBin()
     * @uses doCountInt()
     */
    public function count(): int
    {
        return $this->{$this->fnDoCount}();
    }

    /**
     * Count the number of elements.
     *
     * This is the binary bitset implementation.
     *
     * @return int
     * @see count()
     * @see doCountInt()
     */
    private function doCountBin()
    {
        /** @var string $bitset */
        $bitset  = $this->bitset;
        $count   = 0;
        $byteLen = \strlen($bitset);
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
            if ($bitset[$bytePos] === "\0") {
                // fast skip null byte
                continue;
            }

            $ord = \ord($bitset[$bytePos]);
            if ($ord & 0b00000001) ++$count;
            if ($ord & 0b00000010) ++$count;
            if ($ord & 0b00000100) ++$count;
            if ($ord & 0b00001000) ++$count;
            if ($ord & 0b00010000) ++$count;
            if ($ord & 0b00100000) ++$count;
            if ($ord & 0b01000000) ++$count;
            if ($ord & 0b10000000) ++$count;
        }
        return $count;
    }

    /**
     * Count the number of elements.
     *
     * This is the integer bitset implementation.
     *
     * @return int
     * @see count()
     * @see doCountBin()
     */
    private function doCountInt()
    {
        /** @var int $bitset */
        $bitset = $this->bitset;
        $count  = 0;

        // PHP does not support right shift unsigned
        if ($bitset < 0) {
            $count  = 1;
            $bitset = $bitset & \PHP_INT_MAX;
        }

        // iterate byte by byte and count set bits
        $phpIntBitSize = \PHP_INT_SIZE * 8;
        for ($bitPos = 0; $bitPos < $phpIntBitSize; $bitPos += 8) {
            $bitChk = 0xff << $bitPos;
            $byte = $bitset & $bitChk;
            if ($byte) {
                $byte = $byte >> $bitPos;
                if ($byte & 0b00000001) ++$count;
                if ($byte & 0b00000010) ++$count;
                if ($byte & 0b00000100) ++$count;
                if ($byte & 0b00001000) ++$count;
                if ($byte & 0b00010000) ++$count;
                if ($byte & 0b00100000) ++$count;
                if ($byte & 0b01000000) ++$count;
                if ($byte & 0b10000000) ++$count;
            }

            if ($bitset <= $bitChk) {
                break;
            }
        }

        return $count;
    }

    /**
     * Check if this EnumSet is the same as other
     * @param EnumSet<T> $other
     * @return bool
     */
    public function isEqual(EnumSet $other): bool
    {
        return $this->enumeration === $other->enumeration
            && $this->bitset === $other->bitset;
    }

    /**
     * Check if this EnumSet is a subset of other
     * @param EnumSet<T> $other
     * @return bool
     */
    public function isSubset(EnumSet $other): bool
    {
        return $this->enumeration === $other->enumeration
            && ($this->bitset & $other->bitset) === $this->bitset;
    }

    /**
     * Check if this EnumSet is a superset of other
     * @param EnumSet<T> $other
     * @return bool
     */
    public function isSuperset(EnumSet $other): bool
    {
        return $this->enumeration === $other->enumeration
            && ($this->bitset | $other->bitset) === $this->bitset;
    }

    /**
     * Tests if the set is empty
     *
     * @return bool
     */
    public function isEmpty(): bool
    {
        return $this->bitset === $this->emptyBitset;
    }

    /**
     * Get ordinal numbers of the defined enumerators as array
     * @return array<int, int>
     * @uses  doGetOrdinalsBin()
     * @uses  doGetOrdinalsInt()
     */
    public function getOrdinals(): array
    {
        return $this->{$this->fnDoGetOrdinals}();
    }

    /**
     * Get ordinal numbers of the defined enumerators as array.
     *
     * This is the binary bitset implementation.
     *
     * @return array<int, int>
     * @see getOrdinals()
     * @see goGetOrdinalsInt()
     */
    private function doGetOrdinalsBin()
    {
        /** @var string $bitset */
        $bitset   = $this->bitset;
        $ordinals = [];
        $byteLen  = \strlen($bitset);
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
            if ($bitset[$bytePos] === "\0") {
                // fast skip null byte
                continue;
            }

            $ord = \ord($bitset[$bytePos]);
            for ($bitPos = 0; $bitPos < 8; ++$bitPos) {
                if ($ord & (1 << $bitPos)) {
                    $ordinals[] = $bytePos * 8 + $bitPos;
                }
            }
        }
        return $ordinals;
    }

    /**
     * Get ordinal numbers of the defined enumerators as array.
     *
     * This is the integer bitset implementation.
     *
     * @return array<int, int>
     * @see getOrdinals()
     * @see doGetOrdinalsBin()
     */
    private function doGetOrdinalsInt()
    {
        /** @var int $bitset */
        $bitset   = $this->bitset;
        $ordinals = [];
        $count    = $this->enumerationCount;
        for ($ordinal = 0; $ordinal < $count; ++$ordinal) {
            if ($bitset & (1 << $ordinal)) {
                $ordinals[] = $ordinal;
            }
        }
        return $ordinals;
    }

    /**
     * Get values of the defined enumerators as array
     * @return (null|bool|int|float|string|array)[]
     *
     * @phpstan-return array<int, null|bool|int|float|string|array<mixed>>
     * @psalm-return list<null|bool|int|float|string|array>
     */
    public function getValues(): array
    {
        $enumeration = $this->enumeration;
        $values      = [];
        foreach ($this->getOrdinals() as $ord) {
            $values[] = $enumeration::byOrdinal($ord)->getValue();
        }
        return $values;
    }

    /**
     * Get names of the defined enumerators as array
     * @return string[]
     *
     * @phpstan-return array<int, string>
     * @psalm-return list<string>
     */
    public function getNames(): array
    {
        $enumeration = $this->enumeration;
        $names       = [];
        foreach ($this->getOrdinals() as $ord) {
            $names[] = $enumeration::byOrdinal($ord)->getName();
        }
        return $names;
    }

    /**
     * Get the defined enumerators as array
     * @return Enum[]
     *
     * @phpstan-return array<int, T>
     * @psalm-return list<T>
     */
    public function getEnumerators(): array
    {
        $enumeration = $this->enumeration;
        $enumerators = [];
        foreach ($this->getOrdinals() as $ord) {
            $enumerators[] = $enumeration::byOrdinal($ord);
        }
        return $enumerators;
    }

    /**
     * Get binary bitset in little-endian order
     *
     * @return string
     * @uses doGetBinaryBitsetLeBin()
     * @uses doGetBinaryBitsetLeInt()
     */
    public function getBinaryBitsetLe(): string
    {
        return $this->{$this->fnDoGetBinaryBitsetLe}();
    }

    /**
     * Get binary bitset in little-endian order.
     *
     * This is the binary bitset implementation.
     *
     * @return string
     * @see getBinaryBitsetLe()
     * @see doGetBinaryBitsetLeInt()
     */
    private function doGetBinaryBitsetLeBin()
    {
        /** @var string $bitset */
        $bitset = $this->bitset;

        return $bitset;
    }

    /**
     * Get binary bitset in little-endian order.
     *
     * This is the integer bitset implementation.
     *
     * @return string
     * @see getBinaryBitsetLe()
     * @see doGetBinaryBitsetLeBin()
     */
    private function doGetBinaryBitsetLeInt()
    {
        $bin = \pack(\PHP_INT_SIZE === 8 ? 'P' : 'V', $this->bitset);
        return \substr($bin, 0, (int)\ceil($this->enumerationCount / 8));
    }

    /**
     * Get binary bitset in big-endian order
     *
     * @return string
     */
    public function getBinaryBitsetBe(): string
    {
        return \strrev($this->getBinaryBitsetLe());
    }

    /**
     * Get a bit at the given ordinal number
     *
     * @param int $ordinal Ordinal number of bit to get
     * @return bool
     * @throws InvalidArgumentException If the given ordinal number is out-of-range
     * @uses doGetBitBin()
     * @uses doGetBitInt()
     */
    public function getBit(int $ordinal): bool
    {
        if ($ordinal < 0 || $ordinal > $this->enumerationCount) {
            throw new InvalidArgumentException("Ordinal number must be between 0 and {$this->enumerationCount}");
        }

        return $this->{$this->fnDoGetBit}($ordinal);
    }

    /**
     * Get a bit at the given ordinal number.
     *
     * This is the binary bitset implementation.
     *
     * @param int $ordinal Ordinal number of bit to get
     * @return bool
     * @see getBit()
     * @see doGetBitInt()
     */
    private function doGetBitBin($ordinal)
    {
        /** @var string $bitset */
        $bitset = $this->bitset;

        return (\ord($bitset[(int) ($ordinal / 8)]) & 1 << ($ordinal % 8)) !== 0;
    }

    /**
     * Get a bit at the given ordinal number.
     *
     * This is the integer bitset implementation.
     *
     * @param int $ordinal Ordinal number of bit to get
     * @return bool
     * @see getBit()
     * @see doGetBitBin()
     */
    private function doGetBitInt($ordinal)
    {
        /** @var int $bitset */
        $bitset = $this->bitset;

        return (bool)($bitset & (1 << $ordinal));
    }
}
<?php

declare(strict_types=1);

namespace MabeEnum;

use RuntimeException;
use LogicException;

/**
 * Trait to make enumerations serializable
 *
 * This trait is a workaround to make enumerations serializable.
 *
 * Please note that this feature breaks singleton behaviour of your enumerations
 * if an enumeration will be unserialized after it was instantiated already.
 *
 * @copyright 2020, Marc Bennewitz
 * @license http://github.com/marc-mabe/php-enum/blob/master/LICENSE.txt New BSD License
 * @link http://github.com/marc-mabe/php-enum for the canonical source repository
 * @link https://github.com/marc-mabe/php-enum/issues/52 for further information about this feature
 */
trait EnumSerializableTrait
{
    /**
     * The method will be defined by MabeEnum\Enum
     * @return null|bool|int|float|string|array<mixed>
     */
    abstract public function getValue();

    /**
     * Returns an array of data to be serialized.
     * This magic method will be called by serialize() in PHP >= 7.4
     *
     * @return array<string, null|bool|int|float|string|array<mixed>>
     */
    public function __serialize(): array
    {
        return ['value' => $this->getValue()];
    }

    /**
     * Receives an array of data to be unserialized on a new instance without constructor.
     * This magic method will be called in PHP >= 7.4 is the data where serialized with PHP >= 7.4.
     *
     * @throws RuntimeException On missing, unknown or invalid value
     * @throws LogicException   On calling this method on an already initialized enumerator
     *
     * @param array<string, mixed> $data
     * @return void
     */
    public function __unserialize(array $data): void
    {
        if (!\array_key_exists('value', $data)) {
            throw new RuntimeException('Missing array key "value"');
        }

        /** @var mixed $value */
        $value     = $data['value'];
        $constants = self::getConstants();
        $name      = \array_search($value, $constants, true);
        if ($name === false) {
            $message = \is_scalar($value)
                ? 'Unknown value ' . \var_export($value, true)
                : 'Invalid value of type ' . (\is_object($value) ? \get_class($value) : \gettype($value));
            throw new RuntimeException($message);
        }

        $class      = static::class;
        $enumerator = $this;
        $closure    = function () use ($class, $name, $value, $enumerator) {
            if ($value !== null && $this->value !== null) {
                throw new LogicException('Do not call this directly - please use unserialize($enum) instead');
            }

            $this->value = $value;

            if (!isset(self::$instances[$class][$name])) {
                self::$instances[$class][$name] = $enumerator;
            }
        };
        $closure->bindTo($this, Enum::class)();
    }

    /**
     * Serialize the value of the enumeration
     * This will be called automatically on `serialize()` if the enumeration implements the `Serializable` interface
     *
     * @return string
     * @deprecated Since PHP 7.4
     */
    public function serialize(): string
    {
        return \serialize($this->getValue());
    }

    /**
     * Unserializes a given serialized value and push it into the current instance
     * This will be called automatically on `unserialize()` if the enumeration implements the `Serializable` interface
     * @param string $serialized
     * @return void
     * @throws RuntimeException On an unknown or invalid value
     * @throws LogicException   On calling this method on an already initialized enumerator
     * @deprecated Since PHP 7.4
     */
    public function unserialize($serialized): void
    {
        $this->__unserialize(['value' => \unserialize($serialized)]);
    }
}
<?php

declare(strict_types=1);

namespace MabeEnum;

use ReflectionClass;
use InvalidArgumentException;
use LogicException;

/**
 * Abstract base enumeration class.
 *
 * @copyright 2020, Marc Bennewitz
 * @license http://github.com/marc-mabe/php-enum/blob/master/LICENSE.txt New BSD License
 * @link http://github.com/marc-mabe/php-enum for the canonical source repository
 *
 * @psalm-immutable
 */
abstract class Enum implements \Stringable
{
    /**
     * The selected enumerator value
     *
     * @var null|bool|int|float|string|array<mixed>
     */
    private $value;

    /**
     * The ordinal number of the enumerator
     *
     * @var null|int
     */
    private $ordinal;

    /**
     * A map of enumerator names and values by enumeration class
     *
     * @var array<class-string<Enum>, array<string, null|bool|int|float|string|array<mixed>>>
     */
    private static $constants = [];

    /**
     * A List of available enumerator names by enumeration class
     *
     * @var array<class-string<Enum>, string[]>
     */
    private static $names = [];

    /**
     * A map of enumerator names and instances by enumeration class
     *
     * @var array<class-string<Enum>, array<string, Enum>>
     */
    private static $instances = [];

    /**
     * Constructor
     *
     * @param null|bool|int|float|string|array<mixed> $value   The value of the enumerator
     * @param int|null                                $ordinal The ordinal number of the enumerator
     */
    final private function __construct($value, $ordinal = null)
    {
        $this->value   = $value;
        $this->ordinal = $ordinal;
    }

    /**
     * Get the name of the enumerator
     *
     * @return string
     * @see getName()
     */
    public function __toString(): string
    {
        return $this->getName();
    }

    /**
     * @throws LogicException Enums are not cloneable
     *                        because instances are implemented as singletons
     */
    final public function __clone()
    {
        throw new LogicException('Enums are not cloneable');
    }

    /**
     * @throws LogicException Serialization is not supported by default in this pseudo-enum implementation
     *
     * @psalm-return never-return
     */
    final public function __sleep()
    {
        throw new LogicException('Serialization is not supported by default in this pseudo-enum implementation');
    }

    /**
     * @throws LogicException Serialization is not supported by default in this pseudo-enum implementation
     *
     * @psalm-return never-return
     */
    final public function __wakeup()
    {
        throw new LogicException('Serialization is not supported by default in this pseudo-enum implementation');
    }

    /**
     * Get the value of the enumerator
     *
     * @return null|bool|int|float|string|array<mixed>
     */
    final public function getValue()
    {
        return $this->value;
    }

    /**
     * Get the name of the enumerator
     *
     * @return string
     *
     * @phpstan-return string
     * @psalm-return non-empty-string
     */
    final public function getName()
    {
        return self::$names[static::class][$this->ordinal ?? $this->getOrdinal()];
    }

    /**
     * Get the ordinal number of the enumerator
     *
     * @return int
     */
    final public function getOrdinal()
    {
        if ($this->ordinal === null) {
            $ordinal   = 0;
            $value     = $this->value;
            $constants = self::$constants[static::class] ?? static::getConstants();
            foreach ($constants as $constValue) {
                if ($value === $constValue) {
                    break;
                }
                ++$ordinal;
            }

            $this->ordinal = $ordinal;
        }

        return $this->ordinal;
    }

    /**
     * Compare this enumerator against another and check if it's the same.
     *
     * @param static|null|bool|int|float|string|array<mixed> $enumerator An enumerator object or value
     * @return bool
     */
    final public function is($enumerator)
    {
        return $this === $enumerator || $this->value === $enumerator

            // The following additional conditions are required only because of the issue of serializable singletons
            || ($enumerator instanceof static
                && \get_class($enumerator) === static::class
                && $enumerator->value === $this->value
            );
    }

    /**
     * Get an enumerator instance of the given enumerator value or instance
     *
     * @param static|null|bool|int|float|string|array<mixed> $enumerator An enumerator object or value
     * @return static
     * @throws InvalidArgumentException On an unknown or invalid value
     * @throws LogicException           On ambiguous constant values
     *
     * @psalm-pure
     */
    final public static function get($enumerator)
    {
        if ($enumerator instanceof static) {
            if (\get_class($enumerator) !== static::class) {
                throw new InvalidArgumentException(sprintf(
                    'Invalid value of type %s for enumeration %s',
                    \get_class($enumerator),
                    static::class
                ));
            }

            return $enumerator;
        }

        return static::byValue($enumerator);
    }

    /**
     * Get an enumerator instance by the given value
     *
     * @param null|bool|int|float|string|array<mixed> $value Enumerator value
     * @return static
     * @throws InvalidArgumentException On an unknown or invalid value
     * @throws LogicException           On ambiguous constant values
     *
     * @psalm-pure
     */
    final public static function byValue($value)
    {
        /** @var mixed $value */

        $constants = self::$constants[static::class] ?? static::getConstants();

        $name = \array_search($value, $constants, true);
        if ($name === false) {
            throw new InvalidArgumentException(sprintf(
                'Unknown value %s for enumeration %s',
                \is_scalar($value)
                    ? \var_export($value, true)
                    : 'of type ' . (\is_object($value) ? \get_class($value) : \gettype($value)),
                static::class
            ));
        }

        /** @var static $instance */
        $instance = self::$instances[static::class][$name]
            ?? self::$instances[static::class][$name] = new static($constants[$name]);

        return $instance;
    }

    /**
     * Get an enumerator instance by the given name
     *
     * @param string $name The name of the enumerator
     * @return static
     * @throws InvalidArgumentException On an invalid or unknown name
     * @throws LogicException           On ambiguous values
     *
     * @psalm-pure
     */
    final public static function byName(string $name)
    {
        if (isset(self::$instances[static::class][$name])) {
            /** @var static $instance */
            $instance = self::$instances[static::class][$name];
            return $instance;
        }

        $const = static::class . "::{$name}";
        if (!\defined($const)) {
            throw new InvalidArgumentException("{$const} not defined");
        }

        assert(
            self::noAmbiguousValues(static::getConstants()),
            'Ambiguous enumerator values detected for ' . static::class
        );

        /** @var array<int|string, mixed>|bool|float|int|string|null $value */
        $value = \constant($const);
        return self::$instances[static::class][$name] = new static($value);
    }

    /**
     * Get an enumeration instance by the given ordinal number
     *
     * @param int $ordinal The ordinal number of the enumerator
     * @return static
     * @throws InvalidArgumentException On an invalid ordinal number
     * @throws LogicException           On ambiguous values
     *
     * @psalm-pure
     */
    final public static function byOrdinal(int $ordinal)
    {
        $constants = self::$constants[static::class] ?? static::getConstants();

        if (!isset(self::$names[static::class][$ordinal])) {
            throw new InvalidArgumentException(\sprintf(
                'Invalid ordinal number %s, must between 0 and %s',
                $ordinal,
                \count(self::$names[static::class]) - 1
            ));
        }

        $name = self::$names[static::class][$ordinal];

        /** @var static $instance */
        $instance = self::$instances[static::class][$name]
            ?? self::$instances[static::class][$name] = new static($constants[$name], $ordinal);

        return $instance;
    }

    /**
     * Get a list of enumerator instances ordered by ordinal number
     *
     * @return static[]
     *
     * @phpstan-return array<int, static>
     * @psalm-return list<static>
     * @psalm-pure
     */
    final public static function getEnumerators()
    {
        if (!isset(self::$names[static::class])) {
            static::getConstants();
        }

        /** @var callable $byNameFn */
        $byNameFn = [static::class, 'byName'];
        return \array_map($byNameFn, self::$names[static::class]);
    }

    /**
     * Get a list of enumerator values ordered by ordinal number
     *
     * @return (null|bool|int|float|string|array)[]
     *
     * @phpstan-return array<int, null|bool|int|float|string|array<int|string, mixed>>
     * @psalm-return list<null|bool|int|float|string|array>
     * @psalm-pure
     */
    final public static function getValues()
    {
        return \array_values(self::$constants[static::class] ?? static::getConstants());
    }

    /**
     * Get a list of enumerator names ordered by ordinal number
     *
     * @return string[]
     *
     * @phpstan-return array<int, string>
     * @psalm-return list<non-empty-string>
     * @psalm-pure
     */
    final public static function getNames()
    {
        if (!isset(self::$names[static::class])) {
            static::getConstants();
        }
        return self::$names[static::class];
    }

    /**
     * Get a list of enumerator ordinal numbers
     *
     * @return int[]
     *
     * @phpstan-return array<int, int>
     * @psalm-return list<int>
     * @psalm-pure
     */
    final public static function getOrdinals()
    {
        $count = \count(self::$constants[static::class] ?? static::getConstants());
        return $count ? \range(0, $count - 1) : [];
    }

    /**
     * Get all available constants of the called class
     *
     * @return (null|bool|int|float|string|array)[]
     * @throws LogicException On ambiguous constant values
     *
     * @phpstan-return array<string, null|bool|int|float|string|array<int|string, mixed>>
     * @psalm-return array<non-empty-string, null|bool|int|float|string|array>
     * @psalm-pure
     */
    final public static function getConstants()
    {
        if (isset(self::$constants[static::class])) {
            return self::$constants[static::class];
        }

        $reflection = new ReflectionClass(static::class);
        $constants  = [];

        do {
            $scopeConstants = [];
            // Enumerators must be defined as public class constants
            foreach ($reflection->getReflectionConstants() as $reflConstant) {
                if ($reflConstant->isPublic()) {
                    $scopeConstants[ $reflConstant->getName() ] = $reflConstant->getValue();
                }
            }

            $constants = $scopeConstants + $constants;
        } while (($reflection = $reflection->getParentClass()) && $reflection->name !== __CLASS__);

        /** @var array<string, null|bool|int|float|string|array<mixed>> $constants */

        assert(
            self::noAmbiguousValues($constants),
            'Ambiguous enumerator values detected for ' . static::class
        );

        self::$names[static::class] = \array_keys($constants);
        return self::$constants[static::class] = $constants;
    }

    /**
     * Test that the given constants does not contain ambiguous values
     * @param array<string, null|bool|int|float|string|array<mixed>> $constants
     * @return bool
     */
    private static function noAmbiguousValues($constants)
    {
        foreach ($constants as $value) {
            $names = \array_keys($constants, $value, true);
            if (\count($names) > 1) {
                return false;
            }
        }

        return true;
    }

    /**
     * Test if the given enumerator is part of this enumeration
     *
     * @param static|null|bool|int|float|string|array<mixed> $enumerator
     * @return bool
     *
     * @psalm-pure
     */
    final public static function has($enumerator)
    {
        if ($enumerator instanceof static) {
            return \get_class($enumerator) === static::class;
        }

        return static::hasValue($enumerator);
    }

    /**
     * Test if the given enumerator value is part of this enumeration
     *
     * @param null|bool|int|float|string|array<mixed> $value
     * @return bool
     *
     * @psalm-pure
     */
    final public static function hasValue($value)
    {
        return \in_array($value, self::$constants[static::class] ?? static::getConstants(), true);
    }

    /**
     * Test if the given enumerator name is part of this enumeration
     *
     * @param string $name
     * @return bool
     *
     * @psalm-pure
     */
    final public static function hasName(string $name)
    {
        return \defined("static::{$name}");
    }

    /**
     * Get an enumerator instance by the given name.
     *
     * This will be called automatically on calling a method
     * with the same name of a defined enumerator.
     *
     * @param string       $method The name of the enumerator (called as method)
     * @param array<mixed> $args   There should be no arguments
     * @return static
     * @throws InvalidArgumentException On an invalid or unknown name
     * @throws LogicException           On ambiguous constant values
     *
     * @psalm-pure
     */
    final public static function __callStatic(string $method, array $args)
    {
        return static::byName($method);
    }
}
<?php

if (\PHP_VERSION_ID < 80000) {
    interface Stringable
    {
        /**
         * @return string
         */
        public function __toString();
    }
}
Copyright (c) 2020, Marc Bennewitz
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 organisation 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 HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

use function preg_match;

/**
 * Find the number of logical CPU cores for Windows leveraging the Get-CimInstance
 * cmdlet, which is a newer version that is recommended over Get-WmiObject.
 */
final class CmiCmdletLogicalFinder extends ProcOpenBasedFinder
{
    private const CPU_CORE_COUNT_REGEX = '/NumberOfLogicalProcessors[\s\n]-+[\s\n]+(?<count>\d+)/';

    protected function getCommand(): string
    {
        return 'Get-CimInstance -ClassName Win32_ComputerSystem | Select-Object -Property NumberOfLogicalProcessors';
    }

    public function toString(): string
    {
        return 'CmiCmdletLogicalFinder';
    }

    protected function countCpuCores(string $process): ?int
    {
        if (0 === preg_match(self::CPU_CORE_COUNT_REGEX, $process, $matches)) {
            return parent::countCpuCores($process);
        }

        /** @phpstan-ignore offsetAccess.notFound */
        $count = $matches['count'];

        return parent::countCpuCores($count);
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

use function preg_match;

/**
 * Find the number of physical CPU cores for Windows.
 *
 * @see https://github.com/paratestphp/paratest/blob/c163539818fd96308ca8dc60f46088461e366ed4/src/Runners/PHPUnit/Options.php#L912-L916
 */
final class CmiCmdletPhysicalFinder extends ProcOpenBasedFinder
{
    private const CPU_CORE_COUNT_REGEX = '/NumberOfCores[\s\n]-+[\s\n]+(?<count>\d+)/';

    protected function getCommand(): string
    {
        return 'Get-CimInstance -ClassName Win32_Processor | Select-Object -Property NumberOfCores';
    }

    public function toString(): string
    {
        return 'CmiCmdletPhysicalFinder';
    }

    protected function countCpuCores(string $process): ?int
    {
        if (0 === preg_match(self::CPU_CORE_COUNT_REGEX, $process, $matches)) {
            return parent::countCpuCores($process);
        }

        /** @phpstan-ignore offsetAccess.notFound */
        $count = $matches['count'];

        return parent::countCpuCores($count);
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

use Fidry\CpuCoreCounter\Executor\ProcessExecutor;
use function sprintf;

/**
 * The number of (logical) cores.
 *
 * @see https://github.com/infection/infection/blob/fbd8c44/src/Resource/Processor/CpuCoresCountProvider.php#L69-L82
 * @see https://unix.stackexchange.com/questions/146051/number-of-processors-in-proc-cpuinfo
 */
final class NProcFinder extends ProcOpenBasedFinder
{
    /**
     * @var bool
     */
    private $all;

    /**
     * @param bool $all If disabled will give the number of cores available for the current process
     *                  only. This is disabled by default as it is known to be "buggy" on virtual
     *                  environments as the virtualization tool, e.g. VMWare, might over-commit
     *                  resources by default.
     */
    public function __construct(
        bool $all = false,
        ?ProcessExecutor $executor = null
    ) {
        parent::__construct($executor);

        $this->all = $all;
    }

    public function toString(): string
    {
        return sprintf(
            'NProcFinder(all=%s)',
            $this->all ? 'true' : 'false'
        );
    }

    protected function getCommand(): string
    {
        return 'nproc'.($this->all ? ' --all' : '');
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

use function file_get_contents;
use function is_file;
use function sprintf;
use function substr_count;
use const PHP_EOL;

/**
 * Find the number of CPU cores looking up at the cpuinfo file which is available
 * on Linux systems and Windows systems with a Linux sub-system.
 *
 * @see https://github.com/paratestphp/paratest/blob/c163539818fd96308ca8dc60f46088461e366ed4/src/Runners/PHPUnit/Options.php#L903-L909
 * @see https://unix.stackexchange.com/questions/146051/number-of-processors-in-proc-cpuinfo
 */
final class CpuInfoFinder implements CpuCoreFinder
{
    private const CPU_INFO_PATH = '/proc/cpuinfo';

    public function diagnose(): string
    {
        if (!is_file(self::CPU_INFO_PATH)) {
            return sprintf(
                'The file "%s" could not be found.',
                self::CPU_INFO_PATH
            );
        }

        $cpuInfo = file_get_contents(self::CPU_INFO_PATH);

        if (false === $cpuInfo) {
            return sprintf(
                'Could not get the content of the file "%s".',
                self::CPU_INFO_PATH
            );
        }

        return sprintf(
            'Found the file "%s" with the content:%s%s%sWill return "%s".',
            self::CPU_INFO_PATH,
            PHP_EOL,
            $cpuInfo,
            PHP_EOL,
            self::countCpuCores($cpuInfo)
        );
    }

    /**
     * @return positive-int|null
     */
    public function find(): ?int
    {
        $cpuInfo = self::getCpuInfo();

        return null === $cpuInfo ? null : self::countCpuCores($cpuInfo);
    }

    public function toString(): string
    {
        return 'CpuInfoFinder';
    }

    private static function getCpuInfo(): ?string
    {
        if (!@is_file(self::CPU_INFO_PATH)) {
            return null;
        }

        $cpuInfo = @file_get_contents(self::CPU_INFO_PATH);

        return false === $cpuInfo
            ? null
            : $cpuInfo;
    }

    /**
     * @internal
     *
     * @return positive-int|null
     */
    public static function countCpuCores(string $cpuInfo): ?int
    {
        $processorCount = substr_count($cpuInfo, 'processor');

        return $processorCount > 0 ? $processorCount : null;
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

use function count;
use function explode;
use function is_array;
use function preg_grep;
use const PHP_EOL;

/**
 * The number of logical cores.
 *
 * @see https://stackoverflow.com/a/23378780/5846754
 */
final class LscpuLogicalFinder extends ProcOpenBasedFinder
{
    public function getCommand(): string
    {
        return 'lscpu -p';
    }

    protected function countCpuCores(string $process): ?int
    {
        $lines = explode(PHP_EOL, $process);
        $actualLines = preg_grep('/^\d+,/', $lines);

        if (!is_array($actualLines)) {
            return null;
        }

        $count = count($actualLines);

        return 0 === $count ? null : $count;
    }

    public function toString(): string
    {
        return 'LscpuLogicalFinder';
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

/**
 * Find the number of physical CPU cores for Linux, BSD and OSX.
 *
 * @see https://github.com/paratestphp/paratest/blob/c163539818fd96308ca8dc60f46088461e366ed4/src/Runners/PHPUnit/Options.php#L903-L909
 * @see https://opensource.apple.com/source/xnu/xnu-792.2.4/libkern/libkern/sysctl.h.auto.html
 */
final class HwPhysicalFinder extends ProcOpenBasedFinder
{
    protected function getCommand(): string
    {
        return 'sysctl -n hw.physicalcpu';
    }

    public function toString(): string
    {
        return 'HwPhysicalFinder';
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

use function implode;
use function sprintf;
use const PHP_OS_FAMILY;

final class OnlyOnOSFamilyFinder implements CpuCoreFinder
{
    /**
     * @var list<string>
     */
    private $skippedOSFamilies;

    /**
     * @var CpuCoreFinder
     */
    private $decoratedFinder;

    /**
     * @param string|list<string> $skippedOSFamilyOrFamilies
     */
    public function __construct(
        $skippedOSFamilyOrFamilies,
        CpuCoreFinder $decoratedFinder
    ) {
        $this->skippedOSFamilies = (array) $skippedOSFamilyOrFamilies;
        $this->decoratedFinder = $decoratedFinder;
    }

    public static function forWindows(CpuCoreFinder $decoratedFinder): self
    {
        return new self(
            'Windows',
            $decoratedFinder
        );
    }

    public static function forBSD(CpuCoreFinder $decoratedFinder): self
    {
        return new self(
            'BSD',
            $decoratedFinder
        );
    }

    public static function forDarwin(CpuCoreFinder $decoratedFinder): self
    {
        return new self(
            'Darwin',
            $decoratedFinder
        );
    }

    public static function forSolaris(CpuCoreFinder $decoratedFinder): self
    {
        return new self(
            'Solaris',
            $decoratedFinder
        );
    }

    public static function forLinux(CpuCoreFinder $decoratedFinder): self
    {
        return new self(
            'Linux',
            $decoratedFinder
        );
    }

    public function diagnose(): string
    {
        return $this->skip()
            ? sprintf(
                'Skipped platform detected ("%s").',
                PHP_OS_FAMILY
            )
            : $this->decoratedFinder->diagnose();
    }

    public function find(): ?int
    {
        return $this->skip()
            ? null
            : $this->decoratedFinder->find();
    }

    public function toString(): string
    {
        return sprintf(
            'OnlyOnOSFamilyFinder(only=(%s),%s)',
            implode(',', $this->skippedOSFamilies),
            $this->decoratedFinder->toString()
        );
    }

    private function skip(): bool
    {
        return !in_array(PHP_OS_FAMILY, $this->skippedOSFamilies, true);
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

use function preg_match;

/**
 * Find the number of physical CPU cores for Windows.
 *
 * @see https://github.com/paratestphp/paratest/blob/c163539818fd96308ca8dc60f46088461e366ed4/src/Runners/PHPUnit/Options.php#L912-L916
 */
final class WmicPhysicalFinder extends ProcOpenBasedFinder
{
    private const CPU_CORE_COUNT_REGEX = '/NumberOfCores[\s\n]+(?<count>\d+)/';

    protected function getCommand(): string
    {
        return 'wmic cpu get NumberOfCores';
    }

    public function toString(): string
    {
        return 'WmicPhysicalFinder';
    }

    protected function countCpuCores(string $process): ?int
    {
        if (0 === preg_match(self::CPU_CORE_COUNT_REGEX, $process, $matches)) {
            return parent::countCpuCores($process);
        }

        /** @phpstan-ignore offsetAccess.notFound */
        $count = $matches['count'];

        return parent::countCpuCores($count);
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

/**
 * Find the number of logical CPU cores for Linux and the likes.
 *
 * @see https://twitter.com/freebsdfrau/status/1052016199452700678?s=20&t=M2pHkRqmmna-UF68lfL2hw
 */
final class _NProcessorFinder extends ProcOpenBasedFinder
{
    protected function getCommand(): string
    {
        return 'getconf _NPROCESSORS_ONLN';
    }

    public function toString(): string
    {
        return '_NProcessorFinder';
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

use Fidry\CpuCoreCounter\Executor\ProcessExecutor;
use Fidry\CpuCoreCounter\Executor\ProcOpenExecutor;
use function filter_var;
use function function_exists;
use function is_int;
use function sprintf;
use function trim;
use const FILTER_VALIDATE_INT;
use const PHP_EOL;

abstract class ProcOpenBasedFinder implements CpuCoreFinder
{
    /**
     * @var ProcessExecutor
     */
    private $executor;

    public function __construct(?ProcessExecutor $executor = null)
    {
        $this->executor = $executor ?? new ProcOpenExecutor();
    }

    public function diagnose(): string
    {
        if (!function_exists('proc_open')) {
            return 'The function "proc_open" is not available.';
        }

        $command = $this->getCommand();
        $output = $this->executor->execute($command);

        if (null === $output) {
            return sprintf(
                'Failed to execute the command "%s".',
                $command
            );
        }

        [$stdout, $stderr] = $output;
        $failed = '' !== trim($stderr);

        return $failed
            ? sprintf(
                'Executed the command "%s" which wrote the following output to the STDERR:%s%s%sWill return "null".',
                $command,
                PHP_EOL,
                $stderr,
                PHP_EOL
            )
            : sprintf(
                'Executed the command "%s" and got the following (STDOUT) output:%s%s%sWill return "%s".',
                $command,
                PHP_EOL,
                $stdout,
                PHP_EOL,
                $this->countCpuCores($stdout) ?? 'null'
            );
    }

    /**
     * @return positive-int|null
     */
    public function find(): ?int
    {
        $output = $this->executor->execute($this->getCommand());

        if (null === $output) {
            return null;
        }

        [$stdout, $stderr] = $output;
        $failed = '' !== trim($stderr);

        return $failed
            ? null
            : $this->countCpuCores($stdout);
    }

    /**
     * @internal
     *
     * @return positive-int|null
     */
    protected function countCpuCores(string $process): ?int
    {
        $cpuCount = filter_var($process, FILTER_VALIDATE_INT);

        return is_int($cpuCount) && $cpuCount > 0 ? $cpuCount : null;
    }

    abstract protected function getCommand(): string;
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

final class FinderRegistry
{
    /**
     * @return list<CpuCoreFinder> List of all the known finders with all their variants.
     */
    public static function getAllVariants(): array
    {
        return [
            new CpuInfoFinder(),
            new DummyCpuCoreFinder(1),
            new HwLogicalFinder(),
            new HwPhysicalFinder(),
            new LscpuLogicalFinder(),
            new LscpuPhysicalFinder(),
            new _NProcessorFinder(),
            new NProcessorFinder(),
            new NProcFinder(true),
            new NProcFinder(false),
            new NullCpuCoreFinder(),
            SkipOnOSFamilyFinder::forWindows(
                new DummyCpuCoreFinder(1)
            ),
            OnlyOnOSFamilyFinder::forWindows(
                new DummyCpuCoreFinder(1)
            ),
            new OnlyInPowerShellFinder(new CmiCmdletLogicalFinder()),
            new OnlyInPowerShellFinder(new CmiCmdletPhysicalFinder()),
            new WindowsRegistryLogicalFinder(),
            new WmicPhysicalFinder(),
            new WmicLogicalFinder(),
        ];
    }

    /**
     * @return list<CpuCoreFinder>
     */
    public static function getDefaultLogicalFinders(): array
    {
        return [
            OnlyOnOSFamilyFinder::forWindows(
                new OnlyInPowerShellFinder(
                    new CmiCmdletLogicalFinder()
                )
            ),
            OnlyOnOSFamilyFinder::forWindows(new WindowsRegistryLogicalFinder()),
            OnlyOnOSFamilyFinder::forWindows(new WmicLogicalFinder()),
            new NProcFinder(),
            new HwLogicalFinder(),
            new _NProcessorFinder(),
            new NProcessorFinder(),
            new LscpuLogicalFinder(),
            new CpuInfoFinder(),
        ];
    }

    /**
     * @return list<CpuCoreFinder>
     */
    public static function getDefaultPhysicalFinders(): array
    {
        return [
            OnlyOnOSFamilyFinder::forWindows(
                new OnlyInPowerShellFinder(
                    new CmiCmdletPhysicalFinder()
                )
            ),
            OnlyOnOSFamilyFinder::forWindows(new WmicPhysicalFinder()),
            new HwPhysicalFinder(),
            new LscpuPhysicalFinder(),
        ];
    }

    private function __construct()
    {
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

/**
 * This finder returns whatever value you gave to it. This is useful for testing.
 */
final class NullCpuCoreFinder implements CpuCoreFinder
{
    public function diagnose(): string
    {
        return 'Will return "null".';
    }

    public function find(): ?int
    {
        return null;
    }

    public function toString(): string
    {
        return 'NullCpuCoreFinder';
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

/**
 * Find the number of logical CPU cores for Linux, BSD and OSX.
 *
 * @see https://github.com/paratestphp/paratest/blob/c163539818fd96308ca8dc60f46088461e366ed4/src/Runners/PHPUnit/Options.php#L903-L909
 * @see https://opensource.apple.com/source/xnu/xnu-792.2.4/libkern/libkern/sysctl.h.auto.html
 */
final class HwLogicalFinder extends ProcOpenBasedFinder
{
    protected function getCommand(): string
    {
        return 'sysctl -n hw.logicalcpu';
    }

    public function toString(): string
    {
        return 'HwLogicalFinder';
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

interface CpuCoreFinder
{
    /**
     * Provides an explanation which may offer some insight as to what the finder
     * will be able to find.
     *
     * This is practical to have an idea of what each finder will find collect
     * information for the unit tests, since integration tests are quite complicated
     * as dependent on complex infrastructures.
     */
    public function diagnose(): string;

    /**
     * Find the number of CPU cores. If it could not find it, returns null. The
     * means used to find the cores are at the implementation discretion.
     *
     * @return positive-int|null
     */
    public function find(): ?int;

    public function toString(): string;
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

use function count;
use function explode;
use function is_array;
use function preg_grep;
use function strtok;
use const PHP_EOL;

/**
 * The number of physical processors.
 *
 * @see https://stackoverflow.com/a/23378780/5846754
 */
final class LscpuPhysicalFinder extends ProcOpenBasedFinder
{
    public function toString(): string
    {
        return 'LscpuPhysicalFinder';
    }

    public function getCommand(): string
    {
        return 'lscpu -p';
    }

    protected function countCpuCores(string $process): ?int
    {
        $lines = explode(PHP_EOL, $process);
        /** @var string[]|false $actualLines */
        $actualLines = preg_grep('/^\d+/', $lines);

        if (!is_array($actualLines)) {
            return null;
        }

        $cores = [];
        foreach ($actualLines as $line) {
            strtok($line, ',');
            $core = strtok(',');

            if (false === $core) {
                continue;
            }

            $cores[$core] = true;
        }
        unset($cores['-']);

        $count = count($cores);

        return 0 === $count ? null : $count;
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

use function implode;
use function in_array;
use function sprintf;

final class SkipOnOSFamilyFinder implements CpuCoreFinder
{
    /**
     * @var list<string>
     */
    private $skippedOSFamilies;

    /**
     * @var CpuCoreFinder
     */
    private $decoratedFinder;

    /**
     * @param string|list<string> $skippedOSFamilyOrFamilies
     */
    public function __construct(
        $skippedOSFamilyOrFamilies,
        CpuCoreFinder $decoratedFinder
    ) {
        $this->skippedOSFamilies = (array) $skippedOSFamilyOrFamilies;
        $this->decoratedFinder = $decoratedFinder;
    }

    public static function forWindows(CpuCoreFinder $decoratedFinder): self
    {
        return new self(
            'Windows',
            $decoratedFinder
        );
    }

    public static function forBSD(CpuCoreFinder $decoratedFinder): self
    {
        return new self(
            'BSD',
            $decoratedFinder
        );
    }

    public static function forDarwin(CpuCoreFinder $decoratedFinder): self
    {
        return new self(
            'Darwin',
            $decoratedFinder
        );
    }

    public static function forSolaris(CpuCoreFinder $decoratedFinder): self
    {
        return new self(
            'Solaris',
            $decoratedFinder
        );
    }

    public static function forLinux(CpuCoreFinder $decoratedFinder): self
    {
        return new self(
            'Linux',
            $decoratedFinder
        );
    }

    public function diagnose(): string
    {
        return $this->skip()
            ? sprintf(
                'Skipped platform detected ("%s").',
                PHP_OS_FAMILY
            )
            : $this->decoratedFinder->diagnose();
    }

    public function find(): ?int
    {
        return $this->skip()
            ? null
            : $this->decoratedFinder->find();
    }

    public function toString(): string
    {
        return sprintf(
            'SkipOnOSFamilyFinder(skip=(%s),%s)',
            implode(',', $this->skippedOSFamilies),
            $this->decoratedFinder->toString()
        );
    }

    private function skip(): bool
    {
        return in_array(PHP_OS_FAMILY, $this->skippedOSFamilies, true);
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

use function getenv;
use function preg_match;
use function sprintf;
use function var_export;

final class EnvVariableFinder implements CpuCoreFinder
{
    /** @var string */
    private $environmentVariableName;

    public function __construct(string $environmentVariableName)
    {
        $this->environmentVariableName = $environmentVariableName;
    }

    public function diagnose(): string
    {
        $value = getenv($this->environmentVariableName);

        return sprintf(
            'parse(getenv(%s)=%s)=%s',
            $this->environmentVariableName,
            var_export($value, true),
            self::isPositiveInteger($value) ? $value : 'null'
        );
    }

    public function find(): ?int
    {
        $value = getenv($this->environmentVariableName);

        if (is_string($value) && 1 === preg_match('/^(\d+)m$/', $value, $matches)) {
            $millicores = $matches[1];
            $value = (string) floor($millicores / 1000);
        }

        return self::isPositiveInteger($value)
            ? (int) $value
            : null;
    }

    public function toString(): string
    {
        return sprintf(
            'getenv(%s)',
            $this->environmentVariableName
        );
    }

    /**
     * @param string|false $value
     */
    private static function isPositiveInteger($value): bool
    {
        return false !== $value
            && 1 === preg_match('/^\d+$/', $value)
            && (int) $value > 0;
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

/**
 * Find the number of logical CPU cores for FreeSBD, Solaris and the likes.
 *
 * @see https://twitter.com/freebsdfrau/status/1052016199452700678?s=20&t=M2pHkRqmmna-UF68lfL2hw
 */
final class NProcessorFinder extends ProcOpenBasedFinder
{
    protected function getCommand(): string
    {
        return 'getconf NPROCESSORS_ONLN';
    }

    public function toString(): string
    {
        return 'NProcessorFinder';
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

use function preg_match;

/**
 * Find the number of logical CPU cores for Windows.
 *
 * @see https://github.com/paratestphp/paratest/blob/c163539818fd96308ca8dc60f46088461e366ed4/src/Runners/PHPUnit/Options.php#L912-L916
 */
final class WmicLogicalFinder extends ProcOpenBasedFinder
{
    private const CPU_CORE_COUNT_REGEX = '/NumberOfLogicalProcessors[\s\n]+(?<count>\d+)/';

    protected function getCommand(): string
    {
        return 'wmic cpu get NumberOfLogicalProcessors';
    }

    public function toString(): string
    {
        return 'WmicLogicalFinder';
    }

    protected function countCpuCores(string $process): ?int
    {
        if (0 === preg_match(self::CPU_CORE_COUNT_REGEX, $process, $matches)) {
            return parent::countCpuCores($process);
        }

        /** @phpstan-ignore offsetAccess.notFound */
        $count = $matches['count'];

        return parent::countCpuCores($count);
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

use function sprintf;

/**
 * This finder returns whatever value you gave to it. This is useful for testing
 * or as a fallback to avoid to catch the NumberOfCpuCoreNotFound exception.
 */
final class DummyCpuCoreFinder implements CpuCoreFinder
{
    /**
     * @var positive-int
     */
    private $count;

    public function diagnose(): string
    {
        return sprintf(
            'Will return "%d".',
            $this->count
        );
    }

    /**
     * @param positive-int $count
     */
    public function __construct(int $count)
    {
        $this->count = $count;
    }

    /** @phpstan-ignore return.unusedType */
    public function find(): ?int
    {
        return $this->count;
    }

    public function toString(): string
    {
        return sprintf(
            'DummyCpuCoreFinder(value=%d)',
            $this->count
        );
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

use function getenv;
use function sprintf;

final class OnlyInPowerShellFinder implements CpuCoreFinder
{
    /**
     * @var CpuCoreFinder
     */
    private $decoratedFinder;

    public function __construct(CpuCoreFinder $decoratedFinder)
    {
        $this->decoratedFinder = $decoratedFinder;
    }

    public function diagnose(): string
    {
        $powerShellModulePath = getenv('PSModulePath');

        return $this->skip()
            ? sprintf(
                'Skipped; no power shell module path detected ("%s").',
                $powerShellModulePath
            )
            : $this->decoratedFinder->diagnose();
    }

    public function find(): ?int
    {
        return $this->skip()
            ? null
            : $this->decoratedFinder->find();
    }

    public function toString(): string
    {
        return sprintf(
            'OnlyInPowerShellFinder(%s)',
            $this->decoratedFinder->toString()
        );
    }

    private function skip(): bool
    {
        return false === getenv('PSModulePath');
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Finder;

use function array_filter;
use function count;
use function explode;
use const PHP_EOL;

/**
 * Find the number of logical CPU cores for Windows.
 *
 * @see https://knowledge.informatica.com/s/article/151521
 */
final class WindowsRegistryLogicalFinder extends ProcOpenBasedFinder
{
    protected function getCommand(): string
    {
        return 'reg query HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\CentralProcessor';
    }

    public function toString(): string
    {
        return 'WindowsRegistryLogicalFinder';
    }

    protected function countCpuCores(string $process): ?int
    {
        $count = count(
            array_filter(
                explode(PHP_EOL, $process),
                static function (string $line): bool {
                    return '' !== trim($line);
                }
            )
        );

        return $count > 0 ? $count : null;
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter;

use RuntimeException;

final class NumberOfCpuCoreNotFound extends RuntimeException
{
    public static function create(): self
    {
        return new self(
            'Could not find the number of CPU cores available.'
        );
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter;

/**
 * @readonly
 */
final class ParallelisationResult
{
    /**
     * @var positive-int|0
     */
    public $passedReservedCpus;

    /**
     * @var non-zero-int|null
     */
    public $passedCountLimit;

    /**
     * @var float|null
     */
    public $passedLoadLimit;

    /**
     * @var float|null
     */
    public $passedSystemLoadAverage;

    /**
     * @var non-zero-int|null
     */
    public $correctedCountLimit;

    /**
     * @var float|null
     */
    public $correctedSystemLoadAverage;

    /**
     * @var positive-int
     */
    public $totalCoresCount;

    /**
     * @var positive-int
     */
    public $availableCpus;

    /**
     * @param positive-int|0    $passedReservedCpus
     * @param non-zero-int|null $passedCountLimit
     * @param non-zero-int|null $correctedCountLimit
     * @param positive-int      $totalCoresCount
     * @param positive-int      $availableCpus
     */
    public function __construct(
        int $passedReservedCpus,
        ?int $passedCountLimit,
        ?float $passedLoadLimit,
        ?float $passedSystemLoadAverage,
        ?int $correctedCountLimit,
        ?float $correctedSystemLoadAverage,
        int $totalCoresCount,
        int $availableCpus
    ) {
        $this->passedReservedCpus = $passedReservedCpus;
        $this->passedCountLimit = $passedCountLimit;
        $this->passedLoadLimit = $passedLoadLimit;
        $this->passedSystemLoadAverage = $passedSystemLoadAverage;
        $this->correctedCountLimit = $correctedCountLimit;
        $this->correctedSystemLoadAverage = $correctedSystemLoadAverage;
        $this->totalCoresCount = $totalCoresCount;
        $this->availableCpus = $availableCpus;
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter;

use Fidry\CpuCoreCounter\Finder\CpuCoreFinder;
use function array_map;
use function explode;
use function implode;
use function max;
use function str_repeat;
use const PHP_EOL;

/**
 * Utility to debug.
 *
 * @private
 */
final class Diagnoser
{
    /**
     * Provides an aggregated diagnosis based on each finders diagnosis.
     *
     * @param list<CpuCoreFinder> $finders
     */
    public static function diagnose(array $finders): string
    {
        $diagnoses = array_map(
            static function (CpuCoreFinder $finder): string {
                return self::diagnoseFinder($finder);
            },
            $finders
        );

        return implode(PHP_EOL, $diagnoses);
    }

    /**
     * Executes each finders.
     *
     * @param list<CpuCoreFinder> $finders
     */
    public static function execute(array $finders): string
    {
        $diagnoses = array_map(
            static function (CpuCoreFinder $finder): string {
                $coresCount = $finder->find();

                return implode(
                    '',
                    [
                        $finder->toString(),
                        ': ',
                        null === $coresCount ? 'NULL' : $coresCount,
                    ]
                );
            },
            $finders
        );

        return implode(PHP_EOL, $diagnoses);
    }

    private static function diagnoseFinder(CpuCoreFinder $finder): string
    {
        $diagnosis = $finder->diagnose();

        $maxLineLength = max(
            array_map(
                'strlen',
                explode(PHP_EOL, $diagnosis)
            )
        );

        $separator = str_repeat('-', $maxLineLength);

        return implode(
            '',
            [
                $finder->toString().':'.PHP_EOL,
                $separator.PHP_EOL,
                $diagnosis.PHP_EOL,
                $separator.PHP_EOL,
            ]
        );
    }

    private function __construct()
    {
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter;

use Fidry\CpuCoreCounter\Finder\CpuCoreFinder;
use Fidry\CpuCoreCounter\Finder\EnvVariableFinder;
use Fidry\CpuCoreCounter\Finder\FinderRegistry;
use InvalidArgumentException;
use function implode;
use function max;
use function sprintf;
use function sys_getloadavg;
use const PHP_EOL;

final class CpuCoreCounter
{
    /**
     * @var list<CpuCoreFinder>
     */
    private $finders;

    /**
     * @var positive-int|null
     */
    private $count;

    /**
     * @param list<CpuCoreFinder>|null $finders
     */
    public function __construct(?array $finders = null)
    {
        $this->finders = $finders ?? FinderRegistry::getDefaultLogicalFinders();
    }

    /**
     * @param positive-int|0    $reservedCpus      Number of CPUs to reserve. This is useful when you want
     *                                             to reserve some CPUs for other processes. If the main
     *                                             process is going to be busy still, you may want to set
     *                                             this value to 1.
     * @param non-zero-int|null $countLimit        The maximum number of CPUs to return. If not provided, it
     *                                             may look for a limit in the environment variables, e.g.
     *                                             KUBERNETES_CPU_LIMIT. If negative, the limit will be
     *                                             the total number of cores found minus the absolute value.
     *                                             For instance if the system has 10 cores and countLimit=-2,
     *                                             then the effective limit considered will be 8.
     * @param float|null        $loadLimit         Element of [0., 1.]. Percentage representing the
     *                                             amount of cores that should be used among the available
     *                                             resources. For instance, if set to 0.7, it will use 70%
     *                                             of the available cores, i.e. if 1 core is reserved, 11
     *                                             cores are available and 5 are busy, it will use 70%
     *                                             of (11-1-5)=5 cores, so 3 cores. Set this parameter to null
     *                                             to skip this check. Beware that 1 does not mean "no limit",
     *                                             but 100% of the _available_ resources, i.e. with the
     *                                             previous example, it will return 5 cores. How busy is
     *                                             the system is determined by the system load average
     *                                             (see $systemLoadAverage).
     * @param float|null        $systemLoadAverage The system load average. If passed, it will use
     *                                             this information to limit the available cores based
     *                                             on the _available_ resources. For instance, if there
     *                                             is 10 cores but 3 are busy, then only 7 cores will
     *                                             be considered for further calculation. If set to
     *                                             `null`, it will use `sys_getloadavg()` to check the
     *                                             load of the system in the past minute. You can
     *                                             otherwise pass an arbitrary value. Should be a
     *                                             positive float.
     *
     * @see https://php.net/manual/en/function.sys-getloadavg.php
     */
    public function getAvailableForParallelisation(
        int $reservedCpus = 0,
        ?int $countLimit = null,
        ?float $loadLimit = null,
        ?float $systemLoadAverage = 0.
    ): ParallelisationResult {
        self::checkCountLimit($countLimit);
        self::checkLoadLimit($loadLimit);
        self::checkSystemLoadAverage($systemLoadAverage);

        $totalCoreCount = $this->getCountWithFallback(1);
        $availableCores = max(1, $totalCoreCount - $reservedCpus);

        // Adjust available CPUs based on current load
        if (null !== $loadLimit) {
            // https://github.com/phpstan/phpstan/issues/13198
            /** @var float $correctedSystemLoadAverage */
            $correctedSystemLoadAverage = null === $systemLoadAverage
                ? sys_getloadavg()[0] ?? 0.
                : $systemLoadAverage;

            $availableCores = max(
                1,
                $loadLimit * ($availableCores - $correctedSystemLoadAverage)
            );
        }

        if (null === $countLimit) {
            $correctedCountLimit = self::getKubernetesLimit();
        } else {
            $correctedCountLimit = $countLimit > 0
                ? $countLimit
                : max(1, $totalCoreCount + $countLimit);
        }

        if (null !== $correctedCountLimit && $availableCores > $correctedCountLimit) {
            $availableCores = $correctedCountLimit;
        }

        return new ParallelisationResult(
            $reservedCpus,
            $countLimit,
            $loadLimit,
            $systemLoadAverage,
            $correctedCountLimit,
            $correctedSystemLoadAverage ?? $systemLoadAverage,
            $totalCoreCount,
            (int) $availableCores
        );
    }

    /**
     * @throws NumberOfCpuCoreNotFound
     *
     * @return positive-int
     */
    public function getCount(): int
    {
        // Memoize result
        if (null === $this->count) {
            $this->count = $this->findCount();
        }

        return $this->count;
    }

    /**
     * @param positive-int $fallback
     *
     * @return positive-int
     */
    public function getCountWithFallback(int $fallback): int
    {
        try {
            return $this->getCount();
        } catch (NumberOfCpuCoreNotFound $exception) {
            return $fallback;
        }
    }

    /**
     * This method is mostly for debugging purposes.
     */
    public function trace(): string
    {
        $output = [];

        foreach ($this->finders as $finder) {
            $output[] = sprintf(
                'Executing the finder "%s":',
                $finder->toString()
            );
            $output[] = $finder->diagnose();

            $cores = $finder->find();

            if (null !== $cores) {
                $output[] = 'Result found: '.$cores;

                break;
            }

            $output[] = '–––';
        }

        return implode(PHP_EOL, $output);
    }

    /**
     * @throws NumberOfCpuCoreNotFound
     *
     * @return positive-int
     */
    private function findCount(): int
    {
        foreach ($this->finders as $finder) {
            $cores = $finder->find();

            if (null !== $cores) {
                return $cores;
            }
        }

        throw NumberOfCpuCoreNotFound::create();
    }

    /**
     * @throws NumberOfCpuCoreNotFound
     *
     * @return array{CpuCoreFinder, positive-int}
     */
    public function getFinderAndCores(): array
    {
        foreach ($this->finders as $finder) {
            $cores = $finder->find();

            if (null !== $cores) {
                return [$finder, $cores];
            }
        }

        throw NumberOfCpuCoreNotFound::create();
    }

    /**
     * @return positive-int|null
     */
    public static function getKubernetesLimit(): ?int
    {
        $finder = new EnvVariableFinder('KUBERNETES_CPU_LIMIT');

        return $finder->find();
    }

    private static function checkCountLimit(?int $countLimit): void
    {
        if (0 === $countLimit) {
            throw new InvalidArgumentException(
                'The count limit must be a non zero integer. Got "0".'
            );
        }
    }

    private static function checkLoadLimit(?float $loadLimit): void
    {
        if (null === $loadLimit) {
            return;
        }

        if ($loadLimit < 0. || $loadLimit > 1.) {
            throw new InvalidArgumentException(
                sprintf(
                    'The load limit must be in the range [0., 1.], got "%s".',
                    $loadLimit
                )
            );
        }
    }

    private static function checkSystemLoadAverage(?float $systemLoadAverage): void
    {
        if (null !== $systemLoadAverage && $systemLoadAverage < 0.) {
            throw new InvalidArgumentException(
                sprintf(
                    'The system load average must be a positive float, got "%s".',
                    $systemLoadAverage
                )
            );
        }
    }
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Executor;

interface ProcessExecutor
{
    /**
     * @return array{string, string}|null STDOUT & STDERR tuple
     */
    public function execute(string $command): ?array;
}
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

namespace Fidry\CpuCoreCounter\Executor;

use function fclose;
use function function_exists;
use function is_resource;
use function proc_close;
use function proc_open;
use function stream_get_contents;

final class ProcOpenExecutor implements ProcessExecutor
{
    public function execute(string $command): ?array
    {
        if (!function_exists('proc_open')) {
            return null;
        }

        $pipes = [];

        $process = @proc_open(
            $command,
            [
                ['pipe', 'rb'],
                ['pipe', 'wb'], // stdout
                ['pipe', 'wb'], // stderr
            ],
            $pipes
        );
        // https://github.com/phpstan/phpstan/issues/13197
        /** @var array{resource, resource, resource} $pipes */
        if (!is_resource($process)) {
            return null;
        }

        fclose($pipes[0]);

        $stdout = stream_get_contents($pipes[1]);
        $stderr = stream_get_contents($pipes[2]);

        proc_close($process);

        return [$stdout, $stderr];
    }
}
#!/usr/bin/env php
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

use Fidry\CpuCoreCounter\CpuCoreCounter;
use Fidry\CpuCoreCounter\Finder\FinderRegistry;

require_once __DIR__.'/../vendor/autoload.php';

$separator = str_repeat('–', 80);

echo 'With all finders...'.PHP_EOL.PHP_EOL;
echo (new CpuCoreCounter(FinderRegistry::getAllVariants()))->trace().PHP_EOL;
echo $separator.PHP_EOL.PHP_EOL;

echo 'Logical CPU cores finders...'.PHP_EOL.PHP_EOL;
echo (new CpuCoreCounter(FinderRegistry::getDefaultLogicalFinders()))->trace().PHP_EOL;
echo $separator.PHP_EOL.PHP_EOL;

echo 'Physical CPU cores finders...'.PHP_EOL.PHP_EOL;
echo (new CpuCoreCounter(FinderRegistry::getDefaultPhysicalFinders()))->trace().PHP_EOL;
echo $separator.PHP_EOL.PHP_EOL;
#!/usr/bin/env php
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

use Fidry\CpuCoreCounter\Diagnoser;
use Fidry\CpuCoreCounter\Finder\FinderRegistry;

require_once __DIR__.'/../vendor/autoload.php';

echo 'Executing finders...'.PHP_EOL.PHP_EOL;
echo Diagnoser::execute(FinderRegistry::getAllVariants()).PHP_EOL;
#!/usr/bin/env php
<?php

/*
 * This file is part of the Fidry CPUCounter Config package.
 *
 * (c) Théo FIDRY <theo.fidry@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

use Fidry\CpuCoreCounter\Diagnoser;
use Fidry\CpuCoreCounter\Finder\FinderRegistry;

require_once __DIR__.'/../vendor/autoload.php';

echo 'Running diagnosis...'.PHP_EOL.PHP_EOL;
echo Diagnoser::diagnose(FinderRegistry::getAllVariants()).PHP_EOL;

echo 'Logical CPU cores finders...'.PHP_EOL.PHP_EOL;
echo Diagnoser::diagnose(FinderRegistry::getDefaultLogicalFinders()).PHP_EOL;

echo 'Physical CPU cores finders...'.PHP_EOL.PHP_EOL;
echo Diagnoser::diagnose(FinderRegistry::getDefaultPhysicalFinders()).PHP_EOL;
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.
<?php

namespace Webmozart\Assert;

use ArrayAccess;
use Closure;
use Countable;
use Throwable;

/**
 * This trait provides nurllOr*, all* and allNullOr* variants of assertion base methods.
 * Do not use this trait directly: it will change, and is not designed for reuse.
 */
trait Mixin
{
    /**
     * @psalm-pure
     * @psalm-assert string|null $value
     *
     * @param mixed  $value
     * @param string $message
     *
     * @throws InvalidArgumentException
     *
     * @return void
     */
    public static function nullOrString($value, $message = '')
    {
        null === $value || static::string($value, $message);
    }

    /**
     * @psalm-pure
     * @psalm-assert iterable<string> $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<string|null> $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<non-empty-string> $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<non-empty-string|null> $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<int> $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<int|null> $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<numeric> $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<numeric|null> $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<positive-int> $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<positive-int|null> $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<float> $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<float|null> $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<numeric> $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<numeric|null> $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<positive-int|0> $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<positive-int|0|null> $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<bool> $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<bool|null> $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<scalar> $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<scalar|null> $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<object> $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<object|null> $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<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
     *
     * @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<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 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<callable> $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<callable|null> $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<array> $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<array|null> $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<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<iterable|null> $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<array|ArrayAccess> $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<array|ArrayAccess|null> $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<countable> $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<countable|null> $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<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<iterable|null> $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<ExpectedType> $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<ExpectedType> $class
     * @psalm-assert iterable<ExpectedType> $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<ExpectedType> $class
     * @psalm-assert iterable<ExpectedType|null> $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<ExpectedType> $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<ExpectedType> $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<ExpectedType> $class
     * @psalm-assert iterable<!ExpectedType|null> $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<class-string> $classes
     *
     * @param mixed                $value
     * @param array<object|string> $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<class-string> $classes
     *
     * @param mixed                $value
     * @param array<object|string> $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<class-string> $classes
     *
     * @param mixed                $value
     * @param array<object|string> $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<ExpectedType> $class
     * @psalm-assert ExpectedType|class-string<ExpectedType>|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<ExpectedType> $class
     * @psalm-assert iterable<ExpectedType|class-string<ExpectedType>> $value
     *
     * @param iterable<object|string> $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<ExpectedType> $class
     * @psalm-assert iterable<ExpectedType|class-string<ExpectedType>|null> $value
     *
     * @param iterable<object|string|null> $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<UnexpectedType> $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<UnexpectedType> $class
     *
     * @param iterable<object|string> $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<UnexpectedType> $class
     * @psalm-assert iterable<!UnexpectedType|null> $value
     * @psalm-assert iterable<!class-string<UnexpectedType>|null> $value
     *
     * @param iterable<object|string|null> $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<class-string> $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<class-string> $classes
     *
     * @param iterable<object|string> $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<class-string> $classes
     *
     * @param iterable<object|string|null> $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<empty> $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<empty|null> $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<!empty|null> $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<null> $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<true> $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<true|null> $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<false> $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<false|null> $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<!false|null> $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<array> $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<array|null> $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<string> $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<string|null> $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<string> $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<string|null> $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<string> $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<string|null> $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<string> $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<string|null> $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<string> $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<string|null> $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<string> $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<string|null> $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<string> $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<string|null> $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<string> $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<string|null> $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<string> $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<string|null> $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<string> $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<string|null> $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<string> $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<string|null> $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<lowercase-string> $value
     *
     * @param iterable<string> $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<lowercase-string|null> $value
     *
     * @param iterable<string|null> $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<string> $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<!lowercase-string|null> $value
     *
     * @param iterable<string|null> $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<string> $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<string|null> $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<string> $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<string|null> $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<string> $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<string|null> $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<string> $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<string|null> $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<string> $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<string|null> $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<string> $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<string|null> $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<class-string> $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<class-string|null> $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<ExpectedType> $class
     * @psalm-assert class-string<ExpectedType>|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<ExpectedType> $class
     * @psalm-assert iterable<class-string<ExpectedType>|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<ExpectedType> $class
     * @psalm-assert iterable<class-string<ExpectedType>|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<class-string> $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<class-string|null> $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<ExpectedType> $interface
     * @psalm-assert class-string<ExpectedType>|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<ExpectedType> $interface
     * @psalm-assert iterable<class-string<ExpectedType>> $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<ExpectedType> $interface
     * @psalm-assert iterable<class-string<ExpectedType>|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<class-string|object> $classOrObject
     *
     * @param iterable<string|object> $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<class-string|object|null> $classOrObject
     *
     * @param iterable<string|object|null> $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<class-string|object> $classOrObject
     *
     * @param iterable<string|object> $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<class-string|object|null> $classOrObject
     *
     * @param iterable<string|object|null> $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<class-string|object> $classOrObject
     *
     * @param iterable<string|object> $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<class-string|object|null> $classOrObject
     *
     * @param iterable<string|object|null> $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<class-string|object> $classOrObject
     *
     * @param iterable<string|object> $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<class-string|object|null> $classOrObject
     *
     * @param iterable<string|object|null> $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> $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|null> $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> $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|null> $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<array-key> $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<array-key|null> $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<Countable|array> $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<Countable|array|null> $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<Countable|array> $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<Countable|array|null> $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<Countable|array> $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<Countable|array|null> $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<Countable|array> $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<Countable|array|null> $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<list> $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<list|null> $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<non-empty-list> $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<non-empty-list|null> $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<T>|null $array
     * @psalm-assert array<string, T>|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<mixed|array<T>> $array
     * @psalm-assert iterable<array<string, T>> $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<mixed|array<T>|null> $array
     * @psalm-assert iterable<array<string, T>|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<T>|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<mixed|array<T>> $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<mixed|array<T>|null> $array
     * @psalm-assert iterable<array<string, T>|null> $array
     * @psalm-assert iterable<!empty|null> $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<string> $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<string|null> $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<Throwable> $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<Throwable> $class
     *
     * @param iterable<Closure> $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<Throwable> $class
     *
     * @param iterable<Closure|null> $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);
        }
    }
}
<?php

/*
 * This file is part of the webmozart/assert package.
 *
 * (c) Bernhard Schussek <bschussek@gmail.com>
 *
 * 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
{
}
<?php

/*
 * This file is part of the webmozart/assert package.
 *
 * (c) Bernhard Schussek <bschussek@gmail.com>
 *
 * 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 <bschussek@gmail.com>
 */
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<ExpectedType> $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<ExpectedType> $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<class-string> $classes
     *
     * @param mixed                $value
     * @param array<object|string> $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<ExpectedType> $class
     * @psalm-assert ExpectedType|class-string<ExpectedType> $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<UnexpectedType> $class
     * @psalm-assert !UnexpectedType $value
     * @psalm-assert !class-string<UnexpectedType> $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<class-string> $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<ExpectedType> $class
     * @psalm-assert class-string<ExpectedType>|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<ExpectedType> $interface
     * @psalm-assert class-string<ExpectedType> $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<T> $array
     * @psalm-assert array<string, T> $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<T> $array
     * @psalm-assert array<string, T> $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<Throwable> $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()
    {
    }
}
<?php

namespace Illuminate\Container;

use Exception;
use Psr\Container\NotFoundExceptionInterface;

class EntryNotFoundException extends Exception implements NotFoundExceptionInterface
{
    //
}
<?php

namespace Illuminate\Container;

use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Container\ContextualBindingBuilder as ContextualBindingBuilderContract;

class ContextualBindingBuilder implements ContextualBindingBuilderContract
{
    /**
     * The underlying container instance.
     *
     * @var \Illuminate\Contracts\Container\Container
     */
    protected $container;

    /**
     * The concrete instance.
     *
     * @var string|array
     */
    protected $concrete;

    /**
     * The abstract target.
     *
     * @var string
     */
    protected $needs;

    /**
     * Create a new contextual binding builder.
     *
     * @param  \Illuminate\Contracts\Container\Container  $container
     * @param  string|array  $concrete
     * @return void
     */
    public function __construct(Container $container, $concrete)
    {
        $this->concrete = $concrete;
        $this->container = $container;
    }

    /**
     * Define the abstract target that depends on the context.
     *
     * @param  string  $abstract
     * @return $this
     */
    public function needs($abstract)
    {
        $this->needs = $abstract;

        return $this;
    }

    /**
     * Define the implementation for the contextual binding.
     *
     * @param  \Closure|string|array  $implementation
     * @return void
     */
    public function give($implementation)
    {
        foreach (Util::arrayWrap($this->concrete) as $concrete) {
            $this->container->addContextualBinding($concrete, $this->needs, $implementation);
        }
    }

    /**
     * Define tagged services to be used as the implementation for the contextual binding.
     *
     * @param  string  $tag
     * @return void
     */
    public function giveTagged($tag)
    {
        $this->give(function ($container) use ($tag) {
            $taggedServices = $container->tagged($tag);

            return is_array($taggedServices) ? $taggedServices : iterator_to_array($taggedServices);
        });
    }

    /**
     * Specify the configuration item to bind as a primitive.
     *
     * @param  string  $key
     * @param  mixed  $default
     * @return void
     */
    public function giveConfig($key, $default = null)
    {
        $this->give(fn ($container) => $container->get('config')->get($key, $default));
    }
}
<?php

namespace Illuminate\Container;

use Closure;
use ReflectionNamedType;

/**
 * @internal
 */
class Util
{
    /**
     * If the given value is not an array and not null, wrap it in one.
     *
     * From Arr::wrap() in Illuminate\Support.
     *
     * @param  mixed  $value
     * @return array
     */
    public static function arrayWrap($value)
    {
        if (is_null($value)) {
            return [];
        }

        return is_array($value) ? $value : [$value];
    }

    /**
     * Return the default value of the given value.
     *
     * From global value() helper in Illuminate\Support.
     *
     * @param  mixed  $value
     * @param  mixed  ...$args
     * @return mixed
     */
    public static function unwrapIfClosure($value, ...$args)
    {
        return $value instanceof Closure ? $value(...$args) : $value;
    }

    /**
     * Get the class name of the given parameter's type, if possible.
     *
     * From Reflector::getParameterClassName() in Illuminate\Support.
     *
     * @param  \ReflectionParameter  $parameter
     * @return string|null
     */
    public static function getParameterClassName($parameter)
    {
        $type = $parameter->getType();

        if (! $type instanceof ReflectionNamedType || $type->isBuiltin()) {
            return null;
        }

        $name = $type->getName();

        if (! is_null($class = $parameter->getDeclaringClass())) {
            if ($name === 'self') {
                return $class->getName();
            }

            if ($name === 'parent' && $parent = $class->getParentClass()) {
                return $parent->getName();
            }
        }

        return $name;
    }
}
<?php

namespace Illuminate\Container;

use Countable;
use IteratorAggregate;
use Traversable;

class RewindableGenerator implements Countable, IteratorAggregate
{
    /**
     * The generator callback.
     *
     * @var callable
     */
    protected $generator;

    /**
     * The number of tagged services.
     *
     * @var callable|int
     */
    protected $count;

    /**
     * Create a new generator instance.
     *
     * @param  callable  $generator
     * @param  callable|int  $count
     * @return void
     */
    public function __construct(callable $generator, $count)
    {
        $this->count = $count;
        $this->generator = $generator;
    }

    /**
     * Get an iterator from the generator.
     *
     * @return \Traversable
     */
    public function getIterator(): Traversable
    {
        return ($this->generator)();
    }

    /**
     * Get the total number of tagged services.
     *
     * @return int
     */
    public function count(): int
    {
        if (is_callable($count = $this->count)) {
            $this->count = $count();
        }

        return $this->count;
    }
}
<?php

namespace Illuminate\Container;

use Closure;
use Illuminate\Contracts\Container\BindingResolutionException;
use InvalidArgumentException;
use ReflectionFunction;
use ReflectionMethod;

class BoundMethod
{
    /**
     * Call the given Closure / class@method and inject its dependencies.
     *
     * @param  \Illuminate\Container\Container  $container
     * @param  callable|string  $callback
     * @param  array  $parameters
     * @param  string|null  $defaultMethod
     * @return mixed
     *
     * @throws \ReflectionException
     * @throws \InvalidArgumentException
     */
    public static function call($container, $callback, array $parameters = [], $defaultMethod = null)
    {
        if (is_string($callback) && ! $defaultMethod && method_exists($callback, '__invoke')) {
            $defaultMethod = '__invoke';
        }

        if (static::isCallableWithAtSign($callback) || $defaultMethod) {
            return static::callClass($container, $callback, $parameters, $defaultMethod);
        }

        return static::callBoundMethod($container, $callback, function () use ($container, $callback, $parameters) {
            return $callback(...array_values(static::getMethodDependencies($container, $callback, $parameters)));
        });
    }

    /**
     * Call a string reference to a class using Class@method syntax.
     *
     * @param  \Illuminate\Container\Container  $container
     * @param  string  $target
     * @param  array  $parameters
     * @param  string|null  $defaultMethod
     * @return mixed
     *
     * @throws \InvalidArgumentException
     */
    protected static function callClass($container, $target, array $parameters = [], $defaultMethod = null)
    {
        $segments = explode('@', $target);

        // We will assume an @ sign is used to delimit the class name from the method
        // name. We will split on this @ sign and then build a callable array that
        // we can pass right back into the "call" method for dependency binding.
        $method = count($segments) === 2
                        ? $segments[1] : $defaultMethod;

        if (is_null($method)) {
            throw new InvalidArgumentException('Method not provided.');
        }

        return static::call(
            $container, [$container->make($segments[0]), $method], $parameters
        );
    }

    /**
     * Call a method that has been bound to the container.
     *
     * @param  \Illuminate\Container\Container  $container
     * @param  callable  $callback
     * @param  mixed  $default
     * @return mixed
     */
    protected static function callBoundMethod($container, $callback, $default)
    {
        if (! is_array($callback)) {
            return Util::unwrapIfClosure($default);
        }

        // Here we need to turn the array callable into a Class@method string we can use to
        // examine the container and see if there are any method bindings for this given
        // method. If there are, we can call this method binding callback immediately.
        $method = static::normalizeMethod($callback);

        if ($container->hasMethodBinding($method)) {
            return $container->callMethodBinding($method, $callback[0]);
        }

        return Util::unwrapIfClosure($default);
    }

    /**
     * Normalize the given callback into a Class@method string.
     *
     * @param  callable  $callback
     * @return string
     */
    protected static function normalizeMethod($callback)
    {
        $class = is_string($callback[0]) ? $callback[0] : get_class($callback[0]);

        return "{$class}@{$callback[1]}";
    }

    /**
     * Get all dependencies for a given method.
     *
     * @param  \Illuminate\Container\Container  $container
     * @param  callable|string  $callback
     * @param  array  $parameters
     * @return array
     *
     * @throws \ReflectionException
     */
    protected static function getMethodDependencies($container, $callback, array $parameters = [])
    {
        $dependencies = [];

        foreach (static::getCallReflector($callback)->getParameters() as $parameter) {
            static::addDependencyForCallParameter($container, $parameter, $parameters, $dependencies);
        }

        return array_merge($dependencies, array_values($parameters));
    }

    /**
     * Get the proper reflection instance for the given callback.
     *
     * @param  callable|string  $callback
     * @return \ReflectionFunctionAbstract
     *
     * @throws \ReflectionException
     */
    protected static function getCallReflector($callback)
    {
        if (is_string($callback) && str_contains($callback, '::')) {
            $callback = explode('::', $callback);
        } elseif (is_object($callback) && ! $callback instanceof Closure) {
            $callback = [$callback, '__invoke'];
        }

        return is_array($callback)
                        ? new ReflectionMethod($callback[0], $callback[1])
                        : new ReflectionFunction($callback);
    }

    /**
     * Get the dependency for the given call parameter.
     *
     * @param  \Illuminate\Container\Container  $container
     * @param  \ReflectionParameter  $parameter
     * @param  array  $parameters
     * @param  array  $dependencies
     * @return void
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    protected static function addDependencyForCallParameter($container, $parameter,
                                                            array &$parameters, &$dependencies)
    {
        if (array_key_exists($paramName = $parameter->getName(), $parameters)) {
            $dependencies[] = $parameters[$paramName];

            unset($parameters[$paramName]);
        } elseif (! is_null($className = Util::getParameterClassName($parameter))) {
            if (array_key_exists($className, $parameters)) {
                $dependencies[] = $parameters[$className];

                unset($parameters[$className]);
            } elseif ($parameter->isVariadic()) {
                $variadicDependencies = $container->make($className);

                $dependencies = array_merge($dependencies, is_array($variadicDependencies)
                            ? $variadicDependencies
                            : [$variadicDependencies]);
            } else {
                $dependencies[] = $container->make($className);
            }
        } elseif ($parameter->isDefaultValueAvailable()) {
            $dependencies[] = $parameter->getDefaultValue();
        } elseif (! $parameter->isOptional() && ! array_key_exists($paramName, $parameters)) {
            $message = "Unable to resolve dependency [{$parameter}] in class {$parameter->getDeclaringClass()->getName()}";

            throw new BindingResolutionException($message);
        }
    }

    /**
     * Determine if the given string is in Class@method syntax.
     *
     * @param  mixed  $callback
     * @return bool
     */
    protected static function isCallableWithAtSign($callback)
    {
        return is_string($callback) && str_contains($callback, '@');
    }
}
<?php

namespace Illuminate\Container;

use ArrayAccess;
use Closure;
use Exception;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Contracts\Container\CircularDependencyException;
use Illuminate\Contracts\Container\Container as ContainerContract;
use LogicException;
use ReflectionClass;
use ReflectionException;
use ReflectionFunction;
use ReflectionParameter;
use TypeError;

class Container implements ArrayAccess, ContainerContract
{
    /**
     * The current globally available container (if any).
     *
     * @var static
     */
    protected static $instance;

    /**
     * An array of the types that have been resolved.
     *
     * @var bool[]
     */
    protected $resolved = [];

    /**
     * The container's bindings.
     *
     * @var array[]
     */
    protected $bindings = [];

    /**
     * The container's method bindings.
     *
     * @var \Closure[]
     */
    protected $methodBindings = [];

    /**
     * The container's shared instances.
     *
     * @var object[]
     */
    protected $instances = [];

    /**
     * The container's scoped instances.
     *
     * @var array
     */
    protected $scopedInstances = [];

    /**
     * The registered type aliases.
     *
     * @var string[]
     */
    protected $aliases = [];

    /**
     * The registered aliases keyed by the abstract name.
     *
     * @var array[]
     */
    protected $abstractAliases = [];

    /**
     * The extension closures for services.
     *
     * @var array[]
     */
    protected $extenders = [];

    /**
     * All of the registered tags.
     *
     * @var array[]
     */
    protected $tags = [];

    /**
     * The stack of concretions currently being built.
     *
     * @var array[]
     */
    protected $buildStack = [];

    /**
     * The parameter override stack.
     *
     * @var array[]
     */
    protected $with = [];

    /**
     * The contextual binding map.
     *
     * @var array[]
     */
    public $contextual = [];

    /**
     * All of the registered rebound callbacks.
     *
     * @var array[]
     */
    protected $reboundCallbacks = [];

    /**
     * All of the global before resolving callbacks.
     *
     * @var \Closure[]
     */
    protected $globalBeforeResolvingCallbacks = [];

    /**
     * All of the global resolving callbacks.
     *
     * @var \Closure[]
     */
    protected $globalResolvingCallbacks = [];

    /**
     * All of the global after resolving callbacks.
     *
     * @var \Closure[]
     */
    protected $globalAfterResolvingCallbacks = [];

    /**
     * All of the before resolving callbacks by class type.
     *
     * @var array[]
     */
    protected $beforeResolvingCallbacks = [];

    /**
     * All of the resolving callbacks by class type.
     *
     * @var array[]
     */
    protected $resolvingCallbacks = [];

    /**
     * All of the after resolving callbacks by class type.
     *
     * @var array[]
     */
    protected $afterResolvingCallbacks = [];

    /**
     * Define a contextual binding.
     *
     * @param  array|string  $concrete
     * @return \Illuminate\Contracts\Container\ContextualBindingBuilder
     */
    public function when($concrete)
    {
        $aliases = [];

        foreach (Util::arrayWrap($concrete) as $c) {
            $aliases[] = $this->getAlias($c);
        }

        return new ContextualBindingBuilder($this, $aliases);
    }

    /**
     * Determine if the given abstract type has been bound.
     *
     * @param  string  $abstract
     * @return bool
     */
    public function bound($abstract)
    {
        return isset($this->bindings[$abstract]) ||
               isset($this->instances[$abstract]) ||
               $this->isAlias($abstract);
    }

    /**
     * {@inheritdoc}
     *
     * @return bool
     */
    public function has(string $id): bool
    {
        return $this->bound($id);
    }

    /**
     * Determine if the given abstract type has been resolved.
     *
     * @param  string  $abstract
     * @return bool
     */
    public function resolved($abstract)
    {
        if ($this->isAlias($abstract)) {
            $abstract = $this->getAlias($abstract);
        }

        return isset($this->resolved[$abstract]) ||
               isset($this->instances[$abstract]);
    }

    /**
     * Determine if a given type is shared.
     *
     * @param  string  $abstract
     * @return bool
     */
    public function isShared($abstract)
    {
        return isset($this->instances[$abstract]) ||
               (isset($this->bindings[$abstract]['shared']) &&
               $this->bindings[$abstract]['shared'] === true);
    }

    /**
     * Determine if a given string is an alias.
     *
     * @param  string  $name
     * @return bool
     */
    public function isAlias($name)
    {
        return isset($this->aliases[$name]);
    }

    /**
     * Register a binding with the container.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @param  bool  $shared
     * @return void
     *
     * @throws \TypeError
     */
    public function bind($abstract, $concrete = null, $shared = false)
    {
        $this->dropStaleInstances($abstract);

        // If no concrete type was given, we will simply set the concrete type to the
        // abstract type. After that, the concrete type to be registered as shared
        // without being forced to state their classes in both of the parameters.
        if (is_null($concrete)) {
            $concrete = $abstract;
        }

        // If the factory is not a Closure, it means it is just a class name which is
        // bound into this container to the abstract type and we will just wrap it
        // up inside its own Closure to give us more convenience when extending.
        if (! $concrete instanceof Closure) {
            if (! is_string($concrete)) {
                throw new TypeError(self::class.'::bind(): Argument #2 ($concrete) must be of type Closure|string|null');
            }

            $concrete = $this->getClosure($abstract, $concrete);
        }

        $this->bindings[$abstract] = compact('concrete', 'shared');

        // If the abstract type was already resolved in this container we'll fire the
        // rebound listener so that any objects which have already gotten resolved
        // can have their copy of the object updated via the listener callbacks.
        if ($this->resolved($abstract)) {
            $this->rebound($abstract);
        }
    }

    /**
     * Get the Closure to be used when building a type.
     *
     * @param  string  $abstract
     * @param  string  $concrete
     * @return \Closure
     */
    protected function getClosure($abstract, $concrete)
    {
        return function ($container, $parameters = []) use ($abstract, $concrete) {
            if ($abstract == $concrete) {
                return $container->build($concrete);
            }

            return $container->resolve(
                $concrete, $parameters, $raiseEvents = false
            );
        };
    }

    /**
     * Determine if the container has a method binding.
     *
     * @param  string  $method
     * @return bool
     */
    public function hasMethodBinding($method)
    {
        return isset($this->methodBindings[$method]);
    }

    /**
     * Bind a callback to resolve with Container::call.
     *
     * @param  array|string  $method
     * @param  \Closure  $callback
     * @return void
     */
    public function bindMethod($method, $callback)
    {
        $this->methodBindings[$this->parseBindMethod($method)] = $callback;
    }

    /**
     * Get the method to be bound in class@method format.
     *
     * @param  array|string  $method
     * @return string
     */
    protected function parseBindMethod($method)
    {
        if (is_array($method)) {
            return $method[0].'@'.$method[1];
        }

        return $method;
    }

    /**
     * Get the method binding for the given method.
     *
     * @param  string  $method
     * @param  mixed  $instance
     * @return mixed
     */
    public function callMethodBinding($method, $instance)
    {
        return call_user_func($this->methodBindings[$method], $instance, $this);
    }

    /**
     * Add a contextual binding to the container.
     *
     * @param  string  $concrete
     * @param  string  $abstract
     * @param  \Closure|string  $implementation
     * @return void
     */
    public function addContextualBinding($concrete, $abstract, $implementation)
    {
        $this->contextual[$concrete][$this->getAlias($abstract)] = $implementation;
    }

    /**
     * Register a binding if it hasn't already been registered.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @param  bool  $shared
     * @return void
     */
    public function bindIf($abstract, $concrete = null, $shared = false)
    {
        if (! $this->bound($abstract)) {
            $this->bind($abstract, $concrete, $shared);
        }
    }

    /**
     * Register a shared binding in the container.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @return void
     */
    public function singleton($abstract, $concrete = null)
    {
        $this->bind($abstract, $concrete, true);
    }

    /**
     * Register a shared binding if it hasn't already been registered.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @return void
     */
    public function singletonIf($abstract, $concrete = null)
    {
        if (! $this->bound($abstract)) {
            $this->singleton($abstract, $concrete);
        }
    }

    /**
     * Register a scoped binding in the container.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @return void
     */
    public function scoped($abstract, $concrete = null)
    {
        $this->scopedInstances[] = $abstract;

        $this->singleton($abstract, $concrete);
    }

    /**
     * Register a scoped binding if it hasn't already been registered.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @return void
     */
    public function scopedIf($abstract, $concrete = null)
    {
        if (! $this->bound($abstract)) {
            $this->scoped($abstract, $concrete);
        }
    }

    /**
     * "Extend" an abstract type in the container.
     *
     * @param  string  $abstract
     * @param  \Closure  $closure
     * @return void
     *
     * @throws \InvalidArgumentException
     */
    public function extend($abstract, Closure $closure)
    {
        $abstract = $this->getAlias($abstract);

        if (isset($this->instances[$abstract])) {
            $this->instances[$abstract] = $closure($this->instances[$abstract], $this);

            $this->rebound($abstract);
        } else {
            $this->extenders[$abstract][] = $closure;

            if ($this->resolved($abstract)) {
                $this->rebound($abstract);
            }
        }
    }

    /**
     * Register an existing instance as shared in the container.
     *
     * @param  string  $abstract
     * @param  mixed  $instance
     * @return mixed
     */
    public function instance($abstract, $instance)
    {
        $this->removeAbstractAlias($abstract);

        $isBound = $this->bound($abstract);

        unset($this->aliases[$abstract]);

        // We'll check to determine if this type has been bound before, and if it has
        // we will fire the rebound callbacks registered with the container and it
        // can be updated with consuming classes that have gotten resolved here.
        $this->instances[$abstract] = $instance;

        if ($isBound) {
            $this->rebound($abstract);
        }

        return $instance;
    }

    /**
     * Remove an alias from the contextual binding alias cache.
     *
     * @param  string  $searched
     * @return void
     */
    protected function removeAbstractAlias($searched)
    {
        if (! isset($this->aliases[$searched])) {
            return;
        }

        foreach ($this->abstractAliases as $abstract => $aliases) {
            foreach ($aliases as $index => $alias) {
                if ($alias == $searched) {
                    unset($this->abstractAliases[$abstract][$index]);
                }
            }
        }
    }

    /**
     * Assign a set of tags to a given binding.
     *
     * @param  array|string  $abstracts
     * @param  array|mixed  ...$tags
     * @return void
     */
    public function tag($abstracts, $tags)
    {
        $tags = is_array($tags) ? $tags : array_slice(func_get_args(), 1);

        foreach ($tags as $tag) {
            if (! isset($this->tags[$tag])) {
                $this->tags[$tag] = [];
            }

            foreach ((array) $abstracts as $abstract) {
                $this->tags[$tag][] = $abstract;
            }
        }
    }

    /**
     * Resolve all of the bindings for a given tag.
     *
     * @param  string  $tag
     * @return iterable
     */
    public function tagged($tag)
    {
        if (! isset($this->tags[$tag])) {
            return [];
        }

        return new RewindableGenerator(function () use ($tag) {
            foreach ($this->tags[$tag] as $abstract) {
                yield $this->make($abstract);
            }
        }, count($this->tags[$tag]));
    }

    /**
     * Alias a type to a different name.
     *
     * @param  string  $abstract
     * @param  string  $alias
     * @return void
     *
     * @throws \LogicException
     */
    public function alias($abstract, $alias)
    {
        if ($alias === $abstract) {
            throw new LogicException("[{$abstract}] is aliased to itself.");
        }

        $this->aliases[$alias] = $abstract;

        $this->abstractAliases[$abstract][] = $alias;
    }

    /**
     * Bind a new callback to an abstract's rebind event.
     *
     * @param  string  $abstract
     * @param  \Closure  $callback
     * @return mixed
     */
    public function rebinding($abstract, Closure $callback)
    {
        $this->reboundCallbacks[$abstract = $this->getAlias($abstract)][] = $callback;

        if ($this->bound($abstract)) {
            return $this->make($abstract);
        }
    }

    /**
     * Refresh an instance on the given target and method.
     *
     * @param  string  $abstract
     * @param  mixed  $target
     * @param  string  $method
     * @return mixed
     */
    public function refresh($abstract, $target, $method)
    {
        return $this->rebinding($abstract, function ($app, $instance) use ($target, $method) {
            $target->{$method}($instance);
        });
    }

    /**
     * Fire the "rebound" callbacks for the given abstract type.
     *
     * @param  string  $abstract
     * @return void
     */
    protected function rebound($abstract)
    {
        $instance = $this->make($abstract);

        foreach ($this->getReboundCallbacks($abstract) as $callback) {
            $callback($this, $instance);
        }
    }

    /**
     * Get the rebound callbacks for a given type.
     *
     * @param  string  $abstract
     * @return array
     */
    protected function getReboundCallbacks($abstract)
    {
        return $this->reboundCallbacks[$abstract] ?? [];
    }

    /**
     * Wrap the given closure such that its dependencies will be injected when executed.
     *
     * @param  \Closure  $callback
     * @param  array  $parameters
     * @return \Closure
     */
    public function wrap(Closure $callback, array $parameters = [])
    {
        return fn () => $this->call($callback, $parameters);
    }

    /**
     * Call the given Closure / class@method and inject its dependencies.
     *
     * @param  callable|string  $callback
     * @param  array<string, mixed>  $parameters
     * @param  string|null  $defaultMethod
     * @return mixed
     *
     * @throws \InvalidArgumentException
     */
    public function call($callback, array $parameters = [], $defaultMethod = null)
    {
        $pushedToBuildStack = false;

        if (($className = $this->getClassForCallable($callback)) && ! in_array(
            $className,
            $this->buildStack,
            true
        )) {
            $this->buildStack[] = $className;

            $pushedToBuildStack = true;
        }

        $result = BoundMethod::call($this, $callback, $parameters, $defaultMethod);

        if ($pushedToBuildStack) {
            array_pop($this->buildStack);
        }

        return $result;
    }

    /**
     * Get the class name for the given callback, if one can be determined.
     *
     * @param  callable|string  $callback
     * @return string|false
     */
    protected function getClassForCallable($callback)
    {
        if (PHP_VERSION_ID >= 80200) {
            if (is_callable($callback) &&
                ! ($reflector = new ReflectionFunction($callback(...)))->isAnonymous()) {
                return $reflector->getClosureScopeClass()->name ?? false;
            }

            return false;
        }

        if (! is_array($callback)) {
            return false;
        }

        return is_string($callback[0]) ? $callback[0] : get_class($callback[0]);
    }

    /**
     * Get a closure to resolve the given type from the container.
     *
     * @param  string  $abstract
     * @return \Closure
     */
    public function factory($abstract)
    {
        return fn () => $this->make($abstract);
    }

    /**
     * An alias function name for make().
     *
     * @param  string|callable  $abstract
     * @param  array  $parameters
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    public function makeWith($abstract, array $parameters = [])
    {
        return $this->make($abstract, $parameters);
    }

    /**
     * Resolve the given type from the container.
     *
     * @param  string|callable  $abstract
     * @param  array  $parameters
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    public function make($abstract, array $parameters = [])
    {
        return $this->resolve($abstract, $parameters);
    }

    /**
     * {@inheritdoc}
     *
     * @return mixed
     */
    public function get(string $id)
    {
        try {
            return $this->resolve($id);
        } catch (Exception $e) {
            if ($this->has($id) || $e instanceof CircularDependencyException) {
                throw $e;
            }

            throw new EntryNotFoundException($id, is_int($e->getCode()) ? $e->getCode() : 0, $e);
        }
    }

    /**
     * Resolve the given type from the container.
     *
     * @param  string|callable  $abstract
     * @param  array  $parameters
     * @param  bool  $raiseEvents
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     * @throws \Illuminate\Contracts\Container\CircularDependencyException
     */
    protected function resolve($abstract, $parameters = [], $raiseEvents = true)
    {
        $abstract = $this->getAlias($abstract);

        // First we'll fire any event handlers which handle the "before" resolving of
        // specific types. This gives some hooks the chance to add various extends
        // calls to change the resolution of objects that they're interested in.
        if ($raiseEvents) {
            $this->fireBeforeResolvingCallbacks($abstract, $parameters);
        }

        $concrete = $this->getContextualConcrete($abstract);

        $needsContextualBuild = ! empty($parameters) || ! is_null($concrete);

        // If an instance of the type is currently being managed as a singleton we'll
        // just return an existing instance instead of instantiating new instances
        // so the developer can keep using the same objects instance every time.
        if (isset($this->instances[$abstract]) && ! $needsContextualBuild) {
            return $this->instances[$abstract];
        }

        $this->with[] = $parameters;

        if (is_null($concrete)) {
            $concrete = $this->getConcrete($abstract);
        }

        // We're ready to instantiate an instance of the concrete type registered for
        // the binding. This will instantiate the types, as well as resolve any of
        // its "nested" dependencies recursively until all have gotten resolved.
        $object = $this->isBuildable($concrete, $abstract)
            ? $this->build($concrete)
            : $this->make($concrete);

        // If we defined any extenders for this type, we'll need to spin through them
        // and apply them to the object being built. This allows for the extension
        // of services, such as changing configuration or decorating the object.
        foreach ($this->getExtenders($abstract) as $extender) {
            $object = $extender($object, $this);
        }

        // If the requested type is registered as a singleton we'll want to cache off
        // the instances in "memory" so we can return it later without creating an
        // entirely new instance of an object on each subsequent request for it.
        if ($this->isShared($abstract) && ! $needsContextualBuild) {
            $this->instances[$abstract] = $object;
        }

        if ($raiseEvents) {
            $this->fireResolvingCallbacks($abstract, $object);
        }

        // Before returning, we will also set the resolved flag to "true" and pop off
        // the parameter overrides for this build. After those two things are done
        // we will be ready to return back the fully constructed class instance.
        $this->resolved[$abstract] = true;

        array_pop($this->with);

        return $object;
    }

    /**
     * Get the concrete type for a given abstract.
     *
     * @param  string|callable  $abstract
     * @return mixed
     */
    protected function getConcrete($abstract)
    {
        // If we don't have a registered resolver or concrete for the type, we'll just
        // assume each type is a concrete name and will attempt to resolve it as is
        // since the container should be able to resolve concretes automatically.
        if (isset($this->bindings[$abstract])) {
            return $this->bindings[$abstract]['concrete'];
        }

        return $abstract;
    }

    /**
     * Get the contextual concrete binding for the given abstract.
     *
     * @param  string|callable  $abstract
     * @return \Closure|string|array|null
     */
    protected function getContextualConcrete($abstract)
    {
        if (! is_null($binding = $this->findInContextualBindings($abstract))) {
            return $binding;
        }

        // Next we need to see if a contextual binding might be bound under an alias of the
        // given abstract type. So, we will need to check if any aliases exist with this
        // type and then spin through them and check for contextual bindings on these.
        if (empty($this->abstractAliases[$abstract])) {
            return;
        }

        foreach ($this->abstractAliases[$abstract] as $alias) {
            if (! is_null($binding = $this->findInContextualBindings($alias))) {
                return $binding;
            }
        }
    }

    /**
     * Find the concrete binding for the given abstract in the contextual binding array.
     *
     * @param  string|callable  $abstract
     * @return \Closure|string|null
     */
    protected function findInContextualBindings($abstract)
    {
        return $this->contextual[end($this->buildStack)][$abstract] ?? null;
    }

    /**
     * Determine if the given concrete is buildable.
     *
     * @param  mixed  $concrete
     * @param  string  $abstract
     * @return bool
     */
    protected function isBuildable($concrete, $abstract)
    {
        return $concrete === $abstract || $concrete instanceof Closure;
    }

    /**
     * Instantiate a concrete instance of the given type.
     *
     * @param  \Closure|string  $concrete
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     * @throws \Illuminate\Contracts\Container\CircularDependencyException
     */
    public function build($concrete)
    {
        // If the concrete type is actually a Closure, we will just execute it and
        // hand back the results of the functions, which allows functions to be
        // used as resolvers for more fine-tuned resolution of these objects.
        if ($concrete instanceof Closure) {
            return $concrete($this, $this->getLastParameterOverride());
        }

        try {
            $reflector = new ReflectionClass($concrete);
        } catch (ReflectionException $e) {
            throw new BindingResolutionException("Target class [$concrete] does not exist.", 0, $e);
        }

        // If the type is not instantiable, the developer is attempting to resolve
        // an abstract type such as an Interface or Abstract Class and there is
        // no binding registered for the abstractions so we need to bail out.
        if (! $reflector->isInstantiable()) {
            return $this->notInstantiable($concrete);
        }

        $this->buildStack[] = $concrete;

        $constructor = $reflector->getConstructor();

        // If there are no constructors, that means there are no dependencies then
        // we can just resolve the instances of the objects right away, without
        // resolving any other types or dependencies out of these containers.
        if (is_null($constructor)) {
            array_pop($this->buildStack);

            return new $concrete;
        }

        $dependencies = $constructor->getParameters();

        // Once we have all the constructor's parameters we can create each of the
        // dependency instances and then use the reflection instances to make a
        // new instance of this class, injecting the created dependencies in.
        try {
            $instances = $this->resolveDependencies($dependencies);
        } catch (BindingResolutionException $e) {
            array_pop($this->buildStack);

            throw $e;
        }

        array_pop($this->buildStack);

        return $reflector->newInstanceArgs($instances);
    }

    /**
     * Resolve all of the dependencies from the ReflectionParameters.
     *
     * @param  \ReflectionParameter[]  $dependencies
     * @return array
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    protected function resolveDependencies(array $dependencies)
    {
        $results = [];

        foreach ($dependencies as $dependency) {
            // If the dependency has an override for this particular build we will use
            // that instead as the value. Otherwise, we will continue with this run
            // of resolutions and let reflection attempt to determine the result.
            if ($this->hasParameterOverride($dependency)) {
                $results[] = $this->getParameterOverride($dependency);

                continue;
            }

            // If the class is null, it means the dependency is a string or some other
            // primitive type which we can not resolve since it is not a class and
            // we will just bomb out with an error since we have no-where to go.
            $result = is_null(Util::getParameterClassName($dependency))
                            ? $this->resolvePrimitive($dependency)
                            : $this->resolveClass($dependency);

            if ($dependency->isVariadic()) {
                $results = array_merge($results, $result);
            } else {
                $results[] = $result;
            }
        }

        return $results;
    }

    /**
     * Determine if the given dependency has a parameter override.
     *
     * @param  \ReflectionParameter  $dependency
     * @return bool
     */
    protected function hasParameterOverride($dependency)
    {
        return array_key_exists(
            $dependency->name, $this->getLastParameterOverride()
        );
    }

    /**
     * Get a parameter override for a dependency.
     *
     * @param  \ReflectionParameter  $dependency
     * @return mixed
     */
    protected function getParameterOverride($dependency)
    {
        return $this->getLastParameterOverride()[$dependency->name];
    }

    /**
     * Get the last parameter override.
     *
     * @return array
     */
    protected function getLastParameterOverride()
    {
        return count($this->with) ? end($this->with) : [];
    }

    /**
     * Resolve a non-class hinted primitive dependency.
     *
     * @param  \ReflectionParameter  $parameter
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    protected function resolvePrimitive(ReflectionParameter $parameter)
    {
        if (! is_null($concrete = $this->getContextualConcrete('$'.$parameter->getName()))) {
            return Util::unwrapIfClosure($concrete, $this);
        }

        if ($parameter->isDefaultValueAvailable()) {
            return $parameter->getDefaultValue();
        }

        if ($parameter->isVariadic()) {
            return [];
        }

        $this->unresolvablePrimitive($parameter);
    }

    /**
     * Resolve a class based dependency from the container.
     *
     * @param  \ReflectionParameter  $parameter
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    protected function resolveClass(ReflectionParameter $parameter)
    {
        try {
            return $parameter->isVariadic()
                        ? $this->resolveVariadicClass($parameter)
                        : $this->make(Util::getParameterClassName($parameter));
        }

        // If we can not resolve the class instance, we will check to see if the value
        // is optional, and if it is we will return the optional parameter value as
        // the value of the dependency, similarly to how we do this with scalars.
        catch (BindingResolutionException $e) {
            if ($parameter->isDefaultValueAvailable()) {
                array_pop($this->with);

                return $parameter->getDefaultValue();
            }

            if ($parameter->isVariadic()) {
                array_pop($this->with);

                return [];
            }

            throw $e;
        }
    }

    /**
     * Resolve a class based variadic dependency from the container.
     *
     * @param  \ReflectionParameter  $parameter
     * @return mixed
     */
    protected function resolveVariadicClass(ReflectionParameter $parameter)
    {
        $className = Util::getParameterClassName($parameter);

        $abstract = $this->getAlias($className);

        if (! is_array($concrete = $this->getContextualConcrete($abstract))) {
            return $this->make($className);
        }

        return array_map(fn ($abstract) => $this->resolve($abstract), $concrete);
    }

    /**
     * Throw an exception that the concrete is not instantiable.
     *
     * @param  string  $concrete
     * @return void
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    protected function notInstantiable($concrete)
    {
        if (! empty($this->buildStack)) {
            $previous = implode(', ', $this->buildStack);

            $message = "Target [$concrete] is not instantiable while building [$previous].";
        } else {
            $message = "Target [$concrete] is not instantiable.";
        }

        throw new BindingResolutionException($message);
    }

    /**
     * Throw an exception for an unresolvable primitive.
     *
     * @param  \ReflectionParameter  $parameter
     * @return void
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    protected function unresolvablePrimitive(ReflectionParameter $parameter)
    {
        $message = "Unresolvable dependency resolving [$parameter] in class {$parameter->getDeclaringClass()->getName()}";

        throw new BindingResolutionException($message);
    }

    /**
     * Register a new before resolving callback for all types.
     *
     * @param  \Closure|string  $abstract
     * @param  \Closure|null  $callback
     * @return void
     */
    public function beforeResolving($abstract, ?Closure $callback = null)
    {
        if (is_string($abstract)) {
            $abstract = $this->getAlias($abstract);
        }

        if ($abstract instanceof Closure && is_null($callback)) {
            $this->globalBeforeResolvingCallbacks[] = $abstract;
        } else {
            $this->beforeResolvingCallbacks[$abstract][] = $callback;
        }
    }

    /**
     * Register a new resolving callback.
     *
     * @param  \Closure|string  $abstract
     * @param  \Closure|null  $callback
     * @return void
     */
    public function resolving($abstract, ?Closure $callback = null)
    {
        if (is_string($abstract)) {
            $abstract = $this->getAlias($abstract);
        }

        if (is_null($callback) && $abstract instanceof Closure) {
            $this->globalResolvingCallbacks[] = $abstract;
        } else {
            $this->resolvingCallbacks[$abstract][] = $callback;
        }
    }

    /**
     * Register a new after resolving callback for all types.
     *
     * @param  \Closure|string  $abstract
     * @param  \Closure|null  $callback
     * @return void
     */
    public function afterResolving($abstract, ?Closure $callback = null)
    {
        if (is_string($abstract)) {
            $abstract = $this->getAlias($abstract);
        }

        if ($abstract instanceof Closure && is_null($callback)) {
            $this->globalAfterResolvingCallbacks[] = $abstract;
        } else {
            $this->afterResolvingCallbacks[$abstract][] = $callback;
        }
    }

    /**
     * Fire all of the before resolving callbacks.
     *
     * @param  string  $abstract
     * @param  array  $parameters
     * @return void
     */
    protected function fireBeforeResolvingCallbacks($abstract, $parameters = [])
    {
        $this->fireBeforeCallbackArray($abstract, $parameters, $this->globalBeforeResolvingCallbacks);

        foreach ($this->beforeResolvingCallbacks as $type => $callbacks) {
            if ($type === $abstract || is_subclass_of($abstract, $type)) {
                $this->fireBeforeCallbackArray($abstract, $parameters, $callbacks);
            }
        }
    }

    /**
     * Fire an array of callbacks with an object.
     *
     * @param  string  $abstract
     * @param  array  $parameters
     * @param  array  $callbacks
     * @return void
     */
    protected function fireBeforeCallbackArray($abstract, $parameters, array $callbacks)
    {
        foreach ($callbacks as $callback) {
            $callback($abstract, $parameters, $this);
        }
    }

    /**
     * Fire all of the resolving callbacks.
     *
     * @param  string  $abstract
     * @param  mixed  $object
     * @return void
     */
    protected function fireResolvingCallbacks($abstract, $object)
    {
        $this->fireCallbackArray($object, $this->globalResolvingCallbacks);

        $this->fireCallbackArray(
            $object, $this->getCallbacksForType($abstract, $object, $this->resolvingCallbacks)
        );

        $this->fireAfterResolvingCallbacks($abstract, $object);
    }

    /**
     * Fire all of the after resolving callbacks.
     *
     * @param  string  $abstract
     * @param  mixed  $object
     * @return void
     */
    protected function fireAfterResolvingCallbacks($abstract, $object)
    {
        $this->fireCallbackArray($object, $this->globalAfterResolvingCallbacks);

        $this->fireCallbackArray(
            $object, $this->getCallbacksForType($abstract, $object, $this->afterResolvingCallbacks)
        );
    }

    /**
     * Get all callbacks for a given type.
     *
     * @param  string  $abstract
     * @param  object  $object
     * @param  array  $callbacksPerType
     * @return array
     */
    protected function getCallbacksForType($abstract, $object, array $callbacksPerType)
    {
        $results = [];

        foreach ($callbacksPerType as $type => $callbacks) {
            if ($type === $abstract || $object instanceof $type) {
                $results = array_merge($results, $callbacks);
            }
        }

        return $results;
    }

    /**
     * Fire an array of callbacks with an object.
     *
     * @param  mixed  $object
     * @param  array  $callbacks
     * @return void
     */
    protected function fireCallbackArray($object, array $callbacks)
    {
        foreach ($callbacks as $callback) {
            $callback($object, $this);
        }
    }

    /**
     * Get the container's bindings.
     *
     * @return array
     */
    public function getBindings()
    {
        return $this->bindings;
    }

    /**
     * Get the alias for an abstract if available.
     *
     * @param  string  $abstract
     * @return string
     */
    public function getAlias($abstract)
    {
        return isset($this->aliases[$abstract])
                    ? $this->getAlias($this->aliases[$abstract])
                    : $abstract;
    }

    /**
     * Get the extender callbacks for a given type.
     *
     * @param  string  $abstract
     * @return array
     */
    protected function getExtenders($abstract)
    {
        return $this->extenders[$this->getAlias($abstract)] ?? [];
    }

    /**
     * Remove all of the extender callbacks for a given type.
     *
     * @param  string  $abstract
     * @return void
     */
    public function forgetExtenders($abstract)
    {
        unset($this->extenders[$this->getAlias($abstract)]);
    }

    /**
     * Drop all of the stale instances and aliases.
     *
     * @param  string  $abstract
     * @return void
     */
    protected function dropStaleInstances($abstract)
    {
        unset($this->instances[$abstract], $this->aliases[$abstract]);
    }

    /**
     * Remove a resolved instance from the instance cache.
     *
     * @param  string  $abstract
     * @return void
     */
    public function forgetInstance($abstract)
    {
        unset($this->instances[$abstract]);
    }

    /**
     * Clear all of the instances from the container.
     *
     * @return void
     */
    public function forgetInstances()
    {
        $this->instances = [];
    }

    /**
     * Clear all of the scoped instances from the container.
     *
     * @return void
     */
    public function forgetScopedInstances()
    {
        foreach ($this->scopedInstances as $scoped) {
            unset($this->instances[$scoped]);
        }
    }

    /**
     * Flush the container of all bindings and resolved instances.
     *
     * @return void
     */
    public function flush()
    {
        $this->aliases = [];
        $this->resolved = [];
        $this->bindings = [];
        $this->instances = [];
        $this->abstractAliases = [];
        $this->scopedInstances = [];
    }

    /**
     * Get the globally available instance of the container.
     *
     * @return static
     */
    public static function getInstance()
    {
        if (is_null(static::$instance)) {
            static::$instance = new static;
        }

        return static::$instance;
    }

    /**
     * Set the shared instance of the container.
     *
     * @param  \Illuminate\Contracts\Container\Container|null  $container
     * @return \Illuminate\Contracts\Container\Container|static
     */
    public static function setInstance(?ContainerContract $container = null)
    {
        return static::$instance = $container;
    }

    /**
     * Determine if a given offset exists.
     *
     * @param  string  $key
     * @return bool
     */
    public function offsetExists($key): bool
    {
        return $this->bound($key);
    }

    /**
     * Get the value at a given offset.
     *
     * @param  string  $key
     * @return mixed
     */
    public function offsetGet($key): mixed
    {
        return $this->make($key);
    }

    /**
     * Set the value at a given offset.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return void
     */
    public function offsetSet($key, $value): void
    {
        $this->bind($key, $value instanceof Closure ? $value : fn () => $value);
    }

    /**
     * Unset the value at a given offset.
     *
     * @param  string  $key
     * @return void
     */
    public function offsetUnset($key): void
    {
        unset($this->bindings[$key], $this->instances[$key], $this->resolved[$key]);
    }

    /**
     * Dynamically access container services.
     *
     * @param  string  $key
     * @return mixed
     */
    public function __get($key)
    {
        return $this[$key];
    }

    /**
     * Dynamically set container services.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return void
     */
    public function __set($key, $value)
    {
        $this[$key] = $value;
    }
}
<?php

namespace Illuminate\Contracts\Debug;

use Throwable;

interface ExceptionHandler
{
    /**
     * Report or log an exception.
     *
     * @param  \Throwable  $e
     * @return void
     *
     * @throws \Throwable
     */
    public function report(Throwable $e);

    /**
     * Determine if the exception should be reported.
     *
     * @param  \Throwable  $e
     * @return bool
     */
    public function shouldReport(Throwable $e);

    /**
     * Render an exception into an HTTP response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Throwable  $e
     * @return \Symfony\Component\HttpFoundation\Response
     *
     * @throws \Throwable
     */
    public function render($request, Throwable $e);

    /**
     * Render an exception to the console.
     *
     * @param  \Symfony\Component\Console\Output\OutputInterface  $output
     * @param  \Throwable  $e
     * @return void
     *
     * @internal This method is not meant to be used or overwritten outside the framework.
     */
    public function renderForConsole($output, Throwable $e);
}
<?php

namespace Illuminate\Contracts\Hashing;

interface Hasher
{
    /**
     * Get information about the given hashed value.
     *
     * @param  string  $hashedValue
     * @return array
     */
    public function info($hashedValue);

    /**
     * Hash the given value.
     *
     * @param  string  $value
     * @param  array  $options
     * @return string
     */
    public function make($value, array $options = []);

    /**
     * Check the given plain value against a hash.
     *
     * @param  string  $value
     * @param  string  $hashedValue
     * @param  array  $options
     * @return bool
     */
    public function check($value, $hashedValue, array $options = []);

    /**
     * Check if the given hash has been hashed using the given options.
     *
     * @param  string  $hashedValue
     * @param  array  $options
     * @return bool
     */
    public function needsRehash($hashedValue, array $options = []);
}
<?php

namespace Illuminate\Contracts\Session\Middleware;

interface AuthenticatesSessions
{
    //
}
<?php

namespace Illuminate\Contracts\Session;

interface Session
{
    /**
     * Get the name of the session.
     *
     * @return string
     */
    public function getName();

    /**
     * Set the name of the session.
     *
     * @param  string  $name
     * @return void
     */
    public function setName($name);

    /**
     * Get the current session ID.
     *
     * @return string
     */
    public function getId();

    /**
     * Set the session ID.
     *
     * @param  string  $id
     * @return void
     */
    public function setId($id);

    /**
     * Start the session, reading the data from a handler.
     *
     * @return bool
     */
    public function start();

    /**
     * Save the session data to storage.
     *
     * @return void
     */
    public function save();

    /**
     * Get all of the session data.
     *
     * @return array
     */
    public function all();

    /**
     * Checks if a key exists.
     *
     * @param  string|array  $key
     * @return bool
     */
    public function exists($key);

    /**
     * Checks if a key is present and not null.
     *
     * @param  string|array  $key
     * @return bool
     */
    public function has($key);

    /**
     * Get an item from the session.
     *
     * @param  string  $key
     * @param  mixed  $default
     * @return mixed
     */
    public function get($key, $default = null);

    /**
     * Get the value of a given key and then forget it.
     *
     * @param  string  $key
     * @param  mixed  $default
     * @return mixed
     */
    public function pull($key, $default = null);

    /**
     * Put a key / value pair or array of key / value pairs in the session.
     *
     * @param  string|array  $key
     * @param  mixed  $value
     * @return void
     */
    public function put($key, $value = null);

    /**
     * Get the CSRF token value.
     *
     * @return string
     */
    public function token();

    /**
     * Regenerate the CSRF token value.
     *
     * @return void
     */
    public function regenerateToken();

    /**
     * Remove an item from the session, returning its value.
     *
     * @param  string  $key
     * @return mixed
     */
    public function remove($key);

    /**
     * Remove one or many items from the session.
     *
     * @param  string|array  $keys
     * @return void
     */
    public function forget($keys);

    /**
     * Remove all of the items from the session.
     *
     * @return void
     */
    public function flush();

    /**
     * Flush the session data and regenerate the ID.
     *
     * @return bool
     */
    public function invalidate();

    /**
     * Generate a new session identifier.
     *
     * @param  bool  $destroy
     * @return bool
     */
    public function regenerate($destroy = false);

    /**
     * Generate a new session ID for the session.
     *
     * @param  bool  $destroy
     * @return bool
     */
    public function migrate($destroy = false);

    /**
     * Determine if the session has been started.
     *
     * @return bool
     */
    public function isStarted();

    /**
     * Get the previous URL from the session.
     *
     * @return string|null
     */
    public function previousUrl();

    /**
     * Set the "previous" URL in the session.
     *
     * @param  string  $url
     * @return void
     */
    public function setPreviousUrl($url);

    /**
     * Get the session handler instance.
     *
     * @return \SessionHandlerInterface
     */
    public function getHandler();

    /**
     * Determine if the session handler needs a request.
     *
     * @return bool
     */
    public function handlerNeedsRequest();

    /**
     * Set the request on the handler instance.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return void
     */
    public function setRequestOnHandler($request);
}
<?php

namespace Illuminate\Contracts\Filesystem;

interface Filesystem
{
    /**
     * The public visibility setting.
     *
     * @var string
     */
    const VISIBILITY_PUBLIC = 'public';

    /**
     * The private visibility setting.
     *
     * @var string
     */
    const VISIBILITY_PRIVATE = 'private';

    /**
     * Determine if a file exists.
     *
     * @param  string  $path
     * @return bool
     */
    public function exists($path);

    /**
     * Get the contents of a file.
     *
     * @param  string  $path
     * @return string|null
     */
    public function get($path);

    /**
     * Get a resource to read the file.
     *
     * @param  string  $path
     * @return resource|null The path resource or null on failure.
     */
    public function readStream($path);

    /**
     * Write the contents of a file.
     *
     * @param  string  $path
     * @param  \Psr\Http\Message\StreamInterface|\Illuminate\Http\File|\Illuminate\Http\UploadedFile|string|resource  $contents
     * @param  mixed  $options
     * @return bool
     */
    public function put($path, $contents, $options = []);

    /**
     * Write a new file using a stream.
     *
     * @param  string  $path
     * @param  resource  $resource
     * @param  array  $options
     * @return bool
     */
    public function writeStream($path, $resource, array $options = []);

    /**
     * Get the visibility for the given path.
     *
     * @param  string  $path
     * @return string
     */
    public function getVisibility($path);

    /**
     * Set the visibility for the given path.
     *
     * @param  string  $path
     * @param  string  $visibility
     * @return bool
     */
    public function setVisibility($path, $visibility);

    /**
     * Prepend to a file.
     *
     * @param  string  $path
     * @param  string  $data
     * @return bool
     */
    public function prepend($path, $data);

    /**
     * Append to a file.
     *
     * @param  string  $path
     * @param  string  $data
     * @return bool
     */
    public function append($path, $data);

    /**
     * Delete the file at a given path.
     *
     * @param  string|array  $paths
     * @return bool
     */
    public function delete($paths);

    /**
     * Copy a file to a new location.
     *
     * @param  string  $from
     * @param  string  $to
     * @return bool
     */
    public function copy($from, $to);

    /**
     * Move a file to a new location.
     *
     * @param  string  $from
     * @param  string  $to
     * @return bool
     */
    public function move($from, $to);

    /**
     * Get the file size of a given file.
     *
     * @param  string  $path
     * @return int
     */
    public function size($path);

    /**
     * Get the file's last modification time.
     *
     * @param  string  $path
     * @return int
     */
    public function lastModified($path);

    /**
     * Get an array of all files in a directory.
     *
     * @param  string|null  $directory
     * @param  bool  $recursive
     * @return array
     */
    public function files($directory = null, $recursive = false);

    /**
     * Get all of the files from the given directory (recursive).
     *
     * @param  string|null  $directory
     * @return array
     */
    public function allFiles($directory = null);

    /**
     * Get all of the directories within a given directory.
     *
     * @param  string|null  $directory
     * @param  bool  $recursive
     * @return array
     */
    public function directories($directory = null, $recursive = false);

    /**
     * Get all (recursive) of the directories within a given directory.
     *
     * @param  string|null  $directory
     * @return array
     */
    public function allDirectories($directory = null);

    /**
     * Create a directory.
     *
     * @param  string  $path
     * @return bool
     */
    public function makeDirectory($path);

    /**
     * Recursively delete a directory.
     *
     * @param  string  $directory
     * @return bool
     */
    public function deleteDirectory($directory);
}
<?php

namespace Illuminate\Contracts\Filesystem;

use Exception;

class LockTimeoutException extends Exception
{
    //
}
<?php

namespace Illuminate\Contracts\Filesystem;

use Exception;

class FileNotFoundException extends Exception
{
    //
}
<?php

namespace Illuminate\Contracts\Filesystem;

interface Cloud extends Filesystem
{
    /**
     * Get the URL for the file at the given path.
     *
     * @param  string  $path
     * @return string
     */
    public function url($path);
}
<?php

namespace Illuminate\Contracts\Filesystem;

interface Factory
{
    /**
     * Get a filesystem implementation.
     *
     * @param  string|null  $name
     * @return \Illuminate\Contracts\Filesystem\Filesystem
     */
    public function disk($name = null);
}
<?php

namespace Illuminate\Contracts\View;

interface Engine
{
    /**
     * Get the evaluated contents of the view.
     *
     * @param  string  $path
     * @param  array  $data
     * @return string
     */
    public function get($path, array $data = []);
}
<?php

namespace Illuminate\Contracts\View;

use Illuminate\Contracts\Support\Renderable;

interface View extends Renderable
{
    /**
     * Get the name of the view.
     *
     * @return string
     */
    public function name();

    /**
     * Add a piece of data to the view.
     *
     * @param  string|array  $key
     * @param  mixed  $value
     * @return $this
     */
    public function with($key, $value = null);

    /**
     * Get the array of view data.
     *
     * @return array
     */
    public function getData();
}
<?php

namespace Illuminate\Contracts\View;

use Exception;

class ViewCompilationException extends Exception
{
    //
}
<?php

namespace Illuminate\Contracts\View;

interface Factory
{
    /**
     * Determine if a given view exists.
     *
     * @param  string  $view
     * @return bool
     */
    public function exists($view);

    /**
     * Get the evaluated view contents for the given path.
     *
     * @param  string  $path
     * @param  \Illuminate\Contracts\Support\Arrayable|array  $data
     * @param  array  $mergeData
     * @return \Illuminate\Contracts\View\View
     */
    public function file($path, $data = [], $mergeData = []);

    /**
     * Get the evaluated view contents for the given view.
     *
     * @param  string  $view
     * @param  \Illuminate\Contracts\Support\Arrayable|array  $data
     * @param  array  $mergeData
     * @return \Illuminate\Contracts\View\View
     */
    public function make($view, $data = [], $mergeData = []);

    /**
     * Add a piece of shared data to the environment.
     *
     * @param  array|string  $key
     * @param  mixed  $value
     * @return mixed
     */
    public function share($key, $value = null);

    /**
     * Register a view composer event.
     *
     * @param  array|string  $views
     * @param  \Closure|string  $callback
     * @return array
     */
    public function composer($views, $callback);

    /**
     * Register a view creator event.
     *
     * @param  array|string  $views
     * @param  \Closure|string  $callback
     * @return array
     */
    public function creator($views, $callback);

    /**
     * Add a new namespace to the loader.
     *
     * @param  string  $namespace
     * @param  string|array  $hints
     * @return $this
     */
    public function addNamespace($namespace, $hints);

    /**
     * Replace the namespace hints for the given namespace.
     *
     * @param  string  $namespace
     * @param  string|array  $hints
     * @return $this
     */
    public function replaceNamespace($namespace, $hints);
}
<?php

namespace Illuminate\Contracts\Console;

interface Application
{
    /**
     * Run an Artisan console command by name.
     *
     * @param  string  $command
     * @param  array  $parameters
     * @param  \Symfony\Component\Console\Output\OutputInterface|null  $outputBuffer
     * @return int
     */
    public function call($command, array $parameters = [], $outputBuffer = null);

    /**
     * Get the output from the last command.
     *
     * @return string
     */
    public function output();
}
<?php

namespace Illuminate\Contracts\Console;

interface PromptsForMissingInput
{
    //
}
<?php

namespace Illuminate\Contracts\Console;

interface Kernel
{
    /**
     * Bootstrap the application for artisan commands.
     *
     * @return void
     */
    public function bootstrap();

    /**
     * Handle an incoming console command.
     *
     * @param  \Symfony\Component\Console\Input\InputInterface  $input
     * @param  \Symfony\Component\Console\Output\OutputInterface|null  $output
     * @return int
     */
    public function handle($input, $output = null);

    /**
     * Run an Artisan console command by name.
     *
     * @param  string  $command
     * @param  array  $parameters
     * @param  \Symfony\Component\Console\Output\OutputInterface|null  $outputBuffer
     * @return int
     */
    public function call($command, array $parameters = [], $outputBuffer = null);

    /**
     * Queue an Artisan console command by name.
     *
     * @param  string  $command
     * @param  array  $parameters
     * @return \Illuminate\Foundation\Bus\PendingDispatch
     */
    public function queue($command, array $parameters = []);

    /**
     * Get all of the commands registered with the console.
     *
     * @return array
     */
    public function all();

    /**
     * Get the output for the last run command.
     *
     * @return string
     */
    public function output();

    /**
     * Terminate the application.
     *
     * @param  \Symfony\Component\Console\Input\InputInterface  $input
     * @param  int  $status
     * @return void
     */
    public function terminate($input, $status);
}
<?php

namespace Illuminate\Contracts\Console;

interface Isolatable
{
    //
}
<?php

namespace Illuminate\Contracts\Queue;

interface ShouldQueue
{
    //
}
<?php

namespace Illuminate\Contracts\Queue;

interface ShouldBeUniqueUntilProcessing extends ShouldBeUnique
{
    //
}
<?php

namespace Illuminate\Contracts\Queue;

interface ShouldBeEncrypted
{
    //
}
<?php

namespace Illuminate\Contracts\Queue;

interface ShouldBeUnique
{
    //
}
<?php

namespace Illuminate\Contracts\Queue;

interface Monitor
{
    /**
     * Register a callback to be executed on every iteration through the queue loop.
     *
     * @param  mixed  $callback
     * @return void
     */
    public function looping($callback);

    /**
     * Register a callback to be executed when a job fails after the maximum number of retries.
     *
     * @param  mixed  $callback
     * @return void
     */
    public function failing($callback);

    /**
     * Register a callback to be executed when a daemon queue is stopping.
     *
     * @param  mixed  $callback
     * @return void
     */
    public function stopping($callback);
}
<?php

namespace Illuminate\Contracts\Queue;

interface Job
{
    /**
     * Get the UUID of the job.
     *
     * @return string|null
     */
    public function uuid();

    /**
     * Get the job identifier.
     *
     * @return string
     */
    public function getJobId();

    /**
     * Get the decoded body of the job.
     *
     * @return array
     */
    public function payload();

    /**
     * Fire the job.
     *
     * @return void
     */
    public function fire();

    /**
     * Release the job back into the queue after (n) seconds.
     *
     * @param  int  $delay
     * @return void
     */
    public function release($delay = 0);

    /**
     * Determine if the job was released back into the queue.
     *
     * @return bool
     */
    public function isReleased();

    /**
     * Delete the job from the queue.
     *
     * @return void
     */
    public function delete();

    /**
     * Determine if the job has been deleted.
     *
     * @return bool
     */
    public function isDeleted();

    /**
     * Determine if the job has been deleted or released.
     *
     * @return bool
     */
    public function isDeletedOrReleased();

    /**
     * Get the number of times the job has been attempted.
     *
     * @return int
     */
    public function attempts();

    /**
     * Determine if the job has been marked as a failure.
     *
     * @return bool
     */
    public function hasFailed();

    /**
     * Mark the job as "failed".
     *
     * @return void
     */
    public function markAsFailed();

    /**
     * Delete the job, call the "failed" method, and raise the failed job event.
     *
     * @param  \Throwable|null  $e
     * @return void
     */
    public function fail($e = null);

    /**
     * Get the number of times to attempt a job.
     *
     * @return int|null
     */
    public function maxTries();

    /**
     * Get the maximum number of exceptions allowed, regardless of attempts.
     *
     * @return int|null
     */
    public function maxExceptions();

    /**
     * Get the number of seconds the job can run.
     *
     * @return int|null
     */
    public function timeout();

    /**
     * Get the timestamp indicating when the job should timeout.
     *
     * @return int|null
     */
    public function retryUntil();

    /**
     * Get the name of the queued job class.
     *
     * @return string
     */
    public function getName();

    /**
     * Get the resolved name of the queued job class.
     *
     * Resolves the name of "wrapped" jobs such as class-based handlers.
     *
     * @return string
     */
    public function resolveName();

    /**
     * Get the name of the connection the job belongs to.
     *
     * @return string
     */
    public function getConnectionName();

    /**
     * Get the name of the queue the job belongs to.
     *
     * @return string
     */
    public function getQueue();

    /**
     * Get the raw body string for the job.
     *
     * @return string
     */
    public function getRawBody();
}
<?php

namespace Illuminate\Contracts\Queue;

interface ClearableQueue
{
    /**
     * Delete all of the jobs from the queue.
     *
     * @param  string  $queue
     * @return int
     */
    public function clear($queue);
}
<?php

namespace Illuminate\Contracts\Queue;

use InvalidArgumentException;

class EntityNotFoundException extends InvalidArgumentException
{
    /**
     * Create a new exception instance.
     *
     * @param  string  $type
     * @param  mixed  $id
     * @return void
     */
    public function __construct($type, $id)
    {
        $id = (string) $id;

        parent::__construct("Queueable entity [{$type}] not found for ID [{$id}].");
    }
}
<?php

namespace Illuminate\Contracts\Queue;

interface EntityResolver
{
    /**
     * Resolve the entity for the given ID.
     *
     * @param  string  $type
     * @param  mixed  $id
     * @return mixed
     */
    public function resolve($type, $id);
}
<?php

namespace Illuminate\Contracts\Queue;

interface Queue
{
    /**
     * Get the size of the queue.
     *
     * @param  string|null  $queue
     * @return int
     */
    public function size($queue = null);

    /**
     * Push a new job onto the queue.
     *
     * @param  string|object  $job
     * @param  mixed  $data
     * @param  string|null  $queue
     * @return mixed
     */
    public function push($job, $data = '', $queue = null);

    /**
     * Push a new job onto the queue.
     *
     * @param  string  $queue
     * @param  string|object  $job
     * @param  mixed  $data
     * @return mixed
     */
    public function pushOn($queue, $job, $data = '');

    /**
     * Push a raw payload onto the queue.
     *
     * @param  string  $payload
     * @param  string|null  $queue
     * @param  array  $options
     * @return mixed
     */
    public function pushRaw($payload, $queue = null, array $options = []);

    /**
     * Push a new job onto the queue after (n) seconds.
     *
     * @param  \DateTimeInterface|\DateInterval|int  $delay
     * @param  string|object  $job
     * @param  mixed  $data
     * @param  string|null  $queue
     * @return mixed
     */
    public function later($delay, $job, $data = '', $queue = null);

    /**
     * Push a new job onto a specific queue after (n) seconds.
     *
     * @param  string  $queue
     * @param  \DateTimeInterface|\DateInterval|int  $delay
     * @param  string|object  $job
     * @param  mixed  $data
     * @return mixed
     */
    public function laterOn($queue, $delay, $job, $data = '');

    /**
     * Push an array of jobs onto the queue.
     *
     * @param  array  $jobs
     * @param  mixed  $data
     * @param  string|null  $queue
     * @return mixed
     */
    public function bulk($jobs, $data = '', $queue = null);

    /**
     * Pop the next job off of the queue.
     *
     * @param  string|null  $queue
     * @return \Illuminate\Contracts\Queue\Job|null
     */
    public function pop($queue = null);

    /**
     * Get the connection name for the queue.
     *
     * @return string
     */
    public function getConnectionName();

    /**
     * Set the connection name for the queue.
     *
     * @param  string  $name
     * @return $this
     */
    public function setConnectionName($name);
}
<?php

namespace Illuminate\Contracts\Queue;

interface ShouldQueueAfterCommit extends ShouldQueue
{
    //
}
<?php

namespace Illuminate\Contracts\Queue;

interface QueueableCollection
{
    /**
     * Get the type of the entities being queued.
     *
     * @return string|null
     */
    public function getQueueableClass();

    /**
     * Get the identifiers for all of the entities.
     *
     * @return array<int, mixed>
     */
    public function getQueueableIds();

    /**
     * Get the relationships of the entities being queued.
     *
     * @return array<int, string>
     */
    public function getQueueableRelations();

    /**
     * Get the connection of the entities being queued.
     *
     * @return string|null
     */
    public function getQueueableConnection();
}
<?php

namespace Illuminate\Contracts\Queue;

interface Factory
{
    /**
     * Resolve a queue connection instance.
     *
     * @param  string|null  $name
     * @return \Illuminate\Contracts\Queue\Queue
     */
    public function connection($name = null);
}
<?php

namespace Illuminate\Contracts\Queue;

interface QueueableEntity
{
    /**
     * Get the queueable identity for the entity.
     *
     * @return mixed
     */
    public function getQueueableId();

    /**
     * Get the relationships for the entity.
     *
     * @return array
     */
    public function getQueueableRelations();

    /**
     * Get the connection of the entity.
     *
     * @return string|null
     */
    public function getQueueableConnection();
}
<?php

namespace Illuminate\Contracts\Pipeline;

use Closure;

interface Pipeline
{
    /**
     * Set the traveler object being sent on the pipeline.
     *
     * @param  mixed  $traveler
     * @return $this
     */
    public function send($traveler);

    /**
     * Set the stops of the pipeline.
     *
     * @param  dynamic|array  $stops
     * @return $this
     */
    public function through($stops);

    /**
     * Set the method to call on the stops.
     *
     * @param  string  $method
     * @return $this
     */
    public function via($method);

    /**
     * Run the pipeline with a final destination callback.
     *
     * @param  \Closure  $destination
     * @return mixed
     */
    public function then(Closure $destination);
}
<?php

namespace Illuminate\Contracts\Pipeline;

interface Hub
{
    /**
     * Send an object through one of the available pipelines.
     *
     * @param  mixed  $object
     * @param  string|null  $pipeline
     * @return mixed
     */
    public function pipe($object, $pipeline = null);
}
<?php

namespace Illuminate\Contracts\Translation;

interface Loader
{
    /**
     * Load the messages for the given locale.
     *
     * @param  string  $locale
     * @param  string  $group
     * @param  string|null  $namespace
     * @return array
     */
    public function load($locale, $group, $namespace = null);

    /**
     * Add a new namespace to the loader.
     *
     * @param  string  $namespace
     * @param  string  $hint
     * @return void
     */
    public function addNamespace($namespace, $hint);

    /**
     * Add a new JSON path to the loader.
     *
     * @param  string  $path
     * @return void
     */
    public function addJsonPath($path);

    /**
     * Get an array of all the registered namespaces.
     *
     * @return array
     */
    public function namespaces();
}
<?php

namespace Illuminate\Contracts\Translation;

interface Translator
{
    /**
     * Get the translation for a given key.
     *
     * @param  string  $key
     * @param  array  $replace
     * @param  string|null  $locale
     * @return mixed
     */
    public function get($key, array $replace = [], $locale = null);

    /**
     * Get a translation according to an integer value.
     *
     * @param  string  $key
     * @param  \Countable|int|float|array  $number
     * @param  array  $replace
     * @param  string|null  $locale
     * @return string
     */
    public function choice($key, $number, array $replace = [], $locale = null);

    /**
     * Get the default locale being used.
     *
     * @return string
     */
    public function getLocale();

    /**
     * Set the default locale.
     *
     * @param  string  $locale
     * @return void
     */
    public function setLocale($locale);
}
<?php

namespace Illuminate\Contracts\Translation;

interface HasLocalePreference
{
    /**
     * Get the preferred locale of the entity.
     *
     * @return string|null
     */
    public function preferredLocale();
}
<?php

namespace Illuminate\Contracts\Support;

interface Htmlable
{
    /**
     * Get content as a string of HTML.
     *
     * @return string
     */
    public function toHtml();
}
<?php

namespace Illuminate\Contracts\Support;

interface Jsonable
{
    /**
     * Convert the object to its JSON representation.
     *
     * @param  int  $options
     * @return string
     */
    public function toJson($options = 0);
}
<?php

namespace Illuminate\Contracts\Support;

use ArrayAccess;
use IteratorAggregate;

interface ValidatedData extends Arrayable, ArrayAccess, IteratorAggregate
{
    //
}
<?php

namespace Illuminate\Contracts\Support;

interface CanBeEscapedWhenCastToString
{
    /**
     * Indicate that the object's string representation should be escaped when __toString is invoked.
     *
     * @param  bool  $escape
     * @return $this
     */
    public function escapeWhenCastingToString($escape = true);
}
<?php

namespace Illuminate\Contracts\Support;

interface DeferringDisplayableValue
{
    /**
     * Resolve the displayable value that the class is deferring.
     *
     * @return \Illuminate\Contracts\Support\Htmlable|string
     */
    public function resolveDisplayableValue();
}
<?php

namespace Illuminate\Contracts\Support;

interface MessageProvider
{
    /**
     * Get the messages for the instance.
     *
     * @return \Illuminate\Contracts\Support\MessageBag
     */
    public function getMessageBag();
}
<?php

namespace Illuminate\Contracts\Support;

use Countable;

interface MessageBag extends Arrayable, Countable
{
    /**
     * Get the keys present in the message bag.
     *
     * @return array
     */
    public function keys();

    /**
     * Add a message to the bag.
     *
     * @param  string  $key
     * @param  string  $message
     * @return $this
     */
    public function add($key, $message);

    /**
     * Merge a new array of messages into the bag.
     *
     * @param  \Illuminate\Contracts\Support\MessageProvider|array  $messages
     * @return $this
     */
    public function merge($messages);

    /**
     * Determine if messages exist for a given key.
     *
     * @param  string|array  $key
     * @return bool
     */
    public function has($key);

    /**
     * Get the first message from the bag for a given key.
     *
     * @param  string|null  $key
     * @param  string|null  $format
     * @return string
     */
    public function first($key = null, $format = null);

    /**
     * Get all of the messages from the bag for a given key.
     *
     * @param  string  $key
     * @param  string|null  $format
     * @return array
     */
    public function get($key, $format = null);

    /**
     * Get all of the messages for every key in the bag.
     *
     * @param  string|null  $format
     * @return array
     */
    public function all($format = null);

    /**
     * Remove a message from the bag.
     *
     * @param  string  $key
     * @return $this
     */
    public function forget($key);

    /**
     * Get the raw messages in the container.
     *
     * @return array
     */
    public function getMessages();

    /**
     * Get the default message format.
     *
     * @return string
     */
    public function getFormat();

    /**
     * Set the default message format.
     *
     * @param  string  $format
     * @return $this
     */
    public function setFormat($format = ':message');

    /**
     * Determine if the message bag has any messages.
     *
     * @return bool
     */
    public function isEmpty();

    /**
     * Determine if the message bag has any messages.
     *
     * @return bool
     */
    public function isNotEmpty();
}
<?php

namespace Illuminate\Contracts\Support;

interface DeferrableProvider
{
    /**
     * Get the services provided by the provider.
     *
     * @return array
     */
    public function provides();
}
<?php

namespace Illuminate\Contracts\Support;

interface Renderable
{
    /**
     * Get the evaluated contents of the object.
     *
     * @return string
     */
    public function render();
}
<?php

namespace Illuminate\Contracts\Support;

interface Responsable
{
    /**
     * Create an HTTP response that represents the object.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function toResponse($request);
}
<?php

namespace Illuminate\Contracts\Support;

/**
 * @template TKey of array-key
 * @template TValue
 */
interface Arrayable
{
    /**
     * Get the instance as an array.
     *
     * @return array<TKey, TValue>
     */
    public function toArray();
}
<?php

namespace Illuminate\Contracts\Mail;

interface Attachable
{
    /**
     * Get an attachment instance for this entity.
     *
     * @return \Illuminate\Mail\Attachment
     */
    public function toMailAttachment();
}
<?php

namespace Illuminate\Contracts\Mail;

interface MailQueue
{
    /**
     * Queue a new e-mail message for sending.
     *
     * @param  \Illuminate\Contracts\Mail\Mailable|string|array  $view
     * @param  string|null  $queue
     * @return mixed
     */
    public function queue($view, $queue = null);

    /**
     * Queue a new e-mail message for sending after (n) seconds.
     *
     * @param  \DateTimeInterface|\DateInterval|int  $delay
     * @param  \Illuminate\Contracts\Mail\Mailable|string|array  $view
     * @param  string|null  $queue
     * @return mixed
     */
    public function later($delay, $view, $queue = null);
}
<?php

namespace Illuminate\Contracts\Mail;

interface Mailer
{
    /**
     * Begin the process of mailing a mailable class instance.
     *
     * @param  mixed  $users
     * @return \Illuminate\Mail\PendingMail
     */
    public function to($users);

    /**
     * Begin the process of mailing a mailable class instance.
     *
     * @param  mixed  $users
     * @return \Illuminate\Mail\PendingMail
     */
    public function bcc($users);

    /**
     * Send a new message with only a raw text part.
     *
     * @param  string  $text
     * @param  mixed  $callback
     * @return \Illuminate\Mail\SentMessage|null
     */
    public function raw($text, $callback);

    /**
     * Send a new message using a view.
     *
     * @param  \Illuminate\Contracts\Mail\Mailable|string|array  $view
     * @param  array  $data
     * @param  \Closure|string|null  $callback
     * @return \Illuminate\Mail\SentMessage|null
     */
    public function send($view, array $data = [], $callback = null);
}
<?php

namespace Illuminate\Contracts\Mail;

use Illuminate\Contracts\Queue\Factory as Queue;

interface Mailable
{
    /**
     * Send the message using the given mailer.
     *
     * @param  \Illuminate\Contracts\Mail\Factory|\Illuminate\Contracts\Mail\Mailer  $mailer
     * @return \Illuminate\Mail\SentMessage|null
     */
    public function send($mailer);

    /**
     * Queue the given message.
     *
     * @param  \Illuminate\Contracts\Queue\Factory  $queue
     * @return mixed
     */
    public function queue(Queue $queue);

    /**
     * Deliver the queued message after (n) seconds.
     *
     * @param  \DateTimeInterface|\DateInterval|int  $delay
     * @param  \Illuminate\Contracts\Queue\Factory  $queue
     * @return mixed
     */
    public function later($delay, Queue $queue);

    /**
     * Set the recipients of the message.
     *
     * @param  object|array|string  $address
     * @param  string|null  $name
     * @return self
     */
    public function cc($address, $name = null);

    /**
     * Set the recipients of the message.
     *
     * @param  object|array|string  $address
     * @param  string|null  $name
     * @return $this
     */
    public function bcc($address, $name = null);

    /**
     * Set the recipients of the message.
     *
     * @param  object|array|string  $address
     * @param  string|null  $name
     * @return $this
     */
    public function to($address, $name = null);

    /**
     * Set the locale of the message.
     *
     * @param  string  $locale
     * @return $this
     */
    public function locale($locale);

    /**
     * Set the name of the mailer that should be used to send the message.
     *
     * @param  string  $mailer
     * @return $this
     */
    public function mailer($mailer);
}
<?php

namespace Illuminate\Contracts\Mail;

interface Factory
{
    /**
     * Get a mailer instance by name.
     *
     * @param  string|null  $name
     * @return \Illuminate\Contracts\Mail\Mailer
     */
    public function mailer($name = null);
}
<?php

namespace Illuminate\Contracts\Validation;

use Closure;

interface ValidationRule
{
    /**
     * Run the validation rule.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @param  \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString  $fail
     * @return void
     */
    public function validate(string $attribute, mixed $value, Closure $fail): void;
}
<?php

namespace Illuminate\Contracts\Validation;

use Illuminate\Validation\Validator;

interface ValidatorAwareRule
{
    /**
     * Set the current validator.
     *
     * @param  \Illuminate\Validation\Validator  $validator
     * @return $this
     */
    public function setValidator(Validator $validator);
}
<?php

namespace Illuminate\Contracts\Validation;

/**
 * @deprecated see ValidationRule
 */
interface Rule
{
    /**
     * Determine if the validation rule passes.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @return bool
     */
    public function passes($attribute, $value);

    /**
     * Get the validation error message.
     *
     * @return string|array
     */
    public function message();
}
<?php

namespace Illuminate\Contracts\Validation;

interface ValidatesWhenResolved
{
    /**
     * Validate the given class instance.
     *
     * @return void
     */
    public function validateResolved();
}
<?php

namespace Illuminate\Contracts\Validation;

use Illuminate\Contracts\Support\MessageProvider;

interface Validator extends MessageProvider
{
    /**
     * Run the validator's rules against its data.
     *
     * @return array
     *
     * @throws \Illuminate\Validation\ValidationException
     */
    public function validate();

    /**
     * Get the attributes and values that were validated.
     *
     * @return array
     *
     * @throws \Illuminate\Validation\ValidationException
     */
    public function validated();

    /**
     * Determine if the data fails the validation rules.
     *
     * @return bool
     */
    public function fails();

    /**
     * Get the failed validation rules.
     *
     * @return array
     */
    public function failed();

    /**
     * Add conditions to a given field based on a Closure.
     *
     * @param  string|array  $attribute
     * @param  string|array  $rules
     * @param  callable  $callback
     * @return $this
     */
    public function sometimes($attribute, $rules, callable $callback);

    /**
     * Add an after validation callback.
     *
     * @param  callable|string  $callback
     * @return $this
     */
    public function after($callback);

    /**
     * Get all of the validation error messages.
     *
     * @return \Illuminate\Support\MessageBag
     */
    public function errors();
}
<?php

namespace Illuminate\Contracts\Validation;

interface DataAwareRule
{
    /**
     * Set the data under validation.
     *
     * @param  array  $data
     * @return $this
     */
    public function setData(array $data);
}
<?php

namespace Illuminate\Contracts\Validation;

/**
 * @deprecated see ValidationRule
 */
interface ImplicitRule extends Rule
{
    //
}
<?php

namespace Illuminate\Contracts\Validation;

interface UncompromisedVerifier
{
    /**
     * Verify that the given data has not been compromised in data leaks.
     *
     * @param  array  $data
     * @return bool
     */
    public function verify($data);
}
<?php

namespace Illuminate\Contracts\Validation;

interface Factory
{
    /**
     * Create a new Validator instance.
     *
     * @param  array  $data
     * @param  array  $rules
     * @param  array  $messages
     * @param  array  $attributes
     * @return \Illuminate\Contracts\Validation\Validator
     */
    public function make(array $data, array $rules, array $messages = [], array $attributes = []);

    /**
     * Register a custom validator extension.
     *
     * @param  string  $rule
     * @param  \Closure|string  $extension
     * @param  string|null  $message
     * @return void
     */
    public function extend($rule, $extension, $message = null);

    /**
     * Register a custom implicit validator extension.
     *
     * @param  string  $rule
     * @param  \Closure|string  $extension
     * @param  string|null  $message
     * @return void
     */
    public function extendImplicit($rule, $extension, $message = null);

    /**
     * Register a custom implicit validator message replacer.
     *
     * @param  string  $rule
     * @param  \Closure|string  $replacer
     * @return void
     */
    public function replacer($rule, $replacer);
}
<?php

namespace Illuminate\Contracts\Validation;

use Closure;

/**
 * @deprecated see ValidationRule
 */
interface InvokableRule
{
    /**
     * Run the validation rule.
     *
     * @param  string  $attribute
     * @param  mixed  $value
     * @param  \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString  $fail
     * @return void
     */
    public function __invoke(string $attribute, mixed $value, Closure $fail);
}
<?php

namespace Illuminate\Contracts\Foundation;

interface ExceptionRenderer
{
    /**
     * Renders the given exception as HTML.
     *
     * @param  \Throwable  $throwable
     * @return string
     */
    public function render($throwable);
}
<?php

namespace Illuminate\Contracts\Foundation;

use Illuminate\Contracts\Container\Container;

interface Application extends Container
{
    /**
     * Get the version number of the application.
     *
     * @return string
     */
    public function version();

    /**
     * Get the base path of the Laravel installation.
     *
     * @param  string  $path
     * @return string
     */
    public function basePath($path = '');

    /**
     * Get the path to the bootstrap directory.
     *
     * @param  string  $path
     * @return string
     */
    public function bootstrapPath($path = '');

    /**
     * Get the path to the application configuration files.
     *
     * @param  string  $path
     * @return string
     */
    public function configPath($path = '');

    /**
     * Get the path to the database directory.
     *
     * @param  string  $path
     * @return string
     */
    public function databasePath($path = '');

    /**
     * Get the path to the language files.
     *
     * @param  string  $path
     * @return string
     */
    public function langPath($path = '');

    /**
     * Get the path to the public directory.
     *
     * @param  string  $path
     * @return string
     */
    public function publicPath($path = '');

    /**
     * Get the path to the resources directory.
     *
     * @param  string  $path
     * @return string
     */
    public function resourcePath($path = '');

    /**
     * Get the path to the storage directory.
     *
     * @param  string  $path
     * @return string
     */
    public function storagePath($path = '');

    /**
     * Get or check the current application environment.
     *
     * @param  string|array  ...$environments
     * @return string|bool
     */
    public function environment(...$environments);

    /**
     * Determine if the application is running in the console.
     *
     * @return bool
     */
    public function runningInConsole();

    /**
     * Determine if the application is running unit tests.
     *
     * @return bool
     */
    public function runningUnitTests();

    /**
     * Determine if the application is running with debug mode enabled.
     *
     * @return bool
     */
    public function hasDebugModeEnabled();

    /**
     * Get an instance of the maintenance mode manager implementation.
     *
     * @return \Illuminate\Contracts\Foundation\MaintenanceMode
     */
    public function maintenanceMode();

    /**
     * Determine if the application is currently down for maintenance.
     *
     * @return bool
     */
    public function isDownForMaintenance();

    /**
     * Register all of the configured providers.
     *
     * @return void
     */
    public function registerConfiguredProviders();

    /**
     * Register a service provider with the application.
     *
     * @param  \Illuminate\Support\ServiceProvider|string  $provider
     * @param  bool  $force
     * @return \Illuminate\Support\ServiceProvider
     */
    public function register($provider, $force = false);

    /**
     * Register a deferred provider and service.
     *
     * @param  string  $provider
     * @param  string|null  $service
     * @return void
     */
    public function registerDeferredProvider($provider, $service = null);

    /**
     * Resolve a service provider instance from the class name.
     *
     * @param  string  $provider
     * @return \Illuminate\Support\ServiceProvider
     */
    public function resolveProvider($provider);

    /**
     * Boot the application's service providers.
     *
     * @return void
     */
    public function boot();

    /**
     * Register a new boot listener.
     *
     * @param  callable  $callback
     * @return void
     */
    public function booting($callback);

    /**
     * Register a new "booted" listener.
     *
     * @param  callable  $callback
     * @return void
     */
    public function booted($callback);

    /**
     * Run the given array of bootstrap classes.
     *
     * @param  array  $bootstrappers
     * @return void
     */
    public function bootstrapWith(array $bootstrappers);

    /**
     * Get the current application locale.
     *
     * @return string
     */
    public function getLocale();

    /**
     * Get the application namespace.
     *
     * @return string
     *
     * @throws \RuntimeException
     */
    public function getNamespace();

    /**
     * Get the registered service provider instances if any exist.
     *
     * @param  \Illuminate\Support\ServiceProvider|string  $provider
     * @return array
     */
    public function getProviders($provider);

    /**
     * Determine if the application has been bootstrapped before.
     *
     * @return bool
     */
    public function hasBeenBootstrapped();

    /**
     * Load and boot all of the remaining deferred providers.
     *
     * @return void
     */
    public function loadDeferredProviders();

    /**
     * Set the current application locale.
     *
     * @param  string  $locale
     * @return void
     */
    public function setLocale($locale);

    /**
     * Determine if middleware has been disabled for the application.
     *
     * @return bool
     */
    public function shouldSkipMiddleware();

    /**
     * Register a terminating callback with the application.
     *
     * @param  callable|string  $callback
     * @return \Illuminate\Contracts\Foundation\Application
     */
    public function terminating($callback);

    /**
     * Terminate the application.
     *
     * @return void
     */
    public function terminate();
}
<?php

namespace Illuminate\Contracts\Foundation;

interface CachesRoutes
{
    /**
     * Determine if the application routes are cached.
     *
     * @return bool
     */
    public function routesAreCached();

    /**
     * Get the path to the routes cache file.
     *
     * @return string
     */
    public function getCachedRoutesPath();
}
<?php

namespace Illuminate\Contracts\Foundation;

interface CachesConfiguration
{
    /**
     * Determine if the application configuration is cached.
     *
     * @return bool
     */
    public function configurationIsCached();

    /**
     * Get the path to the configuration cache file.
     *
     * @return string
     */
    public function getCachedConfigPath();

    /**
     * Get the path to the cached services.php file.
     *
     * @return string
     */
    public function getCachedServicesPath();
}
<?php

namespace Illuminate\Contracts\Foundation;

interface MaintenanceMode
{
    /**
     * Take the application down for maintenance.
     *
     * @param  array  $payload
     * @return void
     */
    public function activate(array $payload): void;

    /**
     * Take the application out of maintenance.
     *
     * @return void
     */
    public function deactivate(): void;

    /**
     * Determine if the application is currently down for maintenance.
     *
     * @return bool
     */
    public function active(): bool;

    /**
     * Get the data array which was provided when the application was placed into maintenance.
     *
     * @return array
     */
    public function data(): array;
}
<?php

namespace Illuminate\Contracts\Process;

interface InvokedProcess
{
    /**
     * Get the process ID if the process is still running.
     *
     * @return int|null
     */
    public function id();

    /**
     * Send a signal to the process.
     *
     * @param  int  $signal
     * @return $this
     */
    public function signal(int $signal);

    /**
     * Determine if the process is still running.
     *
     * @return bool
     */
    public function running();

    /**
     * Get the standard output for the process.
     *
     * @return string
     */
    public function output();

    /**
     * Get the error output for the process.
     *
     * @return string
     */
    public function errorOutput();

    /**
     * Get the latest standard output for the process.
     *
     * @return string
     */
    public function latestOutput();

    /**
     * Get the latest error output for the process.
     *
     * @return string
     */
    public function latestErrorOutput();

    /**
     * Wait for the process to finish.
     *
     * @param  callable|null  $output
     * @return \Illuminate\Console\Process\ProcessResult
     */
    public function wait(?callable $output = null);
}
<?php

namespace Illuminate\Contracts\Process;

interface ProcessResult
{
    /**
     * Get the original command executed by the process.
     *
     * @return string
     */
    public function command();

    /**
     * Determine if the process was successful.
     *
     * @return bool
     */
    public function successful();

    /**
     * Determine if the process failed.
     *
     * @return bool
     */
    public function failed();

    /**
     * Get the exit code of the process.
     *
     * @return int|null
     */
    public function exitCode();

    /**
     * Get the standard output of the process.
     *
     * @return string
     */
    public function output();

    /**
     * Get the error output of the process.
     *
     * @return string
     */
    public function errorOutput();

    /**
     * Throw an exception if the process failed.
     *
     * @param  callable|null  $callback
     * @return $this
     */
    public function throw(?callable $callback = null);

    /**
     * Throw an exception if the process failed and the given condition is true.
     *
     * @param  bool  $condition
     * @param  callable|null  $callback
     * @return $this
     */
    public function throwIf(bool $condition, ?callable $callback = null);
}
<?php

namespace Illuminate\Contracts\Config;

interface Repository
{
    /**
     * Determine if the given configuration value exists.
     *
     * @param  string  $key
     * @return bool
     */
    public function has($key);

    /**
     * Get the specified configuration value.
     *
     * @param  array|string  $key
     * @param  mixed  $default
     * @return mixed
     */
    public function get($key, $default = null);

    /**
     * Get all of the configuration items for the application.
     *
     * @return array
     */
    public function all();

    /**
     * Set a given configuration value.
     *
     * @param  array|string  $key
     * @param  mixed  $value
     * @return void
     */
    public function set($key, $value = null);

    /**
     * Prepend a value onto an array configuration value.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return void
     */
    public function prepend($key, $value);

    /**
     * Push a value onto an array configuration value.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return void
     */
    public function push($key, $value);
}
<?php

namespace Illuminate\Contracts\Encryption;

use RuntimeException;

class DecryptException extends RuntimeException
{
    //
}
<?php

namespace Illuminate\Contracts\Encryption;

interface Encrypter
{
    /**
     * Encrypt the given value.
     *
     * @param  mixed  $value
     * @param  bool  $serialize
     * @return string
     *
     * @throws \Illuminate\Contracts\Encryption\EncryptException
     */
    public function encrypt($value, $serialize = true);

    /**
     * Decrypt the given value.
     *
     * @param  string  $payload
     * @param  bool  $unserialize
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Encryption\DecryptException
     */
    public function decrypt($payload, $unserialize = true);

    /**
     * Get the encryption key that the encrypter is currently using.
     *
     * @return string
     */
    public function getKey();
}
<?php

namespace Illuminate\Contracts\Encryption;

interface StringEncrypter
{
    /**
     * Encrypt a string without serialization.
     *
     * @param  string  $value
     * @return string
     *
     * @throws \Illuminate\Contracts\Encryption\EncryptException
     */
    public function encryptString($value);

    /**
     * Decrypt the given string without unserialization.
     *
     * @param  string  $payload
     * @return string
     *
     * @throws \Illuminate\Contracts\Encryption\DecryptException
     */
    public function decryptString($payload);
}
<?php

namespace Illuminate\Contracts\Encryption;

use RuntimeException;

class EncryptException extends RuntimeException
{
    //
}
<?php

namespace Illuminate\Contracts\Auth\Access;

interface Authorizable
{
    /**
     * Determine if the entity has a given ability.
     *
     * @param  iterable|string  $abilities
     * @param  array|mixed  $arguments
     * @return bool
     */
    public function can($abilities, $arguments = []);
}
<?php

namespace Illuminate\Contracts\Auth\Access;

interface Gate
{
    /**
     * Determine if a given ability has been defined.
     *
     * @param  string  $ability
     * @return bool
     */
    public function has($ability);

    /**
     * Define a new ability.
     *
     * @param  string  $ability
     * @param  callable|string  $callback
     * @return $this
     */
    public function define($ability, $callback);

    /**
     * Define abilities for a resource.
     *
     * @param  string  $name
     * @param  string  $class
     * @param  array|null  $abilities
     * @return $this
     */
    public function resource($name, $class, ?array $abilities = null);

    /**
     * Define a policy class for a given class type.
     *
     * @param  string  $class
     * @param  string  $policy
     * @return $this
     */
    public function policy($class, $policy);

    /**
     * Register a callback to run before all Gate checks.
     *
     * @param  callable  $callback
     * @return $this
     */
    public function before(callable $callback);

    /**
     * Register a callback to run after all Gate checks.
     *
     * @param  callable  $callback
     * @return $this
     */
    public function after(callable $callback);

    /**
     * Determine if all of the given abilities should be granted for the current user.
     *
     * @param  iterable|string  $ability
     * @param  array|mixed  $arguments
     * @return bool
     */
    public function allows($ability, $arguments = []);

    /**
     * Determine if any of the given abilities should be denied for the current user.
     *
     * @param  iterable|string  $ability
     * @param  array|mixed  $arguments
     * @return bool
     */
    public function denies($ability, $arguments = []);

    /**
     * Determine if all of the given abilities should be granted for the current user.
     *
     * @param  iterable|string  $abilities
     * @param  array|mixed  $arguments
     * @return bool
     */
    public function check($abilities, $arguments = []);

    /**
     * Determine if any one of the given abilities should be granted for the current user.
     *
     * @param  iterable|string  $abilities
     * @param  array|mixed  $arguments
     * @return bool
     */
    public function any($abilities, $arguments = []);

    /**
     * Determine if the given ability should be granted for the current user.
     *
     * @param  string  $ability
     * @param  array|mixed  $arguments
     * @return \Illuminate\Auth\Access\Response
     *
     * @throws \Illuminate\Auth\Access\AuthorizationException
     */
    public function authorize($ability, $arguments = []);

    /**
     * Inspect the user for the given ability.
     *
     * @param  string  $ability
     * @param  array|mixed  $arguments
     * @return \Illuminate\Auth\Access\Response
     */
    public function inspect($ability, $arguments = []);

    /**
     * Get the raw result from the authorization callback.
     *
     * @param  string  $ability
     * @param  array|mixed  $arguments
     * @return mixed
     *
     * @throws \Illuminate\Auth\Access\AuthorizationException
     */
    public function raw($ability, $arguments = []);

    /**
     * Get a policy instance for a given class.
     *
     * @param  object|string  $class
     * @return mixed
     *
     * @throws \InvalidArgumentException
     */
    public function getPolicyFor($class);

    /**
     * Get a guard instance for the given user.
     *
     * @param  \Illuminate\Contracts\Auth\Authenticatable|mixed  $user
     * @return static
     */
    public function forUser($user);

    /**
     * Get all of the defined abilities.
     *
     * @return array
     */
    public function abilities();
}
<?php

namespace Illuminate\Contracts\Auth;

interface PasswordBrokerFactory
{
    /**
     * Get a password broker instance by name.
     *
     * @param  string|null  $name
     * @return \Illuminate\Contracts\Auth\PasswordBroker
     */
    public function broker($name = null);
}
<?php

namespace Illuminate\Contracts\Auth;

interface UserProvider
{
    /**
     * Retrieve a user by their unique identifier.
     *
     * @param  mixed  $identifier
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveById($identifier);

    /**
     * Retrieve a user by their unique identifier and "remember me" token.
     *
     * @param  mixed  $identifier
     * @param  string  $token
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveByToken($identifier, $token);

    /**
     * Update the "remember me" token for the given user in storage.
     *
     * @param  \Illuminate\Contracts\Auth\Authenticatable  $user
     * @param  string  $token
     * @return void
     */
    public function updateRememberToken(Authenticatable $user, $token);

    /**
     * Retrieve a user by the given credentials.
     *
     * @param  array  $credentials
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function retrieveByCredentials(array $credentials);

    /**
     * Validate a user against the given credentials.
     *
     * @param  \Illuminate\Contracts\Auth\Authenticatable  $user
     * @param  array  $credentials
     * @return bool
     */
    public function validateCredentials(Authenticatable $user, array $credentials);
}
<?php

namespace Illuminate\Contracts\Auth;

use Closure;

interface PasswordBroker
{
    /**
     * Constant representing a successfully sent reminder.
     *
     * @var string
     */
    const RESET_LINK_SENT = 'passwords.sent';

    /**
     * Constant representing a successfully reset password.
     *
     * @var string
     */
    const PASSWORD_RESET = 'passwords.reset';

    /**
     * Constant representing the user not found response.
     *
     * @var string
     */
    const INVALID_USER = 'passwords.user';

    /**
     * Constant representing an invalid token.
     *
     * @var string
     */
    const INVALID_TOKEN = 'passwords.token';

    /**
     * Constant representing a throttled reset attempt.
     *
     * @var string
     */
    const RESET_THROTTLED = 'passwords.throttled';

    /**
     * Send a password reset link to a user.
     *
     * @param  array  $credentials
     * @param  \Closure|null  $callback
     * @return string
     */
    public function sendResetLink(array $credentials, ?Closure $callback = null);

    /**
     * Reset the password for the given token.
     *
     * @param  array  $credentials
     * @param  \Closure  $callback
     * @return mixed
     */
    public function reset(array $credentials, Closure $callback);
}
<?php

namespace Illuminate\Contracts\Auth\Middleware;

interface AuthenticatesRequests
{
    //
}
<?php

namespace Illuminate\Contracts\Auth;

interface CanResetPassword
{
    /**
     * Get the e-mail address where password reset links are sent.
     *
     * @return string
     */
    public function getEmailForPasswordReset();

    /**
     * Send the password reset notification.
     *
     * @param  string  $token
     * @return void
     */
    public function sendPasswordResetNotification($token);
}
<?php

namespace Illuminate\Contracts\Auth;

interface SupportsBasicAuth
{
    /**
     * Attempt to authenticate using HTTP Basic Auth.
     *
     * @param  string  $field
     * @param  array  $extraConditions
     * @return \Symfony\Component\HttpFoundation\Response|null
     */
    public function basic($field = 'email', $extraConditions = []);

    /**
     * Perform a stateless HTTP Basic login attempt.
     *
     * @param  string  $field
     * @param  array  $extraConditions
     * @return \Symfony\Component\HttpFoundation\Response|null
     */
    public function onceBasic($field = 'email', $extraConditions = []);
}
<?php

namespace Illuminate\Contracts\Auth;

interface MustVerifyEmail
{
    /**
     * Determine if the user has verified their email address.
     *
     * @return bool
     */
    public function hasVerifiedEmail();

    /**
     * Mark the given user's email as verified.
     *
     * @return bool
     */
    public function markEmailAsVerified();

    /**
     * Send the email verification notification.
     *
     * @return void
     */
    public function sendEmailVerificationNotification();

    /**
     * Get the email address that should be used for verification.
     *
     * @return string
     */
    public function getEmailForVerification();
}
<?php

namespace Illuminate\Contracts\Auth;

interface StatefulGuard extends Guard
{
    /**
     * Attempt to authenticate a user using the given credentials.
     *
     * @param  array  $credentials
     * @param  bool  $remember
     * @return bool
     */
    public function attempt(array $credentials = [], $remember = false);

    /**
     * Log a user into the application without sessions or cookies.
     *
     * @param  array  $credentials
     * @return bool
     */
    public function once(array $credentials = []);

    /**
     * Log a user into the application.
     *
     * @param  \Illuminate\Contracts\Auth\Authenticatable  $user
     * @param  bool  $remember
     * @return void
     */
    public function login(Authenticatable $user, $remember = false);

    /**
     * Log the given user ID into the application.
     *
     * @param  mixed  $id
     * @param  bool  $remember
     * @return \Illuminate\Contracts\Auth\Authenticatable|bool
     */
    public function loginUsingId($id, $remember = false);

    /**
     * Log the given user ID into the application without sessions or cookies.
     *
     * @param  mixed  $id
     * @return \Illuminate\Contracts\Auth\Authenticatable|bool
     */
    public function onceUsingId($id);

    /**
     * Determine if the user was authenticated via "remember me" cookie.
     *
     * @return bool
     */
    public function viaRemember();

    /**
     * Log the user out of the application.
     *
     * @return void
     */
    public function logout();
}
<?php

namespace Illuminate\Contracts\Auth;

interface Guard
{
    /**
     * Determine if the current user is authenticated.
     *
     * @return bool
     */
    public function check();

    /**
     * Determine if the current user is a guest.
     *
     * @return bool
     */
    public function guest();

    /**
     * Get the currently authenticated user.
     *
     * @return \Illuminate\Contracts\Auth\Authenticatable|null
     */
    public function user();

    /**
     * Get the ID for the currently authenticated user.
     *
     * @return int|string|null
     */
    public function id();

    /**
     * Validate a user's credentials.
     *
     * @param  array  $credentials
     * @return bool
     */
    public function validate(array $credentials = []);

    /**
     * Determine if the guard has a user instance.
     *
     * @return bool
     */
    public function hasUser();

    /**
     * Set the current user.
     *
     * @param  \Illuminate\Contracts\Auth\Authenticatable  $user
     * @return void
     */
    public function setUser(Authenticatable $user);
}
<?php

namespace Illuminate\Contracts\Auth;

interface Authenticatable
{
    /**
     * Get the name of the unique identifier for the user.
     *
     * @return string
     */
    public function getAuthIdentifierName();

    /**
     * Get the unique identifier for the user.
     *
     * @return mixed
     */
    public function getAuthIdentifier();

    /**
     * Get the password for the user.
     *
     * @return string
     */
    public function getAuthPassword();

    /**
     * Get the token value for the "remember me" session.
     *
     * @return string
     */
    public function getRememberToken();

    /**
     * Set the token value for the "remember me" session.
     *
     * @param  string  $value
     * @return void
     */
    public function setRememberToken($value);

    /**
     * Get the column name for the "remember me" token.
     *
     * @return string
     */
    public function getRememberTokenName();
}
<?php

namespace Illuminate\Contracts\Auth;

interface Factory
{
    /**
     * Get a guard instance by name.
     *
     * @param  string|null  $name
     * @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard
     */
    public function guard($name = null);

    /**
     * Set the default guard the factory should serve.
     *
     * @param  string  $name
     * @return void
     */
    public function shouldUse($name);
}
<?php

namespace Illuminate\Contracts\Routing;

interface BindingRegistrar
{
    /**
     * Add a new route parameter binder.
     *
     * @param  string  $key
     * @param  string|callable  $binder
     * @return void
     */
    public function bind($key, $binder);

    /**
     * Get the binding callback for a given binding.
     *
     * @param  string  $key
     * @return \Closure
     */
    public function getBindingCallback($key);
}
<?php

namespace Illuminate\Contracts\Routing;

interface Registrar
{
    /**
     * Register a new GET route with the router.
     *
     * @param  string  $uri
     * @param  array|string|callable  $action
     * @return \Illuminate\Routing\Route
     */
    public function get($uri, $action);

    /**
     * Register a new POST route with the router.
     *
     * @param  string  $uri
     * @param  array|string|callable  $action
     * @return \Illuminate\Routing\Route
     */
    public function post($uri, $action);

    /**
     * Register a new PUT route with the router.
     *
     * @param  string  $uri
     * @param  array|string|callable  $action
     * @return \Illuminate\Routing\Route
     */
    public function put($uri, $action);

    /**
     * Register a new DELETE route with the router.
     *
     * @param  string  $uri
     * @param  array|string|callable  $action
     * @return \Illuminate\Routing\Route
     */
    public function delete($uri, $action);

    /**
     * Register a new PATCH route with the router.
     *
     * @param  string  $uri
     * @param  array|string|callable  $action
     * @return \Illuminate\Routing\Route
     */
    public function patch($uri, $action);

    /**
     * Register a new OPTIONS route with the router.
     *
     * @param  string  $uri
     * @param  array|string|callable  $action
     * @return \Illuminate\Routing\Route
     */
    public function options($uri, $action);

    /**
     * Register a new route with the given verbs.
     *
     * @param  array|string  $methods
     * @param  string  $uri
     * @param  array|string|callable  $action
     * @return \Illuminate\Routing\Route
     */
    public function match($methods, $uri, $action);

    /**
     * Route a resource to a controller.
     *
     * @param  string  $name
     * @param  string  $controller
     * @param  array  $options
     * @return \Illuminate\Routing\PendingResourceRegistration
     */
    public function resource($name, $controller, array $options = []);

    /**
     * Create a route group with shared attributes.
     *
     * @param  array  $attributes
     * @param  \Closure|string  $routes
     * @return void
     */
    public function group(array $attributes, $routes);

    /**
     * Substitute the route bindings onto the route.
     *
     * @param  \Illuminate\Routing\Route  $route
     * @return \Illuminate\Routing\Route
     */
    public function substituteBindings($route);

    /**
     * Substitute the implicit Eloquent model bindings for the route.
     *
     * @param  \Illuminate\Routing\Route  $route
     * @return void
     */
    public function substituteImplicitBindings($route);
}
<?php

namespace Illuminate\Contracts\Routing;

interface ResponseFactory
{
    /**
     * Create a new response instance.
     *
     * @param  array|string  $content
     * @param  int  $status
     * @param  array  $headers
     * @return \Illuminate\Http\Response
     */
    public function make($content = '', $status = 200, array $headers = []);

    /**
     * Create a new "no content" response.
     *
     * @param  int  $status
     * @param  array  $headers
     * @return \Illuminate\Http\Response
     */
    public function noContent($status = 204, array $headers = []);

    /**
     * Create a new response for a given view.
     *
     * @param  string|array  $view
     * @param  array  $data
     * @param  int  $status
     * @param  array  $headers
     * @return \Illuminate\Http\Response
     */
    public function view($view, $data = [], $status = 200, array $headers = []);

    /**
     * Create a new JSON response instance.
     *
     * @param  mixed  $data
     * @param  int  $status
     * @param  array  $headers
     * @param  int  $options
     * @return \Illuminate\Http\JsonResponse
     */
    public function json($data = [], $status = 200, array $headers = [], $options = 0);

    /**
     * Create a new JSONP response instance.
     *
     * @param  string  $callback
     * @param  mixed  $data
     * @param  int  $status
     * @param  array  $headers
     * @param  int  $options
     * @return \Illuminate\Http\JsonResponse
     */
    public function jsonp($callback, $data = [], $status = 200, array $headers = [], $options = 0);

    /**
     * Create a new streamed response instance.
     *
     * @param  callable  $callback
     * @param  int  $status
     * @param  array  $headers
     * @return \Symfony\Component\HttpFoundation\StreamedResponse
     */
    public function stream($callback, $status = 200, array $headers = []);

    /**
     * Create a new streamed response instance as a file download.
     *
     * @param  callable  $callback
     * @param  string|null  $name
     * @param  array  $headers
     * @param  string|null  $disposition
     * @return \Symfony\Component\HttpFoundation\StreamedResponse
     */
    public function streamDownload($callback, $name = null, array $headers = [], $disposition = 'attachment');

    /**
     * Create a new file download response.
     *
     * @param  \SplFileInfo|string  $file
     * @param  string|null  $name
     * @param  array  $headers
     * @param  string|null  $disposition
     * @return \Symfony\Component\HttpFoundation\BinaryFileResponse
     */
    public function download($file, $name = null, array $headers = [], $disposition = 'attachment');

    /**
     * Return the raw contents of a binary file.
     *
     * @param  \SplFileInfo|string  $file
     * @param  array  $headers
     * @return \Symfony\Component\HttpFoundation\BinaryFileResponse
     */
    public function file($file, array $headers = []);

    /**
     * Create a new redirect response to the given path.
     *
     * @param  string  $path
     * @param  int  $status
     * @param  array  $headers
     * @param  bool|null  $secure
     * @return \Illuminate\Http\RedirectResponse
     */
    public function redirectTo($path, $status = 302, $headers = [], $secure = null);

    /**
     * Create a new redirect response to a named route.
     *
     * @param  string  $route
     * @param  mixed  $parameters
     * @param  int  $status
     * @param  array  $headers
     * @return \Illuminate\Http\RedirectResponse
     */
    public function redirectToRoute($route, $parameters = [], $status = 302, $headers = []);

    /**
     * Create a new redirect response to a controller action.
     *
     * @param  array|string  $action
     * @param  mixed  $parameters
     * @param  int  $status
     * @param  array  $headers
     * @return \Illuminate\Http\RedirectResponse
     */
    public function redirectToAction($action, $parameters = [], $status = 302, $headers = []);

    /**
     * Create a new redirect response, while putting the current URL in the session.
     *
     * @param  string  $path
     * @param  int  $status
     * @param  array  $headers
     * @param  bool|null  $secure
     * @return \Illuminate\Http\RedirectResponse
     */
    public function redirectGuest($path, $status = 302, $headers = [], $secure = null);

    /**
     * Create a new redirect response to the previously intended location.
     *
     * @param  string  $default
     * @param  int  $status
     * @param  array  $headers
     * @param  bool|null  $secure
     * @return \Illuminate\Http\RedirectResponse
     */
    public function redirectToIntended($default = '/', $status = 302, $headers = [], $secure = null);
}
<?php

namespace Illuminate\Contracts\Routing;

interface UrlRoutable
{
    /**
     * Get the value of the model's route key.
     *
     * @return mixed
     */
    public function getRouteKey();

    /**
     * Get the route key for the model.
     *
     * @return string
     */
    public function getRouteKeyName();

    /**
     * Retrieve the model for a bound value.
     *
     * @param  mixed  $value
     * @param  string|null  $field
     * @return \Illuminate\Database\Eloquent\Model|null
     */
    public function resolveRouteBinding($value, $field = null);

    /**
     * Retrieve the child model for a bound value.
     *
     * @param  string  $childType
     * @param  mixed  $value
     * @param  string|null  $field
     * @return \Illuminate\Database\Eloquent\Model|null
     */
    public function resolveChildRouteBinding($childType, $value, $field);
}
<?php

namespace Illuminate\Contracts\Routing;

interface UrlGenerator
{
    /**
     * Get the current URL for the request.
     *
     * @return string
     */
    public function current();

    /**
     * Get the URL for the previous request.
     *
     * @param  mixed  $fallback
     * @return string
     */
    public function previous($fallback = false);

    /**
     * Generate an absolute URL to the given path.
     *
     * @param  string  $path
     * @param  mixed  $extra
     * @param  bool|null  $secure
     * @return string
     */
    public function to($path, $extra = [], $secure = null);

    /**
     * Generate a secure, absolute URL to the given path.
     *
     * @param  string  $path
     * @param  array  $parameters
     * @return string
     */
    public function secure($path, $parameters = []);

    /**
     * Generate the URL to an application asset.
     *
     * @param  string  $path
     * @param  bool|null  $secure
     * @return string
     */
    public function asset($path, $secure = null);

    /**
     * Get the URL to a named route.
     *
     * @param  string  $name
     * @param  mixed  $parameters
     * @param  bool  $absolute
     * @return string
     *
     * @throws \InvalidArgumentException
     */
    public function route($name, $parameters = [], $absolute = true);

    /**
     * Get the URL to a controller action.
     *
     * @param  string|array  $action
     * @param  mixed  $parameters
     * @param  bool  $absolute
     * @return string
     */
    public function action($action, $parameters = [], $absolute = true);

    /**
     * Get the root controller namespace.
     *
     * @return string
     */
    public function getRootControllerNamespace();

    /**
     * Set the root controller namespace.
     *
     * @param  string  $rootNamespace
     * @return $this
     */
    public function setRootControllerNamespace($rootNamespace);
}
<?php

namespace Illuminate\Contracts\Cookie;

interface QueueingFactory extends Factory
{
    /**
     * Queue a cookie to send with the next response.
     *
     * @param  mixed  ...$parameters
     * @return void
     */
    public function queue(...$parameters);

    /**
     * Remove a cookie from the queue.
     *
     * @param  string  $name
     * @param  string|null  $path
     * @return void
     */
    public function unqueue($name, $path = null);

    /**
     * Get the cookies which have been queued for the next request.
     *
     * @return array
     */
    public function getQueuedCookies();
}
<?php

namespace Illuminate\Contracts\Cookie;

interface Factory
{
    /**
     * Create a new cookie instance.
     *
     * @param  string  $name
     * @param  string  $value
     * @param  int  $minutes
     * @param  string|null  $path
     * @param  string|null  $domain
     * @param  bool|null  $secure
     * @param  bool  $httpOnly
     * @param  bool  $raw
     * @param  string|null  $sameSite
     * @return \Symfony\Component\HttpFoundation\Cookie
     */
    public function make($name, $value, $minutes = 0, $path = null, $domain = null, $secure = null, $httpOnly = true, $raw = false, $sameSite = null);

    /**
     * Create a cookie that lasts "forever" (five years).
     *
     * @param  string  $name
     * @param  string  $value
     * @param  string|null  $path
     * @param  string|null  $domain
     * @param  bool|null  $secure
     * @param  bool  $httpOnly
     * @param  bool  $raw
     * @param  string|null  $sameSite
     * @return \Symfony\Component\HttpFoundation\Cookie
     */
    public function forever($name, $value, $path = null, $domain = null, $secure = null, $httpOnly = true, $raw = false, $sameSite = null);

    /**
     * Expire the given cookie.
     *
     * @param  string  $name
     * @param  string|null  $path
     * @param  string|null  $domain
     * @return \Symfony\Component\HttpFoundation\Cookie
     */
    public function forget($name, $path = null, $domain = null);
}
<?php

namespace Illuminate\Contracts\Events;

interface ShouldDispatchAfterCommit
{
    //
}
<?php

namespace Illuminate\Contracts\Events;

interface ShouldHandleEventsAfterCommit
{
    //
}
<?php

namespace Illuminate\Contracts\Events;

interface Dispatcher
{
    /**
     * Register an event listener with the dispatcher.
     *
     * @param  \Closure|string|array  $events
     * @param  \Closure|string|array|null  $listener
     * @return void
     */
    public function listen($events, $listener = null);

    /**
     * Determine if a given event has listeners.
     *
     * @param  string  $eventName
     * @return bool
     */
    public function hasListeners($eventName);

    /**
     * Register an event subscriber with the dispatcher.
     *
     * @param  object|string  $subscriber
     * @return void
     */
    public function subscribe($subscriber);

    /**
     * Dispatch an event until the first non-null response is returned.
     *
     * @param  string|object  $event
     * @param  mixed  $payload
     * @return mixed
     */
    public function until($event, $payload = []);

    /**
     * Dispatch an event and call the listeners.
     *
     * @param  string|object  $event
     * @param  mixed  $payload
     * @param  bool  $halt
     * @return array|null
     */
    public function dispatch($event, $payload = [], $halt = false);

    /**
     * Register an event and payload to be fired later.
     *
     * @param  string  $event
     * @param  array  $payload
     * @return void
     */
    public function push($event, $payload = []);

    /**
     * Flush a set of pushed events.
     *
     * @param  string  $event
     * @return void
     */
    public function flush($event);

    /**
     * Remove a set of listeners from the dispatcher.
     *
     * @param  string  $event
     * @return void
     */
    public function forget($event);

    /**
     * Forget all of the queued listeners.
     *
     * @return void
     */
    public function forgetPushed();
}
<?php

namespace Illuminate\Contracts\Broadcasting;

interface ShouldBeUnique
{
    //
}
<?php

namespace Illuminate\Contracts\Broadcasting;

interface HasBroadcastChannel
{
    /**
     * Get the broadcast channel route definition that is associated with the given entity.
     *
     * @return string
     */
    public function broadcastChannelRoute();

    /**
     * Get the broadcast channel name that is associated with the given entity.
     *
     * @return string
     */
    public function broadcastChannel();
}
<?php

namespace Illuminate\Contracts\Broadcasting;

interface ShouldBroadcastNow extends ShouldBroadcast
{
    //
}
<?php

namespace Illuminate\Contracts\Broadcasting;

interface Broadcaster
{
    /**
     * Authenticate the incoming request for a given channel.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return mixed
     */
    public function auth($request);

    /**
     * Return the valid authentication response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  mixed  $result
     * @return mixed
     */
    public function validAuthenticationResponse($request, $result);

    /**
     * Broadcast the given event.
     *
     * @param  array  $channels
     * @param  string  $event
     * @param  array  $payload
     * @return void
     *
     * @throws \Illuminate\Broadcasting\BroadcastException
     */
    public function broadcast(array $channels, $event, array $payload = []);
}
<?php

namespace Illuminate\Contracts\Broadcasting;

interface Factory
{
    /**
     * Get a broadcaster implementation by name.
     *
     * @param  string|null  $name
     * @return \Illuminate\Contracts\Broadcasting\Broadcaster
     */
    public function connection($name = null);
}
<?php

namespace Illuminate\Contracts\Broadcasting;

interface ShouldBroadcast
{
    /**
     * Get the channels the event should broadcast on.
     *
     * @return \Illuminate\Broadcasting\Channel|\Illuminate\Broadcasting\Channel[]|string[]|string
     */
    public function broadcastOn();
}
<?php

namespace Illuminate\Contracts\Notifications;

interface Factory
{
    /**
     * Get a channel instance by name.
     *
     * @param  string|null  $name
     * @return mixed
     */
    public function channel($name = null);

    /**
     * Send the given notification to the given notifiable entities.
     *
     * @param  \Illuminate\Support\Collection|array|mixed  $notifiables
     * @param  mixed  $notification
     * @return void
     */
    public function send($notifiables, $notification);

    /**
     * Send the given notification immediately.
     *
     * @param  \Illuminate\Support\Collection|array|mixed  $notifiables
     * @param  mixed  $notification
     * @return void
     */
    public function sendNow($notifiables, $notification);
}
<?php

namespace Illuminate\Contracts\Notifications;

interface Dispatcher
{
    /**
     * Send the given notification to the given notifiable entities.
     *
     * @param  \Illuminate\Support\Collection|array|mixed  $notifiables
     * @param  mixed  $notification
     * @return void
     */
    public function send($notifiables, $notification);

    /**
     * Send the given notification immediately.
     *
     * @param  \Illuminate\Support\Collection|array|mixed  $notifiables
     * @param  mixed  $notification
     * @return void
     */
    public function sendNow($notifiables, $notification);
}
<?php

namespace Illuminate\Contracts\Container;

interface ContextualBindingBuilder
{
    /**
     * Define the abstract target that depends on the context.
     *
     * @param  string  $abstract
     * @return $this
     */
    public function needs($abstract);

    /**
     * Define the implementation for the contextual binding.
     *
     * @param  \Closure|string|array  $implementation
     * @return void
     */
    public function give($implementation);

    /**
     * Define tagged services to be used as the implementation for the contextual binding.
     *
     * @param  string  $tag
     * @return void
     */
    public function giveTagged($tag);

    /**
     * Specify the configuration item to bind as a primitive.
     *
     * @param  string  $key
     * @param  mixed  $default
     * @return void
     */
    public function giveConfig($key, $default = null);
}
<?php

namespace Illuminate\Contracts\Container;

use Exception;
use Psr\Container\ContainerExceptionInterface;

class CircularDependencyException extends Exception implements ContainerExceptionInterface
{
    //
}
<?php

namespace Illuminate\Contracts\Container;

use Exception;
use Psr\Container\ContainerExceptionInterface;

class BindingResolutionException extends Exception implements ContainerExceptionInterface
{
    //
}
<?php

namespace Illuminate\Contracts\Container;

use Closure;
use Psr\Container\ContainerInterface;

interface Container extends ContainerInterface
{
    /**
     * Determine if the given abstract type has been bound.
     *
     * @param  string  $abstract
     * @return bool
     */
    public function bound($abstract);

    /**
     * Alias a type to a different name.
     *
     * @param  string  $abstract
     * @param  string  $alias
     * @return void
     *
     * @throws \LogicException
     */
    public function alias($abstract, $alias);

    /**
     * Assign a set of tags to a given binding.
     *
     * @param  array|string  $abstracts
     * @param  array|mixed  ...$tags
     * @return void
     */
    public function tag($abstracts, $tags);

    /**
     * Resolve all of the bindings for a given tag.
     *
     * @param  string  $tag
     * @return iterable
     */
    public function tagged($tag);

    /**
     * Register a binding with the container.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @param  bool  $shared
     * @return void
     */
    public function bind($abstract, $concrete = null, $shared = false);

    /**
     * Bind a callback to resolve with Container::call.
     *
     * @param  array|string  $method
     * @param  \Closure  $callback
     * @return void
     */
    public function bindMethod($method, $callback);

    /**
     * Register a binding if it hasn't already been registered.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @param  bool  $shared
     * @return void
     */
    public function bindIf($abstract, $concrete = null, $shared = false);

    /**
     * Register a shared binding in the container.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @return void
     */
    public function singleton($abstract, $concrete = null);

    /**
     * Register a shared binding if it hasn't already been registered.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @return void
     */
    public function singletonIf($abstract, $concrete = null);

    /**
     * Register a scoped binding in the container.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @return void
     */
    public function scoped($abstract, $concrete = null);

    /**
     * Register a scoped binding if it hasn't already been registered.
     *
     * @param  string  $abstract
     * @param  \Closure|string|null  $concrete
     * @return void
     */
    public function scopedIf($abstract, $concrete = null);

    /**
     * "Extend" an abstract type in the container.
     *
     * @param  string  $abstract
     * @param  \Closure  $closure
     * @return void
     *
     * @throws \InvalidArgumentException
     */
    public function extend($abstract, Closure $closure);

    /**
     * Register an existing instance as shared in the container.
     *
     * @param  string  $abstract
     * @param  mixed  $instance
     * @return mixed
     */
    public function instance($abstract, $instance);

    /**
     * Add a contextual binding to the container.
     *
     * @param  string  $concrete
     * @param  string  $abstract
     * @param  \Closure|string  $implementation
     * @return void
     */
    public function addContextualBinding($concrete, $abstract, $implementation);

    /**
     * Define a contextual binding.
     *
     * @param  string|array  $concrete
     * @return \Illuminate\Contracts\Container\ContextualBindingBuilder
     */
    public function when($concrete);

    /**
     * Get a closure to resolve the given type from the container.
     *
     * @param  string  $abstract
     * @return \Closure
     */
    public function factory($abstract);

    /**
     * Flush the container of all bindings and resolved instances.
     *
     * @return void
     */
    public function flush();

    /**
     * Resolve the given type from the container.
     *
     * @param  string  $abstract
     * @param  array  $parameters
     * @return mixed
     *
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    public function make($abstract, array $parameters = []);

    /**
     * Call the given Closure / class@method and inject its dependencies.
     *
     * @param  callable|string  $callback
     * @param  array  $parameters
     * @param  string|null  $defaultMethod
     * @return mixed
     */
    public function call($callback, array $parameters = [], $defaultMethod = null);

    /**
     * Determine if the given abstract type has been resolved.
     *
     * @param  string  $abstract
     * @return bool
     */
    public function resolved($abstract);

    /**
     * Register a new before resolving callback.
     *
     * @param  \Closure|string  $abstract
     * @param  \Closure|null  $callback
     * @return void
     */
    public function beforeResolving($abstract, ?Closure $callback = null);

    /**
     * Register a new resolving callback.
     *
     * @param  \Closure|string  $abstract
     * @param  \Closure|null  $callback
     * @return void
     */
    public function resolving($abstract, ?Closure $callback = null);

    /**
     * Register a new after resolving callback.
     *
     * @param  \Closure|string  $abstract
     * @param  \Closure|null  $callback
     * @return void
     */
    public function afterResolving($abstract, ?Closure $callback = null);
}
<?php

namespace Illuminate\Contracts\Cache;

use Exception;

class LockTimeoutException extends Exception
{
    //
}
<?php

namespace Illuminate\Contracts\Cache;

interface Store
{
    /**
     * Retrieve an item from the cache by key.
     *
     * @param  string|array  $key
     * @return mixed
     */
    public function get($key);

    /**
     * Retrieve multiple items from the cache by key.
     *
     * Items not found in the cache will have a null value.
     *
     * @param  array  $keys
     * @return array
     */
    public function many(array $keys);

    /**
     * Store an item in the cache for a given number of seconds.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @param  int  $seconds
     * @return bool
     */
    public function put($key, $value, $seconds);

    /**
     * Store multiple items in the cache for a given number of seconds.
     *
     * @param  array  $values
     * @param  int  $seconds
     * @return bool
     */
    public function putMany(array $values, $seconds);

    /**
     * Increment the value of an item in the cache.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return int|bool
     */
    public function increment($key, $value = 1);

    /**
     * Decrement the value of an item in the cache.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return int|bool
     */
    public function decrement($key, $value = 1);

    /**
     * Store an item in the cache indefinitely.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return bool
     */
    public function forever($key, $value);

    /**
     * Remove an item from the cache.
     *
     * @param  string  $key
     * @return bool
     */
    public function forget($key);

    /**
     * Remove all items from the cache.
     *
     * @return bool
     */
    public function flush();

    /**
     * Get the cache key prefix.
     *
     * @return string
     */
    public function getPrefix();
}
<?php

namespace Illuminate\Contracts\Cache;

interface LockProvider
{
    /**
     * Get a lock instance.
     *
     * @param  string  $name
     * @param  int  $seconds
     * @param  string|null  $owner
     * @return \Illuminate\Contracts\Cache\Lock
     */
    public function lock($name, $seconds = 0, $owner = null);

    /**
     * Restore a lock instance using the owner identifier.
     *
     * @param  string  $name
     * @param  string  $owner
     * @return \Illuminate\Contracts\Cache\Lock
     */
    public function restoreLock($name, $owner);
}
<?php

namespace Illuminate\Contracts\Cache;

interface Lock
{
    /**
     * Attempt to acquire the lock.
     *
     * @param  callable|null  $callback
     * @return mixed
     */
    public function get($callback = null);

    /**
     * Attempt to acquire the lock for the given number of seconds.
     *
     * @param  int  $seconds
     * @param  callable|null  $callback
     * @return mixed
     */
    public function block($seconds, $callback = null);

    /**
     * Release the lock.
     *
     * @return bool
     */
    public function release();

    /**
     * Returns the current owner of the lock.
     *
     * @return string
     */
    public function owner();

    /**
     * Releases this lock in disregard of ownership.
     *
     * @return void
     */
    public function forceRelease();
}
<?php

namespace Illuminate\Contracts\Cache;

use Closure;
use Psr\SimpleCache\CacheInterface;

interface Repository extends CacheInterface
{
    /**
     * Retrieve an item from the cache and delete it.
     *
     * @template TCacheValue
     *
     * @param  array|string  $key
     * @param  TCacheValue|(\Closure(): TCacheValue)  $default
     * @return (TCacheValue is null ? mixed : TCacheValue)
     */
    public function pull($key, $default = null);

    /**
     * Store an item in the cache.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @param  \DateTimeInterface|\DateInterval|int|null  $ttl
     * @return bool
     */
    public function put($key, $value, $ttl = null);

    /**
     * Store an item in the cache if the key does not exist.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @param  \DateTimeInterface|\DateInterval|int|null  $ttl
     * @return bool
     */
    public function add($key, $value, $ttl = null);

    /**
     * Increment the value of an item in the cache.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return int|bool
     */
    public function increment($key, $value = 1);

    /**
     * Decrement the value of an item in the cache.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return int|bool
     */
    public function decrement($key, $value = 1);

    /**
     * Store an item in the cache indefinitely.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return bool
     */
    public function forever($key, $value);

    /**
     * Get an item from the cache, or execute the given Closure and store the result.
     *
     * @template TCacheValue
     *
     * @param  string  $key
     * @param  \DateTimeInterface|\DateInterval|\Closure|int|null  $ttl
     * @param  \Closure(): TCacheValue  $callback
     * @return TCacheValue
     */
    public function remember($key, $ttl, Closure $callback);

    /**
     * Get an item from the cache, or execute the given Closure and store the result forever.
     *
     * @template TCacheValue
     *
     * @param  string  $key
     * @param  \Closure(): TCacheValue  $callback
     * @return TCacheValue
     */
    public function sear($key, Closure $callback);

    /**
     * Get an item from the cache, or execute the given Closure and store the result forever.
     *
     * @template TCacheValue
     *
     * @param  string  $key
     * @param  \Closure(): TCacheValue  $callback
     * @return TCacheValue
     */
    public function rememberForever($key, Closure $callback);

    /**
     * Remove an item from the cache.
     *
     * @param  string  $key
     * @return bool
     */
    public function forget($key);

    /**
     * Get the cache store implementation.
     *
     * @return \Illuminate\Contracts\Cache\Store
     */
    public function getStore();
}
<?php

namespace Illuminate\Contracts\Cache;

interface Factory
{
    /**
     * Get a cache store instance by name.
     *
     * @param  string|null  $name
     * @return \Illuminate\Contracts\Cache\Repository
     */
    public function store($name = null);
}
<?php

namespace Illuminate\Contracts\Bus;

interface QueueingDispatcher extends Dispatcher
{
    /**
     * Attempt to find the batch with the given ID.
     *
     * @param  string  $batchId
     * @return \Illuminate\Bus\Batch|null
     */
    public function findBatch(string $batchId);

    /**
     * Create a new batch of queueable jobs.
     *
     * @param  \Illuminate\Support\Collection|array  $jobs
     * @return \Illuminate\Bus\PendingBatch
     */
    public function batch($jobs);

    /**
     * Dispatch a command to its appropriate handler behind a queue.
     *
     * @param  mixed  $command
     * @return mixed
     */
    public function dispatchToQueue($command);
}
<?php

namespace Illuminate\Contracts\Bus;

interface Dispatcher
{
    /**
     * Dispatch a command to its appropriate handler.
     *
     * @param  mixed  $command
     * @return mixed
     */
    public function dispatch($command);

    /**
     * Dispatch a command to its appropriate handler in the current process.
     *
     * Queueable jobs will be dispatched to the "sync" queue.
     *
     * @param  mixed  $command
     * @param  mixed  $handler
     * @return mixed
     */
    public function dispatchSync($command, $handler = null);

    /**
     * Dispatch a command to its appropriate handler in the current process.
     *
     * @param  mixed  $command
     * @param  mixed  $handler
     * @return mixed
     */
    public function dispatchNow($command, $handler = null);

    /**
     * Determine if the given command has a handler.
     *
     * @param  mixed  $command
     * @return bool
     */
    public function hasCommandHandler($command);

    /**
     * Retrieve the handler for a command.
     *
     * @param  mixed  $command
     * @return bool|mixed
     */
    public function getCommandHandler($command);

    /**
     * Set the pipes commands should be piped through before dispatching.
     *
     * @param  array  $pipes
     * @return $this
     */
    public function pipeThrough(array $pipes);

    /**
     * Map a command to a handler.
     *
     * @param  array  $map
     * @return $this
     */
    public function map(array $map);
}
<?php

namespace Illuminate\Contracts\Http;

interface Kernel
{
    /**
     * Bootstrap the application for HTTP requests.
     *
     * @return void
     */
    public function bootstrap();

    /**
     * Handle an incoming HTTP request.
     *
     * @param  \Symfony\Component\HttpFoundation\Request  $request
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function handle($request);

    /**
     * Perform any final actions for the request lifecycle.
     *
     * @param  \Symfony\Component\HttpFoundation\Request  $request
     * @param  \Symfony\Component\HttpFoundation\Response  $response
     * @return void
     */
    public function terminate($request, $response);

    /**
     * Get the Laravel application instance.
     *
     * @return \Illuminate\Contracts\Foundation\Application
     */
    public function getApplication();
}
<?php

namespace Illuminate\Contracts\Database\Query;

use Illuminate\Database\Grammar;

interface Expression
{
    /**
     * Get the value of the expression.
     *
     * @param  \Illuminate\Database\Grammar  $grammar
     * @return string|int|float
     */
    public function getValue(Grammar $grammar);
}
<?php

namespace Illuminate\Contracts\Database\Query;

interface ConditionExpression extends Expression
{
}
<?php

namespace Illuminate\Contracts\Database\Query;

/**
 * This interface is intentionally empty and exists to improve IDE support.
 *
 * @mixin \Illuminate\Database\Query\Builder
 */
interface Builder
{
}
<?php

namespace Illuminate\Contracts\Database;

class ModelIdentifier
{
    /**
     * The class name of the model.
     *
     * @var string
     */
    public $class;

    /**
     * The unique identifier of the model.
     *
     * This may be either a single ID or an array of IDs.
     *
     * @var mixed
     */
    public $id;

    /**
     * The relationships loaded on the model.
     *
     * @var array
     */
    public $relations;

    /**
     * The connection name of the model.
     *
     * @var string|null
     */
    public $connection;

    /**
     * The class name of the model collection.
     *
     * @var string|null
     */
    public $collectionClass;

    /**
     * Create a new model identifier.
     *
     * @param  string  $class
     * @param  mixed  $id
     * @param  array  $relations
     * @param  mixed  $connection
     * @return void
     */
    public function __construct($class, $id, array $relations, $connection)
    {
        $this->id = $id;
        $this->class = $class;
        $this->relations = $relations;
        $this->connection = $connection;
    }

    /**
     * Specify the collection class that should be used when serializing / restoring collections.
     *
     * @param  string|null  $collectionClass
     * @return $this
     */
    public function useCollectionClass(?string $collectionClass)
    {
        $this->collectionClass = $collectionClass;

        return $this;
    }
}
<?php

namespace Illuminate\Contracts\Database\Events;

interface MigrationEvent
{
    //
}
<?php

namespace Illuminate\Contracts\Database\Eloquent;

interface DeviatesCastableAttributes
{
    /**
     * Increment the attribute.
     *
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @param  string  $key
     * @param  mixed  $value
     * @param  array  $attributes
     * @return mixed
     */
    public function increment($model, string $key, $value, array $attributes);

    /**
     * Decrement the attribute.
     *
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @param  string  $key
     * @param  mixed  $value
     * @param  array  $attributes
     * @return mixed
     */
    public function decrement($model, string $key, $value, array $attributes);
}
<?php

namespace Illuminate\Contracts\Database\Eloquent;

use Illuminate\Database\Eloquent\Model;

/**
 * @template TGet
 * @template TSet
 */
interface CastsAttributes
{
    /**
     * Transform the attribute from the underlying model values.
     *
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @param  string  $key
     * @param  mixed  $value
     * @param  array<string, mixed>  $attributes
     * @return TGet|null
     */
    public function get(Model $model, string $key, mixed $value, array $attributes);

    /**
     * Transform the attribute to its underlying model values.
     *
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @param  string  $key
     * @param  TSet|null  $value
     * @param  array<string, mixed>  $attributes
     * @return mixed
     */
    public function set(Model $model, string $key, mixed $value, array $attributes);
}
<?php

namespace Illuminate\Contracts\Database\Eloquent;

interface Castable
{
    /**
     * Get the name of the caster class to use when casting from / to this cast target.
     *
     * @param  array  $arguments
     * @return class-string<CastsAttributes|CastsInboundAttributes>|CastsAttributes|CastsInboundAttributes
     */
    public static function castUsing(array $arguments);
}
<?php

namespace Illuminate\Contracts\Database\Eloquent;

use Illuminate\Database\Eloquent\Model;

interface SerializesCastableAttributes
{
    /**
     * Serialize the attribute when converting the model to an array.
     *
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @param  string  $key
     * @param  mixed  $value
     * @param  array  $attributes
     * @return mixed
     */
    public function serialize(Model $model, string $key, mixed $value, array $attributes);
}
<?php

namespace Illuminate\Contracts\Database\Eloquent;

use Illuminate\Contracts\Database\Query\Builder as BaseContract;

/**
 * This interface is intentionally empty and exists to improve IDE support.
 *
 * @mixin \Illuminate\Database\Eloquent\Builder
 */
interface Builder extends BaseContract
{
}
<?php

namespace Illuminate\Contracts\Database\Eloquent;

interface SupportsPartialRelations
{
    /**
     * Indicate that the relation is a single result of a larger one-to-many relationship.
     *
     * @param  string|null  $column
     * @param  string|\Closure|null  $aggregate
     * @param  string  $relation
     * @return $this
     */
    public function ofMany($column = 'id', $aggregate = 'MAX', $relation = null);

    /**
     * Determine whether the relationship is a one-of-many relationship.
     *
     * @return bool
     */
    public function isOneOfMany();

    /**
     * Get the one of many inner join subselect query builder instance.
     *
     * @return \Illuminate\Database\Eloquent\Builder|void
     */
    public function getOneOfManySubQuery();
}
<?php

namespace Illuminate\Contracts\Database\Eloquent;

use Illuminate\Database\Eloquent\Model;

interface CastsInboundAttributes
{
    /**
     * Transform the attribute to its underlying model values.
     *
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @param  string  $key
     * @param  mixed  $value
     * @param  array  $attributes
     * @return mixed
     */
    public function set(Model $model, string $key, mixed $value, array $attributes);
}
<?php

namespace Illuminate\Contracts\Redis;

use Exception;

class LimiterTimeoutException extends Exception
{
    //
}
<?php

namespace Illuminate\Contracts\Redis;

interface Connector
{
    /**
     * Create a connection to a Redis cluster.
     *
     * @param  array  $config
     * @param  array  $options
     * @return \Illuminate\Redis\Connections\Connection
     */
    public function connect(array $config, array $options);

    /**
     * Create a connection to a Redis instance.
     *
     * @param  array  $config
     * @param  array  $clusterOptions
     * @param  array  $options
     * @return \Illuminate\Redis\Connections\Connection
     */
    public function connectToCluster(array $config, array $clusterOptions, array $options);
}
<?php

namespace Illuminate\Contracts\Redis;

use Closure;

interface Connection
{
    /**
     * Subscribe to a set of given channels for messages.
     *
     * @param  array|string  $channels
     * @param  \Closure  $callback
     * @return void
     */
    public function subscribe($channels, Closure $callback);

    /**
     * Subscribe to a set of given channels with wildcards.
     *
     * @param  array|string  $channels
     * @param  \Closure  $callback
     * @return void
     */
    public function psubscribe($channels, Closure $callback);

    /**
     * Run a command against the Redis database.
     *
     * @param  string  $method
     * @param  array  $parameters
     * @return mixed
     */
    public function command($method, array $parameters = []);
}
<?php

namespace Illuminate\Contracts\Redis;

interface Factory
{
    /**
     * Get a Redis connection by name.
     *
     * @param  string|null  $name
     * @return \Illuminate\Redis\Connections\Connection
     */
    public function connection($name = null);
}
<?php

namespace Illuminate\Contracts\Pagination;

interface CursorPaginator
{
    /**
     * Get the URL for a given cursor.
     *
     * @param  \Illuminate\Pagination\Cursor|null  $cursor
     * @return string
     */
    public function url($cursor);

    /**
     * Add a set of query string values to the paginator.
     *
     * @param  array|string|null  $key
     * @param  string|null  $value
     * @return $this
     */
    public function appends($key, $value = null);

    /**
     * Get / set the URL fragment to be appended to URLs.
     *
     * @param  string|null  $fragment
     * @return $this|string|null
     */
    public function fragment($fragment = null);

    /**
     * Add all current query string values to the paginator.
     *
     * @return $this
     */
    public function withQueryString();

    /**
     * Get the URL for the previous page, or null.
     *
     * @return string|null
     */
    public function previousPageUrl();

    /**
     * The URL for the next page, or null.
     *
     * @return string|null
     */
    public function nextPageUrl();

    /**
     * Get all of the items being paginated.
     *
     * @return array
     */
    public function items();

    /**
     * Get the "cursor" of the previous set of items.
     *
     * @return \Illuminate\Pagination\Cursor|null
     */
    public function previousCursor();

    /**
     * Get the "cursor" of the next set of items.
     *
     * @return \Illuminate\Pagination\Cursor|null
     */
    public function nextCursor();

    /**
     * Determine how many items are being shown per page.
     *
     * @return int
     */
    public function perPage();

    /**
     * Get the current cursor being paginated.
     *
     * @return \Illuminate\Pagination\Cursor|null
     */
    public function cursor();

    /**
     * Determine if there are enough items to split into multiple pages.
     *
     * @return bool
     */
    public function hasPages();

    /**
     * Get the base path for paginator generated URLs.
     *
     * @return string|null
     */
    public function path();

    /**
     * Determine if the list of items is empty or not.
     *
     * @return bool
     */
    public function isEmpty();

    /**
     * Determine if the list of items is not empty.
     *
     * @return bool
     */
    public function isNotEmpty();

    /**
     * Render the paginator using a given view.
     *
     * @param  string|null  $view
     * @param  array  $data
     * @return string
     */
    public function render($view = null, $data = []);
}
<?php

namespace Illuminate\Contracts\Pagination;

interface LengthAwarePaginator extends Paginator
{
    /**
     * Create a range of pagination URLs.
     *
     * @param  int  $start
     * @param  int  $end
     * @return array
     */
    public function getUrlRange($start, $end);

    /**
     * Determine the total number of items in the data store.
     *
     * @return int
     */
    public function total();

    /**
     * Get the page number of the last available page.
     *
     * @return int
     */
    public function lastPage();
}
<?php

namespace Illuminate\Contracts\Pagination;

interface Paginator
{
    /**
     * Get the URL for a given page.
     *
     * @param  int  $page
     * @return string
     */
    public function url($page);

    /**
     * Add a set of query string values to the paginator.
     *
     * @param  array|string|null  $key
     * @param  string|null  $value
     * @return $this
     */
    public function appends($key, $value = null);

    /**
     * Get / set the URL fragment to be appended to URLs.
     *
     * @param  string|null  $fragment
     * @return $this|string|null
     */
    public function fragment($fragment = null);

    /**
     * The URL for the next page, or null.
     *
     * @return string|null
     */
    public function nextPageUrl();

    /**
     * Get the URL for the previous page, or null.
     *
     * @return string|null
     */
    public function previousPageUrl();

    /**
     * Get all of the items being paginated.
     *
     * @return array
     */
    public function items();

    /**
     * Get the "index" of the first item being paginated.
     *
     * @return int|null
     */
    public function firstItem();

    /**
     * Get the "index" of the last item being paginated.
     *
     * @return int|null
     */
    public function lastItem();

    /**
     * Determine how many items are being shown per page.
     *
     * @return int
     */
    public function perPage();

    /**
     * Determine the current page being paginated.
     *
     * @return int
     */
    public function currentPage();

    /**
     * Determine if there are enough items to split into multiple pages.
     *
     * @return bool
     */
    public function hasPages();

    /**
     * Determine if there are more items in the data store.
     *
     * @return bool
     */
    public function hasMorePages();

    /**
     * Get the base path for paginator generated URLs.
     *
     * @return string|null
     */
    public function path();

    /**
     * Determine if the list of items is empty or not.
     *
     * @return bool
     */
    public function isEmpty();

    /**
     * Determine if the list of items is not empty.
     *
     * @return bool
     */
    public function isNotEmpty();

    /**
     * Render the paginator using a given view.
     *
     * @param  string|null  $view
     * @param  array  $data
     * @return string
     */
    public function render($view = null, $data = []);
}
Copyright (c) 2012 PHP Framework Interoperability Group

Permission is hereby granted, free of charge, to any person obtaining a copy 
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights 
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
copies of the Software, and to permit persons to whom the Software is 
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in 
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
<?php

namespace Psr\Log;

/**
 * This is a simple Logger trait that classes unable to extend AbstractLogger
 * (because they extend another class, etc) can include.
 *
 * It simply delegates all log-level-specific methods to the `log` method to
 * reduce boilerplate code that a simple Logger that does the same thing with
 * messages regardless of the error level has to implement.
 */
trait LoggerTrait
{
    /**
     * System is unusable.
     */
    public function emergency(string|\Stringable $message, array $context = []): void
    {
        $this->log(LogLevel::EMERGENCY, $message, $context);
    }

    /**
     * Action must be taken immediately.
     *
     * Example: Entire website down, database unavailable, etc. This should
     * trigger the SMS alerts and wake you up.
     */
    public function alert(string|\Stringable $message, array $context = []): void
    {
        $this->log(LogLevel::ALERT, $message, $context);
    }

    /**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     */
    public function critical(string|\Stringable $message, array $context = []): void
    {
        $this->log(LogLevel::CRITICAL, $message, $context);
    }

    /**
     * Runtime errors that do not require immediate action but should typically
     * be logged and monitored.
     */
    public function error(string|\Stringable $message, array $context = []): void
    {
        $this->log(LogLevel::ERROR, $message, $context);
    }

    /**
     * Exceptional occurrences that are not errors.
     *
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
     * that are not necessarily wrong.
     */
    public function warning(string|\Stringable $message, array $context = []): void
    {
        $this->log(LogLevel::WARNING, $message, $context);
    }

    /**
     * Normal but significant events.
     */
    public function notice(string|\Stringable $message, array $context = []): void
    {
        $this->log(LogLevel::NOTICE, $message, $context);
    }

    /**
     * Interesting events.
     *
     * Example: User logs in, SQL logs.
     */
    public function info(string|\Stringable $message, array $context = []): void
    {
        $this->log(LogLevel::INFO, $message, $context);
    }

    /**
     * Detailed debug information.
     */
    public function debug(string|\Stringable $message, array $context = []): void
    {
        $this->log(LogLevel::DEBUG, $message, $context);
    }

    /**
     * Logs with an arbitrary level.
     *
     * @param mixed $level
     *
     * @throws \Psr\Log\InvalidArgumentException
     */
    abstract public function log($level, string|\Stringable $message, array $context = []): void;
}
<?php

namespace Psr\Log;

class InvalidArgumentException extends \InvalidArgumentException
{
}
<?php

namespace Psr\Log;

/**
 * Describes a logger instance.
 *
 * The message MUST be a string or object implementing __toString().
 *
 * The message MAY contain placeholders in the form: {foo} where foo
 * will be replaced by the context data in key "foo".
 *
 * The context array can contain arbitrary data. The only assumption that
 * can be made by implementors is that if an Exception instance is given
 * to produce a stack trace, it MUST be in a key named "exception".
 *
 * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
 * for the full interface specification.
 */
interface LoggerInterface
{
    /**
     * System is unusable.
     *
     * @param mixed[] $context
     */
    public function emergency(string|\Stringable $message, array $context = []): void;

    /**
     * Action must be taken immediately.
     *
     * Example: Entire website down, database unavailable, etc. This should
     * trigger the SMS alerts and wake you up.
     *
     * @param mixed[] $context
     */
    public function alert(string|\Stringable $message, array $context = []): void;

    /**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     *
     * @param mixed[] $context
     */
    public function critical(string|\Stringable $message, array $context = []): void;

    /**
     * Runtime errors that do not require immediate action but should typically
     * be logged and monitored.
     *
     * @param mixed[] $context
     */
    public function error(string|\Stringable $message, array $context = []): void;

    /**
     * Exceptional occurrences that are not errors.
     *
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
     * that are not necessarily wrong.
     *
     * @param mixed[] $context
     */
    public function warning(string|\Stringable $message, array $context = []): void;

    /**
     * Normal but significant events.
     *
     * @param mixed[] $context
     */
    public function notice(string|\Stringable $message, array $context = []): void;

    /**
     * Interesting events.
     *
     * Example: User logs in, SQL logs.
     *
     * @param mixed[] $context
     */
    public function info(string|\Stringable $message, array $context = []): void;

    /**
     * Detailed debug information.
     *
     * @param mixed[] $context
     */
    public function debug(string|\Stringable $message, array $context = []): void;

    /**
     * Logs with an arbitrary level.
     *
     * @param mixed $level
     * @param mixed[] $context
     *
     * @throws \Psr\Log\InvalidArgumentException
     */
    public function log($level, string|\Stringable $message, array $context = []): void;
}
<?php

namespace Psr\Log;

/**
 * Describes a logger-aware instance.
 */
interface LoggerAwareInterface
{
    /**
     * Sets a logger instance on the object.
     */
    public function setLogger(LoggerInterface $logger): void;
}
<?php

namespace Psr\Log;

/**
 * Describes log levels.
 */
class LogLevel
{
    const EMERGENCY = 'emergency';
    const ALERT     = 'alert';
    const CRITICAL  = 'critical';
    const ERROR     = 'error';
    const WARNING   = 'warning';
    const NOTICE    = 'notice';
    const INFO      = 'info';
    const DEBUG     = 'debug';
}
<?php

namespace Psr\Log;

/**
 * This is a simple Logger implementation that other Loggers can inherit from.
 *
 * It simply delegates all log-level-specific methods to the `log` method to
 * reduce boilerplate code that a simple Logger that does the same thing with
 * messages regardless of the error level has to implement.
 */
abstract class AbstractLogger implements LoggerInterface
{
    use LoggerTrait;
}
<?php

namespace Psr\Log;

/**
 * Basic Implementation of LoggerAwareInterface.
 */
trait LoggerAwareTrait
{
    /**
     * The logger instance.
     */
    protected ?LoggerInterface $logger = null;

    /**
     * Sets a logger.
     */
    public function setLogger(LoggerInterface $logger): void
    {
        $this->logger = $logger;
    }
}
<?php

namespace Psr\Log;

/**
 * This Logger can be used to avoid conditional log calls.
 *
 * Logging should always be optional, and if no logger is provided to your
 * library creating a NullLogger instance to have something to throw logs at
 * is a good way to avoid littering your code with `if ($this->logger) { }`
 * blocks.
 */
class NullLogger extends AbstractLogger
{
    /**
     * Logs with an arbitrary level.
     *
     * @param mixed[] $context
     *
     * @throws \Psr\Log\InvalidArgumentException
     */
    public function log($level, string|\Stringable $message, array $context = []): void
    {
        // noop
    }
}
<?php

namespace Psr\SimpleCache;

/**
 * Interface used for all types of exceptions thrown by the implementing library.
 */
interface CacheException extends \Throwable
{
}
<?php

namespace Psr\SimpleCache;

/**
 * Exception interface for invalid cache arguments.
 *
 * When an invalid argument is passed it must throw an exception which implements
 * this interface
 */
interface InvalidArgumentException extends CacheException
{
}
<?php

namespace Psr\SimpleCache;

interface CacheInterface
{
    /**
     * Fetches a value from the cache.
     *
     * @param string $key     The unique key of this item in the cache.
     * @param mixed  $default Default value to return if the key does not exist.
     *
     * @return mixed The value of the item from the cache, or $default in case of cache miss.
     *
     * @throws \Psr\SimpleCache\InvalidArgumentException
     *   MUST be thrown if the $key string is not a legal value.
     */
    public function get(string $key, mixed $default = null): mixed;

    /**
     * Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time.
     *
     * @param string                 $key   The key of the item to store.
     * @param mixed                  $value The value of the item to store, must be serializable.
     * @param null|int|\DateInterval $ttl   Optional. The TTL value of this item. If no value is sent and
     *                                      the driver supports TTL then the library may set a default value
     *                                      for it or let the driver take care of that.
     *
     * @return bool True on success and false on failure.
     *
     * @throws \Psr\SimpleCache\InvalidArgumentException
     *   MUST be thrown if the $key string is not a legal value.
     */
    public function set(string $key, mixed $value, null|int|\DateInterval $ttl = null): bool;

    /**
     * Delete an item from the cache by its unique key.
     *
     * @param string $key The unique cache key of the item to delete.
     *
     * @return bool True if the item was successfully removed. False if there was an error.
     *
     * @throws \Psr\SimpleCache\InvalidArgumentException
     *   MUST be thrown if the $key string is not a legal value.
     */
    public function delete(string $key): bool;

    /**
     * Wipes clean the entire cache's keys.
     *
     * @return bool True on success and false on failure.
     */
    public function clear(): bool;

    /**
     * Obtains multiple cache items by their unique keys.
     *
     * @param iterable<string> $keys    A list of keys that can be obtained in a single operation.
     * @param mixed            $default Default value to return for keys that do not exist.
     *
     * @return iterable<string, mixed> A list of key => value pairs. Cache keys that do not exist or are stale will have $default as value.
     *
     * @throws \Psr\SimpleCache\InvalidArgumentException
     *   MUST be thrown if $keys is neither an array nor a Traversable,
     *   or if any of the $keys are not a legal value.
     */
    public function getMultiple(iterable $keys, mixed $default = null): iterable;

    /**
     * Persists a set of key => value pairs in the cache, with an optional TTL.
     *
     * @param iterable               $values A list of key => value pairs for a multiple-set operation.
     * @param null|int|\DateInterval $ttl    Optional. The TTL value of this item. If no value is sent and
     *                                       the driver supports TTL then the library may set a default value
     *                                       for it or let the driver take care of that.
     *
     * @return bool True on success and false on failure.
     *
     * @throws \Psr\SimpleCache\InvalidArgumentException
     *   MUST be thrown if $values is neither an array nor a Traversable,
     *   or if any of the $values are not a legal value.
     */
    public function setMultiple(iterable $values, null|int|\DateInterval $ttl = null): bool;

    /**
     * Deletes multiple cache items in a single operation.
     *
     * @param iterable<string> $keys A list of string-based keys to be deleted.
     *
     * @return bool True if the items were successfully removed. False if there was an error.
     *
     * @throws \Psr\SimpleCache\InvalidArgumentException
     *   MUST be thrown if $keys is neither an array nor a Traversable,
     *   or if any of the $keys are not a legal value.
     */
    public function deleteMultiple(iterable $keys): bool;

    /**
     * Determines whether an item is present in the cache.
     *
     * NOTE: It is recommended that has() is only to be used for cache warming type purposes
     * and not to be used within your live applications operations for get/set, as this method
     * is subject to a race condition where your has() will return true and immediately after,
     * another script can remove it making the state of your app out of date.
     *
     * @param string $key The cache item key.
     *
     * @return bool
     *
     * @throws \Psr\SimpleCache\InvalidArgumentException
     *   MUST be thrown if the $key string is not a legal value.
     */
    public function has(string $key): bool;
}
The MIT License (MIT)

Copyright (c) 2013-2016 container-interop
Copyright (c) 2016 PHP Framework Interoperability Group

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
<?php

namespace Psr\Container;

/**
 * No entry was found in the container.
 */
interface NotFoundExceptionInterface extends ContainerExceptionInterface
{
}
<?php

declare(strict_types=1);

namespace Psr\Container;

/**
 * Describes the interface of a container that exposes methods to read its entries.
 */
interface ContainerInterface
{
    /**
     * Finds an entry of the container by its identifier and returns it.
     *
     * @param string $id Identifier of the entry to look for.
     *
     * @throws NotFoundExceptionInterface  No entry was found for **this** identifier.
     * @throws ContainerExceptionInterface Error while retrieving the entry.
     *
     * @return mixed Entry.
     */
    public function get(string $id);

    /**
     * Returns true if the container can return an entry for the given identifier.
     * Returns false otherwise.
     *
     * `has($id)` returning true does not mean that `get($id)` will not throw an exception.
     * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
     *
     * @param string $id Identifier of the entry to look for.
     *
     * @return bool
     */
    public function has(string $id): bool;
}
<?php

namespace Psr\Container;

use Throwable;

/**
 * Base interface representing a generic exception in a container.
 */
interface ContainerExceptionInterface extends Throwable
{
}
MIT License

Copyright (c) 2018 PHP-FIG

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.
<?php
declare(strict_types=1);

namespace Psr\EventDispatcher;

/**
 * An Event whose processing may be interrupted when the event has been handled.
 *
 * A Dispatcher implementation MUST check to determine if an Event
 * is marked as stopped after each listener is called.  If it is then it should
 * return immediately without calling any further Listeners.
 */
interface StoppableEventInterface
{
    /**
     * Is propagation stopped?
     *
     * This will typically only be used by the Dispatcher to determine if the
     * previous listener halted propagation.
     *
     * @return bool
     *   True if the Event is complete and no further listeners should be called.
     *   False to continue calling listeners.
     */
    public function isPropagationStopped() : bool;
}
<?php
declare(strict_types=1);

namespace Psr\EventDispatcher;

/**
 * Defines a dispatcher for events.
 */
interface EventDispatcherInterface
{
    /**
     * Provide all relevant listeners with an event to process.
     *
     * @param object $event
     *   The object to process.
     *
     * @return object
     *   The Event that was passed, now modified by listeners.
     */
    public function dispatch(object $event);
}
<?php
declare(strict_types=1);

namespace Psr\EventDispatcher;

/**
 * Mapper from an event to the listeners that are applicable to that event.
 */
interface ListenerProviderInterface
{
    /**
     * @param object $event
     *   An event for which to return the relevant listeners.
     * @return iterable[callable]
     *   An iterable (array, iterator, or generator) of callables.  Each
     *   callable MUST be type-compatible with $event.
     */
    public function getListenersForEvent(object $event) : iterable;
}
<?php

declare(strict_types=1);

namespace Php\Pie\Util;

use RuntimeException;

use function sprintf;

class FileNotFound extends RuntimeException
{
    public static function fromFilename(string $filename): self
    {
        return new self(sprintf(
            'File "%s" does not exist',
            $filename,
        ));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Util;

use Composer\InstalledVersions;

use function class_exists;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class PieVersion
{
    /**
     * Note: magic constant that causes Symfony Console to not display a version
     * {@see Application::getLongVersion()}
     */
    private const SYMFONY_MAGIC_CONST_UNKNOWN = 'UNKNOWN';

    /**
     * This value is replaced dynamically by Box with the real version when
     * we build the PHAR. It is based on the Git tag and/or version
     *
     * It will be replaced with `2.0.0` on an exact tag match, or something
     * like `2.0.0@e558e33` on a commit following a tag.
     *
     * When running not in a PHAR, this will not be replaced, so this
     * method needs additional logic to determine the version.
     *
     * @link https://box-project.github.io/box/configuration/#pretty-git-tag-placeholder-git
     */
    private const PIE_VERSION = '1.2.2';

    public static function isPharBuild(): bool
    {
        /** @psalm-suppress RedundantCondition */

        // phpcs:ignore Generic.Strings.UnnecessaryStringConcat.Found
        return self::PIE_VERSION !== '@pie_version' . '@';
    }

    /**
     * A static method to try to find the version of PIE you are currently
     * running. If running in the PHAR built with Box, this should return a
     * realistic-looking version; usually either a tag (e.g. `2.0.0`), or a tag
     * and following commit short hash (e.g. `2.0.0@e558e33`). If not this will
     * fall back to some other techniques to try to determine a version.
     *
     * @return non-empty-string
     */
    public static function get(): string
    {
        if (self::isPharBuild()) {
            return self::PIE_VERSION;
        }

        if (! class_exists(InstalledVersions::class)) {
            return self::SYMFONY_MAGIC_CONST_UNKNOWN;
        }

        /**
         * This tries to determine the version based on Composer; if we are
         * the root package (i.e. you're developing on it), this will most
         * likely be something like `dev-main` (branch name).
         */
        $installedVersion = InstalledVersions::getVersion(InstalledVersions::getRootPackage()['name']);
        if ($installedVersion === null || $installedVersion === '') {
            return self::SYMFONY_MAGIC_CONST_UNKNOWN;
        }

        return $installedVersion;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Util;

use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process as SymfonyProcess;

use function trim;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class Process
{
    /** @psalm-suppress UnusedConstructor */
    private function __construct()
    {
    }

    /**
     * Just a helper to invoke a Symfony Process command with a simplified API
     * for the common invocations we have in PIE.
     *
     * Things to note:
     *  - uses mustRun (i.e. throws exception if command execution fails)
     *  - very short timeout by default (5 seconds)
     *  - output is trimmed
     *
     * @param list<string>                                                         $command
     * @param callable(SymfonyProcess::ERR|SymfonyProcess::OUT, string): void|null $outputCallback
     *
     * @throws ProcessFailedException
     */
    public static function run(
        array $command,
        string|null $workingDirectory = null,
        int|null $timeout = 5,
        callable|null $outputCallback = null,
    ): string {
        return trim((new SymfonyProcess($command, $workingDirectory, timeout: $timeout))
            ->mustRun($outputCallback)
            ->getOutput());
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Util;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class Emoji
{
    public const GREEN_CHECKMARK = '✅';
    public const WARNING         = '⚠️ ';
    public const PROHIBITED      = '🚫';
}
<?php

declare(strict_types=1);

namespace Php\Pie\Util;

use function restore_error_handler;
use function set_error_handler;

/**
 * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
 *
 * @psalm-type CapturedErrorList = list<array{level: int, message: string, filename: string, line: int}>
 */
final class CaptureErrors
{
    /**
     * @param callable():T      $code
     * @param CapturedErrorList $captured
     *
     * @return T
     *
     * @template T
     */
    public static function for(callable $code, array &$captured): mixed
    {
        set_error_handler(static function (int $level, string $message, string $filename, int $line) use (&$captured): bool {
            $captured[] = [
                'level' => $level,
                'message' => $message,
                'filename' => $filename,
                'line' => $line,
            ];

            return true;
        });

        $returnValue = $code();

        restore_error_handler();

        return $returnValue;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Composer\Semver\Semver;
use Composer\Util\AuthHelper;
use Composer\Util\HttpDownloader;
use Php\Pie\ComposerIntegration\PieComposerFactory;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\ComposerIntegration\QuieterConsoleIO;
use Php\Pie\File\FullPathToSelf;
use Php\Pie\File\SudoFilePut;
use Php\Pie\SelfManage\Update\FetchPieReleaseFromGitHub;
use Php\Pie\SelfManage\Update\ReleaseMetadata;
use Php\Pie\SelfManage\Verify\FailedToVerifyRelease;
use Php\Pie\SelfManage\Verify\VerifyPieReleaseUsingAttestation;
use Php\Pie\Util\Emoji;
use Php\Pie\Util\PieVersion;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
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 function file_get_contents;
use function preg_match;
use function sprintf;
use function unlink;

#[AsCommand(
    name: 'self-update',
    description: 'Self update PIE',
)]
final class SelfUpdateCommand extends Command
{
    private const OPTION_NIGHTLY_UPDATE = 'nightly';

    /** @param non-empty-string $githubApiBaseUrl */
    public function __construct(
        private readonly string $githubApiBaseUrl,
        private readonly QuieterConsoleIO $io,
        private readonly ContainerInterface $container,
        private readonly FullPathToSelf $fullPathToSelf,
    ) {
        parent::__construct();
    }

    public function configure(): void
    {
        parent::configure();

        CommandHelper::configurePhpConfigOptions($this);
        $this->addOption(
            self::OPTION_NIGHTLY_UPDATE,
            null,
            InputOption::VALUE_NONE,
            'Update to the latest nightly version.',
        );
    }

    public function execute(InputInterface $input, OutputInterface $output): int
    {
        if (! PieVersion::isPharBuild()) {
            $output->writeln('<comment>Aborting! You are not running a PHAR, cannot self-update.</comment>');

            return Command::FAILURE;
        }

        $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $output);

        $composer = PieComposerFactory::createPieComposer(
            $this->container,
            PieComposerRequest::noOperation(
                $output,
                $targetPlatform,
            ),
        );

        $httpDownloader        = new HttpDownloader($this->io, $composer->getConfig());
        $authHelper            = new AuthHelper($this->io, $composer->getConfig());
        $fetchLatestPieRelease = new FetchPieReleaseFromGitHub($this->githubApiBaseUrl, $httpDownloader, $authHelper);
        $verifyPiePhar         = VerifyPieReleaseUsingAttestation::factory($this->githubApiBaseUrl, $httpDownloader, $authHelper);

        if ($input->hasOption(self::OPTION_NIGHTLY_UPDATE) && $input->getOption(self::OPTION_NIGHTLY_UPDATE)) {
            $latestRelease = new ReleaseMetadata(
                'nightly',
                'https://php.github.io/pie/pie-nightly.phar',
            );

            $output->writeln('Downloading the latest nightly release.');
        } else {
            $latestRelease = $fetchLatestPieRelease->latestReleaseMetadata();
            $pieVersion    = PieVersion::get();

            if (preg_match('/^(?<tag>.+)@(?<hash>[a-f0-9]{7})$/', $pieVersion, $matches)) {
                // Have to change the version to something the Semver library understands
                $pieVersion = sprintf('dev-main#%s', $matches['hash']);
                $output->writeln(sprintf(
                    'It looks like you are running a nightly build; if you want to get the newest nightly, specify the --%s flag.',
                    self::OPTION_NIGHTLY_UPDATE,
                ));
            }

            $output->writeln(sprintf('You are currently running PIE version %s', $pieVersion));

            if (! Semver::satisfies($latestRelease->tag, '> ' . $pieVersion)) {
                $output->writeln('<info>You already have the latest version 😍</info>');

                return Command::SUCCESS;
            }

            $output->writeln(
                sprintf('Newer version %s found, going to update you... ⏳', $latestRelease->tag),
                OutputInterface::VERBOSITY_VERBOSE,
            );
        }

        $pharFilename = $fetchLatestPieRelease->downloadContent($latestRelease);

        $output->writeln(
            sprintf('Verifying release with digest sha256:%s...', $pharFilename->checksum),
            OutputInterface::VERBOSITY_VERBOSE,
        );

        try {
            $verifyPiePhar->verify($latestRelease, $pharFilename, $output);
        } catch (FailedToVerifyRelease $failedToVerifyRelease) {
            $output->writeln(sprintf(
                '<error>❌ Failed to verify the pie.phar release %s: %s</error>',
                $latestRelease->tag,
                $failedToVerifyRelease->getMessage(),
            ));

            $output->writeln('This means I could not verify that the PHAR we tried to update to was authentic, so I am aborting the self-update.');
            unlink($pharFilename->filePath);

            return Command::FAILURE;
        }

        $fullPathToSelf = ($this->fullPathToSelf)();
        $output->writeln(
            sprintf('Writing new version to %s', $fullPathToSelf),
            OutputInterface::VERBOSITY_VERBOSE,
        );
        SudoFilePut::contents($fullPathToSelf, file_get_contents($pharFilename->filePath));
        unlink($pharFilename->filePath);

        $output->writeln(sprintf(
            '<info>%s PIE has been upgraded to %s</info>',
            Emoji::GREEN_CHECKMARK,
            $latestRelease->tag,
        ));

        $this->exitSuccessfully();
    }

    /**
     * Exit is needed at the moment, as we have an EventDispatcher set on
     * the application, which means classes try to get loaded (such as
     * {@see \Symfony\Component\Console\Event\ConsoleTerminateEvent}), but
     * AFTER we've over-written the PHAR. This results in weird behaviour when
     * the class tries to get loaded, as the PHAR content changed. Not an
     * ideal approach, need to look into better ways of handling it, maybe
     * adding an event listener to overwrite the PHAR *after* the command runs.
     */
    private function exitSuccessfully(): never
    {
        exit(0);
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Composer\Composer;
use Composer\Package\CompletePackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\Repository\ComposerRepository;
use Composer\Repository\PathRepository;
use Composer\Repository\VcsRepository;
use Composer\Util\Platform;
use InvalidArgumentException;
use OutOfRangeException;
use Php\Pie\ComposerIntegration\PieComposerFactory;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\DependencyResolver\InvalidPackageName;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\DependencyResolver\RequestedPackageAndVersion;
use Php\Pie\DependencyResolver\UnableToResolveRequirement;
use Php\Pie\Installing\InstallForPhpProject\FindMatchingPackages;
use Php\Pie\Platform as PiePlatform;
use Php\Pie\Platform\OperatingSystem;
use Php\Pie\Platform\TargetPhp\PhpBinaryPath;
use Php\Pie\Platform\TargetPhp\PhpizePath;
use Php\Pie\Platform\TargetPlatform;
use Psr\Container\ContainerInterface;
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 Throwable;
use Webmozart\Assert\Assert;

use function array_key_exists;
use function array_map;
use function count;
use function is_array;
use function is_string;
use function reset;
use function sprintf;
use function str_starts_with;
use function strtolower;
use function substr;
use function trim;

use const PHP_VERSION;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class CommandHelper
{
    public const ARG_REQUESTED_PACKAGE_AND_VERSION = 'requested-package-and-version';
    public const OPTION_WITH_PHP_CONFIG            = 'with-php-config';
    public const OPTION_WITH_PHP_PATH              = 'with-php-path';
    public const OPTION_WITH_PHPIZE_PATH           = 'with-phpize-path';
    public const OPTION_WORKING_DIRECTORY          = 'working-dir';
    private const OPTION_MAKE_PARALLEL_JOBS        = 'make-parallel-jobs';
    private const OPTION_SKIP_ENABLE_EXTENSION     = 'skip-enable-extension';
    private const OPTION_FORCE                     = 'force';

    /** @psalm-suppress UnusedConstructor */
    private function __construct()
    {
    }

    public static function configurePhpConfigOptions(Command $command): void
    {
        $command->addOption(
            self::OPTION_WITH_PHP_CONFIG,
            null,
            InputOption::VALUE_REQUIRED,
            'The path to the `php-config` binary to find the target PHP platform on ' . OperatingSystem::NonWindows->asFriendlyName() . ', e.g. --' . self::OPTION_WITH_PHP_CONFIG . '=/usr/bin/php-config7.4',
        );
        $command->addOption(
            self::OPTION_WITH_PHP_PATH,
            null,
            InputOption::VALUE_REQUIRED,
            'The path to the `php` binary to use as the target PHP platform on ' . OperatingSystem::Windows->asFriendlyName() . ', e.g. --' . self::OPTION_WITH_PHP_PATH . '=C:\usr\php7.4.33\php.exe',
        );
        $command->addOption(
            self::OPTION_WITH_PHPIZE_PATH,
            null,
            InputOption::VALUE_REQUIRED,
            'The path to the `phpize` binary to use as the target PHP platform, e.g. --' . self::OPTION_WITH_PHPIZE_PATH . '=/usr/bin/phpize7.4',
        );
    }

    public static function configureDownloadBuildInstallOptions(Command $command, bool $withRequestedPackageAndVersion = true): void
    {
        if ($withRequestedPackageAndVersion) {
            $command->addArgument(
                self::ARG_REQUESTED_PACKAGE_AND_VERSION,
                InputArgument::OPTIONAL,
                'The PIE package name and version constraint to use, in the format {vendor/package}{?:{?version-constraint}{?@stability}}, for example `xdebug/xdebug:^3.4@alpha`, `xdebug/xdebug:@alpha`, `xdebug/xdebug:^3.4`, etc.',
            );
        }

        $command->addOption(
            self::OPTION_MAKE_PARALLEL_JOBS,
            'j',
            InputOption::VALUE_REQUIRED,
            'Override many jobs to run in parallel when running compiling (this is passed to "make -jN" during build). PIE will try to detect this by default.',
        );
        $command->addOption(
            self::OPTION_SKIP_ENABLE_EXTENSION,
            null,
            InputOption::VALUE_NONE,
            'Specify this to skip attempting to enable the extension in php.ini',
        );
        $command->addOption(
            self::OPTION_FORCE,
            null,
            InputOption::VALUE_NONE,
            'To attempt to install a version that doesn\'t match the version constraints from the meta-data, for instance to install an older version than recommended, or when the signature is not available.',
        );

        $command->addOption(
            self::OPTION_WORKING_DIRECTORY,
            'd',
            InputOption::VALUE_REQUIRED,
            'The working directory to use, where applicable. If not specified, the current working directory is used. Only used in certain contexts.',
        );

        self::configurePhpConfigOptions($command);

        /**
         * Allows additional options for the `./configure` command to be passed here.
         * Note, this means you probably need to call {@see self::validateInput()} to validate the input manually...
         */
        $command->ignoreValidationErrors();
    }

    public static function validateInput(InputInterface $input, Command $command): void
    {
        $input->bind($command->getDefinition());
    }

    public static function determineTargetPlatformFromInputs(InputInterface $input, OutputInterface $output): TargetPlatform
    {
        $phpBinaryPath = PhpBinaryPath::fromCurrentProcess();

        /** @var mixed $withPhpConfig */
        $withPhpConfig          = $input->getOption(self::OPTION_WITH_PHP_CONFIG);
        $specifiedWithPhpConfig = is_string($withPhpConfig) && $withPhpConfig !== '';
        /** @var mixed $withPhpPath */
        $withPhpPath          = $input->getOption(self::OPTION_WITH_PHP_PATH);
        $specifiedWithPhpPath = is_string($withPhpPath) && $withPhpPath !== '';

        if (Platform::isWindows() && $specifiedWithPhpConfig) {
            throw new InvalidArgumentException('The --with-php-config=/path/to/php-config cannot be used on Windows, use --with-php-path=/path/to/php instead.');
        }

        if (! Platform::isWindows() && $specifiedWithPhpPath && ! $specifiedWithPhpConfig) {
            throw new InvalidArgumentException('The --with-php-path=/path/to/php cannot be used on non-Windows, use --with-php-config=/path/to/php-config instead.');
        }

        if (Platform::isWindows() && $input->hasOption(self::OPTION_WITH_PHPIZE_PATH)) {
            /** @var mixed $withPhpizePath */
            $withPhpizePath = $input->getOption(self::OPTION_WITH_PHPIZE_PATH);

            if (is_string($withPhpizePath) && trim($withPhpizePath) !== '') {
                throw new InvalidArgumentException('The --with-phpize-path=/path/to/phpize cannot be used on Windows.');
            }
        }

        if ($specifiedWithPhpConfig) {
            $phpBinaryPath = PhpBinaryPath::fromPhpConfigExecutable($withPhpConfig);
        }

        if ($specifiedWithPhpPath) {
            $phpBinaryPath = PhpBinaryPath::fromPhpBinaryPath($withPhpPath);
        }

        $makeParallelJobs = null; /** `null` means {@see TargetPlatform} will try to auto-detect */
        if ($input->hasOption(self::OPTION_MAKE_PARALLEL_JOBS)) {
            $makeParallelJobsOptions = (int) $input->getOption(self::OPTION_MAKE_PARALLEL_JOBS);
            if ($makeParallelJobsOptions > 0) {
                $makeParallelJobs = $makeParallelJobsOptions;
            }
        }

        $targetPlatform = TargetPlatform::fromPhpBinaryPath($phpBinaryPath, $makeParallelJobs);

        $output->writeln(sprintf('<info>You are running PHP %s</info>', PHP_VERSION));
        $output->writeln(sprintf(
            '<info>Target PHP installation:</info> %s %s%s, on %s %s (from %s)',
            $phpBinaryPath->version(),
            $targetPlatform->threadSafety->asShort(),
            strtolower($targetPlatform->windowsCompiler !== null ? ', ' . $targetPlatform->windowsCompiler->name : ''),
            $targetPlatform->operatingSystem->asFriendlyName(),
            $targetPlatform->architecture->name,
            $phpBinaryPath->phpBinaryPath,
        ));
        $output->writeln(
            sprintf(
                '<info>Using pie.json:</info> %s',
                PiePlatform::getPieJsonFilename($targetPlatform),
            ),
            OutputInterface::VERBOSITY_VERBOSE,
        );

        return $targetPlatform;
    }

    public static function determineAttemptToSetupIniFile(InputInterface $input): bool
    {
        return ! $input->hasOption(self::OPTION_SKIP_ENABLE_EXTENSION) || ! $input->getOption(self::OPTION_SKIP_ENABLE_EXTENSION);
    }

    public static function determineForceInstallingPackageVersion(InputInterface $input): bool
    {
        return $input->hasOption(self::OPTION_FORCE) && $input->getOption(self::OPTION_FORCE);
    }

    public static function determinePhpizePathFromInputs(InputInterface $input): PhpizePath|null
    {
        if ($input->hasOption(self::OPTION_WITH_PHPIZE_PATH)) {
            $phpizePathOption = (string) $input->getOption(self::OPTION_WITH_PHPIZE_PATH);
            if (trim($phpizePathOption) !== '') {
                /** @psalm-suppress ArgumentTypeCoercion */
                return new PhpizePath($phpizePathOption);
            }
        }

        return null;
    }

    public static function requestedNameAndVersionPair(InputInterface $input): RequestedPackageAndVersion
    {
        $requestedPackageString = $input->getArgument(self::ARG_REQUESTED_PACKAGE_AND_VERSION);

        if (! is_string($requestedPackageString) || $requestedPackageString === '') {
            throw new InvalidArgumentException('No package was requested for installation');
        }

        $nameAndVersionPairs         = (new VersionParser())
            ->parseNameVersionPairs([$requestedPackageString]);
        $requestedNameAndVersionPair = reset($nameAndVersionPairs);

        if (! is_array($requestedNameAndVersionPair)) {
            throw new InvalidArgumentException('Failed to parse the name/version pair');
        }

        if (! array_key_exists('version', $requestedNameAndVersionPair)) {
            $requestedNameAndVersionPair['version'] = null;
        }

        Assert::stringNotEmpty($requestedNameAndVersionPair['name']);
        Assert::nullOrStringNotEmpty($requestedNameAndVersionPair['version']);

        return new RequestedPackageAndVersion(
            $requestedNameAndVersionPair['name'],
            $requestedNameAndVersionPair['version'],
        );
    }

    public static function bindConfigureOptionsFromPackage(Command $command, Package $package, InputInterface $input): void
    {
        foreach ($package->configureOptions() as $configureOption) {
            $command->addOption(
                $configureOption->name,
                null,
                $configureOption->needsValue ? InputOption::VALUE_REQUIRED : InputOption::VALUE_NONE,
                $configureOption->description,
            );
        }

        self::validateInput($input, $command);
    }

    /** @return list<non-empty-string> */
    public static function processConfigureOptionsFromInput(Package $package, InputInterface $input): array
    {
        $configureOptionsValues = [];
        foreach ($package->configureOptions() as $configureOption) {
            if (! $input->hasOption($configureOption->name)) {
                continue;
            }

            $value = $input->getOption($configureOption->name);

            if ($configureOption->needsValue) {
                if (is_string($value) && $value !== '') {
                    $configureOptionsValues[] = '--' . $configureOption->name . '=' . $value;
                }

                continue;
            }

            Assert::boolean($value);
            if ($value !== true) {
                continue;
            }

            $configureOptionsValues[] = '--' . $configureOption->name;
        }

        return $configureOptionsValues;
    }

    public static function listRepositories(Composer $composer, OutputInterface $output): void
    {
        $output->writeln('The following repositories are in use for this Target PHP:');

        foreach ($composer->getRepositoryManager()->getRepositories() as $repo) {
            if ($repo instanceof ComposerRepository) {
                $repoConfig = $repo->getRepoConfig();

                $repoUrl = array_key_exists('url', $repoConfig) && is_string($repoConfig['url']) && $repoConfig['url'] !== '' ? $repoConfig['url'] : null;

                if ($repoUrl === 'https://repo.packagist.org') {
                    $output->writeln('  - Packagist');
                    continue;
                }

                $output->writeln(sprintf('  - Composer (%s)', $repoUrl ?? 'no url?'));
                continue;
            }

            if ($repo instanceof VcsRepository) {
                /** @psalm-suppress InternalMethod */
                $output->writeln(sprintf(
                    '  - VCS Repository (%s)',
                    $repo->getDriver()?->getUrl() ?? 'no url?',
                ));
                continue;
            }

            if (! $repo instanceof PathRepository) {
                continue;
            }

            $repoConfig = $repo->getRepoConfig();
            $output->writeln(sprintf(
                '  - Path Repository (%s)',
                array_key_exists('url', $repoConfig) && is_string($repoConfig['url']) && $repoConfig['url'] !== '' ? $repoConfig['url'] : 'no path?',
            ));
        }
    }

    public static function handlePackageNotFound(
        InvalidPackageName|UnableToResolveRequirement $exception,
        FindMatchingPackages $findMatchingPackages,
        OutputInterface $output,
        TargetPlatform $targetPlatform,
        ContainerInterface $container,
    ): int {
        $pieComposer = PieComposerFactory::createPieComposer(
            $container,
            PieComposerRequest::noOperation(
                $output,
                $targetPlatform,
            ),
        );

        $requestedPackageName = $exception->requestedPackageAndVersion->package;
        if (str_starts_with($requestedPackageName, 'ext-')) {
            $requestedPackageName = substr($requestedPackageName, 4);
        }

        $output->writeln('');
        $output->writeln(sprintf('<error>Could not install package: %s</error>', $requestedPackageName));
        $output->writeln($exception->getMessage());

        try {
            $matches = array_map(
                static function (array $match) use ($output, $pieComposer): array {
                    $composerMatchingPackage = $pieComposer->getRepositoryManager()->findPackage($match['name'], '*');

                    // Attempts to augment the Composer packages found with the PIE extension name
                    if ($composerMatchingPackage instanceof CompletePackageInterface) {
                        try {
                            $match['extension-name'] = Package
                                ::fromComposerCompletePackage($composerMatchingPackage)
                                ->extensionName()
                                ->name();
                        } catch (Throwable $t) {
                            $output->writeln(
                                sprintf(
                                    'Tried looking up extension name for %s, but failed: %s',
                                    $match['name'],
                                    $t->getMessage(),
                                ),
                                OutputInterface::VERBOSITY_VERY_VERBOSE,
                            );
                        }
                    }

                    return $match;
                },
                $findMatchingPackages->for($pieComposer, $requestedPackageName),
            );

            if (count($matches)) {
                $output->writeln('');
                if (count($matches) === 1) {
                    $output->writeln('<info>Did you mean this?</info>');
                } else {
                    $output->writeln('<info>Did you mean one of these?</info>');
                }

                array_map(
                    static function (array $match) use ($output): void {
                        $output->writeln(sprintf(
                            ' - %s%s: %s',
                            $match['name'],
                            array_key_exists('extension-name', $match) && is_string($match['extension-name'])
                                ? ' (provides extension: ' . $match['extension-name'] . ')'
                                : '',
                            $match['description'] ?? 'no description available',
                        ));
                    },
                    $matches,
                );
            }
        } catch (OutOfRangeException) {
            $output->writeln(
                sprintf(
                    'Tried searching for "%s", but nothing was found.',
                    $requestedPackageName,
                ),
                OutputInterface::VERBOSITY_VERBOSE,
            );
        }

        return 1;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Php\Pie\ComposerIntegration\PieComposerFactory;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\ComposerIntegration\PieOperation;
use Php\Pie\DependencyResolver\BundledPhpExtensionRefusal;
use Php\Pie\DependencyResolver\DependencyResolver;
use Php\Pie\DependencyResolver\InvalidPackageName;
use Php\Pie\DependencyResolver\UnableToResolveRequirement;
use Php\Pie\Installing\InstallForPhpProject\FindMatchingPackages;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

use function count;
use function sprintf;

#[AsCommand(
    name: 'info',
    description: 'Show metadata about a given extension.',
)]
final class InfoCommand extends Command
{
    public function __construct(
        private readonly ContainerInterface $container,
        private readonly DependencyResolver $dependencyResolver,
        private readonly FindMatchingPackages $findMatchingPackages,
    ) {
        parent::__construct();
    }

    public function configure(): void
    {
        parent::configure();

        CommandHelper::configureDownloadBuildInstallOptions($this);
    }

    public function execute(InputInterface $input, OutputInterface $output): int
    {
        CommandHelper::validateInput($input, $this);

        $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $output);

        try {
            $requestedNameAndVersion = CommandHelper::requestedNameAndVersionPair($input);
        } catch (InvalidPackageName $invalidPackageName) {
            return CommandHelper::handlePackageNotFound(
                $invalidPackageName,
                $this->findMatchingPackages,
                $output,
                $targetPlatform,
                $this->container,
            );
        }

        $composer = PieComposerFactory::createPieComposer(
            $this->container,
            new PieComposerRequest(
                $output,
                $targetPlatform,
                $requestedNameAndVersion,
                PieOperation::Resolve,
                [], // Configure options are not needed for resolve only
                null,
                false, // setting up INI not needed for info
            ),
        );

        try {
            $package = ($this->dependencyResolver)(
                $composer,
                $targetPlatform,
                $requestedNameAndVersion,
                CommandHelper::determineForceInstallingPackageVersion($input),
            );
        } catch (UnableToResolveRequirement $unableToResolveRequirement) {
            return CommandHelper::handlePackageNotFound(
                $unableToResolveRequirement,
                $this->findMatchingPackages,
                $output,
                $targetPlatform,
                $this->container,
            );
        } catch (BundledPhpExtensionRefusal $bundledPhpExtensionRefusal) {
            $output->writeln('');
            $output->writeln('<comment>' . $bundledPhpExtensionRefusal->getMessage() . '</comment>');

            return self::INVALID;
        }

        $output->writeln(sprintf('<info>Found package:</info> %s which provides <info>%s</info>', $package->prettyNameAndVersion(), $package->extensionName()->nameWithExtPrefix()));

        $output->writeln(sprintf('Extension name: %s', $package->extensionName()->name()));
        $output->writeln(sprintf('Extension type: %s (%s)', $package->extensionType()->value, $package->extensionType()->name));
        $output->writeln(sprintf('Composer package name: %s', $package->name()));
        $output->writeln(sprintf('Version: %s', $package->version()));
        $output->writeln(sprintf('Download URL: %s', $package->downloadUrl() ?? '(not specified)'));

        if (count($package->configureOptions())) {
            $output->writeln('Configure options:');
            foreach ($package->configureOptions() as $configureOption) {
                $output->writeln(sprintf('    --%s%s  (%s)', $configureOption->name, $configureOption->needsValue ? '=?' : '', $configureOption->description));
            }
        } else {
            $output->writeln('No configure options are specified.');
        }

        return Command::SUCCESS;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Php\Pie\ComposerIntegration\PieComposerFactory;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\ComposerIntegration\PieJsonEditor;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Webmozart\Assert\Assert;

use function realpath;
use function str_contains;

#[AsCommand(
    name: 'repository:add',
    description: 'Add a new repository for packages that PIE can use.',
)]
final class RepositoryAddCommand extends Command
{
    private const ARG_TYPE = 'type';
    private const ARG_URL  = 'url';

    private const ALLOWED_TYPES = ['vcs', 'path', 'composer'];

    public function __construct(
        private readonly ContainerInterface $container,
    ) {
        parent::__construct();
    }

    public function configure(): void
    {
        parent::configure();

        CommandHelper::configurePhpConfigOptions($this);

        $this->addArgument(
            self::ARG_TYPE,
            InputArgument::REQUIRED,
            'Specify the type of the repository, e.g. vcs, path, composer',
        );
        $this->addArgument(
            self::ARG_URL,
            InputArgument::REQUIRED,
            'Specify the URL of the repository, e.g. a Github/Gitlab URL, a filesystem path, or Private Packagist URL',
        );
    }

    public function execute(InputInterface $input, OutputInterface $output): int
    {
        $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $output);
        $pieJsonEditor  = PieJsonEditor::fromTargetPlatform($targetPlatform);

        $type = (string) $input->getArgument(self::ARG_TYPE);
        /** @psalm-var 'vcs'|'path'|'composer' $type */
        Assert::inArray($type, self::ALLOWED_TYPES);

        $url = $originalUrl = (string) $input->getArgument(self::ARG_URL);

        if ($type === 'path') {
            $url = realpath($originalUrl);
        }

        if ($type === 'composer' && str_contains($url, 'packagist.org')) {
            // "adding packagist" is really just removing an exclusion
            $pieJsonEditor
                ->ensureExists()
                ->removeRepository('packagist.org');
        } else {
            Assert::stringNotEmpty($url, 'Could not resolve ' . $originalUrl . ' to a real path');

            $pieJsonEditor
                ->ensureExists()
                ->addRepository($type, $url);
        }

        CommandHelper::listRepositories(
            PieComposerFactory::createPieComposer(
                $this->container,
                PieComposerRequest::noOperation(
                    $output,
                    $targetPlatform,
                ),
            ),
            $output,
        );

        return 0;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Php\Pie\ComposerIntegration\ComposerIntegrationHandler;
use Php\Pie\ComposerIntegration\ComposerRunFailed;
use Php\Pie\ComposerIntegration\PieComposerFactory;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\ComposerIntegration\PieOperation;
use Php\Pie\DependencyResolver\BundledPhpExtensionRefusal;
use Php\Pie\DependencyResolver\DependencyResolver;
use Php\Pie\DependencyResolver\InvalidPackageName;
use Php\Pie\DependencyResolver\UnableToResolveRequirement;
use Php\Pie\Installing\InstallForPhpProject\FindMatchingPackages;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

use function sprintf;

#[AsCommand(
    name: 'download',
    description: 'Same behaviour as build, but puts the files in a local directory for manual building and installation.',
)]
final class DownloadCommand extends Command
{
    public function __construct(
        private readonly ContainerInterface $container,
        private readonly DependencyResolver $dependencyResolver,
        private readonly ComposerIntegrationHandler $composerIntegrationHandler,
        private readonly FindMatchingPackages $findMatchingPackages,
    ) {
        parent::__construct();
    }

    public function configure(): void
    {
        parent::configure();

        CommandHelper::configureDownloadBuildInstallOptions($this);
    }

    public function execute(InputInterface $input, OutputInterface $output): int
    {
        CommandHelper::validateInput($input, $this);

        $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $output);
        try {
            $requestedNameAndVersion = CommandHelper::requestedNameAndVersionPair($input);
        } catch (InvalidPackageName $invalidPackageName) {
            return CommandHelper::handlePackageNotFound(
                $invalidPackageName,
                $this->findMatchingPackages,
                $output,
                $targetPlatform,
                $this->container,
            );
        }

        $forceInstallPackageVersion = CommandHelper::determineForceInstallingPackageVersion($input);

        $composer = PieComposerFactory::createPieComposer(
            $this->container,
            new PieComposerRequest(
                $output,
                $targetPlatform,
                $requestedNameAndVersion,
                PieOperation::Download,
                [], // Configure options are not needed for download only
                null,
                false, // setting up INI not needed for download
            ),
        );

        try {
            $package = ($this->dependencyResolver)(
                $composer,
                $targetPlatform,
                $requestedNameAndVersion,
                $forceInstallPackageVersion,
            );
        } catch (UnableToResolveRequirement $unableToResolveRequirement) {
            return CommandHelper::handlePackageNotFound(
                $unableToResolveRequirement,
                $this->findMatchingPackages,
                $output,
                $targetPlatform,
                $this->container,
            );
        } catch (BundledPhpExtensionRefusal $bundledPhpExtensionRefusal) {
            $output->writeln('');
            $output->writeln('<comment>' . $bundledPhpExtensionRefusal->getMessage() . '</comment>');

            return self::INVALID;
        }

        $output->writeln(sprintf('<info>Found package:</info> %s which provides <info>%s</info>', $package->prettyNameAndVersion(), $package->extensionName()->nameWithExtPrefix()));

        try {
            $this->composerIntegrationHandler->runInstall(
                $package,
                $composer,
                $targetPlatform,
                $requestedNameAndVersion,
                $forceInstallPackageVersion,
                false,
            );
        } catch (ComposerRunFailed $composerRunFailed) {
            $output->writeln('<error>' . $composerRunFailed->getMessage() . '</error>');

            return $composerRunFailed->getCode();
        }

        return Command::SUCCESS;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Composer\Util\AuthHelper;
use Composer\Util\HttpDownloader;
use Php\Pie\ComposerIntegration\PieComposerFactory;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\ComposerIntegration\QuieterConsoleIO;
use Php\Pie\File\BinaryFile;
use Php\Pie\File\FullPathToSelf;
use Php\Pie\SelfManage\Update\ReleaseMetadata;
use Php\Pie\SelfManage\Verify\FailedToVerifyRelease;
use Php\Pie\SelfManage\Verify\VerifyPieReleaseUsingAttestation;
use Php\Pie\Util\Emoji;
use Php\Pie\Util\PieVersion;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

use function sprintf;

#[AsCommand(
    name: 'self-verify',
    description: 'Self verify PIE',
)]
final class SelfVerifyCommand extends Command
{
    /** @param non-empty-string $githubApiBaseUrl */
    public function __construct(
        private readonly string $githubApiBaseUrl,
        private readonly QuieterConsoleIO $io,
        private readonly ContainerInterface $container,
        private readonly FullPathToSelf $fullPathToSelf,
    ) {
        parent::__construct();
    }

    public function configure(): void
    {
        parent::configure();

        CommandHelper::configurePhpConfigOptions($this);
    }

    public function execute(InputInterface $input, OutputInterface $output): int
    {
        if (! PieVersion::isPharBuild()) {
            $output->writeln('<comment>Aborting! You are not running a PHAR, cannot self-verify.</comment>');

            return Command::FAILURE;
        }

        $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $output);
        $composer       = PieComposerFactory::createPieComposer(
            $this->container,
            PieComposerRequest::noOperation(
                $output,
                $targetPlatform,
            ),
        );
        $httpDownloader = new HttpDownloader($this->io, $composer->getConfig());
        $authHelper     = new AuthHelper($this->io, $composer->getConfig());
        $latestRelease  = new ReleaseMetadata(PieVersion::get(), 'blah');
        $pharFilename   = BinaryFile::fromFileWithSha256Checksum(($this->fullPathToSelf)());
        $verifyPiePhar  = VerifyPieReleaseUsingAttestation::factory($this->githubApiBaseUrl, $httpDownloader, $authHelper);

        try {
            $verifyPiePhar->verify($latestRelease, $pharFilename, $output);
        } catch (FailedToVerifyRelease $failedToVerifyRelease) {
            $output->writeln(sprintf(
                '<error>❌ Failed to verify the pie.phar release %s: %s</error>',
                $latestRelease->tag,
                $failedToVerifyRelease->getMessage(),
            ));

            return Command::FAILURE;
        }

        $output->writeln(sprintf(
            '<info>%s You are running an authentic PIE version %s.</info>',
            Emoji::GREEN_CHECKMARK,
            $latestRelease->tag,
        ));

        return Command::SUCCESS;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Php\Pie\ComposerIntegration\PieComposerFactory;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\ComposerIntegration\PieInstalledJsonMetadataKeys;
use Php\Pie\DependencyResolver\RequestedPackageAndVersion;
use Php\Pie\DependencyResolver\ResolveDependencyWithComposer;
use Php\Pie\DependencyResolver\UnableToResolveRequirement;
use Php\Pie\File\BinaryFile;
use Php\Pie\File\BinaryFileFailedVerification;
use Php\Pie\Platform as PiePlatform;
use Php\Pie\Platform\InstalledPiePackages;
use Php\Pie\Platform\OperatingSystem;
use Php\Pie\Util\Emoji;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Webmozart\Assert\Assert;

use function array_diff;
use function array_key_exists;
use function array_keys;
use function array_walk;
use function count;
use function file_exists;
use function sprintf;
use function substr;

use const DIRECTORY_SEPARATOR;

/** @psalm-import-type PieMetadata from PieInstalledJsonMetadataKeys */
#[AsCommand(
    name: 'show',
    description: 'List the installed modules and their versions.',
)]
final class ShowCommand extends Command
{
    private const OPTION_ALL = 'all';

    public function __construct(
        private readonly InstalledPiePackages $installedPiePackages,
        private readonly ContainerInterface $container,
        private readonly ResolveDependencyWithComposer $resolveDependencyWithComposer,
    ) {
        parent::__construct();
    }

    public function configure(): void
    {
        parent::configure();

        CommandHelper::configurePhpConfigOptions($this);

        $this->addOption(
            self::OPTION_ALL,
            null,
            InputOption::VALUE_NONE,
            'Show all extensions for the target PHP installation, even those PIE does not manage.',
        );
    }

    public function execute(InputInterface $input, OutputInterface $output): int
    {
        $showAll        = $input->hasOption(self::OPTION_ALL) && $input->getOption(self::OPTION_ALL);
        $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $output);

        if ($output->getVerbosity() < OutputInterface::VERBOSITY_VERBOSE) {
            $output->writeln(
                sprintf(
                    '<info>Using pie.json:</info> %s',
                    PiePlatform::getPieJsonFilename($targetPlatform),
                ),
            );
        }

        if (! $showAll) {
            $output->writeln('Tip: to include extensions in this list that PIE does not manage, use the --all flag.');
        }

        $composer = PieComposerFactory::createPieComposer(
            $this->container,
            PieComposerRequest::noOperation(
                new NullOutput(),
                $targetPlatform,
            ),
        );

        $piePackages          = $this->installedPiePackages->allPiePackages($composer);
        $phpEnabledExtensions = $targetPlatform->phpBinaryPath->extensions();
        $extensionPath        = $targetPlatform->phpBinaryPath->extensionPath();
        $extensionEnding      = $targetPlatform->operatingSystem === OperatingSystem::Windows ? '.dll' : '.so';
        $piePackagesMatched   = [];
        $rootPackageRequires  = $composer->getPackage()->getRequires();

        $output->writeln(sprintf(
            "\n" . '<options=bold,underscore>%s:</>',
            $showAll ? 'All loaded extensions' : 'Loaded PIE extensions',
        ));
        array_walk(
            $phpEnabledExtensions,
            function (string $version, string $phpExtensionName) use ($composer, $rootPackageRequires, $targetPlatform, $showAll, $output, $piePackages, $extensionPath, $extensionEnding, &$piePackagesMatched): void {
                if (! array_key_exists($phpExtensionName, $piePackages)) {
                    if ($showAll) {
                        $output->writeln(sprintf('  <comment>%s:%s</comment>', $phpExtensionName, $version));
                    }

                    return;
                }

                $piePackage           = $piePackages[$phpExtensionName];
                $piePackagesMatched[] = $phpExtensionName;
                $packageName          = $piePackage->name();
                $packageRequirement   = $rootPackageRequires[$piePackage->name()]->getPrettyConstraint();

                try {
                    Assert::stringNotEmpty($packageName);
                    Assert::stringNotEmpty($packageRequirement);

                    $latestPackage = ($this->resolveDependencyWithComposer)(
                        $composer,
                        $targetPlatform,
                        new RequestedPackageAndVersion($packageName, $packageRequirement),
                        false,
                    );
                } catch (UnableToResolveRequirement) {
                    $latestPackage = null;
                }

                $updateNotice = '';
                if ($latestPackage !== null && $latestPackage->version() !== $piePackage->version()) {
                    $updateNotice = sprintf(
                        ' — new version %s available',
                        $latestPackage->version(),
                    );
                }

                $output->writeln(sprintf(
                    '  <info>%s:%s</info> (from 🥧 <info>%s</info>%s)%s',
                    $phpExtensionName,
                    $version,
                    $piePackage->prettyNameAndVersion(),
                    self::verifyChecksumInformation(
                        $extensionPath,
                        $phpExtensionName,
                        $extensionEnding,
                        PieInstalledJsonMetadataKeys::pieMetadataFromComposerPackage($piePackage->composerPackage()),
                    ),
                    $updateNotice,
                ));
            },
        );

        if (! $showAll && ! count($piePackagesMatched)) {
            $output->writeln('(none)');
        }

        $unmatchedPiePackages = array_diff(array_keys($piePackages), $piePackagesMatched);

        if (count($unmatchedPiePackages)) {
            $output->writeln(sprintf(
                '%s %s <options=bold,underscore>PIE packages not loaded:</>',
                "\n",
                Emoji::WARNING,
            ));
            $output->writeln('These extensions were installed with PIE but are not currently enabled.' . "\n");

            foreach ($unmatchedPiePackages as $unmatchedPiePackage) {
                $output->writeln(sprintf(' - %s', $piePackages[$unmatchedPiePackage]->prettyNameAndVersion()));
            }
        }

        return Command::SUCCESS;
    }

    /**
     * @param PieMetadata $installedJsonMetadata
     * @psalm-param '.dll'|'.so' $extensionEnding
     */
    private static function verifyChecksumInformation(
        string $extensionPath,
        string $phpExtensionName,
        string $extensionEnding,
        array $installedJsonMetadata,
    ): string {
        $actualBinaryPathByConvention = $extensionPath . DIRECTORY_SEPARATOR . $phpExtensionName . $extensionEnding;

        // The extension may not be in the usual path (since you can specify a full path to an extension in the INI file)
        if (! file_exists($actualBinaryPathByConvention)) {
            return '';
        }

        $pieExpectedBinaryPath = array_key_exists(PieInstalledJsonMetadataKeys::InstalledBinary->value, $installedJsonMetadata) ? $installedJsonMetadata[PieInstalledJsonMetadataKeys::InstalledBinary->value] : null;
        $pieExpectedChecksum   = array_key_exists(PieInstalledJsonMetadataKeys::BinaryChecksum->value, $installedJsonMetadata) ? $installedJsonMetadata[PieInstalledJsonMetadataKeys::BinaryChecksum->value] : null;

        // Some other kind of mismatch of file path, or we don't have a stored checksum available
        if (
            $pieExpectedBinaryPath === null
            || $pieExpectedChecksum === null
            || $pieExpectedBinaryPath !== $actualBinaryPathByConvention
        ) {
            return '';
        }

        $expectedBinaryFileFromMetadata = new BinaryFile($pieExpectedBinaryPath, $pieExpectedChecksum);
        $actualBinaryFile               = BinaryFile::fromFileWithSha256Checksum($actualBinaryPathByConvention);

        try {
            $expectedBinaryFileFromMetadata->verifyAgainstOther($actualBinaryFile);
        } catch (BinaryFileFailedVerification) {
            return sprintf(
                ' %s was %s..., expected %s...',
                Emoji::WARNING,
                substr($actualBinaryFile->checksum, 0, 8),
                substr($expectedBinaryFileFromMetadata->checksum, 0, 8),
            );
        }

        return ' ' . Emoji::GREEN_CHECKMARK;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Composer\Composer;
use Php\Pie\ComposerIntegration\ComposerIntegrationHandler;
use Php\Pie\ComposerIntegration\PieComposerFactory;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\ComposerIntegration\PieOperation;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\DependencyResolver\RequestedPackageAndVersion;
use Php\Pie\Platform\InstalledPiePackages;
use Php\Pie\Platform\TargetPlatform;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Webmozart\Assert\Assert;

#[AsCommand(
    name: 'uninstall',
    description: 'Disable and remove an extension that has been installed with PIE',
)]
final class UninstallCommand extends Command
{
    private const ARG_PACKAGE_NAME = 'package-name';

    public function __construct(
        private readonly InstalledPiePackages $installedPiePackages,
        private readonly ContainerInterface $container,
        private readonly ComposerIntegrationHandler $composerIntegrationHandler,
    ) {
        parent::__construct();
    }

    public function configure(): void
    {
        parent::configure();

        $this->addArgument(
            self::ARG_PACKAGE_NAME,
            InputArgument::REQUIRED,
            'The package name to remove, in the format {vendor/package}, for example `xdebug/xdebug`',
        );

        CommandHelper::configurePhpConfigOptions($this);
    }

    public function execute(InputInterface $input, OutputInterface $output): int
    {
        if (! TargetPlatform::isRunningAsRoot()) {
            $output->writeln('This command may need elevated privileges, and may prompt you for your password.');
        }

        $packageToRemove = (string) $input->getArgument(self::ARG_PACKAGE_NAME);
        Assert::stringNotEmpty($packageToRemove);
        $requestedPackageAndVersionToRemove = new RequestedPackageAndVersion($packageToRemove, null);

        $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $output);

        $composer = PieComposerFactory::createPieComposer(
            $this->container,
            PieComposerRequest::noOperation(
                new NullOutput(),
                $targetPlatform,
            ),
        );

        $piePackage = $this->findPiePackageByPackageName($packageToRemove, $composer);

        if ($piePackage === null) {
            $output->writeln('<error>No package found: ' . $packageToRemove . '</error>');

            return 1;
        }

        $composer = PieComposerFactory::createPieComposer(
            $this->container,
            new PieComposerRequest(
                $output,
                $targetPlatform,
                $requestedPackageAndVersionToRemove,
                PieOperation::Uninstall,
                [], // Configure options are not needed for uninstall
                null,
                true,
            ),
        );

        $this->composerIntegrationHandler->runUninstall(
            $piePackage,
            $composer,
            $targetPlatform,
            $requestedPackageAndVersionToRemove,
        );

        return 0;
    }

    private function findPiePackageByPackageName(string $packageToRemove, Composer $composer): Package|null
    {
        $piePackages = $this->installedPiePackages->allPiePackages($composer);

        foreach ($piePackages as $piePackage) {
            if ($piePackage->name() === $packageToRemove) {
                return $piePackage;
            }
        }

        return null;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Php\Pie\ComposerIntegration\ComposerIntegrationHandler;
use Php\Pie\ComposerIntegration\ComposerRunFailed;
use Php\Pie\ComposerIntegration\PieComposerFactory;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\ComposerIntegration\PieOperation;
use Php\Pie\DependencyResolver\BundledPhpExtensionRefusal;
use Php\Pie\DependencyResolver\DependencyResolver;
use Php\Pie\DependencyResolver\InvalidPackageName;
use Php\Pie\DependencyResolver\UnableToResolveRequirement;
use Php\Pie\Installing\InstallForPhpProject\FindMatchingPackages;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

use function sprintf;

#[AsCommand(
    name: 'build',
    description: 'Download and build a PIE-compatible PHP extension, without installing it.',
)]
final class BuildCommand extends Command
{
    public function __construct(
        private readonly ContainerInterface $container,
        private readonly DependencyResolver $dependencyResolver,
        private readonly ComposerIntegrationHandler $composerIntegrationHandler,
        private readonly FindMatchingPackages $findMatchingPackages,
    ) {
        parent::__construct();
    }

    public function configure(): void
    {
        parent::configure();

        CommandHelper::configureDownloadBuildInstallOptions($this);
    }

    public function execute(InputInterface $input, OutputInterface $output): int
    {
        $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $output);
        try {
            $requestedNameAndVersion = CommandHelper::requestedNameAndVersionPair($input);
        } catch (InvalidPackageName $invalidPackageName) {
            return CommandHelper::handlePackageNotFound(
                $invalidPackageName,
                $this->findMatchingPackages,
                $output,
                $targetPlatform,
                $this->container,
            );
        }

        $forceInstallPackageVersion = CommandHelper::determineForceInstallingPackageVersion($input);

        $composer = PieComposerFactory::createPieComposer(
            $this->container,
            new PieComposerRequest(
                $output,
                $targetPlatform,
                $requestedNameAndVersion,
                PieOperation::Resolve,
                [], // Configure options are not needed for resolve only
                null,
                false, // setting up INI not needed for build
            ),
        );

        try {
            $package = ($this->dependencyResolver)(
                $composer,
                $targetPlatform,
                $requestedNameAndVersion,
                $forceInstallPackageVersion,
            );
        } catch (UnableToResolveRequirement $unableToResolveRequirement) {
            return CommandHelper::handlePackageNotFound(
                $unableToResolveRequirement,
                $this->findMatchingPackages,
                $output,
                $targetPlatform,
                $this->container,
            );
        } catch (BundledPhpExtensionRefusal $bundledPhpExtensionRefusal) {
            $output->writeln('');
            $output->writeln('<comment>' . $bundledPhpExtensionRefusal->getMessage() . '</comment>');

            return self::INVALID;
        }

        $output->writeln(sprintf('<info>Found package:</info> %s which provides <info>%s</info>', $package->prettyNameAndVersion(), $package->extensionName()->nameWithExtPrefix()));

        // Now we know what package we have, we can validate the configure options for the command and re-create the
        // Composer instance with the populated configure options
        CommandHelper::bindConfigureOptionsFromPackage($this, $package, $input);
        $configureOptionsValues = CommandHelper::processConfigureOptionsFromInput($package, $input);

        $composer = PieComposerFactory::createPieComposer(
            $this->container,
            new PieComposerRequest(
                $output,
                $targetPlatform,
                $requestedNameAndVersion,
                PieOperation::Build,
                $configureOptionsValues,
                CommandHelper::determinePhpizePathFromInputs($input),
                false, // setting up INI not needed for build
            ),
        );

        try {
            $this->composerIntegrationHandler->runInstall(
                $package,
                $composer,
                $targetPlatform,
                $requestedNameAndVersion,
                $forceInstallPackageVersion,
                false,
            );
        } catch (ComposerRunFailed $composerRunFailed) {
            $output->writeln('<error>' . $composerRunFailed->getMessage() . '</error>');

            return $composerRunFailed->getCode();
        }

        return Command::SUCCESS;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Php\Pie\ComposerIntegration\PieComposerFactory;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand(
    name: 'repository:list',
    description: 'List the package repositories that PIE uses.',
)]
final class RepositoryListCommand extends Command
{
    public function __construct(
        private readonly ContainerInterface $container,
    ) {
        parent::__construct();
    }

    public function configure(): void
    {
        parent::configure();

        CommandHelper::configurePhpConfigOptions($this);
    }

    public function execute(InputInterface $input, OutputInterface $output): int
    {
        CommandHelper::listRepositories(
            PieComposerFactory::createPieComposer(
                $this->container,
                PieComposerRequest::noOperation(
                    $output,
                    CommandHelper::determineTargetPlatformFromInputs($input, $output),
                ),
            ),
            $output,
        );

        return 0;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Composer\Package\Link;
use Composer\Package\Version\VersionParser;
use OutOfRangeException;
use Php\Pie\ComposerIntegration\PieComposerFactory;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\ComposerIntegration\PieJsonEditor;
use Php\Pie\ExtensionName;
use Php\Pie\ExtensionType;
use Php\Pie\Installing\InstallForPhpProject\ComposerFactoryForProject;
use Php\Pie\Installing\InstallForPhpProject\DetermineExtensionsRequired;
use Php\Pie\Installing\InstallForPhpProject\FindMatchingPackages;
use Php\Pie\Installing\InstallForPhpProject\InstallPiePackageFromPath;
use Php\Pie\Installing\InstallForPhpProject\InstallSelectedPackage;
use Php\Pie\Platform\InstalledPiePackages;
use Php\Pie\Util\Emoji;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Throwable;

use function array_keys;
use function array_map;
use function array_merge;
use function array_walk;
use function assert;
use function chdir;
use function getcwd;
use function in_array;
use function is_dir;
use function is_string;
use function realpath;
use function sprintf;
use function strpos;
use function substr;

use const PHP_EOL;

#[AsCommand(
    name: 'install-extensions-for-project',
    description: 'Check a project for its extension dependencies, and offers to install them',
)]
final class InstallExtensionsForProjectCommand extends Command
{
    public function __construct(
        private readonly ComposerFactoryForProject $composerFactoryForProject,
        private readonly DetermineExtensionsRequired $determineExtensionsRequired,
        private readonly InstalledPiePackages $installedPiePackages,
        private readonly FindMatchingPackages $findMatchingPackages,
        private readonly InstallSelectedPackage $installSelectedPackage,
        private readonly InstallPiePackageFromPath $installPiePackageFromPath,
        private readonly ContainerInterface $container,
    ) {
        parent::__construct();
    }

    public function configure(): void
    {
        parent::configure();

        CommandHelper::configureDownloadBuildInstallOptions($this, false);
    }

    public function execute(InputInterface $input, OutputInterface $output): int
    {
        $helper = $this->getHelper('question');
        assert($helper instanceof QuestionHelper);

        $workingDirOption  = (string) $input->getOption(CommandHelper::OPTION_WORKING_DIRECTORY);
        $restoreWorkingDir = static function (): void {
        };
        if ($workingDirOption !== '' && is_dir($workingDirOption)) {
            $currentWorkingDir = getcwd();
            $restoreWorkingDir = static function () use ($currentWorkingDir, $output): void {
                chdir($currentWorkingDir);
                $output->writeln(
                    sprintf('Restored working directory to: %s', $currentWorkingDir),
                    OutputInterface::VERBOSITY_VERBOSE,
                );
            };

            chdir($workingDirOption);
            $output->writeln(
                sprintf('Changed working directory to: %s', $workingDirOption),
                OutputInterface::VERBOSITY_VERBOSE,
            );
        }

        $rootPackage = $this->composerFactoryForProject->rootPackage($input, $output);

        if (ExtensionType::isValid($rootPackage->getType())) {
            $cwd = realpath(getcwd());
            if (! is_string($cwd) || $cwd === '') {
                $output->writeln('<error>Failed to determine current working directory.</error>');

                $restoreWorkingDir();

                return Command::FAILURE;
            }

            $exit = ($this->installPiePackageFromPath)(
                $this,
                $cwd,
                $rootPackage,
                PieJsonEditor::fromTargetPlatform(CommandHelper::determineTargetPlatformFromInputs($input, new NullOutput())),
                $input,
                $output,
            );

            $restoreWorkingDir();

            return $exit;
        }

        $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $output);

        $output->writeln(sprintf(
            'Checking extensions for your project <info>%s</info> (path: %s)',
            $rootPackage->getPrettyName(),
            getcwd(),
        ));

        $extensionsRequired = $this->determineExtensionsRequired->forProject($this->composerFactoryForProject->composer($input, $output));

        $pieComposer = PieComposerFactory::createPieComposer(
            $this->container,
            PieComposerRequest::noOperation(
                new NullOutput(),
                $targetPlatform,
            ),
        );

        $phpEnabledExtensions = array_keys($targetPlatform->phpBinaryPath->extensions());
        $installedPiePackages = $this->installedPiePackages->allPiePackages($pieComposer);

        $anyErrorsHappened = false;

        array_walk(
            $extensionsRequired,
            function (Link $link) use ($pieComposer, $phpEnabledExtensions, $installedPiePackages, $input, $output, $helper, &$anyErrorsHappened): void {
                $extension              = ExtensionName::normaliseFromString($link->getTarget());
                $linkRequiresConstraint = $link->getPrettyConstraint();

                $piePackageVersion = null;
                if (in_array($extension->name(), array_keys($installedPiePackages))) {
                    $piePackageVersion = $installedPiePackages[$extension->name()]->version();
                }

                $piePackageVersionMatchesLinkConstraint = null;
                if ($piePackageVersion !== null) {
                    $piePackageVersionMatchesLinkConstraint = $link
                        ->getConstraint()
                        ->matches(
                            (new VersionParser())->parseConstraints($piePackageVersion),
                        );
                }

                if (in_array($extension->name(), $phpEnabledExtensions)) {
                    if ($piePackageVersion !== null && $piePackageVersionMatchesLinkConstraint === false) {
                        $output->writeln(sprintf(
                            '%s: <comment>%s:%s</comment> %s Version %s is installed, but does not meet the version requirement %s',
                            $link->getDescription(),
                            $link->getTarget(),
                            $linkRequiresConstraint,
                            Emoji::WARNING,
                            $piePackageVersion,
                            $link->getConstraint()->getPrettyString(),
                        ));

                        return;
                    }

                    $output->writeln(sprintf(
                        '%s: <info>%s:%s</info> %s Already installed',
                        $link->getDescription(),
                        $link->getTarget(),
                        $linkRequiresConstraint,
                        Emoji::GREEN_CHECKMARK,
                    ));

                    return;
                }

                $output->writeln(sprintf(
                    '%s: <comment>%s:%s</comment> %s Missing',
                    $link->getDescription(),
                    $link->getTarget(),
                    $linkRequiresConstraint,
                    Emoji::PROHIBITED,
                ));

                try {
                    $matches = $this->findMatchingPackages->for($pieComposer, $extension->name());
                } catch (OutOfRangeException) {
                    $anyErrorsHappened = true;

                    $message = sprintf(
                        '<error>No packages were found for %s</error>',
                        $extension->nameWithExtPrefix(),
                    );

                    if ($output instanceof ConsoleOutputInterface) {
                        $output->getErrorOutput()->writeln($message);

                        return;
                    }

                    $output->writeln($message);

                    return;
                }

                $choiceQuestion = new ChoiceQuestion(
                    "\nThe following packages may be suitable, which would you like to install: ",
                    array_merge(
                        ['None'],
                        array_map(
                            static function (array $match): string {
                                return sprintf('%s: %s', $match['name'], $match['description'] ?? 'no description available');
                            },
                            $matches,
                        ),
                    ),
                    0,
                );

                $selectedPackageAnswer = (string) $helper->ask($input, $output, $choiceQuestion);

                if ($selectedPackageAnswer === 'None') {
                    $output->writeln('Okay I won\'t install anything for ' . $extension->name());

                    return;
                }

                $requestInstallConstraint = '';
                if ($linkRequiresConstraint !== '*') {
                    $requestInstallConstraint = ':' . $linkRequiresConstraint;
                }

                try {
                    $this->installSelectedPackage->withPieCli(
                        substr($selectedPackageAnswer, 0, (int) strpos($selectedPackageAnswer, ':')) . $requestInstallConstraint,
                        $input,
                        $output,
                    );
                } catch (Throwable $t) {
                    $anyErrorsHappened = true;

                    $message = '<error>' . $t->getMessage() . '</error>';

                    if ($output instanceof ConsoleOutputInterface) {
                        $output->getErrorOutput()->writeln($message);

                        return;
                    }

                    $output->writeln($message);
                }
            },
        );

        $output->writeln(PHP_EOL . 'Finished checking extensions.');

        $restoreWorkingDir();

        /**
         * @psalm-suppress TypeDoesNotContainType
         * @psalm-suppress RedundantCondition
         */
        return $anyErrorsHappened ? self::FAILURE : self::SUCCESS;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Php\Pie\ComposerIntegration\ComposerIntegrationHandler;
use Php\Pie\ComposerIntegration\ComposerRunFailed;
use Php\Pie\ComposerIntegration\PieComposerFactory;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\ComposerIntegration\PieOperation;
use Php\Pie\DependencyResolver\BundledPhpExtensionRefusal;
use Php\Pie\DependencyResolver\DependencyResolver;
use Php\Pie\DependencyResolver\InvalidPackageName;
use Php\Pie\DependencyResolver\UnableToResolveRequirement;
use Php\Pie\Installing\InstallForPhpProject\FindMatchingPackages;
use Php\Pie\Platform\TargetPlatform;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

use function sprintf;

#[AsCommand(
    name: 'install',
    description: 'Download, build, and install a PIE-compatible PHP extension.',
)]
final class InstallCommand extends Command
{
    public function __construct(
        private readonly ContainerInterface $container,
        private readonly DependencyResolver $dependencyResolver,
        private readonly ComposerIntegrationHandler $composerIntegrationHandler,
        private readonly InvokeSubCommand $invokeSubCommand,
        private readonly FindMatchingPackages $findMatchingPackages,
    ) {
        parent::__construct();
    }

    public function configure(): void
    {
        parent::configure();

        CommandHelper::configureDownloadBuildInstallOptions($this);
    }

    public function execute(InputInterface $input, OutputInterface $output): int
    {
        if (! $input->getArgument(CommandHelper::ARG_REQUESTED_PACKAGE_AND_VERSION)) {
            return ($this->invokeSubCommand)(
                $this,
                ['command' => 'install-extensions-for-project'],
                $input,
                $output,
            );
        }

        if (! TargetPlatform::isRunningAsRoot()) {
            $output->writeln('This command may need elevated privileges, and may prompt you for your password.');
        }

        $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $output);
        try {
            $requestedNameAndVersion = CommandHelper::requestedNameAndVersionPair($input);
        } catch (InvalidPackageName $invalidPackageName) {
            return CommandHelper::handlePackageNotFound(
                $invalidPackageName,
                $this->findMatchingPackages,
                $output,
                $targetPlatform,
                $this->container,
            );
        }

        $forceInstallPackageVersion = CommandHelper::determineForceInstallingPackageVersion($input);

        $composer = PieComposerFactory::createPieComposer(
            $this->container,
            new PieComposerRequest(
                $output,
                $targetPlatform,
                $requestedNameAndVersion,
                PieOperation::Resolve,
                [], // Configure options are not needed for resolve only
                null,
                false, // setting up INI not needed for resolve step
            ),
        );

        try {
            $package = ($this->dependencyResolver)(
                $composer,
                $targetPlatform,
                $requestedNameAndVersion,
                $forceInstallPackageVersion,
            );
        } catch (UnableToResolveRequirement $unableToResolveRequirement) {
            return CommandHelper::handlePackageNotFound(
                $unableToResolveRequirement,
                $this->findMatchingPackages,
                $output,
                $targetPlatform,
                $this->container,
            );
        } catch (BundledPhpExtensionRefusal $bundledPhpExtensionRefusal) {
            $output->writeln('');
            $output->writeln('<comment>' . $bundledPhpExtensionRefusal->getMessage() . '</comment>');

            return self::INVALID;
        }

        $output->writeln(sprintf('<info>Found package:</info> %s which provides <info>%s</info>', $package->prettyNameAndVersion(), $package->extensionName()->nameWithExtPrefix()));

        // Now we know what package we have, we can validate the configure options for the command and re-create the
        // Composer instance with the populated configure options
        CommandHelper::bindConfigureOptionsFromPackage($this, $package, $input);
        $configureOptionsValues = CommandHelper::processConfigureOptionsFromInput($package, $input);

        $composer = PieComposerFactory::createPieComposer(
            $this->container,
            new PieComposerRequest(
                $output,
                $targetPlatform,
                $requestedNameAndVersion,
                PieOperation::Install,
                $configureOptionsValues,
                CommandHelper::determinePhpizePathFromInputs($input),
                CommandHelper::determineAttemptToSetupIniFile($input),
            ),
        );

        try {
            $this->composerIntegrationHandler->runInstall(
                $package,
                $composer,
                $targetPlatform,
                $requestedNameAndVersion,
                $forceInstallPackageVersion,
                true,
            );
        } catch (ComposerRunFailed $composerRunFailed) {
            $output->writeln('<error>' . $composerRunFailed->getMessage() . '</error>');

            return $composerRunFailed->getCode();
        }

        return Command::SUCCESS;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Php\Pie\ComposerIntegration\PieComposerFactory;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\ComposerIntegration\PieJsonEditor;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Webmozart\Assert\Assert;

use function str_contains;

#[AsCommand(
    name: 'repository:remove',
    description: 'Remove a repository for packages that PIE can use.',
)]
final class RepositoryRemoveCommand extends Command
{
    private const ARG_URL = 'url';

    public function __construct(
        private readonly ContainerInterface $container,
    ) {
        parent::__construct();
    }

    public function configure(): void
    {
        parent::configure();

        CommandHelper::configurePhpConfigOptions($this);

        $this->addArgument(
            self::ARG_URL,
            InputArgument::REQUIRED,
            'Specify the URL of the repository, e.g. a Github/Gitlab URL, or a filesystem path',
        );
    }

    public function execute(InputInterface $input, OutputInterface $output): int
    {
        $targetPlatform = CommandHelper::determineTargetPlatformFromInputs($input, $output);
        $pieJsonEditor  = PieJsonEditor::fromTargetPlatform($targetPlatform);

        $url = (string) $input->getArgument(self::ARG_URL);
        Assert::stringNotEmpty($url);

        if (str_contains($url, 'packagist.org')) {
            // "removing packagist" is really just adding an exclusion
            $pieJsonEditor
                ->ensureExists()
                ->excludePackagistOrg();
        } else {
            $pieJsonEditor
                ->ensureExists()
                ->removeRepository($url);
        }

        CommandHelper::listRepositories(
            PieComposerFactory::createPieComposer(
                $this->container,
                PieComposerRequest::noOperation(
                    $output,
                    $targetPlatform,
                ),
            ),
            $output,
        );

        return 0;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Webmozart\Assert\Assert;

use function array_combine;
use function array_filter;
use function array_keys;
use function array_map;
use function array_merge;
use function array_values;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class InvokeSubCommand
{
    /** @param array<array-key, mixed> $subCommandInput */
    public function __invoke(
        Command $command,
        array $subCommandInput,
        InputInterface $originalCommandInput,
        OutputInterface $output,
    ): int {
        $originalSuppliedOptions = array_filter($originalCommandInput->getOptions());
        $installForProjectInput  = new ArrayInput(array_merge(
            $subCommandInput,
            array_combine(
                array_map(static fn ($key) => '--' . $key, array_keys($originalSuppliedOptions)),
                array_values($originalSuppliedOptions),
            ),
        ));

        $application = $command->getApplication();
        Assert::notNull($application);

        return $application->doRun($installForProjectInput, $output);
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\Ini;

use Php\Pie\DependencyResolver\Package;
use Php\Pie\ExtensionType;
use Php\Pie\File\Sudo;
use Php\Pie\File\SudoFilePut;
use Php\Pie\Platform\TargetPhp\PhpBinaryPath;
use Symfony\Component\Console\Output\OutputInterface;
use Throwable;

use function file_get_contents;
use function is_readable;
use function is_string;
use function is_writable;
use function sprintf;

use const PHP_EOL;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class AddExtensionToTheIniFile
{
    /** @param callable():bool|null $additionalEnableStep */
    public function __invoke(
        string $ini,
        Package $package,
        PhpBinaryPath $phpBinaryPath,
        OutputInterface $output,
        callable|null $additionalEnableStep,
    ): bool {
        if (! is_writable($ini) && ! Sudo::exists()) {
            $output->writeln(
                sprintf(
                    'PHP is configured to use %s, but it is not writable by PIE.',
                    $ini,
                ),
                OutputInterface::VERBOSITY_VERBOSE,
            );

            return false;
        }

        if (! is_readable($ini)) {
            $output->writeln(
                sprintf(
                    'Could not read %s to make a backup of it, aborting enablement of extension',
                    $ini,
                ),
                OutputInterface::VERBOSITY_VERBOSE,
            );

            return false;
        }

        $originalIniContent = file_get_contents($ini);

        if (! is_string($originalIniContent)) {
            $output->writeln(
                sprintf(
                    'Tried making a backup of %s but could not read it, aborting enablement of extension',
                    $ini,
                ),
                OutputInterface::VERBOSITY_VERBOSE,
            );

            return false;
        }

        try {
            SudoFilePut::contents(
                $ini,
                $originalIniContent . $this->iniFileContent($package),
            );
            $output->writeln(
                sprintf(
                    'Enabled extension %s in the INI file %s',
                    $package->extensionName()->name(),
                    $ini,
                ),
                OutputInterface::VERBOSITY_VERBOSE,
            );

            if ($additionalEnableStep !== null && ! $additionalEnableStep()) {
                return false;
            }

            $phpBinaryPath->assertExtensionIsLoadedInRuntime($package->extensionName(), $output);

            return true;
        } catch (Throwable $anything) {
            SudoFilePut::contents($ini, $originalIniContent);

            $output->writeln(sprintf(
                '<error>Something went wrong enabling the %s extension: %s</error>',
                $package->extensionName()->name(),
                $anything->getMessage(),
            ));

            return false;
        }
    }

    /** @return non-empty-string */
    private function iniFileContent(Package $package): string
    {
        return PHP_EOL
            . '; PIE automatically added this to enable the ' . $package->name() . ' extension' . PHP_EOL
            . '; priority=' . $package->priority() . PHP_EOL
            . ($package->extensionType() === ExtensionType::PhpModule ? 'extension' : 'zend_extension')
            . '='
            . $package->extensionName()->name() . PHP_EOL;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\Ini;

use Php\Pie\DependencyResolver\Package;
use Php\Pie\Platform\TargetPlatform;
use Symfony\Component\Console\Output\OutputInterface;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
interface RemoveIniEntry
{
    /** @return list<string> Returns a list of INI files that were updated to remove the extension */
    public function __invoke(Package $package, TargetPlatform $targetPlatform, OutputInterface $output): array;
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\Ini;

use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\File\BinaryFile;
use Php\Pie\Platform\TargetPlatform;
use Symfony\Component\Console\Output\OutputInterface;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
interface SetupIniApproach
{
    /**
     * Preliminary indication on whether this approach can be used on the
     * target platform (returns `true`) or not (returns `false`).
     */
    public function canBeUsed(TargetPlatform $targetPlatform): bool;

    /**
     * Should return true if the INI approach successfully set up the extension,
     * or false otherwise.
     */
    public function setup(
        TargetPlatform $targetPlatform,
        DownloadedPackage $downloadedPackage,
        BinaryFile $binaryFile,
        OutputInterface $output,
    ): bool;
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\Ini;

use Php\Pie\ExtensionName;

use function array_key_exists;
use function array_merge;
use function file;
use function in_array;
use function is_string;
use function parse_ini_string;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class IsExtensionAlreadyInTheIniFile
{
    public function __invoke(string $iniFilePath, ExtensionName $extensionName): bool
    {
        $loadedExts = $this->readIniFile($iniFilePath);

        return in_array($extensionName->name(), array_merge($loadedExts['extensions'], $loadedExts['zend_extensions']));
    }

    /** @return array{extensions: list<non-empty-string>, zend_extensions: list<non-empty-string>} */
    private function readIniFile(string $iniFilePath): array
    {
        $iniFileContentLines = file($iniFilePath);

        $extensions     = [];
        $zendExtensions = [];
        foreach ($iniFileContentLines as $line) {
            $lineIni = parse_ini_string($line);

            if (array_key_exists('extension', $lineIni) && is_string($lineIni['extension']) && $lineIni['extension'] !== '') {
                $extensions[] = $lineIni['extension'];
            }

            if (! array_key_exists('zend_extension', $lineIni) || ! is_string($lineIni['zend_extension']) || $lineIni['zend_extension'] === '') {
                continue;
            }

            $zendExtensions[] = $lineIni['zend_extension'];
        }

        return [
            'extensions' => $extensions,
            'zend_extensions' => $zendExtensions,
        ];
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\Ini;

use Composer\Util\Platform;
use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\File\BinaryFile;
use Php\Pie\File\Sudo;
use Php\Pie\File\SudoCreate;
use Php\Pie\File\SudoUnlink;
use Php\Pie\Platform\TargetPlatform;
use Php\Pie\Util\Process;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\Exception\ProcessFailedException;

use function array_unshift;
use function file_exists;
use function is_dir;
use function is_writable;
use function preg_match;
use function rtrim;
use function sprintf;

use const DIRECTORY_SEPARATOR;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class OndrejPhpenmod implements SetupIniApproach
{
    private const DEFAULT_PHPENMOD            = 'phpenmod';
    private const DEFAULT_MODS_AVAILABLE_PATH = '/etc/php/%s/mods-available';

    public function __construct(
        private readonly CheckAndAddExtensionToIniIfNeeded $checkAndAddExtensionToIniIfNeeded,
        private readonly string $phpenmod = self::DEFAULT_PHPENMOD,
        private readonly string $modsAvailablePath = self::DEFAULT_MODS_AVAILABLE_PATH,
    ) {
    }

    public function canBeUsed(TargetPlatform $targetPlatform): bool
    {
        return $this->phpenmodPath() !== null;
    }

    public function setup(
        TargetPlatform $targetPlatform,
        DownloadedPackage $downloadedPackage,
        BinaryFile $binaryFile,
        OutputInterface $output,
    ): bool {
        $phpenmodPath = $this->phpenmodPath();

        /** In practice, this shouldn't happen since {@see canBeUsed()} checks this */
        if ($phpenmodPath === null) {
            return false;
        }

        // the Ondrej repo uses an additional php.ini directory, if this isn't set, we may not actually be using Ondrej repo for this particular PHP install
        $additionalPhpIniPath = $targetPlatform->phpBinaryPath->additionalIniDirectory();

        if ($additionalPhpIniPath === null) {
            $output->writeln(
                'Additional INI file path was not set - may not be Ondrej PHP repo',
                OutputInterface::VERBOSITY_VERBOSE,
            );

            return false;
        }

        // Cursory check for the expected PHP INI directory; this is another indication we're using the Ondrej repo
        if (preg_match('#/etc/php/\d\.\d/[a-z-_]+/conf.d#', $additionalPhpIniPath) !== 1) {
            $output->writeln(
                sprintf(
                    'Warning: additional INI file path was not in the expected format (/etc/php/{version}/{sapi}/conf.d). Path was: %s',
                    $additionalPhpIniPath,
                ),
                OutputInterface::VERBOSITY_VERY_VERBOSE,
            );
        }

        $expectedModsAvailablePath = sprintf($this->modsAvailablePath, $targetPlatform->phpBinaryPath->majorMinorVersion());

        if (! file_exists($expectedModsAvailablePath)) {
            $output->writeln(
                sprintf(
                    'Mods available path %s does not exist',
                    $expectedModsAvailablePath,
                ),
                OutputInterface::VERBOSITY_VERBOSE,
            );

            return false;
        }

        if (! is_dir($expectedModsAvailablePath)) {
            $output->writeln(
                sprintf(
                    'Mods available path %s is not a directory',
                    $expectedModsAvailablePath,
                ),
                OutputInterface::VERBOSITY_VERBOSE,
            );

            return false;
        }

        $needSudo = false;
        if (! is_writable($expectedModsAvailablePath)) {
            if (! Sudo::exists()) {
                $output->writeln(
                    sprintf(
                        'Mods available path %s is not writable',
                        $expectedModsAvailablePath,
                    ),
                    OutputInterface::VERBOSITY_VERBOSE,
                );

                return false;
            }

            $needSudo = true;
        }

        $expectedIniFile = sprintf(
            '%s%s%s.ini',
            rtrim($expectedModsAvailablePath, DIRECTORY_SEPARATOR),
            DIRECTORY_SEPARATOR,
            $downloadedPackage->package->extensionName()->name(),
        );

        $pieCreatedTheIniFile = false;
        if (! file_exists($expectedIniFile)) {
            $output->writeln(
                sprintf(
                    'Creating new INI file based on extension priority: %s',
                    $expectedIniFile,
                ),
                OutputInterface::VERBOSITY_VERY_VERBOSE,
            );
            $pieCreatedTheIniFile = true;
            SudoCreate::file($expectedIniFile);
        }

        $addingExtensionWasSuccessful = ($this->checkAndAddExtensionToIniIfNeeded)(
            $expectedIniFile,
            $targetPlatform,
            $downloadedPackage,
            $output,
            static function () use ($needSudo, $phpenmodPath, $targetPlatform, $downloadedPackage, $output): bool {
                try {
                    $processArgs = [
                        $phpenmodPath,
                        '-v',
                        $targetPlatform->phpBinaryPath->majorMinorVersion(),
                        '-s',
                        'ALL',
                        $downloadedPackage->package->extensionName()->name(),
                    ];

                    if ($needSudo && Sudo::exists()) {
                        $output->writeln('Using sudo to elevate privileges for phpenmod', OutputInterface::VERBOSITY_VERBOSE);
                        array_unshift($processArgs, Sudo::find());
                    }

                    Process::run($processArgs);

                    return true;
                } catch (ProcessFailedException $processFailedException) {
                    $output->writeln(
                        sprintf(
                            'Failed to use %s to enable %s for PHP %s: %s',
                            $phpenmodPath,
                            $downloadedPackage->package->extensionName()->name(),
                            $targetPlatform->phpBinaryPath->majorMinorVersion(),
                            $processFailedException->getMessage(),
                        ),
                        OutputInterface::VERBOSITY_VERBOSE,
                    );

                    return false;
                }
            },
        );

        if (! $addingExtensionWasSuccessful && $pieCreatedTheIniFile) {
            SudoUnlink::singleFile($expectedIniFile);
        }

        return $addingExtensionWasSuccessful;
    }

    /** @return non-empty-string|null */
    private function phpenmodPath(): string|null
    {
        if (Platform::isWindows()) {
            return null;
        }

        try {
            $phpenmodPath = Process::run(['which', $this->phpenmod]);

            return $phpenmodPath !== '' ? $phpenmodPath : null;
        } catch (ProcessFailedException) {
            return null;
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\Ini;

use Php\Pie\DependencyResolver\Package;
use Php\Pie\ExtensionType;
use Php\Pie\File\FailedToWriteFile;
use Php\Pie\File\SudoFilePut;
use Php\Pie\Platform\TargetPlatform;
use Symfony\Component\Console\Output\OutputInterface;

use function array_filter;
use function array_map;
use function array_merge;
use function array_walk;
use function file_exists;
use function file_get_contents;
use function in_array;
use function preg_replace;
use function scandir;
use function sprintf;

use const DIRECTORY_SEPARATOR;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class RemoveIniEntryWithFileGetContents implements RemoveIniEntry
{
    /** @return list<string> Returns a list of INI files that were updated to remove the extension */
    public function __invoke(Package $package, TargetPlatform $targetPlatform, OutputInterface $output): array
    {
        $allIniFiles = [];

        $mainIni = $targetPlatform->phpBinaryPath->loadedIniConfigurationFile();
        if ($mainIni !== null) {
            $allIniFiles[] = $mainIni;
        }

        $additionalIniDirectory = $targetPlatform->phpBinaryPath->additionalIniDirectory();
        if ($additionalIniDirectory !== null) {
            $allIniFiles = array_merge(
                array_map(
                    static function (string $path) use ($additionalIniDirectory): string {
                        return $additionalIniDirectory . DIRECTORY_SEPARATOR . $path;
                    },
                    array_filter(
                        scandir($additionalIniDirectory),
                        static function (string $path) use ($additionalIniDirectory): bool {
                            if (in_array($path, ['.', '..'])) {
                                return false;
                            }

                            return file_exists($additionalIniDirectory . DIRECTORY_SEPARATOR . $path);
                        },
                    ),
                ),
                $allIniFiles,
            );
        }

        // Make sure all symlinks are resolved
        $allIniFiles = array_filter(array_map('realpath', $allIniFiles));

        $regex = sprintf(
            '/^(%s\s*=\s*%s)/m',
            $package->extensionType() === ExtensionType::PhpModule ? 'extension' : 'zend_extension',
            $package->extensionName()->name(),
        );

        $updatedIniFiles = [];
        array_walk(
            $allIniFiles,
            static function (string $iniFile) use (&$updatedIniFiles, $regex, $package, $output): void {
                $currentContent = file_get_contents($iniFile);

                if ($currentContent === false || $currentContent === '') {
                    return;
                }

                $replacedContent = preg_replace(
                    $regex,
                    '; $1 ; removed by PIE',
                    $currentContent,
                );

                if ($replacedContent === null || $replacedContent === $currentContent) {
                    return;
                }

                try {
                    SudoFilePut::contents($iniFile, $replacedContent);
                } catch (FailedToWriteFile) {
                    $output->writeln(sprintf(
                        '<error>Failed to remove extension "%s" from INI file "%s"</error>',
                        $package->extensionName()->name(),
                        $iniFile,
                    ));

                    return;
                }

                $updatedIniFiles[] = $iniFile;
            },
        );

        return $updatedIniFiles;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\Ini;

use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\File\BinaryFile;
use Php\Pie\Platform\TargetPhp\Exception\ExtensionIsNotLoaded;
use Php\Pie\Platform\TargetPlatform;
use Symfony\Component\Console\Output\OutputInterface;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class PreCheckExtensionAlreadyLoaded implements SetupIniApproach
{
    public function canBeUsed(TargetPlatform $targetPlatform): bool
    {
        return true;
    }

    public function setup(
        TargetPlatform $targetPlatform,
        DownloadedPackage $downloadedPackage,
        BinaryFile $binaryFile,
        OutputInterface $output,
    ): bool {
        try {
            $targetPlatform->phpBinaryPath->assertExtensionIsLoadedInRuntime(
                $downloadedPackage->package->extensionName(),
                $output,
            );

            return true;
        } catch (ExtensionIsNotLoaded) {
            return false;
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\Ini;

use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\File\BinaryFile;
use Php\Pie\Platform\TargetPlatform;
use ReflectionClass;
use Symfony\Component\Console\Output\OutputInterface;

use function array_filter;
use function array_values;
use function count;
use function sprintf;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class PickBestSetupIniApproach implements SetupIniApproach
{
    /** @var list<SetupIniApproach>|null */
    private array|null $memoizedApproachesThatCanBeUsed = null;

    /** @param list<SetupIniApproach> $possibleApproaches */
    public function __construct(
        private readonly array $possibleApproaches,
    ) {
    }

    /** @return list<SetupIniApproach> */
    private function approachesThatCanBeUsed(TargetPlatform $targetPlatform): array
    {
        if ($this->memoizedApproachesThatCanBeUsed === null) {
            $this->memoizedApproachesThatCanBeUsed = array_values(array_filter(
                $this->possibleApproaches,
                static fn (SetupIniApproach $approach) => $approach->canBeUsed($targetPlatform),
            ));
        }

        return $this->memoizedApproachesThatCanBeUsed;
    }

    public function canBeUsed(TargetPlatform $targetPlatform): bool
    {
        return count($this->approachesThatCanBeUsed($targetPlatform)) > 0;
    }

    public function setup(
        TargetPlatform $targetPlatform,
        DownloadedPackage $downloadedPackage,
        BinaryFile $binaryFile,
        OutputInterface $output,
    ): bool {
        $approaches = $this->approachesThatCanBeUsed($targetPlatform);

        if (count($approaches) === 0) {
            $output->writeln('No INI setup approaches can be used on this platform.', OutputInterface::VERBOSITY_VERBOSE);

            return false;
        }

        foreach ($approaches as $approach) {
            $output->writeln(
                sprintf(
                    'Trying to enable extension using %s',
                    (new ReflectionClass($approach))->getShortName(),
                ),
                OutputInterface::VERBOSITY_VERBOSE,
            );
            if ($approach->setup($targetPlatform, $downloadedPackage, $binaryFile, $output)) {
                return true;
            }
        }

        $output->writeln('None of the INI setup approaches succeeded.', OutputInterface::VERBOSITY_VERBOSE);

        return false;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\Ini;

use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\Platform\TargetPlatform;
use Symfony\Component\Console\Output\OutputInterface;
use Throwable;

use function file_exists;
use function is_readable;
use function sprintf;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class CheckAndAddExtensionToIniIfNeeded
{
    public function __construct(
        private readonly IsExtensionAlreadyInTheIniFile $isExtensionAlreadyInTheIniFile,
        private readonly AddExtensionToTheIniFile $addExtensionToTheIniFile,
    ) {
    }

    /**
     * @param non-empty-string     $iniFile
     * @param callable():bool|null $additionalEnableStep
     */
    public function __invoke(
        string $iniFile,
        TargetPlatform $targetPlatform,
        DownloadedPackage $downloadedPackage,
        OutputInterface $output,
        callable|null $additionalEnableStep,
    ): bool {
        if (! file_exists($iniFile) || ! is_readable($iniFile)) {
            $output->writeln(
                sprintf(
                    'PHP is configured to use %s, but it did not exist, or is not readable by PIE.',
                    $iniFile,
                ),
                OutputInterface::VERBOSITY_VERBOSE,
            );

            return false;
        }

        if (($this->isExtensionAlreadyInTheIniFile)($iniFile, $downloadedPackage->package->extensionName())) {
            $output->writeln(
                sprintf(
                    'Extension is already enabled in the INI file %s',
                    $iniFile,
                ),
                OutputInterface::VERBOSITY_VERBOSE,
            );

            if ($additionalEnableStep !== null && ! $additionalEnableStep()) {
                return false;
            }

            try {
                $targetPlatform->phpBinaryPath->assertExtensionIsLoadedInRuntime($downloadedPackage->package->extensionName(), $output);

                return true;
            } catch (Throwable $anything) {
                $output->writeln(sprintf(
                    '<error>Something went wrong verifying the %s extension is enabled: %s</error>',
                    $downloadedPackage->package->extensionName()->name(),
                    $anything->getMessage(),
                ));

                return false;
            }
        }

        return ($this->addExtensionToTheIniFile)(
            $iniFile,
            $downloadedPackage->package,
            $targetPlatform->phpBinaryPath,
            $output,
            $additionalEnableStep,
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\Ini;

use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\File\BinaryFile;
use Php\Pie\Platform\TargetPhp\Exception\ExtensionIsNotLoaded;
use Php\Pie\Platform\TargetPlatform;
use Php\Pie\Util\Process;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\Exception\ProcessFailedException;

use function sprintf;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class DockerPhpExtEnable implements SetupIniApproach
{
    private const DOCKER_PHP_EXT_ENABLE = 'docker-php-ext-enable';

    public function __construct(private readonly string $dockerPhpExtEnableName = self::DOCKER_PHP_EXT_ENABLE)
    {
    }

    public function canBeUsed(TargetPlatform $targetPlatform): bool
    {
        return $this->dockerPhpExtEnablePath() !== null;
    }

    public function setup(
        TargetPlatform $targetPlatform,
        DownloadedPackage $downloadedPackage,
        BinaryFile $binaryFile,
        OutputInterface $output,
    ): bool {
        $dockerPhpExtEnable = $this->dockerPhpExtEnablePath();

        if ($dockerPhpExtEnable === null) {
            return false;
        }

        try {
            $enableOutput = Process::run([$dockerPhpExtEnable, $downloadedPackage->package->extensionName()->name()]);
        } catch (ProcessFailedException $processFailed) {
            $output->writeln(
                sprintf(
                    'Could not enable extension %s using %s. Exception was: %s',
                    $downloadedPackage->package->extensionName()->name(),
                    $this->dockerPhpExtEnableName,
                    $processFailed->getMessage(),
                ),
                OutputInterface::VERBOSITY_VERBOSE,
            );

            return false;
        }

        try {
            $targetPlatform->phpBinaryPath->assertExtensionIsLoadedInRuntime(
                $downloadedPackage->package->extensionName(),
                $output,
            );

            return true;
        } catch (ExtensionIsNotLoaded) {
            $output->writeln(
                sprintf(
                    'Asserting that extension %s was enabled using %s failed. Output was: %s',
                    $downloadedPackage->package->extensionName()->name(),
                    $this->dockerPhpExtEnableName,
                    $enableOutput !== '' ? $enableOutput : '(empty)',
                ),
                OutputInterface::VERBOSITY_VERBOSE,
            );

            return false;
        }
    }

    private function dockerPhpExtEnablePath(): string|null
    {
        try {
            return Process::run(['which', $this->dockerPhpExtEnableName]);
        } catch (ProcessFailedException) {
            return null;
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\Ini;

use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\File\BinaryFile;
use Php\Pie\Platform\TargetPlatform;
use Symfony\Component\Console\Output\OutputInterface;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class StandardSinglePhpIni implements SetupIniApproach
{
    public function __construct(
        private readonly CheckAndAddExtensionToIniIfNeeded $checkAndAddExtensionToIniIfNeeded,
    ) {
    }

    public function canBeUsed(TargetPlatform $targetPlatform): bool
    {
        return $targetPlatform->phpBinaryPath->loadedIniConfigurationFile() !== null;
    }

    public function setup(
        TargetPlatform $targetPlatform,
        DownloadedPackage $downloadedPackage,
        BinaryFile $binaryFile,
        OutputInterface $output,
    ): bool {
        $ini = $targetPlatform->phpBinaryPath->loadedIniConfigurationFile();

        /** In practice, this shouldn't happen since {@see canBeUsed()} checks this */
        if ($ini === null) {
            return false;
        }

        return ($this->checkAndAddExtensionToIniIfNeeded)(
            $ini,
            $targetPlatform,
            $downloadedPackage,
            $output,
            null,
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\Ini;

use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\File\BinaryFile;
use Php\Pie\File\Sudo;
use Php\Pie\File\SudoCreate;
use Php\Pie\File\SudoUnlink;
use Php\Pie\Platform\TargetPlatform;
use Symfony\Component\Console\Output\OutputInterface;

use function file_exists;
use function is_writable;
use function rtrim;
use function sprintf;

use const DIRECTORY_SEPARATOR;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class StandardAdditionalPhpIniDirectory implements SetupIniApproach
{
    public function __construct(
        private readonly CheckAndAddExtensionToIniIfNeeded $checkAndAddExtensionToIniIfNeeded,
    ) {
    }

    public function canBeUsed(TargetPlatform $targetPlatform): bool
    {
        return $targetPlatform->phpBinaryPath->additionalIniDirectory() !== null;
    }

    public function setup(
        TargetPlatform $targetPlatform,
        DownloadedPackage $downloadedPackage,
        BinaryFile $binaryFile,
        OutputInterface $output,
    ): bool {
        $additionalIniFilesPath = $targetPlatform->phpBinaryPath->additionalIniDirectory();

        /** In practice, this shouldn't happen since {@see canBeUsed()} checks this */
        if ($additionalIniFilesPath === null) {
            return false;
        }

        if (! file_exists($additionalIniFilesPath)) {
            $output->writeln(
                sprintf(
                    'PHP is configured to use additional INI file path %s, but it did not exist.',
                    $additionalIniFilesPath,
                ),
                OutputInterface::VERBOSITY_VERBOSE,
            );

            return false;
        }

        if (! is_writable($additionalIniFilesPath) && ! Sudo::exists()) {
            $output->writeln(
                sprintf(
                    'PHP is configured to use additional INI file path %s, but it was not writable by PIE.',
                    $additionalIniFilesPath,
                ),
                OutputInterface::VERBOSITY_VERBOSE,
            );

            return false;
        }

        $expectedIniFile = sprintf(
            '%s%s%d-%s.ini',
            rtrim($additionalIniFilesPath, DIRECTORY_SEPARATOR),
            DIRECTORY_SEPARATOR,
            $downloadedPackage->package->priority(),
            $downloadedPackage->package->extensionName()->name(),
        );

        $pieCreatedTheIniFile = false;
        if (! file_exists($expectedIniFile)) {
            $output->writeln(
                sprintf(
                    'Creating new INI file based on extension priority: %s',
                    $expectedIniFile,
                ),
                OutputInterface::VERBOSITY_VERY_VERBOSE,
            );
            $pieCreatedTheIniFile = true;
            SudoCreate::file($expectedIniFile);
        }

        $addingExtensionWasSuccessful = ($this->checkAndAddExtensionToIniIfNeeded)(
            $expectedIniFile,
            $targetPlatform,
            $downloadedPackage,
            $output,
            null,
        );

        if (! $addingExtensionWasSuccessful && $pieCreatedTheIniFile) {
            SudoUnlink::singleFile($expectedIniFile);
        }

        return $addingExtensionWasSuccessful;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing;

use Php\Pie\DependencyResolver\Package;
use RuntimeException;

use function array_diff;
use function array_keys;
use function count;
use function implode;
use function sprintf;

class PackageMetadataMissing extends RuntimeException
{
    /**
     * @param array<string, mixed> $actualMetadata
     * @param list<string>         $wantedKeys
     */
    public static function duringUninstall(Package $package, array $actualMetadata, array $wantedKeys): self
    {
        $missingKeys = array_diff($wantedKeys, array_keys($actualMetadata));

        return new self(sprintf(
            'PIE metadata was missing for package %s. Missing metadata key%s: %s',
            $package->name(),
            count($missingKeys) === 1 ? '' : 's',
            implode(', ', $missingKeys),
        ));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing;

use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\File\BinaryFile;
use Php\Pie\Platform\TargetPlatform;
use Symfony\Component\Console\Output\OutputInterface;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
interface Install
{
    /**
     * Install the extension in the given target platform's PHP, and return the location of the installed shared object
     * or DLL, depending on the platform implementation.
     */
    public function __invoke(
        DownloadedPackage $downloadedPackage,
        TargetPlatform $targetPlatform,
        OutputInterface $output,
        bool $attemptToSetupIniFile,
    ): BinaryFile;
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing;

use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\File\BinaryFile;
use Php\Pie\File\WindowsDelete;
use Php\Pie\Platform\TargetPlatform;
use Php\Pie\Platform\WindowsExtensionAssetName;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use RuntimeException;
use SplFileInfo;
use Symfony\Component\Console\Output\OutputInterface;

use function assert;
use function copy;
use function dirname;
use function file_exists;
use function is_file;
use function mkdir;
use function str_replace;
use function strlen;
use function substr;

use const DIRECTORY_SEPARATOR;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class WindowsInstall implements Install
{
    public function __construct(private readonly SetupIniFile $setupIniFile)
    {
    }

    public function __invoke(
        DownloadedPackage $downloadedPackage,
        TargetPlatform $targetPlatform,
        OutputInterface $output,
        bool $attemptToSetupIniFile,
    ): BinaryFile {
        $extractedSourcePath = $downloadedPackage->extractedSourcePath;
        $sourceDllName       = WindowsExtensionAssetName::determineDllName($targetPlatform, $downloadedPackage);
        $sourcePdbName       = str_replace('.dll', '.pdb', $sourceDllName);
        assert($sourcePdbName !== '');

        $destinationDllName = $this->copyExtensionDll($targetPlatform, $downloadedPackage, $sourceDllName);
        $output->writeln('<info>Copied DLL to:</info> ' . $destinationDllName);

        $destinationPdbName = $this->copyExtensionPdb($targetPlatform, $downloadedPackage, $sourcePdbName, $destinationDllName);
        if ($destinationPdbName !== null) {
            $output->writeln('<info>Copied PDB to:</info> ' . $destinationPdbName);
        }

        foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($extractedSourcePath)) as $file) {
            assert($file instanceof SplFileInfo);

            /**
             * Skip directories, the main DLL, PDB
             */
            if (
                $file->isDir()
                || $this->normalisedPathsMatch($file->getPathname(), $sourceDllName)
                || $this->normalisedPathsMatch($file->getPathname(), $sourcePdbName)
            ) {
                continue;
            }

            $destinationExtraDll = $this->copyDependencyDll($targetPlatform, $file);
            if ($destinationExtraDll !== null) {
                $output->writeln('<info>Copied extra DLL:</info> ' . $destinationExtraDll);

                continue;
            }

            $destinationPathname = $this->copyExtraFile($targetPlatform, $downloadedPackage, $file);
            $output->writeln('<info>Copied extras:</info> ' . $destinationPathname);
        }

        $binaryFile = BinaryFile::fromFileWithSha256Checksum($destinationDllName);

        ($this->setupIniFile)(
            $targetPlatform,
            $downloadedPackage,
            $binaryFile,
            $output,
            $attemptToSetupIniFile,
        );

        return $binaryFile;
    }

    /**
     * Normalise both path parameters (i.e. replace `\` with `/`) and compare them. Useful if the two paths are built
     * differently with different/incorrect directory separators, e.g. "C:\path\to/thing" vs "C:\path\to\thing"
     */
    private function normalisedPathsMatch(string $first, string $second): bool
    {
        return str_replace('\\', '/', $first) === str_replace('\\', '/', $second);
    }

    /**
     * Copy the main PHP extension DLL into the extension path.
     *
     * @param non-empty-string $sourceDllName
     *
     * @return non-empty-string
     */
    private function copyExtensionDll(TargetPlatform $targetPlatform, DownloadedPackage $downloadedPackage, string $sourceDllName): string
    {
        $destinationDllName = $targetPlatform->phpBinaryPath->extensionPath() . DIRECTORY_SEPARATOR
            . 'php_' . $downloadedPackage->package->extensionName()->name() . '.dll';

        if (file_exists($destinationDllName)) {
            WindowsDelete::usingMoveToTemp($destinationDllName);
        }

        if (! copy($sourceDllName, $destinationDllName) || ! file_exists($destinationDllName) && ! is_file($destinationDllName)) {
            throw new RuntimeException('Failed to install DLL to ' . $destinationDllName);
        }

        return $destinationDllName;
    }

    /**
     * Copy the PDB (Program Database, which is debugging information basically), into the same directory as the DLL,
     * if it exists.
     *
     * Returns `null` if the source PDB does not exist (and thus, does not need to be copied).
     *
     * @param non-empty-string $sourcePdbName
     * @param non-empty-string $destinationDllName
     *
     * @return non-empty-string|null
     */
    private function copyExtensionPdb(TargetPlatform $targetPlatform, DownloadedPackage $downloadedPackage, string $sourcePdbName, string $destinationDllName): string|null
    {
        if (! file_exists($sourcePdbName)) {
            return null;
        }

        $destinationPdbName = str_replace('.dll', '.pdb', $destinationDllName);
        assert($destinationPdbName !== '');

        if (! copy($sourcePdbName, $destinationPdbName) || ! file_exists($destinationPdbName) && ! is_file($destinationPdbName)) {
            throw new RuntimeException('Failed to install PDB to ' . $destinationPdbName);
        }

        return $destinationPdbName;
    }

    /**
     * Any other DLL file included in the source package should be copied into the same path where `php.exe` is - these
     * would commonly be dependencies/libraries that the extension depends on, and is bundled with.
     *
     * If the file is NOT a DLL, this method will return `null`
     *
     * @return non-empty-string|null
     */
    private function copyDependencyDll(TargetPlatform $targetPlatform, SplFileInfo $file): string|null
    {
        if ($file->getExtension() !== 'dll') {
            return null;
        }

        $destinationExtraDll = dirname($targetPlatform->phpBinaryPath->phpBinaryPath) . DIRECTORY_SEPARATOR . $file->getFilename();

        if (! copy($file->getPathname(), $destinationExtraDll) || ! file_exists($destinationExtraDll) && ! is_file($destinationExtraDll)) {
            throw new RuntimeException('Failed to copy to ' . $destinationExtraDll);
        }

        return $destinationExtraDll;
    }

    /**
     * Any other remaining file should be copied into the "extras" path, e.g. `C:\php\extras\my-php-ext\.`
     *
     * @return non-empty-string
     */
    private function copyExtraFile(TargetPlatform $targetPlatform, DownloadedPackage $downloadedPackage, SplFileInfo $file): string
    {
        $destinationFullFilename = dirname($targetPlatform->phpBinaryPath->phpBinaryPath) . DIRECTORY_SEPARATOR
            . 'extras' . DIRECTORY_SEPARATOR
            . $downloadedPackage->package->extensionName()->name() . DIRECTORY_SEPARATOR
            . substr($file->getPathname(), strlen($downloadedPackage->extractedSourcePath) + 1);

        $destinationPath = dirname($destinationFullFilename);

        if (! file_exists($destinationPath)) {
            mkdir($destinationPath, 0777, true);
        }

        if (! copy($file->getPathname(), $destinationFullFilename) || ! file_exists($destinationFullFilename) && ! is_file($destinationFullFilename)) {
            throw new RuntimeException('Failed to copy to ' . $destinationFullFilename);
        }

        return $destinationFullFilename;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing;

use Php\Pie\DependencyResolver\Package;
use Php\Pie\File\BinaryFile;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
interface Uninstall
{
    public function __invoke(Package $package): BinaryFile;
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing;

use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\ExtensionType;
use Php\Pie\File\BinaryFile;
use Php\Pie\Installing\Ini\SetupIniApproach;
use Php\Pie\Platform\TargetPlatform;
use Php\Pie\Util\Emoji;
use Symfony\Component\Console\Output\OutputInterface;

use function sprintf;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class SetupIniFile
{
    /** @psalm-suppress PossiblyUnusedMethod no direct reference; used in service locator */
    public function __construct(private readonly SetupIniApproach $setupIniApproach)
    {
    }

    public function __invoke(
        TargetPlatform $targetPlatform,
        DownloadedPackage $downloadedPackage,
        BinaryFile $binaryFile,
        OutputInterface $output,
        bool $attemptToSetupIniFile,
    ): void {
        if (
            $attemptToSetupIniFile
            && $this->setupIniApproach->canBeUsed($targetPlatform)
            && $this->setupIniApproach->setup($targetPlatform, $downloadedPackage, $binaryFile, $output)
        ) {
            $output->writeln(sprintf(
                '<info>%s Extension is enabled and loaded in</info> %s',
                Emoji::GREEN_CHECKMARK,
                $targetPlatform->phpBinaryPath->phpBinaryPath,
            ));
        } else {
            if (! $attemptToSetupIniFile) {
                $output->writeln('Automatic extension enabling was skipped.', OutputInterface::VERBOSITY_VERBOSE);
            }

            $output->writeln(sprintf('<comment>%s Extension has NOT been automatically enabled.</comment>', Emoji::WARNING));
            $output->writeln(sprintf(
                '<comment>You must now add "%s=%s" to your php.ini</comment>',
                $downloadedPackage->package->extensionType() === ExtensionType::PhpModule ? 'extension' : 'zend_extension',
                $downloadedPackage->package->extensionName()->name(),
            ));
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\InstallForPhpProject;

use Composer\Composer;
use Composer\Package\Link;
use Composer\Repository\InstalledRepository;
use Composer\Repository\RootPackageRepository;
use Php\Pie\ExtensionName;

use function array_filter;
use function in_array;
use function ksort;
use function str_starts_with;
use function strlen;
use function substr;

class DetermineExtensionsRequired
{
    public static function linkFilter(Link $link): bool
    {
        $linkTarget = $link->getTarget();
        if (! str_starts_with($linkTarget, 'ext-')) {
            return false;
        }

        return ExtensionName::isValidExtensionName(substr($linkTarget, strlen('ext-')));
    }

    /** @return array<string, Link> */
    public function forProject(Composer $composer): array
    {
        $requires          = [];
        $removeDevPackages = [];

        /** {@see \Composer\Command\CheckPlatformReqsCommand::execute} */
        $installedRepo = $composer->getRepositoryManager()->getLocalRepository();
        if (! $installedRepo->getPackages()) {
            $installedRepo = $composer->getLocker()->getLockedRepository();
        } else {
            $removeDevPackages = $installedRepo->getDevPackageNames();
        }

        foreach (array_filter($composer->getPackage()->getDevRequires(), [self::class, 'linkFilter']) as $require => $link) {
            $requires[$require] = $link;
        }

        $installedRepo = new InstalledRepository([$installedRepo, new RootPackageRepository(clone $composer->getPackage())]);

        foreach ($installedRepo->getPackages() as $package) {
            if (in_array($package->getName(), $removeDevPackages, true)) {
                continue;
            }

            foreach (array_filter($package->getRequires(), [self::class, 'linkFilter']) as $require => $link) {
                $requires[$require] = $link;
            }
        }

        ksort($requires);

        return $requires;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\InstallForPhpProject;

use Composer\Composer;
use Composer\Package\CompletePackage;
use Composer\Repository\RepositoryInterface;
use OutOfRangeException;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\ExtensionName;

use function array_filter;
use function array_key_exists;
use function array_merge;
use function count;
use function usort;

/**
 * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
 *
 * @psalm-type MatchingPackages = list<array{name: string, description: ?string, ...}>
 */
class FindMatchingPackages
{
    /** @return MatchingPackages */
    public function for(Composer $pieComposer, string $searchTerm): array
    {
        $matches = [];
        foreach ($pieComposer->getRepositoryManager()->getRepositories() as $repo) {
            $matches = array_merge($matches, $repo->search($searchTerm, RepositoryInterface::SEARCH_FULLTEXT, 'php-ext'));
            $matches = array_merge($matches, $repo->search($searchTerm, RepositoryInterface::SEARCH_FULLTEXT, 'php-ext-zend'));
        }

        if (ExtensionName::isValidExtensionName($searchTerm)) {
            $extensionName = ExtensionName::normaliseFromString($searchTerm);

            $matches = array_filter(
                $matches,
                static function (array $match) use ($pieComposer, $extensionName): bool {
                    $package = $pieComposer->getRepositoryManager()->findPackage($match['name'], '*');

                    if (! $package instanceof CompletePackage) {
                        return false;
                    }

                    return Package::fromComposerCompletePackage($package)->extensionName()->name() === $extensionName->name();
                },
            );
        }

        if (! count($matches)) {
            throw new OutOfRangeException('No matches found for ' . $searchTerm);
        }

        usort($matches, static function (array $a, array $b): int {
            return (array_key_exists('downloads', $b) ? $b['downloads'] : 0)
                <=> (array_key_exists('downloads', $a) ? $a['downloads'] : 0);
        });

        return $matches;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\InstallForPhpProject;

use Php\Pie\Command\CommandHelper;
use Php\Pie\File\FullPathToSelf;
use Php\Pie\Util\Process;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;

use function array_filter;
use function array_walk;
use function getcwd;
use function in_array;

use const ARRAY_FILTER_USE_BOTH;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class InstallSelectedPackage
{
    public function __construct(private readonly FullPathToSelf $fullPathToSelf)
    {
    }

    public function withPieCli(string $selectedPackage, InputInterface $input, OutputInterface $output): void
    {
        $process = [
            ($this->fullPathToSelf)(),
            'install',
            $selectedPackage,
        ];

        $phpPathOptions = array_filter(
            $input->getOptions(),
            static function (mixed $value, string|int $key): bool {
                return $value !== null
                    && $value !== false
                    && in_array(
                        $key,
                        [
                            CommandHelper::OPTION_WITH_PHP_CONFIG,
                            CommandHelper::OPTION_WITH_PHP_PATH,
                            CommandHelper::OPTION_WITH_PHPIZE_PATH,
                        ],
                    );
            },
            ARRAY_FILTER_USE_BOTH,
        );

        array_walk(
            $phpPathOptions,
            static function (string $value, string $key) use (&$process): void {
                $process[] = '--' . $key;
                $process[] = $value;
            },
        );

        Process::run(
            $process,
            getcwd(),
            null,
            static function (string $outOrErr, string $message) use ($output): void {
                if ($output instanceof ConsoleOutputInterface && $outOrErr === \Symfony\Component\Process\Process::ERR) {
                    $output->getErrorOutput()->write('   > ' . $message);

                    return;
                }

                $output->write('   > ' . $message);
            },
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\InstallForPhpProject;

use Composer\Package\RootPackageInterface;
use Php\Pie\Command\InvokeSubCommand;
use Php\Pie\ComposerIntegration\PieJsonEditor;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

use function sprintf;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class InstallPiePackageFromPath
{
    /** @psalm-suppress PossiblyUnusedMethod no direct reference; used in service locator */
    public function __construct(private readonly InvokeSubCommand $invokeSubCommand)
    {
    }

    /** @param non-empty-string $piePackagePath */
    public function __invoke(Command $invokeContext, string $piePackagePath, RootPackageInterface $pieRootPackage, PieJsonEditor $pieJsonEditor, InputInterface $input, OutputInterface $output): int
    {
        $output->writeln(sprintf('Installing PIE extension from <info>%s</info>', $piePackagePath));
        $pieJsonEditor
            ->ensureExists()
            ->addRepository('path', $piePackagePath);

        try {
            return ($this->invokeSubCommand)(
                $invokeContext,
                [
                    'command' => 'install',
                    'requested-package-and-version' => $pieRootPackage->getName() . ':*@dev',
                ],
                $input,
                $output,
            );
        } finally {
            $output->writeln(
                sprintf(
                    'Removing temporary path repository: %s',
                    $piePackagePath,
                ),
                OutputInterface::VERBOSITY_VERBOSE,
            );
            $pieJsonEditor->removeRepository($piePackagePath);
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing\InstallForPhpProject;

use Composer\Composer;
use Composer\Factory as ComposerFactory;
use Composer\IO\ConsoleIO;
use Composer\Package\RootPackageInterface;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class ComposerFactoryForProject
{
    private Composer|null $memoizedComposer = null;

    public function composer(InputInterface $input, OutputInterface $output): Composer
    {
        if ($this->memoizedComposer === null) {
            $this->memoizedComposer = ComposerFactory::create(new ConsoleIO(
                $input,
                $output,
                new HelperSet([]),
            ));
        }

        return $this->memoizedComposer;
    }

    public function rootPackage(InputInterface $input, OutputInterface $output): RootPackageInterface
    {
        return $this->composer($input, $output)->getPackage();
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing;

use Php\Pie\ComposerIntegration\PieInstalledJsonMetadataKeys;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\File\BinaryFile;
use Php\Pie\File\FailedToUnlinkFile;
use Php\Pie\File\Sudo;
use Php\Pie\File\SudoUnlink;
use Php\Pie\Util\Process;

use function array_key_exists;
use function file_exists;
use function is_writable;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class UninstallUsingUnlink implements Uninstall
{
    public function __invoke(Package $package): BinaryFile
    {
        $pieMetadata = PieInstalledJsonMetadataKeys::pieMetadataFromComposerPackage($package->composerPackage());

        if (
            ! array_key_exists(PieInstalledJsonMetadataKeys::InstalledBinary->value, $pieMetadata)
            || ! array_key_exists(PieInstalledJsonMetadataKeys::BinaryChecksum->value, $pieMetadata)
        ) {
            throw PackageMetadataMissing::duringUninstall(
                $package,
                $pieMetadata,
                [
                    PieInstalledJsonMetadataKeys::InstalledBinary->value,
                    PieInstalledJsonMetadataKeys::BinaryChecksum->value,
                ],
            );
        }

        $expectedBinaryFile = new BinaryFile(
            $pieMetadata[PieInstalledJsonMetadataKeys::InstalledBinary->value],
            $pieMetadata[PieInstalledJsonMetadataKeys::BinaryChecksum->value],
        );

        $expectedBinaryFile->verify();

        // If the target directory isn't writable, or a .so file already exists and isn't writable, try to use sudo
        if (file_exists($expectedBinaryFile->filePath) && ! is_writable($expectedBinaryFile->filePath) && Sudo::exists()) {
            Process::run([Sudo::find(), 'rm', $expectedBinaryFile->filePath]);

            // Removal worked, bail out
            if (! file_exists($expectedBinaryFile->filePath)) {
                return $expectedBinaryFile;
            }
        }

        try {
            SudoUnlink::singleFile($expectedBinaryFile->filePath);
        } catch (FailedToUnlinkFile $failedToUnlinkFile) {
            throw FailedToRemoveExtension::withFilename($expectedBinaryFile, $failedToUnlinkFile);
        }

        return $expectedBinaryFile;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing;

use Php\Pie\File\BinaryFile;
use RuntimeException;
use Throwable;

use function sprintf;

class FailedToRemoveExtension extends RuntimeException
{
    public static function withFilename(BinaryFile $extension, Throwable $previous): self
    {
        return new self(
            sprintf(
                'Failed to remove extension file: %s',
                $extension->filePath,
            ),
            previous: $previous,
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Installing;

use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\File\BinaryFile;
use Php\Pie\File\Sudo;
use Php\Pie\Platform\TargetPlatform;
use Php\Pie\Util\Process;
use RuntimeException;
use Symfony\Component\Console\Output\OutputInterface;

use function array_unshift;
use function file_exists;
use function is_writable;
use function sprintf;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class UnixInstall implements Install
{
    private const MAKE_INSTALL_TIMEOUT_SECS = 300; // 5 minutes

    public function __construct(private readonly SetupIniFile $setupIniFile)
    {
    }

    public function __invoke(
        DownloadedPackage $downloadedPackage,
        TargetPlatform $targetPlatform,
        OutputInterface $output,
        bool $attemptToSetupIniFile,
    ): BinaryFile {
        $targetExtensionPath = $targetPlatform->phpBinaryPath->extensionPath();

        $sharedObjectName             = $downloadedPackage->package->extensionName()->name() . '.so';
        $expectedSharedObjectLocation = sprintf(
            '%s/%s',
            $targetExtensionPath,
            $sharedObjectName,
        );

        $makeInstallCommand = ['make', 'install'];

        // If the target directory isn't writable, or a .so file already exists and isn't writable, try to use sudo
        if (
            (
                ! is_writable($targetExtensionPath)
                || (file_exists($expectedSharedObjectLocation) && ! is_writable($expectedSharedObjectLocation))
            )
            && Sudo::exists()
        ) {
            $output->writeln(sprintf(
                '<comment>Cannot write to %s, so using sudo to elevate privileges.</comment>',
                $targetExtensionPath,
            ));
            array_unshift($makeInstallCommand, Sudo::find());
        }

        $makeInstallOutput = Process::run(
            $makeInstallCommand,
            $downloadedPackage->extractedSourcePath,
            self::MAKE_INSTALL_TIMEOUT_SECS,
        );

        if ($output->isVeryVerbose()) {
            $output->writeln($makeInstallOutput);
        }

        if (! file_exists($expectedSharedObjectLocation)) {
            throw new RuntimeException('Install failed, ' . $expectedSharedObjectLocation . ' was not installed.');
        }

        $output->writeln('<info>Install complete:</info> ' . $expectedSharedObjectLocation);

        $binaryFile = BinaryFile::fromFileWithSha256Checksum($expectedSharedObjectLocation);

        ($this->setupIniFile)(
            $targetPlatform,
            $downloadedPackage,
            $binaryFile,
            $output,
            $attemptToSetupIniFile,
        );

        return $binaryFile;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Building;

use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\File\BinaryFile;
use Php\Pie\Platform\TargetPhp\PhpizePath;
use Php\Pie\Platform\TargetPlatform;
use Symfony\Component\Console\Output\OutputInterface;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
interface Build
{
    /** @param list<non-empty-string> $configureOptions */
    public function __invoke(
        DownloadedPackage $downloadedPackage,
        TargetPlatform $targetPlatform,
        array $configureOptions,
        OutputInterface $output,
        PhpizePath|null $phpizePath,
    ): BinaryFile;
}
<?php

declare(strict_types=1);

namespace Php\Pie\Building;

use Php\Pie\ComposerIntegration\BundledPhpExtensionsRepository;
use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\File\BinaryFile;
use Php\Pie\Platform\TargetPhp\PhpizePath;
use Php\Pie\Platform\TargetPlatform;
use Php\Pie\Util\Process;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\Process as SymfonyProcess;

use function count;
use function file_exists;
use function implode;
use function rename;
use function sprintf;

use const DIRECTORY_SEPARATOR;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class UnixBuild implements Build
{
    private const PHPIZE_TIMEOUT_SECS    = 60; // 1 minute
    private const CONFIGURE_TIMEOUT_SECS = 120; // 2 minutes
    private const MAKE_TIMEOUT_SECS      = null; // unlimited

    /** {@inheritDoc} */
    public function __invoke(
        DownloadedPackage $downloadedPackage,
        TargetPlatform $targetPlatform,
        array $configureOptions,
        OutputInterface $output,
        PhpizePath|null $phpizePath,
    ): BinaryFile {
        $outputCallback = null;
        if ($output->isVerbose()) {
            /** @var callable(SymfonyProcess::ERR|SymfonyProcess::OUT, string):void $outputCallback */
            $outputCallback = static function (string $type, string $outputMessage) use ($output): void {
                $output->write(sprintf(
                    '%s%s%s',
                    $type === SymfonyProcess::ERR ? '<comment>' : '',
                    $outputMessage,
                    $type === SymfonyProcess::ERR ? '</comment>' : '',
                ));
            };
        }

        $phpizePath ??= PhpizePath::guessFrom($targetPlatform->phpBinaryPath);

        /**
         * Call a cleanup first; most of the time, we expect to be changing a
         * version (e.g. upgrade, downgrade), in which case the source is
         * already clean anyway; however, sometimes we want to rebuild the
         * current ext, so this will perform a clean first
         */
        $this->cleanup($phpizePath, $downloadedPackage, $output, $outputCallback);

        $this->phpize(
            $phpizePath,
            $downloadedPackage,
            $output,
            $outputCallback,
        );

        $output->writeln('<info>phpize complete</info>.');

        $phpConfigPath = $targetPlatform->phpBinaryPath->phpConfigPath();
        if ($phpConfigPath !== null) {
            $configureOptions[] = '--with-php-config=' . $phpConfigPath;
        }

        $this->configure($downloadedPackage, $configureOptions, $output, $outputCallback);

        $optionsOutput = count($configureOptions) ? ' with options: ' . implode(' ', $configureOptions) : '.';
        $output->writeln('<info>Configure complete</info>' . $optionsOutput);

        $this->make($targetPlatform, $downloadedPackage, $output, $outputCallback);

        $expectedSoFile = $downloadedPackage->extractedSourcePath . '/modules/' . $downloadedPackage->package->extensionName()->name() . '.so';

        if (! file_exists($expectedSoFile)) {
            throw ExtensionBinaryNotFound::fromExpectedBinary($expectedSoFile);
        }

        $output->writeln(sprintf(
            '<info>Build complete:</info> %s',
            $expectedSoFile,
        ));

        return BinaryFile::fromFileWithSha256Checksum($expectedSoFile);
    }

    private function renamesToConfigM4(DownloadedPackage $downloadedPackage, OutputInterface $output): void
    {
        $configM4 = $downloadedPackage->extractedSourcePath . DIRECTORY_SEPARATOR . 'config.m4';
        if (file_exists($configM4)) {
            return;
        }

        $output->writeln('config.m4 does not exist; checking alternatives', OutputInterface::VERBOSITY_VERY_VERBOSE);
        foreach (['config0.m4', 'config9.m4'] as $alternateConfigM4) {
            $fullPathToAlternate = $downloadedPackage->extractedSourcePath . DIRECTORY_SEPARATOR . $alternateConfigM4;
            if (file_exists($fullPathToAlternate)) {
                $output->writeln(sprintf('Renaming %s to config.m4', $alternateConfigM4), OutputInterface::VERBOSITY_VERY_VERBOSE);
                rename($fullPathToAlternate, $configM4);

                return;
            }
        }
    }

    /** @param callable(SymfonyProcess::ERR|SymfonyProcess::OUT, string): void|null $outputCallback */
    private function phpize(
        PhpizePath $phpize,
        DownloadedPackage $downloadedPackage,
        OutputInterface $output,
        callable|null $outputCallback,
    ): void {
        $phpizeCommand = [$phpize->phpizeBinaryPath];

        if ($output->isVerbose()) {
            $output->writeln('<comment>Running phpize step using: ' . implode(' ', $phpizeCommand) . '</comment>');
        }

        $this->renamesToConfigM4($downloadedPackage, $output);

        Process::run(
            $phpizeCommand,
            $downloadedPackage->extractedSourcePath,
            self::PHPIZE_TIMEOUT_SECS,
            $outputCallback,
        );
    }

    /**
     * @param list<non-empty-string>                                               $configureOptions
     * @param callable(SymfonyProcess::ERR|SymfonyProcess::OUT, string): void|null $outputCallback
     */
    private function configure(
        DownloadedPackage $downloadedPackage,
        array $configureOptions,
        OutputInterface $output,
        callable|null $outputCallback,
    ): void {
        $configureCommand = ['./configure', ...$configureOptions];

        if ($output->isVerbose()) {
            $output->writeln('<comment>Running configure step with: ' . implode(' ', $configureCommand) . '</comment>');
        }

        Process::run(
            $configureCommand,
            $downloadedPackage->extractedSourcePath,
            self::CONFIGURE_TIMEOUT_SECS,
            $outputCallback,
        );
    }

    /** @param callable(SymfonyProcess::ERR|SymfonyProcess::OUT, string): void|null $outputCallback */
    private function make(
        TargetPlatform $targetPlatform,
        DownloadedPackage $downloadedPackage,
        OutputInterface $output,
        callable|null $outputCallback,
    ): void {
        $makeCommand = ['make'];

        if ($targetPlatform->makeParallelJobs === 1) {
            $output->writeln('Running make without parallelization - try providing -jN to PIE where N is the number of cores you have.');
        } else {
            $makeCommand[] = sprintf('-j%d', $targetPlatform->makeParallelJobs);
        }

        $makeCommand = BundledPhpExtensionsRepository::augmentMakeCommandForPhpBundledExtensions(
            $makeCommand,
            $downloadedPackage,
        );

        if ($output->isVerbose()) {
            $output->writeln('<comment>Running make step with: ' . implode(' ', $makeCommand) . '</comment>');
        }

        Process::run(
            $makeCommand,
            $downloadedPackage->extractedSourcePath,
            self::MAKE_TIMEOUT_SECS,
            $outputCallback,
        );
    }

    /** @param callable(SymfonyProcess::ERR|SymfonyProcess::OUT, string): void|null $outputCallback */
    private function cleanup(
        PhpizePath $phpize,
        DownloadedPackage $downloadedPackage,
        OutputInterface $output,
        callable|null $outputCallback,
    ): void {
        /**
         * A basic, but fallible check to see if we should clean first. This
         * should work "most" of the time, unless someone has removed the
         * configure script manually...
         */
        if (! file_exists($downloadedPackage->extractedSourcePath . '/configure')) {
            if ($output->isVerbose()) {
                $output->writeln('<comment>Skipping phpize --clean, configure does not exist</comment>');
            }

            return;
        }

        $phpizeCleanCommand = [$phpize->phpizeBinaryPath, '--clean'];

        if ($output->isVerbose()) {
            $output->writeln('<comment>Running phpize --clean step using: ' . implode(' ', $phpizeCleanCommand) . '</comment>');
        }

        Process::run(
            $phpizeCleanCommand,
            $downloadedPackage->extractedSourcePath,
            self::PHPIZE_TIMEOUT_SECS,
            $outputCallback,
        );

        $output->writeln('<info>Build files cleaned up.</info>');
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Building;

use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\File\BinaryFile;
use Php\Pie\Platform\TargetPhp\PhpizePath;
use Php\Pie\Platform\TargetPlatform;
use Php\Pie\Platform\WindowsExtensionAssetName;
use Symfony\Component\Console\Output\OutputInterface;

use function sprintf;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class WindowsBuild implements Build
{
    /** {@inheritDoc} */
    public function __invoke(
        DownloadedPackage $downloadedPackage,
        TargetPlatform $targetPlatform,
        array $configureOptions,
        OutputInterface $output,
        PhpizePath|null $phpizePath,
    ): BinaryFile {
        $prebuiltDll = WindowsExtensionAssetName::determineDllName($targetPlatform, $downloadedPackage);

        $output->writeln(sprintf(
            '<info>Nothing to do on Windows, prebuilt DLL found:</info> %s',
            $prebuiltDll,
        ));

        return BinaryFile::fromFileWithSha256Checksum($prebuiltDll);
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Building;

use RuntimeException;

use function sprintf;

class ExtensionBinaryNotFound extends RuntimeException
{
    public static function fromExpectedBinary(string $expectedBinaryName): self
    {
        return new self(sprintf(
            'Build complete, but expected %s does not exist.',
            $expectedBinaryName,
        ));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\DependencyResolver;

use RuntimeException;

class IncompatibleThreadSafetyMode extends RuntimeException
{
    public static function ztsExtensionOnNtsPlatform(): self
    {
        return new self('This extension does not support being installed on a non-Thread Safe PHP installation');
    }

    public static function ntsExtensionOnZtsPlatform(): self
    {
        return new self('This extension does not support being installed on a Thread Safe PHP installation');
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\DependencyResolver;

use RuntimeException;

use function sprintf;

use const PHP_EOL;

class BundledPhpExtensionRefusal extends RuntimeException
{
    public static function forPackage(Package $package): self
    {
        return new self(sprintf(
            'Bundled PHP extension %s should be installed by your distribution, not by PIE.%s%sCombining installation methods of bundled PHP extensions can lead to confusing and unintended consequences.%s%sIf you are really sure, you want to install %s using PIE, re-run the command with the --force flag.',
            $package->name(),
            PHP_EOL,
            PHP_EOL,
            PHP_EOL,
            PHP_EOL,
            $package->name(),
        ));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\DependencyResolver;

use RuntimeException;

use function sprintf;

class InvalidPackageName extends RuntimeException
{
    public function __construct(string $message, public readonly RequestedPackageAndVersion $requestedPackageAndVersion)
    {
        parent::__construct($message);
    }

    public static function fromMissingForwardSlash(RequestedPackageAndVersion $requestedPackageAndVersion): self
    {
        return new self(
            sprintf(
                'Requested package name "%s" is invalid; it should contain a forward slash, like "vendor/package".',
                $requestedPackageAndVersion->package,
            ),
            $requestedPackageAndVersion,
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\DependencyResolver;

use Composer\Composer;
use Php\Pie\Platform\TargetPlatform;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
interface DependencyResolver
{
    /** @throws UnableToResolveRequirement */
    public function __invoke(
        Composer $composer,
        TargetPlatform $targetPlatform,
        RequestedPackageAndVersion $requestedPackageAndVersion,
        bool $forceInstallPackageVersion,
    ): Package;
}
<?php

declare(strict_types=1);

namespace Php\Pie\DependencyResolver;

use Composer\Package\PackageInterface;
use Php\Pie\ComposerIntegration\QuieterConsoleIO;
use RuntimeException;

use function count;
use function implode;
use function sprintf;

class UnableToResolveRequirement extends RuntimeException
{
    public function __construct(string $message, public readonly RequestedPackageAndVersion $requestedPackageAndVersion)
    {
        parent::__construct($message);
    }

    public static function fromRequirement(
        RequestedPackageAndVersion $requestedPackageAndVersion,
        QuieterConsoleIO $io,
    ): self {
        $errors = $io->errors;

        return new self(
            sprintf(
                'Unable to find an installable package %s for %s, with minimum stability %s.%s',
                $requestedPackageAndVersion->package,
                $requestedPackageAndVersion->version !== null ? sprintf('version %s', $requestedPackageAndVersion->version) : 'the latest compatible version',
                DetermineMinimumStability::fromRequestedVersion($requestedPackageAndVersion->version),
                count($errors) ? "\n\n" . implode("\n\n", $errors) : '',
            ),
            $requestedPackageAndVersion,
        );
    }

    public static function toPhpOrZendExtension(PackageInterface $locatedComposerPackage, RequestedPackageAndVersion $requestedPackageAndVersion): self
    {
        return new self(
            sprintf(
                'Package %s was not of type php-ext or php-ext-zend (requested %s%s).',
                $locatedComposerPackage->getName(),
                $requestedPackageAndVersion->package,
                $requestedPackageAndVersion->version !== null ? sprintf(' for version %s', $requestedPackageAndVersion->version) : '',
            ),
            $requestedPackageAndVersion,
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\DependencyResolver;

use Php\Pie\Platform\OperatingSystemFamily;
use RuntimeException;

use function array_map;
use function implode;
use function sprintf;

class IncompatibleOperatingSystemFamily extends RuntimeException
{
    /** @param list<OperatingSystemFamily> $required */
    public static function notInCompatibleOperatingSystemFamilies(array $required, OperatingSystemFamily $current): self
    {
        return new self(sprintf(
            'This extension does not support the "%s" operating system family. It is compatible with the following families: "%s".',
            $current->value,
            implode('", "', array_map(static fn (OperatingSystemFamily $osFamily): string => $osFamily->value, $required)),
        ));
    }

    /** @param list<OperatingSystemFamily> $incompatibleOsFamilies */
    public static function inIncompatibleOperatingSystemFamily(array $incompatibleOsFamilies, OperatingSystemFamily $current): self
    {
        return new self(sprintf(
            'This extension does not support the "%s" operating system family. It is incompatible with the following families: "%s".',
            $current->value,
            implode('", "', array_map(static fn (OperatingSystemFamily $osFamily): string => $osFamily->value, $incompatibleOsFamilies)),
        ));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\DependencyResolver;

use Composer\Semver\VersionParser;
use Webmozart\Assert\Assert;

/**
 * Utility to extract a valid Stability (see {@see \Composer\Package\BasePackage::$stabilities}) from a requested
 * package version in a predictable way.
 *
 * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
 */
final class DetermineMinimumStability
{
    private const STABILITY_STABLE = 'stable';
    private const STABILITY_RC     = 'RC';
    private const STABILITY_BETA   = 'beta';
    private const STABILITY_ALPHA  = 'alpha';
    private const STABILITY_DEV    = 'dev';

    private const DEFAULT_MINIMUM_STABILITY = self::STABILITY_STABLE;

    /** @psalm-assert self::STABILITY_* $stability */
    private static function assertValidStabilityString(string $stability): void
    {
        Assert::oneOf(
            $stability,
            [
                self::STABILITY_STABLE,
                self::STABILITY_RC,
                self::STABILITY_BETA,
                self::STABILITY_ALPHA,
                self::STABILITY_DEV,
            ],
        );
    }

    /** @return self::STABILITY_* */
    public static function fromRequestedVersion(string|null $requestedVersion): string
    {
        if ($requestedVersion === null) {
            return self::DEFAULT_MINIMUM_STABILITY;
        }

        $stability = VersionParser::parseStability($requestedVersion);
        self::assertValidStabilityString($stability);

        return $stability;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\DependencyResolver;

use Composer\Composer;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Package\CompletePackageInterface;
use Php\Pie\ComposerIntegration\QuieterConsoleIO;
use Php\Pie\ComposerIntegration\VersionSelectorFactory;
use Php\Pie\ExtensionType;
use Php\Pie\Platform\TargetPlatform;
use Php\Pie\Platform\ThreadSafetyMode;
use Symfony\Component\Console\Output\OutputInterface;

use function in_array;
use function preg_match;
use function sprintf;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class ResolveDependencyWithComposer implements DependencyResolver
{
    public function __construct(
        private readonly OutputInterface $output,
        private readonly QuieterConsoleIO $arrayCollectionIo,
    ) {
    }

    public function __invoke(
        Composer $composer,
        TargetPlatform $targetPlatform,
        RequestedPackageAndVersion $requestedPackageAndVersion,
        bool $forceInstallPackageVersion,
    ): Package {
        $versionSelector = VersionSelectorFactory::make($composer, $requestedPackageAndVersion, $targetPlatform);

        $package = $versionSelector->findBestCandidate(
            $requestedPackageAndVersion->package,
            $requestedPackageAndVersion->version,
            platformRequirementFilter: PlatformRequirementFilterFactory::fromBoolOrList($forceInstallPackageVersion),
            io: $this->arrayCollectionIo,
        );

        if (! $package instanceof CompletePackageInterface) {
            throw UnableToResolveRequirement::fromRequirement($requestedPackageAndVersion, $this->arrayCollectionIo);
        }

        /**
         * If a specific commit hash is requested, override the references in the package. This is approximately what
         * Composer does anyway:
         *
         * > ArrayLoader::parseLinks is in charge of this, it drops commit refs, for package resolution purposes we
         * > only use dev-main, but we ensure in the PoolBuilder that root references (#...) are set so the dev-main
         * > package has its source and dist refs overridden to be whatever you specify and that applies at install
         * > time then but package metadata is only read from the branch's head
         */
        if ($requestedPackageAndVersion->version !== null && preg_match('/#([a-f0-9]{40})$/', $requestedPackageAndVersion->version, $matches)) {
            $package->setSourceDistReferences($matches[1]);
        }

        if (! ExtensionType::isValid($package->getType())) {
            throw UnableToResolveRequirement::toPhpOrZendExtension($package, $requestedPackageAndVersion);
        }

        $piePackage = Package::fromComposerCompletePackage($package);

        $this->assertBuildProviderProvidersBundledExtensions($targetPlatform, $piePackage, $forceInstallPackageVersion);
        $this->assertCompatibleOsFamily($targetPlatform, $piePackage);
        $this->assertCompatibleThreadSafetyMode($targetPlatform->threadSafety, $piePackage);

        return $piePackage;
    }

    private function assertCompatibleThreadSafetyMode(ThreadSafetyMode $threadSafetyMode, Package $resolvedPackage): void
    {
        if ($threadSafetyMode === ThreadSafetyMode::NonThreadSafe && ! $resolvedPackage->supportNts()) {
            throw IncompatibleThreadSafetyMode::ztsExtensionOnNtsPlatform();
        }

        if ($threadSafetyMode === ThreadSafetyMode::ThreadSafe && ! $resolvedPackage->supportZts()) {
            throw IncompatibleThreadSafetyMode::ntsExtensionOnZtsPlatform();
        }
    }

    private function assertCompatibleOsFamily(TargetPlatform $targetPlatform, Package $resolvedPackage): void
    {
        if ($resolvedPackage->compatibleOsFamilies() !== null && ! in_array($targetPlatform->operatingSystemFamily, $resolvedPackage->compatibleOsFamilies(), true)) {
            throw IncompatibleOperatingSystemFamily::notInCompatibleOperatingSystemFamilies(
                $resolvedPackage->compatibleOsFamilies(),
                $targetPlatform->operatingSystemFamily,
            );
        }

        if ($resolvedPackage->incompatibleOsFamilies() !== null && in_array($targetPlatform->operatingSystemFamily, $resolvedPackage->incompatibleOsFamilies(), true)) {
            throw IncompatibleOperatingSystemFamily::inIncompatibleOperatingSystemFamily(
                $resolvedPackage->incompatibleOsFamilies(),
                $targetPlatform->operatingSystemFamily,
            );
        }
    }

    private function assertBuildProviderProvidersBundledExtensions(TargetPlatform $targetPlatform, Package $piePackage, bool $forceInstallPackageVersion): void
    {
        if (! $piePackage->isBundledPhpExtension()) {
            return;
        }

        $buildProvider           = $targetPlatform->phpBinaryPath->buildProvider();
        $identifiedBuildProvider = false;
        $note                    = '<options=bold,underscore;fg=red>Note:</> ';

        if ($buildProvider === 'https://github.com/docker-library/php') {
            $identifiedBuildProvider = true;
            $this->output->writeln(sprintf(
                '<comment>%sYou should probably use "docker-php-ext-install %s" instead</comment>',
                $note,
                $piePackage->extensionName()->name(),
            ));
        }

        if ($buildProvider === 'Debian') {
            $identifiedBuildProvider = true;
            $this->output->writeln(sprintf(
                '<comment>%sYou should probably use "apt install php%s-%s" or "apt install php-%s" (or similar) instead</comment>',
                $note,
                $targetPlatform->phpBinaryPath->majorMinorVersion(),
                $piePackage->extensionName()->name(),
                $piePackage->extensionName()->name(),
            ));
        }

        if ($buildProvider === 'Remi\'s RPM repository <https://rpms.remirepo.net/> #StandWithUkraine') {
            $identifiedBuildProvider = true;
            $this->output->writeln(sprintf(
                '<comment>%sYou should probably use "dnf install php-%s" instead</comment>',
                $note,
                $piePackage->extensionName()->name(),
            ));
        }

        if ($identifiedBuildProvider && ! $forceInstallPackageVersion) {
            throw BundledPhpExtensionRefusal::forPackage($piePackage);
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\DependencyResolver;

use Composer\Package\CompletePackageInterface;
use InvalidArgumentException;
use Php\Pie\ConfigureOption;
use Php\Pie\Downloading\DownloadUrlMethod;
use Php\Pie\ExtensionName;
use Php\Pie\ExtensionType;
use Php\Pie\Platform\OperatingSystemFamily;
use Webmozart\Assert\Assert;

use function array_key_exists;
use function array_map;
use function array_slice;
use function explode;
use function implode;
use function parse_url;
use function str_contains;
use function str_starts_with;
use function strtolower;

/**
 * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
 *
 * @immutable
 *
 * @psalm-suppress PropertyNotSetInConstructor
 */
final class Package
{
    /** @var list<ConfigureOption> */
    private array $configureOptions = [];
    private int $priority           = 80;
    private string|null $buildPath  = null;
    /** @var non-empty-list<OperatingSystemFamily>|null */
    private array|null $compatibleOsFamilies = null;
    /** @var non-empty-list<OperatingSystemFamily>|null */
    private array|null $incompatibleOsFamilies        = null;
    private bool $supportZts                          = true;
    private bool $supportNts                          = true;
    private DownloadUrlMethod|null $downloadUrlMethod = null;

    public function __construct(
        private readonly CompletePackageInterface $composerPackage,
        private readonly ExtensionType $extensionType,
        private readonly ExtensionName $extensionName,
        private readonly string $name,
        private readonly string $version,
        private readonly string|null $downloadUrl,
    ) {
    }

    public static function fromComposerCompletePackage(CompletePackageInterface $completePackage): self
    {
        $package = new self(
            $completePackage,
            ExtensionType::tryFrom($completePackage->getType()) ?? ExtensionType::PhpModule,
            ExtensionName::determineFromComposerPackage($completePackage),
            $completePackage->getPrettyName(),
            $completePackage->getPrettyVersion(),
            $completePackage->getDistUrl(),
        );

        $phpExtOptions = $completePackage->getPhpExt();

        $package->configureOptions = $phpExtOptions !== null && array_key_exists('configure-options', $phpExtOptions)
            ? array_map(
                static fn (array $configureOption): ConfigureOption => ConfigureOption::fromComposerJsonDefinition($configureOption),
                $phpExtOptions['configure-options'],
            )
            : [];

        $package->supportZts = $phpExtOptions['support-zts'] ?? true;
        $package->supportNts = $phpExtOptions['support-nts'] ?? true;
        $package->buildPath  = $phpExtOptions['build-path'] ?? null;

        $compatibleOsFamilies   = $phpExtOptions['os-families'] ?? null;
        $incompatibleOsFamilies = $phpExtOptions['os-families-exclude'] ?? null;

        if ($compatibleOsFamilies !== null && $incompatibleOsFamilies !== null) {
            throw new InvalidArgumentException('Cannot specify both "os-families" and "os-families-exclude" in composer.json');
        }

        $package->compatibleOsFamilies   = self::convertInputStringsToOperatingSystemFamilies($compatibleOsFamilies);
        $package->incompatibleOsFamilies = self::convertInputStringsToOperatingSystemFamilies($incompatibleOsFamilies);

        $package->priority = $phpExtOptions['priority'] ?? 80;

        if ($phpExtOptions !== null && array_key_exists('download-url-method', $phpExtOptions)) {
            $package->downloadUrlMethod = DownloadUrlMethod::tryFrom($phpExtOptions['download-url-method']);
        }

        return $package;
    }

    public function prettyNameAndVersion(): string
    {
        return $this->name . ':' . $this->version;
    }

    public function githubOrgAndRepository(): string
    {
        if ($this->downloadUrl === null || str_contains($this->downloadUrl, '/' . $this->name . '/')) {
            return $this->name;
        }

        if (! str_starts_with($this->downloadUrl, 'https://api.github.com/repos/')) {
            return $this->name;
        }

        $parsed = parse_url($this->downloadUrl);
        if ($parsed === false || ! array_key_exists('path', $parsed)) {
            return $this->name;
        }

        // Converts https://api.github.com/repos/<user>/<repository>/zipball/<sha>" to "<user>/<repository>"
        return implode('/', array_slice(explode('/', $parsed['path']), 2, 2));
    }

    /**
     * @param list<string>|null $input
     *
     * @return non-empty-list<OperatingSystemFamily>|null
     */
    private static function convertInputStringsToOperatingSystemFamilies(array|null $input): array|null
    {
        if ($input === null) {
            return null;
        }

        Assert::isNonEmptyList($input, 'Expected operating systems families to be a non-empty list.');

        return array_map(
            static function ($value): OperatingSystemFamily {
                Assert::inArray(
                    strtolower($value),
                    OperatingSystemFamily::asValuesList(),
                    'Expected operating system family to be one of: %2$s. Got: %s',
                );

                return OperatingSystemFamily::from(strtolower($value));
            },
            $input,
        );
    }

    public function composerPackage(): CompletePackageInterface
    {
        return $this->composerPackage;
    }

    public function extensionType(): ExtensionType
    {
        return $this->extensionType;
    }

    public function extensionName(): ExtensionName
    {
        return $this->extensionName;
    }

    public function isBundledPhpExtension(): bool
    {
        return str_starts_with($this->name(), 'php/');
    }

    public function name(): string
    {
        return $this->name;
    }

    public function version(): string
    {
        return $this->version;
    }

    /** @return list<ConfigureOption> */
    public function configureOptions(): array
    {
        return $this->configureOptions;
    }

    public function downloadUrl(): string|null
    {
        return $this->downloadUrl;
    }

    public function priority(): int
    {
        return $this->priority;
    }

    public function buildPath(): string|null
    {
        return $this->buildPath;
    }

    /** @return non-empty-list<OperatingSystemFamily>|null */
    public function compatibleOsFamilies(): array|null
    {
        return $this->compatibleOsFamilies;
    }

    /** @return non-empty-list<OperatingSystemFamily>|null */
    public function incompatibleOsFamilies(): array|null
    {
        return $this->incompatibleOsFamilies;
    }

    public function supportZts(): bool
    {
        return $this->supportZts;
    }

    public function supportNts(): bool
    {
        return $this->supportNts;
    }

    public function downloadUrlMethod(): DownloadUrlMethod|null
    {
        return $this->downloadUrlMethod;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\DependencyResolver;

use function str_contains;

/**
 * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
 *
 * @immutable
 */
final class RequestedPackageAndVersion
{
    /**
     * @param non-empty-string      $package
     * @param non-empty-string|null $version
     *
     * @throws InvalidPackageName
     */
    public function __construct(
        public readonly string $package,
        public readonly string|null $version,
    ) {
        if (! str_contains($this->package, '/')) {
            throw InvalidPackageName::fromMissingForwardSlash($this);
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie;

use Composer\Package\PackageInterface;
use InvalidArgumentException;
use Webmozart\Assert\Assert;

use function array_key_exists;
use function explode;
use function is_string;
use function preg_match;
use function sprintf;
use function str_starts_with;
use function strlen;
use function substr;

/**
 * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
 *
 * @immutable
 */
final class ExtensionName
{
    /**
     * PECL extension names must contain only alphanumeric characters and underscores, and must start with an
     * alphabetical character. PIE does not change this requirement for consistency.
     *
     * @link https://github.com/pear/pear-core/blob/6f4c3a0b134626d238d75a44af01a2f7c4e688d9/PEAR/Common.php#L28
     */
    private const VALID_PACKAGE_NAME_REGEX = '#^[A-Za-z][a-zA-Z0-9_]+$#';

    // phpcs:disable SlevomatCodingStandard.Classes.RequireConstructorPropertyPromotion.RequiredConstructorPropertyPromotion
    /** @var non-empty-string */
    private readonly string $normalisedExtensionName;
    // phpcs:enable

    private function __construct(string $normalisedExtensionName)
    {
        if (! self::isValidExtensionName($normalisedExtensionName)) {
            throw new InvalidArgumentException(sprintf(
                'The value "%s" is not a valid extension name. An extension must start with a letter, and only contain alphanumeric characters or underscores',
                $normalisedExtensionName,
            ));
        }

        $this->normalisedExtensionName = $normalisedExtensionName;
    }

    /** @psalm-assert-if-true non-empty-string $extensionName */
    public static function isValidExtensionName(string $extensionName): bool
    {
        return preg_match(self::VALID_PACKAGE_NAME_REGEX, $extensionName) >= 1;
    }

    public static function determineFromComposerPackage(PackageInterface $package): self
    {
        $phpExt = $package->getPhpExt();

        /** @psalm-suppress DocblockTypeContradiction just in case runtime type is not correct */
        if (
            $phpExt === null
            || ! array_key_exists('extension-name', $phpExt)
            || ! is_string($phpExt['extension-name'])
            || $phpExt['extension-name'] === ''
        ) {
            $packageNameParts = explode('/', $package->getPrettyName());
            Assert::count($packageNameParts, 2, 'Expected a package name like vendor/package for ' . $package->getPrettyName());
            Assert::keyExists($packageNameParts, 1);

            return self::normaliseFromString($packageNameParts[1]);
        }

        return self::normaliseFromString($phpExt['extension-name']);
    }

    public static function normaliseFromString(string $extensionName): self
    {
        if (str_starts_with($extensionName, 'ext-')) {
            return new self(substr($extensionName, strlen('ext-')));
        }

        return new self($extensionName);
    }

    /** @return non-empty-string */
    public function name(): string
    {
        return $this->normalisedExtensionName;
    }

    /** @return non-empty-string */
    public function nameWithExtPrefix(): string
    {
        return 'ext-' . $this->normalisedExtensionName;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\File;

use Php\Pie\Util\CaptureErrors;
use Php\Pie\Util\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;

use function dirname;
use function file_exists;
use function file_put_contents;
use function is_writable;
use function preg_match;
use function sys_get_temp_dir;
use function tempnam;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class SudoFilePut
{
    public static function contents(string $filename, string $content): void
    {
        $fileWritable = file_exists($filename) && is_writable($filename);
        $pathWritable = ! file_exists($filename) && file_exists(dirname($filename)) && is_writable(dirname($filename));

        if ($fileWritable || $pathWritable) {
            $capturedErrors  = [];
            $writeSuccessful = CaptureErrors::for(
                static fn () => file_put_contents($filename, $content),
                $capturedErrors,
            );

            if ($writeSuccessful === false) {
                throw FailedToWriteFile::fromFilePutContentErrors($filename, $capturedErrors);
            }

            return;
        }

        if (! Sudo::exists()) {
            throw FailedToWriteFile::fromNoPermissions($filename);
        }

        self::writeWithSudo($filename, $content);
    }

    private static function writeWithSudo(string $filename, string $content): void
    {
        $tempFilename = tempnam(sys_get_temp_dir(), 'pie_tmp_');
        if ($tempFilename === false) {
            throw FailedToWriteFile::fromNoPermissions($filename);
        }

        $capturedErrors  = [];
        $writeSuccessful = CaptureErrors::for(
            static fn () => file_put_contents($tempFilename, $content),
            $capturedErrors,
        );

        if ($writeSuccessful === false) {
            throw FailedToWriteFile::fromFilePutContentErrors($tempFilename, $capturedErrors);
        }

        if (file_exists($filename)) {
            self::copyOwnership($filename, $tempFilename);
        }

        Process::run([Sudo::find(), 'mv', $tempFilename, $filename]);
    }

    /**
     * Attempt to copy the ownership details (uid/gid) from the source to the
     * given target file.
     */
    private static function copyOwnership(string $sourceFile, string $targetFile): void
    {
        try {
            // GNU chmod supports `--reference`, so try this first
            Process::run([Sudo::find(), 'chmod', '--reference=' . $sourceFile, $targetFile]);

            return;
        } catch (ProcessFailedException) {
            // Fall back to using `stat` to determine uid/gid
            try {
                // Try using GNU stat (-c) first
                $userAndGroup = Process::run(['stat', '-c', '%u:%g', $sourceFile], timeout: 2);
            } catch (ProcessFailedException) {
                try {
                    // Fall back to using OSX stat (-f)
                    $userAndGroup = Process::run(['stat', '-f', '%u:%g', $sourceFile], timeout: 2);
                } catch (ProcessFailedException) {
                    return;
                }
            }

            if (empty($userAndGroup) || ! preg_match('/^\d+:\d+$/', $userAndGroup)) {
                return;
            }

            Process::run([Sudo::find(), 'chown', $userAndGroup, $targetFile]);
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\File;

use Php\Pie\Util\Process;
use RuntimeException;

use function file_exists;
use function sprintf;
use function sys_get_temp_dir;
use function tempnam;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class WindowsDelete
{
    /**
     * Windows will hold a lock on the DLL, so we can't actually replace or
     * delete the extension. However, Windows lets us move it out the way,
     * so move it to the temp directory. It can then be cleaned up later.
     */
    public static function usingMoveToTemp(string $filename): void
    {
        if (! file_exists($filename)) {
            return;
        }

        $newLockedExtFilename = tempnam(sys_get_temp_dir(), 'pie');
        if ($newLockedExtFilename === false) {
            throw new RuntimeException(sprintf(
                'Failed to create a temporary name for moving %s',
                $filename,
            ));
        }

        Process::run(['move', $filename, $newLockedExtFilename]);

        if (file_exists($filename)) {
            throw new RuntimeException(sprintf(
                'Failed to move %s to %s',
                $filename,
                $newLockedExtFilename,
            ));
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\File;

use RuntimeException;

class SudoNotFoundOnSystem extends RuntimeException
{
    public static function new(): self
    {
        return new self('sudo command was not found on this system');
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\File;

use Php\Pie\Util\CaptureErrors;
use RuntimeException;

use function array_column;
use function implode;
use function sprintf;

/** @psalm-import-type CapturedErrorList from CaptureErrors */
class FailedToWriteFile extends RuntimeException
{
    /** @param CapturedErrorList $recorded */
    public static function fromFilePutContentErrors(string $filename, array $recorded): self
    {
        return new self(sprintf(
            "Failed to write file %s.\n\nErrors:\n - %s",
            $filename,
            implode("\n - ", array_column($recorded, 'message')),
        ));
    }

    public static function fromNoPermissions(string $filename): self
    {
        return new self(sprintf(
            'Failed to write file %s as PIE does not have enough permissions',
            $filename,
        ));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\File;

use RuntimeException;

use function array_key_exists;
use function is_string;
use function preg_match;
use function realpath;

use const DIRECTORY_SEPARATOR;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class FullPathToSelf
{
    /** @psalm-suppress PossiblyUnusedMethod no direct reference; used in service locator */
    public function __construct(private readonly string $originalCwd)
    {
    }

    /** @return non-empty-string */
    public function __invoke(): string
    {
        /** @psalm-suppress TypeDoesNotContainType */
        $phpSelf = array_key_exists('PHP_SELF', $_SERVER) && is_string($_SERVER['PHP_SELF']) ? $_SERVER['PHP_SELF'] : '';
        if ($phpSelf === '') {
            throw new RuntimeException('Could not find PHP_SELF');
        }

        return $this->isAbsolutePath($phpSelf)
            ? $phpSelf
            : ($this->originalCwd . DIRECTORY_SEPARATOR . $phpSelf);
    }

    private function isAbsolutePath(string $path): bool
    {
        if (realpath($path) === $path) {
            return true;
        }

        if ($path === '' || $path === '.') {
            return false;
        }

        if (preg_match('#^[a-zA-Z]:\\\\#', $path)) {
            return true;
        }

        return $path[0] === '/' || $path[0] === '\\';
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\File;

use RuntimeException;

use function sprintf;

class SudoRequiresInteractiveTerminal extends RuntimeException
{
    public static function fromSudo(string $sudoPath): self
    {
        return new self(sprintf(
            'PIE needed to elevate privileges with %s, but you are running in a non-interactive terminal, so prompting for a password is not possible.',
            $sudoPath,
        ));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\File;

use Php\Pie\Util;

use function file_exists;
use function hash_equals;
use function hash_file;

/**
 * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
 *
 * @immutable
 */
final class BinaryFile
{
    private const HASH_TYPE_SHA256 = 'sha256';

    /**
     * @param non-empty-string $filePath
     * @param non-empty-string $checksum
     */
    public function __construct(
        public readonly string $filePath,
        public readonly string $checksum,
    ) {
    }

    /** @param non-empty-string $filePath */
    public static function fromFileWithSha256Checksum(string $filePath): self
    {
        return new self(
            $filePath,
            hash_file(self::HASH_TYPE_SHA256, $filePath),
        );
    }

    public function verify(): void
    {
        if (! file_exists($this->filePath)) {
            throw Util\FileNotFound::fromFilename($this->filePath);
        }

        self::verifyAgainstOther(self::fromFileWithSha256Checksum($this->filePath));
    }

    /** @throws BinaryFileFailedVerification */
    public function verifyAgainstOther(self $other): void
    {
        if ($this->filePath !== $other->filePath) {
            throw BinaryFileFailedVerification::fromFilenameMismatch($this, $other);
        }

        if (! hash_equals($this->checksum, $other->checksum)) {
            throw BinaryFileFailedVerification::fromChecksumMismatch($this, $other);
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\File;

use Php\Pie\Platform;
use Php\Pie\Platform\TargetPlatform;
use Symfony\Component\Process\ExecutableFinder;

use function is_string;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class Sudo
{
    /** @var non-empty-string|null */
    private static string|null $memoizedSudo = null;

    /**
     * @return non-empty-string
     *
     * @throws SudoNotFoundOnSystem
     */
    public static function find(): string
    {
        if (! is_string(self::$memoizedSudo)) {
            $sudo = (new ExecutableFinder())->find('sudo');

            if ($sudo === null || $sudo === '') {
                throw SudoNotFoundOnSystem::new();
            }

            if (! TargetPlatform::isRunningAsRoot() && ! Platform::isInteractive()) {
                throw SudoRequiresInteractiveTerminal::fromSudo($sudo);
            }

            self::$memoizedSudo = $sudo;
        }

        return self::$memoizedSudo;
    }

    public static function exists(): bool
    {
        try {
            self::find();

            return true;
        } catch (SudoNotFoundOnSystem) {
            return false;
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\File;

use Composer\Util\Platform;
use Php\Pie\Util\CaptureErrors;
use Php\Pie\Util\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;

use function file_exists;
use function is_writable;
use function unlink;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class SudoUnlink
{
    public static function singleFile(string $filename): void
    {
        if (! file_exists($filename)) {
            return;
        }

        if (! Platform::isWindows() && is_writable($filename)) {
            $capturedErrors   = [];
            $unlinkSuccessful = CaptureErrors::for(
                static fn () => unlink($filename),
                $capturedErrors,
            );

            if (! $unlinkSuccessful || file_exists($filename)) {
                throw FailedToUnlinkFile::fromUnlinkErrors($filename, $capturedErrors);
            }

            return;
        }

        if (Platform::isWindows()) {
            WindowsDelete::usingMoveToTemp($filename);

            return;
        }

        if (! Sudo::exists()) {
            throw FailedToUnlinkFile::fromNoPermissions($filename);
        }

        try {
            Process::run([Sudo::find(), 'rm', $filename]);
        } catch (ProcessFailedException $processFailedException) {
            throw FailedToUnlinkFile::fromSudoRmProcessFailed($filename, $processFailedException);
        }

        if (! file_exists($filename)) {
            return;
        }

        throw FailedToUnlinkFile::fromNoPermissions($filename);
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\File;

use Php\Pie\Util\CaptureErrors;
use RuntimeException;
use Symfony\Component\Process\Exception\ProcessFailedException;

use function array_column;
use function implode;
use function sprintf;

/** @psalm-import-type CapturedErrorList from CaptureErrors */
class FailedToUnlinkFile extends RuntimeException
{
    /** @param CapturedErrorList $recorded */
    public static function fromUnlinkErrors(string $filename, array $recorded): self
    {
        return new self(sprintf(
            "Failed to unlink file %s.\n\nErrors:\n - %s",
            $filename,
            implode("\n - ", array_column($recorded, 'message')),
        ));
    }

    public static function fromNoPermissions(string $filename): self
    {
        return new self(sprintf(
            'Failed to unlink file %s as PIE does not have enough permissions',
            $filename,
        ));
    }

    public static function fromSudoRmProcessFailed(string $filename, ProcessFailedException $processFailed): self
    {
        return new self(
            sprintf(
                'Failed to unlink file %s using sudo rm: %s',
                $filename,
                $processFailed->getMessage(),
            ),
            previous: $processFailed,
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\File;

use Php\Pie\Util\CaptureErrors;
use RuntimeException;
use Symfony\Component\Process\Exception\ProcessFailedException;

use function array_column;
use function implode;
use function sprintf;

/** @psalm-import-type CapturedErrorList from CaptureErrors */
class FailedToCreateFile extends RuntimeException
{
    /** @param CapturedErrorList $recorded */
    public static function fromTouchErrors(string $filename, array $recorded): self
    {
        return new self(sprintf(
            "Failed to create file %s.\n\nErrors:\n - %s",
            $filename,
            implode("\n - ", array_column($recorded, 'message')),
        ));
    }

    public static function fromNoPermissions(string $filename): self
    {
        return new self(sprintf(
            'Failed to create file %s as PIE does not have enough permissions',
            $filename,
        ));
    }

    public static function fromSudoTouchProcessFailed(string $filename, ProcessFailedException $processFailed): self
    {
        return new self(
            sprintf(
                'Failed to create file %s using sudo touch: %s',
                $filename,
                $processFailed->getMessage(),
            ),
            previous: $processFailed,
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\File;

use Php\Pie\Util\CaptureErrors;
use Php\Pie\Util\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;

use function dirname;
use function file_exists;
use function is_writable;
use function touch;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class SudoCreate
{
    public static function file(string $filename): void
    {
        /**
         * Note: strictly speaking, `touch` is a command to:
         *
         * > Update the access and modification times of each FILE to the current time.
         *
         * But, the way we are using `touch` is to just create the file, hence
         * the class naming is to reflect that. So; if the file already exists
         * we can exit early (as we're not actually interested in updating the
         * access/modification times of the file currently).
         */
        if (file_exists($filename)) {
            return;
        }

        if (is_writable(dirname($filename))) {
            $capturedErrors  = [];
            $touchSuccessful = CaptureErrors::for(
                static fn () => touch($filename),
                $capturedErrors,
            );

            if (! $touchSuccessful) {
                throw FailedToCreateFile::fromTouchErrors($filename, $capturedErrors);
            }

            return;
        }

        if (! Sudo::exists()) {
            throw FailedToCreateFile::fromNoPermissions($filename);
        }

        try {
            Process::run([Sudo::find(), 'touch', $filename]);
        } catch (ProcessFailedException $processFailedException) {
            throw FailedToCreateFile::fromSudoTouchProcessFailed($filename, $processFailedException);
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\File;

use RuntimeException;

use function sprintf;
use function substr;

class BinaryFileFailedVerification extends RuntimeException
{
    public static function fromFilenameMismatch(BinaryFile $expected, BinaryFile $actual): self
    {
        return new self(sprintf(
            'Expected file "%s" but actual file was "%s"',
            $expected->filePath,
            $actual->filePath,
        ));
    }

    public static function fromChecksumMismatch(BinaryFile $expected, BinaryFile $actual): self
    {
        return new self(sprintf(
            'File "%s" failed checksum verification. Expected %s..., was %s...',
            $expected->filePath,
            substr($expected->checksum, 0, 8),
            substr($actual->checksum, 0, 8),
        ));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
enum ExtensionType: string
{
    case PhpModule     = 'php-ext';
    case ZendExtension = 'php-ext-zend';

    public static function isValid(string $toBeChecked): bool
    {
        return self::tryFrom($toBeChecked) !== null;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Downloading;

use Php\Pie\DependencyResolver\Package;
use Php\Pie\Platform\OperatingSystem;
use Php\Pie\Platform\PrePackagedSourceAssetName;
use Php\Pie\Platform\TargetPlatform;
use Php\Pie\Platform\WindowsExtensionAssetName;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
enum DownloadUrlMethod: string
{
    case ComposerDefaultDownload   = 'composer-default';
    case WindowsBinaryDownload     = 'windows-binary';
    case PrePackagedSourceDownload = 'pre-packaged-source';

    /** @return non-empty-list<non-empty-string>|null */
    public function possibleAssetNames(Package $package, TargetPlatform $targetPlatform): array|null
    {
        return match ($this) {
            self::WindowsBinaryDownload => WindowsExtensionAssetName::zipNames($targetPlatform, $package),
            self::PrePackagedSourceDownload => PrePackagedSourceAssetName::packageNames($package),
            self::ComposerDefaultDownload => null,
        };
    }

    public static function fromPackage(Package $package, TargetPlatform $targetPlatform): self
    {
        /**
         * PIE does not support building on Windows (yet, at least). Maintainers
         * should provide pre-built Windows binaries.
         */
        if ($targetPlatform->operatingSystem === OperatingSystem::Windows) {
            return self::WindowsBinaryDownload;
        }

        /**
         * Some packages pre-package source code (e.g. mongodb) as there are
         * external dependencies in Git submodules that otherwise aren't
         * included in GitHub/Gitlab/etc "dist" downloads
         */
        if ($package->downloadUrlMethod() === DownloadUrlMethod::PrePackagedSourceDownload) {
            return self::PrePackagedSourceDownload;
        }

        return self::ComposerDefaultDownload;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Downloading\Exception;

use Php\Pie\DependencyResolver\Package;
use Php\Pie\Downloading\DownloadUrlMethod;
use Php\Pie\Platform\TargetPlatform;
use RuntimeException;

use function implode;
use function sprintf;

use const PHP_EOL;

class CouldNotFindReleaseAsset extends RuntimeException
{
    /** @param non-empty-list<non-empty-string> $expectedAssetNames */
    public static function forPackage(TargetPlatform $targetPlatform, Package $package, array $expectedAssetNames): self
    {
        $downloadUrlMethod = DownloadUrlMethod::fromPackage($package, $targetPlatform);

        if ($downloadUrlMethod === DownloadUrlMethod::WindowsBinaryDownload) {
            return new self(sprintf(
                'Windows archive with prebuilt extension for %s was not attached on release %s - looked for one of "%s"',
                $package->name(),
                $package->version(),
                implode(', ', $expectedAssetNames),
            ));
        }

        return new self(sprintf(
            'Could not find release asset for %s named one of "%s"',
            $package->prettyNameAndVersion(),
            implode(', ', $expectedAssetNames),
        ));
    }

    public static function forPackageWithMissingTag(Package $package): self
    {
        if (
            $package->downloadUrlMethod() === DownloadUrlMethod::PrePackagedSourceDownload
            && $package->composerPackage()->isDev()
        ) {
            return new self(sprintf(
                'The package %s uses pre-packaged source archives, which are not available for branch aliases such as %s. You should either omit the version constraint to use the latest compatible version, or use a tagged version instead. You can find a list of tagged versions on:%shttps://packagist.org/packages/%s',
                $package->name(),
                $package->version(),
                PHP_EOL . PHP_EOL,
                $package->name(),
            ));
        }

        return new self(sprintf(
            'Could not find release by tag name for %s',
            $package->prettyNameAndVersion(),
        ));
    }

    public static function forMissingWindowsCompiler(TargetPlatform $targetPlatform): self
    {
        return new self(sprintf(
            'Could not determine Windows Compiler for PHP %s on %s',
            $targetPlatform->phpBinaryPath->version(),
            $targetPlatform->operatingSystem->name,
        ));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Downloading;

use Composer\Util\AuthHelper;
use Composer\Util\HttpDownloader;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\Platform\TargetPlatform;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
interface PackageReleaseAssets
{
    /**
     * @param non-empty-list<non-empty-string> $possibleReleaseAssetNames
     *
     * @return non-empty-string
     */
    public function findMatchingReleaseAssetUrl(
        TargetPlatform $targetPlatform,
        Package $package,
        AuthHelper $authHelper,
        HttpDownloader $httpDownloader,
        array $possibleReleaseAssetNames,
    ): string;
}
<?php

declare(strict_types=1);

namespace Php\Pie\Downloading;

use Php\Pie\DependencyResolver\Package;
use Php\Pie\Platform\PrePackagedSourceAssetName;

use function array_map;
use function array_unique;
use function file_exists;
use function is_dir;
use function is_string;
use function pathinfo;
use function realpath;
use function str_replace;

use const DIRECTORY_SEPARATOR;
use const PATHINFO_FILENAME;

/**
 * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
 *
 * @immutable
 */
final class DownloadedPackage
{
    private function __construct(
        public readonly Package $package,
        public readonly string $extractedSourcePath,
    ) {
    }

    private static function unfoldUnarchivedSourcePaths(Package $package, string $extractedSourcePath): string
    {
        // There is already something buildable here, don't need to unfold
        if (
            file_exists($extractedSourcePath . DIRECTORY_SEPARATOR . 'config.m4')
            || file_exists($extractedSourcePath . DIRECTORY_SEPARATOR . 'config.w32')
        ) {
            return $extractedSourcePath;
        }

        $possibleAssetNames = array_unique(array_map(
            static fn (string $assetName): string => pathinfo($assetName, PATHINFO_FILENAME),
            PrePackagedSourceAssetName::packageNames($package),
        ));
        foreach ($possibleAssetNames as $possibleAssetName) {
            if (
                ! file_exists($extractedSourcePath . DIRECTORY_SEPARATOR . $possibleAssetName)
                || ! is_dir($extractedSourcePath . DIRECTORY_SEPARATOR . $possibleAssetName)
            ) {
                continue;
            }

            if (
                file_exists($extractedSourcePath . DIRECTORY_SEPARATOR . $possibleAssetName . DIRECTORY_SEPARATOR . 'config.m4')
                || file_exists($extractedSourcePath . DIRECTORY_SEPARATOR . $possibleAssetName . DIRECTORY_SEPARATOR . 'config.w32')
            ) {
                return $extractedSourcePath . DIRECTORY_SEPARATOR . $possibleAssetName;
            }
        }

        return $extractedSourcePath;
    }

    private static function overrideSourcePathUsingBuildPath(Package $package, string $extractedSourcePath): string
    {
        if ($package->buildPath() === null) {
            return $extractedSourcePath;
        }

        $extractedSourcePathWithBuildPath = realpath(
            $extractedSourcePath
            . DIRECTORY_SEPARATOR
            . str_replace('{version}', $package->version(), $package->buildPath()),
        );

        if (! is_string($extractedSourcePathWithBuildPath)) {
            return $extractedSourcePath;
        }

        return $extractedSourcePathWithBuildPath;
    }

    public static function fromPackageAndExtractedPath(Package $package, string $extractedSourcePath): self
    {
        $sourcePath = self::unfoldUnarchivedSourcePaths($package, $extractedSourcePath);

        if ($package->buildPath() !== null) {
            $sourcePath = self::overrideSourcePathUsingBuildPath($package, $extractedSourcePath);
        }

        return new self($package, $sourcePath);
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Downloading;

use Composer\Downloader\TransportException;
use Composer\Util\AuthHelper;
use Composer\Util\HttpDownloader;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\Platform\TargetPlatform;
use Webmozart\Assert\Assert;

use function array_map;
use function in_array;
use function strtolower;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class GithubPackageReleaseAssets implements PackageReleaseAssets
{
    /** @psalm-api */
    public function __construct(
        private readonly string $githubApiBaseUrl,
    ) {
    }

    /**
     * @param non-empty-list<non-empty-string> $possibleReleaseAssetNames
     *
     * @return non-empty-string
     */
    public function findMatchingReleaseAssetUrl(
        TargetPlatform $targetPlatform,
        Package $package,
        AuthHelper $authHelper,
        HttpDownloader $httpDownloader,
        array $possibleReleaseAssetNames,
    ): string {
        $releaseAsset = $this->selectMatchingReleaseAsset(
            $targetPlatform,
            $package,
            $this->getReleaseAssetsForPackage($package, $authHelper, $httpDownloader),
            $possibleReleaseAssetNames,
        );

        return $releaseAsset['browser_download_url'];
    }

    /** @link https://github.com/squizlabs/PHP_CodeSniffer/issues/3734 */
    // phpcs:disable Squiz.Commenting.FunctionComment.MissingParamName
    /**
     * @param list<array{name: non-empty-string, browser_download_url: non-empty-string, ...}> $releaseAssets
     * @param non-empty-list<non-empty-string> $possibleReleaseAssetNames
     *
     * @return array{name: non-empty-string, browser_download_url: non-empty-string, ...}
     */
    // phpcs:enable
    private function selectMatchingReleaseAsset(
        TargetPlatform $targetPlatform,
        Package $package,
        array $releaseAssets,
        array $possibleReleaseAssetNames,
    ): array {
        foreach ($releaseAssets as $releaseAsset) {
            if (in_array(strtolower($releaseAsset['name']), $possibleReleaseAssetNames, true)) {
                return $releaseAsset;
            }
        }

        throw Exception\CouldNotFindReleaseAsset::forPackage($targetPlatform, $package, $possibleReleaseAssetNames);
    }

    /** @return list<array{name: non-empty-string, browser_download_url: non-empty-string, ...}> */
    private function getReleaseAssetsForPackage(
        Package $package,
        AuthHelper $authHelper,
        HttpDownloader $httpDownloader,
    ): array {
        Assert::notNull($package->downloadUrl());

        try {
            $decodedRepsonse = $httpDownloader->get(
                $this->githubApiBaseUrl . '/repos/' . $package->githubOrgAndRepository() . '/releases/tags/' . $package->version(),
                [
                    'retry-auth-failure' => true,
                    'http' => [
                        'method' => 'GET',
                        'header' => $authHelper->addAuthenticationHeader([], $this->githubApiBaseUrl, $package->downloadUrl()),
                    ],
                ],
            )->decodeJson();
        } catch (TransportException $t) {
            /** @link https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#get-a-release-by-tag-name */
            if ($t->getStatusCode() === 404) {
                throw Exception\CouldNotFindReleaseAsset::forPackageWithMissingTag($package);
            }

            throw $t;
        }

        Assert::isArray($decodedRepsonse);
        Assert::keyExists($decodedRepsonse, 'assets');
        Assert::isList($decodedRepsonse['assets']);

        return array_map(
            static function (array $asset): array {
                Assert::keyExists($asset, 'name');
                Assert::stringNotEmpty($asset['name']);
                Assert::keyExists($asset, 'browser_download_url');
                Assert::stringNotEmpty($asset['browser_download_url']);

                return $asset;
            },
            $decodedRepsonse['assets'],
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie;

use Composer\Util\Platform as ComposerPlatform;
use Composer\Util\Silencer;
use Php\Pie\Platform\TargetPlatform;
use RuntimeException;

use function array_keys;
use function defined;
use function fopen;
use function implode;
use function md5;
use function rtrim;
use function strpos;
use function strtr;

use const DIRECTORY_SEPARATOR;
use const STDIN;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class Platform
{
    public static function isInteractive(): bool
    {
        $stdin            = defined('STDIN') ? STDIN : fopen('php://stdin', 'r');
        $noInteractionEnv = ComposerPlatform::getEnv('COMPOSER_NO_INTERACTION');

        return $noInteractionEnv !== false
            && $noInteractionEnv !== '1'
            && $stdin !== false
            && ComposerPlatform::isTty($stdin);
    }

    private static function useXdg(): bool
    {
        foreach (array_keys($_SERVER) as $key) {
            /** @psalm-suppress RedundantCastGivenDocblockType */
            if (strpos((string) $key, 'XDG_') === 0) {
                return true;
            }
        }

        return (bool) Silencer::call('is_dir', '/etc/xdg');
    }

    /** @throws RuntimeException */
    private static function getUserDir(): string
    {
        $home = ComposerPlatform::getEnv('HOME');
        if ($home === false || $home === '') {
            throw new RuntimeException('The HOME or PIE_WORKING_DIRECTORY environment variable must be set for PIE to run correctly');
        }

        return rtrim(strtr($home, '\\', '/'), '/');
    }

    /**
     * This is essentially a Composer-controlled `vendor` directory that has downloaded sources
     *
     * @throws RuntimeException
     */
    public static function getPieWorkingDirectory(TargetPlatform $targetPlatform): string
    {
        // Simple hash of the target platform so we can build against different PHP installs on the same system
        $targetPlatformPath = DIRECTORY_SEPARATOR . 'php' . $targetPlatform->phpBinaryPath->majorMinorVersion() . '_' . md5(implode(
            '|',
            [
                $targetPlatform->operatingSystem->name,
                $targetPlatform->phpBinaryPath->phpBinaryPath,
                $targetPlatform->phpBinaryPath->version(),
                $targetPlatform->architecture->name,
                $targetPlatform->threadSafety->name,
                $targetPlatform->windowsCompiler?->name ?? 'x',
            ],
        ));

        $home = ComposerPlatform::getEnv('PIE_WORKING_DIRECTORY');
        if ($home !== false && $home !== '') {
            return $home . $targetPlatformPath;
        }

        if (ComposerPlatform::isWindows()) {
            $appData = ComposerPlatform::getEnv('APPDATA');
            if ($appData === false || $appData === '') {
                throw new RuntimeException('The APPDATA or PIE_WORKING_DIRECTORY environment variable must be set for PIE to run correctly');
            }

            return rtrim(strtr($appData, '\\', '/'), '/') . '/PIE' . $targetPlatformPath . '/';
        }

        $userDir = self::getUserDir();
        $dirs    = [];

        if (self::useXdg()) {
            // XDG Base Directory Specifications
            $xdgConfig = ComposerPlatform::getEnv('XDG_CONFIG_HOME');
            if ($xdgConfig === false || $xdgConfig === '') {
                $xdgConfig = $userDir . '/.config';
            }

            $dirs[] = $xdgConfig . '/pie';
        }

        $dirs[] = $userDir . '/.pie';

        // select first dir which exists of: $XDG_CONFIG_HOME/pie or ~/.pie
        foreach ($dirs as $dir) {
            if (Silencer::call('is_dir', $dir)) {
                return $dir . $targetPlatformPath;
            }
        }

        // if none exists, we default to first defined one (XDG one if system uses it, or ~/.pie otherwise)
        return $dirs[0] . $targetPlatformPath;
    }

    /** @return non-empty-string */
    public static function getPieJsonFilename(TargetPlatform $targetPlatform): string
    {
        return self::getPieWorkingDirectory($targetPlatform) . '/pie.json';
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform;

use function array_map;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
enum OperatingSystemFamily: string
{
    case Windows = 'windows';
    case Bsd     = 'bsd';
    case Darwin  = 'darwin';
    case Solaris = 'solaris';
    case Linux   = 'linux';
    case Unknown = 'unknown';

    /** @return non-empty-list<non-empty-string> */
    public static function asValuesList(): array
    {
        return array_map(
            static fn (OperatingSystemFamily $osFamily): string => $osFamily->value,
            self::cases(),
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
enum ThreadSafetyMode
{
    case ThreadSafe;
    case NonThreadSafe;

    public function asShort(): string
    {
        return match ($this) {
            self::ThreadSafe => 'ts',
            self::NonThreadSafe => 'nts',
        };
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform\TargetPhp\Exception;

use Php\Pie\ExtensionName;
use Php\Pie\Platform\TargetPhp\PhpBinaryPath;
use RuntimeException;

use function sprintf;

class ExtensionIsNotLoaded extends RuntimeException
{
    public static function fromExpectedExtension(PhpBinaryPath $php, ExtensionName $extension): self
    {
        return new self(sprintf(
            'Expected extension %s to be loaded in PHP %s, but it was not detected.',
            $extension->name(),
            $php->phpBinaryPath,
        ));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform\TargetPhp\Exception;

use RuntimeException;

use function sprintf;

class InvalidPhpBinaryPath extends RuntimeException
{
    public static function fromNonExistentPhpBinary(string $phpBinaryPath): self
    {
        return new self(sprintf(
            'The php binary at "%s" does not exist',
            $phpBinaryPath,
        ));
    }

    public static function fromNonExecutablePhpBinary(string $phpBinaryPath): self
    {
        return new self(sprintf(
            'The php binary at "%s" is not executable',
            $phpBinaryPath,
        ));
    }

    public static function fromInvalidPhpBinary(string $phpBinaryPath): self
    {
        return new self(sprintf(
            'The php binary at "%s" does not appear to be a PHP binary',
            $phpBinaryPath,
        ));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform\TargetPhp;

use RuntimeException;
use Symfony\Component\Process\Process;

use function array_key_exists;
use function assert;
use function file_exists;
use function is_executable;
use function preg_match;
use function preg_replace;
use function trim;

/**
 * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
 *
 * @immutable
 */
final class PhpizePath
{
    /** @param non-empty-string $phpizeBinaryPath */
    public function __construct(public readonly string $phpizeBinaryPath)
    {
    }

    public static function guessFrom(PhpBinaryPath $phpBinaryPath): self
    {
        $expectedApiVersion = $phpBinaryPath->phpApiVersion();

        $phpizeAttempts = [];

        // Try to add `phpize` from path
        $whichPhpize = new Process(['which', 'phpize']);
        if ($whichPhpize->run() === 0) {
            $phpizeAttempts[] = trim($whichPhpize->getOutput());
        }

        // Try to guess based on the `php` path itself
        $phpizeAttempts[] = preg_replace('((.*)php)', '$1phpize', $phpBinaryPath->phpBinaryPath);

        foreach ($phpizeAttempts as $phpizeAttempt) {
            assert($phpizeAttempt !== null);
            assert($phpizeAttempt !== '');
            if (! file_exists($phpizeAttempt) || ! is_executable($phpizeAttempt)) {
                continue;
            }

            $phpizeProcess = new Process([$phpizeAttempt, '--version']);
            if ($phpizeProcess->run() !== 0) {
                continue;
            }

            if (
                ! preg_match('/PHP Api Version:\s*(.*)/', $phpizeProcess->getOutput(), $m)
                || ! array_key_exists(1, $m)
                || $m[1] === ''
            ) {
                continue;
            }

            if ($expectedApiVersion === $m[1]) {
                return new self($phpizeAttempt);
            }
        }

        throw new RuntimeException('Could not find a suitable `phpize` binary, you may provide one using the "--with-phpize-path" option.');
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform\TargetPhp;

use Composer\Semver\VersionParser;
use Composer\Util\Platform;
use Php\Pie\ExtensionName;
use Php\Pie\Platform\Architecture;
use Php\Pie\Platform\OperatingSystem;
use Php\Pie\Platform\OperatingSystemFamily;
use Php\Pie\Util\Process;
use RuntimeException;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\PhpExecutableFinder;
use Webmozart\Assert\Assert;

use function array_combine;
use function array_filter;
use function array_key_exists;
use function array_keys;
use function array_map;
use function assert;
use function dirname;
use function explode;
use function file_exists;
use function implode;
use function in_array;
use function is_dir;
use function is_executable;
use function mkdir;
use function preg_match;
use function sprintf;
use function strtolower;
use function trim;

use const DIRECTORY_SEPARATOR;

/**
 * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
 *
 * @immutable
 */
class PhpBinaryPath
{
    /**
     * @param non-empty-string      $phpBinaryPath
     * @param non-empty-string|null $phpConfigPath
     */
    private function __construct(
        public readonly string $phpBinaryPath,
        private readonly string|null $phpConfigPath,
    ) {
    }

    /** @param non-empty-string $phpBinaryPath */
    private static function assertValidLookingPhpBinary(string $phpBinaryPath): void
    {
        if (! file_exists($phpBinaryPath)) {
            throw Exception\InvalidPhpBinaryPath::fromNonExistentPhpBinary($phpBinaryPath);
        }

        if (! Platform::isWindows() && ! is_executable($phpBinaryPath)) {
            throw Exception\InvalidPhpBinaryPath::fromNonExecutablePhpBinary($phpBinaryPath);
        }

        // This is somewhat of a rudimentary check that the target PHP really is a PHP instance; not sure why you
        // WOULDN'T want to use a real PHP, but this should stop obvious hiccups at least (rather than for security)
        $testOutput = self::cleanWarningAndDeprecationsFromOutput(Process::run([$phpBinaryPath, '-r', 'echo "PHP";']));

        if ($testOutput !== 'PHP') {
            throw Exception\InvalidPhpBinaryPath::fromInvalidPhpBinary($phpBinaryPath);
        }
    }

    /** @return non-empty-string */
    public function phpApiVersion(): string
    {
        if (
            preg_match('/PHP API([ =>\t]*)(.*)/', $this->phpinfo(), $m)
            && array_key_exists(2, $m)
            && $m[2] !== ''
        ) {
            return $m[2];
        }

        throw new RuntimeException('Failed to find PHP API version...');
    }

    /** @return non-empty-string */
    public function extensionPath(): string
    {
        $phpinfo = $this->phpinfo();

        if (
            preg_match('#^extension_dir\s+=>\s+([^=]+)\s+=>\s+([^=]+)$#m', $phpinfo, $matches)
            && array_key_exists(1, $matches)
            && trim($matches[1]) !== ''
            && trim($matches[1]) !== 'no value'
        ) {
            $extensionPath = trim($matches[1]);
            assert($extensionPath !== '');

            if (file_exists($extensionPath) && is_dir($extensionPath)) {
                return $extensionPath;
            }

            // if the path is absolute, try to create it
            if (mkdir($extensionPath, 0777, true) && file_exists($extensionPath) && is_dir($extensionPath)) {
                return $extensionPath;
            }

            // `extension_dir` may be a relative URL on Windows, so resolve it according to the location of PHP
            $phpPath              = dirname($this->phpBinaryPath);
            $attemptExtensionPath = $phpPath . DIRECTORY_SEPARATOR . $extensionPath;

            if (file_exists($attemptExtensionPath) && is_dir($attemptExtensionPath)) {
                return $attemptExtensionPath;
            }
        }

        throw new RuntimeException('Could not determine extension path for ' . $this->phpBinaryPath);
    }

    public function assertExtensionIsLoadedInRuntime(ExtensionName $extension, OutputInterface|null $output = null): void
    {
        if (! in_array(strtolower($extension->name()), array_map('strtolower', array_keys($this->extensions())))) {
            throw Exception\ExtensionIsNotLoaded::fromExpectedExtension(
                $this,
                $extension,
            );
        }

        if ($output === null) {
            return;
        }

        $output->writeln(
            sprintf(
                'Successfully asserted that extension %s is loaded in runtime.',
                $extension->name(),
            ),
            OutputInterface::VERBOSITY_VERBOSE,
        );
    }

    /** @return non-empty-string|null */
    public function additionalIniDirectory(): string|null
    {
        if (
            preg_match('/Scan this dir for additional \.ini files([ =>\t]*)(.*)/', $this->phpinfo(), $m)
            && array_key_exists(2, $m)
            && $m[2] !== ''
            && $m[2] !== '(none)'
        ) {
            return $m[2];
        }

        return null;
    }

    /** @return non-empty-string|null */
    public function loadedIniConfigurationFile(): string|null
    {
        if (
            preg_match('/Loaded Configuration File([ =>\t]*)(.*)/', $this->phpinfo(), $m)
            && array_key_exists(2, $m)
            && $m[2] !== ''
            && $m[2] !== '(none)'
        ) {
            return $m[2];
        }

        return null;
    }

    /** @return non-empty-string|null */
    public function buildProvider(): string|null
    {
        /**
         * Newer versions of PHP will have a `PHP_BUILD_PROVIDER` constant
         * defined - {@link https://github.com/php/php-src/pull/19157}
         */
        if (
            preg_match('/Build Provider([ =>\t]*)(.*)/', $this->phpinfo(), $m)
            && array_key_exists(2, $m)
            && $m[2] !== ''
            && $m[2] !== '(none)'
        ) {
            return $m[2];
        }

        return null;
    }

    /**
     * Returns a map where the key is the name of the extension and the value is the version ('0' if not defined)
     *
     * @return array<string, string>
     */
    public function extensions(): array
    {
        $extVersionsList = self::cleanWarningAndDeprecationsFromOutput(Process::run([
            $this->phpBinaryPath,
            '-r',
            <<<'PHP'
$exts = get_loaded_extensions();
$extVersions = array_map(
    static function ($extension) {
        $extVersion = phpversion($extension);
        if ($extVersion === false) {
            return '0';
        }
        return $extVersion;
    },
    $exts
);
echo implode("\n", array_map(
    static function ($k, $v) {
        return sprintf('%s:%s', $k, $v);
    },
    $exts,
    $extVersions
));
PHP,
        ]));

        $pairs = array_map(
            static fn (string $row) => explode(':', $row),
            explode("\n", $extVersionsList),
        );

        return array_combine(
            array_map(static fn (array $row) => $row[0], $pairs),
            array_map(static fn (array $row) => $row[1], $pairs),
        );
    }

    public function operatingSystem(): OperatingSystem
    {
        $winOrNot = self::cleanWarningAndDeprecationsFromOutput(Process::run([
            $this->phpBinaryPath,
            '-r',
            'echo \\defined(\'PHP_WINDOWS_VERSION_BUILD\') ? \'win\' : \'not\';',
        ]));
        Assert::stringNotEmpty($winOrNot, 'Could not determine PHP version');

        return $winOrNot === 'win' ? OperatingSystem::Windows : OperatingSystem::NonWindows;
    }

    public function operatingSystemFamily(): OperatingSystemFamily
    {
        $output = self::cleanWarningAndDeprecationsFromOutput(Process::run([
            $this->phpBinaryPath,
            '-r',
            'echo PHP_OS_FAMILY;',
        ]));

        $osFamily = OperatingSystemFamily::tryFrom(strtolower(trim($output)));
        Assert::notNull($osFamily, 'Could not determine operating system family');

        return $osFamily;
    }

    /** @return non-empty-string */
    public function version(): string
    {
        $phpVersion = self::cleanWarningAndDeprecationsFromOutput(Process::run([
            $this->phpBinaryPath,
            '-r',
            'echo PHP_VERSION;',
        ]));
        Assert::stringNotEmpty($phpVersion, 'Could not determine PHP version');

        // normalizing the version will throw an exception if it is not a valid version
        (new VersionParser())->normalize($phpVersion);

        return $phpVersion;
    }

    /** @return non-empty-string */
    public function majorMinorVersion(): string
    {
        $phpVersion = self::cleanWarningAndDeprecationsFromOutput(Process::run([
            $this->phpBinaryPath,
            '-r',
            'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;',
        ]));
        Assert::stringNotEmpty($phpVersion, 'Could not determine PHP version');

        // normalizing the version will throw an exception if it is not a valid version
        (new VersionParser())->normalize($phpVersion);

        return $phpVersion;
    }

    public function machineType(): Architecture
    {
        $phpMachineType = self::cleanWarningAndDeprecationsFromOutput(Process::run([
            $this->phpBinaryPath,
            '-r',
            'echo php_uname("m");',
        ]));
        Assert::stringNotEmpty($phpMachineType, 'Could not determine PHP machine type');

        return Architecture::parseArchitecture($phpMachineType);
    }

    public function phpIntSize(): int
    {
        $phpIntSize = self::cleanWarningAndDeprecationsFromOutput(Process::run([
            $this->phpBinaryPath,
            '-r',
            'echo PHP_INT_SIZE;',
        ]));
        Assert::stringNotEmpty($phpIntSize, 'Could not fetch PHP_INT_SIZE');
        Assert::same($phpIntSize, (string) (int) $phpIntSize, 'PHP_INT_SIZE was not an integer processed %2$s from %s');

        return (int) $phpIntSize;
    }

    /** @return non-empty-string */
    public function phpinfo(): string
    {
        $phpInfo = self::cleanWarningAndDeprecationsFromOutput(Process::run([
            $this->phpBinaryPath,
            '-i',
        ]));

        Assert::stringNotEmpty($phpInfo, sprintf('Could not run phpinfo using %s', $this->phpBinaryPath));

        return $phpInfo;
    }

    /**
     * This will only be set if {@see self::fromPhpConfigExecutable()} is used to create this {@see self}, otherwise
     * will return `null`.
     *
     * @return non-empty-string|null
     */
    public function phpConfigPath(): string|null
    {
        return $this->phpConfigPath;
    }

    /** @param non-empty-string $phpConfig */
    public static function fromPhpConfigExecutable(string $phpConfig): self
    {
        $phpExecutable = self::cleanWarningAndDeprecationsFromOutput(Process::run([$phpConfig, '--php-binary']));
        Assert::stringNotEmpty($phpExecutable, 'Could not find path to PHP executable.');

        self::assertValidLookingPhpBinary($phpExecutable);

        return new self($phpExecutable, $phpConfig);
    }

    /** @param non-empty-string $phpBinary */
    public static function fromPhpBinaryPath(string $phpBinary): self
    {
        self::assertValidLookingPhpBinary($phpBinary);

        return new self($phpBinary, null);
    }

    public static function fromCurrentProcess(): self
    {
        $phpExecutable = trim((string) (new PhpExecutableFinder())->find());
        Assert::stringNotEmpty($phpExecutable, 'Could not find path to PHP executable.');

        self::assertValidLookingPhpBinary($phpExecutable);

        return new self($phpExecutable, null);
    }

    private static function cleanWarningAndDeprecationsFromOutput(string $testOutput): string
    {
        // Note: xdebug can prefix `PHP ` onto warnings/deprecations, so filter them out too
        return implode(
            "\n",
            array_filter(
                explode("\n", $testOutput),
                static fn (string $line) => ! preg_match('/^(Deprecated|Warning|PHP Warning|PHP Deprecated):/', $line),
            ),
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
enum OperatingSystem
{
    case NonWindows;
    case Windows;

    public function asFriendlyName(): string
    {
        return match ($this) {
            self::Windows => 'Windows',
            self::NonWindows => 'Linux/OSX/etc',
        };
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform;

use Php\Pie\DependencyResolver\Package;
use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\Downloading\Exception\CouldNotFindReleaseAsset;
use RuntimeException;

use function file_exists;
use function implode;
use function sprintf;
use function strtolower;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class WindowsExtensionAssetName
{
    /** @psalm-suppress UnusedConstructor */
    private function __construct()
    {
    }

    /** @return non-empty-list<non-empty-string> */
    private static function assetNames(TargetPlatform $targetPlatform, Package $package, string $fileExtension): array
    {
        if ($targetPlatform->operatingSystem !== OperatingSystem::Windows || $targetPlatform->windowsCompiler === null) {
            throw CouldNotFindReleaseAsset::forMissingWindowsCompiler($targetPlatform);
        }

        /**
         * During development, we swapped compiler/ts around. It is fairly trivial to support both, so we can check
         * both formats pretty easily, just to avoid confusion for package maintainers...
         */
        return [
            strtolower(sprintf(
                'php_%s-%s-%s-%s-%s-%s.%s',
                $package->extensionName()->name(),
                $package->version(),
                $targetPlatform->phpBinaryPath->majorMinorVersion(),
                $targetPlatform->threadSafety->asShort(),
                strtolower($targetPlatform->windowsCompiler->name),
                $targetPlatform->architecture->name,
                $fileExtension,
            )),
            strtolower(sprintf(
                'php_%s-%s-%s-%s-%s-%s.%s',
                $package->extensionName()->name(),
                $package->version(),
                $targetPlatform->phpBinaryPath->majorMinorVersion(),
                strtolower($targetPlatform->windowsCompiler->name),
                $targetPlatform->threadSafety->asShort(),
                $targetPlatform->architecture->name,
                $fileExtension,
            )),
        ];
    }

    /** @return non-empty-list<non-empty-string> */
    public static function zipNames(TargetPlatform $targetPlatform, Package $package): array
    {
        return self::assetNames($targetPlatform, $package, 'zip');
    }

    /** @return non-empty-list<non-empty-string> */
    public static function dllNames(TargetPlatform $targetPlatform, Package $package): array
    {
        return self::assetNames($targetPlatform, $package, 'dll');
    }

    /** @return non-empty-string */
    public static function determineDllName(TargetPlatform $targetPlatform, DownloadedPackage $package): string
    {
        $possibleDllNames = self::dllNames($targetPlatform, $package->package);
        foreach ($possibleDllNames as $dllName) {
            $fullDllName = $package->extractedSourcePath . '/' . $dllName;
            if (file_exists($fullDllName)) {
                return $fullDllName;
            }
        }

        throw new RuntimeException('Unable to find DLL for package, checked: ' . implode(', ', $possibleDllNames));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
enum Architecture
{
    case x86;
    case x86_64;
    case arm64;

    /**
     * Architecture naming is inconsistent across various platforms, so try to unify the various labels...
     *
     * @param non-empty-string $architecture
     */
    public static function parseArchitecture(string $architecture): self
    {
        return match ($architecture) {
            'x64', 'x86_64', 'AMD64' => self::x86_64,
            'arm64' => self::arm64,
            default => self::x86,
        };
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
enum WindowsCompiler
{
    case VC6;
    case VC8;
    case VC9;
    case VC11;
    case VC14;
    case VC15;
    case VS16;
    case VS17;
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform;

use Fidry\CpuCoreCounter\CpuCoreCounter;
use Php\Pie\Platform\TargetPhp\PhpBinaryPath;

use function array_key_exists;
use function explode;
use function function_exists;
use function posix_getuid;
use function preg_match;
use function trim;

/**
 * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
 *
 * @immutable
 */
class TargetPlatform
{
    public function __construct(
        public readonly OperatingSystem $operatingSystem,
        public readonly OperatingSystemFamily $operatingSystemFamily,
        public readonly PhpBinaryPath $phpBinaryPath,
        public readonly Architecture $architecture,
        public readonly ThreadSafetyMode $threadSafety,
        public readonly int $makeParallelJobs,
        public readonly WindowsCompiler|null $windowsCompiler,
    ) {
    }

    public static function isRunningAsRoot(): bool
    {
        return function_exists('posix_getuid') && posix_getuid() === 0;
    }

    public static function fromPhpBinaryPath(PhpBinaryPath $phpBinaryPath, int|null $makeParallelJobs): self
    {
        $os       = $phpBinaryPath->operatingSystem();
        $osFamily = $phpBinaryPath->operatingSystemFamily();

        $phpinfo = $phpBinaryPath->phpinfo();

        $architecture = $phpBinaryPath->machineType();

        // If we're not on ARM, a more reliable way of determining 32-bit/64-bit is to use PHP_INT_SIZE
        if ($architecture !== Architecture::arm64) {
            $architecture = $phpBinaryPath->phpIntSize() === 4 ? Architecture::x86 : Architecture::x86_64;
        }

        /**
         * Based on xdebug.org wizard, copyright Derick Rethans, used under MIT licence
         *
         * @link https://github.com/xdebug/xdebug.org/blob/aff649f2c3ca303ad471e6ed9dd29c0db16d3e22/src/XdebugVersion.php#L186-L190
         */
        if (
            preg_match('/Architecture([ =>\t]*)(x[0-9]*)/', $phpinfo, $m)
            && array_key_exists(2, $m)
            && $m[2] !== ''
        ) {
            $architecture = Architecture::parseArchitecture($m[2]);
        }

        $windowsCompiler = null;
        $threadSafety    = ThreadSafetyMode::ThreadSafe;

        /**
         * Based on xdebug.org wizard, copyright Derick Rethans, used under MIT licence
         *
         * @link https://github.com/xdebug/xdebug.org/blob/aff649f2c3ca303ad471e6ed9dd29c0db16d3e22/src/XdebugVersion.php#L276-L299
         */
        if (preg_match('/PHP Extension Build([ =>\t]+)(API.*)/', $phpinfo, $m)) {
            $parts = explode(',', trim($m[2]));
            foreach ($parts as $part) {
                switch ($part) {
                    case 'NTS':
                        $threadSafety = ThreadSafetyMode::NonThreadSafe;
                        break;
                    case 'TS':
                        $threadSafety = ThreadSafetyMode::ThreadSafe;
                        break;
                    case 'VC6':
                        $windowsCompiler = WindowsCompiler::VC6;
                        break;
                    case 'VC8':
                        $windowsCompiler = WindowsCompiler::VC8;
                        break;
                    case 'VC9':
                        $windowsCompiler = WindowsCompiler::VC9;
                        break;
                    case 'VC11':
                        $windowsCompiler = WindowsCompiler::VC11;
                        break;
                    case 'VC14':
                        $windowsCompiler = WindowsCompiler::VC14;
                        break;
                    case 'VC15':
                        $windowsCompiler = WindowsCompiler::VC15;
                        break;
                    case 'VS16':
                        $windowsCompiler = WindowsCompiler::VS16;
                        break;
                    case 'VS17':
                        $windowsCompiler = WindowsCompiler::VS17;
                        break;
                }
            }
        }

        if ($makeParallelJobs === null) {
            $makeParallelJobs = (new CpuCoreCounter())->getAvailableForParallelisation()->availableCpus;
        }

        return new self(
            $os,
            $osFamily,
            $phpBinaryPath,
            $architecture,
            $threadSafety,
            $makeParallelJobs,
            $windowsCompiler,
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform;

use Php\Pie\DependencyResolver\Package;

use function sprintf;
use function strtolower;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class PrePackagedSourceAssetName
{
    /** @psalm-suppress UnusedConstructor */
    private function __construct()
    {
    }

    /** @return non-empty-list<non-empty-string> */
    public static function packageNames(Package $package): array
    {
        return [
            // Ideal format for new PIE packages
            strtolower(sprintf(
                'php_%s-%s-src.tgz',
                $package->extensionName()->name(),
                $package->version(),
            )),
            strtolower(sprintf(
                'php_%s-%s-src.zip',
                $package->extensionName()->name(),
                $package->version(),
            )),
            // Old PEAR/PECL convention
            strtolower(sprintf(
                '%s-%s.tgz',
                $package->extensionName()->name(),
                $package->version(),
            )),
        ];
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\Platform;

use Composer\Composer;
use Composer\Package\BasePackage;
use Composer\Package\CompletePackageInterface;
use Php\Pie\DependencyResolver\Package;

use function array_combine;
use function array_filter;
use function array_map;

/**
 * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
 *
 * @psalm-type ListOfPiePackages = array<non-empty-string, Package>
 */
class InstalledPiePackages
{
    /**
     * Returns a list of PIE packages according to PIE; this does NOT check if
     * the extension is actually enabled in the target PHP.
     *
     * @return ListOfPiePackages
     */
    public function allPiePackages(Composer $composer): array
    {
        $composerInstalledPackages = array_map(
            static function (CompletePackageInterface $package): Package {
                return Package::fromComposerCompletePackage($package);
            },
            array_filter(
                $composer
                    ->getRepositoryManager()
                    ->getLocalRepository()
                    ->getPackages(),
                static function (BasePackage $basePackage): bool {
                    return $basePackage instanceof CompletePackageInterface;
                },
            ),
        );

        return array_combine(
            array_map(
            /** @return non-empty-string */
                static function (Package $package): string {
                    return match ($package->extensionName()->name()) {
                        'ffi' => 'FFI',
                        'opcache' => 'Zend OPcache',
                        'simplexml' => 'SimpleXML',
                        default => $package->extensionName()->name(),
                    };
                },
                $composerInstalledPackages,
            ),
            $composerInstalledPackages,
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Composer\Composer;
use Composer\Package\CompletePackage;
use Composer\Pcre\Preg;
use Composer\Repository\PlatformRepository;
use Composer\Semver\VersionParser;
use Php\Pie\ExtensionName;
use Php\Pie\Platform\InstalledPiePackages;
use Php\Pie\Platform\TargetPhp\PhpBinaryPath;
use UnexpectedValueException;

use function in_array;
use function str_replace;
use function str_starts_with;
use function strlen;
use function strtolower;
use function substr;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class PhpBinaryPathBasedPlatformRepository extends PlatformRepository
{
    private VersionParser $versionParser;

    public function __construct(PhpBinaryPath $phpBinaryPath, Composer $composer, InstalledPiePackages $installedPiePackages, ExtensionName|null $extensionBeingInstalled)
    {
        $this->versionParser = new VersionParser();
        $this->packages      = [];

        $phpVersion = $phpBinaryPath->version();
        $php        = new CompletePackage('php', $this->versionParser->normalize($phpVersion), $phpVersion);
        $php->setDescription('The PHP interpreter');
        $this->addPackage($php);

        $extVersions = $phpBinaryPath->extensions();

        $piePackages                          = $installedPiePackages->allPiePackages($composer);
        $extensionsBeingReplacedByPiePackages = [];
        foreach ($piePackages as $piePackage) {
            foreach ($piePackage->composerPackage()->getReplaces() as $replaceLink) {
                $target = $replaceLink->getTarget();
                if (
                    ! str_starts_with($target, 'ext-')
                    || ! ExtensionName::isValidExtensionName(substr($target, strlen('ext-')))
                ) {
                    continue;
                }

                $extensionsBeingReplacedByPiePackages[] = ExtensionName::normaliseFromString($replaceLink->getTarget())->name();
            }
        }

        foreach ($extVersions as $extension => $extensionVersion) {
            /**
             * If the extension we're trying to exclude is not excluded from this list if it is already installed
             * and enabled, it conflicts when running {@see ComposerIntegrationHandler}.
             *
             * @link https://github.com/php/pie/issues/150
             */
            if ($extensionBeingInstalled !== null && $extension === $extensionBeingInstalled->name()) {
                continue;
            }

            /**
             * If any extensions present have `replaces`, we need to remove them otherwise it conflicts too
             *
             * @link https://github.com/php/pie/issues/161
             */
            if (in_array($extension, $extensionsBeingReplacedByPiePackages)) {
                continue;
            }

            $this->addPackage($this->packageForExtension($extension, $extensionVersion));
        }

        parent::__construct();
    }

    private function packageForExtension(string $name, string $prettyVersion): CompletePackage
    {
        $extraDescription = '';

        try {
            $version = $this->versionParser->normalize($prettyVersion);
        } catch (UnexpectedValueException) {
            $extraDescription = ' (actual version: ' . $prettyVersion . ')';
            if (Preg::isMatchStrictGroups('{^(\d+\.\d+\.\d+(?:\.\d+)?)}', $prettyVersion, $match)) {
                $prettyVersion = $match[1];
            } else {
                $prettyVersion = '0';
            }

            $version = $this->versionParser->normalize($prettyVersion);
        }

        $package = new CompletePackage(
            'ext-' . str_replace(' ', '-', strtolower($name)),
            $version,
            $prettyVersion,
        );
        $package->setDescription('The ' . $name . ' PHP extension' . $extraDescription);
        $package->setType('php-ext');

        return $package;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Closure;
use Composer\IO\ConsoleIO;
use Composer\IO\IOInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

use function array_merge;
use function assert;
use function is_array;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class QuieterConsoleIO extends ConsoleIO
{
    /** @var string[] */
    public array $errors = [];

    public function __construct(InputInterface $input, OutputInterface $output, MinimalHelperSet $helperSet)
    {
        parent::__construct($input, $output, $helperSet);

        /**
         * Shifts Composer's normal output level by one, e.g. what Composer
         * normally outputs, only output with `-v`
         */
        $this->overrideVerbosityMap([
            self::QUIET => OutputInterface::VERBOSITY_NORMAL,
            self::NORMAL => OutputInterface::VERBOSITY_VERBOSE,
            self::VERBOSE => OutputInterface::VERBOSITY_VERY_VERBOSE,
            self::VERY_VERBOSE => OutputInterface::VERBOSITY_DEBUG,
            self::DEBUG => OutputInterface::VERBOSITY_DEBUG,
        ]);
    }

    /** @param array<IOInterface::*, OutputInterface::VERBOSITY_*> $newVerbosityMap */
    private function overrideVerbosityMap(array $newVerbosityMap): void
    {
        $overrideFunction = Closure::bind(
            function (ConsoleIO $consoleIO) use ($newVerbosityMap): void {
                /** @psalm-suppress InaccessibleProperty */
                $consoleIO->verbosityMap = $newVerbosityMap;
            },
            null,
            ConsoleIO::class,
        );
        assert($overrideFunction !== null);
        $overrideFunction($this);
    }

    /** {@inheritDoc} */
    public function writeError($messages, bool $newline = true, int $verbosity = self::NORMAL): void
    {
        if ($verbosity <= self::NORMAL) {
            if (is_array($messages)) {
                $this->errors = array_merge($this->errors, $messages);

                parent::writeError($messages, $newline, $verbosity);

                return;
            }

            $this->errors[] = $messages;
        }

        parent::writeError($messages, $newline, $verbosity);
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Composer\Package\CompletePackageInterface;
use Composer\PartialComposer;
use Php\Pie\Building\Build;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\Installing\Install;

use function sprintf;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class InstallAndBuildProcess
{
    /** @psalm-suppress PossiblyUnusedMethod no direct reference; used in service locator */
    public function __construct(
        private readonly Build $pieBuild,
        private readonly Install $pieInstall,
        private readonly InstalledJsonMetadata $installedJsonMetadata,
    ) {
    }

    public function __invoke(
        PartialComposer $composer,
        PieComposerRequest $composerRequest,
        CompletePackageInterface $composerPackage,
        string $installPath,
    ): void {
        $output = $composerRequest->pieOutput;

        $downloadedPackage = DownloadedPackage::fromPackageAndExtractedPath(
            Package::fromComposerCompletePackage($composerPackage),
            $installPath,
        );

        $output->writeln(sprintf(
            '<info>Extracted %s source to:</info> %s',
            $downloadedPackage->package->prettyNameAndVersion(),
            $downloadedPackage->extractedSourcePath,
        ));

        $this->installedJsonMetadata->addDownloadMetadata(
            $composer,
            $composerRequest,
            $composerPackage,
        );

        if ($composerRequest->operation->shouldBuild()) {
            $builtBinaryFile = ($this->pieBuild)(
                $downloadedPackage,
                $composerRequest->targetPlatform,
                $composerRequest->configureOptions,
                $output,
                $composerRequest->phpizePath,
            );

            $this->installedJsonMetadata->addBuildMetadata(
                $composer,
                $composerRequest,
                $composerPackage,
                $builtBinaryFile,
            );
        }

        if (! $composerRequest->operation->shouldInstall()) {
            return;
        }

        $this->installedJsonMetadata->addInstallMetadata(
            $composer,
            $composerPackage,
            ($this->pieInstall)(
                $downloadedPackage,
                $composerRequest->targetPlatform,
                $output,
                $composerRequest->attemptToSetupIniFile,
            ),
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Php\Pie\DependencyResolver\RequestedPackageAndVersion;
use Php\Pie\Platform\TargetPhp\PhpizePath;
use Php\Pie\Platform\TargetPlatform;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
 *
 * @immutable
 */
final class PieComposerRequest
{
    /** @param list<non-empty-string> $configureOptions */
    public function __construct(
        public readonly OutputInterface $pieOutput,
        public readonly TargetPlatform $targetPlatform,
        public readonly RequestedPackageAndVersion $requestedPackage,
        public readonly PieOperation $operation,
        public readonly array $configureOptions,
        public readonly PhpizePath|null $phpizePath,
        public readonly bool $attemptToSetupIniFile,
    ) {
    }

    /**
     * Useful for when we don't want to perform any "write" style operations;
     * for example just reading metadata about the installed system.
     */
    public static function noOperation(
        OutputInterface $pieOutput,
        TargetPlatform $targetPlatform,
    ): self {
        return new PieComposerRequest(
            $pieOutput,
            $targetPlatform,
            new RequestedPackageAndVersion('null/null', null),
            PieOperation::Resolve,
            [],
            null,
            false,
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Composer\Config\JsonConfigSource;
use Composer\Json\JsonFile;
use Php\Pie\Platform;
use Php\Pie\Platform\TargetPlatform;
use RuntimeException;

use function file_exists;
use function file_get_contents;
use function file_put_contents;
use function mkdir;
use function rtrim;
use function sprintf;
use function str_replace;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class PieJsonEditor
{
    public const PACKAGIST_ORG_KEY = 'packagist.org';

    public function __construct(
        private readonly string $pieJsonFilename,
        private readonly string $pieWorkingDirectory,
    ) {
    }

    public static function fromTargetPlatform(TargetPlatform $targetPlatform): self
    {
        return new self(
            Platform::getPieJsonFilename($targetPlatform),
            Platform::getPieWorkingDirectory($targetPlatform),
        );
    }

    /**
     * Ensure the given `pie.json` exists; if it does not exist, create an
     * empty but valid `pie.json`.
     */
    public function ensureExists(): self
    {
        if (file_exists($this->pieJsonFilename)) {
            return $this;
        }

        if (! file_exists($this->pieWorkingDirectory)) {
            mkdir($this->pieWorkingDirectory, recursive: true);
        }

        if (file_put_contents($this->pieJsonFilename, "{\n}\n") === false) {
            throw new RuntimeException(sprintf(
                'Failed to create pie.json in %s (working directory: %s)',
                $this->pieJsonFilename,
                $this->pieWorkingDirectory,
            ));
        }

        return $this;
    }

    /**
     * Add a package to the `require` section of the given `pie.json`. Returns
     * the original `pie.json` content, in case it needs to be restored later.
     *
     * @param non-empty-string $package
     * @param non-empty-string $version
     */
    public function addRequire(string $package, string $version): string
    {
        $originalPieJsonContent = file_get_contents($this->pieJsonFilename);

        (new JsonConfigSource(
            new JsonFile(
                $this->pieJsonFilename,
            ),
        ))->addLink('require', $package, $version);

        return $originalPieJsonContent;
    }

    /**
     * Remove a package from the `require` section of the given `pie.json`.
     * Returns the original `pie.json` content, in case it needs to be
     * restored later.
     *
     * @param non-empty-string $package
     */
    public function removeRequire(string $package): string
    {
        $originalPieJsonContent = file_get_contents($this->pieJsonFilename);

        (new JsonConfigSource(
            new JsonFile(
                $this->pieJsonFilename,
            ),
        ))->removeLink('require', $package);

        return $originalPieJsonContent;
    }

    public function revert(string $originalPieJsonContent): void
    {
        file_put_contents($this->pieJsonFilename, $originalPieJsonContent);
    }

    public function excludePackagistOrg(): string
    {
        $originalPieJsonContent = file_get_contents($this->pieJsonFilename);

        (new JsonConfigSource(
            new JsonFile(
                $this->pieJsonFilename,
            ),
        ))
            ->addRepository(self::PACKAGIST_ORG_KEY, false);

        return $originalPieJsonContent;
    }

    /**
     * Add a repository to the given `pie.json`. Returns the original
     * `pie.json` content, in case it needs to be restored later.
     *
     * @param 'vcs'|'path'|'composer' $type
     * @param non-empty-string        $url
     */
    public function addRepository(
        string $type,
        string $url,
    ): string {
        $originalPieJsonContent = file_get_contents($this->pieJsonFilename);

        (new JsonConfigSource(
            new JsonFile(
                $this->pieJsonFilename,
            ),
        ))
            ->addRepository($this->normaliseRepositoryName($url), [
                'type' => $type,
                'url' => $url,
            ]);

        return $originalPieJsonContent;
    }

    /**
     * Remove a repository from the given `pie.json`. Returns the original
     * `pie.json` content, in case it needs to be restored later.
     *
     * @param non-empty-string $name
     */
    public function removeRepository(
        string $name,
    ): string {
        $originalPieJsonContent = file_get_contents($this->pieJsonFilename);

        (new JsonConfigSource(
            new JsonFile(
                $this->pieJsonFilename,
            ),
        ))
            ->removeRepository($this->normaliseRepositoryName($name));

        return $originalPieJsonContent;
    }

    private function normaliseRepositoryName(string $url): string
    {
        return rtrim(str_replace('\\', '/', $url), '/');
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Composer\Package\CompletePackageInterface;

use function array_column;
use function array_key_exists;
use function is_string;

/**
 * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
 *
 * @psalm-type PieMetadata = array{
 *     pie-target-platform-php-path?: non-empty-string,
 *     pie-target-platform-php-config-path?: non-empty-string,
 *     pie-target-platform-php-version?: non-empty-string,
 *     pie-target-platform-php-thread-safety?: non-empty-string,
 *     pie-target-platform-php-windows-compiler?: non-empty-string,
 *     pie-target-platform-architecture?: non-empty-string,
 *     pie-configure-options?: non-empty-string,
 *     pie-built-binary?: non-empty-string,
 *     pie-installed-binary-checksum?: non-empty-string,
 *     pie-installed-binary?: non-empty-string,
 *     pie-phpize-binary?: non-empty-string,
 * }
 */
enum PieInstalledJsonMetadataKeys: string
{
    case TargetPlatformPhpPath            = 'pie-target-platform-php-path';
    case TargetPlatformPhpConfigPath      = 'pie-target-platform-php-config-path';
    case TargetPlatformPhpVersion         = 'pie-target-platform-php-version';
    case TargetPlatformPhpThreadSafety    = 'pie-target-platform-php-thread-safety';
    case TargetPlatformPhpWindowsCompiler = 'pie-target-platform-php-windows-compiler';
    case TargetPlatformArchitecture       = 'pie-target-platform-architecture';
    case ConfigureOptions                 = 'pie-configure-options';
    case BuiltBinary                      = 'pie-built-binary';
    case BinaryChecksum                   = 'pie-installed-binary-checksum';
    case InstalledBinary                  = 'pie-installed-binary';
    case PhpizeBinary                     = 'pie-phpize-binary';

    /** @return PieMetadata */
    public static function pieMetadataFromComposerPackage(CompletePackageInterface $composerPackage): array
    {
        $composerPackageExtras = $composerPackage->getExtra();

        $onlyPieExtras = [];

        foreach (array_column(self::cases(), 'value') as $pieMetadataKey) {
            if (
                ! array_key_exists($pieMetadataKey, $composerPackageExtras)
                || ! is_string($composerPackageExtras[$pieMetadataKey])
                || $composerPackageExtras[$pieMetadataKey] === ''
            ) {
                continue;
            }

            $onlyPieExtras[$pieMetadataKey] = $composerPackageExtras[$pieMetadataKey];
        }

        return $onlyPieExtras;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Composer\Composer;
use Composer\Util\Filesystem;
use Symfony\Component\Console\Output\OutputInterface;

use function array_filter;
use function array_walk;
use function in_array;
use function is_array;
use function scandir;
use function sprintf;

use const DIRECTORY_SEPARATOR;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class VendorCleanup
{
    public function __construct(
        private readonly OutputInterface $output,
        private readonly Filesystem $filesystem,
    ) {
    }

    public function __invoke(Composer $composer): void
    {
        $vendorDir      = (string) $composer->getConfig()->get('vendor-dir');
        $vendorContents = scandir($vendorDir);

        if (! is_array($vendorContents)) {
            $this->output->writeln(
                sprintf(
                    '<comment>Vendor directory (vendor-dir config) %s seemed invalid?</comment>',
                    $vendorDir,
                ),
                OutputInterface::VERBOSITY_VERY_VERBOSE,
            );

            return;
        }

        $toRemove = array_filter(
            $vendorContents,
            static function (string $path): bool {
                return ! in_array(
                    $path,
                    [
                        '.',
                        '..',
                        'autoload.php',
                        'composer',
                    ],
                );
            },
        );

        array_walk(
            $toRemove,
            function (string $pathToRemove) use ($vendorDir): void {
                $fullPathToRemove = $vendorDir . DIRECTORY_SEPARATOR . $pathToRemove;

                $this->output->writeln(
                    sprintf(
                        '<comment>Removing: %s</comment>',
                        $fullPathToRemove,
                    ),
                    OutputInterface::VERBOSITY_VERY_VERBOSE,
                );

                if ($this->filesystem->remove($fullPathToRemove)) {
                    return;
                }

                $this->output->writeln(
                    sprintf(
                        '<comment>Warning: failed to remove %s</comment>',
                        $fullPathToRemove,
                    ),
                    OutputInterface::VERBOSITY_VERBOSE,
                );
            },
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Composer\Installer;
use RuntimeException;

use function max;
use function sprintf;

class ComposerRunFailed extends RuntimeException
{
    public static function fromExitCode(int $exitCode): self
    {
        return new self(
            sprintf(
                'PIE Composer run failed with error code %d%s',
                $exitCode,
                self::tryDetermineConstant($exitCode),
            ),
            max(1, $exitCode), // ensure we are always non-zero for exit codes
        );
    }

    private static function tryDetermineConstant(int $errorCode): string
    {
        switch ($errorCode) {
            case Installer::ERROR_GENERIC_FAILURE:
                return ' (ERROR_GENERIC_FAILURE)';

            case Installer::ERROR_NO_LOCK_FILE_FOR_PARTIAL_UPDATE:
                return ' (ERROR_NO_LOCK_FILE_FOR_PARTIAL_UPDATE)';

            case Installer::ERROR_LOCK_FILE_INVALID:
                return ' (ERROR_LOCK_FILE_INVALID)';

            case Installer::ERROR_DEPENDENCY_RESOLUTION_FAILED:
                return ' (ERROR_DEPENDENCY_RESOLUTION_FAILED)';

            case Installer::ERROR_AUDIT_FAILED:
                return ' (ERROR_AUDIT_FAILED)';

            case Installer::ERROR_TRANSPORT_EXCEPTION:
                return ' (ERROR_TRANSPORT_EXCEPTION)';

            default:
                return '';
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Composer\Composer;
use Composer\Factory;
use Composer\Installer;
use Composer\IO\IOInterface;
use Composer\PartialComposer;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Util\Filesystem;
use Composer\Util\ProcessExecutor;
use Php\Pie\ComposerIntegration\Listeners\OverrideDownloadUrlInstallListener;
use Php\Pie\ComposerIntegration\Listeners\RemoveUnrelatedInstallOperations;
use Php\Pie\ExtensionType;
use Php\Pie\Platform;
use Psr\Container\ContainerInterface;
use Webmozart\Assert\Assert;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class PieComposerFactory extends Factory
{
    public function __construct(
        private readonly ContainerInterface $container,
        private readonly PieComposerRequest $composerRequest,
    ) {
    }

    protected function createDefaultInstallers(Installer\InstallationManager $im, PartialComposer $composer, IOInterface $io, ProcessExecutor|null $process = null): void
    {
        $fs = new Filesystem($process);

        $installerFactory = function (ExtensionType $type) use ($io, $composer, $fs): PiePackageInstaller {
            return new PiePackageInstaller(
                $io,
                $composer,
                $type,
                $fs,
                $this->container->get(InstallAndBuildProcess::class),
                $this->container->get(UninstallProcess::class),
                $this->composerRequest,
            );
        };

        $im->addInstaller($installerFactory(ExtensionType::PhpModule));
        $im->addInstaller($installerFactory(ExtensionType::ZendExtension));
    }

    public static function createPieComposer(
        ContainerInterface $container,
        PieComposerRequest $composerRequest,
    ): Composer {
        $pieComposer = Platform::getPieJsonFilename($composerRequest->targetPlatform);

        PieJsonEditor::fromTargetPlatform($composerRequest->targetPlatform)->ensureExists();

        $io       = $container->get(QuieterConsoleIO::class);
        $composer = (new PieComposerFactory($container, $composerRequest))->createComposer(
            $io,
            $pieComposer,
            true,
        );

        $composer
            ->getRepositoryManager()
            ->addRepository(BundledPhpExtensionsRepository::forTargetPlatform(
                $composerRequest->targetPlatform,
            ));

        OverrideDownloadUrlInstallListener::selfRegister($composer, $io, $container, $composerRequest);
        RemoveUnrelatedInstallOperations::selfRegister($composer, $composerRequest);

        $composer->getConfig()->merge(['config' => ['__PIE_REQUEST__' => $composerRequest]]);
        $io->loadConfiguration($composer->getConfig());

        return $composer;
    }

    protected function purgePackages(InstalledRepositoryInterface $repo, Installer\InstallationManager $im): void
    {
        /**
         * This is intentionally a no-op in PIE....
         *
         * Why not purge packages?
         *
         * We have a post install job in {@see VendorCleanup} that cleans up the vendor directory to remove all the
         * actual package files; however, this means that Composer thinks they are not installed after that. When
         * creating the Composer instance, the last step is to purge packages from the
         * {@see InstalledRepositoryInterface} if they no longer exist on disk. But, that means we can't list the
         * packages installed with PIE any more! So, we override this method to become a no-op ✅
         */
    }

    public static function recreatePieComposer(
        ContainerInterface $container,
        Composer $existingComposer,
    ): Composer {
        $composerRequest = $existingComposer->getConfig()->get('__PIE_REQUEST__');

        Assert::isInstanceOf($composerRequest, PieComposerRequest::class);

        return self::createPieComposer($container, $composerRequest);
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Composer\Package\CompletePackage;
use Composer\Package\Link;
use Composer\Package\Package;
use Composer\Package\Version\VersionParser;
use Composer\Repository\ArrayRepository;
use Php\Pie\Downloading\DownloadedPackage;
use Php\Pie\ExtensionType;
use Php\Pie\Platform\OperatingSystemFamily;
use Php\Pie\Platform\TargetPlatform;
use Php\Pie\Util\Process;
use RuntimeException;
use Symfony\Component\Process\Exception\ProcessFailedException;

use function array_combine;
use function array_key_exists;
use function array_keys;
use function array_map;
use function count;
use function implode;
use function in_array;
use function realpath;
use function sprintf;

class BundledPhpExtensionsRepository extends ArrayRepository
{
    /**
     * @var list<array{
     *     name: non-empty-string,
     *     require?: array<string, non-empty-string>,
     *     os-families?: non-empty-list<OperatingSystemFamily>,
     *     type?: ExtensionType,
     *     priority?: int,
     * }>
     */
    private static array $bundledPhpExtensions = [
        ['name' => 'bcmath'],
        ['name' => 'bz2'],
        ['name' => 'calendar'],
        ['name' => 'ctype'],
        ['name' => 'curl'],
        ['name' => 'dba'],
        [
            'name' => 'dom',
            'require' => [
                'php' => '>= 5.2.0',
                'ext-libxml' => '*',
            ],
        ],
        [
            'name' => 'enchant',
            'require' => ['php' => '>= 5.2.0'],
        ],
        ['name' => 'exif'],
        [
            'name' => 'ffi',
            'require' => ['php' => '>= 7.4.0'],
        ],
        // ['name' => 'gd'], // build failure - ext/gd/gd.c:79:11: fatal error: ft2build.h: No such file or directory
        ['name' => 'gettext'],
        ['name' => 'gmp'],
        ['name' => 'iconv'],
        [
            'name' => 'intl',
            'require' => ['php' => '>= 5.3.0'],
        ],
        ['name' => 'ldap'],
        ['name' => 'mbstring'],
        [
            'name' => 'mysqlnd',
            'require' => [
                'php' => '>= 5.3.0',
                'ext-openssl' => '*',
            ],
        ],
        [
            'name' => 'mysqli',
            'priority' => 90, // must load after mysqlnd
            'require' => [
                'php' => '>= 5.3.0',
                /**
                 * Note: Whilst mysqli can be built without mysqlnd (you could
                 * specify `--with-mysqli=...`, we have to make installation
                 * with PIE practical at least to start with. We can look at
                 * improving this later, but for now something is better than
                 * nothing :)
                 */
                'ext-mysqlnd' => '*',
            ],
        ],
        [
            'name' => 'opcache',
            'type' => ExtensionType::ZendExtension,
            'require' => ['php' => '>= 5.5.0, < 8.5.0'],
        ],
//        ['name' => 'openssl'], // Not building in CI
        ['name' => 'pcntl'],
        [
            'name' => 'pdo',
            'require' => ['php' => '>= 5.1.0'],
        ],
        [
            'name' => 'pdo_mysql',
            'require' => [
                'php' => '>= 5.1.0',
                'ext-pdo' => '*',
            ],
        ],
        [
            'name' => 'pdo_pgsql',
            'require' => [
                'php' => '>= 5.1.0',
                'ext-pdo' => '*',
            ],
        ],
        [
            'name' => 'pdo_sqlite',
            'require' => [
                'php' => '>= 5.1.0',
                'ext-pdo' => '*',
            ],
        ],
        ['name' => 'pgsql'],
        ['name' => 'posix'],
        ['name' => 'readline'],
        ['name' => 'session'],
        ['name' => 'shmop'],
        [
            'name' => 'simplexml',
            'require' => [
                'php' => '>= 5.2.0',
                'ext-libxml' => '*',
            ],
        ],
        ['name' => 'snmp'],
        [
            'name' => 'soap',
            'require' => [
                'php' => '>= 5.2.0',
                'ext-libxml' => '*',
            ],
        ],
        ['name' => 'sockets'],
        [
            'name' => 'sodium',
            'require' => ['php' => '>= 7.2.0'],
        ],
        [
            'name' => 'sqlite3',
            'require' => ['php' => '>= 5.3.0'],
        ],
        ['name' => 'sysvmsg'],
        ['name' => 'sysvsem'],
        ['name' => 'sysvshm'],
        ['name' => 'tidy'],
        [
            'name' => 'xml',
            'require' => [
                'php' => '>= 5.2.0',
                'ext-libxml' => '*',
            ],
        ],
        [
            'name' => 'xmlreader',
            'require' => [
                'php' => '>= 5.1.0',
                'ext-libxml' => '*',
                'ext-dom' => '*',
            ],
        ],
        [
            'name' => 'xmlwriter',
            'require' => [
                'php' => '>= 5.2.0',
                'ext-libxml' => '*',
            ],
        ],
        [
            'name' => 'xsl',
            'require' => [
                'php' => '>= 5.2.0',
                'ext-libxml' => '*',
            ],
        ],
        [
            'name' => 'zip',
            'require' => ['php' => '>= 5.2.0'],
        ],
        ['name' => 'zlib'],
    ];

    public static function forTargetPlatform(TargetPlatform $targetPlatform): self
    {
        $versionParser = new VersionParser();
        $phpVersion    = $targetPlatform->phpBinaryPath->version();

        return new self(array_map(
            static function (array $extension) use ($versionParser, $phpVersion): Package {
                if (! array_key_exists('require', $extension)) {
                    $extension['require'] = ['php' => $phpVersion];
                }

                $requireLinks = array_map(
                    static function (string $target, string $constraint) use ($extension, $versionParser): Link {
                        return new Link(
                            'php/' . $extension['name'],
                            $target,
                            $versionParser->parseConstraints($constraint),
                            'requires',
                            $constraint,
                        );
                    },
                    array_keys($extension['require']),
                    $extension['require'],
                );

                $package = new CompletePackage('php/' . $extension['name'], $phpVersion . '.0', $phpVersion);
                $package->setType(($extension['type'] ?? ExtensionType::PhpModule)->value);
                $package->setDistType('zip');
                $package->setRequires(array_combine(
                    array_map(static fn (Link $link) => $link->getTarget(), $requireLinks),
                    $requireLinks,
                ));
                $package->setDistUrl(sprintf('https://github.com/php/php-src/archive/refs/tags/php-%s.zip', $phpVersion));
                $package->setDistReference(sprintf('php-%s', $phpVersion));
                $phpExt = [
                    'extension-name' => $extension['name'],
                    'build-path' => 'ext/' . $extension['name'],
                ];

                if (array_key_exists('os-families', $extension)) {
                    $phpExt['os-families'] = array_map(
                        static fn (OperatingSystemFamily $osFamily) => $osFamily->value,
                        $extension['os-families'],
                    );
                }

                if (array_key_exists('priority', $extension)) {
                    $phpExt['priority'] = $extension['priority'];
                }

                $package->setPhpExt($phpExt);

                return $package;
            },
            self::$bundledPhpExtensions,
        ));
    }

    private static function findRe2c(): string
    {
        try {
            return Process::run(['which', 're2c']);
        } catch (ProcessFailedException $processFailed) {
            throw new RuntimeException('Unable to find re2c on the system', previous: $processFailed);
        }
    }

    /**
     * @param list<string> $makeCommand
     *
     * @return list<string>
     */
    public static function augmentMakeCommandForPhpBundledExtensions(array $makeCommand, DownloadedPackage $downloadedPackage): array
    {
        $extraCflags = [];
        if (
            in_array($downloadedPackage->package->name(), [
                'php/xmlreader',
                'php/dom',
            ])
        ) {
            $path = (string) realpath($downloadedPackage->extractedSourcePath . '/../..');
            if ($path !== '') {
                $extraCflags[] = '-I' . $path;
            }
        }

        if ($downloadedPackage->package->name() === 'php/dom') {
            $path = (string) realpath($downloadedPackage->extractedSourcePath . '/../../ext/lexbor');
            if ($path !== '') {
                $extraCflags[] = '-I' . $path;
            }
        }

        if (count($extraCflags)) {
            $makeCommand[] = 'EXTRA_CFLAGS=' . implode(' ', $extraCflags);
        }

        if (
            in_array($downloadedPackage->package->name(), [
                'php/pdo',
                'php/pdo_mysql',
                'php/pdo_pgsql',
                'php/pdo_sqlite',
            ])
        ) {
            $makeCommand[] = 'RE2C=' . self::findRe2c();
        }

        return $makeCommand;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Composer\Installer\LibraryInstaller;
use Composer\IO\IOInterface;
use Composer\Package\CompletePackage;
use Composer\Package\PackageInterface;
use Composer\PartialComposer;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Util\Filesystem;
use Php\Pie\ExtensionType;
use Symfony\Component\Console\Output\OutputInterface;

use function sprintf;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class PiePackageInstaller extends LibraryInstaller
{
    public function __construct(
        IOInterface $io,
        PartialComposer $composer,
        ExtensionType $type,
        Filesystem $filesystem,
        private readonly InstallAndBuildProcess $installAndBuildProcess,
        private readonly UninstallProcess $uninstallProcess,
        private readonly PieComposerRequest $composerRequest,
    ) {
        parent::__construct($io, $composer, $type->value, $filesystem);
    }

    /** @inheritDoc */
    public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        $composerPackage = $package;

        return parent::install($repo, $composerPackage)
            ?->then(function () use ($composerPackage) {
                $output = $this->composerRequest->pieOutput;

                if ($this->composerRequest->requestedPackage->package !== $composerPackage->getName()) {
                    $output->writeln(
                        sprintf(
                            '<comment>Skipping %s install request from Composer as it was not the expected PIE package %s</comment>',
                            $composerPackage->getName(),
                            $this->composerRequest->requestedPackage->package,
                        ),
                        OutputInterface::VERBOSITY_VERY_VERBOSE,
                    );

                    return null;
                }

                if (! $composerPackage instanceof CompletePackage) {
                    $output->writeln(sprintf(
                        '<error>Not using PIE to install %s as it was not a Complete Package</error>',
                        $composerPackage->getName(),
                    ));

                    return null;
                }

                ($this->installAndBuildProcess)(
                    $this->composer,
                    $this->composerRequest,
                    $composerPackage,
                    $this->getInstallPath($composerPackage),
                );

                return null;
            });
    }

    /** @inheritDoc */
    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        $composerPackage = $package;

        return parent::uninstall($repo, $composerPackage)
            ?->then(function () use ($composerPackage) {
                $output = $this->composerRequest->pieOutput;

                if ($this->composerRequest->requestedPackage->package !== $composerPackage->getName()) {
                    $output->writeln(
                        sprintf(
                            '<comment>Skipping %s uninstall request from Composer as it was not the expected PIE package %s</comment>',
                            $composerPackage->getName(),
                            $this->composerRequest->requestedPackage->package,
                        ),
                        OutputInterface::VERBOSITY_VERY_VERBOSE,
                    );

                    return null;
                }

                if (! $composerPackage instanceof CompletePackage) {
                    $output->writeln(sprintf(
                        '<error>Not using PIE to install %s as it was not a Complete Package</error>',
                        $composerPackage->getName(),
                    ));

                    return null;
                }

                ($this->uninstallProcess)(
                    $this->composerRequest,
                    $composerPackage,
                );

                return null;
            });
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Composer\Package\CompletePackageInterface;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\Installing\Ini\RemoveIniEntry;
use Php\Pie\Installing\Uninstall;
use Symfony\Component\Console\Output\OutputInterface;

use function array_walk;
use function count;
use function reset;
use function sprintf;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class UninstallProcess
{
    /** @psalm-suppress PossiblyUnusedMethod no direct reference; used in service locator */
    public function __construct(
        private readonly RemoveIniEntry $removeIniEntry,
        private readonly Uninstall $uninstall,
    ) {
    }

    public function __invoke(
        PieComposerRequest $composerRequest,
        CompletePackageInterface $composerPackage,
    ): void {
        $output = $composerRequest->pieOutput;

        $piePackage = Package::fromComposerCompletePackage($composerPackage);

        $affectedIniFiles = ($this->removeIniEntry)($piePackage, $composerRequest->targetPlatform, $output);

        if (count($affectedIniFiles) === 1) {
            $output->writeln(
                sprintf('INI file "%s" was updated to remove the extension.', reset($affectedIniFiles)),
                OutputInterface::VERBOSITY_VERBOSE,
            );
        } elseif (count($affectedIniFiles) === 0) {
            $output->writeln(
                'No INI files were updated to remove the extension.',
                OutputInterface::VERBOSITY_VERBOSE,
            );
        } else {
            $output->writeln(
                'The following INI files were updated to remove the extnesion:',
                OutputInterface::VERBOSITY_VERBOSE,
            );
            array_walk($affectedIniFiles, static fn (string $ini) => $output->writeln(' - ' . $ini));
        }

        $output->writeln(sprintf('👋 <info>Removed extension:</info> %s', ($this->uninstall)($piePackage)->filePath));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
enum PieOperation
{
    case Resolve;
    case Download;
    case Build;
    case Install;
    case Uninstall;

    public function shouldBuild(): bool
    {
        return $this === PieOperation::Build || $this === PieOperation::Install;
    }

    public function shouldInstall(): bool
    {
        return $this === PieOperation::Install;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Composer\Composer;
use Composer\Package\Version\VersionSelector;
use Composer\Repository\CompositeRepository;
use Composer\Repository\RepositorySet;
use Php\Pie\DependencyResolver\DetermineMinimumStability;
use Php\Pie\DependencyResolver\RequestedPackageAndVersion;
use Php\Pie\Platform\InstalledPiePackages;
use Php\Pie\Platform\TargetPlatform;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class VersionSelectorFactory
{
    /** @psalm-suppress UnusedConstructor */
    private function __construct()
    {
    }

    private static function factoryRepositorySet(Composer $composer, string|null $requestedVersion): RepositorySet
    {
        $repositorySet = new RepositorySet(DetermineMinimumStability::fromRequestedVersion($requestedVersion));
        $repositorySet->addRepository(new CompositeRepository($composer->getRepositoryManager()->getRepositories()));

        return $repositorySet;
    }

    public static function make(
        Composer $composer,
        RequestedPackageAndVersion $requestedPackageAndVersion,
        TargetPlatform $targetPlatform,
    ): VersionSelector {
        return new VersionSelector(
            self::factoryRepositorySet($composer, $requestedPackageAndVersion->version),
            new PhpBinaryPathBasedPlatformRepository($targetPlatform->phpBinaryPath, $composer, new InstalledPiePackages(), null),
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Composer\Composer;
use Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use Composer\Installer;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\DependencyResolver\RequestedPackageAndVersion;
use Php\Pie\Platform;
use Php\Pie\Platform\TargetPlatform;
use Psr\Container\ContainerInterface;

use function file_exists;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class ComposerIntegrationHandler
{
    /** @psalm-api */
    public function __construct(
        private readonly ContainerInterface $container,
        private readonly QuieterConsoleIO $arrayCollectionIo,
        private readonly VendorCleanup $vendorCleanup,
    ) {
    }

    public function runInstall(
        Package $package,
        Composer $composer,
        TargetPlatform $targetPlatform,
        RequestedPackageAndVersion $requestedPackageAndVersion,
        bool $forceInstallPackageVersion,
        bool $runCleanup,
    ): void {
        $versionSelector = VersionSelectorFactory::make($composer, $requestedPackageAndVersion, $targetPlatform);

        $recommendedRequireVersion = $requestedPackageAndVersion->version;

        // If user did not request a specific require version, use Composer to recommend one for the pie.json
        if ($recommendedRequireVersion === null) {
            $recommendedRequireVersion = $versionSelector->findRecommendedRequireVersion($package->composerPackage());
        }

        // Write the new requirement to pie.json; because we later essentially just do a `composer install` using that file
        $pieComposerJson        = Platform::getPieJsonFilename($targetPlatform);
        $pieJsonEditor          = PieJsonEditor::fromTargetPlatform($targetPlatform);
        $originalPieJsonContent = $pieJsonEditor->addRequire(
            $requestedPackageAndVersion->package,
            $recommendedRequireVersion !== '' ? $recommendedRequireVersion : '*',
        );

        // Refresh the Composer instance so it re-reads the updated pie.json
        $composer = PieComposerFactory::recreatePieComposer($this->container, $composer);

        // Removing the package from the local repository will trick Composer into "re-installing" it :)
        foreach ($composer->getRepositoryManager()->getLocalRepository()->findPackages($requestedPackageAndVersion->package) as $pkg) {
            $composer->getRepositoryManager()->getLocalRepository()->removePackage($pkg);
        }

        $composerInstaller = PieComposerInstaller::createWithPhpBinary(
            $targetPlatform->phpBinaryPath,
            $package->extensionName(),
            $this->arrayCollectionIo,
            $composer,
        );
        $composerInstaller
            ->setAllowedTypes(['php-ext', 'php-ext-zend'])
            ->setInstall(true)
            ->setIgnoredTypes([])
            ->setDryRun(false)
            ->setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($forceInstallPackageVersion))
            ->setDownloadOnly(false);

        if (file_exists(PieComposerFactory::getLockFile($pieComposerJson))) {
            $composerInstaller->setUpdate(true);
            $composerInstaller->setUpdateAllowList([$requestedPackageAndVersion->package]);
        }

        $resultCode = $composerInstaller->run();

        if ($resultCode !== Installer::ERROR_NONE) {
            // Revert composer.json change
            $pieJsonEditor->revert($originalPieJsonContent);

            throw ComposerRunFailed::fromExitCode($resultCode);
        }

        if (! $runCleanup) {
            return;
        }

        ($this->vendorCleanup)($composer);
    }

    public function runUninstall(
        Package $packageToRemove,
        Composer $composer,
        TargetPlatform $targetPlatform,
        RequestedPackageAndVersion $requestedPackageAndVersionToRemove,
    ): void {
        // Write the new requirement to pie.json; because we later essentially just do a `composer install` using that file
        $pieComposerJson        = Platform::getPieJsonFilename($targetPlatform);
        $pieJsonEditor          = PieJsonEditor::fromTargetPlatform($targetPlatform);
        $originalPieJsonContent = $pieJsonEditor->removeRequire($requestedPackageAndVersionToRemove->package);

        // Refresh the Composer instance so it re-reads the updated pie.json
        $composer = PieComposerFactory::recreatePieComposer($this->container, $composer);

        $composerInstaller = PieComposerInstaller::createWithPhpBinary(
            $targetPlatform->phpBinaryPath,
            $packageToRemove->extensionName(),
            $this->arrayCollectionIo,
            $composer,
        );
        $composerInstaller
            ->setAllowedTypes(['php-ext', 'php-ext-zend'])
            ->setInstall(true)
            ->setIgnoredTypes([])
            ->setDryRun(false)
            ->setDownloadOnly(false);

        if (file_exists(PieComposerFactory::getLockFile($pieComposerJson))) {
            $composerInstaller->setUpdate(true);
            $composerInstaller->setUpdateAllowList([$requestedPackageAndVersionToRemove->package]);
        }

        $resultCode = $composerInstaller->run();

        if ($resultCode !== Installer::ERROR_NONE) {
            // Revert composer.json change
            $pieJsonEditor->revert($originalPieJsonContent);

            throw ComposerRunFailed::fromExitCode($resultCode);
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\QuestionHelper;
use Webmozart\Assert\Assert;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class MinimalHelperSet extends HelperSet
{
    /** @param array{question: QuestionHelper} $helpers */
    public function __construct(array $helpers)
    {
        Assert::isInstanceOf(
            $helpers['question'] ?? null,
            QuestionHelper::class,
            'The question option must be an instance of %2$s, got %s',
        );

        parent::__construct($helpers);
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Composer\Package\CompletePackage;
use Composer\Package\CompletePackageInterface;
use Composer\PartialComposer;
use Php\Pie\ComposerIntegration\PieInstalledJsonMetadataKeys as MetadataKey;
use Php\Pie\File\BinaryFile;
use Webmozart\Assert\Assert;

use function array_merge;
use function implode;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class InstalledJsonMetadata
{
    public function addDownloadMetadata(
        PartialComposer $composer,
        PieComposerRequest $composerRequest,
        CompletePackageInterface $composerPackage,
    ): void {
        $this->addPieMetadata(
            $composer,
            $composerPackage,
            MetadataKey::TargetPlatformPhpPath,
            $composerRequest->targetPlatform->phpBinaryPath->phpBinaryPath,
        );
        $this->addPieMetadata(
            $composer,
            $composerPackage,
            MetadataKey::TargetPlatformPhpConfigPath,
            $composerRequest->targetPlatform->phpBinaryPath->phpConfigPath(),
        );
        $this->addPieMetadata(
            $composer,
            $composerPackage,
            MetadataKey::TargetPlatformPhpVersion,
            $composerRequest->targetPlatform->phpBinaryPath->version(),
        );
        $this->addPieMetadata(
            $composer,
            $composerPackage,
            MetadataKey::TargetPlatformPhpThreadSafety,
            $composerRequest->targetPlatform->threadSafety->name,
        );
        $this->addPieMetadata(
            $composer,
            $composerPackage,
            MetadataKey::TargetPlatformPhpWindowsCompiler,
            $composerRequest->targetPlatform->windowsCompiler?->name,
        );
        $this->addPieMetadata(
            $composer,
            $composerPackage,
            MetadataKey::TargetPlatformArchitecture,
            $composerRequest->targetPlatform->architecture->name,
        );
    }

    public function addBuildMetadata(
        PartialComposer $composer,
        PieComposerRequest $composerRequest,
        CompletePackageInterface $composerPackage,
        BinaryFile $builtBinary,
    ): void {
        $this->addPieMetadata(
            $composer,
            $composerPackage,
            MetadataKey::ConfigureOptions,
            implode(' ', $composerRequest->configureOptions),
        );

        $this->addPieMetadata(
            $composer,
            $composerPackage,
            MetadataKey::PhpizeBinary,
            $composerRequest->phpizePath->phpizeBinaryPath ?? null,
        );

        $this->addPieMetadata(
            $composer,
            $composerPackage,
            MetadataKey::BuiltBinary,
            $builtBinary->filePath,
        );

        $this->addPieMetadata(
            $composer,
            $composerPackage,
            MetadataKey::BinaryChecksum,
            $builtBinary->checksum,
        );
    }

    public function addInstallMetadata(
        PartialComposer $composer,
        CompletePackageInterface $composerPackage,
        BinaryFile $installedBinary,
    ): void {
        $this->addPieMetadata(
            $composer,
            $composerPackage,
            MetadataKey::InstalledBinary,
            $installedBinary->filePath,
        );
    }

    private function addPieMetadata(
        PartialComposer $composer,
        CompletePackageInterface $composerPackage,
        MetadataKey $key,
        string|null $value,
    ): void {
        $localRepositoryPackage = $composer
            ->getRepositoryManager()
            ->getLocalRepository()
            ->findPackages($composerPackage->getName())[0];
        Assert::isInstanceOf($localRepositoryPackage, CompletePackage::class);

        $localRepositoryPackage->setExtra(array_merge($localRepositoryPackage->getExtra(), [$key->value => $value]));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration\Listeners;

use Composer\Composer;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\Installer\InstallerEvent;
use Composer\Installer\InstallerEvents;
use Composer\IO\IOInterface;
use Composer\Package\CompletePackageInterface;
use Composer\Util\AuthHelper;
use Composer\Util\HttpDownloader;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Php\Pie\DependencyResolver\Package;
use Php\Pie\Downloading\DownloadUrlMethod;
use Php\Pie\Downloading\PackageReleaseAssets;
use Psr\Container\ContainerInterface;

use function array_walk;
use function pathinfo;

use const PATHINFO_EXTENSION;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class OverrideDownloadUrlInstallListener
{
    public function __construct(
        private readonly Composer $composer,
        private readonly IOInterface $io,
        private readonly ContainerInterface $container,
        private readonly PieComposerRequest $composerRequest,
    ) {
    }

    public static function selfRegister(
        Composer $composer,
        IOInterface $io,
        ContainerInterface $container,
        PieComposerRequest $composerRequest,
    ): void {
        $composer
            ->getEventDispatcher()
            ->addListener(
                InstallerEvents::PRE_OPERATIONS_EXEC,
                new self($composer, $io, $container, $composerRequest),
            );
    }

    public function __invoke(InstallerEvent $installerEvent): void
    {
        /** @psalm-suppress InternalMethod */
        $operations = $installerEvent->getTransaction()?->getOperations() ?? [];

        array_walk(
            $operations,
            function (OperationInterface $operation): void {
                if (! $operation instanceof InstallOperation) {
                    return;
                }

                $composerPackage = $operation->getPackage();
                if (! $composerPackage instanceof CompletePackageInterface) {
                    return;
                }

                // Install requests for other packages than the one we want should be ignored
                if ($this->composerRequest->requestedPackage->package !== $composerPackage->getName()) {
                    return;
                }

                $piePackage        = Package::fromComposerCompletePackage($composerPackage);
                $targetPlatform    = $this->composerRequest->targetPlatform;
                $downloadUrlMethod = DownloadUrlMethod::fromPackage($piePackage, $targetPlatform);

                // Exit early if we should just use Composer's normal download
                if ($downloadUrlMethod === DownloadUrlMethod::ComposerDefaultDownload) {
                    return;
                }

                $possibleAssetNames = $downloadUrlMethod->possibleAssetNames($piePackage, $targetPlatform);
                if ($possibleAssetNames === null) {
                    return;
                }

                // @todo https://github.com/php/pie/issues/138 will need to depend on the repo type (GH/GL/BB/etc.)
                $packageReleaseAssets = $this->container->get(PackageReleaseAssets::class);

                $url = $packageReleaseAssets->findMatchingReleaseAssetUrl(
                    $targetPlatform,
                    $piePackage,
                    new AuthHelper($this->io, $this->composer->getConfig()),
                    new HttpDownloader($this->io, $this->composer->getConfig()),
                    $possibleAssetNames,
                );

                $this->composerRequest->pieOutput->writeln('Found prebuilt archive: ' . $url);
                $composerPackage->setDistUrl($url);

                if (pathinfo($url, PATHINFO_EXTENSION) !== 'tgz') {
                    return;
                }

                $composerPackage->setDistType('tar');
            },
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration\Listeners;

use Closure;
use Composer\Composer;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\DependencyResolver\Operation\UninstallOperation;
use Composer\DependencyResolver\Transaction;
use Composer\Installer\InstallerEvent;
use Composer\Installer\InstallerEvents;
use Php\Pie\ComposerIntegration\PieComposerRequest;
use Symfony\Component\Console\Output\OutputInterface;

use function array_filter;
use function assert;
use function sprintf;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
class RemoveUnrelatedInstallOperations
{
    public function __construct(
        private readonly PieComposerRequest $composerRequest,
    ) {
    }

    public static function selfRegister(
        Composer $composer,
        PieComposerRequest $composerRequest,
    ): void {
        $composer
            ->getEventDispatcher()
            ->addListener(
                InstallerEvents::PRE_OPERATIONS_EXEC,
                new self($composerRequest),
            );
    }

    /**
     * @psalm-suppress InternalProperty
     * @psalm-suppress InternalMethod
     */
    public function __invoke(InstallerEvent $installerEvent): void
    {
        $pieOutput = $this->composerRequest->pieOutput;

        $newOperations = array_filter(
            $installerEvent->getTransaction()?->getOperations() ?? [],
            function (OperationInterface $operation) use ($pieOutput): bool {
                if (! $operation instanceof InstallOperation && ! $operation instanceof UninstallOperation) {
                    $pieOutput->writeln(
                        sprintf(
                            'Unexpected operation during installer: %s',
                            $operation::class,
                        ),
                        OutputInterface::VERBOSITY_VERY_VERBOSE,
                    );

                    return false;
                }

                $isRequestedPiePackage = $this->composerRequest->requestedPackage->package === $operation->getPackage()->getName();

                if (! $isRequestedPiePackage) {
                    $pieOutput->writeln(
                        sprintf(
                            'Filtering package %s from install operations, as it was not the requested package',
                            $operation->getPackage()->getName(),
                        ),
                        OutputInterface::VERBOSITY_VERY_VERBOSE,
                    );
                }

                return $isRequestedPiePackage;
            },
        );

        $overrideOperations = Closure::Bind(
            static function (Transaction $transaction) use ($newOperations): void {
                /** @psalm-suppress InaccessibleProperty */
                $transaction->operations = $newOperations;
            },
            null,
            Transaction::class,
        );
        assert($overrideOperations !== null);
        $overrideOperations($installerEvent->getTransaction());
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\ComposerIntegration;

use Composer\Composer;
use Composer\Installer;
use Composer\IO\IOInterface;
use Composer\Repository\PlatformRepository;
use Php\Pie\ExtensionName;
use Php\Pie\Platform\InstalledPiePackages;
use Php\Pie\Platform\TargetPhp\PhpBinaryPath;
use Webmozart\Assert\Assert;

/**
 * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
 *
 * @psalm-suppress PropertyNotSetInConstructor Property $fixedRootPackage is defined in parent
 */
class PieComposerInstaller extends Installer
{
    private PhpBinaryPath|null $phpBinaryPath           = null;
    private ExtensionName|null $extensionBeingInstalled = null;
    private Composer|null $composer                     = null;

    protected function createPlatformRepo(bool $forUpdate): PlatformRepository
    {
        Assert::notNull($this->phpBinaryPath, '$phpBinaryPath was not set, maybe createWithPhpBinary was not used?');
        Assert::notNull($this->extensionBeingInstalled, '$extensionBeingInstalled was not set, maybe createWithPhpBinary was not used?');
        Assert::notNull($this->composer, '$composer was not set, maybe createWithPhpBinary was not used?');

        return new PhpBinaryPathBasedPlatformRepository($this->phpBinaryPath, $this->composer, new InstalledPiePackages(), $this->extensionBeingInstalled);
    }

    public static function createWithPhpBinary(
        PhpBinaryPath $php,
        ExtensionName $extensionBeingInstalled,
        IOInterface $io,
        Composer $composer,
    ): self {
        /** @psalm-suppress InvalidArgument some kind of unrelated type mismatch, defined in parent */
        $composerInstaller = new self(
            $io,
            $composer->getConfig(),
            $composer->getPackage(),
            $composer->getDownloadManager(),
            $composer->getRepositoryManager(),
            $composer->getLocker(),
            $composer->getInstallationManager(),
            $composer->getEventDispatcher(),
            $composer->getAutoloadGenerator(),
        );

        $composerInstaller->phpBinaryPath           = $php;
        $composerInstaller->extensionBeingInstalled = $extensionBeingInstalled;
        $composerInstaller->composer                = $composer;

        return $composerInstaller;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie;

use Composer\Factory;
use Composer\Util\Platform as ComposerPlatform;
use Illuminate\Container\Container as IlluminateContainer;
use Php\Pie\Building\Build;
use Php\Pie\Building\UnixBuild;
use Php\Pie\Building\WindowsBuild;
use Php\Pie\Command\BuildCommand;
use Php\Pie\Command\DownloadCommand;
use Php\Pie\Command\InfoCommand;
use Php\Pie\Command\InstallCommand;
use Php\Pie\Command\InstallExtensionsForProjectCommand;
use Php\Pie\Command\RepositoryAddCommand;
use Php\Pie\Command\RepositoryListCommand;
use Php\Pie\Command\RepositoryRemoveCommand;
use Php\Pie\Command\SelfUpdateCommand;
use Php\Pie\Command\SelfVerifyCommand;
use Php\Pie\Command\ShowCommand;
use Php\Pie\Command\UninstallCommand;
use Php\Pie\ComposerIntegration\MinimalHelperSet;
use Php\Pie\ComposerIntegration\QuieterConsoleIO;
use Php\Pie\DependencyResolver\DependencyResolver;
use Php\Pie\DependencyResolver\ResolveDependencyWithComposer;
use Php\Pie\Downloading\GithubPackageReleaseAssets;
use Php\Pie\Downloading\PackageReleaseAssets;
use Php\Pie\File\FullPathToSelf;
use Php\Pie\Installing\Ini;
use Php\Pie\Installing\Install;
use Php\Pie\Installing\Uninstall;
use Php\Pie\Installing\UninstallUsingUnlink;
use Php\Pie\Installing\UnixInstall;
use Php\Pie\Installing\WindowsInstall;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;

use function getcwd;
use function str_starts_with;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class Container
{
    public static function factory(): ContainerInterface
    {
        $container = new IlluminateContainer();
        $container->instance(ContainerInterface::class, $container);
        $container->singleton(
            InputInterface::class,
            static function () {
                $input = new ArgvInput();

                if (! Platform::isInteractive()) {
                    $input->setInteractive(false);
                }

                return $input;
            },
        );
        $container->singleton(
            OutputInterface::class,
            static function (): OutputInterface {
                return new ConsoleOutput(
                    ConsoleOutput::VERBOSITY_NORMAL,
                    null,
                    new OutputFormatter(false, Factory::createAdditionalStyles()),
                );
            },
        );
        $container->singleton(EventDispatcher::class, static function () {
            $displayedBanner = false;
            $eventDispatcher = new EventDispatcher();
            $eventDispatcher->addListener(
                ConsoleEvents::COMMAND,
                static function (ConsoleCommandEvent $event) use (&$displayedBanner): void {
                    $command     = $event->getCommand();
                    $application = $command?->getApplication();

                    if ($displayedBanner || $command === null || ! str_starts_with($command::class, 'Php\Pie\Command') || $application === null) {
                        return;
                    }

                    $displayedBanner = true;
                    $event->getOutput()->writeln($application->getLongVersion() . ', from The PHP Foundation');
                },
            );

            return $eventDispatcher;
        });

        $container->singleton(DownloadCommand::class);
        $container->singleton(BuildCommand::class);
        $container->singleton(InstallCommand::class);
        $container->singleton(InfoCommand::class);
        $container->singleton(ShowCommand::class);
        $container->singleton(RepositoryListCommand::class);
        $container->singleton(RepositoryAddCommand::class);
        $container->singleton(RepositoryRemoveCommand::class);
        $container->singleton(UninstallCommand::class);
        $container->singleton(SelfUpdateCommand::class);
        $container->singleton(SelfVerifyCommand::class);
        $container->singleton(InstallExtensionsForProjectCommand::class);

        $container->singleton(QuieterConsoleIO::class, static function (ContainerInterface $container): QuieterConsoleIO {
            return new QuieterConsoleIO(
                $container->get(InputInterface::class),
                $container->get(OutputInterface::class),
                new MinimalHelperSet(
                    [
                        'question' => new QuestionHelper(),
                    ],
                ),
            );
        });

        $container->alias(ResolveDependencyWithComposer::class, DependencyResolver::class);

        $container->alias(GithubPackageReleaseAssets::class, PackageReleaseAssets::class);
        $container->when(GithubPackageReleaseAssets::class)
            ->needs('$githubApiBaseUrl')
            ->give('https://api.github.com');

        $container->when(SelfUpdateCommand::class)
            ->needs('$githubApiBaseUrl')
            ->give('https://api.github.com');

        $container->when(SelfVerifyCommand::class)
            ->needs('$githubApiBaseUrl')
            ->give('https://api.github.com');

        $container->when(FullPathToSelf::class)
            ->needs('$originalCwd')
            ->give(getcwd());

        $container->singleton(
            Build::class,
            static function (ContainerInterface $container): Build {
                if (ComposerPlatform::isWindows()) {
                    return $container->get(WindowsBuild::class);
                }

                return $container->get(UnixBuild::class);
            },
        );

        $container->singleton(
            Ini\SetupIniApproach::class,
            static function (ContainerInterface $container): Ini\SetupIniApproach {
                return new Ini\PickBestSetupIniApproach([
                    $container->get(Ini\PreCheckExtensionAlreadyLoaded::class),
                    $container->get(Ini\OndrejPhpenmod::class),
                    $container->get(Ini\DockerPhpExtEnable::class),
                    $container->get(Ini\StandardAdditionalPhpIniDirectory::class),
                    $container->get(Ini\StandardSinglePhpIni::class),
                ]);
            },
        );

        $container->singleton(
            Install::class,
            static function (ContainerInterface $container): Install {
                if (ComposerPlatform::isWindows()) {
                    return $container->get(WindowsInstall::class);
                }

                return $container->get(UnixInstall::class);
            },
        );

        $container->alias(UninstallUsingUnlink::class, Uninstall::class);

        $container->alias(Ini\RemoveIniEntryWithFileGetContents::class, Ini\RemoveIniEntry::class);

        return $container;
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\SelfManage\Update;

use Composer\Util\AuthHelper;
use Composer\Util\HttpDownloader;
use Php\Pie\File\BinaryFile;
use RuntimeException;
use Webmozart\Assert\Assert;

use function array_filter;
use function array_map;
use function file_put_contents;
use function reset;
use function sys_get_temp_dir;
use function tempnam;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class FetchPieReleaseFromGitHub implements FetchPieRelease
{
    private const PIE_PHAR_NAME          = 'pie.phar';
    private const PIE_LATEST_RELEASE_URL = '/repos/php/pie/releases/latest';

    public function __construct(
        private readonly string $githubApiBaseUrl,
        private readonly HttpDownloader $httpDownloader,
        private readonly AuthHelper $authHelper,
    ) {
    }

    public function latestReleaseMetadata(): ReleaseMetadata
    {
        $url = $this->githubApiBaseUrl . self::PIE_LATEST_RELEASE_URL;

        $decodedRepsonse = $this->httpDownloader->get(
            $url,
            [
                'retry-auth-failure' => true,
                'http' => [
                    'method' => 'GET',
                    'header' => $this->authHelper->addAuthenticationHeader([], $this->githubApiBaseUrl, $url),
                ],
            ],
        )->decodeJson();

        Assert::isArray($decodedRepsonse);
        Assert::keyExists($decodedRepsonse, 'tag_name');
        Assert::stringNotEmpty($decodedRepsonse['tag_name']);
        Assert::keyExists($decodedRepsonse, 'assets');
        Assert::isList($decodedRepsonse['assets']);

        $assetsNamedPiePhar = array_filter(
            array_map(
                /** @return array{name: non-empty-string, browser_download_url: non-empty-string, ...} */
                static function (array $asset): array {
                    Assert::keyExists($asset, 'name');
                    Assert::stringNotEmpty($asset['name']);
                    Assert::keyExists($asset, 'browser_download_url');
                    Assert::stringNotEmpty($asset['browser_download_url']);

                    return $asset;
                },
                $decodedRepsonse['assets'],
            ),
            static function (array $asset): bool {
                return $asset['name'] === self::PIE_PHAR_NAME;
            },
        );
        $firstAssetNamedPiePhar = reset($assetsNamedPiePhar);

        return new ReleaseMetadata(
            $decodedRepsonse['tag_name'],
            $firstAssetNamedPiePhar['browser_download_url'],
        );
    }

    public function downloadContent(ReleaseMetadata $releaseMetadata): BinaryFile
    {
        $pharContent = $this->httpDownloader->get(
            $releaseMetadata->downloadUrl,
            [
                'retry-auth-failure' => true,
                'http' => [
                    'method' => 'GET',
                    'header' => $this->authHelper->addAuthenticationHeader([], $this->githubApiBaseUrl, $releaseMetadata->downloadUrl),
                ],
            ],
        )->getBody();
        Assert::stringNotEmpty($pharContent);

        $tempPharFilename = tempnam(sys_get_temp_dir(), 'pie_self_update_');
        Assert::stringNotEmpty($tempPharFilename);

        if (file_put_contents($tempPharFilename, $pharContent) === false) {
            throw new RuntimeException('Failed to write downloaded PHAR to ' . $tempPharFilename);
        }

        return BinaryFile::fromFileWithSha256Checksum($tempPharFilename);
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\SelfManage\Update;

use Php\Pie\File\BinaryFile;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
interface FetchPieRelease
{
    public function latestReleaseMetadata(): ReleaseMetadata;

    /** Download the given pie.phar and return the filename (should be a temp file) */
    public function downloadContent(ReleaseMetadata $releaseMetadata): BinaryFile;
}
<?php

declare(strict_types=1);

namespace Php\Pie\SelfManage\Update;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class ReleaseMetadata
{
    /**
     * @param non-empty-string $tag
     * @param non-empty-string $downloadUrl
     */
    public function __construct(
        public readonly string $tag,
        public readonly string $downloadUrl,
    ) {
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\SelfManage\Verify;

use Composer\Util\AuthHelper;
use Composer\Util\HttpDownloader;
use Php\Pie\File\BinaryFile;
use Php\Pie\SelfManage\Update\ReleaseMetadata;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\ExecutableFinder;

use function extension_loaded;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class VerifyPieReleaseUsingAttestation implements VerifyPiePhar
{
    public function __construct(
        private readonly GithubCliAttestationVerification $githubCliVerification,
        private readonly FallbackVerificationUsingOpenSsl $fallbackVerification,
    ) {
    }

    /** @param non-empty-string $githubApiBaseUrl */
    public static function factory(
        string $githubApiBaseUrl,
        HttpDownloader $httpDownloader,
        AuthHelper $authHelper,
    ): self {
        return new VerifyPieReleaseUsingAttestation(
            new GithubCliAttestationVerification(new ExecutableFinder()),
            new FallbackVerificationUsingOpenSsl(FallbackVerificationUsingOpenSsl::TRUSTED_ROOT_FILE_PATH, $githubApiBaseUrl, $httpDownloader, $authHelper),
        );
    }

    public function verify(ReleaseMetadata $releaseMetadata, BinaryFile $pharFilename, OutputInterface $output): void
    {
        try {
            $this->githubCliVerification->verify($releaseMetadata, $pharFilename, $output);
        } catch (GithubCliNotAvailable $githubCliNotAvailable) {
            $output->writeln($githubCliNotAvailable->getMessage(), OutputInterface::VERBOSITY_VERBOSE);

            if (! extension_loaded('openssl')) {
                throw FailedToVerifyRelease::fromNoOpenssl();
            }

            $this->fallbackVerification->verify($releaseMetadata, $pharFilename, $output);
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\SelfManage\Verify;

use Webmozart\Assert\Assert;

use function base64_decode;
use function wordwrap;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class Attestation
{
    /**
     * @param non-empty-string $certificate
     * @param non-empty-string $dsseEnvelopePayload
     * @param non-empty-string $dsseEnvelopePayloadType
     * @param non-empty-string $dsseEnvelopeSignature
     */
    private function __construct(
        public readonly string $certificate,
        public readonly string $dsseEnvelopePayload,
        public readonly string $dsseEnvelopePayloadType,
        public readonly string $dsseEnvelopeSignature,
    ) {
    }

    /** @param array<array-key, mixed> $attestation */
    public static function fromAttestationBundleWithDsseEnvelope(array $attestation): self
    {
        Assert::keyExists($attestation, 'bundle');
        Assert::isArray($attestation['bundle']);

        Assert::keyExists($attestation['bundle'], 'verificationMaterial');
        Assert::isArray($attestation['bundle']['verificationMaterial']);
        Assert::keyExists($attestation['bundle']['verificationMaterial'], 'certificate');
        Assert::isArray($attestation['bundle']['verificationMaterial']['certificate']);
        Assert::keyExists($attestation['bundle']['verificationMaterial']['certificate'], 'rawBytes');
        Assert::stringNotEmpty($attestation['bundle']['verificationMaterial']['certificate']['rawBytes']);

        Assert::keyExists($attestation['bundle'], 'dsseEnvelope');
        Assert::isArray($attestation['bundle']['dsseEnvelope']);
        Assert::keyExists($attestation['bundle']['dsseEnvelope'], 'payload');
        Assert::stringNotEmpty($attestation['bundle']['dsseEnvelope']['payload']);
        Assert::keyExists($attestation['bundle']['dsseEnvelope'], 'payloadType');
        Assert::stringNotEmpty($attestation['bundle']['dsseEnvelope']['payloadType']);
        Assert::keyExists($attestation['bundle']['dsseEnvelope'], 'signatures');
        Assert::isNonEmptyList($attestation['bundle']['dsseEnvelope']['signatures']);
        Assert::count($attestation['bundle']['dsseEnvelope']['signatures'], 1);
        Assert::keyExists($attestation['bundle']['dsseEnvelope']['signatures'], 0);
        Assert::isArray($attestation['bundle']['dsseEnvelope']['signatures'][0]);
        Assert::keyExists($attestation['bundle']['dsseEnvelope']['signatures'][0], 'sig');
        Assert::stringNotEmpty($attestation['bundle']['dsseEnvelope']['signatures'][0]['sig']);

        $decoratedCertificate = "-----BEGIN CERTIFICATE-----\n"
            . wordwrap($attestation['bundle']['verificationMaterial']['certificate']['rawBytes'], 67, "\n", true) . "\n"
            . "-----END CERTIFICATE-----\n";
        Assert::stringNotEmpty($decoratedCertificate);

        $decodedPayload = base64_decode($attestation['bundle']['dsseEnvelope']['payload']);
        Assert::stringNotEmpty($decodedPayload);

        $decodedSignature = base64_decode($attestation['bundle']['dsseEnvelope']['signatures'][0]['sig']);
        Assert::stringNotEmpty($decodedSignature);

        return new self(
            $decoratedCertificate,
            $decodedPayload,
            $attestation['bundle']['dsseEnvelope']['payloadType'],
            $decodedSignature,
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\SelfManage\Verify;

use RuntimeException;

use function sprintf;

class GithubCliNotAvailable extends RuntimeException
{
    public static function fromExpectedGhToolName(string $expectedGhToolName): self
    {
        return new self(sprintf('The GitHub "%s" CLI tool was not available.', $expectedGhToolName));
    }

    public static function withMissingAttestationCommand(string $expectedGhToolName): self
    {
        return new self(sprintf('The GitHub "%s" CLI tool was available, but the `gh attestation` command failed; perhaps this version is out of date.', $expectedGhToolName));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\SelfManage\Verify;

use Php\Pie\File\BinaryFile;
use Php\Pie\SelfManage\Update\ReleaseMetadata;
use RuntimeException;
use Symfony\Component\Process\Exception\ProcessFailedException;

use function implode;
use function is_array;
use function sprintf;
use function strlen;
use function trim;

class FailedToVerifyRelease extends RuntimeException
{
    public static function fromInvalidSubjectDefinition(): self
    {
        return new self('Unable to extract subject digest from the dsseEnvelope in the attestation.');
    }

    public static function fromMissingAttestation(ReleaseMetadata $releaseMetadata, BinaryFile $file): self
    {
        return new self(sprintf(
            'Attestation for %s (sha256:%s) was not found',
            $releaseMetadata->tag,
            $file->checksum,
        ));
    }

    public static function fromSignatureVerificationFailed(int $attestationIndex, ReleaseMetadata $releaseMetadata): self
    {
        return new self(sprintf(
            'Failed to verify DSSE Envelope payload signature for attestation %d for %s',
            $attestationIndex,
            $releaseMetadata->tag,
        ));
    }

    /** @param array<string,string>|string $issuer */
    public static function fromIssuerCertificateVerificationFailed(array|string $issuer): self
    {
        return new self(sprintf(
            'Failed to verify the attestation certificate was issued by trusted root %s',
            is_array($issuer) ? implode(',', $issuer) : $issuer,
        ));
    }

    /** @param array<string,string>|string $issuer */
    public static function fromNoIssuerCertificateInTrustedRoot(array|string $issuer): self
    {
        return new self(sprintf(
            'Could not find a trusted root certificate for issuer %s',
            is_array($issuer) ? implode(',', $issuer) : $issuer,
        ));
    }

    public static function fromInvalidDerEncodedStringLength(string $derEncodedString, int $expectedLength): self
    {
        return new self(sprintf(
            'DER encoded string length of "%s" was wrong; expected %d characters, was actually %d characters',
            $derEncodedString,
            $expectedLength,
            strlen($derEncodedString),
        ));
    }

    public static function fromMismatchingExtensionValues(string $extension, string $expected, string $actual): self
    {
        return new self(sprintf(
            'Attestation certificate extension %s mismatch; expected "%s", was "%s"',
            $extension,
            $expected,
            $actual,
        ));
    }

    public static function fromNoOpenssl(): self
    {
        return new self('Unable to verify without `gh` CLI tool, or openssl extension.');
    }

    public static function fromGhCliFailure(ReleaseMetadata $releaseMetadata, ProcessFailedException $processFailedException): self
    {
        return new self(
            sprintf(
                "`gh` CLI tool could not verify release %s\n\nError: %s",
                $releaseMetadata->tag,
                trim($processFailedException->getProcess()->getErrorOutput()),
            ),
            previous: $processFailedException,
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\SelfManage\Verify;

use Php\Pie\File\BinaryFile;
use Php\Pie\SelfManage\Update\ReleaseMetadata;
use Symfony\Component\Console\Output\OutputInterface;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
interface VerifyPiePhar
{
    /** @throws FailedToVerifyRelease */
    public function verify(ReleaseMetadata $releaseMetadata, BinaryFile $pharFilename, OutputInterface $output): void;
}
<?php

declare(strict_types=1);

namespace Php\Pie\SelfManage\Verify;

use Php\Pie\File\BinaryFile;
use Php\Pie\SelfManage\Update\ReleaseMetadata;
use Php\Pie\Util\Emoji;
use Php\Pie\Util\Process;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\ExecutableFinder;

use function implode;
use function sprintf;
use function str_starts_with;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class GithubCliAttestationVerification implements VerifyPiePhar
{
    private const GH_CLI_NAME             = 'gh';
    private const GH_ATTESTATION_COMMAND  = 'attestation';
    private const GH_VERIFICATION_TIMEOUT = 30;

    public function __construct(private readonly ExecutableFinder $executableFinder)
    {
    }

    public function verify(ReleaseMetadata $releaseMetadata, BinaryFile $pharFilename, OutputInterface $output): void
    {
        $gh = $this->executableFinder->find(self::GH_CLI_NAME);

        if ($gh === null) {
            throw GithubCliNotAvailable::fromExpectedGhToolName(self::GH_CLI_NAME);
        }

        // Try to use `gh attestation --help` to ensure it is not an old `gh` cli version
        try {
            Process::run([$gh, self::GH_ATTESTATION_COMMAND, '--help'], null, self::GH_VERIFICATION_TIMEOUT);
        } catch (ProcessFailedException $attestationCommandCheck) {
            if (str_starts_with($attestationCommandCheck->getProcess()->getErrorOutput(), sprintf('unknown command "%s" for "%s"', self::GH_ATTESTATION_COMMAND, self::GH_CLI_NAME))) {
                throw GithubCliNotAvailable::withMissingAttestationCommand(self::GH_CLI_NAME);
            }

            throw $attestationCommandCheck;
        }

        $verificationCommand = [
            $gh,
            self::GH_ATTESTATION_COMMAND,
            'verify',
            '--owner=php',
            $pharFilename->filePath,
        ];

        $output->writeln('Verifying using: ' . implode(' ', $verificationCommand), OutputInterface::VERBOSITY_VERBOSE);

        try {
            Process::run($verificationCommand, null, self::GH_VERIFICATION_TIMEOUT);
        } catch (ProcessFailedException $processFailedException) {
            throw FailedToVerifyRelease::fromGhCliFailure($releaseMetadata, $processFailedException);
        }

        $output->writeln(sprintf('<info>%s Verified the new PIE version</info>', Emoji::GREEN_CHECKMARK));
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie\SelfManage\Verify;

use Composer\Downloader\TransportException;
use Composer\Util\AuthHelper;
use Composer\Util\HttpDownloader;
use OpenSSLAsymmetricKey;
use Php\Pie\File\BinaryFile;
use Php\Pie\SelfManage\Update\ReleaseMetadata;
use Php\Pie\Util\Emoji;
use Symfony\Component\Console\Output\OutputInterface;
use Webmozart\Assert\Assert;

use function array_key_exists;
use function array_map;
use function count;
use function explode;
use function extension_loaded;
use function file_get_contents;
use function is_array;
use function is_string;
use function json_decode;
use function openssl_pkey_get_public;
use function openssl_verify;
use function openssl_x509_parse;
use function openssl_x509_verify;
use function ord;
use function sprintf;
use function strlen;
use function substr;
use function trim;
use function wordwrap;

use const OPENSSL_ALGO_SHA256;

/** @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks */
final class FallbackVerificationUsingOpenSsl implements VerifyPiePhar
{
    public const TRUSTED_ROOT_FILE_PATH = __DIR__ . '/../../../resources/trusted-root.jsonl';

    /** @link https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#136141572641--fulcio */
    private const ATTESTATION_CERTIFICATE_EXPECTED_EXTENSION_VALUES = [
        '1.3.6.1.4.1.57264.1.8' => 'https://token.actions.githubusercontent.com',
        '1.3.6.1.4.1.57264.1.12' => 'https://github.com/php/pie',
        '1.3.6.1.4.1.57264.1.16' => 'https://github.com/php',
    ];

    public function __construct(
        private readonly string $trustedRootFilePath,
        private readonly string $githubApiBaseUrl,
        private readonly HttpDownloader $httpDownloader,
        private readonly AuthHelper $authHelper,
    ) {
    }

    public function verify(ReleaseMetadata $releaseMetadata, BinaryFile $pharFilename, OutputInterface $output): void
    {
        $output->writeln(
            'Falling back to basic verification. To use full verification, install the `gh` CLI tool.',
            OutputInterface::VERBOSITY_VERBOSE,
        );

        $attestations = $this->downloadAttestations($releaseMetadata, $pharFilename);

        foreach ($attestations as $attestationIndex => $attestation) {
            /**
             * Useful references. Whilst we don't do the full verification that
             * `gh attestation verify` would (since we don't want to re-invent
             * the wheel), we can do some basic check of the DSSE Envelope.
             * We'll check the payload digest matches our expectation, and
             * verify the signature with the certificate.
             *
             *  - https://github.com/cli/cli/blob/234d2effd545fb9d72ea77aa648caa499aecaa6e/pkg/cmd/attestation/verify/verify.go#L225-L256
             *  - https://docs.sigstore.dev/logging/verify-release/
             *  - https://github.com/secure-systems-lab/dsse/blob/master/protocol.md#protocol
             */
            $this->assertCertificateSignedByTrustedRoot($attestation);
            $output->writeln('#' . $attestationIndex . ': Certificate was signed by a trusted root.', OutputInterface::VERBOSITY_VERBOSE);

            $this->assertCertificateExtensionClaims($attestation);
            $output->writeln('#' . $attestationIndex . ': Certificate extension claims match.', OutputInterface::VERBOSITY_VERBOSE);

            $this->assertDigestFromAttestationMatchesActual($pharFilename, $attestation);
            $output->writeln('#' . $attestationIndex . ': Payload digest matches downloaded file.', OutputInterface::VERBOSITY_VERBOSE);

            $this->verifyDsseEnvelopeSignature($releaseMetadata, $attestationIndex, $attestation);
            $output->writeln('#' . $attestationIndex . ': DSSE payload signature verified with certificate.', OutputInterface::VERBOSITY_VERBOSE);
        }

        $output->writeln(sprintf(
            '<info>%s Verified the new PIE version (using fallback verification)</info>',
            Emoji::GREEN_CHECKMARK,
        ));
    }

    private function assertCertificateSignedByTrustedRoot(Attestation $attestation): void
    {
        $attestationCertificateInfo = openssl_x509_parse($attestation->certificate);

        $trustedRootJsonLines = explode("\n", trim(file_get_contents($this->trustedRootFilePath)));

        /**
         * Now go through our trusted root certificates and attempt to verify that the certificate was signed by an
         * in-date trusted root certificate. The root certificates should be periodically and frequently updated using:
         *
         *     gh attestation trusted-root > resources/trusted-root.jsonl
         *
         * And verifying the contents afterwards to ensure they have not been compromised. This list of JSON blobs may
         * have multiple certificates (e.g. root certificates, intermediate certificates, expired certificates, etc.)
         * so we should loop over to find the correct certificate used to sign the attestation certificate.
         */
        foreach ($trustedRootJsonLines as $jsonLine) {
            /** @var mixed $decoded */
            $decoded = json_decode($jsonLine, true);

            // No certificate authorities defined in this JSON line, skip it...
            if (
                ! is_array($decoded)
                || ! array_key_exists('certificateAuthorities', $decoded)
                || ! is_array($decoded['certificateAuthorities'])
            ) {
                continue;
            }

            /** @var mixed $certificateAuthority */
            foreach ($decoded['certificateAuthorities'] as $certificateAuthority) {
                // We don't have a certificate chain defined, skip it...
                if (
                    ! is_array($certificateAuthority)
                    || ! array_key_exists('certChain', $certificateAuthority)
                    || ! is_array($certificateAuthority['certChain'])
                    || ! array_key_exists('certificates', $certificateAuthority['certChain'])
                    || ! is_array($certificateAuthority['certChain']['certificates'])
                ) {
                    continue;
                }

                /** @var mixed $caCertificateWrapper */
                foreach ($certificateAuthority['certChain']['certificates'] as $caCertificateWrapper) {
                    // Certificate is not in the expected format, i.e. no rawBytes key, skip it...
                    if (
                        ! is_array($caCertificateWrapper)
                        || ! array_key_exists('rawBytes', $caCertificateWrapper)
                        || ! is_string($caCertificateWrapper['rawBytes'])
                        || $caCertificateWrapper['rawBytes'] === ''
                    ) {
                        continue;
                    }

                    // Embed the base64-encoded DER into a PEM envelope for consumption by OpenSSL.
                    $caCertificateString = sprintf(
                        <<<'EOT'
                        -----BEGIN CERTIFICATE-----
                        %s
                        -----END CERTIFICATE-----
                        EOT,
                        wordwrap($caCertificateWrapper['rawBytes'], 67, "\n", true),
                    );

                    $caCertificateInfo = openssl_x509_parse($caCertificateString);

                    // If the CA certificate subject is not the issuer of the attestation certificate,
                    // this was not the cert we were looking for, skip it...
                    if ($caCertificateInfo['subject'] !== $attestationCertificateInfo['issuer']) {
                        continue;
                    }

                    // Finally, verify that the located CA cert was used to sign the attestation certificate
                    if (openssl_x509_verify($attestation->certificate, $caCertificateString) !== 1) {
                        /** @psalm-suppress MixedArgument */
                        throw FailedToVerifyRelease::fromIssuerCertificateVerificationFailed($attestationCertificateInfo['issuer']);
                    }

                    return;
                }
            }
        }

        /**
         * If we got here, we skipped all the certificates in the trusted root collection for various reasons; so we
         * therefore cannot trust the attestation certificate.
         *
         * @psalm-suppress MixedArgument
         */
        throw FailedToVerifyRelease::fromNoIssuerCertificateInTrustedRoot($attestationCertificateInfo['issuer']);
    }

    private function assertCertificateExtensionClaims(Attestation $attestation): void
    {
        $attestationCertificateInfo = openssl_x509_parse($attestation->certificate);
        Assert::isArray($attestationCertificateInfo['extensions']);

        /**
         * See {@link https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md#136141572641--fulcio} for details
         * on the Fulcio extension keys; note the values are DER-encoded strings; the ASN.1 tag is UTF8String (0x0C).
         *
         * Check the extension values are what we expect; these are hard-coded, as we don't expect them
         * to change unless the namespace/repo name change, etc.
         */
        foreach (self::ATTESTATION_CERTIFICATE_EXPECTED_EXTENSION_VALUES as $extension => $expectedValue) {
            Assert::keyExists($attestationCertificateInfo['extensions'], $extension);
            Assert::stringNotEmpty($attestationCertificateInfo['extensions'][$extension]);
            $actualValue = $attestationCertificateInfo['extensions'][$extension];

            // First character (the ASN.1 tag) is expected to be UTF8String (0x0C)
            if (ord($actualValue[0]) !== 0x0C) {
                throw FailedToVerifyRelease::fromMismatchingExtensionValues($extension, $expectedValue, $actualValue);
            }

            /**
             * Second character is expected to be the length of the actual value
             * as long as they are less than 127 bytes (short form)
             *
             * @link https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/basic-encoding-rules.html#Lengths
             */
            $expectedValueLength = ord($actualValue[1]);
            if (strlen($actualValue) !== 2 + $expectedValueLength) {
                throw FailedToVerifyRelease::fromInvalidDerEncodedStringLength($actualValue, 2 + $expectedValueLength);
            }

            $derDecodedValue = substr($actualValue, 2, $expectedValueLength);
            if ($derDecodedValue !== $expectedValue) {
                throw FailedToVerifyRelease::fromMismatchingExtensionValues($extension, $expectedValue, $derDecodedValue);
            }
        }
    }

    private function verifyDsseEnvelopeSignature(ReleaseMetadata $releaseMetadata, int $attestationIndex, Attestation $attestation): void
    {
        if (! extension_loaded('openssl')) {
            throw FailedToVerifyRelease::fromNoOpenssl();
        }

        $publicKey = openssl_pkey_get_public($attestation->certificate);
        Assert::isInstanceOf($publicKey, OpenSSLAsymmetricKey::class);

        $preAuthenticationEncoding = sprintf(
            'DSSEv1 %d %s %d %s',
            strlen($attestation->dsseEnvelopePayloadType),
            $attestation->dsseEnvelopePayloadType,
            strlen($attestation->dsseEnvelopePayload),
            $attestation->dsseEnvelopePayload,
        );

        if (openssl_verify($preAuthenticationEncoding, $attestation->dsseEnvelopeSignature, $publicKey, OPENSSL_ALGO_SHA256) !== 1) {
            throw FailedToVerifyRelease::fromSignatureVerificationFailed($attestationIndex, $releaseMetadata);
        }
    }

    private function assertDigestFromAttestationMatchesActual(BinaryFile $pharFilename, Attestation $attestation): void
    {
        /** @var mixed $decodedPayload */
        $decodedPayload = json_decode($attestation->dsseEnvelopePayload, true);

        if (
            ! is_array($decodedPayload)
            || ! array_key_exists('subject', $decodedPayload)
            || ! is_array($decodedPayload['subject'])
            || count($decodedPayload['subject']) !== 1
            || ! array_key_exists(0, $decodedPayload['subject'])
            || ! is_array($decodedPayload['subject'][0])
            || ! array_key_exists('name', $decodedPayload['subject'][0])
            || $decodedPayload['subject'][0]['name'] !== 'pie.phar'
            || ! array_key_exists('digest', $decodedPayload['subject'][0])
            || ! is_array($decodedPayload['subject'][0]['digest'])
            || ! array_key_exists('sha256', $decodedPayload['subject'][0]['digest'])
            || ! is_string($decodedPayload['subject'][0]['digest']['sha256'])
            || $decodedPayload['subject'][0]['digest']['sha256'] === ''
        ) {
            throw FailedToVerifyRelease::fromInvalidSubjectDefinition();
        }

        $pharFilename->verifyAgainstOther(new BinaryFile(
            $pharFilename->filePath,
            $decodedPayload['subject'][0]['digest']['sha256'],
        ));
    }

    /** @return non-empty-list<Attestation> */
    private function downloadAttestations(ReleaseMetadata $releaseMetadata, BinaryFile $pharFilename): array
    {
        $attestationUrl = $this->githubApiBaseUrl . '/orgs/php/attestations/sha256:' . $pharFilename->checksum;

        try {
            $decodedJson = $this->httpDownloader->get(
                $attestationUrl,
                [
                    'retry-auth-failure' => true,
                    'http' => [
                        'method' => 'GET',
                        'header' => $this->authHelper->addAuthenticationHeader([], $this->githubApiBaseUrl, $attestationUrl),
                    ],
                ],
            )->decodeJson();

            Assert::isArray($decodedJson);
            Assert::keyExists($decodedJson, 'attestations');
            Assert::isNonEmptyList($decodedJson['attestations']);

            return array_map(
                static function (array $attestation): Attestation {
                    return Attestation::fromAttestationBundleWithDsseEnvelope($attestation);
                },
                $decodedJson['attestations'],
            );
        } catch (TransportException $transportException) {
            if ($transportException->getStatusCode() === 404) {
                throw FailedToVerifyRelease::fromMissingAttestation($releaseMetadata, $pharFilename);
            }

            throw $transportException;
        }
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie;

use Webmozart\Assert\Assert;

use function array_key_exists;

/**
 * @internal This is not public API for PIE, so should not be depended upon unless you accept the risk of BC breaks
 *
 * @immutable
 */
final class ConfigureOption
{
    /** @param non-empty-string $name */
    private function __construct(
        public readonly string $name,
        public readonly bool $needsValue,
        public readonly string $description,
    ) {
    }

    /** @param array<array-key, mixed> $configureOptionDefinition */
    public static function fromComposerJsonDefinition(array $configureOptionDefinition): self
    {
        Assert::keyExists($configureOptionDefinition, 'name');
        Assert::stringNotEmpty($configureOptionDefinition['name']);

        $needsValue = false;
        if (array_key_exists('needs-value', $configureOptionDefinition)) {
            Assert::boolean($configureOptionDefinition['needs-value']);
            $needsValue = $configureOptionDefinition['needs-value'];
        }

        $description = '';
        if (array_key_exists('description', $configureOptionDefinition)) {
            Assert::string($configureOptionDefinition['description']);
            $description = $configureOptionDefinition['description'];
        }

        return new self(
            $configureOptionDefinition['name'],
            $needsValue,
            $description,
        );
    }
}
<?php

declare(strict_types=1);

namespace Php\Pie;

use Php\Pie\Command\BuildCommand;
use Php\Pie\Command\InstallExtensionsForProjectCommand;
use Php\Pie\Command\DownloadCommand;
use Php\Pie\Command\InfoCommand;
use Php\Pie\Command\InstallCommand;
use Php\Pie\Command\RepositoryAddCommand;
use Php\Pie\Command\RepositoryListCommand;
use Php\Pie\Command\RepositoryRemoveCommand;
use Php\Pie\Command\SelfUpdateCommand;
use Php\Pie\Command\SelfVerifyCommand;
use Php\Pie\Command\ShowCommand;
use Php\Pie\Command\UninstallCommand;
use Php\Pie\Util\PieVersion;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;

error_reporting(error_reporting() & ~E_DEPRECATED);

/** @psalm-suppress UnresolvableInclude */
include $_composer_autoload_path ?? __DIR__ . '/../vendor/autoload.php';

$container = Container::factory();

$application = new Application('🥧 PHP Installer for Extensions (PIE)', PieVersion::get());

$application->setDispatcher($container->get(EventDispatcher::class));

$application->setCommandLoader(new ContainerCommandLoader(
    $container,
    [
        'download' => DownloadCommand::class,
        'build' => BuildCommand::class,
        'install' => InstallCommand::class,
        'info' => InfoCommand::class,
        'show' => ShowCommand::class,
        'repository:list' => RepositoryListCommand::class,
        'repository:add' => RepositoryAddCommand::class,
        'repository:remove' => RepositoryRemoveCommand::class,
        'uninstall' => UninstallCommand::class,
        'self-update' => SelfUpdateCommand::class,
        'self-verify' => SelfVerifyCommand::class,
        'install-extensions-for-project' => InstallExtensionsForProjectCommand::class,
    ]
));

$application->run($container->get(InputInterface::class), $container->get(OutputInterface::class));
{"mediaType":"application/vnd.dev.sigstore.trustedroot+json;version=0.1","tlogs":[{"baseUrl":"https://rekor.sigstore.dev","hashAlgorithm":"SHA2_256","publicKey":{"rawBytes":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2G2Y+2tabdTV5BcGiBIx0a9fAFwrkBbmLSGtks4L3qX6yYY0zufBnhC8Ur/iy55GhWP/9A/bY2LhC30M9+RYtw==","keyDetails":"PKIX_ECDSA_P256_SHA_256","validFor":{"start":"2021-01-12T11:53:27Z"}},"logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="}}],"certificateAuthorities":[{"subject":{"organization":"sigstore.dev","commonName":"sigstore"},"uri":"https://fulcio.sigstore.dev","certChain":{"certificates":[{"rawBytes":"MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAqMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIxMDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSyA7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0JcastaRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6NmMGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2uSu1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJxVe/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uupHr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ=="}]},"validFor":{"start":"2021-03-07T03:20:29Z","end":"2022-12-31T23:59:59.999Z"}},{"subject":{"organization":"sigstore.dev","commonName":"sigstore"},"uri":"https://fulcio.sigstore.dev","certChain":{"certificates":[{"rawBytes":"MIICGjCCAaGgAwIBAgIUALnViVfnU0brJasmRkHrn/UnfaQwCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMjA0MTMyMDA2MTVaFw0zMTEwMDUxMzU2NThaMDcxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEeMBwGA1UEAxMVc2lnc3RvcmUtaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8RVS/ysH+NOvuDZyPIZtilgUF9NlarYpAd9HP1vBBH1U5CV77LSS7s0ZiH4nE7Hv7ptS6LvvR/STk798LVgMzLlJ4HeIfF3tHSaexLcYpSASr1kS0N/RgBJz/9jWCiXno3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU39Ppz1YkEZb5qNjpKFWixi4YZD8wHwYDVR0jBBgwFoAUWMAeX5FFpWapesyQoZMi0CrFxfowCgYIKoZIzj0EAwMDZwAwZAIwPCsQK4DYiZYDPIaDi5HFKnfxXx6ASSVmERfsynYBiX2X6SJRnZU84/9DZdnFvvxmAjBOt6QpBlc4J/0DxvkTCqpclvziL6BCCPnjdlIB3Pu3BxsPmygUY7Ii2zbdCdliiow="},{"rawBytes":"MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxexX69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92jYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRYwB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQKsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCMWP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ"}]},"validFor":{"start":"2022-04-13T20:06:15Z"}}],"ctlogs":[{"baseUrl":"https://ctfe.sigstore.dev/test","hashAlgorithm":"SHA2_256","publicKey":{"rawBytes":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbfwR+RJudXscgRBRpKX1XFDy3PyudDxz/SfnRi1fT8ekpfBd2O1uoz7jr3Z8nKzxA69EUQ+eFCFI3zeubPWU7w==","keyDetails":"PKIX_ECDSA_P256_SHA_256","validFor":{"start":"2021-03-14T00:00:00Z","end":"2022-10-31T23:59:59.999Z"}},"logId":{"keyId":"CGCS8ChS/2hF0dFrJ4ScRWcYrBY9wzjSbea8IgY2b3I="}},{"baseUrl":"https://ctfe.sigstore.dev/2022","hashAlgorithm":"SHA2_256","publicKey":{"rawBytes":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEiPSlFi0CmFTfEjCUqF9HuCEcYXNKAaYalIJmBZ8yyezPjTqhxrKBpMnaocVtLJBI1eM3uXnQzQGAJdJ4gs9Fyw==","keyDetails":"PKIX_ECDSA_P256_SHA_256","validFor":{"start":"2022-10-20T00:00:00Z"}},"logId":{"keyId":"3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4="}}],"timestampAuthorities":[{"subject":{"organization":"sigstore.dev","commonName":"sigstore-tsa-selfsigned"},"uri":"https://timestamp.sigstore.dev/api/v1/timestamp","certChain":{"certificates":[{"rawBytes":"MIICEDCCAZagAwIBAgIUOhNULwyQYe68wUMvy4qOiyojiwwwCgYIKoZIzj0EAwMwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZDAeFw0yNTA0MDgwNjU5NDNaFw0zNTA0MDYwNjU5NDNaMC4xFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEVMBMGA1UEAxMMc2lnc3RvcmUtdHNhMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE4ra2Z8hKNig2T9kFjCAToGG30jky+WQv3BzL+mKvh1SKNR/UwuwsfNCg4sryoYAd8E6isovVA3M4aoNdm9QDi50Z8nTEyvqgfDPtTIwXItfiW/AFf1V7uwkbkAoj0xxco2owaDAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0OBBYEFIn9eUOHz9BlRsMCRscsc1t9tOsDMB8GA1UdIwQYMBaAFJjsAe9/u1H/1JUeb4qImFMHic6/MBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMAoGCCqGSM49BAMDA2gAMGUCMDtpsV/6KaO0qyF/UMsX2aSUXKQFdoGTptQGc0ftq1csulHPGG6dsmyMNd3JB+G3EQIxAOajvBcjpJmKb4Nv+2Taoj8Uc5+b6ih6FXCCKraSqupe07zqswMcXJTe1cExvHvvlw=="},{"rawBytes":"MIIB9zCCAXygAwIBAgIUV7f0GLDOoEzIh8LXSW80OJiUp14wCgYIKoZIzj0EAwMwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZDAeFw0yNTA0MDgwNjU5NDNaFw0zNTA0MDYwNjU5NDNaMDkxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEgMB4GA1UEAxMXc2lnc3RvcmUtdHNhLXNlbGZzaWduZWQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQUQNtfRT/ou3YATa6wB/kKTe70cfJwyRIBovMnt8RcJph/COE82uyS6FmppLLL1VBPGcPfpQPYJNXzWwi8icwhKQ6W/Qe2h3oebBb2FHpwNJDqo+TMaC/tdfkv/ElJB72jRTBDMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBSY7AHvf7tR/9SVHm+KiJhTB4nOvzAKBggqhkjOPQQDAwNpADBmAjEAwGEGrfGZR1cen1R8/DTVMI943LssZmJRtDp/i7SfGHmGRP6gRbuj9vOK3b67Z0QQAjEAuT2H673LQEaHTcyQSZrkp4mX7WwkmF+sVbkYY5mXN+RMH13KUEHHOqASaemYWK/E"}]},"validFor":{"start":"2025-07-04T00:00:00Z"}}]}
{"mediaType":"application/vnd.dev.sigstore.trustedroot+json;version=0.1","certificateAuthorities":[{"subject":{"organization":"GitHub, Inc.","commonName":"Internal Services Root"},"uri":"fulcio.githubapp.com","certChain":{"certificates":[{"rawBytes":"MIICKjCCAbCgAwIBAgIUW3TJVeOvr+NSvJXdOw8nEEn7HhQwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMB4XDTIzMDkxMjE0MDY1NFoXDTI0MDkxMTE0MDY1NFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEsosodObhuHG6Pr5vp5y+pmnKawS1h2hwv3r3hBwqh3ZHJAw64mhDnDs9fw4jKkZEBYRSVyOHyZppz4day8hgpTIDwdj44Oan4RDb+wmj04jfhVLjLsQ4Q/X4K/ynRgNXo3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUm0vkDkQZ29hutYdayJobIRmf/iMwHwYDVR0jBBgwFoAUwOG4UqRLTz7eejgRBs9JjqFFmzMwCgYIKoZIzj0EAwMDaAAwZQIwIBl93E7vkWTvdeIm1WSIM4qNsj0ApE8LCj3k1vrY5x6/7yhAZs7QlO3/FBCoEeaZAjEAlJcNr37uZq9BYHODHBeO/gP+6EfbzsNaLV22ASBlhF/a9y83ESLuqCNN7IxGxmWT"},{"rawBytes":"MIICFTCCAZugAwIBAgIUD3Jlqt4qhrcZI4UnGfPGrEq/pjQwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDkxMTEyMDAwMFoXDTI4MDkwOTEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7X7nK0wC7uEmDjW+on0sXIX3FacL3hhcrhneA+M/kl1OtvQiPmFrH9lbUQqOj/AfspJ8uGY3jaq8WuSg6ghatzYfuuzLAJIK4nGpCBafncF8EynOssPq64/Dz+JUWXqlo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUwOG4UqRLTz7eejgRBs9JjqFFmzMwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAI8HWLrke7uzhOpwlD1cNixPmoX9XFKe7bEPozo0D+vKi0Gt6VlC7xPedFIw4/AypAIwQP+FGRWvfx0IAH5/n0aRiN7/LVpyFA5RkJASZOVOib2Y8pNuhXa9V3ZbWO6v6kW/"},{"rawBytes":"MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="}]},"validFor":{"start":"2023-10-27T16:30:00Z","end":"2024-05-25T00:00:00Z"}},{"subject":{"organization":"GitHub, Inc.","commonName":"Internal Services Root"},"uri":"fulcio.githubapp.com","certChain":{"certificates":[{"rawBytes":"MIICKzCCAbCgAwIBAgIUOpyw2HaZefsj/4SPXutGof8E2CkwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMB4XDTI0MDUxMzAwMDAwMFoXDTI1MDUxMzAwMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEJNJWvW8fckfk/oQmh+qCeIlFXl9YLEkKSjZCgcVB92Fi1HQnvmpCiyqpvP91SmT1/G6QbrmTGV7MmIQlDnBWHNUT+jwZ3elGu/yfr/v8U0uhZTIli/BMj5Y4ICHK/j4do3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUD0fF3cs+ldPyiWohHJ3JmO91V7gwHwYDVR0jBBgwFoAUwOG4UqRLTz7eejgRBs9JjqFFmzMwCgYIKoZIzj0EAwMDaQAwZgIxAO7BRC9i7oGUHjjlcHU/bfqk2NLy7t6wm3K5W+jBLFbAj6sVjYcY+rrYhop/OjclbQIxALafBKLPIPjoCI29BUHwLBFP6e92ZlyaoFtoqccceXAevRaDjXFvb5+M7wnD6AuAJw=="},{"rawBytes":"MIICFTCCAZugAwIBAgIUD3Jlqt4qhrcZI4UnGfPGrEq/pjQwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDkxMTEyMDAwMFoXDTI4MDkwOTEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7X7nK0wC7uEmDjW+on0sXIX3FacL3hhcrhneA+M/kl1OtvQiPmFrH9lbUQqOj/AfspJ8uGY3jaq8WuSg6ghatzYfuuzLAJIK4nGpCBafncF8EynOssPq64/Dz+JUWXqlo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUwOG4UqRLTz7eejgRBs9JjqFFmzMwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAI8HWLrke7uzhOpwlD1cNixPmoX9XFKe7bEPozo0D+vKi0Gt6VlC7xPedFIw4/AypAIwQP+FGRWvfx0IAH5/n0aRiN7/LVpyFA5RkJASZOVOib2Y8pNuhXa9V3ZbWO6v6kW/"},{"rawBytes":"MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="}]},"validFor":{"start":"2024-05-13T00:00:00Z","end":"2024-10-25T00:00:00Z"}},{"subject":{"organization":"GitHub, Inc.","commonName":"Internal Services Root"},"uri":"fulcio.githubapp.com","certChain":{"certificates":[{"rawBytes":"MIICKzCCAbCgAwIBAgIUQeyd9UH06yZ63pDuqjgUZ58CnpMwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMB4XDTI0MTAwMzEyMDAwMFoXDTI1MTAwMzEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEwvbET2w+j9j9j50iTInH1gb9GSXkpsCvWz5orX1zgme+/Qh/5gMkpfmgfOSLV2ZRgT1hzujYmnKQvP2mCxYnbwQELAkAf+VhEY/7Uw3zZvguGQSdF1cxzRHiMTOha5eFo3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUMib9z4ZYBcQANTVvVCa3KoTGbBUwHwYDVR0jBBgwFoAUwOG4UqRLTz7eejgRBs9JjqFFmzMwCgYIKoZIzj0EAwMDaQAwZgIxAPIU/zlJiJrxn6oTWNdEAD/YBSnhyxcvpq1D2DzFy8E8hbkEfMZPErYL7HyoL/BkdwIxAN9KDEKyktEUBrfHehfcLAzI2kERJx+8DSslXswOIbLaeqYfWsmrQAt5C0X/nOWxXA=="},{"rawBytes":"MIICFTCCAZugAwIBAgIUD3Jlqt4qhrcZI4UnGfPGrEq/pjQwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDkxMTEyMDAwMFoXDTI4MDkwOTEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7X7nK0wC7uEmDjW+on0sXIX3FacL3hhcrhneA+M/kl1OtvQiPmFrH9lbUQqOj/AfspJ8uGY3jaq8WuSg6ghatzYfuuzLAJIK4nGpCBafncF8EynOssPq64/Dz+JUWXqlo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUwOG4UqRLTz7eejgRBs9JjqFFmzMwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAI8HWLrke7uzhOpwlD1cNixPmoX9XFKe7bEPozo0D+vKi0Gt6VlC7xPedFIw4/AypAIwQP+FGRWvfx0IAH5/n0aRiN7/LVpyFA5RkJASZOVOib2Y8pNuhXa9V3ZbWO6v6kW/"},{"rawBytes":"MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="}]},"validFor":{"start":"2024-10-07T00:00:00Z","end":"2025-06-19T00:00:00Z"}},{"subject":{"organization":"GitHub, Inc.","commonName":"Internal Services Root"},"uri":"fulcio.githubapp.com","certChain":{"certificates":[{"rawBytes":"MIICKTCCAbCgAwIBAgIUU7Y2PESb8FK88XCRQvI2rKDB5skwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMB4XDTI1MDUyNzAwMDAwMFoXDTI2MDUyNzAwMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEevMuOTGccTTXLu65xinRJP4gxkOemyAPCmUXEvhXKtLd+aDgN87PI0QxutOmPuQD2dLZVfxQmt5lvwJpUDIVYkKpH3mDxYbF6MJPMbq+A/EhkMaOS2+mcJRJdY2uam0Vo3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU8tE1L3b7FV3qJKzSqy1w97vPt6AwHwYDVR0jBBgwFoAUwOG4UqRLTz7eejgRBs9JjqFFmzMwCgYIKoZIzj0EAwMDZwAwZAIwSIsnfcDIvp+9SGBe9Es4xLDgZ6/B3wtODu+jtNUznQFPw1/WHa3iBMp3vYPkIqZDAjBBD4AKu166Hg7tDd5E8ckSCNNDkXD0Q7Xdl4yujgqv0goVMeOgRA1pKDGszhunjAQ="},{"rawBytes":"MIICFTCCAZugAwIBAgIUD3Jlqt4qhrcZI4UnGfPGrEq/pjQwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDkxMTEyMDAwMFoXDTI4MDkwOTEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZGdWxjaW8gSW50ZXJtZWRpYXRlIGwxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7X7nK0wC7uEmDjW+on0sXIX3FacL3hhcrhneA+M/kl1OtvQiPmFrH9lbUQqOj/AfspJ8uGY3jaq8WuSg6ghatzYfuuzLAJIK4nGpCBafncF8EynOssPq64/Dz+JUWXqlo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBATAdBgNVHQ4EFgQUwOG4UqRLTz7eejgRBs9JjqFFmzMwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAI8HWLrke7uzhOpwlD1cNixPmoX9XFKe7bEPozo0D+vKi0Gt6VlC7xPedFIw4/AypAIwQP+FGRWvfx0IAH5/n0aRiN7/LVpyFA5RkJASZOVOib2Y8pNuhXa9V3ZbWO6v6kW/"},{"rawBytes":"MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="}]},"validFor":{"start":"2025-05-27T00:00:00Z"}}],"timestampAuthorities":[{"subject":{"organization":"GitHub, Inc.","commonName":"Internal Services Root"},"uri":"timestamp.githubapp.com","certChain":{"certificates":[{"rawBytes":"MIICHDCCAaGgAwIBAgIUNDVlmtZuvoujn4KwiC/oxIr8hxAwCgYIKoZIzj0EAwMwMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMB4XDTIzMDgzMTEyMDAwMFoXDTI0MDgzMDEyMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgVGltZXN0YW1waW5nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEV/zJhNTdu0Fa9hGCUih/JvqEoE81tEWrAVwUXXhdRgIY9hIFErLhNo6sSOpV9d7Zuy0KWMHhcimCUr41a1732ByVRy3f+Z4QhqpsgFMh5b5J90HJLK7HOyUZjehAnvSno3gwdjAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUGwE6T5ZIh6lY9wP6vt42UHyVMewwHwYDVR0jBBgwFoAUdh+GTP65aetHLVLs9hdhGgDIKIwwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwCgYIKoZIzj0EAwMDaQAwZgIxAJo48LtrSsn0UmLtqGiUKg2EUvso+aDN5EyjpvMmobZ/Oq9zjnR7Of369hoABW4/1gIxANg5ZW4FqijhsXnA3md6jM9yLrLCI9QL+KnuZnXq6WgAcNQaAN7PNNjVDKV3iJEklw=="},{"rawBytes":"MIICJDCCAaqgAwIBAgIUckXVHpiw7iJY1V/jY8LYLj5TgqAwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTI4MDgwNTEyMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEISv96hTQ58QroEzzu4K+o9p8YkwDCBia2U7Y+VBNbOG/w1mLRibve9hSeUE1FSyLBMkiFSSm6MexcsbjyqOoNtRxuMinyYt6DSEox+/It2s/bTPyNAN0QP0DCQQOpnTZo3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUdh+GTP65aetHLVLs9hdhGgDIKIwwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAIhf+2E5W2yOb/fCDAjhL/G/jerf74M0tG/zyo32U2keawxkzZosDdwnPaHaGLynAQIwa8nr3en4fZz1AdOZm6nK5hr1qK2F94nifgnAJ/WeT0fZnK/oHan0R28x363qYuYH"},{"rawBytes":"MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="}]},"validFor":{"start":"2023-10-27T16:30:00Z","end":"2024-05-25T00:00:00Z"}},{"subject":{"organization":"GitHub, Inc.","commonName":"Internal Services Root"},"uri":"timestamp.githubapp.com","certChain":{"certificates":[{"rawBytes":"MIICGzCCAaGgAwIBAgIUPPgn6ner1PU/75CQ+62fdBuazaAwCgYIKoZIzj0EAwMwMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMB4XDTI0MDUxMzAwMDAwMFoXDTI1MDUxMzAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgVGltZXN0YW1waW5nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEA0pG1mAC4qafk1JJuOoIvhnMME9XmBDxjGFreDLnyzaexIzRw+UHUFy8C2gE6Me+0tIGQt4Ftbu66NGmfvBkR6boPMYQSU2O5X5ykZBm/9LR/Aqz0lgmBy/OlXvTJjglo3gwdjAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUDa4GVhd97Z2V8kiVl9DB0kC53CMwHwYDVR0jBBgwFoAUdh+GTP65aetHLVLs9hdhGgDIKIwwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwCgYIKoZIzj0EAwMDaAAwZQIwEQHd++b7IBAAuqT2/1i/wXf1WM2XrkFF6qd1c3kFcBVvdLQyJ5KoyNUHnfCCVJROAjEA+FoASOEcARlU6RqVcif9JthHwzh6nNwz0AfAHvO8xantN/7HjiLmFrFEGR/g0kN/"},{"rawBytes":"MIICJDCCAaqgAwIBAgIUckXVHpiw7iJY1V/jY8LYLj5TgqAwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTI4MDgwNTEyMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEISv96hTQ58QroEzzu4K+o9p8YkwDCBia2U7Y+VBNbOG/w1mLRibve9hSeUE1FSyLBMkiFSSm6MexcsbjyqOoNtRxuMinyYt6DSEox+/It2s/bTPyNAN0QP0DCQQOpnTZo3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUdh+GTP65aetHLVLs9hdhGgDIKIwwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAIhf+2E5W2yOb/fCDAjhL/G/jerf74M0tG/zyo32U2keawxkzZosDdwnPaHaGLynAQIwa8nr3en4fZz1AdOZm6nK5hr1qK2F94nifgnAJ/WeT0fZnK/oHan0R28x363qYuYH"},{"rawBytes":"MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="}]},"validFor":{"start":"2024-05-13T00:00:00Z","end":"2024-10-25T00:00:00Z"}},{"subject":{"organization":"GitHub, Inc.","commonName":"Internal Services Root"},"uri":"timestamp.githubapp.com","certChain":{"certificates":[{"rawBytes":"MIICGzCCAaGgAwIBAgIUH7swiMTn+svhcDh80OeZccDTj7AwCgYIKoZIzj0EAwMwMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMB4XDTI0MTAwNDEyMDAwMFoXDTI1MTAwNDEyMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgVGltZXN0YW1waW5nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEM7jdYNBTeD6hjym2/y73b50u2AFQsf8305Sr1NleOqamH9aWt6obhJQH3NoNUw9iFzHcDvafYWQFMu7SmOxS5n3aqwwfR8oJxKnEl36uCmGB+8TXS3B76SVTHEhG5rzOo3gwdjAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUQvz9YbWX3S6a+jruBkhRBiE2RCkwHwYDVR0jBBgwFoAUdh+GTP65aetHLVLs9hdhGgDIKIwwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwCgYIKoZIzj0EAwMDaAAwZQIxAI4dhu5iyx/g+z1vKAAWvHtebl1ZwsC+Vwgjm6Ttlq5yLNHHvYEnJ/h15Qv2IuXvdgIwZ8H/iy4lXsFJdFYSsB1/zavl24EgxSzxK/pCpihXMetYYDA/lX3xMyquisMx45rN"},{"rawBytes":"MIICJDCCAaqgAwIBAgIUckXVHpiw7iJY1V/jY8LYLj5TgqAwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTI4MDgwNTEyMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEISv96hTQ58QroEzzu4K+o9p8YkwDCBia2U7Y+VBNbOG/w1mLRibve9hSeUE1FSyLBMkiFSSm6MexcsbjyqOoNtRxuMinyYt6DSEox+/It2s/bTPyNAN0QP0DCQQOpnTZo3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUdh+GTP65aetHLVLs9hdhGgDIKIwwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAIhf+2E5W2yOb/fCDAjhL/G/jerf74M0tG/zyo32U2keawxkzZosDdwnPaHaGLynAQIwa8nr3en4fZz1AdOZm6nK5hr1qK2F94nifgnAJ/WeT0fZnK/oHan0R28x363qYuYH"},{"rawBytes":"MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="}]},"validFor":{"start":"2024-10-07T00:00:00Z","end":"2025-06-19T00:00:00Z"}},{"subject":{"organization":"GitHub, Inc.","commonName":"Internal Services Root"},"uri":"timestamp.githubapp.com","certChain":{"certificates":[{"rawBytes":"MIICGzCCAaGgAwIBAgIUaaAaeAO3NzYXR/muq5YMnGY34IUwCgYIKoZIzj0EAwMwMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMB4XDTI1MDUyNzAwMDAwMFoXDTI2MDUyNzAwMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgVGltZXN0YW1waW5nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEAjyAb+jWITMjkgbjWC02N05Zf5/kPs73ICf5/8lHSyHnoqqItP2z3fkMpWiRpelX8eIjzCjZ0Zht+DpGxtJUNh5jMa+fFv2qoDLGbnQ17DXKXCr9nWHF4vCMflHZK5JHo3gwdjAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUyvAcv8s1s37A/IWZAwdEptzjfWIwHwYDVR0jBBgwFoAUdh+GTP65aetHLVLs9hdhGgDIKIwwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwCgYIKoZIzj0EAwMDaAAwZQIwMnoHA4qDtR3eRohUIhvHGG70xT9XMfEnuj0kRvi9wu9VumL4pXqz1ktJgTBm6DrYAjEA5SYnRfUm2yAYmGEkJr9RvSCV9O7X+fL8KqfM4TOPrXJLMERQjhRXbWZ4DjxvifnF"},{"rawBytes":"MIICJDCCAaqgAwIBAgIUckXVHpiw7iJY1V/jY8LYLj5TgqAwCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTI4MDgwNTEyMDAwMFowMjEVMBMGA1UEChMMR2l0SHViLCBJbmMuMRkwFwYDVQQDExBUU0EgaW50ZXJtZWRpYXRlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEISv96hTQ58QroEzzu4K+o9p8YkwDCBia2U7Y+VBNbOG/w1mLRibve9hSeUE1FSyLBMkiFSSm6MexcsbjyqOoNtRxuMinyYt6DSEox+/It2s/bTPyNAN0QP0DCQQOpnTZo3sweTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUdh+GTP65aetHLVLs9hdhGgDIKIwwHwYDVR0jBBgwFoAUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaAAwZQIxAIhf+2E5W2yOb/fCDAjhL/G/jerf74M0tG/zyo32U2keawxkzZosDdwnPaHaGLynAQIwa8nr3en4fZz1AdOZm6nK5hr1qK2F94nifgnAJ/WeT0fZnK/oHan0R28x363qYuYH"},{"rawBytes":"MIIB9TCCAXqgAwIBAgIUNFryA06EHDIcd5EIbe8swbl9OY4wCgYIKoZIzj0EAwMwODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MB4XDTIzMDgwNzEyMDAwMFoXDTMzMDgwNDEyMDAwMFowODEVMBMGA1UEChMMR2l0SHViLCBJbmMuMR8wHQYDVQQDExZJbnRlcm5hbCBTZXJ2aWNlcyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEXYaXx4H0oNuVP/2cfydA3oaafvvkkkgb5hbL8/j/BO25S7uTmDOCA5e4QLLWCKFuc+xp2j14tCH4WmHzMUDvf2tXtInVliY5wZgQMM9L6klo/IwA9x4omdcjnT+kKJAjo0UwQzAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBAjAdBgNVHQ4EFgQUfFJ5/6rhfHEZPnXAhrQLhGkJJMwwCgYIKoZIzj0EAwMDaQAwZgIxAPzXsV+eokrqOHSQZH/XhhHE1slOscKy3DQpYpYJ1AWmJ2lJu/XOmubBX5s7apllUwIxALw2Ts8CDACiK42UymC8fk6sbNfoXUAWqdyKTVt2Lst+wNdkRniGvx7jT65BKTkcsQ=="}]},"validFor":{"start":"2025-05-27T00:00:00Z"}}]}
{
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "$id": "https://raw.githubusercontent.com/php/pie/main/resources/composer-json-php-ext-schema.json",
    "title": "composer.json php-ext schema",
    "description": "Schema for the proposed php-ext section in composer.json that the new PECL will use to build packages",
    "type": "object",
    "properties": {
        "php-ext": {
            "type": "object",
            "description": "Settings for PHP extension packages.",
            "properties": {
                "extension-name": {
                    "type": "string",
                    "description": "If specified, this will be used as the name of the extension, where needed by tooling. If this is not specified, the extension name will be derived from the Composer package name (e.g. `vendor/name` would become `ext-name`). The extension name may be specified with or without the `ext-` prefix, and tools that use this must normalise this appropriately.",
                    "example": "ext-xdebug"
                },
                "priority": {
                    "type": "integer",
                    "description": "This is used to add a prefix to the INI file, e.g. `90-xdebug.ini` which affects the loading order. The priority is a number in the range 10-99 inclusive, with 10 being the highest priority (i.e. will be processed first), and 99 being the lowest priority (i.e. will be processed last). There are two digits so that the files sort correctly on any platform, whether the sorting is natural or not.",
                    "minimum": 10,
                    "maximum": 99,
                    "example": 80,
                    "default": 80
                },
                "support-zts": {
                    "type": "boolean",
                    "description": "Does this package support Zend Thread Safety",
                    "example": false,
                    "default": true
                },
                "support-nts": {
                    "type": "boolean",
                    "description": "Does this package support non-Thread Safe mode",
                    "example": false,
                    "default": true
                },
                "build-path": {
                    "type": ["string", "null"],
                    "description": "If specified, this is the subdirectory that will be used to build the extension instead of the root of the project.",
                    "example": "my-extension-source",
                    "default": null
                },
                "download-url-method": {
                    "type": "string",
                    "description": "If specified, this technique will be used to override the URL that PIE uses to download the asset. The default, if not specified, is composer-default.",
                    "enum": ["composer-default", "pre-packaged-source"],
                    "example": "composer-default"
                },
                "os-families": {
                    "type": "array",
                    "minItems": 1,
                    "description": "An array of OS families to mark as compatible with the extension. Specifying this property will mean this package is not installable with PIE on any OS family not listed here. Must not be specified alongside os-families-exclude.",
                    "items": {
                        "type": "string",
                        "enum": ["windows", "bsd", "darwin", "solaris", "linux", "unknown"],
                        "description": "The name of the OS family to mark as compatible."
                    }
                },
                "os-families-exclude": {
                    "type": "array",
                    "minItems": 1,
                    "description": "An array of OS families to mark as incompatible with the extension. Specifying this property will mean this package is installable on any OS family except those listed here. Must not be specified alongside os-families.",
                    "items": {
                        "type": "string",
                        "enum": ["windows", "bsd", "darwin", "solaris", "linux", "unknown"],
                        "description": "The name of the OS family to exclude."
                    }
                },
                "configure-options": {
                    "type": "array",
                    "description": "These configure options make up the flags that can be passed to ./configure when installing the extension.",
                    "items": {
                        "type": "object",
                        "required": ["name"],
                        "properties": {
                            "name": {
                                "type": "string",
                                "description": "The name of the flag, this would typically be prefixed with `--`, for example, the value 'the-flag' would be passed as `./configure --the-flag`.",
                                "example": "without-xdebug-compression",
                                "pattern": "^[a-zA-Z0-9][a-zA-Z0-9-_]*$"
                            },
                            "needs-value": {
                                "type": "boolean",
                                "description": "If this is set to true, the flag needs a value (e.g. --with-somelib=<path>), otherwise it is a flag without a value (e.g. --enable-some-feature).",
                                "example": false,
                                "default": false
                            },
                            "description": {
                                "type": "string",
                                "description": "The description of what the flag does or means.",
                                "example": "Disable compression through zlib"
                            }
                        }
                    }
                }
            },
            "allOf": [
                {
                    "not": {
                        "required": ["os-families", "os-families-exclude"]
                    }
                }
            ]
        }
    }
}
<?php

// autoload.php @generated by Composer

if (PHP_VERSION_ID < 50600) {
    if (!headers_sent()) {
        header('HTTP/1.1 500 Internal Server Error');
    }
    $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
    if (!ini_get('display_errors')) {
        if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
            fwrite(STDERR, $err);
        } elseif (!headers_sent()) {
            echo $err;
        }
    }
    trigger_error(
        $err,
        E_USER_ERROR
    );
}

require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInitHumbugBox464::getLoader();

Copyright (c) Nils Adermann, Jordi Boggiano

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.

<?php

namespace HumbugBox464;

return array('root' => array('name' => 'humbug/requirement-checker', 'pretty_version' => '4.x.x-dev', 'version' => '4.9999999.9999999.9999999-dev', 'reference' => null, 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev' => \true), 'versions' => array('composer/semver' => array('pretty_version' => '3.4.0', 'version' => '3.4.0.0', 'reference' => '35e8d0af4486141bc745f23a29cc2091eb624a32', 'type' => 'library', 'install_path' => __DIR__ . '/./semver', 'aliases' => array(), 'dev_requirement' => \false), 'ergebnis/composer-normalize' => array('pretty_version' => '2.43.0', 'version' => '2.43.0.0', 'reference' => '4b46330c84bb8f43fac79f5c5a05162fc7c80d75', 'type' => 'composer-plugin', 'install_path' => __DIR__ . '/../ergebnis/composer-normalize', 'aliases' => array(), 'dev_requirement' => \true), 'ergebnis/json' => array('pretty_version' => '1.2.0', 'version' => '1.2.0.0', 'reference' => 'a457f25a5ba7ea11fc94f84d53678c5211abfce0', 'type' => 'library', 'install_path' => __DIR__ . '/../ergebnis/json', 'aliases' => array(), 'dev_requirement' => \true), 'ergebnis/json-normalizer' => array('pretty_version' => '4.5.0', 'version' => '4.5.0.0', 'reference' => 'f0ee9e70739f121b27fac8b743e4a52b23de2152', 'type' => 'library', 'install_path' => __DIR__ . '/../ergebnis/json-normalizer', 'aliases' => array(), 'dev_requirement' => \true), 'ergebnis/json-pointer' => array('pretty_version' => '3.4.0', 'version' => '3.4.0.0', 'reference' => 'b654757d873050622c2166f55ab25d04685261c5', 'type' => 'library', 'install_path' => __DIR__ . '/../ergebnis/json-pointer', 'aliases' => array(), 'dev_requirement' => \true), 'ergebnis/json-printer' => array('pretty_version' => '3.5.0', 'version' => '3.5.0.0', 'reference' => '549e16fe6de34b8c3aee7b421be12caa552f3ced', 'type' => 'library', 'install_path' => __DIR__ . '/../ergebnis/json-printer', 'aliases' => array(), 'dev_requirement' => \true), 'ergebnis/json-schema-validator' => array('pretty_version' => '4.2.0', 'version' => '4.2.0.0', 'reference' => '10ed514fdc3f9b71f8a92c567afea21a2f6fa1ef', 'type' => 'library', 'install_path' => __DIR__ . '/../ergebnis/json-schema-validator', 'aliases' => array(), 'dev_requirement' => \true), 'fidry/makefile' => array('pretty_version' => '1.0.1', 'version' => '1.0.1.0', 'reference' => 'be5a048dcc5648d04e924facce0f85b406d731e4', 'type' => 'library', 'install_path' => __DIR__ . '/../fidry/makefile', 'aliases' => array(), 'dev_requirement' => \true), 'humbug/requirement-checker' => array('pretty_version' => '4.x.x-dev', 'version' => '4.9999999.9999999.9999999-dev', 'reference' => null, 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev_requirement' => \false), 'justinrainbow/json-schema' => array('pretty_version' => 'v5.2.13', 'version' => '5.2.13.0', 'reference' => 'fbbe7e5d79f618997bc3332a6f49246036c45793', 'type' => 'library', 'install_path' => __DIR__ . '/../justinrainbow/json-schema', 'aliases' => array(), 'dev_requirement' => \true), 'localheinz/diff' => array('pretty_version' => '1.1.1', 'version' => '1.1.1.0', 'reference' => '851bb20ea8358c86f677f5f111c4ab031b1c764c', 'type' => 'library', 'install_path' => __DIR__ . '/../localheinz/diff', 'aliases' => array(), 'dev_requirement' => \true), 'myclabs/deep-copy' => array('pretty_version' => '1.12.0', 'version' => '1.12.0.0', 'reference' => '3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c', 'type' => 'library', 'install_path' => __DIR__ . '/../myclabs/deep-copy', 'aliases' => array(), 'dev_requirement' => \true), 'nikic/php-parser' => array('pretty_version' => 'v5.1.0', 'version' => '5.1.0.0', 'reference' => '683130c2ff8c2739f4822ff7ac5c873ec529abd1', 'type' => 'library', 'install_path' => __DIR__ . '/../nikic/php-parser', 'aliases' => array(), 'dev_requirement' => \true), 'phar-io/manifest' => array('pretty_version' => '2.0.4', 'version' => '2.0.4.0', 'reference' => '54750ef60c58e43759730615a392c31c80e23176', 'type' => 'library', 'install_path' => __DIR__ . '/../phar-io/manifest', 'aliases' => array(), 'dev_requirement' => \true), 'phar-io/version' => array('pretty_version' => '3.2.1', 'version' => '3.2.1.0', 'reference' => '4f7fd7836c6f332bb2933569e566a0d6c4cbed74', 'type' => 'library', 'install_path' => __DIR__ . '/../phar-io/version', 'aliases' => array(), 'dev_requirement' => \true), 'phpunit/php-code-coverage' => array('pretty_version' => '10.1.15', 'version' => '10.1.15.0', 'reference' => '5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae', 'type' => 'library', 'install_path' => __DIR__ . '/../phpunit/php-code-coverage', 'aliases' => array(), 'dev_requirement' => \true), 'phpunit/php-file-iterator' => array('pretty_version' => '4.1.0', 'version' => '4.1.0.0', 'reference' => 'a95037b6d9e608ba092da1b23931e537cadc3c3c', 'type' => 'library', 'install_path' => __DIR__ . '/../phpunit/php-file-iterator', 'aliases' => array(), 'dev_requirement' => \true), 'phpunit/php-invoker' => array('pretty_version' => '4.0.0', 'version' => '4.0.0.0', 'reference' => 'f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7', 'type' => 'library', 'install_path' => __DIR__ . '/../phpunit/php-invoker', 'aliases' => array(), 'dev_requirement' => \true), 'phpunit/php-text-template' => array('pretty_version' => '3.0.1', 'version' => '3.0.1.0', 'reference' => '0c7b06ff49e3d5072f057eb1fa59258bf287a748', 'type' => 'library', 'install_path' => __DIR__ . '/../phpunit/php-text-template', 'aliases' => array(), 'dev_requirement' => \true), 'phpunit/php-timer' => array('pretty_version' => '6.0.0', 'version' => '6.0.0.0', 'reference' => 'e2a2d67966e740530f4a3343fe2e030ffdc1161d', 'type' => 'library', 'install_path' => __DIR__ . '/../phpunit/php-timer', 'aliases' => array(), 'dev_requirement' => \true), 'phpunit/phpunit' => array('pretty_version' => '10.5.26', 'version' => '10.5.26.0', 'reference' => '42e2f13ceaa2e34461bc89bea75407550b40b2aa', 'type' => 'library', 'install_path' => __DIR__ . '/../phpunit/phpunit', 'aliases' => array(), 'dev_requirement' => \true), 'sebastian/cli-parser' => array('pretty_version' => '2.0.1', 'version' => '2.0.1.0', 'reference' => 'c34583b87e7b7a8055bf6c450c2c77ce32a24084', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/cli-parser', 'aliases' => array(), 'dev_requirement' => \true), 'sebastian/code-unit' => array('pretty_version' => '2.0.0', 'version' => '2.0.0.0', 'reference' => 'a81fee9eef0b7a76af11d121767abc44c104e503', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/code-unit', 'aliases' => array(), 'dev_requirement' => \true), 'sebastian/code-unit-reverse-lookup' => array('pretty_version' => '3.0.0', 'version' => '3.0.0.0', 'reference' => '5e3a687f7d8ae33fb362c5c0743794bbb2420a1d', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/code-unit-reverse-lookup', 'aliases' => array(), 'dev_requirement' => \true), 'sebastian/comparator' => array('pretty_version' => '5.0.1', 'version' => '5.0.1.0', 'reference' => '2db5010a484d53ebf536087a70b4a5423c102372', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/comparator', 'aliases' => array(), 'dev_requirement' => \true), 'sebastian/complexity' => array('pretty_version' => '3.2.0', 'version' => '3.2.0.0', 'reference' => '68ff824baeae169ec9f2137158ee529584553799', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/complexity', 'aliases' => array(), 'dev_requirement' => \true), 'sebastian/diff' => array('pretty_version' => '5.1.1', 'version' => '5.1.1.0', 'reference' => 'c41e007b4b62af48218231d6c2275e4c9b975b2e', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/diff', 'aliases' => array(), 'dev_requirement' => \true), 'sebastian/environment' => array('pretty_version' => '6.1.0', 'version' => '6.1.0.0', 'reference' => '8074dbcd93529b357029f5cc5058fd3e43666984', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/environment', 'aliases' => array(), 'dev_requirement' => \true), 'sebastian/exporter' => array('pretty_version' => '5.1.2', 'version' => '5.1.2.0', 'reference' => '955288482d97c19a372d3f31006ab3f37da47adf', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/exporter', 'aliases' => array(), 'dev_requirement' => \true), 'sebastian/global-state' => array('pretty_version' => '6.0.2', 'version' => '6.0.2.0', 'reference' => '987bafff24ecc4c9ac418cab1145b96dd6e9cbd9', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/global-state', 'aliases' => array(), 'dev_requirement' => \true), 'sebastian/lines-of-code' => array('pretty_version' => '2.0.2', 'version' => '2.0.2.0', 'reference' => '856e7f6a75a84e339195d48c556f23be2ebf75d0', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/lines-of-code', 'aliases' => array(), 'dev_requirement' => \true), 'sebastian/object-enumerator' => array('pretty_version' => '5.0.0', 'version' => '5.0.0.0', 'reference' => '202d0e344a580d7f7d04b3fafce6933e59dae906', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/object-enumerator', 'aliases' => array(), 'dev_requirement' => \true), 'sebastian/object-reflector' => array('pretty_version' => '3.0.0', 'version' => '3.0.0.0', 'reference' => '24ed13d98130f0e7122df55d06c5c4942a577957', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/object-reflector', 'aliases' => array(), 'dev_requirement' => \true), 'sebastian/recursion-context' => array('pretty_version' => '5.0.0', 'version' => '5.0.0.0', 'reference' => '05909fb5bc7df4c52992396d0116aed689f93712', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/recursion-context', 'aliases' => array(), 'dev_requirement' => \true), 'sebastian/type' => array('pretty_version' => '4.0.0', 'version' => '4.0.0.0', 'reference' => '462699a16464c3944eefc02ebdd77882bd3925bf', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/type', 'aliases' => array(), 'dev_requirement' => \true), 'sebastian/version' => array('pretty_version' => '4.0.1', 'version' => '4.0.1.0', 'reference' => 'c51fa83a5d8f43f1402e3f32a005e6262244ef17', 'type' => 'library', 'install_path' => __DIR__ . '/../sebastian/version', 'aliases' => array(), 'dev_requirement' => \true), 'symfony/deprecation-contracts' => array('pretty_version' => 'v3.5.0', 'version' => '3.5.0.0', 'reference' => '0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', 'aliases' => array(), 'dev_requirement' => \true), 'symfony/polyfill-ctype' => array('pretty_version' => 'v1.30.0', 'version' => '1.30.0.0', 'reference' => '0424dff1c58f028c451efff2045f5d92410bd540', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-ctype', 'aliases' => array(), 'dev_requirement' => \true), 'symfony/yaml' => array('pretty_version' => 'v6.4.8', 'version' => '6.4.8.0', 'reference' => '52903de178d542850f6f341ba92995d3d63e60c9', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/yaml', 'aliases' => array(), 'dev_requirement' => \true), 'thecodingmachine/safe' => array('pretty_version' => 'v2.5.0', 'version' => '2.5.0.0', 'reference' => '3115ecd6b4391662b4931daac4eba6b07a2ac1f0', 'type' => 'library', 'install_path' => __DIR__ . '/../thecodingmachine/safe', 'aliases' => array(), 'dev_requirement' => \true), 'theseer/tokenizer' => array('pretty_version' => '1.2.3', 'version' => '1.2.3.0', 'reference' => '737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2', 'type' => 'library', 'install_path' => __DIR__ . '/../theseer/tokenizer', 'aliases' => array(), 'dev_requirement' => \true)));
Copyright (C) 2015 Composer

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.
<?php

namespace HumbugBox464\Composer\Semver;

use HumbugBox464\Composer\Semver\Constraint\Constraint;
class Comparator
{
    public static function greaterThan($version1, $version2)
    {
        return self::compare($version1, '>', $version2);
    }
    public static function greaterThanOrEqualTo($version1, $version2)
    {
        return self::compare($version1, '>=', $version2);
    }
    public static function lessThan($version1, $version2)
    {
        return self::compare($version1, '<', $version2);
    }
    public static function lessThanOrEqualTo($version1, $version2)
    {
        return self::compare($version1, '<=', $version2);
    }
    public static function equalTo($version1, $version2)
    {
        return self::compare($version1, '==', $version2);
    }
    public static function notEqualTo($version1, $version2)
    {
        return self::compare($version1, '!=', $version2);
    }
    /**
    @phpstan-param
    */
    public static function compare($version1, $operator, $version2)
    {
        $constraint = new Constraint($operator, $version2);
        return $constraint->matchSpecific(new Constraint('==', $version1), \true);
    }
}
<?php

namespace HumbugBox464\Composer\Semver;

use HumbugBox464\Composer\Semver\Constraint\ConstraintInterface;
use HumbugBox464\Composer\Semver\Constraint\MatchAllConstraint;
use HumbugBox464\Composer\Semver\Constraint\MultiConstraint;
use HumbugBox464\Composer\Semver\Constraint\Constraint;
class VersionParser
{
    private static $modifierRegex = '[._-]?(?:(stable|beta|b|RC|alpha|a|patch|pl|p)((?:[.-]?\d+)*+)?)?([.-]?dev)?';
    private static $stabilitiesRegex = 'stable|RC|beta|alpha|dev';
    /**
    @phpstan-return
    */
    public static function parseStability($version)
    {
        $version = (string) preg_replace('{#.+$}', '', (string) $version);
        if (strpos($version, 'dev-') === 0 || '-dev' === substr($version, -4)) {
            return 'dev';
        }
        preg_match('{' . self::$modifierRegex . '(?:\+.*)?$}i', strtolower($version), $match);
        if (!empty($match[3])) {
            return 'dev';
        }
        if (!empty($match[1])) {
            if ('beta' === $match[1] || 'b' === $match[1]) {
                return 'beta';
            }
            if ('alpha' === $match[1] || 'a' === $match[1]) {
                return 'alpha';
            }
            if ('rc' === $match[1]) {
                return 'RC';
            }
        }
        return 'stable';
    }
    public static function normalizeStability($stability)
    {
        $stability = strtolower((string) $stability);
        return $stability === 'rc' ? 'RC' : $stability;
    }
    public function normalize($version, $fullVersion = null)
    {
        $version = trim((string) $version);
        $origVersion = $version;
        if (null === $fullVersion) {
            $fullVersion = $version;
        }
        if (preg_match('{^([^,\s]++) ++as ++([^,\s]++)$}', $version, $match)) {
            $version = $match[1];
        }
        if (preg_match('{@(?:' . self::$stabilitiesRegex . ')$}i', $version, $match)) {
            $version = substr($version, 0, strlen($version) - strlen($match[0]));
        }
        if (\in_array($version, array('master', 'trunk', 'default'), \true)) {
            $version = 'dev-' . $version;
        }
        if (stripos($version, 'dev-') === 0) {
            return 'dev-' . substr($version, 4);
        }
        if (preg_match('{^([^,\s+]++)\+[^\s]++$}', $version, $match)) {
            $version = $match[1];
        }
        if (preg_match('{^v?(\d{1,5}+)(\.\d++)?(\.\d++)?(\.\d++)?' . self::$modifierRegex . '$}i', $version, $matches)) {
            $version = $matches[1] . (!empty($matches[2]) ? $matches[2] : '.0') . (!empty($matches[3]) ? $matches[3] : '.0') . (!empty($matches[4]) ? $matches[4] : '.0');
            $index = 5;
        } elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3}){0,2})' . self::$modifierRegex . '$}i', $version, $matches)) {
            $version = (string) preg_replace('{\D}', '.', $matches[1]);
            $index = 2;
        }
        if (isset($index)) {
            if (!empty($matches[$index])) {
                if ('stable' === $matches[$index]) {
                    return $version;
                }
                $version .= '-' . $this->expandStability($matches[$index]) . (isset($matches[$index + 1]) && '' !== $matches[$index + 1] ? ltrim($matches[$index + 1], '.-') : '');
            }
            if (!empty($matches[$index + 2])) {
                $version .= '-dev';
            }
            return $version;
        }
        if (preg_match('{(.*?)[.-]?dev$}i', $version, $match)) {
            try {
                $normalized = $this->normalizeBranch($match[1]);
                if (strpos($normalized, 'dev-') === \false) {
                    return $normalized;
                }
            } catch (\Exception $e) {
            }
        }
        $extraMessage = '';
        if (preg_match('{ +as +' . preg_quote($version) . '(?:@(?:' . self::$stabilitiesRegex . '))?$}', $fullVersion)) {
            $extraMessage = ' in "' . $fullVersion . '", the alias must be an exact version';
        } elseif (preg_match('{^' . preg_quote($version) . '(?:@(?:' . self::$stabilitiesRegex . '))? +as +}', $fullVersion)) {
            $extraMessage = ' in "' . $fullVersion . '", the alias source must be an exact version, if it is a branch name you should prefix it with dev-';
        }
        throw new \UnexpectedValueException('Invalid version string "' . $origVersion . '"' . $extraMessage);
    }
    public function parseNumericAliasPrefix($branch)
    {
        if (preg_match('{^(?P<version>(\d++\.)*\d++)(?:\.x)?-dev$}i', (string) $branch, $matches)) {
            return $matches['version'] . '.';
        }
        return \false;
    }
    public function normalizeBranch($name)
    {
        $name = trim((string) $name);
        if (preg_match('{^v?(\d++)(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?(\.(?:\d++|[xX*]))?$}i', $name, $matches)) {
            $version = '';
            for ($i = 1; $i < 5; ++$i) {
                $version .= isset($matches[$i]) ? str_replace(array('*', 'X'), 'x', $matches[$i]) : '.x';
            }
            return str_replace('x', '9999999', $version) . '-dev';
        }
        return 'dev-' . $name;
    }
    public function normalizeDefaultBranch($name)
    {
        if ($name === 'dev-master' || $name === 'dev-default' || $name === 'dev-trunk') {
            return '9999999-dev';
        }
        return (string) $name;
    }
    public function parseConstraints($constraints)
    {
        $prettyConstraint = (string) $constraints;
        $orConstraints = preg_split('{\s*\|\|?\s*}', trim((string) $constraints));
        if (\false === $orConstraints) {
            throw new \RuntimeException('Failed to preg_split string: ' . $constraints);
        }
        $orGroups = array();
        foreach ($orConstraints as $orConstraint) {
            $andConstraints = preg_split('{(?<!^|as|[=>< ,]) *(?<!-)[, ](?!-) *(?!,|as|$)}', $orConstraint);
            if (\false === $andConstraints) {
                throw new \RuntimeException('Failed to preg_split string: ' . $orConstraint);
            }
            if (\count($andConstraints) > 1) {
                $constraintObjects = array();
                foreach ($andConstraints as $andConstraint) {
                    foreach ($this->parseConstraint($andConstraint) as $parsedAndConstraint) {
                        $constraintObjects[] = $parsedAndConstraint;
                    }
                }
            } else {
                $constraintObjects = $this->parseConstraint($andConstraints[0]);
            }
            if (1 === \count($constraintObjects)) {
                $constraint = $constraintObjects[0];
            } else {
                $constraint = new MultiConstraint($constraintObjects);
            }
            $orGroups[] = $constraint;
        }
        $parsedConstraint = MultiConstraint::create($orGroups, \false);
        $parsedConstraint->setPrettyString($prettyConstraint);
        return $parsedConstraint;
    }
    /**
    @phpstan-return
    */
    private function parseConstraint($constraint)
    {
        if (preg_match('{^([^,\s]++) ++as ++([^,\s]++)$}', $constraint, $match)) {
            $constraint = $match[1];
        }
        if (preg_match('{^([^,\s]*?)@(' . self::$stabilitiesRegex . ')$}i', $constraint, $match)) {
            $constraint = '' !== $match[1] ? $match[1] : '*';
            if ($match[2] !== 'stable') {
                $stabilityModifier = $match[2];
            }
        }
        if (preg_match('{^(dev-[^,\s@]+?|[^,\s@]+?\.x-dev)#.+$}i', $constraint, $match)) {
            $constraint = $match[1];
        }
        if (preg_match('{^(v)?[xX*](\.[xX*])*$}i', $constraint, $match)) {
            if (!empty($match[1]) || !empty($match[2])) {
                return array(new Constraint('>=', '0.0.0.0-dev'));
            }
            return array(new MatchAllConstraint());
        }
        $versionRegex = 'v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.(\d++))?(?:' . self::$modifierRegex . '|\.([xX*][.-]?dev))(?:\+[^\s]+)?';
        if (preg_match('{^~>?' . $versionRegex . '$}i', $constraint, $matches)) {
            if (strpos($constraint, '~>') === 0) {
                throw new \UnexpectedValueException('Could not parse version constraint ' . $constraint . ': ' . 'Invalid operator "~>", you probably meant to use the "~" operator');
            }
            if (isset($matches[4]) && '' !== $matches[4] && null !== $matches[4]) {
                $position = 4;
            } elseif (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) {
                $position = 3;
            } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) {
                $position = 2;
            } else {
                $position = 1;
            }
            if (!empty($matches[8])) {
                $position++;
            }
            $stabilitySuffix = '';
            if (empty($matches[5]) && empty($matches[7]) && empty($matches[8])) {
                $stabilitySuffix .= '-dev';
            }
            $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1));
            $lowerBound = new Constraint('>=', $lowVersion);
            $highPosition = max(1, $position - 1);
            $highVersion = $this->manipulateVersionString($matches, $highPosition, 1) . '-dev';
            $upperBound = new Constraint('<', $highVersion);
            return array($lowerBound, $upperBound);
        }
        if (preg_match('{^\^' . $versionRegex . '($)}i', $constraint, $matches)) {
            if ('0' !== $matches[1] || '' === $matches[2] || null === $matches[2]) {
                $position = 1;
            } elseif ('0' !== $matches[2] || '' === $matches[3] || null === $matches[3]) {
                $position = 2;
            } else {
                $position = 3;
            }
            $stabilitySuffix = '';
            if (empty($matches[5]) && empty($matches[7]) && empty($matches[8])) {
                $stabilitySuffix .= '-dev';
            }
            $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1));
            $lowerBound = new Constraint('>=', $lowVersion);
            $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev';
            $upperBound = new Constraint('<', $highVersion);
            return array($lowerBound, $upperBound);
        }
        if (preg_match('{^v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.[xX*])++$}', $constraint, $matches)) {
            if (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) {
                $position = 3;
            } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) {
                $position = 2;
            } else {
                $position = 1;
            }
            $lowVersion = $this->manipulateVersionString($matches, $position) . '-dev';
            $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev';
            if ($lowVersion === '0.0.0.0-dev') {
                return array(new Constraint('<', $highVersion));
            }
            return array(new Constraint('>=', $lowVersion), new Constraint('<', $highVersion));
        }
        if (preg_match('{^(?P<from>' . $versionRegex . ') +- +(?P<to>' . $versionRegex . ')($)}i', $constraint, $matches)) {
            $lowStabilitySuffix = '';
            if (empty($matches[6]) && empty($matches[8]) && empty($matches[9])) {
                $lowStabilitySuffix = '-dev';
            }
            $lowVersion = $this->normalize($matches['from']);
            $lowerBound = new Constraint('>=', $lowVersion . $lowStabilitySuffix);
            $empty = function ($x) {
                return $x === 0 || $x === '0' ? \false : empty($x);
            };
            if (!$empty($matches[12]) && !$empty($matches[13]) || !empty($matches[15]) || !empty($matches[17]) || !empty($matches[18])) {
                $highVersion = $this->normalize($matches['to']);
                $upperBound = new Constraint('<=', $highVersion);
            } else {
                $highMatch = array('', $matches[11], $matches[12], $matches[13], $matches[14]);
                $this->normalize($matches['to']);
                $highVersion = $this->manipulateVersionString($highMatch, $empty($matches[12]) ? 1 : 2, 1) . '-dev';
                $upperBound = new Constraint('<', $highVersion);
            }
            return array($lowerBound, $upperBound);
        }
        if (preg_match('{^(<>|!=|>=?|<=?|==?)?\s*(.*)}', $constraint, $matches)) {
            try {
                try {
                    $version = $this->normalize($matches[2]);
                } catch (\UnexpectedValueException $e) {
                    if (substr($matches[2], -4) === '-dev' && preg_match('{^[0-9a-zA-Z-./]+$}', $matches[2])) {
                        $version = $this->normalize('dev-' . substr($matches[2], 0, -4));
                    } else {
                        throw $e;
                    }
                }
                $op = $matches[1] ?: '=';
                if ($op !== '==' && $op !== '=' && !empty($stabilityModifier) && self::parseStability($version) === 'stable') {
                    $version .= '-' . $stabilityModifier;
                } elseif ('<' === $op || '>=' === $op) {
                    if (!preg_match('/-' . self::$modifierRegex . '$/', strtolower($matches[2]))) {
                        if (strpos($matches[2], 'dev-') !== 0) {
                            $version .= '-dev';
                        }
                    }
                }
                return array(new Constraint($matches[1] ?: '=', $version));
            } catch (\Exception $e) {
            }
        }
        $message = 'Could not parse version constraint ' . $constraint;
        if (isset($e)) {
            $message .= ': ' . $e->getMessage();
        }
        throw new \UnexpectedValueException($message);
    }
    /**
    @phpstan-param
    */
    private function manipulateVersionString(array $matches, $position, $increment = 0, $pad = '0')
    {
        for ($i = 4; $i > 0; --$i) {
            if ($i > $position) {
                $matches[$i] = $pad;
            } elseif ($i === $position && $increment) {
                $matches[$i] += $increment;
                if ($matches[$i] < 0) {
                    $matches[$i] = $pad;
                    --$position;
                    if ($i === 1) {
                        return null;
                    }
                }
            }
        }
        return $matches[1] . '.' . $matches[2] . '.' . $matches[3] . '.' . $matches[4];
    }
    private function expandStability($stability)
    {
        $stability = strtolower($stability);
        switch ($stability) {
            case 'a':
                return 'alpha';
            case 'b':
                return 'beta';
            case 'p':
            case 'pl':
                return 'patch';
            case 'rc':
                return 'RC';
            default:
                return $stability;
        }
    }
}
<?php

namespace HumbugBox464\Composer\Semver;

use HumbugBox464\Composer\Semver\Constraint\Constraint;
use HumbugBox464\Composer\Semver\Constraint\ConstraintInterface;
class CompilingMatcher
{
    /**
    @phpstan-var
    */
    private static $compiledCheckerCache = array();
    /**
    @phpstan-var
    */
    private static $resultCache = array();
    private static $enabled;
    /**
    @phpstan-var
    */
    private static $transOpInt = array(Constraint::OP_EQ => Constraint::STR_OP_EQ, Constraint::OP_LT => Constraint::STR_OP_LT, Constraint::OP_LE => Constraint::STR_OP_LE, Constraint::OP_GT => Constraint::STR_OP_GT, Constraint::OP_GE => Constraint::STR_OP_GE, Constraint::OP_NE => Constraint::STR_OP_NE);
    public static function clear()
    {
        self::$resultCache = array();
        self::$compiledCheckerCache = array();
    }
    /**
    @phpstan-param
    */
    public static function match(ConstraintInterface $constraint, $operator, $version)
    {
        $resultCacheKey = $operator . $constraint . ';' . $version;
        if (isset(self::$resultCache[$resultCacheKey])) {
            return self::$resultCache[$resultCacheKey];
        }
        if (self::$enabled === null) {
            self::$enabled = !\in_array('eval', explode(',', (string) ini_get('disable_functions')), \true);
        }
        if (!self::$enabled) {
            return self::$resultCache[$resultCacheKey] = $constraint->matches(new Constraint(self::$transOpInt[$operator], $version));
        }
        $cacheKey = $operator . $constraint;
        if (!isset(self::$compiledCheckerCache[$cacheKey])) {
            $code = $constraint->compile($operator);
            self::$compiledCheckerCache[$cacheKey] = $function = eval('return function($v, $b){return ' . $code . ';};');
        } else {
            $function = self::$compiledCheckerCache[$cacheKey];
        }
        return self::$resultCache[$resultCacheKey] = $function($version, strpos($version, 'dev-') === 0);
    }
}
<?php

namespace HumbugBox464\Composer\Semver;

use HumbugBox464\Composer\Semver\Constraint\Constraint;
class Semver
{
    const SORT_ASC = 1;
    const SORT_DESC = -1;
    private static $versionParser;
    public static function satisfies($version, $constraints)
    {
        if (null === self::$versionParser) {
            self::$versionParser = new VersionParser();
        }
        $versionParser = self::$versionParser;
        $provider = new Constraint('==', $versionParser->normalize($version));
        $parsedConstraints = $versionParser->parseConstraints($constraints);
        return $parsedConstraints->matches($provider);
    }
    public static function satisfiedBy(array $versions, $constraints)
    {
        $versions = array_filter($versions, function ($version) use ($constraints) {
            return Semver::satisfies($version, $constraints);
        });
        return array_values($versions);
    }
    public static function sort(array $versions)
    {
        return self::usort($versions, self::SORT_ASC);
    }
    public static function rsort(array $versions)
    {
        return self::usort($versions, self::SORT_DESC);
    }
    private static function usort(array $versions, $direction)
    {
        if (null === self::$versionParser) {
            self::$versionParser = new VersionParser();
        }
        $versionParser = self::$versionParser;
        $normalized = array();
        foreach ($versions as $key => $version) {
            $normalizedVersion = $versionParser->normalize($version);
            $normalizedVersion = $versionParser->normalizeDefaultBranch($normalizedVersion);
            $normalized[] = array($normalizedVersion, $key);
        }
        usort($normalized, function (array $left, array $right) use ($direction) {
            if ($left[0] === $right[0]) {
                return 0;
            }
            if (Comparator::lessThan($left[0], $right[0])) {
                return -$direction;
            }
            return $direction;
        });
        $sorted = array();
        foreach ($normalized as $item) {
            $sorted[] = $versions[$item[1]];
        }
        return $sorted;
    }
}
<?php

namespace HumbugBox464\Composer\Semver;

use HumbugBox464\Composer\Semver\Constraint\Constraint;
class Interval
{
    private $start;
    private $end;
    public function __construct(Constraint $start, Constraint $end)
    {
        $this->start = $start;
        $this->end = $end;
    }
    public function getStart()
    {
        return $this->start;
    }
    public function getEnd()
    {
        return $this->end;
    }
    public static function fromZero()
    {
        static $zero;
        if (null === $zero) {
            $zero = new Constraint('>=', '0.0.0.0-dev');
        }
        return $zero;
    }
    public static function untilPositiveInfinity()
    {
        static $positiveInfinity;
        if (null === $positiveInfinity) {
            $positiveInfinity = new Constraint('<', \PHP_INT_MAX . '.0.0.0');
        }
        return $positiveInfinity;
    }
    public static function any()
    {
        return new self(self::fromZero(), self::untilPositiveInfinity());
    }
    public static function anyDev()
    {
        return array('names' => array(), 'exclude' => \true);
    }
    public static function noDev()
    {
        return array('names' => array(), 'exclude' => \false);
    }
}
<?php

namespace HumbugBox464\Composer\Semver;

use HumbugBox464\Composer\Semver\Constraint\Constraint;
use HumbugBox464\Composer\Semver\Constraint\ConstraintInterface;
use HumbugBox464\Composer\Semver\Constraint\MatchAllConstraint;
use HumbugBox464\Composer\Semver\Constraint\MatchNoneConstraint;
use HumbugBox464\Composer\Semver\Constraint\MultiConstraint;
class Intervals
{
    /**
    @phpstan-var
    */
    private static $intervalsCache = array();
    /**
    @phpstan-var
    */
    private static $opSortOrder = array('>=' => -3, '<' => -2, '>' => 2, '<=' => 3);
    public static function clear()
    {
        self::$intervalsCache = array();
    }
    public static function isSubsetOf(ConstraintInterface $candidate, ConstraintInterface $constraint)
    {
        if ($constraint instanceof MatchAllConstraint) {
            return \true;
        }
        if ($candidate instanceof MatchNoneConstraint || $constraint instanceof MatchNoneConstraint) {
            return \false;
        }
        $intersectionIntervals = self::get(new MultiConstraint(array($candidate, $constraint), \true));
        $candidateIntervals = self::get($candidate);
        if (\count($intersectionIntervals['numeric']) !== \count($candidateIntervals['numeric'])) {
            return \false;
        }
        foreach ($intersectionIntervals['numeric'] as $index => $interval) {
            if (!isset($candidateIntervals['numeric'][$index])) {
                return \false;
            }
            if ((string) $candidateIntervals['numeric'][$index]->getStart() !== (string) $interval->getStart()) {
                return \false;
            }
            if ((string) $candidateIntervals['numeric'][$index]->getEnd() !== (string) $interval->getEnd()) {
                return \false;
            }
        }
        if ($intersectionIntervals['branches']['exclude'] !== $candidateIntervals['branches']['exclude']) {
            return \false;
        }
        if (\count($intersectionIntervals['branches']['names']) !== \count($candidateIntervals['branches']['names'])) {
            return \false;
        }
        foreach ($intersectionIntervals['branches']['names'] as $index => $name) {
            if ($name !== $candidateIntervals['branches']['names'][$index]) {
                return \false;
            }
        }
        return \true;
    }
    public static function haveIntersections(ConstraintInterface $a, ConstraintInterface $b)
    {
        if ($a instanceof MatchAllConstraint || $b instanceof MatchAllConstraint) {
            return \true;
        }
        if ($a instanceof MatchNoneConstraint || $b instanceof MatchNoneConstraint) {
            return \false;
        }
        $intersectionIntervals = self::generateIntervals(new MultiConstraint(array($a, $b), \true), \true);
        return \count($intersectionIntervals['numeric']) > 0 || $intersectionIntervals['branches']['exclude'] || \count($intersectionIntervals['branches']['names']) > 0;
    }
    public static function compactConstraint(ConstraintInterface $constraint)
    {
        if (!$constraint instanceof MultiConstraint) {
            return $constraint;
        }
        $intervals = self::generateIntervals($constraint);
        $constraints = array();
        $hasNumericMatchAll = \false;
        if (\count($intervals['numeric']) === 1 && (string) $intervals['numeric'][0]->getStart() === (string) Interval::fromZero() && (string) $intervals['numeric'][0]->getEnd() === (string) Interval::untilPositiveInfinity()) {
            $constraints[] = $intervals['numeric'][0]->getStart();
            $hasNumericMatchAll = \true;
        } else {
            $unEqualConstraints = array();
            for ($i = 0, $count = \count($intervals['numeric']); $i < $count; $i++) {
                $interval = $intervals['numeric'][$i];
                if ($interval->getEnd()->getOperator() === '<' && $i + 1 < $count) {
                    $nextInterval = $intervals['numeric'][$i + 1];
                    if ($interval->getEnd()->getVersion() === $nextInterval->getStart()->getVersion() && $nextInterval->getStart()->getOperator() === '>') {
                        if (\count($unEqualConstraints) === 0 && (string) $interval->getStart() !== (string) Interval::fromZero()) {
                            $unEqualConstraints[] = $interval->getStart();
                        }
                        $unEqualConstraints[] = new Constraint('!=', $interval->getEnd()->getVersion());
                        continue;
                    }
                }
                if (\count($unEqualConstraints) > 0) {
                    if ((string) $interval->getEnd() !== (string) Interval::untilPositiveInfinity()) {
                        $unEqualConstraints[] = $interval->getEnd();
                    }
                    if (\count($unEqualConstraints) > 1) {
                        $constraints[] = new MultiConstraint($unEqualConstraints, \true);
                    } else {
                        $constraints[] = $unEqualConstraints[0];
                    }
                    $unEqualConstraints = array();
                    continue;
                }
                if ($interval->getStart()->getVersion() === $interval->getEnd()->getVersion() && $interval->getStart()->getOperator() === '>=' && $interval->getEnd()->getOperator() === '<=') {
                    $constraints[] = new Constraint('==', $interval->getStart()->getVersion());
                    continue;
                }
                if ((string) $interval->getStart() === (string) Interval::fromZero()) {
                    $constraints[] = $interval->getEnd();
                } elseif ((string) $interval->getEnd() === (string) Interval::untilPositiveInfinity()) {
                    $constraints[] = $interval->getStart();
                } else {
                    $constraints[] = new MultiConstraint(array($interval->getStart(), $interval->getEnd()), \true);
                }
            }
        }
        $devConstraints = array();
        if (0 === \count($intervals['branches']['names'])) {
            if ($intervals['branches']['exclude']) {
                if ($hasNumericMatchAll) {
                    return new MatchAllConstraint();
                }
            }
        } else {
            foreach ($intervals['branches']['names'] as $branchName) {
                if ($intervals['branches']['exclude']) {
                    $devConstraints[] = new Constraint('!=', $branchName);
                } else {
                    $devConstraints[] = new Constraint('==', $branchName);
                }
            }
            if ($intervals['branches']['exclude']) {
                if (\count($constraints) > 1) {
                    return new MultiConstraint(array_merge(array(new MultiConstraint($constraints, \false)), $devConstraints), \true);
                }
                if (\count($constraints) === 1 && (string) $constraints[0] === (string) Interval::fromZero()) {
                    if (\count($devConstraints) > 1) {
                        return new MultiConstraint($devConstraints, \true);
                    }
                    return $devConstraints[0];
                }
                return new MultiConstraint(array_merge($constraints, $devConstraints), \true);
            }
            $constraints = array_merge($constraints, $devConstraints);
        }
        if (\count($constraints) > 1) {
            return new MultiConstraint($constraints, \false);
        }
        if (\count($constraints) === 1) {
            return $constraints[0];
        }
        return new MatchNoneConstraint();
    }
    /**
    @phpstan-return
    */
    public static function get(ConstraintInterface $constraint)
    {
        $key = (string) $constraint;
        if (!isset(self::$intervalsCache[$key])) {
            self::$intervalsCache[$key] = self::generateIntervals($constraint);
        }
        return self::$intervalsCache[$key];
    }
    /**
    @phpstan-return
    */
    private static function generateIntervals(ConstraintInterface $constraint, $stopOnFirstValidInterval = \false)
    {
        if ($constraint instanceof MatchAllConstraint) {
            return array('numeric' => array(new Interval(Interval::fromZero(), Interval::untilPositiveInfinity())), 'branches' => Interval::anyDev());
        }
        if ($constraint instanceof MatchNoneConstraint) {
            return array('numeric' => array(), 'branches' => array('names' => array(), 'exclude' => \false));
        }
        if ($constraint instanceof Constraint) {
            return self::generateSingleConstraintIntervals($constraint);
        }
        if (!$constraint instanceof MultiConstraint) {
            throw new \UnexpectedValueException('The constraint passed in should be an MatchAllConstraint, Constraint or MultiConstraint instance, got ' . \get_class($constraint) . '.');
        }
        $constraints = $constraint->getConstraints();
        $numericGroups = array();
        $constraintBranches = array();
        foreach ($constraints as $c) {
            $res = self::get($c);
            $numericGroups[] = $res['numeric'];
            $constraintBranches[] = $res['branches'];
        }
        if ($constraint->isDisjunctive()) {
            $branches = Interval::noDev();
            foreach ($constraintBranches as $b) {
                if ($b['exclude']) {
                    if ($branches['exclude']) {
                        $branches['names'] = array_intersect($branches['names'], $b['names']);
                    } else {
                        $branches['exclude'] = \true;
                        $branches['names'] = array_diff($b['names'], $branches['names']);
                    }
                } else if ($branches['exclude']) {
                    $branches['names'] = array_diff($branches['names'], $b['names']);
                } else {
                    $branches['names'] = array_merge($branches['names'], $b['names']);
                }
            }
        } else {
            $branches = Interval::anyDev();
            foreach ($constraintBranches as $b) {
                if ($b['exclude']) {
                    if ($branches['exclude']) {
                        $branches['names'] = array_merge($branches['names'], $b['names']);
                    } else {
                        $branches['names'] = array_diff($branches['names'], $b['names']);
                    }
                } else if ($branches['exclude']) {
                    $branches['names'] = array_diff($b['names'], $branches['names']);
                    $branches['exclude'] = \false;
                } else {
                    $branches['names'] = array_intersect($branches['names'], $b['names']);
                }
            }
        }
        $branches['names'] = array_unique($branches['names']);
        if (\count($numericGroups) === 1) {
            return array('numeric' => $numericGroups[0], 'branches' => $branches);
        }
        $borders = array();
        foreach ($numericGroups as $group) {
            foreach ($group as $interval) {
                $borders[] = array('version' => $interval->getStart()->getVersion(), 'operator' => $interval->getStart()->getOperator(), 'side' => 'start');
                $borders[] = array('version' => $interval->getEnd()->getVersion(), 'operator' => $interval->getEnd()->getOperator(), 'side' => 'end');
            }
        }
        $opSortOrder = self::$opSortOrder;
        usort($borders, function ($a, $b) use ($opSortOrder) {
            $order = version_compare($a['version'], $b['version']);
            if ($order === 0) {
                return $opSortOrder[$a['operator']] - $opSortOrder[$b['operator']];
            }
            return $order;
        });
        $activeIntervals = 0;
        $intervals = array();
        $index = 0;
        $activationThreshold = $constraint->isConjunctive() ? \count($numericGroups) : 1;
        $start = null;
        foreach ($borders as $border) {
            if ($border['side'] === 'start') {
                $activeIntervals++;
            } else {
                $activeIntervals--;
            }
            if (!$start && $activeIntervals >= $activationThreshold) {
                $start = new Constraint($border['operator'], $border['version']);
            } elseif ($start && $activeIntervals < $activationThreshold) {
                if (version_compare($start->getVersion(), $border['version'], '=') && ($start->getOperator() === '>' && $border['operator'] === '<=' || $start->getOperator() === '>=' && $border['operator'] === '<')) {
                    unset($intervals[$index]);
                } else {
                    $intervals[$index] = new Interval($start, new Constraint($border['operator'], $border['version']));
                    $index++;
                    if ($stopOnFirstValidInterval) {
                        break;
                    }
                }
                $start = null;
            }
        }
        return array('numeric' => $intervals, 'branches' => $branches);
    }
    /**
    @phpstan-return
    */
    private static function generateSingleConstraintIntervals(Constraint $constraint)
    {
        $op = $constraint->getOperator();
        if (strpos($constraint->getVersion(), 'dev-') === 0) {
            $intervals = array();
            $branches = array('names' => array(), 'exclude' => \false);
            if ($op === '!=') {
                $intervals[] = new Interval(Interval::fromZero(), Interval::untilPositiveInfinity());
                $branches = array('names' => array($constraint->getVersion()), 'exclude' => \true);
            } elseif ($op === '==') {
                $branches['names'][] = $constraint->getVersion();
            }
            return array('numeric' => $intervals, 'branches' => $branches);
        }
        if ($op[0] === '>') {
            return array('numeric' => array(new Interval($constraint, Interval::untilPositiveInfinity())), 'branches' => Interval::noDev());
        }
        if ($op[0] === '<') {
            return array('numeric' => array(new Interval(Interval::fromZero(), $constraint)), 'branches' => Interval::noDev());
        }
        if ($op === '!=') {
            return array('numeric' => array(new Interval(Interval::fromZero(), new Constraint('<', $constraint->getVersion())), new Interval(new Constraint('>', $constraint->getVersion()), Interval::untilPositiveInfinity())), 'branches' => Interval::anyDev());
        }
        return array('numeric' => array(new Interval(new Constraint('>=', $constraint->getVersion()), new Constraint('<=', $constraint->getVersion()))), 'branches' => Interval::noDev());
    }
}
<?php

namespace HumbugBox464\Composer\Semver\Constraint;

interface ConstraintInterface
{
    public function matches(ConstraintInterface $provider);
    /**
    @phpstan-param
    */
    public function compile($otherOperator);
    public function getUpperBound();
    public function getLowerBound();
    public function getPrettyString();
    public function setPrettyString($prettyString);
    public function __toString();
}
<?php

namespace HumbugBox464\Composer\Semver\Constraint;

class Constraint implements ConstraintInterface
{
    const OP_EQ = 0;
    const OP_LT = 1;
    const OP_LE = 2;
    const OP_GT = 3;
    const OP_GE = 4;
    const OP_NE = 5;
    const STR_OP_EQ = '==';
    const STR_OP_EQ_ALT = '=';
    const STR_OP_LT = '<';
    const STR_OP_LE = '<=';
    const STR_OP_GT = '>';
    const STR_OP_GE = '>=';
    const STR_OP_NE = '!=';
    const STR_OP_NE_ALT = '<>';
    /**
    @phpstan-var
    */
    private static $transOpStr = array('=' => self::OP_EQ, '==' => self::OP_EQ, '<' => self::OP_LT, '<=' => self::OP_LE, '>' => self::OP_GT, '>=' => self::OP_GE, '<>' => self::OP_NE, '!=' => self::OP_NE);
    /**
    @phpstan-var
    */
    private static $transOpInt = array(self::OP_EQ => '==', self::OP_LT => '<', self::OP_LE => '<=', self::OP_GT => '>', self::OP_GE => '>=', self::OP_NE => '!=');
    /**
    @phpstan-var
    */
    protected $operator;
    protected $version;
    protected $prettyString;
    protected $lowerBound;
    protected $upperBound;
    /**
    @phpstan-param
    */
    public function __construct($operator, $version)
    {
        if (!isset(self::$transOpStr[$operator])) {
            throw new \InvalidArgumentException(sprintf('Invalid operator "%s" given, expected one of: %s', $operator, implode(', ', self::getSupportedOperators())));
        }
        $this->operator = self::$transOpStr[$operator];
        $this->version = $version;
    }
    public function getVersion()
    {
        return $this->version;
    }
    /**
    @phpstan-return
    */
    public function getOperator()
    {
        return self::$transOpInt[$this->operator];
    }
    public function matches(ConstraintInterface $provider)
    {
        if ($provider instanceof self) {
            return $this->matchSpecific($provider);
        }
        return $provider->matches($this);
    }
    public function setPrettyString($prettyString)
    {
        $this->prettyString = $prettyString;
    }
    public function getPrettyString()
    {
        if ($this->prettyString) {
            return $this->prettyString;
        }
        return $this->__toString();
    }
    /**
    @phpstan-return
    */
    public static function getSupportedOperators()
    {
        return array_keys(self::$transOpStr);
    }
    /**
    @phpstan-param
    @phpstan-return
    */
    public static function getOperatorConstant($operator)
    {
        return self::$transOpStr[$operator];
    }
    /**
    @phpstan-param
    */
    public function versionCompare($a, $b, $operator, $compareBranches = \false)
    {
        if (!isset(self::$transOpStr[$operator])) {
            throw new \InvalidArgumentException(sprintf('Invalid operator "%s" given, expected one of: %s', $operator, implode(', ', self::getSupportedOperators())));
        }
        $aIsBranch = strpos($a, 'dev-') === 0;
        $bIsBranch = strpos($b, 'dev-') === 0;
        if ($operator === '!=' && ($aIsBranch || $bIsBranch)) {
            return $a !== $b;
        }
        if ($aIsBranch && $bIsBranch) {
            return $operator === '==' && $a === $b;
        }
        if (!$compareBranches && ($aIsBranch || $bIsBranch)) {
            return \false;
        }
        return \version_compare($a, $b, $operator);
    }
    public function compile($otherOperator)
    {
        if (strpos($this->version, 'dev-') === 0) {
            if (self::OP_EQ === $this->operator) {
                if (self::OP_EQ === $otherOperator) {
                    return sprintf('$b && $v === %s', \var_export($this->version, \true));
                }
                if (self::OP_NE === $otherOperator) {
                    return sprintf('!$b || $v !== %s', \var_export($this->version, \true));
                }
                return 'false';
            }
            if (self::OP_NE === $this->operator) {
                if (self::OP_EQ === $otherOperator) {
                    return sprintf('!$b || $v !== %s', \var_export($this->version, \true));
                }
                if (self::OP_NE === $otherOperator) {
                    return 'true';
                }
                return '!$b';
            }
            return 'false';
        }
        if (self::OP_EQ === $this->operator) {
            if (self::OP_EQ === $otherOperator) {
                return sprintf('\version_compare($v, %s, \'==\')', \var_export($this->version, \true));
            }
            if (self::OP_NE === $otherOperator) {
                return sprintf('$b || \version_compare($v, %s, \'!=\')', \var_export($this->version, \true));
            }
            return sprintf('!$b && \version_compare(%s, $v, \'%s\')', \var_export($this->version, \true), self::$transOpInt[$otherOperator]);
        }
        if (self::OP_NE === $this->operator) {
            if (self::OP_EQ === $otherOperator) {
                return sprintf('$b || (!$b && \version_compare($v, %s, \'!=\'))', \var_export($this->version, \true));
            }
            if (self::OP_NE === $otherOperator) {
                return 'true';
            }
            return '!$b';
        }
        if (self::OP_LT === $this->operator || self::OP_LE === $this->operator) {
            if (self::OP_LT === $otherOperator || self::OP_LE === $otherOperator) {
                return '!$b';
            }
        } else if (self::OP_GT === $otherOperator || self::OP_GE === $otherOperator) {
            return '!$b';
        }
        if (self::OP_NE === $otherOperator) {
            return 'true';
        }
        $codeComparison = sprintf('\version_compare($v, %s, \'%s\')', \var_export($this->version, \true), self::$transOpInt[$this->operator]);
        if ($this->operator === self::OP_LE) {
            if ($otherOperator === self::OP_GT) {
                return sprintf('!$b && \version_compare($v, %s, \'!=\') && ', \var_export($this->version, \true)) . $codeComparison;
            }
        } elseif ($this->operator === self::OP_GE) {
            if ($otherOperator === self::OP_LT) {
                return sprintf('!$b && \version_compare($v, %s, \'!=\') && ', \var_export($this->version, \true)) . $codeComparison;
            }
        }
        return sprintf('!$b && %s', $codeComparison);
    }
    public function matchSpecific(Constraint $provider, $compareBranches = \false)
    {
        $noEqualOp = str_replace('=', '', self::$transOpInt[$this->operator]);
        $providerNoEqualOp = str_replace('=', '', self::$transOpInt[$provider->operator]);
        $isEqualOp = self::OP_EQ === $this->operator;
        $isNonEqualOp = self::OP_NE === $this->operator;
        $isProviderEqualOp = self::OP_EQ === $provider->operator;
        $isProviderNonEqualOp = self::OP_NE === $provider->operator;
        if ($isNonEqualOp || $isProviderNonEqualOp) {
            if ($isNonEqualOp && !$isProviderNonEqualOp && !$isProviderEqualOp && strpos($provider->version, 'dev-') === 0) {
                return \false;
            }
            if ($isProviderNonEqualOp && !$isNonEqualOp && !$isEqualOp && strpos($this->version, 'dev-') === 0) {
                return \false;
            }
            if (!$isEqualOp && !$isProviderEqualOp) {
                return \true;
            }
            return $this->versionCompare($provider->version, $this->version, '!=', $compareBranches);
        }
        if ($this->operator !== self::OP_EQ && $noEqualOp === $providerNoEqualOp) {
            return !(strpos($this->version, 'dev-') === 0 || strpos($provider->version, 'dev-') === 0);
        }
        $version1 = $isEqualOp ? $this->version : $provider->version;
        $version2 = $isEqualOp ? $provider->version : $this->version;
        $operator = $isEqualOp ? $provider->operator : $this->operator;
        if ($this->versionCompare($version1, $version2, self::$transOpInt[$operator], $compareBranches)) {
            return !(self::$transOpInt[$provider->operator] === $providerNoEqualOp && self::$transOpInt[$this->operator] !== $noEqualOp && \version_compare($provider->version, $this->version, '=='));
        }
        return \false;
    }
    public function __toString()
    {
        return self::$transOpInt[$this->operator] . ' ' . $this->version;
    }
    public function getLowerBound()
    {
        $this->extractBounds();
        return $this->lowerBound;
    }
    public function getUpperBound()
    {
        $this->extractBounds();
        return $this->upperBound;
    }
    private function extractBounds()
    {
        if (null !== $this->lowerBound) {
            return;
        }
        if (strpos($this->version, 'dev-') === 0) {
            $this->lowerBound = Bound::zero();
            $this->upperBound = Bound::positiveInfinity();
            return;
        }
        switch ($this->operator) {
            case self::OP_EQ:
                $this->lowerBound = new Bound($this->version, \true);
                $this->upperBound = new Bound($this->version, \true);
                break;
            case self::OP_LT:
                $this->lowerBound = Bound::zero();
                $this->upperBound = new Bound($this->version, \false);
                break;
            case self::OP_LE:
                $this->lowerBound = Bound::zero();
                $this->upperBound = new Bound($this->version, \true);
                break;
            case self::OP_GT:
                $this->lowerBound = new Bound($this->version, \false);
                $this->upperBound = Bound::positiveInfinity();
                break;
            case self::OP_GE:
                $this->lowerBound = new Bound($this->version, \true);
                $this->upperBound = Bound::positiveInfinity();
                break;
            case self::OP_NE:
                $this->lowerBound = Bound::zero();
                $this->upperBound = Bound::positiveInfinity();
                break;
        }
    }
}
<?php

namespace HumbugBox464\Composer\Semver\Constraint;

class Bound
{
    private $version;
    private $isInclusive;
    public function __construct($version, $isInclusive)
    {
        $this->version = $version;
        $this->isInclusive = $isInclusive;
    }
    public function getVersion()
    {
        return $this->version;
    }
    public function isInclusive()
    {
        return $this->isInclusive;
    }
    public function isZero()
    {
        return $this->getVersion() === '0.0.0.0-dev' && $this->isInclusive();
    }
    public function isPositiveInfinity()
    {
        return $this->getVersion() === \PHP_INT_MAX . '.0.0.0' && !$this->isInclusive();
    }
    public function compareTo(Bound $other, $operator)
    {
        if (!\in_array($operator, array('<', '>'), \true)) {
            throw new \InvalidArgumentException('Does not support any other operator other than > or <.');
        }
        if ($this == $other) {
            return \false;
        }
        $compareResult = version_compare($this->getVersion(), $other->getVersion());
        if (0 !== $compareResult) {
            return ('>' === $operator ? 1 : -1) === $compareResult;
        }
        return '>' === $operator ? $other->isInclusive() : !$other->isInclusive();
    }
    public function __toString()
    {
        return sprintf('%s [%s]', $this->getVersion(), $this->isInclusive() ? 'inclusive' : 'exclusive');
    }
    public static function zero()
    {
        return new Bound('0.0.0.0-dev', \true);
    }
    public static function positiveInfinity()
    {
        return new Bound(\PHP_INT_MAX . '.0.0.0', \false);
    }
}
<?php

namespace HumbugBox464\Composer\Semver\Constraint;

class MultiConstraint implements ConstraintInterface
{
    /**
    @phpstan-var
    */
    protected $constraints;
    protected $prettyString;
    protected $string;
    protected $conjunctive;
    protected $lowerBound;
    protected $upperBound;
    public function __construct(array $constraints, $conjunctive = \true)
    {
        if (\count($constraints) < 2) {
            throw new \InvalidArgumentException('Must provide at least two constraints for a MultiConstraint. Use ' . 'the regular Constraint class for one constraint only or MatchAllConstraint for none. You may use ' . 'MultiConstraint::create() which optimizes and handles those cases automatically.');
        }
        $this->constraints = $constraints;
        $this->conjunctive = $conjunctive;
    }
    public function getConstraints()
    {
        return $this->constraints;
    }
    public function isConjunctive()
    {
        return $this->conjunctive;
    }
    public function isDisjunctive()
    {
        return !$this->conjunctive;
    }
    public function compile($otherOperator)
    {
        $parts = array();
        foreach ($this->constraints as $constraint) {
            $code = $constraint->compile($otherOperator);
            if ($code === 'true') {
                if (!$this->conjunctive) {
                    return 'true';
                }
            } elseif ($code === 'false') {
                if ($this->conjunctive) {
                    return 'false';
                }
            } else {
                $parts[] = '(' . $code . ')';
            }
        }
        if (!$parts) {
            return $this->conjunctive ? 'true' : 'false';
        }
        return $this->conjunctive ? implode('&&', $parts) : implode('||', $parts);
    }
    public function matches(ConstraintInterface $provider)
    {
        if (\false === $this->conjunctive) {
            foreach ($this->constraints as $constraint) {
                if ($provider->matches($constraint)) {
                    return \true;
                }
            }
            return \false;
        }
        if ($provider instanceof MultiConstraint && $provider->isDisjunctive()) {
            return $provider->matches($this);
        }
        foreach ($this->constraints as $constraint) {
            if (!$provider->matches($constraint)) {
                return \false;
            }
        }
        return \true;
    }
    public function setPrettyString($prettyString)
    {
        $this->prettyString = $prettyString;
    }
    public function getPrettyString()
    {
        if ($this->prettyString) {
            return $this->prettyString;
        }
        return (string) $this;
    }
    public function __toString()
    {
        if ($this->string !== null) {
            return $this->string;
        }
        $constraints = array();
        foreach ($this->constraints as $constraint) {
            $constraints[] = (string) $constraint;
        }
        return $this->string = '[' . implode($this->conjunctive ? ' ' : ' || ', $constraints) . ']';
    }
    public function getLowerBound()
    {
        $this->extractBounds();
        if (null === $this->lowerBound) {
            throw new \LogicException('extractBounds should have populated the lowerBound property');
        }
        return $this->lowerBound;
    }
    public function getUpperBound()
    {
        $this->extractBounds();
        if (null === $this->upperBound) {
            throw new \LogicException('extractBounds should have populated the upperBound property');
        }
        return $this->upperBound;
    }
    public static function create(array $constraints, $conjunctive = \true)
    {
        if (0 === \count($constraints)) {
            return new MatchAllConstraint();
        }
        if (1 === \count($constraints)) {
            return $constraints[0];
        }
        $optimized = self::optimizeConstraints($constraints, $conjunctive);
        if ($optimized !== null) {
            list($constraints, $conjunctive) = $optimized;
            if (\count($constraints) === 1) {
                return $constraints[0];
            }
        }
        return new self($constraints, $conjunctive);
    }
    /**
    @phpstan-return
    */
    private static function optimizeConstraints(array $constraints, $conjunctive)
    {
        if (!$conjunctive) {
            $left = $constraints[0];
            $mergedConstraints = array();
            $optimized = \false;
            for ($i = 1, $l = \count($constraints); $i < $l; $i++) {
                $right = $constraints[$i];
                if ($left instanceof self && $left->conjunctive && $right instanceof self && $right->conjunctive && \count($left->constraints) === 2 && \count($right->constraints) === 2 && ($left0 = (string) $left->constraints[0]) && $left0[0] === '>' && $left0[1] === '=' && ($left1 = (string) $left->constraints[1]) && $left1[0] === '<' && ($right0 = (string) $right->constraints[0]) && $right0[0] === '>' && $right0[1] === '=' && ($right1 = (string) $right->constraints[1]) && $right1[0] === '<' && substr($left1, 2) === substr($right0, 3)) {
                    $optimized = \true;
                    $left = new MultiConstraint(array($left->constraints[0], $right->constraints[1]), \true);
                } else {
                    $mergedConstraints[] = $left;
                    $left = $right;
                }
            }
            if ($optimized) {
                $mergedConstraints[] = $left;
                return array($mergedConstraints, \false);
            }
        }
        return null;
    }
    private function extractBounds()
    {
        if (null !== $this->lowerBound) {
            return;
        }
        foreach ($this->constraints as $constraint) {
            if (null === $this->lowerBound || null === $this->upperBound) {
                $this->lowerBound = $constraint->getLowerBound();
                $this->upperBound = $constraint->getUpperBound();
                continue;
            }
            if ($constraint->getLowerBound()->compareTo($this->lowerBound, $this->isConjunctive() ? '>' : '<')) {
                $this->lowerBound = $constraint->getLowerBound();
            }
            if ($constraint->getUpperBound()->compareTo($this->upperBound, $this->isConjunctive() ? '<' : '>')) {
                $this->upperBound = $constraint->getUpperBound();
            }
        }
    }
}
<?php

namespace HumbugBox464\Composer\Semver\Constraint;

class MatchNoneConstraint implements ConstraintInterface
{
    protected $prettyString;
    public function matches(ConstraintInterface $provider)
    {
        return \false;
    }
    public function compile($otherOperator)
    {
        return 'false';
    }
    public function setPrettyString($prettyString)
    {
        $this->prettyString = $prettyString;
    }
    public function getPrettyString()
    {
        if ($this->prettyString) {
            return $this->prettyString;
        }
        return (string) $this;
    }
    public function __toString()
    {
        return '[]';
    }
    public function getUpperBound()
    {
        return new Bound('0.0.0.0-dev', \false);
    }
    public function getLowerBound()
    {
        return new Bound('0.0.0.0-dev', \false);
    }
}
<?php

namespace HumbugBox464\Composer\Semver\Constraint;

class MatchAllConstraint implements ConstraintInterface
{
    protected $prettyString;
    public function matches(ConstraintInterface $provider)
    {
        return \true;
    }
    public function compile($otherOperator)
    {
        return 'true';
    }
    public function setPrettyString($prettyString)
    {
        $this->prettyString = $prettyString;
    }
    public function getPrettyString()
    {
        if ($this->prettyString) {
            return $this->prettyString;
        }
        return (string) $this;
    }
    public function __toString()
    {
        return '*';
    }
    public function getUpperBound()
    {
        return Bound::positiveInfinity();
    }
    public function getLowerBound()
    {
        return Bound::zero();
    }
}
<?php

namespace HumbugBox464\Composer;

use HumbugBox464\Composer\Autoload\ClassLoader;
use HumbugBox464\Composer\Semver\VersionParser;
class InstalledVersions
{
    /**
    @psalm-var
    */
    private static $installed;
    private static $installedIsLocalDir;
    private static $canGetVendors;
    /**
    @psalm-var
    */
    private static $installedByVendor = array();
    /**
    @psalm-return
    */
    public static function getInstalledPackages()
    {
        $packages = array();
        foreach (self::getInstalled() as $installed) {
            $packages[] = array_keys($installed['versions']);
        }
        if (1 === \count($packages)) {
            return $packages[0];
        }
        return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
    }
    /**
    @psalm-return
    */
    public static function getInstalledPackagesByType($type)
    {
        $packagesByType = array();
        foreach (self::getInstalled() as $installed) {
            foreach ($installed['versions'] as $name => $package) {
                if (isset($package['type']) && $package['type'] === $type) {
                    $packagesByType[] = $name;
                }
            }
        }
        return $packagesByType;
    }
    public static function isInstalled($packageName, $includeDevRequirements = \true)
    {
        foreach (self::getInstalled() as $installed) {
            if (isset($installed['versions'][$packageName])) {
                return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === \false;
            }
        }
        return \false;
    }
    public static function satisfies(VersionParser $parser, $packageName, $constraint)
    {
        $constraint = $parser->parseConstraints((string) $constraint);
        $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
        return $provided->matches($constraint);
    }
    public static function getVersionRanges($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }
            $ranges = array();
            if (isset($installed['versions'][$packageName]['pretty_version'])) {
                $ranges[] = $installed['versions'][$packageName]['pretty_version'];
            }
            if (array_key_exists('aliases', $installed['versions'][$packageName])) {
                $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
            }
            if (array_key_exists('replaced', $installed['versions'][$packageName])) {
                $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
            }
            if (array_key_exists('provided', $installed['versions'][$packageName])) {
                $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
            }
            return implode(' || ', $ranges);
        }
        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }
    public static function getVersion($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }
            if (!isset($installed['versions'][$packageName]['version'])) {
                return null;
            }
            return $installed['versions'][$packageName]['version'];
        }
        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }
    public static function getPrettyVersion($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }
            if (!isset($installed['versions'][$packageName]['pretty_version'])) {
                return null;
            }
            return $installed['versions'][$packageName]['pretty_version'];
        }
        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }
    public static function getReference($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }
            if (!isset($installed['versions'][$packageName]['reference'])) {
                return null;
            }
            return $installed['versions'][$packageName]['reference'];
        }
        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }
    public static function getInstallPath($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }
            return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
        }
        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }
    /**
    @psalm-return
    */
    public static function getRootPackage()
    {
        $installed = self::getInstalled();
        return $installed[0]['root'];
    }
    /**
    @psalm-return
    */
    public static function getRawData()
    {
        @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', \E_USER_DEPRECATED);
        if (null === self::$installed) {
            if (substr(__DIR__, -8, 1) !== 'C') {
                self::$installed = include __DIR__ . '/installed.php';
            } else {
                self::$installed = array();
            }
        }
        return self::$installed;
    }
    /**
    @psalm-return
    */
    public static function getAllRawData()
    {
        return self::getInstalled();
    }
    /**
    @psalm-param
    */
    public static function reload($data)
    {
        self::$installed = $data;
        self::$installedByVendor = array();
        self::$installedIsLocalDir = \false;
    }
    /**
    @psalm-return
    */
    private static function getInstalled()
    {
        if (null === self::$canGetVendors) {
            self::$canGetVendors = method_exists('HumbugBox464\Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
        }
        $installed = array();
        $copiedLocalDir = \false;
        if (self::$canGetVendors) {
            $selfDir = strtr(__DIR__, '\\', '/');
            foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
                $vendorDir = strtr($vendorDir, '\\', '/');
                if (isset(self::$installedByVendor[$vendorDir])) {
                    $installed[] = self::$installedByVendor[$vendorDir];
                } elseif (is_file($vendorDir . '/composer/installed.php')) {
                    $required = require $vendorDir . '/composer/installed.php';
                    self::$installedByVendor[$vendorDir] = $required;
                    $installed[] = $required;
                    if (self::$installed === null && $vendorDir . '/composer' === $selfDir) {
                        self::$installed = $required;
                        self::$installedIsLocalDir = \true;
                    }
                }
                if (self::$installedIsLocalDir && $vendorDir . '/composer' === $selfDir) {
                    $copiedLocalDir = \true;
                }
            }
        }
        if (null === self::$installed) {
            if (substr(__DIR__, -8, 1) !== 'C') {
                $required = require __DIR__ . '/installed.php';
                self::$installed = $required;
            } else {
                self::$installed = array();
            }
        }
        if (self::$installed !== array() && !$copiedLocalDir) {
            $installed[] = self::$installed;
        }
        return $installed;
    }
}
<?php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Autoload;

/**
 * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
 *
 *     $loader = new \Composer\Autoload\ClassLoader();
 *
 *     // register classes with namespaces
 *     $loader->add('Symfony\Component', __DIR__.'/component');
 *     $loader->add('Symfony',           __DIR__.'/framework');
 *
 *     // activate the autoloader
 *     $loader->register();
 *
 *     // to enable searching the include path (eg. for PEAR packages)
 *     $loader->setUseIncludePath(true);
 *
 * In this example, if you try to use a class in the Symfony\Component
 * namespace or one of its children (Symfony\Component\Console for instance),
 * the autoloader will first look for the class under the component/
 * directory, and it will then fallback to the framework/ directory if not
 * found before giving up.
 *
 * This class is loosely based on the Symfony UniversalClassLoader.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @see    https://www.php-fig.org/psr/psr-0/
 * @see    https://www.php-fig.org/psr/psr-4/
 */
class ClassLoader
{
    /** @var \Closure(string):void */
    private static $includeFile;

    /** @var string|null */
    private $vendorDir;

    // PSR-4
    /**
     * @var array<string, array<string, int>>
     */
    private $prefixLengthsPsr4 = array();
    /**
     * @var array<string, list<string>>
     */
    private $prefixDirsPsr4 = array();
    /**
     * @var list<string>
     */
    private $fallbackDirsPsr4 = array();

    // PSR-0
    /**
     * List of PSR-0 prefixes
     *
     * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
     *
     * @var array<string, array<string, list<string>>>
     */
    private $prefixesPsr0 = array();
    /**
     * @var list<string>
     */
    private $fallbackDirsPsr0 = array();

    /** @var bool */
    private $useIncludePath = false;

    /**
     * @var array<string, string>
     */
    private $classMap = array();

    /** @var bool */
    private $classMapAuthoritative = false;

    /**
     * @var array<string, bool>
     */
    private $missingClasses = array();

    /** @var string|null */
    private $apcuPrefix;

    /**
     * @var array<string, self>
     */
    private static $registeredLoaders = array();

    /**
     * @param string|null $vendorDir
     */
    public function __construct($vendorDir = null)
    {
        $this->vendorDir = $vendorDir;
        self::initializeIncludeClosure();
    }

    /**
     * @return array<string, list<string>>
     */
    public function getPrefixes()
    {
        if (!empty($this->prefixesPsr0)) {
            return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
        }

        return array();
    }

    /**
     * @return array<string, list<string>>
     */
    public function getPrefixesPsr4()
    {
        return $this->prefixDirsPsr4;
    }

    /**
     * @return list<string>
     */
    public function getFallbackDirs()
    {
        return $this->fallbackDirsPsr0;
    }

    /**
     * @return list<string>
     */
    public function getFallbackDirsPsr4()
    {
        return $this->fallbackDirsPsr4;
    }

    /**
     * @return array<string, string> Array of classname => path
     */
    public function getClassMap()
    {
        return $this->classMap;
    }

    /**
     * @param array<string, string> $classMap Class to filename map
     *
     * @return void
     */
    public function addClassMap(array $classMap)
    {
        if ($this->classMap) {
            $this->classMap = array_merge($this->classMap, $classMap);
        } else {
            $this->classMap = $classMap;
        }
    }

    /**
     * Registers a set of PSR-0 directories for a given prefix, either
     * appending or prepending to the ones previously set for this prefix.
     *
     * @param string              $prefix  The prefix
     * @param list<string>|string $paths   The PSR-0 root directories
     * @param bool                $prepend Whether to prepend the directories
     *
     * @return void
     */
    public function add($prefix, $paths, $prepend = false)
    {
        $paths = (array) $paths;
        if (!$prefix) {
            if ($prepend) {
                $this->fallbackDirsPsr0 = array_merge(
                    $paths,
                    $this->fallbackDirsPsr0
                );
            } else {
                $this->fallbackDirsPsr0 = array_merge(
                    $this->fallbackDirsPsr0,
                    $paths
                );
            }

            return;
        }

        $first = $prefix[0];
        if (!isset($this->prefixesPsr0[$first][$prefix])) {
            $this->prefixesPsr0[$first][$prefix] = $paths;

            return;
        }
        if ($prepend) {
            $this->prefixesPsr0[$first][$prefix] = array_merge(
                $paths,
                $this->prefixesPsr0[$first][$prefix]
            );
        } else {
            $this->prefixesPsr0[$first][$prefix] = array_merge(
                $this->prefixesPsr0[$first][$prefix],
                $paths
            );
        }
    }

    /**
     * Registers a set of PSR-4 directories for a given namespace, either
     * appending or prepending to the ones previously set for this namespace.
     *
     * @param string              $prefix  The prefix/namespace, with trailing '\\'
     * @param list<string>|string $paths   The PSR-4 base directories
     * @param bool                $prepend Whether to prepend the directories
     *
     * @throws \InvalidArgumentException
     *
     * @return void
     */
    public function addPsr4($prefix, $paths, $prepend = false)
    {
        $paths = (array) $paths;
        if (!$prefix) {
            // Register directories for the root namespace.
            if ($prepend) {
                $this->fallbackDirsPsr4 = array_merge(
                    $paths,
                    $this->fallbackDirsPsr4
                );
            } else {
                $this->fallbackDirsPsr4 = array_merge(
                    $this->fallbackDirsPsr4,
                    $paths
                );
            }
        } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
            // Register directories for a new namespace.
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            $this->prefixDirsPsr4[$prefix] = $paths;
        } elseif ($prepend) {
            // Prepend directories for an already registered namespace.
            $this->prefixDirsPsr4[$prefix] = array_merge(
                $paths,
                $this->prefixDirsPsr4[$prefix]
            );
        } else {
            // Append directories for an already registered namespace.
            $this->prefixDirsPsr4[$prefix] = array_merge(
                $this->prefixDirsPsr4[$prefix],
                $paths
            );
        }
    }

    /**
     * Registers a set of PSR-0 directories for a given prefix,
     * replacing any others previously set for this prefix.
     *
     * @param string              $prefix The prefix
     * @param list<string>|string $paths  The PSR-0 base directories
     *
     * @return void
     */
    public function set($prefix, $paths)
    {
        if (!$prefix) {
            $this->fallbackDirsPsr0 = (array) $paths;
        } else {
            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
        }
    }

    /**
     * Registers a set of PSR-4 directories for a given namespace,
     * replacing any others previously set for this namespace.
     *
     * @param string              $prefix The prefix/namespace, with trailing '\\'
     * @param list<string>|string $paths  The PSR-4 base directories
     *
     * @throws \InvalidArgumentException
     *
     * @return void
     */
    public function setPsr4($prefix, $paths)
    {
        if (!$prefix) {
            $this->fallbackDirsPsr4 = (array) $paths;
        } else {
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            $this->prefixDirsPsr4[$prefix] = (array) $paths;
        }
    }

    /**
     * Turns on searching the include path for class files.
     *
     * @param bool $useIncludePath
     *
     * @return void
     */
    public function setUseIncludePath($useIncludePath)
    {
        $this->useIncludePath = $useIncludePath;
    }

    /**
     * Can be used to check if the autoloader uses the include path to check
     * for classes.
     *
     * @return bool
     */
    public function getUseIncludePath()
    {
        return $this->useIncludePath;
    }

    /**
     * Turns off searching the prefix and fallback directories for classes
     * that have not been registered with the class map.
     *
     * @param bool $classMapAuthoritative
     *
     * @return void
     */
    public function setClassMapAuthoritative($classMapAuthoritative)
    {
        $this->classMapAuthoritative = $classMapAuthoritative;
    }

    /**
     * Should class lookup fail if not found in the current class map?
     *
     * @return bool
     */
    public function isClassMapAuthoritative()
    {
        return $this->classMapAuthoritative;
    }

    /**
     * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
     *
     * @param string|null $apcuPrefix
     *
     * @return void
     */
    public function setApcuPrefix($apcuPrefix)
    {
        $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
    }

    /**
     * The APCu prefix in use, or null if APCu caching is not enabled.
     *
     * @return string|null
     */
    public function getApcuPrefix()
    {
        return $this->apcuPrefix;
    }

    /**
     * Registers this instance as an autoloader.
     *
     * @param bool $prepend Whether to prepend the autoloader or not
     *
     * @return void
     */
    public function register($prepend = false)
    {
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);

        if (null === $this->vendorDir) {
            return;
        }

        if ($prepend) {
            self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
        } else {
            unset(self::$registeredLoaders[$this->vendorDir]);
            self::$registeredLoaders[$this->vendorDir] = $this;
        }
    }

    /**
     * Unregisters this instance as an autoloader.
     *
     * @return void
     */
    public function unregister()
    {
        spl_autoload_unregister(array($this, 'loadClass'));

        if (null !== $this->vendorDir) {
            unset(self::$registeredLoaders[$this->vendorDir]);
        }
    }

    /**
     * Loads the given class or interface.
     *
     * @param  string    $class The name of the class
     * @return true|null True if loaded, null otherwise
     */
    public function loadClass($class)
    {
        if ($file = $this->findFile($class)) {
            $includeFile = self::$includeFile;
            $includeFile($file);

            return true;
        }

        return null;
    }

    /**
     * Finds the path to the file where the class is defined.
     *
     * @param string $class The name of the class
     *
     * @return string|false The path if found, false otherwise
     */
    public function findFile($class)
    {
        // class map lookup
        if (isset($this->classMap[$class])) {
            return $this->classMap[$class];
        }
        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
            return false;
        }
        if (null !== $this->apcuPrefix) {
            $file = apcu_fetch($this->apcuPrefix.$class, $hit);
            if ($hit) {
                return $file;
            }
        }

        $file = $this->findFileWithExtension($class, '.php');

        // Search for Hack files if we are running on HHVM
        if (false === $file && defined('HHVM_VERSION')) {
            $file = $this->findFileWithExtension($class, '.hh');
        }

        if (null !== $this->apcuPrefix) {
            apcu_add($this->apcuPrefix.$class, $file);
        }

        if (false === $file) {
            // Remember that this class does not exist.
            $this->missingClasses[$class] = true;
        }

        return $file;
    }

    /**
     * Returns the currently registered loaders keyed by their corresponding vendor directories.
     *
     * @return array<string, self>
     */
    public static function getRegisteredLoaders()
    {
        return self::$registeredLoaders;
    }

    /**
     * @param  string       $class
     * @param  string       $ext
     * @return string|false
     */
    private function findFileWithExtension($class, $ext)
    {
        // PSR-4 lookup
        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

        $first = $class[0];
        if (isset($this->prefixLengthsPsr4[$first])) {
            $subPath = $class;
            while (false !== $lastPos = strrpos($subPath, '\\')) {
                $subPath = substr($subPath, 0, $lastPos);
                $search = $subPath . '\\';
                if (isset($this->prefixDirsPsr4[$search])) {
                    $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
                    foreach ($this->prefixDirsPsr4[$search] as $dir) {
                        if (file_exists($file = $dir . $pathEnd)) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-4 fallback dirs
        foreach ($this->fallbackDirsPsr4 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
                return $file;
            }
        }

        // PSR-0 lookup
        if (false !== $pos = strrpos($class, '\\')) {
            // namespaced class name
            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
        } else {
            // PEAR-like class name
            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
        }

        if (isset($this->prefixesPsr0[$first])) {
            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
                if (0 === strpos($class, $prefix)) {
                    foreach ($dirs as $dir) {
                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-0 fallback dirs
        foreach ($this->fallbackDirsPsr0 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                return $file;
            }
        }

        // PSR-0 include paths.
        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
            return $file;
        }

        return false;
    }

    /**
     * @return void
     */
    private static function initializeIncludeClosure()
    {
        if (self::$includeFile !== null) {
            return;
        }

        /**
         * Scope isolated include.
         *
         * Prevents access to $this/self from included files.
         *
         * @param  string $file
         * @return void
         */
        self::$includeFile = \Closure::bind(static function($file) {
            include $file;
        }, null, null);
    }
}
<?php

// autoload_psr4.php @generated by Composer

$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);

return array(
    'HumbugBox464\\KevinGH\\RequirementChecker\\' => array($baseDir . '/src'),
    'HumbugBox464\\Composer\\Semver\\' => array($vendorDir . '/composer/semver/src'),
);
<?php

// autoload_classmap.php @generated by Composer

$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);

return array(
    'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
    'HumbugBox464\\Composer\\Semver\\Comparator' => $vendorDir . '/composer/semver/src/Comparator.php',
    'HumbugBox464\\Composer\\Semver\\CompilingMatcher' => $vendorDir . '/composer/semver/src/CompilingMatcher.php',
    'HumbugBox464\\Composer\\Semver\\Constraint\\Bound' => $vendorDir . '/composer/semver/src/Constraint/Bound.php',
    'HumbugBox464\\Composer\\Semver\\Constraint\\Constraint' => $vendorDir . '/composer/semver/src/Constraint/Constraint.php',
    'HumbugBox464\\Composer\\Semver\\Constraint\\ConstraintInterface' => $vendorDir . '/composer/semver/src/Constraint/ConstraintInterface.php',
    'HumbugBox464\\Composer\\Semver\\Constraint\\MatchAllConstraint' => $vendorDir . '/composer/semver/src/Constraint/MatchAllConstraint.php',
    'HumbugBox464\\Composer\\Semver\\Constraint\\MatchNoneConstraint' => $vendorDir . '/composer/semver/src/Constraint/MatchNoneConstraint.php',
    'HumbugBox464\\Composer\\Semver\\Constraint\\MultiConstraint' => $vendorDir . '/composer/semver/src/Constraint/MultiConstraint.php',
    'HumbugBox464\\Composer\\Semver\\Interval' => $vendorDir . '/composer/semver/src/Interval.php',
    'HumbugBox464\\Composer\\Semver\\Intervals' => $vendorDir . '/composer/semver/src/Intervals.php',
    'HumbugBox464\\Composer\\Semver\\Semver' => $vendorDir . '/composer/semver/src/Semver.php',
    'HumbugBox464\\Composer\\Semver\\VersionParser' => $vendorDir . '/composer/semver/src/VersionParser.php',
    'HumbugBox464\\KevinGH\\RequirementChecker\\Checker' => $baseDir . '/src/Checker.php',
    'HumbugBox464\\KevinGH\\RequirementChecker\\IO' => $baseDir . '/src/IO.php',
    'HumbugBox464\\KevinGH\\RequirementChecker\\IsExtensionConflictFulfilled' => $baseDir . '/src/IsExtensionConflictFulfilled.php',
    'HumbugBox464\\KevinGH\\RequirementChecker\\IsExtensionFulfilled' => $baseDir . '/src/IsExtensionFulfilled.php',
    'HumbugBox464\\KevinGH\\RequirementChecker\\IsFulfilled' => $baseDir . '/src/IsFulfilled.php',
    'HumbugBox464\\KevinGH\\RequirementChecker\\IsPhpVersionFulfilled' => $baseDir . '/src/IsPhpVersionFulfilled.php',
    'HumbugBox464\\KevinGH\\RequirementChecker\\Printer' => $baseDir . '/src/Printer.php',
    'HumbugBox464\\KevinGH\\RequirementChecker\\Requirement' => $baseDir . '/src/Requirement.php',
    'HumbugBox464\\KevinGH\\RequirementChecker\\RequirementCollection' => $baseDir . '/src/RequirementCollection.php',
    'HumbugBox464\\KevinGH\\RequirementChecker\\Terminal' => $baseDir . '/src/Terminal.php',
);
<?php

// autoload_static.php @generated by Composer

namespace Composer\Autoload;

class ComposerStaticInitHumbugBox464
{
    public static $prefixLengthsPsr4 = array (
        'H' => 
        array (
            'HumbugBox464\\KevinGH\\RequirementChecker\\' => 40,
            'HumbugBox464\\Composer\\Semver\\' => 29,
        ),
    );

    public static $prefixDirsPsr4 = array (
        'HumbugBox464\\KevinGH\\RequirementChecker\\' => 
        array (
            0 => __DIR__ . '/../..' . '/src',
        ),
        'HumbugBox464\\Composer\\Semver\\' => 
        array (
            0 => __DIR__ . '/..' . '/composer/semver/src',
        ),
    );

    public static $classMap = array (
        'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
        'HumbugBox464\\Composer\\Semver\\Comparator' => __DIR__ . '/..' . '/composer/semver/src/Comparator.php',
        'HumbugBox464\\Composer\\Semver\\CompilingMatcher' => __DIR__ . '/..' . '/composer/semver/src/CompilingMatcher.php',
        'HumbugBox464\\Composer\\Semver\\Constraint\\Bound' => __DIR__ . '/..' . '/composer/semver/src/Constraint/Bound.php',
        'HumbugBox464\\Composer\\Semver\\Constraint\\Constraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/Constraint.php',
        'HumbugBox464\\Composer\\Semver\\Constraint\\ConstraintInterface' => __DIR__ . '/..' . '/composer/semver/src/Constraint/ConstraintInterface.php',
        'HumbugBox464\\Composer\\Semver\\Constraint\\MatchAllConstraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/MatchAllConstraint.php',
        'HumbugBox464\\Composer\\Semver\\Constraint\\MatchNoneConstraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/MatchNoneConstraint.php',
        'HumbugBox464\\Composer\\Semver\\Constraint\\MultiConstraint' => __DIR__ . '/..' . '/composer/semver/src/Constraint/MultiConstraint.php',
        'HumbugBox464\\Composer\\Semver\\Interval' => __DIR__ . '/..' . '/composer/semver/src/Interval.php',
        'HumbugBox464\\Composer\\Semver\\Intervals' => __DIR__ . '/..' . '/composer/semver/src/Intervals.php',
        'HumbugBox464\\Composer\\Semver\\Semver' => __DIR__ . '/..' . '/composer/semver/src/Semver.php',
        'HumbugBox464\\Composer\\Semver\\VersionParser' => __DIR__ . '/..' . '/composer/semver/src/VersionParser.php',
        'HumbugBox464\\KevinGH\\RequirementChecker\\Checker' => __DIR__ . '/../..' . '/src/Checker.php',
        'HumbugBox464\\KevinGH\\RequirementChecker\\IO' => __DIR__ . '/../..' . '/src/IO.php',
        'HumbugBox464\\KevinGH\\RequirementChecker\\IsExtensionConflictFulfilled' => __DIR__ . '/../..' . '/src/IsExtensionConflictFulfilled.php',
        'HumbugBox464\\KevinGH\\RequirementChecker\\IsExtensionFulfilled' => __DIR__ . '/../..' . '/src/IsExtensionFulfilled.php',
        'HumbugBox464\\KevinGH\\RequirementChecker\\IsFulfilled' => __DIR__ . '/../..' . '/src/IsFulfilled.php',
        'HumbugBox464\\KevinGH\\RequirementChecker\\IsPhpVersionFulfilled' => __DIR__ . '/../..' . '/src/IsPhpVersionFulfilled.php',
        'HumbugBox464\\KevinGH\\RequirementChecker\\Printer' => __DIR__ . '/../..' . '/src/Printer.php',
        'HumbugBox464\\KevinGH\\RequirementChecker\\Requirement' => __DIR__ . '/../..' . '/src/Requirement.php',
        'HumbugBox464\\KevinGH\\RequirementChecker\\RequirementCollection' => __DIR__ . '/../..' . '/src/RequirementCollection.php',
        'HumbugBox464\\KevinGH\\RequirementChecker\\Terminal' => __DIR__ . '/../..' . '/src/Terminal.php',
    );

    public static function getInitializer(ClassLoader $loader)
    {
        return \Closure::bind(function () use ($loader) {
            $loader->prefixLengthsPsr4 = ComposerStaticInitHumbugBox464::$prefixLengthsPsr4;
            $loader->prefixDirsPsr4 = ComposerStaticInitHumbugBox464::$prefixDirsPsr4;
            $loader->classMap = ComposerStaticInitHumbugBox464::$classMap;

        }, null, ClassLoader::class);
    }
}
<?php

// autoload_real.php @generated by Composer

class ComposerAutoloaderInitHumbugBox464
{
    private static $loader;

    public static function loadClassLoader($class)
    {
        if ('Composer\Autoload\ClassLoader' === $class) {
            require __DIR__ . '/ClassLoader.php';
        }
    }

    /**
     * @return \Composer\Autoload\ClassLoader
     */
    public static function getLoader()
    {
        if (null !== self::$loader) {
            return self::$loader;
        }

        spl_autoload_register(array('ComposerAutoloaderInitHumbugBox464', 'loadClassLoader'), true, true);
        self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
        spl_autoload_unregister(array('ComposerAutoloaderInitHumbugBox464', 'loadClassLoader'));

        require __DIR__ . '/autoload_static.php';
        call_user_func(\Composer\Autoload\ComposerStaticInitHumbugBox464::getInitializer($loader));

        $loader->setClassMapAuthoritative(true);
        $loader->register(true);

        return $loader;
    }
}
<?php

// autoload_namespaces.php @generated by Composer

$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);

return array(
);
<?php

declare (strict_types=1);
namespace HumbugBox464\KevinGH\RequirementChecker;

use function extension_loaded;
final class IsExtensionConflictFulfilled implements IsFulfilled
{
    private $conflictingExtension;
    public function __construct(string $requiredExtension)
    {
        $this->conflictingExtension = $requiredExtension;
    }
    public function __invoke(): bool
    {
        return !extension_loaded($this->conflictingExtension);
    }
}
<?php

declare (strict_types=1);
namespace HumbugBox464\KevinGH\RequirementChecker;

use function exec;
use function fclose;
use function fopen;
use function function_exists;
use function getenv;
use function preg_match;
use function proc_close;
use function proc_open;
use function sapi_windows_cp_get;
use function sapi_windows_cp_set;
use function sapi_windows_vt100_support;
use function stream_get_contents;
use function trim;
use const DIRECTORY_SEPARATOR;
class Terminal
{
    private static $width;
    private static $height;
    private static $stty;
    public function getWidth(): int
    {
        $width = getenv('COLUMNS');
        if (\false !== $width) {
            return (int) trim($width);
        }
        if (!isset(self::$width)) {
            self::initDimensions();
        }
        return self::$width ?: 80;
    }
    public function getHeight(): int
    {
        $height = getenv('LINES');
        if (\false !== $height) {
            return (int) trim($height);
        }
        if (!isset(self::$height)) {
            self::initDimensions();
        }
        return self::$height ?: 50;
    }
    public static function hasSttyAvailable(): bool
    {
        if (isset(self::$stty)) {
            return self::$stty;
        }
        if (!function_exists('exec')) {
            return \false;
        }
        exec('stty 2>&1', $output, $exitcode);
        return self::$stty = 0 === $exitcode;
    }
    private static function initDimensions(): void
    {
        if ('\\' === DIRECTORY_SEPARATOR) {
            if (preg_match('/^(\d+)x(\d+)(?: \((\d+)x(\d+)\))?$/', trim(getenv('ANSICON') ?: ''), $matches)) {
                self::$width = (int) $matches[1];
                self::$height = isset($matches[4]) ? (int) $matches[4] : (int) $matches[2];
            } elseif (!self::hasVt100Support() && self::hasSttyAvailable()) {
                self::initDimensionsUsingStty();
            } elseif (null !== $dimensions = self::getConsoleMode()) {
                self::$width = (int) $dimensions[0];
                self::$height = (int) $dimensions[1];
            }
        } else {
            self::initDimensionsUsingStty();
        }
    }
    private static function hasVt100Support(): bool
    {
        return function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support(fopen('php://stdout', 'wb'));
    }
    private static function initDimensionsUsingStty(): void
    {
        if ($sttyString = self::getSttyColumns()) {
            if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) {
                self::$width = (int) $matches[2];
                self::$height = (int) $matches[1];
            } elseif (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) {
                self::$width = (int) $matches[2];
                self::$height = (int) $matches[1];
            }
        }
    }
    private static function getConsoleMode(): ?array
    {
        $info = self::readFromProcess('mode CON');
        if (null === $info || !preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
            return null;
        }
        return [(int) $matches[2], (int) $matches[1]];
    }
    private static function getSttyColumns(): ?string
    {
        return self::readFromProcess('stty -a | grep columns');
    }
    private static function readFromProcess(string $command): ?string
    {
        if (!function_exists('proc_open')) {
            return null;
        }
        $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
        $cp = function_exists('sapi_windows_cp_set') ? sapi_windows_cp_get() : 0;
        if (!$process = @proc_open($command, $descriptorspec, $pipes, null, null, ['suppress_errors' => \true])) {
            return null;
        }
        $info = stream_get_contents($pipes[1]);
        fclose($pipes[1]);
        fclose($pipes[2]);
        proc_close($process);
        if ($cp) {
            sapi_windows_cp_set($cp);
        }
        return $info;
    }
}
<?php

declare (strict_types=1);
namespace HumbugBox464\KevinGH\RequirementChecker;

use function array_shift;
use function count;
use function explode;
use function ltrim;
use function min;
use function sprintf;
use function str_pad;
use function str_repeat;
use function strlen;
use function trim;
use function wordwrap;
use const PHP_EOL;
final class Printer
{
    private $styles = ['reset' => "\x1b[0m", 'red' => "\x1b[31m", 'green' => "\x1b[32m", 'yellow' => "\x1b[33m", 'title' => "\x1b[33m", 'error' => "\x1b[37;41m", 'success' => "\x1b[30;42m"];
    private $verbosity;
    private $supportColors;
    private $width;
    public function __construct(int $verbosity, bool $supportColors, ?int $width = null)
    {
        if (null === $width) {
            $terminal = new Terminal();
            $width = $terminal->getWidth();
        }
        $this->verbosity = $verbosity;
        $this->supportColors = $supportColors;
        $this->width = $width ?: 80;
    }
    public function getVerbosity(): int
    {
        return $this->verbosity;
    }
    public function setVerbosity($verbosity): void
    {
        $this->verbosity = $verbosity;
    }
    public function title(string $title, int $verbosity, ?string $style = null): void
    {
        if (null === $style) {
            $style = 'title';
        }
        $this->printvln('', $verbosity, $style);
        $this->printvln($title, $verbosity, $style);
        $this->printvln(str_repeat('=', min(strlen($title), $this->width)), $verbosity, $style);
        $this->printvln('', $verbosity, $style);
    }
    public function getRequirementErrorMessage(Requirement $requirement): ?string
    {
        if ($requirement->isFulfilled()) {
            return null;
        }
        return wordwrap($requirement->getHelpText(), $this->width - 3, PHP_EOL . '   ') . PHP_EOL;
    }
    public function block(string $title, string $message, int $verbosity, ?string $style = null): void
    {
        $prefix = ' [' . $title . '] ';
        $lineLength = $this->width - strlen($prefix) - 1;
        if ($lineLength < 0) {
            $lineLength = 0;
        }
        $message = $prefix . trim($message);
        $lines = [];
        $remainingMessage = $message;
        $wrapped = wordwrap($remainingMessage, $lineLength, '¬');
        $wrapped = explode('¬', $wrapped);
        do {
            $line = array_shift($wrapped);
            if ($lines && $lineLength > 0) {
                $line = str_repeat(' ', strlen($prefix)) . ltrim($line);
            }
            $lines[] = str_pad($line, $this->width, ' ', \STR_PAD_RIGHT);
        } while (count($wrapped));
        $this->printvln('', $verbosity);
        $this->printvln(str_repeat(' ', $this->width), $verbosity, $style);
        foreach ($lines as $line) {
            $this->printvln($line, $verbosity, $style);
        }
        $this->printv(str_repeat(' ', $this->width), $verbosity, $style);
        $this->printvln('', $verbosity);
    }
    public function printvln(string $message, int $verbosity, ?string $style = null): void
    {
        $this->printv($message, $verbosity, $style);
        $this->printv(PHP_EOL, $verbosity, null);
    }
    public function printv(string $message, int $verbosity, ?string $style = null): void
    {
        if ($verbosity > $this->verbosity) {
            return;
        }
        $message = wordwrap($message, $this->width);
        $message = sprintf('%s%s%s', $this->supportColors && isset($this->styles[$style]) ? $this->styles[$style] : '', $message, $this->supportColors ? $this->styles['reset'] : '');
        if ('1' === getenv('BOX_REQUIREMENTS_CHECKER_LOG_TO_STDOUT')) {
            echo $message;
        } else {
            fwrite(\STDERR, $message);
        }
    }
}
<?php

declare (strict_types=1);
namespace HumbugBox464\KevinGH\RequirementChecker;

use function extension_loaded;
final class IsExtensionFulfilled implements IsFulfilled
{
    private $requiredExtension;
    public function __construct(string $requiredExtension)
    {
        $this->requiredExtension = $requiredExtension;
    }
    public function __invoke(): bool
    {
        return extension_loaded($this->requiredExtension);
    }
}
<?php

declare (strict_types=1);
namespace HumbugBox464\KevinGH\RequirementChecker;

final class Requirement
{
    private $checkIsFulfilled;
    private $fulfilled;
    private $testMessage;
    private $helpText;
    public function __construct(IsFulfilled $checkIsFulfilled, string $testMessage, string $helpText)
    {
        $this->checkIsFulfilled = $checkIsFulfilled;
        $this->testMessage = $testMessage;
        $this->helpText = $helpText;
    }
    public function isFulfilled(): bool
    {
        if (!isset($this->fulfilled)) {
            $this->fulfilled = $this->checkIsFulfilled->__invoke();
        }
        return $this->fulfilled;
    }
    public function getIsFullfilledChecker(): IsFulfilled
    {
        return $this->checkIsFulfilled;
    }
    public function getTestMessage(): string
    {
        return $this->testMessage;
    }
    public function getHelpText(): string
    {
        return $this->helpText;
    }
}
<?php

declare (strict_types=1);
namespace HumbugBox464\KevinGH\RequirementChecker;

use InvalidArgumentException;
use function count;
use function sprintf;
final class Checker
{
    private static $requirementsConfig;
    public static function checkRequirements(): bool
    {
        $requirements = self::retrieveRequirements();
        $checkPassed = $requirements->evaluateRequirements();
        $io = new IO();
        self::printCheck($checkPassed, new Printer($io->getVerbosity(), $io->hasColorSupport()), $requirements);
        return $checkPassed;
    }
    public static function printCheck($checkPassed, Printer $printer, RequirementCollection $requirements): void
    {
        if (\false === $checkPassed && IO::VERBOSITY_VERY_VERBOSE > $printer->getVerbosity()) {
            $printer->setVerbosity(IO::VERBOSITY_VERY_VERBOSE);
        }
        $verbosity = IO::VERBOSITY_VERY_VERBOSE;
        $iniPath = $requirements->getPhpIniPath();
        $printer->title('Box Requirements Checker', $verbosity);
        $printer->printv('> Using PHP ', $verbosity);
        $printer->printvln(\PHP_VERSION, $verbosity, 'green');
        if ($iniPath) {
            $printer->printvln('> PHP is using the following php.ini file:', $verbosity);
            $printer->printvln('  ' . $iniPath, $verbosity, 'green');
        } else {
            $printer->printvln('> PHP is not using any php.ini file.', $verbosity, 'yellow');
        }
        $printer->printvln('', $verbosity);
        if (count($requirements) > 0) {
            $printer->printvln('> Checking Box requirements:', $verbosity);
            $printer->printv('  ', $verbosity);
        } else {
            $printer->printvln('> No requirements found.', $verbosity);
        }
        $errorMessages = [];
        foreach ($requirements->getRequirements() as $requirement) {
            if ($errorMessage = $printer->getRequirementErrorMessage($requirement)) {
                if (IO::VERBOSITY_DEBUG === $printer->getVerbosity()) {
                    $printer->printvln('✘ ' . $requirement->getTestMessage(), IO::VERBOSITY_DEBUG, 'red');
                    $printer->printv('  ', IO::VERBOSITY_DEBUG);
                    $errorMessages[] = $errorMessage;
                } else {
                    $printer->printv('E', $verbosity, 'red');
                    $errorMessages[] = $errorMessage;
                }
                continue;
            }
            if (IO::VERBOSITY_DEBUG === $printer->getVerbosity()) {
                $printer->printvln('✔ ' . $requirement->getTestMessage(), IO::VERBOSITY_DEBUG, 'green');
                $printer->printv('  ', IO::VERBOSITY_DEBUG);
            } else {
                $printer->printv('.', $verbosity, 'green');
            }
        }
        if (IO::VERBOSITY_DEBUG !== $printer->getVerbosity() && count($requirements) > 0) {
            $printer->printvln('', $verbosity);
        }
        if ($requirements->evaluateRequirements()) {
            $printer->block('OK', 'Your system is ready to run the application.', $verbosity, 'success');
        } else {
            $printer->block('ERROR', 'Your system is not ready to run the application.', $verbosity, 'error');
            $printer->title('Fix the following mandatory requirements:', $verbosity, 'red');
            foreach ($errorMessages as $errorMessage) {
                $printer->printv(' * ' . $errorMessage, $verbosity);
            }
        }
        $printer->printvln('', $verbosity);
    }
    private static function retrieveRequirements(): RequirementCollection
    {
        if (null === self::$requirementsConfig) {
            self::$requirementsConfig = __DIR__ . '/../.requirements.php';
        }
        $config = require self::$requirementsConfig;
        $requirements = new RequirementCollection();
        foreach ($config as $constraint) {
            $requirements->addRequirement(self::createCondition($constraint['type'], $constraint['condition']), $constraint['message'], $constraint['helpMessage']);
        }
        return $requirements;
    }
    private static function createCondition($type, $condition): IsFulfilled
    {
        switch ($type) {
            case 'php':
                return new IsPhpVersionFulfilled($condition);
            case 'extension':
                return new IsExtensionFulfilled($condition);
            case 'extension-conflict':
                return new IsExtensionConflictFulfilled($condition);
            default:
                throw new InvalidArgumentException(sprintf('Unknown requirement type "%s".', $type));
        }
    }
}
<?php

declare (strict_types=1);
namespace HumbugBox464\KevinGH\RequirementChecker;

use function fstat;
use function function_exists;
use function getenv;
use function implode;
use function posix_isatty;
use function preg_match;
use function preg_quote;
use function sapi_windows_vt100_support;
use function sprintf;
use function str_replace;
use function stream_isatty;
use const DIRECTORY_SEPARATOR;
use const STDOUT;
final class IO
{
    public const VERBOSITY_QUIET = 16;
    public const VERBOSITY_NORMAL = 32;
    public const VERBOSITY_VERBOSE = 64;
    public const VERBOSITY_VERY_VERBOSE = 128;
    public const VERBOSITY_DEBUG = 256;
    private $interactive;
    private $verbosity = self::VERBOSITY_NORMAL;
    private $colorSupport;
    private $options;
    public function __construct()
    {
        $this->options = implode(' ', $_SERVER['argv']);
        $shellVerbosity = $this->configureVerbosity();
        $this->interactive = $this->checkInteractivity($shellVerbosity);
        $this->colorSupport = $this->checkColorSupport();
    }
    public function isInteractive(): bool
    {
        return $this->interactive;
    }
    public function getVerbosity(): int
    {
        return $this->verbosity;
    }
    public function hasColorSupport(): bool
    {
        return $this->colorSupport;
    }
    public function hasParameter($values): bool
    {
        $values = (array) $values;
        foreach ($values as $value) {
            $regexp = sprintf('/\s%s\b/', str_replace(' ', '\s+', preg_quote($value, '/')));
            if (1 === preg_match($regexp, $this->options)) {
                return \true;
            }
        }
        return \false;
    }
    private function checkInteractivity(int $shellVerbosity): bool
    {
        if (-1 === $shellVerbosity) {
            return \false;
        }
        if (\true === $this->hasParameter(['--no-interaction', '-n'])) {
            return \false;
        }
        if (function_exists('posix_isatty') && !@posix_isatty(STDOUT) && \false === getenv('SHELL_INTERACTIVE')) {
            return \false;
        }
        return \true;
    }
    private function configureVerbosity(): int
    {
        switch ($shellVerbosity = (int) getenv('SHELL_VERBOSITY')) {
            case -1:
                $this->verbosity = self::VERBOSITY_QUIET;
                break;
            case 1:
                $this->verbosity = self::VERBOSITY_VERBOSE;
                break;
            case 2:
                $this->verbosity = self::VERBOSITY_VERY_VERBOSE;
                break;
            case 3:
                $this->verbosity = self::VERBOSITY_DEBUG;
                break;
            default:
                $shellVerbosity = 0;
                break;
        }
        if ($this->hasParameter(['--quiet', '-q'])) {
            $this->verbosity = self::VERBOSITY_QUIET;
            $shellVerbosity = -1;
        } elseif ($this->hasParameter(['-vvv', '--verbose=3', '--verbose 3'])) {
            $this->verbosity = self::VERBOSITY_DEBUG;
            $shellVerbosity = 3;
        } elseif ($this->hasParameter(['-vv', '--verbose=2', '--verbose 2'])) {
            $this->verbosity = self::VERBOSITY_VERY_VERBOSE;
            $shellVerbosity = 2;
        } elseif ($this->hasParameter(['-v', '--verbose=1', '--verbose 1', '--verbose'])) {
            $this->verbosity = self::VERBOSITY_VERBOSE;
            $shellVerbosity = 1;
        }
        return $shellVerbosity;
    }
    private function checkColorSupport(): bool
    {
        if ($this->hasParameter(['--ansi'])) {
            return \true;
        }
        if ($this->hasParameter(['--no-ansi'])) {
            return \false;
        }
        if (DIRECTORY_SEPARATOR === '\\') {
            return function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support(STDOUT) || \false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') || 'xterm' === getenv('TERM');
        }
        if (function_exists('stream_isatty')) {
            return stream_isatty(STDOUT);
        }
        if (function_exists('posix_isatty')) {
            return posix_isatty(STDOUT);
        }
        $stat = fstat(STDOUT);
        return $stat ? 020000 === ($stat['mode'] & 0170000) : \false;
    }
}
<?php

declare (strict_types=1);
namespace HumbugBox464\KevinGH\RequirementChecker;

use ArrayIterator;
use Countable;
use IteratorAggregate;
use Traversable;
use function count;
use function get_cfg_var;
final class RequirementCollection implements IteratorAggregate, Countable
{
    private $requirements = [];
    private $phpIniPath;
    public function __construct($phpIniPath = null)
    {
        $this->phpIniPath = $phpIniPath ?? get_cfg_var('cfg_file_path');
    }
    public function getIterator(): Traversable
    {
        return new ArrayIterator($this->requirements);
    }
    public function count(): int
    {
        return count($this->requirements);
    }
    public function add(Requirement $requirement): void
    {
        $this->requirements[] = $requirement;
    }
    public function addRequirement(IsFulfilled $checkIsFulfilled, string $testMessage, string $helpText): void
    {
        $this->add(new Requirement($checkIsFulfilled, $testMessage, $helpText));
    }
    public function getRequirements(): array
    {
        return $this->requirements;
    }
    public function getPhpIniPath()
    {
        return $this->phpIniPath;
    }
    public function evaluateRequirements()
    {
        return array_reduce($this->requirements, static function (bool $checkPassed, Requirement $requirement): bool {
            return $checkPassed && $requirement->isFulfilled();
        }, \true);
    }
}
<?php

declare (strict_types=1);
namespace HumbugBox464\KevinGH\RequirementChecker;

use HumbugBox464\Composer\Semver\Semver;
use function sprintf;
use const PHP_MAJOR_VERSION;
use const PHP_MINOR_VERSION;
use const PHP_RELEASE_VERSION;
final class IsPhpVersionFulfilled implements IsFulfilled
{
    private $requiredPhpVersion;
    public function __construct(string $requiredPhpVersion)
    {
        $this->requiredPhpVersion = $requiredPhpVersion;
    }
    public function __invoke(): bool
    {
        return Semver::satisfies(sprintf('%d.%d.%d', PHP_MAJOR_VERSION, PHP_MINOR_VERSION, PHP_RELEASE_VERSION), $this->requiredPhpVersion);
    }
}
<?php

declare (strict_types=1);
namespace HumbugBox464\KevinGH\RequirementChecker;

interface IsFulfilled
{
    public function __invoke(): bool;
}
<?php

namespace HumbugBox464\KevinGH\RequirementChecker;

if (isset($_SERVER['BOX_REQUIREMENT_CHECKER'])) {
    $enableRequirementChecker = $_SERVER['BOX_REQUIREMENT_CHECKER'];
    if (is_bool($enableRequirementChecker) && !$enableRequirementChecker) {
        return;
    }
    if (is_string($enableRequirementChecker) && in_array(strtolower($enableRequirementChecker), ['false', '0'], \true)) {
        return;
    }
    if (!is_bool($enableRequirementChecker) && !is_string($enableRequirementChecker)) {
        echo \PHP_EOL . 'Unhandled value type for "BOX_REQUIREMENT_CHECKER". Got "' . gettype($enableRequirementChecker) . '". Proceeding with the requirement checks.' . \PHP_EOL;
    }
}
if (\false === in_array(\PHP_SAPI, array('cli', 'phpdbg', 'embed', 'micro'), \true)) {
    echo \PHP_EOL . 'The application may only be invoked from a command line, got "' . \PHP_SAPI . '"' . \PHP_EOL;
    exit(1);
}
require __DIR__ . '/../vendor/autoload.php';
if (!Checker::checkRequirements()) {
    exit(1);
}
<?php

namespace _HumbugBox726377e086ba;

return array (
  0 => 
  array (
    'type' => 'php',
    'condition' => '8.1.*||8.2.*||8.3.*||8.4.*||8.5.*',
    'source' => NULL,
    'message' => 'This application requires a PHP version matching "8.1.*||8.2.*||8.3.*||8.4.*||8.5.*".',
    'helpMessage' => 'This application requires a PHP version matching "8.1.*||8.2.*||8.3.*||8.4.*||8.5.*".',
  ),
  1 => 
  array (
    'type' => 'extension',
    'condition' => 'iconv',
    'source' => 'symfony/polyfill-mbstring',
    'message' => 'The package "symfony/polyfill-mbstring" requires the extension "iconv".',
    'helpMessage' => 'The package "symfony/polyfill-mbstring" requires the extension "iconv". You either need to enable it or request the application to be shipped with a polyfill for this extension.',
  ),
  2 => 
  array (
    'type' => 'extension',
    'condition' => 'json',
    'source' => 'justinrainbow/json-schema',
    'message' => 'The package "justinrainbow/json-schema" requires the extension "json".',
    'helpMessage' => 'The package "justinrainbow/json-schema" requires the extension "json". You either need to enable it or request the application to be shipped with a polyfill for this extension.',
  ),
  3 => 
  array (
    'type' => 'extension',
    'condition' => 'openssl',
    'source' => 'composer/ca-bundle',
    'message' => 'The package "composer/ca-bundle" requires the extension "openssl".',
    'helpMessage' => 'The package "composer/ca-bundle" requires the extension "openssl". You either need to enable it or request the application to be shipped with a polyfill for this extension.',
  ),
  4 => 
  array (
    'type' => 'extension',
    'condition' => 'pcre',
    'source' => 'composer/ca-bundle',
    'message' => 'The package "composer/ca-bundle" requires the extension "pcre".',
    'helpMessage' => 'The package "composer/ca-bundle" requires the extension "pcre". You either need to enable it or request the application to be shipped with a polyfill for this extension.',
  ),
  5 => 
  array (
    'type' => 'extension',
    'condition' => 'reflection',
    'source' => 'marc-mabe/php-enum',
    'message' => 'The package "marc-mabe/php-enum" requires the extension "reflection".',
    'helpMessage' => 'The package "marc-mabe/php-enum" requires the extension "reflection". You either need to enable it or request the application to be shipped with a polyfill for this extension.',
  ),
  6 => 
  array (
    'type' => 'extension-conflict',
    'condition' => 'psr',
    'source' => 'symfony/service-contracts',
    'message' => 'The package "symfony/service-contracts" conflicts with the extension "psr".',
    'helpMessage' => 'The package "symfony/service-contracts" conflicts with the extension "psr". You need to disable it in order to run this application.',
  ),
);ݚ.mFnh\T Xm
^+[9ZBϾ>;'f"E6U's   GBMBThe MIT License (MIT)

Copyright (c) 2012 Jan Sorgalla, Christian Lück, Cees-Jan Kiewiet, Chris Boden

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.
<?php

namespace React\Promise\Internal;

use React\Promise\PromiseInterface;
use function React\Promise\_checkTypehint;
use function React\Promise\resolve;
use function React\Promise\set_rejection_handler;

/**
 * @internal
 *
 * @template-implements PromiseInterface<never>
 */
final class RejectedPromise implements PromiseInterface
{
    /** @var \Throwable */
    private $reason;

    /** @var bool */
    private $handled = false;

    /**
     * @param \Throwable $reason
     */
    public function __construct(\Throwable $reason)
    {
        $this->reason = $reason;
    }

    /** @throws void */
    public function __destruct()
    {
        if ($this->handled) {
            return;
        }

        $handler = set_rejection_handler(null);
        if ($handler === null) {
            $message = 'Unhandled promise rejection with ' . $this->reason;

            \error_log($message);
            return;
        }

        try {
            $handler($this->reason);
        } catch (\Throwable $e) {
            \preg_match('/^([^:\s]++)(.*+)$/sm', (string) $e, $match);
            \assert(isset($match[1], $match[2]));
            $message = 'Fatal error: Uncaught ' . $match[1] . ' from unhandled promise rejection handler' . $match[2];

            \error_log($message);
            exit(255);
        }
    }

    /**
     * @template TRejected
     * @param ?callable $onFulfilled
     * @param ?(callable(\Throwable): (PromiseInterface<TRejected>|TRejected)) $onRejected
     * @return PromiseInterface<($onRejected is null ? never : TRejected)>
     */
    public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface
    {
        if (null === $onRejected) {
            return $this;
        }

        $this->handled = true;

        try {
            return resolve($onRejected($this->reason));
        } catch (\Throwable $exception) {
            return new RejectedPromise($exception);
        }
    }

    /**
     * @template TThrowable of \Throwable
     * @template TRejected
     * @param callable(TThrowable): (PromiseInterface<TRejected>|TRejected) $onRejected
     * @return PromiseInterface<TRejected>
     */
    public function catch(callable $onRejected): PromiseInterface
    {
        if (!_checkTypehint($onRejected, $this->reason)) {
            return $this;
        }

        /**
         * @var callable(\Throwable):(PromiseInterface<TRejected>|TRejected) $onRejected
         */
        return $this->then(null, $onRejected);
    }

    public function finally(callable $onFulfilledOrRejected): PromiseInterface
    {
        return $this->then(null, function (\Throwable $reason) use ($onFulfilledOrRejected): PromiseInterface {
            return resolve($onFulfilledOrRejected())->then(function () use ($reason): PromiseInterface {
                return new RejectedPromise($reason);
            });
        });
    }

    public function cancel(): void
    {
        $this->handled = true;
    }

    /**
     * @deprecated 3.0.0 Use `catch()` instead
     * @see self::catch()
     */
    public function otherwise(callable $onRejected): PromiseInterface
    {
        return $this->catch($onRejected);
    }

    /**
     * @deprecated 3.0.0 Use `always()` instead
     * @see self::always()
     */
    public function always(callable $onFulfilledOrRejected): PromiseInterface
    {
        return $this->finally($onFulfilledOrRejected);
    }
}
<?php

namespace React\Promise\Internal;

/**
 * @internal
 */
final class CancellationQueue
{
    /** @var bool */
    private $started = false;

    /** @var object[] */
    private $queue = [];

    public function __invoke(): void
    {
        if ($this->started) {
            return;
        }

        $this->started = true;
        $this->drain();
    }

    /**
     * @param mixed $cancellable
     */
    public function enqueue($cancellable): void
    {
        if (!\is_object($cancellable) || !\method_exists($cancellable, 'then') || !\method_exists($cancellable, 'cancel')) {
            return;
        }

        $length = \array_push($this->queue, $cancellable);

        if ($this->started && 1 === $length) {
            $this->drain();
        }
    }

    private function drain(): void
    {
        for ($i = \key($this->queue); isset($this->queue[$i]); $i++) {
            $cancellable = $this->queue[$i];
            assert(\method_exists($cancellable, 'cancel'));

            $exception = null;

            try {
                $cancellable->cancel();
            } catch (\Throwable $exception) {
            }

            unset($this->queue[$i]);

            if ($exception) {
                throw $exception;
            }
        }

        $this->queue = [];
    }
}
<?php

namespace React\Promise\Internal;

use React\Promise\PromiseInterface;
use function React\Promise\resolve;

/**
 * @internal
 *
 * @template T
 * @template-implements PromiseInterface<T>
 */
final class FulfilledPromise implements PromiseInterface
{
    /** @var T */
    private $value;

    /**
     * @param T $value
     * @throws \InvalidArgumentException
     */
    public function __construct($value = null)
    {
        if ($value instanceof PromiseInterface) {
            throw new \InvalidArgumentException('You cannot create React\Promise\FulfilledPromise with a promise. Use React\Promise\resolve($promiseOrValue) instead.');
        }

        $this->value = $value;
    }

    /**
     * @template TFulfilled
     * @param ?(callable((T is void ? null : T)): (PromiseInterface<TFulfilled>|TFulfilled)) $onFulfilled
     * @return PromiseInterface<($onFulfilled is null ? T : TFulfilled)>
     */
    public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface
    {
        if (null === $onFulfilled) {
            return $this;
        }

        try {
            /**
             * @var PromiseInterface<T>|T $result
             */
            $result = $onFulfilled($this->value);
            return resolve($result);
        } catch (\Throwable $exception) {
            return new RejectedPromise($exception);
        }
    }

    public function catch(callable $onRejected): PromiseInterface
    {
        return $this;
    }

    public function finally(callable $onFulfilledOrRejected): PromiseInterface
    {
        return $this->then(function ($value) use ($onFulfilledOrRejected): PromiseInterface {
            /** @var T $value */
            return resolve($onFulfilledOrRejected())->then(function () use ($value) {
                return $value;
            });
        });
    }

    public function cancel(): void
    {
    }

    /**
     * @deprecated 3.0.0 Use `catch()` instead
     * @see self::catch()
     */
    public function otherwise(callable $onRejected): PromiseInterface
    {
        return $this->catch($onRejected);
    }

    /**
     * @deprecated 3.0.0 Use `finally()` instead
     * @see self::finally()
     */
    public function always(callable $onFulfilledOrRejected): PromiseInterface
    {
        return $this->finally($onFulfilledOrRejected);
    }
}
<?php

namespace React\Promise;

use React\Promise\Exception\CompositeException;
use React\Promise\Internal\FulfilledPromise;
use React\Promise\Internal\RejectedPromise;

/**
 * Creates a promise for the supplied `$promiseOrValue`.
 *
 * If `$promiseOrValue` is a value, it will be the resolution value of the
 * returned promise.
 *
 * If `$promiseOrValue` is a thenable (any object that provides a `then()` method),
 * a trusted promise that follows the state of the thenable is returned.
 *
 * If `$promiseOrValue` is a promise, it will be returned as is.
 *
 * @template T
 * @param PromiseInterface<T>|T $promiseOrValue
 * @return PromiseInterface<T>
 */
function resolve($promiseOrValue): PromiseInterface
{
    if ($promiseOrValue instanceof PromiseInterface) {
        return $promiseOrValue;
    }

    if (\is_object($promiseOrValue) && \method_exists($promiseOrValue, 'then')) {
        $canceller = null;

        if (\method_exists($promiseOrValue, 'cancel')) {
            $canceller = [$promiseOrValue, 'cancel'];
            assert(\is_callable($canceller));
        }

        /** @var Promise<T> */
        return new Promise(function (callable $resolve, callable $reject) use ($promiseOrValue): void {
            $promiseOrValue->then($resolve, $reject);
        }, $canceller);
    }

    return new FulfilledPromise($promiseOrValue);
}

/**
 * Creates a rejected promise for the supplied `$reason`.
 *
 * If `$reason` is a value, it will be the rejection value of the
 * returned promise.
 *
 * If `$reason` is a promise, its completion value will be the rejected
 * value of the returned promise.
 *
 * This can be useful in situations where you need to reject a promise without
 * throwing an exception. For example, it allows you to propagate a rejection with
 * the value of another promise.
 *
 * @return PromiseInterface<never>
 */
function reject(\Throwable $reason): PromiseInterface
{
    return new RejectedPromise($reason);
}

/**
 * Returns a promise that will resolve only once all the items in
 * `$promisesOrValues` have resolved. The resolution value of the returned promise
 * will be an array containing the resolution values of each of the items in
 * `$promisesOrValues`.
 *
 * @template T
 * @param iterable<PromiseInterface<T>|T> $promisesOrValues
 * @return PromiseInterface<array<T>>
 */
function all(iterable $promisesOrValues): PromiseInterface
{
    $cancellationQueue = new Internal\CancellationQueue();

    /** @var Promise<array<T>> */
    return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void {
        $toResolve = 0;
        /** @var bool */
        $continue  = true;
        $values    = [];

        foreach ($promisesOrValues as $i => $promiseOrValue) {
            $cancellationQueue->enqueue($promiseOrValue);
            $values[$i] = null;
            ++$toResolve;

            resolve($promiseOrValue)->then(
                function ($value) use ($i, &$values, &$toResolve, &$continue, $resolve): void {
                    $values[$i] = $value;

                    if (0 === --$toResolve && !$continue) {
                        $resolve($values);
                    }
                },
                function (\Throwable $reason) use (&$continue, $reject): void {
                    $continue = false;
                    $reject($reason);
                }
            );

            if (!$continue && !\is_array($promisesOrValues)) {
                break;
            }
        }

        $continue = false;
        if ($toResolve === 0) {
            $resolve($values);
        }
    }, $cancellationQueue);
}

/**
 * Initiates a competitive race that allows one winner. Returns a promise which is
 * resolved in the same way the first settled promise resolves.
 *
 * The returned promise will become **infinitely pending** if  `$promisesOrValues`
 * contains 0 items.
 *
 * @template T
 * @param iterable<PromiseInterface<T>|T> $promisesOrValues
 * @return PromiseInterface<T>
 */
function race(iterable $promisesOrValues): PromiseInterface
{
    $cancellationQueue = new Internal\CancellationQueue();

    /** @var Promise<T> */
    return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void {
        $continue = true;

        foreach ($promisesOrValues as $promiseOrValue) {
            $cancellationQueue->enqueue($promiseOrValue);

            resolve($promiseOrValue)->then($resolve, $reject)->finally(function () use (&$continue): void {
                $continue = false;
            });

            if (!$continue && !\is_array($promisesOrValues)) {
                break;
            }
        }
    }, $cancellationQueue);
}

/**
 * Returns a promise that will resolve when any one of the items in
 * `$promisesOrValues` resolves. The resolution value of the returned promise
 * will be the resolution value of the triggering item.
 *
 * The returned promise will only reject if *all* items in `$promisesOrValues` are
 * rejected. The rejection value will be an array of all rejection reasons.
 *
 * The returned promise will also reject with a `React\Promise\Exception\LengthException`
 * if `$promisesOrValues` contains 0 items.
 *
 * @template T
 * @param iterable<PromiseInterface<T>|T> $promisesOrValues
 * @return PromiseInterface<T>
 */
function any(iterable $promisesOrValues): PromiseInterface
{
    $cancellationQueue = new Internal\CancellationQueue();

    /** @var Promise<T> */
    return new Promise(function (callable $resolve, callable $reject) use ($promisesOrValues, $cancellationQueue): void {
        $toReject = 0;
        $continue = true;
        $reasons  = [];

        foreach ($promisesOrValues as $i => $promiseOrValue) {
            $cancellationQueue->enqueue($promiseOrValue);
            ++$toReject;

            resolve($promiseOrValue)->then(
                function ($value) use ($resolve, &$continue): void {
                    $continue = false;
                    $resolve($value);
                },
                function (\Throwable $reason) use ($i, &$reasons, &$toReject, $reject, &$continue): void {
                    $reasons[$i] = $reason;

                    if (0 === --$toReject && !$continue) {
                        $reject(new CompositeException(
                            $reasons,
                            'All promises rejected.'
                        ));
                    }
                }
            );

            if (!$continue && !\is_array($promisesOrValues)) {
                break;
            }
        }

        $continue = false;
        if ($toReject === 0 && !$reasons) {
            $reject(new Exception\LengthException(
                'Must contain at least 1 item but contains only 0 items.'
            ));
        } elseif ($toReject === 0) {
            $reject(new CompositeException(
                $reasons,
                'All promises rejected.'
            ));
        }
    }, $cancellationQueue);
}

/**
 * Sets the global rejection handler for unhandled promise rejections.
 *
 * Note that rejected promises should always be handled similar to how any
 * exceptions should always be caught in a `try` + `catch` block. If you remove
 * the last reference to a rejected promise that has not been handled, it will
 * report an unhandled promise rejection. See also the [`reject()` function](#reject)
 * for more details.
 *
 * The `?callable $callback` argument MUST be a valid callback function that
 * accepts a single `Throwable` argument or a `null` value to restore the
 * default promise rejection handler. The return value of the callback function
 * will be ignored and has no effect, so you SHOULD return a `void` value. The
 * callback function MUST NOT throw or the program will be terminated with a
 * fatal error.
 *
 * The function returns the previous rejection handler or `null` if using the
 * default promise rejection handler.
 *
 * The default promise rejection handler will log an error message plus its
 * stack trace:
 *
 * ```php
 * // Unhandled promise rejection with RuntimeException: Unhandled in example.php:2
 * React\Promise\reject(new RuntimeException('Unhandled'));
 * ```
 *
 * The promise rejection handler may be used to use customize the log message or
 * write to custom log targets. As a rule of thumb, this function should only be
 * used as a last resort and promise rejections are best handled with either the
 * [`then()` method](#promiseinterfacethen), the
 * [`catch()` method](#promiseinterfacecatch), or the
 * [`finally()` method](#promiseinterfacefinally).
 * See also the [`reject()` function](#reject) for more details.
 *
 * @param callable(\Throwable):void|null $callback
 * @return callable(\Throwable):void|null
 */
function set_rejection_handler(?callable $callback): ?callable
{
    static $current = null;
    $previous = $current;
    $current = $callback;

    return $previous;
}

/**
 * @internal
 */
function _checkTypehint(callable $callback, \Throwable $reason): bool
{
    if (\is_array($callback)) {
        $callbackReflection = new \ReflectionMethod($callback[0], $callback[1]);
    } elseif (\is_object($callback) && !$callback instanceof \Closure) {
        $callbackReflection = new \ReflectionMethod($callback, '__invoke');
    } else {
        assert($callback instanceof \Closure || \is_string($callback));
        $callbackReflection = new \ReflectionFunction($callback);
    }

    $parameters = $callbackReflection->getParameters();

    if (!isset($parameters[0])) {
        return true;
    }

    $expectedException = $parameters[0];

    // Extract the type of the argument and handle different possibilities
    $type = $expectedException->getType();

    $isTypeUnion = true;
    $types = [];

    switch (true) {
        case $type === null:
            break;
        case $type instanceof \ReflectionNamedType:
            $types = [$type];
            break;
        case $type instanceof \ReflectionIntersectionType:
            $isTypeUnion = false;
        case $type instanceof \ReflectionUnionType:
            $types = $type->getTypes();
            break;
        default:
            throw new \LogicException('Unexpected return value of ReflectionParameter::getType');
    }

    // If there is no type restriction, it matches
    if (empty($types)) {
        return true;
    }

    foreach ($types as $type) {

        if ($type instanceof \ReflectionIntersectionType) {
            foreach ($type->getTypes() as $typeToMatch) {
                assert($typeToMatch instanceof \ReflectionNamedType);
                $name = $typeToMatch->getName();
                if (!($matches = (!$typeToMatch->isBuiltin() && $reason instanceof $name))) {
                    break;
                }
            }
            assert(isset($matches));
        } else {
            assert($type instanceof \ReflectionNamedType);
            $name = $type->getName();
            $matches = !$type->isBuiltin() && $reason instanceof $name;
        }

        // If we look for a single match (union), we can return early on match
        // If we look for a full match (intersection), we can return early on mismatch
        if ($matches) {
            if ($isTypeUnion) {
                return true;
            }
        } else {
            if (!$isTypeUnion) {
                return false;
            }
        }
    }

    // If we look for a single match (union) and did not return early, we matched no type and are false
    // If we look for a full match (intersection) and did not return early, we matched all types and are true
    return $isTypeUnion ? false : true;
}
<?php

namespace React\Promise;

/**
 * @template-covariant T
 */
interface PromiseInterface
{
    /**
     * Transforms a promise's value by applying a function to the promise's fulfillment
     * or rejection value. Returns a new promise for the transformed result.
     *
     * The `then()` method registers new fulfilled and rejection handlers with a promise
     * (all parameters are optional):
     *
     *  * `$onFulfilled` will be invoked once the promise is fulfilled and passed
     *     the result as the first argument.
     *  * `$onRejected` will be invoked once the promise is rejected and passed the
     *     reason as the first argument.
     *
     * It returns a new promise that will fulfill with the return value of either
     * `$onFulfilled` or `$onRejected`, whichever is called, or will reject with
     * the thrown exception if either throws.
     *
     * A promise makes the following guarantees about handlers registered in
     * the same call to `then()`:
     *
     *  1. Only one of `$onFulfilled` or `$onRejected` will be called,
     *      never both.
     *  2. `$onFulfilled` and `$onRejected` will never be called more
     *      than once.
     *
     * @template TFulfilled
     * @template TRejected
     * @param ?(callable((T is void ? null : T)): (PromiseInterface<TFulfilled>|TFulfilled)) $onFulfilled
     * @param ?(callable(\Throwable): (PromiseInterface<TRejected>|TRejected)) $onRejected
     * @return PromiseInterface<($onRejected is null ? ($onFulfilled is null ? T : TFulfilled) : ($onFulfilled is null ? T|TRejected : TFulfilled|TRejected))>
     */
    public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface;

    /**
     * Registers a rejection handler for promise. It is a shortcut for:
     *
     * ```php
     * $promise->then(null, $onRejected);
     * ```
     *
     * Additionally, you can type hint the `$reason` argument of `$onRejected` to catch
     * only specific errors.
     *
     * @template TThrowable of \Throwable
     * @template TRejected
     * @param callable(TThrowable): (PromiseInterface<TRejected>|TRejected) $onRejected
     * @return PromiseInterface<T|TRejected>
     */
    public function catch(callable $onRejected): PromiseInterface;

    /**
     * Allows you to execute "cleanup" type tasks in a promise chain.
     *
     * It arranges for `$onFulfilledOrRejected` to be called, with no arguments,
     * when the promise is either fulfilled or rejected.
     *
     * * If `$promise` fulfills, and `$onFulfilledOrRejected` returns successfully,
     *    `$newPromise` will fulfill with the same value as `$promise`.
     * * If `$promise` fulfills, and `$onFulfilledOrRejected` throws or returns a
     *    rejected promise, `$newPromise` will reject with the thrown exception or
     *    rejected promise's reason.
     * * If `$promise` rejects, and `$onFulfilledOrRejected` returns successfully,
     *    `$newPromise` will reject with the same reason as `$promise`.
     * * If `$promise` rejects, and `$onFulfilledOrRejected` throws or returns a
     *    rejected promise, `$newPromise` will reject with the thrown exception or
     *    rejected promise's reason.
     *
     * `finally()` behaves similarly to the synchronous finally statement. When combined
     * with `catch()`, `finally()` allows you to write code that is similar to the familiar
     * synchronous catch/finally pair.
     *
     * Consider the following synchronous code:
     *
     * ```php
     * try {
     *     return doSomething();
     * } catch(\Exception $e) {
     *     return handleError($e);
     * } finally {
     *     cleanup();
     * }
     * ```
     *
     * Similar asynchronous code (with `doSomething()` that returns a promise) can be
     * written:
     *
     * ```php
     * return doSomething()
     *     ->catch('handleError')
     *     ->finally('cleanup');
     * ```
     *
     * @param callable(): (void|PromiseInterface<void>) $onFulfilledOrRejected
     * @return PromiseInterface<T>
     */
    public function finally(callable $onFulfilledOrRejected): PromiseInterface;

    /**
     * The `cancel()` method notifies the creator of the promise that there is no
     * further interest in the results of the operation.
     *
     * Once a promise is settled (either fulfilled or rejected), calling `cancel()` on
     * a promise has no effect.
     *
     * @return void
     */
    public function cancel(): void;

    /**
     * [Deprecated] Registers a rejection handler for a promise.
     *
     * This method continues to exist only for BC reasons and to ease upgrading
     * between versions. It is an alias for:
     *
     * ```php
     * $promise->catch($onRejected);
     * ```
     *
     * @template TThrowable of \Throwable
     * @template TRejected
     * @param callable(TThrowable): (PromiseInterface<TRejected>|TRejected) $onRejected
     * @return PromiseInterface<T|TRejected>
     * @deprecated 3.0.0 Use catch() instead
     * @see self::catch()
     */
    public function otherwise(callable $onRejected): PromiseInterface;

    /**
     * [Deprecated] Allows you to execute "cleanup" type tasks in a promise chain.
     *
     * This method continues to exist only for BC reasons and to ease upgrading
     * between versions. It is an alias for:
     *
     * ```php
     * $promise->finally($onFulfilledOrRejected);
     * ```
     *
     * @param callable(): (void|PromiseInterface<void>) $onFulfilledOrRejected
     * @return PromiseInterface<T>
     * @deprecated 3.0.0 Use finally() instead
     * @see self::finally()
     */
    public function always(callable $onFulfilledOrRejected): PromiseInterface;
}
<?php

namespace React\Promise;

use React\Promise\Internal\RejectedPromise;

/**
 * @template T
 * @template-implements PromiseInterface<T>
 */
final class Promise implements PromiseInterface
{
    /** @var (callable(callable(T):void,callable(\Throwable):void):void)|null */
    private $canceller;

    /** @var ?PromiseInterface<T> */
    private $result;

    /** @var list<callable(PromiseInterface<T>):void> */
    private $handlers = [];

    /** @var int */
    private $requiredCancelRequests = 0;

    /** @var bool */
    private $cancelled = false;

    /**
     * @param callable(callable(T):void,callable(\Throwable):void):void $resolver
     * @param (callable(callable(T):void,callable(\Throwable):void):void)|null $canceller
     */
    public function __construct(callable $resolver, ?callable $canceller = null)
    {
        $this->canceller = $canceller;

        // Explicitly overwrite arguments with null values before invoking
        // resolver function. This ensure that these arguments do not show up
        // in the stack trace in PHP 7+ only.
        $cb = $resolver;
        $resolver = $canceller = null;
        $this->call($cb);
    }

    public function then(?callable $onFulfilled = null, ?callable $onRejected = null): PromiseInterface
    {
        if (null !== $this->result) {
            return $this->result->then($onFulfilled, $onRejected);
        }

        if (null === $this->canceller) {
            return new static($this->resolver($onFulfilled, $onRejected));
        }

        // This promise has a canceller, so we create a new child promise which
        // has a canceller that invokes the parent canceller if all other
        // followers are also cancelled. We keep a reference to this promise
        // instance for the static canceller function and clear this to avoid
        // keeping a cyclic reference between parent and follower.
        $parent = $this;
        ++$parent->requiredCancelRequests;

        return new static(
            $this->resolver($onFulfilled, $onRejected),
            static function () use (&$parent): void {
                assert($parent instanceof self);
                --$parent->requiredCancelRequests;

                if ($parent->requiredCancelRequests <= 0) {
                    $parent->cancel();
                }

                $parent = null;
            }
        );
    }

    /**
     * @template TThrowable of \Throwable
     * @template TRejected
     * @param callable(TThrowable): (PromiseInterface<TRejected>|TRejected) $onRejected
     * @return PromiseInterface<T|TRejected>
     */
    public function catch(callable $onRejected): PromiseInterface
    {
        return $this->then(null, static function (\Throwable $reason) use ($onRejected) {
            if (!_checkTypehint($onRejected, $reason)) {
                return new RejectedPromise($reason);
            }

            /**
             * @var callable(\Throwable):(PromiseInterface<TRejected>|TRejected) $onRejected
             */
            return $onRejected($reason);
        });
    }

    public function finally(callable $onFulfilledOrRejected): PromiseInterface
    {
        return $this->then(static function ($value) use ($onFulfilledOrRejected): PromiseInterface {
            /** @var T $value */
            return resolve($onFulfilledOrRejected())->then(function () use ($value) {
                return $value;
            });
        }, static function (\Throwable $reason) use ($onFulfilledOrRejected): PromiseInterface {
            return resolve($onFulfilledOrRejected())->then(function () use ($reason): RejectedPromise {
                return new RejectedPromise($reason);
            });
        });
    }

    public function cancel(): void
    {
        $this->cancelled = true;
        $canceller = $this->canceller;
        $this->canceller = null;

        $parentCanceller = null;

        if (null !== $this->result) {
            // Forward cancellation to rejected promise to avoid reporting unhandled rejection
            if ($this->result instanceof RejectedPromise) {
                $this->result->cancel();
            }

            // Go up the promise chain and reach the top most promise which is
            // itself not following another promise
            $root = $this->unwrap($this->result);

            // Return if the root promise is already resolved or a
            // FulfilledPromise or RejectedPromise
            if (!$root instanceof self || null !== $root->result) {
                return;
            }

            $root->requiredCancelRequests--;

            if ($root->requiredCancelRequests <= 0) {
                $parentCanceller = [$root, 'cancel'];
            }
        }

        if (null !== $canceller) {
            $this->call($canceller);
        }

        // For BC, we call the parent canceller after our own canceller
        if ($parentCanceller) {
            $parentCanceller();
        }
    }

    /**
     * @deprecated 3.0.0 Use `catch()` instead
     * @see self::catch()
     */
    public function otherwise(callable $onRejected): PromiseInterface
    {
        return $this->catch($onRejected);
    }

    /**
     * @deprecated 3.0.0 Use `finally()` instead
     * @see self::finally()
     */
    public function always(callable $onFulfilledOrRejected): PromiseInterface
    {
        return $this->finally($onFulfilledOrRejected);
    }

    private function resolver(?callable $onFulfilled = null, ?callable $onRejected = null): callable
    {
        return function (callable $resolve, callable $reject) use ($onFulfilled, $onRejected): void {
            $this->handlers[] = static function (PromiseInterface $promise) use ($onFulfilled, $onRejected, $resolve, $reject): void {
                $promise = $promise->then($onFulfilled, $onRejected);

                if ($promise instanceof self && $promise->result === null) {
                    $promise->handlers[] = static function (PromiseInterface $promise) use ($resolve, $reject): void {
                        $promise->then($resolve, $reject);
                    };
                } else {
                    $promise->then($resolve, $reject);
                }
            };
        };
    }

    private function reject(\Throwable $reason): void
    {
        if (null !== $this->result) {
            return;
        }

        $this->settle(reject($reason));
    }

    /**
     * @param PromiseInterface<T> $result
     */
    private function settle(PromiseInterface $result): void
    {
        $result = $this->unwrap($result);

        if ($result === $this) {
            $result = new RejectedPromise(
                new \LogicException('Cannot resolve a promise with itself.')
            );
        }

        if ($result instanceof self) {
            $result->requiredCancelRequests++;
        } else {
            // Unset canceller only when not following a pending promise
            $this->canceller = null;
        }

        $handlers = $this->handlers;

        $this->handlers = [];
        $this->result = $result;

        foreach ($handlers as $handler) {
            $handler($result);
        }

        // Forward cancellation to rejected promise to avoid reporting unhandled rejection
        if ($this->cancelled && $result instanceof RejectedPromise) {
            $result->cancel();
        }
    }

    /**
     * @param PromiseInterface<T> $promise
     * @return PromiseInterface<T>
     */
    private function unwrap(PromiseInterface $promise): PromiseInterface
    {
        while ($promise instanceof self && null !== $promise->result) {
            /** @var PromiseInterface<T> $promise */
            $promise = $promise->result;
        }

        return $promise;
    }

    /**
     * @param callable(callable(mixed):void,callable(\Throwable):void):void $cb
     */
    private function call(callable $cb): void
    {
        // Explicitly overwrite argument with null value. This ensure that this
        // argument does not show up in the stack trace in PHP 7+ only.
        $callback = $cb;
        $cb = null;

        // Use reflection to inspect number of arguments expected by this callback.
        // We did some careful benchmarking here: Using reflection to avoid unneeded
        // function arguments is actually faster than blindly passing them.
        // Also, this helps avoiding unnecessary function arguments in the call stack
        // if the callback creates an Exception (creating garbage cycles).
        if (\is_array($callback)) {
            $ref = new \ReflectionMethod($callback[0], $callback[1]);
        } elseif (\is_object($callback) && !$callback instanceof \Closure) {
            $ref = new \ReflectionMethod($callback, '__invoke');
        } else {
            assert($callback instanceof \Closure || \is_string($callback));
            $ref = new \ReflectionFunction($callback);
        }
        $args = $ref->getNumberOfParameters();

        try {
            if ($args === 0) {
                $callback();
            } else {
                // Keep references to this promise instance for the static resolve/reject functions.
                // By using static callbacks that are not bound to this instance
                // and passing the target promise instance by reference, we can
                // still execute its resolving logic and still clear this
                // reference when settling the promise. This helps avoiding
                // garbage cycles if any callback creates an Exception.
                // These assumptions are covered by the test suite, so if you ever feel like
                // refactoring this, go ahead, any alternative suggestions are welcome!
                $target =& $this;

                $callback(
                    static function ($value) use (&$target): void {
                        if ($target !== null) {
                            $target->settle(resolve($value));
                            $target = null;
                        }
                    },
                    static function (\Throwable $reason) use (&$target): void {
                        if ($target !== null) {
                            $target->reject($reason);
                            $target = null;
                        }
                    }
                );
            }
        } catch (\Throwable $e) {
            $target = null;
            $this->reject($e);
        }
    }
}
<?php

namespace React\Promise\Exception;

/**
 * Represents an exception that is a composite of one or more other exceptions.
 *
 * This exception is useful in situations where a promise must be rejected
 * with multiple exceptions. It is used for example to reject the returned
 * promise from `some()` and `any()` when too many input promises reject.
 */
class CompositeException extends \Exception
{
    /** @var \Throwable[] */
    private $throwables;

    /** @param \Throwable[] $throwables */
    public function __construct(array $throwables, string $message = '', int $code = 0, ?\Throwable $previous = null)
    {
        parent::__construct($message, $code, $previous);

        $this->throwables = $throwables;
    }

    /**
     * @return \Throwable[]
     */
    public function getThrowables(): array
    {
        return $this->throwables;
    }
}
<?php

namespace React\Promise\Exception;

class LengthException extends \LengthException
{
}
<?php

namespace React\Promise;

/**
 * @template T
 */
final class Deferred
{
    /**
     * @var PromiseInterface<T>
     */
    private $promise;

    /** @var callable(T):void */
    private $resolveCallback;

    /** @var callable(\Throwable):void */
    private $rejectCallback;

    /**
     * @param (callable(callable(T):void,callable(\Throwable):void):void)|null $canceller
     */
    public function __construct(?callable $canceller = null)
    {
        $this->promise = new Promise(function ($resolve, $reject): void {
            $this->resolveCallback = $resolve;
            $this->rejectCallback  = $reject;
        }, $canceller);
    }

    /**
     * @return PromiseInterface<T>
     */
    public function promise(): PromiseInterface
    {
        return $this->promise;
    }

    /**
     * @param T $value
     */
    public function resolve($value): void
    {
        ($this->resolveCallback)($value);
    }

    public function reject(\Throwable $reason): void
    {
        ($this->rejectCallback)($reason);
    }
}
<?php

if (!\function_exists('React\Promise\resolve')) {
    require __DIR__.'/functions.php';
}
`iCuliCS2h (VcPi;Pu0̵3z.ؚ.?+D   GBMB